using System; using System.Collections.Generic; using NAudio.Utils; namespace NAudio.Wave.SampleProviders { /// /// A sample provider mixer, allowing inputs to be added and removed /// public class MixingSampleProvider : ISampleProvider { private readonly List sources; private float[] sourceBuffer; private const int MaxInputs = 1024; // protect ourselves against doing something silly /// /// Creates a new MixingSampleProvider, with no inputs, but a specified WaveFormat /// /// The WaveFormat of this mixer. All inputs must be in this format public MixingSampleProvider(WaveFormat waveFormat) { if (waveFormat.Encoding != WaveFormatEncoding.IeeeFloat) { throw new ArgumentException("Mixer wave format must be IEEE float"); } sources = new List(); WaveFormat = waveFormat; } /// /// Creates a new MixingSampleProvider, based on the given inputs /// /// Mixer inputs - must all have the same waveformat, and must /// all be of the same WaveFormat. There must be at least one input public MixingSampleProvider(IEnumerable sources) { this.sources = new List(); foreach (var source in sources) { AddMixerInput(source); } if (this.sources.Count == 0) { throw new ArgumentException("Must provide at least one input in this constructor"); } } /// /// Returns the mixer inputs (read-only - use AddMixerInput to add an input /// public IEnumerable MixerInputs => sources; /// /// When set to true, the Read method always returns the number /// of samples requested, even if there are no inputs, or if the /// current inputs reach their end. Setting this to true effectively /// makes this a never-ending sample provider, so take care if you plan /// to write it out to a file. /// public bool ReadFully { get; set; } /// /// Adds a WaveProvider as a Mixer input. /// Must be PCM or IEEE float already /// /// IWaveProvider mixer input public void AddMixerInput(IWaveProvider mixerInput) { AddMixerInput(SampleProviderConverters.ConvertWaveProviderIntoSampleProvider(mixerInput)); } /// /// Adds a new mixer input /// /// Mixer input public void AddMixerInput(ISampleProvider mixerInput) { // we'll just call the lock around add since we are protecting against an AddMixerInput at // the same time as a Read, rather than two AddMixerInput calls at the same time lock (sources) { if (sources.Count >= MaxInputs) { throw new InvalidOperationException("Too many mixer inputs"); } sources.Add(mixerInput); } if (WaveFormat == null) { WaveFormat = mixerInput.WaveFormat; } else { if (WaveFormat.SampleRate != mixerInput.WaveFormat.SampleRate || WaveFormat.Channels != mixerInput.WaveFormat.Channels) { throw new ArgumentException("All mixer inputs must have the same WaveFormat"); } } } /// /// Raised when a mixer input has been removed because it has ended /// public event EventHandler MixerInputEnded; /// /// Removes a mixer input /// /// Mixer input to remove public void RemoveMixerInput(ISampleProvider mixerInput) { lock (sources) { sources.Remove(mixerInput); } } /// /// Removes all mixer inputs /// public void RemoveAllMixerInputs() { lock (sources) { sources.Clear(); } } /// /// The output WaveFormat of this sample provider /// public WaveFormat WaveFormat { get; private set; } /// /// Reads samples from this sample provider /// /// Sample buffer /// Offset into sample buffer /// Number of samples required /// Number of samples read public int Read(float[] buffer, int offset, int count) { int outputSamples = 0; sourceBuffer = BufferHelpers.Ensure(sourceBuffer, count); lock (sources) { int index = sources.Count - 1; while (index >= 0) { var source = sources[index]; int samplesRead = source.Read(sourceBuffer, 0, count); int outIndex = offset; for (int n = 0; n < samplesRead; n++) { if (n >= outputSamples) { buffer[outIndex++] = sourceBuffer[n]; } else { buffer[outIndex++] += sourceBuffer[n]; } } outputSamples = Math.Max(samplesRead, outputSamples); if (samplesRead < count) { MixerInputEnded?.Invoke(this, new SampleProviderEventArgs(source)); sources.RemoveAt(index); } index--; } } // optionally ensure we return a full buffer if (ReadFully && outputSamples < count) { int outputIndex = offset + outputSamples; while (outputIndex < offset + count) { buffer[outputIndex++] = 0; } outputSamples = count; } return outputSamples; } } /// /// SampleProvider event args /// public class SampleProviderEventArgs : EventArgs { /// /// Constructs a new SampleProviderEventArgs /// public SampleProviderEventArgs(ISampleProvider sampleProvider) { SampleProvider = sampleProvider; } /// /// The Sample Provider /// public ISampleProvider SampleProvider { get; private set; } } }