/// <summary> /// Start or resume playing the current <see cref="Sound"/> object. /// </summary> protected override void Play() { if (Handle <= 0) { Logger.Warning("SoundSource Handle is not created.\n" + "Use SoundSystem.Play() to play a SoundSource."); return; } // Attach the sound if it's not attached yet // In case the first time call or the Source handle is replaced due to recycle int buffer = 0; ALChecker.Check(() => AL.GetSource(Handle, ALGetSourcei.Buffer, out buffer)); if (!Buffer.IsAttached(this) || Buffer.Handle != buffer) { // Only attach if its not attached if (!Buffer.IsAttached(this)) { Buffer.AttachSound(this); } ALChecker.Check(() => AL.Source(Handle, ALSourcei.Buffer, _buffer.Handle)); } ALChecker.Check(() => AL.SourcePlay(Handle)); }
static AudioDevice() { // Create audio context (which create the ALCdevice and ALCcontext) _context = new AudioContext(); _context.MakeCurrent(); // Configure default state of listener _listenerVolume = 100f; _listenerPosition = new Vector3(0f, 0f, 0f); _listenerDirection = new Vector3(0f, 0f, -1f); _listenerUpVector = new Vector3(0f, 1f, 0f); // Apply the listener properties the user might have set float[] orientation = { _listenerDirection.X, _listenerDirection.Y, _listenerDirection.Z, _listenerUpVector.X, _listenerUpVector.Y, _listenerUpVector.Z }; ALChecker.Check(() => AL.Listener(ALListenerf.Gain, _listenerVolume * 0.01f)); ALChecker.Check(() => AL.Listener(ALListener3f.Position, _listenerPosition.X, _listenerPosition.Y, _listenerPosition.Z)); ALChecker.Check(() => AL.Listener(ALListenerfv.Orientation, ref orientation)); // Dispose Audio Device when exiting application AppDomain.CurrentDomain.ProcessExit += (s, e) => Free(); }
/// <summary> /// Update the <see cref="SoundSystem"/>. /// </summary> public void Update() { // Audio Device no longer active? if (AudioDevice.IsDisposed) { return; } // Remove and dispose unused sources for (int i = _sources.Count - 1; i >= 0; i--) { if (_sources[i] == null) { _sources.RemoveAt(i); continue; } // Check whether the specified source has valid handle bool valid = false; ALChecker.Check(() => valid = AL.IsSource(_sources[i].Handle)); if (!valid || (_sources[i].Status == SoundStatus.Stopped && !_sources[i].IsLooping)) { // No need to dispose invalid source handle if (valid) { _sources[i].Dispose(); } _sources.RemoveAt(i); } } }
/// <summary> /// Play the <see cref="Sound"/>. /// When the <see cref="Sound"/> is provided with valid buffer data, /// <see cref="SoundBuffer.IsValid"/> property will return true. /// </summary> public void Play() { // Just resume if the sounds is paused if (State == SoundState.Paused) { Resume(); return; } // Make sure the sound is well-queued if (Deferred || !IsReady) { Initialize(); } else { SoundSystem.Instance.Add(this); } // Let's rock! ALChecker.Check(() => AL.SourcePlay(Source)); // Triger the event if (SoundStarted != null) { SoundStarted(this, EventArgs.Empty); } }
/// <summary> /// Initialize the sound buffer with a <see cref="SoundDecoder"/>. /// </summary> /// <param name="decoder"><see cref="SoundDecoder"/> containing audio information and sample to fill the current buffer.</param> private void Initialize(SoundDecoder decoder) { // Retrieve the decoder Decoder = decoder; // Retrieve sound parameters SampleCount = decoder.SampleCount; ChannelCount = decoder.ChannelCount; SampleRate = decoder.SampleRate; // Compute duration Duration = TimeSpan.FromSeconds((float)SampleCount / SampleRate / ChannelCount); // Fill entire buffer immediately if its a sample mode if (Mode == BufferMode.Sample) { // Create the buffer handle Handle = ALChecker.Check(() => AL.GenBuffer()); // Decode the samples var samples = new short[SampleCount]; if (decoder.Decode(samples, SampleCount) == SampleCount) { // Update the internal buffer with the new samples LoadBuffer(samples, ChannelCount, SampleRate); } else { throw new Exception("Failed to initialize Sample Buffer"); } } }
/// <summary> /// Queue the Buffer Data. /// </summary> /// <param name="reader">Specify the <see cref="ISoundStreamReader"/>, be sure that the stream reader is able to handle the provided buffer.</param> /// <param name="precache">Specify whether the buffer should be pre-cached.</param> protected void QueueBuffer(ISoundStreamReader reader, bool precache = false) { // Reset position of the Stream Stream.Seek(0, SeekOrigin.Begin); // Use specified reader Reader = reader; if (precache) { // Fill first buffer synchronously Reader.BufferData(SoundSystem.Instance.BufferSize, Buffers[0]); // Here we attach the Source to the Buffer ALChecker.Check(() => AL.SourceQueueBuffer(Source, Buffers[0])); // Schedule the others buffer asynchronously as the game update if (Deferred) { SoundSystem.Instance.Add(this); } else { SoundSystem.Instance.AddUndeferredSource(Source); } } IsReady = true; }
/// <summary> /// Stop the <see cref="Sound"/>. /// </summary> public void Stop() { // This may cause OpenAL error if the Source is not valid. ALChecker.Check(() => AL.SourceStop(Source)); Reader.DecodeTime = 0; if (!Deferred) { SoundSystem.Instance.RemoveUndeferredSource(Source); Deferred = true; } if (SoundStopped != null) { SoundStopped(this, EventArgs.Empty); } // In this case, we really need to check this first // Whether the Source is valid bool isValidSource = AL.IsSource(Source); ALChecker.CheckError(); if (isValidSource) { // TODO: Should we really need to dispose the source here? ALChecker.Check(() => AL.DeleteSource(Source)); Source = -1; // Set to invalid Source to make the next cycle source available } }
private bool FillAndPushBuffer(int bufferNum, bool immediateLoop = false) { bool requestStop = false; // Acquire audio data short[] samples = null; for (int retryCount = 0; !OnGetData(out samples) && (retryCount < BUFFER_RETRIES); ++retryCount) { // Mark the buffer as the last one (so that we know when to reset the playing position) _endBuffers[bufferNum] = true; // Check if the stream must loop or stop if (!_loop) { // Not looping: request stop requestStop = true; break; } // Return to the beginning of the stream source OnSeek(TimeSpan.Zero); // If we got data, break and process it, else try to fill the buffer once again if (samples != null && (samples.Length > 0)) { break; } // If immediateLoop is specified, we have to immediately adjust the sample count if (immediateLoop) { // We just tried to begin preloading at EOF: reset the sample count _processed = 0; _endBuffers[bufferNum] = false; } // We're a looping sound that got no data, so we retry onGetData() } // Fill the buffer if some data was returned if (samples != null && samples.Length > 0) { int buffer = _buffers[bufferNum]; // Fill the buffer int size = samples.Length * sizeof(short); ALChecker.Check(() => AL.BufferData(buffer, _format, samples, size, _sampleRate)); // Push it into the sound queue ALChecker.Check(() => AL.SourceQueueBuffer(Handle, buffer)); } else { // If we get here, we most likely ran out of retries requestStop = true; } return(requestStop); }
/// <summary> /// Initializes a new instance of the <see cref="SoundSource"/> class. /// </summary> public SoundSource() { // Ensure that AudioDevice is initialized AudioDevice.Initialize(); ALChecker.Check(() => _source = AL.GenSource()); ALChecker.Check(() => AL.Source(_source, ALSourcei.Buffer, 0)); }
/// <summary> /// Process buffer of specified index. /// </summary> /// <param name="index">Index of buffer to process.</param> /// <param name="immediateLoop">Treat empty buffers as spent, and act on loops immediately.</param> /// <returns><c>true</c> if the stream source has requested to stop; otherwise, <c>false</c></returns> private bool ProcessBuffer(int index, bool immediateLoop = false) { short[] samples; bool terminating = false; for (int retry = 0; !GetStreamData(out samples) && retry < BufferRetries; ++retry) { // Check if the stream must loop or stop if (!IsLooping) { if (samples != null && samples.Length != 0) { bufferLoop[index] = 0; } terminating = true; break; } // Return to the beginning or loop-start of the stream source using GetLoopPoint(), and store the result in the buffer seek array // This marks the buffer as the "last" one (so that we know where to reset the playing position) bufferLoop[index] = GetLoopPoint(); // If we got data, break and process it, else try to fill the buffer once again if (samples != null && samples.Length != 0) { break; } // If immediateLoop is specified, we have to immediately adjust the sample count if (immediateLoop && (bufferLoop[index] != -1)) { // We just tried to begin preloading at EOF or Loop End: reset the sample count processed = bufferLoop[index]; bufferLoop[index] = -1; } } // Fill the buffer if some data was returned if (samples != null && samples.Length != 0) { int buffer = buffers[index]; // Fill the buffer int size = samples.Length * sizeof(short); ALChecker.Check(() => AL.BufferData(buffer, format, samples, size, SampleRate)); // Push it into the sound queue ALChecker.Check(() => AL.SourceQueueBuffer(Handle, buffer)); } else { // If we get here, we most likely ran out of retries terminating = true; } return(terminating); }
/// <summary> /// Releases the unmanaged resources used by the <see cref="Sound"/> and optionally releases the managed resources. /// </summary> /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> /// <inheritdoc/> protected override void Dispose(bool disposing) { base.Dispose(disposing); Decoder?.Dispose(); if (Handle > 0) { ALChecker.Check(() => AL.DeleteBuffer(Handle)); } }
public bool BufferData(int bufferSize, int bufferId) { byte[] readSampleBuffer = new byte[SoundSystem.Instance.BufferSize]; int readSamples = Read(readSampleBuffer, 0, bufferSize); ALChecker.Check(() => AL.BufferData(bufferId, _alFormat, readSampleBuffer, readSamples, SampleRate)); return(readSamples != bufferSize); }
/// <summary> /// Start or resume playing the <see cref="SoundSource"/> object. /// </summary> /// <param name="source">The <see cref="SoundSource"/> to play or resume.</param> public virtual void Play(SoundSource source) { // Audio Device no longer active? if (AudioDevice.IsDisposed) { throw new ObjectDisposedException("AudioDevice"); } // Nothing to play? if (source == null) { throw new ArgumentNullException("source"); } // Check whether the number of playing sounds is exceed the limit if (_sources.Count >= MAX_SOURCE_COUNT) { // Force to recycle unused source Update(); // Check again if it exceed the limit if (_sources.Count >= MAX_SOURCE_COUNT) { // It still exceed and throw the exception throw new InvalidOperationException("Failed to play the source:\n" + "The number of playing sources is exceed the limit."); } } // Create the source handle, in case it is first call bool valid = false; ALChecker.Check(() => valid = AL.IsSource(source.Handle)); if (!valid) { int handle = 0; ALChecker.Check(() => handle = AL.GenSource()); ALChecker.Check(() => AL.Source(handle, ALSourcei.Buffer, 0)); source.Handle = handle; } // Play the sound try { _play.Invoke(source, null); // Add to the list _sources.Add(source); } catch (Exception ex) { throw ex.InnerException; } }
/// <summary> /// Stop playing the current <see cref="Sound"/> object. /// </summary> protected override void Stop() { if (Handle <= 0) { Logger.Warning("SoundSource Handle is not created.\n" + "Use SoundSystem.Pause() to play a SoundSource."); return; } ALChecker.Check(() => AL.SourceStop(Handle)); }
/// <summary> /// Initialize the Native Handle, De-queue existing buffer and (re)Queue the buffer of the <see cref="SoundBuffer"/>. /// </summary> protected void Initialize() { // Check whether the buffer is already initializing state if (IsPreparing) { return; } // Check whether the current Source is valid bool isValidSource = AL.IsSource(Source); ALChecker.CheckError(); if (!isValidSource || Source == -1) { // Get unused Source from SoundSystem Source = SoundSystem.Instance.GetAvailableSource(); } // We don't have clue which buffer id that being attached to the source right now // So we don't attach (queue) the buffer to the source now // We will attach (queue) it when we are (really) going to queue the buffer (read: Play()) // Initialize() call will dequeue current buffer and queue the buffer when its required // Check the sound state switch (State) { // Sound is already playing, no need preparation(?) case SoundState.Paused: case SoundState.Playing: return; // The sounds is already stopped (which mean its been played once or more before) case SoundState.Stopped: // Reset to 0 Position Reader.DecodeTime = 0; // Dequeue remaining buffer before we (re)queue the buffer DequeueBuffer(); // We are not ready to start yet, we haven't queue the buffer IsReady = false; break; } // Check whether our buffer is ready if (!IsReady) { // If its not ready, lets queue them IsPreparing = true; // set the flag to prevent requeue during process QueueBuffer(precache: true); IsPreparing = false; // done } }
/// <summary> /// Initializes a new instance of the <see cref="SoundBuffer"/> class. /// </summary> public SoundBuffer() { // Ensure that AudioDevice is initialized AudioDevice.Initialize(); _samples = new short[0]; _sounds = new List <Sound>(); _duration = TimeSpan.Zero; // Create the buffer ALChecker.Check(() => _buffer = AL.GenBuffer()); }
internal int GetAvailableSource() { for (int i = 0; i < _sources.Length; i++) { bool isValidSource = AL.IsSource(_sources[i]); ALChecker.CheckError(); if (isValidSource) { // Some sources are probably used by another sounds // Return the one that not in use // Identify source state ALSourceState state = AL.GetSourceState(_sources[i]); ALChecker.CheckError(); // Do not use un-deferred source if (_unDeferredSources.Contains(_sources[i]) && state == ALSourceState.Initial) { continue; } else if (_unDeferredSources.Contains(_sources[i]) && state == ALSourceState.Stopped) { _unDeferredSources.Remove(_sources[i]); } // No sounds using it or no longer in use, use it if (state == ALSourceState.Initial || (state == ALSourceState.Stopped)) { return(_sources[i]); } // Source is in use by a sound, find another one else if (state == ALSourceState.Paused || state == ALSourceState.Playing) { continue; } } else { // Not a source (anymore..?) // Generate and use it _sources[i] = AL.GenSource(); // Since it's newly generated, it must be unused source return(_sources[i]); } } // All sources are used at the moment... // Return invalid source return(-1); }
internal void ResetBuffer() { // First stop the sound in case it is playing Stop(); // Detach the buffer if (_buffer != null) { ALChecker.Check(() => AL.Source(Handle, ALSourcei.Buffer, 0)); _buffer.DetachSound(this); _buffer = null; } }
/// <summary> /// Resume the <see cref="Sound"/>. /// </summary> public void Resume() { // This may cause OpenAL error if the Source is not valid. if (State == SoundState.Paused) { ALChecker.Check(() => AL.SourcePlay(Source)); if (SoundResumed != null) { SoundResumed(this, EventArgs.Empty); } } }
/// <summary> /// Pause the current <see cref="SoundStream"/> object. /// </summary> protected override void Pause() { lock (_mutex) { if (!_isStreaming) { return; } _state = SoundStatus.Paused; } ALChecker.Check(() => AL.SourcePause(Handle)); }
/// <summary> /// Pause the playing <see cref="SoundStream"/>. /// </summary> /// <inheritdoc/> protected internal override void Pause() { lock (mutex) { if (!streaming) { return; } state = SoundStatus.Paused; } ALChecker.Check(() => AL.SourcePause(Handle)); }
public bool BufferData(int bufferSize, int bufferId) { float[] readSampleBuffer = new float[SoundSystem.Instance.BufferSize]; short[] castBuffer = new short[SoundSystem.Instance.BufferSize]; int readSamples = ReadSamples(readSampleBuffer, 0, bufferSize); CastBuffer(readSampleBuffer, castBuffer, readSamples); ALChecker.Check(() => AL.BufferData(bufferId, Channels == 1 ? ALFormat.Mono16 : ALFormat.Stereo16, castBuffer, readSamples * sizeof(short), SampleRate)); return(readSamples != bufferSize); }
/// <summary> /// Clear all the audio buffers and empty the playing queue. /// </summary> private void ClearQueue() { // Reset the playing position processed = 0; // Get the number of buffers that still in queue then enqueue all of them int queued = 0; ALChecker.Check(() => AL.GetSource(Handle, ALGetSourcei.BuffersQueued, out queued)); ALChecker.Check(() => queued > 0 ? AL.SourceUnqueueBuffers(Handle, queued) : new int[0]); // Detach and delete the buffers ALChecker.Check(() => AL.Source(Handle, ALSourcei.Buffer, 0)); ALChecker.Check(() => AL.DeleteBuffers(buffers)); }
/// <summary> /// Update <see cref="Sound"/> with filled audio samples and given channel count and sample rate. /// </summary> /// <param name="samples">The audio sample to fill the buffer.</param> /// <param name="channelCount">The number of audio channels.</param> /// <param name="sampleRate">The number of sample rate.</param> private void LoadBuffer(short[] samples, int channelCount, int sampleRate) { // Check audio properties if (channelCount == 0 || sampleRate == 0 || samples == null || samples.Length == 0) { throw new ArgumentNullException(); } // Fill the buffer int size = samples.Length * sizeof(short); ALChecker.Check(() => AL.BufferData(Handle, Format, samples, size, sampleRate)); Decoder?.Dispose(); }
private void ClearQueue() { // Get the number of buffers still in the queue int nbQueued = 0; ALChecker.Check(() => AL.GetSource(Handle, ALGetSourcei.BuffersQueued, out nbQueued)); // Dequeue them all int buffer = 0; for (int i = 0; i < nbQueued; ++i) { ALChecker.Check(() => buffer = AL.SourceUnqueueBuffer(Handle)); } }
/// <summary> /// Initialize the <see cref="SoundSystem"/>. /// </summary> /// <param name="bufferSize"></param> public void Initialize(int bufferSize = DEFAULT_BUFFER_SIZE) { if (_context == null) { _context = new ALContext(); } BufferSize = bufferSize; XRam = new XRamExtension(); Efx = new EffectsExtension(); // Init empty sources _sources = AL.GenSources(MAXIMUM_NUMBER_OF_SOURCES); ALChecker.CheckError(); }
/// <summary> /// Queue the Buffer Data. /// </summary> /// <param name="precache">Specify whether the buffer should be pre-cached.</param> protected void QueueBuffer(bool precache = false) { // Reset position of the Stream Stream.Seek(0, SeekOrigin.Begin); // Use approriate Sound Stream Reader if (Reader == null) { if (Format == SoundFormat.Vorbis) { Reader = new VorbisStreamReader(this, false); } else if (Format == SoundFormat.Wav) { Reader = new WaveStreamReader(this, false); } else if (Format == SoundFormat.Unknown) { // You need to implement your own Sound Stream Reader // Inherit ISoundStreamReader and pass it via QueueBuffer(ISoundStreamReader, bool) // Or set the implementation under SoundBuffer.Reader property throw new NotSupportedException("Unknown sound data buffer format."); } } if (precache) { // Fill first buffer synchronously Reader.BufferData(SoundSystem.Instance.BufferSize, Buffers[0]); // Here we attach the Source to the Buffer ALChecker.Check(() => AL.SourceQueueBuffer(Source, Buffers[0])); // Schedule the others buffer asynchronously as the game update if (Deferred) { SoundSystem.Instance.Add(this); } else { SoundSystem.Instance.AddUndeferredSource(Source); } } IsReady = true; }
/// <summary> /// Start or resume playing current <see cref="SoundStream"/> object. /// </summary> protected override void Play() { // Check if the sound parameters have been set if (_format == 0) { throw new InvalidOperationException( "Audio parameters must be initialized before played."); } bool isStreaming = false; SoundStatus state = SoundStatus.Stopped; lock (_mutex) { isStreaming = _isStreaming; state = _state; } if (isStreaming && (state == SoundStatus.Paused)) { // If the sound is paused, resume it lock (_mutex) { _state = SoundStatus.Playing; ALChecker.Check(() => AL.SourcePlay(Handle)); } return; } else if (isStreaming && (state == SoundStatus.Playing)) { // If the sound is playing, stop it and continue as if it was stopped Stop(); } // Move to the beginning OnSeek(TimeSpan.Zero); // Start updating the stream in a separate thread to avoid blocking the application _processed = 0; _isStreaming = true; _state = SoundStatus.Playing; _thread.Start(); }
private bool FillAndPushBuffer(int bufferNum) { bool requestStop = false; // Acquire audio data short[] samples = null; if (!OnGetData(out samples)) { // Mark the buffer as the last one (so that we know when to reset the playing position) _endBuffers[bufferNum] = true; // Check if the stream must loop or stop if (_loop) { // Return to the beginning of the stream source OnSeek(TimeSpan.Zero); // If we previously had no data, try to fill the buffer once again if (samples == null || (samples.Length == 0)) { return(FillAndPushBuffer(bufferNum)); } } else { // Not looping: request stop requestStop = true; } } // Fill the buffer if some data was returned if (samples != null && samples.Length > 0) { int buffer = _buffers[bufferNum]; // Fill the buffer int size = samples.Length * sizeof(short); ALChecker.Check(() => AL.BufferData(buffer, _format, samples, size, _sampleRate)); // Push it into the sound queue ALChecker.Check(() => AL.SourceQueueBuffer(Handle, buffer)); } return(requestStop); }
/// <summary> /// Generate or retrieve available OpenAL Source Handle from the source pool. /// </summary> /// <returns>OpenAL Source Handle.</returns> private int GenSource() { // Reuse stopped channel var channel = channels.FirstOrDefault(ch => ch.Status == SoundStatus.Stopped); if (channel != null) { return(Enqueue(channel)?.Handle ?? throw new Exception("Failed to retrieve available channel")); } else if (channels.Count < MaxSource) { // No stopped channel available, generate one as long the pool still below the limit return(ALChecker.Check(() => AL.GenSource())); } // No stopped channel available and the source pool is already at its limit throw new OutOfMemoryException("Insufficient audio source handles."); }