SourceMeasure.ts 24 KB

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