PEQ + X / Auto-Wah + Delay combos — HLE reconstruction and DSP bytecode

DSPHLE selector 0x1a · program image prog71_peq_chorus · Preview (topology decoded and reconstructed; most panel→cell role mappings are position-decoded).

shared combo engine: a PEQ emphasis biquad chained with a 2nd block (selectors 0x1a-0x22; see the branch for each).

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/prog71_peq_chorus.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 -- PEQ+CHORUS
; image rep algo 71  |  slots 71  |  unit 0 (I-RAM load 84)
; family combi  |  confidence high  |  93 words, 31 class-A multiplies (17 named)
; role: 1-band flat PEQ + chorus
; 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    08801308BC   ?word   0x08801308BC   ; 880.1.30.8BC  hi12{ESC ?7 res=080}  [external delay-DRAM READ (FORCED, adjudication-round5 sect. 3 -- addr8 bit 6 is the direction field and 0x60 is the WRITE; this REVERSES R1 F1, which bounded the read latency to one repetition when the descriptors need twenty words); this end moves with the user's DELAY (ms) knob, and the delay is READ_CELL - WRITE_CELL; addr8 0x30 also marks the FIRST DRAM access of a body, 37 of 38 distinct images (R3 sect. 6.2). external delay-DRAM access; address = DESCRIPTOR_CELL[k] + G, from the host bank behind pointer ...825 / tag 0x4C (R3, PROVEN BY CONSTRUCTION) -- the k-th class-1 escape word of a body takes the k-th cell of that body's own descriptor block (the IDENTITY map, FORCED in adjudication-round5 sect. 1), so the address is NOT in this word]
  w1    00002031CD   ld      (p),(p)+3
  w2    000028140E   ld      acc,(p)-127
  w3    021227F00B   ?word   0x021227F00B   ; 212.2.7F.00B  hi12{ST f98=2 f31=1}  [writes mem[ptr] (bit 4); mode 2, so the target IS the pointer]
  w4    002A200000   ?word   0x002A200000   ; 02A.2.00.000  hi12{f31=5 ?5 res=020}  [SPECULATIVE (prospective, not measured): SRC 0x00 = mem[ptr]/delay-RAM read]
  w5    0092A00200   mac.b   c,c+,(p)+0 ; store SUPPRESSED (bit7)
        ; C-RAM[0x00] (coeff, base 0x00 MEASURED)
  w6    00822001C0   mac.b   (p),(p)+0
  w7    0094A00200   wrap    acc,c+          ; acc <- datum(acc) & coef  (LFO modulus)
        ; C-RAM[0x01] (coeff, base 0x00 MEASURED)
  w8    0000248447   ?word   0x0000248447   ; 000.2.48.447  hi12{-}  [SPECULATIVE (prospective, not measured): SRC 0x11 = ACCB (2nd accumulator)]
  w9    0000A001D3   ld.ta   (p),c+,(p)+0
        ; C-RAM[0x02] (coeff, base 0x00 MEASURED)
        ; coeff C-RAM[0x02] = biquad b1 (role EQ, PROVEN)
  w10   0212A01412   mac     acc,c+,(p)+1 ; mem[p]<-acc, acc=0
        ; C-RAM[0x03] (coeff, base 0x00 MEASURED)
        ; coeff C-RAM[0x03] = biquad b0 (role EQ, PROVEN)
  w11   0202A011D5   mac     (p),c+,(p)+1
        ; C-RAM[0x04] (coeff, base 0x00 MEASURED)
        ; coeff C-RAM[0x04] = biquad b2 (role EQ, PROVEN)
  w12   0202A011D4   mac.tb  (p),c+,(p)+1
        ; C-RAM[0x05] (coeff, base 0x00 MEASURED)
        ; coeff C-RAM[0x05] = biquad -a1 (role EQ, PROVEN)
  w13   0202A001D5   mac     (p),c+,(p)+0
        ; C-RAM[0x06] (coeff, base 0x00 MEASURED)
        ; coeff C-RAM[0x06] = biquad -a2 (role EQ, PROVEN)
  w14   01022FF687   mac.st  tb,(p)-1
  w15   0804816415   post    acc,c
  w16   0212AFF407   mac.st  acc,c+,(p)-1 ; mem[p]<-acc, acc=0
        ; C-RAM[0x07] (coeff, base 0x00 MEASURED)
        ; coeff C-RAM[0x07] = biquad makeup (role EQ, PROVEN)
  w17   00002C1647   ld.st   ta,(p)-63
  w18   0000202407   ld.st   acc,(p)+2
  w19   09001601D5   dly.w  dsc[k],p+96
  w20   0192A40000   mac.b   (p)0,c+,(p)+64 ; store SUPPRESSED (bit7)
        ; C-RAM[0x08] (coeff, base 0x00 MEASURED)
  w21   00822001C0   mac.b   (p),(p)+0
  w22   0C4032044C   ldreg   r4C,#25          ; immediate -> the register lo12 selects
  w23   0A00000041   ?word   0x0A00000041   ; A00.0.00.041  hi12{ESC f98=2}  [head of a fixed 3-word template (S-5); operands SRC 0x01/ACT 0x01 dark]
  w24   08801202C7   dly.r  dsc[k],p+32
  w25   0102ABE4C8   ?word   0x0102ABE4C8   ; 102.A.BE.4C8  hi12{f98=1 f31=1} cur+  [gain multiply (same op in phaser all-pass and reverb diffuser)]
        ; C-RAM[0x09] (coeff, base 0x00 MEASURED)
  w26   00002FF407   ld.st   acc,(p)-1
  w27   00122F51C0   mac.b   (p),(p)-11 ; mem[p]<-acc, acc=0
  w28   09001601D5   dly.w  dsc[k],p+96
  w29   0192A4F000   mac.b   (p)0,c+,(p)+79 ; store SUPPRESSED (bit7)
        ; C-RAM[0x0A] (coeff, base 0x00 MEASURED)
  w30   00822001C0   mac.b   (p),(p)+0
  w31   0C4032044C   ldreg   r4C,#25          ; immediate -> the register lo12 selects
  w32   0A00000041   ?word   0x0A00000041   ; A00.0.00.041  hi12{ESC f98=2}  [head of a fixed 3-word template (S-5); operands SRC 0x01/ACT 0x01 dark]
  w33   08801202C7   dly.r  dsc[k],p+32
  w34   0102ABC4C8   ?word   0x0102ABC4C8   ; 102.A.BC.4C8  hi12{f98=1 f31=1} cur+  [gain multiply (same op in phaser all-pass and reverb diffuser)]
        ; C-RAM[0x0B] (coeff, base 0x00 MEASURED)
  w35   0000A00415   ld      acc,c+,(p)+0
        ; C-RAM[0x0C] (coeff, base 0x00 MEASURED)
  w36   0212AFF1D5   mac     (p),c+,(p)-1 ; mem[p]<-acc, acc=0
        ; C-RAM[0x0D] (coeff, base 0x00 MEASURED)
  w37   02022001D5   mac     (p),(p)+0
  w38   0202AFC415   mac     acc,c+,(p)-4
        ; C-RAM[0x0E] (coeff, base 0x00 MEASURED)
        ; coeff C-RAM[0x0E] = op0x66[0] (role mix/tap, INFERRED)
  w39   0204200000   post.b  (p)0,(p)+0
  w40   00922FA700   ?word   0x00922FA700   ; 092.2.FA.700  hi12{ST f31=1 ?7 res=080}  [SPECULATIVE (prospective, not measured): SRC 0x1C = control/mod source into MAC (100% MAC-consumed; LFO in mod fx, envelope/AGC in dynamics) -- NOT LFO-only: present in 19 non-LFO programs (dsp_datapath_fingerprint)]
  w41   0020A061D5   ld      (p),c+,(p)+6
        ; C-RAM[0x0F] (coeff, base 0x00 MEASURED)
  w42   0182200407   mac.st  acc,(p)+0
  w43   0040000C63   ?word   0x0040000C63   ; 040.0.00.C63  hi12{?6 res=040}  [SPECULATIVE (prospective, not measured): SRC 0x11 = ACCB (2nd accumulator)]
  w44   00006184CD   ?word   0x00006184CD   ; 000.6.18.4CD  hi12{-}  [table-lookup idiom, class-6 addr8 = table selector (INFERRED)]
  w45   00124011CE   ?word   0x00124011CE   ; 012.4.01.1CE  hi12{ST f31=1}  [table-lookup idiom, third word (INFERRED)]
  w46   01042061CE   post    (p),(p)+6
  w47   0142000C63   ?word   0x0142000C63   ; 142.0.00.C63  hi12{f98=1 f31=1 ?6 res=040}  [SPECULATIVE (prospective, not measured): SRC 0x11 = ACCB (2nd accumulator)]
  w48   000061E407   ?word   0x000061E407   ; 000.6.1E.407  hi12{-}  [table-lookup idiom, class-6 addr8 = table selector (INFERRED)]
  w49   00124011CE   ?word   0x00124011CE   ; 012.4.01.1CE  hi12{ST f31=1}  [table-lookup idiom, third word (INFERRED)]
  w50   01042F31CE   post    (p),(p)-13
  w51   0102200000   mac.b   (p)0,(p)+0
  w52   0000A00415   ld      acc,c+,(p)+0
        ; C-RAM[0x10] (coeff, base 0x00 MEASURED)
        ; coeff C-RAM[0x10] = op0x66[1] (role mix/tap, INFERRED)
  w53   0212200000   mac.b   (p)0,(p)+0 ; mem[p]<-acc, acc=0
  w54   000020D407   ld.st   acc,(p)+13
  w55   0010AF11D5   ld      (p),c+,(p)-15 ; mem[p]<-acc, acc=0
        ; C-RAM[0x11] (coeff, base 0x00 MEASURED)
        ; coeff C-RAM[0x11] = op0x66[2] (role mix/tap, INFERRED)
  w56   020220A1CD   mac     (p),(p)+10
  w57   00002051CE   ld      (p),(p)+5
  w58   0212244407   mac.st  acc,(p)+68 ; mem[p]<-acc, acc=0
  w59   002A200000   ?word   0x002A200000   ; 02A.2.00.000  hi12{f31=5 ?5 res=020}  [SPECULATIVE (prospective, not measured): SRC 0x00 = mem[ptr]/delay-RAM read]
  w60   0000A001D3   ld.ta   (p),c+,(p)+0
        ; C-RAM[0x12] (coeff, base 0x00 MEASURED)
        ; coeff C-RAM[0x12] = biquad b1 (role EQ, PROVEN)
  w61   0212A01412   mac     acc,c+,(p)+1 ; mem[p]<-acc, acc=0
        ; C-RAM[0x13] (coeff, base 0x00 MEASURED)
        ; coeff C-RAM[0x13] = biquad b0 (role EQ, PROVEN)
  w62   0202A011D5   mac     (p),c+,(p)+1
        ; C-RAM[0x14] (coeff, base 0x00 MEASURED)
        ; coeff C-RAM[0x14] = biquad b2 (role EQ, PROVEN)
  w63   0202A011D4   mac.tb  (p),c+,(p)+1
        ; C-RAM[0x15] (coeff, base 0x00 MEASURED)
        ; coeff C-RAM[0x15] = biquad -a1 (role EQ, PROVEN)
  w64   0202A001D5   mac     (p),c+,(p)+0
        ; C-RAM[0x16] (coeff, base 0x00 MEASURED)
        ; coeff C-RAM[0x16] = biquad -a2 (role EQ, PROVEN)
  w65   01022FF687   mac.st  tb,(p)-1
  w66   0804816415   post    acc,c
  w67   0212AFF407   mac.st  acc,c+,(p)-1 ; mem[p]<-acc, acc=0
        ; C-RAM[0x17] (coeff, base 0x00 MEASURED)
        ; coeff C-RAM[0x17] = biquad makeup (role EQ, PROVEN)
  w68   00002B9647   ld.st   ta,(p)-71
  w69   0000202407   ld.st   acc,(p)+2
  w70   09001601D5   dly.w  dsc[k],p+96
  w71   0192A48000   mac.b   (p)0,c+,(p)+72 ; store SUPPRESSED (bit7)
        ; C-RAM[0x18] (coeff, base 0x00 MEASURED)
  w72   00822001C0   mac.b   (p),(p)+0
  w73   0C4032044C   ldreg   r4C,#25          ; immediate -> the register lo12 selects
  w74   0A00000041   ?word   0x0A00000041   ; A00.0.00.041  hi12{ESC f98=2}  [head of a fixed 3-word template (S-5); operands SRC 0x01/ACT 0x01 dark]
  w75   08801202C7   dly.r  dsc[k],p+32
  w76   0102AB64C8   ?word   0x0102AB64C8   ; 102.A.B6.4C8  hi12{f98=1 f31=1} cur+  [gain multiply (same op in phaser all-pass and reverb diffuser)]
        ; C-RAM[0x19] (coeff, base 0x00 MEASURED)
  w77   00002FF407   ld.st   acc,(p)-1
  w78   00122F51C0   mac.b   (p),(p)-11 ; mem[p]<-acc, acc=0
  w79   09001601D5   dly.w  dsc[k],p+96
  w80   0192A57000   mac.b   (p)0,c+,(p)+87 ; store SUPPRESSED (bit7)
        ; C-RAM[0x1A] (coeff, base 0x00 MEASURED)
  w81   00822001C0   mac.b   (p),(p)+0
  w82   0C4032044C   ldreg   r4C,#25          ; immediate -> the register lo12 selects
  w83   0A00000041   ?word   0x0A00000041   ; A00.0.00.041  hi12{ESC f98=2}  [head of a fixed 3-word template (S-5); operands SRC 0x01/ACT 0x01 dark]
  w84   08801202C7   dly.r  dsc[k],p+32
  w85   0102AB44C8   ?word   0x0102AB44C8   ; 102.A.B4.4C8  hi12{f98=1 f31=1} cur+  [gain multiply (same op in phaser all-pass and reverb diffuser)]
        ; C-RAM[0x1B] (coeff, base 0x00 MEASURED)
  w86   0000A00415   ld      acc,c+,(p)+0
        ; C-RAM[0x1C] (coeff, base 0x00 MEASURED)
  w87   0212AFF1D5   mac     (p),c+,(p)-1 ; mem[p]<-acc, acc=0
        ; C-RAM[0x1D] (coeff, base 0x00 MEASURED)
        ; coeff C-RAM[0x1D] = op0x74[0] (role input/tone, INFERRED)
  w88   02022001D5   mac     (p),(p)+0
  w89   0202AEE415   mac     acc,c+,(p)-18
        ; C-RAM[0x1E] (coeff, base 0x00 MEASURED)
        ; coeff C-RAM[0x1E] = op0x66[3] (role mix/tap, INFERRED)
  w90   0204200000   post.b  (p)0,(p)+0
  w91   0880160000   dly.w  dsc[k],p+96
  w92   042010E000   endblk  #0E             ; END OF BLOCK -- the last word of a block

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 cmbx = pc_hle || pf_hle || pv_hle || pcmp_hle || pcd_hle || pco_hle || pdd_hle || pod_hle || awd_hle;
	double cx_cinc = 0.0, cx_depth = 0.0, cx_wet = 0.0, cx_fb = 0.0;        // modulation 2nd-block
	double cx_drive = 0.0, cx_vol = 0.0; bool cx_tone = false;             // dist/overdrive 2nd-block
	double cx_thr = 0.0, cx_slope = 0.0, cx_atk = 0.0, cx_rel = 0.0, cx_mk = 0.0;  // compressor
	int    cx_dN = 0; double cx_dfb = 0.0; bool cx_peq = false, cx_wah = false;
	if (cmbx)
	{
		// PEQ emphasis biquad design (same RBJ reconstruction as the standalone PEQ / PEQ+S.DELAY),
		// loaded into m_eq[0]; section base differs per program.
		auto design_peq = [&](u8 base) {
			double c1 = q22x(m_dsp1->cram_read(u8(base + 1))) * x_cs;   // GAIN = base+1
			double c3 = q22x(m_dsp1->cram_read(u8(base + 3))) * x_cs;   // FREQ = base+3 (2cos w0)
			const double nrm = (std::fabs(c3) <= 1.0) ? 2.0 : 1.0; c1 *= nrm; c3 *= nrm;
			const double cw = std::clamp(c3 * 0.5, -0.9995, 0.9995);
			const double bc[5] = { 0.9954, 0.99055, 0.98, 0.95595, 0.89665 };
			const double bg[5] = { 465.8, 225.8, 114.2, 58.3, 30.5 };
			double G = bg[4], best = 1e9;
			for (int i = 0; i < 5; i++) { const double d = std::fabs(cw - bc[i]); if (d < best) { best = d; G = bg[i]; } }
			const double asq = std::max(1.0 + G * (c1 - 0.5), 0.02);
			const double A = std::sqrt(asq), w0 = std::acos(cw), al = std::sin(w0) / 4.0;
			const double b0 = 1.0 + al * A, b1 = -2.0 * cw, b2 = 1.0 - al * A;
			const double a0 = 1.0 + al / A, a1 = -2.0 * cw, a2 = 1.0 - al / A;
			m_eq_l[0].set_coeffs(b0 / a0, b1 / a0, b2 / a0, a1 / a0, a2 / a0, 1.0);
			m_eq_r[0].set_coeffs(b0 / a0, b1 / a0, b2 / a0, a1 / a0, a2 / a0, 1.0);
		};
		auto comp_setup = [&](u8 thrcell) {
			cx_thr = std::clamp(0.03 + 0.25 * std::fabs(q22x(m_dsp1->cram_read(thrcell)) * x_cs), 0.03, 0.5);
			cx_slope = 0.75;
			cx_atk = std::exp(-1.0 / (double(STREAM_RATE) * 0.006));
			cx_rel = std::exp(-1.0 / (double(STREAM_RATE) * 0.15));
			cx_mk = std::clamp(1.0 / (cx_thr + 0.2), 1.0, 4.0);
		};
		auto delay_setup = [&]() {   // delay from descriptor 0x26 (single-delay convention)
			const double draw = double(m_dsp1->dsc_read(0x26));
			const double n441 = x_bit1 ? draw : 2.0 * draw;
			cx_dN = std::clamp(int(n441 * double(STREAM_RATE) / 44100.0 + 0.5), 1, 34999);
			cx_dfb = 0.35;
		};
		if (pc_hle)   { cx_peq = true; design_peq(0x02); cx_cinc = std::clamp(rate_hz(0x00),0.05,12.0)/double(STREAM_RATE); cx_depth = 200.0*sr48; cx_wet = 0.5; }
		if (pf_hle)   { cx_peq = true; design_peq(0x04); cx_cinc = std::clamp(rate_hz(0x00),0.02,12.0)/double(STREAM_RATE); cx_depth = 120.0*sr48; cx_fb = std::clamp(std::fabs(q22x(m_dsp1->cram_read(0x0E))*x_cs)*0.5,0.0,0.9); }
		if (pv_hle)   { cx_peq = true; design_peq(0x04); cx_cinc = std::clamp(rate_hz(0x00),0.02,12.0)/double(STREAM_RATE); cx_depth = 180.0*sr48; cx_wet = 1.0; }
		if (pcmp_hle) { cx_peq = true; design_peq(0x00); comp_setup(0x0A); }
		if (pcd_hle)  { cx_peq = true; design_peq(0x00); comp_setup(0x0A); cx_drive = std::clamp(4.0+9.0*std::fabs(q22x(m_dsp1->cram_read(0x0F))*x_cs),4.0,13.0); cx_vol = 0.8; }
		if (pco_hle)  { cx_peq = true; design_peq(0x00); comp_setup(0x0A); cx_drive = std::clamp(3.0+7.0*std::fabs(q22x(m_dsp1->cram_read(0x0F))*x_cs),3.0,10.0); cx_vol = 0.8; cx_tone = true; }
		if (pdd_hle)  { cx_peq = true; design_peq(0x00); cx_drive = std::clamp(4.0+9.0*std::fabs(q22x(m_dsp1->cram_read(0x06))*x_cs),4.0,13.0); cx_vol = 0.8; delay_setup(); }
		if (pod_hle)  { cx_peq = true; design_peq(0x00); cx_drive = std::clamp(3.0+7.0*std::fabs(q22x(m_dsp1->cram_read(0x06))*x_cs),3.0,10.0); cx_vol = 0.8; cx_tone = true; delay_setup(); }
		if (awd_hle)  { cx_wah = true; delay_setup(); }
		if (cx_tone) { m_eq_l[1].set_coeffs(0.30,0.30,0.0,-0.40,0.0,1.0); m_eq_r[1].set_coeffs(0.30,0.30,0.0,-0.40,0.0,1.0); }  // ~4 kHz tone LP
	}

Per-sample insert (the reconstructed signal path):

		if (cmbx)      // PEQ+X / auto-wah+delay combos: emphasis biquad -> 2nd block(s), chained
		{
			double xl = double(mix_l) / 32768.0, xr = double(mix_r) / 32768.0;
			if (cx_peq) { xl = m_eq_l[0].process_one(xl); xr = m_eq_r[0].process_one(xr); }   // PEQ stage
			if (pc_hle)          // chorus (quadrature LFO-swept delay)
			{
				const double s = std::sin(TWO_PI * m_cho_phase), cq = std::cos(TWO_PI * m_cho_phase);
				m_cho_phase += cx_cinc; if (m_cho_phase >= 1.0) m_cho_phase -= 1.0;
				const double dl = m_cho_l.read(cx_depth * (1.0 + s) + 2.0), dr = m_cho_r.read(cx_depth * (1.0 + cq) + 2.0);
				m_cho_l.write(xl); m_cho_r.write(xr);
				xl = xl * (1.0 - cx_wet) + dl * cx_wet; xr = xr * (1.0 - cx_wet) + dr * cx_wet;
			}
			else if (pf_hle)     // flanger (swept delay + feedback)
			{
				const double s = std::sin(TWO_PI * m_fl_phase); m_fl_phase += cx_cinc; if (m_fl_phase >= 1.0) m_fl_phase -= 1.0;
				const double tap = cx_depth * (1.0 + s) + 2.0;
				const double dl = m_fl_l.read(tap), dr = m_fl_r.read(tap);
				m_fl_l.write(xl + cx_fb * dl); m_fl_r.write(xr + cx_fb * dr);
				xl = 0.5 * (xl + dl); xr = 0.5 * (xr + dr);
			}
			else if (pv_hle)     // vibrato (100% wet swept delay = pitch modulation)
			{
				const double s = std::sin(TWO_PI * m_vib_phase); m_vib_phase += cx_cinc; if (m_vib_phase >= 1.0) m_vib_phase -= 1.0;
				const double tap = cx_depth * (1.0 + s) + 2.0;
				const double dl = m_vib_l.read(tap), dr = m_vib_r.read(tap);
				m_vib_l.write(xl); m_vib_r.write(xr);
				xl = dl; xr = dr;
			}
			if (pcmp_hle || pcd_hle || pco_hle)    // compressor
			{
				const double rl = xl * xl, rr = xr * xr;
				const double cl = (rl > m_comp_env_l) ? cx_atk : cx_rel; m_comp_env_l = rl + cl * (m_comp_env_l - rl);
				const double cr = (rr > m_comp_env_r) ? cx_atk : cx_rel; m_comp_env_r = rr + cr * (m_comp_env_r - rr);
				auto g = [&](double e2) -> double { const double e = std::sqrt(std::max(e2, 0.0)); return (e <= cx_thr || e < 1e-6) ? cx_mk : cx_mk * std::pow(cx_thr / e, cx_slope); };
				xl *= g(m_comp_env_l); xr *= g(m_comp_env_r);
			}
			if (pcd_hle || pco_hle || pdd_hle || pod_hle)    // distortion / overdrive waveshaper
			{
				xl = std::tanh(xl * cx_drive) * cx_vol; xr = std::tanh(xr * cx_drive) * cx_vol;
				if (cx_tone) { xl = m_eq_l[1].process_one(xl); xr = m_eq_r[1].process_one(xr); }   // overdrive tone
			}
			if (cx_wah)    // auto-wah: envelope-swept resonant SVF
			{
				m_wah_env_l = std::max(std::fabs(xl), m_wah_env_l * 0.9995);
				m_wah_env_r = std::max(std::fabs(xr), m_wah_env_r * 0.9995);
				const double fcl = 300.0 + std::clamp(m_wah_env_l, 0.0, 1.0) * 3500.0, fcr = 300.0 + std::clamp(m_wah_env_r, 0.0, 1.0) * 3500.0;
				const double fl = 2.0 * std::sin(PI_C * std::min(fcl, 0.45 * STREAM_RATE) / double(STREAM_RATE));
				const double fr = 2.0 * std::sin(PI_C * std::min(fcr, 0.45 * STREAM_RATE) / double(STREAM_RATE));
				m_wah_lp_l += fl * m_wah_bp_l; const double hpl = xl - m_wah_lp_l - 0.15 * m_wah_bp_l; m_wah_bp_l += fl * hpl;
				m_wah_lp_r += fr * m_wah_bp_r; const double hpr = xr - m_wah_lp_r - 0.15 * m_wah_bp_r; m_wah_bp_r += fr * hpr;
				xl = m_wah_bp_l; xr = m_wah_bp_r;
			}
			if (pdd_hle || pod_hle || awd_hle)    // feedback delay stage
			{
				const double dl = m_dly_l.read(double(cx_dN)), dr = m_dly_r.read(double(cx_dN));
				m_dly_l.write(xl + cx_dfb * dl); m_dly_r.write(xr + cx_dfb * dr);
				xl = 0.6 * xl + 0.5 * dl; xr = 0.6 * xr + 0.5 * dr;
			}
			mix_l = int32_t(std::clamp(xl, -1.0, 1.0) * 32767.0);
			mix_r = int32_t(std::clamp(xr, -1.0, 1.0) * 32767.0);
		}