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);
		}