1414using Serilog ;
1515
1616namespace OpenUtau . Core {
17- public class SineGen : ISampleProvider {
17+ public class SineGenerator : ISampleProvider {
1818 public WaveFormat WaveFormat => waveFormat ;
19- public double Freq { get ; set ; }
20- public bool Stop { get ; set ; }
2119 private WaveFormat waveFormat ;
22- private double phase ;
23- private double gain ;
24- public SineGen ( ) {
25- waveFormat = WaveFormat . CreateIeeeFloatWaveFormat ( 44100 , 1 ) ;
26- Freq = 440 ;
27- gain = 1 ;
20+
21+ private readonly double attackSampleCount ;
22+ private readonly double releaseSampleCount ;
23+
24+ public double freq { get ; set ; }
25+
26+ private int position ;
27+ private int releasePosition = 0 ;
28+ private float gain = 1 ;
29+
30+ public bool isActive { get ; private set ; } = true ;
31+ public bool isPlaying { get ; private set ; } = true ;
32+
33+ public SineGenerator ( double freq , float gain , int attackMs = 25 , int releaseMs = 25 ) {
34+ waveFormat = WaveFormat . CreateIeeeFloatWaveFormat ( 44100 , 2 ) ;
35+ this . freq = freq ;
36+ this . gain = gain ;
37+ position = 0 ;
38+
39+ // Number of samples the attack & release fades take
40+ attackSampleCount = ( attackMs / 1000.0f ) * waveFormat . SampleRate ;
41+ releaseSampleCount = ( releaseMs / 1000.0f ) * waveFormat . SampleRate ;
2842 }
43+
2944 public int Read ( float [ ] buffer , int offset , int count ) {
30- double delta = 2 * Math . PI * Freq / waveFormat . SampleRate ;
31- for ( int i = 0 ; i < count ; i ++ ) {
32- if ( Stop ) {
33- gain = Math . Max ( 0 , gain - 0.01 ) ;
45+ // Duplicate sample across two channels
46+ for ( int i = 0 ; i < count / 2 ; i ++ ) {
47+ float sample = GetNextSample ( ) ;
48+ buffer [ offset + ( i * 2 ) ] += ( float ) sample * gain ;
49+ buffer [ offset + ( i * 2 ) + 1 ] += ( float ) sample * gain ;
50+ }
51+ return count ;
52+ }
53+
54+ private float GetNextSample ( ) {
55+ double delta = 2 * Math . PI * freq / waveFormat . SampleRate ;
56+ double sample = Math . Sin ( position * delta ) ;
57+
58+ // Calculate attack envelope
59+ sample *= Math . Clamp ( position / attackSampleCount , 0 , 1 ) ;
60+
61+ // Calculate release envelope
62+ double releaseEnvelope = 1 ;
63+ if ( ! isActive ) {
64+ releaseEnvelope = Math . Clamp ( 1.0f - ( ( position - releasePosition ) / releaseSampleCount ) , 0 , 1 ) ;
65+ }
66+ sample *= releaseEnvelope ;
67+
68+ if ( releaseEnvelope < double . Epsilon ) {
69+ // Stop sampling this generator if release is completed
70+ // Instance will be cleaned up later
71+ isPlaying = false ;
72+ }
73+
74+ position ++ ;
75+ return ( float ) sample * gain ;
76+ }
77+
78+ public void Stop ( ) {
79+ if ( ! isActive ) return ;
80+
81+ isActive = false ;
82+ releasePosition = position ;
83+ }
84+ }
85+
86+ public class ToneGenerator : ISignalSource {
87+ private Dictionary < double , SineGenerator > activeFrequencies = new Dictionary < double , SineGenerator > ( ) ;
88+ private List < SineGenerator > inactiveFrequencies = new List < SineGenerator > ( ) ;
89+ private readonly float gain = 0.4f ;
90+
91+ private readonly object _lockObj = new object ( ) ;
92+
93+ public ToneGenerator ( ) { }
94+
95+ public ToneGenerator ( float gain ) {
96+ this . gain = gain ;
97+ }
98+
99+ public bool IsReady ( int position , int count ) {
100+ return true ;
101+ }
102+
103+ public int Mix ( int position , float [ ] buffer , int offset , int count ) {
104+ lock ( _lockObj ) {
105+ foreach ( var freqEntry in activeFrequencies ) {
106+ if ( freqEntry . Value . isPlaying ) {
107+ freqEntry . Value . Read ( buffer , offset , count ) ;
108+ }
34109 }
35- if ( gain == 0 ) {
36- return i ;
110+ foreach ( var generator in inactiveFrequencies ) {
111+ if ( generator . isPlaying ) {
112+ generator . Read ( buffer , offset , count ) ;
113+ }
37114 }
38- phase += delta ;
39- double sampleValue = Math . Sin ( phase ) * 0.2 * gain ;
40- buffer [ offset ++ ] = ( float ) sampleValue ;
41115 }
42- return count ;
116+
117+ return position + count ;
118+ }
119+ public void StartTone ( double freq ) {
120+ if ( activeFrequencies . ContainsKey ( freq ) ) {
121+ if ( activeFrequencies [ freq ] . isActive ) {
122+ // Don't cut off tone to replace with the same frequency
123+ // Should never happen
124+ return ;
125+ }
126+ }
127+
128+ lock ( _lockObj ) {
129+ activeFrequencies [ freq ] = new SineGenerator ( freq , gain ) ;
130+ }
131+ }
132+
133+ public void EndTone ( double freq ) {
134+ if ( activeFrequencies . ContainsKey ( freq ) ) {
135+ activeFrequencies [ freq ] . Stop ( ) ;
136+
137+ lock ( _lockObj ) {
138+ // Move to inactive frequencies list
139+ inactiveFrequencies . Add ( activeFrequencies [ freq ] ) ;
140+ activeFrequencies . Remove ( freq ) ;
141+ }
142+ }
143+
144+ CleanupTones ( ) ;
145+ }
146+
147+ public void EndAllTones ( ) {
148+ foreach ( var tone in activeFrequencies ) {
149+ tone . Value . Stop ( ) ;
150+
151+ lock ( _lockObj ) {
152+ // Move to inactive frequencies list
153+ inactiveFrequencies . Add ( tone . Value ) ;
154+ activeFrequencies . Remove ( tone . Key ) ;
155+ }
156+ }
157+
158+
159+ CleanupTones ( ) ;
160+ }
161+
162+ private void CleanupTones ( ) {
163+ lock ( _lockObj ) {
164+ inactiveFrequencies . RemoveAll ( gen => ! gen . isPlaying ) ;
165+ }
43166 }
44167 }
45168
@@ -52,17 +175,24 @@ private PlaybackManager() {
52175 } catch ( Exception e ) {
53176 Log . Error ( e , "Failed to release source temp." ) ;
54177 }
178+
179+ toneGenerator = new ToneGenerator ( ) ;
180+ editingMix = new MasterAdapter ( toneGenerator ) ;
55181 }
56182
183+ public readonly ToneGenerator toneGenerator ;
57184 List < Fader > faders ;
58185 MasterAdapter masterMix ;
186+ MasterAdapter editingMix ;
187+
59188 double startMs ;
60189 public int StartTick => DocManager . Inst . Project . timeAxis . MsPosToTickPos ( startMs ) ;
61190 CancellationTokenSource renderCancellation ;
62191
63192 public Audio . IAudioOutput AudioOutput { get ; set ; } = new Audio . DummyAudioOutput ( ) ;
64- public bool Playing => AudioOutput . PlaybackState == PlaybackState . Playing ;
193+ public bool OutputActive => AudioOutput . PlaybackState == PlaybackState . Playing ;
65194 public bool StartingToPlay { get ; private set ; }
195+ public bool PlayingMaster { get ; private set ; }
66196
67197 public void PlayTestSound ( ) {
68198 masterMix = null ;
@@ -71,15 +201,23 @@ public void PlayTestSound() {
71201 AudioOutput . Play ( ) ;
72202 }
73203
74- public SineGen PlayTone ( double freq ) {
75- masterMix = null ;
76- AudioOutput . Stop ( ) ;
77- var sineGen = new SineGen ( ) {
78- Freq = freq ,
79- } ;
80- AudioOutput . Init ( sineGen ) ;
81- AudioOutput . Play ( ) ;
82- return sineGen ;
204+ public void PlayTone ( double freq ) {
205+ toneGenerator . StartTone ( freq ) ;
206+
207+ // If nothing is playing, start editing mix
208+ if ( ! OutputActive ) {
209+ AudioOutput . Stop ( ) ;
210+ AudioOutput . Init ( editingMix ) ;
211+ AudioOutput . Play ( ) ;
212+ }
213+ }
214+
215+ public void EndTone ( double freq ) {
216+ toneGenerator . EndTone ( freq ) ;
217+ }
218+
219+ public void EndAllTones ( ) {
220+ toneGenerator . EndAllTones ( ) ;
83221 }
84222
85223 public void PlayFile ( string file ) {
@@ -98,7 +236,7 @@ public void PlayFile(string file) {
98236 }
99237
100238 public void PlayOrPause ( int tick = - 1 , int endTick = - 1 , int trackNo = - 1 ) {
101- if ( Playing ) {
239+ if ( PlayingMaster ) {
102240 PausePlayback ( ) ;
103241 } else {
104242 Play (
@@ -111,6 +249,7 @@ public void PlayOrPause(int tick = -1, int endTick = -1, int trackNo = -1) {
111249
112250 public void Play ( UProject project , int tick , int endTick = - 1 , int trackNo = - 1 ) {
113251 if ( AudioOutput . PlaybackState == PlaybackState . Paused ) {
252+ PlayingMaster = true ;
114253 AudioOutput . Play ( ) ;
115254 return ;
116255 }
@@ -121,13 +260,17 @@ public void Play(UProject project, int tick, int endTick = -1, int trackNo = -1)
121260
122261 public void StopPlayback ( ) {
123262 AudioOutput . Stop ( ) ;
263+ PlayingMaster = false ;
124264 }
125265
126266 public void PausePlayback ( ) {
127267 AudioOutput . Pause ( ) ;
268+ PlayingMaster = false ;
128269 }
129270
130271 private void StartPlayback ( double startMs , MasterAdapter masterAdapter ) {
272+ toneGenerator . EndAllTones ( ) ;
273+
131274 this . startMs = startMs ;
132275 var start = TimeSpan . FromMilliseconds ( startMs ) ;
133276 Log . Information ( $ "StartPlayback at { start } ") ;
@@ -158,7 +301,7 @@ private void Render(UProject project, int tick, int endTick, int trackNo) {
158301 }
159302
160303 public void UpdatePlayPos ( ) {
161- if ( AudioOutput != null && AudioOutput . PlaybackState == PlaybackState . Playing && masterMix != null ) {
304+ if ( AudioOutput != null && AudioOutput . PlaybackState == PlaybackState . Playing && PlayingMaster ) {
162305 double ms = ( AudioOutput . GetPosition ( ) / sizeof ( float ) - masterMix . Waited / 2 ) * 1000.0 / 44100 ;
163306 int tick = DocManager . Inst . Project . timeAxis . MsPosToTickPos ( startMs + ms ) ;
164307 DocManager . Inst . ExecuteCmd ( new SetPlayPosTickNotification ( tick , masterMix . IsWaiting ) ) ;
0 commit comments