VoxScribe
Local speech recognition from Unity audio.
VoxScribe prepares AudioClip samples for a local native whisper binding. Its recognizer exposes microphone-oriented lifecycle methods, full-clip transcription and UnityEvents for partial and final text.
Overview
VoxScribe is for offline or local speech workflows that start with microphone input or an existing Unity AudioClip. The source converts samples into a mono representation and invokes native whisper-style functions through P/Invoke.
The recognizer has OnPartialText and OnFinalText events for Unity integration, while the included sample registers listeners and starts microphone listening. The shown recognizer implementation does not currently invoke those events itself.
Requirements
- Target Unity baseline: 2021.3 LTS and newer.
- A native whisper library is required for the native path. The source declares
whisperimports and comments that the target bundle provides the library. WhisperNative.IsNativeAvailablereturns true only for Windows, macOS and Linux editor/player runtime platforms in this source.- Microphone capture needs platform microphone permission. Planned site platform messaging is broader than this native availability check.
- Mono/IL2CPP builds need native plugin import settings and native binding validation; this prototype does not include a link.xml or stripping configuration.
Setup
- Copy the plugin folder into
Assets/Denisitree/VoxScribe/. - Place the required native library under the project's Unity native plugin location appropriate to the target.
- Place the model file at the path assigned to
VoxScribeRecognizer.modelPath; the source default isAssets/Models/ggml-base.en.bin. - Add
VoxScribeRecognizerto a GameObject, assign the model and language fields, and register listeners for the declared UnityEvents.
Quick start
This is the complete listener and lifecycle shape from VoxScribeDemo.
recognizer = GetComponent<VoxScribeRecognizer>();
recognizer.OnPartialText.AddListener(text => Debug.Log("Partial: " + text));
recognizer.OnFinalText.AddListener(text => Debug.Log("Final: " + text));
recognizer.StartListening();- Add the recognizer component.
- Register listeners.
- Call
StartListening. - Call
StopListeningduring teardown.
Core concepts
Native boundary
WhisperNative declares native functions for model initialization, inference, segment text and cleanup. CreateContext validates a path and wraps the returned pointer in WhisperContext.
Audio normalization
TranscribeAsync reads interleaved clip samples and passes them through AudioResampler.ToMono16KHz. The implementation averages channel values but does not use sampleRate to resample time; treat the method as the source's current channel-folding helper.
Inference thread
Full inference is wrapped in Task.Run, and the result is returned as a string. MainThreadDispatcher provides a queued callback mechanism, but the recognizer source does not call it.
Microphone windows
MicrophoneStreamer owns a queue, clears it on Start and Stop, and returns fixed-size windows only when enough samples are present. The current source does not add microphone samples to that queue.
API reference
VoxScribeRecognizer
| Name | Type | Description | Default |
|---|---|---|---|
| modelPath | string | Path passed to native context creation. | Assets/Models/ggml-base.en.bin |
| language | string | Language value passed to inference. | auto |
| translateToEnglish | bool | Translation flag passed to inference. | false |
| OnPartialText | UnityEvent<string> | Declared partial-text event. | null |
| OnFinalText | UnityEvent<string> | Declared final-text event. | null |
| StartListening | void | Creates the native wrapper and starts the microphone streamer. | - |
| StopListening | void | Stops the microphone streamer when present. | - |
| TranscribeAsync | Task<string> | Reads an AudioClip, folds channels and runs full native inference. | - |
public string modelPath = "Assets/Models/ggml-base.en.bin";
public string language = "auto";
public bool translateToEnglish;
public UnityEvent<string> OnPartialText;
public UnityEvent<string> OnFinalText;
public void StartListening()
public void StopListening()
public async Task<string> TranscribeAsync(AudioClip clip)
WhisperNative and WhisperContext
| Name | Type | Description | Default |
|---|---|---|---|
| handle | IntPtr | Native context pointer field. | zero value |
| IsNativeAvailable | static bool | Checks the supported desktop runtime platforms. | - |
| CreateContext | WhisperContext | Initializes a native model context from a path. | - |
| RunFullInference | string | Runs native inference at the hard-coded 16000 sample rate and four threads. | - |
public struct WhisperContext { public IntPtr handle; }
static bool IsNativeAvailable()
WhisperContext CreateContext(string modelPath)
string RunFullInference(WhisperContext context, float[] samples, string language, bool translateToEnglish)
AudioResampler
| Name | Type | Description | Default |
|---|---|---|---|
| ToMono16KHz | static float[] | Averages interleaved channels into a mono array. The source does not perform time resampling from sampleRate. | - |
static float[] ToMono16KHz(float[] samples, int channels, int sampleRate)
MicrophoneStreamer
| Name | Type | Description | Default |
|---|---|---|---|
| Start | void | Clears the sample queue. | - |
| Stop | void | Clears the sample queue. | - |
| CaptureWindow | float[] | Returns a 16000-value window when enough samples exist and applies the source overlap dequeue. | - |
void Start()
void Stop()
float[] CaptureWindow()
MainThreadDispatcher
| Name | Type | Description | Default |
|---|---|---|---|
| Post | static void | Queues a non-null Action under a lock. | - |
| Update | static void | Invokes all queued actions under the same lock. | - |
static void Post(Action action)
static void Update()VoxScribeDemo
This public sample class has no public fields, properties, methods or events. Its private Start method registers listeners and starts listening; its private OnDestroy method stops listening.
Recipes
Live subtitles shape
Use the sample's event listener pattern for UI text. The current recognizer declares the events, but the source does not invoke them, so actual live partial subtitles are planned rather than available.
Transcribe an AudioClip
Call TranscribeAsync(clip) from an async method and use the returned string after native availability and model initialization succeed.
Control microphone lifecycle
Call StartListening once the component is configured, then call StopListening on teardown, matching the sample's OnDestroy path.
Post a Unity callback
Queue an Action with MainThreadDispatcher.Post and call Update from a Unity update path. The recognizer does not wire this helper automatically.
Performance and platform notes
Full inference runs inside Task.Run, while native model creation and pointer handling remain explicit. No benchmark or memory claim is made by the source.
Native support is currently guarded to Windows, macOS and Linux editor/player values. Android and iOS microphone workflows require additional native validation and are not implied by the current binding check.
Troubleshooting / FAQ
IsNativeAvailable only returns true for the desktop platforms listed in the source. A native library is also required.
Check that modelPath points to the actual model file and that the native library can read it.
The events are declared and the sample attaches listeners, but the current recognizer source does not invoke them. Treat event emission as planned.
A null clip throws ArgumentNullException. Confirm that the clip contains readable sample data.
Grant microphone permission on Android or iOS before using a microphone workflow. The source does not implement a permission UI.
The source does not include [Preserve] attributes or a link.xml. Validate stripping and native plugin import settings in your build.
Planned
- Working partial and final event emission from the streaming path.
- Population of the microphone sample queue and complete streaming window feed.
- Native packaging and validation for additional platforms.
- Build stripping guidance such as
[Preserve]or link.xml, if needed after validation.
Something unclear?