SourceMeasure.ts 24 KB

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