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.

Audio > Music Engine Advice

#109368 - thegamefreak0134 - Fri Nov 17, 2006 6:09 pm

(mods, not sure if this should be here or in DS development, please move if it is in the wrong place. Thanks!)

I have been playing with the sound functions in libnds to produce sound on the DS in the hopes of using those functions to create a very simple music engine of sorts. I need it to be a custom engine so I can use it in my music creation program, which is going to be very nice I hope, but will be discussed at a later time. Anywho, I have a bunch of questions concerning the process.

(1) I was playing mainly with the frequencies of the sounds played, and by tinkering around with some code on my TI-83+SE, managed to produce a small list of frequencies that play 13 notes chromatically from 11025 to 22050 when set with the sound functions. Is this a generally accepted way to produce sounds on the DS, and if so is there a lookup table somewhere that has the values for an entire note set that spans more than just an octave? If not, what are the better methods of producing different notes?

(2) This issue is probably both the most important, and the simplest answer. How do you compile a sound sample into its respective sound.o and sound.h files as used in the examples? Also, what file format is needed to perform such a conversion? (I assume .wav, but I have received worse cases for assuming stuff in the past so...)

(3) Timing is really throwing me in logic. With the shortest possible note being a 16th note, my engine will be able to produce a BPM of 60 by playing 1 16th note every 15 frames. I cannot produce the more accepted 120 BPM because this would require 7.5 frames per 16th note, which is not possible using conventional logic. I know that one must use timers for such a thing, but I want to have code running that updates the notes on the screen (highlights them and scrolls the score, if you will) as the song plays, so this code needs to be called every time the sound functions are called. Thus, it seems I must delve into the nasty horrid realm of interrupts and ASM on the DS, which will give me more grief than I bargained for. Any help on this issue would be greatly appreciated.

(4) This is just an assumption, based on experimentation. When you play a sound with the sound functions (in libnds) does it pplay the sound on the first free channel, and fail if there are no free channels? (Thus limiting you logically to 16 sounds playing at once) This makes the most sense, I\'m just confirming it.

I know these are some particularly icky issues, but I am new at this and really want to get my music player going. I have been brainstorming this project for a couple of years now, and the DS sound hardware apparently makes it more possible. Serious thanks and possibly some cheese to anyone who can offer help or advice.

-thegamefreak0134
_________________
What if the hokey-pokey really is what it's all about?

[url=http:/www.darknovagames.com/index.php?action=recruit&clanid=1]Support Zeta on DarkNova![/url]

#109374 - DekuTree64 - Fri Nov 17, 2006 8:05 pm

thegamefreak0134 wrote:
(1) I was playing mainly with the frequencies of the sounds played...

You really only need a single octave of frequency table, since you can just multiply or divide by 2 to move up or down an octave.
If you want to calculate your own table, notes are a factor of 2^(1/12) (that is, two to the one-twelfth power) apart. If you want a pre-made one, download impulse tracker, and use the one from the ittech.txt file included with it (this is the one I normally use).

Quote:
(2) How do you compile a sound sample into its respective sound.o and sound.h files as used in the examples?

For a music editor (or any app, if you ask me), you'll want to load samples off a storage device rather than having them linked directly into the binary. The .wav format is fairly simple, basically organized into 'chunks' that have a 4 byte ID tag, followed by a 4 byte size, followed by that much data. Tracker formats are usually even easier to get at the sample data.

Quote:
(3) Timing is really throwing me in logic.

This is probably the most difficult part of any music coding. I'd recommend using a timer interrupt for the song updating, and have the screen display whatever the state happens to be when its update rolls around. Ears notice timing differences much sooner than eyes do.

Quote:
(4) When you play a sound with the sound functions (in libnds) does it play the sound on the first free channel, and fail if there are no free channels?

Yep.


I'm working on a music editor myself, but it's always fun to see what kind of interfaces other people come up with too. Drop me a line sometime on MSN messenger if you want to chat, my email matches my name here, with a hotmail.com at the end.
_________________
___________
The best optimization is to do nothing at all.
Therefore a fully optimized program doesn't exist.
-Deku

#109378 - thegamefreak0134 - Fri Nov 17, 2006 9:22 pm

Wow. Thanks DekuTree, I almost didnt expect such a full response right away.

OK, here are my thoughts. (Sorry about any resluting slashes, my proxy does this with the different quote characters and I have a hard time avoiding it in habitual typing.)

Quote:
You really only need a single octave of frequency table, since you can just multiply or divide by 2 to move up or down an octave.
If you want to calculate your own table, notes are a factor of 2^(1/12) (that is, two to the one-twelfth power) apart. If you want a pre-made one, download impulse tracker, and use the one from the ittech.txt file included with it (this is the one I normally use).


Cool. I was going to calculate the entire table myself, but I never thought of just shifting the bits. This will really come in handy, plus it means I basically already have my table.

Quote:
For a music editor (or any app, if you ask me), you\'ll want to load samples off a storage device rather than having them linked directly into the binary. The .wav format is fairly simple, basically organized into \'chunks\' that have a 4 byte ID tag, followed by a 4 byte size, followed by that much data. Tracker formats are usually even easier to get at the sample data.


I love this idea, I really do. Problem is, I am limited to an old-fashioned GBA FlashCart, so I have to deal with one binary to begin with. Of course for DS programming, this is split into two binaries, which makes it even more confusing, leading to my next question below.

Quote:
This is probably the most difficult part of any music coding. I\'d recommend using a timer interrupt for the song updating, and have the screen display whatever the state happens to be when its update rolls around. Ears notice timing differences much sooner than eyes do.


OK, I can see that then. Have my sound updater play the sounds based on timers, and consequently update the sprites and such after that, then copy to the screen every vblank like normal, taking care to note that the two shouldnt interrupt each other. (A skipped frame here and there wont be noticed by many.) Now I just have to figure out how to do timers and interrupts. Wont that be a blast... but not here.

Quote:
I\'m working on a music editor myself, but it\'s always fun to see what kind of interfaces other people come up with too. Drop me a line sometime on MSN messenger if you want to chat, my email matches my name here, with a hotmail.com at the end.


I would love to.

(5) I do of course have another concern. I am told that the current default configuration of libnds and the templates there for makefiles will create two binaries, one that loads to the main RAM and one that loads into the 64kb of RAM for the ARM7. If this is correct, what do I do if my binary turns out to be bigger than 4 MB? I mean, my binary is bound to be large with 128+ samples at least from the GM library that I plan to include. Can you see the magnitude of my visions here?

As for general info (because you asked) I plan to make this a non-tracker based editor. I like to work with staffs and notes rather than tracker style format, because it is so much easier to see music and rhythm when you can see real notes. I will be modeling it sort of like a cross between noteworthy composer and finale, but it will of course be my work and such.

Thank you sooooo much for your info. Also, I do need that basic sound compiling and linking with the binary code thing, because I need to be able to at least get some piano samples in there to hear real notes. (Right now I am stuck with the blaster noise from the sound sample, which does not do a lot musically when the pitch is changed...)

-thegamefreak
_________________
What if the hokey-pokey really is what it's all about?

[url=http:/www.darknovagames.com/index.php?action=recruit&clanid=1]Support Zeta on DarkNova![/url]

#109406 - tepples - Sat Nov 18, 2006 1:47 am

thegamefreak0134 wrote:
(mods, not sure if this should be here or in DS development, please move if it is in the wrong place. Thanks!)

I'll answer the questions that apply to any music engine.

Quote:
(1) I was playing mainly with the frequencies of the sounds played, and by tinkering around with some code on my TI-83+SE, managed to produce a small list of frequencies that play 13 notes chromatically from 11025 to 22050 when set with the sound functions. Is this a generally accepted way to produce sounds on the DS, and if so is there a lookup table somewhere that has the values for an entire note set that spans more than just an octave? If not, what are the better methods of producing different notes?

Most western music nowadays uses a tuning method called equal temperament. You can base your at middle C = 2093/8 = 261.625 Hz or A = 440 Hz.

Quote:
(2) This issue is probably both the most important, and the simplest answer. How do you compile a sound sample into its respective sound.o and sound.h files as used in the examples? Also, what file format is needed to perform such a conversion? (I assume .wav, but I have received worse cases for assuming stuff in the past so...)

bin2s. But you will need to convert 8-bit .wav files from unsigned samples to signed by inverting their most significant bit.

Quote:
Timing is really throwing me in logic. With the shortest possible note being a 16th note, my engine will be able to produce a BPM of 60 by playing 1 16th note every 15 frames. I cannot produce the more accepted 120 BPM because this would require 7.5 frames per 16th note, which is not possible using conventional logic.

Yes it is: Use 8, 7, 8, 7, 8, 7 frames. As long as the length of each 16th note doesn't vary much, and it averages out to 7.5, listeners won't give a sh** that it's rounded off.
_________________
-- Where is he?
-- Who?
-- You know, the human.
-- I think he moved to Tilwick.

#109527 - DekuTree64 - Sun Nov 19, 2006 12:52 am

thegamefreak0134 wrote:
Problem is, I am limited to an old-fashioned GBA FlashCart, so I have to deal with one binary to begin with.

Not necessarily. You can append data to the .ds.gba so it will go onto the cart, but not be loaded into RAM at first. Tepples' GBFS could be a convenient way of doing this. Then you can load individual samples from the GBA cart as needed.

The data could be pre-converted files (raw sound data that the DS hardware can play directly), or some standard format like .wav, and do any conversions at load time.

With pre-converted data, you could play directly from the GBA cart and not have any RAM problems at all, but I'd recommend loading to RAM either way because then it would be basically effortless to switch to libfat later on.

Anyway, to get up and running quick, I think you can use sox to make some raw sound data files, then append them using GBFS, load, and play.

Quote:
Now I just have to figure out how to do timers and interrupts. Wont that be a blast...

It's really not that bad. You shouldn't have to do any assembly level stuff to have your song update called by a timer interrupt instead of in the main loop. Just read some tutorials (DS or GBA, it's basically the same either way), and post another thread if you get stuck.

Quote:
(5) I am told that the current default configuration of libnds and the templates there for makefiles will create two binaries, one that loads to the main RAM and one that loads into the 64kb of RAM for the ARM7. If this is correct, what do I do if my binary turns out to be bigger than 4 MB? I mean, my binary is bound to be large with 128+ samples at least from the GM library that I plan to include.

Sort of answered above, but to reiterate, you don't have to have all those samples loaded int RAM at once. In fact, it's best to keep as much data seperate from the code as possible, so you can optimize for space or speed as needed by loading and unloading things.

Quote:
As for general info (because you asked) I plan to make this a non-tracker based editor. I like to work with staffs and notes rather than tracker style format, because it is so much easier to see music and rhythm when you can see real notes.

Nice. I'm actually better with staffs myself, but I'm such a fan of tracked music that I want to get better at it. And what better way to improve than to write a tracker.
_________________
___________
The best optimization is to do nothing at all.
Therefore a fully optimized program doesn't exist.
-Deku

#109669 - thegamefreak0134 - Mon Nov 20, 2006 3:27 am

Did I ever mention how awesome y'all are? OK, Last bit now, I'm just assuming for the sake of happy that .wav files should be in the PCM (seems to be standard) format, and that only the "data" chunk should be inverted before converting using bin2s. Or can I do the bit twiddling after the conversion? (That would be much easier)

Of course, one plays diretly with stuff on the cart (on the DS) by declaring it const, right? Then the compiler loads only your working code and non-const variables into RAM at the start of the .ds.gba file, correct? (I'm confirming this, I'm still sorta new at the switch from GBA to DS)

If this is all correct, I should be good to go for a while. I'll go ahead and use the 7,8,7,8 trick for now, but I eventually want to support more flexible tempos and notes that are shorter that a 16th note. I know it can be "faked" with faster tempos, but then it gets harder to read and I don't exactly want that. Now on to the note drawing routine! Ha Ha! (*cough*)

Thanks again! I'll post if I get anything weird. Oh, and is there an emulator that can do sound OK and not choke on simple graphics? dualis appears to handle sound to some degree, but crashes on most ROM loads (mainly commersial stuff I use to test emulator compatability) so I need an alternative. Thanks again! Gosh darn, now I'm late coming off my break...

-thegamefreak
_________________
What if the hokey-pokey really is what it's all about?

[url=http:/www.darknovagames.com/index.php?action=recruit&clanid=1]Support Zeta on DarkNova![/url]

#109674 - tepples - Mon Nov 20, 2006 4:12 am

thegamefreak0134 wrote:
Of course, one plays diretly with stuff on the cart (on the DS) by declaring it const, right? Then the compiler loads only your working code and non-const variables into RAM at the start of the .ds.gba file, correct? (I'm confirming this, I'm still sorta new at the switch from GBA to DS)

That was true on the GBA, but on the DS, everything must be loaded into RAM. Have you ever made a "multiboot" program on the GBA?
_________________
-- Where is he?
-- Who?
-- You know, the human.
-- I think he moved to Tilwick.

#109686 - thegamefreak0134 - Mon Nov 20, 2006 8:15 am

Oh poo. I suppose the logical thing to do would be to set aside an area of RAM and copy sample data to it as needed by the music engine. Well that just stinks. Is there a way to easily do this?

Or wait. Is it just the ARM9 that can copy the data from the cart to the RAM? If so, couldn't I just use the FIFO register to transfer the sample data... oh wait, how am I going to transfer 16 samples at once if that many is needed...

Grr. You had to go and complicate things. Curse you Nintendo!!!

Nuther quick question. If I could boot directly from the DS slot, could I read directly from the DS cart? (As in, do commercial games do this?)

No, I have never made a multiboot program, because I have never seen the need to limit my data to 256k, and have also never needed to test my program using a multiboot method. (I used visualboy advance until I got my flashcart, and now all hardware testing is done with that. Haven't needed to transfer a multiboot program for a game yet either, because I haven't designed multiplayer games yet. So no.)

I'm assuming the logical easiest thing to do would be to append it to the binary and run from there? I know I will need to expand a lot to include more samples, but for now I just want a simple, if limiting, engine up and running...

-thegamefreak
_________________
What if the hokey-pokey really is what it's all about?

[url=http:/www.darknovagames.com/index.php?action=recruit&clanid=1]Support Zeta on DarkNova![/url]

#109786 - tepples - Tue Nov 21, 2006 5:53 am

thegamefreak0134 wrote:
Oh poo. I suppose the logical thing to do would be to set aside an area of RAM and copy sample data to it as needed by the music engine. Well that just stinks. Is there a way to easily do this?

How do .mod/.xm players work?

Quote:
Or wait. Is it just the ARM9 that can copy the data from the cart to the RAM? If so, couldn't I just use the FIFO register to transfer the sample data... oh wait, how am I going to transfer 16 samples at once if that many is needed...

There is a register that allows the DS to give responsibility for the GBA slot to the ARM7 or to the ARM9.

Quote:
Nuther quick question. If I could boot directly from the DS slot, could I read directly from the DS cart? (As in, do commercial games do this?)

Yes, commercial games load their samples from the DS Game Card into RAM.

Quote:
I'm assuming the logical easiest thing to do would be to append it to the binary and run from there?

Anything appended to a ds.gba binary will be placed in GBA ROM space and not automagically copied into RAM. You can use bin2s to assemble and link sample data into your binary.
_________________
-- Where is he?
-- Who?
-- You know, the human.
-- I think he moved to Tilwick.

#110246 - thegamefreak0134 - Mon Nov 27, 2006 6:49 am

OK, I am having some issues with the interrupt process and timer process in general. All I have is a (older) copy of TONC. Wonderful resource (thanks Cearn!), but it only has the info for the GBA interrupt and timer registers, as it should. Are these registers the same and if so should I use them? (Obviously they're there so the GBA games can use them, but can I use them in DS mode is what I'm asking here.)

Also, what mode (ARM or THUMB) does the devkit for DS compile to, and how do I tell it to compile a section in ARM if I need to? I use the default makefile for the combined processors at the moment because I'm to lazy to write one myself, but if I need to go off and change it (sigh) now's the time.

I was thinging about having the timer period be calculated something like this:

Code:

timerfreq = (Clocks_in_a_frame * 60) / BPM / 16;


With clocks in a frame being rather obvious, and 16 being the shortest duration of note, in this case (which will change later if this works out allright) a sixteenth note. I reailze there is the possibility of getting innacurate timings because of the stupid fixed point thing, but I don't think the difference will be that noticeable considering that there are a heck of a lot of clocks in the DS frame.

Anywho, I need to know how to calculate the proper overflow stuff for the timers based on a variable frequency, or I need to know how to generate a lookup table and a handler to set those frequencies for me. I would naturally set one timer to increment every clock starting at (overflow-clocks calculated), but I think this would limit me to too high a frequency to be usefull, since Cearn's demo used two timers to create a 1Hz timer, which turns out to 60 BPM, which is fairly common. (gasp!) Thus, I need a simple way to multiply this ammount. Would I do something like this:

Code:

u32 timer = some random crap;
u32 timer2;
while (timer > 0xFFFF) {
timer = timer - 0xFFFF;
timer2 = timer2 + 1;
}


Then I would tie my interrupt to timer 2, set it to overflow and reset properly after every timer2 itterations, and set timer 1 to 0xFFFF - timer, correct? (confused? me too...)

I'm slightly lost here... I really just need the register handling stuff (and any nice dekKitPro functions that can do some of this for me) and I should be able to play with the rest on my own. Thanks again for any help y'all!

-gamefreak
_________________
What if the hokey-pokey really is what it's all about?

[url=http:/www.darknovagames.com/index.php?action=recruit&clanid=1]Support Zeta on DarkNova![/url]