gbadev.org forum archive

This is a read-only mirror of the content originally found on forum.gbadev.org (now offline), salvaged from Wayback machine copies. A new forum can be found here.

DS development > keep track of audio samples

#170023 - Tommmie - Sun Aug 23, 2009 8:59 pm

hey everyone,

I'm coding an music player at the moment. I use maxmod now for streaming, but i want to code the streaming myself(cannot pause the music stream with maxmod and i want to do some things which are now not possible). but there's one little problem: I do not know how you keep track of the amount of samples that are played. maybe with a timer? but can timers on the nds measure things like this?:
for example the music has a samplerate of 22050hz. that would mean that 1 sample takes 1/22050 of a second to get played or 4,5351473922902494331065759637188e-5 seconds.

ehm. that number above is a bit tiny, i mean really tiny:) that isn't measurable I think. so say i have a buffer of 1kb(512 samples) how can I know when the buffer is empty and needs to be refilled?

thanks,
Tommmie

#170024 - DekuTree64 - Sun Aug 23, 2009 9:44 pm

Yep, timers are the way to go. Sound channels run on timers that tick at half the rate of CPU timers, so you can match them exactly by setting the CPU timer period to channelPeriod*2. Then cascade another timer off that to count how many samples have been played.
_________________
___________
The best optimization is to do nothing at all.
Therefore a fully optimized program doesn't exist.
-Deku

#170025 - Dwedit - Sun Aug 23, 2009 10:11 pm

Master clock: 16756991 Hz (approximately 2^24, but not quite)
You can't really do 22050 samples/second, the closest you can get is 22048.67 samples/second (a timer period of -760).

Two things to know about timers:
* Timers run off the same clock as the sound channels.
* Timers can be run in "Count-Up" mode, where it cascades to a previous timer.

When you set the period of a timer, you tell it how many cycles it will take before it ticks up. So for a 22048.67 Hz timer, you use a 16-bit value of -760, because 16756991/22048.67 = 760.

If you run that timer in Count-Up mode, the previous timer ticks up whenever the next timer expires. So you can use this to set Timer #1 to have a period of -760, and run it in count-up mode. Then timer #0 increments whenever a sample is consumed.
So, let's say you want the timer to go off every time 1024 samples are consumed.
You would set timer #0 to have a period of -1024, and timer #1 to have a period of your sound rate. Have timer #0 generate IRQs so you can call your code to fill the buffer.


Now some facts about how to do the buffering:
You want to use a ring buffer, one that loops back to the beginning when it finishes playing. For example, let's use a buffer size of 8K, using 16 bit samples. Every 1024 samples played, 2048 bytes are consumed. So this buffer has 4 slots. (You can get away with 2, but I'm just using 4 as the example here)
At the beginning, before you start any audio, first pre-fill the entire buffer with the first 4 blocks of audio. You load 8K into the buffer, then tell it to play sound, and start the timers.
When your timer IRQ fires, it's an indication that 2K of data has been consumed. You will want to replace the most recently consumed slot with new audio.

An illustration of how a 4-slot ring buffer might look:
at beginning,
>xxxx xxxx xxxx xxxx (buffer pre-filled with 4 slots of audio)
****>xxxx xxxx xxxx (first slot has been consumed, and an IRQ fires)
XXXX>xxxx xxxx xxxx (first slot has been replaced with new audio)
XXXX ****>xxxx xxxx (second slot has been consumed, IRQ fires)
XXXX XXXX>xxxx xxxx (second slot has been replaced with new audio)
XXXX XXXX ****>xxxx (third slot has been consumed, IRQ fires)
XXXX XXXX XXXX>xxxx (third slot has been replaced with new audio)
>XXXX XXXX XXXX **** (fourth slot has been consumed, IRQ fires)
>XXXX XXXX XXXX XXXX (fourth slot has been replaced with new audio)
****>XXXX XXXX XXXX (First slot has been consumed, IRQ fires)
YYYY>XXXX XXXX XXXX (First slot has been replaced with new audio)
etc...

You can also get by with only 2 slots. The ring buffer does not necessarily need to use fixed sized blocks, or be a multiple size of fixed sized blocks, it just makes the math much easier, nothing needs to be split and have some go to the end and some go to the beginning.
_________________
"We are merely sprites that dance at the beck and call of our button pressing overlord."

#170027 - eKid - Mon Aug 24, 2009 3:19 am

A couple errors.. :)

Quote:
* Timers run off the same clock as the sound channels.

Sound channels run at half-speed of the timers/master clock. Master clock is about 33513982 Hz (I just use 2^25).

Quote:
You would set timer #0 to have a period of -1024, and timer #1 to have a period of your sound rate. Have timer #0 generate IRQs so you can call your code to fill the buffer.


Timer #1 should have the -1024 period and the IRQ. Then timer #0 can be the sound rate, and timer1 can be in cascade mode.

#170028 - Tommmie - Mon Aug 24, 2009 6:54 am

ok it's morning here and i'm not totally awake, but thanks for all the replies(and ther were really fast:)). i'll have to learn some more about timers but that's no problem and then I can code the sound streamer.
really thanks for all the detailed answers.

edit:
okay I looked at the answers and from what I'm understanding I have to do this(be warned i've never worked so with timers before so it can be totally wrong:)) :

-setup two timers:

timer1 with a period of 1024(which will count the samples consumed and will say if the buffer needs to be refilled) and timer 0(a 22048.67 hz timer) with a period of 760(which counts the time of a sample) in count up mode.
so every time timer0 has reached 760(the time of 1 sample) timer1 will increment(this is because of the cascade mode isn't it?). and if timer1 has reached 1024 i've to set it to 0 and fill the buffer with new samples.

#170029 - Min - Mon Aug 24, 2009 10:18 am

Here is a quick example that shows how you can do it without using interrupts:
Code:
// Quick 16bit mono audio streaming example

#define BUFFER_SIZE 4096 // In halfwords
#define FREQUENCY   22050

u16 buffer[BUFFER_SIZE];
int fillStart;
int channelID;


void startStream( int volume, int pan )
{
    /*
    Insert code to fill the buffer here...
    */

    // Sync timer 0 with the sound channel timer
    TIMER0_DATA = -0x2000000 / FREQUENCY;
    TIMER1_DATA = -BUFFER_SIZE;

    // soundPlaySample() wants the buffer size in bytes, hence the '*2'
    channelID = soundPlaySample( buffer, SoundFormat_16Bit, BUFFER_SIZE*2, FREQUENCY, volume, pan, true, 0 );

    // Start timers
    TIMER0_CR = TIMER_ENABLE;
    TIMER1_CR = TIMER_ENABLE | TIMER_CASCADE;

    fillStart = 0;
}


// Call after swiWaitForVBlank()
void updateStream()
{
    int fillEnd = TIMER1_DATA - 65536 + BUFFER_SIZE;

    int fillLen = fillEnd - fillStart;

    if ( fillLen < 0 ) {
        fillLen = BUFFER_SIZE - fillStart;
        fillEnd = 0;
    }

    /*
    Insert code to fill fillLen number of samples starting at buffer[fillStart]...
    */

    fillStart = fillEnd;
}


gbatek on timers and gbatek on sound

I hope it helps. :)

#170030 - Tommmie - Mon Aug 24, 2009 1:51 pm

yes i thought it would be like this but i have a few questions:

-why do you say that 33513982hz is equivalent to 0x2000000(in decimal it's 33554432hz) ?

-why the 65536 in:
Code:
 int fillEnd = TIMER1_DATA - 65536 + BUFFER_SIZE;

where does it stand for?

for the rest I understand the code completely, thanks min for this example:)

#170031 - Min - Mon Aug 24, 2009 2:32 pm

Quote:
-why do you say that 33513982hz is equivalent to 0x2000000(in decimal it's 33554432hz) ?

soundPlaySample() will set the sound channel timer period to '-0x1000000 / freq'.

Quote:
-why the 65536 in:
Code:
 int fillEnd = TIMER1_DATA - 65536 + BUFFER_SIZE;

where does it stand for?

When we set TIMER1_DATA to -BUFFER_SIZE it's the same as setting it to '65536 - BUFFER_SIZE' since the hardware treats the value in the TIMER1_DATA register as unsigned. It will then count up from there until it overflows ( at 65536 ) and then it will reset to '65536 - BUFFER_SIZE'. We want fillEnd to be a value between 0 and BUFFER_SIZE instead of '65536 - BUFFER_SIZE' and 65536, so we convert it. I hope that made any sense.

Quote:
for the rest I understand the code completely, thanks min for this example:)

You're welcome. ^^

#170032 - Tommmie - Mon Aug 24, 2009 3:02 pm

Quote:


soundPlaySample() will set the sound channel timer period to '-0x1000000 / freq'.


so I could also set it to 0x30(for example) because soundPlaySample will fix it?

and this:
Quote:
When we set TIMER1_DATA to -BUFFER_SIZE it's the same as setting it to '65536 - BUFFER_SIZE' since the hardware treats the value in the TIMER1_DATA register as unsigned. It will then count up from there until it overflows ( at 65536 ) and then it will reset to '65536 - BUFFER_SIZE'. We want fillEnd to be a value between 0 and BUFFER_SIZE instead of '65536 - BUFFER_SIZE' and 65536, so we convert it. I hope that made any sense

i'm not good in signed and unsigned converting but say if you have a signed value with the value 0 and you would treat it as a unsigned value it should have a value of 65536?


thanks, Min i really appreciate your great help(also from Dekutree64, Dwedit and Ekid)

Tommmie


[/code]

#170037 - Min - Mon Aug 24, 2009 5:15 pm

Quote:
so I could also set it to 0x30(for example) because soundPlaySample will fix it?

No, soundPlaySample() sets the timer of the sound channel it assigns the buffer to ( see gbatek on sound channels. ) That timer is only used by the sound hardware, you have to setup one of the cpu timers to match it ( in my example that is timer 0. )

Quote:
i'm not good in signed and unsigned converting but say if you have a signed value with the value 0 and you would treat it as a unsigned value it should have a value of 65536?

Signed numbers use the most significant bit as the sign ( it is set for negative numbers. ) In the case of 16 bit numbers ( the timer data registers have 16 bit length ) numbers 0 to 16383 are positive weather they are treated as signed or unsigned. However, 16384 to 65535 become -16384 to -1 if they are treated as signed numbers and so, naturally -16384 to -1 will become 16384 to 65535 if they are treated as unsigned.

This might give a better picture:
Code:
16 bit value, bits 0-15 ( 15 being the most significant bit )

          bit 15 not set:  bit 15 set:
Signed:   0...16383        -16384...-1
Unsigned: 0...16383        16384...65535


65535 is the largest value you can store in 16 bits and 16383 the largest you can store in 15 bits ( for when one bit is used as the sign. )

#170038 - Tommmie - Mon Aug 24, 2009 5:44 pm

ok I think I got it: you did this:
Code:
TIMER0_DATA = -0x2000000 / FREQUENCY;
to get it match with the sound channel timer. you use -0x2000000 instead of -0x1000000 because the cpu timer is twice as fast as the sound channel timer.

the 65536 is now clear to me, normally you could only put 65535 in an u16 but the situation you gave me in the example was when an overflow occured and then it becomes 65536. so the example will only fill the buffer when it's completely empty(or isn't that right?)

can you say if this all right? and really really thanks that you're explaining it so well to (a stupid) me:)

Tommmie[/b]

#170043 - Min - Mon Aug 24, 2009 7:07 pm

Let's start with the value 0, it's binary representation would be 0000 0000 0000 0000. The most significant bit is not set so it doesn't matter if we treat it as signed or unsigned, the value will still be 0, right? Now what happens to the binary representation if we subtract 1 from 0? The answer is; it becomes 1111 1111 1111 1111, treat it as a signed value the value is -1, treat it as unsigned and the value becomes 65535 or you could also say 65536 - 1.

Following this logic, -BUFFER_SIZE would have the same binary representation as 65536-BUFFER_SIZE, right? ;)

#170045 - Tommmie - Mon Aug 24, 2009 7:26 pm

nvm

Last edited by Tommmie on Sun Apr 25, 2010 9:14 pm; edited 1 time in total

#170097 - Tommmie - Sun Aug 30, 2009 10:51 am

sorry about all this, but I think i got it:) I've read up some stuff at coranac(thanks cearn, just what i need signed an unsigned numbers). First i didn't know how negative numbers worked but -1 is the same as 65535 and -2 the same as 65534. so that's why setting it to 65536 - buffer size IS -4096.
thanks Min for all your help, but I was just the problem...... sorry for that.

but how about stereo sound? if i'm right this would only be for mono?

#170099 - Dwedit - Sun Aug 30, 2009 4:46 pm

For stereo sound, you use two buffers, and two hardware sound channels. Tell one hardware channel to output to the left channel, and the other to output to the right channel.
You don't need to use two sets of timers, the same timer that tells you to update one buffer is the same timer you would use to update the other.

Usually stereo WAV files are interleaved, so you get one sample for the left channel, then one sample for the right channel, etc. The NDS does not have any native support for interleaved stereo audio. You'll need to split it yourself into the two buffers.
_________________
"We are merely sprites that dance at the beck and call of our button pressing overlord."

#170100 - Tommmie - Sun Aug 30, 2009 4:52 pm

yes i knew that(about interleave), but do i have to call soundPlaySample 2 times then? and the example min gave me doesn't seem to work. i've put it on my site so you guys can take a look if you want: https://sites.google.com/site/tomdevsndshomebrew/
I think i set up the ogg decoder correctly because it was working with maxmod and I didn't changed much(only maxmod related things).


edit: okay, just did some iprintf debugging and i discovered that the program is prefilling it's buffer, but when it has to update it's stream it goes wrong. it does totally nothing. also it looks like the program can't output sound because it doesn't even play the prefilled buffer. so a bit chaotic....

#170107 - sverx - Mon Aug 31, 2009 10:25 am

Tommmie wrote:
yes i knew that(about interleave), but do i have to call soundPlaySample 2 times then?


Yes. Basically you simply read you stereo sample one sample (halfword or byte) a time and you load it in your 'left' or in your 'right' buffer. Then you play both with playsample, giving full left panning to the left buffer and full right panning to the right buffer.

If you need to stream from a stereo wav file it's a bit more complicated (you need what's called 'double buffering') but the idea is the same, because NDS hardware audio channels are MONO, with panning.

#170111 - Tommmie - Mon Aug 31, 2009 3:56 pm

Ok thanks, i know how it would in theory, but still the mono ogg streamer is not working and i really don't know why.

#170113 - sverx - Mon Aug 31, 2009 4:48 pm

Tommmie wrote:
Ok thanks, i know how it would in theory, but still the mono ogg streamer is not working and i really don't know why.


Starts but doesn't go on? Doesn't start at all? Plays garbage?

#170114 - Tommmie - Mon Aug 31, 2009 5:17 pm

actually this: it displays the specs about the file like samplerate and amount of channels but when it has to start it just do.... nothing. no garbage because there isn't sound. you can download the project from my site, in the post at the top of the page

#170120 - sverx - Tue Sep 01, 2009 8:41 am

if your decoding routines are working, you should at least hear something from the speakers... sure you simply didn't remember to turn them on?

#170124 - Tommmie - Tue Sep 01, 2009 1:24 pm

didn't forgot to call soundenable() so....

#170172 - Tommmie - Sat Sep 05, 2009 2:21 pm

for some reason it's working, i changed something which i'm searching for in the project i uploaded to my site.

edit: i just used another ogg file, and the idea came up that the other was wrong converted and yes it was: stereo. but would that have effect? you should just hear the interleaved buffer or not?