[Portaudio] Lock free queue for callback thread
rossb-lists at audiomulch.com
Fri Apr 13 02:19:30 EDT 2012
On 13/04/2012 6:49 AM, ChordWizard Software wrote:
> Hi Ross,
>> What you want to do is use the srsw queue to buffer *commands*
>> that mutate the non-shared state that's used in the PA callback.
>> With this in mind you might want to re-read my previous message.
> Thanks, it’s becoming clearer now. So the callback thread does the
> synthesis on demand, and hosts any VSTi plugins, but before
> processing it checks the latest events from the lock-free queue
> which have been placed there by the sequencing thread?
I didn't say anything about a sequencing thread. I was thinking:
GUI -> Callback
Two contexts. One queue. Or maybe two queues if you need to return data
to the GUI.
You could have a sequencing thread, probably with a separate queue to
the Callback. And yes, you could have a sequencing thread that runs a
little bit ahead of the callback and sends timestamp events to the
callback. But that's just one way of doing it.
Another way to do it would be to run your sequencing engine in the
callback and use the command queue to communicated updated sequence data
to the callback.
I imagine some considerations that might come up are:
- If you use a scheduling thread, then you can use mutexes over the
sequence data, and if the GUI stalls while it's editing the sequence
data, at least then it just glitches the event playback a bit rather
than glitching the audio.
- If you need to implement time code synchronisation of audio and midi
events it might be easier to do everything including the event
scheduling in the PortAudio callback. But then you need to use the queue
to communicate sequence structure edits from the GUI.
> Should these event be time-stamped do you think, or does the callback
> get invoked frequently enough that we can assume that events on the
> queue have happened 'now'?
If you want sample-accurate sequencing (which some users will expect)
then yes, you'll need timestamps. In general, for average (not high-end)
hardware, with good drivers, you can expect 5ms callbacks, maybe better
-- 5ms is probably too much jitter for a sequencer. With WMME you won't
do better than 20ms -- way too much.
> In fact, now that I think about it, don't we get the same problem?
> That in order to ensure the callback is not missing out on events
> within it's timespan of interest, we have to buffer them a little way
> ahead of time?
Yes. That's another reason to do your scheduling in the callback and
then use the queue to communicate the sequence data. This could be in
the form of:
- new versions of sequence tracks after they are edited
- edit commands for transforming an existing sequence track to the new one.
(Keep in mind that you can't allocate any memory in the callback unless
you implement some non-blocking O(1) allocation infrastructure for the
callback's exclusive use.)
If sequence events are the main type of data you're communicating with
the callback you could also consider using try-locks. The callback trys
to lock a mutex against the sequence data, and if it fails, it just
doesn't shedule any new events that callback. It's going to be up to you
whether that's acceptable.
There are probably other ways of doing it that I havn't thought of either.
Ultimately these are application architecture decisions that are well
outside the scope of PortAudio to solve, but I think you get the point.
>>> In that case, how do we interrogate the stream to find out the
>>> actual buffer size chosen
>> You can't query for that. Although it has been requested. In fact
>> if paFramesPerBufferUnspecified is used there is no guarantee that
>> framesPerBuffer is constant.
> OK, but I guess it doesn't matter now if the callback thread is doing
> the synthesis, it can just keep processing until it has filled
> whatever buffer size appears.
Right. There are still cases where it is useful to know, but in most
cases it's the stream latency you need to know, not the callback buffer
More information about the Portaudio