opensheetmusicdisplay/opensheetmusicdisplay

View on GitHub
src/VexFlowPatch/src/tables.js

Summary

Maintainability
F
1 wk
Test Coverage
// [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 -= options.octave_shift;
  // VexFlowPatch: change "+= -1 *" to "-=". No change in OSMD detected,
  //   though apparently -0 can cause issues on some systems.
  //   https://github.com/0xfe/vexflow/pull/1596

  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 },

  /* Whole and Half Rests with ledger line */
  'rwl': { code: 'rwl', shift_right: 0 },
  'rhl': { code: 'rhl', 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 };