No description
  • TypeScript 71.6%
  • Vue 28.2%
Find a file
Acaua Montiel f29062fa9c
All checks were successful
Build & Push / build-push (push) Successful in 38s
Add production compose
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
2026-09-14 18:16:10 -03:00
.forgejo/workflows Add Forgejo CI: build and push the Docker image, then trigger deploy 2026-09-14 18:04:09 -03:00
app Add drum machine with live MIDI playback and SMF export 2026-08-28 18:00:38 -03:00
public/icons Initial commit: Roland JUNO-Di editor (Nuxt 4 PWA + Web MIDI) 2026-08-25 20:01:01 -03:00
.dockerignore Add Forgejo CI: build and push the Docker image, then trigger deploy 2026-09-14 18:04:09 -03:00
.editorconfig Initial commit: Roland JUNO-Di editor (Nuxt 4 PWA + Web MIDI) 2026-08-25 20:01:01 -03:00
.gitignore Initial commit: Roland JUNO-Di editor (Nuxt 4 PWA + Web MIDI) 2026-08-25 20:01:01 -03:00
compose.prod.yml Add production compose 2026-09-14 18:16:10 -03:00
compose.yaml Make the dev server port configurable with a 3000 default 2026-08-28 18:12:10 -03:00
Dockerfile Add Forgejo CI: build and push the Docker image, then trigger deploy 2026-09-14 18:04:09 -03:00
eslint.config.mjs Initial commit: Roland JUNO-Di editor (Nuxt 4 PWA + Web MIDI) 2026-08-25 20:01:01 -03:00
nuxt.config.ts Initial commit: Roland JUNO-Di editor (Nuxt 4 PWA + Web MIDI) 2026-08-25 20:01:01 -03:00
package.json Initial commit: Roland JUNO-Di editor (Nuxt 4 PWA + Web MIDI) 2026-08-25 20:01:01 -03:00
pnpm-workspace.yaml Initial commit: Roland JUNO-Di editor (Nuxt 4 PWA + Web MIDI) 2026-08-25 20:01:01 -03:00
README.md Make the dev server port configurable with a 3000 default 2026-08-28 18:12:10 -03:00
renovate.json Initial commit: Roland JUNO-Di editor (Nuxt 4 PWA + Web MIDI) 2026-08-25 20:01:01 -03:00
tsconfig.json Initial commit: Roland JUNO-Di editor (Nuxt 4 PWA + Web MIDI) 2026-08-25 20:01:01 -03:00
yarn.lock Initial commit: Roland JUNO-Di editor (Nuxt 4 PWA + Web MIDI) 2026-08-25 20:01:01 -03:00

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 14 (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 (A0C8) 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 13, 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 14, 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 / 0x10 is assumed).
  • A browser with Web MIDI + SysEx support: Chrome / Chromium / Edge, or Firefox with the MIDI permission granted.
  • A secure context: http://localhost works 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.tsParamDef, PatchValues, SavedPatch, export formats.
  • registry.ts — the Patch registry: Patch Common, TMT, Tone 14 (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 13, 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 21108: 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 a ParamDef from 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 (DrumSongDrumPatternDrumTrack, 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, and resolvePosition, 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 with output.send(data, timestamp) (through useMidi().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.ts is 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/divisor for signed and centered values). blocks.ts encodes 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.
  • useMidi owns 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 00 patches, 40 NN 00 00 rhythm sets, 20 NN 00 00 performances). 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 T1T4 / L0L4 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:

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.