I’m looking through the AudioProcessor base class, and I see that there are two versions of the virtual void processBlock(): one that takes an AudioBuffer<float>, and one that takes an AudioBuffer<double>.
If I want my plugin to work with either 32-bit or 64-bit systems, should I implement both of these methods? Does everything have to be written twice, with two versions of any internal buffers…?
or is there some way of doing a function template…?
So if I have any internal buffers as members anywhere, would I need to create two versions, one for float and one for double…? Or is there some way to template/conditional those too?
The used audio sample type doesn’t have anything to do with the CPU or operating system type. That is, you don’t for example need to have support for 64 bit floating point on a 64 bit OS.
so if I were to only implement the <float> version of processBlock and not the <double> version, it would still run on any computer & any operating system…?
but either 32-bit binaries or 64-bit binaries can be built from the <float> version of processBlock?
so is it correct to say that the <double> version is just like an extra layer of precision for some systems that may happen to support it, but the <float> version provides basically universal compatibility/usability?
@rory if I’m going to wrap my processing like this and redirect both versions of processBlock to a templated function, how should I deal with internal buffers I need to use for processing?
Is there a way to convert their type depending on the input, or maybe re-initialize them during prepareToPlay if the type changes…?
If I understand correctly you’re asking about how to deal with buffers that might be members of your AudioProcessor class, and how best to use them in the two template functions? I’m not sure what the best approach is here. I would just use floats throughout. Is there anything in the FloatVectorOperations class that might do buffer conversions?
They can be converted, but that might undo all efforts (not discussing the necessity or uselessness of double precision processing here).
Another thing to point out: there is no need for a 64 bit architecture to support double precision processing. All permutations will work, 64 bit processing on a 32 bit OS and vice versa.
What you can do is write your processSamples method to accept the internal buffer as third parameter. That way you call it in the not templated calls with the correct variant of the internal buffer.
You can also create a templated “engine” class, so everything is wrapped there:
template<typename FloatType>
class Engine
{
public:
Engine (MyProcessor& p) : processor (p) {}
void process (juce::AudioBuffer<FloatType>& buffer);
private:
// use a back reference to access functions of the AudioProcessor
MyProcessor& processor;
// only one buffer to write
juce::AudioBuffer<FloatType> wetBuffer;
juce::AudioBuffer<FloatType> dryBuffer;
};
// processor members
Engine<float> singleMachine { *this };
Engine<double> doubleMachine { *this };
The AudioBuffer would be there in any case. But you will probably need something like setup() in the Engine, that is called from prepareToPlay(). There you can ask there for isUsingDoublePrecision() and only setup the engine you are going to use.
This way the audio buffers in the unused engine remain at zero size.
cool - so the Engine class’s internal buffers would remain at 0 size unless & until its prepare() method is called in the top-level processor’s prepareToPlay, correct?
and there could also be an engine.releaseResources() to resize its buffers to 0, in case the user switches precision processing modes, so it won’t still have memory allocated for both instances of engine…
Something that’s worth baring in mind is that you’ll need to make sure that every process that’s editing your samples (filters, compressors, etc.) will also need to be templated to handle either data type. There’s no point in using double samples in your Engine class if your filters only use floats as you’ll just lose the extra precision.
Also, I try to avoid using juce::AudioBuffer for storing samples passing through processBlock.
It’s really easy to write something like:
void processBlock(juce::AudioBuffer<float>& buffer, juce::MidiBuffer&)
{
dryBuffer = buffer;
// do some processing...
wetBuffer = buffer;
}
The issue is that if the buffer passed to processBlock is not the same size as dryBuffer or wetBuffer then these buffers will be resized by the copy which will involved some memory allocation.
Instead I’d suggest using a fixed-size FIFO for storing audio data, or even a ring buffer. You could always store some write indicies along with your wet/dry buffers and treat them as FIFOs but your code will be a lot cleaner if you use/write a dedicated class for the job (which could use a fixed-size AudioBuffer internally if you really wanted).