01 // Quick jam hear notes in 60 seconds
Stuck on "engine loading library…"? Run
python -m pip install -r jam\requirements.txt.
Missing numpy = no engine. As of 0.9.43 it says so instead of
waiting forever.
VST / Standalone
Live jam client. Models, blend, FM emulate or serial, Studio panel.
Studio chip-DAW
Sections, clips, Journey/Dice, export XRNS / MIDI / A2M / stems.
FM31 silicon
Same register stream to DOSBox, Win3.1, Win98, or real ESS/OPL card.
02 // Watch serial demo + live surfaces
388h — proof the path is real before you wire up silicon.
Full routing in SERIAL_OPL3.md.
03 // Serial FM SB16 lineage · 38400 8N1 · port 388h
Frame:
AA bank reg valHealth:
F0 -> F1Pack:
AdLibitum-FM31-Hardware-*.zipFull kit:
dos_testing\ARMCARE
04 // How we make music six modes · one brain · no random notes
Retrieve LIB
A mined phrase, verbatim. The real thing, played by the dead.
Blend BLND
Two phrases crossfaded. Drift controls the mix.
Latent LTNT
A VAE walk off-manifold, when nothing in the library will do.
Generate GEN
Motif development from the grammar. Not a quote — a composition.
Journey 🚂
"Start ambient, build to breakcore, end on a hymn." Compiled to form.
Dice 🎲
Constrained chaos. For when you are stuck and want a shove.
04a // Why it sounds coherent four rules, in order
04b // Known vs guessed we do not dress a guess as a fact
04c // Anatomy of a song metal_s99 · F minor · 158 BPM · 76 bars
04d // Train your own brain feed it your library
The VST does it too "Train model…" button, or drag a folder onto the plugin.
Your ratings are in the loop Learn/Forget weights retrieval, and banned phrases never even enter a model.
04e // Recall how a phrase gets chosen
Seeded = reproducible. Same seed, same phrase. That is what makes HOLD hold, a reroll change one thing, and a track replayable years later.
One source per request, not per bar. Pick ONE phrase, then develop it every bar. Per-bar picking is song-hopping — a real cause of the old randomness.
Retrieval is one of six modes. Blend/latent/generate leave the library behind by degrees; journey and dice drive the rest.
04f // Move the brain GitHub ships code, not your library
04g // Measured a number without a baseline is decoration
| Claim | Measured | Baseline |
|---|---|---|
| Chip key detection, in-scale fit | 92% median | 58% random |
| Chip songs with >75% fit | 84% | — |
| Lane voices per song | 6/6, zero defaults | was 0 |
| LSDJ model, real Game Boy material | 93% | was 3.4% |
| Chip phrases given a key, no re-harvest | 767,185 | was 0 |
| sid / vgm median key confidence | 0.58 / 0.46 | was 0.00 |
| Patch family kNN guess | 35% | 10% random |
04h // DAW Horsemen AI rides REAPER / Bitwig / Renoise beside the chip band
04i // Click to Chip Explorer is a player now — right-click does the rest
.a2m double-click
Opens in native AT2 (SDL) with the song loaded and playing. Right-click: foobar2000 (AdPlug), real DOS AT2 in DOSBox (auto-loads via keystroke injection), or the NetPlay desk.
A2M NetPlay
a2m → at2net → real OPL3 over TCP. Transport, 18-channel mute/solo before the wire, per-instrument mute, 2s chip AUDITION, TAG/BAN instruments to a cleanup list, and a true tracker view parsed from the module — rests, effects, playhead.
.lsdsng double-click
LSDSNG NetPlay streams LSDJ songs to mGB (real Game Boy or RetroPlug hosting mGB.gb) as MIDI: baked instrument tables, slides/retrigs/chords, kits voiced on NOI, PU1/PU2/WAV/NOI mute/solo + phrase lookahead. Right-click: real LSDJ in RetroPlug playing the actual cart.
04j // Hands off the desk REAPER drives the X-Touch — the plug-in stopped fighting it
The FM Driver used to open the X-Touch itself. Windows grants a MIDI port
to one process, so with REAPER's own Mackie surface enabled the two
owners took turns: SELECT worked on some tracks and not others, and scrubbing
died whenever focus moved. A plug-in is the wrong place for a surface anyway —
one instance per lane means ten would-be owners in a ten-lane import. So the
surface layer came out (parked and documented in jam\fmdriver\attic\)
and the desk is REAPER's.
Reach the instrument
SELECT the lane → PLUG-IN → V-Pots 1-8. The 27 patch parameters moved to the front of the list so bank 1 is the modulator envelope, and every name is unique in its first 7 characters — one Mackie scribble cell. FLIP puts the page on the motor faders.
Write and read automation
Toolbar AUTOMATION WRITE / READ: arms LATCH plus the chosen patch envelopes, then flips back to READ with everything disarmed so the motors follow the envelope. It asks which group, because an FX envelope holds its parameter and arming all 27 would freeze an imported lane on one timbre.
MIDI learn, and unlearn
Right-click any knob and you get REAPER's parameter menu — Learn MIDI, Remove learn, envelope, parameter modulation. The host owns the binding, so it survives, shows in the FX window, and can be undone from either side.
FM MONITOR, docked
A ReaScript panel inside REAPER: all 18 OPL3 channels lit from the real register stream, live writes/s, frames, chip channels held, and an ICMP ping to the Armada with its latency. The plug-in publishes one snapshot at 4 Hz from a single hook that sees the whole chip — no individual instance can.
Names that fit the desk
Tracks are named content-first — kick 01 [FM v4096] — because the old FM 01 kick put "FM 01 " in all seven cells and every strip read the same. Lanes that still collide get the number moved to the front, which is unique by construction.
FM MINI on the Armada
The full-screen Win98 desk colour-cycles its title, so every frame was dirty and a Pentium II's fans never came down. FM MINI is 640×336, fixed, repaints only when something changes, and shows song / composer / order / line / tempo plus the instrument name sounding on each channel. Desktop shortcuts carry real 8-bpp icons — 32-bit alpha icons are an XP feature and Win98 draws them as noise.
05 // What ships 0.9.152 HANDS OFF THE DESK
| Piece | Notes |
|---|---|
| One launcher | AdLibitum.bat → the hub: engine control + Studio + generate + a2m import + FM send + harvest + hardware, one window, tooltips, system tray |
| Compose → DAW | Studio song → REAPER .rpp / Renoise .xrns + .opl.json sidecar; a ReaScript loads the AdLibitum VST per lane at the right FM patch |
| Renoise / REAPER tools | One-click .xrnx Renoise tool + REAPER ReaScripts, auto-copied into your DAWs by Setup (optional task) or python -m jam.install_daw_tools; a live Launch Driver GUI — routing fan to Nuked-OPL emu and/or the Armada box (toggle either live), per-channel mute/solo, instrument names, live notes + VU. Import any scope (lanes × patterns) and Preview it on the chip before committing; capture auto-tempo + one-pass (no loop) |
| Setup.exe | AdLibitum-0.9.152-Setup.exe — VST3 + Standalone + jam engine + starter + FM31 tools. Shows version in wizard and Add/Remove; re-run replaces prior install. Versioned Start Menu / Desktop icons (e.g. AdLibitum (Standalone) 0.9.152). Packages Retroplug Extreme when the local binary is present. |
| Retroplug Extreme | Shortcut AdLibitum (Retroplug Extreme) 0.9.152 → packaged jam\retroplug\retroplugxtreme.exe (or local gameboy\RetroplugDFP\build\bin\). Refresh: python -m jam.make_shortcuts |
| FM Driver VST | Second plugin "AdLibitum FM Driver": plain OPL3 note player for DAW tracks — MIDI in → harvested oplbank voice → Nuked-OPL3 monitor + FM31NET wire to the Armada. Stable enough to sit on 8 tracks; X-Touch pages; voice fav/flag/ban marks keyed to register bytes |
| Studio Bridge VST | Third plugin "AdLibitum Studio Bridge": the Studio as a DAW sidecar — COMPOSE in the plugin, DRAG any lane (or whole song / .rpp / .xrns / SysEx register-verbatim / mGB CC set) straight onto a DAW track. LSDJ LIBRARY tab pulls one channel of a real .lsdsng as mGB MIDI |
| Explorer players | .a2m and .lsdsng are double-clickable (section 04i): native AT2, foobar2000, DOSBox AT2, A2M NetPlay chip mix desk, LSDSNG NetPlay mGB streamer, RetroPlug LSDJ — per-user installers, no admin |
| Ban the brain | Instruments TAG/BAN'd in NetPlay feed train --skip-banned: banned lanes leave the corpus before mining |
| LSDJ carts | Studio LSDJ panel: rich kit browser, synth editor, arrangement/chain grid, phrase tracker view; export .lsdsng straight to Retroplug |
| Hardware ZIP | DOS / Win31 Safe·Stage·Full / Win98 receivers + C source + build scripts |
| Brain | starter.json ships; FULL brain on machines with genre models |
| SF2 live | Studio live button (FluidSynth) + VST SoundFont backend (TinySoundFont) — real-time SF2, not offline render only |
| FM Deck | Studio Deck panel: drag A2M tracks/patterns onto lanes (WebView2-safe drag) |
| Workbench | Raised phrase/table caps (30k/8k) + Generator panel Close (no stuck Text title) |
| Fresh Boot fix | No more forever-hang probing E:\ before install path |
| Voices | 12,615 OPL patches tagged by sound — every lane gets a real FM voice, not a default |
| Chip keys | SID/NES/SNES/YM/LSDJ finally in-key — 767,185 phrases keyed, no re-harvest |
| Needs | Python 3.9+ · optional WebView2 for in-plugin Studio |
07 // Architecture how the pieces fit — one diagram per subsystem
Each box maps to a Claude skill in .claude/skills/ — ask Claude to load one (e.g. "use the adlib-studio skill") to work on that part naturally.
musicbrain — the composition brain
musicbrain is AdLibitum's music-theory + generation core: it mines a module archive into per-genre models, then turns a seed into a coherent SongPlan (key, cadenced progression, developed motif, form) that every chip/DAW backend renders.
LIBRARY (.xrns .xm .mod .it .s3m)
|
v
parsers/ --> ParsedModule (TrackLane: row,midi,vol)
|
v
mine.analyze --> SongAnalysis (Krumhansl key, per-bar romans,
| interval/onset/drum histograms)
v
mine.build_model --> models/style_<genre>.json (+ style_all)
| ^ |
| blend.py (hybrid: anthem) | export
| v
| musicbrain.ts <-- data_musicbrain.js
v (LSDJ workbench, browser)
=================== build_song_plan(seed, style, model) ==============
structural RNG(seed): key -> form -> progression (unconditional draws)
theory.generate_progression form.get_form/parse_form_spec
melody.render_phrase (motif dev, AABA, chord-tones-on-strong-beats)
rhythm (grooves/fills; density thins EVENTS not velocity)
per-lane RNG(seed,lane,var): each lane re-rollable in isolation
|
v
SongPlan = [ PNote(bar, step, midi, vel, dur, lane) ] + critique()
|
+----+---------------+-----------------+----------------+-----------+
v v v v v
generate_xrns export_stems reaper_write at2kit lsdj-
(.xrns) (.mid/.wav) (.rpp) (.a2m) toolkit
AdLibitum Studio — the web chip-DAW
Studio is a single-page browser chip-DAW that turns a GENERATE press into a full arranged, harmonically-coherent song (sections x lanes) saved as an .adlib.json project you can preview in WebAudio and export or stream to a real OPL3 chip.
BROWSER jam/studio.html SPA server.py (HTTP :8770, ThreadingHTTP)
+--------------------------+ POST +-------------------------------------+
| toolbar: preset chip key | JSON | /songplan /journey /dice /genlane |
| scale bpm seed GENERATE +-------->| /clip /section /newsong /export |
| Dice Journey Fill +Track | | /fmstream /fanout /launch /health |
+------------+-------------+ +------------------+------------------+
| | ENGINE = JamEngine
| v .jam() retrieve/
| studio_song.build_song() blend/latent +
| +-----------------------+ _smart_progression
| | musicbrain.form -> |
| | sections x LANES |
| | section_archetypes |
| | .resolve (energy, |
| | roles, tag hints) |
| | CHIP_PROFILES -> |
| | bank/target/voice |
| | lane_patch/voice_for |
| | -> OPL 13-byte reg |
| | _drum_floor _humanize |
| +-----------+-----------+
| v
| *.adlib.json PROJECT
| render() generated/projects | generated/studio/<t>/
v <------------------------+-----------------+
+--------------------------+ | |
| PREVIEW (WebAudio) | studio_export.py fm_stream.py (FM >)
| synth(): per-lane TARGET | xrns mid rpp at2net.exe -> TCP
| oplRawVoice(13 regs) | xm a2m stems :3819 -> real OPL3
| laneInstr>laneAutoVoice | or Nuked-OPL3 emu
+--------------------------+
FM drivers — MIDI/notes to OPL registers
It translates musical intent — MIDI notes+CC, verbatim register SysEx, or a compiled .a2m module — into exact OPL3 [bank,reg,val] writes and streams them over the FM31NET TCP protocol to a real, emulated, or fanned-out FM chip.
Studio / DAW / MIDI keys verbatim regs .a2m module
note + CC (rtmidi) F0 7D..F7 SysEx file
| | |
v v v
+--------------------------------------+ at2net.exe
| OplDriver (pure) | (fm_stream.py mgr;
| 18-voice alloc, LRU steal, patch | mix desk :3820)
| cache; note -> A0/B0/40/C0/60/80/E0 | |
| sysex() -> verbatim register writes | emits [0xAA b r v]
+------------------+-------------------+ |
emit(bank, reg, val) |
| |
+----------+-----------+ |
v v v
+--------------+ +--------------------------------+
| Fm31NetSink | | MultiSink / Fanout proxy |
| F0->F1 probe | | frame_split, F0->F1 ACK, |
| TCP_NODELAY | | mirror to N live targets |
+------+-------+ +----------------+---------------+
| [0xAA bank reg val] over TCP :3819 |
v v v v
fm31emu.exe (Nuked Armada FM31NET backup box test_sink.py
OPL3->waveOut,:3819) real OPL3 chip (redundant) (verify, no HW)
at2net — AT2's replay engine over TCP
at2net reuses AdLib Tracker II's own DOS replay engine (compiled for Win32) but redirects every OPL register write out over TCP as [0xAA bank reg val] frames, so any .a2m version plays on a real networked OPL3 with authentic tracker effects.
.a2m / .a2t file
| a2m_file_loader (v1-14; aPLib depack via patched a2depack.pas)
v
+----------------------------+ AT2 replay engine (engine/*.pas)
| songdata + pattdata (RAM) | patched by patch_engine.py:
+----------------------------+ ~10 asm helpers -> pure Pascal
| parserio.pas CRC override (-Fu.)
| timer_poll_proc <-- driven at effective_hz by the main loop
v (QueryPerformanceCounter, HIGH prio, 1ms)
opl3out / opl2out / opl3exp (engine procedure vars, REDIRECTED)
| DisableTimerIRQ/EnableTimerIRQ -> no_irq (DOS PIC bypass)
v
tcp_out(reg,data): reg_cache dedup + sender mute/solo filter
v
txbuf --tx_flush (1 send/tick, TCP_NODELAY)--> [0xAA bank reg val]
| handshake 0xF0 -> 0xF1 0xF2 [0xB0/B2/B4 metadata]
v
TCP :3819 =========== network ===========> FM31NET (Win98 box)
|
control :3820 <--line cmds-- Studio / MCP v
mute solo jump loop pause status quit real OPL3 chip
test_sink.py = local :3819 stand-in (counts frames, no hardware)
fm_stream.py = spawn at2net.exe + drive :3820 (Studio FM>, MCP)
FM31 Receivers — DOS / Win3.x / Win98 + FM31NET
A family of tiny native receivers that parse AdLibitum's [0xAA bank reg val] FM31 frames off a serial cable or TCP :3819 and write them straight to a real OPL3/ESS-ESFM chip's I/O ports on retro DOS/Windows hardware (or a Nuked-OPL3 emulator).
MODERN BOX (sender) RETRO / EMU BOX (receiver)
+--------------------------+ +----------------------------+
| Studio / VST / at2net | | FM31 4-state frame parser |
| builds the wire stream | | 0 idle -> 1 bank -> 2 reg |
+------------+-------------+ | -> 3 val -> apply |
| +-------------+--------------+
| [0xAA][bank][reg][val] = OPL register write |
| [0xF0] --------------> reply [0xF1] (+[0xF2] v2) |
| [0xB0/B2/B4 ..] = v2 metadata (title / pos / ins) |
v |
+-----------------------------+ v
| TRANSPORT | +----------------------+
| serial 38400 8N1 | | direct OPL port I/O |
| (null-modem, NO flow) | | idx = base+(bank?2:0)|
| or TCP :3819 (NODELAY) | | dat = idx+1 |
+-----------------------------+ | settle 6 / 35 reads |
| base 388h / bank1 38Ah|
SERIAL rx: oplserial, fm31_dos (DOS) +----------+-----------+
fm31 / safe / stage (Win16) |
fm31w98 (Win9x) v
TCP rx: fm31net (9x) fm31w16 (3.x) YMF262 OPL3 / ESS ESFM
fmstage / fm31gui (Win32) NT-class: gNoOpl -> LINK TEST
fm31emu (Nuked-OPL3 -> waveOut) (fm31emu -> waveOut speakers)
AdLibitum VST — the JUCE plugin
A JUCE 8 VST3/Standalone synth that asks the local Python jam engine for MIDI each bar, then turns those notes into OPL3 register writes rendered as FM audio (built-in or Nuked-OPL3) or streamed as serial frames to real AdLib hardware.
DAW host / Standalone
(playhead ppq, transport, host MIDI)
|
v
+=============== AUDIO THREAD (processBlock) ==================+
| params->atomics (fmMode) | pick motif block from ppq |
| emit scheduled MIDI | drive OplVoice/SfPlayer | render out |
+===+================+==========================+=============+
| requestBars | noteOn/Off + fmPatch | render()
v v v
+------------+ +-------------------+ audio out L/R
|EngineClient| | OplVoice |<-- oplbank.bnk (4096 patches)
|HTTP THREAD | | note -> OPL3 regs |
|POST /jam | | onWrite(bank,reg,val) fans to:
|build body | +--------+----------------+
|<-- events | | queueReg | oplEmu.writeReg
|ingestResult| v v
| scheduled[]| +----------------+ +------------------+
+-----+------+ | SerialWriter | | OplEmu (Nuked) |
| | SERIAL THREAD | | or compact FM |
v | [AA bk rg val] | +--------+---------+
jam/server.py | TCP :8890 /COM | | audio out L/R
:8770 +-------+--------+ v
retrieve/blend/ |
gen, harmony, v
provenance FM31 monitor / at2net -> real OPL3 chip
^
| launchEngine (python -m jam.server)
+-----+---------- MESSAGE THREAD (Editor) ------------------+
| NoteHeatmap 30fps <- monQueue/playStep | LatchKeyboard |
| polls /health /sources /models /log /patches | FM31 panel |
| WebView2 Studio | APVTS params | MIDI-CC learn |
+----------------------------------------------------------+
a2m -> DAW importers & drivers
Captures an AdLib module's exact OPL register stream as at2net plays it, then rebuilds it two ways — a bit-true SysEx timeline for REAPER and an editable note/voice song for Renoise — and drives the result back onto the real OPL3 chip over MIDI. Import the whole song or a scope (any lanes × any patterns), Preview that scope on the chip before committing, and steer playback from the Launch Driver GUI: a routing fan to the Nuked-OPL emulator and/or the Armada box (toggle either live), with per-channel mute/solo and live notes + VU.
.a2m / .a2t (any version)
|
+--------------- CAPTURE (real-time) ----------------+
| at2net.exe --[0xAA bank reg val]--> capture_sink |
| (plays it) TCP :3996 (JSONL sink) |
| "tempo N speed M" -> BPM auto ; one-pass loop cut |
+---------------------------+------------------------+
v capture.jsonl {t,b,r,v}
SCOPE (a2m_scope): lanes × patterns, or whole song + Preview♪
+----------------+-----------------+
v v
VERBATIM layer EDITABLE layer
a2m_verbatim.py a2m_to_renoise.py
reg_lane -> 18 ch + GLOBALS decode_notes: fnum->pitch,
19-trk SMF, each write = carrierTL->vel, on/off->dur
SysEx F0 7D..nibbles..F7 18-trk .xrns + .opl.json
verify_smf (bit-true) (oplbank voice per track)
| |
v v
<name>_verbatim.mid <name>_renoise.xrns
| |
REAPER ReaScript Renoise tool main.lua
InsertMedia = 19 trk load_song + route_to_opl
+----------------+-----------------+
v MIDI out -> LoopBe1
daw_fm_driver / fm_driver_gui (Launch Driver GUI)
notes/CC/prog -> OPL regs (+ mute/solo/VU/live notes)
sysex() = verbatim passthrough
|
v fm_route RoutingSink (proxy fan)
+-------------+--------------+
v v
Nuked-OPL emu Armada / Win98 box
127.0.0.1:3819 192.168.1.60:3819
(toggle either live; a dead box never blocks, rejoins) = real OPL3
(a2m_to_daw.py: capture-free v8 parse -> .rpp + note MIDI, same driver)
LSDJ & Game Boy (mGB / Retroplug)
The Game Boy arm: it mines your LSDJ library, generates Game-Boy songs from the jam brain, edits them in-browser, and writes real .lsdsng/.sav carts that route to the Retroplug emulator VST, out as MIDI to mGB hardware, or through PyBoy to a WAV.
YOUR LSDJ LIBRARY (.sav carts / .lsdsng single songs)
| lsdjtk.index + lsdjtk.harvest (tag + dedup + provenance)
v
banks/phrases.json, instruments.json (~25k phrases / 5.8k inst)
| jam/lsdj_bridge.py (GB channel -> role; NB bars=1, key=0)
v
jam/banks/lsdj.json --build_models--> jam/models/lsdj.json
| |
| Studio: chip="lsdj" | jam engine
| CHIP_PROFILES v (retrieve/blend)
+------------------> studio_song.build_song
| bank=lsdj target=retroplug voice=gb
v
project.adlib.json (lead>PU1 arp>PU2 bass/pad>WAV drums>NOI)
| lsdj_store.generate_from_project
v (dedup bars -> shared 255-phrase pool)
/lsdj/* editor routes SongBuilder
(lsdj_store + Studio |
LSDJ panel) <---------> |
v
===== 0x8000 SONG MEMORY (song.py view) =========================
arrangement(256x4 chains) -> chains(16 phrases) -> phrases(16 steps)
+ instruments(64) + synths(16) + waves(256) + kits(ROM) + tables
==================================================================
| native.py (liblsdj.dll compress)
v
.lsdsng / .sav
| | |
v v v
Retroplug lsdjtk.midi lsdjtk.render
(GB VST: -> mGB SMF (PyBoy + real
ROM+.sav) CH1-4=PU1/PU2/ LSDJ ROM -> WAV)
WAV/NOI, CC map