VoiceEntry.ts 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434
  1. import {Fraction} from "../../Common/DataObjects/Fraction";
  2. import {Voice} from "./Voice";
  3. import {SourceStaffEntry} from "./SourceStaffEntry";
  4. import {Note} from "./Note";
  5. import {Pitch} from "../../Common/DataObjects/Pitch";
  6. import {LyricsEntry} from "./Lyrics/LyricsEntry";
  7. import {TechnicalInstruction} from "./Instructions/TechnicalInstruction";
  8. import {OrnamentContainer} from "./OrnamentContainer";
  9. import {KeyInstruction} from "./Instructions/KeyInstruction";
  10. import {OrnamentEnum} from "./OrnamentContainer";
  11. import {AccidentalEnum} from "../../Common/DataObjects/Pitch";
  12. import { Dictionary } from "typescript-collections";
  13. import {Arpeggio} from "./Arpeggio";
  14. import { SourceMeasure } from "./SourceMeasure";
  15. import { Articulation } from "./Articulation";
  16. /**
  17. * A [[VoiceEntry]] contains the notes in a voice at a timestamp.
  18. */
  19. export class VoiceEntry {
  20. /**
  21. *
  22. * @param timestamp The relative timestamp within the source measure.
  23. * @param parentVoice
  24. * @param parentSourceStaffEntry
  25. * @param isGrace States whether the VoiceEntry has (only) grace notes.
  26. * @param graceNoteSlash States whether the grace note(s) have a slash (Acciaccatura, played before the beat)
  27. */
  28. constructor(timestamp: Fraction, parentVoice: Voice, parentSourceStaffEntry: SourceStaffEntry,
  29. isGrace: boolean = false, graceNoteSlash: boolean = false, graceSlur: boolean = false) {
  30. this.timestamp = timestamp;
  31. this.parentVoice = parentVoice;
  32. this.parentSourceStaffEntry = parentSourceStaffEntry;
  33. this.isGrace = isGrace;
  34. this.graceAfterMainNote = false;
  35. this.graceNoteSlash = graceNoteSlash;
  36. this.graceSlur = graceSlur;
  37. // add currentVoiceEntry to staff entry:
  38. if (parentSourceStaffEntry !== undefined) {
  39. const list: VoiceEntry[] = parentSourceStaffEntry.VoiceEntries;
  40. if (list.indexOf(this) === -1) {
  41. list.push(this);
  42. }
  43. }
  44. }
  45. private parentVoice: Voice;
  46. private parentSourceStaffEntry: SourceStaffEntry;
  47. private timestamp: Fraction;
  48. private notes: Note[] = [];
  49. private isGrace: boolean;
  50. /** States whether the grace notes come after a main note (at end of measure). */
  51. private graceAfterMainNote: boolean;
  52. private graceNoteSlash: boolean;
  53. private graceSlur: boolean; // TODO grace slur system could be refined to be non-binary
  54. private articulations: Articulation[] = [];
  55. private technicalInstructions: TechnicalInstruction[] = [];
  56. private lyricsEntries: Dictionary<number, LyricsEntry> = new Dictionary<number, LyricsEntry>();
  57. /** The Arpeggio consisting of this VoiceEntry's notes. Undefined if no arpeggio exists. */
  58. private arpeggio: Arpeggio;
  59. private ornamentContainer: OrnamentContainer;
  60. private wantedStemDirection: StemDirectionType = StemDirectionType.Undefined;
  61. /** Stem direction specified in the xml stem element. */
  62. private stemDirectionXml: StemDirectionType = StemDirectionType.Undefined;
  63. private stemDirection: StemDirectionType = StemDirectionType.Undefined;
  64. /** Color of the stem given in XML. RGB Hexadecimal, like #00FF00. */
  65. private stemColorXml: string;
  66. /** Color of the stem currently set. RGB Hexadecimal, like #00FF00. */
  67. private stemColor: string;
  68. public get ParentSourceStaffEntry(): SourceStaffEntry {
  69. return this.parentSourceStaffEntry;
  70. }
  71. public get ParentVoice(): Voice {
  72. return this.parentVoice;
  73. }
  74. public get Timestamp(): Fraction {
  75. return this.timestamp;
  76. }
  77. public set Timestamp(value: Fraction) {
  78. this.timestamp = value;
  79. }
  80. public get Notes(): Note[] {
  81. return this.notes;
  82. }
  83. public get IsGrace(): boolean {
  84. return this.isGrace;
  85. }
  86. public set IsGrace(value: boolean) {
  87. this.isGrace = value;
  88. }
  89. public get GraceAfterMainNote(): boolean {
  90. return this.graceAfterMainNote;
  91. }
  92. public set GraceAfterMainNote(value: boolean) {
  93. this.graceAfterMainNote = value;
  94. }
  95. public get GraceNoteSlash(): boolean {
  96. return this.graceNoteSlash;
  97. }
  98. public set GraceNoteSlash(value: boolean) {
  99. this.graceNoteSlash = value;
  100. }
  101. public get GraceSlur(): boolean {
  102. return this.graceSlur;
  103. }
  104. public set GraceSlur(value: boolean) {
  105. this.graceSlur = value;
  106. }
  107. public get Articulations(): Articulation[] {
  108. return this.articulations;
  109. }
  110. public get TechnicalInstructions(): TechnicalInstruction[] {
  111. return this.technicalInstructions;
  112. }
  113. public get LyricsEntries(): Dictionary<number, LyricsEntry> {
  114. return this.lyricsEntries;
  115. }
  116. public get Arpeggio(): Arpeggio {
  117. return this.arpeggio;
  118. }
  119. public set Arpeggio(value: Arpeggio) {
  120. this.arpeggio = value;
  121. }
  122. public get OrnamentContainer(): OrnamentContainer {
  123. return this.ornamentContainer;
  124. }
  125. public set OrnamentContainer(value: OrnamentContainer) {
  126. this.ornamentContainer = value;
  127. }
  128. // WantedStemDirection provides the stem direction to VexFlow in case of more than 1 voice
  129. // for optimal graphical appearance
  130. public set WantedStemDirection(value: StemDirectionType) {
  131. this.wantedStemDirection = value;
  132. }
  133. public get WantedStemDirection(): StemDirectionType {
  134. return this.wantedStemDirection;
  135. }
  136. public set StemDirectionXml(value: StemDirectionType) {
  137. this.stemDirectionXml = value;
  138. }
  139. public get StemDirectionXml(): StemDirectionType {
  140. return this.stemDirectionXml;
  141. }
  142. // StemDirection holds the actual value of the stem
  143. public set StemDirection(value: StemDirectionType) {
  144. this.stemDirection = value;
  145. }
  146. public get StemDirection(): StemDirectionType {
  147. return this.stemDirection;
  148. }
  149. public get StemColorXml(): string {
  150. return this.stemColorXml;
  151. }
  152. public set StemColorXml(value: string) {
  153. this.stemColorXml = value;
  154. }
  155. public get StemColor(): string {
  156. return this.stemColor;
  157. }
  158. public set StemColor(value: string) {
  159. this.stemColor = value;
  160. }
  161. public hasArticulation(articulation: Articulation): boolean {
  162. for (const existingArticulation of this.articulations) {
  163. if (existingArticulation.Equals(articulation)) {
  164. return true;
  165. }
  166. }
  167. return false;
  168. }
  169. public static isSupportedArticulation(articulation: ArticulationEnum): boolean {
  170. switch (articulation) {
  171. case ArticulationEnum.accent:
  172. case ArticulationEnum.strongaccent:
  173. case ArticulationEnum.invertedstrongaccent:
  174. case ArticulationEnum.staccato:
  175. case ArticulationEnum.staccatissimo:
  176. case ArticulationEnum.spiccato:
  177. case ArticulationEnum.tenuto:
  178. case ArticulationEnum.fermata:
  179. case ArticulationEnum.invertedfermata:
  180. case ArticulationEnum.breathmark:
  181. case ArticulationEnum.caesura:
  182. case ArticulationEnum.lefthandpizzicato:
  183. case ArticulationEnum.naturalharmonic:
  184. case ArticulationEnum.snappizzicato:
  185. case ArticulationEnum.upbow:
  186. case ArticulationEnum.downbow:
  187. case ArticulationEnum.bend:
  188. return true;
  189. default:
  190. return false;
  191. }
  192. }
  193. public hasTie(): boolean {
  194. for (let idx: number = 0, len: number = this.Notes.length; idx < len; ++idx) {
  195. const note: Note = this.Notes[idx];
  196. if (note.NoteTie) { return true; }
  197. }
  198. return false;
  199. }
  200. public hasSlur(): boolean {
  201. for (let idx: number = 0, len: number = this.Notes.length; idx < len; ++idx) {
  202. const note: Note = this.Notes[idx];
  203. if (note.NoteSlurs.length > 0) { return true; }
  204. }
  205. return false;
  206. }
  207. public isStaccato(): boolean {
  208. for (const articulation of this.Articulations) {
  209. if (articulation.articulationEnum === ArticulationEnum.staccato) {
  210. return true;
  211. }
  212. }
  213. return false;
  214. }
  215. public isAccent(): boolean {
  216. for (const articulation of this.Articulations) {
  217. if (articulation.articulationEnum === ArticulationEnum.accent || articulation.articulationEnum === ArticulationEnum.strongaccent) {
  218. return true;
  219. }
  220. }
  221. return false;
  222. }
  223. public getVerseNumberForLyricEntry(lyricsEntry: LyricsEntry): number {
  224. let verseNumber: number = 1;
  225. this.lyricsEntries.forEach((key: number, value: LyricsEntry): void => {
  226. if (lyricsEntry === value) {
  227. verseNumber = key;
  228. }
  229. });
  230. return verseNumber;
  231. }
  232. //public createVoiceEntriesForOrnament(activeKey: KeyInstruction): VoiceEntry[] {
  233. // return this.createVoiceEntriesForOrnament(this, activeKey);
  234. //}
  235. public createVoiceEntriesForOrnament(voiceEntryWithOrnament: VoiceEntry, activeKey: KeyInstruction): VoiceEntry[] {
  236. if (!voiceEntryWithOrnament) {
  237. voiceEntryWithOrnament = this;
  238. }
  239. const voiceEntries: VoiceEntry[] = [];
  240. if (!voiceEntryWithOrnament.ornamentContainer) {
  241. return;
  242. }
  243. const baseNote: Note = this.notes[0];
  244. const baselength: Fraction = baseNote.Length;
  245. const baseVoice: Voice = voiceEntryWithOrnament.ParentVoice;
  246. const baseTimestamp: Fraction = voiceEntryWithOrnament.Timestamp;
  247. let currentTimestamp: Fraction = Fraction.createFromFraction(baseTimestamp);
  248. //let length: Fraction;
  249. switch (voiceEntryWithOrnament.ornamentContainer.GetOrnament) {
  250. case OrnamentEnum.Trill: {
  251. const length: Fraction = new Fraction(baselength.Numerator, baselength.Denominator * 8);
  252. const higherPitch: Pitch = baseNote.Pitch.getTransposedPitch(1);
  253. let alteration: AccidentalEnum = activeKey.getAlterationForPitch(higherPitch);
  254. if (voiceEntryWithOrnament.OrnamentContainer.AccidentalAbove !== AccidentalEnum.NONE) {
  255. alteration = voiceEntryWithOrnament.ornamentContainer.AccidentalAbove;
  256. }
  257. for (let i: number = 0; i < 8; i++) {
  258. currentTimestamp = Fraction.plus(baseTimestamp, new Fraction(i * length.Numerator, length.Denominator));
  259. if ((i % 2) === 0) {
  260. this.createBaseVoiceEntry(currentTimestamp, length, baseVoice, baseNote, voiceEntries);
  261. } else {
  262. this.createAlteratedVoiceEntry(currentTimestamp, length, baseVoice, baseNote.SourceMeasure, higherPitch, alteration, voiceEntries);
  263. }
  264. }
  265. break;
  266. }
  267. case OrnamentEnum.Turn: {
  268. const length: Fraction = new Fraction(baselength.Numerator, baselength.Denominator * 4);
  269. const lowerPitch: Pitch = baseNote.Pitch.getTransposedPitch(-1);
  270. const lowerAlteration: AccidentalEnum = activeKey.getAlterationForPitch(lowerPitch);
  271. const higherPitch: Pitch = baseNote.Pitch.getTransposedPitch(1);
  272. const higherAlteration: AccidentalEnum = activeKey.getAlterationForPitch(higherPitch);
  273. this.createAlteratedVoiceEntry(
  274. currentTimestamp, length, baseVoice, baseNote.SourceMeasure, higherPitch, higherAlteration, voiceEntries
  275. );
  276. currentTimestamp.Add(length);
  277. this.createBaseVoiceEntry(currentTimestamp, length, baseVoice, baseNote, voiceEntries);
  278. currentTimestamp.Add(length);
  279. this.createAlteratedVoiceEntry(
  280. currentTimestamp, length, baseVoice, baseNote.SourceMeasure, lowerPitch, lowerAlteration, voiceEntries
  281. );
  282. currentTimestamp.Add(length);
  283. this.createBaseVoiceEntry(currentTimestamp, length, baseVoice, baseNote, voiceEntries);
  284. break;
  285. }
  286. case OrnamentEnum.InvertedTurn: {
  287. const length: Fraction = new Fraction(baselength.Numerator, baselength.Denominator * 4);
  288. const lowerPitch: Pitch = baseNote.Pitch.getTransposedPitch(-1);
  289. const lowerAlteration: AccidentalEnum = activeKey.getAlterationForPitch(lowerPitch);
  290. const higherPitch: Pitch = baseNote.Pitch.getTransposedPitch(1);
  291. const higherAlteration: AccidentalEnum = activeKey.getAlterationForPitch(higherPitch);
  292. this.createAlteratedVoiceEntry(
  293. currentTimestamp, length, baseVoice, baseNote.SourceMeasure, lowerPitch, lowerAlteration, voiceEntries
  294. );
  295. currentTimestamp.Add(length);
  296. this.createBaseVoiceEntry(currentTimestamp, length, baseVoice, baseNote, voiceEntries);
  297. currentTimestamp.Add(length);
  298. this.createAlteratedVoiceEntry(
  299. currentTimestamp, length, baseVoice, baseNote.SourceMeasure, higherPitch, higherAlteration, voiceEntries
  300. );
  301. currentTimestamp.Add(length);
  302. this.createBaseVoiceEntry(currentTimestamp, length, baseVoice, baseNote, voiceEntries);
  303. break;
  304. }
  305. case OrnamentEnum.DelayedTurn: {
  306. const length: Fraction = new Fraction(baselength.Numerator, baselength.Denominator * 2);
  307. const lowerPitch: Pitch = baseNote.Pitch.getTransposedPitch(-1);
  308. const lowerAlteration: AccidentalEnum = activeKey.getAlterationForPitch(lowerPitch);
  309. const higherPitch: Pitch = baseNote.Pitch.getTransposedPitch(1);
  310. const higherAlteration: AccidentalEnum = activeKey.getAlterationForPitch(higherPitch);
  311. this.createBaseVoiceEntry(currentTimestamp, length, baseVoice, baseNote, voiceEntries);
  312. currentTimestamp = Fraction.plus(baseTimestamp, length);
  313. length.Denominator = baselength.Denominator * 8;
  314. this.createAlteratedVoiceEntry(currentTimestamp, length, baseVoice, baseNote.SourceMeasure, higherPitch, higherAlteration, voiceEntries);
  315. currentTimestamp.Add(length);
  316. this.createBaseVoiceEntry(currentTimestamp, length, baseVoice, baseNote, voiceEntries);
  317. currentTimestamp.Add(length);
  318. this.createAlteratedVoiceEntry(currentTimestamp, length, baseVoice, baseNote.SourceMeasure, lowerPitch, lowerAlteration, voiceEntries);
  319. currentTimestamp.Add(length);
  320. this.createBaseVoiceEntry(currentTimestamp, length, baseVoice, baseNote, voiceEntries);
  321. break;
  322. }
  323. case OrnamentEnum.DelayedInvertedTurn: {
  324. const length: Fraction = new Fraction(baselength.Numerator, baselength.Denominator * 2);
  325. const lowerPitch: Pitch = baseNote.Pitch.getTransposedPitch(-1);
  326. const lowerAlteration: AccidentalEnum = activeKey.getAlterationForPitch(lowerPitch);
  327. const higherPitch: Pitch = baseNote.Pitch.getTransposedPitch(1);
  328. const higherAlteration: AccidentalEnum = activeKey.getAlterationForPitch(higherPitch);
  329. this.createBaseVoiceEntry(currentTimestamp, length, baseVoice, baseNote, voiceEntries);
  330. currentTimestamp = Fraction.plus(baseTimestamp, length);
  331. length.Denominator = baselength.Denominator * 8;
  332. this.createAlteratedVoiceEntry(currentTimestamp, length, baseVoice, baseNote.SourceMeasure, lowerPitch, lowerAlteration, voiceEntries);
  333. currentTimestamp.Add(length);
  334. this.createBaseVoiceEntry(currentTimestamp, length, baseVoice, baseNote, voiceEntries);
  335. currentTimestamp.Add(length);
  336. this.createAlteratedVoiceEntry(currentTimestamp, length, baseVoice, baseNote.SourceMeasure, higherPitch, higherAlteration, voiceEntries);
  337. currentTimestamp.Add(length);
  338. this.createBaseVoiceEntry(currentTimestamp, length, baseVoice, baseNote, voiceEntries);
  339. break;
  340. }
  341. case OrnamentEnum.Mordent: {
  342. const length: Fraction = new Fraction(baselength.Numerator, baselength.Denominator * 4);
  343. const higherPitch: Pitch = baseNote.Pitch.getTransposedPitch(1);
  344. const alteration: AccidentalEnum = activeKey.getAlterationForPitch(higherPitch);
  345. this.createBaseVoiceEntry(currentTimestamp, length, baseVoice, baseNote, voiceEntries);
  346. currentTimestamp.Add(length);
  347. this.createAlteratedVoiceEntry(currentTimestamp, length, baseVoice, baseNote.SourceMeasure, higherPitch, alteration, voiceEntries);
  348. length.Denominator = baselength.Denominator * 2;
  349. currentTimestamp = Fraction.plus(baseTimestamp, length);
  350. this.createBaseVoiceEntry(currentTimestamp, length, baseVoice, baseNote, voiceEntries);
  351. break;
  352. }
  353. case OrnamentEnum.InvertedMordent: {
  354. const length: Fraction = new Fraction(baselength.Numerator, baselength.Denominator * 4);
  355. const lowerPitch: Pitch = baseNote.Pitch.getTransposedPitch(-1);
  356. const alteration: AccidentalEnum = activeKey.getAlterationForPitch(lowerPitch);
  357. this.createBaseVoiceEntry(currentTimestamp, length, baseVoice, baseNote, voiceEntries);
  358. currentTimestamp.Add(length);
  359. this.createAlteratedVoiceEntry(currentTimestamp, length, baseVoice, baseNote.SourceMeasure, lowerPitch, alteration, voiceEntries);
  360. length.Denominator = baselength.Denominator * 2;
  361. currentTimestamp = Fraction.plus(baseTimestamp, length);
  362. this.createBaseVoiceEntry(currentTimestamp, length, baseVoice, baseNote, voiceEntries);
  363. break;
  364. }
  365. default:
  366. throw new RangeError();
  367. }
  368. return voiceEntries;
  369. }
  370. private createBaseVoiceEntry(
  371. currentTimestamp: Fraction, length: Fraction, baseVoice: Voice, baseNote: Note, voiceEntries: VoiceEntry[]
  372. ): void {
  373. const voiceEntry: VoiceEntry = new VoiceEntry(currentTimestamp, baseVoice, baseNote.ParentStaffEntry);
  374. const pitch: Pitch = new Pitch(baseNote.Pitch.FundamentalNote, baseNote.Pitch.Octave, baseNote.Pitch.Accidental);
  375. const note: Note = new Note(voiceEntry, undefined, length, pitch, baseNote.SourceMeasure);
  376. voiceEntry.Notes.push(note);
  377. voiceEntries.push(voiceEntry);
  378. }
  379. private createAlteratedVoiceEntry(
  380. currentTimestamp: Fraction, length: Fraction, baseVoice: Voice, sourceMeasure: SourceMeasure, higherPitch: Pitch,
  381. alteration: AccidentalEnum, voiceEntries: VoiceEntry[]
  382. ): void {
  383. const voiceEntry: VoiceEntry = new VoiceEntry(currentTimestamp, baseVoice, undefined);
  384. const pitch: Pitch = new Pitch(higherPitch.FundamentalNote, higherPitch.Octave, alteration);
  385. const note: Note = new Note(voiceEntry, undefined, length, pitch, sourceMeasure);
  386. voiceEntry.Notes.push(note);
  387. voiceEntries.push(voiceEntry);
  388. }
  389. }
  390. export enum ArticulationEnum {
  391. accent,
  392. strongaccent,
  393. marcatoup,
  394. marcatodown,
  395. invertedstrongaccent,
  396. staccato,
  397. staccatissimo,
  398. spiccato,
  399. tenuto,
  400. fermata,
  401. invertedfermata,
  402. breathmark,
  403. caesura,
  404. lefthandpizzicato,
  405. naturalharmonic,
  406. snappizzicato,
  407. upbow,
  408. downbow,
  409. scoop,
  410. plop,
  411. doit,
  412. falloff,
  413. stress,
  414. unstress,
  415. detachedlegato,
  416. otherarticulation,
  417. bend
  418. }
  419. export enum StemDirectionType {
  420. Undefined = -1,
  421. Up = 0,
  422. Down = 1,
  423. None = 2,
  424. Double = 3
  425. }