@stringsync/vexml
Version:
MusicXML to Vexflow
249 lines (248 loc) • 11.3 kB
JavaScript
"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;