mirror of
https://github.com/9x/sheetmusictrainer.git
synced 2026-11-03 22:03:54 +01:00
Mobile width fix, 6/8 crash fix, more arpeggio+Giuliani variants
- PhraseSheetMusic: min width per bar scales with notation density (16ths 320px, 8ths 260px, quarters 210px) — narrow screens wrap into more rows instead of clipping notes off-screen. - 6/8 crash fixed at the root: melody templatesFor had no 6/8 templates (empty pool -> pick on empty array). Added 6/8 template sets for levels 1/2 plus an empty-pool fallback guard so generators can never pick from an empty array again (any meter/level combination stays playable). - More arpeggio broken-chord figures: 1-3-5-3, 1-2-1-3-2-1, 1-5-4-5-3-5, 1-3-2-5-3-2. - More Giuliani patterns: p-i-a-i (wide), p-m-a-m-i, p-i-m-a-m-i (waltz feel), p-i-m-a-i-a. - Tests 179/179.
This commit is contained in:
@@ -65,6 +65,22 @@ const TEMPLATES_4_L3: number[][] = [
|
||||
[480, 480, 360, 120, 480],
|
||||
[720, 120, 120, 120, 120, 480],
|
||||
];
|
||||
// 6/8 templates (6 quarter-ticks = 2880 per bar; 8th-note oriented).
|
||||
const TEMPLATES_6_L1: number[][] = [
|
||||
[240, 240, 240, 240, 240, 240, 480, 480],
|
||||
[480, 480, 240, 240, 240, 240, 480, 480],
|
||||
[240, 240, 480, 480, 480, 240, 240, 240, 240],
|
||||
[960, 480, 480, 480, 480],
|
||||
[480, 480, 480, 480, 960],
|
||||
[1440, 480, 480, 480],
|
||||
];
|
||||
const TEMPLATES_6_L2: number[][] = [
|
||||
[480, 480, 120, 120, 120, 120, 240, 240, 480, 480],
|
||||
[120, 120, 120, 120, 480, 480, 240, 240, 240, 240, 240, 240],
|
||||
[720, 240, 240, 480, 480, 240, 240, 240, 240],
|
||||
[360, 120, 480, 720, 480, 240, 240, 240, 240],
|
||||
[480, 240, 240, 480, 240, 240, 480, 240, 240, 240],
|
||||
];
|
||||
const TEMPLATES_3_L1: number[][] = [
|
||||
[480, 480, 480],
|
||||
[960, 480],
|
||||
@@ -87,10 +103,23 @@ const TEMPLATES_3_L3: number[][] = [
|
||||
];
|
||||
|
||||
function templatesFor(meter: { numerator: 3 | 4 | 6 }, level: 1 | 2 | 3): number[][] {
|
||||
const base = meter.numerator === 4
|
||||
? (level === 1 ? TEMPLATES_4_L1 : level === 2 ? [...TEMPLATES_4_L1, ...TEMPLATES_4_L2] : [...TEMPLATES_4_L1, ...TEMPLATES_4_L2, ...TEMPLATES_4_L3])
|
||||
: (level === 1 ? TEMPLATES_3_L1 : level === 2 ? [...TEMPLATES_3_L1, ...TEMPLATES_3_L2] : [...TEMPLATES_3_L1, ...TEMPLATES_3_L2, ...TEMPLATES_3_L3]);
|
||||
return base.filter(t => t.reduce((a, b) => a + b, 0) === meter.numerator * PPQ);
|
||||
let base: number[][];
|
||||
if (meter.numerator === 4) {
|
||||
base = level === 1 ? TEMPLATES_4_L1 : level === 2 ? [...TEMPLATES_4_L1, ...TEMPLATES_4_L2] : [...TEMPLATES_4_L1, ...TEMPLATES_4_L2, ...TEMPLATES_4_L3];
|
||||
} else if (meter.numerator === 6) {
|
||||
base = level === 1 ? TEMPLATES_6_L1 : [...TEMPLATES_6_L1, ...TEMPLATES_6_L2];
|
||||
} else {
|
||||
base = level === 1 ? TEMPLATES_3_L1 : level === 2 ? [...TEMPLATES_3_L1, ...TEMPLATES_3_L2] : [...TEMPLATES_3_L1, ...TEMPLATES_3_L2, ...TEMPLATES_3_L3];
|
||||
}
|
||||
const out = base.filter(t => t.reduce((a, b) => a + b, 0) === meter.numerator * PPQ);
|
||||
// Never return an empty pool — fall back to the closest available level
|
||||
// so generator.pick on an empty array can never crash the app.
|
||||
if (out.length === 0) {
|
||||
const all = [...TEMPLATES_4_L1, ...TEMPLATES_4_L2, ...TEMPLATES_3_L1, ...TEMPLATES_3_L2, ...TEMPLATES_6_L1];
|
||||
const anyFit = all.filter(t => t.reduce((a, b) => a + b, 0) === meter.numerator * PPQ);
|
||||
return anyFit.length > 0 ? anyFit : [[meter.numerator * PPQ]];
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** Motion weights by semitone distance (soft preference, contract C3). */
|
||||
|
||||
Reference in New Issue
Block a user