import { LinkedVoice } from "../VoiceData/LinkedVoice" ; import { Voice } from "../VoiceData/Voice" ; import { MusicSheet } from "../MusicSheet" ; import { VoiceEntry , StemDirectionType } from "../VoiceData/VoiceEntry" ; import { Note } from "../VoiceData/Note" ; import { SourceMeasure } from "../VoiceData/SourceMeasure" ; import { SourceStaffEntry } from "../VoiceData/SourceStaffEntry" ; import { Beam } from "../VoiceData/Beam" ; import { Tie } from "../VoiceData/Tie" ; import { TieTypes } from "../../Common/Enums/" ; import { Tuplet } from "../VoiceData/Tuplet" ; import { Fraction } from "../../Common/DataObjects/Fraction" ; import { IXmlElement } from "../../Common/FileIO/Xml" ; import { ITextTranslation } from "../Interfaces/ITextTranslation" ; import { LyricsReader } from "../ScoreIO/MusicSymbolModules/LyricsReader" ; import { MusicSheetReadingException } from "../Exceptions" ; import { AccidentalEnum } from "../../Common/DataObjects/Pitch" ; import { NoteEnum } from "../../Common/DataObjects/Pitch" ; import { Staff } from "../VoiceData/Staff" ; import { StaffEntryLink } from "../VoiceData/StaffEntryLink" ; import { VerticalSourceStaffEntryContainer } from "../VoiceData/VerticalSourceStaffEntryContainer" ; import log from "loglevel" ; import { Pitch } from "../../Common/DataObjects/Pitch" ; import { IXmlAttribute } from "../../Common/FileIO/Xml" ; import { CollectionUtil } from "../../Util/CollectionUtil" ; import { ArticulationReader } from "./MusicSymbolModules/ArticulationReader" ; import { SlurReader } from "./MusicSymbolModules/SlurReader" ; import { Notehead } from "../VoiceData/Notehead" ; import { Arpeggio , ArpeggioType } from "../VoiceData/Arpeggio" ; import { NoteType , NoteTypeHandler } from "../VoiceData/NoteType" ; import { TabNote } from "../VoiceData/TabNote" ; import { PlacementEnum } from "../VoiceData/Expressions/AbstractExpression" ; import { ReaderPluginManager } from "./ReaderPluginManager" ; import { Instrument } from "../Instrument" ; export class VoiceGenerator { constructor ( pluginManager , staff , voiceId , slurReader , mainVoice = undefined ) { this . staff = staff ; this . instrument = staff . ParentInstrument ; this . musicSheet = this . instrument . GetMusicSheet ; this . slurReader = slurReader ; this . pluginManager = pluginManager ; if ( mainVoice ) { this . voice = new LinkedVoice ( this . instrument , voiceId , mainVoice ) ; } else { this . voice = new Voice ( this . instrument , voiceId ) ; } this . instrument . Voices . push ( this . voice ) ; this . staff . Voices . push ( this . voice ) ; this . lyricsReader = new LyricsReader ( this . musicSheet ) ; this . articulationReader = new ArticulationReader ( this . musicSheet . Rules ) ; } public pluginManager : ReaderPluginManager ; private slurReader : SlurReader ; private lyricsReader : LyricsReader ; private articulationReader : ArticulationReader ; private musicSheet : MusicSheet ; private voice : Voice ; private currentVoiceEntry : VoiceEntry ; private currentNote : Note ; private currentMeasure : SourceMeasure ; private currentStaffEntry : SourceStaffEntry ; private staff : Staff ; private instrument : Instrument ; private openBeams : Beam [ ] = [ ] ; private beamNumberOffset : number = 0 ; private openTieDict : { [ _ ] : Tie ; } = { } ; private currentOctaveShift : number = 0 ; private tupletDict : { [ _ ] : Tuplet ; } = { } ; private openTupletNumber : number = 0 ; public get GetVoice ( ) : Voice { return this . voice ; } public get OctaveShift ( ) : number { return this . currentOctaveShift ; } public set OctaveShift ( value ) { this . currentOctaveShift = value ; } param musicTimestamp param parentStaffEntry param addToVoice param isGrace public createVoiceEntry ( musicTimestamp , parentStaffEntry , addToVoice , isGrace = false , graceNoteSlash = false , graceSlur = false ) { this . currentVoiceEntry = new VoiceEntry ( musicTimestamp . clone ( ) , this . voice , parentStaffEntry , isGrace , graceNoteSlash , graceSlur ) ; if ( addToVoice ) { this . voice . VoiceEntries . push ( this . currentVoiceEntry ) ; } } param noteNode param noteDuration param divisions param restNote param parentStaffEntry param parentMeasure param measureStartAbsoluteTimestamp param maxTieNoteFraction param chord param octavePlusOne param printObject returns { Note } public read ( noteNode , noteDuration , typeDuration , noteTypeXml , normalNotes , restNote , parentStaffEntry , parentMeasure , measureStartAbsoluteTimestamp , maxTieNoteFraction , chord , octavePlusOne , printObject , isCueNote , isGraceNote , stemDirectionXml , tremoloStrokes , stemColorXml , noteheadColorXml , vibratoStrokes , dotsXml ) { this . currentStaffEntry = parentStaffEntry ; this . currentMeasure = parentMeasure ; try { this . currentNote = restNote ? this . addRestNote ( noteNode . element ( "rest" ) , noteDuration , noteTypeXml , normalNotes , printObject , isCueNote , noteheadColorXml ) : this . addSingleNote ( noteNode , noteDuration , noteTypeXml , typeDuration , normalNotes , chord , octavePlusOne , printObject , isCueNote , isGraceNote , stemDirectionXml , tremoloStrokes , stemColorXml , noteheadColorXml , vibratoStrokes ) ; this . currentNote . DotsXml = dotsXml ; const lyricElements = noteNode . elements ( "lyric" ) ; if ( this . lyricsReader !== undefined && lyricElements ) { this . lyricsReader . addLyricEntry ( lyricElements , this . currentVoiceEntry ) ; this . voice . Parent . HasLyrics = true ; } let hasTupletCommand = false ; const notationNode = noteNode . element ( "notations" ) ; if ( notationNode ) { if ( this . articulationReader ) { this . readArticulations ( notationNode , this . currentVoiceEntry , this . currentNote ) ; } const slurElements = notationNode . elements ( "slur" ) ; if ( this . slurReader !== undefined && slurElements . length > 0 && ! this . currentNote . ParentVoiceEntry . IsGrace ) { this . slurReader . addSlur ( slurElements , this . currentNote ) ; } const tupletElements = notationNode . elements ( "tuplet" ) ; if ( tupletElements . length > 0 ) { this . openTupletNumber = this . addTuplet ( noteNode , tupletElements ) ; hasTupletCommand = true ; } const arpeggioNode = notationNode . element ( "arpeggiate" ) ; if ( arpeggioNode !== undefined ) { let currentArpeggio ; if ( this . currentVoiceEntry . Arpeggio ) { currentArpeggio = this . currentVoiceEntry . Arpeggio ; } else { let arpeggioAlreadyExists = false ; for ( const voiceEntry of this . currentStaffEntry . VoiceEntries ) { if ( voiceEntry . Arpeggio ) { arpeggioAlreadyExists = true ; currentArpeggio = voiceEntry . Arpeggio ; break ; } } if ( ! arpeggioAlreadyExists ) { let arpeggioType = ArpeggioType . ARPEGGIO_DIRECTIONLESS ; const directionAttr = arpeggioNode . attribute ( "direction" ) ; if ( directionAttr ) { switch ( directionAttr . value ) { case "up" : arpeggioType = ArpeggioType . ROLL_UP ; break ; case "down" : arpeggioType = ArpeggioType . ROLL_DOWN ; break ; default : arpeggioType = ArpeggioType . ARPEGGIO_DIRECTIONLESS ; } } currentArpeggio = new Arpeggio ( this . currentVoiceEntry , arpeggioType ) ; this . currentVoiceEntry . Arpeggio = currentArpeggio ; } } currentArpeggio . addNote ( this . currentNote ) ; } const tiedNodeList = notationNode . elements ( "tied" ) ; if ( tiedNodeList . length > 0 ) { this . addTie ( tiedNodeList , measureStartAbsoluteTimestamp , maxTieNoteFraction , TieTypes . SIMPLE ) ; } const slideNodeList = notationNode . elements ( "slide" ) ; if ( slideNodeList . length > 0 ) { this . addTie ( slideNodeList , measureStartAbsoluteTimestamp , maxTieNoteFraction , TieTypes . SLIDE ) ; } const technicalNode = notationNode . element ( "technical" ) ; if ( technicalNode ) { const hammerNodeList = technicalNode . elements ( "hammer-on" ) ; if ( hammerNodeList . length > 0 ) { this . addTie ( hammerNodeList , measureStartAbsoluteTimestamp , maxTieNoteFraction , TieTypes . HAMMERON ) ; } const pulloffNodeList = technicalNode . elements ( "pull-off" ) ; if ( pulloffNodeList . length > 0 ) { this . addTie ( pulloffNodeList , measureStartAbsoluteTimestamp , maxTieNoteFraction , TieTypes . PULLOFF ) ; } } } if ( noteNode . element ( "time-modification" ) !== undefined && ! hasTupletCommand ) { this . handleTimeModificationNode ( noteNode ) ; } } catch ( err ) { log . warn ( err ) ; const errorMsg = ITextTranslation . translateText ( "ReaderErrorMessages/NoteError" , "Ignored erroneous Note." ) ; this . musicSheet . SheetErrors . pushMeasureError ( errorMsg ) ; this . musicSheet . SheetErrors . pushMeasureError ( err ) ; } return this . currentNote ; } param index param currentStaff param currentStaffEntry param currentMeasure returns { SourceStaffEntry } public checkForStaffEntryLink ( index , currentStaff , currentStaffEntry , currentMeasure ) { const staffEntryLink = new StaffEntryLink ( this . currentVoiceEntry ) ; staffEntryLink . LinkStaffEntries . push ( currentStaffEntry ) ; currentStaffEntry . Link = staffEntryLink ; const linkMusicTimestamp = this . currentVoiceEntry . Timestamp . clone ( ) ; const verticalSourceStaffEntryContainer = currentMeasure . getVerticalContainerByTimestamp ( linkMusicTimestamp ) ; currentStaffEntry = verticalSourceStaffEntryContainer . StaffEntries [ index ] ; if ( ! currentStaffEntry ) { currentStaffEntry = new SourceStaffEntry ( verticalSourceStaffEntryContainer , currentStaff ) ; verticalSourceStaffEntryContainer . StaffEntries [ index ] = currentStaffEntry ; } currentStaffEntry . VoiceEntries . push ( this . currentVoiceEntry ) ; staffEntryLink . LinkStaffEntries . push ( currentStaffEntry ) ; currentStaffEntry . Link = staffEntryLink ; return currentStaffEntry ; } public checkForOpenBeam ( ) { if ( this . openBeams . length > 0 && this . currentNote ) { this . handleOpenBeam ( ) ; } } public checkOpenTies ( ) { const openTieDict = this . openTieDict ; for ( const key in openTieDict ) { if ( openTieDict . hasOwnProperty ( key ) ) { const tie = openTieDict [ key ] ; if ( Fraction . plus ( tie . StartNote . ParentStaffEntry . Timestamp , tie . Duration ) . lt ( tie . StartNote . SourceMeasure . Duration ) ) { delete openTieDict [ key ] ; } } } } public hasVoiceEntry ( ) { return this . currentVoiceEntry !== undefined ; } private readArticulations ( notationNode , currentVoiceEntry , currentNote ) { const articNode = notationNode . element ( "articulations" ) ; if ( articNode ) { this . articulationReader . addArticulationExpression ( articNode , currentVoiceEntry ) ; } const fermaNode = notationNode . element ( "fermata" ) ; if ( fermaNode ) { this . articulationReader . addFermata ( fermaNode , currentVoiceEntry ) ; } const tecNode = notationNode . element ( "technical" ) ; if ( tecNode ) { this . articulationReader . addTechnicalArticulations ( tecNode , currentVoiceEntry , currentNote ) ; } const ornaNode = notationNode . element ( "ornaments" ) ; if ( ornaNode ) { this . articulationReader . addOrnament ( ornaNode , currentVoiceEntry ) ; } } param node param noteDuration param divisions param chord param octavePlusOne returns { Note } private addSingleNote ( node , noteDuration , noteTypeXml , typeDuration , normalNotes , chord , octavePlusOne , printObject , isCueNote , isGraceNote , stemDirectionXml , tremoloStrokes , stemColorXml , noteheadColorXml , vibratoStrokes ) { let noteAlter = 0 ; let accidentalValue ; let noteAccidental = AccidentalEnum . NONE ; let noteStep = NoteEnum . C ; let displayStepUnpitched = NoteEnum . C ; let noteOctave = 0 ; let displayOctaveUnpitched = 0 ; let playbackInstrumentId = undefined ; let noteheadShapeXml = undefined ; let noteheadFilledXml = undefined ; const xmlnodeElementsArr = node . elements ( ) ; for ( let idx = 0 , len = xmlnodeElementsArr . length ; idx < len ; ++ idx ) { const noteElement = xmlnodeElementsArr [ idx ] ; try { if ( noteElement . name === "pitch" ) { const noteElementsArr = noteElement . elements ( ) ; for ( let idx2 = 0 , len2 = noteElementsArr . length ; idx2 < len2 ; ++ idx2 ) { const pitchElement = noteElementsArr [ idx2 ] ; noteheadShapeXml = undefined ; noteheadFilledXml = undefined ; try { if ( pitchElement . name === "step" ) { noteStep = NoteEnum [ pitchElement . value ] ; if ( noteStep === undefined ) { const errorMsg = ITextTranslation . translateText ( "ReaderErrorMessages/NotePitchError" , "Invalid pitch while reading note." ) ; this . musicSheet . SheetErrors . pushMeasureError ( errorMsg ) ; throw new MusicSheetReadingException ( errorMsg , undefined ) ; } } else if ( pitchElement . name === "alter" ) { noteAlter = parseFloat ( pitchElement . value ) ; if ( isNaN ( noteAlter ) ) { const errorMsg = ITextTranslation . translateText ( "ReaderErrorMessages/NoteAlterationError" , "Invalid alteration while reading note." ) ; this . musicSheet . SheetErrors . pushMeasureError ( errorMsg ) ; throw new MusicSheetReadingException ( errorMsg , undefined ) ; } noteAccidental = Pitch . AccidentalFromHalfTones ( noteAlter ) ; } else if ( pitchElement . name === "octave" ) { noteOctave = parseInt ( pitchElement . value , 10 ) ; if ( isNaN ( noteOctave ) ) { const errorMsg = ITextTranslation . translateText ( "ReaderErrorMessages/NoteOctaveError" , "Invalid octave value while reading note." ) ; this . musicSheet . SheetErrors . pushMeasureError ( errorMsg ) ; throw new MusicSheetReadingException ( errorMsg , undefined ) ; } } } catch ( ex ) { log . info ( "VoiceGenerator.addSingleNote read Step: " , ex . message ) ; } } } else if ( noteElement . name === "accidental" ) { accidentalValue = noteElement . value ; if ( accidentalValue === "natural" ) { noteAccidental = AccidentalEnum . NATURAL ; } else if ( accidentalValue === "slash-flat" ) { noteAccidental = AccidentalEnum . SLASHFLAT ; } else if ( accidentalValue === "slash-quarter-sharp" ) { noteAccidental = AccidentalEnum . SLASHQUARTERSHARP ; } else if ( accidentalValue === "slash-sharp" ) { noteAccidental = AccidentalEnum . SLASHSHARP ; } else if ( accidentalValue === "double-slash-flat" ) { noteAccidental = AccidentalEnum . DOUBLESLASHFLAT ; } else if ( accidentalValue === "sori" ) { noteAccidental = AccidentalEnum . SORI ; } else if ( accidentalValue === "koron" ) { noteAccidental = AccidentalEnum . KORON ; } } else if ( noteElement . name === "unpitched" ) { const displayStepElement = noteElement . element ( "display-step" ) ; const octave = noteElement . element ( "display-octave" ) ; if ( octave ) { noteOctave = parseInt ( octave . value , 10 ) ; displayOctaveUnpitched = noteOctave - 3 ; if ( octavePlusOne ) { noteOctave += 1 ; } if ( this . instrument . Staves [ 0 ] . StafflineCount === 1 ) { displayOctaveUnpitched += 1 ; } } if ( displayStepElement ) { noteStep = NoteEnum [ displayStepElement . value . toUpperCase ( ) ] ; let octaveShift = 0 ; let noteValueShift = this . musicSheet . Rules . PercussionXMLDisplayStepNoteValueShift ; if ( this . instrument . Staves [ 0 ] . StafflineCount === 1 ) { noteValueShift -= 3 ; } [ displayStepUnpitched , octaveShift ] = Pitch . lineShiftFromNoteEnum ( noteStep , noteValueShift ) ; displayOctaveUnpitched += octaveShift ; } } else if ( noteElement . name === "instrument" ) { if ( noteElement . firstAttribute ) { playbackInstrumentId = noteElement . firstAttribute . value ; } } else if ( noteElement . name === "notehead" ) { noteheadShapeXml = noteElement . value ; if ( noteElement . attribute ( "filled" ) ) { noteheadFilledXml = noteElement . attribute ( "filled" ) . value === "yes" ; } } } catch ( ex ) { log . info ( "VoiceGenerator.addSingleNote: " , ex ) ; } } noteOctave -= Pitch . OctaveXmlDifference ; const pitch = new Pitch ( noteStep , noteOctave , noteAccidental , accidentalValue ) ; const noteLength = Fraction . createFromFraction ( noteDuration ) ; let note = undefined ; let stringNumber = - 1 ; let fretNumber = - 1 ; const bends = [ ] ; const notationNode = node . element ( "notations" ) ; if ( notationNode ) { const technicalNode = notationNode . element ( "technical" ) ; if ( technicalNode ) { const stringNode = technicalNode . element ( "string" ) ; if ( stringNode ) { stringNumber = parseInt ( stringNode . value , 10 ) ; } const fretNode = technicalNode . element ( "fret" ) ; if ( fretNode ) { fretNumber = parseInt ( fretNode . value , 10 ) ; } const bendElementsArr = technicalNode . elements ( "bend" ) ; bendElementsArr . forEach ( function ( bend ) : void { const bendalterNote = bend . element ( "bend-alter" ) ; const releaseNode = bend . element ( "release" ) ; if ( releaseNode !== undefined ) { bends . push ( { bendalter : parseInt ( bendalterNote . value , 10 ) , direction : "down" } ) ; } else { bends . push ( { bendalter : parseInt ( bendalterNote . value , 10 ) , direction : "up" } ) ; } } ) ; } } if ( stringNumber < 0 || fretNumber < 0 ) { note = new Note ( this . currentVoiceEntry , this . currentStaffEntry , noteLength , pitch , this . currentMeasure ) ; } else { note = new TabNote ( this . currentVoiceEntry , this . currentStaffEntry , noteLength , pitch , this . currentMeasure , stringNumber , fretNumber , bends , vibratoStrokes ) ; } this . addNoteInfo ( note , noteTypeXml , printObject , isCueNote , normalNotes , displayStepUnpitched , displayOctaveUnpitched , noteheadColorXml , noteheadColorXml ) ; note . TypeLength = typeDuration ; note . IsGraceNote = isGraceNote ; note . StemDirectionXml = stemDirectionXml ; note . TremoloStrokes = tremoloStrokes ; note . PlaybackInstrumentId = playbackInstrumentId ; if ( ( noteheadShapeXml !== undefined && noteheadShapeXml !== "normal" ) || noteheadFilledXml !== undefined ) { note . Notehead = new Notehead ( note , noteheadShapeXml , noteheadFilledXml ) ; } if ( stemDirectionXml === StemDirectionType . None ) { stemColorXml = "#00000000" ; } this . currentVoiceEntry . Notes . push ( note ) ; this . currentVoiceEntry . StemDirectionXml = stemDirectionXml ; if ( stemColorXml ) { this . currentVoiceEntry . StemColorXml = stemColorXml ; this . currentVoiceEntry . StemColor = stemColorXml ; note . StemColorXml = stemColorXml ; } if ( node . elements ( "beam" ) && ! chord ) { this . createBeam ( node , note ) ; } return note ; } param noteDuration param divisions returns { Note } private addRestNote ( node , noteDuration , noteTypeXml , normalNotes , printObject , isCueNote , noteheadColorXml ) { const restFraction = Fraction . createFromFraction ( noteDuration ) ; const displayStepElement = node . element ( "display-step" ) ; const octaveElement = node . element ( "display-octave" ) ; let displayStep ; let displayOctave ; let pitch = undefined ; if ( displayStepElement && octaveElement ) { displayStep = NoteEnum [ displayStepElement . value . toUpperCase ( ) ] ; displayOctave = parseInt ( octaveElement . value , 10 ) ; pitch = new Pitch ( displayStep , displayOctave , AccidentalEnum . NONE ) ; } const restNote = new Note ( this . currentVoiceEntry , this . currentStaffEntry , restFraction , pitch , this . currentMeasure , true ) ; this . addNoteInfo ( restNote , noteTypeXml , printObject , isCueNote , normalNotes , displayStep , displayOctave , noteheadColorXml , noteheadColorXml ) ; this . currentVoiceEntry . Notes . push ( restNote ) ; if ( this . openBeams . length > 0 ) { this . openBeams . last ( ) . ExtendedNoteList . push ( restNote ) ; } return restNote ; } private addNoteInfo ( note , noteTypeXml , printObject , isCueNote , normalNotes , displayStep , displayOctave , noteheadColorXml , noteheadColor ) { note . NoteTypeXml = noteTypeXml ; note . PrintObject = printObject ; note . IsCueNote = isCueNote ; note . NormalNotes = normalNotes ; note . displayStepUnpitched = displayStep ; note . displayOctaveUnpitched = displayOctave ; note . NoteheadColorXml = noteheadColorXml ; note . NoteheadColor = noteheadColorXml ; } param node param note private createBeam ( node , note ) { try { const beamNode = node . element ( "beam" ) ; let beamAttr = undefined ; if ( beamNode !== undefined && beamNode . hasAttributes ) { beamAttr = beamNode . attribute ( "number" ) ; } if ( beamAttr ) { let beamNumber = parseInt ( beamAttr . value , 10 ) ; const mainBeamNode = node . elements ( "beam" ) ; const currentBeamTag = mainBeamNode [ 0 ] . value ; if ( mainBeamNode ) { if ( currentBeamTag === "begin" ) { if ( beamNumber === this . openBeams . last ( ) ?. BeamNumber ) { this . beamNumberOffset ++ ; beamNumber += this . beamNumberOffset ; } else if ( this . openBeams . last ( ) ) { this . handleOpenBeam ( ) ; } this . openBeams . push ( new Beam ( beamNumber , this . beamNumberOffset ) ) ; } else { beamNumber += this . beamNumberOffset ; } } let sameVoiceEntry = false ; if ( ! ( beamNumber > 0 && beamNumber <= this . openBeams . length ) || ! this . openBeams [ beamNumber - 1 ] ) { log . debug ( "[OSMD] invalid beamnumber" ) ; return ; } for ( let idx = 0 , len = this . openBeams [ beamNumber - 1 ] . Notes . length ; idx < len ; ++ idx ) { const beamNote = this . openBeams [ beamNumber - 1 ] . Notes [ idx ] ; if ( this . currentVoiceEntry === beamNote . ParentVoiceEntry ) { sameVoiceEntry = true ; } } if ( ! sameVoiceEntry ) { const openBeam = this . openBeams [ beamNumber - 1 ] ; openBeam . addNoteToBeam ( note ) ; if ( currentBeamTag === "end" ) { this . endBeam ( ) ; } } } } catch ( e ) { const errorMsg = ITextTranslation . translateText ( "ReaderErrorMessages/BeamError" , "Error while reading beam." ) ; this . musicSheet . SheetErrors . pushMeasureError ( errorMsg ) ; throw new MusicSheetReadingException ( "" , e ) ; } } private endBeam ( ) { this . openBeams . pop ( ) ; this . beamNumberOffset = Math . max ( 0 , this . beamNumberOffset - 1 ) ; } /**
     * Check for open [[Beam]]s at end of [[SourceMeasure]] and closes them explicity.
     */ private handleOpenBeam ( ) { const openBeam = this . openBeams . last ( ) ; if ( openBeam . Notes . length === 0 ) { this . endBeam ( ) ; return ; } if ( openBeam . Notes . length === 1 ) { const beamNote = openBeam . Notes [ 0 ] ; beamNote . NoteBeam = undefined ; this . endBeam ( ) ; return ; } if ( this . currentNote === CollectionUtil . last ( openBeam . Notes ) ) { this . endBeam ( ) ; } else { const beamLastNote = CollectionUtil . last ( openBeam . Notes ) ; const beamLastNoteStaffEntry = beamLastNote . ParentStaffEntry ; const horizontalIndex = this . currentMeasure . getVerticalContainerIndexByTimestamp ( beamLastNoteStaffEntry . Timestamp ) ; const verticalIndex = beamLastNoteStaffEntry . VerticalContainerParent . StaffEntries . indexOf ( beamLastNoteStaffEntry ) ; if ( horizontalIndex < this . currentMeasure . VerticalSourceStaffEntryContainers . length - 1 ) { const nextStaffEntry = this . currentMeasure . VerticalSourceStaffEntryContainers [ horizontalIndex + 1 ] . StaffEntries [ verticalIndex ] ; if ( nextStaffEntry ) { for ( let idx = 0 , len = nextStaffEntry . VoiceEntries . length ; idx < len ; ++ idx ) { const voiceEntry = nextStaffEntry . VoiceEntries [ idx ] ; if ( voiceEntry . ParentVoice === this . voice ) { const candidateNote = voiceEntry . Notes [ 0 ] ; if ( candidateNote . Length . lte ( new Fraction ( 1 , 8 ) ) ) { this . openBeams . last ( ) . addNoteToBeam ( candidateNote ) ; this . endBeam ( ) ; } else { this . endBeam ( ) ; } } } } } else { this . endBeam ( ) ; } } } param node param tupletNodeList returns { number } private addTuplet ( node , tupletNodeList ) { let bracketed = false ; if ( tupletNodeList !== undefined && tupletNodeList . length > 1 ) { let timeModNode = node . element ( "time-modification" ) ; if ( timeModNode ) { timeModNode = timeModNode . element ( "actual-notes" ) ; } const tupletNodeListArr = tupletNodeList ; for ( let idx = 0 , len = tupletNodeListArr . length ; idx < len ; ++ idx ) { const tupletNode = tupletNodeListArr [ idx ] ; if ( tupletNode !== undefined && tupletNode . attributes ( ) ) { const bracketAttr = tupletNode . attribute ( "bracket" ) ; if ( bracketAttr && bracketAttr . value === "yes" ) { bracketed = true ; } const type = tupletNode . attribute ( "type" ) ; if ( type && type . value === "start" ) { let tupletNumber = 1 ; if ( tupletNode . attribute ( "number" ) ) { tupletNumber = parseInt ( tupletNode . attribute ( "number" ) . value , 10 ) ; } let tupletLabelNumber = 0 ; if ( timeModNode ) { tupletLabelNumber = parseInt ( timeModNode . value , 10 ) ; if ( isNaN ( tupletLabelNumber ) ) { const errorMsg = ITextTranslation . translateText ( "ReaderErrorMessages/TupletNoteDurationError" , "Invalid tuplet note duration." ) ; this . musicSheet . SheetErrors . pushMeasureError ( errorMsg ) ; throw new MusicSheetReadingException ( errorMsg , undefined ) ; } } const tuplet = new Tuplet ( tupletLabelNumber , bracketed ) ; tuplet . tupletLabelNumberPlacement = PlacementEnum . Above ; const placementAttr = tupletNode . attribute ( "placement" ) ; if ( placementAttr ) { if ( placementAttr . value === "below" ) { tuplet . tupletLabelNumberPlacement = PlacementEnum . Below ; } tuplet . PlacementFromXml = true ; } if ( this . tupletDict [ tupletNumber ] ) { delete this . tupletDict [ tupletNumber ] ; if ( Object . keys ( this . tupletDict ) . length === 0 ) { this . openTupletNumber = 0 ; } else if ( Object . keys ( this . tupletDict ) . length > 1 ) { this . openTupletNumber -- ; } } this . tupletDict [ tupletNumber ] = tuplet ; const subnotelist = [ ] ; subnotelist . push ( this . currentNote ) ; tuplet . Notes . push ( subnotelist ) ; tuplet . Fractions . push ( this . getTupletNoteDurationFromType ( node ) ) ; this . currentNote . NoteTuplet = tuplet ; this . openTupletNumber = tupletNumber ; } else if ( type . value === "stop" ) { let tupletNumber = 1 ; if ( tupletNode . attribute ( "number" ) ) { tupletNumber = parseInt ( tupletNode . attribute ( "number" ) . value , 10 ) ; } const tuplet = this . tupletDict [ tupletNumber ] ; if ( tuplet ) { const placementAttr = tupletNode . attribute ( "placement" ) ; if ( placementAttr ) { if ( placementAttr . value === "below" ) { tuplet . tupletLabelNumberPlacement = PlacementEnum . Below ; } else { tuplet . tupletLabelNumberPlacement = PlacementEnum . Above ; } tuplet . PlacementFromXml = true ; } const subnotelist = [ ] ; subnotelist . push ( this . currentNote ) ; tuplet . Notes . push ( subnotelist ) ; if ( ! tuplet . PlacementFromXml ) { let foundNonRest = false ; for ( const subList of tuplet . Notes ) { for ( const note of subList ) { if ( ! note . isRest ( ) ) { if ( note . StemDirectionXml === StemDirectionType . Down ) { tuplet . tupletLabelNumberPlacement = PlacementEnum . Below ; } else { tuplet . tupletLabelNumberPlacement = PlacementEnum . Above ; } foundNonRest = true ; break ; } } if ( foundNonRest ) { break ; } } } tuplet . Fractions . push ( this . getTupletNoteDurationFromType ( node ) ) ; this . currentNote . NoteTuplet = tuplet ; delete this . tupletDict [ tupletNumber ] ; if ( Object . keys ( this . tupletDict ) . length === 0 ) { this . openTupletNumber = 0 ; } else if ( Object . keys ( this . tupletDict ) . length > 1 ) { this . openTupletNumber -- ; } } } } } } else if ( tupletNodeList [ 0 ] ) { const n = tupletNodeList [ 0 ] ; if ( n . hasAttributes ) { const type = n . attribute ( "type" ) . value ; let tupletnumber = 1 ; if ( n . attribute ( "number" ) ) { tupletnumber = parseInt ( n . attribute ( "number" ) . value , 10 ) ; } const noTupletNumbering = isNaN ( tupletnumber ) ; const bracketAttr = n . attribute ( "bracket" ) ; if ( bracketAttr && bracketAttr . value === "yes" ) { bracketed = true ; } if ( type === "start" ) { let tupletLabelNumber = 0 ; let timeModNode = node . element ( "time-modification" ) ; if ( timeModNode ) { timeModNode = timeModNode . element ( "actual-notes" ) ; } if ( timeModNode ) { tupletLabelNumber = parseInt ( timeModNode . value , 10 ) ; if ( isNaN ( tupletLabelNumber ) ) { const errorMsg = ITextTranslation . translateText ( "ReaderErrorMessages/TupletNoteDurationError" , "Invalid tuplet note duration." ) ; this . musicSheet . SheetErrors . pushMeasureError ( errorMsg ) ; throw new MusicSheetReadingException ( errorMsg ) ; } } if ( noTupletNumbering ) { this . openTupletNumber ++ ; tupletnumber = this . openTupletNumber ; } let tuplet = this . tupletDict [ tupletnumber ] ; if ( ! tuplet ) { tuplet = this . tupletDict [ tupletnumber ] = new Tuplet ( tupletLabelNumber , bracketed ) ; tuplet . tupletLabelNumberPlacement = PlacementEnum . Above ; } const placementAttr = n . attribute ( "placement" ) ; if ( placementAttr ) { if ( placementAttr . value === "below" ) { tuplet . tupletLabelNumberPlacement = PlacementEnum . Below ; } else { tuplet . tupletLabelNumberPlacement = PlacementEnum . Above ; } tuplet . PlacementFromXml = true ; } const subnotelist = [ ] ; subnotelist . push ( this . currentNote ) ; tuplet . Notes . push ( subnotelist ) ; tuplet . Fractions . push ( this . getTupletNoteDurationFromType ( node ) ) ; this . currentNote . NoteTuplet = tuplet ; this . openTupletNumber = tupletnumber ; } else if ( type === "stop" ) { if ( noTupletNumbering ) { tupletnumber = this . openTupletNumber ; } const tuplet = this . tupletDict [ this . openTupletNumber ] ; if ( tuplet ) { const placementAttr = n . attribute ( "placement" ) ; if ( placementAttr ) { if ( placementAttr . value === "below" ) { tuplet . tupletLabelNumberPlacement = PlacementEnum . Below ; } else { tuplet . tupletLabelNumberPlacement = PlacementEnum . Above ; } tuplet . PlacementFromXml = true ; } const subnotelist = [ ] ; subnotelist . push ( this . currentNote ) ; tuplet . Notes . push ( subnotelist ) ; if ( ! tuplet . PlacementFromXml ) { let foundNonRest = false ; for ( const subList of tuplet . Notes ) { for ( const note of subList ) { if ( ! note . isRest ( ) ) { if ( note . StemDirectionXml === StemDirectionType . Down ) { tuplet . tupletLabelNumberPlacement = PlacementEnum . Below ; } else { tuplet . tupletLabelNumberPlacement = PlacementEnum . Above ; } foundNonRest = true ; break ; } } if ( foundNonRest ) { break ; } } } tuplet . Fractions . push ( this . getTupletNoteDurationFromType ( node ) ) ; this . currentNote . NoteTuplet = tuplet ; if ( Object . keys ( this . tupletDict ) . length === 0 ) { this . openTupletNumber = 0 ; } else if ( Object . keys ( this . tupletDict ) . length > 1 ) { this . openTupletNumber -- ; } delete this . tupletDict [ tupletnumber ] ; } } } } return this . openTupletNumber ; } param noteNode private handleTimeModificationNode ( noteNode ) { if ( this . tupletDict [ this . openTupletNumber ] ) { try { const tuplet = this . tupletDict [ this . openTupletNumber ] ; const notes = CollectionUtil . last ( tuplet . Notes ) ; const lastTupletVoiceEntry = notes [ 0 ] . ParentVoiceEntry ; let noteList ; if ( lastTupletVoiceEntry . Timestamp . Equals ( this . currentVoiceEntry . Timestamp ) ) { noteList = notes ; } else { noteList = [ ] ; tuplet . Notes . push ( noteList ) ; tuplet . Fractions . push ( this . getTupletNoteDurationFromType ( noteNode ) ) ; } noteList . push ( this . currentNote ) ; this . currentNote . NoteTuplet = tuplet ; } catch ( ex ) { const errorMsg = ITextTranslation . translateText ( "ReaderErrorMessages/TupletNumberError" , "Invalid tuplet number." ) ; this . musicSheet . SheetErrors . pushMeasureError ( errorMsg ) ; throw ex ; } } else if ( this . currentVoiceEntry . Notes . length > 0 ) { const firstNote = this . currentVoiceEntry . Notes [ 0 ] ; if ( firstNote . NoteTuplet ) { const tuplet = firstNote . NoteTuplet ; const notes = CollectionUtil . last ( tuplet . Notes ) ; notes . push ( this . currentNote ) ; this . currentNote . NoteTuplet = tuplet ; } } } private addTie ( tieNodeList , measureStartAbsoluteTimestamp , maxTieNoteFraction , tieType ) { if ( tieNodeList ) { for ( let i = 0 ; i < tieNodeList . length ; i ++ ) { const tieNode = tieNodeList [ i ] ; if ( tieNode !== undefined && tieNode . attributes ( ) ) { let tieDirection = PlacementEnum . NotYetDefined ; const placementAttr = tieNode . attribute ( "placement" ) ; if ( placementAttr ) { if ( placementAttr . value === "above" ) { tieDirection = PlacementEnum . Above ; } else if ( placementAttr . value === "below" ) { tieDirection = PlacementEnum . Below ; } } const orientationAttr = tieNode . attribute ( "orientation" ) ; if ( orientationAttr ) { if ( orientationAttr . value === "over" ) { tieDirection = PlacementEnum . Above ; } else if ( orientationAttr . value === "under" ) { tieDirection = PlacementEnum . Below ; } } const type = tieNode . attribute ( "type" ) . value ; if ( type === "start" && i === 0 ) { tieNodeList . push ( tieNode ) ; continue ; } try { if ( type === "start" ) { const num = this . findCurrentNoteInTieDict ( this . currentNote ) ; if ( num < 0 ) { delete this . openTieDict [ num ] ; } const newTieNumber = this . getNextAvailableNumberForTie ( ) ; const tie = new Tie ( this . currentNote , tieType ) ; this . openTieDict [ newTieNumber ] = tie ; tie . TieNumber = newTieNumber ; tie . TieDirection = tieDirection ; } else if ( type === "stop" ) { const tieNumber = this . findCurrentNoteInTieDict ( this . currentNote ) ; const tie = this . openTieDict [ tieNumber ] ; if ( tie ) { tie . AddNote ( this . currentNote , false ) ; delete this . openTieDict [ tieNumber ] ; } } } catch ( err ) { const errorMsg = ITextTranslation . translateText ( "ReaderErrorMessages/TieError" , "Error while reading tie." ) ; this . musicSheet . SheetErrors . pushMeasureError ( errorMsg ) ; } } } } } returns { number } private getNextAvailableNumberForTie ( ) { const keys = Object . keys ( this . openTieDict ) ; if ( keys . length === 0 ) { return 1 ; } keys . sort ( ( a , b ) => ( + a - + b ) ) ; for ( let i = 0 ; i < keys . length ; i ++ ) { if ( "" + ( i + 1 ) !== keys [ i ] ) { return i + 1 ; } } return + ( keys [ keys . length - 1 ] ) + 1 ; } param candidateNote returns { number } private findCurrentNoteInTieDict ( candidateNote ) { const openTieDict = this . openTieDict ; for ( const key in openTieDict ) { if ( openTieDict . hasOwnProperty ( key ) ) { const tie = openTieDict [ key ] ; const tieTabNote = tie . Notes [ 0 ] as TabNote ; const tieCandidateNote = candidateNote as TabNote ; if ( tie . Pitch . FundamentalNote === candidateNote . Pitch . FundamentalNote && tie . Pitch . Octave === candidateNote . Pitch . Octave ) { return parseInt ( key , 10 ) ; } else if ( tieTabNote . StringNumberTab !== undefined ) { if ( tieTabNote . StringNumberTab === tieCandidateNote . StringNumberTab ) { return parseInt ( key , 10 ) ; } } } } return - 1 ; } param xmlNode returns { any } private getTupletNoteDurationFromType ( xmlNode ) { if ( xmlNode . element ( "type" ) ) { const typeNode = xmlNode . element ( "type" ) ; if ( typeNode ) { const type = typeNode . value ; try { return NoteTypeHandler . getNoteDurationFromType ( type ) ; } catch ( e ) { const errorMsg = ITextTranslation . translateText ( "ReaderErrorMessages/NoteDurationError" , "Invalid note duration." ) ; this . musicSheet . SheetErrors . pushMeasureError ( errorMsg ) ; throw new MusicSheetReadingException ( "" , e ) ; } } } return undefined ; } }