Skip to main content

Notice

Please note that most of the software linked on this forum is likely to be safe to use. If you are unsure, feel free to ask in the relevant topics, or send a private message to an administrator or moderator. To help curb the problems of false positives, or in the event that you do find actual malware, you can contribute through the article linked here.
Topic: Sinc resampling (Read 5858 times) previous topic - next topic
0 Members and 1 Guest are viewing this topic.

Sinc resampling

I have a Lanczos windowed sinc resampler which I use in a number of projects, and it seems to be going terribly wrong. Two people have complained that it sounds worse than linear or cubic interpolation, which are much simpler functions, but prone to aliasing.

I have a poll topic [a href='index.php?showtopic=105088']here[/a] regarding its use, along with the other functions, in DUMB/foo_dumb. Also included are some lame spectral plots of the damn Lanczos filter.

The source code in question is here (header) (input 16 bit, output floating point)

Fairly typical stuff.

Basically, what I need is a resampler which is low latency, and can switch sampling ratios while running without incurring any noise penalty on the waveform. And it shouldn't sound like crap, either. I thought this filter function was it, but it seems not.

Sinc resampling

Reply #1
I will take a look at your implementation to see if I can spot any problem in it.

Meanwhile, you might check the code that I implemented for Psycle, that I've recently improved significantly (Well, basically increasing the taps and fixing a bug in the windowing being applied to the signal).

From the constants, it seems we use the same sizes, so it can either be the lut creation, the windowing (as it happened to me), or the work loop.  One thing that helps is trying to plot the lut.


http://sourceforge.net/p/psycle/code/HEAD/...s/resampler.hpp
http://sourceforge.net/p/psycle/code/HEAD/...s/resampler.cpp

There's also the use of an accelerated sse sin function. (Previously i tried a polynomial approximation and while the deviation was quite low, for a sinc function it didn't have sinc(0) = 1, but sinc(0)=0 instead):
http://sourceforge.net/p/psycle/code/HEAD/...h/sse_mathfun.h


The output sound of this is comparable to the output sound of XMplay's sinc, but XMPlay's is quite faster (I think it uses less taps and probably integer math).

Sinc resampling

Reply #2
Update:
I am still checking, but i believe that your problem is the sinc function:

static float sinc(float x)
{
    return fEqual(x, 0.0) ? 1.0 : sin(x * M_PI) / (x * M_PI);
}

This function operates in the range 0..2 = 0..360degrees, whereas in lanczos_init, you are assuming that 1 means 360 degrees.

Sinc resampling

Reply #3
The window function you use in a windowed-sinc resampler, as in other applications, defines the trade-off between pass-band (main-lobe) width and stop-band (side-lobe) attenuation.

I suggest you use a different trade-off, i.e. more side-lobe rejection. The Lanczos window is plotted on the Wikipedia page. I suggest you use Nuttall's 3-term window (similar to Blackman's window) or some 2-term window I came up with a few years ago. These taper to zero more smoothly at their ends than the Lanczos function. Both are described in my 2010 DAFx paper.

Meaning: you only have to change the line "lanczos_lut = fabs(x) < LANCZOS_WIDTH ? sinc(x) * sinc(x / LANCZOS_WIDTH) : 0.0;"

Edit: for the 2-term window, something like "lanczos_lut = sinc(x) * (0.79445*cos(0.5*M_PI*...) + 0.20555*cos(1.5*M_PI*...));"

I'm not sure about the vertical lines I see in your plots on the other page, though. Maybe the 8192 isn't enough, or you forgot to round properly in some code related to the phase? Just guessing, though.

Chris
If I don't reply to your reply, it means I agree with you.

Sinc resampling

Reply #4
@C.R. Helmrich: Mmm... maybe Nuttal has a bandwidth too high for the desired usage. In Psycle I opted for blackman:

lanczos
hann
blackman
nuttal
spline, to compare

This is made with the Psycle engine, playing pattern 0E of death rally track 8 - botswana hard - jungletrack.s3m, muting the channels 1st, 2nd, 4th and 5th. Audacity is configured for blackman window and 4096 samples.

@kode54: Well, about my initial comment about the wrong range, just scrap it. I didn't read it correctly ( the sinc window has to run from 0 to pi, which is already doing, and the sinc function is already going at 0 at the end of each resolution, as it should do).

I guess the real problem is, on one side, the lack of filtering when it is downsampling (phase_inc higher than 1), plus the usage of the lanczos window. Also, i am not completely sure about the line "*out++ = (int)(sample / kernel_sum * 256.0);". As i understand it, it is in order for the filter to have a constant gain, but.. the * 256 part?


If you opt to change the window, beware that you are building just half of the sinc (and duplicating it in lanczos_resampling_run), so the window runs from pi to 2pi (as opposed to sinc window, where it runs from 0 to pi).

Sinc resampling

Reply #5
I have two versions of the resampler up. I'll edit them both into the original post.

Thanks for the feedback, I'll see what I can do with it. As for the lines, it may be some sort of phase error. I saw obvious full spectrum lines in a 10 second plot of upsampling white noise, even though the phase_inc was exactly 4096. Perhaps something is up with how the absolute does the wrapping of the window.

As for the filtering where phase_inc is greater than 1.0, note how the filter run treats that process. It scales the phase center point down by 1.0 / phase_inc, and scales down the stepping through the window by the same factor. Although it may be possible that this scale should not be linear.

EDIT: The vertical lines are where the output clipped.

Sinc resampling

Reply #6
Bumping to note that I added multiple resamplers to the above linked source code, including a blep synthesis mode, which uses inverse convolution to apply sinc pulses instead of raw deltas to the output, which acts as a band-limited and 1/8192 phase accurate zero order hold mode, albeit much slower than zero order hold.

Sinc resampling

Reply #7
I've been listening to some different mods (mod,s3m,xm....) lately with foo_dumb 1.0.74. Configured for sinc resampling, use playptmod where applicable and default chip-o-matic. Since I didn't really found anything wrong, I did some tests:

Psycle:

DUMB


20Hz-22Khz swept played at C-4, C-3, C-5, C#4 and B-3.

There are two differences: On those notes that get the default filtering (C-4, C-3, and B-3), the difference between both is mostly because of the different windowing used as discussed in the previous post (sinc/lanczos for dumb and blackman for Psycle).
But on those that require filtering, ( C-5 and to less extent C#4 ) , it is clear that dumb doesn't work well.

I believe I can pinpoint this problem to your decision of scale the generated sinc.  The concept is ok, but it has an important error: the window has to remain constant. In other words, If you want to scale the sinc, you need to have two Lookup tables, one for the window alone, and the other for the sinc alone. Then, scale the sinc and apply the window as is.

If you take a look at Psycle's source, the window is kept on a separated table, and when using the filtered call, it recalculates the sinc (i might try to scale as you do in a newer version to improve CPU efficiency), BUT applies the window without scaling.

Sinc resampling

Reply #8
Can I have your test file so that I may use it to tweak the results? I'm already attempting to implement a fixed window width, but something is going wrong with C#4.

EDIT: I've implemented it, please try it out.

EDIT 2: Also note that for some time now, I've been using a 3 term Nuttall window.

Sinc resampling

Reply #9
Good job! Now it should be good enough. C-5 is not perfect, but is comparable to Psycle with blackman, and better than the version you made with nuttal that didn't have the fix.
You should test if recalculating the sinc() instead of changing the size improves the noise in C-5. The problem might be a lack of precision, or an erroneous calculation, but you got an improvement nevertheless

dumb-nuttal-fixed (1.0.103)
psycle-nuttal, to compare
zipped .xm file
sox.exe  input.wav -n spectrogram -w Kaiser


Said that, seems that I have a slight problem (small window displacement?) in Psycle. Those two lines in the C-3 note look strange.


Edit:

Nice thing about the blep synthesis. These are the first three seconds of "Classic - Loadermusic.mod":
Needed to disable playptmod/modplay. Maybe that's intended.

dumb with none (ZOH)
dumb with blep

Sinc resampling

Reply #10
offtopic:
I realized i had a bug in the xm saving code (xm export is intented mostly for exchanging with other software, not really for producing files, but a bug is a bug). That's why the notes weren't being played at the correct pitch. In fact, in XM, one can't really play a 44.1Khz file anyway (nearest is 44092), but Psycle was saving it one semitone off, and i used this xm file to render the graphics, so that's why all the notes show resampling artifacts.

As such, it really was playing B-3, B-2, B-4, C-4, A#3.

Sinc resampling

Reply #11
Also, if you'll notice, the aliasing appears to be below -96dB.