8000 Recording captures only channel 1 of multichannel audio interfaces → silence when the mic is on input 2+ · Issue #165 · amicalhq/amical · GitHub
[go: up one dir, main page]

Skip to content
< 8000 link rel="preload" as="image" href="https://github.githubassets.com/assets/platform-dark-ac89fbf373737ed3.png"/>

Recording captures only channel 1 of multichannel audio interfaces → silence when the mic is on input 2+ #165

Open

Description

@fututukamono

Summary

On a multichannel audio interface (Universal Audio Apollo, Focusrite, MOTU, etc.), Amical records near-silence if the microphone is on any input other than channel 1. Whisper then returns "no words detected", even though the interface, the mic, and all OS permissions are correct.

Environment

  • macOS (Apple Silicon), Amical 1.9.x, Local / Whisper Medium
  • Audio interface: Universal Audio Apollo Twin (2 preamps; front Hi-Z instrument = ch1, rear Mic 2 = ch2)
  • Microphone (SM58) on rear input 2 → Core Audio channel 2

What happens

  • Amical history shows repeated "no words detected".
  • The recorded WAVs ($TMPDIR/amical-audio/*.wav) are full length but ~-66 dBFS (digital silence).
  • Capturing the interface directly and measuring per channel shows the voice is on channel 2, while channel 1 is silent (it's the empty Hi-Z instrument input).
  • Zoom / Google Meet / other dictation apps capture the voice fine (they take stereo / the correct channel).

Root cause

The recorder requests a mono stream:

getUserMedia({ audio: { channelCount: 1, sampleRate, echoCancellation: false, noiseSuppression: false, autoGainControl: false } })

and the audio-recorder-processor AudioWorklet consumes only channel 0:

const channelData = input[0]; // channel 0 == interface channel 1

With channelCount: 1, Chromium hands over only the device's first channel, so any mic on channel 2+ is dropped. (Confirmed empirically: the recorded audio is bit-for-bit the interface's silent channel 1, not a downmix.)

Impact

Anyone using a pro audio interface with the mic on an input other than #1 — an extremely common studio configuration — gets unusable, silent dictation with no error explaining why.

Suggested fix

Capture at least two channels and let the worklet pick the channel that carries signal (or expose a channel selector). A minimal, backward-compatible change:

  • channelCount: 1 → channelCount: 2
  • worklet: const channelData = input[0]; → const channelData = input[1] || input[0];

The || input[0] keeps ordinary mono microphones working unchanged. A more complete fix could auto-select the loudest channel or add a UI channel picker. PR with the minimal version to follow.

Activity

  1. haritabh-z01 commented on Jul 15, 2026

    @haritabh-z01
    Contributor

    Based on all my research media recorder should be already handling this.
    Can you please share a mechanism to test this out @fututukamono (virtual mic or whatever mic you use etc)

  2. fututukamono commented on Jul 15, 2026

    @fututukamono
    Author

    Sure — here's a self-contained repro plus the exact spot in the code.

    Where it happens: the recorder calls getUserMedia({ audio: { channelCount: 1, ... } }) and the audio-recorder-processor worklet reads only input[0]. With channelCount: 1, Chromium hands over only the device's first channel (it does not downmix), so any input device whose mic sits on channel 2+ records that device's silent channel 1.

    Repro without a pro interface (virtual mic):

    1. Install BlackHole 16ch (free).
    2. Route a test tone (or a real mic via any DAW) to BlackHole channel 2 only, leaving channel 1 silent. (Equivalently, a macOS Aggregate Device with a silent input as sub-device 1 and your mic as sub-device 2 puts the mic on channel 2.)
    3. Select that device in Amical and record → silence, because only channel 1 is captured.

    Real hardware (mine): UA Apollo Twin, SM58 on rear input 2 = Core Audio channel 2, front Hi-Z (ch1) empty. Amical's WAVs come out at ~-66 dBFS (digital silence) — bit-for-bit the interface's silent channel 1, not an attenuated downmix, which is what pins it to discrete channel-0 selection. Zoom/Meet capture fine (they take stereo).

    Minimal fix (mono mics stay unchanged via the fallback):

    channelCount: 1               → channelCount: 2
    const channelData = input[0]; → const channelData = input[1] || input[0];
    

    Local patcher + full writeup: https://github.com/fututukamono/komochi — happy to open a PR with the minimal change, or a fuller version that auto-selects the channel carrying signal.

  3. iibaranov-IG commented on Sep 14, 2026

    @iibaranov-IG
    Contributor

    The fix is ready for hardware validation in PR #184: #184

    @fututukamono, could you please test that build with the Apollo Twin selected, place the microphone or a test tone on input 2 while input 1 is silent, and confirm that the recorded WAV contains the signal and transcription works? If possible, please also repeat once with a mono microphone to confirm the fallback.

    The automated reproduction now passes for a silent channel 1 plus active channel 2, maximum device channel expansion, preservation of the strongest channel without attenuation, mono input, and a device that rejects channel expansion. The full desktop test suite also passes. Physical Apollo validation remains the final check.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      0