vexflow-musicxml
Version:
MusicXml Parser for vexflow
157 lines (142 loc) • 4.16 kB
JavaScript
/**
* @file
* @description Visitor implementation for converting MusicXML to VexFlow
* @author {@link mailto:neumann.benni@gmail.com|neumann.benni@gmail.com}
* @version 0.1
*/
import Vex from 'vexflow';
import { Note } from '../xml/Note';
import { MusicXmlError } from '../xml/Errors.js';
import { ClefVisitor } from './index';
const { Flow } = Vex;
/**
* This class implements a visitor used to display notes in Vex format.
*/
class NoteVisitor {
/**
* Returns the input required for Flow.StaveNote
*/
getVexNote() {
const kStep = this.isRest ? 'b' : this.Pitch.Step;
const kOctave = this.isRest ? '4' : this.Pitch.Octave;
const type = this.Types[this.Type];
if (type === undefined) {
throw new MusicXmlError('BadArguments', 'Invalid type ' + JSON.stringify(this));
}
const ret = { keys: [kStep + '/' + kOctave], duration: type };
if (this.isRest) {
ret.type = 'r';
} else if (this.Clef !== undefined) {
ret.clef = this.Clef.accept(ClefVisitor);
}
// Add additional notes in chord
let nextNode = this.Node.nextElementSibling;
let exitCounter = 100;
while (exitCounter-- > 0) {
if (nextNode !== null && nextNode.tagName === 'note') {
const tempNote = new Note(nextNode, this.mAttributes);
if (tempNote.isInChord) {
ret.keys.push(`${tempNote.Pitch.Step}/${tempNote.Pitch.Octave}`);
} else {
break;
}
nextNode = nextNode.nextElementSibling;
} else {
break;
}
}
return ret;
}
/**
* Calculate the long representation type from the length of the note.
* A length of 4 is a whole note, 2 a half and so on.
*/
calculateType() {
let ret;
if (this.NoteLength === 4) {
ret = 'w';
} else if (this.NoteLength >= 2 && this.NoteLength <= 3) {
ret = 'h';
} else if (this.NoteLength >= 1 && this.NoteLength < 2) {
ret = 'w';
} else if (this.NoteLength === 0.25) {
ret = 'q';
} else if (this.NoteLength === 0.5) {
ret = 'h';
} else if (this.NoteLength <= (1 / 8)) {
ret = Math.round(1 / (1 / 8)).toString();
}
return ret;
}
/**
* Converts accidentials from MusicXML to VexFlow
*/
getAccidental() {
const acc = this.Accidental;
switch (acc) {
case 'natural':
return 'n';
case 'flat':
return 'b';
case 'sharp':
return '#';
default:
return null;
}
}
/**
* Returns the Vex type of note (whole, quarter, etc.) from it's XML representation
*/
get Types() {
return (
{
'': this.calculateType(),
'whole': 'w',
'half': 'h',
'quarter': 'q',
'eighth': '8',
'16th': '16',
'32nd': '32',
'64th': '64',
'128th': '128',
'256th': '256',
'512th': '512',
'1024th': '1024',
});
}
/**
* Converts the XML note object into VexFlow format.
* @param {Note} note XML note object
*/
visit(note) {
// Copy all properties to be used in this class
// TODO: This is refactored code and could use some love
this.NoteLength = note.NoteLength;
this.Pitch = note.Pitch;
this.isRest = note.isRest;
this.Node = note.Node;
this.mAttributes = note.mAttributes;
this.NoteLength = note.Duration / this.mAttributes.Divisions;
this.Dots = this.NoteLength >= 1 && this.NoteLength % 1 === 0.5;
this.Type = note.Type;
this.Accidental = note.Accidental;
this.Clef = note.Clef;
// Create a constructor struct
const vNote = this.getVexNote();
// Create the "real" VexFlow note
const fNote = new Flow.StaveNote(vNote);
// Add accidentials to notes
const acc = this.getAccidental();
if (acc) {
fNote.addAccidental(0, new Flow.Accidental(acc));
}
if (note.hasClefChange) {
console.log(note, this.Clef.accept(ClefVisitor));
// const cn = new Flow.ClefNote(vNote.clef, 'small');
// fNote.addModifier(0, new Flow.NoteSubGroup([cn]));
}
return fNote;
}
}
// Make it singleton
export const noteVisitor = new NoteVisitor();