MusicSystemBuilder.ts 61 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189
  1. import {GraphicalMeasure} from "./GraphicalMeasure";
  2. import {GraphicalMusicPage} from "./GraphicalMusicPage";
  3. import {EngravingRules} from "./EngravingRules";
  4. import {RhythmInstruction} from "../VoiceData/Instructions/RhythmInstruction";
  5. import {KeyInstruction} from "../VoiceData/Instructions/KeyInstruction";
  6. import {ClefInstruction} from "../VoiceData/Instructions/ClefInstruction";
  7. import {SourceMeasure} from "../VoiceData/SourceMeasure";
  8. import {MusicSystem} from "./MusicSystem";
  9. import {BoundingBox} from "./BoundingBox";
  10. import {Staff} from "../VoiceData/Staff";
  11. import {Instrument} from "../Instrument";
  12. import {PointF2D} from "../../Common/DataObjects/PointF2D";
  13. import {StaffLine} from "./StaffLine";
  14. import {GraphicalLine} from "./GraphicalLine";
  15. import {SourceStaffEntry} from "../VoiceData/SourceStaffEntry";
  16. import {AbstractNotationInstruction} from "../VoiceData/Instructions/AbstractNotationInstruction";
  17. import {SystemLinesEnum} from "./SystemLinesEnum";
  18. import {GraphicalMusicSheet} from "./GraphicalMusicSheet";
  19. import {MusicSheetCalculator} from "./MusicSheetCalculator";
  20. import {MidiInstrument} from "../VoiceData/Instructions/ClefInstruction";
  21. import {CollectionUtil} from "../../Util/CollectionUtil";
  22. import {SystemLinePosition} from "./SystemLinePosition";
  23. export class MusicSystemBuilder {
  24. protected measureList: GraphicalMeasure[][];
  25. protected graphicalMusicSheet: GraphicalMusicSheet;
  26. protected currentSystemParams: SystemBuildParameters;
  27. protected numberOfVisibleStaffLines: number;
  28. protected rules: EngravingRules;
  29. protected measureListIndex: number;
  30. protected musicSystems: MusicSystem[] = [];
  31. /**
  32. * Does the mapping from the currently visible staves to the global staff-list of the music sheet.
  33. */
  34. protected visibleStaffIndices: number[];
  35. protected activeRhythm: RhythmInstruction[];
  36. protected activeKeys: KeyInstruction[];
  37. protected activeClefs: ClefInstruction[];
  38. protected globalSystemIndex: number = 0;
  39. protected leadSheet: boolean = false;
  40. public initialize(
  41. graphicalMusicSheet: GraphicalMusicSheet, measureList: GraphicalMeasure[][], numberOfStaffLines: number): void {
  42. this.leadSheet = graphicalMusicSheet.LeadSheet;
  43. this.graphicalMusicSheet = graphicalMusicSheet;
  44. this.rules = this.graphicalMusicSheet.ParentMusicSheet.Rules;
  45. this.measureList = measureList;
  46. this.numberOfVisibleStaffLines = numberOfStaffLines;
  47. this.activeRhythm = new Array(this.numberOfVisibleStaffLines);
  48. this.activeKeys = new Array(this.numberOfVisibleStaffLines);
  49. this.activeClefs = new Array(this.numberOfVisibleStaffLines);
  50. this.initializeActiveInstructions(this.measureList[0]);
  51. }
  52. public buildMusicSystems(): MusicSystem[] {
  53. const systemMaxWidth: number = this.getFullPageSystemWidth();
  54. let prevMeasureEndsPart: boolean = false;
  55. this.measureListIndex = 0;
  56. this.currentSystemParams = new SystemBuildParameters();
  57. // the first System - create also its Labels
  58. this.currentSystemParams.currentSystem = this.initMusicSystem();
  59. let numberOfMeasures: number = 0;
  60. for (let idx: number = 0, len: number = this.measureList.length; idx < len; ++idx) {
  61. if (this.measureList[idx].length > 0) {
  62. numberOfMeasures++;
  63. }
  64. }
  65. // go through measures and add to system until system gets too long -> finish system and start next system [line break, new system].
  66. while (this.measureListIndex < numberOfMeasures) {
  67. const graphicalMeasures: GraphicalMeasure[] = this.measureList[this.measureListIndex];
  68. for (let idx: number = 0, len: number = graphicalMeasures.length; idx < len; ++idx) {
  69. graphicalMeasures[idx].resetLayout();
  70. }
  71. const sourceMeasure: SourceMeasure = graphicalMeasures[0].parentSourceMeasure;
  72. const sourceMeasureEndsPart: boolean = sourceMeasure.HasEndLine;
  73. const isSystemStartMeasure: boolean = this.currentSystemParams.IsSystemStartMeasure();
  74. const isFirstSourceMeasure: boolean = sourceMeasure === this.graphicalMusicSheet.ParentMusicSheet.getFirstSourceMeasure();
  75. let currentMeasureBeginInstructionsWidth: number = this.rules.MeasureLeftMargin;
  76. let currentMeasureEndInstructionsWidth: number = 0;
  77. // calculate the current Measure Width:
  78. // The width of a measure is build up from
  79. // 1. the begin instructions (clef, Key, Rhythm),
  80. // 2. the staff entries (= notes) and
  81. // 3. the end instructions (actually only clefs)
  82. const measureStartLine: SystemLinesEnum = this.getMeasureStartLine();
  83. currentMeasureBeginInstructionsWidth += this.getLineWidth(graphicalMeasures[0], measureStartLine, isSystemStartMeasure);
  84. if (!this.leadSheet) {
  85. let forceShowRhythm: boolean = false;
  86. if (prevMeasureEndsPart && this.rules.ShowRhythmAgainAfterPartEndOrFinalBarline) {
  87. forceShowRhythm = true;
  88. }
  89. currentMeasureBeginInstructionsWidth += this.addBeginInstructions( graphicalMeasures,
  90. isSystemStartMeasure,
  91. isFirstSourceMeasure || forceShowRhythm);
  92. // forceShowRhythm could be a fourth parameter instead in addBeginInstructions, but only affects RhythmInstruction for now.
  93. currentMeasureEndInstructionsWidth += this.addEndInstructions(graphicalMeasures);
  94. }
  95. let currentMeasureVarWidth: number = 0;
  96. for (let i: number = 0; i < this.numberOfVisibleStaffLines; i++) {
  97. currentMeasureVarWidth = Math.max(currentMeasureVarWidth, graphicalMeasures[i].minimumStaffEntriesWidth);
  98. }
  99. // take into account the LineWidth after each Measure
  100. const measureEndLine: SystemLinesEnum = this.getMeasureEndLine();
  101. currentMeasureEndInstructionsWidth += this.getLineWidth(graphicalMeasures[0], measureEndLine, isSystemStartMeasure);
  102. let nextMeasureBeginInstructionWidth: number = this.rules.MeasureLeftMargin;
  103. // Check if there are key or rhythm change instructions within the next measure:
  104. let nextSourceMeasure: SourceMeasure = undefined;
  105. if (this.measureListIndex + 1 < this.measureList.length) {
  106. const nextGraphicalMeasures: GraphicalMeasure[] = this.measureList[this.measureListIndex + 1];
  107. nextSourceMeasure = nextGraphicalMeasures[0].parentSourceMeasure;
  108. if (nextSourceMeasure.hasBeginInstructions()) {
  109. nextMeasureBeginInstructionWidth += this.addBeginInstructions(nextGraphicalMeasures, false, false);
  110. }
  111. }
  112. const totalMeasureWidth: number = currentMeasureBeginInstructionsWidth + currentMeasureEndInstructionsWidth + currentMeasureVarWidth;
  113. const measureFitsInSystem: boolean = this.currentSystemParams.currentWidth + totalMeasureWidth + nextMeasureBeginInstructionWidth < systemMaxWidth;
  114. const doXmlPageBreak: boolean = this.rules.NewPageAtXMLNewPageAttribute && sourceMeasure.printNewPageXml;
  115. const doXmlLineBreak: boolean = doXmlPageBreak || // also create new system if doing page break
  116. (this.rules.NewSystemAtXMLNewSystemAttribute && sourceMeasure.printNewSystemXml);
  117. if (isSystemStartMeasure || (measureFitsInSystem && !doXmlLineBreak)) {
  118. this.addMeasureToSystem(
  119. graphicalMeasures, measureStartLine, measureEndLine, totalMeasureWidth,
  120. currentMeasureBeginInstructionsWidth, currentMeasureVarWidth, currentMeasureEndInstructionsWidth
  121. );
  122. this.updateActiveClefs(sourceMeasure, graphicalMeasures);
  123. this.measureListIndex++;
  124. if (sourceMeasureEndsPart) {
  125. this.finalizeCurrentAndCreateNewSystem(graphicalMeasures, true, false);
  126. }
  127. prevMeasureEndsPart = sourceMeasureEndsPart;
  128. } else {
  129. // finalize current system and prepare a new one
  130. this.finalizeCurrentAndCreateNewSystem(graphicalMeasures, false, true, doXmlPageBreak);
  131. // don't increase measure index to check this measure now again
  132. // don't set prevMeasureEndsPart in this case! because we will loop with the same measure again.
  133. }
  134. }
  135. if (this.currentSystemParams.systemMeasures.length > 0) {
  136. this.finalizeCurrentAndCreateNewSystem(this.measureList[this.measureList.length - 1], true, false);
  137. }
  138. return this.musicSystems;
  139. }
  140. /**
  141. * calculates the y positions of the staff lines within a system and
  142. * furthermore the y positions of the systems themselves.
  143. */
  144. public calculateSystemYLayout(): void {
  145. for (const musicSystem of this.musicSystems) {
  146. this.optimizeDistanceBetweenStaffLines(musicSystem);
  147. }
  148. // set y positions of systems using the previous system and a fixed distance.
  149. this.calculateMusicSystemsRelativePositions();
  150. }
  151. /**
  152. * Set the Width of the staff-Measures of one source measure.
  153. * @param graphicalMeasures
  154. * @param width
  155. * @param beginInstrWidth
  156. * @param endInstrWidth
  157. */
  158. protected setMeasureWidth(graphicalMeasures: GraphicalMeasure[], width: number, beginInstrWidth: number, endInstrWidth: number): void {
  159. for (let idx: number = 0, len: number = graphicalMeasures.length; idx < len; ++idx) {
  160. const measure: GraphicalMeasure = graphicalMeasures[idx];
  161. measure.setWidth(width);
  162. if (beginInstrWidth > 0) {
  163. measure.beginInstructionsWidth = beginInstrWidth;
  164. }
  165. if (endInstrWidth > 0) {
  166. measure.endInstructionsWidth = endInstrWidth;
  167. }
  168. }
  169. }
  170. /**
  171. * When the actual source measure doesn't fit any more, this method finalizes the current system and
  172. * opens up a new empty system, where the actual measure will be added in the next iteration.
  173. * @param measures
  174. * @param isPartEndingSystem
  175. */
  176. protected finalizeCurrentAndCreateNewSystem(measures: GraphicalMeasure[],
  177. systemEndsPart: boolean = false,
  178. checkExtraInstructionMeasure: boolean = true,
  179. startNewPage: boolean = false): void {
  180. this.currentSystemParams.currentSystem.breaksPage = startNewPage;
  181. this.adaptRepetitionLineWithIfNeeded();
  182. if (measures !== undefined &&
  183. checkExtraInstructionMeasure) {
  184. this.checkAndCreateExtraInstructionMeasure(measures);
  185. }
  186. this.stretchMusicSystem(systemEndsPart);
  187. this.currentSystemParams = new SystemBuildParameters();
  188. if (measures !== undefined &&
  189. this.measureListIndex < this.measureList.length) {
  190. this.currentSystemParams.currentSystem = this.initMusicSystem();
  191. }
  192. }
  193. /**
  194. * If a line repetition is ending and a new line repetition is starting at the end of the system,
  195. * the double repetition line has to be split into two: one at the currently ending system and
  196. * one at the next system.
  197. * (this should be refactored at some point to not use a combined end/start line but always separated lines)
  198. */
  199. protected adaptRepetitionLineWithIfNeeded(): void {
  200. const systemMeasures: MeasureBuildParameters[] = this.currentSystemParams.systemMeasures;
  201. if (systemMeasures.length >= 1) {
  202. const measures: GraphicalMeasure[] =
  203. this.currentSystemParams.currentSystem.GraphicalMeasures[this.currentSystemParams.currentSystem.GraphicalMeasures.length - 1];
  204. const measureParams: MeasureBuildParameters = systemMeasures[systemMeasures.length - 1];
  205. let diff: number = 0.0;
  206. if (measureParams.endLine === SystemLinesEnum.DotsBoldBoldDots) {
  207. measureParams.endLine = SystemLinesEnum.DotsThinBold;
  208. diff = measures[0].getLineWidth(SystemLinesEnum.DotsBoldBoldDots) / 2 - measures[0].getLineWidth(SystemLinesEnum.DotsThinBold);
  209. }
  210. this.currentSystemParams.currentSystemFixWidth -= diff;
  211. for (let idx: number = 0, len: number = measures.length; idx < len; ++idx) {
  212. const measure: GraphicalMeasure = measures[idx];
  213. measure.endInstructionsWidth -= diff;
  214. }
  215. }
  216. }
  217. protected addMeasureToSystem(
  218. graphicalMeasures: GraphicalMeasure[], measureStartLine: SystemLinesEnum, measureEndLine: SystemLinesEnum,
  219. totalMeasureWidth: number, currentMeasureBeginInstructionsWidth: number, currentVarWidth: number, currentMeasureEndInstructionsWidth: number
  220. ): void {
  221. this.currentSystemParams.systemMeasures.push({beginLine: measureStartLine, endLine: measureEndLine});
  222. this.setMeasureWidth(
  223. graphicalMeasures, totalMeasureWidth, currentMeasureBeginInstructionsWidth, currentMeasureEndInstructionsWidth
  224. );
  225. this.addStaveMeasuresToSystem(graphicalMeasures);
  226. this.currentSystemParams.currentWidth += totalMeasureWidth;
  227. this.currentSystemParams.currentSystemFixWidth += currentMeasureBeginInstructionsWidth + currentMeasureEndInstructionsWidth;
  228. this.currentSystemParams.currentSystemVarWidth += currentVarWidth;
  229. this.currentSystemParams.systemMeasureIndex++;
  230. }
  231. /**
  232. * Initialize a new [[MusicSystem]].
  233. * @returns {MusicSystem}
  234. */
  235. protected initMusicSystem(): MusicSystem {
  236. const musicSystem: MusicSystem = MusicSheetCalculator.symbolFactory.createMusicSystem(this.globalSystemIndex++, this.rules);
  237. this.musicSystems.push(musicSystem);
  238. this.layoutSystemStaves(musicSystem);
  239. musicSystem.createMusicSystemLabel(
  240. this.rules.InstrumentLabelTextHeight,
  241. this.rules.SystemLabelsRightMargin,
  242. this.rules.LabelMarginBorderFactor,
  243. this.musicSystems.length === 1
  244. );
  245. return musicSystem;
  246. }
  247. /**
  248. * Get the width the system should have for a given page width.
  249. * @returns {number}
  250. */
  251. protected getFullPageSystemWidth(): number {
  252. return this.graphicalMusicSheet.ParentMusicSheet.pageWidth - this.rules.PageLeftMargin
  253. - this.rules.PageRightMargin - this.rules.SystemLeftMargin - this.rules.SystemRightMargin;
  254. }
  255. protected layoutSystemStaves(musicSystem: MusicSystem): void {
  256. const systemWidth: number = this.getFullPageSystemWidth();
  257. const boundingBox: BoundingBox = musicSystem.PositionAndShape;
  258. boundingBox.BorderLeft = 0.0;
  259. boundingBox.BorderRight = systemWidth;
  260. boundingBox.BorderTop = 0.0;
  261. const staffList: Staff[] = [];
  262. const instruments: Instrument[] = this.graphicalMusicSheet.ParentMusicSheet.Instruments;
  263. for (let idx: number = 0, len: number = instruments.length; idx < len; ++idx) {
  264. const instrument: Instrument = instruments[idx];
  265. if (instrument.Voices.length === 0 || !instrument.Visible) {
  266. continue;
  267. }
  268. for (let idx2: number = 0, len2: number = instrument.Staves.length; idx2 < len2; ++idx2) {
  269. const staff: Staff = instrument.Staves[idx2];
  270. staffList.push(staff);
  271. }
  272. }
  273. let multiLyrics: boolean = false;
  274. if (this.leadSheet) {
  275. for (let idx: number = 0, len: number = staffList.length; idx < len; ++idx) {
  276. const staff: Staff = staffList[idx];
  277. if (staff.ParentInstrument.LyricVersesNumbers.length > 1) {
  278. multiLyrics = true;
  279. break;
  280. }
  281. }
  282. }
  283. let yOffsetSum: number = 0;
  284. for (let i: number = 0; i < staffList.length; i++) {
  285. this.addStaffLineToMusicSystem(musicSystem, yOffsetSum, staffList[i]);
  286. yOffsetSum += this.rules.StaffHeight;
  287. if (i + 1 < staffList.length) {
  288. let yOffset: number = 0;
  289. if (this.leadSheet && !multiLyrics) {
  290. yOffset = 2.5;
  291. } else {
  292. if (staffList[i].ParentInstrument === staffList[i + 1].ParentInstrument) {
  293. yOffset = this.rules.BetweenStaffDistance;
  294. } else {
  295. yOffset = this.rules.StaffDistance;
  296. }
  297. }
  298. yOffsetSum += yOffset;
  299. }
  300. }
  301. boundingBox.BorderBottom = yOffsetSum;
  302. }
  303. /**
  304. * Calculate the [[StaffLine]](s) needed for a [[MusicSystem]].
  305. * @param musicSystem
  306. * @param relativeYPosition
  307. * @param staff
  308. */
  309. protected addStaffLineToMusicSystem(musicSystem: MusicSystem, relativeYPosition: number, staff: Staff): void {
  310. if (musicSystem) {
  311. const staffLine: StaffLine = MusicSheetCalculator.symbolFactory.createStaffLine(musicSystem, staff);
  312. musicSystem.StaffLines.push(staffLine);
  313. const boundingBox: BoundingBox = staffLine.PositionAndShape;
  314. const relativePosition: PointF2D = new PointF2D();
  315. relativePosition.x = 0.0;
  316. boundingBox.BorderRight = musicSystem.PositionAndShape.Size.width;
  317. relativePosition.y = relativeYPosition;
  318. boundingBox.RelativePosition = relativePosition;
  319. boundingBox.BorderLeft = 0.0;
  320. boundingBox.BorderTop = 0.0;
  321. boundingBox.BorderBottom = this.rules.StaffHeight;
  322. for (let i: number = 0; i < 5; i++) {
  323. const start: PointF2D = new PointF2D();
  324. start.x = 0.0;
  325. start.y = i * this.rules.StaffHeight / 4;
  326. const end: PointF2D = new PointF2D();
  327. end.x = staffLine.PositionAndShape.Size.width;
  328. end.y = i * this.rules.StaffHeight / 4;
  329. if (this.leadSheet) {
  330. start.y = end.y = 0;
  331. }
  332. staffLine.StaffLines[i] = new GraphicalLine(start, end, this.rules.StaffLineWidth);
  333. }
  334. }
  335. }
  336. /**
  337. * Initialize the active Instructions from the first [[SourceMeasure]] of first [[SourceMusicPart]].
  338. * @param measureList
  339. */
  340. protected initializeActiveInstructions(measureList: GraphicalMeasure[]): void {
  341. const firstSourceMeasure: SourceMeasure = this.graphicalMusicSheet.ParentMusicSheet.getFirstSourceMeasure();
  342. if (firstSourceMeasure) {
  343. this.visibleStaffIndices = this.graphicalMusicSheet.getVisibleStavesIndicesFromSourceMeasure(measureList);
  344. for (let i: number = 0, len: number = this.visibleStaffIndices.length; i < len; i++) {
  345. const staffIndex: number = this.visibleStaffIndices[i];
  346. const graphicalMeasure: GraphicalMeasure = this.graphicalMusicSheet
  347. .getGraphicalMeasureFromSourceMeasureAndIndex(firstSourceMeasure, staffIndex);
  348. this.activeClefs[i] = <ClefInstruction>firstSourceMeasure.FirstInstructionsStaffEntries[staffIndex].Instructions[0];
  349. let keyInstruction: KeyInstruction = KeyInstruction.copy(
  350. <KeyInstruction>firstSourceMeasure.FirstInstructionsStaffEntries[staffIndex].Instructions[1]);
  351. keyInstruction = this.transposeKeyInstruction(keyInstruction, graphicalMeasure);
  352. this.activeKeys[i] = keyInstruction;
  353. this.activeRhythm[i] = <RhythmInstruction>firstSourceMeasure.FirstInstructionsStaffEntries[staffIndex].Instructions[2];
  354. }
  355. }
  356. }
  357. protected transposeKeyInstruction(keyInstruction: KeyInstruction, graphicalMeasure: GraphicalMeasure): KeyInstruction {
  358. if (this.graphicalMusicSheet.ParentMusicSheet.Transpose !== 0
  359. && graphicalMeasure.ParentStaff.ParentInstrument.MidiInstrumentId !== MidiInstrument.Percussion
  360. && MusicSheetCalculator.transposeCalculator !== undefined
  361. ) {
  362. MusicSheetCalculator.transposeCalculator.transposeKey(
  363. keyInstruction,
  364. this.graphicalMusicSheet.ParentMusicSheet.Transpose
  365. );
  366. }
  367. return keyInstruction;
  368. }
  369. /**
  370. * Calculate the width needed for Instructions (Key, Clef, Rhythm, Repetition) for the measure.
  371. * @param measures
  372. * @param isSystemFirstMeasure
  373. * @param isFirstSourceMeasure
  374. * @returns {number}
  375. */
  376. protected addBeginInstructions(measures: GraphicalMeasure[], isSystemFirstMeasure: boolean, isFirstSourceMeasure: boolean): number {
  377. const measureCount: number = measures.length;
  378. if (measureCount === 0) {
  379. return 0;
  380. }
  381. let totalBeginInstructionLengthX: number = 0.0;
  382. const sourceMeasure: SourceMeasure = measures[0].parentSourceMeasure;
  383. for (let idx: number = 0; idx < measureCount; ++idx) {
  384. const measure: GraphicalMeasure = measures[idx];
  385. const staffIndex: number = this.visibleStaffIndices[idx];
  386. const beginInstructionsStaffEntry: SourceStaffEntry = sourceMeasure.FirstInstructionsStaffEntries[staffIndex];
  387. const beginInstructionLengthX: number = this.AddInstructionsAtMeasureBegin(
  388. beginInstructionsStaffEntry, measure,
  389. idx, isFirstSourceMeasure,
  390. isSystemFirstMeasure
  391. );
  392. totalBeginInstructionLengthX = Math.max(totalBeginInstructionLengthX, beginInstructionLengthX);
  393. }
  394. return totalBeginInstructionLengthX;
  395. }
  396. /**
  397. * Calculates the width needed for Instructions (Clef, Repetition) for the measure.
  398. * @param measures
  399. * @returns {number}
  400. */
  401. protected addEndInstructions(measures: GraphicalMeasure[]): number {
  402. const measureCount: number = measures.length;
  403. if (measureCount === 0) {
  404. return 0;
  405. }
  406. let totalEndInstructionLengthX: number = 0.5;
  407. const sourceMeasure: SourceMeasure = measures[0].parentSourceMeasure;
  408. for (let idx: number = 0; idx < measureCount; idx++) {
  409. const measure: GraphicalMeasure = measures[idx];
  410. const staffIndex: number = this.visibleStaffIndices[idx];
  411. const endInstructionsStaffEntry: SourceStaffEntry = sourceMeasure.LastInstructionsStaffEntries[staffIndex];
  412. const endInstructionLengthX: number = this.addInstructionsAtMeasureEnd(endInstructionsStaffEntry, measure);
  413. totalEndInstructionLengthX = Math.max(totalEndInstructionLengthX, endInstructionLengthX);
  414. }
  415. return totalEndInstructionLengthX;
  416. }
  417. protected AddInstructionsAtMeasureBegin(firstEntry: SourceStaffEntry, measure: GraphicalMeasure,
  418. visibleStaffIdx: number, isFirstSourceMeasure: boolean, isSystemStartMeasure: boolean): number {
  419. let instructionsLengthX: number = 0;
  420. let currentClef: ClefInstruction = undefined;
  421. let currentKey: KeyInstruction = undefined;
  422. let currentRhythm: RhythmInstruction = undefined;
  423. if (firstEntry) {
  424. for (let idx: number = 0, len: number = firstEntry.Instructions.length; idx < len; ++idx) {
  425. const abstractNotationInstruction: AbstractNotationInstruction = firstEntry.Instructions[idx];
  426. if (abstractNotationInstruction instanceof ClefInstruction) {
  427. currentClef = <ClefInstruction>abstractNotationInstruction;
  428. } else if (abstractNotationInstruction instanceof KeyInstruction) {
  429. currentKey = <KeyInstruction>abstractNotationInstruction;
  430. } else if (abstractNotationInstruction instanceof RhythmInstruction) {
  431. currentRhythm = <RhythmInstruction>abstractNotationInstruction;
  432. }
  433. }
  434. }
  435. if (isSystemStartMeasure) {
  436. if (!currentClef) {
  437. currentClef = this.activeClefs[visibleStaffIdx];
  438. }
  439. if (!currentKey) {
  440. currentKey = this.activeKeys[visibleStaffIdx];
  441. }
  442. if (isFirstSourceMeasure && !currentRhythm) {
  443. currentRhythm = this.activeRhythm[visibleStaffIdx];
  444. }
  445. }
  446. let clefAdded: boolean = false;
  447. let keyAdded: boolean = false;
  448. let rhythmAdded: boolean = false;
  449. if (currentClef) {
  450. measure.addClefAtBegin(currentClef);
  451. clefAdded = true;
  452. } else {
  453. currentClef = this.activeClefs[visibleStaffIdx];
  454. }
  455. if (currentKey) {
  456. currentKey = this.transposeKeyInstruction(currentKey, measure);
  457. const previousKey: KeyInstruction = isSystemStartMeasure ? undefined : this.activeKeys[visibleStaffIdx];
  458. measure.addKeyAtBegin(currentKey, previousKey, currentClef);
  459. keyAdded = true;
  460. }
  461. if (currentRhythm !== undefined && currentRhythm.PrintObject && this.rules.RenderTimeSignatures) {
  462. measure.addRhythmAtBegin(currentRhythm);
  463. rhythmAdded = true;
  464. }
  465. if (clefAdded || keyAdded || rhythmAdded) {
  466. instructionsLengthX += measure.beginInstructionsWidth;
  467. if (rhythmAdded) {
  468. instructionsLengthX += this.rules.RhythmRightMargin;
  469. }
  470. }
  471. return instructionsLengthX;
  472. }
  473. protected addInstructionsAtMeasureEnd(lastEntry: SourceStaffEntry, measure: GraphicalMeasure): number {
  474. if (!lastEntry || !lastEntry.Instructions || lastEntry.Instructions.length === 0) {
  475. return 0;
  476. }
  477. for (let idx: number = 0, len: number = lastEntry.Instructions.length; idx < len; ++idx) {
  478. const abstractNotationInstruction: AbstractNotationInstruction = lastEntry.Instructions[idx];
  479. if (abstractNotationInstruction instanceof ClefInstruction) {
  480. const activeClef: ClefInstruction = <ClefInstruction>abstractNotationInstruction;
  481. measure.addClefAtEnd(activeClef);
  482. }
  483. }
  484. return this.rules.MeasureRightMargin + measure.endInstructionsWidth;
  485. }
  486. /**
  487. * Track down and update the active ClefInstruction in Measure's StaffEntries.
  488. * This has to be done after the measure is added to a system
  489. * (otherwise already the check if the measure fits to the system would update the active clefs..)
  490. * @param measure
  491. * @param graphicalMeasures
  492. */
  493. protected updateActiveClefs(measure: SourceMeasure, graphicalMeasures: GraphicalMeasure[]): void {
  494. for (let visStaffIdx: number = 0, len: number = graphicalMeasures.length; visStaffIdx < len; visStaffIdx++) {
  495. const staffIndex: number = this.visibleStaffIndices[visStaffIdx];
  496. const firstEntry: SourceStaffEntry = measure.FirstInstructionsStaffEntries[staffIndex];
  497. if (firstEntry) {
  498. for (let idx: number = 0, len2: number = firstEntry.Instructions.length; idx < len2; ++idx) {
  499. const abstractNotationInstruction: AbstractNotationInstruction = firstEntry.Instructions[idx];
  500. if (abstractNotationInstruction instanceof ClefInstruction) {
  501. this.activeClefs[visStaffIdx] = <ClefInstruction>abstractNotationInstruction;
  502. } else if (abstractNotationInstruction instanceof KeyInstruction) {
  503. this.activeKeys[visStaffIdx] = <KeyInstruction>abstractNotationInstruction;
  504. } else if (abstractNotationInstruction instanceof RhythmInstruction) {
  505. this.activeRhythm[visStaffIdx] = <RhythmInstruction>abstractNotationInstruction;
  506. }
  507. }
  508. }
  509. const entries: SourceStaffEntry[] = measure.getEntriesPerStaff(staffIndex);
  510. for (let idx: number = 0, len2: number = entries.length; idx < len2; ++idx) {
  511. const staffEntry: SourceStaffEntry = entries[idx];
  512. if (staffEntry.Instructions) {
  513. for (let idx2: number = 0, len3: number = staffEntry.Instructions.length; idx2 < len3; ++idx2) {
  514. const abstractNotationInstruction: AbstractNotationInstruction = staffEntry.Instructions[idx2];
  515. if (abstractNotationInstruction instanceof ClefInstruction) {
  516. this.activeClefs[visStaffIdx] = <ClefInstruction>abstractNotationInstruction;
  517. }
  518. }
  519. }
  520. }
  521. const lastEntry: SourceStaffEntry = measure.LastInstructionsStaffEntries[staffIndex];
  522. if (lastEntry) {
  523. const instructions: AbstractNotationInstruction[] = lastEntry.Instructions;
  524. for (let idx: number = 0, len3: number = instructions.length; idx < len3; ++idx) {
  525. const abstractNotationInstruction: AbstractNotationInstruction = instructions[idx];
  526. if (abstractNotationInstruction instanceof ClefInstruction) {
  527. this.activeClefs[visStaffIdx] = <ClefInstruction>abstractNotationInstruction;
  528. }
  529. }
  530. }
  531. }
  532. }
  533. /**
  534. * Check if an extra Instruction [[Measure]] is needed.
  535. * @param measures
  536. */
  537. protected checkAndCreateExtraInstructionMeasure(measures: GraphicalMeasure[]): void {
  538. const firstStaffEntries: SourceStaffEntry[] = measures[0].parentSourceMeasure.FirstInstructionsStaffEntries;
  539. const visibleInstructionEntries: SourceStaffEntry[] = [];
  540. for (let idx: number = 0, len: number = measures.length; idx < len; ++idx) {
  541. const measure: GraphicalMeasure = measures[idx];
  542. visibleInstructionEntries.push(firstStaffEntries[measure.ParentStaff.idInMusicSheet]);
  543. }
  544. let maxMeasureWidth: number = 0;
  545. for (let visStaffIdx: number = 0, len: number = visibleInstructionEntries.length; visStaffIdx < len; ++visStaffIdx) {
  546. const sse: SourceStaffEntry = visibleInstructionEntries[visStaffIdx];
  547. if (!sse) {
  548. continue;
  549. }
  550. const instructions: AbstractNotationInstruction[] = sse.Instructions;
  551. let keyInstruction: KeyInstruction = undefined;
  552. let rhythmInstruction: RhythmInstruction = undefined;
  553. for (let idx2: number = 0, len2: number = instructions.length; idx2 < len2; ++idx2) {
  554. const instruction: AbstractNotationInstruction = instructions[idx2];
  555. if (instruction instanceof KeyInstruction && (<KeyInstruction>instruction).Key !== this.activeKeys[visStaffIdx].Key) {
  556. keyInstruction = <KeyInstruction>instruction;
  557. }
  558. if (instruction instanceof RhythmInstruction && (<RhythmInstruction>instruction) !== this.activeRhythm[visStaffIdx]) {
  559. rhythmInstruction = <RhythmInstruction>instruction;
  560. }
  561. }
  562. if (keyInstruction !== undefined || rhythmInstruction) {
  563. const measureWidth: number = this.addExtraInstructionMeasure(visStaffIdx, keyInstruction, rhythmInstruction);
  564. maxMeasureWidth = Math.max(maxMeasureWidth, measureWidth);
  565. }
  566. }
  567. if (maxMeasureWidth > 0) {
  568. this.currentSystemParams.systemMeasures.push({
  569. beginLine: SystemLinesEnum.None,
  570. endLine: SystemLinesEnum.None,
  571. });
  572. this.currentSystemParams.currentWidth += maxMeasureWidth;
  573. this.currentSystemParams.currentSystemFixWidth += maxMeasureWidth;
  574. }
  575. }
  576. protected addExtraInstructionMeasure(visStaffIdx: number, keyInstruction: KeyInstruction, rhythmInstruction: RhythmInstruction): number {
  577. const currentSystem: MusicSystem = this.currentSystemParams.currentSystem;
  578. const measures: GraphicalMeasure[] = [];
  579. const measure: GraphicalMeasure = MusicSheetCalculator.symbolFactory.createExtraGraphicalMeasure(currentSystem.StaffLines[visStaffIdx]);
  580. measures.push(measure);
  581. if (keyInstruction) {
  582. measure.addKeyAtBegin(keyInstruction, this.activeKeys[visStaffIdx], this.activeClefs[visStaffIdx]);
  583. }
  584. if (rhythmInstruction !== undefined && rhythmInstruction.PrintObject) {
  585. measure.addRhythmAtBegin(rhythmInstruction);
  586. }
  587. measure.PositionAndShape.BorderLeft = 0.0;
  588. measure.PositionAndShape.BorderTop = 0.0;
  589. measure.PositionAndShape.BorderBottom = this.rules.StaffHeight;
  590. const width: number = this.rules.MeasureLeftMargin + measure.beginInstructionsWidth + this.rules.MeasureRightMargin;
  591. measure.PositionAndShape.BorderRight = width;
  592. currentSystem.StaffLines[visStaffIdx].Measures.push(measure);
  593. return width;
  594. }
  595. /**
  596. * Add all current vertical Measures to currentSystem.
  597. * @param graphicalMeasures
  598. */
  599. protected addStaveMeasuresToSystem(graphicalMeasures: GraphicalMeasure[]): void {
  600. if (graphicalMeasures[0]) {
  601. const gmeasures: GraphicalMeasure[] = [];
  602. for (let i: number = 0; i < graphicalMeasures.length; i++) {
  603. gmeasures.push(graphicalMeasures[i]);
  604. }
  605. const currentSystem: MusicSystem = this.currentSystemParams.currentSystem;
  606. for (let visStaffIdx: number = 0; visStaffIdx < this.numberOfVisibleStaffLines; visStaffIdx++) {
  607. const measure: GraphicalMeasure = gmeasures[visStaffIdx];
  608. currentSystem.StaffLines[visStaffIdx].Measures.push(measure);
  609. measure.ParentStaffLine = currentSystem.StaffLines[visStaffIdx];
  610. }
  611. currentSystem.AddGraphicalMeasures(gmeasures);
  612. }
  613. }
  614. /**
  615. * Return the width of the corresponding [[SystemLine]] and set the corresponding [[SystemLineEnum]].
  616. * @returns {SystemLinesEnum}
  617. */
  618. protected getMeasureStartLine(): SystemLinesEnum {
  619. const thisMeasureBeginsLineRep: boolean = this.thisMeasureBeginsLineRepetition();
  620. if (thisMeasureBeginsLineRep) {
  621. const isSystemStartMeasure: boolean = this.currentSystemParams.IsSystemStartMeasure();
  622. const isGlobalFirstMeasure: boolean = this.measureListIndex === 0;
  623. if (this.previousMeasureEndsLineRepetition() && !isSystemStartMeasure) {
  624. return SystemLinesEnum.DotsBoldBoldDots;
  625. }
  626. if (!isGlobalFirstMeasure) {
  627. return SystemLinesEnum.BoldThinDots;
  628. }
  629. }
  630. return SystemLinesEnum.None;
  631. }
  632. protected getMeasureEndLine(): SystemLinesEnum {
  633. let sourceMeasure: SourceMeasure = undefined;
  634. try {
  635. sourceMeasure = this.measureList[this.measureListIndex][0].parentSourceMeasure;
  636. } finally {
  637. // do nothing
  638. }
  639. if (this.nextMeasureBeginsLineRepetition() && this.thisMeasureEndsLineRepetition()) {
  640. return SystemLinesEnum.DotsBoldBoldDots;
  641. }
  642. if (this.thisMeasureEndsLineRepetition()) {
  643. return SystemLinesEnum.DotsThinBold;
  644. }
  645. // always end piece with final barline: not a good idea. user should be able to override final barline.
  646. // also, selecting range of measures to draw would always end with final barline, even if extract is from the middle of the piece
  647. // this was probably done before we parsed the barline type from XML.
  648. /*if (this.measureListIndex === this.measureList.length - 1 || this.measureList[this.measureListIndex][0].parentSourceMeasure.endsPiece) {
  649. return SystemLinesEnum.ThinBold;
  650. }*/
  651. if (this.nextMeasureHasKeyInstructionChange() || this.thisMeasureEndsWordRepetition() || this.nextMeasureBeginsWordRepetition()) {
  652. return SystemLinesEnum.DoubleThin;
  653. }
  654. if (!sourceMeasure) {
  655. return SystemLinesEnum.SingleThin;
  656. }
  657. if (sourceMeasure.endingBarStyleEnum !== undefined) {
  658. return sourceMeasure.endingBarStyleEnum;
  659. }
  660. // TODO: print an error message if the default fallback is used.
  661. return SystemLinesEnum.SingleThin;
  662. }
  663. /**
  664. * Return the width of the corresponding [[SystemLine]] and sets the corresponding [[SystemLineEnum]].
  665. * @param measure
  666. * @param systemLineEnum
  667. * @param isSystemStartMeasure
  668. * @returns {number}
  669. */
  670. protected getLineWidth(measure: GraphicalMeasure, systemLineEnum: SystemLinesEnum, isSystemStartMeasure: boolean): number {
  671. let width: number = measure.getLineWidth(systemLineEnum);
  672. if (systemLineEnum === SystemLinesEnum.DotsBoldBoldDots) {
  673. width /= 2;
  674. }
  675. if (isSystemStartMeasure && systemLineEnum === SystemLinesEnum.BoldThinDots) {
  676. width += this.rules.DistanceBetweenLastInstructionAndRepetitionBarline;
  677. }
  678. return width;
  679. }
  680. protected previousMeasureEndsLineRepetition(): boolean {
  681. if (this.measureListIndex === 0) {
  682. return false;
  683. }
  684. for (let idx: number = 0, len: number = this.measureList[this.measureListIndex - 1].length; idx < len; ++idx) {
  685. const measure: GraphicalMeasure = this.measureList[this.measureListIndex - 1][idx];
  686. if (measure.endsWithLineRepetition()) {
  687. return true;
  688. }
  689. }
  690. return false;
  691. }
  692. /**
  693. * Check if at this [[Measure]] starts a [[Repetition]].
  694. * @returns {boolean}
  695. */
  696. protected thisMeasureBeginsLineRepetition(): boolean {
  697. for (let idx: number = 0, len: number = this.measureList[this.measureListIndex].length; idx < len; ++idx) {
  698. const measure: GraphicalMeasure = this.measureList[this.measureListIndex][idx];
  699. if (measure.beginsWithLineRepetition()) {
  700. return true;
  701. }
  702. }
  703. return false;
  704. }
  705. /**
  706. * Check if a [[Repetition]] starts at the next [[Measure]].
  707. * @returns {boolean}
  708. */
  709. protected nextMeasureBeginsLineRepetition(): boolean {
  710. const nextMeasureIndex: number = this.measureListIndex + 1;
  711. if (nextMeasureIndex >= this.graphicalMusicSheet.ParentMusicSheet.SourceMeasures.length
  712. || !this.measureList[nextMeasureIndex]) {
  713. return false;
  714. }
  715. for (let idx: number = 0, len: number = this.measureList[nextMeasureIndex].length; idx < len; ++idx) {
  716. const measure: GraphicalMeasure = this.measureList[nextMeasureIndex][idx];
  717. if (measure.beginsWithLineRepetition()) {
  718. return true;
  719. }
  720. }
  721. return false;
  722. }
  723. /**
  724. * Check if this [[Measure]] is a [[Repetition]] ending.
  725. * @returns {boolean}
  726. */
  727. protected thisMeasureEndsLineRepetition(): boolean {
  728. for (let idx: number = 0, len: number = this.measureList[this.measureListIndex].length; idx < len; ++idx) {
  729. const measure: GraphicalMeasure = this.measureList[this.measureListIndex][idx];
  730. if (measure.endsWithLineRepetition()) {
  731. return true;
  732. }
  733. }
  734. return false;
  735. }
  736. /**
  737. * Check if a [[Repetition]] starts at the next [[Measure]].
  738. * @returns {boolean}
  739. */
  740. protected nextMeasureBeginsWordRepetition(): boolean {
  741. const nextMeasureIndex: number = this.measureListIndex + 1;
  742. if (nextMeasureIndex >= this.graphicalMusicSheet.ParentMusicSheet.SourceMeasures.length ||
  743. nextMeasureIndex > this.measureList.length - 1) {
  744. return false;
  745. }
  746. for (let idx: number = 0, len: number = this.measureList[nextMeasureIndex].length; idx < len; ++idx) {
  747. const measure: GraphicalMeasure = this.measureList[nextMeasureIndex][idx];
  748. if (measure.beginsWithWordRepetition()) {
  749. return true;
  750. }
  751. }
  752. return false;
  753. }
  754. /**
  755. * Check if this [[Measure]] is a [[Repetition]] ending.
  756. * @returns {boolean}
  757. */
  758. protected thisMeasureEndsWordRepetition(): boolean {
  759. for (let idx: number = 0, len: number = this.measureList[this.measureListIndex].length; idx < len; ++idx) {
  760. const measure: GraphicalMeasure = this.measureList[this.measureListIndex][idx];
  761. if (measure.endsWithWordRepetition()) {
  762. return true;
  763. }
  764. }
  765. return false;
  766. }
  767. /**
  768. * Check if the next [[Measure]] has a [[KeyInstruction]] change.
  769. * @returns {boolean}
  770. */
  771. protected nextMeasureHasKeyInstructionChange(): boolean {
  772. return this.getNextMeasureKeyInstruction() !== undefined;
  773. }
  774. protected getNextMeasureKeyInstruction(): KeyInstruction {
  775. if (this.measureListIndex < this.measureList.length - 1) {
  776. for (let visIndex: number = 0; visIndex < this.measureList[this.measureListIndex].length; visIndex++) {
  777. const sourceMeasure: SourceMeasure = this.measureList[this.measureListIndex + 1][visIndex].parentSourceMeasure;
  778. if (!sourceMeasure) {
  779. return undefined;
  780. }
  781. return sourceMeasure.getKeyInstruction(this.visibleStaffIndices[visIndex]);
  782. }
  783. }
  784. return undefined;
  785. }
  786. /**
  787. * Calculate the X ScalingFactor in order to strech the whole System.
  788. * @param systemFixWidth
  789. * @param systemVarWidth
  790. * @returns {number}
  791. */
  792. protected calculateXScalingFactor(systemFixWidth: number, systemVarWidth: number): number {
  793. if (Math.abs(systemVarWidth - 0) < 0.00001 || Math.abs(systemFixWidth - 0) < 0.00001) {
  794. return 1.0;
  795. }
  796. let systemEndX: number;
  797. const currentSystem: MusicSystem = this.currentSystemParams.currentSystem;
  798. systemEndX = currentSystem.StaffLines[0].PositionAndShape.Size.width;
  799. const scalingFactor: number = (systemEndX - systemFixWidth) / systemVarWidth;
  800. return scalingFactor;
  801. }
  802. /**
  803. * Stretch the whole System so that no white space is left at the end.
  804. * @param systemEndsPart
  805. */
  806. protected stretchMusicSystem(systemEndsPart: boolean): void {
  807. let scalingFactor: number = this.calculateXScalingFactor(
  808. this.currentSystemParams.currentSystemFixWidth, this.currentSystemParams.currentSystemVarWidth
  809. );
  810. if (systemEndsPart) {
  811. scalingFactor = Math.min(scalingFactor, this.rules.LastSystemMaxScalingFactor);
  812. }
  813. const currentSystem: MusicSystem = this.currentSystemParams.currentSystem;
  814. for (let visStaffIdx: number = 0, len: number = currentSystem.StaffLines.length; visStaffIdx < len; ++visStaffIdx) {
  815. const staffLine: StaffLine = currentSystem.StaffLines[visStaffIdx];
  816. let currentXPosition: number = 0.0;
  817. for (let measureIndex: number = 0; measureIndex < staffLine.Measures.length; measureIndex++) {
  818. const measure: GraphicalMeasure = staffLine.Measures[measureIndex];
  819. measure.setPositionInStaffline(currentXPosition);
  820. measure.setWidth(measure.beginInstructionsWidth + measure.minimumStaffEntriesWidth * scalingFactor + measure.endInstructionsWidth);
  821. if (measureIndex < this.currentSystemParams.systemMeasures.length) {
  822. const startLine: SystemLinesEnum = this.currentSystemParams.systemMeasures[measureIndex].beginLine;
  823. const lineWidth: number = measure.getLineWidth(SystemLinesEnum.BoldThinDots);
  824. switch (startLine) {
  825. case SystemLinesEnum.BoldThinDots:
  826. let xPosition: number = currentXPosition;
  827. if (measureIndex === 0) {
  828. xPosition = currentXPosition + measure.beginInstructionsWidth - lineWidth;
  829. }
  830. currentSystem.createVerticalLineForMeasure(xPosition, lineWidth, startLine, SystemLinePosition.MeasureBegin, measureIndex, measure);
  831. break;
  832. default:
  833. }
  834. }
  835. measure.staffEntriesScaleFactor = scalingFactor;
  836. measure.layoutSymbols();
  837. const nextMeasureHasRepStartLine: boolean = measureIndex + 1 < this.currentSystemParams.systemMeasures.length
  838. && this.currentSystemParams.systemMeasures[measureIndex + 1].beginLine === SystemLinesEnum.BoldThinDots;
  839. if (!nextMeasureHasRepStartLine) {
  840. let endLine: SystemLinesEnum = SystemLinesEnum.SingleThin;
  841. if (measureIndex < this.currentSystemParams.systemMeasures.length) {
  842. endLine = this.currentSystemParams.systemMeasures[measureIndex].endLine;
  843. }
  844. const lineWidth: number = measure.getLineWidth(endLine);
  845. let xPos: number = measure.PositionAndShape.RelativePosition.x + measure.PositionAndShape.BorderRight - lineWidth;
  846. if (endLine === SystemLinesEnum.DotsBoldBoldDots) {
  847. xPos -= lineWidth / 2;
  848. }
  849. currentSystem.createVerticalLineForMeasure(xPos, lineWidth, endLine, SystemLinePosition.MeasureEnd, measureIndex, measure);
  850. }
  851. currentXPosition = measure.PositionAndShape.RelativePosition.x + measure.PositionAndShape.BorderRight;
  852. }
  853. }
  854. if (systemEndsPart) {
  855. this.decreaseMusicSystemBorders();
  856. }
  857. }
  858. /**
  859. * If the last [[MusicSystem]] doesn't need stretching, then this method decreases the System's Width,
  860. * the [[StaffLine]]'s Width and the 5 [[StaffLine]]s length.
  861. */
  862. protected decreaseMusicSystemBorders(): void {
  863. const currentSystem: MusicSystem = this.currentSystemParams.currentSystem;
  864. const bb: BoundingBox = CollectionUtil.last(currentSystem.StaffLines[0].Measures).PositionAndShape;
  865. const width: number = bb.RelativePosition.x + bb.Size.width;
  866. for (let idx: number = 0, len: number = currentSystem.StaffLines.length; idx < len; ++idx) {
  867. const staffLine: StaffLine = currentSystem.StaffLines[idx];
  868. staffLine.PositionAndShape.BorderRight = width;
  869. for (let idx2: number = 0, len2: number = staffLine.StaffLines.length; idx2 < len2; ++idx2) {
  870. const graphicalLine: GraphicalLine = staffLine.StaffLines[idx2];
  871. graphicalLine.End = new PointF2D(width, graphicalLine.End.y);
  872. }
  873. }
  874. currentSystem.PositionAndShape.BorderRight = width + this.currentSystemParams.maxLabelLength + this.rules.SystemLabelsRightMargin;
  875. }
  876. /**
  877. * This method updates the System's StaffLine's RelativePosition (starting from the given index).
  878. * @param musicSystem
  879. * @param index
  880. * @param value
  881. */
  882. protected updateStaffLinesRelativePosition(musicSystem: MusicSystem, index: number, value: number): void {
  883. for (let i: number = index; i < musicSystem.StaffLines.length; i++) {
  884. musicSystem.StaffLines[i].PositionAndShape.RelativePosition.y = value;
  885. }
  886. musicSystem.PositionAndShape.BorderBottom += value;
  887. }
  888. /**
  889. * Create a new [[GraphicalMusicPage]]
  890. * (for now only one long page is used per music sheet, as we scroll down and have no page flips)
  891. * @returns {GraphicalMusicPage}
  892. */
  893. protected createMusicPage(): GraphicalMusicPage {
  894. const page: GraphicalMusicPage = new GraphicalMusicPage(this.graphicalMusicSheet);
  895. this.graphicalMusicSheet.MusicPages.push(page);
  896. page.PageNumber = this.graphicalMusicSheet.MusicPages.length; // caution: page number = page index + 1
  897. page.PositionAndShape.BorderLeft = 0.0;
  898. page.PositionAndShape.BorderRight = this.graphicalMusicSheet.ParentMusicSheet.pageWidth;
  899. page.PositionAndShape.BorderTop = 0.0;
  900. page.PositionAndShape.BorderBottom = this.rules.PageHeight;
  901. page.PositionAndShape.RelativePosition = new PointF2D(0.0, 0.0);
  902. return page;
  903. }
  904. protected addSystemToPage(page: GraphicalMusicPage, system: MusicSystem): void {
  905. page.MusicSystems.push(system);
  906. system.Parent = page;
  907. }
  908. /**
  909. * This method checks the distances between any two consecutive StaffLines of a System and if needed, shifts the lower one down.
  910. * @param musicSystem
  911. */
  912. protected optimizeDistanceBetweenStaffLines(musicSystem: MusicSystem): void {
  913. // don't perform any y-spacing in case of a StaffEntryLink (in both StaffLines)
  914. if (!musicSystem.checkStaffEntriesForStaffEntryLink()) {
  915. for (let i: number = 0; i < musicSystem.StaffLines.length - 1; i++) {
  916. const upperBottomLine: number[] = musicSystem.StaffLines[i].BottomLine;
  917. const lowerSkyLine: number[] = musicSystem.StaffLines[i + 1].SkyLine;
  918. // 1. Find maximum required space for sky bottom line touching each other
  919. let maxDistance: number = 0;
  920. for (let j: number = 0; j < upperBottomLine.length; j++) {
  921. const bottomLineValue: number = upperBottomLine[j];
  922. // look at a range of +/- 2 Units to also ensure that objects are also not too close in x-direction:
  923. const startIdx: number = Math.max(0, j - 6);
  924. const endIdx: number = Math.min(lowerSkyLine.length - 1, j + 6);
  925. let skylineValue: number = 0;
  926. for (let lowerIdx: number = startIdx; lowerIdx <= endIdx; lowerIdx++) {
  927. skylineValue = Math.min(skylineValue, lowerSkyLine[lowerIdx]);
  928. }
  929. const distance: number = bottomLineValue - skylineValue;
  930. maxDistance = Math.max(distance, maxDistance);
  931. }
  932. // 2. Add user defined distance between sky bottom line
  933. maxDistance += this.rules.MinSkyBottomDistBetweenStaves;
  934. // 3. Take the maximum between previous value and user defined value for staff line minimum distance
  935. maxDistance = Math.max(maxDistance, this.rules.StaffHeight + this.rules.MinimumStaffLineDistance);
  936. const lowerStafflineYPos: number = maxDistance + musicSystem.StaffLines[i].PositionAndShape.RelativePosition.y;
  937. this.updateStaffLinesRelativePosition(musicSystem, i + 1, lowerStafflineYPos);
  938. }
  939. }
  940. const firstStaffLine: StaffLine = musicSystem.StaffLines[0];
  941. musicSystem.PositionAndShape.BorderTop = firstStaffLine.PositionAndShape.RelativePosition.y + firstStaffLine.PositionAndShape.BorderTop;
  942. const lastStaffLine: StaffLine = musicSystem.StaffLines[musicSystem.StaffLines.length - 1];
  943. musicSystem.PositionAndShape.BorderBottom = lastStaffLine.PositionAndShape.RelativePosition.y + lastStaffLine.PositionAndShape.BorderBottom;
  944. }
  945. /** Calculates the relative Positions of all MusicSystems.
  946. *
  947. */
  948. protected calculateMusicSystemsRelativePositions(): void {
  949. let currentPage: GraphicalMusicPage = this.createMusicPage();
  950. let currentYPosition: number = 0;
  951. // xPosition is always fixed
  952. let currentSystem: MusicSystem = this.musicSystems[0];
  953. let timesPageCouldntFitSingleSystem: number = 0;
  954. for (let i: number = 0; i < this.musicSystems.length; i++) {
  955. currentSystem = this.musicSystems[i];
  956. if (currentPage.MusicSystems.length === 0) {
  957. // if this is the first system on the current page:
  958. // take top margins into account
  959. this.addSystemToPage(currentPage, currentSystem);
  960. if (this.rules.CompactMode) {
  961. currentYPosition = this.rules.PageTopMarginNarrow;
  962. } else {
  963. currentYPosition = this.rules.PageTopMargin;
  964. }
  965. if (this.graphicalMusicSheet.MusicPages.length === 1) {
  966. /*
  967. Only need this in the event that lyricist or composer text intersects with title,
  968. which seems exceedingly rare.
  969. Leaving here just in case for future needs.
  970. Prefer to use skyline calculator in MusicSheetCalculator.calculatePageLabels
  971. let maxLineCount: number = this.graphicalMusicSheet.Composer?.TextLines?.length;
  972. let maxFontHeight: number = this.graphicalMusicSheet.Composer?.Label?.fontHeight;
  973. let lyricistLineCount: number = this.graphicalMusicSheet.Lyricist?.TextLines?.length;
  974. let lyricistFontHeight: number = this.graphicalMusicSheet.Lyricist?.Label?.fontHeight;
  975. maxLineCount = maxLineCount ? maxLineCount : 0;
  976. maxFontHeight = maxFontHeight ? maxFontHeight : 0;
  977. lyricistLineCount = lyricistLineCount ? lyricistLineCount : 0;
  978. lyricistFontHeight = lyricistFontHeight ? lyricistFontHeight : 0;
  979. let maxHeight: number = maxLineCount * maxFontHeight;
  980. const totalLyricist: number = lyricistLineCount * lyricistFontHeight;
  981. if (totalLyricist > maxHeight) {
  982. maxLineCount = lyricistLineCount;
  983. maxFontHeight = lyricistFontHeight;
  984. maxHeight = totalLyricist;
  985. } */
  986. if (this.rules.RenderTitle) {
  987. // if it is the first System on the FIRST page: Add Title height and gap-distance
  988. currentYPosition += this.rules.TitleTopDistance + this.rules.SheetTitleHeight +
  989. this.rules.TitleBottomDistance;
  990. }
  991. /*
  992. see comment above - only needed for rare case of composer/lyricist being
  993. wide enough to be below the title (or title wide enough to be above)
  994. if (maxLineCount > 2) {
  995. currentYPosition += maxFontHeight * (maxLineCount - 2);
  996. }*/
  997. }
  998. // now add the border-top: everything that stands out above the staffline:
  999. currentYPosition += -currentSystem.PositionAndShape.BorderTop;
  1000. const relativePosition: PointF2D = new PointF2D(this.rules.PageLeftMargin + this.rules.SystemLeftMargin,
  1001. currentYPosition);
  1002. currentSystem.PositionAndShape.RelativePosition = relativePosition;
  1003. // check if the first system doesn't even fit on the page -> would lead to truncation at bottom end:
  1004. if (currentYPosition + currentSystem.PositionAndShape.BorderBottom > this.rules.PageHeight - this.rules.PageBottomMargin) {
  1005. // can't fit single system on page, maybe PageFormat too small
  1006. timesPageCouldntFitSingleSystem++;
  1007. if (timesPageCouldntFitSingleSystem <= 4) { // only warn once with detailed info
  1008. console.log(`warning: could not fit a single system on page ${currentPage.PageNumber}` +
  1009. ` and measure number ${currentSystem.GraphicalMeasures[0][0].MeasureNumber}.
  1010. The PageFormat may be too small for this sheet."
  1011. Will not give further warnings for all pages, only total.`
  1012. );
  1013. }
  1014. }
  1015. } else {
  1016. // if this is not the first system on the page:
  1017. // find optimum distance between Systems
  1018. const previousSystem: MusicSystem = this.musicSystems[i - 1];
  1019. const prevSystemLastStaffline: StaffLine = previousSystem.StaffLines[previousSystem.StaffLines.length - 1];
  1020. const prevSystemLastStaffLineBB: BoundingBox = prevSystemLastStaffline.PositionAndShape;
  1021. let distance: number = this.findRequiredDistanceWithSkyBottomLine(previousSystem, currentSystem);
  1022. // make sure the optical distance is the user-defined min distance:
  1023. distance += this.rules.MinSkyBottomDistBetweenSystems;
  1024. distance = Math.max(distance, this.rules.MinimumDistanceBetweenSystems + prevSystemLastStaffline.StaffHeight);
  1025. const newYPosition: number = currentYPosition +
  1026. prevSystemLastStaffLineBB.RelativePosition.y +
  1027. distance;
  1028. // calculate the needed height for placing the current system on the page,
  1029. // to see if it still fits:
  1030. const currSystemBottomYPos: number = newYPosition +
  1031. currentSystem.PositionAndShape.BorderMarginBottom;
  1032. const doXmlPageBreak: boolean = this.rules.NewPageAtXMLNewPageAttribute && previousSystem.breaksPage;
  1033. if (!doXmlPageBreak &&
  1034. (currSystemBottomYPos < this.rules.PageHeight - this.rules.PageBottomMargin)) {
  1035. // enough space on this page:
  1036. this.addSystemToPage(currentPage, currentSystem);
  1037. currentYPosition = newYPosition;
  1038. const relativePosition: PointF2D = new PointF2D(this.rules.PageLeftMargin + this.rules.SystemLeftMargin,
  1039. currentYPosition);
  1040. currentSystem.PositionAndShape.RelativePosition = relativePosition;
  1041. } else {
  1042. // new page needed:
  1043. currentPage = this.createMusicPage();
  1044. // re-check this system again:
  1045. i -= 1;
  1046. continue;
  1047. }
  1048. }
  1049. }
  1050. if (timesPageCouldntFitSingleSystem > 0) {
  1051. console.log(`total amount of pages that couldn't fit a single music system: ${timesPageCouldntFitSingleSystem} of ${currentPage.PageNumber}`);
  1052. }
  1053. }
  1054. /**
  1055. * Finds the minimum required distance between two systems
  1056. * with the help of the sky- and bottom lines
  1057. * @param upperSystem
  1058. * @param lowerSystem
  1059. */
  1060. private findRequiredDistanceWithSkyBottomLine(upperSystem: MusicSystem, lowerSystem: MusicSystem): number {
  1061. const upperSystemLastStaffLine: StaffLine = upperSystem.StaffLines[upperSystem.StaffLines.length - 1];
  1062. const lowerSystemFirstStaffLine: StaffLine = lowerSystem.StaffLines[0];
  1063. const upperBottomLineArray: number[] = upperSystemLastStaffLine.BottomLine;
  1064. const lowerSkyLineArray: number[] = lowerSystemFirstStaffLine.SkyLine;
  1065. const upperStaffLineBB: BoundingBox = upperSystemLastStaffLine.PositionAndShape;
  1066. const lowerStaffLineBB: BoundingBox = lowerSystemFirstStaffLine.PositionAndShape;
  1067. const skylinePixelWidth: number = 1 / this.rules.SamplingUnit;
  1068. // Find maximum required space for sky and bottom line touching each other
  1069. let maxDistance: number = 0;
  1070. for (let upperIdx: number = 0; upperIdx < upperBottomLineArray.length; upperIdx++) {
  1071. const bottomLineValue: number = upperBottomLineArray[upperIdx];
  1072. // find index of the same x-position in lower skyline:
  1073. const lowerCenterIdx: number = upperIdx +
  1074. Math.round((upperStaffLineBB.RelativePosition.x - lowerStaffLineBB.RelativePosition.x) * skylinePixelWidth);
  1075. if (lowerCenterIdx < 0) {
  1076. // should actually not happen..
  1077. continue;
  1078. }
  1079. if (lowerCenterIdx >= lowerSkyLineArray.length) {
  1080. // lower system ends earlier x-wise than upper system (e.g. at last system, if it is not stretched)
  1081. break;
  1082. }
  1083. // look at a range of +/- 2 Units to also ensure that objects are also not too close in x-direction:
  1084. const startIdx: number = Math.max(0, lowerCenterIdx - 6);
  1085. const endIdx: number = Math.min(lowerSkyLineArray.length - 1, lowerCenterIdx + 6);
  1086. let skylineValue: number = 0;
  1087. for (let lowerIdx: number = startIdx; lowerIdx <= endIdx; lowerIdx++) {
  1088. skylineValue = Math.min(skylineValue, lowerSkyLineArray[lowerIdx]);
  1089. }
  1090. const distance: number = bottomLineValue - skylineValue;
  1091. maxDistance = Math.max(distance, maxDistance);
  1092. }
  1093. if (maxDistance === 0) {
  1094. // can only happen when the bottom- and skyline have no x-overlap at all:
  1095. // fall back to borders:
  1096. maxDistance = upperStaffLineBB.BorderBottom - lowerStaffLineBB.BorderTop;
  1097. }
  1098. return maxDistance;
  1099. }
  1100. }
  1101. export class SystemBuildParameters {
  1102. public currentSystem: MusicSystem;
  1103. public systemMeasures: MeasureBuildParameters[] = [];
  1104. public systemMeasureIndex: number = 0;
  1105. public currentWidth: number = 0;
  1106. public currentSystemFixWidth: number = 0;
  1107. public currentSystemVarWidth: number = 0;
  1108. public maxLabelLength: number = 0;
  1109. public IsSystemStartMeasure(): boolean {
  1110. return this.systemMeasureIndex === 0;
  1111. }
  1112. }
  1113. export class MeasureBuildParameters {
  1114. public beginLine: SystemLinesEnum;
  1115. public endLine: SystemLinesEnum;
  1116. }