VexFlowMeasure.ts 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667
  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, VexFlowRepetitionType, VexFlowBarlineType} 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 * as log from "loglevel";
  18. import {unitInPixels} from "./VexFlowMusicSheetDrawer";
  19. import {Tuplet} from "../../VoiceData/Tuplet";
  20. import {RepetitionInstructionEnum} from "../../VoiceData/Instructions/RepetitionInstruction";
  21. import {SystemLinePosition} from "../SystemLinePosition";
  22. import {StemDirectionType} from "../../VoiceData/VoiceEntry";
  23. import {GraphicalVoiceEntry} from "../GraphicalVoiceEntry";
  24. import {VexFlowVoiceEntry} from "./VexFlowVoiceEntry";
  25. import {Fraction} from "../../../Common/DataObjects/Fraction";
  26. import { Voice } from "../../VoiceData/Voice";
  27. import { VexFlowInstantaniousDynamicExpression } from "./VexFlowInstantaniousDynamicExpression";
  28. export class VexFlowMeasure extends StaffMeasure {
  29. constructor(staff: Staff, staffLine: StaffLine = undefined, sourceMeasure: SourceMeasure = undefined) {
  30. super(staff, sourceMeasure, staffLine);
  31. this.minimumStaffEntriesWidth = -1;
  32. this.resetLayout();
  33. }
  34. /** octaveOffset according to active clef */
  35. public octaveOffset: number = 3;
  36. /** The VexFlow Voices in the measure */
  37. public vfVoices: { [voiceID: number]: Vex.Flow.Voice; } = {};
  38. /** Call this function (if present) to x-format all the voices in the measure */
  39. public formatVoices: (width: number) => void;
  40. /** The VexFlow Ties in the measure */
  41. public vfTies: Vex.Flow.StaveTie[] = [];
  42. /** The repetition instructions given as words or symbols (coda, dal segno..) */
  43. public vfRepetitionWords: Vex.Flow.Repetition[] = [];
  44. /** Instant dynamics */
  45. public instantaniousDynamics: VexFlowInstantaniousDynamicExpression[] = [];
  46. /** The VexFlow Stave (= one measure in a staffline) */
  47. private stave: Vex.Flow.Stave;
  48. /** VexFlow StaveConnectors (vertical lines) */
  49. private connectors: Vex.Flow.StaveConnector[] = [];
  50. /** Intermediate object to construct beams */
  51. private beams: { [voiceID: number]: [Beam, VexFlowVoiceEntry[]][]; } = {};
  52. /** VexFlow Beams */
  53. private vfbeams: { [voiceID: number]: Vex.Flow.Beam[]; };
  54. /** Intermediate object to construct tuplets */
  55. private tuplets: { [voiceID: number]: [Tuplet, VexFlowVoiceEntry[]][]; } = {};
  56. /** VexFlow Tuplets */
  57. private vftuplets: { [voiceID: number]: Vex.Flow.Tuplet[]; } = {};
  58. // Sets the absolute coordinates of the VFStave on the canvas
  59. public setAbsoluteCoordinates(x: number, y: number): void {
  60. this.stave.setX(x).setY(y);
  61. }
  62. /**
  63. * Reset all the geometric values and parameters of this measure and put it in an initialized state.
  64. * This is needed to evaluate a measure a second time by system builder.
  65. */
  66. public resetLayout(): void {
  67. // Take into account some space for the begin and end lines of the stave
  68. // Will be changed when repetitions will be implemented
  69. //this.beginInstructionsWidth = 20 / UnitInPixels;
  70. //this.endInstructionsWidth = 20 / UnitInPixels;
  71. this.stave = new Vex.Flow.Stave(0, 0, 0, {
  72. space_above_staff_ln: 0,
  73. space_below_staff_ln: 0,
  74. });
  75. this.updateInstructionWidth();
  76. }
  77. public clean(): void {
  78. this.vfTies.length = 0;
  79. this.connectors = [];
  80. // Clean up instructions
  81. this.resetLayout();
  82. }
  83. /**
  84. * returns the x-width (in units) of a given measure line {SystemLinesEnum}.
  85. * @param line
  86. * @returns the x-width in osmd units
  87. */
  88. public getLineWidth(line: SystemLinesEnum): number {
  89. switch (line) {
  90. // return 0 for the normal lines, as the line width will be considered at the updateInstructionWidth() method using the stavemodifiers.
  91. // case SystemLinesEnum.SingleThin:
  92. // return 5.0 / unitInPixels;
  93. // case SystemLinesEnum.DoubleThin:
  94. // return 5.0 / unitInPixels;
  95. // case SystemLinesEnum.ThinBold:
  96. // return 5.0 / unitInPixels;
  97. // but just add a little extra space for repetitions (cosmetics):
  98. case SystemLinesEnum.BoldThinDots:
  99. case SystemLinesEnum.DotsThinBold:
  100. return 10.0 / unitInPixels;
  101. case SystemLinesEnum.DotsBoldBoldDots:
  102. return 10.0 / unitInPixels;
  103. default:
  104. return 0;
  105. }
  106. }
  107. /**
  108. * adds the given clef to the begin of the measure.
  109. * This has to update/increase BeginInstructionsWidth.
  110. * @param clef
  111. */
  112. public addClefAtBegin(clef: ClefInstruction): void {
  113. this.octaveOffset = clef.OctaveOffset;
  114. const vfclef: { type: string, size: string, annotation: string } = VexFlowConverter.Clef(clef, "default");
  115. this.stave.addClef(vfclef.type, vfclef.size, vfclef.annotation, Vex.Flow.Modifier.Position.BEGIN);
  116. this.updateInstructionWidth();
  117. }
  118. /**
  119. * adds the given key to the begin of the measure.
  120. * This has to update/increase BeginInstructionsWidth.
  121. * @param currentKey the new valid key.
  122. * @param previousKey the old cancelled key. Needed to show which accidentals are not valid any more.
  123. * @param currentClef the valid clef. Needed to put the accidentals on the right y-positions.
  124. */
  125. public addKeyAtBegin(currentKey: KeyInstruction, previousKey: KeyInstruction, currentClef: ClefInstruction): void {
  126. this.stave.setKeySignature(
  127. VexFlowConverter.keySignature(currentKey),
  128. VexFlowConverter.keySignature(previousKey),
  129. undefined
  130. );
  131. this.updateInstructionWidth();
  132. }
  133. /**
  134. * adds the given rhythm to the begin of the measure.
  135. * This has to update/increase BeginInstructionsWidth.
  136. * @param rhythm
  137. */
  138. public addRhythmAtBegin(rhythm: RhythmInstruction): void {
  139. const timeSig: Vex.Flow.TimeSignature = VexFlowConverter.TimeSignature(rhythm);
  140. this.stave.addModifier(
  141. timeSig,
  142. Vex.Flow.Modifier.Position.BEGIN
  143. );
  144. this.updateInstructionWidth();
  145. }
  146. /**
  147. * adds the given clef to the end of the measure.
  148. * This has to update/increase EndInstructionsWidth.
  149. * @param clef
  150. */
  151. public addClefAtEnd(clef: ClefInstruction): void {
  152. const vfclef: { type: string, size: string, annotation: string } = VexFlowConverter.Clef(clef, "small");
  153. this.stave.setEndClef(vfclef.type, vfclef.size, vfclef.annotation);
  154. this.updateInstructionWidth();
  155. }
  156. public addMeasureLine(lineType: SystemLinesEnum, linePosition: SystemLinePosition): void {
  157. switch (linePosition) {
  158. case SystemLinePosition.MeasureBegin:
  159. switch (lineType) {
  160. case SystemLinesEnum.BoldThinDots:
  161. this.stave.setBegBarType(VexFlowBarlineType.REPEAT_BEGIN);
  162. break;
  163. default:
  164. break;
  165. }
  166. break;
  167. case SystemLinePosition.MeasureEnd:
  168. switch (lineType) {
  169. case SystemLinesEnum.DotsBoldBoldDots:
  170. this.stave.setEndBarType(VexFlowBarlineType.REPEAT_BOTH);
  171. break;
  172. case SystemLinesEnum.DotsThinBold:
  173. this.stave.setEndBarType(VexFlowBarlineType.REPEAT_END);
  174. break;
  175. case SystemLinesEnum.DoubleThin:
  176. this.stave.setEndBarType(VexFlowBarlineType.DOUBLE);
  177. break;
  178. case SystemLinesEnum.ThinBold:
  179. this.stave.setEndBarType(VexFlowBarlineType.END);
  180. break;
  181. default:
  182. break;
  183. }
  184. break;
  185. default:
  186. break;
  187. }
  188. }
  189. /**
  190. * Adds a measure number to the top left corner of the measure
  191. * This method is not used currently in favor of the calculateMeasureNumberPlacement
  192. * method in the MusicSheetCalculator.ts
  193. */
  194. public addMeasureNumber(): void {
  195. const text: string = this.MeasureNumber.toString();
  196. const position: number = Vex.Flow.StaveModifier.Position.ABOVE;
  197. const options: any = {
  198. justification: 1,
  199. shift_x: 0,
  200. shift_y: 0,
  201. };
  202. this.stave.setText(text, position, options);
  203. }
  204. public addWordRepetition(repetitionInstruction: RepetitionInstructionEnum): void {
  205. let instruction: VexFlowRepetitionType = undefined;
  206. let position: any = Vex.Flow.Modifier.Position.END;
  207. switch (repetitionInstruction) {
  208. case RepetitionInstructionEnum.Segno:
  209. // create Segno Symbol:
  210. instruction = VexFlowRepetitionType.SEGNO_LEFT;
  211. position = Vex.Flow.Modifier.Position.BEGIN;
  212. break;
  213. case RepetitionInstructionEnum.Coda:
  214. // create Coda Symbol:
  215. instruction = VexFlowRepetitionType.CODA_LEFT;
  216. position = Vex.Flow.Modifier.Position.BEGIN;
  217. break;
  218. case RepetitionInstructionEnum.DaCapo:
  219. instruction = VexFlowRepetitionType.DC;
  220. break;
  221. case RepetitionInstructionEnum.DalSegno:
  222. instruction = VexFlowRepetitionType.DS;
  223. break;
  224. case RepetitionInstructionEnum.Fine:
  225. instruction = VexFlowRepetitionType.FINE;
  226. break;
  227. case RepetitionInstructionEnum.ToCoda:
  228. //instruction = "To Coda";
  229. break;
  230. case RepetitionInstructionEnum.DaCapoAlFine:
  231. instruction = VexFlowRepetitionType.DC_AL_FINE;
  232. break;
  233. case RepetitionInstructionEnum.DaCapoAlCoda:
  234. instruction = VexFlowRepetitionType.DC_AL_CODA;
  235. break;
  236. case RepetitionInstructionEnum.DalSegnoAlFine:
  237. instruction = VexFlowRepetitionType.DS_AL_FINE;
  238. break;
  239. case RepetitionInstructionEnum.DalSegnoAlCoda:
  240. instruction = VexFlowRepetitionType.DS_AL_CODA;
  241. break;
  242. default:
  243. break;
  244. }
  245. if (instruction !== undefined) {
  246. this.stave.addModifier(new Vex.Flow.Repetition(instruction, 0, 0), position);
  247. }
  248. }
  249. /**
  250. * Sets the overall x-width of the measure.
  251. * @param width
  252. */
  253. public setWidth(width: number): void {
  254. super.setWidth(width);
  255. // Set the width of the Vex.Flow.Stave
  256. this.stave.setWidth(width * unitInPixels);
  257. // Force the width of the Begin Instructions
  258. //this.stave.setNoteStartX(this.beginInstructionsWidth * UnitInPixels);
  259. }
  260. /**
  261. * This method is called after the StaffEntriesScaleFactor has been set.
  262. * Here the final x-positions of the staff entries have to be set.
  263. * (multiply the minimal positions with the scaling factor, considering the BeginInstructionsWidth)
  264. */
  265. public layoutSymbols(): void {
  266. // vexflow does the x-layout
  267. }
  268. /**
  269. * Draw this measure on a VexFlow CanvasContext
  270. * @param ctx
  271. */
  272. public draw(ctx: Vex.Flow.RenderContext): void {
  273. // Draw stave lines
  274. this.stave.setContext(ctx).draw();
  275. // Draw all voices
  276. for (const voiceID in this.vfVoices) {
  277. if (this.vfVoices.hasOwnProperty(voiceID)) {
  278. this.vfVoices[voiceID].draw(ctx, this.stave);
  279. }
  280. }
  281. // Draw beams
  282. for (const voiceID in this.vfbeams) {
  283. if (this.vfbeams.hasOwnProperty(voiceID)) {
  284. for (const beam of this.vfbeams[voiceID]) {
  285. beam.setContext(ctx).draw();
  286. }
  287. }
  288. }
  289. // Draw tuplets
  290. for (const voiceID in this.vftuplets) {
  291. if (this.vftuplets.hasOwnProperty(voiceID)) {
  292. for (const tuplet of this.vftuplets[voiceID]) {
  293. tuplet.setContext(ctx).draw();
  294. }
  295. }
  296. }
  297. // Draw ties
  298. for (const tie of this.vfTies) {
  299. tie.setContext(ctx).draw();
  300. }
  301. // Draw vertical lines
  302. for (const connector of this.connectors) {
  303. connector.setContext(ctx).draw();
  304. }
  305. }
  306. public format(): void {
  307. // If this is the first stave in the vertical measure, call the format
  308. // method to set the width of all the voices
  309. if (this.formatVoices) {
  310. // The width of the voices does not include the instructions (StaveModifiers)
  311. this.formatVoices((this.PositionAndShape.BorderRight - this.beginInstructionsWidth - this.endInstructionsWidth) * unitInPixels);
  312. }
  313. // Force the width of the Begin Instructions
  314. this.stave.setNoteStartX(this.stave.getX() + unitInPixels * this.beginInstructionsWidth);
  315. }
  316. /**
  317. * Returns all the voices that are present in this measure
  318. */
  319. public getVoicesWithinMeasure(): Voice[] {
  320. const voices: Voice[] = [];
  321. for (const gse of this.staffEntries) {
  322. for (const gve of gse.graphicalVoiceEntries) {
  323. if (voices.indexOf(gve.parentVoiceEntry.ParentVoice) === -1) {
  324. voices.push(gve.parentVoiceEntry.ParentVoice);
  325. }
  326. }
  327. }
  328. return voices;
  329. }
  330. /**
  331. * Returns all the graphicalVoiceEntries of a given Voice.
  332. * @param voice the voice for which the graphicalVoiceEntries shall be returned.
  333. */
  334. public getGraphicalVoiceEntriesPerVoice(voice: Voice): GraphicalVoiceEntry[] {
  335. const voiceEntries: GraphicalVoiceEntry[] = [];
  336. for (const gse of this.staffEntries) {
  337. for (const gve of gse.graphicalVoiceEntries) {
  338. if (gve.parentVoiceEntry.ParentVoice === voice) {
  339. voiceEntries.push(gve);
  340. }
  341. }
  342. }
  343. return voiceEntries;
  344. }
  345. /**
  346. * Finds the gaps between the existing notes within a measure.
  347. * Problem here is, that the graphicalVoiceEntry does not exist yet and
  348. * that Tied notes are not present in the normal voiceEntries.
  349. * To handle this, calculation with absolute timestamps is needed.
  350. * And the graphical notes have to be analysed directly (and not the voiceEntries, as it actually should be -> needs refactoring)
  351. * @param voice the voice for which the ghost notes shall be searched.
  352. */
  353. private getRestFilledVexFlowStaveNotesPerVoice(voice: Voice): GraphicalVoiceEntry[] {
  354. let latestVoiceTimestamp: Fraction = undefined;
  355. const gvEntries: GraphicalVoiceEntry[] = this.getGraphicalVoiceEntriesPerVoice(voice);
  356. for (let idx: number = 0, len: number = gvEntries.length; idx < len; ++idx) {
  357. const gve: GraphicalVoiceEntry = gvEntries[idx];
  358. const gNotesStartTimestamp: Fraction = gve.notes[0].sourceNote.getAbsoluteTimestamp();
  359. // find the voiceEntry end timestamp:
  360. let gNotesEndTimestamp: Fraction = new Fraction();
  361. for (const graphicalNote of gve.notes) {
  362. const noteEnd: Fraction = Fraction.plus(graphicalNote.sourceNote.getAbsoluteTimestamp(), graphicalNote.sourceNote.Length);
  363. if (gNotesEndTimestamp < noteEnd) {
  364. gNotesEndTimestamp = noteEnd;
  365. }
  366. }
  367. // check if this voice has just been found the first time:
  368. if (latestVoiceTimestamp === undefined) {
  369. // if this voice is new, check for a gap from measure start to the start of the current voice entry:
  370. const gapFromMeasureStart: Fraction = Fraction.minus(gNotesStartTimestamp, this.parentSourceMeasure.AbsoluteTimestamp);
  371. if (gapFromMeasureStart.RealValue > 0) {
  372. log.debug("Ghost Found at start");
  373. const vfghost: Vex.Flow.GhostNote = VexFlowConverter.GhostNote(gapFromMeasureStart);
  374. const ghostGve: VexFlowVoiceEntry = new VexFlowVoiceEntry(undefined, undefined);
  375. ghostGve.vfStaveNote = vfghost;
  376. gvEntries.splice(0, 0, ghostGve);
  377. idx++;
  378. }
  379. } else {
  380. // get the length of the empty space between notes:
  381. const inBetweenLength: Fraction = Fraction.minus(gNotesStartTimestamp, latestVoiceTimestamp);
  382. if (inBetweenLength.RealValue > 0) {
  383. log.debug("Ghost Found in between");
  384. const vfghost: Vex.Flow.GhostNote = VexFlowConverter.GhostNote(inBetweenLength);
  385. const ghostGve: VexFlowVoiceEntry = new VexFlowVoiceEntry(undefined, undefined);
  386. ghostGve.vfStaveNote = vfghost;
  387. // add element before current element:
  388. gvEntries.splice(idx, 0, ghostGve);
  389. // and increase index, as we added an element:
  390. idx++;
  391. }
  392. }
  393. // finally set the latest timestamp of this voice to the end timestamp of the longest note in the current voiceEntry:
  394. latestVoiceTimestamp = gNotesEndTimestamp;
  395. }
  396. const measureEndTimestamp: Fraction = Fraction.plus(this.parentSourceMeasure.AbsoluteTimestamp, this.parentSourceMeasure.Duration);
  397. const restLength: Fraction = Fraction.minus(measureEndTimestamp, latestVoiceTimestamp);
  398. if (restLength.RealValue > 0) {
  399. // fill the gap with a rest ghost note
  400. // starting from lastFraction
  401. // with length restLength:
  402. log.debug("Ghost Found at end");
  403. const vfghost: Vex.Flow.GhostNote = VexFlowConverter.GhostNote(restLength);
  404. const ghostGve: VexFlowVoiceEntry = new VexFlowVoiceEntry(undefined, undefined);
  405. ghostGve.vfStaveNote = vfghost;
  406. gvEntries.push(ghostGve);
  407. }
  408. return gvEntries;
  409. }
  410. /**
  411. * Add a note to a beam
  412. * @param graphicalNote
  413. * @param beam
  414. */
  415. public handleBeam(graphicalNote: GraphicalNote, beam: Beam): void {
  416. const voiceID: number = graphicalNote.sourceNote.ParentVoiceEntry.ParentVoice.VoiceId;
  417. let beams: [Beam, VexFlowVoiceEntry[]][] = this.beams[voiceID];
  418. if (beams === undefined) {
  419. beams = this.beams[voiceID] = [];
  420. }
  421. let data: [Beam, VexFlowVoiceEntry[]];
  422. for (const mybeam of beams) {
  423. if (mybeam[0] === beam) {
  424. data = mybeam;
  425. }
  426. }
  427. if (data === undefined) {
  428. data = [beam, []];
  429. beams.push(data);
  430. }
  431. const parent: VexFlowVoiceEntry = graphicalNote.parentVoiceEntry as VexFlowVoiceEntry;
  432. if (data[1].indexOf(parent) < 0) {
  433. data[1].push(parent);
  434. }
  435. }
  436. public handleTuplet(graphicalNote: GraphicalNote, tuplet: Tuplet): void {
  437. const voiceID: number = graphicalNote.sourceNote.ParentVoiceEntry.ParentVoice.VoiceId;
  438. tuplet = graphicalNote.sourceNote.NoteTuplet;
  439. let tuplets: [Tuplet, VexFlowVoiceEntry[]][] = this.tuplets[voiceID];
  440. if (tuplets === undefined) {
  441. tuplets = this.tuplets[voiceID] = [];
  442. }
  443. let currentTupletBuilder: [Tuplet, VexFlowVoiceEntry[]];
  444. for (const t of tuplets) {
  445. if (t[0] === tuplet) {
  446. currentTupletBuilder = t;
  447. }
  448. }
  449. if (currentTupletBuilder === undefined) {
  450. currentTupletBuilder = [tuplet, []];
  451. tuplets.push(currentTupletBuilder);
  452. }
  453. const parent: VexFlowVoiceEntry = graphicalNote.parentVoiceEntry as VexFlowVoiceEntry;
  454. if (currentTupletBuilder[1].indexOf(parent) < 0) {
  455. currentTupletBuilder[1].push(parent);
  456. }
  457. }
  458. /**
  459. * Complete the creation of VexFlow Beams in this measure
  460. */
  461. public finalizeBeams(): void {
  462. // The following line resets the created Vex.Flow Beams and
  463. // created them brand new. Is this needed? And more importantly,
  464. // should the old beams be removed manually by the notes?
  465. this.vfbeams = {};
  466. for (const voiceID in this.beams) {
  467. if (this.beams.hasOwnProperty(voiceID)) {
  468. let vfbeams: Vex.Flow.Beam[] = this.vfbeams[voiceID];
  469. if (vfbeams === undefined) {
  470. vfbeams = this.vfbeams[voiceID] = [];
  471. }
  472. for (const beam of this.beams[voiceID]) {
  473. const notes: Vex.Flow.StaveNote[] = [];
  474. const psBeam: Beam = beam[0];
  475. const voiceEntries: VexFlowVoiceEntry[] = beam[1];
  476. let autoStemBeam: boolean = true;
  477. for (const gve of voiceEntries) {
  478. if (gve.parentVoiceEntry.ParentVoice === psBeam.Notes[0].ParentVoiceEntry.ParentVoice) {
  479. autoStemBeam = gve.parentVoiceEntry.StemDirection === StemDirectionType.Undefined;
  480. }
  481. }
  482. for (const entry of voiceEntries) {
  483. const note: Vex.Flow.StaveNote = ((<VexFlowVoiceEntry>entry).vfStaveNote as StaveNote);
  484. if (note !== undefined) {
  485. notes.push(note);
  486. }
  487. }
  488. if (notes.length > 1) {
  489. const vfBeam: Vex.Flow.Beam = new Vex.Flow.Beam(notes, autoStemBeam);
  490. vfbeams.push(vfBeam);
  491. // just a test for coloring the notes:
  492. // for (let note of notes) {
  493. // (<Vex.Flow.StaveNote> note).setStyle({fillStyle: "green", strokeStyle: "green"});
  494. // }
  495. } else {
  496. log.debug("Warning! Beam with no notes!");
  497. }
  498. }
  499. }
  500. }
  501. }
  502. /**
  503. * Complete the creation of VexFlow Tuplets in this measure
  504. */
  505. public finalizeTuplets(): void {
  506. // The following line resets the created Vex.Flow Tuplets and
  507. // created them brand new. Is this needed? And more importantly,
  508. // should the old tuplets be removed manually from the notes?
  509. this.vftuplets = {};
  510. for (const voiceID in this.tuplets) {
  511. if (this.tuplets.hasOwnProperty(voiceID)) {
  512. let vftuplets: Vex.Flow.Tuplet[] = this.vftuplets[voiceID];
  513. if (vftuplets === undefined) {
  514. vftuplets = this.vftuplets[voiceID] = [];
  515. }
  516. for (const tupletBuilder of this.tuplets[voiceID]) {
  517. const tupletStaveNotes: Vex.Flow.StaveNote[] = [];
  518. const tupletVoiceEntries: VexFlowVoiceEntry[] = tupletBuilder[1];
  519. for (const tupletVoiceEntry of tupletVoiceEntries) {
  520. tupletStaveNotes.push(((tupletVoiceEntry).vfStaveNote as StaveNote));
  521. }
  522. if (tupletStaveNotes.length > 1) {
  523. const notesOccupied: number = 2;
  524. vftuplets.push(new Vex.Flow.Tuplet( tupletStaveNotes,
  525. {
  526. notes_occupied: notesOccupied,
  527. num_notes: tupletStaveNotes.length //, location: -1, ratioed: true
  528. }));
  529. } else {
  530. log.debug("Warning! Tuplet with no notes! Trying to ignore, but this is a serious problem.");
  531. }
  532. }
  533. }
  534. }
  535. }
  536. public layoutStaffEntry(graphicalStaffEntry: GraphicalStaffEntry): void {
  537. return;
  538. }
  539. public staffMeasureCreatedCalculations(): void {
  540. for (const graphicalStaffEntry of this.staffEntries as VexFlowStaffEntry[]) {
  541. // create vex flow Stave Notes:
  542. for (const gve of graphicalStaffEntry.graphicalVoiceEntries) {
  543. (gve as VexFlowVoiceEntry).vfStaveNote = VexFlowConverter.StaveNote(gve);
  544. }
  545. }
  546. this.finalizeBeams();
  547. this.finalizeTuplets();
  548. const voices: Voice[] = this.getVoicesWithinMeasure();
  549. for (const voice of voices) {
  550. // add a vexFlow voice for this voice:
  551. this.vfVoices[voice.VoiceId] = new Vex.Flow.Voice({
  552. beat_value: this.parentSourceMeasure.Duration.Denominator,
  553. num_beats: this.parentSourceMeasure.Duration.Numerator,
  554. resolution: Vex.Flow.RESOLUTION,
  555. }).setMode(Vex.Flow.Voice.Mode.SOFT);
  556. const restFilledEntries: GraphicalVoiceEntry[] = this.getRestFilledVexFlowStaveNotesPerVoice(voice);
  557. // create vex flow voices and add tickables to it:
  558. for (const voiceEntry of restFilledEntries) {
  559. this.vfVoices[voice.VoiceId].addTickable((voiceEntry as VexFlowVoiceEntry).vfStaveNote);
  560. }
  561. }
  562. this.createArticulations();
  563. if (this.instantaniousDynamics.length > 0) {
  564. this.createInstantDynamics();
  565. }
  566. }
  567. private createInstantDynamics(): void {
  568. }
  569. private createArticulations(): void {
  570. for (let idx: number = 0, len: number = this.staffEntries.length; idx < len; ++idx) {
  571. const graphicalStaffEntry: VexFlowStaffEntry = (this.staffEntries[idx] as VexFlowStaffEntry);
  572. // create vex flow articulation:
  573. const graphicalVoiceEntries: GraphicalVoiceEntry[] = graphicalStaffEntry.graphicalVoiceEntries;
  574. for (const gve of graphicalVoiceEntries) {
  575. const vfStaveNote: StaveNote = ((gve as VexFlowVoiceEntry).vfStaveNote as StaveNote);
  576. VexFlowConverter.generateArticulations(vfStaveNote, gve.notes[0].sourceNote.ParentVoiceEntry.Articulations);
  577. }
  578. }
  579. }
  580. /**
  581. * Creates a line from 'top' to this measure, of type 'lineType'
  582. * @param top
  583. * @param lineType
  584. */
  585. public lineTo(top: VexFlowMeasure, lineType: any): void {
  586. const connector: StaveConnector = new Vex.Flow.StaveConnector(top.getVFStave(), this.stave);
  587. connector.setType(lineType);
  588. this.connectors.push(connector);
  589. }
  590. /**
  591. * Return the VexFlow Stave corresponding to this StaffMeasure
  592. * @returns {Vex.Flow.Stave}
  593. */
  594. public getVFStave(): Vex.Flow.Stave {
  595. return this.stave;
  596. }
  597. /**
  598. * After re-running the formatting on the VexFlow Stave, update the
  599. * space needed by Instructions (in VexFlow: StaveModifiers)
  600. */
  601. private updateInstructionWidth(): void {
  602. let beginInstructionsWidth: number = 0;
  603. let endInstructionsWidth: number = 0;
  604. const modifiers: Vex.Flow.StaveModifier[] = this.stave.getModifiers();
  605. for (const mod of modifiers) {
  606. if (mod.getPosition() === Vex.Flow.StaveModifier.Position.BEGIN) {
  607. beginInstructionsWidth += mod.getWidth() + mod.getPadding(undefined);
  608. } else if (mod.getPosition() === Vex.Flow.StaveModifier.Position.END) {
  609. endInstructionsWidth += mod.getWidth() + mod.getPadding(undefined);
  610. }
  611. }
  612. this.beginInstructionsWidth = beginInstructionsWidth / unitInPixels;
  613. this.endInstructionsWidth = endInstructionsWidth / unitInPixels;
  614. }
  615. }