Repository navigation
Pattern sequencer: steps with parameters, polymeter, an editor and a real-time safe engine - #2348
Merged
Merged
Conversation
Lanes share the layer's height and steps the width the note choosers leave; drawing, hit testing and the choosers use the same layout, which follows every resize. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The chooser can now follow the room its owner gives it and show the note however the owner names it, on one line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FCfP6rUUXFnGw6VoTiRCWB
Layer - In an interval the steps fall where they play: on the bar grid of the closest interval with a signature or a tempo, the grid starting over at every bar as in the engine. The first pass is lit, the repetitions are drawn darker to the end of the layer, as in the step sequencer. In a node the steps share the whole box. - Click toggles a step, shift-click ties it, right-click on a note clears it; dragging paints over several steps as one undo step. Hover outline, bar and beat lines, a column on the step playing, and a note when another pattern plays or is about to. - The context menu has copy, paste, clear, rotate, randomize, add lane, and mute, solo or remove on a lane header. Editor - The process UI, in a window or docked: the pattern bank (shown, playing, next), duplicate and remove, the pattern tools, steps and rate, add lane, mute and solo buttons per lane and step numbers. Model and engine - Lanes have mute and solo, applied by the engine, saved in documents. - Lane labels (note and number, name, number, General MIDI drum) are a property of the process, chosen in the inspector. - Every lane is at least as long as its pattern; the inspector lengthens them all, allows 1 to 128 steps, takes an integer rate, and no longer submits commands that change nothing. - The executor reports the pattern playing and the one waiting. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FCfP6rUUXFnGw6VoTiRCWB
- The node and editor grids group steps in the beats and bars of the signature the process plays on, as the timeline does. Every interval up the chain is watched, so a signature added anywhere is followed. - Beats are told apart by a darker shade on every other one, bar lines are faint: they no longer stand out over the steps. - No M/S text in the compact view; mute and solo stay in the editor. - The editor says what copy, paste, clear, rotate and randomize did, and disables Paste when the clipboard holds no pattern. Bank buttons keep their width whichever pattern is shown or playing. - Looking for a pattern in the clipboard no longer goes through the logging JSON parser: right-click logged an error for any text. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FCfP6rUUXFnGw6VoTiRCWB
Model - Every step has a velocity, a chance, an offset (early or late, up to half a step), a gate, a ratchet count, a pitch offset and a condition: fill, not fill, first pass, not first, or pass A of every B. - A lane can have its own length and division, a velocity, and a Euclidean rhythm (pulses, rotation) that replaces, adds to or masks its written steps; printing writes the rhythm into the steps. - Fill, Density, Velocity and Swing inputs, for automation. - Saved in documents; only what differs from the defaults is written. Engine - Each lane counts its own steps and passes, so lanes of different lengths or divisions run against each other. - What a step plays is scheduled at its musical position and sent in the tick it falls in: gates and ratchets inside the step, late steps in a later tick, early steps from the step before. - Without musical time, or rewinding, steps still play at once. Editing - Randomize works on a lane or on the whole pattern. - Right-click on a lane name opens the lane menu instead of starting a drag of its note, which hid the cursor under the menu. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FCfP6rUUXFnGw6VoTiRCWB
Layer - Each lane is laid out on its own length and division: in a node, the longest loop fills the box and the others repeat at their own speed; in the timeline every lane follows the bar grid at its own rate. - A note's height is its velocity, its width its gate, its position its timing; ratchets split it, a note that may not play is faint, a corner mark shows a condition. Euclidean rhythms are drawn as they play. - Each lane highlights its own playing step. - Editing a step of a rhythm that replaces the steps writes the rhythm out first; over a rhythm that adds or masks, clicks edit the written steps. Editor - A selected lane (click its name or a step) with a bar lane under the grid for velocity, chance, timing, gate, ratchet or pitch, drawn by dragging. - A lane panel: length, division, velocity, Euclidean hits, rotation and mode, write into steps, randomize lane. - Right-click on a step: when it plays (always, fill, not fill, first, not first, pass A of every B), and reset its parameters. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FCfP6rUUXFnGw6VoTiRCWB
The grid takes every click, so the layer was never focused, and score only opens the layer menu of the focused layer: right-click on a lane name did nothing until something else had focused it. The layer asks for focus on press and its menu request waits for that focus, which arrives through a queued connection. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FCfP6rUUXFnGw6VoTiRCWB
When a lane's loop is longer than the others, the shorter lanes repeat across the node: those repeats are now whole, editable cells, dimmed, and the step under the mouse is outlined wherever it repeats. When lining the lanes up would make steps too thin to aim at, each lane spreads its own loop over the width. The grid takes its colours from the skin: beats alternate between two close tones (bars when a step lasts a beat or more), bars are marked by a wider gap rather than a line drawn over the cells, and what plays is tinted towards the notes' hue. Notes stay inside their cell. The engine reports how many steps each lane played, so that the view highlights the pass the time is on. Lane names are hidden when the lanes get too thin for them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FCfP6rUUXFnGw6VoTiRCWB
Editor: - Lane names in a larger font, with M and S under them when the lane is tall enough. - The step parameters are chosen with buttons beside their bars, with Random and Reset (Shift: every lane) under them. - Toolbar buttons light up under the mouse. - Select mode, or Ctrl-drag: select steps over several lanes. Ctrl+C, Ctrl+X, Delete, Ctrl+A, Escape; Ctrl+V pastes the copied steps with their first lane and step under the mouse. - Euclidean is a generator next to Random: hits, rotation, and whether it replaces, adds to or masks the steps. The live Euclidean lane mode is gone. The grid keeps two cell tones in repeats; only their notes dim. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FCfP6rUUXFnGw6VoTiRCWB
A selection is the stretch of time dragged over: each lane gets the steps under it, so lanes of different rates or lengths select what plays at the same time, not the same step numbers. Copied steps paste in each lane from the step at the time under the mouse. Steps get a velocity and a pitch spread, drawn anew each time they play, with their own bars, Random and Reset in the editor. The editor's lane names use the toolbar's font, and Select comes first in the toolbar. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FCfP6rUUXFnGw6VoTiRCWB
A tied step played as one held note whatever its ratchet. Its first hit now carries the tie, the others strike again, each released after its gate, and the last is held into the next step. The grid marks those hits on tied steps too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FCfP6rUUXFnGw6VoTiRCWB
1 to 8, for ratcheting the whole pattern from a controller while it plays; a step strikes at most 16 times. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FCfP6rUUXFnGw6VoTiRCWB
- Pace, per step and as an input: bends the spacing of a ratchet's hits, wide then tight or the other way round. The grid draws the hits where they fall. - The Ratchet input takes fractions: the hits divide the step by the step's ratchet times the input, the last gap cut short by the next step. - Repeat time input, a time chooser (synced or in seconds): when set, the steps that ratchet repeat at that interval instead. - In the parameter bars, Shift-drag moves every bar by the same amount and Alt-drag blends them towards a random state, further for a longer drag; on the selected steps when there are some. One undo step each. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FCfP6rUUXFnGw6VoTiRCWB
- Controls go through setters that keep their value on anything that is not a finite number and hold it to its range, from the inlets and from the GUI alike: a NaN on Swing or Pace no longer fills the queue with events that never come due. - No allocation on the audio thread: the queue and the step's releases sort with std::sort on an explicit push order instead of the stable algorithms; notes in flight are a fixed count per note; the grid points come from token_request::for_each_quantification_date. - Notes in flight are counted per note: two lanes on one note share a voice, restarted when struck again and released by the last lane. - A lane releases its held note after that note's note-on, so an early step landing on a late one no longer loses the release. - What is due goes out before every grid point, the lanes' own divisions walked in time order with the pattern's: the queue holds a few steps at most, sized for the worst case, and keeps room for releases. - Notes held by lanes that were removed are released. - A transport moves the step counters to where the playhead lands. - Solo is worked out once per tick instead of once per lane and step. - The GUI reads what it draws from atomics the engine publishes at the end of every tick, never the pattern list being swapped, and repaints only when a step moved. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FCfP6rUUXFnGw6VoTiRCWB
- normalize() gives the default to step and lane values that are not finite numbers (score's JSON reader accepts NaN), holds divisions to 1/64 and lanes to 128, and clamps conditions: a document can no longer stall the audio thread with a huge division or freeze it with NaN. - Every load path ends in sanitize(): at least one pattern, all normalized, the channel and the current pattern within range. - Adding a lane stops at 128, the most the engine plays. - Patterns made by selecting past the end, still untouched, go away when the selection comes back below them, so undoing the selection gives the list back. - An ongoing UpdatePattern follows the edit when the shown pattern changes in the middle of a drag, instead of writing it into the first one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FCfP6rUUXFnGw6VoTiRCWB
- The header width, which measures every lane's name, is worked out once and kept until the lanes, their names or the size change, instead of for every lane on every paint: 128 lanes paint in 2.2 ms instead of 87.5 ms (Debug). - The editor opens at 960 x 600; past what fits, its lanes scroll rather than get lower than 20 px, and the parameter buttons shrink before the bars do. - Mute and solo buttons are left out of lanes too low to aim at them; they stay in the lane's menu. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FCfP6rUUXFnGw6VoTiRCWB
test_unit_patternist_robustness keeps what the adversarial review found as regression tests: - no allocation on the audio thread; - no note left sounding (early on late steps, two lanes on one note, removed lanes, a full step of ratchets, transport jumps); - NaN and out-of-range controls, steps, divisions, selections and channels; - undo of selections and of edits that change pattern mid-drag; - the GUI's reads of what the engine publishes, against a writer thread; - painting in linear time in the lanes. Measurements and renders run on demand ([.][measure], [.][shots]). The layer, its time grid, the editor, the command group and the step serialization are exported so that the tests use the plug-in's own. The engine emits what is due before a grid point only once the queue is half full, which still keeps the order (points come in time order), and ignores a division past 1/64 handed to it directly. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FCfP6rUUXFnGw6VoTiRCWB
In the editor, each lane's header is a card on its row, as tall as its cells and separated by the same gaps, lit with the row when selected; the parameter buttons sit on the same kind of card. With room for a third row, the card holds the lane's steps and rate: dragged up or down, double-clicked to follow the pattern again, dimmed while they do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FCfP6rUUXFnGw6VoTiRCWB
The palette and the fonts the layer derives from the skin are worked out once and again only when another skin loads (Skin::LoadIndex), instead of on every paint and for every button. A skin change remeasures and repaints the layer, and rebuilds the editor's styles and background. The parameter bars of steps without a note are drawn barely off the background. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FCfP6rUUXFnGw6VoTiRCWB
A Grid | Rings toggle in the editor's toolbar shows the lanes as concentric rings, lane 1 outermost, each its own loop, so lanes of different lengths show as cells of different sizes. Cells, notes and their parameters keep the grid's colours and encoding: velocity fills outward, gate and timing along the ring, chance as opacity, ratchets as slits spaced by pace, conditions as a dot. Each ring tints its playing step. The selected lane's ring is twice as thick and outlined, the others step back, its step numbers go around the rings and its name, hits, steps and rate sit in the middle; the lane list on the left lights its row. Clicks, painting, ties and the step menu work as on the grid, and the parameter bars stay underneath. There is no step selection on rings. Nodes and the timeline keep the grid. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FCfP6rUUXFnGw6VoTiRCWB
…ange A tied step only holds the note struck before it, so its own velocity does nothing unless it strikes again (after a rest, with a ratchet or another pitch). The grid and the rings draw a tie as loud as the note it holds, and the velocity bars of ties that do not strike are dimmed like those of empty steps (tieStrikes, struckStep). An Alt-drag in the parameter bars heads for values anywhere in the parameter's range instead of the mild ones of the Random button: pitch random reaches 24 semitones, not 3. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FCfP6rUUXFnGw6VoTiRCWB
A tie held its note until the next step whatever its gate, in the engine and on the grid. A gate under a full step now releases the tied note there, and the grid and the rings draw the tie that long. Early notes were marked with a line the height of the cell at its start, which read as a stray pixel above short or quiet notes: gone, the note already shows its timing by where it starts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FCfP6rUUXFnGw6VoTiRCWB
With rings, the lanes are listed as the same cards as on the grid, with their name, mute and solo, steps and rate, stacked from the top at the height those need. The tooltips, the status and the ring's caption use plain punctuation, one item per line in the tooltips, and the button removing a pattern is a plain hyphen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FCfP6rUUXFnGw6VoTiRCWB
- A lane added while the pattern plays, or given another rate, joins on the step it would be on had it always played, and a pattern given another length goes on from where it is, instead of starting over. The engine flags those when the new list comes in and moves only them on the next tick, keeping a count of the pattern's own steps since it started. - Transpose buttons in the editor: every lane's note by -12, -1, +1 or +12, the accent and slide lanes left as they are. - In a node, a right-click on the pattern opened the layer menu twice, one over the other, the hidden one taking the hover: the layer only takes focus on a click on the timeline, where that is what opens its menu. The mouse grab of the press is released when the menu opens. - With rings, the middle shows the notes struck on the steps playing now, and the selected lane's name and length when stopped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FCfP6rUUXFnGw6VoTiRCWB
- Timing random, per step: each time the step plays, its timing moves by up to half a step either way, early included: the spread of a note is drawn a step ahead, so that one moved early is scheduled in time. It has its bars, Random and Reset like the other spreads. - Each lane chooses, for velocity, pitch and timing, whether the whole note shares one random value or each hit of a ratchet draws its own; a hit's release is for its own pitch, and with pitch drawn per hit a tie holds whatever was struck. The choice is in the editor's lane panel. - Grid and Rings sit on the pattern row, so the tools fit the editor; the random spreads are named alike. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FCfP6rUUXFnGw6VoTiRCWB
The deployment builds of Qt are configured with QT_NO_STYLE_STYLESHEET, where setStyleSheet is not declared at all, which broke the AppImage and Windows arm64 builds. The toolbar and pattern bank buttons draw their hover, pressed and checked states themselves, in the skin's colours. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FCfP6rUUXFnGw6VoTiRCWB
An accent step takes the velocity of every note on it part of the way to the top, by its amount times the accent lane's depth; its outlet carries the amount and drops back to 0 when its gate ends. A slide step holds the notes on it into the next step, which strikes before the held note is released, or carries it on when it is the same note; its outlet holds the glide time until the note it glides into is struck. Both are decided once per step, so the outlet and the notes agree, and a note scheduled early finds the control step it belongs to. The editor adds them from the Add lane menu, keeps them under the note lanes, does not let their chooser turn them into notes, and shows only the parameters they use. Gate random joins the step parameters, with its own per-hit option, and the parameter buttons pair each value with its spread. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FCfP6rUUXFnGw6VoTiRCWB
Ctrl-click on a lane header picks it, the All lanes button over the headers picks them all. With no step selected, Copy, Paste and Clear act on the lanes picked: copied lanes go over the lanes picked, or from the lane under the mouse down, each keeping its note, mute, solo and velocity. With nothing selected, Copy and Clear do nothing. The lane menu copies, pastes and clears its lane. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FCfP6rUUXFnGw6VoTiRCWB
jcelerier
force-pushed
the
feature/pattern-sequencer-ui
branch
from
October 18, 2026 15:01
184540e to
5f62864
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A rework of the Pattern sequencer (Patternist): what a step can hold, how lanes relate in time, a full editor with a Rings view, and an engine that is real-time safe and never leaves a note sounding.
Depends on ossia/libossia#962 (
token_request::for_each_quantification_date). The submodule points at that commit.Steps and lanes
Layer and node
Editor
Engine
Loading and undo
Tests
test_unit_patternist_node: engine, timing, ratchets, ties, spreads.test_unit_patternist_edit: edits, layout, generators, transpose.test_unit_patternist_port: ports, serialization, clipboard.test_unit_patternist_robustness:Measurements and renders run on demand.
🤖 Generated with Claude Code
https://claude.ai/code/session_01FCfP6rUUXFnGw6VoTiRCWB