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; }
}
}