如何发送要由合成器处理的音频块——不间断
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()
来实现这一点——因为它应该在每个 块 中调用一次(不是每个块每个声音一次)。
我正在使用 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()
来实现这一点——因为它应该在每个 块 中调用一次(不是每个块每个声音一次)。