mirror of
https://github.com/9x/sheetmusictrainer.git
synced 2026-11-03 22:03:54 +01:00
Feedback round 3: metronome/phrase integration, 16ths, count-in badge, wider transpose
- Metronome/phrase: 'With tempo' renamed to 'Sync to metronome' (auto-enables the metronome when selected); phrase click now yields to the metronome click (no more double clicks) with an explicit opt-out checkbox. - Count-in: subtle numeric badge next to the status line (muted pill, 11px). - Melody rhythm level 3 'Elaborate': sixteenth/dotted-eighth templates for 4/4 and 3/4; renderer supports 120/360-tick codes and beams 16th pairs. - Transpose control for library/import material widened to ±2 octaves. - Tests 161/161 (bar-total invariants hold over 500 seeds at level 2).
This commit is contained in:
@@ -40,6 +40,7 @@ interface Fragment {
|
||||
ticks: number;
|
||||
isAttack: boolean;
|
||||
isEighth: boolean;
|
||||
isSixteenth: boolean;
|
||||
staveKey: 'treble' | 'bass';
|
||||
rest: boolean;
|
||||
}
|
||||
@@ -47,7 +48,9 @@ interface Fragment {
|
||||
// Duration ticks → VexFlow duration code (subset the model guarantees).
|
||||
function durationCode(ticks: number): string | null {
|
||||
switch (ticks) {
|
||||
case 120: return '16';
|
||||
case 240: return '8';
|
||||
case 360: return '8d';
|
||||
case 480: return '4';
|
||||
case 720: return '4d';
|
||||
case 960: return '2';
|
||||
@@ -226,6 +229,7 @@ export const PhraseSheetMusic: React.FC<PhraseSheetMusicProps> = ({
|
||||
ticks,
|
||||
isAttack,
|
||||
isEighth: code === '8' && !rest,
|
||||
isSixteenth: code === '16' && !rest,
|
||||
staveKey,
|
||||
rest,
|
||||
});
|
||||
@@ -319,12 +323,15 @@ export const PhraseSheetMusic: React.FC<PhraseSheetMusicProps> = ({
|
||||
line.setContext(context).draw();
|
||||
}
|
||||
|
||||
// --- Beams: consecutive eighth pairs within one beat ---
|
||||
const eighths = frags.filter(f => f.isEighth && f.note instanceof StaveNote);
|
||||
for (let i = 0; i + 1 < eighths.length; i++) {
|
||||
const a = eighths[i], b2 = eighths[i + 1];
|
||||
// --- Beams: consecutive eighth/16th pairs within one beat ---
|
||||
const beamed = frags.filter(f => (f.isEighth || f.isSixteenth) && f.note instanceof StaveNote);
|
||||
for (let i = 0; i + 1 < beamed.length; i++) {
|
||||
const a = beamed[i], b2 = beamed[i + 1];
|
||||
if (a.staveKey !== b2.staveKey) continue;
|
||||
if (b2.start - a.start !== 240) continue;
|
||||
// consecutive within the same beat, same subdivision size
|
||||
const step = a.isSixteenth ? 120 : 240;
|
||||
if (b2.start - a.start !== step) continue;
|
||||
if ((a.ticks !== step) || (b2.ticks !== step)) continue;
|
||||
const beatA = Math.floor((a.start - measureStart) / 480);
|
||||
const beatB = Math.floor((b2.start - measureStart) / 480);
|
||||
if (beatA === beatB) {
|
||||
|
||||
Reference in New Issue
Block a user