Back to Moonshine

Moonshine Voice C++ Example

examples/c++/README.md

0.1.15.9 KB
Original Source

Moonshine Voice C++ Example

This is a minimal, platform independent example of using the C++ interface to the Moonshine Voice Library.

To use this you'll first need to download a prebuilt version of the library, or build it yourself using cmake in core/ if you're on a platform without a prebuilt version.

Download

The easiest way to grab everything you need is the download-library.sh helper in this folder. It detects your platform, downloads the matching prebuilt library archive from our GitHub releases, and always extracts it into a folder named moonshine-voice here (regardless of operating system or architecture, so the build commands below never change). It also fetches the Medium Streaming English model into medium-streaming-en/ and a sample recording (two_cities.wav) so the transcriber example runs out of the box:

bash
cd examples/c++
./download-library.sh

On Linux the extracted moonshine-voice/lib folder is self-contained: it holds libmoonshine.so alongside the libonnxruntime.so.1 it depends on, and the library is built with an $ORIGIN rpath so it finds the ONNX Runtime next to itself with no LD_LIBRARY_PATH needed.

If you would rather download by hand, look for moonshine-voice-<platform>.tar.gz on the releases page and extract it into a moonshine-voice folder in this directory. For example, on MacOS:

bash
cd examples/c++
curl -O -L https://github.com/moonshine-ai/moonshine/releases/download/v0.1.1/moonshine-voice-macos-arm64.tar.gz
mkdir -p moonshine-voice
tar xzf moonshine-voice-macos-arm64.tar.gz -C moonshine-voice --strip-components=1

Build

The archive contains the Moonshine library inside the lib folder: a shared libmoonshine.so on Linux, a static libmoonshine.a on MacOS, or moonshine.lib on Windows. This is what you'll link against. There are also two headers, one for the low-level C API, and another for the higher-level C++ framework that's built on top of it. On Linux the lib folder also contains libonnxruntime.so.1, the ONNX Runtime shared library that libmoonshine.so loads at runtime.

Since this is a generic C++ example, I'll show the simplest possible build command lines on some common platforms. Because download-library.sh always extracts into a folder named moonshine-voice, the exact same command works on both x86_64 and 64-bit ARM within each operating system.

Linux

bash
g++ transcriber.cpp \
  -Imoonshine-voice/include \
  -Lmoonshine-voice/lib \
  -lmoonshine \
  -Wl,-rpath,'$ORIGIN/moonshine-voice/lib' \
  -o transcriber

The -Wl,-rpath,'$ORIGIN/...' flag records a runtime library search path relative to the compiled transcriber binary, so it finds libmoonshine.so (and, through that library's own $ORIGIN rpath, libonnxruntime.so.1) without any LD_LIBRARY_PATH. If you prefer, drop the rpath flag and instead export LD_LIBRARY_PATH=$(pwd)/moonshine-voice/lib before running.

You can build text-to-speech.cpp the same way (swap the source and -o output names). Note that it additionally needs the TTS voice and G2P data, so pass its location with --asset-root (in a checkout of this repo that is ../../core/moonshine-tts/data).

MacOS

bash
g++ transcriber.cpp \
  -Imoonshine-voice/include \
  -Lmoonshine-voice/lib \
  -lmoonshine \
  -o transcriber \
  -framework CoreFoundation \
  -framework Foundation
g++ text-to-speech.cpp \
  -Imoonshine-voice/include \
  -Lmoonshine-voice/lib \
  -lmoonshine \
  -o text-to-speech \
  -framework CoreFoundation \
  -framework Foundation

Run

You should now have an executable called transcriber in this folder. Run it with:

bash
./transcriber

What the C++ API does and does not cover

The C++ binding is header-only and opens no devices and no sockets, which is what makes it portable to anywhere the library builds. So it has Transcriber, TextToSpeech, GraphemeToPhonemizer, EmbeddingModel and VoiceClone, but none of the pieces that need a runtime: there is no MicTranscriber or AgentFlow (they need a capture device), no say() (it needs an output device; synthesize() hands you the samples to play yourself), and no downloading, so nothing takes a load() or reports progress. Feed it paths or buffers instead.

Fetching models is therefore your job, but working out which models is not. Every manifest the other bindings download from is available as JSON naming the files, their URLs, sizes and checksums:

cpp
std::string stt = moonshine::Transcriber::getDependencies("en");
std::string diarization = moonshine::Transcriber::getDiarizationDependencies();
std::string tts = moonshine::TextToSpeech::getDependencies("en_us");
std::string g2p = moonshine::GraphemeToPhonemizer::getDependencies("en_us");
std::string embedding =
    moonshine::EmbeddingModel::getDependencies("embeddinggemma-300m");

Transcriber::getCatalog() and EmbeddingModel::getCatalog() list everything published, if you would rather browse than ask for one language.

Voice cloning does work here, because the voice-activity detector that finds a reference clip is compiled into the library and needs no downloads:

cpp
moonshine::TextToSpeech tts("en_us", {{"g2p_root", assetRoot}});
tts.cloneFrom(recording, sampleRate, "what the speaker said");
moonshine::TtsSynthesisResult cloned = tts.synthesize("Hello world!");

Pass a Transcriber in place of the transcript to have Moonshine work out what was said. To capture the clip from a live stream rather than a finished recording, use startCloning() and feed addAudio() until isReady().

By default it transcribes the two_cities.wav sample using the Medium Streaming English model that download-library.sh placed in the medium-streaming-en/ folder, so you should see transcription results printed as it goes. You can point it at different models and inputs using --model-path, --model-arch, and --wav-path.