I'm working on porting some code to the NDS which plays sounds into an already-mixed buffer, but I'm getting sound/buffer glitches in playback in both various emulators and the actual hardware. I'm actually fairly knowledgable about sound and playback, and I've successfully written code to do streaming via DMA on the GBA using either the VBlank interrupt or a timer -- but I've been hacking at this for almost two days now and can't quite figure out what I'm doing wrong.
Since the NDS's VBlank timings are different than the GBA, I'm not using the VBlank method but using a timer for now (I would like to get this to work using the VBlank as well, but that can wait until later).
Basically I have two 2K buffers -- one for the left channel, and one for the right -- but for simplicity's sake I'm just trying to get mono working for now. I call soundPlaySample() and have it loop over the entire 2K block. I then set up a timer at Hz=(sampleRate/(bufSize >> 1)), where in this case if I'm playing at 16Khz, playback would interrupt every 1024 samples, or half the buffer size. In the timer interrupt, I simply update the 1K half portion that is not being played, and flip the index pointer. And yes, I made sure I wrote the data into uncached memory.
It's really quite simple, and I don't understand why I'm getting glitches in my playback.
I've even went so far as to generate a sine wave and sample the actual output from a real NDS, looking at the result, but aside from noticing that there are 8 glitches per second, it hasn't helped me figure out what's causing the problem.
I have also tried doubling and halving the timer rate just to see if I might have a bug somewhere else, but neither made it sound correct. Actually it made it sound worse, so I don't think my timer code is broken. I also have already tested the timers by setting up a 1Hz timer to count seconds, and it is working properly.
I'm completely at a loss at this point, and would be willing to post code/youtube video/etc. if desired. Any help or ideas at this point would be greatly appreciated.
Streaming sound on NDS
Re: Streaming sound on NDS
did you look at the maxmod source?
Re: Streaming sound on NDS
Following your suggestion, I started poking through the maxmod source for clues, and I was finally able to find the source of the problem: while the ARM9 CPU and it's timers run at 33554432Hz, the sound clock on the ARM7 side runs at a slightly different 33513982Hz (from the ARM9's viewpoint).
I was originally timing my buffer switch only to the ARM9 CPU, so after a short while my write cursor would eventually catch up to the play cursor, and then slowly overwrite the data that was being played by the sound hardware.
Thanks for the tip, elhobbs!
I was originally timing my buffer switch only to the ARM9 CPU, so after a short while my write cursor would eventually catch up to the play cursor, and then slowly overwrite the data that was being played by the sound hardware.
Thanks for the tip, elhobbs!
Re: Streaming sound on NDS
Or maybe not... I was going by the values specified in GBATek, and it looks like there isn't any difference in Hz at all between the CPUs. I could still be wrong though.
I was under the impression that if I wanted proper timing, I needed to setup sound and a timer both on the ARM7 side. I've written a custom ARM7, and changed my code so that a timer on the ARM7 side interrupts twice for the playback buffer, and sends a FIFO command back to the ARM9. The ARM9 then mixes the not-playing buffer portion for that FIFO channel.
But I'm still getting glitches in playback. Actually, even worse now than before.
I'm guessing that two interrupts per buffer cycle now combined with the FIFO overhead is just too imprecise. Looking at how some emulators are doing it, they actually set up two timers -- one to count up at the frequency rate samples are played, and the other to count up on overflow, so that the second counter actually contains the number of samples played counter. During VBlank (on the ARM7 side) the number of samples to add to the buffer is sent to the ARM9.
This method should work without problems, but I really don't like using two timers. Isn't there a better method of streaming sound than this?
I was under the impression that if I wanted proper timing, I needed to setup sound and a timer both on the ARM7 side. I've written a custom ARM7, and changed my code so that a timer on the ARM7 side interrupts twice for the playback buffer, and sends a FIFO command back to the ARM9. The ARM9 then mixes the not-playing buffer portion for that FIFO channel.
But I'm still getting glitches in playback. Actually, even worse now than before.
I'm guessing that two interrupts per buffer cycle now combined with the FIFO overhead is just too imprecise. Looking at how some emulators are doing it, they actually set up two timers -- one to count up at the frequency rate samples are played, and the other to count up on overflow, so that the second counter actually contains the number of samples played counter. During VBlank (on the ARM7 side) the number of samples to add to the buffer is sent to the ARM9.
This method should work without problems, but I really don't like using two timers. Isn't there a better method of streaming sound than this?
Re: Streaming sound on NDS
I am going to guess no. Do you need the timers for something else? I want to say that you should stay away from doing anything that takes too much time in a timer interrupt. Maybe use it determine that something needs to be done than set a flag.
-
- Site Admin
- Posts: 2003
- Joined: Tue Aug 09, 2005 3:21 am
- Location: UK
- Contact:
Re: Streaming sound on NDS
Both CPUs run on the same bus clock, as do the timers - it's not possible for these to get out of sync, they use the same clock source.
You can get away with a single timer provided you use a sample rate which generates an even number of samples per interrupt period, i.e. a power of 2 due to the need for a multiple of 4 bytes. On top of that you'll need to ensure that the buffer half can be filled before the audio hardware starts fetching from it - you might need to increase the buffer size.
You can get away with a single timer provided you use a sample rate which generates an even number of samples per interrupt period, i.e. a power of 2 due to the need for a multiple of 4 bytes. On top of that you'll need to ensure that the buffer half can be filled before the audio hardware starts fetching from it - you might need to increase the buffer size.
Re: Streaming sound on NDS
WinterMute: Thanks, I'm glad to hear that from an informed source. I was really starting to doubt Martin's numbers in the GBATek doc about the frequency ratings of the CPUs and the sound clock.
I actually just fixed the source of the problem, and it turned out to be something _completely_ different from the mixing anyway. I thought I had overrided a virtual function in a base class, but the function definition wasn't exactly the same -- I wasn't passing a parameter by reference when I needed to be, so the derived class's virtual wasn't called.
Long story short: the base class started off a completely different timer on its own to synchronize the mixing (at a rate of about 50Hz), which was delaying the FIFO commands from the ARM7 enough in order to cause the glitches in playback.
Once I fixed that issue and had the derived class be called properly, the glitches were completely gone. At 32768Hz playback, I'm able to use a single timer at 8Hz with a 4KB double buffer and it's working perfectly.
Thanks for the help, guys!
I actually just fixed the source of the problem, and it turned out to be something _completely_ different from the mixing anyway. I thought I had overrided a virtual function in a base class, but the function definition wasn't exactly the same -- I wasn't passing a parameter by reference when I needed to be, so the derived class's virtual wasn't called.
Long story short: the base class started off a completely different timer on its own to synchronize the mixing (at a rate of about 50Hz), which was delaying the FIFO commands from the ARM7 enough in order to cause the glitches in playback.
Once I fixed that issue and had the derived class be called properly, the glitches were completely gone. At 32768Hz playback, I'm able to use a single timer at 8Hz with a 4KB double buffer and it's working perfectly.
Thanks for the help, guys!
Who is online
Users browsing this forum: Ahrefs [Bot] and 1 guest