UNPKG

@stringsync/vexml

Version:

MusicXML to Vexflow

292 lines (291 loc) 10.8 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.Timeline = void 0; const duration_1 = require("./duration"); const measuresequenceiterator_1 = require("./measuresequenceiterator"); const util = __importStar(require("../util")); class Timeline { partIndex; moments; describer; constructor(partIndex, moments, describer) { this.partIndex = partIndex; this.moments = moments; this.describer = describer; } static create(log, score) { const partCount = score.getPartCount(); const timelines = new Array(partCount); for (let partIndex = 0; partIndex < partCount; partIndex++) { timelines[partIndex] = new TimelineFactory(log, score, partIndex).create(); } return timelines; } getPartIndex() { return this.partIndex; } getMoment(index) { return this.moments.at(index) ?? null; } getMoments() { return this.moments; } getMomentCount() { return this.moments.length; } getDuration() { return this.moments.at(-1)?.time ?? duration_1.Duration.zero(); } toHumanReadable() { return this.describer.describe(this.moments); } } exports.Timeline = Timeline; class TimelineFactory { logger; score; partIndex; // timeMs -> moment moments = new Map(); currentMeasureStartTime = duration_1.Duration.zero(); nextMeasureStartTime = duration_1.Duration.zero(); constructor(logger, score, partIndex) { this.logger = logger; this.score = score; this.partIndex = partIndex; } create() { this.moments = new Map(); this.currentMeasureStartTime = duration_1.Duration.zero(); this.populateMoments(); this.sortEventsWithinMoments(); const moments = this.getSortedMoments(); const describer = TimelineDescriber.create(this.score, this.partIndex); return new Timeline(this.partIndex, moments, describer); } getMeasuresInPlaybackOrder() { const measures = this.score.getMeasures(); const measureIndexes = Array.from(new measuresequenceiterator_1.MeasureSequenceIterator(measures.map((measure, index) => ({ index, jumps: measure.getJumps() })))); const result = new Array(); for (let i = 0; i < measureIndexes.length; i++) { const current = measureIndexes[i]; const next = measureIndexes.at(i + 1); const willJump = typeof next === 'number' && next !== current + 1; const measure = measures[current]; result.push({ measure, willJump }); } return result; } proposeNextMeasureStartTime(time) { this.nextMeasureStartTime = duration_1.Duration.max(this.nextMeasureStartTime, time); } toDuration(beat, bpm) { const duration = duration_1.Duration.minutes(beat.divide(new util.Fraction(bpm)).toDecimal()); // Round to the nearest 100ms. This is needed to correctly group transitions that should belong together. const ms = Math.round(duration.ms / 100) * 100; return duration_1.Duration.ms(ms); } populateMoments() { for (const { measure, willJump } of this.getMeasuresInPlaybackOrder()) { if (measure.isMultiMeasure()) { this.populateMultiMeasureEvents(measure); } else { this.populateFragmentEvents(measure); } this.currentMeasureStartTime = this.nextMeasureStartTime; if (willJump) { this.addJumpEvent(this.currentMeasureStartTime, measure); } else if (measure.isLastMeasureInSystem()) { const system = this.score.getSystems().at(measure.getSystemIndex()); util.assertDefined(system); this.addSystemEndEvent(this.currentMeasureStartTime, system); } } } populateMultiMeasureEvents(measure) { util.assert(measure.isMultiMeasure(), 'measure must be a multi-measure'); const bpm = measure.getBpm(); const duration = this.toDuration(measure.getBeatCount(), bpm); const startTime = this.currentMeasureStartTime; const stopTime = startTime.add(duration); this.addTransitionStartEvent(startTime, measure, measure); this.addTransitionStopEvent(stopTime, measure, measure); this.proposeNextMeasureStartTime(stopTime); } populateFragmentEvents(measure) { for (const fragment of measure.getFragments()) { if (fragment.isNonMusicalGap()) { this.populateNonMusicalGapEvents(fragment, measure); } else { this.populateVoiceEntryEvents(fragment, measure); } } } populateNonMusicalGapEvents(fragment, measure) { const duration = duration_1.Duration.ms(fragment.getNonMusicalDurationMs()); const startTime = this.currentMeasureStartTime; const stopTime = startTime.add(duration); this.addTransitionStartEvent(startTime, measure, fragment); this.addTransitionStopEvent(stopTime, measure, fragment); this.proposeNextMeasureStartTime(stopTime); } populateVoiceEntryEvents(fragment, measure) { const voiceEntries = fragment .getParts() .filter((part) => part.getIndex() === this.partIndex) .flatMap((fragmentPart) => fragmentPart.getStaves()) .flatMap((stave) => stave.getVoices()) .flatMap((voice) => voice.getEntries()); const bpm = fragment.getBpm(); for (const voiceEntry of voiceEntries) { const duration = this.toDuration(voiceEntry.getBeatCount(), bpm); // NOTE: getStartMeasureBeat() is relative to the start of the measure. const startTime = this.currentMeasureStartTime.add(this.toDuration(voiceEntry.getStartMeasureBeat(), bpm)); const stopTime = startTime.add(duration); this.addTransitionStartEvent(startTime, measure, voiceEntry); this.addTransitionStopEvent(stopTime, measure, voiceEntry); this.proposeNextMeasureStartTime(stopTime); } } sortEventsWithinMoments() { for (const moment of this.moments.values()) { moment.events.sort((a, b) => { return this.getEventTypeOrder(a) - this.getEventTypeOrder(b); }); } } getEventTypeOrder(event) { if (event.type === 'transition' && event.kind === 'stop') { return 0; } if (event.type === 'jump') { return 1; } if (event.type === 'systemend') { return 2; } if (event.type === 'transition' && event.kind === 'start') { return 3; } util.assertUnreachable(); } upsert(time, event) { let moment; if (this.moments.has(time.ms)) { moment = this.moments.get(time.ms); moment.events.push(event); } else { moment = { time, events: [event] }; this.moments.set(time.ms, moment); } return moment; } addTransitionStartEvent(time, measure, element) { this.upsert(time, { type: 'transition', kind: 'start', measure, element, }); } addTransitionStopEvent(time, measure, element) { this.upsert(time, { type: 'transition', kind: 'stop', measure, element, }); } addJumpEvent(time, measure) { this.upsert(time, { type: 'jump', measure }); } addSystemEndEvent(time, system) { this.upsert(time, { type: 'systemend', system }); } getSortedMoments() { const moments = Array.from(this.moments.values()); return moments.sort((a, b) => a.time.compare(b.time)); } } class TimelineDescriber { elements; constructor(elements) { this.elements = elements; } static create(score, partIndex) { const elements = new Map(); score .getMeasures() .flatMap((measure) => measure.getFragments()) .flatMap((fragment) => fragment.getParts().at(partIndex) ?? []) .flatMap((part) => part.getStaves()) .flatMap((stave) => stave.getVoices()) .flatMap((voice) => voice.getEntries()) .forEach((element, index) => { elements.set(element, index); }); return new TimelineDescriber(elements); } describe(moments) { return moments.map((moment) => this.describeMoment(moment)); } describeMoment(moment) { return `[${moment.time.ms}ms] ${moment.events.map((event) => this.describeEvent(event)).join(', ')}`; } describeEvent(event) { switch (event.type) { case 'transition': return this.describeTransition(event); case 'jump': return this.describeJump(); case 'systemend': return this.describeSystemEnd(); } } describeTransition(event) { return `${event.kind}(${this.elements.get(event.element)})`; } describeJump() { return 'jump'; } describeSystemEnd() { return 'systemend'; } }