@real-music-packages/web-core 0.30.0 → 0.32.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/scene/index.d.ts +39 -1
- package/dist/scene/index.js +330 -1
- 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/scene/index.d.ts
CHANGED
|
@@ -1017,6 +1017,24 @@ 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 360 — 6 strings need more vertical room
|
|
1024
|
+
* than the piano strip (220 cramped them to ~37px each). */
|
|
1025
|
+
height?: number;
|
|
1026
|
+
/** Board bottom edge in world px. Default safeBox.bottom. */
|
|
1027
|
+
bottomY?: number;
|
|
1028
|
+
/** Right/left hand dot colours. Defaults R=theme.accent, L=theme.gold. */
|
|
1029
|
+
handColors?: {
|
|
1030
|
+
R: string;
|
|
1031
|
+
L: string;
|
|
1032
|
+
};
|
|
1033
|
+
/** Draw note-name labels inside lit dots. Default false. */
|
|
1034
|
+
showLabels?: boolean;
|
|
1035
|
+
}
|
|
1036
|
+
declare const fretboardFactory: LayerFactory<FretboardProps>;
|
|
1037
|
+
|
|
1020
1038
|
interface FallingNotesProps {
|
|
1021
1039
|
/** Pair with a `keyboard` layer (read its layout + hit-line). When false the
|
|
1022
1040
|
* layer is standalone and builds its own layout from the props below.
|
|
@@ -1971,4 +1989,24 @@ interface EndCardProps {
|
|
|
1971
1989
|
}
|
|
1972
1990
|
declare const endCardFactory: LayerFactory<EndCardProps>;
|
|
1973
1991
|
|
|
1974
|
-
|
|
1992
|
+
type ImageRevealMode = 'pixelate' | 'shuffle' | 'blur';
|
|
1993
|
+
interface ImageRevealProps {
|
|
1994
|
+
/** The image to reveal. Any CanvasImageSource (HTMLImageElement, OffscreenCanvas, …). */
|
|
1995
|
+
image: CanvasImageSource;
|
|
1996
|
+
/** Reveal mode. Default 'pixelate'. */
|
|
1997
|
+
mode?: ImageRevealMode;
|
|
1998
|
+
/** When the reveal starts (ms). Default 0. */
|
|
1999
|
+
startMs?: number;
|
|
2000
|
+
/** How long the full reveal takes (ms). Default 6000. */
|
|
2001
|
+
durationMs?: number;
|
|
2002
|
+
/**
|
|
2003
|
+
* Difficulty scalar 0..1.
|
|
2004
|
+
* - pixelate: controls maximum block size (0→small, 1→large; default 0.5 ≈ 1/8 width).
|
|
2005
|
+
* - shuffle: controls maximum tile offset fraction (default 0.5).
|
|
2006
|
+
* - blur: controls maximum blur radius in px (0→mild, 1→extreme; default 0.5 ≈ 40px).
|
|
2007
|
+
*/
|
|
2008
|
+
difficulty?: number;
|
|
2009
|
+
}
|
|
2010
|
+
declare const imageRevealFactory: LayerFactory<ImageRevealProps>;
|
|
2011
|
+
|
|
2012
|
+
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 ImageRevealMode, type ImageRevealProps, 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, imageRevealFactory, 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 };
|