1- using AlphaTab . Audio ;
2- using AlphaTab . Collections ;
1+ using AlphaTab . Collections ;
32
43namespace AlphaTab . Model
54{
@@ -8,6 +7,9 @@ namespace AlphaTab.Model
87 /// </summary>
98 public class TupletGroup
109 {
10+ public int _totalDuration ;
11+ private bool _isEqualLengthTuplet = true ;
12+
1113 /// <summary>
1214 /// Gets or sets the list of beats contained in this group.
1315 /// </summary>
@@ -18,15 +20,6 @@ public class TupletGroup
1820 /// </summary>
1921 public Voice Voice { get ; set ; }
2022
21- /// <summary>
22- /// Gets the absolute midi tick start when this tuplet group starts.
23- /// </summary>
24- public int TupletStart { get ; set ; }
25-
26- /// <summary>
27- /// Gets the absolute midi tick start when this tuplet group ends.
28- /// </summary>
29- public int TupletEnd { get ; set ; }
3023
3124 /// <summary>
3225 /// Initializes a new instance of the <see cref="TupletGroup"/> class.
@@ -39,58 +32,93 @@ public TupletGroup(Voice voice)
3932 }
4033
4134 /// <summary>
42- /// Gets a value indicating whether the tuplet group is fully filled.
35+ /// Gets a value indicating whether the tuplet group is fully filled.
4336 /// </summary>
4437 public bool IsFull
4538 {
4639 get ;
4740 private set ;
4841 }
4942
50- private static readonly int FullThreshold = Duration . OneHundredTwentyEighth . ToTicks ( ) ;
43+
44+ private const int HalfTicks = 1920 ;
45+ private const int QuarterTicks = 960 ;
46+ private const int EighthTicks = 480 ;
47+ private const int SixteenthTicks = 240 ;
48+ private const int ThirtySecondTicks = 120 ;
49+ private const int SixtyFourthTicks = 60 ;
50+ private const int OneHundredTwentyEighthTicks = 30 ;
51+ private const int TwoHundredFiftySixthTicks = 15 ;
52+
53+
54+ private static readonly int [ ] AllTicks =
55+ {
56+ HalfTicks , QuarterTicks , EighthTicks , SixteenthTicks , ThirtySecondTicks , SixtyFourthTicks ,
57+ OneHundredTwentyEighthTicks , TwoHundredFiftySixthTicks
58+ } ;
5159
5260 internal bool Check ( Beat beat )
5361 {
5462 if ( Beats . Count == 0 )
5563 {
56- // calculate the range for which the tuplet will be valid. ("N notes sound like M")
57- // via this time range check we can have also tuplets with different durations like:
58- // /-----4:2-----\
59- // 4 2 16 16 16 16
60- // - the tuplet is filled fully according to the duration and displayed accordingly
61- //
62- // while with a pure counting we would have
63- // /--4:2--\ 4:2 4:2
64- // 4 2 16 16 16 16
65- // - the first tuplet would be treated as full because there are 4 notes in it, but
66- // duration wise it does not fill 2 quarter notes.
67- TupletStart = beat . AbsolutePlaybackStart ;
68- var beatDuration = beat . PlaybackDuration ;
69- if ( beat . GraceType == GraceType . None )
70- {
71- beatDuration = MidiUtils . RemoveTuplet ( beatDuration , beat . TupletNumerator , beat . TupletDenominator ) ;
72- }
73-
74- TupletEnd = TupletStart + beatDuration * beat . TupletDenominator ;
64+ // accept first beat
65+ Beats . Add ( beat ) ;
66+ _totalDuration += beat . PlaybackDuration ;
67+ return true ;
7568 }
76- else if ( beat . GraceType != GraceType . None )
69+
70+ if ( beat . GraceType != GraceType . None )
7771 {
78- // grace notes do not break tuplet group, but also do not contribute to them.
72+ // grace notes do not break tuplet group, but also do not contribute to them.
7973 return true ;
8074 }
81- else if ( beat . Voice != Voice || IsFull
82- || beat . TupletNumerator != Beats [ 0 ] . TupletNumerator ||
83- beat . TupletDenominator != Beats [ 0 ] . TupletDenominator
84- || beat . AbsolutePlaybackStart > TupletEnd )
75+
76+ if ( beat . Voice != Voice
77+ || IsFull
78+ || beat . TupletNumerator != Beats [ 0 ] . TupletNumerator
79+ || beat . TupletDenominator != Beats [ 0 ] . TupletDenominator )
8580 {
81+ // only same tuplets are potentially accepted
8682 return false ;
8783 }
8884
85+ // TBH: I do not really know how the 100% tuplet grouping of Guitar Pro might work
86+ // it sometimes has really strange rules where notes filling 3 quarters, are considered a full 3:2 tuplet
87+
88+ // in alphaTab we have now 2 rules where we consider a tuplet full:
89+ // 1. if all beats have the same length, the tuplet must contain N notes of an N:M tuplet
90+ // 2. if we have mixed beats, we check if the current set of beats, matches a N:M tuplet
91+ // by checking all potential note durations.
92+
93+ // this logic is very likely not 100% correct but for most cases the tuplets
94+ // appeared correct.
95+
96+ if ( beat . PlaybackDuration != Beats [ 0 ] . PlaybackDuration )
97+ {
98+ _isEqualLengthTuplet = false ;
99+ }
100+
89101 Beats . Add ( beat ) ;
90- var beatEnd = beat . AbsolutePlaybackStart + beat . PlaybackDuration ;
91- if ( TupletEnd < beatEnd + FullThreshold )
102+ _totalDuration += beat . PlaybackDuration ;
103+
104+ if ( _isEqualLengthTuplet )
92105 {
93- IsFull = true ;
106+ if ( Beats . Count == Beats [ 0 ] . TupletNumerator )
107+ {
108+ IsFull = true ;
109+ }
110+ }
111+ else
112+ {
113+ var factor = Beats [ 0 ] . TupletNumerator / Beats [ 0 ] . TupletDenominator ;
114+ foreach ( var potentialMatch in AllTicks )
115+ {
116+ if ( _totalDuration == potentialMatch * factor )
117+ {
118+ IsFull = true ;
119+ break ;
120+ }
121+ }
94122 }
95123
96124 return true ;
0 commit comments