---
source_path: "getting-started/your-first-program.md"
canonical_url: "https://doc.sensory.com/tnl/7.8/getting-started/your-first-program/"
---

# Your first program

If you followed [Quick start](https://doc.sensory.com/tnl/7.8/getting-started/index.md#getting-started), you already ran a wake word with
[snsr-eval](https://doc.sensory.com/tnl/7.8/tools/snsr-eval.md#snsr-eval). This page shows the same Session API flow on each platform.

The **C** tab is a full from-scratch walk-through: a new directory, a short
program, CMake, and a run command. The **Java** tab does the same with Gradle in
a new directory. The **Python** tab does the same with [uv][] and the `snsr`
wheel from the SDK installer. **Android** and **iOS** show the same API steps,
then how to try them using the shipping sample projects. Android uses the Java
API (callable from [Kotlin][] — see [API overview § Kotlin on Android](https://doc.sensory.com/tnl/7.8/api/overview.md#kotlin-on-android));
iOS calls the **C API** from Swift via a [bridging header][] (there is no
separate Swift API).

## Prerequisites

You need the same SDK setup as on the [Quick start](https://doc.sensory.com/tnl/7.8/getting-started/index.md#getting-started) page:

1.  Install the TrulyNatural SDK using the provided installer.
2.  Open a terminal.
3.  Add _~/Sensory/TrulyNaturalSDK/7.9.0-pre.0/bin_ to your `PATH` (optional for this page, but useful for tools).

<!-- tab: c -->

**C/C++**

- A C compiler and [CMake][] 3.10 or later (Linux, macOS, or Windows).
- A microphone for live audio.
- A phrase-spot model file, for example the voice genie model at
  _$HOME/Sensory/TrulyNaturalSDK/7.9.0-pre.0/model/spot-voicegenie-enUS-6.5.1-m.snsr_.

<!-- /tab -->

<!-- tab: java -->

**Java**

- [JDK][] 17 or later and [Gradle][] 8.0 or later (or use a Gradle wrapper).
- A microphone ([Java Audio][] capture via [fromAudioDevice](https://doc.sensory.com/tnl/7.8/api/io.md#fromaudiodevice)).
- A phrase-spot `.snsr` file (same voice genie model path as the C tab).

<!-- /tab -->

<!-- tab: py -->

**Python**

- [Python][] 3.10 or later and [uv][] (or `pip install` the platform wheel).
- A microphone (`Stream.from_audio_device()` — see [fromAudioDevice](https://doc.sensory.com/tnl/7.8/api/io.md#fromaudiodevice)).
- A phrase-spot `.snsr` file (same voice genie model path as the C tab).

<!-- /tab -->

<!-- tab: android -->

**Android**

- [Android Studio][] or a command-line Android SDK install; device or emulator
  with microphone support.
- To try the sample on this page you do **not** need to change sample source —
  only build and run [snsr-debug](https://doc.sensory.com/tnl/7.8/api/sample/android/snsr-debug.md#snsr-debug).

<!-- /tab -->

<!-- tab: ios -->

**iOS**

- [Xcode][] on macOS and a device or simulator with microphone access.
- To try the sample on this page you do **not** need to change sample source —
  only open and run [PhraseSpot](https://doc.sensory.com/tnl/7.8/api/sample/ios/phrasespot.md#ios-ps).

<!-- /tab -->

## The program

The program below mirrors the pull-mode recipe in [API overview](https://doc.sensory.com/tnl/7.8/api/overview.md#api-overview): create a
[new](https://doc.sensory.com/tnl/7.8/api/inference.md#new) [Session](https://doc.sensory.com/tnl/7.8/api/inference.md#session), [load](https://doc.sensory.com/tnl/7.8/api/inference.md#load) a spotter model, attach live [audio](https://doc.sensory.com/tnl/7.8/api/setting-keys/runtime.md#-audio-pcm),
register a [^result](https://doc.sensory.com/tnl/7.8/api/setting-keys/events.md#result) handler, call [run](https://doc.sensory.com/tnl/7.8/api/inference.md#run), then [release](https://doc.sensory.com/tnl/7.8/api/heap.md#release).

This is a **teaching sketch** (minimal error handling). The shipped C sample
[live-spot.c](https://doc.sensory.com/tnl/7.8/api/sample/c/live-spot.md#live-spotc) adds timing in the callback, full [RC](https://doc.sensory.com/tnl/7.8/api/inference.md#rc) checks, and more. The
Python sample [live_audio.py](https://doc.sensory.com/tnl/7.8/api/sample/python/live_audio.md#live_audiopy) adds a timed capture loop.

<!-- tab: c -->

**C/C++**

Create _first-spot.c_ with:

```c
#include <snsr.h>
#include <stdio.h>
#include <stdlib.h>

static SnsrRC
resultEvent(SnsrSession s, const char *key, void *privateData)
{
  const char *phrase;
  SnsrRC r;

  r = snsrGetString(s, SNSR_RES_TEXT, &phrase);
  if (r != SNSR_RC_OK)
    return r;
  printf("Spotted \"%s\".\n", phrase);
  return SNSR_RC_STOP; // (1)!
}

int
main(int argc, char *argv[])
{
  SnsrRC r;
  SnsrSession s;

  if (argc != 2) {
    fprintf(stderr, "usage: %s spotter-model\n", argv[0]);
    return 1;
  }

  snsrNew(&s); // (2)!
  snsrLoad(s, snsrStreamFromFileName(argv[1], "r")); // (3)!
  snsrSetStream(s, SNSR_SOURCE_AUDIO_PCM,
                snsrStreamFromAudioDevice(SNSR_ST_AF_DEFAULT)); // (4)!
  snsrSetHandler(s, SNSR_RESULT_EVENT,
                 snsrCallback(resultEvent, NULL, NULL)); // (5)!
  r = snsrRun(s); // (6)!
  if (r != SNSR_RC_STOP)
    fprintf(stderr, "ERROR: %s\n", snsrErrorDetail(s)); // (7)!
  snsrRelease(s); // (8)!

  return 0;
}
```

1. Returning [STOP](https://doc.sensory.com/tnl/7.8/api/inference.md#rc_stop) from the handler ends [run](https://doc.sensory.com/tnl/7.8/api/inference.md#run); return [OK](https://doc.sensory.com/tnl/7.8/api/inference.md#rc_ok)
   to keep the pull loop running indefinitely.
2. [new](https://doc.sensory.com/tnl/7.8/api/inference.md#new) — allocate an empty [Session](https://doc.sensory.com/tnl/7.8/api/inference.md#session) handle.
3. [load](https://doc.sensory.com/tnl/7.8/api/inference.md#load) the `.snsr` model from disk (pass a phrase-spot `.snsr` file, as on the
   [Quick start](https://doc.sensory.com/tnl/7.8/getting-started/index.md#getting-started) page).
4. [setStream](https://doc.sensory.com/tnl/7.8/api/inference.md#setters) attaches live PCM from the default capture device ([fromAudioDevice](https://doc.sensory.com/tnl/7.8/api/io.md#fromaudiodevice)).
5. [setHandler](https://doc.sensory.com/tnl/7.8/api/inference.md#sethandler) registers `resultEvent` on the [^result](https://doc.sensory.com/tnl/7.8/api/setting-keys/events.md#result) event ([callback](https://doc.sensory.com/tnl/7.8/api/inference.md#callback)).
6. [run](https://doc.sensory.com/tnl/7.8/api/inference.md#run) blocks until the handler returns [STOP](https://doc.sensory.com/tnl/7.8/api/inference.md#rc_stop) or an error [RC](https://doc.sensory.com/tnl/7.8/api/inference.md#rc).
7. On any code other than [STOP](https://doc.sensory.com/tnl/7.8/api/inference.md#rc_stop), print [errorDetail](https://doc.sensory.com/tnl/7.8/api/inference.md#errordetail) to `stderr`
   (same pattern as [live-spot.c](https://doc.sensory.com/tnl/7.8/api/sample/c/live-spot.md#live-spotc)). The handler propagates [RC](https://doc.sensory.com/tnl/7.8/api/inference.md#rc) errors from
   [getString](https://doc.sensory.com/tnl/7.8/api/inference.md#getters).
8. [release](https://doc.sensory.com/tnl/7.8/api/heap.md#release) frees the session and attached streams.

<!-- /tab -->

<!-- tab: java -->

**Java**

Create _FirstSpot.java_ with:

```java
import com.sensory.speech.snsr.Snsr;
import com.sensory.speech.snsr.SnsrRC;
import com.sensory.speech.snsr.SnsrSession;
import com.sensory.speech.snsr.SnsrStream;

public class FirstSpot {
  public static void main(String[] argv) throws Exception {
    if (argv.length != 1) {
      System.err.println("usage: FirstSpot spotter-model");
      System.exit(1);
    }

    SnsrSession s = new SnsrSession(); // (1)!
    s.load(argv[0]) // (2)!
        .setStream(Snsr.SOURCE_AUDIO_PCM, SnsrStream.fromAudioDevice()) // (3)!
        .setHandler(Snsr.RESULT_EVENT, (ses, key) -> { // (4)!
          System.out.println("Spotted \"" + ses.getString(Snsr.RES_TEXT) + "\".");
          return SnsrRC.STOP; // (5)!
        });
    s.run(); // (6)!
    s.release(); // (7)!
  }
}
```

1. `new SnsrSession()` — empty [Session](https://doc.sensory.com/tnl/7.8/api/inference.md#session) ([new](https://doc.sensory.com/tnl/7.8/api/inference.md#new)).
2. [load](https://doc.sensory.com/tnl/7.8/api/inference.md#load) the `.snsr` model from the path in `argv[0]`.
3. [setStream](https://doc.sensory.com/tnl/7.8/api/inference.md#setters) with [fromAudioDevice](https://doc.sensory.com/tnl/7.8/api/io.md#fromaudiodevice) on the method chain.
4. [setHandler](https://doc.sensory.com/tnl/7.8/api/inference.md#sethandler) on [^result](https://doc.sensory.com/tnl/7.8/api/setting-keys/events.md#result).
5. Returning [STOP](https://doc.sensory.com/tnl/7.8/api/inference.md#rc_stop) from the handler ends [run](https://doc.sensory.com/tnl/7.8/api/inference.md#run); return [OK](https://doc.sensory.com/tnl/7.8/api/inference.md#rc_ok)
   to keep the pull loop running indefinitely.
6. [run](https://doc.sensory.com/tnl/7.8/api/inference.md#run) — pull-mode loop until the handler stops.
7. [release](https://doc.sensory.com/tnl/7.8/api/heap.md#release) the session.

Look up signatures under the **Java** tabs on [Inference](https://doc.sensory.com/tnl/7.8/api/inference.md#inference) and [I/O](https://doc.sensory.com/tnl/7.8/api/io.md#input-and-output).
The Java binding throws on failure instead of latched [RC](https://doc.sensory.com/tnl/7.8/api/inference.md#rc) codes (see
[API overview § Language bindings](https://doc.sensory.com/tnl/7.8/api/overview.md#api-overview)).

<!-- /tab -->

<!-- tab: py -->

**Python**

Create _first_spot.py_ with:

```python
import signal
import sys

import snsr

def on_result(s: snsr.Session, _key: str) -> snsr.RC | None:
    phrase = s.get_string(snsr.RES_TEXT)
    print(f'Spotted "{phrase}".')
    return snsr.RC.STOP  # (1)!

def main() -> int:
    if len(sys.argv) != 2:
        print(f"usage: {sys.argv[0]} spotter-model", file=sys.stderr)
        return 1

    signal.signal(signal.SIGINT, signal.SIG_DFL)  # (2)!

    try:
        with snsr.Session() as s:
            s.load(sys.argv[1])  # (3)!
            s.set_stream(
                snsr.SOURCE_AUDIO_PCM,
                snsr.Stream.from_audio_device(),
            )  # (4)!
            s.set_handler(snsr.RESULT_EVENT, on_result)  # (5)!
            s.run()  # (6)!
    except snsr.Error as e:
        print(f"ERROR: {e.message}", file=sys.stderr)  # (7)!
        return 1
    return 0

if __name__ == "__main__":
    raise SystemExit(main())
```

1. Returning [STOP](https://doc.sensory.com/tnl/7.8/api/inference.md#rc_stop) from the handler ends [run](https://doc.sensory.com/tnl/7.8/api/inference.md#run); return [OK](https://doc.sensory.com/tnl/7.8/api/inference.md#rc_ok)
   (or return nothing — treated the same as [OK](https://doc.sensory.com/tnl/7.8/api/inference.md#rc_ok)) to keep the pull loop
   running indefinitely.
2. Restore the default `SIGINT` handler before [run](https://doc.sensory.com/tnl/7.8/api/inference.md#run). Python installs its
   own handler for `^C`; while [run](https://doc.sensory.com/tnl/7.8/api/inference.md#run) blocks in native code that handler
   only raises `KeyboardInterrupt` after the call returns, so `^C` appears
   to do nothing. `signal.SIG_DFL` lets the process exit immediately.
3. [load](https://doc.sensory.com/tnl/7.8/api/inference.md#load) the `.snsr` model from the path on the command line.
4. [set_stream](https://doc.sensory.com/tnl/7.8/api/inference.md#setters) attaches live PCM from the default capture device
   ([fromAudioDevice](https://doc.sensory.com/tnl/7.8/api/io.md#fromaudiodevice), `Stream.from_audio_device()` in Python).
5. [setHandler](https://doc.sensory.com/tnl/7.8/api/inference.md#sethandler) (`set_handler` in Python) registers `on_result` on the
   [^result](https://doc.sensory.com/tnl/7.8/api/setting-keys/events.md#result) event.
6. [run](https://doc.sensory.com/tnl/7.8/api/inference.md#run) — pull-mode loop until the handler stops.
7. On failure the binding raises `snsr.Error` with detail in `Error.message`
   (no latched [RC](https://doc.sensory.com/tnl/7.8/api/inference.md#rc) codes on the session). A handler returning [STOP](https://doc.sensory.com/tnl/7.8/api/inference.md#rc_stop)
   is normal completion, not an error.

Look up signatures under the **Python** tabs on [Inference](https://doc.sensory.com/tnl/7.8/api/inference.md#inference) and
[I/O](https://doc.sensory.com/tnl/7.8/api/io.md#input-and-output). The shipped sample [live_audio.py](https://doc.sensory.com/tnl/7.8/api/sample/python/live_audio.md#live_audiopy) adds a timed
capture loop and more diagnostics.

<!-- /tab -->

<!-- tab: android -->

**Android**

Uses the **Java** Session API — see **Java** tabs on [Inference](https://doc.sensory.com/tnl/7.8/api/inference.md#inference) for method details.
The excerpt is the core wake-word-only flow in [snsr-debug](https://doc.sensory.com/tnl/7.8/api/sample/android/snsr-debug.md#snsr-debug); the shipping app
also uses a worker thread, UI, and optional debug logging. Both Java and
Kotlin call sites use the same classes from the AAR; see
[API overview § Kotlin on Android](https://doc.sensory.com/tnl/7.8/api/overview.md#kotlin-on-android) for Kotlin interop notes.

//// tab | Java code

```java
SnsrStream audio = SnsrStream.fromAudioDevice();
SnsrSession session = new SnsrSession();
session.load(assetToString(BuildConfig.TRIGGER_MODEL))
    .setStream(Snsr.SOURCE_AUDIO_PCM, audio)
    .setHandler(Snsr.RESULT_EVENT, this);
session.run();
```

////

//// tab | Kotlin code

```kotlin
import com.sensory.speech.snsr.Snsr
import com.sensory.speech.snsr.SnsrRC
import com.sensory.speech.snsr.SnsrSession
import com.sensory.speech.snsr.SnsrStream

val audio = SnsrStream.fromAudioDevice()
val session = SnsrSession()
session.load(assetToString(BuildConfig.TRIGGER_MODEL))
    .setStream(Snsr.SOURCE_AUDIO_PCM, audio)
    .setHandler(Snsr.RESULT_EVENT) { ses, _ ->
        println("Spotted \"${ses.getString(Snsr.RES_TEXT)}\".")
        SnsrRC.STOP
    }
session.run()
session.release()
```

[run](https://doc.sensory.com/tnl/7.8/api/inference.md#run) throws `IOException` at runtime even though Kotlin does not require
the `try`/`catch`; wrap it explicitly. See
[API overview § Kotlin on Android](https://doc.sensory.com/tnl/7.8/api/overview.md#kotlin-on-android) for SAM interop,
threading, and `release()` (not `close()`) details.

////

When you integrate into your own app, add `RECORD_AUDIO` to _AndroidManifest.xml_,
use `SnsrStream.fromAudioDevice(int device, int sampleRate)` if you need a specific
[MediaRecorder.AudioSource][], and follow [Integrate with your build § Android](https://doc.sensory.com/tnl/7.8/api/build-system.md#build-android).

<!-- /tab -->

<!-- tab: ios -->

**iOS**

Calls the **C API** from Swift — look up `snsrLoad`, `snsrSetStream`, and related
functions under the **C** tabs on [Inference](https://doc.sensory.com/tnl/7.8/api/inference.md#inference) and [I/O](https://doc.sensory.com/tnl/7.8/api/io.md#input-and-output). There is
no Swift Session wrapper; [PhraseSpot](https://doc.sensory.com/tnl/7.8/api/sample/ios/phrasespot.md#ios-ps) uses a [bridging header][] and
runs [run](https://doc.sensory.com/tnl/7.8/api/inference.md#run) on a background queue.

```swift
// Excerpt — see PhraseSpot.swift for load(), UI, and AVAudioSession setup.
snsrLoad(session, snsrStreamFromFileName(modelPath(modelName), "r"))
snsrSetStream(session, SNSR_SOURCE_AUDIO_PCM, snsrStreamFromDefaultAudioDevice())
snsrSetHandler(session, SNSR_RESULT_EVENT, resultCallback)
// snsrRun(session) — invoked on a background thread in the sample app.
```

<!-- /tab -->

## Build and run

<!-- tab: c -->

**C/C++**

Create a new directory anywhere (not under the SDK sample tree). The steps below
assume _~/first-spot/_.

1.  Save the program above as _first-spot.c_ in that directory.

2.  Create _CMakeLists.txt_ in the same directory:

    ```cmake
    cmake_minimum_required(VERSION 3.10)
    project(first-spot C)
    list(APPEND CMAKE_MODULE_PATH "$ENV{HOME}/Sensory/TrulyNaturalSDK/7.9.0-pre.0")
    include(SnsrLibrary)
    add_executable(first-spot first-spot.c)
    target_link_libraries(first-spot SnsrLibrary)
    ```

    This follows [Integrate with your build](https://doc.sensory.com/tnl/7.8/api/build-system.md#build-cmake) (CMake is the recommended
    approach on Linux, macOS, and Windows). Using GNU Make only? See
    [Integrate with your build § Make](https://doc.sensory.com/tnl/7.8/api/build-system.md#build-make) and
    [C examples § Build with GNU Make](https://doc.sensory.com/tnl/7.8/api/sample/c/index.md#examples-make).

3.  Configure and build:

    ```console
    cd ~/first-spot
    cmake -S . -B build
    cmake --build build
    ```

    On Windows with Visual Studio generators, the executable may be under
    `build\Release\`. See [Integrate with your build](https://doc.sensory.com/tnl/7.8/api/build-system.md#build-cmake) for toolchain notes.

4.  Run the spotter. Say "voice genie" a few times:

    ```console
    ./build/first-spot $HOME/Sensory/TrulyNaturalSDK/7.9.0-pre.0/model/spot-voicegenie-enUS-6.5.1-m.snsr
    Spotted "voicegenie".
    ```

    Press `^C` if the process does not exit after a spot (your handler should stop
    [run](https://doc.sensory.com/tnl/7.8/api/inference.md#run) with [STOP](https://doc.sensory.com/tnl/7.8/api/inference.md#rc_stop)).

<!-- /tab -->

<!-- tab: java -->

**Java**

Create a new directory anywhere (not under the SDK sample tree). The steps below
assume _~/first-spot-java/_.

1.  Create the standard Gradle source layout and save _FirstSpot.java_ from the
    **Program** tab at _src/main/java/FirstSpot.java_ (default package).

2.  Create _settings.gradle_ in the project root:

    ```gradle
    rootProject.name = 'first-spot'
    ```

3.  Create _build.gradle_ in the project root:

    ```gradle
    plugins {
      id 'java'
      id 'application'
    }

    // SDK root is the parent of m2repository (same layout as install.m2repo).
    def snsrMavenRepo = file("${System.getProperty('user.home')}/Sensory/TrulyNaturalSDK/7.9.0-pre.0/m2repository")
    def snsrRoot = snsrMavenRepo.parentFile

    repositories {
      maven {
        url = snsrMavenRepo.toURI()
      }
    }

    dependencies {
      implementation "com.sensory.speech.snsr:tnl:7.9.0-pre.0"
    }

    application {
      mainClass = 'FirstSpot'
    }

    def snsrNativeLib = {
      def os = System.getProperty('os.name').toLowerCase(java.util.Locale.ROOT)
      if (os.contains('mac')) {
        return new File(snsrRoot, 'lib/macos')
      }
      if (os.contains('linux')) {
        def machine = ['gcc', '-dumpmachine'].execute().text.trim()
        return new File(snsrRoot, "lib/${machine}")
      }
      if (os.contains('windows')) {
        return new File(snsrRoot, 'lib/x86_64-windows-msvc')
      }
      throw new GradleException(
        'Set -Djava.library.path on the run task for your platform (see build-java).')
    }()

    tasks.named('run') {
      jvmArgs "-Djava.library.path=${snsrNativeLib.absolutePath}"
    }
    ```

    Coordinates and JNI layout are described in [Integrate with your build § Java](https://doc.sensory.com/tnl/7.8/api/build-system.md#build-java).

4.  From the project root, configure and run:

    ```console
    cd ~/first-spot-java
    gradle run --args="$HOME/Sensory/TrulyNaturalSDK/7.9.0-pre.0/model/spot-voicegenie-enUS-6.5.1-m.snsr"
    ```

    Gradle compiles everything under _src/main/java/_. If you see
    `ClassNotFoundException: FirstSpot`, check that _FirstSpot.java_ is in that
    folder, not the project root. If you see `UnsatisfiedLinkError: no SnsrJNI`,
    confirm the Maven URL ends in _m2repository_ so `snsrRoot` resolves to the SDK
    install (native libraries live in _lib/macos_ on macOS).

5.  Say "voice genie". Expect output similar to the C tab.

### Details: Without Gradle (`javac` / `java`)

On Linux or macOS you can compile and run without a Gradle project. Set `SNSR_ROOT`
to _$HOME/Sensory/TrulyNaturalSDK/7.9.0-pre.0_, pick the Sensory JAR under `m2repository`, and point
`java.library.path` at the native library directory for your OS (see
[build-java](https://doc.sensory.com/tnl/7.8/api/build-system.md#build-java)). These commands expect _FirstSpot.java_ in the current
directory:

```console
export SNSR_ROOT=$HOME/Sensory/TrulyNaturalSDK/7.9.0-pre.0
SNSR_JAR=$(find "$SNSR_ROOT/m2repository" -name 'tnl-*.jar' \
  ! -name '*-sources.jar' -print -quit)
javac -cp "$SNSR_JAR" FirstSpot.java
# macOS example:
java -Djava.library.path="$SNSR_ROOT/lib/macos" \
  -cp ".:$SNSR_JAR" FirstSpot \
  "$SNSR_ROOT/model/spot-voicegenie-enUS-6.5.1-m.snsr"
```

On Linux, replace `lib/macos` with `lib/$(gcc -dumpmachine)`.

<!-- /tab -->

<!-- tab: py -->

**Python**

Create a new directory anywhere (not under the SDK sample tree). The steps below
assume _~/first-spot-python/_.

1.  Save the program above as _first_spot.py_ in that directory.

2.  Create _pyproject.toml_ in the same directory:

    ```toml
    [project]
    name = "first-spot"
    version = "0.1.0"
    requires-python = ">=3.10"
    dependencies = [
        "snsr==7.9.0a0",
    ]

    [[tool.uv.index]]
    name = "snsr-local"

    url = "/path/to/sdk/lib/python"

    explicit = true
    format = "flat"

    [tool.uv.sources]
    snsr = { index = "snsr-local" }
    ```

    This project lives outside the SDK tree, so set `url` to the absolute path of
    _$HOME/Sensory/TrulyNaturalSDK/7.9.0-pre.0/lib/python_ (expand `$HOME` in your shell and paste
    the result into _pyproject.toml_). uv does not expand `~` or `$HOME` in TOML.

    Wheel layout and the flat index are described in
    [Integrate with your build § Python](https://doc.sensory.com/tnl/7.8/api/build-system.md#build-python).

3.  Create a virtual environment and install the wheel:

    ```console
    cd ~/first-spot-python
    uv venv
    uv sync
    ```

4.  Run the spotter. Say "voice genie" a few times:

    ```console
    uv run first_spot.py $HOME/Sensory/TrulyNaturalSDK/7.9.0-pre.0/model/spot-voicegenie-enUS-6.5.1-m.snsr
    Spotted "voicegenie".
    ```

    Press `^C` to stop while [run](https://doc.sensory.com/tnl/7.8/api/inference.md#run) is waiting for audio (step 2 above
    restores the default `SIGINT` handler so `^C` can terminate the process).
    After a spot, [run](https://doc.sensory.com/tnl/7.8/api/inference.md#run) exits when the handler returns [STOP](https://doc.sensory.com/tnl/7.8/api/inference.md#rc_stop).

<!-- /tab -->

<!-- tab: android -->

**Android**

### Try the sample (no code changes)

1.  Open _~/Sensory/TrulyNaturalSDK/7.9.0-pre.0/sample/android/snsr-debug/_ in [Android Studio][] or use the
    command line (see [snsr-debug § Build](https://doc.sensory.com/tnl/7.8/api/sample/android/snsr-debug.md#sd-build)).

2.  Connect a device or start an emulator with microphone support.

3.  Build and install (no edits to sample sources):

    ```console
    cd ~/Sensory/TrulyNaturalSDK/7.9.0-pre.0/sample/android/snsr-debug
    ./gradlew installDebug
    ```

    In Android Studio, press **Run** instead of the command above.

4.  Launch **SnsrDebug**, choose **Wakeword**, press **TALK**, and say the trigger
    phrase (see [snsr-debug § Run](https://doc.sensory.com/tnl/7.8/api/sample/android/snsr-debug.md#sd-run)).

### Integrate in your own app

Use the **Program** excerpt as a guide, then follow [Integrate with your build § Android](https://doc.sensory.com/tnl/7.8/api/build-system.md#build-android)
and the [Android examples](https://doc.sensory.com/tnl/7.8/api/sample/android/index.md#android-examples). A from-scratch Android walk-through is not duplicated
here (manifest, AAR, assets, and UI threading are app-specific).

<!-- /tab -->

<!-- tab: ios -->

**iOS**

### Try the sample (no code changes)

1.  Open _~/Sensory/TrulyNaturalSDK/7.9.0-pre.0/sample/ios/PhraseSpot/PhraseSpot.xcodeproj_ in [Xcode][].

2.  Choose **Product → Run** on a device or simulator. Grant microphone access when
    prompted. Say the trigger phrase from
    [PhraseSpot § Instructions](https://doc.sensory.com/tnl/7.8/api/sample/ios/phrasespot.md#ios-ps-instructions).

    The project is already configured with `SNSR_ROOT`, bridging header, frameworks,
    and `NSMicrophoneUsageDescription` (see [build-ios](https://doc.sensory.com/tnl/7.8/api/build-system.md#build-ios)).

### Integrate in your own app

Use the **Program** excerpt and **C** API reference tabs, then follow
[Integrate with your build § iOS](https://doc.sensory.com/tnl/7.8/api/build-system.md#build-ios) and [iOS examples](https://doc.sensory.com/tnl/7.8/api/sample/ios/index.md#ios-examples). Creating a new
Xcode target from scratch is integration work, not covered step-by-step on this page.

<!-- /tab -->

## What you just did

1. Created a [Session](https://doc.sensory.com/tnl/7.8/api/inference.md#session) with [new](https://doc.sensory.com/tnl/7.8/api/inference.md#new).
2. [Loaded](https://doc.sensory.com/tnl/7.8/api/inference.md#load) a wake-word model.
3. Attached a live [audio](https://doc.sensory.com/tnl/7.8/api/setting-keys/runtime.md#-audio-pcm) source.
4. Registered a [^result](https://doc.sensory.com/tnl/7.8/api/setting-keys/events.md#result) [callback](https://doc.sensory.com/tnl/7.8/api/inference.md#callback) handler.
5. Ran pull-mode inference with [run](https://doc.sensory.com/tnl/7.8/api/inference.md#run).
6. [Released](https://doc.sensory.com/tnl/7.8/api/heap.md#release) the session.

## Next steps

| Platform | Full sample | Look up API under |
|----------|-------------|-------------------|
| C | [live-spot.c](https://doc.sensory.com/tnl/7.8/api/sample/c/live-spot.md#live-spotc) | C tabs on [Inference](https://doc.sensory.com/tnl/7.8/api/inference.md#inference) and [I/O](https://doc.sensory.com/tnl/7.8/api/io.md#input-and-output), … |
| Java | [evalUDT.java](https://doc.sensory.com/tnl/7.8/api/sample/java/evalUDT.md#evaludtjava) | Java tabs on [Inference](https://doc.sensory.com/tnl/7.8/api/inference.md#inference) and [I/O](https://doc.sensory.com/tnl/7.8/api/io.md#input-and-output) |
| Python | [live_audio.py](https://doc.sensory.com/tnl/7.8/api/sample/python/live_audio.md#live_audiopy) | Python tabs on [Inference](https://doc.sensory.com/tnl/7.8/api/inference.md#inference) and [I/O](https://doc.sensory.com/tnl/7.8/api/io.md#input-and-output) |
| Android | [snsr-debug](https://doc.sensory.com/tnl/7.8/api/sample/android/snsr-debug.md#snsr-debug) | Java tabs + [Android examples](https://doc.sensory.com/tnl/7.8/api/sample/android/index.md#android-examples) |
| iOS | [PhraseSpot](https://doc.sensory.com/tnl/7.8/api/sample/ios/phrasespot.md#ios-ps) | **C** tabs + [Integrate with your build § iOS](https://doc.sensory.com/tnl/7.8/api/build-system.md#build-ios) |

- [C examples](https://doc.sensory.com/tnl/7.8/api/sample/c/index.md#c-examples), [Java examples](https://doc.sensory.com/tnl/7.8/api/sample/java/index.md#java-examples), [Python examples](https://doc.sensory.com/tnl/7.8/api/sample/python/index.md#python-examples), [Android examples](https://doc.sensory.com/tnl/7.8/api/sample/android/index.md#android-examples),
  [iOS examples](https://doc.sensory.com/tnl/7.8/api/sample/ios/index.md#ios-examples) — full samples, tests, and platform-specific build files.
- [Inference](https://doc.sensory.com/tnl/7.8/api/inference.md#inference) quick reference — common [Session](https://doc.sensory.com/tnl/7.8/api/inference.md#session) operations.
- [FAQ § Recipes](https://doc.sensory.com/tnl/7.8/faq.md#push-audio-recipe) — push audio, capture speech after the wake
  word, and UI-thread callbacks (one screen each).

### Same program, composed models

You do not need to change `first-spot.c` to run a multi-stage pipeline—only the
model path on the command line. On [Quick start § Speech To Text](https://doc.sensory.com/tnl/7.8/getting-started/index.md#qs-stt),
use [snsr-edit](https://doc.sensory.com/tnl/7.8/tools/snsr-edit.md#snsr-edit) with a template such as
[tpl-opt-spot-vad-lvcsr](https://doc.sensory.com/tnl/7.8/models/tpl/tpl-opt-spot-vad-lvcsr.md#tpl-opt-spot-vad-lvcsr-type) to build a wake-word-gated
STT `.snsr` file, then run `./build/first-spot my-pipeline.snsr`. The same
Session code loads any valid task file. [live-spot.c](https://doc.sensory.com/tnl/7.8/api/sample/c/live-spot.md#live-spotc) *Instructions* step 3
shows this with the full sample (VAD + STT after the wake word).

<!-- Reference definitions from includes/links.md -->
[Android Studio]: https://developer.android.com/studio/index.html "The official IDE for Android app development"
[bridging header]: https://developer.apple.com/documentation/swift/importing-objective-c-into-swift "Importing Objective-C into Swift"
[CMake]: https://cmake.org/ "CMake: A Powerful Software Build System"
[Gradle]: https://gradle.org/install/ "Gradle Build Tool installation"
[Java Audio]: https://docs.oracle.com/javase/tutorial/sound/capturing.html "Audio capturing in Java"
[jdk]: https://adoptium.net "Java Development Kit"
[Kotlin]: https://kotlinlang.org/ "Kotlin programming language"
[MediaRecorder.AudioSource]: https://developer.android.com/reference/android/media/MediaRecorder.AudioSource "Android MediaRecorder.AudioSource"
[Python]: https://en.wikipedia.org/wiki/Python_(programming_language) "Python programming language"
[uv]: https://docs.astral.sh/uv/ "uv Python package and project manager"
[Xcode]: https://developer.apple.com/xcode/ "Xcode enables you to develop, test, and distribute apps for all Apple platforms"

<!-- Abbreviation definitions from includes/abbreviations.md -->
*[API]: Application Programming Interface
*[LVCSR]: Large Vocabulary Continuous Speech Recognition model, feed-forward neural net acoustic model with FST decoder
*[SDK]: Software Development Kit
*[STT]: Speech To Text: transformers with language model and CTC decoding
*[TNL]: TrulyNatural, Sensory's large-vocabulary speech recognition technology
*[VAD]: Voice Activity Detector
