RingBuffer is a missing piece


#1

Everytime I mes with juce audio, I seem to come up against the lack of a ring buffer, either in juce, or fairly easy to use with juce, and ideally non-blocking.

My current situation is that I have a bunch of samples in memory, already pulled from a file, about a second ahead. They are currently in AudioSampleBuffer subclasses, with the sample times. Now I need to make the correct ‘range’ of samples available for audio callbacks to access (using a position to see what samples to get). Obviously, I don’t want them to have to search through all the potential buffers, especially since the reading thread could be trying to deposit newly read ones. So my idea is to keep a ring buffer with the min/max the audio callback should need.

I seem to remember there’s been various ‘just use a ring buffer’ comments, and a recent discussion where Jules noted that even the examples have potential threading problems in the audio area.

Has anyone solved this and can shed some light? Possibly a ring buffer plus juce atomics or similar, so it’s non-blocking, or at least lock-free?

:? Bruce


#2

Quite true, I really ought to do one…


#3

I use and recommend Portaudio’s ring buffer. It’s easy to use and has been well tested. It’s also under an MIT license so it could be used in your closed-source project or in Juce for that matter.


#4

Funny, I’m just on a Google odyssey now.

I found the PortAudio stuff, including a sample version, a mention of one from PortAudio v19 - that’s vanished from their code repo, and then the one in the current trunk. That seems to have a lot of dependencies though. Would you mind sharing the version you recommend?

I’m also looking at Jack’s ringbuffer, which seems usable on it’s own (although I’m targeting Mac and Linux, and Linux will have Jack as a dependency anyway).

I still have some vague hope that Jules will send down a flash of brilliance, as he often does, but he has his hands full usually too.

The odd thing is that though most of for audio, none seem to address multiple channels. Interleaving a bunch of samples just to have to de-interleave them later seems a bit wacky to me.

Bruce


#5

Ok, call me crazy - I’m considering rolling my own. From what I can tell, the main hurdles are:

  1. The lack of atomic operations
  2. Differences in value between cores, due to caching
  3. Re-ordering of operations - write and read being swapped so invalid values may happen
  4. Maybe the memory being moved?

And the simplest solutions as far as I can tell:

  1. Setting and reading an integer of less than 8 bytes is atomic if it’s memory aligned, on x86 and x64
  2. Set the variables as volatile. This should prevent the cores from caching them (yes, I know volatile isn’t magical like in Java)
  3. Use a MemoryBlock. PortAudio has them implemented, so we can copy what they use. I’ve also seen mention that function calls can’t be re-ordered. Also, if all variables are volatile, that restricts the re-ordering the compiler can do.
  4. I think maybe Jack locks the memory down.

So what I’m thinking of is something similar to an AudioBuffer, but it would just add a ‘head’ and ‘tail’ integer (probably offsets into the buffer, and probably they would be pointers, so I can assure alignment).

After I write into the buffer (samples for each channel), I would then set the tail.
After the reader comes in, it sets the head.
I know there’s a fair amount of hassle when the two are equal, or nearly so (empty or full buffer), but since I’m using time to drive the two, I can maintain a fairly good relationship, AFAICT.

This, to be clear, is for a single reader and single writer thread only. I got most ideas from this:
http://www.codeproject.com/KB/threads/LockFree.aspx and the reference links, although I’ve researched this topic 3 or 4 times now, looking more for message thread type stuff generally.

Bruce

PS Just re-read some long screeds about mis-use of ‘volatile’. Forget I mentioned it.


#6

More than happy to help with ideas if you want to get something going there, but too busy concentrating on other stuff to offer very much brain-space right now.

I’d have thought that the Atomic class has enough atomic ops to handle something like this - were you not aware of that class?

Probably because atomically reading and writing multiple separate channels is harder than a stream of sequential values. But if you had a simple ring buffer object that holds a single channel, and wrap some of those up into a multi-channel buffer, that’d probably work…


#7

One reason to do this would be that you need multiple channels. In this case I would consider looking at the Portaudio example, or another example you like, and changing the read and write code to access multiple channels.

To use the Portaudio code I included three files in my project: pa_memorybarrier.h, pa_ringbuffer.c and pa_ringbuffer.h.


#8

I thought it only had increment and decrement. I see there’s a bit more. Did you do 64-bit versions of everything (not that I intend to use it right now) - I thought CAS was tough on 64-bit.

On the multiple buffer issue - first, most implementations seem to work a byte/word at a time, which seems silly. I want to use normal AudioSampleBuffer type operations - slam a whole bunch of samples right into the memory then set the markers.

PortAudio, Jack and the other information is probably enough to make a pretty good pass at it - I’ll try to work in the atomics so I can reduce the number of assumptions.

Bruce


#9

Cool - let me know if you need any more atomics than what’s already there.


#10

Sure, yes please.

AtomicGet and AtomicSet please. Both are CAS derivative, I believe. If you think an AtomicInt class would be cleaner, then that, but I would leave set and get explicit not overload operators, so it’s clear that it’s atomic.

And MemoryBarrier please. Maybe a rare (for Juce) macro, like declare singleton is? Borrowing from PortAudio (thanks PortAudio) the platform calls are:

if defined(APPLE)
OSMemoryBarrier()
#elif defined(GNUC)
__sync_synchronize()
#elif (_MSC_VER >= 1400) && !defined(_WIN32_WCE)
_ReadWriteBarrier()

http://www.portaudio.com/trac/browser/portaudio/trunk/src/common/pa_memorybarrier.h

In terms of samples and multiple channels, I want to handle multiple channels at once so that there isn’t the risk of the reader visiting while only half the channels are updated or vice versa. It makes more sense to me to have one head and one tail, and always read and write all channels.

Bruce


#11

Ok, will get onto that. I think I might have a go at an atomic integer class.


#12

Excuse me, is this helpful?
http://code.google.com/p/juced/source/browse/trunk/juce/src/extended/containers/jucetice_CircularBuffer.h


#13

Thanks, it’s good for another general ringbuffer example, but it doesn’t focus much on multithread safeness, lock-free etc.

Bruce


#14

Ok, this was a good excuse for me to refactor all the atomic ops, and I’m quite pleased with the new class for it. It seems to work pretty well, but may need some fine-tuning, so feedback welcome!

Basically, there’s now a templated Atomic class that you use to wrap your values. It can do all the stuff the old static methods did, but much more elegantly.


#15

When do you sleep?


#16

Please don’t confuse this clever hard-worker with the “S” word. Jules, please pretend it was never mentioned. :smiley:

At 6:48am my time now, I myself am thinking of putting my carcass into suspend mode, at least long enough for my lithium to recharge a bit. But let’s not push it!

I think my eyes are permanently forced open. I may have “Jules Syndrome”. :shock:


#17

I actually sleep quite a lot! I just type very fast while I’m awake!


#18

I dunno if someone already made this but...

I made a circular audio array class ( CAArray.cpp and CAArray.h ) that has optional 2 and 4 point interplation. It's suitable for creating multi-tap delays, modulated delays, reverb and even oscillators and non-delay interpolation.

https://github.com/Jeff-Russ/jATK/tree/master/Source/jATK be aware that it depends on helpers.h

I literally just finished it and started bug-checking. So far so good.  

 

P.S. is something like this already part of JUCE? lol@!@


#19

Cheers, will check this out


#20

This is a very old thread - maybe from before the AbstractFifo class was added..?