MusicSheetReader.ts 47 KB

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