Open MIRA, the synthtoys polysynth, in a floating window on any track of the running song. The window is MIRA's own page, with its knobs, blocks, scenes, keyboard and phrase player. It plays through Volta's engine. Every gesture edits a song-local MIRA patch: a module file saved beside your song, which the song imports and plays. Move a knob and the track changes while it plays. Press a key and you hear the patch through the same mixer strip as the track. Save the song and the patch, knob positions included, is saved with it.
Open MIRA#
The song must be running: MIRA reads the track from the compiled song. There are four ways to open MIRA. Each one acts on one track.
- Mixer: the strip menu → Open MIRA…
- Timeline: the track's row-head menu → Open MIRA…
- Rack case: the panel menu → Open MIRA…. This item appears only when the panel's track or the case's patch is already a MIRA track: a song-local MIRA module, or a
miralibrary preset. - Edit menu: Open MIRA on Track… (
edit.open-mira). It acts on the selected track, or else on the declaration under the source caret. The browser workbench has no menu bar, so there you use the first three.
Each track has one window. Opening MIRA again on the same track brings its window to the front. In a workbench that cannot host the window, the item is greyed out. The browser's static studio.html twin is one such page.
What MIRA starts from depends on what the track plays:
| The track plays… | MIRA opens with… | Change to the song |
|---|---|---|
<alias>.inst of a MIRA module | the patch saved in that module | none |
a mira library preset (import "mira" (halo, haloFx)) | that factory patch, at the song's tempo, with the track's voices | adoption, on MIRA's first change (below) |
| any other instrument | a blank patch, after a Replace instrument with MIRA… confirm | replaces the instrument, on MIRA's first change |
What happens to the song#
The song changes when MIRA first installs a patch. Opening the window alone changes nothing. That first change is one undo step.
- Volta writes a module
mira-<track>-<id4>.voltabeside the song.<id4>is four hex digits that are unique in the folder. The name does not depend on the song's file name, so Save As carries the module along. The file holds:- a comment line, then
-- @tags mira-patch; - a
-- @mira {json}header with the whole MIRA patch, voice count, phrase and a hash of the body; - MIRA's own generated program, verbatim;
- a short footer of stable names written as
X = Y with {}:instand any missingvoice1…voice3. A bare alias such asinst = voiceSeqwould not work: it names the same synth as its target, so the song could not route or preview it.
- a comment line, then
Volta edits the song:
import "mira-marble-3f2a" as miraMarble marble = play miraMarble.inst (…the track's own pattern…) submix miraMarbleOut = [marble, miraMarble.inst, miraMarble.voice, miraMarble.voice1, miraMarble.voice2, miraMarble.voice3] mixer { miraMarbleOut = { inserts = [miraMarble.chain] } }- The alias is
mira<Track>, with a number added if the name is taken. - The track's synth is replaced by
<alias>.inst, and a|> <preset>Fxpipe is dropped. - A sub-mix (mixing §1.3) named
<alias>Outtakes the track and the module's patches. Notes you play on MIRA's keyboard are voices on those patches, so they pass through MIRA's effectchainwith the track. - The mixer row puts that chain on the sub-mix.
The module's own
tempo,auditiontrack and other unused bindings never reach the song, and the song's tempo wins.- The alias is
Song pickers and the workspace file list skip mira-patch modules, so a module file never appears as a song.
Later changes. A structural change in MIRA (a block switched on, a route added, the voice count) regenerates the module text, and Volta hot-swaps the whole song. A change that leaves the text the same does not swap. MIRA queues its own reloads, so a run of fast edits shows up as lag, not as lost edits. If the new text does not compile, the song and the previous module stay as they were, and MIRA shows the compiler's error.
Knobs, saving and hand edits#
A knob turn is a live macro change. It sounds at once and does not recompile. The shell polls MIRA's patch every 500 ms while the window is visible, and again when the page goes away. It writes the knob values back into the module's x = macro v lines. Volta holds that text as pending and folds it in on the next structural install, on ⌘S or Save As, or when the window closes. Then the song is marked unsaved.
⌘S writes every changed MIRA module beside the song. Save As writes every -- @mira module the song imports beside the new path. The -- @mira header is the patch's source of truth: no sidecar file exists, and history and Save As carry it like any other text. Reopening the window loads the patch from the header.
If you edit the module's program by hand, its body no longer matches the header's hash. MIRA warns that the file was edited by hand and overwrites it on its next change. Edit the patch in MIRA, not in the file.
Adopting a mira library preset#
A track that plays a library preset (play (halo with { voices = 5 }) chords |> haloFx) can move onto its own MIRA patch. On the first install Volta rewrites the song's knob entries to the module's names, guarded as one splice:
| Library knob | Module knob |
|---|---|
mira.<p>.<param> (e.g. mira.halo.fCut) | <alias>.<param> |
mira.<p>.depth.<src>.<dst> | <alias>.dep<i> for every active route from src to dst (i = the route's index in the patch) |
mira.<p>.depthBy.<src>.<dst> | <alias>.dby<i> |
mira.<p>.step<n> (1-based) | <alias>.stp<n-1> |
mira.<p>.<label> (a routed macro's label) | <alias>.m<k> |
mira.wheel, mira.press | <alias>.wheel, <alias>.press, only when the patch routes that source |
Only with { … = … } entries are rewritten. If another track or binding still plays the preset, the entry is duplicated (mira.eventHorizon.open = v, miraGrowlT.m1 = v). Otherwise it is renamed. mira.wheel and mira.press are duplicated while any import "mira" remains, and renamed when the last one goes. A local binding that only this track played (haloPad = halo with { voices = 5 }) is removed. import "mira" names that adoption left unused are removed too. Names that were already unused stay.
Adoption is refused with a message, and nothing changes, when:
- the track's synth, or its local binding, overrides anything other than
voices(for examplefolded,v_xFmorsource); voicesis outside 2–12, the range of MIRA's voice selector. This includesvoices = 1;- a
mira.<p>.…name is read as a value, outside awithentry, or appears in a string; - a knob entry has no module counterpart (
mira.halo.glide), or names a macro that MIRA's program never reads (imported unread macros are pruned, so the entry would do nothing); - the preset is a mono or legato patch. MIRA plays glide and legato from its own keyboard, so a song track on
<alias>.instwould play eight-voice polyphony without glide; - the song writes
mira.morphwhile the patch has a second scene. MIRA blends scenes in its page, not in the engine; - the track already plays into a
submix. A track has one output and sub-mixes do not nest, so<alias>Outcannot take it. This applies to every starting point, not only presets.
Checks that need only the song run before the window opens, and a refusal is a toast. Checks that need MIRA's patch run on the first install, and a refusal is that install's error plus a toast.
Desktop and browser#
The window is the same MIRA page and the same Volta shell in both workbenches. Only the transport differs.
| volta-studio (macOS) | Browser workbench | |
|---|---|---|
| Window | a native window holding a web view, titled MIRA · <track>, 1180×820, position remembered per panel kind | a floating frame over the canvas: drag the title bar, resize from the corner, ✕ to close; position kept in browser storage; a second window cascades |
| Hidden MIRA controls | Share, MIDI, tempo, Record | Share, MIDI, tempo |
| Record | hidden: use Volta's own capture | works: MIRA records a WAV of its output |
| ⌘S | saves the song while the window has focus | saves the song while focus is inside the frame |
| ⌘W | closes the MIRA window when it has focus | not available: a page cannot catch ⌘W |
| ⌘Z | MIRA's own undo | MIRA's own undo |
MIRA's tempo field is hidden in both. MIRA bakes synced delay times from its tempo, so Volta sets it from the song instead. MIDI is hidden because the workbench's MIDI input already reaches the track. Links out of MIRA do nothing.
Closing asks the page first. MIRA sends its last knob write-back, Volta folds it in, then the window closes. A page that does not answer within 1.5 s is closed anyway. On the desktop, a second click on the close button closes the window at once.
Engine restart. When the audio engine restarts, each MIRA page reloads and opens again on the same patch. On the desktop only MIRA's inner frame reloads. In the browser the whole shell reloads, because recovery builds a new audio context.
Another song going live closes every MIRA window, with a toast. Knob moves not yet folded in are lost. Save first.
Browser saving. The browser has no folder. A MIRA module is stored inside the song's document, as its import, and never as a new document of its own. With no saved document (a library song, an unsaved session) the module stays in the session's import buffers and is stored by the next Save or Save As.
Updating MIRA from synthtoys#
The code in panels/mira/dist/ is MIRA's own, built unchanged from a synthtoys checkout. Never edit it here: a change to MIRA belongs in synthtoys. To pick up a new MIRA:
node scripts/sync-mira-panel.mjs [path/to/synthtoys] # rebuild dist/ + PROVENANCE.json
node scripts/sync-mira-panel.mjs [path/to/synthtoys] --fixtures # refresh the shell's test fixtures
node runtime/scripts/sync-assets.mjs # mirror into runtime/public/panels/mira/
Commit panels/mira/dist/ and panels/mira/PROVENANCE.json together. The provenance records the synthtoys commit, whether its tree was dirty, the build date and a hash per file. panels/mira/README.md has the full procedure and tests.
Before release: the current bundle was built from a dirty synthtoys tree. It is commit 2184418ee plus uncommitted packages/volta-host edits that MIRA's page imports, and PROVENANCE.json says "dirty": true. Rebuild it from a clean synthtoys commit before shipping.
Notes and limits#
- Macro clutter. Each MIRA track adds its patch's macros (dozens, under
<key>.*) to the Macros panel and strip. Collapsing them is a follow-up. - The sub-mix is about 2 LU quieter than a track pipe. Measured offline, not listened to: a library preset routed as
submix out = [track]with<preset>Fxas the sub-mix insert came out about 2 LU quieter than the same preset piped on the track. Against that sub-mix version, the adopted MIRA module was within ±0.4 LU (halo +0.4, bass 0.0, growl −0.4). So adopting a preset sounds about 1.5–2.5 LU quieter than before, and the patch is not the cause. MIRA's −1 dB limiter did not engage on the patches measured. The cause in the track-pipe vs sub-mix level is not yet found. - Do not pipe a module's chain onto a track.
t = play miraPad.inst … |> miraPad.chainfails to lower ("empty or duplicate control graph name$control:…") when the chain reads an@sharedrouted control. The sub-mix insert, which is what Volta writes, is fine. The pipe bug is open. - Tiling window managers (AeroSpace, for example) re-tile the native window after it opens, so it takes the tile's size, not 1180×820.
- First open is slow on the desktop. The first MIRA window costs one frame of about 370 ms while WebKit starts its process. Later windows are fast.
- Window placement. A second native window opens centred, not cascaded. The browser's default frame covers most of a 1600×1000 workbench.
- Floating only. Docking MIRA into a workbench pane is not built.
Next steps#
- Reuse sounds and patterns with imports: how the song-local module is resolved and named.
- Mixing tracks and buses §1.3: sub-mixes with patch members.
- Imported strips: what an imported module's master chain does instead.