Alesis MMT-8 Firmware Reverse Engineering
Disassembly and decompilation of the Alesis MMT-8 MIDI sequencer firmware (v1.11) for the purpose of understanding the implementation and porting to another platform.
Hardware
- CPU: 80C31 (8051 family, no internal ROM) @ 12 MHz crystal
- EPROM: U12 — 27C256 (32KB,
0x0000–0x7FFF) - RAM: U10, U9 — two 61256 (32KB each, 64KB total external data RAM)
- Address Latch: U11 — HC573 (demuxes Port 0 address/data bus)
- Address Decoder: U5 — HC138 (3-to-8 chip selects)
- I/O Latches: U8, U6, U7 — HC574 (keyboard columns, LEDs, LCD)
- LCD: HD44780-compatible, 2-line, 8-bit mode
- MIDI: UART at 31.25 kbaud via Timer 1 auto-reload
Memory Map
| Space | Address Range | Device | Description |
|---|---|---|---|
| CODE | 0x0000–0x7FFF |
U12 27C256 EPROM | Program memory (PSEN-qualified) |
| XDATA | 0x0000–0x7FFF |
U10 61256 SRAM | External data RAM bank 0 |
| XDATA | 0x8000–0xFFFF |
U9 61256 SRAM | External data RAM bank 1 |
| IRAM | 0x00–0x7F |
Internal | Direct-addressable internal RAM |
| IRAM | 0x80–0xFF |
Internal | Indirect-only internal RAM |
| SFR | 0x80–0xFF |
Internal | Special Function Registers |
XDATA Page Mapping (via P2 register)
The firmware uses P2 as a page selector for 256-byte pages in XDATA, accessed via MOVX @R0 / MOVX @R1:
| P2 Value | Usage |
|---|---|
| 0 | MIDI TX buffer |
| 1 | MIDI RX buffer |
| 2 | Track event buffers (note on/off/CC queues) |
| 3 | Active note tracking |
| 4 | Sequence parameters and configuration |
| 5 | Song mode data and extended parameters |
Memory-Mapped I/O Ports (XDATA via HC138 decode)
| Address | Label | Description |
|---|---|---|
0xFF00 |
IO_LED_CONTROL |
LED control output latch (HC574) |
0xFF02 |
IO_LED_DATA |
LED data output |
0xFF04 |
IO_STATUS_LATCH |
Status output latch (bit fields) |
0xFF06 |
IO_KEY_COLUMN_SEL |
Keyboard column select (matrix scan) |
0xFF08 |
LCD_CMD_DATA |
HD44780 LCD command/data register |
0xFF0E |
IO_TRANSPORT_STATE |
Transport state (0=stopped, 1=playing, 2=recording) |
0xFF0F |
IO_BEAT_DIVIDER |
Beat divider setting |
0xFF1A |
IO_CLICK_ENABLE |
Metronome click enable |
Interrupt Vectors
| Vector | Address | Handler | Description |
|---|---|---|---|
| Reset | 0x0000 |
LJMP main_init_and_loop |
Jumps to 0x00FB |
| EXT INT0 | 0x0003 |
EXT_INT0_isr |
Increments tick counter (IRAM 0x7D) |
| Timer 0 | 0x000B |
TIMER0_isr |
Reloads TH0/TL0 from IRAM 0x7E/0x7F, increments tick counter |
| EXT INT1 | 0x0013 |
EXT_INT1_isr |
Unused — shares RETI with Timer 0 handler |
| Timer 1 | 0x001B |
LJMP TIMER1_isr |
Jumps to 0x76B2 |
| Serial | 0x0023 |
SERIAL_isr |
MIDI RX/TX using register banks 2 (TX) and 3 (RX) |
Note: Bytes between interrupt vectors contain ASCII padding strings ("12345", "1234567") — likely a version signature.
SFR Configuration at Init
| Register | Value | Meaning |
|---|---|---|
SP |
0x2F |
Stack pointer (above register banks + bit-addressable area) |
TMOD |
0x21 |
Timer 0: mode 1 (16-bit), Timer 1: mode 2 (8-bit auto-reload) |
TCON |
0x51 |
Timer 0 and Timer 1 running, EXT INT0 edge-triggered |
SCON |
0x70 |
UART mode 1, receiver enabled |
IP |
0x01 |
EXT INT0 is high priority |
TH1/TL1 |
0xFF |
31.25 kbaud MIDI baud rate (12 MHz / 12 / 32 / 1) |
IE |
0x92 |
Enables: EA, Timer 0, Serial |
Firmware Architecture
The firmware is structured as a single main init + event loop in main_init_and_loop (0x00FB, ~1000 bytes). After hardware init and LCD setup, it enters a while(1) loop:
scan_keyboard()
process_midi_input() // with register bank 2/3 context
process_midi_realtime_msgs() // external sync (clock/start/stop)
if playing:
sequence_playback_engine()
process_recording_realtime() or process_recording_step()
handle button events (play, stop, record, copy, delete, etc.)
update_display()
Key Subsystems
-
Timing Engine — Timer 0 ISR + EXT INT0 provide a 96 PPQN (pulses per quarter note) sequencer clock. Timer reload values in IRAM
0x7E/0x7Fare computed from tempo BPM byconfigure_tempo_timer/compute_tempo_reload. -
MIDI I/O — The Serial ISR uses 8051 register bank switching for zero-overhead context saves. Bank 2 manages the TX ring buffer, bank 3 manages RX. The P2 register (XDATA page select) is saved/restored via IRAM
0x5C. -
Keyboard Scanner —
scan_keyboarddrives a 6-column matrix viaIO_KEY_COLUMN_SEL(0xFF06), reads rows from P1. Debounces by rejecting multiple simultaneous keys. Edge-detection via XOR with previous state. Auto-repeat after 0x80 ticks, then every 0x14 ticks. -
Sequencer Playback Engine —
sequence_playback_engine(0x0AF5, 1513 bytes) is the largest and most critical function. It iterates through all 8 tracks, reads timestamped MIDI events from XDATA, and outputs events whose timestamps match the current position. MIDI event dispatch by status byte type:< 0x7A: Note On/Off (0x90/0xB0| channel)= 0x7A: Program Change (0xC0)= 0x7B: Channel Pressure (0xD0)= 0x7C: Pitch Bend (0xE0)= 0x7D: System Exclusive (0xF0)
-
Recording — Realtime (
process_recording_realtime) and step (process_recording_step) modes. Incoming MIDI is timestamped with the clock counter and appended to sequence data in XDATA. -
Song Mode — Chains parts into songs.
get_song_step_datareads the song step table,handle_song_part_changemanages transitions between parts. -
LCD Display — HD44780 driver with separate screens for track mode (
display_track_mode_screen) and song mode (display_song_mode_screen). Init sequence:0x38(8-bit/2-line),0x06(entry mode),0x0E(display on),0x01(clear). -
SysEx Dump —
sysex_dump_engine(598 bytes) handles bulk MIDI SysEx data transfer for backup/restore of sequence data. -
Self-Test — Boot diagnostic mode activated by holding specific buttons at power-on. Tests RAM read/write (
selftest_ram_rw), MIDI loopback, and external interrupt lines. Halts with error message on failure.
Key IRAM Variables
| Address | Name | Description |
|---|---|---|
0x20–0x25 |
key_edge_col0–5 |
Keyboard new-keypress edge detect (6 columns) |
0x26–0x2B |
key_scan_col0–5 |
Current keyboard scan state |
0x48 |
last_button_state |
Previous button state for edge detection |
0x4B |
key_repeat_timer |
Auto-repeat countdown timer |
0x4D/0x4E |
display_ptr |
Pointer to current display string (hi/lo) |
0x50 |
tick_subdivider |
Tick subdivision counter (reloads from 0x60 = 96 PPQN) |
0x51/0x52 |
measure_bcd |
Current measure counter (BCD, hi/lo) |
0x53/0x54 |
total_measures |
Total measures in sequence (BCD, hi/lo) |
0x5C |
saved_P2_page |
P2 backup for ISR context preservation |
0x6D |
current_track_idx |
Track being processed (0–7) |
0x6E |
active_track_mask |
Bitmask of tracks with data |
0x6F |
track_bit_rotate |
Rotating bit for current track (1, 2, 4, ..., 128) |
0x70 |
track_mute_mask |
Bitmask of muted tracks |
0x71/0x72 |
seq_start_ptr |
Sequence data start pointer (lo/hi) |
0x73/0x74 |
playback_ptr |
Current playback position pointer (lo/hi) |
0x75/0x76 |
record_ptr |
Current recording position pointer (hi/lo) |
0x77/0x78 |
midi_clock |
MIDI clock tick counter (hi/lo) |
0x7D |
tick_counter |
Sequencer tick counter (incremented by ISRs) |
0x7E/0x7F |
timer0_reload |
Timer 0 reload values for tempo (hi/lo) |
Porting Considerations
Key 8051-specific idioms that will need adaptation:
-
P2 page register — The
MOVX @R0/MOVX @R1instructions use P2 as the upper address byte to access 256-byte pages in XDATA. Replace with flat memory addressing (e.g.,xdata[page * 256 + offset]). -
Register bank switching — ISRs use
RS0/RS1bits in PSW to switch between register banks 0–3 for zero-overhead context saves. Replace with normal interrupt save/restore or dedicated ISR variables. -
Bit-addressable variables — Flags like
_c_0through_f_7are individual bits in the 8051's bit-addressable IRAM area (0x20–0x2F). Map these toboolvariables or bitfield structs. -
Timing recalibration — Timer reload values are computed for a 12 MHz / 12 = 1 MHz timer clock. The
compute_tempo_reloadfunction will need recalculation for a different clock source. -
MIDI baud rate — 31.25 kbaud is generated by Timer 1 auto-reload with
TH1=0xFFat 12 MHz. Use your platform's UART peripheral configured for 31250 baud. -
undefined1/undefined2— These are Ghidra placeholder types for unresolved data types. Treatundefined1asuint8_tandundefined2asuint16_t(usually a CODE or XDATA address).
Generated Files
| File | Description |
|---|---|
alesis_mmt8_v111.bin |
Original firmware binary (32KB, 27C256 EPROM dump) |
alesis_mmt8_v111.hex |
Intel HEX format conversion of the binary |
alesis_mmt8_v111.asm |
dis51 assembly listing (29,208 lines) |
mmt8_decompiled.c |
Annotated Ghidra decompiled C pseudocode (7,924 lines, 108 functions) |
mmt8_functions.txt |
Function list with addresses, sizes, and callers |
mmt8_callgraph.txt |
Call graph showing function relationships |
ghidra_setup.py |
Ghidra Jython script: disassembly and function creation from entry points |
ghidra_annotate.py |
Ghidra Jython script: function renames, IRAM labels, I/O labels, comments |
ghidra_export.py |
Ghidra Jython script: exports decompiled C, function list, call graph |
ghidra_project/ |
Ghidra project directory (open in Ghidra GUI for interactive analysis) |
Reference Documents
| File | Description |
|---|---|
Alesis MMT-8 Schematic.pdf |
Circuit schematic (MMTPCB, 1987) |
Alesis MMT-8 Service Manual.pdf |
Service manual |
Alesis MMT-8 Instruction Manual.pdf |
User manual |
Alesis MMT-8 DC battery power mod.pdf |
Battery power modification |
Tools Used
- dis51 — 8051 hex file disassembler (initial disassembly with code flow analysis)
- Ghidra 12.0 — Headless analysis, decompilation, and annotation via Jython scripts
- objcopy — Binary to Intel HEX format conversion