MusicSheetCalculator.ts 168 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995
  1. import { GraphicalStaffEntry } from "./GraphicalStaffEntry";
  2. import { StaffLine } from "./StaffLine";
  3. import { GraphicalMusicSheet } from "./GraphicalMusicSheet";
  4. import { EngravingRules } from "./EngravingRules";
  5. import { Tie } from "../VoiceData/Tie";
  6. import { Fraction } from "../../Common/DataObjects/Fraction";
  7. import { Note } from "../VoiceData/Note";
  8. import { MusicSheet } from "../MusicSheet";
  9. import { GraphicalMeasure } from "./GraphicalMeasure";
  10. import {ClefInstruction, ClefEnum} from "../VoiceData/Instructions/ClefInstruction";
  11. import { LyricWord } from "../VoiceData/Lyrics/LyricsWord";
  12. import { SourceMeasure } from "../VoiceData/SourceMeasure";
  13. import { GraphicalMusicPage } from "./GraphicalMusicPage";
  14. import { GraphicalNote } from "./GraphicalNote";
  15. import { Beam } from "../VoiceData/Beam";
  16. import { OctaveEnum } from "../VoiceData/Expressions/ContinuousExpressions/OctaveShift";
  17. import { VoiceEntry, StemDirectionType } from "../VoiceData/VoiceEntry";
  18. import { OrnamentContainer } from "../VoiceData/OrnamentContainer";
  19. import { ArticulationEnum } from "../VoiceData/VoiceEntry";
  20. import { Tuplet } from "../VoiceData/Tuplet";
  21. import { MusicSystem } from "./MusicSystem";
  22. import { GraphicalTie } from "./GraphicalTie";
  23. import { RepetitionInstruction } from "../VoiceData/Instructions/RepetitionInstruction";
  24. import { MultiExpression, MultiExpressionEntry } from "../VoiceData/Expressions/MultiExpression";
  25. import { StaffEntryLink } from "../VoiceData/StaffEntryLink";
  26. import { MusicSystemBuilder } from "./MusicSystemBuilder";
  27. import { MultiTempoExpression } from "../VoiceData/Expressions/MultiTempoExpression";
  28. import { Repetition } from "../MusicSource/Repetition";
  29. import { PointF2D } from "../../Common/DataObjects/PointF2D";
  30. import { SourceStaffEntry } from "../VoiceData/SourceStaffEntry";
  31. import { BoundingBox } from "./BoundingBox";
  32. import { Instrument } from "../Instrument";
  33. import { GraphicalLabel } from "./GraphicalLabel";
  34. import { TextAlignmentEnum } from "../../Common/Enums/TextAlignment";
  35. import { VerticalGraphicalStaffEntryContainer } from "./VerticalGraphicalStaffEntryContainer";
  36. import { KeyInstruction } from "../VoiceData/Instructions/KeyInstruction";
  37. import { AbstractNotationInstruction } from "../VoiceData/Instructions/AbstractNotationInstruction";
  38. import { TechnicalInstruction } from "../VoiceData/Instructions/TechnicalInstruction";
  39. import { Pitch } from "../../Common/DataObjects/Pitch";
  40. import { LinkedVoice } from "../VoiceData/LinkedVoice";
  41. import { ColDirEnum } from "./BoundingBox";
  42. import { IGraphicalSymbolFactory } from "../Interfaces/IGraphicalSymbolFactory";
  43. import { ITextMeasurer } from "../Interfaces/ITextMeasurer";
  44. import { ITransposeCalculator } from "../Interfaces/ITransposeCalculator";
  45. import { OctaveShiftParams } from "./OctaveShiftParams";
  46. import { AccidentalCalculator } from "./AccidentalCalculator";
  47. import { MidiInstrument } from "../VoiceData/Instructions/ClefInstruction";
  48. import { Staff } from "../VoiceData/Staff";
  49. import { OctaveShift } from "../VoiceData/Expressions/ContinuousExpressions/OctaveShift";
  50. import log from "loglevel";
  51. import { Dictionary } from "typescript-collections";
  52. import { GraphicalLyricEntry } from "./GraphicalLyricEntry";
  53. import { GraphicalLyricWord } from "./GraphicalLyricWord";
  54. import { GraphicalLine } from "./GraphicalLine";
  55. import { Label } from "../Label";
  56. import { GraphicalVoiceEntry } from "./GraphicalVoiceEntry";
  57. import { VerticalSourceStaffEntryContainer } from "../VoiceData/VerticalSourceStaffEntryContainer";
  58. import { SkyBottomLineCalculator } from "./SkyBottomLineCalculator";
  59. import { PlacementEnum } from "../VoiceData/Expressions/AbstractExpression";
  60. import { AbstractGraphicalInstruction } from "./AbstractGraphicalInstruction";
  61. import { GraphicalInstantaneousTempoExpression } from "./GraphicalInstantaneousTempoExpression";
  62. import { InstantaneousTempoExpression, TempoEnum } from "../VoiceData/Expressions/InstantaneousTempoExpression";
  63. import { ContinuousTempoExpression } from "../VoiceData/Expressions/ContinuousExpressions/ContinuousTempoExpression";
  64. import { FontStyles } from "../../Common/Enums/FontStyles";
  65. import { AbstractTempoExpression } from "../VoiceData/Expressions/AbstractTempoExpression";
  66. import { GraphicalInstantaneousDynamicExpression } from "./GraphicalInstantaneousDynamicExpression";
  67. import { ContDynamicEnum } from "../VoiceData/Expressions/ContinuousExpressions/ContinuousDynamicExpression";
  68. import { GraphicalContinuousDynamicExpression } from "./GraphicalContinuousDynamicExpression";
  69. import { FillEmptyMeasuresWithWholeRests } from "../../OpenSheetMusicDisplay/OSMDOptions";
  70. import { IStafflineNoteCalculator } from "../Interfaces/IStafflineNoteCalculator";
  71. import { GraphicalUnknownExpression } from "./GraphicalUnknownExpression";
  72. /**
  73. * Class used to do all the calculations in a MusicSheet, which in the end populates a GraphicalMusicSheet.
  74. */
  75. export abstract class MusicSheetCalculator {
  76. public static symbolFactory: IGraphicalSymbolFactory;
  77. public static transposeCalculator: ITransposeCalculator;
  78. public static stafflineNoteCalculator: IStafflineNoteCalculator;
  79. protected static textMeasurer: ITextMeasurer;
  80. protected staffEntriesWithGraphicalTies: GraphicalStaffEntry[] = [];
  81. protected staffEntriesWithOrnaments: GraphicalStaffEntry[] = [];
  82. protected staffEntriesWithChordSymbols: GraphicalStaffEntry[] = [];
  83. protected staffLinesWithLyricWords: StaffLine[] = [];
  84. protected graphicalLyricWords: GraphicalLyricWord[] = [];
  85. protected graphicalMusicSheet: GraphicalMusicSheet;
  86. protected rules: EngravingRules;
  87. protected musicSystems: MusicSystem[];
  88. public static get TextMeasurer(): ITextMeasurer {
  89. return MusicSheetCalculator.textMeasurer;
  90. }
  91. public static set TextMeasurer(value: ITextMeasurer) {
  92. MusicSheetCalculator.textMeasurer = value;
  93. }
  94. protected get leadSheet(): boolean {
  95. return this.graphicalMusicSheet.LeadSheet;
  96. }
  97. protected static setMeasuresMinStaffEntriesWidth(measures: GraphicalMeasure[], minimumStaffEntriesWidth: number): void {
  98. for (let idx: number = 0, len: number = measures.length; idx < len; ++idx) {
  99. const measure: GraphicalMeasure = measures[idx];
  100. if (measure) {
  101. measure.minimumStaffEntriesWidth = minimumStaffEntriesWidth;
  102. }
  103. }
  104. }
  105. public initialize(graphicalMusicSheet: GraphicalMusicSheet): void {
  106. this.graphicalMusicSheet = graphicalMusicSheet;
  107. this.rules = graphicalMusicSheet.ParentMusicSheet.Rules;
  108. this.prepareGraphicalMusicSheet();
  109. //this.calculate();
  110. }
  111. /**
  112. * Build the 2D [[GraphicalMeasure]] list needed for the [[MusicSheetCalculator]].
  113. * Internally it creates [[GraphicalMeasure]]s, [[GraphicalStaffEntry]]'s and [[GraphicalNote]]s.
  114. */
  115. public prepareGraphicalMusicSheet(): void {
  116. // Clear the stored system images dict - all systems have to be redrawn.
  117. // Not necessary now. TODO Check
  118. // this.graphicalMusicSheet.SystemImages.length = 0;
  119. const musicSheet: MusicSheet = this.graphicalMusicSheet.ParentMusicSheet;
  120. this.staffEntriesWithGraphicalTies = [];
  121. this.staffEntriesWithOrnaments = [];
  122. this.staffEntriesWithChordSymbols = [];
  123. this.staffLinesWithLyricWords = [];
  124. // this.staffLinesWithGraphicalExpressions = [];
  125. this.graphicalMusicSheet.Initialize();
  126. const measureList: GraphicalMeasure[][] = this.graphicalMusicSheet.MeasureList;
  127. // one AccidentalCalculator for each Staff (regardless of Instrument)
  128. const accidentalCalculators: AccidentalCalculator[] = this.createAccidentalCalculators();
  129. // List of Active ClefInstructions
  130. const activeClefs: ClefInstruction[] = this.graphicalMusicSheet.initializeActiveClefs();
  131. // LyricWord - GraphicalLyricWord Lists
  132. const lyricWords: LyricWord[] = [];
  133. const completeNumberOfStaves: number = musicSheet.getCompleteNumberOfStaves();
  134. // Octave Shifts List
  135. const openOctaveShifts: OctaveShiftParams[] = [];
  136. // TieList - timestampsArray
  137. for (let i: number = 0; i < completeNumberOfStaves; i++) {
  138. openOctaveShifts.push(undefined);
  139. }
  140. // go through all SourceMeasures (taking into account normal SourceMusicParts and Repetitions)
  141. for (let idx: number = 0, len: number = musicSheet.SourceMeasures.length; idx < len; ++idx) {
  142. const sourceMeasure: SourceMeasure = musicSheet.SourceMeasures[idx];
  143. const graphicalMeasures: GraphicalMeasure[] = this.createGraphicalMeasuresForSourceMeasure(
  144. sourceMeasure,
  145. accidentalCalculators,
  146. lyricWords,
  147. openOctaveShifts,
  148. activeClefs
  149. );
  150. measureList.push(graphicalMeasures);
  151. if (sourceMeasure.multipleRestMeasures > 0 && this.rules.RenderMultipleRestMeasures) {
  152. // multiRest given in XML, skip the next measures included
  153. sourceMeasure.isReducedToMultiRest = true;
  154. sourceMeasure.multipleRestMeasureNumber = 1;
  155. const measuresToSkip: number = sourceMeasure.multipleRestMeasures - 1;
  156. // console.log(`skipping ${measuresToSkip} measures for measure #${sourceMeasure.MeasureNumber}.`);
  157. idx += measuresToSkip;
  158. for (let idx2: number = 1; idx2 <= measuresToSkip; idx2++) {
  159. const nextSourceMeasure: SourceMeasure = musicSheet.SourceMeasures[sourceMeasure.MeasureNumber - 1 + idx2];
  160. // TODO handle the case that a measure after the first multiple rest measure can't be reduced
  161. nextSourceMeasure.multipleRestMeasureNumber = idx2 + 1;
  162. nextSourceMeasure.isReducedToMultiRest = true;
  163. measureList.push([undefined]);
  164. // TODO we could push an object here or push nothing entirely,
  165. // but then the index doesn't correspond to measure numbers anymore.
  166. }
  167. }
  168. }
  169. if (this.rules.AutoGenerateMutipleRestMeasuresFromRestMeasures && this.rules.RenderMultipleRestMeasures) {
  170. //track number of multirests
  171. let beginMultiRestMeasure: SourceMeasure = undefined;
  172. let multiRestCount: number = 0;
  173. //go through all source measures again. Need to calc auto-multi-rests
  174. for (let idx: number = 0, len: number = musicSheet.SourceMeasures.length; idx < len; ++idx) {
  175. const sourceMeasure: SourceMeasure = musicSheet.SourceMeasures[idx];
  176. if (!sourceMeasure.isReducedToMultiRest && sourceMeasure.canBeReducedToMultiRest()) {
  177. //we've already been initialized, we are in the midst of a multirest sequence
  178. if (multiRestCount > 0) {
  179. beginMultiRestMeasure.isReducedToMultiRest = true;
  180. beginMultiRestMeasure.multipleRestMeasureNumber = 1;
  181. multiRestCount++;
  182. sourceMeasure.multipleRestMeasureNumber = multiRestCount;
  183. sourceMeasure.isReducedToMultiRest = true;
  184. //clear out these measures. We know now that we are in multirest mode
  185. for (let idx2: number = 0; idx2 < measureList[idx].length; idx2++) {
  186. measureList[idx][idx2] = undefined;
  187. }
  188. } else { //else this is the (potential) beginning
  189. beginMultiRestMeasure = sourceMeasure;
  190. multiRestCount = 1;
  191. }
  192. } else { //not multirest measure
  193. if (multiRestCount > 1) { //Actual multirest sequence just happened. Process
  194. beginMultiRestMeasure.multipleRestMeasures = multiRestCount;
  195. //regen graphical measures for this source measure
  196. const graphicalMeasures: GraphicalMeasure[] = this.createGraphicalMeasuresForSourceMeasure(
  197. beginMultiRestMeasure,
  198. accidentalCalculators,
  199. lyricWords,
  200. openOctaveShifts,
  201. activeClefs
  202. );
  203. measureList[beginMultiRestMeasure.measureListIndex] = graphicalMeasures;
  204. multiRestCount = 0;
  205. beginMultiRestMeasure = undefined;
  206. } else { //had a potential multirest sequence, but didn't pan out. only one measure was rests
  207. multiRestCount = 0;
  208. beginMultiRestMeasure = undefined;
  209. }
  210. }
  211. }
  212. //If we reached the end of the sheet and have pending multirest measure, process
  213. if (multiRestCount > 1) {
  214. beginMultiRestMeasure.multipleRestMeasures = multiRestCount;
  215. beginMultiRestMeasure.isReducedToMultiRest = true;
  216. //regen graphical measures for this source measure
  217. const graphicalMeasures: GraphicalMeasure[] = this.createGraphicalMeasuresForSourceMeasure(
  218. beginMultiRestMeasure,
  219. accidentalCalculators,
  220. lyricWords,
  221. openOctaveShifts,
  222. activeClefs
  223. );
  224. measureList[beginMultiRestMeasure.measureListIndex] = graphicalMeasures;
  225. multiRestCount = 0;
  226. beginMultiRestMeasure = undefined;
  227. }
  228. }
  229. const staffIsPercussionArray: Array<boolean> =
  230. activeClefs.map(clef => (clef.ClefType === ClefEnum.percussion));
  231. this.handleStaffEntries(staffIsPercussionArray);
  232. this.calculateVerticalContainersList();
  233. this.setIndicesToVerticalGraphicalContainers();
  234. }
  235. /**
  236. * The main method for the Calculator.
  237. */
  238. public calculate(): void {
  239. this.musicSystems = [];
  240. this.clearSystemsAndMeasures();
  241. // delete graphicalObjects (currently: ties) that will be recalculated, newly create GraphicalObjects streching over a single StaffEntry
  242. this.clearRecreatedObjects();
  243. this.createGraphicalTies();
  244. // calculate SheetLabelBoundingBoxes
  245. this.calculateSheetLabelBoundingBoxes();
  246. this.calculateXLayout(this.graphicalMusicSheet, this.maxInstrNameLabelLength());
  247. // create List<MusicPage>
  248. this.graphicalMusicSheet.MusicPages.length = 0;
  249. // create new MusicSystems and StaffLines (as many as necessary) and populate them with Measures from measureList
  250. this.calculateMusicSystems();
  251. // Add some white space at the end of the piece:
  252. //this.graphicalMusicSheet.MusicPages[0].PositionAndShape.BorderMarginBottom += 9;
  253. // transform Relative to Absolute Positions
  254. GraphicalMusicSheet.transformRelativeToAbsolutePosition(this.graphicalMusicSheet);
  255. }
  256. public calculateXLayout(graphicalMusicSheet: GraphicalMusicSheet, maxInstrNameLabelLength: number): void {
  257. // for each inner List in big Measure List calculate new Positions for the StaffEntries
  258. // and adjust Measures sizes
  259. // calculate max measure length for maximum zoom in.
  260. // let minLength: number = 0; // currently unused
  261. // const maxInstructionsLength: number = this.rules.MaxInstructionsConstValue;
  262. if (this.graphicalMusicSheet.MeasureList.length > 0) {
  263. /** list of vertically ordered measures belonging to one bar */
  264. let measures: GraphicalMeasure[] = this.graphicalMusicSheet.MeasureList[0];
  265. let minimumStaffEntriesWidth: number = this.calculateMeasureXLayout(measures);
  266. minimumStaffEntriesWidth = this.calculateMeasureWidthFromLyrics(measures, minimumStaffEntriesWidth);
  267. MusicSheetCalculator.setMeasuresMinStaffEntriesWidth(measures, minimumStaffEntriesWidth);
  268. // minLength = minimumStaffEntriesWidth * 1.2 + maxInstrNameLabelLength + maxInstructionsLength;
  269. for (let i: number = 1; i < this.graphicalMusicSheet.MeasureList.length; i++) {
  270. measures = this.graphicalMusicSheet.MeasureList[i];
  271. minimumStaffEntriesWidth = this.calculateMeasureXLayout(measures);
  272. minimumStaffEntriesWidth = this.calculateMeasureWidthFromLyrics(measures, minimumStaffEntriesWidth);
  273. MusicSheetCalculator.setMeasuresMinStaffEntriesWidth(measures, minimumStaffEntriesWidth);
  274. // minLength = Math.max(minLength, minimumStaffEntriesWidth * 1.2 + maxInstructionsLength);
  275. }
  276. }
  277. // this.graphicalMusicSheet.MinAllowedSystemWidth = minLength; // currently unused
  278. }
  279. public calculateMeasureWidthFromLyrics(measuresVertical: GraphicalMeasure[], oldMinimumStaffEntriesWidth: number): number {
  280. throw new Error("abstract, not implemented");
  281. }
  282. protected formatMeasures(): void {
  283. throw new Error("abstract, not implemented");
  284. }
  285. /**
  286. * Calculates the x layout of the staff entries within the staff measures belonging to one source measure.
  287. * All staff entries are x-aligned throughout all the measures.
  288. * @param measures - The minimum required x width of the source measure
  289. */
  290. protected calculateMeasureXLayout(measures: GraphicalMeasure[]): number {
  291. throw new Error("abstract, not implemented");
  292. }
  293. /**
  294. * Called for every source measure when generating the list of staff measures for it.
  295. */
  296. protected initGraphicalMeasuresCreation(): void {
  297. throw new Error("abstract, not implemented");
  298. }
  299. protected handleBeam(graphicalNote: GraphicalNote, beam: Beam, openBeams: Beam[]): void {
  300. throw new Error("abstract, not implemented");
  301. }
  302. /**
  303. * Check if the tied graphical note belongs to any beams or tuplets and react accordingly.
  304. * @param tiedGraphicalNote
  305. * @param beams
  306. * @param activeClef
  307. * @param octaveShiftValue
  308. * @param graphicalStaffEntry
  309. * @param duration
  310. * @param openTie
  311. * @param isLastTieNote
  312. */
  313. protected handleTiedGraphicalNote(tiedGraphicalNote: GraphicalNote, beams: Beam[], activeClef: ClefInstruction,
  314. octaveShiftValue: OctaveEnum, graphicalStaffEntry: GraphicalStaffEntry, duration: Fraction,
  315. openTie: Tie, isLastTieNote: boolean): void {
  316. throw new Error("abstract, not implemented");
  317. }
  318. protected handleVoiceEntryLyrics(voiceEntry: VoiceEntry, graphicalStaffEntry: GraphicalStaffEntry,
  319. openLyricWords: LyricWord[]): void {
  320. throw new Error("abstract, not implemented");
  321. }
  322. protected handleVoiceEntryOrnaments(ornamentContainer: OrnamentContainer, voiceEntry: VoiceEntry,
  323. graphicalStaffEntry: GraphicalStaffEntry): void {
  324. throw new Error("abstract, not implemented");
  325. }
  326. protected handleVoiceEntryArticulations(articulations: ArticulationEnum[],
  327. voiceEntry: VoiceEntry,
  328. staffEntry: GraphicalStaffEntry): void {
  329. throw new Error("abstract, not implemented");
  330. }
  331. /**
  332. * Adds a technical instruction at the given staff entry.
  333. * @param technicalInstructions
  334. * @param voiceEntry
  335. * @param staffEntry
  336. */
  337. protected handleVoiceEntryTechnicalInstructions(technicalInstructions: TechnicalInstruction[],
  338. voiceEntry: VoiceEntry, staffEntry: GraphicalStaffEntry): void {
  339. throw new Error("abstract, not implemented");
  340. }
  341. protected handleTuplet(graphicalNote: GraphicalNote, tuplet: Tuplet, openTuplets: Tuplet[]): void {
  342. throw new Error("abstract, not implemented");
  343. }
  344. protected layoutVoiceEntry(voiceEntry: VoiceEntry, graphicalNotes: GraphicalNote[],
  345. graphicalStaffEntry: GraphicalStaffEntry, hasPitchedNote: boolean): void {
  346. throw new Error("abstract, not implemented");
  347. }
  348. protected layoutStaffEntry(graphicalStaffEntry: GraphicalStaffEntry): void {
  349. throw new Error("abstract, not implemented");
  350. }
  351. protected createGraphicalTie(tie: Tie, startGse: GraphicalStaffEntry, endGse: GraphicalStaffEntry, startNote: GraphicalNote,
  352. endNote: GraphicalNote): GraphicalTie {
  353. throw new Error("abstract, not implemented");
  354. }
  355. protected updateStaffLineBorders(staffLine: StaffLine): void {
  356. throw new Error("abstract, not implemented");
  357. }
  358. /**
  359. * Iterate through all Measures and calculates the MeasureNumberLabels.
  360. * @param musicSystem
  361. */
  362. protected calculateMeasureNumberPlacement(musicSystem: MusicSystem): void {
  363. const staffLine: StaffLine = musicSystem.StaffLines[0];
  364. if (!staffLine || !staffLine.Measures[0]) {
  365. log.warn("calculateMeasureNumberPlacement: measure undefined for system.Id " + musicSystem.Id);
  366. return; // TODO apparently happens in script sometimes (mp #70)
  367. }
  368. let previousLabelMeasureNumber: number = staffLine.Measures[0].MeasureNumber;
  369. let labelOffsetX: number = 0;
  370. for (let i: number = 0; i < staffLine.Measures.length; i++) {
  371. if (this.rules.RenderMeasureNumbersOnlyAtSystemStart && i > 0) {
  372. return; // no more measures number labels need to be rendered for this system, so we can just return instead of continue.
  373. }
  374. const measure: GraphicalMeasure = staffLine.Measures[i];
  375. if (measure.MeasureNumber === 0 || measure.MeasureNumber === 1) {
  376. previousLabelMeasureNumber = measure.MeasureNumber;
  377. // for the first measure, this label still needs to be created. Afterwards, this variable will hold the previous label's measure number.
  378. }
  379. if (measure !== staffLine.Measures[0] && this.rules.MeasureNumberLabelXOffset) {
  380. labelOffsetX = this.rules.MeasureNumberLabelXOffset;
  381. } else {
  382. labelOffsetX = 0; // don't offset label for first measure in staffline
  383. }
  384. if ((measure.MeasureNumber === previousLabelMeasureNumber ||
  385. measure.MeasureNumber >= previousLabelMeasureNumber + this.rules.MeasureNumberLabelOffset) &&
  386. !measure.parentSourceMeasure.ImplicitMeasure) {
  387. if (measure.MeasureNumber !== 1 ||
  388. (measure.MeasureNumber === 1 && measure !== staffLine.Measures[0])) {
  389. this.calculateSingleMeasureNumberPlacement(measure, staffLine, musicSystem, labelOffsetX);
  390. }
  391. previousLabelMeasureNumber = measure.MeasureNumber;
  392. }
  393. }
  394. }
  395. /// <summary>
  396. /// This method calculates a single MeasureNumberLabel and adds it to the graphical label list of the music system
  397. /// </summary>
  398. /// <param name="measure"></param>
  399. /// <param name="staffLine"></param>
  400. /// <param name="musicSystem"></param>
  401. private calculateSingleMeasureNumberPlacement(measure: GraphicalMeasure, staffLine: StaffLine, musicSystem: MusicSystem,
  402. labelOffsetX: number = 0): void {
  403. const labelNumber: string = measure.MeasureNumber.toString();
  404. const label: Label = new Label(labelNumber);
  405. // maybe give rules as argument instead of just setting fontStyle and maybe other settings manually afterwards
  406. const graphicalLabel: GraphicalLabel = new GraphicalLabel(label, this.rules.MeasureNumberLabelHeight,
  407. TextAlignmentEnum.LeftBottom, this.rules);
  408. const skyBottomLineCalculator: SkyBottomLineCalculator = staffLine.SkyBottomLineCalculator;
  409. // calculate LabelBoundingBox and set PSI parent
  410. graphicalLabel.setLabelPositionAndShapeBorders();
  411. graphicalLabel.PositionAndShape.Parent = musicSystem.PositionAndShape;
  412. // calculate relative Position
  413. const relativeX: number = staffLine.PositionAndShape.RelativePosition.x +
  414. measure.PositionAndShape.RelativePosition.x - graphicalLabel.PositionAndShape.BorderMarginLeft +
  415. labelOffsetX;
  416. let relativeY: number;
  417. // and the corresponding SkyLine indices
  418. let start: number = relativeX;
  419. let end: number = relativeX - graphicalLabel.PositionAndShape.BorderLeft + graphicalLabel.PositionAndShape.BorderRight;
  420. start -= staffLine.PositionAndShape.RelativePosition.x;
  421. end -= staffLine.PositionAndShape.RelativePosition.x;
  422. // correct for hypersensitive collision checks, notes having skyline extend too far to left and right
  423. const startCollisionCheck: number = start + 0.5;
  424. const endCollisionCheck: number = end - 0.5;
  425. // get the minimum corresponding SkyLine value
  426. const skyLineMinValue: number = skyBottomLineCalculator.getSkyLineMinInRange(startCollisionCheck, endCollisionCheck);
  427. if (measure === staffLine.Measures[0]) {
  428. // must take into account possible MusicSystem Brackets
  429. let minBracketTopBorder: number = 0;
  430. if (musicSystem.GroupBrackets.length > 0) {
  431. for (const groupBracket of musicSystem.GroupBrackets) {
  432. minBracketTopBorder = Math.min(minBracketTopBorder, groupBracket.PositionAndShape.BorderTop);
  433. }
  434. }
  435. relativeY = Math.min(skyLineMinValue, minBracketTopBorder);
  436. } else {
  437. relativeY = skyLineMinValue;
  438. }
  439. relativeY = Math.min(0, relativeY);
  440. graphicalLabel.PositionAndShape.RelativePosition = new PointF2D(relativeX, relativeY);
  441. skyBottomLineCalculator.updateSkyLineInRange(start, end, relativeY + graphicalLabel.PositionAndShape.BorderMarginTop);
  442. musicSystem.MeasureNumberLabels.push(graphicalLabel);
  443. }
  444. /**
  445. * Calculate the shape (Bézier curve) for this tie.
  446. * @param tie
  447. * @param tieIsAtSystemBreak
  448. */
  449. protected layoutGraphicalTie(tie: GraphicalTie, tieIsAtSystemBreak: boolean, isTab: boolean): void {
  450. throw new Error("abstract, not implemented");
  451. }
  452. /**
  453. * Calculate the Lyrics YPositions for a single [[StaffLine]].
  454. * @param staffLine
  455. * @param lyricVersesNumber
  456. */
  457. protected calculateSingleStaffLineLyricsPosition(staffLine: StaffLine, lyricVersesNumber: number[]): GraphicalStaffEntry[] {
  458. let numberOfVerses: number = 0;
  459. let lyricsStartYPosition: number = this.rules.StaffHeight; // Add offset to prevent collision
  460. const lyricsStaffEntriesList: GraphicalStaffEntry[] = [];
  461. const skyBottomLineCalculator: SkyBottomLineCalculator = staffLine.SkyBottomLineCalculator;
  462. // first find maximum Ycoordinate for the whole StaffLine
  463. let len: number = staffLine.Measures.length;
  464. for (let idx: number = 0; idx < len; ++idx) {
  465. const measure: GraphicalMeasure = staffLine.Measures[idx];
  466. const measureRelativePosition: PointF2D = measure.PositionAndShape.RelativePosition;
  467. const len2: number = measure.staffEntries.length;
  468. for (let idx2: number = 0; idx2 < len2; ++idx2) {
  469. const staffEntry: GraphicalStaffEntry = measure.staffEntries[idx2];
  470. if (staffEntry.LyricsEntries.length > 0) {
  471. lyricsStaffEntriesList.push(staffEntry);
  472. numberOfVerses = Math.max(numberOfVerses, staffEntry.LyricsEntries.length);
  473. // Position of Staffentry relative to StaffLine
  474. const staffEntryPositionX: number = staffEntry.PositionAndShape.RelativePosition.x +
  475. measureRelativePosition.x;
  476. let minMarginLeft: number = Number.MAX_VALUE;
  477. let maxMarginRight: number = Number.MIN_VALUE;
  478. // if more than one LyricEntry in StaffEntry, find minMarginLeft, maxMarginRight of all corresponding Labels
  479. for (let i: number = 0; i < staffEntry.LyricsEntries.length; i++) {
  480. const lyricsEntryLabel: GraphicalLabel = staffEntry.LyricsEntries[i].GraphicalLabel;
  481. minMarginLeft = Math.min(minMarginLeft, staffEntryPositionX + lyricsEntryLabel.PositionAndShape.BorderMarginLeft);
  482. maxMarginRight = Math.max(maxMarginRight, staffEntryPositionX + lyricsEntryLabel.PositionAndShape.BorderMarginRight);
  483. }
  484. // check BottomLine in this range and take the maximum between the two values
  485. const bottomLineMax: number = skyBottomLineCalculator.getBottomLineMaxInRange(minMarginLeft, maxMarginRight);
  486. lyricsStartYPosition = Math.max(lyricsStartYPosition, bottomLineMax);
  487. }
  488. }
  489. }
  490. let maxPosition: number = 0;
  491. // iterate again through the Staffentries with LyricEntries
  492. len = lyricsStaffEntriesList.length;
  493. for (const staffEntry of lyricsStaffEntriesList) {
  494. // set LyricEntryLabel RelativePosition
  495. for (let i: number = 0; i < staffEntry.LyricsEntries.length; i++) {
  496. const lyricEntry: GraphicalLyricEntry = staffEntry.LyricsEntries[i];
  497. const lyricsEntryLabel: GraphicalLabel = lyricEntry.GraphicalLabel;
  498. // read the verseNumber and get index of this number in the sorted LyricVerseNumbersList of Instrument
  499. // eg verseNumbers: 2,3,4,6 => 1,2,3,4
  500. const verseNumber: number = lyricEntry.LyricsEntry.VerseNumber;
  501. const sortedLyricVerseNumberIndex: number = lyricVersesNumber.indexOf(verseNumber);
  502. const firstPosition: number = lyricsStartYPosition + this.rules.LyricsHeight + this.rules.VerticalBetweenLyricsDistance +
  503. this.rules.LyricsYOffsetToStaffHeight;
  504. // Y-position calculated according to aforementioned mapping
  505. let position: number = firstPosition + (this.rules.VerticalBetweenLyricsDistance + this.rules.LyricsHeight) * sortedLyricVerseNumberIndex;
  506. if (this.leadSheet) {
  507. position = 3.4 + (this.rules.VerticalBetweenLyricsDistance + this.rules.LyricsHeight) * (sortedLyricVerseNumberIndex);
  508. }
  509. const previousRelativeX: number = lyricsEntryLabel.PositionAndShape.RelativePosition.x;
  510. lyricsEntryLabel.PositionAndShape.RelativePosition = new PointF2D(previousRelativeX, position);
  511. maxPosition = Math.max(maxPosition, position);
  512. }
  513. }
  514. // update BottomLine (on the whole StaffLine's length)
  515. if (lyricsStaffEntriesList.length > 0) {
  516. const endX: number = staffLine.PositionAndShape.Size.width;
  517. let startX: number = lyricsStaffEntriesList[0].PositionAndShape.RelativePosition.x +
  518. lyricsStaffEntriesList[0].PositionAndShape.BorderMarginLeft +
  519. lyricsStaffEntriesList[0].parentMeasure.PositionAndShape.RelativePosition.x;
  520. startX = startX > endX ? endX : startX;
  521. skyBottomLineCalculator.updateBottomLineInRange(startX, endX, maxPosition);
  522. }
  523. return lyricsStaffEntriesList;
  524. }
  525. /**
  526. * calculates the dashes of lyric words and the extending underscore lines of syllables sung on more than one note.
  527. * @param lyricsStaffEntries
  528. */
  529. protected calculateLyricsExtendsAndDashes(lyricsStaffEntries: GraphicalStaffEntry[]): void {
  530. // iterate again to create now the extend lines and dashes for words
  531. for (let idx: number = 0, len: number = lyricsStaffEntries.length; idx < len; ++idx) {
  532. const staffEntry: GraphicalStaffEntry = lyricsStaffEntries[idx];
  533. // set LyricEntryLabel RelativePosition
  534. for (let i: number = 0; i < staffEntry.LyricsEntries.length; i++) {
  535. const lyricEntry: GraphicalLyricEntry = staffEntry.LyricsEntries[i];
  536. // calculate LyricWord's Dashes and underscoreLine
  537. if (lyricEntry.ParentLyricWord &&
  538. lyricEntry.ParentLyricWord.GraphicalLyricsEntries[lyricEntry.ParentLyricWord.GraphicalLyricsEntries.length - 1] !== lyricEntry) {
  539. this.calculateSingleLyricWord(lyricEntry);
  540. }
  541. // calculate the underscore line extend if needed
  542. if (lyricEntry.LyricsEntry.extend) {
  543. this.calculateLyricExtend(lyricEntry);
  544. }
  545. }
  546. }
  547. }
  548. /**
  549. * Calculate a single OctaveShift for a [[MultiExpression]].
  550. * @param sourceMeasure
  551. * @param multiExpression
  552. * @param measureIndex
  553. * @param staffIndex
  554. */
  555. protected calculateSingleOctaveShift(sourceMeasure: SourceMeasure, multiExpression: MultiExpression,
  556. measureIndex: number, staffIndex: number): void {
  557. throw new Error("abstract, not implemented");
  558. }
  559. /**
  560. * Calculate all the textual [[RepetitionInstruction]]s (e.g. dal segno) for a single [[SourceMeasure]].
  561. * @param repetitionInstruction
  562. * @param measureIndex
  563. */
  564. protected calculateWordRepetitionInstruction(repetitionInstruction: RepetitionInstruction,
  565. measureIndex: number): void {
  566. throw new Error("abstract, not implemented");
  567. }
  568. /**
  569. * Calculate all the Mood and Unknown Expressions for a single [[MultiExpression]].
  570. * @param multiExpression
  571. * @param measureIndex
  572. * @param staffIndex
  573. */
  574. protected calculateMoodAndUnknownExpression(multiExpression: MultiExpression, measureIndex: number, staffIndex: number): void {
  575. // calculate absolute Timestamp
  576. const absoluteTimestamp: Fraction = multiExpression.AbsoluteTimestamp;
  577. const measures: GraphicalMeasure[] = this.graphicalMusicSheet.MeasureList[measureIndex];
  578. let relative: PointF2D = new PointF2D();
  579. if ((multiExpression.MoodList.length > 0) || (multiExpression.UnknownList.length > 0)) {
  580. let combinedExprString: string = "";
  581. for (let idx: number = 0, len: number = multiExpression.EntriesList.length; idx < len; ++idx) {
  582. const entry: MultiExpressionEntry = multiExpression.EntriesList[idx];
  583. if (entry.prefix !== "") {
  584. if (combinedExprString === "") {
  585. combinedExprString += entry.prefix;
  586. } else {
  587. combinedExprString += " " + entry.prefix;
  588. }
  589. }
  590. if (combinedExprString === "") {
  591. combinedExprString += entry.label;
  592. } else {
  593. combinedExprString += " " + entry.label;
  594. }
  595. }
  596. const staffLine: StaffLine = measures[staffIndex].ParentStaffLine;
  597. if (!staffLine) {
  598. log.debug("MusicSheetCalculator.calculateMoodAndUnknownExpression: staffLine undefined. Returning.");
  599. return;
  600. }
  601. relative = this.getRelativePositionInStaffLineFromTimestamp(absoluteTimestamp, staffIndex, staffLine, staffLine?.isPartOfMultiStaffInstrument());
  602. if (Math.abs(relative.x - 0) < 0.0001) {
  603. relative.x = measures[staffIndex].beginInstructionsWidth + this.rules.RhythmRightMargin;
  604. }
  605. const fontHeight: number = this.rules.UnknownTextHeight;
  606. const placement: PlacementEnum = multiExpression.getPlacementOfFirstEntry();
  607. const graphLabel: GraphicalLabel = this.calculateLabel(staffLine,
  608. relative, combinedExprString,
  609. multiExpression.getFontstyleOfFirstEntry(),
  610. placement,
  611. fontHeight);
  612. const gue: GraphicalUnknownExpression = new GraphicalUnknownExpression(
  613. staffLine, graphLabel, placement, measures[staffIndex]?.parentSourceMeasure, multiExpression);
  614. // multiExpression); // TODO would be nice to hand over and save reference to original expression,
  615. // but MultiExpression is not an AbstractExpression.
  616. staffLine.AbstractExpressions.push(gue);
  617. }
  618. }
  619. /**
  620. * Delete all Objects that must be recalculated.
  621. * If graphicalMusicSheet.reCalculate has been called, then this method will be called to reset or remove all flexible
  622. * graphical music symbols (e.g. Ornaments, Lyrics, Slurs) graphicalMusicSheet will have MusicPages, they will have MusicSystems etc...
  623. */
  624. protected clearRecreatedObjects(): void {
  625. // Clear StaffEntries with GraphicalTies
  626. for (let idx: number = 0, len: number = this.staffEntriesWithGraphicalTies.length; idx < len; ++idx) {
  627. const staffEntriesWithGraphicalTie: GraphicalStaffEntry = this.staffEntriesWithGraphicalTies[idx];
  628. staffEntriesWithGraphicalTie.GraphicalTies.length = 0;
  629. }
  630. this.staffEntriesWithGraphicalTies.length = 0;
  631. return;
  632. }
  633. /**
  634. * This method handles a [[StaffEntryLink]].
  635. * @param graphicalStaffEntry
  636. * @param staffEntryLinks
  637. */
  638. protected handleStaffEntryLink(graphicalStaffEntry: GraphicalStaffEntry,
  639. staffEntryLinks: StaffEntryLink[]): void {
  640. log.debug("handleStaffEntryLink not implemented");
  641. }
  642. /**
  643. * Store the newly computed [[Measure]]s in newly created [[MusicSystem]]s.
  644. */
  645. protected calculateMusicSystems(): void {
  646. if (!this.graphicalMusicSheet.MeasureList) {
  647. return;
  648. }
  649. const allMeasures: GraphicalMeasure[][] = this.graphicalMusicSheet.MeasureList;
  650. if (!allMeasures) {
  651. return;
  652. }
  653. if (this.rules.MinMeasureToDrawIndex > allMeasures.length - 1) {
  654. log.debug("minimum measure to draw index out of range. resetting min measure index to limit.");
  655. this.rules.MinMeasureToDrawIndex = allMeasures.length - 1;
  656. }
  657. // visible 2D-MeasureList
  658. const visibleMeasureList: GraphicalMeasure[][] = [];
  659. for (let idx: number = this.rules.MinMeasureToDrawIndex, len: number = allMeasures.length;
  660. idx < len && idx <= this.rules.MaxMeasureToDrawIndex; ++idx) {
  661. const graphicalMeasures: GraphicalMeasure[] = allMeasures[idx];
  662. const visiblegraphicalMeasures: GraphicalMeasure[] = [];
  663. for (let idx2: number = 0, len2: number = graphicalMeasures.length; idx2 < len2; ++idx2) {
  664. const graphicalMeasure: GraphicalMeasure = allMeasures[idx][idx2];
  665. if (graphicalMeasure?.isVisible()) {
  666. visiblegraphicalMeasures.push(graphicalMeasure);
  667. if (this.rules.ColoringEnabled) {
  668. // (re-)color notes
  669. for (const staffEntry of graphicalMeasure.staffEntries) {
  670. for (const gve of staffEntry.graphicalVoiceEntries) {
  671. gve.color();
  672. }
  673. }
  674. }
  675. }
  676. }
  677. visibleMeasureList.push(visiblegraphicalMeasures);
  678. }
  679. // find out how many StaffLine Instances we need
  680. let numberOfStaffLines: number = 0;
  681. for (let idx: number = 0, len: number = visibleMeasureList.length; idx < len; ++idx) {
  682. const gmlist: GraphicalMeasure[] = visibleMeasureList[idx];
  683. numberOfStaffLines = Math.max(gmlist.length, numberOfStaffLines);
  684. break;
  685. }
  686. if (numberOfStaffLines === 0) {
  687. return;
  688. }
  689. // build the MusicSystems
  690. const musicSystemBuilder: MusicSystemBuilder = new MusicSystemBuilder();
  691. musicSystemBuilder.initialize(this.graphicalMusicSheet, visibleMeasureList, numberOfStaffLines);
  692. this.musicSystems = musicSystemBuilder.buildMusicSystems();
  693. this.formatMeasures();
  694. // check for Measures with only WholeRestNotes and correct their X-Position (middle of Measure)
  695. // this.checkMeasuresForWholeRestNotes(); // this currently does nothing
  696. if (!this.leadSheet) {
  697. // calculate Beam Placement
  698. // this.calculateBeams(); // does nothing for now, because layoutBeams() is an empty method
  699. // possible Displacement of RestNotes
  700. this.optimizeRestPlacement();
  701. // possible Displacement of RestNotes
  702. this.calculateStaffEntryArticulationMarks();
  703. if (this.rules.RenderSlurs) { // technically we should separate slurs and ties, but shouldn't be relevant for now
  704. // calculate Ties
  705. this.calculateTieCurves();
  706. }
  707. }
  708. // calculate Sky- and BottomLine
  709. // will have reasonable values only between ObjectsBorders (eg StaffEntries)
  710. this.calculateSkyBottomLines();
  711. // calculate TupletsNumbers
  712. this.calculateTupletNumbers();
  713. // calculate MeasureNumbers
  714. if (this.rules.RenderMeasureNumbers) {
  715. for (let idx: number = 0, len: number = this.musicSystems.length; idx < len; ++idx) {
  716. const musicSystem: MusicSystem = this.musicSystems[idx];
  717. this.calculateMeasureNumberPlacement(musicSystem);
  718. }
  719. }
  720. // calculate Slurs
  721. if (!this.leadSheet && this.rules.RenderSlurs) {
  722. this.calculateSlurs();
  723. }
  724. // calculate StaffEntry Ornaments
  725. // (must come after Slurs)
  726. if (!this.leadSheet) {
  727. this.calculateOrnaments();
  728. }
  729. // calculate StaffEntry ChordSymbols
  730. this.calculateChordSymbols();
  731. if (!this.leadSheet) {
  732. // calculate all Instantaneous/Continuous Dynamics Expressions
  733. this.calculateDynamicExpressions();
  734. // calculate all Mood and Unknown Expression
  735. this.calculateMoodAndUnknownExpressions();
  736. // Calculate the alignment of close expressions
  737. this.calculateExpressionAlignements();
  738. // calculate all OctaveShifts
  739. this.calculateOctaveShifts();
  740. // calcualte RepetitionInstructions (Dal Segno, Coda, etc)
  741. this.calculateWordRepetitionInstructions();
  742. }
  743. // calculate endings last, so they appear above measure numbers
  744. this.calculateRepetitionEndings();
  745. // calcualte all Tempo Expressions
  746. if (!this.leadSheet) {
  747. this.calculateTempoExpressions();
  748. }
  749. // calculate all LyricWords Positions
  750. this.calculateLyricsPosition();
  751. // update all StaffLine's Borders
  752. // create temporary Object, just to call the methods (in order to avoid declaring them static)
  753. for (let idx2: number = 0, len2: number = this.musicSystems.length; idx2 < len2; ++idx2) {
  754. const musicSystem: MusicSystem = this.musicSystems[idx2];
  755. for (let idx3: number = 0, len3: number = musicSystem.StaffLines.length; idx3 < len3; ++idx3) {
  756. const staffLine: StaffLine = musicSystem.StaffLines[idx3];
  757. this.updateStaffLineBorders(staffLine);
  758. }
  759. }
  760. // calculate Y-spacing -> MusicPages are created here
  761. musicSystemBuilder.calculateSystemYLayout();
  762. // calculate Comments for each Staffline
  763. this.calculateComments();
  764. // calculate marked Areas for Systems
  765. this.calculateMarkedAreas();
  766. // the following must be done after Y-spacing, when the MusicSystems's final Dimensions are set
  767. // set the final yPositions of Objects such as SystemLabels and SystemLinesContainers,
  768. // create all System Lines, Brackets and MeasureNumbers (for all systems and for all pages)
  769. for (let idx: number = 0, len: number = this.graphicalMusicSheet.MusicPages.length; idx < len; ++idx) {
  770. const graphicalMusicPage: GraphicalMusicPage = this.graphicalMusicSheet.MusicPages[idx];
  771. for (let idx2: number = 0, len2: number = graphicalMusicPage.MusicSystems.length; idx2 < len2; ++idx2) {
  772. const isFirstSystem: boolean = idx === 0 && idx2 === 0;
  773. const musicSystem: MusicSystem = graphicalMusicPage.MusicSystems[idx2];
  774. musicSystem.setMusicSystemLabelsYPosition();
  775. if (!this.leadSheet) {
  776. musicSystem.setYPositionsToVerticalLineObjectsAndCreateLines(this.rules);
  777. musicSystem.createSystemLeftLine(this.rules.SystemThinLineWidth, this.rules.SystemLabelsRightMargin, isFirstSystem);
  778. musicSystem.createInstrumentBrackets(this.graphicalMusicSheet.ParentMusicSheet.Instruments, this.rules.StaffHeight);
  779. musicSystem.createGroupBrackets(this.graphicalMusicSheet.ParentMusicSheet.InstrumentalGroups, this.rules.StaffHeight, 0);
  780. musicSystem.alignBeginInstructions();
  781. } else if (musicSystem === musicSystem.Parent.MusicSystems[0]) {
  782. musicSystem.createSystemLeftLine(this.rules.SystemThinLineWidth, this.rules.SystemLabelsRightMargin, isFirstSystem);
  783. }
  784. musicSystem.calculateBorders(this.rules);
  785. }
  786. const distance: number = graphicalMusicPage.MusicSystems[0].PositionAndShape.BorderTop;
  787. for (let idx2: number = 0, len2: number = graphicalMusicPage.MusicSystems.length; idx2 < len2; ++idx2) {
  788. const musicSystem: MusicSystem = graphicalMusicPage.MusicSystems[idx2];
  789. // let newPosition: PointF2D = new PointF2D(musicSystem.PositionAndShape.RelativePosition.x,
  790. // musicSystem.PositionAndShape.RelativePosition.y - distance);
  791. musicSystem.PositionAndShape.RelativePosition =
  792. new PointF2D(musicSystem.PositionAndShape.RelativePosition.x, musicSystem.PositionAndShape.RelativePosition.y - distance);
  793. }
  794. for (let idx2: number = 0, len2: number = graphicalMusicPage.MusicSystems.length; idx2 < len2; ++idx2) {
  795. const musicSystem: MusicSystem = graphicalMusicPage.MusicSystems[idx2];
  796. for (let idx3: number = 0, len3: number = musicSystem.StaffLines.length; idx3 < len3; ++idx3) {
  797. const staffLine: StaffLine = musicSystem.StaffLines[idx3];
  798. staffLine.addActivitySymbolClickArea();
  799. }
  800. }
  801. // calculate TopBottom Borders for all elements recursively
  802. graphicalMusicPage.PositionAndShape.calculateTopBottomBorders(); // necessary for composer label (page labels) for high notes in first system
  803. // TODO how much performance does this cost? can we reduce the amount of calculations, e.g. only checking top?
  804. // calculate all Labels's Positions for the first Page
  805. if (graphicalMusicPage === this.graphicalMusicSheet.MusicPages[0]) {
  806. this.calculatePageLabels(graphicalMusicPage);
  807. }
  808. // calculate TopBottom Borders for all elements recursively
  809. graphicalMusicPage.PositionAndShape.calculateTopBottomBorders(); // this is where top bottom borders were originally calculated (only once)
  810. }
  811. }
  812. protected calculateMarkedAreas(): void {
  813. //log.debug("calculateMarkedAreas not implemented");
  814. return;
  815. }
  816. protected calculateComments(): void {
  817. //log.debug("calculateComments not implemented");
  818. return;
  819. }
  820. protected calculateChordSymbols(): void {
  821. for (const musicSystem of this.musicSystems) {
  822. for (const staffLine of musicSystem.StaffLines) {
  823. const sbc: SkyBottomLineCalculator = staffLine.SkyBottomLineCalculator;
  824. for (const measure of staffLine.Measures) {
  825. for (const staffEntry of measure.staffEntries) {
  826. if (!staffEntry.graphicalChordContainers || staffEntry.graphicalChordContainers.length === 0) {
  827. continue;
  828. }
  829. for (const graphicalChordContainer of staffEntry.graphicalChordContainers) {
  830. const sps: BoundingBox = staffEntry.PositionAndShape;
  831. const gps: BoundingBox = graphicalChordContainer.PositionAndShape;
  832. const start: number = gps.BorderMarginLeft + sps.AbsolutePosition.x;
  833. const end: number = gps.BorderMarginRight + sps.AbsolutePosition.x;
  834. sbc.updateSkyLineInRange(start, end, sps.BorderMarginTop);
  835. }
  836. }
  837. }
  838. }
  839. }
  840. }
  841. /**
  842. * Do layout on staff measures which only consist of a full rest.
  843. * @param rest
  844. * @param gse
  845. * @param measure
  846. */
  847. protected layoutMeasureWithWholeRest(rest: GraphicalNote, gse: GraphicalStaffEntry,
  848. measure: GraphicalMeasure): void {
  849. return;
  850. }
  851. protected layoutBeams(staffEntry: GraphicalStaffEntry): void {
  852. return;
  853. }
  854. protected layoutArticulationMarks(articulations: ArticulationEnum[], voiceEntry: VoiceEntry, graphicalStaffEntry: GraphicalStaffEntry): void {
  855. return;
  856. }
  857. protected layoutOrnament(ornaments: OrnamentContainer, voiceEntry: VoiceEntry,
  858. graphicalStaffEntry: GraphicalStaffEntry): void {
  859. return;
  860. }
  861. protected calculateRestNotePlacementWithinGraphicalBeam(graphicalStaffEntry: GraphicalStaffEntry,
  862. restNote: GraphicalNote,
  863. previousNote: GraphicalNote,
  864. nextStaffEntry: GraphicalStaffEntry,
  865. nextNote: GraphicalNote): void {
  866. return;
  867. }
  868. protected calculateTupletNumbers(): void {
  869. return;
  870. }
  871. protected calculateSlurs(): void {
  872. return;
  873. }
  874. protected calculateDynamicExpressionsForMultiExpression(multiExpression: MultiExpression, measureIndex: number, staffIndex: number): void {
  875. return;
  876. }
  877. /**
  878. * This method calculates the RelativePosition of a single verbal GraphicalContinuousDynamic.
  879. * @param graphicalContinuousDynamic Graphical continous dynamic to be calculated
  880. * @param startPosInStaffline Starting point in staff line
  881. */
  882. protected calculateGraphicalVerbalContinuousDynamic(graphicalContinuousDynamic: GraphicalContinuousDynamicExpression,
  883. startPosInStaffline: PointF2D): void {
  884. // if ContinuousDynamicExpression is given from words
  885. const graphLabel: GraphicalLabel = graphicalContinuousDynamic.Label;
  886. const left: number = startPosInStaffline.x + graphLabel.PositionAndShape.BorderMarginLeft;
  887. const right: number = startPosInStaffline.x + graphLabel.PositionAndShape.BorderMarginRight;
  888. // placement always below the currentStaffLine, with the exception of Voice Instrument (-> above)
  889. const placement: PlacementEnum = graphicalContinuousDynamic.ContinuousDynamic.Placement;
  890. const staffLine: StaffLine = graphicalContinuousDynamic.ParentStaffLine;
  891. const skyBottomLineCalculator: SkyBottomLineCalculator = staffLine.SkyBottomLineCalculator;
  892. let drawingHeight: number;
  893. if (placement === PlacementEnum.Below) {
  894. drawingHeight = skyBottomLineCalculator.getBottomLineMaxInRange(left, right); // Bottom line
  895. graphLabel.PositionAndShape.RelativePosition = new PointF2D(startPosInStaffline.x, drawingHeight - graphLabel.PositionAndShape.BorderMarginTop);
  896. } else {
  897. drawingHeight = skyBottomLineCalculator.getSkyLineMinInRange(left, right);
  898. graphLabel.PositionAndShape.RelativePosition = new PointF2D(startPosInStaffline.x, drawingHeight - graphLabel.PositionAndShape.BorderMarginBottom);
  899. }
  900. }
  901. /**
  902. * This method calculates the RelativePosition of a single GraphicalContinuousDynamic.
  903. * @param graphicalContinuousDynamic Graphical continous dynamic to be calculated
  904. * @param startPosInStaffline Starting point in staff line
  905. */
  906. public calculateGraphicalContinuousDynamic(graphicalContinuousDynamic: GraphicalContinuousDynamicExpression, startPosInStaffline: PointF2D): void {
  907. const staffIndex: number = graphicalContinuousDynamic.ParentStaffLine.ParentStaff.idInMusicSheet;
  908. // TODO: Previously the staffIndex was passed down. BUT you can (and this function actually does this) get it from
  909. // the musicSystem OR from the ParentStaffLine. Is this the same index?
  910. // const staffIndex: number = musicSystem.StaffLines.indexOf(staffLine);
  911. // We know we have an end measure because otherwise we won't be called
  912. const endMeasure: GraphicalMeasure = this.graphicalMusicSheet.getGraphicalMeasureFromSourceMeasureAndIndex(
  913. graphicalContinuousDynamic.ContinuousDynamic.EndMultiExpression.SourceMeasureParent, staffIndex);
  914. if (!endMeasure) {
  915. log.warn("MusicSheetCalculator.calculateGraphicalContinuousDynamic: No endMeasure found");
  916. return;
  917. }
  918. graphicalContinuousDynamic.EndMeasure = endMeasure;
  919. const staffLine: StaffLine = graphicalContinuousDynamic.ParentStaffLine;
  920. const endStaffLine: StaffLine = endMeasure.ParentStaffLine;
  921. // check if Expression spreads over the same StaffLine or not
  922. const sameStaffLine: boolean = endStaffLine && staffLine === endStaffLine;
  923. let isPartOfMultiStaffInstrument: boolean = false;
  924. if (endStaffLine) { // unfortunately we can't do something like (endStaffLine?.check() || staffLine?.check()) in this typescript version
  925. isPartOfMultiStaffInstrument = endStaffLine?.isPartOfMultiStaffInstrument();
  926. } else if (staffLine) {
  927. isPartOfMultiStaffInstrument = staffLine?.isPartOfMultiStaffInstrument();
  928. }
  929. const endAbsoluteTimestamp: Fraction = Fraction.createFromFraction(graphicalContinuousDynamic.ContinuousDynamic.EndMultiExpression.AbsoluteTimestamp);
  930. const endPosInStaffLine: PointF2D = this.getRelativePositionInStaffLineFromTimestamp(
  931. endAbsoluteTimestamp, staffIndex, endStaffLine, isPartOfMultiStaffInstrument, 0);
  932. //currentMusicSystem and currentStaffLine
  933. const musicSystem: MusicSystem = staffLine.ParentMusicSystem;
  934. const currentStaffLineIndex: number = musicSystem.StaffLines.indexOf(staffLine);
  935. const skyBottomLineCalculator: SkyBottomLineCalculator = staffLine.SkyBottomLineCalculator;
  936. // let expressionIndex: number;
  937. // placement always below the currentStaffLine, with the exception of Voice Instrument (-> above)
  938. const placement: PlacementEnum = graphicalContinuousDynamic.ContinuousDynamic.Placement;
  939. // if ContinuousDynamicExpression is given from wedge
  940. let secondGraphicalContinuousDynamic: GraphicalContinuousDynamicExpression = undefined;
  941. // last length check
  942. if (sameStaffLine && endPosInStaffLine.x - startPosInStaffline.x < this.rules.WedgeMinLength) {
  943. endPosInStaffLine.x = startPosInStaffline.x + this.rules.WedgeMinLength;
  944. }
  945. // Upper staff wedge always starts at the given position and the lower staff wedge always starts at the begin of measure
  946. const upperStartX: number = startPosInStaffline.x;
  947. const lowerStartX: number = endStaffLine.Measures[0].beginInstructionsWidth - this.rules.WedgeHorizontalMargin - 2;
  948. //TODO fix this when a range of measures to draw is given that doesn't include all the dynamic's measures (e.g. for crescendo)
  949. let upperEndX: number = 0;
  950. let lowerEndX: number = 0;
  951. if (!sameStaffLine) {
  952. upperEndX = staffLine.PositionAndShape.Size.width;
  953. lowerEndX = endPosInStaffLine.x;
  954. // must create a new Wedge
  955. secondGraphicalContinuousDynamic = new GraphicalContinuousDynamicExpression(
  956. graphicalContinuousDynamic.ContinuousDynamic, endStaffLine, endMeasure.parentSourceMeasure);
  957. secondGraphicalContinuousDynamic.IsSplittedPart = true;
  958. graphicalContinuousDynamic.IsSplittedPart = true;
  959. } else {
  960. upperEndX = endPosInStaffLine.x;
  961. }
  962. // the Height of the Expression's placement
  963. let idealY: number = 0;
  964. let secondIdealY: number = 0;
  965. if (placement === PlacementEnum.Below) {
  966. // can be a single Staff Instrument or an Instrument with 2 Staves
  967. let nextStaffLineIndex: number = 0;
  968. if (currentStaffLineIndex < musicSystem.StaffLines.length - 1) {
  969. nextStaffLineIndex = currentStaffLineIndex + 1;
  970. }
  971. // check, maybe currentStaffLine is the last of the MusicSystem (and it has a ContinuousDynamicExpression with placement below)
  972. if (nextStaffLineIndex > currentStaffLineIndex) {
  973. // currentStaffLine isn't the last of the MusicSystem
  974. const nextStaffLine: StaffLine = musicSystem.StaffLines[nextStaffLineIndex];
  975. const distanceBetweenStaffLines: number = nextStaffLine.PositionAndShape.RelativePosition.y -
  976. staffLine.PositionAndShape.RelativePosition.y -
  977. this.rules.StaffHeight;
  978. // ideal Height is exactly between the two StaffLines
  979. idealY = this.rules.StaffHeight + distanceBetweenStaffLines / 2;
  980. } else {
  981. // currentStaffLine is the MusicSystem's last
  982. idealY = this.rules.WedgePlacementBelowY;
  983. }
  984. // must consider the upperWedge starting/ending tip for the comparison with the BottomLine
  985. idealY -= this.rules.WedgeOpeningLength / 2;
  986. if (!sameStaffLine) {
  987. // Set the value for the splitted y position to the ideal position before we check and modify it with
  988. // the skybottom calculator detection
  989. secondIdealY = idealY;
  990. }
  991. // must check BottomLine for possible collisions within the Length of the Expression
  992. // find the corresponding max value for the given Length
  993. let maxBottomLineValueForExpressionLength: number = skyBottomLineCalculator.getBottomLineMaxInRange(upperStartX, upperEndX);
  994. // if collisions, then set the Height accordingly
  995. if (maxBottomLineValueForExpressionLength > idealY) {
  996. idealY = maxBottomLineValueForExpressionLength;
  997. }
  998. // special case - wedge must be drawn within the boundaries of a crossedBeam
  999. const withinCrossedBeam: boolean = false;
  1000. if (currentStaffLineIndex < musicSystem.StaffLines.length - 1) {
  1001. // find GraphicalStaffEntries closest to wedge's xPositions
  1002. const closestToEndStaffEntry: GraphicalStaffEntry = staffLine.findClosestStaffEntry(upperEndX);
  1003. const closestToStartStaffEntry: GraphicalStaffEntry = staffLine.findClosestStaffEntry(upperStartX);
  1004. if (closestToStartStaffEntry && closestToEndStaffEntry) {
  1005. // must check both StaffLines
  1006. const startVerticalContainer: VerticalGraphicalStaffEntryContainer = closestToStartStaffEntry.parentVerticalContainer;
  1007. // const endVerticalContainer: VerticalGraphicalStaffEntryContainer = closestToEndStaffEntry.parentVerticalContainer;
  1008. if (startVerticalContainer) {
  1009. // TODO: Needs to be implemented?
  1010. // withinCrossedBeam = areStaffEntriesWithinCrossedBeam(startVerticalContainer,
  1011. // endVerticalContainer, currentStaffLineIndex, nextStaffLineIndex);
  1012. }
  1013. if (withinCrossedBeam) {
  1014. const nextStaffLine: StaffLine = musicSystem.StaffLines[nextStaffLineIndex];
  1015. const nextStaffLineMinSkyLineValue: number = nextStaffLine.SkyBottomLineCalculator.getSkyLineMinInRange(upperStartX, upperEndX);
  1016. const distanceBetweenStaffLines: number = nextStaffLine.PositionAndShape.RelativePosition.y -
  1017. staffLine.PositionAndShape.RelativePosition.y;
  1018. const relativeSkyLineHeight: number = distanceBetweenStaffLines + nextStaffLineMinSkyLineValue;
  1019. if (relativeSkyLineHeight - this.rules.WedgeOpeningLength > this.rules.StaffHeight) {
  1020. idealY = relativeSkyLineHeight - this.rules.WedgeVerticalMargin;
  1021. } else {
  1022. idealY = this.rules.StaffHeight + this.rules.WedgeOpeningLength;
  1023. }
  1024. graphicalContinuousDynamic.NotToBeRemoved = true;
  1025. }
  1026. }
  1027. }
  1028. // do the same in case of a Wedge ending at another StaffLine
  1029. if (!sameStaffLine) {
  1030. maxBottomLineValueForExpressionLength = endStaffLine.SkyBottomLineCalculator.getBottomLineMaxInRange(lowerStartX, lowerEndX);
  1031. if (maxBottomLineValueForExpressionLength > secondIdealY) {
  1032. secondIdealY = maxBottomLineValueForExpressionLength;
  1033. }
  1034. secondIdealY += this.rules.WedgeOpeningLength / 2;
  1035. secondIdealY += this.rules.WedgeVerticalMargin;
  1036. }
  1037. if (!withinCrossedBeam) {
  1038. idealY += this.rules.WedgeOpeningLength / 2;
  1039. idealY += this.rules.WedgeVerticalMargin;
  1040. }
  1041. } else if (placement === PlacementEnum.Above) {
  1042. // single Staff Instrument (eg Voice)
  1043. if (staffLine.ParentStaff.ParentInstrument.Staves.length === 1) {
  1044. // single Staff Voice Instrument
  1045. idealY = this.rules.WedgePlacementAboveY;
  1046. } else {
  1047. // Staff = not the first Staff of a 2-staved Instrument
  1048. let previousStaffLineIndex: number = 0;
  1049. if (currentStaffLineIndex > 0) {
  1050. previousStaffLineIndex = currentStaffLineIndex - 1;
  1051. }
  1052. const previousStaffLine: StaffLine = musicSystem.StaffLines[previousStaffLineIndex];
  1053. const distanceBetweenStaffLines: number = staffLine.PositionAndShape.RelativePosition.y -
  1054. previousStaffLine.PositionAndShape.RelativePosition.y -
  1055. this.rules.StaffHeight;
  1056. // ideal Height is exactly between the two StaffLines
  1057. idealY = -distanceBetweenStaffLines / 2;
  1058. }
  1059. // must consider the upperWedge starting/ending tip for the comparison with the SkyLine
  1060. idealY += this.rules.WedgeOpeningLength / 2;
  1061. if (!sameStaffLine) {
  1062. secondIdealY = idealY;
  1063. }
  1064. // must check SkyLine for possible collisions within the Length of the Expression
  1065. // find the corresponding min value for the given Length
  1066. let minSkyLineValueForExpressionLength: number = skyBottomLineCalculator.getSkyLineMinInRange(upperStartX, upperEndX);
  1067. // if collisions, then set the Height accordingly
  1068. if (minSkyLineValueForExpressionLength < idealY) {
  1069. idealY = minSkyLineValueForExpressionLength;
  1070. }
  1071. const withinCrossedBeam: boolean = false;
  1072. // special case - wedge must be drawn within the boundaries of a crossedBeam
  1073. if (staffLine.ParentStaff.ParentInstrument.Staves.length > 1 && currentStaffLineIndex > 0) {
  1074. // find GraphicalStaffEntries closest to wedge's xPositions
  1075. const closestToStartStaffEntry: GraphicalStaffEntry = staffLine.findClosestStaffEntry(upperStartX);
  1076. const closestToEndStaffEntry: GraphicalStaffEntry = staffLine.findClosestStaffEntry(upperEndX);
  1077. if (closestToStartStaffEntry && closestToEndStaffEntry) {
  1078. // must check both StaffLines
  1079. const startVerticalContainer: VerticalGraphicalStaffEntryContainer = closestToStartStaffEntry.parentVerticalContainer;
  1080. // const endVerticalContainer: VerticalGraphicalStaffEntryContainer = closestToEndStaffEntry.parentVerticalContainer;
  1081. const formerStaffLineIndex: number = currentStaffLineIndex - 1;
  1082. if (startVerticalContainer) {
  1083. // withinCrossedBeam = this.areStaffEntriesWithinCrossedBeam(startVerticalContainer,
  1084. // endVerticalContainer, currentStaffLineIndex, formerStaffLineIndex);
  1085. }
  1086. if (withinCrossedBeam) {
  1087. const formerStaffLine: StaffLine = musicSystem.StaffLines[formerStaffLineIndex];
  1088. const formerStaffLineMaxBottomLineValue: number = formerStaffLine.SkyBottomLineCalculator.
  1089. getBottomLineMaxInRange(upperStartX, upperEndX);
  1090. const distanceBetweenStaffLines: number = staffLine.PositionAndShape.RelativePosition.y -
  1091. formerStaffLine.PositionAndShape.RelativePosition.y;
  1092. const relativeSkyLineHeight: number = distanceBetweenStaffLines - formerStaffLineMaxBottomLineValue;
  1093. idealY = (relativeSkyLineHeight - this.rules.StaffHeight) / 2 + this.rules.StaffHeight;
  1094. }
  1095. }
  1096. }
  1097. // do the same in case of a Wedge ending at another StaffLine
  1098. if (!sameStaffLine) {
  1099. minSkyLineValueForExpressionLength = endStaffLine.SkyBottomLineCalculator.getSkyLineMinInRange(lowerStartX, lowerEndX);
  1100. if (minSkyLineValueForExpressionLength < secondIdealY) {
  1101. secondIdealY = minSkyLineValueForExpressionLength;
  1102. }
  1103. secondIdealY -= this.rules.WedgeOpeningLength / 2;
  1104. }
  1105. if (!withinCrossedBeam) {
  1106. idealY -= this.rules.WedgeOpeningLength / 2;
  1107. idealY -= this.rules.WedgeVerticalMargin;
  1108. }
  1109. if (!sameStaffLine) {
  1110. secondIdealY -= this.rules.WedgeVerticalMargin;
  1111. }
  1112. }
  1113. // now we have the correct placement Height for the Expression
  1114. // the idealY is calculated relative to the currentStaffLine
  1115. // Crescendo (point to the left, opening to the right)
  1116. graphicalContinuousDynamic.Lines.clear();
  1117. if (graphicalContinuousDynamic.ContinuousDynamic.DynamicType === ContDynamicEnum.crescendo) {
  1118. if (sameStaffLine) {
  1119. graphicalContinuousDynamic.createCrescendoLines(upperStartX, upperEndX, idealY);
  1120. graphicalContinuousDynamic.calcPsi();
  1121. } else {
  1122. // two different Wedges
  1123. graphicalContinuousDynamic.createFirstHalfCrescendoLines(upperStartX, upperEndX, idealY);
  1124. graphicalContinuousDynamic.calcPsi();
  1125. secondGraphicalContinuousDynamic.createSecondHalfCrescendoLines(lowerStartX, lowerEndX, secondIdealY);
  1126. secondGraphicalContinuousDynamic.calcPsi();
  1127. }
  1128. } else if (graphicalContinuousDynamic.ContinuousDynamic.DynamicType === ContDynamicEnum.diminuendo) {
  1129. if (sameStaffLine) {
  1130. graphicalContinuousDynamic.createDiminuendoLines(upperStartX, upperEndX, idealY);
  1131. graphicalContinuousDynamic.calcPsi();
  1132. } else {
  1133. graphicalContinuousDynamic.createFirstHalfDiminuendoLines(upperStartX, upperEndX, idealY);
  1134. graphicalContinuousDynamic.calcPsi();
  1135. secondGraphicalContinuousDynamic.createSecondHalfDiminuendoLines(lowerStartX, lowerEndX, secondIdealY);
  1136. secondGraphicalContinuousDynamic.calcPsi();
  1137. }
  1138. } //End Diminuendo
  1139. }
  1140. /**
  1141. * This method calculates the RelativePosition of a single GraphicalInstantaneousDynamicExpression.
  1142. * @param graphicalInstantaneousDynamic Dynamic expression to be calculated
  1143. * @param startPosInStaffline Starting point in staff line
  1144. */
  1145. protected calculateGraphicalInstantaneousDynamicExpression(graphicalInstantaneousDynamic: GraphicalInstantaneousDynamicExpression,
  1146. startPosInStaffline: PointF2D): void {
  1147. // get Margin Dimensions
  1148. const staffLine: StaffLine = graphicalInstantaneousDynamic.ParentStaffLine;
  1149. if (!staffLine) {
  1150. return; // TODO can happen when drawing range modified (osmd.setOptions({drawFromMeasureNumber...}))
  1151. }
  1152. const left: number = startPosInStaffline.x + graphicalInstantaneousDynamic.PositionAndShape.BorderMarginLeft;
  1153. const right: number = startPosInStaffline.x + graphicalInstantaneousDynamic.PositionAndShape.BorderMarginRight;
  1154. const skyBottomLineCalculator: SkyBottomLineCalculator = staffLine.SkyBottomLineCalculator;
  1155. let yPosition: number = 0;
  1156. // calculate yPosition according to Placement
  1157. if (graphicalInstantaneousDynamic.Placement === PlacementEnum.Above) {
  1158. const skyLineValue: number = skyBottomLineCalculator.getSkyLineMinInRange(left, right);
  1159. // if StaffLine part of multiStaff Instrument and not the first one, ideal yPosition middle of distance between Staves
  1160. if (staffLine.isPartOfMultiStaffInstrument() && staffLine.ParentStaff !== staffLine.ParentStaff.ParentInstrument.Staves[0]) {
  1161. const formerStaffLine: StaffLine = staffLine.ParentMusicSystem.StaffLines[staffLine.ParentMusicSystem.StaffLines.indexOf(staffLine) - 1];
  1162. const difference: number = staffLine.PositionAndShape.RelativePosition.y -
  1163. formerStaffLine.PositionAndShape.RelativePosition.y - this.rules.StaffHeight;
  1164. // take always into account the size of the Dynamic
  1165. if (skyLineValue > -difference / 2) {
  1166. yPosition = -difference / 2;
  1167. } else {
  1168. yPosition = skyLineValue - graphicalInstantaneousDynamic.PositionAndShape.BorderMarginBottom;
  1169. }
  1170. } else {
  1171. yPosition = skyLineValue - graphicalInstantaneousDynamic.PositionAndShape.BorderMarginBottom;
  1172. }
  1173. graphicalInstantaneousDynamic.PositionAndShape.RelativePosition = new PointF2D(startPosInStaffline.x, yPosition);
  1174. } else if (graphicalInstantaneousDynamic.Placement === PlacementEnum.Below) {
  1175. const bottomLineValue: number = skyBottomLineCalculator.getBottomLineMaxInRange(left, right);
  1176. // if StaffLine part of multiStaff Instrument and not the last one, ideal yPosition middle of distance between Staves
  1177. const lastStaff: Staff = staffLine.ParentStaff.ParentInstrument.Staves[staffLine.ParentStaff.ParentInstrument.Staves.length - 1];
  1178. if (staffLine.isPartOfMultiStaffInstrument() && staffLine.ParentStaff !== lastStaff) {
  1179. const nextStaffLine: StaffLine = staffLine.ParentMusicSystem.StaffLines[staffLine.ParentMusicSystem.StaffLines.indexOf(staffLine) + 1];
  1180. const difference: number = nextStaffLine.PositionAndShape.RelativePosition.y -
  1181. staffLine.PositionAndShape.RelativePosition.y - this.rules.StaffHeight;
  1182. const border: number = graphicalInstantaneousDynamic.PositionAndShape.BorderMarginBottom;
  1183. // take always into account the size of the Dynamic
  1184. if (bottomLineValue + border < this.rules.StaffHeight + difference / 2) {
  1185. yPosition = this.rules.StaffHeight + difference / 2;
  1186. } else {
  1187. yPosition = bottomLineValue - graphicalInstantaneousDynamic.PositionAndShape.BorderMarginTop;
  1188. }
  1189. } else {
  1190. yPosition = bottomLineValue - graphicalInstantaneousDynamic.PositionAndShape.BorderMarginTop;
  1191. }
  1192. graphicalInstantaneousDynamic.PositionAndShape.RelativePosition = new PointF2D(startPosInStaffline.x, yPosition);
  1193. }
  1194. graphicalInstantaneousDynamic.updateSkyBottomLine();
  1195. }
  1196. protected calcGraphicalRepetitionEndingsRecursively(repetition: Repetition): void {
  1197. return;
  1198. }
  1199. /**
  1200. * Calculate a single GraphicalRepetition.
  1201. * @param start
  1202. * @param end
  1203. * @param numberText
  1204. * @param offset
  1205. * @param leftOpen
  1206. * @param rightOpen
  1207. */
  1208. protected layoutSingleRepetitionEnding(start: GraphicalMeasure, end: GraphicalMeasure, numberText: string,
  1209. offset: number, leftOpen: boolean, rightOpen: boolean): void {
  1210. return;
  1211. }
  1212. protected calculateLabel(staffLine: StaffLine,
  1213. relative: PointF2D,
  1214. combinedString: string,
  1215. style: FontStyles,
  1216. placement: PlacementEnum,
  1217. fontHeight: number,
  1218. textAlignment: TextAlignmentEnum = TextAlignmentEnum.CenterBottom): GraphicalLabel {
  1219. const label: Label = new Label(combinedString, textAlignment);
  1220. label.fontStyle = style;
  1221. label.fontHeight = fontHeight;
  1222. // TODO_RR: TextHeight from first Entry
  1223. const graphLabel: GraphicalLabel = new GraphicalLabel(label, fontHeight, label.textAlignment, this.rules, staffLine.PositionAndShape);
  1224. const marginFactor: number = 1.1;
  1225. if (placement === PlacementEnum.Below) {
  1226. graphLabel.Label.textAlignment = TextAlignmentEnum.LeftTop;
  1227. }
  1228. graphLabel.setLabelPositionAndShapeBorders();
  1229. graphLabel.PositionAndShape.BorderMarginBottom *= marginFactor;
  1230. graphLabel.PositionAndShape.BorderMarginTop *= marginFactor;
  1231. graphLabel.PositionAndShape.BorderMarginLeft *= marginFactor;
  1232. graphLabel.PositionAndShape.BorderMarginRight *= marginFactor;
  1233. let left: number = relative.x + graphLabel.PositionAndShape.BorderMarginLeft;
  1234. let right: number = relative.x + graphLabel.PositionAndShape.BorderMarginRight;
  1235. // check if GraphicalLabel exceeds the StaffLine's borders.
  1236. if (right > staffLine.PositionAndShape.Size.width) {
  1237. right = staffLine.PositionAndShape.Size.width - this.rules.MeasureRightMargin;
  1238. left = right - graphLabel.PositionAndShape.MarginSize.width;
  1239. relative.x = left - graphLabel.PositionAndShape.BorderMarginLeft;
  1240. }
  1241. // find allowed position (where the Label can be positioned) from Sky- BottomLine
  1242. let drawingHeight: number;
  1243. const skyBottomLineCalculator: SkyBottomLineCalculator = staffLine.SkyBottomLineCalculator;
  1244. if (placement === PlacementEnum.Below) {
  1245. drawingHeight = skyBottomLineCalculator.getBottomLineMaxInRange(left, right);
  1246. } else {
  1247. drawingHeight = skyBottomLineCalculator.getSkyLineMinInRange(left, right);
  1248. }
  1249. // set RelativePosition
  1250. graphLabel.PositionAndShape.RelativePosition = new PointF2D(relative.x, drawingHeight);
  1251. // update Sky- BottomLine
  1252. if (placement === PlacementEnum.Below) {
  1253. skyBottomLineCalculator.updateBottomLineInRange(left, right, graphLabel.PositionAndShape.BorderMarginBottom + drawingHeight);
  1254. } else {
  1255. skyBottomLineCalculator.updateSkyLineInRange(left, right, graphLabel.PositionAndShape.BorderMarginTop + drawingHeight);
  1256. }
  1257. return graphLabel;
  1258. }
  1259. protected calculateTempoExpressionsForMultiTempoExpression(sourceMeasure: SourceMeasure, multiTempoExpression: MultiTempoExpression,
  1260. measureIndex: number): void {
  1261. // calculate absolute Timestamp
  1262. const absoluteTimestamp: Fraction = Fraction.plus(sourceMeasure.AbsoluteTimestamp, multiTempoExpression.Timestamp);
  1263. const measures: GraphicalMeasure[] = this.graphicalMusicSheet.MeasureList[measureIndex];
  1264. let relative: PointF2D = new PointF2D();
  1265. if (multiTempoExpression.ContinuousTempo || multiTempoExpression.InstantaneousTempo) {
  1266. // TempoExpressions always on the first visible System's StaffLine // TODO is it though?
  1267. if (this.rules.MinMeasureToDrawIndex > 0) {
  1268. return; // assuming that the tempo is always in measure 1 (idx 0), adding the expression causes issues when we don't draw measure 1
  1269. }
  1270. if (!measures[0]) {
  1271. return;
  1272. }
  1273. let staffLine: StaffLine = measures[0].ParentStaffLine;
  1274. let firstVisibleMeasureX: number = measures[0].PositionAndShape.RelativePosition.x;
  1275. let verticalIndex: number = 0;
  1276. for (let j: number = 0; j < measures.length; j++) {
  1277. if (!measures[j].ParentStaffLine || measures[j].ParentStaffLine.Measures.length === 0) {
  1278. continue;
  1279. }
  1280. if (measures[j].ParentStaffLine.Measures.length > 0) {
  1281. staffLine = measures[j].ParentStaffLine;
  1282. firstVisibleMeasureX = measures[j].PositionAndShape.RelativePosition.x;
  1283. verticalIndex = j;
  1284. break;
  1285. }
  1286. }
  1287. relative = this.getRelativePositionInStaffLineFromTimestamp(absoluteTimestamp,
  1288. verticalIndex,
  1289. staffLine,
  1290. staffLine.isPartOfMultiStaffInstrument(),
  1291. firstVisibleMeasureX);
  1292. // also placement Above
  1293. if (multiTempoExpression.EntriesList.length > 0 &&
  1294. multiTempoExpression.EntriesList[0].Expression instanceof InstantaneousTempoExpression) {
  1295. const instantaniousTempo: InstantaneousTempoExpression = (multiTempoExpression.EntriesList[0].Expression as InstantaneousTempoExpression);
  1296. instantaniousTempo.Placement = PlacementEnum.Above;
  1297. // if an InstantaniousTempoExpression exists at the very beginning then
  1298. // check if expression is positioned at first ever StaffEntry and
  1299. // check if MusicSystem is first MusicSystem
  1300. if (staffLine.Measures[0].staffEntries.length > 0 &&
  1301. Math.abs(relative.x - staffLine.Measures[0].staffEntries[0].PositionAndShape.RelativePosition.x) === 0 &&
  1302. staffLine.ParentMusicSystem === this.musicSystems[0]) {
  1303. const firstInstructionEntry: GraphicalStaffEntry = staffLine.Measures[0].FirstInstructionStaffEntry;
  1304. if (firstInstructionEntry) {
  1305. const lastInstruction: AbstractGraphicalInstruction = firstInstructionEntry.GraphicalInstructions.last();
  1306. relative.x = lastInstruction.PositionAndShape.RelativePosition.x;
  1307. }
  1308. if (this.rules.CompactMode) {
  1309. relative.x = staffLine.PositionAndShape.RelativePosition.x +
  1310. staffLine.Measures[0].PositionAndShape.RelativePosition.x;
  1311. }
  1312. }
  1313. }
  1314. // const addAtLastList: GraphicalObject[] = [];
  1315. for (const entry of multiTempoExpression.EntriesList) {
  1316. let textAlignment: TextAlignmentEnum = TextAlignmentEnum.CenterBottom;
  1317. if (this.rules.CompactMode) {
  1318. textAlignment = TextAlignmentEnum.LeftBottom;
  1319. }
  1320. const graphLabel: GraphicalLabel = this.calculateLabel(staffLine,
  1321. relative,
  1322. entry.label,
  1323. multiTempoExpression.getFontstyleOfFirstEntry(),
  1324. entry.Expression.Placement,
  1325. this.rules.UnknownTextHeight,
  1326. textAlignment);
  1327. if (entry.Expression instanceof InstantaneousTempoExpression) {
  1328. //already added?
  1329. for (const expr of staffLine.AbstractExpressions) {
  1330. if (expr instanceof GraphicalInstantaneousTempoExpression &&
  1331. (expr.SourceExpression as AbstractTempoExpression).Label === entry.Expression.Label) {
  1332. //already added
  1333. continue;
  1334. }
  1335. }
  1336. const graphicalTempoExpr: GraphicalInstantaneousTempoExpression = new GraphicalInstantaneousTempoExpression(entry.Expression, graphLabel);
  1337. if (!graphicalTempoExpr.ParentStaffLine) {
  1338. log.warn("Adding staffline didn't work");
  1339. // I am actually fooling the linter here and use the created object. This method needs refactoring,
  1340. // all graphical expression creations should be in one place and have basic stuff like labels, lines, ...
  1341. // in their constructor
  1342. }
  1343. // in case of metronome mark:
  1344. if (this.rules.MetronomeMarksDrawn) {
  1345. if ((entry.Expression as InstantaneousTempoExpression).Enum === TempoEnum.metronomeMark) {
  1346. this.createMetronomeMark((entry.Expression as InstantaneousTempoExpression));
  1347. continue;
  1348. }
  1349. }
  1350. } else if (entry.Expression instanceof ContinuousTempoExpression) {
  1351. // FIXME: Not yet implemented
  1352. // let alreadyAdded: boolean = false;
  1353. // for (const expr of staffLine.AbstractExpressions) {
  1354. // if (expr instanceof GraphicalContinuousTempoExpression &&
  1355. // expr.GetContinuousTempoExpression.Label === entry.Expression.Label) {
  1356. // alreadyAdded = true;
  1357. // }
  1358. // }
  1359. // if (alreadyAdded) {
  1360. // continue;
  1361. // }
  1362. // staffLine.AbstractExpressions.push(new GraphicalContinuousTempoExpression((ContinuousTempoExpression)(entry.Expression), graphLabel));
  1363. }
  1364. }
  1365. }
  1366. }
  1367. protected createMetronomeMark(metronomeExpression: InstantaneousTempoExpression): void {
  1368. throw new Error("abstract, not implemented");
  1369. }
  1370. protected graphicalMeasureCreatedCalculations(measure: GraphicalMeasure): void {
  1371. return;
  1372. }
  1373. protected clearSystemsAndMeasures(): void {
  1374. for (let idx: number = 0, len: number = this.graphicalMusicSheet.MusicPages.length; idx < len; ++idx) {
  1375. const graphicalMusicPage: GraphicalMusicPage = this.graphicalMusicSheet.MusicPages[idx];
  1376. for (let idx2: number = 0, len2: number = graphicalMusicPage.MusicSystems.length; idx2 < len2; ++idx2) {
  1377. const musicSystem: MusicSystem = graphicalMusicPage.MusicSystems[idx2];
  1378. for (let idx3: number = 0, len3: number = musicSystem.StaffLines.length; idx3 < len3; ++idx3) {
  1379. const staffLine: StaffLine = musicSystem.StaffLines[idx3];
  1380. for (let idx4: number = 0, len4: number = staffLine.Measures.length; idx4 < len4; ++idx4) {
  1381. const graphicalMeasure: GraphicalMeasure = staffLine.Measures[idx4];
  1382. if (graphicalMeasure.FirstInstructionStaffEntry) {
  1383. const index: number = graphicalMeasure.PositionAndShape.ChildElements.indexOf(
  1384. graphicalMeasure.FirstInstructionStaffEntry.PositionAndShape
  1385. );
  1386. if (index > -1) {
  1387. graphicalMeasure.PositionAndShape.ChildElements.splice(index, 1);
  1388. }
  1389. graphicalMeasure.FirstInstructionStaffEntry = undefined;
  1390. graphicalMeasure.beginInstructionsWidth = 0.0;
  1391. }
  1392. if (graphicalMeasure.LastInstructionStaffEntry) {
  1393. const index: number = graphicalMeasure.PositionAndShape.ChildElements.indexOf(
  1394. graphicalMeasure.LastInstructionStaffEntry.PositionAndShape
  1395. );
  1396. if (index > -1) {
  1397. graphicalMeasure.PositionAndShape.ChildElements.splice(index, 1);
  1398. }
  1399. graphicalMeasure.LastInstructionStaffEntry = undefined;
  1400. graphicalMeasure.endInstructionsWidth = 0.0;
  1401. }
  1402. }
  1403. staffLine.Measures = [];
  1404. staffLine.PositionAndShape.ChildElements = [];
  1405. }
  1406. musicSystem.StaffLines.length = 0;
  1407. musicSystem.PositionAndShape.ChildElements = [];
  1408. }
  1409. graphicalMusicPage.MusicSystems = [];
  1410. graphicalMusicPage.PositionAndShape.ChildElements = [];
  1411. }
  1412. this.graphicalMusicSheet.MusicPages = [];
  1413. }
  1414. protected handleVoiceEntry(voiceEntry: VoiceEntry, graphicalStaffEntry: GraphicalStaffEntry,
  1415. accidentalCalculator: AccidentalCalculator, openLyricWords: LyricWord[],
  1416. activeClef: ClefInstruction,
  1417. openTuplets: Tuplet[], openBeams: Beam[],
  1418. octaveShiftValue: OctaveEnum, staffIndex: number,
  1419. linkedNotes: Note[] = undefined,
  1420. sourceStaffEntry: SourceStaffEntry = undefined): OctaveEnum {
  1421. if (voiceEntry.StemDirectionXml !== StemDirectionType.Undefined &&
  1422. this.rules.SetWantedStemDirectionByXml &&
  1423. voiceEntry.StemDirectionXml !== undefined) {
  1424. voiceEntry.WantedStemDirection = voiceEntry.StemDirectionXml;
  1425. } else {
  1426. this.calculateStemDirectionFromVoices(voiceEntry);
  1427. }
  1428. // if GraphicalStaffEntry has been created earlier (because of Tie), then the GraphicalNotesLists have also been created
  1429. const gve: GraphicalVoiceEntry = graphicalStaffEntry.findOrCreateGraphicalVoiceEntry(voiceEntry);
  1430. gve.octaveShiftValue = octaveShiftValue;
  1431. // check for Tabs:
  1432. const tabStaffEntry: GraphicalStaffEntry = graphicalStaffEntry.tabStaffEntry;
  1433. let graphicalTabVoiceEntry: GraphicalVoiceEntry;
  1434. if (tabStaffEntry) {
  1435. graphicalTabVoiceEntry = tabStaffEntry.findOrCreateGraphicalVoiceEntry(voiceEntry);
  1436. }
  1437. for (let idx: number = 0, len: number = voiceEntry.Notes.length; idx < len; ++idx) {
  1438. const note: Note = voiceEntry.Notes[idx];
  1439. if (!note) {
  1440. continue;
  1441. }
  1442. if (sourceStaffEntry !== undefined && sourceStaffEntry.Link !== undefined && linkedNotes !== undefined && linkedNotes.indexOf(note) > -1) {
  1443. continue;
  1444. }
  1445. let graphicalNote: GraphicalNote;
  1446. if (voiceEntry.IsGrace) {
  1447. graphicalNote = MusicSheetCalculator.symbolFactory.createGraceNote(note, gve, activeClef, octaveShiftValue);
  1448. } else {
  1449. graphicalNote = MusicSheetCalculator.symbolFactory.createNote(note, gve, activeClef, octaveShiftValue, undefined);
  1450. MusicSheetCalculator.stafflineNoteCalculator.trackNote(graphicalNote);
  1451. }
  1452. if (note.Pitch) {
  1453. this.checkNoteForAccidental(graphicalNote, accidentalCalculator, activeClef, octaveShiftValue);
  1454. }
  1455. this.resetYPositionForLeadSheet(graphicalNote.PositionAndShape);
  1456. graphicalStaffEntry.addGraphicalNoteToListAtCorrectYPosition(gve, graphicalNote);
  1457. graphicalNote.PositionAndShape.calculateBoundingBox();
  1458. if (!this.leadSheet) {
  1459. if (note.NoteBeam !== undefined && note.PrintObject) {
  1460. this.handleBeam(graphicalNote, note.NoteBeam, openBeams);
  1461. }
  1462. if (note.NoteTuplet !== undefined && note.PrintObject) {
  1463. this.handleTuplet(graphicalNote, note.NoteTuplet, openTuplets);
  1464. }
  1465. }
  1466. // handle TabNotes:
  1467. if (graphicalTabVoiceEntry) {
  1468. // notes should be either TabNotes or RestNotes -> add all:
  1469. const graphicalTabNote: GraphicalNote = MusicSheetCalculator.symbolFactory.createNote( note,
  1470. graphicalTabVoiceEntry,
  1471. activeClef,
  1472. octaveShiftValue,
  1473. undefined);
  1474. tabStaffEntry.addGraphicalNoteToListAtCorrectYPosition(graphicalTabVoiceEntry, graphicalTabNote);
  1475. graphicalTabNote.PositionAndShape.calculateBoundingBox();
  1476. if (!this.leadSheet) {
  1477. if (note.NoteTuplet) {
  1478. this.handleTuplet(graphicalTabNote, note.NoteTuplet, openTuplets);
  1479. }
  1480. }
  1481. }
  1482. }
  1483. if (voiceEntry.Articulations.length > 0) {
  1484. this.handleVoiceEntryArticulations(voiceEntry.Articulations, voiceEntry, graphicalStaffEntry);
  1485. }
  1486. if (voiceEntry.TechnicalInstructions.length > 0) {
  1487. this.handleVoiceEntryTechnicalInstructions(voiceEntry.TechnicalInstructions, voiceEntry, graphicalStaffEntry);
  1488. }
  1489. if (voiceEntry.LyricsEntries.size() > 0) {
  1490. this.handleVoiceEntryLyrics(voiceEntry, graphicalStaffEntry, openLyricWords);
  1491. }
  1492. if (voiceEntry.OrnamentContainer) {
  1493. this.handleVoiceEntryOrnaments(voiceEntry.OrnamentContainer, voiceEntry, graphicalStaffEntry);
  1494. }
  1495. return octaveShiftValue;
  1496. }
  1497. protected resetYPositionForLeadSheet(psi: BoundingBox): void {
  1498. if (this.leadSheet) {
  1499. psi.RelativePosition = new PointF2D(psi.RelativePosition.x, 0.0);
  1500. }
  1501. }
  1502. protected layoutVoiceEntries(graphicalStaffEntry: GraphicalStaffEntry, staffIndex: number): void {
  1503. graphicalStaffEntry.PositionAndShape.RelativePosition = new PointF2D(0.0, 0.0);
  1504. if (!this.leadSheet) {
  1505. for (const gve of graphicalStaffEntry.graphicalVoiceEntries) {
  1506. const graphicalNotes: GraphicalNote[] = gve.notes;
  1507. if (graphicalNotes.length === 0) {
  1508. continue;
  1509. }
  1510. const voiceEntry: VoiceEntry = graphicalNotes[0].sourceNote.ParentVoiceEntry;
  1511. const hasPitchedNote: boolean = graphicalNotes[0].sourceNote.Pitch !== undefined;
  1512. this.layoutVoiceEntry(voiceEntry, graphicalNotes, graphicalStaffEntry, hasPitchedNote);
  1513. }
  1514. }
  1515. }
  1516. protected maxInstrNameLabelLength(): number {
  1517. let maxLabelLength: number = 0.0;
  1518. for (const instrument of this.graphicalMusicSheet.ParentMusicSheet.Instruments) {
  1519. if (instrument.NameLabel?.print && instrument.Voices.length > 0 && instrument.Voices[0].Visible) {
  1520. let renderedLabel: Label = instrument.NameLabel;
  1521. if (!this.rules.RenderPartNames) {
  1522. renderedLabel = new Label("", renderedLabel.textAlignment, renderedLabel.font);
  1523. }
  1524. const graphicalLabel: GraphicalLabel = new GraphicalLabel(
  1525. renderedLabel, this.rules.InstrumentLabelTextHeight, TextAlignmentEnum.LeftCenter, this.rules);
  1526. graphicalLabel.setLabelPositionAndShapeBorders();
  1527. maxLabelLength = Math.max(maxLabelLength, graphicalLabel.PositionAndShape.MarginSize.width);
  1528. }
  1529. }
  1530. if (!this.rules.RenderPartNames) {
  1531. return 0;
  1532. }
  1533. return maxLabelLength;
  1534. }
  1535. protected calculateSheetLabelBoundingBoxes(): void {
  1536. const musicSheet: MusicSheet = this.graphicalMusicSheet.ParentMusicSheet;
  1537. const defaultColorTitle: string = this.rules.DefaultColorTitle; // can be undefined => black
  1538. if (musicSheet.Title !== undefined && this.rules.RenderTitle) {
  1539. const title: GraphicalLabel = new GraphicalLabel(musicSheet.Title, this.rules.SheetTitleHeight, TextAlignmentEnum.CenterBottom, this.rules);
  1540. title.Label.colorDefault = defaultColorTitle;
  1541. this.graphicalMusicSheet.Title = title;
  1542. title.setLabelPositionAndShapeBorders();
  1543. } else if (!this.rules.RenderTitle) {
  1544. this.graphicalMusicSheet.Title = undefined; // clear label if rendering it was disabled after last render
  1545. }
  1546. if (musicSheet.Subtitle !== undefined && this.rules.RenderSubtitle) {
  1547. const subtitle: GraphicalLabel = new GraphicalLabel(
  1548. musicSheet.Subtitle, this.rules.SheetSubtitleHeight, TextAlignmentEnum.CenterCenter, this.rules);
  1549. subtitle.Label.colorDefault = defaultColorTitle;
  1550. this.graphicalMusicSheet.Subtitle = subtitle;
  1551. subtitle.setLabelPositionAndShapeBorders();
  1552. } else if (!this.rules.RenderSubtitle) {
  1553. this.graphicalMusicSheet.Subtitle = undefined;
  1554. }
  1555. if (musicSheet.Composer !== undefined && this.rules.RenderComposer) {
  1556. const composer: GraphicalLabel = new GraphicalLabel(
  1557. musicSheet.Composer, this.rules.SheetComposerHeight, TextAlignmentEnum.RightCenter, this.rules);
  1558. composer.Label.colorDefault = defaultColorTitle;
  1559. this.graphicalMusicSheet.Composer = composer;
  1560. composer.setLabelPositionAndShapeBorders();
  1561. } else if (!this.rules.RenderComposer) {
  1562. this.graphicalMusicSheet.Composer = undefined;
  1563. }
  1564. if (musicSheet.Lyricist !== undefined && this.rules.RenderLyricist) {
  1565. const lyricist: GraphicalLabel = new GraphicalLabel(
  1566. musicSheet.Lyricist, this.rules.SheetAuthorHeight, TextAlignmentEnum.LeftCenter, this.rules);
  1567. lyricist.Label.colorDefault = defaultColorTitle;
  1568. this.graphicalMusicSheet.Lyricist = lyricist;
  1569. lyricist.setLabelPositionAndShapeBorders();
  1570. } else if (!this.rules.RenderLyricist) {
  1571. this.graphicalMusicSheet.Lyricist = undefined;
  1572. }
  1573. }
  1574. protected checkMeasuresForWholeRestNotes(): void {
  1575. for (let idx2: number = 0, len2: number = this.musicSystems.length; idx2 < len2; ++idx2) {
  1576. const musicSystem: MusicSystem = this.musicSystems[idx2];
  1577. for (let idx3: number = 0, len3: number = musicSystem.StaffLines.length; idx3 < len3; ++idx3) {
  1578. const staffLine: StaffLine = musicSystem.StaffLines[idx3];
  1579. for (let idx4: number = 0, len4: number = staffLine.Measures.length; idx4 < len4; ++idx4) {
  1580. const measure: GraphicalMeasure = staffLine.Measures[idx4];
  1581. if (measure.staffEntries.length === 1) {
  1582. const gse: GraphicalStaffEntry = measure.staffEntries[0];
  1583. if (gse.graphicalVoiceEntries.length > 0 && gse.graphicalVoiceEntries[0].notes.length === 1) {
  1584. const graphicalNote: GraphicalNote = gse.graphicalVoiceEntries[0].notes[0];
  1585. if (!graphicalNote.sourceNote.Pitch && (new Fraction(1, 2)).lt(graphicalNote.sourceNote.Length)) {
  1586. this.layoutMeasureWithWholeRest(graphicalNote, gse, measure);
  1587. }
  1588. }
  1589. }
  1590. }
  1591. }
  1592. }
  1593. }
  1594. protected optimizeRestNotePlacement(graphicalStaffEntry: GraphicalStaffEntry, measure: GraphicalMeasure): void {
  1595. if (graphicalStaffEntry.graphicalVoiceEntries.length === 0) {
  1596. return;
  1597. }
  1598. const voice1Notes: GraphicalNote[] = graphicalStaffEntry.graphicalVoiceEntries[0].notes;
  1599. if (voice1Notes.length === 0) {
  1600. return;
  1601. }
  1602. const voice1Note1: GraphicalNote = voice1Notes[0];
  1603. const voice1Note1IsRest: boolean = voice1Note1.sourceNote.isRest();
  1604. if (graphicalStaffEntry.graphicalVoiceEntries.length === 2) {
  1605. let voice2Note1IsRest: boolean = false;
  1606. const voice2Notes: GraphicalNote[] = graphicalStaffEntry.graphicalVoiceEntries[1].notes;
  1607. if (voice2Notes.length > 0) {
  1608. const voice2Note1: GraphicalNote = voice2Notes[0];
  1609. voice2Note1IsRest = voice2Note1.sourceNote.isRest();
  1610. }
  1611. if (voice1Note1IsRest && voice2Note1IsRest) {
  1612. this.calculateTwoRestNotesPlacementWithCollisionDetection(graphicalStaffEntry);
  1613. } else if (voice1Note1IsRest || voice2Note1IsRest) {
  1614. this.calculateRestNotePlacementWithCollisionDetectionFromGraphicalNote(graphicalStaffEntry);
  1615. }
  1616. } else if (voice1Note1IsRest && graphicalStaffEntry !== measure.staffEntries[0] &&
  1617. graphicalStaffEntry !== measure.staffEntries[measure.staffEntries.length - 1]) {
  1618. const staffEntryIndex: number = measure.staffEntries.indexOf(graphicalStaffEntry);
  1619. const previousStaffEntry: GraphicalStaffEntry = measure.staffEntries[staffEntryIndex - 1];
  1620. const nextStaffEntry: GraphicalStaffEntry = measure.staffEntries[staffEntryIndex + 1];
  1621. if (previousStaffEntry.graphicalVoiceEntries.length === 1) {
  1622. const previousNote: GraphicalNote = previousStaffEntry.graphicalVoiceEntries[0].notes[0];
  1623. if (previousNote.sourceNote.NoteBeam !== undefined && nextStaffEntry.graphicalVoiceEntries.length === 1) {
  1624. const nextNote: GraphicalNote = nextStaffEntry.graphicalVoiceEntries[0].notes[0];
  1625. if (nextNote.sourceNote.NoteBeam !== undefined && previousNote.sourceNote.NoteBeam === nextNote.sourceNote.NoteBeam) {
  1626. this.calculateRestNotePlacementWithinGraphicalBeam(
  1627. graphicalStaffEntry, voice1Note1, previousNote,
  1628. nextStaffEntry, nextNote
  1629. );
  1630. graphicalStaffEntry.PositionAndShape.calculateBoundingBox();
  1631. }
  1632. }
  1633. }
  1634. }
  1635. }
  1636. protected getRelativePositionInStaffLineFromTimestamp(timestamp: Fraction, verticalIndex: number, staffLine: StaffLine,
  1637. multiStaffInstrument: boolean, firstVisibleMeasureRelativeX: number = 0.0): PointF2D {
  1638. let relative: PointF2D = new PointF2D();
  1639. let leftStaffEntry: GraphicalStaffEntry = undefined;
  1640. let rightStaffEntry: GraphicalStaffEntry = undefined;
  1641. const numEntries: number = this.graphicalMusicSheet.VerticalGraphicalStaffEntryContainers.length;
  1642. const index: number = this.graphicalMusicSheet.GetInterpolatedIndexInVerticalContainers(timestamp);
  1643. const leftIndex: number = Math.min(Math.floor(index), numEntries - 1);
  1644. const rightIndex: number = Math.min(Math.ceil(index), numEntries - 1);
  1645. if (leftIndex < 0 || verticalIndex < 0) {
  1646. return relative;
  1647. }
  1648. leftStaffEntry = this.getFirstLeftNotNullStaffEntryFromContainer(leftIndex, verticalIndex, multiStaffInstrument);
  1649. rightStaffEntry = this.getFirstRightNotNullStaffEntryFromContainer(rightIndex, verticalIndex, multiStaffInstrument);
  1650. if (leftStaffEntry && rightStaffEntry) {
  1651. let measureRelativeX: number = leftStaffEntry.parentMeasure.PositionAndShape.RelativePosition.x;
  1652. if (firstVisibleMeasureRelativeX > 0) {
  1653. measureRelativeX = firstVisibleMeasureRelativeX;
  1654. }
  1655. let leftX: number = leftStaffEntry.PositionAndShape.RelativePosition.x + measureRelativeX;
  1656. let rightX: number = rightStaffEntry.PositionAndShape.RelativePosition.x + rightStaffEntry.parentMeasure.PositionAndShape.RelativePosition.x;
  1657. if (firstVisibleMeasureRelativeX > 0) {
  1658. rightX = rightStaffEntry.PositionAndShape.RelativePosition.x + measureRelativeX;
  1659. }
  1660. let timestampQuotient: number = 0.0;
  1661. if (leftStaffEntry !== rightStaffEntry) {
  1662. const leftTimestamp: Fraction = leftStaffEntry.getAbsoluteTimestamp();
  1663. const rightTimestamp: Fraction = rightStaffEntry.getAbsoluteTimestamp();
  1664. const leftDifference: Fraction = Fraction.minus(timestamp, leftTimestamp);
  1665. timestampQuotient = leftDifference.RealValue / Fraction.minus(rightTimestamp, leftTimestamp).RealValue;
  1666. }
  1667. if (leftStaffEntry.parentMeasure.ParentStaffLine !== rightStaffEntry.parentMeasure.ParentStaffLine) {
  1668. if (leftStaffEntry.parentMeasure.ParentStaffLine === staffLine) {
  1669. rightX = staffLine.PositionAndShape.Size.width;
  1670. } else {
  1671. leftX = staffLine.PositionAndShape.RelativePosition.x;
  1672. }
  1673. }
  1674. relative = new PointF2D(leftX + (rightX - leftX) * timestampQuotient, 0.0);
  1675. }
  1676. return relative;
  1677. }
  1678. protected getRelativeXPositionFromTimestamp(timestamp: Fraction): number {
  1679. const numEntries: number = this.graphicalMusicSheet.VerticalGraphicalStaffEntryContainers.length;
  1680. const index: number = this.graphicalMusicSheet.GetInterpolatedIndexInVerticalContainers(timestamp);
  1681. const discreteIndex: number = Math.max(0, Math.min(Math.round(index), numEntries - 1));
  1682. const gse: GraphicalStaffEntry = this.graphicalMusicSheet.VerticalGraphicalStaffEntryContainers[discreteIndex].getFirstNonNullStaffEntry();
  1683. const posX: number = gse.PositionAndShape.RelativePosition.x + gse.parentMeasure.PositionAndShape.RelativePosition.x;
  1684. return posX;
  1685. }
  1686. protected calculatePageLabels(page: GraphicalMusicPage): void {
  1687. if (this.rules.RenderSingleHorizontalStaffline) {
  1688. page.PositionAndShape.BorderRight = page.PositionAndShape.Size.width;
  1689. page.PositionAndShape.calculateBoundingBox();
  1690. this.graphicalMusicSheet.ParentMusicSheet.pageWidth = page.PositionAndShape.Size.width;
  1691. }
  1692. // The PositionAndShape child elements of page need to be manually connected to the lyricist, composer, subtitle, etc.
  1693. // because the page is only available now
  1694. let firstSystemAbsoluteTopMargin: number = 10;
  1695. if (page.MusicSystems.length > 0) {
  1696. const firstMusicSystem: MusicSystem = page.MusicSystems[0];
  1697. firstSystemAbsoluteTopMargin = firstMusicSystem.PositionAndShape.RelativePosition.y + firstMusicSystem.PositionAndShape.BorderTop;
  1698. }
  1699. //const firstStaffLine: StaffLine = this.graphicalMusicSheet.MusicPages[0].MusicSystems[0].StaffLines[0];
  1700. if (this.graphicalMusicSheet.Title) {
  1701. const title: GraphicalLabel = this.graphicalMusicSheet.Title;
  1702. title.PositionAndShape.Parent = page.PositionAndShape;
  1703. //title.PositionAndShape.Parent = firstStaffLine.PositionAndShape;
  1704. const relative: PointF2D = new PointF2D();
  1705. relative.x = this.graphicalMusicSheet.ParentMusicSheet.pageWidth / 2;
  1706. //relative.x = firstStaffLine.PositionAndShape.RelativePosition.x + firstStaffLine.PositionAndShape.Size.width / 2; // half of first staffline width
  1707. relative.y = this.rules.TitleTopDistance + this.rules.SheetTitleHeight;
  1708. title.PositionAndShape.RelativePosition = relative;
  1709. page.Labels.push(title);
  1710. }
  1711. if (this.graphicalMusicSheet.Subtitle) {
  1712. const subtitle: GraphicalLabel = this.graphicalMusicSheet.Subtitle;
  1713. //subtitle.PositionAndShape.Parent = firstStaffLine.PositionAndShape;
  1714. subtitle.PositionAndShape.Parent = page.PositionAndShape;
  1715. const relative: PointF2D = new PointF2D();
  1716. relative.x = this.graphicalMusicSheet.ParentMusicSheet.pageWidth / 2;
  1717. //relative.x = firstStaffLine.PositionAndShape.RelativePosition.x + firstStaffLine.PositionAndShape.Size.width / 2; // half of first staffline width
  1718. relative.y = this.rules.TitleTopDistance + this.rules.SheetTitleHeight + this.rules.SheetMinimumDistanceBetweenTitleAndSubtitle;
  1719. subtitle.PositionAndShape.RelativePosition = relative;
  1720. page.Labels.push(subtitle);
  1721. }
  1722. //Get the first system, first staffline skybottomcalculator
  1723. const topStaffline: StaffLine = page.MusicSystems[0].StaffLines[0];
  1724. const skyBottomLineCalculator: SkyBottomLineCalculator = topStaffline.SkyBottomLineCalculator;
  1725. const composer: GraphicalLabel = this.graphicalMusicSheet.Composer;
  1726. if (composer) {
  1727. composer.PositionAndShape.Parent = page.PositionAndShape; // if using pageWidth. (which can currently be too wide) TODO fix pageWidth (#578)
  1728. //composer.PositionAndShape.Parent = firstStaffLine.PositionAndShape; if using firstStaffLine...width.
  1729. // y-collision problems, harder to y-align with lyrics
  1730. composer.setLabelPositionAndShapeBorders();
  1731. const relative: PointF2D = new PointF2D();
  1732. //const firstStaffLineEndX: number = this.rules.PageLeftMargin + this.rules.SystemLeftMargin + this.rules.left
  1733. // firstStaffLine.PositionAndShape.RelativePosition.x + firstStaffLine.PositionAndShape.Size.width;
  1734. //relative.x = Math.min(this.graphicalMusicSheet.ParentMusicSheet.pageWidth - this.rules.PageRightMargin,
  1735. // firstStaffLineEndX); // awkward with 2-bar score
  1736. relative.x = this.graphicalMusicSheet.ParentMusicSheet.pageWidth - this.rules.PageRightMargin;
  1737. //relative.x = firstStaffLine.PositionAndShape.Size.width;
  1738. //when this is less, goes higher.
  1739. //So 0 is top of the sheet, 22 or so is touching the music system margin
  1740. relative.y = firstSystemAbsoluteTopMargin;
  1741. //relative.y = - this.rules.SystemComposerDistance;
  1742. //relative.y = -firstStaffLine.PositionAndShape.Size.height;
  1743. // TODO only add measure label height if rendering labels and composer measure has label
  1744. // TODO y-align with lyricist? which is harder if they have different bbox parents (page and firstStaffLine).
  1745. // when the pageWidth gets fixed, we could use page as parent again.
  1746. if (!composer.TextLines || composer.TextLines?.length === 1) {
  1747. //Don't want to affect existing behavior
  1748. relative.y -= this.rules.SystemComposerDistance;
  1749. } else {
  1750. //Sufficient for now to just use the longest composer entry instead of bottom.
  1751. //Otherwise we need to construct a 'bottom line' for the text block
  1752. const endX: number = topStaffline.PositionAndShape.BorderMarginRight;
  1753. const startX: number = endX - composer.PositionAndShape.Size.width;
  1754. const currentMin: number = skyBottomLineCalculator.getSkyLineMinInRange(startX, endX);
  1755. relative.y += currentMin - composer.PositionAndShape.BorderBottom;
  1756. skyBottomLineCalculator.updateSkyLineInRange(startX, endX, currentMin - composer.PositionAndShape.MarginSize.height);
  1757. }
  1758. composer.PositionAndShape.RelativePosition = relative;
  1759. page.Labels.push(composer);
  1760. }
  1761. const lyricist: GraphicalLabel = this.graphicalMusicSheet.Lyricist;
  1762. if (lyricist) {
  1763. lyricist.PositionAndShape.Parent = page.PositionAndShape;
  1764. lyricist.setLabelPositionAndShapeBorders();
  1765. const relative: PointF2D = new PointF2D();
  1766. relative.x = this.rules.PageLeftMargin;
  1767. relative.y = firstSystemAbsoluteTopMargin;
  1768. if (!lyricist.TextLines || lyricist.TextLines?.length === 1) {
  1769. relative.y -= this.rules.SystemComposerDistance;
  1770. } else {
  1771. const startX: number = topStaffline.PositionAndShape.BorderMarginLeft - relative.x;
  1772. const endX: number = startX + lyricist.PositionAndShape.Size.width;
  1773. const currentMin: number = skyBottomLineCalculator.getSkyLineMinInRange(startX, endX);
  1774. relative.y += currentMin - lyricist.PositionAndShape.BorderBottom;
  1775. skyBottomLineCalculator.updateSkyLineInRange(startX, endX, currentMin - lyricist.PositionAndShape.MarginSize.height);
  1776. }
  1777. //relative.y = Math.max(relative.y, composer.PositionAndShape.RelativePosition.y);
  1778. lyricist.PositionAndShape.RelativePosition = relative;
  1779. page.Labels.push(lyricist);
  1780. }
  1781. }
  1782. protected createGraphicalTies(): void {
  1783. for (let measureIndex: number = 0; measureIndex < this.graphicalMusicSheet.ParentMusicSheet.SourceMeasures.length; measureIndex++) {
  1784. const sourceMeasure: SourceMeasure = this.graphicalMusicSheet.ParentMusicSheet.SourceMeasures[measureIndex];
  1785. for (let staffIndex: number = 0; staffIndex < sourceMeasure.CompleteNumberOfStaves; staffIndex++) {
  1786. for (let j: number = 0; j < sourceMeasure.VerticalSourceStaffEntryContainers.length; j++) {
  1787. const sourceStaffEntry: SourceStaffEntry = sourceMeasure.VerticalSourceStaffEntryContainers[j].StaffEntries[staffIndex];
  1788. if (sourceStaffEntry) {
  1789. const startStaffEntry: GraphicalStaffEntry = this.graphicalMusicSheet.findGraphicalStaffEntryFromMeasureList(
  1790. staffIndex, measureIndex, sourceStaffEntry
  1791. );
  1792. for (let idx: number = 0, len: number = sourceStaffEntry.VoiceEntries.length; idx < len; ++idx) {
  1793. const voiceEntry: VoiceEntry = sourceStaffEntry.VoiceEntries[idx];
  1794. for (let idx2: number = 0, len2: number = voiceEntry.Notes.length; idx2 < len2; ++idx2) {
  1795. const note: Note = voiceEntry.Notes[idx2];
  1796. if (note.NoteTie) {
  1797. const tie: Tie = note.NoteTie;
  1798. this.handleTie(tie, startStaffEntry, staffIndex, measureIndex);
  1799. }
  1800. }
  1801. }
  1802. }
  1803. }
  1804. }
  1805. }
  1806. }
  1807. private handleTie(tie: Tie, startGraphicalStaffEntry: GraphicalStaffEntry, staffIndex: number, measureIndex: number): void {
  1808. let startGse: GraphicalStaffEntry = startGraphicalStaffEntry;
  1809. let startNote: GraphicalNote = undefined;
  1810. let endGse: GraphicalStaffEntry = undefined;
  1811. let endNote: GraphicalNote = undefined;
  1812. for (let i: number = 1; i < tie.Notes.length; i++) {
  1813. startNote = startGse.findTieGraphicalNoteFromNote(tie.Notes[i - 1]);
  1814. endGse = this.graphicalMusicSheet.GetGraphicalFromSourceStaffEntry(tie.Notes[i].ParentStaffEntry);
  1815. if (!endGse) {
  1816. continue;
  1817. }
  1818. endNote = endGse.findTieGraphicalNoteFromNote(tie.Notes[i]);
  1819. if (startNote !== undefined && endNote !== undefined && endGse) {
  1820. if (!startNote.sourceNote.PrintObject || !endNote.sourceNote.PrintObject) {
  1821. continue;
  1822. }
  1823. const graphicalTie: GraphicalTie = this.createGraphicalTie(tie, startGse, endGse, startNote, endNote);
  1824. startGse.GraphicalTies.push(graphicalTie);
  1825. if (this.staffEntriesWithGraphicalTies.indexOf(startGse) >= 0) {
  1826. this.staffEntriesWithGraphicalTies.push(startGse);
  1827. }
  1828. }
  1829. startGse = endGse;
  1830. }
  1831. }
  1832. private createAccidentalCalculators(): AccidentalCalculator[] {
  1833. const accidentalCalculators: AccidentalCalculator[] = [];
  1834. const firstSourceMeasure: SourceMeasure = this.graphicalMusicSheet.ParentMusicSheet.getFirstSourceMeasure();
  1835. if (firstSourceMeasure) {
  1836. for (let i: number = 0; i < firstSourceMeasure.CompleteNumberOfStaves; i++) {
  1837. const accidentalCalculator: AccidentalCalculator = new AccidentalCalculator();
  1838. accidentalCalculators.push(accidentalCalculator);
  1839. if (firstSourceMeasure.FirstInstructionsStaffEntries[i]) {
  1840. for (let idx: number = 0, len: number = firstSourceMeasure.FirstInstructionsStaffEntries[i].Instructions.length; idx < len; ++idx) {
  1841. const abstractNotationInstruction: AbstractNotationInstruction = firstSourceMeasure.FirstInstructionsStaffEntries[i].Instructions[idx];
  1842. if (abstractNotationInstruction instanceof KeyInstruction) {
  1843. const keyInstruction: KeyInstruction = <KeyInstruction>abstractNotationInstruction;
  1844. accidentalCalculator.ActiveKeyInstruction = keyInstruction;
  1845. }
  1846. }
  1847. }
  1848. }
  1849. }
  1850. return accidentalCalculators;
  1851. }
  1852. private calculateVerticalContainersList(): void {
  1853. const numberOfEntries: number = this.graphicalMusicSheet.MeasureList[0].length;
  1854. for (let i: number = 0; i < this.graphicalMusicSheet.MeasureList.length; i++) {
  1855. for (let j: number = 0; j < numberOfEntries; j++) {
  1856. const measure: GraphicalMeasure = this.graphicalMusicSheet.MeasureList[i][j];
  1857. if (!measure) {
  1858. continue;
  1859. }
  1860. for (let idx: number = 0, len: number = measure.staffEntries.length; idx < len; ++idx) {
  1861. const graphicalStaffEntry: GraphicalStaffEntry = measure.staffEntries[idx];
  1862. const verticalContainer: VerticalGraphicalStaffEntryContainer =
  1863. this.graphicalMusicSheet.getOrCreateVerticalContainer(graphicalStaffEntry.getAbsoluteTimestamp());
  1864. if (verticalContainer) {
  1865. verticalContainer.StaffEntries[j] = graphicalStaffEntry;
  1866. graphicalStaffEntry.parentVerticalContainer = verticalContainer;
  1867. }
  1868. }
  1869. }
  1870. }
  1871. }
  1872. private setIndicesToVerticalGraphicalContainers(): void {
  1873. for (let i: number = 0; i < this.graphicalMusicSheet.VerticalGraphicalStaffEntryContainers.length; i++) {
  1874. this.graphicalMusicSheet.VerticalGraphicalStaffEntryContainers[i].Index = i;
  1875. }
  1876. }
  1877. private createGraphicalMeasuresForSourceMeasure(sourceMeasure: SourceMeasure, accidentalCalculators: AccidentalCalculator[],
  1878. openLyricWords: LyricWord[],
  1879. openOctaveShifts: OctaveShiftParams[], activeClefs: ClefInstruction[]): GraphicalMeasure[] {
  1880. this.initGraphicalMeasuresCreation();
  1881. const verticalMeasureList: GraphicalMeasure[] = []; // (VexFlowMeasure, extends GraphicalMeasure)
  1882. const openBeams: Beam[] = [];
  1883. const openTuplets: Tuplet[] = [];
  1884. const staffEntryLinks: StaffEntryLink[] = [];
  1885. let restInAllGraphicalMeasures: boolean = true;
  1886. for (let staffIndex: number = 0; staffIndex < sourceMeasure.CompleteNumberOfStaves; staffIndex++) {
  1887. const measure: GraphicalMeasure = this.createGraphicalMeasure( // (VexFlowMeasure)
  1888. sourceMeasure, openTuplets, openBeams,
  1889. accidentalCalculators[staffIndex], activeClefs, openOctaveShifts, openLyricWords, staffIndex, staffEntryLinks
  1890. );
  1891. restInAllGraphicalMeasures = restInAllGraphicalMeasures && measure.hasOnlyRests;
  1892. verticalMeasureList.push(measure);
  1893. }
  1894. sourceMeasure.allRests = restInAllGraphicalMeasures;
  1895. sourceMeasure.VerticalMeasureList = verticalMeasureList; // much easier way to link sourceMeasure to graphicalMeasures than Dictionary
  1896. //this.graphicalMusicSheet.sourceToGraphicalMeasureLinks.setValue(sourceMeasure, verticalMeasureList); // overwrites entries because:
  1897. //this.graphicalMusicSheet.sourceToGraphicalMeasureLinks[sourceMeasure] = verticalMeasureList; // can't use SourceMeasure as key.
  1898. // to save the reference by dictionary we would need two Dictionaries, id -> sourceMeasure and id -> GraphicalMeasure.
  1899. return verticalMeasureList;
  1900. }
  1901. private createGraphicalMeasure(sourceMeasure: SourceMeasure, openTuplets: Tuplet[], openBeams: Beam[],
  1902. accidentalCalculator: AccidentalCalculator, activeClefs: ClefInstruction[],
  1903. openOctaveShifts: OctaveShiftParams[], openLyricWords: LyricWord[], staffIndex: number,
  1904. staffEntryLinks: StaffEntryLink[]): GraphicalMeasure {
  1905. const staff: Staff = this.graphicalMusicSheet.ParentMusicSheet.getStaffFromIndex(staffIndex);
  1906. let measure: GraphicalMeasure = undefined;
  1907. if (activeClefs[staffIndex].ClefType === ClefEnum.TAB) {
  1908. staff.isTab = true;
  1909. measure = MusicSheetCalculator.symbolFactory.createTabStaffMeasure(sourceMeasure, staff);
  1910. } else if (sourceMeasure.multipleRestMeasures && this.rules.RenderMultipleRestMeasures) {
  1911. measure = MusicSheetCalculator.symbolFactory.createMultiRestMeasure(sourceMeasure, staff);
  1912. } else {
  1913. measure = MusicSheetCalculator.symbolFactory.createGraphicalMeasure(sourceMeasure, staff);
  1914. }
  1915. measure.hasError = sourceMeasure.getErrorInMeasure(staffIndex);
  1916. // check for key instruction changes
  1917. if (sourceMeasure.FirstInstructionsStaffEntries[staffIndex]) {
  1918. for (let idx: number = 0, len: number = sourceMeasure.FirstInstructionsStaffEntries[staffIndex].Instructions.length; idx < len; ++idx) {
  1919. const instruction: AbstractNotationInstruction = sourceMeasure.FirstInstructionsStaffEntries[staffIndex].Instructions[idx];
  1920. if (instruction instanceof KeyInstruction) {
  1921. const key: KeyInstruction = KeyInstruction.copy(instruction);
  1922. if (this.graphicalMusicSheet.ParentMusicSheet.Transpose !== 0 &&
  1923. measure.ParentStaff.ParentInstrument.MidiInstrumentId !== MidiInstrument.Percussion &&
  1924. MusicSheetCalculator.transposeCalculator) {
  1925. MusicSheetCalculator.transposeCalculator.transposeKey(
  1926. key, this.graphicalMusicSheet.ParentMusicSheet.Transpose
  1927. );
  1928. }
  1929. accidentalCalculator.ActiveKeyInstruction = key;
  1930. }
  1931. }
  1932. }
  1933. // check for octave shifts
  1934. for (let idx: number = 0, len: number = sourceMeasure.StaffLinkedExpressions[staffIndex].length; idx < len; ++idx) {
  1935. const multiExpression: MultiExpression = sourceMeasure.StaffLinkedExpressions[staffIndex][idx];
  1936. if (multiExpression.OctaveShiftStart) {
  1937. const openOctaveShift: OctaveShift = multiExpression.OctaveShiftStart;
  1938. let absoluteEnd: Fraction = openOctaveShift?.ParentEndMultiExpression?.AbsoluteTimestamp;
  1939. if (!openOctaveShift?.ParentEndMultiExpression) {
  1940. const measureEndTimestamp: Fraction = Fraction.plus(sourceMeasure.AbsoluteTimestamp, sourceMeasure.Duration);
  1941. absoluteEnd = measureEndTimestamp;
  1942. // TODO better handling if end expression missing
  1943. // old comment:
  1944. // TODO check if octaveshift end exists, otherwise set to last measure end. only necessary if xml was cut manually and is incomplete
  1945. }
  1946. openOctaveShifts[staffIndex] = new OctaveShiftParams(
  1947. openOctaveShift, multiExpression?.AbsoluteTimestamp,
  1948. absoluteEnd
  1949. );
  1950. }
  1951. }
  1952. // create GraphicalStaffEntries - always check for possible null Entry
  1953. for (let entryIndex: number = 0; entryIndex < sourceMeasure.VerticalSourceStaffEntryContainers.length; entryIndex++) {
  1954. const sourceStaffEntry: SourceStaffEntry = sourceMeasure.VerticalSourceStaffEntryContainers[entryIndex].StaffEntries[staffIndex];
  1955. // is there a SourceStaffEntry at this Index
  1956. if (sourceStaffEntry) {
  1957. // a SourceStaffEntry exists
  1958. // is there an inStaff ClefInstruction? -> update activeClef
  1959. for (let idx: number = 0, len: number = sourceStaffEntry.Instructions.length; idx < len; ++idx) {
  1960. const abstractNotationInstruction: AbstractNotationInstruction = sourceStaffEntry.Instructions[idx];
  1961. if (abstractNotationInstruction instanceof ClefInstruction) {
  1962. activeClefs[staffIndex] = <ClefInstruction>abstractNotationInstruction;
  1963. }
  1964. }
  1965. // create new GraphicalStaffEntry
  1966. const graphicalStaffEntry: GraphicalStaffEntry = MusicSheetCalculator.symbolFactory.createStaffEntry(sourceStaffEntry, measure);
  1967. if (entryIndex < measure.staffEntries.length) {
  1968. // a GraphicalStaffEntry has been inserted already at this Index (from Tie)
  1969. measure.addGraphicalStaffEntryAtTimestamp(graphicalStaffEntry);
  1970. } else {
  1971. measure.addGraphicalStaffEntry(graphicalStaffEntry);
  1972. }
  1973. const linkedNotes: Note[] = [];
  1974. if (sourceStaffEntry.Link) {
  1975. sourceStaffEntry.findLinkedNotes(linkedNotes);
  1976. this.handleStaffEntryLink(graphicalStaffEntry, staffEntryLinks);
  1977. }
  1978. // check for possible OctaveShift
  1979. let octaveShiftValue: OctaveEnum = OctaveEnum.NONE;
  1980. if (openOctaveShifts[staffIndex]) {
  1981. if (openOctaveShifts[staffIndex].getAbsoluteStartTimestamp.lte(sourceStaffEntry.AbsoluteTimestamp) &&
  1982. sourceStaffEntry.AbsoluteTimestamp.lte(openOctaveShifts[staffIndex].getAbsoluteEndTimestamp)) {
  1983. octaveShiftValue = openOctaveShifts[staffIndex].getOpenOctaveShift.Type;
  1984. }
  1985. }
  1986. // for each visible Voice create the corresponding GraphicalNotes
  1987. for (let idx: number = 0, len: number = sourceStaffEntry.VoiceEntries.length; idx < len; ++idx) {
  1988. const voiceEntry: VoiceEntry = sourceStaffEntry.VoiceEntries[idx];
  1989. // Normal Notes...
  1990. octaveShiftValue = this.handleVoiceEntry(
  1991. voiceEntry, graphicalStaffEntry,
  1992. accidentalCalculator, openLyricWords,
  1993. activeClefs[staffIndex], openTuplets,
  1994. openBeams, octaveShiftValue, staffIndex,
  1995. linkedNotes, sourceStaffEntry
  1996. );
  1997. }
  1998. // SourceStaffEntry has inStaff ClefInstruction -> create graphical clef
  1999. if (sourceStaffEntry.Instructions.length > 0) {
  2000. const clefInstruction: ClefInstruction = <ClefInstruction>sourceStaffEntry.Instructions[0];
  2001. MusicSheetCalculator.symbolFactory.createInStaffClef(graphicalStaffEntry, clefInstruction);
  2002. }
  2003. if (sourceStaffEntry.ChordContainers && sourceStaffEntry.ChordContainers.length > 0) {
  2004. sourceStaffEntry.ParentStaff.ParentInstrument.HasChordSymbols = true;
  2005. MusicSheetCalculator.symbolFactory.createChordSymbols(
  2006. sourceStaffEntry,
  2007. graphicalStaffEntry,
  2008. accidentalCalculator.ActiveKeyInstruction,
  2009. this.graphicalMusicSheet.ParentMusicSheet.Transpose);
  2010. }
  2011. }
  2012. }
  2013. accidentalCalculator.doCalculationsAtEndOfMeasure();
  2014. // update activeClef given at end of measure if needed
  2015. if (sourceMeasure.LastInstructionsStaffEntries[staffIndex]) {
  2016. const lastStaffEntry: SourceStaffEntry = sourceMeasure.LastInstructionsStaffEntries[staffIndex];
  2017. for (let idx: number = 0, len: number = lastStaffEntry.Instructions.length; idx < len; ++idx) {
  2018. const abstractNotationInstruction: AbstractNotationInstruction = lastStaffEntry.Instructions[idx];
  2019. if (abstractNotationInstruction instanceof ClefInstruction) {
  2020. activeClefs[staffIndex] = <ClefInstruction>abstractNotationInstruction;
  2021. }
  2022. }
  2023. }
  2024. for (let idx: number = 0, len: number = sourceMeasure.StaffLinkedExpressions[staffIndex].length; idx < len; ++idx) {
  2025. const multiExpression: MultiExpression = sourceMeasure.StaffLinkedExpressions[staffIndex][idx];
  2026. if (multiExpression.OctaveShiftEnd !== undefined && openOctaveShifts[staffIndex] !== undefined &&
  2027. multiExpression.OctaveShiftEnd === openOctaveShifts[staffIndex].getOpenOctaveShift) {
  2028. openOctaveShifts[staffIndex] = undefined;
  2029. }
  2030. }
  2031. // check wantedStemDirections of beam notes at end of measure (e.g. for beam with grace notes)
  2032. for (const staffEntry of measure.staffEntries) {
  2033. for (const voiceEntry of staffEntry.graphicalVoiceEntries) {
  2034. this.setBeamNotesWantedStemDirections(voiceEntry.parentVoiceEntry);
  2035. }
  2036. }
  2037. // if there are no staffEntries in this measure, create a rest for the whole measure:
  2038. // check OSMDOptions.fillEmptyMeasuresWithWholeRest
  2039. if (this.rules.FillEmptyMeasuresWithWholeRest >= 1) { // fill measures with no notes given with whole rests, visible (1) or invisible (2)
  2040. if (measure.staffEntries.length === 0) {
  2041. const sourceStaffEntry: SourceStaffEntry = new SourceStaffEntry(
  2042. new VerticalSourceStaffEntryContainer(measure.parentSourceMeasure,
  2043. measure.parentSourceMeasure.AbsoluteTimestamp,
  2044. measure.parentSourceMeasure.CompleteNumberOfStaves),
  2045. staff);
  2046. const voiceEntry: VoiceEntry = new VoiceEntry(new Fraction(0, 1), staff.Voices[0], sourceStaffEntry);
  2047. const note: Note = new Note(voiceEntry, sourceStaffEntry, Fraction.createFromFraction(sourceMeasure.Duration), undefined, sourceMeasure);
  2048. note.PrintObject = this.rules.FillEmptyMeasuresWithWholeRest === FillEmptyMeasuresWithWholeRests.YesVisible;
  2049. // don't display whole rest that wasn't given in XML, only for layout/voice completion
  2050. voiceEntry.Notes.push(note);
  2051. const graphicalStaffEntry: GraphicalStaffEntry = MusicSheetCalculator.symbolFactory.createStaffEntry(sourceStaffEntry, measure);
  2052. measure.addGraphicalStaffEntry(graphicalStaffEntry);
  2053. graphicalStaffEntry.relInMeasureTimestamp = voiceEntry.Timestamp;
  2054. const gve: GraphicalVoiceEntry = MusicSheetCalculator.symbolFactory.createVoiceEntry(voiceEntry, graphicalStaffEntry);
  2055. graphicalStaffEntry.graphicalVoiceEntries.push(gve);
  2056. const graphicalNote: GraphicalNote = MusicSheetCalculator.symbolFactory.createNote(note,
  2057. gve,
  2058. new ClefInstruction(),
  2059. OctaveEnum.NONE, undefined);
  2060. MusicSheetCalculator.stafflineNoteCalculator.trackNote(graphicalNote);
  2061. gve.notes.push(graphicalNote);
  2062. }
  2063. }
  2064. measure.hasOnlyRests = true;
  2065. //if staff entries empty, loop will not start. so true is valid
  2066. for (const graphicalStaffEntry of measure.staffEntries) {
  2067. //Loop until we get just one false
  2068. measure.hasOnlyRests = graphicalStaffEntry.hasOnlyRests();
  2069. if (!measure.hasOnlyRests) {
  2070. break;
  2071. }
  2072. }
  2073. return measure;
  2074. }
  2075. private checkNoteForAccidental(graphicalNote: GraphicalNote, accidentalCalculator: AccidentalCalculator, activeClef: ClefInstruction,
  2076. octaveEnum: OctaveEnum): void {
  2077. let pitch: Pitch = graphicalNote.sourceNote.Pitch;
  2078. const transpose: number = this.graphicalMusicSheet.ParentMusicSheet.Transpose;
  2079. if (transpose !== 0 && graphicalNote.sourceNote.ParentStaffEntry.ParentStaff.ParentInstrument.MidiInstrumentId !== MidiInstrument.Percussion) {
  2080. pitch = graphicalNote.Transpose(
  2081. accidentalCalculator.ActiveKeyInstruction, activeClef, transpose, octaveEnum
  2082. );
  2083. }
  2084. graphicalNote.sourceNote.halfTone = pitch.getHalfTone();
  2085. accidentalCalculator.checkAccidental(graphicalNote, pitch);
  2086. }
  2087. // // needed to disable linter, as it doesn't recognize the existing usage of this method.
  2088. // // ToDo: check if a newer version doesn't have the problem.
  2089. // /* tslint:disable:no-unused-variable */
  2090. // private createStaffEntryForTieNote(measure: StaffMeasure, absoluteTimestamp: Fraction, openTie: Tie): GraphicalStaffEntry {
  2091. // /* tslint:enable:no-unused-variable */
  2092. // let graphicalStaffEntry: GraphicalStaffEntry;
  2093. // graphicalStaffEntry = MusicSheetCalculator.symbolFactory.createStaffEntry(openTie.Start.ParentStaffEntry, measure);
  2094. // graphicalStaffEntry.relInMeasureTimestamp = Fraction.minus(absoluteTimestamp, measure.parentSourceMeasure.AbsoluteTimestamp);
  2095. // this.resetYPositionForLeadSheet(graphicalStaffEntry.PositionAndShape);
  2096. // measure.addGraphicalStaffEntryAtTimestamp(graphicalStaffEntry);
  2097. // return graphicalStaffEntry;
  2098. // }
  2099. private handleStaffEntries(staffIsPercussionArray: Array<boolean>): void {
  2100. for (let idx: number = 0, len: number = this.graphicalMusicSheet.MeasureList.length; idx < len; ++idx) {
  2101. const measures: GraphicalMeasure[] = this.graphicalMusicSheet.MeasureList[idx];
  2102. for (let idx2: number = 0, len2: number = measures.length; idx2 < len2; ++idx2) {
  2103. const measure: GraphicalMeasure = measures[idx2];
  2104. if (!measure) {
  2105. continue;
  2106. }
  2107. //This property is active...
  2108. if (this.rules.PercussionOneLineCutoff !== undefined && this.rules.PercussionOneLineCutoff !== 0) {
  2109. //We have a percussion clef, check to see if this property applies...
  2110. if (staffIsPercussionArray[idx2]) {
  2111. //-1 means always trigger, or we are under the cutoff number specified
  2112. if (this.rules.PercussionOneLineCutoff === -1 ||
  2113. MusicSheetCalculator.stafflineNoteCalculator.getStafflineUniquePositionCount(idx2) < this.rules.PercussionOneLineCutoff) {
  2114. measure.ParentStaff.StafflineCount = 1;
  2115. }
  2116. }
  2117. }
  2118. for (const graphicalStaffEntry of measure.staffEntries) {
  2119. if (graphicalStaffEntry.parentMeasure !== undefined
  2120. && graphicalStaffEntry.graphicalVoiceEntries.length > 0
  2121. && graphicalStaffEntry.graphicalVoiceEntries[0].notes.length > 0) {
  2122. this.layoutVoiceEntries(graphicalStaffEntry, idx2);
  2123. this.layoutStaffEntry(graphicalStaffEntry);
  2124. }
  2125. }
  2126. this.graphicalMeasureCreatedCalculations(measure);
  2127. }
  2128. }
  2129. }
  2130. private calculateSkyBottomLines(): void {
  2131. for (const musicSystem of this.musicSystems) {
  2132. for (const staffLine of musicSystem.StaffLines) {
  2133. staffLine.SkyBottomLineCalculator.calculateLines();
  2134. }
  2135. }
  2136. }
  2137. /**
  2138. * Re-adjust the x positioning of expressions.
  2139. */
  2140. protected calculateExpressionAlignements(): void {
  2141. // override
  2142. }
  2143. // does nothing for now, because layoutBeams() is an empty method
  2144. // private calculateBeams(): void {
  2145. // for (let idx2: number = 0, len2: number = this.musicSystems.length; idx2 < len2; ++idx2) {
  2146. // const musicSystem: MusicSystem = this.musicSystems[idx2];
  2147. // for (let idx3: number = 0, len3: number = musicSystem.StaffLines.length; idx3 < len3; ++idx3) {
  2148. // const staffLine: StaffLine = musicSystem.StaffLines[idx3];
  2149. // for (let idx4: number = 0, len4: number = staffLine.Measures.length; idx4 < len4; ++idx4) {
  2150. // const measure: GraphicalMeasure = staffLine.Measures[idx4];
  2151. // for (let idx5: number = 0, len5: number = measure.staffEntries.length; idx5 < len5; ++idx5) {
  2152. // const staffEntry: GraphicalStaffEntry = measure.staffEntries[idx5];
  2153. // this.layoutBeams(staffEntry);
  2154. // }
  2155. // }
  2156. // }
  2157. // }
  2158. // }
  2159. private calculateStaffEntryArticulationMarks(): void {
  2160. for (let idx2: number = 0, len2: number = this.musicSystems.length; idx2 < len2; ++idx2) {
  2161. const system: MusicSystem = this.musicSystems[idx2];
  2162. for (let idx3: number = 0, len3: number = system.StaffLines.length; idx3 < len3; ++idx3) {
  2163. const line: StaffLine = system.StaffLines[idx3];
  2164. for (let idx4: number = 0, len4: number = line.Measures.length; idx4 < len4; ++idx4) {
  2165. const measure: GraphicalMeasure = line.Measures[idx4];
  2166. for (let idx5: number = 0, len5: number = measure.staffEntries.length; idx5 < len5; ++idx5) {
  2167. const graphicalStaffEntry: GraphicalStaffEntry = measure.staffEntries[idx5];
  2168. for (let idx6: number = 0, len6: number = graphicalStaffEntry.sourceStaffEntry.VoiceEntries.length; idx6 < len6; ++idx6) {
  2169. const voiceEntry: VoiceEntry = graphicalStaffEntry.sourceStaffEntry.VoiceEntries[idx6];
  2170. if (voiceEntry.Articulations.length > 0) {
  2171. this.layoutArticulationMarks(voiceEntry.Articulations, voiceEntry, graphicalStaffEntry);
  2172. }
  2173. }
  2174. }
  2175. }
  2176. }
  2177. }
  2178. }
  2179. private calculateOrnaments(): void {
  2180. for (let idx2: number = 0, len2: number = this.musicSystems.length; idx2 < len2; ++idx2) {
  2181. const system: MusicSystem = this.musicSystems[idx2];
  2182. for (let idx3: number = 0, len3: number = system.StaffLines.length; idx3 < len3; ++idx3) {
  2183. const line: StaffLine = system.StaffLines[idx3];
  2184. for (let idx4: number = 0, len4: number = line.Measures.length; idx4 < len4; ++idx4) {
  2185. const measure: GraphicalMeasure = line.Measures[idx4];
  2186. for (let idx5: number = 0, len5: number = measure.staffEntries.length; idx5 < len5; ++idx5) {
  2187. const graphicalStaffEntry: GraphicalStaffEntry = measure.staffEntries[idx5];
  2188. for (let idx6: number = 0, len6: number = graphicalStaffEntry.sourceStaffEntry.VoiceEntries.length; idx6 < len6; ++idx6) {
  2189. const voiceEntry: VoiceEntry = graphicalStaffEntry.sourceStaffEntry.VoiceEntries[idx6];
  2190. if (voiceEntry.OrnamentContainer) {
  2191. if (voiceEntry.hasTie() && !graphicalStaffEntry.relInMeasureTimestamp.Equals(voiceEntry.Timestamp)) {
  2192. continue;
  2193. }
  2194. this.layoutOrnament(voiceEntry.OrnamentContainer, voiceEntry, graphicalStaffEntry);
  2195. if (!(this.staffEntriesWithOrnaments.indexOf(graphicalStaffEntry) !== -1)) {
  2196. this.staffEntriesWithOrnaments.push(graphicalStaffEntry);
  2197. }
  2198. }
  2199. }
  2200. }
  2201. }
  2202. }
  2203. }
  2204. }
  2205. private optimizeRestPlacement(): void {
  2206. for (let idx2: number = 0, len2: number = this.musicSystems.length; idx2 < len2; ++idx2) {
  2207. const system: MusicSystem = this.musicSystems[idx2];
  2208. for (let idx3: number = 0, len3: number = system.StaffLines.length; idx3 < len3; ++idx3) {
  2209. const line: StaffLine = system.StaffLines[idx3];
  2210. for (let idx4: number = 0, len4: number = line.Measures.length; idx4 < len4; ++idx4) {
  2211. const measure: GraphicalMeasure = line.Measures[idx4];
  2212. for (let idx5: number = 0, len5: number = measure.staffEntries.length; idx5 < len5; ++idx5) {
  2213. const graphicalStaffEntry: GraphicalStaffEntry = measure.staffEntries[idx5];
  2214. this.optimizeRestNotePlacement(graphicalStaffEntry, measure);
  2215. }
  2216. }
  2217. }
  2218. }
  2219. }
  2220. private calculateTwoRestNotesPlacementWithCollisionDetection(graphicalStaffEntry: GraphicalStaffEntry): void {
  2221. const firstRestNote: GraphicalNote = graphicalStaffEntry.graphicalVoiceEntries[0].notes[0];
  2222. const secondRestNote: GraphicalNote = graphicalStaffEntry.graphicalVoiceEntries[1].notes[0];
  2223. secondRestNote.PositionAndShape.RelativePosition = new PointF2D(0.0, 2.5);
  2224. graphicalStaffEntry.PositionAndShape.calculateAbsolutePositionsRecursiveWithoutTopelement();
  2225. firstRestNote.PositionAndShape.computeNonOverlappingPositionWithMargin(
  2226. graphicalStaffEntry.PositionAndShape, ColDirEnum.Up,
  2227. new PointF2D(0.0, secondRestNote.PositionAndShape.RelativePosition.y)
  2228. );
  2229. const relative: PointF2D = firstRestNote.PositionAndShape.RelativePosition;
  2230. relative.y -= 1.0;
  2231. firstRestNote.PositionAndShape.RelativePosition = relative;
  2232. graphicalStaffEntry.PositionAndShape.calculateBoundingBox();
  2233. }
  2234. private calculateRestNotePlacementWithCollisionDetectionFromGraphicalNote(graphicalStaffEntry: GraphicalStaffEntry): void {
  2235. let restNote: GraphicalNote;
  2236. let graphicalNotes: GraphicalNote[];
  2237. if (graphicalStaffEntry.graphicalVoiceEntries[0].notes[0].sourceNote.isRest()) {
  2238. restNote = graphicalStaffEntry.graphicalVoiceEntries[0].notes[0];
  2239. graphicalNotes = graphicalStaffEntry.graphicalVoiceEntries[1].notes;
  2240. } else {
  2241. graphicalNotes = graphicalStaffEntry.graphicalVoiceEntries[0].notes;
  2242. restNote = graphicalStaffEntry.graphicalVoiceEntries[1].notes[0];
  2243. }
  2244. //restNote.parallelVoiceEntryNotes = graphicalNotes; // TODO maybe save potentially colliding notes, check them in VexFlowConverter.StaveNote
  2245. let collision: boolean = false;
  2246. graphicalStaffEntry.PositionAndShape.calculateAbsolutePositionsRecursiveWithoutTopelement();
  2247. for (let idx: number = 0, len: number = graphicalNotes.length; idx < len; ++idx) {
  2248. const graphicalNote: GraphicalNote = graphicalNotes[idx];
  2249. if (restNote.PositionAndShape.marginCollisionDetection(graphicalNote.PositionAndShape)) {
  2250. // TODO bounding box of graphical note isn't set correctly yet.
  2251. // we could do manual collision checking here
  2252. collision = true;
  2253. break;
  2254. }
  2255. }
  2256. if (collision) {
  2257. if (restNote.sourceNote.ParentVoiceEntry.ParentVoice instanceof LinkedVoice) {
  2258. const bottomBorder: number = graphicalNotes[0].PositionAndShape.BorderMarginBottom + graphicalNotes[0].PositionAndShape.RelativePosition.y;
  2259. restNote.PositionAndShape.RelativePosition = new PointF2D(0.0, bottomBorder - restNote.PositionAndShape.BorderMarginTop + 0.5);
  2260. } else {
  2261. const last: GraphicalNote = graphicalNotes[graphicalNotes.length - 1];
  2262. const topBorder: number = last.PositionAndShape.BorderMarginTop + last.PositionAndShape.RelativePosition.y;
  2263. if (graphicalNotes[0].sourceNote.ParentVoiceEntry.ParentVoice instanceof LinkedVoice) {
  2264. restNote.PositionAndShape.RelativePosition = new PointF2D(0.0, topBorder - restNote.PositionAndShape.BorderMarginBottom - 0.5);
  2265. } else {
  2266. const bottomBorder: number = graphicalNotes[0].PositionAndShape.BorderMarginBottom + graphicalNotes[0].PositionAndShape.RelativePosition.y;
  2267. if (bottomBorder < 2.0) {
  2268. restNote.PositionAndShape.RelativePosition = new PointF2D(0.0, bottomBorder - restNote.PositionAndShape.BorderMarginTop + 0.5);
  2269. } else {
  2270. restNote.PositionAndShape.RelativePosition = new PointF2D(0.0, topBorder - restNote.PositionAndShape.BorderMarginBottom - 0.0);
  2271. }
  2272. }
  2273. }
  2274. }
  2275. graphicalStaffEntry.PositionAndShape.calculateBoundingBox();
  2276. }
  2277. private calculateTieCurves(): void {
  2278. for (const musicSystem of this.musicSystems) {
  2279. for (const staffLine of musicSystem.StaffLines) {
  2280. for (const measure of staffLine.Measures) {
  2281. for (const staffEntry of measure.staffEntries) {
  2282. for (const graphicalTie of staffEntry.GraphicalTies) {
  2283. if (graphicalTie.StartNote !== undefined && graphicalTie.StartNote.parentVoiceEntry.parentStaffEntry === staffEntry) {
  2284. const tieIsAtSystemBreak: boolean = (
  2285. graphicalTie.StartNote.parentVoiceEntry.parentStaffEntry.parentMeasure.ParentStaffLine !==
  2286. graphicalTie.EndNote.parentVoiceEntry.parentStaffEntry.parentMeasure.ParentStaffLine
  2287. );
  2288. this.layoutGraphicalTie(graphicalTie, tieIsAtSystemBreak, measure.ParentStaff.isTab);
  2289. }
  2290. }
  2291. }
  2292. }
  2293. }
  2294. }
  2295. }
  2296. private calculateLyricsPosition(): void {
  2297. const lyricStaffEntriesDict: Dictionary<StaffLine, GraphicalStaffEntry[]> = new Dictionary<StaffLine, GraphicalStaffEntry[]>();
  2298. // sort the lyriceVerseNumbers for every Instrument that has Lyrics
  2299. for (let idx: number = 0, len: number = this.graphicalMusicSheet.ParentMusicSheet.Instruments.length; idx < len; ++idx) {
  2300. const instrument: Instrument = this.graphicalMusicSheet.ParentMusicSheet.Instruments[idx];
  2301. if (instrument.HasLyrics && instrument.LyricVersesNumbers.length > 0) {
  2302. instrument.LyricVersesNumbers.sort();
  2303. }
  2304. }
  2305. // first calc lyrics text positions
  2306. for (let idx2: number = 0, len2: number = this.musicSystems.length; idx2 < len2; ++idx2) {
  2307. const musicSystem: MusicSystem = this.musicSystems[idx2];
  2308. for (let idx3: number = 0, len3: number = musicSystem.StaffLines.length; idx3 < len3; ++idx3) {
  2309. const staffLine: StaffLine = musicSystem.StaffLines[idx3];
  2310. const lyricsStaffEntries: GraphicalStaffEntry[] =
  2311. this.calculateSingleStaffLineLyricsPosition(staffLine, staffLine.ParentStaff.ParentInstrument.LyricVersesNumbers);
  2312. lyricStaffEntriesDict.setValue(staffLine, lyricsStaffEntries);
  2313. this.calculateLyricsExtendsAndDashes(lyricStaffEntriesDict.getValue(staffLine));
  2314. }
  2315. }
  2316. // then fill in the lyric word dashes and lyrics extends/underscores
  2317. for (let idx2: number = 0, len2: number = this.musicSystems.length; idx2 < len2; ++idx2) {
  2318. const musicSystem: MusicSystem = this.musicSystems[idx2];
  2319. for (let idx3: number = 0, len3: number = musicSystem.StaffLines.length; idx3 < len3; ++idx3) {
  2320. const staffLine: StaffLine = musicSystem.StaffLines[idx3];
  2321. this.calculateLyricsExtendsAndDashes(lyricStaffEntriesDict.getValue(staffLine));
  2322. }
  2323. }
  2324. }
  2325. /**
  2326. * This method calculates the dashes within the syllables of a LyricWord
  2327. * @param lyricEntry
  2328. */
  2329. private calculateSingleLyricWord(lyricEntry: GraphicalLyricEntry): void {
  2330. // const skyBottomLineCalculator: SkyBottomLineCalculator = new SkyBottomLineCalculator (this.rules);
  2331. const graphicalLyricWord: GraphicalLyricWord = lyricEntry.ParentLyricWord;
  2332. const index: number = graphicalLyricWord.GraphicalLyricsEntries.indexOf(lyricEntry);
  2333. let nextLyricEntry: GraphicalLyricEntry = undefined;
  2334. if (index >= 0) {
  2335. nextLyricEntry = graphicalLyricWord.GraphicalLyricsEntries[index + 1];
  2336. }
  2337. if (!nextLyricEntry) {
  2338. return;
  2339. }
  2340. const startStaffLine: StaffLine = <StaffLine>lyricEntry.StaffEntryParent.parentMeasure.ParentStaffLine;
  2341. const nextStaffLine: StaffLine = <StaffLine>nextLyricEntry.StaffEntryParent.parentMeasure.ParentStaffLine;
  2342. const startStaffEntry: GraphicalStaffEntry = lyricEntry.StaffEntryParent;
  2343. const endStaffentry: GraphicalStaffEntry = nextLyricEntry.StaffEntryParent;
  2344. // if on the same StaffLine
  2345. if (lyricEntry.StaffEntryParent.parentMeasure.ParentStaffLine === nextLyricEntry.StaffEntryParent.parentMeasure.ParentStaffLine) {
  2346. // start- and End margins from the text Labels
  2347. const startX: number = startStaffEntry.parentMeasure.PositionAndShape.RelativePosition.x +
  2348. startStaffEntry.PositionAndShape.RelativePosition.x +
  2349. lyricEntry.GraphicalLabel.PositionAndShape.RelativePosition.x +
  2350. lyricEntry.GraphicalLabel.PositionAndShape.BorderMarginRight;
  2351. const endX: number = endStaffentry.parentMeasure.PositionAndShape.RelativePosition.x +
  2352. endStaffentry.PositionAndShape.RelativePosition.x +
  2353. lyricEntry.GraphicalLabel.PositionAndShape.RelativePosition.x +
  2354. nextLyricEntry.GraphicalLabel.PositionAndShape.BorderMarginLeft;
  2355. const y: number = lyricEntry.GraphicalLabel.PositionAndShape.RelativePosition.y;
  2356. let numberOfDashes: number = 1;
  2357. if ((endX - startX) > this.rules.MinimumDistanceBetweenDashes * 3) {
  2358. // *3: need distance between word to first dash, dash to dash, dash to next word
  2359. numberOfDashes = Math.floor((endX - startX) / this.rules.MinimumDistanceBetweenDashes) - 1;
  2360. }
  2361. // check distance and create the adequate number of Dashes
  2362. if (numberOfDashes === 1) {
  2363. // distance between the two GraphicalLyricEntries is big for only one Dash, position in the middle
  2364. this.calculateSingleDashForLyricWord(startStaffLine, startX, endX, y);
  2365. } else {
  2366. // distance is big enough for more Dashes
  2367. // calculate the adequate number of Dashes from the distance between the two LyricEntries
  2368. // distance between the Dashes should be equal
  2369. this.calculateDashes(startStaffLine, startX, endX, y);
  2370. }
  2371. } else {
  2372. // start and end on different StaffLines
  2373. // start margin from the text Label until the End of StaffLine
  2374. const startX: number = startStaffEntry.parentMeasure.PositionAndShape.RelativePosition.x +
  2375. startStaffEntry.PositionAndShape.RelativePosition.x +
  2376. lyricEntry.GraphicalLabel.PositionAndShape.BorderMarginRight;
  2377. const lastGraphicalMeasure: GraphicalMeasure = startStaffLine.Measures[startStaffLine.Measures.length - 1];
  2378. const endX: number = lastGraphicalMeasure.PositionAndShape.RelativePosition.x + lastGraphicalMeasure.PositionAndShape.Size.width;
  2379. let y: number = lyricEntry.GraphicalLabel.PositionAndShape.RelativePosition.y;
  2380. // calculate Dashes for the first StaffLine
  2381. this.calculateDashes(startStaffLine, startX, endX, y);
  2382. // calculate Dashes for the second StaffLine (only if endStaffEntry isn't the first StaffEntry of the StaffLine)
  2383. if (nextStaffLine && // check for undefined objects e.g. when drawingRange given
  2384. nextStaffLine.Measures[0] &&
  2385. endStaffentry.parentMeasure.ParentStaffLine &&
  2386. !(endStaffentry === endStaffentry.parentMeasure.staffEntries[0] &&
  2387. endStaffentry.parentMeasure === endStaffentry.parentMeasure.ParentStaffLine.Measures[0])) {
  2388. const secondStartX: number = nextStaffLine.Measures[0].staffEntries[0].PositionAndShape.RelativePosition.x;
  2389. const secondEndX: number = endStaffentry.parentMeasure.PositionAndShape.RelativePosition.x +
  2390. endStaffentry.PositionAndShape.RelativePosition.x +
  2391. nextLyricEntry.GraphicalLabel.PositionAndShape.BorderMarginLeft;
  2392. y = nextLyricEntry.GraphicalLabel.PositionAndShape.RelativePosition.y;
  2393. this.calculateDashes(nextStaffLine, secondStartX, secondEndX, y);
  2394. }
  2395. }
  2396. }
  2397. /**
  2398. * This method calculates Dashes for a LyricWord.
  2399. * @param staffLine
  2400. * @param startX
  2401. * @param endX
  2402. * @param y
  2403. */
  2404. private calculateDashes(staffLine: StaffLine, startX: number, endX: number, y: number): void {
  2405. let distance: number = endX - startX;
  2406. if (distance < this.rules.MinimumDistanceBetweenDashes * 3) {
  2407. this.calculateSingleDashForLyricWord(staffLine, startX, endX, y);
  2408. } else {
  2409. // enough distance for more Dashes
  2410. const numberOfDashes: number = Math.floor(distance / this.rules.MinimumDistanceBetweenDashes) - 1;
  2411. const distanceBetweenDashes: number = distance / (numberOfDashes + 1);
  2412. let counter: number = 0;
  2413. startX += distanceBetweenDashes;
  2414. endX -= distanceBetweenDashes;
  2415. while (counter <= Math.floor(numberOfDashes / 2.0) && endX > startX) {
  2416. distance = this.calculateRightAndLeftDashesForLyricWord(staffLine, startX, endX, y);
  2417. startX += distanceBetweenDashes;
  2418. endX -= distanceBetweenDashes;
  2419. counter++;
  2420. }
  2421. // if the remaining distance isn't big enough for two Dashes,
  2422. // but long enough for a middle dash inbetween,
  2423. // then put the last Dash in the middle of the remaining distance
  2424. if (distance > distanceBetweenDashes * 2) {
  2425. this.calculateSingleDashForLyricWord(staffLine, startX, endX, y);
  2426. }
  2427. }
  2428. }
  2429. /**
  2430. * This method calculates a single Dash for a LyricWord, positioned in the middle of the given distance.
  2431. * @param {StaffLine} staffLine
  2432. * @param {number} startX
  2433. * @param {number} endX
  2434. * @param {number} y
  2435. */
  2436. private calculateSingleDashForLyricWord(staffLine: StaffLine, startX: number, endX: number, y: number): void {
  2437. const label: Label = new Label("-");
  2438. const dash: GraphicalLabel = new GraphicalLabel(
  2439. label, this.rules.LyricsHeight, TextAlignmentEnum.CenterBottom, this.rules);
  2440. dash.setLabelPositionAndShapeBorders();
  2441. staffLine.LyricsDashes.push(dash);
  2442. if (this.staffLinesWithLyricWords.indexOf(staffLine) === -1) {
  2443. this.staffLinesWithLyricWords.push(staffLine);
  2444. }
  2445. dash.PositionAndShape.Parent = staffLine.PositionAndShape;
  2446. const relative: PointF2D = new PointF2D(startX + (endX - startX) / 2, y);
  2447. dash.PositionAndShape.RelativePosition = relative;
  2448. }
  2449. /**
  2450. * Layouts the underscore line when a lyric entry is marked as extend
  2451. * @param {GraphicalLyricEntry} lyricEntry
  2452. */
  2453. private calculateLyricExtend(lyricEntry: GraphicalLyricEntry): void {
  2454. let startY: number = lyricEntry.GraphicalLabel.PositionAndShape.RelativePosition.y;
  2455. const startStaffEntry: GraphicalStaffEntry = lyricEntry.StaffEntryParent;
  2456. const startStaffLine: StaffLine = startStaffEntry.parentMeasure.ParentStaffLine;
  2457. // find endstaffEntry and staffLine
  2458. let endStaffEntry: GraphicalStaffEntry = undefined;
  2459. let endStaffLine: StaffLine = undefined;
  2460. const staffIndex: number = startStaffEntry.parentMeasure.ParentStaff.idInMusicSheet;
  2461. for (let index: number = startStaffEntry.parentVerticalContainer.Index + 1;
  2462. index < this.graphicalMusicSheet.VerticalGraphicalStaffEntryContainers.length;
  2463. ++index) {
  2464. const gse: GraphicalStaffEntry = this.graphicalMusicSheet.VerticalGraphicalStaffEntryContainers[index].StaffEntries[staffIndex];
  2465. if (!gse) {
  2466. continue;
  2467. }
  2468. if (gse.hasOnlyRests()) {
  2469. break;
  2470. }
  2471. if (gse.LyricsEntries.length > 0) {
  2472. break;
  2473. }
  2474. endStaffEntry = gse;
  2475. endStaffLine = <StaffLine>endStaffEntry.parentMeasure.ParentStaffLine;
  2476. }
  2477. if (!endStaffEntry) {
  2478. return;
  2479. }
  2480. // if on the same StaffLine
  2481. if (startStaffLine === endStaffLine) {
  2482. // start- and End margins from the text Labels
  2483. const startX: number = startStaffEntry.parentMeasure.PositionAndShape.RelativePosition.x +
  2484. startStaffEntry.PositionAndShape.RelativePosition.x +
  2485. lyricEntry.GraphicalLabel.PositionAndShape.BorderMarginRight;
  2486. // + startStaffLine.PositionAndShape.AbsolutePosition.x; // doesn't work, done in drawer
  2487. const endX: number = endStaffEntry.parentMeasure.PositionAndShape.RelativePosition.x +
  2488. endStaffEntry.PositionAndShape.RelativePosition.x +
  2489. endStaffEntry.PositionAndShape.BorderMarginRight;
  2490. // + endStaffLine.PositionAndShape.AbsolutePosition.x; // doesn't work, done in drawer
  2491. // TODO maybe add half-width of following note.
  2492. // though we don't have the vexflow note's bbox yet and extend layouting is unconstrained,
  2493. // we have more room for spacing without it.
  2494. // needed in order to line up with the Label's text bottom line (is the y position of the underscore)
  2495. startY -= lyricEntry.GraphicalLabel.PositionAndShape.Size.height / 4;
  2496. // create a Line (as underscore after the LyricLabel's End)
  2497. this.calculateSingleLyricWordWithUnderscore(startStaffLine, startX, endX, startY);
  2498. } else { // start and end on different StaffLines
  2499. // start margin from the text Label until the End of StaffLine
  2500. const lastMeasureBb: BoundingBox = startStaffLine.Measures[startStaffLine.Measures.length - 1].PositionAndShape;
  2501. const startX: number = startStaffEntry.parentMeasure.PositionAndShape.RelativePosition.x +
  2502. startStaffEntry.PositionAndShape.RelativePosition.x +
  2503. lyricEntry.GraphicalLabel.PositionAndShape.BorderMarginRight;
  2504. const endX: number = lastMeasureBb.RelativePosition.x +
  2505. lastMeasureBb.Size.width;
  2506. // needed in order to line up with the Label's text bottom line
  2507. startY -= lyricEntry.GraphicalLabel.PositionAndShape.Size.height / 4;
  2508. // first Underscore until the StaffLine's End
  2509. this.calculateSingleLyricWordWithUnderscore(startStaffLine, startX, endX, startY);
  2510. if (!endStaffEntry) {
  2511. return;
  2512. }
  2513. // second Underscore in the endStaffLine until endStaffEntry (if endStaffEntry isn't the first StaffEntry of the StaffLine))
  2514. if (!(endStaffEntry === endStaffEntry.parentMeasure.staffEntries[0] &&
  2515. endStaffEntry.parentMeasure === endStaffEntry.parentMeasure.ParentStaffLine.Measures[0])) {
  2516. const secondStartX: number = endStaffLine.Measures[0].staffEntries[0].PositionAndShape.RelativePosition.x;
  2517. const secondEndX: number = endStaffEntry.parentMeasure.PositionAndShape.RelativePosition.x +
  2518. endStaffEntry.PositionAndShape.RelativePosition.x +
  2519. endStaffEntry.PositionAndShape.BorderMarginRight;
  2520. this.calculateSingleLyricWordWithUnderscore(endStaffLine, secondStartX, secondEndX, startY);
  2521. }
  2522. }
  2523. }
  2524. /**
  2525. * This method calculates a single underscoreLine.
  2526. * @param staffLine
  2527. * @param startX
  2528. * @param end
  2529. * @param y
  2530. */
  2531. private calculateSingleLyricWordWithUnderscore(staffLine: StaffLine, startX: number, endX: number, y: number): void {
  2532. const lineStart: PointF2D = new PointF2D(startX, y);
  2533. const lineEnd: PointF2D = new PointF2D(endX, y);
  2534. const graphicalLine: GraphicalLine = new GraphicalLine(lineStart, lineEnd, this.rules.LyricUnderscoreLineWidth);
  2535. staffLine.LyricLines.push(graphicalLine);
  2536. if (this.staffLinesWithLyricWords.indexOf(staffLine) === -1) {
  2537. this.staffLinesWithLyricWords.push(staffLine);
  2538. }
  2539. }
  2540. /**
  2541. * This method calculates two Dashes for a LyricWord, positioned at the the two ends of the given distance.
  2542. * @param {StaffLine} staffLine
  2543. * @param {number} startX
  2544. * @param {number} endX
  2545. * @param {number} y
  2546. * @returns {number}
  2547. */
  2548. private calculateRightAndLeftDashesForLyricWord(staffLine: StaffLine, startX: number, endX: number, y: number): number {
  2549. const leftLabel: Label = new Label("-");
  2550. const leftDash: GraphicalLabel = new GraphicalLabel(
  2551. leftLabel, this.rules.LyricsHeight, TextAlignmentEnum.CenterBottom, this.rules);
  2552. leftDash.setLabelPositionAndShapeBorders();
  2553. staffLine.LyricsDashes.push(leftDash);
  2554. if (this.staffLinesWithLyricWords.indexOf(staffLine) === -1) {
  2555. this.staffLinesWithLyricWords.push(staffLine);
  2556. }
  2557. leftDash.PositionAndShape.Parent = staffLine.PositionAndShape;
  2558. const leftDashRelative: PointF2D = new PointF2D(startX, y);
  2559. leftDash.PositionAndShape.RelativePosition = leftDashRelative;
  2560. const rightLabel: Label = new Label("-");
  2561. const rightDash: GraphicalLabel = new GraphicalLabel(
  2562. rightLabel, this.rules.LyricsHeight, TextAlignmentEnum.CenterBottom, this.rules);
  2563. rightDash.setLabelPositionAndShapeBorders();
  2564. staffLine.LyricsDashes.push(rightDash);
  2565. rightDash.PositionAndShape.Parent = staffLine.PositionAndShape;
  2566. const rightDashRelative: PointF2D = new PointF2D(endX, y);
  2567. rightDash.PositionAndShape.RelativePosition = rightDashRelative;
  2568. return (rightDash.PositionAndShape.RelativePosition.x - leftDash.PositionAndShape.RelativePosition.x);
  2569. }
  2570. private calculateDynamicExpressions(): void {
  2571. const maxIndex: number = Math.min(this.graphicalMusicSheet.ParentMusicSheet.SourceMeasures.length - 1, this.rules.MaxMeasureToDrawIndex);
  2572. const minIndex: number = Math.min(this.rules.MinMeasureToDrawIndex, this.graphicalMusicSheet.ParentMusicSheet.SourceMeasures.length);
  2573. for (let i: number = minIndex; i <= maxIndex; i++) {
  2574. const sourceMeasure: SourceMeasure = this.graphicalMusicSheet.ParentMusicSheet.SourceMeasures[i];
  2575. for (let j: number = 0; j < sourceMeasure.StaffLinkedExpressions.length; j++) {
  2576. if (!this.graphicalMusicSheet.MeasureList[i] || !this.graphicalMusicSheet.MeasureList[i][j]) {
  2577. continue;
  2578. }
  2579. if (this.graphicalMusicSheet.MeasureList[i][j].ParentStaff.ParentInstrument.Visible) {
  2580. for (let k: number = 0; k < sourceMeasure.StaffLinkedExpressions[j].length; k++) {
  2581. if (sourceMeasure.StaffLinkedExpressions[j][k].InstantaneousDynamic !== undefined ||
  2582. (sourceMeasure.StaffLinkedExpressions[j][k].StartingContinuousDynamic !== undefined &&
  2583. sourceMeasure.StaffLinkedExpressions[j][k].StartingContinuousDynamic.StartMultiExpression ===
  2584. sourceMeasure.StaffLinkedExpressions[j][k] && sourceMeasure.StaffLinkedExpressions[j][k].UnknownList.length === 0)
  2585. ) {
  2586. this.calculateDynamicExpressionsForMultiExpression(sourceMeasure.StaffLinkedExpressions[j][k], i, j);
  2587. }
  2588. }
  2589. }
  2590. }
  2591. }
  2592. }
  2593. private calculateOctaveShifts(): void {
  2594. for (let i: number = 0; i < this.graphicalMusicSheet.ParentMusicSheet.SourceMeasures.length; i++) {
  2595. const sourceMeasure: SourceMeasure = this.graphicalMusicSheet.ParentMusicSheet.SourceMeasures[i];
  2596. for (let j: number = 0; j < sourceMeasure.StaffLinkedExpressions.length; j++) {
  2597. if (!this.graphicalMusicSheet.MeasureList[i] || !this.graphicalMusicSheet.MeasureList[i][j]) {
  2598. continue;
  2599. }
  2600. if (this.graphicalMusicSheet.MeasureList[i][j].ParentStaff.ParentInstrument.Visible) {
  2601. for (let k: number = 0; k < sourceMeasure.StaffLinkedExpressions[j].length; k++) {
  2602. if ((sourceMeasure.StaffLinkedExpressions[j][k].OctaveShiftStart)) {
  2603. this.calculateSingleOctaveShift(sourceMeasure, sourceMeasure.StaffLinkedExpressions[j][k], i, j);
  2604. }
  2605. }
  2606. }
  2607. }
  2608. }
  2609. }
  2610. private getFirstLeftNotNullStaffEntryFromContainer(horizontalIndex: number, verticalIndex: number, multiStaffInstrument: boolean): GraphicalStaffEntry {
  2611. if (this.graphicalMusicSheet.VerticalGraphicalStaffEntryContainers[horizontalIndex].StaffEntries[verticalIndex]) {
  2612. return this.graphicalMusicSheet.VerticalGraphicalStaffEntryContainers[horizontalIndex].StaffEntries[verticalIndex];
  2613. }
  2614. for (let i: number = horizontalIndex - 1; i >= 0; i--) {
  2615. if (this.graphicalMusicSheet.VerticalGraphicalStaffEntryContainers[i].StaffEntries[verticalIndex]) {
  2616. return this.graphicalMusicSheet.VerticalGraphicalStaffEntryContainers[i].StaffEntries[verticalIndex];
  2617. }
  2618. }
  2619. return undefined;
  2620. }
  2621. private getFirstRightNotNullStaffEntryFromContainer(horizontalIndex: number, verticalIndex: number, multiStaffInstrument: boolean): GraphicalStaffEntry {
  2622. if (this.graphicalMusicSheet.VerticalGraphicalStaffEntryContainers[horizontalIndex].StaffEntries[verticalIndex]) {
  2623. return this.graphicalMusicSheet.VerticalGraphicalStaffEntryContainers[horizontalIndex].StaffEntries[verticalIndex];
  2624. }
  2625. for (let i: number = horizontalIndex + 1; i < this.graphicalMusicSheet.VerticalGraphicalStaffEntryContainers.length; i++) {
  2626. if (this.graphicalMusicSheet.VerticalGraphicalStaffEntryContainers[i].StaffEntries[verticalIndex]) {
  2627. return this.graphicalMusicSheet.VerticalGraphicalStaffEntryContainers[i].StaffEntries[verticalIndex];
  2628. }
  2629. }
  2630. return undefined;
  2631. }
  2632. private calculateWordRepetitionInstructions(): void {
  2633. for (let i: number = 0; i < this.graphicalMusicSheet.ParentMusicSheet.SourceMeasures.length; i++) {
  2634. const sourceMeasure: SourceMeasure = this.graphicalMusicSheet.ParentMusicSheet.SourceMeasures[i];
  2635. for (let idx: number = 0, len: number = sourceMeasure.FirstRepetitionInstructions.length; idx < len; ++idx) {
  2636. const instruction: RepetitionInstruction = sourceMeasure.FirstRepetitionInstructions[idx];
  2637. this.calculateWordRepetitionInstruction(instruction, i);
  2638. }
  2639. for (let idx: number = 0, len: number = sourceMeasure.LastRepetitionInstructions.length; idx < len; ++idx) {
  2640. const instruction: RepetitionInstruction = sourceMeasure.LastRepetitionInstructions[idx];
  2641. this.calculateWordRepetitionInstruction(instruction, i);
  2642. }
  2643. }
  2644. }
  2645. private calculateRepetitionEndings(): void {
  2646. const musicsheet: MusicSheet = this.graphicalMusicSheet.ParentMusicSheet;
  2647. for (let idx: number = 0, len: number = musicsheet.Repetitions.length; idx < len; ++idx) {
  2648. const repetition: Repetition = musicsheet.Repetitions[idx];
  2649. this.calcGraphicalRepetitionEndingsRecursively(repetition);
  2650. }
  2651. }
  2652. private calculateTempoExpressions(): void {
  2653. const maxIndex: number = Math.min(this.graphicalMusicSheet.ParentMusicSheet.SourceMeasures.length - 1, this.rules.MaxMeasureToDrawIndex);
  2654. const minIndex: number = this.rules.MinMeasureToDrawIndex;
  2655. for (let i: number = minIndex; i <= maxIndex; i++) {
  2656. const sourceMeasure: SourceMeasure = this.graphicalMusicSheet.ParentMusicSheet.SourceMeasures[i];
  2657. for (let j: number = 0; j < sourceMeasure.TempoExpressions.length; j++) {
  2658. this.calculateTempoExpressionsForMultiTempoExpression(sourceMeasure, sourceMeasure.TempoExpressions[j], i);
  2659. }
  2660. }
  2661. }
  2662. private calculateMoodAndUnknownExpressions(): void {
  2663. for (let i: number = 0; i < this.graphicalMusicSheet.ParentMusicSheet.SourceMeasures.length; i++) {
  2664. const sourceMeasure: SourceMeasure = this.graphicalMusicSheet.ParentMusicSheet.SourceMeasures[i];
  2665. for (let j: number = 0; j < sourceMeasure.StaffLinkedExpressions.length; j++) {
  2666. if (!this.graphicalMusicSheet.MeasureList[i] || !this.graphicalMusicSheet.MeasureList[i][j]) {
  2667. continue;
  2668. }
  2669. if (this.graphicalMusicSheet.MeasureList[i][j].ParentStaff.ParentInstrument.Visible) {
  2670. for (let k: number = 0; k < sourceMeasure.StaffLinkedExpressions[j].length; k++) {
  2671. if ((sourceMeasure.StaffLinkedExpressions[j][k].MoodList.length > 0) ||
  2672. (sourceMeasure.StaffLinkedExpressions[j][k].UnknownList.length > 0)) {
  2673. this.calculateMoodAndUnknownExpression(sourceMeasure.StaffLinkedExpressions[j][k], i, j);
  2674. }
  2675. }
  2676. }
  2677. }
  2678. }
  2679. }
  2680. /**
  2681. * Calculates the desired stem direction depending on the number (or type) of voices.
  2682. * If more than one voice is there, the main voice (typically the first or upper voice) will get stem up direction.
  2683. * The others get stem down direction.
  2684. * @param voiceEntry the voiceEntry for which the stem direction has to be calculated
  2685. */
  2686. private calculateStemDirectionFromVoices(voiceEntry: VoiceEntry): void {
  2687. // Stem direction calculation:
  2688. const hasLink: boolean = voiceEntry.ParentSourceStaffEntry.Link !== undefined;
  2689. if (hasLink) {
  2690. // in case of StaffEntryLink don't check mainVoice / linkedVoice
  2691. if (voiceEntry === voiceEntry.ParentSourceStaffEntry.VoiceEntries[0]) {
  2692. // set stem up:
  2693. voiceEntry.WantedStemDirection = StemDirectionType.Up;
  2694. return;
  2695. } else {
  2696. // set stem down:
  2697. voiceEntry.WantedStemDirection = StemDirectionType.Down;
  2698. return;
  2699. }
  2700. } else {
  2701. if (voiceEntry.ParentVoice instanceof LinkedVoice) {
  2702. // Linked voice: set stem down:
  2703. voiceEntry.WantedStemDirection = StemDirectionType.Down;
  2704. } else {
  2705. // if this voiceEntry belongs to the mainVoice:
  2706. // check first that there are also more voices present:
  2707. if (voiceEntry.ParentSourceStaffEntry.VoiceEntries.length > 1) {
  2708. // as this voiceEntry belongs to the mainVoice: stem Up
  2709. voiceEntry.WantedStemDirection = StemDirectionType.Up;
  2710. }
  2711. }
  2712. }
  2713. // setBeamNotesWantedStemDirections() will be called at end of measure (createGraphicalMeasure)
  2714. }
  2715. /** Sets a voiceEntry's stem direction to one already set in other notes in its beam, if it has one. */
  2716. private setBeamNotesWantedStemDirections(voiceEntry: VoiceEntry): void {
  2717. if (voiceEntry.WantedStemDirection === StemDirectionType.Undefined &&
  2718. voiceEntry.Notes.length > 0) {
  2719. const beam: Beam = voiceEntry.Notes[0].NoteBeam;
  2720. if (beam) {
  2721. // if there is a beam, find any already set stemDirection in the beam:
  2722. for (const note of beam.Notes) {
  2723. if (note.ParentVoiceEntry === voiceEntry) {
  2724. continue;
  2725. } else if (note.ParentVoiceEntry.WantedStemDirection !== StemDirectionType.Undefined) {
  2726. // set the stem direction
  2727. voiceEntry.WantedStemDirection = note.ParentVoiceEntry.WantedStemDirection;
  2728. break;
  2729. }
  2730. }
  2731. }
  2732. }
  2733. }
  2734. }