UNPKG

@stringsync/vexml

Version:

MusicXML to Vexflow

249 lines (248 loc) 11.3 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.Note = void 0; const conversions = __importStar(require("./conversions")); const util = __importStar(require("../../util")); const accidental_1 = require("./accidental"); const fraction_1 = require("./fraction"); const contexts_1 = require("./contexts"); const annotation_1 = require("./annotation"); const pitch_1 = require("./pitch"); const curve_1 = require("./curve"); const beam_1 = require("./beam"); const tuplet_1 = require("./tuplet"); const vibrato_1 = require("./vibrato"); const articulation_1 = require("./articulation"); const bend_1 = require("./bend"); const tabposition_1 = require("./tabposition"); class Note { config; log; pitch; head; durationType; dotCount; stemDirection; duration; measureBeat; lyrics; accidental; curves; tuplets; beam; slash; graceEntries; vibratos; articulations; bends; tabPositions; constructor(config, log, pitch, head, durationType, dotCount, stemDirection, duration, measureBeat, lyrics, accidental, curves, tuplets, beam, slash, graceEntries, vibratos, articulations, bends, tabPositions) { this.config = config; this.log = log; this.pitch = pitch; this.head = head; this.durationType = durationType; this.dotCount = dotCount; this.stemDirection = stemDirection; this.duration = duration; this.measureBeat = measureBeat; this.lyrics = lyrics; this.accidental = accidental; this.curves = curves; this.tuplets = tuplets; this.beam = beam; this.slash = slash; this.graceEntries = graceEntries; this.vibratos = vibratos; this.articulations = articulations; this.bends = bends; this.tabPositions = tabPositions; } static create(config, log, measureBeat, duration, musicXML) { const pitch = new pitch_1.Pitch(config, log, musicXML.note.getStep(), musicXML.note.getOctave()); const head = conversions.fromNoteheadToNotehead(musicXML.note.getNotehead()); let durationType = conversions.fromNoteTypeToDurationType(musicXML.note.getType()); let dotCount = musicXML.note.getDotCount(); if (!durationType) { [durationType, dotCount] = conversions.fromFractionToDurationType(duration); } const stem = conversions.fromStemToStemDirection(musicXML.note.getStem()); const annotations = musicXML.note.getLyrics().map((lyric) => annotation_1.Annotation.fromLyric(config, log, { lyric })); const code = conversions.fromAccidentalTypeToAccidentalCode(musicXML.note.getAccidentalType()) ?? conversions.fromAlterToAccidentalCode(musicXML.note.getAlter()) ?? 'n'; const isCautionary = musicXML.note.hasAccidentalCautionary(); const accidental = new accidental_1.Accidental(config, log, code, isCautionary); const curves = musicXML.note.getNotations().flatMap((notation) => curve_1.Curve.create(config, log, { notation })); const tuplets = musicXML.note .getNotations() .flatMap((notation) => notation.getTuplets()) .map((tuplet) => tuplet_1.Tuplet.create(config, log, { tuplet })); const vibratos = musicXML.note .getNotations() .flatMap((notation) => notation.getOrnaments()) .flatMap((ornament) => ornament.getWavyLines()) .map((entry) => vibrato_1.Vibrato.create(config, log, { wavyLine: entry.value })); // Since data.Note is a superset of data.GraceNote, we can use the same model. We terminate recursion by checking if // the note is a grace note. const graceEntries = new Array(); if (!musicXML.note.isGrace()) { for (const graceNote of musicXML.note.getGraceNotes()) { if (graceNote.isChordTail()) { continue; } const note = Note.create(config, log, measureBeat, util.Fraction.zero(), { note: graceNote }); if (graceNote.isChordHead()) { const tail = graceNote .getChordTail() .map((note) => Note.create(config, log, measureBeat, util.Fraction.zero(), { note })); graceEntries.push({ type: 'gracechord', head: note, tail }); } else { graceEntries.push({ type: 'gracenote', note }); } } } // MusicXML encodes each beam line as a separate <beam>. We only care about the presence of beams, so we only check // the first one. vexflow will eventually do the heavy lifting of inferring the note durations and beam structures. let beam = null; if (musicXML.note.getBeams().length > 0) { beam = beam_1.Beam.create(config, log, { beam: musicXML.note.getBeams().at(0) }); } const articulations = articulation_1.Articulation.create(config, log, { note: musicXML.note }); const bends = musicXML.note .getNotations() .flatMap((n) => n.getTechnicals()) .flatMap((t) => t.getBends()) .map((bend) => bend_1.Bend.create(config, log, { bend })); const slash = musicXML.note.hasGraceSlash(); const fretPositions = tabposition_1.TabPosition.create(config, log, { note: musicXML.note }); return new Note(config, log, pitch, head, durationType, dotCount, stem, new fraction_1.Fraction(duration), new fraction_1.Fraction(measureBeat), annotations, accidental, curves, tuplets, beam, slash, graceEntries, vibratos, articulations, bends, fretPositions); } parse(voiceCtx) { const voiceEntryCtx = contexts_1.VoiceEntryContext.note(voiceCtx, this.pitch.getStep(), this.pitch.getOctave()); const tupletIds = util.unique([ ...this.tuplets.map((tuplet) => tuplet.parse(voiceEntryCtx)).filter((id) => id !== null), ...voiceEntryCtx.continueOpenTuplets(), ]); // Grace entries need to be parsed before the curves because a slur may start on a grace entry. const graceEntries = this.parseGraceEntries(voiceEntryCtx); const curveIds = this.curves.map((curve) => curve.parse(voiceEntryCtx)); return { type: 'note', pitch: this.pitch.parse(), head: this.head, dotCount: this.dotCount, stemDirection: this.stemDirection, durationType: this.durationType, duration: this.duration.parse(), measureBeat: this.measureBeat.parse(), accidental: this.maybeParseAccidental(voiceEntryCtx) ?? null, annotations: this.parseAnnotations(), curveIds, tupletIds, beamId: this.beam?.parse(voiceEntryCtx) ?? null, graceEntries, wedgeId: voiceEntryCtx.continueOpenWedge(), pedalMark: voiceEntryCtx.continueOpenPedal(), octaveShiftId: voiceEntryCtx.continueOpenOctaveShift(), vibratoIds: this.vibratos.map((vibrato) => vibrato.parse(voiceEntryCtx)), articulations: this.articulations.map((articulation) => articulation.parse()), bends: this.bends.map((bend) => bend.parse()), tabPositions: this.tabPositions.map((fretPosition) => fretPosition.parse()), }; } parseAnnotations() { return [...this.lyrics].map((annotation) => annotation.parse()); } maybeParseAccidental(voiceEntryCtx) { const isCautionary = this.accidental.isCautionary; const noteAccidental = this.accidental.code; const keyAccidental = voiceEntryCtx.getKeyAccidental(); const activeAccidental = voiceEntryCtx.getActiveAccidental(); if (!isCautionary && keyAccidental === noteAccidental) { return null; } if (!isCautionary && activeAccidental === noteAccidental) { return null; } return this.accidental.parse(voiceEntryCtx); } parseGraceEntries(voiceEntryCtx) { return this.graceEntries.map((graceEntry) => { switch (graceEntry.type) { case 'gracenote': return this.parseGraceNote(voiceEntryCtx, graceEntry); case 'gracechord': return this.parseGraceChord(voiceEntryCtx, graceEntry); default: util.assertUnreachable(); } }); } parseGraceNote(voiceEntryCtx, graceNote) { const note = graceNote.note; return { type: 'gracenote', head: note.head, accidental: note.maybeParseAccidental(voiceEntryCtx), beamId: note.beam?.parse(voiceEntryCtx) ?? null, durationType: note.durationType, curveIds: note.curves.map((curve) => curve.parse(voiceEntryCtx)), pitch: note.pitch.parse(), slash: note.slash, tabPositions: note.tabPositions.map((tabPosition) => tabPosition.parse()), }; } parseGraceChord(voiceEntryCtx, graceChord) { const notes = [graceChord.head, ...graceChord.tail].map((note) => ({ type: 'gracechordnote', pitch: note.pitch.parse(), head: note.head, accidental: note.maybeParseAccidental(voiceEntryCtx), curveIds: note.curves.map((curve) => curve.parse(voiceEntryCtx)), slash: note.slash, tabPositions: note.tabPositions.map((tabPosition) => tabPosition.parse()), })); return { type: 'gracechord', beamId: graceChord.head.beam?.parse(voiceEntryCtx) ?? null, durationType: graceChord.head.durationType, notes, }; } } exports.Note = Note;