Bandlimited waveforms algorithm?

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
Bandlimited waveforms algorithm?
by on (#356)
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.

by on (#363)
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

by on (#364)
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/

by on (#366)
Thank you for the information... Quite interesting. :)

by on (#369)
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.

by on (#370)
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.
:(
by on (#372)
My head hurts :(

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 :)

by on (#373)
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.

by on (#374)
WOOHOO!!! :lol:
What a nice day... hehehe ;)
It worked nicely. Thanks for the help!

by on (#375)
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. :)

by on (#376)
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.
:)
by on (#381)
Would you mind posting those emails somewhere Disch for those who may run into the same problems you did?
Re: :)
by on (#383)
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.
Re: :)
by on (#390)
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.

by on (#651)
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

by on (#659)
TimW wrote:
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

That is FIR. FIR and convolution are the same thing. Or are you describing some sort of IIR filter with a roughly Gaussian impulse response?