Mobile clipping fix + 6/8 render crash fix (root cause) + more variants

- Clipping: min width per bar raised (16ths 420px, 8ths 330px, quarters
  240px) and format width reserves more room (clef/key 100px, barline
  margin 24px) — narrow screens now wrap into more rows with real margins
  instead of clipping the last bar.
- 6/8 crash ROOT CAUSE: Giuliani figures whose length didn't divide the
  bar into renderable note values produced 96-tick durations (32nd-ish)
  that durationCode can't spell -> renderer threw -> white screen.
  Giuliani generator now snaps figure subdivision to renderable durations
  (per 2/3/4 divisions, quarter-note walk as last resort); pimamim in
  6/8 renders as a walking quarter figure.
- 6/8 melody templates: fixed sums (were 2640/3120 in 4 templates — those
  filtered out, silently degrading to the fallback).
- More variants: arpeggio figures 1-3-5-3, 1-2-1-3-2-1, 1-5-4-5-3-5,
  1-3-2-5-3-2; Giuliani patterns p-i-a-i, p-m-a-m-i, p-i-m-a-m-i (waltz),
  p-i-m-a-i-a.
- Tests 179/179.
This commit is contained in:
OpenClaw
2026-09-15 03:35:00 +00:00
parent a7a1d34c11
commit 0b85f24797
4 changed files with 51 additions and 30 deletions

View File

@@ -109,7 +109,7 @@ export const PhraseSheetMusic: React.FC<PhraseSheetMusicProps> = ({
// horizontal room than quarters) so narrow screens wrap into more rows
// instead of clipping notes.
const densestTick = Math.min(...events.filter(e => e.pitch).map(e => e.durationTicks), 1920);
const minWidthPerBar = densestTick <= 120 ? 320 : densestTick <= 240 ? 260 : 210;
const minWidthPerBar = densestTick <= 120 ? 420 : densestTick <= 240 ? 330 : 240;
const barsPerRow = Math.max(1, Math.min(4, Math.floor((width - 40) / minWidthPerBar)));
const rowCount = Math.max(1, Math.ceil(totalBars / barsPerRow));
const isGrand = clef === 'grand';
@@ -324,7 +324,7 @@ export const PhraseSheetMusic: React.FC<PhraseSheetMusicProps> = ({
voices.push(buildVoice(treble, () => true));
}
const formatWidth = Math.max(60, barWidth - (isFirstOfRow ? 92 : 16));
const formatWidth = Math.max(60, barWidth - (isFirstOfRow ? 100 : 24));
const formatter = new Formatter();
formatter.joinVoices(voices.map(v => v.voice));
formatter.format(voices.map(v => v.voice), formatWidth);