VexFlowMeasure.ts 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  1. import Vex = require("vexflow");
  2. import {StaffMeasure} from "../StaffMeasure";
  3. import {SourceMeasure} from "../../VoiceData/SourceMeasure";
  4. import {Staff} from "../../VoiceData/Staff";
  5. import {StaffLine} from "../StaffLine";
  6. import {SystemLinesEnum} from "../SystemLinesEnum";
  7. import {ClefInstruction} from "../../VoiceData/Instructions/ClefInstruction";
  8. import {KeyInstruction} from "../../VoiceData/Instructions/KeyInstruction";
  9. import {RhythmInstruction} from "../../VoiceData/Instructions/RhythmInstruction";
  10. import {VexFlowConverter} from "./VexFlowConverter";
  11. import {VexFlowStaffEntry} from "./VexFlowStaffEntry";
  12. import {Beam} from "../../VoiceData/Beam";
  13. import {GraphicalNote} from "../GraphicalNote";
  14. import {GraphicalStaffEntry} from "../GraphicalStaffEntry";
  15. import StaveConnector = Vex.Flow.StaveConnector;
  16. import StaveNote = Vex.Flow.StaveNote;
  17. import {Logging} from "../../../Common/Logging";
  18. import {unitInPixels} from "./VexFlowMusicSheetDrawer";
  19. import {Tuplet} from "../../VoiceData/Tuplet";
  20. export class VexFlowMeasure extends StaffMeasure {
  21. constructor(staff: Staff, staffLine: StaffLine = undefined, sourceMeasure: SourceMeasure = undefined) {
  22. super(staff, sourceMeasure, staffLine);
  23. this.minimumStaffEntriesWidth = -1;
  24. this.resetLayout();
  25. }
  26. // octaveOffset according to active clef
  27. public octaveOffset: number = 3;
  28. // The VexFlow Voices in the measure
  29. public vfVoices: { [voiceID: number]: Vex.Flow.Voice; } = {};
  30. // Call this function (if present) to x-format all the voices in the measure
  31. public formatVoices: (width: number) => void;
  32. // The VexFlow Ties in the measure
  33. public vfTies: Vex.Flow.StaveTie[] = [];
  34. // The VexFlow Stave (one measure in one line)
  35. private stave: Vex.Flow.Stave;
  36. // VexFlow StaveConnectors (vertical lines)
  37. private connectors: Vex.Flow.StaveConnector[] = [];
  38. // Intermediate object to construct beams
  39. private beams: { [voiceID: number]: [Beam, VexFlowStaffEntry[]][]; } = {};
  40. // VexFlow Beams
  41. private vfbeams: { [voiceID: number]: Vex.Flow.Beam[]; };
  42. // Intermediate object to construct tuplets
  43. private tuplets: { [voiceID: number]: [Tuplet, VexFlowStaffEntry[]][]; } = {};
  44. // VexFlow Tuplets
  45. private vftuplets: { [voiceID: number]: Vex.Flow.Tuplet[]; } = {};
  46. // Sets the absolute coordinates of the VFStave on the canvas
  47. public setAbsoluteCoordinates(x: number, y: number): void {
  48. this.stave.setX(x).setY(y);
  49. }
  50. /**
  51. * Reset all the geometric values and parameters of this measure and put it in an initialized state.
  52. * This is needed to evaluate a measure a second time by system builder.
  53. */
  54. public resetLayout(): void {
  55. // Take into account some space for the begin and end lines of the stave
  56. // Will be changed when repetitions will be implemented
  57. //this.beginInstructionsWidth = 20 / UnitInPixels;
  58. //this.endInstructionsWidth = 20 / UnitInPixels;
  59. this.stave = new Vex.Flow.Stave(0, 0, 0, {
  60. space_above_staff_ln: 0,
  61. space_below_staff_ln: 0,
  62. });
  63. this.updateInstructionWidth();
  64. }
  65. public clean(): void {
  66. this.vfTies.length = 0;
  67. this.connectors = [];
  68. // Clean up instructions
  69. this.resetLayout();
  70. }
  71. /**
  72. * returns the x-width of a given measure line.
  73. * @param line
  74. * @returns {SystemLinesEnum} the x-width
  75. */
  76. public getLineWidth(line: SystemLinesEnum): number {
  77. // FIXME: See values in VexFlow's stavebarline.js
  78. const vfline: any = VexFlowConverter.line(line);
  79. switch (vfline) {
  80. case Vex.Flow.StaveConnector.type.SINGLE:
  81. return 1.0 / unitInPixels;
  82. case Vex.Flow.StaveConnector.type.DOUBLE:
  83. return 3.0 / unitInPixels;
  84. default:
  85. return 0;
  86. }
  87. }
  88. /**
  89. * adds the given clef to the begin of the measure.
  90. * This has to update/increase BeginInstructionsWidth.
  91. * @param clef
  92. */
  93. public addClefAtBegin(clef: ClefInstruction): void {
  94. this.octaveOffset = clef.OctaveOffset;
  95. const vfclef: { type: string, size: string, annotation: string } = VexFlowConverter.Clef(clef, "default");
  96. this.stave.addClef(vfclef.type, vfclef.size, vfclef.annotation, Vex.Flow.Modifier.Position.BEGIN);
  97. this.updateInstructionWidth();
  98. }
  99. /**
  100. * adds the given key to the begin of the measure.
  101. * This has to update/increase BeginInstructionsWidth.
  102. * @param currentKey the new valid key.
  103. * @param previousKey the old cancelled key. Needed to show which accidentals are not valid any more.
  104. * @param currentClef the valid clef. Needed to put the accidentals on the right y-positions.
  105. */
  106. public addKeyAtBegin(currentKey: KeyInstruction, previousKey: KeyInstruction, currentClef: ClefInstruction): void {
  107. this.stave.setKeySignature(
  108. VexFlowConverter.keySignature(currentKey),
  109. VexFlowConverter.keySignature(previousKey),
  110. undefined
  111. );
  112. this.updateInstructionWidth();
  113. }
  114. /**
  115. * adds the given rhythm to the begin of the measure.
  116. * This has to update/increase BeginInstructionsWidth.
  117. * @param rhythm
  118. */
  119. public addRhythmAtBegin(rhythm: RhythmInstruction): void {
  120. const timeSig: Vex.Flow.TimeSignature = VexFlowConverter.TimeSignature(rhythm);
  121. this.stave.addModifier(
  122. timeSig,
  123. Vex.Flow.Modifier.Position.BEGIN
  124. );
  125. this.updateInstructionWidth();
  126. }
  127. /**
  128. * adds the given clef to the end of the measure.
  129. * This has to update/increase EndInstructionsWidth.
  130. * @param clef
  131. */
  132. public addClefAtEnd(clef: ClefInstruction): void {
  133. const vfclef: { type: string, size: string, annotation: string } = VexFlowConverter.Clef(clef, "small");
  134. this.stave.setEndClef(vfclef.type, vfclef.size, vfclef.annotation);
  135. this.updateInstructionWidth();
  136. }
  137. /**
  138. * Sets the overall x-width of the measure.
  139. * @param width
  140. */
  141. public setWidth(width: number): void {
  142. super.setWidth(width);
  143. // Set the width of the Vex.Flow.Stave
  144. this.stave.setWidth(width * unitInPixels);
  145. // Force the width of the Begin Instructions
  146. //this.stave.setNoteStartX(this.beginInstructionsWidth * UnitInPixels);
  147. }
  148. /**
  149. * This method is called after the StaffEntriesScaleFactor has been set.
  150. * Here the final x-positions of the staff entries have to be set.
  151. * (multiply the minimal positions with the scaling factor, considering the BeginInstructionsWidth)
  152. */
  153. public layoutSymbols(): void {
  154. //this.stave.format();
  155. }
  156. //public addGraphicalStaffEntry(entry: VexFlowStaffEntry): void {
  157. // super.addGraphicalStaffEntry(entry);
  158. //}
  159. //
  160. //public addGraphicalStaffEntryAtTimestamp(entry: VexFlowStaffEntry): void {
  161. // super.addGraphicalStaffEntryAtTimestamp(entry);
  162. // // TODO
  163. //}
  164. /**
  165. * Draw this measure on a VexFlow CanvasContext
  166. * @param ctx
  167. */
  168. public draw(ctx: Vex.Flow.RenderContext): void {
  169. // Draw stave lines
  170. this.stave.setContext(ctx).draw();
  171. // Draw all voices
  172. for (const voiceID in this.vfVoices) {
  173. if (this.vfVoices.hasOwnProperty(voiceID)) {
  174. this.vfVoices[voiceID].draw(ctx, this.stave);
  175. }
  176. }
  177. // Draw beams
  178. for (const voiceID in this.vfbeams) {
  179. if (this.vfbeams.hasOwnProperty(voiceID)) {
  180. for (const beam of this.vfbeams[voiceID]) {
  181. beam.setContext(ctx).draw();
  182. }
  183. }
  184. }
  185. // Draw tuplets
  186. for (const voiceID in this.vftuplets) {
  187. if (this.vftuplets.hasOwnProperty(voiceID)) {
  188. for (const tuplet of this.vftuplets[voiceID]) {
  189. tuplet.setContext(ctx).draw();
  190. }
  191. }
  192. }
  193. // Draw ties
  194. for (const tie of this.vfTies) {
  195. tie.setContext(ctx).draw();
  196. }
  197. // Draw vertical lines
  198. for (const connector of this.connectors) {
  199. connector.setContext(ctx).draw();
  200. }
  201. }
  202. public format(): void {
  203. // If this is the first stave in the vertical measure, call the format
  204. // method to set the width of all the voices
  205. if (this.formatVoices) {
  206. // The width of the voices does not include the instructions (StaveModifiers)
  207. this.formatVoices((this.PositionAndShape.BorderRight - this.beginInstructionsWidth - this.endInstructionsWidth) * unitInPixels);
  208. }
  209. // Force the width of the Begin Instructions
  210. this.stave.setNoteStartX(this.stave.getX() + unitInPixels * this.beginInstructionsWidth);
  211. }
  212. /**
  213. * Add a note to a beam
  214. * @param graphicalNote
  215. * @param beam
  216. */
  217. public handleBeam(graphicalNote: GraphicalNote, beam: Beam): void {
  218. const voiceID: number = graphicalNote.sourceNote.ParentVoiceEntry.ParentVoice.VoiceId;
  219. let beams: [Beam, VexFlowStaffEntry[]][] = this.beams[voiceID];
  220. if (beams === undefined) {
  221. beams = this.beams[voiceID] = [];
  222. }
  223. let data: [Beam, VexFlowStaffEntry[]];
  224. for (const mybeam of beams) {
  225. if (mybeam[0] === beam) {
  226. data = mybeam;
  227. }
  228. }
  229. if (data === undefined) {
  230. data = [beam, []];
  231. beams.push(data);
  232. }
  233. const parent: VexFlowStaffEntry = graphicalNote.parentStaffEntry as VexFlowStaffEntry;
  234. if (data[1].indexOf(parent) < 0) {
  235. data[1].push(parent);
  236. }
  237. }
  238. public handleTuplet(graphicalNote: GraphicalNote, tuplet: Tuplet): void {
  239. const voiceID: number = graphicalNote.sourceNote.ParentVoiceEntry.ParentVoice.VoiceId;
  240. tuplet = graphicalNote.sourceNote.NoteTuplet;
  241. let tuplets: [Tuplet, VexFlowStaffEntry[]][] = this.tuplets[voiceID];
  242. if (tuplets === undefined) {
  243. tuplets = this.tuplets[voiceID] = [];
  244. }
  245. let currentTupletBuilder: [Tuplet, VexFlowStaffEntry[]];
  246. for (const t of tuplets) {
  247. if (t[0] === tuplet) {
  248. currentTupletBuilder = t;
  249. }
  250. }
  251. if (currentTupletBuilder === undefined) {
  252. currentTupletBuilder = [tuplet, []];
  253. tuplets.push(currentTupletBuilder);
  254. }
  255. const parent: VexFlowStaffEntry = graphicalNote.parentStaffEntry as VexFlowStaffEntry;
  256. if (currentTupletBuilder[1].indexOf(parent) < 0) {
  257. currentTupletBuilder[1].push(parent);
  258. }
  259. }
  260. /**
  261. * Complete the creation of VexFlow Beams in this measure
  262. */
  263. public finalizeBeams(): void {
  264. // The following line resets the created Vex.Flow Beams and
  265. // created them brand new. Is this needed? And more importantly,
  266. // should the old beams be removed manually by the notes?
  267. this.vfbeams = {};
  268. for (const voiceID in this.beams) {
  269. if (this.beams.hasOwnProperty(voiceID)) {
  270. let vfbeams: Vex.Flow.Beam[] = this.vfbeams[voiceID];
  271. if (vfbeams === undefined) {
  272. vfbeams = this.vfbeams[voiceID] = [];
  273. }
  274. for (const beam of this.beams[voiceID]) {
  275. const notes: Vex.Flow.StaveNote[] = [];
  276. for (const entry of beam[1]) {
  277. const note: Vex.Flow.StaveNote = (<VexFlowStaffEntry>entry).vfNotes[voiceID];
  278. if (note !== undefined) {
  279. notes.push(note);
  280. }
  281. }
  282. if (notes.length > 1) {
  283. vfbeams.push(new Vex.Flow.Beam(notes, true));
  284. // just a test for coloring the notes:
  285. // for (let note of notes) {
  286. // (<Vex.Flow.StaveNote> note).setStyle({fillStyle: "green", strokeStyle: "green"});
  287. // }
  288. } else {
  289. Logging.log("Warning! Beam with no notes! Trying to ignore, but this is a serious problem.");
  290. }
  291. }
  292. }
  293. }
  294. }
  295. /**
  296. * Complete the creation of VexFlow Tuplets in this measure
  297. */
  298. public finalizeTuplets(): void {
  299. // The following line resets the created Vex.Flow Tuplets and
  300. // created them brand new. Is this needed? And more importantly,
  301. // should the old tuplets be removed manually from the notes?
  302. this.vftuplets = {};
  303. for (const voiceID in this.tuplets) {
  304. if (this.tuplets.hasOwnProperty(voiceID)) {
  305. let vftuplets: Vex.Flow.Tuplet[] = this.vftuplets[voiceID];
  306. if (vftuplets === undefined) {
  307. vftuplets = this.vftuplets[voiceID] = [];
  308. }
  309. for (const tupletBuilder of this.tuplets[voiceID]) {
  310. const tupletStaveNotes: Vex.Flow.StaveNote[] = [];
  311. const tupletStaffEntries: VexFlowStaffEntry[] = tupletBuilder[1];
  312. for (const tupletStaffEntry of tupletStaffEntries) {
  313. tupletStaveNotes.push((tupletStaffEntry).vfNotes[voiceID]);
  314. }
  315. if (tupletStaveNotes.length > 1) {
  316. const notesOccupied: number = 2;
  317. vftuplets.push(new Vex.Flow.Tuplet( tupletStaveNotes,
  318. {
  319. notes_occupied: notesOccupied,
  320. num_notes: tupletStaveNotes.length //, location: -1, ratioed: true
  321. }));
  322. } else {
  323. Logging.log("Warning! Tuplet with no notes! Trying to ignore, but this is a serious problem.");
  324. }
  325. }
  326. }
  327. }
  328. }
  329. public layoutStaffEntry(graphicalStaffEntry: GraphicalStaffEntry): void {
  330. return;
  331. }
  332. public staffMeasureCreatedCalculations(): void {
  333. for (let idx: number = 0, len: number = this.staffEntries.length; idx < len; ++idx) {
  334. const graphicalStaffEntry: VexFlowStaffEntry = (this.staffEntries[idx] as VexFlowStaffEntry);
  335. // create vex flow Notes:
  336. const gnotes: { [voiceID: number]: GraphicalNote[]; } = graphicalStaffEntry.graphicalNotes;
  337. for (const voiceID in gnotes) {
  338. if (gnotes.hasOwnProperty(voiceID)) {
  339. const vfnote: StaveNote = VexFlowConverter.StaveNote(gnotes[voiceID]);
  340. (graphicalStaffEntry as VexFlowStaffEntry).vfNotes[voiceID] = vfnote;
  341. }
  342. }
  343. }
  344. this.finalizeBeams();
  345. this.finalizeTuplets();
  346. for (let idx: number = 0, len: number = this.staffEntries.length; idx < len; ++idx) {
  347. const graphicalStaffEntry: VexFlowStaffEntry = (this.staffEntries[idx] as VexFlowStaffEntry);
  348. const gnotes: { [voiceID: number]: GraphicalNote[]; } = graphicalStaffEntry.graphicalNotes;
  349. // create vex flow voices and add tickables to it:
  350. const vfVoices: { [voiceID: number]: Vex.Flow.Voice; } = this.vfVoices;
  351. for (const voiceID in gnotes) {
  352. if (gnotes.hasOwnProperty(voiceID)) {
  353. if (!(voiceID in vfVoices)) {
  354. vfVoices[voiceID] = new Vex.Flow.Voice({
  355. beat_value: this.parentSourceMeasure.Duration.Denominator,
  356. num_beats: this.parentSourceMeasure.Duration.Numerator,
  357. resolution: Vex.Flow.RESOLUTION,
  358. }).setMode(Vex.Flow.Voice.Mode.SOFT);
  359. }
  360. vfVoices[voiceID].addTickable(graphicalStaffEntry.vfNotes[voiceID]);
  361. }
  362. }
  363. }
  364. }
  365. /**
  366. * Creates a line from 'top' to this measure, of type 'lineType'
  367. * @param top
  368. * @param lineType
  369. */
  370. public lineTo(top: VexFlowMeasure, lineType: any): void {
  371. const connector: StaveConnector = new Vex.Flow.StaveConnector(top.getVFStave(), this.stave);
  372. connector.setType(lineType);
  373. this.connectors.push(connector);
  374. }
  375. /**
  376. * Return the VexFlow Stave corresponding to this StaffMeasure
  377. * @returns {Vex.Flow.Stave}
  378. */
  379. public getVFStave(): Vex.Flow.Stave {
  380. return this.stave;
  381. }
  382. //private increaseBeginInstructionWidth(): void {
  383. // let modifiers: StaveModifier[] = this.stave.getModifiers();
  384. // let modifier: StaveModifier = modifiers[modifiers.length - 1];
  385. // //let padding: number = modifier.getCategory() === "keysignatures" ? modifier.getPadding(2) : 0;
  386. // let padding: number = modifier.getPadding(20);
  387. // let width: number = modifier.getWidth();
  388. // this.beginInstructionsWidth += (padding + width) / UnitInPixels;
  389. //}
  390. //
  391. //private increaseEndInstructionWidth(): void {
  392. // let modifiers: StaveModifier[] = this.stave.getModifiers();
  393. // let modifier: StaveModifier = modifiers[modifiers.length - 1];
  394. // let padding: number = 0;
  395. // let width: number = modifier.getWidth();
  396. // this.endInstructionsWidth += (padding + width) / UnitInPixels;
  397. //
  398. //}
  399. /**
  400. * After re-running the formatting on the VexFlow Stave, update the
  401. * space needed by Instructions (in VexFlow: StaveModifiers)
  402. */
  403. private updateInstructionWidth(): void {
  404. this.beginInstructionsWidth = (this.stave.getNoteStartX() - this.stave.getX()) / unitInPixels;
  405. this.endInstructionsWidth = (this.stave.getX() + this.stave.getWidth() - this.stave.getNoteEndX()) / unitInPixels;
  406. }
  407. }