Home and End are the bass knob

The volume keys wanted a partner, and the bass trim could not be one as it
stood: it scaled the sample data as it loaded, so by the time anyone pressed
a key the audio was already sitting in OpenAL buffers and nothing short of a
restart would move it.

So the trim is now a per-zone gain applied in the mix instead. Each buffer's
depth - how much of the low band it occupies - is still worked out once at
load from its playback rate, but the trim itself is read every frame, which
is what lets Home and End move it while sounds are playing. It is the better
form regardless: no rewriting of sample data, and no quantisation on top of
audio that has already been through one gain stage.

Home raises, End lowers, in steps of 0.05, and the setting is written to
bass.cfg beside the exe exactly as the volume writes volume.cfg. Together
with PageUp and PageDown that is the amplifier and the crossover the
cabinets had in hardware and a desktop does not.

Builds clean, runs, and neither knob fires unprompted.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-08-06 00:08:55 -05:00
co-authored by Claude Opus 5
parent 4e8392fcfb
commit 25e25260b1
10 changed files with 214 additions and 72 deletions
+15 -11
View File
@@ -126,19 +126,23 @@ Every one of these buttons is also **clickable on screen** — the red
strips around each MFD and the amber strips beside the map are the same
addresses, and they light up when the game commands their lamps.
### Volume
### Volume and bass
`Page Up` and `Page Down` raise and lower the master volume in steps of
0.05, from silent up to 2.0. The level is written to `volume.cfg` beside
the exe as you change it and is picked up again next launch, so it stays
where you left it. `RP412AUDIOVOLUME` in `environ.ini` sets where a
machine starts out before anyone touches the keys; delete `volume.cfg` to
go back to it.
| Key | Does |
|---|---|
| `Page Up` / `Page Down` | master volume, in steps of 0.05 from silent to 2.0 |
| `Home` / `End` | bass, in steps of 0.05 from as-authored down to none |
The cabinets had no volume control of their own — they ran the game at
unity and left level and tone to an external amplifier and a 3-way
crossover. These keys stand in for the amplifier; `RP412AUDIOBASS` in
`environ.ini` stands in for the crossover's low band. See
Both take effect as you press them, including on sounds already playing,
and both are written beside the exe as you change them — `volume.cfg` and
`bass.cfg` — so they stay where you left them. `RP412AUDIOVOLUME` and
`RP412AUDIOBASS` in `environ.ini` set where a machine starts out before
anyone touches the keys; delete the matching `.cfg` to go back to them.
The cabinets had neither control. They ran the game at unity and left
level and tone to an external amplifier and a 3-way crossover, so these
keys are standing in for hardware the arcade had and you do not: Page
Up/Down is the amplifier, Home/End is the crossover's low band. See
[SOUND.md](SOUND.md).
### Not bound by default
+19 -10
View File
@@ -522,7 +522,10 @@ the pod played it:
| Knob | Stands in for | Range | Default |
|---|---|---|---|
| `RP412AUDIOVOLUME` / **PgUp**, **PgDn** | the amplifier's volume | 0.0 2.0 | 1.0 (unity, as the pod ran) |
| `RP412AUDIOBASS` | the crossover's low band | 0.0 1.0 | 1.0 (as authored) |
| `RP412AUDIOBASS` / **Home**, **End** | the crossover's low band | 0.0 1.0 | 1.0 (as authored) |
Both step live by 0.05 and persist beside the exe (`volume.cfg`, `bass.cfg`),
which then win over `environ.ini` next launch.
`RP412AUDIOVOLUME` is a straight `alListenerf(AL_GAIN, …)` at renderer init.
There was no listener gain call at all before, so the default is a genuine
@@ -549,15 +552,21 @@ not OpenAL Soft, and it rejects both `AL_FILTER_HIGHPASS` and
the neat answer, carrying the authored brightness model on `GAINHF` and the trim
on `GAINLF` across the one direct filter a source gets. It is not available.
So the trim scales sample data as buffers load. That works because of *how* RP's
low end is built: the weight sits in discrete deep layer zones whose per-zone
tuning bakes out to a very low playback rate — 13 zones below 8 kHz, 3.4 to 5.2
octaves below their recorded pitch, against 81% of the set at 22 kHz and above.
A zone's baked rate is a reliable proxy for which band it occupies, so
attenuating the low-rate zones is a real low-band trim rather than a blunt
overall cut. The ramp is untouched at/above 22050 Hz, full trim at/below
5512 Hz, log-interpolated between. At `0.7` that is 3.1 dB on the deepest
layers, 1.4 dB at 11 kHz, nothing from 22 kHz up.
So the trim is a **per-zone gain applied in the mix**. That works because of
*how* RP's low end is built: the weight sits in discrete deep layer zones whose
per-zone tuning bakes out to a very low playback rate — 13 zones below 8 kHz,
3.4 to 5.2 octaves below their recorded pitch, against 81% of the set at 22 kHz
and above. A zone's baked rate is a reliable proxy for which band it occupies,
so attenuating the low-rate zones is a real low-band trim rather than a blunt
overall cut. Each buffer's *depth* is fixed at load from its rate; the trim
itself is read at mix time, which is what lets Home/End move it while sounds are
playing. The ramp is untouched at/above 22050 Hz, full trim at/below 5512 Hz,
log-interpolated between. At `0.7` that is 3.1 dB on the deepest layers,
1.4 dB at 11 kHz, nothing from 22 kHz up.
(An earlier revision scaled the PCM at load instead. That could never be a live
knob — the samples are already in OpenAL buffers by the time a key is pressed —
and it risked quantisation on top. The gain form is both live and cleaner.)
**A caution for anyone extending the EFX work:** `EFX_Initialize` reads
`alGetError()` after configuring the scratch filter, so asking for a filter type