VexFlowConverter.ts 41 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963
  1. import Vex from "vexflow";
  2. import {ClefEnum} from "../../VoiceData/Instructions/ClefInstruction";
  3. import {ClefInstruction} from "../../VoiceData/Instructions/ClefInstruction";
  4. import {Pitch} from "../../../Common/DataObjects/Pitch";
  5. import {Fraction} from "../../../Common/DataObjects/Fraction";
  6. import {RhythmInstruction} from "../../VoiceData/Instructions/RhythmInstruction";
  7. import {RhythmSymbolEnum} from "../../VoiceData/Instructions/RhythmInstruction";
  8. import {KeyInstruction} from "../../VoiceData/Instructions/KeyInstruction";
  9. import {KeyEnum} from "../../VoiceData/Instructions/KeyInstruction";
  10. import {AccidentalEnum} from "../../../Common/DataObjects/Pitch";
  11. import {NoteEnum} from "../../../Common/DataObjects/Pitch";
  12. import {VexFlowGraphicalNote} from "./VexFlowGraphicalNote";
  13. import {GraphicalNote} from "../GraphicalNote";
  14. import {SystemLinesEnum} from "../SystemLinesEnum";
  15. import {FontStyles} from "../../../Common/Enums/FontStyles";
  16. import {Fonts} from "../../../Common/Enums/Fonts";
  17. import {OutlineAndFillStyleEnum, OUTLINE_AND_FILL_STYLE_DICT} from "../DrawingEnums";
  18. import log from "loglevel";
  19. import { ArticulationEnum, StemDirectionType, VoiceEntry } from "../../VoiceData/VoiceEntry";
  20. import { SystemLinePosition } from "../SystemLinePosition";
  21. import { GraphicalVoiceEntry } from "../GraphicalVoiceEntry";
  22. import { OrnamentEnum, OrnamentContainer } from "../../VoiceData/OrnamentContainer";
  23. import { Notehead, NoteHeadShape } from "../../VoiceData/Notehead";
  24. import { unitInPixels } from "./VexFlowMusicSheetDrawer";
  25. import { EngravingRules } from "../EngravingRules";
  26. import { Note } from "../../../MusicalScore/VoiceData/Note";
  27. import StaveNote = Vex.Flow.StaveNote;
  28. import { ArpeggioType } from "../../VoiceData/Arpeggio";
  29. import { TabNote } from "../../VoiceData/TabNote";
  30. import { PlacementEnum } from "../../VoiceData/Expressions/AbstractExpression";
  31. import { GraphicalStaffEntry } from "../GraphicalStaffEntry";
  32. import { Articulation } from "../../VoiceData/Articulation";
  33. /**
  34. * Helper class, which contains static methods which actually convert
  35. * from OSMD objects to VexFlow objects.
  36. */
  37. export class VexFlowConverter {
  38. /**
  39. * Mapping from numbers of alterations on the key signature to major keys
  40. * @type {[alterationsNo: number]: string; }
  41. */
  42. private static majorMap: {[_: number]: string } = {
  43. "-1": "F", "-2": "Bb", "-3": "Eb", "-4": "Ab", "-5": "Db", "-6": "Gb", "-7": "Cb", "-8": "Fb",
  44. "0": "C", "1": "G", "2": "D", "3": "A", "4": "E", "5": "B", "6": "F#", "7": "C#", "8": "G#"
  45. };
  46. /**
  47. * Mapping from numbers of alterations on the key signature to minor keys
  48. * @type {[alterationsNo: number]: string; }
  49. */
  50. private static minorMap: {[_: number]: string } = {
  51. "-1": "D", "-2": "G", "-3": "C", "-4": "F", "-5": "Bb", "-6": "Eb", "-7": "Ab", "-8": "Db",
  52. "0": "A", "1": "E", "2": "B", "3": "F#", "4": "C#", "5": "G#", "6": "D#", "7": "A#", "8": "E#"
  53. };
  54. /**
  55. * Convert a fraction to a string which represents a duration in VexFlow
  56. * @param fraction a fraction representing the duration of a note
  57. * @returns {string}
  58. */
  59. public static duration(fraction: Fraction, isTuplet: boolean): string {
  60. const dur: number = fraction.RealValue;
  61. if (dur === 2) { // Breve
  62. return "1/2";
  63. }
  64. // TODO consider long (dur=4) and maxima (dur=8), though Vexflow doesn't seem to support them
  65. if (dur >= 1) {
  66. return "w";
  67. } else if (dur < 1 && dur >= 0.5) {
  68. // change to the next higher straight note to get the correct note display type
  69. if (isTuplet && dur > 0.5) {
  70. return "w";
  71. }
  72. return "h";
  73. } else if (dur < 0.5 && dur >= 0.25) {
  74. // change to the next higher straight note to get the correct note display type
  75. if (isTuplet && dur > 0.25) {
  76. return "h";
  77. }
  78. return "q";
  79. } else if (dur < 0.25 && dur >= 0.125) {
  80. // change to the next higher straight note to get the correct note display type
  81. if (isTuplet && dur > 0.125) {
  82. return "q";
  83. }
  84. return "8";
  85. } else if (dur < 0.125 && dur >= 0.0625) {
  86. // change to the next higher straight note to get the correct note display type
  87. if (isTuplet && dur > 0.0625) {
  88. return "8";
  89. }
  90. return "16";
  91. } else if (dur < 0.0625 && dur >= 0.03125) {
  92. // change to the next higher straight note to get the correct note display type
  93. if (isTuplet && dur > 0.03125) {
  94. return "16";
  95. }
  96. return "32";
  97. } else if (dur < 0.03125 && dur >= 0.015625) {
  98. // change to the next higher straight note to get the correct note display type
  99. if (isTuplet && dur > 0.015625) {
  100. return "32";
  101. }
  102. return "64";
  103. }
  104. if (isTuplet) {
  105. return "64";
  106. }
  107. return "128";
  108. }
  109. /**
  110. * Takes a Pitch and returns a string representing a VexFlow pitch,
  111. * which has the form "b/4", plus its alteration (accidental)
  112. * @param pitch
  113. * @returns {string[]}
  114. */
  115. public static pitch(pitch: Pitch, isRest: boolean, clef: ClefInstruction,
  116. notehead: Notehead = undefined): [string, string, ClefInstruction] {
  117. //FIXME: The octave seems to need a shift of three?
  118. //FIXME: Also rests seem to use different offsets depending on the clef.
  119. let fixmeOffset: number = 3;
  120. if (isRest) {
  121. fixmeOffset = 0;
  122. if (clef.ClefType === ClefEnum.F) {
  123. fixmeOffset = 2;
  124. }
  125. if (clef.ClefType === ClefEnum.C) {
  126. fixmeOffset = 2;
  127. }
  128. // TODO the pitch for rests will be the start position, for eights rests it will be the bottom point
  129. // maybe we want to center on the display position instead of having the bottom there?
  130. }
  131. const fund: string = NoteEnum[pitch.FundamentalNote].toLowerCase();
  132. const acc: string = Pitch.accidentalVexflow(pitch.Accidental);
  133. const octave: number = pitch.Octave - clef.OctaveOffset + fixmeOffset;
  134. let noteheadCode: string = "";
  135. if (notehead) {
  136. noteheadCode = this.NoteHeadCode(notehead);
  137. }
  138. return [fund + "n/" + octave + noteheadCode, acc, clef];
  139. }
  140. public static restToNotePitch(pitch: Pitch, clefType: ClefEnum): Pitch {
  141. let octave: number = pitch.Octave;
  142. // offsets see pitch()
  143. switch (clefType) {
  144. case ClefEnum.C:
  145. case ClefEnum.F: {
  146. octave += 2;
  147. break;
  148. }
  149. case ClefEnum.G:
  150. default:
  151. }
  152. return new Pitch(pitch.FundamentalNote, octave, AccidentalEnum.NONE);
  153. }
  154. /** returns the Vexflow code for a note head. Some are still unsupported, see Vexflow/tables.js */
  155. public static NoteHeadCode(notehead: Notehead): string {
  156. const codeStart: string = "/";
  157. const codeFilled: string = notehead.Filled ? "2" : "1"; // filled/unfilled notehead code in most vexflow glyphs
  158. switch (notehead.Shape) {
  159. case NoteHeadShape.NORMAL:
  160. return "";
  161. case NoteHeadShape.DIAMOND:
  162. return codeStart + "D" + codeFilled;
  163. case NoteHeadShape.TRIANGLE:
  164. return codeStart + "T" + codeFilled;
  165. case NoteHeadShape.X:
  166. return codeStart + "X" + codeFilled;
  167. case NoteHeadShape.CIRCLEX:
  168. return codeStart + "X3";
  169. case NoteHeadShape.RECTANGLE:
  170. return codeStart + "R" + codeFilled;
  171. case NoteHeadShape.SQUARE:
  172. return codeStart + "S" + codeFilled;
  173. case NoteHeadShape.SLASH:
  174. return ""; // slash is specified at end of duration string in Vexflow
  175. default:
  176. return "";
  177. }
  178. }
  179. public static GhostNote(frac: Fraction): Vex.Flow.GhostNote {
  180. return new Vex.Flow.GhostNote({
  181. duration: VexFlowConverter.duration(frac, false),
  182. });
  183. }
  184. /**
  185. * Convert a GraphicalVoiceEntry to a VexFlow StaveNote
  186. * @param gve the GraphicalVoiceEntry which can hold a note or a chord on the staff belonging to one voice
  187. * @returns {Vex.Flow.StaveNote}
  188. */
  189. public static StaveNote(gve: GraphicalVoiceEntry): Vex.Flow.StaveNote {
  190. // if (gve.octaveShiftValue !== OctaveEnum.NONE) { // gves with accidentals in octave shift brackets can be unsorted
  191. gve.sortForVexflow(); // also necessary for some other cases, see test_sorted_notes... sample
  192. // sort and reverse replace the array anyways, so we might as well directly sort them reversely for now.
  193. // otherwise we should copy the array, see the commented GraphicalVoiceEntry.sortedNotesCopyForVexflow()
  194. // another alternative: don't sort gve notes, instead collect and sort tickables in an array,
  195. // then iterate over the array by addTickable() in VexFlowMeasure.graphicalMeasureCreatedCalculations()
  196. const notes: GraphicalNote[] = gve.notes;
  197. // for (const note of gve.notes) { // debug
  198. // const pitch: Pitch = note.sourceNote.Pitch;
  199. // console.log('note: ' + pitch?.ToString() + ', halftone: ' + pitch?.getHalfTone());
  200. // }
  201. const rules: EngravingRules = gve.parentStaffEntry.parentMeasure.parentSourceMeasure.Rules;
  202. const baseNote: GraphicalNote = notes[0];
  203. let keys: string[] = [];
  204. const accidentals: string[] = [];
  205. const baseNoteLength: Fraction = baseNote.graphicalNoteLength;
  206. const isTuplet: boolean = baseNote.sourceNote.NoteTuplet !== undefined;
  207. let duration: string = VexFlowConverter.duration(baseNoteLength, isTuplet);
  208. if (baseNote.sourceNote.TypeLength !== undefined && baseNote.sourceNote.TypeLength !== baseNoteLength) {
  209. duration = VexFlowConverter.duration(baseNote.sourceNote.TypeLength, isTuplet);
  210. }
  211. let vfClefType: string = undefined;
  212. let numDots: number = baseNote.numberOfDots;
  213. let alignCenter: boolean = false;
  214. let xShift: number = 0;
  215. let slashNoteHead: boolean = false;
  216. let isRest: boolean = false;
  217. for (const note of notes) {
  218. if (numDots < note.numberOfDots) {
  219. numDots = note.numberOfDots;
  220. }
  221. // if it is a rest:
  222. if (note.sourceNote.isRest()) {
  223. isRest = true;
  224. if (note.sourceNote.Pitch) {
  225. const restVfPitch: [string, string, ClefInstruction] = (note as VexFlowGraphicalNote).vfpitch;
  226. keys = [restVfPitch[0]];
  227. break;
  228. } else {
  229. keys = ["b/4"]; // default placement
  230. // pause rest encircled by two beamed notes: place rest just below previous note
  231. const pauseVoiceEntry: VoiceEntry = note.parentVoiceEntry?.parentVoiceEntry;
  232. if (pauseVoiceEntry) {
  233. const neighborGSEs: GraphicalStaffEntry[] = note.parentVoiceEntry?.parentStaffEntry.parentMeasure.staffEntries;
  234. let previousVoiceEntry: VoiceEntry, followingVoiceEntry: VoiceEntry;
  235. let pauseVEIndex: number = -1;
  236. for (let i: number = 0; i < neighborGSEs.length; i++) {
  237. if (neighborGSEs[i]?.graphicalVoiceEntries[0].parentVoiceEntry === pauseVoiceEntry) {
  238. pauseVEIndex = i;
  239. break;
  240. }
  241. }
  242. if (pauseVEIndex >= 1 && (neighborGSEs.length - 1) >= (pauseVEIndex + 1)) {
  243. previousVoiceEntry = neighborGSEs[pauseVEIndex - 1]?.graphicalVoiceEntries[0]?.parentVoiceEntry;
  244. followingVoiceEntry = neighborGSEs[pauseVEIndex + 1]?.graphicalVoiceEntries[0]?.parentVoiceEntry;
  245. if (previousVoiceEntry && followingVoiceEntry) {
  246. const previousNote: Note = previousVoiceEntry.Notes[0];
  247. const followingNote: Note = followingVoiceEntry.Notes[0];
  248. if (previousNote.NoteBeam?.Notes.includes(followingNote)) {
  249. const previousNotePitch: Pitch = previousVoiceEntry.Notes.last().Pitch;
  250. const clef: ClefInstruction = (note as VexFlowGraphicalNote).Clef();
  251. const vfpitch: [string, string, ClefInstruction] = VexFlowConverter.pitch(
  252. VexFlowConverter.restToNotePitch(previousNotePitch.getTransposedPitch(-2), clef.ClefType),
  253. false, clef, undefined);
  254. keys = [vfpitch[0]];
  255. }
  256. }
  257. }
  258. }
  259. }
  260. // TODO do collision checking, place rest e.g. either below staff (A3, for stem direction below voice) or above (C5)
  261. // if it is a full measure rest:
  262. // (a whole rest note signifies a whole measure duration, unless the time signature is longer than 4 quarter notes, e.g. 6/4 or 3/2.
  263. // Note: this should not apply to most pickup measures, e.g. with an 8th pickup measure in a 3/4 time signature)
  264. // const measureDuration: number = note.sourceNote.SourceMeasure.Duration.RealValue;
  265. const isWholeMeasureRest: boolean = baseNoteLength.RealValue === note.sourceNote.SourceMeasure.ActiveTimeSignature.RealValue;
  266. if (isWholeMeasureRest) {
  267. keys = ["d/5"];
  268. duration = "w";
  269. numDots = 0;
  270. // If it's a whole rest we want it smack in the middle. Apparently there is still an issue in vexflow:
  271. // https://github.com/0xfe/vexflow/issues/579 The author reports that he needs to add some negative x shift
  272. // if the measure has no modifiers.
  273. alignCenter = true;
  274. xShift = rules.WholeRestXShiftVexflow * unitInPixels; // TODO find way to make dependent on the modifiers
  275. // affects VexFlowStaffEntry.calculateXPosition()
  276. }
  277. if (note.sourceNote.ParentStaff.Voices.length > 1) {
  278. let visibleVoiceEntries: number = 0;
  279. //Find all visible voice entries (don't want invisible rests/notes causing visible shift)
  280. for (let idx: number = 0; idx < note.sourceNote.ParentStaffEntry.VoiceEntries.length ; idx++) {
  281. if (note.sourceNote.ParentStaffEntry.VoiceEntries[idx].Notes[0].PrintObject) {
  282. visibleVoiceEntries++;
  283. }
  284. }
  285. //If we have more than one visible voice entry, shift the rests so no collision occurs
  286. if (visibleVoiceEntries > 1) {
  287. switch (note.sourceNote.ParentVoiceEntry?.ParentVoice?.VoiceId) {
  288. case 1:
  289. keys = ["e/5"];
  290. break;
  291. case 2:
  292. keys = ["f/4"];
  293. break;
  294. default:
  295. break;
  296. }
  297. }
  298. }
  299. break;
  300. }
  301. if (note.sourceNote.Notehead) {
  302. if (note.sourceNote.Notehead.Shape === NoteHeadShape.SLASH) {
  303. slashNoteHead = true;
  304. // if we have slash heads and other heads in the voice entry, this will create the same head for all.
  305. // same problem with numDots. The slash case should be extremely rare though.
  306. }
  307. }
  308. const pitch: [string, string, ClefInstruction] = (note as VexFlowGraphicalNote).vfpitch;
  309. keys.push(pitch[0]);
  310. accidentals.push(pitch[1]);
  311. if (!vfClefType) {
  312. const vfClef: {type: string, annotation: string} = VexFlowConverter.Clef(pitch[2]);
  313. vfClefType = vfClef.type;
  314. }
  315. }
  316. for (let i: number = 0, len: number = numDots; i < len; ++i) {
  317. duration += "d";
  318. }
  319. if (slashNoteHead) {
  320. duration += "s"; // we have to specify a slash note head like this in Vexflow
  321. }
  322. if (isRest) {
  323. // "r" has to be put after the "d"s for rest notes.
  324. duration += "r";
  325. }
  326. let vfnote: Vex.Flow.StaveNote;
  327. const vfnoteStruct: any = {
  328. align_center: alignCenter,
  329. auto_stem: true,
  330. clef: vfClefType,
  331. duration: duration,
  332. keys: keys,
  333. slash: gve.parentVoiceEntry.GraceNoteSlash,
  334. };
  335. const firstNote: Note = gve.notes[0].sourceNote;
  336. if (firstNote.IsCueNote) {
  337. vfnoteStruct.glyph_font_scale = Vex.Flow.DEFAULT_NOTATION_FONT_SCALE * Vex.Flow.GraceNote.SCALE;
  338. vfnoteStruct.stroke_px = Vex.Flow.GraceNote.LEDGER_LINE_OFFSET;
  339. }
  340. if (gve.parentVoiceEntry.IsGrace || gve.notes[0].sourceNote.IsCueNote) {
  341. vfnote = new Vex.Flow.GraceNote(vfnoteStruct);
  342. } else {
  343. vfnote = new Vex.Flow.StaveNote(vfnoteStruct);
  344. }
  345. if (rules.LedgerLineWidth || rules.LedgerLineStrokeStyle) {
  346. // FIXME should probably use vfnote.setLedgerLineStyle. this doesn't seem to do anything.
  347. // however, this is also set in VexFlowVoiceEntry.color() anyways.
  348. if (!((vfnote as any).ledgerLineStyle)) {
  349. (vfnote as any).ledgerLineStyle = {};
  350. }
  351. if (rules.LedgerLineWidth) {
  352. (vfnote as any).ledgerLineStyle.lineWidth = rules.LedgerLineWidth;
  353. }
  354. if (rules.LedgerLineStrokeStyle) {
  355. (vfnote as any).ledgerLineStyle.strokeStyle = rules.LedgerLineStrokeStyle;
  356. }
  357. }
  358. if (rules.ColoringEnabled) {
  359. const defaultColorStem: string = rules.DefaultColorStem;
  360. let stemColor: string = gve.parentVoiceEntry.StemColor;
  361. if (!stemColor && defaultColorStem) {
  362. stemColor = defaultColorStem;
  363. }
  364. const stemStyle: Object = { fillStyle: stemColor, strokeStyle: stemColor };
  365. if (stemColor) {
  366. gve.parentVoiceEntry.StemColor = stemColor;
  367. vfnote.setStemStyle(stemStyle);
  368. if (vfnote.flag && rules.ColorFlags) {
  369. vfnote.setFlagStyle(stemStyle);
  370. }
  371. }
  372. }
  373. vfnote.x_shift = xShift;
  374. if (gve.parentVoiceEntry.IsGrace && gve.notes[0].sourceNote.NoteBeam) {
  375. // Vexflow seems to have issues with wanted stem direction for beamed grace notes,
  376. // when the stem is connected to a beamed main note (e.g. Haydn Concertante bar 57)
  377. gve.parentVoiceEntry.WantedStemDirection = gve.notes[0].sourceNote.NoteBeam.Notes[0].ParentVoiceEntry.WantedStemDirection;
  378. }
  379. if (gve.parentVoiceEntry) {
  380. const wantedStemDirection: StemDirectionType = gve.parentVoiceEntry.WantedStemDirection;
  381. switch (wantedStemDirection) {
  382. case(StemDirectionType.Up):
  383. vfnote.setStemDirection(Vex.Flow.Stem.UP);
  384. gve.parentVoiceEntry.StemDirection = StemDirectionType.Up;
  385. break;
  386. case (StemDirectionType.Down):
  387. vfnote.setStemDirection(Vex.Flow.Stem.DOWN);
  388. gve.parentVoiceEntry.StemDirection = StemDirectionType.Down;
  389. break;
  390. default:
  391. }
  392. }
  393. // add accidentals
  394. for (let i: number = 0, len: number = notes.length; i < len; i += 1) {
  395. (notes[i] as VexFlowGraphicalNote).setIndex(vfnote, i);
  396. if (accidentals[i]) {
  397. if (accidentals[i] === "++") { // triple sharp
  398. vfnote.addAccidental(i, new Vex.Flow.Accidental("##"));
  399. vfnote.addAccidental(i, new Vex.Flow.Accidental("#"));
  400. continue;
  401. } else if (accidentals[i] === "bbs") { // triple flat
  402. vfnote.addAccidental(i, new Vex.Flow.Accidental("bb"));
  403. vfnote.addAccidental(i, new Vex.Flow.Accidental("b"));
  404. continue;
  405. }
  406. vfnote.addAccidental(i, new Vex.Flow.Accidental(accidentals[i])); // normal accidental
  407. }
  408. // add Tremolo strokes (only single note tremolos for now, Vexflow doesn't have beams for two-note tremolos yet)
  409. const tremoloStrokes: number = notes[i].sourceNote.TremoloStrokes;
  410. if (tremoloStrokes > 0) {
  411. const tremolo: Vex.Flow.Tremolo = new Vex.Flow.Tremolo(tremoloStrokes);
  412. (tremolo as any).extra_stroke_scale = rules.TremoloStrokeScale;
  413. (tremolo as any).y_spacing_scale = rules.TremoloYSpacingScale;
  414. vfnote.addModifier(i, tremolo);
  415. }
  416. }
  417. // half note tremolo: set notehead to half note (Vexflow otherwise takes the notehead from duration) (Hack)
  418. if (firstNote.Length.RealValue === 0.25 && firstNote.Notehead && firstNote.Notehead.Filled === false) {
  419. const keyProps: Object[] = vfnote.getKeyProps();
  420. for (let i: number = 0; i < keyProps.length; i++) {
  421. (<any>keyProps[i]).code = "v81";
  422. }
  423. }
  424. for (let i: number = 0, len: number = numDots; i < len; ++i) {
  425. vfnote.addDotToAll();
  426. }
  427. return vfnote;
  428. }
  429. public static generateArticulations(vfnote: Vex.Flow.StemmableNote, articulations: Articulation[],
  430. rules: EngravingRules): void {
  431. if (!vfnote || vfnote.getAttribute("type") === "GhostNote") {
  432. return;
  433. }
  434. for (const articulation of articulations) {
  435. let vfArtPosition: number = Vex.Flow.Modifier.Position.ABOVE;
  436. if (vfnote.getStemDirection() === Vex.Flow.Stem.UP) {
  437. vfArtPosition = Vex.Flow.Modifier.Position.BELOW;
  438. }
  439. let vfArt: Vex.Flow.Articulation = undefined;
  440. const articulationEnum: ArticulationEnum = articulation.articulationEnum;
  441. if (rules.ArticulationPlacementFromXML) {
  442. if (articulation.placement === PlacementEnum.Above) {
  443. vfArtPosition = Vex.Flow.Modifier.Position.ABOVE;
  444. } else if (articulation.placement === PlacementEnum.Below) {
  445. vfArtPosition = Vex.Flow.Modifier.Position.BELOW;
  446. } // else if undefined: don't change
  447. }
  448. switch (articulationEnum) {
  449. case ArticulationEnum.accent: {
  450. vfArt = new Vex.Flow.Articulation("a>");
  451. break;
  452. }
  453. case ArticulationEnum.downbow: {
  454. vfArt = new Vex.Flow.Articulation("am");
  455. if (articulation.placement === undefined) { // downbow/upbow should be above by default
  456. vfArtPosition = Vex.Flow.Modifier.Position.ABOVE;
  457. }
  458. break;
  459. }
  460. case ArticulationEnum.fermata: {
  461. vfArt = new Vex.Flow.Articulation("a@a");
  462. vfArtPosition = Vex.Flow.Modifier.Position.ABOVE;
  463. break;
  464. }
  465. case ArticulationEnum.marcatodown: {
  466. vfArt = new Vex.Flow.Articulation("a|"); // Vexflow only knows marcato up, so we use a down stroke here.
  467. break;
  468. }
  469. case ArticulationEnum.marcatoup: {
  470. vfArt = new Vex.Flow.Articulation("a^");
  471. break;
  472. }
  473. case ArticulationEnum.invertedfermata: {
  474. vfArt = new Vex.Flow.Articulation("a@u");
  475. vfArtPosition = Vex.Flow.Modifier.Position.BELOW;
  476. break;
  477. }
  478. case ArticulationEnum.lefthandpizzicato: {
  479. vfArt = new Vex.Flow.Articulation("a+");
  480. break;
  481. }
  482. case ArticulationEnum.naturalharmonic: {
  483. vfArt = new Vex.Flow.Articulation("ah");
  484. break;
  485. }
  486. case ArticulationEnum.snappizzicato: {
  487. vfArt = new Vex.Flow.Articulation("ao");
  488. break;
  489. }
  490. case ArticulationEnum.staccatissimo: {
  491. vfArt = new Vex.Flow.Articulation("av");
  492. break;
  493. }
  494. case ArticulationEnum.staccato: {
  495. vfArt = new Vex.Flow.Articulation("a.");
  496. break;
  497. }
  498. case ArticulationEnum.tenuto: {
  499. vfArt = new Vex.Flow.Articulation("a-");
  500. break;
  501. }
  502. case ArticulationEnum.upbow: {
  503. vfArt = new Vex.Flow.Articulation("a|");
  504. if (articulation.placement === undefined) { // downbow/upbow should be above by default
  505. vfArtPosition = Vex.Flow.Modifier.Position.ABOVE;
  506. }
  507. break;
  508. }
  509. case ArticulationEnum.strongaccent: {
  510. vfArt = new Vex.Flow.Articulation("a^");
  511. break;
  512. }
  513. default: {
  514. break;
  515. }
  516. }
  517. if (vfArt) {
  518. vfArt.setPosition(vfArtPosition);
  519. (vfnote as StaveNote).addModifier(0, vfArt);
  520. }
  521. }
  522. }
  523. public static generateOrnaments(vfnote: Vex.Flow.StemmableNote, oContainer: OrnamentContainer): void {
  524. let vfPosition: number = Vex.Flow.Modifier.Position.ABOVE;
  525. if (oContainer.placement === PlacementEnum.Below) {
  526. vfPosition = Vex.Flow.Modifier.Position.BELOW;
  527. }
  528. let vfOrna: Vex.Flow.Ornament = undefined;
  529. switch (oContainer.GetOrnament) {
  530. case OrnamentEnum.DelayedInvertedTurn: {
  531. vfOrna = new Vex.Flow.Ornament("turn_inverted");
  532. vfOrna.setDelayed(true);
  533. break;
  534. }
  535. case OrnamentEnum.DelayedTurn: {
  536. vfOrna = new Vex.Flow.Ornament("turn");
  537. vfOrna.setDelayed(true);
  538. break;
  539. }
  540. case OrnamentEnum.InvertedMordent: {
  541. vfOrna = new Vex.Flow.Ornament("mordent"); // Vexflow uses baroque, not MusicXML definition
  542. vfOrna.setDelayed(false);
  543. break;
  544. }
  545. case OrnamentEnum.InvertedTurn: {
  546. vfOrna = new Vex.Flow.Ornament("turn_inverted");
  547. vfOrna.setDelayed(false);
  548. break;
  549. }
  550. case OrnamentEnum.Mordent: {
  551. vfOrna = new Vex.Flow.Ornament("mordent_inverted");
  552. vfOrna.setDelayed(false);
  553. break;
  554. }
  555. case OrnamentEnum.Trill: {
  556. vfOrna = new Vex.Flow.Ornament("tr");
  557. vfOrna.setDelayed(false);
  558. break;
  559. }
  560. case OrnamentEnum.Turn: {
  561. vfOrna = new Vex.Flow.Ornament("turn");
  562. vfOrna.setDelayed(false);
  563. break;
  564. }
  565. default: {
  566. log.warn("unhandled OrnamentEnum type: " + oContainer.GetOrnament);
  567. return;
  568. }
  569. }
  570. if (vfOrna) {
  571. if (oContainer.AccidentalBelow !== AccidentalEnum.NONE) {
  572. vfOrna.setLowerAccidental(Pitch.accidentalVexflow(oContainer.AccidentalBelow));
  573. }
  574. if (oContainer.AccidentalAbove !== AccidentalEnum.NONE) {
  575. vfOrna.setUpperAccidental(Pitch.accidentalVexflow(oContainer.AccidentalAbove));
  576. }
  577. vfOrna.setPosition(vfPosition); // Vexflow draws it above right now in any case, never below
  578. (vfnote as StaveNote).addModifier(0, vfOrna);
  579. }
  580. }
  581. public static StrokeTypeFromArpeggioType(arpeggioType: ArpeggioType): Vex.Flow.Stroke.Type {
  582. switch (arpeggioType) {
  583. case ArpeggioType.ARPEGGIO_DIRECTIONLESS:
  584. return Vex.Flow.Stroke.Type.ARPEGGIO_DIRECTIONLESS;
  585. case ArpeggioType.BRUSH_DOWN:
  586. return Vex.Flow.Stroke.Type.BRUSH_UP; // TODO somehow up and down are mixed up in Vexflow right now
  587. case ArpeggioType.BRUSH_UP:
  588. return Vex.Flow.Stroke.Type.BRUSH_DOWN; // TODO somehow up and down are mixed up in Vexflow right now
  589. case ArpeggioType.RASQUEDO_DOWN:
  590. return Vex.Flow.Stroke.Type.RASQUEDO_UP;
  591. case ArpeggioType.RASQUEDO_UP:
  592. return Vex.Flow.Stroke.Type.RASQUEDO_DOWN;
  593. case ArpeggioType.ROLL_DOWN:
  594. return Vex.Flow.Stroke.Type.ROLL_UP; // TODO somehow up and down are mixed up in Vexflow right now
  595. case ArpeggioType.ROLL_UP:
  596. return Vex.Flow.Stroke.Type.ROLL_DOWN; // TODO somehow up and down are mixed up in Vexflow right now
  597. default:
  598. return Vex.Flow.Stroke.Type.ARPEGGIO_DIRECTIONLESS;
  599. }
  600. }
  601. /**
  602. * Convert a set of GraphicalNotes to a VexFlow StaveNote
  603. * @param notes form a chord on the staff
  604. * @returns {Vex.Flow.StaveNote}
  605. */
  606. public static CreateTabNote(gve: GraphicalVoiceEntry): Vex.Flow.TabNote {
  607. const tabPositions: {str: number, fret: number}[] = [];
  608. const notes: GraphicalNote[] = gve.notes.reverse();
  609. const tabPhrases: { type: number, text: string, width: number }[] = [];
  610. const frac: Fraction = gve.notes[0].graphicalNoteLength;
  611. const isTuplet: boolean = gve.notes[0].sourceNote.NoteTuplet !== undefined;
  612. let duration: string = VexFlowConverter.duration(frac, isTuplet);
  613. let numDots: number = 0;
  614. let tabVibrato: boolean = false;
  615. for (const note of gve.notes) {
  616. const tabNote: TabNote = note.sourceNote as TabNote;
  617. const tabPosition: {str: number, fret: number} = {str: tabNote.StringNumberTab, fret: tabNote.FretNumber};
  618. tabPositions.push(tabPosition);
  619. if (tabNote.BendArray) {
  620. tabNote.BendArray.forEach( function( bend: {bendalter: number, direction: string} ): void {
  621. let phraseText: string;
  622. const phraseStep: number = bend.bendalter - tabPosition.fret;
  623. if (phraseStep > 1) {
  624. phraseText = "Full";
  625. } else if (phraseStep === 1) {
  626. phraseText = "1/2";
  627. } else {
  628. phraseText = "1/4";
  629. }
  630. if (bend.direction === "up") {
  631. tabPhrases.push({type: Vex.Flow.Bend.UP, text: phraseText, width: 10});
  632. } else {
  633. tabPhrases.push({type: Vex.Flow.Bend.DOWN, text: phraseText, width: 10});
  634. }
  635. });
  636. }
  637. if (tabNote.VibratoStroke) {
  638. tabVibrato = true;
  639. }
  640. if (numDots < note.numberOfDots) {
  641. numDots = note.numberOfDots;
  642. }
  643. }
  644. for (let i: number = 0, len: number = numDots; i < len; ++i) {
  645. duration += "d";
  646. }
  647. const vfnote: Vex.Flow.TabNote = new Vex.Flow.TabNote({
  648. duration: duration,
  649. positions: tabPositions,
  650. });
  651. for (let i: number = 0, len: number = notes.length; i < len; i += 1) {
  652. (notes[i] as VexFlowGraphicalNote).setIndex(vfnote, i);
  653. }
  654. tabPhrases.forEach(function(phrase: { type: number, text: string, width: number }): void {
  655. if (phrase.type === Vex.Flow.Bend.UP) {
  656. vfnote.addModifier (new Vex.Flow.Bend(phrase.text, false));
  657. } else {
  658. vfnote.addModifier (new Vex.Flow.Bend(phrase.text, true));
  659. }
  660. });
  661. if (tabVibrato) {
  662. vfnote.addModifier(new Vex.Flow.Vibrato());
  663. }
  664. return vfnote;
  665. }
  666. /**
  667. * Convert a ClefInstruction to a string represention of a clef type in VexFlow.
  668. *
  669. * @param clef The OSMD object to be converted representing the clef
  670. * @param size The VexFlow size to be used. Can be `default` or `small`.
  671. * As soon as #118 is done, this parameter will be dispensable.
  672. * @returns A string representation of a VexFlow clef
  673. * @see https://github.com/0xfe/vexflow/blob/master/src/clef.js
  674. * @see https://github.com/0xfe/vexflow/blob/master/tests/clef_tests.js
  675. */
  676. public static Clef(clef: ClefInstruction, size: string = "default"): { type: string, size: string, annotation: string } {
  677. let type: string;
  678. let annotation: string;
  679. // Make sure size is either "default" or "small"
  680. if (size !== "default" && size !== "small") {
  681. log.warn(`Invalid VexFlow clef size "${size}" specified. Using "default".`);
  682. size = "default";
  683. }
  684. /*
  685. * For all of the following conversions, OSMD uses line numbers 1-5 starting from
  686. * the bottom, while VexFlow uses 0-4 starting from the top.
  687. */
  688. switch (clef.ClefType) {
  689. // G Clef
  690. case ClefEnum.G:
  691. switch (clef.Line) {
  692. case 1:
  693. type = "french"; // VexFlow line 4
  694. break;
  695. case 2:
  696. type = "treble"; // VexFlow line 3
  697. break;
  698. default:
  699. type = "treble";
  700. log.error(`Clef ${ClefEnum[clef.ClefType]} on line ${clef.Line} not supported by VexFlow. Using default value "${type}".`);
  701. }
  702. break;
  703. // F Clef
  704. case ClefEnum.F:
  705. switch (clef.Line) {
  706. case 4:
  707. type = "bass"; // VexFlow line 1
  708. break;
  709. case 3:
  710. type = "baritone-f"; // VexFlow line 2
  711. break;
  712. case 5:
  713. type = "subbass"; // VexFlow line 0
  714. break;
  715. default:
  716. type = "bass";
  717. log.error(`Clef ${ClefEnum[clef.ClefType]} on line ${clef.Line} not supported by VexFlow. Using default value "${type}".`);
  718. }
  719. break;
  720. // C Clef
  721. case ClefEnum.C:
  722. switch (clef.Line) {
  723. case 3:
  724. type = "alto"; // VexFlow line 2
  725. break;
  726. case 4:
  727. type = "tenor"; // VexFlow line 1
  728. break;
  729. case 1:
  730. type = "soprano"; // VexFlow line 4
  731. break;
  732. case 2:
  733. type = "mezzo-soprano"; // VexFlow line 3
  734. break;
  735. default:
  736. type = "alto";
  737. log.error(`Clef ${ClefEnum[clef.ClefType]} on line ${clef.Line} not supported by VexFlow. Using default value "${type}".`);
  738. }
  739. break;
  740. // Percussion Clef
  741. case ClefEnum.percussion:
  742. type = "percussion";
  743. break;
  744. // TAB Clef
  745. case ClefEnum.TAB:
  746. // only used currently for creating the notes in the normal stave: There we need a normal treble clef
  747. type = "treble";
  748. break;
  749. default:
  750. log.info("bad clef type: " + clef.ClefType);
  751. type = "treble";
  752. }
  753. // annotations in vexflow don't allow bass and 8va. No matter the offset :(
  754. if (clef.OctaveOffset === 1 && type !== "bass" ) {
  755. annotation = "8va";
  756. } else if (clef.OctaveOffset === -1) {
  757. annotation = "8vb";
  758. }
  759. return { type, size, annotation };
  760. }
  761. /**
  762. * Convert a RhythmInstruction to a VexFlow TimeSignature object
  763. * @param rhythm
  764. * @returns {Vex.Flow.TimeSignature}
  765. * @constructor
  766. */
  767. public static TimeSignature(rhythm: RhythmInstruction): Vex.Flow.TimeSignature {
  768. let timeSpec: string;
  769. switch (rhythm.SymbolEnum) {
  770. case RhythmSymbolEnum.NONE:
  771. timeSpec = rhythm.Rhythm.Numerator + "/" + rhythm.Rhythm.Denominator;
  772. break;
  773. case RhythmSymbolEnum.COMMON:
  774. timeSpec = "C";
  775. break;
  776. case RhythmSymbolEnum.CUT:
  777. timeSpec = "C|";
  778. break;
  779. default:
  780. }
  781. return new Vex.Flow.TimeSignature(timeSpec);
  782. }
  783. /**
  784. * Convert a KeyInstruction to a string representing in VexFlow a key
  785. * @param key
  786. * @returns {string}
  787. */
  788. public static keySignature(key: KeyInstruction): string {
  789. if (!key) {
  790. return undefined;
  791. }
  792. let ret: string;
  793. switch (key.Mode) {
  794. case KeyEnum.minor:
  795. ret = VexFlowConverter.minorMap[key.Key] + "m";
  796. break;
  797. case KeyEnum.major:
  798. ret = VexFlowConverter.majorMap[key.Key];
  799. break;
  800. // some XMLs don't have the mode set despite having a key signature.
  801. case KeyEnum.none:
  802. ret = VexFlowConverter.majorMap[key.Key];
  803. break;
  804. default:
  805. ret = "C";
  806. }
  807. return ret;
  808. }
  809. /**
  810. * Converts a lineType to a VexFlow StaveConnector type
  811. * @param lineType
  812. * @returns {any}
  813. */
  814. public static line(lineType: SystemLinesEnum, linePosition: SystemLinePosition): any {
  815. switch (lineType) {
  816. case SystemLinesEnum.SingleThin:
  817. if (linePosition === SystemLinePosition.MeasureBegin) {
  818. return Vex.Flow.StaveConnector.type.SINGLE;
  819. }
  820. return Vex.Flow.StaveConnector.type.SINGLE_RIGHT;
  821. case SystemLinesEnum.DoubleThin:
  822. return Vex.Flow.StaveConnector.type.DOUBLE;
  823. case SystemLinesEnum.ThinBold:
  824. return Vex.Flow.StaveConnector.type.BOLD_DOUBLE_RIGHT;
  825. case SystemLinesEnum.BoldThinDots:
  826. return Vex.Flow.StaveConnector.type.BOLD_DOUBLE_LEFT;
  827. case SystemLinesEnum.DotsThinBold:
  828. return Vex.Flow.StaveConnector.type.BOLD_DOUBLE_RIGHT;
  829. case SystemLinesEnum.DotsBoldBoldDots:
  830. return Vex.Flow.StaveConnector.type.BOLD_DOUBLE_RIGHT;
  831. case SystemLinesEnum.None:
  832. return Vex.Flow.StaveConnector.type.NONE;
  833. default:
  834. }
  835. }
  836. /**
  837. * Construct a string which can be used in a CSS font property
  838. * @param fontSize
  839. * @param fontStyle
  840. * @param font
  841. * @returns {string}
  842. */
  843. public static font(fontSize: number, fontStyle: FontStyles = FontStyles.Regular,
  844. font: Fonts = Fonts.TimesNewRoman, rules: EngravingRules, fontFamily: string = undefined): string {
  845. let style: string = "normal";
  846. let weight: string = "normal";
  847. let family: string = `'${rules.DefaultFontFamily}'`; // default "'Times New Roman'"
  848. switch (fontStyle) {
  849. case FontStyles.Bold:
  850. weight = "bold";
  851. break;
  852. case FontStyles.Italic:
  853. style = "italic";
  854. break;
  855. case FontStyles.BoldItalic:
  856. style = "italic";
  857. weight = "bold";
  858. break;
  859. case FontStyles.Underlined:
  860. // TODO
  861. break;
  862. default:
  863. break;
  864. }
  865. switch (font) { // currently not used
  866. case Fonts.Kokila:
  867. // TODO Not Supported
  868. break;
  869. default:
  870. }
  871. if (fontFamily && fontFamily !== "default") {
  872. family = `'${fontFamily}'`;
  873. }
  874. return style + " " + weight + " " + Math.floor(fontSize) + "px " + family;
  875. }
  876. /**
  877. * Converts the style into a string that VexFlow RenderContext can understand
  878. * as the weight of the font
  879. */
  880. public static fontStyle(style: FontStyles): string {
  881. switch (style) {
  882. case FontStyles.Bold:
  883. return "bold";
  884. case FontStyles.Italic:
  885. return "italic";
  886. case FontStyles.BoldItalic:
  887. return "italic bold";
  888. default:
  889. return "normal";
  890. }
  891. }
  892. /**
  893. * Convert OutlineAndFillStyle to CSS properties
  894. * @param styleId
  895. * @returns {string}
  896. */
  897. public static style(styleId: OutlineAndFillStyleEnum): string {
  898. const ret: string = OUTLINE_AND_FILL_STYLE_DICT.getValue(styleId);
  899. return ret;
  900. }
  901. }