DSP
← devlog

You wrote, I fixed it: Niner v0.7.1 (and v0.7.2 a few hours later)

Niner v0.7.1 just shipped. It’s a polish patch — no new DSP, no new sequencer features, no new bounce options. What it does have is most of a bullet list a tester sent me the day v0.7.0 went out.

That’s the entire post. Someone wrote down what was wrong with the UI; one week later, here’s what’s different.

The seven bullets

I’ll quote them verbatim and then walk through what shipped.

1. diode is too long to fit in the display. make both displays in sat section about 2 full led characters longer — the knobs on the right of them should keep the same distance.

The SAT-section selectors went from 44 px wide to 68 px. That’s ~2 LCD characters of room, enough that dIOdE and tAnH sit inside the lit area with breathing room instead of clipping the right edge. The downstream knobs (DRV / MIX / CDRV / NDEC / ACC) hold their position relative to the display — they didn’t drift. The arrow glyphs got swapped to the slimmer ◂ ▸ at 12 pt to match the preset bar; the L/R buttons in the SAT row now look like the preset L/R buttons, which is bullet 2.

2. the left right buttons in sat section must look identical to the preset left right picker buttons.

Done in the same change. Same glyph, same point size, same dark inset.

3. any selected item must be easy and smooth to resize/scale by dragging a corner.

This is the load-bearing one. The layout-editor schema already had an OverrideEntry::size_scale field — I’d written it months ago, never wired it up. v0.7.1 wires it into instrument() so any selected element grows a bottom-right green corner handle. Drag it. The element scales from its center, clamped to 0.2..=5.0 (so you can’t accidentally make a chip occupy half the panel). Right-click resets back to 1.0.

The OUTPUT display reads size_scale too, so the lit area itself resizes live without the knobs moving — which means you can stretch the GR/BPM lit zone wider on a 3× UI without the DRV/MIX knobs sliding to the right. That separation took a small refactor: display_paint_w (the visual width) and wf_width (the layout width that drives knobs_x) are now independent.

4. the 120 bpm should also be in red to match the led display. same with the gr number 08 12 6 etc. and this section needs to be movable independent of the led display, so I can move it more to the right.

GR meter renders in RED_LED now, in-display, with ticks every 3 dB instead of every 6 — 0/3/6/9/12/15, evenly spaced. The “GR” label sits left-aligned with the BPM text. BPM was decoupled from the display offset, so it has its own drag handle and you can shove it wherever you want without the lit area following.

While I was in there, I also fixed a sneaky standalone bug: a startup race where one PipeWire/JACK buffer would arrive with playing=false before the host_ever_played flag flipped, and the standalone would lock itself into “I think I’m in a DAW” sync mode forever. New prerequisite: standalone only enters HOST mode if it has actually seen playing=true at some point. Quiet fix; I owe nobody a changelog headline for it but it’s there.

5. button corner rounding should be adjustable on all knobs and all corners of each knob, all at once — not only the bottom l r corners.

Not in v0.7.1. This needs a per-knob style override that lives in the layout editor schema, and the layout editor refactor in v0.7.1 was already touching too much. I’d rather ship corner-drag resize working everywhere than ship corner rounding half-built. It’s on the v0.7.2 pile.

6. the UI 1 x should be aligned vertically with KICK SYNTHESIZER — and v.0.7.0 should be on the same line. move all this closer to TEST button to make it fit perfectly.

Header chrome is now one line. KICK SYNTHESIZER · v0.7.1 · UI 1× packed tightly behind the TEST button, all three text elements instrument_text-wrapped so each is independently draggable. If the bullet hadn’t said “all on the same line,” I would have left the UI scale label below the title forever. Fresh eyes see what tired eyes don’t.

7. POST and CLAP buttons and leds should be same size, with the led on the RIGHT, like in LIM. leds right aligned at the edge.

All five status chips — COMP, LIM, PRECISE, CLAP, POST — now render at 8 pt mono with the same LABEL ● ordering: label on the left, LED dot on the right. They’re all instrument-wrapped, so you can drag any one of them. (You probably won’t, but you can.)

The thing nobody asked for

The round screws are gone. They’re hex-socket Allen bolts now — drawn procedurally, each corner with a slightly different rotation angle (TL = 0.30 rad, TR = 1.05, BL = 0.68, BR = 1.82) so no two look perfectly aligned. That’s the kind of thing that reads as “real metal” rather than “decorative chrome” in the peripheral vision while you’re focused on the kick.

The chassis was also re-baked with screws.enabled = false in the Blender preset, because if you drag a screw two pixels in the layout editor, you don’t want a phantom round circle peeking out from underneath. The procedural hex bolts are now the only screw rendering on the panel.

The new feedback channel

The most direct path is now [email protected] — a real inbox I read, no third-party processor, no signup. There’s also a form at /support with three radio buttons (bug / feature / just saying hi) that opens your mail client with the fields filled in; same destination, slightly less typing. If you prefer to file something public, the GitHub issue templates are live too.

The reason this exists at all is that the v0.7.1 release notes you just read started life as a markdown file someone sent me. The next release works the same way.

And then, a few hours later: v0.7.2

This part wasn’t in the plan, but the plan is rarely the plan.

While v0.7.1 was rolling out to AUR, the LED status-flash code that prints SAVED:… after a preset save was leaking text into the area where the TEST button lives. The fix was small — flash on the preset display itself, not the chrome strip — but small enough that it earned a same-day v0.7.2 instead of a “hotfix branch we’ll get to.”

Same patch also rolled in a refactor I’d been putting off: the factory preset bank used to be a 635-line ParamSnapshot literal in Rust, one giant struct per preset, hand-edited every time a parameter changed. v0.7.2 moves the bank to per-preset JSON files under assets/factory_presets/, loaded at compile time via include_str!. The factory_presets() function shrank from 635 lines to 30. The bank itself was retuned in production from saved files (so what you hear is now what I dial in, not what I typed); a new niner preset got added; the old 909old preset was deprecated out.

Two patches in one day is unusual; both were ready, neither needed to wait. v0.7.2 is what you get from yay -Syu niner or the latest release right now.

What’s deferred

For honesty’s sake, the v0.7.2 pile, in priority order:

  • Per-knob corner rounding (bullet 5 above), as its own layout-editor field.
  • Splitting the 256-line Plugin::process() into four named helpers so the next person reading it (probably me, in six months) doesn’t have to hold the whole thing in their head.
  • Persisting display mode (Waveform / Spectrum / Off) across DAW reload — currently it resets every project open.
  • A declarative macro for NinerParams to eliminate the triple-mirror between params, snapshot, and serde.

None of these block v1.0. v1.0 is still scheduled for July; that post will be a different kind of post.

Other Niner devlogs