@sudobility/music_types 0.11.19 → 0.11.21
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/domain/commands/edit-commands.d.ts +66 -0
- package/dist/domain/commands/edit-commands.d.ts.map +1 -0
- package/dist/domain/commands/edit-commands.js +185 -0
- package/dist/domain/commands/edit-commands.js.map +1 -0
- package/dist/domain/commands/note-commands.d.ts +73 -0
- package/dist/domain/commands/note-commands.d.ts.map +1 -0
- package/dist/domain/commands/note-commands.js +292 -0
- package/dist/domain/commands/note-commands.js.map +1 -0
- package/dist/domain/commands/reflow.d.ts +69 -0
- package/dist/domain/commands/reflow.d.ts.map +1 -0
- package/dist/domain/commands/reflow.js +395 -0
- package/dist/domain/commands/reflow.js.map +1 -0
- package/dist/domain/commands/region-commands.d.ts +21 -0
- package/dist/domain/commands/region-commands.d.ts.map +1 -0
- package/dist/domain/commands/region-commands.js +47 -0
- package/dist/domain/commands/region-commands.js.map +1 -0
- package/dist/domain/commands/relocate-commands.d.ts +18 -0
- package/dist/domain/commands/relocate-commands.d.ts.map +1 -0
- package/dist/domain/commands/relocate-commands.js +108 -0
- package/dist/domain/commands/relocate-commands.js.map +1 -0
- package/dist/domain/commands/ripple-commands.d.ts +44 -0
- package/dist/domain/commands/ripple-commands.d.ts.map +1 -0
- package/dist/domain/commands/ripple-commands.js +104 -0
- package/dist/domain/commands/ripple-commands.js.map +1 -0
- package/dist/domain/commands/snapshot.d.ts +38 -0
- package/dist/domain/commands/snapshot.d.ts.map +1 -0
- package/dist/domain/commands/snapshot.js +44 -0
- package/dist/domain/commands/snapshot.js.map +1 -0
- package/dist/domain/commands/structure-commands.d.ts +38 -0
- package/dist/domain/commands/structure-commands.d.ts.map +1 -0
- package/dist/domain/commands/structure-commands.js +220 -0
- package/dist/domain/commands/structure-commands.js.map +1 -0
- package/dist/domain/commands/track-commands.d.ts +15 -0
- package/dist/domain/commands/track-commands.d.ts.map +1 -0
- package/dist/domain/commands/track-commands.js +56 -0
- package/dist/domain/commands/track-commands.js.map +1 -0
- package/dist/domain/commands/types.d.ts +31 -0
- package/dist/domain/commands/types.d.ts.map +1 -0
- package/dist/domain/commands/types.js +2 -0
- package/dist/domain/commands/types.js.map +1 -0
- package/dist/domain/instruments/gm-kit.d.ts +45 -0
- package/dist/domain/instruments/gm-kit.d.ts.map +1 -0
- package/dist/domain/instruments/gm-kit.js +59 -0
- package/dist/domain/instruments/gm-kit.js.map +1 -0
- package/dist/domain/instruments/gm-percussion.d.ts +42 -0
- package/dist/domain/instruments/gm-percussion.d.ts.map +1 -0
- package/dist/domain/instruments/gm-percussion.js +85 -0
- package/dist/domain/instruments/gm-percussion.js.map +1 -0
- package/dist/domain/instruments/gm-polyphony.d.ts +13 -0
- package/dist/domain/instruments/gm-polyphony.d.ts.map +1 -0
- package/dist/domain/instruments/gm-polyphony.js +76 -0
- package/dist/domain/instruments/gm-polyphony.js.map +1 -0
- package/dist/domain/instruments/gm-range.d.ts +15 -0
- package/dist/domain/instruments/gm-range.d.ts.map +1 -0
- package/dist/domain/instruments/gm-range.js +97 -0
- package/dist/domain/instruments/gm-range.js.map +1 -0
- package/dist/domain/instruments/gm-transposition.d.ts +11 -0
- package/dist/domain/instruments/gm-transposition.d.ts.map +1 -0
- package/dist/domain/instruments/gm-transposition.js +69 -0
- package/dist/domain/instruments/gm-transposition.js.map +1 -0
- package/dist/domain/instruments/gm.d.ts +28 -0
- package/dist/domain/instruments/gm.d.ts.map +1 -0
- package/dist/domain/instruments/gm.js +217 -0
- package/dist/domain/instruments/gm.js.map +1 -0
- package/dist/domain/pitch/pitch.d.ts +15 -0
- package/dist/domain/pitch/pitch.d.ts.map +1 -0
- package/dist/domain/pitch/pitch.js +81 -0
- package/dist/domain/pitch/pitch.js.map +1 -0
- package/dist/domain/pitch/transpose.d.ts +37 -0
- package/dist/domain/pitch/transpose.d.ts.map +1 -0
- package/dist/domain/pitch/transpose.js +65 -0
- package/dist/domain/pitch/transpose.js.map +1 -0
- package/dist/domain/quantization/options.d.ts +55 -0
- package/dist/domain/quantization/options.d.ts.map +1 -0
- package/dist/domain/quantization/options.js +2 -0
- package/dist/domain/quantization/options.js.map +1 -0
- package/dist/domain/quantization/quantize.d.ts +13 -0
- package/dist/domain/quantization/quantize.d.ts.map +1 -0
- package/dist/domain/quantization/quantize.js +199 -0
- package/dist/domain/quantization/quantize.js.map +1 -0
- package/dist/domain/score/factory.d.ts +39 -0
- package/dist/domain/score/factory.d.ts.map +1 -0
- package/dist/domain/score/factory.js +148 -0
- package/dist/domain/score/factory.js.map +1 -0
- package/dist/domain/score/fragment.d.ts +28 -0
- package/dist/domain/score/fragment.d.ts.map +1 -0
- package/dist/domain/score/fragment.js +68 -0
- package/dist/domain/score/fragment.js.map +1 -0
- package/dist/domain/score/ids.d.ts +8 -0
- package/dist/domain/score/ids.d.ts.map +1 -0
- package/dist/domain/score/ids.js +38 -0
- package/dist/domain/score/ids.js.map +1 -0
- package/dist/domain/score/queries.d.ts +34 -0
- package/dist/domain/score/queries.d.ts.map +1 -0
- package/dist/domain/score/queries.js +132 -0
- package/dist/domain/score/queries.js.map +1 -0
- package/dist/domain/score/ties.d.ts +73 -0
- package/dist/domain/score/ties.d.ts.map +1 -0
- package/dist/domain/score/ties.js +200 -0
- package/dist/domain/score/ties.js.map +1 -0
- package/dist/domain/selection/selection.d.ts +63 -0
- package/dist/domain/selection/selection.d.ts.map +1 -0
- package/dist/domain/selection/selection.js +129 -0
- package/dist/domain/selection/selection.js.map +1 -0
- package/dist/domain/selection/types.d.ts +10 -0
- package/dist/domain/selection/types.d.ts.map +1 -0
- package/dist/domain/selection/types.js +5 -0
- package/dist/domain/selection/types.js.map +1 -0
- package/dist/domain/time/durations.d.ts +21 -0
- package/dist/domain/time/durations.d.ts.map +1 -0
- package/dist/domain/time/durations.js +53 -0
- package/dist/domain/time/durations.js.map +1 -0
- package/dist/domain/time/fraction.d.ts +19 -0
- package/dist/domain/time/fraction.d.ts.map +1 -0
- package/dist/domain/time/fraction.js +60 -0
- package/dist/domain/time/fraction.js.map +1 -0
- package/dist/domain/time/tempo-map.d.ts +24 -0
- package/dist/domain/time/tempo-map.d.ts.map +1 -0
- package/dist/domain/time/tempo-map.js +85 -0
- package/dist/domain/time/tempo-map.js.map +1 -0
- package/dist/domain/time/ticks.d.ts +34 -0
- package/dist/domain/time/ticks.d.ts.map +1 -0
- package/dist/domain/time/ticks.js +93 -0
- package/dist/domain/time/ticks.js.map +1 -0
- package/dist/domain/validation/issues.d.ts +44 -0
- package/dist/domain/validation/issues.d.ts.map +1 -0
- package/dist/domain/validation/issues.js +34 -0
- package/dist/domain/validation/issues.js.map +1 -0
- package/dist/domain/validation/validator.d.ts +11 -0
- package/dist/domain/validation/validator.d.ts.map +1 -0
- package/dist/domain/validation/validator.js +375 -0
- package/dist/domain/validation/validator.js.map +1 -0
- package/dist/domain/voicing/allocate.d.ts +19 -0
- package/dist/domain/voicing/allocate.d.ts.map +1 -0
- package/dist/domain/voicing/allocate.js +100 -0
- package/dist/domain/voicing/allocate.js.map +1 -0
- package/dist/index.d.ts +46 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +50 -2
- package/dist/index.js.map +1 -1
- package/dist/platform/audio.d.ts +12 -0
- package/dist/platform/audio.d.ts.map +1 -1
- package/dist/platform/playback.d.ts +85 -15
- package/dist/platform/playback.d.ts.map +1 -1
- package/dist/services/regeneration/controller.d.ts +60 -0
- package/dist/services/regeneration/controller.d.ts.map +1 -0
- package/dist/services/regeneration/controller.js +202 -0
- package/dist/services/regeneration/controller.js.map +1 -0
- package/dist/test/fixtures.d.ts +45 -0
- package/dist/test/fixtures.d.ts.map +1 -0
- package/dist/test/fixtures.js +420 -0
- package/dist/test/fixtures.js.map +1 -0
- package/package.json +2 -1
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Regeneration workflow controller (spec §12): turns a score selection +
|
|
3
|
+
* instruction into a structured `RegenerateRegionRequest` (extracting
|
|
4
|
+
* preceding/selected/following context, expanding a partial-measure
|
|
5
|
+
* selection to full measures), and turns an accepted candidate back into a
|
|
6
|
+
* single undoable `ScoreCommand`.
|
|
7
|
+
*/
|
|
8
|
+
import { findTrack } from '../../domain/score/queries.js';
|
|
9
|
+
import { selectionToRange } from '../../domain/selection/selection.js';
|
|
10
|
+
import { extractFragment } from '../../domain/score/fragment.js';
|
|
11
|
+
import { replaceRegionCommand } from '../../domain/commands/region-commands.js';
|
|
12
|
+
/**
|
|
13
|
+
* One, always. Generation is a background job now: nobody is present to pick
|
|
14
|
+
* between alternatives when it lands, so a result that needed choosing would
|
|
15
|
+
* leave the project in limbo.
|
|
16
|
+
*/
|
|
17
|
+
const CANDIDATE_COUNT = 1;
|
|
18
|
+
const CONTEXT_MEASURE_LIMIT = 2;
|
|
19
|
+
/**
|
|
20
|
+
* The tick extent implied by `sel` *before* any measure-boundary
|
|
21
|
+
* alignment — duplicates the first half of `selectionToRange`'s logic
|
|
22
|
+
* (which always returns an already-aligned range) so this controller can
|
|
23
|
+
* detect whether alignment actually changed anything.
|
|
24
|
+
*/
|
|
25
|
+
function rawTickExtent(score, sel) {
|
|
26
|
+
let min = Infinity;
|
|
27
|
+
let max = -Infinity;
|
|
28
|
+
for (const eventId of sel.eventIds) {
|
|
29
|
+
const event = findEventTicks(score, eventId);
|
|
30
|
+
if (!event)
|
|
31
|
+
continue;
|
|
32
|
+
min = Math.min(min, event.start);
|
|
33
|
+
max = Math.max(max, event.end);
|
|
34
|
+
}
|
|
35
|
+
for (const measureId of sel.measureIds) {
|
|
36
|
+
const measure = findMeasureTicks(score, measureId);
|
|
37
|
+
if (!measure)
|
|
38
|
+
continue;
|
|
39
|
+
min = Math.min(min, measure.start);
|
|
40
|
+
max = Math.max(max, measure.end);
|
|
41
|
+
}
|
|
42
|
+
if (sel.range) {
|
|
43
|
+
min = Math.min(min, sel.range.startTick);
|
|
44
|
+
max = Math.max(max, sel.range.endTick);
|
|
45
|
+
}
|
|
46
|
+
if (!Number.isFinite(min) || !Number.isFinite(max))
|
|
47
|
+
return null;
|
|
48
|
+
return { start: min, end: max };
|
|
49
|
+
}
|
|
50
|
+
function findEventTicks(score, eventId) {
|
|
51
|
+
for (const track of score.tracks) {
|
|
52
|
+
for (const measure of track.measures) {
|
|
53
|
+
for (const voice of measure.voices) {
|
|
54
|
+
const event = voice.events.find(e => e.id === eventId);
|
|
55
|
+
if (event)
|
|
56
|
+
return {
|
|
57
|
+
start: event.startTick,
|
|
58
|
+
end: event.startTick + event.durationTicks,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
function findMeasureTicks(score, measureId) {
|
|
66
|
+
for (const track of score.tracks) {
|
|
67
|
+
const measure = track.measures.find(m => m.id === measureId);
|
|
68
|
+
if (measure)
|
|
69
|
+
return {
|
|
70
|
+
start: measure.startTick,
|
|
71
|
+
end: measure.startTick + measure.durationTicks,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
/** Up to `maxMeasures` measures immediately preceding `range.startTick`, unioned across `range`'s tracks (all tracks if `range.trackIds` is empty). */
|
|
77
|
+
function precedingContextRange(score, range, maxMeasures) {
|
|
78
|
+
const trackIds = range.trackIds.length > 0 ? range.trackIds : score.tracks.map(t => t.id);
|
|
79
|
+
let earliestStart = range.startTick;
|
|
80
|
+
for (const trackId of trackIds) {
|
|
81
|
+
const track = findTrack(score, trackId);
|
|
82
|
+
if (!track)
|
|
83
|
+
continue;
|
|
84
|
+
const priorMeasures = track.measures
|
|
85
|
+
.filter(m => m.startTick < range.startTick)
|
|
86
|
+
.slice(-maxMeasures);
|
|
87
|
+
if (priorMeasures.length > 0) {
|
|
88
|
+
earliestStart = Math.min(earliestStart, priorMeasures[0].startTick);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
startTick: earliestStart,
|
|
93
|
+
endTick: range.startTick,
|
|
94
|
+
trackIds: range.trackIds,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/** Up to `maxMeasures` measures immediately following `range.endTick`, unioned across `range`'s tracks (all tracks if `range.trackIds` is empty). */
|
|
98
|
+
function followingContextRange(score, range, maxMeasures) {
|
|
99
|
+
const trackIds = range.trackIds.length > 0 ? range.trackIds : score.tracks.map(t => t.id);
|
|
100
|
+
let latestEnd = range.endTick;
|
|
101
|
+
for (const trackId of trackIds) {
|
|
102
|
+
const track = findTrack(score, trackId);
|
|
103
|
+
if (!track)
|
|
104
|
+
continue;
|
|
105
|
+
const nextMeasures = track.measures
|
|
106
|
+
.filter(m => m.startTick >= range.endTick)
|
|
107
|
+
.slice(0, maxMeasures);
|
|
108
|
+
if (nextMeasures.length > 0) {
|
|
109
|
+
const last = nextMeasures[nextMeasures.length - 1];
|
|
110
|
+
latestEnd = Math.max(latestEnd, last.startTick + last.durationTicks);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
startTick: range.endTick,
|
|
115
|
+
endTick: latestEnd,
|
|
116
|
+
trackIds: range.trackIds,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Builds a `RegenerateRegionRequest` for `selection` on `score`: aligns the
|
|
121
|
+
* selection to full-measure boundaries (`selectionToRange` always does
|
|
122
|
+
* this; `expandedToFullMeasures` reports whether that changed anything),
|
|
123
|
+
* and extracts up to 2 measures of preceding/following context alongside
|
|
124
|
+
* the selected fragment itself (spec §12 steps 1-3).
|
|
125
|
+
*
|
|
126
|
+
* Throws if `selection` has no resolvable tick range (nothing selected).
|
|
127
|
+
*/
|
|
128
|
+
export function prepareRegenerationRequest(score, selection, instruction, options = {}) {
|
|
129
|
+
const alignedRange = selectionToRange(score, selection);
|
|
130
|
+
if (!alignedRange) {
|
|
131
|
+
throw new Error('prepareRegenerationRequest: selection has no resolvable tick range.');
|
|
132
|
+
}
|
|
133
|
+
const raw = rawTickExtent(score, selection);
|
|
134
|
+
const expandedToFullMeasures = raw !== null &&
|
|
135
|
+
(raw.start !== alignedRange.startTick || raw.end !== alignedRange.endTick);
|
|
136
|
+
// `selectionToRange` has already aligned, so the region below is aligned by
|
|
137
|
+
// construction — hence the explicit `true` rather than trusting the default.
|
|
138
|
+
return {
|
|
139
|
+
...prepareRegenerationRequestForRange(score, alignedRange, instruction, {
|
|
140
|
+
...options,
|
|
141
|
+
measureAligned: true,
|
|
142
|
+
}),
|
|
143
|
+
expandedToFullMeasures,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Builds a request for an explicit tick range, used **verbatim**.
|
|
148
|
+
*
|
|
149
|
+
* This is the entry point Replace Notes needs. `prepareRegenerationRequest`
|
|
150
|
+
* snaps its selection out to whole measures, which is exactly what "replace
|
|
151
|
+
* only these notes" forbids; the snapping policy lives there alone so it
|
|
152
|
+
* cannot be half-applied here.
|
|
153
|
+
*
|
|
154
|
+
* `expandedToFullMeasures` is always false: nothing was expanded, because
|
|
155
|
+
* nothing was derived — the caller said what it meant.
|
|
156
|
+
*/
|
|
157
|
+
export function prepareRegenerationRequestForRange(score, range, instruction, options = {}) {
|
|
158
|
+
const selectedFragment = extractFragment(score, range);
|
|
159
|
+
const precedingContext = extractFragment(score, precedingContextRange(score, range, CONTEXT_MEASURE_LIMIT));
|
|
160
|
+
const followingContext = extractFragment(score, followingContextRange(score, range, CONTEXT_MEASURE_LIMIT));
|
|
161
|
+
const constraints = {
|
|
162
|
+
// Omitted, not false, when the region does not sit on barlines: there is
|
|
163
|
+
// no measure count to preserve, and asserting one confuses the prompt.
|
|
164
|
+
...((options.measureAligned ?? true)
|
|
165
|
+
? { preserveMeasureCount: true }
|
|
166
|
+
: {}),
|
|
167
|
+
preserveTimeSignatures: true,
|
|
168
|
+
preserveTempoEvents: true,
|
|
169
|
+
...options.constraints,
|
|
170
|
+
};
|
|
171
|
+
return {
|
|
172
|
+
scoreId: score.id,
|
|
173
|
+
instruction,
|
|
174
|
+
range,
|
|
175
|
+
precedingContext,
|
|
176
|
+
selectedFragment,
|
|
177
|
+
followingContext,
|
|
178
|
+
constraints,
|
|
179
|
+
candidateCount: CANDIDATE_COUNT,
|
|
180
|
+
...(options.style ? { style: options.style } : {}),
|
|
181
|
+
...(options.mood ? { mood: options.mood } : {}),
|
|
182
|
+
...(options.complexity ? { complexity: options.complexity } : {}),
|
|
183
|
+
expandedToFullMeasures: false,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Turns an accepted regeneration `candidate` into the single undoable
|
|
188
|
+
* `ScoreCommand` that replaces the region with the candidate's fragment
|
|
189
|
+
* (spec §12 steps 10-13). `score` is used defensively, to confirm every
|
|
190
|
+
* track the candidate references still exists before building the command
|
|
191
|
+
* (a candidate produced against a since-edited score could reference a
|
|
192
|
+
* deleted track).
|
|
193
|
+
*/
|
|
194
|
+
export function applyCandidate(score, candidate) {
|
|
195
|
+
const { range } = candidate.fragment;
|
|
196
|
+
const missingTrackId = range.trackIds.find(id => findTrack(score, id) === null);
|
|
197
|
+
if (missingTrackId) {
|
|
198
|
+
throw new Error(`applyCandidate: candidate references track "${missingTrackId}", which is not present in the score.`);
|
|
199
|
+
}
|
|
200
|
+
return replaceRegionCommand(range, candidate.fragment);
|
|
201
|
+
}
|
|
202
|
+
//# sourceMappingURL=controller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"controller.js","sourceRoot":"","sources":["../../../src/services/regeneration/controller.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Score fixtures for the codec suites.
|
|
3
|
+
*
|
|
4
|
+
* Copied from `music_lib`'s test tree when the codecs moved here, minus
|
|
5
|
+
* `testRenderTheme` — that one needs a `RenderTheme`, and nothing in this
|
|
6
|
+
* package renders anything.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Deterministic score fixtures shared across the test suite (Task 3 brief).
|
|
10
|
+
* No randomness: ids are generated by a simple per-call counter (not
|
|
11
|
+
* `createId`/`crypto.randomUUID`) and timestamps are a fixed constant, so
|
|
12
|
+
* every call with the same arguments produces a structurally identical
|
|
13
|
+
* `Score`.
|
|
14
|
+
*/
|
|
15
|
+
import type { Score } from '../index.js';
|
|
16
|
+
/** An 8-measure C-major "Twinkle Twinkle Little Star" melody on a single piano track. */
|
|
17
|
+
export declare function twinkleScore(): Score;
|
|
18
|
+
/** A 4-measure two-track score: a treble melody and a supporting bass line. */
|
|
19
|
+
export declare function twoTrackScore(): Score;
|
|
20
|
+
/**
|
|
21
|
+
* Three tracks over 2 measures, deliberately plain.
|
|
22
|
+
*
|
|
23
|
+
* For tests about *which* tracks are involved — visibility, active-track
|
|
24
|
+
* resolution, per-track filtering — where two tracks cannot tell "the first
|
|
25
|
+
* one" apart from "the first visible one", and the music itself is beside the
|
|
26
|
+
* point.
|
|
27
|
+
*/
|
|
28
|
+
export declare function threeTrackScore(): Score;
|
|
29
|
+
/**
|
|
30
|
+
* Two tracks with sharply different densities: a treble line of 16
|
|
31
|
+
* sixteenth notes per measure over a bass of one whole note per measure.
|
|
32
|
+
* Exercises cross-track timeline sync — the dense track needs a wider
|
|
33
|
+
* measure than `BASE_MEASURE_WIDTH` and must still share barlines and
|
|
34
|
+
* tick-aligned x positions with the sparse track.
|
|
35
|
+
*/
|
|
36
|
+
export declare function denseVsSparseScore(): Score;
|
|
37
|
+
/** A 4-measure I-IV-V-I block-chord progression on a single piano track. */
|
|
38
|
+
export declare function chordScore(): Score;
|
|
39
|
+
/**
|
|
40
|
+
* Generates a score with `trackCount` tracks of `measureCount` measures
|
|
41
|
+
* each (4 quarter notes per measure, cycling through a C-major scale), for
|
|
42
|
+
* performance/stress tests. Deterministic and allocation-light.
|
|
43
|
+
*/
|
|
44
|
+
export declare function stressScore(trackCount: number, measureCount: number): Score;
|
|
45
|
+
//# sourceMappingURL=fixtures.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fixtures.d.ts","sourceRoot":"","sources":["../../src/test/fixtures.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;;;;GAMG;AACH,OAAO,KAAK,EAMV,KAAK,EAGN,MAAM,aAAa,CAAC;AAsGrB,yFAAyF;AACzF,wBAAgB,YAAY,IAAI,KAAK,CA0DpC;AAED,+EAA+E;AAC/E,wBAAgB,aAAa,IAAI,KAAK,CAkFrC;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,IAAI,KAAK,CAiDvC;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,IAAI,KAAK,CAgF1C;AAED,4EAA4E;AAC5E,wBAAgB,UAAU,IAAI,KAAK,CAsClC;AAID;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,KAAK,CA+D3E"}
|
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Score fixtures for the codec suites.
|
|
3
|
+
*
|
|
4
|
+
* Copied from `music_lib`'s test tree when the codecs moved here, minus
|
|
5
|
+
* `testRenderTheme` — that one needs a `RenderTheme`, and nothing in this
|
|
6
|
+
* package renders anything.
|
|
7
|
+
*/
|
|
8
|
+
import { measureDurationTicks, ticksFor } from '../index.js';
|
|
9
|
+
const FIXED_TIMESTAMP = '2024-01-01T00:00:00.000Z';
|
|
10
|
+
/** A per-call, per-prefix incrementing id generator (deterministic, cheap). */
|
|
11
|
+
function makeIdFactory() {
|
|
12
|
+
const counters = new Map();
|
|
13
|
+
return {
|
|
14
|
+
next(prefix) {
|
|
15
|
+
const n = counters.get(prefix) ?? 0;
|
|
16
|
+
counters.set(prefix, n + 1);
|
|
17
|
+
return `${prefix}-${n}`;
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/** Builds consecutive single-voice melody measures from a per-measure list of note specs. */
|
|
22
|
+
function buildMelodyMeasures(measuresOfNotes, ppq, timeSignature, keySignature, trackId, ids) {
|
|
23
|
+
const measureTicks = measureDurationTicks(timeSignature, ppq);
|
|
24
|
+
return measuresOfNotes.map((notes, index) => {
|
|
25
|
+
const startTick = index * measureTicks;
|
|
26
|
+
const voiceId = ids.next('voice');
|
|
27
|
+
let cursor = startTick;
|
|
28
|
+
const events = notes.map(spec => {
|
|
29
|
+
const durationTicks = ticksFor(spec.duration, ppq);
|
|
30
|
+
const event = {
|
|
31
|
+
id: ids.next('note'),
|
|
32
|
+
pitch: spec.pitch,
|
|
33
|
+
startTick: cursor,
|
|
34
|
+
durationTicks,
|
|
35
|
+
velocity: 80,
|
|
36
|
+
voiceId,
|
|
37
|
+
trackId,
|
|
38
|
+
};
|
|
39
|
+
cursor += durationTicks;
|
|
40
|
+
return event;
|
|
41
|
+
});
|
|
42
|
+
return {
|
|
43
|
+
id: ids.next('measure'),
|
|
44
|
+
index,
|
|
45
|
+
startTick,
|
|
46
|
+
durationTicks: measureTicks,
|
|
47
|
+
timeSignature,
|
|
48
|
+
keySignature,
|
|
49
|
+
voices: [{ id: voiceId, name: 'Voice 1', events }],
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
/** Builds consecutive single-voice measures, each a block chord spanning the full measure. */
|
|
54
|
+
function buildChordMeasures(chordsByMeasure, ppq, timeSignature, keySignature, trackId, ids) {
|
|
55
|
+
const measureTicks = measureDurationTicks(timeSignature, ppq);
|
|
56
|
+
return chordsByMeasure.map((pitches, index) => {
|
|
57
|
+
const startTick = index * measureTicks;
|
|
58
|
+
const voiceId = ids.next('voice');
|
|
59
|
+
const events = pitches.map(pitch => ({
|
|
60
|
+
id: ids.next('note'),
|
|
61
|
+
pitch,
|
|
62
|
+
startTick,
|
|
63
|
+
durationTicks: measureTicks,
|
|
64
|
+
velocity: 80,
|
|
65
|
+
voiceId,
|
|
66
|
+
trackId,
|
|
67
|
+
}));
|
|
68
|
+
return {
|
|
69
|
+
id: ids.next('measure'),
|
|
70
|
+
index,
|
|
71
|
+
startTick,
|
|
72
|
+
durationTicks: measureTicks,
|
|
73
|
+
timeSignature,
|
|
74
|
+
keySignature,
|
|
75
|
+
voices: [{ id: voiceId, name: 'Voice 1', events }],
|
|
76
|
+
};
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
const C_MAJOR = { fifths: 0, mode: 'major' };
|
|
80
|
+
const FOUR_FOUR = { numerator: 4, denominator: 4 };
|
|
81
|
+
function naturalPitch(step, octave) {
|
|
82
|
+
return { step, accidental: 0, octave };
|
|
83
|
+
}
|
|
84
|
+
/** An 8-measure C-major "Twinkle Twinkle Little Star" melody on a single piano track. */
|
|
85
|
+
export function twinkleScore() {
|
|
86
|
+
const ids = makeIdFactory();
|
|
87
|
+
const ppq = 480;
|
|
88
|
+
const q = (step, octave = 4) => ({
|
|
89
|
+
pitch: naturalPitch(step, octave),
|
|
90
|
+
duration: 'quarter',
|
|
91
|
+
});
|
|
92
|
+
const h = (step, octave = 4) => ({
|
|
93
|
+
pitch: naturalPitch(step, octave),
|
|
94
|
+
duration: 'half',
|
|
95
|
+
});
|
|
96
|
+
const measures = [
|
|
97
|
+
[q('C'), q('C'), q('G'), q('G')],
|
|
98
|
+
[q('A'), q('A'), h('G')],
|
|
99
|
+
[q('F'), q('F'), q('E'), q('E')],
|
|
100
|
+
[q('D'), q('D'), h('C')],
|
|
101
|
+
[q('G'), q('G'), q('F'), q('F')],
|
|
102
|
+
[q('E'), q('E'), h('D')],
|
|
103
|
+
[q('G'), q('G'), q('F'), q('F')],
|
|
104
|
+
[q('E'), q('E'), h('D')],
|
|
105
|
+
];
|
|
106
|
+
const trackId = ids.next('track');
|
|
107
|
+
const track = {
|
|
108
|
+
id: trackId,
|
|
109
|
+
name: 'Piano',
|
|
110
|
+
instrumentName: 'Piano',
|
|
111
|
+
midiProgram: 0,
|
|
112
|
+
midiChannel: 0,
|
|
113
|
+
clef: 'treble',
|
|
114
|
+
volume: 1,
|
|
115
|
+
pan: 0,
|
|
116
|
+
muted: false,
|
|
117
|
+
solo: false,
|
|
118
|
+
measures: buildMelodyMeasures(measures, ppq, FOUR_FOUR, C_MAJOR, trackId, ids),
|
|
119
|
+
};
|
|
120
|
+
return {
|
|
121
|
+
id: ids.next('score'),
|
|
122
|
+
version: 1,
|
|
123
|
+
ppq,
|
|
124
|
+
metadata: {
|
|
125
|
+
title: 'Twinkle Twinkle Little Star',
|
|
126
|
+
createdAt: FIXED_TIMESTAMP,
|
|
127
|
+
updatedAt: FIXED_TIMESTAMP,
|
|
128
|
+
},
|
|
129
|
+
tempoMap: [{ id: ids.next('tempo'), tick: 0, bpm: 120 }],
|
|
130
|
+
tracks: [track],
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/** A 4-measure two-track score: a treble melody and a supporting bass line. */
|
|
134
|
+
export function twoTrackScore() {
|
|
135
|
+
const ids = makeIdFactory();
|
|
136
|
+
const ppq = 480;
|
|
137
|
+
const q = (step, octave) => ({
|
|
138
|
+
pitch: naturalPitch(step, octave),
|
|
139
|
+
duration: 'quarter',
|
|
140
|
+
});
|
|
141
|
+
const w = (step, octave) => ({
|
|
142
|
+
pitch: naturalPitch(step, octave),
|
|
143
|
+
duration: 'whole',
|
|
144
|
+
});
|
|
145
|
+
const trebleMeasures = [
|
|
146
|
+
[q('C', 4), q('D', 4), q('E', 4), q('F', 4)],
|
|
147
|
+
[q('G', 4), q('F', 4), q('E', 4), q('D', 4)],
|
|
148
|
+
[q('C', 4), q('D', 4), q('E', 4), q('F', 4)],
|
|
149
|
+
[q('G', 4), q('F', 4), q('E', 4), q('D', 4)],
|
|
150
|
+
];
|
|
151
|
+
const bassMeasures = [
|
|
152
|
+
[w('C', 2)],
|
|
153
|
+
[w('G', 2)],
|
|
154
|
+
[w('C', 2)],
|
|
155
|
+
[w('G', 2)],
|
|
156
|
+
];
|
|
157
|
+
const trebleId = ids.next('track');
|
|
158
|
+
const bassId = ids.next('track');
|
|
159
|
+
const treble = {
|
|
160
|
+
id: trebleId,
|
|
161
|
+
name: 'Treble',
|
|
162
|
+
instrumentName: 'Piano',
|
|
163
|
+
midiProgram: 0,
|
|
164
|
+
midiChannel: 0,
|
|
165
|
+
clef: 'treble',
|
|
166
|
+
volume: 1,
|
|
167
|
+
pan: 0,
|
|
168
|
+
muted: false,
|
|
169
|
+
solo: false,
|
|
170
|
+
measures: buildMelodyMeasures(trebleMeasures, ppq, FOUR_FOUR, C_MAJOR, trebleId, ids),
|
|
171
|
+
};
|
|
172
|
+
const bass = {
|
|
173
|
+
id: bassId,
|
|
174
|
+
name: 'Bass',
|
|
175
|
+
instrumentName: 'Acoustic Bass',
|
|
176
|
+
midiProgram: 32,
|
|
177
|
+
midiChannel: 1,
|
|
178
|
+
clef: 'bass',
|
|
179
|
+
volume: 1,
|
|
180
|
+
pan: 0,
|
|
181
|
+
muted: false,
|
|
182
|
+
solo: false,
|
|
183
|
+
measures: buildMelodyMeasures(bassMeasures, ppq, FOUR_FOUR, C_MAJOR, bassId, ids),
|
|
184
|
+
};
|
|
185
|
+
return {
|
|
186
|
+
id: ids.next('score'),
|
|
187
|
+
version: 1,
|
|
188
|
+
ppq,
|
|
189
|
+
metadata: {
|
|
190
|
+
title: 'Two Track Demo',
|
|
191
|
+
createdAt: FIXED_TIMESTAMP,
|
|
192
|
+
updatedAt: FIXED_TIMESTAMP,
|
|
193
|
+
},
|
|
194
|
+
tempoMap: [{ id: ids.next('tempo'), tick: 0, bpm: 120 }],
|
|
195
|
+
tracks: [treble, bass],
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Three tracks over 2 measures, deliberately plain.
|
|
200
|
+
*
|
|
201
|
+
* For tests about *which* tracks are involved — visibility, active-track
|
|
202
|
+
* resolution, per-track filtering — where two tracks cannot tell "the first
|
|
203
|
+
* one" apart from "the first visible one", and the music itself is beside the
|
|
204
|
+
* point.
|
|
205
|
+
*/
|
|
206
|
+
export function threeTrackScore() {
|
|
207
|
+
const ids = makeIdFactory();
|
|
208
|
+
const ppq = 480;
|
|
209
|
+
const q = (step, octave) => ({
|
|
210
|
+
pitch: naturalPitch(step, octave),
|
|
211
|
+
duration: 'quarter',
|
|
212
|
+
});
|
|
213
|
+
const measures = [
|
|
214
|
+
[q('C', 4), q('D', 4), q('E', 4), q('F', 4)],
|
|
215
|
+
[q('G', 4), q('F', 4), q('E', 4), q('D', 4)],
|
|
216
|
+
];
|
|
217
|
+
const tracks = ['Alpha', 'Beta', 'Gamma'].map((name, index) => {
|
|
218
|
+
const trackId = ids.next('track');
|
|
219
|
+
return {
|
|
220
|
+
id: trackId,
|
|
221
|
+
name,
|
|
222
|
+
instrumentName: 'Piano',
|
|
223
|
+
midiProgram: 0,
|
|
224
|
+
midiChannel: index,
|
|
225
|
+
clef: 'treble',
|
|
226
|
+
volume: 1,
|
|
227
|
+
pan: 0,
|
|
228
|
+
muted: false,
|
|
229
|
+
solo: false,
|
|
230
|
+
measures: buildMelodyMeasures(measures, ppq, FOUR_FOUR, C_MAJOR, trackId, ids),
|
|
231
|
+
};
|
|
232
|
+
});
|
|
233
|
+
return {
|
|
234
|
+
id: ids.next('score'),
|
|
235
|
+
version: 1,
|
|
236
|
+
ppq,
|
|
237
|
+
metadata: {
|
|
238
|
+
title: 'Three Track Demo',
|
|
239
|
+
createdAt: FIXED_TIMESTAMP,
|
|
240
|
+
updatedAt: FIXED_TIMESTAMP,
|
|
241
|
+
},
|
|
242
|
+
tempoMap: [{ id: ids.next('tempo'), tick: 0, bpm: 120 }],
|
|
243
|
+
tracks,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Two tracks with sharply different densities: a treble line of 16
|
|
248
|
+
* sixteenth notes per measure over a bass of one whole note per measure.
|
|
249
|
+
* Exercises cross-track timeline sync — the dense track needs a wider
|
|
250
|
+
* measure than `BASE_MEASURE_WIDTH` and must still share barlines and
|
|
251
|
+
* tick-aligned x positions with the sparse track.
|
|
252
|
+
*/
|
|
253
|
+
export function denseVsSparseScore() {
|
|
254
|
+
const ids = makeIdFactory();
|
|
255
|
+
const ppq = 480;
|
|
256
|
+
const s = (step, octave) => ({
|
|
257
|
+
pitch: naturalPitch(step, octave),
|
|
258
|
+
duration: 'sixteenth',
|
|
259
|
+
});
|
|
260
|
+
const w = (step, octave) => ({
|
|
261
|
+
pitch: naturalPitch(step, octave),
|
|
262
|
+
duration: 'whole',
|
|
263
|
+
});
|
|
264
|
+
const runUp = ['C', 'D', 'E', 'F', 'G', 'A', 'B']
|
|
265
|
+
.flatMap(step => [s(step, 4), s(step, 4)])
|
|
266
|
+
.concat([s('C', 5), s('C', 5)]);
|
|
267
|
+
const denseMeasures = [runUp, runUp, runUp, runUp];
|
|
268
|
+
const sparseMeasures = [
|
|
269
|
+
[w('C', 2)],
|
|
270
|
+
[w('G', 2)],
|
|
271
|
+
[w('C', 2)],
|
|
272
|
+
[w('G', 2)],
|
|
273
|
+
];
|
|
274
|
+
const denseId = ids.next('track');
|
|
275
|
+
const sparseId = ids.next('track');
|
|
276
|
+
const dense = {
|
|
277
|
+
id: denseId,
|
|
278
|
+
name: 'Dense',
|
|
279
|
+
instrumentName: 'Piano',
|
|
280
|
+
midiProgram: 0,
|
|
281
|
+
midiChannel: 0,
|
|
282
|
+
clef: 'treble',
|
|
283
|
+
volume: 1,
|
|
284
|
+
pan: 0,
|
|
285
|
+
muted: false,
|
|
286
|
+
solo: false,
|
|
287
|
+
measures: buildMelodyMeasures(denseMeasures, ppq, FOUR_FOUR, C_MAJOR, denseId, ids),
|
|
288
|
+
};
|
|
289
|
+
const sparse = {
|
|
290
|
+
id: sparseId,
|
|
291
|
+
name: 'Sparse',
|
|
292
|
+
instrumentName: 'Acoustic Bass',
|
|
293
|
+
midiProgram: 32,
|
|
294
|
+
midiChannel: 1,
|
|
295
|
+
clef: 'bass',
|
|
296
|
+
volume: 1,
|
|
297
|
+
pan: 0,
|
|
298
|
+
muted: false,
|
|
299
|
+
solo: false,
|
|
300
|
+
measures: buildMelodyMeasures(sparseMeasures, ppq, FOUR_FOUR, C_MAJOR, sparseId, ids),
|
|
301
|
+
};
|
|
302
|
+
return {
|
|
303
|
+
id: ids.next('score'),
|
|
304
|
+
version: 1,
|
|
305
|
+
ppq,
|
|
306
|
+
metadata: {
|
|
307
|
+
title: 'Dense vs Sparse',
|
|
308
|
+
createdAt: FIXED_TIMESTAMP,
|
|
309
|
+
updatedAt: FIXED_TIMESTAMP,
|
|
310
|
+
},
|
|
311
|
+
tempoMap: [{ id: ids.next('tempo'), tick: 0, bpm: 120 }],
|
|
312
|
+
tracks: [dense, sparse],
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
/** A 4-measure I-IV-V-I block-chord progression on a single piano track. */
|
|
316
|
+
export function chordScore() {
|
|
317
|
+
const ids = makeIdFactory();
|
|
318
|
+
const ppq = 480;
|
|
319
|
+
const chords = [
|
|
320
|
+
[naturalPitch('C', 4), naturalPitch('E', 4), naturalPitch('G', 4)], // I
|
|
321
|
+
[naturalPitch('F', 4), naturalPitch('A', 4), naturalPitch('C', 5)], // IV
|
|
322
|
+
[naturalPitch('G', 4), naturalPitch('B', 4), naturalPitch('D', 5)], // V
|
|
323
|
+
[naturalPitch('C', 4), naturalPitch('E', 4), naturalPitch('G', 4)], // I
|
|
324
|
+
];
|
|
325
|
+
const trackId = ids.next('track');
|
|
326
|
+
const track = {
|
|
327
|
+
id: trackId,
|
|
328
|
+
name: 'Piano',
|
|
329
|
+
instrumentName: 'Piano',
|
|
330
|
+
midiProgram: 0,
|
|
331
|
+
midiChannel: 0,
|
|
332
|
+
clef: 'treble',
|
|
333
|
+
volume: 1,
|
|
334
|
+
pan: 0,
|
|
335
|
+
muted: false,
|
|
336
|
+
solo: false,
|
|
337
|
+
measures: buildChordMeasures(chords, ppq, FOUR_FOUR, C_MAJOR, trackId, ids),
|
|
338
|
+
};
|
|
339
|
+
return {
|
|
340
|
+
id: ids.next('score'),
|
|
341
|
+
version: 1,
|
|
342
|
+
ppq,
|
|
343
|
+
metadata: {
|
|
344
|
+
title: 'Chord Progression Demo',
|
|
345
|
+
createdAt: FIXED_TIMESTAMP,
|
|
346
|
+
updatedAt: FIXED_TIMESTAMP,
|
|
347
|
+
},
|
|
348
|
+
tempoMap: [{ id: ids.next('tempo'), tick: 0, bpm: 90 }],
|
|
349
|
+
tracks: [track],
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
const STRESS_SCALE = ['C', 'D', 'E', 'F', 'G', 'A', 'B'];
|
|
353
|
+
/**
|
|
354
|
+
* Generates a score with `trackCount` tracks of `measureCount` measures
|
|
355
|
+
* each (4 quarter notes per measure, cycling through a C-major scale), for
|
|
356
|
+
* performance/stress tests. Deterministic and allocation-light.
|
|
357
|
+
*/
|
|
358
|
+
export function stressScore(trackCount, measureCount) {
|
|
359
|
+
const ids = makeIdFactory();
|
|
360
|
+
const ppq = 480;
|
|
361
|
+
const measureTicks = measureDurationTicks(FOUR_FOUR, ppq);
|
|
362
|
+
const quarterTicks = ticksFor('quarter', ppq);
|
|
363
|
+
const tracks = [];
|
|
364
|
+
for (let t = 0; t < trackCount; t += 1) {
|
|
365
|
+
const trackId = ids.next('track');
|
|
366
|
+
const measures = [];
|
|
367
|
+
for (let m = 0; m < measureCount; m += 1) {
|
|
368
|
+
const startTick = m * measureTicks;
|
|
369
|
+
const voiceId = ids.next('voice');
|
|
370
|
+
const events = [];
|
|
371
|
+
for (let beat = 0; beat < 4; beat += 1) {
|
|
372
|
+
const step = STRESS_SCALE[(t + m + beat) % STRESS_SCALE.length];
|
|
373
|
+
events.push({
|
|
374
|
+
id: ids.next('note'),
|
|
375
|
+
pitch: naturalPitch(step, 4),
|
|
376
|
+
startTick: startTick + beat * quarterTicks,
|
|
377
|
+
durationTicks: quarterTicks,
|
|
378
|
+
velocity: 80,
|
|
379
|
+
voiceId,
|
|
380
|
+
trackId,
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
measures.push({
|
|
384
|
+
id: ids.next('measure'),
|
|
385
|
+
index: m,
|
|
386
|
+
startTick,
|
|
387
|
+
durationTicks: measureTicks,
|
|
388
|
+
timeSignature: FOUR_FOUR,
|
|
389
|
+
keySignature: C_MAJOR,
|
|
390
|
+
voices: [{ id: voiceId, name: 'Voice 1', events }],
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
tracks.push({
|
|
394
|
+
id: trackId,
|
|
395
|
+
name: `Track ${t + 1}`,
|
|
396
|
+
instrumentName: 'Piano',
|
|
397
|
+
midiProgram: 0,
|
|
398
|
+
midiChannel: t % 16,
|
|
399
|
+
clef: 'treble',
|
|
400
|
+
volume: 1,
|
|
401
|
+
pan: 0,
|
|
402
|
+
muted: false,
|
|
403
|
+
solo: false,
|
|
404
|
+
measures,
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
return {
|
|
408
|
+
id: ids.next('score'),
|
|
409
|
+
version: 1,
|
|
410
|
+
ppq,
|
|
411
|
+
metadata: {
|
|
412
|
+
title: `Stress ${trackCount}x${measureCount}`,
|
|
413
|
+
createdAt: FIXED_TIMESTAMP,
|
|
414
|
+
updatedAt: FIXED_TIMESTAMP,
|
|
415
|
+
},
|
|
416
|
+
tempoMap: [{ id: ids.next('tempo'), tick: 0, bpm: 120 }],
|
|
417
|
+
tracks,
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
//# sourceMappingURL=fixtures.js.map
|