Skip to content

Slim the arp view, and move the controls onto the bars - #14

Open
owenpkent wants to merge 8 commits into
mainfrom
feat/arp-macro-slim
Open

Slim the arp view, and move the controls onto the bars#14
owenpkent wants to merge 8 commits into
mainfrom
feat/arp-macro-slim

Conversation

@owenpkent

@owenpkent owenpkent commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Seven commits from one day of passes on the arp and the section bars. The
through-line: controls move out of bands that fold away and onto the bars
that survive the fold, and the window Keys opens in gets a lot shorter.

The arp

  • The All view is two boxed line cards and nothing else. The twelve
    slots and the Copy / Clear / Stop / Chain row went back to the per-line
    tabs where every one of those buttons already is; LTCH, PLAY and Chain
    left the rows. Each card draws its own captioned frame (LINE A / LINE B)
    and the outer box is gone, because a frame around both was the strongest
    cue they were one thing. ~110 px off the window.
  • A and B on the bar are power switches now, bound straight to
    arpOn / arp2On. They used to be a navigation tab with a separate On
    chip a few pixels away; the chip is deleted, and so is the macro card's
    own On toggle. An off line is scrimmed whole instead, with nothing on it
    disabled: a rate must be dialable before switching on, and a chord
    dropped onto an off line has to land. Details beside Anchor is the
    route from a card to that line's deep view.
  • A click on a chord card never feeds a line. The drag - onto a card,
    a letter tab, or a slot - is the only way in. The one left-click arp
    behaviour kept is a stop.
  • HUMAN split into H.TIME and H.VEL, and VOL became VEL: bipolar,
    centred on "as played", full left mutes. The multiplier is squared
    (hearing is logarithmic), multiplies after the engine's 0.05 audibility
    floor rather than before it, and bottoms at MIDI velocity 1.

The bars

  • The tempo is a plain number on the Controls bar, Ableton-style:
    BpmField is a juce::Slider subclass that overrides paint, so the
    APVTS attachment still drives it while no LookAndFeel ever draws a track.
    The < > pair beside it is the click-only path the mouse-only contract
    wants - the one part of "just a number" Keys cannot copy from a DAW that
    expects a keyboard for that field.
  • Root, Scale, Lock, Voices and MIDI Ch ride that bar too. They did not
    fit at the editor's floor, so the group has two sizes and measures
    which it can afford rather than assuming.
  • Size and Octave move to the Keyboard bar, Humanize to the Pads bar,
    and the Knobs chip is deleted rather than hidden. The Controls band is
    one row now; headerH drops 112 -> 52.
  • An Instrument chip gives KeysEditor its first extension point for
    something embedding it. Keys Host's own 44 px top bar is deleted with it.

Parameter layout changes - saved sessions

Three parameters appended per line (arpHumanVel*, arpVelTrim*) plus a
global bpmSync. Old sessions open sounding identical: Humanize carries on
as the timing half, migrateVelTrim folds Volume into VelTrim through the
same curve, and migrateBpmSync backfills the default.

Writing those migrations exposed a kit bug that had left every
absence-detecting migration silently dead (migrateStrumRange,
migrateRateMode included): state::load handed replaceState the parsed
root's own parameter child, and replaceState backfills absent parameters
into the shared tree before onExtra reads it. Fixed in
okstudio-juce-kit (StateHelpers.h); this PR pairs with that one.

The editor's minimum width moves to 1280, up from 1070.

Verification

Keys_tests: 166 cases, 2422 checks, all passing. Security review of the
branch found no HIGH or MEDIUM findings - the new drop target, the two
migrations and the velocity math all terminate in existing clamped choke
points.

The All view is now a 34 px header (A/B/All tabs, BPM, Launch Quantize)
over the two line rows: the twelve slots and the Copy/Clear/Stop/Chain
action row belong to the per-line tabs, and LTCH, PLAY and Chain left
the rows (Latch and Chain already had per-line homes; Play is new on
the band beside Retrigger, same arpKeys parameter). Takes ~110 px off
the window Keys opens in.

HUMAN is two knobs now, H.TIME (late-nudge) and H.VEL (velocity shave),
and VOL became VEL: bipolar, centred on "as played", full left mutes.

Parameter layout change, loudly: arpHumanVel* and arpVelTrim* are
appended per line. Old sessions open sounding identical - Humanize
carries on as the timing half, and migrateVelTrim folds Volume into
VelTrim exactly (volume% == 1 + (volume-100)/100) before resetting
Volume to 100. arpVolume stays registered; nothing in the UI writes it.

Writing that migration exposed a kit bug that had every absence-
detecting migration (migrateStrumRange, migrateRateMode) silently dead:
state::load handed replaceState the parsed root's own parameter child,
and replaceState backfills absent parameters into the shared tree
before onExtra reads it. Fixed in okstudio-juce-kit (StateHelpers.h,
replaceState now gets a copy); this commit pairs with that one.

ArpTests pins the split and the trim: H.TIME leaves velocities alone,
H.VEL only shaves, -50 halves, +100 doubles into the 1.0 ceiling,
-100 emits nothing.
Each line is now a card - rate and shape on top, the eight knobs under
their own headings, Dot/Trip/Anchor with the held chord along the
bottom - and the two cards share the panel's width. Another ~30 px off
the view, and the lines read as two parallel instruments.

Three fixes under one complaint ("I was at negative 96, and it was
still pretty loud"):

- The VEL multiplier is squared: hearing is logarithmic, and the
  linear curve crammed its audible change into the last few degrees.
- The fader now multiplies after the engine's 0.05 audibility floor
  (which protects lanes and H.VEL draws, not the line's own level) and
  bottoms at MIDI velocity 1 instead of pinning at 6 from -90 down.
- A note bound for a line's queue skips the keyboard Humanize range's
  velocity replacement, so VEL's "as played" reference holds still.
  Keybed notes keep Humanize - that is playing - even when lifted.

migrateVelTrim folds Volume through the curve (100*(sqrt(v%)-1)),
still level-exact within the velocity quantum. VEL values set under
the few-hours-old linear build read quieter now; dev machines only.
@owenpkent

Copy link
Copy Markdown
Owner Author

Follow-up commit 0a5d817, both Owen's calls: the two lines are now side-by-side cards (rate/shape, eight knobs under their own headings, Dot/Trip/Anchor + held chord), and the VEL knob got three fixes for 'at -96 it was still pretty loud': squared response curve, the fader now applies after the engine's 0.05 audibility floor (bottoms at MIDI velocity 1 instead of pinning at 6 below about -90), and chords bound for a line's queue skip the keyboard Humanize velocity range so the 'as played' reference holds still. migrateVelTrim folds old Volumes through the curve, still level-exact. Tests updated and passing (162 cases).

…feed a line

Delineation (Owen: "they kinda look like one right now"): each macro
card draws its own captioned ruled frame - LINE A, LINE B, filled -
and the outer LINES box and caption are gone; a frame around both
cards was the strongest cue that they were one thing. RATE and SHAPE
micro-caps sit over the top line's stepper groups, since two flanked
< > pairs touching read as one puzzle without names.

The A/B/All tabs, BPM and Launch Quantize moved from the panel to the
ARP section bar, editor-owned (the bar outlives the panel): the tabs
hide when the section folds and are still chord drop targets under
their old accessible names; BPM (a no-jump value bar with < > steppers)
and Quantize stay when it folds. The panel's LineTab class, header
strip and slot-row tab cells are deleted; twelve slots share the row.

A click on a chord card never feeds a line any more ("I don't want it
to send it to the arpeggiator unless you drag it"): the drag - onto a
card, a letter tab, or a slot - is the only way in, Send to arp slot
stays as the aimed accelerator, and a click just plays the pad. The
one left-click arp behaviour kept is a stop: clicking a cleared card
that still feeds a line releases that hold. The Pads bar's letter chip
now only names Send to arp slot's target and cycles without leaving
the All view.
@owenpkent

Copy link
Copy Markdown
Owner Author

Third follow-up, 859f351: each line is now its own captioned boxed card (LINE A / LINE B, outer LINES frame removed - a box around both was what made them read as one); RATE and SHAPE are named over their stepper pairs; the A/B/All tabs, BPM and Quantize moved to the ARP section bar (editor-owned, tabs hide on fold, still drop targets under the same accessible names); and a click on a chord card never feeds a line - drag is the only way in, with the cleared-card ring click kept as a release. Window is 1192 px, from 1450 at the start of the day.

Owen: "I think the bpm should live in the controls header. I want it
to be like the bpm in ableton, just a number."

It was a labelled drag slider in row B of the Controls band, spent one
build on the arp bar, and now sits on the Controls bar as a recessed
field showing nothing but the number, dragged vertically. That is the
right home on the merits: the tempo is the plugin's clock, not the
arpeggiator's, and the arp is only its loudest consumer - Launch
Quantize stayed behind on the arp bar, the same distinction read the
other way. On a bar it also survives folding Controls away, which the
band copy never did.

BpmField is a juce::Slider subclass that overrides paint: a Slider so
the APVTS attachment still drives it, paint overridden rather than a
style chosen because every built-in style draws a track, a bar or a
knob. The < > pair stays - a drag is a drag, and the mouse-only
contract wants a click-only path to every value, which is the one part
of "just a number" Keys cannot copy from a DAW that expects a keyboard
for that field. Row B keeps the 170 px the old slider held.
…he Pads bar

Owen: "let's also add the scale, root and scale lock, voices and MIDI
channel into the controls header. remove the scale and percentage and
letter b from pads header."

Root, Scale, Scale Lock, Voices and MIDI Ch move from the Controls band
to the Controls bar beside the tempo, so the band's first row is just
Size and Octave. Like the tempo they never hide with the fold, which is
the point: a settings band you have folded away is exactly when you
still want to change key.

They did not fit at the editor's minimum width, and Owen's call was to
shrink them rather than widen the window, so the group has two sizes and
measures which it can afford. Roomy captions Root, Voices and CH, since
"C", "Off" and "1" say nothing alone; Scale and Lock never get one.
Tight drops every caption and is what fits on the day the update button
claims 170 px of the same bar - deciding by measurement is what stops
that day starving the last combo to zero width. Two labels shrank and
both kept their real name where it matters: Scale Lock reads "Lock" with
its accessible name intact, MIDI Ch reads "CH" with the phrase in its
tooltip. Voices was built at 52 px and drew "..." because "Off" plus a
chevron is wider than the digits either side of it; a ComboBox
ellipsises rather than complaining, so it was measured, not guessed.

Off the Pads bar: the generator's Mode and Scale Compliance combos and
the arp target-line letter. Both combos are still in the Generator
window, which holds every setting anyway; the Key stays as the one you
change between fills. The letter had already lost its job when a card
click stopped feeding a line, and the arp bar's A/B tabs say it now.
genModeBox, genComplianceBox, arpTargetButton, cycleArpTargetLine(),
refreshArpTargetButton(), barModeNames() and complianceItems() are
deleted rather than hidden. StepComboBox stays in the tree unused: it is
the answer for any future coarse-steps-of-a-continuous-parameter control
and the bug it documents is worth keeping written down.
@owenpkent

Copy link
Copy Markdown
Owner Author

Two more passes, cb701f2 and 1e9c2b6. The tempo is a plain Ableton-style number on the Controls bar (a Slider subclass that overrides paint, so no track/bar/knob is drawn, with < > for the click-only path); it left the Controls band entirely. Then Root, Scale, Scale Lock, Voices and MIDI Ch joined it there, leaving the band's first row as Size and Octave. That group did not fit at the editor's minimum width, so it has two sizes and measures which it can afford - captions drop first, which is what keeps the day the update button claims 170 px from starving the last combo to zero width. Off the Pads bar: the generator's Mode and Scale Compliance combos (still in the Generator window) and the arp target-line letter (the A/B tabs on the arp bar say it now). Dead code deleted rather than hidden. Both standalone targets build, 162 tests pass.

The tail of today's documentation pass: ARP_DESIGN's UI-placement and
macro-view sections now describe the bars as they are, rather than the
letter chip and slot-row tabs that are gone.

Two claims were wrong rather than merely dated. The control band's FEEL
group is four sliders now, not three (Humanize split into its timing and
velocity halves), and PLAYBACK gained Play when the macro rows slimmed
down; both entries in the band-layout table said otherwise. And the arp
bar's A/B tabs do not show each line's held chord the way the panel's
old slot-row tabs did: a 34 px bar has no height for it, and in the All
view the line's own card carries it.
The day's remaining passes, all of them moving controls out of bands
that fold away and onto the bars that survive the fold.

Tempo Sync (parameter layout change, loudly): bpmSync is appended last,
default on. Keys already followed the host's tempo whenever the
transport rolled - the parameter is not what adds that, it is the
ability to turn it off and keep the arp on Keys' own clock while the
DAW plays. Threaded through the two places that read
`clock.playing && clock.bpm > 0`: ArpEngine::Params::followHost and
advanceChainClock. The Hz free-rate path stays deaf to it, the same as
it always was to the host's bpm: a subdivision of a beat means nothing
where there is no beat. migrateBpmSync backfills an older session,
the migrateRateMode shape. While Sync is lit and a host tempo is live,
the field shows the host's number and its drag and steppers grey out.

The arp bar's A and B stop navigating and start switching. They were a
tab selecting which line the panel edited, with a separate lettered On
chip doing the actual switching a few pixels away; the chip is deleted
and A/B bind straight to arpOn / arp2On. Being a power switch rather
than navigation changes what folding means for them - they stay on a
folded bar now, the case Hold off and Quantize already made. The macro
card's own On toggle goes for the same reason: two switches bound to
one parameter, one of them buried in a card. An off line is scrimmed
whole instead, and nothing on it is ever setEnabled(false) - a rate
must be dialable before switching on, and a chord dropped on an off
line has to land. A Details button beside Anchor is the way from a
card to that line's deep view now that A/B navigate nothing.

Size and Octave move to the Keyboard bar, Humanize and its velocity
range to the Pads bar, and the Knobs chip is deleted rather than
hidden: its row is unconditional whenever Controls is open. With both
gone the Controls band is one row, and headerH drops 112 -> 52.

An Instrument chip on the Controls bar gives KeysEditor its first
extension point for something embedding it (onBuildInstrumentMenu,
instrumentName, refreshInstrumentChip). Plain Keys never sets them, so
the chip stays invisible there. Keys Host's own 44 px top bar is
deleted along with it: resized() is one line now, and every window
height that added barHeight on top of idealHeight() drops it. The chip
is the one elastic control on that bar, so the fixed groups either
side of it are measured first.

The editor's floor moves to 1280, up from 1070: BPM's label, the Sync
chip and two newly captioned cells cost the Controls bar 186 px more
than the 87 px of slack it had. When a bar outgrows its floor, raise
the floor rather than starving a control.

Keys_tests: 166 cases, 2422 checks, all passing.
@owenpkent owenpkent changed the title Slim the arp All view, split Humanize, make the level knob bipolar Slim the arp view, and move the controls onto the bars Aug 3, 2026
Trip becomes a Tuplet combo (Straight / Triplet / 5-tuplet / 7-tuplet /
9-tuplet), and the arp's rate readout becomes the step length as an exact
fraction of a bar: 1/8, 1/12 in threes, 1/5 for a quarter in fives. The
usual DAW notation has a letter for triplets and none at all for a
quintuplet, which is why the first cut of this invented one.

RangeKnob is a rotary holding two values, built from Serum's manual: the
face sets one end, a lamp beside it opens and closes the range, and the
knob's own arc is the range. Clicking the lamp switches the feature off
and on. The arp's two Humanize knobs use it, as do Strum and Humanize,
which move into the chord strip.

The pads grid drops to twelve a page, two rows of six, and the columns
that freed up carry those two knobs. That loses each page's last four
pads on load: narrowing a page has nowhere to put them, and the old
re-base silently overwrote the next page instead. Owen's call, asked
before it was built.

Appended parameters: arpTuplet, arpHumanizeSpan, arpHumanVelSpan (and
their B/C twins), each with a migration. arpTrip is retired into
arpTuplet the way arpVolume was retired into arpVelTrim.

169 tests pass.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant