SourceMeasure.ts 27 KB

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