- TypeScript 71.6%
- Vue 28.2%
|
All checks were successful
Build & Push / build-push (push) Successful in 38s
Single web service running the registry image behind the shared reverse proxy network (duat), no published ports; the proxy must serve HTTPS since Web MIDI requires a secure context outside localhost. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0152ghYh2zQQxBQHLcPynAqe |
||
|---|---|---|
| .forgejo/workflows | ||
| app | ||
| public/icons | ||
| .dockerignore | ||
| .editorconfig | ||
| .gitignore | ||
| compose.prod.yml | ||
| compose.yaml | ||
| Dockerfile | ||
| eslint.config.mjs | ||
| nuxt.config.ts | ||
| package.json | ||
| pnpm-workspace.yaml | ||
| README.md | ||
| renovate.json | ||
| tsconfig.json | ||
| yarn.lock | ||
Juno Di Editor
A self-hosted, browser-based editor for the Roland JUNO-Di synthesizer. It talks to the hardware over Web MIDI + Roland SysEx (DT1/RQ1) using the addresses from the official MIDI Implementation, and works offline as a PWA.
Stack: Nuxt 4 · Nuxt UI v4 (Pug templates) · Pinia · @vite-pwa/nuxt.
Features
- Patch editor — Common, Tone 1–4 (OSC / TVF / TVA / LFO1 / LFO2 with 5-point envelopes), MFX (all 79 effect types with per-type parameter names), Chorus / Reverb (per-type parameters), Matrix Control, TMT ranges and every "advanced" parameter of the engine. All 850 PCM waveforms are selectable by name or number.
- Rhythm Set editor — the 88 keys (A0–C8) of a kit, each with its name, assign type, mute group, level / pan / tuning, sends, receive switches, the four WMT waves (wave pair, gain, tuning, pan, velocity window, FXM) and the pitch / TVF / TVA envelopes; plus the kit's level, MFX and chorus / reverb. Click a key to hear it on the Juno, copy / paste keys, browse the 20 preset kits and 9 GM2 kits, and edit the rhythm set of any performance part.
- Performance editor — 16 parts with the full part parameter set (patch by bank/PC with the real patch name read from the hardware, level, pan, key range/zone, sends, offsets, vibrato, scale tune, MIDI receive switches), MFX 1–3, performance chorus/reverb, effect routing, voice reserve and solo part.
- Arpeggiator — style (128 named styles), grid, duration, motif, octave range, velocity and accent. Roland's official editor does not expose the arpeggiator at all.
- Drums — a TR-style step sequencer for the Juno's rhythm sets: patterns of 16 or 32 sixteenth-note steps with three velocities per step (soft / normal / accent), swing, mute, one lane per rhythm set key (named after the kit chosen), any number of patterns chained into a song with repeats, and a tempo. Patterns play live over MIDI on the drum channel with a lookahead scheduler, and the whole song exports as a Standard MIDI File the Juno's Song Player plays from USB memory — kit selection (Bank Select + Program Change) included. Songs are saved to a local library and exported / imported as JSON.
- System settings — master tune / key shift / level, channels, System Control 1–4, CTRL pedal, D-Beam and scale tune, read and written live.
- Librarian — browse the 128 user patches / 64 user performances / 8 user rhythm sets stored in the Juno with their names, load a slot into the editor, write the current sound to a slot, or make the Juno select a slot (the editor then syncs with what the hardware loaded).
- Preset browsers — the hardware's full Tone List (1082 presets + 256 GM2 sounds) with names, categories and bank/program, searchable and filterable by panel button and category; pick one to load it on the Juno and read it into the editor, or to assign it to a performance part. The 64 factory performances and the 29 rhythm sets get the same treatment in Performance and Rhythm mode.
- Edit a part's patch or rhythm set — point the patch (or rhythm set) editor at the temporary area of any performance part and edit it in place.
- Bidirectional — changes made on the Juno's panel show up in the editor; changes in the editor are sent as DT1 immediately. "Read from Juno" pulls the whole temporary patch / rhythm set / performance / system block.
- Local library — save, load, export and import patches, rhythm sets,
performances and drum songs as JSON (
.junodi.json/.junodirhythm.json/.junodiperf.json/.junodidrums.json); everything is persisted in the browser. - Workflow helpers — solo a tone or a part directly on the hardware, copy/paste tones and rhythm keys, Ctrl+click any label to reset it to its default, randomize the filter/amp of the active tones, and a two-octave virtual keyboard to audition sounds without touching the synth.
- Ships with presets — a few classic lead / brass sounds are seeded into the library on first run (see Bundled presets).
Requirements
- A Roland JUNO-Di connected over USB (the Juno appears as a class-compliant
USB MIDI device; its factory Device ID
17/0x10is assumed). - A browser with Web MIDI + SysEx support: Chrome / Chromium / Edge, or Firefox with the MIDI permission granted.
- A secure context:
http://localhostworks out of the box; when served from another host it must be over HTTPS.
The editor also runs without the synth — you can browse, edit, save and export sounds offline and send them later.
Running
With Docker:
docker compose up
# http://localhost:3000 — set PORT in a .env file (e.g. PORT=7000) to use another port
With a local Node.js (24+):
yarn
yarn dev # dev server at http://localhost:3000
yarn lint # eslint
yarn typecheck # vue-tsc
yarn build # production build (generates the PWA service worker/manifest)
The production build is a fully static SPA (ssr: false) — serve
.output/public from any static host or reverse proxy (HTTPS required for
Web MIDI outside localhost).
How it works
Parameter registry (app/lib/params/)
There are no hardcoded controls in the UI: every parameter is a declarative
ParamDef (id, label, section, type, range/enum, default, display formatter
and its SysEx address + encoding). Components render parameters from their
ids, and the registry alone decides what is sent to the hardware.
types.ts—ParamDef,PatchValues,SavedPatch, export formats.registry.ts— the Patch registry: Patch Common, TMT, Tone 1–4 (OSC / TVF / TVA / LFO1 / LFO2), MFX, Chorus and Reverb. Tone parameters are generated by a function, so a fix applies to all four tones. It covers every byte of Patch Common / TMT / Patch Tone in the MIDI Implementation, even the ones that stay hidden in the UI — so sending a patch replaces the whole temporary area with no leftovers from the previous sound.performance.ts— the Performance registry: Performance Common, MFX 1–3, chorus/reverb, 16 × Part, 16 × Zone, 16 × MIDI, and the arpeggiator (which lives in the Setup area on the hardware and is saved with the performance).rhythm.ts— the Rhythm Set registry: Rhythm Common, MFX, chorus, reverb and 88 × Rhythm Tone (key 21–108: key settings, four WMT waves, pitch / TVF / TVA envelopes). Like the patch registry it covers every byte of every block, so a kit can be read and written block by block.rhythm-kits.ts— the 20 preset kits and 9 GM2 kits with the instrument assigned to each key, used by the preset browser.system.ts— the System registry (System Common + Controller).mfx.ts— the 79 MFX types with their parameter names (from the Effects List) and heuristic defaults per parameter name.fx.ts— per-type parameter specs (labels, ranges, enums, defaults) for chorus (Chorus / Delay / GM2) and reverb (Reverb / SRV Room / Hall / Plate / GM2).waves.ts,arp-styles.ts— the 850 PCM waveforms and the 128 arpeggio styles, by number and name.preset-patches.ts,preset-performances.ts— the Tone List (1338 entries: name, category, MSB/LSB/PC) and the 64 factory performances, used by the preset browsers and to name part patches offline.lookup.ts— resolves aParamDeffrom any registry; the id namespace (perf.*/arp.*/system.*/rhythm.*/ everything else) decides which store owns it.presets.ts— the bundled presets.
State (Pinia)
Four stores mirror the hardware's documents — stores/patch.ts,
stores/rhythm.ts, stores/performance.ts and stores/system.ts — all
built on useParamState(registry), the single write funnel: every change
goes through setParam(id, value), which coerces the value, persists it and,
when MIDI is connected, emits the DT1. The stores add document-level behaviour
on top: init, randomize, library, export/import, seed presets, solo,
copy/paste (tones, rhythm keys), the selected rhythm key, and applying sane
defaults whenever an effect type changes (the firmware does not initialize
effect parameters when the type is set via SysEx).
stores/drums.ts is the odd one out: the drum song is not a hardware
document but plain MIDI data (patterns, chain, tempo, channel, kit), so the
store owns it directly — pattern / lane / step / chain mutations, validation
of anything loaded from storage or a file (normalizeSong), the local
library, JSON export/import and the SMF export. A deep watcher hands every
change to the player (so edits are heard on the next step) and debounces the
localStorage write.
Drums (app/lib/drums/, app/lib/midi/smf.ts, useDrumPlayer)
pattern.ts— the song model (DrumSong→DrumPattern→DrumTrack, steps as velocities, 0 = rest), default GM lanes, resizing 16 ↔ 32 steps, swing as a delay on the off-beat 16ths (50 straight … 67 triplet … 75), and the expansion of the chain into absolute-tick notes for the export.scheduler.ts— pure timing/position helpers: step duration and swing in ms, andresolvePosition, which maps a (possibly stale) chain position onto the current song so tempo, swing, steps, step count and chain edits are all picked up at the next step while playing.smf.ts— a Standard MIDI File (format 0) writer: tempo, time signature and track name meta events, Bank Select + Program Change for the kit followed by a one-beat lead-in so the hardware has loaded the rhythm set before the first hit, note on/off with running absolute ticks (480 PPQ). The Juno's Song Player plays SMF 0/1 from USB memory; in Performance mode channel 10 drives Part 10, so the file plays with the kit it names.useDrumPlayer— a lookahead scheduler over Web MIDI: every 25 ms it schedules the steps of the next 120 ms withoutput.send(data, timestamp)(throughuseMidi().sendNoteAt), lights the step in the grid when it actually sounds, resyncs after a background-tab stall and stops when the port goes away. Pattern mode loops the selected pattern, song mode walks the chain and loops it.
MIDI (app/lib/midi/, app/composables/useMidi.ts)
roland.tsis pure protocol: Roland checksum, DT1/RQ1 builders, DT1 parsing, 7-bit address arithmetic, ASCII name encoding and value encode/decode (1 byte direct; 2/4 bytes as nibbles;bias/divisorfor signed and centered values).blocks.tsencodes a whole address block from the registry (every byte, names included) and splits it into DT1 chunks of at most 128 bytes that never cut a parameter in two.useMidiowns the Web MIDI connection. It picks the port whose name contains "JUNO", throttles per-parameter sends (20 ms), dumps whole documents in batches (effect types first, so the parameters that follow land on the right effect), reads blocks with RQ1 and applies every incoming DT1 — replies and panel edits alike — through an address → parameter index, with an echo guard. Rhythm sets (~14k values) are read and written block-wise: one RQ1/DT1 per key instead of one per parameter.- Base addressing — registry addresses are relative to the temporary
patch (
1F 00 00 00) / rhythm set (1F 10 00 00) / performance (10 00 00 00); the composable rebases them on the fly to the temporary patch or rhythm set of part N (11 00 00 00+ N ×20 00 00, rhythm at+10 00 00) or to user memory (30 NN 00 00patches,40 NN 00 00rhythm sets,20 NN 00 00performances). That is what makes "edit part patch / rhythm set" and the Librarian work with the same editor. - Sound Mode — on connect the editor reads the Sound Mode from Setup and, before any bulk read/write, aligns the Juno to the editor's mode (PATCH ↔ PERFORM), so reading a performance works even when the synth is in Patch mode. Performances and Patch Mode rhythm sets are selected by writing the Setup area's Bank/Program bytes, which works regardless of the Performance Control Channel setting.
- Names — patch/rhythm set/performance names are written as 12 ASCII bytes when edited; part patch names are read live from each part's temporary patch (so user patches show their real names too); offline, presets and GM2 sounds are named from the bundled Tone List.
- Audition — rhythm keys are played on MIDI channel 10 in Patch Mode, or on the part's receive channel when editing a part's rhythm set.
- Solo is done on the hardware only (TMT switches for tones, Part Mute for parts) and never touches the document; disconnecting clears it.
Persistence (app/composables/usePatchStorage.ts)
All localStorage access lives in one composable, parameterized by document
kind (patch | rhythm | performance | system): current document,
library and seed version for each. useDrumStorage.ts does the same for the
drum song and its library. Swapping in a backend later means reimplementing
these two files only.
Components
ParamSlider / ParamSelect / ParamSwitch / ParamKnob render any
parameter from its registry id; EnvelopeChart + EnvelopeEditor draw the
5-point TVF/TVA envelopes (ADSR or full T1–T4 / L0–L4 view). Tabs are plain
compositions of those primitives: CommonTab, ToneTab (+
ToneAdvancedCard), MfxEditor, FxBlockEditor, ArpeggioTab,
RhythmKeysTab (+ RhythmKeyEditor, RhythmWmtCard), RhythmCommonTab,
PerformancePartsTab (+ PartPatchSelect), PerformanceEffectsTab,
PerformanceCommonTab, plus PatchLibraryModal, UserBankModal
(Librarian), PresetBankModal / PresetPerformanceModal /
PresetRhythmModal (preset browsers), VirtualKeyboard and AppTopbar.
The drum machine is DrumTransport (play / stop, pattern vs song mode,
tempo, channel, kit, SMF export), DrumPatternGrid (patterns, lanes and
step buttons; click cycles rest → normal → accent → soft, Shift+click sets
an accent, Ctrl+click or right-click clears, drag paints), DrumChainEditor
(the song's pattern order and repeats), DrumAddTrackModal (pick a rhythm
set key for a lane) and DrumLibraryModal.
Bundled presets
Seeded into the local library on first run (patches are written to the Juno's temporary area when sent — use the Librarian or the panel's WRITE to keep them):
| Preset | Notes |
|---|---|
| Monster Lead | Derek Sherinian-style lead: SBF Vox + saws, Matrix Control CC01 → cutoff for a hand-driven wah |
| MonsterLdWah | Same lead with the wah on MFX Auto Wah, driven by the mod wheel through MFX Control |
| MNTL Octa | Three-oscillator octave stack (MG Saw HD at −12 / −10 / +10) in POLY |
| Final Cntdwn | Europe – The Final Countdown brass/saw stack |
| Jump OB-Xa | Van Halen – Jump Oberheim-style brass |
| Thriller Hit | Michael Jackson – Thriller brass hit |
| Stage Split (performance) | Piano on the left hand, user patch 001 on the right, drums on part 10 |
Reference documents
The SysEx addresses, parameter ranges/enums, wave list, arpeggio styles and effect tables in this project were derived from Roland's official documentation:
- JUNO-Di MIDI Implementation
- JUNO-Di Owner's Manual
- JUNO-Di Editor Owner's Manual
- JUNO-DS Parameter Guide (arpeggio style list — same engine)
- JUNO-Di Waveform List
The extracted wave names, arpeggio style names and effect tables are factual data derived from those documents. The PDFs themselves are Roland's and are not redistributed in this repository.
Limitations
- Effect defaults applied when an MFX/chorus/reverb type changes are heuristics derived from the parameter names — Roland publishes no default table — so a freshly selected effect is audible and sane, not identical to the factory init.
- Patch and performance dumps are one DT1 per parameter (only rhythm sets use block writes), so a full patch dump takes a couple of seconds.
- The hardware does not report which preset kit a rhythm set came from, so keys without a name show a dash instead of the factory instrument name.
- The drum machine plays over MIDI in real time, so live timing is as good as the browser's timers and Web MIDI scheduling (a hidden tab may stall and resync); the exported SMF is tick-exact. Whether a channel other than 10 plays drums depends on which part receives it on the Juno.
This is an independent project and is not affiliated with or endorsed by Roland Corporation. JUNO-Di is a trademark of Roland Corporation.