@viritura/score-engine 0.2.1-next.41 → 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 +159 -76
- 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);
|
|
@@ -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;
|
|
@@ -17439,6 +17473,17 @@ function compileNavigation(measures, initialOrder, measureLengths) {
|
|
|
17439
17473
|
|
|
17440
17474
|
// packages/midi/src/navigation/realization.ts
|
|
17441
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
|
+
}
|
|
17442
17487
|
function authoredOnsetInSpan(event, start, end) {
|
|
17443
17488
|
return event.type === "noteOn" && event.scoreBeat !== void 0 && event.scoreBeat >= start - 1e-9 && event.scoreBeat < end - 1e-9;
|
|
17444
17489
|
}
|
|
@@ -17469,7 +17514,7 @@ function spanTempoMap(timeline, index, span, model, outputBeat) {
|
|
|
17469
17514
|
}
|
|
17470
17515
|
return entries;
|
|
17471
17516
|
}
|
|
17472
|
-
function realizeNavigation(timeline, spans) {
|
|
17517
|
+
function realizeNavigation(timeline, spans, crossJumpContinuations = /* @__PURE__ */ new Map()) {
|
|
17473
17518
|
let sourceSpans = spans.map((span, index) => ({
|
|
17474
17519
|
startBeat: timeline.measureStartBeats[index] + span.startBeat,
|
|
17475
17520
|
endBeat: timeline.measureStartBeats[index] + span.endBeat
|
|
@@ -17477,7 +17522,7 @@ function realizeNavigation(timeline, spans) {
|
|
|
17477
17522
|
for (let index = 0; index < spans.length; index++) {
|
|
17478
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;
|
|
17479
17524
|
measureStartBeats.push(outputBeat), tempoMap.push(...spanTempoMap(timeline, index, span, model, outputBeat));
|
|
17480
|
-
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));
|
|
17481
17526
|
for (let event of timeline.events) {
|
|
17482
17527
|
let authoredOnset = authoredOnsetInSpan(event, source.startBeat, source.endBeat);
|
|
17483
17528
|
if (event.time < startTime - 1e-9 && !authoredOnset) {
|
|
@@ -17501,10 +17546,10 @@ function realizeNavigation(timeline, spans) {
|
|
|
17501
17546
|
)
|
|
17502
17547
|
};
|
|
17503
17548
|
events.push(output);
|
|
17504
|
-
let key =
|
|
17549
|
+
let key = navigationNoteKey(event), pending = activeNotes.get(key) ?? [];
|
|
17505
17550
|
pending.push(output), activeNotes.set(key, pending);
|
|
17506
17551
|
} else if (event.type === "noteOff") {
|
|
17507
|
-
let pending = activeNotes.get(
|
|
17552
|
+
let pending = activeNotes.get(navigationNoteKey(event));
|
|
17508
17553
|
if (!pending?.length) continue;
|
|
17509
17554
|
pending.shift(), events.push({ ...event, time: translate(event.time) });
|
|
17510
17555
|
} else event.time < endTime - 1e-9 && events.push({ ...event, time: translate(event.time) });
|
|
@@ -17515,18 +17560,12 @@ function realizeNavigation(timeline, spans) {
|
|
|
17515
17560
|
...Array.from(controllers.values(), (event) => ({ ...event, time: model.timeAtBeat(outputBeat) }))
|
|
17516
17561
|
);
|
|
17517
17562
|
let next = spans[index + 1];
|
|
17518
|
-
|
|
17519
|
-
|
|
17520
|
-
|
|
17521
|
-
|
|
17522
|
-
|
|
17523
|
-
|
|
17524
|
-
velocity: 0,
|
|
17525
|
-
time: model.timeAtBeat(outputBeat + span.endBeat - span.startBeat, "release")
|
|
17526
|
-
});
|
|
17527
|
-
pending.length = 0;
|
|
17528
|
-
}
|
|
17529
|
-
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;
|
|
17530
17569
|
}
|
|
17531
17570
|
let rank = { noteOff: 0, programChange: 1, controlChange: 2, noteOn: 3 };
|
|
17532
17571
|
return events.sort((a, b) => a.time - b.time || rank[a.type] - rank[b.type]), {
|
|
@@ -18202,6 +18241,44 @@ function applyMeasureTechniques(expressions, ctx, state, caps, ratioForStaff = (
|
|
|
18202
18241
|
return { program, muted, ...instrumentKey === void 0 ? {} : { instrumentKey } };
|
|
18203
18242
|
}
|
|
18204
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
|
+
|
|
18205
18282
|
// packages/midi/src/instrumentPlayback/index.ts
|
|
18206
18283
|
init_define_import_meta_env();
|
|
18207
18284
|
function instrumentPart(part, instrument) {
|
|
@@ -18955,7 +19032,15 @@ function generateTimeline(inputScore, options) {
|
|
|
18955
19032
|
let expandedGlobal = playbackGlobalMeasures(score, measureOrder), holdSchedule = suppressCadenzaFermataHolds(
|
|
18956
19033
|
expandedGlobal,
|
|
18957
19034
|
buildHoldSchedule(score, measureOrder, globalMeasures)
|
|
18958
|
-
), { tempoMap } = buildTempoMap(expandedGlobal, holdSchedule), tempoBuild = buildTempoModel(expandedGlobal, holdSchedule), { model, measureStartTimes, measureStartBeats } = tempoBuild, rhythm = compileRhythmProgram(score),
|
|
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);
|
|
18959
19044
|
for (let partIdx = 0; partIdx < score.parts.length; partIdx++)
|
|
18960
19045
|
processPart(
|
|
18961
19046
|
score,
|
|
@@ -19005,7 +19090,7 @@ function generateTimeline(inputScore, options) {
|
|
|
19005
19090
|
measureTimeSignatures,
|
|
19006
19091
|
diagnostics
|
|
19007
19092
|
};
|
|
19008
|
-
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 };
|
|
19009
19094
|
}
|
|
19010
19095
|
function assignPhysicalMidiChannels(events, score) {
|
|
19011
19096
|
let melodicChannels = [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15], lanesByPart = /* @__PURE__ */ new Map();
|
|
@@ -19036,24 +19121,6 @@ function assignPhysicalMidiChannels(events, score) {
|
|
|
19036
19121
|
event.channel = channelByLane.get(event.playbackLaneId ?? `part:${event.partIndex}`) ?? event.channel;
|
|
19037
19122
|
return diagnostics;
|
|
19038
19123
|
}
|
|
19039
|
-
function collectNoteTieTargets(notes, targets) {
|
|
19040
|
-
if (notes) {
|
|
19041
|
-
for (let note of notes)
|
|
19042
|
-
if (note.ties)
|
|
19043
|
-
for (let tie of note.ties)
|
|
19044
|
-
tie.target && targets.add(tie.target);
|
|
19045
|
-
}
|
|
19046
|
-
}
|
|
19047
|
-
function collectTieTargets(content, targets) {
|
|
19048
|
-
for (let item of content)
|
|
19049
|
-
if (item.type === "event")
|
|
19050
|
-
collectNoteTieTargets(item.notes, targets);
|
|
19051
|
-
else if (item.type === "tuplet")
|
|
19052
|
-
collectTieTargets(item.content, targets);
|
|
19053
|
-
else if (item.type === "grace")
|
|
19054
|
-
for (let evt of item.content)
|
|
19055
|
-
collectNoteTieTargets(evt.notes, targets);
|
|
19056
|
-
}
|
|
19057
19124
|
function allocatePartChannels(parts) {
|
|
19058
19125
|
let channels = [], melodicCounter = 0;
|
|
19059
19126
|
for (let part of parts)
|
|
@@ -19081,18 +19148,17 @@ function buildKitAltProgramMap(part) {
|
|
|
19081
19148
|
typeof comp.drumKit == "number" && (map ??= /* @__PURE__ */ new Map()).set(id, comp.drumKit);
|
|
19082
19149
|
return map;
|
|
19083
19150
|
}
|
|
19084
|
-
function
|
|
19085
|
-
let
|
|
19086
|
-
|
|
19087
|
-
|
|
19088
|
-
|
|
19089
|
-
|
|
19090
|
-
|
|
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);
|
|
19091
19158
|
}
|
|
19092
|
-
return tieTargets;
|
|
19093
19159
|
}
|
|
19094
19160
|
function processPart(score, partIdx, options, partChannels, timing, measureOrder, globalMeasures, holdSchedule, impliedDynamics, allEvents) {
|
|
19095
|
-
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(
|
|
19096
19162
|
part,
|
|
19097
19163
|
partIdx,
|
|
19098
19164
|
measureOrder,
|
|
@@ -19108,8 +19174,19 @@ function processPart(score, partIdx, options, partChannels, timing, measureOrder
|
|
|
19108
19174
|
emitDynamicsCc11(lane.envelope, partIdx, channel, allEvents, lane.id);
|
|
19109
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;
|
|
19110
19176
|
for (let expandedIdx = 0; expandedIdx < measureOrder.length; expandedIdx++) {
|
|
19111
|
-
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());
|
|
19112
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
|
+
}
|
|
19113
19190
|
if (!partMeasure) continue;
|
|
19114
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;
|
|
19115
19192
|
instrumentKey !== techniqueState.instrumentKey && (techniqueState = { program: activeProgram, muted: !1, instrumentKey });
|
|
@@ -19125,7 +19202,7 @@ function processPart(score, partIdx, options, partChannels, timing, measureOrder
|
|
|
19125
19202
|
measureStartBeat,
|
|
19126
19203
|
partIndex: partIdx,
|
|
19127
19204
|
channel,
|
|
19128
|
-
|
|
19205
|
+
pendingTieTargets,
|
|
19129
19206
|
pendingTieOffs,
|
|
19130
19207
|
legatoOutIds,
|
|
19131
19208
|
pendingLegato,
|
|
@@ -19259,7 +19336,7 @@ function tryProcessSpecialEvent(ctx, event, measureStartTime, beatOffset, baseVe
|
|
|
19259
19336
|
}
|
|
19260
19337
|
function processNoteEvent(ctx, event, measureStartTime, beatOffset, baseVelocity, tupletRatio) {
|
|
19261
19338
|
ctx.gmProgram = ctx.instrumentAtBeatOffset?.(beatOffset * ctx.staffMeterRatio).program ?? ctx.gmProgram;
|
|
19262
|
-
let { partIndex, channel,
|
|
19339
|
+
let { partIndex, channel, out, timeSig, kitMidiMap, kitAltProgramMap } = ctx, beats = durationBeats4(event.duration) * tupletRatio;
|
|
19263
19340
|
if (!beatInPlayedSpan(beatOffset * ctx.staffMeterRatio, ctx.playedSpan)) return beats;
|
|
19264
19341
|
if (isRest(event))
|
|
19265
19342
|
return flushVoiceLegato(ctx.pendingLegato, ctx.voiceKey, ctx.out), beats;
|
|
@@ -19294,23 +19371,29 @@ function processNoteEvent(ctx, event, measureStartTime, beatOffset, baseVelocity
|
|
|
19294
19371
|
for (let note of event.notes ?? []) {
|
|
19295
19372
|
let midiNote = noteToMidi(note);
|
|
19296
19373
|
if (midiNote < 0 || midiNote > 127) continue;
|
|
19297
|
-
let
|
|
19298
|
-
|
|
19299
|
-
ctx,
|
|
19300
|
-
|
|
19301
|
-
|
|
19302
|
-
|
|
19303
|
-
|
|
19304
|
-
|
|
19305
|
-
|
|
19306
|
-
|
|
19307
|
-
|
|
19308
|
-
|
|
19309
|
-
|
|
19310
|
-
|
|
19311
|
-
|
|
19312
|
-
|
|
19313
|
-
|
|
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);
|
|
19314
19397
|
}
|
|
19315
19398
|
if (kitMidiMap) {
|
|
19316
19399
|
let jitteredStart = Math.max(0, startTime + timingOffset);
|
|
@@ -19344,7 +19427,7 @@ function processNoteEvent(ctx, event, measureStartTime, beatOffset, baseVelocity
|
|
|
19344
19427
|
return beats;
|
|
19345
19428
|
}
|
|
19346
19429
|
function extendOrFinalizeTie(ctx, midiNote, startTime, durationSec, durationScale, hasTieOut) {
|
|
19347
|
-
let { partIndex, channel, pendingTieOffs, out } = ctx, tieKey =
|
|
19430
|
+
let { partIndex, channel, pendingTieOffs, out } = ctx, tieKey = tiePlaybackKey(midiNote, partIndex, ctx.voiceKey), pending = pendingTieOffs.get(tieKey);
|
|
19348
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, {
|
|
19349
19432
|
type: "noteOff",
|
|
19350
19433
|
time: startTime + durationSec,
|
|
@@ -19365,7 +19448,7 @@ function emitFreshNote(ctx, midiNote, startTime, timingOffset, durationSec, dura
|
|
|
19365
19448
|
channel,
|
|
19366
19449
|
...metadata
|
|
19367
19450
|
}), hasTieOut) {
|
|
19368
|
-
let tieKey =
|
|
19451
|
+
let tieKey = tiePlaybackKey(midiNote, partIndex, ctx.voiceKey);
|
|
19369
19452
|
pendingTieOffs.set(tieKey, {
|
|
19370
19453
|
type: "noteOff",
|
|
19371
19454
|
time: startTime + durationSec,
|