feat(audio): a Mono choice on the Audio output row #136
No reviewers
Labels
No labels
area/accessibility
area/audio
area/input
area/install
area/menu
area/renderer
area/saves
area/upscaling
good first issue
gpu/amd
gpu/igpu
gpu/intel
gpu/nvidia
help wanted
kind
bug
kind
chore
kind
docs
kind
feature
kind
performance
kind
question
needs measurements
platform/linux
platform/steam-deck
platform/windows
preview
priority
critical
priority
high
priority
low
status
blocked
status
confirmed
status
duplicate
status
invalid
status
needs-info
status
wontfix
testing wanted
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Longshot!136
Loading…
Reference in a new issue
No description provided.
Delete branch "simonwjackson/Longshot:feat/mono-audio"
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?
Why
Players who hear with one ear, or use one earbud, can miss sounds panned to the other side. Mono plays the stereo average in both ears. It now belongs in the existing Audio output row, as HumanBean suggested, rather than in a second control.
Carried over from https://github.com/NotRexed/Hookshot/pull/2860.
What changed
vk_menu.cpp/vk_menu.h: Audio output now offers Auto / Stereo / 5.1 / Mono. Removed the separate Mono audio row andmono_audiofield. Mono and Stereo share a restart-tracking value, so switching between them does not ask for a restart.host/nn_audio.c: Mono selects a two-channel layout at startup and uses the PR's existing 32-bit stereo average live. Auto, Stereo and 5.1 never apply that average. Six-channel output remains untouched.HOS_OPT_AUDIO_OUTPUT;HOS_OPT_MONO_AUDIOis gone.gpu/vk/menu_i18n.h: the Mono hint (The stereo average in both ears. Changes live on stereo output; restart from 5.1.) in all 13 tables, and theMonovalue label in the five whose word differs (ru, ja, ko, zh-Hans, zh-Hant; likeStereo, de/it/nl/pl/pt/es/fr keepMono); each language's count inkLangsfollows.gpu/vk/vk_menu.cpp: the free function that reads the value a row's restart is tracked by isrow_restart_value(const Row&), so it no longer shares a name withmenu_restart::State::restart_value(slot, v), which sets a slot's start value;tests/test_audio_output_menu.pyandtests/test_menu_restart_slots.pyread the new name.Flags and settings
audio_outputExisting saved values keep their meanings; a file without
audio_outputstill uses Auto.HOS_AUDIO_CHANNELSretains main's channel-layout override. No migration or fallback read of the superseded PR-onlymono_audiokey is added.Measurements
main:
e65cb1e00b(2026-11-13), the base of this PR nowthis PR: feat/mono-audio @
0fd02afbac(the measured commit, before the rebase; the head is nowe457a41df0)Why the numbers still hold for
e457a41d: since0fd02afbthe PR's audio and menu code is unchanged apart from the menu's translations and a function rename (row_restart_value); main's changes sinced9648863(input hold-to-toggle, saves, VRAM mirror, setup, scripts, docs) do not touchhost/nn_audio.cor the audio path. Not measured again on the rebased head.Build fingerprint:
mono-output-review (source 528a52f6). Original PR head:df5edcd34bda7ca8f1381d9bf4a8db8a186cf998.Baseline: this PR @
0fd02afb, Audio output StereoCompared with this PR @
0fd02afb, Audio output Mono in the same live process. This isolates the new row’s live switch; main has no Mono choice. The older main/PR comparison below is historical, not a measurement of this new head.Hardware: RX 7900 XT 20 GB, RADV Mesa 26.2.3; Ryzen 5 7600X, 32 GB RAM; NixOS 26.11 / Linux 7.2.7. Private headless Sway/Xwayland, copied Castle Town save, stationary at the wooden door. 1920×1080, 100% render resolution, game's upscaler, FG/HDR Off, Uncapped, VSync Auto, compute mesh fallback.
HOS_AUDIO=0: nothing played through speakers. A fresh private source build; shader preparation used a 60-second prediction budget, with remaining pipelines built in play.Method: warmed in the scene, then Stereo → Mono → Stereo → Mono, five seconds settling and 20 seconds sampled per choice. FPS is the median of the last five native presents-log samples. PCM is captured after mixing; counts are from the stereo sample pairs, with nonzero audio required. Audio stays on aka; only counts and screenshots are published.
0fd02afb, Audio output Stereo0fd02afb, Audio output MonoNative presents-log samples
The Audio tab screenshot shows the single row on Mono. The same process restored stereo separation and returned to zero unequal pairs on Mono. These numbers do not establish a cost on all hardware or in other scenes; no 1% lows are claimed.
Historical measurement, before the menu revision: main
316f9319/ original PRdf5edcd3, two interleaved runs each in the same scene. Main FPS medians 53.50 / 54.20 (median 53.85); original PR Mono 53.80 / 54.00 (53.90). Main unequal PCM pairs 960,966 / 960,949; original PR Mono 0 / 0. The PR history retains those original logs and captures. No old screenshot is presented as the new Audio tab.Testing
Tested: (by the author at
0fd02afb; nothing was run on the rebased heade457a41dyet, and nothing was built or run on the PC that rebased it)test_menu_i18n.py). Includes extrema, silence, all non-Mono choices unchanged, live transitions, six-channel identity, missing/old/new saved values, environment precedence and restart-value normalization.d9648863: the knowntest_linux_tools.pyNix PATH problem (readlink/dirnameabsent from/usr/bin:/bin), andtest_menu_i18n.py, whose Debug-tools help translations were left behind when #138 added damage scaling (main has since fixed that table in #273). No new failures.test_display_pointerandtest_texture_staging_publicationpass on both trees.git diff --checkpasses. Native fixtures compile with warnings as errors; the full Linux game and renderer build passed. No standalone formatter or typechecker is configured.korri*unit changed.Not tested: anything on
e457a41d(the rebase ontoe65cb1e0, the Mono hint and label translations and therow_restart_valuerename are checked by reading only; the suite and a build are for CI or the author); the translated Mono hint on screen; Windows build/gameplay, physical 5.1 speakers, listening by ear, other GPU families and full playthroughs. Channel-layout restart behavior is covered by game-free checks; the live proof uses stereo output.Needs other testers: Existing request T-7YW2, Windows / llvm-mingw / stereo output: compare Audio output Stereo and Mono, switch live, reload the saved choice, and report non-silent L/R equality counts and FPS. The request is not a passing result.
Screenshots
Risk and rollback
Mono removes stereo positioning and can cancel opposite-phase samples. Choose Stereo to restore stereo live on a stereo output. For a different channel layout, restart as on main. Revert the PR to remove the feature.
Reviewer notes
Rebased onto main
e65cb1e00b8116363326988ca05db15822f0924eas two linear commits: the PR's change (the original commitdf5edcd3, the mergebc9285c2and0fd02afbsquashed into one, Simon's authorship and trailers kept) and one commit for review 1187's suggestions (Mono translations,row_restart_value). The rebase met two conflicts, both additions side by side:gpu/vk/vk_api.hkeeps #137'sHOS_OPT_HOLD_TOGGLE = 54and this PR'sHOS_OPT_AUDIO_OUTPUT = 57(IDs 54, 55, 57 stay unique), anddocs/tested-configurations.mdkeeps both PRs' rows. No publicMkwVkHost/MkwVkRendererstructure changed (only an enum is added tovk_api.h), so noMKWVK_API_VERSIONbump. Auto→Mono shows "Restart needed" as Auto→Stereo does on main; left as it is on purpose. The translations, old and new, follow the existing machine-translated tables and have not had native-speaker review.Issues
None.
Checklist
For everyone
type(scope): description).main.python -I -B tests/run_all.pyrun and passing (any failure that also fails onmainis named above). (Run by the author on0fd02afb; not yet on the rebased heade457a41d: CI or the author.)third_party/untouched (a new folder with aPROVENANCE.mdinstead).If applicable
Assisted-by:/Co-Authored-By:trailers.docs/tested-configurations.mdfor each measured run.tests/that reads changed source updated in this PR, not weakened.0fd02afb. Windows: T-7YW2, no result yet.)Code review only (I didn't build or run anything).
The change is small and correct.
audio_monosums in 32 bits, so full-scale samples can't overflow. It runs after the gain and beforeoutput_frame, so the device and theHOS_AUDIO_DUMPcapture both get the mixed signal. It returns early when the row is Off or the output isn't 2 channels, so 5.1 is left alone. The row is saved throughini_keyasmono_audio=, its README line is in menu order, and ID 57 is unique on current main (main hasn't touched these files since316f9319, and a merge-tree against main is clean). Only an enum is added tovk_api.h, andMkwVkHost/MkwVkRendererdon't change, soMKWVK_API_VERSIONdoesn't need a bump.pr statussuggests adding that box, but it doesn't apply.I found no blockers in the code. The inline comments are nits. It stays WIP until the open boxes are settled: Longshot measurements (main against this PR, interleaved), the Windows test request, and the Windows build box. I'm leaving this as a comment, not an approval, because the PR is WIP.
@ -94,6 +94,7 @@ struct Options {int discord = 1; // (longshot) the Discord status (host/discord_rpc.c): 1 on, shown while a Discord client runs on this PCint debug_tools = 0; // (longshot) the Debug tab's tools (#785: flight, free camera, teleport, inventory): 0 off; HOS_OOT_CHEATS=1 also turns them onint volume = 100; // (longshot) the game's sound in percent, 0 to 100 (host/nn_audio.c: HOS_OPT_VOLUME, 27)int mono_audio = 0; // final stereo output only; 5.1 is unchangedNit: the neighbouring fields name their reader and option ID, e.g.
// (longshot) ... (host/nn_audio.c: HOS_OPT_MONO_AUDIO, 57). Doing the same here (and adding HOS_OPT_MONO_AUDIO to the include comment at host/nn_audio.c:21) keeps the cross-reference greppable.@ -1336,2 +1345,4 @@if (gain_frames-- <= 0) gain_want = audio_player_gain(), gain_frames = 20; /* (the menu read every 100 ms) */gain = audio_apply_gain(pcm, FRAME, out_channels, gain, gain_want);int rx_option(int id, int otherwise);audio_mono(pcm, FRAME, out_channels, rx_option(HOS_OPT_MONO_AUDIO, 0));Nit: this asks the menu on every 5 ms audio frame, while Volume and Mute in background are read every 100 ms through
gain_frames("the menu read every 100 ms"). It's cheap either way. Still, reading it next togain_want(e.g.mono_want, refreshed in the samegain_framesbranch) would follow the loop's pattern, and so would declaringrx_optiononce at file scope instead of a third block-scope declaration. If you do that, the source-reading check intests/test_audio_mono.pyneeds the same update.@ -0,0 +56,4 @@assert re.search(r'\{TAB_AUDIO, "Mono audio", &opts.mono_audio, 0, 1, 1, tx_onoff,\s*'r'"[^"]+", nullptr, false, true\}', read("gpu/vk/vk_menu.cpp"))# Main has assigned the fork's original id 46 to Button prompts.Stale comment: on Longshot the ID is 57 (53 is HOS_OPT_FPS_CAP), and the "id 46 / Button prompts" history is from the Hookshot branch. Either say what the assert checks (the Mono ID is unique among the explicit
HOS_OPT_IDs) or drop the comment.Added a fresh interleaved main/PR Linux measurement session at the named commits, the last-five-sample FPS medians, feature proof and inspected screenshots. Windows testing requests are open; the description names the remaining gaps. The branch also rebases cleanly onto main
1be180bbwith an identical stable patch ID and passing focused tests; the force-with-lease push is still gated, so the measured remote head stays pinned. No new baseline FPS is claimed for the local rebase.WIP: feat(audio): add live mono audio for stereo outputto feat(audio): add live mono audio for stereo outputDon't we already have a setting for 5.1 audio? maybe this should be in there
feat(audio): add live mono audio for stereo outputto feat(audio): a Mono choice on the Audio output rowYes—Mono is now a choice on Audio output alongside Auto, Stereo and 5.1, rather than a separate row. Existing saved values keep their meanings; Mono is 3, and Mono/Stereo switches live on stereo output. Pushed
0fd02afafter merging main normally. The updated description includes the Audio tab screenshot and non-silent post-mix captures: Mono has 0 unequal L/R frames in both samples; Stereo restores separation. The full suite has no new failures; both inherited failures are reproduced on untouched main.The code is sound. Folding Mono into Audio output is clean: saved values 0–2 keep their meanings, Mono only acts on a two-channel output, the 32-bit average can't overflow, and treating Mono like Stereo for restart tracking is right. The changes I'm asking for are in the checklist, not the code.
Must change before merge
host/nn_audio.c,gpu/vk/vk_menu.cpp,gpu/vk/vk_api.h), so the template box Host code builds on both Windows (llvm-mingw) and Linux, with UTF-8 paths applies.pr statusalso points it out. The description replaces it with "Linux host build verified; Windows remains assigned to T-7YW2" and ticks that. Please put the template's wording back and leave the box unticked until the Windows build is done, with T-7YW2 named beside it. Another option is a local llvm-mingw cross-build, which needs no Windows machine. The C looks portable, so I expect it to pass, but the box has to say what was actually checked.514591a3, docs only since your merge) or leave the box unticked with the reason beside it.Suggestions (non-blocking)
Monovalue label, have no entries in the 13 translated tables. The other three Audio output hints are in all of them, so a translated menu shows this one hint in English (inline).restart_value(const Row&)reads a value, whileState::restart_value(slot, v)sets one. Both are in the same file, so a different name would read better (inline).CI for
0fd02afbwas still pending when I reviewed it. I only read the code; I didn't run any checks.@ -1130,2 +1130,3 @@MENU_HINTS(hn_audio_output, 0, "5.1 when the default output device has 5.1 or more speakers, else stereo.","Stereo, whatever the device has.", "5.1, whatever the device has. Mixed down on stereo speakers.")"Stereo, whatever the device has.", "5.1, whatever the device has. Mixed down on stereo speakers.","The stereo average in both ears. Changes live on stereo output; restart from 5.1.")This hint, and
Monointx_audio_output, have no entries in the 13 tables inmenu_i18n.h. The other threehn_audio_outputhints are translated in every table, so a translated menu shows only this one in English. Please add the entries alongside the row help you already updated.@ -1606,2 +1607,4 @@int restart_slot[kRows]; // (a row's slot in restart_state, -1 none)const char* restart_names[mr::kMax];// Mono and Stereo use the same channel layout; their live mix does not need a restart.int restart_value(const Row& r) {Nit:
menu_restart::State::restart_value(slot, v)(used inprompts_at_once) sets a slot's start value, and this freerestart_value(row)reads the value a row is tracked by. Having both names in one file makesrestart_state.restart_value(...)andrestart_value(r)easy to mix up. Mayberestart_tracked_valueorrestart_layout?@ -1607,1 +1608,4 @@const char* restart_names[mr::kMax];// Mono and Stereo use the same channel layout; their live mix does not need a restart.int restart_value(const Row& r) {return r.value == &opts.audio_output && *r.value == 3 ? 1 : *r.value;Minor: if the game started on Auto with a stereo device (start value 0), choosing Mono gives 1 != 0, so the row says a restart is needed even though Mono already applies live. Main does the same for Auto→Stereo, so this isn't new. It could be fixed by tracking Auto by the layout it resolved to. Not blocking.
0fd02afbace457a41df0Review 1187 addressed, rebased and pushed: head is now
e457a41d, on maine65cb1e00b8116363326988ca05db15822f0924e.bc9285c2and0fd02afbare squashed into one commit, with Simon still the author, and the review fixes are a second commit on top. Two conflicts came up, both additions side by side. Ingpu/vk/vk_api.h, #137'sHOS_OPT_HOLD_TOGGLE = 54sits next toHOS_OPT_AUDIO_OUTPUT = 57. Indocs/tested-configurations.md, both PRs' rows are kept. The box has the template's wording again and is ticked.testing wantedlabel), and no comment reports a Windows build. The box waits on T-7YW2.gpu/vk/menu_i18n.h, with the same terms as the neighbouring Audio output entries. TheMonolabel is added where the word is written differently (ru, ja, ko, zh-Hans, zh-Hant). As withStereo, the de/it/nl/pl/pt/es/fr tables keep the English word. Each language's count inkLangsis raised to match.restart_value(const Row&)is renamedrow_restart_value, and its three callers are updated. So are the checks that read it by text:tests/test_audio_output_menu.pyandtests/test_menu_restart_slots.py.The description has the template's wording again on the other reworded boxes too. The
run_all.pybox is unticked because the author's run was on0fd02afb, so CI or the author still has to run it one457a41d. Measurements now explain why the0fd02afbnumbers still hold for the rebased head. No tests, checks or builds were run on the PC that did this rebase: the changes were checked by reading only.Light re-review of
6606b94b+e457a41dagainst review 1187. The code is right. I'm not approving yet only because of two unticked boxes (below).Rebase (
6606b94b): its added and removed lines againste65cb1e0are the same as0fd02afb's against its based9648863. Only context lines differ, at the two conflicts.vk_api.hkeeps #137'sHOS_OPT_HOLD_TOGGLE = 54and addsHOS_OPT_AUDIO_OUTPUT = 57. The IDs 51–55 and 57 are all unique, and none of #137's lines are removed.tested-configurations.mdkeeps both PRs' rows.Review fixes (
e457a41d)hn_audio_outputstring invk_menu.cpp. It sorts between "The speakers…" and "The sticks…" in all 13 tables, andMonosorts between "Monitor's shape" and "Motion". The translations read well and use the same terms as the Audio output hint beside them in each table (Moно/モノラル/모노/单声道/單聲道).{"rows in each table, and everykLangscount matches (530, 536, 538, 533, 528, 531, 522, 531, 545, 538, 543, 538, 538).Monois left out where the word is the same, which matches how the tables handleStereo(de/it/nl/pl don't have it). Nit: the description and the fix comment say pt/es/fr keepStereotoo, but they translate it (Estéreo,Stéréo). Those three skipMonoonly because the word is spelled the same there, which is fine.row_restart_valuerename is complete. The onlyrestart_value(calls left aremenu_restart::State::restart_value(slot, v)(vk_menu.cpp 1939/4253/4701, tests/test_menu_restart.cpp). The text cuts intest_audio_output_menu.pyandtest_menu_restart_slots.pymatch the new source.Boxes: both unticked boxes are true as written. The Rebased box is true (
e65cb1e0is main's tip). But they block a merge as is (CONTRIBUTING: every For everyone box ticked, and every If applicable box kept must be done):run_all.pyone457a41d: CI's Python source checks run only onworkflow_dispatch, and they were skipped on this head. A maintainer dispatch (or the author's run) can settle it.While the
run_allbox is unticked, the PR should be WIP (pr wip 136) or get that run soon. I'll approve once both are settled. No further code changes are needed.