/// <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> /// 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)); }
/// <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> /// 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> /// 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 } }
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(); }
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); }
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> /// 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)); } }
/// <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> /// 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 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> /// 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."); }
/// <summary> /// Releases all resources used by <see cref="SoundBuffer"/>. /// </summary> public void Dispose() { // To prevent the iterator from becoming invalid, move the entire buffer to another // container. Otherwise calling resetBuffer would result in detachSound being // called which removes the sound from the internal list. var sounds = new List <Sound>(_sounds); // Detach the buffer from the sounds that use it (to avoid OpenAL errors) foreach (var sound in sounds) { sound.ResetBuffer(); } // Destroy the buffer if (_buffer > 0) { ALChecker.Check(() => AL.DeleteBuffer(_buffer)); } }
/// <summary> /// Pause the <see cref="SoundSource"/> object. /// </summary> /// <param name="source">The <see cref="SoundSource"/> to pause.</param> public virtual void Pause(SoundSource source) { // Audio Device no longer active? if (AudioDevice.IsDisposed) { throw new ObjectDisposedException("AudioDevice"); } // Nothing to pause if (source == null) { throw new ArgumentNullException("source"); } // Ignore if sources is not listed or handle is not yet created if (!_sources.Contains(source) || source.Handle <= 0) { return; } // Check whether the specified source has valid handle bool valid = false; ALChecker.Check(() => valid = AL.IsSource(source.Handle)); // ignore if its not valid if (!valid) { return; } // Pause the sound try { _pause.Invoke(source, null); } catch (Exception ex) { throw ex.InnerException; } }
/// <summary> /// Start or resume playing of the <see cref="SoundStream"/>. /// </summary> /// <inheritdoc/> protected internal override void Play() { if (format == 0) { throw new Exception("Failed to play audio stream:\nSound parameters have not been initialized"); } bool streaming = false; var state = SoundStatus.Stopped; lock (mutex) { streaming = this.streaming; state = this.state; } // Check whether the stream is active if (streaming) { // If the sound is paused, resume it if (state == SoundStatus.Paused) { lock (mutex) { this.state = SoundStatus.Playing; } ALChecker.Check(() => AL.SourcePlay(Handle)); return; } else if (state == SoundStatus.Playing) { Stop(); } } // Set playing status this.state = SoundStatus.Playing; this.streaming = true; // Start streaming the audio sample task = Task.Run(() => StreamDataAsync()); }