VexFlowMeasure.ts 51 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082
  1. import Vex = require("vexflow");
  2. import {GraphicalMeasure} from "../GraphicalMeasure";
  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 StemmableNote = Vex.Flow.StemmableNote;
  18. import NoteSubGroup = Vex.Flow.NoteSubGroup;
  19. import * as log from "loglevel";
  20. import {unitInPixels} from "./VexFlowMusicSheetDrawer";
  21. import {Tuplet} from "../../VoiceData/Tuplet";
  22. import {RepetitionInstructionEnum, RepetitionInstruction, AlignmentType} from "../../VoiceData/Instructions/RepetitionInstruction";
  23. import {SystemLinePosition} from "../SystemLinePosition";
  24. import {StemDirectionType} from "../../VoiceData/VoiceEntry";
  25. import {GraphicalVoiceEntry} from "../GraphicalVoiceEntry";
  26. import {VexFlowVoiceEntry} from "./VexFlowVoiceEntry";
  27. import {Fraction} from "../../../Common/DataObjects/Fraction";
  28. import {Voice} from "../../VoiceData/Voice";
  29. import {LinkedVoice} from "../../VoiceData/LinkedVoice";
  30. import {EngravingRules} from "../EngravingRules";
  31. import {OrnamentContainer} from "../../VoiceData/OrnamentContainer";
  32. import {TechnicalInstruction} from "../../VoiceData/Instructions/TechnicalInstruction";
  33. import {PlacementEnum} from "../../VoiceData/Expressions/AbstractExpression";
  34. import {VexFlowGraphicalNote} from "./VexFlowGraphicalNote";
  35. import {AutoBeamOptions} from "../../../OpenSheetMusicDisplay/OSMDOptions";
  36. import {NoteType} from "../../VoiceData";
  37. export class VexFlowMeasure extends GraphicalMeasure {
  38. constructor(staff: Staff, staffLine: StaffLine = undefined, sourceMeasure: SourceMeasure = undefined) {
  39. super(staff, sourceMeasure, staffLine);
  40. this.minimumStaffEntriesWidth = -1;
  41. this.resetLayout();
  42. }
  43. /** octaveOffset according to active clef */
  44. public octaveOffset: number = 3;
  45. /** The VexFlow Voices in the measure */
  46. public vfVoices: { [voiceID: number]: Vex.Flow.Voice; } = {};
  47. /** Call this function (if present) to x-format all the voices in the measure */
  48. public formatVoices: (width: number) => void;
  49. /** The VexFlow Ties in the measure */
  50. public vfTies: Vex.Flow.StaveTie[] = [];
  51. /** The repetition instructions given as words or symbols (coda, dal segno..) */
  52. public vfRepetitionWords: Vex.Flow.Repetition[] = [];
  53. /** The VexFlow Stave (= one measure in a staffline) */
  54. private stave: Vex.Flow.Stave;
  55. /** VexFlow StaveConnectors (vertical lines) */
  56. private connectors: Vex.Flow.StaveConnector[] = [];
  57. /** Intermediate object to construct beams */
  58. private beams: { [voiceID: number]: [Beam, VexFlowVoiceEntry[]][]; } = {};
  59. /** Beams created by (optional) autoBeam function. */
  60. private autoVfBeams: Vex.Flow.Beam[];
  61. /** Beams of tuplet notes created by (optional) autoBeam function. */
  62. private autoTupletVfBeams: Vex.Flow.Beam[];
  63. /** VexFlow Beams */
  64. private vfbeams: { [voiceID: number]: Vex.Flow.Beam[]; };
  65. /** Intermediate object to construct tuplets */
  66. private tuplets: { [voiceID: number]: [Tuplet, VexFlowVoiceEntry[]][]; } = {};
  67. /** VexFlow Tuplets */
  68. private vftuplets: { [voiceID: number]: Vex.Flow.Tuplet[]; } = {};
  69. // Sets the absolute coordinates of the VFStave on the canvas
  70. public setAbsoluteCoordinates(x: number, y: number): void {
  71. this.stave.setX(x).setY(y);
  72. }
  73. /**
  74. * Reset all the geometric values and parameters of this measure and put it in an initialized state.
  75. * This is needed to evaluate a measure a second time by system builder.
  76. */
  77. public resetLayout(): void {
  78. // Take into account some space for the begin and end lines of the stave
  79. // Will be changed when repetitions will be implemented
  80. //this.beginInstructionsWidth = 20 / UnitInPixels;
  81. //this.endInstructionsWidth = 20 / UnitInPixels;
  82. this.stave = new Vex.Flow.Stave(0, 0, 0, {
  83. space_above_staff_ln: 0,
  84. space_below_staff_ln: 0,
  85. });
  86. this.updateInstructionWidth();
  87. }
  88. public clean(): void {
  89. this.vfTies.length = 0;
  90. this.connectors = [];
  91. // Clean up instructions
  92. this.resetLayout();
  93. }
  94. /**
  95. * returns the x-width (in units) of a given measure line {SystemLinesEnum}.
  96. * @param line
  97. * @returns the x-width in osmd units
  98. */
  99. public getLineWidth(line: SystemLinesEnum): number {
  100. switch (line) {
  101. // return 0 for the normal lines, as the line width will be considered at the updateInstructionWidth() method using the stavemodifiers.
  102. // case SystemLinesEnum.SingleThin:
  103. // return 5.0 / unitInPixels;
  104. // case SystemLinesEnum.DoubleThin:
  105. // return 5.0 / unitInPixels;
  106. // case SystemLinesEnum.ThinBold:
  107. // return 5.0 / unitInPixels;
  108. // but just add a little extra space for repetitions (cosmetics):
  109. case SystemLinesEnum.BoldThinDots:
  110. case SystemLinesEnum.DotsThinBold:
  111. return 10.0 / unitInPixels;
  112. case SystemLinesEnum.DotsBoldBoldDots:
  113. return 10.0 / unitInPixels;
  114. default:
  115. return 0;
  116. }
  117. }
  118. /**
  119. * adds the given clef to the begin of the measure.
  120. * This has to update/increase BeginInstructionsWidth.
  121. * @param clef
  122. */
  123. public addClefAtBegin(clef: ClefInstruction): void {
  124. this.octaveOffset = clef.OctaveOffset;
  125. const vfclef: { type: string, size: string, annotation: string } = VexFlowConverter.Clef(clef, "default");
  126. this.stave.addClef(vfclef.type, vfclef.size, vfclef.annotation, Vex.Flow.StaveModifier.Position.BEGIN);
  127. this.updateInstructionWidth();
  128. }
  129. /**
  130. * adds the given key to the begin of the measure.
  131. * This has to update/increase BeginInstructionsWidth.
  132. * @param currentKey the new valid key.
  133. * @param previousKey the old cancelled key. Needed to show which accidentals are not valid any more.
  134. * @param currentClef the valid clef. Needed to put the accidentals on the right y-positions.
  135. */
  136. public addKeyAtBegin(currentKey: KeyInstruction, previousKey: KeyInstruction, currentClef: ClefInstruction): void {
  137. this.stave.setKeySignature(
  138. VexFlowConverter.keySignature(currentKey),
  139. VexFlowConverter.keySignature(previousKey),
  140. undefined
  141. );
  142. this.updateInstructionWidth();
  143. }
  144. /**
  145. * adds the given rhythm to the begin of the measure.
  146. * This has to update/increase BeginInstructionsWidth.
  147. * @param rhythm
  148. */
  149. public addRhythmAtBegin(rhythm: RhythmInstruction): void {
  150. const timeSig: Vex.Flow.TimeSignature = VexFlowConverter.TimeSignature(rhythm);
  151. this.stave.addModifier(
  152. timeSig,
  153. Vex.Flow.StaveModifier.Position.BEGIN
  154. );
  155. this.updateInstructionWidth();
  156. }
  157. /**
  158. * adds the given clef to the end of the measure.
  159. * This has to update/increase EndInstructionsWidth.
  160. * @param clef
  161. */
  162. public addClefAtEnd(clef: ClefInstruction): void {
  163. const vfclef: { type: string, size: string, annotation: string } = VexFlowConverter.Clef(clef, "small");
  164. this.stave.setEndClef(vfclef.type, vfclef.size, vfclef.annotation);
  165. this.updateInstructionWidth();
  166. }
  167. public addMeasureLine(lineType: SystemLinesEnum, linePosition: SystemLinePosition): void {
  168. switch (linePosition) {
  169. case SystemLinePosition.MeasureBegin:
  170. switch (lineType) {
  171. case SystemLinesEnum.BoldThinDots:
  172. this.stave.setBegBarType(Vex.Flow.Barline.type.REPEAT_BEGIN);
  173. break;
  174. default:
  175. break;
  176. }
  177. break;
  178. case SystemLinePosition.MeasureEnd:
  179. switch (lineType) {
  180. case SystemLinesEnum.DotsBoldBoldDots:
  181. this.stave.setEndBarType(Vex.Flow.Barline.type.REPEAT_BOTH);
  182. break;
  183. case SystemLinesEnum.DotsThinBold:
  184. this.stave.setEndBarType(Vex.Flow.Barline.type.REPEAT_END);
  185. break;
  186. case SystemLinesEnum.DoubleThin:
  187. this.stave.setEndBarType(Vex.Flow.Barline.type.DOUBLE);
  188. break;
  189. case SystemLinesEnum.ThinBold:
  190. this.stave.setEndBarType(Vex.Flow.Barline.type.END);
  191. break;
  192. case SystemLinesEnum.None:
  193. this.stave.setEndBarType(Vex.Flow.Barline.type.NONE);
  194. break;
  195. // TODO: Add support for additional Barline types when VexFlow supports them
  196. default:
  197. break;
  198. }
  199. break;
  200. default:
  201. break;
  202. }
  203. }
  204. /**
  205. * Adds a measure number to the top left corner of the measure
  206. * This method is not used currently in favor of the calculateMeasureNumberPlacement
  207. * method in the MusicSheetCalculator.ts
  208. */
  209. public addMeasureNumber(): void {
  210. const text: string = this.MeasureNumber.toString();
  211. const position: number = StavePositionEnum.ABOVE; //Vex.Flow.StaveModifier.Position.ABOVE;
  212. const options: any = {
  213. justification: 1,
  214. shift_x: 0,
  215. shift_y: 0,
  216. };
  217. this.stave.setText(text, position, options);
  218. }
  219. public addWordRepetition(repetitionInstruction: RepetitionInstruction): void {
  220. let instruction: Vex.Flow.Repetition.type = undefined;
  221. let position: any = Vex.Flow.StaveModifier.Position.END;
  222. switch (repetitionInstruction.type) {
  223. case RepetitionInstructionEnum.Segno:
  224. // create Segno Symbol:
  225. instruction = Vex.Flow.Repetition.type.SEGNO_LEFT;
  226. position = Vex.Flow.StaveModifier.Position.BEGIN;
  227. break;
  228. case RepetitionInstructionEnum.Coda:
  229. // create Coda Symbol:
  230. instruction = Vex.Flow.Repetition.type.CODA_LEFT;
  231. position = Vex.Flow.StaveModifier.Position.BEGIN;
  232. break;
  233. case RepetitionInstructionEnum.DaCapo:
  234. instruction = Vex.Flow.Repetition.type.DC;
  235. break;
  236. case RepetitionInstructionEnum.DalSegno:
  237. instruction = Vex.Flow.Repetition.type.DS;
  238. break;
  239. case RepetitionInstructionEnum.Fine:
  240. instruction = Vex.Flow.Repetition.type.FINE;
  241. break;
  242. case RepetitionInstructionEnum.ToCoda:
  243. //instruction = "To Coda";
  244. break;
  245. case RepetitionInstructionEnum.DaCapoAlFine:
  246. instruction = Vex.Flow.Repetition.type.DC_AL_FINE;
  247. break;
  248. case RepetitionInstructionEnum.DaCapoAlCoda:
  249. instruction = Vex.Flow.Repetition.type.DC_AL_CODA;
  250. break;
  251. case RepetitionInstructionEnum.DalSegnoAlFine:
  252. instruction = Vex.Flow.Repetition.type.DS_AL_FINE;
  253. break;
  254. case RepetitionInstructionEnum.DalSegnoAlCoda:
  255. instruction = Vex.Flow.Repetition.type.DS_AL_CODA;
  256. break;
  257. default:
  258. break;
  259. }
  260. if (instruction !== undefined) {
  261. this.stave.addModifier(new Vex.Flow.Repetition(instruction, 0, 0), position);
  262. return;
  263. }
  264. this.addVolta(repetitionInstruction);
  265. }
  266. private addVolta(repetitionInstruction: RepetitionInstruction): void {
  267. let voltaType: number = Vex.Flow.Volta.type.BEGIN;
  268. if (repetitionInstruction.type === RepetitionInstructionEnum.Ending) {
  269. switch (repetitionInstruction.alignment) {
  270. case AlignmentType.Begin:
  271. if (this.parentSourceMeasure.endsRepetitionEnding()) {
  272. voltaType = Vex.Flow.Volta.type.BEGIN_END;
  273. } else {
  274. voltaType = Vex.Flow.Volta.type.BEGIN;
  275. }
  276. break;
  277. case AlignmentType.End:
  278. if (this.parentSourceMeasure.beginsRepetitionEnding()) {
  279. //voltaType = Vex.Flow.Volta.type.BEGIN_END;
  280. // don't add BEGIN_END volta a second time:
  281. return;
  282. } else {
  283. voltaType = Vex.Flow.Volta.type.END;
  284. }
  285. break;
  286. default:
  287. break;
  288. }
  289. this.stave.setVoltaType(voltaType, repetitionInstruction.endingIndices[0], 0);
  290. }
  291. }
  292. /**
  293. * Sets the overall x-width of the measure.
  294. * @param width
  295. */
  296. public setWidth(width: number): void {
  297. super.setWidth(width);
  298. // Set the width of the Vex.Flow.Stave
  299. this.stave.setWidth(width * unitInPixels);
  300. // Force the width of the Begin Instructions
  301. //this.stave.setNoteStartX(this.beginInstructionsWidth * UnitInPixels);
  302. }
  303. /**
  304. * This method is called after the StaffEntriesScaleFactor has been set.
  305. * Here the final x-positions of the staff entries have to be set.
  306. * (multiply the minimal positions with the scaling factor, considering the BeginInstructionsWidth)
  307. */
  308. public layoutSymbols(): void {
  309. // vexflow does the x-layout
  310. }
  311. /**
  312. * Draw this measure on a VexFlow CanvasContext
  313. * @param ctx
  314. */
  315. public draw(ctx: Vex.IRenderContext): void {
  316. // Draw stave lines
  317. this.stave.setContext(ctx).draw();
  318. // Draw all voices
  319. for (const voiceID in this.vfVoices) {
  320. if (this.vfVoices.hasOwnProperty(voiceID)) {
  321. ctx.save();
  322. this.vfVoices[voiceID].draw(ctx, this.stave);
  323. ctx.restore();
  324. // this.vfVoices[voiceID].tickables.forEach(t => t.getBoundingBox().draw(ctx));
  325. // this.vfVoices[voiceID].tickables.forEach(t => t.getBoundingBox().draw(ctx));
  326. }
  327. }
  328. // Draw beams
  329. for (const voiceID in this.vfbeams) {
  330. if (this.vfbeams.hasOwnProperty(voiceID)) {
  331. for (const beam of this.vfbeams[voiceID]) {
  332. beam.setContext(ctx).draw();
  333. }
  334. }
  335. }
  336. // Draw auto-generated beams from Beam.generateBeams()
  337. if (this.autoVfBeams) {
  338. for (const beam of this.autoVfBeams) {
  339. beam.setContext(ctx).draw();
  340. }
  341. }
  342. if (this.autoTupletVfBeams) {
  343. for (const beam of this.autoTupletVfBeams) {
  344. beam.setContext(ctx).draw();
  345. }
  346. }
  347. // Draw tuplets
  348. for (const voiceID in this.vftuplets) {
  349. if (this.vftuplets.hasOwnProperty(voiceID)) {
  350. for (const tuplet of this.vftuplets[voiceID]) {
  351. tuplet.setContext(ctx).draw();
  352. }
  353. }
  354. }
  355. // Draw ties
  356. for (const tie of this.vfTies) {
  357. tie.setContext(ctx).draw();
  358. }
  359. // Draw vertical lines
  360. for (const connector of this.connectors) {
  361. connector.setContext(ctx).draw();
  362. }
  363. }
  364. // this currently formats multiple measures, see VexFlowMusicSheetCalculator.formatMeasures()
  365. public format(): void {
  366. // If this is the first stave in the vertical measure, call the format
  367. // method to set the width of all the voices
  368. if (this.formatVoices) {
  369. // set the width of the voices to the current measure width:
  370. // (The width of the voices does not include the instructions (StaveModifiers))
  371. this.formatVoices((this.PositionAndShape.Size.width - this.beginInstructionsWidth - this.endInstructionsWidth) * unitInPixels);
  372. }
  373. }
  374. /**
  375. * Returns all the voices that are present in this measure
  376. */
  377. public getVoicesWithinMeasure(): Voice[] {
  378. const voices: Voice[] = [];
  379. for (const gse of this.staffEntries) {
  380. for (const gve of gse.graphicalVoiceEntries) {
  381. if (voices.indexOf(gve.parentVoiceEntry.ParentVoice) === -1) {
  382. voices.push(gve.parentVoiceEntry.ParentVoice);
  383. }
  384. }
  385. }
  386. return voices;
  387. }
  388. /**
  389. * Returns all the graphicalVoiceEntries of a given Voice.
  390. * @param voice the voice for which the graphicalVoiceEntries shall be returned.
  391. */
  392. public getGraphicalVoiceEntriesPerVoice(voice: Voice): GraphicalVoiceEntry[] {
  393. const voiceEntries: GraphicalVoiceEntry[] = [];
  394. for (const gse of this.staffEntries) {
  395. for (const gve of gse.graphicalVoiceEntries) {
  396. if (gve.parentVoiceEntry.ParentVoice === voice) {
  397. voiceEntries.push(gve);
  398. }
  399. }
  400. }
  401. return voiceEntries;
  402. }
  403. /**
  404. * Finds the gaps between the existing notes within a measure.
  405. * Problem here is, that the graphicalVoiceEntry does not exist yet and
  406. * that Tied notes are not present in the normal voiceEntries.
  407. * To handle this, calculation with absolute timestamps is needed.
  408. * And the graphical notes have to be analysed directly (and not the voiceEntries, as it actually should be -> needs refactoring)
  409. * @param voice the voice for which the ghost notes shall be searched.
  410. */
  411. private getRestFilledVexFlowStaveNotesPerVoice(voice: Voice): GraphicalVoiceEntry[] {
  412. let latestVoiceTimestamp: Fraction = undefined;
  413. const gvEntries: GraphicalVoiceEntry[] = this.getGraphicalVoiceEntriesPerVoice(voice);
  414. for (let idx: number = 0, len: number = gvEntries.length; idx < len; ++idx) {
  415. const gve: GraphicalVoiceEntry = gvEntries[idx];
  416. const gNotesStartTimestamp: Fraction = gve.notes[0].sourceNote.getAbsoluteTimestamp();
  417. // find the voiceEntry end timestamp:
  418. let gNotesEndTimestamp: Fraction = new Fraction();
  419. for (const graphicalNote of gve.notes) {
  420. const noteEnd: Fraction = Fraction.plus(graphicalNote.sourceNote.getAbsoluteTimestamp(), graphicalNote.sourceNote.Length);
  421. if (gNotesEndTimestamp < noteEnd) {
  422. gNotesEndTimestamp = noteEnd;
  423. }
  424. }
  425. // check if this voice has just been found the first time:
  426. if (latestVoiceTimestamp === undefined) {
  427. // if this voice is new, check for a gap from measure start to the start of the current voice entry:
  428. const gapFromMeasureStart: Fraction = Fraction.minus(gNotesStartTimestamp, this.parentSourceMeasure.AbsoluteTimestamp);
  429. if (gapFromMeasureStart.RealValue > 0) {
  430. log.debug("Ghost Found at start");
  431. const vfghost: Vex.Flow.GhostNote = VexFlowConverter.GhostNote(gapFromMeasureStart);
  432. const ghostGve: VexFlowVoiceEntry = new VexFlowVoiceEntry(undefined, undefined);
  433. ghostGve.vfStaveNote = vfghost;
  434. gvEntries.splice(0, 0, ghostGve);
  435. idx++;
  436. }
  437. } else {
  438. // get the length of the empty space between notes:
  439. const inBetweenLength: Fraction = Fraction.minus(gNotesStartTimestamp, latestVoiceTimestamp);
  440. if (inBetweenLength.RealValue > 0) {
  441. log.debug("Ghost Found in between");
  442. const vfghost: Vex.Flow.GhostNote = VexFlowConverter.GhostNote(inBetweenLength);
  443. const ghostGve: VexFlowVoiceEntry = new VexFlowVoiceEntry(undefined, undefined);
  444. ghostGve.vfStaveNote = vfghost;
  445. // add element before current element:
  446. gvEntries.splice(idx, 0, ghostGve);
  447. // and increase index, as we added an element:
  448. idx++;
  449. }
  450. }
  451. // finally set the latest timestamp of this voice to the end timestamp of the longest note in the current voiceEntry:
  452. latestVoiceTimestamp = gNotesEndTimestamp;
  453. }
  454. const measureEndTimestamp: Fraction = Fraction.plus(this.parentSourceMeasure.AbsoluteTimestamp, this.parentSourceMeasure.Duration);
  455. const restLength: Fraction = Fraction.minus(measureEndTimestamp, latestVoiceTimestamp);
  456. if (restLength.RealValue > 0) {
  457. // fill the gap with a rest ghost note
  458. // starting from lastFraction
  459. // with length restLength:
  460. log.debug("Ghost Found at end");
  461. const vfghost: Vex.Flow.GhostNote = VexFlowConverter.GhostNote(restLength);
  462. const ghostGve: VexFlowVoiceEntry = new VexFlowVoiceEntry(undefined, undefined);
  463. ghostGve.vfStaveNote = vfghost;
  464. gvEntries.push(ghostGve);
  465. }
  466. return gvEntries;
  467. }
  468. /**
  469. * Add a note to a beam
  470. * @param graphicalNote
  471. * @param beam
  472. */
  473. public handleBeam(graphicalNote: GraphicalNote, beam: Beam): void {
  474. const voiceID: number = graphicalNote.sourceNote.ParentVoiceEntry.ParentVoice.VoiceId;
  475. let beams: [Beam, VexFlowVoiceEntry[]][] = this.beams[voiceID];
  476. if (beams === undefined) {
  477. beams = this.beams[voiceID] = [];
  478. }
  479. let data: [Beam, VexFlowVoiceEntry[]];
  480. for (const mybeam of beams) {
  481. if (mybeam[0] === beam) {
  482. data = mybeam;
  483. }
  484. }
  485. if (data === undefined) {
  486. data = [beam, []];
  487. beams.push(data);
  488. }
  489. const parent: VexFlowVoiceEntry = graphicalNote.parentVoiceEntry as VexFlowVoiceEntry;
  490. if (data[1].indexOf(parent) < 0) {
  491. data[1].push(parent);
  492. }
  493. }
  494. public handleTuplet(graphicalNote: GraphicalNote, tuplet: Tuplet): void {
  495. const voiceID: number = graphicalNote.sourceNote.ParentVoiceEntry.ParentVoice.VoiceId;
  496. tuplet = graphicalNote.sourceNote.NoteTuplet;
  497. let tuplets: [Tuplet, VexFlowVoiceEntry[]][] = this.tuplets[voiceID];
  498. if (tuplets === undefined) {
  499. tuplets = this.tuplets[voiceID] = [];
  500. }
  501. let currentTupletBuilder: [Tuplet, VexFlowVoiceEntry[]];
  502. for (const t of tuplets) {
  503. if (t[0] === tuplet) {
  504. currentTupletBuilder = t;
  505. }
  506. }
  507. if (currentTupletBuilder === undefined) {
  508. currentTupletBuilder = [tuplet, []];
  509. tuplets.push(currentTupletBuilder);
  510. }
  511. const parent: VexFlowVoiceEntry = graphicalNote.parentVoiceEntry as VexFlowVoiceEntry;
  512. if (currentTupletBuilder[1].indexOf(parent) < 0) {
  513. currentTupletBuilder[1].push(parent);
  514. }
  515. }
  516. /**
  517. * Complete the creation of VexFlow Beams in this measure
  518. */
  519. public finalizeBeams(): void {
  520. // The following line resets the created Vex.Flow Beams and
  521. // created them brand new. Is this needed? And more importantly,
  522. // should the old beams be removed manually by the notes?
  523. this.vfbeams = {};
  524. const beamedNotes: StaveNote[] = []; // already beamed notes, will be ignored by this.autoBeamNotes()
  525. for (const voiceID in this.beams) {
  526. if (this.beams.hasOwnProperty(voiceID)) {
  527. let vfbeams: Vex.Flow.Beam[] = this.vfbeams[voiceID];
  528. if (vfbeams === undefined) {
  529. vfbeams = this.vfbeams[voiceID] = [];
  530. }
  531. for (const beam of this.beams[voiceID]) {
  532. let beamHasQuarterNoteOrLonger: boolean = false;
  533. for (const note of beam[0].Notes) {
  534. if (note.Length.RealValue >= new Fraction(1, 4).RealValue
  535. // check whether the note has a TypeLength that's also not suitable for a beam (bigger than an eigth)
  536. && (note.TypeLength === undefined || note.TypeLength.RealValue > 0.125)) {
  537. beamHasQuarterNoteOrLonger = true;
  538. break;
  539. }
  540. }
  541. if (beamHasQuarterNoteOrLonger) {
  542. log.debug("Beam between note >= quarter, likely tremolo, currently unsupported. continuing.");
  543. continue;
  544. }
  545. const notes: Vex.Flow.StaveNote[] = [];
  546. const psBeam: Beam = beam[0];
  547. const voiceEntries: VexFlowVoiceEntry[] = beam[1];
  548. let autoStemBeam: boolean = true;
  549. for (const gve of voiceEntries) {
  550. if (gve.parentVoiceEntry.ParentVoice === psBeam.Notes[0].ParentVoiceEntry.ParentVoice) {
  551. autoStemBeam = gve.parentVoiceEntry.WantedStemDirection === StemDirectionType.Undefined;
  552. }
  553. }
  554. let isGraceBeam: boolean = false;
  555. let beamColor: string;
  556. const stemColors: string[] = [];
  557. for (const entry of voiceEntries) {
  558. const note: Vex.Flow.StaveNote = ((<VexFlowVoiceEntry>entry).vfStaveNote as StaveNote);
  559. if (note !== undefined) {
  560. notes.push(note);
  561. beamedNotes.push(note);
  562. }
  563. if (entry.parentVoiceEntry.IsGrace) {
  564. isGraceBeam = true;
  565. }
  566. if (entry.parentVoiceEntry.StemColor && EngravingRules.Rules.ColoringEnabled) {
  567. stemColors.push(entry.parentVoiceEntry.StemColor);
  568. }
  569. }
  570. if (notes.length > 1) {
  571. const vfBeam: Vex.Flow.Beam = new Vex.Flow.Beam(notes, autoStemBeam);
  572. if (isGraceBeam) {
  573. // smaller beam, as in Vexflow.GraceNoteGroup.beamNotes()
  574. (<any>vfBeam).render_options.beam_width = 3;
  575. (<any>vfBeam).render_options.partial_beam_length = 4;
  576. }
  577. if (stemColors.length >= 2 && EngravingRules.Rules.ColorBeams) {
  578. beamColor = stemColors[0];
  579. for (const stemColor of stemColors) {
  580. if (stemColor !== beamColor) {
  581. beamColor = undefined;
  582. break;
  583. }
  584. }
  585. vfBeam.setStyle({ fillStyle: beamColor, strokeStyle: beamColor });
  586. }
  587. vfbeams.push(vfBeam);
  588. } else {
  589. log.debug("Warning! Beam with no notes!");
  590. }
  591. }
  592. }
  593. }
  594. if (EngravingRules.Rules.AutoBeamNotes) {
  595. this.autoBeamNotes(beamedNotes); // try to autobeam notes except those that are already beamed (beamedNotes).
  596. }
  597. }
  598. /** Automatically creates beams for notes except beamedNotes, using Vexflow's Beam.generateBeams().
  599. * Takes options from EngravingRules.Rules.AutoBeamOptions.
  600. * @param beamedNotes notes that will not be autobeamed (usually because they are already beamed)
  601. */
  602. private autoBeamNotes(beamedNotes: StemmableNote[]): void {
  603. const notesToAutoBeam: StemmableNote[] = [];
  604. let consecutiveBeamableNotes: StemmableNote[] = [];
  605. let currentTuplet: Tuplet;
  606. let tupletNotesToAutoBeam: StaveNote[] = [];
  607. this.autoTupletVfBeams = [];
  608. for (const staffEntry of this.staffEntries) {
  609. for (const gve of staffEntry.graphicalVoiceEntries) {
  610. const vfStaveNote: StaveNote = <StaveNote> (gve as VexFlowVoiceEntry).vfStaveNote;
  611. if (gve.parentVoiceEntry.IsGrace || // don't beam grace notes
  612. gve.notes[0].graphicalNoteLength.CompareTo(new Fraction(1, 4)) === 1 || // don't beam quarter or longer notes
  613. beamedNotes.contains(vfStaveNote)) { // don't beam already beamed notes
  614. if (consecutiveBeamableNotes.length >= 2) {
  615. // if we already have at least 2 notes to beam, beam them. don't beam notes surrounded by quarter notes etc.
  616. for (const note of consecutiveBeamableNotes) {
  617. notesToAutoBeam.push(note); // "flush" already beamed notes
  618. }
  619. }
  620. consecutiveBeamableNotes = []; // reset notes to beam
  621. continue;
  622. }
  623. // create beams for tuplets separately
  624. const noteTuplet: Tuplet = gve.notes[0].sourceNote.NoteTuplet;
  625. if (noteTuplet) {
  626. // check if there are quarter notes or longer in the tuplet, then don't beam.
  627. // (TODO: check for consecutiveBeamableNotes inside tuplets like for non-tuplet notes above
  628. // e.g quarter eigth eighth -> beam the two eigth notes)
  629. let tupletContainsUnbeamableNote: boolean = false;
  630. for (const notes of noteTuplet.Notes) {
  631. for (const note of notes) {
  632. //const stavenote: StemmableNote = (gve as VexFlowVoiceEntry).vfStaveNote;
  633. //console.log("note " + note.ToString() + ", stavenote type: " + stavenote.getNoteType());
  634. if (note.NoteTypeXml >= NoteType.QUARTER || // quarter note or longer: don't beam
  635. // TODO: don't take Note (head) type from XML, but from current model,
  636. // so that rendering can react dynamically to changes compared to the XML.
  637. // however, taking the note length as fraction is tricky because of tuplets.
  638. // a quarter in a triplet has length < quarter, but quarter note head, which Vexflow can't beam.
  639. note.ParentVoiceEntry.IsGrace ||
  640. note.isRest() && !EngravingRules.Rules.AutoBeamOptions.beam_rests) {
  641. tupletContainsUnbeamableNote = true;
  642. break;
  643. }
  644. }
  645. if (tupletContainsUnbeamableNote) {
  646. break;
  647. }
  648. }
  649. if (currentTuplet === undefined) {
  650. currentTuplet = noteTuplet;
  651. } else {
  652. if (currentTuplet !== noteTuplet) { // new tuplet, finish old one
  653. if (tupletNotesToAutoBeam.length > 1) {
  654. this.autoTupletVfBeams.push(new Vex.Flow.Beam(tupletNotesToAutoBeam, true));
  655. }
  656. tupletNotesToAutoBeam = [];
  657. currentTuplet = noteTuplet;
  658. }
  659. }
  660. if (!tupletContainsUnbeamableNote) {
  661. tupletNotesToAutoBeam.push(vfStaveNote);
  662. }
  663. continue;
  664. } else {
  665. currentTuplet = undefined;
  666. }
  667. consecutiveBeamableNotes.push(vfStaveNote);
  668. }
  669. }
  670. if (tupletNotesToAutoBeam.length >= 2) {
  671. this.autoTupletVfBeams.push(new Vex.Flow.Beam(tupletNotesToAutoBeam, true));
  672. }
  673. if (consecutiveBeamableNotes.length >= 2) {
  674. for (const note of consecutiveBeamableNotes) {
  675. notesToAutoBeam.push(note);
  676. }
  677. }
  678. // create options for generateBeams
  679. const autoBeamOptions: AutoBeamOptions = EngravingRules.Rules.AutoBeamOptions;
  680. const generateBeamOptions: any = {
  681. beam_middle_only: autoBeamOptions.beam_middle_rests_only,
  682. beam_rests: autoBeamOptions.beam_rests,
  683. maintain_stem_directions: autoBeamOptions.maintain_stem_directions,
  684. };
  685. if (autoBeamOptions.groups && autoBeamOptions.groups.length) {
  686. const groups: Vex.Flow.Fraction[] = [];
  687. for (const fraction of autoBeamOptions.groups) {
  688. groups.push(new Vex.Flow.Fraction(fraction[0], fraction[1]));
  689. }
  690. generateBeamOptions.groups = groups;
  691. }
  692. this.autoVfBeams = Vex.Flow.Beam.generateBeams(notesToAutoBeam, generateBeamOptions);
  693. }
  694. /**
  695. * Complete the creation of VexFlow Tuplets in this measure
  696. */
  697. public finalizeTuplets(): void {
  698. // The following line resets the created Vex.Flow Tuplets and
  699. // created them brand new. Is this needed? And more importantly,
  700. // should the old tuplets be removed manually from the notes?
  701. this.vftuplets = {};
  702. for (const voiceID in this.tuplets) {
  703. if (this.tuplets.hasOwnProperty(voiceID)) {
  704. let vftuplets: Vex.Flow.Tuplet[] = this.vftuplets[voiceID];
  705. if (vftuplets === undefined) {
  706. vftuplets = this.vftuplets[voiceID] = [];
  707. }
  708. for (const tupletBuilder of this.tuplets[voiceID]) {
  709. const tupletStaveNotes: Vex.Flow.StaveNote[] = [];
  710. const tupletVoiceEntries: VexFlowVoiceEntry[] = tupletBuilder[1];
  711. for (const tupletVoiceEntry of tupletVoiceEntries) {
  712. tupletStaveNotes.push(((tupletVoiceEntry).vfStaveNote as StaveNote));
  713. }
  714. if (tupletStaveNotes.length > 1) {
  715. const tuplet: Tuplet = tupletBuilder[0];
  716. const notesOccupied: number = tuplet.Notes[0][0].NormalNotes;
  717. const bracketed: boolean = tuplet.Bracket ||
  718. (tuplet.TupletLabelNumber === 3 && EngravingRules.Rules.TripletsBracketed) ||
  719. (tuplet.TupletLabelNumber !== 3 && EngravingRules.Rules.TupletsBracketed);
  720. vftuplets.push(new Vex.Flow.Tuplet( tupletStaveNotes,
  721. {
  722. bracketed: bracketed,
  723. notes_occupied: notesOccupied,
  724. num_notes: tuplet.TupletLabelNumber, //, location: -1, ratioed: true
  725. ratioed: EngravingRules.Rules.TupletsRatioed,
  726. }));
  727. } else {
  728. log.debug("Warning! Tuplet with no notes! Trying to ignore, but this is a serious problem.");
  729. }
  730. }
  731. }
  732. }
  733. }
  734. public layoutStaffEntry(graphicalStaffEntry: GraphicalStaffEntry): void {
  735. return;
  736. }
  737. public graphicalMeasureCreatedCalculations(): void {
  738. let graceSlur: boolean;
  739. let graceGVoiceEntriesBefore: GraphicalVoiceEntry[];
  740. for (const graphicalStaffEntry of this.staffEntries as VexFlowStaffEntry[]) {
  741. graceSlur = false;
  742. graceGVoiceEntriesBefore = [];
  743. // create vex flow Stave Notes:
  744. for (const gve of graphicalStaffEntry.graphicalVoiceEntries) {
  745. if (gve.parentVoiceEntry.IsGrace) {
  746. // save grace notes for the next non-grace note
  747. graceGVoiceEntriesBefore.push(gve);
  748. if (!graceSlur) {
  749. graceSlur = gve.parentVoiceEntry.GraceSlur;
  750. }
  751. continue;
  752. }
  753. if (gve.notes[0].sourceNote.PrintObject) {
  754. (gve as VexFlowVoiceEntry).vfStaveNote = VexFlowConverter.StaveNote(gve);
  755. } else {
  756. // note can now also be added as StaveNote instead of GhostNote, because we set it to transparent
  757. (gve as VexFlowVoiceEntry).vfStaveNote = VexFlowConverter.StaveNote(gve);
  758. // previous method: add as GhostNote instead of StaveNote. Can cause formatting issues if critical notes are missing in the measure
  759. // don't render note. add ghost note, otherwise Vexflow can have issues with layouting when voices not complete.
  760. //(gve as VexFlowVoiceEntry).vfStaveNote = VexFlowConverter.GhostNote(gve.notes[0].sourceNote.Length);
  761. //graceGVoiceEntriesBefore = []; // if note is not rendered, its grace notes shouldn't be rendered, might need to be removed
  762. //continue;
  763. }
  764. if (graceGVoiceEntriesBefore.length > 0) {
  765. // add grace notes that came before this main note to a GraceNoteGroup in Vexflow, attached to the main note
  766. const graceNotes: Vex.Flow.GraceNote[] = [];
  767. for (let i: number = 0; i < graceGVoiceEntriesBefore.length; i++) {
  768. const gveGrace: VexFlowVoiceEntry = <VexFlowVoiceEntry>graceGVoiceEntriesBefore[i];
  769. //if (gveGrace.notes[0].sourceNote.PrintObject) {
  770. // grace notes should generally be rendered independently of main note instead of skipped if main note is invisible
  771. // could be an option to make grace notes transparent if main note is transparent. set grace notes' PrintObject to false then.
  772. const vfStaveNote: StaveNote = VexFlowConverter.StaveNote(gveGrace);
  773. gveGrace.vfStaveNote = vfStaveNote;
  774. graceNotes.push(vfStaveNote);
  775. }
  776. const graceNoteGroup: Vex.Flow.GraceNoteGroup = new Vex.Flow.GraceNoteGroup(graceNotes, graceSlur);
  777. ((gve as VexFlowVoiceEntry).vfStaveNote as StaveNote).addModifier(0, graceNoteGroup);
  778. graceGVoiceEntriesBefore = [];
  779. }
  780. }
  781. }
  782. // remaining grace notes at end of measure, turned into stand-alone grace notes:
  783. if (graceGVoiceEntriesBefore.length > 0) {
  784. for (const graceGve of graceGVoiceEntriesBefore) {
  785. (graceGve as VexFlowVoiceEntry).vfStaveNote = VexFlowConverter.StaveNote(graceGve);
  786. graceGve.parentVoiceEntry.GraceAfterMainNote = true;
  787. }
  788. }
  789. this.finalizeBeams();
  790. this.finalizeTuplets();
  791. const voices: Voice[] = this.getVoicesWithinMeasure();
  792. for (const voice of voices) {
  793. if (voice === undefined) {
  794. continue;
  795. }
  796. const isMainVoice: boolean = !(voice instanceof LinkedVoice);
  797. // add a vexFlow voice for this voice:
  798. this.vfVoices[voice.VoiceId] = new Vex.Flow.Voice({
  799. beat_value: this.parentSourceMeasure.Duration.Denominator,
  800. num_beats: this.parentSourceMeasure.Duration.Numerator,
  801. resolution: Vex.Flow.RESOLUTION,
  802. }).setMode(Vex.Flow.Voice.Mode.SOFT);
  803. const restFilledEntries: GraphicalVoiceEntry[] = this.getRestFilledVexFlowStaveNotesPerVoice(voice);
  804. // create vex flow voices and add tickables to it:
  805. for (const voiceEntry of restFilledEntries) {
  806. if (voiceEntry.parentVoiceEntry) {
  807. if (voiceEntry.parentVoiceEntry.IsGrace && !voiceEntry.parentVoiceEntry.GraceAfterMainNote) {
  808. continue;
  809. }
  810. }
  811. const vexFlowVoiceEntry: VexFlowVoiceEntry = voiceEntry as VexFlowVoiceEntry;
  812. if (voiceEntry.notes.length === 0 || !voiceEntry.notes[0] || !voiceEntry.notes[0].sourceNote.PrintObject) {
  813. // GhostNote, don't add modifiers like in-measure clefs
  814. this.vfVoices[voice.VoiceId].addTickable(vexFlowVoiceEntry.vfStaveNote);
  815. continue;
  816. }
  817. // check for in-measure clefs:
  818. // only add clefs in main voice (to not add them twice)
  819. if (isMainVoice) {
  820. const vfse: VexFlowStaffEntry = vexFlowVoiceEntry.parentStaffEntry as VexFlowStaffEntry;
  821. if (vfse && vfse.vfClefBefore !== undefined) {
  822. // add clef as NoteSubGroup so that we get modifier layouting
  823. const clefModifier: NoteSubGroup = new NoteSubGroup( [vfse.vfClefBefore] );
  824. // The cast is necesary because...vexflow -> see types
  825. (vexFlowVoiceEntry.vfStaveNote as Vex.Flow.StaveNote).addModifier(0, clefModifier);
  826. }
  827. }
  828. // add fingering
  829. if (voiceEntry.parentVoiceEntry && EngravingRules.Rules.RenderFingerings) {
  830. this.createFingerings(voiceEntry);
  831. }
  832. // add Arpeggio
  833. if (voiceEntry.parentVoiceEntry && voiceEntry.parentVoiceEntry.Arpeggio !== undefined) {
  834. const type: Vex.Flow.Stroke.Type = voiceEntry.parentVoiceEntry.Arpeggio.type;
  835. vexFlowVoiceEntry.vfStaveNote.addStroke(0, new Vex.Flow.Stroke(type));
  836. }
  837. this.vfVoices[voice.VoiceId].addTickable(vexFlowVoiceEntry.vfStaveNote);
  838. }
  839. }
  840. this.createArticulations();
  841. this.createOrnaments();
  842. this.setStemDirectionFromVexFlow();
  843. }
  844. /**
  845. * Copy the stem directions chosen by VexFlow to the StemDirection variable of the graphical notes
  846. */
  847. private setStemDirectionFromVexFlow(): void {
  848. //if StemDirection was not set then read out what VexFlow has chosen
  849. for ( const vfStaffEntry of this.staffEntries ) {
  850. for ( const gVoiceEntry of vfStaffEntry.graphicalVoiceEntries) {
  851. for ( const gnote of gVoiceEntry.notes) {
  852. const vfnote: [StaveNote, number] = (gnote as VexFlowGraphicalNote).vfnote;
  853. if (vfnote === undefined || vfnote[0] === undefined) {
  854. continue;
  855. }
  856. const vfStemDir: number = vfnote[0].getStemDirection();
  857. switch (vfStemDir) {
  858. case (Vex.Flow.Stem.UP):
  859. gVoiceEntry.parentVoiceEntry.StemDirection = StemDirectionType.Up;
  860. break;
  861. case (Vex.Flow.Stem.DOWN):
  862. gVoiceEntry.parentVoiceEntry.StemDirection = StemDirectionType.Down;
  863. break;
  864. default:
  865. }
  866. }
  867. }
  868. }
  869. }
  870. /**
  871. * Create the articulations for all notes of the current staff entry
  872. */
  873. private createArticulations(): void {
  874. for (let idx: number = 0, len: number = this.staffEntries.length; idx < len; ++idx) {
  875. const graphicalStaffEntry: VexFlowStaffEntry = (this.staffEntries[idx] as VexFlowStaffEntry);
  876. // create vex flow articulation:
  877. const graphicalVoiceEntries: GraphicalVoiceEntry[] = graphicalStaffEntry.graphicalVoiceEntries;
  878. for (const gve of graphicalVoiceEntries) {
  879. const vfStaveNote: StemmableNote = (gve as VexFlowVoiceEntry).vfStaveNote;
  880. VexFlowConverter.generateArticulations(vfStaveNote, gve.notes[0].sourceNote.ParentVoiceEntry.Articulations);
  881. }
  882. }
  883. }
  884. /**
  885. * Create the ornaments for all notes of the current staff entry
  886. */
  887. private createOrnaments(): void {
  888. for (let idx: number = 0, len: number = this.staffEntries.length; idx < len; ++idx) {
  889. const graphicalStaffEntry: VexFlowStaffEntry = (this.staffEntries[idx] as VexFlowStaffEntry);
  890. const gvoices: { [voiceID: number]: GraphicalVoiceEntry; } = graphicalStaffEntry.graphicalVoiceEntries;
  891. for (const voiceID in gvoices) {
  892. if (gvoices.hasOwnProperty(voiceID)) {
  893. const vfStaveNote: StemmableNote = (gvoices[voiceID] as VexFlowVoiceEntry).vfStaveNote;
  894. const ornamentContainer: OrnamentContainer = gvoices[voiceID].notes[0].sourceNote.ParentVoiceEntry.OrnamentContainer;
  895. if (ornamentContainer !== undefined) {
  896. VexFlowConverter.generateOrnaments(vfStaveNote, ornamentContainer);
  897. }
  898. }
  899. }
  900. }
  901. }
  902. private createFingerings(voiceEntry: GraphicalVoiceEntry): void {
  903. const vexFlowVoiceEntry: VexFlowVoiceEntry = voiceEntry as VexFlowVoiceEntry;
  904. const technicalInstructions: TechnicalInstruction[] = voiceEntry.parentVoiceEntry.TechnicalInstructions;
  905. const fingeringsCount: number = technicalInstructions.length;
  906. for (let i: number = 0; i < technicalInstructions.length; i++) {
  907. const technicalInstruction: TechnicalInstruction = technicalInstructions[i];
  908. let fingeringPosition: PlacementEnum = EngravingRules.Rules.FingeringPosition;
  909. if (technicalInstruction.placement !== PlacementEnum.NotYetDefined) {
  910. fingeringPosition = technicalInstruction.placement;
  911. }
  912. let modifierPosition: any; // Vex.Flow.Stavemodifier.Position
  913. switch (fingeringPosition) {
  914. default:
  915. case PlacementEnum.Left:
  916. modifierPosition = Vex.Flow.StaveModifier.Position.LEFT;
  917. break;
  918. case PlacementEnum.Right:
  919. modifierPosition = Vex.Flow.StaveModifier.Position.RIGHT;
  920. break;
  921. case PlacementEnum.Above:
  922. modifierPosition = Vex.Flow.StaveModifier.Position.ABOVE;
  923. break;
  924. case PlacementEnum.Below:
  925. modifierPosition = Vex.Flow.StaveModifier.Position.BELOW;
  926. break;
  927. case PlacementEnum.NotYetDefined: // automatic fingering placement, could be more complex/customizable
  928. const sourceStaff: Staff = voiceEntry.parentStaffEntry.sourceStaffEntry.ParentStaff;
  929. if (voiceEntry.notes.length > 1 || voiceEntry.parentStaffEntry.graphicalVoiceEntries.length > 1) {
  930. modifierPosition = Vex.Flow.StaveModifier.Position.LEFT;
  931. } else if (sourceStaff.idInMusicSheet === 0) {
  932. modifierPosition = Vex.Flow.StaveModifier.Position.ABOVE;
  933. fingeringPosition = PlacementEnum.Above;
  934. } else {
  935. modifierPosition = Vex.Flow.StaveModifier.Position.BELOW;
  936. fingeringPosition = PlacementEnum.Below;
  937. }
  938. }
  939. const fretFinger: Vex.Flow.FretHandFinger = new Vex.Flow.FretHandFinger(technicalInstruction.value);
  940. fretFinger.setPosition(modifierPosition);
  941. if (fingeringPosition === PlacementEnum.Above || fingeringPosition === PlacementEnum.Below) {
  942. const offsetYSign: number = fingeringPosition === PlacementEnum.Above ? -1 : 1; // minus y is up
  943. const ordering: number = fingeringPosition === PlacementEnum.Above ? i :
  944. technicalInstructions.length - 1 - i; // reverse order for fingerings below staff
  945. if (EngravingRules.Rules.FingeringInsideStafflines && fingeringsCount > 1) { // y-shift for single fingering is ok
  946. // experimental, bounding boxes wrong for fretFinger above/below, better would be creating Labels
  947. // set y-shift. vexflow fretfinger simply places directly above/below note
  948. const perFingeringShift: number = fretFinger.getWidth() / 2;
  949. const shiftCount: number = fingeringsCount * 2.5;
  950. (<any>fretFinger).setOffsetY(offsetYSign * (ordering + shiftCount) * perFingeringShift);
  951. } else if (!EngravingRules.Rules.FingeringInsideStafflines) { // use StringNumber for placement above/below stafflines
  952. const stringNumber: Vex.Flow.StringNumber = new Vex.Flow.StringNumber(technicalInstruction.value);
  953. (<any>stringNumber).radius = 0; // hack to remove the circle around the number
  954. stringNumber.setPosition(modifierPosition);
  955. stringNumber.setOffsetY(offsetYSign * ordering * stringNumber.getWidth() * 2 / 3);
  956. // Vexflow made a mess with the addModifier signature that changes through each class so we just cast to any :(
  957. vexFlowVoiceEntry.vfStaveNote.addModifier((i as any), (stringNumber as any));
  958. continue;
  959. }
  960. }
  961. // Vexflow made a mess with the addModifier signature that changes through each class so we just cast to any :(
  962. vexFlowVoiceEntry.vfStaveNote.addModifier((i as any), (fretFinger as any));
  963. }
  964. }
  965. /**
  966. * Creates a line from 'top' to this measure, of type 'lineType'
  967. * @param top
  968. * @param lineType
  969. */
  970. public lineTo(top: VexFlowMeasure, lineType: any): void {
  971. const connector: StaveConnector = new Vex.Flow.StaveConnector(top.getVFStave(), this.stave);
  972. connector.setType(lineType);
  973. this.connectors.push(connector);
  974. }
  975. /**
  976. * Return the VexFlow Stave corresponding to this graphicalMeasure
  977. * @returns {Vex.Flow.Stave}
  978. */
  979. public getVFStave(): Vex.Flow.Stave {
  980. return this.stave;
  981. }
  982. /**
  983. * After re-running the formatting on the VexFlow Stave, update the
  984. * space needed by Instructions (in VexFlow: StaveModifiers)
  985. */
  986. private updateInstructionWidth(): void {
  987. let vfBeginInstructionsWidth: number = 0;
  988. let vfEndInstructionsWidth: number = 0;
  989. const modifiers: Vex.Flow.StaveModifier[] = this.stave.getModifiers();
  990. for (const mod of modifiers) {
  991. if (mod.getPosition() === StavePositionEnum.BEGIN) { //Vex.Flow.StaveModifier.Position.BEGIN) {
  992. vfBeginInstructionsWidth += mod.getWidth() + mod.getPadding(undefined);
  993. } else if (mod.getPosition() === StavePositionEnum.END) { //Vex.Flow.StaveModifier.Position.END) {
  994. vfEndInstructionsWidth += mod.getWidth() + mod.getPadding(undefined);
  995. }
  996. }
  997. this.beginInstructionsWidth = vfBeginInstructionsWidth / unitInPixels;
  998. this.endInstructionsWidth = vfEndInstructionsWidth / unitInPixels;
  999. }
  1000. }
  1001. // Gives the position of the Stave - replaces the function get Position() in the description of class StaveModifier in vexflow.d.ts
  1002. // The latter gave an error because function cannot be defined in the class descriptions in vexflow.d.ts
  1003. export enum StavePositionEnum {
  1004. LEFT = 1,
  1005. RIGHT = 2,
  1006. ABOVE = 3,
  1007. BELOW = 4,
  1008. BEGIN = 5,
  1009. END = 6
  1010. }