@viritura/score-engine 0.2.1-next.45 → 0.2.1-next.47

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -2265,9 +2265,9 @@ function interpolateBeatAnchors(bounds, beat, endX) {
2265
2265
 
2266
2266
  // packages/score-engine/src/buildInfo.ts
2267
2267
  init_define_import_meta_env();
2268
- var PACKAGE_VERSION = "0.2.1-next.45", BUNDLE_WORKER_FILE = "score-engine.worker.js";
2268
+ var PACKAGE_VERSION = "0.2.1-next.47", BUNDLE_WORKER_FILE = "score-engine.worker.js";
2269
2269
  function buildCommit() {
2270
- return "2d5b797c6600d93f903b46e4ce31ef2b057c1763";
2270
+ return "78ae5d80a2cd938fb7b991f4bdebebbc72b18896";
2271
2271
  }
2272
2272
  function bundledAssetBase() {
2273
2273
  let moduleUrl = import.meta.url;
@@ -13058,7 +13058,7 @@ function validate460(data, { instancePath = "", parentData, parentDataProperty,
13058
13058
  return validate460.errors = vErrors, errors === 0;
13059
13059
  }
13060
13060
  validate460.evaluated = { props: !0, dynamicProps: !1, dynamicItems: !1 };
13061
- var schema455 = { type: "string", enum: ["glissando", "portamento"], description: "Semantic kind of a glissando-family line." }, schema456 = { type: "string", enum: ["straight", "wavy"], description: "Glissando/portamento line style." };
13061
+ var schema454 = { type: "object", description: "A glissando or portamento connecting this event to a target event.", required: ["target"], properties: { target: { type: "string", description: "ID of the target event." }, kind: { $ref: "#/$defs/glissando-kind", description: "Semantic kind. Default: 'glissando'." }, style: { $ref: "#/$defs/glissando-style", description: "Line style. Default: 'straight'." }, playbackType: { type: "string", enum: ["chromatic", "diatonic", "pitch-bend"], description: "Playback interpolation. Omission means diatonic for glissando and pitch-bend for portamento." }, text: { type: "string", description: "Optional text label (e.g. 'gliss.', 'port.')." }, showText: { type: "boolean", description: "Whether to display the text label. Default: true." } }, additionalProperties: !1 }, schema455 = { type: "string", enum: ["glissando", "portamento"], description: "Semantic kind of a glissando-family line." }, schema456 = { type: "string", enum: ["straight", "wavy"], description: "Glissando/portamento line style." };
13062
13062
  function validate462(data, { instancePath = "", parentData, parentDataProperty, rootData = data, dynamicAnchors = {} } = {}) {
13063
13063
  let vErrors = null, errors = 0, evaluated0 = validate462.evaluated;
13064
13064
  if (evaluated0.dynamicProps && (evaluated0.props = void 0), evaluated0.dynamicItems && (evaluated0.items = void 0), data && typeof data == "object" && !Array.isArray(data)) {
@@ -13067,7 +13067,7 @@ function validate462(data, { instancePath = "", parentData, parentDataProperty,
13067
13067
  vErrors === null ? vErrors = [err0] : vErrors.push(err0), errors++;
13068
13068
  }
13069
13069
  for (let key0 in data)
13070
- if (!(key0 === "target" || key0 === "kind" || key0 === "style" || key0 === "text" || key0 === "showText")) {
13070
+ if (!(key0 === "target" || key0 === "kind" || key0 === "style" || key0 === "playbackType" || key0 === "text" || key0 === "showText")) {
13071
13071
  let err1 = { instancePath, schemaPath: "#/additionalProperties", keyword: "additionalProperties", params: { additionalProperty: key0 }, message: "must NOT have additional properties" };
13072
13072
  vErrors === null ? vErrors = [err1] : vErrors.push(err1), errors++;
13073
13073
  }
@@ -13097,17 +13097,28 @@ function validate462(data, { instancePath = "", parentData, parentDataProperty,
13097
13097
  vErrors === null ? vErrors = [err6] : vErrors.push(err6), errors++;
13098
13098
  }
13099
13099
  }
13100
+ if (data.playbackType !== void 0) {
13101
+ let data3 = data.playbackType;
13102
+ if (typeof data3 != "string") {
13103
+ let err7 = { instancePath: instancePath + "/playbackType", schemaPath: "#/properties/playbackType/type", keyword: "type", params: { type: "string" }, message: "must be string" };
13104
+ vErrors === null ? vErrors = [err7] : vErrors.push(err7), errors++;
13105
+ }
13106
+ if (!(data3 === "chromatic" || data3 === "diatonic" || data3 === "pitch-bend")) {
13107
+ let err8 = { instancePath: instancePath + "/playbackType", schemaPath: "#/properties/playbackType/enum", keyword: "enum", params: { allowedValues: schema454.properties.playbackType.enum }, message: "must be equal to one of the allowed values" };
13108
+ vErrors === null ? vErrors = [err8] : vErrors.push(err8), errors++;
13109
+ }
13110
+ }
13100
13111
  if (data.text !== void 0 && typeof data.text != "string") {
13101
- let err7 = { instancePath: instancePath + "/text", schemaPath: "#/properties/text/type", keyword: "type", params: { type: "string" }, message: "must be string" };
13102
- vErrors === null ? vErrors = [err7] : vErrors.push(err7), errors++;
13112
+ let err9 = { instancePath: instancePath + "/text", schemaPath: "#/properties/text/type", keyword: "type", params: { type: "string" }, message: "must be string" };
13113
+ vErrors === null ? vErrors = [err9] : vErrors.push(err9), errors++;
13103
13114
  }
13104
13115
  if (data.showText !== void 0 && typeof data.showText != "boolean") {
13105
- let err8 = { instancePath: instancePath + "/showText", schemaPath: "#/properties/showText/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" };
13106
- vErrors === null ? vErrors = [err8] : vErrors.push(err8), errors++;
13116
+ let err10 = { instancePath: instancePath + "/showText", schemaPath: "#/properties/showText/type", keyword: "type", params: { type: "boolean" }, message: "must be boolean" };
13117
+ vErrors === null ? vErrors = [err10] : vErrors.push(err10), errors++;
13107
13118
  }
13108
13119
  } else {
13109
- let err9 = { instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" };
13110
- vErrors === null ? vErrors = [err9] : vErrors.push(err9), errors++;
13120
+ let err11 = { instancePath, schemaPath: "#/type", keyword: "type", params: { type: "object" }, message: "must be object" };
13121
+ vErrors === null ? vErrors = [err11] : vErrors.push(err11), errors++;
13111
13122
  }
13112
13123
  return validate462.errors = vErrors, errors === 0;
13113
13124
  }
@@ -16266,7 +16277,7 @@ function parseSlur(raw) {
16266
16277
  }
16267
16278
  function parseGlissando(raw) {
16268
16279
  let gliss = { target: raw.target };
16269
- return raw.kind && (gliss.kind = raw.kind), raw.style && (gliss.style = raw.style), raw.text && (gliss.text = raw.text), raw.showText !== void 0 && (gliss.showText = raw.showText), gliss;
16280
+ return raw.kind && (gliss.kind = raw.kind), raw.style && (gliss.style = raw.style), raw.playbackType && (gliss.playbackType = raw.playbackType), raw.text && (gliss.text = raw.text), raw.showText !== void 0 && (gliss.showText = raw.showText), gliss;
16270
16281
  }
16271
16282
  function parseNotes(arr) {
16272
16283
  return arr.map(parseNote);
@@ -17765,7 +17776,7 @@ function realizeNavigation(timeline, spans, crossJumpContinuations = /* @__PURE_
17765
17776
  events
17766
17777
  ), outputBeat += span.endBeat - span.startBeat;
17767
17778
  }
17768
- let rank = { noteOff: 0, programChange: 1, controlChange: 2, noteOn: 3 };
17779
+ let rank = { noteOff: 0, programChange: 1, controlChange: 2, pitchBend: 2, noteOn: 3 };
17769
17780
  return events.sort((a, b) => a.time - b.time || rank[a.type] - rank[b.type]), {
17770
17781
  ...timeline,
17771
17782
  events,
@@ -17906,6 +17917,159 @@ function expandScoreMeasureRepeats(score) {
17906
17917
  return parts.every((part, index) => part === score.parts[index]) ? score : { ...score, parts };
17907
17918
  }
17908
17919
 
17920
+ // packages/midi/src/glissandoPlayback/index.ts
17921
+ init_define_import_meta_env();
17922
+ function linesForPart(score, partIndex) {
17923
+ let lines = [];
17924
+ for (let measure of score.parts[partIndex]?.measures ?? [])
17925
+ for (let sequence of measure.sequences)
17926
+ for (let { event } of walkSequenceEvents(sequence.content))
17927
+ if (event.glissandos?.length) {
17928
+ if (!event.id) throw new Error("Glissando source event requires an ID.");
17929
+ for (let glissando of event.glissandos) lines.push({ source: event.id, glissando });
17930
+ }
17931
+ return lines;
17932
+ }
17933
+ function noteKey(event) {
17934
+ return JSON.stringify([event.partIndex, event.playbackLaneId, event.playbackInstrumentKey, event.midiNote]);
17935
+ }
17936
+ function releasesFor(events) {
17937
+ let held = /* @__PURE__ */ new Map(), releases = /* @__PURE__ */ new Map();
17938
+ for (let event of events) {
17939
+ let key = noteKey(event);
17940
+ if (event.type === "noteOn") {
17941
+ let queue = held.get(key) ?? [];
17942
+ queue.push(event), held.set(key, queue);
17943
+ } else if (event.type === "noteOff") {
17944
+ let onset = held.get(key)?.shift();
17945
+ onset && releases.set(onset, event);
17946
+ }
17947
+ }
17948
+ return releases;
17949
+ }
17950
+ function measureAt(timeline, beat) {
17951
+ let index = 0;
17952
+ for (; index + 1 < timeline.measureStartBeats.length && timeline.measureStartBeats[index + 1] <= beat + 1e-9; )
17953
+ index++;
17954
+ return index;
17955
+ }
17956
+ function continuousTo(timeline, start, end) {
17957
+ let startBeat = start.scoreBeat ?? timeline.model.beatAtTime(start.time), endBeat = end.scoreBeat ?? timeline.model.beatAtTime(end.time);
17958
+ for (let index = measureAt(timeline, startBeat); index < measureAt(timeline, endBeat); index++)
17959
+ if (timeline.playedSpans) {
17960
+ if (!playedSpansContinue(timeline.playedSpans[index], timeline.playedSpans[index + 1])) return !1;
17961
+ } else if (timeline.expandedMeasureToOriginal[index + 1] !== timeline.expandedMeasureToOriginal[index] + 1)
17962
+ return !1;
17963
+ return !0;
17964
+ }
17965
+ function keyPitches(score, timeline, onset) {
17966
+ let beat = onset.scoreBeat ?? timeline.model.beatAtTime(onset.time), measureIndex = timeline.expandedMeasureToOriginal[measureAt(timeline, beat)], fifths = 0;
17967
+ for (let index = 0; index <= measureIndex; index++)
17968
+ fifths = score.global.measures[index]?.key?.fifths ?? fifths;
17969
+ let steps = ["C", "D", "E", "F", "G", "A", "B"], pitches = [0, 2, 4, 5, 7, 9, 11], order = fifths >= 0 ? SHARP_ORDER : FLAT_ORDER;
17970
+ for (let index = 0; index < Math.abs(fifths); index++) {
17971
+ let position = steps.indexOf(order[index % order.length]);
17972
+ pitches[position] = pitches[position] + Math.sign(fifths);
17973
+ }
17974
+ return new Set(pitches.map((pitch) => (pitch % 12 + 12) % 12));
17975
+ }
17976
+ function steppedPitches(start, end, key) {
17977
+ let direction = Math.sign(end - start), pitches = [start];
17978
+ for (let pitch = start + direction; direction && direction * (end - pitch) > 0; pitch += direction)
17979
+ (!key || key.has(pitch % 12)) && pitches.push(pitch);
17980
+ return pitches;
17981
+ }
17982
+ function renderSteps(on, off, target, pitches, timeline) {
17983
+ let events = [], duration2 = target.time - on.time;
17984
+ return pitches.forEach((midiNote, index) => {
17985
+ let time = on.time + duration2 * index / pitches.length, end = on.time + duration2 * (index + 1) / pitches.length;
17986
+ events.push({
17987
+ ...on,
17988
+ midiNote,
17989
+ time,
17990
+ scoreBeat: timeline.model.beatAtTime(time),
17991
+ scoreDurationBeats: timeline.model.beatAtTime(end) - timeline.model.beatAtTime(time)
17992
+ }), events.push({ ...off, midiNote, time: end });
17993
+ }), events;
17994
+ }
17995
+ function renderBend(on, off, target, lane) {
17996
+ let base = { ...on, playbackLaneId: lane }, end = target.time, range = Math.max(2, Math.abs(target.midiNote - on.midiNote)), control = (cc, value) => ({
17997
+ ...base,
17998
+ type: "controlChange",
17999
+ time: on.time,
18000
+ cc,
18001
+ value
18002
+ }), events = [
18003
+ control(101, 0),
18004
+ control(100, 0),
18005
+ control(6, range),
18006
+ control(38, 0),
18007
+ control(101, 127),
18008
+ control(100, 127),
18009
+ base,
18010
+ { ...off, time: end, playbackLaneId: lane }
18011
+ ], samples = Math.max(1, Math.ceil((end - on.time) * 200));
18012
+ for (let index = 0; index < samples; index++) {
18013
+ let fraction = index / samples, delta = (target.midiNote - on.midiNote) * fraction;
18014
+ events.push({
18015
+ ...base,
18016
+ type: "pitchBend",
18017
+ time: on.time + (end - on.time) * fraction,
18018
+ bendRange: range,
18019
+ value: Math.max(0, Math.min(16383, Math.round(8192 + delta * 8192 / range)))
18020
+ });
18021
+ }
18022
+ return events.push({ ...base, type: "controlChange", time: end, cc: 120, value: 0 }), events.push({ ...base, type: "pitchBend", time: end, value: 8192, bendRange: range }), events;
18023
+ }
18024
+ function applyGlissandoPlayback(score, timeline) {
18025
+ let partLines = score.parts.map((_part, partIndex) => linesForPart(score, partIndex));
18026
+ if (partLines.every((lines) => lines.length === 0)) return [];
18027
+ let diagnostics = [], original = [...timeline.events], releases = releasesFor(original), removed = /* @__PURE__ */ new Set(), added = [], mirroredLanes = /* @__PURE__ */ new Set();
18028
+ return partLines.forEach((lines, partIndex) => {
18029
+ for (let { source, glissando } of lines) {
18030
+ let onsets = original.filter((event) => event.partIndex === partIndex && event.type === "noteOn"), sources = onsets.filter((event) => event.scoreEventId === source), exactTargets = onsets.filter((event) => event.scoreEventId === glissando.target), targets = exactTargets.length > 0 ? exactTargets : onsets.filter((event) => event.scoreEventId?.replaceAll("/", "_") === glissando.target), realized = !1;
18031
+ for (let onset of sources) {
18032
+ if (removed.has(onset)) continue;
18033
+ let time = targets.find(
18034
+ (target2) => target2.time > onset.time + 1e-9 && continuousTo(timeline, onset, target2)
18035
+ )?.time;
18036
+ if (time === void 0) continue;
18037
+ let sourceChord = sources.filter((event) => Math.abs(event.time - onset.time) < 1e-9).sort((a, b) => a.midiNote - b.midiNote), targetChord = targets.filter((event) => Math.abs(event.time - time) < 1e-9).sort((a, b) => a.midiNote - b.midiNote), target = targetChord[Math.min(sourceChord.indexOf(onset), targetChord.length - 1)], off = releases.get(onset);
18038
+ if (!off || onset.channel === 9) continue;
18039
+ realized = !0;
18040
+ let type = glissando.playbackType ?? (glissando.kind === "portamento" ? "pitch-bend" : "diatonic");
18041
+ removed.add(onset), removed.add(off);
18042
+ let lane = `${onset.playbackLaneId ?? `part:${partIndex}`}:gliss:${source}:${sourceChord.indexOf(onset)}`;
18043
+ if (type === "pitch-bend") {
18044
+ let bendEvents = renderBend(onset, off, target, lane), bendOnset = bendEvents.find((event) => event.type === "noteOn");
18045
+ bendOnset.scoreDurationBeats = timeline.model.beatAtTime(target.time) - (onset.scoreBeat ?? timeline.model.beatAtTime(onset.time)), added.push(...bendEvents);
18046
+ } else {
18047
+ let key = type === "diatonic" ? keyPitches(score, timeline, onset) : void 0;
18048
+ added.push(
18049
+ ...renderSteps(
18050
+ { ...onset, playbackLaneId: lane },
18051
+ { ...off, playbackLaneId: lane },
18052
+ target,
18053
+ steppedPitches(onset.midiNote, target.midiNote, key),
18054
+ timeline
18055
+ )
18056
+ );
18057
+ }
18058
+ if (!mirroredLanes.has(lane)) {
18059
+ mirroredLanes.add(lane);
18060
+ for (let event of original)
18061
+ event.partIndex === partIndex && event.playbackLaneId === onset.playbackLaneId && (event.type === "programChange" || event.type === "controlChange") && added.push({ ...event, playbackLaneId: lane });
18062
+ }
18063
+ }
18064
+ realized || diagnostics.push({
18065
+ code: "glissando-playback-unsupported",
18066
+ message: `Glissando from "${source}" to "${glissando.target}" has no forward pitched endpoints on a continuous playback route.`,
18067
+ playbackLaneIds: sources.flatMap((event) => event.playbackLaneId ? [event.playbackLaneId] : [])
18068
+ });
18069
+ }
18070
+ }), timeline.events = original.filter((event) => !removed.has(event)).concat(added), diagnostics;
18071
+ }
18072
+
17909
18073
  // packages/midi/src/chordPlayback/index.ts
17910
18074
  init_define_import_meta_env();
17911
18075
  function getChordPlaybackPart(score) {
@@ -19260,18 +19424,13 @@ function generateTimeline(inputScore, options) {
19260
19424
  return aOrder !== bOrder ? aOrder - bOrder : a.instrumentChange !== b.instrumentChange ? a.instrumentChange ? -1 : 1 : a.midiNote - b.midiNote;
19261
19425
  };
19262
19426
  allEvents.sort(compareEvents);
19263
- let instrumentDiagnostics = applyInstrumentPlayback(
19427
+ let diagnostics = [...applyInstrumentPlayback(
19264
19428
  score,
19265
19429
  allEvents,
19266
19430
  { model, measureStartBeats, measureOrder },
19267
19431
  options?.instrumentPrograms,
19268
19432
  options?.partPrograms
19269
- ), diagnostics = [
19270
- ...assignPhysicalMidiChannels(allEvents, score),
19271
- ...instrumentDiagnostics,
19272
- ...rhythm.diagnostics
19273
- ];
19274
- for (let diagnostic of diagnostics) options?.onDiagnostic?.(diagnostic);
19433
+ ), ...rhythm.diagnostics];
19275
19434
  allEvents.sort(compareEvents);
19276
19435
  let duration2 = model.timeAtBeat(tempoBuild.totalBeats), measureTimeSignatures = measureOrder.map((origIdx) => {
19277
19436
  let ts = resolveActiveTime(globalMeasures, origIdx);
@@ -19287,8 +19446,10 @@ function generateTimeline(inputScore, options) {
19287
19446
  expandedMeasureToOriginal: measureOrder,
19288
19447
  measureTimeSignatures,
19289
19448
  diagnostics
19290
- };
19291
- return spans && (globalMeasures.some((measure) => measure.jump !== void 0) || spans.some((span) => span.startBeat > 0 || span.endBeat < span.measureBeats)) ? realizeNavigation(result, spans, crossJumpContinuations) : { ...result, playedSpans: spans };
19449
+ }, realized = spans && (globalMeasures.some((measure) => measure.jump !== void 0) || spans.some((span) => span.startBeat > 0 || span.endBeat < span.measureBeats)) ? realizeNavigation(result, spans, crossJumpContinuations) : { ...result, playedSpans: spans };
19450
+ diagnostics.push(...applyGlissandoPlayback(score, realized), ...assignPhysicalMidiChannels(realized.events, score));
19451
+ for (let diagnostic of diagnostics) options?.onDiagnostic?.(diagnostic);
19452
+ return realized.events.sort(compareEvents), realized;
19292
19453
  }
19293
19454
  function assignPhysicalMidiChannels(events, score) {
19294
19455
  let melodicChannels = [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14, 15], lanesByPart = /* @__PURE__ */ new Map();