VexFlowConverter.ts 49 KB

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