Skip to content

C examples

The C sample programs are available in sample/c/ in the TrulyNatural installation directory.

See ~/Sensory/TrulyNaturalSDK/7.9.0-pre.0+19.ged1a5d37de/sample/c/

You can build the sample code with CMake or with GNU Make.

Examples

live-spot.c
Shows how to run a wake word recognizer on live audio captured from the default audio source. For a shorter teaching version you type yourself, see Your first program.
live-spot-stream.c
Runs a wake word recognizer on live audio captured using a custom audio stream, defined in alsa-stream.c.
live-segment.c
Runs a wake word recognizer on live audio, segments the speech following the wake word with a VAD, and then saves this audio snippet to a file.
push-audio.c
Runs a recognizer where the application pushes data through the recognition pipeline. Shows VAD audio processing for use with third-party recognizers such as keyword-to-search applications.
spot-data.c
Runs a small keyword spotter from code space. It uses a custom memory allocator to avoid calls to the system heap allocator, and reads audio data from code space to avoid file system use.
spot-data-stream.c
This example runs a wake word from code space with a custom audio stream, using pull mode processing with run. It is a reasonable starting point for running on a small device with an RTOS.
alsa-stream.c
Source for the fromAudioDevice Stream implementation for ALSA, used for live audio capture on Linux.
aqs-stream.c
Source for the fromAudioDevice Stream implementation for Audio Queue Services, used for live audio capture on macOS and iOS.
wmme-stream.c
Source for the fromAudioDevice Stream implementation for Windows Multimedia Extensions, used for live audio capture on Windows.
data-stream.c
This is the source for the fromAudioDevice Stream implementation for memory data, similar to fromMemory. It's used in the spot-data-stream.c example.
snsr-edit.c
Source for the snsr-edit command-line tool.
snsr-eval.c
Source for the snsr-eval command-line tool, and the snsr-eval-subset sample.
spot-convert.c
Source for the spot-convert command-line tool.
spot-enroll.c
Source for the spot-enroll command-line tool.
live-enroll.c
Source for the live-enroll command-line tool.

Build with CMake

Build the code samples with CMake on Linux, macOS, and Windows. This requires CMake 3.15 or later and a compiler toolchain.

Open a terminal window and enter the commands below.

cd ~/Sensory/TrulyNaturalSDK/7.9.0-pre.0+19.ged1a5d37de/sample/c
cmake -S . -B build-sample -DCMAKE_BUILD_TYPE=Release
cmake --build build-sample --parallel
cmake --install build-sample --prefix .

This installs the sample executables in the bin/ subdirectory.

The build type defaults to Release when none is selected. With a multi-configuration generator (for example Visual Studio, Xcode, or "Ninja Multi-Config"), CMAKE_BUILD_TYPE is ignored — choose the build type at build time instead with cmake --build build-sample --parallel --config Release. The built executables are also available directly under build-sample/bin/ (or build-sample/bin/<config>/ for multi-configuration generators).

CMakeLists.txt

These are the cmake configuration files used to build the sample code.

CMakeLists.txt
# Sensory Confidential
# Copyright (C)2024-2026 Sensory, Inc. https://sensory.com/
#
# TrulyNatural SDK sample code build configuration
#
# With a single-configuration generator (e.g. Unix Makefiles, the default),
# choose the build type at configure time:
#
# cmake -S . -B build-sample -DCMAKE_BUILD_TYPE=Release
# cmake --build build-sample --parallel
#
# With a multi-configuration generator (e.g. Visual Studio, Xcode, "Ninja
# Multi-Config"), choose the build type at build time instead (--config is
# ignored by single-configuration generators):
#
# cmake -S . -B build-sample -G Xcode
# cmake --build build-sample --parallel --config Release
#
# The build type defaults to Release when none is selected.
#
# The sample executables are written to build-sample/bin/ (single-configuration
# generators) or build-sample/bin/<config>/ (multi-configuration generators).

cmake_minimum_required(VERSION 3.15.0)

project(SnsrSamples LANGUAGES C CXX)

find_package(SnsrLibrary REQUIRED CONFIG
             PATHS "$ENV{HOME}/Sensory/TrulyNaturalSDK/7.9.0-pre.0+19.ged1a5d37de"
             NO_DEFAULT_PATH)

if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
  message(STATUS "Setting build type to 'Release' as none was specified.")
  set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build." FORCE)
endif()

add_subdirectory(src)
src/CMakeLists.txt
# Sensory Confidential
# Copyright (C)2024-2026 Sensory, Inc. https://sensory.com/
#
# This is not a stand-alone configuration. See ../CMakeLists.txt

# Locate the .snsr models via the SDK package.
set(MODEL_DIR ${SnsrLibrary_MODEL_DIR})
set(SRC_GEN ${PROJECT_BINARY_DIR}/src)
set(SPT_HBG spot-hbg-enUS-1.4.0-m)
set(TPL_VAD tpl-vad-lvcsr-3.17.0)

# Place built sample executables in build-sample/bin/ and install them under
# <CMAKE_INSTALL_PREFIX>/bin.
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin)

# Build, link, and install a sample executable in one step.
#   snsr_sample(<name> <link-target> <source>...)
function(snsr_sample name lib)
  add_executable(${name} ${ARGN})
  target_link_libraries(${name} ${lib})
  install(TARGETS ${name} RUNTIME DESTINATION bin)
endfunction()

# ===========================================================================
# Code generation
#
# snsr-edit converts .snsr models into C source. It is both an installed sample
# and the tool that generates the model sources used below, so it is defined
# first.
# ===========================================================================

snsr_sample(snsr-edit SnsrLibrary snsr-edit.c)

# Select the snsr-edit used to generate the model source files below. For a
# native build, use the tool we just built. When cross-compiling that binary
# targets the wrong architecture and cannot run on the host, so use the host
# build shipped with the SDK instead -- unless an emulator is configured, in
# which case the freshly built tool is used and CMake runs it through the
# emulator (it prepends CROSSCOMPILING_EMULATOR when COMMAND names a target).
if (CMAKE_CROSSCOMPILING AND NOT CMAKE_CROSSCOMPILING_EMULATOR)
  set(SNSR_EDIT ${SnsrLibrary_SNSR_EDIT_EXECUTABLE})
else ()
  set(SNSR_EDIT snsr-edit)
endif ()

# Generate model source files with snsr-edit. The output file name is derived
# from the model, so run in SRC_GEN and depend on both the tool and the input
# model.
add_custom_command(
  OUTPUT ${SRC_GEN}/${SPT_HBG}.c
  COMMAND ${SNSR_EDIT}
    -c spot_hbg_enUS
    -t ${MODEL_DIR}/${SPT_HBG}.snsr
  DEPENDS ${SNSR_EDIT} ${MODEL_DIR}/${SPT_HBG}.snsr
  WORKING_DIRECTORY ${SRC_GEN}
  COMMENT "Generating ${SPT_HBG}.c"
  VERBATIM
)

add_custom_command(
  OUTPUT ${SRC_GEN}/${TPL_VAD}.c
  COMMAND ${SNSR_EDIT}
    -c tpl_vad_lvcsr
    -t ${MODEL_DIR}/${TPL_VAD}.snsr
  DEPENDS ${SNSR_EDIT} ${MODEL_DIR}/${TPL_VAD}.snsr
  WORKING_DIRECTORY ${SRC_GEN}
  COMMENT "Generating ${TPL_VAD}.c"
  VERBATIM
)

add_custom_command(
  OUTPUT ${SRC_GEN}/snsr-custom-init.c
  COMMAND ${SNSR_EDIT}
    -it ${MODEL_DIR}/${SPT_HBG}.snsr
  DEPENDS ${SNSR_EDIT} ${MODEL_DIR}/${SPT_HBG}.snsr
  WORKING_DIRECTORY ${SRC_GEN}
  COMMENT "Generating snsr-custom-init.c"
  VERBATIM
)

# Compile each shared generated model source exactly once into an object
# library, so that every custom-command OUTPUT is the source of a single
# target. Listing a generated file directly as a source of multiple executables
# makes the Unix Makefiles generator give each one its own copy of the
# generation rule, and "cmake --build --parallel" then runs them concurrently,
# racing on the shared output file. The generated sources include <snsr.h>, so
# the object libraries need the SDK usage requirements (SnsrLibraryCommon
# provides the include path). snsr-custom-init.c has a single consumer, so it
# cannot race and is compiled directly into that target instead.
add_library(model-spt-hbg OBJECT ${SRC_GEN}/${SPT_HBG}.c)
target_link_libraries(model-spt-hbg PRIVATE SnsrLibraryCommon)

add_library(model-tpl-vad OBJECT ${SRC_GEN}/${TPL_VAD}.c)
target_link_libraries(model-tpl-vad PRIVATE SnsrLibraryCommon)

# ===========================================================================
# Sample executables
# ===========================================================================

# Live audio capture
snsr_sample(live-enroll  SnsrLibraryOmitOSS live-enroll.c)
snsr_sample(live-segment SnsrLibraryOmitOSS live-segment.c)
snsr_sample(live-spot    SnsrLibrary        live-spot.c)

if (APPLE)
  set(STREAM_SRC aqs-stream.c)
elseif (UNIX)
  set(STREAM_SRC alsa-stream.c)
elseif (WIN32)
  set(STREAM_SRC wmme-stream.c)
else ()
  message(FATAL_ERROR "live-spot-stream: no audio backend for this platform")
endif ()
snsr_sample(live-spot-stream SnsrLibrary live-spot-stream.c ${STREAM_SRC})

snsr_sample(push-audio SnsrLibrary push-audio.c)

# Model evaluation (links the generated model object libraries)
snsr_sample(snsr-eval SnsrLibrary snsr-eval.c)
target_link_libraries(snsr-eval model-tpl-vad)

snsr_sample(snsr-eval-subset SnsrLibrary
            snsr-eval.c ${SRC_GEN}/snsr-custom-init.c)
target_link_libraries(snsr-eval-subset model-tpl-vad)
target_compile_definitions(snsr-eval-subset PRIVATE SNSR_USE_SUBSET)

# Spotting
snsr_sample(spot-convert SnsrLibraryOmitOSS spot-convert.c)

snsr_sample(spot-data SnsrLibrary spot-data.c data.c)
target_link_libraries(spot-data model-spt-hbg)

snsr_sample(spot-data-stream SnsrLibrary spot-data-stream.c data-stream.c data.c)
target_link_libraries(spot-data-stream model-spt-hbg)

snsr_sample(spot-enroll SnsrLibraryOmitOSS spot-enroll.c)

Build with GNU Make

Build the code samples with GNU Make on Linux and macOS only. This requires GNU Make 3.81 or later and a compiler toolchain.

Open a terminal window and enter the commands below.

cd ~/Sensory/TrulyNaturalSDK/7.9.0-pre.0+19.ged1a5d37de/sample/c
make -j all

This installs the sample executables in the bin/ subdirectory.

If you run make without arguments the Makefile lists all available targets:

% make
Make targets:

  make all      # build all executables in ./bin
  make clean    # remove build artifacts
  make debug    # build all with debugging enabled
  make help     # display this help message
  make test     # run enrollment and spotting tests

Building for macos from SDK root directory
../..

Run the sample tests:

% make -j -s test
Running test-enroll-0.
Running test-enroll-1.
Running test-enroll-2.
Running test-enroll-3.
Running test-convert-0.
Running test-data-0.
Running test-data-1.
Running test-subset-0.
./build/out/dsp-pc38-3.4.0-op10-prod-net.bin: OK
./build/out/dsp-pc38-3.4.0-op10-prod-search.bin: OK
./build/out/dsp-search-check.h: OK

Say the enrollment phrase (1/4) for "armadillo-1"
Recording:   1.88 s

Say the enrollment phrase (2/4) for "armadillo-1"
Recording:   1.71 s

Say the enrollment phrase (3/4) for "armadillo-1" with context,
  for example: "<phrase> will it rain tomorrow?"
Recording:   4.02 s

Say the enrollment phrase (4/4) for "armadillo-1" with context,
  for example: "<phrase> will it rain tomorrow?"
Recording:   2.91 s
Running test-push-0.
Running test-push-1.
SUCCESS: All tests passed.

Makefile

This is the make configuration file used to build and test the sample code.

Makefile
# Sensory Confidential
# Copyright (C)2015-2026 Sensory, Inc. https://sensory.com/
#
# TrulyNatural SDK GNU make build script

SNSR_ROOT := ../..


SNSR_EDIT = $(BIN_DIR)/snsr-edit
# This Makefile is meant to run on the target platform.
# Uncomment the following line if cross-compiling instead.
# SNSR_EDIT = $(TOOL_DIR)/snsr-edit

# OS-specific compiler defaults
OS_NAME := $(shell uname -s)

ifeq ($(OS_NAME),Linux)
# Linux
ARCH_NAME := $(shell $(CC) -dumpmachine)
OS_CFLAGS := -O3 -fPIC -DNDEBUG
OS_CFLAGS += -Wall -Werror
OS_CFLAGS += -fdata-sections -ffunction-sections
OS_LIBS   := -lsnsr -lasound -lpthread -lm -ldl -lstdc++
OS_LDFLAGS+= -Wl,--gc-sections
STATSIZE  := stat -c %s

else ifeq ($(OS_NAME),Darwin)
# macOS
ARCH_NAME := macos
ARCH := $(shell uname -m)
XCODE := /Applications/Xcode.app/Contents/Developer
SYSROOT := $(XCODE)/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk
CC := $(XCODE)/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang
OS_ARCH   := -arch $(ARCH)
OS_CFLAGS := -O3 -fPIC -DNDEBUG
OS_CFLAGS += $(OS_ARCH)
OS_CFLAGS += -Wall -Werror
OS_CFLAGS += -isysroot $(SYSROOT)
OS_CFLAGS += -fdata-sections -ffunction-sections
OS_LDFLAGS+= -isysroot $(SYSROOT)
OS_LDFLAGS+= -dead_strip
OS_LDFLAGS+= $(OS_ARCH)
OS_LIBS   := -lsnsr -framework AudioToolbox -framework CoreFoundation
OS_LIBS   += -framework Foundation -framework Accelerate
OS_LIBS   += -lm -lstdc++
STATSIZE  := stat -f %z

else
$(error This operating system ($(OS_NAME)) is not supported)
endif

OS_CFLAGS  += -I$(SNSR_ROOT)/include
OS_LDFLAGS += -L$(SNSR_ROOT)/lib/$(ARCH_NAME)

TARGET_DIR := .
BIN_DIR    = $(TARGET_DIR)/bin
SRC_DIR    = $(TARGET_DIR)/src
OBJ_DIR    = $(BUILD_DIR)/obj
OUT_DIR    = $(BUILD_DIR)/out
BUILD_DIR  = $(TARGET_DIR)/build
TEST_DIR   = $(TARGET_DIR)/test

MODEL_DIR  = $(SNSR_ROOT)/model
DATA_DIR   = $(SNSR_ROOT)/data
TOOL_DIR   = $(SNSR_ROOT)/bin

# $(call audio-files,filename-prefix,index-list)
# e.g. $(call audio-files,armadillo-6-,0 1 2)
# returns a list of absolute paths to SDK enrollment test data
audio-files = $(addsuffix .wav,$(addprefix $(DATA_DIR)/enrollments/$1,$2))

TEST_DATA := $(call audio-files,armadillo-1-,0 1 2 3 4 5)
TEST_DATA += $(call audio-files,armadillo-6-,0 1 2 3 4 5)
TEST_DATA += $(call audio-files,jackalope-1-,0 1 2 3 4 5)
TEST_DATA += $(call audio-files,jackalope-4-,0 1 2 3 4 5)
TEST_DATA += $(call audio-files,terminator-2-,0 1 2 3 4 5)
TEST_DATA += $(call audio-files,terminator-6-,0 1 2 3 4 5)
TEST_DATA += $(call audio-files,armadillo-1-,0-c 1-c 2-c 3-c 4-c 5-c)
TEST_DATA += $(call audio-files,jackalope-1-,0-c 1-c 2-c 3-c 4-c 5-c)


UDT_MODEL   = $(MODEL_DIR)/udt-universal-3.67.1.0.snsr
UDT_MODEL_5 = $(MODEL_DIR)/udt-enUS-5.1.1.9.snsr
VTPL_MODEL  = $(MODEL_DIR)/tpl-spot-vad-3.13.0.snsr
HBG_MODEL_V = spot-hbg-enUS-1.4.0-m
HBG_MODEL   = $(MODEL_DIR)/$(HBG_MODEL_V).snsr
VG_MODEL    = $(MODEL_DIR)/spot-voicegenie-enUS-6.5.1-m.snsr
BASE_MODEL  = $(OUT_DIR)/enrolled-sv
VAD_MODEL_V = tpl-vad-lvcsr-3.17.0
VAD_MODEL   = $(MODEL_DIR)/$(VAD_MODEL_V).snsr


.PHONY: all clean debug help test
.PHONY: test-enroll-0 test-enroll-1 test-enroll-2 test-enroll-3
.PHONY: test-convert-0
.PHONY: test-push-0 test-push-1

define help
Make targets:

  make all      # build all executables in $(BIN_DIR)
  make clean    # remove build artifacts
  make debug    # build all with debugging enabled
  make help     # display this help message
  make test     # run enrollment and spotting tests

Building for $(ARCH_NAME) from SDK root directory
$(SNSR_ROOT)

endef

# Adjust test program verbosity
# Resolves to -v, unless make is run with the -s (silent) flag.
v = $(if $(findstring s,$(MAKEFLAGS)),,-v)

# Default target
help:; $(info $(help))

clean:
    rm -rf $(BIN_DIR) $(BUILD_DIR) $(OBJ_DIR) $(OUT_DIR) segmented-audio.wav
    rm -f $(SRC_DIR)/snsr-custom-init.c
    rm -f $(SRC_DIR)/$(HBG_MODEL_V).c $(SRC_DIR)/$(VAD_MODEL_V).c

debug: all
debug: CFLAGS=-O0 -g -UNDEBUG

test: test-enroll-0 test-enroll-1 test-enroll-2 test-enroll-3\
      test-convert-0 test-push-0 test-push-1 test-data-0 test-data-1\
      test-subset-0
    $(info SUCCESS: All tests passed.)

# End-to-end UDT enrollment test
test-enroll-0: $(BIN_DIR)/spot-enroll $(BIN_DIR)/snsr-eval | $(OUT_DIR)
    $(info Running $@.)
    $(BIN_DIR)/spot-enroll $v $v -t $(UDT_MODEL)\
      -o $(BASE_MODEL)-0.snsr\
      +armadillo-6 $(call audio-files,armadillo-6-,0 1 2 3)\
      +jackalope-4 $(call audio-files,jackalope-4-,0 1 2 3)\
      +terminator-2 $(call audio-files,terminator-2-,0 1 2 3)\
      +terminator-6 $(call audio-files,terminator-6-,0 1 2 3)\
      +armadillo-1 $(call audio-files,armadillo-1-,0 1)\
       -c $(call audio-files,armadillo-1-,0-c)\
       -c $(call audio-files,armadillo-1-,1-c)\
      +jackalope-1 $(call audio-files,jackalope-1-,0 1)\
       -c $(call audio-files,jackalope-1-,0-c)\
       -c $(call audio-files,jackalope-1-,1-c)
    $(BIN_DIR)/snsr-eval -t $(BASE_MODEL)-0.snsr $(TEST_DATA)\
      > $(OUT_DIR)/$@.txt
    diff $(OUT_DIR)/$@.txt $(TEST_DIR)/$@.txt\
      || (echo ERROR: $@ validation failed; exit 100)

# End-to-end UDT enrollment test, using adapted enrollment contexts
test-enroll-1: $(BIN_DIR)/spot-enroll $(BIN_DIR)/snsr-eval | $(OUT_DIR)
    $(info Running $@.)
    $(BIN_DIR)/spot-enroll $v -t $(UDT_MODEL)\
      -a $(OUT_DIR)/armadillo-6.snsr\
      -o $(OUT_DIR)/enrolled-armadillo-6.snsr\
      +armadillo-6 $(call audio-files,armadillo-6-,0 1 2 3)
    $(BIN_DIR)/spot-enroll $v -t $(UDT_MODEL)\
      -a $(OUT_DIR)/jackalope-4.snsr\
      -o $(OUT_DIR)/enrolled-jackalope-4.snsr\
      +jackalope-4 $(call audio-files,jackalope-4-,0 1 2 3)
    $(BIN_DIR)/spot-enroll $v -t $(UDT_MODEL)\
      -a $(OUT_DIR)/terminator-2.snsr\
      -o $(OUT_DIR)/enrolled-terminator-2.snsr\
      +terminator-2 $(call audio-files,terminator-2-,0 1 2 3)
    $(BIN_DIR)/spot-enroll $v -t $(UDT_MODEL)\
      -a $(OUT_DIR)/terminator-6.snsr\
      -o $(OUT_DIR)/enrolled-terminator-6.snsr\
      +terminator-6 $(call audio-files,terminator-6-,0 1 2 3)
    $(BIN_DIR)/spot-enroll $v -t $(UDT_MODEL)\
      -a $(OUT_DIR)/armadillo-1.snsr\
      -o $(OUT_DIR)/enrolled-armadillo.snsr\
      +armadillo-1 $(call audio-files,armadillo-1-,0 1)\
       -c $(call audio-files,armadillo-1-,0-c)\
       -c $(call audio-files,armadillo-1-,1-c)
    $(BIN_DIR)/spot-enroll $v -t $(UDT_MODEL)\
      -a $(OUT_DIR)/jackalope-1.snsr\
      -o $(OUT_DIR)/enrolled-jackalope-1.snsr\
      +jackalope-1 $(call audio-files,jackalope-1-,0 1)\
       -c $(call audio-files,jackalope-1-,0-c)\
       -c $(call audio-files,jackalope-1-,1-c)
    $(BIN_DIR)/spot-enroll $v -t $(UDT_MODEL)\
      -t $(OUT_DIR)/armadillo-6.snsr\
      -t $(OUT_DIR)/jackalope-4.snsr\
      -t $(OUT_DIR)/terminator-2.snsr\
      -t $(OUT_DIR)/terminator-6.snsr\
      -t $(OUT_DIR)/armadillo-1.snsr\
      -t $(OUT_DIR)/jackalope-1.snsr\
      -o $(BASE_MODEL)-1.snsr
    $(BIN_DIR)/snsr-eval -t $(BASE_MODEL)-1.snsr $(TEST_DATA)\
      > $(OUT_DIR)/$@.txt
    diff $(OUT_DIR)/$@.txt $(TEST_DIR)/$@.txt >/dev/null\
      || diff $(OUT_DIR)/$@.txt $(TEST_DIR)/$@-alt.txt >/dev/null\
      || (echo ERROR: $@ validation failed; exit 101)

# Live end-to-end UDT enrollment test.
test-enroll-2: $(BIN_DIR)/live-enroll $(BIN_DIR)/snsr-eval | $(OUT_DIR)
    $(info Running $@.)
    $(BIN_DIR)/live-enroll $v $v -t $(UDT_MODEL)\
      -o $(BASE_MODEL)-2.snsr\
      +armadillo-1 $(call audio-files,armadillo-1-,0 1 0-c 1-c)
    $(BIN_DIR)/snsr-eval -t $(BASE_MODEL)-2.snsr $(TEST_DATA)\
      > $(OUT_DIR)/$@.txt
    diff $(OUT_DIR)/$@.txt $(TEST_DIR)/$@.txt\
      || (echo ERROR: $@ validation failed; exit 102)

# Test old UDT model
test-enroll-3: $(BIN_DIR)/spot-enroll $(BIN_DIR)/snsr-eval | $(OUT_DIR)
    $(info Running $@.)
    $(BIN_DIR)/spot-enroll $v $v -t $(UDT_MODEL_5)\
      -o $(BASE_MODEL)-3.snsr\
      +armadillo-1 $(call audio-files,armadillo-1-,0 1 2 3)
    $(BIN_DIR)/snsr-eval -t $(BASE_MODEL)-3.snsr $(TEST_DATA)\
      > $(OUT_DIR)/$@.txt
    diff $(OUT_DIR)/$@.txt $(TEST_DIR)/$@.txt\
      || (echo ERROR: $@ validation failed; exit 103)

# Validate DSP conversion
test-convert-0: $(BIN_DIR)/spot-convert | $(OUT_DIR)
    $(info Running $@.)
    $(BIN_DIR)/spot-convert -t $(HBG_MODEL) -p $(OUT_DIR)/dsp pc38
    tail -10 $(OUT_DIR)/dsp-pc38-3.4.0-op10-prod-search.h\
      > $(OUT_DIR)/dsp-search-check.h
    shasum -c $(TEST_DIR)/dsp-checksum.txt

# Push audio samples instead of the default pull
# Uses test-enroll-0 models
test-push-0: test-enroll-0 $(BIN_DIR)/push-audio | $(OUT_DIR)
    $(info Running $@.)
    $(BIN_DIR)/push-audio $(BASE_MODEL)-0.snsr\
      $(call audio-files,jackalope-4-,0)\
      > $(OUT_DIR)/$@.txt
    diff $(OUT_DIR)/$@.txt $(TEST_DIR)/$@.txt\
      || (echo ERROR: $@ validation failed; exit 104)

# Push audio samples instead of the default pull
# Uses test-enroll-0 models and the tpl-spot-vad-*.snsr template
test-push-1: test-enroll-0 $(BIN_DIR)/push-audio $(SNSR_EDIT) | $(OUT_DIR)
    $(info Running $@.)
    $(SNSR_EDIT) -t $(VTPL_MODEL)\
      -f 0 $(BASE_MODEL)-0.snsr -o $(OUT_DIR)/spot-vad.snsr
    $(BIN_DIR)/push-audio $(OUT_DIR)/spot-vad.snsr\
      $(call audio-files,armadillo-1-,1-c)\
      > $(OUT_DIR)/$@.txt
    diff $(OUT_DIR)/$@.txt $(TEST_DIR)/$@.txt\
      || (echo ERROR: $@ validation failed; exit 105)

test-data-0: $(BIN_DIR)/spot-data | $(OUT_DIR)
    $(info Running $@.)
    $(BIN_DIR)/spot-data > $(OUT_DIR)/$@.txt
    diff $(OUT_DIR)/$@.txt $(TEST_DIR)/$@.txt\
      || (echo ERROR: $@ validation failed; exit 104)

test-data-1: $(BIN_DIR)/spot-data-stream | $(OUT_DIR)
    $(info Running $@.)
    $(BIN_DIR)/spot-data-stream > $(OUT_DIR)/$@.txt
    diff $(OUT_DIR)/$@.txt $(TEST_DIR)/$@.txt\
      || (echo ERROR: $@ validation failed; exit 104)

test-subset-0: $(BIN_DIR)/snsr-eval-subset $(BIN_DIR)/snsr-eval | $(OUT_DIR)
    $(info Running $@.)
    test $(shell $(STATSIZE) $(BIN_DIR)/snsr-eval-subset) -lt \
         $(shell $(STATSIZE) $(BIN_DIR)/snsr-eval) ||\
      (echo ERROR: $@ size validation failed; exit 105)
    $(BIN_DIR)/snsr-eval-subset -t $(HBG_MODEL) /dev/null ||\
      (echo ERROR: $@ validation failed; exit 106)
    $(BIN_DIR)/snsr-eval-subset -t $(VG_MODEL) /dev/null 2>&1 |\
      grep SNSR_USE_SUBSET >/dev/null ||\
      (echo ERROR: $@ validation failed; exit 107)

# Create a rule for building name from source, in $(BIN_DIR)
# $(call add-target-rule,name,source1.c source2.c ...)
add-target-rule = $(eval $(call emit-target-rule,$1,$2))
define emit-target-rule
all: $$(BIN_DIR)/$(strip $1)
$$(BIN_DIR)/$(strip $1): $$(addprefix $$(OBJ_DIR)/,$(2:.c=.o)) | $$(BIN_DIR)
    $$(CC) $$(OS_LDFLAGS) $$(LDFLAGS) -o $$@ $$^ $$(OS_LIBS) $$(LIBS)
endef

# Command-line application targets
$(call add-target-rule, spot-convert, spot-convert.c)
$(call add-target-rule, snsr-edit,    snsr-edit.c)
$(call add-target-rule, spot-enroll,  spot-enroll.c)
$(call add-target-rule, snsr-eval,    snsr-eval.c tpl-vad-lvcsr-3.17.0.c)
$(call add-target-rule, snsr-eval-subset,\
       snsr-eval-subset.c snsr-custom-init.c tpl-vad-lvcsr-3.17.0.c)
$(call add-target-rule, live-enroll,  live-enroll.c)
$(call add-target-rule, live-segment, live-segment.c)
$(call add-target-rule, live-spot,    live-spot.c)
$(call add-target-rule, push-audio,    push-audio.c)
$(call add-target-rule, spot-data,\
       spot-data.c spot-hbg-enUS-1.4.0-m.c data.c)
$(call add-target-rule, spot-data-stream,\
       spot-data-stream.c data-stream.c spot-hbg-enUS-1.4.0-m.c data.c)

ifeq ($(OS_NAME),Linux)
# The custom stream sample uses ALSA on Linux.
$(call add-target-rule, live-spot-stream, live-spot-stream.c alsa-stream.c)

else ifeq ($(OS_NAME),Darwin)
# The custom stream sample uses AQS on macOS.
$(call add-target-rule, live-spot-stream, live-spot-stream.c aqs-stream.c)
endif

# Build object files from C sources
$(OBJ_DIR)/%.o : $(SRC_DIR)/%.c  | $(OBJ_DIR)
    $(CC) -c $(OS_CFLAGS) $(CFLAGS) -o $@ $<

# spot-enroll doesn't use OSS modules
$(OBJ_DIR)/spot-enroll.o : $(SRC_DIR)/spot-enroll.c  | $(OBJ_DIR)
    $(CC) -DSNSR_OMIT_OSS_COMPONENTS -c $(OS_CFLAGS) $(CFLAGS) -o $@ $<

# Create $(SRC_DIR)/snsr-custom-init.c using snsr-edit,
# limit support to those modules needed for $(HBG_MODEL)
$(SRC_DIR)/snsr-custom-init.c: $(SNSR_EDIT)
    $(SNSR_EDIT) -o $@ -vit $(HBG_MODEL)

# Create $(SRC_DIR)/spot-hbg-enUS-*.c from the snsr model
$(SRC_DIR)/$(HBG_MODEL_V).c: $(SNSR_EDIT)
    $(SNSR_EDIT) -o $@ -c spot_hbg_enUS -vt $(HBG_MODEL)

# Create $(SRC_DIR)/tpl-vad-lvcsr-*.c from the snsr model
$(SRC_DIR)/$(VAD_MODEL_V).c: $(SNSR_EDIT)
    $(SNSR_EDIT) -o $@ -c tpl_vad_lvcsr -vt $(VAD_MODEL)

# Build snsr-eval-subset object files with -DSNSR_USE_SUBSET
$(OBJ_DIR)/snsr-eval-subset.o: $(SRC_DIR)/snsr-eval.c | $(OBJ_DIR)
    $(CC) -c  $(OS_CFLAGS) $(CFLAGS) -DSNSR_USE_SUBSET -o $@ $<

# Create output directories
$(BIN_DIR) $(BUILD_DIR) $(OBJ_DIR) $(OUT_DIR):
    mkdir -p $@