GraphicalMeasure.ts 13 KB

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