## Bandlimited waveforms algorithm?

Discuss emulation of the Nintendo Entertainment System and Famicom.

Moderator: Moderators

Zepper
Formerly Fx3
Posts: 3192
Joined: Fri Nov 12, 2004 4:59 pm
Location: Brazil
Contact:

### Bandlimited waveforms algorithm?

I was on hold... but i decided to ask anyways...
Yes, I know the Blargg's page, but not much, only graphics and basic information. FCEUltra and VirtuaNES have their own way to generate an outstanding pAPU output.

Is there any algorithm or a more detailed information available for bandlimited synthesis? Thanks in advance.

Guest
A slow, but generally accurate, way would be to generate one sample per NES CPU cycle(effective rate would be about 1.78977272Mhz with NTSC), and resample it to 44.1KHz or whatever the output rate is.

If I were to reimplement the resampling code in FCE Ultra from scratch, I'd look into using an MMX or SSE based FIR filtering routine to resample the stream by an integral divisor(32 would be good) of the original rate, and then use libsamplerate http://www.mega-nerd.com/SRC/api.html to resample it to the output rate.

So, you would have:

1.789772 MHz MMX/SSE FIR filter-> 55,930.4 Hz libsamplerate-> 44,100Hz

Guest
Oh, I forgot one thing. I'd use gmeteor to create the filter coefficients. Be warned that it can run very slowly.

http://gmeteor.sourceforge.net/

Zepper
Formerly Fx3
Posts: 3192
Joined: Fri Nov 12, 2004 4:59 pm
Location: Brazil
Contact:
Thank you for the information... Quite interesting.
Last edited by Zepper on Sat Feb 27, 2010 6:57 pm, edited 1 time in total.

Disch
Posts: 1849
Joined: Wed Nov 10, 2004 6:47 pm
The easiest way I've found to downsample is simple:

Accumulate the output of each channel every cycle and when you output a sample, divide that value by the number of cycles passed. It's not dreadfully slow, and produces a mighty-fine quality sound (although there are better methods)

For an example... every cycle the channel is clocked (this can be optimized by mutliplying Channel_Output by a value... I'll show sample code later):

OutputBuffer += Channel_Output;

When you output a sample:

SampleOut = OutputBuffer / CyclesPastSinceLastSample;
fTicksUntilNextSample += fTicksPerSample;

fTicksPerSample should be floating point and set to CPU_CLOCK_RATE / SAMPLE_RATE. On an NTSC system outputting at 44KHz, this will be roughly 40.58 (don't round too much). This means CyclesPastSinceLastSample will alternate between 40 and 41 as samples are generated.

This is the method I use in NotSo Fatso and I'd say it works quite well.

In case it's still unclear... here's some demo code to further clarify:

void DoSquareTicks(int ticks)
{
int mn;

while(ticks)
{
mn = min(nFreqCount, ticks);

ticks -= mn;
nFreqCount -= mn;

if(nDutyCount < nDutyCycle)
nSquareOutputBuffer += nVolume * mn; /*output volume if duty cycle is in positive section*/

if(nFreqCount <= 0)
{
nFreqCount = nFreqTimer.W + 1;
nDutyCount = (nDutyCount + 1) & 0x0F;
}
}
}

void RunAPU(int tick)
{
int mn;

while(tick)
{
mn = min( tick, ceil(fTicksUntilNextSample) );

fTicksUntilNextSample -= mn;
tick -= mn;

DoSquareTicks(mn);

nCyclesPassed += mn;

if(fTicksUntilNextSample <= 0)
{
fTicksUntilNextSample += fTicksPerSample;
OutputSample( nSquareOutputBuffer / nCyclesPassed );
nCyclesPassed = 0;
}
}
}

That code wont' work... but it'll give the general idea.

The method Blargg discusses in his doc (the one where you put transitions in a buffer, then run through the buffer later to generate samples) is likely faster and higher quality. It's kind of hard to understand though. I have yet to actually finish a player which impliments it.

baisoku
Posts: 121
Joined: Thu Nov 11, 2004 5:30 am
Location: San Francisco, CA
Contact:
Disch wrote:The easiest way I've found to downsample is simple:

Accumulate the output of each channel every cycle and when you output a sample, divide that value by the number of cycles passed. It's not dreadfully slow, and produces a mighty-fine quality sound (although there are better methods)
[snip]
while the naive linear interpolation method you describe does produce generally acceptable results (it's what i used in my shitty nsf plugin way back in the day), it's important to note that it is exactly why shay presented his bandlimited technique - lerp creates aliasing, especially in the upper range of the nes frequencies.
...patience...

laughy
Posts: 41
Joined: Wed Nov 17, 2004 12:34 pm
Contact:

### :(

I did it the "naive" way

Hey Disch, if you ever actually implement Blargg's buffering thing make sure you tell us if it was worth it

Quietust
Posts: 1551
Joined: Sun Sep 19, 2004 10:59 pm
Contact:
My emulator also does the same 'naive' downsampling, mainly because a proper FIR filter would be trying to consume CPU time that simply isn't available.
Quietust, QMT Productions
P.S. If you don't get this note, let me know and I'll write you another.

Zepper
Formerly Fx3
Posts: 3192
Joined: Fri Nov 12, 2004 4:59 pm
Location: Brazil
Contact:
WOOHOO!!!
What a nice day... hehehe
It worked nicely. Thanks for the help!

baisoku
Posts: 121
Joined: Thu Nov 11, 2004 5:30 am
Location: San Francisco, CA
Contact:
Quietust wrote:My emulator also does the same 'naive' downsampling, mainly because a proper FIR filter would be trying to consume CPU time that simply isn't available.
as does mine. and i assume that's what most are doing. but that's why on all of them, you can hear the aliasing effects in the kid icarus intro and the solstice music.
...patience...

Disch
Posts: 1849
Joined: Wed Nov 10, 2004 6:47 pm
baisoku wrote:but that's why on all of them, you can hear the aliasing effects in the kid icarus intro and the solstice music. :)
The reasoning the aliasing appears for Kid Icarus in NotSo is because I emulate the non-linear interdependency square output as layed out in blargg's doc (and he tells me that he noticed the same aliasing appearing from the real thing.. though I haven't personally tested this). When I have both channels outputting independent, linear output... I can't notice any audible aliasing.

Perhaps I could record a quick wav or something for a demo and see if you guys notice any?

But yes... I've gotten Blargg's methods working and the results were definatly worth it. Less CPU intensive since you don't have to run each channel seperately for each sample (you can run them all at once for a big chunk the size of your output buffer). A crisper sound, and allows for more nifty sound tricks and easier filter application.

It's definatly worth it. It's just somewhat weird to understand. I didn't understand it fully myself until I exchanged several e-mails with Blargg.

laughy
Posts: 41
Joined: Wed Nov 17, 2004 12:34 pm
Contact:

### :)

Would you mind posting those emails somewhere Disch for those who may run into the same problems you did?

Zepper
Formerly Fx3
Posts: 3192
Joined: Fri Nov 12, 2004 4:59 pm
Location: Brazil
Contact:

### Re: :)

laughy wrote:Would you mind posting those emails somewhere Disch for those who may run into the same problems you did?
Yay better, assemble a document. I'm sure it'll be much welcome.

laughy
Posts: 41
Joined: Wed Nov 17, 2004 12:34 pm
Contact:

### Re: :)

Fx3 wrote:
laughy wrote:Would you mind posting those emails somewhere Disch for those who may run into the same problems you did?
Yay better, assemble a document. I'm sure it'll be much welcome.
Hey you did a good job on RockNes - however my facepic is still better.

TimW
Posts: 6
Joined: Mon Dec 20, 2004 4:51 am
sllightly better then linear interpolation with out the over head of fir would be a weighed guassian with it's center at the center of the sample period I can't remember the coefficents, but there are some with power of two coefficents which of course eliminates the divide, if anyone cares, I'll post em. example