#125836 - M-.-n - Tue Apr 17, 2007 1:01 pm
Did anyone succeeded in implementing proper sound streaming without glitches ? I thought I had the method but it doesn't seem to be ok.
What I did is the following:
1- Hack the arm7 startSound to force looping playback
Code: |
SCHANNEL_CR(channel) = SCHANNEL_ENABLE | SOUND_REPEAT | SOUND_VOL(vol) | SOUND_PAN(pan) | (format==1?SOUND_8BIT:SOUND_16BIT);
|
2- In the arm9, create a ring buffer containing a certain amount of subbuffers of size RING_BUFFER_SIZE and setup an array for the sub buffer boundaries:
Code: |
unsigned short *ringBuffer=(unsigned short *)malloc(RING_SAMPLE_SIZE*2*RING_BUFFER_COUNT);
for (i=0;i<RING_BUFFER_COUNT;i++) {
gBoundaries[i]=ringBuffer+RING_SAMPLE_SIZE*i ;
} ;
|
3- Setup a timer that will be triggered regularly every RING_SAMPLE_SIZE. Since the DAC is running at 32768, I'm using this freq as base. It's the lower setting of DIV_1024 so for one interrupt every RING_SAMPLE_SIZE, I do:
Code: |
TIMER0_DATA =65536-RING_SAMPLE_SIZE;
TIMER0_CR = TIMER_ENABLE | TIMER_DIV_1024 | TIMER_IRQ_REQ ;
|
4- Trigger a sound on the whole buffer at 32768
Code: |
TransferSoundData blaster = {
ringBuffer, /* Sample address */
RING_SAMPLE_SIZE*RING_BUFFER_COUNT, /* Sample length */
32768 , /* Sample rate */
127, /* Volume */
64, /* panning */
0 /* format 16 bits */
} ;
playSound(&blaster);
|
From there on, every time my timer handler is called, I should fill RING_SAMPLE_SIZE samples.
Code: |
void timerHandler() {
unsigned short period=150 ;
unsigned short *ptr=gBoundaries[gCurrentBoundary] ;
int i ;
for (i=0;i<RING_SAMPLE_SIZE;i++) {
*ptr++=65535*gSample/period ;
gSample++ ;
if (gSample>=period){
gSample=0 ;
}
} ;
gCurrentBoundary=(gCurrentBoundary+1)%RING_BUFFER_COUNT ;
} ;
|
When I do this, even with a fair amount of sub buffers, I still get glitches that makes me think the timer and playback are not in sync. Does anyone knows if there's something obviously wrong with this ? or if it is achievable at all ?
I've put the whole code here:
http://rafb.net/p/VbRrvt14.html
Thanks for any help
Marc.
#125848 - silent_code - Tue Apr 17, 2007 3:38 pm
you best contact simon (who's making the quake port), he's got mp3 decoding (and streaming i guess) working afaik.
good luck!
#125849 - M-.-n - Tue Apr 17, 2007 3:45 pm
yes, someone from #dsdev was kind enough to send me some code.
Hopefully will not be to hard to slap in !
#125851 - 0xtob - Tue Apr 17, 2007 3:58 pm
I found the problem: The length of the sample is specified in bytes, not samples, i.e. only half of your ringbuffer gets played, causing a glitch when it loops. You have to change the sound struct command to:
Code: |
TransferSoundData blaster = {
ringBuffer, /* Sample address */
RING_SAMPLE_SIZE*RING_BUFFER_COUNT*2, /* Sample length */
32768 , /* Sample rate */
127, /* Volume */
64, /* panning */
0 /* format 16 bits */
} ; |
Also, you should give the ringbuffer a little head start by calling the timer handler once before starting the sound.
Code: |
timerHandler();
playSound(&blaster);
irqEnable(IRQ_TIMER0); |
Lastly, it would probably be a good idea to flush the cache after each time you wrote to the ring buffer, to make sure everything is written to RAM. See the DC_FlushRange command for this.
Here's the fixed code:
http://rafb.net/p/mKjMd923.html
_________________
http://blog.dev-scene.com/0xtob | http://nitrotracker.tobw.net | http://dsmi.tobw.net
#125855 - M-.-n - Tue Apr 17, 2007 4:27 pm
very smooth indeed mr. Tracker !
Any idea why I get the sound panned straight left ? I thought 64 would give a centered sound position.
Thanks for that, sound is the only shard I still have to get LittleGPTracker up and running I think.. can't wait to have all piece together to see what the NDS will be able to deliver !
<3
Marc.
#125856 - M-.-n - Tue Apr 17, 2007 4:52 pm
M-.-n wrote: |
Any idea why I get the sound panned straight left ? I thought 64 would give a centered sound position.
|
mm.; strange. I recompiled and it is correct now. I wonder if the buffer doesn't have to be 4byte aligned or something. I had similar issue on the GP2X.
Will dig a dug.
#125857 - 0xtob - Tue Apr 17, 2007 5:07 pm
No problem, looking forward to playing with the piggy on the DS :)
As far as I know, sound buffers don't need to be aligned. But remember that DS channels are mono. So, if you want stereo, you have to use two channels and set the panning to left and right respectively.
_________________
http://blog.dev-scene.com/0xtob | http://nitrotracker.tobw.net | http://dsmi.tobw.net
#125858 - M-.-n - Tue Apr 17, 2007 5:17 pm
yeah..that's what I'm trying right now. However doing two Playsound in a row seems to have both channels to end up with the same panning.
I'll try cutting some more in the arm7 part to be sure I'm initializing things syncroneoulsy. *scratches head* ;)
#125861 - 0xtob - Tue Apr 17, 2007 5:59 pm
Oh, this is actually one of the problems with the libnds sound functions:
playSound writes the sound struct to a place in IPC RAM, from where it is read by the ARM7 in the vblank interrupt, i.e. only 60 times per second. So, if you call playSound two times directly after another, the first struct gets overwritten without being read by the ARM7. So, only the second sound gets played.
Strangely, in the IPC transfer struct there is an array of 16 TransferSoundData structs that could be used to transfer multiple sound commands at the same time, but libnds only writes to the first element.
Workarounds would be to use a custom transfer struct (there's one in the DS Sampling Keyboard source), or to use the FIFO.
_________________
http://blog.dev-scene.com/0xtob | http://nitrotracker.tobw.net | http://dsmi.tobw.net
#125864 - M-.-n - Tue Apr 17, 2007 6:14 pm
Ha. that's it then.
Well, I guess anyway I should dive in the arm7 a little more since I might need some trickery there. I'm going to work on updating my makefile to take into account the double core thing (I'm using only one proc on the GP2X) and get in the custom command.
thanks for the help !
#125866 - silent_code - Tue Apr 17, 2007 6:48 pm
<randomly speaking> it's actually a nice (although initially unusual) thing that big n forces you to use both processors by strictly dividing tasks among them. that way less processing power gets lost.
just think like ppl only used about 50% of the saturn's cpus' (that's an 's' like in multiple "cpus") power most of the time - the hw was really superior to the ps', but there were like only poo{p, r}ly performing (relative to the ps) games for it (no sir, i'm actually not even a bit of a sega fan boy, sir. quite the contrary, i may say.). game (console) programmers weren't used to the parallel nature of the system and abandoned it's possibilities. now add some rather bad doc and tools and there you have it: a money burning machine. but who cares anyway?
ps: "double core" <grin> that's what you get from watching too much [fill in pc cpu maker's name] adds ;^) it's a multi processor system with two very distinct main processors. ;^p what happened to multi-cpu-pcs anyway? they used to be around a while ago - i remember them to be "big" around the 400 mhz era. i can only find (expensive) """workstations""" now... ?
#125870 - M-.-n - Tue Apr 17, 2007 7:54 pm
hehe; yes, I'm not good at vocabulary... I love the adds that now state 'solo core' coz it's just ridiculous
I agree about the fact that it's good it forces you to use both CPU's and it's certainly going to have a serious impact on what I can take off the 2X after gaining this experience !
I'm not exactly sure at what is going to be the optimal uses of the arm7 in my context. for the moment it is just a way to trigger states rather than really do processing. I'll keep it mostly that way in a first instance, especially since I need to keep cross-compatibility between GP32,GP2x, NDS, Windows and Mac (and probably linuss soon). So I'll first go for 'crude' implementation and go back to the design board in a second phase.
#125951 - M-.-n - Wed Apr 18, 2007 1:21 pm
Just for the records of his thread. I've done a stereo implementation of the above code. There was a one big issue worth mentioning: although driven by the same clock (I guess :)), the timers on both CPU are not in sync. Since the sound sampling frequency is generated on the Arm7, the timer for buffers NEED to run there too. In my previous sample, the play and write head would slightly go out of sync because of the timer discrepencies.
So basically, allocate the ring buffer on the arm9:
Code: |
// Creates the ring buffer
unsigned short *ringBufferL=(unsigned short *)malloc(RING_SAMPLE_SIZE*2*RING_BUFFER_COUNT+4);
unsigned short *ringBufferR=(unsigned short *)malloc(RING_SAMPLE_SIZE*2*RING_BUFFER_COUNT+4);
// fills with zero to start with
memset(ringBufferL,0,RING_SAMPLE_SIZE*2*RING_BUFFER_COUNT) ;
memset(ringBufferR,0,RING_SAMPLE_SIZE*2*RING_BUFFER_COUNT) ;
CommandStartEngine(ringBufferL,ringBufferR,RING_SAMPLE_SIZE*RING_BUFFER_COUNT*2) ;
|
And run the timer & start sound on the Arm7:
Code: |
TIMER0_DATA =65536-RING_SAMPLE_SIZE;
TIMER0_CR = TIMER_ENABLE | TIMER_DIV_1024 | TIMER_IRQ_REQ ;
// Prebuffer
timerHandler();
irqSet(IRQ_TIMER0, timerHandler);
irqEnable(IRQ_TIMER0);
|
So that means now, I need to find the way to trigger an IRQ on the arm9 from the arm7 each time a new buffer cycle is needed.
FIFO business I guess....
#125953 - 0xtob - Wed Apr 18, 2007 1:36 pm
Good to know. Someone should make a streaming sound template, since this is a very common problem.
Another option than using the FIFO would be to extend the Command struct for 2-way communication. I've done this in NitroTracker to notify the ARM9 when the next line is played or the pattern changes.
_________________
http://blog.dev-scene.com/0xtob | http://nitrotracker.tobw.net | http://dsmi.tobw.net
#125954 - M-.-n - Wed Apr 18, 2007 1:48 pm
Yeah.. once I got something running properly I'll dump the code somewhere. Something that will implement SDL-style callback. Maybe I could put it up your wiki.
There is a code sample here that works, with usage of FIFO too but I want to make sure I get all the details by doing my own implementation and his method is based on the VBlank which is probably fine but makes me cringe a little.
It would be better for me to get an interrupt in the arm9 because in my architecture the sound buffers are used to rule all timing... my sort of Vblank but audiowise, so I need accuracy there.
More on later....
#126037 - M-.-n - Thu Apr 19, 2007 2:32 pm
w00t. Piggy did it's first sounds today. It's far from perfect as you can hear here:
http://www.10pm.org/nostromo/lgpt/piggy-ds-barf.mp3
basically seems to work at half speed + some crap in there. At least it means it's getting closer !
Plus my R4 has arrived so I'll be able to work out of no$gb and do the real thing.
I'd say in about a week I'll have a first cut :)
#129876 - TheChuckster - Mon May 28, 2007 4:05 pm
The pastebin pages are down, could somebody please rehost?
#129992 - Noda - Tue May 29, 2007 10:41 pm
I'm also greatly interested by your stereo sound streaming engine :-)
#130165 - M-.-n - Thu May 31, 2007 11:57 am
All I can do is to provide the code I have for LittleGPTracker, so there will be a little bit of things to chop off as I don't have a clean code sample (It got nuked). Please try to look at:
http://www.10pm.org/nostromo/ndsstream.zip
and see if you can get what you want out of if.
Roughly we got the following:
Code: |
NDSSound::Init() - initializes the ring buffer and a few others needed by the app
The most important is the ringBufferL & ringBufferR. This is the one that will be sent to the arm7 to read. The ringBuffer has a size equal to a number of time a certain base buffer size. The 'base' buffer size is the size of data to fill each time the callback will be called.
That way, we can start the playback at zero and fill the buffer a little further to avoid clash between reading & writing.
In theory, we could deal with only 2 of them, but in my experience a little bigger is better.
|
Code: |
NDSSound::Close() - free whatever was allocated in init
|
Code: |
NSSSound::Start() - sends the ARM7 a command through the FIFO that playback should be started. This will kick the arm7 to read in loop the buffer. The playback position is computed in the arm7 using a timer. When the buffer size has been played, the arm7 will send a FIFO command back to the arm9 that will trigger an IRQ and call SoundBufferCallback
|
Code: |
NDSSound::Stop() - tells the ARM7 to stop reading the buffer and kill the timer notification
|
Sorry that I can't get a clearer example, if you guys absolutely need a complete sample, let me know and I'll try to slam something.
Cheers,
Marc.
#130171 - Noda - Thu May 31, 2007 1:12 pm
Thanks, I'll take a look at it!