Skip to content

fix(voice): honour the volume argument on Linux players#1548

Closed
m8ryx wants to merge 1 commit into
danielmiessler:mainfrom
m8ryx:fix/linux-voice-volume
Closed

fix(voice): honour the volume argument on Linux players#1548
m8ryx wants to merge 1 commit into
danielmiessler:mainfrom
m8ryx:fix/linux-voice-volume

Conversation

@m8ryx

@m8ryx m8ryx commented Jul 19, 2026

Copy link
Copy Markdown

playAudio() accepts a volume multiplier and threads it all the way through to the player:

const proc = spawn(player.path, player.buildArgs(tempFile, volume))

The macOS branch honours it:

{ cmd: "afplay", buildArgs: (file, volume) => ["-v", volume.toString(), file] }

Every Linux candidate declares buildArgs: (file) => ... and silently discards the argument:

{ cmd: "ffplay", buildArgs: (file) => ["-nodisp", "-autoexit", "-loglevel", "quiet", file] },
{ cmd: "mpg123", buildArgs: (file) => ["-q", file] },
{ cmd: "paplay", buildArgs: (file) => [file] },
{ cmd: "aplay",  buildArgs: (file) => ["-q", file] },

So per-voice volume in the config, and the volume field on the notification API, do nothing on Linux — playback is always at system level. There is no error; the value is simply dropped. FALLBACK_VOLUME = 1.0 and per-voice entry.volume ?? 1.0 confirm the intended scale is a multiplier where 1.0 is normal, matching afplay's -v.

Fix

Map that multiplier onto each player's own integer scale, taken from the tools' own help output:

$ ffplay -h | grep volume
-volume volume      set startup volume 0=min 100=max

$ paplay --help | grep volume
      --volume=VOLUME    Specify the initial (linear) volume in range 0...65536

A shared helper clamps and rounds:

function scaleVolume(volume: number, max: number): number {
  if (!Number.isFinite(volume) || volume < 0) return max
  return Math.min(max, Math.round(volume * max))
}

A non-finite or negative value falls back to the player's normal level rather than silencing playback, which seemed the safer failure direction for a notification system.

Deliberately not changed

  • aplay has no volume-set option — only --disable-softvol. Volume cannot be honoured on this fallback.
  • mpg123 scales with -f, but I could not verify its range on the machine I tested on, and guessing a factor risks making playback quieter for everyone else. Left as-is, with a comment marking it.

Both are noted inline so the gap is visible rather than silent.

Verification

Flags confirmed present rather than assumed — a deliberately misspelled variant is rejected while the real one is accepted:

$ ffplay -nodisp -autoexit -loglevel error -volume 50 tone.mp3     # accepted
$ ffplay -nodisp -autoexit -loglevel error -vollume 50 tone.mp3
Failed to set value '50' for option 'vollume': Option not found

$ paplay --volume=32768 tone.mp3                                    # exit 0
$ paplay --vollume=32768 tone.mp3
paplay: unrecognized option '--vollume=32768'

scaleVolume covered by unit checks: 1.0 → max, 0.5 → half, 0 → 0, 2.0 → clamped (no amplification), NaN → normal, -1 → normal.

bun build VoiceServer/voice.ts --target=bun compiles clean in place.

No change to macOS behaviour — the darwin branch is untouched.

playAudio() threads a volume multiplier through to buildArgs(), and the macOS afplay branch uses it, but every Linux candidate declares buildArgs: (file) => and silently discards it. Per-voice volume config and the notification API's volume field therefore do nothing on Linux — playback is always at system level, with no error.

Map the multiplier (1.0 = normal, matching afplay -v) onto each player's own integer scale, read from their help output: ffplay -volume is 0..100, paplay --volume is linear 0..65536. A shared helper clamps and rounds; a non-finite or negative value falls back to normal rather than silencing playback.

aplay has no volume-set option and is left alone. mpg123 scales with -f but its range was not verified here, so it is left alone rather than guessing a factor — both noted inline.

No change to macOS behaviour.
@danielmiessler

Copy link
Copy Markdown
Owner

Ported into source as-is, thank you. The per-player scaling with clamping is right, and the restraint on mpg123 (unverified range) and aplay (no flag) is the kind of honesty we want in the tree; both comments survive. Merge-back note: the public repo is generated from private source at release, so this closes as ported, with credit in the README. Ships with the next release.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants