UNPKG

vexflow

Version:

A JavaScript library for rendering music notation and guitar tablature.

665 lines (664 loc) 22.5 kB
import { Fraction } from './fraction.js'; import { Glyphs } from './glyphs.js'; import { RuntimeError } from './util.js'; const RESOLUTION = 16384; const durations = { '1/2': RESOLUTION * 2, 1: RESOLUTION / 1, 2: RESOLUTION / 2, 4: RESOLUTION / 4, 8: RESOLUTION / 8, 16: RESOLUTION / 16, 32: RESOLUTION / 32, 64: RESOLUTION / 64, 128: RESOLUTION / 128, 256: RESOLUTION / 256, }; const durationAliases = { w: '1', h: '2', q: '4', b: '256', }; const keySignatures = { C: { num: 0 }, Am: { num: 0 }, F: { accidental: 'b', num: 1 }, Dm: { accidental: 'b', num: 1 }, Bb: { accidental: 'b', num: 2 }, Gm: { accidental: 'b', num: 2 }, Eb: { accidental: 'b', num: 3 }, Cm: { accidental: 'b', num: 3 }, Ab: { accidental: 'b', num: 4 }, Fm: { accidental: 'b', num: 4 }, Db: { accidental: 'b', num: 5 }, Bbm: { accidental: 'b', num: 5 }, Gb: { accidental: 'b', num: 6 }, Ebm: { accidental: 'b', num: 6 }, Cb: { accidental: 'b', num: 7 }, Abm: { accidental: 'b', num: 7 }, G: { accidental: '#', num: 1 }, Em: { accidental: '#', num: 1 }, D: { accidental: '#', num: 2 }, Bm: { accidental: '#', num: 2 }, A: { accidental: '#', num: 3 }, 'F#m': { accidental: '#', num: 3 }, E: { accidental: '#', num: 4 }, 'C#m': { accidental: '#', num: 4 }, B: { accidental: '#', num: 5 }, 'G#m': { accidental: '#', num: 5 }, 'F#': { accidental: '#', num: 6 }, 'D#m': { accidental: '#', num: 6 }, 'C#': { accidental: '#', num: 7 }, 'A#m': { accidental: '#', num: 7 }, }; const clefs = { treble: { lineShift: 0 }, bass: { lineShift: 6 }, tenor: { lineShift: 4 }, alto: { lineShift: 3 }, soprano: { lineShift: 1 }, percussion: { lineShift: 0 }, 'mezzo-soprano': { lineShift: 2 }, 'baritone-c': { lineShift: 5 }, 'baritone-f': { lineShift: 5 }, subbass: { lineShift: 7 }, french: { lineShift: -1 }, }; const notesInfo = { C: { index: 0, intVal: 0 }, CN: { index: 0, intVal: 0 }, 'C#': { index: 0, intVal: 1 }, 'C##': { index: 0, intVal: 2 }, CB: { index: 0, intVal: 11 }, CBB: { index: 0, intVal: 10 }, D: { index: 1, intVal: 2 }, DN: { index: 1, intVal: 2 }, 'D#': { index: 1, intVal: 3 }, 'D##': { index: 1, intVal: 4 }, DB: { index: 1, intVal: 1 }, DBB: { index: 1, intVal: 0 }, E: { index: 2, intVal: 4 }, EN: { index: 2, intVal: 4 }, 'E#': { index: 2, intVal: 5 }, 'E##': { index: 2, intVal: 6 }, EB: { index: 2, intVal: 3 }, EBB: { index: 2, intVal: 2 }, F: { index: 3, intVal: 5 }, FN: { index: 3, intVal: 5 }, 'F#': { index: 3, intVal: 6 }, 'F##': { index: 3, intVal: 7 }, FB: { index: 3, intVal: 4 }, FBB: { index: 3, intVal: 3 }, G: { index: 4, intVal: 7 }, GN: { index: 4, intVal: 7 }, 'G#': { index: 4, intVal: 8 }, 'G##': { index: 4, intVal: 9 }, GB: { index: 4, intVal: 6 }, GBB: { index: 4, intVal: 5 }, A: { index: 5, intVal: 9 }, AN: { index: 5, intVal: 9 }, 'A#': { index: 5, intVal: 10 }, 'A##': { index: 5, intVal: 11 }, AB: { index: 5, intVal: 8 }, ABB: { index: 5, intVal: 7 }, B: { index: 6, intVal: 11 }, BN: { index: 6, intVal: 11 }, 'B#': { index: 6, intVal: 12 }, 'B##': { index: 6, intVal: 13 }, BB: { index: 6, intVal: 10 }, BBB: { index: 6, intVal: 9 }, R: { index: 6 }, X: { index: 6 }, }; const validNoteTypes = { n: { name: 'note' }, r: { name: 'rest' }, h: { name: 'harmonic' }, m: { name: 'muted' }, s: { name: 'slash' }, g: { name: 'ghost' }, d: { name: 'diamond' }, x: { name: 'x' }, ci: { name: 'circled' }, cx: { name: 'circle x' }, sf: { name: 'slashed' }, sb: { name: 'slashed backward' }, sq: { name: 'square' }, tu: { name: 'triangle up' }, td: { name: 'triangle down' }, }; const accidentals = { '#': Glyphs.accidentalSharp, '##': Glyphs.accidentalDoubleSharp, b: Glyphs.accidentalFlat, bb: Glyphs.accidentalDoubleFlat, n: Glyphs.accidentalNatural, '{': Glyphs.accidentalParensLeft, '}': Glyphs.accidentalParensRight, db: Glyphs.accidentalThreeQuarterTonesFlatZimmermann, d: Glyphs.accidentalQuarterToneFlatStein, '++': Glyphs.accidentalThreeQuarterTonesSharpStein, '+': Glyphs.accidentalQuarterToneSharpStein, '+-': Glyphs.accidentalKucukMucennebSharp, bs: Glyphs.accidentalBakiyeFlat, bss: Glyphs.accidentalBuyukMucennebFlat, o: Glyphs.accidentalSori, k: Glyphs.accidentalKoron, bbs: Glyphs.accidentalBuyukMucennebSharp, '++-': Glyphs.accidentalBuyukMucennebSharp, ashs: Glyphs.accidentalBuyukMucennebSharp, afhf: Glyphs.accidentalBuyukMucennebSharp, }; const accidentalColumns = { 1: { a: [1], b: [1], }, 2: { a: [1, 2], }, 3: { a: [1, 3, 2], b: [1, 2, 1], secondOnBottom: [1, 2, 3], }, 4: { a: [1, 3, 4, 2], b: [1, 2, 3, 1], spacedOutTetrachord: [1, 2, 1, 2], }, 5: { a: [1, 3, 5, 4, 2], b: [1, 2, 4, 3, 1], spacedOutPentachord: [1, 2, 3, 2, 1], verySpacedOutPentachord: [1, 2, 1, 2, 1], }, 6: { a: [1, 3, 5, 6, 4, 2], b: [1, 2, 4, 5, 3, 1], spacedOutHexachord: [1, 3, 2, 1, 3, 2], verySpacedOutHexachord: [1, 2, 1, 2, 1, 2], }, }; const articulations = { 'a.': { code: Glyphs.augmentationDot, betweenLines: true }, av: { aboveCode: Glyphs.articStaccatissimoAbove, belowCode: Glyphs.articStaccatissimoBelow, betweenLines: true, }, 'a>': { aboveCode: Glyphs.articAccentAbove, belowCode: Glyphs.articAccentBelow, betweenLines: true, }, 'a-': { aboveCode: Glyphs.articTenutoAbove, belowCode: Glyphs.articTenutoBelow, betweenLines: true, }, 'a^': { aboveCode: Glyphs.articMarcatoAbove, belowCode: Glyphs.articMarcatoBelow, betweenLines: false, }, 'a+': { code: Glyphs.pluckedLeftHandPizzicato, betweenLines: false }, ao: { aboveCode: Glyphs.pluckedSnapPizzicatoAbove, belowCode: Glyphs.pluckedSnapPizzicatoBelow, betweenLines: false, }, ah: { code: Glyphs.stringsHarmonic, betweenLines: false }, 'a@': { aboveCode: Glyphs.fermataAbove, belowCode: Glyphs.fermataBelow, betweenLines: false }, 'a@a': { code: Glyphs.fermataAbove, betweenLines: false }, 'a@u': { code: Glyphs.fermataBelow, betweenLines: false }, 'a@s': { aboveCode: Glyphs.fermataShortAbove, belowCode: Glyphs.fermataShortBelow, betweenLines: false }, 'a@as': { code: Glyphs.fermataShortAbove, betweenLines: false }, 'a@us': { code: Glyphs.fermataShortBelow, betweenLines: false }, 'a@l': { aboveCode: Glyphs.fermataLongAbove, belowCode: Glyphs.fermataLongBelow, betweenLines: false }, 'a@al': { code: Glyphs.fermataLongAbove, betweenLines: false }, 'a@ul': { code: Glyphs.fermataLongBelow, betweenLines: false }, 'a@vl': { aboveCode: Glyphs.fermataVeryLongAbove, belowCode: Glyphs.fermataVeryLongBelow, betweenLines: false, }, 'a@avl': { code: Glyphs.fermataVeryLongAbove, betweenLines: false }, 'a@uvl': { code: Glyphs.fermataVeryLongBelow, betweenLines: false }, 'a|': { code: Glyphs.stringsUpBow, betweenLines: false }, am: { code: Glyphs.stringsDownBow, betweenLines: false }, 'a,': { code: Glyphs.pictChokeCymbal, betweenLines: false }, }; const ornaments = { mordent: Glyphs.ornamentShortTrill, mordentInverted: Glyphs.ornamentMordent, turn: Glyphs.ornamentTurn, turnInverted: Glyphs.ornamentTurnSlash, tr: Glyphs.ornamentTrill, upprall: Glyphs.ornamentPrecompSlideTrillDAnglebert, downprall: Glyphs.ornamentPrecompDoubleCadenceUpperPrefix, prallup: Glyphs.ornamentPrecompTrillSuffixDandrieu, pralldown: Glyphs.ornamentPrecompTrillLowerSuffix, upmordent: Glyphs.ornamentPrecompSlideTrillBach, downmordent: Glyphs.ornamentPrecompDoubleCadenceUpperPrefixTurn, lineprall: Glyphs.ornamentPrecompAppoggTrill, prallprall: Glyphs.ornamentTremblement, scoop: Glyphs.brassScoop, doit: Glyphs.brassDoitMedium, fall: Glyphs.brassFallLipShort, doitLong: Glyphs.brassLiftMedium, fallLong: Glyphs.brassFallRoughMedium, bend: Glyphs.brassBend, plungerClosed: Glyphs.brassMuteClosed, plungerOpen: Glyphs.brassMuteOpen, flip: Glyphs.brassFlip, jazzTurn: Glyphs.brassJazzTurn, smear: Glyphs.brassSmear, }; export class Tables { static clefProperties(clef) { if (!clef || !(clef in clefs)) throw new RuntimeError('BadArgument', 'Invalid clef: ' + clef); return clefs[clef]; } static keyProperties(keyOctaveGlyph, clef = 'treble', type = 'N', params) { let options = { octaveShift: 0, duration: '4' }; if (typeof params === 'object') { options = Object.assign(Object.assign({}, options), params); } const duration = Tables.sanitizeDuration(options.duration); const pieces = keyOctaveGlyph.split('/'); if (pieces.length < 2) { throw new RuntimeError('BadArguments', `First argument must be note/octave or note/octave/glyph-code: ${keyOctaveGlyph}`); } const key = pieces[0].toUpperCase(); type = type.toUpperCase(); const value = notesInfo[key]; if (!value) throw new RuntimeError('BadArguments', 'Invalid key name: ' + key); let octave = parseInt(pieces[1], 10); octave -= options.octaveShift; const baseIndex = octave * 7 - 4 * 7; let line = (baseIndex + value.index) / 2; line += Tables.clefProperties(clef).lineShift; const intValue = typeof value.intVal !== 'undefined' ? octave * 12 + value.intVal : undefined; let code = ''; let glyphName = 'N'; if (pieces.length > 2 && pieces[2]) { glyphName = pieces[2].toUpperCase(); } else if (type !== 'N') { glyphName = type; } else glyphName = key; code = this.codeNoteHead(glyphName, duration); return { key, octave, line, intValue, code, displaced: false, }; } static integerToNote(integer) { if (typeof integer === 'undefined' || integer < 0 || integer > 11) { throw new RuntimeError('BadArguments', `integerToNote() requires an integer in the range [0, 11]: ${integer}`); } const 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', }; const noteValue = table[integer]; if (!noteValue) { throw new RuntimeError('BadArguments', `Unknown note value for integer: ${integer}`); } return noteValue; } static textWidth(text) { return 7 * text.toString().length; } static articulationCodes(artic) { return articulations[artic]; } static accidentalCodes(accidental) { var _a; return (_a = accidentals[accidental]) !== null && _a !== void 0 ? _a : accidental; } static ornamentCodes(ornament) { var _a; return (_a = ornaments[ornament]) !== null && _a !== void 0 ? _a : ornament; } static keySignature(spec) { const keySpec = keySignatures[spec]; if (!keySpec) { throw new RuntimeError('BadKeySignature', `Bad key signature spec: '${spec}'`); } if (!keySpec.accidental) { return []; } const accidentalList = { b: [2, 0.5, 2.5, 1, 3, 1.5, 3.5], '#': [0, 1.5, -0.5, 1, 2.5, 0.5, 2], }; const notes = accidentalList[keySpec.accidental]; const accList = []; for (let i = 0; i < keySpec.num; ++i) { const line = notes[i]; accList.push({ type: keySpec.accidental, line }); } return accList; } static getKeySignatures() { return keySignatures; } static hasKeySignature(spec) { return spec in keySignatures; } static sanitizeDuration(duration) { const durationNumber = durationAliases[duration]; if (durationNumber !== undefined) { duration = durationNumber; } if (durations[duration] === undefined) { throw new RuntimeError('BadArguments', `The provided duration is not valid: ${duration}`); } return duration; } static durationToFraction(duration) { return new Fraction().parse(Tables.sanitizeDuration(duration)); } static durationToNumber(duration) { return Tables.durationToFraction(duration).value(); } static durationToTicks(duration) { duration = Tables.sanitizeDuration(duration); const ticks = durations[duration]; if (ticks === undefined) { throw new RuntimeError('InvalidDuration'); } return ticks; } static codeNoteHead(type, duration) { switch (type) { case 'D0': return Glyphs.noteheadDiamondWhole; case 'D1': return Glyphs.noteheadDiamondHalf; case 'D2': return Glyphs.noteheadDiamondBlack; case 'D3': return Glyphs.noteheadDiamondBlack; case 'T0': return Glyphs.noteheadTriangleUpWhole; case 'T1': return Glyphs.noteheadTriangleUpHalf; case 'T2': return Glyphs.noteheadTriangleUpBlack; case 'T3': return Glyphs.noteheadTriangleUpBlack; case 'X0': return Glyphs.noteheadXWhole; case 'X1': return Glyphs.noteheadXHalf; case 'X2': return Glyphs.noteheadXBlack; case 'X3': return Glyphs.noteheadCircleX; case 'S1': return Glyphs.noteheadSquareWhite; case 'S2': return Glyphs.noteheadSquareBlack; case 'R1': return Glyphs.noteheadSquareWhite; case 'R2': return Glyphs.noteheadSquareWhite; case 'DO': return Glyphs.noteheadTriangleUpBlack; case 'RE': return Glyphs.noteheadMoonBlack; case 'MI': return Glyphs.noteheadDiamondBlack; case 'FA': return Glyphs.noteheadTriangleLeftBlack; case 'FAUP': return Glyphs.noteheadTriangleRightBlack; case 'SO': return Glyphs.noteheadBlack; case 'LA': return Glyphs.noteheadSquareBlack; case 'TI': return Glyphs.noteheadTriangleRoundDownBlack; case 'DI': case 'H': switch (duration) { case '1/2': return Glyphs.noteheadDiamondDoubleWhole; case '1': return Glyphs.noteheadDiamondWhole; case '2': return Glyphs.noteheadDiamondHalf; default: return Glyphs.noteheadDiamondBlack; } case 'X': case 'M': switch (duration) { case '1/2': return Glyphs.noteheadXDoubleWhole; case '1': return Glyphs.noteheadXWhole; case '2': return Glyphs.noteheadXHalf; default: return Glyphs.noteheadXBlack; } case 'CX': switch (duration) { case '1/2': return Glyphs.noteheadCircleXDoubleWhole; case '1': return Glyphs.noteheadCircleXWhole; case '2': return Glyphs.noteheadCircleXHalf; default: return Glyphs.noteheadCircleX; } case 'CI': switch (duration) { case '1/2': return Glyphs.noteheadCircledDoubleWhole; case '1': return Glyphs.noteheadCircledWhole; case '2': return Glyphs.noteheadCircledHalf; default: return Glyphs.noteheadCircledBlack; } case 'SQ': switch (duration) { case '1/2': return Glyphs.noteheadDoubleWholeSquare; case '1': return Glyphs.noteheadSquareWhite; case '2': return Glyphs.noteheadSquareWhite; default: return Glyphs.noteheadSquareBlack; } case 'TU': switch (duration) { case '1/2': return Glyphs.noteheadTriangleUpDoubleWhole; case '1': return Glyphs.noteheadTriangleUpWhole; case '2': return Glyphs.noteheadTriangleUpHalf; default: return Glyphs.noteheadTriangleUpBlack; } case 'TD': switch (duration) { case '1/2': return Glyphs.noteheadTriangleDownDoubleWhole; case '1': return Glyphs.noteheadTriangleDownWhole; case '2': return Glyphs.noteheadTriangleDownHalf; default: return Glyphs.noteheadTriangleDownBlack; } case 'SF': switch (duration) { case '1/2': return Glyphs.noteheadSlashedDoubleWhole1; case '1': return Glyphs.noteheadSlashedWhole1; case '2': return Glyphs.noteheadSlashedHalf1; default: return Glyphs.noteheadSlashedBlack1; } case 'SB': switch (duration) { case '1/2': return Glyphs.noteheadSlashedDoubleWhole2; case '1': return Glyphs.noteheadSlashedWhole2; case '2': return Glyphs.noteheadSlashedHalf2; default: return Glyphs.noteheadSlashedBlack2; } case 'R': switch (duration) { case '1/2': return Glyphs.restDoubleWhole; case '1': return Glyphs.restWhole; case '2': return Glyphs.restHalf; case '4': return Glyphs.restQuarter; case '8': return Glyphs.rest8th; case '16': return Glyphs.rest16th; case '32': return Glyphs.rest32nd; case '64': return Glyphs.rest64th; case '128': return Glyphs.rest128th; } break; case 'S': switch (duration) { case '1/2': return Glyphs.noteheadSlashWhiteDoubleWhole; case '1': return Glyphs.noteheadSlashWhiteWhole; case '2': return Glyphs.noteheadSlashWhiteHalf; default: return Glyphs.noteheadSlashVerticalEnds; } default: switch (duration) { case '1/2': return Glyphs.noteheadDoubleWhole; case '1': return Glyphs.noteheadWhole; case '2': return Glyphs.noteheadHalf; default: return Glyphs.noteheadBlack; } } return Glyphs.null; } } Tables.UNISON = true; Tables.SOFTMAX_FACTOR = 10; Tables.STEM_WIDTH = 1.5; Tables.STEM_HEIGHT = 35; Tables.STAVE_LINE_THICKNESS = 1; Tables.RENDER_PRECISION_PLACES = 3; Tables.RESOLUTION = RESOLUTION; Tables.durationCodes = { '1/2': { stem: false, }, 1: { stem: false, }, 2: { stem: true, }, 4: { stem: true, }, 8: { stem: true, beamCount: 1, stemBeamExtension: 0, codeFlagUp: Glyphs.flag8thUp, }, 16: { beamCount: 2, stemBeamExtension: 0, stem: true, codeFlagUp: Glyphs.flag16thUp, }, 32: { beamCount: 3, stemBeamExtension: 7.5, stem: true, codeFlagUp: Glyphs.flag32ndUp, }, 64: { beamCount: 4, stemBeamExtension: 15, stem: true, codeFlagUp: Glyphs.flag64thUp, }, 128: { beamCount: 5, stemBeamExtension: 22.5, stem: true, codeFlagUp: Glyphs.flag128thUp, }, }; Tables.NOTATION_FONT_SCALE = 39; Tables.TABLATURE_FONT_SCALE = 39; Tables.SLASH_NOTEHEAD_WIDTH = 15; Tables.STAVE_LINE_DISTANCE = 10; Tables.TEXT_HEIGHT_OFFSET_HACK = 1; Tables.accidentalColumnsTable = accidentalColumns; Tables.unicode = { sharp: '\u266f', flat: '\u266d', natural: '\u266e', triangle: '\u25b3', 'o-with-slash': '\u00f8', degrees: '\u00b0', circle: '\u25cb', }; Tables.validTypes = validNoteTypes; Tables.TIME4_4 = { numBeats: 4, beatValue: 4, resolution: RESOLUTION, };