VexFlowConverter.ts 20 KB

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