diff --git a/MonoGame.Framework/Android/AndroidGamePlatform.cs b/MonoGame.Framework/Android/AndroidGamePlatform.cs index 678aa31f784..2f078154cf0 100644 --- a/MonoGame.Framework/Android/AndroidGamePlatform.cs +++ b/MonoGame.Framework/Android/AndroidGamePlatform.cs @@ -23,14 +23,6 @@ public AndroidGamePlatform(Game game) Window = _gameWindow; MediaLibrary.Context = Game.Activity; - try - { - OpenALSoundController soundControllerInstance = OpenALSoundController.GetInstance; - } - catch (DllNotFoundException ex) - { - throw (new NoAudioHardwareException("Failed to init OpenALSoundController", ex)); - } } protected override void Dispose(bool disposing) diff --git a/MonoGame.Framework/Audio/DynamicSoundEffectInstance.cs b/MonoGame.Framework/Audio/DynamicSoundEffectInstance.cs index 3146fd6481a..3899732722c 100644 --- a/MonoGame.Framework/Audio/DynamicSoundEffectInstance.cs +++ b/MonoGame.Framework/Audio/DynamicSoundEffectInstance.cs @@ -76,6 +76,10 @@ public int PendingBufferCount /// Number of channels (mono or stereo). public DynamicSoundEffectInstance(int sampleRate, AudioChannels channels) { + SoundEffect.Initialize(); + if (SoundEffect._systemState != SoundEffect.SoundSystemState.Initialized) + throw new NoAudioHardwareException("Audio has failed to initialize. Call SoundEffect.Initialize() before sound operation to get more specific errors."); + if ((sampleRate < 8000) || (sampleRate > 48000)) throw new ArgumentOutOfRangeException("sampleRate"); if ((channels != AudioChannels.Mono) && (channels != AudioChannels.Stereo)) diff --git a/MonoGame.Framework/Audio/Microphone.cs b/MonoGame.Framework/Audio/Microphone.cs index 144fe276905..0e31926ffea 100644 --- a/MonoGame.Framework/Audio/Microphone.cs +++ b/MonoGame.Framework/Audio/Microphone.cs @@ -115,6 +115,7 @@ public static ReadOnlyCollection All { get { + SoundEffect.Initialize(); if (_allMicrophones == null) _allMicrophones = new List(); return new ReadOnlyCollection(_allMicrophones); @@ -128,7 +129,7 @@ public static ReadOnlyCollection All /// public static Microphone Default { - get { return _default; } + get { SoundEffect.Initialize(); return _default; } } #endregion diff --git a/MonoGame.Framework/Audio/OALSoundBuffer.cs b/MonoGame.Framework/Audio/OALSoundBuffer.cs index 1ac5c405ec0..dd7b8992bac 100644 --- a/MonoGame.Framework/Audio/OALSoundBuffer.cs +++ b/MonoGame.Framework/Audio/OALSoundBuffer.cs @@ -41,9 +41,9 @@ public double Duration public void BindDataBuffer(byte[] dataBuffer, ALFormat format, int size, int sampleRate, int sampleAlignment = 0) { - if ((format == ALFormat.MonoMSAdpcm || format == ALFormat.StereoMSAdpcm) && !OpenALSoundController.GetInstance.SupportsAdpcm) + if ((format == ALFormat.MonoMSAdpcm || format == ALFormat.StereoMSAdpcm) && !OpenALSoundController.Instance.SupportsAdpcm) throw new InvalidOperationException("MS-ADPCM is not supported by this OpenAL driver"); - if ((format == ALFormat.MonoIma4 || format == ALFormat.StereoIma4) && !OpenALSoundController.GetInstance.SupportsIma4) + if ((format == ALFormat.MonoIma4 || format == ALFormat.StereoIma4) && !OpenALSoundController.Instance.SupportsIma4) throw new InvalidOperationException("IMA/ADPCM is not supported by this OpenAL driver"); openALFormat = format; diff --git a/MonoGame.Framework/Audio/OggStream.cs b/MonoGame.Framework/Audio/OggStream.cs index fede4ecd3f7..5f6064ce4be 100644 --- a/MonoGame.Framework/Audio/OggStream.cs +++ b/MonoGame.Framework/Audio/OggStream.cs @@ -45,7 +45,7 @@ public OggStream(string filename, Action finishedAction = null, int bufferCount alBufferIds = AL.GenBuffers(bufferCount); ALHelper.CheckError("Failed to generate buffers."); - alSourceId = OpenALSoundController.GetInstance.ReserveSource(); + alSourceId = OpenALSoundController.Instance.ReserveSource(); if (OggStreamer.Instance.XRam.IsInitialized) { @@ -238,7 +238,7 @@ public void Dispose() AL.Source(alSourceId, ALSourcei.Buffer, 0); ALHelper.CheckError("Failed to free source from buffers."); - OpenALSoundController.GetInstance.RecycleSource(alSourceId); + OpenALSoundController.Instance.RecycleSource(alSourceId); AL.DeleteBuffers(alBufferIds); ALHelper.CheckError("Failed to delete buffer."); if (OggStreamer.Instance.Efx.IsInitialized) diff --git a/MonoGame.Framework/Audio/OpenALSoundController.cs b/MonoGame.Framework/Audio/OpenALSoundController.cs index 9a95a22bcb0..c404cb2bf58 100644 --- a/MonoGame.Framework/Audio/OpenALSoundController.cs +++ b/MonoGame.Framework/Audio/OpenALSoundController.cs @@ -108,6 +108,9 @@ internal sealed class OpenALSoundController : IDisposable /// private OpenALSoundController() { + if (AL.NativeLibrary == IntPtr.Zero) + throw new DllNotFoundException("Couldn't initialize OpenAL because the native binaries couldn't be found."); + if (!OpenSoundController()) { throw new NoAudioHardwareException("OpenAL device could not be initialized, see console output for details."); @@ -149,10 +152,6 @@ private bool OpenSoundController() _device = Alc.OpenDevice(string.Empty); EffectsExtension.device = _device; } - catch (DllNotFoundException ex) - { - throw ex; - } catch (Exception ex) { throw new NoAudioHardwareException("OpenAL device could not be initialized.", ex); @@ -287,12 +286,36 @@ private bool OpenSoundController() return false; } - public static OpenALSoundController GetInstance + public static void EnsureInitialized() + { + if (_instance == null) + { + try + { + _instance = new OpenALSoundController(); + } + catch (DllNotFoundException) + { + throw; + } + catch (NoAudioHardwareException) + { + throw; + } + catch (Exception ex) + { + throw (new NoAudioHardwareException("Failed to init OpenALSoundController", ex)); + } + } + } + + + public static OpenALSoundController Instance { get { - if (_instance == null) - _instance = new OpenALSoundController(); + if (_instance == null) + throw new NoAudioHardwareException("OpenAL context has failed to initialize. Call SoundEffect.Initialize() before sound operation to get more specific errors."); return _instance; } } diff --git a/MonoGame.Framework/Audio/SoundEffect.OpenAL.cs b/MonoGame.Framework/Audio/SoundEffect.OpenAL.cs index 86a31c1ff20..429ca15f28d 100644 --- a/MonoGame.Framework/Audio/SoundEffect.OpenAL.cs +++ b/MonoGame.Framework/Audio/SoundEffect.OpenAL.cs @@ -63,7 +63,7 @@ private void PlatformInitializePcm(byte[] buffer, int offset, int count, int sam private void PlatformInitializeIeeeFloat(byte[] buffer, int offset, int count, int sampleRate, AudioChannels channels, int loopStart, int loopLength) { - if (!OpenALSoundController.GetInstance.SupportsIeee) + if (!OpenALSoundController.Instance.SupportsIeee) { // If 32-bit IEEE float is not supported, convert to 16-bit signed PCM buffer = AudioLoader.ConvertFloatTo16(buffer, offset, count); @@ -80,7 +80,7 @@ private void PlatformInitializeIeeeFloat(byte[] buffer, int offset, int count, i private void PlatformInitializeAdpcm(byte[] buffer, int offset, int count, int sampleRate, AudioChannels channels, int blockAlignment, int loopStart, int loopLength) { - if (!OpenALSoundController.GetInstance.SupportsAdpcm) + if (!OpenALSoundController.Instance.SupportsAdpcm) { // If MS-ADPCM is not supported, convert to 16-bit signed PCM buffer = AudioLoader.ConvertMsAdpcmToPcm(buffer, offset, count, (int)channels, blockAlignment); @@ -100,7 +100,7 @@ private void PlatformInitializeAdpcm(byte[] buffer, int offset, int count, int s private void PlatformInitializeIma4(byte[] buffer, int offset, int count, int sampleRate, AudioChannels channels, int blockAlignment, int loopStart, int loopLength) { - if (!OpenALSoundController.GetInstance.SupportsIma4) + if (!OpenALSoundController.Instance.SupportsIma4) { // If IMA/ADPCM is not supported, convert to 16-bit signed PCM buffer = AudioLoader.ConvertIma4ToPcm(buffer, offset, count, (int)channels, blockAlignment); @@ -235,17 +235,9 @@ private void PlatformDispose(bool disposing) #endregion - internal static void InitializeSoundEffect() + internal static void PlatformInitialize() { - try - { - // Getting the instance for the first time initializes OpenAL - var oal = OpenALSoundController.GetInstance; - } - catch (DllNotFoundException ex) - { - throw new NoAudioHardwareException("Failed to init OpenALSoundController", ex); - } + OpenALSoundController.EnsureInitialized(); } internal static void PlatformShutdown() diff --git a/MonoGame.Framework/Audio/SoundEffect.Web.cs b/MonoGame.Framework/Audio/SoundEffect.Web.cs index 6d7d0fe36d6..bb1e6c46a44 100644 --- a/MonoGame.Framework/Audio/SoundEffect.Web.cs +++ b/MonoGame.Framework/Audio/SoundEffect.Web.cs @@ -47,7 +47,7 @@ internal static void PlatformSetReverbSettings(ReverbSettings reverbSettings) { } - internal static void InitializeSoundEffect() + internal static void PlatformInitialize() { } diff --git a/MonoGame.Framework/Audio/SoundEffect.XAudio.cs b/MonoGame.Framework/Audio/SoundEffect.XAudio.cs index 87cbfd90e4e..18c57fc90c5 100644 --- a/MonoGame.Framework/Audio/SoundEffect.XAudio.cs +++ b/MonoGame.Framework/Audio/SoundEffect.XAudio.cs @@ -91,7 +91,7 @@ internal static SubmixVoice ReverbVoice /// /// Initializes XAudio. /// - internal static void InitializeSoundEffect() + internal static void PlatformInitialize() { try { diff --git a/MonoGame.Framework/Audio/SoundEffect.cs b/MonoGame.Framework/Audio/SoundEffect.cs index ff0e8ff5e22..b5c3ebd1d36 100644 --- a/MonoGame.Framework/Audio/SoundEffect.cs +++ b/MonoGame.Framework/Audio/SoundEffect.cs @@ -6,7 +6,7 @@ using System.IO; namespace Microsoft.Xna.Framework.Audio -{ +{ /// Represents a loaded sound resource. /// /// A SoundEffect represents the buffer used to hold audio data and metadata. SoundEffectInstances are used to play from SoundEffects. Multiple SoundEffectInstance objects can be created and played from the same SoundEffect object. @@ -28,7 +28,11 @@ public sealed partial class SoundEffect : IDisposable // Only used from SoundEffect.FromStream. private SoundEffect(Stream stream) - { + { + Initialize(); + if (_systemState != SoundSystemState.Initialized) + throw new NoAudioHardwareException("Audio has failed to initialize. Call SoundEffect.Initialize() before sound operation to get more specific errors."); + /* The Stream object must point to the head of a valid PCM wave file. Also, this wave file must be in the RIFF bitstream format. The audio format has the following restrictions: @@ -43,7 +47,11 @@ private SoundEffect(Stream stream) // Only used from SoundEffectReader. internal SoundEffect(byte[] header, byte[] buffer, int bufferSize, int durationMs, int loopStart, int loopLength) - { + { + Initialize(); + if (_systemState != SoundSystemState.Initialized) + throw new NoAudioHardwareException("Audio has failed to initialize. Call SoundEffect.Initialize() before sound operation to get more specific errors."); + _duration = TimeSpan.FromMilliseconds(durationMs); // Peek at the format... handle regular PCM data. @@ -63,7 +71,11 @@ internal SoundEffect(byte[] header, byte[] buffer, int bufferSize, int durationM // Only used from XACT WaveBank. internal SoundEffect(MiniFormatTag codec, byte[] buffer, int channels, int sampleRate, int blockAlignment, int loopStart, int loopLength) - { + { + Initialize(); + if (_systemState != SoundSystemState.Initialized) + throw new NoAudioHardwareException("Audio has failed to initialize. Call SoundEffect.Initialize() before sound operation to get more specific errors."); + // Handle the common case... the rest is platform specific. if (codec == MiniFormatTag.Pcm) { @@ -73,12 +85,47 @@ internal SoundEffect(MiniFormatTag codec, byte[] buffer, int channels, int sampl } PlatformInitializeXact(codec, buffer, channels, sampleRate, blockAlignment, loopStart, loopLength, out _duration); - } - + } + + #endregion + + #region Audio System Initialization + + internal enum SoundSystemState + { + NotInitialized, + Initialized, + FailedToInitialized + } + + internal static SoundSystemState _systemState = SoundSystemState.NotInitialized; + + /// + /// Initializes the sound system for SoundEffect support. + /// This method is automatically called when a SoundEffect is loaded, a DynamicSoundEffectInstance is created, or Microphone.All is queried. + /// You can however call this method manually (preferably in, or before the Game constructor) to catch any Exception that may occur during the sound system initialization (and act accordingly). + /// + public static void Initialize() + { + if (_systemState != SoundSystemState.NotInitialized) + return; + + try + { + PlatformInitialize(); + _systemState = SoundSystemState.Initialized; + } + catch (Exception) + { + _systemState = SoundSystemState.FailedToInitialized; + throw; + } + } + #endregion - + #region Public Constructors - + /// /// Create a sound effect. /// @@ -103,7 +150,11 @@ public SoundEffect(byte[] buffer, int sampleRate, AudioChannels channels) /// The duration of the sound data loop in samples. /// This only supports uncompressed 16bit PCM wav data. public SoundEffect(byte[] buffer, int offset, int count, int sampleRate, AudioChannels channels, int loopStart, int loopLength) - { + { + Initialize(); + if (_systemState != SoundSystemState.Initialized) + throw new NoAudioHardwareException("Audio has failed to initialize. Call SoundEffect.Initialize() before sound operation to get more specific errors."); + if (sampleRate < 8000 || sampleRate > 48000) throw new ArgumentOutOfRangeException("sampleRate"); if ((int)channels != 1 && (int)channels != 2) diff --git a/MonoGame.Framework/Audio/SoundEffectInstance.OpenAL.cs b/MonoGame.Framework/Audio/SoundEffectInstance.OpenAL.cs index 91aae7fbf45..929bc409b03 100644 --- a/MonoGame.Framework/Audio/SoundEffectInstance.OpenAL.cs +++ b/MonoGame.Framework/Audio/SoundEffectInstance.OpenAL.cs @@ -41,7 +41,7 @@ internal void PlatformInitialize(byte[] buffer, int sampleRate, int channels) /// internal void InitializeSound() { - controller = OpenALSoundController.GetInstance; + controller = OpenALSoundController.Instance; } #endregion // Initialization @@ -174,7 +174,7 @@ private void PlatformStop(bool immediate) // Reset the SendFilter to 0 if we are NOT using reverb since // sources are recycled - if (OpenALSoundController.GetInstance.SupportsEfx) + if (OpenALSoundController.Instance.SupportsEfx) { OpenALSoundController.Efx.BindSourceToAuxiliarySlot(SourceId, 0, 0, 0); ALHelper.CheckError("Failed to unset reverb."); diff --git a/MonoGame.Framework/FrameworkDispatcher.cs b/MonoGame.Framework/FrameworkDispatcher.cs index 94ba46c4bdd..ca3f6369f93 100644 --- a/MonoGame.Framework/FrameworkDispatcher.cs +++ b/MonoGame.Framework/FrameworkDispatcher.cs @@ -38,9 +38,6 @@ private static void DoUpdate() private static void Initialize() { - // Initialize sound system - SoundEffect.InitializeSoundEffect(); - _initialized = true; } } diff --git a/MonoGame.Framework/Game.cs b/MonoGame.Framework/Game.cs index 80b0ea953f0..95ecdbeebbe 100644 --- a/MonoGame.Framework/Game.cs +++ b/MonoGame.Framework/Game.cs @@ -139,8 +139,9 @@ protected virtual void Dispose(bool disposing) } ContentTypeReaderManager.ClearTypeCreators(); - - SoundEffect.PlatformShutdown(); + + if (SoundEffect._systemState == SoundEffect.SoundSystemState.Initialized) + SoundEffect.PlatformShutdown(); } #if ANDROID Activity = null; diff --git a/MonoGame.Framework/Media/Song.NVorbis.cs b/MonoGame.Framework/Media/Song.NVorbis.cs index 67a8d4faf4c..073c95c7687 100644 --- a/MonoGame.Framework/Media/Song.NVorbis.cs +++ b/MonoGame.Framework/Media/Song.NVorbis.cs @@ -16,6 +16,9 @@ public sealed partial class Song : IEquatable, IDisposable private void PlatformInitialize(string fileName) { + // init OpenAL if need be + OpenALSoundController.EnsureInitialized(); + stream = new OggStream(fileName, OnFinishedPlaying); stream.Prepare(); diff --git a/MonoGame.Framework/SDL/SDLGamePlatform.cs b/MonoGame.Framework/SDL/SDLGamePlatform.cs index 69b3d44d960..11580715ace 100644 --- a/MonoGame.Framework/SDL/SDLGamePlatform.cs +++ b/MonoGame.Framework/SDL/SDLGamePlatform.cs @@ -7,7 +7,6 @@ using System.Diagnostics; using System.Threading; using System.Runtime.InteropServices; -using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; using MonoGame.Utilities; @@ -22,7 +21,6 @@ public override GameRunBehavior DefaultRunBehavior } private readonly Game _game; - private readonly OpenALSoundController _soundControllerInstance; private readonly List _keys; private int _isExiting; @@ -62,15 +60,6 @@ public SdlGamePlatform(Game game) GamePad.InitDatabase(); Window = _view = new SdlGameWindow(_game); - - try - { - _soundControllerInstance = OpenALSoundController.GetInstance; - } - catch (DllNotFoundException ex) - { - throw (new NoAudioHardwareException("Failed to init OpenALSoundController", ex)); - } } public override void BeforeInitialize () diff --git a/MonoGame.Framework/iOS/iOSGamePlatform.cs b/MonoGame.Framework/iOS/iOSGamePlatform.cs index 79f4b5ad773..ee34aa988e7 100644 --- a/MonoGame.Framework/iOS/iOSGamePlatform.cs +++ b/MonoGame.Framework/iOS/iOSGamePlatform.cs @@ -95,16 +95,6 @@ class iOSGamePlatform : GamePlatform base(game) { game.Services.AddService(typeof(iOSGamePlatform), this); - - // Setup our OpenALSoundController to handle our SoundBuffer pools - try - { - OpenALSoundController soundControllerInstance = OpenALSoundController.GetInstance; - } - catch (DllNotFoundException ex) - { - throw (new NoAudioHardwareException("Failed to init OpenALSoundController", ex)); - } //This also runs the TitleContainer static constructor, ensuring it is done on the main thread Directory.SetCurrentDirectory(TitleContainer.Location);