如何将多个 NAudio ISampleProvider 效果链接在一起

How to chain together multiple NAudio ISampleProvider Effects

我在 ISampleProvider 模型中编码了一些 DSP 效果。为了应用一种效果,我这样做了,效果很好。

string filename = "C:\myaudio.mp3";
MediaFoundationReader mediaFileReader = new MediaFoundationReader(filename);
ISampleProvider sampProvider = mediaFileReader.ToSampleProvider();
ReverbSampleProvider reverbSamplr = new ReverbSampleProvider(sampProvider);
IWavePlayer waveOutDevice.Init(reverbSamplr);
waveOutDevice.Play();

如何将多个效果同时应用到同一个输入文件? 例如,如果我有一个 Reverb 效果器和一个 Distortion 效果器提供程序,我如何将它们链接在一起以同时将它们应用到一个输入文件?

效果可以通过传递一个作为下一个的 "source" 来链接在一起。所以如果你想让你的音频先通过混响,然后失真,你可以这样做,将原始音频传递到混响效果中,将混响的输出传递到失真效果中,然后将失真发送到 waveOut设备。

var reverb = new ReverbSampleProvider(sampProvider);
var distortion = new DistortionSampleProvider(reverb);
waveOutDevice.Init(distortion);

(n.b。NAudio 没有内置 reverb/distortion 效果器 - 您必须自己制作这些效果器或从其他地方获取它们)

Mark 的回答是正确的,但是如果您以不同的顺序复制和粘贴东西,这种方法会很痛苦,因为您必须更改您正在传递的变量。

例如,如果您开始于:

var lpf = new LowPassEffectStream(input);
var reverb = new ReverbEffectStream(lpf);
var stereo = new StereoEffectStream(reverb);
var vol = new VolumeSampleProvider(stereo);
waveOutDevice.Init(vol);

如果您想交换混响和立体声,快速复制粘贴会让您的输入变量倒过来:

var lpf = new LowPassEffectStream(input);
var stereo = new StereoEffectStream(reverb); // <--
var reverb = new ReverbEffectStream(lpf);    // <--
var vol = new VolumeSampleProvider(stereo);
waveOutDevice.Init(vol);

这也使得修复一个参数而忘记修复另一个参数变得容易,例如修复 stereo 效果以将 lpf 作为其输入,但忘记修复 reverb 效果。这通常会导致链中的效果被跳过,从而在效果似乎不起作用时导致调试受挫。

为了在我将效果堆叠在一起并重新排序时使事情变得更容易且更不容易出错,我创建了以下助手 class:

class EffectChain : ISampleProvider
{
    public EffectChain(ISampleProvider source)
    {
        this._sourceStream = source;
    }

    private readonly ISampleProvider _sourceStream;
    private readonly List<ISampleProvider> _chain = new List<ISampleProvider>();

    public ISampleProvider Head
    {
        get
        {
            return _chain.LastOrDefault() ?? _sourceStream;
        }
    }

    public WaveFormat WaveFormat
    {
        get
        {
            return Head.WaveFormat;
        }
    }

    public void AddEffect(ISampleProvider effect)
    {
        _chain.Add(effect);
    }

    public int Read(float[] buffer, int offset, int count)
    {
        return Head.Read(buffer, offset, count);
    }
}

你可以这样使用它:

var effectChain = new EffectChain(input);

var lpf = new LowPassEffectStream(effectChain.Head);
effectChain.AddEffect(lpf);
var stereo = new StereoEffectStream(effectChain.Head);
effectChain.AddEffect(stereo);
var reverb = new ReverbEffectStream(effectChain.Head);
effectChain.AddEffect(reverb);
var vol = new VolumeSampleProvider(effectChain.Head);
effectChain.AddEffect(vol);

waveOutDevice.Init(effectChain);

这使您可以快速重新排序链中的效果,因为每个效果都将效果链的头部作为输入。如果您不添加任何效果,它只是作为一个传递。如果您愿意,您可以轻松地扩展此 class 以获得更多方法来管理包含的效果,但就目前而言,它工作得非常干净。