GraphicalMeasure.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. import {MusicSystem} from "./MusicSystem";
  2. import {GraphicalStaffEntry} from "./GraphicalStaffEntry";
  3. import {SourceMeasure} from "../VoiceData/SourceMeasure";
  4. import {StaffLine} from "./StaffLine";
  5. import {Staff} from "../VoiceData/Staff";
  6. import {GraphicalObject} from "./GraphicalObject";
  7. import {ClefInstruction} from "../VoiceData/Instructions/ClefInstruction";
  8. import {KeyInstruction} from "../VoiceData/Instructions/KeyInstruction";
  9. import {RhythmInstruction} from "../VoiceData/Instructions/RhythmInstruction";
  10. import {Fraction} from "../../Common/DataObjects/Fraction";
  11. import {Voice} from "../VoiceData/Voice";
  12. import {VoiceEntry} from "../VoiceData/VoiceEntry";
  13. import {SystemLinesEnum} from "./SystemLinesEnum";
  14. import {BoundingBox} from "./BoundingBox";
  15. import {PointF2D} from "../../Common/DataObjects/PointF2D";
  16. /**
  17. * Represents a measure in the music sheet (one measure in one staff line)
  18. */
  19. export abstract class GraphicalMeasure extends GraphicalObject {
  20. protected firstInstructionStaffEntry: GraphicalStaffEntry;
  21. protected lastInstructionStaffEntry: GraphicalStaffEntry;
  22. constructor(staff: Staff = undefined, parentSourceMeasure: SourceMeasure = undefined, staffLine: StaffLine = undefined) {
  23. super();
  24. this.parentStaff = staff;
  25. this.parentSourceMeasure = parentSourceMeasure;
  26. this.parentStaffLine = staffLine;
  27. if (staffLine) {
  28. this.parentStaff = staffLine.ParentStaff;
  29. this.PositionAndShape = new BoundingBox(this, staffLine.PositionAndShape);
  30. } else {
  31. this.PositionAndShape = new BoundingBox(this);
  32. }
  33. this.PositionAndShape.BorderBottom = 4;
  34. if (this.parentSourceMeasure) {
  35. this.measureNumber = this.parentSourceMeasure.MeasureNumber;
  36. }
  37. this.staffEntries = [];
  38. }
  39. public parentSourceMeasure: SourceMeasure;
  40. public staffEntries: GraphicalStaffEntry[];
  41. /**
  42. * The x-width of possibly existing: repetition start line, clef, key, rhythm.
  43. */
  44. public beginInstructionsWidth: number;
  45. /**
  46. * The minimum possible x-width of all staff entries without overlapping.
  47. */
  48. public minimumStaffEntriesWidth: number;
  49. /**
  50. * Will be set by music system builder while building systems.
  51. */
  52. public staffEntriesScaleFactor: number;
  53. /**
  54. * The x-width of possibly existing: repetition end line, clef.
  55. */
  56. public endInstructionsWidth: number;
  57. public hasError: boolean;
  58. private parentStaff: Staff;
  59. private parentMusicSystem: MusicSystem;
  60. private measureNumber: number = -1;
  61. private parentStaffLine: StaffLine;
  62. public get ParentStaff(): Staff {
  63. return this.parentStaff;
  64. }
  65. public get ParentMusicSystem(): MusicSystem {
  66. return this.parentMusicSystem;
  67. }
  68. public set ParentMusicSystem(value: MusicSystem) {
  69. this.parentMusicSystem = value;
  70. }
  71. public get MeasureNumber(): number {
  72. return this.measureNumber;
  73. }
  74. public get FirstInstructionStaffEntry(): GraphicalStaffEntry {
  75. return this.firstInstructionStaffEntry;
  76. }
  77. public set FirstInstructionStaffEntry(value: GraphicalStaffEntry) {
  78. this.firstInstructionStaffEntry = value;
  79. }
  80. public get LastInstructionStaffEntry(): GraphicalStaffEntry {
  81. return this.lastInstructionStaffEntry;
  82. }
  83. public set LastInstructionStaffEntry(value: GraphicalStaffEntry) {
  84. this.lastInstructionStaffEntry = value;
  85. }
  86. public get ParentStaffLine(): StaffLine {
  87. return this.parentStaffLine;
  88. }
  89. public set ParentStaffLine(value: StaffLine) {
  90. this.parentStaffLine = value;
  91. if (this.parentStaffLine) {
  92. this.PositionAndShape.Parent = this.parentStaffLine.PositionAndShape;
  93. }
  94. }
  95. /**
  96. * Reset all the geometric values and parameters of this measure and put it in an initialized state.
  97. * This is needed to evaluate a measure a second time by system builder.
  98. */
  99. public resetLayout(): void {
  100. throw new Error("not implemented");
  101. }
  102. /**
  103. * Return the x-width of a given measure line.
  104. * @param line
  105. */
  106. public getLineWidth(line: SystemLinesEnum): number {
  107. throw new Error("not implemented");
  108. }
  109. /**
  110. * Add the given clef to the begin of the measure.
  111. * This has to update/increase BeginInstructionsWidth.
  112. * @param clef
  113. */
  114. public addClefAtBegin(clef: ClefInstruction): void {
  115. throw new Error("not implemented");
  116. }
  117. /**
  118. * Add the given key to the begin of the measure.
  119. * This has to update/increase BeginInstructionsWidth.
  120. * @param currentKey - The new valid key.
  121. * @param previousKey - The old cancelled key. Needed to show which accidentals are not valid any more.
  122. * @param currentClef - The valid clef. Needed to put the accidentals on the right y-positions.
  123. */
  124. public addKeyAtBegin(currentKey: KeyInstruction, previousKey: KeyInstruction, currentClef: ClefInstruction): void {
  125. throw new Error("not implemented");
  126. }
  127. /**
  128. * Add the given rhythm to the begin of the measure.
  129. * This has to update/increase BeginInstructionsWidth.
  130. * @param rhythm
  131. */
  132. public addRhythmAtBegin(rhythm: RhythmInstruction): void {
  133. throw new Error("not implemented");
  134. }
  135. /**
  136. * Add the given clef to the end of the measure.
  137. * This has to update/increase EndInstructionsWidth.
  138. * @param clef
  139. */
  140. public addClefAtEnd(clef: ClefInstruction): void {
  141. throw new Error("not implemented");
  142. }
  143. /**
  144. * Set the x-position relative to the staffline (y-Position is always 0 relative to the staffline).
  145. * @param xPos
  146. */
  147. public setPositionInStaffline(xPos: number): void {
  148. this.PositionAndShape.RelativePosition = new PointF2D(xPos, 0);
  149. }
  150. /**
  151. * Set the overall x-width of the measure.
  152. * @param width
  153. */
  154. public setWidth(width: number): void {
  155. this.PositionAndShape.BorderRight = width;
  156. }
  157. /**
  158. * This method is called after the StaffEntriesScaleFactor has been set.
  159. * Here the final x-positions of the staff entries have to be set.
  160. * (multiply the minimal positions with the scaling factor, considering the BeginInstructionsWidth).
  161. */
  162. public layoutSymbols(): void {
  163. throw new Error("not implemented");
  164. }
  165. public findGraphicalStaffEntryFromTimestamp(relativeTimestamp: Fraction): GraphicalStaffEntry {
  166. for (let idx: number = 0, len: number = this.staffEntries.length; idx < len; ++idx) {
  167. const graphicalStaffEntry: GraphicalStaffEntry = this.staffEntries[idx];
  168. if (graphicalStaffEntry.relInMeasureTimestamp.Equals(relativeTimestamp)) {
  169. return graphicalStaffEntry;
  170. }
  171. }
  172. return undefined;
  173. }
  174. /**
  175. * Iterate from start to end and find the [[GraphicalStaffEntry]] with the same absolute timestamp.
  176. * @param absoluteTimestamp
  177. * @returns {any}
  178. */
  179. public findGraphicalStaffEntryFromVerticalContainerTimestamp(absoluteTimestamp: Fraction): GraphicalStaffEntry {
  180. for (let idx: number = 0, len: number = this.staffEntries.length; idx < len; ++idx) {
  181. const graphicalStaffEntry: GraphicalStaffEntry = this.staffEntries[idx];
  182. if (graphicalStaffEntry.sourceStaffEntry.VerticalContainerParent.getAbsoluteTimestamp().Equals(absoluteTimestamp)) {
  183. return graphicalStaffEntry;
  184. }
  185. }
  186. return undefined;
  187. }
  188. /**
  189. * Check if the all the [[GraphicalMeasure]]'s [[StaffEntry]]s (their minimum Length) have the same duration with the [[SourceMeasure]].
  190. * @returns {boolean}
  191. */
  192. public hasSameDurationWithSourceMeasureParent(): boolean {
  193. const duration: Fraction = new Fraction(0, 1);
  194. for (let idx: number = 0, len: number = this.staffEntries.length; idx < len; ++idx) {
  195. const graphicalStaffEntry: GraphicalStaffEntry = this.staffEntries[idx];
  196. duration.Add(graphicalStaffEntry.findStaffEntryMinNoteLength());
  197. }
  198. return duration.Equals(this.parentSourceMeasure.Duration);
  199. }
  200. /**
  201. * Check a whole [[Measure]] for the presence of multiple Voices (used for Stem direction).
  202. * @returns {boolean}
  203. */
  204. public hasMultipleVoices(): boolean {
  205. if (this.staffEntries.length === 0) {
  206. return false;
  207. }
  208. const voices: Voice[] = [];
  209. for (let idx: number = 0, len: number = this.staffEntries.length; idx < len; ++idx) {
  210. const staffEntry: GraphicalStaffEntry = this.staffEntries[idx];
  211. for (let idx2: number = 0, len2: number = staffEntry.sourceStaffEntry.VoiceEntries.length; idx2 < len2; ++idx2) {
  212. const voiceEntry: VoiceEntry = staffEntry.sourceStaffEntry.VoiceEntries[idx2];
  213. if (voices.indexOf(voiceEntry.ParentVoice) < 0) {
  214. voices.push(voiceEntry.ParentVoice);
  215. }
  216. }
  217. }
  218. if (voices.length > 1) {
  219. return true;
  220. }
  221. return false;
  222. }
  223. public isVisible(): boolean {
  224. return this.ParentStaff.ParentInstrument.Visible;
  225. }
  226. public getGraphicalMeasureDurationFromStaffEntries(): Fraction {
  227. let duration: Fraction = new Fraction(0, 1);
  228. const voices: Voice[] = [];
  229. for (let idx: number = 0, len: number = this.staffEntries.length; idx < len; ++idx) {
  230. const graphicalStaffEntry: GraphicalStaffEntry = this.staffEntries[idx];
  231. for (let idx2: number = 0, len2: number = graphicalStaffEntry.sourceStaffEntry.VoiceEntries.length; idx2 < len2; ++idx2) {
  232. const voiceEntry: VoiceEntry = graphicalStaffEntry.sourceStaffEntry.VoiceEntries[idx2];
  233. if (voices.indexOf(voiceEntry.ParentVoice) < 0) {
  234. voices.push(voiceEntry.ParentVoice);
  235. }
  236. }
  237. }
  238. for (let idx: number = 0, len: number = voices.length; idx < len; ++idx) {
  239. const voice: Voice = voices[idx];
  240. const voiceDuration: Fraction = new Fraction(0, 1);
  241. for (const graphicalStaffEntry of this.staffEntries) {
  242. for (const gve of graphicalStaffEntry.graphicalVoiceEntries) {
  243. if (gve.parentVoiceEntry.ParentVoice === voice && gve.notes.length > 0) {
  244. voiceDuration.Add(gve.notes[0].graphicalNoteLength);
  245. }
  246. }
  247. }
  248. if (duration.lt(voiceDuration)) {
  249. duration = Fraction.createFromFraction(voiceDuration);
  250. }
  251. }
  252. return duration;
  253. }
  254. public addGraphicalStaffEntry(graphicalStaffEntry: GraphicalStaffEntry): void {
  255. this.staffEntries.push(graphicalStaffEntry);
  256. }
  257. /**
  258. * Add a [[StaffEntry]] (along with its [[BoundingBox]]) to the current Measure.
  259. * @param staffEntry
  260. */
  261. public addGraphicalStaffEntryAtTimestamp(staffEntry: GraphicalStaffEntry): void {
  262. if (staffEntry) {
  263. if (this.staffEntries.length === 0 || this.staffEntries[this.staffEntries.length - 1].relInMeasureTimestamp.lt(staffEntry.relInMeasureTimestamp)) {
  264. this.staffEntries.push(staffEntry);
  265. } else {
  266. for (let i: number = this.staffEntries.length - 1; i >= 0; i--) {
  267. if (this.staffEntries[i].relInMeasureTimestamp.lt(staffEntry.relInMeasureTimestamp)) {
  268. this.staffEntries.splice(i + 1, 0, staffEntry);
  269. break;
  270. }
  271. if (i === 0) {
  272. this.staffEntries.splice(i, 0, staffEntry);
  273. }
  274. }
  275. }
  276. }
  277. }
  278. public beginsWithLineRepetition(): boolean {
  279. const sourceMeasure: SourceMeasure = this.parentSourceMeasure;
  280. if (!sourceMeasure) {
  281. return false;
  282. }
  283. return sourceMeasure.beginsWithLineRepetition();
  284. }
  285. /**
  286. * Check if this Measure is a Repetition Ending.
  287. * @returns {boolean}
  288. */
  289. public endsWithLineRepetition(): boolean {
  290. const sourceMeasure: SourceMeasure = this.parentSourceMeasure;
  291. if (!sourceMeasure) {
  292. return false;
  293. }
  294. return sourceMeasure.endsWithLineRepetition();
  295. }
  296. /**
  297. * Check if a Repetition starts at the next Measure.
  298. * @returns {boolean}
  299. */
  300. public beginsWithWordRepetition(): boolean {
  301. const sourceMeasure: SourceMeasure = this.parentSourceMeasure;
  302. if (!sourceMeasure) {
  303. return false;
  304. }
  305. return sourceMeasure.beginsWithWordRepetition();
  306. }
  307. /**
  308. * Check if this Measure is a Repetition Ending.
  309. */
  310. public endsWithWordRepetition(): boolean {
  311. const sourceMeasure: SourceMeasure = this.parentSourceMeasure;
  312. if (!sourceMeasure) {
  313. return false;
  314. }
  315. return sourceMeasure.endsWithWordRepetition();
  316. }
  317. }