Audio (AUDIO_FIDELITY F14 residue): bake the authored static resonant

low-pass into the zone WAVs

232 zones author initialFilterFc(8)/initialFilterQ(9) (SBK 0..127) -- the
fixed resonant low-pass the EMU8000 applied in hardware; the port played
them unfiltered (flagship: the LaserLoaded charge hum, fc=57 Q=87, much
brighter/harsher than the arcade).  The extractor now applies an RBJ 2-pole
low-pass per zone: cutoff = the AWE NRPN curve (100 + fc*7900/127 Hz),
resonance = SBK Q -> 0..+12 dB peak [T3 curve, endpoints exact], designed at
the zone's baked rate so note-60 playback reproduces the hardware's absolute
cutoff (pitch-shifted notes carry the filter -- same limitation as the
rate-baked tuning).  Synthetic sweep verified: flat lows, +5.4 dB at the
authored 3.6 kHz cutoff, -22 dB @10 kHz, -46 dB @14 kHz.

Whole bake pipeline (filter -> attenuation) now runs in float with ONE int16
conversion + peak normalization: an int-per-stage draft hard-clipped 165 of
232 zones (resonance overshoot, worst 3% of samples); now only pre-existing
source-material clipping remains (14 files).  160 WAVs re-baked; the preset
table is unchanged (no engine rebuild).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-16 15:36:44 -05:00
co-authored by Claude Opus 4.8
parent a11a697824
commit c581553a6c
161 changed files with 62 additions and 6 deletions
+62 -6
View File
@@ -42,6 +42,7 @@ MAX_ZONES = 25 # PRESETINFO samples[] capacity (AllExplosion = 25 la
G_KEYRANGE, G_PAN, G_RELEASE, G_INST, G_KEYRANGE2 = 43, 17, 38, 41, 43
G_ATTEN, G_COARSE, G_FINE, G_SAMPLEID, G_MODES, G_PITCH, G_ROOT = 48, 51, 52, 53, 54, 55, 58
G_FILTER_FC, G_FILTER_Q = 8, 9
SIGNED_GENS = {G_COARSE, G_FINE, G_RELEASE}
@@ -138,16 +139,71 @@ def zone_fields(bank, g):
release = min(8.0, 2.0 ** (rel_tc / 1200.0)) if rel_tc is not None else 0.0
pan = g.get(G_PAN, 64)
chan = "CHANNEL_LEFT" if pan <= 42 else ("CHANNEL_RIGHT" if pan >= 85 else "CHANNEL_CENTER")
# (AUDIO_FIDELITY F14) the authored static resonant low-pass the EMU8000
# applied in hardware: initialFilterFc(8) / initialFilterQ(9), SBK 0..127
# scale. Absent fc = filter fully open.
filter_fc = g.get(G_FILTER_FC, 127)
filter_q = g.get(G_FILTER_Q, 0)
return dict(pcm=pcm, rate=rate, keylo=keylo, keyhi=keyhi, looping=looping,
lstart=lstart, lend=lend, gain=gain, release=release, chan=chan,
sid=g[G_SAMPLEID], root_cents=root_cents, tune=tune)
sid=g[G_SAMPLEID], root_cents=root_cents, tune=tune,
fc=filter_fc, q=filter_q)
def write_wav(fname, pcm, rate, gain):
if gain < 0.999: # bake layer-balance attenuation
def bake_lowpass(pcm, rate, fc127, q127):
"""(AUDIO_FIDELITY F14) bake the zone's authored static resonant low-pass
into the PCM. Cutoff uses the AWE NRPN curve (100 + fc*7900/127 Hz);
resonance maps SBK Q 0..127 -> 0..+12 dB biquad peak [T3 -- curve shape
approximate, off/open endpoints exact]. The filter is designed at the
zone's BAKED rate, so note-60 playback (1 file second == 1 real second)
reproduces the hardware's absolute cutoff exactly; pitch-shifted notes
carry the filter with them (same limitation as the rate-baked tuning)."""
cutoff_hz = 100.0 + fc127 * 7900.0 / 127.0
if cutoff_hz >= 0.45 * rate: # at/above Nyquist headroom: no-op
return None
q_biquad = 0.7071 * 10.0 ** ((q127 / 127.0 * 12.0) / 20.0)
# RBJ cookbook 2-pole low-pass, run in FLOAT (the caller converts once)
w0 = 2.0 * math.pi * cutoff_hz / rate
alpha = math.sin(w0) / (2.0 * q_biquad)
cw = math.cos(w0)
b0 = (1.0 - cw) / 2.0
b1 = 1.0 - cw
b2 = (1.0 - cw) / 2.0
a0 = 1.0 + alpha
a1 = -2.0 * cw
a2 = 1.0 - alpha
b0 /= a0; b1 /= a0; b2 /= a0; a1 /= a0; a2 /= a0
n = len(pcm) // 2
vals = struct.unpack("<%dh" % n, pcm[:n * 2])
out = [0.0] * n
x1 = x2 = y1 = y2 = 0.0
for i, x0 in enumerate(vals):
y0 = b0 * x0 + b1 * x1 + b2 * x2 - a1 * y1 - a2 * y2
x2 = x1; x1 = x0
y2 = y1; y1 = y0
out[i] = y0
return out
def write_wav(fname, pcm, rate, gain, fc127=127, q127=0):
# Full bake pipeline in FLOAT, one int16 conversion at the end: the
# resonance peak can overshoot full scale (165 of 232 filtered zones
# hard-clipped in an int-per-stage draft) -- normalize the whole file
# down to fit instead of clipping (level error <= the overshoot, wave
# shape preserved; the EMU's filter stage had internal headroom).
fvals = None
if fc127 < 127: # authored static low-pass
fvals = bake_lowpass(pcm, rate, fc127, q127)
if fvals is None and gain < 0.999:
n = len(pcm) // 2
vals = struct.unpack("<%dh" % n, pcm[:n * 2])
pcm = struct.pack("<%dh" % n, *[max(-32768, min(32767, int(v * gain))) for v in vals])
fvals = [float(v) for v in struct.unpack("<%dh" % n, pcm[:n * 2])]
if fvals is not None:
if gain < 0.999: # layer-balance attenuation
fvals = [v * gain for v in fvals]
peak = max(1.0, max(abs(v) for v in fvals))
norm = (32767.0 / peak) if peak > 32767.0 else 1.0
pcm = struct.pack("<%dh" % len(fvals),
*[int(v * norm) for v in fvals])
with wave.open(os.path.join(AUDIO, fname), "wb") as w:
w.setnchannels(1); w.setsampwidth(2); w.setframerate(rate)
w.writeframes(pcm)
@@ -178,7 +234,7 @@ def main():
continue
for k, z in enumerate(zs):
z["file"] = f"{name}.wav" if len(zs) == 1 else f"{name}_z{k}.wav"
write_wav(z["file"], z["pcm"], z["rate"], z["gain"])
write_wav(z["file"], z["pcm"], z["rate"], z["gain"], z["fc"], z["q"])
cents = 1200.0 * math.log2(z["rate"] / 44100.0)
if abs(cents - round(cents / 100.0) * 100) > 3 and not z["tune"] % 100:
grid_outliers.append((name, k, z["rate"], round(cents, 1)))