Acoustic Modelling LSI (L7A1429)
The L7A1429 acoustic modelling LSI — the device at 0x00104000
The SX-WSA1 / SX-WSA1R is named after its modelling
engine, and this is that engine: IC3, the service manual’s L7A1429 MODELING
LSI, sitting in CPU 2’s address space at 0x00104000 behind a two-word port
pair. MAME models it as l7a1429_device, “Technics Acoustic Modeling LSI”.
It is a per-channel pair of coupled linear resonators, sixty-four channels of them. Each channel is programmed with a tuning, a damping one-pole filter, an excitation shaping pair, and a position along the resonator that the firmware modulates in real time. A third, independently indexed copy of the damping filter sits in the register set with a fixed, tone-specified index that no editor control can reach. It is not a sample player: not one of its nineteen per-channel registers carries an address, a length or a loop point, and its own output is not audio — it is a 13-bit waveform stream handed to the tone generator on request. The sample player is the other chip.
Where the line between measurement and inference runs on this page. What the firmware writes is measured, instruction by instruction. What the silicon does with it is reconstructed from the numbers’ closed forms and the tone editor’s own captions. Every row of every table below carries a grade, and What is not known is a section rather than a footnote.
⚠ Two limits that do not move
1. The part identity is settled by the schematic, not inferred. No WSA1R ROM
names any part — but the service manual closes the chain in two steps. IC27, a
D74HC139GS, decodes CPU 2’s SCS0 with SA14/SA15 into four selects, and
1Y1 is WFICS = 0x104000 (its siblings being SMIF = 0x100000, the
link; KSCS = 0x108000, the keybed; and SGCS = 0x10C000, the tone
generator — all four in the order the disassembly derived them). IC3’s host port
then takes NSGCE ← WFICS, and IC3 is labelled L7A1429 MODELING LSI.
The interface matches the one read off the instruction stream, pin for pin:
NAD ← SA1 means a single address line selects address-versus-data, which
is the driver’s +0 = select, +2 = data.
⚠ What remains inference is the internal signal path — a vocabulary and a coefficient set are not a block diagram. See What is not known.
1b. Its output is not audio — it feeds the tone generator. IC3’s RQWFI and
IOWFI(0..12) outputs are the nets RQWFI and DWFI0..DWFI12 arriving on
IC4’s pins 25-40: a 13-bit stream with a request line. IC4 is
TC183C230002, the sheet’s own spelling being “TONE GENELATOR LSI”, and its
crystal X4 runs at 33.8688 MHz = 768 x 44100, which is where this page’s
44.1 kHz sample rate comes from. So the modelling LSI and the tone generator are
two stages of one signal path, not two independent voice engines — which
settles a question the firmware alone could not.
2. “No executable payload crosses this device” is a claim about the BUS, not
about the silicon. The whole 2 MB image holds nine 0x00104000 literals, none
of them a read, and a live bus trace sees writes only. But an engine with fixed
on-die microcode exposing only coefficients would produce exactly this traffic.
The bus carries a fixed destination set with block * 0x40 + channel numbering
and values marshalled out of a packed record — no opaque byte stream, no
handshake, no micro-DMA channel. That constrains the algorithm from outside; it
does not open it.
The bus interface
0x00104000 + 0x00 write 16-bit REGISTER NUMBER (select)
0x00104000 + 0x02 write that register's 16-bit VALUE (data)
0x00104000 + 0x04 -- no located access
register number = block * 0x40 + channel channel = 0x00 .. 0x3F
All nineteen per-channel registers are 16 bits: every store into the staging
struct and every store to the data port is a word store. The eighteen non-zero
blocks run 0x0040 .. 0x0480 with no gap, each 0x40 from the last, and block
0 is the channel with no arithmetic at all.
The device is write-only as far as this firmware is concerned. There is no
read port anywhere in the image — and that is a difference from its neighbour,
not an assumption: the companion tone generator at 0x0010C000 has a documented
read port at +0x04 and a dedicated read accessor. A model should return 0 for a
read and log it, because a read would be new information.
The (select, data) pair is the only atomic unit. Every write is a store of
the register number followed by a store of the value, in that order, in all eight
driver routines, with one to four unrelated instructions in between and no
interrupts disabled. Nothing larger is atomic, and there is no commit strobe —
see Timing.
No write-recovery delay is needed. Every 0x0010C000 data write in this
firmware is followed by five nops of bus padding. Inside the eight
0x00104000 routines that five-nop run occurs zero times; consecutive
register writes are separated only by the three to five instructions that build
the next select value. The 0x0010C000 loops of the very same reset routine are
the positive control, and they have it.
The register table
chan is 0x00 .. 0x3F; the register number is block + chan. Names in bold
are the tone editor’s own captions, drawn by the MODELING pages’ display lists.
Q is the 43-byte wave-select record, and pN is tone-edit parameter N =
wave-select byte +0x0N in hex.
This is the page’s CROSSCHECK-NAME-TABLE: wsa1/notes/l7a1429_crosscheck.py
reads the name column below and fails if it disagrees with the disassembly’s
notes, its HLE guide or the MAME driver’s block_name().
| reg | what the firmware writes | name | role | grade |
|---|---|---|---|---|
0x0000 |
(R[+0x07]<<8) | P[+0x07]; bit 7 cleared when its own bits 6..4 are non-zero; bits 13..8 = the channel on the power-on path; bit 2 set by every note event, on and off |
— | a mode / enable word. Its fields are named — bits 15 and 14 are the MAIN and SUB RESO MODE flags, bits 6..4 a part-level enable that gates 0x0300, bit 7 the sign of SUB GAIN — but the word as a whole is not, and bits 3, 1 and 0 are unaccounted for |
UNIDENTIFIED |
0x0040 |
SatAsym(P[+0x0A] + P[+0x12] + d1), saturating to [0x0000, 0x7FFF] |
MAIN RESONATOR KEY SHIFT + DETUNE |
a tuning offset in 1/256 semitone: p29 whole semitones, p30 ≈ 0.78 cent a step |
PROVEN |
0x0080 |
the same, from P[+0x0C] + P[+0x14] and d2 |
SUB RESONATOR KEY SHIFT + DETUNE |
as above, from p41 / p42 |
PROVEN |
0x00C0 |
Curve_Log2_251[clamp(R[+0x12]+R[+0x16]+R[+0x21], 0..250)] + (0x4280 − R[+0x0E]) − R[+0x0C], forced to 0x0000 / 0x7F00 on underflow |
resonator P0SITI0N, with its TOUCH and its P0SITI0N M0VEMENT |
a log-domain period, 3072 counts per octave = 1/256 semitone, pitch entering negated, pivot note 66.5, range [0, 0x7F00] = 10.58 octaves. Its key-follow term is switched on and off by FORMANT (FIX / MOVE, p14 bit 7) |
name PROVEN, unit and direction STRONG; absolute scale UNIDENTIFIED |
0x0100 |
Const_0100_251[the same index as 0x00C0] = 0x0100 in all 251 entries; 0x0000 on the ROM-image path |
— | 0x00C0’s table-pair companion |
value PROVEN, unit UNIDENTIFIED |
0x0140 |
Curve_Exp2Decay_256[clampU8(0xCF − g(v1) + (s8)(0x00E08C))] & 0xFFF8, or 0x0000 on the gated arm; g(v) = v<48 ? v/2+24 : v |
MAIN FITTING, decay form |
a Q15 quantity on a 0.3763 dB/step exponential, 13 bits used, 78.6 dB span; p21 value, p23 touch |
name PROVEN, role UNIDENTIFIED |
0x0180 |
the same, with v2, from p31 / p34 |
SUB FITTING, decay form |
as above | PROVEN / UNIDENTIFIED |
0x01C0 |
high16( fold(reg 0x0400's word) × Curve_Exp2Rise_128[clamp(v1, 0..PART[+0x11])] ) |
MAIN FITTING, rise form |
section A’s cutoff coefficient scaled by 1 − 2^(−v1/16) ∈ [0, 0.996) = 0 to −4.56 octaves |
name PROVEN; STRONG that it is the same quantity as 0x0400; see the one undecided reading |
0x0200 |
the same, with v2 and reg 0x0440’s word |
SUB FITTING, rise form |
as above | PROVEN |
0x0240 |
( high16( fold(reg 0x0480's word) × Curve_Exp2Rise_128[clamp(R[+0x14]+R[+0x18]+abs(R[+0x21])/4, 0..0x7F)] ) & 0xFFF8 ) | 7 |
the register DEPTH scales |
section C’s Rise-scaled coefficient. Its index is DEPTH (p14 bits 0-6) plus SUB GAIN plus the P0SITI0N M0VEMENT term; low 3 bits a separate field |
structure PROVEN, name PROVEN |
0x0280 |
Curve_Exp2Decay_101[clamp(R[+0x10]+R[+0x23], 0..100)] |
SUB GAIN |
a Q15 gain over a 0..100 percent control with an explicit OFF, 37.3 dB taper; p33 value, p36 touch |
PROVEN |
0x02C0 |
the literal 0xFF00, always, on every path |
— | — | UNIDENTIFIED |
0x0300 |
b = ExpCurve_0_to_0x80[Q[+0x13]], written as (b<<8) | b; 0x0000 when reg 0’s bits 6..4 are clear |
INTERACTION GAIN |
an 8-bit gain over a 0..127 control, 42.1 dB, duplicated into both halves, and zero unless the part has layers grouped | PROVEN |
0x0340 |
Curve_FE05C9[i3] |
MAIN MUTING, Q13 form |
the Q13 companion of 0x0400’s cutoff — computable from it |
PROVEN |
0x0380 |
Curve_FE05C9[i4] |
SUB MUTING, Q13 form |
as above, of 0x0440 |
PROVEN |
0x03C0 |
Curve_FE05C9[i5], i5 = clamp(Q[+0x0F], 44..96) |
— no editor name exists | section C’s companion coefficient | table identity PROVEN; name resolved, negatively |
0x0400 |
Curve_FE04C9[i3] |
MAIN MUTING, Q16 form |
★ a one-pole lowpass cutoff, index = MIDI note − 36; clamped Table_FDFF96[zone] .. PART[+0x12] = 466 Hz .. 16.7 kHz |
fit PROVEN, name PROVEN |
0x0440 |
Curve_FE04C9[i4] |
SUB MUTING, Q16 form |
as above | PROVEN |
0x0480 |
Curve_FE04C9[i5] |
— no editor name exists | section C’s cutoff, clamped 44..96 = 831 Hz .. 16.7 kHz | fit PROVEN; name resolved, negatively |
0x0800 (no channel) |
the literal 0x1100, once, at power-on |
— | a 20th register number, outside the per-channel map | UNIDENTIFIED |
Fourteen of the nineteen carry a name in the machine’s own vocabulary, two are
known constants, two are refused a name with a reason, and one — 0x0000 —
is named only in its fields.
⚠ The two refusals are an answer, not a gap. 0x03C0 and 0x0480 have exactly
one input, p15, and no editor field writes p15: of 258 call sites across
the firmware’s twenty tone-message builders, exactly one passes parameter
0x0F, and that one addresses a different record on a different screen. p15
is set by the RESONATOR TYPE preset instead. The firmware has no caption to
give those two registers, so the right result is a name withheld — not a name
guessed, and not a question left open.
The registers group into two pairs and three triples
The A/B twinning is not a reading of the addresses; it is measured in the code, in the ROM’s own reset image and in the factory data.
pair 0x0040 0x0080 MAIN / SUB tuning
pair 0x0140 0x0180 MAIN / SUB FITTING, decay form
triple 0x01C0 0x0200 0x0240 three Rise-scaled coefficients
triple 0x0340 0x0380 0x03C0 three Curve_FE05C9 coefficients
triple 0x0400 0x0440 0x0480 three Curve_FE04C9 coefficients
alone 0x0000 0x00C0 0x0100 0x0280 0x02C0 0x0300
In the code: for each claimed pair the two code runs agree byte for byte at 0.91–0.98, against a null that slides the second run over 122 nearby offsets and peaks at 0.20. In the ROM: the power-on reset image’s words satisfy w5 = w6, w7 = w8, w13 = w14, w16 = w17. In the data: over 133 factory wave-select records whose framing self-checks, the ten twinned parameters are equal per record in 130–133 cases each — and the null over all 43 × 42 ordered column pairs finds no other cross-half twinning outside the four envelope-descriptor parameters, which the firmware’s own case table treats as a separate group.
The third instance is real, not an artefact of the addressing.
Curve_FE04C9, Curve_FE05C9 and Curve_Exp2Rise_128 are each cited exactly
three times in the whole 512 KB image, at three independent indices: two
key-scaled ones for MAIN and SUB, and a third, i5 = clamp(Q[+0x0F], 44..96),
computed and cached earlier. Two further ROM images agree from paths that never
run the packer, and the third slot’s index of 84 is the value the factory data
carries in 123 of 133 records.
And the factory tones drive MAIN and SUB identically. The two resonators
differ only through SUB GAIN — 100 in 131 of 133 records — and, in three
records, a −12 semitone SUB KEY SHIFT. They are a matched pair, not two
independent voices.
The pairs are not “two elements per voice”. A four-element tone occupies four channels, each with its own full nineteen-register set: the packer’s sub-record selector is driven by a loop counter bounded by 4 and by 2, and each iteration re-reads a different channel byte. Element multiplicity is already spent on channels. MAIN and SUB live inside one channel.
How the names were established
The nineteen registers are named from the tone editor’s seven MODELING pages, and the instrument that reads those pages is the screen geometry, not a guess at which caption goes with which parameter.
Every value the firmware draws carries its display-RAM byte address. The
LCD controller’s own SYSTEM SET bytes give C/R + 1 = 40 bytes per line, so
row = address / 40 and x = 8 · (address % 40) put every drawn value into the
same coordinate space as the captions the same routine draws. The stride is not
fitted: sweeping every stride from 4 to 256, four of them make PAGE1/3’s ten
values fall into two rows with identical column sets, and what picks 40 is the
controller’s own initialisation.
A page’s read-back order is its parameter map. Each page’s ENTER routine
fires a run of read-back requests at CPU 2 and stores reply n at
((u8 *)0x27A6)[n], so the request order is the map from tone-edit parameter
to the RAM byte the page draws. That is checked, not assumed: the per-field
editors name an (index, parameter) pair directly, in two immediates a few bytes
apart, and all thirty-three such bindings agree with this map — zero
contradictions, where ten indices are in play per page, so an unrelated map could
not (see the field editors and their limits below).
That gives, among others:
| control | where it lives | what it reaches |
|---|---|---|
P0SITI0N |
p13, drawn as p13/5 . 2·(p13%5) |
0x00C0 |
DEPTH |
p14 bits 0-6 | the index of 0x0240 |
FORMANT |
p14 bit 7, drawn FIX / MOVE |
not a register: the packer tests that exact bit to switch 0x00C0’s key-follow term on or off |
INTERACTION GAIN |
p19 | 0x0300 |
RESO MODE |
p21 / p31 bit 7, drawn OFF / ON |
0x0000 bits 15 / 14, and 0x0C00 = one octave added to that resonator’s tuning |
RESO SCALE |
p22 / p32 bit 7, drawn OFF / ON |
R[+0x1A] — and no further; see What is not known |
GROUP |
p11 bits 7:6, on the MODELING top page | the part-level enable in 0x0000 bits 6..4, which gates 0x0300 |
⚠ DEPTH and FORMANT are two halves of one byte, and the firmware splits it
at exactly the bit the screen splits it at: one consumer masks p14 with 0x80
and skips a key-follow term when it is set, the other clears bit 7 and stores
the remainder as 0x0240’s index. A formant that is fixed or moves with the
note is what that control is for.
INTERACTION GAIN has a second, independent argument beyond its field
editor: register 0x0300 is written as zero
unless the part has layers grouped. A gain that only exists once layers are
grouped, on a screen whose grouping control decides which layers interact,
captioned INTERACTION GAIN. DEPTH and FORMANT have no reason to be
mode-gated; this register is.
MAIN and SUB, from the row labels
PAGE1/3 draws ten values on two rows with identical column sets, and the
same paint routine draws the labels MAIN RESONATOR and SUB RESONATOR.
Value row 156 is +4 display rows below the MAIN label and value row 187 is
+4 below the SUB label — the same offset. Swapped, the offsets are +35
and −27: unequal, and one would put a value above its own label.
That argument never mentions SUB GAIN, which is the other, independent reason
to call the sub side the sub side: it is the one parameter belonging to one
resonator and not the other, and the editor captions it on two pages. Two routes,
one answer. FITTING and MUTING are likewise held by two facts — the same
page’s own column headers stand over their own fields, and only one of the two
families carries a key-follow stage, which is the one the editor heads
MUTING. The five headers are two-line stacks — FIT/TING, MUT/ING,
KEY/SHIFT, DE/TUNE, RESO/SCALE — which is why the fourth column is
DETUNE and the fifth is RESO SCALE.
The field editors, and each field’s limits
Each MODELING page routes its DATA-dial edits through a table of handler pointers
indexed by the panel event code — twenty-four editors in all, none of them
the target of a call, which is why they carried no name until the tables that
reach them were decoded. Every editor builds one 11-byte edit descriptor and
hands it to ToneEdit_ApplyStep (0xFD6CE1), which reads a MASK, a
SHIFT, a MIN and a MAX — all immediates inside the editor — and
clamps (packed >> SHIFT) & MASK into [MIN, MAX]. So each field’s limits are
read straight from the ROM, and consumed by code read from the ROM, with
nothing interpreted between. The step is ±1, or ±3 for the coarse key group.
Thirty-three (screen, RAM index, parameter) bindings come out of the
twenty-four, and every one agrees with the read-back-order map above — zero
contradictions; the cross-check is verified able to fail (a run against a map
with MAIN/SUB FITTING swapped registers exactly two clashes). Two independent
instruments — the order in which a page’s ENTER routine fires its read-back
requests, and immediates inside routines those ENTER routines never touch — land
on the same map.
| control | parameter (MAIN / SUB) | limits |
|---|---|---|
RESONATOR TYPE |
p11 bits 0-5 | 0..63 (the 64-name table) |
GROUP |
p11 bits 7:6 | 0..4 |
P0SITI0N |
p13 | 0..250 = 0.0..50.0 in 0.2 |
DEPTH |
p14 bits 0-6 | 0..127 |
FORMANT |
p14 bit 7 | 0..1 (FIX / MOVE) |
INTERACTION GAIN |
p19 | 0..127 |
WIDTH |
p17 | 0..50 |
SPEED |
p18 | 0..50 |
S/H |
p18 bit 7 | 0..1 |
TOUCH |
p16 | −50..+50 |
FITTING |
p21 / p31 | 0..127 |
MUTING |
p22 / p32 | 0..127 |
KEY SHIFT |
p29 / p41 | −60..+60 (five octaves either way) |
DETUNE |
p30 / p42 | −128..+127 |
RESO SCALE |
p22 / p32 bit 7 | 0..1 (OFF / ON) |
SUB GAIN |
p33 (SUB only) | −100..+100, signed |
FITTING touch |
p23 / p34 | −50..+50 |
MUTING touch |
p24 / p35 | −50..+50 |
SUB GAIN touch |
p36 (SUB only) | −50..+50 |
RESO MODE |
p21 / p31 bit 7 | 0..1 (OFF / ON) |
MUTING SLOPE |
p28 / p40 | −50..+50 |
KEY FOLLOW low / break / high |
p26·p25·p27 / p38·p37·p39 | mutually clamped low ≤ break ≤ high |
Two of these settle earlier open points. RESO SCALE is edited by
ToneEditField_A5_ResoScale (0xFD4EFB) — the same parameter as MUTING
(p22 / p32) with MASK 0x01, SHIFT 7 — so it is a bit-field editor over bit 7
of that byte, not a separate parameter. And the three KEY FOLLOW notes
bound one another: the editor takes its MAX and MIN from the other two RAM
cells, so the firmware itself enforces low ≤ break ≤ high, pinning the ordering
independently of any screen coordinate. SUB GAIN being signed −100..+100
is what makes 0x0000’s bit 7 (the sign of SUB GAIN) a state a user can
actually reach.
(wsa1/notes/wsa1_toneedit_field_editors.py --selftest, 61 checks.)
The units
This is the most useful thing on the page. Six of the nineteen registers have an exact closed form, and the closed forms name physical quantities.
The storage format has to be undone first
Read as s16, Curve_FE04C9 is not monotone. Passed through the firmware’s
own fold() — the step the packer already applies on the way to register
0x01C0 —
fold(x) = (x & 0x8000) ? 0x8000 − (x & 0x7FFF) : x + 0x8000
both coefficient tables become strictly monotone over all 128 entries. fold is
a sign-magnitude → offset-binary converter, and it is the inverse of the
table’s encoding: applying it is what makes the numbers admit any fit at all.
The null is the two’s-complement reading, which is not monotone.
★★ MUTING is a bilinear one-pole lowpass, and its index is a SEMITONE
Over the live band k = 9..100, with g = tan(π·f_k/44100) and
f_k = 440·2^((k−33)/12):
fold(Curve_FE04C9)[k] = round(65536 · g/(1+g)) max |residual| = 1 count
fold(Curve_FE05C9)[k] = round( 8192 · (1 − 1/(128·g))) max |residual| = 5 counts
g/(1+g) with g = tan(π f/fs) is the coefficient of a one-pole lowpass under
the bilinear transform, and g is the prewarped cutoff.
⚠ The slope is fitted, not assumed, and the rivals are all far worse:
fit over k = 9..100 |
R² | max residual |
|---|---|---|
straight line in F |
0.7781 | 25678 counts |
straight line in log2 F — a plain exponential |
0.9982 | 0.155 in log2 |
straight line in log2 atan(F/(65536−F)) — the claimed law |
0.99999998 | 0.00136 in log2 = 1.63 cents |
The fitted slope is 1 / 12.0017 per index step — twelve steps per octave to 0.01%. Forcing the slope to exactly 1/12 and solving the ROM’s own entries for the index-0 frequency gives 65.4201 Hz = MIDI note 36.0036, which is 0.36 cents from note 36 exactly against a fit scatter of 1.63 cents. So
the cutoff index is a semitone, and
k = MIDI note − 36.
Four rival value-to-frequency maps were fitted as the control, and the best of them is 50× worse than the bilinear reading:
| map | steps/octave | max residual |
|---|---|---|
bilinear, f = atan((1+a)/(1−a))/π |
12.0016 | 0.8 cents |
b0 read as f directly |
13.3058 | 188.5 cents |
one-pole alpha, −ln(1−b0)/2π |
12.4414 | 41.6 cents |
the word read as a POLE, −ln(abs(a))/2π |
10.9995 | 1333.2 cents |
K read as f |
11.6180 | 317.0 cents |
The sample rate is 44,100 Hz and it was fixed independently. IC4’s crystal
is 33.8688 MHz = 768 × 44100; the f64 constant pool in prom_c holds 44100,
1/44100, 1/220500 and 1/441000. Requiring f(i) = 440·2^((i+36−69)/12) of
the ROM entries alone implies a sample rate of 44,091 ± 11 Hz — 44,100 to
within a third of a cent — and no other standard rate family is reachable,
because 32 k and 48 k would need a non-integer note offset. A curve table’s
closed form and a crystal on a schematic agree to 0.33 cent by wholly
independent routes.
★ And the ceiling is Nyquist, which needs no external constant at all. Both
tables saturate at exactly k = 100 and hold that value for the remaining 28
entries: θ(100) = 1.50285 rad < π/2 < θ(101) = 1.59221. A table of a tan()
that stops one step before its own pole is a bilinear filter coefficient.
★ The two coefficient tables are ONE parameter, not two. They imply the same
prewarped cutoff to 17.4 cents worst case over the live band and 3.4 cents over
k = 9..60; entry for entry, (1 − G/8192)·g = 1/128. So 0x0340 / 0x0380 /
0x03C0 are computable from 0x0400 / 0x0440 / 0x0480. An emulator has
one degree of freedom per section there, not two.
// registers 0x0400 / 0x0440 / 0x0480
static double a1_from_word(uint16_t w) { // sign-magnitude Q15 -> [-1, +1)
int mag = (w & 0x8000) ? -(int)(w & 0x7FFF) : (int)w;
return mag / 32768.0;
}
static double cutoff_hz(uint16_t w, double fs) {
double a1 = a1_from_word(w);
if (a1 >= 1.0) return fs / 2.0;
double K = (1.0 + a1) / (1.0 - a1); // K = tan(pi * fc / fs), the prewarp
return fs * atan(K) / M_PI;
}
Musical sanity, and the reason this is a filter and not a pitch: the readers
clamp the index to 44..96 and to Table_FDFF96[zone] .. PART[+0x12], i.e.
831 Hz to 16.7 kHz and 466 Hz to 16.7 kHz. A pitch parameter would not be
clamped to start at 831 Hz.
★★ P0SITI0N is a log-domain PERIOD at 1/256 semitone per count
T[0] = 0x6C00 = 27648 = 108.000 semitones
T[k] = round(27543 − 3072 · log2 k) for k >= 1, max |residual| = 1 count
3072 counts per halving is 12 × 256, so one count is 1/256 semitone — the
same unit the tone generator’s own pitch register uses. Null: a straight line in
T against k gives R² = 0.781; against log2 k, R² = 1.000000 with a max
residual of 0.56 counts.
The register the table feeds subtracts the key-followed pitch from 0x4280 =
note 66 plus the half-step centre, so the pitch enters negated with a slope of
exactly −1, and the result is clamped to [0x0000, 0x7F00] = 10.58 octaves.
A log-domain quantity that falls by one octave when the note rises by one octave is a period or a time, not a frequency. In a resonator, an excitation or pickup position along the medium is a delay-tap time, and it scales with the pitch period — which is exactly a slope of −1 against key.
★ Two independent routes meet here. From the ROM numbers alone 0x00C0 is a
log-domain period with slope −1 against key; from the drawn captions alone it is
the resonator P0SITI0N, with its own P0SITI0N M0VEMENT page. Likewise
0x0400/0x0440 are a one-pole cutoff by fit and MUTING by caption — and
muting a string or a bore is damping, which is modelled by a one-pole lowpass
in the loop. A slope of exactly −1 against key is the single strongest
discriminator available: a filter envelope’s time may track the key partially; a
delay-tap position must track it exactly.
⚠ The absolute scale is unidentified. The unit per count, the direction and the 10.58-octave span are fixed. The constant that turns a register value into a time in samples is nowhere in the image, and there is no reader in the firmware to supply one.
The gain controls: 37 to 42 dB, and their index ranges name them
All the exponential tables share one slope — 16 steps per doubling = 0.3763 dB per step.
| table | closed form | max residual | span |
|---|---|---|---|
Curve_Exp2Decay_256 |
round(32768·2^((k−255)/16)) |
4 counts | 0 for k ≤ 46, then 78.6 dB |
Curve_Exp2Rise_128 |
32768·(1 − 2^(−k/16)) |
0 — exact on all 128 | 0 .. 0x7F7A |
Curve_Exp2Decay_101 |
T[0]=0; round(32768·2^((k−100)/16)) |
4 counts | 0, then 37.28 dB |
ExpCurve_0_to_0x80 |
T[0]=0; max(1, round(128·2^((k−127)/16))) |
0 — exact on all 128 | 0, then 42.1 dB |
★ Two of them have index ranges that are UI controls, and say so.
Curve_Exp2Decay_101 is indexed by a value clamped 0..100: a 0..100 percent
control with an explicit OFF position, given a 37 dB logarithmic taper — and it
is exactly what SUB GAIN needs. ExpCurve_0_to_0x80 is indexed by a record
byte 0..127: a 0..127 control over 42 dB, written into register 0x0300 as
one byte in both halves, which is the shape of a device with two 8-bit fields
fed the same number.
Nulls, over bands where quantisation is under 0.0014 in log2 so the comparison is
about the law and not the rounding: a straight line in T reaches R² 0.70–0.97;
a straight line in log2 T reaches R² 0.99985–1.000000.
Touch scaling has an exact unit too
Four LinCoef_* ramps scale a record depth by the touch level,
(T[D]·A) >> 5, with the curve mirrored — not negated — for a negative depth,
so a negative depth flips which end of the range is the pivot rather than the
sign of the contribution. Each ramp is exact against its integer law on every
entry.
The index is the velocity: it is voice[+0x0C] & 0x7F, and the note is held
separately in the same record at +0x05 as note | 0x80. The editor agrees six
for six — the six depth bytes these ramps consume are exactly the six fields of
the page captioned TOUCH DEPTH, plus TOUCH on P0SITI0N M0VEMENT.
| table | law | Q5 range | destination, and therefore its unit |
|---|---|---|---|
LinCoef_Position_TouchRamp_Q5_128 |
2k − 128 |
−4.000 .. +3.938 | the Curve_Log2_251 index |
LinCoef_Fitting_TouchRamp_Q5_128 |
65k//128 − 32 |
±1.000, bipolar, single zero at k=64 |
an exp2 index — so one unit is 0.3763 dB |
LinCoef_Muting_TouchRamp_Q5_128 |
k//2 − 64, T[127]=0 |
−2.000 .. −0.031 | the cutoff index, in semitones |
LinCoef_SubGain_TouchRamp_Q5_128 |
byte-identical to the Muting ramp |
— | a percentage of SUB GAIN: one point = 0.376 dB |
★ The Muting ramp’s slope is 1/64 of a Q5 unit per touch step and its
destination is in semitones of cutoff, so a depth byte of 64 is exactly one
semitone of cutoff per touch step, and the signed byte’s ±127 range is ±198%.
The SubGain ramp is the second exact one: a depth of 64 moves SUB GAIN by one
point of its 0..100 control per touch step = 0.376 dB, so a depth of 50 sweeps
the whole control and anything above it saturates.
⚠ The cutoff index carries a second scaling stage, and that one is keyed on
the note. ks(Q, o) takes a breakpoint, two note bounds and a Q5 slope, and
its slope byte is a Q5 where 32 = 100%, not 64. The two stages differ in
their variable and in their constant; an implementation that shares one
constant between them is wrong by a factor of two, and one that shares the
variable is wrong about what the control does.
★ In the factory data every one of these depth bytes but a single −5 is a
multiple of ten, so the editor’s control moves in steps of ten: 15.6% of cutoff
follow per click. The SUB GAIN touch depth is zero in every factory record,
which is what an unused control looks like.
Table_FDFF96 — 256 bytes, 27 distinct values 0x22..0x3C, indexed by the key
zone — is the lower clamp on the cutoff index, i.e. a minimum cutoff of
466 Hz to 2093 Hz per key zone, a floor that keeps the filter above the zone’s
own band. It has no closed form; the values are data.
⚠ One table is musically absurd at the sample rate
Curve_Exp2Decay_256’s two largest entries are 0x8000 (= 1.0) and 0x7A90
(= 0.957520), with nothing in between. As a per-sample pole at 44.1 kHz,
0.957520 is a time constant of 0.522 ms, and even 1 ms is unrepresentable. A
per-sample reading of that table is wrong.
★ Stepped instead at the firmware’s own 40.69 Hz control rate, the same table
gives τ = 8.9–566 ms and T60 = 24 ms to 3.9 s, which is musical. ⚠ But that
refresh rate is measured for the 0x00C0 / 0x0100 / 0x0240 trio, not for
0x0140 / 0x0180, and nothing in the ROM says the LSI advances anything on the
host’s write cadence. It is a hypothesis with a number attached. An
implementation should make the rate a named constant and not bury it.
The one arithmetic that reads two ways
0x01C0 carries b0 × r, where b0 is section A’s own bilinear coefficient
(register 0x0400) and r = 1 − 2^(−v1/16). That number reads two ways and
the arithmetic is identical under both:
- as a second, lower cutoff for the same section — which is what the
editor’s caption structure supports,
FITTINGhaving a rise form and a decay form, the two halves of a filter envelope; - as an input gain pre-multiplied into the coefficient, which is what a
filter implementation does to save a multiply, and which explains why
0x01C0lands in Q15 (b0·rpeaks at0x775A) whilefold(0x0400)is Q16.
⚠ Nothing in the ROM decides it. Store b0·r and expose both derived views;
do not commit a device to either.
Timing: the note lifecycle
64 channels
Not by analogy with the neighbour, and not from the 0x40 block stride, but from
the loops that drive this device: the power-on reset routine contains two loops
closed by cp HL,0x0040, each calling a 0x00104000 writer with the loop
counter as the channel argument. Five further runtime paths bound their channel
argument with cp H,0x40 and skip the device on NC, 5 of 5.
⚠ The null matters. On the companion device a channel argument of
0x40..0x7F is not a 65th channel — six of its routines branch on
cp HL,0x0040 and the high arm re-aims the same staging field at another slot.
This device has none of that: cp HL,0x0040 occurs 0 times inside the eight
0x00104000 routines, against 6 in the companion’s driver blocks. So 64 is a
count, not a re-encoding. PROVEN.
Power-on
The equivalent of a device reset is the second half of the shared reset routine,
called from the very first thing the main loop does. In order: register 0x0800
takes the literal 0x1100; a nineteen-word ROM image is copied into the staging
struct; all 64 channels take a full nineteen-register write with the channel
number placed in word 0’s bits 13..8; then all 64 channels take a block-0-only
write with bit 2 cleared.
Total power-on traffic: 1 + 64×19 + 64×1 = 1281 register writes = 2562 word writes to the port pair.
★ The reset image independently checks the register map. Its word 4 is
0x0100 — and register 0x0100’s table holds 0x0100 in all 251 entries; its
word 11 is 0xFF00 — and register 0x02C0 is the literal 0xFF00, the only
value any instruction ever puts in that word. An off-by-one in the
word↔register mapping would break both.
⚠ Bit 2 of block 0 is not a key gate, though it has exactly that shape: cleared once at the end of the power-on sweep, set at every note event. The routine that sets it has five call sites and all five are note handlers, including the note-OFF tail. Recorded so it is not re-derived as a gate.
Periodic versus one-shot
CPU 2’s timer 1 interrupts at 488.28 Hz (φT256, TREG1 = 28, fc = 28 MHz).
Its handler steps a six-phase counter; one phase’s flag reaches a dispatcher that
alternates between two paths on every call, and the odd path walks the
active-voice list. So
the periodic refresh runs at 488.28 / 6 / 2 = 40.69 Hz — a period of 24.6 ms.
⚠ That is an upper bound as well as a rate: the flag is a level, so a main-loop iteration longer than 12.3 ms coalesces two settings. It can be slower, never faster.
| register | class | rate / trigger |
|---|---|---|
0x00C0 |
periodic — model as a stream | 40.69 Hz per sounding voice, plus parameter edit, plus every full write |
0x0100 |
periodic, but a constant on this firmware | as above; an emulator that ignores the value must still expect the writes |
0x0240 |
periodic | 40.69 Hz per sounding voice on one arm, plus parameter edit, plus every full write |
0x0280 |
one-shot + parameter edit | full writes, plus a parameter-change arm |
0x0140, 0x0180 |
one-shot, with a conditional extra write at note time | full writes, plus a pair write at all five note sites when a predicate returns non-zero |
0x0000 |
one-shot, but written twice per note event and twice per channel at power-on | — |
| the other twelve | one-shot — voice parameters | only ever inside a full nineteen-register write |
★ Producer and shipper match, register for register. The sub-packer that
recomputes staging words {+0x06, +0x08, +0x12} has exactly the callers that
pairing predicts, and the accessor that ships them writes exactly 0x00C0,
0x0100, 0x0240. Three producers, three consumers, no leftovers. ★ And the two
periodic registers whose index carries the movement term are precisely 0x00C0
and 0x0240 — the P0SITI0N M0VEMENT modulation, and nothing else.
A parameter edit or MIDI controller reaches this device through exactly three of
the parameter dispatcher’s 49 arms, and they touch four registers — the same
three plus 0x0280. The other 46 arms never touch it.
Block 0 is not a commit strobe
Two independent reasons the latch reading fails:
- The four routines that never write block 0 are exactly the four with real
callers. Every parameter change that actually happens on this firmware — the
40.69 Hz refresh, every MIDI-CC edit, the note-time
0x0140/0x0180pair — lands on the device with no block-0 write after it. 4 of 4. - The order is not even consistent. Block 0 is written last in one routine, first in two others, alone in one, and absent from four. A commit strobe cannot be both first and last.
A write to any single register takes effect on its own. Nothing has to be buffered until a later write arrives — and the firmware’s own behaviour is the strongest evidence: the periodic refresh rewrites 3 registers of 19 and leaves the other 16 standing, forty times a second.
★ There is no key-off register
A note-off is the same full nineteen-register re-program with release values.
The note-off routine is byte-for-byte a different routine from the note-on one —
205 of 278 bytes differ — but its device traffic is the same shape in the same
order: block 0, then all nineteen, then all nineteen again, then the conditional
0x0140/0x0180 pair.
⚠ And then nothing. The periodic refresh only visits voices still on the active list, so once the voice is retired the device sees no further traffic at all for that channel. The last thing written is the note-off’s own nineteen words, and the entire decay to silence happens inside the chip, with no input.
That is the most load-bearing consequence of the write-only bus for an emulation. The release envelope is not driven from the CPU. A device that treats the registers as instantaneous parameters will cut every note off; the register values are the initial conditions and time constants of an internal process, and the model must sustain and decay a voice autonomously.
Worst case per note-on, on the busiest part mode: 1 + 19 + 2 + 1 + 19 + 19 + 2 = 63 register writes = 126 word writes. The two full nineteen-register bursts back to back are real — nothing between them recomputes the staging struct — so a model matching a bus trace must expect the burst twice.
⚠ RESONATOR TYPE is a UI preset selector the chip never sees
The tone editor’s MODELING pages offer a resonator family out of a 64-name list:
ORIGINAL STRING CYLINDER CONE FLARE PLATE L PLATE H MEMB L MEMB H THROUGH
MELLOW MUTE BRIGHT MOVE RANDOM OCTAVE HARMONIC METAL BOTTLE … SPECIAL1
SPECIAL2. That control is wave-select byte +0x0B, and it is the one parameter
closed end to end from the drawn caption through the CPU 1 sender to the CPU 2
receiver.
Writing it overwrites bytes 13..42 of the record — every coefficient in the
table above — from a preset. Over the whole factory set the byte reads
ORIGINAL in 455 of 459 records; four carry a real family (types 1, 14, 32
and 63). ORIGINAL means “no family — these are the record’s own coefficients”.
⚠ State the denominator with the rate. 459 is the loose framing of the tone database; a stricter one that self-checks against the element blocks yields 133 records, and every rate over that set excludes the tones which use the other field of this same byte. Nothing on this page rests on a count taken over the strict set alone.
The other two bits of the byte are a separate control. p11 bits 7:6 are
GROUP, the second column of the MODELING top page’s own legend ON/OFF GROUP
DRIVER RESONATOR INTERACTION. Its editor is a five-state control that ORs
0x40 into two layers, 0x80 into all four, and can never produce 0xC0 — and
the factory data carries exactly those three values and never 0xC0. GROUP is
what opens the 0x0300 gate, and eight factory tones use it: Fantasia,
Dream, Mist, Halo Pad, Voxmosphere, Dark Universe, Goblins and
Windy Sweep. Every one is a pad, which is what a control that makes layers
interact would be used for.
An emulation must implement the coefficients, not the families. There is no
if (type == CYLINDER)anywhere to write: the family list exists only to bulk load thirty bytes into a record, and the factory tones have all been edited past it. Nothing tells the chip which family it is.
The captions are drawn, not merely present: the WSA1R has no string table, so
every caption is a byte run inside a display-list record, and the interpreter
executes every record of the list it is handed. ⚠ The null says why that matters:
prom_b holds 2,325 runs of four or more bytes drawn only from [A-Z ], and
only 594 (25.5%) start inside a walked display-list text payload. Three
quarters of UI-label-shaped ASCII in this image is not a UI label. Membership
of a walked record is the discriminator, and strings is the wrong instrument.
The chain from caption to register is measured at every link: a tone-edit
parameter number is a record byte offset (the receiver computes
base + parameter with no indirection in between), and the control that grades
the method is one whose answer is already known — element bytes +0x02/+0x03
taken as a key into prom_d’s 307-entry wave catalogue hit 392 of 392, where
chance over the 65,536-pair key space would give 1.8, and the names round-trip
(E.Piano 1 → E.Piano 1, Marimba → Marimba).
What is not known
- The absolute scale of
P0SITI0N. Unit, direction and name are STRONG; the constant that turns register0x00C0into a time in samples is nowhere in the image. This is the single most valuable missing number, and a device should expose it as one named, adjustable parameter. - The internal signal path. Series or parallel; where the excitation
waveform enters; whether
MUTING’s two coefficients are two cascaded poles or one stage; how MAIN and SUB are coupled.INTERACTION GAINnames a register and a condition, but not a topology: what it is a gain on is not traced. - What
RESO SCALEdoes to the coefficients. The control and its editor are located —ToneEditField_A5_ResoScalesets bit 7 ofp22/p32(OFF/ON), the same parameter asMUTING, which the packer folds into the fieldR[+0x1A]— but what the silicon does with that field is not traced: a named, editable control with no traced effect on the coefficients. - Whether
0x01C0is a gain or a lower cutoff. The arithmetic is the same under both readings; the chip decides, and nothing in the ROM does. - Register
0x0000’s remaining bits. Bits 3, 1 and 0 are unaccounted for by any reading. The rest of the word is decoded: bits 15 and 14 areRESO MODE, bits 13..8 the channel on the power-on path, bit 7 the sign ofSUB GAIN(cleared whenever bits 6..4 are non-zero), bits 6..4 theGROUPenable that gates0x0300, and bit 2 is set by every note event including note-off. 0x02C0=0xFF00,0x0300’s mirrored byte, and the global0x0800=0x1100.- What the four private DRAM banks hold. IC3 has four private 16-bit DRAM
ports,
M1,M2,S1,S2— the “SOUND RAM” the firmware’s own bank-name strings refer to. The CPU never addresses that memory, and nothing in the ROMs can seed it. ★ TheM/Snaming lines up with MAIN and SUB, but that is a coincidence of initials between a schematic and a screen, and it is graded WEAK. 0x00E093, a per-element block the three gate-opening routines fill with both gains and both tuning words — the shape of a mix or coupling matrix over the four elements. It has eleven writers and no located reader.
The cheapest thing that would close most of this list is still not the
instrument. The controls are named; what is missing is what the silicon does
with three of them. The two nearest items are what the coefficients do with
R[+0x1A] — where RESO SCALE reaches them — and the reader of 0x00E093.
What a first implementation should do
- Build the register file and the plumbing before any DSP. 64 channels × 19
registers plus the single global
0x0800; an address latch at+0, data at+2; no read port; no write-recovery delay; no commit-on-block-0 model. - Come up in the power-on state —
0x0800 = 0x1100, all 64 channels loaded with the reset image, then block 0 rewritten with bit 2 cleared. - Decode the coefficients, because that part is understood — and label it “the coefficient decode”, not “the filter”. What is proven is that the numbers are bilinear one-pole coefficients; that the chip runs a one-pole with them is STRONG.
- Derive
0x0340/0x0380/0x03C0from their cutoff partners rather than treating them as a second degree of freedom. - Where you must fake, fake with the real mechanism: two resonators per
channel mixed by
SUB GAIN, each with its tuning, its damping one-pole and itsFITTINGpair, and one sharedP0SITI0Nas a delay-tap time proportional to2^(v/3072)with the unknown constant exposed as a named parameter. Route the third coefficient set in inert — decoded, exposed, multiplied by nothing. Store0x0000,0x02C0,0x0300and0x0800and model nothing from them — but decode them in the debugger, because two of the four are understood:0x0000’s bits 6..4 are a part-level enable taking only0x00,0x10or0x20, and0x0300isINTERACTION GAINwritten into both halves of the word, zeroed unless that enable is non-zero. Put every stand-in behind one switch so it is drop-in replaceable. - Do not call it a sound device. IC3’s output leaves on
RQWFIandDWFI0..DWFI12into IC4, the tone generator at0x0010C000: a 13-bit word on request, not audio. Until the tone generator is modelled and the six 16 Mbit wave mask ROMs are dumped, a device here emits into nothing. The right first device is a register file with a decoded, inspectable parameter view.
A high-level-emulation reference now runs the model (2026-09-10)
The MAME driver decodes this chip’s registers into engineering units and stops there
(acoustic_modeling.cpp / l7a1429_device, decoded_channel()) — correctly, because the
internal signal path and the excitation are unmeasured. That decode is now complete enough to
build the other half: a reference that runs the documented resonator model so the physics
on this page can be heard and shown implementation-complete for the settled parameters. It exists
and runs: wsa1/hle/ in the disassembly repo, built strictly from wsa1/notes/HLE-GUIDE-l7a1429.md.
It is HLE, not LLE — it realizes the block diagram the register vocabulary describes, not the
chip’s silicon — and it does not touch the register-decode device; it consumes the same decoded
per-channel parameters. Per channel it runs a pair of coupled digital-waveguide resonators (the
media the firmware names itself — STRING, CYLINDER, CONE, FLARE, PLATE, MEMB): the tuning sets
the delay length, MUTING is the loop-loss one-pole using its PROVEN bilinear cutoff decode
(index = MIDI note − 36), POSITION is a delay-tap along the medium, FITTING shapes the
excitation, and SUB GAIN mixes the coupled SUB resonator. It is self-validating: the voice rings
at the note’s fundamental (220 / 440 / 880 Hz), sustains past a second and decays gradually with
no input — the no-key-off requirement of §”There is no key-off register” — a brighter MUTING
yields more high-frequency energy, and the INTERACTION GAIN coupling widens the normal-mode split
(faithful to the sub_FC4269 solver model, not its exact fixed-point arithmetic).
Two inputs remain honest stand-ins, each behind a switch and drop-in replaceable: the DRIVER
excitation — a synthetic shaped burst standing for IC4’s undumped wave mask ROMs — and the
POSITION absolute scale, the single unknown constant of §”P0SITI0N is a log-domain PERIOD”
(it sets the pickup-comb timbre; the value needs a hardware trace). So this is a physically
plausible realization of the documented model, not a faithful reproduction of the chip’s
audio — that stays impossible without the wave ROMs and an internal-path trace, exactly as the
list above concludes. Run python3 wsa1/hle/test_l7a1429_hle.py (all checks pass) and
python3 wsa1/hle/render_l7a1429_demo.py; the effects DSP has a matching reference under
dsp/hle/ (see the Effects-DSP page §9).
The resonator coupling
The two resonators interact, and not through a register — there is none. Three routines fill a 68-byte argument block whenever layers are grouped, and a solver evaluates the loop characteristic function of the grouped resonators, converting the phase error into a pitch offset:
D = -3072 * log2(1 - dphi / 2pi) in 1/256 semitone
which is added to the MAIN and SUB tuning words before they reach chan+0x0040 and chan+0x0080.
Its constants are Q11 renderings of named quantities — 0x555 = 2/3, 0x1922 = π, 0x3244 = 2π,
0x146 = 1/2π — and INTERACTION GAIN sets the solver’s pole radius through the same curve that
feeds register 0x0300.
★ So an emulation faithful to chan+0x0040 and chan+0x0080 already models the interaction.
The offset is quantised to 13.28 cents per index step and bounded to +1146 / −675 cents; it is
exactly zero when INTERACTION GAIN is zero, and when every grouped resonator shares one tuning
word. Derivation and controls: wsa1/notes/FINDINGS-l7a1429-e093-block.md, probe
wsa1/notes/w24_e093_coupling_solver.py.
Where this lives in the disassembly
The register blocks are .equ symbols in the driver’s own header, so the source
reads in the same vocabulary as this page rather than in bare numbers:
| symbol | block | |
|---|---|---|
DEV104_BASE |
0x00104000 |
+0x00 select, +0x02 data |
DEV104_MAIN_TUNE / DEV104_SUB_TUNE |
0x0040 / 0x0080 |
KEY SHIFT + TUNE |
DEV104_POSITION |
0x00C0 |
the log-domain period |
DEV104_MAIN_FITTING_DECAY / _SUB_ |
0x0140 / 0x0180 |
|
DEV104_MAIN_FITTING_RISE / _SUB_ |
0x01C0 / 0x0200 |
|
DEV104_SUB_GAIN |
0x0280 |
|
DEV104_MAIN_MUTING_Q13 / _SUB_ |
0x0340 / 0x0380 |
|
DEV104_MAIN_MUTING_Q16 / _SUB_ |
0x0400 / 0x0440 |
the bilinear cutoff |
DEV104_BLK_0000, _0100, _0240, _02C0, _0300, _03C0, _0480, _0800 |
named by block number on purpose — a symbol that guessed would be believed |
Two of those block-number symbols now have an earned name, and the MAME driver
prints it in a bus trace: 0x0240 is the register DEPTH scales and 0x0300 is
INTERACTION GAIN. The other six stay numbers: 0x0000, 0x0100, 0x02C0 and
0x0800 are unidentified, and 0x03C0 and 0x0480 have no name to give.
The curve tables carry the same vocabulary — Curve_Muting_Cutoff_Q16_128,
Curve_Muting_Cutoff_Q13_128, Curve_Position_Log2Period_251,
Curve_Fitting_Exp2Decay_256, Curve_Fitting_Exp2Rise_128,
Table_Muting_CutoffFloor_ByKeyZone_256, and the four LinCoef_*_TouchRamp_Q5_128
touch ramps — indexed by VELOCITY, not by key: their index is voice[+0x0C] & 0x7F,
and the voice record holds the note separately at +0x05 as note|0x80 — each documented above its definition with its fit, endpoints, unit
and grade.
| file | what is in it |
|---|---|
wsa1/prom_c/devices/dev10c_dev104_drivers.s |
the driver, the symbol definitions, and the full register map |
wsa1/prom_c/data_tables/tail_data_zone.s |
the curve tables and the touch ramps |
wsa1/prom_c/field_accessors.s |
the packers that compute each register’s value |
⚠ Note that dev10c_dev104_drivers.s holds the drivers for two devices. The
four routines in it that are still sub_XXXXXX drive 0x0010C000, the tone
generator’s register file, not this chip — their block numbers merely overlap.
Reproducing every number on this page
Each script reads the ROM images and no .s file, and each carries a
--selftest that must reject a wrong value. All paths are relative to the
disassembly tree.
| script | the question it answers |
|---|---|
wsa1/notes/dev104_topology_probe.py |
what kind of engine the nineteen registers add up to — the section census, the code-pairing nulls, the factory-data twinning, the shift adjudication |
wsa1/notes/lsi_curve_tables.py |
what quantity each ROM curve produces — every fit, with its residual and its null (--fit, --summary, --dump NAME) |
wsa1/notes/wsa1_l7a1429_write_timing_probe.py |
when each register is written and how often — the channel count, the bus surface, the refresh rate, the note lifecycle |
wsa1/notes/wsa1_toneedit_vocabulary.py |
what the machine calls each register — the walked display lists of the SOUND EDIT screens, with the strings null |
wsa1/notes/wsa1_toneedit_field_editors.py |
each field’s limits and step — the 24 DATA-dial editors, their edit descriptors, and the 33-binding cross-check against the read-back map |
wsa1/notes/wsa1_tone_record_probe.py |
the 43 wave-select columns, the factory-data signatures, and the wave-catalogue control |
wsa1/notes/prom_c_dev104_regmap_checks.py |
what the firmware writes into each register, decoded from the instructions that compute it |
wsa1/notes/l7a1429_crosscheck.py |
do those documents agree — every register block named in both the sequencing and the curve account; the name of every block, compared across four artefacts at once: this page’s register table, the disassembly’s editor-pages note, its HLE guide and the MAME driver’s block_name(); the sample rate re-derived from the crystal; and the semitone claim re-derived from equal temperament |
The register map’s own prose lives in the disassembly beside the code it
describes, in wsa1/prom_c/devices/dev10c_dev104_drivers.s.
Related
| Page | Description |
|---|---|
| SX-WSA1 / SX-WSA1R Overview | The machine, the hardware, the memory maps, the model strap |
| Emulation Status | The MAME driver, what is modelled and what is not |
| Disassembly | The byte-exact reassembly the register decode comes out of |
| Control Panel & Switch Matrix | The panel the tone editor is driven from |
| KN5000 Tone Generator | The PCM sibling: the KN5000 commands no such per-channel modelling device |