UNPKG

@stringsync/vexml

Version:

MusicXML to Vexflow

205 lines (204 loc) 8.78 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.MeasureSequenceIterator = void 0; const util = __importStar(require("../util")); /** * A class that iterates over measures in playback order (accounting for repeats and jumps). */ class MeasureSequenceIterator { measures; constructor(measures) { this.measures = measures; } [Symbol.iterator]() { const repeats = Repeat.from(this.measures); const activeRepeats = new util.Stack(); function getState() { const activeRepeat = activeRepeats.peek(); const measureRepeats = repeats.filter((repeat) => repeat.to === measureIndex); let measureRepeatIndex = measureRepeats.findIndex((repeat) => activeRepeat?.matches(repeat)); measureRepeatIndex = measureRepeatIndex === -1 ? 0 : measureRepeatIndex; const measureRepeat = measureRepeats.at(measureRepeatIndex); const nextMeasureRepeat = measureRepeats.at(measureRepeatIndex + 1); return { activeRepeat, measureRepeat, nextMeasureRepeat }; } let measureIndex = 0; const iterator = { next: () => { // We've reached the end of the measures. if (measureIndex >= this.measures.length) { return { value: null, done: true }; } const measure = this.measures[measureIndex]; const { activeRepeat, measureRepeat, nextMeasureRepeat } = getState(); const isMeasureExcluded = activeRepeat?.isMeasureExcluded(measureIndex); if (isMeasureExcluded) { measureIndex++; return iterator.next(); } const isMeasureRepeatActive = measureRepeat && activeRepeat && measureRepeat.matches(activeRepeat); // The measure repeat is active, has finished, and there is another repeat to process. if (isMeasureRepeatActive && activeRepeat.isFinished() && nextMeasureRepeat) { activeRepeats.pop(); const nextActiveRepeat = nextMeasureRepeat.clone(); activeRepeats.push(nextActiveRepeat); nextActiveRepeat.decrement(); measureIndex = nextMeasureRepeat.from; return { value: measure.index, done: false }; } // The measure repeat is active, has finished, and there is not another repeat to process. if (isMeasureRepeatActive && activeRepeat.isFinished() && !nextMeasureRepeat) { activeRepeats.pop(); measureIndex++; return { value: measure.index, done: false }; } // The measure repeat is active and has not finished. if (isMeasureRepeatActive && !activeRepeat.isFinished()) { activeRepeat.decrement(); measureIndex = activeRepeat.from; return { value: measure.index, done: false }; } // The measure repeat is not active, but it should be. if (measureRepeat && !measureRepeat.matches(activeRepeat)) { const nextActiveRepeat = measureRepeat.clone(); activeRepeats.push(nextActiveRepeat); nextActiveRepeat.decrement(); measureIndex = measureRepeat.from; return { value: measure.index, done: false }; } // Nothing special to do with this measure, move forward. measureIndex++; return { value: measure.index, done: false }; }, }; return iterator; } } exports.MeasureSequenceIterator = MeasureSequenceIterator; /** A class that conveniently wraps repeat metadata. */ class Repeat { from; to; id; times; excluding; constructor(opts) { this.from = opts.from; this.to = opts.to; this.id = opts.id; this.times = opts.times; this.excluding = opts.excluding; } static from(measures) { const result = new Array(); let nextId = 1; const startMeasureIndexes = new util.Stack(); for (let measureIndex = 0; measureIndex < measures.length; measureIndex++) { const measure = measures[measureIndex]; const hasRepeatEnding = measure.jumps.some((jump) => jump.type === 'repeatending'); for (const jump of measure.jumps) { if (jump.type === 'repeatstart') { startMeasureIndexes.push(measureIndex); } // We only process repeatends if there is no repeatending in the same measure. if (!hasRepeatEnding && jump.type === 'repeatend') { // Not all repeatends have a corresponding repeatstart. Assume they're supposed to repeat from the beginning. const startMeasureIndex = startMeasureIndexes.pop() ?? 0; result.push(new Repeat({ id: nextId++, times: jump.times, from: startMeasureIndex, to: measureIndex, excluding: [], })); } if (jump.type === 'repeatending' && jump.times > 0) { // Not all repeatendings have a corresponding repeatstart. Assume they're supposed to repeat from the // beginning. const startMeasureIndex = startMeasureIndexes.pop() ?? 0; if (jump.times > 1) { result.push(new Repeat({ id: nextId++, times: jump.times - 1, from: startMeasureIndex, to: measureIndex, excluding: [], })); } // Exclude all the previous repeatendings. const excluding = new Array(); let i = measureIndex; while (i > startMeasureIndex && measures[i].jumps.some((jump) => jump.type === 'repeatending')) { excluding.push(i); i--; } result.push(new Repeat({ id: nextId++, times: 1, from: startMeasureIndex, to: measureIndex, excluding, })); } } } return result; } matches(repeat) { return this.id === repeat?.id; } isMeasureExcluded(measureIndex) { return this.excluding.includes(measureIndex); } isFinished() { return this.times === 0; } decrement() { if (this.times === 0) { throw new Error('Cannot decrement a repeat that has already been exhausted.'); } this.times--; } clone() { return new Repeat({ id: this.id, times: this.times, from: this.from, to: this.to, excluding: [...this.excluding], }); } }