VoiceEntry.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. import { Fraction } from "../../Common/DataObjects/Fraction";
  2. import {Voice} from "./Voice";
  3. import {SourceStaffEntry} from "./SourceStaffEntry";
  4. import {Note} from "./Note";
  5. import {LyricsEntry} from "./Lyrics/LyricsEntry";
  6. import {TechnicalInstruction} from "./Instructions/TechnicalInstruction";
  7. import {OrnamentContainer} from "./OrnamentContainer";
  8. import { Dictionary } from "typescript-collections";
  9. import {Arpeggio} from "./Arpeggio";
  10. import { PlaybackEntry } from "../Playback/PlaybackEntry";
  11. import { Articulation } from "./Articulation";
  12. import { PlaybackNote } from "../Playback/PlaybackNote";
  13. /**
  14. * A [[VoiceEntry]] contains the notes in a voice at a timestamp.
  15. */
  16. export class VoiceEntry {
  17. /**
  18. *
  19. * @param timestamp The relative timestamp within the source measure.
  20. * @param parentVoice
  21. * @param parentSourceStaffEntry
  22. * @param isGrace States whether the VoiceEntry has (only) grace notes.
  23. * @param graceNoteSlash States whether the grace note(s) have a slash (Acciaccatura, played before the beat)
  24. */
  25. constructor(timestamp: Fraction, parentVoice: Voice, parentSourceStaffEntry: SourceStaffEntry, addToStaffEntry: boolean = true,
  26. isGrace: boolean = false, graceNoteSlash: boolean = false, graceSlur: boolean = false) {
  27. this.timestamp = timestamp;
  28. this.parentVoice = parentVoice;
  29. this.parentSourceStaffEntry = parentSourceStaffEntry;
  30. this.isGrace = isGrace;
  31. this.graceAfterMainNote = false;
  32. this.graceNoteSlash = graceNoteSlash;
  33. this.graceSlur = graceSlur;
  34. if (!isGrace) {
  35. parentVoice.VoiceEntries.push(this);
  36. }
  37. // add currentVoiceEntry to staff entry:
  38. if (addToStaffEntry && parentSourceStaffEntry !== undefined) {
  39. const list: VoiceEntry[] = parentSourceStaffEntry.VoiceEntries;
  40. if (list.indexOf(this) === -1) {
  41. list.push(this);
  42. }
  43. }
  44. // ToDo: at this moment there are no notes added to the voice entry
  45. this.mainPlaybackEntry = new PlaybackEntry(this);
  46. this.PlaybackEntries.push(this.mainPlaybackEntry);
  47. }
  48. private parentVoice: Voice;
  49. private parentSourceStaffEntry: SourceStaffEntry;
  50. private timestamp: Fraction;
  51. private notes: Note[] = [];
  52. private graceVoiceEntriesBefore: VoiceEntry[] = [];
  53. private graceVoiceEntriesAfter: VoiceEntry[] = [];
  54. private isGrace: boolean;
  55. /** States whether the grace notes come after a main note (at end of measure). */
  56. private graceAfterMainNote: boolean;
  57. private graceNoteSlash: boolean;
  58. private graceSlur: boolean; // TODO grace slur system could be refined to be non-binary
  59. private articulations: Articulation[] = [];
  60. private playbackEntries: PlaybackEntry[] = [];
  61. private fermata: Articulation;
  62. private technicalInstructions: TechnicalInstruction[] = [];
  63. private lyricsEntries: Dictionary<number, LyricsEntry> = new Dictionary<number, LyricsEntry>();
  64. /** The Arpeggio consisting of this VoiceEntry's notes. Undefined if no arpeggio exists. */
  65. private arpeggio: Arpeggio;
  66. private ornamentContainer: OrnamentContainer;
  67. private wantedStemDirection: StemDirectionType = StemDirectionType.Undefined;
  68. /** Stem direction specified in the xml stem element. */
  69. private stemDirectionXml: StemDirectionType = StemDirectionType.Undefined;
  70. private stemDirection: StemDirectionType = StemDirectionType.Undefined;
  71. /** Color of the stem given in XML. RGB Hexadecimal, like #00FF00. */
  72. private stemColorXml: string;
  73. /** Color of the stem currently set. RGB Hexadecimal, like #00FF00. */
  74. private stemColor: string;
  75. private mainPlaybackEntry: PlaybackEntry;
  76. private volumeModifier: Articulation;
  77. private durationModifier: Articulation;
  78. public get ParentSourceStaffEntry(): SourceStaffEntry {
  79. return this.parentSourceStaffEntry;
  80. }
  81. public get ParentVoice(): Voice {
  82. return this.parentVoice;
  83. }
  84. public get Timestamp(): Fraction {
  85. return this.timestamp;
  86. }
  87. public set Timestamp(value: Fraction) {
  88. this.timestamp = value;
  89. }
  90. public get Notes(): Note[] {
  91. return this.notes;
  92. }
  93. public addNote(note: Note): void {
  94. this.notes.push(note);
  95. // only add playback notes when these are no rests and are not tied notes (besides the first note of a tie)
  96. if (!note.isRest() &&
  97. (note.NoteTie === undefined || note.NoteTie.StartNote === note)) {
  98. this.MainPlaybackEntry.Notes.push(new PlaybackNote(this.MainPlaybackEntry, note));
  99. }
  100. }
  101. public get GraceVoiceEntriesBefore(): VoiceEntry[] {
  102. return this.graceVoiceEntriesBefore;
  103. }
  104. public set GraceVoiceEntriesBefore(value: VoiceEntry[] ) {
  105. this.graceVoiceEntriesBefore = value;
  106. for (const ve of this.graceVoiceEntriesBefore) {
  107. ve.parentSourceStaffEntry = this.ParentSourceStaffEntry;
  108. }
  109. }
  110. public get GraceVoiceEntriesAfter(): VoiceEntry[] {
  111. return this.graceVoiceEntriesAfter;
  112. }
  113. public set GraceVoiceEntriesAfter(value: VoiceEntry[] ) {
  114. this.graceVoiceEntriesAfter = value;
  115. for (const ve of this.graceVoiceEntriesAfter) {
  116. ve.parentSourceStaffEntry = this.ParentSourceStaffEntry;
  117. }
  118. }
  119. public get IsGrace(): boolean {
  120. return this.isGrace;
  121. }
  122. public set IsGrace(value: boolean) {
  123. this.isGrace = value;
  124. }
  125. public get GraceAfterMainNote(): boolean {
  126. return this.graceAfterMainNote;
  127. }
  128. public set GraceAfterMainNote(value: boolean) {
  129. this.graceAfterMainNote = value;
  130. }
  131. public get GraceNoteSlash(): boolean {
  132. return this.graceNoteSlash;
  133. }
  134. public set GraceNoteSlash(value: boolean) {
  135. this.graceNoteSlash = value;
  136. }
  137. public get GraceSlur(): boolean {
  138. return this.graceSlur;
  139. }
  140. public set GraceSlur(value: boolean) {
  141. this.graceSlur = value;
  142. }
  143. public get Articulations(): Articulation[] {
  144. return this.articulations;
  145. }
  146. /** Stores all playback entries (e.g. extra grace and ornament entries).
  147. * Also holds the main playback entry.
  148. * The entries are sorted in ascending timestamp.
  149. */
  150. public get PlaybackEntries(): PlaybackEntry[] {
  151. return this.playbackEntries;
  152. }
  153. public get Fermata(): Articulation {
  154. return this.fermata;
  155. }
  156. public get MainPlaybackEntry(): PlaybackEntry {
  157. return this.mainPlaybackEntry;
  158. }
  159. public set MainPlaybackEntry(value: PlaybackEntry) {
  160. this.mainPlaybackEntry = value;
  161. }
  162. public removeMainPlaybackEntry(): void {
  163. if (this.mainPlaybackEntry !== undefined) {
  164. this.removePlaybackEntry(this.mainPlaybackEntry);
  165. }
  166. }
  167. public removePlaybackEntry(value: PlaybackEntry): void {
  168. if (this.mainPlaybackEntry === value) {
  169. this.mainPlaybackEntry = undefined;
  170. }
  171. const index: number = this.playbackEntries.indexOf(value);
  172. if (index > -1) {
  173. this.playbackEntries.splice(index, 1);
  174. }
  175. }
  176. public get TechnicalInstructions(): TechnicalInstruction[] {
  177. return this.technicalInstructions;
  178. }
  179. public get LyricsEntries(): Dictionary<number, LyricsEntry> {
  180. return this.lyricsEntries;
  181. }
  182. public get Arpeggio(): Arpeggio {
  183. return this.arpeggio;
  184. }
  185. public set Arpeggio(value: Arpeggio) {
  186. this.arpeggio = value;
  187. }
  188. public get OrnamentContainer(): OrnamentContainer {
  189. return this.ornamentContainer;
  190. }
  191. public set OrnamentContainer(value: OrnamentContainer) {
  192. this.ornamentContainer = value;
  193. }
  194. // WantedStemDirection provides the stem direction to VexFlow in case of more than 1 voice
  195. // for optimal graphical appearance
  196. public set WantedStemDirection(value: StemDirectionType) {
  197. this.wantedStemDirection = value;
  198. }
  199. public get WantedStemDirection(): StemDirectionType {
  200. return this.wantedStemDirection;
  201. }
  202. public set StemDirectionXml(value: StemDirectionType) {
  203. this.stemDirectionXml = value;
  204. }
  205. public get StemDirectionXml(): StemDirectionType {
  206. return this.stemDirectionXml;
  207. }
  208. // StemDirection holds the actual value of the stem
  209. public set StemDirection(value: StemDirectionType) {
  210. this.stemDirection = value;
  211. }
  212. public get StemDirection(): StemDirectionType {
  213. return this.stemDirection;
  214. }
  215. public get StemColorXml(): string {
  216. return this.stemColorXml;
  217. }
  218. public set StemColorXml(value: string) {
  219. this.stemColorXml = value;
  220. }
  221. public get StemColor(): string {
  222. return this.stemColor;
  223. }
  224. public set StemColor(value: string) {
  225. this.stemColor = value;
  226. }
  227. public get VolumeModifier(): Articulation {
  228. return this.volumeModifier;
  229. }
  230. public set VolumeModifier(value: Articulation) {
  231. this.volumeModifier = value;
  232. }
  233. public get DurationModifier(): Articulation {
  234. return this.durationModifier;
  235. }
  236. public set DurationModifier(value: Articulation) {
  237. this.durationModifier = value;
  238. }
  239. public hasArticulation(articulation: Articulation): boolean {
  240. for (const existingArticulation of this.articulations) {
  241. if (existingArticulation.Equals(articulation)) {
  242. return true;
  243. }
  244. }
  245. return false;
  246. }
  247. public static isSupportedArticulation(articulation: ArticulationEnum): boolean {
  248. switch (articulation) {
  249. case ArticulationEnum.accent:
  250. case ArticulationEnum.strongaccent:
  251. case ArticulationEnum.softaccent:
  252. case ArticulationEnum.invertedstrongaccent:
  253. case ArticulationEnum.staccato:
  254. case ArticulationEnum.staccatissimo:
  255. case ArticulationEnum.spiccato:
  256. case ArticulationEnum.tenuto:
  257. case ArticulationEnum.fermata:
  258. case ArticulationEnum.invertedfermata:
  259. case ArticulationEnum.breathmark:
  260. case ArticulationEnum.caesura:
  261. case ArticulationEnum.lefthandpizzicato:
  262. case ArticulationEnum.naturalharmonic:
  263. case ArticulationEnum.snappizzicato:
  264. case ArticulationEnum.upbow:
  265. case ArticulationEnum.downbow:
  266. case ArticulationEnum.bend:
  267. return true;
  268. default:
  269. return false;
  270. }
  271. }
  272. public hasTie(): boolean {
  273. for (let idx: number = 0, len: number = this.Notes.length; idx < len; ++idx) {
  274. const note: Note = this.Notes[idx];
  275. if (note.NoteTie) { return true; }
  276. }
  277. return false;
  278. }
  279. public hasSlur(): boolean {
  280. for (let idx: number = 0, len: number = this.Notes.length; idx < len; ++idx) {
  281. const note: Note = this.Notes[idx];
  282. if (note.NoteSlurs.length > 0) { return true; }
  283. }
  284. return false;
  285. }
  286. public isStaccato(): boolean {
  287. for (const articulation of this.Articulations) {
  288. if (articulation.articulationEnum === ArticulationEnum.staccato) {
  289. return true;
  290. }
  291. }
  292. return false;
  293. }
  294. public isAccent(): boolean {
  295. for (const articulation of this.Articulations) {
  296. if (articulation.articulationEnum === ArticulationEnum.accent || articulation.articulationEnum === ArticulationEnum.strongaccent) {
  297. return true;
  298. }
  299. }
  300. return false;
  301. }
  302. public getVerseNumberForLyricEntry(lyricsEntry: LyricsEntry): number {
  303. let verseNumber: number = 1;
  304. this.lyricsEntries.forEach((key: number, value: LyricsEntry): void => {
  305. if (lyricsEntry === value) {
  306. verseNumber = key;
  307. }
  308. });
  309. return verseNumber;
  310. }
  311. }
  312. export enum ArticulationEnum {
  313. accent,
  314. strongaccent,
  315. softaccent,
  316. marcatoup,
  317. marcatodown,
  318. invertedstrongaccent,
  319. staccato,
  320. staccatissimo,
  321. spiccato,
  322. tenuto,
  323. fermata,
  324. invertedfermata,
  325. breathmark,
  326. caesura,
  327. lefthandpizzicato,
  328. naturalharmonic,
  329. snappizzicato,
  330. upbow,
  331. downbow,
  332. scoop,
  333. plop,
  334. doit,
  335. falloff,
  336. stress,
  337. unstress,
  338. detachedlegato,
  339. otherarticulation,
  340. bend
  341. }
  342. export enum StemDirectionType {
  343. Undefined = -1,
  344. Up = 0,
  345. Down = 1,
  346. None = 2,
  347. Double = 3
  348. }