Sound FIR Boxfilter Question

Discuss emulation of the Nintendo Entertainment System and Famicom.

Moderator: Moderators

Post Reply
User avatar
Anes
Posts: 621
Joined: Tue Dec 21, 2004 8:35 pm
Location: Mendoza, Argentina

Sound FIR Boxfilter Question

Post by Anes » Mon Mar 29, 2021 7:18 pm

Well, im taking the sound from APU about 32~ cc.
So i decided to make a Filterbox for it. This means summing the samples in that "window" (32~ cc) and then dividing it for the cc passed, that is about 32~ cc.
And i came with this class:

Code: Select all

class BoxFilter {
	float * in_samples;
	const int size;
	int sample_count = 0;
public:
	BoxFilter(int p_size) : size(p_size) 
	{
		in_samples = new float[size];
	}
	void In(float sample)
	{
		if (sample_count < size)
			in_samples[sample_count++] = sample;
	}
	float Filter()
	{
		float sum = 0;
		for (int i = 0; i < size; i++)
			sum += in_samples[i];
		sample_count = 0;
		return sum / size;
	}
	~BoxFilter()
	{
		delete in_samples;
	}
};
Have two questions:
1 - Is this code right for a Box Filter. The Sound doesn't seem to sound better (or maybe i can't hear it).
2 - Its said that multiplying each sample by a "weight" i can get better sound. Which would be this "weight"?
ANes

lidnariq
Posts: 10463
Joined: Sun Apr 13, 2008 11:12 am
Location: Seattle

Re: Sound FIR Boxfilter Question

Post by lidnariq » Mon Mar 29, 2021 7:36 pm

Anes wrote:
Mon Mar 29, 2021 7:18 pm
1 - Is this code right for a Box Filter. The Sound doesn't seem to sound better (or maybe i can't hear it).
Looks roughly right.

"Boxcar" filters are basically useless. Their sole virtue is that they're easy to explain. And that they're not too bad computationally. But they have absolutely terrible filtering characteristics.
2 - Its said that multiplying each sample by a "weight" i can get better sound. Which would be this "weight"?
In this case, probably a sinc function (sin(n·x)÷(n·x) ). You could also choose other FIR filters.

(why sinc? Because that's an ideal lowpass)

User avatar
Anes
Posts: 621
Joined: Tue Dec 21, 2004 8:35 pm
Location: Mendoza, Argentina

Re: Sound FIR Boxfilter Question

Post by Anes » Mon Mar 29, 2021 8:39 pm

(sin(n·x)÷(n·x) )
I don't understand the above func/formula.
1 - Is "n" the sample?
2 - What about "x"?
3 - I take the sin() when?

Sorry my poor understanding.
ANes

lidnariq
Posts: 10463
Joined: Sun Apr 13, 2008 11:12 am
Location: Seattle

Re: Sound FIR Boxfilter Question

Post by lidnariq » Mon Mar 29, 2021 8:59 pm

"n" and "x" are some constant that chooses the lowpass frequency and the "i" in your for loop. Maybe something like

Code: Select all

float sinc(int element) {
  if (element == 0) return 1; // Prevent divide-by-zero. We know that lim x→0 sin(x)/x is 1 because of L'Hôpital's rule
  float k = frequency * element;
  return sin(k)/k;
}
[...]
for (int ii = 0; ii < size; ii++) {
  sum += sinc(ii-(size/2)) * in_sample[ii]
}

User avatar
rainwarrior
Posts: 8027
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Sound FIR Boxfilter Question

Post by rainwarrior » Mon Mar 29, 2021 9:03 pm

This reference might help with implementation:
https://www.dspguide.com/ch16/1.htm

The actual sinc filter with ideal response requires contribution from infinite samples before and after the current time.

To make it more practical, its common to use a "windowed" sinc filter. This takes the sinc shape and tapers the edges to constrain it to a narrower range of samples before and after the current one.

The wider a window you can accomodate, the closer it gets to ideal. Not sure how many specific samples to suggest, but you'll probably want more than just the 32 samples used in your box filter. (It's normal for your window for the previous downsampled-result to overlap with the window for the next one.)

User avatar
Anes
Posts: 621
Joined: Tue Dec 21, 2004 8:35 pm
Location: Mendoza, Argentina

Re: Sound FIR Boxfilter Question

Post by Anes » Mon Mar 29, 2021 9:39 pm

rainwarrior wrote:
Mon Mar 29, 2021 9:03 pm
This reference might help with implementation:
https://www.dspguide.com/ch16/1.htm
What an outstanding link. I suck in DSP, now i have a good resource.
Thnks!! :D
ANes

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

Re: Sound FIR Boxfilter Question

Post by Zepper » Tue Mar 30, 2021 7:17 am

Well, my emulator generates raw unsigned samples. I do what Anes wrote, the resample method is adding around 40 samples, then DIV by approx 40.
There's a formula to convert to signed samples, working like a charm... but it's a second step though.

I prefer to work with integer numbers for speed.

User avatar
Quietust
Posts: 1719
Joined: Sun Sep 19, 2004 10:59 pm
Contact:

Re: Sound FIR Boxfilter Question

Post by Quietust » Tue Mar 30, 2021 8:02 am

Zepper wrote:
Tue Mar 30, 2021 7:17 am
Well, my emulator generates raw unsigned samples. I do what Anes wrote, the resample method is adding around 40 samples, then DIV by approx 40.
There's a formula to convert to signed samples, working like a charm... but it's a second step though.

I prefer to work with integer numbers for speed.
I was going to say that my emulator does pretty much the same thing (i.e. accumulating 40-41 samples worth of data and then dividing the result), and while it doesn't sound terrific, it at least makes the noise channel's "looped" mode sound halfway-decent at high frequencies, at least compared to just taking every Nth sample and dropping the rest (which sounds quite terrible).
Quietust, QMT Productions
P.S. If you don't get this note, let me know and I'll write you another.

User avatar
rainwarrior
Posts: 8027
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Sound FIR Boxfilter Question

Post by rainwarrior » Tue Mar 30, 2021 1:35 pm

Yeah, the noise channel will never sound right unless the high frequencies are accounted for in some way. Averaging them is pretty good for that.

For a simple but pretty good sounding setup, I've used this in a few projects:
  • 1. Sample most channels at 192kHz (4 x 48kHz), but for the noise channel take an average of the full 1.8MHz output.
  • 2. Apply simple highpass/lowpass IIR to the 192kHz output to approximate the NES' internal filters.
  • 3. Average every 4 samples to downsample to 48kHz.
That will have some minor aliasing, especially at some edge cases, but it's a pretty good tradeoff of complexity/computation vs. sound quality. 32-bit integers are perfectly fine for this level of approach. 2x instead of 4x might be an acceptable trade as well, but in my tests 4x sounded better enough vs. the cost that it was worthwhile.

The periodic noise mode is one thing that tends to have harsh aliasing without some more thorough high frequency emulation / better downsampling, but I think the quality even for this simple version is quite acceptable. There's other edge cases with e.g. triangle set to its highest frequency in some games in lieu of being "silenced", or how MMC5 pulses can go to very high frequencies that 2A03 gets muted for. You can work around these by just muting them when at high frequencies, which might be an easier solution than trying to generate them and then filter them out.

For higher powered accuracy, I'd want to do everything at 1.8MHz and have a robust downsampler, probably a windowed sinc of some sort, but there's a lot of options. Blargg's blip-buffer is an interesting implementation where it approximates a FIR by replacing the step changes of the input with a frequency-appropriate filtered step curve instead.

Post Reply