MoonBit native bindings for whisper.cpp speech-to-text inference
Dependencies
git clone --recursive https://github.com/mizchi/whisper-mbt.git
cd whisper-mbt
# Build whisper.cpp and download model
just setup
# Run
WHISPER_MODEL=models/ggml-base.bin \
WHISPER_WAV=vendor/whisper.cpp/samples/jfk.wav \
moon run src/main --target nativemoon add mizchi/whisper{
"name": "your/project",
"deps": {
"mizchi/whisper": {
"path": "/path/to/whisper"
}
},
"preferred-target": "native"
}git clone https://github.com/ggml-org/whisper.cpp.git
cd whisper.cpp
# macOS (Metal)
cmake -B build -DCMAKE_BUILD_TYPE=Release \
-DGGML_METAL=ON -DGGML_METAL_EMBED_LIBRARY=ON \
-DBUILD_SHARED_LIBS=OFF \
-DWHISPER_BUILD_EXAMPLES=OFF -DWHISPER_BUILD_TESTS=OFF -DWHISPER_BUILD_SERVER=OFF
cmake --build build --config Release -j$(sysctl -n hw.logicalcpu)
# Linux (CPU only)
cmake -B build -DCMAKE_BUILD_TYPE=Release \
-DBUILD_SHARED_LIBS=OFF \
-DWHISPER_BUILD_EXAMPLES=OFF -DWHISPER_BUILD_TESTS=OFF -DWHISPER_BUILD_SERVER=OFF
cmake --build build --config Release -j$(nproc)// cmd/main/moon.pkg
import {
"mizchi/whisper" @whisper,
}
options(
"is-main": true,
link: {
"native": {
// macOS (Metal + Accelerate)
"cc-link-flags": "/path/to/whisper.cpp/build/src/libwhisper.a /path/to/whisper.cpp/build/ggml/src/libggml.a /path/to/whisper.cpp/build/ggml/src/libggml-base.a /path/to/whisper.cpp/build/ggml/src/libggml-cpu.a /path/to/whisper.cpp/build/ggml/src/ggml-metal/libggml-metal.a /path/to/whisper.cpp/build/ggml/src/ggml-blas/libggml-blas.a -lstdc++ -framework Accelerate -framework Metal -framework Foundation -framework MetalKit",
},
},
"supported-targets": [ "native" ],
)"cc-link-flags": "/path/to/whisper.cpp/build/src/libwhisper.a /path/to/whisper.cpp/build/ggml/src/libggml.a /path/to/whisper.cpp/build/ggml/src/libggml-base.a /path/to/whisper.cpp/build/ggml/src/libggml-cpu.a -lstdc++ -lm -lpthread"# ggml-base (147 MB)
curl -L -o models/ggml-base.bin \
https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base.binfn main {
let ctx = @whisper.WhisperContext::init("models/ggml-base.bin")
match ctx {
None => println("Failed to load model")
Some(ctx) => {
let segments = ctx.transcribe("audio.wav", language="auto")
for i = 0; i < segments.length(); i = i + 1 {
println(segments[i].text)
}
ctx.free()
}
}
}WhisperContext::init(model_path : String) -> WhisperContext?
WhisperContext::transcribe(self, wav_path, language?="en", translate?=false, n_threads?=4, ...) -> Array[Segment]
WhisperContext::transcribe_parallel(self, wav_path, n_processors?=4, ...) -> Array[Segment]
WhisperContext::get_tokens(self, segment_index) -> Array[TokenData]
WhisperContext::token_count(self, text) -> Int
WhisperContext::tokenize(self, text, max_tokens?=512) -> Array[Int]
WhisperContext::detect_language(self, wav_path, n_threads?=4) -> String
WhisperContext::detect_language_with_probs(self, wav_path, n_threads?=4) -> Array[LangProb]
WhisperContext::model_info(self) -> ModelInfo
WhisperContext::detected_language(self) -> String // after transcribe()
WhisperContext::get_timings(self) -> Timings
WhisperContext::print_timings(self) -> Unit
WhisperContext::free(self) -> Unitlang_id(lang : String) -> Int // "en" -> 0
lang_str(id : Int) -> String // 0 -> "en"
lang_str_full(id : Int) -> String // 0 -> "english"
lang_max_id() -> Int // max language id (98)
system_info() -> String // CPU/GPU feature infostruct Segment { text: String, t0: Int64, t1: Int64, no_speech_prob: Double, speaker_turn_next: Bool }
struct TokenData { text: String, id: Int, prob: Double, t0: Int64, t1: Int64 }
struct LangProb { lang: String, lang_full: String, prob: Double }
struct ModelInfo { model_type: String, is_multilingual: Bool, n_vocab: Int, n_text_ctx: Int, n_audio_ctx: Int }
struct Timings { sample_ms: Double, encode_ms: Double, decode_ms: Double, batchd_ms: Double, prompt_ms: Double }
struct VadParams { threshold: Double, min_speech_duration_ms: Int, min_silence_duration_ms: Int, max_speech_duration_s: Double, speech_pad_ms: Int }
enum Strategy { Greedy; BeamSearch }| Parameter | Type | Default | Description |
|---|---|---|---|
| language | String | "en" | Language code or "auto" |
| translate | Bool | false | Translate to English |
| n_threads | Int | 4 | Number of threads |
| offset_ms | Int | 0 | Start offset in ms |
| duration_ms | Int | 0 | Duration to process (0 = all) |
| no_timestamps | Bool | false | Disable timestamps |
| single_segment | Bool | false | Force single segment |
| token_timestamps | Bool | false | Token-level timestamps |
| max_len | Int | 0 | Max segment length (chars) |
| max_tokens | Int | 0 | Max tokens per segment |
| initial_prompt | String | "" | Initial prompt |
| temperature | Double | 0.0 | Decoding temperature |
| strategy | Strategy | Greedy | Greedy or BeamSearch |
| beam_size | Int | 5 | Beam size (when BeamSearch) |
| no_context | Bool | false | Disable past context |
| vad_model_path | String | "" | Path to Silero VAD model (enables VAD) |
| vad_params | VadParams? | None | VAD tuning parameters |
curl -L -o models/ggml-silero-v5.1.2.bin \
https://huggingface.co/ggml-org/whisper-vad/resolve/main/ggml-silero-v5.1.2.binlet segments = ctx.transcribe(
"audio.wav",
language="auto",
vad_model_path="models/ggml-silero-v5.1.2.bin",
)let count = ctx.token_count("Hello world") // 2
let ids = ctx.tokenize("Hello world") // [15947, 1002]let lang = ctx.detect_language("audio.wav") // "en"
// With probabilities for all languages
let probs = ctx.detect_language_with_probs("audio.wav")
for i = 0; i < probs.length(); i = i + 1 {
let p = probs[i]
println(p.lang + " (" + p.lang_full + "): " + p.prob.to_string())
}let segments = ctx.transcribe_parallel(
"long_audio.wav",
n_processors=4,
language="auto",
)just build-whisper
just update-headerspub struct LangProb {
lang : String
lang_full : String
prob : Double
}pub struct ModelInfo {
model_type : String
is_multilingual : Bool
n_vocab : Int
n_text_ctx : Int
n_audio_ctx : Int
}pub struct Segment {
text : String
t0 : Int64
t1 : Int64
no_speech_prob : Double
speaker_turn_next : Bool
}pub(all) enum Strategy {
Greedy
BeamSearch
}pub struct Timings {
sample_ms : Double
encode_ms : Double
decode_ms : Double
batchd_ms : Double
prompt_ms : Double
}pub struct TokenData {
text : String
id : Int
prob : Double
t0 : Int64
t1 : Int64
}pub struct VadParams {
threshold : Double
min_speech_duration_ms : Int
min_silence_duration_ms : Int
max_speech_duration_s : Double
speech_pad_ms : Int
}pub struct WhisperContext {
// private fields
}fn WhisperContext::detect_language(self : WhisperContext, wav_path : String, n_threads? : Int) -> Stringfn WhisperContext::detect_language_with_probs(self : WhisperContext, wav_path : String, n_threads? : Int) -> Array[LangProb]fn WhisperContext::transcribe(self : WhisperContext, wav_path : String, language? : String, translate? : Bool, n_threads? : Int, offset_ms? : Int, duration_ms? : Int, no_timestamps? : Bool, single_segment? : Bool, token_timestamps? : Bool, max_len? : Int, max_tokens? : Int, audio_ctx? : Int, initial_prompt? : String, temperature? : Double, print_progress? : Bool, strategy? : Strategy, beam_size? : Int, no_context? : Bool, vad_model_path? : String, vad_params? : VadParams?) -> Array[Segment]fn WhisperContext::transcribe_parallel(self : WhisperContext, wav_path : String, n_processors? : Int, language? : String, translate? : Bool, n_threads? : Int, offset_ms? : Int, duration_ms? : Int, no_timestamps? : Bool, single_segment? : Bool, token_timestamps? : Bool, max_len? : Int, max_tokens? : Int, audio_ctx? : Int, initial_prompt? : String, temperature? : Double, print_progress? : Bool, strategy? : Strategy, beam_size? : Int, no_context? : Bool, vad_model_path? : String, vad_params? : VadParams?) -> Array[Segment]MoonBit native bindings for whisper.cpp speech-to-text inference
Dependencies