我正在使用Juce框架来构建VST / AU音频插件。音频插件接受MIDI,并将MIDI作为音频样本呈现 - 通过发送由FluidSynth(声音合成器)处理的MIDI信息。
这几乎正常。 MIDI信息正确发送到FluidSynth。实际上,如果音频插件告诉FluidSynth将MIDI信息直接渲染到其音频驱动程序 - 使用正弦波声音 - 我们可以获得完美的结果:
但我不应该要求FluidSynth直接渲染音频驱动程序。因为那时VST主机不会收到任何音频。
要正确执行此操作:我需要实现渲染器。 VST主机每秒会询问我(44100÷512)次以呈现512个音频样本。
我尝试按需渲染音频样本块,并将其输出到VST主机的音频缓冲区,但这是我得到的那种波形:
这是相同的文件,每512个样本(即每个音频块)都有标记:
所以,显然我做错了什么。我没有得到连续的波形。我处理的每个音频块之间的不连续性非常明显。
这是我的代码中最重要的部分:我对JUCE SynthesiserVoice
的实现。
#include "SoundfontSynthVoice.h"
#include "SoundfontSynthSound.h"
SoundfontSynthVoice::SoundfontSynthVoice(const shared_ptr<fluid_synth_t> synth)
: midiNoteNumber(0),
synth(synth)
{}
bool SoundfontSynthVoice::canPlaySound(SynthesiserSound* sound) {
return dynamic_cast<SoundfontSynthSound*> (sound) != nullptr;
}
void SoundfontSynthVoice::startNote(int midiNoteNumber, float velocity, SynthesiserSound* /*sound*/, int /*currentPitchWheelPosition*/) {
this->midiNoteNumber = midiNoteNumber;
fluid_synth_noteon(synth.get(), 0, midiNoteNumber, static_cast<int>(velocity * 127));
}
void SoundfontSynthVoice::stopNote (float /*velocity*/, bool /*allowTailOff*/) {
clearCurrentNote();
fluid_synth_noteoff(synth.get(), 0, this->midiNoteNumber);
}
void SoundfontSynthVoice::renderNextBlock (
AudioBuffer<float>& outputBuffer,
int startSample,
int numSamples
) {
fluid_synth_process(
synth.get(), // fluid_synth_t *synth //FluidSynth instance
numSamples, // int len //Count of audio frames to synthesize
1, // int nin //ignored
nullptr, // float **in //ignored
outputBuffer.getNumChannels(), // int nout //Count of arrays in 'out'
outputBuffer.getArrayOfWritePointers() // float **out //Array of arrays to store audio to
);
}
这是要求合成器的每个声音产生512个音频样本块的地方。
这里的重要功能是SynthesiserVoice::renderNextBlock()
,其中我要求fluid_synth_process()
生成一个音频样本块。
这是代码,告诉每个语音renderNextBlock()
:我AudioProcessor
的实现。
AudioProcessor::processBlock()
是音频插件的主循环。在其中,Synthesiser::renderNextBlock()
会调用每个语音的SynthesiserVoice::renderNextBlock()
:
void LazarusAudioProcessor::processBlock (
AudioBuffer<float>& buffer,
MidiBuffer& midiMessages
) {
jassert (!isUsingDoublePrecision());
const int numSamples = buffer.getNumSamples();
// Now pass any incoming midi messages to our keyboard state object, and let it
// add messages to the buffer if the user is clicking on the on-screen keys
keyboardState.processNextMidiBuffer (midiMessages, 0, numSamples, true);
// and now get our synth to process these midi events and generate its output.
synth.renderNextBlock (
buffer, // AudioBuffer<float> &outputAudio
midiMessages, // const MidiBuffer &inputMidi
0, // int startSample
numSamples // int numSamples
);
// In case we have more outputs than inputs, we'll clear any output
// channels that didn't contain input data, (because these aren't
// guaranteed to be empty - they may contain garbage).
for (int i = getTotalNumInputChannels(); i < getTotalNumOutputChannels(); ++i)
buffer.clear (i, 0, numSamples);
}
我有什么误解吗?是否需要一些精确的时间来使FluidSynth为我提供与前一块样品背靠背的样品?也许是我需要传递的偏移?
也许FluidSynth是有状态的,并且有自己的时钟,我需要控制它?
我的波形是否有一些众所周知的问题?
答案 0 :(得分:1)
在我写完最后一段时,我意识到:
fluid_synth_process()
没有提供指定时序信息或样本偏移的机制。然而,我们观察到时间不断前进(每个块都不同),因此最简单的解释是:FluidSynth实例从时间0开始,每次调用fluid_synth_process()
时前进numSamples * sampleRate秒。
这导致了一个启示:因为const int numVoices = 8;
对FluidSynth实例的时间有副作用: 多个声音在同一个合成实例上运行< / em>的
我尝试将const int numVoices = 1;
缩减为fluid_synth_process()
。因此,每个块只有一个代理会调用SynthesiserVoice::renderNextBlock()
。
这解决了问题;它产生了一个完美的波形,并揭示了不连续性的来源。
所以,我现在离开了一个更容易的问题“什么是在FluidSynth中合成多个声音的最佳方法”。这是一个更好的问题。这超出了这个问题的范围,我将分别进行调查。谢谢你的时间!
编辑:修复了多个声音。我这样做是为了让fluid_synth_process()
成为无操作,并将其AudioProcessor::processBlock()
移动到{{1}} - 因为它应该每块调用一次(不是每次一次)每块语音)。