1- import { GeneralMidi } from '@coderline/alphatab/midi/GeneralMidi' ;
2-
31import { ScoreImporter } from '@coderline/alphatab/importer/ScoreImporter' ;
42import { UnsupportedFormatError } from '@coderline/alphatab/importer/UnsupportedFormatError' ;
5-
63import { IOHelper } from '@coderline/alphatab/io/IOHelper' ;
7- import { OverflowError , type IReadable } from '@coderline/alphatab/io/IReadable' ;
4+ import { type IReadable , OverflowError } from '@coderline/alphatab/io/IReadable' ;
5+ import type { IWriteable } from '@coderline/alphatab/io/IWriteable' ;
6+ import { Logger } from '@coderline/alphatab/Logger' ;
7+ import { GeneralMidi } from '@coderline/alphatab/midi/GeneralMidi' ;
88import { AccentuationType } from '@coderline/alphatab/model/AccentuationType' ;
9+ import { AccidentalType } from '@coderline/alphatab/model/AccidentalType' ;
910import { Automation , AutomationType } from '@coderline/alphatab/model/Automation' ;
1011import { Bar , BarLineStyle } from '@coderline/alphatab/model/Bar' ;
1112import { Beat , BeatBeamingMode } from '@coderline/alphatab/model/Beat' ;
@@ -14,38 +15,34 @@ import { BrushType } from '@coderline/alphatab/model/BrushType';
1415import { Chord } from '@coderline/alphatab/model/Chord' ;
1516import { Clef } from '@coderline/alphatab/model/Clef' ;
1617import { Color } from '@coderline/alphatab/model/Color' ;
18+ import { Direction } from '@coderline/alphatab/model/Direction' ;
1719import { Duration } from '@coderline/alphatab/model/Duration' ;
1820import { DynamicValue } from '@coderline/alphatab/model/DynamicValue' ;
21+ import { FadeType } from '@coderline/alphatab/model/FadeType' ;
1922import type { Fingers } from '@coderline/alphatab/model/Fingers' ;
2023import { GraceType } from '@coderline/alphatab/model/GraceType' ;
2124import { HarmonicType } from '@coderline/alphatab/model/HarmonicType' ;
2225import type { KeySignature } from '@coderline/alphatab/model/KeySignature' ;
2326import type { KeySignatureType } from '@coderline/alphatab/model/KeySignatureType' ;
2427import { Lyrics } from '@coderline/alphatab/model/Lyrics' ;
2528import { MasterBar } from '@coderline/alphatab/model/MasterBar' ;
29+ import { ModelUtils } from '@coderline/alphatab/model/ModelUtils' ;
2630import { Note } from '@coderline/alphatab/model/Note' ;
2731import { NoteAccidentalMode } from '@coderline/alphatab/model/NoteAccidentalMode' ;
32+ import { Ottavia } from '@coderline/alphatab/model/Ottavia' ;
2833import { PickStroke } from '@coderline/alphatab/model/PickStroke' ;
2934import { PlaybackInformation } from '@coderline/alphatab/model/PlaybackInformation' ;
35+ import { Rasgueado } from '@coderline/alphatab/model/Rasgueado' ;
3036import { Score , ScoreSubElement } from '@coderline/alphatab/model/Score' ;
3137import { Section } from '@coderline/alphatab/model/Section' ;
3238import { SlideInType } from '@coderline/alphatab/model/SlideInType' ;
3339import { SlideOutType } from '@coderline/alphatab/model/SlideOutType' ;
3440import type { Staff } from '@coderline/alphatab/model/Staff' ;
3541import { Track } from '@coderline/alphatab/model/Track' ;
42+ import { TremoloPickingEffect } from '@coderline/alphatab/model/TremoloPickingEffect' ;
3643import { TripletFeel } from '@coderline/alphatab/model/TripletFeel' ;
3744import { VibratoType } from '@coderline/alphatab/model/VibratoType' ;
3845import { Voice } from '@coderline/alphatab/model/Voice' ;
39-
40- import type { IWriteable } from '@coderline/alphatab/io/IWriteable' ;
41- import { Logger } from '@coderline/alphatab/Logger' ;
42- import { AccidentalType } from '@coderline/alphatab/model/AccidentalType' ;
43- import { Direction } from '@coderline/alphatab/model/Direction' ;
44- import { FadeType } from '@coderline/alphatab/model/FadeType' ;
45- import { ModelUtils } from '@coderline/alphatab/model/ModelUtils' ;
46- import { Ottavia } from '@coderline/alphatab/model/Ottavia' ;
47- import { Rasgueado } from '@coderline/alphatab/model/Rasgueado' ;
48- import { TremoloPickingEffect } from '@coderline/alphatab/model/TremoloPickingEffect' ;
4946import { WahPedal } from '@coderline/alphatab/model/WahPedal' ;
5047import { BeamDirection } from '@coderline/alphatab/rendering/utils/BeamDirection' ;
5148
@@ -807,6 +804,33 @@ export class Gp3To5Importer extends ScoreImporter {
807804 if ( beatCount === 0 ) {
808805 return ;
809806 }
807+
808+ //
809+ // fast path where we know we read the full voice
810+ // - its the first voice to read
811+ // - we are multivoice and know we can read all voices in full extend
812+ const currentVoiceCount = bar . index === 0 ? 1 : bar . previousBar ! . voices . length ;
813+ if ( bar . voices . length === 0 || currentVoiceCount === 2 ) {
814+ this . _readAndChainVoice ( track , bar , beatCount ) ;
815+ return ;
816+ }
817+
818+ //
819+ // fallback to detection of empty voices we can skip.
820+ // GP5 empty second voices are always a single beat with the isEmpty flag,
821+ // so a beat count above 1 is guaranteed to be real content.
822+ if ( beatCount === 1 && this . _skipEmptyVoice ( ) ) {
823+ return ;
824+ }
825+
826+ //
827+ // secondary fallback: the first filled second voice for this staff.
828+ // we have to backfill and read fully.
829+ ModelUtils . backfillStaffVoices ( bar . staff , 2 ) ;
830+ this . _readAndChainVoice ( track , bar , beatCount ) ;
831+ }
832+
833+ private _readAndChainVoice ( track : Track , bar : Bar , beatCount : number ) {
810834 const newVoice : Voice = new Voice ( ) ;
811835 bar . addVoice ( newVoice ) ;
812836
@@ -816,6 +840,38 @@ export class Gp3To5Importer extends ScoreImporter {
816840 }
817841 }
818842
843+ /**
844+ * Attempts to skip a fully empty voice.
845+ * @returns true if we detected an empty voice, false if the beat was not empty and a full voice has to be read.
846+ */
847+ private _skipEmptyVoice ( ) : boolean {
848+ const startOfVoice = this . data . position ;
849+
850+ // aligned with readBeat, find out if we are having an empty beat.
851+ const flags = this . data . readByte ( ) ;
852+ let isEmpty = false ;
853+ if ( ( flags & 0x40 ) !== 0 ) {
854+ const type = this . data . readByte ( ) ;
855+ isEmpty = ( type & 0x02 ) === 0 ;
856+ }
857+
858+ // NOTE: we don't check any of the other flags on an empty voice, we assume no effects are set to be read.
859+
860+ // seek back to start of voice for a full read; costs a duplicate read of these bytes.
861+ if ( ! isEmpty ) {
862+ this . data . position = startOfVoice ;
863+ return false ;
864+ }
865+
866+ // read the remaining bytes of the empty beat to avoid input stream desync (aligned with readBeat).
867+ this . data . skip ( 2 ) ; // duration + stringFlags
868+ if ( this . _versionNumber >= 500 ) {
869+ this . data . skip ( 2 ) ; // flags2
870+ }
871+
872+ return true ;
873+ }
874+
819875 public readBeat ( track : Track , bar : Bar , voice : Voice ) : void {
820876 const newBeat : Beat = new Beat ( ) ;
821877 const flags : number = this . data . readByte ( ) ;
0 commit comments