Audio on the TinyArcade

MagnusRunesson

  • Full Member
  • ***
    • Posts: 23
  • Eagerly awaiting TinyArcade!
    • View Profile
Hi all,

How is audio played on the tiny arcade? Will it be possible to have a separate thread that does the mixing and feed a mixed buffer to the audio some how?


MagnusRunesson

  • Full Member
  • ***
    • Posts: 23
  • Eagerly awaiting TinyArcade!
    • View Profile
Ok, I'm changing this question to, how is audio played on the TinyArcade?

Does the TinyArcade audio do mixing of sound effects at all or do I have to do that myself?

I've read up on threading on the Arduino and since it isn't very common I'm assuming that none of the libraries (i2c, wire etc) are thread safe.

Soooo, if the TinyArcade doesn't do mixing, and none of the libraries to communicate with the audio hardware are thread safe, is it at least possible to queue buffers of audio that are played one after another seamlessly. (Like how OpenAL work)


tastewar

  • Full Member
  • ***
    • Posts: 26
    • View Profile
I'm afraid I can't answer your question directly, but maybe some information is better than none. It is true that you won't find general multi-threading on micro-controller platforms, except perhaps as computer science demonstrations, or for very specific projects. A lot of times, when writing for a micro-controller, you are near the limits of RAM or some other resource anyway, so the overhead of a general-purpose multi-threading library isn't welcome. Instead, one way a similar capability can be fabricated is to simply make a function per "thread" and ensure that each "thread" function is cooperatively multi-tasking, which is to say, when you call into the function, it should have a quick check on whether it's time to do some piece of work, and exit quickly if not. That way, you can get to the next "thread" quickly. But it all has to be cooperative. Make sure you never call any "delay" function. If you need to "wait" you can take the current timestamp, add the "wait" time to it, and set a static variable with the wakeup time. Then, in your function, you can check to see if it's time to do that important thing you needed to do.

Another thing to note is that often times, we add "shields" to our projects that have processors on them that are much more capable than the main micro-controller itself! I've worked with the Rogue Robotics audio shield (traditional Arduino), and it has quite a lot of capabilities. So in those cases, you are handing off a complex task to a "co-processor" but you generally retain authority over what happens there. Think of all the intelligence that has to go into the kinds of Ethernet and WiFi shields where the entire TCP/IP stack is baked into the hardware. Those are a lot more capable than the Atmel on most Arduini!!


MagnusRunesson

  • Full Member
  • ***
    • Posts: 23
  • Eagerly awaiting TinyArcade!
    • View Profile
Instead, one way a similar capability can be fabricated is to simply make a function per "thread" and ensure that each "thread" function is cooperatively multi-tasking, which is to say, when you call into the function, it should have a quick check on whether it's time to do some piece of work, and exit quickly if not.

Thanks for the tip. I think I will go down this route.

My current plan is to have a ring buffer with audio samples that is filled up by my mixing function, running as a regular function call from loop(), and emptied by the T5 interrupt, similar to how the Audio_Handler is implemented in this file https://github.com/arduino-libraries/AudioZero/blob/master/src/AudioZero.cpp#L165-L179

If I'm just a bit careful with timing and when audio is playing (so I don't try to play audio when I know I don't have time to fill the buffer, such as during load times) it shouldn't be a problem.

The only thing I need to do before implementing this is to verify that the TinyArcade play their audio by doing an analogWrite from the CPU instead of shipping the audio sample over from CPU to another shield via i2c.
« Last Edit: January 18, 2016, 04:24:11 AM by MagnusRunesson »


tastewar

  • Full Member
  • ***
    • Posts: 26
    • View Profile

Ben Rose

  • Administrator
  • Hero Member
  • *****
    • Posts: 392
    • View Profile
Tastewar is exactly correct with the general response, excellent. Especially about the 'quick function check' part quoted by Magnus, which will match most of our example code.

In our case for TinyArcade audio, what you've found is correct- the basic setup in the AudioZero example is the way to go, we're using the hardware in the same way. So just set up an interrupt(for now- I need to figure out the DMA approach) to load the 10 bit DAC output from a buffer, and make sure the buffer is loaded as necessary. I have this working well enough to play raw audio and video at 30 FPS from an SD card with some processing to spare, but the code is ugly- I'll clean this up asap so you can take a look.


MagnusRunesson

  • Full Member
  • ***
    • Posts: 23
  • Eagerly awaiting TinyArcade!
    • View Profile
Excellent, thank you!

The AudioZero example I've looked at ( https://www.arduino.cc/en/Tutorial/SimpleAudioPlayerZero ) set up the timer to feed the analog pin a stereo sample at 44.1KHz, so the timer is called 88200 times per second, and every other sample sent to the analog pin is for the left channel and then for the right channel.

I can't fully understand how many instructions the Audio_Handler adds up to, but considering that it is calling analogWrite, and analogWrite in turn call pinMode, which in turn call pgm_read_byte via the digitalPinToBitMask macro, it seems like a lot of cycles will be spent in the Audio_Handler. (Which means a lot of it can probably be removed/customized for this specific case of analogWrite :) )

I assume the TinyArcade will not be stereo, so the timer can be set up to only feed 44100 samples if I want to play in 44.1KHz mode, right? I'll probably go even lower than that, considering I won't have any 44.1KHz samples playing in my games.

If the Audio_Handler and all of its subsequent function calls amounts to, say, 100 instructions, and it is running 88200 times per second that is 15% of the total frame time for the CPU.

Also, out of curiosity, what will you use DMA for? I assume DMA means direct memory access, as in transferring memory without the CPU having to bother. It seems like a lot of time is spent setting up the pin and writing to it, rather than spending time in memory transfers.
« Last Edit: January 20, 2016, 05:42:22 AM by MagnusRunesson »


Ben Rose

  • Administrator
  • Hero Member
  • *****
    • Posts: 392
    • View Profile
All good thoughts. Right, that function requires optimization- since we're doing something specific and care less about portability, the main functionality of it is simply:

DAC->DATA.reg =  audioBuffer[__SampleIndex++];

The pin doesn't actually need to be set up each time, so nothing more than filling a register and incrementing through the buffer. This is probably in the area of 50-100 instructions, running at (in my case for video playback) 30720 times per second- 3% to 6% of processor time based on this rough guess.

The DMA is(should be) able to do just the same thing, but setting it up for a particular use like this is not trivial, so I'm not sure when that will happen.


 

SMF spam blocked by CleanTalk