Я использую структуру Juce для создания аудиоплагина VST/AU. Аудио-плагин принимает MIDI-данные и обрабатывает их как аудиосэмплы, отправляя MIDI-сообщения для обработки FluidSynth (синтезатор звуковых шрифтов).
Это почти работает. 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 поддерживает состояние и имеет собственные часы, которыми мне нужно управлять?
Является ли моя осциллограмма симптомом какой-то известной проблемы?
Исходный код здесь на случай, если я пропустил что-то важное. Вопрос опубликован во время фиксации 95605
.