src/MusicalScore/VoiceData/VoiceEntry.ts
import {Fraction} from "../../Common/DataObjects/Fraction";
import {Voice} from "./Voice";
import {SourceStaffEntry} from "./SourceStaffEntry";
import {Note} from "./Note";
import {Pitch} from "../../Common/DataObjects/Pitch";
import {LyricsEntry} from "./Lyrics/LyricsEntry";
import {TechnicalInstruction} from "./Instructions/TechnicalInstruction";
import {OrnamentContainer} from "./OrnamentContainer";
import {KeyInstruction} from "./Instructions/KeyInstruction";
import {OrnamentEnum} from "./OrnamentContainer";
import {AccidentalEnum} from "../../Common/DataObjects/Pitch";
import { Dictionary } from "typescript-collections";
import {Arpeggio} from "./Arpeggio";
import { SourceMeasure } from "./SourceMeasure";
import { Articulation } from "./Articulation";
/**
* A [[VoiceEntry]] contains the notes in a voice at a timestamp.
*/
export class VoiceEntry {
/**
*
* @param timestamp The relative timestamp within the source measure.
* @param parentVoice
* @param parentSourceStaffEntry
* @param isGrace States whether the VoiceEntry has (only) grace notes.
* @param graceNoteSlash States whether the grace note(s) have a slash (Acciaccatura, played before the beat)
*/
constructor(timestamp: Fraction, parentVoice: Voice, parentSourceStaffEntry: SourceStaffEntry,
isGrace: boolean = false, graceNoteSlash: boolean = false, graceSlur: boolean = false) {
this.timestamp = timestamp;
this.parentVoice = parentVoice;
this.parentSourceStaffEntry = parentSourceStaffEntry;
this.isGrace = isGrace;
this.graceAfterMainNote = false;
this.graceNoteSlash = graceNoteSlash;
this.graceSlur = graceSlur;
// add currentVoiceEntry to staff entry:
if (parentSourceStaffEntry !== undefined) {
const list: VoiceEntry[] = parentSourceStaffEntry.VoiceEntries;
if (list.indexOf(this) === -1) {
list.push(this);
}
}
}
private parentVoice: Voice;
private parentSourceStaffEntry: SourceStaffEntry;
private timestamp: Fraction;
private notes: Note[] = [];
private isGrace: boolean;
/** States whether the grace notes come after a main note (at end of measure). */
private graceAfterMainNote: boolean;
private graceNoteSlash: boolean;
private graceSlur: boolean; // TODO grace slur system could be refined to be non-binary
private articulations: Articulation[] = [];
private technicalInstructions: TechnicalInstruction[] = [];
private lyricsEntries: Dictionary<string, LyricsEntry> = new Dictionary<string, LyricsEntry>();
/** The Arpeggio consisting of this VoiceEntry's notes. Undefined if no arpeggio exists. */
private arpeggio: Arpeggio;
private ornamentContainer: OrnamentContainer;
private wantedStemDirection: StemDirectionType = StemDirectionType.Undefined;
/** Stem direction specified in the xml stem element. */
private stemDirectionXml: StemDirectionType = StemDirectionType.Undefined;
private stemDirection: StemDirectionType = StemDirectionType.Undefined;
/** Color of the stem given in XML. RGB Hexadecimal, like #00FF00. */
private stemColorXml: string;
/** Color of the stem currently set. RGB Hexadecimal, like #00FF00. */
private stemColor: string;
public get ParentSourceStaffEntry(): SourceStaffEntry {
return this.parentSourceStaffEntry;
}
public get ParentVoice(): Voice {
return this.parentVoice;
}
public get Timestamp(): Fraction {
return this.timestamp;
}
public set Timestamp(value: Fraction) {
this.timestamp = value;
}
public get Notes(): Note[] {
return this.notes;
}
public get IsGrace(): boolean {
return this.isGrace;
}
public set IsGrace(value: boolean) {
this.isGrace = value;
}
public get GraceAfterMainNote(): boolean {
return this.graceAfterMainNote;
}
public set GraceAfterMainNote(value: boolean) {
this.graceAfterMainNote = value;
}
public get GraceNoteSlash(): boolean {
return this.graceNoteSlash;
}
public set GraceNoteSlash(value: boolean) {
this.graceNoteSlash = value;
}
public get GraceSlur(): boolean {
return this.graceSlur;
}
public set GraceSlur(value: boolean) {
this.graceSlur = value;
}
public get Articulations(): Articulation[] {
return this.articulations;
}
public set Articulations(value: Articulation[]) {
this.articulations = value;
}
public get TechnicalInstructions(): TechnicalInstruction[] {
return this.technicalInstructions;
}
public get LyricsEntries(): Dictionary<string, LyricsEntry> {
return this.lyricsEntries;
}
public get Arpeggio(): Arpeggio {
return this.arpeggio;
}
public set Arpeggio(value: Arpeggio) {
this.arpeggio = value;
}
public get OrnamentContainer(): OrnamentContainer {
return this.ornamentContainer;
}
public set OrnamentContainer(value: OrnamentContainer) {
this.ornamentContainer = value;
}
// WantedStemDirection provides the stem direction to VexFlow in case of more than 1 voice
// for optimal graphical appearance
public set WantedStemDirection(value: StemDirectionType) {
this.wantedStemDirection = value;
}
public get WantedStemDirection(): StemDirectionType {
return this.wantedStemDirection;
}
public set StemDirectionXml(value: StemDirectionType) {
this.stemDirectionXml = value;
}
public get StemDirectionXml(): StemDirectionType {
return this.stemDirectionXml;
}
// StemDirection holds the actual value of the stem
public set StemDirection(value: StemDirectionType) {
this.stemDirection = value;
}
public get StemDirection(): StemDirectionType {
return this.stemDirection;
}
public get StemColorXml(): string {
return this.stemColorXml;
}
public set StemColorXml(value: string) {
this.stemColorXml = value;
}
public get StemColor(): string {
return this.stemColor;
}
public set StemColor(value: string) {
this.stemColor = value;
}
public hasArticulation(articulation: Articulation): boolean {
for (const existingArticulation of this.articulations) {
if (existingArticulation.Equals(articulation)) {
return true;
}
}
return false;
}
public static isSupportedArticulation(articulation: ArticulationEnum): boolean {
switch (articulation) {
case ArticulationEnum.accent:
case ArticulationEnum.strongaccent:
case ArticulationEnum.softaccent:
case ArticulationEnum.invertedstrongaccent:
case ArticulationEnum.staccato:
case ArticulationEnum.staccatissimo:
case ArticulationEnum.spiccato:
case ArticulationEnum.tenuto:
case ArticulationEnum.fermata:
case ArticulationEnum.invertedfermata:
case ArticulationEnum.breathmark:
case ArticulationEnum.caesura:
case ArticulationEnum.lefthandpizzicato:
case ArticulationEnum.naturalharmonic:
case ArticulationEnum.snappizzicato:
case ArticulationEnum.upbow:
case ArticulationEnum.downbow:
case ArticulationEnum.bend:
return true;
default:
return false;
}
}
public hasTie(): boolean {
for (let idx: number = 0, len: number = this.Notes.length; idx < len; ++idx) {
const note: Note = this.Notes[idx];
if (note.NoteTie) { return true; }
}
return false;
}
public hasSlur(): boolean {
for (let idx: number = 0, len: number = this.Notes.length; idx < len; ++idx) {
const note: Note = this.Notes[idx];
if (note.NoteSlurs.length > 0) { return true; }
}
return false;
}
public isStaccato(): boolean {
for (const articulation of this.Articulations) {
if (articulation.articulationEnum === ArticulationEnum.staccato) {
return true;
}
}
return false;
}
public isAccent(): boolean {
for (const articulation of this.Articulations) {
if (articulation.articulationEnum === ArticulationEnum.accent || articulation.articulationEnum === ArticulationEnum.strongaccent) {
return true;
}
}
return false;
}
public getVerseNumberForLyricEntry(lyricsEntry: LyricsEntry): string {
let verseNumber: string = "1";
this.lyricsEntries.forEach((key: string, value: LyricsEntry): void => {
if (lyricsEntry === value) {
verseNumber = key;
}
});
return verseNumber;
}
//public createVoiceEntriesForOrnament(activeKey: KeyInstruction): VoiceEntry[] {
// return this.createVoiceEntriesForOrnament(this, activeKey);
//}
public createVoiceEntriesForOrnament(voiceEntryWithOrnament: VoiceEntry, activeKey: KeyInstruction): VoiceEntry[] {
if (!voiceEntryWithOrnament) {
voiceEntryWithOrnament = this;
}
const voiceEntries: VoiceEntry[] = [];
if (!voiceEntryWithOrnament.ornamentContainer) {
return;
}
const baseNote: Note = this.notes[0];
const baselength: Fraction = baseNote.Length;
const baseVoice: Voice = voiceEntryWithOrnament.ParentVoice;
const baseTimestamp: Fraction = voiceEntryWithOrnament.Timestamp;
let currentTimestamp: Fraction = Fraction.createFromFraction(baseTimestamp);
//let length: Fraction;
switch (voiceEntryWithOrnament.ornamentContainer.GetOrnament) {
case OrnamentEnum.Trill: {
const length: Fraction = new Fraction(baselength.Numerator, baselength.Denominator * 8);
const higherPitch: Pitch = baseNote.Pitch.getTransposedPitch(1);
let alteration: AccidentalEnum = activeKey.getAlterationForPitch(higherPitch);
if (voiceEntryWithOrnament.OrnamentContainer.AccidentalAbove !== AccidentalEnum.NONE) {
alteration = voiceEntryWithOrnament.ornamentContainer.AccidentalAbove;
}
for (let i: number = 0; i < 8; i++) {
currentTimestamp = Fraction.plus(baseTimestamp, new Fraction(i * length.Numerator, length.Denominator));
if ((i % 2) === 0) {
this.createBaseVoiceEntry(currentTimestamp, length, baseVoice, baseNote, voiceEntries);
} else {
this.createAlteratedVoiceEntry(currentTimestamp, length, baseVoice, baseNote.SourceMeasure, higherPitch, alteration, voiceEntries);
}
}
break;
}
case OrnamentEnum.Turn: {
const length: Fraction = new Fraction(baselength.Numerator, baselength.Denominator * 4);
const lowerPitch: Pitch = baseNote.Pitch.getTransposedPitch(-1);
const lowerAlteration: AccidentalEnum = activeKey.getAlterationForPitch(lowerPitch);
const higherPitch: Pitch = baseNote.Pitch.getTransposedPitch(1);
const higherAlteration: AccidentalEnum = activeKey.getAlterationForPitch(higherPitch);
this.createAlteratedVoiceEntry(
currentTimestamp, length, baseVoice, baseNote.SourceMeasure, higherPitch, higherAlteration, voiceEntries
);
currentTimestamp.Add(length);
this.createBaseVoiceEntry(currentTimestamp, length, baseVoice, baseNote, voiceEntries);
currentTimestamp.Add(length);
this.createAlteratedVoiceEntry(
currentTimestamp, length, baseVoice, baseNote.SourceMeasure, lowerPitch, lowerAlteration, voiceEntries
);
currentTimestamp.Add(length);
this.createBaseVoiceEntry(currentTimestamp, length, baseVoice, baseNote, voiceEntries);
break;
}
case OrnamentEnum.InvertedTurn: {
const length: Fraction = new Fraction(baselength.Numerator, baselength.Denominator * 4);
const lowerPitch: Pitch = baseNote.Pitch.getTransposedPitch(-1);
const lowerAlteration: AccidentalEnum = activeKey.getAlterationForPitch(lowerPitch);
const higherPitch: Pitch = baseNote.Pitch.getTransposedPitch(1);
const higherAlteration: AccidentalEnum = activeKey.getAlterationForPitch(higherPitch);
this.createAlteratedVoiceEntry(
currentTimestamp, length, baseVoice, baseNote.SourceMeasure, lowerPitch, lowerAlteration, voiceEntries
);
currentTimestamp.Add(length);
this.createBaseVoiceEntry(currentTimestamp, length, baseVoice, baseNote, voiceEntries);
currentTimestamp.Add(length);
this.createAlteratedVoiceEntry(
currentTimestamp, length, baseVoice, baseNote.SourceMeasure, higherPitch, higherAlteration, voiceEntries
);
currentTimestamp.Add(length);
this.createBaseVoiceEntry(currentTimestamp, length, baseVoice, baseNote, voiceEntries);
break;
}
case OrnamentEnum.DelayedTurn: {
const length: Fraction = new Fraction(baselength.Numerator, baselength.Denominator * 2);
const lowerPitch: Pitch = baseNote.Pitch.getTransposedPitch(-1);
const lowerAlteration: AccidentalEnum = activeKey.getAlterationForPitch(lowerPitch);
const higherPitch: Pitch = baseNote.Pitch.getTransposedPitch(1);
const higherAlteration: AccidentalEnum = activeKey.getAlterationForPitch(higherPitch);
this.createBaseVoiceEntry(currentTimestamp, length, baseVoice, baseNote, voiceEntries);
currentTimestamp = Fraction.plus(baseTimestamp, length);
length.Denominator = baselength.Denominator * 8;
this.createAlteratedVoiceEntry(currentTimestamp, length, baseVoice, baseNote.SourceMeasure, higherPitch, higherAlteration, voiceEntries);
currentTimestamp.Add(length);
this.createBaseVoiceEntry(currentTimestamp, length, baseVoice, baseNote, voiceEntries);
currentTimestamp.Add(length);
this.createAlteratedVoiceEntry(currentTimestamp, length, baseVoice, baseNote.SourceMeasure, lowerPitch, lowerAlteration, voiceEntries);
currentTimestamp.Add(length);
this.createBaseVoiceEntry(currentTimestamp, length, baseVoice, baseNote, voiceEntries);
break;
}
case OrnamentEnum.DelayedInvertedTurn: {
const length: Fraction = new Fraction(baselength.Numerator, baselength.Denominator * 2);
const lowerPitch: Pitch = baseNote.Pitch.getTransposedPitch(-1);
const lowerAlteration: AccidentalEnum = activeKey.getAlterationForPitch(lowerPitch);
const higherPitch: Pitch = baseNote.Pitch.getTransposedPitch(1);
const higherAlteration: AccidentalEnum = activeKey.getAlterationForPitch(higherPitch);
this.createBaseVoiceEntry(currentTimestamp, length, baseVoice, baseNote, voiceEntries);
currentTimestamp = Fraction.plus(baseTimestamp, length);
length.Denominator = baselength.Denominator * 8;
this.createAlteratedVoiceEntry(currentTimestamp, length, baseVoice, baseNote.SourceMeasure, lowerPitch, lowerAlteration, voiceEntries);
currentTimestamp.Add(length);
this.createBaseVoiceEntry(currentTimestamp, length, baseVoice, baseNote, voiceEntries);
currentTimestamp.Add(length);
this.createAlteratedVoiceEntry(currentTimestamp, length, baseVoice, baseNote.SourceMeasure, higherPitch, higherAlteration, voiceEntries);
currentTimestamp.Add(length);
this.createBaseVoiceEntry(currentTimestamp, length, baseVoice, baseNote, voiceEntries);
break;
}
case OrnamentEnum.Mordent: {
const length: Fraction = new Fraction(baselength.Numerator, baselength.Denominator * 4);
const higherPitch: Pitch = baseNote.Pitch.getTransposedPitch(1);
const alteration: AccidentalEnum = activeKey.getAlterationForPitch(higherPitch);
this.createBaseVoiceEntry(currentTimestamp, length, baseVoice, baseNote, voiceEntries);
currentTimestamp.Add(length);
this.createAlteratedVoiceEntry(currentTimestamp, length, baseVoice, baseNote.SourceMeasure, higherPitch, alteration, voiceEntries);
length.Denominator = baselength.Denominator * 2;
currentTimestamp = Fraction.plus(baseTimestamp, length);
this.createBaseVoiceEntry(currentTimestamp, length, baseVoice, baseNote, voiceEntries);
break;
}
case OrnamentEnum.InvertedMordent: {
const length: Fraction = new Fraction(baselength.Numerator, baselength.Denominator * 4);
const lowerPitch: Pitch = baseNote.Pitch.getTransposedPitch(-1);
const alteration: AccidentalEnum = activeKey.getAlterationForPitch(lowerPitch);
this.createBaseVoiceEntry(currentTimestamp, length, baseVoice, baseNote, voiceEntries);
currentTimestamp.Add(length);
this.createAlteratedVoiceEntry(currentTimestamp, length, baseVoice, baseNote.SourceMeasure, lowerPitch, alteration, voiceEntries);
length.Denominator = baselength.Denominator * 2;
currentTimestamp = Fraction.plus(baseTimestamp, length);
this.createBaseVoiceEntry(currentTimestamp, length, baseVoice, baseNote, voiceEntries);
break;
}
default:
throw new RangeError();
}
return voiceEntries;
}
private createBaseVoiceEntry(
currentTimestamp: Fraction, length: Fraction, baseVoice: Voice, baseNote: Note, voiceEntries: VoiceEntry[]
): void {
const voiceEntry: VoiceEntry = new VoiceEntry(currentTimestamp, baseVoice, baseNote.ParentStaffEntry);
const pitch: Pitch = new Pitch(baseNote.Pitch.FundamentalNote, baseNote.Pitch.Octave, baseNote.Pitch.Accidental);
const note: Note = new Note(voiceEntry, undefined, length, pitch, baseNote.SourceMeasure);
voiceEntry.Notes.push(note);
voiceEntries.push(voiceEntry);
}
private createAlteratedVoiceEntry(
currentTimestamp: Fraction, length: Fraction, baseVoice: Voice, sourceMeasure: SourceMeasure, higherPitch: Pitch,
alteration: AccidentalEnum, voiceEntries: VoiceEntry[]
): void {
const voiceEntry: VoiceEntry = new VoiceEntry(currentTimestamp, baseVoice, undefined);
const pitch: Pitch = new Pitch(higherPitch.FundamentalNote, higherPitch.Octave, alteration);
const note: Note = new Note(voiceEntry, undefined, length, pitch, sourceMeasure);
voiceEntry.Notes.push(note);
voiceEntries.push(voiceEntry);
}
}
export enum ArticulationEnum {
accent,
strongaccent,
softaccent,
marcatoup,
marcatodown,
invertedstrongaccent,
staccato,
staccatissimo,
spiccato,
tenuto,
fermata,
invertedfermata,
breathmark,
caesura,
lefthandpizzicato,
naturalharmonic,
snappizzicato,
upbow,
downbow,
scoop,
plop,
doit,
falloff,
stress,
unstress,
detachedlegato,
otherarticulation,
bend
}
export enum StemDirectionType {
Undefined = -1,
Up = 0,
Down = 1,
None = 2,
Double = 3
}