Audio Out with double buffering

I’m using the Audio Out example on a Panda with success - playing up to 22kHz 8 bit. It sounds just fine for what I’m doing at the moment so I don’t need the MP3 shield.

I modified the sample to play large files (more than the 20k buffer) but there is a pause in between playing back the blocks where it reads the next block from the SD card.

Is there a way to “double buffer”?

If I run a background thread to read ahead while the current block is playing it should get rid of the pause between the blocks.

Does AnalogOut.Set() block background .Net threads while it is playing or will my own thread still get some processor time? I’d like to know because it will be quite a bit of work to implement the double buffer only to find out it doesn’t work.

Another question… Can Analog Out play 10bit (from a modified 16 bit source)?

You can do all using RLP see support page please

Hi Gus,

Which support page are you referring to?

If I use all RLP I’ll have to do the SD card reading with RLP too I assume. Is there an example of that anywhere?

How would you suggest I tackle this problem?

If you figure it out post the code! I did the same thing when audio out was first introduced but didn’t have time to figure out the delay issue b/c I was finishing Pyxis 2 and starting Spiral at the time.

Hi skewworks,

It must be possible because the rate of reading the SD card is far greater than the speed of playback. I’m just not sure if RLP will block the .Net buffer thread. Only one way to find out I guess - and that is to write the code :slight_smile:

Just a side-note I just discovered… If you have a locally declared PersistentStorage variable (as in the Audio Out example), the FileStream class will fail after the GC comes around collecting. It collects the PersistentStorage object, even though it is still referenced (the SD is still mounted). The solution was to declare PersistentStorage as a static global variable in the class. I don’t know if this is a bug or by design, but watch out for this!

Thanks Gus.

I went through the RLP examples and see what you mean. One can create a time-slice with a call-back from the RLP back to the managed world to do things while RLP is idle.

I can imagine RLP might be idle in between outputting the analog value from the array, but not for very long. At 8 KHz, there would only be 125 microseconds - hardly enough time to complete a callback in MF. Am I missing something? Can RLP give back time to regular MF threads in slices to complete a longer job (like reading the next chunck of data from SD card)?

I’ll probably try and do something where MF passes a 10k buffer to RLP, then RLP sets up a timer interrupt to output the value to analog “in the background”. It signals MF to get ready for the next block - and MF places the next block in memory and it runs as a double-buffer or a circular buffer between the two. As long as RLP doesn’t read faster than MF can fill the buffer it should be fine. The trick is not to take more than 125 microseconds to switch from one block to the next otherwise there will be a “blip” in the stream. That should be doable with RLP speed.

What would be really cool as a feature is virtual memory - where the framework uses a swapfile on SD card as memory that it can share with RLP functions. The MF programmer will simply see it as a large memory block and the GHI magic underneath takes care of moving the data around.

Alternatively, a non-blocking library function that can stream from block storage (or network stream) at a predictable rate would be awesome. Something like AnalogOut.Stream(fromstream, samplerate);
and
OutputCompare.Stream(fromstream, samplerate)

The magic here is that the heavy lifting and time-critical stuff is done “on the metal” and not in MF. Setting up the stream is an ideal job for MF - so it should be a good partnership.

Then don’t swap buffers.

Allocate a single 20K buffer and setup a timer interrupt that plays that buffer in a loop.

When your readpointer gets over the BufferSize/2 level, notify managed code that it can fill the first half of the 20K buffer.

When your readpointer overflows, notify managed code that it can fill the second half of the 20K buffer.

The hardest part will probably be notifying the managed code within less then 125 µs. I think this will not work from the interrupt.

Maybe you’ll have to setup a managed thread that polls until it needs to fill the next buffer.

Of course all this can be made much better by adding $3 audio chip

Buying a chip wouldn’t be any fun now would it :slight_smile: Probaby the best idea in the end - so I’ll look into it - thanks Gus.

Wouter: Whether I use one buffer split in two on two seperate ones shouldn’t really matter - it’s just a change in pointer. I like your idea of signalling to the managed code to do things. There is actually plenty of time to do this - not just 125 uS.

The interrupt routine just needs to do a few simple things during the 125 uS (in between sample times):

  • 0 Bytes available? If not, shut down timer and return.

  • Increment the buffer pointer
  • Output the value to AnalogOut
  • Halfway through buffer? Set BLOCK1FREE flag. Managed side now has access to fill it.
  • Completely through buffer? Set BLOCK2FREE flag. Set pointer to start of buffer (BLOCK 1).

In the meantime the managed code polls the BLOCK1FREE and BLOCK2FREE flags and fills the regions appropriately. It “flip-flops” between loading data into the two regions.

I like the flag idea more than the callback because it doesn’t take many cycles away from the RLP routine. There is plenty of time (milliseconds) for the managed code to load the next block while RLP is streaming out the data on the timer.

In theory it should be possible to play 20 kHz audio with a 2x10k buffer if the RLP can do the stuff above in 20 uS or less and the managed side can read the SD and fill 10k in less than 200 ms per block.

I’m now more interested in learning how to create cooperative, timing sensitive Managed and RLP code than playing audio - but that would also be a bonus :slight_smile:

Alright, I already got a RLP timer interrupt firing every 22kHz and writing a toggling DAC value to generate a square wave. First problem: the interrupt seems to get interrupted by something else… so the square wave is not stable, that might give problems when outputting audio.

@ GHI: any idea why the timer3 interrupt is not stable? which higher level interrupt could be the cause?

Note that I already tried raising the interrupt priority to highest, without any luck.


VICVectPriority27 = 0; // Raise TIMER3 IRQ priority

Wow. Not a Fez master for nothing :slight_smile: I’d love to see that code when it’s done.

I’m going through the LPC23XX user manual and so far I don’t see anything. All I can think is that GHI might be using Timer 3 for something. There may be other code attached to that interrupt in some way. Did you try one of the other timers?

Timer3 is off by default. But I was thinking Sdram controller, tft controller, dma controller…

At the end I think I don’t miss any interrupt at 22khz, so I should try it with audio data. Maybe I’m just worried about nothing :slight_smile:

I think the timer that GHI uses as scheduling timer has higher priority than yours. I used GHI task scheduling to toogle a pin and the jitter I measured was nothing (10 ns).

Did you use TIMER3 AND GHIs tasks at the same time ?

Guys, I got it working!

I just played a song of more then 3 minutes 16bit PCM at 22050Hz, streamed from SD card, I got not a single buffer underrun. Awesome :slight_smile:

I’ll see if I can beautify the code and create a nice C# class to stream wave files. Be patient…

@ Pablo: I even lowered all other IRQ priorities with 1, so I’m sure the timer3 is absolutly on top. But the yitter is still there (and it’s not small). Even when I deploy a release version.

Wouter you’re the man!

I’m pretty sure I’m going to grab you for some work on Game Slate in the future :wink: Which, by the way, is getting ready for KickStarter and found an outside investor…looks like it’s actually going to happen :smiley:

Wow man!!

Unbelievable! Well done Wouter!

I’m curious about this GHI scheduler Pablo is talking about. How does that work and can it be used to clock out the stream without the jitter?

I think this should be a very nice class when you release it. I’m looking forward to learn from it.

Rudie

Nasty question: Is the DMA being used? Can’t it be set up to copy data from ram to the DAC on every timer3 interrupt?

I have never used DMA, but isn’t that it’s main job in life, to copy data around unattended?