Changelog

What is new

Every build gets a new version number, and every entry here is written as the work lands rather than reconstructed later.

0.2.592026-11-04 Current release
LATEST

Version 0.2.58

v0.2.582026-11-04
  • Added:
  • A Details panel in the patch browser. Click a patch and the right of the browser shows its name, the soundbank it came with and that bank's cover, its author, whether it is a favourite (click to change it), and what it uses: the oscillators heard, the filters, FM and the ring modulator, the effects, the arpeggiator, the sequencer and the LFOs and routes that move it, with a complexity rating from one to five stars. The browser is wider for it; in a narrow window the panel steps aside.
  • Soundbank covers and authors. The export box has an Author field (remembered) and an optional cover: click the frame to choose a picture, or drop one on the window while the box is open. Any picture is cropped square from its centre and saved at 1024 x 1024. The import box shows the cover and the author with the terms and the password, and an imported bank keeps cover.jpg and bank.json beside its patches for the Details panel. Patches without a designer take the bank's author.
  • Audition the patch browser from the keyboard. Up and Down move to the patch above or below and load it, so a folder can be played through without the mouse; holding a key runs through the rows and only the one it stops on is loaded. Left and Right step through the folders. The search field keeps Left and Right for its caret.
  • Export a folder as a soundbank, with terms. Right-click any folder and choose *Export as soundbank (.nik)*. One box asks for the bank's name, a description, the password (twice), an optional legal statement the buyer must approve, and the destination folder (Browse... opens the system's folder picker). The legal text and the folder are remembered.
  • The terms and the description sit in the bank's header, readable before the password, and covered by its tag: editing or removing them makes the bank fail to open. A bank with terms is container version 2, which an older Nikkosynth refuses instead of importing it without showing them.
  • Import with a check, a password and an approval. *Import .nik* opens the system's file box (a double-click picks the file), and a .nik dropped anywhere on the window is imported too. The file is checked first: a NikPak archive (same extension) or any other file is refused with the reason. The import box shows the bank's name, description and size, asks for the password, and shows the terms; Import stays locked until I approve is clicked, and the backend refuses a bank whose terms it was not handed back word for word. The patches land in Users > <bank name>, with the terms beside them in Terms of use.txt, and the open browser jumps there.
  • Fixed:
  • Dropping a .nik on the patch browser did nothing: the window takes file drops itself, so the browser's HTML drop handler never saw them. Drops are now read from the window's own drag-and-drop event.
  • Changed:
  • New dependency: rfd 0.15 (the system's file and folder pickers), the same crate NikPak's window uses.

Version 0.2.57

v0.2.572026-11-04
  • Changed:
  • A clean attack, a moving tail. In four patches out of five, every LFO (vibrato, filter sweep, pan sway, the slow motion LFOs) now waits 1 to 2 seconds after the note-on and then fades in, so the attack is heard unmodulated and only the tail moves. Which patches, and how long, come from each patch's name, so the choice is stable. Sound effects, drums and arpeggios are exempt: a siren's swing is its attack, a drum is over within a second, and an arp restarts its LFOs on every step. A test holds the share near 80%.
  • Added:
  • Save means "save this patch". The Save dialog opens on the patch that is loaded, with its name, folder and mood already filled in and the Save button focused, so loading a patch, changing it and saving takes one press.
  • Your own patch is overwritten in place.
  • A factory patch is saved as your own copy, with the same name and category (the factory bank is never changed); from then on, Save overwrites that copy.
  • The dialog's title says which of the two will happen.
  • The loaded patch is remembered across launches, restored on Cancel in the browser, and followed through a rename or a move.
  • Patch browser: right-click menu and drag-and-drop.
  • Your own patches: right-click for Rename (F2), Move to category, Duplicate, and Delete (with a confirmation; the Delete key works too).
  • Factory patches: right-click offers Copy to my patches. The factory bank is rebuilt on every bank update, so it is never edited in place.
  • Drag a patch onto a folder: your own patches move there; a factory one is copied into your patches in that category.
  • The drag uses pointer events (a label follows the mouse and the folder under it lights up), because the window's own file drop swallows HTML5 drags on Windows.
  • Moving into a sub-folder ("Pure Analog/Bass") keeps it a sub-folder.

Version 0.2.56

v0.2.562026-11-04
  • Automatic build.

Version 0.2.55

v0.2.552026-11-04
  • Fixed:
  • The patch browser's folder names stay readable when it is short. The folder buttons were flex items that shrank with the dialog until their text was cut in half. They keep their full height now, and the folder list scrolls.
  • A moved dialog ran past the bottom of the window, and a dragged plate sat about 100 px under the hand, when the interface was zoomed. Under the CSS zoom, the mouse and on-screen measurements are in screen pixels, while position styles apply in the element's own unzoomed pixels. The code mixed the two, so every position was off by the zoom factor (+25% at 125%). Both now measure the actual zoom and convert. Checked in Chromium at 100%, 125%, 150% and 200%: the patch browser's bottom sits exactly on the 90% line wherever it is moved, with Cancel and Apply visible.

Version 0.2.54

v0.2.542026-11-04
  • Changed:
  • Filters over their whole range again. The earlier pass pushed every cutoff toward open and set resonance floors, which took the dark, round, soft and sad sounds out of the bank. Each seed now draws its own cutoff and resonance (0 to 128 and 0 to 118) from the region its mood calls for: Dark and Soft low, Warm and Dreamy in the lower middle, Bright high, Aggressive anywhere. Bass leans low. The Super, Complex and Noisy families spread over the whole knob instead of one band in the middle. A low cutoff (under 48) now follows the keyboard, so a nearly closed filter stays round and audible on every key. A very closed filter with no envelope gets enough to speak on the attack.

Version 0.2.53

v0.2.532026-11-04
  • Fixed:
  • Dragging a plate no longer moves anything until you drop it. The row used to rearrange live: the plate jumped into a new slot as soon as the drag began, and the plates around it shifted under the hand. The plate now follows the pointer from the point where it was grabbed, and a bright bar shows where it will land (above or below a plate, or beside a column). The row changes once, on release. Dropping it back on its own place changes nothing.

Version 0.2.52

v0.2.522026-11-04
  • Fixed:
  • The mixer faders sat at 0.5 or below in every patch. The bank scaled each patch's oscillators to sum to 1, so a pair sat at 0.5 and an Octet's at 0.08 to 0.45, and everything sounded quiet. Every heard oscillator now sits between 0.5 and 1, the loudest at full, in the author's order. FM modulators keep their level, because for them it is FM depth. The mix bus runs up to 1.6x hotter, and beyond that the VCA's OSCs Level trims it, so an Octet does not arrive six times louder than a Solo. The noise channels follow the same gain. A test holds every patch to it.
  • Oscillators no longer pan 64% left or right. The spread is graduated: the first pair about 25% out, wider pairs after, never past about 60%.
  • Held sounds that starved for voices. The Reese, Sub, Hollow Bass, Sub Pulse, Sub Floor, FM Growl and Round Bass basses ran on two polyphonic voices and cut their own notes off. They now play mono (legato). Heartbeat and a quarter of the Complex family get 6 voices. A test holds every held polyphonic sound to at least 4.

Version 0.2.51

v0.2.512026-11-04
  • Changed:
  • Twice the output level. The master trim goes from 0.62 to 1.24 (+6 dB). At 0.62 most patches peaked 6 to 12 dB under full scale. The brickwall limiter still holds -1 dBFS, so nothing can spike. The sound audit's thresholds follow the trim.
  • Analog is its own plate, stacked under the Mixer and the Spectrum, with an ON/OFF key in its title (off bypasses the drive and keeps the knob), the lamp, and the Drive knob. It was a row at the bottom of the FM plate, under the Particle stage, and hard to reach.
  • Pure Analog's Analog drive is always on, anywhere from a touch to the maximum.
  • Fixed:
  • The Moog filter was still too quiet. Re-measured on a saw against the SEM and the ARP, the ladder needed nearly its whole (1 + k) passband back, not the partial make-up from earlier: +4 dB at resonance 0.15, +9 dB at 0.45, +13 dB at 0.9. The make-up is now (1 + 0.9k), which puts it within about 2 dB of the other circuits at every cutoff and resonance.

Version 0.2.50

v0.2.502026-11-04
  • Changed:
  • Pure Analog, rebuilt for variety. Each of the 200 patches now rolls its own settings instead of sharing its style's recipe:
  • cutoff over the whole range;
  • resonance from none to self-oscillation (a third run extreme, 90 to 125);
  • filter envelope from -70 to +95, decay from 20 ms to 3 s;
  • envelope → resonance routes (spiking or settling), resonance LFOs, cutoff wobbles and slow sweeps, PWM;
  • the analog synth's own tricks: cross-mod FM (OSC 2 modulating), hard sync with a swept ratio, and ring modulation;
  • waves and octave layouts that vary from patch to patch. It is still three classic oscillators, the analog filter alone, the Analog drive on and no effects. Patches can now carry their own Mod Matrix routes.
  • Fixed:
  • The Moog analog filter cut the volume too much. A fixed 0.65 output scale, plus the ladder's natural bass loss as resonance rises (1/(1+k), down to -14 dB), left it 13 to 18 dB under the SEM, ARP and 303 at ordinary settings. The make-up gain is now (1 + 0.6k)/(1 + k): full level with no resonance and about -3 dB at half and full resonance. It still thins as it resonates, without the patch dropping away. A test holds it within 3 dB of the other circuits.
  • Changed:
  • Levels trimmed from the sound audit. A per-seed correction in dB brings the audit's outliers toward their category's typical level. Variants and layers inherit it.
  • Up: Ring Koto, Music Box, Glass Pluck, Ring Sparks, Coin, Zap, Clang, Bubble, Reed Lead, Clav, Plucked Bass, Ice Hit and the Whoosh effects.
  • Down: Sub Drop, Bell Choir, Kick Thump, Tuned Tom and Alarm.
  • Each boost stops where the sound's peak would reach the limiter.
  • Patch Level takes the correction up to its ceiling, and the patch's Final Mixer gain carries the rest (up to +6 dB more).
  • Documentation:
  • Manual figures fill their space. tools/docs/manual/lib.js now sizes every capture to the column it sits in (capped only by a maximum height and by 150 dpi so nothing turns soft), instead of a fixed 0.30 scale that left small plates small. The effects are shown two plates per row instead of three, the sequencer's two lanes are full width, and the paired plates (FM/Ring, mixer, VCF, envelopes, LFO/matrix, skins) are taller. 48 pages; the contents page is regenerated.
  • Website:
  • Every screenshot opens full size. The lightbox only knew the old .g-item buttons, so the home page's figures could not be enlarged; it now covers every figure.shot, with previous / next, the arrow keys, swipe, a counter and focus return. Figures are keyboard-reachable.
  • Visual effects: a scroll-progress line, a header that firms up once you scroll, a pointer spotlight on feature, stat, price and download cards, a hero glow that follows the pointer, a slow sheen across the headline, a zoom-and-sweep on screenshots, staggered reveals and a back-to-top button. The hero spectrum now shows a moving filter cutoff, pauses when off-screen or in a hidden tab, and draws a single still frame under reduced motion (it used to keep animating). All of it stands down under prefers-reduced-motion.

Version 0.2.49

v0.2.492026-11-03
  • Fixed:
  • The SEM analog filter could turn to NaN. Its first integrator used the one-pole formula instead of the state-variable one, so the loop was unstable. 66 patches went silent or broken over time, and some overshot by 8 dB. It is now Zavalishin's TPT state-variable filter: stable at every cutoff and resonance, with a bounded soft limit in the feedback for the SEM's smooth resonance, and a guard against non-finite input. A new test runs it across the whole range.
  • Levels fixed from the first sound audit:
  • Iron Room, Squelch 303, Glass Ladder and the 303 arps and sequences in Pure Analog were too loud; each now has a Patch Level that brings it to its folder's typical loudness.
  • Phaser Clav uses a snapping low-pass instead of a band-pass.
  • The Complex and Noisy families no longer use the band-pass that left a fifth and a quarter of them 10 to 25 dB under the rest.
  • On drums, the added wavetables sit lower.
  • Added:
  • Sound audit (scripts\sound-audit.ps1): plays every patch in the bank as the app does (wavetables loaded, arp and sequencer running), measures its level before the limiter, and flags each one that is BROKEN (NaN), SILENT, QUIET, TOO LOUD, or an OUTLIER against its category's median. It writes src-tauri\target\sound-audit.txt (flagged patches worst first, each with the settings that set its level) and sound-audit.tsv (every patch). -Filter <text> audits a subset.
  • Documentation:
  • The manual is rewritten for the current instrument (documents/Nikkosynth-Manual.pdf and .docx, 45 pages): ten oscillators with the noise pair, fifteen envelopes and Invert, the sequencer and its 500-sequence library, Phaser and Lo-Fi, Mono and Glide, microtuning, movable plates and UI Reset, .nik banks, Mutate, Pure Analog and the current function keys and licence prefix (NSY1-). Every figure is a 2x capture of the real GUI cropped to the plate or detail the text describes, with no letterboxing. Set in Oxanium, Exo 2 and JetBrains Mono, embedded in the Word file. Source in tools/docs/.
  • The website uses the same faces (self-hosted in website/assets/fonts/) at smaller sizes, shows the new captures at their own aspect ratio, and its feature list, stats, hero text and FAQ match the current build.

Version 0.2.48

v0.2.482026-11-03
  • Added:
  • Pure Analog: 200 new patches, made the way a Minimoog, an Oberheim SEM or an ARP 2600 made them: three oscillators on classic waves only, the analog filter alone (the digital one is off) with its own resonance and filter envelope, the Analog drive on, and no effects at all. Sharp and dry. They come in twelve styles (Bass, Lead, Brass, Strings, Pad, Keys, Organ, Stab, Pluck, Arp, Sequence and Drums), and the four circuits (Moog, SEM, ARP and 303) rotate through every style.
  • Sub-folders in the patch browser: "Pure Analog" holds its twelve styles, indented under it. Clicking the parent shows all 200.
  • Inverted envelopes. Every envelope has an Invert key: wherever the envelope modulates (filters, Mod Matrix routes), it runs upside down, so the sweep dips instead of rising. The curve is drawn upside down to match. The volume envelope itself is never inverted, so a note still sounds and ends. Several Pure Analog pads, strings, brass and stabs ("Inverse") use it.

Version 0.2.47

v0.2.472026-11-03
  • Automatic build.

Version 0.2.46

v0.2.462026-11-03
  • Added:
  • 32 showcase sounds built on the synth's own features:
  • 14 new arps, each spanning 2 to 4 octaves into a multitap echo: Crystal Cascade, Acid Spiral, Ring Sparks, Particle Rain, FM Bell Run, Vowel Walker, Sync Stairs, Wobble Steps, Dual Filter Chase, Glass Ladder, Tremor Pulse, Morph Glitter, Gated Nebula and Pinball.
  • 18 sounds across the other categories, using the ring modulator, the Particle stage, the two filters in series, LFO filter sweeps and wobbles, FM and sync, and vowel and SEM morphs: Ring Siren Lead, FM Screamer, Particle Flute, Wobble Monster, Ring Growl, Acid Squelch, Prism Sweep, Granular Choir, Ring Halo, FM Glass Piano, Phaser Clav, Particle Pluck, Ring Koto, Ring Abyss, Particle Storm, Frozen Particles, Sweep Brass, Phase Strings and Ring Rotor Organ.
  • Changed:
  • Every arp moves and echoes: at least two octaves, and a three-tap echo.
  • The echo bounces. The bank asked for a "pingpong" pan, which the engine does not know, so no patch's echo ever moved. Patches can now set 1 to 4 echo taps.
  • Super stacks use three different waves: OSC 4 keeps the standard wave, and OSC 5 and 6 each get a different wavetable. The Complex and Noisy families join the same walk through the catalog, and their names follow the table they use.
  • Every oscillator stands out. In Duo to Octet and the merged patches, oscillators sitting on 0 cents get a small detune of their own (±3 to 9 cents), so they beat gently instead of fusing.
  • The Cinematic drones are an octave higher (Drone Bed, Swell Up). Drone Bed's filter now breathes on a slow LFO.
  • Filters open, resonance speaks. The seeds' low-pass and SEM cutoffs move toward fully open: far for bright and aggressive sounds, a little for dark and soft ones. Leads, bass, arps, plucks, brass, pads and keys get a resonance floor (34 down to 18).
  • Playing moves the filter. In every patch velocity opens the cutoff, and on held sounds key pressure (poly aftertouch and channel pressure) opens the cutoff, lifts the resonance, and opens the analog filter when it is on.
  • The filter moves by itself. Every sustained patch gets three slow LFOs: LFO 2 sweeps the cutoff, LFO 4 varies LFO 2's speed so the sweep never repeats exactly, and LFO 5 moves the resonance. The rates come from each patch's name, so no two move in step. Existing motion is kept, and the Evolving pads keep their own designs.
  • The VCF's Digital / Analog tabs are 50% larger.
  • The Analog stage is used. The mixer's Analog drive (a soft saturation before the filters that adds harmonics) was at 0 in every patch. Each category now sets its own amount: 0.05 on bells, 0.1 on pads and strings, 0.2 on arps, 0.35 on leads and SFX, 0.4 on bass. The generated families vary theirs.
  • The Analog drive no longer changes the volume. The saturation is followed by an automatic make-up gain that matches its output level to its input (averaged over about 30 ms), then a brickwall limit at full scale. It adds harmonics without making the patch louder.
  • One colour rule for on/off: every on/off key is green when on and red when off. A plate switched off from its title key (FM, Ring Modulator) is dimmed.
  • Dialogs keep their buttons on screen. Every dialog leaves 10% of the window free below it, even after it has been moved and the zoom has changed.

Version 0.2.45

v0.2.452026-11-03
  • Changed:
  • SoundFX is a game-audio kit. 28 designs replace the old effects: Explosion, Distant Blast, Gunshot, Shotgun, Machine Gun (hold to fire), Blaster, Laser Beam, Ray Gun, Riser, Downlifter, Falling Whistle, Cartoon Fall, Jump, Coin, Power Up/Down, Hit, Punch, Siren, Alarm, Teleport, Zap, Engine, Clang, Glass, Whoosh, Thunder and Bubble. They are built on pitch sweeps, fast pitch LFOs and noise bursts with their own envelopes, rather than notes. The 200 "NFX" noise patches are now 200 "GFX" variations of these designs, from XS to Max. Sound effects are no longer layered with tonal patches in the merged set.
  • Duo to Octet add wavetables, not copies. The seed's own oscillators stay; each oscillator added on top is a different wavetable, walked through the whole catalog. "String Sextet" is the strings plus five wavetable layers. Bank version 13: the factory bank regenerates on the next launch.
  • Panels pack together. A column always sits right under whatever is above it, and a column that would hang alone at the end of a line takes the rest of the line, as long as that is not narrower than its minimum, so no gap opens at any zoom.
  • Pointing at the empty space under a column stacks the plate at its bottom.
  • Each effect is its own plate, with its title first and its bypass key in the title row, so the effects stack like any other panel. The bottom row's default, one column each from left to right: Arpeggiator + Sequencer; Chorus + Flanger + Phaser + Distortion + Lo-Fi; Rotary + Tremolo + Vibrato + Wah; Echo + Reverb + EQ; Final Mixer.
  • UI Reset in the title bar puts every panel back in its default place, unfolds every plate, and restores the divider's default height.
  • Fixed:
  • A dragged plate could stay lifted, as if selected, and a click elsewhere did not release it. Moving the plate in the page dropped the mouse capture, so the release never arrived. The drag now listens to the whole window.

Version 0.2.44

v0.2.442026-11-03
  • Changed:
  • Plates stack into columns. Drag a plate by its grip onto the upper or lower half of another and it stacks in that plate's column; drop it on a left or right edge and it becomes a column of its own. Each row's layout is remembered, and a double-click on any grip restores the factory one. That layout stacks the short plates (Mixer over Spectrum, VCF over VCA, Mod Matrix over LFO, Arpeggiator over Sequencer), so no space is lost under them. A column takes its widest plate's width.
  • One width for the narrow modules, so any of them stacks on any other: FM, Ring Modulator, VCF, VCA, Mod Matrix and LFO are all 260 px (220 px at the narrowest). The Mixer and the Spectrum share one width, as the VCF and the VCA do, with the Mixer on top.
  • The Ring Modulator is its own plate, under FM by default. The selected modulator's Width knob stays in FM, next to the modes it belongs to.
  • The Spectrum plate no longer draws a second "SPECTRUM" title and frame inside itself.
  • Layouts saved by earlier builds are not reused: the factory layout applies once.
  • The envelope's five ADSR knobs are the same size as every other knob on the plate. They were the only medium knobs on the synth, and they now fit on one line.

Version 0.2.43

v0.2.432026-11-03
  • Added:
  • Mono and Glide in the Final Mixer. Mono is a real mono mode — one note, last-note priority, legato (a key played while another is held moves the note without a new attack, and lifting it returns to the key still held) — where it used to be just a single voice that re-struck every key. Glide is portamento, in mono and in poly. Every mono lead and bass in the bank now plays legato with a short default glide (40 ms); struck mono sounds (drums) do not glide.
  • Sequencer transport: ▶ / ■ plays the pattern with no key held (and switches the Sequencer on); Latch keeps it running after the keys are lifted. Play is not saved in the patch, so loading one never starts playing.
  • Free phase: LFOs start every note at their Phase plus a random offset, and chorus, flanger, phaser, tremolo, vibrato, wah and the rotary start their sweep anywhere, so a moving stereo sound no longer begins hard on one side every time.
  • The Spectrum has its own panel, under the Mixer in one column (as the VCA sits under the VCF); each plate has its own grip and fold.
  • Changed:
  • No plate is stretched to the height of its row any more: each takes its own height, as the VCF and VCA always did. That stretching, plus a 315 px minimum on the spectrum, is what made the Mixer far taller than its contents.
  • Panels keep their size and only follow the zoom. Every plate used to grow, so whichever one wrapped onto a row alone (the Envelopes, at 150 %) stretched across the window. Plates now never grow; a narrow window still shrinks them to their minimum and wraps.
  • Mixer faders are twice as tall; the pan readout (C / L / R) is half its size; the ENV tabs are normal-sized buttons rather than a fifth of the plate each.
  • Long-release patches get at least 12 voices (pads, cinematic beds, any release of a second or more — some had 2). AUTH_VERSION 12.
  • The patch browser lists only patches that load; the Import Hydra button is gone.
  • Fixed:
  • The patch browser did nothing on click for most rows: it listed the 992 original .hydra patches, which have no sound mapping. And the factory bank was built only when the browser first opened (or on Rebuild): it is now built at launch in the background, and rebuilt whenever its folder is empty.
  • The FM algorithm digits never shrank: a fixed 26 px in the stylesheet overrode the SVG's own size. They are now half that.
  • Escape cancels every dialog (the top-most one), exactly as ✕ does, including the confirmation and password boxes, whose promises now always answer.

Version 0.2.42

v0.2.422026-11-03
  • Automatic build.

Version 0.2.41

v0.2.412026-11-03
  • Added:
  • A sequence library and browser. 500 factory sequences in 20 genres (Techno, House, Deep House, Trance, Progressive, Drum & Bass, Dubstep, Hip-Hop, Trap, Synthwave, Electro, Acid, Ambient, Funk, Disco, Pop, Reggaeton, Breakbeat, Minimal, Cinematic), 25 each. They are generated from a per-genre profile (src-tauri/src/seqbank.rs): its division and swing, its scale, its known rhythms, its kind of line (bass, arpeggio, lead or stab), its use of gate, ratchets, microtiming and chance, a counter line and, where the style has one, a polymetric third track. Generated in code, they are Factory Seq: always present, never renamed or deleted, the same on every machine.
  • The browser is a movable window: genres on the left; All / Factory Seq / User Seq filters and a search; a click auditions a sequence live, Preview holds a key so it plays hands-free, and Keep or Cancel (which puts back the one you had) decides. A double-click keeps at once.
  • The Sequencer plate shows the current sequence's name and its tag, with Browse and Save beside it. Save asks for a name and a genre (a factory genre or a new one) and writes a User Seq file under patches/sequences/<genre>/. User sequences can be renamed, moved to another genre and removed in the browser; factory ones refuse both, in the UI and in the backend.
  • Locked by seqbank's tests (500 in 20 × 25, unique names per genre, every sequence plays and stays in range, deterministic) and by user_sequences_save_rename_and_refuse_factory_edits.
  • The Sequencer is its own module, in its own plate pinned under the Arpeggiator (one column; each plate has its own grip and fold). Redesigned after what current step sequencers share:
  • eight tracks with their own length, each with a visible arm lamp;
  • sixteen bright steps on screen with pages for longer tracks, beats marked, unused steps visibly absent, and a live playhead that follows the page;
  • per-step lanes drawn by dragging across bars: Note, Velocity, Gate, Chance, Ratchet, a microtiming Nudge, and Mod, a per-step value the matrix routes anywhere as the new Seq Mod source;
  • its own clock: Division, Swing and Direction (forward, backward, ping-pong, random). Only the tempo is shared with the arpeggiator;
  • tools: Clear, Random (rhythm plus a minor pentatonic), shift ◀ ▶, copy and paste a track.
  • OSC 9 and OSC 10 (the noise pair) are full oscillators. Their mixer strips look and behave like strips 1–8 (same pan slider, no extra controls), they are rows 9 and 10 of the OSC panel with the same detail page (Initial / ENV Level, ENV Source with their own editable envelope, Invert, Velocity, PolyAT), the colour is chosen there, where OSC 1–8 choose their waveform, and the matrix reaches them under the same names (osc9InitialLevel, osc9EnvLevel, osc9Level, osc9Pan, source oscenv9). Saved patches carry the new fields; older ones load unchanged. The bank uses them: Kick Thump's beater and Snare Crack's wires now decay on their own envelopes, faster than the drum body.
  • Fixed:
  • Sequencer ratchets played all their repeats at the same instant: a 4× step was four stacked copies of one note. They are now spread across the step (after its nudge). And the step's Velocity was ignored: every sequencer note started at a flat 0.85.
  • The sequencer's Steps knob changed the length of all eight tracks, and its Gate / Velocity / Chance knobs overwrote every step of every track. The new panel edits the selected track and each step.
  • Soloing an oscillator left the noise playing (and soloing a noise channel did not silence the other one): the mixer sent each channel's effective on/off to the engine for OSC 1–8 only.
  • The reverb type sat above its plate's title. A plate's title is always its first line; the selector now sits under it.
  • Microtuning. A per-patch, octave-repeating 12-note tuning applied once per note at note-on (engine/src/tuning.rs), so keys, arpeggiator, sequencer and MPE all follow it and transpose and detune still work on top. Presets: Equal, Just (5-limit), Pythagorean, ¼-comma Meantone, Werckmeister III and Rast (quarter-tone), each with a selectable root; Custom starts from whatever was playing and offers twelve ±100-cent knobs labelled with the notes they fall on. Microtuning only ever moves a key by under a semitone, so one key is still one note. Saved in the patch (tuning, tuningRoot, tuningCents; older patches load as Equal). Locked by the tuning module's tests (pure Just thirds and fifths, Meantone's pure thirds, the root wrap) plus a_tuning_moves_the_note and tuning_reaches_the_engine. Translated into all ten languages.

Version 0.2.40

v0.2.402026-11-03
  • Automatic build.

Version 0.2.39

v0.2.392026-11-03
  • Automatic build.

Version 0.2.38

v0.2.382026-11-03
  • Automatic build.

Version 0.2.37

v0.2.372026-11-03
  • Automatic build.

Version 0.2.36

v0.2.362026-11-03
  • Added:
  • Lo-Fi, after the distortion in the Pre-FX group: sample-rate reduction (a phase-accumulated hold, so any rate from 500 Hz to 48 kHz and a smooth sweep), bit-depth reduction (1–16 bits), a 12 dB/oct tone filter and Mix. The aliasing is deliberate. Own plate, translated, wired end to end (fxLofiOn, fxLofiBits, fxLofiRate, fxLofiTone, fxLofiMix); locked by lofi_reduces_bits_and_rate.
  • Four new mod-matrix destinations: oscNWidth (pulse width from any source, not only an FM modulator in PW mode), oscNDetune (Super detune, OSC 4–6), noise1Level / noise2Level, and ring (the ring modulator's depth). In the matrix picker, and locked by width_detune_noise_and_ring_are_matrix_destinations. They let four Evolving pads do what they were first written to: Event Horizon's ring creeps in and out, Pulse Garden is a real PWM pad, Long Orbit's supersaw detune widens and narrows, Tide Table's noise bed rises and falls. AUTH_VERSION 10.
  • A Phaser in the Pre-FX group, between the flanger and the distortion: 2–12 first-order all-pass stages swept exponentially between 160 Hz and 5.2 kHz, with Rate, Depth, stereo Offset, Feedback (±63), Stages and Mix. Coefficients are updated at control rate (every 32 samples) so a tan per sample is avoided; the path is allocation-free. It has its own plate on the FX page, translated into all ten languages, and is wired end to end (fxPhaserOn, fxPhaserRate, …). Locked by the_phaser_sweeps_its_notches_and_stays_stable plus the existing every-effect tests. The bank's Phaser character, which had been quietly running the Vibrato effect, now uses it. AUTH_VERSION 9.
  • Fixed:
  • The release build failed to link (CVT1100: duplicate resource). The Common-Controls 6 manifest that lets the wrapper's *test* harness load was being added to every executable link, including the app itself, which tauri-build already gives a manifest. build.rs now adds it only when NIKKOSYNTH_TEST_MANIFEST=1; check.ps1 sets that around the wrapper tests and removes it afterwards (in a finally, so a failed run cannot leave it in the session), and the CI test step sets it for its own run.
  • Lo-Fi did not compile (two mutable borrows of the tone filter pair in one expression); split into two statements.
  • The shimmer reverb froze long pads into a constant DC level. Its feedback loop is meant to be high-passed then low-passed, but both filters were the same one-pole *low*-pass, so DC and bass went round the loop untouched (a pitch shifter leaves DC where it is), and the loop's small-signal gain was 1.5 on a tail that recirculates near unity at long reverb times. On a 12–20 s shimmer the loop latched onto an offset at the tanh ceiling: nine of the ten Evolving pads stopped crossing zero after ~5 s — a steady level, no sound. Found by the new 40-second Evolving render. The high-pass is now the input minus its lows, and the loop gain is 0.8. Locked by a_long_shimmer_neither_latches_nor_runs_away (6/14/20 s, ten seconds of chord then eight of silence).
  • A DC blocker on the master bus (5 Hz, one pole, before the master trim and limiter). The filters guard their own loops, but DC made after them — a ring modulator multiplying signals of the same pitch, asymmetric drive, the shimmer's old latch — went straight to the output, costing limiter headroom. Locked by the_master_bus_blocks_dc_and_passes_audio (0.5 offset gone in a second, 100 Hz within 1 %).
  • Glass Hours had a 2.5 s attack on a plucked string, so the pluck died before the envelope opened; its measured level had been the shimmer's DC all along. It now strikes, over a triangle-and-sine bed that sustains. Event Horizon's drift closed its ladder almost shut for a quarter of every cycle (44 ± 40); now 50 ± 22.
  • Some derived patches played chords. The owner's rule: oscillators may be microtuned or moved by octaves, never set to an interval. FM Growl and Sync Scream keep a fifth on an FM modulator, where it is timbre; but thickening a seed toward eight oscillators, or layering it into another, copied that fifth into an always-heard slot, and dropping a dead FM setting turned the modulator itself into a carrier. Every copy is now snapped to the nearest octave (octave_ratio), and so is any oscillator FM leaves audible. Tension Bed, which held a tritone by design, is rebuilt as a microtuned beating stack. Locked by every_oscillator_that_is_heard_sits_on_an_octave (Perc's ring-mod clang exempt).
  • The ten Evolving pads all moved identically, and none as written. voice() gave every patch the same two LFOs — cutoff at 0.07 Hz, pan at 0.11 Hz — and the Voice struct had no way to ask for more, so "the ring depth drifts", "the wavetable is scanned", "the vowel wanders" and "the detune widens" were sentences with no modulation behind them. Voice gains drift_rate, sway_rate and motion (two more LFOs, 4 and 5, routed through the matrix), and each pad now has its own: Prism scans its wavetables across 90 frames, Drift Choir's vowel order is moved by two unrelated LFOs, Long Orbit's upper supersaw drifts against the lower, Slow Machine and Tide Table have one LFO varying another's rate so the movement never repeats. Where the engine has no destination for what an intent claimed (ring depth, pulse width, a single noise oscillator's level), the intent was rewritten to describe what the patch really does. Two tests: one checks that every pad has four slow routes and no two move alike, one renders each for 40 s on a held chord and writes target/evolving-motion.txt. AUTH_VERSION 8.
  • Complex: the Karplus layer was inaudible on 50 of the 200 patches. The slow-attack quarter opens its amp envelope over 2.5 s, and the pluck's 0.55 decay had died by then; on those patches it now rings (0.92).
  • Saving a patch nested its folder twice (Users/My Pads/My Pads/…): save_patch passed root/folder *and* folder to patch_path. Its stricter ASCII cleaning could also rename the folder (Pads.v2 → Pads_v2), so a save missed the folder it was aimed at. The file now goes directly into the folder the user picked.
  • Moving a patch dropped it out of Users. move_patch wrote the bank as User <folder>, which the browser's folderOf recognises in neither of its forms, so a moved patch reappeared among the factory categories. It is Users/<folder> now, and a rename without a new folder keeps the patch where it is instead of pulling it out of an imported bank. A case-only rename works on Windows.
  • A .nik bank could write outside the patch store. unpack_to joined the bank's own owner field into the path unchecked; import now always files under Users, and both owner and name go through safe_name. Locked by unpacking_cannot_climb_out_of_the_root.
  • A line break in a name could write extra keys into a patch file (the format is one key = value per line). Every field is flattened to one line on write; locked by a_line_break_in_a_field_cannot_write_another_key.
  • .nik hardening: decompression is capped at 512 MB (a crafted bank could otherwise expand to fill memory), salt and nonce come from fresh_seed() (clock, per-process random keys and a counter) rather than the clock alone, and safe_name refuses Windows device names (CON, NUL.txt…).
  • MIDI channel pressure ignored the receive channel, so a second keyboard on another channel could still drive this patch's aftertouch. System messages are now dropped explicitly.
  • The sustain pedal's held-note list is preallocated, so the audio thread never grows it; a held − / + stepper stops when the window loses focus.
  • Removed the leftover _claude_backup_20261102/ and the dead src/navigate.ts.
  • The drum kit was 20 dB apart, hats buried under the kick. Shaker played at -28.3 dBFS and Closed Hat at -20.3 against Kick Thump at -7.9. Both had a source and a filter that disagreed: the hat band-passed *violet* noise at ~5.5 kHz, but violet's energy sits near Nyquist, so the band saw a few percent of it; the shaker band-passed *pink* and *grey* noise (energy low) at ~2 kHz behind a 35 % grain crossfade. Both are now white plus a bright colour through a high-pass (~3 kHz for the hat, ~3.6 kHz for the shaker), with explicit Patch Levels so they sit under the kick rather than over it. Tuned Tom had env_to_filter: -34, which on a decaying envelope sweeps the cutoff *up* — the reverse of its own intent; it is +34 now. A new assertion in every_seed_has_a_comparable_loudness holds every kit piece within 12 dB of the kick; the Perc floor itself is unchanged. AUTH_VERSION 7.
  • The Evolving pads were 20–36 dB too quiet, and the cause was an arpeggiator. A bulk edit had given six pads arp: true (and a sequencer) — a 1/16 step clock against a 3–5 second attack cuts every note off after ~70 ms, before the envelope can rise, so a pad that should swell forever barely sounded. Kick Thump had the same fault: a kick drum with an arpeggiator. All of them are pads and percussion again, and Grain Clave now uses a *repeating* step pattern instead of a bass figure, which is what "a 15 ms grain repeated" actually describes.
  • The vowel filter was 40 dB down and could not be corrected from the patch. Three narrow band-passes pass a small slice of a saw; at 6× makeup the voiced patches measured −23 dBFS with Patch Level already against its 128 ceiling, so no patch setting could recover them. The makeup is now 24×, applied where the loss happens.
  • The loudness test measured every patch at C4. A bass whose filter closes at ≈150 Hz has its fundamental above that at C4, so it was scored on a note it would never be asked to play — Sub Floor and Reese read 20 dB quiet for that reason alone. A patch that comes out quiet at C4 is now re-measured at C2 and the louder of the two counts. Percussion gets its own floor (−32 dBFS against −26) and is left out of the evenness check, because a shaker's 20 dB crest factor is the instrument, not a fault.
  • clone_voice had lost two fields, so every derived patch dropped its arpeggiator division and octave range. The cause was mine: a script that stripped arp:/seq: lines from seven seeds never reset its "inside this patch" flag, so it also stripped matching-looking lines from clone_voice further down the file. Restored — and a new test, clone_voice_copies_every_field_of_the_voice, compares the struct's field list against the copy list so a *deleted* line fails the build's tests instead of quietly falling back to Default.
  • Two tests had never run, and both were checking the wrong thing. The wrapper harness could not start (below), so patchgen's amp-route test and the Hydra import test had never executed. The first inspected the *saved patch* for the env1 → level route, which the engine adds when it builds its matrix — so every patch, correctly, looked unreachable; it now checks the engine's matrix. The second expected the Hydra importer to convert a bank, which has been deliberately blocked since the byte map was found to be missing; it now asserts the refusal and that nothing is written.
  • Added:
  • Every panel plate can be moved. A slim grip sits across the top edge of each group, in the plate's own padding so it costs no layout height; drag it and the plate reorders live among its neighbours. A plate can only be dropped within its own row — top-row plates among the top row, the bottom strip among the bottom — because the drop target is only ever looked for among the row's own children, so the rule cannot be forgotten. The order is remembered (double-click a grip to restore the factory layout).
  • Dialogs can be dragged by their title bar — the patch browser, Settings, the wavetable picker and every other box. The first drag pins the dialog at the point it was picked up, it is clamped so its title bar can never leave the window, and a double-click on the bar puts it back in the middle.
  • Fixed:
  • authored.rs did not compile — and the six errors were all mine, from the last batch of edits: five oscillators were written as a bare OscDef where the slot wants Some(OscDef), and in one patch the arpeggiator's octave range had been inserted *inside* an oscillator literal instead of the voice's. Both are simple typos, and both would have been caught by a compiler — which is why this build had to run.
  • The brown noise oscillator was clipping. A hard clamp sat *before* the output gain, so the integrator was squashed flat at the ceiling and then multiplied by 6: a distorted walk, not a dark one (the peak measured 4.28). It is bounded by its own leak instead, and the output gain is now a per-colour trim.
  • The noise colours were up to 18 dB apart in level, so changing colour sounded like a gain fault. The six continuous colours are trimmed to the same RMS; velvet and crackle stay quieter on purpose, because their RMS is far below their peak by construction and trimming them to parity would turn a rattle into a gunshot. The trims are the mean of eight seeds — pink, brown and grey are running filter states whose level varies 2–5 % from seed to seed, and the first version, tuned to one seed, was 1.4 dB out on the next. The test now averages seeds for the same reason. Locked by the_continuous_colours_play_at_the_same_loudness.
  • The wrapper tests now get a Common-Controls 6 manifest (src-tauri/test.manifest + build.rs), and the check runs cargo test --lib. This is the actual cause of STATUS_ENTRYPOINT_NOT_FOUND: a Rust test harness embeds no manifest, so Windows binds it to the legacy comctl32 5.82 stub, which does not export TaskDialogIndirect, SetWindowSubclass, RemoveWindowSubclass or DefSubclassProc — all of which this crate's tests import through the Tauri stack. The application was never affected because tauri-build gives it a manifest. Two details cost a round each: cargo:rustc-link-arg-tests is rejected outright for a package whose tests are unit tests inside the lib (it applies to *integration* targets only), so it is plain rustc-link-arg; and that flag reaches the bin too, where tauri-build has already embedded a manifest, giving CVT1100: duplicate resource. The check therefore runs cargo test --lib, which builds only the harness that needs the manifest and never the bin.
  • check.ps1 no longer prints "all checks passed" over a failure. The wrapper step's failure counter only fired when both the run *and* the retry failed, so a compile error that never reached the retry was reported as a clean run. It counts any non-zero result now, and only does the by-hand probe for an actual *load* failure.
  • **check.ps1 now names the missing *function*, not just the module. It parses the harness's import name tables (Get-PeImportedFunctions) and checks each against that DLL's own export table (Get-PeExportNames / Get-MissingImports), so the next run reports * <dll> does not export: <symbol> instead of a list of candidates. API sets (api-ms-win-*) are labelled as loader-schema names with no file, which removes six false alarms from the previous report.
  • The wrapper tests' STATUS_ENTRYPOINT_NOT_FOUND is not about nikkosynth_lib.dll at all. Three rounds of diagnosis blamed that file; the proof against it is that the harness, run by hand with the DLL moved aside, fails with the *same* 0xc0000139 — not the "DLL not found" code. So the harness does not import it, and the missing export is in a different module — most likely one the Windows loader resolves from System32 in a version older than the exe was linked against (Tauri's WebView2Loader.dll is built next to the *binary*, not next to the harness in deps\). check.ps1 no longer guesses: it reads the harness's PE import table (Get-PeImports), prints every import and where it resolves (flagging System32 copies), copies in anything missing beside the exe that exists elsewhere in the build tree (Repair-Imports), and retries once. -OnlyWrapper / -SkipWrapper make a diagnostic round take seconds instead of the 2.5-minute engine suite.
  • Two real faults found on the way, both fixed: a leftover nikkosynth_lib-<hash>.exe harness was holding the DLL open, and the script was planting its own copy of the cdylib in deps — the one folder Windows looks in first — so its "remove the stray copy" step never tested anything.
  • check.ps1 no longer aborts on a PATH entry it cannot join (Join-Path threw "drive is null" for a bare C: entry and took the whole DLL report with it), and the step's closing line now names the failure code rather than saying "stale, run -Clean" — advice this script has been giving for days without ever being true.
  • Three dead items removed rather than left warning on every build: nikpack's unused list_packs and PATCH_FORMAT, its now-unused FORMAT import and PathBuf, and patchgen::Feature made pub(crate) so it matches the visibility of the build that takes it. Patch::intent and Patch::osc_count are kept — the tests read them and the intent is the record of why a patch sounds as it does — with an allow(dead_code) that says so.
  • Velvet and crackle were not sparse: their grain envelope decayed so slowly that a single trigger rang for ~4 500 samples, which is a hiss with a wobble rather than a rattle. Grains now decay in ~110 and ~35 samples.
  • Two sequencer tests encoded the wrong expectation, not a wrong clock — one counted notes across a loop where both tracks fire every step (the polymeter shows in *which* steps, not how many), and one ran two passes where it meant one. Both now assert the step pattern itself.
  • The panel's column widths follow each plate, not its position. They were :nth-child rules, which was fine while the order was fixed; a dragged plate would have inherited the width of the slot it landed in. They now match the plate's drag key (data-group-key), and the VCA's signal-path strip looks panels up by that key too instead of assuming the oscillator plate is first.
  • Changed:
  • The mixer and the spectrum are 50 % taller. The strips went 120 → 180 px, split between the meter (52 → 78) and the fader throw (44–64 → 66–96, with a taller cap), so a level can be set by hand rather than nudged. The spectrum slot went 210 → 315 px and its canvas 90 → 135 (the standalone visualiser 160 → 240): at the old height the bass bands were a few pixels of a sixteen-band meter. The fader's cap height is now measured from the element instead of a number copied into the drag maths, so a CSS change can no longer leave the cap mis-centred at the ends of the throw.
  • Added:
  • Two noise oscillators: OSC 9 and OSC 10. Each is a real mixer channel — on key, level, pan and meter — with a colour instead of a waveform: white, pink, brown, grey, blue, violet, velvet and crackle. Noise is a spectrum, not a pitch, so the two are not FM operators and never join the carrier bank; they are summed into the mix beside the oscillators. Per-voice generators, so a chord gets a bed rather than one shared hiss.
  • The mix bus is now accumulated in f64. Eight oscillators plus two noise sources, each with its own pan, summed sample by sample in double precision and converted once, after the mix — so a quiet noise bed under a loud saw, or a low harmonic under a full-scale one, keeps its detail instead of being rounded away.
  • Six patch families, 1 800 patches.
  • Perc (10 seeds) — short and struck: kick, snare, hat, rim, tom, shaker, anvil, wood block, sub drop, clave. Noise, ring modulation, the granular stage and the Karplus string do the work; every one dies inside 300 ms.
  • Evolving (10 seeds) — pads that never repeat: several slow LFOs on different destinations, filter drift, granulated layers and 11–20 s shimmer rooms.
  • Complex — 200 — all eight tone oscillators, three effects minimum, ring modulation, the FM algorithms walked across all 144, and the exotic wavetables (bent sines, skewed ramps, spectral tables) on OSC 1 and 5.
  • Noisy — 200 — the noise pair carrying a band of the sound, over three tone oscillators.
  • Super — 200 — JP-8080 territory: only OSC 4–6 (the Super stack), one widened to the maximum, the classic tables and the exotic ones alternating.
  • 200 noise FX — risers, impacts, wind beds, ticks and drones in SoundFX, named NFX nnn, where the noise generator is the sound.
  • The sequencer, as the ARP group's second page (engine/src/sequencer.rs): eight tracks of up to 64 steps, each with its own length (polymeter), and per-step note, velocity, gate, chance and ratchet. It shares the ARP clock and replaces the arp pattern when on; the held key transposes the whole pattern. The panel has the SEQ block under the arp: track keys, a 64-cell grid (click to arm a step, right-click to raise its note, alt-click to lower it) and the shared gate/velocity/chance knobs.
  • .nik soundbanks — encrypted, compressed, one file (src-tauri/src/nikpack.rs). The whole bank — names, categories, parameters — is deflated at the best setting and then encrypted as one stream; only a small header (bank name, count, salt, nonce) is readable. Key derivation is SHA-256 iterated 20 000 times over the salt and password, the keystream is a SHA-256 counter stream, and a tag over the ciphertext rejects a wrong password and a tampered file alike. The factory key is compiled in, so a shipped bank needs no password from the buyer and is still unreadable outside the app; a user export always needs one. Import asks every time. Right-click a folder to export it whole, drag a .nik onto the browser to import it, and imported banks land in Users/<bank name>/… so they never mix with the factory sounds.
  • The patch browser now warns before Rebuild patches: a dialog that says the factory patches reset to their original values and every change is lost, with the destructive choice red on the right.
  • User patches live under Users/ only, in folders the player names; the save dialog offers those folders and can create a new one, and the browser shows them as Users › <folder> instead of mixing them into the factory categories.
  • Settings gained a soundbank password field (masked, kept in memory for the session rather than written to disk), used when an export is made without typing one.
  • Fixed:
  • Every ARP patch now climbs at least one octave. A range of 0 holds one note forever, which is not an arpeggio; arp_octaves defaults to 1 and the ARP seeds use 1 or 2.
  • The FX section (continued):
  • The gain was four faults at once, and the master bus was the smallest. The owner's report — "the OSC mixer shows a loud full sound but the Global mixer is almost not moving, something eats the gain" — was accurate, and the causes compound:
  • the master trim was a fixed 0.35 (−9 dB) applied after the mixer, which the OSC meters sit in front of, so a full-looking oscillator mix could never read full on the master VU. It is now 0.62; headroom is the brickwall limiter's job;
  • the reverb and the delay were crossfades, not sends. A 58 % reverb mix meant dry × 0.42 (−7.5 dB), so a pad that used its room lost more than 10 dB of itself before the master bus — the effects were eating the instrument. Both are now additive sends (fx.rs): turning up a reverb adds the tail, it does not turn the patch down. The character effects stay crossfades, because a chorus replacing part of the dry signal *is* the effect, and Patch Level covers their few dB;
  • patch loudness was just the sum of the author's oscillator levels, so a four-oscillator pad and a one-oscillator pluck were dramatically apart. Every patch now mixes its oscillators to the same sum;
  • a band-pass or vowel filter passes only a few narrow bands, 6–10 dB below a low-pass at the same settings, and a low cutoff removes the harmonics that carry a saw's energy. Patch Level compensates for the filter in use. SynthParams' full velocity (64 = "the gain *is* the velocity") is now 40 per patch, so a keyboard sending 0.6 no longer makes the whole bank sound quiet. A new test, every_seed_has_a_comparable_loudness, renders all 100 seeds through the real master trim and limiter and writes target/bank-loudness.txt.
  • The vowel filter picked the wrong vowel, which is why the choirs sounded like strings. It chose the vowel from cutoff_hz / (sample_rate × 0.49) — a frequency compared against a linear fraction of Nyquist — while the knob reaches the cutoff through an exponential 20 Hz…20 kHz map. Every cutoff below ~84 therefore selected the *first* vowel, so Aah Choir, Ooh Choir and Vowel Solo were one formant set played at three cutoffs: a saw through a single static vowel reads as a bowed string. filters.rs now undoes the map so the knob position chooses the vowel (13 = A, 38 = E, 64 = I, 90 = O, 108 = U), and the Vocal patches use four different vowels and a formant order.
  • The bank was mono. One seed out of a hundred panned an oscillator and six moved the pan. voice() now spreads a polyphonic patch's oscillators around the centre (the loudest stays put) and gives long patches a slow pan; bass sets width: Some(0.0) and stays in the middle.
  • Echo was used by 15 seeds and shimmer by 7, which is why the pads had nowhere to sit. 53 seeds now carry an echo and 18 use the shimmer room: every Pad and Cinematic patch has an echo or a shimmer, and the strings, keys and plucks hand their tails across the field with a ping-pong.
  • Every authored patch ran the engine's starter FM algorithm, which silenced its second oscillator. SynthParams::default() selects algorithm 002 — the "saw carrier, sine modulator" starter patch — and voice() inherited it, so on nearly every patch OSC 2 was not heard at all: it was taken out of the mix and used to frequency- modulate OSC 1. A pad's second saw was bending the first instead of doubling it, which is metallic, not warm, and is a large part of why the bank did not match its own intents. voice() now starts on algorithm 001 (all carriers) with the FM section off, and a patch that wants FM asks for it in the new Voice::fm. Asserted by every_switched_on_oscillator_is_actually_heard and fm_is_off_unless_a_patch_asks_for_it.
  • The arpeggiator ignored the patch. Every arpeggio in the bank was up at 1/8 whatever its name said: "Down Arp" climbed, "Chord Arp" played single notes, and "Sixteenth Engine" was not a sixteenth. Voice now carries arp_mode and arp_division, so Down Arp descends, Chord Arp plays blocks, Sixteenth Engine and Fast Sequence are 1/16, Glass Arp and Down Arp 1/8, Bell Arp 1/4, Satellite a slow up-down. Asserted by the_arpeggios_differ_from_one_another.
  • The bells were sine pairs, not bells. The Bell category's five voices all used two plain sines or pulses, so the inharmonic partials that make a bell a bell were absent. Bell Choir, Bell Keys, Tubular and Bell Arp are now real DX7 voices with a struck bell's own partials (1 : 2.76 : 5.40); Music Box and Chime Wash are the Karplus string excited by metal and by glass.
  • Patches that named a mechanism they did not have were rewritten rather than re-described: FM Growl now really runs a 1.5:1 FM modulator (7.02 semitones) instead of two unison sines; Sync Scream really hard-syncs the carrier on a modulator a fifth above; Hollow Bass really is a 25 % pulse (width 32, not a square); Tension Bed's "dissonant stack" is a tritone rather than 41 cents of drift.
  • A DX7 patch no longer writes its 0–31 algorithm into the global 144-algorithm FM field, where it silently made some plain oscillators modulators. The algorithm lives on the oscillator (dx7_algorithm), which is what the engine reads, and each DX7 patch now states its own six operator ratios, levels, feedback and decay.
  • Changed:
  • authored::AUTH_VERSION is 6 and the bank is 1 800 patches: 1 000 derived from the 120 seeds plus the four 200-patch families.
  • The muted patches were replaced, not re-described. The owner's verdict was that a sound which is barely noticeable is garbage: Whisper is now Vowel Tide (a choir morphing through four vowels), Muted Keys is Studio Piano (a close piano that plays forward instead of thudding), Muted Brass is Fanfare (bright, wide, open), Muted Guitar is Steel Pluck (a struck steel string with a ping-pong), and Underwater is Tidal (the same granular water, but open and carrying).
  • authored::AUTH_VERSION is 5, so every existing install regenerates the bank.
  • The bank is 1 000 patches, derived from the 100 authored seeds. The owner asked for 1 000 with a fixed shape: 75 patches at each oscillator count 1…8 (7.5 % each) and 400 merged (two seeds layered into one). bank() in authored.rs builds it deterministically, so the same build always produces the same bank:
  • *Thinning* a seed keeps its strongest oscillators and gives their level to the survivors; *thickening* adds detuned, octave-shifted copies of its own oscillators at reduced level. Either way it stays the same sound at a different density, and the patch really uses the count in its name (asserted by the_bank_has_the_requested_size_and_shape).
  • Merged patches keep the first seed's filter, envelope, character and room, and take the second's oscillators into the free slots. All 400 pairs are distinct.
  • Oscillator slots are preserved, never re-packed, because the engine picks the special type by slot (OSC 4–6 Super, 7 Karplus, 8 DX7): moving one would silently turn it into a plain saw. AUTH_VERSION went 1 → 2 for this, so existing installs rewrite the bank and the writer sweeps away the 100-patch files.
  • The factory bank is now hand-authored, one folder per category. The 2 000 seeded patches are gone: the owner's verdict ("clic clic shhh") was right, and no amount of filtering fixes a bank that nobody composed. src-tauri/src/authored.rs holds all 100 as a name, a category, a mood, the sentence the sound was written from, and a Voice of choices (oscillators, filter, envelope, one character effect, room). One code path (voice()) builds every patch from those choices, so the invariants that stop a patch being silent hold for all of them by construction, and tests check the count, unique names, categories (15), no silent patch, no pitch LFO that is a trill, and that the bank exercises chorus, delay, reverb, the granular stage, the arpeggiator, ring modulation, the analog filter and the special oscillators. The seeded generator stays compiled only for tools/patchaudit. The 100 seeds are Pad 10, Bass 10, Lead 9, Keys 9, Strings 8, Pluck 7, Drone 6, Arp 6, Cinematic 6, Bells 5, Brass 5, Vocal 5, Ambient 5, SoundFX 5, Organ 4; the 1 000-patch bank keeps every category as a folder.
  • Added:
  • Mutate (F8, title bar). A musical nudge of the patch that is playing: tuning ±4 cents, pan, cutoff ±5 (bounded by the filter family so it can never reach the silent corner), resonance, envelope times ±12 %, LFO rates ±8 %, reverb/delay/chorus/drive depth. It never touches what makes a patch *that* patch — the oscillator sources, the filter type, the effect selection, the voice count. Verified over 400 runs: identity unchanged every time, no patch pushed into silence, no level or envelope time zeroed; twenty presses move a cutoff from 20 to 9 and an attack from 1.2 s to 1.9 s, i.e. a different take of the same sound.
  • Fixed:
  • Import Hydra no longer writes empty patches. The importer has no byte-to- parameter map (hydra_map.json does not exist), so it mapped nothing and saved the frontend's *default* patch under every Hydra name — about a thousand browser rows that all sounded the same and ignored every control, which read as "the patches don't load". It now refuses to run, with the reason, and writes no files. The mapping is documented as blocked in engine/ref/patch_mapping.md (it needs the full NRPN table). The Import Hydra button is disabled with that explanation until a map exists. Delete %APPDATA%\nikkosynth\patches\converted to clear the old ones.

Version 0.2.35

v0.2.352026-11-02
  • Fixed:
  • The bank had a shared wobble, and vibrato that was a trill. Every Lead got LFO 1 → pitch at up to 1.2 semitones, and LFO 1 is a bare 4 Hz sine with no fade — so the whole bank warbled the same way on every note. Vibrato is now 5–6.5 Hz, at most 0.32 of a semitone, with a 0.15–0.45 s delay and a 0.35–0.9 s fade-in (shape_lfos), and the wavetable archetype no longer moves its scan with LFO 1 (a 4 Hz wobble); it uses LFO 3. A sweep on cutoff, pan or scan is held to 0.05–2 Hz so the bank does not turn into one long tremolo.
  • The factory bank generated silent patches. The rotation puts the three high-pass types (hpMs20, hp3Ler, hpStn12) into the bank, and every archetype then wrote a *low-pass* cutoff — 45…125, which means "wide open" on a low-pass and "almost everything filtered out" on a high-pass. Those patches were generated dead: the mixer's meters danced (they read the oscillators, before the filters) while the final output was silent, which reads as a broken synth. A high-pass is now given its own range (knob 10…40 = a 21…176 Hz bass cut) instead of a mirrored low-pass value, and GEN_VERSION is 7 so every installation regenerates its bank. New test no_generated_patch_is_silenced_by_its_filter covers all 2 000 patches.
  • A patch can no longer be silently killed by a filter setting without saying so. A high-pass above ~4 kHz (or a low-pass at the very bottom) passes almost nothing: the owner's patch had HP 3-Ler at Cutoff 104, which is 5551 Hz — a C2 fundamental comes out at −77 dB. The synth looked broken because the mixer's meters keep dancing (they read the oscillators, *before* the filters) while the master VU, the spectrum and the speakers are silent. The VCF now shows an amber warning naming the frequency and what it does, with an Open it button that sets Cutoff where the filter passes the whole note (47 Hz for a high-pass, 8.4 kHz for a low-pass). filterSilent() is a pure predicate over all 18 types: it flags the three hp* above 96 and the eleven lp* below 12, and never the band-pass, the SEM morphs or the vowel.

Version 0.2.34

v0.2.342026-11-02
  • Added:
  • The EQ curve is draggable. The five bands' handles were drawn and inert; grab one and move it and it sets frequency (left/right, log scale) and gain (up/down, ±24 dB), with a readout naming the band and its numbers while you drag. The curve redraws live, the band's frequency is clamped to that band's own knob range (the high shelf cannot go below 1 kHz), and turning any EQ knob now redraws the curve too — it used to be drawn once and never updated.
  • Touching the curve switches the EQ on. Every effect plate starts bypassed, so shaping the curve did nothing audible, which reads as "the EQ is broken". An explicit edit on the curve itself now lights the plate's key (a fresh patch is still dry).
  • The VCA's signal-path stages now navigate: each of OSC / D.FILT / A.FILT / VCA / FX is a real button that jumps to that section (opening the right VCF tab, and unfolding a folded destination) with a brief ring on the plate it lands on. A caption under the knobs says what the strip is.
  • Fixed:
  • The VCA signal-path pills are centred, each at its natural width. Sharing the width out equally stretched "OSC" and squeezed "D.FILT", which read as ragged.
  • The DX7 algorithm diagram's digits are half the size. They were set to fill their boxes (min(bw × 0.85, bh × 0.62)) and read as the subject of the picture rather than labels on it; they are now min(bw × 0.42, bh × 0.31) with a 4-unit floor, so the six-operators-on-a-row algorithms (32, 25) stay readable. Sizes are in the SVG's own units, so this holds at any interface zoom.
  • A drag on the EQ pushed two values (frequency and gain) through one coalescer, which holds a single pending edit — only the gain reached the engine while the dot moved to the new frequency, so the sound and the picture disagreed. One coalescer per parameter now, both verified in a harness that asserts the two set_param calls.

Version 0.2.33

v0.2.332026-11-02
  • Fixed:
  • Silence after switching the digital filter to a high-pass. A low-pass is wide open at Cutoff 128 and closed at 0; a high-pass is the exact opposite. Changing the type with the knob left where it was therefore turned "no filtering" into "everything filtered out" — the synth went silent while the mixer's meters kept dancing, because those meters read the oscillators *before* the filters (the master VU, the spectrum and the footer's peak all read after, and all read zero). Crossing between the low-pass and high-pass families now mirrors the Cutoff knob (128 ↔ 0), so "open" stays open; low-pass ↔ band-pass ↔ vowel keep the knob where it is.

Version 0.2.32

v0.2.322026-11-02
  • Fixed:
  • The splitter drag works, and it travels the whole way. 0.2.31's pass fixed the layout (#app no longer scrolls, both heights pinned) but the divider still did nothing on a drag, while a double-click worked. Cause: applyLowerHeight fires a synthetic resize so the canvases redraw, and the resize listener re-applied the split — the two called each other 24 deep on every mouse move (measured in a harness: 172 calls for 6 moves), each one forcing a full re-layout. The throw landed inside pointerdown, so the drag never started. Now a re-entrancy flag stops the loop (7 calls for 7 moves) and the splitter is driven from the pointer 1:1.
  • The strip can now take the whole window, or give it all away. The clamps were 90/120 px; they are gone, so the divider runs from "top fills the window" to "bottom fills the window", like a window splitter. .panel's inline min-height: 0 lets it collapse. A collapsed strip is remembered too (a splitTouched flag distinguishes a deliberate 0 px from "never touched").
  • sharedHeight() now subtracts the app's own padding, so the footer is no longer clipped by 2 × --gap at the extremes.
  • The splitter stays under the mouse at every zoom, and the bottom never moves. At 150 % the divider leapt ~100 px away from the pointer on the first move. Under CSS zoom (the browser fallback in zoom.ts) getBoundingClientRect() reports zoomed pixels — 1278 for a box whose clientHeight is 852 — while clientHeight, offsetHeight, computed styles and style.height are all layout pixels. The old code measured the app with clientHeight, the chrome with rects and the drag with the raw clientY, i.e. in three different spaces. Now every height is converted to CSS px (cssHeight() = rect ÷ zoom) and only the pointer delta is divided by the zoom, so the divider tracks 1:1 at 100 %, 125 %, 150 % and 175 % (verified: −90 px of pointer = −90 px of divider, at both 100 % and 150 %).
  • The footer is pinned to the bottom. The panel is no longer given a computed height: it is flex: 1 1 0, i.e. whatever is left, so no rounding can accumulate and push the last row off the window; the strip is flex: 0 1 auto so it, not the footer, gives back a stray device pixel. Measured: the gap under the footer is 10 px at 100 % and 15 px at 150 % — constant through a drag, at both extremes, and through a zoom change.
  • A resize no longer destroys the chosen split. The applied height was clamped to the window and the clamped value was then remembered, so shrinking the window permanently shrank the strip (420 → 269 px, and it stayed there when the window grew back). The *requested* height is now kept separately and re-clamped on every resize, so it comes back (420 px again).
  • Added:
  • Tab swaps the two extremes — top filling the window ⇄ bottom filling the window. It is ignored with any modifier, while a dialog is open, and when the focus is in a field, a select or a list, where Tab still moves focus.

Version 0.2.31

v0.2.312026-11-02
  • Fixed:
  • The splitter: a first pass — #app no longer scrolls and the panel and the strip both take an explicit height. It still did not drag; see the real fix under [Unreleased].
  • The VCF's comment now describes the selected filter. It was a fixed "18 models · first in the chain". Each of the 18 digital types has its own line (slope, resonance character, what Morph sweeps), rewritten on every change, and the analog tab's line follows the MINI / SEM / 303 / ARP circuit.
  • The Voices knob is a ± bar. 16 discrete steps over a 270° turn needed ~13 px of drag per voice and read as "it does nothing". The control is now [−] ▇▇ 8 ▇▇ [+]: a click steps, holding repeats, the track drags straight to a count, the wheel and the arrow keys work, and the number is shown in place.
  • Added:
  • Particle on/off key, in its section heading like the ring modulator's. Off is a true bypass — the Mix value is kept and the delay buffer keeps filling, so switching it back on restores the sound. New particleOn parameter, ParticleDto.on and ParticleParams.on (engine/src/particle.rs), with a bypass test.
  • Function keys printed on the title-bar buttons (a small F1…F10 badge), driven by the same table that dispatches them. Mapping: F1 Save, F2 Patches, F3 Settings, F4 About, F5 Bright/Dark, F6 Register, F7 Keyboard, F10 Skins. The bar's gaps and button padding were tightened so it still fits one line at the 1200 px default window.
  • Changed:
  • The VCA sits under the VCF, as on the hardware, in one panel column; the Mod Matrix takes the slot the VCA held in the row, instead of living inside the Mixer plate. panelGroups() is now the single list of panel groups, shared by mount and rebuildPanel, so an undo cannot lay the panel out differently from a start.
  • Final Mixer: the stereo meters are the plate's left-hand column — half their old width and the full height of the group (they were a 38 px stub), with the gain/pan, Voices and MIDI columns to their right.
  • The master meter is a real VU ladder:
  • The master meter now reads like the hardware: silence at the bottom, green from −60 dB up to 0 dB, amber at +2/+4 and red at +6/+8, with the dB scale printed between the two bars and 0 dB the one number in full contrast.
  • The rows sit on a meter's uneven dB ladder (−60, −40, −28, −20, −16, −12, −8, −4, −2, 0, +2, +4, +6, +8) rather than an evenly divided range, and the whole thing is one CSS grid — [L row] [dB] [R row] — so the numbers cannot drift off their rows. (Built as three independent flex columns, the 1 px segment gaps against gapless labels accumulated a pixel per row and the scale pointed at the wrong segment. Checked in a mock built from the real stylesheet: every segment and its tick match to 0.1 px.)
  • A peak-hold line snaps to the loudest row and falls back ≈0.4 dB per poll.
  • The bar resolves 4× finer, the numbers are untouched. One block per printed mark meant a lit step could be 20 dB wide (−60 → −40) and only 2 dB at the top, so the bar jumped in big lumps. Each printed step is now drawn as VU_SUB = 4 sub-segments (vuLadder()), each lighting on its own quarter of the step — 56 levels per bar instead of 14. The 14 dB labels are unchanged: same text, still one per printed mark, measured at a 19.07 px pitch with 0.01 px drift. Measured resolution: 8 blocks lit at −30 dB where there used to be 2 (4.00×), ~3.7× through the rest of the scale (the top steps were already 2 dB). The peak line is positioned continuously (vuPosition()), so it reads finer than the blocks it sits on. Sub-segments are 4.1 px tall in the default strip, 1.8 px in a 200 px one, with a 0.5 px gap so they stay blocks and not stripes.

Version 0.2.30

v0.2.302026-11-02
  • Automatic build.

Version 0.2.29

v0.2.292026-11-02
  • Automatic build.

Version 0.2.28

v0.2.282026-11-02
  • Build and release scripts:
  • buildwin.ps1 / buildunix.ps1 / buildmac.ps1 / buildweb.ps1, on NikEdit's model and all self-elevating: Windows locally; Linux (.deb, .rpm, AppImage, binary) and macOS (.dmg) on GitHub Actions (ci/build.yml -> .github/workflows/build.yml), downloaded into binaries\ and website\downloads\; the site uploaded over FTP 10 files at a time, with one retry and a non-zero exit on any failure. commit.ps1 (one folder up) now includes Nikkosynth.
  • Only the current version is ever published: older files move to binaries-old\. (buildall.ps1 used to copy every file of binaries\ back into the downloads, so 0.2.23 and 0.2.24 were still offered.) SHA256SUMS.txt covers all three platforms.
  • The app's update check works: the site now publishes version.json, the file the app asks for (it only had update.json), and site.json follows the app's version.
  • Fixed (code audit, 2026-11-02):
  • Wavetable factory patches no longer play a saw. 313 of the 338 wavetable patches pointed at third-party tables (indices 451–3118) that exist only in the project folder, so on a customer's machine they fell back to a plain saw. The bank now uses the 451 built-in tables every installation has (GEN_VERSION 6, so the bank regenerates on first launch). The app also loads the third-party folder and a user wavetables folder whenever they exist, and patches find their table again by name if the catalog order ever changes.
  • Patch Level no longer cuts the volume by 30 dB. The knob is 0–128 (64 = unity) but the backend clamped it to 0–2. MIDI CC 7 now spans the same 0–128 range. Patches saved with the broken value (a level of 2 or less) are restored to unity when loaded.
  • Patches are stored in the per-user data folder (%APPDATA%\nikkosynth\patches on Windows) instead of next to Info/patches, which only exists in the project tree. An installed copy can now build its factory bank and save patches. User patches saved by earlier builds are copied over once, on first launch.
  • Switching the audio device really closes the old one. The old stream and its thread used to keep running forever.
  • MIDI edits follow the current device's sample rate after a device switch (they used the launch-time rate, so timings and filters drifted ~9 %).
  • The Final Mixer gain is applied at launch and after a device switch, not only after its knob is next moved.
  • The audio output reopens by itself when the device disappears (unplugged, disabled); before, the app went silent until restarted.
  • Sustain pedal: a key struck again while the pedal held it is no longer cut when the pedal lifts.
  • Unison: a note-off on one MIDI channel no longer ends the same pitch held on another channel.
  • The window no longer piles up timers. Every panel rebuild (undo, redo, patch load) started two more polling timers that never stopped. All meters now share one poll, paused while the window is hidden.
  • Knob edits can't save a stale patch. Replies from the backend could arrive out of order and overwrite the saved/undo snapshot with an older one.
  • Licence: a build made with the placeholder public key now says so in the registration box instead of "key does not match", and a release build refuses to compile with the placeholder (NIKKOSYNTH_ALLOW_PLACEHOLDER_KEY=1 overrides for local test builds).
  • Patch names such as CON or NUL can be saved on Windows.
  • Changed:
  • Real-time safety: the audio callback no longer frees old parameter sets (they go to a housekeeping thread), no longer locks the analytic-table cache, and no longer allocates for meters, note-offs, aftertouch or DX7 knob moves.
  • Faster DSP: sines come from a 4096-point table (error ≈ 3·10⁻⁷, −130 dB); DX7 operators skip recomputing a sine whose input has not changed; the DX7 detune factor and the digital filter's coefficients are cached until their parameter changes.
  • Less IPC: one meters call replaces three polled commands; the spectrum fetches only the 4096 frames it analyses, and nothing when no new audio exists.
  • Security: patch delete/move/load commands only accept .nikkosynth files inside the patch folders; the webview has a strict content security policy and no file-system asset access.
  • The wavetable cache keeps at most 32 tables (plus the patch's own) instead of every table ever previewed.
  • 17 missing UI strings translated into all ten languages.
  • CI on GitHub Actions (.github/workflows/ci.yml): TypeScript, clippy with warnings as errors, engine and backend tests, patch auditor build.
  • .gitignore keeps build outputs, the 4 GB reference folder, installers and website/site-config.php (secrets) out of git.
  • Changed:
  • The factory bank is now 2,000 curated patches, not 20,000. Every one of the 20,000 was rendered headlessly and measured (tools/patchaudit): 682 were completely silent and thousands more too quiet, and whole runs were near-duplicates of the same timbre. The bank is now the loudest, most distinct ~2,000 — silent and too-quiet patches are gone, near-duplicates are clustered and only the loudest of each kept — while still covering every oscillator type, all 18 digital filters, all 5 reverbs, all 32 DX7 algorithms, all 12 moods, the Particle stage and the arpeggiator. The keep-list is baked into the generator (KEEP in patchgen.rs), so the bank is reproducible, and stale files from the old bank are removed on rebuild.
  • The buy page is now a real shop (same flow as Nikkomander): licence-type selector, a seat stepper with the volume tiers, Google Pay, Binance Pay, six cryptocurrencies with QR codes and live rates, the store links and a FAQ — served from order.php / binancepay.php on the server. Company pricing is now set: 1–9 seats $89, 10–24 $79, 25–49 $69, 50–99 $59, 100+ $49 (was "contact us" above 9), and the same ladder is mirrored in license.rs and the website. The contact form now pre-fills from the buy page's "contact us" links.

Version 0.2.27

v0.2.272026-11-02
  • Automatic build.

Version 0.2.26

v0.2.262026-11-02
  • Automatic build.

Version 0.2.25

v0.2.252026-11-02
  • Added:
  • The oscillator's own envelope is drawn and editable. ENV Source = Self used to point at a fixed shape no control could reach; the page now shows the envelope as a curve with draggable handles — drag a stage sideways to lengthen it, drag the sustain handle up or down for the rest level — plus a one-line readout. It appears only when ENV Source = Self, since with ENV 1–3 selected the shape is not used. This is what makes the eight oscillator envelopes real, and with the five global ones it completes the manual's thirteen.
  • Fixed:
  • The two VCF filters switch on and off individually again, and only that. Each tab carries its own ON/OFF key at the top of its controls, and the "at least one filter must stay on" guard is gone — each key simply switches its own filter, so both can be off. Bypassing one dims that filter's controls and nothing else.
  • Invert sits under ENV Source instead of beside it, since it belongs to the waveform rather than to the envelope choice.
  • Fixed:
  • The default performance routes were in the defaults but not in the patch you were running. A patch saved before the routes existed (Mod Wheel → LFO 1, Pitch Wheel → Pitch, PolyAT / MPE X / MPE Y → the digital filter cutoff) replaces the defaults outright when the app restores it, so the matrix showed the old single route and the wheels did nothing — on every launch. The routes are now merged into a restored patch once, guarded by a stored version, so a route you delete on purpose stays deleted. Factory patches carry the mod-wheel route too (GEN_VERSION 4), and a patch loaded from the browser gains any it lacks.
  • The mod matrix shows all six default routes without scrolling. Its list was capped at 190 px, so three of them sat behind a scrollbar — which reads as "the route is missing". The cap is 330 px now and longer matrices still scroll.
  • Added:
  • A user and technical manual: documents/Nikkosynth-Manual.docx and .pdf, 41 pages, with every figure clickable to open its full-resolution image in documents/figures/ — a screenshot stays readable without a magnifier. Part I is the user manual, Part II the technical and algorithmic reference, Appendix A a full parameter table, Part III the licence, updates and copyright. Thirteen figures, including a zoomed picture of every panel group, the Settings box, the patch browser and the skin gallery.
  • A website in website/, served from https://nikkosynth.nikkoid.com: home, support, downloads, buy, changelog, terms, privacy and contact, built from one shell (build.py) so the header, the Our Apps menu, the language picker and the footer are identical to the other Nikkoid sites. Logo-derived favicons, a live spectrum in the hero, and the full build archive in website/downloads/.
  • Changed:
  • The personal licence is $89 (was $19). license.rs PRICE_PERSONAL, the frontend's offline default, PLAN.md and the manual all agree. The three UI strings that still promised *"free for personal use"* now state the price, in all ten languages — they contradicted a paid licence and would have confused buyers at the registration box.
  • Changed:
  • The DX7 algorithm moved onto the DX7 oscillator's own page, into the space that was empty there: the diagram of the routing in use, with ‹ Algorithm n › buttons under it to step through the set — and the wheel over the diagram steps too. There is deliberately no list: only the selected algorithm is on screen. The chooser grid that 0.2.24 put in the FM plate is gone, and the plate is back to its natural width.

Version 0.2.24

v0.2.242026-11-02
  • Added:
  • The 32 DX7 algorithms are drawn in the FM panel, in the plate's empty lower half: a large diagram of the selected routing, then all 32 as a chooser grid. Carriers are filled, modulators are outlines, and the feedback operator has its loop — in the skin's colours, so the diagrams follow the selected skin. Clicking one re-routes OSC 8, the DX7 oscillator. The pictures are generated from the *same* edge table the DSP plays, so a diagram cannot show a routing the sound does not have.
  • Changed:
  • The Super oscillator (OSC 4, 5, 6) is now the real Super Saw algorithm, adapted to any waveform: * Seven copies, with the measured detune offsets (±0.110, ±0.0629, ±0.0195 of the fundamental) and the 11th-degree detune curve from Adam Szabo's analysis of the JP-8000 (How to Emulate the Super Saw, KTH 2010). The curve barely moves until about half the knob, then opens up steeply — which is what makes the control usable instead of one wide smear. * The centre/side gain polynomials from the same paper: the centre voice fades linearly while the six copies swell on a parabola. * A pitch-tracked high-pass at the fundamental on the output — the Super Saw's signature, and what clears the aliasing noise below the note. * Free-running phases: every note-on gives the seven copies new phases, so repeated notes are not identical. * Any waveform: super-sine, super-triangle, super-square, super-pulse and even a super-wavetable — the copies play whatever the oscillator's source is.
  • The Super's knobs are now Detune and Mix. The old Voices knob was wired to nothing (the copy count was fixed inside the engine) and the Delay/de-sync knob had no counterpart in the measured behaviour; both are gone.
  • Fixed:
  • The DX7 oscillator was not doing FM at all. Its modulation input was accumulated and then never applied to the operator's phase, so every algorithm was a bank of six plain sines and Feedback had nothing to act on. It now phase-modulates properly.
  • The DX7 played three times too high. Each operator's phase was advanced once per convergence pass (three passes) instead of once per sample, so every DX7 note came out a twelfth sharp. Caught by a measurement, not by ear.
  • The DX7 ignored the device sample rate — the phase increment was hard-coded to 44 100.
  • Old patches and the factory bank regenerate with the new Super (GEN_VERSION 3), and patches saved with superSpread/superMangle keep their width through serde aliases.

Version 0.2.23

v0.2.232026-11-02
  • Added:
  • Function-key shortcuts for the title bar. F1 Keyboard · F2 Save · F3 Patches · F4 Settings · F5 About · F6 Bright / Dark · F7 Register · F10 Skins (reserved for the skin picker). F8 and F9 stay free. The key is appended to each button's tooltip, so it is discoverable without a manual, and a single table drives both the tooltip and the action so the two cannot drift apart. A shortcut never stacks a dialog over another dialog or over the "Please Wait" meter, never fires while typing in a field, and ignores modifier combinations — those belong to Ctrl+Z / Ctrl+± / Ctrl+0.
  • Fixed:
  • The Keyboard button's LED now follows the keyboard's real state, including when the keyboard's own chevron or F1 folds it (it used to keep whatever state it was built with, so a folded keyboard still read "on").

Version 0.2.22

v0.2.222026-11-02
  • Fixed:
  • The factory bank had seven dead channels on every patch. build() started each patch by setting every oscillator's level to zero but leaving all eight switched on, so the mixer showed eight live channels with seven faders at the bottom — "if one OSC is on, the mixer volume cannot be at zero". Empty channels are now switched off, osc()/wt() switch on the ones they give a level, and enforce_audible_channels guarantees the invariant across the whole bank: on means audible, off means silent. The default patch had the same fault and is fixed too.
  • The bank was mono. No generated patch panned anything. Now the loudest oscillator stays centred — the note's body must not wander off-centre — while the supporting oscillators are nudged 12–35 % to alternating sides, on two patches in three so the bank is not uniformly smeared. Never hard-panned.
  • Most patches used no effects. Every patch now gets a room (long for pads and strings, short for percussion), a character effect drawn per patch — chorus, rotary, tremolo, flanger, drive, or a tone tilt — and an echo on the patches that suit one; a third of those bounce the echo across the field. Which effects, how much, and whether a second and third join are all drawn from the patch's seed.
  • Added:
  • A "Please Wait" meter with a real percentage. Building the 20 000-patch bank, and then reading it back, now reports patch-progress from the backend and the window draws a circular percentage dial with the current stage ("Building the factory bank" / "Reading patches"). It appears only if the work outlasts 200 ms, so a quick job never flashes it.
  • The bank stamps itself and rebuilds when the generator changes. patchgen carries a GEN_VERSION written as factory/bank-version.txt; list_nikpatches rebuilds the bank when the stamp does not match, so a change to the generator reaches the files on disk instead of leaving 20 000 patches written by the previous code. The stamp is written last, so an interrupted run rebuilds next launch rather than being trusted. The bank is also built once per run instead of twice.

Version 0.2.21

v0.2.212026-11-02
  • Automatic build.

Version 0.2.20

v0.2.202026-11-02
  • Added:
  • The pitch wheel bends, out of the box. New patches ship a Pitch Bend → Pitch route, and the wheel value is now scaled by the patch's Bend setting, so the Bend knob still sets the range. Factory patches get the same route, so the wheel works on the whole 20 000-patch bank.
  • Global Pitch (±24 semitones) in the Final Mixer: a transpose for the whole patch. The pitch wheel offsets it live.
  • MPE X / MPE Y / MPE Y+ are now in the mod-matrix source picker; they were in the engine but unreachable from the UI.
  • Default matrix routes: Mod Wheel → LFO 1 rate, PolyAT → Digital filter cutoff, MPE X / Y → Digital filter cutoff.
  • Changed:
  • The Mod Matrix moved into the Mixer plate, centred directly under the spectrum. It is no longer the eighth panel group, which also gives the remaining seven groups more width.
  • The OSC detail page is a different surface: a dashed inset frame instead of a raised plate, and matte dark knobs with cyan pointers instead of the brushed silver face — so the optional settings behind the chevron cannot be confused with the row controls.
  • The registration form is half the size (340 px wide, with every gap, padding and type step halved).
  • Envelope tabs are compact enough that ENV 1–5 always share one line.
  • One VCF on/off key, inside the tab strip, bypassing both filters together. The analog circuit keys (MINI / SEM / 303 / ARP) now show their names instead of ON/OFF, and the circuit row lives inside the Analog tab.
  • Fixed:
  • The OSC power key lagged behind the mixer. makeSquareSwitch kept its state private, so the mixer could turn a channel off while the oscillator row still showed On. The key now exposes setOn, and the mixer repaints both.
  • The OSC row's horizontal scrollbar (the extra power column pushed the grid past the plate's content width; the row gap is 3 px now).

Version 0.2.19

v0.2.192026-11-02
  • Changed:
  • The window is 1200 × 1000 by default (was 760 tall). At 760 the panel needed 1274 px and the effects strip 1487 px, so on launch you saw about a quarter of the panel and a sliver of the strip. A window saved before this is lifted to 1000 px once (winHeightMigrated), then the user's own size is respected.
  • The bottom strip has a real height (330 px) instead of "whatever the flexbox leaves" (~116 px), so the Final Mixer and a useful part of the FX are visible. The splitter still drags it, and a double-click on the handle restores 330.
  • The mixer plate is wider (340 px minimum, 380 preferred): eight strips at 35 px were too narrow, now ≈ 42 px.
  • Fixed:
  • EQ and Reverb were not side by side. Their half-width plates had a 240 px min-width, which is more than half of the FX plate width, so min-width won and the pair wrapped onto two rows. The floor is 150 px now and the two share one row, equal width.
  • The Final Mixer overflowed the strip. It was 417 px tall in a 116 px strip; the pitch-bend range moved to the master column, the MPE key moved into the MIDI heading, the master VU is 38 px, and the "Any channel" chip is gone (the select beside it already says Omni). It is 310 px now and fits.
  • The mixer's pan caption collided between strips: at 42 px per strip the "R" of one strip nearly touched the "L" of the next. The L / R row is gone; the value under the bar already reads "C", "L 30" or "R 60".
  • Added:
  • Patch browser previews load — selecting a loadable .nikkosynth patch now plays it immediately (not just highlights the name); Apply keeps it, Cancel restores the patch that was playing. ASM bank rows still say "convert first".
  • Final Mixer is twice as wide, laid out in two columns (gain/pan + VU on the left, Voices + MIDI on the right), and gained its own fold button.
  • EQ and Reverb now sit side by side, half the FX group each.
  • The on-screen keyboard defaults to 78 keys.
  • Fixed:
  • The lower-section resize handle is no longer blocked: the handle is a full 16 px tall (its negative margins used to let the bottom strip paint over half of it), the drag listens on the window (so leaving the strip does not drop it), and pointer capture is best-effort rather than a hard requirement.
  • Folding the keyboard now hides the wheels too — they are part of the keyboard, not a separate block.
  • The OSC on/off switch moved to the left edge of each oscillator row, and it and the mixer's channel key now repaint *each other* (one switch, two places).
  • Super oscillators render band-limited: they read the same mip-mapped pyramids as the regular oscillators instead of the naive Wave::sample, so a saw/square no longer alias into the same harsh noise at the top of the keyboard — sine, triangle, saw and square are audibly distinct (a DTO-level test pins it).

Version 0.2.18

v0.2.182026-11-02
  • Added:
  • Envelope extras (manual p. 73–76), on all thirteen envelopes — the five global ENV 1–5 and the eight per-oscillator ones: * Per-segment curves for Attack (−64 Exp → 0 Lin → +64 Log), Decay and Release (−64 Log → 0 Lin → +64 Exp), each drawn live on the envelope graph from the same warp formula the engine runs. * Env Loop (Off / 2–50 / Infinite) with a Stage Loop range (Delay > Attack / Delay > Hold / Delay > Decay), so an envelope can repeat part of itself — a usable complex-LFO fallback. * Quantize (0 = smooth, 2–128 = levels), Legato / Reset / Freerun, the per-envelope Level master (0–128, 128 = unity), and the velocity response (Velocity Curve + Velocity > ENV, ±64; 0 = none). * The engine now reads the EnvDto extras in to_engine_params_at, the DTO setters accept envN<Field> names (env2AttackCurve, env2EnvLoop, env2StageLoop, …), and the envelope plate carries the new controls.
  • Fixed:
  • The decay stage previously rose from sustain back toward the peak (an inverted curve); it now falls peak → sustain, matching the manual, and a note released at level zero releases instantly instead of ringing for a whole release time.

Version 0.2.17

v0.2.172026-11-02
  • Added:
  • EQ effect: a five-band equaliser in the FX chain — low shelf, three parametric peaks (gain / frequency / Q each) and a high shelf, with a dry/wet mix. Coefficients are the RBJ Audio-EQ-Cookbook set in Direct Form II transposed, so a low-frequency high-Q band behaves. Flat by default: with every band at 0 dB the effect is bit-transparent, which a test enforces.
  • A live response curve on the EQ plate, drawn from the same coefficients the engine runs: log frequency axis, octave grid, the 0 dB rule, a lit line with a soft fill, and a handle on each band that turns amber for a boost and cyan for a cut. It is the graphic an EQ needs — five bands readable as one shape.
  • Fixed:
  • Two build failures in hand-reviewed files: audio.rs's build() was missing the voice_levels parameter, and ImportReport lacked serde::Serialize (the cause of the cryptic async_kind / IpcResponse errors from a #[tauri::command]). PatchInfo/PatchInfoDto also gained the mood field they were missing.

Version 0.2.16

v0.2.162026-11-02
  • Added:
  • Four analog filter circuits, selected by a row of four keys (MINI, SEM, 303, ARP) that light one at a time, in the same ON/OFF style as the rest of the panel: * MINI — a Huovilainen-model Moog ladder: four one-pole sections with a tanh nonlinearity *inside each stage*, a global feedback loop and 2× oversampling. The faithful ladder, not a linear cascade with one saturating loop. * SEM — an Oberheim SEM built as a topology-preserving state-variable filter with a cubic soft-clip in its feedback path, which is where the SEM's smooth resonance comes from. * 303 — a TB-303: a low one-pole high-pass into a sharp resonant core, calibrated so its peak lands near 5× (+13 dB) at full resonance, as the published 303 response does. * ARP — an ARP 2600, modelled as two cascaded 2-pole sections with their own local feedback rather than one four-pole ladder. Modelling it as a ladder with different coefficients made it measurably identical to the MINI; a test caught that.
  • Fixed:
  • The ladder detuned itself. Ladder::process took its feedback from the previous output, so the loop gain depended on the resonance alone; the resonance therefore peaked an octave below the frequency the cutoff knob named, and raising resonance was a cliff rather than a sweep. The delay-free loop is now solved (u = (x − k·S) / (1 + k·g⁴)), and the one-pole coefficient no longer divides by 2·sr.
  • Filter saturation was on the wrong signal. Clipping the summed input after the feedback subtraction still let the loop gain exceed unity — the 303 self-oscillated to 71 000× flat. Limiting belongs on the path that actually closes the loop.
  • Added:
  • Save your own patches. A Save button in the title bar opens a box with a name, a Category and a Mood; the patch is written to Info/patches/converted/user/<Category>/ as a .nikkosynth file and appears in the browser immediately. Saving the same name in the same category overwrites it.
  • Patch management in the browser: every saved patch has a move button (to another category) and a delete button (with a confirmation), and the list has sortable, resizable columns — click a heading to sort, drag its right edge to resize, and the widths are remembered. The favourite star is its own column.
  • Automatic mood tags on the factory bank. Each patch's mood is *derived from its own parameters* — an arpeggiated patch is Playful, a closed filter is Dark, mixer saturation is Warm, high resonance with an open filter is Cold, a slow attack with a long reverb tail is Dreamy. A test asserts all twelve moods occur, so the tags cannot quietly collapse into two.
  • The Digital and Analog filters now carry a one-line description each ("18 models · first in the chain" / "4-pole ladder · second in the chain"), and both use the same ON/OFF key style as the Ring Modulator.
  • A 20,000-patch factory bank, generated from Nikkosynth's own parameters. 19 categories — Pad, Ambient, Cinematic, Bass, Lead, Keys, Piano, Pluck, Bells, Arp, FX, SoundFX, Strings, Textures, Sequence, Brass, Organ, Vocal, Drums — built from musical *archetypes* rather than random numbers, with real names. The new styles each have their own character: Ambient is long attack and long tail with the reverb doing the work, Cinematic is a wide dynamic swell with a big room, Piano is all transient and velocity with no sustain and no filter movement, SoundFX leans on the granular stage and a long tail, and Textures sits between Ambient and SoundFX. Build factory in the patch browser writes them to Info/patches/factory/<Category>/, and they load like any other patch. The bank is seeded, so regenerating produces exactly the same patches. Across the bank every oscillator type is used (analog, wavetables, the Super stack, Karplus, DX7, the FM bank), all 18 digital filter types, all 32 DX7 algorithms, all five reverbs, the Particle stage, the arpeggiator and the mod matrix — all asserted by tests, so a future change cannot quietly leave a corner of the synth unused.
  • LFO extras (manual p. 78–84): Delay, Fade In, Phase, Level, Smooth (slew), Quantize, Trig Sync (Poly / Single / Off) and One-Shot, plus the full Step LFO — 2–64 steps with a draggable 64-slot editor and Semi Lock, so an LFO can play a melody.
  • .nikkosynth patch format and a Hydra importer. Text-based, one key = value per line, with the parameter block as a JSON line the frontend owns — so a patch loads through the same set_all path as everything else and the Rust side needs no JSON parser. Every converted file keeps the original Hydra body (1382 words) losslessly, so it can be re-derived once the parameter map is finished.
  • Import Hydra in the patch browser: converts all nine banks into .nikkosynth files under Info/patches/converted/ and reports what it wrote. Converted patches appear in the list and can be loaded; a bank row says so when it has no mapping yet instead of silently doing nothing.
  • nikkosynth words <bank> <i> prints a patch's body as words, for comparing against the hardware display while one parameter is edited.
  • Fixed:
  • The .patch body is u16 little-endian from a fixed byte offset, not bytes right-aligned to the end of the file. The name is NUL-terminated and space-padded into a 17-byte field, so the body always starts at byte 22. Body values above 32767 are negative (i16). See engine/ref/hydra_patch_format.md.
  • Byte 4 is the category, not an unknown flag — confirmed against all 992 patch names (label purity 0.66 vs 0.31 for any other candidate).
  • Known limit:
  • No body word is mapped to a named parameter yet. The supplied hydrasynth_cc_chart.csv carries nrpn_msb/nrpn_lsb columns but no values, and no other document holds the table. A converted patch therefore keeps the default parameters: it is named, categorised and loadable, and it does not yet sound like its original. Inventing a mapping would produce 992 patches that all play the same default — which is worse than an obviously unmapped one. The hydra_map.json table and the words/diff tools are the path to closing it.

Version 0.2.15

v0.2.152026-11-02
  • Voice rotation and the voice LEDs. Every note takes the next voice in turn (wrapping after the last), which is what keeps a repeated note from re-using the same oscillator phase. The Final Mixer shows a Voices knob (1–16) and one LED per voice that lights while that voice sounds, so you can watch the rotation.
  • MIDI receive modes: Omni, Unison or a single channel 1–16, with a 16-lamp ladder showing which one is active. *Omni* plays on any channel and a repeated key replaces its note; *Unison* also plays on any channel but stacks, so the same key from two channels sounds twice.
  • MPE (manual p. 151): each note gets its own channel, so its pitch bend, CC 74 timbre and pressure reach that note alone. MPE pressure is mapped onto PolyAT, as the manual specifies. New mod-matrix sources: MPE-X, MPE-Y Relative, MPE-Y Absolute. Pitch-bend range is a knob in the Final Mixer.
  • Polyphonic aftertouch from the on-screen keyboard: the vertical position of the press is the pressure, so the PolyAT routes can be tested without hardware.
  • Patch catalogue in the browser: folders by style decoded from each patch's embedded category byte, search across name/bank/style/designer, sorting by name, patch number, category or designer, and ★ favourites. Click previews, Apply keeps, Cancel restores. The dialog is capped at 80% of the window.
  • Undo / redo over the whole patch: Ctrl+Z and Ctrl+Shift+Z (or Ctrl+Y), plus two title-bar buttons that disable when there is nothing to step to. 120 steps.
  • Keyboard size: 37, 49, 61, 76 or 88 keys. Pitch and mod wheels in their own plate to the left of the keys; the pitch wheel springs back, the mod wheel stays.
  • Tooltips on the controls and every button, with a master on/off switch in Settings.
  • Changed:
  • Envelopes and Mod Matrix plates are about half their height, which gives the Final Mixer room for the voice and MIDI sections (separated by glowing rules).
  • An oscillator's row is 20% brighter with a green tint and outline while it is on, and each row now carries its own on/off key (the same parameter as the mixer's).
  • The knob pointer is a slim 2.5 px translucent line; the sub-buttons inside an expanded oscillator page are smaller, squarer and unglowed, so they read as secondary controls.
  • AudioMotion-style display options from the previous build unchanged.
  • Fixed:
  • Super oscillators ignored the waveform buttons. The Super kept the wave it was built with, so selecting sine or triangle changed nothing and "sine" was audibly not a sine. It now follows the oscillator's source, on live notes too.
  • Karplus materials are genuinely different instruments. Guitar and Piano run the string model (the piano with a hammer pulse, a detuned unison pair and stiff- string dispersion); Metal and Glass are struck bodies running a bank of inharmonic modes. Before, all four were the same string with different damping numbers, and their levels now match so the material is not a volume control.
  • The horizontal splitter no longer locks. The strip had a max-height: 46vh that silently capped every drag; dragging now lifts the cap, and the default cap is a percentage of the app box rather than a viewport unit.
  • Dead controls removed: Feedback and Phase were shown on the Super, Karplus and DX7 pages although those oscillators have no feedback path and their own phases.

Version 0.2.14

v0.2.142026-11-02
  • Thirteen visual styles for the spectrum, all in the dropdown beside the mode selector: Classic bars, Radial, Radial inverted, Radar (spin), Mirror ←→, Mirror ↑↓, Reflex, LED bars, True LED, Lumi bars, Outline, Rounded and Halo. They compose with every analyser mode (a radial *graph* draws as a polar contour) and with the VU palette. A PK button turns the peak holds on or off. Mode, style, palette and peaks are all remembered between runs.
  • The styles are written in our own renderer. They cover the same ideas as audioMotion-analyzer's display options, but that library is AGPL-3.0-or-later — its source header and package.json both say so, even though the LICENSE file in the downloaded folder is an MIT text naming a different copyright holder. Vendoring it would have made Nikkosynth a combined AGPL work, which is incompatible with selling it as closed-source shareware.
  • Fixed:
  • Lumi bars now carry the level in brightness. They were painted with the one shared accent gradient, so every bar looked identical and the spectrum was lost.
  • The radial graph no longer floods its interior with a fill; a closed polar polygon read as a blob rather than a spectrum.

Version 0.2.13

v0.2.132026-11-01
  • Automatic build.

Version 0.2.12

v0.2.122026-11-01
  • Patch browser (title-bar Patches): the nine .hydra banks as folders and their patches as rows (7 × 128 + 64 + 32 = 992 patches). Names only for now — the byte → NRPN sound mapping is still open.
  • Shareware registration, ported from NikPak. The registration form appears at launch when no licence is installed, the About box uses NikPak's layout (section label, name, version, copyright, distributor, licence status, web link, Licence terms), and the title bar carries the licence status next to a Register button. Keys are the existing NKS1 Ed25519 format.
  • Ten languages. French, Spanish, German, Italian, Portuguese, Dutch, Polish, Russian, Japanese and Chinese, each with every UI string. A language menu at the extreme right of the title bar shows the flag and the language's own name.
  • Ring Modulator section with its own title and ON/OFF key, and an ON/OFF key for FM in its plate heading. Both gate the depth rather than clearing it, so switching back on restores the patch exactly.
  • Mod matrix expanded to the manual's full documented set (p. 125–128): thirteen envelope sources (five global + eight per-oscillator), the unipolar LFO n+ variants, Keytrack, PolyAT/MonoAT, Mod Wheel, Pitch Bend, Exp/Sus Pedal, Voice Mod/Voice Mod+, MIDI CC as sources; per-oscillator Initial Level / ENV Level / Ratio / Feedback, the All-Osc group, D. Filter Drive / ENV 1 Amt / LFO 1 Amt / Level, A. Filter Pre-drive / ENV 2 Amt / LFO 2 Amt, VCA Level / LFO 3 Amt and LFO Rate/Level as destinations. Every one is covered by a test that fails if it stops changing the sound.
  • Changed:
  • The horizontal splitter between the panel and the strip below now resizes both (it drives an explicit height instead of a max-height), has a taller grab area with a visible grip, is remembered across runs, and double-clicks back to its natural size.
  • An enabled oscillator's name glows amber, the same lamp colour as the Karplus badge; a muted row keeps its dim grey name.
  • The title bar wraps instead of squeezing its contents at a narrow window.
  • Fixed:
  • Repeated notes no longer click. Striking a key that is still sounding used to re-use the same voice and reset its oscillator phases, filters and envelopes mid-cycle — a step straight to zero, i.e. a click on every repeat. The old voice is now moved to a tail pool and faded over ~4 ms while the new note starts in a fresh voice, so the two overlap and the edge is smoothed. Slot stealing fades the same way. A finishing voice can also no longer unmap the *new* voice that shares its note (which left a held note unreachable by note-off).
  • Karplus-Strong Material does something. Each material now has its own excitation burst (brightness, level and pluck position), not just a different loop, and changing it re-plucks the string — so it is heard on a note that is already ringing instead of only on the next one.
  • The Super oscillators had two Wider/Delay pairs. The always-visible row under OSC 4–6 duplicated the pair in the detail panel, both bound to the same backend values (so their readouts drifted apart). One pair remains, in each Super oscillator's panel, with a regression test that both knobs are audible on OSC 4, 5 and 6.
  • Changed:
  • Default skin is Nikkoid Blue, and the Bright/Dark button toggles it with Nikkoid Bright. The two Fractal Engine skins were removed.
  • The skin picker takes half the height it did and scrolls inside.
  • The expanded oscillator page is a shade lighter than its plate, and its disclosure chevron glows blue while open.
  • DX7: the Algorithm knob sits at the far right of the oscillator page.

Version 0.2.11

v0.2.112026-11-01
  • Fixed:
  • Dialogs no longer run off the screen when the interface is zoomed. They were sized in vw/vh, which under CSS zoom still describe the *unzoomed* screen. The overlay now pads the window and scrolls, and the dialog sizes itself against that box, so it fits at any zoom.
  • Dialog buttons stay reachable. Every dialog appends its footer *inside* the scrolling body, so with the 91-swatch skin picker the Apply/Cancel buttons scrolled out of reach. The footer is now sticky.
  • The disclosure arrows are twice the size and scale with the font. They had a font-relative glyph in a fixed-pixel box, which clipped it; both are now em, so they grow with the interface zoom.
  • The OSC waveform / wavetable name was invisible. The row's fixed columns summed past the plate's min-width, so the flexible name column collapsed to zero width. It now has a 64 px minimum and the OSC plate a matching minimum; a selected wavetable shows its real name (e.g. "Alweg 7") in accent colour. A patch saved before names were stored is back-filled from the catalog.
  • The Super oscillator's two knobs could not be found — they were inside the collapsed detail panel. Wider and Delay now sit on their own always-visible line under OSC 4, 5 and 6.
  • The Final Mixer no longer claims 320 px: it is sized to its two knobs and a compact stereo VU, and the arpeggiator takes the reclaimed width.
  • The wavetable browser auditions: click a table to hear it immediately, click another to compare, then Apply to keep it or Cancel / the ✕ to put the oscillator back exactly as it was.

Version 0.2.10

v0.2.102026-11-01
  • Fixed:
  • OSC 5–8 (the Super / Karplus / DX7 oscillators) were silent while their mixer meters moved. The special oscillators are complete voices, not FM operators, but they were routed through the FM algorithm's carrier/modulator split, so any algorithm where they land as modulators muted them. They now always reach the output (and never modulate another oscillator).
  • Changed:
  • The Super oscillator now matches the supersaw spec: the base wave is duplicated 10 times and summed with the gain held constant. Wider is the detune spread (five copies sharp, five flat), and Delay spreads the copies in time (0 = all trigger together, 1 = ~9 ms apart).
  • OSC rows show the waveform name in full (Sine / Sawtooth / Square / Pulse) and, for a wavetable, its real name from the browser (e.g. "Alweg 7") instead of a bare index.

Version 0.2.9

v0.2.92026-11-01
  • Automatic build.

Version 0.2.8

v0.2.82026-11-01
  • Fixed:
  • The Karplus oscillator (OSC 7) now plucks on the first note-on (it was silent until a note was re-triggered).
  • Switching an oscillator's Pitch mode to Ratio or Frequency no longer silences it: the ratio value is converted to keep the same pitch (Semitone ↔ Ratio ↔ Frequency).
  • Ring modulation is a real global ring modulator (a sine at the note frequency that ring-modulates the whole mix) and defaults to 0, so it is audible and clean by default. The per-oscillator Ring Mod mode is now full-depth.
  • The five envelope tabs are editable, each with its own DAHDSR curve and knobs.
  • The mixer channel on/off now greys the matching OSC row live; the special oscillators (4–8) start audible so they can be tested.
  • Both filters have an on/off key (with a small description for the Analog filter); the UI refuses to switch them both off together.
  • The collapsed keyboard keeps its header so it is always findable; the skin picker modal can no longer push its buttons off-screen at high zoom.
  • Mixer pans show their written value; double-click centres them.
  • The LFO scope now draws the real waveform (rate and depth).
  • OSC rows show their type (Analog / Super / Karplus / DX7 FM) and, where it applies, the selected waveform name.

Version 0.2.7

v0.2.72026-11-01
  • Automatic build.

Version 0.2.6

v0.2.62026-11-01
  • Special oscillator types (engine/src/special.rs), by position: OSC 4–6 are a Roland-style Super oscillator (N detuned copies with Spread + Mangle de-sync), OSC 7 is a Karplus-Strong physical model (guitar/piano/metal/ glass presets with damping, brightness, dispersion and decay), OSC 8 is a 6-operator DX7 FM oscillator with the full 32 algorithms, per-operator ratio/level and a feedback loop.
  • Particle granular stage (engine/src/particle.rs), applied to the mix of all oscillators before the FX: grains behave like particles that Density can separate (sparse) or stack (dense cloud), with Size, Spread and Pitch controls.
  • Final Mixer gain + brickwall limiter (engine/src/limiter.rs) and a stereo VU meter: a lookahead limiter caps the master bus at −1 dBFS, so no spike ever reaches the output regardless of the Gain knob.
  • UI ergonomics: double-click a knob resets it to its factory default, the numeric readout under a knob is editable (click, type, Enter), and a draggable divider between the top and bottom sections resizes the FX strip.
  • Fixed:
  • The expanded FX section no longer hides the Envelopes / Mod Matrix (the bottom strip is height-capped and scrolls internally), and those two groups are now foldable. The FM group's eight mode buttons wrap and its plate is wide enough that the panel never forces a horizontal scrollbar.

Version 0.2.5

v0.2.52026-11-01
  • Fixed:
  • FM parameters (Algorithm, Mod Index) and the other knobs without a local onChange no longer reset on reload: the patch snapshot is now refreshed from the backend's returned state after every edit, and it is flushed on app close so the debounce cannot drop the last change.
  • Ring modulation was amplitude modulation (carrier × (1 + modulator)), which kept the carrier and was nearly inaudible. It is now true ring modulation — a blend from the untouched carrier to full carrier × modulator — so it is clearly audible. (Note: the manual lists 7 oscillator modes; ring mod is a Hydrasynth-Deluxe addition kept deliberately.)
  • engine/ref/architecture_gaps.md — a full gap analysis of the engine against the Leviasynth Desktop Owners Manual and Algorithms PDF.

Version 0.2.4

v0.2.42026-11-01
  • Automatic build.

Version 0.2.3

v0.2.32026-11-01
  • Fixed:
  • Reverb was effectively inaudible after the FDN rework: the wet output trim was 0.4, putting a full-wet signal ~19 dB below the dry. The trim is now 1.6 (full wet ≈ −6.9 dB vs dry), and each type's tail is clearly audible and decays in the right order (room fastest, hall longest).

Version 0.2.2

v0.2.22026-11-01
  • Changed:
  • Shimmer reverb now uses a granular pitch shifter (octave + fifth layers with tone-shaping and soft-clip in the feedback loop), following Info/Shimmer-Reverb-main.
  • Room/Hall/Chamber/Shimmer reverbs use an 8-line Lexicon 224-style FDN tank (following Info/borato-224-master); the plate stays on its comb bank.
  • The multitap echo's feedback damping is now per-channel, so a mono input stays symmetrical in the default pan mode.
  • Multitap echo ping-pong: a Pan setting (Off / Bounce / Sweep) plus a Speed knob — bounce every echo, at 1/2 or 1/4, or a slow left-right sweep.
  • A modulation FX group, after the distortion and before the echo: Rotary (a Leslie sim — crossover-split horn/drum rotors with Doppler pitch modulation, directivity AM, inertia glide and two virtual mics, ported from the DAFx-02 model in Info/effect-main), Tremolo (sine-LFO amplitude modulation), Vibrato (pitch wobble on a modulated delay line) and Wah (a swept resonant bandpass with Auto/Manual mode). Each has its own bypass key and mix, matching the existing chorus/flanger/echo/reverb plates.

Version 0.2.1

v0.2.12026-11-01
  • Automatic build.

Version 0.2.0

v0.2.02026-11-01
  • Band-limited, mip-mapped oscillators: saw, triangle and pulse with no aliasing at high notes (engine/src/mip.rs), including a band-limited ladder for the 3,119-table wavetable catalog.
  • VCA module (manual p. 68–70): OSCs Level, VCA Level, Initial Level, LFO 3 Amount, Velocity > ENV and PolyAT, with the VCA Level ceiling the manual describes. PolyAT is real end-to-end (MIDI 0xA0 → the one voice on that key).
  • Oscillator page (manual p. 36–42): Initial Level / ENV Level split, ENV Source (Self / Env 1–3, with eight per-oscillator envelopes), Velocity, PolyAT, waveform invert, feedback, keytrack (±200 %) and phase, in an expandable per-oscillator panel.
  • Own spectrum visualiser (radix-2 FFT + Canvas2D): bars, fractional-octave bands and graph, plus a VU colour palette (green → amber → red) and a 3D Terrain view.
  • Audio diagnostics in the status bar: output peak and frame count, so a silent engine is told apart from a silent output path.
  • Changed:
  • Knob arc now starts at the bottom-left stop and fills exactly to the pointer — fixed a 90° start offset (SVG circles begin at 3 o'clock) and a dash-length bug (the ring is 276 units, not 100).
  • On/off keys are green when on and red when off, everywhere (mixer, FX, arp).
  • VU meters fill upward: green at the bottom, red at the peak.
  • Real factory wavetable names (Faebear, RanN, Salex, …) replace the invented ones.
  • Removed:
  • audioMotion-analyzer (AGPL-3.0) — replaced with the in-house renderer, so no AGPL code ships.

Version 0.1.0

v0.1.02026-11-01
  • Initial engine: 8 oscillators, 144 FM algorithms, 5 DAHDSR envelopes, 5 LFOs, 32-route mod matrix, digital + analog filters in series, FX chain (chorus, flanger, distortion, echo, reverb), arpeggiator, and the Tauri GUI (skins, i18n, zoom, persistence, MIDI and live cpal audio).