@viritura/score-engine 0.2.1-next.40 → 0.2.1-next.42
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/index.js +320 -118
- package/dist/index.js.map +4 -4
- package/dist/wasm/viritura_wasm_bg.wasm +0 -0
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -358,11 +358,41 @@ init_define_import_meta_env();
|
|
|
358
358
|
|
|
359
359
|
// packages/core/src/operations/chordSymbols/modifiers.ts
|
|
360
360
|
init_define_import_meta_env();
|
|
361
|
+
function parseAlteredDegree(text) {
|
|
362
|
+
let match = /^([b#♭♯])(13|11|9|5)/u.exec(text);
|
|
363
|
+
if (match)
|
|
364
|
+
return {
|
|
365
|
+
degree: Number(match[2]),
|
|
366
|
+
alter: match[1] === "b" || match[1] === "\u266D" ? -1 : 1,
|
|
367
|
+
length: match[0].length
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
function finishModifiers(result, inGroup, needsDegree, afterDegree, afterComma) {
|
|
371
|
+
if (!(inGroup || needsDegree || !afterDegree || afterComma))
|
|
372
|
+
return {
|
|
373
|
+
added: [...new Set(result.added)].sort((a, b) => a - b),
|
|
374
|
+
omitted: [...new Set(result.omitted)].sort((a, b) => a - b),
|
|
375
|
+
...result.altered ? {
|
|
376
|
+
altered: result.altered.filter(
|
|
377
|
+
(item, index, items) => items.findIndex((other) => other.degree === item.degree && other.alter === item.alter) === index
|
|
378
|
+
).sort((a, b) => a.degree - b.degree || a.alter - b.alter)
|
|
379
|
+
} : {}
|
|
380
|
+
};
|
|
381
|
+
}
|
|
361
382
|
function parseModifiers(text) {
|
|
362
383
|
let result = { added: [], omitted: [] }, rest = text.trim().toLowerCase();
|
|
363
384
|
if (!rest) return result;
|
|
364
385
|
let operation, needsDegree = !1, afterDegree = !1, inGroup = !1, afterComma = !1;
|
|
365
386
|
for (; rest; ) {
|
|
387
|
+
let altered = parseAlteredDegree(rest);
|
|
388
|
+
if (altered) {
|
|
389
|
+
if (needsDegree) return;
|
|
390
|
+
(result.altered ??= []).push({
|
|
391
|
+
degree: altered.degree,
|
|
392
|
+
alter: altered.alter
|
|
393
|
+
}), operation = void 0, afterDegree = !0, afterComma = !1, rest = rest.slice(altered.length).trimStart();
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
366
396
|
let marker = /^(add|omit|no)/.exec(rest)?.[0];
|
|
367
397
|
if (marker) {
|
|
368
398
|
if (needsDegree) return;
|
|
@@ -386,14 +416,16 @@ function parseModifiers(text) {
|
|
|
386
416
|
return;
|
|
387
417
|
rest = rest.slice(1).trimStart();
|
|
388
418
|
}
|
|
389
|
-
|
|
390
|
-
return {
|
|
391
|
-
added: [...new Set(result.added)].sort((a, b) => a - b),
|
|
392
|
-
omitted: [...new Set(result.omitted)].sort((a, b) => a - b)
|
|
393
|
-
};
|
|
419
|
+
return finishModifiers(result, inGroup, needsDegree, afterDegree, afterComma);
|
|
394
420
|
}
|
|
395
421
|
var ADDED_INTERVALS = { 2: 2, 4: 5, 6: 9, 7: 10, 9: 2, 11: 5, 13: 9 };
|
|
396
422
|
function applyModifiers(degrees, modifiers) {
|
|
423
|
+
let altered = /* @__PURE__ */ new Map();
|
|
424
|
+
for (let { degree, alter } of modifiers?.altered ?? []) {
|
|
425
|
+
let natural = degree === 5 ? 7 : ADDED_INTERVALS[degree];
|
|
426
|
+
altered.set(degree, [...altered.get(degree) ?? [], natural + alter]);
|
|
427
|
+
}
|
|
428
|
+
for (let [degree, intervals] of altered) degrees.set(degree, intervals);
|
|
397
429
|
for (let degree of modifiers?.added ?? [])
|
|
398
430
|
degrees.set(degree, [...degrees.get(degree) ?? [], ADDED_INTERVALS[degree]]);
|
|
399
431
|
for (let degree of modifiers?.omitted ?? []) degrees.delete(degree);
|
|
@@ -582,11 +614,13 @@ function parseBaseQuality(text) {
|
|
|
582
614
|
return extension === void 0 ? { quality } : { quality, extension };
|
|
583
615
|
}
|
|
584
616
|
function parseQuality(text) {
|
|
585
|
-
let
|
|
617
|
+
let complete = parseBaseQuality(text);
|
|
618
|
+
if (complete) return complete;
|
|
619
|
+
let boundary = text.search(/add|omit|no|\(|[b#♭♯](?:13|11|9|5)/iu);
|
|
586
620
|
if (boundary < 0) return parseBaseQuality(text);
|
|
587
621
|
let base = parseBaseQuality(text.slice(0, boundary).trim()), modifiers = parseModifiers(text.slice(boundary));
|
|
588
622
|
if (!(!base || !modifiers))
|
|
589
|
-
return base.quality === "major" && base.extension === void 0 && modifiers.added.length === 1 && modifiers.added[0] === 6 && modifiers.omitted.length === 0 ? { quality: "major", extension: 6 } : { ...base, modifiers };
|
|
623
|
+
return base.quality === "major" && base.extension === void 0 && modifiers.added.length === 1 && modifiers.added[0] === 6 && modifiers.omitted.length === 0 && !modifiers.altered?.length ? { quality: "major", extension: 6 } : { ...base, modifiers };
|
|
590
624
|
}
|
|
591
625
|
function parseChordSymbolText(input, position) {
|
|
592
626
|
let parts = splitChordSymbolText(input);
|
|
@@ -602,9 +636,9 @@ function parseChordSymbolText(input, position) {
|
|
|
602
636
|
|
|
603
637
|
// packages/core/src/operations/lyricExtenders.ts
|
|
604
638
|
init_define_import_meta_env();
|
|
605
|
-
function durationBeats(
|
|
606
|
-
let base = DURATION_BEATS[
|
|
607
|
-
for (let index = 0; index < (
|
|
639
|
+
function durationBeats(duration2) {
|
|
640
|
+
let base = DURATION_BEATS[duration2.base], dots = 1;
|
|
641
|
+
for (let index = 0; index < (duration2.dots ?? 0); index++) dots += 0.5 ** (index + 1);
|
|
608
642
|
return base * dots;
|
|
609
643
|
}
|
|
610
644
|
function quantityBeats(quantity) {
|
|
@@ -614,7 +648,7 @@ function walkTimedContent(content, startBeat, scale, partIndex, sequenceIndex, v
|
|
|
614
648
|
let cursor = startBeat;
|
|
615
649
|
for (let item of content) {
|
|
616
650
|
if (item.type === "event") {
|
|
617
|
-
let
|
|
651
|
+
let duration2 = durationBeats(item.duration) * scale;
|
|
618
652
|
output.push({
|
|
619
653
|
event: item,
|
|
620
654
|
partIndex,
|
|
@@ -623,10 +657,10 @@ function walkTimedContent(content, startBeat, scale, partIndex, sequenceIndex, v
|
|
|
623
657
|
staff,
|
|
624
658
|
measureIndex,
|
|
625
659
|
startBeat: cursor,
|
|
626
|
-
durationBeats:
|
|
660
|
+
durationBeats: duration2,
|
|
627
661
|
rest: isRest(item),
|
|
628
662
|
ordinal: ordinal.value++
|
|
629
|
-
}), cursor +=
|
|
663
|
+
}), cursor += duration2;
|
|
630
664
|
continue;
|
|
631
665
|
}
|
|
632
666
|
if (item.type === "space") {
|
|
@@ -2231,9 +2265,9 @@ function interpolateBeatAnchors(bounds, beat, endX) {
|
|
|
2231
2265
|
|
|
2232
2266
|
// packages/score-engine/src/buildInfo.ts
|
|
2233
2267
|
init_define_import_meta_env();
|
|
2234
|
-
var PACKAGE_VERSION = "0.2.1-next.
|
|
2268
|
+
var PACKAGE_VERSION = "0.2.1-next.42", BUNDLE_WORKER_FILE = "score-engine.worker.js";
|
|
2235
2269
|
function buildCommit() {
|
|
2236
|
-
return "
|
|
2270
|
+
return "addcef3a89be5d1d76a3f918b2aacbdc0dd7c30a";
|
|
2237
2271
|
}
|
|
2238
2272
|
function bundledAssetBase() {
|
|
2239
2273
|
let moduleUrl = import.meta.url;
|
|
@@ -16659,8 +16693,8 @@ function parseVideoMedia(raw) {
|
|
|
16659
16693
|
typeof contentHash == "string" && contentHash.length > 0 && (media.contentHash = contentHash);
|
|
16660
16694
|
let demoSourceId = raw?.demoSourceId;
|
|
16661
16695
|
typeof demoSourceId == "string" && demoSourceId.length > 0 && (media.demoSourceId = demoSourceId);
|
|
16662
|
-
let
|
|
16663
|
-
return typeof
|
|
16696
|
+
let duration2 = raw?.durationSeconds;
|
|
16697
|
+
return typeof duration2 == "number" && Number.isFinite(duration2) && duration2 > 0 && (media.durationSeconds = duration2), media;
|
|
16664
16698
|
}
|
|
16665
16699
|
function parseRootVideoSync(rootX) {
|
|
16666
16700
|
let raw = rootX?.viritura?.videoSync;
|
|
@@ -17153,7 +17187,7 @@ function buildTempoMap(globalMeasures, holds) {
|
|
|
17153
17187
|
if (gm.tempos && gm.tempos.length > 0) {
|
|
17154
17188
|
for (let tempo of gm.tempos) {
|
|
17155
17189
|
if (tempo.bpm === void 0) continue;
|
|
17156
|
-
let beatOffset = tempo.location ? tempo.location.fraction
|
|
17190
|
+
let beatOffset = tempo.location ? posBeats(tempo.location.fraction) : 0, qpm = effectiveQpm(tempo.bpm, tempo.value.base, tempo.value.dots);
|
|
17157
17191
|
changes.push({ beatOffset, qpm });
|
|
17158
17192
|
}
|
|
17159
17193
|
changes.sort((a, b) => a.beatOffset - b.beatOffset);
|
|
@@ -17186,10 +17220,10 @@ function buildTempoModel(globalMeasures, holds) {
|
|
|
17186
17220
|
nominalTime && (currentTime = { count: nominalTime.count, unit: nominalTime.unit });
|
|
17187
17221
|
let mBeats = measureBeatsFromTime(gm.time ?? currentTime);
|
|
17188
17222
|
if (measureStartBeats.push(cumBeats), gm.tempos && gm.tempos.length > 0) {
|
|
17189
|
-
let sorted = [...gm.tempos].sort((a, b) => tempoBeat(a
|
|
17223
|
+
let sorted = [...gm.tempos].sort((a, b) => tempoBeat(a) - tempoBeat(b));
|
|
17190
17224
|
for (let tempo of sorted) {
|
|
17191
17225
|
if (tempo.bpm === void 0) continue;
|
|
17192
|
-
let startBeat = cumBeats + tempoBeat(tempo
|
|
17226
|
+
let startBeat = cumBeats + tempoBeat(tempo), qpm = effectiveQpm(tempo.bpm, tempo.value.base, tempo.value.dots);
|
|
17193
17227
|
regions.push({ startBeat, endBeat: 1 / 0, startBpm: qpm, endBpm: qpm });
|
|
17194
17228
|
}
|
|
17195
17229
|
}
|
|
@@ -17243,8 +17277,8 @@ function resolveGradualEndBeat(end, fromIdx, globalMeasures, measureStartBeats,
|
|
|
17243
17277
|
if (start !== void 0)
|
|
17244
17278
|
return start + posBeats(end.position.fraction);
|
|
17245
17279
|
}
|
|
17246
|
-
function tempoBeat(tempo
|
|
17247
|
-
return tempo.location ? tempo.location.fraction
|
|
17280
|
+
function tempoBeat(tempo) {
|
|
17281
|
+
return tempo.location ? posBeats(tempo.location.fraction) : 0;
|
|
17248
17282
|
}
|
|
17249
17283
|
|
|
17250
17284
|
// packages/midi/src/cadenzaTiming.ts
|
|
@@ -17284,14 +17318,14 @@ function playbackGlobalMeasures(score, measureOrder) {
|
|
|
17284
17318
|
let activeBeats = 4, activeTime = { count: 4, unit: 4 }, activeTempo = {
|
|
17285
17319
|
bpm: DEFAULT_BPM,
|
|
17286
17320
|
value: { base: "quarter" }
|
|
17287
|
-
}, resolved = score.global.measures.map((original, measureIndex) => {
|
|
17288
|
-
original.time && (activeTime = original.time);
|
|
17321
|
+
}, inheritedTempos = [], resolved = score.global.measures.map((original, measureIndex) => {
|
|
17322
|
+
inheritedTempos.push(activeTempo), original.time && (activeTime = original.time);
|
|
17289
17323
|
let measure = {
|
|
17290
17324
|
...original,
|
|
17291
17325
|
time: { ...activeTime },
|
|
17292
|
-
tempos: original.tempos
|
|
17293
|
-
};
|
|
17294
|
-
if (
|
|
17326
|
+
tempos: original.tempos
|
|
17327
|
+
}, metronomicTempos = original.tempos?.filter((tempo) => tempo.bpm !== void 0);
|
|
17328
|
+
if (metronomicTempos?.length && (activeTempo = [...metronomicTempos].sort(
|
|
17295
17329
|
(a, b) => (a.location?.fraction[0] ?? 0) / (a.location?.fraction[1] ?? 1) - (b.location?.fraction[0] ?? 0) / (b.location?.fraction[1] ?? 1)
|
|
17296
17330
|
).at(-1)), measure.time && (activeBeats = measure.time.count * 4 / measure.time.unit), measureIndex === 0 && measure.number === 0) {
|
|
17297
17331
|
let pickupBeats = writtenMeasureBeats(score, measureIndex, 0);
|
|
@@ -17317,7 +17351,13 @@ function playbackGlobalMeasures(score, measureOrder) {
|
|
|
17317
17351
|
}
|
|
17318
17352
|
};
|
|
17319
17353
|
});
|
|
17320
|
-
return measureOrder.map((index) =>
|
|
17354
|
+
return measureOrder.map((index, expandedIndex) => {
|
|
17355
|
+
let measure = resolved[index];
|
|
17356
|
+
return expandedIndex === 0 || index === measureOrder[expandedIndex - 1] + 1 || measure.tempos?.some((tempo) => tempo.bpm !== void 0 && (!tempo.location || tempo.location.fraction[0] === 0)) ? measure : {
|
|
17357
|
+
...measure,
|
|
17358
|
+
tempos: [{ ...inheritedTempos[index], location: { fraction: [0, 1] } }, ...measure.tempos ?? []]
|
|
17359
|
+
};
|
|
17360
|
+
});
|
|
17321
17361
|
}
|
|
17322
17362
|
function suppressCadenzaFermataHolds(measures, holds) {
|
|
17323
17363
|
return holds.map(
|
|
@@ -17433,6 +17473,17 @@ function compileNavigation(measures, initialOrder, measureLengths) {
|
|
|
17433
17473
|
|
|
17434
17474
|
// packages/midi/src/navigation/realization.ts
|
|
17435
17475
|
init_define_import_meta_env();
|
|
17476
|
+
function navigationNoteKey(event) {
|
|
17477
|
+
return `${event.partIndex}:${event.playbackLaneId ?? event.channel}:${event.midiNote}:${event.drumKitProgram ?? ""}`;
|
|
17478
|
+
}
|
|
17479
|
+
function releaseUncontinuedNotes(activeNotes, continuations, time, out) {
|
|
17480
|
+
let continued = new Set(continuations.map(navigationNoteKey));
|
|
17481
|
+
for (let [key, pending] of activeNotes)
|
|
17482
|
+
if (!continued.has(key)) {
|
|
17483
|
+
for (let onset of pending) out.push({ ...onset, type: "noteOff", velocity: 0, time });
|
|
17484
|
+
pending.length = 0;
|
|
17485
|
+
}
|
|
17486
|
+
}
|
|
17436
17487
|
function authoredOnsetInSpan(event, start, end) {
|
|
17437
17488
|
return event.type === "noteOn" && event.scoreBeat !== void 0 && event.scoreBeat >= start - 1e-9 && event.scoreBeat < end - 1e-9;
|
|
17438
17489
|
}
|
|
@@ -17463,7 +17514,7 @@ function spanTempoMap(timeline, index, span, model, outputBeat) {
|
|
|
17463
17514
|
}
|
|
17464
17515
|
return entries;
|
|
17465
17516
|
}
|
|
17466
|
-
function realizeNavigation(timeline, spans) {
|
|
17517
|
+
function realizeNavigation(timeline, spans, crossJumpContinuations = /* @__PURE__ */ new Map()) {
|
|
17467
17518
|
let sourceSpans = spans.map((span, index) => ({
|
|
17468
17519
|
startBeat: timeline.measureStartBeats[index] + span.startBeat,
|
|
17469
17520
|
endBeat: timeline.measureStartBeats[index] + span.endBeat
|
|
@@ -17471,7 +17522,7 @@ function realizeNavigation(timeline, spans) {
|
|
|
17471
17522
|
for (let index = 0; index < spans.length; index++) {
|
|
17472
17523
|
let span = spans[index], source = sourceSpans[index], startTime = timeline.model.timeAtBeat(source.startBeat), endTime = timeline.model.timeAtBeat(source.endBeat), controllers = /* @__PURE__ */ new Map(), segmentStart = events.length;
|
|
17473
17524
|
measureStartBeats.push(outputBeat), tempoMap.push(...spanTempoMap(timeline, index, span, model, outputBeat));
|
|
17474
|
-
let translate = (time) => model.timeAtBeat(outputBeat) + Math.min(endTime - startTime, Math.max(0, time - startTime))
|
|
17525
|
+
let translate = (time) => model.timeAtBeat(outputBeat) + Math.min(endTime - startTime, Math.max(0, time - startTime));
|
|
17475
17526
|
for (let event of timeline.events) {
|
|
17476
17527
|
let authoredOnset = authoredOnsetInSpan(event, source.startBeat, source.endBeat);
|
|
17477
17528
|
if (event.time < startTime - 1e-9 && !authoredOnset) {
|
|
@@ -17495,10 +17546,10 @@ function realizeNavigation(timeline, spans) {
|
|
|
17495
17546
|
)
|
|
17496
17547
|
};
|
|
17497
17548
|
events.push(output);
|
|
17498
|
-
let key =
|
|
17549
|
+
let key = navigationNoteKey(event), pending = activeNotes.get(key) ?? [];
|
|
17499
17550
|
pending.push(output), activeNotes.set(key, pending);
|
|
17500
17551
|
} else if (event.type === "noteOff") {
|
|
17501
|
-
let pending = activeNotes.get(
|
|
17552
|
+
let pending = activeNotes.get(navigationNoteKey(event));
|
|
17502
17553
|
if (!pending?.length) continue;
|
|
17503
17554
|
pending.shift(), events.push({ ...event, time: translate(event.time) });
|
|
17504
17555
|
} else event.time < endTime - 1e-9 && events.push({ ...event, time: translate(event.time) });
|
|
@@ -17509,18 +17560,12 @@ function realizeNavigation(timeline, spans) {
|
|
|
17509
17560
|
...Array.from(controllers.values(), (event) => ({ ...event, time: model.timeAtBeat(outputBeat) }))
|
|
17510
17561
|
);
|
|
17511
17562
|
let next = spans[index + 1];
|
|
17512
|
-
|
|
17513
|
-
|
|
17514
|
-
|
|
17515
|
-
|
|
17516
|
-
|
|
17517
|
-
|
|
17518
|
-
velocity: 0,
|
|
17519
|
-
time: model.timeAtBeat(outputBeat + span.endBeat - span.startBeat, "release")
|
|
17520
|
-
});
|
|
17521
|
-
pending.length = 0;
|
|
17522
|
-
}
|
|
17523
|
-
outputBeat += span.endBeat - span.startBeat;
|
|
17563
|
+
playedSpansContinue(span, next) || releaseUncontinuedNotes(
|
|
17564
|
+
activeNotes,
|
|
17565
|
+
crossJumpContinuations.get(index + 1) ?? [],
|
|
17566
|
+
model.timeAtBeat(outputBeat + span.endBeat - span.startBeat, "release"),
|
|
17567
|
+
events
|
|
17568
|
+
), outputBeat += span.endBeat - span.startBeat;
|
|
17524
17569
|
}
|
|
17525
17570
|
let rank = { noteOff: 0, programChange: 1, controlChange: 2, noteOn: 3 };
|
|
17526
17571
|
return events.sort((a, b) => a.time - b.time || rank[a.type] - rank[b.type]), {
|
|
@@ -17687,9 +17732,9 @@ function compileChordPlayback(score, includeGlobalChords = !0) {
|
|
|
17687
17732
|
function realizeChordPlayback(program, measureOrder, timing) {
|
|
17688
17733
|
let notes = [], pitches = [], startBeat = 0, occurrence = 0, finish = (endBeat) => {
|
|
17689
17734
|
if (endBeat <= startBeat) return;
|
|
17690
|
-
let startTime = timing.model.timeAtBeat(startBeat),
|
|
17735
|
+
let startTime = timing.model.timeAtBeat(startBeat), duration2 = timing.model.timeAtBeat(endBeat) - startTime;
|
|
17691
17736
|
for (let pitch of pitches)
|
|
17692
|
-
notes.push({ id: `${CHORDS_PART_ID}:${occurrence}:${pitch}`, pitch, startTime, duration });
|
|
17737
|
+
notes.push({ id: `${CHORDS_PART_ID}:${occurrence}:${pitch}`, pitch, startTime, duration: duration2 });
|
|
17693
17738
|
occurrence++;
|
|
17694
17739
|
};
|
|
17695
17740
|
for (let expandedIndex = 0; expandedIndex < measureOrder.length; expandedIndex++) {
|
|
@@ -17758,9 +17803,9 @@ var WHOLE_DURATIONS = {
|
|
|
17758
17803
|
"512th": 1 / 512,
|
|
17759
17804
|
"1024th": 1 / 1024
|
|
17760
17805
|
};
|
|
17761
|
-
function durationValue(
|
|
17762
|
-
let base = WHOLE_DURATIONS[
|
|
17763
|
-
for (let dot = 0; dot < (
|
|
17806
|
+
function durationValue(duration2) {
|
|
17807
|
+
let base = WHOLE_DURATIONS[duration2.base] ?? 0, value = base;
|
|
17808
|
+
for (let dot = 0; dot < (duration2.dots ?? 0); dot++) value += base / 2 ** (dot + 1);
|
|
17764
17809
|
return value;
|
|
17765
17810
|
}
|
|
17766
17811
|
function contentInfo(content, scale = 1) {
|
|
@@ -17769,8 +17814,8 @@ function contentInfo(content, scale = 1) {
|
|
|
17769
17814
|
if (item.type === "event") {
|
|
17770
17815
|
let sounding = !!item.notes?.length || !!item.kitNotes?.length;
|
|
17771
17816
|
sounding && (active = !0, onset ??= cursor);
|
|
17772
|
-
let
|
|
17773
|
-
return cursor +=
|
|
17817
|
+
let duration2 = durationValue(item.duration) * scale;
|
|
17818
|
+
return cursor += duration2, {
|
|
17774
17819
|
duration: item.duration,
|
|
17775
17820
|
sounding
|
|
17776
17821
|
};
|
|
@@ -17780,13 +17825,13 @@ function contentInfo(content, scale = 1) {
|
|
|
17780
17825
|
return nested.active && (active = !0, onset ??= cursor + nested.onset), cursor += nested.duration, { type: item.type, inner: item.inner, outer: item.outer, content: nested.signature };
|
|
17781
17826
|
}
|
|
17782
17827
|
if (item.type === "tremolo") {
|
|
17783
|
-
let
|
|
17784
|
-
return item.content.some((event) => !!event.notes?.length || !!event.kitNotes?.length) && (active = !0, onset ??= cursor), cursor +=
|
|
17828
|
+
let duration2 = durationValue(item.outer.duration) * item.outer.multiple * scale;
|
|
17829
|
+
return item.content.some((event) => !!event.notes?.length || !!event.kitNotes?.length) && (active = !0, onset ??= cursor), cursor += duration2, { type: item.type, outer: item.outer, content: item.content.map((event) => event.duration) };
|
|
17785
17830
|
}
|
|
17786
17831
|
if (item.type === "grace") return { type: item.type, content: item.content.map((event) => event.duration) };
|
|
17787
17832
|
if (item.type === "space") {
|
|
17788
|
-
let
|
|
17789
|
-
return cursor +=
|
|
17833
|
+
let duration2 = (item.duration[1] === 0 ? 0 : item.duration[0] / item.duration[1]) * scale;
|
|
17834
|
+
return cursor += duration2, { type: item.type, duration: item.duration };
|
|
17790
17835
|
}
|
|
17791
17836
|
return {};
|
|
17792
17837
|
}), active, onset, duration: cursor };
|
|
@@ -18128,8 +18173,8 @@ function resolveVoiceLegato(a) {
|
|
|
18128
18173
|
if (!bucket || bucket.length === 0) return;
|
|
18129
18174
|
let onset = Math.max(
|
|
18130
18175
|
0,
|
|
18131
|
-
a.model.timeAtBeat(a.measureStartBeat + a.beatOffset) + timingHumanize(a.measureStartTime, a.beatOffset, a.partIndex)
|
|
18132
|
-
), nextDur = a.model.secondsForBeats(a.measureStartBeat + a.beatOffset, a.beats);
|
|
18176
|
+
(a.soundingOnset ?? a.model.timeAtBeat(a.measureStartBeat + a.beatOffset)) + timingHumanize(a.measureStartTime, a.beatOffset, a.partIndex)
|
|
18177
|
+
), nextDur = a.soundingDuration ?? a.model.secondsForBeats(a.measureStartBeat + a.beatOffset, a.beats);
|
|
18133
18178
|
resolveLegato(bucket, a.out, onset, nextDur, a.notes);
|
|
18134
18179
|
}
|
|
18135
18180
|
|
|
@@ -18196,6 +18241,44 @@ function applyMeasureTechniques(expressions, ctx, state, caps, ratioForStaff = (
|
|
|
18196
18241
|
return { program, muted, ...instrumentKey === void 0 ? {} : { instrumentKey } };
|
|
18197
18242
|
}
|
|
18198
18243
|
|
|
18244
|
+
// packages/midi/src/tiePlayback/index.ts
|
|
18245
|
+
init_define_import_meta_env();
|
|
18246
|
+
function tiePlaybackKey(pitch, partIndex, voice) {
|
|
18247
|
+
return `${pitch}:${partIndex}:${voice}`;
|
|
18248
|
+
}
|
|
18249
|
+
function recordTieTargets(state, key, note) {
|
|
18250
|
+
let targets = note.ties?.filter((tie) => tie.lv !== !0 && tie.target !== void 0) ?? [];
|
|
18251
|
+
targets.length ? state.set(key, targets) : state.delete(key);
|
|
18252
|
+
}
|
|
18253
|
+
function tieContinuesTo(state, key, note) {
|
|
18254
|
+
return note.id !== void 0 && (state.get(key)?.some((tie) => tie.target === note.id) ?? !1);
|
|
18255
|
+
}
|
|
18256
|
+
function notesAtBeat(content, target, notes, offset = 0, ratio = 1) {
|
|
18257
|
+
let beat = offset;
|
|
18258
|
+
for (let item of content) {
|
|
18259
|
+
if (item.type === "event")
|
|
18260
|
+
Math.abs(beat - target) < 1e-9 && notes.push(...item.notes ?? []), beat += durationBeats4(item.duration) * ratio;
|
|
18261
|
+
else if (item.type === "tuplet") {
|
|
18262
|
+
let factor = durationBeats4(item.outer.duration) * item.outer.multiple / (durationBeats4(item.inner.duration) * item.inner.multiple);
|
|
18263
|
+
beat = notesAtBeat(item.content, target, notes, beat, ratio * factor);
|
|
18264
|
+
} else item.type === "space" ? beat += item.duration[0] / item.duration[1] * 4 * ratio : item.type === "tremolo" && (Math.abs(beat - target) < 1e-9 && notes.push(...item.content[0]?.notes ?? []), beat += durationBeats4(item.outer.duration) * item.outer.multiple * ratio);
|
|
18265
|
+
if (beat > target + 1e-9) break;
|
|
18266
|
+
}
|
|
18267
|
+
return beat;
|
|
18268
|
+
}
|
|
18269
|
+
function crossJumpContinuationKeys(state, measure, startBeat, partIndex, ratioForStaff) {
|
|
18270
|
+
let keys = /* @__PURE__ */ new Set();
|
|
18271
|
+
for (let [index, sequence] of (measure?.sequences ?? []).entries()) {
|
|
18272
|
+
let notes = [];
|
|
18273
|
+
notesAtBeat(sequence.content, startBeat / ratioForStaff(sequence.staff ?? 1), notes);
|
|
18274
|
+
for (let note of notes) {
|
|
18275
|
+
let key = tiePlaybackKey(pitchToMidi(note.pitch), partIndex, sequence.voice ?? `v${index}`);
|
|
18276
|
+
note.id !== void 0 && state.get(key)?.some((tie) => tie.targetType === "crossJump" && tie.target === note.id) && keys.add(key);
|
|
18277
|
+
}
|
|
18278
|
+
}
|
|
18279
|
+
return keys;
|
|
18280
|
+
}
|
|
18281
|
+
|
|
18199
18282
|
// packages/midi/src/instrumentPlayback/index.ts
|
|
18200
18283
|
init_define_import_meta_env();
|
|
18201
18284
|
function instrumentPart(part, instrument) {
|
|
@@ -18808,6 +18891,107 @@ function laneForScope(program, partIndex, staff, voice) {
|
|
|
18808
18891
|
return program.lanes.get(id) ?? program.lanes.values().next().value;
|
|
18809
18892
|
}
|
|
18810
18893
|
|
|
18894
|
+
// packages/midi/src/rhythmRealization/index.ts
|
|
18895
|
+
init_define_import_meta_env();
|
|
18896
|
+
var EPS3 = 1e-9;
|
|
18897
|
+
function duration(d) {
|
|
18898
|
+
return DURATION_BEATS[d.base] * (d.dots ? 2 - 2 ** -d.dots : 1);
|
|
18899
|
+
}
|
|
18900
|
+
function itemBeats(item) {
|
|
18901
|
+
switch (item.type) {
|
|
18902
|
+
case "event":
|
|
18903
|
+
return duration(item.duration);
|
|
18904
|
+
case "space":
|
|
18905
|
+
return item.duration[0] * 4 / item.duration[1];
|
|
18906
|
+
case "tuplet":
|
|
18907
|
+
case "tremolo":
|
|
18908
|
+
return duration(item.outer.duration) * item.outer.multiple;
|
|
18909
|
+
case "grace":
|
|
18910
|
+
return 0;
|
|
18911
|
+
}
|
|
18912
|
+
}
|
|
18913
|
+
function eligible(item, sequence) {
|
|
18914
|
+
return item?.type === "event" && !item.duration.dots && (item.staff === void 0 || item.staff === (sequence.staff ?? 1)) && item.markings?.tremolo === void 0 && item.markings?.trill === void 0;
|
|
18915
|
+
}
|
|
18916
|
+
function swingAt(anchors, beat) {
|
|
18917
|
+
let result = { mode: "straight" };
|
|
18918
|
+
for (let anchor of anchors) {
|
|
18919
|
+
if (anchor.beat > beat + EPS3) break;
|
|
18920
|
+
result = anchor.swing;
|
|
18921
|
+
}
|
|
18922
|
+
return result;
|
|
18923
|
+
}
|
|
18924
|
+
function containsGrace(content) {
|
|
18925
|
+
return content.some((item) => item.type === "grace" || item.type === "tuplet" && containsGrace(item.content));
|
|
18926
|
+
}
|
|
18927
|
+
function compileSequence(sequence, anchors, metricOffset, measureBeats3, holdBeats) {
|
|
18928
|
+
let timings = /* @__PURE__ */ new Map();
|
|
18929
|
+
if (sequence.fullMeasure || containsGrace(sequence.content)) return timings;
|
|
18930
|
+
let cursor = 0;
|
|
18931
|
+
for (let index = 0; index < sequence.content.length; index++) {
|
|
18932
|
+
let first = sequence.content[index], second = sequence.content[index + 1], swing2 = swingAt(anchors, cursor), beats = itemBeats(first), pairEnd = cursor + 2 * beats;
|
|
18933
|
+
if (swing2.mode === "swing" && eligible(first, sequence) && eligible(second, sequence) && first.duration.base === swing2.subdivision && second.duration.base === swing2.subdivision && Math.abs((cursor + metricOffset) / (2 * beats) - Math.round((cursor + metricOffset) / (2 * beats))) < EPS3 && pairEnd <= measureBeats3 + EPS3 && !holdBeats.some((beat) => beat > cursor + EPS3 && beat < pairEnd - EPS3) && !anchors.some((anchor) => anchor.beat > cursor + EPS3 && anchor.beat < pairEnd - EPS3)) {
|
|
18934
|
+
let split = cursor + 2 * beats * swing2.ratio[0] / (swing2.ratio[0] + swing2.ratio[1]);
|
|
18935
|
+
timings.set(first, { start: cursor, end: split, pairStart: cursor, pairEnd }), timings.set(second, { start: split, end: pairEnd, pairStart: cursor, pairEnd });
|
|
18936
|
+
}
|
|
18937
|
+
cursor += beats;
|
|
18938
|
+
}
|
|
18939
|
+
return timings;
|
|
18940
|
+
}
|
|
18941
|
+
function compileRhythmProgram(score) {
|
|
18942
|
+
let indices = score.global.measures.map((_, index) => index), playbackMeasures = playbackGlobalMeasures(score, indices), holds = buildHoldSchedule(score, indices, score.global.measures), inherited = { mode: "straight" }, anchorsByMeasure = score.global.measures.map((measure) => {
|
|
18943
|
+
let anchors = [{ beat: -1 / 0, swing: inherited }], ordered = (measure.tempos ?? []).map((tempo, index) => ({
|
|
18944
|
+
tempo,
|
|
18945
|
+
index,
|
|
18946
|
+
beat: (tempo.location?.fraction[0] ?? 0) * 4 / (tempo.location?.fraction[1] ?? 1)
|
|
18947
|
+
})).sort((a, b) => a.beat - b.beat || a.index - b.index);
|
|
18948
|
+
for (let { tempo, beat } of ordered)
|
|
18949
|
+
if (tempo.swing) {
|
|
18950
|
+
if (tempo.swing.mode === "swing" && !tempo.swing.ratio.every((value) => Number.isSafeInteger(value) && value > 0))
|
|
18951
|
+
throw new Error("Swing ratios must be positive safe integers.");
|
|
18952
|
+
inherited = tempo.swing, anchors.push({ beat, swing: inherited });
|
|
18953
|
+
}
|
|
18954
|
+
return anchors;
|
|
18955
|
+
}), sequences = /* @__PURE__ */ new Map(), diagnostics = [];
|
|
18956
|
+
return score.parts.forEach((part, partIndex) => {
|
|
18957
|
+
let staffMeters = resolvePartStaffMeterTable(part, score.global.measures);
|
|
18958
|
+
part.measures.forEach((measure, measureIndex) => {
|
|
18959
|
+
let anchors = anchorsByMeasure[measureIndex];
|
|
18960
|
+
if (!anchors) return;
|
|
18961
|
+
let time = resolveActiveTime(score.global.measures, measureIndex), playbackTime = playbackMeasures[measureIndex].time, measureBeats3 = playbackTime.count * 4 / playbackTime.unit, metricOffset = measureIndex === 0 && score.global.measures[0].number === 0 ? time.count * 4 / time.unit - measureBeats3 : 0, diagnosed = /* @__PURE__ */ new Set(), hasSwing = [
|
|
18962
|
+
0,
|
|
18963
|
+
...anchors.map((anchor) => anchor.beat).filter((beat) => beat > 0 && beat < measureBeats3)
|
|
18964
|
+
].some((beat) => swingAt(anchors, beat).mode === "swing");
|
|
18965
|
+
for (let sequence of measure.sequences) {
|
|
18966
|
+
let staff = sequence.staff ?? 1;
|
|
18967
|
+
if (staffMeters[measureIndex]?.has(staff)) {
|
|
18968
|
+
!diagnosed.has(staff) && hasSwing && (diagnostics.push({
|
|
18969
|
+
code: "swing-staff-meter-unsupported",
|
|
18970
|
+
source: { partIndex, measureIndex, staff },
|
|
18971
|
+
message: `Swing left literal in part ${partIndex}, measure ${measureIndex}, staff ${staff}: local staff meters are not supported.`,
|
|
18972
|
+
playbackLaneIds: []
|
|
18973
|
+
}), diagnosed.add(staff));
|
|
18974
|
+
continue;
|
|
18975
|
+
}
|
|
18976
|
+
time.display !== "senzaMisura" && sequences.set(
|
|
18977
|
+
sequence,
|
|
18978
|
+
compileSequence(
|
|
18979
|
+
sequence,
|
|
18980
|
+
anchors,
|
|
18981
|
+
metricOffset,
|
|
18982
|
+
measureBeats3,
|
|
18983
|
+
(holds[measureIndex] ?? []).map((hold) => hold.atBeat)
|
|
18984
|
+
)
|
|
18985
|
+
);
|
|
18986
|
+
}
|
|
18987
|
+
});
|
|
18988
|
+
}), { sequences, diagnostics };
|
|
18989
|
+
}
|
|
18990
|
+
function realizeRhythmEvent(timings, event, start, beats, span) {
|
|
18991
|
+
let timing = timings?.get(event);
|
|
18992
|
+
return !timing || span && (timing.pairStart < span.startBeat - EPS3 || timing.pairEnd > span.endBeat + EPS3) ? { start, beats } : { start: timing.start, beats: timing.end - timing.start };
|
|
18993
|
+
}
|
|
18994
|
+
|
|
18811
18995
|
// packages/midi/src/timeline.ts
|
|
18812
18996
|
var GM_DRUM_CHANNEL = 9;
|
|
18813
18997
|
function noteToMidi(note) {
|
|
@@ -18848,7 +19032,15 @@ function generateTimeline(inputScore, options) {
|
|
|
18848
19032
|
let expandedGlobal = playbackGlobalMeasures(score, measureOrder), holdSchedule = suppressCadenzaFermataHolds(
|
|
18849
19033
|
expandedGlobal,
|
|
18850
19034
|
buildHoldSchedule(score, measureOrder, globalMeasures)
|
|
18851
|
-
), { tempoMap } = buildTempoMap(expandedGlobal, holdSchedule), tempoBuild = buildTempoModel(expandedGlobal, holdSchedule), { model, measureStartTimes, measureStartBeats } = tempoBuild,
|
|
19035
|
+
), { tempoMap } = buildTempoMap(expandedGlobal, holdSchedule), tempoBuild = buildTempoModel(expandedGlobal, holdSchedule), { model, measureStartTimes, measureStartBeats } = tempoBuild, rhythm = compileRhythmProgram(score), crossJumpContinuations = /* @__PURE__ */ new Map(), timing = {
|
|
19036
|
+
tempoMap,
|
|
19037
|
+
measureStartTimes,
|
|
19038
|
+
model,
|
|
19039
|
+
measureStartBeats,
|
|
19040
|
+
playedSpans: spans,
|
|
19041
|
+
rhythm,
|
|
19042
|
+
crossJumpContinuations
|
|
19043
|
+
}, allEvents = [], partChannels = allocatePartChannels(score.parts);
|
|
18852
19044
|
for (let partIdx = 0; partIdx < score.parts.length; partIdx++)
|
|
18853
19045
|
processPart(
|
|
18854
19046
|
score,
|
|
@@ -18876,14 +19068,19 @@ function generateTimeline(inputScore, options) {
|
|
|
18876
19068
|
{ model, measureStartBeats, measureOrder },
|
|
18877
19069
|
options?.instrumentPrograms,
|
|
18878
19070
|
options?.partPrograms
|
|
18879
|
-
), diagnostics = [
|
|
19071
|
+
), diagnostics = [
|
|
19072
|
+
...assignPhysicalMidiChannels(allEvents, score),
|
|
19073
|
+
...instrumentDiagnostics,
|
|
19074
|
+
...rhythm.diagnostics
|
|
19075
|
+
];
|
|
19076
|
+
for (let diagnostic of diagnostics) options?.onDiagnostic?.(diagnostic);
|
|
18880
19077
|
allEvents.sort(compareEvents);
|
|
18881
|
-
let
|
|
19078
|
+
let duration2 = model.timeAtBeat(tempoBuild.totalBeats), measureTimeSignatures = measureOrder.map((origIdx) => {
|
|
18882
19079
|
let ts = resolveActiveTime(globalMeasures, origIdx);
|
|
18883
19080
|
return { count: ts.count, unit: ts.unit };
|
|
18884
19081
|
}), result = {
|
|
18885
19082
|
events: allEvents,
|
|
18886
|
-
duration,
|
|
19083
|
+
duration: duration2,
|
|
18887
19084
|
tempoMap,
|
|
18888
19085
|
measureStartTimes,
|
|
18889
19086
|
model,
|
|
@@ -18893,7 +19090,7 @@ function generateTimeline(inputScore, options) {
|
|
|
18893
19090
|
measureTimeSignatures,
|
|
18894
19091
|
diagnostics
|
|
18895
19092
|
};
|
|
18896
|
-
return spans && (globalMeasures.some((measure) => measure.jump !== void 0) || spans.some((span) => span.startBeat > 0 || span.endBeat < span.measureBeats)) ? realizeNavigation(result, spans) : { ...result, playedSpans: spans };
|
|
19093
|
+
return spans && (globalMeasures.some((measure) => measure.jump !== void 0) || spans.some((span) => span.startBeat > 0 || span.endBeat < span.measureBeats)) ? realizeNavigation(result, spans, crossJumpContinuations) : { ...result, playedSpans: spans };
|
|
18897
19094
|
}
|
|
18898
19095
|
function assignPhysicalMidiChannels(events, score) {
|
|
18899
19096
|
let melodicChannels = [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15], lanesByPart = /* @__PURE__ */ new Map();
|
|
@@ -18924,24 +19121,6 @@ function assignPhysicalMidiChannels(events, score) {
|
|
|
18924
19121
|
event.channel = channelByLane.get(event.playbackLaneId ?? `part:${event.partIndex}`) ?? event.channel;
|
|
18925
19122
|
return diagnostics;
|
|
18926
19123
|
}
|
|
18927
|
-
function collectNoteTieTargets(notes, targets) {
|
|
18928
|
-
if (notes) {
|
|
18929
|
-
for (let note of notes)
|
|
18930
|
-
if (note.ties)
|
|
18931
|
-
for (let tie of note.ties)
|
|
18932
|
-
tie.target && targets.add(tie.target);
|
|
18933
|
-
}
|
|
18934
|
-
}
|
|
18935
|
-
function collectTieTargets(content, targets) {
|
|
18936
|
-
for (let item of content)
|
|
18937
|
-
if (item.type === "event")
|
|
18938
|
-
collectNoteTieTargets(item.notes, targets);
|
|
18939
|
-
else if (item.type === "tuplet")
|
|
18940
|
-
collectTieTargets(item.content, targets);
|
|
18941
|
-
else if (item.type === "grace")
|
|
18942
|
-
for (let evt of item.content)
|
|
18943
|
-
collectNoteTieTargets(evt.notes, targets);
|
|
18944
|
-
}
|
|
18945
19124
|
function allocatePartChannels(parts) {
|
|
18946
19125
|
let channels = [], melodicCounter = 0;
|
|
18947
19126
|
for (let part of parts)
|
|
@@ -18969,18 +19148,17 @@ function buildKitAltProgramMap(part) {
|
|
|
18969
19148
|
typeof comp.drumKit == "number" && (map ??= /* @__PURE__ */ new Map()).set(id, comp.drumKit);
|
|
18970
19149
|
return map;
|
|
18971
19150
|
}
|
|
18972
|
-
function
|
|
18973
|
-
let
|
|
18974
|
-
|
|
18975
|
-
|
|
18976
|
-
|
|
18977
|
-
|
|
18978
|
-
|
|
19151
|
+
function retainCrossJumpNoteOffs(pending, targets, continuing, cutTime, continued, out) {
|
|
19152
|
+
for (let [key, noteOff] of pending) {
|
|
19153
|
+
if (continuing.has(key) && noteOff.time >= cutTime - 1e-7) {
|
|
19154
|
+
continued.push(noteOff);
|
|
19155
|
+
continue;
|
|
19156
|
+
}
|
|
19157
|
+
noteOff.time = Math.min(noteOff.time, cutTime), out.push(noteOff), pending.delete(key), targets.delete(key);
|
|
18979
19158
|
}
|
|
18980
|
-
return tieTargets;
|
|
18981
19159
|
}
|
|
18982
19160
|
function processPart(score, partIdx, options, partChannels, timing, measureOrder, globalMeasures, holdSchedule, impliedDynamics, allEvents) {
|
|
18983
|
-
let { tempoMap, measureStartTimes, model, measureStartBeats } = timing, part = score.parts[partIdx], channel = partChannels[partIdx], gmProgram = options?.partPrograms?.[partIdx] ?? -1, kitMidiMap = buildKitMidiMap(score, part), kitAltProgramMap = buildKitAltProgramMap(part),
|
|
19161
|
+
let { tempoMap, measureStartTimes, model, measureStartBeats } = timing, part = score.parts[partIdx], channel = partChannels[partIdx], gmProgram = options?.partPrograms?.[partIdx] ?? -1, kitMidiMap = buildKitMidiMap(score, part), kitAltProgramMap = buildKitAltProgramMap(part), pendingTieTargets = /* @__PURE__ */ new Map(), pendingTieOffs = /* @__PURE__ */ new Map(), legatoOutIds = collectPartLegatoOut(part), pendingLegato = /* @__PURE__ */ new Map(), staffMeterTable = resolvePartStaffMeterTable(part, globalMeasures), dynamicProgram = compileDynamicProgram(
|
|
18984
19162
|
part,
|
|
18985
19163
|
partIdx,
|
|
18986
19164
|
measureOrder,
|
|
@@ -18996,8 +19174,19 @@ function processPart(score, partIdx, options, partChannels, timing, measureOrder
|
|
|
18996
19174
|
emitDynamicsCc11(lane.envelope, partIdx, channel, allEvents, lane.id);
|
|
18997
19175
|
let techniqueState = { program: gmProgram, muted: !1, instrumentKey: "initial" }, instruments = new Map(playbackInstruments(part).map(({ key, part: instrumentPart2 }) => [key, instrumentPart2])), programForInstrument = (key) => key === "initial" ? gmProgram : options?.instrumentPrograms?.get(partIdx)?.get(key) ?? instruments.get(key)?._x?.viritura?.midiProgram ?? -1;
|
|
18998
19176
|
for (let expandedIdx = 0; expandedIdx < measureOrder.length; expandedIdx++) {
|
|
18999
|
-
expandedIdx > 0 && timing.playedSpans && !playedSpansContinue(timing.playedSpans[expandedIdx - 1], timing.playedSpans[expandedIdx]) && (allEvents.push(...pendingTieOffs.values(), ...Array.from(pendingLegato.values()).flat()), pendingTieOffs.clear(), pendingLegato.clear());
|
|
19000
19177
|
let origMeasureIdx = measureOrder[expandedIdx], partMeasure = part.measures[origMeasureIdx];
|
|
19178
|
+
if (expandedIdx > 0 && timing.playedSpans && !playedSpansContinue(timing.playedSpans[expandedIdx - 1], timing.playedSpans[expandedIdx])) {
|
|
19179
|
+
let continuing = crossJumpContinuationKeys(
|
|
19180
|
+
pendingTieTargets,
|
|
19181
|
+
partMeasure,
|
|
19182
|
+
timing.playedSpans[expandedIdx].startBeat,
|
|
19183
|
+
partIdx,
|
|
19184
|
+
(staff) => staffMeterRatioAt(staffMeterTable, origMeasureIdx, staff)
|
|
19185
|
+
), cutTime = model.timeAtBeat(
|
|
19186
|
+
measureStartBeats[expandedIdx - 1] + timing.playedSpans[expandedIdx - 1].endBeat
|
|
19187
|
+
), continued = timing.crossJumpContinuations.get(expandedIdx) ?? [];
|
|
19188
|
+
retainCrossJumpNoteOffs(pendingTieOffs, pendingTieTargets, continuing, cutTime, continued, allEvents), timing.crossJumpContinuations.set(expandedIdx, continued), allEvents.push(...Array.from(pendingLegato.values()).flat()), pendingLegato.clear();
|
|
19189
|
+
}
|
|
19001
19190
|
if (!partMeasure) continue;
|
|
19002
19191
|
let measureStartTime = measureStartTimes[expandedIdx], measureStartBeat = measureStartBeats[expandedIdx], instrumentKey = playbackInstrumentKeyAt(part, origMeasureIdx), activeProgram = programForInstrument(instrumentKey), bowCapable = ARCO_CAPABLE_PROGRAMS.has(activeProgram), muteFamily = muteFamilyForProgram(activeProgram), techniqueEnabled = bowCapable || muteFamily !== null;
|
|
19003
19192
|
instrumentKey !== techniqueState.instrumentKey && (techniqueState = { program: activeProgram, muted: !1, instrumentKey });
|
|
@@ -19013,7 +19202,7 @@ function processPart(score, partIdx, options, partChannels, timing, measureOrder
|
|
|
19013
19202
|
measureStartBeat,
|
|
19014
19203
|
partIndex: partIdx,
|
|
19015
19204
|
channel,
|
|
19016
|
-
|
|
19205
|
+
pendingTieTargets,
|
|
19017
19206
|
pendingTieOffs,
|
|
19018
19207
|
legatoOutIds,
|
|
19019
19208
|
pendingLegato,
|
|
@@ -19056,7 +19245,7 @@ function processPart(score, partIdx, options, partChannels, timing, measureOrder
|
|
|
19056
19245
|
let lane = laneForSequence(dynamicProgram, partIdx, seq, seqIdx);
|
|
19057
19246
|
ctx.sequenceStaff = seq.staff ?? 1, ctx.sequenceVoice = lane.voice, ctx.dynamicsEnvelope = lane.envelope, ctx.playbackLaneId = lane.id;
|
|
19058
19247
|
let effectiveStaffMeter = staffMeterTable[origMeasureIdx]?.get(ctx.sequenceStaff);
|
|
19059
|
-
ctx.timeSig = effectiveStaffMeter ? effectiveStaffMeter.timeSignature : activeTime, ctx.staffMeterRatio = staffMeterRatioAt(staffMeterTable, origMeasureIdx, ctx.sequenceStaff);
|
|
19248
|
+
ctx.timeSig = effectiveStaffMeter ? effectiveStaffMeter.timeSignature : activeTime, ctx.staffMeterRatio = staffMeterRatioAt(staffMeterTable, origMeasureIdx, ctx.sequenceStaff), ctx.rhythm = timing.rhythm.sequences.get(seq);
|
|
19060
19249
|
let firstNewEvent = allEvents.length;
|
|
19061
19250
|
processSequence(ctx, seq, measureStartTime, measureHolds, measureSpq), stampPlaybackLane(allEvents, firstNewEvent, lane.id), stampPendingLane(ctx, lane.id);
|
|
19062
19251
|
});
|
|
@@ -19093,7 +19282,11 @@ function processScopedNoteEvent(ctx, event, measureStartTime, beatOffset, tuplet
|
|
|
19093
19282
|
event,
|
|
19094
19283
|
measureStartTime,
|
|
19095
19284
|
beatOffset,
|
|
19096
|
-
dynamicVelocityAt(
|
|
19285
|
+
dynamicVelocityAt(
|
|
19286
|
+
ctx,
|
|
19287
|
+
measureStartTime,
|
|
19288
|
+
realizeRhythmEvent(ctx.rhythm, event, beatOffset, durationBeats4(event.duration) * tupletRatio, ctx.playedSpan).start
|
|
19289
|
+
),
|
|
19097
19290
|
tupletRatio
|
|
19098
19291
|
);
|
|
19099
19292
|
return stampPlaybackLane(ctx.out, firstNewEvent, lane.id), stampPendingLane(ctx, lane.id), ctx.dynamicsEnvelope = previousEnvelope, ctx.playbackLaneId = previousLaneId, beats;
|
|
@@ -19120,13 +19313,13 @@ function processContentItem(ctx, item, measureStartTime, beatOffset, tupletRatio
|
|
|
19120
19313
|
return 0;
|
|
19121
19314
|
}
|
|
19122
19315
|
}
|
|
19123
|
-
function resolveFermataDuration(ctx, event, naturalDurationSec, beatOffset) {
|
|
19316
|
+
function resolveFermataDuration(ctx, event, naturalDurationSec, beatOffset, soundingBeatOffset = beatOffset) {
|
|
19124
19317
|
if (event.fermata && event.fermata.duration !== "none" && ctx.fermataGroups.length > 0) {
|
|
19125
19318
|
let globalBeatOffset = beatOffset * ctx.staffMeterRatio, group = ctx.fermataGroups.find(
|
|
19126
19319
|
(g) => globalBeatOffset >= g.startBeat - 1e-9 && globalBeatOffset < g.spanEndBeat - 1e-9
|
|
19127
19320
|
);
|
|
19128
19321
|
if (group) {
|
|
19129
|
-
let startTime = eventTime(ctx,
|
|
19322
|
+
let startTime = eventTime(ctx, soundingBeatOffset), resumeTime = ctx.model.timeAtBeat(ctx.measureStartBeat + group.insertBeat);
|
|
19130
19323
|
return { durationSec: Math.max(naturalDurationSec, resumeTime - startTime), durationScale: 1, held: !0 };
|
|
19131
19324
|
}
|
|
19132
19325
|
}
|
|
@@ -19143,10 +19336,11 @@ function tryProcessSpecialEvent(ctx, event, measureStartTime, beatOffset, baseVe
|
|
|
19143
19336
|
}
|
|
19144
19337
|
function processNoteEvent(ctx, event, measureStartTime, beatOffset, baseVelocity, tupletRatio) {
|
|
19145
19338
|
ctx.gmProgram = ctx.instrumentAtBeatOffset?.(beatOffset * ctx.staffMeterRatio).program ?? ctx.gmProgram;
|
|
19146
|
-
let { partIndex, channel,
|
|
19339
|
+
let { partIndex, channel, out, timeSig, kitMidiMap, kitAltProgramMap } = ctx, beats = durationBeats4(event.duration) * tupletRatio;
|
|
19147
19340
|
if (!beatInPlayedSpan(beatOffset * ctx.staffMeterRatio, ctx.playedSpan)) return beats;
|
|
19148
19341
|
if (isRest(event))
|
|
19149
19342
|
return flushVoiceLegato(ctx.pendingLegato, ctx.voiceKey, ctx.out), beats;
|
|
19343
|
+
let realized = realizeRhythmEvent(ctx.rhythm, event, beatOffset, beats, ctx.playedSpan), startTime = eventTime(ctx, realized.start), naturalDurationSec = eventSeconds(ctx, realized.start, realized.beats);
|
|
19150
19344
|
resolveVoiceLegato({
|
|
19151
19345
|
map: ctx.pendingLegato,
|
|
19152
19346
|
voiceKey: ctx.voiceKey,
|
|
@@ -19157,15 +19351,17 @@ function processNoteEvent(ctx, event, measureStartTime, beatOffset, baseVelocity
|
|
|
19157
19351
|
measureStartTime,
|
|
19158
19352
|
beatOffset,
|
|
19159
19353
|
beats,
|
|
19160
|
-
notes: event.notes ?? []
|
|
19354
|
+
notes: event.notes ?? [],
|
|
19355
|
+
soundingOnset: startTime,
|
|
19356
|
+
soundingDuration: naturalDurationSec
|
|
19161
19357
|
});
|
|
19162
19358
|
let specialBeats = tryProcessSpecialEvent(ctx, event, measureStartTime, beatOffset, baseVelocity, beats);
|
|
19163
19359
|
if (specialBeats !== null) return specialBeats;
|
|
19164
|
-
let
|
|
19360
|
+
let timingOffset = timingHumanize(measureStartTime, beatOffset, partIndex), {
|
|
19165
19361
|
durationSec,
|
|
19166
19362
|
durationScale,
|
|
19167
19363
|
held: fermataHeld
|
|
19168
|
-
} = resolveFermataDuration(ctx, event, naturalDurationSec, beatOffset), velocity = Math.min(
|
|
19364
|
+
} = resolveFermataDuration(ctx, event, naturalDurationSec, beatOffset, realized.start), velocity = Math.min(
|
|
19169
19365
|
127,
|
|
19170
19366
|
Math.max(
|
|
19171
19367
|
1,
|
|
@@ -19175,23 +19371,29 @@ function processNoteEvent(ctx, event, measureStartTime, beatOffset, baseVelocity
|
|
|
19175
19371
|
for (let note of event.notes ?? []) {
|
|
19176
19372
|
let midiNote = noteToMidi(note);
|
|
19177
19373
|
if (midiNote < 0 || midiNote > 127) continue;
|
|
19178
|
-
let
|
|
19179
|
-
|
|
19180
|
-
ctx,
|
|
19181
|
-
|
|
19182
|
-
|
|
19183
|
-
|
|
19184
|
-
|
|
19185
|
-
|
|
19186
|
-
|
|
19187
|
-
|
|
19188
|
-
|
|
19189
|
-
|
|
19190
|
-
|
|
19191
|
-
|
|
19192
|
-
|
|
19193
|
-
|
|
19194
|
-
|
|
19374
|
+
let key = tiePlaybackKey(midiNote, partIndex, ctx.voiceKey), isTiedTo = tieContinuesTo(ctx.pendingTieTargets, key, note) && ctx.pendingTieOffs.has(key), hasTieOut = note.ties?.some((t) => t.lv !== !0 && t.target) ?? !1;
|
|
19375
|
+
if (isTiedTo)
|
|
19376
|
+
extendOrFinalizeTie(ctx, midiNote, startTime, durationSec, durationScale, hasTieOut);
|
|
19377
|
+
else {
|
|
19378
|
+
let previous = ctx.pendingTieOffs.get(key);
|
|
19379
|
+
previous && (out.push(previous), ctx.pendingTieOffs.delete(key)), emitFreshNote(
|
|
19380
|
+
ctx,
|
|
19381
|
+
midiNote,
|
|
19382
|
+
startTime,
|
|
19383
|
+
timingOffset,
|
|
19384
|
+
durationSec,
|
|
19385
|
+
durationScale,
|
|
19386
|
+
velocity,
|
|
19387
|
+
hasTieOut,
|
|
19388
|
+
legatoOut,
|
|
19389
|
+
{
|
|
19390
|
+
scoreBeat: ctx.measureStartBeat + beatOffset * ctx.staffMeterRatio,
|
|
19391
|
+
scoreDurationBeats: beats * ctx.staffMeterRatio,
|
|
19392
|
+
scoreEventId: event.id
|
|
19393
|
+
}
|
|
19394
|
+
);
|
|
19395
|
+
}
|
|
19396
|
+
recordTieTargets(ctx.pendingTieTargets, key, note);
|
|
19195
19397
|
}
|
|
19196
19398
|
if (kitMidiMap) {
|
|
19197
19399
|
let jitteredStart = Math.max(0, startTime + timingOffset);
|
|
@@ -19225,7 +19427,7 @@ function processNoteEvent(ctx, event, measureStartTime, beatOffset, baseVelocity
|
|
|
19225
19427
|
return beats;
|
|
19226
19428
|
}
|
|
19227
19429
|
function extendOrFinalizeTie(ctx, midiNote, startTime, durationSec, durationScale, hasTieOut) {
|
|
19228
|
-
let { partIndex, channel, pendingTieOffs, out } = ctx, tieKey =
|
|
19430
|
+
let { partIndex, channel, pendingTieOffs, out } = ctx, tieKey = tiePlaybackKey(midiNote, partIndex, ctx.voiceKey), pending = pendingTieOffs.get(tieKey);
|
|
19229
19431
|
pending && (hasTieOut ? pending.time = startTime + durationSec : (pending.time = startTime + durationSec * durationScale, out.push(pending), pendingTieOffs.delete(tieKey))), hasTieOut && !pendingTieOffs.has(tieKey) && pendingTieOffs.set(tieKey, {
|
|
19230
19432
|
type: "noteOff",
|
|
19231
19433
|
time: startTime + durationSec,
|
|
@@ -19246,7 +19448,7 @@ function emitFreshNote(ctx, midiNote, startTime, timingOffset, durationSec, dura
|
|
|
19246
19448
|
channel,
|
|
19247
19449
|
...metadata
|
|
19248
19450
|
}), hasTieOut) {
|
|
19249
|
-
let tieKey =
|
|
19451
|
+
let tieKey = tiePlaybackKey(midiNote, partIndex, ctx.voiceKey);
|
|
19250
19452
|
pendingTieOffs.set(tieKey, {
|
|
19251
19453
|
type: "noteOff",
|
|
19252
19454
|
time: startTime + durationSec,
|