VexFlowConverter.ts 35 KB

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