VexFlowMeasure.ts 84 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672
  1. import Vex from "vexflow";
  2. import VF = Vex.Flow;
  3. import {GraphicalMeasure} from "../GraphicalMeasure";
  4. import {SourceMeasure} from "../../VoiceData/SourceMeasure";
  5. import {Staff} from "../../VoiceData/Staff";
  6. import {StaffLine} from "../StaffLine";
  7. import {SystemLinesEnum} from "../SystemLinesEnum";
  8. import {ClefInstruction, ClefEnum} from "../../VoiceData/Instructions/ClefInstruction";
  9. import {KeyInstruction} from "../../VoiceData/Instructions/KeyInstruction";
  10. import {RhythmInstruction} from "../../VoiceData/Instructions/RhythmInstruction";
  11. import {VexFlowConverter} from "./VexFlowConverter";
  12. import {VexFlowStaffEntry} from "./VexFlowStaffEntry";
  13. import {Beam} from "../../VoiceData/Beam";
  14. import {GraphicalNote} from "../GraphicalNote";
  15. import {GraphicalStaffEntry} from "../GraphicalStaffEntry";
  16. import StaveNote = VF.StaveNote;
  17. import StemmableNote = VF.StemmableNote;
  18. import NoteSubGroup = VF.NoteSubGroup;
  19. import 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 {EngravingRules} from "../EngravingRules";
  30. import {OrnamentContainer} from "../../VoiceData/OrnamentContainer";
  31. import {TechnicalInstruction} from "../../VoiceData/Instructions/TechnicalInstruction";
  32. import {PlacementEnum} from "../../VoiceData/Expressions/AbstractExpression";
  33. import {VexFlowGraphicalNote} from "./VexFlowGraphicalNote";
  34. import {AutoBeamOptions} from "../../../OpenSheetMusicDisplay/OSMDOptions";
  35. import {SkyBottomLineCalculator} from "../SkyBottomLineCalculator";
  36. import { NoteType } from "../../VoiceData/NoteType";
  37. import { Arpeggio } from "../../VoiceData/Arpeggio";
  38. import { GraphicalTie } from "../GraphicalTie";
  39. import { Note } from "../../VoiceData/Note";
  40. // type StemmableNote = VF.StemmableNote;
  41. export class VexFlowMeasure extends GraphicalMeasure {
  42. constructor(staff: Staff, sourceMeasure?: SourceMeasure, staffLine?: StaffLine) {
  43. super(staff, sourceMeasure, staffLine);
  44. this.minimumStaffEntriesWidth = -1;
  45. /*
  46. * There is no case in which `staffLine === undefined && sourceMeasure === undefined` holds.
  47. * Hence, it is not necessary to specify an `else` case.
  48. * One can verify this through a usage search for this constructor.
  49. */
  50. if (staffLine) {
  51. this.rules = staffLine.ParentMusicSystem.rules;
  52. } else if (sourceMeasure) {
  53. this.rules = sourceMeasure.Rules;
  54. } else {
  55. this.rules = new EngravingRules();
  56. }
  57. this.resetLayout();
  58. }
  59. public isTabMeasure: boolean = false;
  60. /** octaveOffset according to active clef */
  61. public octaveOffset: number = 3;
  62. /** The VexFlow Voices in the measure */
  63. public vfVoices: { [voiceID: number]: VF.Voice } = {};
  64. /** Call this function (if present) to x-format all the voices in the measure */
  65. public formatVoices?: (width: number, parent: VexFlowMeasure) => void;
  66. /** The VexFlow Ties in the measure */
  67. public vfTies: VF.StaveTie[] = [];
  68. /** The repetition instructions given as words or symbols (coda, dal segno..) */
  69. public vfRepetitionWords: VF.Repetition[] = [];
  70. public hasMetronomeMark: boolean = false;
  71. /** The VexFlow Stave (= one measure in a staffline) */
  72. protected stave!: VF.Stave;
  73. /** VexFlow StaveConnectors (vertical lines) */
  74. protected connectors: VF.StaveConnector[] = [];
  75. /** Intermediate object to construct beams */
  76. private beams: { [voiceID: number]: [Beam, VexFlowVoiceEntry[]] [] } = {};
  77. /** Beams created by (optional) autoBeam function. */
  78. private autoVfBeams: VF.Beam[] = [];
  79. /** Beams of tuplet notes created by (optional) autoBeam function. */
  80. private autoTupletVfBeams: VF.Beam[] = [];
  81. /** VexFlow Beams */
  82. private vfbeams: { [voiceID: number]: VF.Beam[] } = {};
  83. /** Intermediate object to construct tuplets */
  84. protected tuplets: { [voiceID: number]: [Tuplet, VexFlowVoiceEntry[]][] } = {};
  85. /** VexFlow Tuplets */
  86. private vftuplets: { [voiceID: number]: VF.Tuplet[] } = {};
  87. // The engraving rules of OSMD.
  88. public rules: EngravingRules;
  89. // Sets the absolute coordinates of the VFStave on the canvas
  90. public setAbsoluteCoordinates(x: number, y: number): void {
  91. this.stave.setX(x).setY(y);
  92. }
  93. /**
  94. * Reset all the geometric values and parameters of this measure and put it in an initialized state.
  95. * This is needed to evaluate a measure a second time by system builder.
  96. */
  97. public resetLayout(): void {
  98. // Take into account some space for the begin and end lines of the stave
  99. // Will be changed when repetitions will be implemented
  100. //this.beginInstructionsWidth = 20 / UnitInPixels;
  101. //this.endInstructionsWidth = 20 / UnitInPixels;
  102. // TODO save beginning and end bar type, set these again after new stave.
  103. this.stave = new VF.Stave(0, 0, 0, {
  104. fill_style: this.rules.StaffLineColor,
  105. space_above_staff_ln: 0,
  106. space_below_staff_ln: 0
  107. });
  108. if (this.InitiallyActiveClef) {
  109. (this.stave as any).clef = VexFlowConverter.Clef(this.InitiallyActiveClef).type;
  110. // Vexflow sets stave.clef to treble by default. It needs this info e.g. for key signature accidentals on new key sig
  111. }
  112. (this.stave as any).MeasureNumber = this.MeasureNumber; // for debug info. vexflow automatically uses stave.measure for rendering measure numbers
  113. // also see VexFlowMusicSheetDrawer.drawSheet() for some other vexflow default value settings (like default font scale)
  114. this.hasMetronomeMark = false;
  115. if (this.ParentStaff) {
  116. this.setLineNumber(this.ParentStaff.StafflineCount);
  117. }
  118. // constructor sets beginning and end bar type to standard
  119. this.stave.setBegBarType(VF.Barline.type.NONE); // technically not correct, but we'd need to set the next measure's beginning bar type
  120. if (this.parentSourceMeasure && this.parentSourceMeasure.endingBarStyleEnum === SystemLinesEnum.None) {
  121. // fix for vexflow ignoring ending barline style after new stave, apparently
  122. this.stave.setEndBarType(VF.Barline.type.NONE);
  123. }
  124. // the correct bar types seem to be set later
  125. this.updateInstructionWidth();
  126. }
  127. public clean(): void {
  128. this.vfTies.length = 0;
  129. this.connectors = [];
  130. // Clean up instructions
  131. this.resetLayout();
  132. }
  133. /**
  134. * returns the x-width (in units) of a given measure line {SystemLinesEnum}.
  135. * @param line
  136. * @returns the x-width in osmd units
  137. */
  138. public getLineWidth(line: SystemLinesEnum): number {
  139. switch (line) {
  140. // return 0 for the normal lines, as the line width will be considered at the updateInstructionWidth() method using the stavemodifiers.
  141. // case SystemLinesEnum.SingleThin:
  142. // return 5.0 / unitInPixels;
  143. // case SystemLinesEnum.DoubleThin:
  144. // return 5.0 / unitInPixels;
  145. // case SystemLinesEnum.ThinBold:
  146. // return 5.0 / unitInPixels;
  147. // but just add a little extra space for repetitions (cosmetics):
  148. case SystemLinesEnum.BoldThinDots:
  149. case SystemLinesEnum.DotsThinBold:
  150. return 10.0 / unitInPixels;
  151. case SystemLinesEnum.DotsBoldBoldDots: // :||: = repeat ends, another repeat starts in next measure
  152. return 10.0 / unitInPixels + this.rules.RepeatEndStartPadding;
  153. default:
  154. return 0;
  155. }
  156. }
  157. /**
  158. * adds the given clef to the begin of the measure.
  159. * This has to update/increase BeginInstructionsWidth.
  160. * @param clef
  161. */
  162. public addClefAtBegin(clef: ClefInstruction): void {
  163. if (!this.rules.RenderClefsAtBeginningOfStaffline) {
  164. return;
  165. }
  166. this.octaveOffset = clef.OctaveOffset;
  167. if (clef.ClefType === ClefEnum.TAB) {
  168. this.stave.addClef("tab", undefined, undefined, undefined);
  169. } else {
  170. const vfclef: { type: string, size: string, annotation: string } = VexFlowConverter.Clef(clef, "default");
  171. this.stave.addClef(vfclef.type, vfclef.size, vfclef.annotation, VF.StaveModifier.Position.BEGIN);
  172. }
  173. this.updateInstructionWidth();
  174. }
  175. /**
  176. * Sets the number of stafflines that are rendered, so that they are centered properly
  177. * @param lineNumber
  178. */
  179. public setLineNumber(lineNumber: number): void {
  180. if (lineNumber !== 5) {
  181. if (lineNumber === 0) {
  182. this.stave.setNumLines(0);
  183. this.stave.getBottomLineY = function(): number {
  184. return this.getYForLine(this.options.num_lines);
  185. };
  186. } else if (lineNumber === 1) {
  187. // VF.Stave.setNumLines hides all but the top line.
  188. // this is better
  189. this.stave.options.line_config = [
  190. { visible: false },
  191. { visible: false },
  192. { visible: true }, // show middle
  193. { visible: false },
  194. { visible: false },
  195. ];
  196. //quick fix to see if this matters for calculation. Doesn't seem to
  197. this.stave.getBottomLineY = function(): number {
  198. return this.getYForLine(2);
  199. };
  200. //lines (which isn't this case here)
  201. //this.stave.options.num_lines = parseInt(lines, 10);
  202. } else if (lineNumber === 2) {
  203. this.stave.options.line_config = [
  204. { visible: false },
  205. { visible: false },
  206. { visible: true }, // show middle
  207. { visible: true },
  208. { visible: false },
  209. ];
  210. this.stave.getBottomLineY = function(): number {
  211. return this.getYForLine(3);
  212. };
  213. } else if (lineNumber === 3) {
  214. this.stave.options.line_config = [
  215. { visible: false },
  216. { visible: true },
  217. { visible: true }, // show middle
  218. { visible: true },
  219. { visible: false },
  220. ];
  221. this.stave.getBottomLineY = function(): number {
  222. return this.getYForLine(2);
  223. };
  224. } else {
  225. this.stave.setNumLines(lineNumber);
  226. this.stave.getBottomLineY = function(): number {
  227. return this.getYForLine(this.options.num_lines);
  228. };
  229. }
  230. }
  231. }
  232. /**
  233. * adds the given key to the begin of the measure.
  234. * This has to update/increase BeginInstructionsWidth.
  235. * @param currentKey the new valid key.
  236. * @param previousKey the old cancelled key. Needed to show which accidentals are not valid any more.
  237. * @param currentClef the valid clef. Needed to put the accidentals on the right y-positions.
  238. */
  239. public addKeyAtBegin(currentKey: KeyInstruction, previousKey: KeyInstruction, currentClef: ClefInstruction): void {
  240. if (!this.rules.RenderKeySignatures || !this.ShowKeySignature) {
  241. return;
  242. }
  243. if (this.parentSourceMeasure?.isReducedToMultiRest && !this.rules.MultipleRestMeasureAddKeySignature) {
  244. return;
  245. }
  246. this.stave.setKeySignature(
  247. VexFlowConverter.keySignature(currentKey),
  248. VexFlowConverter.keySignature(previousKey),
  249. undefined
  250. );
  251. this.updateInstructionWidth();
  252. }
  253. /**
  254. * adds the given rhythm to the begin of the measure.
  255. * This has to update/increase BeginInstructionsWidth.
  256. * @param rhythm
  257. */
  258. public addRhythmAtBegin(rhythm: RhythmInstruction): void {
  259. const timeSig: VF.TimeSignature = VexFlowConverter.TimeSignature(rhythm);
  260. this.stave.addModifier(
  261. timeSig,
  262. VF.StaveModifier.Position.BEGIN
  263. );
  264. if (!this.ShowTimeSignature) {
  265. // extends Element is missing from class StaveModifier in DefinitelyTyped definitions, so setStyle isn't found
  266. timeSig.setStyle({ fillStyle: "#00000000"}); // transparent. requires VexflowPatch
  267. }
  268. this.updateInstructionWidth();
  269. }
  270. /**
  271. * adds the given clef to the end of the measure.
  272. * This has to update/increase EndInstructionsWidth.
  273. * @param clef
  274. */
  275. public addClefAtEnd(clef: ClefInstruction, visible: boolean = true): void {
  276. const vfclef: { type: string, size: string, annotation: string } = VexFlowConverter.Clef(clef, "small");
  277. if (!visible && this.stave.endClef) {
  278. return; // don't overwrite existing clef with invisible clef
  279. }
  280. this.stave.setEndClef(vfclef.type, vfclef.size, vfclef.annotation);
  281. for (const modifier of this.stave.getModifiers()) {
  282. if (!visible) {
  283. // make clef invisible in vexflow. (only rendered to correct layout and staffentry boundingbox)
  284. if (modifier.getCategory() === "clefs" && modifier.getPosition() === VF.StaveModifier.Position.END) {
  285. if ((modifier as any).type === vfclef.type) { // any = VF.Clef
  286. const transparentStyle: string = "#12345600";
  287. const originalStyle: any = modifier.getStyle();
  288. if (originalStyle) {
  289. (modifier as any).originalStrokeStyle = originalStyle.strokeStyle;
  290. (modifier as any).originalFillStyle = originalStyle.fillStyle;
  291. }
  292. modifier.setStyle({strokeStyle: transparentStyle, fillStyle: transparentStyle});
  293. }
  294. }
  295. } else {
  296. // reset invisible style
  297. const originalStrokeStyle: any = (modifier as any).originalStrokeStyle;
  298. const originalFillStyle: any = (modifier as any).originalFillStyle;
  299. if (modifier.getStyle()) {
  300. if (originalStrokeStyle && originalFillStyle) {
  301. modifier.getStyle().strokeStyle = originalStrokeStyle;
  302. modifier.getStyle().fillStyle = originalFillStyle;
  303. } else {
  304. modifier.getStyle().strokeStyle = null;
  305. modifier.getStyle().fillStyle = null;
  306. }
  307. }
  308. }
  309. }
  310. this.parentSourceMeasure.hasEndClef = true;
  311. return this.updateInstructionWidth();
  312. }
  313. // Render initial line is whether or not to render a single bar line at the beginning (if the repeat line we are drawing is
  314. // offset by a clef, for ex.)
  315. public addMeasureLine(lineType: SystemLinesEnum, linePosition: SystemLinePosition, renderInitialLine: boolean = true): void {
  316. switch (linePosition) {
  317. case SystemLinePosition.MeasureBegin:
  318. switch (lineType) {
  319. case SystemLinesEnum.BoldThinDots:
  320. //customize the barline draw function if repeat is beginning of system
  321. if (!renderInitialLine) {
  322. (this.stave as any).modifiers[0].draw = function(stave: VF.Stave): void {
  323. (stave as any).checkContext();
  324. this.setRendered();
  325. switch (this.type) {
  326. case VF.Barline.type.SINGLE:
  327. this.drawVerticalBar(stave, this.x, false);
  328. break;
  329. case VF.Barline.type.DOUBLE:
  330. this.drawVerticalBar(stave, this.x, true);
  331. break;
  332. case VF.Barline.type.END:
  333. this.drawVerticalEndBar(stave, this.x);
  334. break;
  335. case VF.Barline.type.REPEAT_BEGIN:
  336. //removed the vertical line rendering that exists in VF codebase
  337. this.drawRepeatBar(stave, this.x, true);
  338. break;
  339. case VF.Barline.type.REPEAT_END:
  340. this.drawRepeatBar(stave, this.x, false);
  341. break;
  342. case VF.Barline.type.REPEAT_BOTH:
  343. this.drawRepeatBar(stave, this.x, false);
  344. this.drawRepeatBar(stave, this.x, true);
  345. break;
  346. default:
  347. // Default is NONE, so nothing to draw
  348. break;
  349. }
  350. };
  351. }
  352. this.stave.setBegBarType(VF.Barline.type.REPEAT_BEGIN);
  353. break;
  354. default:
  355. //this.stave.setBegBarType(VF.Barline.type.NONE); // not necessary, it seems
  356. break;
  357. }
  358. break;
  359. case SystemLinePosition.MeasureEnd:
  360. switch (lineType) {
  361. case SystemLinesEnum.DotsBoldBoldDots:
  362. this.stave.setEndBarType(VF.Barline.type.REPEAT_BOTH);
  363. break;
  364. case SystemLinesEnum.DotsThinBold:
  365. this.stave.setEndBarType(VF.Barline.type.REPEAT_END);
  366. break;
  367. case SystemLinesEnum.DoubleThin:
  368. this.stave.setEndBarType(VF.Barline.type.DOUBLE);
  369. break;
  370. case SystemLinesEnum.ThinBold:
  371. this.stave.setEndBarType(VF.Barline.type.END);
  372. break;
  373. case SystemLinesEnum.None:
  374. this.stave.setEndBarType(VF.Barline.type.NONE);
  375. break;
  376. // TODO: Add support for additional Barline types when VexFlow supports them
  377. default:
  378. break;
  379. }
  380. break;
  381. default:
  382. break;
  383. }
  384. }
  385. /**
  386. * Adds a measure number to the top left corner of the measure
  387. * This method is not used currently in favor of the calculateMeasureNumberPlacement
  388. * method in the MusicSheetCalculator.ts
  389. */
  390. public addMeasureNumber(): void {
  391. const text: string = this.MeasureNumber.toString();
  392. const position: number = StavePositionEnum.ABOVE; //VF.StaveModifier.Position.ABOVE;
  393. this.stave.setText(text, position, {
  394. justification: 1,
  395. shift_x: 0,
  396. shift_y: 0,
  397. });
  398. }
  399. public addWordRepetition(repetitionInstruction: RepetitionInstruction): void {
  400. let instruction: number | undefined;
  401. let position: number = VF.StaveModifier.Position.END;
  402. const xShift: number = this.beginInstructionsWidth;
  403. switch (repetitionInstruction.type) {
  404. case RepetitionInstructionEnum.Segno:
  405. // create Segno Symbol:
  406. instruction = VF.Repetition.type.SEGNO_LEFT;
  407. position = VF.StaveModifier.Position.LEFT;
  408. break;
  409. case RepetitionInstructionEnum.Coda:
  410. // create Coda Symbol:
  411. instruction = VF.Repetition.type.CODA_LEFT;
  412. position = VF.StaveModifier.Position.LEFT;
  413. break;
  414. case RepetitionInstructionEnum.DaCapo:
  415. instruction = VF.Repetition.type.DC;
  416. break;
  417. case RepetitionInstructionEnum.DalSegno:
  418. instruction = VF.Repetition.type.DS;
  419. break;
  420. case RepetitionInstructionEnum.Fine:
  421. instruction = VF.Repetition.type.FINE;
  422. break;
  423. case RepetitionInstructionEnum.ToCoda:
  424. instruction = (VF.Repetition as any).type.TO_CODA;
  425. break;
  426. case RepetitionInstructionEnum.DaCapoAlFine:
  427. instruction = VF.Repetition.type.DC_AL_FINE;
  428. break;
  429. case RepetitionInstructionEnum.DaCapoAlCoda:
  430. instruction = VF.Repetition.type.DC_AL_CODA;
  431. break;
  432. case RepetitionInstructionEnum.DalSegnoAlFine:
  433. instruction = VF.Repetition.type.DS_AL_FINE;
  434. break;
  435. case RepetitionInstructionEnum.DalSegnoAlCoda:
  436. instruction = VF.Repetition.type.DS_AL_CODA;
  437. break;
  438. default:
  439. break;
  440. }
  441. if (instruction) {
  442. const repetition: VF.Repetition = new VF.Repetition(instruction, xShift, -this.rules.RepetitionSymbolsYOffset);
  443. (repetition as any).xShiftAsPercentOfStaveWidth = this.rules.RepetitionEndInstructionXShiftAsPercentOfStaveWidth;
  444. this.stave.addModifier(repetition, position);
  445. return;
  446. }
  447. this.addVolta(repetitionInstruction);
  448. }
  449. protected addVolta(repetitionInstruction: RepetitionInstruction): void {
  450. let voltaType: number = VF.Volta.type.BEGIN;
  451. if (repetitionInstruction.type === RepetitionInstructionEnum.Ending) {
  452. switch (repetitionInstruction.alignment) {
  453. case AlignmentType.Begin:
  454. if (this.parentSourceMeasure.endsRepetitionEnding()) {
  455. voltaType = VF.Volta.type.BEGIN_END;
  456. } else {
  457. voltaType = VF.Volta.type.BEGIN;
  458. }
  459. break;
  460. case AlignmentType.End:
  461. if (this.parentSourceMeasure.beginsRepetitionEnding()) {
  462. //voltaType = VF.Volta.type.BEGIN_END;
  463. // don't add BEGIN_END volta a second time:
  464. return;
  465. } else {
  466. voltaType = VF.Volta.type.END;
  467. }
  468. break;
  469. default:
  470. break;
  471. }
  472. const skyBottomLineCalculator: SkyBottomLineCalculator = this.ParentStaffLine.SkyBottomLineCalculator;
  473. //Because of loss of accuracy when sampling (see SkyBottomLineCalculator.updateInRange), measures tend to overlap
  474. //This causes getSkyLineMinInRange to return an incorrect min value (one from the previous measure, which has been modified)
  475. //We need to offset the end of what we are measuring by a bit to prevent this, otherwise volta pairs step up
  476. const start: number = this.PositionAndShape.AbsolutePosition.x + this.PositionAndShape.BorderMarginLeft + 0.4;
  477. const end: number = Math.max(this.PositionAndShape.AbsolutePosition.x + this.PositionAndShape.BorderMarginRight, start + 0.4);
  478. //2 unit gap, since volta is positioned from y center it seems.
  479. //This prevents cases where the volta is rendered over another element
  480. const skylineMinForMeasure: number = skyBottomLineCalculator.getSkyLineMinInRange( start, end ) - 2;
  481. //-6 OSMD units is the 0 value that the volta is placed on. .1 extra so the skyline goes above the volta
  482. //instead of on the line itself
  483. let newSkylineValueForMeasure: number = -6.1 + this.rules.VoltaOffset;
  484. let vexFlowVoltaHeight: number = this.rules.VoltaOffset;
  485. //EngravingRules default offset is 2.5, can be user set.
  486. //2.5 gives us a good default value to work with.
  487. //if we calculate that the minimum skyline allowed by elements is above the default volta position, need to adjust volta up further
  488. if (skylineMinForMeasure < newSkylineValueForMeasure) {
  489. const skylineDifference: number = skylineMinForMeasure - newSkylineValueForMeasure;
  490. vexFlowVoltaHeight += skylineDifference;
  491. newSkylineValueForMeasure = skylineMinForMeasure;
  492. }
  493. let prevMeasure: VexFlowMeasure = undefined;
  494. //if we already have a volta in the prev measure, should match it's height, or if we are higher, it should match ours
  495. //find previous sibling measure that may have volta
  496. const currentMeasureNumber: number = this.parentSourceMeasure.MeasureNumber;
  497. for (let i: number = 0; i < this.ParentStaffLine.Measures.length; i++) {
  498. const tempMeasure: GraphicalMeasure = this.ParentStaffLine.Measures[i];
  499. if (!(tempMeasure instanceof VexFlowMeasure)) {
  500. // can happen for MultipleRestMeasures
  501. continue;
  502. }
  503. if (tempMeasure.MeasureNumber === currentMeasureNumber - 1 ||
  504. tempMeasure.MeasureNumber + tempMeasure.parentSourceMeasure?.multipleRestMeasures === currentMeasureNumber) {
  505. //We found the previous top measure
  506. prevMeasure = tempMeasure as VexFlowMeasure;
  507. }
  508. }
  509. if (prevMeasure) {
  510. const prevStaveModifiers: VF.StaveModifier[] = prevMeasure.stave.getModifiers();
  511. for (let i: number = 0; i < prevStaveModifiers.length; i++) {
  512. const nextStaveModifier: VF.StaveModifier = prevStaveModifiers[i];
  513. if (nextStaveModifier.hasOwnProperty("volta")) {
  514. const prevskyBottomLineCalculator: SkyBottomLineCalculator = prevMeasure.ParentStaffLine.SkyBottomLineCalculator;
  515. const prevStart: number = prevMeasure.PositionAndShape.AbsolutePosition.x + prevMeasure.PositionAndShape.BorderMarginLeft + 0.4;
  516. const prevEnd: number = Math.max(
  517. prevMeasure.PositionAndShape.AbsolutePosition.x + prevMeasure.PositionAndShape.BorderMarginRight,
  518. prevStart + 0.4);
  519. const prevMeasureSkyline: number = prevskyBottomLineCalculator.getSkyLineMinInRange(prevStart, prevEnd);
  520. //if prev skyline is higher, use it
  521. if (prevMeasureSkyline <= newSkylineValueForMeasure) {
  522. const skylineDifference: number = prevMeasureSkyline - newSkylineValueForMeasure;
  523. vexFlowVoltaHeight += skylineDifference;
  524. newSkylineValueForMeasure = prevMeasureSkyline;
  525. } else { //otherwise, we are higher. Need to adjust prev
  526. (nextStaveModifier as any).y_shift = vexFlowVoltaHeight * unitInPixels;
  527. prevMeasure.ParentStaffLine.SkyBottomLineCalculator.updateSkyLineInRange(prevStart, prevEnd, newSkylineValueForMeasure);
  528. }
  529. }
  530. }
  531. }
  532. //convert to VF units (pixels)
  533. vexFlowVoltaHeight *= 10;
  534. this.stave.setVoltaType(voltaType, repetitionInstruction.endingIndices[0], vexFlowVoltaHeight);
  535. skyBottomLineCalculator.updateSkyLineInRange(start, end, newSkylineValueForMeasure);
  536. }
  537. }
  538. /**
  539. * Sets the overall x-width of the measure.
  540. * @param width
  541. */
  542. public setWidth(width: number): void {
  543. super.setWidth(width);
  544. // Set the width of the VF.Stave
  545. this.stave.setWidth(width * unitInPixels);
  546. // Force the width of the Begin Instructions
  547. //this.stave.setNoteStartX(this.beginInstructionsWidth * UnitInPixels);
  548. }
  549. /**
  550. * This method is called after the StaffEntriesScaleFactor has been set.
  551. * Here the final x-positions of the staff entries have to be set.
  552. * (multiply the minimal positions with the scaling factor, considering the BeginInstructionsWidth)
  553. */
  554. public layoutSymbols(): void {
  555. // vexflow does the x-layout
  556. }
  557. /**
  558. * Draw this measure on a VexFlow CanvasContext
  559. * @param ctx
  560. */
  561. public draw(ctx: Vex.IRenderContext): Node {
  562. const measureNode: SVGGElement = ctx.openGroup() as SVGGElement;
  563. measureNode?.classList?.add("vf-measure");
  564. const staveLineNode: Node = ctx.openGroup();
  565. (staveLineNode as SVGGElement)?.classList?.add("vf-stave");
  566. // Draw stave lines
  567. this.stave.setContext(ctx).draw();
  568. ctx.closeGroup();
  569. const voicesNode: Node = ctx.openGroup();
  570. (voicesNode as SVGGElement)?.classList?.add("vf-voices");
  571. // Draw all voices
  572. for (const voiceID in this.vfVoices) {
  573. if (this.vfVoices.hasOwnProperty(voiceID)) {
  574. ctx.save();
  575. this.vfVoices[voiceID].draw(ctx, this.stave);
  576. ctx.restore();
  577. // this.vfVoices[voiceID].tickables.forEach(t => t.getBoundingBox().draw(ctx));
  578. // this.vfVoices[voiceID].tickables.forEach(t => t.getBoundingBox().draw(ctx));
  579. }
  580. }
  581. ctx.closeGroup();
  582. const beamsNode: Node = ctx.openGroup();
  583. (beamsNode as SVGGElement)?.classList?.add("vf-beams");
  584. // Draw beams
  585. for (const voiceID in this.vfbeams) {
  586. if (this.vfbeams.hasOwnProperty(voiceID)) {
  587. for (const beam of this.vfbeams[voiceID]) {
  588. beam.setContext(ctx).draw();
  589. }
  590. }
  591. }
  592. // Draw auto-generated beams from Beam.generateBeams()
  593. if (this.autoVfBeams) {
  594. for (const beam of this.autoVfBeams) {
  595. beam.setContext(ctx).draw();
  596. }
  597. }
  598. if (!this.isTabMeasure || this.rules.TupletNumbersInTabs) {
  599. if (this.autoTupletVfBeams) {
  600. for (const beam of this.autoTupletVfBeams) {
  601. beam.setContext(ctx).draw();
  602. }
  603. }
  604. // Draw tuplets
  605. for (const voiceID in this.vftuplets) {
  606. if (this.vftuplets.hasOwnProperty(voiceID)) {
  607. for (let i: number = 0; i < this.tuplets[voiceID].length; i++) {
  608. const tuplet: Tuplet = this.tuplets[voiceID][i][0];
  609. const vftuplet: VF.Tuplet = this.vftuplets[voiceID][i];
  610. if (!tuplet.RenderTupletNumber) {
  611. // (vftuplet as any).numerator_glyphs_stored = [...(vftuplet as any).numerator_glyphs];
  612. // (vftuplet as any).numerator_glyphs = [];
  613. (vftuplet as any).RenderTupletNumber = false;
  614. } else {
  615. // issue with restoring glyphs (version without vexflowpatch): need to deep copy array, otherwise the reference is overwritten
  616. // (vftuplet as any).numerator_glyphs = [...(vftuplet as any).numerator_glyphs_stored];
  617. // (vftuplet as any).numerator_glyphs_stored = undefined;
  618. (vftuplet as any).RenderTupletNumber = true;
  619. }
  620. vftuplet.setContext(ctx).draw();
  621. }
  622. }
  623. }
  624. }
  625. ctx.closeGroup();
  626. //Close the measure group
  627. ctx.closeGroup();
  628. //Ties need special treatment, should not be part of hte measure bounding box
  629. const tieNode: Node = ctx.openGroup();
  630. (tieNode as SVGGElement)?.classList?.add("vf-ties");
  631. // Draw ties
  632. for (const tie of this.vfTies) {
  633. if (tie instanceof VF.TabSlide) {
  634. return; // rendered later in VexFlowMusicSheetDrawer.drawGlissandi(), when all staffline measures are rendered
  635. }
  636. tie.setContext(ctx);
  637. tie.draw();
  638. }
  639. ctx.closeGroup();
  640. // Draw vertical lines
  641. for (const connector of this.connectors) {
  642. connector.setContext(ctx).draw();
  643. }
  644. this.correctNotePositions();
  645. return measureNode;
  646. }
  647. // this currently formats multiple measures, see VexFlowMusicSheetCalculator.formatMeasures()
  648. public format(): void {
  649. // If this is the first stave in the vertical measure, call the format
  650. // method to set the width of all the voices
  651. if (this.formatVoices) {
  652. // set the width of the voices to the current measure width:
  653. // (The width of the voices does not include the instructions (StaveModifiers))
  654. this.formatVoices((this.PositionAndShape.Size.width - this.beginInstructionsWidth - this.endInstructionsWidth) * unitInPixels, this);
  655. }
  656. // this.correctNotePositions(); // now done at the end of draw()
  657. }
  658. // correct position / bounding box (note.setIndex() needs to have been called)
  659. public correctNotePositions(): void {
  660. if (this.isTabMeasure) {
  661. return;
  662. }
  663. for (const voice of this.getVoicesWithinMeasure()) {
  664. for (const ve of voice.VoiceEntries) {
  665. for (const note of ve.Notes) {
  666. const gNote: VexFlowGraphicalNote = this.rules.GNote(note) as VexFlowGraphicalNote;
  667. if (!gNote?.vfnote) { // can happen were invisible, then multi rest measure. TODO fix multi rest measure not removed
  668. return;
  669. }
  670. const vfnote: VF.StemmableNote = gNote.vfnote[0];
  671. // if (note.isRest()) // TODO somehow there are never rest notes in ve.Notes
  672. // TODO also, grace notes are not included here, need to be fixed as well. (and a few triple beamed notes in Bach Air)
  673. let relPosY: number = 0;
  674. if (gNote.parentVoiceEntry.parentVoiceEntry.StemDirection === StemDirectionType.Up && gNote.vfnote[0].getDuration() !== "w") {
  675. relPosY += 3.5; // about 3.5 lines too high. this seems to be related to the default stem height, not actual stem height.
  676. // alternate calculation using actual stem height: somehow wildly varying.
  677. // if (ve.Notes.length > 1) {
  678. // const stemHeight: number = vfnote.getStem().getHeight();
  679. // // relPosY += shortFactor * stemHeight / unitInPixels - 3.5;
  680. // relPosY += stemHeight / unitInPixels - 3.5; // for some reason this varies in its correctness between similar notes
  681. // } else {
  682. // relPosY += 3.5;
  683. // }
  684. } else {
  685. relPosY += 0.5; // center-align bbox
  686. }
  687. const line: number = -gNote.notehead(vfnote).line; // vexflow y direction is opposite of osmd's
  688. relPosY += line + (gNote.parentVoiceEntry.notes.last() as VexFlowGraphicalNote).notehead().line; // don't move for first note: - (-vexline)
  689. gNote.PositionAndShape.RelativePosition.y = relPosY;
  690. }
  691. }
  692. }
  693. }
  694. /**
  695. * Returns all the voices that are present in this measure
  696. */
  697. public getVoicesWithinMeasure(): Voice[] {
  698. const voices: Voice[] = [];
  699. for (const gse of this.staffEntries) {
  700. for (const gve of gse.graphicalVoiceEntries) {
  701. if (voices.indexOf(gve.parentVoiceEntry.ParentVoice) === -1) {
  702. voices.push(gve.parentVoiceEntry.ParentVoice);
  703. }
  704. }
  705. }
  706. return voices;
  707. }
  708. /**
  709. * Returns all the graphicalVoiceEntries of a given Voice.
  710. * @param voice the voice for which the graphicalVoiceEntries shall be returned.
  711. */
  712. public getGraphicalVoiceEntriesPerVoice(voice: Voice): GraphicalVoiceEntry[] {
  713. const voiceEntries: GraphicalVoiceEntry[] = [];
  714. for (const gse of this.staffEntries) {
  715. for (const gve of gse.graphicalVoiceEntries) {
  716. if (gve.parentVoiceEntry.ParentVoice === voice) {
  717. voiceEntries.push(gve);
  718. }
  719. }
  720. }
  721. return voiceEntries;
  722. }
  723. /**
  724. * Finds the gaps between the existing notes within a measure.
  725. * Problem here is, that the graphicalVoiceEntry does not exist yet and
  726. * that Tied notes are not present in the normal voiceEntries.
  727. * To handle this, calculation with absolute timestamps is needed.
  728. * And the graphical notes have to be analysed directly (and not the voiceEntries, as it actually should be -> needs refactoring)
  729. * @param voice the voice for which the ghost notes shall be searched.
  730. */
  731. protected getRestFilledVexFlowStaveNotesPerVoice(voice: Voice): GraphicalVoiceEntry[] {
  732. let latestVoiceTimestamp: Fraction;
  733. let gvEntries: GraphicalVoiceEntry[] = this.getGraphicalVoiceEntriesPerVoice(voice);
  734. for (let idx: number = 0; idx < gvEntries.length; idx++) {
  735. const gve: GraphicalVoiceEntry = gvEntries[idx];
  736. const gNotesStartTimestamp: Fraction = gve.notes[0].sourceNote.getAbsoluteTimestamp();
  737. // find the voiceEntry end timestamp:
  738. let gNotesEndTimestamp: Fraction = new Fraction();
  739. for (const graphicalNote of gve.notes) {
  740. const noteEnd: Fraction = Fraction.plus(graphicalNote.sourceNote.getAbsoluteTimestamp(), graphicalNote.sourceNote.Length);
  741. if (gNotesEndTimestamp.lt(noteEnd)) {
  742. gNotesEndTimestamp = noteEnd;
  743. }
  744. }
  745. // check if this voice has just been found the first time:
  746. if (!latestVoiceTimestamp) {
  747. // if this voice is new, check for a gap from measure start to the start of the current voice entry:
  748. const gapFromMeasureStart: Fraction = Fraction.minus(gNotesStartTimestamp, this.parentSourceMeasure.AbsoluteTimestamp);
  749. if (gapFromMeasureStart.RealValue > 0) {
  750. log.trace(`Ghost Found at start (measure ${this.MeasureNumber})`); // happens too often for valid measures to be logged to debug
  751. const ghostGves: GraphicalVoiceEntry[] = this.createGhostGves(gapFromMeasureStart);
  752. gvEntries.splice(0, 0, ...ghostGves);
  753. idx += ghostGves.length;
  754. }
  755. } else {
  756. // get the length of the empty space between notes:
  757. const inBetweenLength: Fraction = Fraction.minus(gNotesStartTimestamp, latestVoiceTimestamp);
  758. if (inBetweenLength.RealValue > 0) {
  759. log.trace(`Ghost Found in between (measure ${this.MeasureNumber})`); // happens too often for valid measures to be logged to debug
  760. const ghostGves: VexFlowVoiceEntry[] = this.createGhostGves(inBetweenLength);
  761. // add elements before current element:
  762. gvEntries.splice(idx, 0, ...ghostGves);
  763. // and increase index, as we added elements:
  764. idx += ghostGves.length;
  765. }
  766. }
  767. // finally set the latest timestamp of this voice to the end timestamp of the longest note in the current voiceEntry:
  768. latestVoiceTimestamp = gNotesEndTimestamp;
  769. }
  770. const measureEndTimestamp: Fraction = Fraction.plus(this.parentSourceMeasure.AbsoluteTimestamp, this.parentSourceMeasure.Duration);
  771. const restLength: Fraction = Fraction.minus(measureEndTimestamp, latestVoiceTimestamp);
  772. if (restLength.RealValue > 0) {
  773. // fill the gap with a rest ghost note
  774. // starting from lastFraction
  775. // with length restLength:
  776. log.trace(`Ghost Found at end (measure ${this.MeasureNumber})`); // happens too often for valid measures to be logged to debug
  777. const ghostGves: VexFlowVoiceEntry[] = this.createGhostGves(restLength);
  778. gvEntries = gvEntries.concat(ghostGves);
  779. }
  780. return gvEntries;
  781. }
  782. private createGhostGves(duration: Fraction): VexFlowVoiceEntry[] {
  783. const vfghosts: VF.GhostNote[] = VexFlowConverter.GhostNotes(duration);
  784. const ghostGves: VexFlowVoiceEntry[] = [];
  785. for (const vfghost of vfghosts) {
  786. const ghostGve: VexFlowVoiceEntry = new VexFlowVoiceEntry(undefined, undefined, this.rules);
  787. ghostGve.vfStaveNote = vfghost;
  788. ghostGves.push(ghostGve);
  789. }
  790. return ghostGves;
  791. }
  792. /**
  793. * Add a note to a beam
  794. * @param graphicalNote
  795. * @param beam
  796. */
  797. public handleBeam(graphicalNote: GraphicalNote, beam: Beam): void {
  798. const voiceID: number = graphicalNote.sourceNote.ParentVoiceEntry.ParentVoice.VoiceId;
  799. let beams: [Beam, VexFlowVoiceEntry[]][] = this.beams[voiceID];
  800. if (!beams) {
  801. beams = this.beams[voiceID] = [];
  802. }
  803. let data: [Beam, VexFlowVoiceEntry[]];
  804. for (const mybeam of beams) {
  805. if (mybeam[0] === beam) {
  806. data = mybeam;
  807. }
  808. }
  809. if (!data) {
  810. data = [beam, []];
  811. beams.push(data);
  812. }
  813. const parent: VexFlowVoiceEntry = graphicalNote.parentVoiceEntry as VexFlowVoiceEntry;
  814. if (data[1].indexOf(parent) < 0) {
  815. data[1].push(parent);
  816. }
  817. }
  818. public handleTuplet(graphicalNote: GraphicalNote, tuplet: Tuplet): void {
  819. const voiceID: number = graphicalNote.sourceNote.ParentVoiceEntry.ParentVoice.VoiceId;
  820. tuplet = graphicalNote.sourceNote.NoteTuplet;
  821. let tuplets: [Tuplet, VexFlowVoiceEntry[]][] = this.tuplets[voiceID];
  822. if (!tuplets) {
  823. tuplets = this.tuplets[voiceID] = [];
  824. }
  825. let currentTupletBuilder: [Tuplet, VexFlowVoiceEntry[]];
  826. for (const t of tuplets) {
  827. if (t[0] === tuplet) {
  828. currentTupletBuilder = t;
  829. }
  830. }
  831. if (!currentTupletBuilder) {
  832. currentTupletBuilder = [tuplet, []];
  833. tuplets.push(currentTupletBuilder);
  834. }
  835. const parent: VexFlowVoiceEntry = graphicalNote.parentVoiceEntry as VexFlowVoiceEntry;
  836. if (currentTupletBuilder[1].indexOf(parent) < 0) {
  837. currentTupletBuilder[1].push(parent);
  838. }
  839. }
  840. /**
  841. * Complete the creation of VexFlow Beams in this measure
  842. */
  843. public finalizeBeams(): void {
  844. // The following line resets the created Vex.Flow Beams and
  845. // created them brand new. Is this needed? And more importantly,
  846. // should the old beams be removed manually by the notes?
  847. this.vfbeams = {};
  848. const beamedNotes: StaveNote[] = []; // already beamed notes, will be ignored by this.autoBeamNotes()
  849. for (const voiceID in this.beams) {
  850. if (this.beams.hasOwnProperty(voiceID)) {
  851. let vfbeams: VF.Beam[] = this.vfbeams[voiceID];
  852. if (!vfbeams) {
  853. vfbeams = this.vfbeams[voiceID] = [];
  854. }
  855. for (const beam of this.beams[voiceID]) {
  856. let beamHasQuarterNoteOrLonger: boolean = false;
  857. for (const note of beam[0].Notes) {
  858. if (note.Length.RealValue >= new Fraction(1, 4).RealValue
  859. // check whether the note has a TypeLength that's also not suitable for a beam (bigger than an eigth)
  860. && (!note.TypeLength || note.TypeLength.RealValue > 0.125)) {
  861. beamHasQuarterNoteOrLonger = true;
  862. break;
  863. }
  864. }
  865. if (beamHasQuarterNoteOrLonger) {
  866. log.debug("Beam between note >= quarter, likely tremolo, currently unsupported. continuing.");
  867. continue;
  868. }
  869. const notes: VF.StaveNote[] = [];
  870. const psBeam: Beam = beam[0];
  871. const voiceEntries: VexFlowVoiceEntry[] = beam[1];
  872. let autoStemBeam: boolean = true;
  873. for (const gve of voiceEntries) {
  874. if (gve.parentVoiceEntry.ParentVoice === psBeam.Notes[0].ParentVoiceEntry.ParentVoice) {
  875. autoStemBeam = gve.parentVoiceEntry.WantedStemDirection === StemDirectionType.Undefined;
  876. // if (psBeam.Notes[0].NoteTuplet) {
  877. // autoStemBeam = true;
  878. // // this fix seemed temporarily necessary for tuplets with beams, see test_drum_tublet_beams
  879. // break;
  880. // }
  881. }
  882. }
  883. let isGraceBeam: boolean = false;
  884. let beamColor: string;
  885. const stemColors: string[] = [];
  886. for (const entry of voiceEntries) {
  887. const note: VF.StaveNote = entry.vfStaveNote as StaveNote;
  888. if (note) {
  889. notes.push(note);
  890. beamedNotes.push(note);
  891. }
  892. if (entry.parentVoiceEntry.IsGrace) {
  893. isGraceBeam = true;
  894. }
  895. if (entry.parentVoiceEntry.StemColor && this.rules.ColoringEnabled) {
  896. stemColors.push(entry.parentVoiceEntry.StemColor);
  897. }
  898. }
  899. if (notes.length > 1) {
  900. const vfBeam: VF.Beam = new VF.Beam(notes, autoStemBeam);
  901. if (isGraceBeam) {
  902. // smaller beam, as in Vexflow.GraceNoteGroup.beamNotes()
  903. (<any>vfBeam).render_options.beam_width = 3;
  904. (<any>vfBeam).render_options.partial_beam_length = 4;
  905. }
  906. if (stemColors.length >= 2 && this.rules.ColorBeams) {
  907. beamColor = stemColors[0];
  908. for (const stemColor of stemColors) {
  909. if (stemColor !== beamColor) {
  910. beamColor = undefined;
  911. break;
  912. }
  913. }
  914. vfBeam.setStyle({ fillStyle: beamColor, strokeStyle: beamColor });
  915. }
  916. if (this.rules.FlatBeams) {
  917. (<any>vfBeam).render_options.flat_beams = true;
  918. (<any>vfBeam).render_options.flat_beam_offset = this.rules.FlatBeamOffset;
  919. (<any>vfBeam).render_options.flat_beam_offset_per_beam = this.rules.FlatBeamOffsetPerBeam;
  920. }
  921. vfbeams.push(vfBeam);
  922. } else {
  923. log.debug("Warning! Beam with no notes!");
  924. }
  925. }
  926. }
  927. }
  928. if (this.rules.AutoBeamNotes) {
  929. this.autoBeamNotes(beamedNotes); // try to autobeam notes except those that are already beamed (beamedNotes).
  930. }
  931. }
  932. /** Automatically creates beams for notes except beamedNotes, using Vexflow's Beam.generateBeams().
  933. * Takes options from this.rules.AutoBeamOptions.
  934. * @param beamedNotes notes that will not be autobeamed (usually because they are already beamed)
  935. */
  936. private autoBeamNotes(beamedNotes: StemmableNote[]): void {
  937. if (!this.rules.AutoBeamTabs && this.isTabMeasure) { // could also use an option tabBeams to disable beams there completely
  938. return;
  939. }
  940. let autoBeamId: number = 60; // start with 60 to not collide (ids) with xml beams
  941. /** Link between OSMD note (Note) and Vexflow note (StaveNote).
  942. * For adding OSMD beams (note.NoteBeam), we also need the note (+ corresponding vfnote)
  943. * This avoids needing to check (stavenote as any).beam, and registers the beam in the OSMD Note(.NoteBeam).
  944. */
  945. interface LinkedNote {
  946. vfStaveNote: StaveNote;
  947. sourceNote: Note;
  948. }
  949. let notesToAutoBeam: LinkedNote[] = [];
  950. let consecutiveBeamableNotes: LinkedNote[] = [];
  951. let currentTuplet: Tuplet;
  952. let tupletNotesToAutoBeam: LinkedNote[] = [];
  953. this.autoTupletVfBeams = [];
  954. const separateAutoBeams: LinkedNote[][] = []; // a set of separate beams, each having a set of notes (StemmableNote[]).
  955. this.autoVfBeams = []; // final VF.Beams will be pushed/collected into this
  956. let timeSignature: Fraction = this.parentSourceMeasure.ActiveTimeSignature;
  957. if (!timeSignature) { // this doesn't happen in OSMD, but maybe in a SourceGenerator
  958. timeSignature = this.parentSourceMeasure.Duration; // suboptimal, can be 1/1 in a 4/4 time signature
  959. }
  960. /*if (this.parentSourceMeasure.FirstInstructionsStaffEntries[0]) {
  961. for (const instruction of this.parentSourceMeasure.FirstInstructionsStaffEntries[0].Instructions) {
  962. if (instruction instanceof RhythmInstruction) { // there is not always a RhythmInstruction, but this could be useful some time.
  963. timeSignature = (instruction as RhythmInstruction).Rhythm;
  964. }
  965. }
  966. }*/
  967. for (const staffEntry of this.staffEntries) {
  968. for (const gve of staffEntry.graphicalVoiceEntries) {
  969. const vfStaveNote: StaveNote = <StaveNote> (gve as VexFlowVoiceEntry).vfStaveNote;
  970. const gNote: GraphicalNote = gve.notes[0]; // TODO check for all notes within the graphical voice entry
  971. const linkedNote: LinkedNote = {
  972. vfStaveNote: vfStaveNote,
  973. sourceNote: gNote.sourceNote
  974. };
  975. const isOnBeat: boolean = staffEntry.relInMeasureTimestamp.isOnBeat(timeSignature);
  976. const haveTwoOrMoreNotesToBeamAlready: boolean = consecutiveBeamableNotes.length >= 2;
  977. //const noteIsQuarterOrLonger: boolean = gNote.sourceNote.Length.CompareTo(new Fraction(1, 4)) >= 0; // trusting Fraction class, no float check
  978. const noteIsQuarterOrLonger: boolean = gNote.sourceNote.Length.RealValue - new Fraction(1, 4).RealValue > (-Fraction.FloatInaccuracyTolerance);
  979. const unbeamableNote: boolean =
  980. gve.parentVoiceEntry.IsGrace || // don't beam grace notes
  981. noteIsQuarterOrLonger || // don't beam quarter or longer notes
  982. beamedNotes.contains(vfStaveNote);
  983. if (unbeamableNote || isOnBeat) { // end beam
  984. if (haveTwoOrMoreNotesToBeamAlready) {
  985. // if we already have at least 2 notes to beam, beam them. don't beam notes surrounded by quarter notes etc.
  986. for (const note of consecutiveBeamableNotes) {
  987. notesToAutoBeam.push(note); // "flush" already beamed notes
  988. }
  989. separateAutoBeams.push(notesToAutoBeam.slice()); // copy array, otherwise this beam gets the next notes of next beam later
  990. notesToAutoBeam = []; // reset notesToAutoBeam, otherwise the next beam includes the previous beam's notes too
  991. }
  992. consecutiveBeamableNotes = []; // reset notes to beam
  993. if (unbeamableNote) {
  994. continue;
  995. }
  996. // else, note will be pushed to consecutiveBeamableNotes after tuplet check, also for note on new beat
  997. }
  998. // create beams for tuplets separately
  999. const noteTuplet: Tuplet = gve.notes[0].sourceNote.NoteTuplet;
  1000. if (noteTuplet) {
  1001. // check if there are quarter notes or longer in the tuplet, then don't beam.
  1002. // (TODO: check for consecutiveBeamableNotes inside tuplets like for non-tuplet notes above
  1003. // e.g quarter eigth eighth -> beam the two eigth notes)
  1004. let tupletContainsUnbeamableNote: boolean = false;
  1005. for (const notes of noteTuplet.Notes) {
  1006. for (const note of notes) {
  1007. //const stavenote: StemmableNote = (gve as VexFlowVoiceEntry).vfStaveNote;
  1008. //console.log("note " + note.ToString() + ", stavenote type: " + stavenote.getNoteType());
  1009. if (note.NoteTypeXml >= NoteType.QUARTER || // quarter note or longer: don't beam
  1010. // TODO: don't take Note (head) type from XML, but from current model,
  1011. // so that rendering can react dynamically to changes compared to the XML.
  1012. // however, taking the note length as fraction is tricky because of tuplets.
  1013. // a quarter in a triplet has length < quarter, but quarter note head, which Vexflow can't beam.
  1014. note.ParentVoiceEntry.IsGrace ||
  1015. note.isRest() && !this.rules.AutoBeamOptions.beam_rests) {
  1016. tupletContainsUnbeamableNote = true;
  1017. break;
  1018. }
  1019. }
  1020. if (tupletContainsUnbeamableNote) {
  1021. break;
  1022. }
  1023. }
  1024. if (!currentTuplet) {
  1025. currentTuplet = noteTuplet;
  1026. } else {
  1027. if (currentTuplet !== noteTuplet) { // new tuplet, finish old one
  1028. if (tupletNotesToAutoBeam.length > 1) {
  1029. const beamVFNotes: StaveNote[] = [];
  1030. for (const tupletNote of tupletNotesToAutoBeam) {
  1031. beamVFNotes.push(tupletNote.vfStaveNote);
  1032. }
  1033. const vfBeam: VF.Beam = new VF.Beam(beamVFNotes, true);
  1034. if (this.rules.FlatBeams) {
  1035. (<any>vfBeam).render_options.flat_beams = true;
  1036. (<any>vfBeam).render_options.flat_beam_offset = this.rules.FlatBeamOffset;
  1037. (<any>vfBeam).render_options.flat_beam_offset_per_beam = this.rules.FlatBeamOffsetPerBeam;
  1038. }
  1039. this.autoTupletVfBeams.push(vfBeam);
  1040. const osmdBeam: Beam = new Beam(autoBeamId++);
  1041. osmdBeam.AutoGenerated = true;
  1042. for (const tupletNote of tupletNotesToAutoBeam) {
  1043. osmdBeam.addNoteToBeam(tupletNote.sourceNote);
  1044. }
  1045. }
  1046. tupletNotesToAutoBeam = [];
  1047. currentTuplet = noteTuplet;
  1048. }
  1049. }
  1050. if (!tupletContainsUnbeamableNote) {
  1051. tupletNotesToAutoBeam.push(linkedNote);
  1052. }
  1053. continue;
  1054. } else {
  1055. currentTuplet = undefined;
  1056. }
  1057. consecutiveBeamableNotes.push(linkedNote); // also happens on new beat
  1058. }
  1059. }
  1060. if (tupletNotesToAutoBeam.length >= 2) {
  1061. const beamVFNotes: StaveNote[] = [];
  1062. for (const tupletNote of tupletNotesToAutoBeam) {
  1063. beamVFNotes.push(tupletNote.vfStaveNote);
  1064. }
  1065. const vfBeam: VF.Beam = new VF.Beam(beamVFNotes, true);
  1066. if (this.rules.FlatBeams) {
  1067. (<any>vfBeam).render_options.flat_beams = true;
  1068. (<any>vfBeam).render_options.flat_beam_offset = this.rules.FlatBeamOffset;
  1069. (<any>vfBeam).render_options.flat_beam_offset_per_beam = this.rules.FlatBeamOffsetPerBeam;
  1070. }
  1071. this.autoTupletVfBeams.push(vfBeam);
  1072. const osmdBeam: Beam = new Beam(autoBeamId++);
  1073. osmdBeam.AutoGenerated = true;
  1074. for (const tupletNote of tupletNotesToAutoBeam) {
  1075. osmdBeam.addNoteToBeam(tupletNote.sourceNote);
  1076. }
  1077. }
  1078. if (consecutiveBeamableNotes.length >= 2) {
  1079. for (const note of consecutiveBeamableNotes) {
  1080. notesToAutoBeam.push(note);
  1081. }
  1082. separateAutoBeams.push(notesToAutoBeam);
  1083. }
  1084. // create options for generateBeams
  1085. const autoBeamOptions: AutoBeamOptions = this.rules.AutoBeamOptions;
  1086. const generateBeamOptions: any = {
  1087. beam_middle_only: autoBeamOptions.beam_middle_rests_only,
  1088. beam_rests: autoBeamOptions.beam_rests,
  1089. maintain_stem_directions: autoBeamOptions.maintain_stem_directions,
  1090. };
  1091. if (autoBeamOptions.groups && autoBeamOptions.groups.length) {
  1092. const groups: VF.Fraction[] = [];
  1093. for (const fraction of autoBeamOptions.groups) {
  1094. groups.push(new VF.Fraction(fraction[0], fraction[1]));
  1095. }
  1096. generateBeamOptions.groups = groups;
  1097. }
  1098. for (const notesForSeparateAutoBeam of separateAutoBeams) {
  1099. const beamVFNotes: StaveNote[] = [];
  1100. for (const linkedNote of notesForSeparateAutoBeam) {
  1101. beamVFNotes.push(linkedNote.vfStaveNote);
  1102. }
  1103. const newBeams: VF.Beam[] = VF.Beam.generateBeams(beamVFNotes, generateBeamOptions);
  1104. for (const vfBeam of newBeams) {
  1105. if (this.rules.FlatBeams) {
  1106. (<any>vfBeam).render_options.flat_beams = true;
  1107. (<any>vfBeam).render_options.flat_beam_offset = this.rules.FlatBeamOffset;
  1108. (<any>vfBeam).render_options.flat_beam_offset_per_beam = this.rules.FlatBeamOffsetPerBeam;
  1109. }
  1110. this.autoVfBeams.push(vfBeam);
  1111. }
  1112. }
  1113. }
  1114. /**
  1115. * Complete the creation of VexFlow Tuplets in this measure
  1116. */
  1117. public finalizeTuplets(): void {
  1118. // The following line resets the created VF Tuplets and
  1119. // created them brand new. Is this needed? And more importantly,
  1120. // should the old tuplets be removed manually from the notes?
  1121. this.vftuplets = {};
  1122. for (const voiceID in this.tuplets) {
  1123. if (this.tuplets.hasOwnProperty(voiceID)) {
  1124. let vftuplets: VF.Tuplet[] = this.vftuplets[voiceID];
  1125. if (!vftuplets) {
  1126. vftuplets = this.vftuplets[voiceID] = [];
  1127. }
  1128. for (const tupletBuilder of this.tuplets[voiceID]) {
  1129. const tupletStaveNotes: VF.StaveNote[] = [];
  1130. const tupletVoiceEntries: VexFlowVoiceEntry[] = tupletBuilder[1];
  1131. for (const tupletVoiceEntry of tupletVoiceEntries) {
  1132. tupletStaveNotes.push(((tupletVoiceEntry).vfStaveNote as StaveNote));
  1133. }
  1134. if (tupletStaveNotes.length > 1) {
  1135. const tuplet: Tuplet = tupletBuilder[0];
  1136. const notesOccupied: number = tuplet.Notes[0][0].NormalNotes;
  1137. const bracketed: boolean = tuplet.shouldBeBracketed(
  1138. this.rules.TupletsBracketedUseXMLValue,
  1139. this.rules.TupletsBracketed,
  1140. this.rules.TripletsBracketed
  1141. );
  1142. let location: number = VF.Tuplet.LOCATION_TOP;
  1143. if (tuplet.tupletLabelNumberPlacement === PlacementEnum.Below) {
  1144. location = VF.Tuplet.LOCATION_BOTTOM;
  1145. }
  1146. const vftuplet: VF.Tuplet = new VF.Tuplet(tupletStaveNotes,
  1147. {
  1148. bracketed: bracketed,
  1149. location: location,
  1150. notes_occupied: notesOccupied,
  1151. num_notes: tuplet.TupletLabelNumber, //, location: -1, ratioed: true
  1152. ratioed: this.rules.TupletsRatioed,
  1153. });
  1154. vftuplets.push(vftuplet);
  1155. } else {
  1156. log.debug("Warning! Tuplet with no notes! Trying to ignore, but this is a serious problem.");
  1157. }
  1158. }
  1159. }
  1160. }
  1161. }
  1162. public layoutStaffEntry(graphicalStaffEntry: GraphicalStaffEntry): void {
  1163. return;
  1164. }
  1165. public graphicalMeasureCreatedCalculations(): void {
  1166. let graceSlur: boolean;
  1167. let graceGVoiceEntriesBefore: GraphicalVoiceEntry[] = [];
  1168. const graveGVoiceEntriesAdded: GraphicalVoiceEntry[] = [];
  1169. for (const graphicalStaffEntry of this.staffEntries as VexFlowStaffEntry[]) {
  1170. graceSlur = false;
  1171. graceGVoiceEntriesBefore = [];
  1172. // create vex flow Stave Notes:
  1173. for (const gve of graphicalStaffEntry.graphicalVoiceEntries) {
  1174. if (gve.parentVoiceEntry.IsGrace) {
  1175. // save grace notes for the next non-grace note
  1176. graceGVoiceEntriesBefore.push(gve);
  1177. graveGVoiceEntriesAdded.push(gve);
  1178. if (!graceSlur) {
  1179. graceSlur = gve.parentVoiceEntry.GraceSlur;
  1180. }
  1181. continue;
  1182. }
  1183. (gve as VexFlowVoiceEntry).vfStaveNote = VexFlowConverter.StaveNote(gve);
  1184. //if (!gve.notes[0].sourceNote.PrintObject) {
  1185. // note can now also be added as StaveNote instead of GhostNote, because we set it to transparent
  1186. // previous method: add as GhostNote instead of StaveNote. Can cause formatting issues if critical notes are missing in the measure
  1187. // don't render note. add ghost note, otherwise Vexflow can have issues with layouting when voices not complete.
  1188. //(gve as VexFlowVoiceEntry).vfStaveNote = VexFlowConverter.GhostNote(gve.notes[0].sourceNote.Length);
  1189. //graceGVoiceEntriesBefore = []; // if note is not rendered, its grace notes shouldn't be rendered, might need to be removed
  1190. //continue;
  1191. //}
  1192. if (graceGVoiceEntriesBefore.length > 0) {
  1193. // add grace notes that came before this main note to a GraceNoteGroup in Vexflow, attached to the main note
  1194. const graceNotes: VF.GraceNote[] = [];
  1195. for (let i: number = 0; i < graceGVoiceEntriesBefore.length; i++) {
  1196. const gveGrace: VexFlowVoiceEntry = <VexFlowVoiceEntry>graceGVoiceEntriesBefore[i];
  1197. //if (gveGrace.notes[0].sourceNote.PrintObject) {
  1198. // grace notes should generally be rendered independently of main note instead of skipped if main note is invisible
  1199. // could be an option to make grace notes transparent if main note is transparent. set grace notes' PrintObject to false then.
  1200. gveGrace.GraceSlash = gveGrace.parentVoiceEntry.GraceNoteSlash;
  1201. if (i > 0) {
  1202. gveGrace.GraceSlash = false; // without this, Vexflow draws multiple grace slashes, which looks wrong.
  1203. }
  1204. const vfStaveNote: StaveNote = VexFlowConverter.StaveNote(gveGrace);
  1205. gveGrace.vfStaveNote = vfStaveNote;
  1206. graceNotes.push(vfStaveNote);
  1207. }
  1208. const graceNoteGroup: VF.GraceNoteGroup = new VF.GraceNoteGroup(graceNotes, graceSlur);
  1209. (graceNoteGroup as any).spacing = this.rules.GraceNoteGroupXMargin * 10;
  1210. ((gve as VexFlowVoiceEntry).vfStaveNote as StaveNote).addModifier(0, graceNoteGroup);
  1211. graceGVoiceEntriesBefore = [];
  1212. }
  1213. }
  1214. }
  1215. // remaining grace notes at end of measure, turned into stand-alone grace notes:
  1216. if (graceGVoiceEntriesBefore.length > 0) {
  1217. for (const graceGve of graceGVoiceEntriesBefore) {
  1218. (graceGve as VexFlowVoiceEntry).vfStaveNote = VexFlowConverter.StaveNote(graceGve);
  1219. graceGve.parentVoiceEntry.GraceAfterMainNote = true;
  1220. }
  1221. }
  1222. // const t0: number = performance.now();
  1223. this.finalizeBeams();
  1224. // const t1: number = performance.now();
  1225. // console.log("Call to finalizeBeams in VexFlowMeasure took " + (t1 - t0) + " milliseconds.");
  1226. this.finalizeTuplets();
  1227. const voices: Voice[] = this.getVoicesWithinMeasure();
  1228. // Calculate offsets for fingerings
  1229. if (this.rules.RenderFingerings) {
  1230. for (const graphicalStaffEntry of this.staffEntries as VexFlowStaffEntry[]) {
  1231. graphicalStaffEntry.setModifierXOffsets();
  1232. }
  1233. }
  1234. for (const voice of voices) {
  1235. if (!voice) {
  1236. continue;
  1237. }
  1238. //const isMainVoice: boolean = !(voice instanceof LinkedVoice);
  1239. // add a vexFlow voice for this voice:
  1240. this.vfVoices[voice.VoiceId] = new VF.Voice({
  1241. beat_value: this.parentSourceMeasure.ActiveTimeSignature.Denominator,
  1242. num_beats: this.parentSourceMeasure.ActiveTimeSignature.Numerator,
  1243. resolution: VF.RESOLUTION,
  1244. }).setMode(VF.Voice.Mode.SOFT);
  1245. const restFilledEntries: GraphicalVoiceEntry[] = this.getRestFilledVexFlowStaveNotesPerVoice(voice);
  1246. // .sort((a,b) => a.)
  1247. // create vex flow voices and add tickables to it:
  1248. for (const voiceEntry of restFilledEntries) {
  1249. if (voiceEntry.parentVoiceEntry) {
  1250. if (voiceEntry.parentVoiceEntry.IsGrace && !voiceEntry.parentVoiceEntry.GraceAfterMainNote) {
  1251. continue;
  1252. }
  1253. }
  1254. const vexFlowVoiceEntry: VexFlowVoiceEntry = voiceEntry as VexFlowVoiceEntry;
  1255. if (vexFlowVoiceEntry.vfStaveNote.getTicks().denominator === 0) {
  1256. vexFlowVoiceEntry.vfStaveNote.getTicks().denominator = 1;
  1257. // TODO not sure why the ticks aren't calculated correctly, see #1073
  1258. // if denominator === 0, addTickable() below goes into an infinite loop.
  1259. // continue; // previous solution, but can lead to valid notes skipped, further problems, see #1073
  1260. }
  1261. if (voiceEntry.notes.length === 0 || !voiceEntry.notes[0] || !voiceEntry.notes[0].sourceNote.PrintObject) {
  1262. // GhostNote, don't add modifiers like in-measure clefs
  1263. this.vfVoices[voice.VoiceId].addTickable(vexFlowVoiceEntry.vfStaveNote);
  1264. continue;
  1265. }
  1266. // check for in-measure clefs:
  1267. // Note: we used to only add clefs in main voice to not add them twice,
  1268. // but there are many legitimate clefs e.g. in 2nd voices, and this doesn't seem to cause issues.
  1269. //if (isMainVoice) {
  1270. const vfse: VexFlowStaffEntry = vexFlowVoiceEntry.parentStaffEntry as VexFlowStaffEntry;
  1271. if (vfse && vfse.vfClefBefore) {
  1272. // add clef as NoteSubGroup so that we get modifier layouting
  1273. const clefModifier: NoteSubGroup = new NoteSubGroup( [vfse.vfClefBefore] );
  1274. // The cast is necesary because...vexflow -> see types
  1275. if (vexFlowVoiceEntry.vfStaveNote.getCategory && vexFlowVoiceEntry.vfStaveNote.getCategory() === "stavenotes") {
  1276. // GhostNotes and other StemmableNotes don't have this function
  1277. (vexFlowVoiceEntry.vfStaveNote as VF.StaveNote).addModifier(0, clefModifier);
  1278. }
  1279. }
  1280. // add fingering
  1281. if (voiceEntry.parentVoiceEntry && this.rules.RenderFingerings) {
  1282. if (this.rules.FingeringPosition === PlacementEnum.Left ||
  1283. this.rules.FingeringPosition === PlacementEnum.Right) {
  1284. this.createFingerings(voiceEntry);
  1285. } // else created in MusicSheetCalculater.createFingerings() as Labels
  1286. this.createStringNumber(voiceEntry);
  1287. }
  1288. // add Arpeggio
  1289. this.createArpeggio(voiceEntry);
  1290. this.vfVoices[voice.VoiceId].addTickable(vexFlowVoiceEntry.vfStaveNote);
  1291. }
  1292. }
  1293. this.setStemDirectionFromVexFlow();
  1294. for (const graceGVoiceEntry of graveGVoiceEntriesAdded) {
  1295. this.createFingerings(graceGVoiceEntry);
  1296. this.createStringNumber(graceGVoiceEntry);
  1297. this.createArpeggio(graceGVoiceEntry);
  1298. }
  1299. this.createArticulations();
  1300. this.createOrnaments();
  1301. }
  1302. private createArpeggio(voiceEntry: GraphicalVoiceEntry): void {
  1303. if (voiceEntry.parentVoiceEntry && voiceEntry.parentVoiceEntry.Arpeggio) {
  1304. const arpeggio: Arpeggio = voiceEntry.parentVoiceEntry.Arpeggio;
  1305. // TODO right now our arpeggio object has all arpeggio notes from arpeggios across all voices.
  1306. // see VoiceGenerator. Doesn't matter for Vexflow for now though
  1307. if (voiceEntry.notes && voiceEntry.notes.length > 1) {
  1308. const type: VF.Stroke.Type = VexFlowConverter.StrokeTypeFromArpeggioType(arpeggio.type);
  1309. const stroke: VF.Stroke = new VF.Stroke(type, {
  1310. all_voices: this.rules.ArpeggiosGoAcrossVoices
  1311. // default: false. This causes arpeggios to always go across all voices, which is often unwanted.
  1312. // also, this can cause infinite height of stroke, see #546
  1313. });
  1314. //if (arpeggio.notes.length === vexFlowVoiceEntry.notes.length) { // different workaround for endless y bug
  1315. if (this.rules.RenderArpeggios) {
  1316. (voiceEntry as VexFlowVoiceEntry).vfStaveNote.addStroke(0, stroke);
  1317. }
  1318. } else {
  1319. log.debug(`[OSMD] arpeggio in measure ${this.MeasureNumber} could not be drawn.
  1320. voice entry had less than two notes, arpeggio is likely between voice entries, not currently supported in Vexflow.`);
  1321. // TODO: create new arpeggio with all the arpeggio's notes (arpeggio.notes), perhaps with GhostNotes in a new vfStaveNote. not easy.
  1322. }
  1323. }
  1324. }
  1325. /**
  1326. * Copy the stem directions chosen by VexFlow to the StemDirection variable of the graphical notes
  1327. */
  1328. private setStemDirectionFromVexFlow(): void {
  1329. //if StemDirection was not set then read out what VexFlow has chosen
  1330. for ( const vfStaffEntry of this.staffEntries ) {
  1331. for ( const gVoiceEntry of vfStaffEntry.graphicalVoiceEntries) {
  1332. for ( const gnote of gVoiceEntry.notes) {
  1333. const vfnote: [StemmableNote , number] = (gnote as VexFlowGraphicalNote).vfnote;
  1334. if (!vfnote || !vfnote[0]) {
  1335. continue;
  1336. }
  1337. const vfStemDir: number = vfnote[0].getStemDirection();
  1338. switch (vfStemDir) {
  1339. case (VF.Stem.UP):
  1340. gVoiceEntry.parentVoiceEntry.StemDirection = StemDirectionType.Up;
  1341. break;
  1342. case (VF.Stem.DOWN):
  1343. gVoiceEntry.parentVoiceEntry.StemDirection = StemDirectionType.Down;
  1344. break;
  1345. default:
  1346. }
  1347. }
  1348. }
  1349. }
  1350. }
  1351. /**
  1352. * Create the articulations for all notes of the current staff entry
  1353. */
  1354. protected createArticulations(): void {
  1355. for (let idx: number = 0, len: number = this.staffEntries.length; idx < len; ++idx) {
  1356. const graphicalStaffEntry: VexFlowStaffEntry = (this.staffEntries[idx] as VexFlowStaffEntry);
  1357. // create vex flow articulation:
  1358. const graphicalVoiceEntries: GraphicalVoiceEntry[] = graphicalStaffEntry.graphicalVoiceEntries;
  1359. for (const gve of graphicalVoiceEntries) {
  1360. const vfStaveNote: StemmableNote = (gve as VexFlowVoiceEntry).vfStaveNote;
  1361. VexFlowConverter.generateArticulations(vfStaveNote, gve.notes[0], this.rules);
  1362. }
  1363. }
  1364. }
  1365. /**
  1366. * Create the ornaments for all notes of the current staff entry
  1367. */
  1368. protected createOrnaments(): void {
  1369. for (let idx: number = 0, len: number = this.staffEntries.length; idx < len; ++idx) {
  1370. const graphicalStaffEntry: VexFlowStaffEntry = (this.staffEntries[idx] as VexFlowStaffEntry);
  1371. const gvoices: { [voiceID: number]: GraphicalVoiceEntry } = graphicalStaffEntry.graphicalVoiceEntries;
  1372. for (const voiceID in gvoices) {
  1373. if (gvoices.hasOwnProperty(voiceID)) {
  1374. const vfStaveNote: StemmableNote = (gvoices[voiceID] as VexFlowVoiceEntry).vfStaveNote;
  1375. const ornamentContainer: OrnamentContainer = gvoices[voiceID].notes[0].sourceNote.ParentVoiceEntry.OrnamentContainer;
  1376. if (ornamentContainer) {
  1377. VexFlowConverter.generateOrnaments(vfStaveNote, ornamentContainer);
  1378. }
  1379. }
  1380. }
  1381. }
  1382. }
  1383. protected createFingerings(voiceEntry: GraphicalVoiceEntry): void {
  1384. const vexFlowVoiceEntry: VexFlowVoiceEntry = voiceEntry as VexFlowVoiceEntry;
  1385. let numberOfFingerings: number = 0;
  1386. // count total number of fingerings
  1387. for (const note of voiceEntry.notes) {
  1388. const fingering: TechnicalInstruction = note.sourceNote.Fingering;
  1389. if (fingering) {
  1390. numberOfFingerings++;
  1391. }
  1392. }
  1393. let fingeringIndex: number = -1;
  1394. for (const note of voiceEntry.notes) {
  1395. const fingering: TechnicalInstruction = note.sourceNote.Fingering;
  1396. if (!fingering) {
  1397. fingeringIndex++;
  1398. continue;
  1399. }
  1400. fingeringIndex++; // 0 for first fingering
  1401. let fingeringPosition: PlacementEnum = this.rules.FingeringPosition;
  1402. //currently only relevant for grace notes, because we create other fingerings above/below in MusicSheetCalculator.createFingerings
  1403. if (this.rules.FingeringPositionGrace === PlacementEnum.AboveOrBelow) {
  1404. //if (this.rules.FingeringPosition === PlacementEnum.AboveOrBelow) {
  1405. if (this.isUpperStaffOfInstrument()) { // (e.g. piano right hand)
  1406. fingeringPosition = PlacementEnum.Above;
  1407. } else if (this.isLowerStaffOfInstrument()) {
  1408. fingeringPosition = PlacementEnum.Below;
  1409. }
  1410. }
  1411. if (fingering.placement !== PlacementEnum.NotYetDefined) {
  1412. fingeringPosition = fingering.placement;
  1413. }
  1414. let offsetX: number = this.rules.FingeringOffsetX;
  1415. let modifierPosition: number; // VF.Stavemodifier.Position
  1416. switch (fingeringPosition) {
  1417. default:
  1418. case PlacementEnum.Left:
  1419. modifierPosition = VF.StaveModifier.Position.LEFT;
  1420. offsetX -= note.baseFingeringXOffset * unitInPixels;
  1421. break;
  1422. case PlacementEnum.Right:
  1423. modifierPosition = VF.StaveModifier.Position.RIGHT;
  1424. offsetX += note.baseFingeringXOffset * unitInPixels;
  1425. break;
  1426. case PlacementEnum.Above:
  1427. modifierPosition = VF.StaveModifier.Position.ABOVE;
  1428. break;
  1429. case PlacementEnum.Below:
  1430. modifierPosition = VF.StaveModifier.Position.BELOW;
  1431. break;
  1432. case PlacementEnum.NotYetDefined: // automatic fingering placement, could be more complex/customizable
  1433. const sourceStaff: Staff = voiceEntry.parentStaffEntry.sourceStaffEntry.ParentStaff;
  1434. if (voiceEntry.notes.length > 1 || voiceEntry.parentStaffEntry.graphicalVoiceEntries.length > 1) {
  1435. modifierPosition = VF.StaveModifier.Position.LEFT;
  1436. } else if (sourceStaff.idInMusicSheet === 0) {
  1437. modifierPosition = VF.StaveModifier.Position.ABOVE;
  1438. fingeringPosition = PlacementEnum.Above;
  1439. } else {
  1440. modifierPosition = VF.StaveModifier.Position.BELOW;
  1441. fingeringPosition = PlacementEnum.Below;
  1442. }
  1443. }
  1444. const fretFinger: VF.FretHandFinger = new VF.FretHandFinger(fingering.value);
  1445. fretFinger.setPosition(modifierPosition);
  1446. fretFinger.setOffsetX(offsetX);
  1447. if (fingeringPosition === PlacementEnum.Above || fingeringPosition === PlacementEnum.Below) {
  1448. const offsetYSign: number = fingeringPosition === PlacementEnum.Above ? -1 : 1; // minus y is up
  1449. const ordering: number = fingeringPosition === PlacementEnum.Above ? fingeringIndex :
  1450. numberOfFingerings - 1 - fingeringIndex; // reverse order for fingerings below staff
  1451. if (this.rules.FingeringInsideStafflines && numberOfFingerings > 1) { // y-shift for single fingering is ok
  1452. // experimental, bounding boxes wrong for fretFinger above/below, better would be creating Labels
  1453. // set y-shift. vexflow fretfinger simply places directly above/below note
  1454. const perFingeringShift: number = fretFinger.getWidth() / 2;
  1455. const shiftCount: number = numberOfFingerings * 2.5;
  1456. fretFinger.setOffsetY(offsetYSign * (ordering + shiftCount) * perFingeringShift);
  1457. } else if (!this.rules.FingeringInsideStafflines) { // use StringNumber for placement above/below stafflines
  1458. const stringNumber: VF.StringNumber = new VF.StringNumber(fingering.value);
  1459. stringNumber.radius = 0; // hack to remove the circle around the number
  1460. stringNumber.setPosition(modifierPosition);
  1461. stringNumber.setOffsetY(offsetYSign * ordering * stringNumber.getWidth() * 2 / 3);
  1462. // Vexflow made a mess with the addModifier signature that changes through each class so we just cast to any :(
  1463. vexFlowVoiceEntry.vfStaveNote.addModifier((fingeringIndex as any), (stringNumber as any));
  1464. continue;
  1465. }
  1466. }
  1467. // if (vexFlowVoiceEntry.vfStaveNote.getCategory() === "tabnotes") {
  1468. // TODO this doesn't work yet for tabnotes. don't add fingering for tabs for now.
  1469. // vexFlowVoiceEntry.vfStaveNote.addModifier(fretFinger, fingeringIndex);
  1470. // Vexflow made a mess with the addModifier signature that changes through each class so we just cast to any :(
  1471. vexFlowVoiceEntry.vfStaveNote.addModifier((fingeringIndex as any), (fretFinger as any));
  1472. }
  1473. }
  1474. protected createStringNumber(voiceEntry: GraphicalVoiceEntry): void {
  1475. if (!this.rules.RenderStringNumbersClassical) {
  1476. return;
  1477. }
  1478. const vexFlowVoiceEntry: VexFlowVoiceEntry = voiceEntry as VexFlowVoiceEntry;
  1479. voiceEntry.notes.forEach((note, stringIndex) => {
  1480. const stringInstruction: TechnicalInstruction = note.sourceNote.StringInstruction;
  1481. if (stringInstruction) {
  1482. let stringNumber: string = stringInstruction.value;
  1483. switch (stringNumber) {
  1484. case "1":
  1485. stringNumber = "I";
  1486. break;
  1487. case "2":
  1488. stringNumber = "II";
  1489. break;
  1490. case "3":
  1491. stringNumber = "III";
  1492. break;
  1493. case "4":
  1494. stringNumber = "IV";
  1495. break;
  1496. case "5":
  1497. stringNumber = "V";
  1498. break;
  1499. case "6":
  1500. stringNumber = "VI";
  1501. break;
  1502. default:
  1503. // log.warn("stringNumber > 6 not supported"); // TODO do we need to support more?
  1504. // leave stringNumber as is, warning not really necessary
  1505. }
  1506. const vfStringNumber: VF.StringNumber = new VF.StringNumber(stringNumber);
  1507. // Remove circle from string number. Not needed for
  1508. // disambiguation from fingerings since we use Roman
  1509. // Numerals for RenderStringNumbersClassical
  1510. vfStringNumber.radius = 0;
  1511. const offsetY: number = -this.rules.StringNumberOffsetY;
  1512. // if (note.sourceNote.halfTone < 50) { // place string number a little higher for notes with ledger lines below staff
  1513. // // TODO also check for treble clef (adjust for viola, cello, etc)
  1514. // offsetY += 10;
  1515. // }
  1516. if (voiceEntry.notes.length > 1 || voiceEntry.parentStaffEntry.graphicalVoiceEntries.length > 1) {
  1517. vfStringNumber.setOffsetX(note.baseStringNumberXOffset * 13);
  1518. vfStringNumber.setPosition(VF.Modifier.Position.RIGHT);
  1519. } else {
  1520. vfStringNumber.setPosition(VF.Modifier.Position.ABOVE);
  1521. }
  1522. vfStringNumber.setOffsetY(offsetY);
  1523. vexFlowVoiceEntry.vfStaveNote.addModifier((stringIndex as any), (vfStringNumber as any)); // see addModifier() above
  1524. }
  1525. });
  1526. }
  1527. /**
  1528. * Creates a line from 'top' to this measure, of type 'lineType'
  1529. * @param top
  1530. * @param lineType
  1531. */
  1532. public lineTo(top: VexFlowMeasure, lineType: any): void {
  1533. const connector: VF.StaveConnector = new VF.StaveConnector(top.getVFStave(), this.stave);
  1534. connector.setType(lineType);
  1535. this.connectors.push(connector);
  1536. }
  1537. /**
  1538. * Return the VexFlow Stave corresponding to this graphicalMeasure
  1539. * @returns {VF.Stave}
  1540. */
  1541. public getVFStave(): VF.Stave {
  1542. return this.stave;
  1543. }
  1544. /**
  1545. * After re-running the formatting on the VexFlow Stave, update the
  1546. * space needed by Instructions (in VexFlow: StaveModifiers)
  1547. */
  1548. protected updateInstructionWidth(): void {
  1549. let vfBeginInstructionsWidth: number = 0;
  1550. let vfEndInstructionsWidth: number = 0;
  1551. const modifiers: VF.StaveModifier[] = this.stave.getModifiers();
  1552. for (const mod of modifiers) {
  1553. if (mod.getPosition() === StavePositionEnum.BEGIN) { //VF.StaveModifier.Position.BEGIN) {
  1554. vfBeginInstructionsWidth += mod.getWidth() + mod.getPadding(undefined);
  1555. } else if (mod.getPosition() === StavePositionEnum.END) { //VF.StaveModifier.Position.END) {
  1556. vfEndInstructionsWidth += mod.getWidth() + mod.getPadding(undefined);
  1557. }
  1558. }
  1559. this.beginInstructionsWidth = (vfBeginInstructionsWidth ?? 0) / unitInPixels;
  1560. this.endInstructionsWidth = (vfEndInstructionsWidth ?? 0) / unitInPixels;
  1561. }
  1562. public addStaveTie(stavetie: VF.StaveTie, graphicalTie: GraphicalTie): void {
  1563. this.vfTies.push(stavetie);
  1564. graphicalTie.vfTie = stavetie;
  1565. if (graphicalTie.Tie.TieDirection === PlacementEnum.Below) {
  1566. (stavetie as any).setDirection(1);
  1567. }
  1568. }
  1569. }
  1570. // Gives the position of the Stave - replaces the function get Position() in the description of class StaveModifier in vexflow.d.ts
  1571. // The latter gave an error because function cannot be defined in the class descriptions in vexflow.d.ts
  1572. export enum StavePositionEnum {
  1573. LEFT = 1,
  1574. RIGHT = 2,
  1575. ABOVE = 3,
  1576. BELOW = 4,
  1577. BEGIN = 5,
  1578. END = 6
  1579. }