Hello everybody. I need help with NES sound cause i dont really get it. basically what i understand is that you can enable certain channels and you can set a timer which counts down and plays a sound until the timer is zero. It takes ((data + 1) << 1) CPU cycles for the timer to run out where data = value in the timer. What i have implemented is changing the reg values and getting all the data each frame. I dont know what the next step is.
Some things in specific that i dont understand are the divider period and clock. If there is already one timer for the wavelength, then why is there another timer. One more thing is how to calculate the frequency.
Also, how would you put that frequency into openal because i cant figure out how to put in raw data.
Could anyone help please?
Thanks in advance
Solve one problem at a time. First start with generating a simple square wave with your OS. Once you have that, generate one with a half-period specified in 1/1789773ths of a second. Once you have that, implement a basic NES APU that supports one square wave with frequency only, nothing else (that is, you only look at writes to $4002 and the low three bits of $4003). Once that plays the first channel of songs at the right pitch, you can start adding more stuff.
Thanks for telling me!
I never really thought of learning how to actually play a square wave, but i cant find out how to in openal because all of the examples(actually only one) deal with sine waves and even when i change the frequency of the wave, it stays the same.
If anyone could post some code or try to explain to me how to create a square wave in openal or even Core Audio for mac i would really appreciate it.
Thanks
Output a series of samples, where N samples are 0, N are some non-zero value, N are 0, etc. Surely you can adapt the sine wave code easily, unless these APIs work at a level higher than a sample stream. Sounds like you might lack an introduction to sampled sound, by you questions.
yea i dont really have an introduction to sampled sound
if you could give me a link to anything that would help that would be great cause i have search on google and cant find anything that helps. So examples work but they dont sound right.
PCM works by having "samples". Each "sample" represents the output level at a given time. As time moves forward, the output level changes. These changes form the sound wave, which produces an audible sound.
Imagine a grid. On the X axis is time, and the on the Y axis is the output level. Each sample would be 1 point/dot on the grid. You "connect the dots" to form the sound wave.
The "height" of the wave is the volume. Taller waves are louder.
The "width" of the wave is the tone. Wider waves are lower in pitch.
Audio has a "samplerate". Normally this is something like 44100 Hz, which means 44100 samples represents 1 second worth of audio.
In order to playback audio, you need to generate these "samples" and feed them to the audio driver (in your case, OpenAL).
A simple square wave would just alternate between an output level of 0 and an output level of X (the higher X is, the louder the sound).
For example, say you output the following:
0 0 0 0 8 8 8 8 0 0 0 0 8 8 8 8 ...
That would be a square wave. 4 samples of '0' and 4 samples of '8', then you just repeat that pattern.
If you change it from 4 samples and 4 samples to 5 samples and 5 samples:
0 0 0 0 0 8 8 8 8 8 0 0 0 0 0 8 ...
Then you just made the tone a little lower.
That should be enough to get you started into tinkering around. If you have questions feel free to ask.
And a sine wave looks more like this:
Code:
0, 12, 24, 36, 45, 53, 59, 63
64, 63, 59, 53, 45, 36, 24, 12
0,-12,-24,-36,-45,-53,-59,-63
-64,-63,-59,-53,-45,-36,-24,-12
0, 12, 24, 36, 45, 53, 59, 63
64, 63, 59, 53, 45, 36, 24, 12
...
Notice how it smoothly goes from -64 to 64 and slowly turns around without abrupt transitions between the numbers.
A tone has a "period", or number of samples until it repeats. For example, the sine wave shown above has a period of 32, so its frequency is 1/32 of the sample frequency that you provided to OpenAL when you told it to play sound.
Oh yeah,
there's a wikibook for that.
well i tried that and it does make more sense to me but i still dont think that the sound sounds right.
Is this(
http://www.megaupload.com/?d=VHUBX8P7) suppose to be what it sounds like?
I used the square wave which is like 0 0 0 0 8 8 8 8 and that thing repeats four times.
Let's say your sample rate is 44100Hz.
You want a square wave at middle C.
The A above middle C is 440Hz.
Frequency of a note formula:
F = F0*2^((1/12)*(n-n0))
where n0 is the reference note (the A above middle c), and f0 is the frequency of that note (440Hz).
The middle C below the A is 9 half-steps behind it.
So frequency of middle C is 261.625 Hz
Your sample rate is 44100Hz. You want the square wave to go up and down at the correct rate.
Do the unit conversion... 44100 samples/sec, and 261.625 cycles/sec. Want to get samples/cycle.
That's 44100 (samp/sec) / (261.625 (cyc/sec)) = 168.561... samples per cycle.
A square wave at 50% duty cycle has half of the samples up, and half down.
So that's 84.28 samples up, and 84.28 samples down for each full period of the square wave.
So if you want a one-second square wave at 44100Hz sampling rate, you fill 44100 samples of that.
Of course, you have fractional pieces of the wave remaining. If you just round to the nearest integer, you get a horrible sound. Normally you do linear interpolation to get a value between up and down for those places.
ok well i got the simple square wave working:
Code:
// Create square wave
s8* SquareWave(u32 silent, u32 playing, s8 data, s32 loop)
{
s8* tmp = (s8*)malloc(loop * (silent + playing));
for (int z = 0; z < loop * (silent + playing); z++)
{
int tempAddr = z % (silent + playing);
if (tempAddr < silent)
tmp[z] = 0;
else
tmp[z] = data;
}
return tmp;
}
// Update sounds for one frame - gets called 60 times / sec
void APU_DoLoop()
{
u32 buffer = 0;
alGenBuffers(1, &buffer);
s8* data = SquareWave(25, 25, 8, 882); // Makes 882 * (25 + 25) = 44100
alBufferData(buffer, AL_FORMAT_MONO8, data, 44100, 44100);
free(data);
data = NULL;
alSourcei(sources[0], AL_BUFFER, buffer);
alSourcePlay(sources[0]);
alDeleteBuffers(1, &buffer);
}
ps : s8 = signed char, u32 = unsigned int
so this code works pretty well and i can even change the values in the SqaureWave function and it sounds different. But what i dont know is how to calculate the frequency from it and also how to use a period in the timing of it.
edit: i think i figured it out.
the equation for this function is
F = Desired Frequency
R = Sample Rate
T = Time in seconds of sound
Code:
unsigned int total = S * T;
float other = total / (float)F;
unsigned int halfPeriod = other / 2;
unsigned int volume = 8;
unsigned char* data = SquareWave(halfPeriod, halfPeriod, volume, F);
alBufferData(buffer, AL_FORMAT_MONO8, data, (F * (halfPeriod * 2)) * T, S);
free(data);
data = NULL;
alSourcei(source, AL_BUFFER, buffer);
alSourcePlay(source);
Is there anything wrong with this?
Also how would i put the period of the NES Square 1 wave in there cause...
You should first get your synthesis function so that you can have it generate the next N samples of the wave into a caller-supplied buffer, then have your test program generate several buffers back-to-back and ensure they play seamlessly. Once you have that, get that integrated into your emulator where it's running graphics and generating this test square wave seamlessly, but not yet controlled by the NES. THEN you can start having it respond to $40xx writes.
well i think that i already have that because i load the whole wave at once, but when i play it, i have a pointer that points to the current location of where the last part of the sound got cut off like
Code:
alBufferData(buffer, AL_FORMAT_MONO8, &data[pos++], 1, ?); // Dont know how many samples to play or fast to play them
Is that not right?
Also it does play well with the graphics running.
You must be able to generate new sound data each frame, rather than generating it all in advance. Start by generating a little more than 1/60 second worth of samples every frame. This will slowly fill the host's sound buffer, eventually causing your alBufferData() to block a few milliseconds, unless this "al" driver is broken.
ok so if i generate the data each frame then it'll look like this
Code:
u32 period = ((square1.periodLow | ((square1.periodHigh & 0x7) << 8)) + 1);
float freq = CPU_SPEED / ((square1.periodLow | (square1.periodHigh << 8)) + 1);
if (period == 0)
return;
u32 size = (period * freq / 2) / 60;
s8* data = SquareWave(0, period / 2, 8, freq / 2);
alBufferData(buffer, AL_FORMAT_MONO8, data, size, size / 60.0);
i dont think thats right so if you could tell me what was wrong that would be helpful.
Unless SquareWave() has some static or member data, there's no way what it generates on a previous call will cleanly mesh with what's generated on the current call. Here's a simple square wave function which keeps state between calls:
Code:
static int delay;
static int phase;
void make_square( short out [], int count, int volume, int half_period )
{
for ( int i = 0; i < count; i++ )
{
out [i] = phase * volume;
delay--;
if ( delay <= 0 )
{
delay = half_period;
phase = 1 - phase;
}
}
}
To use, call this each frame, then pass the samples to the sound driver, rather than allocating a buffer each time:
Code:
#define BUF_SIZE (735 + 1)
void do_frame()
{
...
short buf [BUF_SIZE];
make_square( buf, BUF_SIZE, volume, half_period );
play_samples( buf, BUF_SIZE );
}
My general point is to get the host part working before the NES part.
alright i get that, but what should the sample rate be because i seem to be getting the same sound
44100 Hz is probably the most widely supported sample rate.
ok but how do u put in a specific frequency
for example: 1 kHz?
period = sample rate / desired frequency
period = sampling frequency / desired frequency
period = 44100 / 1024 (1 kHz)
period ~= 44
then this produces the right frequency but sounds weird
Code:
short data[736];
makeSqaure(data, 736, 255, 22);
play_samples(data, 736);
cause the half period of 44 is 22. but it has a scratchy sound and doesnt sound like the 1 kHz example in
http://en.wikipedia.org/wiki/Square_wav ... uare_waves (on the right side)
Take a look at the data array to see whether it's makeSquare that's generating something imperfect, or something wrong with play_samples(). It's such a short segment of sound, that it might be hard to tell how clean it is.
BTW, 1 kHz is 1000 Hz, not 1024 Hz (not that this is the cause of the problem here). Only when measuring bytes does k ever (sometimes) mean 1024; when measuring bits and anything else, it always means 1000.
i finally got actual sound working from the host working thanks to all of you
but how do u find the sample rate of the wave, the size of the wave, and when the wave should be played in the nes
right now i have
Code:
// Check Length Counter
if ((square1.control >> 4) & 0x1)
{
if (square1.length != 0) // Count Down
square1.length--;
}
u32 buffer = 0;
alGenBuffers(1, &buffer);
u16 period = (square1.periodLow | (square1.periodHigh << 8));
s16 data[?]; // Dont know size
SquareWave(data, ?, 255, period); // Dont know size
alBufferData(buffer, AL_FORMAT_MONO8, data, ?, ?); // Dont know size or sample rate
alSourcei(sources[0], AL_BUFFER, buffer);
alSourcePlay(sources[0]);
alDeleteBuffers(1, &buffer);
which gets called 60 times per second (every frame)
thanks
The NES sample rate is effectively nearly 1789773 Hz. You can either generate sound at this rate and resample it to your host's rate, or use a lower rate (perhaps the host's rate) and translate the period before generating at the host's rate. The number of samples needed in N seconds is simply sampling rate (in Hz) * N.
i have sound now and i changes like it should and almost sounds like the real thing but it is garbaged and too high or low at times.
Code:
if ((square1.length != 0 && ((square1.control >> 4) & 0x1)) ||
!((square1.control >> 4) & 0x1))
{
// Check Length Counter
if ((square1.control >> 4) & 0x1)
{
if (square1.length != 0) // Count Down
square1.length--;
}
u32 buffer = 0;
alGenBuffers(1, &buffer);
u16 period = (square1.periodLow | (square1.periodHigh << 8));
u32 sampleRate = 1789773;
u32 hostRate = 44100;
period = (u32)(((double)hostRate / sampleRate) * period);
if (period == 0)
return;
u32 size = (hostRate / 60.0) + 1;
s16 data[size];
SquareWave(data, size, 60, period);
alBufferData(buffer, AL_FORMAT_MONO8, data, size, hostRate);
alSourcei(sources[0], AL_BUFFER, buffer);
alSourcePlay(sources[0]);
alDeleteBuffers(1, &buffer);
}
is that right?
Your host needs samples no matter what. Your code seems to generate them only when the square wave is not silent.
I don't think you want to create and destroy buffers every time. Can't you just call alBufferData() every frame, and nothing more?
What are the values of sampleRate and hostRate?
I'll try producing samples all the time
I can't use alBufferData() every time cause it won't change unless I create a new buffer. I hink it might be locked in and cannot be changed after the first alBufferData()
also samplerate is the sample rate of the nes and host rate is the sample rate of the hosts computer. I use these to convert the period to the hosts sample rate