VexFlowMusicSheetCalculator.ts 108 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124
  1. import { MusicSheetCalculator } from "../MusicSheetCalculator";
  2. import { VexFlowGraphicalSymbolFactory } from "./VexFlowGraphicalSymbolFactory";
  3. import { GraphicalMeasure } from "../GraphicalMeasure";
  4. import { StaffLine } from "../StaffLine";
  5. import { SkyBottomLineBatchCalculator } from "../SkyBottomLineBatchCalculator";
  6. import { VoiceEntry } from "../../VoiceData/VoiceEntry";
  7. import { GraphicalNote } from "../GraphicalNote";
  8. import { GraphicalStaffEntry } from "../GraphicalStaffEntry";
  9. import { GraphicalTie } from "../GraphicalTie";
  10. import { Tie } from "../../VoiceData/Tie";
  11. import { SourceMeasure } from "../../VoiceData/SourceMeasure";
  12. import { MultiExpression } from "../../VoiceData/Expressions/MultiExpression";
  13. import { RepetitionInstruction } from "../../VoiceData/Instructions/RepetitionInstruction";
  14. import { Beam } from "../../VoiceData/Beam";
  15. import { ClefInstruction } from "../../VoiceData/Instructions/ClefInstruction";
  16. import { OctaveEnum, OctaveShift } from "../../VoiceData/Expressions/ContinuousExpressions/OctaveShift";
  17. import { Fraction } from "../../../Common/DataObjects/Fraction";
  18. import { LyricWord } from "../../VoiceData/Lyrics/LyricsWord";
  19. import { OrnamentContainer, OrnamentEnum } from "../../VoiceData/OrnamentContainer";
  20. import { Articulation } from "../../VoiceData/Articulation";
  21. import { Tuplet } from "../../VoiceData/Tuplet";
  22. import { VexFlowMeasure } from "./VexFlowMeasure";
  23. import { VexFlowTextMeasurer } from "./VexFlowTextMeasurer";
  24. import Vex from "vexflow";
  25. import VF = Vex.Flow;
  26. import log from "loglevel";
  27. import { unitInPixels } from "./VexFlowMusicSheetDrawer";
  28. import { VexFlowGraphicalNote } from "./VexFlowGraphicalNote";
  29. import { TechnicalInstruction } from "../../VoiceData/Instructions/TechnicalInstruction";
  30. import { GraphicalLyricEntry } from "../GraphicalLyricEntry";
  31. import { GraphicalLabel } from "../GraphicalLabel";
  32. import { LyricsEntry } from "../../VoiceData/Lyrics/LyricsEntry";
  33. import { GraphicalLyricWord } from "../GraphicalLyricWord";
  34. import { VexFlowStaffEntry } from "./VexFlowStaffEntry";
  35. import { VexFlowOctaveShift } from "./VexFlowOctaveShift";
  36. import { VexFlowInstantaneousDynamicExpression } from "./VexFlowInstantaneousDynamicExpression";
  37. import { Slur } from "../../VoiceData/Expressions/ContinuousExpressions/Slur";
  38. /* VexFlow Version - for later use
  39. // import { VexFlowSlur } from "./VexFlowSlur";
  40. // import { VexFlowStaffLine } from "./VexFlowStaffLine";
  41. // import { VexFlowVoiceEntry } from "./VexFlowVoiceEntry";
  42. */
  43. import { PointF2D } from "../../../Common/DataObjects/PointF2D";
  44. import { TextAlignmentEnum, TextAlignment } from "../../../Common/Enums/TextAlignment";
  45. import { GraphicalSlur } from "../GraphicalSlur";
  46. import { BoundingBox } from "../BoundingBox";
  47. import { ContinuousDynamicExpression } from "../../VoiceData/Expressions/ContinuousExpressions/ContinuousDynamicExpression";
  48. import { VexFlowContinuousDynamicExpression } from "./VexFlowContinuousDynamicExpression";
  49. import { InstantaneousTempoExpression, TempoEnum } from "../../VoiceData/Expressions/InstantaneousTempoExpression";
  50. import { AlignRestOption } from "../../../OpenSheetMusicDisplay/OSMDOptions";
  51. import { VexFlowStaffLine } from "./VexFlowStaffLine";
  52. import { EngravingRules } from "../EngravingRules";
  53. import { VexflowStafflineNoteCalculator } from "./VexflowStafflineNoteCalculator";
  54. import { MusicSystem } from "../MusicSystem";
  55. import { NoteTypeHandler } from "../../VoiceData/NoteType";
  56. import { VexFlowConverter } from "./VexFlowConverter";
  57. import { TabNote } from "../../VoiceData/TabNote";
  58. import { PlacementEnum } from "../../VoiceData/Expressions";
  59. import { GraphicalChordSymbolContainer } from "../GraphicalChordSymbolContainer";
  60. import { RehearsalExpression } from "../../VoiceData/Expressions/RehearsalExpression";
  61. import { SystemLinesEnum } from "../SystemLinesEnum";
  62. import { Pedal } from "../../VoiceData/Expressions/ContinuousExpressions/Pedal";
  63. import { VexFlowPedal } from "./VexFlowPedal";
  64. import { MusicSymbol } from "../MusicSymbol";
  65. import { VexFlowVoiceEntry } from "./VexFlowVoiceEntry";
  66. import { CollectionUtil } from "../../../Util/CollectionUtil";
  67. import { GraphicalGlissando } from "../GraphicalGlissando";
  68. import { Glissando } from "../../VoiceData/Glissando";
  69. import { VexFlowGlissando } from "./VexFlowGlissando";
  70. import { WavyLine } from "../../VoiceData/Expressions/ContinuousExpressions/WavyLine";
  71. import { VexflowVibratoBracket } from "./VexflowVibratoBracket";
  72. export class VexFlowMusicSheetCalculator extends MusicSheetCalculator {
  73. /** space needed for a dash for lyrics spacing, calculated once */
  74. private dashSpace: number;
  75. public beamsNeedUpdate: boolean = false;
  76. constructor(rules: EngravingRules) {
  77. super();
  78. this.rules = rules;
  79. MusicSheetCalculator.symbolFactory = new VexFlowGraphicalSymbolFactory();
  80. MusicSheetCalculator.TextMeasurer = new VexFlowTextMeasurer(this.rules);
  81. MusicSheetCalculator.stafflineNoteCalculator = new VexflowStafflineNoteCalculator(this.rules);
  82. // prepare Vexflow font (doesn't affect Vexflow 1.x). It seems like this has to be done here for now, otherwise it's too slow for the generateImages script.
  83. // (first image will have the non-updated font, in this case the Vexflow default Bravura, while we want Gonville here)
  84. if (this.rules.DefaultVexFlowNoteFont?.toLowerCase() === "gonville") {
  85. (Vex.Flow as any).DEFAULT_FONT_STACK = [(Vex.Flow as any).Fonts?.Gonville, (Vex.Flow as any).Fonts?.Bravura, (Vex.Flow as any).Fonts?.Custom];
  86. } else if (this.rules.DefaultVexFlowNoteFont?.toLowerCase() === "petaluma") {
  87. (Vex.Flow as any).DEFAULT_FONT_STACK = [(Vex.Flow as any).Fonts?.Petaluma, (Vex.Flow as any).Fonts?.Gonville, (Vex.Flow as any).Fonts?.Bravura];
  88. }
  89. // else keep new vexflow default Bravura (more cursive, bold)
  90. }
  91. protected clearRecreatedObjects(): void {
  92. super.clearRecreatedObjects();
  93. MusicSheetCalculator.stafflineNoteCalculator = new VexflowStafflineNoteCalculator(this.rules);
  94. for (const graphicalMeasures of this.graphicalMusicSheet.MeasureList) {
  95. for (const graphicalMeasure of graphicalMeasures) {
  96. (<VexFlowMeasure>graphicalMeasure)?.clean();
  97. }
  98. }
  99. }
  100. protected formatMeasures(): void {
  101. // let totalFinalizeBeamsTime: number = 0;
  102. for (const verticalMeasureList of this.graphicalMusicSheet.MeasureList) {
  103. if (!verticalMeasureList || !verticalMeasureList[0]) {
  104. continue;
  105. }
  106. const firstVisibleMeasure: VexFlowMeasure = verticalMeasureList.find(measure => measure?.isVisible()) as VexFlowMeasure;
  107. // first measure has formatting method as lambda function object, but formats all measures. TODO this could be refactored
  108. firstVisibleMeasure.format();
  109. for (const measure of verticalMeasureList) {
  110. for (const staffEntry of measure.staffEntries) {
  111. (<VexFlowStaffEntry>staffEntry).calculateXPosition();
  112. }
  113. // const t0: number = performance.now();
  114. if (true || this.beamsNeedUpdate) {
  115. // finalizeBeams takes a few milliseconds, so we can save some performance here sometimes,
  116. // but we'd have to check for every setting change that would affect beam rendering. See #843
  117. (measure as VexFlowMeasure).finalizeBeams(); // without this, when zooming a lot (e.g. 250%), beams keep their old, now wrong slope.
  118. // totalFinalizeBeamsTime += performance.now() - t0;
  119. // console.log("Total calls to finalizeBeams in VexFlowMusicSheetCalculator took " + totalFinalizeBeamsTime + " milliseconds.");
  120. }
  121. }
  122. }
  123. this.beamsNeedUpdate = false;
  124. }
  125. //protected clearSystemsAndMeasures(): void {
  126. // for (let measure of measures) {
  127. //
  128. // }
  129. //}
  130. /**
  131. * Calculates the x layout of the staff entries within the staff measures belonging to one source measure.
  132. * All staff entries are x-aligned throughout all vertically aligned staff measures.
  133. * This method is called within calculateXLayout.
  134. * The staff entries are aligned with minimum needed x distances.
  135. * The MinimumStaffEntriesWidth of every measure will be set - needed for system building.
  136. * Prepares the VexFlow formatter for later formatting
  137. * Does not calculate measure width from lyrics (which is called from MusicSheetCalculator)
  138. * @param measures
  139. * @returns the minimum required x width of the source measure (=list of staff measures)
  140. */
  141. protected calculateMeasureXLayout(measures: GraphicalMeasure[]): number {
  142. const visibleMeasures: GraphicalMeasure[] = [];
  143. for (const measure of measures) {
  144. if (measure?.isVisible()) { // if we don't check for visibility, invisible parts affect layout (#1444)
  145. visibleMeasures.push(measure);
  146. }
  147. }
  148. if (visibleMeasures.length === 0) { // e.g. after Multiple Rest measures (VexflowMultiRestMeasure)
  149. return 0;
  150. }
  151. measures = visibleMeasures;
  152. // Format the voices
  153. const allVoices: VF.Voice[] = [];
  154. const formatter: VF.Formatter = new VF.Formatter({
  155. // maxIterations: 2,
  156. softmaxFactor: this.rules.SoftmaxFactorVexFlow // this setting is only applied in Vexflow 3.x. also this needs @types/vexflow ^3.0.0
  157. });
  158. let maxStaffEntries: number = measures[0].staffEntries.length;
  159. let maxStaffEntriesPlusAccidentals: number = 1;
  160. for (const measure of measures) {
  161. if (!measure) {
  162. continue;
  163. }
  164. let measureAccidentals: number = 0;
  165. for (const staffEntry of measure.staffEntries) {
  166. measureAccidentals += (staffEntry as VexFlowStaffEntry).setMaxAccidentals(); // staffEntryAccidentals
  167. }
  168. // TODO the if is a TEMP change to show pure diff for pickup measures, should be done for all measures, but increases spacing
  169. if (measure.parentSourceMeasure.ImplicitMeasure) {
  170. maxStaffEntries = Math.max(measure.staffEntries.length, maxStaffEntries);
  171. maxStaffEntriesPlusAccidentals = Math.max(measure.staffEntries.length + measureAccidentals, maxStaffEntriesPlusAccidentals);
  172. }
  173. const mvoices: { [voiceID: number]: VF.Voice } = (measure as VexFlowMeasure).vfVoices;
  174. const voices: VF.Voice[] = [];
  175. for (const voiceID in mvoices) {
  176. if (mvoices.hasOwnProperty(voiceID)) {
  177. const mvoice: any = mvoices[voiceID];
  178. if (measure.hasOnlyRests && !mvoice.ticksUsed.equals(mvoice.totalTicks)) {
  179. // fix layouting issues with whole measure rests in one staff and notes in other. especially in 12/8 rthythm (#1187)
  180. mvoice.ticksUsed = mvoice.totalTicks;
  181. // Vexflow 1.2.93: needs VexFlowPatch for formatter.js (see #1187)
  182. }
  183. voices.push(mvoice);
  184. allVoices.push(mvoice);
  185. }
  186. }
  187. if (voices.length === 0) {
  188. log.debug("Found a measure with no voices. Continuing anyway.", mvoices);
  189. // no need to log this, measures with no voices/notes are fine. see OSMDOptions.fillEmptyMeasuresWithWholeRest
  190. continue;
  191. }
  192. // all voices that belong to one stave are collectively added to create a common context in VexFlow.
  193. formatter.joinVoices(voices);
  194. }
  195. let minStaffEntriesWidth: number = 12; // a typical measure has roughly a length of 3*StaffHeight (3*4 = 12)
  196. const parentSourceMeasure: SourceMeasure = measures[0].parentSourceMeasure;
  197. // the voicing space bonus addition makes the voicing more relaxed. With a bonus of 0 the notes are basically completely squeezed together.
  198. const staffEntryFactor: number = 0.3;
  199. if (allVoices.length > 0) {
  200. minStaffEntriesWidth = formatter.preCalculateMinTotalWidth(allVoices) / unitInPixels
  201. * this.rules.VoiceSpacingMultiplierVexflow
  202. + this.rules.VoiceSpacingAddendVexflow
  203. + maxStaffEntries * staffEntryFactor; // TODO use maxStaffEntriesPlusAccidentals here as well, adjust spacing
  204. if (parentSourceMeasure?.ImplicitMeasure) {
  205. // shrink width in the ratio that the pickup measure is shorter compared to a full measure('s time signature):
  206. minStaffEntriesWidth = parentSourceMeasure.Duration.RealValue / parentSourceMeasure.ActiveTimeSignature.RealValue * minStaffEntriesWidth;
  207. // e.g. a 1/4 pickup measure in a 3/4 time signature should be 1/4 / 3/4 = 1/3 as long (a third)
  208. // it seems like this should be respected by staffEntries.length and preCaculateMinTotalWidth, but apparently not,
  209. // without this the pickup measures were always too long.
  210. let barlineSpacing: number = 0;
  211. const measureListIndex: number = parentSourceMeasure.measureListIndex;
  212. if (measureListIndex > 1) {
  213. // only give this implicit measure more space if the previous one had a thick barline (e.g. repeat end)
  214. for (const gMeasure of this.graphicalMusicSheet.MeasureList[measureListIndex - 1]) {
  215. const endingBarStyleEnum: SystemLinesEnum = gMeasure?.parentSourceMeasure.endingBarStyleEnum;
  216. if (endingBarStyleEnum === SystemLinesEnum.ThinBold ||
  217. endingBarStyleEnum === SystemLinesEnum.DotsThinBold
  218. ) {
  219. barlineSpacing = this.rules.PickupMeasureRepetitionSpacing;
  220. break;
  221. }
  222. }
  223. }
  224. minStaffEntriesWidth += barlineSpacing;
  225. // add more than the original staffEntries scaling again: (removing it above makes it too short)
  226. if (maxStaffEntries > 1) { // not necessary for only 1 StaffEntry
  227. minStaffEntriesWidth += maxStaffEntriesPlusAccidentals * staffEntryFactor * 1.5; // don't scale this for implicit measures
  228. // in fact overscale it, this needs a lot of space the more staffEntries (and modifiers like accidentals) there are
  229. } else if (measureListIndex > 1 && maxStaffEntries === 1) {
  230. // do this also for measures not after repetitions:
  231. minStaffEntriesWidth += this.rules.PickupMeasureSpacingSingleNoteAddend;
  232. }
  233. minStaffEntriesWidth *= this.rules.PickupMeasureWidthMultiplier;
  234. }
  235. // TODO this could use some fine-tuning. currently using *1.5 + 1 by default, results in decent spacing.
  236. // firstMeasure.formatVoices = (w: number) => {
  237. // formatter.format(allVoices, w);
  238. // };
  239. MusicSheetCalculator.setMeasuresMinStaffEntriesWidth(measures, minStaffEntriesWidth);
  240. const formatVoicesDefault: (w: number, p: VexFlowMeasure) => void = (w, p) => {
  241. formatter.formatToStave(allVoices, p.getVFStave());
  242. };
  243. const formatVoicesAlignRests: (w: number, p: VexFlowMeasure) => void = (w, p) => {
  244. formatter.formatToStave(allVoices, p.getVFStave(), {
  245. align_rests: true,
  246. context: undefined
  247. });
  248. };
  249. for (const measure of measures) {
  250. // determine whether to align rests
  251. if (this.rules.AlignRests === AlignRestOption.Never) {
  252. (measure as VexFlowMeasure).formatVoices = formatVoicesDefault;
  253. } else if (this.rules.AlignRests === AlignRestOption.Always) {
  254. (measure as VexFlowMeasure).formatVoices = formatVoicesAlignRests;
  255. } else if (this.rules.AlignRests === AlignRestOption.Auto) {
  256. let alignRests: boolean = false;
  257. for (const staffEntry of measure.staffEntries) {
  258. let collidableVoiceEntries: number = 0;
  259. let numberOfRests: number = 0;
  260. for (const voiceEntry of staffEntry.graphicalVoiceEntries) {
  261. if (!voiceEntry.parentVoiceEntry.IsGrace) {
  262. if (voiceEntry && voiceEntry.notes && voiceEntry.notes[0] && voiceEntry.notes[0].sourceNote) {// TODO null chaining, TS 3.7
  263. if (voiceEntry.notes[0].sourceNote.PrintObject) { // only respect collision when not invisible
  264. collidableVoiceEntries++;
  265. }
  266. }
  267. }
  268. if (voiceEntry && voiceEntry.notes && voiceEntry.notes[0] && voiceEntry.notes[0].sourceNote) {// TODO null chaining, TS 3.7
  269. if (voiceEntry.notes[0].sourceNote.isRest() && voiceEntry.notes[0].sourceNote.PrintObject) {
  270. numberOfRests++; // only align rests if there is actually a rest (which could collide)
  271. }
  272. }
  273. if (collidableVoiceEntries > 1 && numberOfRests >= 1) {
  274. // TODO could add further checks like if any of the already checked voice entries actually collide
  275. alignRests = true;
  276. break;
  277. }
  278. }
  279. if (alignRests) {
  280. break;
  281. }
  282. }
  283. // set measure's format function
  284. if (alignRests) {
  285. (measure as VexFlowMeasure).formatVoices = formatVoicesAlignRests;
  286. } else {
  287. (measure as VexFlowMeasure).formatVoices = formatVoicesDefault;
  288. }
  289. }
  290. // format first measure with minimum width
  291. if (measure === measures[0]) {
  292. const vexflowMeasure: VexFlowMeasure = (measure as VexFlowMeasure);
  293. // prepare format function for voices, will be called later for formatting measure again
  294. //vexflowMeasure.formatVoices = formatVoicesDefault;
  295. // format now for minimum width, calculateMeasureWidthFromLyrics later
  296. vexflowMeasure.formatVoices(minStaffEntriesWidth * unitInPixels, vexflowMeasure);
  297. } else {
  298. //(measure as VexFlowMeasure).formatVoices = undefined;
  299. // TODO why was the formatVoices function disabled for other measures? would now disable the new align rests option.
  300. }
  301. }
  302. }
  303. for (const graphicalMeasure of measures) {
  304. if (!graphicalMeasure) {
  305. continue;
  306. }
  307. for (const staffEntry of graphicalMeasure.staffEntries) {
  308. // here the measure modifiers are not yet set, therefore the begin instruction width will be empty
  309. (<VexFlowStaffEntry>staffEntry).calculateXPosition();
  310. }
  311. }
  312. //Can't quite figure out why, but this is the calculation that needs redone to have consistent rendering.
  313. //The first render of a sheet vs. subsequent renders are calculated differently by vexflow without this re-joining of the voices
  314. for (const measure of measures) {
  315. if (!measure) {
  316. continue;
  317. }
  318. const mvoices: { [voiceID: number]: VF.Voice } = (measure as VexFlowMeasure).vfVoices;
  319. const voices: VF.Voice[] = [];
  320. for (const voiceID in mvoices) {
  321. if (mvoices.hasOwnProperty(voiceID)) {
  322. voices.push(mvoices[voiceID]);
  323. }
  324. }
  325. if (voices.length === 0) {
  326. log.debug("Found a measure with no voices. Continuing anyway.", mvoices);
  327. // no need to log this, measures with no voices/notes are fine. see OSMDOptions.fillEmptyMeasuresWithWholeRest
  328. continue;
  329. }
  330. // all voices that belong to one stave are collectively added to create a common context in VexFlow.
  331. formatter.joinVoices(voices);
  332. }
  333. // calculateMeasureWidthFromLyrics() will be called from MusicSheetCalculator after this
  334. return minStaffEntriesWidth;
  335. }
  336. private calculateElongationFactor(containers: (GraphicalLyricEntry|GraphicalChordSymbolContainer)[], staffEntry: GraphicalStaffEntry, lastEntryDict: any,
  337. oldMinimumStaffEntriesWidth: number, elongationFactorForMeasureWidth: number,
  338. measureNumber: number, oldMinSpacing: number, nextMeasureOverlap: number): number {
  339. let newElongationFactorForMeasureWidth: number = elongationFactorForMeasureWidth;
  340. let currentContainerIndex: number = 0;
  341. for (const container of containers) {
  342. const alignment: TextAlignmentEnum = container.GraphicalLabel.Label.textAlignment;
  343. let minSpacing: number = oldMinSpacing;
  344. let overlapAllowedIntoNextMeasure: number = nextMeasureOverlap;
  345. if (container instanceof GraphicalLyricEntry && container.ParentLyricWord) {
  346. // spacing for multi-syllable words
  347. if (container.LyricsEntry.SyllableIndex > 0) { // syllables after first
  348. // give a little more spacing for dash between syllables
  349. minSpacing = this.rules.BetweenSyllableMinimumDistance;
  350. if (TextAlignment.IsCenterAligned(alignment)) {
  351. minSpacing += 1.0; // TODO check for previous lyric alignment too. though center is not standard
  352. // without this, there's not enough space for dashes between long syllables on eigth notes
  353. }
  354. }
  355. const syllables: LyricsEntry[] = container.ParentLyricWord.GetLyricWord.Syllables;
  356. if (syllables.length > 1) {
  357. if (container.LyricsEntry.SyllableIndex < syllables.length - 1) {
  358. // if a middle syllable of a word, give less measure overlap into next measure, to give room for dash
  359. if (this.dashSpace === undefined) { // don't replace undefined check
  360. this.dashSpace = 1.5;
  361. // better method, doesn't work:
  362. // this.dashLength = new GraphicalLabel(new Label("-"), this.rules.LyricsHeight, TextAlignmentEnum.CenterBottom)
  363. // .PositionAndShape.Size.width; // always returns 0
  364. }
  365. overlapAllowedIntoNextMeasure -= this.dashSpace;
  366. }
  367. }
  368. }
  369. const bBox: BoundingBox = container instanceof GraphicalLyricEntry ? container.GraphicalLabel.PositionAndShape : container.PositionAndShape;
  370. const labelWidth: number = bBox.Size.width;
  371. const staffEntryXPosition: number = (staffEntry as VexFlowStaffEntry).PositionAndShape.RelativePosition.x;
  372. let xPosition: number = staffEntryXPosition + bBox.BorderMarginLeft;
  373. if (container instanceof GraphicalChordSymbolContainer && container.PositionAndShape.Parent.DataObject instanceof GraphicalMeasure) {
  374. // the parent is only the measure for whole measure rest notes with chord symbols,
  375. // which should start near the beginning of the measure instead of the middle, where there is no desired staffEntry position.
  376. // TODO somehow on the 2nd render, above xPosition (from VexFlowStaffEntry) is way too big (for whole measure rests).
  377. xPosition = this.rules.ChordSymbolWholeMeasureRestXOffset + bBox.BorderMarginLeft +
  378. (container.PositionAndShape.Parent.DataObject as GraphicalMeasure).beginInstructionsWidth;
  379. }
  380. if (lastEntryDict[currentContainerIndex] !== undefined) {
  381. if (lastEntryDict[currentContainerIndex].extend) {
  382. // TODO handle extend of last entry (extend is stored in lyrics entry of preceding syllable)
  383. // only necessary for center alignment
  384. }
  385. }
  386. let spacingNeededToLastContainer: number;
  387. let currentSpacingToLastContainer: number; // undefined for first container in measure
  388. if (lastEntryDict[currentContainerIndex]) {
  389. currentSpacingToLastContainer = xPosition - lastEntryDict[currentContainerIndex].xPosition;
  390. }
  391. let currentSpacingToMeasureEnd: number;
  392. let spacingNeededToMeasureEnd: number;
  393. const maxXInMeasure: number = oldMinimumStaffEntriesWidth * elongationFactorForMeasureWidth;
  394. if (TextAlignment.IsCenterAligned(alignment)) {
  395. overlapAllowedIntoNextMeasure /= 4; // reserve space for overlap from next measure. its first note can't be spaced.
  396. currentSpacingToMeasureEnd = maxXInMeasure - xPosition;
  397. spacingNeededToMeasureEnd = (labelWidth / 2) - overlapAllowedIntoNextMeasure;
  398. // spacing to last lyric only done if not first lyric in measure:
  399. if (lastEntryDict[currentContainerIndex]) {
  400. spacingNeededToLastContainer =
  401. lastEntryDict[currentContainerIndex].labelWidth / 2 + labelWidth / 2 + minSpacing;
  402. }
  403. } else if (TextAlignment.IsLeft(alignment)) {
  404. currentSpacingToMeasureEnd = maxXInMeasure - xPosition;
  405. spacingNeededToMeasureEnd = labelWidth - overlapAllowedIntoNextMeasure;
  406. if (lastEntryDict[currentContainerIndex]) {
  407. spacingNeededToLastContainer = lastEntryDict[currentContainerIndex].labelWidth + minSpacing;
  408. }
  409. }
  410. // get factor of how much we need to stretch the measure to space the current lyric
  411. let elongationFactorForMeasureWidthForCurrentContainer: number = 1;
  412. const elongationFactorNeededForMeasureEnd: number =
  413. spacingNeededToMeasureEnd / currentSpacingToMeasureEnd;
  414. let elongationFactorNeededForLastContainer: number = 1;
  415. if (container instanceof GraphicalLyricEntry && container.LyricsEntry) {
  416. if (lastEntryDict[currentContainerIndex]) { // if previous lyric needs more spacing than measure end, take that spacing
  417. const lastNoteDuration: Fraction = lastEntryDict[currentContainerIndex].sourceNoteDuration;
  418. elongationFactorNeededForLastContainer = spacingNeededToLastContainer / currentSpacingToLastContainer;
  419. if ((lastNoteDuration.Denominator) > 4) {
  420. elongationFactorNeededForLastContainer *= 1.1; // from 1.2 upwards, this unnecessarily bloats shorter measures
  421. // spacing in Vexflow depends on note duration, our minSpacing is calibrated for quarter notes
  422. // if we double the measure length, the distance between eigth notes only gets half of the added length
  423. // compared to a quarter note.
  424. }
  425. }
  426. } else if (lastEntryDict[currentContainerIndex]) {
  427. elongationFactorNeededForLastContainer =
  428. spacingNeededToLastContainer / currentSpacingToLastContainer;
  429. }
  430. elongationFactorForMeasureWidthForCurrentContainer = Math.max(
  431. elongationFactorNeededForMeasureEnd,
  432. elongationFactorNeededForLastContainer
  433. );
  434. newElongationFactorForMeasureWidth = Math.max(
  435. newElongationFactorForMeasureWidth,
  436. elongationFactorForMeasureWidthForCurrentContainer
  437. );
  438. let overlap: number = Math.max((spacingNeededToLastContainer - currentSpacingToLastContainer) || 0, 0);
  439. if (lastEntryDict[currentContainerIndex]) {
  440. overlap += lastEntryDict[currentContainerIndex].cumulativeOverlap;
  441. }
  442. // set up information about this lyric entry of verse j for next lyric entry of verse j
  443. lastEntryDict[currentContainerIndex] = {
  444. cumulativeOverlap: overlap,
  445. extend: container instanceof GraphicalLyricEntry ? container.LyricsEntry.extend : false,
  446. labelWidth: labelWidth,
  447. measureNumber: measureNumber,
  448. sourceNoteDuration: container instanceof GraphicalLyricEntry ? (container.LyricsEntry && container.LyricsEntry.Parent.Notes[0].Length) : false,
  449. text: container instanceof GraphicalLyricEntry ? container.LyricsEntry.Text : container.GraphicalLabel.Label.text,
  450. xPosition: xPosition,
  451. };
  452. currentContainerIndex++;
  453. }
  454. return newElongationFactorForMeasureWidth;
  455. }
  456. public calculateElongationFactorFromStaffEntries(staffEntries: GraphicalStaffEntry[], oldMinimumStaffEntriesWidth: number,
  457. elongationFactorForMeasureWidth: number, measureNumber: number): number {
  458. interface EntryInfo {
  459. cumulativeOverlap: number;
  460. extend: boolean;
  461. labelWidth: number;
  462. xPosition: number;
  463. sourceNoteDuration: Fraction;
  464. text: string;
  465. measureNumber: number;
  466. }
  467. // holds lyrics entries for verses i
  468. interface EntryDict {
  469. [i: number]: EntryInfo;
  470. }
  471. let newElongationFactorForMeasureWidth: number = elongationFactorForMeasureWidth;
  472. const lastLyricEntryDict: EntryDict = {}; // holds info about last lyric entries for all verses j???
  473. const lastChordEntryDict: EntryDict = {}; // holds info about last chord entries for all verses j???
  474. // for all staffEntries i, each containing the lyric entry for all verses at that timestamp in the measure
  475. for (const staffEntry of staffEntries) {
  476. if (staffEntry.LyricsEntries.length > 0 && this.rules.RenderLyrics) {
  477. newElongationFactorForMeasureWidth =
  478. this.calculateElongationFactor(
  479. staffEntry.LyricsEntries,
  480. staffEntry,
  481. lastLyricEntryDict,
  482. oldMinimumStaffEntriesWidth,
  483. newElongationFactorForMeasureWidth,
  484. measureNumber,
  485. this.rules.HorizontalBetweenLyricsDistance,
  486. this.rules.LyricOverlapAllowedIntoNextMeasure,
  487. );
  488. }
  489. if (staffEntry.graphicalChordContainers.length > 0 && this.rules.RenderChordSymbols) {
  490. newElongationFactorForMeasureWidth =
  491. this.calculateElongationFactor(
  492. staffEntry.graphicalChordContainers,
  493. staffEntry,
  494. lastChordEntryDict,
  495. oldMinimumStaffEntriesWidth,
  496. newElongationFactorForMeasureWidth,
  497. measureNumber,
  498. this.rules.ChordSymbolXSpacing,
  499. this.rules.ChordOverlapAllowedIntoNextMeasure,
  500. );
  501. }
  502. }
  503. return newElongationFactorForMeasureWidth;
  504. }
  505. public calculateMeasureWidthFromStaffEntries(measuresVertical: GraphicalMeasure[], oldMinimumStaffEntriesWidth: number): number {
  506. let elongationFactorForMeasureWidth: number = 1;
  507. for (const measure of measuresVertical) {
  508. if (!measure || measure.staffEntries.length === 0) {
  509. continue;
  510. }
  511. elongationFactorForMeasureWidth =
  512. this.calculateElongationFactorFromStaffEntries(
  513. measure.staffEntries,
  514. oldMinimumStaffEntriesWidth,
  515. elongationFactorForMeasureWidth,
  516. measure.MeasureNumber,
  517. );
  518. }
  519. elongationFactorForMeasureWidth = Math.min(elongationFactorForMeasureWidth, this.rules.MaximumLyricsElongationFactor);
  520. // TODO check when this is > 2.0. there seems to be an error here where this is unnecessarily > 2 in Beethoven Geliebte.
  521. const newMinimumStaffEntriesWidth: number = oldMinimumStaffEntriesWidth * elongationFactorForMeasureWidth;
  522. return newMinimumStaffEntriesWidth;
  523. }
  524. protected createGraphicalTie(tie: Tie, startGse: GraphicalStaffEntry, endGse: GraphicalStaffEntry,
  525. startNote: GraphicalNote, endNote: GraphicalNote): GraphicalTie {
  526. return new GraphicalTie(tie, startNote, endNote);
  527. }
  528. protected updateStaffLineBorders(staffLine: StaffLine): void {
  529. staffLine.SkyBottomLineCalculator.updateStaffLineBorders();
  530. }
  531. protected graphicalMeasureCreatedCalculations(measure: GraphicalMeasure): void {
  532. (measure as VexFlowMeasure).rules = this.rules;
  533. (measure as VexFlowMeasure).graphicalMeasureCreatedCalculations();
  534. }
  535. /**
  536. * Can be used to calculate articulations, stem directions, helper(ledger) lines, and overlapping note x-displacement.
  537. * Is Excecuted per voice entry of a staff entry.
  538. * After that layoutStaffEntry is called.
  539. * @param voiceEntry
  540. * @param graphicalNotes
  541. * @param graphicalStaffEntry
  542. * @param hasPitchedNote
  543. */
  544. protected layoutVoiceEntry(voiceEntry: VoiceEntry, graphicalNotes: GraphicalNote[], graphicalStaffEntry: GraphicalStaffEntry,
  545. hasPitchedNote: boolean): void {
  546. for (let i: number = 0; i < graphicalNotes.length; i++) {
  547. graphicalNotes[i] = MusicSheetCalculator.stafflineNoteCalculator.positionNote(graphicalNotes[i]);
  548. }
  549. }
  550. /**
  551. * Do all layout calculations that have to be done per staff entry, like dots, ornaments, arpeggios....
  552. * This method is called after the voice entries are handled by layoutVoiceEntry().
  553. * @param graphicalStaffEntry
  554. */
  555. protected layoutStaffEntry(graphicalStaffEntry: GraphicalStaffEntry): void {
  556. (graphicalStaffEntry.parentMeasure as VexFlowMeasure).layoutStaffEntry(graphicalStaffEntry);
  557. }
  558. /**
  559. * Is called at the begin of the method for creating the vertically aligned staff measures belonging to one source measure.
  560. */
  561. protected initGraphicalMeasuresCreation(): void {
  562. return;
  563. }
  564. /**
  565. * add here all given articulations to the VexFlowGraphicalStaffEntry and prepare them for rendering.
  566. * @param articulations
  567. * @param voiceEntry
  568. * @param graphicalStaffEntry
  569. */
  570. protected layoutArticulationMarks(articulations: Articulation[], voiceEntry: VoiceEntry, graphicalStaffEntry: GraphicalStaffEntry): void {
  571. // uncomment this when implementing:
  572. // let vfse: VexFlowStaffEntry = (graphicalStaffEntry as VexFlowStaffEntry);
  573. return;
  574. }
  575. /**
  576. * Calculate the shape (Bezier curve) for this tie.
  577. * @param tie
  578. * @param tieIsAtSystemBreak
  579. * @param isTab Whether this tie is for a tab note (guitar tabulature)
  580. */
  581. protected layoutGraphicalTie(tie: GraphicalTie, tieIsAtSystemBreak: boolean, isTab: boolean): void {
  582. const startNote: VexFlowGraphicalNote = (tie.StartNote as VexFlowGraphicalNote);
  583. const endNote: VexFlowGraphicalNote = (tie.EndNote as VexFlowGraphicalNote);
  584. let vfStartNote: VF.StemmableNote = undefined;
  585. let startNoteIndexInTie: number = 0;
  586. if (startNote && startNote.vfnote && startNote.vfnote.length >= 2) {
  587. vfStartNote = startNote.vfnote[0];
  588. startNoteIndexInTie = startNote.vfnote[1];
  589. }
  590. let vfEndNote: VF.StemmableNote = undefined;
  591. let endNoteIndexInTie: number = 0;
  592. if (endNote && endNote.vfnote && endNote.vfnote.length >= 2) {
  593. vfEndNote = endNote.vfnote[0];
  594. endNoteIndexInTie = endNote.vfnote[1];
  595. }
  596. if (tieIsAtSystemBreak) {
  597. // split tie into two ties:
  598. if (vfStartNote) { // first_note or last_note must be not null in Vexflow
  599. const vfTie1: VF.StaveTie = new VF.StaveTie({
  600. first_indices: [startNoteIndexInTie],
  601. first_note: vfStartNote
  602. });
  603. const measure1: VexFlowMeasure = (startNote.parentVoiceEntry.parentStaffEntry.parentMeasure as VexFlowMeasure);
  604. measure1.addStaveTie(vfTie1, tie);
  605. }
  606. if (vfEndNote) {
  607. const vfTie2: VF.StaveTie = new VF.StaveTie({
  608. last_indices: [endNoteIndexInTie],
  609. last_note: vfEndNote
  610. });
  611. const measure2: VexFlowMeasure = (endNote.parentVoiceEntry.parentStaffEntry.parentMeasure as VexFlowMeasure);
  612. measure2.addStaveTie(vfTie2, tie);
  613. }
  614. } else {
  615. // normal case
  616. if (vfStartNote || vfEndNote) { // one of these must be not null in Vexflow
  617. let vfTie: any;
  618. if (isTab) {
  619. if (tie.Tie.Type === "S") {
  620. //calculate direction
  621. const startTieNote: TabNote = <TabNote> tie.StartNote.sourceNote;
  622. const endTieNote: TabNote = <TabNote> tie.EndNote.sourceNote;
  623. let slideDirection: number = 1;
  624. if (startTieNote.FretNumber > endTieNote.FretNumber) {
  625. slideDirection = -1;
  626. }
  627. vfTie = new VF.TabSlide(
  628. {
  629. first_indices: [startNoteIndexInTie],
  630. first_note: vfStartNote,
  631. last_indices: [endNoteIndexInTie],
  632. last_note: vfEndNote,
  633. },
  634. slideDirection
  635. );
  636. } else {
  637. vfTie = new VF.TabTie(
  638. {
  639. first_indices: [startNoteIndexInTie],
  640. first_note: vfStartNote,
  641. last_indices: [endNoteIndexInTie],
  642. last_note: vfEndNote,
  643. },
  644. tie.Tie.Type
  645. );
  646. }
  647. } else { // not Tab (guitar), normal StaveTie
  648. vfTie = new VF.StaveTie({
  649. first_indices: [startNoteIndexInTie],
  650. first_note: vfStartNote,
  651. last_indices: [endNoteIndexInTie],
  652. last_note: vfEndNote
  653. });
  654. const tieDirection: PlacementEnum = tie.Tie.getTieDirection(startNote.sourceNote);
  655. if (tieDirection === PlacementEnum.Below) {
  656. vfTie.setDirection(1); // + is down in vexflow
  657. } else if (tieDirection === PlacementEnum.Above) {
  658. vfTie.setDirection(-1);
  659. }
  660. }
  661. const measure: VexFlowMeasure = (endNote.parentVoiceEntry.parentStaffEntry.parentMeasure as VexFlowMeasure);
  662. measure.addStaveTie(vfTie, tie);
  663. }
  664. }
  665. }
  666. protected calculateDynamicExpressionsForMultiExpression(multiExpression: MultiExpression, measureIndex: number, staffIndex: number): void {
  667. if (measureIndex < this.rules.MinMeasureToDrawIndex || measureIndex > this.rules.MaxMeasureToDrawIndex) {
  668. return;
  669. // we do already use the min/max in MusicSheetCalculator.calculateDynamicsExpressions,
  670. // but this may be necessary for StaffLinkedExpressions, not tested.
  671. }
  672. // calculate absolute Timestamp
  673. const absoluteTimestamp: Fraction = multiExpression.AbsoluteTimestamp;
  674. const measures: GraphicalMeasure[] = this.graphicalMusicSheet.MeasureList[measureIndex];
  675. const staffLine: StaffLine = measures[staffIndex].ParentStaffLine;
  676. const startMeasure: GraphicalMeasure = measures[staffIndex];
  677. if (!staffLine) {
  678. return;
  679. }
  680. // start position in staffline:
  681. // const useStaffEntryBorderLeft: boolean = multiExpression.StartingContinuousDynamic?.DynamicType === ContDynamicEnum.diminuendo;
  682. const continuousDynamic: ContinuousDynamicExpression = multiExpression.StartingContinuousDynamic;
  683. const useStaffEntryBorderLeft: boolean = continuousDynamic !== undefined && !continuousDynamic.IsStartOfSoftAccent;
  684. const dynamicStartPosition: PointF2D = this.getRelativePositionInStaffLineFromTimestamp(
  685. absoluteTimestamp,
  686. staffIndex,
  687. staffLine,
  688. staffLine?.isPartOfMultiStaffInstrument(),
  689. undefined,
  690. useStaffEntryBorderLeft
  691. );
  692. if (dynamicStartPosition.x <= 0) {
  693. dynamicStartPosition.x = startMeasure.beginInstructionsWidth + this.rules.RhythmRightMargin;
  694. }
  695. if (multiExpression.InstantaneousDynamic) {
  696. const graphicalInstantaneousDynamic: VexFlowInstantaneousDynamicExpression = new VexFlowInstantaneousDynamicExpression(
  697. multiExpression.InstantaneousDynamic,
  698. staffLine,
  699. startMeasure);
  700. // compare with multiExpression.InstantaneousDynamic.InMeasureTimestamp or add a relative timestamp? if we ever need a separate timestamp
  701. this.calculateGraphicalInstantaneousDynamicExpression(graphicalInstantaneousDynamic, dynamicStartPosition, absoluteTimestamp);
  702. this.dynamicExpressionMap.set(absoluteTimestamp.RealValue, graphicalInstantaneousDynamic.PositionAndShape);
  703. }
  704. if (continuousDynamic) {
  705. const graphicalContinuousDynamic: VexFlowContinuousDynamicExpression = new VexFlowContinuousDynamicExpression(
  706. continuousDynamic,
  707. staffLine,
  708. startMeasure.parentSourceMeasure);
  709. graphicalContinuousDynamic.StartMeasure = startMeasure;
  710. graphicalContinuousDynamic.IsSoftAccent = multiExpression.StartingContinuousDynamic.IsStartOfSoftAccent;
  711. //graphicalContinuousDynamic.StartIsEnd = multiExpression.StartingContinuousDynamic.EndMultiExpression === multiExpression;
  712. if (!graphicalContinuousDynamic.IsVerbal && continuousDynamic.EndMultiExpression) {
  713. try {
  714. this.calculateGraphicalContinuousDynamic(graphicalContinuousDynamic, dynamicStartPosition);
  715. graphicalContinuousDynamic.updateSkyBottomLine();
  716. } catch (e) {
  717. // TODO this sometimes fails when the measure range to draw doesn't include all the dynamic's measures, method needs to be adjusted
  718. // see calculateGraphicalContinuousDynamic(), also in MusicSheetCalculator.
  719. }
  720. } else if (graphicalContinuousDynamic.IsVerbal) {
  721. this.calculateGraphicalVerbalContinuousDynamic(graphicalContinuousDynamic, dynamicStartPosition);
  722. } else {
  723. log.warn("This continuous dynamic is not covered. measure" + multiExpression.SourceMeasureParent.MeasureNumber);
  724. }
  725. }
  726. }
  727. protected createMetronomeMark(metronomeExpression: InstantaneousTempoExpression): void {
  728. // note: sometimes MeasureNumber is 0 here, e.g. in Christbaum, maybe because of pickup measure (auftakt)
  729. const measureNumber: number = Math.max(metronomeExpression.ParentMultiTempoExpression.SourceMeasureParent.MeasureNumber - 1, 0);
  730. const staffNumber: number = Math.max(metronomeExpression.StaffNumber - 1, 0);
  731. const firstMetronomeMark: boolean = measureNumber === 0 && staffNumber === 0;
  732. const vfMeasure: VexFlowMeasure = (this.graphicalMusicSheet.MeasureList[measureNumber][staffNumber] as VexFlowMeasure);
  733. if (vfMeasure.hasMetronomeMark) {
  734. return; // don't create more than one metronome mark per measure;
  735. // TODO some measures still seem to have two metronome marks, one less bold than the other (or not bold),
  736. // might be because of both <sound> node and <per-minute> node (within <metronome>) creating metronome marks
  737. }
  738. const vfStave: VF.Stave = vfMeasure.getVFStave();
  739. //vfStave.addModifier(new VF.StaveTempo( // needs Vexflow PR
  740. let vexflowDuration: string = "q";
  741. if (metronomeExpression.beatUnit) {
  742. const duration: Fraction = NoteTypeHandler.getNoteDurationFromType(metronomeExpression.beatUnit);
  743. vexflowDuration = VexFlowConverter.durations(duration, false)[0];
  744. }
  745. let yShift: number = this.rules.MetronomeMarkYShift;
  746. let hasExpressionsAboveStaffline: boolean = false;
  747. for (const expression of metronomeExpression.parentMeasure.TempoExpressions) {
  748. const isMetronomeExpression: boolean = expression.InstantaneousTempo?.Enum === TempoEnum.metronomeMark;
  749. if (expression.getPlacementOfFirstEntry() === PlacementEnum.Above &&
  750. !isMetronomeExpression) {
  751. hasExpressionsAboveStaffline = true;
  752. break;
  753. }
  754. }
  755. if (hasExpressionsAboveStaffline) {
  756. yShift -= 1.4;
  757. // TODO improve this with proper skyline / collision detection. unfortunately we don't have a skyline here yet.
  758. // let maxSkylineBeginning: number = 0;
  759. // for (let i = 0; i < skyline.length / 1; i++) { // search in first 3rd, disregard end of measure
  760. // maxSkylineBeginning = Math.max(skyline[i], maxSkylineBeginning);
  761. // }
  762. // console.log('max skyline: ' + maxSkylineBeginning);
  763. }
  764. const skyline: number[] = this.graphicalMusicSheet.MeasureList[0][0].ParentStaffLine?.SkyLine;
  765. vfStave.setTempo(
  766. {
  767. bpm: metronomeExpression.TempoInBpm,
  768. dots: metronomeExpression.dotted,
  769. duration: vexflowDuration
  770. },
  771. yShift * unitInPixels);
  772. // -50, -30), 0); //needs Vexflow PR
  773. //.setShiftX(-50);
  774. const xShift: number = firstMetronomeMark ? this.rules.MetronomeMarkXShift * unitInPixels : 0;
  775. (<any>vfStave.getModifiers()[vfStave.getModifiers().length - 1]).setShiftX(
  776. xShift
  777. );
  778. vfMeasure.hasMetronomeMark = true;
  779. if (skyline) {
  780. // TODO calculate bounding box of metronome mark instead of hacking skyline to fix lyricist collision
  781. skyline[0] = Math.min(skyline[0], -4.5 + yShift);
  782. }
  783. // somehow this is called repeatedly in Clementi, so skyline[0] = Math.min instead of -=
  784. }
  785. protected calculateRehearsalMark(measure: SourceMeasure): void {
  786. const rehearsalExpression: RehearsalExpression = measure.rehearsalExpression;
  787. if (!rehearsalExpression) {
  788. return;
  789. }
  790. const firstMeasureNumber: number = this.graphicalMusicSheet.MeasureList[0][0].MeasureNumber; // 0 for pickup, 1 otherwise
  791. const measureNumber: number = Math.max(measure.MeasureNumber - firstMeasureNumber, 0);
  792. const staffNumber: number = 0;
  793. const vfStave: VF.Stave = (this.graphicalMusicSheet.MeasureList[measureNumber][staffNumber] as VexFlowMeasure)?.getVFStave();
  794. if (!vfStave) { // potentially multi measure rest
  795. return;
  796. }
  797. const yOffset: number = -this.rules.RehearsalMarkYOffsetDefault - this.rules.RehearsalMarkYOffset;
  798. let xOffset: number = this.rules.RehearsalMarkXOffsetDefault + this.rules.RehearsalMarkXOffset;
  799. if (measure.IsSystemStartMeasure) {
  800. xOffset += this.rules.RehearsalMarkXOffsetSystemStartMeasure;
  801. }
  802. // const section: VF.StaveSection = new VF.StaveSection(rehearsalExpression.label, vfStave.getX(), yOffset);
  803. // (vfStave as any).modifiers.push(section);
  804. const fontSize: number = this.rules.RehearsalMarkFontSize;
  805. (vfStave as any).setSection(rehearsalExpression.label, yOffset, xOffset, fontSize); // fontSize is an extra argument from VexFlowPatch
  806. }
  807. /**
  808. * Calculate a single OctaveShift for a [[MultiExpression]].
  809. * @param sourceMeasure
  810. * @param multiExpression
  811. * @param measureIndex
  812. * @param staffIndex
  813. */
  814. protected calculateSingleOctaveShift(sourceMeasure: SourceMeasure, multiExpression: MultiExpression, measureIndex: number, staffIndex: number): void {
  815. // calculate absolute Timestamp and startStaffLine (and EndStaffLine if needed)
  816. const octaveShift: OctaveShift = multiExpression.OctaveShiftStart;
  817. const startTimeStamp: Fraction = octaveShift.ParentStartMultiExpression.Timestamp;
  818. const endTimeStamp: Fraction = octaveShift.ParentEndMultiExpression?.Timestamp;
  819. const minMeasureToDrawIndex: number = this.rules.MinMeasureToDrawIndex;
  820. const maxMeasureToDrawIndex: number = this.rules.MaxMeasureToDrawIndex;
  821. let startStaffLine: StaffLine = this.graphicalMusicSheet.MeasureList[measureIndex][staffIndex].ParentStaffLine;
  822. if (!startStaffLine) { // fix for rendering range set. all of these can probably be done cleaner.
  823. startStaffLine = this.graphicalMusicSheet.MeasureList[minMeasureToDrawIndex][staffIndex].ParentStaffLine;
  824. }
  825. let endMeasure: GraphicalMeasure = undefined;
  826. if (octaveShift.ParentEndMultiExpression) {
  827. endMeasure = this.graphicalMusicSheet.getGraphicalMeasureFromSourceMeasureAndIndex(octaveShift.ParentEndMultiExpression.SourceMeasureParent,
  828. staffIndex);
  829. } else {
  830. endMeasure = this.graphicalMusicSheet.getLastGraphicalMeasureFromIndex(staffIndex, true); // get last rendered measure
  831. }
  832. if (endMeasure.MeasureNumber > maxMeasureToDrawIndex + 1) { // octaveshift ends in measure not rendered
  833. endMeasure = this.graphicalMusicSheet.getLastGraphicalMeasureFromIndex(staffIndex, true);
  834. }
  835. let startMeasure: GraphicalMeasure = undefined;
  836. if (octaveShift.ParentEndMultiExpression) {
  837. startMeasure = this.graphicalMusicSheet.getGraphicalMeasureFromSourceMeasureAndIndex(octaveShift.ParentStartMultiExpression.SourceMeasureParent,
  838. staffIndex);
  839. } else {
  840. startMeasure = this.graphicalMusicSheet.MeasureList[minMeasureToDrawIndex][staffIndex]; // first rendered measure
  841. }
  842. if (startMeasure.MeasureNumber < minMeasureToDrawIndex + 1) { // octaveshift starts before range of measures selected to render
  843. startMeasure = this.graphicalMusicSheet.MeasureList[minMeasureToDrawIndex][staffIndex]; // first rendered measure
  844. }
  845. if (startMeasure.parentSourceMeasure.measureListIndex < minMeasureToDrawIndex ||
  846. startMeasure.parentSourceMeasure.measureListIndex > maxMeasureToDrawIndex ||
  847. endMeasure.parentSourceMeasure.measureListIndex < minMeasureToDrawIndex ||
  848. endMeasure.parentSourceMeasure.measureListIndex > maxMeasureToDrawIndex) {
  849. // completely out of drawing range, don't draw anything
  850. return;
  851. }
  852. let endStaffLine: StaffLine = endMeasure.ParentStaffLine;
  853. if (!endStaffLine) {
  854. endStaffLine = startStaffLine;
  855. }
  856. if (endMeasure && startStaffLine && endStaffLine) {
  857. // calculate GraphicalOctaveShift and RelativePositions
  858. const graphicalOctaveShift: VexFlowOctaveShift = new VexFlowOctaveShift(octaveShift, startStaffLine.PositionAndShape);
  859. if (!graphicalOctaveShift.startNote) { // fix for rendering range set
  860. let startGse: GraphicalStaffEntry;
  861. for (const gse of startMeasure.staffEntries) {
  862. if (gse) {
  863. startGse = gse;
  864. break;
  865. } // sometimes the first graphical staff entry is undefined, not sure why.
  866. }
  867. if (!startGse) {
  868. return; // couldn't find a start staffentry, don't draw the octave shift
  869. }
  870. graphicalOctaveShift.setStartNote(startGse);
  871. if (!graphicalOctaveShift.startNote) {
  872. return; // couldn't find a start note, don't draw the octave shift
  873. }
  874. }
  875. if (!graphicalOctaveShift.endNote) { // fix for rendering range set
  876. let endGse: GraphicalStaffEntry;
  877. for (let i: number = endMeasure.staffEntries.length - 1; i >= 0; i++) {
  878. // search backwards from end of measure
  879. if (endMeasure.staffEntries[i]) {
  880. endGse = endMeasure.staffEntries[i];
  881. break;
  882. }
  883. }
  884. if (!endGse) {
  885. // shouldn't happen, but apparently some MusicXMLs (GuitarPro/Sibelius) have measures without StaffEntries.
  886. graphicalOctaveShift.graphicalEndAtMeasureEnd = true;
  887. return;
  888. }
  889. graphicalOctaveShift.setEndNote(endGse);
  890. if (!graphicalOctaveShift.endNote) {
  891. return;
  892. }
  893. }
  894. // calculate RelativePosition and Dashes
  895. let startStaffEntry: GraphicalStaffEntry = startMeasure.findGraphicalStaffEntryFromTimestamp(startTimeStamp);
  896. if (!startStaffEntry) { // fix for rendering range set
  897. startStaffEntry = startMeasure.staffEntries[0];
  898. }
  899. let endStaffEntry: GraphicalStaffEntry = endMeasure.findGraphicalStaffEntryFromTimestamp(endTimeStamp);
  900. if (!endStaffEntry) { // fix for rendering range set
  901. endStaffEntry = endMeasure.staffEntries[endMeasure.staffEntries.length - 1];
  902. }
  903. graphicalOctaveShift.setStartNote(startStaffEntry);
  904. if (endStaffLine !== startStaffLine) {
  905. graphicalOctaveShift.endsOnDifferentStaffLine = true;
  906. let lastMeasureOfFirstShift: GraphicalMeasure = this.findLastStafflineMeasure(startStaffLine);
  907. if (lastMeasureOfFirstShift === undefined) { // TODO handle this case correctly (e.g. when no staffentries found above or drawUpToMeasureNumber set)
  908. lastMeasureOfFirstShift = endMeasure;
  909. }
  910. const lastNoteOfFirstShift: GraphicalStaffEntry = lastMeasureOfFirstShift.staffEntries[lastMeasureOfFirstShift.staffEntries.length - 1];
  911. graphicalOctaveShift.setEndNote(lastNoteOfFirstShift);
  912. graphicalOctaveShift.graphicalEndAtMeasureEnd = true;
  913. graphicalOctaveShift.endMeasure = lastMeasureOfFirstShift;
  914. const systemsInBetweenCount: number = endStaffLine.ParentMusicSystem.Id - startStaffLine.ParentMusicSystem.Id;
  915. if (systemsInBetweenCount > 0) {
  916. //Loop through the stafflines in between to the end
  917. for (let i: number = startStaffLine.ParentMusicSystem.Id; i < endStaffLine.ParentMusicSystem.Id; i++) {
  918. const idx: number = i + 1;
  919. const nextShiftMusicSystem: MusicSystem = this.musicSystems[idx];
  920. let nextShiftStaffline: StaffLine; // not always = nextShiftMusicSystem.StaffLines[staffIndex], e.g. when first instrument invisible
  921. for (const staffline of nextShiftMusicSystem.StaffLines) {
  922. if (staffline.ParentStaff.idInMusicSheet === staffIndex) {
  923. nextShiftStaffline = staffline;
  924. break;
  925. }
  926. }
  927. if (!nextShiftStaffline) { // shouldn't happen
  928. continue;
  929. }
  930. const nextShiftFirstMeasure: GraphicalMeasure = nextShiftStaffline.Measures[0];
  931. // Shift starts on the first measure
  932. const nextOctaveShift: VexFlowOctaveShift = new VexFlowOctaveShift(octaveShift, nextShiftFirstMeasure.PositionAndShape);
  933. let nextShiftLastMeasure: GraphicalMeasure = this.findLastStafflineMeasure(nextShiftStaffline);
  934. if (i < systemsInBetweenCount - 1) {
  935. // if not - 1, the last octaveshift will always go to the end of the staffline
  936. nextOctaveShift.endsOnDifferentStaffLine = true;
  937. nextOctaveShift.graphicalEndAtMeasureEnd = true;
  938. nextOctaveShift.endMeasure = nextShiftLastMeasure;
  939. }
  940. const firstNote: GraphicalStaffEntry = nextShiftFirstMeasure.staffEntries[0];
  941. let lastNote: GraphicalStaffEntry = nextShiftLastMeasure.staffEntries[nextShiftLastMeasure.staffEntries.length - 1];
  942. //If the end measure's staffline is the ending staffline, this endMeasure is the end of the shift
  943. if (endMeasure.ParentStaffLine === nextShiftStaffline) {
  944. nextShiftLastMeasure = endMeasure;
  945. lastNote = endStaffEntry;
  946. }
  947. if (lastNote.graphicalVoiceEntries.length === 1 &&
  948. lastNote.graphicalVoiceEntries[0].notes.length === 1 &&
  949. lastNote.graphicalVoiceEntries[0].notes[0].sourceNote.isWholeMeasureNote()
  950. ) {
  951. // also draw octaveshift until end of measure if we have a whole note that goes over the whole measure
  952. nextOctaveShift.graphicalEndAtMeasureEnd = true;
  953. nextOctaveShift.endMeasure = nextShiftLastMeasure;
  954. }
  955. const logPrefix: string = "VexFlowMusicSheetCalculator.calculateSingleOctaveShift: ";
  956. if (!firstNote) {
  957. log.warn(logPrefix + "no firstNote found");
  958. }
  959. if (!lastNote) {
  960. log.warn(logPrefix + "no lastNote found");
  961. }
  962. nextOctaveShift.setStartNote(firstNote);
  963. nextOctaveShift.setEndNote(lastNote);
  964. nextShiftStaffline.OctaveShifts.push(nextOctaveShift);
  965. this.calculateOctaveShiftSkyBottomLine(firstNote, lastNote, nextOctaveShift, nextShiftStaffline);
  966. }
  967. }
  968. this.calculateOctaveShiftSkyBottomLine(startStaffEntry, lastNoteOfFirstShift, graphicalOctaveShift, startStaffLine);
  969. } else {
  970. graphicalOctaveShift.setEndNote(endStaffEntry);
  971. this.calculateOctaveShiftSkyBottomLine(startStaffEntry, endStaffEntry, graphicalOctaveShift, startStaffLine);
  972. }
  973. startStaffLine.OctaveShifts.push(graphicalOctaveShift);
  974. } else {
  975. log.warn("End measure or staffLines for octave shift are undefined! This should not happen!");
  976. }
  977. }
  978. /** Finds the last staffline measure that has staffentries. (staffentries necessary for octaveshift and pedal) */
  979. protected findLastStafflineMeasure(staffline: StaffLine): GraphicalMeasure {
  980. for (let i: number = staffline.Measures.length - 1; i >= 0; i--) {
  981. const measure: GraphicalMeasure = staffline.Measures[i];
  982. if (measure.staffEntries.length > 0) {
  983. return measure;
  984. // a measure can have no staff entries if e.g. measure.IsExtraGraphicalMeasure, used to show key/rhythm changes.
  985. }
  986. // else continue with the measure before this one
  987. }
  988. }
  989. protected calculateSinglePedal(sourceMeasure: SourceMeasure, multiExpression: MultiExpression, measureIndex: number, staffIndex: number): void {
  990. // calculate absolute Timestamp and startStaffLine (and EndStaffLine if needed)
  991. const pedal: Pedal = multiExpression.PedalStart;
  992. const startTimeStamp: Fraction = pedal.ParentStartMultiExpression.Timestamp;
  993. const endTimeStamp: Fraction = pedal.ParentEndMultiExpression?.Timestamp;
  994. const minMeasureToDrawIndex: number = this.rules.MinMeasureToDrawIndex;
  995. const maxMeasureToDrawIndex: number = this.rules.MaxMeasureToDrawIndex;
  996. let startStaffLine: StaffLine = this.graphicalMusicSheet.MeasureList[measureIndex][staffIndex].ParentStaffLine;
  997. if (!startStaffLine) { // fix for rendering range set. all of these can probably be done cleaner.
  998. startStaffLine = this.graphicalMusicSheet.MeasureList[minMeasureToDrawIndex][staffIndex].ParentStaffLine;
  999. }
  1000. let endMeasure: GraphicalMeasure = undefined;
  1001. if (pedal.ParentEndMultiExpression) {
  1002. endMeasure = this.graphicalMusicSheet.getGraphicalMeasureFromSourceMeasureAndIndex(pedal.ParentEndMultiExpression.SourceMeasureParent,
  1003. staffIndex);
  1004. } else {
  1005. //return; // also possible: don't handle faulty pedal without end
  1006. endMeasure = this.graphicalMusicSheet.getLastGraphicalMeasureFromIndex(staffIndex, true); // get last rendered measure
  1007. }
  1008. if (endMeasure.MeasureNumber > maxMeasureToDrawIndex + 1) { // ends in measure not rendered
  1009. endMeasure = this.graphicalMusicSheet.getLastGraphicalMeasureFromIndex(staffIndex, true);
  1010. }
  1011. let startMeasure: GraphicalMeasure = undefined;
  1012. if (pedal.ParentEndMultiExpression) {
  1013. startMeasure = this.graphicalMusicSheet.getGraphicalMeasureFromSourceMeasureAndIndex(pedal.ParentStartMultiExpression.SourceMeasureParent,
  1014. staffIndex);
  1015. } else {
  1016. startMeasure = this.graphicalMusicSheet.getGraphicalMeasureFromSourceMeasureAndIndex(
  1017. pedal.ParentStartMultiExpression.SourceMeasureParent,
  1018. staffIndex);
  1019. if (!startMeasure) {
  1020. startMeasure = this.graphicalMusicSheet.MeasureList[minMeasureToDrawIndex][staffIndex]; // first rendered measure
  1021. }
  1022. //console.log("no end multi expression for start measure " + startMeasure.MeasureNumber);
  1023. }
  1024. if (startMeasure.MeasureNumber < minMeasureToDrawIndex + 1) { // starts before range of measures selected to render
  1025. startMeasure = this.graphicalMusicSheet.MeasureList[minMeasureToDrawIndex][staffIndex]; // first rendered measure
  1026. }
  1027. if (startMeasure.parentSourceMeasure.measureListIndex < minMeasureToDrawIndex ||
  1028. startMeasure.parentSourceMeasure.measureListIndex > maxMeasureToDrawIndex ||
  1029. endMeasure.parentSourceMeasure.measureListIndex < minMeasureToDrawIndex ||
  1030. endMeasure.parentSourceMeasure.measureListIndex > maxMeasureToDrawIndex) {
  1031. // completely out of drawing range, don't draw anything
  1032. return;
  1033. }
  1034. let endStaffLine: StaffLine = endMeasure.ParentStaffLine;
  1035. if (!endStaffLine) {
  1036. endStaffLine = startStaffLine;
  1037. }
  1038. if (endMeasure && startStaffLine && endStaffLine) {
  1039. let openEnd: boolean = false;
  1040. if (startStaffLine !== endStaffLine) {
  1041. openEnd = true;
  1042. }
  1043. // calculate GraphicalPedal and RelativePositions
  1044. const graphicalPedal: VexFlowPedal = new VexFlowPedal(pedal, startStaffLine.PositionAndShape, false, openEnd);
  1045. graphicalPedal.setEndsStave(endMeasure, endTimeStamp); // unfortunately this can't already be checked in ExpressionReader
  1046. // calculate RelativePosition
  1047. let startStaffEntry: GraphicalStaffEntry = startMeasure.findGraphicalStaffEntryFromTimestamp(startTimeStamp);
  1048. if (!startStaffEntry) { // fix for rendering range set
  1049. startStaffEntry = startMeasure.staffEntries[0];
  1050. }
  1051. let endStaffEntry: GraphicalStaffEntry = endMeasure.findGraphicalStaffEntryFromTimestamp(endTimeStamp);
  1052. if (!endStaffEntry) { // fix for rendering range set
  1053. endStaffEntry = endMeasure.staffEntries[endMeasure.staffEntries.length - 1];
  1054. // TODO can be undefined if no notes in end measure
  1055. }
  1056. if (!graphicalPedal.setStartNote(startStaffEntry)){
  1057. return;
  1058. }
  1059. graphicalPedal.setBeginsStave(graphicalPedal.startNote.isRest(), startTimeStamp);
  1060. if (endStaffLine !== startStaffLine) {
  1061. if(graphicalPedal.pedalSymbol === MusicSymbol.PEDAL_SYMBOL){
  1062. graphicalPedal.setEndNote(endStaffEntry);
  1063. graphicalPedal.setEndMeasure(endMeasure);
  1064. graphicalPedal.ReleaseText = " ";
  1065. graphicalPedal.CalculateBoundingBox();
  1066. this.calculatePedalSkyBottomLine(graphicalPedal.startVfVoiceEntry, graphicalPedal.endVfVoiceEntry, graphicalPedal, startStaffLine);
  1067. const nextPedalFirstMeasure: GraphicalMeasure = endStaffLine.Measures[0];
  1068. // pedal starts on the first measure
  1069. const nextPedal: VexFlowPedal = new VexFlowPedal(pedal, nextPedalFirstMeasure.PositionAndShape);
  1070. graphicalPedal.setEndsStave(endMeasure, endTimeStamp);
  1071. const firstNote: GraphicalStaffEntry = nextPedalFirstMeasure.staffEntries[0];
  1072. if(!nextPedal.setStartNote(firstNote)){
  1073. return;
  1074. }
  1075. nextPedal.setEndNote(endStaffEntry);
  1076. nextPedal.setEndMeasure(endMeasure);
  1077. graphicalPedal.setEndMeasure(endMeasure);
  1078. endStaffLine.Pedals.push(nextPedal);
  1079. nextPedal.CalculateBoundingBox();
  1080. nextPedal.DepressText = " ";
  1081. this.calculatePedalSkyBottomLine(nextPedal.startVfVoiceEntry, nextPedal.endVfVoiceEntry, nextPedal, endStaffLine);
  1082. } else {
  1083. let lastMeasureOfFirstShift: GraphicalMeasure = this.findLastStafflineMeasure(startStaffLine);
  1084. if (lastMeasureOfFirstShift === undefined) { // TODO handle this case correctly (when drawUpToMeasureNumber etc set)
  1085. lastMeasureOfFirstShift = endMeasure;
  1086. }
  1087. const lastNoteOfFirstShift: GraphicalStaffEntry = lastMeasureOfFirstShift.staffEntries[lastMeasureOfFirstShift.staffEntries.length - 1];
  1088. graphicalPedal.setEndNote(lastNoteOfFirstShift);
  1089. graphicalPedal.setEndMeasure(endMeasure);
  1090. graphicalPedal.ChangeEnd = false;
  1091. const systemsInBetweenCount: number = endStaffLine.ParentMusicSystem.Id - startStaffLine.ParentMusicSystem.Id;
  1092. if (systemsInBetweenCount > 0) {
  1093. //Loop through the stafflines in between to the end
  1094. let currentCount: number = 1;
  1095. for (let i: number = startStaffLine.ParentMusicSystem.Id; i < endStaffLine.ParentMusicSystem.Id; i++) {
  1096. const nextPedalMusicSystem: MusicSystem = this.musicSystems[i + 1];
  1097. const nextPedalStaffline: StaffLine = nextPedalMusicSystem.StaffLines[staffIndex];
  1098. const nextPedalFirstMeasure: GraphicalMeasure = nextPedalStaffline.Measures[0];
  1099. let nextOpenEnd: boolean = false;
  1100. let nextChangeEndFromParent: boolean = false;
  1101. if (currentCount < systemsInBetweenCount) {
  1102. nextOpenEnd = true;
  1103. } else {
  1104. nextChangeEndFromParent = true;
  1105. }
  1106. currentCount++;
  1107. // pedal starts on the first measure
  1108. const nextPedal: VexFlowPedal = new VexFlowPedal(pedal, nextPedalFirstMeasure.PositionAndShape, true, nextOpenEnd);
  1109. graphicalPedal.setEndsStave(endMeasure, endTimeStamp);
  1110. nextPedal.ChangeBegin = false;
  1111. if(nextChangeEndFromParent){
  1112. nextPedal.ChangeEnd = pedal.ChangeEnd;
  1113. } else {
  1114. nextPedal.ChangeEnd = false;
  1115. }
  1116. let nextPedalLastMeasure: GraphicalMeasure = this.findLastStafflineMeasure(nextPedalStaffline);
  1117. const firstNote: GraphicalStaffEntry = nextPedalFirstMeasure.staffEntries[0];
  1118. let lastNote: GraphicalStaffEntry = nextPedalLastMeasure.staffEntries[nextPedalLastMeasure.staffEntries.length - 1];
  1119. //If the end measure's staffline is the ending staffline, this endMeasure is the end of the pedal
  1120. if (endMeasure.ParentStaffLine === nextPedalStaffline) {
  1121. nextPedalLastMeasure = endMeasure;
  1122. nextPedal.setEndMeasure(endMeasure);
  1123. lastNote = endStaffEntry;
  1124. } else {
  1125. nextPedal.setEndMeasure(nextPedalStaffline.Measures.last());
  1126. }
  1127. if(!nextPedal.setStartNote(firstNote)){
  1128. break;
  1129. }
  1130. nextPedal.setEndNote(lastNote);
  1131. graphicalPedal.setEndMeasure(endMeasure);
  1132. nextPedalStaffline.Pedals.push(nextPedal);
  1133. nextPedal.CalculateBoundingBox();
  1134. this.calculatePedalSkyBottomLine(nextPedal.startVfVoiceEntry, nextPedal.endVfVoiceEntry, nextPedal, nextPedalStaffline);
  1135. }
  1136. }
  1137. graphicalPedal.CalculateBoundingBox();
  1138. this.calculatePedalSkyBottomLine(graphicalPedal.startVfVoiceEntry, graphicalPedal.endVfVoiceEntry, graphicalPedal, startStaffLine);
  1139. }
  1140. } else {
  1141. graphicalPedal.setEndNote(endStaffEntry);
  1142. graphicalPedal.setEndMeasure(endMeasure);
  1143. graphicalPedal.CalculateBoundingBox();
  1144. this.calculatePedalSkyBottomLine(graphicalPedal.startVfVoiceEntry, graphicalPedal.endVfVoiceEntry, graphicalPedal, startStaffLine);
  1145. }
  1146. startStaffLine.Pedals.push(graphicalPedal);
  1147. } else {
  1148. log.warn("End measure or staffLines for pedal are undefined! This should not happen!");
  1149. }
  1150. }
  1151. protected calculateSingleWavyLine(sourceMeasure: SourceMeasure, multiExpression: MultiExpression, measureIndex: number, staffIndex: number): void {
  1152. // calculate absolute Timestamp and startStaffLine (and EndStaffLine if needed)
  1153. const wavyLine: WavyLine = multiExpression.WavyLineStart;
  1154. const startTimeStamp: Fraction = wavyLine.ParentStartMultiExpression.Timestamp;
  1155. const endTimeStamp: Fraction = wavyLine.ParentEndMultiExpression?.Timestamp;
  1156. const minMeasureToDrawIndex: number = this.rules.MinMeasureToDrawIndex;
  1157. const maxMeasureToDrawIndex: number = this.rules.MaxMeasureToDrawIndex;
  1158. let startStaffLine: StaffLine = this.graphicalMusicSheet.MeasureList[measureIndex][staffIndex].ParentStaffLine;
  1159. if (!startStaffLine) { // fix for rendering range set. all of these can probably be done cleaner.
  1160. startStaffLine = this.graphicalMusicSheet.MeasureList[minMeasureToDrawIndex][staffIndex].ParentStaffLine;
  1161. }
  1162. let endMeasure: GraphicalMeasure = undefined;
  1163. if (wavyLine.ParentEndMultiExpression) {
  1164. endMeasure = this.graphicalMusicSheet.getGraphicalMeasureFromSourceMeasureAndIndex(wavyLine.ParentEndMultiExpression.SourceMeasureParent,
  1165. staffIndex);
  1166. } else {
  1167. endMeasure = this.graphicalMusicSheet.getLastGraphicalMeasureFromIndex(staffIndex, true); // get last rendered measure
  1168. }
  1169. if (endMeasure.MeasureNumber > maxMeasureToDrawIndex + 1) { // ends in measure not rendered
  1170. endMeasure = this.graphicalMusicSheet.getLastGraphicalMeasureFromIndex(staffIndex, true);
  1171. }
  1172. let startMeasure: GraphicalMeasure = undefined;
  1173. if (wavyLine.ParentEndMultiExpression) {
  1174. startMeasure = this.graphicalMusicSheet.getGraphicalMeasureFromSourceMeasureAndIndex(wavyLine.ParentStartMultiExpression.SourceMeasureParent,
  1175. staffIndex);
  1176. } else {
  1177. startMeasure = this.graphicalMusicSheet.MeasureList[minMeasureToDrawIndex][staffIndex]; // first rendered measure
  1178. }
  1179. if (startMeasure.MeasureNumber < minMeasureToDrawIndex + 1) { // starts before range of measures selected to render
  1180. startMeasure = this.graphicalMusicSheet.MeasureList[minMeasureToDrawIndex][staffIndex]; // first rendered measure
  1181. }
  1182. if (startMeasure.parentSourceMeasure.measureListIndex < minMeasureToDrawIndex ||
  1183. startMeasure.parentSourceMeasure.measureListIndex > maxMeasureToDrawIndex ||
  1184. endMeasure.parentSourceMeasure.measureListIndex < minMeasureToDrawIndex ||
  1185. endMeasure.parentSourceMeasure.measureListIndex > maxMeasureToDrawIndex) {
  1186. // completely out of drawing range, don't draw anything
  1187. return;
  1188. }
  1189. let endStaffLine: StaffLine = endMeasure.ParentStaffLine;
  1190. if (!endStaffLine) {
  1191. endStaffLine = startStaffLine;
  1192. }
  1193. if (endMeasure && startStaffLine && endStaffLine) {
  1194. const graphicalWavyLine: VexflowVibratoBracket = new VexflowVibratoBracket(wavyLine, startStaffLine.PositionAndShape, startMeasure.ParentStaff.isTab);
  1195. // calculate RelativePosition
  1196. let startStaffEntry: GraphicalStaffEntry = startMeasure.findGraphicalStaffEntryFromTimestamp(startTimeStamp);
  1197. if (!startStaffEntry) { // fix for rendering range set
  1198. startStaffEntry = startMeasure.staffEntries[0];
  1199. }
  1200. let endStaffEntry: GraphicalStaffEntry = endMeasure.findGraphicalStaffEntryFromTimestamp(endTimeStamp);
  1201. if (!endStaffEntry) { // fix for rendering range set
  1202. endStaffEntry = endMeasure.staffEntries[endMeasure.staffEntries.length - 1];
  1203. }
  1204. graphicalWavyLine.setStartNote(startStaffEntry);
  1205. if (endStaffLine !== startStaffLine) {
  1206. let lastMeasureOfFirstShift: GraphicalMeasure = startStaffLine.Measures[startStaffLine.Measures.length - 1];
  1207. if (lastMeasureOfFirstShift === undefined) { // TODO handle this case correctly (when drawUpToMeasureNumber etc set)
  1208. lastMeasureOfFirstShift = endMeasure;
  1209. }
  1210. const lastNoteOfFirstShift: GraphicalStaffEntry = lastMeasureOfFirstShift.staffEntries[lastMeasureOfFirstShift.staffEntries.length - 1];
  1211. graphicalWavyLine.setEndNote(lastNoteOfFirstShift);
  1212. const systemsInBetweenCount: number = endStaffLine.ParentMusicSystem.Id - startStaffLine.ParentMusicSystem.Id;
  1213. if (systemsInBetweenCount > 0) {
  1214. for (let i: number = startStaffLine.ParentMusicSystem.Id; i < endStaffLine.ParentMusicSystem.Id; i++) {
  1215. const nextWavyLineMusicSystem: MusicSystem = this.musicSystems[i + 1];
  1216. const nextWavyLineStaffline: StaffLine = nextWavyLineMusicSystem.StaffLines[staffIndex];
  1217. const nextWavyLineFirstMeasure: GraphicalMeasure = nextWavyLineStaffline.Measures[0];
  1218. // vibrato starts on the first measure
  1219. const nextWavyLine: VexflowVibratoBracket = new VexflowVibratoBracket(wavyLine, nextWavyLineFirstMeasure.PositionAndShape,
  1220. nextWavyLineStaffline.ParentStaff.isTab);
  1221. let nextWavyLineLastMeasure: GraphicalMeasure = nextWavyLineStaffline.Measures[nextWavyLineStaffline.Measures.length - 1];
  1222. const firstNote: GraphicalStaffEntry = nextWavyLineFirstMeasure.staffEntries[0];
  1223. let lastNote: GraphicalStaffEntry = nextWavyLineLastMeasure.staffEntries[nextWavyLineLastMeasure.staffEntries.length - 1];
  1224. //If the end measure's is the ending staffline, this endMeasure is the end of the wavy line
  1225. if (endMeasure.ParentStaffLine === nextWavyLineStaffline) {
  1226. nextWavyLineLastMeasure = endMeasure;
  1227. lastNote = endStaffEntry;
  1228. }
  1229. nextWavyLine.setStartNote(firstNote);
  1230. nextWavyLine.setEndNote(lastNote);
  1231. nextWavyLineStaffline.WavyLines.push(nextWavyLine);
  1232. nextWavyLine.CalculateBoundingBox();
  1233. this.calculateWavyLineSkyBottomLine(nextWavyLine.startVfVoiceEntry, nextWavyLine.endVfVoiceEntry, nextWavyLine, nextWavyLineStaffline);
  1234. }
  1235. }
  1236. graphicalWavyLine.CalculateBoundingBox();
  1237. this.calculateWavyLineSkyBottomLine(graphicalWavyLine.startVfVoiceEntry, graphicalWavyLine.endVfVoiceEntry, graphicalWavyLine, startStaffLine);
  1238. } else {
  1239. graphicalWavyLine.setEndNote(endStaffEntry);
  1240. graphicalWavyLine.CalculateBoundingBox();
  1241. this.calculateWavyLineSkyBottomLine(graphicalWavyLine.startVfVoiceEntry, graphicalWavyLine.endVfVoiceEntry, graphicalWavyLine, startStaffLine);
  1242. }
  1243. startStaffLine.WavyLines.push(graphicalWavyLine);
  1244. } else {
  1245. log.warn("End measure or staffLines for wavy line are undefined! This should not happen!");
  1246. }
  1247. }
  1248. private calculateWavyLineSkyBottomLine(startVfVoiceEntry: VexFlowVoiceEntry, endVfVoiceEntry: VexFlowVoiceEntry,
  1249. vfVibratoBracket: VexflowVibratoBracket, parentStaffline: StaffLine): void {
  1250. const startStave: Vex.Flow.Stave = vfVibratoBracket.startNote.getStave();
  1251. const endStave: Vex.Flow.Stave = vfVibratoBracket.endNote.getStave();
  1252. //In VF Line positions, need to negate for our units
  1253. const highestVFTopTextPosition: number = Math.max(
  1254. startStave.options.top_text_position,
  1255. endStave.options.top_text_position
  1256. );
  1257. //Whichever is higher, set the other to match
  1258. startStave.options.top_text_position = highestVFTopTextPosition;
  1259. endStave.options.top_text_position = highestVFTopTextPosition;
  1260. let headroom: number = -highestVFTopTextPosition;
  1261. let trillStartX: number = 0;
  1262. let trillEndX: number = 0;
  1263. let trillSkyline: number = Infinity;
  1264. let trillWavyLineBottom: number = Infinity;
  1265. const TRILL_HEIGHT: number = 1.85;
  1266. let startX: number = startVfVoiceEntry.PositionAndShape.AbsolutePosition.x + startVfVoiceEntry.PositionAndShape.BorderLeft;
  1267. if (startVfVoiceEntry.parentVoiceEntry?.OrnamentContainer?.GetOrnament === OrnamentEnum.Trill) {
  1268. trillStartX = startX;
  1269. //Width of trill mark
  1270. startX += 2;
  1271. trillEndX = startX;
  1272. //Since the trill mark is not managed or calculated by our bounding boxes, we have to get the location this way
  1273. //Also at this point the skyline has already been updated with the trill mark. So we can't determine if it should go lower
  1274. //Need to trust Vexflow later on, unless the wavy line must be rendered higher
  1275. trillSkyline = parentStaffline.SkyBottomLineCalculator.getSkyLineMinInRange(trillStartX, trillEndX);
  1276. //height of the trill mark
  1277. trillWavyLineBottom = trillSkyline + TRILL_HEIGHT;
  1278. }
  1279. let stopX: number = undefined;
  1280. //If the end of the line is the last note in the measure, go all the way to the end of the stave
  1281. if(vfVibratoBracket.ToEndOfStopStave) {
  1282. //vexflow backs off by 1 unit (10 pixels) from stave edge
  1283. stopX = endVfVoiceEntry.parentStaffEntry.parentMeasure.PositionAndShape.AbsolutePosition.x +
  1284. endVfVoiceEntry.parentStaffEntry.parentMeasure.PositionAndShape.BorderRight - 1;
  1285. } else {
  1286. stopX = endVfVoiceEntry.PositionAndShape.AbsolutePosition.x + endVfVoiceEntry.PositionAndShape.BorderRight;
  1287. //Take into account in-staff clefs associated with the staff entry (they modify the bounding box position)
  1288. const vfClefBefore: Vex.Flow.ClefNote = (endVfVoiceEntry.parentStaffEntry as VexFlowStaffEntry).vfClefBefore;
  1289. if (vfClefBefore) {
  1290. const clefWidth: number = vfClefBefore.getWidth() / 10;
  1291. stopX += clefWidth;
  1292. }
  1293. }
  1294. headroom = parentStaffline.SkyBottomLineCalculator.getSkyLineMinInRange(startX, stopX);
  1295. if (headroom === Infinity) { // will cause Vexflow error
  1296. return;
  1297. }
  1298. //If somewhere in our wavy line path we have to render higher than where the trill mark is set...
  1299. if (headroom < trillSkyline) {
  1300. startStave.options.top_text_position = -headroom;
  1301. endStave.options.top_text_position = -headroom;
  1302. //A decent enough approximation. Better than recalculating via Canvas or SVG sampling
  1303. parentStaffline.SkyBottomLineCalculator.updateSkyLineInRange(trillStartX, trillEndX, headroom - TRILL_HEIGHT);
  1304. } else { //Else just render where Vexflow has set the trill mark
  1305. vfVibratoBracket.line = -trillWavyLineBottom;
  1306. headroom = trillWavyLineBottom;
  1307. }
  1308. //Update skyline to include height of the wavy line
  1309. headroom -= vfVibratoBracket.PositionAndShape.Size.height;
  1310. parentStaffline.SkyBottomLineCalculator.updateSkyLineInRange(startX, stopX, headroom);
  1311. }
  1312. private calculatePedalSkyBottomLine(startVfVoiceEntry: VexFlowVoiceEntry, endVfVoiceEntry: VexFlowVoiceEntry,
  1313. vfPedal: VexFlowPedal, parentStaffline: StaffLine): void {
  1314. let endBbox: BoundingBox = endVfVoiceEntry?.PositionAndShape;
  1315. if (!endBbox) {
  1316. endBbox = vfPedal.endMeasure.PositionAndShape;
  1317. }
  1318. //Just for shorthand. Easier readability below
  1319. const PEDAL_STYLES_ENUM: any = Vex.Flow.PedalMarking.Styles;
  1320. const pedalMarking: any = vfPedal.getPedalMarking();
  1321. //VF adds 3 lines to whatever the pedal line is set to.
  1322. //VF also measures from the bottom line, whereas our bottom line is from the top staff line
  1323. const yLineForPedalMarking: number = (pedalMarking.line + 3 + (parentStaffline.StaffLines.length - 1));
  1324. //VF Uses a margin offset for rendering. Take this into account
  1325. const pedalMarkingMarginXOffset: number = pedalMarking.render_options.text_margin_right / 10;
  1326. //TODO: Most of this should be in the bounding box calculation
  1327. let startX: number = startVfVoiceEntry.PositionAndShape.AbsolutePosition.x - pedalMarkingMarginXOffset;
  1328. if (pedalMarking.style === PEDAL_STYLES_ENUM.MIXED ||
  1329. pedalMarking.style === PEDAL_STYLES_ENUM.MIXED_OPEN_END ||
  1330. pedalMarking.style === PEDAL_STYLES_ENUM.TEXT) {
  1331. //Accomodate the Ped. sign
  1332. startX -= 1;
  1333. }
  1334. let stopX: number = undefined;
  1335. let footroom: number = (parentStaffline.StaffLines.length - 1);
  1336. //Find the highest foot room in our staffline
  1337. for (const otherPedal of parentStaffline.Pedals) {
  1338. const vfOtherPedal: VexFlowPedal = otherPedal as VexFlowPedal;
  1339. const otherPedalMarking: any = vfOtherPedal.getPedalMarking();
  1340. const yLineForOtherPedalMarking: number = (otherPedalMarking.line + 3 + (parentStaffline.StaffLines.length - 1));
  1341. footroom = Math.max(yLineForOtherPedalMarking, footroom);
  1342. }
  1343. //We have the two seperate symbols, with two bounding boxes
  1344. if (vfPedal.EndSymbolPositionAndShape) {
  1345. const symbolHalfHeight: number = pedalMarking.render_options.glyph_point_size / 20;
  1346. //Width of the Ped. symbol
  1347. stopX = startX + 3.4;
  1348. const startX2: number = endBbox.AbsolutePosition.x - pedalMarkingMarginXOffset;
  1349. //Width of * symbol
  1350. const stopX2: number = startX2 + 1.5;
  1351. footroom = Math.max(parentStaffline.SkyBottomLineCalculator.getBottomLineMaxInRange(startX, stopX), footroom);
  1352. footroom = Math.max(yLineForPedalMarking + symbolHalfHeight * 2, footroom);
  1353. const footroom2: number = parentStaffline.SkyBottomLineCalculator.getBottomLineMaxInRange(startX2, stopX2);
  1354. //If Depress text is set, means we are not rendering the begin label (we are just rendering the end one)
  1355. if (!vfPedal.DepressText) {
  1356. footroom = Math.max(footroom, footroom2);
  1357. }
  1358. vfPedal.setLine(footroom - 3 - (parentStaffline.StaffLines.length - 1));
  1359. parentStaffline.SkyBottomLineCalculator.updateBottomLineInRange(startX, stopX, footroom + symbolHalfHeight);
  1360. parentStaffline.SkyBottomLineCalculator.updateBottomLineInRange(startX2, stopX2, footroom + symbolHalfHeight);
  1361. } else {
  1362. const bracketHeight: number = pedalMarking.render_options.bracket_height / 10;
  1363. if(pedalMarking.EndsStave){
  1364. if(endVfVoiceEntry){
  1365. stopX = endVfVoiceEntry.parentStaffEntry.parentMeasure.PositionAndShape.AbsolutePosition.x +
  1366. endVfVoiceEntry.parentStaffEntry.parentMeasure.PositionAndShape.Size.width - pedalMarkingMarginXOffset;
  1367. } else {
  1368. stopX = endBbox.AbsolutePosition.x + endBbox.Size.width;
  1369. }
  1370. } else {
  1371. switch (pedalMarking.style) {
  1372. case PEDAL_STYLES_ENUM.BRACKET_OPEN_END:
  1373. case PEDAL_STYLES_ENUM.BRACKET_OPEN_BOTH:
  1374. case PEDAL_STYLES_ENUM.MIXED_OPEN_END:
  1375. stopX = endBbox.AbsolutePosition.x + endBbox.BorderRight - pedalMarkingMarginXOffset;
  1376. break;
  1377. default:
  1378. stopX = endBbox.AbsolutePosition.x + endBbox.BorderLeft - pedalMarkingMarginXOffset;
  1379. break;
  1380. }
  1381. }
  1382. //Take into account in-staff clefs associated with the staff entry (they modify the bounding box position)
  1383. const vfClefBefore: Vex.Flow.ClefNote = (endVfVoiceEntry?.parentStaffEntry as VexFlowStaffEntry)?.vfClefBefore;
  1384. if (vfClefBefore) {
  1385. const clefWidth: number = vfClefBefore.getWidth() / 10;
  1386. stopX += clefWidth;
  1387. }
  1388. footroom = Math.max(parentStaffline.SkyBottomLineCalculator.getBottomLineMaxInRange(startX, stopX), footroom);
  1389. if (footroom === Infinity) { // will cause Vexflow error
  1390. return;
  1391. }
  1392. //Whatever is currently lower - the set render height of the begin vf stave, the set render height of the end vf stave,
  1393. //or the bottom line. Use that as the render height of both staves
  1394. footroom = Math.max(footroom, yLineForPedalMarking + bracketHeight);
  1395. vfPedal.setLine(footroom - 3 - (parentStaffline.StaffLines.length - 1));
  1396. if (startX > stopX) { // TODO hotfix for skybottomlinecalculator after pedal no endNote fix
  1397. const newStart: number = stopX;
  1398. stopX = startX;
  1399. startX = newStart;
  1400. }
  1401. parentStaffline.SkyBottomLineCalculator.updateBottomLineInRange(startX, stopX, footroom + bracketHeight);
  1402. }
  1403. //If our current pedal is below the other pedals in this staffline, set them all to this height
  1404. for (const otherPedal of parentStaffline.Pedals) {
  1405. const vfOtherPedal: VexFlowPedal = otherPedal as VexFlowPedal;
  1406. const otherPedalMarking: any = vfOtherPedal.getPedalMarking();
  1407. const yLineForOtherPedalMarking: number = (otherPedalMarking.line + 3 + (parentStaffline.StaffLines.length - 1));
  1408. //Only do these changes if current footroom is higher
  1409. if(footroom > yLineForOtherPedalMarking) {
  1410. const otherPedalMarkingMarginXOffset: number = otherPedalMarking.render_options.text_margin_right / 10;
  1411. let otherPedalStartX: number = vfOtherPedal.startVfVoiceEntry.PositionAndShape.AbsolutePosition.x - otherPedalMarkingMarginXOffset;
  1412. let otherPedalStopX: number = undefined;
  1413. vfOtherPedal.setLine(footroom - 3 - (parentStaffline.StaffLines.length - 1));
  1414. let otherPedalEndBBox: BoundingBox = vfOtherPedal.endVfVoiceEntry?.PositionAndShape;
  1415. if (!otherPedalEndBBox) {
  1416. otherPedalEndBBox = vfOtherPedal.endMeasure.PositionAndShape;
  1417. }
  1418. if (vfOtherPedal.EndSymbolPositionAndShape) {
  1419. const otherSymbolHalfHeight: number = pedalMarking.render_options.glyph_point_size / 20;
  1420. //Width of the Ped. symbol
  1421. otherPedalStopX = otherPedalStartX + 3.4;
  1422. const otherPedalStartX2: number = otherPedalEndBBox.AbsolutePosition.x - otherPedalMarkingMarginXOffset;
  1423. //Width of * symbol
  1424. const otherPedalStopX2: number = otherPedalStartX2 + 1.5;
  1425. parentStaffline.SkyBottomLineCalculator.updateBottomLineInRange(otherPedalStartX, otherPedalStopX, footroom + otherSymbolHalfHeight);
  1426. parentStaffline.SkyBottomLineCalculator.updateBottomLineInRange(otherPedalStartX2, otherPedalStopX2, footroom + otherSymbolHalfHeight);
  1427. } else {
  1428. const otherPedalBracketHeight: number = otherPedalMarking.render_options.bracket_height / 10;
  1429. if(otherPedalMarking.EndsStave){
  1430. otherPedalStopX = otherPedalEndBBox.AbsolutePosition.x + otherPedalEndBBox.Size.width - otherPedalMarkingMarginXOffset;
  1431. } else {
  1432. switch (pedalMarking.style) {
  1433. case PEDAL_STYLES_ENUM.BRACKET_OPEN_END:
  1434. case PEDAL_STYLES_ENUM.BRACKET_OPEN_BOTH:
  1435. case PEDAL_STYLES_ENUM.MIXED_OPEN_END:
  1436. otherPedalStopX = otherPedalEndBBox.AbsolutePosition.x + otherPedalEndBBox.BorderRight - otherPedalMarkingMarginXOffset;
  1437. break;
  1438. default:
  1439. otherPedalStopX = otherPedalEndBBox.AbsolutePosition.x + otherPedalEndBBox.BorderLeft - otherPedalMarkingMarginXOffset;
  1440. break;
  1441. }
  1442. }
  1443. //Take into account in-staff clefs associated with the staff entry (they modify the bounding box position)
  1444. const vfOtherClefBefore: Vex.Flow.ClefNote = (vfOtherPedal.endVfVoiceEntry?.parentStaffEntry as VexFlowStaffEntry)?.vfClefBefore;
  1445. if (vfOtherClefBefore) {
  1446. const otherClefWidth: number = vfOtherClefBefore.getWidth() / 10;
  1447. otherPedalStopX += otherClefWidth;
  1448. }
  1449. if (otherPedalStartX > otherPedalStopX) {
  1450. // TODO this shouldn't happen, though this fixes the SkyBottomLineCalculator error for now (startIndex needs to be <= endIndex)
  1451. // switch startX and stopX
  1452. const otherStartX: number = otherPedalStartX;
  1453. otherPedalStartX = otherPedalStopX;
  1454. otherPedalStopX = otherStartX;
  1455. }
  1456. parentStaffline.SkyBottomLineCalculator.updateBottomLineInRange(otherPedalStartX, otherPedalStopX, footroom + otherPedalBracketHeight);
  1457. }
  1458. }
  1459. }
  1460. }
  1461. private calculateOctaveShiftSkyBottomLine(startStaffEntry: GraphicalStaffEntry, endStaffEntry: GraphicalStaffEntry,
  1462. vfOctaveShift: VexFlowOctaveShift, parentStaffline: StaffLine): void {
  1463. if (!endStaffEntry) {
  1464. log.warn("octaveshift: no endStaffEntry");
  1465. return;
  1466. }
  1467. let endBbox: BoundingBox = endStaffEntry.PositionAndShape;
  1468. if (vfOctaveShift.graphicalEndAtMeasureEnd) {
  1469. endBbox = endStaffEntry.parentMeasure.PositionAndShape;
  1470. }
  1471. let startXOffset: number = startStaffEntry.PositionAndShape.Size.width;
  1472. let endXOffset: number = endBbox.Size.width;
  1473. //Vexflow renders differently with rests
  1474. if (startStaffEntry.hasOnlyRests()) {
  1475. startXOffset = -startXOffset;
  1476. } else {
  1477. startXOffset /= 2;
  1478. }
  1479. if (!vfOctaveShift.graphicalEndAtMeasureEnd) {
  1480. if (!endStaffEntry.hasOnlyRests()) {
  1481. endXOffset /= 2;
  1482. } else {
  1483. endXOffset *= 2;
  1484. }
  1485. if (startStaffEntry === endStaffEntry) {
  1486. endXOffset *= 2;
  1487. }
  1488. }
  1489. let startX: number = startStaffEntry.PositionAndShape.AbsolutePosition.x - startXOffset;
  1490. let stopX: number = endBbox.AbsolutePosition.x + endXOffset;
  1491. if (startX > stopX) {
  1492. // very rare case of the start staffentry being before end staffentry. would lead to error in skybottomline. See #1281
  1493. // reverse startX and endX
  1494. const oldStartX: number = startX;
  1495. startX = stopX;
  1496. stopX = oldStartX;
  1497. }
  1498. vfOctaveShift.PositionAndShape.Size.width = stopX - startX;
  1499. const textBracket: VF.TextBracket = vfOctaveShift.getTextBracket();
  1500. const fontSize: number = (textBracket as any).font.size / 10;
  1501. if ((<any>textBracket).position === VF.TextBracket.Positions.TOP) {
  1502. const headroom: number = Math.ceil(parentStaffline.SkyBottomLineCalculator.getSkyLineMinInRange(startX, stopX));
  1503. if (headroom === Infinity) { // will cause Vexflow error
  1504. return;
  1505. }
  1506. (textBracket.start.getStave().options as any).top_text_position = Math.abs(headroom);
  1507. parentStaffline.SkyBottomLineCalculator.updateSkyLineInRange(startX, stopX, headroom - fontSize * 2);
  1508. } else {
  1509. const footroom: number = parentStaffline.SkyBottomLineCalculator.getBottomLineMaxInRange(startX, stopX);
  1510. if (footroom === Infinity) { // will cause Vexflow error
  1511. return;
  1512. }
  1513. (textBracket.start.getStave().options as any).bottom_text_position = footroom;
  1514. if (startStaffEntry.parentMeasure !== endStaffEntry.parentMeasure) {
  1515. (textBracket.stop.getStave().options as any).bottom_text_position = footroom;
  1516. }
  1517. //Vexflow positions top vs. bottom text in a slightly inconsistent way it seems
  1518. parentStaffline.SkyBottomLineCalculator.updateBottomLineInRange(startX, stopX, footroom + fontSize * 1.5);
  1519. }
  1520. }
  1521. /**
  1522. * Calculate all the textual and symbolic [[RepetitionInstruction]]s (e.g. dal segno) for a single [[SourceMeasure]].
  1523. * @param repetitionInstruction
  1524. * @param measureIndex
  1525. */
  1526. protected calculateWordRepetitionInstruction(repetitionInstruction: RepetitionInstruction, measureIndex: number): void {
  1527. // find first visible StaffLine
  1528. let uppermostMeasure: VexFlowMeasure = undefined;
  1529. const measures: VexFlowMeasure[] = <VexFlowMeasure[]>this.graphicalMusicSheet.MeasureList[measureIndex];
  1530. for (let idx: number = 0, len: number = measures.length; idx < len; ++idx) {
  1531. const graphicalMeasure: VexFlowMeasure = measures[idx];
  1532. if (graphicalMeasure && graphicalMeasure.ParentStaffLine && graphicalMeasure.ParentStaff.ParentInstrument.Visible) {
  1533. uppermostMeasure = <VexFlowMeasure>graphicalMeasure;
  1534. break;
  1535. }
  1536. }
  1537. // ToDo: feature/Repetitions
  1538. // now create corresponding graphical symbol or Text in VexFlow:
  1539. // use top measure and staffline for positioning.
  1540. if (uppermostMeasure) {
  1541. uppermostMeasure.addWordRepetition(repetitionInstruction);
  1542. }
  1543. }
  1544. protected calculateSkyBottomLines(): void {
  1545. const staffLines: StaffLine[] = CollectionUtil.flat(this.musicSystems.map(musicSystem => musicSystem.StaffLines));
  1546. //const numMeasures: number = staffLines.map(staffLine => staffLine.Measures.length).reduce((a, b) => a + b, 0);
  1547. let numMeasures: number = 0; // number of graphical measures that are rendered
  1548. for (const staffline of staffLines) {
  1549. for (const measure of staffline.Measures) {
  1550. if (measure) { // can be undefined and not rendered in multi-measure rest
  1551. numMeasures++;
  1552. }
  1553. }
  1554. }
  1555. if (this.rules.AlwaysSetPreferredSkyBottomLineBackendAutomatically) {
  1556. this.rules.setPreferredSkyBottomLineBackendAutomatically(numMeasures);
  1557. }
  1558. if (numMeasures >= this.rules.SkyBottomLineBatchMinMeasures) {
  1559. const calculator: SkyBottomLineBatchCalculator = new SkyBottomLineBatchCalculator(
  1560. staffLines, this.rules.PreferredSkyBottomLineBatchCalculatorBackend);
  1561. calculator.calculateLines();
  1562. } else {
  1563. for (const staffLine of staffLines) {
  1564. staffLine.SkyBottomLineCalculator.calculateLines();
  1565. }
  1566. }
  1567. }
  1568. /**
  1569. * Re-adjust the x positioning of expressions. Update the skyline afterwards
  1570. */
  1571. protected calculateExpressionAlignements(): void {
  1572. for (const musicSystem of this.musicSystems) {
  1573. for (const staffLine of musicSystem.StaffLines) {
  1574. try {
  1575. (<VexFlowStaffLine>staffLine).AlignmentManager.alignDynamicExpressions();
  1576. staffLine.AbstractExpressions.forEach(ae => {
  1577. ae.updateSkyBottomLine();
  1578. });
  1579. } catch (e) {
  1580. // TODO still necessary when calculation of expression fails, see calculateDynamicExpressionsForMultiExpression()
  1581. // see calculateGraphicalContinuousDynamic(), also in MusicSheetCalculator.
  1582. }
  1583. }
  1584. }
  1585. }
  1586. /**
  1587. * Check if the tied graphical note belongs to any beams or tuplets and react accordingly.
  1588. * @param tiedGraphicalNote
  1589. * @param beams
  1590. * @param activeClef
  1591. * @param octaveShiftValue
  1592. * @param graphicalStaffEntry
  1593. * @param duration
  1594. * @param openTie
  1595. * @param isLastTieNote
  1596. */
  1597. protected handleTiedGraphicalNote(tiedGraphicalNote: GraphicalNote, beams: Beam[], activeClef: ClefInstruction,
  1598. octaveShiftValue: OctaveEnum, graphicalStaffEntry: GraphicalStaffEntry, duration: Fraction,
  1599. openTie: Tie, isLastTieNote: boolean): void {
  1600. return;
  1601. }
  1602. /**
  1603. * Is called if a note is part of a beam.
  1604. * @param graphicalNote
  1605. * @param beam
  1606. * @param openBeams a list of all currently open beams
  1607. */
  1608. protected handleBeam(graphicalNote: GraphicalNote, beam: Beam, openBeams: Beam[]): void {
  1609. (graphicalNote.parentVoiceEntry.parentStaffEntry.parentMeasure as VexFlowMeasure).handleBeam(graphicalNote, beam);
  1610. }
  1611. protected handleVoiceEntryLyrics(voiceEntry: VoiceEntry, graphicalStaffEntry: GraphicalStaffEntry, lyricWords: LyricWord[]): void {
  1612. voiceEntry.LyricsEntries.forEach((key: string, lyricsEntry: LyricsEntry) => {
  1613. const graphicalLyricEntry: GraphicalLyricEntry = new GraphicalLyricEntry(lyricsEntry,
  1614. graphicalStaffEntry,
  1615. this.rules.LyricsHeight,
  1616. this.rules.StaffHeight);
  1617. graphicalStaffEntry.LyricsEntries.push(graphicalLyricEntry);
  1618. // create corresponding GraphicalLabel
  1619. const graphicalLabel: GraphicalLabel = graphicalLyricEntry.GraphicalLabel;
  1620. graphicalLabel.setLabelPositionAndShapeBorders();
  1621. if (lyricsEntry.Word) {
  1622. const lyricsEntryIndex: number = lyricsEntry.Word.Syllables.indexOf(lyricsEntry);
  1623. let index: number = lyricWords.indexOf(lyricsEntry.Word);
  1624. if (index === -1) {
  1625. lyricWords.push(lyricsEntry.Word);
  1626. index = lyricWords.indexOf(lyricsEntry.Word);
  1627. }
  1628. if (this.graphicalLyricWords.length === 0 || index > this.graphicalLyricWords.length - 1) {
  1629. const graphicalLyricWord: GraphicalLyricWord = new GraphicalLyricWord(lyricsEntry.Word);
  1630. graphicalLyricEntry.ParentLyricWord = graphicalLyricWord;
  1631. graphicalLyricWord.GraphicalLyricsEntries[lyricsEntryIndex] = graphicalLyricEntry;
  1632. this.graphicalLyricWords.push(graphicalLyricWord);
  1633. } else {
  1634. const graphicalLyricWord: GraphicalLyricWord = this.graphicalLyricWords[index];
  1635. graphicalLyricEntry.ParentLyricWord = graphicalLyricWord;
  1636. graphicalLyricWord.GraphicalLyricsEntries[lyricsEntryIndex] = graphicalLyricEntry;
  1637. if (graphicalLyricWord.isFilled()) {
  1638. lyricWords.splice(index, 1);
  1639. this.graphicalLyricWords.splice(this.graphicalLyricWords.indexOf(graphicalLyricWord), 1);
  1640. }
  1641. }
  1642. }
  1643. });
  1644. }
  1645. protected handleVoiceEntryOrnaments(ornamentContainer: OrnamentContainer, voiceEntry: VoiceEntry, graphicalStaffEntry: GraphicalStaffEntry): void {
  1646. return;
  1647. }
  1648. /**
  1649. * Add articulations to the given vexflow staff entry.
  1650. * @param articulations
  1651. * @param voiceEntry
  1652. * @param graphicalStaffEntry
  1653. */
  1654. protected handleVoiceEntryArticulations(articulations: Articulation[],
  1655. voiceEntry: VoiceEntry, staffEntry: GraphicalStaffEntry): void {
  1656. // uncomment this when implementing:
  1657. // let vfse: VexFlowStaffEntry = (graphicalStaffEntry as VexFlowStaffEntry);
  1658. return;
  1659. }
  1660. /**
  1661. * Add technical instructions to the given vexflow staff entry.
  1662. * @param technicalInstructions
  1663. * @param voiceEntry
  1664. * @param staffEntry
  1665. */
  1666. protected handleVoiceEntryTechnicalInstructions(technicalInstructions: TechnicalInstruction[],
  1667. voiceEntry: VoiceEntry, staffEntry: GraphicalStaffEntry): void {
  1668. // uncomment this when implementing:
  1669. // let vfse: VexFlowStaffEntry = (graphicalStaffEntry as VexFlowStaffEntry);
  1670. return;
  1671. }
  1672. /**
  1673. * Is called if a note is part of a tuplet.
  1674. * @param graphicalNote
  1675. * @param tuplet
  1676. * @param openTuplets a list of all currently open tuplets
  1677. */
  1678. protected handleTuplet(graphicalNote: GraphicalNote, tuplet: Tuplet, openTuplets: Tuplet[]): void {
  1679. (graphicalNote.parentVoiceEntry.parentStaffEntry.parentMeasure as VexFlowMeasure).handleTuplet(graphicalNote, tuplet);
  1680. }
  1681. /**
  1682. * Find the Index of the item of the array of all VexFlow Slurs that holds a specified slur
  1683. * @param gSlurs
  1684. * @param slur
  1685. */
  1686. public findIndexGraphicalSlurFromSlur(gSlurs: GraphicalSlur[], slur: Slur): number {
  1687. for (let slurIndex: number = 0; slurIndex < gSlurs.length; slurIndex++) {
  1688. if (gSlurs[slurIndex].slur === slur) {
  1689. return slurIndex;
  1690. }
  1691. }
  1692. return -1;
  1693. }
  1694. public indexOfGraphicalGlissFromGliss(gGlissandi: GraphicalGlissando[], glissando: Glissando): number {
  1695. for (let glissIndex: number = 0; glissIndex < gGlissandi.length; glissIndex++) {
  1696. if (gGlissandi[glissIndex].Glissando === glissando) {
  1697. return glissIndex;
  1698. }
  1699. }
  1700. return -1;
  1701. }
  1702. /* VexFlow Version - for later use
  1703. public findIndexVFSlurFromSlur(vfSlurs: VexFlowSlur[], slur: Slur): number {
  1704. for (let slurIndex: number = 0; slurIndex < vfSlurs.length; slurIndex++) {
  1705. if (vfSlurs[slurIndex].vfSlur === slur) {
  1706. return slurIndex;
  1707. }
  1708. }
  1709. }
  1710. */
  1711. // Generate all Graphical Slurs and attach them to the staffline
  1712. protected calculateSlurs(): void {
  1713. const openSlursDict: { [staffId: number]: GraphicalSlur[] } = {};
  1714. for (const graphicalMeasure of this.graphicalMusicSheet.MeasureList[0]) { //let i: number = 0; i < this.graphicalMusicSheet.MeasureList[0].length; i++) {
  1715. openSlursDict[graphicalMeasure.ParentStaff.idInMusicSheet] = [];
  1716. }
  1717. /* VexFlow Version - for later use
  1718. // Generate an empty dictonary to index an array of VexFlowSlur classes
  1719. const vfOpenSlursDict: { [staffId: number]: VexFlowSlur[]; } = {}; //VexFlowSlur[]; } = {};
  1720. // use first SourceMeasure to get all graphical measures to know how many staves are currently visible in this musicsheet
  1721. // foreach stave: create an empty array. It can later hold open slurs.
  1722. // Measure how many staves are visible and reserve space for them.
  1723. for (const graphicalMeasure of this.graphicalMusicSheet.MeasureList[0]) { //let i: number = 0; i < this.graphicalMusicSheet.MeasureList[0].length; i++) {
  1724. vfOpenSlursDict[graphicalMeasure.ParentStaff.idInMusicSheet] = [];
  1725. }
  1726. */
  1727. for (const musicSystem of this.musicSystems) {
  1728. for (const staffLine of musicSystem.StaffLines) {
  1729. // if a graphical slur reaches out of the last musicsystem, we have to create another graphical slur reaching into this musicsystem
  1730. // (one slur needs 2 graphical slurs)
  1731. const openGraphicalSlurs: GraphicalSlur[] = openSlursDict[staffLine.ParentStaff.idInMusicSheet];
  1732. for (let slurIndex: number = 0; slurIndex < openGraphicalSlurs.length; slurIndex++) {
  1733. const oldGSlur: GraphicalSlur = openGraphicalSlurs[slurIndex];
  1734. const newGSlur: GraphicalSlur = new GraphicalSlur(oldGSlur.slur, this.rules); //Graphicalslur.createFromSlur(oldSlur);
  1735. staffLine.addSlurToStaffline(newGSlur); // every VFSlur is added to the array in the VFStaffline!
  1736. openGraphicalSlurs[slurIndex] = newGSlur;
  1737. }
  1738. /* VexFlow Version - for later use
  1739. const vfOpenSlurs: VexFlowSlur[] = vfOpenSlursDict[staffLine.ParentStaff.idInMusicSheet];
  1740. const vfStaffLine: VexFlowStaffLine = <VexFlowStaffLine> staffLine;
  1741. for (let slurIndex: number = 0; slurIndex < vfOpenSlurs.length; slurIndex++) {
  1742. const oldVFSlur: VexFlowSlur = vfOpenSlurs[slurIndex];
  1743. const newVFSlur: VexFlowSlur = VexFlowSlur.createFromVexflowSlur(oldVFSlur);
  1744. newVFSlur.vfStartNote = undefined;
  1745. vfStaffLine.addVFSlurToVFStaffline(newVFSlur); // every VFSlur is added to the array in the VFStaffline!
  1746. vfOpenSlurs[slurIndex] = newVFSlur;
  1747. }
  1748. */
  1749. // add reference of slur array to the VexFlowStaffline class
  1750. for (const graphicalMeasure of staffLine.Measures) {
  1751. for (const graphicalStaffEntry of graphicalMeasure.staffEntries) {
  1752. // loop over "normal" notes (= no gracenotes)
  1753. for (const graphicalVoiceEntry of graphicalStaffEntry.graphicalVoiceEntries) {
  1754. for (const graphicalNote of graphicalVoiceEntry.notes) {
  1755. for (const slur of graphicalNote.sourceNote.NoteSlurs) {
  1756. // extra check for some MusicSheets that have openSlurs (because only the first Page is available -> Recordare files)
  1757. if (!slur.EndNote || !slur.StartNote) {
  1758. continue;
  1759. }
  1760. // add new VexFlowSlur to List
  1761. if (slur.StartNote === graphicalNote.sourceNote) {
  1762. // TODO the following seems to have been intended to prevent unnecessary slurs that overlap ties,
  1763. // but it simply leads to correct slurs being left out where the tie end note is the slur start note.
  1764. // visual regression tests simply show valid slurs being left out in 4 samples.
  1765. // if (graphicalNote.sourceNote.NoteTie) {
  1766. // if (graphicalNote.parentVoiceEntry.parentStaffEntry.getAbsoluteTimestamp() !==
  1767. // graphicalNote.sourceNote.NoteTie.StartNote.getAbsoluteTimestamp()) {
  1768. // break;
  1769. // }
  1770. // }
  1771. // Add a Graphical Slur to the staffline, if the recent note is the Startnote of a slur
  1772. const gSlur: GraphicalSlur = new GraphicalSlur(slur, this.rules);
  1773. openGraphicalSlurs.push(gSlur);
  1774. staffLine.addSlurToStaffline(gSlur);
  1775. /* VexFlow Version - for later use
  1776. const vfSlur: VexFlowSlur = new VexFlowSlur(slur);
  1777. vfOpenSlurs.push(vfSlur); //add open... adding / removing is JUST DONE in the open... array
  1778. vfSlur.vfStartNote = (graphicalVoiceEntry as VexFlowVoiceEntry).vfStaveNote;
  1779. vfStaffLine.addVFSlurToVFStaffline(vfSlur); // every VFSlur is added to the array in the VFStaffline!
  1780. */
  1781. }
  1782. if (slur.EndNote === graphicalNote.sourceNote) {
  1783. // Remove the Graphical Slur from the staffline if the note is the Endnote of a slur
  1784. const index: number = this.findIndexGraphicalSlurFromSlur(openGraphicalSlurs, slur);
  1785. if (index >= 0) {
  1786. // save Voice Entry in VFSlur and then remove it from array of open VFSlurs
  1787. const gSlur: GraphicalSlur = openGraphicalSlurs[index];
  1788. if (gSlur.staffEntries.indexOf(graphicalStaffEntry) === -1) {
  1789. gSlur.staffEntries.push(graphicalStaffEntry);
  1790. }
  1791. openGraphicalSlurs.splice(index, 1);
  1792. }
  1793. /* VexFlow Version - for later use
  1794. const vfIndex: number = this.findIndexVFSlurFromSlur(vfOpenSlurs, slur);
  1795. if (vfIndex !== undefined) {
  1796. // save Voice Entry in VFSlur and then remove it from array of open VFSlurs
  1797. const vfSlur: VexFlowSlur = vfOpenSlurs[vfIndex];
  1798. vfSlur.vfEndNote = (graphicalVoiceEntry as VexFlowVoiceEntry).vfStaveNote;
  1799. vfSlur.createVexFlowCurve();
  1800. vfOpenSlurs.splice(vfIndex, 1);
  1801. }
  1802. */
  1803. }
  1804. }
  1805. }
  1806. }
  1807. //add the present Staffentry to all open slurs that don't contain this Staffentry already
  1808. for (const gSlur of openGraphicalSlurs) {
  1809. if (gSlur.staffEntries.indexOf(graphicalStaffEntry) === -1) {
  1810. gSlur.staffEntries.push(graphicalStaffEntry);
  1811. }
  1812. }
  1813. } // loop over StaffEntries
  1814. } // loop over Measures
  1815. } // loop over StaffLines
  1816. // Attach vfSlur array to the vfStaffline to be drawn
  1817. //vfStaffLine.SlursInVFStaffLine = vfSlurs;
  1818. } // loop over MusicSystems
  1819. // order slurs that were saved to the Staffline
  1820. for (const musicSystem of this.musicSystems) {
  1821. for (const staffLine of musicSystem.StaffLines) {
  1822. // Sort all gSlurs in the staffline using the Compare function in class GraphicalSlurSorter
  1823. const sortedGSlurs: GraphicalSlur[] = staffLine.GraphicalSlurs.sort(GraphicalSlur.Compare);
  1824. for (const gSlur of sortedGSlurs) {
  1825. // crossed slurs will be handled later:
  1826. if (gSlur.slur.isCrossed()) {
  1827. continue;
  1828. }
  1829. gSlur.calculateCurve(this.rules);
  1830. }
  1831. }
  1832. }
  1833. }
  1834. public calculateGlissandi(): void {
  1835. const openGlissDict: { [staffId: number]: GraphicalGlissando[] } = {};
  1836. for (const graphicalMeasure of this.graphicalMusicSheet.MeasureList[0]) { //let i: number = 0; i < this.graphicalMusicSheet.MeasureList[0].length; i++) {
  1837. openGlissDict[graphicalMeasure.ParentStaff.idInMusicSheet] = [];
  1838. }
  1839. for (const musicSystem of this.musicSystems) {
  1840. for (const staffLine of musicSystem.StaffLines) {
  1841. // if a glissando reaches out of the last musicsystem, we have to create another glissando reaching into this musicsystem
  1842. // (one gliss needs 2 graphical gliss)
  1843. // const isTab: boolean = staffLine.ParentStaff.isTab;
  1844. const openGlissandi: GraphicalGlissando[] = openGlissDict[staffLine.ParentStaff.idInMusicSheet];
  1845. for (let glissIndex: number = 0; glissIndex < openGlissandi.length; glissIndex++) {
  1846. const oldGliss: GraphicalGlissando = openGlissandi[glissIndex];
  1847. const newGliss: GraphicalGlissando = new VexFlowGlissando(oldGliss.Glissando);
  1848. staffLine.addGlissandoToStaffline(newGliss);
  1849. openGlissandi[glissIndex] = newGliss;
  1850. }
  1851. // add reference of gliss array to the VexFlowStaffline class
  1852. for (const graphicalMeasure of staffLine.Measures) {
  1853. for (const graphicalStaffEntry of graphicalMeasure.staffEntries) {
  1854. // loop over "normal" notes (= no gracenotes)
  1855. for (const graphicalVoiceEntry of graphicalStaffEntry.graphicalVoiceEntries) {
  1856. for (const graphicalNote of graphicalVoiceEntry.notes) {
  1857. const gliss: Glissando = graphicalNote.sourceNote.NoteGlissando;
  1858. // extra check for some MusicSheets that have openSlurs (because only the first Page is available -> Recordare files)
  1859. if (!gliss?.EndNote || !gliss?.StartNote) {
  1860. continue;
  1861. }
  1862. // add new VexFlowGlissando to List
  1863. if (gliss.StartNote === graphicalNote.sourceNote) {
  1864. // Add a Graphical Glissando to the staffline, if the recent note is the Startnote of a slur
  1865. const gGliss: GraphicalGlissando = new VexFlowGlissando(gliss);
  1866. openGlissandi.push(gGliss);
  1867. //gGliss.staffEntries.push(graphicalStaffEntry);
  1868. staffLine.addGlissandoToStaffline(gGliss);
  1869. }
  1870. if (gliss.EndNote === graphicalNote.sourceNote) {
  1871. // Remove the gliss from the staffline if the note is the Endnote of a gliss
  1872. const index: number = this.indexOfGraphicalGlissFromGliss(openGlissandi, gliss);
  1873. if (index >= 0) {
  1874. // save Voice Entry in gliss and then remove it from array of open glissandi
  1875. const gGliss: GraphicalGlissando = openGlissandi[index];
  1876. if (gGliss.staffEntries.indexOf(graphicalStaffEntry) === -1) {
  1877. gGliss.staffEntries.push(graphicalStaffEntry);
  1878. }
  1879. openGlissandi.splice(index, 1);
  1880. }
  1881. }
  1882. }
  1883. }
  1884. // probably unnecessary, as a gliss only has 2 staffentries
  1885. //add the present Staffentry to all open slurs that don't contain this Staffentry already
  1886. for (const gGliss of openGlissandi) {
  1887. if (gGliss.staffEntries.indexOf(graphicalStaffEntry) === -1) {
  1888. gGliss.staffEntries.push(graphicalStaffEntry);
  1889. }
  1890. }
  1891. } // loop over StaffEntries
  1892. } // loop over Measures
  1893. } // loop over StaffLines
  1894. } // loop over MusicSystems
  1895. for (const musicSystem of this.musicSystems) {
  1896. for (const staffLine of musicSystem.StaffLines) {
  1897. // order glissandi that were saved to the Staffline
  1898. // TODO? Sort all gSlurs in the staffline using the Compare function in class GraphicalSlurSorter
  1899. //const sortedGSlurs: GraphicalSlur[] = staffLine.GraphicalSlurs.sort(GraphicalSlur.Compare);
  1900. for (const gGliss of staffLine.GraphicalGlissandi) {
  1901. const isTab: boolean = staffLine.ParentStaff.isTab;
  1902. if (isTab) {
  1903. const startNote: TabNote = <TabNote> gGliss.Glissando.StartNote;
  1904. const endNote: TabNote = <TabNote> gGliss.Glissando.EndNote;
  1905. const vfStartNote: VexFlowGraphicalNote = gGliss.staffEntries[0].findGraphicalNoteFromNote(startNote) as VexFlowGraphicalNote;
  1906. const vfEndNote: VexFlowGraphicalNote = gGliss.staffEntries.last().findGraphicalNoteFromNote(endNote) as VexFlowGraphicalNote;
  1907. if (!vfStartNote && !vfEndNote) {
  1908. return; // otherwise causes Vexflow error
  1909. }
  1910. let slideDirection: number = 1;
  1911. if (startNote.FretNumber > endNote.FretNumber) {
  1912. slideDirection = -1;
  1913. }
  1914. let first_indices: number[] = undefined;
  1915. let last_indices: number[] = undefined;
  1916. let startStemmableNote: VF.StemmableNote = undefined;
  1917. // let startNoteIndexInTie: number = 0;
  1918. if (vfStartNote && vfStartNote.vfnote && vfStartNote.vfnote.length >= 2) {
  1919. startStemmableNote = vfStartNote.vfnote[0]; // otherwise needs to be undefined in TabSlide constructor!
  1920. first_indices = [0];
  1921. // startNoteIndexInTie = vfStartNote.vfnote[1];
  1922. }
  1923. let endStemmableNote: VF.StemmableNote = undefined;
  1924. // let endNoteIndexInTie: number = 0;
  1925. if (vfEndNote && vfEndNote.vfnote && vfEndNote.vfnote.length >= 2) {
  1926. endStemmableNote = vfEndNote.vfnote[0];
  1927. last_indices = [0];
  1928. // endNoteIndexInTie = vfEndNote.vfnote[1];
  1929. }
  1930. const vfTie: VF.TabSlide = new VF.TabSlide(
  1931. {
  1932. first_indices: first_indices,
  1933. first_note: startStemmableNote,
  1934. last_indices: last_indices,
  1935. last_note: endStemmableNote,
  1936. },
  1937. slideDirection
  1938. );
  1939. const startMeasure: VexFlowMeasure = (vfStartNote?.parentVoiceEntry.parentStaffEntry.parentMeasure as VexFlowMeasure);
  1940. if (startMeasure) {
  1941. startMeasure.vfTies.push(vfTie);
  1942. (gGliss as VexFlowGlissando).vfTie = vfTie;
  1943. }
  1944. const endMeasure: VexFlowMeasure = (vfEndNote?.parentVoiceEntry.parentStaffEntry.parentMeasure as VexFlowMeasure);
  1945. if (endMeasure) {
  1946. endMeasure.vfTies.push(vfTie);
  1947. (gGliss as VexFlowGlissando).vfTie = vfTie;
  1948. }
  1949. } else {
  1950. //gGliss.calculateLine(this.rules);
  1951. }
  1952. }
  1953. }
  1954. }
  1955. }
  1956. }