MusicSheetReader.ts 46 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905
  1. import {MusicSheet} from "../MusicSheet";
  2. import {SourceMeasure} from "../VoiceData/SourceMeasure";
  3. import {Fraction} from "../../Common/DataObjects/Fraction";
  4. import {InstrumentReader} from "./InstrumentReader";
  5. import {IXmlElement} from "../../Common/FileIO/Xml";
  6. import {Instrument} from "../Instrument";
  7. import {ITextTranslation} from "../Interfaces/ITextTranslation";
  8. import {MusicSheetReadingException} from "../Exceptions";
  9. import * as log from "loglevel";
  10. import {IXmlAttribute} from "../../Common/FileIO/Xml";
  11. import {RhythmInstruction} from "../VoiceData/Instructions/RhythmInstruction";
  12. import {RhythmSymbolEnum} from "../VoiceData/Instructions/RhythmInstruction";
  13. import {SourceStaffEntry} from "../VoiceData/SourceStaffEntry";
  14. import {VoiceEntry} from "../VoiceData/VoiceEntry";
  15. import {InstrumentalGroup} from "../InstrumentalGroup";
  16. import {SubInstrument} from "../SubInstrument";
  17. import {MidiInstrument} from "../VoiceData/Instructions/ClefInstruction";
  18. import {AbstractNotationInstruction} from "../VoiceData/Instructions/AbstractNotationInstruction";
  19. import {Label} from "../Label";
  20. import {MusicSymbolModuleFactory} from "./MusicSymbolModuleFactory";
  21. import {IAfterSheetReadingModule} from "../Interfaces/IAfterSheetReadingModule";
  22. import {RepetitionInstructionReader} from "./MusicSymbolModules/RepetitionInstructionReader";
  23. import {RepetitionCalculator} from "./MusicSymbolModules/RepetitionCalculator";
  24. export class MusicSheetReader /*implements IMusicSheetReader*/ {
  25. constructor(afterSheetReadingModules: IAfterSheetReadingModule[] = undefined) {
  26. if (afterSheetReadingModules === undefined) {
  27. this.afterSheetReadingModules = [];
  28. } else {
  29. this.afterSheetReadingModules = afterSheetReadingModules;
  30. }
  31. this.repetitionInstructionReader = MusicSymbolModuleFactory.createRepetitionInstructionReader();
  32. this.repetitionCalculator = MusicSymbolModuleFactory.createRepetitionCalculator();
  33. }
  34. private repetitionInstructionReader: RepetitionInstructionReader;
  35. private repetitionCalculator: RepetitionCalculator;
  36. private afterSheetReadingModules: IAfterSheetReadingModule[];
  37. private musicSheet: MusicSheet;
  38. private completeNumberOfStaves: number = 0;
  39. private currentMeasure: SourceMeasure;
  40. private previousMeasure: SourceMeasure;
  41. private currentFraction: Fraction;
  42. public get CompleteNumberOfStaves(): number {
  43. return this.completeNumberOfStaves;
  44. }
  45. private static doCalculationsAfterDurationHasBeenSet(instrumentReaders: InstrumentReader[]): void {
  46. for (const instrumentReader of instrumentReaders) {
  47. instrumentReader.doCalculationsAfterDurationHasBeenSet();
  48. }
  49. }
  50. /**
  51. * Read a music XML file and saves the values in the MusicSheet class.
  52. * @param root
  53. * @param path
  54. * @returns {MusicSheet}
  55. */
  56. public createMusicSheet(root: IXmlElement, path: string): MusicSheet {
  57. try {
  58. return this._createMusicSheet(root, path);
  59. } catch (e) {
  60. log.info("MusicSheetReader.CreateMusicSheet", e);
  61. return undefined;
  62. }
  63. }
  64. private _removeFromArray(list: any[], elem: any): void {
  65. const i: number = list.indexOf(elem);
  66. if (i !== -1) {
  67. list.splice(i, 1);
  68. }
  69. }
  70. // Trim from a string also newlines
  71. private trimString(str: string): string {
  72. return str.replace(/^\s+|\s+$/g, "");
  73. }
  74. private _lastElement<T>(list: T[]): T {
  75. return list[list.length - 1];
  76. }
  77. //public SetPhonicScoreInterface(phonicScoreInterface: IPhonicScoreInterface): void {
  78. // this.phonicScoreInterface = phonicScoreInterface;
  79. //}
  80. //public ReadMusicSheetParameters(sheetObject: MusicSheetParameterObject, root: IXmlElement, path: string): MusicSheetParameterObject {
  81. // this.musicSheet = new MusicSheet();
  82. // if (root !== undefined) {
  83. // this.pushSheetLabels(root, path);
  84. // if (this.musicSheet.Title !== undefined) {
  85. // sheetObject.Title = this.musicSheet.Title.text;
  86. // }
  87. // if (this.musicSheet.Composer !== undefined) {
  88. // sheetObject.Composer = this.musicSheet.Composer.text;
  89. // }
  90. // if (this.musicSheet.Lyricist !== undefined) {
  91. // sheetObject.Lyricist = this.musicSheet.Lyricist.text;
  92. // }
  93. // let partlistNode: IXmlElement = root.element("part-list");
  94. // let partList: IXmlElement[] = partlistNode.elements();
  95. // this.createInstrumentGroups(partList);
  96. // for (let idx: number = 0, len: number = this.musicSheet.Instruments.length; idx < len; ++idx) {
  97. // let instr: Instrument = this.musicSheet.Instruments[idx];
  98. // sheetObject.InstrumentList.push(__init(new MusicSheetParameterObject.LibrarySheetInstrument(), { name: instr.name }));
  99. // }
  100. // }
  101. // return sheetObject;
  102. //}
  103. private _createMusicSheet(root: IXmlElement, path: string): MusicSheet {
  104. const instrumentReaders: InstrumentReader[] = [];
  105. let sourceMeasureCounter: number = 0;
  106. this.musicSheet = new MusicSheet();
  107. this.musicSheet.Path = path;
  108. if (root === undefined) {
  109. throw new MusicSheetReadingException("Undefined root element");
  110. }
  111. this.pushSheetLabels(root, path);
  112. const partlistNode: IXmlElement = root.element("part-list");
  113. if (partlistNode === undefined) {
  114. throw new MusicSheetReadingException("Undefined partListNode");
  115. }
  116. const partInst: IXmlElement[] = root.elements("part");
  117. const partList: IXmlElement[] = partlistNode.elements();
  118. this.initializeReading(partList, partInst, instrumentReaders);
  119. let couldReadMeasure: boolean = true;
  120. this.currentFraction = new Fraction(0, 1);
  121. let guitarPro: boolean = false;
  122. let encoding: IXmlElement = root.element("identification");
  123. if (encoding !== undefined) {
  124. encoding = encoding.element("encoding");
  125. }
  126. if (encoding !== undefined) {
  127. encoding = encoding.element("software");
  128. }
  129. if (encoding !== undefined && encoding.value === "Guitar Pro 5") {
  130. guitarPro = true;
  131. }
  132. while (couldReadMeasure) {
  133. if (this.currentMeasure !== undefined && this.currentMeasure.endsPiece) {
  134. sourceMeasureCounter = 0;
  135. }
  136. this.currentMeasure = new SourceMeasure(this.completeNumberOfStaves);
  137. for (const instrumentReader of instrumentReaders) {
  138. try {
  139. couldReadMeasure = couldReadMeasure && instrumentReader.readNextXmlMeasure(this.currentMeasure, this.currentFraction, guitarPro);
  140. } catch (e) {
  141. const errorMsg: string = ITextTranslation.translateText("ReaderErrorMessages/InstrumentError", "Error while reading instruments.");
  142. throw new MusicSheetReadingException(errorMsg, e);
  143. }
  144. }
  145. if (couldReadMeasure) {
  146. this.musicSheet.addMeasure(this.currentMeasure);
  147. this.checkIfRhythmInstructionsAreSetAndEqual(instrumentReaders);
  148. this.checkSourceMeasureForNullEntries();
  149. sourceMeasureCounter = this.setSourceMeasureDuration(instrumentReaders, sourceMeasureCounter);
  150. MusicSheetReader.doCalculationsAfterDurationHasBeenSet(instrumentReaders);
  151. this.currentMeasure.AbsoluteTimestamp = this.currentFraction.clone();
  152. this.musicSheet.SheetErrors.finalizeMeasure(this.currentMeasure.MeasureNumber);
  153. this.currentFraction.Add(this.currentMeasure.Duration);
  154. this.previousMeasure = this.currentMeasure;
  155. }
  156. }
  157. if (this.repetitionInstructionReader !== undefined) {
  158. this.repetitionInstructionReader.removeRedundantInstructions();
  159. if (this.repetitionCalculator !== undefined) {
  160. this.repetitionCalculator.calculateRepetitions(this.musicSheet, this.repetitionInstructionReader.repetitionInstructions);
  161. }
  162. }
  163. this.musicSheet.checkForInstrumentWithNoVoice();
  164. this.musicSheet.fillStaffList();
  165. //this.musicSheet.DefaultStartTempoInBpm = this.musicSheet.SheetPlaybackSetting.BeatsPerMinute;
  166. for (let idx: number = 0, len: number = this.afterSheetReadingModules.length; idx < len; ++idx) {
  167. const afterSheetReadingModule: IAfterSheetReadingModule = this.afterSheetReadingModules[idx];
  168. afterSheetReadingModule.calculate(this.musicSheet);
  169. }
  170. this.musicSheet.DefaultStartTempoInBpm = this.musicSheet.SourceMeasures[0].TempoInBPM;
  171. this.musicSheet.userStartTempoInBPM = this.musicSheet.userStartTempoInBPM || this.musicSheet.DefaultStartTempoInBpm;
  172. return this.musicSheet;
  173. }
  174. private initializeReading(partList: IXmlElement[], partInst: IXmlElement[], instrumentReaders: InstrumentReader[]): void {
  175. const instrumentDict: { [_: string]: Instrument; } = this.createInstrumentGroups(partList);
  176. this.completeNumberOfStaves = this.getCompleteNumberOfStavesFromXml(partInst);
  177. if (partInst.length !== 0) {
  178. this.repetitionInstructionReader.MusicSheet = this.musicSheet;
  179. this.currentFraction = new Fraction(0, 1);
  180. this.currentMeasure = undefined;
  181. this.previousMeasure = undefined;
  182. }
  183. let counter: number = 0;
  184. for (const node of partInst) {
  185. const idNode: IXmlAttribute = node.attribute("id");
  186. if (idNode) {
  187. const currentInstrument: Instrument = instrumentDict[idNode.value];
  188. const xmlMeasureList: IXmlElement[] = node.elements("measure");
  189. let instrumentNumberOfStaves: number = 1;
  190. try {
  191. instrumentNumberOfStaves = this.getInstrumentNumberOfStavesFromXml(node);
  192. } catch (err) {
  193. const errorMsg: string = ITextTranslation.translateText(
  194. "ReaderErrorMessages/InstrumentStavesNumberError",
  195. "Invalid number of staves at instrument: "
  196. );
  197. this.musicSheet.SheetErrors.push(errorMsg + currentInstrument.Name);
  198. continue;
  199. }
  200. currentInstrument.createStaves(instrumentNumberOfStaves);
  201. instrumentReaders.push(new InstrumentReader(this.repetitionInstructionReader, xmlMeasureList, currentInstrument));
  202. if (this.repetitionInstructionReader !== undefined) {
  203. this.repetitionInstructionReader.xmlMeasureList[counter] = xmlMeasureList;
  204. }
  205. counter++;
  206. }
  207. }
  208. }
  209. /**
  210. * Check if all (should there be any apart from the first Measure) [[RhythmInstruction]]s in the [[SourceMeasure]] are the same.
  211. *
  212. * If not, then the max [[RhythmInstruction]] (Fraction) is set to all staves.
  213. * Also, if it happens to have the same [[RhythmInstruction]]s in RealValue but given in Symbol AND Fraction, then the Fraction prevails.
  214. * @param instrumentReaders
  215. */
  216. private checkIfRhythmInstructionsAreSetAndEqual(instrumentReaders: InstrumentReader[]): void {
  217. const rhythmInstructions: RhythmInstruction[] = [];
  218. for (let i: number = 0; i < this.completeNumberOfStaves; i++) {
  219. if (this.currentMeasure.FirstInstructionsStaffEntries[i] !== undefined) {
  220. const last: AbstractNotationInstruction = this.currentMeasure.FirstInstructionsStaffEntries[i].Instructions[
  221. this.currentMeasure.FirstInstructionsStaffEntries[i].Instructions.length - 1
  222. ];
  223. if (last instanceof RhythmInstruction) {
  224. rhythmInstructions.push(<RhythmInstruction>last);
  225. }
  226. }
  227. }
  228. let maxRhythmValue: number = 0.0;
  229. let index: number = -1;
  230. for (let idx: number = 0, len: number = rhythmInstructions.length; idx < len; ++idx) {
  231. const rhythmInstruction: RhythmInstruction = rhythmInstructions[idx];
  232. if (rhythmInstruction.Rhythm.RealValue > maxRhythmValue) {
  233. if (this.areRhythmInstructionsMixed(rhythmInstructions) && rhythmInstruction.SymbolEnum !== RhythmSymbolEnum.NONE) {
  234. continue;
  235. }
  236. maxRhythmValue = rhythmInstruction.Rhythm.RealValue;
  237. index = rhythmInstructions.indexOf(rhythmInstruction);
  238. }
  239. }
  240. if (rhythmInstructions.length > 0 && rhythmInstructions.length < this.completeNumberOfStaves) {
  241. const rhythmInstruction: RhythmInstruction = rhythmInstructions[index].clone();
  242. for (let i: number = 0; i < this.completeNumberOfStaves; i++) {
  243. if (
  244. this.currentMeasure.FirstInstructionsStaffEntries[i] !== undefined &&
  245. !(this._lastElement(this.currentMeasure.FirstInstructionsStaffEntries[i].Instructions) instanceof RhythmInstruction)
  246. ) {
  247. this.currentMeasure.FirstInstructionsStaffEntries[i].removeAllInstructionsOfTypeRhythmInstruction();
  248. this.currentMeasure.FirstInstructionsStaffEntries[i].Instructions.push(rhythmInstruction.clone());
  249. }
  250. if (this.currentMeasure.FirstInstructionsStaffEntries[i] === undefined) {
  251. this.currentMeasure.FirstInstructionsStaffEntries[i] = new SourceStaffEntry(undefined, undefined);
  252. this.currentMeasure.FirstInstructionsStaffEntries[i].Instructions.push(rhythmInstruction.clone());
  253. }
  254. }
  255. for (let idx: number = 0, len: number = instrumentReaders.length; idx < len; ++idx) {
  256. const instrumentReader: InstrumentReader = instrumentReaders[idx];
  257. instrumentReader.ActiveRhythm = rhythmInstruction;
  258. }
  259. }
  260. if (rhythmInstructions.length === 0 && this.currentMeasure === this.musicSheet.SourceMeasures[0]) {
  261. const rhythmInstruction: RhythmInstruction = new RhythmInstruction(new Fraction(4, 4, 0, false), RhythmSymbolEnum.NONE);
  262. for (let i: number = 0; i < this.completeNumberOfStaves; i++) {
  263. if (this.currentMeasure.FirstInstructionsStaffEntries[i] === undefined) {
  264. this.currentMeasure.FirstInstructionsStaffEntries[i] = new SourceStaffEntry(undefined, undefined);
  265. } else {
  266. this.currentMeasure.FirstInstructionsStaffEntries[i].removeAllInstructionsOfTypeRhythmInstruction();
  267. }
  268. this.currentMeasure.FirstInstructionsStaffEntries[i].Instructions.push(rhythmInstruction);
  269. }
  270. for (let idx: number = 0, len: number = instrumentReaders.length; idx < len; ++idx) {
  271. const instrumentReader: InstrumentReader = instrumentReaders[idx];
  272. instrumentReader.ActiveRhythm = rhythmInstruction;
  273. }
  274. }
  275. for (let idx: number = 0, len: number = rhythmInstructions.length; idx < len; ++idx) {
  276. const rhythmInstruction: RhythmInstruction = rhythmInstructions[idx];
  277. if (rhythmInstruction.Rhythm.RealValue < maxRhythmValue) {
  278. if (this._lastElement(
  279. this.currentMeasure.FirstInstructionsStaffEntries[rhythmInstructions.indexOf(rhythmInstruction)].Instructions
  280. ) instanceof RhythmInstruction) {
  281. // TODO Test correctness
  282. const instrs: AbstractNotationInstruction[] =
  283. this.currentMeasure.FirstInstructionsStaffEntries[rhythmInstructions.indexOf(rhythmInstruction)].Instructions;
  284. instrs[instrs.length - 1] = rhythmInstructions[index].clone();
  285. }
  286. }
  287. if (
  288. Math.abs(rhythmInstruction.Rhythm.RealValue - maxRhythmValue) < 0.000001 &&
  289. rhythmInstruction.SymbolEnum !== RhythmSymbolEnum.NONE &&
  290. this.areRhythmInstructionsMixed(rhythmInstructions)
  291. ) {
  292. rhythmInstruction.SymbolEnum = RhythmSymbolEnum.NONE;
  293. }
  294. }
  295. }
  296. /**
  297. * True in case of 4/4 and COMMON TIME (or 2/2 and CUT TIME)
  298. * @param rhythmInstructions
  299. * @returns {boolean}
  300. */
  301. private areRhythmInstructionsMixed(rhythmInstructions: RhythmInstruction[]): boolean {
  302. for (let i: number = 1; i < rhythmInstructions.length; i++) {
  303. if (
  304. Math.abs(rhythmInstructions[i].Rhythm.RealValue - rhythmInstructions[0].Rhythm.RealValue) < 0.000001 &&
  305. rhythmInstructions[i].SymbolEnum !== rhythmInstructions[0].SymbolEnum
  306. ) {
  307. return true;
  308. }
  309. }
  310. return false;
  311. }
  312. /**
  313. * Set the [[Measure]]'s duration taking into account the longest [[Instrument]] duration and the active Rhythm read from XML.
  314. * @param instrumentReaders
  315. * @param sourceMeasureCounter
  316. * @returns {number}
  317. */
  318. private setSourceMeasureDuration(instrumentReaders: InstrumentReader[], sourceMeasureCounter: number): number {
  319. let activeRhythm: Fraction = new Fraction(0, 1);
  320. const instrumentsMaxTieNoteFractions: Fraction[] = [];
  321. for (const instrumentReader of instrumentReaders) {
  322. instrumentsMaxTieNoteFractions.push(instrumentReader.MaxTieNoteFraction);
  323. const activeRythmMeasure: Fraction = instrumentReader.ActiveRhythm.Rhythm;
  324. if (activeRhythm.lt(activeRythmMeasure)) {
  325. activeRhythm = new Fraction(activeRythmMeasure.Numerator, activeRythmMeasure.Denominator, 0, false);
  326. }
  327. }
  328. const instrumentsDurations: Fraction[] = this.currentMeasure.calculateInstrumentsDuration(this.musicSheet, instrumentsMaxTieNoteFractions);
  329. let maxInstrumentDuration: Fraction = new Fraction(0, 1);
  330. for (const instrumentsDuration of instrumentsDurations) {
  331. if (maxInstrumentDuration.lt(instrumentsDuration)) {
  332. maxInstrumentDuration = instrumentsDuration;
  333. }
  334. }
  335. if (Fraction.Equal(maxInstrumentDuration, activeRhythm)) {
  336. this.checkFractionsForEquivalence(maxInstrumentDuration, activeRhythm);
  337. } else {
  338. if (maxInstrumentDuration.lt(activeRhythm)) {
  339. maxInstrumentDuration = this.currentMeasure.reverseCheck(this.musicSheet, maxInstrumentDuration);
  340. this.checkFractionsForEquivalence(maxInstrumentDuration, activeRhythm);
  341. }
  342. }
  343. this.currentMeasure.ImplicitMeasure = this.checkIfMeasureIsImplicit(maxInstrumentDuration, activeRhythm);
  344. if (!this.currentMeasure.ImplicitMeasure) {
  345. sourceMeasureCounter++;
  346. }
  347. this.currentMeasure.Duration = maxInstrumentDuration;
  348. this.currentMeasure.MeasureNumber = sourceMeasureCounter;
  349. for (let i: number = 0; i < instrumentsDurations.length; i++) {
  350. const instrumentsDuration: Fraction = instrumentsDurations[i];
  351. if (
  352. (this.currentMeasure.ImplicitMeasure && instrumentsDuration !== maxInstrumentDuration) ||
  353. !Fraction.Equal(instrumentsDuration, activeRhythm) &&
  354. !this.allInstrumentsHaveSameDuration(instrumentsDurations, maxInstrumentDuration)
  355. ) {
  356. const firstStaffIndexOfInstrument: number = this.musicSheet.getGlobalStaffIndexOfFirstStaff(this.musicSheet.Instruments[i]);
  357. for (let staffIndex: number = 0; staffIndex < this.musicSheet.Instruments[i].Staves.length; staffIndex++) {
  358. if (!this.graphicalMeasureIsEmpty(firstStaffIndexOfInstrument + staffIndex)) {
  359. this.currentMeasure.setErrorInGraphicalMeasure(firstStaffIndexOfInstrument + staffIndex, true);
  360. const errorMsg: string = ITextTranslation.translateText("ReaderErrorMessages/MissingNotesError",
  361. "Given Notes don't correspond to measure duration.");
  362. this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
  363. }
  364. }
  365. }
  366. }
  367. return sourceMeasureCounter;
  368. }
  369. /**
  370. * Check the Fractions for Equivalence and if so, sets maxInstrumentDuration's members accordingly.
  371. * *
  372. * Example: if maxInstrumentDuration = 1/1 and sourceMeasureDuration = 4/4, maxInstrumentDuration becomes 4/4.
  373. * @param maxInstrumentDuration
  374. * @param activeRhythm
  375. */
  376. private checkFractionsForEquivalence(maxInstrumentDuration: Fraction, activeRhythm: Fraction): void {
  377. if (activeRhythm.Denominator > maxInstrumentDuration.Denominator) {
  378. const factor: number = activeRhythm.Denominator / maxInstrumentDuration.Denominator;
  379. maxInstrumentDuration.expand(factor);
  380. }
  381. }
  382. /**
  383. * Handle the case of an implicit [[SourceMeasure]].
  384. * @param maxInstrumentDuration
  385. * @param activeRhythm
  386. * @returns {boolean}
  387. */
  388. private checkIfMeasureIsImplicit(maxInstrumentDuration: Fraction, activeRhythm: Fraction): boolean {
  389. if (this.previousMeasure === undefined && maxInstrumentDuration.lt(activeRhythm)) {
  390. return true;
  391. }
  392. if (this.previousMeasure !== undefined) {
  393. return Fraction.plus(this.previousMeasure.Duration, maxInstrumentDuration).Equals(activeRhythm);
  394. }
  395. return false;
  396. }
  397. /**
  398. * Check the Duration of all the given Instruments.
  399. * @param instrumentsDurations
  400. * @param maxInstrumentDuration
  401. * @returns {boolean}
  402. */
  403. private allInstrumentsHaveSameDuration(instrumentsDurations: Fraction[], maxInstrumentDuration: Fraction): boolean {
  404. let counter: number = 0;
  405. for (let idx: number = 0, len: number = instrumentsDurations.length; idx < len; ++idx) {
  406. const instrumentsDuration: Fraction = instrumentsDurations[idx];
  407. if (instrumentsDuration.Equals(maxInstrumentDuration)) {
  408. counter++;
  409. }
  410. }
  411. return (counter === instrumentsDurations.length && maxInstrumentDuration !== new Fraction(0, 1));
  412. }
  413. private graphicalMeasureIsEmpty(index: number): boolean {
  414. let counter: number = 0;
  415. for (let i: number = 0; i < this.currentMeasure.VerticalSourceStaffEntryContainers.length; i++) {
  416. if (this.currentMeasure.VerticalSourceStaffEntryContainers[i].StaffEntries[index] === undefined) {
  417. counter++;
  418. }
  419. }
  420. return (counter === this.currentMeasure.VerticalSourceStaffEntryContainers.length);
  421. }
  422. /**
  423. * Check a [[SourceMeasure]] for possible empty / undefined entries ([[VoiceEntry]], [[SourceStaffEntry]], VerticalContainer)
  424. * (caused from TieAlgorithm removing EndTieNote) and removes them if completely empty / null
  425. */
  426. private checkSourceMeasureForNullEntries(): void {
  427. for (let i: number = this.currentMeasure.VerticalSourceStaffEntryContainers.length - 1; i >= 0; i--) {
  428. for (let j: number = this.currentMeasure.VerticalSourceStaffEntryContainers[i].StaffEntries.length - 1; j >= 0; j--) {
  429. const sourceStaffEntry: SourceStaffEntry = this.currentMeasure.VerticalSourceStaffEntryContainers[i].StaffEntries[j];
  430. if (sourceStaffEntry !== undefined) {
  431. for (let k: number = sourceStaffEntry.VoiceEntries.length - 1; k >= 0; k--) {
  432. const voiceEntry: VoiceEntry = sourceStaffEntry.VoiceEntries[k];
  433. if (voiceEntry.Notes.length === 0) {
  434. this._removeFromArray(voiceEntry.ParentVoice.VoiceEntries, voiceEntry);
  435. this._removeFromArray(sourceStaffEntry.VoiceEntries, voiceEntry);
  436. }
  437. }
  438. }
  439. if (sourceStaffEntry !== undefined && sourceStaffEntry.VoiceEntries.length === 0) {
  440. this.currentMeasure.VerticalSourceStaffEntryContainers[i].StaffEntries[j] = undefined;
  441. }
  442. }
  443. }
  444. for (let i: number = this.currentMeasure.VerticalSourceStaffEntryContainers.length - 1; i >= 0; i--) {
  445. let counter: number = 0;
  446. for (let idx: number = 0, len: number = this.currentMeasure.VerticalSourceStaffEntryContainers[i].StaffEntries.length; idx < len; ++idx) {
  447. const sourceStaffEntry: SourceStaffEntry = this.currentMeasure.VerticalSourceStaffEntryContainers[i].StaffEntries[idx];
  448. if (sourceStaffEntry === undefined) {
  449. counter++;
  450. }
  451. }
  452. if (counter === this.currentMeasure.VerticalSourceStaffEntryContainers[i].StaffEntries.length) {
  453. this._removeFromArray(this.currentMeasure.VerticalSourceStaffEntryContainers, this.currentMeasure.VerticalSourceStaffEntryContainers[i]);
  454. }
  455. }
  456. }
  457. /**
  458. * Read the XML file and creates the main sheet Labels.
  459. * @param root
  460. * @param filePath
  461. */
  462. private pushSheetLabels(root: IXmlElement, filePath: string): void {
  463. this.readComposer(root);
  464. this.readTitle(root);
  465. try {
  466. if (this.musicSheet.Title === undefined || this.musicSheet.Composer === undefined) {
  467. this.readTitleAndComposerFromCredits(root); // this can also throw an error
  468. }
  469. } catch (ex) {
  470. log.info("MusicSheetReader.pushSheetLabels", "readTitleAndComposerFromCredits", ex);
  471. }
  472. try {
  473. if (this.musicSheet.Title === undefined) {
  474. const barI: number = Math.max(
  475. 0, filePath.lastIndexOf("/"), filePath.lastIndexOf("\\")
  476. );
  477. const filename: string = filePath.substr(barI);
  478. const filenameSplits: string[] = filename.split(".", 1);
  479. this.musicSheet.Title = new Label(filenameSplits[0]);
  480. }
  481. } catch (ex) {
  482. log.info("MusicSheetReader.pushSheetLabels", "read title from file name", ex);
  483. }
  484. }
  485. // Checks whether _elem_ has an attribute with value _val_.
  486. private presentAttrsWithValue(elem: IXmlElement, val: string): boolean {
  487. for (const attr of elem.attributes()) {
  488. if (attr.value === val) {
  489. return true;
  490. }
  491. }
  492. return false;
  493. }
  494. private readComposer(root: IXmlElement): void {
  495. const identificationNode: IXmlElement = root.element("identification");
  496. if (identificationNode !== undefined) {
  497. const creators: IXmlElement[] = identificationNode.elements("creator");
  498. for (let idx: number = 0, len: number = creators.length; idx < len; ++idx) {
  499. const creator: IXmlElement = creators[idx];
  500. if (creator.hasAttributes) {
  501. if (this.presentAttrsWithValue(creator, "composer")) {
  502. this.musicSheet.Composer = new Label(this.trimString(creator.value));
  503. continue;
  504. }
  505. if (this.presentAttrsWithValue(creator, "lyricist") || this.presentAttrsWithValue(creator, "poet")) {
  506. this.musicSheet.Lyricist = new Label(this.trimString(creator.value));
  507. }
  508. }
  509. }
  510. }
  511. }
  512. private readTitleAndComposerFromCredits(root: IXmlElement): void {
  513. const systemYCoordinates: number = this.computeSystemYCoordinates(root);
  514. if (systemYCoordinates === 0) {
  515. return;
  516. }
  517. let largestTitleCreditSize: number = 1;
  518. let finalTitle: string = undefined;
  519. let largestCreditYInfo: number = 0;
  520. let finalSubtitle: string = undefined;
  521. let possibleTitle: string = undefined;
  522. const creditElements: IXmlElement[] = root.elements("credit");
  523. for (let idx: number = 0, len: number = creditElements.length; idx < len; ++idx) {
  524. const credit: IXmlElement = creditElements[idx];
  525. if (!credit.attribute("page")) {
  526. return;
  527. }
  528. if (credit.attribute("page").value === "1") {
  529. let creditChild: IXmlElement = undefined;
  530. if (credit !== undefined) {
  531. creditChild = credit.element("credit-words");
  532. if (!creditChild.attribute("justify")) {
  533. break;
  534. }
  535. const creditJustify: string = creditChild.attribute("justify").value;
  536. const creditY: string = creditChild.attribute("default-y").value;
  537. const creditYInfo: number = parseFloat(creditY);
  538. if (creditYInfo > systemYCoordinates) {
  539. if (this.musicSheet.Title === undefined) {
  540. const creditSize: string = creditChild.attribute("font-size").value;
  541. const titleCreditSizeInt: number = parseFloat(creditSize);
  542. if (largestTitleCreditSize < titleCreditSizeInt) {
  543. largestTitleCreditSize = titleCreditSizeInt;
  544. finalTitle = creditChild.value;
  545. }
  546. }
  547. if (this.musicSheet.Subtitle === undefined) {
  548. if (creditJustify !== "right" && creditJustify !== "left") {
  549. if (largestCreditYInfo < creditYInfo) {
  550. largestCreditYInfo = creditYInfo;
  551. if (possibleTitle) {
  552. finalSubtitle = possibleTitle;
  553. possibleTitle = creditChild.value;
  554. } else {
  555. possibleTitle = creditChild.value;
  556. }
  557. }
  558. }
  559. }
  560. if (!(this.musicSheet.Composer !== undefined && this.musicSheet.Lyricist !== undefined)) {
  561. switch (creditJustify) {
  562. case "right":
  563. this.musicSheet.Composer = new Label(this.trimString(creditChild.value));
  564. break;
  565. case "left":
  566. this.musicSheet.Lyricist = new Label(this.trimString(creditChild.value));
  567. break;
  568. default:
  569. break;
  570. }
  571. }
  572. }
  573. }
  574. }
  575. }
  576. if (this.musicSheet.Title === undefined && finalTitle) {
  577. this.musicSheet.Title = new Label(this.trimString(finalTitle));
  578. }
  579. if (this.musicSheet.Subtitle === undefined && finalSubtitle) {
  580. this.musicSheet.Subtitle = new Label(this.trimString(finalSubtitle));
  581. }
  582. }
  583. private computeSystemYCoordinates(root: IXmlElement): number {
  584. if (root.element("defaults") === undefined) {
  585. return 0;
  586. }
  587. let paperHeight: number = 0;
  588. let topSystemDistance: number = 0;
  589. try {
  590. const defi: string = root.element("defaults").element("page-layout").element("page-height").value;
  591. paperHeight = parseFloat(defi);
  592. } catch (e) {
  593. log.info("MusicSheetReader.computeSystemYCoordinates(): couldn't find page height, not reading title/composer.");
  594. return 0;
  595. }
  596. let found: boolean = false;
  597. const parts: IXmlElement[] = root.elements("part");
  598. for (let idx: number = 0, len: number = parts.length; idx < len; ++idx) {
  599. const measures: IXmlElement[] = parts[idx].elements("measure");
  600. for (let idx2: number = 0, len2: number = measures.length; idx2 < len2; ++idx2) {
  601. const measure: IXmlElement = measures[idx2];
  602. if (measure.element("print") !== undefined) {
  603. const systemLayouts: IXmlElement[] = measure.element("print").elements("system-layout");
  604. for (let idx3: number = 0, len3: number = systemLayouts.length; idx3 < len3; ++idx3) {
  605. const syslab: IXmlElement = systemLayouts[idx3];
  606. if (syslab.element("top-system-distance") !== undefined) {
  607. const topSystemDistanceString: string = syslab.element("top-system-distance").value;
  608. topSystemDistance = parseFloat(topSystemDistanceString);
  609. found = true;
  610. break;
  611. }
  612. }
  613. break;
  614. }
  615. }
  616. if (found) {
  617. break;
  618. }
  619. }
  620. if (root.element("defaults").element("system-layout") !== undefined) {
  621. const syslay: IXmlElement = root.element("defaults").element("system-layout");
  622. if (syslay.element("top-system-distance") !== undefined) {
  623. const topSystemDistanceString: string = root.element("defaults").element("system-layout").element("top-system-distance").value;
  624. topSystemDistance = parseFloat(topSystemDistanceString);
  625. }
  626. }
  627. if (topSystemDistance === 0) {
  628. return 0;
  629. }
  630. return paperHeight - topSystemDistance;
  631. }
  632. private readTitle(root: IXmlElement): void {
  633. const titleNode: IXmlElement = root.element("work");
  634. let titleNodeChild: IXmlElement = undefined;
  635. if (titleNode !== undefined) {
  636. titleNodeChild = titleNode.element("work-title");
  637. if (titleNodeChild !== undefined && titleNodeChild.value) {
  638. this.musicSheet.Title = new Label(this.trimString(titleNodeChild.value));
  639. }
  640. }
  641. const movementNode: IXmlElement = root.element("movement-title");
  642. let finalSubTitle: string = "";
  643. if (movementNode !== undefined) {
  644. if (this.musicSheet.Title === undefined) {
  645. this.musicSheet.Title = new Label(this.trimString(movementNode.value));
  646. } else {
  647. finalSubTitle = this.trimString(movementNode.value);
  648. }
  649. }
  650. if (titleNode !== undefined) {
  651. const subtitleNodeChild: IXmlElement = titleNode.element("work-number");
  652. if (subtitleNodeChild !== undefined) {
  653. const workNumber: string = subtitleNodeChild.value;
  654. if (workNumber) {
  655. if (finalSubTitle) {
  656. finalSubTitle = workNumber;
  657. } else {
  658. finalSubTitle = finalSubTitle + ", " + workNumber;
  659. }
  660. }
  661. }
  662. }
  663. if (finalSubTitle
  664. ) {
  665. this.musicSheet.Subtitle = new Label(finalSubTitle);
  666. }
  667. }
  668. /**
  669. * Build the [[InstrumentalGroup]]s and [[Instrument]]s.
  670. * @param entryList
  671. * @returns {{}}
  672. */
  673. private createInstrumentGroups(entryList: IXmlElement[]): { [_: string]: Instrument; } {
  674. let instrumentId: number = 0;
  675. const instrumentDict: { [_: string]: Instrument; } = {};
  676. let currentGroup: InstrumentalGroup;
  677. try {
  678. const entryArray: IXmlElement[] = entryList;
  679. for (let idx: number = 0, len: number = entryArray.length; idx < len; ++idx) {
  680. const node: IXmlElement = entryArray[idx];
  681. if (node.name === "score-part") {
  682. const instrIdString: string = node.attribute("id").value;
  683. const instrument: Instrument = new Instrument(instrumentId, instrIdString, this.musicSheet, currentGroup);
  684. instrumentId++;
  685. const partElements: IXmlElement[] = node.elements();
  686. for (let idx2: number = 0, len2: number = partElements.length; idx2 < len2; ++idx2) {
  687. const partElement: IXmlElement = partElements[idx2];
  688. try {
  689. if (partElement.name === "part-name") {
  690. instrument.Name = partElement.value;
  691. } else if (partElement.name === "part-abbreviation") {
  692. instrument.PartAbbreviation = partElement.value;
  693. } else if (partElement.name === "score-instrument") {
  694. const subInstrument: SubInstrument = new SubInstrument(instrument);
  695. subInstrument.idString = partElement.firstAttribute.value;
  696. instrument.SubInstruments.push(subInstrument);
  697. const subElement: IXmlElement = partElement.element("instrument-name");
  698. if (subElement !== undefined) {
  699. subInstrument.name = subElement.value;
  700. subInstrument.setMidiInstrument(subElement.value);
  701. }
  702. } else if (partElement.name === "midi-instrument") {
  703. let subInstrument: SubInstrument = instrument.getSubInstrument(partElement.firstAttribute.value);
  704. for (let idx3: number = 0, len3: number = instrument.SubInstruments.length; idx3 < len3; ++idx3) {
  705. const subInstr: SubInstrument = instrument.SubInstruments[idx3];
  706. if (subInstr.idString === partElement.value) {
  707. subInstrument = subInstr;
  708. break;
  709. }
  710. }
  711. const instrumentElements: IXmlElement[] = partElement.elements();
  712. for (let idx3: number = 0, len3: number = instrumentElements.length; idx3 < len3; ++idx3) {
  713. const instrumentElement: IXmlElement = instrumentElements[idx3];
  714. try {
  715. if (instrumentElement.name === "midi-channel") {
  716. if (parseInt(instrumentElement.value, 10) === 10) {
  717. instrument.MidiInstrumentId = MidiInstrument.Percussion;
  718. }
  719. } else if (instrumentElement.name === "midi-program") {
  720. if (instrument.SubInstruments.length > 0 && instrument.MidiInstrumentId !== MidiInstrument.Percussion) {
  721. subInstrument.midiInstrumentID = <MidiInstrument>Math.max(0, parseInt(instrumentElement.value, 10) - 1);
  722. }
  723. } else if (instrumentElement.name === "midi-unpitched") {
  724. subInstrument.fixedKey = Math.max(0, parseInt(instrumentElement.value, 10));
  725. } else if (instrumentElement.name === "volume") {
  726. try {
  727. const result: number = parseFloat(instrumentElement.value);
  728. subInstrument.volume = result / 127.0;
  729. } catch (ex) {
  730. log.debug("ExpressionReader.readExpressionParameters", "read volume", ex);
  731. }
  732. } else if (instrumentElement.name === "pan") {
  733. try {
  734. const result: number = parseFloat(instrumentElement.value);
  735. subInstrument.pan = result / 64.0;
  736. } catch (ex) {
  737. log.debug("ExpressionReader.readExpressionParameters", "read pan", ex);
  738. }
  739. }
  740. } catch (ex) {
  741. log.info("MusicSheetReader.createInstrumentGroups midi settings: ", ex);
  742. }
  743. }
  744. }
  745. } catch (ex) {
  746. log.info("MusicSheetReader.createInstrumentGroups: ", ex);
  747. }
  748. }
  749. if (instrument.SubInstruments.length === 0) {
  750. const subInstrument: SubInstrument = new SubInstrument(instrument);
  751. instrument.SubInstruments.push(subInstrument);
  752. }
  753. instrumentDict[instrIdString] = instrument;
  754. if (currentGroup !== undefined) {
  755. currentGroup.InstrumentalGroups.push(instrument);
  756. this.musicSheet.Instruments.push(instrument);
  757. } else {
  758. this.musicSheet.InstrumentalGroups.push(instrument);
  759. this.musicSheet.Instruments.push(instrument);
  760. }
  761. } else {
  762. if ((node.name === "part-group") && (node.attribute("type").value === "start")) {
  763. const iG: InstrumentalGroup = new InstrumentalGroup("group", this.musicSheet, currentGroup);
  764. if (currentGroup !== undefined) {
  765. currentGroup.InstrumentalGroups.push(iG);
  766. } else {
  767. this.musicSheet.InstrumentalGroups.push(iG);
  768. }
  769. currentGroup = iG;
  770. } else {
  771. if ((node.name === "part-group") && (node.attribute("type").value === "stop")) {
  772. if (currentGroup !== undefined) {
  773. if (currentGroup.InstrumentalGroups.length === 1) {
  774. const instr: InstrumentalGroup = currentGroup.InstrumentalGroups[0];
  775. if (currentGroup.Parent !== undefined) {
  776. currentGroup.Parent.InstrumentalGroups.push(instr);
  777. this._removeFromArray(currentGroup.Parent.InstrumentalGroups, currentGroup);
  778. } else {
  779. this.musicSheet.InstrumentalGroups.push(instr);
  780. this._removeFromArray(this.musicSheet.InstrumentalGroups, currentGroup);
  781. }
  782. }
  783. currentGroup = currentGroup.Parent;
  784. }
  785. }
  786. }
  787. }
  788. }
  789. } catch (e) {
  790. const errorMsg: string = ITextTranslation.translateText(
  791. "ReaderErrorMessages/InstrumentError", "Error while reading Instruments"
  792. );
  793. throw new MusicSheetReadingException(errorMsg, e);
  794. }
  795. for (let idx: number = 0, len: number = this.musicSheet.Instruments.length; idx < len; ++idx) {
  796. const instrument: Instrument = this.musicSheet.Instruments[idx];
  797. if (!instrument.Name) {
  798. instrument.Name = "Instr. " + instrument.IdString;
  799. }
  800. }
  801. return instrumentDict;
  802. }
  803. /**
  804. * Read from each xmlInstrumentPart the first xmlMeasure in order to find out the [[Instrument]]'s number of Staves
  805. * @param partInst
  806. * @returns {number} - Complete number of Staves for all Instruments.
  807. */
  808. private getCompleteNumberOfStavesFromXml(partInst: IXmlElement[]): number {
  809. let num: number = 0;
  810. for (const partNode of partInst) {
  811. const xmlMeasureList: IXmlElement[] = partNode.elements("measure");
  812. if (xmlMeasureList.length > 0) {
  813. const xmlMeasure: IXmlElement = xmlMeasureList[0];
  814. if (xmlMeasure !== undefined) {
  815. let stavesNode: IXmlElement = xmlMeasure.element("attributes");
  816. if (stavesNode !== undefined) {
  817. stavesNode = stavesNode.element("staves");
  818. }
  819. if (stavesNode === undefined) {
  820. num++;
  821. } else {
  822. num += parseInt(stavesNode.value, 10);
  823. }
  824. }
  825. }
  826. }
  827. if (isNaN(num) || num <= 0) {
  828. const errorMsg: string = ITextTranslation.translateText(
  829. "ReaderErrorMessages/StaffError", "Invalid number of staves."
  830. );
  831. throw new MusicSheetReadingException(errorMsg);
  832. }
  833. return num;
  834. }
  835. /**
  836. * Read from XML for a single [[Instrument]] the first xmlMeasure in order to find out the Instrument's number of Staves.
  837. * @param partNode
  838. * @returns {number}
  839. */
  840. private getInstrumentNumberOfStavesFromXml(partNode: IXmlElement): number {
  841. let num: number = 0;
  842. const xmlMeasure: IXmlElement = partNode.element("measure");
  843. if (xmlMeasure !== undefined) {
  844. const attributes: IXmlElement = xmlMeasure.element("attributes");
  845. let staves: IXmlElement = undefined;
  846. if (attributes !== undefined) {
  847. staves = attributes.element("staves");
  848. }
  849. if (attributes === undefined || staves === undefined) {
  850. num = 1;
  851. } else {
  852. num = parseInt(staves.value, 10);
  853. }
  854. }
  855. if (isNaN(num) || num <= 0) {
  856. const errorMsg: string = ITextTranslation.translateText(
  857. "ReaderErrorMessages/StaffError", "Invalid number of Staves."
  858. );
  859. throw new MusicSheetReadingException(errorMsg);
  860. }
  861. return num;
  862. }
  863. }