VexFlowConverter.ts 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747
  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. import { OrnamentEnum, OrnamentContainer } from "../../VoiceData/OrnamentContainer";
  23. import { Notehead, NoteHeadShape } from "../../VoiceData/Notehead";
  24. import { unitInPixels } from "./VexFlowMusicSheetDrawer";
  25. import { EngravingRules } from "../EngravingRules";
  26. import { Note } from "../..";
  27. import StaveNote = Vex.Flow.StaveNote;
  28. import { TabNote } from "../../VoiceData/TabNote";
  29. /**
  30. * Helper class, which contains static methods which actually convert
  31. * from OSMD objects to VexFlow objects.
  32. */
  33. export class VexFlowConverter {
  34. /**
  35. * Mapping from numbers of alterations on the key signature to major keys
  36. * @type {[alterationsNo: number]: string; }
  37. */
  38. private static majorMap: {[_: number]: string; } = {
  39. "-1": "F", "-2": "Bb", "-3": "Eb", "-4": "Ab", "-5": "Db", "-6": "Gb", "-7": "Cb", "-8": "Fb",
  40. "0": "C", "1": "G", "2": "D", "3": "A", "4": "E", "5": "B", "6": "F#", "7": "C#", "8": "G#"
  41. };
  42. /**
  43. * Mapping from numbers of alterations on the key signature to minor keys
  44. * @type {[alterationsNo: number]: string; }
  45. */
  46. private static minorMap: {[_: number]: string; } = {
  47. "-1": "D", "-2": "G", "-3": "C", "-4": "F", "-5": "Bb", "-6": "Eb", "-7": "Ab", "-8": "Db",
  48. "0": "A", "1": "E", "2": "B", "3": "F#", "4": "C#", "5": "G#", "6": "D#", "7": "A#", "8": "E#"
  49. };
  50. /**
  51. * Convert a fraction to a string which represents a duration in VexFlow
  52. * @param fraction a fraction representing the duration of a note
  53. * @returns {string}
  54. */
  55. public static duration(fraction: Fraction, isTuplet: boolean): string {
  56. const dur: number = fraction.RealValue;
  57. if (dur >= 1) {
  58. return "w";
  59. } else if (dur < 1 && dur >= 0.5) {
  60. // change to the next higher straight note to get the correct note display type
  61. if (isTuplet) {
  62. return "w";
  63. }
  64. return "h";
  65. } else if (dur < 0.5 && dur >= 0.25) {
  66. // change to the next higher straight note to get the correct note display type
  67. if (isTuplet && dur > 0.25) {
  68. return "h";
  69. }
  70. return "q";
  71. } else if (dur < 0.25 && dur >= 0.125) {
  72. // change to the next higher straight note to get the correct note display type
  73. if (isTuplet && dur > 0.125) {
  74. return "q";
  75. }
  76. return "8";
  77. } else if (dur < 0.125 && dur >= 0.0625) {
  78. // change to the next higher straight note to get the correct note display type
  79. if (isTuplet && dur > 0.0625) {
  80. return "8";
  81. }
  82. return "16";
  83. } else if (dur < 0.0625 && dur >= 0.03125) {
  84. // change to the next higher straight note to get the correct note display type
  85. if (isTuplet && dur > 0.03125) {
  86. return "16";
  87. }
  88. return "32";
  89. } else if (dur < 0.03125 && dur >= 0.015625) {
  90. // change to the next higher straight note to get the correct note display type
  91. if (isTuplet && dur > 0.015625) {
  92. return "32";
  93. }
  94. return "64";
  95. }
  96. if (isTuplet) {
  97. return "64";
  98. }
  99. return "128";
  100. }
  101. /**
  102. * Takes a Pitch and returns a string representing a VexFlow pitch,
  103. * which has the form "b/4", plus its alteration (accidental)
  104. * @param pitch
  105. * @returns {string[]}
  106. */
  107. public static pitch(note: VexFlowGraphicalNote, pitch: Pitch): [string, string, ClefInstruction] {
  108. const fund: string = NoteEnum[pitch.FundamentalNote].toLowerCase();
  109. const acc: string = Pitch.accidentalVexflow(pitch.Accidental);
  110. // The octave seems to need a shift of three FIXME?
  111. const octave: number = pitch.Octave - note.Clef().OctaveOffset + 3;
  112. const notehead: Notehead = note.sourceNote.Notehead;
  113. let noteheadCode: string = "";
  114. if (notehead !== undefined) {
  115. noteheadCode = this.NoteHeadCode(notehead);
  116. }
  117. return [fund + "n/" + octave + noteheadCode, acc, note.Clef()];
  118. }
  119. /** returns the Vexflow code for a note head. Some are still unsupported, see Vexflow/tables.js */
  120. public static NoteHeadCode(notehead: Notehead): string {
  121. const codeStart: string = "/";
  122. const codeFilled: string = notehead.Filled ? "2" : "1"; // filled/unfilled notehead code in most vexflow glyphs
  123. switch (notehead.Shape) {
  124. case NoteHeadShape.NORMAL:
  125. return "";
  126. case NoteHeadShape.DIAMOND:
  127. return codeStart + "D" + codeFilled;
  128. case NoteHeadShape.TRIANGLE:
  129. return codeStart + "T" + codeFilled;
  130. case NoteHeadShape.X:
  131. return codeStart + "X" + codeFilled;
  132. case NoteHeadShape.CIRCLEX:
  133. return codeStart + "X3";
  134. case NoteHeadShape.RECTANGLE:
  135. return codeStart + "R" + codeFilled;
  136. case NoteHeadShape.SQUARE:
  137. return codeStart + "S" + codeFilled;
  138. case NoteHeadShape.SLASH:
  139. return ""; // slash is specified at end of duration string in Vexflow
  140. default:
  141. return "";
  142. }
  143. }
  144. public static GhostNote(frac: Fraction): Vex.Flow.GhostNote {
  145. return new Vex.Flow.GhostNote({
  146. duration: VexFlowConverter.duration(frac, false),
  147. });
  148. }
  149. /**
  150. * Convert a GraphicalVoiceEntry to a VexFlow StaveNote
  151. * @param gve the GraphicalVoiceEntry which can hold a note or a chord on the staff belonging to one voice
  152. * @returns {Vex.Flow.StaveNote}
  153. */
  154. public static StaveNote(gve: GraphicalVoiceEntry): Vex.Flow.StaveNote {
  155. // sort notes
  156. /* seems unnecessary for now
  157. if (gve.octaveShiftValue !== undefined && gve.octaveShiftValue !== OctaveEnum.NONE) {
  158. gve.sort(); // gves with accidentals in octave shift brackets can be unsorted
  159. } */
  160. // VexFlow needs the notes ordered vertically in the other direction:
  161. const notes: GraphicalNote[] = gve.notes.reverse();
  162. const baseNote: GraphicalNote = notes[0];
  163. let keys: string[] = [];
  164. const accidentals: string[] = [];
  165. const frac: Fraction = baseNote.graphicalNoteLength;
  166. const isTuplet: boolean = baseNote.sourceNote.NoteTuplet !== undefined;
  167. let duration: string = VexFlowConverter.duration(frac, isTuplet);
  168. if (baseNote.sourceNote.TypeLength !== undefined && baseNote.sourceNote.TypeLength !== frac) {
  169. duration = VexFlowConverter.duration(baseNote.sourceNote.TypeLength, isTuplet);
  170. }
  171. let vfClefType: string = undefined;
  172. let numDots: number = baseNote.numberOfDots;
  173. let alignCenter: boolean = false;
  174. let xShift: number = 0;
  175. let slashNoteHead: boolean = false;
  176. let isRest: boolean = false;
  177. for (const note of notes) {
  178. if (numDots < note.numberOfDots) {
  179. numDots = note.numberOfDots;
  180. }
  181. // if it is a rest:
  182. if (note.sourceNote.isRest()) {
  183. isRest = true;
  184. keys = ["b/4"];
  185. // TODO do collision checking, place rest e.g. either below staff (A3, for stem direction below voice) or above (C5)
  186. // if it is a full measure rest:
  187. if (note.parentVoiceEntry.parentStaffEntry.parentMeasure.parentSourceMeasure.Duration.RealValue <= frac.RealValue) {
  188. keys = ["d/5"];
  189. duration = "w";
  190. numDots = 0;
  191. // If it's a whole rest we want it smack in the middle. Apparently there is still an issue in vexflow:
  192. // https://github.com/0xfe/vexflow/issues/579 The author reports that he needs to add some negative x shift
  193. // if the measure has no modifiers.
  194. alignCenter = true;
  195. xShift = EngravingRules.Rules.WholeRestXShiftVexflow * unitInPixels; // TODO find way to make dependent on the modifiers
  196. // affects VexFlowStaffEntry.calculateXPosition()
  197. }
  198. break;
  199. }
  200. if (note.sourceNote.Notehead) {
  201. if (note.sourceNote.Notehead.Shape === NoteHeadShape.SLASH) {
  202. slashNoteHead = true;
  203. // if we have slash heads and other heads in the voice entry, this will create the same head for all.
  204. // same problem with numDots. The slash case should be extremely rare though.
  205. }
  206. }
  207. const pitch: [string, string, ClefInstruction] = (note as VexFlowGraphicalNote).vfpitch;
  208. keys.push(pitch[0]);
  209. accidentals.push(pitch[1]);
  210. if (!vfClefType) {
  211. const vfClef: {type: string, annotation: string} = VexFlowConverter.Clef(pitch[2]);
  212. vfClefType = vfClef.type;
  213. }
  214. }
  215. for (let i: number = 0, len: number = numDots; i < len; ++i) {
  216. duration += "d";
  217. }
  218. if (slashNoteHead) {
  219. duration += "s"; // we have to specify a slash note head like this in Vexflow
  220. }
  221. if (isRest) {
  222. // "r" has to be put after the "d"s for rest notes.
  223. duration += "r";
  224. }
  225. let vfnote: Vex.Flow.StaveNote;
  226. const vfnoteStruct: any = {
  227. align_center: alignCenter,
  228. auto_stem: true,
  229. clef: vfClefType,
  230. duration: duration,
  231. keys: keys,
  232. slash: gve.parentVoiceEntry.GraceNoteSlash,
  233. };
  234. const firstNote: Note = gve.notes[0].sourceNote;
  235. if (firstNote.IsCueNote) {
  236. vfnoteStruct.glyph_font_scale = Vex.Flow.DEFAULT_NOTATION_FONT_SCALE * Vex.Flow.GraceNote.SCALE;
  237. vfnoteStruct.stroke_px = Vex.Flow.GraceNote.LEDGER_LINE_OFFSET;
  238. }
  239. if (gve.parentVoiceEntry.IsGrace || gve.notes[0].sourceNote.IsCueNote) {
  240. vfnote = new Vex.Flow.GraceNote(vfnoteStruct);
  241. } else {
  242. vfnote = new Vex.Flow.StaveNote(vfnoteStruct);
  243. }
  244. if (EngravingRules.Rules.ColoringEnabled) {
  245. const defaultColorStem: string = EngravingRules.Rules.DefaultColorStem;
  246. let stemColor: string = gve.parentVoiceEntry.StemColor;
  247. if (!stemColor && defaultColorStem) {
  248. stemColor = defaultColorStem;
  249. }
  250. const stemStyle: Object = { fillStyle: stemColor, strokeStyle: stemColor };
  251. if (stemColor) {
  252. gve.parentVoiceEntry.StemColor = stemColor;
  253. vfnote.setStemStyle(stemStyle);
  254. if (vfnote.flag && EngravingRules.Rules.ColorFlags) {
  255. vfnote.setFlagStyle(stemStyle);
  256. }
  257. }
  258. }
  259. vfnote.x_shift = xShift;
  260. if (gve.parentVoiceEntry.IsGrace && gve.notes[0].sourceNote.NoteBeam) {
  261. // Vexflow seems to have issues with wanted stem direction for beamed grace notes,
  262. // when the stem is connected to a beamed main note (e.g. Haydn Concertante bar 57)
  263. gve.parentVoiceEntry.WantedStemDirection = gve.notes[0].sourceNote.NoteBeam.Notes[0].ParentVoiceEntry.WantedStemDirection;
  264. }
  265. if (gve.parentVoiceEntry !== undefined) {
  266. const wantedStemDirection: StemDirectionType = gve.parentVoiceEntry.WantedStemDirection;
  267. switch (wantedStemDirection) {
  268. case(StemDirectionType.Up):
  269. vfnote.setStemDirection(Vex.Flow.Stem.UP);
  270. break;
  271. case (StemDirectionType.Down):
  272. vfnote.setStemDirection(Vex.Flow.Stem.DOWN);
  273. break;
  274. default:
  275. }
  276. }
  277. // add accidentals
  278. for (let i: number = 0, len: number = notes.length; i < len; i += 1) {
  279. (notes[i] as VexFlowGraphicalNote).setIndex(vfnote, i);
  280. if (accidentals[i]) {
  281. if (accidentals[i] === "++") { // triple sharp
  282. vfnote.addAccidental(i, new Vex.Flow.Accidental("##"));
  283. vfnote.addAccidental(i, new Vex.Flow.Accidental("#"));
  284. continue;
  285. } else if (accidentals[i] === "bbs") { // triple flat
  286. vfnote.addAccidental(i, new Vex.Flow.Accidental("bb"));
  287. vfnote.addAccidental(i, new Vex.Flow.Accidental("b"));
  288. continue;
  289. }
  290. vfnote.addAccidental(i, new Vex.Flow.Accidental(accidentals[i])); // normal accidental
  291. }
  292. // add Tremolo strokes (only single note tremolos for now, Vexflow doesn't have beams for two-note tremolos yet)
  293. const tremoloStrokes: number = notes[i].sourceNote.TremoloStrokes;
  294. if (tremoloStrokes > 0) {
  295. vfnote.addModifier(i, new Vex.Flow.Tremolo(tremoloStrokes));
  296. }
  297. }
  298. // half note tremolo: set notehead to half note (Vexflow otherwise takes the notehead from duration) (Hack)
  299. if (firstNote.Length.RealValue === 0.25 && firstNote.Notehead && firstNote.Notehead.Filled === false) {
  300. const keyProps: Object[] = vfnote.getKeyProps();
  301. for (let i: number = 0; i < keyProps.length; i++) {
  302. (<any>keyProps[i]).code = "v81";
  303. }
  304. }
  305. for (let i: number = 0, len: number = numDots; i < len; ++i) {
  306. vfnote.addDotToAll();
  307. }
  308. return vfnote;
  309. }
  310. public static generateArticulations(vfnote: Vex.Flow.StemmableNote, articulations: ArticulationEnum[]): void {
  311. if (vfnote === undefined || vfnote.getAttribute("type") === "GhostNote") {
  312. return;
  313. }
  314. // Articulations:
  315. let vfArtPosition: number = Vex.Flow.Modifier.Position.ABOVE;
  316. if (vfnote.getStemDirection() === Vex.Flow.Stem.UP) {
  317. vfArtPosition = Vex.Flow.Modifier.Position.BELOW;
  318. }
  319. for (const articulation of articulations) {
  320. // tslint:disable-next-line:switch-default
  321. let vfArt: Vex.Flow.Articulation = undefined;
  322. switch (articulation) {
  323. case ArticulationEnum.accent: {
  324. vfArt = new Vex.Flow.Articulation("a>");
  325. break;
  326. }
  327. case ArticulationEnum.downbow: {
  328. vfArt = new Vex.Flow.Articulation("am");
  329. break;
  330. }
  331. case ArticulationEnum.fermata: {
  332. vfArt = new Vex.Flow.Articulation("a@a");
  333. vfArtPosition = Vex.Flow.Modifier.Position.ABOVE;
  334. break;
  335. }
  336. case ArticulationEnum.invertedfermata: {
  337. vfArt = new Vex.Flow.Articulation("a@u");
  338. vfArtPosition = Vex.Flow.Modifier.Position.BELOW;
  339. break;
  340. }
  341. case ArticulationEnum.lefthandpizzicato: {
  342. vfArt = new Vex.Flow.Articulation("a+");
  343. break;
  344. }
  345. case ArticulationEnum.snappizzicato: {
  346. vfArt = new Vex.Flow.Articulation("ao");
  347. break;
  348. }
  349. case ArticulationEnum.staccatissimo: {
  350. vfArt = new Vex.Flow.Articulation("av");
  351. break;
  352. }
  353. case ArticulationEnum.staccato: {
  354. vfArt = new Vex.Flow.Articulation("a.");
  355. break;
  356. }
  357. case ArticulationEnum.tenuto: {
  358. vfArt = new Vex.Flow.Articulation("a-");
  359. break;
  360. }
  361. case ArticulationEnum.upbow: {
  362. vfArt = new Vex.Flow.Articulation("a|");
  363. break;
  364. }
  365. case ArticulationEnum.strongaccent: {
  366. vfArt = new Vex.Flow.Articulation("a^");
  367. break;
  368. }
  369. default: {
  370. break;
  371. }
  372. }
  373. if (vfArt !== undefined) {
  374. vfArt.setPosition(vfArtPosition);
  375. (vfnote as StaveNote).addModifier(0, vfArt);
  376. }
  377. }
  378. }
  379. public static generateOrnaments(vfnote: Vex.Flow.StemmableNote, oContainer: OrnamentContainer): void {
  380. let vfPosition: number = Vex.Flow.Modifier.Position.ABOVE;
  381. if (vfnote.getStemDirection() === Vex.Flow.Stem.UP) {
  382. vfPosition = Vex.Flow.Modifier.Position.BELOW;
  383. }
  384. let vfOrna: Vex.Flow.Ornament = undefined;
  385. switch (oContainer.GetOrnament) {
  386. case OrnamentEnum.DelayedInvertedTurn: {
  387. vfOrna = new Vex.Flow.Ornament("turn_inverted");
  388. vfOrna.setDelayed(true);
  389. break;
  390. }
  391. case OrnamentEnum.DelayedTurn: {
  392. vfOrna = new Vex.Flow.Ornament("turn");
  393. vfOrna.setDelayed(true);
  394. break;
  395. }
  396. case OrnamentEnum.InvertedMordent: {
  397. vfOrna = new Vex.Flow.Ornament("mordent_inverted");
  398. vfOrna.setDelayed(false);
  399. break;
  400. }
  401. case OrnamentEnum.InvertedTurn: {
  402. vfOrna = new Vex.Flow.Ornament("turn_inverted");
  403. vfOrna.setDelayed(false);
  404. break;
  405. }
  406. case OrnamentEnum.Mordent: {
  407. vfOrna = new Vex.Flow.Ornament("mordent");
  408. vfOrna.setDelayed(false);
  409. break;
  410. }
  411. case OrnamentEnum.Trill: {
  412. vfOrna = new Vex.Flow.Ornament("tr");
  413. vfOrna.setDelayed(false);
  414. break;
  415. }
  416. case OrnamentEnum.Turn: {
  417. vfOrna = new Vex.Flow.Ornament("turn");
  418. vfOrna.setDelayed(false);
  419. break;
  420. }
  421. default: {
  422. log.warn("unhandled OrnamentEnum type: " + oContainer.GetOrnament);
  423. return;
  424. }
  425. }
  426. if (vfOrna !== undefined) {
  427. if (oContainer.AccidentalBelow !== AccidentalEnum.NONE) {
  428. vfOrna.setLowerAccidental(Pitch.accidentalVexflow(oContainer.AccidentalBelow));
  429. }
  430. if (oContainer.AccidentalAbove !== AccidentalEnum.NONE) {
  431. vfOrna.setUpperAccidental(Pitch.accidentalVexflow(oContainer.AccidentalAbove));
  432. }
  433. vfOrna.setPosition(vfPosition);
  434. (vfnote as StaveNote).addModifier(0, vfOrna);
  435. }
  436. }
  437. /**
  438. * Convert a set of GraphicalNotes to a VexFlow StaveNote
  439. * @param notes form a chord on the staff
  440. * @returns {Vex.Flow.StaveNote}
  441. */
  442. public static CreateTabNote(gve: GraphicalVoiceEntry): Vex.Flow.TabNote {
  443. const tabPositions: {str: number, fret: number}[] = [];
  444. const frac: Fraction = gve.notes[0].graphicalNoteLength;
  445. const isTuplet: boolean = gve.notes[0].sourceNote.NoteTuplet !== undefined;
  446. let duration: string = VexFlowConverter.duration(frac, isTuplet);
  447. let numDots: number = 0;
  448. for (const note of gve.notes) {
  449. const tabNote: TabNote = note.sourceNote as TabNote;
  450. const tabPosition: {str: number, fret: number} = {str: tabNote.StringNumber, fret: tabNote.FretNumber};
  451. tabPositions.push(tabPosition);
  452. if (numDots < note.numberOfDots) {
  453. numDots = note.numberOfDots;
  454. }
  455. }
  456. for (let i: number = 0, len: number = numDots; i < len; ++i) {
  457. duration += "d";
  458. }
  459. const vfnote: Vex.Flow.TabNote = new Vex.Flow.TabNote({
  460. duration: duration,
  461. positions: tabPositions,
  462. });
  463. return vfnote;
  464. }
  465. /**
  466. * Convert a ClefInstruction to a string represention of a clef type in VexFlow.
  467. *
  468. * @param clef The OSMD object to be converted representing the clef
  469. * @param size The VexFlow size to be used. Can be `default` or `small`. As soon as
  470. * #118 is done, this parameter will be dispensable.
  471. * @returns A string representation of a VexFlow clef
  472. * @see https://github.com/0xfe/vexflow/blob/master/src/clef.js
  473. * @see https://github.com/0xfe/vexflow/blob/master/tests/clef_tests.js
  474. */
  475. public static Clef(clef: ClefInstruction, size: string = "default"): { type: string, size: string, annotation: string } {
  476. let type: string;
  477. let annotation: string;
  478. // Make sure size is either "default" or "small"
  479. if (size !== "default" && size !== "small") {
  480. log.warn(`Invalid VexFlow clef size "${size}" specified. Using "default".`);
  481. size = "default";
  482. }
  483. /*
  484. * For all of the following conversions, OSMD uses line numbers 1-5 starting from
  485. * the bottom, while VexFlow uses 0-4 starting from the top.
  486. */
  487. switch (clef.ClefType) {
  488. // G Clef
  489. case ClefEnum.G:
  490. switch (clef.Line) {
  491. case 1:
  492. type = "french"; // VexFlow line 4
  493. break;
  494. case 2:
  495. type = "treble"; // VexFlow line 3
  496. break;
  497. default:
  498. type = "treble";
  499. log.error(`Clef ${ClefEnum[clef.ClefType]} on line ${clef.Line} not supported by VexFlow. Using default value "${type}".`);
  500. }
  501. break;
  502. // F Clef
  503. case ClefEnum.F:
  504. switch (clef.Line) {
  505. case 4:
  506. type = "bass"; // VexFlow line 1
  507. break;
  508. case 3:
  509. type = "baritone-f"; // VexFlow line 2
  510. break;
  511. case 5:
  512. type = "subbass"; // VexFlow line 0
  513. break;
  514. default:
  515. type = "bass";
  516. log.error(`Clef ${ClefEnum[clef.ClefType]} on line ${clef.Line} not supported by VexFlow. Using default value "${type}".`);
  517. }
  518. break;
  519. // C Clef
  520. case ClefEnum.C:
  521. switch (clef.Line) {
  522. case 3:
  523. type = "alto"; // VexFlow line 2
  524. break;
  525. case 4:
  526. type = "tenor"; // VexFlow line 1
  527. break;
  528. case 1:
  529. type = "soprano"; // VexFlow line 4
  530. break;
  531. case 2:
  532. type = "mezzo-soprano"; // VexFlow line 3
  533. break;
  534. default:
  535. type = "alto";
  536. log.error(`Clef ${ClefEnum[clef.ClefType]} on line ${clef.Line} not supported by VexFlow. Using default value "${type}".`);
  537. }
  538. break;
  539. // Percussion Clef
  540. case ClefEnum.percussion:
  541. type = "percussion";
  542. break;
  543. // TAB Clef
  544. case ClefEnum.TAB:
  545. // only used currently for creating the notes in the normal stave: There we need a normal treble clef
  546. type = "treble";
  547. break;
  548. default:
  549. }
  550. // annotations in vexflow don't allow bass and 8va. No matter the offset :(
  551. if (clef.OctaveOffset === 1 && type !== "bass" ) {
  552. annotation = "8va";
  553. } else if (clef.OctaveOffset === -1) {
  554. annotation = "8vb";
  555. }
  556. return { type, size, annotation };
  557. }
  558. /**
  559. * Convert a RhythmInstruction to a VexFlow TimeSignature object
  560. * @param rhythm
  561. * @returns {Vex.Flow.TimeSignature}
  562. * @constructor
  563. */
  564. public static TimeSignature(rhythm: RhythmInstruction): Vex.Flow.TimeSignature {
  565. let timeSpec: string;
  566. switch (rhythm.SymbolEnum) {
  567. case RhythmSymbolEnum.NONE:
  568. timeSpec = rhythm.Rhythm.Numerator + "/" + rhythm.Rhythm.Denominator;
  569. break;
  570. case RhythmSymbolEnum.COMMON:
  571. timeSpec = "C";
  572. break;
  573. case RhythmSymbolEnum.CUT:
  574. timeSpec = "C|";
  575. break;
  576. default:
  577. }
  578. return new Vex.Flow.TimeSignature(timeSpec);
  579. }
  580. /**
  581. * Convert a KeyInstruction to a string representing in VexFlow a key
  582. * @param key
  583. * @returns {string}
  584. */
  585. public static keySignature(key: KeyInstruction): string {
  586. if (key === undefined) {
  587. return undefined;
  588. }
  589. let ret: string;
  590. switch (key.Mode) {
  591. case KeyEnum.minor:
  592. ret = VexFlowConverter.minorMap[key.Key] + "m";
  593. break;
  594. case KeyEnum.major:
  595. ret = VexFlowConverter.majorMap[key.Key];
  596. break;
  597. // some XMLs don't have the mode set despite having a key signature.
  598. case KeyEnum.none:
  599. ret = VexFlowConverter.majorMap[key.Key];
  600. break;
  601. default:
  602. ret = "C";
  603. }
  604. return ret;
  605. }
  606. /**
  607. * Converts a lineType to a VexFlow StaveConnector type
  608. * @param lineType
  609. * @returns {any}
  610. */
  611. public static line(lineType: SystemLinesEnum, linePosition: SystemLinePosition): any {
  612. switch (lineType) {
  613. case SystemLinesEnum.SingleThin:
  614. if (linePosition === SystemLinePosition.MeasureBegin) {
  615. return Vex.Flow.StaveConnector.type.SINGLE;
  616. }
  617. return Vex.Flow.StaveConnector.type.SINGLE_RIGHT;
  618. case SystemLinesEnum.DoubleThin:
  619. return Vex.Flow.StaveConnector.type.DOUBLE;
  620. case SystemLinesEnum.ThinBold:
  621. return Vex.Flow.StaveConnector.type.BOLD_DOUBLE_RIGHT;
  622. case SystemLinesEnum.BoldThinDots:
  623. return Vex.Flow.StaveConnector.type.BOLD_DOUBLE_LEFT;
  624. case SystemLinesEnum.DotsThinBold:
  625. return Vex.Flow.StaveConnector.type.BOLD_DOUBLE_RIGHT;
  626. case SystemLinesEnum.DotsBoldBoldDots:
  627. return Vex.Flow.StaveConnector.type.BOLD_DOUBLE_RIGHT;
  628. case SystemLinesEnum.None:
  629. return Vex.Flow.StaveConnector.type.NONE;
  630. default:
  631. }
  632. }
  633. /**
  634. * Construct a string which can be used in a CSS font property
  635. * @param fontSize
  636. * @param fontStyle
  637. * @param font
  638. * @returns {string}
  639. */
  640. public static font(fontSize: number, fontStyle: FontStyles = FontStyles.Regular, font: Fonts = Fonts.TimesNewRoman): string {
  641. let style: string = "normal";
  642. let weight: string = "normal";
  643. const family: string = "'" + EngravingRules.Rules.DefaultFontFamily + "'"; // default "'Times New Roman'"
  644. switch (fontStyle) {
  645. case FontStyles.Bold:
  646. weight = "bold";
  647. break;
  648. case FontStyles.Italic:
  649. style = "italic";
  650. break;
  651. case FontStyles.BoldItalic:
  652. style = "italic";
  653. weight = "bold";
  654. break;
  655. case FontStyles.Underlined:
  656. // TODO
  657. break;
  658. default:
  659. break;
  660. }
  661. switch (font) { // currently not used
  662. case Fonts.Kokila:
  663. // TODO Not Supported
  664. break;
  665. default:
  666. }
  667. return style + " " + weight + " " + Math.floor(fontSize) + "px " + family;
  668. }
  669. /**
  670. * Converts the style into a string that VexFlow RenderContext can understand
  671. * as the weight of the font
  672. */
  673. public static fontStyle(style: FontStyles): string {
  674. switch (style) {
  675. case FontStyles.Bold:
  676. return "bold";
  677. case FontStyles.Italic:
  678. return "italic";
  679. case FontStyles.BoldItalic:
  680. return "italic bold";
  681. default:
  682. return "normal";
  683. }
  684. }
  685. /**
  686. * Convert OutlineAndFillStyle to CSS properties
  687. * @param styleId
  688. * @returns {string}
  689. */
  690. public static style(styleId: OutlineAndFillStyleEnum): string {
  691. const ret: string = OUTLINE_AND_FILL_STYLE_DICT.getValue(styleId);
  692. return ret;
  693. }
  694. }