OCTAMAX
Educational reverse engineering of the Elektron Octatrack MKII firmware (OS 1.40C, ColdFire + DSP56xxx) — tooling, notes and behavior patches
About
Highlights
- 256 STATIC sample slots
- Slice playhead view
- Lazy Part transitions
- More MIDI arpeggiator scales and PERSONALIZE toggles that survive a restart
What you need
An Octatrack MKII (the patch refuses MKI files), your own official OS 1.40C and Python 3.8+. The setup script builds the firmware tool locally and applies the published patch to your file.
Good to know
Version 2.0 is a beta and the README reports testing on one MKII. A project that uses the extra STATIC slots opens on stock OS with those slots empty. Read FLASHING.md for flashing and recovery first.
README
██████╗ ██████╗████████╗ █████╗ ███╗ ███╗ █████╗ ██╗ ██╗
██╔═══██╗██╔════╝╚══██╔══╝██╔══██╗████╗ ████║██╔══██╗╚██╗██╔╝
██║ ██║██║ ██║ ███████║██╔████╔██║███████║ ╚███╔╝
██║ ██║██║ ██║ ██╔══██║██║╚██╔╝██║██╔══██║ ██╔██╗
╚██████╔╝╚██████╗ ██║ ██║ ██║██║ ╚═╝ ██║██║ ██║██╔╝ ██╗
╚═════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝
▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄
▐░░░░░░░░░░░░ E L E K T R O N O C T A T R A C K ░░░░▌
▐░░ a firmware study toolkit · for educational use ░░▌
▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
READY.
LOAD "OCTAMAX",8,1
█
OCTAMAX
An educational toolkit for understanding how the firmware of the Elektron Octatrack works.
OCTAMAX is a reverse-engineering workspace built to study the Octatrack MKII operating system: how the update files are packed, how the code is laid out in memory, how the microkernel schedules tasks, how the sequencer drives the audio DSP — and, as a hands-on way of proving that understanding, how a few small, optional, entirely reversible behavior changes can be added to the OS image.
Everything here is for educational purposes only. No Elektron binary is redistributed. You bring your own copy of the official OS; the tools analyze it and, if you ask them to, produce a modified image byte-for-byte reproducibly from that copy.
Version 2.0 BETA
BETA — expect bugs. This build is new and has been tested on one MKII unit. Keep your official
.syxat hand:[FUNC]+ power on →[TRIG 3]recovers the unit even if the OS is corrupt, because the bootloader is never touched. Your CF card, projects and samples are not affected by flashing.
What's new
SLICE PLAYHEAD — a readable SLICES view. A new PERSONALIZE toggle replaces the
4×16 slice grid of the FUNC+[down] SLICES view with the number of the slice that is
actually playing (drawn large, in a trig-key style square frame), a live progress bar
fed by the audio engine's real playback position, and a marker at the slice's loop
point. The trig button of the playing slice lights amber and pulses in lockstep
with the tempo LED. Off by default; with the toggle off the view is stock, byte for
byte. Works on high (129+) slots too. Design and addresses in
DESIGN_SLICEVIEW.md.
PERSONALIZE toggles now persist. The custom switches used to reset on every power
cycle (and OS upgrade): the firmware re-images their RAM window at boot and restores
only the stock-sized settings block from battery SRAM. The restore length now covers
the custom words and the setters write the battery shadow, so all toggles survive
power cycles and OS upgrades, exactly like the stock PERSONALIZE settings. No project
file format is touched. (Root cause and fix in NOTES.md, "PERSONALIZE persistence
root-caused".)
STATIC sample slots go from 128 to 256. Slots 129–256 behave like the stock ones:
they load samples, keep their slice grids (including .ot sidecar files), are assignable
to tracks, accept parameter locks, survive a project save/reload and a power cycle,
and play on the first trig. FLEX slots and the recorder buffers are untouched.
Persistence uses the native project.work (which gains SLOT=129..256 records) plus a
project.256 sidecar file next to your project. A project saved with high slots still
opens on stock firmware — those slots simply come back empty.
Known limitations, and the reverse-engineering story behind each fix, are in
DUAL256.md. The short version: the LOCK TRIG popup still stops at 128,
so to author locks on a high slot, select it on the track first and then place trigs.
Carried over from version 1.x
Everything from the previous release is still here, still off by default:
- Lazy transitions — on a pattern change to a different Part, sounding tracks keep the previous Part's sound instead of jumping volume; the track LED dims until a trig commits the track to the destination Part, and the A/B scene slots stay put across the change.
- No BANK/PTN countdown — the SELECT BANK / SELECT PATTERN windows stop expiring after four seconds; press the key again (or pick a trig) to close them.
- Arp key scales — the MIDI arpeggiator's key-scale (ARP SETUP, F knob) gains 10
qualities beyond the stock major/minor: the five Greek modes, blues,
phrygian-dominant, melodic-minor, octatonic and hirajoshi — 12 qualities × 12 roots.
OFF/maj/minstay byte-identical to stock, so the extra scales only appear if you scroll past them.
The behaviour switches live in the PERSONALIZE menu, unchecked by default. The unit
reports OCTAMAX_2c in the boot splash and under SYSTEM STATUS → OS VERSION.
How to build your flashable file →
Motivation
Hi, I'm Maxolydian, an electronic artist based in Palermo, Italy.
For many years Elektron's instruments have been a cornerstone of my live performances. A big part of my artistic search is bringing my compositions to the stage while always leaving the door open to improvisation. The central challenge in that search has always been striking the right balance between automation and hands-on control. Too much automation makes things rigid and takes away the freedom to step off the script. At the same time, getting consistent results — in performance and in sound — demands a setup that is reliable and predictable, something a purely hardware rig makes genuinely difficult.
On that front Elektron hardware offers exceptional reliability and sound quality — qualities recognized the world over, and the reason each of their machines has been so successful. And yet the Octatrack is now more than 16 years old, and while its firmware has been updated several times, the way it works for live performance hasn't evolved substantially. Elektron's developers must surely feel swamped by all the feature requests from their users, and it must be hard to decide where to invest the team's precious time to deliver the most value.
That's why I started this project: to open up the possibility of experimenting with changes and small modifications that make my artistic search easier — and at the same time to feed my passion for the hardware and my hunger to learn from the best. In other words, for strictly educational purposes. I hope it proves useful to other artists on a similar search.
⚠️ Warning — read before doing anything
This project is strictly for personal use, and I honestly do not recommend updating the firmware of any Elektron unit with anything other than official firmware. It is a risky operation: it puts the product warranty in question and it can leave the unit unusable.
Nothing in this repository is endorsed by, supported by, or affiliated with Elektron. If you flash a modified OS you do so entirely at your own risk. The study of the firmware (static analysis) is harmless; writing a non-official OS to real hardware is not. If in doubt, don't flash — just read, disassemble, and learn.
What has been investigated
Everything below was verified against the official OS 1.40C for Octatrack
MKII — either from the firmware's own checksums, byte-exact decompilation, or
direct disassembly. Full write-ups live in ARCHITECTURE.md
(consolidated architecture) and NOTES.md (chronological log).
Hardware
- CPU: Freescale/NXP ColdFire (likely MCF5445x, 32-bit, big-endian,
~266 MHz) — a 68000-family core, not ARM. The firmware corroborates it: it
drives the on-chip ATA controller in the MBAR region (
0xFC04_51xx) that characterizes the MCF5445x. - Audio DSP: Freescale DSP56xxx, confirmed by the 24-bit word size used when the boot loader uploads the DSP program 3 bytes at a time.
- Storage: CompactFlash (FAT16/32) over the ColdFire's on-chip ATA controller, reached through the FlexBus.
Memory map
Two RAM chips, recovered from a static scan of every real address-operand reference in the
MAIN OS: a 128 MB main DDR at 0x40000000 and a separate ~1 MB metadata SRAM at
0x10000000 (a different chip-select). Full derivation in NOTES.md.
| Segment | Range | Size | Use |
|---|---|---|---|
| Metadata SRAM | 0x10000000–~0x10100000 |
~1 MB | Sample settings tables (0x448 B/slot: flex 0x100b14f0, static 0x100d5b30) + project globals. Separate small chip — reads past its end bus-fault; full, cannot grow in place. |
| DDR — code + BSS | 0x40000000–~0x40200000 |
~2 MB | OS image (@0x40000400) + BSS + the free code cave (0x400d64da–0x400d7c3b) the patches live in |
| DDR — bank buffers | 0x400e21e0–0x40a955e0 |
~10 MB | 16 resident banks (stride 0x9b340) |
| DDR — flex pool | 0x40a955e0–~0x46000000 |
~85 MB | Flex sample RAM + recorder buffers (the shared 85.5 MB budget) |
| DDR — app structs | 0x46000000–~0x46ceb400 |
~13 MB | Recorder metadata, sample state tables (0x2c B/slot), streaming tables |
| DDR — reserved | 0x40a955e0–0x40af55e0 |
384 KB | Reclaimed by moving the flex pool +64 pages; canary-confirmed untouched — a fixed home for relocated tables |
| DSP shared RAM | 0x80000000–~0x80010000 |
~64 KB | Voice state (0x80004dc8, stride 0xA8), double-buffered DSP frames, mailboxes |
| DSP coprocessor | 0x20000000 |
— | Command / status / frame-index MMIO |
| Peripherals (MBAR) | 0xFC000000 |
— | ColdFire on-chip: DDR controller 0xFC0B8000, ATA host 0xFC0451xx, IRQ ctrl 0xFC04C010 |
Firmware format and update chain
Elektron ships a ZIP with two transports of the same OS — a .bin and a
.syx — both wrapping the same compressed container:
.bin = [ELUP hdr][seed] + XOR-feedback( [len] + ELEK( aPLib( MAIN OS ) ) ) + checksum
.syx = SysEx 7-bit( ELEK( aPLib( MAIN OS ) ) )
- ELUP layer (
.binonly): XOR obfuscation with feedback plus an additive checksum. Reimplemented intools/make_bin.py/tools/bin_decode.pyand validated by regenerating Elektron's own official.binbyte-for-byte. - ELEK layer: a proprietary container whose payload is compressed with
aPLib; it decompresses to the MAIN OS (1,112,560 bytes, loaded at base
0x40000400). - No cryptographic signature on any layer — the OS is analyzable and, with recalculated checksums, rebuildable. That's why the format can be repacked at all; it is not a security bypass.
- The updater validates the OS (
FUN_4007f748) with explicit error codes:-2not a valid OS ·-3length ·-4checksum ·-5MK1 not allowed ·-6no downgrade.
Operating system
- A proprietary preemptive microkernel (not MQX/ThreadX/VxWorks — banner
ElektronOctatrack DPS-1). Task Control Blocks, per-priority ready queues, context switch viaTRAP #0, blocking message queues, and a time slice driven by the ColdFire PIT timer (0xFC08_0000). - The same message-queue pattern unifies the whole firmware: the ATA "async queues" and the audio "voice mailboxes" are kernel message queues.
Audio engine and sequencer
- 8 track voices in the
0x80000000shared-RAM window (base0x800049d8, stride0xA8). - Control path: a sequencer trig writes a voice mailbox → a control-rate frame
builder assembles a parameter frame into a double buffer → handshake to the
DSP56xxx over MMIO at
0x20000000, which does the real-time synthesis. - Work split: ColdFire = control (RTOS, sequencer, parameter assembly); DSP = signal (playback, time-stretch, filters, FX).
Practical outcome — the optional patches
As a demonstration of the above, OCTAMAX can build an image with a few behavior changes, all OFF by default and toggled from the PERSONALIZE menu, so a freshly flashed unit is indistinguishable from stock until you opt in:
| feature | effect |
|---|---|
| Lazy transitions | On a pattern change to a different Part, sounding tracks keep the previous Part's sound (no volume jump). The track LED dims while the track hasn't been re-trigged since the change; a trig commits it to the destination Part. Also keeps the A/B scene pointers on the same slots across the change. |
| No BANK/PTN countdown | The SELECT BANK / SELECT PATTERN windows stop expiring after four seconds. |
| Arp key scales | The MIDI arpeggiator's key-scale (ARP SETUP, F knob) gains 10 extra qualities beyond the stock major/minor: the five Greek modes (Dorian, Phrygian, Lydian, Mixolydian, Locrian) plus blues, phrygian-dominant, melodic-minor, octatonic and hirajoshi — 12 qualities × 12 roots. OFF/maj/min stay byte-identical to stock, so the extra scales only appear if you scroll the F knob past them. |
| Slice playhead | (new in 2.0) The SRC>SLICES view shows the playing slice number in a trig-key frame, a live progress bar and the slice's loop point; the playing slice's trig button lights amber and pulses with the tempo LED. |
| PERSONALIZE options | The three behavior switches (lazy transitions, no countdown, slice playhead), added to the PERSONALIZE menu, unchecked by default and persistent across power cycles and OS upgrades. |
| 256 STATIC slots | (new in 2.0) Sample slots 129–256, with slices, track assignment, parameter locks and persistence across a power cycle. Always on — it extends capacity rather than changing behaviour. Written up in DUAL256.md. |
| Boot branding | Boot splash and SYSTEM STATUS show OCTAMAX_2c instead of 1.40C. |
The behaviour switches are the ones that stay off until you opt in; the extra arp scales only
appear if you scroll past OFF/maj/min, and the extra slots simply exist.
The code changes live in a free code cave and are reached by 6-byte jump detours. All three
feature sets were developed against that same cave, so tools/build_all.py
relocates their stubs around each other and then reads every block back out of the finished image
to prove nothing was overwritten.
The arp-scales work is written up in NOTES.md (search "ARP key-scale");
the behavior patches have a per-hunk table in sysex/README.md.
Repository layout
ARCHITECTURE.md consolidated architecture (hardware, OS, memory map)
NOTES.md chronological reverse-engineering log
FLASHING.md safe-flashing guide + recovery net (read before flashing)
sysex/ the patch (source + JSON hunks) and the reproducible patcher
tools/ analysis + build scripts (Ghidra headless, emulators, packers)
fetch-os.sh download + extract the official OS
setup.sh clone/build elektron-firmware-tool into vendor/
analyze.sh entropy + binwalk + strings + container unpack -> out/
Downloaded Elektron binaries (downloads/, out/, vendor/*.bin, *.syx,
*.bin, *.pdf) are git-ignored on purpose — none of them are redistributed.
Building a .syx or .bin from scratch
You need your own copy of the official OS. The build is fully reproducible: given
the same stock file it emits a .syx byte-identical to the reference build.
0. Prerequisites
- Python 3.8+
- A cross-assembler for the ColdFire (only needed to rebuild the stubs from
source):
m68k-elf-as,m68k-elf-ld,m68k-elf-objcopy(targeting-mcpu=5407). elektron-firmware-tool— cloned and built bysetup.shintovendor/. It's patched locally (seetools/elektron-firmware-tool.patch) so it can write the full 10-character version field and emit the rebuilt container.
./fetch-os.sh # downloads the official OS 1.40C into downloads/ and extracts it ./setup.sh # clones + patches + builds elektron-firmware-tool into vendor/
1. The fast path — apply the pre-built patch
No firmware is distributed here, so there is nothing to download and flash directly.
What the repository ships is the patch: the ColdFire code authored in this project,
captured hunk by hunk in sysex/patches/, where each hunk carries its load address, the
original bytes it expects and the replacement bytes. You apply it to your own copy of
the official OS, and the result is byte-identical to the reference build.
Three commands, from a clean clone:
./fetch-os.sh # downloads the official OS 1.40C from elektron.se into downloads/ ./setup.sh # builds elektron-firmware-tool into vendor/ python3 sysex/apply_patch.py \ -i downloads/extracted/OCTATRACK_OS1.40C.syx \ -p sysex/patches/octamax-2.0-beta.json \ -o OCTAMAX_2c.syx --bin OCTAMAX_2c.bin
patch : octamax-2.0-beta 2.0-BETA
target : Elektron Octatrack MKII OS 1.40C
[1/5] stock .syx checksum ok
[2/5] extracted section_3_MAIN_OS.bin (1,112,560 bytes)
[3/5] applied 223 hunks (4587 bytes)
[4/5] repacked -> OCTAMAX_2c.syx
CF image -> OCTAMAX_2c.bin
[5/5] output checksum ok — byte-identical to the reference build
That gives you both flashable files:
| file | how you flash it |
|---|---|
OCTAMAX_2.bin |
copy to the root of the CF card, then PROJECT → OS UPGRADE → [YES]. Fast, and what most people want. |
OCTAMAX_2.syx |
send over MIDI DIN (not USB). Takes several minutes. |
--bin is optional; without it only the .syx is written. To build the older
behaviour-only 1.x patch instead, drop -p (it defaults to
sysex/patches/maxolydian-r10.json).
The script aborts before writing anything if the stock checksum is wrong, if the original bytes under any hunk don't match (wrong firmware, or already patched), or if the patched image's checksum is off. The final line confirms your output matches the reference build bit for bit — if it does, you built exactly what was tested.
2. The full path — rebuild the stubs from source
If you want to change the behavior (or just verify the JSON), rebuild the patched
MAIN OS from the assembly sources. tools/build.py assembles every stub
(tools/patch*.s), places them in the free code cave, and derives every
detour target from the linker's symbol table (never hardcoded — a stale detour
once froze the unit on the logo screen), verifying the original bytes at each
site first:
python3 tools/build.py # assembles the stubs -> out/mainos.binThen wrap out/mainos.bin back into a transport with the patched
elektron-firmware-tool. Section 3 is the MAIN OS; -V MAXOLYDIAN sets the
10-character version field:
# -> .syx (for MIDI upgrade, or the reference artifact)
vendor/elektron-firmware-tool/elektron-firmware-tool \
-i downloads/extracted/OCTATRACK_OS1.40C.syx \
-c 3 out/mainos.bin -V MAXOLYDIAN \
-o OCTATRACK_MAXOLYDIAN.syx3. Making a .bin for the CF-card OS UPGRADE
The .bin transport (flashed from the CF card via PROJECT → OS UPGRADE) is
much faster than trickling the .syx over MIDI at 31250 baud. tools/make_bin.py
wraps the ELEK container into an ELUP .bin. Its correctness is not assumed — it
regenerates Elektron's own official .bin byte-for-byte before writing yours:
# dump the rebuilt ELEK container, then wrap it as an ELUP .bin
EFT_EMIT_CONTAINER=elek.bin vendor/elektron-firmware-tool/elektron-firmware-tool \
-i downloads/extracted/OCTATRACK_OS1.40C.syx \
-c 3 out/mainos.bin -V MAXOLYDIAN -o OCTATRACK_MAXOLYDIAN.syx
python3 tools/make_bin.py elek.bin -o OCTATRACK_MAXOLYDIAN.bin4. Flashing
Read FLASHING.md first — it covers the recovery net in
detail. In short:
- The upgrade goes over MIDI DIN, not USB (the
.syxpath), or from the CF card (the.binpath, faster). - Keep the official
.syxat hand.[FUNC]+ power on →[TRIG 3](MIDI UPGRADE) recovers the unit even if the OS is corrupt — the bootloader is never touched by an OS update, which is why a brick here is soft and recoverable. - Never cut power during
UPDATING FLASH. - Your CF card, projects and samples are not affected by an OS update.
- The PERSONALIZE settings persist across OS upgrades and power cycles (they live in the same battery-backed block as the stock settings). A Startup-Menu EMPTY RESET still clears them.
Legality (not legal advice)
- Static analysis of the publicly distributed OS carries zero risk to the hardware and is the whole point of this project.
- EU: Directive 2009/24/EC Art. 5 (observe/study/test a program you lawfully use) and Art. 6 (decompilation for interoperability). Elektron's EULA may contain anti-RE clauses — a contractual matter separate from copyright.
- Private and educational use is low-risk. Redistributing modified binaries is a different question; this repo deliberately redistributes no Elektron binary.
OCTAMAX is an independent, unofficial, educational project. "Elektron" and "Octatrack" are trademarks of Elektron Music Machines MAV AB, used here only to identify the hardware under study. Not affiliated with or endorsed by Elektron.
Pictures in the README load from GitHub.
- Language
- Python
- Stars
- 65
- Forks
- 6
- Created
- 07-28-2026
- Last commit
- 1 week ago






