SourceMeasure.ts 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511
  1. import {Fraction} from "../../Common/DataObjects/Fraction";
  2. import {VerticalSourceStaffEntryContainer} from "./VerticalSourceStaffEntryContainer";
  3. import {SourceStaffEntry} from "./SourceStaffEntry";
  4. import {RepetitionInstruction, RepetitionInstructionEnum} from "./Instructions/RepetitionInstruction";
  5. import {Staff} from "./Staff";
  6. import {VoiceEntry} from "./VoiceEntry";
  7. import {Voice} from "./Voice";
  8. import {MusicSheet} from "../MusicSheet";
  9. import {MultiExpression} from "./Expressions/MultiExpression";
  10. import {MultiTempoExpression} from "./Expressions/MultiTempoExpression";
  11. import {KeyInstruction} from "./Instructions/KeyInstruction";
  12. import {AbstractNotationInstruction} from "./Instructions/AbstractNotationInstruction";
  13. import {Repetition} from "../MusicSource/Repetition";
  14. import {BaseIdClass} from "../../Util/BaseIdClass";
  15. /**
  16. * The Source Measure represents the source data of a unique measure, including all instruments with their staves.
  17. * There exists one source measure per XML measure or per paper sheet measure (e.g. the source measures are not doubled in repetitions)
  18. */
  19. export class SourceMeasure extends BaseIdClass {
  20. /**
  21. * The data entries and data lists will be filled with null values according to the total number of staves,
  22. * so that existing objects can be referred to by staff index.
  23. * @param completeNumberOfStaves
  24. */
  25. constructor(completeNumberOfStaves: number) {
  26. super();
  27. this.completeNumberOfStaves = completeNumberOfStaves;
  28. this.implicitMeasure = false;
  29. this.breakSystemAfter = false;
  30. this.endsPiece = false;
  31. this.firstInstructionsStaffEntries = new Array(completeNumberOfStaves);
  32. this.lastInstructionsStaffEntries = new Array(completeNumberOfStaves);
  33. for (let i: number = 0; i < completeNumberOfStaves; i++) {
  34. this.staffMeasureErrors.push(false);
  35. this.staffLinkedExpressions.push([]);
  36. }
  37. }
  38. /**
  39. * The unique measure list index starting with 0.
  40. */
  41. public measureListIndex: number;
  42. /**
  43. * The measure number for showing on the music sheet. Typically starts with 1.
  44. */
  45. public endsPiece: boolean;
  46. private measureNumber: number;
  47. private absoluteTimestamp: Fraction;
  48. private completeNumberOfStaves: number;
  49. private duration: Fraction;
  50. private staffLinkedExpressions: MultiExpression[][] = [];
  51. private tempoExpressions: MultiTempoExpression[] = [];
  52. private verticalSourceStaffEntryContainers: VerticalSourceStaffEntryContainer[] = [];
  53. private implicitMeasure: boolean;
  54. private breakSystemAfter: boolean;
  55. private staffMeasureErrors: boolean[] = [];
  56. private firstInstructionsStaffEntries: SourceStaffEntry[];
  57. private lastInstructionsStaffEntries: SourceStaffEntry[];
  58. private firstRepetitionInstructions: RepetitionInstruction[] = [];
  59. private lastRepetitionInstructions: RepetitionInstruction[] = [];
  60. public get MeasureNumber(): number {
  61. return this.measureNumber;
  62. }
  63. public set MeasureNumber(value: number) {
  64. this.measureNumber = value;
  65. }
  66. public get AbsoluteTimestamp(): Fraction {
  67. return this.absoluteTimestamp;
  68. }
  69. public set AbsoluteTimestamp(value: Fraction) {
  70. this.absoluteTimestamp = value;
  71. }
  72. public get CompleteNumberOfStaves(): number {
  73. return this.completeNumberOfStaves;
  74. }
  75. public get Duration(): Fraction {
  76. return this.duration;
  77. }
  78. public set Duration(value: Fraction) {
  79. this.duration = value;
  80. }
  81. public get ImplicitMeasure(): boolean {
  82. return this.implicitMeasure;
  83. }
  84. public set ImplicitMeasure(value: boolean) {
  85. this.implicitMeasure = value;
  86. }
  87. public get BreakSystemAfter(): boolean {
  88. return this.breakSystemAfter;
  89. }
  90. public set BreakSystemAfter(value: boolean) {
  91. this.breakSystemAfter = value;
  92. }
  93. public get StaffLinkedExpressions(): MultiExpression[][] {
  94. return this.staffLinkedExpressions;
  95. }
  96. public get TempoExpressions(): MultiTempoExpression[] {
  97. return this.tempoExpressions;
  98. }
  99. public get VerticalSourceStaffEntryContainers(): VerticalSourceStaffEntryContainer[] {
  100. return this.verticalSourceStaffEntryContainers;
  101. }
  102. public get FirstInstructionsStaffEntries(): SourceStaffEntry[] {
  103. return this.firstInstructionsStaffEntries;
  104. }
  105. public get LastInstructionsStaffEntries(): SourceStaffEntry[] {
  106. return this.lastInstructionsStaffEntries;
  107. }
  108. public get FirstRepetitionInstructions(): RepetitionInstruction[] {
  109. return this.firstRepetitionInstructions;
  110. }
  111. public get LastRepetitionInstructions(): RepetitionInstruction[] {
  112. return this.lastRepetitionInstructions;
  113. }
  114. public getErrorInMeasure(staffIndex: number): boolean {
  115. return this.staffMeasureErrors[staffIndex];
  116. }
  117. public setErrorInStaffMeasure(staffIndex: number, hasError: boolean): void {
  118. this.staffMeasureErrors[staffIndex] = hasError;
  119. }
  120. public getNextMeasure(measures: SourceMeasure[]): SourceMeasure {
  121. return measures[this.measureListIndex + 1];
  122. }
  123. public getPreviousMeasure(measures: SourceMeasure[]): SourceMeasure {
  124. if (this.measureListIndex > 1) {
  125. return measures[this.measureListIndex - 1];
  126. }
  127. return undefined;
  128. }
  129. /**
  130. * Check at the given timestamp if a VerticalContainer exists, if not creates a new, timestamp-ordered one,
  131. * and at the given index, if a [[SourceStaffEntry]] exists, and if not, creates a new one.
  132. * @param inMeasureTimestamp
  133. * @param inSourceMeasureStaffIndex
  134. * @param staff
  135. * @returns {{createdNewContainer: boolean, staffEntry: SourceStaffEntry}}
  136. */
  137. public findOrCreateStaffEntry(inMeasureTimestamp: Fraction, inSourceMeasureStaffIndex: number,
  138. staff: Staff): {createdNewContainer: boolean, staffEntry: SourceStaffEntry} {
  139. let staffEntry: SourceStaffEntry = undefined;
  140. // Find:
  141. let existingVerticalSourceStaffEntryContainer: VerticalSourceStaffEntryContainer;
  142. for (const container of this.verticalSourceStaffEntryContainers) {
  143. if (container.Timestamp.Equals(inMeasureTimestamp)) {
  144. existingVerticalSourceStaffEntryContainer = container;
  145. break;
  146. }
  147. }
  148. if (existingVerticalSourceStaffEntryContainer !== undefined) {
  149. if (existingVerticalSourceStaffEntryContainer.StaffEntries[inSourceMeasureStaffIndex] !== undefined) {
  150. staffEntry = existingVerticalSourceStaffEntryContainer.StaffEntries[inSourceMeasureStaffIndex];
  151. } else {
  152. staffEntry = new SourceStaffEntry(existingVerticalSourceStaffEntryContainer, staff);
  153. existingVerticalSourceStaffEntryContainer.StaffEntries[inSourceMeasureStaffIndex] = staffEntry;
  154. }
  155. return {createdNewContainer: false, staffEntry: staffEntry};
  156. }
  157. const last: VerticalSourceStaffEntryContainer = this.verticalSourceStaffEntryContainers[this.verticalSourceStaffEntryContainers.length - 1];
  158. if (this.verticalSourceStaffEntryContainers.length === 0 || last.Timestamp.lt(inMeasureTimestamp)) {
  159. const container: VerticalSourceStaffEntryContainer = new VerticalSourceStaffEntryContainer(
  160. this, inMeasureTimestamp.clone(), this.completeNumberOfStaves
  161. );
  162. this.verticalSourceStaffEntryContainers.push(container);
  163. staffEntry = new SourceStaffEntry(container, staff);
  164. container.StaffEntries[inSourceMeasureStaffIndex] = staffEntry;
  165. } else {
  166. for (
  167. let i: number = this.verticalSourceStaffEntryContainers.length - 1;
  168. i >= 0; i--
  169. ) {
  170. if (this.verticalSourceStaffEntryContainers[i].Timestamp.lt(inMeasureTimestamp)) {
  171. const container: VerticalSourceStaffEntryContainer = new VerticalSourceStaffEntryContainer(
  172. this, inMeasureTimestamp.clone(), this.completeNumberOfStaves
  173. );
  174. this.verticalSourceStaffEntryContainers.splice(i + 1, 0, container);
  175. staffEntry = new SourceStaffEntry(container, staff);
  176. container.StaffEntries[inSourceMeasureStaffIndex] = staffEntry;
  177. break;
  178. }
  179. if (i === 0) {
  180. const container: VerticalSourceStaffEntryContainer = new VerticalSourceStaffEntryContainer(
  181. this, inMeasureTimestamp.clone(), this.completeNumberOfStaves
  182. );
  183. this.verticalSourceStaffEntryContainers.splice(i, 0, container);
  184. staffEntry = new SourceStaffEntry(container, staff);
  185. container.StaffEntries[inSourceMeasureStaffIndex] = staffEntry;
  186. break;
  187. }
  188. }
  189. }
  190. //Logging.debug("created new container: ", staffEntry, this.verticalSourceStaffEntryContainers);
  191. return {createdNewContainer: true, staffEntry: staffEntry};
  192. }
  193. /**
  194. * Check if a VerticalContainer, a staffEntry and a voiceEntry exist at the given timestamp.
  195. * If not, create the necessary entries.
  196. * @param sse
  197. * @param voice
  198. * @returns {{createdVoiceEntry: boolean, voiceEntry: VoiceEntry}}
  199. */
  200. public findOrCreateVoiceEntry(sse: SourceStaffEntry, voice: Voice): { createdVoiceEntry: boolean, voiceEntry: VoiceEntry } {
  201. let ve: VoiceEntry = undefined;
  202. let createdNewVoiceEntry: boolean = false;
  203. for (const voiceEntry of sse.VoiceEntries) {
  204. if (voiceEntry.ParentVoice === voice) {
  205. ve = voiceEntry;
  206. break;
  207. }
  208. }
  209. if (ve === undefined) {
  210. ve = new VoiceEntry(sse.Timestamp, voice, sse);
  211. sse.VoiceEntries.push(ve);
  212. createdNewVoiceEntry = true;
  213. }
  214. return {createdVoiceEntry: createdNewVoiceEntry, voiceEntry: ve};
  215. }
  216. /**
  217. * Search for a non-null [[SourceStaffEntry]] at the given verticalIndex,
  218. * starting from the given horizontalIndex and moving backwards. If none is found, then return undefined.
  219. * @param verticalIndex
  220. * @param horizontalIndex
  221. * @returns {any}
  222. */
  223. public getPreviousSourceStaffEntryFromIndex(verticalIndex: number, horizontalIndex: number): SourceStaffEntry {
  224. for (let i: number = horizontalIndex - 1; i >= 0; i--) {
  225. if (this.verticalSourceStaffEntryContainers[i][verticalIndex] !== undefined) {
  226. return this.verticalSourceStaffEntryContainers[i][verticalIndex];
  227. }
  228. }
  229. return undefined;
  230. }
  231. /**
  232. * Return the index of the existing VerticalContainer at the given timestamp.
  233. * @param musicTimestamp
  234. * @returns {number}
  235. */
  236. public getVerticalContainerIndexByTimestamp(musicTimestamp: Fraction): number {
  237. for (let idx: number = 0, len: number = this.VerticalSourceStaffEntryContainers.length; idx < len; ++idx) {
  238. if (this.VerticalSourceStaffEntryContainers[idx].Timestamp.Equals(musicTimestamp)) {
  239. return idx; // this.verticalSourceStaffEntryContainers.indexOf(verticalSourceStaffEntryContainer);
  240. }
  241. }
  242. return -1;
  243. }
  244. /**
  245. * Return the existing VerticalContainer at the given timestamp.
  246. * @param musicTimestamp
  247. * @returns {any}
  248. */
  249. public getVerticalContainerByTimestamp(musicTimestamp: Fraction): VerticalSourceStaffEntryContainer {
  250. for (let idx: number = 0, len: number = this.VerticalSourceStaffEntryContainers.length; idx < len; ++idx) {
  251. const verticalSourceStaffEntryContainer: VerticalSourceStaffEntryContainer = this.VerticalSourceStaffEntryContainers[idx];
  252. if (verticalSourceStaffEntryContainer.Timestamp.Equals(musicTimestamp)) {
  253. return verticalSourceStaffEntryContainer;
  254. }
  255. }
  256. return undefined;
  257. }
  258. /**
  259. * Check the [[SourceMeasure]] for a possible VerticalContainer with all of its [[StaffEntry]]s undefined,
  260. * and if found, remove the VerticalContainer from the [[SourceMeasure]].
  261. * @param index
  262. */
  263. public checkForEmptyVerticalContainer(index: number): void {
  264. let undefinedCounter: number = 0;
  265. for (let i: number = 0; i < this.completeNumberOfStaves; i++) {
  266. if (this.verticalSourceStaffEntryContainers[index][i] === undefined) {
  267. undefinedCounter++;
  268. }
  269. }
  270. if (undefinedCounter === this.completeNumberOfStaves) {
  271. this.verticalSourceStaffEntryContainers.splice(index, 1);
  272. }
  273. }
  274. /**
  275. * This method is used for handling a measure with the following error (in the procedure of finding out the Instrument's Duration):
  276. * If the LastStaffEntry is missing (implied restNote or error), then go back the StaffEntries until you find a TiedNote (tie Start),
  277. * which gives the correct MeasureDuration.
  278. * @param musicSheet
  279. * @param maxInstDuration
  280. * @returns {Fraction}
  281. */
  282. public reverseCheck(musicSheet: MusicSheet, maxInstDuration: Fraction): Fraction {
  283. let maxDuration: Fraction = new Fraction(0, 1);
  284. const instrumentsDurations: Fraction[] = [];
  285. for (let i: number = 0; i < musicSheet.Instruments.length; i++) {
  286. let instrumentDuration: Fraction = new Fraction(0, 1);
  287. const inSourceMeasureInstrumentIndex: number = musicSheet.getGlobalStaffIndexOfFirstStaff(musicSheet.Instruments[i]);
  288. for (let j: number = 0; j < musicSheet.Instruments[i].Staves.length; j++) {
  289. const lastStaffEntry: SourceStaffEntry = this.getLastSourceStaffEntryForInstrument(inSourceMeasureInstrumentIndex + j);
  290. if (lastStaffEntry !== undefined && !lastStaffEntry.hasTie()) {
  291. const verticalContainerIndex: number = this.verticalSourceStaffEntryContainers.indexOf(lastStaffEntry.VerticalContainerParent);
  292. for (let m: number = verticalContainerIndex - 1; m >= 0; m--) {
  293. const previousStaffEntry: SourceStaffEntry = this.verticalSourceStaffEntryContainers[m][inSourceMeasureInstrumentIndex + j];
  294. if (previousStaffEntry !== undefined && previousStaffEntry.hasTie()) {
  295. if (instrumentDuration.lt(Fraction.plus(previousStaffEntry.Timestamp, previousStaffEntry.calculateMaxNoteLength()))) {
  296. instrumentDuration = Fraction.plus(previousStaffEntry.Timestamp, previousStaffEntry.calculateMaxNoteLength());
  297. break;
  298. }
  299. }
  300. }
  301. }
  302. }
  303. instrumentsDurations.push(instrumentDuration);
  304. }
  305. for (let idx: number = 0, len: number = instrumentsDurations.length; idx < len; ++idx) {
  306. const instrumentsDuration: Fraction = instrumentsDurations[idx];
  307. if (maxDuration.lt(instrumentsDuration)) {
  308. maxDuration = instrumentsDuration;
  309. }
  310. }
  311. return Fraction.max(maxDuration, maxInstDuration);
  312. }
  313. /**
  314. * Calculate all the [[Instrument]]'s NotesDurations for this Measures.
  315. * @param musicSheet
  316. * @param instrumentMaxTieNoteFractions
  317. * @returns {Fraction[]}
  318. */
  319. public calculateInstrumentsDuration(musicSheet: MusicSheet, instrumentMaxTieNoteFractions: Fraction[]): Fraction[] {
  320. const instrumentsDurations: Fraction[] = [];
  321. for (let i: number = 0; i < musicSheet.Instruments.length; i++) {
  322. let instrumentDuration: Fraction = new Fraction(0, 1);
  323. const inSourceMeasureInstrumentIndex: number = musicSheet.getGlobalStaffIndexOfFirstStaff(musicSheet.Instruments[i]);
  324. for (let j: number = 0; j < musicSheet.Instruments[i].Staves.length; j++) {
  325. const lastStaffEntry: SourceStaffEntry = this.getLastSourceStaffEntryForInstrument(inSourceMeasureInstrumentIndex + j);
  326. if (lastStaffEntry !== undefined && lastStaffEntry.Timestamp !== undefined) {
  327. if (instrumentDuration.lt(Fraction.plus(lastStaffEntry.Timestamp, lastStaffEntry.calculateMaxNoteLength()))) {
  328. instrumentDuration = Fraction.plus(lastStaffEntry.Timestamp, lastStaffEntry.calculateMaxNoteLength());
  329. }
  330. }
  331. }
  332. if (instrumentDuration.lt(instrumentMaxTieNoteFractions[i])) {
  333. instrumentDuration = instrumentMaxTieNoteFractions[i];
  334. }
  335. instrumentsDurations.push(instrumentDuration);
  336. }
  337. return instrumentsDurations;
  338. }
  339. public getEntriesPerStaff(staffIndex: number): SourceStaffEntry[] {
  340. const sourceStaffEntries: SourceStaffEntry[] = [];
  341. for (const container of this.VerticalSourceStaffEntryContainers) {
  342. const sse: SourceStaffEntry = container.StaffEntries[staffIndex];
  343. if (sse !== undefined) {
  344. sourceStaffEntries.push(sse);
  345. }
  346. }
  347. return sourceStaffEntries;
  348. }
  349. /**
  350. *
  351. * @returns {boolean} true iff some measure begin instructions have been found for at least one staff
  352. */
  353. public hasBeginInstructions(): boolean {
  354. for (let staffIndex: number = 0, len: number = this.FirstInstructionsStaffEntries.length; staffIndex < len; staffIndex++) {
  355. const beginInstructionsStaffEntry: SourceStaffEntry = this.FirstInstructionsStaffEntries[staffIndex];
  356. if (beginInstructionsStaffEntry !== undefined && beginInstructionsStaffEntry.Instructions.length > 0) {
  357. return true;
  358. }
  359. }
  360. return false;
  361. }
  362. public beginsWithLineRepetition(): boolean {
  363. for (let idx: number = 0, len: number = this.FirstRepetitionInstructions.length; idx < len; ++idx) {
  364. const instr: RepetitionInstruction = this.FirstRepetitionInstructions[idx];
  365. if (instr.type === RepetitionInstructionEnum.StartLine) {
  366. return true;
  367. }
  368. if (instr.parentRepetition !== undefined && instr === instr.parentRepetition.startMarker && !instr.parentRepetition.FromWords) {
  369. return true;
  370. }
  371. }
  372. return false;
  373. }
  374. /**
  375. * Check if this measure is a Repetition Ending.
  376. * @returns {boolean}
  377. */
  378. public endsWithLineRepetition(): boolean {
  379. for (let idx: number = 0, len: number = this.LastRepetitionInstructions.length; idx < len; ++idx) {
  380. const instruction: RepetitionInstruction = this.LastRepetitionInstructions[idx];
  381. if (instruction.type === RepetitionInstructionEnum.BackJumpLine) {
  382. return true;
  383. }
  384. const rep: Repetition = instruction.parentRepetition;
  385. if (rep === undefined) {
  386. continue;
  387. }
  388. if (rep.FromWords) {
  389. continue;
  390. }
  391. for (let idx2: number = 0, len2: number = rep.BackwardJumpInstructions.length; idx2 < len2; ++idx2) {
  392. const backJumpInstruction: RepetitionInstruction = rep.BackwardJumpInstructions[idx2];
  393. if (instruction === backJumpInstruction) {
  394. return true;
  395. }
  396. }
  397. }
  398. return false;
  399. }
  400. /**
  401. * Check if a Repetition starts at the next Measure.
  402. * @returns {boolean}
  403. */
  404. public beginsWithWordRepetition(): boolean {
  405. for (let idx: number = 0, len: number = this.FirstRepetitionInstructions.length; idx < len; ++idx) {
  406. const instruction: RepetitionInstruction = this.FirstRepetitionInstructions[idx];
  407. if (instruction.parentRepetition !== undefined &&
  408. instruction === instruction.parentRepetition.startMarker && instruction.parentRepetition.FromWords) {
  409. return true;
  410. }
  411. }
  412. return false;
  413. }
  414. /**
  415. * Check if this Measure is a Repetition Ending.
  416. * @returns {boolean}
  417. */
  418. public endsWithWordRepetition(): boolean {
  419. for (let idx: number = 0, len: number = this.LastRepetitionInstructions.length; idx < len; ++idx) {
  420. const instruction: RepetitionInstruction = this.LastRepetitionInstructions[idx];
  421. const rep: Repetition = instruction.parentRepetition;
  422. if (rep === undefined) {
  423. continue;
  424. }
  425. if (!rep.FromWords) {
  426. continue;
  427. }
  428. for (let idx2: number = 0, len2: number = rep.BackwardJumpInstructions.length; idx2 < len2; ++idx2) {
  429. const backJumpInstruction: RepetitionInstruction = rep.BackwardJumpInstructions[idx2];
  430. if (instruction === backJumpInstruction) {
  431. return true;
  432. }
  433. }
  434. if (instruction === rep.forwardJumpInstruction) {
  435. return true;
  436. }
  437. }
  438. return false;
  439. }
  440. public getKeyInstruction(staffIndex: number): KeyInstruction {
  441. if (this.FirstInstructionsStaffEntries[staffIndex] !== undefined) {
  442. const sourceStaffEntry: SourceStaffEntry = this.FirstInstructionsStaffEntries[staffIndex];
  443. for (let idx: number = 0, len: number = sourceStaffEntry.Instructions.length; idx < len; ++idx) {
  444. const abstractNotationInstruction: AbstractNotationInstruction = sourceStaffEntry.Instructions[idx];
  445. if (abstractNotationInstruction instanceof KeyInstruction) {
  446. return <KeyInstruction>abstractNotationInstruction;
  447. }
  448. }
  449. }
  450. return undefined;
  451. }
  452. /**
  453. * Return the first non-null [[SourceStaffEntry]] at the given InstrumentIndex.
  454. * @param instrumentIndex
  455. * @returns {SourceStaffEntry}
  456. */
  457. private getLastSourceStaffEntryForInstrument(instrumentIndex: number): SourceStaffEntry {
  458. let entry: SourceStaffEntry;
  459. for (let i: number = this.verticalSourceStaffEntryContainers.length - 1; i >= 0; i--) {
  460. entry = this.verticalSourceStaffEntryContainers[i].StaffEntries[instrumentIndex];
  461. if (entry) {
  462. break;
  463. }
  464. }
  465. return entry;
  466. }
  467. }