VexFlowMusicSheetCalculator.ts 46 KB

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