VexFlowMusicSheetCalculator.ts 47 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979
  1. import { MusicSheetCalculator } from "../MusicSheetCalculator";
  2. import { VexFlowGraphicalSymbolFactory } from "./VexFlowGraphicalSymbolFactory";
  3. import { GraphicalMeasure } from "../GraphicalMeasure";
  4. import { StaffLine } from "../StaffLine";
  5. import { VoiceEntry } from "../../VoiceData/VoiceEntry";
  6. import { GraphicalNote } from "../GraphicalNote";
  7. import { GraphicalStaffEntry } from "../GraphicalStaffEntry";
  8. import { GraphicalTie } from "../GraphicalTie";
  9. import { Tie } from "../../VoiceData/Tie";
  10. import { SourceMeasure } from "../../VoiceData/SourceMeasure";
  11. import { MultiExpression } from "../../VoiceData/Expressions/MultiExpression";
  12. import { RepetitionInstruction } from "../../VoiceData/Instructions/RepetitionInstruction";
  13. import { Beam } from "../../VoiceData/Beam";
  14. import { ClefInstruction } from "../../VoiceData/Instructions/ClefInstruction";
  15. import { OctaveEnum, OctaveShift } from "../../VoiceData/Expressions/ContinuousExpressions/OctaveShift";
  16. import { Fraction } from "../../../Common/DataObjects/Fraction";
  17. import { LyricWord } from "../../VoiceData/Lyrics/LyricsWord";
  18. import { OrnamentContainer } from "../../VoiceData/OrnamentContainer";
  19. import { ArticulationEnum } from "../../VoiceData/VoiceEntry";
  20. import { Tuplet } from "../../VoiceData/Tuplet";
  21. import { VexFlowMeasure } from "./VexFlowMeasure";
  22. import { VexFlowTextMeasurer } from "./VexFlowTextMeasurer";
  23. import Vex = require("vexflow");
  24. import * as log from "loglevel";
  25. import { unitInPixels } from "./VexFlowMusicSheetDrawer";
  26. import { VexFlowGraphicalNote } from "./VexFlowGraphicalNote";
  27. import { TechnicalInstruction } from "../../VoiceData/Instructions/TechnicalInstruction";
  28. import { GraphicalLyricEntry } from "../GraphicalLyricEntry";
  29. import { GraphicalLabel } from "../GraphicalLabel";
  30. import { LyricsEntry } from "../../VoiceData/Lyrics/LyricsEntry";
  31. import { GraphicalLyricWord } from "../GraphicalLyricWord";
  32. import { VexFlowStaffEntry } from "./VexFlowStaffEntry";
  33. import { VexFlowOctaveShift } from "./VexFlowOctaveShift";
  34. import { VexFlowInstantaneousDynamicExpression } from "./VexFlowInstantaneousDynamicExpression";
  35. import { Slur } from "../../VoiceData/Expressions/ContinuousExpressions/Slur";
  36. /* VexFlow Version - for later use
  37. // import { VexFlowSlur } from "./VexFlowSlur";
  38. // import { VexFlowStaffLine } from "./VexFlowStaffLine";
  39. // import { VexFlowVoiceEntry } from "./VexFlowVoiceEntry";
  40. */
  41. import { PointF2D } from "../../../Common/DataObjects/PointF2D";
  42. import { TextAlignmentEnum, TextAlignment } from "../../../Common/Enums/TextAlignment";
  43. import { GraphicalSlur } from "../GraphicalSlur";
  44. import { BoundingBox } from "../BoundingBox";
  45. import { ContinuousDynamicExpression } from "../../VoiceData/Expressions/ContinuousExpressions/ContinuousDynamicExpression";
  46. import { VexFlowContinuousDynamicExpression } from "./VexFlowContinuousDynamicExpression";
  47. import { InstantaneousTempoExpression } from "../../VoiceData/Expressions";
  48. import { AlignRestOption } from "../../../OpenSheetMusicDisplay";
  49. import { VexFlowStaffLine } from "./VexFlowStaffLine";
  50. import { EngravingRules } from "..";
  51. export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
  52. /** space needed for a dash for lyrics spacing, calculated once */
  53. private dashSpace: number;
  54. public beamsNeedUpdate: boolean = false;
  55. constructor(rules: EngravingRules) {
  56. super();
  57. this.rules = rules;
  58. MusicSheetCalculator.symbolFactory = new VexFlowGraphicalSymbolFactory();
  59. MusicSheetCalculator.TextMeasurer = new VexFlowTextMeasurer(this.rules);
  60. }
  61. protected clearRecreatedObjects(): void {
  62. super.clearRecreatedObjects();
  63. for (const graphicalMeasures of this.graphicalMusicSheet.MeasureList) {
  64. for (const graphicalMeasure of graphicalMeasures) {
  65. (<VexFlowMeasure>graphicalMeasure).clean();
  66. }
  67. }
  68. }
  69. protected formatMeasures(): void {
  70. // let totalFinalizeBeamsTime: number = 0;
  71. for (const verticalMeasureList of this.graphicalMusicSheet.MeasureList) {
  72. const firstMeasure: VexFlowMeasure = verticalMeasureList[0] as VexFlowMeasure;
  73. // first measure has formatting method as lambda function object, but formats all measures. TODO this could be refactored
  74. firstMeasure.format();
  75. for (const measure of verticalMeasureList) {
  76. for (const staffEntry of measure.staffEntries) {
  77. (<VexFlowStaffEntry>staffEntry).calculateXPosition();
  78. }
  79. // const t0: number = performance.now();
  80. if (this.beamsNeedUpdate) { // finalizeBeams takes a few milliseconds, so we can save some performance here
  81. (measure as VexFlowMeasure).finalizeBeams(); // without this, when zooming a lot (e.g. 250%), beams keep their old, now wrong slope.
  82. // totalFinalizeBeamsTime += performance.now() - t0;
  83. // console.log("Total calls to finalizeBeams in VexFlowMusicSheetCalculator took " + totalFinalizeBeamsTime + " milliseconds.");
  84. }
  85. }
  86. }
  87. this.beamsNeedUpdate = false;
  88. }
  89. //protected clearSystemsAndMeasures(): void {
  90. // for (let measure of measures) {
  91. //
  92. // }
  93. //}
  94. /**
  95. * Calculates the x layout of the staff entries within the staff measures belonging to one source measure.
  96. * All staff entries are x-aligned throughout all vertically aligned staff measures.
  97. * This method is called within calculateXLayout.
  98. * The staff entries are aligned with minimum needed x distances.
  99. * The MinimumStaffEntriesWidth of every measure will be set - needed for system building.
  100. * Prepares the VexFlow formatter for later formatting
  101. * Does not calculate measure width from lyrics (which is called from MusicSheetCalculator)
  102. * @param measures
  103. * @returns the minimum required x width of the source measure (=list of staff measures)
  104. */
  105. protected calculateMeasureXLayout(measures: GraphicalMeasure[]): number {
  106. const visibleMeasures: GraphicalMeasure[] = [];
  107. for (const measure of measures) {
  108. visibleMeasures.push(measure);
  109. }
  110. measures = visibleMeasures;
  111. // Format the voices
  112. const allVoices: Vex.Flow.Voice[] = [];
  113. const formatter: Vex.Flow.Formatter = new Vex.Flow.Formatter();
  114. for (const measure of measures) {
  115. const mvoices: { [voiceID: number]: Vex.Flow.Voice; } = (measure as VexFlowMeasure).vfVoices;
  116. const voices: Vex.Flow.Voice[] = [];
  117. for (const voiceID in mvoices) {
  118. if (mvoices.hasOwnProperty(voiceID)) {
  119. voices.push(mvoices[voiceID]);
  120. allVoices.push(mvoices[voiceID]);
  121. }
  122. }
  123. if (voices.length === 0) {
  124. log.debug("Found a measure with no voices. Continuing anyway.", mvoices);
  125. // no need to log this, measures with no voices/notes are fine. see OSMDOptions.fillEmptyMeasuresWithWholeRest
  126. continue;
  127. }
  128. // all voices that belong to one stave are collectively added to create a common context in VexFlow.
  129. formatter.joinVoices(voices);
  130. }
  131. let minStaffEntriesWidth: number = 200;
  132. if (allVoices.length > 0) {
  133. // FIXME: The following ``+ 5.0'' is temporary: it was added as a workaround for
  134. // FIXME: a more relaxed formatting of voices
  135. minStaffEntriesWidth = formatter.preCalculateMinTotalWidth(allVoices) / unitInPixels + 5.0;
  136. // firstMeasure.formatVoices = (w: number) => {
  137. // formatter.format(allVoices, w);
  138. // };
  139. MusicSheetCalculator.setMeasuresMinStaffEntriesWidth(measures, minStaffEntriesWidth);
  140. const formatVoicesDefault: (w: number) => void = (w) => {
  141. formatter.format(allVoices, w);
  142. };
  143. const formatVoicesAlignRests: (w: number) => void = (w) => {
  144. formatter.format(allVoices, w, {
  145. align_rests: true,
  146. context: undefined
  147. });
  148. };
  149. for (const measure of measures) {
  150. // determine whether to align rests
  151. if (this.rules.AlignRests === AlignRestOption.Never) {
  152. (measure as VexFlowMeasure).formatVoices = formatVoicesDefault;
  153. } else if (this.rules.AlignRests === AlignRestOption.Always) {
  154. (measure as VexFlowMeasure).formatVoices = formatVoicesAlignRests;
  155. } else if (this.rules.AlignRests === AlignRestOption.Auto) {
  156. let alignRests: boolean = false;
  157. for (const staffEntry of measure.staffEntries) {
  158. let collidableVoiceEntries: number = 0;
  159. let numberOfRests: number = 0;
  160. for (const voiceEntry of staffEntry.graphicalVoiceEntries) {
  161. if (!voiceEntry.parentVoiceEntry.IsGrace) {
  162. if (voiceEntry && voiceEntry.notes && voiceEntry.notes[0] && voiceEntry.notes[0].sourceNote) {// TODO null chaining, TS 3.7
  163. if (voiceEntry.notes[0].sourceNote.PrintObject) { // only respect collision when not invisible
  164. collidableVoiceEntries++;
  165. }
  166. }
  167. }
  168. if (voiceEntry && voiceEntry.notes && voiceEntry.notes[0] && voiceEntry.notes[0].sourceNote) {// TODO null chaining, TS 3.7
  169. if (voiceEntry.notes[0].sourceNote.isRest() && voiceEntry.notes[0].sourceNote.PrintObject) {
  170. numberOfRests++; // only align rests if there is actually a rest (which could collide)
  171. }
  172. }
  173. if (collidableVoiceEntries > 1 && numberOfRests >= 1) {
  174. // TODO could add further checks like if any of the already checked voice entries actually collide
  175. alignRests = true;
  176. break;
  177. }
  178. }
  179. if (alignRests) {
  180. break;
  181. }
  182. }
  183. // set measure's format function
  184. if (alignRests) {
  185. (measure as VexFlowMeasure).formatVoices = formatVoicesAlignRests;
  186. } else {
  187. (measure as VexFlowMeasure).formatVoices = formatVoicesDefault;
  188. }
  189. }
  190. // format first measure with minimum width
  191. if (measure === measures[0]) {
  192. const vexflowMeasure: VexFlowMeasure = (measure as VexFlowMeasure);
  193. // prepare format function for voices, will be called later for formatting measure again
  194. //vexflowMeasure.formatVoices = formatVoicesDefault;
  195. // format now for minimum width, calculateMeasureWidthFromLyrics later
  196. vexflowMeasure.formatVoices(minStaffEntriesWidth * unitInPixels);
  197. } else {
  198. //(measure as VexFlowMeasure).formatVoices = undefined;
  199. // TODO why was the formatVoices function disabled for other measures? would now disable the new align rests option.
  200. }
  201. }
  202. }
  203. for (const graphicalMeasure of measures) {
  204. for (const staffEntry of graphicalMeasure.staffEntries) {
  205. // here the measure modifiers are not yet set, therefore the begin instruction width will be empty
  206. (<VexFlowStaffEntry>staffEntry).calculateXPosition();
  207. }
  208. }
  209. // calculateMeasureWidthFromLyrics() will be called from MusicSheetCalculator after this
  210. return minStaffEntriesWidth;
  211. }
  212. public calculateMeasureWidthFromLyrics(measuresVertical: GraphicalMeasure[], oldMinimumStaffEntriesWidth: number): number {
  213. let elongationFactorForMeasureWidth: number = 1;
  214. // information we need for the previous lyricsEntries to space the current one
  215. interface LyricEntryInfo {
  216. extend: boolean;
  217. labelWidth: number;
  218. lyricsXPosition: number;
  219. sourceNoteDuration: Fraction;
  220. text: string;
  221. measureNumber: number;
  222. }
  223. // holds lyrics entries for verses i
  224. interface LyricEntryDict {
  225. [i: number]: LyricEntryInfo;
  226. }
  227. for (const measure of measuresVertical) {
  228. const lastLyricEntryDict: LyricEntryDict = {}; // holds info about last lyrics entries for all verses j
  229. // for all staffEntries i, each containing the lyric entry for all verses at that timestamp in the measure
  230. for (let i: number = 0; i < measure.staffEntries.length; i++) {
  231. const staffEntry: GraphicalStaffEntry = measure.staffEntries[i];
  232. if (staffEntry.LyricsEntries.length === 0) {
  233. continue;
  234. }
  235. // for all verses j
  236. for (let j: number = 0; j < staffEntry.LyricsEntries.length; j++) {
  237. const lyricsEntry: GraphicalLyricEntry = staffEntry.LyricsEntries[j];
  238. // const lyricsEntryText = lyricsEntry.LyricsEntry.Text; // for easier debugging
  239. const lyricAlignment: TextAlignmentEnum = lyricsEntry.GraphicalLabel.Label.textAlignment;
  240. let minLyricsSpacing: number = this.rules.HorizontalBetweenLyricsDistance;
  241. // for quarter note in Vexflow, where spacing is halfed for each smaller note duration.
  242. let lyricOverlapAllowedIntoNextMeasure: number =
  243. this.rules.LyricOverlapAllowedIntoNextMeasure;
  244. // TODO allow more overlap if there are no lyrics in next measure
  245. // spacing for multi-syllable words
  246. if (lyricsEntry.ParentLyricWord) {
  247. if (lyricsEntry.LyricsEntry.SyllableIndex > 0) { // syllables after first
  248. // give a little more spacing for dash between syllables
  249. minLyricsSpacing = this.rules.BetweenSyllableMinimumDistance;
  250. if (TextAlignment.IsCenterAligned(lyricsEntry.GraphicalLabel.Label.textAlignment)) {
  251. minLyricsSpacing += 1.0; // TODO check for previous lyric alignment too. though center is not standard
  252. // without this, there's not enough space for dashes between long syllables on eigth notes
  253. }
  254. }
  255. const syllables: LyricsEntry[] = lyricsEntry.ParentLyricWord.GetLyricWord.Syllables;
  256. if (syllables.length > 1) {
  257. if (lyricsEntry.LyricsEntry.SyllableIndex < syllables.length - 1) {
  258. // if a middle syllable of a word, give less measure overlap into next measure, to give room for dash
  259. if (this.dashSpace === undefined) {
  260. this.dashSpace = 1.5;
  261. // better method, doesn't work:
  262. // this.dashLength = new GraphicalLabel(new Label("-"), this.rules.LyricsHeight, TextAlignmentEnum.CenterBottom)
  263. // .PositionAndShape.Size.width; // always returns 0
  264. }
  265. lyricOverlapAllowedIntoNextMeasure -= this.dashSpace;
  266. }
  267. }
  268. }
  269. const lyricsBbox: BoundingBox = lyricsEntry.GraphicalLabel.PositionAndShape;
  270. const lyricsLabelWidth: number = lyricsBbox.Size.width;
  271. const staffEntryXPosition: number = (staffEntry as VexFlowStaffEntry).PositionAndShape.RelativePosition.x;
  272. const lyricsXPosition: number = staffEntryXPosition + lyricsBbox.BorderMarginLeft;
  273. if (lastLyricEntryDict[j] !== undefined) {
  274. if (lastLyricEntryDict[j].extend) {
  275. // TODO handle extend of last entry (extend is stored in lyrics entry of preceding syllable)
  276. // only necessary for center alignment
  277. }
  278. }
  279. let spacingNeededToLastLyric: number;
  280. let currentSpacingToLastLyric: number; // undefined for first lyric in measure
  281. if (lastLyricEntryDict[j]) {
  282. currentSpacingToLastLyric = lyricsXPosition - lastLyricEntryDict[j].lyricsXPosition;
  283. }
  284. let currentSpacingToMeasureEnd: number;
  285. let spacingNeededToMeasureEnd: number;
  286. const maxXInMeasure: number = oldMinimumStaffEntriesWidth * elongationFactorForMeasureWidth;
  287. // when the lyrics are centered, we need to consider spacing differently than when they are left-aligned:
  288. if (TextAlignment.IsCenterAligned(lyricAlignment)) {
  289. lyricOverlapAllowedIntoNextMeasure /= 4; // reserve space for overlap from next measure. its first note can't be spaced.
  290. currentSpacingToMeasureEnd = maxXInMeasure - lyricsXPosition;
  291. spacingNeededToMeasureEnd = (lyricsLabelWidth / 2) - lyricOverlapAllowedIntoNextMeasure;
  292. // spacing to last lyric only done if not first lyric in measure:
  293. if (lastLyricEntryDict[j]) {
  294. spacingNeededToLastLyric =
  295. lastLyricEntryDict[j].labelWidth / 2 + lyricsLabelWidth / 2 + minLyricsSpacing;
  296. }
  297. } else if (TextAlignment.IsLeft(lyricAlignment)) {
  298. currentSpacingToMeasureEnd = maxXInMeasure - lyricsXPosition;
  299. spacingNeededToMeasureEnd = lyricsLabelWidth - lyricOverlapAllowedIntoNextMeasure;
  300. if (lastLyricEntryDict[j]) {
  301. spacingNeededToLastLyric = lastLyricEntryDict[j].labelWidth + minLyricsSpacing;
  302. }
  303. }
  304. // get factor of how much we need to stretch the measure to space the current lyric
  305. let elongationFactorForMeasureWidthForCurrentLyric: number = 1;
  306. const elongationFactorNeededForMeasureEnd: number =
  307. spacingNeededToMeasureEnd / currentSpacingToMeasureEnd;
  308. let elongationFactorNeededForLastLyric: number = 1;
  309. if (lastLyricEntryDict[j]) { // if previous lyric needs more spacing than measure end, take that spacing
  310. const lastNoteDuration: Fraction = lastLyricEntryDict[j].sourceNoteDuration;
  311. elongationFactorNeededForLastLyric = spacingNeededToLastLyric / currentSpacingToLastLyric;
  312. if (lastNoteDuration.Denominator > 4) {
  313. elongationFactorNeededForLastLyric *= 1.1; // from 1.2 upwards, this unnecessarily bloats shorter measures
  314. // spacing in Vexflow depends on note duration, our minSpacing is calibrated for quarter notes
  315. // if we double the measure length, the distance between eigth notes only gets half of the added length
  316. // compared to a quarter note.
  317. }
  318. }
  319. elongationFactorForMeasureWidthForCurrentLyric = Math.max(
  320. elongationFactorNeededForMeasureEnd,
  321. elongationFactorNeededForLastLyric
  322. );
  323. elongationFactorForMeasureWidth = Math.max(
  324. elongationFactorForMeasureWidth,
  325. elongationFactorForMeasureWidthForCurrentLyric
  326. );
  327. // set up information about this lyric entry of verse j for next lyric entry of verse j
  328. lastLyricEntryDict[j] = {
  329. extend: lyricsEntry.LyricsEntry.extend,
  330. labelWidth: lyricsLabelWidth,
  331. lyricsXPosition: lyricsXPosition,
  332. measureNumber: measure.MeasureNumber,
  333. sourceNoteDuration: lyricsEntry.LyricsEntry.Parent.Notes[0].Length,
  334. text: lyricsEntry.LyricsEntry.Text,
  335. };
  336. }
  337. }
  338. }
  339. return oldMinimumStaffEntriesWidth * elongationFactorForMeasureWidth;
  340. }
  341. protected createGraphicalTie(tie: Tie, startGse: GraphicalStaffEntry, endGse: GraphicalStaffEntry,
  342. startNote: GraphicalNote, endNote: GraphicalNote): GraphicalTie {
  343. return new GraphicalTie(tie, startNote, endNote);
  344. }
  345. protected updateStaffLineBorders(staffLine: StaffLine): void {
  346. staffLine.SkyBottomLineCalculator.updateStaffLineBorders();
  347. }
  348. protected graphicalMeasureCreatedCalculations(measure: GraphicalMeasure): void {
  349. (measure as VexFlowMeasure).rules = this.rules;
  350. (measure as VexFlowMeasure).graphicalMeasureCreatedCalculations();
  351. }
  352. /**
  353. * Can be used to calculate articulations, stem directions, helper(ledger) lines, and overlapping note x-displacement.
  354. * Is Excecuted per voice entry of a staff entry.
  355. * After that layoutStaffEntry is called.
  356. * @param voiceEntry
  357. * @param graphicalNotes
  358. * @param graphicalStaffEntry
  359. * @param hasPitchedNote
  360. */
  361. protected layoutVoiceEntry(voiceEntry: VoiceEntry, graphicalNotes: GraphicalNote[], graphicalStaffEntry: GraphicalStaffEntry,
  362. hasPitchedNote: boolean): void {
  363. return;
  364. }
  365. /**
  366. * Do all layout calculations that have to be done per staff entry, like dots, ornaments, arpeggios....
  367. * This method is called after the voice entries are handled by layoutVoiceEntry().
  368. * @param graphicalStaffEntry
  369. */
  370. protected layoutStaffEntry(graphicalStaffEntry: GraphicalStaffEntry): void {
  371. (graphicalStaffEntry.parentMeasure as VexFlowMeasure).layoutStaffEntry(graphicalStaffEntry);
  372. }
  373. /**
  374. * Is called at the begin of the method for creating the vertically aligned staff measures belonging to one source measure.
  375. */
  376. protected initGraphicalMeasuresCreation(): void {
  377. return;
  378. }
  379. /**
  380. * add here all given articulations to the VexFlowGraphicalStaffEntry and prepare them for rendering.
  381. * @param articulations
  382. * @param voiceEntry
  383. * @param graphicalStaffEntry
  384. */
  385. protected layoutArticulationMarks(articulations: ArticulationEnum[], voiceEntry: VoiceEntry, graphicalStaffEntry: GraphicalStaffEntry): void {
  386. // uncomment this when implementing:
  387. // let vfse: VexFlowStaffEntry = (graphicalStaffEntry as VexFlowStaffEntry);
  388. return;
  389. }
  390. /**
  391. * Calculate the shape (Bezier curve) for this tie.
  392. * @param tie
  393. * @param tieIsAtSystemBreak
  394. */
  395. protected layoutGraphicalTie(tie: GraphicalTie, tieIsAtSystemBreak: boolean): void {
  396. const startNote: VexFlowGraphicalNote = (tie.StartNote as VexFlowGraphicalNote);
  397. const endNote: VexFlowGraphicalNote = (tie.EndNote as VexFlowGraphicalNote);
  398. let vfStartNote: Vex.Flow.StaveNote = undefined;
  399. let startNoteIndexInTie: number = 0;
  400. if (startNote !== undefined && startNote.vfnote !== undefined && startNote.vfnote.length >= 2) {
  401. vfStartNote = startNote.vfnote[0];
  402. startNoteIndexInTie = startNote.vfnote[1];
  403. }
  404. let vfEndNote: Vex.Flow.StaveNote = undefined;
  405. let endNoteIndexInTie: number = 0;
  406. if (endNote !== undefined && endNote.vfnote !== undefined && endNote.vfnote.length >= 2) {
  407. vfEndNote = endNote.vfnote[0];
  408. endNoteIndexInTie = endNote.vfnote[1];
  409. }
  410. if (tieIsAtSystemBreak) {
  411. // split tie into two ties:
  412. if (vfStartNote) { // first_note or last_note must be not null in Vexflow
  413. const vfTie1: Vex.Flow.StaveTie = new Vex.Flow.StaveTie({
  414. first_indices: [startNoteIndexInTie],
  415. first_note: vfStartNote
  416. });
  417. const measure1: VexFlowMeasure = (startNote.parentVoiceEntry.parentStaffEntry.parentMeasure as VexFlowMeasure);
  418. measure1.vfTies.push(vfTie1);
  419. }
  420. if (vfEndNote) {
  421. const vfTie2: Vex.Flow.StaveTie = new Vex.Flow.StaveTie({
  422. last_indices: [endNoteIndexInTie],
  423. last_note: vfEndNote
  424. });
  425. const measure2: VexFlowMeasure = (endNote.parentVoiceEntry.parentStaffEntry.parentMeasure as VexFlowMeasure);
  426. measure2.vfTies.push(vfTie2);
  427. }
  428. } else {
  429. // normal case
  430. if (vfStartNote || vfEndNote) { // one of these must be not null in Vexflow
  431. const vfTie: Vex.Flow.StaveTie = new Vex.Flow.StaveTie({
  432. first_indices: [startNoteIndexInTie],
  433. first_note: vfStartNote,
  434. last_indices: [endNoteIndexInTie],
  435. last_note: vfEndNote
  436. });
  437. const measure: VexFlowMeasure = (endNote.parentVoiceEntry.parentStaffEntry.parentMeasure as VexFlowMeasure);
  438. measure.vfTies.push(vfTie);
  439. }
  440. }
  441. }
  442. protected calculateDynamicExpressionsForMultiExpression(multiExpression: MultiExpression, measureIndex: number, staffIndex: number): void {
  443. if (measureIndex < this.rules.MinMeasureToDrawIndex || measureIndex > this.rules.MaxMeasureToDrawIndex) {
  444. return;
  445. // we do already use the min/max in MusicSheetCalculator.calculateDynamicsExpressions,
  446. // but this may be necessary for StaffLinkedExpressions, not tested.
  447. }
  448. // calculate absolute Timestamp
  449. const absoluteTimestamp: Fraction = multiExpression.AbsoluteTimestamp;
  450. const measures: GraphicalMeasure[] = this.graphicalMusicSheet.MeasureList[measureIndex];
  451. const staffLine: StaffLine = measures[staffIndex].ParentStaffLine;
  452. const startMeasure: GraphicalMeasure = measures[staffIndex];
  453. const startPosInStaffline: PointF2D = this.getRelativePositionInStaffLineFromTimestamp(
  454. absoluteTimestamp,
  455. staffIndex,
  456. staffLine,
  457. staffLine.isPartOfMultiStaffInstrument());
  458. const dynamicStartPosition: PointF2D = startPosInStaffline;
  459. if (startPosInStaffline.x <= 0) {
  460. dynamicStartPosition.x = startMeasure.beginInstructionsWidth + this.rules.RhythmRightMargin;
  461. }
  462. if (multiExpression.InstantaneousDynamic) {
  463. const graphicalInstantaneousDynamic: VexFlowInstantaneousDynamicExpression = new VexFlowInstantaneousDynamicExpression(
  464. multiExpression.InstantaneousDynamic,
  465. staffLine,
  466. startMeasure);
  467. this.calculateGraphicalInstantaneousDynamicExpression(graphicalInstantaneousDynamic, dynamicStartPosition);
  468. }
  469. if (multiExpression.StartingContinuousDynamic) {
  470. const continuousDynamic: ContinuousDynamicExpression = multiExpression.StartingContinuousDynamic;
  471. const graphicalContinuousDynamic: VexFlowContinuousDynamicExpression = new VexFlowContinuousDynamicExpression(
  472. multiExpression.StartingContinuousDynamic,
  473. staffLine);
  474. graphicalContinuousDynamic.StartMeasure = startMeasure;
  475. if (!graphicalContinuousDynamic.IsVerbal && continuousDynamic.EndMultiExpression) {
  476. try {
  477. this.calculateGraphicalContinuousDynamic(graphicalContinuousDynamic, dynamicStartPosition);
  478. } catch (e) {
  479. // TODO this sometimes fails when the measure range to draw doesn't include all the dynamic's measures, method needs to be adjusted
  480. // see calculateGraphicalContinuousDynamic(), also in MusicSheetCalculator.
  481. }
  482. } else if (graphicalContinuousDynamic.IsVerbal) {
  483. this.calculateGraphicalVerbalContinuousDynamic(graphicalContinuousDynamic, dynamicStartPosition);
  484. } else {
  485. log.warn("This continuous dynamic is not covered");
  486. }
  487. }
  488. }
  489. protected createMetronomeMark(metronomeExpression: InstantaneousTempoExpression): void {
  490. const vfStave: Vex.Flow.Stave = (this.graphicalMusicSheet.MeasureList[0][0] as VexFlowMeasure).getVFStave();
  491. //vfStave.addModifier(new Vex.Flow.StaveTempo( // needs Vexflow PR
  492. vfStave.setTempo(
  493. {
  494. bpm: metronomeExpression.TempoInBpm,
  495. dots: metronomeExpression.dotted,
  496. //duration: metronomeExpression.beatUnit
  497. duration: "q"
  498. },
  499. this.rules.MetronomeMarkYShift * unitInPixels);
  500. // -50, -30), 0); //needs Vexflow PR
  501. //.setShiftX(-50);
  502. (<any>vfStave.getModifiers()[vfStave.getModifiers().length - 1]).setShiftX(
  503. this.rules.MetronomeMarkXShift * unitInPixels
  504. );
  505. // TODO calculate bounding box of metronome mark instead of hacking skyline to fix lyricist collision
  506. const skyline: number[] = this.graphicalMusicSheet.MeasureList[0][0].ParentStaffLine.SkyLine;
  507. skyline[0] = Math.min(skyline[0], -4.5 + this.rules.MetronomeMarkYShift);
  508. // somehow this is called repeatedly in Clementi, so skyline[0] = Math.min instead of -=
  509. }
  510. /**
  511. * Calculate a single OctaveShift for a [[MultiExpression]].
  512. * @param sourceMeasure
  513. * @param multiExpression
  514. * @param measureIndex
  515. * @param staffIndex
  516. */
  517. protected calculateSingleOctaveShift(sourceMeasure: SourceMeasure, multiExpression: MultiExpression, measureIndex: number, staffIndex: number): void {
  518. // calculate absolute Timestamp and startStaffLine (and EndStaffLine if needed)
  519. const octaveShift: OctaveShift = multiExpression.OctaveShiftStart;
  520. const startTimeStamp: Fraction = octaveShift.ParentStartMultiExpression.Timestamp;
  521. const endTimeStamp: Fraction = octaveShift.ParentEndMultiExpression.Timestamp;
  522. const minMeasureToDrawIndex: number = this.rules.MinMeasureToDrawIndex;
  523. const maxMeasureToDrawIndex: number = this.rules.MaxMeasureToDrawIndex;
  524. let startStaffLine: StaffLine = this.graphicalMusicSheet.MeasureList[measureIndex][staffIndex].ParentStaffLine;
  525. if (startStaffLine === undefined) { // fix for rendering range set. all of these can probably done cleaner.
  526. startStaffLine = this.graphicalMusicSheet.MeasureList[minMeasureToDrawIndex][staffIndex].ParentStaffLine;
  527. }
  528. let endMeasure: GraphicalMeasure = undefined;
  529. if (octaveShift.ParentEndMultiExpression !== undefined) {
  530. endMeasure = this.graphicalMusicSheet.getGraphicalMeasureFromSourceMeasureAndIndex(octaveShift.ParentEndMultiExpression.SourceMeasureParent,
  531. staffIndex);
  532. } else {
  533. endMeasure = this.graphicalMusicSheet.getLastGraphicalMeasureFromIndex(staffIndex, true); // get last rendered measure
  534. }
  535. if (endMeasure.MeasureNumber > maxMeasureToDrawIndex + 1) { // octaveshift ends in measure not rendered
  536. endMeasure = this.graphicalMusicSheet.getLastGraphicalMeasureFromIndex(staffIndex, true);
  537. }
  538. let startMeasure: GraphicalMeasure = undefined;
  539. if (octaveShift.ParentEndMultiExpression !== undefined) {
  540. startMeasure = this.graphicalMusicSheet.getGraphicalMeasureFromSourceMeasureAndIndex(octaveShift.ParentStartMultiExpression.SourceMeasureParent,
  541. staffIndex);
  542. } else {
  543. startMeasure = this.graphicalMusicSheet.MeasureList[minMeasureToDrawIndex][staffIndex]; // first rendered measure
  544. }
  545. if (startMeasure.MeasureNumber < minMeasureToDrawIndex + 1) { // octaveshift starts before range of measures selected to render
  546. startMeasure = this.graphicalMusicSheet.MeasureList[minMeasureToDrawIndex][staffIndex]; // first rendered measure
  547. }
  548. if (startMeasure.MeasureNumber < minMeasureToDrawIndex + 1 ||
  549. startMeasure.MeasureNumber > maxMeasureToDrawIndex + 1 ||
  550. endMeasure.MeasureNumber < minMeasureToDrawIndex + 1 ||
  551. endMeasure.MeasureNumber > maxMeasureToDrawIndex + 1) {
  552. // octave shift completely out of drawing range, don't draw anything
  553. return;
  554. }
  555. let endStaffLine: StaffLine = endMeasure.ParentStaffLine;
  556. if (endStaffLine === undefined) {
  557. endStaffLine = startStaffLine;
  558. }
  559. if (endMeasure !== undefined && startStaffLine !== undefined && endStaffLine !== undefined) {
  560. // calculate GraphicalOctaveShift and RelativePositions
  561. const graphicalOctaveShift: VexFlowOctaveShift = new VexFlowOctaveShift(octaveShift, startStaffLine.PositionAndShape);
  562. if (graphicalOctaveShift.getStartNote() === undefined) { // fix for rendering range set
  563. graphicalOctaveShift.setStartNote(startMeasure.staffEntries[0]);
  564. }
  565. if (graphicalOctaveShift.getStartNote() === undefined) { // fix for rendering range set
  566. graphicalOctaveShift.setEndNote(endMeasure.staffEntries.last());
  567. }
  568. startStaffLine.OctaveShifts.push(graphicalOctaveShift);
  569. // calculate RelativePosition and Dashes
  570. let startStaffEntry: GraphicalStaffEntry = startMeasure.findGraphicalStaffEntryFromTimestamp(startTimeStamp);
  571. if (startStaffEntry === undefined) { // fix for rendering range set
  572. startStaffEntry = startMeasure.staffEntries[0];
  573. }
  574. let endStaffEntry: GraphicalStaffEntry = endMeasure.findGraphicalStaffEntryFromTimestamp(endTimeStamp);
  575. if (endStaffEntry === undefined) { // fix for rendering range set
  576. endStaffEntry = endMeasure.staffEntries[endMeasure.staffEntries.length - 1];
  577. }
  578. graphicalOctaveShift.setStartNote(startStaffEntry);
  579. if (endStaffLine !== startStaffLine) {
  580. graphicalOctaveShift.endsOnDifferentStaffLine = true;
  581. let lastMeasure: GraphicalMeasure = startStaffLine.Measures[startStaffLine.Measures.length - 1];
  582. if (lastMeasure === undefined) { // TODO handle this case correctly (when drawUpToMeasureNumber etc set)
  583. lastMeasure = endMeasure;
  584. }
  585. const lastNote: GraphicalStaffEntry = lastMeasure.staffEntries[lastMeasure.staffEntries.length - 1];
  586. graphicalOctaveShift.setEndNote(lastNote);
  587. // Now finish the shift on the next line
  588. const remainingOctaveShift: VexFlowOctaveShift = new VexFlowOctaveShift(octaveShift, endMeasure.PositionAndShape);
  589. endStaffLine.OctaveShifts.push(remainingOctaveShift);
  590. let firstMeasure: GraphicalMeasure = endStaffLine.Measures[0];
  591. if (firstMeasure === undefined) { // TODO handle this case correctly (when drawUpToMeasureNumber etc set)
  592. firstMeasure = startMeasure;
  593. }
  594. const firstNote: GraphicalStaffEntry = firstMeasure.staffEntries[0];
  595. remainingOctaveShift.setStartNote(firstNote);
  596. remainingOctaveShift.setEndNote(endStaffEntry);
  597. } else {
  598. graphicalOctaveShift.setEndNote(endStaffEntry);
  599. }
  600. } else {
  601. log.warn("End measure or staffLines for octave shift are undefined! This should not happen!");
  602. }
  603. }
  604. /**
  605. * Calculate all the textual and symbolic [[RepetitionInstruction]]s (e.g. dal segno) for a single [[SourceMeasure]].
  606. * @param repetitionInstruction
  607. * @param measureIndex
  608. */
  609. protected calculateWordRepetitionInstruction(repetitionInstruction: RepetitionInstruction, measureIndex: number): void {
  610. // find first visible StaffLine
  611. let uppermostMeasure: VexFlowMeasure = undefined;
  612. const measures: VexFlowMeasure[] = <VexFlowMeasure[]>this.graphicalMusicSheet.MeasureList[measureIndex];
  613. for (let idx: number = 0, len: number = measures.length; idx < len; ++idx) {
  614. const graphicalMeasure: VexFlowMeasure = measures[idx];
  615. if (graphicalMeasure.ParentStaffLine !== undefined && graphicalMeasure.ParentStaff.ParentInstrument.Visible) {
  616. uppermostMeasure = <VexFlowMeasure>graphicalMeasure;
  617. break;
  618. }
  619. }
  620. // ToDo: feature/Repetitions
  621. // now create corresponding graphical symbol or Text in VexFlow:
  622. // use top measure and staffline for positioning.
  623. if (uppermostMeasure !== undefined) {
  624. uppermostMeasure.addWordRepetition(repetitionInstruction);
  625. }
  626. }
  627. protected calculateMoodAndUnknownExpression(multiExpression: MultiExpression, measureIndex: number, staffIndex: number): void {
  628. return;
  629. }
  630. /**
  631. * Re-adjust the x positioning of expressions. Update the skyline afterwards
  632. */
  633. protected calculateExpressionAlignements(): void {
  634. for (const musicSystem of this.musicSystems) {
  635. for (const staffLine of musicSystem.StaffLines) {
  636. try {
  637. (<VexFlowStaffLine>staffLine).AlignmentManager.alignDynamicExpressions();
  638. staffLine.AbstractExpressions.forEach(ae => ae.updateSkyBottomLine());
  639. } catch (e) {
  640. // TODO still necessary when calculation of expression fails, see calculateDynamicExpressionsForMultiExpression()
  641. // see calculateGraphicalContinuousDynamic(), also in MusicSheetCalculator.
  642. }
  643. }
  644. }
  645. }
  646. /**
  647. * Check if the tied graphical note belongs to any beams or tuplets and react accordingly.
  648. * @param tiedGraphicalNote
  649. * @param beams
  650. * @param activeClef
  651. * @param octaveShiftValue
  652. * @param graphicalStaffEntry
  653. * @param duration
  654. * @param openTie
  655. * @param isLastTieNote
  656. */
  657. protected handleTiedGraphicalNote(tiedGraphicalNote: GraphicalNote, beams: Beam[], activeClef: ClefInstruction,
  658. octaveShiftValue: OctaveEnum, graphicalStaffEntry: GraphicalStaffEntry, duration: Fraction,
  659. openTie: Tie, isLastTieNote: boolean): void {
  660. return;
  661. }
  662. /**
  663. * Is called if a note is part of a beam.
  664. * @param graphicalNote
  665. * @param beam
  666. * @param openBeams a list of all currently open beams
  667. */
  668. protected handleBeam(graphicalNote: GraphicalNote, beam: Beam, openBeams: Beam[]): void {
  669. (graphicalNote.parentVoiceEntry.parentStaffEntry.parentMeasure as VexFlowMeasure).handleBeam(graphicalNote, beam);
  670. }
  671. protected handleVoiceEntryLyrics(voiceEntry: VoiceEntry, graphicalStaffEntry: GraphicalStaffEntry, lyricWords: LyricWord[]): void {
  672. voiceEntry.LyricsEntries.forEach((key: number, lyricsEntry: LyricsEntry) => {
  673. const graphicalLyricEntry: GraphicalLyricEntry = new GraphicalLyricEntry(lyricsEntry,
  674. graphicalStaffEntry,
  675. this.rules.LyricsHeight,
  676. this.rules.StaffHeight);
  677. graphicalStaffEntry.LyricsEntries.push(graphicalLyricEntry);
  678. // create corresponding GraphicalLabel
  679. const graphicalLabel: GraphicalLabel = graphicalLyricEntry.GraphicalLabel;
  680. graphicalLabel.setLabelPositionAndShapeBorders();
  681. if (lyricsEntry.Word !== undefined) {
  682. const lyricsEntryIndex: number = lyricsEntry.Word.Syllables.indexOf(lyricsEntry);
  683. let index: number = lyricWords.indexOf(lyricsEntry.Word);
  684. if (index === -1) {
  685. lyricWords.push(lyricsEntry.Word);
  686. index = lyricWords.indexOf(lyricsEntry.Word);
  687. }
  688. if (this.graphicalLyricWords.length === 0 || index > this.graphicalLyricWords.length - 1) {
  689. const graphicalLyricWord: GraphicalLyricWord = new GraphicalLyricWord(lyricsEntry.Word);
  690. graphicalLyricEntry.ParentLyricWord = graphicalLyricWord;
  691. graphicalLyricWord.GraphicalLyricsEntries[lyricsEntryIndex] = graphicalLyricEntry;
  692. this.graphicalLyricWords.push(graphicalLyricWord);
  693. } else {
  694. const graphicalLyricWord: GraphicalLyricWord = this.graphicalLyricWords[index];
  695. graphicalLyricEntry.ParentLyricWord = graphicalLyricWord;
  696. graphicalLyricWord.GraphicalLyricsEntries[lyricsEntryIndex] = graphicalLyricEntry;
  697. if (graphicalLyricWord.isFilled()) {
  698. lyricWords.splice(index, 1);
  699. this.graphicalLyricWords.splice(this.graphicalLyricWords.indexOf(graphicalLyricWord), 1);
  700. }
  701. }
  702. }
  703. });
  704. }
  705. protected handleVoiceEntryOrnaments(ornamentContainer: OrnamentContainer, voiceEntry: VoiceEntry, graphicalStaffEntry: GraphicalStaffEntry): void {
  706. return;
  707. }
  708. /**
  709. * Add articulations to the given vexflow staff entry.
  710. * @param articulations
  711. * @param voiceEntry
  712. * @param graphicalStaffEntry
  713. */
  714. protected handleVoiceEntryArticulations(articulations: ArticulationEnum[],
  715. voiceEntry: VoiceEntry, staffEntry: GraphicalStaffEntry): void {
  716. // uncomment this when implementing:
  717. // let vfse: VexFlowStaffEntry = (graphicalStaffEntry as VexFlowStaffEntry);
  718. return;
  719. }
  720. /**
  721. * Add technical instructions to the given vexflow staff entry.
  722. * @param technicalInstructions
  723. * @param voiceEntry
  724. * @param staffEntry
  725. */
  726. protected handleVoiceEntryTechnicalInstructions(technicalInstructions: TechnicalInstruction[],
  727. voiceEntry: VoiceEntry, staffEntry: GraphicalStaffEntry): void {
  728. // uncomment this when implementing:
  729. // let vfse: VexFlowStaffEntry = (graphicalStaffEntry as VexFlowStaffEntry);
  730. return;
  731. }
  732. /**
  733. * Is called if a note is part of a tuplet.
  734. * @param graphicalNote
  735. * @param tuplet
  736. * @param openTuplets a list of all currently open tuplets
  737. */
  738. protected handleTuplet(graphicalNote: GraphicalNote, tuplet: Tuplet, openTuplets: Tuplet[]): void {
  739. (graphicalNote.parentVoiceEntry.parentStaffEntry.parentMeasure as VexFlowMeasure).handleTuplet(graphicalNote, tuplet);
  740. }
  741. /**
  742. * Find the Index of the item of the array of all VexFlow Slurs that holds a specified slur
  743. * @param gSlurs
  744. * @param slur
  745. */
  746. public findIndexGraphicalSlurFromSlur(gSlurs: GraphicalSlur[], slur: Slur): number {
  747. for (let slurIndex: number = 0; slurIndex < gSlurs.length; slurIndex++) {
  748. if (gSlurs[slurIndex].slur === slur) {
  749. return slurIndex;
  750. }
  751. }
  752. return -1;
  753. }
  754. /* VexFlow Version - for later use
  755. public findIndexVFSlurFromSlur(vfSlurs: VexFlowSlur[], slur: Slur): number {
  756. for (let slurIndex: number = 0; slurIndex < vfSlurs.length; slurIndex++) {
  757. if (vfSlurs[slurIndex].vfSlur === slur) {
  758. return slurIndex;
  759. }
  760. }
  761. }
  762. */
  763. // Generate all Graphical Slurs and attach them to the staffline
  764. protected calculateSlurs(): void {
  765. const openSlursDict: { [staffId: number]: GraphicalSlur[]; } = {};
  766. for (const graphicalMeasure of this.graphicalMusicSheet.MeasureList[0]) { //let i: number = 0; i < this.graphicalMusicSheet.MeasureList[0].length; i++) {
  767. openSlursDict[graphicalMeasure.ParentStaff.idInMusicSheet] = [];
  768. }
  769. /* VexFlow Version - for later use
  770. // Generate an empty dictonary to index an array of VexFlowSlur classes
  771. const vfOpenSlursDict: { [staffId: number]: VexFlowSlur[]; } = {}; //VexFlowSlur[]; } = {};
  772. // use first SourceMeasure to get all graphical measures to know how many staves are currently visible in this musicsheet
  773. // foreach stave: create an empty array. It can later hold open slurs.
  774. // Measure how many staves are visible and reserve space for them.
  775. for (const graphicalMeasure of this.graphicalMusicSheet.MeasureList[0]) { //let i: number = 0; i < this.graphicalMusicSheet.MeasureList[0].length; i++) {
  776. vfOpenSlursDict[graphicalMeasure.ParentStaff.idInMusicSheet] = [];
  777. }
  778. */
  779. for (const musicSystem of this.musicSystems) {
  780. for (const staffLine of musicSystem.StaffLines) {
  781. // if a graphical slur reaches out of the last musicsystem, we have to create another graphical slur reaching into this musicsystem
  782. // (one slur needs 2 graphical slurs)
  783. const openGraphicalSlurs: GraphicalSlur[] = openSlursDict[staffLine.ParentStaff.idInMusicSheet];
  784. for (let slurIndex: number = 0; slurIndex < openGraphicalSlurs.length; slurIndex++) {
  785. const oldGSlur: GraphicalSlur = openGraphicalSlurs[slurIndex];
  786. const newGSlur: GraphicalSlur = new GraphicalSlur(oldGSlur.slur); //Graphicalslur.createFromSlur(oldSlur);
  787. staffLine.addSlurToStaffline(newGSlur); // every VFSlur is added to the array in the VFStaffline!
  788. openGraphicalSlurs[slurIndex] = newGSlur;
  789. }
  790. /* VexFlow Version - for later use
  791. const vfOpenSlurs: VexFlowSlur[] = vfOpenSlursDict[staffLine.ParentStaff.idInMusicSheet];
  792. const vfStaffLine: VexFlowStaffLine = <VexFlowStaffLine> staffLine;
  793. for (let slurIndex: number = 0; slurIndex < vfOpenSlurs.length; slurIndex++) {
  794. const oldVFSlur: VexFlowSlur = vfOpenSlurs[slurIndex];
  795. const newVFSlur: VexFlowSlur = VexFlowSlur.createFromVexflowSlur(oldVFSlur);
  796. newVFSlur.vfStartNote = undefined;
  797. vfStaffLine.addVFSlurToVFStaffline(newVFSlur); // every VFSlur is added to the array in the VFStaffline!
  798. vfOpenSlurs[slurIndex] = newVFSlur;
  799. }
  800. */
  801. // add reference of slur array to the VexFlowStaffline class
  802. for (const graphicalMeasure of staffLine.Measures) {
  803. for (const graphicalStaffEntry of graphicalMeasure.staffEntries) {
  804. // loop over "normal" notes (= no gracenotes)
  805. for (const graphicalVoiceEntry of graphicalStaffEntry.graphicalVoiceEntries) {
  806. for (const graphicalNote of graphicalVoiceEntry.notes) {
  807. for (const slur of graphicalNote.sourceNote.NoteSlurs) {
  808. // extra check for some MusicSheets that have openSlurs (because only the first Page is available -> Recordare files)
  809. if (slur.EndNote === undefined || slur.StartNote === undefined) {
  810. continue;
  811. }
  812. // add new VexFlowSlur to List
  813. if (slur.StartNote === graphicalNote.sourceNote) {
  814. if (graphicalNote.sourceNote.NoteTie !== undefined) {
  815. if (graphicalNote.parentVoiceEntry.parentStaffEntry.getAbsoluteTimestamp() !==
  816. graphicalNote.sourceNote.NoteTie.StartNote.getAbsoluteTimestamp()) {
  817. break;
  818. }
  819. }
  820. // Add a Graphical Slur to the staffline, if the recent note is the Startnote of a slur
  821. const gSlur: GraphicalSlur = new GraphicalSlur(slur);
  822. openGraphicalSlurs.push(gSlur);
  823. staffLine.addSlurToStaffline(gSlur);
  824. /* VexFlow Version - for later use
  825. const vfSlur: VexFlowSlur = new VexFlowSlur(slur);
  826. vfOpenSlurs.push(vfSlur); //add open... adding / removing is JUST DONE in the open... array
  827. vfSlur.vfStartNote = (graphicalVoiceEntry as VexFlowVoiceEntry).vfStaveNote;
  828. vfStaffLine.addVFSlurToVFStaffline(vfSlur); // every VFSlur is added to the array in the VFStaffline!
  829. */
  830. }
  831. if (slur.EndNote === graphicalNote.sourceNote) {
  832. // Remove the Graphical Slur from the staffline if the note is the Endnote of a slur
  833. const index: number = this.findIndexGraphicalSlurFromSlur(openGraphicalSlurs, slur);
  834. if (index >= 0) {
  835. // save Voice Entry in VFSlur and then remove it from array of open VFSlurs
  836. const gSlur: GraphicalSlur = openGraphicalSlurs[index];
  837. if (gSlur.staffEntries.indexOf(graphicalStaffEntry) === -1) {
  838. gSlur.staffEntries.push(graphicalStaffEntry);
  839. }
  840. openGraphicalSlurs.splice(index, 1);
  841. }
  842. /* VexFlow Version - for later use
  843. const vfIndex: number = this.findIndexVFSlurFromSlur(vfOpenSlurs, slur);
  844. if (vfIndex !== undefined) {
  845. // save Voice Entry in VFSlur and then remove it from array of open VFSlurs
  846. const vfSlur: VexFlowSlur = vfOpenSlurs[vfIndex];
  847. vfSlur.vfEndNote = (graphicalVoiceEntry as VexFlowVoiceEntry).vfStaveNote;
  848. vfSlur.createVexFlowCurve();
  849. vfOpenSlurs.splice(vfIndex, 1);
  850. }
  851. */
  852. }
  853. }
  854. }
  855. }
  856. //add the present Staffentry to all open slurs that don't contain this Staffentry already
  857. for (const gSlur of openGraphicalSlurs) {
  858. if (gSlur.staffEntries.indexOf(graphicalStaffEntry) === -1) {
  859. gSlur.staffEntries.push(graphicalStaffEntry);
  860. }
  861. }
  862. } // loop over StaffEntries
  863. } // loop over Measures
  864. } // loop over StaffLines
  865. // Attach vfSlur array to the vfStaffline to be drawn
  866. //vfStaffLine.SlursInVFStaffLine = vfSlurs;
  867. } // loop over MusicSystems
  868. // order slurs that were saved to the Staffline
  869. for (const musicSystem of this.musicSystems) {
  870. for (const staffLine of musicSystem.StaffLines) {
  871. // Sort all gSlurs in the staffline using the Compare function in class GraphicalSlurSorter
  872. const sortedGSlurs: GraphicalSlur[] = staffLine.GraphicalSlurs.sort(GraphicalSlur.Compare);
  873. for (const gSlur of sortedGSlurs) {
  874. // crossed slurs will be handled later:
  875. if (gSlur.slur.isCrossed()) {
  876. continue;
  877. }
  878. gSlur.calculateCurve(this.rules);
  879. }
  880. }
  881. }
  882. }
  883. }