VexFlowConverter.ts 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542
  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. /**
  134. * Convert a set of GraphicalNotes to a VexFlow StaveNote
  135. * @param notes form a chord on the staff
  136. * @returns {Vex.Flow.StaveNote}
  137. */
  138. public static StaveNote(notes: GraphicalNote[]): Vex.Flow.StaveNote {
  139. let keys: string[] = [];
  140. const accidentals: string[] = [];
  141. const frac: Fraction = notes[0].graphicalNoteLength;
  142. const isTuplet: boolean = notes[0].sourceNote.NoteTuplet !== undefined;
  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 wantedStemDirection: StemDirectionType = notes[0].sourceNote.ParentVoiceEntry.StemDirection;
  173. switch (wantedStemDirection) {
  174. case(StemDirectionType.Up):
  175. vfnote.setStemDirection(Vex.Flow.Stem.UP);
  176. break;
  177. case (StemDirectionType.Down):
  178. vfnote.setStemDirection(Vex.Flow.Stem.DOWN);
  179. break;
  180. default:
  181. break;
  182. }
  183. for (let i: number = 0, len: number = notes.length; i < len; i += 1) {
  184. (notes[i] as VexFlowGraphicalNote).setIndex(vfnote, i);
  185. if (accidentals[i]) {
  186. vfnote.addAccidental(i, new Vex.Flow.Accidental(accidentals[i]));
  187. }
  188. }
  189. for (let i: number = 0, len: number = numDots; i < len; ++i) {
  190. vfnote.addDotToAll();
  191. }
  192. return vfnote;
  193. }
  194. public static generateArticulations(vfnote: Vex.Flow.StaveNote, articulations: ArticulationEnum[]): void {
  195. // Articulations:
  196. let vfArtPosition: number = Vex.Flow.Modifier.Position.ABOVE;
  197. if (vfnote.getStemDirection() === Vex.Flow.Stem.UP) {
  198. vfArtPosition = Vex.Flow.Modifier.Position.BELOW;
  199. }
  200. for (const articulation of articulations) {
  201. // tslint:disable-next-line:switch-default
  202. let vfArt: Vex.Flow.Articulation = undefined;
  203. switch (articulation) {
  204. case ArticulationEnum.accent: {
  205. vfArt = new Vex.Flow.Articulation("a>");
  206. break;
  207. }
  208. case ArticulationEnum.downbow: {
  209. vfArt = new Vex.Flow.Articulation("am");
  210. break;
  211. }
  212. case ArticulationEnum.fermata: {
  213. vfArt = new Vex.Flow.Articulation("a@a");
  214. vfArtPosition = Vex.Flow.Modifier.Position.ABOVE;
  215. break;
  216. }
  217. case ArticulationEnum.invertedfermata: {
  218. vfArt = new Vex.Flow.Articulation("a@u");
  219. vfArtPosition = Vex.Flow.Modifier.Position.BELOW;
  220. break;
  221. }
  222. case ArticulationEnum.lefthandpizzicato: {
  223. vfArt = new Vex.Flow.Articulation("a+");
  224. break;
  225. }
  226. case ArticulationEnum.snappizzicato: {
  227. vfArt = new Vex.Flow.Articulation("ao");
  228. break;
  229. }
  230. case ArticulationEnum.staccatissimo: {
  231. vfArt = new Vex.Flow.Articulation("av");
  232. break;
  233. }
  234. case ArticulationEnum.staccato: {
  235. vfArt = new Vex.Flow.Articulation("a.");
  236. break;
  237. }
  238. case ArticulationEnum.tenuto: {
  239. vfArt = new Vex.Flow.Articulation("a-");
  240. break;
  241. }
  242. case ArticulationEnum.upbow: {
  243. vfArt = new Vex.Flow.Articulation("a|");
  244. break;
  245. }
  246. case ArticulationEnum.strongaccent: {
  247. vfArt = new Vex.Flow.Articulation("a^");
  248. break;
  249. }
  250. default: {
  251. break;
  252. }
  253. }
  254. if (vfArt !== undefined) {
  255. vfArt.setPosition(vfArtPosition);
  256. vfnote.addModifier(0, vfArt);
  257. }
  258. }
  259. }
  260. /**
  261. * Convert a ClefInstruction to a string represention of a clef type in VexFlow.
  262. *
  263. * @param clef The OSMD object to be converted representing the clef
  264. * @param size The VexFlow size to be used. Can be `default` or `small`. As soon as
  265. * #118 is done, this parameter will be dispensable.
  266. * @returns A string representation of a VexFlow clef
  267. * @see https://github.com/0xfe/vexflow/blob/master/src/clef.js
  268. * @see https://github.com/0xfe/vexflow/blob/master/tests/clef_tests.js
  269. */
  270. public static Clef(clef: ClefInstruction, size: string = "default"): { type: string, size: string, annotation: string } {
  271. let type: string;
  272. let annotation: string;
  273. // Make sure size is either "default" or "small"
  274. if (size !== "default" && size !== "small") {
  275. Logging.warn(`Invalid VexFlow clef size "${size}" specified. Using "default".`);
  276. size = "default";
  277. }
  278. /*
  279. * For all of the following conversions, OSMD uses line numbers 1-5 starting from
  280. * the bottom, while VexFlow uses 0-4 starting from the top.
  281. */
  282. switch (clef.ClefType) {
  283. // G Clef
  284. case ClefEnum.G:
  285. switch (clef.Line) {
  286. case 1:
  287. type = "french"; // VexFlow line 4
  288. break;
  289. case 2:
  290. type = "treble"; // VexFlow line 3
  291. break;
  292. default:
  293. type = "treble";
  294. Logging.error(`Clef ${ClefEnum[clef.ClefType]} on line ${clef.Line} not supported by VexFlow. Using default value "${type}".`);
  295. }
  296. break;
  297. // F Clef
  298. case ClefEnum.F:
  299. switch (clef.Line) {
  300. case 4:
  301. type = "bass"; // VexFlow line 1
  302. break;
  303. case 3:
  304. type = "baritone-f"; // VexFlow line 2
  305. break;
  306. case 5:
  307. type = "subbass"; // VexFlow line 0
  308. break;
  309. default:
  310. type = "bass";
  311. Logging.error(`Clef ${ClefEnum[clef.ClefType]} on line ${clef.Line} not supported by VexFlow. Using default value "${type}".`);
  312. }
  313. break;
  314. // C Clef
  315. case ClefEnum.C:
  316. switch (clef.Line) {
  317. case 3:
  318. type = "alto"; // VexFlow line 2
  319. break;
  320. case 4:
  321. type = "tenor"; // VexFlow line 1
  322. break;
  323. case 1:
  324. type = "soprano"; // VexFlow line 4
  325. break;
  326. case 2:
  327. type = "mezzo-soprano"; // VexFlow line 3
  328. break;
  329. default:
  330. type = "alto";
  331. Logging.error(`Clef ${ClefEnum[clef.ClefType]} on line ${clef.Line} not supported by VexFlow. Using default value "${type}".`);
  332. }
  333. break;
  334. // Percussion Clef
  335. case ClefEnum.percussion:
  336. type = "percussion";
  337. break;
  338. // TAB Clef
  339. case ClefEnum.TAB:
  340. type = "tab";
  341. break;
  342. default:
  343. }
  344. // annotations in vexflow don't allow bass and 8va. No matter the offset :(
  345. if (clef.OctaveOffset === 1 && type !== "bass" ) {
  346. annotation = "8va";
  347. } else if (clef.OctaveOffset === -1) {
  348. annotation = "8vb";
  349. }
  350. return { type, size, annotation };
  351. }
  352. /**
  353. * Convert a RhythmInstruction to a VexFlow TimeSignature object
  354. * @param rhythm
  355. * @returns {Vex.Flow.TimeSignature}
  356. * @constructor
  357. */
  358. public static TimeSignature(rhythm: RhythmInstruction): Vex.Flow.TimeSignature {
  359. let timeSpec: string;
  360. switch (rhythm.SymbolEnum) {
  361. case RhythmSymbolEnum.NONE:
  362. timeSpec = rhythm.Rhythm.Numerator + "/" + rhythm.Rhythm.Denominator;
  363. break;
  364. case RhythmSymbolEnum.COMMON:
  365. timeSpec = "C";
  366. break;
  367. case RhythmSymbolEnum.CUT:
  368. timeSpec = "C|";
  369. break;
  370. default:
  371. }
  372. return new Vex.Flow.TimeSignature(timeSpec);
  373. }
  374. /**
  375. * Convert a KeyInstruction to a string representing in VexFlow a key
  376. * @param key
  377. * @returns {string}
  378. */
  379. public static keySignature(key: KeyInstruction): string {
  380. if (key === undefined) {
  381. return undefined;
  382. }
  383. let ret: string;
  384. switch (key.Mode) {
  385. case KeyEnum.minor:
  386. ret = VexFlowConverter.minorMap[key.Key] + "m";
  387. break;
  388. case KeyEnum.major:
  389. ret = VexFlowConverter.majorMap[key.Key];
  390. break;
  391. case KeyEnum.none:
  392. default:
  393. ret = "C";
  394. }
  395. return ret;
  396. }
  397. /**
  398. * Converts a lineType to a VexFlow StaveConnector type
  399. * @param lineType
  400. * @returns {any}
  401. */
  402. public static line(lineType: SystemLinesEnum, linePosition: SystemLinePosition): any {
  403. switch (lineType) {
  404. case SystemLinesEnum.SingleThin:
  405. if (linePosition === SystemLinePosition.MeasureBegin) {
  406. return Vex.Flow.StaveConnector.type.SINGLE;
  407. }
  408. return Vex.Flow.StaveConnector.type.SINGLE_RIGHT;
  409. case SystemLinesEnum.DoubleThin:
  410. return Vex.Flow.StaveConnector.type.DOUBLE;
  411. case SystemLinesEnum.ThinBold:
  412. return Vex.Flow.StaveConnector.type.BOLD_DOUBLE_RIGHT;
  413. case SystemLinesEnum.BoldThinDots:
  414. return Vex.Flow.StaveConnector.type.BOLD_DOUBLE_LEFT;
  415. case SystemLinesEnum.DotsThinBold:
  416. return Vex.Flow.StaveConnector.type.BOLD_DOUBLE_RIGHT;
  417. case SystemLinesEnum.DotsBoldBoldDots:
  418. return Vex.Flow.StaveConnector.type.BOLD_DOUBLE_RIGHT;
  419. case SystemLinesEnum.None:
  420. return Vex.Flow.StaveConnector.type.NONE;
  421. default:
  422. }
  423. }
  424. /**
  425. * Construct a string which can be used in a CSS font property
  426. * @param fontSize
  427. * @param fontStyle
  428. * @param font
  429. * @returns {string}
  430. */
  431. public static font(fontSize: number, fontStyle: FontStyles = FontStyles.Regular, font: Fonts = Fonts.TimesNewRoman): string {
  432. let style: string = "normal";
  433. let weight: string = "normal";
  434. const family: string = "'Times New Roman'";
  435. switch (fontStyle) {
  436. case FontStyles.Bold:
  437. weight = "bold";
  438. break;
  439. case FontStyles.Italic:
  440. style = "italic";
  441. break;
  442. case FontStyles.BoldItalic:
  443. style = "italic";
  444. weight = "bold";
  445. break;
  446. case FontStyles.Underlined:
  447. // TODO
  448. break;
  449. default:
  450. break;
  451. }
  452. switch (font) {
  453. case Fonts.Kokila:
  454. // TODO Not Supported
  455. break;
  456. default:
  457. }
  458. return style + " " + weight + " " + Math.floor(fontSize) + "px " + family;
  459. }
  460. /**
  461. * Converts the style into a string that VexFlow RenderContext can understand
  462. * as the weight of the font
  463. */
  464. public static fontStyle(style: FontStyles): string {
  465. switch (style) {
  466. case FontStyles.Bold:
  467. return "bold";
  468. case FontStyles.Italic:
  469. return "italic";
  470. case FontStyles.BoldItalic:
  471. return "italic bold";
  472. default:
  473. return "normal";
  474. }
  475. }
  476. /**
  477. * Convert OutlineAndFillStyle to CSS properties
  478. * @param styleId
  479. * @returns {string}
  480. */
  481. public static style(styleId: OutlineAndFillStyleEnum): string {
  482. const ret: string = OUTLINE_AND_FILL_STYLE_DICT.getValue(styleId);
  483. return ret;
  484. }
  485. }
  486. export enum VexFlowRepetitionType {
  487. NONE = 1, // no coda or segno
  488. CODA_LEFT = 2, // coda at beginning of stave
  489. CODA_RIGHT = 3, // coda at end of stave
  490. SEGNO_LEFT = 4, // segno at beginning of stave
  491. SEGNO_RIGHT = 5, // segno at end of stave
  492. DC = 6, // D.C. at end of stave
  493. DC_AL_CODA = 7, // D.C. al coda at end of stave
  494. DC_AL_FINE = 8, // D.C. al Fine end of stave
  495. DS = 9, // D.S. at end of stave
  496. DS_AL_CODA = 10, // D.S. al coda at end of stave
  497. DS_AL_FINE = 11, // D.S. al Fine at end of stave
  498. FINE = 12, // Fine at end of stave
  499. }
  500. export enum VexFlowBarlineType {
  501. SINGLE = 1,
  502. DOUBLE = 2,
  503. END = 3,
  504. REPEAT_BEGIN = 4,
  505. REPEAT_END = 5,
  506. REPEAT_BOTH = 6,
  507. NONE = 7,
  508. }