VexFlowConverter.ts 30 KB

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