Compressor — HLE + bytecode
Compressor — HLE reconstruction and DSP bytecode
DSPHLE selector 0x16 · program image prog36_compressor · Preview (topology decoded and reconstructed; most panel→cell role mappings are position-decoded).
2/pi RECTIFY-and-smooth level detector (ROM 0x517CC1) -> attack/release one-pole (the ROM’s own 4.712 ms / 11.764 ms) -> gain computer; the DETECTOR is decoded, the GAIN LAW is not.
This page pairs the bytecode the effect runs on the NEC µPD6383GF with the high-level reconstruction that makes it audible in MAME. The reconstruction is not the chip’s microcode — it is a textbook DSP block built from the decoded meaning of that microcode, and reading the two together is how the bytecode is understood (and, in time, driven toward a faithful low-level emulation). See also the signal-flow flowchart.
The bytecode below is the source of truth; the HLE reconstruction is not. The HLE is our best current interpretation and may contain mistakes — where the two disagree, the bytecode wins, and a better HLE should be updated here. This page is the permanent archive of the reference HLE: it is kept here even after the code is eventually removed from the MAME sources.
DSP bytecode (reverse-engineered microprogram)
The disassembled image the chip executes for this effect. Source (regenerable):
dsp/disasm/prog36_compressor.dsm.
; Reverse-engineered disassembly of the KN5000 effects-DSP microcode
; (NEC uPD6383GF), recovered from the original firmware. The microcode is the
; work of its original authors; this is a disassembly for preservation and
; interoperability, and no claim of copyright is made over the disassembled program.
; KN5000 effects-DSP program -- COMPRESSOR
; image rep algo 36 | slots 36 | unit 0 (I-RAM load 84)
; family dynamics | confidence medium | 40 words, 10 class-A multiplies (1 named)
; role: compressor: level detector + gain-computer (THRESHOLD/RATIO). NOTE: the old 'hi12=0xC40 = envelope detector' reading is WITHDRAWN -- C40/C41 is a 13-bit immediate load (analysis/k5-output-stage.md); the detector is here on other grounds
; coefficient cursor base 0x00
;
; GENERATED by dsp/tools/gen_dsp_disasm.py -- DO NOT EDIT.
; Put labels/comments in the matching dsp/sym/*.sym; analysis in dsp/algorithms/.
w0 002A20F000 ?word 0x002A20F000 ; 02A.2.0F.000 hi12{f31=5 ?5 res=020} [SPECULATIVE (prospective, not measured): SRC 0x00 = mem[ptr]/delay-RAM read]
w1 0880130407 dly.r dsc[k],p+48
w2 00262F3000 ?word 0x00262F3000 ; 026.2.F3.000 hi12{f31=3 ?5 res=020} [SPECULATIVE (prospective, not measured): SRC 0x00 = mem[ptr]/delay-RAM read]
w3 0018A001D5 ?word 0x0018A001D5 ; 018.A.00.1D5 hi12{ST f31=4} cur+ [SPECULATIVE: class-A multiply (P = coef x source 0x07); the source id / accumulator-combine f31=4 / ACT 0x15 may be OPEN]
; C-RAM[0x00] (coeff, base 0x00 MEASURED)
w4 0104A001D5 post (p),c+,(p)+0
; C-RAM[0x01] (coeff, base 0x00 MEASURED)
w5 0C402C0000 ldreg r00,#22 ; immediate -> the register lo12 selects
w6 0182A00000 mac.b (p)0,c+,(p)+0
; C-RAM[0x02] (coeff, base 0x00 MEASURED)
w7 00A620D447 ?word 0x00A620D447 ; 0A6.2.0D.447 hi12{f31=3 ?7 ?5 res=0A0} [SPECULATIVE (prospective, not measured): SRC 0x11 = ACCB (2nd accumulator)]
w8 00A2200000 mac.b (p)0,(p)+0
w9 001223C1C0 mac.b (p),(p)+60 ; mem[p]<-acc, acc=0
w10 0000A00219 ld.ta2 c,c+,(p)+0
; C-RAM[0x03] (coeff, base 0x00 MEASURED)
w11 009AA00200 ?word 0x009AA00200 ; 09A.A.00.200 hi12{ST f31=5 ?7 res=080} cur+ [SPECULATIVE (prospective, not measured): SRC 0x08 = C-RAM[cursor] (the COEFFICIENT), MEASURED: the chorus LFO at iw89 reads L = 114 and acc = 114<<16 exactly, and 114 = C-RAM[0x00] = floor(0.5993*2^23/44100), the ROM's own ramp constant; the rival "sample source" is REFUTED from disk (SQUARING-MULTIPLY item B)]
; C-RAM[0x04] (coeff, base 0x00 MEASURED)
; coeff C-RAM[0x04] = op0x72[0] (role gain-computer, PROVEN)
w12 0C401E0451 ldreg r51,#15 ; immediate -> the register lo12 selects
w13 01022B5000 mac.b (p)0,(p)-75
w14 0026200000 ?word 0x0026200000 ; 026.2.00.000 hi12{f31=3 ?5 res=020} [SPECULATIVE (prospective, not measured): SRC 0x00 = mem[ptr]/delay-RAM read]
w15 00002091CD ld (p),(p)+9
w16 000024240E ld acc,(p)+66
w17 02122BE000 mac.b (p)0,(p)-66 ; mem[p]<-acc, acc=0
w18 0028200000 ?word 0x0028200000 ; 028.2.00.000 hi12{f31=4 ?5 res=020} [SPECULATIVE (prospective, not measured): SRC 0x00 = mem[ptr]/delay-RAM read]
w19 0880130407 dly.r dsc[k],p+48
w20 0000201000 nop
w21 00122001C0 mac.b (p),(p)+0 ; mem[p]<-acc, acc=0
w22 002A200000 ?word 0x002A200000 ; 02A.2.00.000 hi12{f31=5 ?5 res=020} [SPECULATIVE (prospective, not measured): SRC 0x00 = mem[ptr]/delay-RAM read]
w23 00262F9407 ?word 0x00262F9407 ; 026.2.F9.407 hi12{f31=3 ?5 res=020} [SPECULATIVE: class-2 post-increment MAC (source 0x10); the accumulator-combine f31=3 and/or ACT 0x07 are OPEN]
w24 0018A001D5 ?word 0x0018A001D5 ; 018.A.00.1D5 hi12{ST f31=4} cur+ [SPECULATIVE: class-A multiply (P = coef x source 0x07); the source id / accumulator-combine f31=4 / ACT 0x15 may be OPEN]
; C-RAM[0x05] (coeff, base 0x00 MEASURED)
w25 0104A001D5 post (p),c+,(p)+0
; C-RAM[0x06] (coeff, base 0x00 MEASURED)
w26 0C402C0000 ldreg r00,#22 ; immediate -> the register lo12 selects
w27 0182A00000 mac.b (p)0,c+,(p)+0
; C-RAM[0x07] (coeff, base 0x00 MEASURED)
w28 00A6207447 ?word 0x00A6207447 ; 0A6.2.07.447 hi12{f31=3 ?7 ?5 res=0A0} [SPECULATIVE (prospective, not measured): SRC 0x11 = ACCB (2nd accumulator)]
w29 00A2200000 mac.b (p)0,(p)+0
w30 00122421C0 mac.b (p),(p)+66 ; mem[p]<-acc, acc=0
w31 0000A00219 ld.ta2 c,c+,(p)+0
; C-RAM[0x08] (coeff, base 0x00 MEASURED)
w32 009AA00200 ?word 0x009AA00200 ; 09A.A.00.200 hi12{ST f31=5 ?7 res=080} cur+ [SPECULATIVE (prospective, not measured): SRC 0x08 = C-RAM[cursor] (the COEFFICIENT), MEASURED: the chorus LFO at iw89 reads L = 114 and acc = 114<<16 exactly, and 114 = C-RAM[0x00] = floor(0.5993*2^23/44100), the ROM's own ramp constant; the rival "sample source" is REFUTED from disk (SQUARING-MULTIPLY item B)]
; C-RAM[0x09] (coeff, base 0x00 MEASURED)
w33 0C401E0451 ldreg r51,#15 ; immediate -> the register lo12 selects
w34 01022B4000 mac.b (p)0,(p)-76
w35 0026200000 ?word 0x0026200000 ; 026.2.00.000 hi12{f31=3 ?5 res=020} [SPECULATIVE (prospective, not measured): SRC 0x00 = mem[ptr]/delay-RAM read]
w36 00002FB1CD ld (p),(p)-5
w37 000025140E ld acc,(p)+81
w38 02122AF000 mac.b (p)0,(p)-81 ; mem[p]<-acc, acc=0
w39 042810E000 ?word 0x042810E000 ; 428.1.0E.000 hi12{END f31=4 ?5 res=020} [END OF BLOCK, unit 0 -- CALL/RETURN -- and still performs the rest of the word]
HLE reconstruction (MAME, kn5000_tonegen.cpp)
The decode of the bytecode above, rebuilt as audible DSP. Two parts: the parameter refresh
(reads the decoded C-RAM coefficient cells once per update) and the per-sample insert (the
signal processing). Default OFF, behind the DSPHLE research port. Source:
src/mame/matsushita/kn5000_tonegen.cpp.
Parameter refresh (decode the C-RAM coefficients):
const bool comp_hle = (dsphle == 0x16);
double comp_thr = 0.0, comp_slope = 0.0, comp_atk = 0.0, comp_rel = 0.0, comp_makeup = 0.0;
double comp_rect = 0.0;
if (comp_hle)
{
// C-RAM[0x00] = 2/pi, the rectifier calibration. Fall back to the ROM's own value if
// the cell has not been uploaded yet, so the detector is never silently scaled by zero.
comp_rect = std::fabs(q22x(m_dsp1->cram_read(0x00)) * x_cs);
if (comp_rect < 1e-6) comp_rect = 5340353.0 / 8388608.0; // 0x517CC1
comp_thr = std::clamp(0.03 + 0.25 * std::fabs(q22x(m_dsp1->cram_read(0x04)) * x_cs), 0.03, 0.5);
comp_slope = 0.75; // 1 - 1/ratio (RATIO ~4:1); SPECULATIVE
// tau = 1/(a * 44100) with `a' the uploaded one-pole coefficient, converted to a
// retention factor at this stream's own rate so the TIME is preserved, not the
// coefficient. Clamped to a musically sane span in case the cells are unloaded.
auto tau_from = [&](u8 cell, double dflt) -> double {
const double a = std::fabs(q22x(m_dsp1->cram_read(cell)) * x_cs);
return (a < 1e-9) ? dflt : std::clamp(1.0 / (a * 44100.0), 0.0005, 0.5); };
comp_atk = std::exp(-1.0 / (double(STREAM_RATE) * tau_from(0x02, 0.004712)));
comp_rel = std::exp(-1.0 / (double(STREAM_RATE) * tau_from(0x03, 0.011764)));
comp_makeup = 1.0 / (comp_thr + 0.2); // bring level back up after compression
comp_makeup = std::clamp(comp_makeup, 1.0, 4.0);
}
Per-sample insert (the reconstructed signal path):
if (comp_hle) // COMPRESSOR: 2/pi rectify-and-smooth detector -> gain computer
{
const double xl = double(mix_l) / 32768.0, xr = double(mix_r) / 32768.0;
// ★ RECTIFY and scale by the ROM's 2/pi -- NOT square-law; see the setup block.
// The envelope is therefore already an amplitude, so the gain computer below no
// longer takes a square root (it used to, to undo the squaring).
const double rl = std::fabs(xl) * comp_rect, rr = std::fabs(xr) * comp_rect;
const double cl = (rl > m_comp_env_l) ? comp_atk : comp_rel;
m_comp_env_l = rl + cl * (m_comp_env_l - rl);
const double cr = (rr > m_comp_env_r) ? comp_atk : comp_rel;
m_comp_env_r = rr + cr * (m_comp_env_r - rr);
// ⚠ THE GAIN LAW IS STILL SPECULATIVE and is deliberately left alone: what the
// corpus establishes is only the NEGATIVE constraint that there is no comparator
// opcode (the bodies are branchless), so THRESHOLD and RATIO must enter as
// coefficients. Nothing measured chooses between this knee and the linear
// `g = clip(1 - k*env, 1/ratio, 1)' the Python reference uses.
auto gain = [&](double e) -> double {
if (e <= comp_thr || e < 1e-6) return comp_makeup;
return comp_makeup * std::pow(comp_thr / e, comp_slope); };
mix_l = int32_t(std::clamp(xl * gain(m_comp_env_l), -1.0, 1.0) * 32767.0);
mix_r = int32_t(std::clamp(xr * gain(m_comp_env_r), -1.0, 1.0) * 32767.0);
}