VexFlowConverter.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  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} from "../DrawingEnums";
  18. import {Logging} from "../../../Common/Logging";
  19. /**
  20. * Helper class, which contains static methods which actually convert
  21. * from OSMD objects to VexFlow objects.
  22. */
  23. export class VexFlowConverter {
  24. /**
  25. * Mapping from numbers of alterations on the key signature to major keys
  26. * @type {[alterationsNo: number]: string; }
  27. */
  28. private static majorMap: {[_: number]: string; } = {
  29. "-1": "F", "-2": "Bb", "-3": "Eb", "-4": "Ab", "-5": "Db", "-6": "Gb", "-7": "Cb", "-8": "Fb",
  30. "0": "C", "1": "G", "2": "D", "3": "A", "4": "E", "5": "B", "6": "F#", "7": "C#", "8": "G#"
  31. };
  32. /**
  33. * Mapping from numbers of alterations on the key signature to minor keys
  34. * @type {[alterationsNo: number]: string; }
  35. */
  36. private static minorMap: {[_: number]: string; } = {
  37. "-1": "D", "-2": "G", "-3": "C", "-4": "F", "-5": "Bb", "-6": "Eb", "-7": "Ab", "-8": "Db",
  38. "0": "A", "1": "E", "2": "B", "3": "F#", "4": "C#", "5": "G#", "6": "D#", "7": "A#", "8": "E#"
  39. };
  40. /**
  41. * Convert a fraction to a string which represents a duration in VexFlow
  42. * @param fraction a fraction representing the duration of a note
  43. * @returns {string}
  44. */
  45. public static duration(fraction: Fraction): string {
  46. let dur: number = fraction.RealValue;
  47. if (dur >= 1) {
  48. return "w";
  49. } else if (dur < 1 && dur >= 0.5) {
  50. return "h";
  51. } else if (dur < 0.5 && dur >= 0.25) {
  52. return "q";
  53. } else if (dur < 0.25 && dur >= 0.125) {
  54. return "8";
  55. } else if (dur < 0.125 && dur >= 0.0625) {
  56. return "16";
  57. } else if (dur < 0.0625 && dur >= 0.03125) {
  58. return "32";
  59. }
  60. return "128";
  61. }
  62. /**
  63. * Takes a Pitch and returns a string representing a VexFlow pitch,
  64. * which has the form "b/4", plus its alteration (accidental)
  65. * @param pitch
  66. * @returns {string[]}
  67. */
  68. public static pitch(pitch: Pitch, clef: ClefInstruction): [string, string, ClefInstruction] {
  69. let fund: string = NoteEnum[pitch.FundamentalNote].toLowerCase();
  70. // The octave seems to need a shift of three FIXME?
  71. let octave: number = pitch.Octave - clef.OctaveOffset + 3;
  72. let acc: string = VexFlowConverter.accidental(pitch.Accidental);
  73. return [fund + "n/" + octave, acc, clef];
  74. }
  75. /**
  76. * Converts AccidentalEnum to a string which represents an accidental in VexFlow
  77. * @param accidental
  78. * @returns {string}
  79. */
  80. public static accidental(accidental: AccidentalEnum): string {
  81. let acc: string;
  82. switch (accidental) {
  83. case AccidentalEnum.NONE:
  84. acc = "n";
  85. break;
  86. case AccidentalEnum.FLAT:
  87. acc = "b";
  88. break;
  89. case AccidentalEnum.SHARP:
  90. acc = "#";
  91. break;
  92. case AccidentalEnum.DOUBLESHARP:
  93. acc = "##";
  94. break;
  95. case AccidentalEnum.DOUBLEFLAT:
  96. acc = "bb";
  97. break;
  98. default:
  99. }
  100. return acc;
  101. }
  102. /**
  103. * Convert a set of GraphicalNotes to a VexFlow StaveNote
  104. * @param notes form a chord on the staff
  105. * @returns {Vex.Flow.StaveNote}
  106. */
  107. public static StaveNote(notes: GraphicalNote[]): Vex.Flow.StaveNote {
  108. let keys: string[] = [];
  109. let accidentals: string[] = [];
  110. let frac: Fraction = notes[0].graphicalNoteLength;
  111. let duration: string = VexFlowConverter.duration(frac);
  112. let vfClefType: string = undefined;
  113. let numDots: number = 0;
  114. for (let note of notes) {
  115. let res: [string, string, ClefInstruction] = (note as VexFlowGraphicalNote).vfpitch;
  116. if (res === undefined) {
  117. keys = ["b/4"];
  118. duration += "r";
  119. break;
  120. }
  121. keys.push(res[0]);
  122. accidentals.push(res[1]);
  123. if (!vfClefType) {
  124. let vfClef: {type: string, annotation: string} = VexFlowConverter.Clef(res[2]);
  125. vfClefType = vfClef.type;
  126. }
  127. if (numDots < note.numberOfDots) {
  128. numDots = note.numberOfDots;
  129. }
  130. }
  131. for (let i: number = 0, len: number = numDots; i < len; ++i) {
  132. duration += "d";
  133. }
  134. let vfnote: Vex.Flow.StaveNote = new Vex.Flow.StaveNote({
  135. auto_stem: true,
  136. clef: vfClefType,
  137. duration: duration,
  138. duration_override: {
  139. denominator: frac.Denominator,
  140. numerator: frac.Numerator,
  141. },
  142. keys: keys,
  143. });
  144. for (let i: number = 0, len: number = notes.length; i < len; i += 1) {
  145. (notes[i] as VexFlowGraphicalNote).setIndex(vfnote, i);
  146. if (accidentals[i]) {
  147. vfnote.addAccidental(i, new Vex.Flow.Accidental(accidentals[i]));
  148. }
  149. }
  150. for (let i: number = 0, len: number = numDots; i < len; ++i) {
  151. vfnote.addDotToAll();
  152. }
  153. return vfnote;
  154. }
  155. /**
  156. * Convert a ClefInstruction to a string represention of a clef type in VexFlow.
  157. *
  158. * @param clef The OSMD object to be converted representing the clef
  159. * @param size The VexFlow size to be used. Can be `default` or `small`. As soon as
  160. * #118 is done, this parameter will be dispensable.
  161. * @returns A string representation of a VexFlow clef
  162. * @see https://github.com/0xfe/vexflow/blob/master/src/clef.js
  163. * @see https://github.com/0xfe/vexflow/blob/master/tests/clef_tests.js
  164. */
  165. public static Clef(clef: ClefInstruction, size: string = "default"): { type: string, size: string, annotation: string } {
  166. let type: string;
  167. let annotation: string;
  168. // Make sure size is either "default" or "small"
  169. if (size !== "default" && size !== "small") {
  170. Logging.warn(`Invalid VexFlow clef size "${size}" specified. Using "default".`);
  171. size = "default";
  172. }
  173. /*
  174. * For all of the following conversions, OSMD uses line numbers 1-5 starting from
  175. * the bottom, while VexFlow uses 0-4 starting from the top.
  176. */
  177. switch (clef.ClefType) {
  178. // G Clef
  179. case ClefEnum.G:
  180. switch (clef.Line) {
  181. case 1:
  182. type = "french"; // VexFlow line 4
  183. break;
  184. case 2:
  185. type = "treble"; // VexFlow line 3
  186. break;
  187. default:
  188. type = "treble";
  189. Logging.error(`Clef ${ClefEnum[clef.ClefType]} on line ${clef.Line} not supported by VexFlow. Using default value "${type}".`);
  190. }
  191. break;
  192. // F Clef
  193. case ClefEnum.F:
  194. switch (clef.Line) {
  195. case 4:
  196. type = "bass"; // VexFlow line 1
  197. break;
  198. case 3:
  199. type = "baritone-f"; // VexFlow line 2
  200. break;
  201. case 5:
  202. type = "subbass"; // VexFlow line 0
  203. break;
  204. default:
  205. type = "bass";
  206. Logging.error(`Clef ${ClefEnum[clef.ClefType]} on line ${clef.Line} not supported by VexFlow. Using default value "${type}".`);
  207. }
  208. break;
  209. // C Clef
  210. case ClefEnum.C:
  211. switch (clef.Line) {
  212. case 3:
  213. type = "alto"; // VexFlow line 2
  214. break;
  215. case 4:
  216. type = "tenor"; // VexFlow line 1
  217. break;
  218. case 1:
  219. type = "soprano"; // VexFlow line 4
  220. break;
  221. case 2:
  222. type = "mezzo-soprano"; // VexFlow line 3
  223. break;
  224. default:
  225. type = "alto";
  226. Logging.error(`Clef ${ClefEnum[clef.ClefType]} on line ${clef.Line} not supported by VexFlow. Using default value "${type}".`);
  227. }
  228. break;
  229. // Percussion Clef
  230. case ClefEnum.percussion:
  231. type = "percussion";
  232. break;
  233. // TAB Clef
  234. case ClefEnum.TAB:
  235. type = "tab";
  236. break;
  237. default:
  238. }
  239. switch (clef.OctaveOffset) {
  240. case 1:
  241. annotation = "8va";
  242. break;
  243. case -1:
  244. annotation = "8vb";
  245. break;
  246. default:
  247. }
  248. return { type, size, annotation };
  249. }
  250. /**
  251. * Convert a RhythmInstruction to a VexFlow TimeSignature object
  252. * @param rhythm
  253. * @returns {Vex.Flow.TimeSignature}
  254. * @constructor
  255. */
  256. public static TimeSignature(rhythm: RhythmInstruction): Vex.Flow.TimeSignature {
  257. let timeSpec: string;
  258. switch (rhythm.SymbolEnum) {
  259. case RhythmSymbolEnum.NONE:
  260. timeSpec = rhythm.Rhythm.Numerator + "/" + rhythm.Rhythm.Denominator;
  261. break;
  262. case RhythmSymbolEnum.COMMON:
  263. timeSpec = "C";
  264. break;
  265. case RhythmSymbolEnum.CUT:
  266. timeSpec = "C|";
  267. break;
  268. default:
  269. }
  270. return new Vex.Flow.TimeSignature(timeSpec);
  271. }
  272. /**
  273. * Convert a KeyInstruction to a string representing in VexFlow a key
  274. * @param key
  275. * @returns {string}
  276. */
  277. public static keySignature(key: KeyInstruction): string {
  278. if (key === undefined) {
  279. return undefined;
  280. }
  281. let ret: string;
  282. switch (key.Mode) {
  283. case KeyEnum.minor:
  284. ret = VexFlowConverter.minorMap[key.Key] + "m";
  285. break;
  286. case KeyEnum.major:
  287. ret = VexFlowConverter.majorMap[key.Key];
  288. break;
  289. case KeyEnum.none:
  290. default:
  291. ret = "C";
  292. }
  293. return ret;
  294. }
  295. /**
  296. * Converts a lineType to a VexFlow StaveConnector type
  297. * @param lineType
  298. * @returns {any}
  299. */
  300. public static line(lineType: SystemLinesEnum): any {
  301. // TODO Not all line types are correctly mapped!
  302. switch (lineType) {
  303. case SystemLinesEnum.SingleThin:
  304. return Vex.Flow.StaveConnector.type.SINGLE;
  305. case SystemLinesEnum.DoubleThin:
  306. return Vex.Flow.StaveConnector.type.DOUBLE;
  307. case SystemLinesEnum.ThinBold:
  308. return Vex.Flow.StaveConnector.type.SINGLE;
  309. case SystemLinesEnum.BoldThinDots:
  310. return Vex.Flow.StaveConnector.type.DOUBLE;
  311. case SystemLinesEnum.DotsThinBold:
  312. return Vex.Flow.StaveConnector.type.DOUBLE;
  313. case SystemLinesEnum.DotsBoldBoldDots:
  314. return Vex.Flow.StaveConnector.type.DOUBLE;
  315. case SystemLinesEnum.None:
  316. return Vex.Flow.StaveConnector.type.NONE;
  317. default:
  318. }
  319. }
  320. /**
  321. * Construct a string which can be used in a CSS font property
  322. * @param fontSize
  323. * @param fontStyle
  324. * @param font
  325. * @returns {string}
  326. */
  327. public static font(fontSize: number, fontStyle: FontStyles = FontStyles.Regular, font: Fonts = Fonts.TimesNewRoman): string {
  328. let style: string = "normal";
  329. let weight: string = "normal";
  330. let family: string = "'Times New Roman'";
  331. switch (fontStyle) {
  332. case FontStyles.Bold:
  333. weight = "bold";
  334. break;
  335. case FontStyles.Italic:
  336. style = "italic";
  337. break;
  338. case FontStyles.BoldItalic:
  339. style = "italic";
  340. weight = "bold";
  341. break;
  342. case FontStyles.Underlined:
  343. // TODO
  344. break;
  345. default:
  346. break;
  347. }
  348. switch (font) {
  349. case Fonts.Kokila:
  350. // TODO Not Supported
  351. break;
  352. default:
  353. }
  354. return style + " " + weight + " " + Math.floor(fontSize) + "px " + family;
  355. }
  356. /**
  357. * Convert OutlineAndFillStyle to CSS properties
  358. * @param styleId
  359. * @returns {string}
  360. */
  361. public static style(styleId: OutlineAndFillStyleEnum): string {
  362. // TODO To be implemented
  363. return "lightgreen";
  364. }
  365. }