VexFlowConverter.ts 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529
  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 {Logging} from "../../../Common/Logging";
  19. import { ArticulationEnum } from "../../VoiceData/VoiceEntry";
  20. /**
  21. * Helper class, which contains static methods which actually convert
  22. * from OSMD objects to VexFlow objects.
  23. */
  24. export class VexFlowConverter {
  25. /**
  26. * Mapping from numbers of alterations on the key signature to major keys
  27. * @type {[alterationsNo: number]: string; }
  28. */
  29. private static majorMap: {[_: number]: string; } = {
  30. "-1": "F", "-2": "Bb", "-3": "Eb", "-4": "Ab", "-5": "Db", "-6": "Gb", "-7": "Cb", "-8": "Fb",
  31. "0": "C", "1": "G", "2": "D", "3": "A", "4": "E", "5": "B", "6": "F#", "7": "C#", "8": "G#"
  32. };
  33. /**
  34. * Mapping from numbers of alterations on the key signature to minor keys
  35. * @type {[alterationsNo: number]: string; }
  36. */
  37. private static minorMap: {[_: number]: string; } = {
  38. "-1": "D", "-2": "G", "-3": "C", "-4": "F", "-5": "Bb", "-6": "Eb", "-7": "Ab", "-8": "Db",
  39. "0": "A", "1": "E", "2": "B", "3": "F#", "4": "C#", "5": "G#", "6": "D#", "7": "A#", "8": "E#"
  40. };
  41. /**
  42. * Convert a fraction to a string which represents a duration in VexFlow
  43. * @param fraction a fraction representing the duration of a note
  44. * @returns {string}
  45. */
  46. public static duration(fraction: Fraction, isTuplet: boolean): string {
  47. const dur: number = fraction.RealValue;
  48. if (dur >= 1) {
  49. return "w";
  50. } else if (dur < 1 && dur >= 0.5) {
  51. // change to the next higher straight note to get the correct note display type
  52. if (isTuplet) {
  53. return "w";
  54. }
  55. return "h";
  56. } else if (dur < 0.5 && dur >= 0.25) {
  57. // change to the next higher straight note to get the correct note display type
  58. if (isTuplet && dur > 0.25) {
  59. return "h";
  60. }
  61. return "q";
  62. } else if (dur < 0.25 && dur >= 0.125) {
  63. // change to the next higher straight note to get the correct note display type
  64. if (isTuplet && dur > 0.125) {
  65. return "q";
  66. }
  67. return "8";
  68. } else if (dur < 0.125 && dur >= 0.0625) {
  69. // change to the next higher straight note to get the correct note display type
  70. if (isTuplet && dur > 0.0625) {
  71. return "8";
  72. }
  73. return "16";
  74. } else if (dur < 0.0625 && dur >= 0.03125) {
  75. // change to the next higher straight note to get the correct note display type
  76. if (isTuplet && dur > 0.03125) {
  77. return "16";
  78. }
  79. return "32";
  80. } else if (dur < 0.03125 && dur >= 0.015625) {
  81. // change to the next higher straight note to get the correct note display type
  82. if (isTuplet && dur > 0.015625) {
  83. return "32";
  84. }
  85. return "64";
  86. }
  87. if (isTuplet) {
  88. return "64";
  89. }
  90. return "128";
  91. }
  92. /**
  93. * Takes a Pitch and returns a string representing a VexFlow pitch,
  94. * which has the form "b/4", plus its alteration (accidental)
  95. * @param pitch
  96. * @returns {string[]}
  97. */
  98. public static pitch(pitch: Pitch, clef: ClefInstruction): [string, string, ClefInstruction] {
  99. const fund: string = NoteEnum[pitch.FundamentalNote].toLowerCase();
  100. // The octave seems to need a shift of three FIXME?
  101. const octave: number = pitch.Octave - clef.OctaveOffset + 3;
  102. const acc: string = VexFlowConverter.accidental(pitch.Accidental);
  103. return [fund + "n/" + octave, acc, clef];
  104. }
  105. /**
  106. * Converts AccidentalEnum to a string which represents an accidental in VexFlow
  107. * @param accidental
  108. * @returns {string}
  109. */
  110. public static accidental(accidental: AccidentalEnum): string {
  111. let acc: string;
  112. switch (accidental) {
  113. case AccidentalEnum.NONE:
  114. acc = "n";
  115. break;
  116. case AccidentalEnum.FLAT:
  117. acc = "b";
  118. break;
  119. case AccidentalEnum.SHARP:
  120. acc = "#";
  121. break;
  122. case AccidentalEnum.DOUBLESHARP:
  123. acc = "##";
  124. break;
  125. case AccidentalEnum.DOUBLEFLAT:
  126. acc = "bb";
  127. break;
  128. default:
  129. }
  130. return acc;
  131. }
  132. /**
  133. * Convert a set of GraphicalNotes to a VexFlow StaveNote
  134. * @param notes form a chord on the staff
  135. * @returns {Vex.Flow.StaveNote}
  136. */
  137. public static StaveNote(notes: GraphicalNote[]): Vex.Flow.StaveNote {
  138. let keys: string[] = [];
  139. const accidentals: string[] = [];
  140. const frac: Fraction = notes[0].graphicalNoteLength;
  141. const isTuplet: boolean = notes[0].sourceNote.NoteTuplet !== undefined;
  142. const articulations: ArticulationEnum[] = notes[0].sourceNote.ParentVoiceEntry.Articulations;
  143. let duration: string = VexFlowConverter.duration(frac, isTuplet);
  144. let vfClefType: string = undefined;
  145. let numDots: number = 0;
  146. for (const note of notes) {
  147. const pitch: [string, string, ClefInstruction] = (note as VexFlowGraphicalNote).vfpitch;
  148. if (pitch === undefined) { // if it is a rest:
  149. keys = ["b/4"];
  150. duration += "r";
  151. break;
  152. }
  153. keys.push(pitch[0]);
  154. accidentals.push(pitch[1]);
  155. if (!vfClefType) {
  156. const vfClef: {type: string, annotation: string} = VexFlowConverter.Clef(pitch[2]);
  157. vfClefType = vfClef.type;
  158. }
  159. if (numDots < note.numberOfDots) {
  160. numDots = note.numberOfDots;
  161. }
  162. }
  163. for (let i: number = 0, len: number = numDots; i < len; ++i) {
  164. duration += "d";
  165. }
  166. const vfnote: Vex.Flow.StaveNote = new Vex.Flow.StaveNote({
  167. auto_stem: true,
  168. clef: vfClefType,
  169. duration: duration,
  170. keys: keys,
  171. });
  172. const stemDirection: number = vfnote.getStemDirection();
  173. for (let i: number = 0, len: number = notes.length; i < len; i += 1) {
  174. (notes[i] as VexFlowGraphicalNote).setIndex(vfnote, i);
  175. if (accidentals[i]) {
  176. vfnote.addAccidental(i, new Vex.Flow.Accidental(accidentals[i]));
  177. }
  178. }
  179. for (let i: number = 0, len: number = numDots; i < len; ++i) {
  180. vfnote.addDotToAll();
  181. }
  182. // Articulations:
  183. let vfArtPosition: number = Vex.Flow.Modifier.Position.ABOVE;
  184. if (stemDirection === Vex.Flow.Stem.UP) {
  185. vfArtPosition = Vex.Flow.Modifier.Position.BELOW;
  186. }
  187. for (const articulation of articulations) {
  188. // tslint:disable-next-line:switch-default
  189. let vfArt: Vex.Flow.Articulation = undefined;
  190. switch (articulation) {
  191. case ArticulationEnum.accent: {
  192. vfArt = new Vex.Flow.Articulation("a>");
  193. break;
  194. }
  195. case ArticulationEnum.downbow: {
  196. vfArt = new Vex.Flow.Articulation("am");
  197. break;
  198. }
  199. case ArticulationEnum.fermata: {
  200. vfArt = new Vex.Flow.Articulation("a@a");
  201. vfArtPosition = Vex.Flow.Modifier.Position.ABOVE;
  202. break;
  203. }
  204. case ArticulationEnum.invertedfermata: {
  205. vfArt = new Vex.Flow.Articulation("a@u");
  206. vfArtPosition = Vex.Flow.Modifier.Position.BELOW;
  207. break;
  208. }
  209. case ArticulationEnum.lefthandpizzicato: {
  210. vfArt = new Vex.Flow.Articulation("a+");
  211. break;
  212. }
  213. case ArticulationEnum.snappizzicato: {
  214. vfArt = new Vex.Flow.Articulation("ao");
  215. break;
  216. }
  217. case ArticulationEnum.staccatissimo: {
  218. vfArt = new Vex.Flow.Articulation("av");
  219. break;
  220. }
  221. case ArticulationEnum.staccato: {
  222. vfArt = new Vex.Flow.Articulation("a.");
  223. break;
  224. }
  225. case ArticulationEnum.tenuto: {
  226. vfArt = new Vex.Flow.Articulation("a-");
  227. break;
  228. }
  229. case ArticulationEnum.upbow: {
  230. vfArt = new Vex.Flow.Articulation("a|");
  231. break;
  232. }
  233. case ArticulationEnum.strongaccent: {
  234. vfArt = new Vex.Flow.Articulation("a^");
  235. break;
  236. }
  237. default: {
  238. break;
  239. }
  240. }
  241. if (vfArt !== undefined) {
  242. vfArt.setPosition(vfArtPosition);
  243. vfnote.addModifier(0, vfArt);
  244. }
  245. }
  246. return vfnote;
  247. }
  248. /**
  249. * Convert a ClefInstruction to a string represention of a clef type in VexFlow.
  250. *
  251. * @param clef The OSMD object to be converted representing the clef
  252. * @param size The VexFlow size to be used. Can be `default` or `small`. As soon as
  253. * #118 is done, this parameter will be dispensable.
  254. * @returns A string representation of a VexFlow clef
  255. * @see https://github.com/0xfe/vexflow/blob/master/src/clef.js
  256. * @see https://github.com/0xfe/vexflow/blob/master/tests/clef_tests.js
  257. */
  258. public static Clef(clef: ClefInstruction, size: string = "default"): { type: string, size: string, annotation: string } {
  259. let type: string;
  260. let annotation: string;
  261. // Make sure size is either "default" or "small"
  262. if (size !== "default" && size !== "small") {
  263. Logging.warn(`Invalid VexFlow clef size "${size}" specified. Using "default".`);
  264. size = "default";
  265. }
  266. /*
  267. * For all of the following conversions, OSMD uses line numbers 1-5 starting from
  268. * the bottom, while VexFlow uses 0-4 starting from the top.
  269. */
  270. switch (clef.ClefType) {
  271. // G Clef
  272. case ClefEnum.G:
  273. switch (clef.Line) {
  274. case 1:
  275. type = "french"; // VexFlow line 4
  276. break;
  277. case 2:
  278. type = "treble"; // VexFlow line 3
  279. break;
  280. default:
  281. type = "treble";
  282. Logging.error(`Clef ${ClefEnum[clef.ClefType]} on line ${clef.Line} not supported by VexFlow. Using default value "${type}".`);
  283. }
  284. break;
  285. // F Clef
  286. case ClefEnum.F:
  287. switch (clef.Line) {
  288. case 4:
  289. type = "bass"; // VexFlow line 1
  290. break;
  291. case 3:
  292. type = "baritone-f"; // VexFlow line 2
  293. break;
  294. case 5:
  295. type = "subbass"; // VexFlow line 0
  296. break;
  297. default:
  298. type = "bass";
  299. Logging.error(`Clef ${ClefEnum[clef.ClefType]} on line ${clef.Line} not supported by VexFlow. Using default value "${type}".`);
  300. }
  301. break;
  302. // C Clef
  303. case ClefEnum.C:
  304. switch (clef.Line) {
  305. case 3:
  306. type = "alto"; // VexFlow line 2
  307. break;
  308. case 4:
  309. type = "tenor"; // VexFlow line 1
  310. break;
  311. case 1:
  312. type = "soprano"; // VexFlow line 4
  313. break;
  314. case 2:
  315. type = "mezzo-soprano"; // VexFlow line 3
  316. break;
  317. default:
  318. type = "alto";
  319. Logging.error(`Clef ${ClefEnum[clef.ClefType]} on line ${clef.Line} not supported by VexFlow. Using default value "${type}".`);
  320. }
  321. break;
  322. // Percussion Clef
  323. case ClefEnum.percussion:
  324. type = "percussion";
  325. break;
  326. // TAB Clef
  327. case ClefEnum.TAB:
  328. type = "tab";
  329. break;
  330. default:
  331. }
  332. switch (clef.OctaveOffset) {
  333. case 1:
  334. annotation = "8va";
  335. break;
  336. case -1:
  337. annotation = "8vb";
  338. break;
  339. default:
  340. }
  341. return { type, size, annotation };
  342. }
  343. /**
  344. * Convert a RhythmInstruction to a VexFlow TimeSignature object
  345. * @param rhythm
  346. * @returns {Vex.Flow.TimeSignature}
  347. * @constructor
  348. */
  349. public static TimeSignature(rhythm: RhythmInstruction): Vex.Flow.TimeSignature {
  350. let timeSpec: string;
  351. switch (rhythm.SymbolEnum) {
  352. case RhythmSymbolEnum.NONE:
  353. timeSpec = rhythm.Rhythm.Numerator + "/" + rhythm.Rhythm.Denominator;
  354. break;
  355. case RhythmSymbolEnum.COMMON:
  356. timeSpec = "C";
  357. break;
  358. case RhythmSymbolEnum.CUT:
  359. timeSpec = "C|";
  360. break;
  361. default:
  362. }
  363. return new Vex.Flow.TimeSignature(timeSpec);
  364. }
  365. /**
  366. * Convert a KeyInstruction to a string representing in VexFlow a key
  367. * @param key
  368. * @returns {string}
  369. */
  370. public static keySignature(key: KeyInstruction): string {
  371. if (key === undefined) {
  372. return undefined;
  373. }
  374. let ret: string;
  375. switch (key.Mode) {
  376. case KeyEnum.minor:
  377. ret = VexFlowConverter.minorMap[key.Key] + "m";
  378. break;
  379. case KeyEnum.major:
  380. ret = VexFlowConverter.majorMap[key.Key];
  381. break;
  382. case KeyEnum.none:
  383. default:
  384. ret = "C";
  385. }
  386. return ret;
  387. }
  388. /**
  389. * Converts a lineType to a VexFlow StaveConnector type
  390. * @param lineType
  391. * @returns {any}
  392. */
  393. public static line(lineType: SystemLinesEnum): any {
  394. switch (lineType) {
  395. case SystemLinesEnum.SingleThin:
  396. return Vex.Flow.StaveConnector.type.SINGLE;
  397. case SystemLinesEnum.DoubleThin:
  398. return Vex.Flow.StaveConnector.type.DOUBLE;
  399. case SystemLinesEnum.ThinBold:
  400. return Vex.Flow.StaveConnector.type.BOLD_DOUBLE_RIGHT;
  401. case SystemLinesEnum.BoldThinDots:
  402. return Vex.Flow.StaveConnector.type.BOLD_DOUBLE_LEFT;
  403. case SystemLinesEnum.DotsThinBold:
  404. return Vex.Flow.StaveConnector.type.BOLD_DOUBLE_RIGHT;
  405. case SystemLinesEnum.DotsBoldBoldDots:
  406. return Vex.Flow.StaveConnector.type.BOLD_DOUBLE_RIGHT;
  407. case SystemLinesEnum.None:
  408. return Vex.Flow.StaveConnector.type.NONE;
  409. default:
  410. }
  411. }
  412. /**
  413. * Construct a string which can be used in a CSS font property
  414. * @param fontSize
  415. * @param fontStyle
  416. * @param font
  417. * @returns {string}
  418. */
  419. public static font(fontSize: number, fontStyle: FontStyles = FontStyles.Regular, font: Fonts = Fonts.TimesNewRoman): string {
  420. let style: string = "normal";
  421. let weight: string = "normal";
  422. const family: string = "'Times New Roman'";
  423. switch (fontStyle) {
  424. case FontStyles.Bold:
  425. weight = "bold";
  426. break;
  427. case FontStyles.Italic:
  428. style = "italic";
  429. break;
  430. case FontStyles.BoldItalic:
  431. style = "italic";
  432. weight = "bold";
  433. break;
  434. case FontStyles.Underlined:
  435. // TODO
  436. break;
  437. default:
  438. break;
  439. }
  440. switch (font) {
  441. case Fonts.Kokila:
  442. // TODO Not Supported
  443. break;
  444. default:
  445. }
  446. return style + " " + weight + " " + Math.floor(fontSize) + "px " + family;
  447. }
  448. /**
  449. * Converts the style into a string that VexFlow RenderContext can understand
  450. * as the weight of the font
  451. */
  452. public static fontStyle(style: FontStyles): string {
  453. switch (style) {
  454. case FontStyles.Bold:
  455. return "bold";
  456. case FontStyles.Italic:
  457. return "italic";
  458. case FontStyles.BoldItalic:
  459. return "italic bold";
  460. default:
  461. return "normal";
  462. }
  463. }
  464. /**
  465. * Convert OutlineAndFillStyle to CSS properties
  466. * @param styleId
  467. * @returns {string}
  468. */
  469. public static style(styleId: OutlineAndFillStyleEnum): string {
  470. const ret: string = OUTLINE_AND_FILL_STYLE_DICT.getValue(styleId);
  471. return ret;
  472. }
  473. }
  474. export enum VexFlowRepetitionType {
  475. NONE = 1, // no coda or segno
  476. CODA_LEFT = 2, // coda at beginning of stave
  477. CODA_RIGHT = 3, // coda at end of stave
  478. SEGNO_LEFT = 4, // segno at beginning of stave
  479. SEGNO_RIGHT = 5, // segno at end of stave
  480. DC = 6, // D.C. at end of stave
  481. DC_AL_CODA = 7, // D.C. al coda at end of stave
  482. DC_AL_FINE = 8, // D.C. al Fine end of stave
  483. DS = 9, // D.S. at end of stave
  484. DS_AL_CODA = 10, // D.S. al coda at end of stave
  485. DS_AL_FINE = 11, // D.S. al Fine at end of stave
  486. FINE = 12, // Fine at end of stave
  487. }
  488. export enum VexFlowBarlineType {
  489. SINGLE = 1,
  490. DOUBLE = 2,
  491. END = 3,
  492. REPEAT_BEGIN = 4,
  493. REPEAT_END = 5,
  494. REPEAT_BOTH = 6,
  495. NONE = 7,
  496. }