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
05 // What ships 0.9.104 Panel Close
| 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 | VST3 + Standalone + jam engine + starter model + FM31 tools. One click. |
| 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