@real-music-packages/web-core 0.29.0 → 0.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-ILIXNGPE.js +127 -0
- package/dist/chunk-ILIXNGPE.js.map +1 -0
- package/dist/index.d.ts +100 -1
- package/dist/index.js +22 -2
- package/dist/index.js.map +1 -1
- package/dist/promo.js +11 -1
- package/dist/promo.js.map +1 -1
- package/dist/scene/index.d.ts +19 -2
- package/dist/scene/index.js +169 -29
- package/dist/scene/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-N56UTMWA.js +0 -24
- package/dist/chunk-N56UTMWA.js.map +0 -1
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// src/intervals.ts
|
|
2
|
+
var INTERVALS = [
|
|
3
|
+
{ semitones: 1, label: "m2", mnemonic: "Jaws theme" },
|
|
4
|
+
{ semitones: 2, label: "M2", mnemonic: "Happy Birthday opening" },
|
|
5
|
+
{ semitones: 3, label: "m3", mnemonic: "Brahms' Lullaby" },
|
|
6
|
+
{ semitones: 4, label: "M3", mnemonic: "When the Saints" },
|
|
7
|
+
{ semitones: 5, label: "P4", mnemonic: "Here Comes the Bride" },
|
|
8
|
+
{ semitones: 6, label: "TT", mnemonic: "The Simpsons theme" },
|
|
9
|
+
{ semitones: 7, label: "P5", mnemonic: "Twinkle Twinkle" },
|
|
10
|
+
{ semitones: 8, label: "m6", mnemonic: "Love Story theme" },
|
|
11
|
+
{ semitones: 9, label: "M6", mnemonic: "NBC chimes" },
|
|
12
|
+
{ semitones: 10, label: "m7", mnemonic: "Somewhere (West Side Story)" },
|
|
13
|
+
{ semitones: 11, label: "M7", mnemonic: "Take On Me chorus" },
|
|
14
|
+
{ semitones: 12, label: "P8", mnemonic: "Somewhere Over the Rainbow" }
|
|
15
|
+
];
|
|
16
|
+
function intervalBySemitones(semitones) {
|
|
17
|
+
return INTERVALS.find((i) => i.semitones === semitones);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// src/music/fretboard.ts
|
|
21
|
+
var STRINGS = [40, 45, 50, 55, 59, 64];
|
|
22
|
+
var STRING_COUNT = STRINGS.length;
|
|
23
|
+
var MAX_FRET = 15;
|
|
24
|
+
function noteToFret(midi, stringIndex) {
|
|
25
|
+
const fret = midi - STRINGS[stringIndex];
|
|
26
|
+
if (fret < 0 || fret > MAX_FRET) return null;
|
|
27
|
+
return fret;
|
|
28
|
+
}
|
|
29
|
+
function midiToPositions(midi) {
|
|
30
|
+
const positions = [];
|
|
31
|
+
for (let s = 0; s < STRING_COUNT; s++) {
|
|
32
|
+
const fret = noteToFret(midi, s);
|
|
33
|
+
if (fret !== null) {
|
|
34
|
+
positions.push({ string: s, fret });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return positions;
|
|
38
|
+
}
|
|
39
|
+
function chosenPlacement(midi) {
|
|
40
|
+
for (let s = 0; s < STRING_COUNT; s++) {
|
|
41
|
+
const fret = noteToFret(midi, s);
|
|
42
|
+
if (fret !== null) {
|
|
43
|
+
return { string: s, fret };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
function buildPlacementMap(midiNotes) {
|
|
49
|
+
const map = /* @__PURE__ */ new Map();
|
|
50
|
+
for (const midi of midiNotes) {
|
|
51
|
+
const pos = chosenPlacement(midi);
|
|
52
|
+
if (pos !== null) {
|
|
53
|
+
map.set(midi, pos);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return map;
|
|
57
|
+
}
|
|
58
|
+
function enumerateBoardCells(visibleFrets = MAX_FRET) {
|
|
59
|
+
const cells = [];
|
|
60
|
+
for (let s = 0; s < STRING_COUNT; s++) {
|
|
61
|
+
for (let f = 0; f <= visibleFrets; f++) {
|
|
62
|
+
const midi = STRINGS[s] + f;
|
|
63
|
+
cells.push({ string: s, fret: f, midi });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return cells;
|
|
67
|
+
}
|
|
68
|
+
function bestPosition(midi, opts = {}) {
|
|
69
|
+
const max = opts.maxFret ?? MAX_FRET;
|
|
70
|
+
if (opts.preferHighString) {
|
|
71
|
+
for (let s = STRING_COUNT - 1; s >= 0; s--) {
|
|
72
|
+
const fret = midi - STRINGS[s];
|
|
73
|
+
if (fret >= 0 && fret <= max) return { string: s, fret };
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
for (let s = 0; s < STRING_COUNT; s++) {
|
|
78
|
+
const fret = midi - STRINGS[s];
|
|
79
|
+
if (fret >= 0 && fret <= max) return { string: s, fret };
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
function distributeChord(midiNotes) {
|
|
84
|
+
const sorted = [...midiNotes].sort((a, b) => a - b);
|
|
85
|
+
const usedStrings = /* @__PURE__ */ new Set();
|
|
86
|
+
const result = [];
|
|
87
|
+
for (const midi of sorted) {
|
|
88
|
+
let assigned = null;
|
|
89
|
+
for (let s = 0; s < STRING_COUNT; s++) {
|
|
90
|
+
if (usedStrings.has(s)) continue;
|
|
91
|
+
const fret = midi - STRINGS[s];
|
|
92
|
+
if (fret >= 0 && fret <= MAX_FRET) {
|
|
93
|
+
assigned = { string: s, fret };
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const idx = result.length;
|
|
98
|
+
if (assigned !== null) {
|
|
99
|
+
usedStrings.add(assigned.string);
|
|
100
|
+
result.push({
|
|
101
|
+
string: assigned.string,
|
|
102
|
+
fret: assigned.fret,
|
|
103
|
+
midi,
|
|
104
|
+
strumOffsetMs: idx * 12
|
|
105
|
+
});
|
|
106
|
+
} else {
|
|
107
|
+
result.push({ string: -1, fret: -1, midi, strumOffsetMs: idx * 12 });
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return result;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export {
|
|
114
|
+
INTERVALS,
|
|
115
|
+
intervalBySemitones,
|
|
116
|
+
STRINGS,
|
|
117
|
+
STRING_COUNT,
|
|
118
|
+
MAX_FRET,
|
|
119
|
+
noteToFret,
|
|
120
|
+
midiToPositions,
|
|
121
|
+
chosenPlacement,
|
|
122
|
+
buildPlacementMap,
|
|
123
|
+
enumerateBoardCells,
|
|
124
|
+
bestPosition,
|
|
125
|
+
distributeChord
|
|
126
|
+
};
|
|
127
|
+
//# sourceMappingURL=chunk-ILIXNGPE.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/intervals.ts","../src/music/fretboard.ts"],"sourcesContent":["export interface IntervalDef { semitones: number; label: string; mnemonic: string }\n\nexport const INTERVALS: IntervalDef[] = [\n { semitones: 1, label: 'm2', mnemonic: 'Jaws theme' },\n { semitones: 2, label: 'M2', mnemonic: 'Happy Birthday opening' },\n { semitones: 3, label: 'm3', mnemonic: \"Brahms' Lullaby\" },\n { semitones: 4, label: 'M3', mnemonic: 'When the Saints' },\n { semitones: 5, label: 'P4', mnemonic: 'Here Comes the Bride' },\n { semitones: 6, label: 'TT', mnemonic: 'The Simpsons theme' },\n { semitones: 7, label: 'P5', mnemonic: 'Twinkle Twinkle' },\n { semitones: 8, label: 'm6', mnemonic: 'Love Story theme' },\n { semitones: 9, label: 'M6', mnemonic: 'NBC chimes' },\n { semitones: 10, label: 'm7', mnemonic: 'Somewhere (West Side Story)' },\n { semitones: 11, label: 'M7', mnemonic: 'Take On Me chorus' },\n { semitones: 12, label: 'P8', mnemonic: 'Somewhere Over the Rainbow' },\n];\n\nexport function intervalBySemitones(semitones: number): IntervalDef | undefined {\n return INTERVALS.find(i => i.semitones === semitones);\n}\n","/**\n * fretboard.ts — Pure MIDI <-> guitar fretboard mapping utilities.\n *\n * Standard tuning EADGBE, index 0 = low E (6th string), index 5 = high E (1st string).\n * Open-string MIDI values: E2=40, A2=45, D3=50, G3=55, B3=59, E4=64.\n *\n * No Svelte, no audio, no side effects. Unit-testable in isolation.\n */\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\n/** Open-string MIDI pitches, low E to high E (index 0 = 6th string). */\nexport const STRINGS: readonly number[] = [40, 45, 50, 55, 59, 64];\n\n/** Number of strings on a standard guitar. */\nexport const STRING_COUNT = STRINGS.length; // 6\n\n/**\n * Maximum fret number rendered. Covers MIDI range 40–79 while staying\n * readable on mobile. Frets 0–15 give string 5 (high E, open=64) a top note\n * of 79 (G5), well above the piano's practical teaching range.\n */\nexport const MAX_FRET = 15;\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** A playable position on the fretboard. string is 0-based (0 = low E). */\nexport interface FretPosition {\n\t/** String index, 0 = low E (6th string), 5 = high E (1st string). */\n\tstring: number;\n\t/** Fret number, 0 = open. */\n\tfret: number;\n}\n\n// ---------------------------------------------------------------------------\n// Core helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Compute the fret number for a MIDI note on a specific string, or null if\n * outside the playable range [0, MAX_FRET].\n */\nexport function noteToFret(midi: number, stringIndex: number): number | null {\n\tconst fret = midi - STRINGS[stringIndex];\n\tif (fret < 0 || fret > MAX_FRET) return null;\n\treturn fret;\n}\n\n/**\n * Return ALL playable positions for a MIDI note across all strings.\n * Results are ordered from lowest string (index 0) to highest (index 5).\n * Returns an empty array if the note is unreachable on any string.\n */\nexport function midiToPositions(midi: number): FretPosition[] {\n\tconst positions: FretPosition[] = [];\n\tfor (let s = 0; s < STRING_COUNT; s++) {\n\t\tconst fret = noteToFret(midi, s);\n\t\tif (fret !== null) {\n\t\t\tpositions.push({ string: s, fret });\n\t\t}\n\t}\n\treturn positions;\n}\n\n/**\n * Single canonical placement for a MIDI note: the lowest string that can\n * reach it. This mirrors the piano's \"lowest sensible voicing\" — a beginner\n * sees the note in the lowest fret cluster available.\n *\n * Returns null if the note is off-board (below MIDI 40 or above 64+15=79).\n * Callers should silently skip off-board notes, matching Piano.svelte's\n * behavior for notes outside the visible white-key range.\n */\nexport function chosenPlacement(midi: number): FretPosition | null {\n\tfor (let s = 0; s < STRING_COUNT; s++) {\n\t\tconst fret = noteToFret(midi, s);\n\t\tif (fret !== null) {\n\t\t\treturn { string: s, fret };\n\t\t}\n\t}\n\treturn null;\n}\n\n/**\n * Build a Map from MIDI number to its chosen (canonical) FretPosition for a\n * collection of MIDI values. Off-board values are omitted.\n */\nexport function buildPlacementMap(midiNotes: Iterable<number>): Map<number, FretPosition> {\n\tconst map = new Map<number, FretPosition>();\n\tfor (const midi of midiNotes) {\n\t\tconst pos = chosenPlacement(midi);\n\t\tif (pos !== null) {\n\t\t\tmap.set(midi, pos);\n\t\t}\n\t}\n\treturn map;\n}\n\n// ---------------------------------------------------------------------------\n// Board enumeration\n// ---------------------------------------------------------------------------\n\n/**\n * Enumerate every (string, fret, midi) cell on the board for a given visible\n * fret range [0, visibleFrets]. Useful for rendering the fretboard grid.\n */\nexport function enumerateBoardCells(\n\tvisibleFrets: number = MAX_FRET\n): Array<FretPosition & { midi: number }> {\n\tconst cells: Array<FretPosition & { midi: number }> = [];\n\tfor (let s = 0; s < STRING_COUNT; s++) {\n\t\tfor (let f = 0; f <= visibleFrets; f++) {\n\t\t\tconst midi = STRINGS[s] + f;\n\t\t\tcells.push({ string: s, fret: f, midi });\n\t\t}\n\t}\n\treturn cells;\n}\n\n// ---------------------------------------------------------------------------\n// Best single position (public alias with options)\n// ---------------------------------------------------------------------------\n\nexport interface BestPositionOpts {\n\t/** Maximum fret to consider. Defaults to MAX_FRET. */\n\tmaxFret?: number;\n\t/** Prefer higher strings (index closer to 5) over lower ones. Default false. */\n\tpreferHighString?: boolean;\n}\n\n/**\n * A single playable, low-fret choice for a MIDI note, respecting opts.\n * Returns null if no string can reach the note within maxFret.\n */\nexport function bestPosition(midi: number, opts: BestPositionOpts = {}): FretPosition | null {\n\tconst max = opts.maxFret ?? MAX_FRET;\n\tif (opts.preferHighString) {\n\t\t// Iterate high string first\n\t\tfor (let s = STRING_COUNT - 1; s >= 0; s--) {\n\t\t\tconst fret = midi - STRINGS[s];\n\t\t\tif (fret >= 0 && fret <= max) return { string: s, fret };\n\t\t}\n\t\treturn null;\n\t}\n\t// Default: lowest string first (same as chosenPlacement)\n\tfor (let s = 0; s < STRING_COUNT; s++) {\n\t\tconst fret = midi - STRINGS[s];\n\t\tif (fret >= 0 && fret <= max) return { string: s, fret };\n\t}\n\treturn null;\n}\n\n// ---------------------------------------------------------------------------\n// Chord voicing helper\n// ---------------------------------------------------------------------------\n\nexport interface ChordVoicing {\n\t/** Assigned string index. */\n\tstring: number;\n\t/** Assigned fret. */\n\tfret: number;\n\t/** The MIDI note placed here. */\n\tmidi: number;\n\t/**\n\t * Strum offset in milliseconds from the first note. Notes are sorted\n\t * ascending (low string first) with 12 ms between each successive pick.\n\t */\n\tstrumOffsetMs: number;\n}\n\n/**\n * Distribute a set of MIDI notes across distinct strings for a strummed chord.\n *\n * Algorithm:\n * 1. Sort incoming MIDI notes ascending (lowest pitch first).\n * 2. For each note, greedily assign to the lowest available string whose fret\n * is in [0, MAX_FRET] and has not yet been used.\n * 3. If no free string is available for a note, it is still included in the\n * result (midi/strumOffsetMs set) but string/fret will be -1 to signal\n * \"play at frequency only\" — audio is still correct, just unpositioned.\n * 4. Add 12 ms strum offset per successive note.\n *\n * This keeps audio faithful to the lesson's intended harmony (unchanged lesson\n * data) while sounding guitar-like.\n */\nexport function distributeChord(midiNotes: number[]): ChordVoicing[] {\n\tconst sorted = [...midiNotes].sort((a, b) => a - b);\n\tconst usedStrings = new Set<number>();\n\tconst result: ChordVoicing[] = [];\n\n\tfor (const midi of sorted) {\n\t\tlet assigned: FretPosition | null = null;\n\t\t// Try each string from low to high, skip already-used ones\n\t\tfor (let s = 0; s < STRING_COUNT; s++) {\n\t\t\tif (usedStrings.has(s)) continue;\n\t\t\tconst fret = midi - STRINGS[s];\n\t\t\tif (fret >= 0 && fret <= MAX_FRET) {\n\t\t\t\tassigned = { string: s, fret };\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tconst idx = result.length;\n\t\tif (assigned !== null) {\n\t\t\tusedStrings.add(assigned.string);\n\t\t\tresult.push({\n\t\t\t\tstring: assigned.string,\n\t\t\t\tfret: assigned.fret,\n\t\t\t\tmidi,\n\t\t\t\tstrumOffsetMs: idx * 12\n\t\t\t});\n\t\t} else {\n\t\t\t// Off-board or all strings taken: include but mark unpositioned\n\t\t\tresult.push({ string: -1, fret: -1, midi, strumOffsetMs: idx * 12 });\n\t\t}\n\t}\n\n\treturn result;\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.ts
CHANGED
|
@@ -61,4 +61,103 @@ interface ChordTemplate {
|
|
|
61
61
|
* most specific quality (e.g. maj7 before plain major). */
|
|
62
62
|
declare const CHORD_TEMPLATES: ChordTemplate[];
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
/**
|
|
65
|
+
* fretboard.ts — Pure MIDI <-> guitar fretboard mapping utilities.
|
|
66
|
+
*
|
|
67
|
+
* Standard tuning EADGBE, index 0 = low E (6th string), index 5 = high E (1st string).
|
|
68
|
+
* Open-string MIDI values: E2=40, A2=45, D3=50, G3=55, B3=59, E4=64.
|
|
69
|
+
*
|
|
70
|
+
* No Svelte, no audio, no side effects. Unit-testable in isolation.
|
|
71
|
+
*/
|
|
72
|
+
/** Open-string MIDI pitches, low E to high E (index 0 = 6th string). */
|
|
73
|
+
declare const STRINGS: readonly number[];
|
|
74
|
+
/** Number of strings on a standard guitar. */
|
|
75
|
+
declare const STRING_COUNT: number;
|
|
76
|
+
/**
|
|
77
|
+
* Maximum fret number rendered. Covers MIDI range 40–79 while staying
|
|
78
|
+
* readable on mobile. Frets 0–15 give string 5 (high E, open=64) a top note
|
|
79
|
+
* of 79 (G5), well above the piano's practical teaching range.
|
|
80
|
+
*/
|
|
81
|
+
declare const MAX_FRET = 15;
|
|
82
|
+
/** A playable position on the fretboard. string is 0-based (0 = low E). */
|
|
83
|
+
interface FretPosition {
|
|
84
|
+
/** String index, 0 = low E (6th string), 5 = high E (1st string). */
|
|
85
|
+
string: number;
|
|
86
|
+
/** Fret number, 0 = open. */
|
|
87
|
+
fret: number;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Compute the fret number for a MIDI note on a specific string, or null if
|
|
91
|
+
* outside the playable range [0, MAX_FRET].
|
|
92
|
+
*/
|
|
93
|
+
declare function noteToFret(midi: number, stringIndex: number): number | null;
|
|
94
|
+
/**
|
|
95
|
+
* Return ALL playable positions for a MIDI note across all strings.
|
|
96
|
+
* Results are ordered from lowest string (index 0) to highest (index 5).
|
|
97
|
+
* Returns an empty array if the note is unreachable on any string.
|
|
98
|
+
*/
|
|
99
|
+
declare function midiToPositions(midi: number): FretPosition[];
|
|
100
|
+
/**
|
|
101
|
+
* Single canonical placement for a MIDI note: the lowest string that can
|
|
102
|
+
* reach it. This mirrors the piano's "lowest sensible voicing" — a beginner
|
|
103
|
+
* sees the note in the lowest fret cluster available.
|
|
104
|
+
*
|
|
105
|
+
* Returns null if the note is off-board (below MIDI 40 or above 64+15=79).
|
|
106
|
+
* Callers should silently skip off-board notes, matching Piano.svelte's
|
|
107
|
+
* behavior for notes outside the visible white-key range.
|
|
108
|
+
*/
|
|
109
|
+
declare function chosenPlacement(midi: number): FretPosition | null;
|
|
110
|
+
/**
|
|
111
|
+
* Build a Map from MIDI number to its chosen (canonical) FretPosition for a
|
|
112
|
+
* collection of MIDI values. Off-board values are omitted.
|
|
113
|
+
*/
|
|
114
|
+
declare function buildPlacementMap(midiNotes: Iterable<number>): Map<number, FretPosition>;
|
|
115
|
+
/**
|
|
116
|
+
* Enumerate every (string, fret, midi) cell on the board for a given visible
|
|
117
|
+
* fret range [0, visibleFrets]. Useful for rendering the fretboard grid.
|
|
118
|
+
*/
|
|
119
|
+
declare function enumerateBoardCells(visibleFrets?: number): Array<FretPosition & {
|
|
120
|
+
midi: number;
|
|
121
|
+
}>;
|
|
122
|
+
interface BestPositionOpts {
|
|
123
|
+
/** Maximum fret to consider. Defaults to MAX_FRET. */
|
|
124
|
+
maxFret?: number;
|
|
125
|
+
/** Prefer higher strings (index closer to 5) over lower ones. Default false. */
|
|
126
|
+
preferHighString?: boolean;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* A single playable, low-fret choice for a MIDI note, respecting opts.
|
|
130
|
+
* Returns null if no string can reach the note within maxFret.
|
|
131
|
+
*/
|
|
132
|
+
declare function bestPosition(midi: number, opts?: BestPositionOpts): FretPosition | null;
|
|
133
|
+
interface ChordVoicing {
|
|
134
|
+
/** Assigned string index. */
|
|
135
|
+
string: number;
|
|
136
|
+
/** Assigned fret. */
|
|
137
|
+
fret: number;
|
|
138
|
+
/** The MIDI note placed here. */
|
|
139
|
+
midi: number;
|
|
140
|
+
/**
|
|
141
|
+
* Strum offset in milliseconds from the first note. Notes are sorted
|
|
142
|
+
* ascending (low string first) with 12 ms between each successive pick.
|
|
143
|
+
*/
|
|
144
|
+
strumOffsetMs: number;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Distribute a set of MIDI notes across distinct strings for a strummed chord.
|
|
148
|
+
*
|
|
149
|
+
* Algorithm:
|
|
150
|
+
* 1. Sort incoming MIDI notes ascending (lowest pitch first).
|
|
151
|
+
* 2. For each note, greedily assign to the lowest available string whose fret
|
|
152
|
+
* is in [0, MAX_FRET] and has not yet been used.
|
|
153
|
+
* 3. If no free string is available for a note, it is still included in the
|
|
154
|
+
* result (midi/strumOffsetMs set) but string/fret will be -1 to signal
|
|
155
|
+
* "play at frequency only" — audio is still correct, just unpositioned.
|
|
156
|
+
* 4. Add 12 ms strum offset per successive note.
|
|
157
|
+
*
|
|
158
|
+
* This keeps audio faithful to the lesson's intended harmony (unchanged lesson
|
|
159
|
+
* data) while sounding guitar-like.
|
|
160
|
+
*/
|
|
161
|
+
declare function distributeChord(midiNotes: number[]): ChordVoicing[];
|
|
162
|
+
|
|
163
|
+
export { ALL_KEYS, type BestPositionOpts, CHORD_TEMPLATES, type ChordTemplate, type ChordVoicing, type FretPosition, INTERVALS, type IntervalDef, KEYS_PREFER_FLATS, MAJOR_SCALE_INTERVALS, MAX_FRET, NOTE_NAMES, NOTE_NAMES_FLAT, STRINGS, STRING_COUNT, bestPosition, buildPlacementMap, chosenPlacement, distributeChord, enumerateBoardCells, getMidiNote, getScaleDegree, getStability, intervalBySemitones, isInScale, midiToFrequency, midiToNoteName, midiToPositions, noteNameToIndex, noteToFret, pitchClass, useFlatsForKeyFifths, useFlatsForKeyName };
|
package/dist/index.js
CHANGED
|
@@ -5,8 +5,18 @@ import {
|
|
|
5
5
|
} from "./chunk-BYZBLH25.js";
|
|
6
6
|
import {
|
|
7
7
|
INTERVALS,
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
MAX_FRET,
|
|
9
|
+
STRINGS,
|
|
10
|
+
STRING_COUNT,
|
|
11
|
+
bestPosition,
|
|
12
|
+
buildPlacementMap,
|
|
13
|
+
chosenPlacement,
|
|
14
|
+
distributeChord,
|
|
15
|
+
enumerateBoardCells,
|
|
16
|
+
intervalBySemitones,
|
|
17
|
+
midiToPositions,
|
|
18
|
+
noteToFret
|
|
19
|
+
} from "./chunk-ILIXNGPE.js";
|
|
10
20
|
import {
|
|
11
21
|
ALL_KEYS,
|
|
12
22
|
MAJOR_SCALE_INTERVALS,
|
|
@@ -46,8 +56,16 @@ export {
|
|
|
46
56
|
INTERVALS,
|
|
47
57
|
KEYS_PREFER_FLATS,
|
|
48
58
|
MAJOR_SCALE_INTERVALS,
|
|
59
|
+
MAX_FRET,
|
|
49
60
|
NOTE_NAMES,
|
|
50
61
|
NOTE_NAMES_FLAT,
|
|
62
|
+
STRINGS,
|
|
63
|
+
STRING_COUNT,
|
|
64
|
+
bestPosition,
|
|
65
|
+
buildPlacementMap,
|
|
66
|
+
chosenPlacement,
|
|
67
|
+
distributeChord,
|
|
68
|
+
enumerateBoardCells,
|
|
51
69
|
getMidiNote,
|
|
52
70
|
getScaleDegree,
|
|
53
71
|
getStability,
|
|
@@ -55,7 +73,9 @@ export {
|
|
|
55
73
|
isInScale,
|
|
56
74
|
midiToFrequency,
|
|
57
75
|
midiToNoteName,
|
|
76
|
+
midiToPositions,
|
|
58
77
|
noteNameToIndex,
|
|
78
|
+
noteToFret,
|
|
59
79
|
pitchClass,
|
|
60
80
|
useFlatsForKeyFifths,
|
|
61
81
|
useFlatsForKeyName
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/frequency.ts","../src/chords.ts"],"sourcesContent":["/** Equal-temperament MIDI → frequency (A4 = MIDI 69 = 440 Hz). */\nexport function midiToFrequency(midi: number): number {\n return 440 * Math.pow(2, (midi - 69) / 12);\n}\n","export interface ChordTemplate { name: string; intervals: number[] }\n\n/** Chord-quality templates, richer qualities first so naming matches the\n * most specific quality (e.g. maj7 before plain major). */\nexport const CHORD_TEMPLATES: ChordTemplate[] = [\n { name: 'maj7', intervals: [0, 4, 7, 11] },\n { name: '7', intervals: [0, 4, 7, 10] },\n { name: 'm7', intervals: [0, 3, 7, 10] },\n { name: 'dim7', intervals: [0, 3, 6, 9] },\n { name: 'm7b5', intervals: [0, 3, 6, 10] },\n { name: '', intervals: [0, 4, 7] },\n { name: 'm', intervals: [0, 3, 7] },\n { name: 'dim', intervals: [0, 3, 6] },\n { name: 'aug', intervals: [0, 4, 8] },\n { name: 'sus4', intervals: [0, 5, 7] },\n { name: 'sus2', intervals: [0, 2, 7] },\n];\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/frequency.ts","../src/chords.ts"],"sourcesContent":["/** Equal-temperament MIDI → frequency (A4 = MIDI 69 = 440 Hz). */\nexport function midiToFrequency(midi: number): number {\n return 440 * Math.pow(2, (midi - 69) / 12);\n}\n","export interface ChordTemplate { name: string; intervals: number[] }\n\n/** Chord-quality templates, richer qualities first so naming matches the\n * most specific quality (e.g. maj7 before plain major). */\nexport const CHORD_TEMPLATES: ChordTemplate[] = [\n { name: 'maj7', intervals: [0, 4, 7, 11] },\n { name: '7', intervals: [0, 4, 7, 10] },\n { name: 'm7', intervals: [0, 3, 7, 10] },\n { name: 'dim7', intervals: [0, 3, 6, 9] },\n { name: 'm7b5', intervals: [0, 3, 6, 10] },\n { name: '', intervals: [0, 4, 7] },\n { name: 'm', intervals: [0, 3, 7] },\n { name: 'dim', intervals: [0, 3, 6] },\n { name: 'aug', intervals: [0, 4, 8] },\n { name: 'sus4', intervals: [0, 5, 7] },\n { name: 'sus2', intervals: [0, 2, 7] },\n];\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACO,SAAS,gBAAgB,MAAsB;AACpD,SAAO,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,EAAE;AAC3C;;;ACCO,IAAM,kBAAmC;AAAA,EAC9C,EAAE,MAAM,QAAQ,WAAW,CAAC,GAAG,GAAG,GAAG,EAAE,EAAE;AAAA,EACzC,EAAE,MAAM,KAAQ,WAAW,CAAC,GAAG,GAAG,GAAG,EAAE,EAAE;AAAA,EACzC,EAAE,MAAM,MAAQ,WAAW,CAAC,GAAG,GAAG,GAAG,EAAE,EAAE;AAAA,EACzC,EAAE,MAAM,QAAQ,WAAW,CAAC,GAAG,GAAG,GAAG,CAAC,EAAE;AAAA,EACxC,EAAE,MAAM,QAAQ,WAAW,CAAC,GAAG,GAAG,GAAG,EAAE,EAAE;AAAA,EACzC,EAAE,MAAM,IAAQ,WAAW,CAAC,GAAG,GAAG,CAAC,EAAE;AAAA,EACrC,EAAE,MAAM,KAAQ,WAAW,CAAC,GAAG,GAAG,CAAC,EAAE;AAAA,EACrC,EAAE,MAAM,OAAQ,WAAW,CAAC,GAAG,GAAG,CAAC,EAAE;AAAA,EACrC,EAAE,MAAM,OAAQ,WAAW,CAAC,GAAG,GAAG,CAAC,EAAE;AAAA,EACrC,EAAE,MAAM,QAAQ,WAAW,CAAC,GAAG,GAAG,CAAC,EAAE;AAAA,EACrC,EAAE,MAAM,QAAQ,WAAW,CAAC,GAAG,GAAG,CAAC,EAAE;AACvC;","names":[]}
|
package/dist/promo.js
CHANGED
|
@@ -209,7 +209,17 @@ function extractGeometry(osmd, canvas, inkSumThreshold) {
|
|
|
209
209
|
const pageW = page?.PositionAndShape?.Size?.width;
|
|
210
210
|
const musicSystems = page?.MusicSystems ?? [];
|
|
211
211
|
if (!pageW || !musicSystems.length) return { systems: [], measures: [], measureColumns: [], content: full, noteCols: [] };
|
|
212
|
-
|
|
212
|
+
let contentRightU = -Infinity, contentLeftU = Infinity;
|
|
213
|
+
for (const staves of graphic?.MeasureList ?? []) {
|
|
214
|
+
for (const m of staves ?? []) {
|
|
215
|
+
const ps = m?.PositionAndShape;
|
|
216
|
+
if (!ps?.AbsolutePosition || !ps?.Size || !(ps.Size.width > 0.1)) continue;
|
|
217
|
+
contentRightU = Math.max(contentRightU, ps.AbsolutePosition.x + ps.Size.width);
|
|
218
|
+
contentLeftU = Math.min(contentLeftU, ps.AbsolutePosition.x);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
const spanU = Number.isFinite(contentRightU) && Number.isFinite(contentLeftU) ? contentRightU + contentLeftU : pageW;
|
|
222
|
+
const f = canvas.width / (spanU > 0 ? spanU : pageW);
|
|
213
223
|
const toBox = (pas) => {
|
|
214
224
|
const p = pas?.AbsolutePosition;
|
|
215
225
|
const sz = pas?.Size;
|
package/dist/promo.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/promo.ts"],"sourcesContent":["// Promo utilities — browser-only except for parseMidi/midiDurationMs which are\n// pure (no DOM).\n//\n// Exports:\n// parseMidi / midiDurationMs — pure MIDI parser (no DOM; testable in Node)\n// renderNotation — OSMD canvas renderer (browser/OSMD only)\n// createPromoSampler — Tone.js sampler factory (browser/Tone only)\n//\n// Unit tests cover parseMidi (tests/midi.test.ts).\n// renderNotation and createPromoSampler are browser-only — consumers' dom tests\n// cover them (OSMD and Tone.js require a browser context).\n\n// ─── MIDI parser ──────────────────────────────────────────────────────────────\n// Moved verbatim from realmusictheory/site/src/lib/promo/midiDuration.ts.\n\nexport interface MidiNote {\n midi: number;\n startMs: number;\n /** Audible duration in ms — extended while the sustain pedal is held. */\n durMs: number;\n /** 0..1 */\n velocity: number;\n}\n\nexport interface ParsedMidi {\n /** When the last notated note is released (pre-pedal-tail), in ms — paces the playhead. */\n durationMs: number;\n notes: MidiNote[];\n}\n\ninterface RawEvent {\n tick: number;\n kind: 'on' | 'off' | 'sustain' | 'tempo';\n midi?: number;\n velocity?: number;\n on?: boolean; // sustain down?\n us?: number; // tempo in µs/beat\n}\n\nexport function parseMidi(buf: ArrayBuffer): ParsedMidi {\n try {\n const dv = new DataView(buf);\n let p = 0;\n const u8 = () => dv.getUint8(p++);\n const u16 = () => { const v = dv.getUint16(p); p += 2; return v; };\n const u32 = () => { const v = dv.getUint32(p); p += 4; return v; };\n\n if (u32() !== 0x4d546864) return { durationMs: 0, notes: [] }; // 'MThd'\n const headerLen = u32();\n u16(); // format\n const ntrk = u16();\n const division = u16();\n p = 8 + headerLen;\n if (division & 0x8000) return { durationMs: 0, notes: [] }; // SMPTE — not handled\n const tpq = division || 480;\n\n const events: RawEvent[] = [];\n for (let t = 0; t < ntrk; t++) {\n if (p + 8 > dv.byteLength || u32() !== 0x4d54726b) break; // 'MTrk'\n const len = u32();\n const end = Math.min(p + len, dv.byteLength);\n let tick = 0;\n let running = 0;\n while (p < end) {\n let dt = 0, b: number;\n do { b = u8(); dt = (dt << 7) | (b & 0x7f); } while (b & 0x80 && p < end);\n tick += dt;\n let status = dv.getUint8(p);\n if (status & 0x80) { p++; running = status; } else { status = running; }\n if (status === 0xff) {\n const type = u8();\n let l = 0, bb: number;\n do { bb = u8(); l = (l << 7) | (bb & 0x7f); } while (bb & 0x80 && p < end);\n if (type === 0x51 && l === 3) {\n events.push({\n tick,\n kind: 'tempo',\n us: (dv.getUint8(p) << 16) | (dv.getUint8(p + 1) << 8) | dv.getUint8(p + 2),\n });\n }\n p += l;\n } else if (status === 0xf0 || status === 0xf7) {\n let l = 0, bb: number;\n do { bb = u8(); l = (l << 7) | (bb & 0x7f); } while (bb & 0x80 && p < end);\n p += l;\n } else {\n const hi = status & 0xf0;\n if (hi === 0x90 || hi === 0x80) {\n const midi = u8();\n const vel = u8();\n if (hi === 0x90 && vel > 0) events.push({ tick, kind: 'on', midi, velocity: vel / 127 });\n else events.push({ tick, kind: 'off', midi });\n } else if (hi === 0xb0) {\n const cc = u8();\n const val = u8();\n if (cc === 64) events.push({ tick, kind: 'sustain', on: val >= 64 });\n } else {\n p += hi === 0xc0 || hi === 0xd0 ? 1 : 2;\n }\n }\n }\n p = end;\n }\n\n // tick → ms via the tempo map\n const tempos = events.filter((e) => e.kind === 'tempo').sort((a, b) => a.tick - b.tick);\n if (!tempos.length || tempos[0].tick > 0) tempos.unshift({ tick: 0, kind: 'tempo', us: 500000 });\n const tickToMs = (tick: number): number => {\n let ms = 0;\n for (let i = 0; i < tempos.length; i++) {\n const segStart = tempos[i].tick;\n if (segStart >= tick) break;\n const segEnd = i + 1 < tempos.length ? Math.min(tempos[i + 1].tick, tick) : tick;\n ms += ((segEnd - segStart) / tpq) * ((tempos[i].us ?? 500000) / 1000);\n }\n return ms;\n };\n\n // Sustain spans (tick ranges where the pedal is down)\n const sustainEvents = events.filter((e) => e.kind === 'sustain').sort((a, b) => a.tick - b.tick);\n const pedalUpAfter = (tick: number): number | null => {\n for (const s of sustainEvents) if (!s.on && s.tick >= tick) return s.tick;\n return null;\n };\n const pedalDownAt = (tick: number): boolean => {\n let down = false;\n for (const s of sustainEvents) { if (s.tick > tick) break; down = !!s.on; }\n return down;\n };\n\n // Pair note-ons with the next matching note-off\n const ordered = events.filter((e) => e.kind === 'on' || e.kind === 'off').sort((a, b) => a.tick - b.tick);\n const open: Record<number, { tick: number; vel: number }[]> = {};\n const notes: MidiNote[] = [];\n let lastOffTick = 0;\n for (const e of ordered) {\n const m = e.midi!;\n if (e.kind === 'on') {\n (open[m] ??= []).push({ tick: e.tick, vel: e.velocity ?? 0.7 });\n } else {\n const stack = open[m];\n if (stack && stack.length) {\n const start = stack.shift()!;\n let endTick = e.tick;\n lastOffTick = Math.max(lastOffTick, endTick);\n // Extend while pedal is held past the note-off.\n if (pedalDownAt(endTick)) {\n const up = pedalUpAfter(endTick);\n if (up != null) endTick = up;\n }\n const startMs = tickToMs(start.tick);\n notes.push({\n midi: m,\n startMs,\n durMs: Math.max(60, tickToMs(endTick) - startMs),\n velocity: start.vel,\n });\n }\n }\n }\n\n return { durationMs: tickToMs(lastOffTick), notes };\n } catch {\n return { durationMs: 0, notes: [] };\n }\n}\n\n/** Total playback duration in ms (0 if unparseable). */\nexport function midiDurationMs(buf: ArrayBuffer): number {\n return parseMidi(buf).durationMs;\n}\n\n// ─── Notation renderer ────────────────────────────────────────────────────────\n// Superset of RSR (stave-web-sightread/src/lib/promo/notation.ts) and RMT\n// (realmusictheory/site/src/lib/promo/notation.ts). Both per-staff measure boxes\n// (RSR) and per-measure column union boxes (RMT) are computed every render.\n//\n// Browser-only: requires document + opensheetmusicdisplay. No unit tests here —\n// consumers' dom tests cover renderNotation.\n\nexport interface Box {\n x: number;\n y: number;\n w: number;\n h: number;\n}\n\n/** One staff's slice of a measure, in canvas px. */\nexport interface StaffMeasureBox {\n index: number; // 0-based measure position within the rendered range\n staff: number; // 0 = top staff (RH/treble), 1 = bottom staff (LH/bass)\n box: Box;\n /** x of the measure's FIRST note (canvas px) — past any clef/key/time signature. */\n noteStartX: number;\n}\n\n/** Per-measure column box: union across all staves for that measure, in canvas px. */\nexport interface MeasureColumnBox extends Box {\n noteStartX: number;\n}\n\nexport interface RenderedNotation {\n canvas: HTMLCanvasElement;\n /** Per-row grand-staff system boxes in canvas px, top-to-bottom. */\n systems: Box[];\n /** Per-(measure, staff) boxes — RSR's geometry. */\n measures: StaffMeasureBox[];\n /** Per-measure column union across staves — RMT's geometry. */\n measureColumns: MeasureColumnBox[];\n /** Tight bounding box of all notation in canvas px (page whitespace cropped). */\n content: Box;\n /**\n * Per-distinct-onset engraved column position (measureIndex + fraction across\n * the note region), in time (left→right) order — one entry per played onset,\n * rests excluded, chords/both-hands collapsed. The scroll-cursor anchors to\n * THESE (the notes' real engraved X) instead of a time-fraction, so the\n * playhead lands exactly on each notehead. Empty/absent when extraction fails\n * or the engraving predates this field (cursor falls back to time/ordinal).\n */\n noteCols?: number[];\n}\n\nexport interface RenderNotationOpts {\n /** OSMD drawFrom/drawUpToMeasureNumber. Applied only when provided. */\n bars?: [number, number];\n /** Background fill colour. Default '#faf7f0' (RSR paper). */\n paper?: string;\n /** Pixel-sum threshold below which a pixel is counted as ink.\n * Default 690 (RSR: paper #faf7f0 sum ≈ 737). RMT uses 620. */\n inkSumThreshold?: number;\n /** Host div width in CSS px. Default 560 (RSR). RMT uses 620. */\n hostWidth?: number;\n /**\n * Notation scroll mode — drives BOTH the engraving layout here and the\n * follow-camera in the scroll-cursor/notation layers.\n *\n * 'hstack' (default) — engrave all measures on ONE horizontal staffline\n * (OSMD RenderSingleHorizontalStaffline). The follow window is a purely\n * HORIZONTAL slice so the playhead pans left→right with no vertical\n * row-break jump. The rasterized bitmap is one very wide row; geometry\n * (systems/measures) all share one row of y.\n *\n * 'vstack' — engrave the classic STACKED systems (normal page wrap into\n * multiple rows). The follow camera frames the ACTIVE system at a fixed\n * band position and scrolls VERTICALLY (eased) to the next system as the\n * playhead crosses systems — never a vertical leap across staff lines.\n *\n * Default 'hstack' (preserves the current single-row behavior).\n */\n scrollMode?: 'hstack' | 'vstack';\n /**\n * @deprecated Use `scrollMode` instead. Back-compat alias: `singleRow:true`\n * ⇔ `scrollMode:'hstack'`, `singleRow:false` ⇔ `scrollMode:'vstack'`.\n * When both are given, `scrollMode` wins.\n */\n singleRow?: boolean;\n}\n\n/** Padded union of boxes, clamped to the canvas. */\nfunction unionBox(boxes: Box[], canvas: HTMLCanvasElement, pad: number): Box {\n if (!boxes.length) return { x: 0, y: 0, w: canvas.width, h: canvas.height };\n const minX = Math.min(...boxes.map((b) => b.x));\n const minY = Math.min(...boxes.map((b) => b.y));\n const maxX = Math.max(...boxes.map((b) => b.x + b.w));\n const maxY = Math.max(...boxes.map((b) => b.y + b.h));\n const x = Math.max(0, minX - pad);\n const y = Math.max(0, minY - pad);\n return {\n x,\n y,\n w: Math.min(canvas.width, maxX + pad) - x,\n h: Math.min(canvas.height, maxY + pad) - y,\n };\n}\n\n/** Tight box around all non-paper pixels (notes, ledger lines, stems), padded. */\nfunction inkBoundingBox(canvas: HTMLCanvasElement, inkSumThreshold: number): Box | null {\n try {\n const ctx = canvas.getContext('2d', { willReadFrequently: true });\n if (!ctx) return null;\n const { width: W, height: H } = canvas;\n const data = ctx.getImageData(0, 0, W, H).data;\n let minX = W, minY = H, maxX = -1, maxY = -1;\n const step = 2;\n for (let y = 0; y < H; y += step) {\n for (let x = 0; x < W; x += step) {\n const i = (y * W + x) * 4;\n if (data[i + 3] < 16) continue;\n if (data[i] + data[i + 1] + data[i + 2] < inkSumThreshold) {\n if (x < minX) minX = x;\n if (x > maxX) maxX = x;\n if (y < minY) minY = y;\n if (y > maxY) maxY = y;\n }\n }\n }\n if (maxX < 0) return null;\n const pad = 16;\n const x = Math.max(0, minX - pad);\n const y = Math.max(0, minY - pad);\n return {\n x,\n y,\n w: Math.min(W, maxX + pad) - x,\n h: Math.min(H, maxY + pad) - y,\n };\n } catch {\n return null;\n }\n}\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\nfunction extractGeometry(\n osmd: any,\n canvas: HTMLCanvasElement,\n inkSumThreshold: number,\n): { systems: Box[]; measures: StaffMeasureBox[]; measureColumns: MeasureColumnBox[]; content: Box; noteCols: number[] } {\n const full: Box = { x: 0, y: 0, w: canvas.width, h: canvas.height };\n try {\n const graphic: any = osmd.GraphicSheet;\n const page: any = graphic?.MusicPages?.[0];\n const pageW: number = page?.PositionAndShape?.Size?.width;\n const musicSystems: any[] = page?.MusicSystems ?? [];\n if (!pageW || !musicSystems.length) return { systems: [], measures: [], measureColumns: [], content: full, noteCols: [] };\n\n // OSMD units → canvas px\n const f = canvas.width / pageW;\n const toBox = (pas: any): Box | null => {\n const p = pas?.AbsolutePosition;\n const sz = pas?.Size;\n if (!p || !sz) return null;\n return { x: p.x * f, y: p.y * f, w: sz.width * f, h: sz.height * f };\n };\n\n const systems: Box[] = musicSystems\n .map((s) => toBox(s?.PositionAndShape))\n .filter((b): b is Box => !!b && b.w > 1 && b.h > 1)\n .sort((a, b) => a.y - b.y);\n if (!systems.length) return { systems: [], measures: [], measureColumns: [], content: full, noteCols: [] };\n\n const measureList: any[][] = graphic?.MeasureList ?? [];\n\n // RSR geometry: per-(measure, staff) individual boxes\n const measures: StaffMeasureBox[] = [];\n measureList.forEach((staves, index) => {\n (staves ?? []).forEach((m: any, staff: number) => {\n const box = toBox(m?.PositionAndShape);\n if (box && box.w > 1 && box.h > 1) {\n const seX = (m?.staffEntries ?? [])[0]?.PositionAndShape?.AbsolutePosition?.x;\n const noteStartX = typeof seX === 'number' ? seX * f : box.x;\n measures.push({ index, staff, box, noteStartX });\n }\n });\n });\n\n // RMT geometry: per-measure column boxes (union across staves)\n const measureColumns: MeasureColumnBox[] = measureList\n .map((staves) => {\n const arr = staves ?? [];\n const boxes = arr\n .map((m: any) => toBox(m?.PositionAndShape))\n .filter((b: Box | null): b is Box => !!b && b.w > 1 && b.h > 1);\n if (!boxes.length) return null;\n const x0 = Math.min(...boxes.map((b) => b.x));\n const y0 = Math.min(...boxes.map((b) => b.y));\n const x1 = Math.max(...boxes.map((b) => b.x + b.w));\n const y1 = Math.max(...boxes.map((b) => b.y + b.h));\n // First note x across all staves; clamped into the column box\n let nx = Infinity;\n for (const m of arr) {\n const seX = (m?.staffEntries ?? [])[0]?.PositionAndShape?.AbsolutePosition?.x;\n if (typeof seX === 'number') nx = Math.min(nx, seX * f);\n }\n const noteStartX = Number.isFinite(nx) ? Math.max(x0, Math.min(nx, x1)) : x0;\n return { x: x0, y: y0, w: x1 - x0, h: y1 - y0, noteStartX };\n })\n .filter((b): b is MeasureColumnBox => !!b);\n\n // Per-distinct-onset engraved columns, in render (= time) order, rests excluded\n // — the cursor's TRUE anchor X. Each value is `ordinal + frac`, where `ordinal`\n // is the column's position among the DRAWN measures (matching the array index of\n // measureColumnsFromLayout — NOT the absolute MeasureList index, which would\n // overflow the columns array for excerpts whose first measure isn't bar 0), and\n // `frac` is the notehead's position across the measure's note region. Crucially\n // we iterate ONLY drawn measures (a valid box): with drawFrom/drawUpTo, OSMD's\n // MeasureList still holds EVERY source measure, so without this filter the whole\n // score leaks in (e.g. 128 cols for a 22-note excerpt) and never pairs 1:1 with\n // the played onsets, disabling the engraved-x anchor.\n // We key each note's engraved X by its rhythmic TIMESTAMP within the measure,\n // NOT by raw distinct X. Two staves' notes at the SAME beat engrave at slightly\n // different X (the grand staff isn't pixel-aligned), so deduping by X yielded\n // MORE columns than there are onsets (e.g. 45 cols vs 40 onsets) — which broke\n // the 1:1 pairing with the played onsets and forced the cursor onto a\n // linear-time fallback that drifts ~½ bar ahead of the real noteheads on dense\n // bars. Keying by timestamp collapses each beat to ONE column at the (leftmost)\n // engraved X of its noteheads, so the column count equals the distinct-onset\n // count and the cursor lands on the actual notehead, beat for beat.\n const noteCols: number[] = [];\n let ordinal = 0;\n measureList.forEach((staves) => {\n const arr = staves ?? [];\n let mx0 = Infinity, mx1 = -Infinity, nsx = Infinity;\n const byTs = new Map<number, number>(); // rhythmic timestamp → leftmost note X (px)\n for (const m of arr) {\n const mb = toBox(m?.PositionAndShape);\n if (mb) { mx0 = Math.min(mx0, mb.x); mx1 = Math.max(mx1, mb.x + mb.w); }\n const se0 = (m?.staffEntries ?? [])[0]?.PositionAndShape?.AbsolutePosition?.x;\n if (typeof se0 === 'number') nsx = Math.min(nsx, se0 * f);\n for (const se of (m?.staffEntries ?? [])) {\n const sx = se?.PositionAndShape?.AbsolutePosition?.x;\n if (typeof sx !== 'number') continue;\n const hasNote = (se?.graphicalVoiceEntries ?? []).some(\n (gve: any) => (gve?.notes ?? []).some(\n (gn: any) => !(gn?.sourceNote?.isRestFlag ?? gn?.sourceNote?.IsRest ?? false)));\n if (!hasNote) continue;\n // Within-measure rhythmic position (whole notes). Round to a fine grid so\n // float noise doesn't split a beat; falls back to X if unavailable.\n const tsRaw =\n se?.relInMeasureTimestamp?.RealValue ??\n se?.getAbsoluteTimestamp?.()?.RealValue ??\n se?.sourceStaffEntry?.Timestamp?.RealValue;\n const key = typeof tsRaw === 'number' ? Math.round(tsRaw * 10000) : Math.round(sx * f);\n const px = sx * f;\n const prev = byTs.get(key);\n if (prev === undefined || px < prev) byTs.set(key, px);\n }\n }\n // Skip undrawn measures (no valid box / zero width) — only drawn measures get\n // a column, so `ordinal` stays in lockstep with measureColumnsFromLayout.\n if (!Number.isFinite(mx0) || (mx1 - mx0) <= 1) return;\n const startX = Number.isFinite(nsx) ? Math.max(mx0, Math.min(nsx, mx1)) : mx0;\n const denom = (mx1 - startX) || 1;\n // Emit one column per distinct beat, in time order (the byTs keys are the\n // rounded timestamps), so column k ↔ onset k.\n for (const key of [...byTs.keys()].sort((a, b) => a - b)) {\n const x = byTs.get(key)!;\n noteCols.push(ordinal + Math.min(1, Math.max(0, (x - startX) / denom)));\n }\n ordinal++;\n });\n\n const content = inkBoundingBox(canvas, inkSumThreshold) ?? unionBox(systems, canvas, 14);\n return { systems, measures, measureColumns, content, noteCols };\n } catch {\n return { systems: [], measures: [], measureColumns: [], content: full, noteCols: [] };\n }\n}\n/* eslint-enable @typescript-eslint/no-explicit-any */\n\n/** Render a MusicXML string to a detached canvas + geometry.\n * Browser-only (requires document + opensheetmusicdisplay dynamic import). */\nexport async function renderNotation(\n xml: string,\n opts?: RenderNotationOpts,\n): Promise<RenderedNotation> {\n const paper = opts?.paper ?? '#faf7f0';\n const inkSumThreshold = opts?.inkSumThreshold ?? 690;\n const hostWidth = opts?.hostWidth ?? 560;\n // scrollMode drives the engraving layout; singleRow is the deprecated alias.\n // Default 'hstack' (single horizontal staffline) preserves current behavior.\n const scrollMode: 'hstack' | 'vstack' =\n opts?.scrollMode ?? (opts?.singleRow === false ? 'vstack' : 'hstack');\n const singleRow = scrollMode === 'hstack';\n\n // Literal dynamic import: consumers' bundlers (Vite/Rollup) must be able to\n // statically see the specifier to resolve + code-split it — a variable\n // specifier would reach the browser as a bare import and fail at runtime.\n const { OpenSheetMusicDisplay } = await import('opensheetmusicdisplay');\n\n const host = document.createElement('div');\n host.style.cssText = `position:fixed;left:-9999px;top:0;width:${hostWidth}px;background:${paper};`;\n document.body.appendChild(host);\n\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const osmd: any = new OpenSheetMusicDisplay(host, {\n backend: 'canvas',\n autoResize: false,\n drawTitle: false,\n drawSubtitle: false,\n drawComposer: false,\n drawLyricist: false,\n drawPartNames: false,\n });\n\n await osmd.load(xml);\n\n // hstack: engrave on a single horizontal staffline so the follow window pans\n // purely HORIZONTALLY (no stacked-system row breaks → no vertical playhead\n // jump). vstack: leave OSMD's default page wrap → classic stacked systems\n // (the follow camera then scrolls vertically between systems). Must be set\n // before render(); when on, the rasterized canvas becomes one wide row.\n if (singleRow) {\n osmd.setOptions({ renderSingleHorizontalStaffline: true } as never);\n }\n\n // Apply bar range only when provided (RSR's drawFrom/drawUpTo).\n if (opts?.bars) {\n osmd.setOptions({\n drawFromMeasureNumber: opts.bars[0],\n drawUpToMeasureNumber: opts.bars[1],\n } as never);\n }\n\n osmd.render();\n\n const canvas = host.querySelector('canvas');\n if (canvas) {\n const { systems, measures, measureColumns, content, noteCols } = extractGeometry(osmd, canvas, inkSumThreshold);\n document.body.removeChild(host);\n return { canvas, systems, measures, measureColumns, content, noteCols };\n }\n\n // SVG fallback: rasterize into a canvas so callers always get a canvas.\n const svg = host.querySelector('svg');\n if (svg) {\n const w = svg.clientWidth || hostWidth;\n const h = svg.clientHeight || 300;\n const svgStr = new XMLSerializer().serializeToString(svg);\n const dataUrl = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svgStr)));\n const img = new Image();\n await new Promise<void>((resolve, reject) => {\n img.onload = () => resolve();\n img.onerror = () => reject(new Error('svg rasterize failed'));\n img.src = dataUrl;\n });\n const out = document.createElement('canvas');\n out.width = w;\n out.height = h;\n const c2d = out.getContext('2d');\n if (!c2d) throw new Error('2d context unavailable');\n c2d.fillStyle = paper;\n c2d.fillRect(0, 0, w, h);\n c2d.drawImage(img, 0, 0, w, h);\n document.body.removeChild(host);\n return {\n canvas: out,\n systems: [],\n measures: [],\n measureColumns: [],\n content: { x: 0, y: 0, w, h },\n noteCols: [],\n };\n }\n\n document.body.removeChild(host);\n throw new Error('OSMD produced neither canvas nor SVG');\n } catch (e) {\n if (host.parentNode) document.body.removeChild(host);\n throw e;\n }\n}\n\n// ─── Promo sampler ────────────────────────────────────────────────────────────\n// The shared Tone.js setup prefix under both apps' createPromoAudio.\n// Scheduling (playEvents / playMidi / playWindowSolo / playForeground) stays\n// in each app.\n//\n// Browser-only: requires Tone.js dynamic import. No unit tests here —\n// consumers' dom tests cover createPromoSampler.\n\nimport { createSalamanderSampler, generateReverb } from './audioHelpers';\nimport { SALAMANDER_CDN_BASE, SALAMANDER_URLS_8, SALAMANDER_URLS_FULL } from './salamander';\n\nexport interface PromoSampler {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n Tone: any; // typeof Tone — typed loose like audioHelpers.ts does for Tone\n sampler: unknown; // Tone.Sampler; typed loose like audioHelpers\n getStream(): MediaStream;\n /** Master-bus analyser (post-chain, sink-only) for reactive visualizers, or\n * null if `analyser` wasn't requested. whozart's \"listen\" spectrum reads it. */\n getAnalyser(): AnalyserNode | null;\n /** Current audio-clock time in seconds (Tone.now()). */\n audioNow(): number;\n /** releaseAll on the sampler; safe no-op on failure. */\n stop(): void;\n}\n\n/** Per-app tone shaping for the shared promo mastering bus. Ported from\n * whozart's audio-impl chain (the richest of the fleet); `reading` is a drier,\n * closer voicing so sight-reading notation stays legible note-to-note. Apps\n * pick one via `createPromoSampler({ voicing })`. */\nexport type PromoVoicing = 'concertHall' | 'reading' | 'dry';\n\ninterface VoicingPreset {\n release: number;\n attack: number;\n eq: { low: number; mid: number; high: number; lowFrequency: number; highFrequency: number };\n comp: { threshold: number; ratio: number; attack: number; release: number; knee: number };\n widen: number;\n reverb: { decay: number; preDelay: number; wet: number };\n}\n\nconst PROMO_VOICINGS: Record<PromoVoicing, VoicingPreset> = {\n // whozart's hall sound: long ringing release, lifted lows/air, gentle glue\n // compression, wide stereo, a real-hall reverb tail.\n concertHall: {\n release: 1.8, attack: 0.005,\n eq: { low: 1.5, mid: -1, high: 1.5, lowFrequency: 280, highFrequency: 4500 },\n comp: { threshold: -18, ratio: 1.6, attack: 0.012, release: 0.3, knee: 18 },\n widen: 0.30,\n reverb: { decay: 3.5, preDelay: 0.04, wet: 0.22 },\n },\n // Sight-reading: shorter release + much drier/shorter reverb so consecutive\n // notes don't smear into each other while the eye tracks the staff. Small\n // presence bump in the mids for note-attack clarity, narrower stereo.\n reading: {\n release: 1.1, attack: 0.004,\n eq: { low: 0.5, mid: 1, high: 1, lowFrequency: 280, highFrequency: 5000 },\n comp: { threshold: -16, ratio: 2.0, attack: 0.008, release: 0.25, knee: 12 },\n widen: 0.18,\n reverb: { decay: 1.4, preDelay: 0.02, wet: 0.10 },\n },\n // Bone-dry — no reverb at all (e.g. a debugging/reference voicing).\n dry: {\n release: 1.0, attack: 0.004,\n eq: { low: 0, mid: 0, high: 0, lowFrequency: 280, highFrequency: 4500 },\n comp: { threshold: -14, ratio: 2.0, attack: 0.008, release: 0.25, knee: 10 },\n widen: 0,\n reverb: { decay: 1.0, preDelay: 0, wet: 0 },\n },\n};\n\nexport async function createPromoSampler(opts?: {\n /** Feed a 0-value ConstantSource into the capture stream so the recorder's\n * audio track is live from t=0 (silent intro scenes aren't dropped).\n * Default false (RSR behavior). RMT passes true. */\n keepAlive?: boolean;\n /** Override the voicing's sampler release time (seconds). */\n release?: number;\n /** Sampler volume in dB. Default -2. */\n volumeDb?: number;\n /** Per-app tone shaping (see PromoVoicing). Default 'concertHall'. */\n voicing?: PromoVoicing;\n /** Use the full A0..C8 Salamander anchor set instead of the 8-anchor set —\n * fuller tone, more samples to fetch. Default false (8-anchor). */\n fullSamples?: boolean;\n /** Expose a master-bus analyser (for reactive visualizers). Default false. */\n analyser?: boolean;\n}): Promise<PromoSampler> {\n const voicing = PROMO_VOICINGS[opts?.voicing ?? 'concertHall'];\n const release = opts?.release ?? voicing.release;\n const volumeDb = opts?.volumeDb ?? -2;\n const keepAlive = opts?.keepAlive ?? false;\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const Tone = (await import('tone')) as any;\n await Tone.start();\n\n // Unrouted sampler — we build the mastering bus off it (don't send the dry\n // sampler to the speakers, which would bypass the chain).\n const sampler = createSalamanderSampler(Tone, {\n urls: opts?.fullSamples ? SALAMANDER_URLS_FULL : SALAMANDER_URLS_8,\n baseUrl: SALAMANDER_CDN_BASE,\n release,\n attack: voicing.attack,\n volumeDb,\n connectToDestination: false,\n });\n\n // Mastering bus (ported from whozart): EQ tilt → glue compressor → stereo\n // widener → reverb → brick-wall limiter. Reverb is generated with a timeout\n // fallback so a slow/again-failing IR can never hang the render; on failure\n // the chain simply omits reverb.\n const eq = new Tone.EQ3(voicing.eq);\n const compressor = new Tone.Compressor(voicing.comp);\n const widener = new Tone.StereoWidener(voicing.widen);\n const limiter = new Tone.Limiter(-1.5);\n\n const reverb =\n voicing.reverb.wet > 0\n ? await generateReverb(\n Tone,\n { decay: voicing.reverb.decay, wet: voicing.reverb.wet },\n 4000,\n )\n : null;\n if (reverb) reverb.preDelay = voicing.reverb.preDelay;\n\n // chain() wires node→node in order; tail node feeds the taps below.\n const chainNodes = reverb ? [eq, compressor, widener, reverb, limiter] : [eq, compressor, widener, limiter];\n sampler.chain(...chainNodes);\n\n const ctx = Tone.getContext().rawContext as AudioContext;\n const mediaDest = ctx.createMediaStreamDestination();\n limiter.connect(mediaDest);\n // Also monitor through the speakers so a non-headless preview is audible.\n limiter.connect(Tone.getDestination());\n\n // Optional master-bus analyser (sink-only — no onward connection so it can't\n // double the signal). Drives whozart's reactive \"listen\" visualizer.\n let analyserNode: AnalyserNode | null = null;\n if (opts?.analyser) {\n analyserNode = ctx.createAnalyser();\n analyserNode.fftSize = 2048;\n analyserNode.smoothingTimeConstant = 0.8;\n limiter.connect(analyserNode);\n }\n\n // Optional: keep the audio track alive from t=0 so the recorder doesn't drop\n // silent intro scenes. RMT uses this; RSR does not.\n if (keepAlive) {\n const source = ctx.createConstantSource();\n source.offset.value = 0;\n source.connect(mediaDest);\n source.start();\n }\n\n await Tone.loaded();\n\n return {\n Tone,\n sampler,\n getStream(): MediaStream {\n return mediaDest.stream;\n },\n getAnalyser(): AnalyserNode | null {\n return analyserNode;\n },\n audioNow(): number {\n return Tone.now();\n },\n stop(): void {\n try {\n (sampler as unknown as { releaseAll?: () => void }).releaseAll?.();\n } catch {\n /* no-op */\n }\n },\n };\n}\n"],"mappings":"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","names":["y","x"]}
|
|
1
|
+
{"version":3,"sources":["../src/promo.ts"],"sourcesContent":["// Promo utilities — browser-only except for parseMidi/midiDurationMs which are\n// pure (no DOM).\n//\n// Exports:\n// parseMidi / midiDurationMs — pure MIDI parser (no DOM; testable in Node)\n// renderNotation — OSMD canvas renderer (browser/OSMD only)\n// createPromoSampler — Tone.js sampler factory (browser/Tone only)\n//\n// Unit tests cover parseMidi (tests/midi.test.ts).\n// renderNotation and createPromoSampler are browser-only — consumers' dom tests\n// cover them (OSMD and Tone.js require a browser context).\n\n// ─── MIDI parser ──────────────────────────────────────────────────────────────\n// Moved verbatim from realmusictheory/site/src/lib/promo/midiDuration.ts.\n\nexport interface MidiNote {\n midi: number;\n startMs: number;\n /** Audible duration in ms — extended while the sustain pedal is held. */\n durMs: number;\n /** 0..1 */\n velocity: number;\n}\n\nexport interface ParsedMidi {\n /** When the last notated note is released (pre-pedal-tail), in ms — paces the playhead. */\n durationMs: number;\n notes: MidiNote[];\n}\n\ninterface RawEvent {\n tick: number;\n kind: 'on' | 'off' | 'sustain' | 'tempo';\n midi?: number;\n velocity?: number;\n on?: boolean; // sustain down?\n us?: number; // tempo in µs/beat\n}\n\nexport function parseMidi(buf: ArrayBuffer): ParsedMidi {\n try {\n const dv = new DataView(buf);\n let p = 0;\n const u8 = () => dv.getUint8(p++);\n const u16 = () => { const v = dv.getUint16(p); p += 2; return v; };\n const u32 = () => { const v = dv.getUint32(p); p += 4; return v; };\n\n if (u32() !== 0x4d546864) return { durationMs: 0, notes: [] }; // 'MThd'\n const headerLen = u32();\n u16(); // format\n const ntrk = u16();\n const division = u16();\n p = 8 + headerLen;\n if (division & 0x8000) return { durationMs: 0, notes: [] }; // SMPTE — not handled\n const tpq = division || 480;\n\n const events: RawEvent[] = [];\n for (let t = 0; t < ntrk; t++) {\n if (p + 8 > dv.byteLength || u32() !== 0x4d54726b) break; // 'MTrk'\n const len = u32();\n const end = Math.min(p + len, dv.byteLength);\n let tick = 0;\n let running = 0;\n while (p < end) {\n let dt = 0, b: number;\n do { b = u8(); dt = (dt << 7) | (b & 0x7f); } while (b & 0x80 && p < end);\n tick += dt;\n let status = dv.getUint8(p);\n if (status & 0x80) { p++; running = status; } else { status = running; }\n if (status === 0xff) {\n const type = u8();\n let l = 0, bb: number;\n do { bb = u8(); l = (l << 7) | (bb & 0x7f); } while (bb & 0x80 && p < end);\n if (type === 0x51 && l === 3) {\n events.push({\n tick,\n kind: 'tempo',\n us: (dv.getUint8(p) << 16) | (dv.getUint8(p + 1) << 8) | dv.getUint8(p + 2),\n });\n }\n p += l;\n } else if (status === 0xf0 || status === 0xf7) {\n let l = 0, bb: number;\n do { bb = u8(); l = (l << 7) | (bb & 0x7f); } while (bb & 0x80 && p < end);\n p += l;\n } else {\n const hi = status & 0xf0;\n if (hi === 0x90 || hi === 0x80) {\n const midi = u8();\n const vel = u8();\n if (hi === 0x90 && vel > 0) events.push({ tick, kind: 'on', midi, velocity: vel / 127 });\n else events.push({ tick, kind: 'off', midi });\n } else if (hi === 0xb0) {\n const cc = u8();\n const val = u8();\n if (cc === 64) events.push({ tick, kind: 'sustain', on: val >= 64 });\n } else {\n p += hi === 0xc0 || hi === 0xd0 ? 1 : 2;\n }\n }\n }\n p = end;\n }\n\n // tick → ms via the tempo map\n const tempos = events.filter((e) => e.kind === 'tempo').sort((a, b) => a.tick - b.tick);\n if (!tempos.length || tempos[0].tick > 0) tempos.unshift({ tick: 0, kind: 'tempo', us: 500000 });\n const tickToMs = (tick: number): number => {\n let ms = 0;\n for (let i = 0; i < tempos.length; i++) {\n const segStart = tempos[i].tick;\n if (segStart >= tick) break;\n const segEnd = i + 1 < tempos.length ? Math.min(tempos[i + 1].tick, tick) : tick;\n ms += ((segEnd - segStart) / tpq) * ((tempos[i].us ?? 500000) / 1000);\n }\n return ms;\n };\n\n // Sustain spans (tick ranges where the pedal is down)\n const sustainEvents = events.filter((e) => e.kind === 'sustain').sort((a, b) => a.tick - b.tick);\n const pedalUpAfter = (tick: number): number | null => {\n for (const s of sustainEvents) if (!s.on && s.tick >= tick) return s.tick;\n return null;\n };\n const pedalDownAt = (tick: number): boolean => {\n let down = false;\n for (const s of sustainEvents) { if (s.tick > tick) break; down = !!s.on; }\n return down;\n };\n\n // Pair note-ons with the next matching note-off\n const ordered = events.filter((e) => e.kind === 'on' || e.kind === 'off').sort((a, b) => a.tick - b.tick);\n const open: Record<number, { tick: number; vel: number }[]> = {};\n const notes: MidiNote[] = [];\n let lastOffTick = 0;\n for (const e of ordered) {\n const m = e.midi!;\n if (e.kind === 'on') {\n (open[m] ??= []).push({ tick: e.tick, vel: e.velocity ?? 0.7 });\n } else {\n const stack = open[m];\n if (stack && stack.length) {\n const start = stack.shift()!;\n let endTick = e.tick;\n lastOffTick = Math.max(lastOffTick, endTick);\n // Extend while pedal is held past the note-off.\n if (pedalDownAt(endTick)) {\n const up = pedalUpAfter(endTick);\n if (up != null) endTick = up;\n }\n const startMs = tickToMs(start.tick);\n notes.push({\n midi: m,\n startMs,\n durMs: Math.max(60, tickToMs(endTick) - startMs),\n velocity: start.vel,\n });\n }\n }\n }\n\n return { durationMs: tickToMs(lastOffTick), notes };\n } catch {\n return { durationMs: 0, notes: [] };\n }\n}\n\n/** Total playback duration in ms (0 if unparseable). */\nexport function midiDurationMs(buf: ArrayBuffer): number {\n return parseMidi(buf).durationMs;\n}\n\n// ─── Notation renderer ────────────────────────────────────────────────────────\n// Superset of RSR (stave-web-sightread/src/lib/promo/notation.ts) and RMT\n// (realmusictheory/site/src/lib/promo/notation.ts). Both per-staff measure boxes\n// (RSR) and per-measure column union boxes (RMT) are computed every render.\n//\n// Browser-only: requires document + opensheetmusicdisplay. No unit tests here —\n// consumers' dom tests cover renderNotation.\n\nexport interface Box {\n x: number;\n y: number;\n w: number;\n h: number;\n}\n\n/** One staff's slice of a measure, in canvas px. */\nexport interface StaffMeasureBox {\n index: number; // 0-based measure position within the rendered range\n staff: number; // 0 = top staff (RH/treble), 1 = bottom staff (LH/bass)\n box: Box;\n /** x of the measure's FIRST note (canvas px) — past any clef/key/time signature. */\n noteStartX: number;\n}\n\n/** Per-measure column box: union across all staves for that measure, in canvas px. */\nexport interface MeasureColumnBox extends Box {\n noteStartX: number;\n}\n\nexport interface RenderedNotation {\n canvas: HTMLCanvasElement;\n /** Per-row grand-staff system boxes in canvas px, top-to-bottom. */\n systems: Box[];\n /** Per-(measure, staff) boxes — RSR's geometry. */\n measures: StaffMeasureBox[];\n /** Per-measure column union across staves — RMT's geometry. */\n measureColumns: MeasureColumnBox[];\n /** Tight bounding box of all notation in canvas px (page whitespace cropped). */\n content: Box;\n /**\n * Per-distinct-onset engraved column position (measureIndex + fraction across\n * the note region), in time (left→right) order — one entry per played onset,\n * rests excluded, chords/both-hands collapsed. The scroll-cursor anchors to\n * THESE (the notes' real engraved X) instead of a time-fraction, so the\n * playhead lands exactly on each notehead. Empty/absent when extraction fails\n * or the engraving predates this field (cursor falls back to time/ordinal).\n */\n noteCols?: number[];\n}\n\nexport interface RenderNotationOpts {\n /** OSMD drawFrom/drawUpToMeasureNumber. Applied only when provided. */\n bars?: [number, number];\n /** Background fill colour. Default '#faf7f0' (RSR paper). */\n paper?: string;\n /** Pixel-sum threshold below which a pixel is counted as ink.\n * Default 690 (RSR: paper #faf7f0 sum ≈ 737). RMT uses 620. */\n inkSumThreshold?: number;\n /** Host div width in CSS px. Default 560 (RSR). RMT uses 620. */\n hostWidth?: number;\n /**\n * Notation scroll mode — drives BOTH the engraving layout here and the\n * follow-camera in the scroll-cursor/notation layers.\n *\n * 'hstack' (default) — engrave all measures on ONE horizontal staffline\n * (OSMD RenderSingleHorizontalStaffline). The follow window is a purely\n * HORIZONTAL slice so the playhead pans left→right with no vertical\n * row-break jump. The rasterized bitmap is one very wide row; geometry\n * (systems/measures) all share one row of y.\n *\n * 'vstack' — engrave the classic STACKED systems (normal page wrap into\n * multiple rows). The follow camera frames the ACTIVE system at a fixed\n * band position and scrolls VERTICALLY (eased) to the next system as the\n * playhead crosses systems — never a vertical leap across staff lines.\n *\n * Default 'hstack' (preserves the current single-row behavior).\n */\n scrollMode?: 'hstack' | 'vstack';\n /**\n * @deprecated Use `scrollMode` instead. Back-compat alias: `singleRow:true`\n * ⇔ `scrollMode:'hstack'`, `singleRow:false` ⇔ `scrollMode:'vstack'`.\n * When both are given, `scrollMode` wins.\n */\n singleRow?: boolean;\n}\n\n/** Padded union of boxes, clamped to the canvas. */\nfunction unionBox(boxes: Box[], canvas: HTMLCanvasElement, pad: number): Box {\n if (!boxes.length) return { x: 0, y: 0, w: canvas.width, h: canvas.height };\n const minX = Math.min(...boxes.map((b) => b.x));\n const minY = Math.min(...boxes.map((b) => b.y));\n const maxX = Math.max(...boxes.map((b) => b.x + b.w));\n const maxY = Math.max(...boxes.map((b) => b.y + b.h));\n const x = Math.max(0, minX - pad);\n const y = Math.max(0, minY - pad);\n return {\n x,\n y,\n w: Math.min(canvas.width, maxX + pad) - x,\n h: Math.min(canvas.height, maxY + pad) - y,\n };\n}\n\n/** Tight box around all non-paper pixels (notes, ledger lines, stems), padded. */\nfunction inkBoundingBox(canvas: HTMLCanvasElement, inkSumThreshold: number): Box | null {\n try {\n const ctx = canvas.getContext('2d', { willReadFrequently: true });\n if (!ctx) return null;\n const { width: W, height: H } = canvas;\n const data = ctx.getImageData(0, 0, W, H).data;\n let minX = W, minY = H, maxX = -1, maxY = -1;\n const step = 2;\n for (let y = 0; y < H; y += step) {\n for (let x = 0; x < W; x += step) {\n const i = (y * W + x) * 4;\n if (data[i + 3] < 16) continue;\n if (data[i] + data[i + 1] + data[i + 2] < inkSumThreshold) {\n if (x < minX) minX = x;\n if (x > maxX) maxX = x;\n if (y < minY) minY = y;\n if (y > maxY) maxY = y;\n }\n }\n }\n if (maxX < 0) return null;\n const pad = 16;\n const x = Math.max(0, minX - pad);\n const y = Math.max(0, minY - pad);\n return {\n x,\n y,\n w: Math.min(W, maxX + pad) - x,\n h: Math.min(H, maxY + pad) - y,\n };\n } catch {\n return null;\n }\n}\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\nfunction extractGeometry(\n osmd: any,\n canvas: HTMLCanvasElement,\n inkSumThreshold: number,\n): { systems: Box[]; measures: StaffMeasureBox[]; measureColumns: MeasureColumnBox[]; content: Box; noteCols: number[] } {\n const full: Box = { x: 0, y: 0, w: canvas.width, h: canvas.height };\n try {\n const graphic: any = osmd.GraphicSheet;\n const page: any = graphic?.MusicPages?.[0];\n const pageW: number = page?.PositionAndShape?.Size?.width;\n const musicSystems: any[] = page?.MusicSystems ?? [];\n if (!pageW || !musicSystems.length) return { systems: [], measures: [], measureColumns: [], content: full, noteCols: [] };\n\n // OSMD units → canvas px.\n //\n // BUG FIX: the naive `canvas.width / pageW` is WRONG whenever the engraving\n // OVERFLOWS the nominal page width — which `renderSingleHorizontalStaffline`\n // (hstack) does routinely: one wide row of measures spills past pageW, and OSMD\n // sizes the canvas to the CONTENT (with its page margins), not to pageW. Using\n // canvas.width/pageW then OVER-scales every position (~5% on a 5-bar row), so\n // the cursor/highlight geometry drifts steadily RIGHT of the real noteheads and\n // DOWN in y (the same scale is applied to both axes) — accumulating to ~half a\n // bar by the end of the clip.\n //\n // OSMD renders at a single uniform scale `s` (px per OSMD unit) with symmetric\n // page margins, so `canvas.width = (contentRight + contentLeft) * s` where\n // contentLeft is the left margin. Hence `s = canvas.width / (contentRight +\n // contentLeft)`. This SELF-CALIBRATES to OSMD's zoom and, when the content fits\n // the page (vstack: contentRight ≈ pageW - margin, contentLeft = margin), it\n // reduces to canvas.width/pageW — so it's correct for both layouts. Falls back\n // to pageW if the content extent can't be measured.\n let contentRightU = -Infinity, contentLeftU = Infinity;\n for (const staves of (graphic?.MeasureList ?? []) as any[][]) {\n for (const m of (staves ?? [])) {\n const ps = m?.PositionAndShape;\n if (!ps?.AbsolutePosition || !ps?.Size || !(ps.Size.width > 0.1)) continue;\n contentRightU = Math.max(contentRightU, ps.AbsolutePosition.x + ps.Size.width);\n contentLeftU = Math.min(contentLeftU, ps.AbsolutePosition.x);\n }\n }\n const spanU = Number.isFinite(contentRightU) && Number.isFinite(contentLeftU)\n ? contentRightU + contentLeftU\n : pageW;\n const f = canvas.width / (spanU > 0 ? spanU : pageW);\n const toBox = (pas: any): Box | null => {\n const p = pas?.AbsolutePosition;\n const sz = pas?.Size;\n if (!p || !sz) return null;\n return { x: p.x * f, y: p.y * f, w: sz.width * f, h: sz.height * f };\n };\n\n const systems: Box[] = musicSystems\n .map((s) => toBox(s?.PositionAndShape))\n .filter((b): b is Box => !!b && b.w > 1 && b.h > 1)\n .sort((a, b) => a.y - b.y);\n if (!systems.length) return { systems: [], measures: [], measureColumns: [], content: full, noteCols: [] };\n\n const measureList: any[][] = graphic?.MeasureList ?? [];\n\n // RSR geometry: per-(measure, staff) individual boxes\n const measures: StaffMeasureBox[] = [];\n measureList.forEach((staves, index) => {\n (staves ?? []).forEach((m: any, staff: number) => {\n const box = toBox(m?.PositionAndShape);\n if (box && box.w > 1 && box.h > 1) {\n const seX = (m?.staffEntries ?? [])[0]?.PositionAndShape?.AbsolutePosition?.x;\n const noteStartX = typeof seX === 'number' ? seX * f : box.x;\n measures.push({ index, staff, box, noteStartX });\n }\n });\n });\n\n // RMT geometry: per-measure column boxes (union across staves)\n const measureColumns: MeasureColumnBox[] = measureList\n .map((staves) => {\n const arr = staves ?? [];\n const boxes = arr\n .map((m: any) => toBox(m?.PositionAndShape))\n .filter((b: Box | null): b is Box => !!b && b.w > 1 && b.h > 1);\n if (!boxes.length) return null;\n const x0 = Math.min(...boxes.map((b) => b.x));\n const y0 = Math.min(...boxes.map((b) => b.y));\n const x1 = Math.max(...boxes.map((b) => b.x + b.w));\n const y1 = Math.max(...boxes.map((b) => b.y + b.h));\n // First note x across all staves; clamped into the column box\n let nx = Infinity;\n for (const m of arr) {\n const seX = (m?.staffEntries ?? [])[0]?.PositionAndShape?.AbsolutePosition?.x;\n if (typeof seX === 'number') nx = Math.min(nx, seX * f);\n }\n const noteStartX = Number.isFinite(nx) ? Math.max(x0, Math.min(nx, x1)) : x0;\n return { x: x0, y: y0, w: x1 - x0, h: y1 - y0, noteStartX };\n })\n .filter((b): b is MeasureColumnBox => !!b);\n\n // Per-distinct-onset engraved columns, in render (= time) order, rests excluded\n // — the cursor's TRUE anchor X. Each value is `ordinal + frac`, where `ordinal`\n // is the column's position among the DRAWN measures (matching the array index of\n // measureColumnsFromLayout — NOT the absolute MeasureList index, which would\n // overflow the columns array for excerpts whose first measure isn't bar 0), and\n // `frac` is the notehead's position across the measure's note region. Crucially\n // we iterate ONLY drawn measures (a valid box): with drawFrom/drawUpTo, OSMD's\n // MeasureList still holds EVERY source measure, so without this filter the whole\n // score leaks in (e.g. 128 cols for a 22-note excerpt) and never pairs 1:1 with\n // the played onsets, disabling the engraved-x anchor.\n // We key each note's engraved X by its rhythmic TIMESTAMP within the measure,\n // NOT by raw distinct X. Two staves' notes at the SAME beat engrave at slightly\n // different X (the grand staff isn't pixel-aligned), so deduping by X yielded\n // MORE columns than there are onsets (e.g. 45 cols vs 40 onsets) — which broke\n // the 1:1 pairing with the played onsets and forced the cursor onto a\n // linear-time fallback that drifts ~½ bar ahead of the real noteheads on dense\n // bars. Keying by timestamp collapses each beat to ONE column at the (leftmost)\n // engraved X of its noteheads, so the column count equals the distinct-onset\n // count and the cursor lands on the actual notehead, beat for beat.\n const noteCols: number[] = [];\n let ordinal = 0;\n measureList.forEach((staves) => {\n const arr = staves ?? [];\n let mx0 = Infinity, mx1 = -Infinity, nsx = Infinity;\n const byTs = new Map<number, number>(); // rhythmic timestamp → leftmost note X (px)\n for (const m of arr) {\n const mb = toBox(m?.PositionAndShape);\n if (mb) { mx0 = Math.min(mx0, mb.x); mx1 = Math.max(mx1, mb.x + mb.w); }\n const se0 = (m?.staffEntries ?? [])[0]?.PositionAndShape?.AbsolutePosition?.x;\n if (typeof se0 === 'number') nsx = Math.min(nsx, se0 * f);\n for (const se of (m?.staffEntries ?? [])) {\n const sx = se?.PositionAndShape?.AbsolutePosition?.x;\n if (typeof sx !== 'number') continue;\n const hasNote = (se?.graphicalVoiceEntries ?? []).some(\n (gve: any) => (gve?.notes ?? []).some(\n (gn: any) => !(gn?.sourceNote?.isRestFlag ?? gn?.sourceNote?.IsRest ?? false)));\n if (!hasNote) continue;\n // Within-measure rhythmic position (whole notes). Round to a fine grid so\n // float noise doesn't split a beat; falls back to X if unavailable.\n const tsRaw =\n se?.relInMeasureTimestamp?.RealValue ??\n se?.getAbsoluteTimestamp?.()?.RealValue ??\n se?.sourceStaffEntry?.Timestamp?.RealValue;\n const key = typeof tsRaw === 'number' ? Math.round(tsRaw * 10000) : Math.round(sx * f);\n const px = sx * f;\n const prev = byTs.get(key);\n if (prev === undefined || px < prev) byTs.set(key, px);\n }\n }\n // Skip undrawn measures (no valid box / zero width) — only drawn measures get\n // a column, so `ordinal` stays in lockstep with measureColumnsFromLayout.\n if (!Number.isFinite(mx0) || (mx1 - mx0) <= 1) return;\n const startX = Number.isFinite(nsx) ? Math.max(mx0, Math.min(nsx, mx1)) : mx0;\n const denom = (mx1 - startX) || 1;\n // Emit one column per distinct beat, in time order (the byTs keys are the\n // rounded timestamps), so column k ↔ onset k.\n for (const key of [...byTs.keys()].sort((a, b) => a - b)) {\n const x = byTs.get(key)!;\n noteCols.push(ordinal + Math.min(1, Math.max(0, (x - startX) / denom)));\n }\n ordinal++;\n });\n\n const content = inkBoundingBox(canvas, inkSumThreshold) ?? unionBox(systems, canvas, 14);\n return { systems, measures, measureColumns, content, noteCols };\n } catch {\n return { systems: [], measures: [], measureColumns: [], content: full, noteCols: [] };\n }\n}\n/* eslint-enable @typescript-eslint/no-explicit-any */\n\n/** Render a MusicXML string to a detached canvas + geometry.\n * Browser-only (requires document + opensheetmusicdisplay dynamic import). */\nexport async function renderNotation(\n xml: string,\n opts?: RenderNotationOpts,\n): Promise<RenderedNotation> {\n const paper = opts?.paper ?? '#faf7f0';\n const inkSumThreshold = opts?.inkSumThreshold ?? 690;\n const hostWidth = opts?.hostWidth ?? 560;\n // scrollMode drives the engraving layout; singleRow is the deprecated alias.\n // Default 'hstack' (single horizontal staffline) preserves current behavior.\n const scrollMode: 'hstack' | 'vstack' =\n opts?.scrollMode ?? (opts?.singleRow === false ? 'vstack' : 'hstack');\n const singleRow = scrollMode === 'hstack';\n\n // Literal dynamic import: consumers' bundlers (Vite/Rollup) must be able to\n // statically see the specifier to resolve + code-split it — a variable\n // specifier would reach the browser as a bare import and fail at runtime.\n const { OpenSheetMusicDisplay } = await import('opensheetmusicdisplay');\n\n const host = document.createElement('div');\n host.style.cssText = `position:fixed;left:-9999px;top:0;width:${hostWidth}px;background:${paper};`;\n document.body.appendChild(host);\n\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const osmd: any = new OpenSheetMusicDisplay(host, {\n backend: 'canvas',\n autoResize: false,\n drawTitle: false,\n drawSubtitle: false,\n drawComposer: false,\n drawLyricist: false,\n drawPartNames: false,\n });\n\n await osmd.load(xml);\n\n // hstack: engrave on a single horizontal staffline so the follow window pans\n // purely HORIZONTALLY (no stacked-system row breaks → no vertical playhead\n // jump). vstack: leave OSMD's default page wrap → classic stacked systems\n // (the follow camera then scrolls vertically between systems). Must be set\n // before render(); when on, the rasterized canvas becomes one wide row.\n if (singleRow) {\n osmd.setOptions({ renderSingleHorizontalStaffline: true } as never);\n }\n\n // Apply bar range only when provided (RSR's drawFrom/drawUpTo).\n if (opts?.bars) {\n osmd.setOptions({\n drawFromMeasureNumber: opts.bars[0],\n drawUpToMeasureNumber: opts.bars[1],\n } as never);\n }\n\n osmd.render();\n\n const canvas = host.querySelector('canvas');\n if (canvas) {\n const { systems, measures, measureColumns, content, noteCols } = extractGeometry(osmd, canvas, inkSumThreshold);\n document.body.removeChild(host);\n return { canvas, systems, measures, measureColumns, content, noteCols };\n }\n\n // SVG fallback: rasterize into a canvas so callers always get a canvas.\n const svg = host.querySelector('svg');\n if (svg) {\n const w = svg.clientWidth || hostWidth;\n const h = svg.clientHeight || 300;\n const svgStr = new XMLSerializer().serializeToString(svg);\n const dataUrl = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svgStr)));\n const img = new Image();\n await new Promise<void>((resolve, reject) => {\n img.onload = () => resolve();\n img.onerror = () => reject(new Error('svg rasterize failed'));\n img.src = dataUrl;\n });\n const out = document.createElement('canvas');\n out.width = w;\n out.height = h;\n const c2d = out.getContext('2d');\n if (!c2d) throw new Error('2d context unavailable');\n c2d.fillStyle = paper;\n c2d.fillRect(0, 0, w, h);\n c2d.drawImage(img, 0, 0, w, h);\n document.body.removeChild(host);\n return {\n canvas: out,\n systems: [],\n measures: [],\n measureColumns: [],\n content: { x: 0, y: 0, w, h },\n noteCols: [],\n };\n }\n\n document.body.removeChild(host);\n throw new Error('OSMD produced neither canvas nor SVG');\n } catch (e) {\n if (host.parentNode) document.body.removeChild(host);\n throw e;\n }\n}\n\n// ─── Promo sampler ────────────────────────────────────────────────────────────\n// The shared Tone.js setup prefix under both apps' createPromoAudio.\n// Scheduling (playEvents / playMidi / playWindowSolo / playForeground) stays\n// in each app.\n//\n// Browser-only: requires Tone.js dynamic import. No unit tests here —\n// consumers' dom tests cover createPromoSampler.\n\nimport { createSalamanderSampler, generateReverb } from './audioHelpers';\nimport { SALAMANDER_CDN_BASE, SALAMANDER_URLS_8, SALAMANDER_URLS_FULL } from './salamander';\n\nexport interface PromoSampler {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n Tone: any; // typeof Tone — typed loose like audioHelpers.ts does for Tone\n sampler: unknown; // Tone.Sampler; typed loose like audioHelpers\n getStream(): MediaStream;\n /** Master-bus analyser (post-chain, sink-only) for reactive visualizers, or\n * null if `analyser` wasn't requested. whozart's \"listen\" spectrum reads it. */\n getAnalyser(): AnalyserNode | null;\n /** Current audio-clock time in seconds (Tone.now()). */\n audioNow(): number;\n /** releaseAll on the sampler; safe no-op on failure. */\n stop(): void;\n}\n\n/** Per-app tone shaping for the shared promo mastering bus. Ported from\n * whozart's audio-impl chain (the richest of the fleet); `reading` is a drier,\n * closer voicing so sight-reading notation stays legible note-to-note. Apps\n * pick one via `createPromoSampler({ voicing })`. */\nexport type PromoVoicing = 'concertHall' | 'reading' | 'dry';\n\ninterface VoicingPreset {\n release: number;\n attack: number;\n eq: { low: number; mid: number; high: number; lowFrequency: number; highFrequency: number };\n comp: { threshold: number; ratio: number; attack: number; release: number; knee: number };\n widen: number;\n reverb: { decay: number; preDelay: number; wet: number };\n}\n\nconst PROMO_VOICINGS: Record<PromoVoicing, VoicingPreset> = {\n // whozart's hall sound: long ringing release, lifted lows/air, gentle glue\n // compression, wide stereo, a real-hall reverb tail.\n concertHall: {\n release: 1.8, attack: 0.005,\n eq: { low: 1.5, mid: -1, high: 1.5, lowFrequency: 280, highFrequency: 4500 },\n comp: { threshold: -18, ratio: 1.6, attack: 0.012, release: 0.3, knee: 18 },\n widen: 0.30,\n reverb: { decay: 3.5, preDelay: 0.04, wet: 0.22 },\n },\n // Sight-reading: shorter release + much drier/shorter reverb so consecutive\n // notes don't smear into each other while the eye tracks the staff. Small\n // presence bump in the mids for note-attack clarity, narrower stereo.\n reading: {\n release: 1.1, attack: 0.004,\n eq: { low: 0.5, mid: 1, high: 1, lowFrequency: 280, highFrequency: 5000 },\n comp: { threshold: -16, ratio: 2.0, attack: 0.008, release: 0.25, knee: 12 },\n widen: 0.18,\n reverb: { decay: 1.4, preDelay: 0.02, wet: 0.10 },\n },\n // Bone-dry — no reverb at all (e.g. a debugging/reference voicing).\n dry: {\n release: 1.0, attack: 0.004,\n eq: { low: 0, mid: 0, high: 0, lowFrequency: 280, highFrequency: 4500 },\n comp: { threshold: -14, ratio: 2.0, attack: 0.008, release: 0.25, knee: 10 },\n widen: 0,\n reverb: { decay: 1.0, preDelay: 0, wet: 0 },\n },\n};\n\nexport async function createPromoSampler(opts?: {\n /** Feed a 0-value ConstantSource into the capture stream so the recorder's\n * audio track is live from t=0 (silent intro scenes aren't dropped).\n * Default false (RSR behavior). RMT passes true. */\n keepAlive?: boolean;\n /** Override the voicing's sampler release time (seconds). */\n release?: number;\n /** Sampler volume in dB. Default -2. */\n volumeDb?: number;\n /** Per-app tone shaping (see PromoVoicing). Default 'concertHall'. */\n voicing?: PromoVoicing;\n /** Use the full A0..C8 Salamander anchor set instead of the 8-anchor set —\n * fuller tone, more samples to fetch. Default false (8-anchor). */\n fullSamples?: boolean;\n /** Expose a master-bus analyser (for reactive visualizers). Default false. */\n analyser?: boolean;\n}): Promise<PromoSampler> {\n const voicing = PROMO_VOICINGS[opts?.voicing ?? 'concertHall'];\n const release = opts?.release ?? voicing.release;\n const volumeDb = opts?.volumeDb ?? -2;\n const keepAlive = opts?.keepAlive ?? false;\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const Tone = (await import('tone')) as any;\n await Tone.start();\n\n // Unrouted sampler — we build the mastering bus off it (don't send the dry\n // sampler to the speakers, which would bypass the chain).\n const sampler = createSalamanderSampler(Tone, {\n urls: opts?.fullSamples ? SALAMANDER_URLS_FULL : SALAMANDER_URLS_8,\n baseUrl: SALAMANDER_CDN_BASE,\n release,\n attack: voicing.attack,\n volumeDb,\n connectToDestination: false,\n });\n\n // Mastering bus (ported from whozart): EQ tilt → glue compressor → stereo\n // widener → reverb → brick-wall limiter. Reverb is generated with a timeout\n // fallback so a slow/again-failing IR can never hang the render; on failure\n // the chain simply omits reverb.\n const eq = new Tone.EQ3(voicing.eq);\n const compressor = new Tone.Compressor(voicing.comp);\n const widener = new Tone.StereoWidener(voicing.widen);\n const limiter = new Tone.Limiter(-1.5);\n\n const reverb =\n voicing.reverb.wet > 0\n ? await generateReverb(\n Tone,\n { decay: voicing.reverb.decay, wet: voicing.reverb.wet },\n 4000,\n )\n : null;\n if (reverb) reverb.preDelay = voicing.reverb.preDelay;\n\n // chain() wires node→node in order; tail node feeds the taps below.\n const chainNodes = reverb ? [eq, compressor, widener, reverb, limiter] : [eq, compressor, widener, limiter];\n sampler.chain(...chainNodes);\n\n const ctx = Tone.getContext().rawContext as AudioContext;\n const mediaDest = ctx.createMediaStreamDestination();\n limiter.connect(mediaDest);\n // Also monitor through the speakers so a non-headless preview is audible.\n limiter.connect(Tone.getDestination());\n\n // Optional master-bus analyser (sink-only — no onward connection so it can't\n // double the signal). Drives whozart's reactive \"listen\" visualizer.\n let analyserNode: AnalyserNode | null = null;\n if (opts?.analyser) {\n analyserNode = ctx.createAnalyser();\n analyserNode.fftSize = 2048;\n analyserNode.smoothingTimeConstant = 0.8;\n limiter.connect(analyserNode);\n }\n\n // Optional: keep the audio track alive from t=0 so the recorder doesn't drop\n // silent intro scenes. RMT uses this; RSR does not.\n if (keepAlive) {\n const source = ctx.createConstantSource();\n source.offset.value = 0;\n source.connect(mediaDest);\n source.start();\n }\n\n await Tone.loaded();\n\n return {\n Tone,\n sampler,\n getStream(): MediaStream {\n return mediaDest.stream;\n },\n getAnalyser(): AnalyserNode | null {\n return analyserNode;\n },\n audioNow(): number {\n return Tone.now();\n },\n stop(): void {\n try {\n (sampler as unknown as { releaseAll?: () => void }).releaseAll?.();\n } catch {\n /* no-op */\n }\n },\n };\n}\n"],"mappings":"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","names":["y","x"]}
|
package/dist/scene/index.d.ts
CHANGED
|
@@ -862,7 +862,7 @@ declare function distinctOnsets(notes: {
|
|
|
862
862
|
* (first→last onset), not on musicMs — so the fade tracks the notes too.
|
|
863
863
|
*/
|
|
864
864
|
declare function audioPlayheadLine(layout: NotationLayout, onsetsMs: number[], tMs: number, barDurMs?: number, noteCols?: number[]): PlayheadLine | null;
|
|
865
|
-
declare function vstackAudioPlayheadLine(layout: NotationLayout, onsetsMs: number[], tMs: number, nBars: number): PlayheadLine | null;
|
|
865
|
+
declare function vstackAudioPlayheadLine(layout: NotationLayout, onsetsMs: number[], tMs: number, nBars: number, noteCols?: number[]): PlayheadLine | null;
|
|
866
866
|
|
|
867
867
|
/** Map a canvas-space box through a base notation layout into world/screen coords. */
|
|
868
868
|
declare function mapBoxThroughLayout(base: NotationLayout, b: Box): Box;
|
|
@@ -1017,6 +1017,23 @@ interface KeyboardProps {
|
|
|
1017
1017
|
}
|
|
1018
1018
|
declare const keyboardFactory: LayerFactory<KeyboardProps>;
|
|
1019
1019
|
|
|
1020
|
+
interface FretboardProps {
|
|
1021
|
+
/** Highest fret drawn. Default: auto-fit the excerpt (min 5, max 15), padded. */
|
|
1022
|
+
visibleFrets?: number;
|
|
1023
|
+
/** Board height in world px. Default 220. */
|
|
1024
|
+
height?: number;
|
|
1025
|
+
/** Board bottom edge in world px. Default safeBox.bottom. */
|
|
1026
|
+
bottomY?: number;
|
|
1027
|
+
/** Right/left hand dot colours. Defaults R=theme.accent, L=theme.gold. */
|
|
1028
|
+
handColors?: {
|
|
1029
|
+
R: string;
|
|
1030
|
+
L: string;
|
|
1031
|
+
};
|
|
1032
|
+
/** Draw note-name labels inside lit dots. Default false. */
|
|
1033
|
+
showLabels?: boolean;
|
|
1034
|
+
}
|
|
1035
|
+
declare const fretboardFactory: LayerFactory<FretboardProps>;
|
|
1036
|
+
|
|
1020
1037
|
interface FallingNotesProps {
|
|
1021
1038
|
/** Pair with a `keyboard` layer (read its layout + hit-line). When false the
|
|
1022
1039
|
* layer is standalone and builds its own layout from the props below.
|
|
@@ -1971,4 +1988,4 @@ interface EndCardProps {
|
|
|
1971
1988
|
}
|
|
1972
1989
|
declare const endCardFactory: LayerFactory<EndCardProps>;
|
|
1973
1990
|
|
|
1974
|
-
export { type Affine, type AudioClock, type AudioEvent, type BackgroundProps, type BeatGridOpts, type BeatPulseProps, type BeatTick, type BrandingProps, type BufferFactory, type BuildSceneOpts, type BuiltScene, type CameraState, type CaptionCue, type CaptionProps, type CaptionScript, type CaptionStyle, type ChordRibbonProps, type ChordSpan, type CircleOfFifthsProps, type CirclePoint, type ClickTrackOpts, type ColorBy, type ContourPlot, type ContourPoint, type CountInOpts, type CountdownProps, type CountingTrackProps, type CtaProps, DEFAULT_FUNCTION_COLORS, type DegreeLabel, type DegreeLabelsProps, type DroneOpts, type DuckWindow, type Easing, type EndCardProps, type ExtendedDemoOpts, FIFTHS_MAJOR, FIFTHS_MINOR, FOLLOW_BARS, FOLLOW_PAD, type FallingKeyboardDemoOpts, type FallingNotesProps, type FunctionColors, type FunctionalHarmonyProps, type GateError, type GateInput, type HandColors, type HarmonicFunction, type HarmonyMode, type HighlightRegion, type HookProps, type IntervalArcsProps, type KaraokeCaptionProps, type KaraokeWord, type KeyRect, type KeyboardLayout, type KeyboardLayoutOpts, type KeyboardProps, type KineticMode, type KineticTextProps, type LabelMode, type Layer, type LayerFactory, type McqCardProps, type NotationEngraving, type NotationLayout, type NotationLayoutOpts, type NotationProps, type NotationRect, type OutputProbe, PIANO_HIGH, PIANO_LOW, type ParticleBurstProps, type PitchContourProps, type Placement, type PlayheadLine, type PortraitProps, type ProgressRingProps, type PromoCardsDemoOpts, type PulseStyle, type Quiz, type QuizOption, type QuizPhase, type RadialSpectrumProps, type RayEndpoints, type RecordSceneSpecOpts, type Rect, type RenderCtx, type ResolvedSegment, type RevealProps, SCALE_INTERVALS, type SafeGuidesProps, type ScaleHighlightProps, type SceneSpec, type ScheduleTarget, type Score, type ScoreFromMusicXMLOpts, type ScoreNote, type ScrollCursorProps, type Section, type SectionMinimapProps, type SegmentAudio, type SegmentAudioCtx, type SegmentTransition, type SpecLayer, type SpectrumInput, type SpectrumProps, type StaffKeyboardRayProps, type StatCounterProps, type TempoMap, type TensionGraphProps, type TensionPoint, type TextureProps, type TimeAnchor, type TimelineSegment, type WaveformInput, type WaveformProps, activeChord, activeCue, activeSection, animatedSlot, applySchedule, applyToContext, assertGate, audioPlayheadLine, ballArc, ballX, beatGrid, beatPhase, beatPulseFactory, blackKeys, bpmOf, brandingFactory, buildScene, cameraForFollow, cameraTransform, chordRibbonFactory, circleOfFifthsDemoSpec, circleOfFifthsFactory, clamp, clickTrackSchedule, contourMinimapDemoSpec, contourPoints, contourPolyline, countInLeadSec, countInSchedule, countdownFactory, countdownRemaining, countdownSeconds, countingDegreeDemoSpec, countingTrackFactory, cropAroundBox, ctaFactory, cubicEaseInOut, cueOpacity, degreeLabel, degreeLabelsFactory, distinctMeasureIndices, distinctOnsets, dotAt, drawCaption, drawHighlight, droneSchedule, duckGainAt, easeIn, easeInOut, easeOut, endCardFactory, fallingKeyboardDemoScore, fallingKeyboardDemoSpec, fallingNotesFactory, firstMeasureBox, followBoxAt, followSrcBox, followWindowStart, fracSlotPoint, frameRect, functionColor, functionalHarmonyFactory, getKeyboardLayout, getLayerFactory, getNotationEngraving, harmonyDemoSpec, highlightIntensity, hookFactory, identityCamera, inRange, intervalArcsFactory, invLerp, isBlackKey, karaokeCaptionFactory, kenBurns, keyCenterX, keyColumnWidth, keyRect, keySlot, keyboardFactory, keyboardLayout, kineticTextFactory, lerp, lerpBox, lerpCamera, linear, mapBoxThroughLayout, mcqCardFactory, mcqDemoSpec, measureColumnsFromLayout, measureCount, measureSpanBox, measureSpans, measureSystemMap, timeToX as minimapTimeToX, msPerBeat, notationFactory, notationLayout, noteColor, noteSetXRange, parseKey, parseTimeSig, particleBurstFactory, pcToSlot, pitchAt, pitchContourFactory, pitchRange, playheadLine, portraitFactory, progress01, progressRingFactory, projectPoint, promoCardsDemoSpec, pulseAt, quizPhase, radialSpectrumFactory, rayEndpoints, rayPointAt, recordSceneSpec, registerLayer, registeredKeys, resolveAnchor, resolveKeyboardLayout, resolveTimeline, revealFactory, revealProgress, runGate, safeGuidesFactory, scaleHighlightFactory, scoreFromMusicXML, scorePitchSpan, scrollCursorFactory, sectionMinimapFactory, setFollowLayoutProvider, setKeyboardLayout, setNotationEngraving, slotAngle, slotPc, slotPoint, spectrumFactory, staffAnchor, staffKeyboardRayFactory, staffRayDemoSpec, statCounterFactory, systemBox, tensionGraphFactory, textureFactory, transitionProgress, validateChordTrack, validateQuiz, validateSections, visualTimelineMs, vstackAudioPlayheadLine, vstackFollowBox, waveformFactory, whiteKeys, worldToViewport };
|
|
1991
|
+
export { type Affine, type AudioClock, type AudioEvent, type BackgroundProps, type BeatGridOpts, type BeatPulseProps, type BeatTick, type BrandingProps, type BufferFactory, type BuildSceneOpts, type BuiltScene, type CameraState, type CaptionCue, type CaptionProps, type CaptionScript, type CaptionStyle, type ChordRibbonProps, type ChordSpan, type CircleOfFifthsProps, type CirclePoint, type ClickTrackOpts, type ColorBy, type ContourPlot, type ContourPoint, type CountInOpts, type CountdownProps, type CountingTrackProps, type CtaProps, DEFAULT_FUNCTION_COLORS, type DegreeLabel, type DegreeLabelsProps, type DroneOpts, type DuckWindow, type Easing, type EndCardProps, type ExtendedDemoOpts, FIFTHS_MAJOR, FIFTHS_MINOR, FOLLOW_BARS, FOLLOW_PAD, type FallingKeyboardDemoOpts, type FallingNotesProps, type FretboardProps, type FunctionColors, type FunctionalHarmonyProps, type GateError, type GateInput, type HandColors, type HarmonicFunction, type HarmonyMode, type HighlightRegion, type HookProps, type IntervalArcsProps, type KaraokeCaptionProps, type KaraokeWord, type KeyRect, type KeyboardLayout, type KeyboardLayoutOpts, type KeyboardProps, type KineticMode, type KineticTextProps, type LabelMode, type Layer, type LayerFactory, type McqCardProps, type NotationEngraving, type NotationLayout, type NotationLayoutOpts, type NotationProps, type NotationRect, type OutputProbe, PIANO_HIGH, PIANO_LOW, type ParticleBurstProps, type PitchContourProps, type Placement, type PlayheadLine, type PortraitProps, type ProgressRingProps, type PromoCardsDemoOpts, type PulseStyle, type Quiz, type QuizOption, type QuizPhase, type RadialSpectrumProps, type RayEndpoints, type RecordSceneSpecOpts, type Rect, type RenderCtx, type ResolvedSegment, type RevealProps, SCALE_INTERVALS, type SafeGuidesProps, type ScaleHighlightProps, type SceneSpec, type ScheduleTarget, type Score, type ScoreFromMusicXMLOpts, type ScoreNote, type ScrollCursorProps, type Section, type SectionMinimapProps, type SegmentAudio, type SegmentAudioCtx, type SegmentTransition, type SpecLayer, type SpectrumInput, type SpectrumProps, type StaffKeyboardRayProps, type StatCounterProps, type TempoMap, type TensionGraphProps, type TensionPoint, type TextureProps, type TimeAnchor, type TimelineSegment, type WaveformInput, type WaveformProps, activeChord, activeCue, activeSection, animatedSlot, applySchedule, applyToContext, assertGate, audioPlayheadLine, ballArc, ballX, beatGrid, beatPhase, beatPulseFactory, blackKeys, bpmOf, brandingFactory, buildScene, cameraForFollow, cameraTransform, chordRibbonFactory, circleOfFifthsDemoSpec, circleOfFifthsFactory, clamp, clickTrackSchedule, contourMinimapDemoSpec, contourPoints, contourPolyline, countInLeadSec, countInSchedule, countdownFactory, countdownRemaining, countdownSeconds, countingDegreeDemoSpec, countingTrackFactory, cropAroundBox, ctaFactory, cubicEaseInOut, cueOpacity, degreeLabel, degreeLabelsFactory, distinctMeasureIndices, distinctOnsets, dotAt, drawCaption, drawHighlight, droneSchedule, duckGainAt, easeIn, easeInOut, easeOut, endCardFactory, fallingKeyboardDemoScore, fallingKeyboardDemoSpec, fallingNotesFactory, firstMeasureBox, followBoxAt, followSrcBox, followWindowStart, fracSlotPoint, frameRect, fretboardFactory, functionColor, functionalHarmonyFactory, getKeyboardLayout, getLayerFactory, getNotationEngraving, harmonyDemoSpec, highlightIntensity, hookFactory, identityCamera, inRange, intervalArcsFactory, invLerp, isBlackKey, karaokeCaptionFactory, kenBurns, keyCenterX, keyColumnWidth, keyRect, keySlot, keyboardFactory, keyboardLayout, kineticTextFactory, lerp, lerpBox, lerpCamera, linear, mapBoxThroughLayout, mcqCardFactory, mcqDemoSpec, measureColumnsFromLayout, measureCount, measureSpanBox, measureSpans, measureSystemMap, timeToX as minimapTimeToX, msPerBeat, notationFactory, notationLayout, noteColor, noteSetXRange, parseKey, parseTimeSig, particleBurstFactory, pcToSlot, pitchAt, pitchContourFactory, pitchRange, playheadLine, portraitFactory, progress01, progressRingFactory, projectPoint, promoCardsDemoSpec, pulseAt, quizPhase, radialSpectrumFactory, rayEndpoints, rayPointAt, recordSceneSpec, registerLayer, registeredKeys, resolveAnchor, resolveKeyboardLayout, resolveTimeline, revealFactory, revealProgress, runGate, safeGuidesFactory, scaleHighlightFactory, scoreFromMusicXML, scorePitchSpan, scrollCursorFactory, sectionMinimapFactory, setFollowLayoutProvider, setKeyboardLayout, setNotationEngraving, slotAngle, slotPc, slotPoint, spectrumFactory, staffAnchor, staffKeyboardRayFactory, staffRayDemoSpec, statCounterFactory, systemBox, tensionGraphFactory, textureFactory, transitionProgress, validateChordTrack, validateQuiz, validateSections, visualTimelineMs, vstackAudioPlayheadLine, vstackFollowBox, waveformFactory, whiteKeys, worldToViewport };
|