@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,375 @@
|
|
|
1
|
+
import { isNoteEvent } from '../../index.js';
|
|
2
|
+
import { pitchToMidi } from '../pitch/pitch.js';
|
|
3
|
+
import { voiceChannel } from '../score/ties.js';
|
|
4
|
+
import { ISSUE_CODES } from './issues.js';
|
|
5
|
+
const MIN_MIDI = 0;
|
|
6
|
+
const MAX_MIDI = 127;
|
|
7
|
+
const MIN_VELOCITY = 0;
|
|
8
|
+
const MAX_VELOCITY = 127;
|
|
9
|
+
const MIN_MIDI_PROGRAM = 0;
|
|
10
|
+
const MAX_MIDI_PROGRAM = 127;
|
|
11
|
+
const MIN_MIDI_CHANNEL = 0;
|
|
12
|
+
const MAX_MIDI_CHANNEL = 15;
|
|
13
|
+
const VALID_TIME_SIG_DENOMINATORS = new Set([1, 2, 4, 8, 16, 32]);
|
|
14
|
+
const MIN_FIFTHS = -7;
|
|
15
|
+
const MAX_FIFTHS = 7;
|
|
16
|
+
const MIN_BPM = 20;
|
|
17
|
+
const MAX_BPM = 400;
|
|
18
|
+
/**
|
|
19
|
+
* Maximum number of notes sounding at once (within a single track) before a
|
|
20
|
+
* "too many simultaneous notes" readability warning fires. Spec §23 names
|
|
21
|
+
* this rule but doesn't give a number; 10 is a deliberate implementer
|
|
22
|
+
* default (roughly as many notes as a two-hand piano voicing on one staff
|
|
23
|
+
* can render legibly), documented here so a later task can make it
|
|
24
|
+
* configurable if that turns out to be too strict/loose in practice.
|
|
25
|
+
*/
|
|
26
|
+
const MAX_SIMULTANEOUS_NOTES = 10;
|
|
27
|
+
function samePitch(a, b) {
|
|
28
|
+
return (a.step === b.step && a.accidental === b.accidental && a.octave === b.octave);
|
|
29
|
+
}
|
|
30
|
+
function issue(severity, code, message, extra) {
|
|
31
|
+
return { severity, code, message, ...extra };
|
|
32
|
+
}
|
|
33
|
+
// ---- Whole-score: tempo map, unique ids -----------------------------------
|
|
34
|
+
/** Tempo-map tick validity/ordering and bpm range (20-400). */
|
|
35
|
+
function checkTempoMap(score) {
|
|
36
|
+
const issues = [];
|
|
37
|
+
let lastTick = -Infinity;
|
|
38
|
+
for (const event of score.tempoMap) {
|
|
39
|
+
if (event.tick < 0) {
|
|
40
|
+
issues.push(issue('error', ISSUE_CODES.NEGATIVE_TICK, `Tempo event ${event.id} has a negative tick (${event.tick}).`, {
|
|
41
|
+
objectId: event.id,
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
if (event.tick < lastTick) {
|
|
45
|
+
issues.push(issue('error', ISSUE_CODES.TEMPO_MAP_UNSORTED, `Tempo map is not sorted by tick at event ${event.id} (tick ${event.tick} follows tick ${lastTick}).`, { objectId: event.id }));
|
|
46
|
+
}
|
|
47
|
+
lastTick = event.tick;
|
|
48
|
+
if (event.bpm < MIN_BPM || event.bpm > MAX_BPM) {
|
|
49
|
+
issues.push(issue('error', ISSUE_CODES.INVALID_TEMPO_BPM, `Tempo event ${event.id} has bpm ${event.bpm}, expected ${MIN_BPM}-${MAX_BPM}.`, { objectId: event.id }));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return issues;
|
|
53
|
+
}
|
|
54
|
+
/** Every id in the score (score/tempo/track/measure/voice/event) must be unique. */
|
|
55
|
+
function checkUniqueIds(score) {
|
|
56
|
+
const counts = new Map();
|
|
57
|
+
const record = (id) => {
|
|
58
|
+
counts.set(id, (counts.get(id) ?? 0) + 1);
|
|
59
|
+
};
|
|
60
|
+
record(score.id);
|
|
61
|
+
for (const tempoEvent of score.tempoMap)
|
|
62
|
+
record(tempoEvent.id);
|
|
63
|
+
for (const track of score.tracks) {
|
|
64
|
+
record(track.id);
|
|
65
|
+
for (const measure of track.measures) {
|
|
66
|
+
record(measure.id);
|
|
67
|
+
for (const voice of measure.voices) {
|
|
68
|
+
record(voice.id);
|
|
69
|
+
for (const event of voice.events)
|
|
70
|
+
record(event.id);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const issues = [];
|
|
75
|
+
for (const [id, count] of counts) {
|
|
76
|
+
if (count > 1) {
|
|
77
|
+
issues.push(issue('error', ISSUE_CODES.DUPLICATE_ID, `Id "${id}" is used ${count} times; ids must be unique.`, {
|
|
78
|
+
objectId: id,
|
|
79
|
+
}));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return issues;
|
|
83
|
+
}
|
|
84
|
+
// ---- Track-level ------------------------------------------------------------
|
|
85
|
+
/** midiProgram (0-127) and midiChannel (0-15) range checks. */
|
|
86
|
+
function checkTrack(track) {
|
|
87
|
+
const issues = [];
|
|
88
|
+
if (track.midiProgram < MIN_MIDI_PROGRAM ||
|
|
89
|
+
track.midiProgram > MAX_MIDI_PROGRAM) {
|
|
90
|
+
issues.push(issue('error', ISSUE_CODES.INVALID_MIDI_PROGRAM, `Track "${track.name}" has midiProgram ${track.midiProgram}, expected ${MIN_MIDI_PROGRAM}-${MAX_MIDI_PROGRAM}.`, { objectId: track.id, trackId: track.id }));
|
|
91
|
+
}
|
|
92
|
+
if (track.midiChannel < MIN_MIDI_CHANNEL ||
|
|
93
|
+
track.midiChannel > MAX_MIDI_CHANNEL) {
|
|
94
|
+
issues.push(issue('error', ISSUE_CODES.INVALID_MIDI_CHANNEL, `Track "${track.name}" has midiChannel ${track.midiChannel}, expected ${MIN_MIDI_CHANNEL}-${MAX_MIDI_CHANNEL}.`, { objectId: track.id, trackId: track.id }));
|
|
95
|
+
}
|
|
96
|
+
return issues;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* A track's measures must be contiguous and sequentially indexed: measure
|
|
100
|
+
* at array position `i` must have `index === i` and `startTick` equal to
|
|
101
|
+
* the previous measure's `startTick + durationTicks` (0 for the first).
|
|
102
|
+
* Checked against each measure's *actual* preceding measure (not a
|
|
103
|
+
* recomputed ideal), so one bad measure doesn't cascade a flood of
|
|
104
|
+
* downstream false positives.
|
|
105
|
+
*/
|
|
106
|
+
function checkMeasureOrdering(track) {
|
|
107
|
+
const issues = [];
|
|
108
|
+
let expectedStart = 0;
|
|
109
|
+
track.measures.forEach((measure, position) => {
|
|
110
|
+
if (measure.index !== position) {
|
|
111
|
+
issues.push(issue('error', ISSUE_CODES.MEASURE_ORDERING, `Measure at position ${position} of track "${track.name}" has index ${measure.index}, expected ${position}.`, { objectId: measure.id, trackId: track.id, measureId: measure.id }));
|
|
112
|
+
}
|
|
113
|
+
if (measure.startTick !== expectedStart) {
|
|
114
|
+
issues.push(issue('error', ISSUE_CODES.MEASURE_ORDERING, `Measure ${measure.index} of track "${track.name}" starts at tick ${measure.startTick}, expected ${expectedStart} (immediately after the previous measure).`, { objectId: measure.id, trackId: track.id, measureId: measure.id }));
|
|
115
|
+
}
|
|
116
|
+
expectedStart = measure.startTick + measure.durationTicks;
|
|
117
|
+
});
|
|
118
|
+
return issues;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Sweep-line max concurrent NoteEvent count across the whole track (all
|
|
122
|
+
* measures/voices). Note-off is treated as occurring strictly before a
|
|
123
|
+
* note-on at the same tick, so back-to-back (non-overlapping) notes don't
|
|
124
|
+
* count as simultaneous.
|
|
125
|
+
*/
|
|
126
|
+
function checkTooManySimultaneousNotes(track) {
|
|
127
|
+
const points = [];
|
|
128
|
+
for (const measure of track.measures) {
|
|
129
|
+
for (const voice of measure.voices) {
|
|
130
|
+
for (const event of voice.events) {
|
|
131
|
+
if (!isNoteEvent(event))
|
|
132
|
+
continue;
|
|
133
|
+
points.push({ tick: event.startTick, delta: 1 });
|
|
134
|
+
points.push({ tick: event.startTick + event.durationTicks, delta: -1 });
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
points.sort((a, b) => a.tick - b.tick || a.delta - b.delta);
|
|
139
|
+
let current = 0;
|
|
140
|
+
let max = 0;
|
|
141
|
+
for (const point of points) {
|
|
142
|
+
current += point.delta;
|
|
143
|
+
max = Math.max(max, current);
|
|
144
|
+
}
|
|
145
|
+
if (max > MAX_SIMULTANEOUS_NOTES) {
|
|
146
|
+
return [
|
|
147
|
+
issue('warning', ISSUE_CODES.TOO_MANY_SIMULTANEOUS_NOTES, `Track "${track.name}" has up to ${max} notes sounding simultaneously (readability threshold ${MAX_SIMULTANEOUS_NOTES}).`, { trackId: track.id }),
|
|
148
|
+
];
|
|
149
|
+
}
|
|
150
|
+
return [];
|
|
151
|
+
}
|
|
152
|
+
// ---- Measure-level ------------------------------------------------------------
|
|
153
|
+
/** Time signature (numerator >= 1, denominator in {1,2,4,8,16,32}) and key signature (fifths -7..7). */
|
|
154
|
+
function checkMeasureSignatures(track, measure) {
|
|
155
|
+
const issues = [];
|
|
156
|
+
const { timeSignature, keySignature } = measure;
|
|
157
|
+
if (timeSignature.numerator < 1 ||
|
|
158
|
+
!VALID_TIME_SIG_DENOMINATORS.has(timeSignature.denominator)) {
|
|
159
|
+
issues.push(issue('error', ISSUE_CODES.INVALID_TIME_SIGNATURE, `Measure ${measure.index} has invalid time signature ${timeSignature.numerator}/${timeSignature.denominator} (numerator must be >= 1; denominator must be one of 1, 2, 4, 8, 16, 32).`, { objectId: measure.id, trackId: track.id, measureId: measure.id }));
|
|
160
|
+
}
|
|
161
|
+
if (keySignature.fifths < MIN_FIFTHS || keySignature.fifths > MAX_FIFTHS) {
|
|
162
|
+
issues.push(issue('error', ISSUE_CODES.INVALID_KEY_SIGNATURE, `Measure ${measure.index} has key signature fifths=${keySignature.fifths}, expected ${MIN_FIFTHS}..${MAX_FIFTHS}.`, { objectId: measure.id, trackId: track.id, measureId: measure.id }));
|
|
163
|
+
}
|
|
164
|
+
return issues;
|
|
165
|
+
}
|
|
166
|
+
/** Same-pitch overlap (error) within one voice's note events. Different-pitch overlaps (chords/independent lines) are out of scope for this rule. */
|
|
167
|
+
function checkOverlappingSamePitch(track, measure, voice) {
|
|
168
|
+
const issues = [];
|
|
169
|
+
const notes = voice.events.filter(isNoteEvent);
|
|
170
|
+
for (let i = 0; i < notes.length; i += 1) {
|
|
171
|
+
for (let j = i + 1; j < notes.length; j += 1) {
|
|
172
|
+
const a = notes[i];
|
|
173
|
+
const b = notes[j];
|
|
174
|
+
const overlaps = a.startTick < b.startTick + b.durationTicks &&
|
|
175
|
+
b.startTick < a.startTick + a.durationTicks;
|
|
176
|
+
if (overlaps && samePitch(a.pitch, b.pitch)) {
|
|
177
|
+
issues.push(issue('error', ISSUE_CODES.OVERLAPPING_SAME_PITCH, `Notes ${a.id} and ${b.id} in voice "${voice.name}" (measure ${measure.index}) overlap in time at the same pitch.`, { objectId: b.id, trackId: track.id, measureId: measure.id }));
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return issues;
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Total *covered* ticks of a voice's events, merging overlapping/touching
|
|
185
|
+
* spans into disjoint intervals before summing their lengths. A plain
|
|
186
|
+
* "sum every event's durationTicks" would double-count block chords
|
|
187
|
+
* (multiple `NoteEvent`s deliberately sharing one startTick/duration, as
|
|
188
|
+
* built by `fixtures.ts`'s `buildChordMeasures`/spec §25 voice allocation)
|
|
189
|
+
* as if they were sequential content several times the measure's length.
|
|
190
|
+
* Merging first means a chord (or any simultaneous/overlapping content)
|
|
191
|
+
* counts once, at its own span, matching how the notated timeline actually
|
|
192
|
+
* reads.
|
|
193
|
+
*/
|
|
194
|
+
function coveredTicks(events) {
|
|
195
|
+
const spans = events
|
|
196
|
+
.map(e => ({ start: e.startTick, end: e.startTick + e.durationTicks }))
|
|
197
|
+
.sort((a, b) => a.start - b.start);
|
|
198
|
+
let total = 0;
|
|
199
|
+
let i = 0;
|
|
200
|
+
while (i < spans.length) {
|
|
201
|
+
let end = spans[i].end;
|
|
202
|
+
let j = i + 1;
|
|
203
|
+
while (j < spans.length && spans[j].start <= end) {
|
|
204
|
+
end = Math.max(end, spans[j].end);
|
|
205
|
+
j += 1;
|
|
206
|
+
}
|
|
207
|
+
total += end - spans[i].start;
|
|
208
|
+
i = j;
|
|
209
|
+
}
|
|
210
|
+
return total;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Per-voice checks within a measure: positive duration, nonnegative
|
|
214
|
+
* startTick, containment within the measure, valid track/voice references,
|
|
215
|
+
* note-only velocity/pitch-range checks, same-pitch overlap, and the
|
|
216
|
+
* voice's covered-ticks total vs. the measure's duration (error if it
|
|
217
|
+
* covers more, warning if less).
|
|
218
|
+
*/
|
|
219
|
+
function checkVoice(track, measure, voice) {
|
|
220
|
+
const issues = [];
|
|
221
|
+
for (const event of voice.events) {
|
|
222
|
+
if (event.durationTicks <= 0) {
|
|
223
|
+
issues.push(issue('error', ISSUE_CODES.NON_POSITIVE_DURATION, `Event ${event.id} has non-positive durationTicks (${event.durationTicks}).`, { objectId: event.id, trackId: track.id, measureId: measure.id }));
|
|
224
|
+
}
|
|
225
|
+
if (event.startTick < 0) {
|
|
226
|
+
issues.push(issue('error', ISSUE_CODES.NEGATIVE_TICK, `Event ${event.id} has a negative startTick (${event.startTick}).`, {
|
|
227
|
+
objectId: event.id,
|
|
228
|
+
trackId: track.id,
|
|
229
|
+
measureId: measure.id,
|
|
230
|
+
}));
|
|
231
|
+
}
|
|
232
|
+
if (event.startTick < measure.startTick ||
|
|
233
|
+
event.startTick + event.durationTicks >
|
|
234
|
+
measure.startTick + measure.durationTicks) {
|
|
235
|
+
issues.push(issue('error', ISSUE_CODES.EVENT_OUTSIDE_MEASURE, `Event ${event.id} [${event.startTick}, ${event.startTick + event.durationTicks}) falls outside measure ${measure.index}'s span [${measure.startTick}, ${measure.startTick + measure.durationTicks}).`, { objectId: event.id, trackId: track.id, measureId: measure.id }));
|
|
236
|
+
}
|
|
237
|
+
if (event.trackId !== track.id) {
|
|
238
|
+
issues.push(issue('error', ISSUE_CODES.INVALID_TRACK_REFERENCE, `Event ${event.id} references trackId "${event.trackId}" but lives on track "${track.id}".`, { objectId: event.id, trackId: track.id, measureId: measure.id }));
|
|
239
|
+
}
|
|
240
|
+
if (event.voiceId !== voice.id) {
|
|
241
|
+
issues.push(issue('error', ISSUE_CODES.INVALID_VOICE_REFERENCE, `Event ${event.id} references voiceId "${event.voiceId}" but lives in voice "${voice.id}".`, { objectId: event.id, trackId: track.id, measureId: measure.id }));
|
|
242
|
+
}
|
|
243
|
+
if (isNoteEvent(event)) {
|
|
244
|
+
if (event.velocity < MIN_VELOCITY || event.velocity > MAX_VELOCITY) {
|
|
245
|
+
issues.push(issue('error', ISSUE_CODES.INVALID_VELOCITY, `Note ${event.id} has velocity ${event.velocity}, expected ${MIN_VELOCITY}-${MAX_VELOCITY}.`, { objectId: event.id, trackId: track.id, measureId: measure.id }));
|
|
246
|
+
}
|
|
247
|
+
const midi = pitchToMidi(event.pitch);
|
|
248
|
+
if (midi < MIN_MIDI || midi > MAX_MIDI) {
|
|
249
|
+
issues.push(issue('error', ISSUE_CODES.INVALID_PITCH_RANGE, `Note ${event.id} has pitch MIDI ${midi}, expected ${MIN_MIDI}-${MAX_MIDI}.`, { objectId: event.id, trackId: track.id, measureId: measure.id }));
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
const covered = coveredTicks(voice.events);
|
|
254
|
+
if (covered > measure.durationTicks) {
|
|
255
|
+
issues.push(issue('error', ISSUE_CODES.MEASURE_OVERFULL, `Voice "${voice.name}" in measure ${measure.index} covers ${covered} ticks, exceeding the measure's ${measure.durationTicks}.`, { objectId: voice.id, trackId: track.id, measureId: measure.id }));
|
|
256
|
+
}
|
|
257
|
+
else if (covered < measure.durationTicks) {
|
|
258
|
+
issues.push(issue('warning', ISSUE_CODES.MEASURE_UNDERFULL, `Voice "${voice.name}" in measure ${measure.index} covers ${covered} ticks, short of the measure's ${measure.durationTicks}.`, { objectId: voice.id, trackId: track.id, measureId: measure.id }));
|
|
259
|
+
}
|
|
260
|
+
issues.push(...checkOverlappingSamePitch(track, measure, voice));
|
|
261
|
+
return issues;
|
|
262
|
+
}
|
|
263
|
+
// ---- Ties (whole-score, since a tie may cross a measure boundary) ---------
|
|
264
|
+
/** The most voices any single measure of `track` has (so every voice-ordinal channel is covered). */
|
|
265
|
+
function trackVoiceCount(track) {
|
|
266
|
+
return track.measures.reduce((max, measure) => Math.max(max, measure.voices.length), 0);
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* The first candidate in `index.get(tick)` (if any) that isn't `excludeId`
|
|
270
|
+
* itself, carries `requireFlag`, and shares `pitch` — the same (tick,
|
|
271
|
+
* pitch, tie-flag) matching `domain/score/ties.ts`'s `findForwardPartner`/
|
|
272
|
+
* `findBackwardPartner` use, just against a prebuilt index instead of a
|
|
273
|
+
* fresh linear scan.
|
|
274
|
+
*/
|
|
275
|
+
function findIndexedPartner(index, tick, excludeId, pitch, requireFlag) {
|
|
276
|
+
const candidates = index.get(tick);
|
|
277
|
+
if (!candidates)
|
|
278
|
+
return undefined;
|
|
279
|
+
return candidates.find(c => c.event.id !== excludeId &&
|
|
280
|
+
requireFlag(c) &&
|
|
281
|
+
samePitch(c.event.pitch, pitch));
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* A tieStart note must have a matching tieStop note at the next position
|
|
285
|
+
* (same track/voice-channel, immediately following tick, same pitch) —
|
|
286
|
+
* warning otherwise. Symmetrically, a tieStop note must have a matching
|
|
287
|
+
* tieStart note immediately preceding it.
|
|
288
|
+
*
|
|
289
|
+
* Originally implemented via `tieChainFor` per tied note, which — for a
|
|
290
|
+
* track with many tied notes — rebuilds its voice-ordinal channel
|
|
291
|
+
* (`voiceChainFor`'s `locateVoiceIndex` + `voiceChannel`, each O(track
|
|
292
|
+
* size)) from scratch on *every* call and walks the full backward/forward
|
|
293
|
+
* chain by linear-scanning the channel at each hop: O(n) work per tied
|
|
294
|
+
* note, O(n^2) worst case across a track with O(n) tied notes (flagged in
|
|
295
|
+
* the Task 4 ledger as a risk against spec §29's 20k-note target).
|
|
296
|
+
*
|
|
297
|
+
* This only ever needs to know whether *one specific* note has a forward/
|
|
298
|
+
* backward partner (not the whole chain), so it builds each track/voice-
|
|
299
|
+
* ordinal channel exactly once via `voiceChannel` (same shared convention
|
|
300
|
+
* `tieChainFor` uses — see its doc comment), then indexes that channel by
|
|
301
|
+
* start tick and by end tick (`startTick + durationTicks`) for O(1)-average
|
|
302
|
+
* partner lookups. Net cost: O(n) per track instead of O(n^2). Matching
|
|
303
|
+
* semantics (same "does a same-pitch, correctly-tie-flagged event exist
|
|
304
|
+
* at the adjoining tick, within this voice-ordinal channel" question) are
|
|
305
|
+
* identical to the previous `tieChainFor`-based check, so the exact same
|
|
306
|
+
* set of issues is produced — see `checkTieTargets`'s tests, which cover
|
|
307
|
+
* both directions.
|
|
308
|
+
*/
|
|
309
|
+
function checkTieTargets(score) {
|
|
310
|
+
const issues = [];
|
|
311
|
+
for (const track of score.tracks) {
|
|
312
|
+
const voiceCount = trackVoiceCount(track);
|
|
313
|
+
for (let voiceIndex = 0; voiceIndex < voiceCount; voiceIndex += 1) {
|
|
314
|
+
const channel = voiceChannel(track, voiceIndex);
|
|
315
|
+
if (channel.length === 0)
|
|
316
|
+
continue;
|
|
317
|
+
const byStartTick = new Map();
|
|
318
|
+
const byEndTick = new Map();
|
|
319
|
+
for (const candidate of channel) {
|
|
320
|
+
const start = candidate.event.startTick;
|
|
321
|
+
const end = start + candidate.event.durationTicks;
|
|
322
|
+
const startBucket = byStartTick.get(start);
|
|
323
|
+
if (startBucket)
|
|
324
|
+
startBucket.push(candidate);
|
|
325
|
+
else
|
|
326
|
+
byStartTick.set(start, [candidate]);
|
|
327
|
+
const endBucket = byEndTick.get(end);
|
|
328
|
+
if (endBucket)
|
|
329
|
+
endBucket.push(candidate);
|
|
330
|
+
else
|
|
331
|
+
byEndTick.set(end, [candidate]);
|
|
332
|
+
}
|
|
333
|
+
for (const { event, measureIndex } of channel) {
|
|
334
|
+
if (!event.tieStart && !event.tieStop)
|
|
335
|
+
continue;
|
|
336
|
+
const measureId = track.measures[measureIndex]?.id;
|
|
337
|
+
if (event.tieStart) {
|
|
338
|
+
const targetTick = event.startTick + event.durationTicks;
|
|
339
|
+
const partner = findIndexedPartner(byStartTick, targetTick, event.id, event.pitch, c => Boolean(c.event.tieStop));
|
|
340
|
+
if (!partner) {
|
|
341
|
+
issues.push(issue('warning', ISSUE_CODES.MISSING_TIE_TARGET, `Note ${event.id} has tieStart but no matching tieStop note at the next position with the same pitch.`, { objectId: event.id, trackId: track.id, measureId }));
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
if (event.tieStop) {
|
|
345
|
+
const partner = findIndexedPartner(byEndTick, event.startTick, event.id, event.pitch, c => Boolean(c.event.tieStart));
|
|
346
|
+
if (!partner) {
|
|
347
|
+
issues.push(issue('warning', ISSUE_CODES.MISSING_TIE_TARGET, `Note ${event.id} has tieStop but no matching tieStart note ending at its start tick with the same pitch.`, { objectId: event.id, trackId: track.id, measureId }));
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return issues;
|
|
354
|
+
}
|
|
355
|
+
// ---- Entry point ------------------------------------------------------------
|
|
356
|
+
/** Validates a `Score` against every rule in spec §23 (Task 4 brief scope). Read-only; never mutates `score`. */
|
|
357
|
+
export function validateScore(score) {
|
|
358
|
+
const issues = [];
|
|
359
|
+
issues.push(...checkUniqueIds(score));
|
|
360
|
+
issues.push(...checkTempoMap(score));
|
|
361
|
+
for (const track of score.tracks) {
|
|
362
|
+
issues.push(...checkTrack(track));
|
|
363
|
+
issues.push(...checkMeasureOrdering(track));
|
|
364
|
+
issues.push(...checkTooManySimultaneousNotes(track));
|
|
365
|
+
for (const measure of track.measures) {
|
|
366
|
+
issues.push(...checkMeasureSignatures(track, measure));
|
|
367
|
+
for (const voice of measure.voices) {
|
|
368
|
+
issues.push(...checkVoice(track, measure, voice));
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
issues.push(...checkTieTargets(score));
|
|
373
|
+
return issues;
|
|
374
|
+
}
|
|
375
|
+
//# sourceMappingURL=validator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validator.js","sourceRoot":"","sources":["../../../src/domain/validation/validator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { NoteEvent } from '../../index.js';
|
|
2
|
+
export type AllocateVoicesOptions = {
|
|
3
|
+
maxVoices: number;
|
|
4
|
+
splitPoint?: number;
|
|
5
|
+
};
|
|
6
|
+
export type VoiceGroup = {
|
|
7
|
+
voiceIndex: number;
|
|
8
|
+
staff: 'upper' | 'lower';
|
|
9
|
+
notes: NoteEvent[];
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* Allocates `notes` into voices (rhythm/overlap-based) and staves
|
|
13
|
+
* (pitch-based). A wide chord straddling `splitPoint` (or grand-staff
|
|
14
|
+
* tracks generally) can produce two output groups for the same
|
|
15
|
+
* `voiceIndex` — one per staff — since staff assignment is per-note while
|
|
16
|
+
* voice assignment is per-cluster.
|
|
17
|
+
*/
|
|
18
|
+
export declare function allocateVoices(notes: NoteEvent[], opts: AllocateVoicesOptions): VoiceGroup[];
|
|
19
|
+
//# sourceMappingURL=allocate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"allocate.d.ts","sourceRoot":"","sources":["../../../src/domain/voicing/allocate.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAIhD,MAAM,MAAM,qBAAqB,GAAG;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAE/E,MAAM,MAAM,UAAU,GAAG;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC;IACzB,KAAK,EAAE,SAAS,EAAE,CAAC;CACpB,CAAC;AAuEF;;;;;;GAMG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,SAAS,EAAE,EAClB,IAAI,EAAE,qBAAqB,GAC1B,UAAU,EAAE,CA6Bd"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Basic voice allocation (spec §25): groups simultaneous notes into
|
|
3
|
+
* chords, separates overlapping independent lines into separate voices
|
|
4
|
+
* (preferring minimal voice-hopping), respects a configurable maximum
|
|
5
|
+
* voice count, and assigns notes to a treble/bass staff by pitch for
|
|
6
|
+
* grand-staff tracks. Output need not match professional engraving, but
|
|
7
|
+
* must be readable; a caller may reassign manually afterward. Pure
|
|
8
|
+
* function: never mutates `notes`.
|
|
9
|
+
*/
|
|
10
|
+
import { pitchToMidi } from '../pitch/pitch.js';
|
|
11
|
+
const DEFAULT_SPLIT_POINT = 60; // middle C
|
|
12
|
+
/**
|
|
13
|
+
* Groups `notes` into chronological clusters of notes sharing the exact
|
|
14
|
+
* same `startTick` *and* `durationTicks` — "simultaneous, equal-duration"
|
|
15
|
+
* per spec §25, since only such notes can be notated as one chord. Two
|
|
16
|
+
* simultaneous notes of differing duration are deliberately kept as
|
|
17
|
+
* separate one-note clusters (they can't share a single notated value).
|
|
18
|
+
* Clusters are returned in ascending `startTick` order.
|
|
19
|
+
*/
|
|
20
|
+
function groupChordClusters(notes) {
|
|
21
|
+
const sorted = [...notes].sort((a, b) => a.startTick - b.startTick);
|
|
22
|
+
const order = [];
|
|
23
|
+
const byKey = new Map();
|
|
24
|
+
for (const note of sorted) {
|
|
25
|
+
const key = `${note.startTick}:${note.durationTicks}`;
|
|
26
|
+
let cluster = byKey.get(key);
|
|
27
|
+
if (!cluster) {
|
|
28
|
+
cluster = [];
|
|
29
|
+
byKey.set(key, cluster);
|
|
30
|
+
order.push(key);
|
|
31
|
+
}
|
|
32
|
+
cluster.push(note);
|
|
33
|
+
}
|
|
34
|
+
return order.map(key => byKey.get(key));
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The index of the best voice slot for a cluster starting at `startTick`:
|
|
38
|
+
* among slots free at or before `startTick` (no overlap), the one whose
|
|
39
|
+
* `lastEnd` is largest — i.e. whose previous note ended closest before the
|
|
40
|
+
* new one, minimizing voice-hopping. Opens a fresh slot if none are free
|
|
41
|
+
* and the voice cap hasn't been reached (or none exist yet). Otherwise
|
|
42
|
+
* (at capacity, all busy) reuses the least-overlapping slot (largest
|
|
43
|
+
* `lastEnd`) rather than exceeding `maxVoices`.
|
|
44
|
+
*/
|
|
45
|
+
function chooseVoiceIndex(voices, startTick, maxVoices) {
|
|
46
|
+
let bestFreeIndex = -1;
|
|
47
|
+
for (let i = 0; i < voices.length; i += 1) {
|
|
48
|
+
if (voices[i].lastEnd <= startTick) {
|
|
49
|
+
if (bestFreeIndex === -1 ||
|
|
50
|
+
voices[i].lastEnd > voices[bestFreeIndex].lastEnd) {
|
|
51
|
+
bestFreeIndex = i;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (bestFreeIndex !== -1)
|
|
56
|
+
return bestFreeIndex;
|
|
57
|
+
if (voices.length === 0 || voices.length < maxVoices) {
|
|
58
|
+
return voices.length; // caller pushes a fresh slot at this index
|
|
59
|
+
}
|
|
60
|
+
let leastOverlapIndex = 0;
|
|
61
|
+
for (let i = 1; i < voices.length; i += 1) {
|
|
62
|
+
if (voices[i].lastEnd > voices[leastOverlapIndex].lastEnd)
|
|
63
|
+
leastOverlapIndex = i;
|
|
64
|
+
}
|
|
65
|
+
return leastOverlapIndex;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Allocates `notes` into voices (rhythm/overlap-based) and staves
|
|
69
|
+
* (pitch-based). A wide chord straddling `splitPoint` (or grand-staff
|
|
70
|
+
* tracks generally) can produce two output groups for the same
|
|
71
|
+
* `voiceIndex` — one per staff — since staff assignment is per-note while
|
|
72
|
+
* voice assignment is per-cluster.
|
|
73
|
+
*/
|
|
74
|
+
export function allocateVoices(notes, opts) {
|
|
75
|
+
if (notes.length === 0)
|
|
76
|
+
return [];
|
|
77
|
+
const splitPoint = opts.splitPoint ?? DEFAULT_SPLIT_POINT;
|
|
78
|
+
const clusters = groupChordClusters(notes);
|
|
79
|
+
const voices = [];
|
|
80
|
+
for (const cluster of clusters) {
|
|
81
|
+
const startTick = cluster[0].startTick;
|
|
82
|
+
const endTick = cluster[0].startTick + cluster[0].durationTicks;
|
|
83
|
+
const index = chooseVoiceIndex(voices, startTick, opts.maxVoices);
|
|
84
|
+
if (!voices[index])
|
|
85
|
+
voices[index] = { lastEnd: -Infinity, notes: [] };
|
|
86
|
+
voices[index].notes.push(...cluster);
|
|
87
|
+
voices[index].lastEnd = Math.max(voices[index].lastEnd, endTick);
|
|
88
|
+
}
|
|
89
|
+
const result = [];
|
|
90
|
+
voices.forEach((voice, voiceIndex) => {
|
|
91
|
+
const upper = voice.notes.filter(n => pitchToMidi(n.pitch) >= splitPoint);
|
|
92
|
+
const lower = voice.notes.filter(n => pitchToMidi(n.pitch) < splitPoint);
|
|
93
|
+
if (upper.length > 0)
|
|
94
|
+
result.push({ voiceIndex, staff: 'upper', notes: upper });
|
|
95
|
+
if (lower.length > 0)
|
|
96
|
+
result.push({ voiceIndex, staff: 'lower', notes: lower });
|
|
97
|
+
});
|
|
98
|
+
return result;
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=allocate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"allocate.js","sourceRoot":"","sources":["../../../src/domain/voicing/allocate.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -12,8 +12,15 @@
|
|
|
12
12
|
* 8. Zod schemas for the project API
|
|
13
13
|
* 9. Response envelope + error codes
|
|
14
14
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
15
|
+
* Contains the types and schemas, plus the pure domain primitives both sides
|
|
16
|
+
* of the system need: pitch and tick math, the score factory, quantization,
|
|
17
|
+
* ties and voice allocation.
|
|
18
|
+
*
|
|
19
|
+
* Those primitives used to live in `@sudobility/music_lib`, which put them out
|
|
20
|
+
* of reach of the backend — `music_api` must never depend on `music_lib`, and
|
|
21
|
+
* neither may depend on the other. Anything both sides need lives here or in
|
|
22
|
+
* `@sudobility/music_codecs`; `music_lib` keeps what is frontend-only (store,
|
|
23
|
+
* commands, rendering, playback).
|
|
17
24
|
*/
|
|
18
25
|
import { z } from 'zod';
|
|
19
26
|
export type UUID = string;
|
|
@@ -1697,9 +1704,9 @@ export declare const generationJobKindSchema: z.ZodEnum<{
|
|
|
1697
1704
|
export type GenerationJobStatus = 'queued' | 'running' | 'done' | 'failed' | 'cancelled';
|
|
1698
1705
|
export declare const generationJobStatusSchema: z.ZodEnum<{
|
|
1699
1706
|
failed: "failed";
|
|
1707
|
+
done: "done";
|
|
1700
1708
|
queued: "queued";
|
|
1701
1709
|
running: "running";
|
|
1702
|
-
done: "done";
|
|
1703
1710
|
cancelled: "cancelled";
|
|
1704
1711
|
}>;
|
|
1705
1712
|
/**
|
|
@@ -1750,9 +1757,9 @@ export declare const generationJobSchema: z.ZodObject<{
|
|
|
1750
1757
|
}>;
|
|
1751
1758
|
status: z.ZodEnum<{
|
|
1752
1759
|
failed: "failed";
|
|
1760
|
+
done: "done";
|
|
1753
1761
|
queued: "queued";
|
|
1754
1762
|
running: "running";
|
|
1755
|
-
done: "done";
|
|
1756
1763
|
cancelled: "cancelled";
|
|
1757
1764
|
}>;
|
|
1758
1765
|
createdAt: z.ZodString;
|
|
@@ -2714,4 +2721,39 @@ export declare function successResponse<T>(data: T): ApiResponse<T>;
|
|
|
2714
2721
|
/** Wraps an error message (and optional typed code) in the standard error envelope. */
|
|
2715
2722
|
export declare function errorResponse(message: string, code?: ApiErrorCode): ApiResponse<never>;
|
|
2716
2723
|
export * from './platform/index.js';
|
|
2724
|
+
export * from './domain/pitch/pitch.js';
|
|
2725
|
+
export * from './domain/pitch/transpose.js';
|
|
2726
|
+
export * from './domain/quantization/options.js';
|
|
2727
|
+
export * from './domain/quantization/quantize.js';
|
|
2728
|
+
export * from './domain/score/factory.js';
|
|
2729
|
+
export * from './domain/score/fragment.js';
|
|
2730
|
+
export * from './domain/score/ids.js';
|
|
2731
|
+
export * from './domain/score/queries.js';
|
|
2732
|
+
export * from './domain/score/ties.js';
|
|
2733
|
+
export * from './domain/selection/types.js';
|
|
2734
|
+
export * from './domain/time/durations.js';
|
|
2735
|
+
export * from './domain/time/fraction.js';
|
|
2736
|
+
export * from './domain/time/tempo-map.js';
|
|
2737
|
+
export * from './domain/time/ticks.js';
|
|
2738
|
+
export * from './domain/voicing/allocate.js';
|
|
2739
|
+
export * from './domain/validation/issues.js';
|
|
2740
|
+
export * from './domain/validation/validator.js';
|
|
2741
|
+
export * from './domain/commands/types.js';
|
|
2742
|
+
export * from './domain/commands/reflow.js';
|
|
2743
|
+
export * from './domain/commands/snapshot.js';
|
|
2744
|
+
export * from './domain/commands/structure-commands.js';
|
|
2745
|
+
export * from './domain/commands/track-commands.js';
|
|
2746
|
+
export * from './domain/commands/region-commands.js';
|
|
2747
|
+
export * from './domain/commands/note-commands.js';
|
|
2748
|
+
export * from './domain/commands/edit-commands.js';
|
|
2749
|
+
export * from './domain/commands/relocate-commands.js';
|
|
2750
|
+
export * from './domain/commands/ripple-commands.js';
|
|
2751
|
+
export * from './domain/instruments/gm.js';
|
|
2752
|
+
export * from './domain/instruments/gm-kit.js';
|
|
2753
|
+
export * from './domain/instruments/gm-range.js';
|
|
2754
|
+
export * from './domain/instruments/gm-polyphony.js';
|
|
2755
|
+
export * from './domain/instruments/gm-transposition.js';
|
|
2756
|
+
export * from './domain/instruments/gm-percussion.js';
|
|
2757
|
+
export * from './domain/selection/selection.js';
|
|
2758
|
+
export * from './services/regeneration/controller.js';
|
|
2717
2759
|
//# sourceMappingURL=index.d.ts.map
|