Source: src/visitors/NoteVisitor.js

/**
* @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();