Rotary Speaker — HLE reconstruction and DSP bytecode

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

Leslie: crossover + two Doppler-swept (measured 7.26/3.63 ms) + AM rotors. Rotation rate not in program (canonical speeds).

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/prog15_rock_rotary.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 -- ROCK ROTARY
; image rep algo 15  |  slots 15,53  |  unit 0 (I-RAM load 84)
; family rotary  |  confidence high  |  86 words, 33 class-A multiplies (6 named)
; role: rock rotary / rotary speaker (shared with algo 53)
; 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    0000A0A415   ld      acc,c+,(p)+10
        ; C-RAM[0x00] (coeff, base 0x00 MEASURED)
        ; coeff C-RAM[0x00] = op0x61[0] (role coeff, INFERRED)
  w2    0212AF61D5   mac     (p),c+,(p)-10 ; mem[p]<-acc, acc=0
        ; C-RAM[0x01] (coeff, base 0x00 MEASURED)
        ; coeff C-RAM[0x01] = op0x61[1] (role coeff, INFERRED)
  w3    0202A00216   ?word   0x0202A00216   ; 202.A.00.216  hi12{f98=2 f31=1} 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[0x02] (coeff, base 0x00 MEASURED)
  w4    0020200000   ld.b    (p)0,(p)+0
  w5    002E20000B   ?word   0x002E20000B   ; 02E.2.00.00B  hi12{f31=7 ?5 res=020}  [SPECULATIVE (prospective, not measured): SRC 0x00 = mem[ptr]/delay-RAM read; ACT 0x0B = delay-line access (READ/WRITE class-borne)]
  w6    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)]
  w7    0000A00415   ld      acc,c+,(p)+0
        ; C-RAM[0x03] (coeff, base 0x00 MEASURED)
  w8    01822F6000   mac.b   (p)0,(p)-10
  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   010220B1CD   mac     (p),(p)+11
  w14   00002FF40E   ld      acc,(p)-1
  w15   0212201000   mac.b   (p)0,(p)+1 ; mem[p]<-acc, acc=0
  w16   0000A40415   ld      acc,c+,(p)+64
        ; C-RAM[0x04] (coeff, base 0x00 MEASURED)
        ; coeff C-RAM[0x04] = op0x62[0] (role output-level, INFERRED)
  w17   0292A001D3   mac.ta  (p),c+,(p)+0 ; store SUPPRESSED (bit7)
        ; C-RAM[0x05] (coeff, base 0x00 MEASURED)
  w18   0212A01452   ?word   0x0212A01452   ; 212.A.01.452  hi12{ST f98=2 f31=1} cur+  [writes mem[ptr] (bit 4); mode 2, so the target IS the pointer]
        ; C-RAM[0x06] (coeff, base 0x00 MEASURED)
  w19   0202A011D5   mac     (p),c+,(p)+1
        ; C-RAM[0x07] (coeff, base 0x00 MEASURED)
  w20   0202A011D4   mac.tb  (p),c+,(p)+1
        ; C-RAM[0x08] (coeff, base 0x00 MEASURED)
  w21   0202A001D5   mac     (p),c+,(p)+0
        ; C-RAM[0x09] (coeff, base 0x00 MEASURED)
  w22   01022FF687   mac.st  tb,(p)-1
  w23   0804816415   post    acc,c
  w24   0212AFF407   mac.st  acc,c+,(p)-1 ; mem[p]<-acc, acc=0
        ; C-RAM[0x0A] (coeff, base 0x00 MEASURED)
  w25   00002BB647   ld.st   ta,(p)-69
  w26   0000204407   ld.st   acc,(p)+4
  w27   0000204447   ?word   0x0000204447   ; 000.2.04.447  hi12{-}  [SPECULATIVE (prospective, not measured): SRC 0x11 = ACCB (2nd accumulator)]
  w28   09001601D5   dly.w  dsc[k],p+96
  w29   0192A40000   mac.b   (p)0,c+,(p)+64 ; store SUPPRESSED (bit7)
        ; C-RAM[0x0B] (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   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[0x0C] (coeff, base 0x00 MEASURED)
  w35   0000243407   ld.st   acc,(p)+67
  w36   0C400001DA   ldreg   rDA,#0          ; immediate -> the register lo12 selects
  w37   00122FC1D5   mac     (p),(p)-4 ; mem[p]<-acc, acc=0
  w38   01042C01D5   post    (p),(p)-64
  w39   0102A401D5   mac     (p),c+,(p)+64
        ; C-RAM[0x0D] (coeff, base 0x00 MEASURED)
  w40   0000200000   nop
  w41   0302A00655   mac     ta,c+,(p)+0
        ; C-RAM[0x0E] (coeff, base 0x00 MEASURED)
  w42   02122BE41D   ?word   0x02122BE41D   ; 212.2.BE.41D  hi12{ST f98=2 f31=1}  [writes mem[ptr] (bit 4); mode 2, so the target IS the pointer]
  w43   0000247407   ld.st   acc,(p)+71
  w44   0C400001DA   ldreg   rDA,#0          ; immediate -> the register lo12 selects
  w45   00122FC1D5   mac     (p),(p)-4 ; mem[p]<-acc, acc=0
  w46   01042C01D5   post    (p),(p)-64
  w47   0102A401D5   mac     (p),c+,(p)+64
        ; C-RAM[0x0F] (coeff, base 0x00 MEASURED)
  w48   0000200000   nop
  w49   0302A00655   mac     ta,c+,(p)+0
        ; C-RAM[0x10] (coeff, base 0x00 MEASURED)
        ; coeff C-RAM[0x10] = op0x66[0] (role mix/tap, INFERRED)
  w50   02122AC41D   ?word   0x02122AC41D   ; 212.2.AC.41D  hi12{ST f98=2 f31=1}  [writes mem[ptr] (bit 4); mode 2, so the target IS the pointer]
  w51   0000209407   ld.st   acc,(p)+9
  w52   0000A501D5   ld      (p),c+,(p)+80
        ; C-RAM[0x11] (coeff, base 0x00 MEASURED)
  w53   0292A011D5   mac     (p),c+,(p)+1 ; store SUPPRESSED (bit7)
        ; C-RAM[0x12] (coeff, base 0x00 MEASURED)
  w54   0182AFF1D4   mac.tb  (p),c+,(p)-1
        ; C-RAM[0x13] (coeff, base 0x00 MEASURED)
  w55   0282A01692   mac     tb,c+,(p)+1
        ; C-RAM[0x14] (coeff, base 0x00 MEASURED)
        ; coeff C-RAM[0x14] = op0x66[1] (role mix/tap, INFERRED)
  w56   080A816000   ?word   0x080A816000   ; 80A.8.16.000  hi12{ESC f31=5} cur  [class 8: post-sum step (rescale/round/saturate?), OPERATION UNKNOWN]
  w57   02122B0447   ?word   0x02122B0447   ; 212.2.B0.447  hi12{ST f98=2 f31=1}  [writes mem[ptr] (bit 4); mode 2, so the target IS the pointer]
  w58   00002F6407   ld.st   acc,(p)-10
  w59   09001601D5   dly.w  dsc[k],p+96
  w60   0192A5B000   mac.b   (p)0,c+,(p)+91 ; store SUPPRESSED (bit7)
        ; C-RAM[0x15] (coeff, base 0x00 MEASURED)
  w61   00822001C0   mac.b   (p),(p)+0
  w62   0C4032044C   ldreg   r4C,#25          ; immediate -> the register lo12 selects
  w63   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]
  w64   08801202C7   dly.r  dsc[k],p+32
  w65   0102AA64C8   ?word   0x0102AA64C8   ; 102.A.A6.4C8  hi12{f98=1 f31=1} cur+  [gain multiply (same op in phaser all-pass and reverb diffuser)]
        ; C-RAM[0x16] (coeff, base 0x00 MEASURED)
  w66   0000A0A412   ld      acc,c+,(p)+10
        ; C-RAM[0x17] (coeff, base 0x00 MEASURED)
  w67   0212AF91D5   mac     (p),c+,(p)-7 ; mem[p]<-acc, acc=0
        ; C-RAM[0x18] (coeff, base 0x00 MEASURED)
  w68   0202A051D5   mac     (p),c+,(p)+5
        ; C-RAM[0x19] (coeff, base 0x00 MEASURED)
  w69   0202AFD1D5   mac     (p),c+,(p)-3
        ; C-RAM[0x1A] (coeff, base 0x00 MEASURED)
  w70   0202200000   mac.b   (p)0,(p)+0
  w71   00002FE407   ld.st   acc,(p)-2
  w72   00122081C0   mac.b   (p),(p)+8 ; mem[p]<-acc, acc=0
  w73   09001601D5   dly.w  dsc[k],p+96
  w74   0192A50000   mac.b   (p)0,c+,(p)+80 ; store SUPPRESSED (bit7)
        ; C-RAM[0x1B] (coeff, base 0x00 MEASURED)
  w75   00822001C0   mac.b   (p),(p)+0
  w76   0C4032044C   ldreg   r4C,#25          ; immediate -> the register lo12 selects
  w77   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]
  w78   08801202C7   dly.r  dsc[k],p+32
  w79   0102AA74C8   ?word   0x0102AA74C8   ; 102.A.A7.4C8  hi12{f98=1 f31=1} cur+  [gain multiply (same op in phaser all-pass and reverb diffuser)]
        ; C-RAM[0x1C] (coeff, base 0x00 MEASURED)
  w80   0000A00415   ld      acc,c+,(p)+0
        ; C-RAM[0x1D] (coeff, base 0x00 MEASURED)
        ; coeff C-RAM[0x1D] = op0x74[0] (role input/tone, INFERRED)
  w81   0212A051D5   mac     (p),c+,(p)+5 ; mem[p]<-acc, acc=0
        ; C-RAM[0x1E] (coeff, base 0x00 MEASURED)
  w82   0202AF81D5   mac     (p),c+,(p)-8
        ; C-RAM[0x1F] (coeff, base 0x00 MEASURED)
  w83   0202AF71D5   mac     (p),c+,(p)-9
        ; C-RAM[0x20] (coeff, base 0x00 MEASURED)
  w84   0880160000   dly.w  dsc[k],p+96
  w85   060210E000   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 rot_hle = (dsphle == 0x19);
	double rot_tinc = 0.0, rot_binc = 0.0, rot_tdep = 0.0, rot_bdep = 0.0; int rot_tbase = 0, rot_bbase = 0;
	if (rot_hle)
	{
		rot_tinc  = 6.8 / double(STREAM_RATE);                  // treble horn ~6.8 Hz (fast), canonical
		rot_binc  = 5.7 / double(STREAM_RATE);                  // bass drum ~5.7 Hz (fast), canonical
		rot_tbase = int(320.0 * sr48 + 0.5);                    // measured Doppler line (7.26 ms)
		rot_bbase = int(320.0 * sr48 + 0.5);
		rot_tdep  = 40.0 * sr48;                                // Doppler depth (vibrato)
		rot_bdep  = 24.0 * sr48;
		m_rot_lp_l.set_damping(0.88); m_rot_lp_r.set_damping(0.88);   // ~800 Hz crossover
	}

Per-sample insert (the reconstructed signal path):

		if (rot_hle)   // ROTARY SPEAKER: crossover + two Doppler-swept, amplitude-modulated rotors
		{
			const double x = 0.5 * (double(mix_l) + double(mix_r)) / 32768.0;
			const double low = m_rot_lp_l.process_one(x), high = x - low;
			const double ts = std::sin(TWO_PI * m_rot_tph), bs = std::sin(TWO_PI * m_rot_bph);
			m_rot_tph += rot_tinc; if (m_rot_tph >= 1.0) m_rot_tph -= 1.0;
			m_rot_bph += rot_binc; if (m_rot_bph >= 1.0) m_rot_bph -= 1.0;
			const double ttap = rot_tbase + rot_tdep * 0.5 * (1.0 + ts), btap = rot_bbase + rot_bdep * 0.5 * (1.0 + bs);
			m_rot_t_l.write(high); m_rot_t_r.write(high); m_rot_b_l.write(low); m_rot_b_r.write(low);
			const double thl = m_rot_t_l.read(ttap), thr = m_rot_t_r.read(ttap + 18.0);
			const double bl = m_rot_b_l.read(btap), br = m_rot_b_r.read(btap + 10.0);
			const double yl = thl * (0.6 + 0.4 * ts) + bl * (0.7 + 0.3 * bs);
			const double yr = thr * (0.6 - 0.4 * ts) + br * (0.7 - 0.3 * bs);
			mix_l = int32_t(std::clamp(yl, -1.0, 1.0) * 32767.0);
			mix_r = int32_t(std::clamp(yr, -1.0, 1.0) * 32767.0);
		}