@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.
Files changed (153) hide show
  1. package/dist/domain/commands/edit-commands.d.ts +66 -0
  2. package/dist/domain/commands/edit-commands.d.ts.map +1 -0
  3. package/dist/domain/commands/edit-commands.js +185 -0
  4. package/dist/domain/commands/edit-commands.js.map +1 -0
  5. package/dist/domain/commands/note-commands.d.ts +73 -0
  6. package/dist/domain/commands/note-commands.d.ts.map +1 -0
  7. package/dist/domain/commands/note-commands.js +292 -0
  8. package/dist/domain/commands/note-commands.js.map +1 -0
  9. package/dist/domain/commands/reflow.d.ts +69 -0
  10. package/dist/domain/commands/reflow.d.ts.map +1 -0
  11. package/dist/domain/commands/reflow.js +395 -0
  12. package/dist/domain/commands/reflow.js.map +1 -0
  13. package/dist/domain/commands/region-commands.d.ts +21 -0
  14. package/dist/domain/commands/region-commands.d.ts.map +1 -0
  15. package/dist/domain/commands/region-commands.js +47 -0
  16. package/dist/domain/commands/region-commands.js.map +1 -0
  17. package/dist/domain/commands/relocate-commands.d.ts +18 -0
  18. package/dist/domain/commands/relocate-commands.d.ts.map +1 -0
  19. package/dist/domain/commands/relocate-commands.js +108 -0
  20. package/dist/domain/commands/relocate-commands.js.map +1 -0
  21. package/dist/domain/commands/ripple-commands.d.ts +44 -0
  22. package/dist/domain/commands/ripple-commands.d.ts.map +1 -0
  23. package/dist/domain/commands/ripple-commands.js +104 -0
  24. package/dist/domain/commands/ripple-commands.js.map +1 -0
  25. package/dist/domain/commands/snapshot.d.ts +38 -0
  26. package/dist/domain/commands/snapshot.d.ts.map +1 -0
  27. package/dist/domain/commands/snapshot.js +44 -0
  28. package/dist/domain/commands/snapshot.js.map +1 -0
  29. package/dist/domain/commands/structure-commands.d.ts +38 -0
  30. package/dist/domain/commands/structure-commands.d.ts.map +1 -0
  31. package/dist/domain/commands/structure-commands.js +220 -0
  32. package/dist/domain/commands/structure-commands.js.map +1 -0
  33. package/dist/domain/commands/track-commands.d.ts +15 -0
  34. package/dist/domain/commands/track-commands.d.ts.map +1 -0
  35. package/dist/domain/commands/track-commands.js +56 -0
  36. package/dist/domain/commands/track-commands.js.map +1 -0
  37. package/dist/domain/commands/types.d.ts +31 -0
  38. package/dist/domain/commands/types.d.ts.map +1 -0
  39. package/dist/domain/commands/types.js +2 -0
  40. package/dist/domain/commands/types.js.map +1 -0
  41. package/dist/domain/instruments/gm-kit.d.ts +45 -0
  42. package/dist/domain/instruments/gm-kit.d.ts.map +1 -0
  43. package/dist/domain/instruments/gm-kit.js +59 -0
  44. package/dist/domain/instruments/gm-kit.js.map +1 -0
  45. package/dist/domain/instruments/gm-percussion.d.ts +42 -0
  46. package/dist/domain/instruments/gm-percussion.d.ts.map +1 -0
  47. package/dist/domain/instruments/gm-percussion.js +85 -0
  48. package/dist/domain/instruments/gm-percussion.js.map +1 -0
  49. package/dist/domain/instruments/gm-polyphony.d.ts +13 -0
  50. package/dist/domain/instruments/gm-polyphony.d.ts.map +1 -0
  51. package/dist/domain/instruments/gm-polyphony.js +76 -0
  52. package/dist/domain/instruments/gm-polyphony.js.map +1 -0
  53. package/dist/domain/instruments/gm-range.d.ts +15 -0
  54. package/dist/domain/instruments/gm-range.d.ts.map +1 -0
  55. package/dist/domain/instruments/gm-range.js +97 -0
  56. package/dist/domain/instruments/gm-range.js.map +1 -0
  57. package/dist/domain/instruments/gm-transposition.d.ts +11 -0
  58. package/dist/domain/instruments/gm-transposition.d.ts.map +1 -0
  59. package/dist/domain/instruments/gm-transposition.js +69 -0
  60. package/dist/domain/instruments/gm-transposition.js.map +1 -0
  61. package/dist/domain/instruments/gm.d.ts +28 -0
  62. package/dist/domain/instruments/gm.d.ts.map +1 -0
  63. package/dist/domain/instruments/gm.js +217 -0
  64. package/dist/domain/instruments/gm.js.map +1 -0
  65. package/dist/domain/pitch/pitch.d.ts +15 -0
  66. package/dist/domain/pitch/pitch.d.ts.map +1 -0
  67. package/dist/domain/pitch/pitch.js +81 -0
  68. package/dist/domain/pitch/pitch.js.map +1 -0
  69. package/dist/domain/pitch/transpose.d.ts +37 -0
  70. package/dist/domain/pitch/transpose.d.ts.map +1 -0
  71. package/dist/domain/pitch/transpose.js +65 -0
  72. package/dist/domain/pitch/transpose.js.map +1 -0
  73. package/dist/domain/quantization/options.d.ts +55 -0
  74. package/dist/domain/quantization/options.d.ts.map +1 -0
  75. package/dist/domain/quantization/options.js +2 -0
  76. package/dist/domain/quantization/options.js.map +1 -0
  77. package/dist/domain/quantization/quantize.d.ts +13 -0
  78. package/dist/domain/quantization/quantize.d.ts.map +1 -0
  79. package/dist/domain/quantization/quantize.js +199 -0
  80. package/dist/domain/quantization/quantize.js.map +1 -0
  81. package/dist/domain/score/factory.d.ts +39 -0
  82. package/dist/domain/score/factory.d.ts.map +1 -0
  83. package/dist/domain/score/factory.js +148 -0
  84. package/dist/domain/score/factory.js.map +1 -0
  85. package/dist/domain/score/fragment.d.ts +28 -0
  86. package/dist/domain/score/fragment.d.ts.map +1 -0
  87. package/dist/domain/score/fragment.js +68 -0
  88. package/dist/domain/score/fragment.js.map +1 -0
  89. package/dist/domain/score/ids.d.ts +8 -0
  90. package/dist/domain/score/ids.d.ts.map +1 -0
  91. package/dist/domain/score/ids.js +38 -0
  92. package/dist/domain/score/ids.js.map +1 -0
  93. package/dist/domain/score/queries.d.ts +34 -0
  94. package/dist/domain/score/queries.d.ts.map +1 -0
  95. package/dist/domain/score/queries.js +132 -0
  96. package/dist/domain/score/queries.js.map +1 -0
  97. package/dist/domain/score/ties.d.ts +73 -0
  98. package/dist/domain/score/ties.d.ts.map +1 -0
  99. package/dist/domain/score/ties.js +200 -0
  100. package/dist/domain/score/ties.js.map +1 -0
  101. package/dist/domain/selection/selection.d.ts +63 -0
  102. package/dist/domain/selection/selection.d.ts.map +1 -0
  103. package/dist/domain/selection/selection.js +129 -0
  104. package/dist/domain/selection/selection.js.map +1 -0
  105. package/dist/domain/selection/types.d.ts +10 -0
  106. package/dist/domain/selection/types.d.ts.map +1 -0
  107. package/dist/domain/selection/types.js +5 -0
  108. package/dist/domain/selection/types.js.map +1 -0
  109. package/dist/domain/time/durations.d.ts +21 -0
  110. package/dist/domain/time/durations.d.ts.map +1 -0
  111. package/dist/domain/time/durations.js +53 -0
  112. package/dist/domain/time/durations.js.map +1 -0
  113. package/dist/domain/time/fraction.d.ts +19 -0
  114. package/dist/domain/time/fraction.d.ts.map +1 -0
  115. package/dist/domain/time/fraction.js +60 -0
  116. package/dist/domain/time/fraction.js.map +1 -0
  117. package/dist/domain/time/tempo-map.d.ts +24 -0
  118. package/dist/domain/time/tempo-map.d.ts.map +1 -0
  119. package/dist/domain/time/tempo-map.js +85 -0
  120. package/dist/domain/time/tempo-map.js.map +1 -0
  121. package/dist/domain/time/ticks.d.ts +34 -0
  122. package/dist/domain/time/ticks.d.ts.map +1 -0
  123. package/dist/domain/time/ticks.js +93 -0
  124. package/dist/domain/time/ticks.js.map +1 -0
  125. package/dist/domain/validation/issues.d.ts +44 -0
  126. package/dist/domain/validation/issues.d.ts.map +1 -0
  127. package/dist/domain/validation/issues.js +34 -0
  128. package/dist/domain/validation/issues.js.map +1 -0
  129. package/dist/domain/validation/validator.d.ts +11 -0
  130. package/dist/domain/validation/validator.d.ts.map +1 -0
  131. package/dist/domain/validation/validator.js +375 -0
  132. package/dist/domain/validation/validator.js.map +1 -0
  133. package/dist/domain/voicing/allocate.d.ts +19 -0
  134. package/dist/domain/voicing/allocate.d.ts.map +1 -0
  135. package/dist/domain/voicing/allocate.js +100 -0
  136. package/dist/domain/voicing/allocate.js.map +1 -0
  137. package/dist/index.d.ts +46 -4
  138. package/dist/index.d.ts.map +1 -1
  139. package/dist/index.js +50 -2
  140. package/dist/index.js.map +1 -1
  141. package/dist/platform/audio.d.ts +12 -0
  142. package/dist/platform/audio.d.ts.map +1 -1
  143. package/dist/platform/playback.d.ts +85 -15
  144. package/dist/platform/playback.d.ts.map +1 -1
  145. package/dist/services/regeneration/controller.d.ts +60 -0
  146. package/dist/services/regeneration/controller.d.ts.map +1 -0
  147. package/dist/services/regeneration/controller.js +202 -0
  148. package/dist/services/regeneration/controller.js.map +1 -0
  149. package/dist/test/fixtures.d.ts +45 -0
  150. package/dist/test/fixtures.d.ts.map +1 -0
  151. package/dist/test/fixtures.js +420 -0
  152. package/dist/test/fixtures.js.map +1 -0
  153. 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