Skip to content

feat: playable voicings and Rhythm Lab - #2

Merged
cankilic-gh merged 14 commits into
mainfrom
feature/playable-voicings-rhythm-lab
Oct 4, 2026
Merged

cankilic-gh merged 14 commits into
mainfrom
feature/playable-voicings-rhythm-lab

Conversation

@cankilic-gh

Copy link
Copy Markdown
Collaborator

Summary

  • add hand-profile based playable voicing generation and Finder workflow
  • add Rhythm Lab with editable patterns, chord slots, playback, and MIDI export
  • preserve edited rhythm events when slot count changes
  • prevent delayed playback restart after Stop
  • avoid musically incorrect Chord Explorer links for unsupported shapes
  • add regression coverage for engine, links, rhythm state, and accessibility behavior

Verification

  • npm test: 32 files, 813 tests passed
  • npx tsc --noEmit: passed
  • npm run build: passed
  • git diff --check: passed
  • desktop and 390 px browser QA passed with no JavaScript console errors

cankilic-gh and others added 14 commits October 3, 2026 19:10
Pure TypeScript engine under engine/ (no React, Tone.js, DOM or runtime
dependencies), deterministic and seeded:

- pitch, tuning presets, mulberry32 PRNG, mm fret geometry
- one shapeToMidi conversion for tabs and audio, plus RiffForge and
  Chord Explorer adapters (string 0 = lowest string everywhere)
- personal hand profile with calibration, reach interpolation and pair
  limits (comfort guidance, not a safety assessment)
- exhaustive finger assignment with barre detection, side-by-side finger
  spacing, reach and pair limits, physical cost breakdown
- voicing families (power, sus/add, shells, quartal, clusters, phrygian,
  triads, drones), legacy recipes from library notes, relaxation ladder
- honest naming: degree strings from sounding notes, confident symbols only
- playability cost (physical, right hand, musical) and novelty axis
- windowed voicing generator with diversification and named empty results
- transition cost and exact minimax Viterbi over chord candidates

Includes a property test over 12 roots x 20 families x {E Standard, Drop D}
(100 percent coverage, every voicing barre-free, inside reach, audio equals
tab) and a brute-force completeness check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
engine/rhythm/: 480 PPQ grid math, nine original style presets (chug engine,
gallop, reverse gallop, displaced threes, halftime stomp, tremolo wall,
syncopated stabs, 7/8 as 2+2+3, five over four), a seeded generator with
accent grouping, polymeter cycles, rests, ties, palm mute, accents and
harmony changes only on accents, picking guidance (alternate on the 16th
grid, downstrokes mode, fast runs on their own subdivision), validation with
a tooFastForProfile warning, mutate and lock-aware regenerate, grid editing
helpers, and pure playback and MIDI note events.

Built alongside the M1 engine core; the Rhythm Lab UI follows separately.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Library cards no longer transpose the curated fretboard strings. Each card's
JSON notes become an interval recipe (fromLegacyNotes); the engine generates a
no-barre shape for the selected root, tuning and hand profile, and both the
tab and the played notes come from that one shape. The card picks the
generated shape closest to the curated idea (bass register, note count, the
curated tab moved rigidly) and shows string names, finger numbers, degrees
per string and an honest "sounds as" name. Unplayable recipes show the
binding constraint instead of a broken tab; relaxed recipes say what changed.

Removed transposeTabs and its tests. They codified two bugs: Drop mode always
added 2 frets to the lowest string even though the drop JSON is authored in
Drop D (wrong bass, E instead of D), and per-string +12 wrapping turned open
strings into hidden barres (Em(add9) to G became 3 5 7 3 3 3). Name and
subtext transposition stay, now without gluing interval words to the root
("E b2", not "Eb2"), without mistaking "Dim/b2" for a D root, and replacing
flat roots completely ("Bb(VI)" -> "E(VI)").

Also:
- Chord Explorer deep links send the case-sensitive "Major" type id (it
  was rejected as "major") and the card shape as gv in E Standard.
- Stored v1 riffs with flat spellings such as Bb2 are respelled on load so
  MIDI export no longer throws.
- UPGRADE.md item 4 is marked as superseded by engine/.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Hand profile store persisted at riffforge:hand-profile:v1 with full
  validation and safe fallback, export/import as JSON for Chord Explorer.
- Calibration drawer (modal, focus trapped, Escape closes): scale length
  presets or custom mm, two pinky-reach checks, no-barre, stretches, open
  strings, highest fret and comfortable 16th tempo, live derived mm values
  and the comfort disclaimer ("Stop if you feel pain or tension.").
- Voicing Finder view: family chips grouped by sound, Easiest / Most unusual
  sort, string-group and pedal-compatible filters, result cards with tab,
  finger numbers, degrees, honest name, cost explanation, play, add to riff
  and open in Chord Explorer; empty results name the binding constraint and
  offer one-click profile hints that never suggest barres.
- Library cards follow the calibrated profile.
- Workbench view switch persisted as ?view=finder; finder and drawer are
  lazy-loaded chunks.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Rhythm store persisted at riffforge:rhythm:v1 (params, seed, pattern,
  up to 4 chord slots, BPM 60-240, locked bars, metronome) with validation
  and safe fallback.
- Rhythm Lab view: nine style presets, density and syncopation, 16th or
  16th-triplet grid, 1/2/4 bars, alternate or downstroke picking, New idea,
  Mutate, per-bar Lock, play/stop with metronome and playhead, MIDI export.
- Editable grid with a lane per target (pedal, chord slots, dead), bar
  lines, accent groups, counting, accent/PM/tie/pick glyphs with text, one
  tab stop with roving focus (arrows, Space, A, P, Delete) and spoken cell
  labels; scrolls inside its container on phones with 44 px cells.
- Chord slots from the Voicing Finder ("Slot") or the RiffBar ("Use RiffBar
  chords as slots"); Optimize fingerings runs minimax optimizeSequence with
  the time before each chord change and shows the hardest transition.
- Audio: playRhythm/stopRhythm on Tone.Part through the existing chain;
  palm mute shortens and softens notes, accents are louder, dead notes are
  short scratches. The RiffBar and the lab stop each other; RiffBar
  behaviour is otherwise unchanged.
- MIDI: encodeMidiNotes and rhythmToMidi (velocities, palm-mute durations,
  time signature); the existing writer API and tests are unchanged.
- Tempo note when a pattern is faster than the profile's comfortable 16th
  tempo (a warning, never a block).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Chord Explorer link parsed the curated card name, so every "Drop ..."
card (and "Diminished Phrygian") linked to root D. Resolved voicings now
carry an explorer target: the selected root, and a type from the matched
engine family or a full chord formula present without a contradicting
third. Power chords, dyads, the tritone, fourths, quartal stacks and
clusters send no type instead of a wrong one. The shape still travels as
gv only in E Standard. parseRoot is kept as a fallback for unplayable
cards and now rejects words that merely start with a note letter and reads
flat roots as sharps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The load effect now cancels on cleanup, like the favorites effect, so an
older getChords response cannot overwrite cards for a newer root, tuning
or vibe.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Card titles came from curated labels that never moved with the root: every
"Drop X" name stayed put ("DROP BB MAJ7" while sounding Gmaj7 at G), and
key-bound subtexts such as "Bb(VI)" described a different chord. The title
is now the engine's honest name for the sounded shape, shown in its own case
(uppercase turned "b9" into "B9"). The curated nickname, with "Drop "
removed and its root moved to the selected root, plus any descriptive
subtext ("m2 Clash") form the secondary line; Roman numerals and stored
chord symbols are dropped. The RiffBar stores the same honest name.

Data: the "Am Add9" and "Drop Gm Add9" rows and their related copies had no
minor third, so they sounded sus2; they now include the b3. "Drop Fm
Mediant" had a fourth where its minor third belongs. Ids and baseRoot are
unchanged, so favorites survive.

A test resolves every library entry at all 12 roots in its tuning and checks
that the title, the sounded name and any nickname root agree with the
selected root, with no Roman-numeral subtext left.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Framer Motion makes any element with whileTap focusable (tabindex=0), so the
card wrapper and the lock wrapper were empty tab stops. Both now use CSS
active styles instead, the lock keeps a single handler on its button with a
fixed label and aria-pressed, and the dead "Lock as main chord" selector is
gone. The Play button stays the keyboard path for playing a card. The
Clean/Distortion channel buttons get aria-pressed, type="button" and a
visible focus ring in the channel color.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The hand profile is editable now, so an empty card names the profile
rather than the player's hand.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The finder kept its own table that sent power chords as Major, the tritone
as dim and fourths or quartal stacks as sus4. It now uses the same rule as
library cards: a type only where Chord Explorer has one, none for power
chords, dyads, the tritone, fourths, quartal stacks and clusters.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Row labels, string names and degrees were 9-10px; they are now 11px. Muted
"x" cells use neutral-400 so they reach 4.5:1 on the card background.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Card titles are now sounded chord symbols, and CSS uppercase turned
"Gm(add9)" into "GM(ADD9)" (read as G major) in the RiffBar chips and the
Related Variations header. Only the chord name keeps its case; the RiffBar
layout and the rest of its styling are unchanged. The decorative letters
behind each card are hidden from screen readers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@vercel

vercel Bot commented Oct 4, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
riff-forge Ready Ready Preview Oct 4, 2026 1:23am UTC

@cankilic-gh
cankilic-gh merged commit 0907934 into main Oct 4, 2026
3 checks passed
@cankilic-gh
cankilic-gh deleted the feature/playable-voicings-rhythm-lab branch October 4, 2026 01:25

This branch was successfully deployed

1 active deployment
Preview — 2dc3385b Deployed Oct 4, 2026 by vercel[bot]
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