@stringsync/vexml
Version:
MusicXML to Vexflow
103 lines (102 loc) • 3.88 kB
JavaScript
/** A generic way of representing a curved connector in music notation. */
export class Curve {
config;
log;
curveNumber;
phase;
placement;
opening;
articulation;
constructor(config, log, curveNumber, phase, placement, opening, articulation) {
this.config = config;
this.log = log;
this.curveNumber = curveNumber;
this.phase = phase;
this.placement = placement;
this.opening = opening;
this.articulation = articulation;
}
static create(config, log, musicXML) {
return [...Curve.createSlurs(config, log, musicXML), ...Curve.createTies(config, log, musicXML)];
}
static createSlurs(config, log, musicXML) {
const curves = new Array();
for (const slur of musicXML.notation.getSlurs()) {
const placement = slur.getPlacement() ?? 'auto';
let phase;
switch (slur.getType()) {
case 'start':
phase = 'start';
break;
default:
phase = 'continue';
break;
}
let opening = 'auto';
switch (slur.getOrientation()) {
// Yes, these translations are correct.
case 'over':
opening = 'down';
break;
case 'under':
opening = 'up';
break;
}
let curveNumber = slur.getNumber();
let articulation = 'unspecified';
const slides = musicXML.notation.getSlides();
const hammerOns = musicXML.notation.getTechnicals().flatMap((t) => t.getHammerOns());
const pullOffs = musicXML.notation.getTechnicals().flatMap((t) => t.getPullOffs());
if (slides.length > 0) {
curveNumber = slides.at(0)?.getNumber() ?? curveNumber;
articulation = 'slide';
}
else if (hammerOns.length > 0) {
curveNumber = hammerOns.at(0)?.getNumber() ?? curveNumber;
articulation = 'hammeron';
}
else if (pullOffs.length > 0) {
curveNumber = pullOffs.at(0)?.getNumber() ?? curveNumber;
articulation = 'pulloff';
}
curves.push(new Curve(config, log, curveNumber, phase, placement, opening, articulation));
}
return curves;
}
static createTies(config, log, musicXML) {
const curves = new Array();
for (const tied of musicXML.notation.getTieds()) {
const placement = tied.getPlacement() ?? 'auto';
let phase;
switch (tied.getType()) {
case 'start':
phase = 'start';
break;
default:
phase = 'continue';
break;
}
let opening = 'auto';
switch (tied.getOrientation()) {
// Yes, these translations are correct.
case 'over':
opening = 'down';
break;
case 'under':
opening = 'up';
break;
}
const curveNumber = tied.getNumber();
const articulation = 'unspecified';
curves.push(new Curve(config, log, curveNumber, phase, placement, opening, articulation));
}
return curves;
}
parse(voiceEntryCtx) {
if (this.phase === 'start') {
return voiceEntryCtx.beginCurve(this.curveNumber, this.placement, this.opening, this.articulation);
}
return (voiceEntryCtx.continueCurve(this.curveNumber) ??
voiceEntryCtx.beginCurve(this.curveNumber, this.placement, this.opening, this.articulation));
}
}