Bugs, quirks & confidence
A faithful decompilation reproduces the original's mistakes. These are the notable ones found in this Manatee build (v1.1 build 0x3F), each verified independently against the disassembly. Other builds shipped with other games may have fixed some or introduced others.
How sure is all this?
Every routine of the driver was rewritten as C, then a second, independent reviewer re-derived each one path by path from the ARM disassembly (conditional execution, flags, byte vs word accesses, fallthroughs) and fixed discrepancies. "High" means every path was checked. The one medium-rated function is an unreferenced alternate entry point into the controller-reset code. The C compiles for ARMv4 and links as a whole.
Hardware behaviour beyond what the code shows (for example why the interrupt-clear register is written several times, or how the MIDI-in interrupt is released without an explicit acknowledge) is general AICA knowledge or inference. Who writes the driver's "external FX request" block is unknown (nothing in the game's sound library does). How Soul Calibur uses the driver (which songs, which reverb when) is SH-4 game code and was not traced.
Bugs that can misbehave badly
| Where | What happens | When |
|---|---|---|
| MIDI voice allocation | If every voice has a higher priority than the new note, the allocator reads two of its own instruction words as the "chosen" voice and reports success; note-on then writes AICA settings to garbage addresses. The intended "no voice" result can never happen. | all 48 voices busy with higher-priority notes |
| PCM slot stealing | When a one-shot or stream steals a slot found by the priority scan, the loop counter (-1) is stored as the slot number. Later lookups index 255 entries past the slot table and the monitor ends up watching a MIDI voice's channel. | 16 PCM slots busy, new sound outranks one |
| Sequencer overload | If both the pending note-off queue and the overflow queue are full, the steal path leaves a value on the stack and "returns" to a low address: the reset vector (ports 0-3) or the undefined-instruction vector (ports 4-7). | extreme note density |
| Sequencer stall | If the event ring fills while a port is processing a zero-delay event, the port's delay counter goes negative and is compared unsigned: that port stops for about 17 minutes. | event ring full |
| Drum-mode command | If the drum-kit bank is missing, the fallback installs the built-in kit but computes the program and velocity-curve pointers from a stale register. | drum mode without the bank downloaded |
Wrong results and reporting
- FX program with a too-small ring buffer reports the ring-size code times four (0, 4, 8 or 12) as the error instead of "not downloaded": the error is lost or looks like a different one.
- Current FX program readback: the driver stores the current program and output set at one
address, while the version of the SH-4 library in the game reads another, so
sdSndGetFxPrgdoes not see what the driver loaded. - Command log: empty slots also write a result, so the last command of each batch has its logged result overwritten (nothing reads the log).
- Stream position for 8-bit streams is reported as "start + 1" instead of the real position.
- One-shot numbers: the entry count check is off by one, so number = count is accepted and reads a tag as an offset.
MIDI engine quirks
- RPN numbers are never compared. Any RPN data entry sets the pitch-bend range.
- Data increment (CC 96) on an RPN depends on a CPU flag left over from reading the event ring: normally it sets the bend range straight to the maximum.
- Data decrement (CC 97) on an RPN decrements the NRPN value instead.
- Reverb send 0 (CC 91 = 0) mutes live voices' sends rather than restoring the tone's default, and drops a CC 76 input-bus override.
- Sustain pedal key-offs also clear the sample-source bit of the channel; reset-all uses a different mask that keeps it.
- Envelope offsets: the second decay rate is offset by the first-decay controller; one stored controller (CC 90) is never used.
- A layer with zero splits would make note-on scan 232 splits.
- Program change ignores drum mode and can replace a drum-kit selection.
Fades and players
- The port pan fade raises the "volume changed" flag instead of "pan changed".
- Downward port pitch and PCM speed fades jump straight to the target (an inverted condition); PCM volume and pan fades can wrap below zero for a tick.
- The PCM speed/pitch fade-step calculation uses an inverted loop test.
- Stereo streams: mono play on a port whose right channel still plays leaves its "playing" flag set; a missing right-channel ring leaves the left channel armed but not started, so the next key-on of any channel starts it.
- Parameter resets zero the effect-send level (a value is shifted twice); the one-shot "reset all" also resets the stream players.
Smaller oddities
- Undefined-instruction and SWI exceptions return to the same instruction, looping forever; data aborts skip the faulting instruction.
- The MIDI input parser treats the byte after an MTC quarter-frame (F1) as running-status data.
- The sequencer "purge" loop in the tick routine is unreachable; the external MIDI path and the main loop append to the event ring without locking.
- A few words of dead code, unreferenced literals and NOP padding remain in the image.
Where to read more
Every item above is commented at the place it occurs in src/arm/*.c and in the Ghidra project;
docs/HOW_IT_WORKS.md and docs/notes/*.md have the full formats.