import {MusicSheet} from "../../MusicSheet"; import {IXmlElement} from "../../../Common/FileIO/Xml"; import {SourceMeasure} from "../../VoiceData/SourceMeasure"; import {RepetitionInstruction, RepetitionInstructionEnum, AlignmentType} from "../../VoiceData/Instructions/RepetitionInstruction"; import {StringUtil} from "../../../Common/Strings/StringUtil"; export class RepetitionInstructionReader { /** * A global list of all repetition instructions in the musicsheet. */ public repetitionInstructions: RepetitionInstruction[]; public xmlMeasureList: IXmlElement[][]; private musicSheet: MusicSheet; private currentMeasureIndex: number; public set MusicSheet(value: MusicSheet) { this.musicSheet = value; this.xmlMeasureList = new Array(this.musicSheet.Instruments.length); this.repetitionInstructions = []; } /** * is called when starting reading an xml measure * @param measure * @param currentMeasureIndex */ public prepareReadingMeasure(measure: SourceMeasure, currentMeasureIndex: number): void { this.currentMeasureIndex = currentMeasureIndex; } public handleLineRepetitionInstructions(barlineNode: IXmlElement, pieceEndingDetected: boolean): void { pieceEndingDetected = false; if (barlineNode.elements().length > 0) { let location: string = ""; let hasRepeat: boolean = false; let direction: string = ""; let type: string = ""; let style: string = ""; let endingIndices: number[] = []; // read barline style let styleNode: IXmlElement = barlineNode.element("bar-style"); // if location is ommited in Xml, right is implied (from documentation) if (styleNode !== undefined) { style = styleNode.value; } if (barlineNode.attributes().length > 0 && barlineNode.attribute("location") !== undefined) { location = barlineNode.attribute("location").value; } else { location = "right"; } let barlineNodeElements: IXmlElement[] = barlineNode.elements(); // read repeat- or ending line information for (let idx: number = 0, len: number = barlineNodeElements.length; idx < len; ++idx) { let childNode: IXmlElement = barlineNodeElements[idx]; if ("repeat" === childNode.name && childNode.hasAttributes) { hasRepeat = true; direction = childNode.attribute("direction").value; } else if ( "ending" === childNode.name && childNode.hasAttributes && childNode.attribute("type") !== undefined && childNode.attribute("number") !== undefined) { type = childNode.attribute("type").value; let num: string = childNode.attribute("number").value; // Parse the given ending indices: // handle cases like: "1, 2" or "1 + 2" or even "1 - 3, 6" let separatedEndingIndices: string[] = num.split("[,+]"); for (let idx2: number = 0, len2: number = separatedEndingIndices.length; idx2 < len2; ++idx2) { let separatedEndingIndex: string = separatedEndingIndices[idx2]; let indices: string[] = separatedEndingIndex.match("[0-9]"); // check if possibly something like "1-3" is given.. if (separatedEndingIndex.search("-") !== -1 && indices.length === 2) { let startIndex: number = parseInt(indices[0], 10); let endIndex: number = parseInt(indices[1], 10); for (let index: number = startIndex; index <= endIndex; index++) { endingIndices.push(index); } } else { for (let idx3: number = 0, len3: number = indices.length; idx3 < len3; ++idx3) { let index: string = indices[idx3]; endingIndices.push(parseInt(index, 10)); } } } } } // reset measure counter if not lastMeasure if (style === "light-heavy" && endingIndices.length === 0 && !hasRepeat) { pieceEndingDetected = true; } if (hasRepeat || endingIndices.length > 0) { if (location === "left") { if (type === "start") { let newInstruction: RepetitionInstruction = new RepetitionInstruction(this.currentMeasureIndex, RepetitionInstructionEnum.Ending, AlignmentType.Begin, undefined, endingIndices); this.addInstruction(this.repetitionInstructions, newInstruction); } if (direction === "forward") { // start new Repetition let newInstruction: RepetitionInstruction = new RepetitionInstruction(this.currentMeasureIndex, RepetitionInstructionEnum.StartLine); this.addInstruction(this.repetitionInstructions, newInstruction); } } else { // location right if (type === "stop" || type === "discontinue") { let newInstruction: RepetitionInstruction = new RepetitionInstruction(this.currentMeasureIndex, RepetitionInstructionEnum.Ending, AlignmentType.End, undefined, endingIndices); this.addInstruction(this.repetitionInstructions, newInstruction); } if (direction === "backward") { let newInstruction: RepetitionInstruction = new RepetitionInstruction(this.currentMeasureIndex, RepetitionInstructionEnum.BackJumpLine); this.addInstruction(this.repetitionInstructions, newInstruction); } } } } } public handleRepetitionInstructionsFromWordsOrSymbols(directionTypeNode: IXmlElement, relativeMeasurePosition: number): boolean { let wordsNode: IXmlElement = directionTypeNode.element("words"); if (wordsNode !== undefined) { // must Trim string and ToLower before compare let innerText: string = wordsNode.value.trim().toLowerCase(); if (StringUtil.StringContainsSeparatedWord(innerText, "d.s. al fine") || StringUtil.StringContainsSeparatedWord(innerText, "d. s. al fine")) { let measureIndex: number = this.currentMeasureIndex; if (relativeMeasurePosition < 0.5 && this.currentMeasureIndex < this.xmlMeasureList[0].length - 1) { // not in last measure measureIndex--; } let newInstruction: RepetitionInstruction = new RepetitionInstruction(measureIndex, RepetitionInstructionEnum.DalSegnoAlFine); this.addInstruction(this.repetitionInstructions, newInstruction); return true; } if (StringUtil.StringContainsSeparatedWord(innerText, "d.s. al coda") || StringUtil.StringContainsSeparatedWord(innerText, "d. s. al coda")) { let measureIndex: number = this.currentMeasureIndex; if (relativeMeasurePosition < 0.5) { measureIndex--; } let newInstruction: RepetitionInstruction = new RepetitionInstruction(measureIndex, RepetitionInstructionEnum.DalSegnoAlCoda); this.addInstruction(this.repetitionInstructions, newInstruction); return true; } if (StringUtil.StringContainsSeparatedWord(innerText, "d.c. al fine") || StringUtil.StringContainsSeparatedWord(innerText, "d. c. al fine")) { let measureIndex: number = this.currentMeasureIndex; if (relativeMeasurePosition < 0.5 && this.currentMeasureIndex < this.xmlMeasureList[0].length - 1) { // not in last measure measureIndex--; } let newInstruction: RepetitionInstruction = new RepetitionInstruction(measureIndex, RepetitionInstructionEnum.DaCapoAlFine); this.addInstruction(this.repetitionInstructions, newInstruction); return true; } if (StringUtil.StringContainsSeparatedWord(innerText, "d.c. al coda") || StringUtil.StringContainsSeparatedWord(innerText, "d. c. al coda")) { let measureIndex: number = this.currentMeasureIndex; if (relativeMeasurePosition < 0.5) { measureIndex--; } let newInstruction: RepetitionInstruction = new RepetitionInstruction(measureIndex, RepetitionInstructionEnum.DaCapoAlCoda); this.addInstruction(this.repetitionInstructions, newInstruction); return true; } if (StringUtil.StringContainsSeparatedWord(innerText, "d.c.") || StringUtil.StringContainsSeparatedWord(innerText, "d. c.") || StringUtil.StringContainsSeparatedWord(innerText, "dacapo") || StringUtil.StringContainsSeparatedWord(innerText, "da capo")) { let measureIndex: number = this.currentMeasureIndex; if (relativeMeasurePosition < 0.5 && this.currentMeasureIndex < this.xmlMeasureList[0].length - 1) { // not in last measure measureIndex--; } let newInstruction: RepetitionInstruction = new RepetitionInstruction(measureIndex, RepetitionInstructionEnum.DaCapo); this.addInstruction(this.repetitionInstructions, newInstruction); return true; } if (StringUtil.StringContainsSeparatedWord(innerText, "d.s.") || StringUtil.StringContainsSeparatedWord(innerText, "d. s.") || StringUtil.StringContainsSeparatedWord(innerText, "dalsegno") || StringUtil.StringContainsSeparatedWord(innerText, "dal segno")) { let measureIndex: number = this.currentMeasureIndex; if (relativeMeasurePosition < 0.5 && this.currentMeasureIndex < this.xmlMeasureList[0].length - 1) { // not in last measure measureIndex--; } let newInstruction: RepetitionInstruction = new RepetitionInstruction(measureIndex, RepetitionInstructionEnum.DalSegno); this.addInstruction(this.repetitionInstructions, newInstruction); return true; } if (StringUtil.StringContainsSeparatedWord(innerText, "tocoda") || StringUtil.StringContainsSeparatedWord(innerText, "to coda") || StringUtil.StringContainsSeparatedWord(innerText, "a coda") || StringUtil.StringContainsSeparatedWord(innerText, "a la coda")) { let measureIndex: number = this.currentMeasureIndex; if (relativeMeasurePosition < 0.5) { measureIndex--; } let newInstruction: RepetitionInstruction = new RepetitionInstruction(measureIndex, RepetitionInstructionEnum.ToCoda); this.addInstruction(this.repetitionInstructions, newInstruction); return true; } if (StringUtil.StringContainsSeparatedWord(innerText, "fine")) { let measureIndex: number = this.currentMeasureIndex; if (relativeMeasurePosition < 0.5) { measureIndex--; } let newInstruction: RepetitionInstruction = new RepetitionInstruction(measureIndex, RepetitionInstructionEnum.Fine); this.addInstruction(this.repetitionInstructions, newInstruction); return true; } if (StringUtil.StringContainsSeparatedWord(innerText, "coda")) { let measureIndex: number = this.currentMeasureIndex; if (relativeMeasurePosition > 0.5) { measureIndex++; } let newInstruction: RepetitionInstruction = new RepetitionInstruction(measureIndex, RepetitionInstructionEnum.Coda); this.addInstruction(this.repetitionInstructions, newInstruction); return true; } if (StringUtil.StringContainsSeparatedWord(innerText, "segno")) { let measureIndex: number = this.currentMeasureIndex; if (relativeMeasurePosition > 0.5) { measureIndex++; } let newInstruction: RepetitionInstruction = new RepetitionInstruction(measureIndex, RepetitionInstructionEnum.Segno); this.addInstruction(this.repetitionInstructions, newInstruction); return true; } } else if (directionTypeNode.element("segno") !== undefined) { let measureIndex: number = this.currentMeasureIndex; if (relativeMeasurePosition > 0.5) { measureIndex++; } let newInstruction: RepetitionInstruction = new RepetitionInstruction(measureIndex, RepetitionInstructionEnum.Segno); this.addInstruction(this.repetitionInstructions, newInstruction); return true; } else if (directionTypeNode.element("coda") !== undefined) { let measureIndex: number = this.currentMeasureIndex; if (relativeMeasurePosition > 0.5) { measureIndex++; } let newInstruction: RepetitionInstruction = new RepetitionInstruction(measureIndex, RepetitionInstructionEnum.Coda); this.addInstruction(this.repetitionInstructions, newInstruction); return true; } return false; } public removeRedundantInstructions(): void { let segnoCount: number = 0; let codaCount: number = 0; let fineCount: number = 0; let toCodaCount: number = 0; let dalSegnaCount: number = 0; for (let index: number = 0; index < this.repetitionInstructions.length; index++) { let instruction: RepetitionInstruction = this.repetitionInstructions[index]; switch (instruction.type) { case RepetitionInstructionEnum.Coda: if (toCodaCount > 0) { if (this.findInstructionInPreviousMeasure(index, instruction.measureIndex, RepetitionInstructionEnum.ToCoda)) { instruction.type = RepetitionInstructionEnum.None; } } if (codaCount === 0 && toCodaCount === 0) { instruction.type = RepetitionInstructionEnum.ToCoda; instruction.alignment = AlignmentType.End; instruction.measureIndex--; } break; case RepetitionInstructionEnum.Segno: if (segnoCount - dalSegnaCount > 0) { // two segnos in a row let foundInstruction: boolean = false; for (let idx: number = 0, len: number = this.repetitionInstructions.length; idx < len; ++idx) { let instr: RepetitionInstruction = this.repetitionInstructions[idx]; if (instruction.measureIndex - instr.measureIndex === 1) { switch (instr.type) { case RepetitionInstructionEnum.BackJumpLine: if (toCodaCount - codaCount > 0) { // open toCoda existing instr.type = RepetitionInstructionEnum.DalSegnoAlCoda; } else { instr.type = RepetitionInstructionEnum.DalSegno; } instruction.type = RepetitionInstructionEnum.None; foundInstruction = true; break; case RepetitionInstructionEnum.DalSegno: case RepetitionInstructionEnum.DalSegnoAlFine: case RepetitionInstructionEnum.DalSegnoAlCoda: instruction.type = RepetitionInstructionEnum.None; foundInstruction = true; break; default: break; } } if (foundInstruction) { break; } } if (foundInstruction) { break; } // convert to dal segno instruction: if (toCodaCount - codaCount > 0) { // open toCoda existing instruction.type = RepetitionInstructionEnum.DalSegnoAlCoda; } else { instruction.type = RepetitionInstructionEnum.DalSegno; } instruction.alignment = AlignmentType.End; instruction.measureIndex--; } break; default: break; } // check if this instruction already exists or is otherwise redundant: if (this.backwardSearchForPreviousIdenticalInstruction(index, instruction) || instruction.type === RepetitionInstructionEnum.None) { this.repetitionInstructions.splice(index, 1); index--; } else { switch (instruction.type) { case RepetitionInstructionEnum.Fine: fineCount++; break; case RepetitionInstructionEnum.ToCoda: toCodaCount++; break; case RepetitionInstructionEnum.Coda: codaCount++; break; case RepetitionInstructionEnum.Segno: segnoCount++; break; case RepetitionInstructionEnum.DalSegnoAlFine: case RepetitionInstructionEnum.DalSegnoAlCoda: dalSegnaCount++; break; default: break; } } } this.repetitionInstructions.sort(RepetitionInstruction.compare); } private findInstructionInPreviousMeasure(currentInstructionIndex: number, currentMeasureIndex: number, searchedType: RepetitionInstructionEnum): boolean { for (let index: number = currentInstructionIndex - 1; index >= 0; index--) { let instruction: RepetitionInstruction = this.repetitionInstructions[index]; if (currentMeasureIndex - instruction.measureIndex === 1 && instruction.type === searchedType) { return true; } } return false; } private backwardSearchForPreviousIdenticalInstruction(currentInstructionIndex: number, currentInstruction: RepetitionInstruction): boolean { for (let index: number = currentInstructionIndex - 1; index >= 0; index--) { let instruction: RepetitionInstruction = this.repetitionInstructions[index]; if (instruction.equals(currentInstruction)) { return true; } } return false; } private addInstruction(currentRepetitionInstructions: RepetitionInstruction[], newInstruction: RepetitionInstruction): void { let addInstruction: boolean = true; for (let idx: number = 0, len: number = currentRepetitionInstructions.length; idx < len; ++idx) { let repetitionInstruction: RepetitionInstruction = currentRepetitionInstructions[idx]; if (newInstruction.equals(repetitionInstruction)) { addInstruction = false; break; } } if (addInstruction) { currentRepetitionInstructions.push(newInstruction); } } }