VexFlowConverter.ts 21 KB

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