Skip to content

feat: add Grooves app - #604

Draft
kosmar wants to merge 29 commits into
ATOVproject:mainfrom
kosmar:feat/grooves
Draft

feat: add Grooves app#604
kosmar wants to merge 29 commits into
ATOVproject:mainfrom
kosmar:feat/grooves

Conversation

@kosmar

@kosmar kosmar commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Adds Grooves (app ID 30): a one-channel multi-genre MIDI drum helper with MPC-style feel, density fills and Any/Stacked jack modes.
  • Seven drum voices (Kick, Snare, Hats, Open Hat, Low Tom, High Tom, Clap). Everything past Hats is optional and off by default, addressed through a packed channel map so the extra voices cost no extra params.
  • Nine genres on a morph axis (Dub through Dubstep), scrubbed with Shift+fader and synced back to the Configurator Groove param.
  • Eight drummer personas that transform the genre pattern after it is read, the way Bassment's bassist voices do: Elvin, Purdie, Clyde, Jaki, Sly, Allen, Pretty Tony and Dilla. Each profile carries shuffle, ghost density, backbeat lock, tom dispersal, clave rotation, roll rate, push or pull, limb drift and a machine amount. All of it is Feel scaled, so a flat Feel still plays the plain genre grid and the personas open up as Feel rises.
  • Shift+tap plays a genre fill, Shift+hold escalates it into a solo.
  • Manual entry with gesture and LED tables, including markdown table rendering so those tables display in the Configurator.

Note on the param ceiling

The app was already using all sixteen slots APP_MAX_PARAMS allows, so the separate Range param folds into the Jack enum, which now carries duty, stacked mode and range together. Stored blobs migrate on the value type at index 14, which preserves both the duty and the range the user had picked.

Gestures

Gesture Action
Fader Density
Shift + fader Genre
Button + fader Feel
Short press Mute; unmuting also resets to the downbeat
Long press Next drummer (LED flashes the persona colour)
Shift + tap Genre fill until the next downbeat
Shift + hold Solo

Test plan

  • Flash firmware; load Grooves on a channel
  • Kick, Snare and Hats fire in 4/4 with the selected Groove
  • Fader raises density so extra hits fade in; Shift+fader scrubs the genre with the LED spectrum following
  • Button+fader scrubs Feel; at Feel 0 the grid is flat and quantised
  • Long press cycles the drummer and the button LED flashes a distinct colour per persona
  • Personas are audibly distinct at high Feel: Purdie shuffles with ghosts, Jaki stays dead straight, Pretty Tony shifts the kick off the downbeat, Dilla drifts the hats against the kick
  • Short press mutes; unmuting lands on the downbeat
  • Shift+tap fills into the next bar; Shift+hold turns it into a solo
  • Jack enum covers Any and Stacked in both ranges, plus CV In for Density, Feel and Reset
  • A layout saved before this change still loads with its previous jack duty and range intact
  • Configurator Manual for Grooves renders its tables correctly

Prerequisites

The platform pieces this app needs are under review as their own PRs, so that each app PR stays small enough to review on its own.

Required, since these APIs do not exist on current main:

This branch currently carries its own inline copy of the code in #638, #639, #640 and #641. Once those land it will be rebased to drop the duplicates, which shrinks this diff to the app file plus its registry line.

Building any of this on a current Rust nightly also needs the toolchain fix in #632.

Note: ID 28 is reserved for Sift; Heat Pump holds 29 (#603). These PRs are independent from main.

Multi-genre MIDI drum helper with swing, density fills, and jack modes
(app ID 29), plus configurator manual with gesture tables.
@kosmar
kosmar marked this pull request as draft July 24, 2026 14:17
kosmar and others added 10 commits August 10, 2026 14:16
…grooves

Co-authored-by: Cursor <cursoragent@cursor.com>

# Conflicts:
#	faderpunk/src/app.rs
…t/grooves

Co-authored-by: Cursor <cursoragent@cursor.com>

# Conflicts:
#	faderpunk/src/storage.rs
…t+Hold

Shift+Tap plays a genre-specific fill that resolves on the next downbeat,
turning subtractive into a break above roughly 60 % density. Holding Shift
escalates into a four-bar solo phrase (build, busier, peak, turnaround) with
its own accent mask instead of looping the fill; releasing drops back into
the end-weighted fill so it always lands on the one. Each genre carries
three fill, break and solo figures, with Feel rolling between them. Jack
Mode and Swing Dir move to the Configurator to free the Shift+Long gesture.

Authored by an AI coding agent on behalf of kosmar.

Co-authored-by: Cursor <cursoragent@cursor.com>
The simulator catalog generator turned every path expression into an enum tag,
so `variants: GENRE_NAMES` came out as `{ tag: "GENRE_NAMES" }` where the
TypeScript side expects `string[]`, breaking the configurator build. Top-level
consts an app references now resolve to their values — both integer bounds and
string-array variant lists — and a signed bound like `min: -100` parses instead
of panicking.

Const names are not unique across app modules (Bassment and Chord Vamp each
declare their own `SCALE_NAMES`), so resolution goes module-local first and
only then falls back to consts declared in another app module.

Authored by an AI coding agent on behalf of kosmar.

Co-authored-by: Cursor <cursoragent@cursor.com>
Adds Open Hat, Low Tom, High Tom and Clap. Each is derived from a hit the
groove already plays, so no genre needs extra mask data: Clap layers under
the accented snare, Open Hat takes over an accented offbeat hat (muting the
closed one), and the toms lift the snare figure into a descending run in the
back half of a fill, break or solo. Note 0 switches a voice off, which is the
default — with all four off the app behaves exactly as before.

To fit seven voices in the 16-param budget, the per-voice channels move into
one packed i32: seven nibbles, 0 meaning "follow the base MIDI Ch". Jack,
Jack Mode and CV Dest collapse into a single enum (a CV In destination and a
CV Out level shape were never live at once), and Swing Dir folds into the
sign of Swing max %.

Stacked CV keeps its binary-weighted family voltages (hats 1, snare 2,
kick 4 of 10) by folding the new voices into the family they belong to, so
existing patches stay calibrated. The jack now follows the groove rather
than the note assignment, so a CV-only patch still pulses.

Authored by an AI coding agent on behalf of kosmar.

Co-authored-by: Cursor <cursoragent@cursor.com>
Microtiming moves from clock ticks to milliseconds, so the pocket no longer
snaps to the 20.8 ms tick grid. Each drum family carries its own push and
jitter width scaled by a per-genre tightness, gate lengths are per voice so
closed hats choke inside their step while open hats and toms ring past it,
and velocity gains jitter, offbeat hat ducking and a phrase arc. At Groove
(Feel) = 0 everything collapses onto the grid, so Grooves still locks against
the other apps.

Authored by an AI coding agent on behalf of kosmar.

Co-authored-by: Cursor <cursoragent@cursor.com>
The branch still used the ID it was first drafted with. ID 29 now belongs to
another app in docs/wip-app-ids.md, and 28 is reserved for Sift, so two of these
app branches would have collided on merge.

Authored by an AI coding agent on behalf of kosmar.
kosmar and others added 6 commits August 13, 2026 18:45
Review feedback on ATOVproject#639: a dropped NoteOff hangs the note with no later
message to correct it, and APP_MIDI_CHANNEL is shared by all 16
channels, so a busy neighbour can cause the drop. All three call sites
run in the async voice task, so awaiting queue space costs at most a
late NoteOff — and with per-voice drum channels a leaked voice would
drone until the next hit on that same voice.

Drops the now-unused try_send_note_off from the branch copy of app.rs,
matching ATOVproject#639.

Authored by an AI coding agent on behalf of kosmar.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>

# Conflicts:
#	gen-bindings/src/catalog.rs
Same mutate+save API as ATOVproject#640 so WIP apps can call params.update now.
Host AppState push stays out until the real writeback lands.

Authored by an AI coding agent on behalf of kosmar.

Co-authored-by: Cursor <cursoragent@cursor.com>
ParamStore::update polyfill (mutate+FRAM, no host push).

Authored by an AI coding agent on behalf of kosmar.

Co-authored-by: Cursor <cursoragent@cursor.com>
…branch

Brings grooves.rs, groove.rs and genre_palette.rs up to the state the
playground integration branch has been flashing, and adds the shared
ornament module they now depend on. The platform work that landed
alongside it there (perf-mute, non-blocking notes, panic beacon) stays
on its own feature branches and is deliberately not pulled in.

Bassment and Contura are the other consumers of the shared modules and
live on their own branches, so their helpers read as dead code here.
The lint is silenced at the module declaration to keep the shared files
byte-identical to the integration branch.

Authored by an AI coding agent on behalf of kosmar.

Co-authored-by: Cursor <cursoragent@cursor.com>
Adds eight drummer personas that transform the genre pattern after it is
read, the way Bassment's bassist voices do: Elvin, Purdie, Clyde, Jaki,
Sly, Allen, Pretty Tony and Dilla. Each profile carries shuffle, ghost
density, backbeat lock, tom dispersal, clave rotation, roll rate, push
or pull, limb drift and a machine amount, and every one of them is Feel
scaled, so a flat Feel still plays the plain genre grid.

Long press cycles the drummer, matching Bassment. The reset it used to
carry moves onto the unmute, so coming back in still lands on the
downbeat, and CV In Reset is unchanged.

The app was already at the sixteen param ceiling, so the separate Range
param folds into the Jack enum, which now carries duty, stacked mode and
range together. Stored blobs migrate on the value type at index 14, which
keeps both the duty and the range the user picked.

Authored by an AI coding agent on behalf of kosmar.

Co-authored-by: Cursor <cursoragent@cursor.com>
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