我刚刚开始尝试为我一直在研究的小型音乐节目制作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,虽然似乎没有太多运气,我目前正在假设他们测量从当前数据块开始的音频帧数,到该区块内的事件发生时。不幸的是,我一直在努力寻找有用的文档,所以我可能完全错了。
期待任何回复
亲切的问候
詹姆斯
答案 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事件的方法之外,我一直在从比特率转换到采样率。我的音频现在播放的速度远远超出了我的预期,并且在时间上似乎更可靠,但我还没有机会对此进行全面测试。