{"record":{"id":"1b5d6b384fd1797a","repo":"mudler/LocalAI","slug":"web-audio-api-not-available-1b5d6b","errorCode":null,"errorMessage":"Web Audio API not available","messagePattern":"Web Audio API not available","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"core/http/react-ui/src/hooks/useSpectrogram.js","lineNumber":48,"sourceCode":"// hundred px wide, so computing an FFT per native hop would be wasted work.\nexport default function useSpectrogram(\n  src,\n  { fftSize = 512, hop = 256, maxFrames = 900, dbFloor = -90 } = {},\n) {\n  const [state, setState] = useState(EMPTY)\n\n  useEffect(() => {\n    setState(EMPTY)\n    if (!src) return\n    let cancelled = false\n    setState((s) => ({ ...s, loading: true }))\n\n    async function run() {\n      try {\n        const resp = await fetch(src)\n        const raw = await resp.arrayBuffer()\n        const ctx = getSharedAudioContext()\n        if (!ctx) throw new Error('Web Audio API not available')\n        const audio = await ctx.decodeAudioData(raw.slice(0))\n        if (cancelled) return\n\n        const data = audio.getChannelData(0)\n        const bins = fftSize >> 1\n        const win = hann(fftSize)\n\n        // Frame count, then a stride so we never run more than maxFrames FFTs.\n        const rawFrames = data.length >= fftSize ? 1 + Math.floor((data.length - fftSize) / hop) : 1\n        const stride = rawFrames > maxFrames ? Math.ceil(rawFrames / maxFrames) : 1\n        const frames = Math.ceil(rawFrames / stride)\n\n        const spec = new Float32Array(frames * bins)\n        const re = new Float64Array(fftSize)\n        const im = new Float64Array(fftSize)\n        let peakDb = dbFloor\n\n        for (let f = 0; f < frames; f++) {","sourceCodeStart":30,"sourceCodeEnd":66,"githubUrl":"https://github.com/mudler/LocalAI/blob/44413a9d06bf5bc52ce088ba8ca74e5a2e8bee26/core/http/react-ui/src/hooks/useSpectrogram.js#L30-L66","documentation":"This error is printed by the cgo bridge when whisper_full() — whisper.cpp's main transcription entry point — returns a nonzero status. whisper_full returns nonzero when the encoder/decoder compute fails, when language auto-detection fails (typically on silence or extremely short audio), or when the input cannot be processed at all (null/empty PCM, model/context mismatch). The code first distinguishes the intentional client abort path (g_abort set from Go via set_abort(1), which returns 2), so return code 1 here means a genuine processing failure, not cancellation.","triggerScenarios":"Calling transcribe() with pcmf32_len == 0 or a near-empty buffer after VAD filtering removed everything; passing lang=\"\" causes wparams.language = nullptr which forces whisper auto-detection, which fails (returns -3 in whisper.cpp) on pure silence/noise; a ggml backend failure during whisper_full (failed to encode, CUDA/Metal OOM, missing backend in the prebuilt library); thread count of 0 or a value the runtime rejects; a prompt buffer that is not NUL-terminated (it is printed with %s and passed as initial_prompt); version mismatch between the shim and the vendored whisper.cpp causing ABI-level misreads of whisper_full_params.","commonSituations":"LocalAI users sending silent or sub-second audio files; enabling tdrz (tinydiarize) with a non-tdrz model; GPU memory exhaustion when a larger whisper model is loaded alongside other backends; a stale gowhisper.a built against older whisper.cpp headers after a dependency bump; concurrent calls reusing the single static ctx (this shim has one global whisper_context, so overlapping transcribe() calls corrupt state and can fail nondeterministically).","solutions":["Check the ggml [ERROR]/[WARN] log lines emitted just above this message — they state the exact internal failure (e.g. 'failed to auto-detect language', 'failed to encode', 'ggml_backend_alloc failed'); fix that root cause first.","Guard the caller: skip transcribe() when pcmf32_len is 0 or when VAD reported no speech segments; whisper_full on empty input is guaranteed to fail.","If lang is empty, auto-detect runs on silence-prone audio — pass an explicit language (e.g. 'en') or ensure audio has detectable speech; upgrade/verify the whisper model GGUF is intact (re-download if the hash or file size looks wrong).","Serialize access to transcribe() — the shim uses a single static whisper_context, so wrap calls in a mutex on the Go side to prevent interleaved whisper_full calls.","Set a sane thread count (>= 1, typically min(4, NumCPU)) and confirm the prompt string is NUL-terminated before crossing cgo.","If the failure appeared after a whisper.cpp/ggml update, rebuild the gowhisper shim from source so headers and library match."],"exampleFix":"// Go caller: validate input and serialize before crossing into C\n// before\ntext, err := gw.Transcribe(threads, lang, translate, tdrz, pcm)\n\n// after\nmu.Lock() // single static whisper_context in the shim: no concurrent whisper_full\ndefer mu.Unlock()\nif len(pcm) == 0 {\n    return \"\", errors.New(\"no audio to transcribe\")\n}\nif len(segs) == 0 { // VAD found no speech\n    return \"\", nil\n}\ntext, err := gw.Transcribe(threads, \"en\", translate, tdrz, pcm)\nif err == gw.ErrAborted { // return code 2 from C\n    return \"\", context.Canceled\n}","handlingStrategy":"validation","validationCode":"// Run before transcribe(): non-empty audio, explicit language, loaded model\nfunc canTranscribe(sampleCount int, modelLoaded bool, lang string) error {\n    if !modelLoaded { // load_model() must have returned 0\n        return errors.New(\"whisper model not loaded\")\n    }\n    if sampleCount < 320 { // whisper needs >= 30 ms; use >= 1 s in practice\n        return fmt.Errorf(\"audio too short to transcribe: %d samples\", sampleCount)\n    }\n    if lang == \"\" && sampleCount < 16000 { // auto-detect needs enough speech\n        return errors.New(\"explicit language required for audio < 1 s\")\n    }\n    return nil\n}","typeGuard":"// Go: distinguish the two nonzero return codes from transcribe()\nvar errAborted = errors.New(\"transcription aborted by client\")\n\nfunc isAbortRC(rc int) bool { return rc == 2 }\nfunc isHardFailRC(rc int) bool { return rc == 1 }","tryCatchPattern":"// Map C return codes at the cgo boundary; never retry rc==1 blindly\nrc := C.transcribe(C.uint32_t(threads), cLang, b2i(translate), b2i(tdrz),\n    (*C.float)(&pcm[0]), C.size_t(len(pcm)), &segLen, cPrompt)\nswitch rc {\ncase 0: // success\ncase 2: // client abort via set_abort(1): not an error, propagate cancellation\n    return context.Canceled\ncase 1: // hard failure: inspect ggml log output, do not retry same input\n    return fmt.Errorf(\"transcription failed (rc=1): check ggml log; len=%d lang=%q\", len(pcm), lang)\n}","preventionTips":["Never call transcribe() with an empty PCM slice; short-circuit when VAD yields zero segments","Serialize transcribe() calls behind a mutex — the shim holds one static whisper_context","Pass an explicit language instead of relying on auto-detect for short or noisy clips","Cap and validate thread count (1..NumCPU) and NUL-terminate lang/prompt strings before crossing cgo","Rebuild the gowhisper shim whenever whisper.cpp or ggml is updated to keep the ABI aligned","Capture the ggml log lines on failure — they name the real internal error that caused whisper_full to return nonzero"],"tags":["whisper-cpp","transcription","audio","cgo","localai","ggml"],"backgroundTag":null,"analyzedSha":"44413a9d06bf5bc52ce088ba8ca74e5a2e8bee26","analyzedAt":"2026-08-15T10:13:50.291Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}