StaffMeasure.ts 13 KB

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