J'utilise le framework Juce pour créer un plug-in audio VST/AU. Le plugin audio accepte le MIDI, et rend ce MIDI comme échantillons audio - en envoyant les messages MIDI à traiter par FluidSynth (un synthétiseur soundfont).Comment envoyer des blocs d'audio à traiter par synthétiseur - sans discontinuités
Cela fonctionne presque. Les messages MIDI sont envoyés à FluidSynth correctement. En effet, si le plug-in audio indique FluidSynth pour rendre les messages MIDI directement à son pilote audio - en utilisant une soundfont onde sinusoïdale - nous obtenons un résultat parfait:
Mais je ne devrais pas demander FluidSynth rendre directement à le pilote audio. Car alors l'hôte VST ne recevra aucun son.
Pour ce faire correctement: J'ai besoin de mettre en œuvre un renderer. L'hôte VST me demandera (44100 ÷ 512) fois par seconde de rendre 512 échantillons d'audio.
J'ai essayé de rendu des blocs d'échantillons audio à la demande, et la sortie ceux du tampon audio hôte VST, mais c'est le genre de forme d'onde que je suis:
Voici la même fichier, avec des marqueurs tous les 512 échantillons (soit tous les blocs de données audio):
Donc, clairement, je fais quelque chose de mal. Je ne reçois pas une forme d'onde continue. Les discontinuités sont très visibles entre chaque bloc d'audio que je traite.
est ici la partie la plus importante de mon code: ma mise en œuvre de JUCE de 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
);
}
C'est ici que l'on demande à chaque voix du synthétiseur de produire le bloc de 512 échantillons audio.
La fonction importante ici est SynthesiserVoice::renderNextBlock()
, dans laquelle je demande fluid_synth_process()
pour produire un bloc d'échantillons audio.
Et voici le code qui indique toutes les voix à renderNextBlock()
: ma mise en œuvre de AudioProcessor
.
AudioProcessor::processBlock()
est la boucle principale du plug-in audio. En son sein, Synthesiser::renderNextBlock()
invoque SynthesiserVoice::renderNextBlock()
de toutes les voix:
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);
}
Y at-il quelque chose que je suis malentendu ici? Y a-t-il une certaine subtilité de synchronisation nécessaire pour que FluidSynth me donne des échantillons qui sont dos à dos avec le bloc d'échantillons précédent? Peut-être un décalage que je dois passer?
Peut-être que FluidSynth est à état, et a sa propre horloge dont j'ai besoin de prendre le contrôle de?
Ma forme d'onde est-elle symptomatique d'un problème bien connu?
Le code source est here, dans le cas où j'ai omis quelque chose d'important. Question publiée au moment de la validation 95605
.
Beau travail. :) En fait, c'est un peu surprenant que FluidSynth soit ce lourd apatride. Est-ce qu'il effectue beaucoup de filtrage avec des retards? – bipll
@bipll merci. :) Je ne suis pas sûr de comprendre votre question. Demandez-vous si fluidsynth peut rendre l'audio assez rapidement pour remplir le tampon? certainement, il peut rendre 512 échantillons 44100 fois par seconde; l'audio sonne bien. ou demandez-vous s'il peut appliquer des filtres assez rapidement pour remplir le tampon? Je n'ai pas encore essayé le chorus et la réverbération. ou demandez-vous s'il a un filtre "retard"? Je pense que non. demandez-vous si la synthèse est à faible latence? Je suppose que la seule latence qui compte est "à quelle vitesse peut-il traiter midiNoteOn". Cependant, je n'ai pas mesuré cette latence. – Birchlabs
's/stateless/stateful /'> _ <. Je suis simplement curieux que fluid_synth_process n'ait même pas d'argument startSample et renvoie simplement les wavedata séquentiellement, block by block. Cela a probablement quelque chose à voir avec ses filtres internes qui utilisent des retards. – bipll