UNPKG

@stringsync/vexml

Version:

MusicXML to Vexflow

346 lines (345 loc) 13.3 kB
import * as musicxml from '../../musicxml'; import { Fraction } from '../../util'; import { StaveCount } from './stavecount'; import { StaveLineCount } from './stavelinecount'; import { Clef } from './clef'; import { Key } from './key'; import { Time } from './time'; import { Metronome } from './metronome'; import { Note } from './note'; import { Rest } from './rest'; import { Chord } from './chord'; import { Dynamics } from './dynamics'; import { Wedge } from './wedge'; import { Pedal } from './pedal'; import { OctaveShift } from './octaveshift'; export class EventCalculator { config; log; musicXML; measureBeat = Fraction.zero(); events = new Array(); quarterNoteDivisions = 1; // partId -> voiceId previousExplicitVoiceId = {}; // partId -> staveNumber previousExplicitStaveNumber = {}; // partId -> staveCount previousExplicitStaveCount = {}; // staveNumber -> Key previousKeys = new Map(); constructor(config, log, musicXML) { this.config = config; this.log = log; this.musicXML = musicXML; } calculate() { this.events = []; for (const part of this.musicXML.scorePartwise.getParts()) { this.quarterNoteDivisions = 1; this.previousKeys = new Map(); const partId = part.getId(); const measures = part.getMeasures(); this.previousExplicitStaveNumber[partId] = 1; this.previousExplicitVoiceId[partId] = '1'; this.previousExplicitStaveCount[partId] = 1; for (let measureIndex = 0; measureIndex < measures.length; measureIndex++) { this.measureBeat = Fraction.zero(); for (const entry of measures[measureIndex].getEntries()) { this.process(entry, partId, measureIndex); } } } return this.events; } process(entry, partId, measureIndex) { if (entry instanceof musicxml.Note) { this.processNote(entry, partId, measureIndex); } if (entry instanceof musicxml.Backup) { this.processBackup(entry); } if (entry instanceof musicxml.Forward) { this.processForward(entry); } if (entry instanceof musicxml.Attributes) { this.processAttributes(entry, partId, measureIndex); } if (entry instanceof musicxml.Direction) { this.processDirection(entry, partId, measureIndex); } } processNote(note, partId, measureIndex) { const staveNumber = this.resolveStaveNumber(partId, note.getStaveNumber()); const voiceId = this.resolveVoiceId(partId, note.getVoice()); const quarterNotes = note.getDuration(); const duration = new Fraction(quarterNotes, this.quarterNoteDivisions); if (note.isChordTail()) { return; } if (note.isGrace()) { return; } if (note.isChordHead()) { this.events.push({ type: 'chord', partId, measureIndex, staveNumber, voiceId, measureBeat: this.measureBeat, duration, chord: Chord.create(this.config, this.log, this.measureBeat, duration, { note }), }); } else if (note.isRest()) { this.events.push({ type: 'rest', partId, measureIndex, staveNumber, voiceId, measureBeat: this.measureBeat, duration, rest: Rest.create(this.config, this.log, this.measureBeat, duration, { note }), }); } else { this.events.push({ type: 'note', partId, measureIndex, staveNumber, voiceId, measureBeat: this.measureBeat, duration, note: Note.create(this.config, this.log, this.measureBeat, duration, { note }), }); } this.measureBeat = this.measureBeat.add(duration); } processBackup(backup) { const quarterNotes = backup.getDuration(); const duration = new Fraction(quarterNotes, this.quarterNoteDivisions); this.measureBeat = this.measureBeat.subtract(duration); if (this.measureBeat.isLessThan(Fraction.zero())) { this.measureBeat = Fraction.zero(); } } processForward(forward) { const quarterNotes = forward.getDuration(); const duration = new Fraction(quarterNotes, this.quarterNoteDivisions); this.measureBeat = this.measureBeat.add(duration); } processAttributes(attributes, partId, measureIndex) { this.quarterNoteDivisions = attributes.getQuarterNoteDivisions() ?? this.quarterNoteDivisions; const staveCount = attributes.getStaveCount() ?? this.previousExplicitStaveCount[partId]; if (attributes.getStaveCount()) { this.events.push({ type: 'stavecount', partId, measureIndex, measureBeat: this.measureBeat, staveCount: new StaveCount(this.config, this.log, partId, staveCount), }); } const staveLineCounts = attributes .getStaveDetails() .map((staveDetails) => StaveLineCount.create(this.config, this.log, partId, { staveDetails })); for (const staveLineCount of staveLineCounts) { this.events.push({ type: 'stavelinecount', partId, measureIndex, measureBeat: this.measureBeat, staveNumber: staveLineCount.getStaveNumber(), staveLineCount, }); } const clefs = attributes.getClefs().map((clef) => Clef.create(this.config, this.log, partId, { clef })); for (const clef of clefs) { this.events.push({ type: 'clef', partId, measureIndex, measureBeat: this.measureBeat, staveNumber: clef.getStaveNumber(), clef, }); } // Processing keys is particularly messy because they can be applied to a specific stave or all staves. We need to // keep track of the previous key to know if we need to show cancel accidentals. for (const attributeKey of attributes.getKeys()) { const staveNumber = attributeKey.getStaveNumber(); if (typeof staveNumber === 'number') { // If the key is applied to a specific stave, proceed forward as normal. this.resolveStaveNumber(partId, staveNumber); const previousKey = this.previousKeys.get(staveNumber) ?? null; const key = Key.create(this.config, this.log, partId, staveNumber, previousKey, { key: attributeKey }); this.previousKeys.set(staveNumber, key); this.events.push({ type: 'key', partId, measureIndex, measureBeat: this.measureBeat, staveNumber, key, }); } else { // Otherwise, apply the key to all staves, checking the previous key as we go along. for (let index = 0; index < staveCount; index++) { const previousKey = this.previousKeys.get(index + 1) ?? null; const key = Key.create(this.config, this.log, partId, index + 1, previousKey, { key: attributeKey }); this.previousKeys.set(index + 1, key); this.events.push({ type: 'key', partId, measureIndex, measureBeat: this.measureBeat, staveNumber: index + 1, key, }); } } } const times = attributes .getTimes() .flatMap((time) => { const staveNumber = time.getStaveNumber(); if (typeof staveNumber === 'number') { return [Time.create(this.config, this.log, partId, this.resolveStaveNumber(partId, staveNumber), { time })]; } else { return Time.createMulti(this.config, this.log, partId, staveCount, { time }); } }) .filter((time) => time !== null); for (const time of times) { this.events.push({ type: 'time', partId, measureIndex, measureBeat: this.measureBeat, staveNumber: time.getStaveNumber(), time, }); } const measureStyle = attributes.getMeasureStyles().find((measureStyle) => measureStyle.getMultipleRestCount() > 0); if (measureStyle) { this.events.push({ type: 'multirest', partId, measureIndex, measureBeat: this.measureBeat, measureCount: measureStyle.getMultipleRestCount(), staveNumber: measureStyle.getStaveNumber(), }); } } processDirection(direction, partId, measureIndex) { const metronome = direction.getMetronome(); const mark = metronome?.getMark(); if (metronome && mark) { this.events.push({ type: 'metronome', partId, measureIndex, measureBeat: this.measureBeat, metronome: Metronome.create(this.config, this.log, { metronome, mark }), }); } const segnos = direction.getSegnos(); if (segnos.length > 0) { this.events.push({ type: 'segno', partId, measureIndex, measureBeat: this.measureBeat, }); } const coda = direction.getCodas(); if (coda.length > 0) { this.events.push({ type: 'coda', partId, measureIndex, measureBeat: this.measureBeat, }); } const dynamicType = direction .getDynamics() .flatMap((d) => d.getTypes()) .at(0); if (dynamicType) { const staveNumber = this.resolveStaveNumber(partId, direction.getStaveNumber()); const voiceId = this.resolveVoiceId(partId, direction.getVoice()); this.events.push({ type: 'dynamics', partId, measureIndex, staveNumber, voiceId, measureBeat: this.measureBeat, dynamics: new Dynamics(this.config, this.log, this.measureBeat, dynamicType), }); } const wedge = direction .getWedges() .map((wedge) => Wedge.create({ direction, wedge })) .at(0); if (wedge) { const staveNumber = this.resolveStaveNumber(partId, direction.getStaveNumber()); const voiceId = this.resolveVoiceId(partId, direction.getVoice()); this.events.push({ type: 'wedge', partId, measureIndex, measureBeat: this.measureBeat, staveNumber, voiceId, wedge, }); } const pedal = direction .getPedals() .map((pedal) => Pedal.create(this.config, this.log, { pedal })) .at(0); if (pedal) { const staveNumber = this.resolveStaveNumber(partId, direction.getStaveNumber()); const voiceId = this.resolveVoiceId(partId, direction.getVoice()); this.events.push({ type: 'pedal', partId, measureIndex, measureBeat: this.measureBeat, staveNumber, voiceId, pedal, }); } const octaveShift = direction .getOctaveShifts() .map((octaveShift) => OctaveShift.create(this.config, this.log, { octaveShift })) .at(0); if (octaveShift) { const staveNumber = this.resolveStaveNumber(partId, direction.getStaveNumber()); const voiceId = this.resolveVoiceId(partId, direction.getVoice()); this.events.push({ type: 'octaveshift', partId, measureIndex, measureBeat: this.measureBeat, staveNumber, voiceId, octaveShift, }); } } resolveVoiceId(partId, voiceId) { return (this.previousExplicitVoiceId[partId] = voiceId ?? this.previousExplicitVoiceId[partId]); } resolveStaveNumber(partId, staveNumber) { return (this.previousExplicitStaveNumber[partId] = staveNumber ?? this.previousExplicitStaveNumber[partId]); } }