— · —
NO PATCH LOADED
MIDI Monitor Logs
// Editor active. Awaiting connection signals...
Editor Info & Version Control
G
GEOMASK Companion
Version: 5.10.0 (Dark/Light Glass)
v5.10.0 Release Notes — News Notice in the Librarian Sidebar
- New: A dismissible notice can now appear at the top of the Librarian sidebar, sourced from a separate news.json file rather than anything baked into the app itself — editing and re-uploading that one small file is enough to post or change an announcement, no app redeploy needed. Dismissal is tracked per-message (by its id), so updating the message later shows it again even if an older one was already dismissed. Missing or unreachable news.json fails completely silently — no error, no notice — so this never affects anyone running the app without one, including offline.
v5.9.0 Release Notes — Play Mode: Selector Buttons, Wider Playback Card
- Changed: Play Mode's 9-option dropdown (Poly, Mono, Slur, Mono Porta, Para, Moly, Para Slur, Mono Retrig, Poly Retrig) is now a 3x3 grid of buttons showing the full option names, kept in the schema's own order rather than regrouped by any inferred mono/poly/retrig relationship. Widened the Playback card from col-span-4 to col-span-6 to fit them comfortably — General's row was 3+4+3 of 12 columns, leaving a 2-column gap at the end; it's now 3+6+3, filling the row exactly with System unaffected.
v5.8.0 Release Notes — Filter Mode & Filter 2 Extra: Selector Buttons
- Changed: Filter Mode's 4-option dropdown (Low Pass/High Pass/Band Pass/Notch) is now a single row of equal-width buttons. Filter 2 Extra's 16-option dropdown is now three rows: 12db/24db centered and narrower than the other rows, then LP1-7 and HP1-7 each as an equal-width 7-button row. Both are direct-value selectors — every option is already an explicitly named raw value in the schema, no derived math needed the way FX Type's category/variant encoding required.
- Fixed: Grouped the three Filter Controls knobs (Cutoff, Resonance, Key Tracking) into one row via explicit CSS order, independent of their position in the schema array — without it, Filter Mode's new full-width row would wrap in ahead of Key Tracking during grid auto-flow, stranding it alone in its own row.
v5.7.1 Release Notes — Sync Completion Toast Spam Fixed
- Bugfix: Finishing a sync could show a burst of "Loaded: [name]" toasts (often several reading "Loaded: INIT") instead of one clean completion message. Cause: a straggler SysEx response physically arriving just after state.isSyncing had already flipped to false at sync completion fell through to the single-patch-load code path, which shows a toast — one straggler, one toast, occasionally several in a row. The completion message itself ("Hardware Sync Complete!") was already correct and already firing at the right moment; it was just getting buried immediately after by this noise. Toasts on this path now only fire for a genuine, deliberate patch selection (clicking a Librarian slot), not an incidental decode. Confirmed directly: replayed a real captured response that used to trigger the bug and verified no toast fires post-sync, while a deliberate slot click still correctly shows its own toast.
v5.7.0 Release Notes — Linked Patch: Bank Buttons + Scoped Dropdown
- Changed: Same two-tier shape as the FX Type picker. Layer/Split's Linked Patch dropdown previously listed every patch across every bank at once via optgroups (up to 583 entries on MK1/MK2, 727 on MASK1). Replaced with bank buttons plus a dropdown scoped to just the selected bank's own patches. Since getBankDefs() is already model-aware from the multi-device work, this renders 4 buttons (Factory 1-3, User) on MK1/MK2 and 2 (Factory, User) on MASK1 automatically, with no per-model branching in the new code. Switching bank resets to slot 0 of the new bank rather than preserving the local index — unlike FX Type's variant number, a patch bank's local index carries no consistent meaning across banks.
v5.6.1 Release Notes — FX Type: Jump-to Dropdown, Equal-Width Categories
- Changed: The − / + variant stepper introduced in v5.6.0 only moved one step at a time. Replaced with a dropdown scoped to just the active category's 16 variants (e.g. selecting Chorus shows only Chorus 1-16), so jumping from Chorus 2 straight to Chorus 6 is one selection instead of four clicks. Switching category still preserves the current variant number and regenerates the dropdown with that category's own options.
- Fixed: Category buttons now use CSS grid (equal columns by construction) instead of flexbox growth, for a firmer equal-width guarantee across every category button in both FX1 (4) and FX2 (3).
v5.6.0 Release Notes — FX Type: Category Buttons + Variant Stepper
- Changed: FX1's Type dropdown (64 flat options) and FX2's (48) are replaced with a two-tier picker: a row of category buttons (FX1: Chorus/Distortion/Bit Crush/Ring Mod; FX2: Delay/Reverb/Distortion) plus a − / + stepper for that category's 16 numbered variants. The data was always really two stacked decisions rather than one long list — confirmed the raw-value encoding is uniform across every option (categoryIndex×32 + (variant−1)×2) — so the picker now matches that shape directly instead of just reformatting the same flat list. Switching category keeps the current variant number (e.g. CH 5 → DT 5) rather than resetting to 1. Both directions of the stepper are clamped to the true 1–16 range on each category.
v5.5.0 Release Notes — Octave-Jump Buttons for Semitone
- New: Small − / + buttons now flank each oscillator's Semitone knob, jumping a full octave (12 semitones) per click instead of dragging the knob a long way. Different Wave Mask numbers can need wildly different Semitone offsets to sound in tune — the range runs from -60 to +60, and neighboring mask numbers can need swings of several octaves to compensate. Clamped to the knob's own range on both ends, updates live the same way the knob itself does, and correctly marks the patch as edited so Write Patch saves the new value. Independent per oscillator.
v5.4.0 Release Notes — GEOSynths Panel in the Librarian
- New: A GEOSynths panel now sits at the foot of the Librarian sidebar. The logo links to geosynths.com, with Patreon and a link to the instructional video below it. It borrows the app's own glass, type scale and spacing so it reads as part of the instrument, with the brand red as the single colour outside the cyan/violet system. The one flourish: the logo is itself a knob, and this is a knob editor, so it turns when you hover the link — reduced-motion settings are respected.
- Improvement: The logo is embedded directly in the file rather than loaded from the web, so it renders instantly and still works with no connection. Quantising the flat-colour artwork kept that to roughly 7KB.
v5.3.0 Release Notes — Layer/Split Link Target Encoding Corrected
- Bugfix: Linking a Layer/Split target to User slot 000 stored the same value as Factory 000, so the app could not address it. Isolated hardware capture (same patch, target set to P000 vs U000 on the synth's own front panel, exactly two bytes differing) showed the real convention: Factory targets store as their absolute position, User targets as -(index + 1) — P000 is 0x0000, U000 is 0xFFFF. That offset on the User side is precisely what frees zero to mean Factory 000, so every User slot including 000 is addressable. The previous encoder used plain -index, which collided at slot 0. The decoder carried a matching "+1" fudge that made User slots 1 and up read back correctly, which is why this stayed hidden. Both sides now use the convention the hardware actually uses, and the encoder reproduces the captured bytes exactly.
- Reverted: v5.2.5 concluded User 000 was unreachable and redirected it to Factory 000. That conclusion was wrong — the hardware addresses both independently, as its own front panel demonstrates. The redirect is removed and User 000 selects normally.
v5.2.4 Release Notes — Pre-Deployment Review Fixes (MASK1)
- Bugfix: Layer/Split linked-patch numbers above 199 were silently truncated on MASK1. The clamp used the parameter schema's fixed maximum of 199, which is MK2's User Bank size — MASK1's User Bank holds 600, so any link past slot 199 was clipped. Now clamps to the selected model's actual largest slot index; MK2's works out to exactly 199, so its behaviour is unchanged.
- Bugfix: The Linked Patch dropdown fell back to bank id 3 (MK2's User Bank) when no bank was stored. MASK1's User Bank is id 1, so the dropdown's value matched none of its own options and displayed the wrong entry. Now resolves to whichever bank is actually the User Bank on the selected model.
- Known limitation (resolved in v5.3.0): Linking to slot 000 resolved to the User Bank rather than Factory. Flagged here as needing a hardware test; that test was done and the full encoding is now corrected — see the v5.3.0 notes above.
v5.2.3 Release Notes — Speed/Mask Zipper: Duplicate CCs Removed at Source
- Improvement: Replaces v5.2.2's throttled mask restore, which made things worse — pacing the corrections stretched each wrong-value window out, so the display flashed slowly and looked broken rather than merely glitching. Root cause addressed instead: Speed has only 41 distinct states across the whole knob sweep, but a drag fired an update per pixel of movement, so most Speed CCs re-sent a value the hardware already had. Because the hardware clears mask's overflow bit on every Speed CC it receives, each of those redundant messages triggered another drop/restore cycle on mask. Duplicates are now dropped before transmission: a fast full sweep goes from ~126 cycles to 41 (the true number of real state changes), and a slow, small adjustment drops by roughly 97%. Every genuine change still transmits immediately, so there is no added latency, and the mask restore is back to firing immediately rather than on a timer.
v5.2.1 Release Notes — Delta Speed Clearing Mask Above 255
- Bugfix: Adjusting an oscillator's Delta Speed knob live was silently dropping that oscillator's Wave Mask by exactly 256 on the hardware whenever mask was set above 255 (confirmed via a live report: mask 269 dropped to 13 after only touching Speed). Cause: Speed and mask's "above 255" flag share the same byte on the hardware, and the hardware's own Speed CC handler overwrites the whole byte rather than preserving that one bit. Since a single CC message can't selectively preserve one bit of a byte the hardware itself controls, now immediately re-sends mask's own live CC right after Speed's whenever that oscillator's mask is above 255 — restores the correct sound without the user needing to notice or intervene. The patch's actual stored data was never affected by this — only live playback after a Speed tweak sounded wrong until reselecting or rewriting.
v5.2.0 Release Notes — Oscillator Mask Values Above 255 Fixed
- Bugfix: Wave Mask values above 255 (up to the full 511 range) were being truncated on write — a patch saved with mask 325 would come back as 69 on reload, exactly 256 low. Confirmed the app's own encoding was mathematically correct and consistently transmitted the intended value; the bug was that the 9th bit's assumed location (bit 7 of the byte shared with Delta Speed) was simply wrong — the hardware doesn't use that bit at all. Found the actual bit via an isolated, hardware-only test: the same patch, saved directly from the synth's own front panel, with only the mask value changed above vs. below 255 — exactly one byte differed, by exactly one bit (bit 6, not bit 7). Both oscillators' Wave Mask now correctly round-trip the full 0-511 range.
v5.1.0 Release Notes — Paste Didn't Refresh the App's Own Display
- Bugfix: Pasting a patch (or importing one from file) correctly wrote the hardware — confirmed by sound — but never updated the app's own displayed parameter values. Layer/Split and every other tab kept showing whatever was there before the paste until a full reselect forced a fresh hardware fetch. Cause: writing to hardware only sends bytes out, it was never telling the app itself what had just changed. Extracted the parameter-decoding logic already used for hardware reads into its own reusable function, and now call it directly on the just-written bytes whenever the write target is the currently active voice — no extra hardware round-trip needed, the app already has the data it just sent.
v5.0.2 Release Notes
- Fix: Librarian Layer/Split badge (L/S) switched to near-black and lost opacity when its slot was selected, making it hard to read against the active slot's own styling. Now stays purple and fully opaque regardless of selection state, with a slightly larger font size.
v5.0.1 Release Notes — Stale Layer/Split Badge
- Bugfix: Pasting a Voice-mode patch over a slot that previously showed the Layer/Split badge (L/S) left the old badge showing, even though the hardware and the app's own underlying data were both already correct. The badge lives in a separate span from the patch name with no ID of its own, so the existing per-write and per-sync updates — which only ever touched the name — silently never reached it. Gave the badge its own dedicated element and added a proper update alongside every existing name update (write, sync sweep, individual patch load), so it now stays in sync everywhere the name already did rather than only after a full Librarian rebuild.
v5.0.0 Release Notes — Multi-Device Support (Phase 1)
- New: Hardware model selector added next to the MIDI port pickers — MASK1EX MK2, MASK1EX (Original), or MASK1 (Keyboard). Bank structure is now generated per model rather than a fixed 4-bank list: MK1/MK2 keep the familiar Factory 1-3 + User Bank split (377/200, confirmed identical for both directly from Kodamo's own librarian source), while MASK1 gets a single Factory bank (120 patches) and a single, larger User Bank starting at slot 128. Confirmed via regression test that MK2's generated bank list is byte-for-byte identical to the original hardcoded version, so nothing changes for existing MK2 users unless they actively switch models. Switching models clears synced patch data (with a confirmation) and rebuilds every affected dropdown, since slot numbering means something different under each model.
- Still unverified: This covers bank addressing and the SEND/GET protocol only, both confirmed shared across the family via Kodamo's own source and our own MK2 testing. Every specific parameter's CC mapping and quantization behavior (Arp Type, the destination dropdowns, the dual-CC tempo split, Layer/Split's sub-parameters) remains untested on real MASK1 or MASK1EX MK1 hardware.
v4.18.0 Release Notes
- Fix: Effect 1/Effect 2's Type dropdown had the same large label gap Delta Envelope had — same root cause (the card grew taller to stack against Arp, but only the dropdown's centering fix from before applied). Now uses the same tight-group treatment as Delta Envelope, applied to both Type and Mix.
- New: Sync HW now pulses whenever the Librarian has no patches loaded yet, the same treatment as Apply to Hardware — clears automatically once a sync actually populates at least one patch.
v4.17.0 Release Notes
- Changed: Browser tab title updated to "GEOMASK - Kodamo Mask 1 Editor & Librarian".
- Fix: Delta Envelope's label-to-control gap was much larger than the rest of the app (e.g. Fine) — a direct side effect of the previous centering fix, which pinned the label to the top and separately centered the control within the remaining tall space below it, pushing them apart. Restructured so label and control sit tight together like every other card's default style, with that whole tight group centered as one unit within the card instead — keeps the small, consistent gap everywhere else uses while still avoiding the earlier "stuck at the bottom" problem.
v4.16.2 Release Notes
- Fix: Delta Envelope's Loop buttons were centering relative to the knob's whole bounding box, but each knob also has a value readout below its dial — that readout shifts the dial's own visual center above the box's true geometric center, so the buttons (with nothing below them) landed lower than the dial itself. Added a matching invisible spacer beneath the buttons so both structures center the same way, putting the buttons' own center at the same point as the dial's.
v4.16.1 Release Notes
- Fix: Delta Envelope's Start/Speed knobs were stuck at the bottom of the now-taller card instead of centered — the row's default bottom-alignment only had Loop getting the centering treatment from the last fix, leaving the two knobs mismatched against it. All three controls (Start, Speed, Loop) now share the same full-height, centered treatment and sit together on one horizontal line, the same approach Wave Mask already uses for its own taller layout.
v4.16.0 Release Notes
- Fix: Delta Loop's segmented buttons (Off/On/BiDi) now use the same centering wrapper the actual dropdown controls use, rather than relying on the outer container's alignment alone — makes its behavior consistent with the already-confirmed-working dropdown fix. "Bidirectional" shortened to "BiDi" so the three buttons have breathing room instead of nearly touching.
- Fix: Oscillator section's Pitch & Tuning, Wave Mask, and Delta Envelope cards were shrinking to their own content instead of stretching to fill their row — stacked together, they now properly match the height of the Oscillator ADSR card beside them instead of leaving a visible height gap.
v4.15.1 Release Notes
- Cleanup: Removed the "Factory 2/3 addressing unverified" warning from Layer/Split — stale now that all three Factory banks and User Bank have been confirmed working correctly via direct hardware testing.
v4.15.0 Release Notes
- Fix: Filter Mode and Filter Extra now use the same label/dropdown alignment as every other section, adapted for the Filter Controls card's grid layout specifically — that card's grid aligns items to the bottom without stretching them by default, so the dropdown wrappers needed an explicit stretch added before the label-pinned-to-top fix could take effect, unlike the simpler flex-row layout every other affected control uses. The grid's column-span sizing (Cutoff/Resonance wide, Filter Mode/Key Tracking/Filter Extra narrower) is unchanged.
v4.14.1 Release Notes
- Changed: Subheading updated to "Kodamo Mask 1 EX MK2".
v4.14.0 Release Notes
- Confirmed: Layer/Split's sub-controls (Semitone, Tune, Balance, Pan, Split Point, Linked Patch) have no live MIDI path at all — confirmed directly via hardware test: the synth's own data wheel transmits no CC when adjusting these on the front panel. Not an app limitation, a genuine hardware one.
- New: Apply to Hardware now visually pulses whenever there are unsent changes, updating immediately as you turn a knob rather than only on the next full refresh, so it's clear at a glance whether what's showing in the app matches what's actually live on the synth.
v4.13.0 Release Notes — Factory Bank Linking, Actually Fixed
- Bugfix: Found the real cause of Factory bank linking always loading the wrong (User Bank) patch — confirmed via a direct hardware test with a Factory target (Factory Patch 100) that the sign of the stored value is the actual bank indicator: positive means Factory, negative means User Bank. The previous encoder always forced a negative sign regardless of bank, so the magnitude was always correct but the sign silently told the hardware "User Bank" every time. Factory bank selection is back in the Linked Patch dropdown, now correctly signed. Factory 1's addressing is directly confirmed; Factory 2/3 use the same formula by strong inference (matches how the Librarian already addresses them) but haven't had their own independent hardware test — flagged as such directly in the app.
v4.12.0 Release Notes
- Bugfix: Arp Type button grid selected the wrong pattern the further into the list you went (correct for the first 4, then drifting by 1, 2, 4...). Same class of issue as the earlier Velocity Destination bug — the buttons sent each option's own raw display value as live CC instead of accounting for the hardware's own quantization. Confirmed via 7/7 exact match against reported results that the hardware quantizes via floor(cc/4), a clean 32-bucket scheme matching the 5-bit storage field exactly. Fixed to send each option's bucket center instead.
- Reverted: Factory bank linking in Layer/Split, back to User Bank only. Two different, well-reasoned addressing formulas were tried across this project and both failed identically on real hardware — any non-zero Factory selection loaded the User Bank patch with the same number instead of the intended Factory patch. That's strong evidence this hardware field doesn't support Factory linking at all, rather than needing a third formula guess. User Bank linking remains solidly confirmed and unaffected.
v4.11.0 Release Notes — Factory Bank Linking Fix
- Bugfix: Selecting a Factory patch as a Layer/Split link target silently loaded the User Bank patch with the same number instead — confirmed cause: the encoder never actually read which bank was selected, only the local index, regardless of Factory or User. Now sends the correct value per bank: Factory targets send their absolute position within the hardware's single continuous 0-376 preset range (the app's Factory 1/2/3 split is purely a display organization, matching how the Librarian already addresses them), while User Bank keeps its already-confirmed local 0-199 index. Decode improved to match: a stored value above 199 can only be a Factory index (User Bank tops out at 199), so it's now correctly mapped back to the right bank and local number when reading a synced patch. Still unverified against real hardware — worth testing the same way as everything else in this project: link to a specific Factory patch, write it, and confirm the correct patch actually loads.
v4.10.0 Release Notes
- Fix: Linked Patch dropdown in Layer/Split now uses the same label/dropdown alignment fix as Effect 1/Effect 2 and the other sections — label pinned to the top matching the Semitone/Tune/Balance/Pan/Split Point knob labels beside it, dropdown centered below.
- Changed: Osc1/Osc2 Delta Loop replaced dropdowns with a 3-way segmented toggle (Off/On/Bidirectional), matching the style already used for the Layer/Split mode selector — a real 3-state control rather than squeezing it into a binary toggle or leaving it as a dropdown for just 3 options.
v4.9.0 Release Notes
- Fix: The dropdown/knob label alignment fix (v4.8.1) extended from Effect 1/Effect 2 to General (Play Mode), Oscillators (Osc1/Osc2 Delta Loop), Filter (Filter Extra), and Modulation (LFO1/LFO2 Destination and Shape, Velocity/Mod Wheel/Aftertouch Destination). One exclusion: Filter Mode uses a different custom grid layout (the Filter Controls card), not the simple row layout this fix targets — applying the same change there risked breaking it rather than helping, so it's untouched for now and would need its own separate look if it has the same issue.
v4.8.1 Release Notes
- Fix: The Type dropdown's new vertical alignment (v4.8.0) left it flush left instead of centered under its own label — the centering container only specified vertical alignment, not horizontal, so the dropdown's max-width cap left it sitting at the left edge with unused space to its right. Added horizontal centering to match.
v4.8.0 Release Notes — Dropdown/Knob Alignment Test
- Test: When a dropdown sits in the same row as knobs, its label previously ended up lower than the knobs' labels — the wrapper pushed the whole (shorter) label+dropdown group to the bottom of the row, rather than pinning the label at the same top position knobs use. Scoped a fix to Effect 1/Effect 2's Type dropdown as a test: label now pins to the same top position as neighboring knob labels, with the dropdown centered in the remaining space below — matching roughly where a knob's own center would sit. If this looks right, the same fix could extend to every other dropdown-next-to-knob row in the app.
v4.7.2 Release Notes
- Layout: Arp's three rows (Tempo, Hold/Retrig, Arp Type grid) now spread evenly across the full container height instead of clustering together with empty space above/below.
v4.7.1 Release Notes
- Layout: Tempo knob split onto its own centered row, with Hold/Retrig toggles in a separate centered row beneath it, rather than sharing one row together.
v4.7.0 Release Notes — Arp Control Redesign
- Changed: Hold and Retrig are now toggle switches instead of dropdowns, since they're genuinely just On/Off — reuses the same checkbox-pill style already used for the envelope LOOP toggle, rather than a new control style. Arp Type's 27 options are now a compact button grid rather than a dropdown, keeping every pattern visible and one click away. Tempo knob reduced back to its previous size to make room. Worth a visual check — this is a more experimental layout than most changes this session, packing a lot into the same footprint as before.
v4.6.1 Release Notes
- Fix: Tempo row was right-aligned instead of centered — inherited an "items-end" alignment class from the original horizontal layout, where it correctly meant "align to the bottom," but pushed everything right once the layout became vertical. Also increased the Tempo knob's size, since it has the row to itself with room to spare.
v4.6.0 Release Notes
- Layout: Arpeggiator's internal layout restructured into two rows — Tempo knob centered on top, Pattern/Hold/Retrig dropdowns in a row below, rather than all four controls side by side.
- New: −/+ fine-adjust buttons flank the Tempo knob for 1 BPM steps. Worth knowing: the hardware's tempo control only resolves in steps of 2 BPM (a pre-existing, confirmed hardware limitation, not new to this feature) — stepping to an odd value may shift by 1 to the nearest value the hardware can actually represent. A tooltip on each button notes this.
v4.5.0 Release Notes
- Fix: Balance and Pan knobs dragged far too fast compared to every other knob — their range (12/14) is much narrower than the standard reference the drag sensitivity was tuned for, so the same pixel movement covered a much larger fraction of their total range. Scaled down specifically for these two; Semitone, Tune, and Split Point (already confirmed to feel correct) are untouched, as is every other knob in the app.
- Layout: Arp & FX rearranged — Arpeggiator now matches Filter Controls' sizing (left-aligned, same width/height as the Filter section), Effect 1 and Effect 2 stack vertically to its right, and the X/Y Pad now centers in the row below, using the space much better and reducing scroll.
- New: Layer/Split's Linked Patch dropdown now includes all four banks (Factory 1-3 and User), organized with a sub-menu per bank rather than one flat list. Important: only User Bank linking has been confirmed against real hardware — every test so far used a User Bank target. Factory bank linking uses the same local-index encoding as our best guess, since we've confirmed the stored value is a local index rather than an absolute ID, but this specific case hasn't been verified. A warning appears directly in the app whenever a Factory bank patch is selected as the link target.
v4.4.2 Release Notes — Velocity Destination, Take 3
- Bugfix: Velocity Destination's previous fix (round(index*127/N)) only held for the first 3 of 6 options — a follow-up test showed a much sharper boundary than that formula predicted (63 correctly selects MASK, but 64 already overshoots to P EG). Rather than refine the formula again, this now uses a direct lookup built entirely from confirmed hardware responses: the original bug report already showed each option's own display value (21, 42, 63, 76, 106) correctly selects the next index up, so those are reused directly as the send values, shifted down by one, with 0 confirmed separately for the first option. No extrapolation — every value here was actually tested against the hardware.
v4.4.1 Release Notes — Correcting an Overreach
- Bugfix: Mod Wheel and Aftertouch Destination broke in v4.4.0 — they were never actually broken to begin with, and v4.4.0 incorrectly applied Velocity Destination's fix to them based on sharing the same storage mechanism, without checking that their option spacing is completely different. Their 16 options are evenly spaced by exactly 8 (0,8,16...120), already landing correctly on nibble boundaries with plain raw passthrough — confirmed working both before and after this correction. The shared formula's use of 127 as a denominator introduced a small rounding error that broke their upper range. Scoped the fix back to Velocity Destination only, which is the one actually confirmed broken.
v4.4.0 Release Notes
- Bugfix: Velocity/Mod Wheel/Aftertouch Destination dropdowns selected the wrong target one step off (e.g. picking FLT landed on F EG on the hardware). Confirmed via an exact 6/6 match against a full reported pattern for Velocity Destination that these are nibble-index dropdowns (storage encodes a sequential list position, not the option's own display value) whose live-CC path the hardware quantizes via round(cc*N/127) where N is the option count — sending the option's own display value doesn't land back on that same index once re-quantized. Fixed to send the correct value for each. Mod Wheel/Aftertouch Destination share the identical storage mechanism so get the same fix, though only Velocity Destination's own live pattern was independently confirmed.
- Bugfix: Semitone and Tune only supported positive values (0 and up). Confirmed via an isolated ±36 hardware test that negative values use standard two's-complement encoding, the same convention already used elsewhere in this app. Both now correctly support negative values — Semitone -32/+32, Tune -50/+50, both centered at 0.
- Improvement: Layer/Split controls (knobs, Linked Patch, Apply to Hardware) now stay visible at all times instead of hiding in Voice mode — keeps the Apply button reachable no matter which mode is selected, and stops the page layout jumping around on every mode change.
v4.3.0 Release Notes
- New: "Apply to Hardware" button on the Layer/Split card (User Bank patches only) — writes the current patch straight back to its own slot immediately, reusing the exact same tested write pipeline as the normal Write flow, without needing the full slot-picker modal. Fixes the mismatch where selecting Layer/Split in the app didn't reflect on the hardware until a separate manual write.
- Changed: Balance range narrowed to -6/+6 and Pan to -7/+7, both symmetric around 0 — safe within the already-confirmed encoding (a simple linear offset in a 4-bit field), just restricting the UI to a clean centered range.
v4.2.0 Release Notes
- New: A small (L) or (S) badge now appears next to each synced patch's slot number in the Librarian, showing whether it's in Layer or Split mode — reads directly from the confirmed b[50] flag bits, so it only appears once a slot has actually been synced.
- Improvement: Layer/Split moved to the top of the General tab, before Output Settings and the rest. Semitone, Tune, Balance, and Pan (plus Split Point in Split mode) are now real knobs matching Output/Playback/System exactly, including the same amber dial color — reusing the app's own knob component directly rather than a separate custom control. Split Point's knob shows the actual note name (e.g. "C4") instead of a raw MIDI number, and now correctly defaults to C4.
v4.1.2 Release Notes — Hardware Not Picking Up Layer/Split Immediately After Write
- Fix: After writing to the currently active voice, the hardware briefly showed Voice mode instead of Layer/Split until the patch was reselected in the Librarian — confirmed the written data itself was already correct (reselecting always showed the right mode), so this is a hardware firmware behavior, not an encoding bug: the write updates the stored patch correctly, but the currently-playing voice engine doesn't re-evaluate that state on its own. Writing to the active voice now automatically sends the same Bank Select + Program Change that clicking the patch in the Librarian already sends, forcing the hardware to properly reload what was just written. Applies to any write to the active voice, not just Layer/Split changes.
v4.1.1 Release Notes — Critical Fix: Layer/Split Wasn't Actually Writing
- Critical Bugfix: Changing Layer/Split settings and writing to hardware silently did nothing. Cause: the write flow only calls the byte encoder at all when state.isEdited is true — every normal knob sets this automatically via transmitParameter, but the new Layer/Split controls (which have no live CC by design) never did, so the write path took its "perfect 1:1 clone" fast path and cloned the original patch verbatim, completely ignoring every Layer/Split change made in the UI. All four Layer/Split controls (mode, linked patch, semitone/tune/balance/pan, split point) now correctly mark the patch as edited.
v4.1.0 Release Notes — Layer / Split
- New Feature: Layer/Split editing, in the General tab. Voice/Layer/Split mode selector, plus Linked Patch, Semitone, Tune, Balance, and Pan (Split mode also adds a Split Point note picker). Every byte here was confirmed via isolated single-variable hardware capture tests, not guessed — each field changed exactly one variable at a time against a known baseline before being implemented. These fields have no live CC (confirmed by testing, not assumed) and only take effect once the patch is written to hardware, unlike every other control in this app — the card is labeled accordingly. One open item: the linked patch's sign convention in the raw SysEx data is still unexplained (every observed sample was negative regardless of mode), though the magnitude and byte location are fully confirmed and the encoder replicates the observed pattern exactly.
- Moved: The X/Y Pad relocated from General to Arp & FX, since it functions as an effect rather than a global patch parameter.
v4.0.6 Release Notes — Two Real Zoom Bugs Found
- Bugfix: The zoom-height calculation was adding a fixed +48px to account for the outer wrapper's own padding, but that number was never updated through two prior padding reductions (48px to 32px to 16px) — it was reserving 32px more height than actually needed, which would directly contribute to residual page scroll. Corrected to match the current padding.
- Bugfix: The auto-fit calculation could re-run on any resize event after a manual zoom change and silently reapply its own calculated scale, overriding the user's choice — very likely why zoom appeared to "snap back" and refuse to go below the auto-calculated percentage. It now respects a manual zoom adjustment instead of continuously recalculating over it.
v4.0.5 Release Notes
- Test: Reduced the outermost page padding from 16px to 8px on all sides. This sits outside app-wrapper entirely, so it doesn't touch controls-viewport's available space or risk the internal-scroll issue the canvas height reduction caused — it only affects the margin between the app and the edge of the browser window.
v4.0.4 Release Notes
- Rollback: v4.0.3's second 30px canvas reduction (994px to 964px) removed — the Oscillators tab's content no longer fit in the reduced space, introducing its own internal scroll where there wasn't one before. Reverted cleanly to 994px; nothing else from this version is affected.
v4.0.2 Release Notes — Critical Data-Loss Fix
- Critical Bugfix: Writing any patch through this app silently erased its Layer/Split configuration, regardless of whether Layer/Split was ever touched directly. Confirmed via Kodamo's own reference tool that byte 50 carries the Layer flag (bit 6) and Split flag (bit 7) alongside Arp Type (bits 0-4) — our encode was overwriting the entire byte instead of only the bits Arp Type actually owns. Fixed to preserve bits 5-7 untouched. This app still doesn't display or edit Layer/Split settings, but it will no longer destroy them on a patch you didn't even touch those settings on.
v4.0.1 Release Notes — iPad Fixes (No Internal Containers Touched)
- Fix ("space before the app starts"): The outermost page padding (outside the app entirely, not an internal container) was 48px, but the auto-fit calculation only budgets 40px for it — reduced to fit within that budget properly this time, independent of the envelope card work that was rolled back.
- Fix ("not a fixed app"): iOS Safari's address bar can show/hide as the page is used, changing the real visible viewport height without reliably firing a standard resize event to react to it — this is very likely why the fit didn't feel stable. Switched to the visualViewport API, the standard fix for this exact behavior, which tracks the true visible area and fires its own resize event when it changes. Falls back to the previous method on browsers without it.
v3.9.9 Release Notes — Height Test
- Test: Reduced the app's overall canvas height by 30px (1024px to 994px) — the wrapper size itself, not internal padding, so the envelope containers and other cards are untouched this time. Updated the two other places that number appears (the zoom-scaling height calculation and the auto-fit-to-screen formula) to match, so they stay in sync. The content area simply gets 30px less space; any tab whose content doesn't fit falls back to its own internal scroll (same as the Librarian already does), not page-level scroll.
v3.9.8 Release Notes
- Rollback: v3.9.7's vertical space trims (envelope card padding/height, grid gaps, header/outer padding) removed — shrinking the envelope containers left a worse visual gap before the footer than before. Reverted cleanly; nothing else from this version is affected.
v3.9.6 Release Notes
- Rollback: Patch Morpher (v4.1.0) removed — didn't work as intended. Reverted cleanly to the pre-Morpher version; nothing else from this version is affected.
v3.9.5 Release Notes
- Improvement: Added Osc1/Osc2 Level, Osc1/Osc2 Mask, Noise Frequency and Level, Pan Spread, and Osc1/Osc2 Delta Start and Speed to the X/Y Pad's destination list (21 options total, up from 10). Confirmed Mask can be safely driven this way — it already has dedicated multi-CC transmission handling elsewhere in the app, not the generic single-CC path. Switched to explicit labels instead of deriving them from each parameter's group name, since Osc1 and Osc2's Delta Envelope and Wave Mask groups share identical names — group name alone couldn't tell them apart the way it could for LFO1/LFO2.
v3.9.4 Release Notes
- Fix: The X/Y Pad's destination dropdowns filtered by category, which excluded the entire "Arp & FX" category — meaning FX Type and FX Level weren't just buried, they were completely unreachable. Replaced with a short curated list: Filter Cutoff, Resonance, both LFOs' Speed and Shape, and both FX slots' Type and Level — 10 options instead of everything. Labels now show which LFO/FX slot each option belongs to, since "Modulation > Speed" alone couldn't distinguish LFO1 from LFO2. Also guards against a previously-assigned destination outside this list silently staying active behind an "Unassigned" dropdown.
v3.9.3 Release Notes
- Rollback: Ghost Banks (v4.0.0) removed — they couldn't provide live audition without overwriting a real hardware slot for every preview, which wasn't the intended use. Reverted cleanly to the pre-Ghost-Banks version; nothing else from this version is affected.
v3.9.2 Release Notes
- Improvement: The Bank Import dialog's Start Slot dropdown now shows each slot's current patch name too, matching the Export Range dialog. The warning text now names both the first and last patch that will be overwritten, so it's clear at a glance what's about to be replaced before flashing.
v3.9.1 Release Notes
- Improvement: The Export Range dialog's Start Slot and End Slot dropdowns now show each patch's actual name (e.g. "051: HallTine"), not just the bare slot number, matching how the Librarian grid already displays patches — makes it much easier to pick the right boundaries by recognizing patches directly. The confirmation text below also names both boundary patches for a clear at-a-glance summary before exporting.
v3.9.0 Release Notes
- New Feature: Export Bank now opens a dialog to pick a Start Slot and End Slot, the export counterpart to the Start Slot import feature — a bank stored at slots 51-131 can now be exported as just that range, instead of always exporting the full 200-slot bank. Defaults to the full range, so exporting everything still works exactly as before if left untouched. The filename reflects the chosen range automatically.
v3.8.8 Release Notes
- Fix: The main MIDI connection LED turned green once, the very first time any MIDI Out got auto-selected at startup, and never rechecked itself afterward — it stayed green even if you switched to an unrelated device or the synth was unplugged. It now actively checks that both the selected MIDI In and Out are the Mask 1 (by name) every time either dropdown changes or the port list updates, and reflects that live instead of a one-time guess.
v3.8.7 Release Notes
- Fix: The Thru status LED never lit up even when Thru was working correctly. Found the cause: the dot had a leftover inline style baked into its markup that permanently overrode any class-based color, so the "turn it green" logic was always correct but silently had no visible effect. Removed the inline style — the dot now actually reflects its computed state, the same way the main MIDI connection LED already did.
v3.8.6 Release Notes
- Bugfix: Filter Tracking, Noise Tracking, FX1 Balance, and FX2 Balance could intermittently land one step-of-5 off when moved live (e.g. app showing 55, synth showing 60) — the same class of live-CC bucket mismatch fixed for Resonance last time, just with these four never having had any rescaling applied at all. Traced the exact "sometimes" behavior to the raw CC range spanning a hardware quantization boundary. Fixed to send safely mid-bucket. Verified exact across all 21 steps mathematically, though this is from a single reported case rather than a full range like Resonance was — worth confirming across the full knob travel.
v3.8.5 Release Notes — MIDI Thru Diagnostics
- Fix: The Thru checkbox now reads the Controller dropdown's live value directly when toggled, instead of trusting a separate internal variable that could fall out of sync with what the dropdown actually shows — previously this could silently reject enabling Thru even with a controller visibly selected, with only an easy-to-miss toast as feedback.
- Fix: If Thru is enabled but no MIDI Out is selected, every incoming controller message was being silently dropped with zero feedback — indistinguishable from Thru simply not working. Now logs a clear one-time warning per enable attempt instead of failing silently.
- Fix: Both the successful enable and the rejection are now logged to the console, not just shown as a toast that's easy to miss.
v3.8.4 Release Notes — Knob Accuracy Fixes
- Critical Bugfix: Portamento was inverted live (app at max/13 showed 2 on the synth, and vice versa) — confirmed via exact match that the previous fix wrongly applied patch-storage's inversion to live transmission too; the hardware's live display shows the raw value directly. Removed the inversion for live CC only; patch storage is untouched.
- Critical Bugfix: Pitch Envelope Speed's live scale was still wrong — same root cause as Delta Speed (hardware maps live CC via roughly cc/2 onto its real register, not through the display range). Fixed with the same approach, verified exact against the reported values.
- Critical Bugfix: The Resonance fix from the previous release was itself wrong and introduced a skipped value (12) plus a consistent off-by-one above it. Re-diagnosed from the exact reported pattern: the hardware quantizes live CC via floor(cc/4), a plain 4-wide bucket — not the ~4.13-wide bucket the previous fix assumed from the 0-30 display range. Corrected and verified exact across all 31 states.
v3.8.3 Release Notes
- Fix: Pitch Envelope Speed's display range corrected from 0-40 to 1-40, matching the hardware's real range (same convention already used correctly for LFO Speed). Display-only change — patch storage format is untouched, so this doesn't affect sync/read behavior in any way.
- Bugfix: Filter Resonance drifted by 1 when moved live. It's stored as a 0-30 value even though its byte has more raw capacity, and live CC was being sent with no rescaling toward that narrower range. Fixed with the same bucket-centering approach as the envelope/portamento fixes, sized for its own 31-state range — verified exact across the full range.
- Investigated, not a bug: Tempo's "off by 1" is the hardware's own 2-BPM-per-CC-step storage resolution — even BPM targets (100, 120, ...) round-trip exactly with zero error; only odd targets (101, 121, ...) round to the nearest even value, which is unavoidable given the storage format, not something this app can fix.
v3.8.2 Release Notes
- Critical Bugfix: Osc1/Osc2 Delta Envelope Speed drifted badly when moved live (e.g. displaying 16 in the app showed as 26 on the synth) — confirmed via exact math match that these are packed as a 6-bit storage field (0-63) sharing a byte with an unrelated flag bit, even though the display only ever shows 0-40 of those states. Live CC was being scaled against the 0-40 display range instead of the true 6-bit register, causing the reported value to run nearly 63% too high. Fixed to target the actual register directly — verified exact (zero error) across the full range. Pitch Envelope Speed uses a different, non-shared byte and wasn't reported as affected, so it was left as-is pending separate confirmation if you notice the same there.
v3.8.1 Release Notes
- Bugfix: Portamento drifted when moved live, the same class of issue previously fixed for the envelope knobs — it's stored as a nibble just like them, but was never given the "send the center of the hardware's live-CC bucket" treatment, and its narrower, inverted range meant it couldn't just be added to the generic fix. It now has its own matching formula. Audited every other parameter against its storage format — all other full-range nibble params (ADSR, LFO, velocity/mod/aftertouch amounts, pan spread, env rate) were already handled correctly; portamento was the only gap.
v3.8.0 Release Notes — Sync Reliability Overhaul
- Critical Bugfix (byte assembly): Found by comparing against Kodamo's own reference tool and re-examining the MIDI capture — the raw SysEx byte assembler had no defense against malformed/garbage frames arriving mid-message (confirmed present in the capture as "Invalid" entries). If one arrived while a real voice response was still being assembled, its bytes were silently appended into the same buffer, risking a corrupted mix of real and garbage data being decoded as if it were legitimate. The assembler now discards a stale in-progress buffer instead of contaminating it whenever an unrelated message interrupts it, and caps runaway buffer growth as a backstop.
- Reliability (proactive recovery): Diagnosed by comparing sync behavior against Kodamo's own tool, which never reads past a gap in the user bank at all — it just stops at the first one. Our sync needs to read past gaps (to support imports that don't fill the whole bank), but crossing from an empty stretch back into real data is exactly the transition their software never exercises, and the likely point this hardware's firmware loses track of state. Sync now proactively sends a Bank/PC reset before crossing back into data after 3+ consecutive empty slots, rather than only recovering reactively after already getting stuck.
v3.7.2 Release Notes — Critical Sync Fix
- Critical Bugfix: Diagnosed via a direct MIDI monitor capture during a full sync — under sustained load the hardware can get stuck and re-transmit the exact same stale voice data hundreds of times in a row, regardless of which voice is actually being requested. The app had no way to detect this and was blindly trusting the last-requested voice ID as the identity of any incoming response, silently assigning the same wrong patch to many different slots (this is what was showing up as repeated "INIT" reads and unrelated "Empty Slot" entries). Sync now detects three byte-identical responses in a row as a stuck hardware state, discards the data instead of applying it, and attempts recovery using the same Bank/PC-change technique already confirmed elsewhere in this project to clear a similar hardware lockup, before retrying the same voice. Capped at 3 recovery attempts per voice to avoid hanging the sync indefinitely if the hardware stays stuck.
v3.7.1 Release Notes
- Critical Bugfix: Sync could still show real patches as "Empty Slot" even after v3.5.0's fix — that fix stopped good data from being deleted, but a single retry attempt still wasn't enough patience for hardware that's fallen behind partway through a long sync. With the User Bank now syncing 200 slots in one unbroken sweep (previously two separate 100-slot syncs), there's more room for the hardware's internal queue to build up a backlog. Added a periodic breather every 20 requests during the main sweep to help prevent that backlog forming in the first place, and a second retry round with increasing patience for anything still unresolved after the first. Genuinely empty slots take a little longer to confirm as a result — a deliberate tradeoff favoring accuracy.
v3.7.0 Release Notes
- New Feature: Bank Import now has a Start Slot dropdown — a smaller downloaded bank (e.g. 50 patches) no longer has to land at slot 000 and overwrite whatever's already there; pick any starting position and it writes from there instead. The warning message updates live to show the exact slot range that will be overwritten. Also raised the file-parsing cap from 128 to 200 patches, so a full User Bank export from this app can be re-imported without being truncated before you even get to choose a start slot.
v3.6.0 Release Notes
- Architecture: Collapsed "User 1" and "User 2" into a single "User Bank" (384-583, 200 slots) matching the real hardware exactly — the two-bank split never corresponded to anything on the actual synth, it was an app-side leftover from an earlier wrong assumption. Sidebar, Sync, and Bank Import all show one continuous 200-slot bank now. Also removed the import-spillover logic entirely, since there's no longer a second bank to spill into — an oversized import into the User Bank now just truncates at 200 with a clear warning, same as any other bank.
- Clarity: Subtitle changed to "Mask 1 EX MK2" — the 200-slot user memory and other specifics reflected throughout this app are particular to the MK2; other Mask 1 hardware variants may differ.
v3.5.0 Release Notes
- Critical Bugfix: Sync could show a real, populated User bank slot as "Empty Slot" even though clicking it loaded the correct patch. Root cause: a response arriving just after the per-voice timeout window still got its data applied in the background, but the retry pass didn't know that — if its own re-request also ran long, it unconditionally deleted whatever was there, wiping out data that had actually arrived correctly moments earlier. The retry pass now checks whether a voice was already resolved before re-querying it, and never deletes data that's already present. Also widened the timeout windows (250ms→350ms main sweep, 400ms→700ms retry) to reduce how often this race triggers in the first place, and downgraded the informational log for the harmless case from alarming red "error" styling to neutral, since it's now a handled outcome rather than a problem.
v3.4.2 Release Notes
- Fix: Footer was still using the original Jade Vine gradient (pink/coral/orange), missed entirely during the redesign. Now matches the same cyan-to-violet gradient used for the active Librarian bank and selected patch slot.
v3.4.0 – v3.4.1
- Added "MASK 1 Editor & Librarian" subheader under the GEOMASK title.
- Active-patch readout redesigned as a retro LED screen (red, matching the real synth) — glowing monospace text on a dark recessed panel with scanlines, bigger text with clearer separation between slot and name; INIT shows in amber as a distinct state.
v3.3.4 Release Notes
- Bugfix: Noise Frequency's range was 0-127, but 0 doesn't behave as a real usable value on hardware — the knob now matches the confirmed 1-127 range, same convention as other range-limited controls like Portamento.
v3.3.0 – v3.3.3 — Hardware Accuracy Fixes
- Added draggable envelope handles alongside the A/D/S/R knobs; moved Link Osc 1&2 Envelopes into Oscillator 1 Envelope's header.
- Resonance's actual byte encoding was using the wrong internal scale (0-60 instead of the confirmed 0-30) — both the display and the real hardware data were affected, not just the readout.
- Key Tracking (Filter + Noise) and FX1/FX2 Mix only have 21 real hardware states (steps of 5), not a smooth 0-100 — display now matches that exact quantization.
- Attack/Decay/Sustain/Release and every other 4-bit-nibble-encoded parameter (LFOs, Velocity/Mod Wheel/Aftertouch Amount, Pan Spread, Env Rate) were off-by-one specifically on live knob/handle turns, not on patch load — live transmission for these 29 params now sends the center of the raw-CC range that unambiguously maps to the intended value.
- A Noise Frequency "fix" based on a single static snapshot was wrong and reverted — confirmed via range testing that live CC needs no inversion at all.
v3.0.0 – v3.2.1 — Visual Redesign
- Replaced the Jade Vine theme with a glass aesthetic — translucent frosted panels over a deep void background, cyan/violet dual-tone accent system (a callback to the synth's own wave-MASKing concept). Light/Dark toggle in the header.
- Redesigned envelope display: glowing "phosphor trace" curve with stage-divider lines, uniform cyan coloring across curve and dots.
- Reskinned Oscillator/Filter/LFO cards, the Librarian panel, XY pad, and knobs to match.
- Added the always-visible "currently loaded patch" indicator in the tabs bar.
- Fixed several invisible-line and stale-styling bugs from the reskin, plus unnecessary page scroll on larger screens.
Earlier Versions (v2.0.0 – v2.9.2)
- v2.9.2: Factory-to-User bank import spillover, visual busy states on trigger buttons, accessibility labels, bank-lookup guards.
- v2.9.1: Import warning reflects actual destination capacity; Sync HW and every write operation now check each other's busy state.
- v2.9.0: Root cause fix — hardware has 200 total User slots (100/bank), not 256. Bank boundaries centralized.
- v2.8.1: Retry pass for sync timeouts.
- v2.7.9: Fixed post-import auto-sync scanning all banks instead of just the imported one.
- v2.7.7: Fixed SysEx receive buffering to ignore interleaved MIDI Realtime bytes.
- v2.7.5: Sync HW chains GET requests instead of a blind timer.
- v2.7.0: Corrected Filter Envelope Level formula; INIT PATCH confirms and writes to hardware; transmission lock preventing overlapping SysEx writes.
- v2.6.3 and earlier: SysEx addressing/commit corrections, envelope encoding fixes, partial bank import support, memory safety cleanup.
Save Patch to HW
Import Bank
Warning: This will overwrite patches on the hardware.
Export Range
Initialize Patch
This will overwrite the currently active voice on your connected hardware with a blank INIT patch.
This cannot be undone. Any unsaved sound in that slot will be lost.
Requires a connected MIDI Output. If none is connected, only the app's own controls will reset.