Moonshine Voice
repository·main·Indexed 26 days ago
https://github.com/moonshine-ai/moonshineAn open-source AI toolkit for building real-time, on-device voice interfaces. It provides low-latency Speech-to-Text (STT), Text-to-Speech (TTS), and conversational agent capabilities across Python, mobile, desktop, and embedded devices. Features include speaker identification via a C++/ONNX Runtime port of pyannote and support for multiple language bundles including English (US), Arabic (MSA), and Portuguese (Brazil and Portugal).
What's inside moonshine-voice
- Moonshine Voice is an open-source AI toolkit for building real-time, on-device voice agents. It supports Speech-to-Text (STT), Text-to-Speech (TTS), voice cloning, speaker identification, and conversational agents. The framework is optimized for low-latency live streaming and runs entirely on-device without requiring API keys or internet connectivity.
Overview of kaldi-native-fbank
mainThekaldi-native-fbanklibrary provides a native C++ implementation for extracting Kaldi-compatible filter bank (fbank) features. It is designed to function without a dependency on the full Kaldi framework, making it a lightweight option for feature extraction tasks that require Kaldi compatibility.Overview of Moonshine Micro for Microcontrollers
mainMoonshine Micro is a version of Moonshine Voice designed for embedded system processors, microcontrollers, and DSPs. It is optimized for resource-constrained environments and can run in as little as 470 KB of RAM. The reference platform is the Raspberry Pi RP2350.
Key capabilities include:
- Voice Activity Detection (VAD)
- Command Recognition (STT) using SpellingCNN
- Neural Speech Synthesis (TTS) using neural diphone synthesis
The components (VAD, STT, and TTS) can be used independently and rely on the TensorFlow Lite Micro library for neural computations.
Core Concepts of Moonshine Voice
mainMoonshine Voice uses an event-driven architecture to handle speech processing. Key concepts include:
- Transcriber: The primary object for converting audio input into text. Requires a path to downloaded models.
- MicTranscriber: A specialized helper class that automatically connects to the system's default microphone.
- Stream: A handler for audio input, allowing a single transcriber to process multiple audio sources simultaneously without duplicating model resources.
- TranscriptLine: A data structure representing a segment of speech (a phrase) identified by a pause. It includes state (started, speaking, or complete), start time, and duration.
- Transcript: A time-ordered list of
TranscriptLineobjects representing the recognized text of a session. - TranscriptEvent: Contains information about changes to the transcript, such as a new line starting, text updating, or a line completing.
- TranscriptEventListener: A protocol/interface used to attach application logic to transcription events.
- TextToSpeech: An object used to synthesize audio for playback.
- DialogFlow: A specialized
TranscriptEventListenerthat manages conversations and can trigger callbacks based on registered phrases (voice commands). - Dialog: An object representing a single conversational exchange between an agent and a user.
Use DialogFlow to build conversational voice interfaces
mainThe
DialogFlowclass is a runner that drives generator-based conversational flows. It routes completed transcript lines either to matching trigger phrases (when no flow is active) or to the currently suspended generator (when one is).Matching is semantic using an embedding model. A 'flow' is a Python generator function that takes a
Dialogobject as an argument andyields prompt objects back to the runner. This allows you to write multi-step, branching conversations using standard Python control flow (loops, exceptions) without async machinery.Understand Moonshine API Design Principles
mainMoonshine's language bindings are designed to provide a fluent, opinionated API focused on building voice interfaces. Developers should expect the following patterns when using the library:
- Opinionated Defaults: Common voice interface tasks are designed to be terse, often requiring only one or two function calls. Implementation details are abstracted away to simplify standard workflows.
- Builder Pattern: Configuration is typically handled via a series of method calls (builder pattern) rather than a single large options object.
- Platform Idioms: APIs are designed to follow the native idioms and patterns of the specific platform (e.g., Pythonic patterns in Python, idiomatic Java in Android).
- Asynchronous Resource Loading: High-latency operations like model loading follow native asynchronous patterns to prevent blocking.
- High-Level Abstractions: Complex workflows like
DialogFlowand voice cloning are exposed as single high-level concepts. For example,DialogFlowmanages the orchestration of STT, intent, and TTS models internally, while voice cloning handles audio extraction and transcription automatically.
Compare Moonshine vs Whisper for live speech
mainMoonshine is optimized for live voice interfaces and streaming applications, whereas Whisper is better suited for bulk batch processing in the cloud.
Key advantages of Moonshine for live speech:
- Flexible Input Windows: Unlike Whisper's fixed 30-second window, Moonshine processes only the audio provided (recommended < 30s), avoiding zero-padding latency.
- Streaming Caching: Moonshine supports incremental audio addition by caching input encoding and decoder states, significantly reducing redundant computation.
- Language-Specific Accuracy: Moonshine offers specialized models for languages like Arabic, Japanese, Korean, Spanish, Ukrainian, Vietnamese, and Chinese, providing higher accuracy than multilingual models of similar size.
- Edge & Cross-Platform Support: Designed with a portable C++ core and OnnxRuntime, Moonshine provides consistent APIs across Linux, MacOS, Windows, iOS, Android, Python, Swift, Java, and C++.
- Efficiency: Moonshine models (e.g., Medium Streaming) can achieve lower Word Error Rates (WER) than Whisper Large v3 while using significantly fewer parameters (250M vs 1.5B), making them ideal for edge deployment.
Train an on-device command recognizer
mainThestt-trainingdirectory provides a pipeline to train a custom on-device command recognizer. The process involves choosing a vocabulary, synthesizing command words using ZipVoice, mining data from People's Speech, cutting aligned clips, and optionally adding noise/reverb. The pipeline can be executed end-to-end using a single command script.Understand breaking changes in Moonshine client bindings
mainThe Moonshine client bindings (JavaScript, Swift, Python, and C++) have undergone a breaking API change to unify their patterns.
Key Changes:
- Entry Points: High-level entry points have changed shape, though low-level types like
Transcriber,Stream,GraphemeToPhonemizer, andAssetDownloaderremain public and maintain existing behavior. - IntentRecognizer: This is now internal. Use
DialogFlowinstead, which manages the embedding model automatically on first use. - JavaScript Renaming:
MicrophoneTranscriberhas been renamed. - Python Binding Updates: The Python
DialogFlownow follows a unified 'construct-configure-load' pattern. It no longer requires manual construction ofTextToSpeechorMicTranscriber; instead, it opens all three models on.load()and the microphone on.start_listening().
- Entry Points: High-level entry points have changed shape, though low-level types like
Understand the Moonshine Voice System Architecture
mainThe Moonshine Voice system uses a two-stage pipeline for on-device speech processing:
- Always-on Listening (TinyVadCNN): Processes 16 kHz PCM audio using 32 ms hops. It consumes a log-mel stream to detect voice activity. When speech is detected, a segmenter identifies the speech boundaries.
- On-speech Classification (SpellingCNN): Once speech ends, a ~1 second clip is captured and processed. This stage converts the audio into a log-mel batch, which is then classified by SpellingCNN to produce logits for spoken tokens (letters, digits, or commands).
Note: Both models consume normalised log-mel features. The
.tfliteand.onnxfiles provided contain only the classifier heads; the mel front-end (16 kHz PCM → Slaney filterbank → normalisation) must be implemented separately.flowchart LR subgraph listen["Always-on (32 ms hops)"] PCM1["16 kHz PCM"] --> MelS["Log-mel stream"] MelS --> VAD["TinyVadCNN"] VAD --> Seg["Segmenter"] end subgraph classify["On speech end (~1 s clip)"] Seg --> Clip["1 s int16 clip"] Clip --> MelB["Log-mel batch"] MelB --> STT["SpellingCNN"] STT --> Logits["51 logits"] Logits --> TTS["TTS"] end TTS --> Spk["spoken reply"]Understand the Kokoro ONNX bundle structure
mainThe Kokoro ONNX bundle is a specific directory layout required for
moonshine_tts(C++ implementation). It must contain the following files to function:Path Role config.jsonModel configuration, including the phoneme vocabfor ONNX.model.onnxThe Kokoro-82M acoustic ONNX model. voices/*.kokorovoiceStyle tensors for ONNX inference. Note that C++ cannot load Hugging Face .ptpickles; you must use the.kokorovoiceformat.Note: While the Python TTS can use PyTorch weights, the C++ path requires only the ONNX model and
.kokorovoicefiles.Implement Transcription with a Transcriber
mainTo transcribe audio, initialize a
Transcriberwith your model path and architecture, then attach aTranscriptEventListenerto handle events. You can feed audio to the transcriber usingadd_audio()in a loop to simulate live streaming, or usetranscribe_without_streaming()for processing existing audio arrays.Note: The transcriber analyzes speech at a default interval of 500ms, which can be adjusted via the
update_intervalargument in the constructor. Always callstart()to begin a session andstop()to end it.# Initialize the transcriber transcriber = Transcriber(model_path=model_path, model_arch=model_arch) # Define a listener for events class TestListener(TranscriptEventListener): def on_line_started(self, event): print(f"Line started: {event.line.text}") def on_line_text_changed(self, event): print(f"Line text changed: {event.line.text}") def on_line_completed(self, event): print(f"Line completed: {event.line.text}") # Attach listener and start processing transcriber.add_listener(listener) transcriber.start() # Example: Feeding audio chunks from a wav file # (Assuming load_wav_file is available from the library) audio_data, sample_rate = load_wav_file(wav_path) chunk_duration = 0.1 chunk_size = int(chunk_duration * sample_rate) for i in range(0, len(audio_data), chunk_size): chunk = audio_data[i: i + chunk_size] transcriber.add_audio(chunk, sample_rate) transcriber.stop()