VST主机播放时序问题(VST.NET + NAudio)

时间:2017-05-21 23:02:03

标签: c# midi host naudio vst

我刚刚开始尝试为我一直在研究的小型音乐节目制作VST插件托管。我现在已经达到了能够获取存储在我的程序中的旋律并将midi数据发送到托管插件(使用VST.NET)并将音频输出到WaveOut(NAudio)的程度。问题是音频输出播放得太快而且不及时。

这是我用于播放的代码,部分内容基于VST.NET示例项目中的示例主机:

public class PhraseEditorWaveProvider : VstWaveProvider
{

    public PhraseEditor PhraseEditor { get; private set; }


    public Rational PlaybackBeat { get; private set; }


    public PhraseEditorWaveProvider(PhraseEditor phraseEditor, string pluginPath, WaveFormat waveFormat = null)
        : base(pluginPath, waveFormat)
    {
        PhraseEditor = phraseEditor;
    }



    public override int Read(byte[] buffer, int offset, int count)
    {
        decimal framesPerBeat = (60 / (decimal)PhraseEditor.Phrase.Tempo) * WaveFormat.SampleRate;

        Rational startBeat = PlaybackBeat;
        Rational endBeat = startBeat + Rational.FromDecimal(count / framesPerBeat);

        //Get list of note starts and note ends that occur within the beat range
        List<VstEvent> vstEvents = new List<VstEvent>();
        foreach(Note note in PhraseEditor.Phrase.Notes)
        {
            if(note.StartBeat >= startBeat && note.StartBeat < endBeat)
                vstEvents.Add(NoteOnEvent(1, (byte)note.Pitch.Value, 100, (int)(note.Duration * framesPerBeat), (int)((note.StartBeat - startBeat) * framesPerBeat)));

            if(note.EndBeat >= startBeat && note.EndBeat < endBeat)
                vstEvents.Add(NoteOffEvent(1, (byte)note.Pitch.Value, (int)((note.EndBeat - startBeat) * framesPerBeat)));
        }

        foreach(Chord chord in PhraseEditor.Phrase.Chords)
        {
            if(chord.StartBeat >= startBeat && chord.StartBeat < endBeat)
            {
                //Play each note within a chord in the 4th octave, with velocity 70
                foreach (Pitch pitch in chord.Pitches)
                    vstEvents.Add(NoteOnEvent(1, (byte)((pitch.Value % 12) + 48), 70, (int)(chord.Duration * framesPerBeat), (int)((chord.StartBeat - startBeat) * framesPerBeat)));
            }
            if(chord.EndBeat >= startBeat && chord.EndBeat < endBeat)
            {
                foreach(Pitch pitch in chord.Pitches)
                    vstEvents.Add(NoteOffEvent(1, (byte)((pitch.Value % 12) + 48), (int)((chord.EndBeat - startBeat) * framesPerBeat)));
            }
        }
        PlaybackBeat = endBeat;

        return base.Read(vstEvents.OrderBy(x => x.DeltaFrames).ToArray(), buffer, offset, count);
    }

}

public abstract class VstWaveProvider : IWaveProvider
{

    private WaveFormat _waveFormat;
    public WaveFormat WaveFormat
    {
        get
        {
            return _waveFormat;
        }
        set
        {
            _waveFormat = value;
            BytesPerWaveSample = _waveFormat.BitsPerSample / 8;
        }
    }

    public VstPluginContext VstContext { get; private set; }

    public int BytesPerWaveSample { get; private set; }



    public VstWaveProvider(VstPluginContext vstContext, WaveFormat waveFormat = null)
    {
        WaveFormat = (waveFormat == null) ? new WaveFormat(44100, 2) : waveFormat;
        VstContext = vstContext;
    }

    public VstWaveProvider(string pluginPath, WaveFormat waveFormat = null)
    {
        WaveFormat = (waveFormat == null) ? new WaveFormat(44100, 2) : waveFormat;
        VstContext = OpenPlugin(pluginPath);
    }


    public abstract int Read(byte[] buffer, int offset, int count);


    protected int Read(VstEvent[] vstEvents, byte[] outputBuffer, int offset, int count)
    {
        VstAudioBufferManager inputBuffers = new VstAudioBufferManager(
            VstContext.PluginInfo.AudioInputCount,
            count / (Math.Max(1, VstContext.PluginInfo.AudioInputCount) * BytesPerWaveSample)
        );

        return Read(inputBuffers, vstEvents, outputBuffer, offset, count);
    }


    protected int Read(VstAudioBufferManager inputBuffers, VstEvent[] vstEvents, byte[] outputBuffer, int offset, int count)
    {
        VstAudioBufferManager outputBuffers = new VstAudioBufferManager(
            VstContext.PluginInfo.AudioOutputCount,
            count / (VstContext.PluginInfo.AudioOutputCount * BytesPerWaveSample)
        );

        VstContext.PluginCommandStub.StartProcess();
        if(vstEvents.Length > 0)
            VstContext.PluginCommandStub.ProcessEvents(vstEvents);
        VstContext.PluginCommandStub.ProcessReplacing(inputBuffers.ToArray(), outputBuffers.ToArray());
        VstContext.PluginCommandStub.StopProcess();

        //Convert from multi-track to interleaved data
        int bufferIndex = offset;
        for (int i = 0; i < outputBuffers.BufferSize; i++)
        {
            foreach (VstAudioBuffer vstBuffer in outputBuffers)
            {
                Int16 waveValue = (Int16)((vstBuffer[i] + 1) * 128);
                byte[] bytes = BitConverter.GetBytes(waveValue);
                outputBuffer[bufferIndex] = bytes[0];
                outputBuffer[bufferIndex + 1] = bytes[1];
                bufferIndex += 2;
            }
        }
        return count;
    }


    private VstPluginContext OpenPlugin(string pluginPath)
    {
        HostCommandStub hostCmdStub = new HostCommandStub();
        hostCmdStub.PluginCalled += new EventHandler<PluginCalledEventArgs>(HostCmdStub_PluginCalled);

        VstPluginContext ctx = VstPluginContext.Create(pluginPath, hostCmdStub);

        ctx.Set("PluginPath", pluginPath);
        ctx.Set("HostCmdStub", hostCmdStub);

        ctx.PluginCommandStub.Open();
        ctx.PluginCommandStub.MainsChanged(true);

        return ctx;
    }


    private void HostCmdStub_PluginCalled(object sender, PluginCalledEventArgs e)
    {
        Debug.WriteLine(e.Message);
    }


    protected VstMidiEvent NoteOnEvent(byte channel, byte pitch, byte velocity, int noteLength, int deltaFrames = 0)
    {
        return new VstMidiEvent(deltaFrames, noteLength, 0, new byte[] { (byte)(144 + channel), pitch, velocity, 0 }, 0, 0);
    }


    protected VstMidiEvent NoteOffEvent(byte channel, byte pitch, int deltaFrames = 0)
    {
        return new VstMidiEvent(deltaFrames, 0, 0, new byte[] { (byte)(144 + channel), pitch, 0, 0 }, 0, 0);
    }

}

以下内容会被调用:

WaveOut waveOut = new WaveOut(WaveCallbackInfo.FunctionCallback());
waveOut.Init(new PhraseEditorWaveProvider(this, @"C:\Users\james\Downloads\Cobalt\Cobalt\Cobalt 64bit\Cobalt.dll"));
waveOut.Play();

Cobalt是我用于测试的当前插件。

对于上下文,Rational是我自己的数据类型,因为我的程序的其他部分正在对旋律进行大量操作,我发现双精度和小数并没有给出我所需的精度。

此外,VST插件上下文和WaveOut都设置为44.1kHz的采样率,因此在将插件输出数据传递到WaveOut缓冲区时,不需要任何上/下采样。<​​/ p >

我完全不知道为什么音频的播放速度比预期的要快。它似乎比预期快大约4倍。如果有人能指出可能导致这种情况的任何指示,我将非常感激。

随着时间的推移,我怀疑这是由于我没有正确理解deltaFrame属性在VstMidiEvent中的工作原理。我尝试过使用deltaFrame和noteOffset,虽然似乎没有太多运气,我目前正在假设他们测量从当前数据块开始的音频帧数,到该区块内的事件发生时。不幸的是,我一直在努力寻找有用的文档,所以我可能完全错了。

期待任何回复

亲切的问候

詹姆斯

1 个答案:

答案 0 :(得分:1)

好的,我想我找到了导致问题的原因,就在这部分代码中:

public override int Read(byte[] buffer, int offset, int count)
{
    decimal framesPerBeat = (60 / (decimal)PhraseEditor.Phrase.Tempo) * WaveFormat.SampleRate;

    Rational startBeat = PlaybackBeat;
    Rational endBeat = startBeat + Rational.FromDecimal(count / framesPerBeat);
    ...
}

我刚才改为:

public override int Read(byte[] buffer, int offset, int count)
{
    decimal framesPerBeat = (60 / (decimal)PhraseEditor.Phrase.Tempo) * WaveFormat.SampleRate;
    int samplesRequired = count / (WaveFormat.Channels * (WaveFormat.BitsPerSample / 8));

    Rational startBeat = PlaybackBeat;
    Rational endBeat = startBeat + Rational.FromDecimal(samplesRequired / framesPerBeat);
    ...
}

我的愚蠢错误,除了我获得即将到来的midi事件的方法之外,我一直在从比特率转换到采样率。我的音频现在播放的速度远远超出了我的预期,并且在时间上似乎更可靠,但我还没有机会对此进行全面测试。