VexFlowMeasure.ts 56 KB

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