src/MusicalScore/VoiceData/SourceStaffEntry.ts
import {Fraction} from "../../Common/DataObjects/Fraction";
import {VerticalSourceStaffEntryContainer} from "./VerticalSourceStaffEntryContainer";
import {Staff} from "./Staff";
import {AbstractNotationInstruction} from "./Instructions/AbstractNotationInstruction";
import {VoiceEntry} from "./VoiceEntry";
import {Note} from "./Note";
import {StaffEntryLink} from "./StaffEntryLink";
import {ChordSymbolContainer} from "./ChordSymbolContainer";
import {ClefInstruction} from "./Instructions/ClefInstruction";
import {KeyInstruction} from "./Instructions/KeyInstruction";
import {RhythmInstruction} from "./Instructions/RhythmInstruction";
/**
* A [[SourceStaffEntry]] is a container spanning all the [[VoiceEntry]]s at one timestamp for one [[StaffLine]].
*/
export class SourceStaffEntry {
constructor(verticalContainerParent: VerticalSourceStaffEntryContainer, parentStaff: Staff) {
this.verticalContainerParent = verticalContainerParent;
this.parentStaff = parentStaff;
}
private parentStaff: Staff;
private verticalContainerParent: VerticalSourceStaffEntryContainer;
private voiceEntries: VoiceEntry[] = [];
private staffEntryLink: StaffEntryLink;
private instructions: AbstractNotationInstruction[] = [];
private chordSymbolContainers: ChordSymbolContainer[] = [];
public get ParentStaff(): Staff {
return this.parentStaff;
}
public get VerticalContainerParent(): VerticalSourceStaffEntryContainer {
return this.verticalContainerParent;
}
public get Timestamp(): Fraction {
if (this.VerticalContainerParent) {
return this.VerticalContainerParent.Timestamp;
}
return undefined;
}
public get AbsoluteTimestamp(): Fraction {
if (this.VerticalContainerParent) {
return Fraction.plus(this.VerticalContainerParent.ParentMeasure.AbsoluteTimestamp, this.VerticalContainerParent.Timestamp);
}
return undefined;
}
public get VoiceEntries(): VoiceEntry[] {
return this.voiceEntries;
}
public set VoiceEntries(value: VoiceEntry[]) {
this.voiceEntries = value;
}
public get Link(): StaffEntryLink {
return this.staffEntryLink;
}
public set Link(value: StaffEntryLink) {
this.staffEntryLink = value;
}
public get Instructions(): AbstractNotationInstruction[] {
return this.instructions;
}
public set Instructions(value: AbstractNotationInstruction[]) {
this.instructions = value;
}
public get ChordContainers(): ChordSymbolContainer[] {
return this.chordSymbolContainers;
}
public set ChordContainers(value: ChordSymbolContainer[]) {
this.chordSymbolContainers = value;
}
// public removeAllInstructionsOfType(type: AbstractNotationInstruction): number {
// let i: number = 0;
// let ret: number = 0;
// while (i < this.instructions.length) {
// let instruction: AbstractNotationInstruction = this.instructions[i];
// if (instruction instanceof type) {
// this.instructions.splice(i, 1);
// ret++;
// } else {
// i++;
// }
// }
// return ret;
// }
//
// public removeFirstInstructionOfType(type: AbstractNotationInstruction): boolean {
// for (let i: number = 0; i < this.instructions.length; i++) {
// if (this.instructions[i] instanceof type) {
// this.instructions.splice(i, 1);
// return true;
// }
// }
// return false;
// }
public removeAllInstructionsOfTypeClefInstruction(): number {
let i: number = 0;
let ret: number = 0;
while (i < this.instructions.length) {
if (this.instructions[i] instanceof ClefInstruction) {
this.instructions.splice(i, 1);
ret++;
} else {
i++;
}
}
return ret;
}
/**
* Similar to RemoveAllInstructionsOfType but faster,
* because it stops searching when the first instruction of the given type is found.
* @returns {boolean}
*/
public removeFirstInstructionOfTypeClefInstruction(): boolean {
for (let i: number = 0; i < this.instructions.length; i++) {
if (this.instructions[i] instanceof ClefInstruction) {
this.instructions.splice(i, 1);
return true;
}
}
return false;
}
public removeAllInstructionsOfTypeKeyInstruction(): number {
let i: number = 0;
let ret: number = 0;
while (i < this.instructions.length) {
if (this.instructions[i] instanceof KeyInstruction) {
this.instructions.splice(i, 1);
ret++;
} else {
i++;
}
}
return ret;
}
/**
* Similar to RemoveAllInstructionsOfType but faster,
* because it stops searching when the first instruction of the given type is found.
* @returns {boolean}
*/
public removeFirstInstructionOfTypeKeyInstruction(): boolean {
for (let i: number = 0; i < this.instructions.length; i++) {
if (this.instructions[i] instanceof KeyInstruction) {
this.instructions.splice(i, 1);
return true;
}
}
return false;
}
public removeAllInstructionsOfTypeRhythmInstruction(): number {
let i: number = 0;
let ret: number = 0;
while (i < this.instructions.length) {
if (this.instructions[i] instanceof RhythmInstruction) {
this.instructions.splice(i, 1);
ret++;
} else {
i++;
}
}
return ret;
}
public removeFirstInstructionOfTypeRhythmInstruction(): boolean {
for (let i: number = 0; i < this.instructions.length; i++) {
if (this.instructions[i] instanceof RhythmInstruction) {
this.instructions.splice(i, 1);
return true;
}
}
return false;
}
/**
* Calculate the [[SourceStaffEntry]]'s minimum NoteLength.
* @returns {Fraction}
*/
public calculateMinNoteLength(): Fraction {
let duration: Fraction = new Fraction(Number.MAX_VALUE, 1);
for (let idx: number = 0, len: number = this.VoiceEntries.length; idx < len; ++idx) {
const voiceEntry: VoiceEntry = this.VoiceEntries[idx];
for (let idx2: number = 0, len2: number = voiceEntry.Notes.length; idx2 < len2; ++idx2) {
const note: Note = voiceEntry.Notes[idx2];
if (note.Length.lt(duration)) {
duration = note.Length;
}
}
}
return duration;
}
public calculateMaxNoteLength(untilEndOfTie: boolean = true): Fraction {
let duration: Fraction = new Fraction(0, 1);
for (let idx: number = 0, len: number = this.VoiceEntries.length; idx < len; ++idx) {
const voiceEntry: VoiceEntry = this.VoiceEntries[idx];
for (let idx2: number = 0, len2: number = voiceEntry.Notes.length; idx2 < len2; ++idx2) {
const note: Note = voiceEntry.Notes[idx2];
if (untilEndOfTie && note.NoteTie) {
// only add notes from this and after this sse!!
const tieRestDuration: Fraction = Fraction.createFromFraction(note.Length);
let addFollowingNotes: boolean = false;
for (const n of note.NoteTie.Notes) {
if (n === note) {
addFollowingNotes = true;
continue;
}
if (addFollowingNotes) {
tieRestDuration.Add(n.Length);
}
}
if (duration.lt(tieRestDuration)) {
duration = tieRestDuration;
}
} else if (duration.lt(note.Length)) {
duration = note.Length;
}
}
}
return duration;
}
public hasNotes(): boolean {
for (let idx: number = 0, len: number = this.VoiceEntries.length; idx < len; ++idx) {
const voiceEntry: VoiceEntry = this.VoiceEntries[idx];
if (voiceEntry.Notes.length > 0) {
return true;
}
}
return false;
}
public hasTie(): boolean {
for (let idx: number = 0, len: number = this.VoiceEntries.length; idx < len; ++idx) {
const voiceEntry: VoiceEntry = this.VoiceEntries[idx];
if (voiceEntry.hasTie()) {
return true;
}
}
return false;
}
public findLinkedNotes(linkedNotes: Note[]): void {
for (let idx: number = 0, len: number = this.voiceEntries.length; idx < len; ++idx) {
const voiceEntry: VoiceEntry = this.voiceEntries[idx];
for (let idx2: number = 0, len2: number = voiceEntry.Notes.length; idx2 < len2; ++idx2) {
const note: Note = voiceEntry.Notes[idx2];
if (note.ParentStaffEntry === this) {
linkedNotes.push(note);
}
}
}
}
public get hasOnlyRests(): boolean {
for (const voiceEntry of this.voiceEntries) {
for (const note of voiceEntry.Notes) {
if (!note.isRest()) {
return false;
}
}
}
return true;
}
}