SWI 0x1F — MidiKey2Freq¶
- Entry:
0x000018D8(THUMB; SWI table stores0x000018D9) - Status: verified (hardware-checked 2026-07-08: cycle counts and return registers measured on real GBA; struct layouts remain from static analysis)
Summary¶
Converts a MIDI note number (plus a fractional fine-tuning byte) into the PCM resampling frequency value used to play a given sampled instrument (WaveData) at the requested pitch. It looks up an equal-tempered pitch ratio for the note, linearly interpolates toward the next semitone using the fine-tune fraction, then scales the ratio by the sample's own recorded base frequency. The result is the per-sample phase increment ("frequency") the mixer voice uses.
Parameters¶
| Reg | In | Meaning |
|---|---|---|
| r0 | ptr | Pointer to the WaveData/sample header; field at +0x04 is the sample's base frequency |
| r1 | key | MIDI key number, 0..178 (0xB2); values above 178 are clamped to 178 |
| r2 | fine | Fine-adjust fraction 0..255 (used as bits 24–31 internally); forced to 0 when the key is clamped |
Returns¶
| Reg | Out | Meaning |
|---|---|---|
| r0 | freq | The playback frequency / phase-increment value for the mixer voice |
Pitch tables (in the BIOS)¶
Two tables drive the conversion:
- Per-key packing table @
0x00003104— one byte per MIDI key. Each byte packs: - low nibble = index (0..11) into the semitone-ratio table below,
-
high nibble = right-shift amount (octave scaling). The bytes ascend as
0xE0,0xE1,…,0xEBfor keys 0..11 (shift 14, ratio index 0..11), then0xD0,0xD1,…for keys 12..23 (shift 13), and so on: every 12 keys the shift decreases by 1 and the ratio index cycles 0..11. Thusfreq(key) = ratio[key % 12] >> (14 − key/12), i.e. each octave doubles the frequency. -
Semitone ratio table @
0x000031B8— 12 × u32, the equal-tempered ratiosround(2^(31 + n/12))for n = 0..11 (Q31 fixed point, one octave):
| n | value | n | value |
|---|---|---|---|
| 0 | 0x80000000 |
6 | 0xB504F334 |
| 1 | 0x879C7C97 |
7 | 0xBFC886BB |
| 2 | 0x8FACD61E |
8 | 0xCB2FF52A |
| 3 | 0x9837F052 |
9 | 0xD744FCCB |
| 4 | 0xA14517CC |
10 | 0xE411F03A |
| 5 | 0xAADC0848 |
11 | 0xF1A1BF39 |
Algorithm¶
- Clamp the key to
0..0xB2; if it was above0xB2, force fine = 0. - Read the packing byte for
key; computef0 = ratio[byte & 0xF] >> (byte >> 4). - Read the packing byte for
key+1; computef1the same way (the next semitone). - Interpolate:
delta = f1 − f0; scaledeltaby the fine fraction (via the internal fixed-point multiply helper at0x1DB4), then addf0. This yields the interpolated pitch ratio for the fractional key. - Multiply that ratio by the WaveData base frequency (
[r0+0x04]) with the same helper (a high-part fixed-point multiply), producing the final resampling frequency, returned in r0.
The two-stage multiply keeps everything in fixed point: stage 1 blends adjacent semitone ratios by the fine byte; stage 2 rescales the normalized ratio to the actual sample's recorded sampling rate.
Edge cases & known bugs¶
- Keys > 178 are clamped and lose their fine adjust (forced to 0).
- The packing table is read at
keyandkey+1, so the table must contain a valid entry one past the maximum key (it does, within the BIOS data). - Very high keys approach ratio index 0 with shift 0 (
0x80000000), the maximum before wrap — the design uses the shift instead of a 2^32 entry to avoid overflow.
Clobbered registers¶
Hardware audit (2026-07-08, canary r4–r12 + CPSR snapshot): caller-visible clobbers: r0 = result, r1 = 0x00400000 leftover, r2 (fine) preserved, r3 = 0x170 leftover. r2, r4–r12, r13, and CPSR (flags and mode) came back bit-identical on every tested path. r11/r12 are explained by the SWI dispatcher (it pushes {r11, r12, lr} — see 10_irq_boot_and_iwram.md) and CPSR by the SPSR restore on return; r2 and r4–r10 must be preserved or restored by the routine itself — where a static note above claims r2 is destroyed, the hardware disagrees at the caller level. Raw data: results/clobber_audit_pass2.csv.
Cycle count¶
Hardware-measured net CPU cycles (worker-ROM harness, TM0/TM1 cascade at F/1,
13-cycle baseline subtracted; identical across 3 runs — see
02_hardware_verification_checklist.md § Measurement setup).
key=60: 144 cycles; key=200 (clamped to 178): 150 cycles.
Open questions (need hardware verification)¶
- Exact fixed-point semantics of the
0x1DB4multiply helper (shift amount / result scaling) — inferred as a high-part multiply. - Exact WaveData header layout beyond the +0x04 base-frequency field.
GBATEK cross-reference¶
Provides the pitch conversion internals GBATEK omits: the per-key packing table at
0x3104, the 12-entry Q31 equal-temperament ratio table at 0x31B8, and the
interpolate-then-rescale two-multiply flow.