Browse Source

merge osmd-public (1.8.1): inverted triangle notehead, cursor null check. (run npm run prebuild)

if you just want to run `npm start` after checking this out / pulling, you need to run npm run prebuild first.
(if you run npm build, it does prebuild automatically)
sschmidTU 1 year ago
parent
commit
c23de0fc33

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "osmd-extended",
-  "version": "1.8.0",
+  "version": "1.8.1",
   "description": "Private / sponsor exclusive OSMD mirror/audio player.",
   "main": "build/opensheetmusicdisplay.min.js",
   "types": "build/dist/src/index.d.ts",

+ 2 - 1
src/MusicalScore/Graphical/GraphicalMusicSheet.ts

@@ -220,8 +220,9 @@ export class GraphicalMusicSheet {
     }
 
     public findGraphicalMeasure(measureIndex: number, staffIndex: number): GraphicalMeasure {
+        // note the cursor calls this with measureIndex 1 (measure 2) when advancing beyond the end of a 1-measure piece
         for (let i: number = measureIndex; i >= 0; i--) {
-            const gMeasure: GraphicalMeasure = this.measureList[i][staffIndex];
+            const gMeasure: GraphicalMeasure = this.measureList[i]?.[staffIndex];
             if (gMeasure) {
                 return gMeasure;
             }

+ 2 - 0
src/MusicalScore/Graphical/VexFlow/VexFlowConverter.ts

@@ -207,6 +207,8 @@ export class VexFlowConverter {
                 return codeStart + "D" + codeFilled;
             case NoteHeadShape.TRIANGLE:
                 return codeStart + "T" + codeFilled;
+            case NoteHeadShape.TRIANGLE_INVERTED:
+                return codeStart + "TI";
             case NoteHeadShape.X:
                 return codeStart + "X" + codeFilled;
             case NoteHeadShape.CIRCLEX:

+ 3 - 0
src/MusicalScore/VoiceData/Notehead.ts

@@ -77,6 +77,8 @@ export class Notehead {
             case "do":
             case "triangle":
                 return NoteHeadShape.TRIANGLE;
+            case "inverted triangle":
+                return NoteHeadShape.TRIANGLE_INVERTED;
             case "rectangle":
                 return NoteHeadShape.RECTANGLE;
             case "circle-x":
@@ -97,6 +99,7 @@ export enum NoteHeadShape {
     SLASH,
     SQUARE,
     TRIANGLE,
+    TRIANGLE_INVERTED,
     X,
     // TODO: Add the rest from https://usermanuals.musicxml.com/MusicXML/Content/ST-MusicXML-notehead-value.htm
     // currently all Vexflow supported shapes present

+ 5 - 1
src/OpenSheetMusicDisplay/Cursor.ts

@@ -294,7 +294,11 @@ export class Cursor implements IPlaybackListener {
     height = endY - y;
 
     // Update the graphical cursor
-    const measurePositionAndShape: BoundingBox = this.graphic.findGraphicalMeasure(currentMeasureIndex, 0).PositionAndShape;
+    const visibleMeasure: GraphicalMeasure = this.findVisibleGraphicalMeasure(currentMeasureIndex);
+    if (!visibleMeasure) {
+      return;
+    }
+    const measurePositionAndShape: BoundingBox = visibleMeasure.PositionAndShape;
     this.updateWidthAndStyle(measurePositionAndShape, x, y, height);
 
     if (this.openSheetMusicDisplay.FollowCursor && this.cursorOptions.follow) {

+ 1 - 1
src/OpenSheetMusicDisplay/OpenSheetMusicDisplay.ts

@@ -35,7 +35,7 @@ import { DynamicsCalculator } from "../MusicalScore/ScoreIO/MusicSymbolModules/D
  * After the constructor, use load() and render() to load and render a MusicXML file.
  */
 export class OpenSheetMusicDisplay {
-    private version: string = "1.8.0-audio-extended"; // getter: this.Version
+    private version: string = "1.8.1-audio-extended"; // getter: this.Version
     // at release, bump version and change to -release, afterwards to -dev again
 
     /**

+ 7 - 0
src/VexFlowPatch/readme.txt

@@ -29,6 +29,9 @@ gracenotegroup.js (custom addition, needs check if necessary in vexflow 4):
 check for gracenotegroup.spacing set, to allow e.g. spacing = 0 by default.
 (with previous default 4, spacing is way too large unnecessarily, in most cases)
 
+notehead.js (custom addition):
+add stem_up_y_shift and stem_down_y_shift to shift notehead (independent of stem length)
+
 keysignature.js (merged vexflow 4):
 open group to get SVG group+class for key signature
 
@@ -51,6 +54,7 @@ Fix stem/flag formatting. Instead of shifting notes by default, update the stem/
 able to add svg node id+class to stem (merged vexflow 4.x)
 Save and restore noteheads (e.g. slash noteheads) in reset()
 preFormat() and getBoundingBox(): add paddingRight variable to allow for custom right padding (e.g. for long lyrics below note)
+allow notehead y_shift without 
 
 staverepetition.js (fixed vexflow 4):
 add TO_CODA enum to type() and draw()
@@ -79,6 +83,9 @@ svgcontext.js (custom addition, probably not necessary for vexflow 4):
 able to add extra attributes (like svg node id) to a stroke (e.g. stem)
 fix rect() always using black color, ignoring attributes.stroke (ctx strokeStlye) -> fix defaultColorMusic ignored
 
+tables.js (custom addition):
+add inverted triangle notehead ('TI')
+
 tabnote.js (merged Vexflow 3.x):
 Add a context group for each tabnote, so that it can be found in the SVG DOM ("vf-tabnote")
 

+ 233 - 0
src/VexFlowPatch/src/notehead.js

@@ -0,0 +1,233 @@
+// [VexFlow](http://vexflow.com) - Copyright (c) Mohit Muthanna 2010.
+//
+// ## Description
+//
+// This file implements `NoteHeads`. `NoteHeads` are typically not manipulated
+// directly, but used internally in `StaveNote`.
+//
+// See `tests/notehead_tests.js` for usage examples.
+
+import { Vex } from './vex';
+import { Flow } from './tables';
+import { Note } from './note';
+import { Stem } from './stem';
+import { StaveNote } from './stavenote';
+import { Glyph } from './glyph';
+
+// To enable logging for this class. Set `Vex.Flow.NoteHead.DEBUG` to `true`.
+function L(...args) { if (NoteHead.DEBUG) Vex.L('Vex.Flow.NoteHead', args); }
+
+// Draw slashnote head manually. No glyph exists for this.
+//
+// Parameters:
+// * `ctx`: the Canvas context
+// * `duration`: the duration of the note. ex: "4"
+// * `x`: the x coordinate to draw at
+// * `y`: the y coordinate to draw at
+// * `stem_direction`: the direction of the stem
+function drawSlashNoteHead(ctx, duration, x, y, stem_direction, staveSpace) {
+  const width = Flow.SLASH_NOTEHEAD_WIDTH;
+  ctx.save();
+  ctx.setLineWidth(Flow.STEM_WIDTH);
+
+  let fill = false;
+
+  if (Flow.durationToNumber(duration) > 2) {
+    fill = true;
+  }
+
+  if (!fill) x -= (Flow.STEM_WIDTH / 2) * stem_direction;
+
+  ctx.beginPath();
+  ctx.moveTo(x, y + staveSpace);
+  ctx.lineTo(x, y + 1);
+  ctx.lineTo(x + width, y - staveSpace);
+  ctx.lineTo(x + width, y);
+  ctx.lineTo(x, y + staveSpace);
+  ctx.closePath();
+
+  if (fill) {
+    ctx.fill();
+  } else {
+    ctx.stroke();
+  }
+
+  if (Flow.durationToFraction(duration).equals(0.5)) {
+    const breve_lines = [-3, -1, width + 1, width + 3];
+    for (let i = 0; i < breve_lines.length; i++) {
+      ctx.beginPath();
+      ctx.moveTo(x + breve_lines[i], y - 10);
+      ctx.lineTo(x + breve_lines[i], y + 11);
+      ctx.stroke();
+    }
+  }
+
+  ctx.restore();
+}
+
+export class NoteHead extends Note {
+  static get CATEGORY() { return 'notehead'; }
+
+  constructor(head_options) {
+    super(head_options);
+    this.setAttribute('type', 'NoteHead');
+
+    this.index = head_options.index;
+    this.x_shift = head_options.x_shift || 0;
+    this.x = (head_options.x || 0) + this.x_shift;
+    this.y = head_options.y || 0;
+    this.note_type = head_options.note_type;
+    this.duration = head_options.duration;
+    this.displaced = head_options.displaced || false;
+    this.stem_direction = head_options.stem_direction || StaveNote.STEM_UP;
+    this.line = head_options.line;
+
+    // Get glyph code based on duration and note type. This could be
+    // regular notes, rests, or other custom codes.
+    this.glyph = Flow.getGlyphProps(this.duration, this.note_type);
+    if (!this.glyph) {
+      throw new Vex.RuntimeError(
+        'BadArguments',
+        `No glyph found for duration '${this.duration}' and type '${this.note_type}'`);
+    }
+
+    this.glyph_code = this.glyph.code_head;
+    this.x_shift = head_options.x_shift || 0;
+    if (head_options.custom_glyph_code) {
+      this.custom_glyph = true;
+      this.glyph_code = head_options.custom_glyph_code;
+      this.stem_up_x_offset = head_options.stem_up_x_offset || 0;
+      this.stem_down_x_offset = head_options.stem_down_x_offset || 0;
+      // VexFlowPatch: add stem_up_y_shift and stem_down_y_shift to shift notehead (instead of stem in variables above)
+      this.stem_up_y_shift = head_options.stem_up_y_shift || 0;
+      this.stem_down_y_shift = head_options.stem_down_y_shift || 0;
+    }
+
+    this.style = head_options.style;
+    this.slashed = head_options.slashed;
+
+    Vex.Merge(this.render_options, {
+      // font size for note heads
+      glyph_font_scale: head_options.glyph_font_scale || Flow.DEFAULT_NOTATION_FONT_SCALE,
+      // number of stroke px to the left and right of head
+      stroke_px: 3,
+    });
+
+    this.setWidth(this.glyph.getWidth(this.render_options.glyph_font_scale));
+  }
+
+  getCategory() { return NoteHead.CATEGORY; }
+
+  // Get the width of the notehead
+  getWidth() { return this.width; }
+
+  // Determine if the notehead is displaced
+  isDisplaced() { return this.displaced === true; }
+
+  // Get the glyph data
+  getGlyph() { return this.glyph; }
+
+  // Set the X coordinate
+  setX(x) { this.x = x; return this; }
+
+  // get/set the Y coordinate
+  getY() { return this.y; }
+  setY(y) { this.y = y;  return this; }
+
+  // Get/set the stave line the notehead is placed on
+  getLine() { return this.line; }
+  setLine(line) { this.line = line; return this; }
+
+  // Get the canvas `x` coordinate position of the notehead.
+  getAbsoluteX() {
+    // If the note has not been preformatted, then get the static x value
+    // Otherwise, it's been formatted and we should use it's x value relative
+    // to its tick context
+    const x = !this.preFormatted ? this.x : super.getAbsoluteX();
+
+    // For a more natural displaced notehead, we adjust the displacement amount
+    // by half the stem width in order to maintain a slight overlap with the stem
+    const displacementStemAdjustment = (Stem.WIDTH / 2);
+
+    return x + (this.displaced
+      ? (this.width - displacementStemAdjustment) * this.stem_direction
+      : 0
+    );
+  }
+
+  // Get the `BoundingBox` for the `NoteHead`
+  getBoundingBox() {
+    if (!this.preFormatted) {
+      throw new Vex.RERR('UnformattedNote', "Can't call getBoundingBox on an unformatted note.");
+    }
+
+    const spacing = this.stave.getSpacingBetweenLines();
+    const half_spacing = spacing / 2;
+    const min_y = this.y - half_spacing;
+
+    return new Flow.BoundingBox(this.getAbsoluteX(), min_y, this.width, spacing);
+  }
+
+  // Set notehead to a provided `stave`
+  setStave(stave) {
+    const line = this.getLine();
+
+    this.stave = stave;
+    this.setY(stave.getYForNote(line));
+    this.context = this.stave.context;
+    return this;
+  }
+
+  // Pre-render formatting
+  preFormat() {
+    if (this.preFormatted) return this;
+
+    const width = this.getWidth() + this.extraLeftPx + this.extraRightPx;
+
+    this.setWidth(width);
+    this.setPreFormatted(true);
+    return this;
+  }
+
+  // Draw the notehead
+  draw() {
+    this.checkContext();
+    this.setRendered();
+
+    const ctx = this.context;
+    let head_x = this.getAbsoluteX();
+    let y = this.y;
+    if (this.custom_glyph) {
+      // head_x += this.x_shift;
+      if (this.stem_direction === Stem.UP) {
+        head_x += this.stem_up_x_offset;
+        // VexFlowPatch: also allow notehead shift independent of stem length
+        y += (this.stem_up_y_shift || 0);
+      } else {
+        head_x += this.stem_down_x_offset
+        y += (this.stem_down_y_shift || 0)
+      }
+    }
+
+    L("Drawing note head '", this.note_type, this.duration, "' at", head_x, y);
+
+    // Begin and end positions for head.
+    const stem_direction = this.stem_direction;
+    const glyph_font_scale = this.render_options.glyph_font_scale;
+
+    if (this.style) {
+      this.applyStyle(ctx);
+    }
+
+    if (this.note_type === 's') {
+      const staveSpace = this.stave.getSpacingBetweenLines();
+      drawSlashNoteHead(ctx, this.duration, head_x, y, stem_direction, staveSpace);
+    } else {
+      Glyph.renderGlyph(ctx, head_x, y, glyph_font_scale, this.glyph_code);
+    }
+
+    if (this.style) {
+      this.restoreStyle(ctx);
+    }
+  }
+}

+ 3 - 0
src/VexFlowPatch/src/stavenote.js

@@ -517,6 +517,9 @@ export class StaveNote extends StemmableNote {
         x_shift: noteProps.shift_right,
         stem_up_x_offset: noteProps.stem_up_x_offset,
         stem_down_x_offset: noteProps.stem_down_x_offset,
+        // VexFlowPatch: add option to shift notehead up or down (instead of stem in the variables above)
+        stem_up_y_shift: noteProps.stem_up_y_shift,
+        stem_down_y_shift: noteProps.stem_down_y_shift,
         line: noteProps.line,
       });
 

+ 1237 - 0
src/VexFlowPatch/src/tables.js

@@ -0,0 +1,1237 @@
+// [VexFlow](http://vexflow.com) - Copyright (c) Mohit Muthanna 2010.
+
+/* eslint-disable key-spacing */
+
+import { Vex } from './vex';
+import { Fraction } from './fraction';
+import { Glyph } from './glyph';
+
+const Flow = {
+  STEM_WIDTH: 1.5,
+  STEM_HEIGHT: 35,
+  STAVE_LINE_THICKNESS: 1,
+  RESOLUTION: 16384,
+  DEFAULT_NOTATION_FONT_SCALE: 39,
+  DEFAULT_TABLATURE_FONT_SCALE: 39,
+  SLASH_NOTEHEAD_WIDTH: 15,
+
+  // HACK:
+  // Since text origins are positioned at the baseline, we must
+  // compensate for the ascender of the text. Of course, 1 staff space is
+  // a very poor approximation.
+  //
+  // This will be deprecated in the future. This is a temporary solution until
+  // we have more robust text metrics.
+  TEXT_HEIGHT_OFFSET_HACK: 1,
+
+  /* Kerning (DEPRECATED) */
+  IsKerned: true,
+};
+
+
+Flow.clefProperties = clef => {
+  if (!clef) throw new Vex.RERR('BadArgument', 'Invalid clef: ' + clef);
+
+  const props = Flow.clefProperties.values[clef];
+  if (!props) throw new Vex.RERR('BadArgument', 'Invalid clef: ' + clef);
+
+  return props;
+};
+
+Flow.clefProperties.values = {
+  'treble': { line_shift: 0 },
+  'bass': { line_shift: 6 },
+  'tenor': { line_shift: 4 },
+  'alto': { line_shift: 3 },
+  'soprano': { line_shift: 1 },
+  'percussion': { line_shift: 0 },
+  'mezzo-soprano': { line_shift: 2 },
+  'baritone-c': { line_shift: 5 },
+  'baritone-f': { line_shift: 5 },
+  'subbass': { line_shift: 7 },
+  'french': { line_shift: -1 },
+};
+
+/*
+  Take a note in the format "Key/Octave" (e.g., "C/5") and return properties.
+
+  The last argument, params, is a struct the currently can contain one option,
+  octave_shift for clef ottavation (0 = default; 1 = 8va; -1 = 8vb, etc.).
+*/
+Flow.keyProperties = (key, clef, params) => {
+  if (clef === undefined) {
+    clef = 'treble';
+  }
+
+  const options = { octave_shift: 0 };
+
+  if (typeof params === 'object') {
+    Vex.Merge(options, params);
+  }
+
+  const pieces = key.split('/');
+
+  if (pieces.length < 2) {
+    throw new Vex.RERR('BadArguments', `Key must have note + octave and an optional glyph: ${key}`);
+  }
+
+  const k = pieces[0].toUpperCase();
+  const value = Flow.keyProperties.note_values[k];
+  if (!value) throw new Vex.RERR('BadArguments', 'Invalid key name: ' + k);
+  if (value.octave) pieces[1] = value.octave;
+
+  let octave = parseInt(pieces[1], 10);
+
+  // Octave_shift is the shift to compensate for clef 8va/8vb.
+  octave += -1 * options.octave_shift;
+
+  const base_index = (octave * 7) - (4 * 7);
+  let line = (base_index + value.index) / 2;
+  line += Flow.clefProperties(clef).line_shift;
+
+  let stroke = 0;
+
+  if (line <= 0 && (((line * 2) % 2) === 0)) stroke = 1;  // stroke up
+  if (line >= 6 && (((line * 2) % 2) === 0)) stroke = -1; // stroke down
+
+  // Integer value for note arithmetic.
+  const int_value = typeof (value.int_val) !== 'undefined'
+    ? (octave * 12) + value.int_val
+    : null;
+
+  /* Check if the user specified a glyph. */
+  const code = value.code;
+  const shift_right = value.shift_right;
+  let extraProps = {};
+  if (pieces.length > 2 && pieces[2]) {
+    const glyph_name = pieces[2].toUpperCase();
+    extraProps = Flow.keyProperties.customNoteHeads[glyph_name] || {};
+  }
+
+  return {
+    key: k,
+    octave,
+    line,
+    int_value,
+    accidental: value.accidental,
+    code,
+    stroke,
+    shift_right,
+    displaced: false,
+    ...extraProps,
+  };
+};
+
+Flow.keyProperties.note_values = {
+  'C': { index: 0, int_val: 0, accidental: null },
+  'CN': { index: 0, int_val: 0, accidental: 'n' },
+  'C#': { index: 0, int_val: 1, accidental: '#' },
+  'C##': { index: 0, int_val: 2, accidental: '##' },
+  'CB': { index: 0, int_val: -1, accidental: 'b' },
+  'CBB': { index: 0, int_val: -2, accidental: 'bb' },
+  'D': { index: 1, int_val: 2, accidental: null },
+  'DN': { index: 1, int_val: 2, accidental: 'n' },
+  'D#': { index: 1, int_val: 3, accidental: '#' },
+  'D##': { index: 1, int_val: 4, accidental: '##' },
+  'DB': { index: 1, int_val: 1, accidental: 'b' },
+  'DBB': { index: 1, int_val: 0, accidental: 'bb' },
+  'E': { index: 2, int_val: 4, accidental: null },
+  'EN': { index: 2, int_val: 4, accidental: 'n' },
+  'E#': { index: 2, int_val: 5, accidental: '#' },
+  'E##': { index: 2, int_val: 6, accidental: '##' },
+  'EB': { index: 2, int_val: 3, accidental: 'b' },
+  'EBB': { index: 2, int_val: 2, accidental: 'bb' },
+  'F': { index: 3, int_val: 5, accidental: null },
+  'FN': { index: 3, int_val: 5, accidental: 'n' },
+  'F#': { index: 3, int_val: 6, accidental: '#' },
+  'F##': { index: 3, int_val: 7, accidental: '##' },
+  'FB': { index: 3, int_val: 4, accidental: 'b' },
+  'FBB': { index: 3, int_val: 3, accidental: 'bb' },
+  'G': { index: 4, int_val: 7, accidental: null },
+  'GN': { index: 4, int_val: 7, accidental: 'n' },
+  'G#': { index: 4, int_val: 8, accidental: '#' },
+  'G##': { index: 4, int_val: 9, accidental: '##' },
+  'GB': { index: 4, int_val: 6, accidental: 'b' },
+  'GBB': { index: 4, int_val: 5, accidental: 'bb' },
+  'A': { index: 5, int_val: 9, accidental: null },
+  'AN': { index: 5, int_val: 9, accidental: 'n' },
+  'A#': { index: 5, int_val: 10, accidental: '#' },
+  'A##': { index: 5, int_val: 11, accidental: '##' },
+  'AB': { index: 5, int_val: 8, accidental: 'b' },
+  'ABB': { index: 5, int_val: 7, accidental: 'bb' },
+  'B': { index: 6, int_val: 11, accidental: null },
+  'BN': { index: 6, int_val: 11, accidental: 'n' },
+  'B#': { index: 6, int_val: 12, accidental: '#' },
+  'B##': { index: 6, int_val: 13, accidental: '##' },
+  'BB': { index: 6, int_val: 10, accidental: 'b' },
+  'BBB': { index: 6, int_val: 9, accidental: 'bb' },
+  'R': { index: 6, int_val: 9, rest: true }, // Rest
+  'X': {
+    index: 6,
+    accidental: '',
+    octave: 4,
+    code: 'v3e',
+    shift_right: 5.5,
+  },
+};
+
+// Custom note heads
+Flow.keyProperties.customNoteHeads = {
+  /* Diamond */
+  'D0': {
+    code: 'v27',
+    shift_right: 0, // deprecated for stem_{up,down}_x_offset
+    stem_up_x_offset: 0,
+    stem_down_x_offset: 0,
+    stem_up_y_offset: -1,
+    stem_down_y_offset: 0
+  },
+  'D1': { code: 'v2d', shift_right: -0.5 },
+  'D2': { code: 'v22', shift_right: -0.5 },
+  'D3': { code: 'v70', shift_right: -0.5 },
+
+  /* Triangle */
+  'T0': { code: 'v49', shift_right: -2, stem_up_y_offset: -4, stem_down_y_offset: 4 },
+  'T1': { code: 'v93', shift_right: 0.5, stem_up_y_offset: -4, stem_down_y_offset: 4 },
+  'T2': { code: 'v40', shift_right: 0.5, stem_up_y_offset: -4, stem_down_y_offset: 4 },
+  'T3': { code: 'v7d', shift_right: 0.5, stem_up_y_offset: -4, stem_down_y_offset: 4 },
+
+  /* Triangle inverted */
+  'TI': { code: 'v11', shift_right: 0, stem_up_y_shift: 5, stem_down_y_shift: 5,
+    stem_up_x_offset: 6, stem_down_x_offset: 4 },
+  // VexFlowPatch: inverted triangle notehead added as custom glyph
+
+  /* Cross */
+  'X0': {
+    code: 'v92',
+    stem_up_x_offset: -2,
+    stem_down_x_offset: 0,
+    stem_up_y_offset: 4,
+    stem_down_y_offset: 4
+  },
+  'X1': { code: 'v95', shift_right: -0.5, stem_up_y_offset: 4, stem_down_y_offset: 4 },
+  'X2': { code: 'v3e', shift_right: 0.5, stem_up_y_offset: 4, stem_down_y_offset: 4 },
+  'X3': {
+    code: 'v3b',
+    shift_right: 0,
+    stem_up_x_offset: -1.2,
+    stem_down_x_offset: 0,
+    stem_up_y_offset: -1,
+    stem_down_y_offset: 2
+  },
+
+  /* Square */
+  'S1': { code: 'vd3', shift_right: 0 },
+  'S2': { code: 'vd2', shift_right: 0 },
+
+  /* Rectangle */
+  'R1': { code: 'vd5', shift_right: 0 },
+  'R2': { code: 'vd4', shift_right: 0 },
+};
+
+Flow.integerToNote = integer => {
+  if (typeof (integer) === 'undefined') {
+    throw new Vex.RERR('BadArguments', 'Undefined integer for integerToNote');
+  }
+
+  if (integer < -2) {
+    throw new Vex.RERR('BadArguments', `integerToNote requires integer > -2: ${integer}`);
+  }
+
+  const noteValue = Flow.integerToNote.table[integer];
+  if (!noteValue) {
+    throw new Vex.RERR('BadArguments', `Unknown note value for integer: ${integer}`);
+  }
+
+  return noteValue;
+};
+
+Flow.integerToNote.table = {
+  0: 'C',
+  1: 'C#',
+  2: 'D',
+  3: 'D#',
+  4: 'E',
+  5: 'F',
+  6: 'F#',
+  7: 'G',
+  8: 'G#',
+  9: 'A',
+  10: 'A#',
+  11: 'B',
+};
+
+Flow.tabToGlyph = (fret, scale = 1.0) => {
+  let glyph = null;
+  let width = 0;
+  let shift_y = 0;
+
+  if (fret.toString().toUpperCase() === 'X') {
+    const glyphMetrics = new Glyph('v7f', Flow.DEFAULT_TABLATURE_FONT_SCALE).getMetrics();
+    glyph = 'v7f';
+    width = glyphMetrics.width;
+    shift_y = -glyphMetrics.height / 2;
+  } else {
+    width = Flow.textWidth(fret.toString());
+  }
+
+  return {
+    text: fret,
+    code: glyph,
+    getWidth: () => width * scale,
+    shift_y,
+  };
+};
+
+Flow.textWidth = text => 7 * text.toString().length;
+
+Flow.articulationCodes = artic => Flow.articulationCodes.articulations[artic];
+
+Flow.articulationCodes.articulations = {
+  'a.': { code: 'v23', between_lines: true }, // Staccato
+  'av': { code: 'v28', between_lines: true }, // Staccatissimo
+  'a>': { code: 'v42', between_lines: true }, // Accent
+  'a-': { code: 'v25', between_lines: true }, // Tenuto
+  'a^': { code: 'va', between_lines: false }, // Marcato
+  'a+': { code: 'v8b', between_lines: false }, // Left hand pizzicato
+  'ao': { code: 'v94', between_lines: false }, // Snap pizzicato
+  'ah': { code: 'vb9', between_lines: false }, // Natural harmonic or open note
+  'a@a': { code: 'v43', between_lines: false }, // Fermata above staff
+  'a@u': { code: 'v5b', between_lines: false }, // Fermata below staff
+  'a|': { code: 'v75', between_lines: false }, // Bow up - up stroke
+  'am': { code: 'v97', between_lines: false }, // Bow down - down stroke
+  'a,': { code: 'vb3', between_lines: false }, // Choked
+};
+
+Flow.accidentalCodes = acc => Flow.accidentalCodes.accidentals[acc];
+
+Flow.accidentalCodes.accidentals = {
+  '#': { code: 'v18', parenRightPaddingAdjustment: -1 },
+  '##': { code: 'v7f', parenRightPaddingAdjustment: -1 },
+  'b': { code: 'v44', parenRightPaddingAdjustment: -2 },
+  'bb': { code: 'v26', parenRightPaddingAdjustment: -2 },
+  'n': { code: 'v4e', parenRightPaddingAdjustment: -1 },
+  '{': { code: 'v9c', parenRightPaddingAdjustment: -1 },
+  '}': { code: 'v84', parenRightPaddingAdjustment: -1 },
+  'db': { code: 'v9e', parenRightPaddingAdjustment: -1 },
+  'd': { code: 'vab', parenRightPaddingAdjustment: 0 },
+  'bbs': { code: 'v90', parenRightPaddingAdjustment: -1 },
+  '++': { code: 'v51', parenRightPaddingAdjustment: -1 },
+  '+': { code: 'v78', parenRightPaddingAdjustment: -1 },
+  '+-': { code: 'v8d', parenRightPaddingAdjustment: -1 },
+  '++-': { code: 'v7a', parenRightPaddingAdjustment: -1 },
+  'bs': { code: 'vb7', parenRightPaddingAdjustment: -1 },
+  'bss': { code: 'v39', parenRightPaddingAdjustment: -1 },
+  'o': { code: 'vd0', parenRightPaddingAdjustment: -1 },
+  'k': { code: 'vd1', parenRightPaddingAdjustment: -1 },
+  'ashs': { code: 'vd6', parenRightPaddingAdjustment: -1 },  // arabic sharp half sharp
+  'afhf': { code: 'vd7', parenRightPaddingAdjustment: -1 },  // arabic flat half flat
+};
+
+Flow.accidentalColumnsTable = {
+  1: {
+    a: [1],
+    b: [1],
+  },
+  2: {
+    a: [1, 2],
+  },
+  3: {
+    a: [1, 3, 2],
+    b: [1, 2, 1],
+    second_on_bottom: [1, 2, 3],
+  },
+  4: {
+    a: [1, 3, 4, 2],
+    b: [1, 2, 3, 1],
+    spaced_out_tetrachord: [1, 2, 1, 2],
+  },
+  5: {
+    a: [1, 3, 5, 4, 2],
+    b: [1, 2, 4, 3, 1],
+    spaced_out_pentachord: [1, 2, 3, 2, 1],
+    very_spaced_out_pentachord: [1, 2, 1, 2, 1],
+  },
+  6: {
+    a: [1, 3, 5, 6, 4, 2],
+    b: [1, 2, 4, 5, 3, 1],
+    spaced_out_hexachord: [1, 3, 2, 1, 3, 2],
+    very_spaced_out_hexachord: [1, 2, 1, 2, 1, 2],
+  },
+};
+
+Flow.ornamentCodes = acc => Flow.ornamentCodes.ornaments[acc];
+
+Flow.ornamentCodes.ornaments = {
+  'mordent': { code: 'v1e' },
+  'mordent_inverted': { code: 'v45' },
+  'turn': { code: 'v72' },
+  'turn_inverted': { code: 'v33' },
+  'tr': { code: 'v1f' },
+  'upprall': { code: 'v60' },
+  'downprall': { code: 'vb4' },
+  'prallup': { code: 'v6d' },
+  'pralldown': { code: 'v2c' },
+  'upmordent': { code: 'v29' },
+  'downmordent': { code: 'v68' },
+  'lineprall': { code: 'v20' },
+  'prallprall': { code: 'v86' },
+};
+
+Flow.keySignature = spec => {
+  const keySpec = Flow.keySignature.keySpecs[spec];
+
+  if (!keySpec) {
+    throw new Vex.RERR('BadKeySignature', `Bad key signature spec: '${spec}'`);
+  }
+
+  if (!keySpec.acc) {
+    return [];
+  }
+
+  const notes = Flow.keySignature.accidentalList(keySpec.acc);
+
+  const acc_list = [];
+  for (let i = 0; i < keySpec.num; ++i) {
+    const line = notes[i];
+    acc_list.push({ type: keySpec.acc, line });
+  }
+
+  return acc_list;
+};
+
+Flow.keySignature.keySpecs = {
+  'C': { acc: null, num: 0 },
+  'Am': { acc: null, num: 0 },
+  'F': { acc: 'b', num: 1 },
+  'Dm': { acc: 'b', num: 1 },
+  'Bb': { acc: 'b', num: 2 },
+  'Gm': { acc: 'b', num: 2 },
+  'Eb': { acc: 'b', num: 3 },
+  'Cm': { acc: 'b', num: 3 },
+  'Ab': { acc: 'b', num: 4 },
+  'Fm': { acc: 'b', num: 4 },
+  'Db': { acc: 'b', num: 5 },
+  'Bbm': { acc: 'b', num: 5 },
+  'Gb': { acc: 'b', num: 6 },
+  'Ebm': { acc: 'b', num: 6 },
+  'Cb': { acc: 'b', num: 7 },
+  'Abm': { acc: 'b', num: 7 },
+  'G': { acc: '#', num: 1 },
+  'Em': { acc: '#', num: 1 },
+  'D': { acc: '#', num: 2 },
+  'Bm': { acc: '#', num: 2 },
+  'A': { acc: '#', num: 3 },
+  'F#m': { acc: '#', num: 3 },
+  'E': { acc: '#', num: 4 },
+  'C#m': { acc: '#', num: 4 },
+  'B': { acc: '#', num: 5 },
+  'G#m': { acc: '#', num: 5 },
+  'F#': { acc: '#', num: 6 },
+  'D#m': { acc: '#', num: 6 },
+  'C#': { acc: '#', num: 7 },
+  'A#m': { acc: '#', num: 7 },
+};
+
+Flow.unicode = {
+  // Unicode accidentals
+  'sharp': String.fromCharCode(parseInt('266F', 16)),
+  'flat': String.fromCharCode(parseInt('266D', 16)),
+  'natural': String.fromCharCode(parseInt('266E', 16)),
+  // Major Chord
+  'triangle': String.fromCharCode(parseInt('25B3', 16)),
+  // half-diminished
+  'o-with-slash': String.fromCharCode(parseInt('00F8', 16)),
+  // Diminished
+  'degrees': String.fromCharCode(parseInt('00B0', 16)),
+  'circle': String.fromCharCode(parseInt('25CB', 16)),
+};
+
+Flow.keySignature.accidentalList = (acc) => {
+  const patterns = {
+    'b': [2, 0.5, 2.5, 1, 3, 1.5, 3.5],
+    '#': [0, 1.5, -0.5, 1, 2.5, 0.5, 2],
+  };
+
+  return patterns[acc];
+};
+
+Flow.parseNoteDurationString = durationString => {
+  if (typeof (durationString) !== 'string') {
+    return null;
+  }
+
+  const regexp = /(\d*\/?\d+|[a-z])(d*)([nrhms]|$)/;
+
+  const result = regexp.exec(durationString);
+  if (!result) {
+    return null;
+  }
+
+  const duration = result[1];
+  const dots = result[2].length;
+  let type = result[3];
+
+  if (type.length === 0) {
+    type = 'n';
+  }
+
+  return {
+    duration,
+    dots,
+    type,
+  };
+};
+
+Flow.parseNoteStruct = noteStruct => {
+  const duration = noteStruct.duration;
+
+  // Preserve backwards-compatibility
+  const durationStringData = Flow.parseNoteDurationString(duration);
+  if (!durationStringData) {
+    return null;
+  }
+
+  let ticks = Flow.durationToTicks(durationStringData.duration);
+  if (ticks == null) {
+    return null;
+  }
+
+  let type = noteStruct.type;
+  const customTypes = [];
+
+  if (type) {
+    if (!Flow.getGlyphProps.validTypes[type]) {
+      return null;
+    }
+  } else {
+    type = durationStringData.type || 'n';
+
+    // If we have keys, try and check if we've got a custom glyph
+    if (noteStruct.keys !== undefined) {
+      noteStruct.keys.forEach((k, i) => {
+        const result = k.split('/');
+        // We have a custom glyph specified after the note eg. /X2
+        if (result && result.length === 3) {
+          customTypes[i] = result[2];
+        }
+      });
+    }
+  }
+
+  const dots = noteStruct.dots ? noteStruct.dots : durationStringData.dots;
+
+  if (typeof (dots) !== 'number') {
+    return null;
+  }
+
+  let currentTicks = ticks;
+
+  for (let i = 0; i < dots; i++) {
+    if (currentTicks <= 1) return null;
+
+    currentTicks = currentTicks / 2;
+    ticks += currentTicks;
+  }
+
+  return {
+    duration: durationStringData.duration,
+    type,
+    customTypes,
+    dots,
+    ticks,
+  };
+};
+
+// Used to convert duration aliases to the number based duration.
+// If the input isn't an alias, simply return the input.
+//
+// example: 'q' -> '4', '8' -> '8'
+Flow.sanitizeDuration = duration => {
+  const alias = Flow.durationAliases[duration];
+  if (alias !== undefined) {
+    duration = alias;
+  }
+
+  if (Flow.durationToTicks.durations[duration] === undefined) {
+    throw new Vex.RERR('BadArguments', `The provided duration is not valid: ${duration}`);
+  }
+
+  return duration;
+};
+
+// Convert the `duration` to an fraction
+Flow.durationToFraction = duration => new Fraction().parse(Flow.sanitizeDuration(duration));
+
+// Convert the `duration` to an number
+Flow.durationToNumber = duration => Flow.durationToFraction(duration).value();
+
+// Convert the `duration` to total ticks
+Flow.durationToTicks = duration => {
+  duration = Flow.sanitizeDuration(duration);
+
+  const ticks = Flow.durationToTicks.durations[duration];
+  if (ticks === undefined) {
+    return null;
+  }
+
+  return ticks;
+};
+
+Flow.durationToTicks.durations = {
+  '1/2': Flow.RESOLUTION * 2,
+  '1': Flow.RESOLUTION / 1,
+  '2': Flow.RESOLUTION / 2,
+  '4': Flow.RESOLUTION / 4,
+  '8': Flow.RESOLUTION / 8,
+  '16': Flow.RESOLUTION / 16,
+  '32': Flow.RESOLUTION / 32,
+  '64': Flow.RESOLUTION / 64,
+  '128': Flow.RESOLUTION / 128,
+  '256': Flow.RESOLUTION / 256,
+};
+
+Flow.durationAliases = {
+  'w': '1',
+  'h': '2',
+  'q': '4',
+
+  // This is the default duration used to render bars (BarNote). Bars no longer
+  // consume ticks, so this should be a no-op.
+  //
+  // TODO(0xfe): This needs to be cleaned up.
+  'b': '256',
+};
+
+// Return a glyph given duration and type. The type can be a custom glyph code from customNoteHeads.
+Flow.getGlyphProps = (duration, type) => {
+  duration = Flow.sanitizeDuration(duration);
+
+  const code = Flow.getGlyphProps.duration_codes[duration];
+  if (code === undefined) {
+    return null;
+  }
+
+  if (!type) {
+    type = 'n';
+  }
+
+  let glyphTypeProperties = code.type[type];
+
+  if (glyphTypeProperties === undefined) {
+    // Try and get it from the custom list of note heads
+    const customGlyphTypeProperties = Flow.keyProperties.customNoteHeads[type.toUpperCase()];
+
+    // If not, then return with nothing
+    if (customGlyphTypeProperties === undefined) {
+      return null;
+    }
+
+    // Otherwise set it as the code_head value
+    glyphTypeProperties = {
+      code_head: customGlyphTypeProperties.code,
+      ...customGlyphTypeProperties,
+    };
+  }
+
+  return { ...code.common, ...glyphTypeProperties };
+};
+
+Flow.getGlyphProps.validTypes = {
+  'n': { name: 'note' },
+  'r': { name: 'rest' },
+  'h': { name: 'harmonic' },
+  'm': { name: 'muted' },
+  's': { name: 'slash' },
+};
+
+Flow.getGlyphProps.duration_codes = {
+  '1/2': {
+    common: {
+      getWidth(scale = Flow.DEFAULT_NOTATION_FONT_SCALE) {
+        return new Glyph(this.code_head || 'v53', scale).getMetrics().width;
+      },
+      stem: false,
+      stem_offset: 0,
+      flag: false,
+      stem_up_extension: -Flow.STEM_HEIGHT,
+      stem_down_extension: -Flow.STEM_HEIGHT,
+      tabnote_stem_up_extension: -Flow.STEM_HEIGHT,
+      tabnote_stem_down_extension: -Flow.STEM_HEIGHT,
+      dot_shiftY: 0,
+      line_above: 0,
+      line_below: 0,
+    },
+    type: {
+      'n': { // Breve note
+        code_head: 'v53',
+      },
+      'h': { // Breve note harmonic
+        code_head: 'v59',
+      },
+      'm': { // Breve note muted -
+        code_head: 'vf',
+        stem_offset: 0,
+      },
+      'r': { // Breve rest
+        code_head: 'v31',
+        rest: true,
+        position: 'B/5',
+        dot_shiftY: 0.5,
+      },
+      's': { // Breve note slash -
+        // Drawn with canvas primitives
+        getWidth: () => Flow.SLASH_NOTEHEAD_WIDTH,
+        position: 'B/4',
+      },
+    },
+  },
+  '1': {
+    common: {
+      getWidth(scale = Flow.DEFAULT_NOTATION_FONT_SCALE) {
+        return new Glyph(this.code_head || 'v1d', scale).getMetrics().width;
+      },
+      stem: false,
+      stem_offset: 0,
+      flag: false,
+      stem_up_extension: -Flow.STEM_HEIGHT,
+      stem_down_extension: -Flow.STEM_HEIGHT,
+      tabnote_stem_up_extension: -Flow.STEM_HEIGHT,
+      tabnote_stem_down_extension: -Flow.STEM_HEIGHT,
+      dot_shiftY: 0,
+      line_above: 0,
+      line_below: 0,
+    },
+    type: {
+      'n': { // Whole note
+        code_head: 'v1d',
+      },
+      'h': { // Whole note harmonic
+        code_head: 'v46',
+      },
+      'm': { // Whole note muted
+        code_head: 'v92',
+        stem_offset: -3,
+      },
+      'r': { // Whole rest
+        code_head: 'v5c',
+        rest: true,
+        position: 'D/5',
+        dot_shiftY: 0.5,
+      },
+      's': { // Whole note slash
+        // Drawn with canvas primitives
+        getWidth: () => Flow.SLASH_NOTEHEAD_WIDTH,
+        position: 'B/4',
+      },
+    },
+  },
+  '2': {
+    common: {
+      getWidth(scale = Flow.DEFAULT_NOTATION_FONT_SCALE) {
+        return new Glyph(this.code_head || 'v81', scale).getMetrics().width;
+      },
+      stem: true,
+      stem_offset: 0,
+      flag: false,
+      stem_up_extension: 0,
+      stem_down_extension: 0,
+      tabnote_stem_up_extension: 0,
+      tabnote_stem_down_extension: 0,
+      dot_shiftY: 0,
+      line_above: 0,
+      line_below: 0,
+    },
+    type: {
+      'n': { // Half note
+        code_head: 'v81',
+      },
+      'h': { // Half note harmonic
+        code_head: 'v2d',
+      },
+      'm': { // Half note muted
+        code_head: 'v95',
+        stem_offset: -3,
+      },
+      'r': { // Half rest
+        code_head: 'vc',
+        stem: false,
+        rest: true,
+        position: 'B/4',
+        dot_shiftY: -0.5,
+      },
+      's': { // Half note slash
+        // Drawn with canvas primitives
+        getWidth: () => Flow.SLASH_NOTEHEAD_WIDTH,
+        position: 'B/4',
+      },
+    },
+  },
+  '4': {
+    common: {
+      getWidth(scale = Flow.DEFAULT_NOTATION_FONT_SCALE) {
+        return new Glyph(this.code_head || 'vb', scale).getMetrics().width;
+      },
+      stem: true,
+      stem_offset: 0,
+      flag: false,
+      stem_up_extension: 0,
+      stem_down_extension: 0,
+      tabnote_stem_up_extension: 0,
+      tabnote_stem_down_extension: 0,
+      dot_shiftY: 0,
+      line_above: 0,
+      line_below: 0,
+    },
+    type: {
+      'n': { // Quarter note
+        code_head: 'vb',
+      },
+      'h': { // Quarter harmonic
+        code_head: 'v22',
+      },
+      'm': { // Quarter muted
+        code_head: 'v3e',
+        stem_offset: -3,
+      },
+      'r': { // Quarter rest
+        code_head: 'v7c',
+        stem: false,
+        rest: true,
+        position: 'B/4',
+        dot_shiftY: -0.5,
+        line_above: 1.5,
+        line_below: 1.5,
+      },
+      's': { // Quarter slash
+        // Drawn with canvas primitives
+        getWidth: () => Flow.SLASH_NOTEHEAD_WIDTH,
+        position: 'B/4',
+      },
+    },
+  },
+  '8': {
+    common: {
+      getWidth(scale = Flow.DEFAULT_NOTATION_FONT_SCALE) {
+        return new Glyph(this.code_head || 'vb', scale).getMetrics().width;
+      },
+      stem: true,
+      stem_offset: 0,
+      flag: true,
+      beam_count: 1,
+      code_flag_upstem: 'v54',
+      code_flag_downstem: 'v9a',
+      stem_up_extension: 0,
+      stem_down_extension: 0,
+      tabnote_stem_up_extension: 0,
+      tabnote_stem_down_extension: 0,
+      dot_shiftY: 0,
+      line_above: 0,
+      line_below: 0,
+    },
+    type: {
+      'n': { // Eighth note
+        code_head: 'vb',
+      },
+      'h': { // Eighth note harmonic
+        code_head: 'v22',
+      },
+      'm': { // Eighth note muted
+        code_head: 'v3e',
+      },
+      'r': { // Eighth rest
+        code_head: 'va5',
+        stem: false,
+        flag: false,
+        rest: true,
+        position: 'B/4',
+        dot_shiftY: -0.5,
+        line_above: 1.0,
+        line_below: 1.0,
+      },
+      's': { // Eight slash
+        // Drawn with canvas primitives
+        getWidth: () => Flow.SLASH_NOTEHEAD_WIDTH,
+        position: 'B/4',
+      },
+    },
+  },
+  '16': {
+    common: {
+      beam_count: 2,
+      getWidth(scale = Flow.DEFAULT_NOTATION_FONT_SCALE) {
+        return new Glyph(this.code_head || 'vb', scale).getMetrics().width;
+      },
+      stem: true,
+      stem_offset: 0,
+      flag: true,
+      code_flag_upstem: 'v3f',
+      code_flag_downstem: 'v8f',
+      stem_up_extension: 0,
+      stem_down_extension: 0,
+      tabnote_stem_up_extension: 0,
+      tabnote_stem_down_extension: 0,
+      dot_shiftY: 0,
+      line_above: 0,
+      line_below: 0,
+    },
+    type: {
+      'n': { // Sixteenth note
+        code_head: 'vb',
+      },
+      'h': { // Sixteenth note harmonic
+        code_head: 'v22',
+      },
+      'm': { // Sixteenth note muted
+        code_head: 'v3e',
+      },
+      'r': { // Sixteenth rest
+        code_head: 'v3c',
+        stem: false,
+        flag: false,
+        rest: true,
+        position: 'B/4',
+        dot_shiftY: -0.5,
+        line_above: 1.0,
+        line_below: 2.0,
+      },
+      's': { // Sixteenth slash
+        // Drawn with canvas primitives
+        getWidth: () => Flow.SLASH_NOTEHEAD_WIDTH,
+        position: 'B/4',
+      },
+    },
+  },
+  '32': {
+    common: {
+      beam_count: 3,
+      getWidth(scale = Flow.DEFAULT_NOTATION_FONT_SCALE) {
+        return new Glyph(this.code_head || 'vb', scale).getMetrics().width;
+      },
+      stem: true,
+      stem_offset: 0,
+      flag: true,
+      code_flag_upstem: 'v47',
+      code_flag_downstem: 'v2a',
+      stem_up_extension: 9,
+      stem_down_extension: 9,
+      tabnote_stem_up_extension: 8,
+      tabnote_stem_down_extension: 5,
+      dot_shiftY: 0,
+      line_above: 0,
+      line_below: 0,
+    },
+    type: {
+      'n': { // Thirty-second note
+        code_head: 'vb',
+      },
+      'h': { // Thirty-second harmonic
+        code_head: 'v22',
+      },
+      'm': { // Thirty-second muted
+        code_head: 'v3e',
+      },
+      'r': { // Thirty-second rest
+        code_head: 'v55',
+        stem: false,
+        flag: false,
+        rest: true,
+        position: 'B/4',
+        dot_shiftY: -1.5,
+        line_above: 2.0,
+        line_below: 2.0,
+      },
+      's': { // Thirty-second slash
+        // Drawn with canvas primitives
+        getWidth: () => Flow.SLASH_NOTEHEAD_WIDTH,
+        position: 'B/4',
+      },
+    },
+  },
+  '64': {
+    common: {
+      beam_count: 4,
+      getWidth(scale = Flow.DEFAULT_NOTATION_FONT_SCALE) {
+        return new Glyph(this.code_head || 'vb', scale).getMetrics().width;
+      },
+      stem: true,
+      stem_offset: 0,
+      flag: true,
+      code_flag_upstem: 'va9',
+      code_flag_downstem: 'v58',
+      stem_up_extension: 13,
+      stem_down_extension: 13,
+      tabnote_stem_up_extension: 12,
+      tabnote_stem_down_extension: 9,
+      dot_shiftY: 0,
+      line_above: 0,
+      line_below: 0,
+    },
+    type: {
+      'n': { // Sixty-fourth note
+        code_head: 'vb',
+      },
+      'h': { // Sixty-fourth harmonic
+        code_head: 'v22',
+      },
+      'm': { // Sixty-fourth muted
+        code_head: 'v3e',
+      },
+      'r': { // Sixty-fourth rest
+        code_head: 'v38',
+        stem: false,
+        flag: false,
+        rest: true,
+        position: 'B/4',
+        dot_shiftY: -1.5,
+        line_above: 2.0,
+        line_below: 3.0,
+      },
+      's': { // Sixty-fourth slash
+        // Drawn with canvas primitives
+        getWidth: () => Flow.SLASH_NOTEHEAD_WIDTH,
+        position: 'B/4',
+      },
+    },
+  },
+  '128': {
+    common: {
+      beam_count: 5,
+      getWidth(scale = Flow.DEFAULT_NOTATION_FONT_SCALE) {
+        return new Glyph(this.code_head || 'vb', scale).getMetrics().width;
+      },
+      stem: true,
+      stem_offset: 0,
+      flag: true,
+      code_flag_upstem: 'v9b',
+      code_flag_downstem: 'v30',
+      stem_up_extension: 22,
+      stem_down_extension: 22,
+      tabnote_stem_up_extension: 21,
+      tabnote_stem_down_extension: 18,
+      dot_shiftY: 0,
+      line_above: 0,
+      line_below: 0,
+    },
+    type: {
+      'n': {  // Hundred-twenty-eight note
+        code_head: 'vb',
+      },
+      'h': { // Hundred-twenty-eight harmonic
+        code_head: 'v22',
+      },
+      'm': { // Hundred-twenty-eight muted
+        code_head: 'v3e',
+      },
+      'r': {  // Hundred-twenty-eight rest
+        code_head: 'vaa',
+        stem: false,
+        flag: false,
+        rest: true,
+        position: 'B/4',
+        dot_shiftY: 1.5,
+        line_above: 3.0,
+        line_below: 3.0,
+      },
+      's': { // Hundred-twenty-eight rest
+        // Drawn with canvas primitives
+        getWidth: () => Flow.SLASH_NOTEHEAD_WIDTH,
+        position: 'B/4',
+      },
+    },
+  },
+};
+
+// For future collaboration with the SMuFL Standard Music Font Layout
+
+Flow.smufl = {};
+
+// add references between smufl glyph names and code points.
+Flow.smufl.to_code_points = {
+  // staff brackets and dividers (e000-e00f)
+  bracketTop: 'v1b',
+  bracketBottom: 'v10',
+
+  // barlines (e030-e03f)
+  barlineTick: 'v6f',
+
+  // repeats (e040-e04f)
+  segno: 'v8c',
+  coda: 'v4d',
+
+  // clefs (e050-e07f)
+  gClef: 'v83',
+  cClef: 'vad',
+  fClef: 'v79',
+  unpitchedPercussionClef1: 'v59', // same as breveNoteheadHarmonic
+  '6stringTabClef': 'v2f',
+
+  // time signatures (e080-e09f)
+  timeSig0: 'v0',
+  timeSig1: 'v1',
+  timeSig2: 'v2',
+  timeSig3: 'v3',
+  timeSig4: 'v4',
+  timeSig5: 'v5',
+  timeSig6: 'v6',
+  timeSig7: 'v7',
+  timeSig8: 'v8',
+  timeSig9: 'v9',
+  timeSigCommon: 'v41',
+  timeSigCutCommon: 'vb6',
+
+  // notehead (e0a0-e0ff)
+  noteheadDoubleWhole: 'v53',
+  noteheadWhole: 'v1d',
+  noteheadHalf: 'v81',
+  noteheadBlack: 'vb',
+  noteheadXWhole: 'v92',
+  noteheadXHalf: 'v95',
+  noteheadXBlack: 'v3e',
+  noteheadCircleX: 'v3b',
+  noteheadTriangleUpWhole: 'v49',
+  noteheadTriangleUpHalf: 'v93',
+  noteheadTriangleUpBlack: 'v40',
+  noteheadDiamondWhole: 'v46',
+  noteheadDiamondHalf: 'v2d',
+  noteheadDiamondBlack: 'v22',
+
+  // individual notes (e1d0-e1ef)
+  augmentationDot: 'v23',
+
+  // temolos (e220-e23f)
+  tremolo1: 'v74',
+
+  // flags (e240-e25f)
+  flag8thUp: 'v54',
+  flag8thDown: 'v9a',
+  flag16thUp: 'v3f',
+  flag16thDown: 'v8f',
+  flag32ndUp: 'v47',
+  flag32ndDown: 'v2a',
+  flag64thUp: 'va9',
+  flag64thDown: 'v58',
+  flag128thUp: 'v9b',
+  flag128thDown: 'v30',
+
+  // standard accidentals (e260-e26f)
+  accidentalFlat: 'v44',
+  accidentalNatural: 'v4e',
+  accidentalSharp: 'v18',
+  accidentalDoubleSharp: 'v7f',
+  accidentalDoubleFlat: 'v26',
+  accidentalParensLeft: 'v9c',
+  accidentalParensRight: 'v84',
+
+  // stein-zimmermann accidentals (24-edo) (e280-e28f)
+  accidentalQuarterToneFlatStein: 'vab',
+  accidentalThreeQuarterTonesFlatZimmermann: 'v9e',
+  accidentalQuarterToneSharpStein: 'v78',
+  accidentalThreeQuarterTonesSharpStein: 'v51',
+
+  // arel-ezgi-uzdilek accidentals (e440-e44f)
+  accidentalBuyukMucennebFlat: 'v39',
+  accidentalBakiyeFlat: 'vb7',
+  accidentalKomaSharp: 'v51', // same as accidentalQuarterToneSharpStein
+  accidentalKucukMucennebSharp: 'v8d',
+
+  // persian accidentals (e460-e46f)
+  accidentalKoron: 'vd1',
+  accidentalSori: 'vd0',
+
+  // articulation (e4a0-e4bf)
+  articAccentAbove: 'v42',
+  articAccentBelow: 'v42', // same as above
+  articTenutoAbove: 'v25',
+  articTenutoBelow: 'v25', // same as above
+  articStaccatoAbove: 'v23', // = dot
+  articStaccatoBelow: 'v23', // = dot
+  articStaccatissimoAbove: 'v28',
+  articMarcatoAbove: 'va',
+
+  // holds and pauses (e4c0-e4df)
+  fermataAbove: 'v43',
+  fermataBelow: 'v5b',
+  breathMarkComma: 'v6c',
+  breathMarkUpbow: 'v8a', // looks better than current upbow
+  caesura: 'v34',
+  caesuraCurved: 'v4b',
+
+  // rests (e4e0-e4ff)
+  restMaxima: 'v59', // not designed for this, but should do the trick
+  // need restLonga -- used in multimeasure rests, like above
+  restDoubleWhole: 'v31',
+  restWhole: 'v5c',
+  restHalf: 'vc',
+  restQuarter: 'v7c',
+  rest8th: 'va5',
+  rest16th: 'v3c',
+  rest32nd: 'v55',
+  rest64th: 'v38',
+  rest128th: 'vaa',
+
+  // dynamics (e520-e54f)
+  dynamicPiano: 'vbf',
+  dynamicMezzo: 'v62',
+  dynamicForte: 'vba',
+  dynamicRinforzando: 'vba',
+  dynamicSforzando: 'v4a',
+  dynamicZ: 'v80',
+
+  // common ornaments (e560-e56f)
+  ornamentTrill: 'v1f',
+  ornamentTurn: 'v72',
+  ornamentTurnSlash: 'v33',
+  ornamentMordent: 'v45',
+  ornamentMordentInverted: 'v1e',
+  ornamentTremblement: 'v86',
+
+  // precomposed trills and mordents (e5b0-e5cf)
+  ornamentPrecompAppoggTrill: 'v20',
+  ornamentPrecompSlideTrillDAnglebert: 'v60',
+  ornamentPrecompSlideTrillBach: 'v29',
+  ornamentPrecompTrillSuffixDandrieu: 'v6d',
+  ornamentPrecompDoubleCadenceUpperPrefix: 'vb4',
+  ornamentPrecompDoubleCadenceUpperPrefixTurn: 'v68',
+  ornamentPrecompTrillLowerSuffix: 'v2c',
+
+  // string techniques (e610-e62f)
+  stringsDownBow: 'v94',
+  stringsUpBow: 'v75',
+  stringsHarmonic: 'vb9',
+
+  // plucked techniques (e630-e63f)
+  pluckedSnapPizzicatoAbove: 'v94',
+  pluckedLeftHandPizzicato: 'v8b', // plus sign
+
+  // keyboard techniques (e650-e67f)
+  keyboardPedalPed: 'v36',
+  keyboardPedalUp: 'v5d',
+
+  // percussion playing technique pictograms (e7f0-e80f)
+  pictChokeCymbal: 'vb3',
+
+  // multi-segment lines (eaa0-eb0f)
+  wiggleArpeggiatoUp: 'va3', // rotated 90deg from reference implementation
+
+  // arrows and arrowheads (eb60-eb8f)
+  arrowheadBlackUp: 'vc3',
+  arrowheadBlackDown: 'v52',
+
+  // not found:
+  // noteheadDiamondWhole: 'v27', stylistic alternate to v46?
+  // noteheadDiamondBlack: 'v70', stylistic alternate to v22?
+  // noteheadTriangleUpBlack: 'v7d', stylistic alternate to v40?
+  // accidentalSlashedDoubleFlat: 'v90',
+  // accidentalOneAndAHalfSharpTurned: 'v7a',
+  // unused marcato alternative?  'v5a',
+  // arpeggioBrushDown: 'v11',
+};
+
+// Some defaults
+Flow.TIME4_4 = {
+  num_beats: 4,
+  beat_value: 4,
+  resolution: Flow.RESOLUTION,
+};
+export { Flow };

+ 224 - 0
test/data/test_notehead_inverted_triangle_stem_down.musicxml

@@ -0,0 +1,224 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<score-partwise version="3.1">
+    <work>
+        <work-title>test_notehead_inverted_triangle_stem_down</work-title>
+    </work>
+    <identification>
+        <encoding>
+            <software>RhythMix</software>
+        </encoding>
+    </identification>
+    <part-list>
+        <score-part id="P1">
+            <part-name>P1</part-name>
+            <part-abbreviation>P1</part-abbreviation>
+            <score-instrument id="P1-I57">
+                <instrument-name>Cowbell</instrument-name>
+            </score-instrument>
+            <score-instrument id="P1-I39">
+                <instrument-name>???????????? ????? ???????</instrument-name>
+            </score-instrument>
+            <score-instrument id="P1-I37">
+                <instrument-name>BD</instrument-name>
+            </score-instrument>
+            <midi-instrument id="P1-I57">
+                <midi-program>1</midi-program>
+                <midi-unpitched>57</midi-unpitched>
+                <volume>78.7402</volume>
+                <pan>0</pan>
+            </midi-instrument>
+            <midi-instrument id="P1-I39">
+                <midi-program>1</midi-program>
+                <midi-unpitched>39</midi-unpitched>
+                <volume>78.7402</volume>
+                <pan>0</pan>
+            </midi-instrument>
+            <midi-instrument id="P1-I37">
+                <midi-program>1</midi-program>
+                <midi-unpitched>37</midi-unpitched>
+                <volume>78.7402</volume>
+                <pan>0</pan>
+            </midi-instrument>
+        </score-part>
+    </part-list>
+    <part id="P1">
+        <measure number="1">
+            <attributes>
+                <divisions>2</divisions>
+                <key>
+                    <fifths>0</fifths>
+                </key>
+                <time>
+                    <beats>4</beats>
+                    <beat-type>4</beat-type>
+                </time>
+                <clef>
+                    <sign>percussion</sign>
+                    <line>2</line>
+                </clef>
+            </attributes>
+            <direction placement="above">
+                <direction-type>
+                    <metronome>
+                        <beat-unit>quarter</beat-unit>
+                        <per-minute>120</per-minute>
+                    </metronome>
+                </direction-type>
+                <sound tempo="120">
+                </sound>
+            </direction>
+            <note>
+                <unpitched>
+                    <display-step>E</display-step>
+                    <display-octave>5</display-octave>
+                </unpitched>
+                <duration>1</duration>
+                <instrument id="P1-I57" />
+                <voice>1</voice>
+                <type>eighth</type>
+                <stem>down</stem>
+                <notehead>inverted triangle</notehead>
+                <beam number="1">begin</beam>
+            </note>
+            <note>
+                <chord />
+                <unpitched>
+                    <display-step>F</display-step>
+                    <display-octave>4</display-octave>
+                </unpitched>
+                <duration>1</duration>
+                <instrument id="P1-I37" />
+                <voice>1</voice>
+                <type>eighth</type>
+                <stem>down</stem>
+            </note>
+            <note>
+                <unpitched>
+                    <display-step>E</display-step>
+                    <display-octave>5</display-octave>
+                </unpitched>
+                <duration>1</duration>
+                <instrument id="P1-I57" />
+                <voice>1</voice>
+                <type size="cue">eighth</type>
+                <stem>down</stem>
+                <notehead>inverted triangle</notehead>
+                <beam number="1">end</beam>
+            </note>
+            <note>
+                <unpitched>
+                    <display-step>E</display-step>
+                    <display-octave>5</display-octave>
+                </unpitched>
+                <duration>1</duration>
+                <instrument id="P1-I57" />
+                <voice>1</voice>
+                <type>eighth</type>
+                <stem>down</stem>
+                <notehead>inverted triangle</notehead>
+                <beam number="1">begin</beam>
+            </note>
+            <note>
+                <chord />
+                <unpitched>
+                    <display-step>C</display-step>
+                    <display-octave>5</display-octave>
+                </unpitched>
+                <duration>1</duration>
+                <instrument id="P1-I39" />
+                <voice>1</voice>
+                <type>eighth</type>
+                <stem>down</stem>
+            </note>
+            <note>
+                <unpitched>
+                    <display-step>E</display-step>
+                    <display-octave>5</display-octave>
+                </unpitched>
+                <duration>1</duration>
+                <instrument id="P1-I57" />
+                <voice>1</voice>
+                <type size="cue">eighth</type>
+                <stem>down</stem>
+                <notehead>inverted triangle</notehead>
+                <beam number="1">end</beam>
+            </note>
+            <note>
+                <unpitched>
+                    <display-step>E</display-step>
+                    <display-octave>5</display-octave>
+                </unpitched>
+                <duration>1</duration>
+                <instrument id="P1-I57" />
+                <voice>1</voice>
+                <type>eighth</type>
+                <stem>down</stem>
+                <notehead>inverted triangle</notehead>
+                <beam number="1">begin</beam>
+            </note>
+            <note>
+                <chord />
+                <unpitched>
+                    <display-step>F</display-step>
+                    <display-octave>4</display-octave>
+                </unpitched>
+                <duration>1</duration>
+                <instrument id="P1-I37" />
+                <voice>1</voice>
+                <type>eighth</type>
+                <stem>down</stem>
+            </note>
+            <note>
+                <unpitched>
+                    <display-step>E</display-step>
+                    <display-octave>5</display-octave>
+                </unpitched>
+                <duration>1</duration>
+                <instrument id="P1-I57" />
+                <voice>1</voice>
+                <type size="cue">eighth</type>
+                <stem>down</stem>
+                <notehead>inverted triangle</notehead>
+                <beam number="1">end</beam>
+            </note>
+            <note>
+                <unpitched>
+                    <display-step>E</display-step>
+                    <display-octave>5</display-octave>
+                </unpitched>
+                <duration>1</duration>
+                <instrument id="P1-I57" />
+                <voice>1</voice>
+                <type>eighth</type>
+                <stem>down</stem>
+                <notehead>inverted triangle</notehead>
+                <beam number="1">begin</beam>
+            </note>
+            <note>
+                <chord />
+                <unpitched>
+                    <display-step>C</display-step>
+                    <display-octave>5</display-octave>
+                </unpitched>
+                <duration>1</duration>
+                <instrument id="P1-I39" />
+                <voice>1</voice>
+                <type>eighth</type>
+                <stem>down</stem>
+            </note>
+            <note>
+                <unpitched>
+                    <display-step>E</display-step>
+                    <display-octave>5</display-octave>
+                </unpitched>
+                <duration>1</duration>
+                <instrument id="P1-I57" />
+                <voice>1</voice>
+                <type size="cue">eighth</type>
+                <stem>down</stem>
+                <notehead>inverted triangle</notehead>
+                <beam number="1">end</beam>
+            </note>
+        </measure>
+    </part>
+</score-partwise>

+ 224 - 0
test/data/test_notehead_inverted_triangle_stem_up.musicxml

@@ -0,0 +1,224 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<score-partwise version="3.1">
+    <work>
+        <work-title>test_notehead_inverted_triangle_stem_up</work-title>
+    </work>
+    <identification>
+        <encoding>
+            <software>RhythMix</software>
+        </encoding>
+    </identification>
+    <part-list>
+        <score-part id="P1">
+            <part-name>P1</part-name>
+            <part-abbreviation>P1</part-abbreviation>
+            <score-instrument id="P1-I57">
+                <instrument-name>Cowbell</instrument-name>
+            </score-instrument>
+            <score-instrument id="P1-I39">
+                <instrument-name>???????????? ????? ???????</instrument-name>
+            </score-instrument>
+            <score-instrument id="P1-I37">
+                <instrument-name>BD</instrument-name>
+            </score-instrument>
+            <midi-instrument id="P1-I57">
+                <midi-program>1</midi-program>
+                <midi-unpitched>57</midi-unpitched>
+                <volume>78.7402</volume>
+                <pan>0</pan>
+            </midi-instrument>
+            <midi-instrument id="P1-I39">
+                <midi-program>1</midi-program>
+                <midi-unpitched>39</midi-unpitched>
+                <volume>78.7402</volume>
+                <pan>0</pan>
+            </midi-instrument>
+            <midi-instrument id="P1-I37">
+                <midi-program>1</midi-program>
+                <midi-unpitched>37</midi-unpitched>
+                <volume>78.7402</volume>
+                <pan>0</pan>
+            </midi-instrument>
+        </score-part>
+    </part-list>
+    <part id="P1">
+        <measure number="1">
+            <attributes>
+                <divisions>2</divisions>
+                <key>
+                    <fifths>0</fifths>
+                </key>
+                <time>
+                    <beats>4</beats>
+                    <beat-type>4</beat-type>
+                </time>
+                <clef>
+                    <sign>percussion</sign>
+                    <line>2</line>
+                </clef>
+            </attributes>
+            <direction placement="above">
+                <direction-type>
+                    <metronome>
+                        <beat-unit>quarter</beat-unit>
+                        <per-minute>120</per-minute>
+                    </metronome>
+                </direction-type>
+                <sound tempo="120">
+                </sound>
+            </direction>
+            <note>
+                <unpitched>
+                    <display-step>E</display-step>
+                    <display-octave>5</display-octave>
+                </unpitched>
+                <duration>1</duration>
+                <instrument id="P1-I57" />
+                <voice>1</voice>
+                <type>eighth</type>
+                <stem>up</stem>
+                <notehead>inverted triangle</notehead>
+                <beam number="1">begin</beam>
+            </note>
+            <note>
+                <chord />
+                <unpitched>
+                    <display-step>F</display-step>
+                    <display-octave>4</display-octave>
+                </unpitched>
+                <duration>1</duration>
+                <instrument id="P1-I37" />
+                <voice>1</voice>
+                <type>eighth</type>
+                <stem>up</stem>
+            </note>
+            <note>
+                <unpitched>
+                    <display-step>E</display-step>
+                    <display-octave>5</display-octave>
+                </unpitched>
+                <duration>1</duration>
+                <instrument id="P1-I57" />
+                <voice>1</voice>
+                <type size="cue">eighth</type>
+                <stem>up</stem>
+                <notehead>inverted triangle</notehead>
+                <beam number="1">end</beam>
+            </note>
+            <note>
+                <unpitched>
+                    <display-step>E</display-step>
+                    <display-octave>5</display-octave>
+                </unpitched>
+                <duration>1</duration>
+                <instrument id="P1-I57" />
+                <voice>1</voice>
+                <type>eighth</type>
+                <stem>up</stem>
+                <notehead>inverted triangle</notehead>
+                <beam number="1">begin</beam>
+            </note>
+            <note>
+                <chord />
+                <unpitched>
+                    <display-step>C</display-step>
+                    <display-octave>5</display-octave>
+                </unpitched>
+                <duration>1</duration>
+                <instrument id="P1-I39" />
+                <voice>1</voice>
+                <type>eighth</type>
+                <stem>up</stem>
+            </note>
+            <note>
+                <unpitched>
+                    <display-step>E</display-step>
+                    <display-octave>5</display-octave>
+                </unpitched>
+                <duration>1</duration>
+                <instrument id="P1-I57" />
+                <voice>1</voice>
+                <type size="cue">eighth</type>
+                <stem>up</stem>
+                <notehead>inverted triangle</notehead>
+                <beam number="1">end</beam>
+            </note>
+            <note>
+                <unpitched>
+                    <display-step>E</display-step>
+                    <display-octave>5</display-octave>
+                </unpitched>
+                <duration>1</duration>
+                <instrument id="P1-I57" />
+                <voice>1</voice>
+                <type>eighth</type>
+                <stem>up</stem>
+                <notehead>inverted triangle</notehead>
+                <beam number="1">begin</beam>
+            </note>
+            <note>
+                <chord />
+                <unpitched>
+                    <display-step>F</display-step>
+                    <display-octave>4</display-octave>
+                </unpitched>
+                <duration>1</duration>
+                <instrument id="P1-I37" />
+                <voice>1</voice>
+                <type>eighth</type>
+                <stem>up</stem>
+            </note>
+            <note>
+                <unpitched>
+                    <display-step>E</display-step>
+                    <display-octave>5</display-octave>
+                </unpitched>
+                <duration>1</duration>
+                <instrument id="P1-I57" />
+                <voice>1</voice>
+                <type size="cue">eighth</type>
+                <stem>up</stem>
+                <notehead>inverted triangle</notehead>
+                <beam number="1">end</beam>
+            </note>
+            <note>
+                <unpitched>
+                    <display-step>E</display-step>
+                    <display-octave>5</display-octave>
+                </unpitched>
+                <duration>1</duration>
+                <instrument id="P1-I57" />
+                <voice>1</voice>
+                <type>eighth</type>
+                <stem>up</stem>
+                <notehead>inverted triangle</notehead>
+                <beam number="1">begin</beam>
+            </note>
+            <note>
+                <chord />
+                <unpitched>
+                    <display-step>C</display-step>
+                    <display-octave>5</display-octave>
+                </unpitched>
+                <duration>1</duration>
+                <instrument id="P1-I39" />
+                <voice>1</voice>
+                <type>eighth</type>
+                <stem>up</stem>
+            </note>
+            <note>
+                <unpitched>
+                    <display-step>E</display-step>
+                    <display-octave>5</display-octave>
+                </unpitched>
+                <duration>1</duration>
+                <instrument id="P1-I57" />
+                <voice>1</voice>
+                <type size="cue">eighth</type>
+                <stem>up</stem>
+                <notehead>inverted triangle</notehead>
+                <beam number="1">end</beam>
+            </note>
+        </measure>
+    </part>
+</score-partwise>