Distortion — HLE + bytecode
Distortion — HLE reconstruction and DSP bytecode
DSPHLE selector 0x0f · program image prog32_distortion · Preview (topology decoded and reconstructed; most panel→cell role mappings are position-decoded).
AGC waveshaper: DRIVE=0x00, VOLUME=0x02 measured; tanh clip is a labelled stand-in (class-6 table not undumped, open decode).
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/prog32_distortion.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 -- DISTORTION
; image rep algo 32 | slots 32 | unit 0 (I-RAM load 84)
; family distortion | confidence high | 42 words, 6 class-A multiplies (4 named)
; role: distortion: AGC waveshaper, curve A
; 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 00400008BC ?word 0x00400008BC ; 040.0.00.8BC hi12{?6 res=040} [SPECULATIVE: lo12 bit-11 modifier word + pointer-mode (bit11-family); the base selector/register is OPEN]
w1 002E200000 ?word 0x002E200000 ; 02E.2.00.000 hi12{f31=7 ?5 res=020} [SPECULATIVE (prospective, not measured): SRC 0x00 = mem[ptr]/delay-RAM read]
w2 0000200415 ld acc,(p)+0
w3 0212200000 mac.b (p)0,(p)+0 ; mem[p]<-acc, acc=0
w4 0000A00415 ld acc,c+,(p)+0
; C-RAM[0x00] (coeff, base 0x00 MEASURED)
; coeff C-RAM[0x00] = op0x61[0] (role coeff, INFERRED)
w5 0212200000 mac.b (p)0,(p)+0 ; mem[p]<-acc, acc=0
w6 00922F9700 ?word 0x00922F9700 ; 092.2.F9.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)]
w7 0000A00415 ld acc,c+,(p)+0
; C-RAM[0x01] (coeff, base 0x00 MEASURED)
w8 0182207000 mac.b (p)0,(p)+7
w9 0040000C63 ?word 0x0040000C63 ; 040.0.00.C63 hi12{?6 res=040} [SPECULATIVE (prospective, not measured): SRC 0x11 = ACCB (2nd accumulator)]
w10 00006284CD ?word 0x00006284CD ; 000.6.28.4CD hi12{-} [table-lookup idiom, class-6 addr8 = table selector (INFERRED)]
w11 00124011CE ?word 0x00124011CE ; 012.4.01.1CE hi12{ST f31=1} [table-lookup idiom, third word (INFERRED)]
w12 01042001CE post (p),(p)+0
w13 0102200000 mac.b (p)0,(p)+0
w14 0000A00415 ld acc,c+,(p)+0
; C-RAM[0x02] (coeff, base 0x00 MEASURED)
; coeff C-RAM[0x02] = op0x62[0] (role output-level, INFERRED)
w15 02122091CD mac (p),(p)+9 ; mem[p]<-acc, acc=0
w16 00002F040E ld acc,(p)-16
w17 0212210000 mac.b (p)0,(p)+16 ; 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 00122F61C0 mac.b (p),(p)-10 ; mem[p]<-acc, acc=0
w22 002E200000 ?word 0x002E200000 ; 02E.2.00.000 hi12{f31=7 ?5 res=020} [SPECULATIVE (prospective, not measured): SRC 0x00 = mem[ptr]/delay-RAM read]
w23 0000200415 ld acc,(p)+0
w24 0212200000 mac.b (p)0,(p)+0 ; mem[p]<-acc, acc=0
w25 0000A00415 ld acc,c+,(p)+0
; C-RAM[0x03] (coeff, base 0x00 MEASURED)
; coeff C-RAM[0x03] = op0x61[1] (role coeff, INFERRED)
w26 0212200000 mac.b (p)0,(p)+0 ; mem[p]<-acc, acc=0
w27 009220A700 ?word 0x009220A700 ; 092.2.0A.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)]
w28 0000A00415 ld acc,c+,(p)+0
; C-RAM[0x04] (coeff, base 0x00 MEASURED)
w29 01822F6000 mac.b (p)0,(p)-10
w30 0040000C63 ?word 0x0040000C63 ; 040.0.00.C63 hi12{?6 res=040} [SPECULATIVE (prospective, not measured): SRC 0x11 = ACCB (2nd accumulator)]
w31 00006284CD ?word 0x00006284CD ; 000.6.28.4CD hi12{-} [table-lookup idiom, class-6 addr8 = table selector (INFERRED)]
w32 00124011CE ?word 0x00124011CE ; 012.4.01.1CE hi12{ST f31=1} [table-lookup idiom, third word (INFERRED)]
w33 01042001CE post (p),(p)+0
w34 0102200000 mac.b (p)0,(p)+0
w35 0000A00415 ld acc,c+,(p)+0
; C-RAM[0x05] (coeff, base 0x00 MEASURED)
; coeff C-RAM[0x05] = op0x62[1] (role output-level, INFERRED)
w36 02122FB1CD mac (p),(p)-5 ; mem[p]<-acc, acc=0
w37 000020F40E ld acc,(p)+15
w38 02122F1000 mac.b (p)0,(p)-15 ; mem[p]<-acc, acc=0
w39 0028200000 ?word 0x0028200000 ; 028.2.00.000 hi12{f31=4 ?5 res=020} [SPECULATIVE (prospective, not measured): SRC 0x00 = mem[ptr]/delay-RAM read]
w40 0880130000 dly.r dsc[k],p+48
w41 040010E000 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 dist_hle = (dsphle == 15);
double dist_drive = 0.0, dist_vol = 0.0;
if (dist_hle)
{
auto q22d = [](u32 v) -> double {
int32_t s = (v & 0x800000) ? int32_t(v) - 0x1000000 : int32_t(v);
return double(s) / 4194304.0; };
const bool bit1 = (dspcfg & 2) != 0;
const double cs = bit1 ? 1.0 : 2.0;
const double c0 = std::fabs(q22d(m_dsp1->cram_read(0x00)) * cs); // DRIVE (measured)
const double c2 = std::fabs(q22d(m_dsp1->cram_read(0x02)) * cs); // VOLUME (measured)
// Map the decoded DRIVE coeff (cell 0x00) to the shaper pre-gain. The AGC normalises the
// signal toward unity, so this directly sets how hard it hits tanh: DISTORTION (curve A)
// is the HARDEST clip of the family, so the floor clips aggressively (a near-square tone)
// and a larger decoded DRIVE pushes it further toward a full square wave.
dist_drive = std::clamp(4.0 + 9.0 * c0, 4.0, 13.0);
// VOLUME: the decoded output-level, kept in a sane makeup range.
dist_vol = std::clamp(0.25 + 0.75 * c2, 0.25, 1.0);
}
Per-sample insert (the reconstructed signal path):
if (dist_hle)
{
const double sl = double(mix_l) / 32768.0, sr = double(mix_r) / 32768.0;
// AGC: a slow peak-envelope follower NORMALISES the signal to a roughly constant
// level before the shaper (this is what an "AGC waveshaper" does -- it is why the
// effect sustains and clips consistently regardless of how loud the note is). A
// small floor keeps true silence silent (no noise pumping).
m_dist_env_l = std::max(std::fabs(sl), m_dist_env_l * 0.9993);
m_dist_env_r = std::max(std::fabs(sr), m_dist_env_r * 0.9993);
const double norm_l = sl / (m_dist_env_l + 0.02); // peak ~1 during a sustained note
const double norm_r = sr / (m_dist_env_r + 0.02);
// DRIVE (decoded) sets how hard the normalised signal hits the shaper; tanh is the
// labelled SPECULATIVE clip curve. VOLUME (decoded) is the output makeup gain.
const double wl = std::tanh(norm_l * dist_drive) * dist_vol;
const double wr = std::tanh(norm_r * dist_drive) * dist_vol;
mix_l = int32_t(std::clamp(wl, -1.0, 1.0) * 32767.0);
mix_r = int32_t(std::clamp(wr, -1.0, 1.0) * 32767.0);
}