@viritura/score-engine 0.1.1-next.28 → 0.1.1-next.31
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/CHANGELOG.md +17 -0
- package/dist/index.js +789 -555
- 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
|
@@ -2213,9 +2213,9 @@ function interpolateBeatAnchors(bounds, beat, endX) {
|
|
|
2213
2213
|
|
|
2214
2214
|
// packages/score-engine/src/buildInfo.ts
|
|
2215
2215
|
init_define_import_meta_env();
|
|
2216
|
-
var PACKAGE_VERSION = "0.1.1-next.
|
|
2216
|
+
var PACKAGE_VERSION = "0.1.1-next.31", BUNDLE_WORKER_FILE = "score-engine.worker.js";
|
|
2217
2217
|
function buildCommit() {
|
|
2218
|
-
return "
|
|
2218
|
+
return "90b89f13680a7855b46725024057cc9919dfbc20";
|
|
2219
2219
|
}
|
|
2220
2220
|
function bundledAssetBase() {
|
|
2221
2221
|
let moduleUrl = import.meta.url;
|
|
@@ -15714,6 +15714,8 @@ var KEYS = {
|
|
|
15714
15714
|
"ending",
|
|
15715
15715
|
"tempos",
|
|
15716
15716
|
"segno",
|
|
15717
|
+
"coda",
|
|
15718
|
+
"caesura",
|
|
15717
15719
|
"fine",
|
|
15718
15720
|
"jump",
|
|
15719
15721
|
"_x"
|
|
@@ -15777,7 +15779,7 @@ var KEYS = {
|
|
|
15777
15779
|
eventLyricLine: /* @__PURE__ */ new Set(["type", "text", "extender"]),
|
|
15778
15780
|
eventLyricLineExtender: /* @__PURE__ */ new Set(["show", "target", "id"]),
|
|
15779
15781
|
rest: /* @__PURE__ */ new Set(["staffPosition"]),
|
|
15780
|
-
note: /* @__PURE__ */ new Set(["pitch", "id", "ties", "accidentalDisplay", "written"]),
|
|
15782
|
+
note: /* @__PURE__ */ new Set(["pitch", "id", "ties", "accidentalDisplay", "written", "markings"]),
|
|
15781
15783
|
pitch: /* @__PURE__ */ new Set(["step", "octave", "alter"]),
|
|
15782
15784
|
tie: /* @__PURE__ */ new Set(["target", "targetType", "side", "lv"]),
|
|
15783
15785
|
accidentalDisplay: /* @__PURE__ */ new Set(["show", "force", "enclosure"]),
|
|
@@ -15797,6 +15799,7 @@ var KEYS = {
|
|
|
15797
15799
|
"stress",
|
|
15798
15800
|
"unstress",
|
|
15799
15801
|
"breath",
|
|
15802
|
+
"caesura",
|
|
15800
15803
|
"bowDirection",
|
|
15801
15804
|
"_x"
|
|
15802
15805
|
]),
|
|
@@ -16109,6 +16112,8 @@ function scanGlobalMeasure(gm, p, dx) {
|
|
|
16109
16112
|
});
|
|
16110
16113
|
let segno = gm.segno;
|
|
16111
16114
|
segno && emitUnknown(segno, KEYS.segno, p + ptr("segno"), dx);
|
|
16115
|
+
let coda = gm.coda;
|
|
16116
|
+
coda && emitUnknown(coda, KEYS.segno, p + ptr("coda"), dx);
|
|
16112
16117
|
let fine = gm.fine;
|
|
16113
16118
|
fine && emitUnknown(fine, KEYS.fine, p + ptr("fine"), dx);
|
|
16114
16119
|
let jump = gm.jump;
|
|
@@ -16239,19 +16244,575 @@ init_define_import_meta_env();
|
|
|
16239
16244
|
|
|
16240
16245
|
// packages/midi/src/repeatExpansion.ts
|
|
16241
16246
|
init_define_import_meta_env();
|
|
16242
|
-
|
|
16243
|
-
|
|
16244
|
-
|
|
16245
|
-
|
|
16247
|
+
|
|
16248
|
+
// packages/midi/src/cadenzaTiming.ts
|
|
16249
|
+
init_define_import_meta_env();
|
|
16250
|
+
|
|
16251
|
+
// packages/midi/src/tempoMap.ts
|
|
16252
|
+
init_define_import_meta_env();
|
|
16253
|
+
|
|
16254
|
+
// packages/midi/src/tempoModel.ts
|
|
16255
|
+
init_define_import_meta_env();
|
|
16256
|
+
var bpmToSpb = (bpm) => 60 / bpm;
|
|
16257
|
+
function integrateTime(b0, b, bpm0, slope) {
|
|
16258
|
+
let db = b - b0;
|
|
16259
|
+
if (Math.abs(slope) < 1e-6) return bpmToSpb(bpm0) * db;
|
|
16260
|
+
let bpmB = bpm0 + slope * db;
|
|
16261
|
+
return 60 / slope * Math.log(bpmB / bpm0);
|
|
16246
16262
|
}
|
|
16247
|
-
function
|
|
16248
|
-
|
|
16249
|
-
|
|
16250
|
-
|
|
16251
|
-
|
|
16252
|
-
|
|
16263
|
+
function inverseBeat(dt, bpm0, slope) {
|
|
16264
|
+
return Math.abs(slope) < 1e-6 ? dt * bpm0 / 60 : (bpm0 * Math.exp(dt * slope / 60) - bpm0) / slope;
|
|
16265
|
+
}
|
|
16266
|
+
var TempoModel = class _TempoModel {
|
|
16267
|
+
knots;
|
|
16268
|
+
constructor(knots) {
|
|
16269
|
+
this.knots = knots;
|
|
16270
|
+
}
|
|
16271
|
+
/** Realize selected source-beat intervals on one continuous performance axis. */
|
|
16272
|
+
static concatenate(source, spans) {
|
|
16273
|
+
let regions = [], insertions = [], outputBeat = 0;
|
|
16274
|
+
for (let span of spans) {
|
|
16275
|
+
let boundaries = [
|
|
16276
|
+
span.startBeat,
|
|
16277
|
+
...source.knots.filter((knot) => knot.beat > span.startBeat && knot.beat < span.endBeat).map((knot) => knot.beat),
|
|
16278
|
+
span.endBeat
|
|
16279
|
+
];
|
|
16280
|
+
for (let index = 0; index < boundaries.length - 1; index++) {
|
|
16281
|
+
let start = boundaries[index], end = boundaries[index + 1], knot = source.knots[source.knotIndexForBeat(start)], bpm = knot.bpm + knot.slope * (start - knot.beat);
|
|
16282
|
+
regions.push({
|
|
16283
|
+
startBeat: outputBeat + start - span.startBeat,
|
|
16284
|
+
endBeat: outputBeat + end - span.startBeat,
|
|
16285
|
+
startBpm: bpm,
|
|
16286
|
+
endBpm: bpm + knot.slope * (end - start)
|
|
16287
|
+
});
|
|
16288
|
+
}
|
|
16289
|
+
for (let knot of source.knots)
|
|
16290
|
+
knot.beat > span.startBeat && knot.beat <= span.endBeat && knot.insertBefore > 0 && insertions.push({ beat: outputBeat + knot.beat - span.startBeat, seconds: knot.insertBefore });
|
|
16291
|
+
outputBeat += span.endBeat - span.startBeat;
|
|
16292
|
+
}
|
|
16293
|
+
let last = regions.at(-1);
|
|
16294
|
+
return last && regions.push({ startBeat: outputBeat, endBeat: 1 / 0, startBpm: last.endBpm, endBpm: last.endBpm }), _TempoModel.build(regions, insertions);
|
|
16295
|
+
}
|
|
16296
|
+
/**
|
|
16297
|
+
* Build a model from tempo regions and point insertions.
|
|
16298
|
+
*
|
|
16299
|
+
* `regions` need not be pre-sorted or gapless; they are sorted by `startBeat`
|
|
16300
|
+
* and gaps are filled by extending the previous tempo (a held constant). The
|
|
16301
|
+
* first region's start tempo applies from beat 0. `insertions` are point gaps
|
|
16302
|
+
* (caesuras) keyed by beat.
|
|
16303
|
+
*/
|
|
16304
|
+
static build(regions, insertions = []) {
|
|
16305
|
+
let sorted = [...regions].sort((a, b) => a.startBeat - b.startBeat), insByBeat = /* @__PURE__ */ new Map();
|
|
16306
|
+
for (let ins of insertions)
|
|
16307
|
+
ins.seconds > 0 && insByBeat.set(ins.beat, (insByBeat.get(ins.beat) ?? 0) + ins.seconds);
|
|
16308
|
+
let boundarySet = /* @__PURE__ */ new Set([0]);
|
|
16309
|
+
for (let r of sorted) boundarySet.add(r.startBeat);
|
|
16310
|
+
for (let ins of insByBeat.keys()) boundarySet.add(ins);
|
|
16311
|
+
let boundaries = [...boundarySet].sort((a, b) => a - b), tempoAt = (beat) => {
|
|
16312
|
+
let active;
|
|
16313
|
+
for (let r of sorted)
|
|
16314
|
+
if (r.startBeat <= beat + 1e-9) active = r;
|
|
16315
|
+
else break;
|
|
16316
|
+
if (!active) {
|
|
16317
|
+
let first = sorted[0];
|
|
16318
|
+
return { bpm: first ? first.startBpm : 120, slope: 0 };
|
|
16319
|
+
}
|
|
16320
|
+
let span = active.endBeat - active.startBeat, slope = span > 0 && Number.isFinite(span) ? (active.endBpm - active.startBpm) / span : 0;
|
|
16321
|
+
return { bpm: active.startBpm + slope * (beat - active.startBeat), slope };
|
|
16322
|
+
}, knots = [], time = 0, prevBeat = boundaries[0], prev = tempoAt(prevBeat), ins0 = insByBeat.get(prevBeat) ?? 0;
|
|
16323
|
+
time += ins0, knots.push({ beat: prevBeat, time, bpm: prev.bpm, slope: prev.slope, insertBefore: ins0 });
|
|
16324
|
+
for (let i = 1; i < boundaries.length; i++) {
|
|
16325
|
+
let beat = boundaries[i];
|
|
16326
|
+
time += integrateTime(prevBeat, beat, prev.bpm, prev.slope);
|
|
16327
|
+
let ins = insByBeat.get(beat) ?? 0;
|
|
16328
|
+
time += ins;
|
|
16329
|
+
let t = tempoAt(beat);
|
|
16330
|
+
knots.push({ beat, time, bpm: t.bpm, slope: t.slope, insertBefore: ins }), prevBeat = beat, prev = t;
|
|
16331
|
+
}
|
|
16332
|
+
return new _TempoModel(knots);
|
|
16333
|
+
}
|
|
16334
|
+
/** Index of the last knot whose beat ≤ `beat`. */
|
|
16335
|
+
knotIndexForBeat(beat) {
|
|
16336
|
+
let k = this.knots, lo = 0, hi = k.length - 1, ans = 0;
|
|
16337
|
+
for (; lo <= hi; ) {
|
|
16338
|
+
let mid = lo + hi >> 1;
|
|
16339
|
+
k[mid].beat <= beat + 1e-9 ? (ans = mid, lo = mid + 1) : hi = mid - 1;
|
|
16340
|
+
}
|
|
16341
|
+
return ans;
|
|
16342
|
+
}
|
|
16343
|
+
/** Index of the last knot whose time ≤ `time`. */
|
|
16344
|
+
knotIndexForTime(time) {
|
|
16345
|
+
let k = this.knots, lo = 0, hi = k.length - 1, ans = 0;
|
|
16346
|
+
for (; lo <= hi; ) {
|
|
16347
|
+
let mid = lo + hi >> 1;
|
|
16348
|
+
k[mid].time <= time + 1e-9 ? (ans = mid, lo = mid + 1) : hi = mid - 1;
|
|
16349
|
+
}
|
|
16350
|
+
return ans;
|
|
16351
|
+
}
|
|
16352
|
+
/**
|
|
16353
|
+
* Absolute time (seconds) at a global quarter-note beat.
|
|
16354
|
+
*
|
|
16355
|
+
* `endpoint` selects how a point insertion EXACTLY at `beat` is treated:
|
|
16356
|
+
* - `"onset"` (default): time AFTER the insertion — what an event STARTING at
|
|
16357
|
+
* `beat` wants (it begins once the gap is over).
|
|
16358
|
+
* - `"release"`: time BEFORE the insertion — what a note ENDING at `beat`
|
|
16359
|
+
* wants (a caesura/gap at the release point isn't part of the note).
|
|
16360
|
+
*/
|
|
16361
|
+
timeAtBeat(beat, endpoint = "onset") {
|
|
16362
|
+
let i = this.knotIndexForBeat(beat), knot = this.knots[i], base = knot.time + integrateTime(knot.beat, beat, knot.bpm, knot.slope);
|
|
16363
|
+
return endpoint === "release" && Math.abs(knot.beat - beat) < 1e-9 ? base - knot.insertBefore : base;
|
|
16364
|
+
}
|
|
16365
|
+
/**
|
|
16366
|
+
* Seconds elapsed over `[beat, beat + beats)` — a note/event DURATION. The end
|
|
16367
|
+
* uses release semantics so a point insertion (caesura) exactly at the release
|
|
16368
|
+
* beat is not absorbed into the note's sounding length.
|
|
16369
|
+
*/
|
|
16370
|
+
secondsForBeats(beat, beats) {
|
|
16371
|
+
return this.timeAtBeat(beat + beats, "release") - this.timeAtBeat(beat);
|
|
16372
|
+
}
|
|
16373
|
+
/**
|
|
16374
|
+
* Global quarter-note beat at an absolute time (inverse of {@link timeAtBeat}).
|
|
16375
|
+
* Within a point-insertion gap, returns the insertion's beat (position is
|
|
16376
|
+
* frozen during a caesura).
|
|
16377
|
+
*/
|
|
16378
|
+
beatAtTime(time) {
|
|
16379
|
+
let i = this.knotIndexForTime(time), knot = this.knots[i], next = this.knots[i + 1];
|
|
16380
|
+
if (next) {
|
|
16381
|
+
let musicEnd = next.time - next.insertBefore;
|
|
16382
|
+
if (time >= musicEnd - 1e-9) return next.beat;
|
|
16253
16383
|
}
|
|
16254
|
-
|
|
16384
|
+
return knot.beat + inverseBeat(time - knot.time, knot.bpm, knot.slope);
|
|
16385
|
+
}
|
|
16386
|
+
/** Seconds-per-quarter-note beat at a global beat. */
|
|
16387
|
+
spbAtBeat(beat) {
|
|
16388
|
+
let i = this.knotIndexForBeat(beat), knot = this.knots[i], bpmHere = knot.bpm + knot.slope * (beat - knot.beat);
|
|
16389
|
+
return bpmToSpb(bpmHere);
|
|
16390
|
+
}
|
|
16391
|
+
/** Quarter-note BPM at a global beat. */
|
|
16392
|
+
bpmAtBeat(beat) {
|
|
16393
|
+
return 60 / this.spbAtBeat(beat);
|
|
16394
|
+
}
|
|
16395
|
+
/**
|
|
16396
|
+
* Generate click times (seconds) for a metronome / studio click track over
|
|
16397
|
+
* `[startBeat, endBeat)`. `clickBeats` are the beat positions to click,
|
|
16398
|
+
* expressed as offsets within each "grid step" — but for the common case
|
|
16399
|
+
* callers pass a flat list of global beat positions and we just map them.
|
|
16400
|
+
* Returns the absolute time of every beat in `beats`.
|
|
16401
|
+
*/
|
|
16402
|
+
clickTimes(beats) {
|
|
16403
|
+
return beats.map((b) => this.timeAtBeat(b));
|
|
16404
|
+
}
|
|
16405
|
+
};
|
|
16406
|
+
|
|
16407
|
+
// packages/midi/src/tempoMap.ts
|
|
16408
|
+
function playbackNominalTime(gm) {
|
|
16409
|
+
return gm.__playbackNominalTime;
|
|
16410
|
+
}
|
|
16411
|
+
var DEFAULT_BPM = 120, DEFAULT_TIME = { count: 4, unit: 4 };
|
|
16412
|
+
function measureBeatsFromTime(ts) {
|
|
16413
|
+
return ts.count * 4 / ts.unit;
|
|
16414
|
+
}
|
|
16415
|
+
function tempoNoteBeats(base, dots) {
|
|
16416
|
+
let baseBeats = DURATION_BEATS[base] ?? 1;
|
|
16417
|
+
return dots ? baseBeats * (2 - Math.pow(2, -dots)) : baseBeats;
|
|
16418
|
+
}
|
|
16419
|
+
function effectiveQpm(bpm, tempoBase, tempoDots) {
|
|
16420
|
+
return bpm * tempoNoteBeats(tempoBase, tempoDots);
|
|
16421
|
+
}
|
|
16422
|
+
function buildTempoMap(globalMeasures, holds) {
|
|
16423
|
+
let tempoMap = [], measureStartTimes = [], currentQpm = DEFAULT_BPM, currentSpq = 60 / currentQpm, currentTime = { count: DEFAULT_TIME.count, unit: DEFAULT_TIME.unit }, absoluteTime = 0, hasInitialTempo = !1;
|
|
16424
|
+
for (let m = 0; m < globalMeasures.length; m++) {
|
|
16425
|
+
let gm = globalMeasures[m], nominalTime = playbackNominalTime(gm) ?? gm.time;
|
|
16426
|
+
nominalTime && (currentTime = { count: nominalTime.count, unit: nominalTime.unit });
|
|
16427
|
+
let mBeats = measureBeatsFromTime(gm.time ?? currentTime);
|
|
16428
|
+
measureStartTimes.push(absoluteTime);
|
|
16429
|
+
let measureStartSpq = currentSpq, changes = [];
|
|
16430
|
+
if (gm.tempos && gm.tempos.length > 0) {
|
|
16431
|
+
for (let tempo of gm.tempos) {
|
|
16432
|
+
let beatOffset = tempo.location ? tempo.location.fraction[0] / tempo.location.fraction[1] * mBeats : 0, qpm = effectiveQpm(tempo.bpm, tempo.value.base, tempo.value.dots);
|
|
16433
|
+
changes.push({ beatOffset, qpm });
|
|
16434
|
+
}
|
|
16435
|
+
changes.sort((a, b) => a.beatOffset - b.beatOffset);
|
|
16436
|
+
}
|
|
16437
|
+
let beatCursor = 0;
|
|
16438
|
+
for (let change of changes)
|
|
16439
|
+
change.beatOffset > beatCursor && (absoluteTime += (change.beatOffset - beatCursor) * currentSpq, beatCursor = change.beatOffset), tempoMap.push({
|
|
16440
|
+
measureIndex: m,
|
|
16441
|
+
beatInMeasure: change.beatOffset,
|
|
16442
|
+
timeSeconds: absoluteTime,
|
|
16443
|
+
bpm: change.qpm
|
|
16444
|
+
}), m === 0 && change.beatOffset === 0 && (hasInitialTempo = !0), currentQpm = change.qpm, currentSpq = 60 / currentQpm;
|
|
16445
|
+
mBeats > beatCursor && (absoluteTime += (mBeats - beatCursor) * currentSpq);
|
|
16446
|
+
let measureHolds = holds?.[m];
|
|
16447
|
+
if (measureHolds)
|
|
16448
|
+
for (let hold of measureHolds)
|
|
16449
|
+
absoluteTime += hold.extraBeats * measureStartSpq;
|
|
16450
|
+
}
|
|
16451
|
+
return hasInitialTempo || tempoMap.unshift({
|
|
16452
|
+
measureIndex: 0,
|
|
16453
|
+
beatInMeasure: 0,
|
|
16454
|
+
timeSeconds: 0,
|
|
16455
|
+
bpm: DEFAULT_BPM
|
|
16456
|
+
}), { tempoMap, measureStartTimes };
|
|
16457
|
+
}
|
|
16458
|
+
function buildTempoModel(globalMeasures, holds) {
|
|
16459
|
+
let regions = [], measureStartBeats = [], cumBeats = 0, currentTime = { count: DEFAULT_TIME.count, unit: DEFAULT_TIME.unit }, gradualDecls = [];
|
|
16460
|
+
for (let m = 0; m < globalMeasures.length; m++) {
|
|
16461
|
+
let gm = globalMeasures[m], nominalTime = playbackNominalTime(gm) ?? gm.time;
|
|
16462
|
+
nominalTime && (currentTime = { count: nominalTime.count, unit: nominalTime.unit });
|
|
16463
|
+
let mBeats = measureBeatsFromTime(gm.time ?? currentTime);
|
|
16464
|
+
if (measureStartBeats.push(cumBeats), gm.tempos && gm.tempos.length > 0) {
|
|
16465
|
+
let sorted = [...gm.tempos].sort((a, b) => tempoBeat(a, mBeats) - tempoBeat(b, mBeats));
|
|
16466
|
+
for (let tempo of sorted) {
|
|
16467
|
+
let startBeat = cumBeats + tempoBeat(tempo, mBeats), qpm = effectiveQpm(tempo.bpm, tempo.value.base, tempo.value.dots);
|
|
16468
|
+
regions.push({ startBeat, endBeat: 1 / 0, startBpm: qpm, endBpm: qpm });
|
|
16469
|
+
}
|
|
16470
|
+
}
|
|
16471
|
+
gm.gradualTempo && gradualDecls.push({
|
|
16472
|
+
declIdx: m,
|
|
16473
|
+
gt: gm.gradualTempo,
|
|
16474
|
+
startBeat: cumBeats + posBeats(gm.gradualTempo.position.fraction)
|
|
16475
|
+
}), cumBeats += mBeats;
|
|
16476
|
+
}
|
|
16477
|
+
if ((regions.length === 0 || regions.every((r) => r.startBeat > 1e-9)) && regions.unshift({ startBeat: 0, endBeat: 1 / 0, startBpm: DEFAULT_BPM, endBpm: DEFAULT_BPM }), gradualDecls.length > 0) {
|
|
16478
|
+
let idToIdx = buildMeasureIdToIndex(globalMeasures), constModel = TempoModel.build(regions);
|
|
16479
|
+
for (let { declIdx, gt, startBeat } of gradualDecls) {
|
|
16480
|
+
let endBeat = resolveGradualEndBeat(gt.end, declIdx, globalMeasures, measureStartBeats, idToIdx);
|
|
16481
|
+
if (endBeat === void 0 || endBeat <= startBeat + 1e-9) continue;
|
|
16482
|
+
let startBpm = gt.startBpm ?? constModel.bpmAtBeat(startBeat);
|
|
16483
|
+
regions.push({ startBeat, endBeat, startBpm, endBpm: gt.endBpm }), regions.some((r) => Math.abs(r.startBeat - endBeat) < 1e-9) || regions.push({ startBeat: endBeat, endBeat: 1 / 0, startBpm: gt.endBpm, endBpm: gt.endBpm });
|
|
16484
|
+
}
|
|
16485
|
+
}
|
|
16486
|
+
regions.sort((a, b) => a.startBeat - b.startBeat);
|
|
16487
|
+
for (let i = 0; i < regions.length - 1; i++)
|
|
16488
|
+
regions[i].endBeat = regions[i + 1].startBeat;
|
|
16489
|
+
let regionsModel = TempoModel.build(regions), insertions = [];
|
|
16490
|
+
if (holds)
|
|
16491
|
+
for (let m = 0; m < holds.length; m++) {
|
|
16492
|
+
let measureHolds = holds[m];
|
|
16493
|
+
if (!measureHolds || measureHolds.length === 0) continue;
|
|
16494
|
+
let startBeat = measureStartBeats[m], spb = regionsModel.spbAtBeat(startBeat);
|
|
16495
|
+
for (let hold of measureHolds)
|
|
16496
|
+
insertions.push({ beat: startBeat + hold.atBeat, seconds: hold.extraBeats * spb });
|
|
16497
|
+
}
|
|
16498
|
+
let model = insertions.length > 0 ? TempoModel.build(regions, insertions) : regionsModel, measureStartTimes = measureStartBeats.map((b) => model.timeAtBeat(b));
|
|
16499
|
+
return { model, measureStartTimes, measureStartBeats, totalBeats: cumBeats };
|
|
16500
|
+
}
|
|
16501
|
+
function posBeats(frac) {
|
|
16502
|
+
return !frac || frac[1] === 0 ? 0 : frac[0] / frac[1] * 4;
|
|
16503
|
+
}
|
|
16504
|
+
function buildMeasureIdToIndex(globalMeasures) {
|
|
16505
|
+
let m = /* @__PURE__ */ new Map();
|
|
16506
|
+
return globalMeasures.forEach((gm, i) => {
|
|
16507
|
+
gm.id && !m.has(gm.id) && m.set(gm.id, i);
|
|
16508
|
+
}), m;
|
|
16509
|
+
}
|
|
16510
|
+
function resolveGradualEndBeat(end, fromIdx, globalMeasures, measureStartBeats, idToIdx) {
|
|
16511
|
+
let idx = idToIdx.get(end.measure);
|
|
16512
|
+
if (idx === void 0) {
|
|
16513
|
+
let parsed = Number.parseInt(end.measure, 10);
|
|
16514
|
+
!Number.isNaN(parsed) && parsed >= 0 && parsed < globalMeasures.length && (idx = parsed);
|
|
16515
|
+
}
|
|
16516
|
+
idx === void 0 && (idx = fromIdx);
|
|
16517
|
+
let start = measureStartBeats[idx];
|
|
16518
|
+
if (start !== void 0)
|
|
16519
|
+
return start + posBeats(end.position.fraction);
|
|
16520
|
+
}
|
|
16521
|
+
function tempoBeat(tempo, mBeats) {
|
|
16522
|
+
return tempo.location ? tempo.location.fraction[0] / tempo.location.fraction[1] * mBeats : 0;
|
|
16523
|
+
}
|
|
16524
|
+
|
|
16525
|
+
// packages/midi/src/cadenzaTiming.ts
|
|
16526
|
+
function contentBeats(content) {
|
|
16527
|
+
let beats = 0;
|
|
16528
|
+
for (let item of content)
|
|
16529
|
+
switch (item.type) {
|
|
16530
|
+
case "event":
|
|
16531
|
+
beats += durationBeats2(item.duration.base, item.duration.dots);
|
|
16532
|
+
break;
|
|
16533
|
+
case "space":
|
|
16534
|
+
beats += item.duration[0] / item.duration[1] * 4;
|
|
16535
|
+
break;
|
|
16536
|
+
case "tuplet":
|
|
16537
|
+
case "tremolo":
|
|
16538
|
+
beats += durationBeats2(item.outer.duration.base, item.outer.duration.dots) * item.outer.multiple;
|
|
16539
|
+
break;
|
|
16540
|
+
case "grace":
|
|
16541
|
+
break;
|
|
16542
|
+
}
|
|
16543
|
+
return beats;
|
|
16544
|
+
}
|
|
16545
|
+
function durationBeats2(base, dots) {
|
|
16546
|
+
let beats = DURATION_BEATS[base];
|
|
16547
|
+
return dots ? beats * (2 - Math.pow(2, -dots)) : beats;
|
|
16548
|
+
}
|
|
16549
|
+
function writtenMeasureBeats(score, measureIndex, minimumBeats) {
|
|
16550
|
+
return score.parts.reduce((maxBeats, part) => {
|
|
16551
|
+
let measure = part.measures[measureIndex];
|
|
16552
|
+
return measure ? Math.max(
|
|
16553
|
+
maxBeats,
|
|
16554
|
+
...measure.sequences.filter((sequence) => !sequence.fullMeasure).map((sequence) => contentBeats(sequence.content))
|
|
16555
|
+
) : maxBeats;
|
|
16556
|
+
}, minimumBeats);
|
|
16557
|
+
}
|
|
16558
|
+
function playbackGlobalMeasures(score, measureOrder) {
|
|
16559
|
+
let activeBeats = 4, activeTime = { count: 4, unit: 4 }, activeTempo = {
|
|
16560
|
+
bpm: DEFAULT_BPM,
|
|
16561
|
+
value: { base: "quarter" }
|
|
16562
|
+
}, resolved = score.global.measures.map((original, measureIndex) => {
|
|
16563
|
+
original.time && (activeTime = original.time);
|
|
16564
|
+
let measure = {
|
|
16565
|
+
...original,
|
|
16566
|
+
time: { ...activeTime },
|
|
16567
|
+
tempos: original.tempos?.some((tempo) => !tempo.location || tempo.location.fraction[0] === 0) ? original.tempos : [{ ...activeTempo, location: { fraction: [0, 1] } }, ...original.tempos ?? []]
|
|
16568
|
+
};
|
|
16569
|
+
if (original.tempos?.length && (activeTempo = [...original.tempos].sort(
|
|
16570
|
+
(a, b) => (a.location?.fraction[0] ?? 0) / (a.location?.fraction[1] ?? 1) - (b.location?.fraction[0] ?? 0) / (b.location?.fraction[1] ?? 1)
|
|
16571
|
+
).at(-1)), measure.time && (activeBeats = measure.time.count * 4 / measure.time.unit), measureIndex === 0 && measure.number === 0) {
|
|
16572
|
+
let pickupBeats = writtenMeasureBeats(score, measureIndex, 0);
|
|
16573
|
+
if (pickupBeats > 0)
|
|
16574
|
+
return {
|
|
16575
|
+
...measure,
|
|
16576
|
+
__playbackNominalTime: measure.time ? { ...measure.time } : void 0,
|
|
16577
|
+
time: {
|
|
16578
|
+
...measure.time,
|
|
16579
|
+
count: pickupBeats,
|
|
16580
|
+
unit: 4
|
|
16581
|
+
}
|
|
16582
|
+
};
|
|
16583
|
+
}
|
|
16584
|
+
if (measure.time?.display !== "senzaMisura") return measure;
|
|
16585
|
+
let cadenzaBeats = writtenMeasureBeats(score, measureIndex, activeBeats);
|
|
16586
|
+
return {
|
|
16587
|
+
...measure,
|
|
16588
|
+
time: {
|
|
16589
|
+
...measure.time,
|
|
16590
|
+
count: cadenzaBeats,
|
|
16591
|
+
unit: 4
|
|
16592
|
+
}
|
|
16593
|
+
};
|
|
16594
|
+
});
|
|
16595
|
+
return measureOrder.map((index) => resolved[index]);
|
|
16596
|
+
}
|
|
16597
|
+
function suppressCadenzaFermataHolds(measures, holds) {
|
|
16598
|
+
return holds.map(
|
|
16599
|
+
(measureHolds, index) => measures[index]?.time?.display === "senzaMisura" ? measureHolds.filter((hold) => hold.kind !== "fermata") : [...measureHolds]
|
|
16600
|
+
);
|
|
16601
|
+
}
|
|
16602
|
+
|
|
16603
|
+
// packages/midi/src/navigation/index.ts
|
|
16604
|
+
init_define_import_meta_env();
|
|
16605
|
+
|
|
16606
|
+
// packages/midi/src/navigation/itinerary.ts
|
|
16607
|
+
init_define_import_meta_env();
|
|
16608
|
+
function beatInPlayedSpan(beat, span) {
|
|
16609
|
+
return span === void 0 || beat >= span.startBeat - 1e-9 && (beat < span.endBeat - 1e-9 || span.endBeat === span.measureBeats && Math.abs(beat - span.endBeat) < 1e-9);
|
|
16610
|
+
}
|
|
16611
|
+
function playedSpansContinue(span, next) {
|
|
16612
|
+
return next !== void 0 && span.endBeat === span.measureBeats && next.startBeat === 0 && next.measureIndex === span.measureIndex + 1;
|
|
16613
|
+
}
|
|
16614
|
+
var NavigationError = class extends Error {
|
|
16615
|
+
constructor(message, pointers) {
|
|
16616
|
+
super(`${message} (${pointers.join(", ")})`);
|
|
16617
|
+
this.pointers = pointers;
|
|
16618
|
+
this.name = "NavigationError";
|
|
16619
|
+
}
|
|
16620
|
+
pointers;
|
|
16621
|
+
}, EPS = 1e-9, MAX_SPANS = 1e5;
|
|
16622
|
+
function locationBeat(position, length, pointer) {
|
|
16623
|
+
let [numerator, denominator] = position.fraction, beat = numerator * 4 / denominator;
|
|
16624
|
+
if (!Number.isFinite(beat) || denominator <= 0 || beat < 0 || beat > length + EPS)
|
|
16625
|
+
throw new NavigationError("Navigation location is outside its measure", [pointer]);
|
|
16626
|
+
return Math.min(beat, length);
|
|
16627
|
+
}
|
|
16628
|
+
function resolveDestinations(measures, measureLengths) {
|
|
16629
|
+
let destinations = (kind) => measures.flatMap((measure, measureIndex) => {
|
|
16630
|
+
let marker = measure[kind];
|
|
16631
|
+
return marker ? [
|
|
16632
|
+
{
|
|
16633
|
+
measureIndex,
|
|
16634
|
+
beat: locationBeat(
|
|
16635
|
+
marker.location,
|
|
16636
|
+
measureLengths[measureIndex],
|
|
16637
|
+
`/global/measures/${measureIndex}/${kind}/location`
|
|
16638
|
+
)
|
|
16639
|
+
}
|
|
16640
|
+
] : [];
|
|
16641
|
+
}), markers = { segno: destinations("segno"), coda: destinations("coda"), fine: destinations("fine") }, required = (kind, source) => {
|
|
16642
|
+
let found = markers[kind];
|
|
16643
|
+
if (found.length !== 1)
|
|
16644
|
+
throw new NavigationError(`Navigation requires exactly one ${kind} destination`, [
|
|
16645
|
+
`/global/measures/${source}/jump`,
|
|
16646
|
+
...found.map(({ measureIndex }) => `/global/measures/${measureIndex}/${kind}`)
|
|
16647
|
+
]);
|
|
16648
|
+
return found[0];
|
|
16649
|
+
};
|
|
16650
|
+
for (let index = 0; index < measures.length; index++) {
|
|
16651
|
+
let jump = measures[index]?.jump;
|
|
16652
|
+
if (!jump) continue;
|
|
16653
|
+
let type = jump.type;
|
|
16654
|
+
if (locationBeat(jump.location, measureLengths[index], `/global/measures/${index}/jump/location`), ["segno", "dsAlFine", "dsAlCoda"].includes(type) && required("segno", index), ["dcAlFine", "dsAlFine"].includes(type) && required("fine", index), ["dcAlCoda", "dsAlCoda", "toCoda"].includes(type) && required("coda", index), ["dcAlCoda", "dsAlCoda"].includes(type) && !measures.some((measure) => String(measure.jump?.type) === "toCoda"))
|
|
16655
|
+
throw new NavigationError("Al Coda requires an explicit To Coda instruction", [`/global/measures/${index}/jump`]);
|
|
16656
|
+
if (!["dc", "dcAlCoda", "dcAlFine", "segno", "dsAlFine", "dsAlCoda", "toCoda"].includes(type))
|
|
16657
|
+
throw new NavigationError(`Unsupported jump type: ${type}`, [`/global/measures/${index}/jump/type`]);
|
|
16658
|
+
}
|
|
16659
|
+
return { markers, required };
|
|
16660
|
+
}
|
|
16661
|
+
function nextInstruction(measure, cursor, length, state, fine, taken, initialOrder) {
|
|
16662
|
+
let index = cursor.measureIndex, jump = measure.jump, type = jump ? String(jump.type) : "", jumpBeat = jump ? locationBeat(jump.location, length, `/global/measures/${index}/jump/location`) : 1 / 0, fineBeat = state.fineArmed && fine?.measureIndex === index ? fine.beat : 1 / 0, jumpActive = jump !== void 0 && !taken.has(index) && jumpBeat >= cursor.beat - EPS && (type !== "toCoda" || state.codaArmed) && (state.replay || initialOrder.lastIndexOf(index) === state.orderIndex), stop = fineBeat >= cursor.beat - EPS ? Math.min(length, fineBeat) : length, endBeat = jumpActive ? Math.min(stop, jumpBeat) : stop;
|
|
16663
|
+
return {
|
|
16664
|
+
type,
|
|
16665
|
+
jumpActive,
|
|
16666
|
+
jumpBeat,
|
|
16667
|
+
fineReached: fineBeat >= cursor.beat - EPS && fineBeat <= endBeat + EPS,
|
|
16668
|
+
endBeat
|
|
16669
|
+
};
|
|
16670
|
+
}
|
|
16671
|
+
function compileNavigation(measures, initialOrder, measureLengths) {
|
|
16672
|
+
let { markers, required } = resolveDestinations(measures, measureLengths), result = [], taken = /* @__PURE__ */ new Set(), state = { fineArmed: !1, codaArmed: !1, replay: !1, orderIndex: 0 }, cursor = { measureIndex: initialOrder[0] ?? 0, beat: 0 }, transitions = 0;
|
|
16673
|
+
for (; cursor.measureIndex < measures.length && (state.replay || state.orderIndex < initialOrder.length); ) {
|
|
16674
|
+
if (++transitions > MAX_SPANS)
|
|
16675
|
+
throw new NavigationError("Navigation exceeds the safe playback traversal limit", ["/global/measures"]);
|
|
16676
|
+
let index = cursor.measureIndex, measure = measures[index], length = measureLengths[index], { type, jumpActive, jumpBeat, fineReached, endBeat } = nextInstruction(
|
|
16677
|
+
measure,
|
|
16678
|
+
cursor,
|
|
16679
|
+
length,
|
|
16680
|
+
state,
|
|
16681
|
+
markers.fine[0],
|
|
16682
|
+
taken,
|
|
16683
|
+
initialOrder
|
|
16684
|
+
);
|
|
16685
|
+
if (endBeat > cursor.beat + EPS && result.push({ measureIndex: index, startBeat: cursor.beat, endBeat, measureBeats: length }), state.fineArmed && fineReached) {
|
|
16686
|
+
state.fineArmed = !1;
|
|
16687
|
+
break;
|
|
16688
|
+
}
|
|
16689
|
+
if (jumpActive && jumpBeat <= endBeat + EPS) {
|
|
16690
|
+
taken.add(index), state.replay = !0, type === "toCoda" ? (state.codaArmed = !1, cursor = required("coda", index)) : (state.fineArmed = type === "dcAlFine" || type === "dsAlFine", state.codaArmed = type === "dcAlCoda" || type === "dsAlCoda", cursor = ["segno", "dsAlFine", "dsAlCoda"].includes(type) ? required("segno", index) : { measureIndex: 0, beat: 0 });
|
|
16691
|
+
continue;
|
|
16692
|
+
}
|
|
16693
|
+
state.orderIndex++, cursor = { measureIndex: state.replay ? index + 1 : initialOrder[state.orderIndex] ?? measures.length, beat: 0 };
|
|
16694
|
+
}
|
|
16695
|
+
if (state.fineArmed || state.codaArmed)
|
|
16696
|
+
throw new NavigationError("The armed Fine or To Coda instruction is not reachable on replay", ["/global/measures"]);
|
|
16697
|
+
return result;
|
|
16698
|
+
}
|
|
16699
|
+
|
|
16700
|
+
// packages/midi/src/navigation/realization.ts
|
|
16701
|
+
init_define_import_meta_env();
|
|
16702
|
+
function authoredOnsetInSpan(event, start, end) {
|
|
16703
|
+
return event.type === "noteOn" && event.scoreBeat !== void 0 && event.scoreBeat >= start - 1e-9 && event.scoreBeat < end - 1e-9;
|
|
16704
|
+
}
|
|
16705
|
+
function continuousEndBeat(spans, index, startBeat) {
|
|
16706
|
+
let end = startBeat + spans[index].endBeat;
|
|
16707
|
+
for (let nextIndex = index; playedSpansContinue(spans[nextIndex], spans[nextIndex + 1]); nextIndex++)
|
|
16708
|
+
end += spans[nextIndex].measureBeats - spans[nextIndex].endBeat + spans[nextIndex + 1].endBeat;
|
|
16709
|
+
return end;
|
|
16710
|
+
}
|
|
16711
|
+
function spanTempoMap(timeline, index, span, model, outputBeat) {
|
|
16712
|
+
let entries = [
|
|
16713
|
+
{
|
|
16714
|
+
measureIndex: index,
|
|
16715
|
+
beatInMeasure: 0,
|
|
16716
|
+
timeSeconds: model.timeAtBeat(outputBeat),
|
|
16717
|
+
bpm: model.bpmAtBeat(outputBeat)
|
|
16718
|
+
}
|
|
16719
|
+
];
|
|
16720
|
+
for (let entry of timeline.tempoMap) {
|
|
16721
|
+
if (entry.measureIndex !== index || entry.beatInMeasure <= span.startBeat + 1e-9 || entry.beatInMeasure >= span.endBeat - 1e-9)
|
|
16722
|
+
continue;
|
|
16723
|
+
let beatInMeasure = entry.beatInMeasure - span.startBeat;
|
|
16724
|
+
entries.push({
|
|
16725
|
+
...entry,
|
|
16726
|
+
beatInMeasure,
|
|
16727
|
+
timeSeconds: model.timeAtBeat(outputBeat + beatInMeasure)
|
|
16728
|
+
});
|
|
16729
|
+
}
|
|
16730
|
+
return entries;
|
|
16731
|
+
}
|
|
16732
|
+
function realizeNavigation(timeline, spans) {
|
|
16733
|
+
let sourceSpans = spans.map((span, index) => ({
|
|
16734
|
+
startBeat: timeline.measureStartBeats[index] + span.startBeat,
|
|
16735
|
+
endBeat: timeline.measureStartBeats[index] + span.endBeat
|
|
16736
|
+
})), model = TempoModel.concatenate(timeline.model, sourceSpans), events = [], measureStartBeats = [], tempoMap = [], outputBeat = 0, activeNotes = /* @__PURE__ */ new Map();
|
|
16737
|
+
for (let index = 0; index < spans.length; index++) {
|
|
16738
|
+
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;
|
|
16739
|
+
measureStartBeats.push(outputBeat), tempoMap.push(...spanTempoMap(timeline, index, span, model, outputBeat));
|
|
16740
|
+
let translate = (time) => model.timeAtBeat(outputBeat) + Math.min(endTime - startTime, Math.max(0, time - startTime)), noteKey = (event) => `${event.partIndex}:${event.playbackLaneId ?? event.channel}:${event.midiNote}:${event.drumKitProgram ?? ""}`;
|
|
16741
|
+
for (let event of timeline.events) {
|
|
16742
|
+
let authoredOnset = authoredOnsetInSpan(event, source.startBeat, source.endBeat);
|
|
16743
|
+
if (event.time < startTime - 1e-9 && !authoredOnset) {
|
|
16744
|
+
(event.type === "controlChange" || event.type === "programChange") && controllers.set(
|
|
16745
|
+
`${event.partIndex}:${event.playbackLaneId ?? event.channel}:${event.type}:${event.cc ?? ""}`,
|
|
16746
|
+
event
|
|
16747
|
+
);
|
|
16748
|
+
continue;
|
|
16749
|
+
}
|
|
16750
|
+
if (!(event.time > endTime + 1e-9 && !authoredOnset))
|
|
16751
|
+
if (event.type === "noteOn") {
|
|
16752
|
+
let beat = event.scoreBeat ?? timeline.model.beatAtTime(event.time);
|
|
16753
|
+
if (beat < source.startBeat - 1e-9 || beat >= source.endBeat - 1e-9) continue;
|
|
16754
|
+
let output = {
|
|
16755
|
+
...event,
|
|
16756
|
+
time: translate(event.time),
|
|
16757
|
+
scoreBeat: outputBeat + beat - source.startBeat,
|
|
16758
|
+
scoreDurationBeats: event.scoreDurationBeats === void 0 ? void 0 : Math.min(
|
|
16759
|
+
event.scoreDurationBeats,
|
|
16760
|
+
continuousEndBeat(spans, index, timeline.measureStartBeats[index]) - beat
|
|
16761
|
+
)
|
|
16762
|
+
};
|
|
16763
|
+
events.push(output);
|
|
16764
|
+
let key = noteKey(event), pending = activeNotes.get(key) ?? [];
|
|
16765
|
+
pending.push(output), activeNotes.set(key, pending);
|
|
16766
|
+
} else if (event.type === "noteOff") {
|
|
16767
|
+
let pending = activeNotes.get(noteKey(event));
|
|
16768
|
+
if (!pending?.length) continue;
|
|
16769
|
+
pending.shift(), events.push({ ...event, time: translate(event.time) });
|
|
16770
|
+
} else event.time < endTime - 1e-9 && events.push({ ...event, time: translate(event.time) });
|
|
16771
|
+
}
|
|
16772
|
+
events.splice(
|
|
16773
|
+
segmentStart,
|
|
16774
|
+
0,
|
|
16775
|
+
...Array.from(controllers.values(), (event) => ({ ...event, time: model.timeAtBeat(outputBeat) }))
|
|
16776
|
+
);
|
|
16777
|
+
let next = spans[index + 1];
|
|
16778
|
+
if (!playedSpansContinue(span, next))
|
|
16779
|
+
for (let pending of activeNotes.values()) {
|
|
16780
|
+
for (let onset of pending)
|
|
16781
|
+
events.push({
|
|
16782
|
+
...onset,
|
|
16783
|
+
type: "noteOff",
|
|
16784
|
+
velocity: 0,
|
|
16785
|
+
time: model.timeAtBeat(outputBeat + span.endBeat - span.startBeat, "release")
|
|
16786
|
+
});
|
|
16787
|
+
pending.length = 0;
|
|
16788
|
+
}
|
|
16789
|
+
outputBeat += span.endBeat - span.startBeat;
|
|
16790
|
+
}
|
|
16791
|
+
let rank = { noteOff: 0, programChange: 1, controlChange: 2, noteOn: 3 };
|
|
16792
|
+
return events.sort((a, b) => a.time - b.time || rank[a.type] - rank[b.type]), {
|
|
16793
|
+
...timeline,
|
|
16794
|
+
events,
|
|
16795
|
+
model,
|
|
16796
|
+
tempoMap,
|
|
16797
|
+
totalBeats: outputBeat,
|
|
16798
|
+
duration: model.timeAtBeat(outputBeat),
|
|
16799
|
+
measureStartBeats,
|
|
16800
|
+
measureStartTimes: measureStartBeats.map((beat) => model.timeAtBeat(beat)),
|
|
16801
|
+
playedSpans: [...spans]
|
|
16802
|
+
};
|
|
16803
|
+
}
|
|
16804
|
+
|
|
16805
|
+
// packages/midi/src/repeatExpansion.ts
|
|
16806
|
+
function expandPlaybackItinerary(measures, lengths) {
|
|
16807
|
+
let activeBeats = 4, measureLengths = lengths ?? measures.map((measure) => (measure.time && (activeBeats = measure.time.count * 4 / measure.time.unit), activeBeats));
|
|
16808
|
+
return compileNavigation(measures, expandRepeats(measures), measureLengths);
|
|
16809
|
+
}
|
|
16810
|
+
function scorePlaybackItinerary(score) {
|
|
16811
|
+
let indices = score.global.measures.map((_, index) => index), lengths = playbackGlobalMeasures(score, indices).map((measure) => {
|
|
16812
|
+
let time = measure.time;
|
|
16813
|
+
return time ? time.count * 4 / time.unit : 4;
|
|
16814
|
+
});
|
|
16815
|
+
return expandPlaybackItinerary(score.global.measures, lengths);
|
|
16255
16816
|
}
|
|
16256
16817
|
function appendVoltaPass(order, endings, pass, startIdx, endIdx) {
|
|
16257
16818
|
for (let j = startIdx; j <= endIdx; j++) {
|
|
@@ -16264,7 +16825,12 @@ function expandRepeats(globalMeasures) {
|
|
|
16264
16825
|
for (; i < len; ) {
|
|
16265
16826
|
let gm = globalMeasures[i];
|
|
16266
16827
|
if (gm.repeatEnd) {
|
|
16267
|
-
let repeatTimes = gm.repeatEnd.times ?? 2
|
|
16828
|
+
let repeatTimes = gm.repeatEnd.times ?? 2;
|
|
16829
|
+
if (!Number.isInteger(repeatTimes) || repeatTimes < 1 || repeatTimes > 1e3)
|
|
16830
|
+
throw new NavigationError("Repeat count must be an integer between 1 and 1000", [
|
|
16831
|
+
`/global/measures/${i}/repeatEnd/times`
|
|
16832
|
+
]);
|
|
16833
|
+
let repeatStartIdx = 0;
|
|
16268
16834
|
for (let j = i; j >= 0; j--)
|
|
16269
16835
|
if (globalMeasures[j].repeatStart) {
|
|
16270
16836
|
repeatStartIdx = j;
|
|
@@ -16294,45 +16860,6 @@ function expandRepeats(globalMeasures) {
|
|
|
16294
16860
|
}
|
|
16295
16861
|
return order;
|
|
16296
16862
|
}
|
|
16297
|
-
function appendRange(order, start, endInclusive) {
|
|
16298
|
-
for (let measure = start; measure <= endInclusive; measure++) order.push(measure);
|
|
16299
|
-
}
|
|
16300
|
-
function appendAlCodaReplay(result, replayStart, codaIdx, measureCount, toCodaMeasureIdx) {
|
|
16301
|
-
let replayEnd = toCodaMeasureIdx !== void 0 && toCodaMeasureIdx >= replayStart && toCodaMeasureIdx < codaIdx ? toCodaMeasureIdx : codaIdx - 1;
|
|
16302
|
-
appendRange(result, replayStart, replayEnd), appendRange(result, codaIdx, measureCount - 1);
|
|
16303
|
-
}
|
|
16304
|
-
function expandJumps(globalMeasures, initialOrder, toCodaMeasureIdx) {
|
|
16305
|
-
let segnoIdx = -1, fineIdx = -1, codaIdx = -1;
|
|
16306
|
-
for (let m = 0; m < globalMeasures.length; m++) {
|
|
16307
|
-
let gm = globalMeasures[m];
|
|
16308
|
-
gm.segno && (segnoIdx = m), gm.fine && (fineIdx = m), gm.coda && (codaIdx = m);
|
|
16309
|
-
}
|
|
16310
|
-
let jumpOrderPos = -1, jumpMeasureIdx = -1;
|
|
16311
|
-
for (let p = 0; p < initialOrder.length; p++) {
|
|
16312
|
-
let mi = initialOrder[p];
|
|
16313
|
-
if (globalMeasures[mi].jump) {
|
|
16314
|
-
jumpOrderPos = p, jumpMeasureIdx = mi;
|
|
16315
|
-
break;
|
|
16316
|
-
}
|
|
16317
|
-
}
|
|
16318
|
-
if (jumpOrderPos === -1) return [...initialOrder];
|
|
16319
|
-
let jump = globalMeasures[jumpMeasureIdx].jump, result = initialOrder.slice(0, jumpOrderPos + 1);
|
|
16320
|
-
switch (jump.type) {
|
|
16321
|
-
case "dsAlFine":
|
|
16322
|
-
segnoIdx >= 0 && fineIdx >= 0 && appendRange(result, segnoIdx, fineIdx);
|
|
16323
|
-
break;
|
|
16324
|
-
case "dsAlCoda":
|
|
16325
|
-
segnoIdx >= 0 && codaIdx >= 0 && appendAlCodaReplay(result, segnoIdx, codaIdx, globalMeasures.length, toCodaMeasureIdx);
|
|
16326
|
-
break;
|
|
16327
|
-
case "dcAlCoda":
|
|
16328
|
-
codaIdx >= 0 && appendAlCodaReplay(result, 0, codaIdx, globalMeasures.length, toCodaMeasureIdx);
|
|
16329
|
-
break;
|
|
16330
|
-
case "segno":
|
|
16331
|
-
segnoIdx >= 0 && appendRange(result, segnoIdx, globalMeasures.length - 1);
|
|
16332
|
-
break;
|
|
16333
|
-
}
|
|
16334
|
-
return result;
|
|
16335
|
-
}
|
|
16336
16863
|
|
|
16337
16864
|
// packages/midi/src/measureRepeats.ts
|
|
16338
16865
|
init_define_import_meta_env();
|
|
@@ -16671,7 +17198,7 @@ function fermataMultiplier(fermata) {
|
|
|
16671
17198
|
function caesuraBeats(style) {
|
|
16672
17199
|
return style === "short" ? CAESURA_SHORT_BEATS : CAESURA_BEATS;
|
|
16673
17200
|
}
|
|
16674
|
-
function
|
|
17201
|
+
function durationBeats3(d) {
|
|
16675
17202
|
let base = DURATION_BEATS[d.base] ?? 1;
|
|
16676
17203
|
return d.dots ? base * (2 - Math.pow(2, -d.dots)) : base;
|
|
16677
17204
|
}
|
|
@@ -16689,16 +17216,16 @@ function collectFromContent(content, startBeat, ratio, spans, caesuraByBeat) {
|
|
|
16689
17216
|
let cursor = startBeat;
|
|
16690
17217
|
for (let item of content)
|
|
16691
17218
|
if (item.type === "event") {
|
|
16692
|
-
let dur =
|
|
17219
|
+
let dur = durationBeats3(item.duration) * ratio, endBeat = cursor + dur;
|
|
16693
17220
|
if (item.fermata && item.fermata.duration !== "none") {
|
|
16694
17221
|
let mult = fermataMultiplier(item.fermata);
|
|
16695
17222
|
dur > 0 && mult > 1 && spans.push({ onset: cursor, end: endBeat, duration: dur, multiplier: mult });
|
|
16696
17223
|
}
|
|
16697
17224
|
item.markings?.caesura && mergeMax(caesuraByBeat, endBeat, caesuraBeats(item.markings.caesura.style)), cursor = endBeat;
|
|
16698
17225
|
} else if (item.type === "tuplet") {
|
|
16699
|
-
let outerBeats =
|
|
17226
|
+
let outerBeats = durationBeats3(item.outer.duration) * item.outer.multiple, innerBeats = durationBeats3(item.inner.duration) * item.inner.multiple, childRatio = innerBeats > 0 ? outerBeats / innerBeats * ratio : ratio;
|
|
16700
17227
|
collectFromContent(item.content, cursor, childRatio, spans, caesuraByBeat), cursor += outerBeats * ratio;
|
|
16701
|
-
} else item.type === "tremolo" ? cursor +=
|
|
17228
|
+
} else item.type === "tremolo" ? cursor += durationBeats3(item.outer.duration) * item.outer.multiple * ratio : item.type === "space" && (cursor += item.duration[0] / item.duration[1] * 4 * ratio);
|
|
16702
17229
|
return cursor - startBeat;
|
|
16703
17230
|
}
|
|
16704
17231
|
function collectPartMeasureHolds(partMeasure, originalMeasureIndex, staffMeterTable, spans, caesuraByBeat) {
|
|
@@ -16733,94 +17260,25 @@ function buildHoldSchedule(score, measureOrder, globalMeasures) {
|
|
|
16733
17260
|
let origIdx = measureOrder[expandedIdx], fermataSpans = [], caesuraByBeat = /* @__PURE__ */ new Map();
|
|
16734
17261
|
for (let partIndex = 0; partIndex < score.parts.length; partIndex++) {
|
|
16735
17262
|
let part = score.parts[partIndex];
|
|
16736
|
-
collectPartMeasureHolds(
|
|
16737
|
-
part.measures[origIdx],
|
|
16738
|
-
origIdx,
|
|
16739
|
-
staffMeterTables[partIndex],
|
|
16740
|
-
fermataSpans,
|
|
16741
|
-
caesuraByBeat
|
|
16742
|
-
);
|
|
16743
|
-
}
|
|
16744
|
-
let globalCaesura = globalMeasures[origIdx]?.caesura;
|
|
16745
|
-
if (globalCaesura) {
|
|
16746
|
-
let mBeats = measureBeats2(resolveTimeSig(globalMeasures, origIdx));
|
|
16747
|
-
mergeMax(caesuraByBeat, mBeats, caesuraBeats(globalCaesura.style));
|
|
16748
|
-
}
|
|
16749
|
-
let holds = groupFermataSpans(fermataSpans);
|
|
16750
|
-
for (let [atBeat, extraBeats] of caesuraByBeat)
|
|
16751
|
-
holds.push({ atBeat, extraBeats, kind: "caesura" });
|
|
16752
|
-
holds.sort((a, b) => a.atBeat - b.atBeat), schedule.push(holds);
|
|
16753
|
-
}
|
|
16754
|
-
return schedule;
|
|
16755
|
-
}
|
|
16756
|
-
|
|
16757
|
-
// packages/midi/src/cadenzaTiming.ts
|
|
16758
|
-
init_define_import_meta_env();
|
|
16759
|
-
function contentBeats(content) {
|
|
16760
|
-
let beats = 0;
|
|
16761
|
-
for (let item of content)
|
|
16762
|
-
switch (item.type) {
|
|
16763
|
-
case "event":
|
|
16764
|
-
beats += durationBeats3(item.duration.base, item.duration.dots);
|
|
16765
|
-
break;
|
|
16766
|
-
case "space":
|
|
16767
|
-
beats += item.duration[0] / item.duration[1] * 4;
|
|
16768
|
-
break;
|
|
16769
|
-
case "tuplet":
|
|
16770
|
-
case "tremolo":
|
|
16771
|
-
beats += durationBeats3(item.outer.duration.base, item.outer.duration.dots) * item.outer.multiple;
|
|
16772
|
-
break;
|
|
16773
|
-
case "grace":
|
|
16774
|
-
break;
|
|
16775
|
-
}
|
|
16776
|
-
return beats;
|
|
16777
|
-
}
|
|
16778
|
-
function durationBeats3(base, dots) {
|
|
16779
|
-
let beats = DURATION_BEATS[base];
|
|
16780
|
-
return dots ? beats * (2 - Math.pow(2, -dots)) : beats;
|
|
16781
|
-
}
|
|
16782
|
-
function writtenMeasureBeats(score, measureIndex, minimumBeats) {
|
|
16783
|
-
return score.parts.reduce((maxBeats, part) => {
|
|
16784
|
-
let measure = part.measures[measureIndex];
|
|
16785
|
-
return measure ? Math.max(
|
|
16786
|
-
maxBeats,
|
|
16787
|
-
...measure.sequences.filter((sequence) => !sequence.fullMeasure).map((sequence) => contentBeats(sequence.content))
|
|
16788
|
-
) : maxBeats;
|
|
16789
|
-
}, minimumBeats);
|
|
16790
|
-
}
|
|
16791
|
-
function playbackGlobalMeasures(score, measureOrder) {
|
|
16792
|
-
let activeBeats = 4;
|
|
16793
|
-
return measureOrder.map((measureIndex) => {
|
|
16794
|
-
let measure = score.global.measures[measureIndex];
|
|
16795
|
-
if (measure.time && (activeBeats = measure.time.count * 4 / measure.time.unit), measureIndex === 0 && measure.number === 0) {
|
|
16796
|
-
let pickupBeats = writtenMeasureBeats(score, measureIndex, 0);
|
|
16797
|
-
if (pickupBeats > 0)
|
|
16798
|
-
return {
|
|
16799
|
-
...measure,
|
|
16800
|
-
__playbackNominalTime: measure.time ? { ...measure.time } : void 0,
|
|
16801
|
-
time: {
|
|
16802
|
-
...measure.time,
|
|
16803
|
-
count: pickupBeats,
|
|
16804
|
-
unit: 4
|
|
16805
|
-
}
|
|
16806
|
-
};
|
|
16807
|
-
}
|
|
16808
|
-
if (measure.time?.display !== "senzaMisura") return measure;
|
|
16809
|
-
let cadenzaBeats = writtenMeasureBeats(score, measureIndex, activeBeats);
|
|
16810
|
-
return {
|
|
16811
|
-
...measure,
|
|
16812
|
-
time: {
|
|
16813
|
-
...measure.time,
|
|
16814
|
-
count: cadenzaBeats,
|
|
16815
|
-
unit: 4
|
|
16816
|
-
}
|
|
16817
|
-
};
|
|
16818
|
-
});
|
|
16819
|
-
}
|
|
16820
|
-
function suppressCadenzaFermataHolds(measures, holds) {
|
|
16821
|
-
return holds.map(
|
|
16822
|
-
(measureHolds, index) => measures[index]?.time?.display === "senzaMisura" ? measureHolds.filter((hold) => hold.kind !== "fermata") : [...measureHolds]
|
|
16823
|
-
);
|
|
17263
|
+
collectPartMeasureHolds(
|
|
17264
|
+
part.measures[origIdx],
|
|
17265
|
+
origIdx,
|
|
17266
|
+
staffMeterTables[partIndex],
|
|
17267
|
+
fermataSpans,
|
|
17268
|
+
caesuraByBeat
|
|
17269
|
+
);
|
|
17270
|
+
}
|
|
17271
|
+
let globalCaesura = globalMeasures[origIdx]?.caesura;
|
|
17272
|
+
if (globalCaesura) {
|
|
17273
|
+
let mBeats = measureBeats2(resolveTimeSig(globalMeasures, origIdx));
|
|
17274
|
+
mergeMax(caesuraByBeat, mBeats, caesuraBeats(globalCaesura.style));
|
|
17275
|
+
}
|
|
17276
|
+
let holds = groupFermataSpans(fermataSpans);
|
|
17277
|
+
for (let [atBeat, extraBeats] of caesuraByBeat)
|
|
17278
|
+
holds.push({ atBeat, extraBeats, kind: "caesura" });
|
|
17279
|
+
holds.sort((a, b) => a.atBeat - b.atBeat), schedule.push(holds);
|
|
17280
|
+
}
|
|
17281
|
+
return schedule;
|
|
16824
17282
|
}
|
|
16825
17283
|
|
|
16826
17284
|
// packages/midi/src/legato.ts
|
|
@@ -16996,388 +17454,139 @@ function applyMeasureTechniques(expressions, ctx, state, caps, ratioForStaff = (
|
|
|
16996
17454
|
});
|
|
16997
17455
|
} else if (mark.action.kind === "mute" && muteFamily) {
|
|
16998
17456
|
if (mark.action.muted === muted) continue;
|
|
16999
|
-
muted = mark.action.muted;
|
|
17000
|
-
let curve = muted ? MUTE_CURVES[muteFamily] : OPEN_CURVE;
|
|
17001
|
-
emitCc(ctx, time, CC_BRIGHTNESS, curve.brightness), emitCc(ctx, time, CC_RESONANCE, curve.resonance);
|
|
17002
|
-
}
|
|
17003
|
-
}
|
|
17004
|
-
return { program, muted, ...instrumentKey === void 0 ? {} : { instrumentKey } };
|
|
17005
|
-
}
|
|
17006
|
-
|
|
17007
|
-
// packages/midi/src/instrumentPlayback/index.ts
|
|
17008
|
-
init_define_import_meta_env();
|
|
17009
|
-
function instrumentPart(part, instrument) {
|
|
17010
|
-
return instrument ? {
|
|
17011
|
-
...part,
|
|
17012
|
-
name: instrument.name ?? part.name,
|
|
17013
|
-
_x: {
|
|
17014
|
-
...part._x,
|
|
17015
|
-
viritura: {
|
|
17016
|
-
...part._x?.viritura,
|
|
17017
|
-
instrumentId: instrument.instrumentId,
|
|
17018
|
-
midiProgram: instrument.midiProgram,
|
|
17019
|
-
initialInstrument: void 0,
|
|
17020
|
-
instruments: void 0
|
|
17021
|
-
}
|
|
17022
|
-
}
|
|
17023
|
-
} : part;
|
|
17024
|
-
}
|
|
17025
|
-
function playbackInstruments(part) {
|
|
17026
|
-
let initial = initialInstrumentState(part);
|
|
17027
|
-
if (initial.instrumentKey !== void 0 && !initial.instrument)
|
|
17028
|
-
throw new Error(`Unknown initial instrument "${initial.instrumentKey}" in "${part.name}".`);
|
|
17029
|
-
let result = [{ key: "initial", part: instrumentPart(part, initial.instrument) }], seen = /* @__PURE__ */ new Set();
|
|
17030
|
-
for (let located of listInstrumentChanges(part)) {
|
|
17031
|
-
let [numerator, denominator] = located.position;
|
|
17032
|
-
if (denominator <= 0 || numerator < 0) throw new Error(`Invalid instrument-change position in "${part.name}".`);
|
|
17033
|
-
let key = located.change.instrument;
|
|
17034
|
-
if (!(key === void 0 || seen.has(key))) {
|
|
17035
|
-
if (!located.state.instrument) throw new Error(`Unknown instrument "${key}" in "${part.name}".`);
|
|
17036
|
-
seen.add(key), result.push({ key: `instrument:${key}`, part: instrumentPart(part, located.state.instrument) });
|
|
17037
|
-
}
|
|
17038
|
-
}
|
|
17039
|
-
return result;
|
|
17040
|
-
}
|
|
17041
|
-
function playbackInstrumentKeyAt(part, measureIndex, beatOffset = 0) {
|
|
17042
|
-
let key = "initial";
|
|
17043
|
-
for (let located of listInstrumentChanges(part)) {
|
|
17044
|
-
if (located.measureIndex > measureIndex || located.measureIndex === measureIndex && located.position[0] / located.position[1] * 4 > beatOffset) break;
|
|
17045
|
-
located.change.instrument !== void 0 && (key = `instrument:${located.change.instrument}`);
|
|
17046
|
-
}
|
|
17047
|
-
return key;
|
|
17048
|
-
}
|
|
17049
|
-
function boundariesForPart(part, timing, programs, initialProgram) {
|
|
17050
|
-
let instruments = playbackInstruments(part), definitions = new Map(instruments.map((instrument) => [instrument.key, instrument.part])), changes = listInstrumentChanges(part).filter((located) => located.change.instrument !== void 0), boundaries = [], add = (beat, key) => {
|
|
17051
|
-
let program = programs?.get(key) ?? (key === "initial" ? initialProgram : void 0) ?? definitions.get(key)?._x?.viritura?.midiProgram;
|
|
17052
|
-
boundaries.push({
|
|
17053
|
-
beat,
|
|
17054
|
-
time: timing.model.timeAtBeat(beat),
|
|
17055
|
-
key,
|
|
17056
|
-
program: typeof program == "number" && Number.isInteger(program) && program >= 0 && program <= 127 ? program : void 0
|
|
17057
|
-
});
|
|
17058
|
-
};
|
|
17059
|
-
return timing.measureOrder.forEach((measureIndex, expandedIndex) => {
|
|
17060
|
-
let start = timing.measureStartBeats[expandedIndex];
|
|
17061
|
-
add(start, playbackInstrumentKeyAt(part, measureIndex));
|
|
17062
|
-
for (let located of changes) {
|
|
17063
|
-
if (located.measureIndex !== measureIndex || located.position[0] === 0) continue;
|
|
17064
|
-
let [numerator, denominator] = located.position;
|
|
17065
|
-
if (denominator <= 0 || numerator < 0) throw new Error(`Invalid instrument-change position in "${part.name}".`);
|
|
17066
|
-
add(start + numerator / denominator * 4, `instrument:${located.change.instrument}`);
|
|
17067
|
-
}
|
|
17068
|
-
}), boundaries;
|
|
17069
|
-
}
|
|
17070
|
-
function applyInstrumentPlayback(score, events, timing, programs, initialPrograms) {
|
|
17071
|
-
let diagnostics = [];
|
|
17072
|
-
return score.parts.forEach((part, partIndex) => {
|
|
17073
|
-
if (playbackInstruments(part).length === 1) return;
|
|
17074
|
-
let boundaries = boundariesForPart(part, timing, programs?.get(partIndex), initialPrograms?.[partIndex]), partEvents = events.filter((event) => event.partIndex === partIndex), lanes = new Set(partEvents.flatMap((event) => event.playbackLaneId ? [event.playbackLaneId] : []));
|
|
17075
|
-
lanes.size === 0 && lanes.add(`part:${partIndex}`);
|
|
17076
|
-
let missingPrograms = [
|
|
17077
|
-
...new Set(boundaries.filter((boundary) => boundary.program === void 0).map((boundary) => boundary.key))
|
|
17078
|
-
];
|
|
17079
|
-
missingPrograms.length > 0 && diagnostics.push({
|
|
17080
|
-
code: "instrument-program-unavailable",
|
|
17081
|
-
message: `Physical MIDI cannot select ${missingPrograms.join(", ")} in "${part.name}"; provide resolved instrumentPrograms or a MIDI program fallback.`,
|
|
17082
|
-
playbackLaneIds: [...lanes]
|
|
17083
|
-
});
|
|
17084
|
-
for (let event of [...partEvents]) {
|
|
17085
|
-
if (event.playbackLaneId) continue;
|
|
17086
|
-
let [first, ...others] = lanes;
|
|
17087
|
-
event.playbackLaneId = first;
|
|
17088
|
-
for (let lane of others) {
|
|
17089
|
-
let scoped = { ...event, playbackLaneId: lane };
|
|
17090
|
-
events.push(scoped), partEvents.push(scoped);
|
|
17091
|
-
}
|
|
17092
|
-
}
|
|
17093
|
-
let attackDelays = /* @__PURE__ */ new Map(), attackKey = (event) => JSON.stringify([event.playbackLaneId, event.channel, event.midiNote, event.scoreBeat, event.scoreEventId]);
|
|
17094
|
-
for (let event of partEvents) {
|
|
17095
|
-
if (event.type === "controlChange" && event.cc !== 74 && event.cc !== 71) continue;
|
|
17096
|
-
let beat = event.scoreBeat ?? timing.model.beatAtTime(event.time), instrument = "initial", instrumentStart = 0;
|
|
17097
|
-
for (let boundary of boundaries) {
|
|
17098
|
-
if (boundary.beat > beat + 1e-9) break;
|
|
17099
|
-
instrument = boundary.key, instrumentStart = boundary.time;
|
|
17100
|
-
}
|
|
17101
|
-
if (event.type === "noteOn") {
|
|
17102
|
-
let delay = Math.max(0, instrumentStart - event.time);
|
|
17103
|
-
attackDelays.set(attackKey(event), delay), event.time = Math.max(event.time, instrumentStart);
|
|
17104
|
-
}
|
|
17105
|
-
event.playbackInstrumentKey = instrument;
|
|
17106
|
-
}
|
|
17107
|
-
for (let event of partEvents)
|
|
17108
|
-
event.type === "noteOff" && (event.time += attackDelays.get(attackKey(event)) ?? 0);
|
|
17109
|
-
for (let lane of lanes) {
|
|
17110
|
-
let previous;
|
|
17111
|
-
for (let boundary of boundaries)
|
|
17112
|
-
boundary.key !== previous && (previous = boundary.key, events.push({
|
|
17113
|
-
type: "programChange",
|
|
17114
|
-
time: boundary.time,
|
|
17115
|
-
scoreBeat: boundary.beat,
|
|
17116
|
-
midiNote: 0,
|
|
17117
|
-
velocity: 0,
|
|
17118
|
-
partIndex,
|
|
17119
|
-
playbackLaneId: lane,
|
|
17120
|
-
channel: partEvents.find((event) => event.playbackLaneId === lane)?.channel ?? 0,
|
|
17121
|
-
playbackInstrumentKey: boundary.key,
|
|
17122
|
-
instrumentChange: !0,
|
|
17123
|
-
program: boundary.program
|
|
17124
|
-
}));
|
|
17125
|
-
}
|
|
17126
|
-
}), diagnostics;
|
|
17127
|
-
}
|
|
17128
|
-
|
|
17129
|
-
// packages/midi/src/tempoMap.ts
|
|
17130
|
-
init_define_import_meta_env();
|
|
17131
|
-
|
|
17132
|
-
// packages/midi/src/tempoModel.ts
|
|
17133
|
-
init_define_import_meta_env();
|
|
17134
|
-
var bpmToSpb = (bpm) => 60 / bpm;
|
|
17135
|
-
function integrateTime(b0, b, bpm0, slope) {
|
|
17136
|
-
let db = b - b0;
|
|
17137
|
-
if (Math.abs(slope) < 1e-6) return bpmToSpb(bpm0) * db;
|
|
17138
|
-
let bpmB = bpm0 + slope * db;
|
|
17139
|
-
return 60 / slope * Math.log(bpmB / bpm0);
|
|
17140
|
-
}
|
|
17141
|
-
function inverseBeat(dt, bpm0, slope) {
|
|
17142
|
-
return Math.abs(slope) < 1e-6 ? dt * bpm0 / 60 : (bpm0 * Math.exp(dt * slope / 60) - bpm0) / slope;
|
|
17143
|
-
}
|
|
17144
|
-
var TempoModel = class _TempoModel {
|
|
17145
|
-
knots;
|
|
17146
|
-
constructor(knots) {
|
|
17147
|
-
this.knots = knots;
|
|
17148
|
-
}
|
|
17149
|
-
/**
|
|
17150
|
-
* Build a model from tempo regions and point insertions.
|
|
17151
|
-
*
|
|
17152
|
-
* `regions` need not be pre-sorted or gapless; they are sorted by `startBeat`
|
|
17153
|
-
* and gaps are filled by extending the previous tempo (a held constant). The
|
|
17154
|
-
* first region's start tempo applies from beat 0. `insertions` are point gaps
|
|
17155
|
-
* (caesuras) keyed by beat.
|
|
17156
|
-
*/
|
|
17157
|
-
static build(regions, insertions = []) {
|
|
17158
|
-
let sorted = [...regions].sort((a, b) => a.startBeat - b.startBeat), insByBeat = /* @__PURE__ */ new Map();
|
|
17159
|
-
for (let ins of insertions)
|
|
17160
|
-
ins.seconds > 0 && insByBeat.set(ins.beat, (insByBeat.get(ins.beat) ?? 0) + ins.seconds);
|
|
17161
|
-
let boundarySet = /* @__PURE__ */ new Set([0]);
|
|
17162
|
-
for (let r of sorted) boundarySet.add(r.startBeat);
|
|
17163
|
-
for (let ins of insByBeat.keys()) boundarySet.add(ins);
|
|
17164
|
-
let boundaries = [...boundarySet].sort((a, b) => a - b), tempoAt = (beat) => {
|
|
17165
|
-
let active;
|
|
17166
|
-
for (let r of sorted)
|
|
17167
|
-
if (r.startBeat <= beat + 1e-9) active = r;
|
|
17168
|
-
else break;
|
|
17169
|
-
if (!active) {
|
|
17170
|
-
let first = sorted[0];
|
|
17171
|
-
return { bpm: first ? first.startBpm : 120, slope: 0 };
|
|
17172
|
-
}
|
|
17173
|
-
let span = active.endBeat - active.startBeat, slope = span > 0 && Number.isFinite(span) ? (active.endBpm - active.startBpm) / span : 0;
|
|
17174
|
-
return { bpm: active.startBpm + slope * (beat - active.startBeat), slope };
|
|
17175
|
-
}, knots = [], time = 0, prevBeat = boundaries[0], prev = tempoAt(prevBeat), ins0 = insByBeat.get(prevBeat) ?? 0;
|
|
17176
|
-
time += ins0, knots.push({ beat: prevBeat, time, bpm: prev.bpm, slope: prev.slope, insertBefore: ins0 });
|
|
17177
|
-
for (let i = 1; i < boundaries.length; i++) {
|
|
17178
|
-
let beat = boundaries[i];
|
|
17179
|
-
time += integrateTime(prevBeat, beat, prev.bpm, prev.slope);
|
|
17180
|
-
let ins = insByBeat.get(beat) ?? 0;
|
|
17181
|
-
time += ins;
|
|
17182
|
-
let t = tempoAt(beat);
|
|
17183
|
-
knots.push({ beat, time, bpm: t.bpm, slope: t.slope, insertBefore: ins }), prevBeat = beat, prev = t;
|
|
17184
|
-
}
|
|
17185
|
-
return new _TempoModel(knots);
|
|
17186
|
-
}
|
|
17187
|
-
/** Index of the last knot whose beat ≤ `beat`. */
|
|
17188
|
-
knotIndexForBeat(beat) {
|
|
17189
|
-
let k = this.knots, lo = 0, hi = k.length - 1, ans = 0;
|
|
17190
|
-
for (; lo <= hi; ) {
|
|
17191
|
-
let mid = lo + hi >> 1;
|
|
17192
|
-
k[mid].beat <= beat + 1e-9 ? (ans = mid, lo = mid + 1) : hi = mid - 1;
|
|
17193
|
-
}
|
|
17194
|
-
return ans;
|
|
17195
|
-
}
|
|
17196
|
-
/** Index of the last knot whose time ≤ `time`. */
|
|
17197
|
-
knotIndexForTime(time) {
|
|
17198
|
-
let k = this.knots, lo = 0, hi = k.length - 1, ans = 0;
|
|
17199
|
-
for (; lo <= hi; ) {
|
|
17200
|
-
let mid = lo + hi >> 1;
|
|
17201
|
-
k[mid].time <= time + 1e-9 ? (ans = mid, lo = mid + 1) : hi = mid - 1;
|
|
17202
|
-
}
|
|
17203
|
-
return ans;
|
|
17204
|
-
}
|
|
17205
|
-
/**
|
|
17206
|
-
* Absolute time (seconds) at a global quarter-note beat.
|
|
17207
|
-
*
|
|
17208
|
-
* `endpoint` selects how a point insertion EXACTLY at `beat` is treated:
|
|
17209
|
-
* - `"onset"` (default): time AFTER the insertion — what an event STARTING at
|
|
17210
|
-
* `beat` wants (it begins once the gap is over).
|
|
17211
|
-
* - `"release"`: time BEFORE the insertion — what a note ENDING at `beat`
|
|
17212
|
-
* wants (a caesura/gap at the release point isn't part of the note).
|
|
17213
|
-
*/
|
|
17214
|
-
timeAtBeat(beat, endpoint = "onset") {
|
|
17215
|
-
let i = this.knotIndexForBeat(beat), knot = this.knots[i], base = knot.time + integrateTime(knot.beat, beat, knot.bpm, knot.slope);
|
|
17216
|
-
return endpoint === "release" && Math.abs(knot.beat - beat) < 1e-9 ? base - knot.insertBefore : base;
|
|
17217
|
-
}
|
|
17218
|
-
/**
|
|
17219
|
-
* Seconds elapsed over `[beat, beat + beats)` — a note/event DURATION. The end
|
|
17220
|
-
* uses release semantics so a point insertion (caesura) exactly at the release
|
|
17221
|
-
* beat is not absorbed into the note's sounding length.
|
|
17222
|
-
*/
|
|
17223
|
-
secondsForBeats(beat, beats) {
|
|
17224
|
-
return this.timeAtBeat(beat + beats, "release") - this.timeAtBeat(beat);
|
|
17225
|
-
}
|
|
17226
|
-
/**
|
|
17227
|
-
* Global quarter-note beat at an absolute time (inverse of {@link timeAtBeat}).
|
|
17228
|
-
* Within a point-insertion gap, returns the insertion's beat (position is
|
|
17229
|
-
* frozen during a caesura).
|
|
17230
|
-
*/
|
|
17231
|
-
beatAtTime(time) {
|
|
17232
|
-
let i = this.knotIndexForTime(time), knot = this.knots[i], next = this.knots[i + 1];
|
|
17233
|
-
if (next) {
|
|
17234
|
-
let musicEnd = next.time - next.insertBefore;
|
|
17235
|
-
if (time >= musicEnd - 1e-9) return next.beat;
|
|
17457
|
+
muted = mark.action.muted;
|
|
17458
|
+
let curve = muted ? MUTE_CURVES[muteFamily] : OPEN_CURVE;
|
|
17459
|
+
emitCc(ctx, time, CC_BRIGHTNESS, curve.brightness), emitCc(ctx, time, CC_RESONANCE, curve.resonance);
|
|
17236
17460
|
}
|
|
17237
|
-
return knot.beat + inverseBeat(time - knot.time, knot.bpm, knot.slope);
|
|
17238
|
-
}
|
|
17239
|
-
/** Seconds-per-quarter-note beat at a global beat. */
|
|
17240
|
-
spbAtBeat(beat) {
|
|
17241
|
-
let i = this.knotIndexForBeat(beat), knot = this.knots[i], bpmHere = knot.bpm + knot.slope * (beat - knot.beat);
|
|
17242
|
-
return bpmToSpb(bpmHere);
|
|
17243
|
-
}
|
|
17244
|
-
/** Quarter-note BPM at a global beat. */
|
|
17245
|
-
bpmAtBeat(beat) {
|
|
17246
|
-
return 60 / this.spbAtBeat(beat);
|
|
17247
|
-
}
|
|
17248
|
-
/**
|
|
17249
|
-
* Generate click times (seconds) for a metronome / studio click track over
|
|
17250
|
-
* `[startBeat, endBeat)`. `clickBeats` are the beat positions to click,
|
|
17251
|
-
* expressed as offsets within each "grid step" — but for the common case
|
|
17252
|
-
* callers pass a flat list of global beat positions and we just map them.
|
|
17253
|
-
* Returns the absolute time of every beat in `beats`.
|
|
17254
|
-
*/
|
|
17255
|
-
clickTimes(beats) {
|
|
17256
|
-
return beats.map((b) => this.timeAtBeat(b));
|
|
17257
17461
|
}
|
|
17258
|
-
};
|
|
17259
|
-
|
|
17260
|
-
// packages/midi/src/tempoMap.ts
|
|
17261
|
-
function playbackNominalTime(gm) {
|
|
17262
|
-
return gm.__playbackNominalTime;
|
|
17462
|
+
return { program, muted, ...instrumentKey === void 0 ? {} : { instrumentKey } };
|
|
17263
17463
|
}
|
|
17264
|
-
|
|
17265
|
-
|
|
17266
|
-
|
|
17464
|
+
|
|
17465
|
+
// packages/midi/src/instrumentPlayback/index.ts
|
|
17466
|
+
init_define_import_meta_env();
|
|
17467
|
+
function instrumentPart(part, instrument) {
|
|
17468
|
+
return instrument ? {
|
|
17469
|
+
...part,
|
|
17470
|
+
name: instrument.name ?? part.name,
|
|
17471
|
+
_x: {
|
|
17472
|
+
...part._x,
|
|
17473
|
+
viritura: {
|
|
17474
|
+
...part._x?.viritura,
|
|
17475
|
+
instrumentId: instrument.instrumentId,
|
|
17476
|
+
midiProgram: instrument.midiProgram,
|
|
17477
|
+
initialInstrument: void 0,
|
|
17478
|
+
instruments: void 0
|
|
17479
|
+
}
|
|
17480
|
+
}
|
|
17481
|
+
} : part;
|
|
17267
17482
|
}
|
|
17268
|
-
function
|
|
17269
|
-
let
|
|
17270
|
-
|
|
17483
|
+
function playbackInstruments(part) {
|
|
17484
|
+
let initial = initialInstrumentState(part);
|
|
17485
|
+
if (initial.instrumentKey !== void 0 && !initial.instrument)
|
|
17486
|
+
throw new Error(`Unknown initial instrument "${initial.instrumentKey}" in "${part.name}".`);
|
|
17487
|
+
let result = [{ key: "initial", part: instrumentPart(part, initial.instrument) }], seen = /* @__PURE__ */ new Set();
|
|
17488
|
+
for (let located of listInstrumentChanges(part)) {
|
|
17489
|
+
let [numerator, denominator] = located.position;
|
|
17490
|
+
if (denominator <= 0 || numerator < 0) throw new Error(`Invalid instrument-change position in "${part.name}".`);
|
|
17491
|
+
let key = located.change.instrument;
|
|
17492
|
+
if (!(key === void 0 || seen.has(key))) {
|
|
17493
|
+
if (!located.state.instrument) throw new Error(`Unknown instrument "${key}" in "${part.name}".`);
|
|
17494
|
+
seen.add(key), result.push({ key: `instrument:${key}`, part: instrumentPart(part, located.state.instrument) });
|
|
17495
|
+
}
|
|
17496
|
+
}
|
|
17497
|
+
return result;
|
|
17271
17498
|
}
|
|
17272
|
-
function
|
|
17273
|
-
|
|
17499
|
+
function playbackInstrumentKeyAt(part, measureIndex, beatOffset = 0) {
|
|
17500
|
+
let key = "initial";
|
|
17501
|
+
for (let located of listInstrumentChanges(part)) {
|
|
17502
|
+
if (located.measureIndex > measureIndex || located.measureIndex === measureIndex && located.position[0] / located.position[1] * 4 > beatOffset) break;
|
|
17503
|
+
located.change.instrument !== void 0 && (key = `instrument:${located.change.instrument}`);
|
|
17504
|
+
}
|
|
17505
|
+
return key;
|
|
17274
17506
|
}
|
|
17275
|
-
function
|
|
17276
|
-
let
|
|
17277
|
-
|
|
17278
|
-
|
|
17279
|
-
|
|
17280
|
-
|
|
17281
|
-
|
|
17282
|
-
|
|
17283
|
-
|
|
17284
|
-
|
|
17285
|
-
|
|
17286
|
-
|
|
17287
|
-
|
|
17288
|
-
|
|
17507
|
+
function boundariesForPart(part, timing, programs, initialProgram) {
|
|
17508
|
+
let instruments = playbackInstruments(part), definitions = new Map(instruments.map((instrument) => [instrument.key, instrument.part])), changes = listInstrumentChanges(part).filter((located) => located.change.instrument !== void 0), boundaries = [], add = (beat, key) => {
|
|
17509
|
+
let program = programs?.get(key) ?? (key === "initial" ? initialProgram : void 0) ?? definitions.get(key)?._x?.viritura?.midiProgram;
|
|
17510
|
+
boundaries.push({
|
|
17511
|
+
beat,
|
|
17512
|
+
time: timing.model.timeAtBeat(beat),
|
|
17513
|
+
key,
|
|
17514
|
+
program: typeof program == "number" && Number.isInteger(program) && program >= 0 && program <= 127 ? program : void 0
|
|
17515
|
+
});
|
|
17516
|
+
};
|
|
17517
|
+
return timing.measureOrder.forEach((measureIndex, expandedIndex) => {
|
|
17518
|
+
let start = timing.measureStartBeats[expandedIndex];
|
|
17519
|
+
add(start, playbackInstrumentKeyAt(part, measureIndex));
|
|
17520
|
+
for (let located of changes) {
|
|
17521
|
+
if (located.measureIndex !== measureIndex || located.position[0] === 0) continue;
|
|
17522
|
+
let [numerator, denominator] = located.position;
|
|
17523
|
+
if (denominator <= 0 || numerator < 0) throw new Error(`Invalid instrument-change position in "${part.name}".`);
|
|
17524
|
+
add(start + numerator / denominator * 4, `instrument:${located.change.instrument}`);
|
|
17289
17525
|
}
|
|
17290
|
-
|
|
17291
|
-
for (let change of changes)
|
|
17292
|
-
change.beatOffset > beatCursor && (absoluteTime += (change.beatOffset - beatCursor) * currentSpq, beatCursor = change.beatOffset), tempoMap.push({
|
|
17293
|
-
measureIndex: m,
|
|
17294
|
-
beatInMeasure: change.beatOffset,
|
|
17295
|
-
timeSeconds: absoluteTime,
|
|
17296
|
-
bpm: change.qpm
|
|
17297
|
-
}), m === 0 && change.beatOffset === 0 && (hasInitialTempo = !0), currentQpm = change.qpm, currentSpq = 60 / currentQpm;
|
|
17298
|
-
mBeats > beatCursor && (absoluteTime += (mBeats - beatCursor) * currentSpq);
|
|
17299
|
-
let measureHolds = holds?.[m];
|
|
17300
|
-
if (measureHolds)
|
|
17301
|
-
for (let hold of measureHolds)
|
|
17302
|
-
absoluteTime += hold.extraBeats * measureStartSpq;
|
|
17303
|
-
}
|
|
17304
|
-
return hasInitialTempo || tempoMap.unshift({
|
|
17305
|
-
measureIndex: 0,
|
|
17306
|
-
beatInMeasure: 0,
|
|
17307
|
-
timeSeconds: 0,
|
|
17308
|
-
bpm: DEFAULT_BPM
|
|
17309
|
-
}), { tempoMap, measureStartTimes };
|
|
17526
|
+
}), boundaries;
|
|
17310
17527
|
}
|
|
17311
|
-
function
|
|
17312
|
-
let
|
|
17313
|
-
|
|
17314
|
-
|
|
17315
|
-
|
|
17316
|
-
|
|
17317
|
-
|
|
17318
|
-
|
|
17319
|
-
|
|
17320
|
-
|
|
17321
|
-
|
|
17528
|
+
function applyInstrumentPlayback(score, events, timing, programs, initialPrograms) {
|
|
17529
|
+
let diagnostics = [];
|
|
17530
|
+
return score.parts.forEach((part, partIndex) => {
|
|
17531
|
+
if (playbackInstruments(part).length === 1) return;
|
|
17532
|
+
let boundaries = boundariesForPart(part, timing, programs?.get(partIndex), initialPrograms?.[partIndex]), partEvents = events.filter((event) => event.partIndex === partIndex), lanes = new Set(partEvents.flatMap((event) => event.playbackLaneId ? [event.playbackLaneId] : []));
|
|
17533
|
+
lanes.size === 0 && lanes.add(`part:${partIndex}`);
|
|
17534
|
+
let missingPrograms = [
|
|
17535
|
+
...new Set(boundaries.filter((boundary) => boundary.program === void 0).map((boundary) => boundary.key))
|
|
17536
|
+
];
|
|
17537
|
+
missingPrograms.length > 0 && diagnostics.push({
|
|
17538
|
+
code: "instrument-program-unavailable",
|
|
17539
|
+
message: `Physical MIDI cannot select ${missingPrograms.join(", ")} in "${part.name}"; provide resolved instrumentPrograms or a MIDI program fallback.`,
|
|
17540
|
+
playbackLaneIds: [...lanes]
|
|
17541
|
+
});
|
|
17542
|
+
for (let event of [...partEvents]) {
|
|
17543
|
+
if (event.playbackLaneId) continue;
|
|
17544
|
+
let [first, ...others] = lanes;
|
|
17545
|
+
event.playbackLaneId = first;
|
|
17546
|
+
for (let lane of others) {
|
|
17547
|
+
let scoped = { ...event, playbackLaneId: lane };
|
|
17548
|
+
events.push(scoped), partEvents.push(scoped);
|
|
17322
17549
|
}
|
|
17323
17550
|
}
|
|
17324
|
-
|
|
17325
|
-
|
|
17326
|
-
|
|
17327
|
-
|
|
17328
|
-
|
|
17329
|
-
|
|
17330
|
-
|
|
17331
|
-
|
|
17332
|
-
|
|
17333
|
-
|
|
17334
|
-
|
|
17335
|
-
|
|
17336
|
-
|
|
17551
|
+
let attackDelays = /* @__PURE__ */ new Map(), attackKey = (event) => JSON.stringify([event.playbackLaneId, event.channel, event.midiNote, event.scoreBeat, event.scoreEventId]);
|
|
17552
|
+
for (let event of partEvents) {
|
|
17553
|
+
if (event.type === "controlChange" && event.cc !== 74 && event.cc !== 71) continue;
|
|
17554
|
+
let beat = event.scoreBeat ?? timing.model.beatAtTime(event.time), instrument = "initial", instrumentStart = 0;
|
|
17555
|
+
for (let boundary of boundaries) {
|
|
17556
|
+
if (boundary.beat > beat + 1e-9) break;
|
|
17557
|
+
instrument = boundary.key, instrumentStart = boundary.time;
|
|
17558
|
+
}
|
|
17559
|
+
if (event.type === "noteOn") {
|
|
17560
|
+
let delay = Math.max(0, instrumentStart - event.time);
|
|
17561
|
+
attackDelays.set(attackKey(event), delay), event.time = Math.max(event.time, instrumentStart);
|
|
17562
|
+
}
|
|
17563
|
+
event.playbackInstrumentKey = instrument;
|
|
17337
17564
|
}
|
|
17338
|
-
|
|
17339
|
-
|
|
17340
|
-
|
|
17341
|
-
|
|
17342
|
-
|
|
17343
|
-
|
|
17344
|
-
|
|
17345
|
-
|
|
17346
|
-
|
|
17347
|
-
|
|
17348
|
-
|
|
17349
|
-
|
|
17565
|
+
for (let event of partEvents)
|
|
17566
|
+
event.type === "noteOff" && (event.time += attackDelays.get(attackKey(event)) ?? 0);
|
|
17567
|
+
for (let lane of lanes) {
|
|
17568
|
+
let previous;
|
|
17569
|
+
for (let boundary of boundaries)
|
|
17570
|
+
boundary.key !== previous && (previous = boundary.key, events.push({
|
|
17571
|
+
type: "programChange",
|
|
17572
|
+
time: boundary.time,
|
|
17573
|
+
scoreBeat: boundary.beat,
|
|
17574
|
+
midiNote: 0,
|
|
17575
|
+
velocity: 0,
|
|
17576
|
+
partIndex,
|
|
17577
|
+
playbackLaneId: lane,
|
|
17578
|
+
channel: partEvents.find((event) => event.playbackLaneId === lane)?.channel ?? 0,
|
|
17579
|
+
playbackInstrumentKey: boundary.key,
|
|
17580
|
+
instrumentChange: !0,
|
|
17581
|
+
program: boundary.program
|
|
17582
|
+
}));
|
|
17350
17583
|
}
|
|
17351
|
-
|
|
17352
|
-
return { model, measureStartTimes, measureStartBeats, totalBeats: cumBeats };
|
|
17353
|
-
}
|
|
17354
|
-
function posBeats(frac) {
|
|
17355
|
-
return !frac || frac[1] === 0 ? 0 : frac[0] / frac[1] * 4;
|
|
17356
|
-
}
|
|
17357
|
-
function buildMeasureIdToIndex(globalMeasures) {
|
|
17358
|
-
let m = /* @__PURE__ */ new Map();
|
|
17359
|
-
return globalMeasures.forEach((gm, i) => {
|
|
17360
|
-
gm.id && !m.has(gm.id) && m.set(gm.id, i);
|
|
17361
|
-
}), m;
|
|
17362
|
-
}
|
|
17363
|
-
function resolveGradualEndBeat(end, fromIdx, globalMeasures, measureStartBeats, idToIdx) {
|
|
17364
|
-
let idx = idToIdx.get(end.measure);
|
|
17365
|
-
if (idx === void 0) {
|
|
17366
|
-
let parsed = Number.parseInt(end.measure, 10);
|
|
17367
|
-
!Number.isNaN(parsed) && parsed >= 0 && parsed < globalMeasures.length && (idx = parsed);
|
|
17368
|
-
}
|
|
17369
|
-
idx === void 0 && (idx = fromIdx);
|
|
17370
|
-
let start = measureStartBeats[idx];
|
|
17371
|
-
if (start !== void 0)
|
|
17372
|
-
return start + posBeats(end.position.fraction);
|
|
17373
|
-
}
|
|
17374
|
-
function tempoBeat(tempo, mBeats) {
|
|
17375
|
-
return tempo.location ? tempo.location.fraction[0] / tempo.location.fraction[1] * mBeats : 0;
|
|
17584
|
+
}), diagnostics;
|
|
17376
17585
|
}
|
|
17377
17586
|
|
|
17378
17587
|
// packages/midi/src/dynamicsEnvelope.ts
|
|
17379
17588
|
init_define_import_meta_env();
|
|
17380
|
-
var CC_EXPRESSION = 11,
|
|
17589
|
+
var CC_EXPRESSION = 11, EPS2 = 1e-6, DEFAULT_DYNAMIC = { velocity: 84, cc11: 100 }, DYNAMIC_AXES = {
|
|
17381
17590
|
n: { velocity: 1, cc11: 0 },
|
|
17382
17591
|
pppppp: { velocity: 22, cc11: 11 },
|
|
17383
17592
|
ppppp: { velocity: 28, cc11: 17 },
|
|
@@ -17444,7 +17653,7 @@ function stepRung(axes, dir) {
|
|
|
17444
17653
|
function sampleAnchorsAxes(anchors, t) {
|
|
17445
17654
|
let active = DEFAULT_DYNAMIC;
|
|
17446
17655
|
for (let a of anchors) {
|
|
17447
|
-
if (a.time > t +
|
|
17656
|
+
if (a.time > t + EPS2) break;
|
|
17448
17657
|
active = { velocity: a.velocity, cc11: a.cc11 };
|
|
17449
17658
|
}
|
|
17450
17659
|
return active;
|
|
@@ -17460,24 +17669,24 @@ function gradualTextDirection(text) {
|
|
|
17460
17669
|
if (/^cresc(?:endo)?\.?$/.test(normalized)) return 1;
|
|
17461
17670
|
if (/^(?:dim(?:inuendo)?|decresc(?:endo)?)\.?$/.test(normalized)) return -1;
|
|
17462
17671
|
}
|
|
17463
|
-
function collectMeasureDynamicGroups(pm, originalMeasureIndex, measureStartBeat, model, expandedIdx, groups, staffMeterTable) {
|
|
17672
|
+
function collectMeasureDynamicGroups(pm, originalMeasureIndex, measureStartBeat, model, expandedIdx, groups, staffMeterTable, span) {
|
|
17464
17673
|
for (let group of pm.dynamics ?? []) {
|
|
17465
|
-
let ratio = staffMeterRatioAt(staffMeterTable, originalMeasureIndex, group.staff);
|
|
17466
|
-
groups.push({
|
|
17674
|
+
let ratio = staffMeterRatioAt(staffMeterTable, originalMeasureIndex, group.staff), beat = fractionToBeats2(group.position.fraction) * ratio;
|
|
17675
|
+
beatInPlayedSpan(beat, span) && groups.push({
|
|
17467
17676
|
group,
|
|
17468
|
-
time: model.timeAtBeat(measureStartBeat +
|
|
17677
|
+
time: model.timeAtBeat(measureStartBeat + beat),
|
|
17469
17678
|
expandedIdx
|
|
17470
17679
|
});
|
|
17471
17680
|
}
|
|
17472
17681
|
}
|
|
17473
|
-
function collectMeasureGradualTexts(pm, originalMeasureIndex, measureStartBeat, model, expandedIdx, gradualTexts, staffMeterTable) {
|
|
17682
|
+
function collectMeasureGradualTexts(pm, originalMeasureIndex, measureStartBeat, model, expandedIdx, gradualTexts, staffMeterTable, span) {
|
|
17474
17683
|
for (let index = 0; index < (pm.expressions?.length ?? 0); index++) {
|
|
17475
17684
|
let expression = pm.expressions[index], dir = gradualTextDirection(plainTextContent(expression.text));
|
|
17476
|
-
dir
|
|
17685
|
+
if (!dir) continue;
|
|
17686
|
+
let beat = fractionToBeats2(expression.position.fraction) * staffMeterRatioAt(staffMeterTable, originalMeasureIndex, expression.staff);
|
|
17687
|
+
beatInPlayedSpan(beat, span) && gradualTexts.push({
|
|
17477
17688
|
id: `text-gradual-${expandedIdx}-${index}`,
|
|
17478
|
-
startTime: model.timeAtBeat(
|
|
17479
|
-
measureStartBeat + fractionToBeats2(expression.position.fraction) * staffMeterRatioAt(staffMeterTable, originalMeasureIndex, expression.staff)
|
|
17480
|
-
),
|
|
17689
|
+
startTime: model.timeAtBeat(measureStartBeat + beat),
|
|
17481
17690
|
dir,
|
|
17482
17691
|
expandedIdx
|
|
17483
17692
|
});
|
|
@@ -17500,7 +17709,7 @@ function resolveEndMeasureLocation(endMeasureId, fromExpandedIdx, measureOrder,
|
|
|
17500
17709
|
}
|
|
17501
17710
|
function explicitAnchorAt(anchors, time) {
|
|
17502
17711
|
for (let a of anchors)
|
|
17503
|
-
if (Math.abs(a.time - time) <=
|
|
17712
|
+
if (Math.abs(a.time - time) <= EPS2) return { velocity: a.velocity, cc11: a.cc11 };
|
|
17504
17713
|
}
|
|
17505
17714
|
function measureHasSoundingNotes(part, measureIndex) {
|
|
17506
17715
|
return measureIndex === void 0 ? !1 : (part.measures[measureIndex]?.sequences ?? []).some(
|
|
@@ -17521,15 +17730,15 @@ function resolveRamps(timed, anchors) {
|
|
|
17521
17730
|
startsAfterSilence,
|
|
17522
17731
|
measureStartTime
|
|
17523
17732
|
} of ordered) {
|
|
17524
|
-
if (endTime <= startTime +
|
|
17525
|
-
let chained = prev !== void 0 && Math.abs(prev.endTime - startTime) <=
|
|
17526
|
-
(anchor) => anchor.time >= measureStartTime -
|
|
17733
|
+
if (endTime <= startTime + EPS2) continue;
|
|
17734
|
+
let chained = prev !== void 0 && Math.abs(prev.endTime - startTime) <= EPS2, currentMeasureAnchor = anchors.some(
|
|
17735
|
+
(anchor) => anchor.time >= measureStartTime - EPS2 && anchor.time <= startTime + EPS2
|
|
17527
17736
|
), start = authoredStart ?? explicitAnchorAt(anchors, startTime) ?? (startsAfterSilence && !currentMeasureAnchor ? axesOf("p") : chained ? prev.end : sampleAnchorsAxes(anchors, startTime)), end = explicitAnchorAt(anchors, endTime) ?? stepRung(start, dir), ramp = { groupId, startTime, endTime, start, end };
|
|
17528
17737
|
ramps.push(ramp), prev = ramp;
|
|
17529
17738
|
}
|
|
17530
17739
|
return ramps;
|
|
17531
17740
|
}
|
|
17532
|
-
function collectExpandedDynamics(part, measureOrder, measureStartBeats, model, staffMeterTable, timedGroups, gradualTexts) {
|
|
17741
|
+
function collectExpandedDynamics(part, measureOrder, measureStartBeats, model, staffMeterTable, timedGroups, gradualTexts, playedSpans) {
|
|
17533
17742
|
for (let expandedIdx = 0; expandedIdx < measureOrder.length; expandedIdx++) {
|
|
17534
17743
|
let originalMeasureIndex = measureOrder[expandedIdx], measure = part.measures[originalMeasureIndex];
|
|
17535
17744
|
if (!measure) continue;
|
|
@@ -17541,7 +17750,8 @@ function collectExpandedDynamics(part, measureOrder, measureStartBeats, model, s
|
|
|
17541
17750
|
model,
|
|
17542
17751
|
expandedIdx,
|
|
17543
17752
|
timedGroups,
|
|
17544
|
-
staffMeterTable
|
|
17753
|
+
staffMeterTable,
|
|
17754
|
+
playedSpans?.[expandedIdx]
|
|
17545
17755
|
), collectMeasureGradualTexts(
|
|
17546
17756
|
measure,
|
|
17547
17757
|
originalMeasureIndex,
|
|
@@ -17549,13 +17759,23 @@ function collectExpandedDynamics(part, measureOrder, measureStartBeats, model, s
|
|
|
17549
17759
|
model,
|
|
17550
17760
|
expandedIdx,
|
|
17551
17761
|
gradualTexts,
|
|
17552
|
-
staffMeterTable
|
|
17762
|
+
staffMeterTable,
|
|
17763
|
+
playedSpans?.[expandedIdx]
|
|
17553
17764
|
);
|
|
17554
17765
|
}
|
|
17555
17766
|
}
|
|
17556
|
-
function buildDynamicsEnvelope(part, measureOrder, measureStartBeats, model, globalMeasures, impliedAnchors = [], resolvedStaffMeters) {
|
|
17767
|
+
function buildDynamicsEnvelope(part, measureOrder, measureStartBeats, model, globalMeasures, impliedAnchors = [], resolvedStaffMeters, playedSpans) {
|
|
17557
17768
|
let anchors = [], attacks = [], timedGroups = [], gradualTexts = [], idToOrig = buildMeasureIdToOrig(globalMeasures), staffMeterTable = resolvedStaffMeters ?? resolvePartStaffMeterTable(part, globalMeasures);
|
|
17558
|
-
collectExpandedDynamics(
|
|
17769
|
+
collectExpandedDynamics(
|
|
17770
|
+
part,
|
|
17771
|
+
measureOrder,
|
|
17772
|
+
measureStartBeats,
|
|
17773
|
+
model,
|
|
17774
|
+
staffMeterTable,
|
|
17775
|
+
timedGroups,
|
|
17776
|
+
gradualTexts,
|
|
17777
|
+
playedSpans
|
|
17778
|
+
);
|
|
17559
17779
|
let typeRank = { immediate: 0, relative: 1, accent: 2, gradual: 3 };
|
|
17560
17780
|
timedGroups.sort(
|
|
17561
17781
|
(a, b) => a.time - b.time || typeRank[a.group.type] - typeRank[b.group.type] || a.group.id.localeCompare(b.group.id)
|
|
@@ -17565,7 +17785,7 @@ function buildDynamicsEnvelope(part, measureOrder, measureStartBeats, model, glo
|
|
|
17565
17785
|
let body = axesOf(group.value);
|
|
17566
17786
|
anchors.push({ time, velocity: body.velocity, cc11: body.cc11 });
|
|
17567
17787
|
} else if (group.type === "relative") {
|
|
17568
|
-
let current = sampleAnchorsAxes(anchors, time -
|
|
17788
|
+
let current = sampleAnchorsAxes(anchors, time - EPS2), next = stepRung(current, group.relativeValue === "louder" ? 1 : -1);
|
|
17569
17789
|
anchors.push({ time, velocity: next.velocity, cc11: next.cc11 });
|
|
17570
17790
|
} else if (group.type === "accent" && (attacks.push({ time, attackVelocity: axesOf(group.value).velocity }), group.residualValue !== void 0)) {
|
|
17571
17791
|
let body = axesOf(group.residualValue);
|
|
@@ -17574,9 +17794,9 @@ function buildDynamicsEnvelope(part, measureOrder, measureStartBeats, model, glo
|
|
|
17574
17794
|
for (let expandedIdx = 0; expandedIdx < measureOrder.length; expandedIdx++) {
|
|
17575
17795
|
let measureIndex = measureOrder[expandedIdx];
|
|
17576
17796
|
for (let implied of impliedAnchors) {
|
|
17577
|
-
if (implied.measureIndex !== measureIndex) continue;
|
|
17797
|
+
if (implied.measureIndex !== measureIndex || !beatInPlayedSpan(fractionToBeats2(implied.position), playedSpans?.[expandedIdx])) continue;
|
|
17578
17798
|
let time = model.timeAtBeat(measureStartBeats[expandedIdx] + fractionToBeats2(implied.position));
|
|
17579
|
-
if (anchors.some((anchor) => Math.abs(anchor.time - time) <=
|
|
17799
|
+
if (anchors.some((anchor) => Math.abs(anchor.time - time) <= EPS2)) continue;
|
|
17580
17800
|
let axes = axesOf(implied.value);
|
|
17581
17801
|
anchors.push({ time, velocity: axes.velocity, cc11: axes.cc11 });
|
|
17582
17802
|
}
|
|
@@ -17611,8 +17831,8 @@ function buildDynamicsEnvelope(part, measureOrder, measureStartBeats, model, glo
|
|
|
17611
17831
|
});
|
|
17612
17832
|
}
|
|
17613
17833
|
for (let text of gradualTexts) {
|
|
17614
|
-
if (timed.some((hairpin) => Math.abs(hairpin.startTime - text.startTime) <=
|
|
17615
|
-
let endpoint = anchors.find((anchor) => anchor.time > text.startTime +
|
|
17834
|
+
if (timed.some((hairpin) => Math.abs(hairpin.startTime - text.startTime) <= EPS2)) continue;
|
|
17835
|
+
let endpoint = anchors.find((anchor) => anchor.time > text.startTime + EPS2);
|
|
17616
17836
|
endpoint && timed.push({
|
|
17617
17837
|
groupId: text.id,
|
|
17618
17838
|
startTime: text.startTime,
|
|
@@ -17627,7 +17847,7 @@ function buildDynamicsEnvelope(part, measureOrder, measureStartBeats, model, glo
|
|
|
17627
17847
|
}
|
|
17628
17848
|
function rampAt(env, t) {
|
|
17629
17849
|
for (let r of env.ramps)
|
|
17630
|
-
if (t >= r.startTime -
|
|
17850
|
+
if (t >= r.startTime - EPS2 && t <= r.endTime + EPS2) return r;
|
|
17631
17851
|
}
|
|
17632
17852
|
function sampleDynamics(env, t) {
|
|
17633
17853
|
let r = rampAt(env, t);
|
|
@@ -17639,7 +17859,7 @@ function sampleDynamics(env, t) {
|
|
|
17639
17859
|
}
|
|
17640
17860
|
function attackAt(env, t) {
|
|
17641
17861
|
for (let c of env.attacks)
|
|
17642
|
-
if (Math.abs(c.time - t) <=
|
|
17862
|
+
if (Math.abs(c.time - t) <= EPS2) return c;
|
|
17643
17863
|
}
|
|
17644
17864
|
function noteVelocityAt(env, t) {
|
|
17645
17865
|
let c = attackAt(env, t);
|
|
@@ -17647,7 +17867,7 @@ function noteVelocityAt(env, t) {
|
|
|
17647
17867
|
}
|
|
17648
17868
|
function rampQuantizationEvents(ramp) {
|
|
17649
17869
|
let start = ramp.start.cc11, end = ramp.end.cc11;
|
|
17650
|
-
if (start === end || ramp.endTime <= ramp.startTime +
|
|
17870
|
+
if (start === end || ramp.endTime <= ramp.startTime + EPS2)
|
|
17651
17871
|
return [
|
|
17652
17872
|
{ time: ramp.startTime, value: start },
|
|
17653
17873
|
{ time: ramp.endTime, value: end }
|
|
@@ -17670,13 +17890,13 @@ function cc11Events(env) {
|
|
|
17670
17890
|
let out = [];
|
|
17671
17891
|
for (let e of raw) {
|
|
17672
17892
|
let prev = out[out.length - 1];
|
|
17673
|
-
if (prev && Math.abs(prev.time - e.time) <=
|
|
17893
|
+
if (prev && Math.abs(prev.time - e.time) <= EPS2) {
|
|
17674
17894
|
out[out.length - 1] = e;
|
|
17675
17895
|
continue;
|
|
17676
17896
|
}
|
|
17677
17897
|
prev && prev.value === e.value || out.push(e);
|
|
17678
17898
|
}
|
|
17679
|
-
return (out.length === 0 || out[0].time >
|
|
17899
|
+
return (out.length === 0 || out[0].time > EPS2) && out.unshift({ time: 0, value: DEFAULT_DYNAMIC.cc11 }), out;
|
|
17680
17900
|
}
|
|
17681
17901
|
function emitDynamicsCc11(env, partIndex, channel, out, playbackLaneId2) {
|
|
17682
17902
|
for (let ev of cc11Events(env))
|
|
@@ -17819,7 +18039,7 @@ function collectRampDiagnostics(lanes) {
|
|
|
17819
18039
|
}
|
|
17820
18040
|
return diagnostics;
|
|
17821
18041
|
}
|
|
17822
|
-
function compileDynamicProgram(part, partIndex, measureOrder, measureStartBeats, model, globalMeasures, gmProgram = -1, impliedAnchors = [], staffMeterTable) {
|
|
18042
|
+
function compileDynamicProgram(part, partIndex, measureOrder, measureStartBeats, model, globalMeasures, gmProgram = -1, impliedAnchors = [], staffMeterTable, playedSpans) {
|
|
17823
18043
|
let scoped = part.measures.some(
|
|
17824
18044
|
(measure) => measure.dynamics?.some((group) => group.staff !== void 0 || group.voice !== void 0)
|
|
17825
18045
|
), scopes = collectLaneScopes(part), selectedScopes = scoped ? scopes : [scopes[0]], profile = selectDynamicResponseProfile(gmProgram), lanes = /* @__PURE__ */ new Map();
|
|
@@ -17832,7 +18052,8 @@ function compileDynamicProgram(part, partIndex, measureOrder, measureStartBeats,
|
|
|
17832
18052
|
model,
|
|
17833
18053
|
globalMeasures,
|
|
17834
18054
|
impliedAnchors,
|
|
17835
|
-
staffMeterTable
|
|
18055
|
+
staffMeterTable,
|
|
18056
|
+
playedSpans
|
|
17836
18057
|
),
|
|
17837
18058
|
profile
|
|
17838
18059
|
);
|
|
@@ -17887,13 +18108,13 @@ function generateTimeline(inputScore, options) {
|
|
|
17887
18108
|
let score = expandScoreMeasureRepeats(inputScore), impliedSectionDynamics = analyzeImpliedSectionDynamics(score), globalMeasures = score.global.measures;
|
|
17888
18109
|
if (globalMeasures.length === 0)
|
|
17889
18110
|
return emptyTimeline();
|
|
17890
|
-
let chords = compileChordPlayback(score, options?.includeGlobalChords),
|
|
18111
|
+
let chords = compileChordPlayback(score, options?.includeGlobalChords), spans = options?.expandRepeats === !1 ? void 0 : scorePlaybackItinerary(score), measureOrder = options?.expandRepeats === !1 ? globalMeasures.map((_, index) => index) : spans.map((span) => span.measureIndex);
|
|
17891
18112
|
if (measureOrder.length === 0)
|
|
17892
18113
|
return emptyTimeline();
|
|
17893
18114
|
let expandedGlobal = playbackGlobalMeasures(score, measureOrder), holdSchedule = suppressCadenzaFermataHolds(
|
|
17894
18115
|
expandedGlobal,
|
|
17895
18116
|
buildHoldSchedule(score, measureOrder, globalMeasures)
|
|
17896
|
-
), { tempoMap } = buildTempoMap(expandedGlobal, holdSchedule), tempoBuild = buildTempoModel(expandedGlobal, holdSchedule), { model, measureStartTimes, measureStartBeats } = tempoBuild, timing = { tempoMap, measureStartTimes, model, measureStartBeats }, allEvents = [], partChannels = allocatePartChannels(score.parts);
|
|
18117
|
+
), { tempoMap } = buildTempoMap(expandedGlobal, holdSchedule), tempoBuild = buildTempoModel(expandedGlobal, holdSchedule), { model, measureStartTimes, measureStartBeats } = tempoBuild, timing = { tempoMap, measureStartTimes, model, measureStartBeats, playedSpans: spans }, allEvents = [], partChannels = allocatePartChannels(score.parts);
|
|
17897
18118
|
for (let partIdx = 0; partIdx < score.parts.length; partIdx++)
|
|
17898
18119
|
processPart(
|
|
17899
18120
|
score,
|
|
@@ -17926,8 +18147,7 @@ function generateTimeline(inputScore, options) {
|
|
|
17926
18147
|
let duration = model.timeAtBeat(tempoBuild.totalBeats), measureTimeSignatures = measureOrder.map((origIdx) => {
|
|
17927
18148
|
let ts = resolveActiveTime(globalMeasures, origIdx);
|
|
17928
18149
|
return { count: ts.count, unit: ts.unit };
|
|
17929
|
-
})
|
|
17930
|
-
return {
|
|
18150
|
+
}), result = {
|
|
17931
18151
|
events: allEvents,
|
|
17932
18152
|
duration,
|
|
17933
18153
|
tempoMap,
|
|
@@ -17939,6 +18159,7 @@ function generateTimeline(inputScore, options) {
|
|
|
17939
18159
|
measureTimeSignatures,
|
|
17940
18160
|
diagnostics
|
|
17941
18161
|
};
|
|
18162
|
+
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 };
|
|
17942
18163
|
}
|
|
17943
18164
|
function assignPhysicalMidiChannels(events, score) {
|
|
17944
18165
|
let melodicChannels = [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15], lanesByPart = /* @__PURE__ */ new Map();
|
|
@@ -18034,12 +18255,14 @@ function processPart(score, partIdx, options, partChannels, timing, measureOrder
|
|
|
18034
18255
|
globalMeasures,
|
|
18035
18256
|
gmProgram,
|
|
18036
18257
|
impliedDynamics,
|
|
18037
|
-
staffMeterTable
|
|
18258
|
+
staffMeterTable,
|
|
18259
|
+
timing.playedSpans
|
|
18038
18260
|
);
|
|
18039
18261
|
for (let lane of dynamicProgram.lanes.values())
|
|
18040
18262
|
emitDynamicsCc11(lane.envelope, partIdx, channel, allEvents, lane.id);
|
|
18041
18263
|
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;
|
|
18042
18264
|
for (let expandedIdx = 0; expandedIdx < measureOrder.length; expandedIdx++) {
|
|
18265
|
+
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());
|
|
18043
18266
|
let origMeasureIdx = measureOrder[expandedIdx], partMeasure = part.measures[origMeasureIdx];
|
|
18044
18267
|
if (!partMeasure) continue;
|
|
18045
18268
|
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;
|
|
@@ -18049,6 +18272,8 @@ function processPart(score, partIdx, options, partChannels, timing, measureOrder
|
|
|
18049
18272
|
spanEndBeat: h.spanEndBeat ?? h.atBeat,
|
|
18050
18273
|
insertBeat: h.atBeat
|
|
18051
18274
|
})), ctx = {
|
|
18275
|
+
playedSpan: timing.playedSpans?.[expandedIdx],
|
|
18276
|
+
graceBoundaryBeat: timing.playedSpans && (expandedIdx === 0 || !playedSpansContinue(timing.playedSpans[expandedIdx - 1], timing.playedSpans[expandedIdx])) ? timing.playedSpans[expandedIdx].startBeat : void 0,
|
|
18052
18277
|
tempoMap,
|
|
18053
18278
|
model,
|
|
18054
18279
|
measureStartBeat,
|
|
@@ -18079,7 +18304,12 @@ function processPart(score, partIdx, options, partChannels, timing, measureOrder
|
|
|
18079
18304
|
fermataGroups
|
|
18080
18305
|
};
|
|
18081
18306
|
(techniqueEnabled || instruments.size > 1) && (techniqueState = applyMeasureTechniques(
|
|
18082
|
-
partMeasure.expressions
|
|
18307
|
+
partMeasure.expressions?.filter(
|
|
18308
|
+
(expression) => beatInPlayedSpan(
|
|
18309
|
+
fractionToBeats(expression.position.fraction) * staffMeterRatioAt(staffMeterTable, origMeasureIdx, expression.staff),
|
|
18310
|
+
timing.playedSpans?.[expandedIdx]
|
|
18311
|
+
)
|
|
18312
|
+
),
|
|
18083
18313
|
ctx,
|
|
18084
18314
|
techniqueState,
|
|
18085
18315
|
{
|
|
@@ -18180,6 +18410,7 @@ function tryProcessSpecialEvent(ctx, event, measureStartTime, beatOffset, baseVe
|
|
|
18180
18410
|
function processNoteEvent(ctx, event, measureStartTime, beatOffset, baseVelocity, tupletRatio) {
|
|
18181
18411
|
ctx.gmProgram = ctx.instrumentAtBeatOffset?.(beatOffset * ctx.staffMeterRatio).program ?? ctx.gmProgram;
|
|
18182
18412
|
let { partIndex, channel, tieTargets, out, timeSig, kitMidiMap, kitAltProgramMap } = ctx, beats = durationBeats4(event.duration) * tupletRatio;
|
|
18413
|
+
if (!beatInPlayedSpan(beatOffset * ctx.staffMeterRatio, ctx.playedSpan)) return beats;
|
|
18183
18414
|
if (isRest(event))
|
|
18184
18415
|
return flushVoiceLegato(ctx.pendingLegato, ctx.voiceKey, ctx.out), beats;
|
|
18185
18416
|
resolveVoiceLegato({
|
|
@@ -18210,7 +18441,7 @@ function processNoteEvent(ctx, event, measureStartTime, beatOffset, baseVelocity
|
|
|
18210
18441
|
for (let note of event.notes ?? []) {
|
|
18211
18442
|
let midiNote = noteToMidi(note);
|
|
18212
18443
|
if (midiNote < 0 || midiNote > 127) continue;
|
|
18213
|
-
let noteId = note.id ?? "", isTiedTo = tieTargets.has(noteId), hasTieOut = note.ties?.some((t) => t.target) ?? !1;
|
|
18444
|
+
let noteId = note.id ?? "", isTiedTo = tieTargets.has(noteId) && ctx.pendingTieOffs.has(`${midiNote}:${partIndex}:${ctx.voiceKey}`), hasTieOut = note.ties?.some((t) => t.target) ?? !1;
|
|
18214
18445
|
isTiedTo ? extendOrFinalizeTie(ctx, midiNote, startTime, durationSec, durationScale, hasTieOut) : emitFreshNote(
|
|
18215
18446
|
ctx,
|
|
18216
18447
|
midiNote,
|
|
@@ -18488,19 +18719,22 @@ function tryProcessTrill(ctx, event, measureStartTime, beatOffset, baseVelocity,
|
|
|
18488
18719
|
function processGrace(ctx, grace, measureStartTime, beatOffset, tupletRatio) {
|
|
18489
18720
|
let events = grace.content;
|
|
18490
18721
|
if (events.length === 0) return 0;
|
|
18491
|
-
let graceBeats = GRACE_NOTE_BEATS * tupletRatio, totalGraceBeats = events.length * graceBeats;
|
|
18492
|
-
if (
|
|
18722
|
+
let graceBeats = GRACE_NOTE_BEATS * tupletRatio, totalGraceBeats = events.length * graceBeats, following = grace.graceType === "stealFollowing", principalBeat = beatOffset + (following ? totalGraceBeats : 0);
|
|
18723
|
+
if (!beatInPlayedSpan(principalBeat * ctx.staffMeterRatio, ctx.playedSpan))
|
|
18724
|
+
return following ? totalGraceBeats : 0;
|
|
18725
|
+
let graceCtx = { ...ctx, playedSpan: void 0 };
|
|
18726
|
+
if (following) {
|
|
18493
18727
|
let cursor2 = beatOffset;
|
|
18494
18728
|
for (let evt of events) {
|
|
18495
18729
|
let ratio = graceBeats / durationBeats4(evt.duration);
|
|
18496
|
-
processScopedNoteEvent(
|
|
18730
|
+
processScopedNoteEvent(graceCtx, evt, measureStartTime, cursor2, ratio), cursor2 += graceBeats;
|
|
18497
18731
|
}
|
|
18498
18732
|
return totalGraceBeats;
|
|
18499
18733
|
}
|
|
18500
|
-
let cursor = beatOffset - totalGraceBeats;
|
|
18734
|
+
let cursor = Math.max(beatOffset - totalGraceBeats, (ctx.graceBoundaryBeat ?? -1 / 0) / ctx.staffMeterRatio);
|
|
18501
18735
|
for (let evt of events) {
|
|
18502
18736
|
let ratio = graceBeats / durationBeats4(evt.duration);
|
|
18503
|
-
processScopedNoteEvent(
|
|
18737
|
+
processScopedNoteEvent(graceCtx, evt, measureStartTime, cursor, ratio), cursor += graceBeats;
|
|
18504
18738
|
}
|
|
18505
18739
|
return 0;
|
|
18506
18740
|
}
|