@stringsync/vexml
Version:
MusicXML to Vexflow
171 lines (170 loc) • 6.28 kB
JavaScript
import { Beam } from './beam';
import { ACCIDENTAL_TYPES, NOTEHEADS, NOTE_TYPES, STEMS } from './enums';
import { Notations } from './notations';
import { Lyric } from './lyric';
import { TimeModification } from './timemodification';
/**
* Contains graphical and musical information about a note.
*
* https://www.w3.org/2021/06/musicxml40/musicxml-reference/elements/note/
*/
export class Note {
element;
constructor(element) {
this.element = element;
}
/** Returns the stem of the note or null when missing or invalid. */
getStem() {
return this.element.first('stem')?.content().enum(STEMS) ?? null;
}
/** Returns the type of note or null when missing or invalid. */
getType() {
return this.element.first('type')?.content().enum(NOTE_TYPES) ?? null;
}
/** Returns the duration of the note. Defaults to 4 */
getDuration() {
return this.element.first('duration')?.content().int() ?? 4;
}
/** Returns how many dots are on the note. */
getDotCount() {
return this.element.all('dot').length;
}
/** Returns the lyrics associated with the note. */
getLyrics() {
return this.element.all('lyric').map((element) => new Lyric(element));
}
/** Whether or not the note is a grace note. */
isGrace() {
return this.element.all('grace').length > 0;
}
/** Whether the note has a grace note. */
hasGrace() {
const element = this.element.previous('note');
if (!element) {
return false;
}
return new Note(element).isGrace();
}
/** Returns the notes that are part of this note's grace note. */
getGraceNotes() {
const notes = new Array();
let sibling = this.element.previous('note');
while (sibling) {
const note = new Note(sibling);
if (!note.isGrace()) {
break;
}
notes.push(note);
sibling = sibling.previous('note');
}
return notes.reverse();
}
/** Whether or not the note has a glash slash. */
hasGraceSlash() {
return this.element.first('grace')?.attr('slash').str() === 'yes';
}
/** Returns the notations of the note. */
getNotations() {
return this.element.all('notations').map((element) => new Notations(element));
}
/** Returns the voice this note belongs to. */
getVoice() {
return this.element.first('voice')?.content().str() ?? null;
}
/** Returns the stave the note belongs to. */
getStaveNumber() {
return this.element.first('staff')?.content().int() ?? null;
}
/** Returns the step of the note. Defaults to 'C'. */
getStep() {
return this.element.first('step')?.content().str() ?? 'C';
}
/** Returns the octave of the note. Defaults to 4. */
getOctave() {
return this.element.first('octave')?.content().int() ?? 4;
}
/**
* Returns the step and octave of the rest in the format `${step}/${octave}`.
*
* Defaults to null. This is intended to only be used for notes that contain a <rest> element.
*/
getRestDisplayPitch() {
const step = this.getRestDisplayStep();
const octave = this.getRestDisplayOctave();
return typeof step === 'string' && typeof octave === 'number' ? `${step}/${octave}` : null;
}
getRestDisplayStep() {
return this.element.first('display-step')?.content().str() ?? null;
}
getRestDisplayOctave() {
return this.element.first('display-octave')?.content().int() ?? null;
}
/** Returns the accidental type of the note. Defaults to null. */
getAccidentalType() {
return this.element.first('accidental')?.content().enum(ACCIDENTAL_TYPES) ?? null;
}
/** Returns the alteration of the note. Defaults to null. */
getAlter() {
return this.element.first('alter')?.content().float() ?? null;
}
/** Whether or not the accidental is cautionary. Defaults to false. */
hasAccidentalCautionary() {
return this.element.first('accidental')?.attr('cautionary').str() === 'yes';
}
/** Returns the notehead of the note. */
getNotehead() {
return this.element.first('notehead')?.content().enum(NOTEHEADS) ?? null;
}
/** Whether or not the note is the first note of a chord. */
isChordHead() {
if (this.isChordTail()) {
return false;
}
const sibling = this.element.next('note');
if (!sibling) {
return false;
}
const note = new Note(sibling);
// The next note has to be part of a chord tail, otherwise there would only one note in the chord.
return note.isChordTail() && note.isGrace() === this.isGrace();
}
/** Whether or not the note is part of a chord and *not* the first note of the chord. */
isChordTail() {
return this.element.all('chord').length > 0;
}
/** Returns the rest of the notes in the chord iff the current note is a chord head. Defaults to an empty array. */
getChordTail() {
const tail = new Array();
if (!this.isChordHead()) {
return tail;
}
const isGrace = this.isGrace();
let sibling = this.element.next('note');
while (sibling) {
const note = new Note(sibling);
if (!note.isChordTail() || note.isGrace() !== isGrace) {
break;
}
tail.push(note);
sibling = sibling.next('note');
}
return tail;
}
/** Returns whether or not the note is a rest. */
isRest() {
return this.element.all('rest').length > 0;
}
/** Returns the beams of the note. */
getBeams() {
return this.element.all('beam').map((element) => new Beam(element));
}
/** Returns the time modification of the note. Defaults to null. */
getTimeModification() {
const element = this.element.first('time-modification');
return element ? new TimeModification(element) : null;
}
/** Whether to print the object. Defaults to true. */
printObject() {
return this.element.attr('print-object').withDefault('yes').str() !== 'no';
}
}