Gameboy sound wave channel

Discussion of programming and development for the original Game Boy and Game Boy Color.
Cloudy
Posts: 9
Joined: Wed Apr 22, 2009 12:47 am

Gameboy sound wave channel

Post by Cloudy »

Hi Guys,

First of all it is great to see a gameboy dev forum here as I havent been able to find a decent one on the net. I have a fully working gameboy emulator and now just need to add sound support. I have previously posted on the nesdevemu forum asking for help with square waves because the sound chip on the gameboy and NES are very similar. I am now attempting to implement the wave channel and have come stuck. My main problem with this channel is knowing how to interpret the 32 samples in memory and how to play them. This channel has a frequency like the square wave channels so Im assuming that when enough clock cycles have passed and the frequency has decreased to 0 then you move onto the next sample in memory and reload the frequency and start counting down again (this is similar to how I handle square waves by counting down the frequency and upon 0 I increase the duty cycle and change the polarity if needed).

Does this sound correct so far?

My problem now is how to play the current sample. So if the sample Im playing has the value of 0xA what does this actually mean? You see with square waves I simply multiply the volume by the polarity (either +1 or -1) and total them all up before adding to the playback buffer, but im unsure how to do this with a wave. Also am I correct in assuming the value returned from a wave is +1 and 0 and not +1 and -1 like square waves? If so then how do I mix a wave and a squre wave before adding it to the playback buffer?

Thanks for your time and any help.
User avatar
blargg
Posts: 3715
Joined: Mon Sep 27, 2004 8:33 am
Location: Central Texas, USA
Contact:

Post by blargg »

Have you read the wave section of the Game Boy Sound Hardware wiki page yet?
Cloudy
Posts: 9
Joined: Wed Apr 22, 2009 12:47 am

Post by Cloudy »

Thanks for the reply Blarrg.

I hadnt read that link before I posted I had only read Lord Nightmares GBSOUND.txt and also NESSOUND.txt to see if I could clarify anything in there.

I have briefly read that wiki page and it seems fantastic, exactly what I needed. Although I will probably post in this thread again when I need something else clarifying I believe that document answers my immediate questions.

Thanks again.
tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Post by tepples »

I've worked with the GBA sound hardware; that's close enough to that of the Game Boy. You might want to try asking on forum.gbadev.org; there are probably still some people left who haven't moved on completely to the DS and remember what "01 23 45 67 89 AB CD EF FE DC BA 98 76 54 32 10" in wave RAM means. (It's what you write to get the same waveform as the NES triangle wave channel.)
Cloudy
Posts: 9
Joined: Wed Apr 22, 2009 12:47 am

Post by Cloudy »

Thanks for the reply.

I've had a stab at the wave pattern channel after reading that doc. It isnt sounding right so im unsure if it is a bug in my code or a misunderstanding with how the wave channel works.

This is the algorithm I use for calculating the wave.

Code: Select all


int CalculateWave ( int numCycles )
{
// dac power, master on off and length counter (if length counter is enabled)
if (channelIsNotEnabled)
 return 0;

bool timeToUpdate = frequency <= numCycles ;
frequency -= numCycles ;

if (timeToUpdate)
{
// reset the frequency
int value = (set5 << 8) | set4 ;
value &= 0x7FF ;
frequency = (2048 - value) * 2 ;

// move on to next wave pos
wavePos++ ;
wavePos = wavePos % 32 ;
}

int volumeControl = set3 >> 5;
volumeControl &= 3;

int shift = 0 ;
switch(volumeControl)
{
case 0: shift = 4 ; break ;
case 1: shift = 0 ; break ;
case 2: shift = 1 ; break ;
case 3: shift = 2 ; break ;
}

return GetDACOutput (wave[wavePos] >> shift)
}

This is just pretty much pseudo code of what im doing. Basically it decreases the frequency by the num of clock cycles the last opcode took. If the frequency becomes less than 0 then it reloads the frequency and moves onto the next wave pos.

The input value given to the DAC is the current sample shifted by the volume control.

This is what the GetDACOutput does

Code: Select all

int GetDACOutput(int volume)
{
	static double analog[] = { -1, -0.8667, -0.7334, -0.6, -0.4668, -0.3335, -0.2, -0.067, 0.0664, 0.2, 0.333, 0.4668,0.6,0.7334,0.8667,1  } ;
	return static_cast<int>(8000 * analog[volume] ) ;
}	
The reason why im doing 8000 multiply the analog value is because SDL is using AUDIO_S16SYS which gives a range of approx -32000 to 32000 and as there are 4 channels 8000 will fit in range.

Also just to confirm something with the other square wave channels, if the current polarity is 0 then it always returns a value of 0, correct? However if the value is 1 then it passes its current volume into GetDACOutput which will return -1 to 1?

Thanks for any help
Last edited by Cloudy on Thu Apr 30, 2009 11:40 pm, edited 2 times in total.
Celius
Posts: 2158
Joined: Sun Jun 05, 2005 2:04 pm
Location: Minneapolis, Minnesota, United States
Contact:

Post by Celius »

tepples wrote:You might want to try asking on forum.gbadev.org; there are probably still some people left who haven't moved on completely to the DS and remember what "01 23 45 67 89 AB CD EF FE DC BA 98 76 54 32 10" in wave RAM means. (It's what you write to get the same waveform as the NES triangle wave channel.)
That makes sense. The waveform looks like:

Code: Select all

F |               **
E |              *  *
D |             *    *
C |            *      *
B |           *        *
A |          *          *
9 |         *            *
8 |        *              *
7 |       *                *
6 |      *                  *
5 |     *                    *
4 |    *                      *
3 |   *                        *
2 |  *                          *
1 | *                            *
0 |*                              *
  *--------------------------------
And each 4 bits represents an entry. That's definitely a triangle wave. But it seems like it would be slightly off, as it repeats the wave, playing volume 0 twice.

I think this is how the GB's wave engine works, too. I know it uses 4-bit samples with 16 bytes of RAM to do it with.
tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Post by tepples »

It plays the entry with level 0 twice, and it also plays the entry with level 15 twice. But it's still triangular enough that it sounds like a triangle wave. And each of the 32 steps is flat, which gives the characteristic aliasing at 31f and 33f that defines part of the "NES sound".
User avatar
blargg
Posts: 3715
Joined: Mon Sep 27, 2004 8:33 am
Location: Central Texas, USA
Contact:

Post by blargg »

Cloudy wrote:This is just pretty much pseudo code of what im doing.
Well, I see problems with it, but I'm not sure it's what your real code does. Do you want to get your pseudo-code working, or the real code?
User avatar
Bregalad
Posts: 8056
Joined: Fri Nov 12, 2004 2:49 pm
Location: Divonne-les-bains, France

Post by Bregalad »

And each 4 bits represents an entry. That's definitely a triangle wave. But it seems like it would be slightly off, as it repeats the wave, playing volume 0 twice.
I'm pretty sure the same happens on the NES : The output to the DAC is the low 4 bits each XNORED with the 5th bit of a 5 bit counter. This effectively resulting the 0, 1, ...., 14, 15, 15, 14, ...., 1, 0, 0, 1, .... sequence.

Yet, I guess it doesn't sound the exact same as triangle wave on the NES. Very probably this is due to analog filtering past the DAC, that most emulators/music players emulate (I guess).[/list]
Useless, lumbering half-wits don't scare us.
User avatar
Dwedit
Posts: 4924
Joined: Fri Nov 19, 2004 7:35 pm
Contact:

Post by Dwedit »

Is it the step aliasing as it increments in 16 steps that causes the "NES Sound", or is it the doubling of the numbers at the top and bottom of the wave?

Edit:
I just generated two wave files, one using 16 step aliasing, and one using 17 step aliasing (so that the second half goes 16...1 instead of 15...0). Couldn't tell them apart very well listening to them side by side.
Here come the fortune cookies! Here come the fortune cookies! They're wearing paper hats!
Cloudy
Posts: 9
Joined: Wed Apr 22, 2009 12:47 am

Post by Cloudy »

Thanks for the reply Blarrg.

I have edited my above code to show exactly what I am doing in my real code. The only difference is that my code belongs inside a wave channel class and the CalculateWave is a member of that class a long with the variables "frequency", "wavePos", "wave" and the sets "set3/4/5". I believe that link to the sound development you posted indexes the channel registers (sets) from 0 where I index them from 1 (this is because this is how lord nightmare indexed them and I read his document first).

The boolean "channelNotEnabled" is set to true if the DAC power is off, or the master control is off or the length counter is 0 (including if the length counter is enabled).

So if you can spot any problems im sure they are problems in my actual code.

My second question is when square waves output a polarity of 1 then i feed this to the DAC along with its volume in the same manner as I do with the wave channel to get its analog value. However if the square wave outputs a polarity of 0 then do i just return 0 instead of going though the DAC?

Thanks for any help.

Edit:

I should probably clarify that my actual wave memory is 32 bytes not 16. This is because I find it easier to treat the 2 samples encoded into one byte as two different indexes in the wave memory.
User avatar
blargg
Posts: 3715
Joined: Mon Sep 27, 2004 8:33 am
Location: Central Texas, USA
Contact:

Post by blargg »

How are you spreading the 16 wave RAM bytes into your 32-entry wave table? What happens when numCycles is great enough that more than one wave step should occur? Seems you need a while loop. Otherwise, the code looks correct.
Cloudy
Posts: 9
Joined: Wed Apr 22, 2009 12:47 am

Post by Cloudy »

Thanks for the reply. I havent been able to reply over the weekend as I have no internet access. I managed to get some progress over the weekend and now you can clearly hear the correct melody being played in all games which is very encouraging. I feel it is still a long way off being finished though because it sounds awful. Interestingly if I disable all but one of the 4 channels and play it then im convinced I can hear the same channel being played on the real gameboy. So it is like individually they are playing correct but when combined although you can hear each channel is still playing correctly it sounds awful. This is probably because I still have the mixer to emulate. I havent done this yet as I dont know how to use SDL to play a left and right sound buffer. I'll have to look into that.

I wish there was an emulator available that allows you to turn on and off the different channels so you can hear what they individually sound like. I guess that'd really help me see where I am going wrong lol.

Thanks again.
tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Post by tepples »

Cloudy wrote:I wish there was an emulator available that allows you to turn on and off the different channels so you can hear what they individually sound like.
VisualBoyAdvance.
Cloudy
Posts: 9
Joined: Wed Apr 22, 2009 12:47 am

Post by Cloudy »

Thanks for that. My sound channels seems fine but raspy in comparison. Im almost there I can feel it :-)
Post Reply