The sound effects play at the pitch they were written at

Red Planet's original AWE32 soundbanks are back in the tree, and the game's
sound effects are now generated from them instead of from an incomplete
one-off extraction.

AUDIO1.RES and AUDIO2.RES come from the 1996 release in the TeslaRel410
archive, hash-identical. AUDIO.INI has named them all along - they were
simply never carried into the port. tools/rp_sf2extract.py reads them and
regenerates both the WAV set and RP_L4/WTPresets.cpp, so the assets are
reproducible from the banks rather than hand-maintained.

Two things were wrong with the old set:

Pitch. Every shipped WAV was flat 44100 Hz with the banks' tuning discarded,
so 202 of the 219 zones played at the wrong speed - the worst by nine
semitones. The EMU8000's per-zone root key and tuning are now baked into
each file's declared sample rate, which is exact and needs no engine change.
Layers that were meant to be deep now are: a collision sub-thud that lasted
18 milliseconds at the wrong rate is a 0.66 second one at 1228 Hz.

Missing layers. 93 presets were short of zones and 176 were missing outright,
219 of 395. Nothing was lost recovering them - the 46 preset slots that
disappeared were all empty placeholders. The old files were also over-read,
running past the end of their sample into whatever PCM came next;
WellheadDrill02a was six seconds where the bank says eight hundred
milliseconds. Every one of the 395 files now matches its bank record exactly.

Also baked in: per-zone layer attenuation, and the static resonant low-pass
the EMU8000 applied in hardware.

Measured while doing it, and worth knowing: RP's banks contain no key-splits
at all - every multi-zone preset is a pure layer stack - and no preset has
more than four zones, which is what the engine's own "AWE appears to only
play 1st 4 voices" warning has been asserting since 1995.

Still to do: loop regions and the release fades, which 349 zones ask for and
which need new SAMPLEINFO fields. And voice demand per sound has gone from
about one zone to about two and a half, so the per-event alGenSources and
alDeleteSources churn roughly doubles - the BT tree measured pooling as the
fix for that, and a CPU win besides.

Builds clean. The extreme baked rates, 1228 Hz up to 88200, were checked
through the real path - libsndfile, alBufferData, alSourcePlay - and all
load. Not yet listened to on the pod.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-08-05 23:16:13 -05:00
co-authored by Claude Opus 5
parent ce1b0ab9c3
commit d361a0b8be
400 changed files with 744 additions and 408 deletions
+67 -24
View File
@@ -334,11 +334,16 @@ gains and four ITD pitch offsets. Every consumer of those values is commented ou
## 8. Where the original assets actually are
RP412 ships 223 loose `.wav` files loaded through libsndfile, a hand-maintained
preset table in `MUNGA_L4/L4AUDLVL.cpp` + `WTPresets.cpp`, and a **1-byte stub**
> **Update (2026-08-05):** both banks now live in `assets/RP411/AUDIO/`, and the
> WAV set and preset table are generated from them by `tools/rp_sf2extract.py`.
> The history below is kept because it is what made that possible, and because
> the `.SCP` sources are still only in TeslaRel410.
RP412 used to ship 223 loose `.wav` files loaded through libsndfile, a
hand-maintained preset table in `RP_L4/WTPresets.cpp`, and a **1-byte stub**
`AUDIO.RES`. The `front_audio_resource`/`rear_audio_resource` lines in AUDIO.INI
are not stale leftovers — they are the original authored configuration, and the
banks they name exist. They were simply not carried into `dist/`.
were never stale leftovers — they are the original authored configuration, and
the banks they name existed all along, simply never carried into the port.
Everything below is verified present in `../TeslaRel410/`:
@@ -420,27 +425,61 @@ points resolve, so F9/F11 are live rather than silently inert. The driver grants
explicitly via context attributes. RP412 still accepts the default; worth
revisiting if voice starvation shows up in a busy match.
### Asset-side — unlocked by §8, blocked until the banks are wired in
### Asset-side — measured against RP's own banks
These are all bank-derived, so they cannot even be assessed against RP412's flat
WAV set. Prevalences are BT's; RP's own numbers need measuring once its banks are
parsed.
RP's banks turned out to differ from BT's in ways that matter, so BT's
prevalences do not transfer. Measured directly (`tools/rp_sf2extract.py --stats`):
| # | Gap | What is lost |
| | RP total |
|---|---|
| Presets | 154 (67 in bank 1, 87 in bank 2) |
| Instrument zones | **395** |
| Multi-zone presets | 130 / 154 (84%) |
| **Key-splits** | **zero** |
| Max zones in any preset | **4** |
| Looping zones | 129 |
| Zones with an authored low-pass | 154 |
| Zones with layer attenuation | 97 |
| Zones with `releaseVolEnv` | 349 |
**Two RP-specific findings that change the work:**
1. **RP's banks contain no key-splits at all.** Every multi-zone preset is a pure
*layer* stack whose zones share one key range, all covering note 60. Combined
with RP's content never authoring a note (§9 above), key ranges are
unreachable here — so zone selection is a non-issue and every zone is simply a
simultaneous voice. BT's F1, which is largely about key-splits, mostly does
not apply; what applies is the plain zone count.
2. **Max 4 zones per preset**, which fits `PRESETINFO.samples[5]` as it stands.
No structural change was needed — and the engine's long-standing
`Warn(GetVoiceCount() > 4)` ("AWE appears to only play 1st 4 voices",
`L4AUDLVL.cpp:29`) matches the bank data exactly.
| # | Gap | RP status |
|---|---|---|
| F1 | Multi-zone preset collapse | The extractor keeps only the first sample-bearing zone: key-splits, layers and stereo pairs dropped. In BT, 68/115 and 94/126 presets are multi-zone |
| F2 | Root-key and tuning metadata dropped | Everything plays as if rooted at MIDI 60. In BT, ~83% of presets land ≥1 semitone off, worst 36 st. Fix is algebraically exact: bake tuning into each WAV's declared sample rate |
| F13 | Loop regions and release envelopes | Whole-buffer looping instead of authored sub-regions; instant cuts where 1.13.9 s releases were authored |
| F14 | Per-zone generators | `initialAttenuation` (**inverted scale in SBK**: 127 = full volume), `initialFilterFc`/`Q`, volume envelopes — `SAMPLEINFO` has no fields for any of it |
| F1 | Zones dropped | **fixed** — 93 presets were short; 176 zones recovered, 219 → 395 |
| F2 | Root key / tuning dropped | **fixed** — every shipped WAV was flat 44100 Hz; 202 of 219 checked zones were off, worst ~9 semitones. Tuning is now baked into each WAV's declared rate |
| F14 | Per-zone generators | **partly fixed**`initialAttenuation` (SBK inverted scale) and the authored `initialFilterFc`/`Q` resonant low-pass are baked into the PCM. Volume envelopes still dropped |
| F13 | Loop regions and release envelopes | **open** — needs `SAMPLEINFO` fields plus engine work (see below) |
**Ordering hazard, inherited from BT's F13:** loop-region support must ship *with
or before* multi-zone extraction. Some layer zones carry loop regions covering as
little as 1.5% of the sample; whole-buffer looping over those would replay an
entire explosion on every cycle.
**F13 remains the outstanding asset-side item.** It needs `loopStart`/`loopEnd`
and `releaseSec` on `SAMPLEINFO`, `AL_SOFT_loop_points` at buffer setup, and a
gain ramp on the stop path. 349 of 395 zones carry an authored release envelope
that is currently an instant cut.
Tooling already exists — `../BT411/tools/sf2extract.py` — but note it is the
source of F1 and F2 in its current form. It needs the multi-zone and tuning fixes
before being pointed at RP's banks.
The **ordering hazard** BT flagged — loop regions must land with or before
multi-zone extraction, or whole-buffer looping over a short loop region replays
an entire sample every cycle — is worth re-checking for RP now that zone counts
have gone up. RP's 129 looping zones should be measured for loop-region coverage
before F13 lands.
**A churn risk this work introduces.** Voice demand per sound has gone from ~1.1
to ~2.6 zones. `RequestAudioChannels` calls `alGenSources` per sound event and
`ReleaseSourceSet` calls `alDeleteSources` on release, so the allocation churn
roughly doubles. BT411 hit exactly this and its measured conclusion was that
raising the source budget was *not* the fix — **pooling** the sources was, and it
was a net CPU win (8.67 ms → 7.79 ms per frame). RP412 has the same churn
pattern and no pooling. Worth doing before any complaint arrives, not after.
## 10. A recovery path, in order
@@ -450,10 +489,14 @@ before being pointed at RP's banks.
against vRIO on COM1 with `RP412STEAM=0` — reaches gameplay and holds a
steady frame loop. **Not yet listened to on the pod**, which is the real
acceptance test: F4 in particular changes the level of everything.
2. **Wire RP's banks in.** Copy `AUDIO1.RES`/`AUDIO2.RES` from
`ALPHA_1/REL410/RP/AUDIO/` into `dist/AUDIO/` — the AUDIO.INI already names
them. Fix `sf2extract.py` for multi-zone (F1), tuning (F2) and loop regions
(F13) *before* regenerating, then rebuild the preset table.
2. ~~**Wire RP's banks in.**~~ **Done (2026-08-05).** Both banks are now in
`assets/RP411/AUDIO/`, hash-identical to the 1996 originals.
`tools/rp_sf2extract.py` extracts all 395 zones with tuning, layer
attenuation and the authored low-pass baked in, and regenerates
`RP_L4/WTPresets.cpp`. Verified: 176 zones recovered with none lost (the 46
preset slots that disappeared were all empty placeholders); every extreme
baked rate (1228 Hz 88200 Hz) accepted by libsndfile → `alBufferData`
`alSourcePlay` on the real runtime path. **Still open here: F13.**
3. **Recover the `.SCP` sources** from `sda4/RPLIVE/AUDIO/` into the asset
pipeline, so authored audio becomes editable again rather than frozen in
`RPL4.RES`.