schwung-movy, image 1 from its README
1/4
Firmware

schwung-movy

Loud80/100

Elektron-style knob UI + 16-track step sequencer for Schwung modules on Ableton Move

schwung-movy is custom firmware for the Ableton Move, by @DimaDake. Open source under MIT, latest release v0.37.0 (October 2026), 76 stars on GitHub.

forAbleton MoveΒ·by@DimaDake

Unclaimed. Is this yours? Sign in with GitHub as @DimaDake to claim it.

Downloads
0
Stars
76
Forks
5
Commits
1,560
Last commit
Download v0.37.0

movy-module.tar.gzΒ· 4.0 MB

MITΒ· DimaDake/schwung-movyΒ·downloads come from the creator’s GitHub releases

1,517
Commits
19
Releases
1,517 commits and 19 releases in the last year
Activity Β· last 12 months
19 releases
  1. v0.37.0v0.37.0 β€” Mixer, note views, MIDI keyboardsLatest1 file

    Added

    • Web mirror shows the whole device and records sound. Under Movy, Schwung's /mirror page used to show only the screen. It now also shows every LED and the controls as you hold them. With Mirror Display on, it plays and records Movy's audio too. With the setting off, Movy doesn't copy any audio. An idle frame costs ~60 ns on the audio thread.

    • External MIDI keyboard (USB-A). Notes on any channel play the selected track at pad latency (the engine sounds them, ENGINE 0.103.0) and record like pad notes. On drum tracks note 36 upward plays one pad per key. Mod wheel and pitch bend play live and are not recorded. There is no CC mapping yet.

    • Held-step piano. Hold a step on a melodic track and the bar strip becomes a keyboard with the step's notes filled in, below the knobs. Keys are equal slots along the top edge; the range starts on a C, shows at least two octaves and narrows the keys (5/4/3/2 px) for wider chords, and never shrinks during a hold.

    • Session mixer. Latch Session view and hold Note/Session: 16 track faders with stereo post-fader meters on one screen. Knobs drive tracks 1-8 / 9-16, the jog switches the page, release returns to Session. Built as a Schwung custom page (header, page bar and held-knob toast are Schwung's); meters come from a new engine status field (mtr, ENGINE 0.101.0).

    • Loop-mode note view. While Loop mode is on, the screen above the bar strip shows the clip's notes as a folded piano roll (only used pitches get a row, bars aligned with the strip). Dense clips fold instead of dropping notes: pitches share rows past 30, and pixels with several notes are hatched. Recording shows live, including auto-extend. Knobs are idle in this view. Replaces the Loop readout band. New engine status nh= + notes get (ENGINE 0.102.0).

    Fixed

    • The power dialog no longer opens by itself. Move's battery report shares the power button's SysEx, so every one-percent battery drop opened Power off? on an idle device. A press is now only the button bit's rising edge.
  2. v0.36.0v0.36.0 β€” Movy runs on its own1 file

    Highlights

    • Movy runs on its own. It is now a standalone Schwung tool: while it is open it owns the whole device (pads, screen, audio, volume, power button) and Move's software stops until you leave. It can also be the thing the Move boots into.
    • Movy keeps its own Sets (Shift + Step 1), brought over from Move once by a single migration.
    • Modules follow Movy's transport and tempo, so clock-driven modules such as Breakbeat lock to the bar.
    • Parameter pages are always Schwung's.

    Added

    • Boot straight into Movy. Movy adds a row to Schwung's boot picker: press Back in the two-second window at power-on, pick Movy, and the Move powers on into Movy from then on. Leaving Movy goes to Schwung; pick Schwung in the same window to go back. Installing never changes the default.
    • Movy keeps its own Sets. Shift + Step 1 opens a list of Movy's own Sets: make a new one, open, rename (Capture), duplicate (Copy) and delete (Delete, with a confirm). If the library folder cannot be written, Movy says so on screen instead of starting a Set. (Engine 0.88.0.)
    • One migration from Move, run once. The first start of standalone Movy brings over every Set from every Schwung set page, under its Move name (Set 1 P3 when two pages share one). It turns Schwung's tracks 1–4 and master chain into Movy's, and converts every older Movy storage format, including chains kept only in ui-state.json. It skips empty Sets and never runs again on its own. Settings β†’ MIGRATE FROM MOVE (click twice) runs it again, replacing only Sets that came from Move and keeping what it replaced in BACKUPS. Nothing of the old install is deleted, so an older Movy still finds it. (Engine 0.95.0.)
    • Settings β†’ CLICK VOLUME (knob 1) raises the metronome from the default (0 dB) up to +11 dB, the loudest it can go without clipping. (Engine 0.99.0.)
    • Renaming a Set suggests a name. A Set still named for its date (2026-10-17_01) opens the rename keyboard on a two-word name from a hand-picked list (Velvet Echo, Paper Moon, …), never one already in the library.
    • Shift + knob 1 on a preset page steps the module's bank (obxd, Dexed), as Schwung's own Shift+Left/Right does: it wraps and lands on the bank's first preset. Knob 1 browses presets without entering the page, and the Loop strip stays visible underneath.
    • Canvas knobs say how to open them. A knob that opens a module's own screen (Breakbeat Status / Edit A / Edit B, DR32 ENGN) does not turn; held, it now shows CLK OPEN, as a file knob shows CLK BROWSE β€” on every view where its click works, including the chain view.

    Changed

    • Standalone. Movy owns the device while it is open: the volume knob is master volume (1 dB per detent), and the power button asks Power off? and saves first. Move's software comes back when you leave. Needs Schwung 1.6.0 or newer. Updating from the store keeps everything the old Movy stored.
    • Leaving: Back at the top opens Leave Movy? with Close Movy. If Movy stops responding, Shift + touch volume + jog click closes it (hard close after two seconds). Closing from the Tools menu returns to Schwung, even when Movy is the boot default.
    • Parameter pages are always Schwung's. The PARAM PAGES setting is gone: every module's pages are planned and drawn by Schwung, as they have been by default since 0.35. While a module is still loading its knob rows stay blank and the knobs do nothing. Shift + jog on a knob page now turns one page; Schwung's section list is Shift + jog click.
    • One alphabet. Movy's own text uses Schwung's parameter-page font, so the chrome and the pages match.
    • More tracks before the CPU limit. Tracks now render across all four cores instead of three: a busy set takes up to a fifth less time per audio block. (Engine 0.94.0.)
    • Movy's tracks run the chain host Movy ships, built from a pinned Schwung release, so a Schwung update can no longer change how Movy's tracks sound or how their pages look without a Movy update. (Engine 0.86.0.)
    • Synced LFOs follow Movy's transport only. While stopped they free-run at Movy's current tempo, so a tempo change takes effect at once.
    • Empty Sets are not kept. A Set with no clip and no module is never saved, and leaving it moves it to Trash; the open one stays. (Engine 0.96.0.)
    • Settings lists BACKUPS, CLICK VOLUME and MIGRATE FROM MOVE. The debug-only Engine Saves switch is gone; the engine always owns the Set files. (Engine 0.97.0.)
    • The engine reports an unreadable Set. The UI reads only a Set's chains.json; ui-state.json no longer carries a copy of the chains. An unreadable chains.json opens with no chains; its bytes stay in the Set's BACKUPS. (Engine 0.98.0.)
    • The Sets page's buttons say what they do. Capture, Copy and Delete are lit dim on a Set and dark on [NEW] or under an armed delete; under the rename keyboard only Back stays lit.
    • Powering off shows that it is happening. After Power off? the screen reads Powering off... and a rainbow drifts slowly across the pads until the power goes, instead of what looked like a hang.
    • A broken install says so. If Movy's engine is missing or from another build, the start screen shows ENGINE DID NOT START or ENGINE MISMATCH with REINSTALL MOVY, instead of asking you to restart the Move.

    Fixed

    • Modules follow Movy's transport. Play, Stop and MIDI clock now reach every chain (tracks, sends, master). Before, a clock-driven module such as Breakbeat free-ran on its own tempo, ignored Stop, and its phrase/fill/retrigger logic never lined up with the bar. (Engine 0.100.0.)
    • Modules read Movy's tempo, not Move's. A module asking for Move's set settings (move_info, e.g. Breakbeat, TinyK) now gets Movy's tempo, transport, song position and metronome instead of Move's, frozen.
    • Live module screens update. A module screen declaring live values (fullscreen_live_ms) now receives them, and module-drawn canvas pages (Pixel Walkers) animate instead of freezing on their first frame.
    • Drum racks show one pad per note. A module declaring the same pads on several pages (Strut) showed a 32-pad grid for 16 voices.
    • Virus (osirus) no longer crackles. Its emulator kept Movy's audio waiting on the same core; Movy now moves it to another core.
    • The power-off dialog opens reliably. A companion message the power button sends could close the dialog before it was drawn.
    • Switching Sets no longer drops the last couple of seconds of playing. The engine saves the Set being left before opening the next one.
    • Opening a Set no longer risks overwriting it with the previous one. An autosave falling due between asking for a Set and its contents arriving could save the Set being left into the one being opened. Autosave now waits for the open to land. (Engine 0.92.0.)
    • A fast jog spin moves the whole way in the Sets, Backups and Settings lists, instead of a single row.
    • Setting a slot to None no longer leaves the departed module's knobs on the page under a "β€”" header.

    Removed

    • The things that only existed to share the device with Move: Background mode, LINK to Move's transport, Sets that follow Move's Set pads, Move's master volume and master FX slots, Shift + Back, and tracks on Schwung's slots. The Set page's knob 3 is empty where LINK was.
    • Movy's own parameter-page renderer (kept in the history at tag movy-pages-final), the MIGRATE TRACKS Settings row, and the debug flags Movy Sets, Commit New Sets, Movy Master Chain, Pinned Chain Host and Engine Saves. Stored values of those flags are ignored; nothing on disk is deleted. (Engine 0.91.0.)

    Known limitations

    • Move's built-in speaker EQ is not applied yet, so the speaker sounds different from the same Set under Move. Headphones and line out are unaffected.
  3. v0.35.0v0.35.0 β€” Schwung pages, drum racks, safer Sets1 file

    Movy v0.35.0 β€” Schwung pages, drum racks done properly, and a much safer Set

    Recommended host: Schwung 1.6.0 or newer (1.7.3 is current). Movy still installs on 1.1.0+, but Schwung-drawn pages need 1.5.0 (older hosts stay on Movy's own pages) and per-pad copy needs 1.6.0.

    ⚠️ Read first

    • Schwung tracks are no longer supported. All 16 tracks are Movy chains, and the TRACKS 1-4 HOST setting is gone. The first time you open a Set that still uses Schwung's slots for tracks 1-4, Movy migrates it behind a MIGRATING TRACKS screen. The module, preset, volume and LFO assignments come across. A chain position Movy's UI never shows is left behind and reported as a warning. The Schwung slot itself is never cleared, it just stops playing. The Set is marked migrated either way, so a bad read doesn't retry forever. Settings β†’ MIGRATE TRACKS reruns the migration by hand, and unlike the automatic pass it overwrites whatever those chains hold.

    What's new

    1. Modules look the way their authors drew them. Settings β†’ PARAM PAGES chooses who draws a module's parameter pages, and it is now a release setting: β€’ SCHWUNG (the new default; every device is moved to it once) uses Schwung's own page code, the same pages Schwung's Slot Settings and Master FX Settings show. It needs Schwung 1.5.0+; on an older host the row stays on MOVY. β€’ MOVY keeps Movy's own knob grid for now. It will be removed soon, so please report anything that only works there.

      Under SCHWUNG: module-drawn pages render (MonkSynth's face page), canvas knobs open the module's own fullscreen screen (DR32's ENGN, the only way to pick a pad's sample), hidden pages stay hidden (DR32: 8-11 pages, not 52), file browsers preview the file under the cursor, and the LFO pages are Schwung's own.

    2. Drum racks work properly. β€’ Per-voice mute and solo: hold Mute + pad mutes a voice, Shift + Mute + pad solos it. Silenced voices show grey. Saved with the Set and undoable. β€’ Drum modules' per-pad sends (Simian, DR32) now feed Movy's SEND 1 / SEND 2.

    3. More automatable. Choices and switches (filter type, waveform, on/off) can be locked on a step or recorded live. 32 lanes per track (was 8). A lane and an LFO on the same parameter now combine.

    Fixed: things that lost work

    • Clear + knob could delete the whole clip on a knob with no parameter.
    • Reopening a Set could show no automation, although it was still on disk.
    • A copied Set lost its instruments.
    • Send FX are now saved with the Set.
    • Play presses, step toggles and undos were silently dropped under load (15 of 24 Play presses in one device test). They're now retried until they land.
    • A chain synth could crash the whole device at load (e.g. breakbeat) and boot-loop it.
    • Saves are now written atomically by the engine, so a crash or a busy moment can't leave a half-written Set or reset a chain's patch to defaults.

    Fixed: playing and editing

    • Turning a knob on a held step no longer changes the live sound.
    • Knob turns on Schwung pages are undoable. Step copy now carries probability, condition and invert.
    • Arps and generators on an idle track no longer lose notes.
    • Knob 1 on a module-drawn preset page edits the module's knob again. Knobs on the Schwung MIX page turn at Movy's MIX rate (they needed 8Γ— the movement).
    • Choosing NONE in the module browser goes back to the chain view. A module loaded into an empty master FX slot shows its parameters. The capture overlay no longer goes missing.
    • Loading mrdrums into a Movy chain no longer closes Movy.

    Changed

    • The CPU settings are gone. Parallel render, idle skip and send co-location are always on at the settings that measured best.
    • Newly routed LFOs start at 100% depth (they used to start at 0%, running but silent).
    • Modules can declare read-only meters, action buttons and short labels, and Movy now uses them.

    Full details: CHANGELOG.md

  4. v0.34.0v0.34.0 β€” Master-FX workaround retired1 file

    Changed

    Movy no longer reaches into Schwung to keep the master FX chain saved.

    Movy loads a master FX slot by writing to the shim directly, and Schwung used to persist that chain from a JS mirror which never saw the write β€” so the save wrote {} over a slot that was genuinely loaded, and the whole master chain was gone on the next boot. Movy worked around it by reaching into Schwung's published context and repairing the mirror itself.

    Schwung fixed this upstream in 1.1.0: the saver now asks the shim what each position holds, prefers the path the shim actually loaded, and keeps the existing file rather than writing an incomplete one. The workaround is removed.

    It was not merely redundant. Repairing the mirror meant re-reading all four slots, and a failed read there is indistinguishable from an empty slot β€” so a read that did not answer could blank a position Movy had never touched. The window where that mattered is a Schwung update, when the shim and the UI can briefly be at different versions and reads fail for that reason alone.

    Requires Schwung 1.1.0 or newer

    Older hosts do not have the fix and are no longer offered the module.

    Notes

    Engine unchanged since v0.32.0 β€” ENGINE_VERSION stays 0.70.0. If you update from the store, restart your Move: the previous engine is still loaded until you do.

    Everything in v0.33.0 (Set backups, the restore menu, the update/failure screens) is included β€” see its notes if you are coming from v0.32.0.

  5. v0.33.0v0.33.0 β€” Set backups1 file

    Highlights

    Every Set now keeps its own history β€” up to 32 older versions, restorable on the device.

    Movy already kept two rotating copies of a Set's sequence, but that is a torn-write guard and nothing more: both are rewritten every few seconds, so they survive a crash and not a decision. Undoing an afternoon was not something you could do.

    Backups

    A version is kept when a Set is opened β€” a snapshot of what was on disk before Movy can write anything β€” roughly every ten minutes of work, when the Set is left, and always before anything that would wipe it. They are thinned on a ladder rather than a queue: the last hour in detail, today by the hour, this week by the day, then by the week, so a long session cannot push out the milestone you actually want.

    Settings β†’ BACKUPS lists them by age, reason and clip count β€” the clip count is the quickest way to spot the one that still has your work. Jog scrolls, Shift + jog jumps eight at a time, and a jog click asks to confirm before restoring. Restoring is itself undoable, since the state you had is kept first.

    Confirm

    Schwung's own four track slots are not included and the confirm says so: those live in Move's Set file, which Movy cannot write.

    Backups belong to the Set β€” deleting it in Move deletes its history with it, and a new Set on the same pad starts empty rather than inheriting the old one's versions.

    Sets made by earlier builds are adopted, not started from scratch. The first time one is opened, the copies already on disk are copied into its history, so the menu is useful immediately. Older adopted versions restore the sequence alone and leave your instruments untouched, marked * in the list.

    Fixed

    • "Movy was updated β€” restart your Move", instead of a dead end. Movy's sequencer engine is loaded once per boot and cannot be swapped while the Move is running: the shim opens dsp.so by path, and the system goes on serving the copy already loaded there. So straight after an update from the module store, the previous engine is still the one running, the new Movy will not run against it, and every Set failed to open behind ENGINE DID NOT START. Movy now tells the two apart β€” an engine that answered with another version is an update needing a restart, and says so.

      Restart

    • A failure screen no longer offers to wipe a Set it cannot fix. The JOG CLICK = START EMPTY recovery answers exactly one failure: this Set's own file will not parse. It was offered for engine failures too, where the Set is intact and blanking it cannot possibly help β€” so a jog click meant as "get me past this screen" overwrote a good Set with an empty one. The engine screen now offers no jog click at all.

    • scripts/recover-sets.mjs puts back a Set blanked that way. The save before it is usually still in one of the rotating shadow copies; run it with Movy closed and before reopening the Set.

    Notes

    Engine unchanged since v0.32.0 β€” ENGINE_VERSION stays 0.70.0. If you update from the store, restart your Move: the previous engine is still loaded until you do, and this release now says so instead of failing behind ENGINE DID NOT START.

  6. v0.32.0v0.32.0 β€” Send FX, the MIX page, and co-located send render1 file

    Three send FX buses, a MIX page on every track, and a render change that makes a send cost no more than the same effect on the track itself.

    The MIX page

    Send FX

    The master chain gains three send slots at its head, hosted by Movy itself. Load one audio FX into each, then feed it from any Movy track's new MIX page. The sends are post-fader and post-pan β€” pulling a track down takes its reverb with it β€” and their output joins Movy's own, which the master FX then process.

    A send FX slot

    One reverb shared by eight tracks costs a fraction of eight reverbs. Sends still only make sense for wet effects: an insert-shaped one (distortion, compression, EQ) belongs on the track, because of what it does to the sound rather than what it costs.

    Sends render beside their tracks

    A send bus used to wait for every chain at the join and then run alone on the audio thread with both helper lanes idle β€” so below about four tracks it cost MORE than putting the effect on the track, which is not something anyone should have to know. Movy now renders a bus on the same lane as the tracks feeding it whenever that shortens the block.

    Measured on twelve chains with one heavy delay: 244-299 Β΅s back, 8-10% of the audio frame, at one, two and three feeding tracks. A send now costs within 10 Β΅s of the same effect inserted on the track.

    Nothing is delayed to achieve it. A lane runs its work in order, so the bus still sees the complete sum at the same point in musical time. chcolo on the Flags page (Send On Lane) turns it off.

    Heavy buses also no longer wait on each other β€” they go onto the same helper threads Movy already renders its chains across. Against a 2902 Β΅s block: two heavy reverbs, 627 Β΅s down to 421 Β΅s; three, 847 Β΅s down to 423 Β΅s.

    With no send module loaded and every track at zero, the added per-block work is a handful of branches: an untouched bus is never accumulated into, never processed, and never cleared.

    A MIX page per track

    The last slot in every chain: VOL and PAN on the top row, SEND1, SEND2 and SEND3 together on the bottom under encoders 5-7. Pan and mute existed in Movy's mixer from the start with no control surface; this is it. All five automate like any module parameter, and one turn of a knob undoes as a single gesture.

    Two held MIX knobs

    VOL is drawn as a fader and PAN as a bipolar bar β€” vertical and horizontal versions of the same picture β€” so the page reads as a channel strip rather than as five identical dials.

    On a Schwung-hosted track only VOL is shown. That track's audio never passes through Movy, so there is nothing to pan or to tap for a send β€” turn on Movy tracks for the full page on tracks 1-4.

    The CPU meter shows the send buses

    The CPU meter with send columns

    Once any send bus holds an effect, three more columns appear at the right of the plot under SND, one per bus, on the same scale as the tracks β€” so a reverb can be read against the instruments feeding it. A send column is checkered all the way up: a bus is an effect pass, with no instrument stage to draw. A bus nothing is feeding, and whose tail has died away, drops to a dash and keeps its peak line.

    Pan knobs are drawn as a bipolar bar

    Pan dials

    A stereo position is the one common parameter whose default is the middle rather than an end, and on a 300-degree dial that middle is an unremarkable spot two thirds of the way round. Pan now gets a dotted rail, a centre tick and a bar filling out from the middle. It applies to the MIX page and to pan-like parameters inside modules β€” 24 across the dumped fleet.

    Knob labels use the whole cell

    Every parameter name was capped at five characters, which is the worst case of a proportional font β€” five M's fill a cell, but CUTOFF, ATTACK, OUTPUT and PRESET all fit inside one and were being cut anyway. The cap is now the cell's width in pixels, so 62 of the 80 dumped modules gained letters back and nothing got wider than it already was.

    Fixed

    • Hold-to-modulate offered an LFO on knobs no LFO could drive. Holding a knob on a send FX slot or on the MIX page raised the assign prompt, and clicking through wrote a target the chain host cannot route β€” so nothing modulated, and the write replaced whatever that LFO was legitimately driving.
    • The MIX page's knobs moved in whole-dB jumps β€” reported as "too sensitive". They now travel at exactly the rate a module's knob does and land on the round numbers.
    • A send's arc stopped four fifths of the way round. It was drawn against the fader's travel, which runs 12 dB past unity, while a send stops at unity.
    • Automated MIX parameters were stuck against one end. A MIX lane's 0-127 is now the fader's own position, in the engine and in the UI.
    • Only one held MIX knob showed its value. Every held knob shows its value now; the header still follows the one touched last.
    • Automation lanes on tracks 5-16 were never restored. The engine always emitted labels for all 16 tracks; the UI read only the first four β€” a leftover from when Movy had four. Automation was silently gone the next time the Set was opened.
    • The track-volume gesture would have zeroed every send. It rebuilt the rest of the mixer value from a fixed three fields; it now carries the remainder opaquely.

    Upgrade note

    Existing MIX automation will play back at different levels than it was recorded at β€” the lane's scale changed from linear amplitude to the fader's own position.

    Engine: ENGINE_VERSION 0.62.0 β†’ 0.70.0.

  7. v0.31.01 file

    Highlights

    • Scenes and Song mode. Hold Loop in Session view and the step row becomes eight scenes, each one launching a whole clip column across all 16 tracks β€” not just the four on screen. Keep holding and press more scenes to build a looping song: each scene plays for as long as its longest clip, pressing one twice holds it twice as long, and the arrangement is saved with the Set. An empty scene is the end of the song, so a song can play once instead of looping. Recording works through it β€” a take follows the arrangement into whichever clip ends up playing.

      Song mode

    • A CPU meter β€” Shift + Step 12. One column per track for what that track's chain costs per audio block, over a bar for the whole render, with the peak each has hit since you opened the page. It is calibrated against where the device actually glitches rather than against the raw block, so 100 % means "at the edge" β€” the page to open when a set starts crackling, to find which track is responsible before turning anything off.

      CPU meter

    • Drum machines with a page per voice. A kit whose voices are separate circuits β€” a 909's snare has no Attack, its kick has no sends β€” can now give each voice its own page and let the pad choose which one you are editing, the way the device's own editor does. The voice pages share one seat in the jog rotation, so an eight-voice kit is still four pages to walk rather than nineteen, and the voice you last hit is the one that comes back. Movy ships corrected configs for 6W6, 8W8, 9W9 and CW-78, so those four kits get the layout whether or not the module has been updated.

      A page per voice, selected by pad

    • The loading screen now covers the whole load. Opening a Set no longer drops you onto a live surface still showing the previous Set's module for a second or more.

    Added

    • Scene launching. Hold Loop in Session view and the step row becomes eight scenes, on the buttons printed 1, 3, 5 … 15. A scene launches a whole clip column across all 16 tracks, not just the four on screen β€” bar quantized like any clip launch, and a track whose slot in that column is empty stops, because a scene is a snapshot of what plays. Loop is a pure modifier there: Session view has no bars on the row to select, so holding it neither latches the Loop view nor resizes a loop you cannot see, and an unmodified press is still the track selector.

    • Song mode. Keep holding Loop and press more scenes to build a looping arrangement. Each scene lasts as long as its longest clip, rounded up to whole bars; pressing the same scene twice holds it twice as long without re-triggering it. The next scene is queued a full bar early, so it flashes in the grid the way a launched clip does, and every scene the song uses pulses on the step row. The bottom of the screen shows SONG and the scene numbers with the one playing boxed, scrolling when the arrangement outgrows the row. Launching any clip by hand ends the song, Play restarts it from the top, and it is saved with the Set.

      A scene with no clips on any track is the end of the song: there is no clip to take a length from, so the arrangement parks there, goes quiet and shows END on screen β€” while the transport keeps running, so you stay in time. Ending a song with an empty scene makes it play once instead of looping.

      Recording works through a song: a take follows the arrangement into whichever clip ends up playing. Punching in used to cancel a queued launch β€” correct for the stale queue a Session empty-slot selection leaves behind, but with a song running that queue is the arrangement, so it is now left alone.

    • CPU meter β€” Shift + Step 12. One column per track for what that track's chain costs per audio block, over a bar for the block as a whole. A solid bar is the instrument and a checkered one the effects after it; a dotted line above each column is the worst block that track has had since the page opened, and the notch above the capacity bar is the same thing for the whole render. Press the gesture again to clear the held peaks. Four states a column can be in, because a bar reading zero has four different reasons: costing something, loaded-but-silent (a dash β€” Movy is skipping it), empty, and Schwung-hosted (a dotted column β€” it renders outside Movy, which is not the same as being free). The scale starts at 1 ms β€” so a column means the same thing across sessions and across the CPU Optimize setting β€” and grows to fit when a track goes past it, rather than clamping the one column you opened the page to look at; the number under the plot says what full height is worth, and a row of dots marks 1 ms β€” at the top while the scale is at its floor, dropping as it grows. Costs nothing to run: the measurement was already on the audio thread, the numbers ride the status poll Movy already makes, and the page repaints only when a drawn pixel would change. The capacity bar is calibrated against the device rather than against the raw block: dropouts set in around 70 % of a 2.9 ms block, so that is what the bar calls 100 %. Under 80 % is comfortable, over 100 % you are hearing it. The step button under the gesture lights like the other page openers: dim while Shift is held, full bright while the page is up β€” and Settings (Shift + Step 2) now does the same, which it never did.

    • A page per voice for drum machines, selected by the pad. Until now a kit could re-point one page at whichever pad you last hit, which fits a machine whose voices share a control set and not one whose voices are separate circuits. A kit can now give each voice its own page and name the pad that selects it, and pressing a pad both sounds that voice and brings its page up β€” the same thing the device's own editor does.

      The voice pages share one seat in the page rotation, showing whichever voice the pad last selected, so a sixteen-voice kit is four pages to jog through rather than nineteen; jog away to Reverb and back and you return to the voice you were editing. A pad only turns the page when you are already on a voice page β€” from Master or Delay it re-points the voice slot and leaves you where you are, so reaching for a pad to hear an edit never costs your place. The pad-grid icon in the header says which voice is under the knobs, and is drawn on the chain page too, where you land after a slot change.

    • Movy ships corrected configs for 6W6, 8W8, 9W9 and CW-78. All four declare a pad on pages that have no voice behind them, which reads as "every bank is a voice" and collapses the whole module to a single page. Fixes for all four were offered upstream and closed unmerged, so Movy carries them and uses them instead of the file each module installs. The bundled copies carry no Movy-only field β€” each is a faithful copy of what the module should ship, and retiring one is deleting an id from a list. Every other module, including the ones that describe themselves correctly, is unaffected.

    Changed

    • The shifted step shortcuts now work in Session view. The CPU meter, Settings, Set params, the metronome, full velocity, double loop and clip quantization are global, and Session view is where you are most likely to reach for them β€” but the step row there is the 16-track selector, and Shift was not consulted, so the sixteen printed icons did nothing in the one view that shows all the tracks. Shift now reaches them; an unmodified press is still the track selector. Clip Params (Shift + Step 3) is unchanged β€” it edits one clip, so it stays Track-view only.

    • Movy's bundled kit configs are data files, not part of the bundle. They are read from disk for the module actually loaded, rather than compiled into ui.js and re-parsed on every tool open β€” 91 KB of JSON that only four modules ever need. ui.js is back to 535 KB from 627 KB, so opening Movy is quicker again.

    • A kit's Master bus is the last page, not the first. Reading the chain left to right β€” voices, their sends, then the bus everything lands on β€” is the order the rest of the fleet uses and the one a chain view implies. The modules' own editors still open on Master; that is their UI.

    Fixed

    • The loading screen now covers the whole load. Opening a Set β€” or opening Movy with one already selected β€” could put you on a live surface showing an empty module slot, or the module from the Set you just left, for a second or more before the real one appeared. The screen was only ever waiting on the engine: Movy's own knob page caches each slot's module and parameter layout behind a once-a-second poll, and nothing told it a Set had loaded, so the first frame after the splash was drawn from answers older than the Set. The page is now re-read before Movy goes live, and a change of loading stage repaints immediately instead of leaving the previous Set's page on screen.

    • A sleeping chain was still being charged for what it cost awake. The parallel render folded each chain's last measured cost in on every block, but a chain that chidle had put to sleep builds no render task at all and the pool never clears the previous reading β€” so its cost never decayed, and the render planner went on reserving a lane for a chain producing nothing. Silent chains now fold in a zero, which both fixes the partition and is what lets the CPU meter show them at rest.

    • An envelope drawn over something that is not one. Movy detects A/D/S/R from parameter keys as well as labels, and a key stem is not a promise: 9W9's bd_c_attack is a click level, so pairing it with bd_c_decay drew an envelope over a level and hoisted Attack to knob 1, disagreeing with the module's own editor about knob order. A module can now veto the grouping (viz: false), and Movy honours the one it already declares.

    Testing

    • The device suites run on both track hosts. Tracks 1-4 belong to whichever host chtracks names, and until now the fixture spoke only Schwung: it seeded Schwung's four shadow slots and the suites reached the instrument through them, so with the flag on MOVY six suites failed on assertions about a chain that was genuinely empty. The fixture now seeds both hosts β€” slots.txt for Schwung's slots, a chains array in ui-state.json for Movy's β€” and pins the host in prefs.json for the run, so it does not depend on what the active Move set happens to carry. Two sweeps: scripts/test-all-device-schwung.sh and scripts/test-all-device-movy.sh.

    • A Movy-hosted chain can be read back at all. New engine diagnostic chloadedlog logs what each chain HOLDS, read off the live instance. The remote-UI socket has no get verb, and the per-load line is not a substitute: a chain already holding the right module is deliberately left alone, so a second run against the same fixture logs no load and would read as a failure. (ENGINE_VERSION 0.55.0.)

    • A Movy chain is a fixed parameter state, not just the right module. set_chain_set leaves a chain that already holds the module alone, so only the first run got shipped defaults and every run after inherited the previous suite's knob turns. The fixture's ui-state.json is now rendered at install time (scripts/fixture-ui-state.mjs) with each component's preset blob taken from the same slot_<N>.json the Schwung half restores from β€” one declaration of the fixture's values, so the two hosts cannot drift apart.

    • The harness puts the track-host setting back. It pins chtracks for the length of a run, and that is a real user setting; a sweep used to leave the device on whichever host it had just tested.

    • test.sh stopped reporting a missing instrument as a pass. It keyed the module-config check on a phrase (config loaded for) that no longer exists anywhere in src/, so plaits β€” which HAS a bundled config β€” never matched, fell through to the "no synth loaded" branch, and printed green precisely where the fixture had failed to reach the track. Pinned by a new browser-test/device-scripts.mjs invariant.

    • The CPU meter's numbers are checked against the device. scripts/test-cpu.sh covers the two claims no host build can reach: that the per-chain figures are real, and that a chain going silent stops being charged for what it cost awake. New engine diagnostic cpulog (ENGINE_VERSION 0.62.0) β€” the remote-UI socket can write but not read, so a write that makes the engine log is the only way to see the meter from outside.

    • The kit override is verified on hardware, against the module's own config. scripts/test-voice-slot.sh asserts up front that the device is serving a different config from Movy's bundled one β€” without that the run would pass just as well on a device that happened to have Movy's copy installed, proving nothing about the override β€” and then drives a real pad note through Schwung's overtake ring into Movy's router.

    • The store tarball carries the override configs. build-module.sh packed module.json, ui.js and dsp.so only, so a fresh store install would have had no configs at all and the four kits would have silently fallen back to the layout the override exists to replace. Two guards, because the failure is invisible on either side of the tar: the override list and the shipping files must agree, and the finished tarball is read back and counted.

  8. v0.30.01 file

    Highlights

    • The CPU optimization is on out of the box. Movy now renders its chains across several threads and stops rendering the ones that are silent β€” together worth roughly 2Γ— on a heavy set, which is the difference between a full set playing and a set that crackles. It applies to Movy-hosted tracks only; a Schwung track renders exactly as it always did. If a module misbehaves under it, CPU OPTIMIZE on the Settings page (Shift + Step 2) turns the whole thing off in one switch β€” no other setting to find, no reinstall.

    • New sets put tracks 1-4 on Movy's own chains. Until now Movy's first four tracks were Schwung's slots and the other twelve were Movy's, so the same gesture could behave two different ways depending on which track you were on β€” and every feature had to be written, and debugged, twice. One host for all sixteen tracks means one code path: fewer bugs, and the ones that remain are far easier to find because every track behaves the same. It is also the only way those four tracks get the CPU optimization above, which exists only for Movy-hosted chains. Sets you already have are left exactly as they are β€” the change applies to sets Movy has never opened β€” and TRACKS 1-4 on the Settings page moves any set either way (with THIS SET for just the one you are in). Nothing is migrated when you switch: the Schwung slot keeps its module and simply stops being played.

    • A Set you make by pressing an empty pad now keeps its contents. Move only writes a Set to disk once Move itself has something to save in it, so a pad you filled entirely through Schwung and Movy was never a real Set β€” it stayed provisional, took a new identity on every visit, and neither Movy's sequence nor Schwung's instruments could be filed anywhere durable. Movy now makes Move commit the Set for it: it hands Move's own firmware a track-1 press, which is enough for Move to file the Set, and Movy's data is then saved alongside Move's. The one visible cost is that the pads may blink for a moment while this happens at startup β€” the gesture briefly belongs to Move. It is a workaround living in Movy today; the proper home for it is Schwung, which could also perform it without the blink, and that is the intent.

    • A lot of other bug fixes β€” set persistence, note routing, drum pads, automation, LEDs and the sequencer's current track. They are listed in full below.

    Added

    • Settings page β€” Shift + Step 2. Two settings now ship in the release build, on the page that used to be developer-only. CPU OPTIMIZE (ON) is one switch over Movy's whole render optimisation β€” multi-threaded chain render and skipping chains that are silent, together worth roughly 2Γ— on a heavy set β€” with OFF as a full serial fallback for a module that misbehaves off the audio thread. Everything under it (lane count, how aggressively silent chains sleep, duplicate pinning) stays hidden at the setting that measured best.

    • Tracks 1-4 default to Movy's own chains in new sets. The same Settings page carries TRACKS 1-4: NEW SETS (the default), MOVY or SCHWUNG. On NEW SETS, a Set Movy has never opened starts with tracks 1-4 on Movy chains β€” joining the parallel lanes, worth ~20-25 % of the chain render β€” while a Set you already have keeps the Schwung slots it was built on. The choice is stored in the Set and travels with it, and a THIS SET row moves just the Set you are in. Nothing is migrated in either direction: the Schwung slot keeps its module and simply stops being played. A SCHWUNG track behaves exactly as it does without Movy, including staying outside the CPU optimization above.

      Flipping the setting releases whatever is sounding on those four tracks first, through the ports as they are at that moment β€” a note-off is routed by looking up the port at release time, so the other order strands the note on the host that played it.

      Movy now hosts sixteen chains, one per track, and ch<N> is track N. Chains for tracks 1-4 sit empty while those tracks are Schwung's. This renumbered the twelve existing chains (track 4 was chain 0, and is now chain 4) and needs no migration: Movy's saved state records a track, never a chain.

    • Every Settings row explains itself. A two-line band at the bottom of the page describes whichever row is selected β€” a name short enough to sit beside its value is never long enough to say what the setting does.

    • Sample Slicer is playable from Movy. The module publishes no UI hierarchy and never declares its sample parameter, so Movy could only show the handful of knobs its chain_params listed β€” with no way to choose a sample at all. It now has a Movy layout of its own: a Sample browser and a Scan Slices action on the Main page, a per-slice page (start/end/attack/decay/ pitch/gain/mode/loop) that follows the pad you last hit, an Advanced page (trigger mode, slice algorithm, playthrough, tempo sync, global envelope, reroll, detect BPM), and the module's 32 pads mapped to its 32 slices. Works with the module exactly as shipped.

    • Mute and solo reach all 16 tracks. toggleMute/toggleSolo carried a track > 3 ceiling from when movy had four tracks, so the existing gestures already pointed at tracks 5-16 and were dropped at the door β€” silently, because the toast, the log line and the LED all read a mirror that never moved. Everything underneath the guard (the muted array, the persisted blob, the engine's mute command) had been 16-wide since 578139b.

    • Mute + step: a 16-track mute map, in any view. Holding Mute turns the step row into a mute map β€” step 1 is track 1, step 16 is track 16, each in its track colour and dim while silenced β€” so a track two groups away is muted without scrolling the focused quartet first. Shift makes the same press a solo.

      The map outranks whatever the row was showing, for as long as the button is down: steps, Loop mode's bars, the step-record head, the Session track selector. Muting from the pads you are playing no longer costs a trip to Session view and back. A press the map consumed enters no note (and its release is consumed with it), while a step already held when Mute arrives still belongs to the step path. In Session view it works in every state where that row is a selector, including a held Note/Session peek: the press does not switch tracks, and muting inside a peek no longer latches the view.

    • Mute is visible wherever a track is drawn. A muted track takes its dim colour in the Session track selector and in its clip-grid cells, matching the cue its track button already carried. In the selector it composes with the focus pulse instead of replacing it (a muted focused track pulses to its dim colour), and clips keep their white playing/queued pulse, since a muted track is still running. The Mute button now lights bright whenever anything is muted or soloed β€” with 16 tracks and 4 visible track buttons, that is the only always-visible "something is silenced" cue.

    • Silent chains stop rendering (chidle, on by default). A movy chain used to cost CPU whenever it was loaded, playing or not β€” twelve idle helm chains cost ~2340 Β΅s against a ~2000 Β΅s frame budget, with nothing playing. schwung's shim has skipped silent host slots for years; movy's twelve now do the same, on the same chain module and with the same constants.

      Measured on device, twelve mixed chains loaded and nothing playing: 978 Β΅s β†’ 71 Β΅s per block, a 13.8Γ— reduction, with 10 of 12 chains asleep. The two that stayed awake were still ringing, which is the gate working.

      The synth and the FX sleep separately, because otherwise any FX that never settles β€” a long reverb, a delay with high feedback, a noise floor β€” would hold the expensive synth awake forever. A sleeping synth still hands its FX a block of silence, so tails decay normally, and FX that declare requires_continuous_processing (loopers, modulated delays) never sleep.

      Sleeping chains keep their LFOs moving (mod:tick), and are dropped from the parallel-render partition so lanes balance around what is actually sounding. chidle 0 restores the old single-call path byte for byte; chidle 1 is the split with nothing sleeping, which is what chdigest compares against 0.

    • Track + volume no longer needs Shift. Holding a track button and turning the volume encoder sets that track's level. Move's firmware takes that encoder for its own master-volume overlay and hands itself the screen while it is touched, so Movy could only keep its own slider visible while Shift was held. A Schwung build carrying the new suppress-master-volume capability lets an overtaking tool exclude Move for the duration of the gesture instead β€” the overlay then shows on its own, and Move's master volume is never touched. Older Schwung builds keep exactly today's behaviour, Shift and all.

    • The whole flag list, on the same page in debug builds. Under the two released settings, a debug build lists every runtime chain-render flag with its value: jog scrolls, knob 1 edits, and knob 1's LED brightness carries the value. Unlike the other two param pages this is a list rather than one knob per parameter, so it can grow past eight entries β€” which is how the two released settings came to sit on it.

      The flags are now persistent (prefs.json, machine-level like the quantize default) and have readable names. They used to reset on every engine load, which is right for a measurement instrument and wrong for a setting. They are re-applied on every engine boot, since a re-dlopened engine is a brand new one that has never heard of them.

      scripts/build-module.sh builds the store tarball with the gate off and asserts the substituted constant, so the page cannot reach a release by accident.

    Changed

    • Launching a clip in Session view selects its track. The knobs, screen, pads and step row all move to the clip you just started, instead of only the step row moving while the instrument stayed behind on another track.

    • The loading screen now waits for the whole Set. Movy used to declare itself ready the moment a Set's sequence was in the engine β€” but the Set's instruments are loaded one per audio callback after that, and the track-button press that makes Move file a new Set hands Move the pads for about a second. Both happened behind a UI that looked live, so a pad hit in that window could reach the wrong module or Move itself. The splash now stays up through both, naming what it is waiting on (LOADING MODULES β†’ PREPARING SET), and switching Sets shows it again. The wait is capped at ten seconds: a module that never loads no longer strands the instrument. The engine reports its load backlog as chpend on the status poll it already serves (ENGINE_VERSION 0.50.0).

    • Parallel chain render is on by default (chparallel). Movy's twelve chains render across three lanes instead of one at a time on the audio thread. Measured on device: 2.15Γ— on the twelve-chain obxd ramp, 2.0-2.2Γ— across the mid-weight fleet. What that buys is not a number but headroom β€” hera fails at one note per chain serially and passes at four in parallel, obxd and nusaw passed twelve chains only at reduced polyphony and now pass at four notes, and the nine-chain row that was past the shim's overrun threshold is comfortably under it. Tables in docs/track-performance.md Β§1-2.

      Light and idle sets pay nothing for it, which was checked before the default moved rather than assumed: an empty set costs 378 Β΅s serial against 380 Β΅s parallel, and twelve sleeping chains cost 445 Β΅s against 440 Β΅s. (The pool also no longer wakes its helpers when every helper lane is empty. That is worth ~0 Β΅s β€” it was measured against a build without it β€” and is kept only because doing nothing should not cost three thread wakes.)

      Turning it off is one switch β€” CPU OPTIMIZE on the Settings page β€” and chpin 1, on the debug flag list, remains the containment for a set that misbehaves.

    • Modules are assumed thread-safe under parallel chain render. Duplicated modules are no longer pinned to a single lane by default, so twelve tracks of one instrument can actually divide across lanes. Containment for a module that turns out to race is a blacklist (moduleBlacklist in prefs.json) that puts all its instances back together; chpin 1 still pins every duplicate as the blunt fallback. This is deliberately optimistic β€” nothing has been measured racing, but nothing has been proven safe either, and with chparallel now on by default that optimism is what every set runs under.

    Removed

    • Per-chain module isolation (chiso, chcanary, the .movy-iso trees). Giving each duplicate a private copy of its .so took MoveOriginal down with helm, inside the second dlopen, for a reason that was never established β€” and it did not reproduce outside MoveOriginal. The blacklist above replaces it.

    Fixed

    • A drum pad on a Movy-hosted track plays one voice again. Since the engine started answering live pads from the audio thread, it was pushed the melodic pad map whatever the track held β€” so on a drum track every press sounded twice: the drum note from the UI, plus the keyboard note the engine looked up, which on a rack numbered from note 36 is simply another pad (hit the kick, hear the kick and a cowbell). The pushed map now follows the rack when the track's synth is a drum module, the drum input path skips its own send when the engine already sounded the note β€” as the melodic path always did, which is also why the drum note-off was going out never β€” and Shift hands the pads back to the UI, since whether a shift-select sounds is the module's decision and the audio thread cannot see a held button. Affects every drum module on a Movy chain (8W8, Forge, Weird Dreams, MrDrums, KrautDrums, …).

    • A freshly loaded drum module shows which pad the step lane is editing. The focused pad opened on pad 1 and the sequencer lane followed it, but no pad on the grid lit white until you physically hit one β€” the rack looked like it had nothing selected while it was already being edited.

    • Modules can be added to the master chain again. Since tracks 1–4 could move onto Movy's own chains (Tracks 1-4 Host, which new Sets get by default), the master FX slots stopped loading anything: the master chain is not a track, and its master_fx: params are Schwung's own and merely ride on slot 0 as a carrier β€” so a port picked by track number sent them to Movy's engine as ch0:master_fx:…, a key nothing answers to. The load, the browser's read-back, undo of a master module swap, a master module's file parameters and the master LFOs were all affected. Every one of them now resolves its port from the component being edited, so a Schwung-owned component stays on a Schwung slot however that setting is resolved.

    • A redeployed engine is now the engine that runs. deploy.sh shipped dsp.so and left it to the version gate to hot-reload β€” but the shim opens the engine by path and the loader keeps serving the copy already loaded there until Move itself restarts, so the gate just re-asked for a load the shim answered with the old binary. The restart helper could not have saved it either: Move runs as root, so it was pkilling nothing and reporting success after a 60-second wait. Deploying a changed engine now restarts the stack (as root, verified by the process actually cycling) and fails loudly if it cannot. Costed a full session of a real fix looking dead on the device.

    • Full velocity now actually applies. Shift + Step 10 left pad notes coming out at whatever you hit the pad with, so the pads stayed as dynamic as ever with the toggle on. Movy applied the 127 where the UI sends a pad note β€” but on a movy-chain track it sends none: the engine answers pads itself on the audio thread and built the note-on from the raw hardware velocity. So the setting worked on schwung host tracks and was inert on every other track. The engine is now told the setting (padvel, pushed beside the pad map by the same comparison, so a reloaded engine is told again), and the velocity is decided in the one place the note is built.

    • Reopening a Set no longer resets its instruments to factory defaults. A chain's module was restored, but its sound was not: the filter you closed came back open, oscillator octaves reverted, LFO assignments and track levels were gone. Movy sent the set document β€” which is what starts the module loads β€” and then immediately wrote each track's preset blob, LFOs and mixer level on a channel the shim services on the audio thread. That thread was by then inside a blocking module load (measured at 428 ms for OB-Xd), so all four writes hit their 100 ms timeout and were dropped. Worse, the next save then read those defaults back out of the live chain and wrote them over the patch in the Set file, so the sound was not merely un-restored but lost. The payload is now delivered once the loads have drained and the channel is free, retried until it lands, and the Set is not playable until it does. A capture taken before it lands returns what the Set file already holds, so a failed restore can never overwrite a patch.

    • A take no longer records onto the wrong track. Movy opens on the track Move had selected, but only the screen, pads and knobs were told: the sequencer went on editing track 1. Open Movy on track 2, pick a module, step-record β€” and the notes you played and heard on track 2 were written into track 1's clip. There is now one current track rather than two kept in step by hand: the step row follows the selected track, and the engine is told by comparing what it reports against that, once a tick. So it also survives the cases a list of gestures could never cover β€” the sequencer engine keeps running after Movy is closed, and is replaced outright if it ever has to be reloaded, and either one used to leave the step row on a track of its own.

    • Full velocity is remembered. Shift + Step 10 β€” every pad note leaves at 127 β€” was forgotten the moment you closed Movy, so anyone who plays that way had to switch it back on at every open. It is now a preference of the Move rather than of one piece of music: it holds across sets and across restarts, alongside the default quantize setting.

    • A Movy track's volume is saved with the Set. The level for tracks Movy hosts itself lives in Movy's own summing mixer, and nothing wrote it down: it came back at 100% after a power cycle, and β€” never being cleared either β€” the level you set in one Set went on attenuating whatever the next Set loaded into that chain. It now travels in the set file beside the chain's modules, and a new Set starts every chain at unity.

      Two things went with it. The mixer state had no reader in the engine (ch<N>:mix was forwarded to the chain host, which has no such key), so every read answered "absent" and the gesture restarted at unity each time you took it β€” turning a quiet track down put it back to 0 dB first. And undo recorded its inverse as a bare gain rather than the whole gain,pan,muted triple the engine parses, so undoing a Movy track's volume change silently did nothing.

    • Modules no longer go missing from a set. A set could come back from a power cycle with its sequences intact but half its tracks empty, and it got worse every time the set was opened β€” one real case went from eleven chains to eight to five. The chains crossed to the engine as one blocking param write per component, and the shim services those writes on the audio thread, which a cold module load holds for 78-276 ms; a write issued during that window could not be serviced and was thrown away by a caller that never checked. The next autosave then read back what had survived and wrote the smaller set to disk. The whole chain set now travels as one acknowledged document that is retried when it is refused, and the engine reports what was requested rather than what has finished loading β€” so a save taken while modules are still loading can no longer shrink the set. Engine 0.48.0 β†’ 0.49.0.

    • Modules whose UI lives in their module.json showed the wrong params β€” or none of the interesting ones. Schwung serves a synth slot's ui_hierarchy from the plugin binary alone (only FX and MIDI FX slots get the manifest's, cached by the chain host), so a module that describes its knobs in module.json and not in its DSP arrived at movy with nothing and fell back to whatever chain_params happened to list. Sample Slicer is the visible case: its sample browser is declared only in the manifest, so from movy there was no way to choose a sample at all. Movy now reads the module's own module.json when the host serves no hierarchy β€” the same file schwung parses for the slot's param table β€” and a filepath param declared there keeps its root, filter and start_path instead of falling back to an unfiltered /data/UserData.

    • Recording a new clip while the beat played started it mid-bar. With the transport already running, pressing Rec to record into an empty slot began capturing on the spot, planting the new clip's first tick wherever in the bar the press landed β€” so the take was permanently out of phase with the metronome and with every clip already playing, which became obvious as soon as you stopped and hit play again. A first take is now bar-quantized like any other clip launch: the Rec button flashes until the next bar and capture begins there, on the grid. Tapping Rec again while it flashes cancels. Overdubbing a clip that is already running is unchanged β€” it punches in immediately, since it is already in phase. Engine 0.48.0.

    • Play with LINK on made the transport blink instead of play. Pressing Movy's Play button with the Move link on left the Play button and the step row flashing about eighteen times a second, forever, and neither Movy nor Move started; Play on Move's side still worked. Movy asks Move to start by handing it a Play press through the host's inject queue, and Schwung 2026-07-29 (which taught its test bus to drive overtaking tools) began draining that queue back onto the tool's own surface while a takeover is live. Movy's press therefore came home as a press on Movy: it toggled the transport, which sent another press, which came home again. Schwung's own fix (2026-08-27) gives an overtake DSP a queue that really does reach Move, plus a sentinel to recognise it by β€” Movy now reads that sentinel on every engine boot and, when the host cannot carry a press, keeps the Movyβ†’Move half of the link switched off rather than talking to itself. Play/Stop in Move still drives Movy on every build, as do the tempo lock and EXT. ENGINE_VERSION 0.46.0 β†’ 0.47.0.

    • Every pad going black after returning from background. The LED layer has a per-frame send budget because the MIDI output buffer holds ~64 packets and drops the overflow silently β€” but only the cached step/button layer respected it. The pad painters and the knob rings wrote straight to setLED, so a cold repaint (32 pads + the step row + eight knob rings) reached 74 packets in one tick. The overflow never arrived, and because each painter had already recorded the colour in its own cache, nothing ever repainted it. Movy's log insisted it had painted all 32 pads, and the hardware was dark. Every LED writer now shares one budget and leaves its cache untouched when it cannot send, so the write simply retries on the next tick.

    • A deleted Set no longer comes back. Switching to a Set that had no Movy sequence of its own was treated as the current Set being renamed, so the work in hand was carried into it β€” delete a Set in Move and the Set Move made in its place inherited the deleted one's sequence. The rename now applies only to the one transition it was written for: Schwung's provisional id being replaced by the real one Move finally materialises. Every other change of Set is a switch, and a Set with no sequence starts empty, the same way Schwung seeds an unseen Set with empty slots.

    • Movy asks Move to commit a Set it never wrote to disk. Move only materialises a Set once Move itself has something to save in it, so a pad played entirely through Schwung never becomes a real Set β€” and both Movy's sequence and Schwung's instruments are then filed under an id that changes on every visit. Movy now sends the gesture that commits it: a held track-button press, while parked, once per Set. It has to be while parked β€” Schwung's inject drain refuses to feed Move's MIDI_IN whenever a tool is overtaking β€” and it has to be held, since a momentary press does not register.

    • Switching between two Sets Move never materialised no longer drags the first one along. Both such pads are addressed by a provisional id, and any change of identity away from one was treated as that Set arriving under its real name β€” so the sequence and the modules followed you onto the next pad and were saved there. Schwung mints one provisional id per pad and does not re-mint while you sit on one, so a changed provisional id always means you moved. The rename is now the single transition it was written for: a provisional id being replaced by a real one.

    • A set switch unloads the modules the incoming Set does not want. Schwung clears every slot on a set change before loading the new Set's; Movy only ever loaded. So a module on a Movy-hosted track outlived every switch, followed you into a Set that had never held it, and was written into that Set's state on the next autosave. A component both Sets want at the same module is left alone rather than torn down and re-opened.

    • Sequences for deleted Sets are collected. Deleting a Set in Move left Movy's state for it on the device permanently. It is now removed on load β€” only for Sets Move no longer has, and only when Move's own Set directory can be read, so an unreadable one never reads as "everything was deleted". A Set parked on another Schwung set page counts as alive: switching pages moves whole Sets out of UserLibrary/Sets/, so checking there alone would read every Set on every other page as deleted.

    • Mute no longer strands another button's momentary. momentary.ts holds exactly one active button, and Mute's press took it for a restore closure that was a no-op. Holding Note/Session and then pressing Mute therefore evicted Session's own momentary, so its release found nothing to restore and the peek silently latched into Session view. Mute now keeps its "was a gesture used while held" flag on its own β€” the only thing it ever read back.

    • chtracks reaches the engine, not just the UI. The flag was marked UI-only, but drain_out is what decides whether a sequenced note goes out as MIDI or into a chain: every sequenced note kept going to schwung while the UI looked entirely switched over. The engine also mapped a track to track - HOST_TRACKS while the UI had moved to ch<N> = track N, so track 4's notes were sequenced into track 0's synth β€” wrong instrument, no error, nothing in any log. Both are now one function (chain_for) with tests. (ENGINE 0.46.0.)

    • chtracks re-points the param pages. A model captures the port it was built with, so re-pointing the registry left every page reading the host the track had just left until movy was restarted β€” the flip looked like it did nothing. The four moved tracks' models are now rebuilt on the flip; the twelve that did not move keep theirs.

    • Master FX no longer follows track 0 onto a chain. master_fx: keys are global to schwung and only ride on a slot number as a carrier, and that carrier has always been slot 0 β€” which chtracks can turn into a movy chain, namespacing those keys ch0:master_fx:… and sending the master chain's edits into a synth. The master models now take a hostPort, whatever chtracks says track 0 is.

    Engine

    0.35.0 β†’ 0.54.0, nineteen steps, most of them load-bearing for the entries above:

    • 0.36.0–0.45.0 β€” the render work. Parallel chain render (the pool, the two-stage tasks, the serial/parallel equivalence oracle, unpinned duplicates) and then the idle skip that stops rendering silent chains, with the module copying that was tried between them removed again.
    • 0.46.0 β€” chain_for. One function decides whether a sequenced note leaves as MIDI or enters a chain, so chtracks reaches the routing rather than only the UI.
    • 0.47.0 β€” the play-link sentinel, which tells Movy whether the host's inject queue really reaches Move or comes home to Movy itself.
    • 0.48.0 β€” a first take is bar-quantized instead of starting under the press.
    • 0.49.0 β€” the chain set as one acknowledged document, reporting what was requested rather than what has finished loading.
    • 0.50.0 β€” chpend, the load backlog the loading screen waits on.
    • 0.51.0 β€” ch<N>:mix is readable, so a track's level survives being read back and undone.
    • 0.54.0 β€” padvel, the full-velocity setting, decided where the engine builds the note.

    Every step also bumps the number for its own sake: the ping handshake is what stops a stale dsp.so from passing as current after a redeploy.

  9. v0.29.0v0.29.0 β€” bank selectors, factory kits, master LFO1 file

    This one is about reaching the content that was already on your Move β€” the banks, the soundfonts, the factory drum kits β€” without leaving the knob page.

    Bank / soundfont / model selectors

    Dexed's .syx banks, OB-Xd's .fxb banks, SF2's soundfonts, NAM's models and cabinets, Midiverb's units: these were declared by the modules all along, but they carried no knobs, so Movy drew nothing for them and the collections were simply unreachable. Each now gets one cell immediately left of the preset knob.

    Bank selector cell

    Touch it and the list opens; only the release loads, so scrolling through banks doesn't load one per detent. Undo restores the module as it was.

    Bank list overlay

    It is generic, not per-module: a level qualifies when its selection reads back as one of the listed indices, which is what keeps the write-only commands sharing the same declaration (Surge and CLAP's category jump, MiniJV's save-to-slot) out.

    Move's factory drum kits from the knob overlay

    MrDrums' preset knob now opens in /data/CoreLibrary/Track Presets/Drums/… and accepts .json alongside .ablpreset β€” Move's own kits ship as .json and were invisible to Movy until now. The per-pad sample knob likewise starts in the factory drum samples and takes .aif/.aiff. Both can still walk out to your own UserLibrary content.

    And every file knob reopens where you left it: the folder you last loaded from is remembered per parameter, machine-wide, and outranks the module's configured start folder β€” browse to your own kits once and that is the default from then on, in any set.

    The master chain has its own LFO page

    Schwung's shim has had two master LFOs all along; Movy simply had no page for them. There is now a fifth slot after MFX 4, working exactly like a track's LFO page β€” waveform preview, tempo sync, hold-a-knob to assign β€” targeting the four master FX slots. Schwung saves them with the Set.

    Master LFO page


    Also in this release

    • The Leave-Movy menu no longer holds the instrument hostage. Pads, steps, Track, Session, Clear, Capture, octave and the arrows all dismiss it and do their job β€” using the instrument answers the question by walking away. Shift and the transport act without dismissing; the param knobs stay swallowed, since the menu covers the screen.
    • A two-state switch flips on one detent in the direction you turned, both ways β€” it used to take two detents on and three off. Thanks @athousanddetails (#14).
    • An LFO assigned on a Movy-hosted track (5-16) did nothing β€” the write went through Schwung's slot-addressed param API, which refuses any index past slot 3 and silently wrote nothing. Same bug dropped an LFO restore on undo.
    • Master FX loaded from Movy survived a power cycle at last: Schwung saves the master chain from a JavaScript mirror that never saw Movy's load, and the next save wrote {} over a slot that genuinely had a module in it. Movy now resyncs that mirror (a temporary workaround β€” the real fix belongs upstream).
    • Back no longer cycles forever between the Set and Clip parameter pages.
    • A Movy-hosted track's LFO settings are saved with the Set. They live in the chain instance rather than a preset blob, so an assignment used to survive only until the tool closed.
    • Store installs unpacked dsp.so into a GNUSparseFile.0/ folder on BusyBox β€” the release tarball is plain ustar now. If v0.28.x never brought its sequencer engine up for you, this is why.
    • Engine 0.35.0 β€” adds chlfolog, a diagnostic that logs a chain's LFO assignments with the live value of each driven param, so a device test can prove modulation is actually moving. Audio and render paths untouched.

    πŸ“– Manual Β· πŸ“œ Full changelog

  10. v0.28.2v0.28.2 β€” bug fixes1 file

    A bug-fix release addressing track octave setup on tracks 5-16, set lifecycle durability, and input gating during boot.

    Update from the Schwung manager as usual.

    Tracks 5-16 keyboard bug fix

    Startup handed the keyboard a four-entry octave table while Movy had sixteen tracks, causing tracks past track 4 to have no octave and play MIDI note 0. The octave table is now correctly sized across all sixteen tracks.

    Set Lifecycle & Input Gate

    Movy now manages the Set lifecycle through a single owner (set-session.ts). Surface inputs are guarded while the engine is booting or loading the active Set, preventing presses from queuing into uninitialized state. In-progress work seamlessly follows provisional Set renames without being overwritten.

    Loading & Corrupted State Recovery

    Provides clear visual status during startup (STARTING ENGINE / LOADING SET) and an explicit recovery modal (CANNOT LOAD THIS SET / JOG CLICK = START EMPTY) if a state file on disk is unreadable.

    Track Colors

    Rebuilt track color mappings against Schwung's palette.


    Verified: all six local test suites, 261 seq-core + 48 movy-dsp Rust tests passing.

  11. v0.28.1v0.28.1 β€” bug fixes1 file

    A bug-fix release. Everything here is a fault that predates the 16-track release β€” the reports simply landed together after it, so a couple were filed as regressions and are not.

    Update from the Schwung manager as usual.

    A pattern started on a brand-new Set is no longer thrown away

    Move calls a Set __pending-… until it saves it, and Movy correctly refuses to treat that as a Set β€” but the pads, steps and transport all work meanwhile, so a whole pattern could exist before Movy learned which Set it was in. Resolving the Set then pushed that Set's state β€” nonexistent, so blank β€” straight on top of the pattern.

    That is one bug wearing four faces: the sequence vanished mid-session, the clip went back to zero steps, Play ran an empty clip so nothing moved, and reopening Movy showed nothing at all.

    Movy now adopts what the engine is already holding, because that pattern belongs to the Set it just learned about. A Set that has state of its own still restores it. (#4, #5)

    The keyboard settings chosen before that survive too

    The same window reset scale, mode, layout and per-track octaves back to chromatic/4th the moment the Set resolved β€” and picking In Key + Inline is exactly what you do before playing the first note. (#6)

    The knob lock-up

    Three ordinary presses β€” Shift+Step 5 for Main Params, Note/Session, then a jog click β€” left the Main Params page latched "open" behind whatever took the screen. That page is asked first when a knob turns, so from then on every knob anywhere fed the tempo invisibly, and step length, tempo and module params were all dead until Movy was closed and reopened.

    A page being open is now the same fact as being on screen rather than a second flag kept in sync by hand, so the two cannot come apart. (#7)

    A 15-step pattern stays 15

    Pressing a step in the invisible remainder of the bar β€” the part a sub-bar clip length hides β€” placed a note there, and the clip was rounded up to the bar end. The rule now lives in the clip itself rather than in a mirror that is briefly stale after opening Movy or switching tracks. Tapping a later bar still grows a clip, an empty slot still takes its first note anywhere, and a note already past the length can still be cleared. (#5)

    Engine

    ENGINE_VERSION 0.33.0 β†’ 0.34.0, so the DSP reloads on update.


    Thanks to @NeuroNoNeuro for the reports that made all of this findable β€” the new-Set repro in particular is what cracked it.

    Verified: all six local suites, 261 seq-core + 48 movy-dsp tests, and all nine device suites on hardware.

  12. v0.28.0v0.28.0 β€” 16 tracks1 file

    Here is my experiment to give everybody 16 Schwung tracks.

    The UI is very straightforward and backward compatible. Tracks 1-4 are the same four Schwung tracks as always β€” I hope I didn't break anything there, but let me know if that's not true. Tracks 5-16 are chains Movy hosts itself: load a module onto one exactly as you would on a track button track.

    • In Session view, switch between the 4 groups of 4 tracks by pressing octave up / down.
    • You can select any of the 16 tracks by pressing a step pad in Session view.
    • Shortcut: hold the Session button and press a step β€” works from anywhere, and the row stays a track selector while you hold, so you can keep tapping through tracks and auditioning them.

    Obviously, in most cases Move runs out of CPU already at 6-7 tracks filled with synth modules and notes. But if you are very accurate with module selection, you can go with all 16 tracks filled with dexed (the most CPU-efficient synth we have) playing 2-4 notes each, and half of the tracks with mverb enabled. I am curious how far you can push the Move hardware with that β€” but prepare for some level of disappointment πŸ™‚

    As I always state, this is still a very early prototype, and that is especially true of this change:

    • ⚠️ track + volume doesn't work on the new tracks. Use Shift + track + volume for tracks 5-16.
    • Tracks 5-16 only sound while Movy is open (or parked in Background mode). Tracks 1-4 are Schwung's and keep playing under Move's own UI.

    Per-synth CPU numbers, measured on device, are in docs/chain-cpu-benchmarks.md.


    Also in this release

    • Track colours are eight in a Latin square β€” a colour reappears in another group but never where you can see both at once. Picked by hue separation after a 16-colour table shipped pairs that measured far apart and looked identical on the hardware.
    • The pad path got faster. Live pads are answered by the engine on the audio thread, port reads are batched, and a knob page no longer pays a round trip per tick β€” tick rate 110 β†’ ~145 Hz on a Movy-hosted track, which is also the pad sampling interval.
    • Engine 0.33.0 β€” NUM_TRACKS 4 β†’ 16, private chain hosting with a serialised load queue that never blocks the audio callback, a saturating summing mixer, and per-chain state saved with the set.

    πŸ“– Manual Β· πŸ“œ Full changelog

  13. v0.27.0v0.27.0 β€” parameter visualisations1 file

    Added β€” parameter visualisations

    Movy already drew filter curves, envelopes and LFO shapes. This release extends that to the rest of a synth's page: a control now shows what it is in its own cell, so a page can be read at a glance instead of one five-character label at a time. Everything below is detected from what the module reports β€” no per-module configuration, and a control that does not genuinely qualify keeps its knob.

    • Waveform selectors draw their wave. A knob that picks an oscillator or LFO shape draws the shape itself β€” sine, triangle, saw, square, pulse, ring, wavetable, noise β€” rather than abbreviating its name. 22 selectors across the 79-module fleet qualify:

      Waveform knob cells

      The full-screen list shows the same glyph beside every name, so choosing a shape is a matter of recognising it:

      The waveform list

      Where a module's shapes are stepped β€” Helm's stepped ramp and pyramid β€” the drawing carries the level count, so a 4-step ramp reads as four steps and a 16-step one as a slope:

      Helm's stepped waveforms

      And where a module offers its waves as separate on/off switches instead of one selector (9 placements), each draws solid when on, dotted when off:

      Waveform on/off toggles

    • A sampler shows its sample. A playback position next to the file it plays draws them as one graphic: the waveform of the sample, with the position marked. The marker is inverted over the wave β€” a bright line through a quiet passage, a dark notch through a loud one β€” so it stays the highest-contrast thing on the line wherever it sits. The waveform is normalised to the full cell height, so a quiet recording is still readable:

      Sample waveform with position

      WAV (8/16/24-bit and float) and AIFF are both read, a block at a time in the background so a long sample fills in over a moment instead of stalling the knobs, and the result is kept until the file changes.

    • Loop points draw as brackets on the sample. A sampler's loop start and end are drawn where they fall in the file, so the loop is visible as a span rather than as two percentages:

      Loop brackets on the waveform

      Parameters a module says do not currently apply are now hidden rather than shown dead: switching the loop off takes its start/end away and the waveform takes back the room:

      The same page with the loop off

    • A granular sampler sprays visibly. Grain spray is drawn as the span of file it can reach around the play position, dotted at its edges β€” so you can see it widen, and see it saturate once it covers the whole sample:

      Granular spray across the sample

    • Band gains draw an EQ curve. Two or three band gains on a page (low / mid / high, or a module's own names such as Body and Air) are placed in frequency order and drawn as one response curve. The dotted line is 0 dB, so a cut reads as clearly as a boost β€” shelves at the ends, a bell in the middle. 5 modules qualify:

      EQ band curve

    • Low cut and high cut draw the band they leave. A cut pair on one page is drawn as the band that survives it, each corner following its own knob; a cut with no partner keeps its cell and shows just its corner, rising for a low cut and falling for a high cut. 8 pairs and 8 lone cuts across the fleet:

      Low and high cut curves

    • A lone attack or decay draws as a ramp. An envelope stage with no ADSR partners to group with is drawn as the ramp it is β€” a rise for an attack, a fall for a decay β€” instead of an arc that says nothing about its shape. 21 placements, Plaits' DECAY among them. An attack/decay pair on one line is drawn as the single shape it makes:

      Lone attack and decay stages A lone decay on Plaits' main page

    • Loudness knobs are faders. A control that sets a level β€” a volume, a gain, an oscillator or send level β€” is drawn as a fader: a filled bar between two dotted rails with a head at the value. It fills from the bottom in every case, so one glance across a page finds the levels. 140 parameters across 53 modules:

      Loudness knobs drawn as faders

      Only a genuine output level qualifies β€” an envelope's Level, a key-follow amount, a compressor Threshold and anything measured in dB-per-something keep their knobs, because each is an amount of something else.

    • Booleans are on/off switches. An on/off parameter is drawn as a switch, filled with its knob to the right when on and hollow with the knob to the left when off, so its state reads from across the desk. 222 parameters across 44 modules, plus Movy's own Retrigger and Play Link:

      Boolean knobs drawn as switches

    Several of these share a page routinely β€” here a drum pad's sample, its level, its attack and its mode, each drawn as what it is:

    Graphics sharing one page

    Changed

    • Loop-view bars breathe, together. The bar selector fades each bar in and out against black β€” green for the bar playing, white for the one you are viewing, the track colour for the rest of the loop, near-black for everything outside it β€” with every pulsing bar on one animation channel, so the row breathes as a single movement instead of drifting apart. The hardware does the fade, so it stays smooth and no longer freezes while the transport is stopped, which is precisely when you are reading these. An idle Loop view now sends no LED traffic at all, where the old blink sent the whole row four times a second. Whether a bar holds notes is deliberately no longer shown: in this view a bar's job is to tell you whether it plays.

    • Loop mode says which window you are editing. Entering it flashed Loop for a third of a second and then left nothing on screen. A band now stays up naming the window and the bar you are on β€” LOOP 3-4 BAR 3 β€” and follows you as you navigate.

      The Loop view

    • A handful of values no longer means a hair trigger. Any parameter with eight discrete values or fewer now takes four clicks per step β€” the rate Movy's enum knobs already used β€” so OB-Xd's five-position octave takes a deliberate turn instead of crossing its whole range in four clicks. 86 parameters across 23 modules are affected, Dexed's operator settings and OB-Xd's voice_count and legato among them. On/off switches are untouched and still flip on a single click. A turn that doesn't cross a step now writes nothing at all, so it costs no undo entry either.

    • Octave offsets and voice counts read as numbers, not arcs. 41 parameters across 24 modules β€” every octave/octave_transpose, Moog's four oscillator ranges, and every voice count from obxd voice_count to sfz voices β€” are drawn as a framed value, with a sign on the 26 that are offsets. An arc shows a position in a range, which is the wrong reading for a value you think of by name; the cell otherwise showed the parameter's name and revealed the number only while the knob was touched.

      Step cells

    Fixed

    • Two graphics side by side no longer merge into one. A curve or waveform that ran to the very edge of its cell touched whatever was drawn next to it, reading as a single shape. Every span graphic is now inset by the same amount on both sides β€” but only where it meets a neighbour, never against the screen edge, where there is nothing to separate from and the pixels were wasted:

      Graphics that meet, kept apart

    • A sample position no longer jumps to the start when you let go. A sampler's position is a fractional value the module reports as an integer type, so releasing the knob wrote back a rounded 0 and the sound snapped to the top of the file. Movy now keeps the value the knob is actually holding.

    • The browse hint appears on the chain page too. The prompt that tells you a long jog press opens the module browser only showed on parameter pages, which is not where you go looking to change a module.

    • Watching a parameter's visibility costs nothing. Deciding whether a control applies was polled from the host every tick, then on a throttle; it is now read from the value the ordinary refresh already caches, so it makes no host call at all. Sample peaks also survive a resize β€” toggling a loop used to widen the graphic and re-read the whole file, which is exactly when the stall was felt.

    • A loop set in the middle of a part now works at all. Pressing two bars to loop, say, bars 3–4 left every view wrong, because the engine stores an absolute window while the display read the loop's length as if it always started at bar 1. The step row went completely dark, hiding the notes and making step editing blind; the bar strip drew its segments on bars 1–2 and marked bar 3 β€” selected and playing β€” as an out-of-loop bar; the play line sat pinned to the right edge for the whole loop; the arrows could not reach the loop's own last bar but could wander below its first; and Loop view gave no indication of the window at all, so a bar holding leftover notes outside the loop looked exactly like one inside it.

      A loop on bars 3-4

    • Opening Movy shows the bars that play, not bar 1. On a fresh start the bar indicator led with the clip's first bars as inactive + markers whenever the loop began later β€” the view was parked on bar 1 while the engine looped, say, bars 3–5. The view now adopts the window as soon as the engine reports it, and again whenever that window moves under it (switching tracks, launching a clip, undo). Deliberately selecting a bar outside the loop in Loop view still sticks.

    • Setting a loop no longer strands the view outside it. Pressing two bars, or shrinking with Loop + jog, left the viewed bar where it was β€” which could be a bar that had just stopped playing, so edits went somewhere you could not hear. Navigating out of the loop deliberately still works, and the strip says so:

      Navigated past the loop

    • A bar double-tap is a consistent length now. The window counted engine ticks, and the tick rate moves with load (63–205 Hz observed), so the same double-tap was anywhere from 0.29 s to 0.95 s depending on what the UI was busy with. It is 450 ms of wall clock.

    • Knobs keep up on a big synth's busiest page. Turning a knob on Helm's Main page lagged the hardware badly, while the same page on OB-Xd was fine. Two causes, both paid on every tick: the app read drumPadCount and drumCurrentPad through getViewModel(), building the entire page β€” layout, envelope and filter graphics β€” and discarding all but one number, up to three times per tick and even on frames that did not repaint; and a filter pair with no mode enum on its own page re-scanned all of the module's parameters, with a regex per enum, to find one elsewhere. Both now use cheap accessors and a cache keyed on the loaded parameter list. On device, Helm's Main page went from 52 ms to 8 ms per tick and its tick rate from ~18 Hz to ~105 Hz; of 150 injected detents it registered 136 where it had registered 14. The tick period is also how often Movy samples knob MIDI, which is why the dropped detents felt like lag. Every module benefits β€” Helm was simply the one dense enough to show it.

    • An empty track no longer slows every knob down. Movy asks the host what module a track holds so the engine knows whether to transpose its clip. An empty slot never answers, so the question was asked again on the very next tick β€” forever. Each ask is a blocking round-trip the audio shim only services once per SPI frame (~2.7 ms), and the tick period is also how often Movy samples knob MIDI, so a set with three empty tracks spent most of every tick waiting and the knobs felt heavy. Unanswered slots are now retried on the same ~1 s cadence as the module-name poll, so a module loaded from outside Movy is still picked up. On a set with one empty track this cut host round-trips per tick from 2.6 to 1.7 and the tick period from 13.1 ms to ~11.9 ms; the effect grows with the number of empty tracks.

    • A negative value in a boxed cell no longer loses its minus sign. The box splits a label across two lines on _ and - so LOW_PASS reads LOW/PAS, but it applied that to numbers too: -3 drew as 3, identical to the positive 3 that Surge publishes as the very next octave option. Affected every enum with signed numeric options (Surge's four octave selectors, Essaim's v_octave, Eucalypso's and SuperArp's octave ranges) as well as the new octave cells.

    • Fixed three glyphs in the small font, checked against 5x3-font.otf: + sat two pixels above the digits beside it, = had the same slip, and 1 was mirrored β€” its flag pointed right off a left-hand stem, so 12 read oddly in cells like the filter's SLOPE. The normal and large fonts were already correct.

    • An automated parameter now shows its value before you touch it. A parameter with an automation lane, or one an LFO targets, deliberately stops reading its value back so the page keeps showing the base you set. But that base was only ever filled in by turning the knob, so a parameter automated before it was first touched had no value at all: its arc sat pinned at the bottom of its range (on an octave βˆ’3..3 that looks like a real βˆ’3), and assigning automation used the range minimum as the base rather than the real value. It is now read once, then left alone as before.

    • A knob now moves the same distance in both directions. On any parameter with discrete values and a range of 200 or less β€” which is most of them, 257 of the 464 in the surveyed module fleet, including every knob on OB-Xd's Global and Filter pages β€” one click clockwise moved the value by one while one click counter-clockwise moved it by nothing at all. The arc sensitivity scale put those parameters on a half-unit step, which the rounding that stores an integer resolved upward in one direction and swallowed in the other. Discrete parameters now step whole units, so counter-clockwise matches the clockwise feel that was already correct.

    • Turning a knob faster no longer covers less ground. The same fractional step also lost the remainder each time the host batched several clicks into one tick, so a quick sweep advanced roughly half as far as the same clicks made slowly β€” the "clockwise is smoother" half of the report above. A turn now moves in proportion to how far it is turned, at any speed.

  14. v0.26.0v0.26.0 β€” Undo, Capture, Step Recording, Non-destructive Quantization1 file

    Four features that change how a take gets made: undo & redo across every musical edit, capture for the phrase you just played, step recording on held Rec, and non-destructive quantization you can dial back at any time. Plus the swing-recording fix those exposed, and the end of the knob lock-up.

    Engine 0.28.0 β†’ 0.32.0.

    Undo & redo

    Undo takes back the last edit and Shift + Undo redoes it, with an overlay naming what changed. It covers every musical edit β€” notes and steps, clip and bar operations, automation, tempo/swing/root/key, mute and volume, synth and LFO parameters, LFO assignments, and module or preset loads β€” while leaving navigation, transport and keyboard layout alone.

    One gesture is one undo rather than one per detent: a whole knob turn is a single entry, and each pass of live recording is its own, so two recorded loops take two presses to remove. An edit that changes nothing costs no press.

    Undo toast Clip undo toast

    The engine keeps snapshots addressed by id, so no state crosses IPC; chain parameters record their inverse at the write; module swaps dump the outgoing module's parameters and replay them once it is back. History is in memory, 64 deep, per set.

    Capture

    Play freely, then press Capture to keep what you just played β€” Move's retroactive capture, notes only. Movy buffers live pad input whenever the track isn't recording. A capture while the transport runs lands the phrase where you heard it; one made while stopped reads a tempo off your playing, sizes the clip, and starts the transport so you hear the take at once.

    Three tempo candidates are offered on screen and the jog applies each as you pass it. When the tempo isn't ours to set β€” Move is clocking us, or the clip already has notes β€” the take is fitted to the existing tempo through whichever reading is closest, so the stretch is the smallest one available.

    Capture tempo selector Capture fitted

    The buffer holds what you were just playing: every deliberate edit, a track button, a transport edge, Session, two bars of silence and an 8-bar ceiling all clear it. Hold Clear and press Capture to throw it away by hand.

    Prior art: schwung-davebox shipped capture on Schwung first. Movy's implementation is its own β€” davebox is PolyForm Noncommercial and Movy is MIT, so no code is shared; the behaviour follows Move's manual (Β§14.3) and the tempo estimator was written and tuned here.

    Step recording

    Hold Rec while the transport is stopped and play the pads to enter notes step by step, on melodic and drum tracks. Notes that overlap land on the same step, so chords need no modifier; the head advances when the last pad lifts.

    Right leaves a rest or ties the held chord into the next step, Left steps back and plays what is there ready to be replaced, and a step button jumps the head. An empty clip grows to exactly what you play, rests included; an existing one wraps at its end and overwrites. Melodic entry replaces the step, drum entry only adds its own lane, so kit patterns can be built up one pass at a time. A quick tap of Rec still arms live recording.

    Step recording

    Non-destructive quantization

    Quantization is now a per-clip value from 0 to 100 %, applied as notes are emitted rather than by rewriting them, so recorded timing survives and can be dialled back at any time.

    Shift + Step 16 cycles the clip through 0 % / the set default / 100 % and shows the choices for 1.2 s, with the jog picking between them while the panel is up. QUANT on the Clip page sets one clip; QUANT on the Set page sets the default new clips are created with, and that default lives in a machine-level prefs.json, so it follows you into sets you have never opened.

    Quantize panel Clip QUANT

    Clips saved before this release load at 0 %, so existing sets play back exactly as they did. 100 % reproduces the old destructive behaviour exactly.

    Fixed

    • Recording or capturing against swing lost the upbeats. A note's step anchor was rounded against the straight grid, but swing moves an off-beat 16th later inside its own cell β€” so on swing 70 there were only ~21 ms of late tolerance at 120 BPM before an upbeat anchored to the following step and quantization snapped it onto the downbeat. Anchors now round against the swung grid, at live recording and at both capture paths, and are stored in the clip rather than re-derived on load.
    • A pad still held when recording stopped was erased. Ending a take on a sustained note discarded it outright rather than shortening it. Held notes now survive the stop, are written with the length they were actually played for, and sound on the very next loop.
    • A note played just before the count-in ended was lost. Notes within half a step of the start are now recorded on the downbeat, keeping their true length.
    • The knobs no longer lock up. A step-button release that never reached Movy left it believing the step was still held, which routed every knob turn into step automation instead of the parameter under it, forever. Releases are no longer swallowed, and pressing a step again recovers one the host dropped.
    • The sequencer no longer gives up on its engine for good. Three engine losses over a session added up to a permanent stop with every command silently dropped; the retry budget is now per outage.
    • Track volume is usable at quiet settings. One detent is now one dB across the whole range, and the slider reads out in dB as well as percent.
    • A note added to an occupied step joins what is already there rather than landing hard on the grid with a one-step length.

    Performance

    An enum's options were re-classified by the shape and filter detectors on every frame, so Surge's 274-option modulation pickers dragged the page behind the knob; the classification is cached on the parameter now β€” a 1024-option page rebuilds ~50Γ— faster. Opening the file overlay on a big sample folder ran an os.stat per entry; 1024 entries went from 8.7 ms and 1024 syscalls to 0.17 ms and none.


    Full detail in CHANGELOG.md Β· MANUAL.md

  15. v0.25.0v0.25.0 β€” every parameter reachable, scales & pad layouts, LED ownership, durable saves1 file

    The largest release since Movy went into the store. The headline is complete parameter coverage β€” every parameter a module publishes is now reachable on a Movy page β€” alongside scales and pad layouts for the melodic grid, full LED ownership under overtake, and a rewritten persistence layer that closes five ways a set could be silently lost.

    Engine 0.27.0 β†’ 0.28.0.

    Restart your Move after updating. The host reads a module's capabilities once per session and caches them, so the LED-ownership fix only takes effect after a restart. Everything else updates normally.

    Every parameter a module lists is now reachable

    A Schwung module gives each section two things: the eight parameters it binds to the encoders, and the full list of parameters that section contains. Movy rendered only the former, so anything past the eighth parameter existed only in Move's own module UI.

    Sections now continue onto - 2 / - 3 pages carrying the rest, and a section that binds no encoders at all gets a page instead of being skipped. That is 601 parameters across 43 modules β€” including Osirus's ROM and model selectors, OB-Xd's voice count and Surge's per-oscillator detail. Parameters a module reports as unturnable (one possible value) are left out rather than drawn as a dead knob.

    Overflow page Settings page

    Shift + jog wheel jumps between sections, skipping a section's overflow pages β€” paging one at a time is slow on a 20-page module. The page bar stays legible as the count climbs: separators hold at one pixel, the bar fills the full width, and pages group by section.

    Dense page bar

    Values on screen track the device faster. Read-back swept the whole parameter array one entry per tick, so a value's lag grew with the module's page count. It now interleaves the current page with that sweep β€” the same one read per tick, but what you are looking at converges in ~16 ticks instead of ~200 on a 25-page module.

    Asynchronous preset lists and option names are picked up. Osirus scans its ROM after loading and reports an empty preset list and a (loading) option set until it finishes; Movy read those once and kept the placeholder for the session. It now re-checks for a few seconds and redraws as soon as the real names arrive.

    Scales & pad layouts

    Two new Set-page knobs shape the melodic grid. MODE picks Chromatic or In Key β€” the grid folds to the scale, so every pad is in key. LAYOUT picks the geometry, and its options follow MODE: 4ths or Piano when chromatic, 4ths (three scale degrees per row, Push-style) or Inline (one scale octave per row) when in key.

    The piano layout puts the white keys on rows 1 and 3 with the black keys offset right above them; its gap pads are dark and silent, and out-of-key black keys take a dimmer tint so the keyboard shape reads.

    Mode Layout

    + / βˆ’ now shifts only the active track's octave. Each track keeps its own, saved with the set.

    Mute, solo and track volume

    • Solo β€” Shift + Mute solos the current track, Shift + Mute + track any other. Built on the engine's per-track mute, so it gates sequenced notes while live pads on a silenced track still sound.
    • Mute now responds to a deliberate press, not just a quick tap β€” any press of 500 ms or more previously did nothing.
    • Every mute or solo names the track (T2 MUTED) and, for solo, the resulting set (SOLO T1 T3, SOLO OFF).
    • Track volume β€” hold a track button and turn the volume encoder to set that track's Schwung slot volume (0–400%, unity marked). With Shift held Movy draws its own slider; without it Move shows its native overlay.

    Track volume

    Action knobs

    A parameter a module marks as a one-shot action is drawn as a circle in a box rather than a knob. Firing blinks the circle and flashes that knob's LED; while spent the box goes dashed and a bar drains along the top showing when it re-arms. Previously these rendered as an ordinary enum cell reading IDLE forever, with no sign that anything had happened.

    Armed Fired Cooling

    Movy owns the LEDs under overtake

    Movy declared a capability (skip_led_clear) dating from its first release, when it drew highlights on top of Move's clip colours. It has painted every pad itself for a long time, but the flag was still letting Move's LED writes reach the hardware β€” pads, step buttons, knob rings and RGB clip colours repainted underneath Movy and stuck, because Movy only sends colours that changed. It also made Movy's existing "suppress Move's RGB sysex" request a no-op.

    Movy now takes the LEDs outright, re-claims them after backgrounding (the host clears the suppression when a tool parks), and takes cable-0 sysex too β€” the channel Move's own sequencer used to leave stuck green step LEDs behind. Two visible consequences: opening Movy briefly shows Loading… while the surface is cleared (~330 ms), and leaving Movy hands Move its LEDs back.

    With those repaints gone, two force-write workarounds could go: knob-ring LEDs no longer cost 16 packets per tick on an unchanged page, and a track switch repaints the drum grid once (66 messages) instead of flooding it for 40 ticks (~1150).

    Sets can no longer be silently lost

    Movy's per-set autosave had five separate ways to throw work away. The persistence layer has been rewritten around them:

    • A screen freeze wiped the set. A reloaded engine comes up empty, and the next edit saved that blank engine over the file. Movy now tracks which engine instance it restored and refuses to save one it did not.
    • Closing Movy dropped the last few seconds of edits. Teardown now flushes, after releasing any sounding notes.
    • A crash or power-cut could truncate the state file. State files now carry a generation marker and a length + checksum trailer, and each save also goes to one of two rotating shadow copies. Older files still load, and older builds can still read the new ones.
    • A failed write vanished. Writes are now confirmed by reading them back, and a failure stays pending until it succeeds.
    • Edits could land in the wrong set. An unreadable or placeholder set id now means "keep the current set".

    Separately, a track could load silent and stay silent: a clip selection pointing at an empty slot now falls back to that track's lowest real clip on load.

    Other fixes

    • A module could re-enable automation on a parameter the host cannot reach β€” global-bank parameters aren't addressable as chain target:params, and module metadata was overriding that guard.
    • A fast knob sweep crossed a wide range in one flick: knob_acceleration: "wide" multiplied an already-accumulated detent count. It now scales a single step.
    • A one-shot action could fire twice, because the hierarchy reload that lands a second after a module loads cleared the gesture state.
    • Module-owned movy_config.json layouts are found beside audio FX and MIDI FX as well as sound generators, including audio FX loaded as Master FX.
    • Knob-touch note mapping: note 8 is the master knob and note 9 the jog wheel, not the other way round.

    Install: update from the Schwung module store, or download movy-module.tar.gz below and extract into /data/UserData/schwung/modules/tools/. Full changelog: CHANGELOG.md.

  16. v0.24.0v0.24.0 β€” every module page, everywhere1 file

    A fix-focused release. The headline is module coverage: Movy now walks a module's whole parameter menu instead of the first level or two, so synths with deep menus expose every page they publish.

    Engine 0.26.0 β†’ 0.27.0.

    Modules with deep parameter menus show all of their pages

    Movy read a module's ui_hierarchy too narrowly: it took the section list from either the module's own menu or the section it delegates to (never both), stopped descending once a section had knobs of its own, and gave up below two levels. Modules that publish their real menu one level down therefore collapsed to a single page β€” Helm showed 1 of its 30 pages, hiding 152 parameters.

    Movy now walks the whole section graph:

    ModulePages beforePages now
    Helm230
    MiniJV749
    Dexed523

    Forge, Moog, Surge, Nusaw and Chiptune also reach sections that were previously unreachable. Nested sections are named Parent/Child, and a section that merely repeats another one's knobs is shown once instead of twice (11 modules had such a duplicate page). No parameter that was reachable before became unreachable β€” the module regression suite asserts it across all 77 captured modules.

    Deep page

    Helm's and MiniJV's LFOs draw waveforms

    The name inference missed three things: shape lists using Saw Up / Sample & Glide / N Step / N Pyramid (Helm's list scored below the "is this a shape enum?" bar), rate and depth spelled Frequency and Amp, and keys that glue the role onto the LFO token (MiniJV's lfo1form / lfo1rate, which landed the shape and its rate in different groups).

    Helm's three LFO pages and MiniJV's eight per-tone LFO pages now draw a live waveform instead of plain knobs. Helm's stepped families get their own silhouettes rather than borrowing the step-sequencer glyph: N Step climbs in levels, N Pyramid climbs and falls.

    Helm N Step Helm N Pyramid

    Clip transpose no longer shifts drum tracks

    A drum module's pitches are pad addresses, so a transposed step fired the wrong pad β€” or nothing at all, when the shift landed outside the module's pad range. The engine now ignores clip transpose on a drum track (playback, live recording and step entry alike, so stored pitches keep one meaning), including for clips that already carried a transpose from before the drum module was loaded. On the Clip page the TRANS cell reads n/a there instead of offering a control that could never be heard.

    Clip page on a drum track

    Step automation is audible again after reselecting a module

    Reselecting a self-describing module (OB-Xd, Weird Dreams, Noisemaker, …) hot-reloaded its chain host and left the host's static param cache empty, so automation playback was silently dropped while the UI still showed it β€” only a restart recovered it. Movy now refreshes that cache after a reselect, so recorded automation keeps playing without a restart.


    Install: available in the Schwung module store, or download movy-module.tar.gz below and extract into /data/UserData/schwung/modules/tools/.

    Full changelog: CHANGELOG.md

  17. v0.23.0v0.23.0 β€” Move sync, LFO page, visualisations, per-voice drums1 file

    A large release focused on synchronising Movy with Move's transport, a new per-track LFO page, parameter visualisations, per-voice drum editing, and a fleet-wide pass over module layouts. Sequencer engine bumped 0.22.0 β†’ 0.26.0.

    One transport with Move

    Movy's playhead now locks to Move's transport (drift-free), captures its tempo, and shows an EXT indicator while following. The TEMPO knob writes tempo back to the device.

    EXT follow indicator

    Flip on the LINK toggle (Set page, knob 4) and Play/Stop on either Movy or Move starts and stops both.

    LINK toggle on the Set page

    Background mode β€” Back at the root view opens a Leave menu; choose Background to keep Movy sequencing under Move's own screens (synced LFOs stay locked). Shift + Back exits instantly.

    Leave / background menu

    Per-track LFO page

    A fifth chain slot exposes the track's two Schwung slot LFOs with a live waveform display (shape, rate/sync, depth, phase, retrigger). Hold any knob (1 s) to assign it as an LFO modulation target β€” modulated params are marked with a ~.

    Track LFO page A modulated parameter

    Parameter visualisations

    Envelopes now cover partial shapes (AD / AR / ASR / ADS render as 2- or 3-stage graphics), filters draw a mode-aware response curve from a detected cutoff+resonance pair, and any module's LFO shows as a waveform.

    Partial (AD) envelope Low-pass filter curve Band-pass filter curve

    Per-voice drum editing

    Forge gets a 16-pad Kit A/B layout with full per-voice editing (playback-safe), a curated per-voice automation set, and a per-voice Send page (reverb/delay/pan). libpo32 consumes its module's self-describing per-voice layout.

    Forge per-voice page Forge per-voice Send page Signal per-voice page

    Module layouts (fleet-wide dump pass)

    Module metadata now wins over config (config only fills gaps); curated layouts for chordism, sfz, 303, chiptune, hush1; param pages for chain_params-only modules; knob sensitivity normalised to a consistent per-range sweep; corrected param ranges to match DSP clamps. A dump-replay regression suite over all 76 fleet modules is wired into npm test.


    Install: in Schwung's module store, or drop movy-module.tar.gz under modules/tools/. See the full changelog and MANUAL.

  18. v0.22.0Movy v0.22.01 file

    Per-set sequencer state

    The sequencer and UI state (root note + scale) are now stored per Ableton Move set, keyed by the active set's UUID. Switching sets recalls an independent Movy project, aligned with how Schwung stores its tracks per set. Duplicating a set in Move (Copy/Paste) inherits the parent set's Movy state.

    Breaking: Movy no longer keeps a single global seq-state.json; existing global state is not migrated. Each set starts from its own per-set state (blank unless inherited from a copied parent).

    Device-verified: per-set autosave + reload across set switches.

  19. v0.21.0Movy v0.21.01 file

    [0.21.0] β€” 2026-06-30

    First documented release. Highlights of everything built to date:

    Added β€” parameter UI

    • Automatic parameter pages for any Schwung module (reads the module's hierarchy).
    • Arc knobs, enum knobs, and a full-screen scrollable enum overlay.
    • ADSR envelope graphics β€” A/D/S/R groups auto-detected and drawn as one envelope shape instead of four knobs.
    • Multi-page modules; full chain navigation (MIDI FX β†’ Synth β†’ FX 1 β†’ FX 2) and a master FX chain in Session view.
    • Module browser to load/swap modules per slot.

    Added β€” sequencer (4 Schwung tracks, aligned with Move)

    • Rust sequencer engine (seq-core + dsp.so) with transport, clips, recording, sessions, and persistence.
    • Clip step entry/editing, Session view & clip launching, live recording with count-in and metronome, loop/bar editing, duplicate/delete, quantize, mute.
    • Parameter automation β€” record knob moves live or per-step; values latch to their end trigger; on-screen knob arc follows automation; per-lane clearing.

    Added β€” beyond Move

    • Step parameters β€” per-trig velocity, length, probability, A:B condition, invert (Elektron-style parameter locks).
    • Clip parameters β€” scale, length, transpose (Shift + Step 3).
    • Set parameters β€” tempo, swing, root, key (Shift + Step 5 / 7 / 9).

    Added β€” keyboard & drums

    • Two-octave chromatic keyboard per track with octave shifting.
    • Drum modules switch the pads to a 4Γ—4 rack with per-voice parameter pages; layout templates for Mr Drums and Weird Dreams.

    Added β€” docs & project

    • README, MANUAL, CONTRIBUTING, MIT LICENSE, and UI screenshots.

    Known limitations

    • No undo, no capture, chromatic keyboard only (no scale-aware pad layouts), four Schwung tracks only, simplified clip model. See the manual.

    Development milestones

    A condensed timeline of how Movy got here (pre-0.21.0):

    • 2026-06-30 β€” ADSR envelope UI (auto-detect β†’ envelope graphic).
    • 2026-06-23 β€” Clip parameters (scale / length / transpose; Shift + Step 3).
    • 2026-06-22 β€” Set/Main parameters page (tempo / swing / root / key).
    • 2026-06-21 β€” Step parameters (per-trig velocity / length / probability / condition / invert); free unused automation lanes.
    • 2026-06-20 β€” Per-voice drum scoping (Mr Drums, Weird Dreams); selected slot always the playing slot.
    • 2026-06-18 β†’ 19 β€” Automation latch playback; unified duplicate gesture.
    • 2026-06-16 β€” Parameter automation (tap-vs-hold step gesture β†’ step-auto mode, automation dot, per-step/per-bar locks, knob arc).
    • 2026-06-12 β†’ 15 β€” Sequencer core: transport, recording, Session, Loop, step editing; LED affordances; count-in/metronome.
    • 2026-06-12 β†’ 20 β€” Drum support: 4Γ—4 rack, drum detection, LED grid, preset browser.
    • 2026-06-07 β†’ 08 β€” Module chain view, multi-track, render performance work.
    • 2026-06-06 β€” Initial release: chromatic keyboard + module host for Schwung.