Repository navigation
Name or write a rhythm PATTERN only at 4/4, the one beat whose list is charted - #152
Merged
Merged
Conversation
Two readings of a working RC-5 (#147, 2026-09-30 and 2026-10-01): Rock2 chosen on the pedal is stored as 12 in a 4/4 memory and as 3 in a 6/4 one, and a 19 written by this app at 6/4 shows a blank name on the screen. So the number in <Pattern> indexes the pattern list of the memory's CURRENT BEAT, and the manual's printed list - kPatterns - is the 4/4 list. Every other beat's list is uncharted: its length, its order, where Blank sits. The range cannot catch this. A short list's numbers all fall inside the 57-entry one, so patternName answered 3 with SimpleBeat4 and no error, and the editor wrote 4/4 indices into 6/4 memories. The guard keys off the beat (#149): - patternListCharted(beat) says which beats have a list we can name from, only kBeatFourFour, and Values carries the answer as patternCharted, so read() returns the raw number at every beat and names nothing. - apply() checks a pattern edit against the beat the edit leaves the memory at: a pattern together with a move to 4/4 passes; a pattern alone at 6/4, or together with a move away from 4/4, is refused before any byte moves, naming the beat as the screen prints it. Changing BEAT alone stays allowed and reproduces the pattern bytes exactly. - The switch's two paths that write a pattern number - Blank to the default on the way on, the groove to Blank on the way off with a count-in to keep - are refused at an uncharted beat (switchRefusal, shared with the tab so it can say so before the click). State alone is still ours, and "already there" still moves nothing. - describe() takes the memory's beat and refuses the same pattern edit, so the history never gets a 4/4 name for a 6/4 number. Tests from the hardware pairing and the rule, not from the code: 6/4 bodies read but not named, refusals that leave the body untouched and say "6/4" rather than 4, BEAT moving with the pattern bytes reproduced, the pattern checked against the new beat, each switch case at 6/4 refused or passed, and 4/4 unchanged. 266 checks; with the guard switched off 54 of them fail, with apply()'s pattern check removed 12 do.
Where the core will not name the pattern (#149), the box shows the number the card holds - as text, with no item behind it - and is a lamp, in the shape BEAT already takes under a take: the caption reads "PATTERN (list at 6/4 not charted)". At 4/4 nothing changes. Going grey and coming back re-applies the lock the way it does BEAT's, so a dropdown moved past the greyed face snaps back to the number. The switch follows the core: where a click would need a pattern number - on over Blank, off with the count-in on - the switch is a lamp and the line beside it carries the core's own sentence (switchRefusal), so the tab and a banner can never disagree, and no click ends in an error for something the tab could have said up front. The "forgets <name>" line is reached only where the list is charted. The owner hands describe() the memory's beat from the row it is editing: the words are made before the job is enqueued and before the history opens its row, so a pattern at an uncharted beat is refused with no row written. The card itself is checked again on the worker, against the beat it holds then. rhythm_pane_harness: four 6/4 rows - the pedal's own Rock2 at 6/4 (3) with drums on, the same with a count-in in front, Blank off, and the 19 the pedal could not name - checking the number on the box, the caption, which controls stay the player's, which switch clicks are offered, the lock surviving busy, and 4/4 as before.
The RHYTHM table's Pattern row now carries the two hardware readings of #147 (Rock2 = 12 at 4/4 and 3 at 6/4, 2026-10-01) and says what is known: the 4/4 list, and nothing of any other beat's.
Review of the beat lock found the hole it left open: Play Count-In has two
paths of its own that write <Pattern> - the borrow on the way on (State on,
Pattern 57) and the hand-back on the way off (State 0, Pattern 0) - and
both numbers are 4/4 numbers. Through the table's column or the card,
"count-in on" over the pedal's own Rock2 at 6/4 (stored as 3) wrote 57,
and "off" then wrote 0. The header said no edit writes <Pattern> at an
uncharted beat; now it is true of both features.
BEAT moves to Beat.hpp, where both can see it: the manual's list, the one
charted beat, patternListCharted, the shared fact sentence, and two ways to
name a number - beatName, which throws for an action on BEAT itself, and
label, which never throws and tells the truth about a number the list
lacks ("BEAT 17, not in the manual's list"). kBeats is inferred past its
one anchor, so a memory can hold such a number, and reading it must not
end the app: switchRefusal now decides its two branches before any name is
looked up, so a State-only switch at beat 17 passes; the beat is named
only inside the refusal, through label. The rhythm's names for the beat
stay, as using-declarations.
countin::refusal(body, on) is the count-in's typed answer, pure over the
body: on is refused where the section is off (the borrow), off where the
section was borrowed for the count (the hand-back), and a PlayCount-only
switch - on over a playing rhythm, off where nothing was borrowed - passes
at every beat. apply() asks it before any byte moves; refusalText carries
the fact for the banner. patternAtRisk answers nothing at an uncharted
beat: the click that would replace the pattern is refused there, so the
refusal is what the card says, and deciding from 0 or 57 would read 4/4
names into another list's numbers. SlotInfo carries the refusal of the
next click (countInRefused) for the table and the card.
Both switch refusals are typed now (SwitchRefusal, Refusal) with the
sentence built apart, so the UI can say the same fact in fewer words.
Tests from the rule, not the code: every beat but 4/4 plus -1, 17 and 99,
both toggles, all eight section states - refused exactly where a pattern
would be written, the body untouched, PlayCount alone elsewhere; the
sentence naming 6/4 and never 4; the P0 end to end through
commands::setCountIn on a scratch volume, both memory files byte-identical
after each refusal and a 4/4 slot ahead in the batch not landing first;
the read model's refusal per slot; a beat the list lacks read, labelled,
switched State-only and refused a pattern. usecase_count_in_tests 1501
checks, usecase_rhythm_tests 324, catalog 944, commands 1584; with the
guard removed from apply() 200 and 8 of them fail, with patternAtRisk's
removed 4 and 1 do.
… switch
One place for the lines a lamp carries (RefusalWords.h): the rhythm switch,
the Play Count-In card and the table's Count-In cell each ask the core's
typed refusal and say it in one short line - no date, no field name - with
the beat as the screen prints it, or the truth about a number the manual's
list does not have. The core keeps the full fact in its Error text for the
banner and the log.
The Play Count-In card is a lamp where the count-in's click would write a
pattern number at an uncharted beat, its orange line saying why; the
table's dot is the same lamp, and its hover says the same line, since a
seven-pixel dot cannot. The Rhythm tab's PATTERN caption and cost line no
longer look a beat's name up on every snapshot: a memory holding a beat the
list lacks is shown ("PATTERN (BEAT 17 is not in the manual's list)"), not
thrown on.
editRhythm catches what describe() may throw rather than dropping the edit:
the tab locks PATTERN where the words would be refused, so reaching the
catch means the tab and the core disagree, and a silent drop would hide
exactly that. The banner shows the core's sentence; no job and no history
row follow.
rhythm_pane_harness: two rows at Beat 17 - painted without a throw, the
caption, a State-only switch offered, the lamp and its line over 57.
slot_table_harness: a 6/4 row whose Count-In click calls nobody and whose
hover says why, a 6/4 row with the rhythm playing that still toggles, One
Shot untouched by any of it.
One sentence in the Play Count-In bullet: where the click would have to write a rhythm pattern number, the switch waits and the card says what to do instead, and the Rhythm tab shows such a pattern as a number for the same reason.
Re-review of the beat lock found the one BEAT move that strands a memory:
State on, Blank, count on is the count-in's own shape, and moved away from
4/4 it is stuck in the app - the count-in's hand-back and the rhythm's
switch would both need a pattern number there, both are lamps, and BEAT
itself is the only way back. So rhythm::apply refuses that one move with a
typed Error ("BEAT cannot leave 4/4 while the count-in borrows the rhythm
section: switch the count-in off first"), Values carries the fact as
beatHeldByCountIn, and the tab makes BEAT a lamp with the caption in the
take's shape ("BEAT (held by the count-in)") and the way out on the line
beside the switch. Every other BEAT move keeps the pattern bytes as the
issue asks - 4/4 to 6/4 with a count over Rock2 still passes and keeps 12 -
and a move to 4/4 is never refused; a memory already stuck at 6/4 is not
"held", since the hold is a 4/4 fact.
Two sentences reworded from the same review: the count-in's off refusal
now says what to do ("switch the count-in off on the pedal instead",
core and card alike), and a beat the manual's list lacks is labelled
"BEAT 99 (not in the manual's list)", so every sentence that names it still
reads as one at either label.
One line in Rhythm.hpp's header: a restore (history, undo, redo) writes a
recorded body faithfully, not through apply(), so a row recorded before
this guard can bring a 4/4 number back to a memory at another beat; the
tab then shows it as a number. No code change for restore.
Tests: the stuck case refused with the body untouched, with a kit in the
same edit, and under a take (the manual's refusal first); the same beat
not a move; the count-in's own switch as the way out; a count over a
groove, a rhythm on with Blank and no count, and the factory slot not
held; the stuck memory's way home never refused; the reworded sentences.
rhythm_pane_harness: a 4/4 row in the count-in's shape - BEAT a lamp with
its caption and line, PATTERN and the switch still the player's, a pick
past the greyed face heard by nobody. usecase_rhythm_tests 345 checks;
with the hold removed from apply() 6 of them fail.
This was referenced Oct 4, 2026
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.
Closes #149. Follows from #147: the number in
<Pattern>indexes the pattern list of the memory's current BEAT (hardware, 2026-09-30/10-01: Rock2 is 12 at 4/4 and 3 at 6/4; a 19 written at 6/4 shows a blank name on the pedal).kPatternsis the 4/4 list; every other beat's list is uncharted, and a short list's numbers all fall inside the 57-entry one, so the range cannot tell. The guard keys off the beat.Core (
usecases/Beat.hpp, new, shared byRhythm.hppandCountIn.hpp)<Pattern>: a pattern edit is refused (checked against the beat the memory will be at after the edit), the two switch paths that write a pattern number (Blank to default on the way on, groove to Blank on the way off with a count-in) are refused, and the count-in's two borrowing paths are refused the same way; a State-only or PlayCount-only switch still passes. Changing BEAT keeps the pattern bytes exactly, except the one move that would strand a memory (leaving 4/4 while the count-in borrows the rhythm section).App
app/RefusalWords.h, one place to reword). Play Count-In card and table cell: lamps with the same line (hover on the cell). A refusal reachingeditRhythmlands in the banner, never terminates the app.Docs: the atlas's
Patternrow says what the number is; the guide's Play Count-In bullet says the count waits at other time signatures.For the product owner: the UI sentences in
app/RefusalWords.hare first drafts. A restore (history, undo, redo) writes a recorded body faithfully, so a row recorded before this guard can bring a 4/4 number back to a non-4/4 memory; the tab then shows it as a number. Where Blank sits in the other lists, and what the pedal does with 57 or 12 at 6/4, are still hardware questions (#147).Tests:
usecase_rhythm_tests345,usecase_count_in_tests1502,catalog944,commands1584 (an end-to-end refusal on a scratch card leaves both memory files byte-identical); harness checks for 6/4 and unlisted-beat rows. Non-GUI ctest 56/56; the six GUI harnesses 6/6 locally.