I never understood why “rewriting” samples was ever considered a good design 
choice. As I recall, DirectSound employs this technique.

The big problem here is that you’re assuming you have enough CPU to write the 
same sample slots more than once. Most modern users expect to be able to 
dedicate nearly 100% of the available CPU to meaningful audio calculations. If 
there were ever enough CPU cycles to afford going back to invalidate and 
rewrite samples on the fly, then those users would simply crank up the number 
of plugins and/or the complexity of the synthesis until no spare cycles are 
left. In other words, you can’t rely on being given an opportunity for a “do 
over” when it comes to audio.

Basically, the best design choice that meets customer expectations is to assume 
in your code that you only have one chance to write a given sample slot. 
Reducing latency between parameter updates and audible results simply requires 
smaller buffers at the hardware level.


In response to your earlier question, I was going to say that shifting audio 
processing to another thread and then feeding the results to the audio thread 
via a ringbuffer doesn’t actually give you any more time. There is still a hard 
limit for real time audio. Whatever time duration is represented by the audio 
buffer (given the sample rate and buffer size), that’s all the time you have 
for audio processing. Non-audio threads can’t run any faster than the audio 
thread. The only exception is, as Paul points out, those cases where audio is 
coming from non-real-time sources like disk I/O or perhaps network streaming. 
In the latter cases, having a non-audio-thread buffering scheme is a good idea 
(and necessary, since disk I/O cannot occur on the audio thread).

There are probably some special cases like convolution reverb where you can 
benefit from processing some of the algorithm on a separate thread that feeds 
the audio thread. But there will still be a requirement that low-latency 
processing be handling in the audio thread itself, without forgetting that 
low-latency processing must be bounded by the amount of CPU processing 
available in each audio time slot.

I suppose we should also consider the cases where multi-threaded processing 
could potentially run faster than the audio thread, in which case feeding the 
audio thread via a ringbuffer could allow a lot more processing. Note that 
latency is nearly always increased in these situations.

In contrast, if you have an audio application with basically no real-time 
requirements, e.g., iTunes playback, then you could easily process all audio in 
a separate thread and feed the finished results to the audio thread with a ring 
buffer. I believe that iTunes computes the crossfades between tracks in advance 
- it’s rather entertaining to listen to the audio glitches that occur when 
seemingly innocuous edits to the playlist trigger iTunes’ heuristics to 
recalculate that crossfade. iTunes used to be a glitch-free playback system, 
but there have been so many features tacked on that it has become unreliable.

Brian


On Feb 26, 2018, at 7:46 PM, Brian Armstrong <[email protected]> 
wrote:
> Thanks, that makes sense.
> 
> Even in the example you suggested, couldn't the producer invalidate/rewrite 
> the samples in the ring on parameter change? If the consumer reads front to 
> back, and producer writes back to front, then the change will occur at some 
> indeterminate point in the stream but will be applied consistently after 
> that. Assuming correct memory fences anyway, I think.
> 
> Brian
> 
> On Mon, Feb 26, 2018 at 7:36 PM Paul Davis <[email protected]> wrote:
>> On Mon, Feb 26, 2018 at 9:16 PM, Brian Armstrong 
>> <[email protected]> wrote:
>>> As a related question, is there any reason not to just feed the audio 
>>> thread with a ringbuffer filled on a different thread? You could manage 
>>> pointers either with try-lock or atomics.
>> 
>> ​that's exactly what you're supposed to do. and what just about every 
>> correctly implemented audio application does.
>> 
>> except that processing needs to happen in the audio thread to minimize 
>> latency. imagine the user tweaks the parameter of a plugin. they expect to 
>> hear the result ASAP. if you pre-process the audio before it hits the audio 
>> thread, you can't ensure that. so, you disk i/o and other 
>> non-latency-dependent processing outside the audio thread, write to the 
>> buffer; audio thread reads from it, runs plugins and the rest, delivers to 
>> the "hardware" (you don't have access to the hardware with coreaudio, but 
>> the concept is the same).
>> 
>> you don't need locks on a single-reader/single-writer circular buffer. you 
>> do need to ensure correct memory ordering.​

 _______________________________________________
Do not post admin requests to the list. They will be ignored.
Coreaudio-api mailing list      ([email protected])
Help/Unsubscribe/Update your Subscription:
https://lists.apple.com/mailman/options/coreaudio-api/archive%40mail-archive.com

This email sent to [email protected]

Reply via email to