Audio: __disable_irq() and update()

Status
Not open for further replies.

endolith

New member
I don't understand this: http://www.pjrc.com/teensy/td_libs_AudioNewObjects.html says:

Normally sketch-called function should use __disable_irq() and __enable_irq() to temporarily disable all interrupts while reading or writing a set of variables that are used used by update().

The built-in synth functions do not do this, such as AudioSynthWaveformSine just modifies phase_increment, phase_accumulator, and magnitude directly, and then AudioSynthWaveformSine::update uses the phase_increment and magnitude values directly. But when I try to update these in the middle of the update() function, it doesn't work:

Code:
void loop()
{
	waveform.amplitude(1);
	delay(1);
	waveform.amplitude(0);
	delay(1);
}

The waveform still pulses on and off at the block size, 2.9 ms, not at 1 ms when I change the magnitude variable. Why is this?

(I don't actually need to pulse it on and off at 1 ms; my goal is just to learn how the variable modifications work.)
 
It is because the data is processed in blocks. When the audio processing is triggered it will halt the main program and go into an interrupt routine that handles all audio stuff. This will calculate the next 128 samples at once and put them in a DMA buffer. after that the interrupt routine ends and the control is returned to the main program. In the background the DMA will cause all prepared samples to be output.

I do not know Pauls intentions, but in my view, the settings are just settings and these should be set once, or updated on a slow basis, like eg the amplitude of a sine wave tracking a potentiometer.
To generate audio effects, you should build them using audio blocks, eg if you want an audio effect like your hypothetical 1 ms example, use AudioEffectMultiply with an offset square wave. As a general idea, for audio effects, you only want to see settings applied in the setup/loop functions and not actually audio processing.
 
When the audio processing is triggered it will halt the main program and go into an interrupt routine that handles all audio stuff.

But then why does it say you need to disable interrupts when modifying these variables?
 
Just a bad example, since phase accumulator is private, but cannot think of a better example right away
If you for example want to increment the phase by 90 degrees
Code:
sine.phase_accumulator += 4294967296 / 4;
[CODE]
Worst case this can happen, in pseudo-code
[CODE]
register r0 = sine.phase_accumulator;

(interrupt starts and enters AudioSynthWaveformSine::update)
ph = phase_accumulator.
(ph is updated in a loop over128 samples)
phase_accumulator = ph
(interrupt ends)

r0 += 4294967296 / 4
sine.phase_accumulator = r0;
So the value is read, the adjusted by the interrupt and the modification of the interrupt is ignored, thus resetting the accumulator.
You want the read/update/write to be 'atomic', so you disable interrupts for a short while. (The DMA buffer has enough overlap to not cause audible problems).
I know the Audio library itself does not seem very consistent in this manner, but this is most certainly caused by the fact that I also do not fully comprehend Pauls concept.
 
Last edited:
Yes, the data is processed in 128 sample blocks, which is 2.9 ms. If you change a setting for 1 ms, you have about a 30% chance your new setting will be used for the next 2.9 ms. But odds are more likely, approx 70%, that your change 1 ms later will overwrite the previous setting before the library makes use of it in the next 2.9 ms update.

The advise about disabling interrupts is for designing new audio processing objects. There are 2 different ways to disable the audio interrupt. Each should be used in different ways. That page is trying to give guidance, in the hope that new objects will follow the established convention, so the library as a whole will (in a perfect world) keep a very consistent design. But the truth is, even now, not everything in the library is perfectly consistent. It's a lot of code which developed over about one year, and during that time I made numerous changes and revised the overall design many times as I learned more and recognized more complexities as more of the features matured.

Still, the general guidance is good, I believe, that the code INSIDE the objects should use __disable_irq() and the Arduino sketch code OUTSIDE the library should use AudioNoInterrupts(). The rationale is to only block the urgent DMA interrupt for very short times, and the avoid using the NVIC-based AudioNoInterrupts() inside the library functions, to keep that mechanism available for the sketch code's usage. If any library functions use it and reenable the NVIC way, it would ruin the sketch's ability to use that way to make changes to several different objects and be certain to have all the changes apply together on the next 2.9 ms update.

However, it's not always necessary to use __disable_irq(). Changing shared variables without disabling interrupts is risky. I believe a general rule of thumb would be "if you have to ask..." Really, I'm reluctant to write a lot on this, because I'd prefer to encourage the safer, more conservative approach. But I suppose that could be "do as I say, not as I do!"

The are many places I didn't disable interrupts. Usually they are cases where a single shared variable is written exactly once, with a write that is known to compile to a single write-only bus cycle. Interrupt disable can also be avoided in some very special cases where multiple variables are written in a particular order, all with single bus cycle writes, where the design of the interrupt code will still give proper operation if the interrupt occurs between the writes. Designing such code is tricky, but there are a few very well established cases. You must ALWAYS do so with an awareness of the actual bus cycles the compiler will actually generate. Truly, "if you have to ask", you really ought to play it safe. But if you've read much of this library's code, I believe you can see it's been mostly designed with a lot of careful thought about the actual instructions the compiler will use. I can tell you most of the update() functions have been checked by running objdump to disassemble and verify the generated code.
 
Also,I should mention the envelope object. Maybe the code fragment above was just a rhetorical question? But if you really do wish to change the amplitude for 1 ms, that's exactly what the envelope object is meant to do. Or at least it's a subset of a more general delay-attack-hold-decay-sustain-release gradual gain change.

If you want to create changes on such a fine time scale, that's certainly the domain of crafting custom objects. Control from the Arduino sketch has a granularity of 2.9 ms. Indeed you were correct, that changes from the sketch code are meant to occur on a relatively slow time scale..... the time frame that humans can perceive musical tempo and distinct sounds.

One of many things I want to eventually do with the library is creating alternate versions of many objects, where your parameter changes apply gradually. Most likely the mixer will be the only one to get this relatively soon. Still, it's on my very long term wish list.
 
Status
Not open for further replies.
Back
Top