MusicSystemBuilder.ts 59 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162
  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. if (musicSystem === this.musicSystems[0] &&
  316. !this.rules.CompactMode) {
  317. relativePosition.x = this.rules.FirstSystemMargin;
  318. boundingBox.BorderRight = musicSystem.PositionAndShape.Size.width - this.rules.FirstSystemMargin;
  319. } else {
  320. relativePosition.x = 0.0;
  321. boundingBox.BorderRight = musicSystem.PositionAndShape.Size.width;
  322. }
  323. relativePosition.y = relativeYPosition;
  324. boundingBox.RelativePosition = relativePosition;
  325. boundingBox.BorderLeft = 0.0;
  326. boundingBox.BorderTop = 0.0;
  327. boundingBox.BorderBottom = this.rules.StaffHeight;
  328. for (let i: number = 0; i < 5; i++) {
  329. const start: PointF2D = new PointF2D();
  330. start.x = 0.0;
  331. start.y = i * this.rules.StaffHeight / 4;
  332. const end: PointF2D = new PointF2D();
  333. end.x = staffLine.PositionAndShape.Size.width;
  334. end.y = i * this.rules.StaffHeight / 4;
  335. if (this.leadSheet) {
  336. start.y = end.y = 0;
  337. }
  338. staffLine.StaffLines[i] = new GraphicalLine(start, end, this.rules.StaffLineWidth);
  339. }
  340. }
  341. }
  342. /**
  343. * Initialize the active Instructions from the first [[SourceMeasure]] of first [[SourceMusicPart]].
  344. * @param measureList
  345. */
  346. protected initializeActiveInstructions(measureList: GraphicalMeasure[]): void {
  347. const firstSourceMeasure: SourceMeasure = this.graphicalMusicSheet.ParentMusicSheet.getFirstSourceMeasure();
  348. if (firstSourceMeasure) {
  349. this.visibleStaffIndices = this.graphicalMusicSheet.getVisibleStavesIndicesFromSourceMeasure(measureList);
  350. for (let i: number = 0, len: number = this.visibleStaffIndices.length; i < len; i++) {
  351. const staffIndex: number = this.visibleStaffIndices[i];
  352. const graphicalMeasure: GraphicalMeasure = this.graphicalMusicSheet
  353. .getGraphicalMeasureFromSourceMeasureAndIndex(firstSourceMeasure, staffIndex);
  354. this.activeClefs[i] = <ClefInstruction>firstSourceMeasure.FirstInstructionsStaffEntries[staffIndex].Instructions[0];
  355. let keyInstruction: KeyInstruction = KeyInstruction.copy(
  356. <KeyInstruction>firstSourceMeasure.FirstInstructionsStaffEntries[staffIndex].Instructions[1]);
  357. keyInstruction = this.transposeKeyInstruction(keyInstruction, graphicalMeasure);
  358. this.activeKeys[i] = keyInstruction;
  359. this.activeRhythm[i] = <RhythmInstruction>firstSourceMeasure.FirstInstructionsStaffEntries[staffIndex].Instructions[2];
  360. }
  361. }
  362. }
  363. protected transposeKeyInstruction(keyInstruction: KeyInstruction, graphicalMeasure: GraphicalMeasure): KeyInstruction {
  364. if (this.graphicalMusicSheet.ParentMusicSheet.Transpose !== 0
  365. && graphicalMeasure.ParentStaff.ParentInstrument.MidiInstrumentId !== MidiInstrument.Percussion
  366. && MusicSheetCalculator.transposeCalculator !== undefined
  367. ) {
  368. MusicSheetCalculator.transposeCalculator.transposeKey(
  369. keyInstruction,
  370. this.graphicalMusicSheet.ParentMusicSheet.Transpose
  371. );
  372. }
  373. return keyInstruction;
  374. }
  375. /**
  376. * Calculate the width needed for Instructions (Key, Clef, Rhythm, Repetition) for the measure.
  377. * @param measures
  378. * @param isSystemFirstMeasure
  379. * @param isFirstSourceMeasure
  380. * @returns {number}
  381. */
  382. protected addBeginInstructions(measures: GraphicalMeasure[], isSystemFirstMeasure: boolean, isFirstSourceMeasure: boolean): number {
  383. const measureCount: number = measures.length;
  384. if (measureCount === 0) {
  385. return 0;
  386. }
  387. let totalBeginInstructionLengthX: number = 0.0;
  388. const sourceMeasure: SourceMeasure = measures[0].parentSourceMeasure;
  389. for (let idx: number = 0; idx < measureCount; ++idx) {
  390. const measure: GraphicalMeasure = measures[idx];
  391. const staffIndex: number = this.visibleStaffIndices[idx];
  392. const beginInstructionsStaffEntry: SourceStaffEntry = sourceMeasure.FirstInstructionsStaffEntries[staffIndex];
  393. const beginInstructionLengthX: number = this.AddInstructionsAtMeasureBegin(
  394. beginInstructionsStaffEntry, measure,
  395. idx, isFirstSourceMeasure,
  396. isSystemFirstMeasure
  397. );
  398. totalBeginInstructionLengthX = Math.max(totalBeginInstructionLengthX, beginInstructionLengthX);
  399. }
  400. return totalBeginInstructionLengthX;
  401. }
  402. /**
  403. * Calculates the width needed for Instructions (Clef, Repetition) for the measure.
  404. * @param measures
  405. * @returns {number}
  406. */
  407. protected addEndInstructions(measures: GraphicalMeasure[]): number {
  408. const measureCount: number = measures.length;
  409. if (measureCount === 0) {
  410. return 0;
  411. }
  412. let totalEndInstructionLengthX: number = 0.5;
  413. const sourceMeasure: SourceMeasure = measures[0].parentSourceMeasure;
  414. for (let idx: number = 0; idx < measureCount; idx++) {
  415. const measure: GraphicalMeasure = measures[idx];
  416. const staffIndex: number = this.visibleStaffIndices[idx];
  417. const endInstructionsStaffEntry: SourceStaffEntry = sourceMeasure.LastInstructionsStaffEntries[staffIndex];
  418. const endInstructionLengthX: number = this.addInstructionsAtMeasureEnd(endInstructionsStaffEntry, measure);
  419. totalEndInstructionLengthX = Math.max(totalEndInstructionLengthX, endInstructionLengthX);
  420. }
  421. return totalEndInstructionLengthX;
  422. }
  423. protected AddInstructionsAtMeasureBegin(firstEntry: SourceStaffEntry, measure: GraphicalMeasure,
  424. visibleStaffIdx: number, isFirstSourceMeasure: boolean, isSystemStartMeasure: boolean): number {
  425. let instructionsLengthX: number = 0;
  426. let currentClef: ClefInstruction = undefined;
  427. let currentKey: KeyInstruction = undefined;
  428. let currentRhythm: RhythmInstruction = undefined;
  429. if (firstEntry) {
  430. for (let idx: number = 0, len: number = firstEntry.Instructions.length; idx < len; ++idx) {
  431. const abstractNotationInstruction: AbstractNotationInstruction = firstEntry.Instructions[idx];
  432. if (abstractNotationInstruction instanceof ClefInstruction) {
  433. currentClef = <ClefInstruction>abstractNotationInstruction;
  434. } else if (abstractNotationInstruction instanceof KeyInstruction) {
  435. currentKey = <KeyInstruction>abstractNotationInstruction;
  436. } else if (abstractNotationInstruction instanceof RhythmInstruction) {
  437. currentRhythm = <RhythmInstruction>abstractNotationInstruction;
  438. }
  439. }
  440. }
  441. if (isSystemStartMeasure) {
  442. if (!currentClef) {
  443. currentClef = this.activeClefs[visibleStaffIdx];
  444. }
  445. if (!currentKey) {
  446. currentKey = this.activeKeys[visibleStaffIdx];
  447. }
  448. if (isFirstSourceMeasure && !currentRhythm) {
  449. currentRhythm = this.activeRhythm[visibleStaffIdx];
  450. }
  451. }
  452. let clefAdded: boolean = false;
  453. let keyAdded: boolean = false;
  454. let rhythmAdded: boolean = false;
  455. if (currentClef) {
  456. measure.addClefAtBegin(currentClef);
  457. clefAdded = true;
  458. } else {
  459. currentClef = this.activeClefs[visibleStaffIdx];
  460. }
  461. if (currentKey) {
  462. currentKey = this.transposeKeyInstruction(currentKey, measure);
  463. const previousKey: KeyInstruction = isSystemStartMeasure ? undefined : this.activeKeys[visibleStaffIdx];
  464. measure.addKeyAtBegin(currentKey, previousKey, currentClef);
  465. keyAdded = true;
  466. }
  467. if (currentRhythm !== undefined && currentRhythm.PrintObject && this.rules.RenderTimeSignatures) {
  468. measure.addRhythmAtBegin(currentRhythm);
  469. rhythmAdded = true;
  470. }
  471. if (clefAdded || keyAdded || rhythmAdded) {
  472. instructionsLengthX += measure.beginInstructionsWidth;
  473. if (rhythmAdded) {
  474. instructionsLengthX += this.rules.RhythmRightMargin;
  475. }
  476. }
  477. return instructionsLengthX;
  478. }
  479. protected addInstructionsAtMeasureEnd(lastEntry: SourceStaffEntry, measure: GraphicalMeasure): number {
  480. if (!lastEntry || !lastEntry.Instructions || lastEntry.Instructions.length === 0) {
  481. return 0;
  482. }
  483. for (let idx: number = 0, len: number = lastEntry.Instructions.length; idx < len; ++idx) {
  484. const abstractNotationInstruction: AbstractNotationInstruction = lastEntry.Instructions[idx];
  485. if (abstractNotationInstruction instanceof ClefInstruction) {
  486. const activeClef: ClefInstruction = <ClefInstruction>abstractNotationInstruction;
  487. measure.addClefAtEnd(activeClef);
  488. }
  489. }
  490. return this.rules.MeasureRightMargin + measure.endInstructionsWidth;
  491. }
  492. /**
  493. * Track down and update the active ClefInstruction in Measure's StaffEntries.
  494. * This has to be done after the measure is added to a system
  495. * (otherwise already the check if the measure fits to the system would update the active clefs..)
  496. * @param measure
  497. * @param graphicalMeasures
  498. */
  499. protected updateActiveClefs(measure: SourceMeasure, graphicalMeasures: GraphicalMeasure[]): void {
  500. for (let visStaffIdx: number = 0, len: number = graphicalMeasures.length; visStaffIdx < len; visStaffIdx++) {
  501. const staffIndex: number = this.visibleStaffIndices[visStaffIdx];
  502. const firstEntry: SourceStaffEntry = measure.FirstInstructionsStaffEntries[staffIndex];
  503. if (firstEntry) {
  504. for (let idx: number = 0, len2: number = firstEntry.Instructions.length; idx < len2; ++idx) {
  505. const abstractNotationInstruction: AbstractNotationInstruction = firstEntry.Instructions[idx];
  506. if (abstractNotationInstruction instanceof ClefInstruction) {
  507. this.activeClefs[visStaffIdx] = <ClefInstruction>abstractNotationInstruction;
  508. } else if (abstractNotationInstruction instanceof KeyInstruction) {
  509. this.activeKeys[visStaffIdx] = <KeyInstruction>abstractNotationInstruction;
  510. } else if (abstractNotationInstruction instanceof RhythmInstruction) {
  511. this.activeRhythm[visStaffIdx] = <RhythmInstruction>abstractNotationInstruction;
  512. }
  513. }
  514. }
  515. const entries: SourceStaffEntry[] = measure.getEntriesPerStaff(staffIndex);
  516. for (let idx: number = 0, len2: number = entries.length; idx < len2; ++idx) {
  517. const staffEntry: SourceStaffEntry = entries[idx];
  518. if (staffEntry.Instructions) {
  519. for (let idx2: number = 0, len3: number = staffEntry.Instructions.length; idx2 < len3; ++idx2) {
  520. const abstractNotationInstruction: AbstractNotationInstruction = staffEntry.Instructions[idx2];
  521. if (abstractNotationInstruction instanceof ClefInstruction) {
  522. this.activeClefs[visStaffIdx] = <ClefInstruction>abstractNotationInstruction;
  523. }
  524. }
  525. }
  526. }
  527. const lastEntry: SourceStaffEntry = measure.LastInstructionsStaffEntries[staffIndex];
  528. if (lastEntry) {
  529. const instructions: AbstractNotationInstruction[] = lastEntry.Instructions;
  530. for (let idx: number = 0, len3: number = instructions.length; idx < len3; ++idx) {
  531. const abstractNotationInstruction: AbstractNotationInstruction = instructions[idx];
  532. if (abstractNotationInstruction instanceof ClefInstruction) {
  533. this.activeClefs[visStaffIdx] = <ClefInstruction>abstractNotationInstruction;
  534. }
  535. }
  536. }
  537. }
  538. }
  539. /**
  540. * Check if an extra Instruction [[Measure]] is needed.
  541. * @param measures
  542. */
  543. protected checkAndCreateExtraInstructionMeasure(measures: GraphicalMeasure[]): void {
  544. const firstStaffEntries: SourceStaffEntry[] = measures[0].parentSourceMeasure.FirstInstructionsStaffEntries;
  545. const visibleInstructionEntries: SourceStaffEntry[] = [];
  546. for (let idx: number = 0, len: number = measures.length; idx < len; ++idx) {
  547. const measure: GraphicalMeasure = measures[idx];
  548. visibleInstructionEntries.push(firstStaffEntries[measure.ParentStaff.idInMusicSheet]);
  549. }
  550. let maxMeasureWidth: number = 0;
  551. for (let visStaffIdx: number = 0, len: number = visibleInstructionEntries.length; visStaffIdx < len; ++visStaffIdx) {
  552. const sse: SourceStaffEntry = visibleInstructionEntries[visStaffIdx];
  553. if (!sse) {
  554. continue;
  555. }
  556. const instructions: AbstractNotationInstruction[] = sse.Instructions;
  557. let keyInstruction: KeyInstruction = undefined;
  558. let rhythmInstruction: RhythmInstruction = undefined;
  559. for (let idx2: number = 0, len2: number = instructions.length; idx2 < len2; ++idx2) {
  560. const instruction: AbstractNotationInstruction = instructions[idx2];
  561. if (instruction instanceof KeyInstruction && (<KeyInstruction>instruction).Key !== this.activeKeys[visStaffIdx].Key) {
  562. keyInstruction = <KeyInstruction>instruction;
  563. }
  564. if (instruction instanceof RhythmInstruction && (<RhythmInstruction>instruction) !== this.activeRhythm[visStaffIdx]) {
  565. rhythmInstruction = <RhythmInstruction>instruction;
  566. }
  567. }
  568. if (keyInstruction !== undefined || rhythmInstruction) {
  569. const measureWidth: number = this.addExtraInstructionMeasure(visStaffIdx, keyInstruction, rhythmInstruction);
  570. maxMeasureWidth = Math.max(maxMeasureWidth, measureWidth);
  571. }
  572. }
  573. if (maxMeasureWidth > 0) {
  574. this.currentSystemParams.systemMeasures.push({
  575. beginLine: SystemLinesEnum.None,
  576. endLine: SystemLinesEnum.None,
  577. });
  578. this.currentSystemParams.currentWidth += maxMeasureWidth;
  579. this.currentSystemParams.currentSystemFixWidth += maxMeasureWidth;
  580. }
  581. }
  582. protected addExtraInstructionMeasure(visStaffIdx: number, keyInstruction: KeyInstruction, rhythmInstruction: RhythmInstruction): number {
  583. const currentSystem: MusicSystem = this.currentSystemParams.currentSystem;
  584. const measures: GraphicalMeasure[] = [];
  585. const measure: GraphicalMeasure = MusicSheetCalculator.symbolFactory.createExtraGraphicalMeasure(currentSystem.StaffLines[visStaffIdx]);
  586. measures.push(measure);
  587. if (keyInstruction) {
  588. measure.addKeyAtBegin(keyInstruction, this.activeKeys[visStaffIdx], this.activeClefs[visStaffIdx]);
  589. }
  590. if (rhythmInstruction !== undefined && rhythmInstruction.PrintObject) {
  591. measure.addRhythmAtBegin(rhythmInstruction);
  592. }
  593. measure.PositionAndShape.BorderLeft = 0.0;
  594. measure.PositionAndShape.BorderTop = 0.0;
  595. measure.PositionAndShape.BorderBottom = this.rules.StaffHeight;
  596. const width: number = this.rules.MeasureLeftMargin + measure.beginInstructionsWidth + this.rules.MeasureRightMargin;
  597. measure.PositionAndShape.BorderRight = width;
  598. currentSystem.StaffLines[visStaffIdx].Measures.push(measure);
  599. return width;
  600. }
  601. /**
  602. * Add all current vertical Measures to currentSystem.
  603. * @param graphicalMeasures
  604. */
  605. protected addStaveMeasuresToSystem(graphicalMeasures: GraphicalMeasure[]): void {
  606. if (graphicalMeasures[0]) {
  607. const gmeasures: GraphicalMeasure[] = [];
  608. for (let i: number = 0; i < graphicalMeasures.length; i++) {
  609. gmeasures.push(graphicalMeasures[i]);
  610. }
  611. const currentSystem: MusicSystem = this.currentSystemParams.currentSystem;
  612. for (let visStaffIdx: number = 0; visStaffIdx < this.numberOfVisibleStaffLines; visStaffIdx++) {
  613. const measure: GraphicalMeasure = gmeasures[visStaffIdx];
  614. currentSystem.StaffLines[visStaffIdx].Measures.push(measure);
  615. measure.ParentStaffLine = currentSystem.StaffLines[visStaffIdx];
  616. }
  617. currentSystem.AddGraphicalMeasures(gmeasures);
  618. }
  619. }
  620. /**
  621. * Return the width of the corresponding [[SystemLine]] and set the corresponding [[SystemLineEnum]].
  622. * @returns {SystemLinesEnum}
  623. */
  624. protected getMeasureStartLine(): SystemLinesEnum {
  625. const thisMeasureBeginsLineRep: boolean = this.thisMeasureBeginsLineRepetition();
  626. if (thisMeasureBeginsLineRep) {
  627. const isSystemStartMeasure: boolean = this.currentSystemParams.IsSystemStartMeasure();
  628. const isGlobalFirstMeasure: boolean = this.measureListIndex === 0;
  629. if (this.previousMeasureEndsLineRepetition() && !isSystemStartMeasure) {
  630. return SystemLinesEnum.DotsBoldBoldDots;
  631. }
  632. if (!isGlobalFirstMeasure) {
  633. return SystemLinesEnum.BoldThinDots;
  634. }
  635. }
  636. return SystemLinesEnum.None;
  637. }
  638. protected getMeasureEndLine(): SystemLinesEnum {
  639. let sourceMeasure: SourceMeasure = undefined;
  640. try {
  641. sourceMeasure = this.measureList[this.measureListIndex][0].parentSourceMeasure;
  642. } finally {
  643. // do nothing
  644. }
  645. if (this.nextMeasureBeginsLineRepetition() && this.thisMeasureEndsLineRepetition()) {
  646. return SystemLinesEnum.DotsBoldBoldDots;
  647. }
  648. if (this.thisMeasureEndsLineRepetition()) {
  649. return SystemLinesEnum.DotsThinBold;
  650. }
  651. // always end piece with final barline: not a good idea. user should be able to override final barline.
  652. // also, selecting range of measures to draw would always end with final barline, even if extract is from the middle of the piece
  653. // this was probably done before we parsed the barline type from XML.
  654. /*if (this.measureListIndex === this.measureList.length - 1 || this.measureList[this.measureListIndex][0].parentSourceMeasure.endsPiece) {
  655. return SystemLinesEnum.ThinBold;
  656. }*/
  657. if (this.nextMeasureHasKeyInstructionChange() || this.thisMeasureEndsWordRepetition() || this.nextMeasureBeginsWordRepetition()) {
  658. return SystemLinesEnum.DoubleThin;
  659. }
  660. if (!sourceMeasure) {
  661. return SystemLinesEnum.SingleThin;
  662. }
  663. if (sourceMeasure.endingBarStyleEnum !== undefined) {
  664. return sourceMeasure.endingBarStyleEnum;
  665. }
  666. // TODO: print an error message if the default fallback is used.
  667. return SystemLinesEnum.SingleThin;
  668. }
  669. /**
  670. * Return the width of the corresponding [[SystemLine]] and sets the corresponding [[SystemLineEnum]].
  671. * @param measure
  672. * @param systemLineEnum
  673. * @param isSystemStartMeasure
  674. * @returns {number}
  675. */
  676. protected getLineWidth(measure: GraphicalMeasure, systemLineEnum: SystemLinesEnum, isSystemStartMeasure: boolean): number {
  677. let width: number = measure.getLineWidth(systemLineEnum);
  678. if (systemLineEnum === SystemLinesEnum.DotsBoldBoldDots) {
  679. width /= 2;
  680. }
  681. if (isSystemStartMeasure && systemLineEnum === SystemLinesEnum.BoldThinDots) {
  682. width += this.rules.DistanceBetweenLastInstructionAndRepetitionBarline;
  683. }
  684. return width;
  685. }
  686. protected previousMeasureEndsLineRepetition(): boolean {
  687. if (this.measureListIndex === 0) {
  688. return false;
  689. }
  690. for (let idx: number = 0, len: number = this.measureList[this.measureListIndex - 1].length; idx < len; ++idx) {
  691. const measure: GraphicalMeasure = this.measureList[this.measureListIndex - 1][idx];
  692. if (measure.endsWithLineRepetition()) {
  693. return true;
  694. }
  695. }
  696. return false;
  697. }
  698. /**
  699. * Check if at this [[Measure]] starts a [[Repetition]].
  700. * @returns {boolean}
  701. */
  702. protected thisMeasureBeginsLineRepetition(): boolean {
  703. for (let idx: number = 0, len: number = this.measureList[this.measureListIndex].length; idx < len; ++idx) {
  704. const measure: GraphicalMeasure = this.measureList[this.measureListIndex][idx];
  705. if (measure.beginsWithLineRepetition()) {
  706. return true;
  707. }
  708. }
  709. return false;
  710. }
  711. /**
  712. * Check if a [[Repetition]] starts at the next [[Measure]].
  713. * @returns {boolean}
  714. */
  715. protected nextMeasureBeginsLineRepetition(): boolean {
  716. const nextMeasureIndex: number = this.measureListIndex + 1;
  717. if (nextMeasureIndex >= this.graphicalMusicSheet.ParentMusicSheet.SourceMeasures.length
  718. || !this.measureList[nextMeasureIndex]) {
  719. return false;
  720. }
  721. for (let idx: number = 0, len: number = this.measureList[nextMeasureIndex].length; idx < len; ++idx) {
  722. const measure: GraphicalMeasure = this.measureList[nextMeasureIndex][idx];
  723. if (measure.beginsWithLineRepetition()) {
  724. return true;
  725. }
  726. }
  727. return false;
  728. }
  729. /**
  730. * Check if this [[Measure]] is a [[Repetition]] ending.
  731. * @returns {boolean}
  732. */
  733. protected thisMeasureEndsLineRepetition(): boolean {
  734. for (let idx: number = 0, len: number = this.measureList[this.measureListIndex].length; idx < len; ++idx) {
  735. const measure: GraphicalMeasure = this.measureList[this.measureListIndex][idx];
  736. if (measure.endsWithLineRepetition()) {
  737. return true;
  738. }
  739. }
  740. return false;
  741. }
  742. /**
  743. * Check if a [[Repetition]] starts at the next [[Measure]].
  744. * @returns {boolean}
  745. */
  746. protected nextMeasureBeginsWordRepetition(): boolean {
  747. const nextMeasureIndex: number = this.measureListIndex + 1;
  748. if (nextMeasureIndex >= this.graphicalMusicSheet.ParentMusicSheet.SourceMeasures.length ||
  749. nextMeasureIndex > this.measureList.length - 1) {
  750. return false;
  751. }
  752. for (let idx: number = 0, len: number = this.measureList[nextMeasureIndex].length; idx < len; ++idx) {
  753. const measure: GraphicalMeasure = this.measureList[nextMeasureIndex][idx];
  754. if (measure.beginsWithWordRepetition()) {
  755. return true;
  756. }
  757. }
  758. return false;
  759. }
  760. /**
  761. * Check if this [[Measure]] is a [[Repetition]] ending.
  762. * @returns {boolean}
  763. */
  764. protected thisMeasureEndsWordRepetition(): boolean {
  765. for (let idx: number = 0, len: number = this.measureList[this.measureListIndex].length; idx < len; ++idx) {
  766. const measure: GraphicalMeasure = this.measureList[this.measureListIndex][idx];
  767. if (measure.endsWithWordRepetition()) {
  768. return true;
  769. }
  770. }
  771. return false;
  772. }
  773. /**
  774. * Check if the next [[Measure]] has a [[KeyInstruction]] change.
  775. * @returns {boolean}
  776. */
  777. protected nextMeasureHasKeyInstructionChange(): boolean {
  778. return this.getNextMeasureKeyInstruction() !== undefined;
  779. }
  780. protected getNextMeasureKeyInstruction(): KeyInstruction {
  781. if (this.measureListIndex < this.measureList.length - 1) {
  782. for (let visIndex: number = 0; visIndex < this.measureList[this.measureListIndex].length; visIndex++) {
  783. const sourceMeasure: SourceMeasure = this.measureList[this.measureListIndex + 1][visIndex].parentSourceMeasure;
  784. if (!sourceMeasure) {
  785. return undefined;
  786. }
  787. return sourceMeasure.getKeyInstruction(this.visibleStaffIndices[visIndex]);
  788. }
  789. }
  790. return undefined;
  791. }
  792. /**
  793. * Calculate the X ScalingFactor in order to strech the whole System.
  794. * @param systemFixWidth
  795. * @param systemVarWidth
  796. * @returns {number}
  797. */
  798. protected calculateXScalingFactor(systemFixWidth: number, systemVarWidth: number): number {
  799. if (Math.abs(systemVarWidth - 0) < 0.00001 || Math.abs(systemFixWidth - 0) < 0.00001) {
  800. return 1.0;
  801. }
  802. let systemEndX: number;
  803. const currentSystem: MusicSystem = this.currentSystemParams.currentSystem;
  804. systemEndX = currentSystem.StaffLines[0].PositionAndShape.Size.width;
  805. const scalingFactor: number = (systemEndX - systemFixWidth) / systemVarWidth;
  806. return scalingFactor;
  807. }
  808. /**
  809. * Stretch the whole System so that no white space is left at the end.
  810. * @param systemEndsPart
  811. */
  812. protected stretchMusicSystem(systemEndsPart: boolean): void {
  813. let scalingFactor: number = this.calculateXScalingFactor(
  814. this.currentSystemParams.currentSystemFixWidth, this.currentSystemParams.currentSystemVarWidth
  815. );
  816. if (systemEndsPart) {
  817. scalingFactor = Math.min(scalingFactor, this.rules.LastSystemMaxScalingFactor);
  818. }
  819. const currentSystem: MusicSystem = this.currentSystemParams.currentSystem;
  820. for (let visStaffIdx: number = 0, len: number = currentSystem.StaffLines.length; visStaffIdx < len; ++visStaffIdx) {
  821. const staffLine: StaffLine = currentSystem.StaffLines[visStaffIdx];
  822. let currentXPosition: number = 0.0;
  823. for (let measureIndex: number = 0; measureIndex < staffLine.Measures.length; measureIndex++) {
  824. const measure: GraphicalMeasure = staffLine.Measures[measureIndex];
  825. measure.setPositionInStaffline(currentXPosition);
  826. measure.setWidth(measure.beginInstructionsWidth + measure.minimumStaffEntriesWidth * scalingFactor + measure.endInstructionsWidth);
  827. if (measureIndex < this.currentSystemParams.systemMeasures.length) {
  828. const startLine: SystemLinesEnum = this.currentSystemParams.systemMeasures[measureIndex].beginLine;
  829. const lineWidth: number = measure.getLineWidth(SystemLinesEnum.BoldThinDots);
  830. switch (startLine) {
  831. case SystemLinesEnum.BoldThinDots:
  832. let xPosition: number = currentXPosition;
  833. if (measureIndex === 0) {
  834. xPosition = currentXPosition + measure.beginInstructionsWidth - lineWidth;
  835. }
  836. currentSystem.createVerticalLineForMeasure(xPosition, lineWidth, startLine, SystemLinePosition.MeasureBegin, measureIndex, measure);
  837. break;
  838. default:
  839. }
  840. }
  841. measure.staffEntriesScaleFactor = scalingFactor;
  842. measure.layoutSymbols();
  843. const nextMeasureHasRepStartLine: boolean = measureIndex + 1 < this.currentSystemParams.systemMeasures.length
  844. && this.currentSystemParams.systemMeasures[measureIndex + 1].beginLine === SystemLinesEnum.BoldThinDots;
  845. if (!nextMeasureHasRepStartLine) {
  846. let endLine: SystemLinesEnum = SystemLinesEnum.SingleThin;
  847. if (measureIndex < this.currentSystemParams.systemMeasures.length) {
  848. endLine = this.currentSystemParams.systemMeasures[measureIndex].endLine;
  849. }
  850. const lineWidth: number = measure.getLineWidth(endLine);
  851. let xPos: number = measure.PositionAndShape.RelativePosition.x + measure.PositionAndShape.BorderRight - lineWidth;
  852. if (endLine === SystemLinesEnum.DotsBoldBoldDots) {
  853. xPos -= lineWidth / 2;
  854. }
  855. currentSystem.createVerticalLineForMeasure(xPos, lineWidth, endLine, SystemLinePosition.MeasureEnd, measureIndex, measure);
  856. }
  857. currentXPosition = measure.PositionAndShape.RelativePosition.x + measure.PositionAndShape.BorderRight;
  858. }
  859. }
  860. if (systemEndsPart) {
  861. this.decreaseMusicSystemBorders();
  862. }
  863. }
  864. /**
  865. * If the last [[MusicSystem]] doesn't need stretching, then this method decreases the System's Width,
  866. * the [[StaffLine]]'s Width and the 5 [[StaffLine]]s length.
  867. */
  868. protected decreaseMusicSystemBorders(): void {
  869. const currentSystem: MusicSystem = this.currentSystemParams.currentSystem;
  870. const bb: BoundingBox = CollectionUtil.last(currentSystem.StaffLines[0].Measures).PositionAndShape;
  871. const width: number = bb.RelativePosition.x + bb.Size.width;
  872. for (let idx: number = 0, len: number = currentSystem.StaffLines.length; idx < len; ++idx) {
  873. const staffLine: StaffLine = currentSystem.StaffLines[idx];
  874. staffLine.PositionAndShape.BorderRight = width;
  875. for (let idx2: number = 0, len2: number = staffLine.StaffLines.length; idx2 < len2; ++idx2) {
  876. const graphicalLine: GraphicalLine = staffLine.StaffLines[idx2];
  877. graphicalLine.End = new PointF2D(width, graphicalLine.End.y);
  878. }
  879. }
  880. currentSystem.PositionAndShape.BorderRight = width + this.currentSystemParams.maxLabelLength + this.rules.SystemLabelsRightMargin;
  881. }
  882. /**
  883. * This method updates the System's StaffLine's RelativePosition (starting from the given index).
  884. * @param musicSystem
  885. * @param index
  886. * @param value
  887. */
  888. protected updateStaffLinesRelativePosition(musicSystem: MusicSystem, index: number, value: number): void {
  889. for (let i: number = index; i < musicSystem.StaffLines.length; i++) {
  890. musicSystem.StaffLines[i].PositionAndShape.RelativePosition.y = value;
  891. }
  892. musicSystem.PositionAndShape.BorderBottom += value;
  893. }
  894. /**
  895. * Create a new [[GraphicalMusicPage]]
  896. * (for now only one long page is used per music sheet, as we scroll down and have no page flips)
  897. * @returns {GraphicalMusicPage}
  898. */
  899. protected createMusicPage(): GraphicalMusicPage {
  900. const page: GraphicalMusicPage = new GraphicalMusicPage(this.graphicalMusicSheet);
  901. this.graphicalMusicSheet.MusicPages.push(page);
  902. page.PageNumber = this.graphicalMusicSheet.MusicPages.length; // caution: page number = page index + 1
  903. page.PositionAndShape.BorderLeft = 0.0;
  904. page.PositionAndShape.BorderRight = this.graphicalMusicSheet.ParentMusicSheet.pageWidth;
  905. page.PositionAndShape.BorderTop = 0.0;
  906. page.PositionAndShape.BorderBottom = this.rules.PageHeight;
  907. page.PositionAndShape.RelativePosition = new PointF2D(0.0, 0.0);
  908. return page;
  909. }
  910. protected addSystemToPage(page: GraphicalMusicPage, system: MusicSystem): void {
  911. page.MusicSystems.push(system);
  912. system.Parent = page;
  913. }
  914. /**
  915. * This method checks the distances between any two consecutive StaffLines of a System and if needed, shifts the lower one down.
  916. * @param musicSystem
  917. */
  918. protected optimizeDistanceBetweenStaffLines(musicSystem: MusicSystem): void {
  919. // don't perform any y-spacing in case of a StaffEntryLink (in both StaffLines)
  920. if (!musicSystem.checkStaffEntriesForStaffEntryLink()) {
  921. for (let i: number = 0; i < musicSystem.StaffLines.length - 1; i++) {
  922. const upperBottomLine: number[] = musicSystem.StaffLines[i].BottomLine;
  923. const lowerSkyLine: number[] = musicSystem.StaffLines[i + 1].SkyLine;
  924. // 1. Find maximum required space for sky bottom line touching each other
  925. let maxDistance: number = 0;
  926. for (let j: number = 0; j < upperBottomLine.length; j++) {
  927. const bottomLineValue: number = upperBottomLine[j];
  928. // look at a range of +/- 2 Units to also ensure that objects are also not too close in x-direction:
  929. const startIdx: number = Math.max(0, j - 6);
  930. const endIdx: number = Math.min(lowerSkyLine.length - 1, j + 6);
  931. let skylineValue: number = 0;
  932. for (let lowerIdx: number = startIdx; lowerIdx <= endIdx; lowerIdx++) {
  933. skylineValue = Math.min(skylineValue, lowerSkyLine[lowerIdx]);
  934. }
  935. const distance: number = bottomLineValue - skylineValue;
  936. maxDistance = Math.max(distance, maxDistance);
  937. }
  938. // 2. Add user defined distance between sky bottom line
  939. maxDistance += this.rules.MinSkyBottomDistBetweenStaves;
  940. // 3. Take the maximum between previous value and user defined value for staff line minimum distance
  941. maxDistance = Math.max(maxDistance, this.rules.StaffHeight + this.rules.MinimumStaffLineDistance);
  942. const lowerStafflineYPos: number = maxDistance + musicSystem.StaffLines[i].PositionAndShape.RelativePosition.y;
  943. this.updateStaffLinesRelativePosition(musicSystem, i + 1, lowerStafflineYPos);
  944. }
  945. }
  946. const firstStaffLine: StaffLine = musicSystem.StaffLines[0];
  947. musicSystem.PositionAndShape.BorderTop = firstStaffLine.PositionAndShape.RelativePosition.y + firstStaffLine.PositionAndShape.BorderTop;
  948. const lastStaffLine: StaffLine = musicSystem.StaffLines[musicSystem.StaffLines.length - 1];
  949. musicSystem.PositionAndShape.BorderBottom = lastStaffLine.PositionAndShape.RelativePosition.y + lastStaffLine.PositionAndShape.BorderBottom;
  950. }
  951. /** Calculates the relative Positions of all MusicSystems.
  952. *
  953. */
  954. protected calculateMusicSystemsRelativePositions(): void {
  955. let currentPage: GraphicalMusicPage = this.createMusicPage();
  956. let currentYPosition: number = 0;
  957. // xPosition is always fixed
  958. let currentSystem: MusicSystem = this.musicSystems[0];
  959. let timesPageCouldntFitSingleSystem: number = 0;
  960. for (let i: number = 0; i < this.musicSystems.length; i++) {
  961. currentSystem = this.musicSystems[i];
  962. if (currentPage.MusicSystems.length === 0) {
  963. // if this is the first system on the current page:
  964. // take top margins into account
  965. this.addSystemToPage(currentPage, currentSystem);
  966. if (this.rules.CompactMode) {
  967. currentYPosition = this.rules.PageTopMarginNarrow;
  968. } else {
  969. currentYPosition = this.rules.PageTopMargin;
  970. }
  971. // if it is the first System on the FIRST page: Add Title height and gap-distance
  972. if (this.graphicalMusicSheet.MusicPages.length === 1 &&
  973. this.rules.RenderTitle) {
  974. currentYPosition += this.rules.TitleTopDistance + this.rules.SheetTitleHeight +
  975. this.rules.TitleBottomDistance;
  976. }
  977. // now add the border-top: everything that stands out above the staffline:
  978. currentYPosition += -currentSystem.PositionAndShape.BorderTop;
  979. const relativePosition: PointF2D = new PointF2D(this.rules.PageLeftMargin + this.rules.SystemLeftMargin,
  980. currentYPosition);
  981. currentSystem.PositionAndShape.RelativePosition = relativePosition;
  982. // check if the first system doesn't even fit on the page -> would lead to truncation at bottom end:
  983. if (currentYPosition + currentSystem.PositionAndShape.BorderBottom > this.rules.PageHeight - this.rules.PageBottomMargin) {
  984. // can't fit single system on page, maybe PageFormat too small
  985. timesPageCouldntFitSingleSystem++;
  986. if (timesPageCouldntFitSingleSystem <= 4) { // only warn once with detailed info
  987. console.log(`warning: could not fit a single system on page ${currentPage.PageNumber}` +
  988. ` and measure number ${currentSystem.GraphicalMeasures[0][0].MeasureNumber}.
  989. The PageFormat may be too small for this sheet."
  990. Will not give further warnings for all pages, only total.`
  991. );
  992. }
  993. }
  994. } else {
  995. // if this is not the first system on the page:
  996. // find optimum distance between Systems
  997. const previousSystem: MusicSystem = this.musicSystems[i - 1];
  998. const prevSystemLastStaffline: StaffLine = previousSystem.StaffLines[previousSystem.StaffLines.length - 1];
  999. const prevSystemLastStaffLineBB: BoundingBox = prevSystemLastStaffline.PositionAndShape;
  1000. let distance: number = this.findReqiredDistanceWithSkyBottomLine(previousSystem, currentSystem);
  1001. // make sure the optical distance is the user-defined min distance:
  1002. distance += this.rules.MinSkyBottomDistBetweenSystems;
  1003. distance = Math.max(distance, this.rules.MinimumDistanceBetweenSystems + prevSystemLastStaffline.StaffHeight);
  1004. const newYPosition: number = currentYPosition +
  1005. prevSystemLastStaffLineBB.RelativePosition.y +
  1006. distance;
  1007. // calculate the needed height for placing the current system on the page,
  1008. // to see if it still fits:
  1009. const currSystemBottomYPos: number = newYPosition +
  1010. currentSystem.PositionAndShape.BorderMarginBottom;
  1011. const doXmlPageBreak: boolean = this.rules.NewPageAtXMLNewPageAttribute && previousSystem.breaksPage;
  1012. if (!doXmlPageBreak &&
  1013. (currSystemBottomYPos < this.rules.PageHeight - this.rules.PageBottomMargin)) {
  1014. // enough space on this page:
  1015. this.addSystemToPage(currentPage, currentSystem);
  1016. currentYPosition = newYPosition;
  1017. const relativePosition: PointF2D = new PointF2D(this.rules.PageLeftMargin + this.rules.SystemLeftMargin,
  1018. currentYPosition);
  1019. currentSystem.PositionAndShape.RelativePosition = relativePosition;
  1020. } else {
  1021. // new page needed:
  1022. currentPage = this.createMusicPage();
  1023. // re-check this system again:
  1024. i -= 1;
  1025. continue;
  1026. }
  1027. }
  1028. }
  1029. if (timesPageCouldntFitSingleSystem > 0) {
  1030. console.log(`total amount of pages that couldn't fit a single music system: ${timesPageCouldntFitSingleSystem} of ${currentPage.PageNumber}`);
  1031. }
  1032. }
  1033. /**
  1034. * Finds the minimum required distance between two systems
  1035. * with the help of the sky- and bottom lines
  1036. * @param upperSystem
  1037. * @param lowerSystem
  1038. */
  1039. private findReqiredDistanceWithSkyBottomLine(upperSystem: MusicSystem, lowerSystem: MusicSystem): number {
  1040. const upperSystemLastStaffLine: StaffLine = upperSystem.StaffLines[upperSystem.StaffLines.length - 1];
  1041. const lowerSystemFirstStaffLine: StaffLine = lowerSystem.StaffLines[0];
  1042. const upperBottomLineArray: number[] = upperSystemLastStaffLine.BottomLine;
  1043. const lowerSkyLineArray: number[] = lowerSystemFirstStaffLine.SkyLine;
  1044. const upperStaffLineBB: BoundingBox = upperSystemLastStaffLine.PositionAndShape;
  1045. const lowerStaffLineBB: BoundingBox = lowerSystemFirstStaffLine.PositionAndShape;
  1046. const skylinePixelWidth: number = 1 / this.rules.SamplingUnit;
  1047. // Find maximum required space for sky and bottom line touching each other
  1048. let maxDistance: number = 0;
  1049. for (let upperIdx: number = 0; upperIdx < upperBottomLineArray.length; upperIdx++) {
  1050. const bottomLineValue: number = upperBottomLineArray[upperIdx];
  1051. // find index of the same x-position in lower skyline:
  1052. const lowerCenterIdx: number = upperIdx +
  1053. Math.round((upperStaffLineBB.RelativePosition.x - lowerStaffLineBB.RelativePosition.x) * skylinePixelWidth);
  1054. if (lowerCenterIdx < 0) {
  1055. // should actually not happen..
  1056. continue;
  1057. }
  1058. if (lowerCenterIdx >= lowerSkyLineArray.length) {
  1059. // lower system ends earlier x-wise than upper system (e.g. at last system, if it is not stretched)
  1060. break;
  1061. }
  1062. // look at a range of +/- 2 Units to also ensure that objects are also not too close in x-direction:
  1063. const startIdx: number = Math.max(0, lowerCenterIdx - 6);
  1064. const endIdx: number = Math.min(lowerSkyLineArray.length - 1, lowerCenterIdx + 6);
  1065. let skylineValue: number = 0;
  1066. for (let lowerIdx: number = startIdx; lowerIdx <= endIdx; lowerIdx++) {
  1067. skylineValue = Math.min(skylineValue, lowerSkyLineArray[lowerIdx]);
  1068. }
  1069. const distance: number = bottomLineValue - skylineValue;
  1070. maxDistance = Math.max(distance, maxDistance);
  1071. }
  1072. if (maxDistance === 0) {
  1073. // can only happen when the bottom- and skyline have no x-overlap at all:
  1074. // fall back to borders:
  1075. maxDistance = upperStaffLineBB.BorderBottom - lowerStaffLineBB.BorderTop;
  1076. }
  1077. return maxDistance;
  1078. }
  1079. }
  1080. export class SystemBuildParameters {
  1081. public currentSystem: MusicSystem;
  1082. public systemMeasures: MeasureBuildParameters[] = [];
  1083. public systemMeasureIndex: number = 0;
  1084. public currentWidth: number = 0;
  1085. public currentSystemFixWidth: number = 0;
  1086. public currentSystemVarWidth: number = 0;
  1087. public maxLabelLength: number = 0;
  1088. public IsSystemStartMeasure(): boolean {
  1089. return this.systemMeasureIndex === 0;
  1090. }
  1091. }
  1092. export class MeasureBuildParameters {
  1093. public beginLine: SystemLinesEnum;
  1094. public endLine: SystemLinesEnum;
  1095. }