Understanding the need for audio buffers


#1

So I started all my audio coding with the FX and Synthbooks by Will Pirkle. When poking around the interwebs, I found that some experienced coders were dissatisfied with the code and concepts presented within. One particular point was the omition of audio buffers (i.e. everithing is done one sample at a time).

So how is the workflow and what are the benefits of using buffers?
I’ll just type what I think it is. Please correct me if I am wrong:

I create a whole buffer of samples in my oscs, pass the entire buffer to my filters, then to the next component and so on. The benefit seems to be that data fetching from RAM is heavily reduced a variable is reused multiple times.

Is there more to it? Are there unforseen problems arising here? (steppy controls and such…)
Appreciante any input and nudges towards more literature. :slight_smile:


#2

You will get steppy parameter changes for plugins when using JUCE, no matter if your internal processing uses buffers or sample-by-sample processing. JUCE does not support the sample accurate parameter change mechanisms available in some plugin formats. (With the exception of MIDI CC’s but that is mostly an instrument plugin feature.) You can of course alleviate the issue by doing internal smoothing in the plugin for the parameters, but you will get the parameter updates from the host only at the audio buffer boundaries.


#3

Not sure what you mean by “a variable is reused multiple times”.

The benefit of processing blocks of samples is that CPUs are good at processing items stored continuous in memory with the same operation (google for SIMD if you want further information) so after having created your buffer in your oscillator you might want to filter it, where each sample would maybe undergo the same mathematical operation. The compiler might not be able to use this processing power of the CPU if your code doesn’t work on continuous buffers but on single samples.

Furthermore when it comes to caching, CPUs usually cache completely lines of memory, so if you are working on continuous buffers, it’s likely that multiple samples will be fetched into the cache at one time, which also speeds up your code. Working on single samples could result in more cache misses.

And at last, if you call your processing callback on a per sample basis instead of a per buffer basis, it might be possible that the function call overhead will become a much bigger part of your code execution time and therefore will slow down your code.

However, these are just some general considerations and they might not be true for all situations. There are cases where per sample processing is really senseful or unavoidable and compilers are quite good at optimizing nowadays, so a lot of overhead might be reduced by the intelligence of your compiler.


#4

I meant to say that various variables still lingure in L1 or L2 cache and needn’t be fetched from RAM again.
Anyway thanks for your input, this was quite helpful! :slight_smile:


#5

Buffers are needed when you’re moving data in-and-out of plugins, and to/from audio i/o devices, because there’s a fixed overhead for each callback to move data, so grouping samples into chunks reduces that overhead.

However… the fact that people use buffers so pervasively in their own internal code is probably a mistake/habit (which we’re trying to correct with the SOUL architecture). As mentioned above, for most simple purposes the compiler is perfectly good at vectorising your code, and writing things in a sample-by-sample style is the best approach where possible.

I’d definitely suggest writing your code as sample-by-sample initially, and only refactoring it to use buffers if you actually hit a measurable performance problem and can show that using buffers would help.


#6

Just to clarify: Even for an entire synth voice with multiple oscs, filters and FX and more?

Anyway, I’ll just profile it and test for my specific plugin! :slight_smile:


#7

Will told me that the upcoming revision of the FX book (May 2019?) addresses this question in detail and uses a new architecture that doesn’t tie it so closely to his RackAFX system.


#8

Is this also true, if you write your code frame based instead of channel based?

I do see the benefit for modern code like the dsp module, but I think if it involves virtual calls, then the optimiser is out of it’s possibilities, or am I wrong in that thinking?

Sorry, but this statement bangs on the fundaments of my view of the world, maybe I missed the memo… :wink:


#9

Depending on what you’re doing, then it might be even better to write frame-based algorithms in a per-sample style, if for example you can put all the frames into a vector and operate on them all at the same time - that’s the easiest thing for the compiler to convert SIMD.

And no, you probably don’t want to be making a virtual call per sample, but I’m talking about inner loop stuff where you’d want to avoid high-level stuff like polymorphism anyway.