VoiceGenerator.ts 46 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867
  1. import {Instrument} from "../Instrument";
  2. import {LinkedVoice} from "../VoiceData/LinkedVoice";
  3. import {Voice} from "../VoiceData/Voice";
  4. import {MusicSheet} from "../MusicSheet";
  5. import {VoiceEntry} from "../VoiceData/VoiceEntry";
  6. import {Note} from "../VoiceData/Note";
  7. import {SourceMeasure} from "../VoiceData/SourceMeasure";
  8. import {SourceStaffEntry} from "../VoiceData/SourceStaffEntry";
  9. import {Beam} from "../VoiceData/Beam";
  10. import {Tie} from "../VoiceData/Tie";
  11. import {Tuplet} from "../VoiceData/Tuplet";
  12. import {Fraction} from "../../Common/DataObjects/fraction";
  13. //import {MusicSymbolModuleFactory} from "./InstrumentReader";
  14. import {IXmlElement} from "../../Common/FileIO/Xml";
  15. import {ITextTranslation} from "../Interfaces/ITextTranslation";
  16. import {ArticulationEnum} from "../VoiceData/VoiceEntry";
  17. import {Slur} from "../VoiceData/Expressions/ContinuousExpressions/Slur";
  18. import {LyricsEntry} from "../VoiceData/Lyrics/LyricsEntry";
  19. import {MusicSheetReadingException} from "../Exceptions";
  20. import {AccidentalEnum} from "../../Common/DataObjects/pitch";
  21. import {NoteEnum} from "../../Common/DataObjects/pitch";
  22. import {Staff} from "../VoiceData/Staff";
  23. import {StaffEntryLink} from "../VoiceData/StaffEntryLink";
  24. import {VerticalSourceStaffEntryContainer} from "../VoiceData/VerticalSourceStaffEntryContainer";
  25. import {Logging} from "../../Common/logging";
  26. import {Pitch} from "../../Common/DataObjects/pitch";
  27. import {IXmlAttribute} from "../../Common/FileIO/Xml";
  28. import {CollectionUtil} from "../../Util/collectionUtil";
  29. type SlurReader = any;
  30. export class VoiceGenerator {
  31. constructor(instrument: Instrument, voiceId: number, slurReader: SlurReader, mainVoice: Voice = undefined) {
  32. this.musicSheet = instrument.GetMusicSheet;
  33. this.slurReader = slurReader;
  34. if (mainVoice !== undefined) {
  35. this.voice = new LinkedVoice(instrument, voiceId, mainVoice);
  36. } else {
  37. this.voice = new Voice(instrument, voiceId);
  38. }
  39. instrument.Voices.push(this.voice);
  40. //this.lyricsReader = MusicSymbolModuleFactory.createLyricsReader(this.musicSheet);
  41. //this.articulationReader = MusicSymbolModuleFactory.createArticulationReader();
  42. }
  43. private slurReader: SlurReader;
  44. //private lyricsReader: LyricsReader;
  45. //private articulationReader: ArticulationReader;
  46. private musicSheet: MusicSheet;
  47. private voice: Voice;
  48. private currentVoiceEntry: VoiceEntry;
  49. private currentNote: Note;
  50. private currentMeasure: SourceMeasure;
  51. private currentStaffEntry: SourceStaffEntry;
  52. private lastBeamTag: string = "";
  53. private openBeam: Beam;
  54. private openGraceBeam: Beam;
  55. private openTieDict: { [_: number]: Tie; } = {};
  56. private currentOctaveShift: number = 0;
  57. private tupletDict: { [_: number]: Tuplet; } = {};
  58. private openTupletNumber: number = 0;
  59. public get GetVoice(): Voice {
  60. return this.voice;
  61. }
  62. public get OctaveShift(): number {
  63. return this.currentOctaveShift;
  64. }
  65. public set OctaveShift(value: number) {
  66. this.currentOctaveShift = value;
  67. }
  68. public createVoiceEntry(musicTimestamp: Fraction, parentStaffEntry: SourceStaffEntry, addToVoice: boolean): void {
  69. this.currentVoiceEntry = new VoiceEntry(musicTimestamp.clone(), this.voice, parentStaffEntry);
  70. if (addToVoice) {
  71. this.voice.VoiceEntries.push(this.currentVoiceEntry);
  72. }
  73. if (parentStaffEntry.VoiceEntries.indexOf(this.currentVoiceEntry) === -1) {
  74. parentStaffEntry.VoiceEntries.push(this.currentVoiceEntry);
  75. }
  76. }
  77. public read(
  78. noteNode: IXmlElement, noteDuration: number, divisions: number, restNote: boolean, graceNote: boolean,
  79. parentStaffEntry: SourceStaffEntry, parentMeasure: SourceMeasure,
  80. measureStartAbsoluteTimestamp: Fraction, maxTieNoteFraction: Fraction, chord: boolean, guitarPro: boolean
  81. ): Note {
  82. this.currentStaffEntry = parentStaffEntry;
  83. this.currentMeasure = parentMeasure;
  84. Logging.debug("read called:", restNote);
  85. try {
  86. this.currentNote = restNote
  87. ? this.addRestNote(noteDuration, divisions)
  88. : this.addSingleNote(noteNode, noteDuration, divisions, graceNote, chord, guitarPro);
  89. // (*)
  90. //if (this.lyricsReader !== undefined && noteNode.element("lyric") !== undefined) {
  91. // this.lyricsReader.addLyricEntry(noteNode, this.currentVoiceEntry);
  92. // this.voice.Parent.HasLyrics = true;
  93. //}
  94. let notationNode: IXmlElement = noteNode.element("notations");
  95. if (notationNode !== undefined) {
  96. // let articNode: IXmlElement = undefined;
  97. // (*)
  98. //if (this.articulationReader !== undefined) {
  99. // this.readArticulations(notationNode, this.currentVoiceEntry);
  100. //}
  101. //let slurNodes: IXmlElement[] = undefined;
  102. // (*)
  103. //if (this.slurReader !== undefined && (slurNodes = notationNode.elements("slur")))
  104. // this.slurReader.addSlur(slurNodes, this.currentNote);
  105. let tupletNodeList: IXmlElement[] = notationNode.elements("tuplet");
  106. if (tupletNodeList) {
  107. this.openTupletNumber = this.addTuplet(noteNode, tupletNodeList);
  108. }
  109. if (notationNode.element("arpeggiate") !== undefined && !graceNote) {
  110. this.currentVoiceEntry.ArpeggiosNotesIndices.push(this.currentVoiceEntry.Notes.indexOf(this.currentNote));
  111. }
  112. let tiedNodeList: IXmlElement[] = notationNode.elements("tied");
  113. if (tiedNodeList) {
  114. this.addTie(tiedNodeList, measureStartAbsoluteTimestamp, maxTieNoteFraction);
  115. }
  116. let openTieDict: { [_: number]: Tie; } = this.openTieDict;
  117. for (let key in openTieDict) {
  118. if (openTieDict.hasOwnProperty(key)) {
  119. let tie: Tie = openTieDict[key];
  120. if (Fraction.plus(tie.Start.ParentStaffEntry.Timestamp, tie.Start.Length).lt(this.currentStaffEntry.Timestamp)) {
  121. delete openTieDict[key];
  122. }
  123. }
  124. }
  125. }
  126. if (noteNode.element("time-modification") !== undefined && notationNode === undefined) {
  127. this.handleTimeModificationNode(noteNode);
  128. }
  129. } catch (err) {
  130. let errorMsg: string = ITextTranslation.translateText(
  131. "ReaderErrorMessages/NoteError", "Ignored erroneous Note."
  132. );
  133. this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
  134. }
  135. return this.currentNote;
  136. }
  137. public checkForOpenGraceNotes(): void {
  138. if (
  139. this.currentStaffEntry !== undefined
  140. && this.currentStaffEntry.VoiceEntries.length === 0
  141. && this.currentVoiceEntry.graceVoiceEntriesBefore !== undefined
  142. && this.currentVoiceEntry.graceVoiceEntriesBefore.length > 0
  143. ) {
  144. let voice: Voice = this.currentVoiceEntry.ParentVoice;
  145. let horizontalIndex: number = this.currentMeasure.VerticalSourceStaffEntryContainers.indexOf(this.currentStaffEntry.VerticalContainerParent);
  146. let verticalIndex: number = this.currentStaffEntry.VerticalContainerParent.StaffEntries.indexOf(this.currentStaffEntry);
  147. let previousStaffEntry: SourceStaffEntry = this.currentMeasure.getPreviousSourceStaffEntryFromIndex(verticalIndex, horizontalIndex);
  148. if (previousStaffEntry !== undefined) {
  149. let previousVoiceEntry: VoiceEntry = undefined;
  150. for (let idx: number = 0, len: number = previousStaffEntry.VoiceEntries.length; idx < len; ++idx) {
  151. let voiceEntry: VoiceEntry = previousStaffEntry.VoiceEntries[idx];
  152. if (voiceEntry.ParentVoice === voice) {
  153. previousVoiceEntry = voiceEntry;
  154. previousVoiceEntry.graceVoiceEntriesAfter = [];
  155. for (let idx2: number = 0, len2: number = this.currentVoiceEntry.graceVoiceEntriesBefore.length; idx2 < len2; ++idx2) {
  156. let graceVoiceEntry: VoiceEntry = this.currentVoiceEntry.graceVoiceEntriesBefore[idx2];
  157. previousVoiceEntry.graceVoiceEntriesAfter.push(graceVoiceEntry);
  158. }
  159. this.currentVoiceEntry.graceVoiceEntriesBefore = [];
  160. this.currentStaffEntry = undefined;
  161. break;
  162. }
  163. }
  164. }
  165. }
  166. }
  167. public checkForStaffEntryLink(
  168. index: number, currentStaff: Staff, currentStaffEntry: SourceStaffEntry, currentMeasure: SourceMeasure
  169. ): SourceStaffEntry {
  170. let staffEntryLink: StaffEntryLink = new StaffEntryLink(this.currentVoiceEntry);
  171. staffEntryLink.LinkStaffEntries.push(currentStaffEntry);
  172. currentStaffEntry.Link = staffEntryLink;
  173. let linkMusicTimestamp: Fraction = this.currentVoiceEntry.Timestamp.clone();
  174. let verticalSourceStaffEntryContainer: VerticalSourceStaffEntryContainer = currentMeasure.getVerticalContainerByTimestamp(linkMusicTimestamp);
  175. currentStaffEntry = verticalSourceStaffEntryContainer.StaffEntries[index];
  176. if (currentStaffEntry === undefined) {
  177. currentStaffEntry = new SourceStaffEntry(verticalSourceStaffEntryContainer, currentStaff);
  178. verticalSourceStaffEntryContainer.StaffEntries[index] = currentStaffEntry;
  179. }
  180. currentStaffEntry.VoiceEntries.push(this.currentVoiceEntry);
  181. staffEntryLink.LinkStaffEntries.push(currentStaffEntry);
  182. currentStaffEntry.Link = staffEntryLink;
  183. return currentStaffEntry;
  184. }
  185. public checkForOpenBeam(): void {
  186. if (this.openBeam !== undefined && this.currentNote !== undefined) {
  187. this.handleOpenBeam();
  188. }
  189. }
  190. public checkOpenTies(): void {
  191. let openTieDict: {[key: number]: Tie} = this.openTieDict;
  192. for (let key in openTieDict) {
  193. if (openTieDict.hasOwnProperty(key)) {
  194. let tie: Tie = openTieDict[key];
  195. if (Fraction.plus(tie.Start.ParentStaffEntry.Timestamp, tie.Start.Length)
  196. .lt(tie.Start.ParentStaffEntry.VerticalContainerParent.ParentMeasure.Duration)) {
  197. delete openTieDict[key];
  198. }
  199. }
  200. }
  201. }
  202. public hasVoiceEntry(): boolean {
  203. return this.currentVoiceEntry !== undefined;
  204. }
  205. public getNoteDurationFromType(type: string): Fraction {
  206. switch (type) {
  207. case "1024th":
  208. return new Fraction(1, 1024);
  209. case "512th":
  210. return new Fraction(1, 512);
  211. case "256th":
  212. return new Fraction(1, 256);
  213. case "128th":
  214. return new Fraction(1, 128);
  215. case "64th":
  216. return new Fraction(1, 64);
  217. case "32th":
  218. case "32nd":
  219. return new Fraction(1, 32);
  220. case "16th":
  221. return new Fraction(1, 16);
  222. case "eighth":
  223. return new Fraction(1, 8);
  224. case "quarter":
  225. return new Fraction(1, 4);
  226. case "half":
  227. return new Fraction(1, 2);
  228. case "whole":
  229. return new Fraction(1, 1);
  230. case "breve":
  231. return new Fraction(2, 1);
  232. case "long":
  233. return new Fraction(4, 1);
  234. case "maxima":
  235. return new Fraction(8, 1);
  236. default: {
  237. let errorMsg: string = ITextTranslation.translateText(
  238. "ReaderErrorMessages/NoteDurationError", "Invalid note duration."
  239. );
  240. throw new MusicSheetReadingException(errorMsg);
  241. }
  242. }
  243. }
  244. // (*)
  245. //private readArticulations(notationNode: IXmlElement, currentVoiceEntry: VoiceEntry): void {
  246. // let articNode: IXmlElement;
  247. // if ((articNode = notationNode.element("articulations")) !== undefined)
  248. // this.articulationReader.addArticulationExpression(articNode, currentVoiceEntry);
  249. // let fermaNode: IXmlElement = undefined;
  250. // if ((fermaNode = notationNode.element("fermata")) !== undefined)
  251. // this.articulationReader.addFermata(fermaNode, currentVoiceEntry);
  252. // let tecNode: IXmlElement = undefined;
  253. // if ((tecNode = notationNode.element("technical")) !== undefined)
  254. // this.articulationReader.addTechnicalArticulations(tecNode, currentVoiceEntry);
  255. // let ornaNode: IXmlElement = undefined;
  256. // if ((ornaNode = notationNode.element("ornaments")) !== undefined)
  257. // this.articulationReader.addOrnament(ornaNode, currentVoiceEntry);
  258. //}
  259. private addSingleNote(
  260. node: IXmlElement, noteDuration: number, divisions: number, graceNote: boolean, chord: boolean, guitarPro: boolean
  261. ): Note {
  262. Logging.debug("addSingleNote called");
  263. let noteAlter: AccidentalEnum = AccidentalEnum.NONE;
  264. let noteStep: NoteEnum = NoteEnum.C;
  265. let noteOctave: number = 0;
  266. let playbackInstrumentId: string = undefined;
  267. let xmlnodeElementsArr: IXmlElement[] = node.elements();
  268. for (let idx: number = 0, len: number = xmlnodeElementsArr.length; idx < len; ++idx) {
  269. let noteElement: IXmlElement = xmlnodeElementsArr[idx];
  270. try {
  271. if (noteElement.name === "pitch") {
  272. let noteElementsArr: IXmlElement[] = noteElement.elements();
  273. for (let idx2: number = 0, len2: number = noteElementsArr.length; idx2 < len2; ++idx2) {
  274. let pitchElement: IXmlElement = noteElementsArr[idx2];
  275. try {
  276. if (pitchElement.name === "step") {
  277. noteStep = NoteEnum[pitchElement.value];
  278. if (noteStep === undefined) {
  279. let errorMsg: string = ITextTranslation.translateText(
  280. "ReaderErrorMessages/NotePitchError",
  281. "Invalid pitch while reading note."
  282. );
  283. this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
  284. throw new MusicSheetReadingException(errorMsg, undefined);
  285. }
  286. } else if (pitchElement.name === "alter") {
  287. noteAlter = parseInt(pitchElement.value, 10);
  288. if (isNaN(noteAlter)) {
  289. let errorMsg: string = ITextTranslation.translateText(
  290. "ReaderErrorMessages/NoteAlterationError", "Invalid alteration while reading note."
  291. );
  292. this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
  293. throw new MusicSheetReadingException(errorMsg, undefined);
  294. }
  295. } else if (pitchElement.name === "octave") {
  296. noteOctave = parseInt(pitchElement.value, 10);
  297. if (isNaN(noteOctave)) {
  298. let errorMsg: string = ITextTranslation.translateText(
  299. "ReaderErrorMessages/NoteOctaveError", "Invalid octave value while reading note."
  300. );
  301. this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
  302. throw new MusicSheetReadingException(errorMsg, undefined);
  303. }
  304. }
  305. } catch (ex) {
  306. Logging.log("VoiceGenerator.addSingleNote read Step: ", ex.message);
  307. }
  308. }
  309. } else if (noteElement.name === "unpitched") {
  310. let displayStep: IXmlElement = noteElement.element("display-step");
  311. if (displayStep !== undefined) {
  312. noteStep = NoteEnum[displayStep.value.toUpperCase()];
  313. }
  314. let octave: IXmlElement = noteElement.element("display-octave");
  315. if (octave !== undefined) {
  316. noteOctave = parseInt(octave.value, 10);
  317. if (guitarPro) {
  318. noteOctave += 1;
  319. }
  320. }
  321. } else if (noteElement.name === "instrument") {
  322. if (noteElement.firstAttribute !== undefined) {
  323. playbackInstrumentId = noteElement.firstAttribute.value;
  324. }
  325. }
  326. } catch (ex) {
  327. Logging.log("VoiceGenerator.addSingleNote: ", ex);
  328. }
  329. }
  330. noteOctave -= Pitch.OctaveXmlDifference;
  331. let pitch: Pitch = new Pitch(noteStep, noteOctave, noteAlter);
  332. let noteLength: Fraction = new Fraction(noteDuration, divisions);
  333. let note: Note = new Note(this.currentVoiceEntry, this.currentStaffEntry, noteLength, pitch);
  334. note.PlaybackInstrumentId = playbackInstrumentId;
  335. if (!graceNote) {
  336. this.currentVoiceEntry.Notes.push(note);
  337. } else {
  338. this.handleGraceNote(node, note);
  339. }
  340. if (node.elements("beam") && !chord) {
  341. this.createBeam(node, note, graceNote);
  342. }
  343. return note;
  344. }
  345. private addRestNote(noteDuration: number, divisions: number): Note {
  346. let restFraction: Fraction = new Fraction(noteDuration, divisions);
  347. let restNote: Note = new Note(this.currentVoiceEntry, this.currentStaffEntry, restFraction, undefined);
  348. this.currentVoiceEntry.Notes.push(restNote);
  349. if (this.openBeam !== undefined) {
  350. this.openBeam.ExtendedNoteList.push(restNote);
  351. }
  352. return restNote;
  353. }
  354. private createBeam(node: IXmlElement, note: Note, grace: boolean): void {
  355. try {
  356. let beamNode: IXmlElement = node.element("beam");
  357. let beamAttr: IXmlAttribute = undefined;
  358. if (beamNode !== undefined && beamNode.hasAttributes) {
  359. beamAttr = beamNode.attribute("number");
  360. }
  361. if (beamAttr !== undefined) {
  362. let beamNumber: number = parseInt(beamAttr.value, 10);
  363. let mainBeamNode: IXmlElement[] = node.elements("beam");
  364. let currentBeamTag: string = mainBeamNode[0].value;
  365. if (beamNumber === 1 && mainBeamNode !== undefined) {
  366. if (currentBeamTag === "begin" && this.lastBeamTag !== currentBeamTag) {
  367. if (grace) {
  368. if (this.openGraceBeam !== undefined) {
  369. this.handleOpenBeam();
  370. }
  371. this.openGraceBeam = new Beam();
  372. } else {
  373. if (this.openBeam !== undefined) {
  374. this.handleOpenBeam();
  375. }
  376. this.openBeam = new Beam();
  377. }
  378. }
  379. this.lastBeamTag = currentBeamTag;
  380. }
  381. let sameVoiceEntry: boolean = false;
  382. if (grace) {
  383. if (this.openGraceBeam === undefined) { return; }
  384. for (let idx: number = 0, len: number = this.openGraceBeam.Notes.length; idx < len; ++idx) {
  385. let beamNote: Note = this.openGraceBeam.Notes[idx];
  386. if (this.currentVoiceEntry === beamNote.ParentVoiceEntry) {
  387. sameVoiceEntry = true;
  388. }
  389. }
  390. if (!sameVoiceEntry) {
  391. this.openGraceBeam.addNoteToBeam(note);
  392. if (currentBeamTag === "end" && beamNumber === 1) {
  393. this.openGraceBeam = undefined;
  394. }
  395. }
  396. } else {
  397. if (this.openBeam === undefined) { return; }
  398. for (let idx: number = 0, len: number = this.openBeam.Notes.length; idx < len; ++idx) {
  399. let beamNote: Note = this.openBeam.Notes[idx];
  400. if (this.currentVoiceEntry === beamNote.ParentVoiceEntry) {
  401. sameVoiceEntry = true;
  402. }
  403. }
  404. if (!sameVoiceEntry) {
  405. this.openBeam.addNoteToBeam(note);
  406. if (currentBeamTag === "end" && beamNumber === 1) {
  407. this.openBeam = undefined;
  408. }
  409. }
  410. }
  411. }
  412. } catch (e) {
  413. let errorMsg: string = ITextTranslation.translateText(
  414. "ReaderErrorMessages/BeamError", "Error while reading beam."
  415. );
  416. this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
  417. throw new MusicSheetReadingException("", e);
  418. }
  419. }
  420. private handleOpenBeam(): void {
  421. if (this.openBeam.Notes.length === 1) {
  422. let beamNote: Note = this.openBeam.Notes[0];
  423. beamNote.NoteBeam = undefined;
  424. this.openBeam = undefined;
  425. return;
  426. }
  427. if (this.currentNote === CollectionUtil.last(this.openBeam.Notes)) {
  428. this.openBeam = undefined;
  429. } else {
  430. let beamLastNote: Note = CollectionUtil.last(this.openBeam.Notes);
  431. let beamLastNoteStaffEntry: SourceStaffEntry = beamLastNote.ParentStaffEntry;
  432. let horizontalIndex: number = this.currentMeasure.getVerticalContainerIndexByTimestamp(beamLastNoteStaffEntry.Timestamp);
  433. let verticalIndex: number = beamLastNoteStaffEntry.VerticalContainerParent.StaffEntries.indexOf(beamLastNoteStaffEntry);
  434. if (horizontalIndex < this.currentMeasure.VerticalSourceStaffEntryContainers.length - 1) {
  435. let nextStaffEntry: SourceStaffEntry = this.currentMeasure.VerticalSourceStaffEntryContainers[horizontalIndex + 1][verticalIndex];
  436. if (nextStaffEntry !== undefined) {
  437. for (let idx: number = 0, len: number = nextStaffEntry.VoiceEntries.length; idx < len; ++idx) {
  438. let voiceEntry: VoiceEntry = nextStaffEntry.VoiceEntries[idx];
  439. if (voiceEntry.ParentVoice === this.voice) {
  440. let candidateNote: Note = voiceEntry.Notes[0];
  441. if (candidateNote.Length <= new Fraction(1, 8)) {
  442. this.openBeam.addNoteToBeam(candidateNote);
  443. this.openBeam = undefined;
  444. } else {
  445. this.openBeam = undefined;
  446. }
  447. }
  448. }
  449. }
  450. } else {
  451. this.openBeam = undefined;
  452. }
  453. }
  454. }
  455. private handleGraceNote(node: IXmlElement, note: Note): void {
  456. let graceChord: boolean = false;
  457. let type: string = "";
  458. if (node.elements("type")) {
  459. let typeNode: IXmlElement[] = node.elements("type");
  460. if (typeNode) {
  461. type = typeNode[0].value;
  462. try {
  463. note.Length = this.getNoteDurationFromType(type);
  464. note.Length.Numerator = 1;
  465. } catch (e) {
  466. let errorMsg: string = ITextTranslation.translateText(
  467. "ReaderErrorMessages/NoteDurationError", "Invalid note duration."
  468. );
  469. this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
  470. throw new MusicSheetReadingException(errorMsg, e);
  471. }
  472. }
  473. }
  474. let graceNode: IXmlElement = node.element("grace");
  475. if (graceNode !== undefined && graceNode.attributes()) {
  476. if (graceNode.attribute("slash")) {
  477. let slash: string = graceNode.attribute("slash").value;
  478. if (slash === "yes") {
  479. note.GraceNoteSlash = true;
  480. }
  481. }
  482. }
  483. if (node.element("chord") !== undefined) {
  484. graceChord = true;
  485. }
  486. let graceVoiceEntry: VoiceEntry = undefined;
  487. if (!graceChord) {
  488. graceVoiceEntry = new VoiceEntry(
  489. new Fraction(0, 1), this.currentVoiceEntry.ParentVoice, this.currentStaffEntry
  490. );
  491. if (this.currentVoiceEntry.graceVoiceEntriesBefore === undefined) {
  492. this.currentVoiceEntry.graceVoiceEntriesBefore = [];
  493. }
  494. this.currentVoiceEntry.graceVoiceEntriesBefore.push(graceVoiceEntry);
  495. } else {
  496. if (
  497. this.currentVoiceEntry.graceVoiceEntriesBefore !== undefined
  498. && this.currentVoiceEntry.graceVoiceEntriesBefore.length > 0
  499. ) {
  500. graceVoiceEntry = CollectionUtil.last(this.currentVoiceEntry.graceVoiceEntriesBefore);
  501. }
  502. }
  503. if (graceVoiceEntry !== undefined) {
  504. graceVoiceEntry.Notes.push(note);
  505. note.ParentVoiceEntry = graceVoiceEntry;
  506. }
  507. }
  508. private addTuplet(node: IXmlElement, tupletNodeList: IXmlElement[]): number {
  509. if (tupletNodeList !== undefined && tupletNodeList.length > 1) {
  510. let timeModNode: IXmlElement = node.element("time-modification");
  511. if (timeModNode !== undefined) {
  512. timeModNode = timeModNode.element("actual-notes");
  513. }
  514. let tupletNodeListArr: IXmlElement[] = tupletNodeList;
  515. for (let idx: number = 0, len: number = tupletNodeListArr.length; idx < len; ++idx) {
  516. let tupletNode: IXmlElement = tupletNodeListArr[idx];
  517. if (tupletNode !== undefined && tupletNode.attributes()) {
  518. let type: string = tupletNode.attribute("type").value;
  519. if (type === "start") {
  520. let tupletNumber: number = 1;
  521. if (tupletNode.attribute("number")) {
  522. tupletNumber = parseInt(tupletNode.attribute("number").value, 10);
  523. }
  524. let tupletLabelNumber: number = 0;
  525. if (timeModNode !== undefined) {
  526. tupletLabelNumber = parseInt(timeModNode.value, 10);
  527. if (isNaN(tupletLabelNumber)) {
  528. let errorMsg: string = ITextTranslation.translateText(
  529. "ReaderErrorMessages/TupletNoteDurationError", "Invalid tuplet note duration."
  530. );
  531. this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
  532. throw new MusicSheetReadingException(errorMsg, undefined);
  533. }
  534. }
  535. let tuplet: Tuplet = new Tuplet(tupletLabelNumber);
  536. if (this.tupletDict[tupletNumber] !== undefined) {
  537. delete this.tupletDict[tupletNumber];
  538. if (Object.keys(this.tupletDict).length === 0) {
  539. this.openTupletNumber = 0;
  540. } else if (Object.keys(this.tupletDict).length > 1) {
  541. this.openTupletNumber--;
  542. }
  543. }
  544. this.tupletDict[tupletNumber] = tuplet;
  545. let subnotelist: Note[] = [];
  546. subnotelist.push(this.currentNote);
  547. tuplet.Notes.push(subnotelist);
  548. tuplet.Fractions.push(this.getTupletNoteDurationFromType(node));
  549. this.currentNote.NoteTuplet = tuplet;
  550. this.openTupletNumber = tupletNumber;
  551. } else if (type === "stop") {
  552. let tupletNumber: number = 1;
  553. if (tupletNode.attribute("number")) {
  554. tupletNumber = parseInt(tupletNode.attribute("number").value, 10);
  555. }
  556. let tuplet: Tuplet = this.tupletDict[tupletNumber];
  557. if (tuplet !== undefined) {
  558. let subnotelist: Note[] = [];
  559. subnotelist.push(this.currentNote);
  560. tuplet.Notes.push(subnotelist);
  561. tuplet.Fractions.push(this.getTupletNoteDurationFromType(node));
  562. this.currentNote.NoteTuplet = tuplet;
  563. delete this.tupletDict[tupletNumber];
  564. if (Object.keys(this.tupletDict).length === 0) {
  565. this.openTupletNumber = 0;
  566. } else if (Object.keys(this.tupletDict).length > 1) {
  567. this.openTupletNumber--;
  568. }
  569. }
  570. }
  571. }
  572. }
  573. } else if (tupletNodeList[0] !== undefined) {
  574. let n: IXmlElement = tupletNodeList[0];
  575. if (n.hasAttributes) {
  576. let type: string = n.attribute("type").value;
  577. let tupletnumber: number = 1;
  578. if (n.attribute("number")) {
  579. tupletnumber = parseInt(n.attribute("number").value, 10);
  580. }
  581. let noTupletNumbering: boolean = isNaN(tupletnumber);
  582. if (type === "start") {
  583. let tupletLabelNumber: number = 0;
  584. let timeModNode: IXmlElement = node.element("time-modification");
  585. if (timeModNode !== undefined) {
  586. timeModNode = timeModNode.element("actual-notes");
  587. }
  588. if (timeModNode !== undefined) {
  589. tupletLabelNumber = parseInt(timeModNode.value, 10);
  590. if (isNaN(tupletLabelNumber)) {
  591. let errorMsg: string = ITextTranslation.translateText(
  592. "ReaderErrorMessages/TupletNoteDurationError", "Invalid tuplet note duration."
  593. );
  594. this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
  595. throw new MusicSheetReadingException(errorMsg);
  596. }
  597. }
  598. if (noTupletNumbering) {
  599. this.openTupletNumber++;
  600. tupletnumber = this.openTupletNumber;
  601. }
  602. let tuplet: Tuplet = this.tupletDict[tupletnumber];
  603. if (tuplet === undefined) {
  604. tuplet = this.tupletDict[tupletnumber] = new Tuplet(tupletLabelNumber);
  605. }
  606. let subnotelist: Note[] = [];
  607. subnotelist.push(this.currentNote);
  608. tuplet.Notes.push(subnotelist);
  609. tuplet.Fractions.push(this.getTupletNoteDurationFromType(node));
  610. this.currentNote.NoteTuplet = tuplet;
  611. this.openTupletNumber = tupletnumber;
  612. } else if (type === "stop") {
  613. if (noTupletNumbering) {
  614. tupletnumber = this.openTupletNumber;
  615. }
  616. let tuplet: Tuplet = this.tupletDict[this.openTupletNumber];
  617. if (tuplet !== undefined) {
  618. let subnotelist: Note[] = [];
  619. subnotelist.push(this.currentNote);
  620. tuplet.Notes.push(subnotelist);
  621. tuplet.Fractions.push(this.getTupletNoteDurationFromType(node));
  622. this.currentNote.NoteTuplet = tuplet;
  623. if (Object.keys(this.tupletDict).length === 0) {
  624. this.openTupletNumber = 0;
  625. } else if (Object.keys(this.tupletDict).length > 1) {
  626. this.openTupletNumber--;
  627. }
  628. delete this.tupletDict[tupletnumber];
  629. }
  630. }
  631. }
  632. }
  633. return this.openTupletNumber;
  634. }
  635. private handleTimeModificationNode(noteNode: IXmlElement): void {
  636. if (Object.keys(this.tupletDict).length !== 0) {
  637. try {
  638. let tuplet: Tuplet = this.tupletDict[this.openTupletNumber];
  639. let notes: Note[] = CollectionUtil.last(tuplet.Notes);
  640. let lastTupletVoiceEntry: VoiceEntry = notes[0].ParentVoiceEntry;
  641. let noteList: Note[];
  642. if (lastTupletVoiceEntry.Timestamp === this.currentVoiceEntry.Timestamp) {
  643. noteList = notes;
  644. } else {
  645. noteList = [];
  646. tuplet.Notes.push(noteList);
  647. tuplet.Fractions.push(this.getTupletNoteDurationFromType(noteNode));
  648. }
  649. noteList.push(this.currentNote);
  650. this.currentNote.NoteTuplet = tuplet;
  651. } catch (ex) {
  652. let errorMsg: string = ITextTranslation.translateText(
  653. "ReaderErrorMessages/TupletNumberError", "Invalid tuplet number."
  654. );
  655. this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
  656. throw ex;
  657. }
  658. } else if (this.currentVoiceEntry.Notes.length > 0) {
  659. let firstNote: Note = this.currentVoiceEntry.Notes[0];
  660. if (firstNote.NoteTuplet !== undefined) {
  661. let tuplet: Tuplet = firstNote.NoteTuplet;
  662. let notes: Note[] = CollectionUtil.last(tuplet.Notes);
  663. notes.push(this.currentNote);
  664. this.currentNote.NoteTuplet = tuplet;
  665. }
  666. }
  667. }
  668. private addTie(tieNodeList: IXmlElement[], measureStartAbsoluteTimestamp: Fraction, maxTieNoteFraction: Fraction): void {
  669. if (tieNodeList !== undefined) {
  670. if (tieNodeList.length === 1) {
  671. let tieNode: IXmlElement = tieNodeList[0];
  672. if (tieNode !== undefined && tieNode.attributes()) {
  673. let type: string = tieNode.attribute("type").value;
  674. try {
  675. if (type === "start") {
  676. let num: number = this.findCurrentNoteInTieDict(this.currentNote);
  677. if (num < 0) {
  678. delete this.openTieDict[num];
  679. }
  680. let newTieNumber: number = this.getNextAvailableNumberForTie();
  681. let tie: Tie = new Tie(this.currentNote);
  682. this.openTieDict[newTieNumber] = tie;
  683. if (this.currentNote.NoteBeam !== undefined) {
  684. if (this.currentNote.NoteBeam.Notes[0] === this.currentNote) {
  685. tie.BeamStartTimestamp = Fraction.plus(measureStartAbsoluteTimestamp, this.currentVoiceEntry.Timestamp);
  686. } else {
  687. for (let idx: number = 0, len: number = this.currentNote.NoteBeam.Notes.length; idx < len; ++idx) {
  688. let note: Note = this.currentNote.NoteBeam.Notes[idx];
  689. if (note.NoteTie !== undefined && note.NoteTie !== tie && note.NoteTie.BeamStartTimestamp !== undefined) {
  690. tie.BeamStartTimestamp = note.NoteTie.BeamStartTimestamp;
  691. break;
  692. }
  693. }
  694. if (this.currentNote === CollectionUtil.last(this.currentNote.NoteBeam.Notes)) {
  695. tie.BeamStartTimestamp = Fraction.plus(measureStartAbsoluteTimestamp, this.currentVoiceEntry.Timestamp);
  696. }
  697. }
  698. }
  699. } else if (type === "stop") {
  700. let tieNumber: number = this.findCurrentNoteInTieDict(this.currentNote);
  701. let tie: Tie = this.openTieDict[tieNumber];
  702. if (tie !== undefined) {
  703. let tieStartNote: Note = tie.Start;
  704. tieStartNote.NoteTie = tie;
  705. tieStartNote.Length.Add(this.currentNote.Length);
  706. tie.Fractions.push(this.currentNote.Length);
  707. if (maxTieNoteFraction.lt(Fraction.plus(this.currentStaffEntry.Timestamp, this.currentNote.Length))) {
  708. maxTieNoteFraction = Fraction.plus(this.currentStaffEntry.Timestamp, this.currentNote.Length);
  709. }
  710. let i: number = this.currentVoiceEntry.Notes.indexOf(this.currentNote);
  711. if (i !== -1) { delete this.currentVoiceEntry.Notes[i]; }
  712. if (
  713. this.currentVoiceEntry.Articulations.length === 1
  714. && this.currentVoiceEntry.Articulations[0] === ArticulationEnum.fermata
  715. && tieStartNote.ParentVoiceEntry.Articulations[ArticulationEnum.fermata] === undefined
  716. ) {
  717. tieStartNote.ParentVoiceEntry.Articulations.push(ArticulationEnum.fermata);
  718. }
  719. if (this.currentNote.NoteBeam !== undefined) {
  720. let noteBeamIndex: number = this.currentNote.NoteBeam.Notes.indexOf(this.currentNote);
  721. if (noteBeamIndex === 0 && tie.BeamStartTimestamp === undefined) {
  722. tie.BeamStartTimestamp = Fraction.plus(measureStartAbsoluteTimestamp, this.currentVoiceEntry.Timestamp);
  723. }
  724. let noteBeam: Beam = this.currentNote.NoteBeam;
  725. noteBeam.Notes[noteBeamIndex] = tieStartNote;
  726. tie.TieBeam = noteBeam;
  727. }
  728. if (this.currentNote.NoteTuplet !== undefined) {
  729. let noteTupletIndex: number = this.currentNote.NoteTuplet.getNoteIndex(this.currentNote);
  730. let index: number = this.currentNote.NoteTuplet.Notes[noteTupletIndex].indexOf(this.currentNote);
  731. let noteTuplet: Tuplet = this.currentNote.NoteTuplet;
  732. noteTuplet.Notes[noteTupletIndex][index] = tieStartNote;
  733. tie.TieTuplet = noteTuplet;
  734. }
  735. for (let idx: number = 0, len: number = this.currentNote.NoteSlurs.length; idx < len; ++idx) {
  736. let slur: Slur = this.currentNote.NoteSlurs[idx];
  737. if (slur.StartNote === this.currentNote) {
  738. slur.StartNote = tie.Start;
  739. slur.StartNote.NoteSlurs.push(slur);
  740. }
  741. if (slur.EndNote === this.currentNote) {
  742. slur.EndNote = tie.Start;
  743. slur.EndNote.NoteSlurs.push(slur);
  744. }
  745. }
  746. let lyricsEntries: { [n: number]: LyricsEntry; } = this.currentVoiceEntry.LyricsEntries;
  747. for (let lyricsEntry in lyricsEntries) {
  748. if (lyricsEntries.hasOwnProperty(lyricsEntry)) {
  749. let val: LyricsEntry = this.currentVoiceEntry.LyricsEntries[lyricsEntry];
  750. if (!tieStartNote.ParentVoiceEntry.LyricsEntries.hasOwnProperty(lyricsEntry)) {
  751. tieStartNote.ParentVoiceEntry.LyricsEntries[lyricsEntry] = val;
  752. val.Parent = tieStartNote.ParentVoiceEntry;
  753. }
  754. }
  755. }
  756. delete this.openTieDict[tieNumber];
  757. }
  758. }
  759. } catch (err) {
  760. let errorMsg: string = ITextTranslation.translateText("ReaderErrorMessages/TieError", "Error while reading tie.");
  761. this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
  762. }
  763. }
  764. } else if (tieNodeList.length === 2) {
  765. let tieNumber: number = this.findCurrentNoteInTieDict(this.currentNote);
  766. if (tieNumber >= 0) {
  767. let tie: Tie = this.openTieDict[tieNumber];
  768. let tieStartNote: Note = tie.Start;
  769. tieStartNote.Length.Add(this.currentNote.Length);
  770. tie.Fractions.push(this.currentNote.Length);
  771. if (this.currentNote.NoteBeam !== undefined) {
  772. let noteBeamIndex: number = this.currentNote.NoteBeam.Notes.indexOf(this.currentNote);
  773. if (noteBeamIndex === 0 && tie.BeamStartTimestamp === undefined) {
  774. tie.BeamStartTimestamp = Fraction.plus(measureStartAbsoluteTimestamp, this.currentVoiceEntry.Timestamp);
  775. }
  776. let noteBeam: Beam = this.currentNote.NoteBeam;
  777. noteBeam.Notes[noteBeamIndex] = tieStartNote;
  778. tie.TieBeam = noteBeam;
  779. }
  780. for (let idx: number = 0, len: number = this.currentNote.NoteSlurs.length; idx < len; ++idx) {
  781. let slur: Slur = this.currentNote.NoteSlurs[idx];
  782. if (slur.StartNote === this.currentNote) {
  783. slur.StartNote = tie.Start;
  784. slur.StartNote.NoteSlurs.push(slur);
  785. }
  786. if (slur.EndNote === this.currentNote) {
  787. slur.EndNote = tie.Start;
  788. slur.EndNote.NoteSlurs.push(slur);
  789. }
  790. }
  791. let lyricsEntries: { [_: number]: LyricsEntry; } = this.currentVoiceEntry.LyricsEntries;
  792. for (let key in lyricsEntries) {
  793. if (lyricsEntries.hasOwnProperty(key)) {
  794. let lyricsEntry: LyricsEntry = lyricsEntries[key];
  795. if (!tieStartNote.ParentVoiceEntry.LyricsEntries.hasOwnProperty(key)) {
  796. tieStartNote.ParentVoiceEntry.LyricsEntries[key] = lyricsEntry;
  797. lyricsEntry.Parent = tieStartNote.ParentVoiceEntry;
  798. }
  799. }
  800. }
  801. if (maxTieNoteFraction.lt(Fraction.plus(this.currentStaffEntry.Timestamp, this.currentNote.Length))) {
  802. maxTieNoteFraction = Fraction.plus(this.currentStaffEntry.Timestamp, this.currentNote.Length);
  803. }
  804. // delete currentNote from Notes:
  805. let i: number = this.currentVoiceEntry.Notes.indexOf(this.currentNote);
  806. if (i !== -1) { delete this.currentVoiceEntry.Notes[i]; }
  807. }
  808. }
  809. }
  810. }
  811. private getNextAvailableNumberForTie(): number {
  812. let keys: string[] = Object.keys(this.openTieDict);
  813. if (keys.length === 0) { return 1; }
  814. keys.sort((a, b) => (+a - +b)); // FIXME Andrea: test
  815. for (let i: number = 0; i < keys.length; i++) {
  816. if ("" + (i + 1) !== keys[i]) {
  817. return i + 1;
  818. }
  819. }
  820. return +(keys[keys.length - 1]) + 1;
  821. }
  822. private findCurrentNoteInTieDict(candidateNote: Note): number {
  823. let openTieDict: { [_: number]: Tie; } = this.openTieDict;
  824. for (let key in openTieDict) {
  825. if (openTieDict.hasOwnProperty(key)) {
  826. let tie: Tie = openTieDict[key];
  827. if (tie.Start.Pitch.FundamentalNote === candidateNote.Pitch.FundamentalNote && tie.Start.Pitch.Octave === candidateNote.Pitch.Octave) {
  828. return +key;
  829. }
  830. }
  831. }
  832. return -1;
  833. }
  834. private getTupletNoteDurationFromType(xmlNode: IXmlElement): Fraction {
  835. if (xmlNode.element("type") !== undefined) {
  836. let typeNode: IXmlElement = xmlNode.element("type");
  837. if (typeNode !== undefined) {
  838. let type: string = typeNode.value;
  839. try {
  840. return this.getNoteDurationFromType(type);
  841. } catch (e) {
  842. let errorMsg: string = ITextTranslation.translateText("ReaderErrorMessages/NoteDurationError", "Invalid note duration.");
  843. this.musicSheet.SheetErrors.pushMeasureError(errorMsg);
  844. throw new MusicSheetReadingException("", e);
  845. }
  846. }
  847. }
  848. return undefined;
  849. }
  850. }