99using System . Diagnostics ;
1010using System . Linq ;
1111using System . Threading ;
12+ using JetBrains . Annotations ;
1213using ManagedBass ;
1314using ManagedBass . Fx ;
1415using ManagedBass . Mix ;
1718using osu . Framework . Audio . Sample ;
1819using osu . Framework . Audio . Track ;
1920using osu . Framework . Bindables ;
21+ using osu . Framework . Configuration ;
2022using osu . Framework . Development ;
2123using osu . Framework . Extensions . TypeExtensions ;
2224using osu . Framework . IO . Stores ;
@@ -176,19 +178,27 @@ public class AudioManager : AudioCollectionManager<AudioComponent>
176178 /// <param name="audioThread">The host's audio thread.</param>
177179 /// <param name="trackStore">The resource store containing all audio tracks to be used in the future.</param>
178180 /// <param name="sampleStore">The sample store containing all audio samples to be used in the future.</param>
179- public AudioManager ( AudioThread audioThread , ResourceStore < byte [ ] > trackStore , ResourceStore < byte [ ] > sampleStore )
181+ /// <param name="config"></param>
182+ public AudioManager ( AudioThread audioThread , ResourceStore < byte [ ] > trackStore , ResourceStore < byte [ ] > sampleStore , [ CanBeNull ] FrameworkConfigManager config )
180183 {
181184 thread = audioThread ;
182185
183186 thread . RegisterManager ( this ) ;
184187
185- AudioDevice . ValueChanged += _ => onDeviceChanged ( ) ;
186- UseExperimentalWasapi . ValueChanged += _ => onDeviceChanged ( ) ;
187- GlobalMixerHandle . ValueChanged += handle =>
188+ if ( config != null )
188189 {
189- onDeviceChanged ( ) ;
190- usingGlobalMixer . Value = handle . NewValue . HasValue ;
191- } ;
190+ // attach config bindables
191+ config . BindWith ( FrameworkSetting . AudioDevice , AudioDevice ) ;
192+ config . BindWith ( FrameworkSetting . AudioUseExperimentalWasapi , UseExperimentalWasapi ) ;
193+ config . BindWith ( FrameworkSetting . VolumeUniversal , Volume ) ;
194+ config . BindWith ( FrameworkSetting . VolumeEffect , VolumeSample ) ;
195+ config . BindWith ( FrameworkSetting . VolumeMusic , VolumeTrack ) ;
196+ }
197+
198+ AudioDevice . ValueChanged += _ => scheduler . AddOnce ( initCurrentDevice ) ;
199+ UseExperimentalWasapi . ValueChanged += _ => scheduler . AddOnce ( initCurrentDevice ) ;
200+ // initCurrentDevice not required for changes to `GlobalMixerHandle` as it is only changed when experimental wasapi is toggled (handled above).
201+ GlobalMixerHandle . ValueChanged += handle => usingGlobalMixer . Value = handle . NewValue . HasValue ;
192202
193203 AddItem ( TrackMixer = createAudioMixer ( null , nameof ( TrackMixer ) ) ) ;
194204 AddItem ( SampleMixer = createAudioMixer ( null , nameof ( SampleMixer ) ) ) ;
@@ -209,12 +219,12 @@ public AudioManager(AudioThread audioThread, ResourceStore<byte[]> trackStore, R
209219 return store ;
210220 } ) ;
211221
212- CancellationToken token = cancelSource . Token ;
213-
214222 syncAudioDevices ( ) ;
223+
224+ // check for changes in any audio devices every 1000ms (slightly expensive operation)
225+ CancellationToken token = cancelSource . Token ;
215226 scheduler . AddDelayed ( ( ) =>
216227 {
217- // sync audioDevices every 1000ms
218228 new Thread ( ( ) =>
219229 {
220230 while ( ! token . IsCancellationRequested )
@@ -248,23 +258,6 @@ protected override void Dispose(bool disposing)
248258 base . Dispose ( disposing ) ;
249259 }
250260
251- private void onDeviceChanged ( )
252- {
253- scheduler . Add ( ( ) => setAudioDevice ( AudioDevice . Value ) ) ;
254- }
255-
256- private void onDevicesChanged ( )
257- {
258- scheduler . Add ( ( ) =>
259- {
260- if ( cancelSource . IsCancellationRequested )
261- return ;
262-
263- if ( ! IsCurrentDeviceValid ( ) )
264- setAudioDevice ( ) ;
265- } ) ;
266- }
267-
268261 private static int userMixerID ;
269262
270263 /// <summary>
@@ -334,81 +327,58 @@ public ISampleStore GetSampleStore(IResourceStore<byte[]> store = null, AudioMix
334327 }
335328
336329 /// <summary>
337- /// Sets the output audio device by its name .
330+ /// (Re-)Initialises BASS for the current <see cref="AudioDevice"/> .
338331 /// This will automatically fall back to the system default device on failure.
339332 /// </summary>
340- /// <param name="deviceName">Name of the audio device, or null to use the configured device preference <see cref="AudioDevice"/>.</param>
341- private bool setAudioDevice ( string deviceName = null )
333+ private void initCurrentDevice ( )
342334 {
343- deviceName ?? = AudioDevice . Value ;
335+ string deviceName = AudioDevice . Value ;
344336
345337 // try using the specified device
346338 int deviceIndex = audioDeviceNames . FindIndex ( d => d == deviceName ) ;
347- if ( deviceIndex >= 0 && setAudioDevice ( BASS_INTERNAL_DEVICE_COUNT + deviceIndex ) )
348- return true ;
339+ if ( deviceIndex >= 0 && trySetDevice ( BASS_INTERNAL_DEVICE_COUNT + deviceIndex ) ) return ;
349340
350341 // try using the system default if there is any device present.
351342 // mobiles are an exception as the built-in speakers may not be provided as an audio device name,
352343 // but they are still provided by BASS under the internal device name "Default".
353- if ( ( audioDeviceNames . Count > 0 || RuntimeInfo . IsMobile ) && setAudioDevice ( bass_default_device ) )
354- return true ;
344+ if ( ( audioDeviceNames . Count > 0 || RuntimeInfo . IsMobile ) && trySetDevice ( bass_default_device ) ) return ;
355345
356346 // no audio devices can be used, so try using Bass-provided "No sound" device as last resort.
357- if ( setAudioDevice ( Bass . NoSoundDevice ) )
358- return true ;
347+ trySetDevice ( Bass . NoSoundDevice ) ;
359348
360349 // we're boned. even "No sound" device won't initialise.
361- return false ;
362- }
350+ return ;
363351
364- private bool setAudioDevice ( int deviceIndex )
365- {
366- var device = audioDevices . ElementAtOrDefault ( deviceIndex ) ;
352+ bool trySetDevice ( int deviceId )
353+ {
354+ var device = audioDevices . ElementAtOrDefault ( deviceId ) ;
367355
368- // device is invalid
369- if ( ! device . IsEnabled )
370- return false ;
356+ // device is invalid
357+ if ( ! device . IsEnabled )
358+ return false ;
371359
372- // we don't want bass initializing with real audio device on headless test runs.
373- if ( deviceIndex != Bass . NoSoundDevice && DebugUtils . IsNUnitRunning )
374- return false ;
360+ // we don't want bass initializing with real audio device on headless test runs.
361+ if ( deviceId != Bass . NoSoundDevice && DebugUtils . IsNUnitRunning )
362+ return false ;
375363
376- // initialize new device
377- bool initSuccess = InitBass ( deviceIndex ) ;
378- if ( Bass . LastError != Errors . Already && BassUtils . CheckFaulted ( false ) )
379- return false ;
364+ // initialize new device
365+ if ( ! InitBass ( deviceId ) )
366+ return false ;
380367
381- if ( ! initSuccess )
382- {
383- Logger . Log ( "BASS failed to initialize but did not provide an error code" , level : LogLevel . Error ) ;
384- return false ;
385- }
368+ //we have successfully initialised a new device.
369+ UpdateDevice ( deviceId ) ;
386370
387- Logger . Log ( $@ "🔈 BASS initialised
388- BASS version: { Bass . Version }
389- BASS FX version: { BassFx . Version }
390- BASS MIX version: { BassMix . Version }
391- Device: { device . Name }
392- Driver: { device . Driver }
393- Update period: { Bass . UpdatePeriod } ms
394- Device buffer length: { Bass . DeviceBufferLength } ms
395- Playback buffer length: { Bass . PlaybackBufferLength } ms" ) ;
396-
397- //we have successfully initialised a new device.
398- UpdateDevice ( deviceIndex ) ;
399-
400- return true ;
371+ return true ;
372+ }
401373 }
402374
403375 /// <summary>
404376 /// This method calls <see cref="Bass.Init(int, int, DeviceInitFlags, IntPtr, IntPtr)"/>.
405377 /// It can be overridden for unit testing.
406378 /// </summary>
379+ /// <param name="device">The device to initialise.</param>
407380 protected virtual bool InitBass ( int device )
408381 {
409- if ( Bass . CurrentDevice == device )
410- return true ;
411-
412382 // this likely doesn't help us but also doesn't seem to cause any issues or any cpu increase.
413383 Bass . UpdatePeriod = 5 ;
414384
@@ -438,16 +408,47 @@ protected virtual bool InitBass(int device)
438408 // See https://www.un4seen.com/forum/?topic=19601 for more information.
439409 Bass . Configure ( ( ManagedBass . Configuration ) 70 , false ) ;
440410
441- if ( UseExperimentalWasapi . Value )
411+ bool success = attemptInit ( ) ;
412+
413+ if ( success || ! UseExperimentalWasapi . Value )
414+ return success ;
415+
416+ // in the case we're using experimental WASAPI, give a second chance of initialisation by forcefully disabling it.
417+ Logger . Log ( $ "BASS device { device } failed to initialise with experimental WASAPI, disabling", level : LogLevel . Error ) ;
418+ UseExperimentalWasapi . Value = false ;
419+ return attemptInit ( ) ;
420+
421+ bool attemptInit ( )
442422 {
443- if ( thread . InitDevice ( device , true ) )
423+ bool innerSuccess = thread . InitDevice ( device , UseExperimentalWasapi . Value ) ;
424+ bool alreadyInitialised = Bass . LastError == Errors . Already ;
425+
426+ if ( alreadyInitialised )
444427 return true ;
445428
446- Logger . Log ( $ "BASS device { device } failed to initialise with experimental WASAPI, disabling", level : LogLevel . Error ) ;
447- UseExperimentalWasapi . Value = false ;
448- }
429+ if ( BassUtils . CheckFaulted ( false ) )
430+ return false ;
431+
432+ if ( ! innerSuccess )
433+ {
434+ Logger . Log ( "BASS failed to initialize but did not provide an error code" , level : LogLevel . Error ) ;
435+ return false ;
436+ }
437+
438+ var deviceInfo = audioDevices . ElementAtOrDefault ( device ) ;
449439
450- return thread . InitDevice ( device , false ) ;
440+ Logger . Log ( $@ "🔈 BASS initialised
441+ BASS version: { Bass . Version }
442+ BASS FX version: { BassFx . Version }
443+ BASS MIX version: { BassMix . Version }
444+ Device: { deviceInfo . Name }
445+ Driver: { deviceInfo . Driver }
446+ Update period: { Bass . UpdatePeriod } ms
447+ Device buffer length: { Bass . DeviceBufferLength } ms
448+ Playback buffer length: { Bass . PlaybackBufferLength } ms" ) ;
449+
450+ return true ;
451+ }
451452 }
452453
453454 private void syncAudioDevices ( )
@@ -460,7 +461,14 @@ private void syncAudioDevices()
460461 var oldDeviceNames = audioDeviceNames ;
461462 var newDeviceNames = audioDeviceNames = audioDevices . Skip ( BASS_INTERNAL_DEVICE_COUNT ) . Where ( d => d . IsEnabled ) . Select ( d => d . Name ) . ToImmutableList ( ) ;
462463
463- onDevicesChanged ( ) ;
464+ scheduler . Add ( ( ) =>
465+ {
466+ if ( cancelSource . IsCancellationRequested )
467+ return ;
468+
469+ if ( ! IsCurrentDeviceValid ( ) )
470+ initCurrentDevice ( ) ;
471+ } , false ) ;
464472
465473 var newDevices = newDeviceNames . Except ( oldDeviceNames ) . ToList ( ) ;
466474 var lostDevices = oldDeviceNames . Except ( newDeviceNames ) . ToList ( ) ;
0 commit comments