Skip to content

feat: Audio tab in the Insert menu with Volume and Mute in background - #1270

Merged
hckrman101 merged 1 commit into
NotRexed:mainfrom
bvtterbroot:feat/audio-settings
Nov 10, 2026
Merged

hckrman101 merged 1 commit into
NotRexed:mainfrom
bvtterbroot:feat/audio-settings

Conversation

@bvtterbroot

Copy link
Copy Markdown
Contributor

What

The Insert menu gets a fourth tab, Audio, between Graphics and Gameplay, with two rows:

  • Volume, 0 to 100% in steps of 5 (default 100%). The mixer (host/nn_audio.c) scales each frame before the output, on every output channel (stereo or Mix for 5.1 when the default output has 5.1 speakers #1182's 5.1). A change ramps over one 5 ms frame, so it does not click. Gains stay at or below 1, so nothing clips. At 100% the frame is left alone.
  • Mute in background (default Off): silent while another window has the focus. An overlay in front (a tool or no-activate window, as host/nn_hid.c's game_in_front treats Steam's or Discord's) still counts as the game's window.

The host reads both through the renderer's option table every 100 ms, as new ids HOS_OPT_VOLUME = 27 and HOS_OPT_MUTE_BACKGROUND = 28 (gpu/vk/vk_api.h; 18 to 22 are the motion rows, 24 to 26 DLSS). Without the Vulkan renderer's menu the game plays at full volume. Both are saved in hookshot_options.ini (volume=, mute_in_background=).

menu_hit::tab_slot_w now takes the tab count instead of assuming 3, so the tab strip and its clicks fit four tabs.

Why

There was no way to set the game's volume apart from the Windows mixer, and no way to silence it while alt-tabbed. Both are common requests for a PC game.

#1240's Audio output row (Auto, Stereo, 5.1) would fit in this tab too. Whichever of the two lands second can move it here.

How it was tested

  • Machine: Windows 11, AMD Radeon RX 7900 XTX (driver 32.0.32015.7029), Ryzen 7 7800X3D.
  • In game: Kokiri Forest and Link's house. Volume from 100% to 0% and back: the level follows with no clicks, 0% is silent. Mute in background On: alt-tab to another window silences the game within about 0.1 s, switching back brings the sound back. Frame rate unchanged (the change is in the audio thread only).
  • Game-free: new tests/test_audio_volume.py compiles audio_apply_gain from the source and checks the ramp, the end gain, no clipping and equal gain on 2 and 6 channels. It also checks the option ids against the renderer's table and the menu's rows and defaults. tests/test_menu_hit.cpp gains checks for a four-tab strip. tests/run_all.py shows no new failures on Windows: the 3 that fail locally fail the same way on main.
  • readme: the options table and a note under it.

🤖 Generated with Claude Code

@bvtterbroot

Copy link
Copy Markdown
Contributor Author

Thanks for the careful review. All points are in 5976ed6 (the branch is also rebased on current main):

  • Overlays: the foreground check now keeps its own last_normal_was_us, as game_in_front does: a tool or no-activate window counts as the game's only while the last normal window in front was the game's. The mixer thread keeps its own copy of the state rather than sharing game_in_front's statics across threads. The decision is a small pure function (audio_front_decide), and tests/test_audio_volume.py checks the toast-behind-the-game case.
  • Volume curve: now squared (50% is -12 dB). The readme says so.
  • Stale comments: fixed (27, 28).
  • Tab widths: measured with GDI (Segoe UI, 13 px bold at scale 1): GAMEPLAY is 69 px and CONTROLS 68 px. In 77 px slots, GAMEPLAY's highlight came within 1.5 px of CONTROLS' title. kTabSide is now 70 (the [Q / LB] / [E / RB] hints are 49 / 47 px from 16 in), so the slots are 85 px with ~14 px between the highlight and the next title. test_menu_hit.cpp has the new edges. Checked in game at scale 2 (1440p).
  • Audio output in the Insert menu: Auto, Stereo or 5.1 #1240: agreed, its Audio output row belongs in this tab. Happy to move it if Audio output in the Insert menu: Auto, Stereo or 5.1 #1240 lands first, or @janvitos can move it if this one does.

wyatt-poole added a commit to wyatt-poole/Hookshot that referenced this pull request Nov 10, 2026
Conflicts in gpu/vk/vk_menu.cpp with main's Audio output row (NotRexed#1240) and
the Restart/Quit confirm (NotRexed#1266, NotRexed#1255): tx_deadzone and tx_audio_output
both kept; Esc on the Motion page goes back before the pending confirm
is cancelled; the Motion row opens its page after the Cheats row's test
in main's confirm chain. TAB_MOTION is now TAB_COUNT itself (one past
the strip), and tab_selected is sized by it, so a tab added before
TAB_COUNT (NotRexed#1270) moves the Motion page along instead of colliding.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BsKC8mjbyEUFAv6uy3SQiW
The Insert menu gets an Audio tab between Graphics and Gameplay:

- Volume, 0 to 100% in steps of 5, on a squared curve (50% is -12 dB): the
  mixer scales each frame before the output, on every output channel, and
  ramps a change over one frame so it does not click.
- Mute in background: silent while another window has the focus. A tool or
  no-activate window in front (Steam's or Discord's overlay, a toast, a tray
  flyout) counts as the game's only while the last normal window in front was
  the game's, as host/nn_hid.c's game_in_front decides it; the mixer keeps its
  own state.
- Audio output (NotRexed#1240) moves here from the Gameplay tab.

The host reads the two new rows through the renderer's option table every
100 ms (HOS_OPT_VOLUME 27, HOS_OPT_MUTE_BACKGROUND 28). Out of the box nothing
changes: 100% and off. Without the Vulkan renderer's menu the game plays at
full volume.

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

Copy link
Copy Markdown
Contributor Author

Rebased onto the ImGui menu (#1459) and #1240. Thanks for the offer. Since #1459 replaced the menu, I redid the menu part on top of current main (9edb3a5) as one commit, df2d84c, instead of carrying the old GDI tab-strip changes:

  • Audio is a tab of the new strip, between Graphics and Gameplay (TAB_COUNT 5). The tab widths come from the titles now, so the vk_menu_hit.h and test_menu_hit.cpp changes are gone.
  • Audio output moved from Gameplay into the Audio tab, and tests/test_audio_output_menu.py expects TAB_AUDIO.
  • Mixer (host/nn_audio.c): unchanged from the reviewed version. It has the squared curve and the overlay rule with its own last_normal_was_us, applied on out_channels. The ids are still 27/28, and nothing on main uses them.
  • Tests: test_audio_volume.py declares RUN_ALL_ARGS like the other tests now. run_all.py: 258 passed. The 3 that fail locally on Windows (test_nup_out_of_space, test_fpx_sticky, test_precompile_compute_guard) fail the same way on main.
  • In game: built at 484cfd1 plus this change and played on Windows 11 with an RX 7900 XTX. The Audio tab shows its three rows in the new style, the volume slider and Mute in background work, and Audio output works as before. The rebase onto 9edb3a5 merged cleanly (the only conflict was run_all.py's move to per-test declarations), so I did not rebuild for it.

@hckrman101
hckrman101 merged commit 52ca067 into NotRexed:main Nov 10, 2026
getzabelz added a commit to getzabelz/Hookshot that referenced this pull request Nov 10, 2026
The Insert menu's Gameplay tab gets a Text size row (Original, 110%, 125%, 150%,
175%), under Text speed. It scales the text of dialogue, Navi and signs.

nx2/oot_patches.py's letters' block of f_80b1e140 (a LetterAnimControl's step,
already patched for Text speed) now also calls hos_oot_text_pane with the control
(x19). host/oot_text_size.c scales the diagonal of the pane wrapper's 2D scale
(+0x28, +0x38; 0.8 for dialogue) from the game's own value: never twice, again
from the game's value whenever the game writes it, and back to it at Original. A
field never scaled is not touched at Original. HOS_TEXT_SIZE=<0..4> stands over
the menu, as HOS_TEXT_SPEED does.

The option id is HOS_OPT_TEXT_SIZE (10). tests/test_rumble_menu.py's id table
now reads one-name HOS_OPT_* enums too, so it still checks that no option id is
answered twice. tests/test_audio_volume.py (NotRexed#1270) reads them the same way now.

Tests: tests/test_text_size.py compiles host/oot_text_size.c against a fake guest
memory (Original, each size, the game writing its value again, back to Original,
missing control/wrapper/pane, more panes than the table keeps).
tests/test_text_speed.py checks the hook gets x19. Played on Windows (this change
applied to the v2.0.11 builder's source): dialogue
and signs grow at each size and go back at Original; the choice is kept in
hookshot_options.ini.

Assisted-by: Claude Code (Claude Opus 5.5)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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