如何发送要由合成器处理的音频块——不间断

How to send blocks of audio to be processed by synthesizer -- without discontinuities

我正在使用 Juce framework to build a VST/AU audio plugin. The audio plugin accepts MIDI, and renders that MIDI as audio samples — by sending the MIDI messages to be processed by FluidSynth(一种音色合成器)。

这几乎可以正常工作了。 MIDI 消息被正确发送到 FluidSynth。事实上,如果音频插件告诉 FluidSynth 将 MIDI 消息直接渲染到它的音频驱动程序——使用正弦波 soundfont——我们会得到一个完美的结果:

但我不应该让 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(), wherein I ask fluid_synth_process()生成音频样本块。


这是告诉每个声音 renderNextBlock() 的代码:我对 AudioProcessor 的实现。

AudioProcessor::processBlock() is the main loop of the audio plugin. Within it, 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 是有状态的,并且有我需要控制的自己的时钟?

我的波形是否是某个众所周知问题的征兆?

源代码是here, in case I've omitted anything important. Question posted at the time of commit 95605.

当我写下最后一段时,我意识到:

fluid_synth_process() 不提供指定计时信息或样本偏移量的机制。然而,我们观察到时间仍然在推进(每个块都不同),所以最简单的解释是:FluidSynth 实例从时间 0 开始,每次调用 fluid_synth_process() 时推进 numSamples*sampleRate 秒。

这导致了启示:因为 fluid_synth_process() 对 FluidSynth 实例的时间有副作用:多个声音 到 运行 这在同一个合成器实例上

我尝试将 const int numVoices = 8; 减少到 const int numVoices = 1;。因此每个区块只有一个代理会调用 fluid_synth_process()

这解决了问题;它产生了一个完美的波形,并揭示了不连续的来源。

所以,我现在面临一个更简单的问题 "what's the best way to synthesize a plurality of voices in FluidSynth"。这是一个更好的问题。这超出了这个问题的范围,我将单独调查。感谢您的宝贵时间!

编辑:修复了多个语音。我通过使 SynthesiserVoice::renderNextBlock() 成为空操作,并将其 fluid_synth_process() 移动到 AudioProcessor::processBlock() 来实现这一点——因为它应该在每个 中调用一次(不是每个块每个声音一次)。