8.7.7. dasLLAMA-07 — Speech to Text

dasLLAMA transcribes speech through one uniform surface: a single loader that sniffs the file format, one verb set, and a caps() call that tells you what the loaded model honestly supports. No family names appear in the API — the same program runs a whisper.cpp bin, a Qwen3-ASR GGUF pair, or a Parakeet-TDT bin.

Run it with any supported model and a 16 kHz mono PCM16 WAV:

daslang.exe -jit tutorials/dasLLAMA/07_speech_to_text.das -- ggml-tiny.bin jfk.wav
daslang.exe -jit ... -- Qwen3-ASR-0.6B-Q8_0.gguf mmproj-Qwen3-ASR-0.6B-bf16.gguf jfk.wav
daslang.exe -jit ... -- ggml-parakeet-tdt-0.6b-v2-f32.bin jfk.wav

8.7.7.1. One loader, sniffed formats

load_asr_model looks at the file, not the filename: a ggml bin routes to whisper or Parakeet by its vocabulary size; a GGUF decoder takes its audio-encoder mmproj as a second path. A mismatched or unsupported file panics with a message that says what to do instead.

var m <- load_asr_model("ggml-tiny.bin")                    // whisper family
var q <- load_asr_model("Qwen3-ASR-0.6B-Q8_0.gguf",
                        "mmproj-Qwen3-ASR-0.6B-bf16.gguf")  // GGUF pair

8.7.7.2. caps(): ask, don’t assume

Capabilities are advisory — they let a front end grey out what a model can’t do. Invalid requests still panic loudly at the call site; nothing is silently ignored.

let c <- caps(m)
// c.languages  — codes create_session accepts (empty = the model detects itself)
// c.translate  — speech-to-English translation task
// c.timestamps — none / segment / word granularity
// c.streaming  — native incremental decode
// c.prompt     — context/prompt conditioning (Qwen3-ASR: AsrSession.context)

8.7.7.3. Transcribe

The one-shot form returns the full text; the block form yields each TranscribeSegment as its window completes — with centisecond timestamps, the raw token ids, and avg_logprob, the mean per-token log-probability (closer to zero = more confident).

var s <- create_session(m, "auto")   // "auto": whisper detects the language
let text = transcribe(m, s, samples) // one-shot
print("{s.detected_lang}: {text}\n")

transcribe(m, s, samples) $(seg) {   // per-segment
    print("[{seg.t0} - {seg.t1}] {seg.text} (avg_logprob {seg.avg_logprob})\n")
}

Language auto-detection costs whisper one extra prompt decode on the first audio, then sticks for the session. Qwen3-ASR always detects — it reports the spoken language itself, surfaced through the same detected_lang field.

8.7.7.4. The push-chunks rail

feed buffers 16 kHz samples, drain transcribes every complete 30 s window (leaving the rest pending), and flush finishes the tail. This is the shape a live audio source drives — pair it with dasAudio’s microphone capture (AUDIO-11 — Recording from the Microphone). Models that transcribe whole clips at once say so with a loud panic, matching caps().streaming.

feed(m, s, chunk)                 // as audio arrives
drain(m, s) $(seg) { ... }        // complete windows only
flush(m, s) $(seg) { ... }        // the sub-30 s tail, at end of stream

See also

Full source: tutorials/dasLLAMA/07_speech_to_text.das

Previous tutorial: dasLLAMA-06 — The Architecture Registry · Next tutorial: dasLLAMA-08 — Audio Chat

The transcription CLI: examples/dasLLAMA/transcribe.das