Skip to content

Your first program

If you followed Quick start, you already ran a wake word with 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); 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 page:

  1. Install the TrulyNatural SDK using the provided installer.
  2. Open a terminal.
  3. Add ~/Sensory/TrulyNaturalSDK/7.8.0-pre.2/bin to your PATH (optional for this page, but useful for tools).
  • 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.8.0-pre.2/model/spot-voicegenie-enUS-6.5.1-m.snsr.
  • JDK 17 or later and Gradle 8.0 or later (or use a Gradle wrapper).
  • A microphone (Java Audio capture via fromAudioDevice).
  • A phrase-spot .snsr file (same voice genie model path as the C tab).
  • Python 3.10 or later and uv (or pip install the platform wheel).
  • A microphone (Stream.from_audio_device() — see fromAudioDevice).
  • A phrase-spot .snsr file (same voice genie model path as the C tab).
  • 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.
  • 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.

The program

The program below mirrors the pull-mode recipe in API overview: create a Session, load a spotter model, attach live audio, register a ^result handler, call run, then release.

This is a teaching sketch (minimal error handling). The shipped C sample live-spot.c adds timing in the callback, full RC checks, and more. The Python sample live_audio.py adds a timed capture loop.

Create first-spot.c with:

#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;
}

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

  return 0;
}
  1. new — allocate an empty Session handle.
  2. load the .snsr model from disk (pass a phrase-spot .snsr file, as on the Quick start page).
  3. setStream attaches live PCM from the default capture device (fromAudioDevice).
  4. setHandler registers resultEvent on the ^result event (callback).
  5. run blocks until the handler returns STOP or an error RC.
  6. On any code other than STOP, print errorDetail to stderr (same pattern as live-spot.c). The handler propagates RC errors from getString.
  7. release frees the session and attached streams.

Create FirstSpot.java with:

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;
        });
    s.run(); // (5)!
    s.release(); // (6)!
  }
}
  1. new SnsrSession() — empty Session (new).
  2. load the .snsr model from the path in argv[0].
  3. setStream with fromAudioDevice on the method chain.
  4. setHandler on ^result; return STOP to end run.
  5. run — pull-mode loop until the handler stops.
  6. release the session.

Look up signatures under the Java tabs on Inference and I/O. The Java binding throws on failure instead of latched RC codes (see API overview § Language bindings).

Create first_spot.py with:

import sys

import snsr


def on_result(s: snsr.Session, _key: bytes) -> snsr.RC:
    phrase = s.get_string(snsr.RES_TEXT)
    print(f'Spotted "{phrase}".')
    return snsr.RC.STOP


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

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


if __name__ == "__main__":
    raise SystemExit(main())
  1. load the .snsr model from the path on the command line.
  2. set_stream attaches live PCM from the default capture device (fromAudioDevice, Stream.from_audio_device() in Python).
  3. setHandler (set_handler in Python) registers on_result on the ^result event; return STOP to end run.
  4. run — pull-mode loop until the handler stops.
  5. On failure the binding raises snsr.Error with detail in Error.message (no latched RC codes on the session). A handler returning STOP is normal completion, not an error.

Look up signatures under the Python tabs on Inference and I/O. The shipped sample live_audio.py at ~/Sensory/TrulyNaturalSDK/7.8.0-pre.2/sample/python/src/live_audio.py adds a timed capture loop and more diagnostics.

Uses the Java Session API — see Java tabs on Inference for method details. The excerpt is the core wake-word-only flow in 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 for Kotlin interop notes.

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();
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 throws IOException at runtime even though Kotlin does not require the try/catch; wrap it explicitly. See API overview § 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.

Calls the C API from Swift — look up snsrLoad, snsrSetStream, and related functions under the C tabs on Inference and I/O. There is no Swift Session wrapper; PhraseSpot uses a bridging header and runs run on a background queue.

// 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.

Build and run

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_minimum_required(VERSION 3.10)
    project(first-spot C)
    list(APPEND CMAKE_MODULE_PATH "$ENV{HOME}/Sensory/TrulyNaturalSDK/7.8.0-pre.2")
    include(SnsrLibrary)
    add_executable(first-spot first-spot.c)
    target_link_libraries(first-spot SnsrLibrary)
    

    This follows Integrate with your build (CMake is the recommended approach on Linux, macOS, and Windows). Using GNU Make only? See Integrate with your build § Make and C examples § Build with GNU Make.

  3. Configure and build:

    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 for toolchain notes.

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

    ./build/first-spot $HOME/Sensory/TrulyNaturalSDK/7.8.0-pre.2/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 with STOP).

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:

    rootProject.name = 'first-spot'
    
  3. Create build.gradle in the project root:

    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.8.0-pre.2/m2repository")
    def snsrRoot = snsrMavenRepo.parentFile
    
    repositories {
      maven {
        url = snsrMavenRepo.toURI()
      }
    }
    
    dependencies {
      implementation "com.sensory.speech.snsr:tnl:7.8.0-pre.2"
    }
    
    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.

  4. From the project root, configure and run:

    cd ~/first-spot-java
    gradle run --args="$HOME/Sensory/TrulyNaturalSDK/7.8.0-pre.2/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.

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.8.0-pre.2, pick the Sensory JAR under m2repository, and point java.library.path at the native library directory for your OS (see build-java). These commands expect FirstSpot.java in the current directory:

export SNSR_ROOT=$HOME/Sensory/TrulyNaturalSDK/7.8.0-pre.2
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).

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:

    [project]
    name = "first-spot"
    version = "0.1.0"
    requires-python = ">=3.10"
    dependencies = [
        "snsr==7.8.0a2",
    ]
    
    [[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.8.0-pre.2/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.

  3. Create a virtual environment and install the wheel:

    cd ~/first-spot-python
    uv venv
    uv sync
    
  4. Run the spotter. Say "voice genie" a few times:

    uv run first_spot.py $HOME/Sensory/TrulyNaturalSDK/7.8.0-pre.2/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 with STOP).

Try the sample (no code changes)

  1. Open ~/Sensory/TrulyNaturalSDK/7.8.0-pre.2/sample/android/snsr-debug/ in Android Studio or use the command line (see snsr-debug § Build).

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

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

    cd ~/Sensory/TrulyNaturalSDK/7.8.0-pre.2/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).

Integrate in your own app

Use the Program excerpt as a guide, then follow Integrate with your build § Android and the Android examples. A from-scratch Android walk-through is not duplicated here (manifest, AAR, assets, and UI threading are app-specific).

Try the sample (no code changes)

  1. Open ~/Sensory/TrulyNaturalSDK/7.8.0-pre.2/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.

    The project is already configured with SNSR_ROOT, bridging header, frameworks, and NSMicrophoneUsageDescription (see build-ios).

Integrate in your own app

Use the Program excerpt and C API reference tabs, then follow Integrate with your build § iOS and iOS examples. Creating a new Xcode target from scratch is integration work, not covered step-by-step on this page.

What you just did

  1. Created a Session with new.
  2. Loaded a wake-word model.
  3. Attached a live audio source.
  4. Registered a ^result callback handler.
  5. Ran pull-mode inference with run.
  6. Released the session.

Next steps

Platform Full sample Look up API under
C live-spot.c C tabs on Inference and I/O, …
Java evalUDT.java Java tabs on Inference and I/O
Python live_audio.py at ~/Sensory/TrulyNaturalSDK/7.8.0-pre.2/sample/python/src/ Python tabs on Inference and I/O
Android snsr-debug Java tabs + Android examples
iOS PhraseSpot C tabs + Integrate with your build § iOS

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 ( stt), use snsr-edit with a template such as tpl-opt-spot-vad-lvcsr 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 Instructions step 3 shows this with the full sample (VAD + STT after the wake word).