@@ -43,12 +43,20 @@ public void RefreshMixers(int hardwareSampleRate)
4343 lastHardwareSampleRate = hardwareSampleRate ;
4444
4545 Console . WriteLine ( $ "[osu!] Oboe redirector: Refreshing mixers with rate { lastHardwareSampleRate } Hz") ;
46- restoreDefaultAudio ( ) ;
46+
47+ // Clean up previous state but keep the silencing if we are already silenced.
48+ ActiveMasterMixer = 0 ;
49+ if ( masterMixer != 0 )
50+ {
51+ Bass . StreamFree ( masterMixer ) ;
52+ masterMixer = 0 ;
53+ }
54+
55+ restoreToParents ( ) ;
4756 mixerHandles . Clear ( ) ;
4857 originalParents . Clear ( ) ;
4958
5059 sampleRate = lastHardwareSampleRate ;
51- mixerHandles . Clear ( ) ;
5260
5361 addRootMixer ( audioManager . TrackMixer ) ;
5462 addRootMixer ( audioManager . SampleMixer ) ;
@@ -81,8 +89,6 @@ public void RefreshMixers(int hardwareSampleRate)
8189 {
8290 Console . WriteLine ( "[osu!] Oboe redirector: Failed to setup master mixer, restoring default audio." ) ;
8391 restoreDefaultAudio ( ) ;
84- mixerHandles . Clear ( ) ;
85- originalParents . Clear ( ) ;
8692 return ;
8793 }
8894
@@ -92,19 +98,28 @@ public void RefreshMixers(int hardwareSampleRate)
9298
9399 private IEnumerable < AudioMixer > getActiveMixers ( )
94100 {
95- // Use reflection to access the internal activeMixers list in AudioManager.
96- // In the official framework, it is an internal BindableList<AudioMixer> activeMixers.
97- FieldInfo ? field = typeof ( AudioManager ) . GetField ( "activeMixers" , BindingFlags . Instance | BindingFlags . NonPublic ) ;
98- if ( field == null ) yield break ;
101+ // Exhaustive search for the activeMixers list in AudioManager.
102+ Type type = typeof ( AudioManager ) ;
99103
100- object ? val = field . GetValue ( audioManager ) ;
101- if ( val is IEnumerable enumerable )
104+ while ( type != null && type != typeof ( object ) )
102105 {
103- foreach ( var item in enumerable )
106+ foreach ( var field in type . GetFields ( BindingFlags . Instance | BindingFlags . Public | BindingFlags . NonPublic ) )
104107 {
105- if ( item is AudioMixer mixer )
106- yield return mixer ;
108+ if ( field . FieldType . IsGenericType && field . FieldType . GetGenericArguments ( ) . Contains ( typeof ( AudioMixer ) ) )
109+ {
110+ object ? val = field . GetValue ( audioManager ) ;
111+ if ( val is IEnumerable enumerable )
112+ {
113+ foreach ( var item in enumerable )
114+ {
115+ if ( item is AudioMixer mixer )
116+ yield return mixer ;
117+ }
118+ yield break ;
119+ }
120+ }
107121 }
122+ type = type . BaseType ! ;
108123 }
109124 }
110125
@@ -129,6 +144,7 @@ private bool setupMasterMixer()
129144 return false ;
130145 }
131146
147+ // Disable BASS-internal buffering for the lowest possible latency.
132148 Bass . ChannelSetAttribute ( masterMixer , ChannelAttribute . Buffer , 0 ) ;
133149
134150 int successfullyAdded = 0 ;
@@ -137,7 +153,7 @@ private bool setupMasterMixer()
137153 {
138154 int parent = BassMix . ChannelGetMixer ( handle ) ;
139155
140- if ( parent != 0 )
156+ if ( parent != 0 && parent != masterMixer )
141157 {
142158 originalParents [ handle ] = parent ;
143159 BassMix . MixerRemoveChannel ( handle ) ;
@@ -149,7 +165,7 @@ private bool setupMasterMixer()
149165 if ( ! Bass . ChannelSetDevice ( handle , 0 ) )
150166 {
151167 Console . WriteLine ( $ "[osu!] Failed to move source mixer { handle } to silent device: { Bass . LastError } ") ;
152- continue ;
168+ // Try to proceed anyway, as some devices might behave strangely with device 0.
153169 }
154170 }
155171
@@ -186,6 +202,20 @@ private bool silenceDefaultAudio()
186202 }
187203 }
188204
205+ private void restoreToParents ( )
206+ {
207+ foreach ( var kvp in originalParents )
208+ {
209+ int handle = kvp . Key ;
210+ int parent = kvp . Value ;
211+
212+ BassMix . MixerRemoveChannel ( handle ) ;
213+ Bass . ChannelSetDevice ( handle , 1 ) ;
214+ BassMix . MixerAddChannel ( parent , handle , BassFlags . MixerChanNoRampin ) ;
215+ }
216+ originalParents . Clear ( ) ;
217+ }
218+
189219 private void restoreDefaultAudio ( )
190220 {
191221 ActiveMasterMixer = 0 ;
@@ -198,16 +228,7 @@ private void restoreDefaultAudio()
198228 masterMixer = 0 ;
199229 }
200230
201- // Restore hijacked mixers to their original parents.
202- foreach ( var kvp in originalParents )
203- {
204- int handle = kvp . Key ;
205- int parent = kvp . Value ;
206-
207- BassMix . MixerRemoveChannel ( handle ) ;
208- Bass . ChannelSetDevice ( handle , 1 ) ;
209- BassMix . MixerAddChannel ( parent , handle , BassFlags . MixerChanNoRampin ) ;
210- }
231+ restoreToParents ( ) ;
211232
212233 // Restore any other discovered mixers to the default device.
213234 foreach ( int handle in mixerHandles )
@@ -218,7 +239,6 @@ private void restoreDefaultAudio()
218239 Bass . ChannelSetDevice ( handle , 1 ) ;
219240 }
220241
221- originalParents . Clear ( ) ;
222242 devicesSilenced = false ;
223243 }
224244 catch ( Exception e )
@@ -261,31 +281,29 @@ private int findHandle(object obj)
261281
262282 while ( type != null && type != typeof ( object ) )
263283 {
284+ // Broad search for anything that looks like a BASS handle.
285+ // We check int, long, and IntPtr as different wrappers use different types.
264286 foreach ( var field in type . GetFields ( BindingFlags . Instance | BindingFlags . Public | BindingFlags . NonPublic ) )
265287 {
266- if ( field . FieldType == typeof ( int ) || field . FieldType == typeof ( IntPtr ) )
288+ if ( isHandleType ( field . FieldType ) )
267289 {
268290 string name = field . Name . ToLowerInvariant ( ) ;
269-
270- if ( name . Contains ( "handle" ) || name . Contains ( "mixer" ) )
291+ if ( name . Contains ( "handle" ) || name . Contains ( "mixer" ) || name . Contains ( "id" ) || name . Contains ( "stream" ) || name . Contains ( "channel" ) )
271292 {
272293 int h = convertToHandle ( field . GetValue ( obj ) ) ;
273-
274294 if ( h != 0 ) return h ;
275295 }
276296 }
277297 }
278298
279299 foreach ( var prop in type . GetProperties ( BindingFlags . Instance | BindingFlags . Public | BindingFlags . NonPublic ) )
280300 {
281- if ( prop . PropertyType == typeof ( int ) || prop . PropertyType == typeof ( IntPtr ) )
301+ if ( isHandleType ( prop . PropertyType ) )
282302 {
283303 string name = prop . Name . ToLowerInvariant ( ) ;
284-
285- if ( name . Contains ( "handle" ) || name . Contains ( "mixer" ) )
304+ if ( name . Contains ( "handle" ) || name . Contains ( "mixer" ) || name . Contains ( "id" ) || name . Contains ( "stream" ) || name . Contains ( "channel" ) )
286305 {
287306 int h = convertToHandle ( prop . GetValue ( obj ) ) ;
288-
289307 if ( h != 0 ) return h ;
290308 }
291309 }
@@ -297,16 +315,14 @@ private int findHandle(object obj)
297315 return 0 ;
298316 }
299317
318+ private bool isHandleType ( Type type ) => type == typeof ( int ) || type == typeof ( IntPtr ) || type == typeof ( long ) ;
319+
300320 private int convertToHandle ( object ? val )
301321 {
302322 if ( val == null ) return 0 ;
303-
304323 if ( val is int ih ) return ih ;
305-
306324 if ( val is long lh ) return ( int ) lh ;
307-
308325 if ( val is IntPtr ph ) return ( int ) ph . ToInt64 ( ) ;
309-
310326 return 0 ;
311327 }
312328
@@ -317,7 +333,7 @@ private static int provideAudio(IntPtr audioData, int numFrames)
317333
318334 if ( mixer == 0 ) return 0 ;
319335
320- int bytesToRead = numFrames * 8 ;
336+ int bytesToRead = numFrames * 8 ; // 2 channels * 4 bytes (float)
321337 int bytesRead = Bass . ChannelGetData ( mixer , audioData , bytesToRead ) ;
322338
323339 if ( bytesRead <= 0 ) return 0 ;
0 commit comments