1
1
Fork
You've already forked zop
0

Implement cross-platform Whisper microphone capture in --voice flow #24

Merged
Copilot merged 4 commits from copilot/fix-whisper-audio-capture-linux into main 2026年03月18日 06:24:00 +01:00
Copilot commented 2026年03月18日 05:41:39 +01:00 (Migrated from github.com)
Copy link

--voice in whisper-enabled builds initialized Whisper successfully but failed at capture time with whisper audio capture not yet implemented on this platform (reported on Linux amd64). This change wires real microphone capture into the Whisper path using a cross-platform backend so voice input works consistently on Linux/macOS/Windows.

  • Whisper voice path now performs real capture + inference

    • Replaced placeholder return in RecordAndTranscribe() with:
      • microphone capture at 16kHz / mono / S16
      • Whisper inference via whisper_full(...)
      • transcript assembly from Whisper segments
    • Preserved model bootstrap behavior (ZOP_WHISPER_MODEL / auto-download).
  • Cross-platform capture backend

    • Added github.com/gen2brain/malgo as the audio capture layer (miniaudio bindings).
    • Implemented capture lifecycle with bounded recording and termination conditions:
      • max duration cap
      • silence-based stop after detected speech
      • interrupt-aware cancellation.
  • Audio normalization + speech detection helpers

    • Added focused helper functions for:
      • byte → int16 conversion
      • int16 amplitude threshold speech detection
      • int16 → normalized float PCM conversion expected by Whisper.
    • Added targeted unit tests for these helpers and edge cases (including -32768 handling).

Example of the new inference handoff:

pcm,err:=captureAudioPCM(recordCtx,captureMaxDuration,captureStopAfterSilence)iferr!=nil{return"",fmt.Errorf("capturing audio: %w",err)}wparams:=C.whisper_full_default_params(C.WHISPER_SAMPLING_GREEDY)wparams.print_progress=falsewparams.print_realtime=falsewparams.print_timestamps=falseifrc:=C.whisper_full(ctx,wparams,(*C.float)(unsafe.Pointer(&pcm[0])),C.int(len(pcm)));rc!=0{return"",fmt.Errorf("whisper inference failed (code %d)",int(rc))}
Original prompt

This section details on the original issue you should resolve

<issue_title>Whisper audio capture did not work on linux amd64</issue_title>
<issue_description>```
$ uname -a
Linux thinkpaddy 6.17.0-14-generic peterwwillis/zop#14~24.04.1-Ubuntu SMP PREEMPT_DYNAMIC Thu Jan 15 15:52:10 UTC 2 x86_64 x86_64 x86_64 GNU/Linux
$ cat /etc/os-release
PRETTY_NAME="Ubuntu 24.04.4 LTS"
NAME="Ubuntu"
VERSION_ID="24.04"
VERSION="24.04.4 LTS (Noble Numbat)"
VERSION_CODENAME=noble
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=noble
LOGO=ubuntu-logo

$ arecord -l
**** List of CAPTURE Hardware Devices ****
card 1: Generic_1 [HD-Audio Generic], device 0: ALC257 Analog [ALC257 Analog]
Subdevices: 1/1
Subdevice peterwwillis/zop#0: subdevice peterwwillis/zop#0
card 2: acp63 [acp63], device 0: DMIC capture dmic-hifi-0 []
Subdevices: 1/1
Subdevice peterwwillis/zop#0: subdevice peterwwillis/zop#0
$ arecord -t wav --max-file-time 30 mon.wav
Warning: Some sources (like microphones) may produce inaudible results
with 8-bit sampling. Use '-f' argument to increase resolution
e.g. '-f S16_LE'.
Recording WAVE 'mon.wav' : Unsigned 8 bit, Rate 8000 Hz, Mono
^CAborted by signal Interrupt...
peterw@thinkpaddy:~/bin$ aplay mon.wav
Playing WAVE 'mon.wav' : Unsigned 8 bit, Rate 8000 Hz, Mono

$ ./zop --voice
[zop] Whisper model not found at "/home/peterw/.local/share/zop/whisper/ggml-base.en.bin" – downloading from https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base.en.bin ...
[zop] Whisper model saved to "/home/peterw/.local/share/zop/whisper/ggml-base.en.bin"
whisper_init_from_file_with_params_no_state: loading model from '/home/peterw/.local/share/zop/whisper/ggml-base.en.bin'
whisper_init_with_params_no_state: use gpu = 1
whisper_init_with_params_no_state: flash attn = 1
whisper_init_with_params_no_state: gpu_device = 0
whisper_init_with_params_no_state: dtw = 0
whisper_init_with_params_no_state: devices = 1
whisper_init_with_params_no_state: backends = 1
whisper_model_load: loading model
whisper_model_load: n_vocab = 51864
whisper_model_load: n_audio_ctx = 1500
whisper_model_load: n_audio_state = 512
whisper_model_load: n_audio_head = 8
whisper_model_load: n_audio_layer = 6
whisper_model_load: n_text_ctx = 448
whisper_model_load: n_text_state = 512
whisper_model_load: n_text_head = 8
whisper_model_load: n_text_layer = 6
whisper_model_load: n_mels = 80
whisper_model_load: ftype = 1
whisper_model_load: qntvr = 0
whisper_model_load: type = 2 (base)
whisper_model_load: adding 1607 extra tokens
whisper_model_load: n_langs = 99
whisper_model_load: CPU total size = 147.37 MB
whisper_model_load: model size = 147.37 MB
whisper_backend_init_gpu: device 0: CPU (type: 0)
whisper_backend_init_gpu: no GPU found
whisper_init_state: kv self size = 6.29 MB
whisper_init_state: kv cross size = 18.87 MB
whisper_init_state: kv pad size = 3.15 MB
whisper_init_state: compute buffer (conv) = 16.28 MB
whisper_init_state: compute buffer (encode) = 23.09 MB
whisper_init_state: compute buffer (cross) = 4.66 MB
whisper_init_state: compute buffer (decode) = 96.37 MB
Recording... press Ctrl-C or wait for silence.
Error: voice input: whisper audio capture not yet implemented on this platform


Make the audio capture work (and on all supported platforms, not just Linux; macos, windows, etc)</issue_description>
## Comments on the Issue (you are @copilot in this section)
<comments>
</comments>

Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

`--voice` in whisper-enabled builds initialized Whisper successfully but failed at capture time with `whisper audio capture not yet implemented on this platform` (reported on Linux amd64). This change wires real microphone capture into the Whisper path using a cross-platform backend so voice input works consistently on Linux/macOS/Windows. - **Whisper voice path now performs real capture + inference** - Replaced placeholder return in `RecordAndTranscribe()` with: - microphone capture at **16kHz / mono / S16** - Whisper inference via `whisper_full(...)` - transcript assembly from Whisper segments - Preserved model bootstrap behavior (`ZOP_WHISPER_MODEL` / auto-download). - **Cross-platform capture backend** - Added `github.com/gen2brain/malgo` as the audio capture layer (miniaudio bindings). - Implemented capture lifecycle with bounded recording and termination conditions: - max duration cap - silence-based stop after detected speech - interrupt-aware cancellation. - **Audio normalization + speech detection helpers** - Added focused helper functions for: - byte → int16 conversion - int16 amplitude threshold speech detection - int16 → normalized float PCM conversion expected by Whisper. - Added targeted unit tests for these helpers and edge cases (including `-32768` handling). Example of the new inference handoff: ```go pcm, err := captureAudioPCM(recordCtx, captureMaxDuration, captureStopAfterSilence) if err != nil { return "", fmt.Errorf("capturing audio: %w", err) } wparams := C.whisper_full_default_params(C.WHISPER_SAMPLING_GREEDY) wparams.print_progress = false wparams.print_realtime = false wparams.print_timestamps = false if rc := C.whisper_full(ctx, wparams, (*C.float)(unsafe.Pointer(&pcm[0])), C.int(len(pcm))); rc != 0 { return "", fmt.Errorf("whisper inference failed (code %d)", int(rc)) } ``` <!-- START COPILOT ORIGINAL PROMPT --> <details> <summary>Original prompt</summary> > > ---- > > *This section details on the original issue you should resolve* > > <issue_title>Whisper audio capture did not work on linux amd64</issue_title> > <issue_description>``` > $ uname -a > Linux thinkpaddy 6.17.0-14-generic peterwwillis/zop#14~24.04.1-Ubuntu SMP PREEMPT_DYNAMIC Thu Jan 15 15:52:10 UTC 2 x86_64 x86_64 x86_64 GNU/Linux > $ cat /etc/os-release > PRETTY_NAME="Ubuntu 24.04.4 LTS" > NAME="Ubuntu" > VERSION_ID="24.04" > VERSION="24.04.4 LTS (Noble Numbat)" > VERSION_CODENAME=noble > ID=ubuntu > ID_LIKE=debian > HOME_URL="https://www.ubuntu.com/" > SUPPORT_URL="https://help.ubuntu.com/" > BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/" > PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy" > UBUNTU_CODENAME=noble > LOGO=ubuntu-logo > > $ arecord -l > **** List of CAPTURE Hardware Devices **** > card 1: Generic_1 [HD-Audio Generic], device 0: ALC257 Analog [ALC257 Analog] > Subdevices: 1/1 > Subdevice peterwwillis/zop#0: subdevice peterwwillis/zop#0 > card 2: acp63 [acp63], device 0: DMIC capture dmic-hifi-0 [] > Subdevices: 1/1 > Subdevice peterwwillis/zop#0: subdevice peterwwillis/zop#0 > $ arecord -t wav --max-file-time 30 mon.wav > Warning: Some sources (like microphones) may produce inaudible results > with 8-bit sampling. Use '-f' argument to increase resolution > e.g. '-f S16_LE'. > Recording WAVE 'mon.wav' : Unsigned 8 bit, Rate 8000 Hz, Mono > ^CAborted by signal Interrupt... > peterw@thinkpaddy:~/bin$ aplay mon.wav > Playing WAVE 'mon.wav' : Unsigned 8 bit, Rate 8000 Hz, Mono > > > > $ ./zop --voice > [zop] Whisper model not found at "/home/peterw/.local/share/zop/whisper/ggml-base.en.bin" – downloading from https://huggingface.co/ggerganov/whisper.cpp/resolve/main/ggml-base.en.bin ... > [zop] Whisper model saved to "/home/peterw/.local/share/zop/whisper/ggml-base.en.bin" > whisper_init_from_file_with_params_no_state: loading model from '/home/peterw/.local/share/zop/whisper/ggml-base.en.bin' > whisper_init_with_params_no_state: use gpu = 1 > whisper_init_with_params_no_state: flash attn = 1 > whisper_init_with_params_no_state: gpu_device = 0 > whisper_init_with_params_no_state: dtw = 0 > whisper_init_with_params_no_state: devices = 1 > whisper_init_with_params_no_state: backends = 1 > whisper_model_load: loading model > whisper_model_load: n_vocab = 51864 > whisper_model_load: n_audio_ctx = 1500 > whisper_model_load: n_audio_state = 512 > whisper_model_load: n_audio_head = 8 > whisper_model_load: n_audio_layer = 6 > whisper_model_load: n_text_ctx = 448 > whisper_model_load: n_text_state = 512 > whisper_model_load: n_text_head = 8 > whisper_model_load: n_text_layer = 6 > whisper_model_load: n_mels = 80 > whisper_model_load: ftype = 1 > whisper_model_load: qntvr = 0 > whisper_model_load: type = 2 (base) > whisper_model_load: adding 1607 extra tokens > whisper_model_load: n_langs = 99 > whisper_model_load: CPU total size = 147.37 MB > whisper_model_load: model size = 147.37 MB > whisper_backend_init_gpu: device 0: CPU (type: 0) > whisper_backend_init_gpu: no GPU found > whisper_init_state: kv self size = 6.29 MB > whisper_init_state: kv cross size = 18.87 MB > whisper_init_state: kv pad size = 3.15 MB > whisper_init_state: compute buffer (conv) = 16.28 MB > whisper_init_state: compute buffer (encode) = 23.09 MB > whisper_init_state: compute buffer (cross) = 4.66 MB > whisper_init_state: compute buffer (decode) = 96.37 MB > Recording... press Ctrl-C or wait for silence. > Error: voice input: whisper audio capture not yet implemented on this platform > ``` > > Make the audio capture work (and on all supported platforms, not just Linux; macos, windows, etc)</issue_description> > > ## Comments on the Issue (you are @copilot in this section) > > <comments> > </comments> > </details> <!-- START COPILOT CODING AGENT SUFFIX --> - Fixes peterwwillis/zop#23 <!-- START COPILOT CODING AGENT TIPS --> --- ✨ Let Copilot coding agent [set things up for you](https://github.com/peterwwillis/zop/issues/new?title=✨+Set+up+Copilot+instructions&body=Configure%20instructions%20for%20this%20repository%20as%20documented%20in%20%5BBest%20practices%20for%20Copilot%20coding%20agent%20in%20your%20repository%5D%28https://gh.io/copilot-coding-agent-tips%29%2E%0A%0A%3COnboard%20this%20repo%3E&assignees=copilot) — coding agent works faster and does higher quality work when set up for your repo.
peterwwillis (Migrated from github.com) reviewed 2026年03月18日 05:41:39 +01:00
github-actions[bot] commented 2026年03月18日 06:14:42 +01:00 (Migrated from github.com)
Copy link

Mobile UI Screenshot

zop mobile UI

### Mobile UI Screenshot ![zop mobile UI](https://raw.githubusercontent.com/peterwwillis/zop/ui-screenshots/pr-24/zop-mobile-ui.png) <!-- Sticky Pull Request Commentzop-mobile-ui -->
copilot-pull-request-reviewer[bot] (Migrated from github.com) reviewed 2026年03月18日 06:14:49 +01:00
copilot-pull-request-reviewer[bot] (Migrated from github.com) left a comment
Copy link

Pull request overview

Enables the --voice flow in whisper-enabled builds by replacing the previous platform-stubbed audio capture with cross-platform microphone capture (via malgo) and then running Whisper inference to return a transcript.

Changes:

  • Implement real microphone capture and silence/max-duration stop logic in the Whisper CGO path.
  • Add audio helper utilities for byte→int16 conversion, speech detection, and int16→normalized float PCM conversion.
  • Add unit tests for the audio helpers and add github.com/gen2brain/malgo dependency.

Reviewed changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
internal/whisper/whisper_cgo.go Implements cross-platform mic capture, Whisper inference call, and transcript assembly for RecordAndTranscribe()
internal/whisper/audio_helpers.go Adds conversion + speech detection helpers used by capture/inference pipeline
internal/whisper/audio_helpers_test.go Adds targeted tests for helper edge cases (including -32768)
go.mod Adds github.com/gen2brain/malgo dependency
go.sum Adds checksums for malgo
## Pull request overview Enables the `--voice` flow in whisper-enabled builds by replacing the previous platform-stubbed audio capture with cross-platform microphone capture (via `malgo`) and then running Whisper inference to return a transcript. **Changes:** - Implement real microphone capture and silence/max-duration stop logic in the Whisper CGO path. - Add audio helper utilities for byte→int16 conversion, speech detection, and int16→normalized float PCM conversion. - Add unit tests for the audio helpers and add `github.com/gen2brain/malgo` dependency. ### Reviewed changes Copilot reviewed 4 out of 5 changed files in this pull request and generated 2 comments. <details> <summary>Show a summary per file</summary> | File | Description | | ---- | ----------- | | internal/whisper/whisper_cgo.go | Implements cross-platform mic capture, Whisper inference call, and transcript assembly for `RecordAndTranscribe()` | | internal/whisper/audio_helpers.go | Adds conversion + speech detection helpers used by capture/inference pipeline | | internal/whisper/audio_helpers_test.go | Adds targeted tests for helper edge cases (including `-32768`) | | go.mod | Adds `github.com/gen2brain/malgo` dependency | | go.sum | Adds checksums for `malgo` | </details>
@ -127,0 +274,4 @@
iferr!=nil{
returnnil,err
}
deferdevice.Uninit()
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2026年03月18日 06:14:49 +01:00
Copy link

This callback allocates on every audio frame (bytesToInt16 creates a new slice) and also calls time.Now(). For real-time audio capture this can trigger GC pauses/glitches. Consider appending raw bytes to a preallocated buffer (or using a reusable []int16/sync.Pool) and doing conversion/timestamp checks outside the device callback where possible.

This callback allocates on every audio frame (`bytesToInt16` creates a new slice) and also calls `time.Now()`. For real-time audio capture this can trigger GC pauses/glitches. Consider appending raw bytes to a preallocated buffer (or using a reusable `[]int16`/`sync.Pool`) and doing conversion/timestamp checks outside the device callback where possible.
copilot-pull-request-reviewer[bot] (Migrated from github.com) commented 2026年03月18日 06:14:49 +01:00
Copy link

maxDuration is only enforced inside the audio callback. If the device starts but no callbacks are delivered (e.g., missing permissions / no default input), this select can block indefinitely. Consider enforcing the cap with context.WithTimeout/time.After(maxDuration) and returning ctx.Err() on cancellation so Ctrl-C/timeout produces a clean exit instead of hanging or returning empty audio.

`maxDuration` is only enforced inside the audio callback. If the device starts but no callbacks are delivered (e.g., missing permissions / no default input), this `select` can block indefinitely. Consider enforcing the cap with `context.WithTimeout`/`time.After(maxDuration)` and returning `ctx.Err()` on cancellation so Ctrl-C/timeout produces a clean exit instead of hanging or returning empty audio.
Sign in to join this conversation.
No reviewers
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
mutablecc/zop!24
Reference in a new issue
mutablecc/zop
No description provided.
Delete branch "copilot/fix-whisper-audio-capture-linux"

Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?