MusicSystem.ts 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472
  1. import {StaffLine} from "./StaffLine";
  2. import {Instrument} from "../Instrument";
  3. import {BoundingBox} from "./BoundingBox";
  4. import {Fraction} from "../../Common/DataObjects/Fraction";
  5. import {SourceMeasure} from "../VoiceData/SourceMeasure";
  6. import {InstrumentalGroup} from "../InstrumentalGroup";
  7. import {TextAlignment} from "../../Common/Enums/TextAlignment";
  8. import {GraphicalMusicPage} from "./GraphicalMusicPage";
  9. import {GraphicalLabel} from "./GraphicalLabel";
  10. import {StaffMeasure} from "./StaffMeasure";
  11. import {GraphicalObject} from "./GraphicalObject";
  12. import {EngravingRules} from "./EngravingRules";
  13. import {PointF2D} from "../../Common/DataObjects/PointF2D";
  14. import {GraphicalStaffEntry} from "./GraphicalStaffEntry";
  15. import {SystemLinesEnum} from "./SystemLinesEnum";
  16. import Dictionary from "typescript-collections/dist/lib/Dictionary";
  17. import {GraphicalComment} from "./GraphicalComment";
  18. import {GraphicalMarkedArea} from "./GraphicalMarkedArea";
  19. import {SystemLine} from "./SystemLine";
  20. import {SystemLinePosition} from "./SystemLinePosition";
  21. import {Staff} from "../VoiceData/Staff";
  22. /**
  23. * A MusicSystem contains the [[StaffLine]]s for all instruments, until a line break
  24. */
  25. export abstract class MusicSystem extends GraphicalObject {
  26. public needsToBeRedrawn: boolean = true;
  27. protected parent: GraphicalMusicPage;
  28. protected id: number;
  29. protected staffLines: StaffLine[] = [];
  30. protected graphicalMeasures: StaffMeasure[][] = [];
  31. protected labels: Dictionary<GraphicalLabel, Instrument> = new Dictionary<GraphicalLabel, Instrument>();
  32. protected measureNumberLabels: GraphicalLabel[] = [];
  33. protected maxLabelLength: number;
  34. protected objectsToRedraw: [Object[], Object][] = [];
  35. protected instrumentBrackets: GraphicalObject[] = [];
  36. protected groupBrackets: GraphicalObject[] = [];
  37. protected graphicalMarkedAreas: GraphicalMarkedArea[] = [];
  38. protected graphicalComments: GraphicalComment[] = [];
  39. protected systemLines: SystemLine[] = [];
  40. protected rules: EngravingRules;
  41. constructor(parent: GraphicalMusicPage, id: number) {
  42. super();
  43. this.parent = parent;
  44. this.id = id;
  45. this.boundingBox = new BoundingBox(this, parent.PositionAndShape);
  46. this.maxLabelLength = 0.0;
  47. this.rules = this.parent.Parent.ParentMusicSheet.Rules;
  48. }
  49. public get Parent(): GraphicalMusicPage {
  50. return this.parent;
  51. }
  52. public set Parent(value: GraphicalMusicPage) {
  53. this.parent = value;
  54. }
  55. public get NextSystem(): MusicSystem {
  56. const idxInParent: number = this.Parent.MusicSystems.indexOf(this);
  57. return idxInParent !== this.Parent.MusicSystems.length ? this.Parent.MusicSystems[idxInParent + 1] : undefined;
  58. }
  59. public get StaffLines(): StaffLine[] {
  60. return this.staffLines;
  61. }
  62. public get GraphicalMeasures(): StaffMeasure[][] {
  63. return this.graphicalMeasures;
  64. }
  65. public get MeasureNumberLabels(): GraphicalLabel[] {
  66. return this.measureNumberLabels;
  67. }
  68. public get Labels(): GraphicalLabel[] {
  69. return this.labels.keys();
  70. }
  71. public get ObjectsToRedraw(): [Object[], Object][] {
  72. return this.objectsToRedraw;
  73. }
  74. public get InstrumentBrackets(): GraphicalObject[] {
  75. return this.instrumentBrackets;
  76. }
  77. public get GroupBrackets(): GraphicalObject[] {
  78. return this.groupBrackets;
  79. }
  80. public get GraphicalMarkedAreas(): GraphicalMarkedArea[] {
  81. return this.graphicalMarkedAreas;
  82. }
  83. public get GraphicalComments(): GraphicalComment[] {
  84. return this.graphicalComments;
  85. }
  86. public get SystemLines(): SystemLine[] {
  87. return this.systemLines;
  88. }
  89. public get Id(): number {
  90. return this.id;
  91. }
  92. /**
  93. * Create the left vertical Line connecting all staves of the [[MusicSystem]].
  94. * @param lineWidth
  95. * @param systemLabelsRightMargin
  96. */
  97. public createSystemLeftLine(lineWidth: number, systemLabelsRightMargin: number): void {
  98. let xPosition: number = -lineWidth / 2;
  99. if (this === this.parent.MusicSystems[0] && this.parent === this.parent.Parent.MusicPages[0]) {
  100. xPosition = this.maxLabelLength + systemLabelsRightMargin - lineWidth / 2;
  101. }
  102. const top: StaffMeasure = this.staffLines[0].Measures[0];
  103. let bottom: StaffMeasure = undefined;
  104. if (this.staffLines.length > 1) {
  105. bottom = this.staffLines[this.staffLines.length - 1].Measures[0];
  106. }
  107. const leftSystemLine: SystemLine = this.createSystemLine(xPosition, lineWidth, SystemLinesEnum.SingleThin,
  108. SystemLinePosition.MeasureBegin, this, top, bottom);
  109. this.SystemLines.push(leftSystemLine);
  110. leftSystemLine.PositionAndShape.RelativePosition = new PointF2D(xPosition, 0);
  111. leftSystemLine.PositionAndShape.BorderLeft = 0;
  112. leftSystemLine.PositionAndShape.BorderRight = lineWidth;
  113. leftSystemLine.PositionAndShape.BorderTop = 0;
  114. leftSystemLine.PositionAndShape.BorderBottom = this.boundingBox.Size.height;
  115. this.createLinesForSystemLine(leftSystemLine);
  116. }
  117. /**
  118. * Create the vertical Lines after the End of all [[StaffLine]]'s Measures
  119. * @param xPosition
  120. * @param lineWidth
  121. * @param lineType
  122. * @param linePosition indicates if the line belongs to start or end of measure
  123. * @param measureIndex the measure index within the staffline
  124. * @param measure
  125. */
  126. public createVerticalLineForMeasure(xPosition: number, lineWidth: number, lineType: SystemLinesEnum, linePosition: SystemLinePosition,
  127. measureIndex: number, measure: StaffMeasure): void {
  128. const staffLine: StaffLine = measure.ParentStaffLine;
  129. const staffLineRelative: PointF2D = new PointF2D(staffLine.PositionAndShape.RelativePosition.x,
  130. staffLine.PositionAndShape.RelativePosition.y);
  131. const staves: Staff[] = staffLine.ParentStaff.ParentInstrument.Staves;
  132. if (staffLine.ParentStaff === staves[0]) {
  133. let bottomMeasure: StaffMeasure = undefined;
  134. if (staves.length > 1) {
  135. bottomMeasure = this.getBottomStaffLine(staffLine).Measures[measureIndex];
  136. }
  137. const singleVerticalLineAfterMeasure: SystemLine = this.createSystemLine(xPosition, lineWidth, lineType,
  138. linePosition, this, measure, bottomMeasure);
  139. const systemXPosition: number = staffLineRelative.x + xPosition;
  140. singleVerticalLineAfterMeasure.PositionAndShape.RelativePosition = new PointF2D(systemXPosition, 0);
  141. singleVerticalLineAfterMeasure.PositionAndShape.BorderLeft = 0;
  142. singleVerticalLineAfterMeasure.PositionAndShape.BorderRight = lineWidth;
  143. this.SystemLines.push(singleVerticalLineAfterMeasure);
  144. }
  145. }
  146. /**
  147. * Set the y-Positions of all the system lines in the system and creates the graphical Lines and dots within.
  148. * @param rules
  149. */
  150. public setYPositionsToVerticalLineObjectsAndCreateLines(rules: EngravingRules): void {
  151. // empty
  152. }
  153. public calculateBorders(rules: EngravingRules): void {
  154. // empty
  155. }
  156. public alignBeginInstructions(): void {
  157. // empty
  158. }
  159. public GetLeftBorderAbsoluteXPosition(): number {
  160. return this.StaffLines[0].PositionAndShape.AbsolutePosition.x + this.StaffLines[0].Measures[0].beginInstructionsWidth;
  161. }
  162. public GetRightBorderAbsoluteXPosition(): number {
  163. return this.StaffLines[0].PositionAndShape.AbsolutePosition.x + this.StaffLines[0].StaffLines[0].End.x;
  164. }
  165. public AddStaffMeasures(graphicalMeasures: StaffMeasure[]): void {
  166. for (let idx: number = 0, len: number = graphicalMeasures.length; idx < len; ++idx) {
  167. const graphicalMeasure: StaffMeasure = graphicalMeasures[idx];
  168. graphicalMeasure.parentMusicSystem = this;
  169. }
  170. this.graphicalMeasures.push(graphicalMeasures);
  171. }
  172. public GetSystemsFirstTimeStamp(): Fraction {
  173. return this.graphicalMeasures[0][0].parentSourceMeasure.AbsoluteTimestamp;
  174. }
  175. public GetSystemsLastTimeStamp(): Fraction {
  176. const m: SourceMeasure = this.graphicalMeasures[this.graphicalMeasures.length - 1][0].parentSourceMeasure;
  177. return Fraction.plus(m.AbsoluteTimestamp, m.Duration);
  178. }
  179. /**
  180. * Create an InstrumentBracket for each multiStave Instrument.
  181. * @param instruments
  182. * @param staffHeight
  183. */
  184. public createInstrumentBrackets(instruments: Instrument[], staffHeight: number): void {
  185. for (let idx: number = 0, len: number = instruments.length; idx < len; ++idx) {
  186. const instrument: Instrument = instruments[idx];
  187. if (instrument.Staves.length > 1) {
  188. let firstStaffLine: StaffLine = undefined, lastStaffLine: StaffLine = undefined;
  189. for (let idx2: number = 0, len2: number = this.staffLines.length; idx2 < len2; ++idx2) {
  190. const staffLine: StaffLine = this.staffLines[idx2];
  191. if (staffLine.ParentStaff === instrument.Staves[0]) {
  192. firstStaffLine = staffLine;
  193. }
  194. if (staffLine.ParentStaff === instrument.Staves[instrument.Staves.length - 1]) {
  195. lastStaffLine = staffLine;
  196. }
  197. }
  198. if (firstStaffLine !== undefined && lastStaffLine !== undefined) {
  199. this.createInstrumentBracket(firstStaffLine, lastStaffLine);
  200. }
  201. }
  202. }
  203. }
  204. /**
  205. * Create a GroupBracket for an [[InstrumentalGroup]].
  206. * @param instrumentGroups
  207. * @param staffHeight
  208. * @param recursionDepth
  209. */
  210. public createGroupBrackets(instrumentGroups: InstrumentalGroup[], staffHeight: number, recursionDepth: number): void {
  211. for (let idx: number = 0, len: number = instrumentGroups.length; idx < len; ++idx) {
  212. const instrumentGroup: InstrumentalGroup = instrumentGroups[idx];
  213. if (instrumentGroup.InstrumentalGroups.length < 1) {
  214. continue;
  215. }
  216. const instrument1: Instrument = this.findFirstVisibleInstrumentInInstrumentalGroup(instrumentGroup);
  217. const instrument2: Instrument = this.findLastVisibleInstrumentInInstrumentalGroup(instrumentGroup);
  218. if (instrument1 === undefined || instrument2 === undefined) {
  219. continue;
  220. }
  221. let firstStaffLine: StaffLine = undefined;
  222. let lastStaffLine: StaffLine = undefined;
  223. for (let idx2: number = 0, len2: number = this.staffLines.length; idx2 < len2; ++idx2) {
  224. const staffLine: StaffLine = this.staffLines[idx2];
  225. if (staffLine.ParentStaff === instrument1.Staves[0]) {
  226. firstStaffLine = staffLine;
  227. }
  228. if (staffLine.ParentStaff === instrument2.Staves[0]) {
  229. lastStaffLine = staffLine;
  230. }
  231. }
  232. if (firstStaffLine !== undefined && lastStaffLine !== undefined) {
  233. this.createGroupBracket(firstStaffLine, lastStaffLine, recursionDepth);
  234. }
  235. if (instrumentGroup.InstrumentalGroups.length < 1) {
  236. continue;
  237. }
  238. this.createGroupBrackets(instrumentGroup.InstrumentalGroups, staffHeight, recursionDepth + 1);
  239. }
  240. }
  241. /**
  242. * Create the Instrument's Labels (only for the first [[MusicSystem]] of the first MusicPage).
  243. * @param instrumentLabelTextHeight
  244. * @param systemLabelsRightMargin
  245. * @param labelMarginBorderFactor
  246. */
  247. public createMusicSystemLabel(instrumentLabelTextHeight: number, systemLabelsRightMargin: number, labelMarginBorderFactor: number): void {
  248. if (this.parent === this.parent.Parent.MusicPages[0] && this === this.parent.MusicSystems[0]) {
  249. const instruments: Instrument[] = this.parent.Parent.ParentMusicSheet.getVisibleInstruments();
  250. for (let idx: number = 0, len: number = instruments.length; idx < len; ++idx) {
  251. const instrument: Instrument = instruments[idx];
  252. const graphicalLabel: GraphicalLabel = new GraphicalLabel(
  253. instrument.NameLabel, instrumentLabelTextHeight, TextAlignment.LeftCenter, this.boundingBox
  254. );
  255. graphicalLabel.setLabelPositionAndShapeBorders();
  256. this.labels.setValue(graphicalLabel, instrument);
  257. // X-Position will be 0 (Label starts at the same PointF_2D with MusicSystem)
  258. // Y-Position will be calculated after the y-Spacing
  259. // graphicalLabel.PositionAndShape.RelativePosition = new PointF2D(0.0, 0.0);
  260. }
  261. // calculate maxLabelLength (needed for X-Spacing)
  262. this.maxLabelLength = 0.0;
  263. const labels: GraphicalLabel[] = this.labels.keys();
  264. for (let idx: number = 0, len: number = labels.length; idx < len; ++idx) {
  265. const label: GraphicalLabel = labels[idx];
  266. if (label.PositionAndShape.Size.width > this.maxLabelLength) {
  267. this.maxLabelLength = label.PositionAndShape.Size.width;
  268. }
  269. }
  270. this.updateMusicSystemStaffLineXPosition(systemLabelsRightMargin);
  271. }
  272. }
  273. /**
  274. * Set the Y-Positions for the MusicSystem's Labels.
  275. */
  276. public setMusicSystemLabelsYPosition(): void {
  277. if (this.parent === this.parent.Parent.MusicPages[0] && this === this.parent.MusicSystems[0]) {
  278. this.labels.forEach((key: GraphicalLabel, value: Instrument): void => {
  279. let ypositionSum: number = 0;
  280. let staffCounter: number = 0;
  281. for (let i: number = 0; i < this.staffLines.length; i++) {
  282. if (this.staffLines[i].ParentStaff.ParentInstrument === value) {
  283. for (let j: number = i; j < this.staffLines.length; j++) {
  284. const staffLine: StaffLine = this.staffLines[j];
  285. if (staffLine.ParentStaff.ParentInstrument !== value) {
  286. break;
  287. }
  288. ypositionSum += staffLine.PositionAndShape.RelativePosition.y;
  289. staffCounter++;
  290. }
  291. break;
  292. }
  293. }
  294. if (staffCounter > 0) {
  295. key.PositionAndShape.RelativePosition = new PointF2D(0.0, ypositionSum / staffCounter + 2.0);
  296. }
  297. });
  298. }
  299. }
  300. /**
  301. * Check if two "adjacent" StaffLines have BOTH a StaffEntry with a StaffEntryLink.
  302. * This is needed for the y-spacing algorithm.
  303. * @returns {boolean}
  304. */
  305. public checkStaffEntriesForStaffEntryLink(): boolean {
  306. let first: boolean = false;
  307. let second: boolean = false;
  308. for (let i: number = 0; i < this.staffLines.length - 1; i++) {
  309. for (let idx: number = 0, len: number = this.staffLines[i].Measures.length; idx < len; ++idx) {
  310. const measure: StaffMeasure = this.staffLines[i].Measures[idx];
  311. for (let idx2: number = 0, len2: number = measure.staffEntries.length; idx2 < len2; ++idx2) {
  312. const staffEntry: GraphicalStaffEntry = measure.staffEntries[idx2];
  313. if (staffEntry.sourceStaffEntry.Link !== undefined) {
  314. first = true;
  315. }
  316. }
  317. }
  318. for (let idx: number = 0, len: number = this.staffLines[i + 1].Measures.length; idx < len; ++idx) {
  319. const measure: StaffMeasure = this.staffLines[i + 1].Measures[idx];
  320. for (let idx2: number = 0, len2: number = measure.staffEntries.length; idx2 < len2; ++idx2) {
  321. const staffEntry: GraphicalStaffEntry = measure.staffEntries[idx2];
  322. if (staffEntry.sourceStaffEntry.Link !== undefined) {
  323. second = true;
  324. }
  325. }
  326. }
  327. }
  328. if (first && second) {
  329. return true;
  330. }
  331. return false;
  332. }
  333. public getBottomStaffLine(topStaffLine: StaffLine): StaffLine {
  334. const staves: Staff[] = topStaffLine.ParentStaff.ParentInstrument.Staves;
  335. const last: Staff = staves[staves.length - 1];
  336. for (const line of topStaffLine.ParentMusicSystem.staffLines) {
  337. if (line.ParentStaff === last) {
  338. return line;
  339. }
  340. }
  341. return undefined;
  342. }
  343. /**
  344. * Here the system line is generated, which acts as the container of graphical lines and dots that will be finally rendered.
  345. * It holds al the logical parameters of the system line.
  346. * @param xPosition The x position within the system
  347. * @param lineWidth The total x width
  348. * @param lineType The line type enum
  349. * @param linePosition indicates if the line belongs to start or end of measure
  350. * @param musicSystem
  351. * @param topMeasure
  352. * @param bottomMeasure
  353. */
  354. protected createSystemLine(xPosition: number, lineWidth: number, lineType: SystemLinesEnum, linePosition: SystemLinePosition,
  355. musicSystem: MusicSystem, topMeasure: StaffMeasure, bottomMeasure: StaffMeasure = undefined): SystemLine {
  356. throw new Error("not implemented");
  357. }
  358. /**
  359. * Create all the graphical lines and dots needed to render a system line (e.g. bold-thin-dots..).
  360. * @param systemLine
  361. */
  362. protected createLinesForSystemLine(systemLine: SystemLine): void {
  363. //Empty
  364. }
  365. /**
  366. * Calculates the summed x-width of a possibly given Instrument Brace and/or Group Bracket(s).
  367. * @returns {number} the x-width
  368. */
  369. protected calcBracketsWidth(): number {
  370. let width: number = 0;
  371. for (let idx: number = 0, len: number = this.GroupBrackets.length; idx < len; ++idx) {
  372. const groupBracket: GraphicalObject = this.GroupBrackets[idx];
  373. width = Math.max(width, groupBracket.PositionAndShape.Size.width);
  374. }
  375. for (let idx2: number = 0, len2: number = this.InstrumentBrackets.length; idx2 < len2; ++idx2) {
  376. const instrumentBracket: GraphicalObject = this.InstrumentBrackets[idx2];
  377. width = Math.max(width, instrumentBracket.PositionAndShape.Size.width);
  378. }
  379. return width;
  380. }
  381. protected createInstrumentBracket(firstStaffLine: StaffLine, lastStaffLine: StaffLine): void {
  382. // no impl here
  383. }
  384. protected createGroupBracket(firstStaffLine: StaffLine, lastStaffLine: StaffLine, recursionDepth: number): void {
  385. // no impl here
  386. }
  387. private findFirstVisibleInstrumentInInstrumentalGroup(instrumentalGroup: InstrumentalGroup): Instrument {
  388. for (let idx: number = 0, len: number = instrumentalGroup.InstrumentalGroups.length; idx < len; ++idx) {
  389. const groupOrInstrument: InstrumentalGroup = instrumentalGroup.InstrumentalGroups[idx];
  390. if (groupOrInstrument instanceof Instrument) {
  391. if ((<Instrument>groupOrInstrument).Visible === true) {
  392. return <Instrument>groupOrInstrument;
  393. }
  394. continue;
  395. }
  396. return this.findFirstVisibleInstrumentInInstrumentalGroup(groupOrInstrument);
  397. }
  398. return undefined;
  399. }
  400. private findLastVisibleInstrumentInInstrumentalGroup(instrumentalGroup: InstrumentalGroup): Instrument {
  401. let groupOrInstrument: InstrumentalGroup;
  402. for (let i: number = instrumentalGroup.InstrumentalGroups.length - 1; i >= 0; i--) {
  403. groupOrInstrument = instrumentalGroup.InstrumentalGroups[i];
  404. if (groupOrInstrument instanceof Instrument) {
  405. if ((<Instrument>groupOrInstrument).Visible === true) {
  406. return <Instrument>groupOrInstrument;
  407. }
  408. continue;
  409. }
  410. return this.findLastVisibleInstrumentInInstrumentalGroup(groupOrInstrument);
  411. }
  412. return undefined;
  413. }
  414. /**
  415. * Update the xPosition of the [[MusicSystem]]'s [[StaffLine]]'s due to [[Label]] positioning.
  416. * @param systemLabelsRightMargin
  417. */
  418. private updateMusicSystemStaffLineXPosition(systemLabelsRightMargin: number): void {
  419. for (let idx: number = 0, len: number = this.StaffLines.length; idx < len; ++idx) {
  420. const staffLine: StaffLine = this.StaffLines[idx];
  421. const relative: PointF2D = staffLine.PositionAndShape.RelativePosition;
  422. relative.x = this.maxLabelLength + systemLabelsRightMargin;
  423. staffLine.PositionAndShape.RelativePosition = relative;
  424. staffLine.PositionAndShape.BorderRight = this.boundingBox.Size.width - this.maxLabelLength - systemLabelsRightMargin;
  425. for (let i: number = 0; i < staffLine.StaffLines.length; i++) {
  426. const lineEnd: PointF2D = new PointF2D(staffLine.PositionAndShape.Size.width, staffLine.StaffLines[i].End.y);
  427. staffLine.StaffLines[i].End = lineEnd;
  428. }
  429. }
  430. }
  431. }