Skip to content

feat(menu): Volume slider for the game's sound - #1737

Closed
getzabelz wants to merge 2 commits into
NotRexed:mainfrom
getzabelz:feat/audio-volume
Closed

getzabelz wants to merge 2 commits into
NotRexed:mainfrom
getzabelz:feat/audio-volume

Conversation

@getzabelz

@getzabelz getzabelz commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

What

A Volume row on the Insert menu's Gameplay tab (above Audio output): a slider from Muted to 100% in steps of 5, 100% by default (the game's own level). It applies at once and is kept in hookshot_options.ini as volume=. Help line: "Master volume level."

The Insert menu's Gameplay tab with the new Volume row at 35%

How

  • host/nn_audio.c: au_volume_apply scales the final mix in render_frame, after the limiter and before the samples become int16, with rx_option(HOS_OPT_VOLUME, 100) read once per 5 ms frame.
    • The gain is the square of the share (50% = -12 dB, 25% = -24 dB), so each step of the slider sounds about as large as the last.
    • It ramps from the last frame's gain to the new one over the frame, like the game's own volume changes, so dragging the slider does not click.
    • At 100% the samples are left untouched (early return).
  • gpu/vk/vk_api.h: HOS_OPT_VOLUME (44), after HOS_OPT_DISCORD.
  • gpu/vk/vk_replay.cpp: the option table answers it (clamped 0..100). An id the table does not answer reads as 0, which would silence the game, so the test checks for this line.
  • gpu/vk/vk_menu.{h,cpp}: opts.volume, the row (preset_exempt, not a restart row), tx_volume, and the slider / stop-at-ends lists.
  • readme.md: the options table and a line in the option notes.

Tests

  • New tests/test_audio_volume.py (registered in run_all.py with --cc gcc): checks the row, the default, the slider flags, the enum and the renderer's answer, and that the call sits after the limiter. It also compiles the real au_volume_gain / au_volume_apply and checks 100% = unchanged, 0% = silence, 50% = 0.25, monotonic steps, and the ramp up and down without overshoot.
  • Full run_all.py on Windows: everything passes except nup_paths, builder_dlss_update, fpx_sticky and precompile_compute_guard, which fail the same way on a clean main here (local environment).
  • Built with the v2.0.13 builder and played in game: the slider changes the volume at once, Muted is silent, 100% sounds as before, and the value persists across restarts.

getzabelz and others added 2 commits November 10, 2026 18:44
A Volume slider on the Insert menu's Gameplay tab (Muted, 5% to 100%, in
steps of 5; 100% by default, the game's own). It applies at once and is
kept in hookshot_options.ini as volume=.

host/nn_audio.c scales the final mix after the limiter, before the samples
are made, with HOS_OPT_VOLUME read once per 5 ms frame. The gain is the
square of the share (50% is -12 dB), so each step sounds about as large as
the last, and it ramps over a frame like the game's own volume changes, so
dragging the slider does not click. At 100% the samples are untouched.

tests/test_audio_volume.py checks the row, the renderer's answer for the
new id (an unanswered id would be 0: silence) and runs the real gain and
ramp code.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@getzabelz

Copy link
Copy Markdown
Contributor Author

Closing this: #1270 (merged today) already adds a Volume slider in the new Audio tab, with the same curve (the square of the share) and ramp, plus Mute in background. It also saves as volume=, so players keep their setting. Thanks for the review!

@getzabelz getzabelz closed this Nov 10, 2026
@getzabelz
getzabelz deleted the feat/audio-volume branch November 10, 2026 22:33
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.

1 participant