Up/downsampling


#1

I'm testing a 4-fold up/downsampler based on FIR filters, for alias-reduced non-linear effects, and sharing my preliminary results in axoloti-contrib/patches/jt/devel/updownsampler_x4.axp

I'm using 16-tap filters for both up- and downsampling. The coefficients are generated with Scilab:

fs = 48000;
overx = 4;
fcut = 15000;
order = 16;
// for upsampling: equiripple
hn_up=eqfir(order,[0 fcut/fs/overx;0.49/overx 0.5],[1 0],[1 8]);

// for downsampling: attenuate aliases harder that we are most sensitive for
hn_down1=eqfir(order-3,[0 fcut/fs/overx;0.47/overx 0.5],[1 0],[1 1.5]);
hn_down = conv(hn_down1,{.25 .25 .25 .25});

(and further scaled/rounded to 16 bit coefficients for use with the dual-MAC instructions...)

Filter responses:
(upsampling in black, downsampling in blue)

The motivation for using a different filter than equiripple is that aliases showing up at low frequencies are far more disturbing than aliases above 10kHz, those are likely psychoacoustically masked by other frequencies.
The advantage is more evident on a reprojected frequency plot:


There should be a better way to compute coefficients, probably using a rough equal loudness contour function as error weights in remez()...

Feedback/suggestions welcome before I re-assemble the test-patch into library objects...

(cross-references to related topics:)



Upsampling / downsampling: am i doing it good?
#2

That's cool, @johannes.
Any plans to implement an in/outlet type for 4x oversampled audio, so that multiple oversampled objects can be connected together?

a|x


#3

I'm hesitant to add a specific inlet/outlet type for 4x oversampled audio, an alternate approach is dealing with up- and downsampling as well as buffersize re-partitioning at sub-patch boundaries. That's what PD and Max do. A wire datatype for 4x oversampling seems a bit specific, could be complemented with 2x, 4x, 8x and 16x oversampling wire datatypes in combination with other bufferlengths too. The rainbow is too small to assign an unique color to every combination. So I'm inclined to think that doing this at sub-patch boundaries is a cleaner design pattern.

In the meantime, I believe quite some use-cases can be served using up/downsampling inside object-code.


#4

Great news! I was just about to ask if there had been some initiatives to reduce (audible) aliasing.


#5

A thought: What would be amazing is if there was some way objects could automatically exist as oversampled versions. It seems theoretically possible as long as everything is done with reference to the sample rate (whether oversampled or native).

EDIT: Ah, just read your post about subpatch boundaries @johannes. So would oversampling factor be an attribute on the subpatch and the upsampling/downsampling be automatically included in that? That seems like a great idea if any object could be oversampled by the patcher/firmware. Would we have to account for the difference in frequency ranges or would that be taken care of in the manner I describe at the start of this post?


#6

Taking care of different frequency ranges complicates the implementation: sometimes it is most useful to restrict the frequency range to baseband audio, sometimes it is too restrictive. For example, an oversampled oscillator, this would be expected, ultrasonic pitch 'd produce aliases. I can't currently think of a common examples where you want ultrasonic pitch, can anyone think of an application scenario where you'd need this? Still, in case of undersampled audio processing (to reduce dsp load), the frequency range should be reduced.


#7

I'm not suggesting the frequency range be increased, but rather that the very nature of oversampling would shift the range upwards. If you were to just increase the sample rate 4x then everything would be two octaves higher without some kind of compensation? As long as the frequency is calculated with reference to a sample rate global variable rather than an assumption that the sample rate is 48kHz then things should be automatically ok. Unless I've missed something.


#8

You'd have to divide the phase-increment amount by 4, assuming you were working on an oscillator, I imagine.

a|x


#9

That's another way of doing it, but it still requires the objects to use a global variable so that they'd work as expected when oversampled.


#10

I don't know enough about DSP to know if quadrupling the sample-rate would have an effect on e.g. filter cutoff frequencies etc.

a|x


#11

If my understanding is correct, everything frequency related is relative to the sample rate. I guess it wouldn't be too much of a problem if everything was shifted two octaves up, oscillators can always be offset to -24.
I'd love to be able to oversample some FM patches to get some super-sharp sounds with less aliasing.


#12

I see.

I'd love to port some of the MI Braids code over to Axoloti. Some of the oscillator types from that module are great, and should run easily on the Axo Core, as it's much more powerful than the processor Braids uses.

A lot of Braids' 'digital' oscillator (as opposed to 'virtual analog') types rely on oversampling for antialiasing, so I never tackled them before.

a|x


#13

@johannes do you envisage rolling the up/downsampling filters into some kind of builtin function that can be called in object code, or would we just copy-paste the functions into our own objects code?

a|x


#14

@toneburst Have you looked more into the porting of braids code? I was just looking at the specifictions of it. Patching something similar in Axoloti with factory objects would definatly reach the DSP limit. So probably something custom have to be made to achive this.

Anyway, just wanted to know of you had looked more into it :wink:


#15

Hi,
sorry for digging up this thread.
I've already have the same kind of discussion on the JSFX/Reaper forum:
Reaper/JSFX oversampling thread

In this thread I suggested some solutions and provided some interpolation/decimation filters.

At the moment, I'm experimenting with first order differentiated polynomials and x2 oversampling.

At the interfacing level, I think that using interleaved audio signals is a viable solution:

int2_3float is the interpolator (x2 upsampler).
It has an ordinary audio input and it has two audio outputs.
The audio outputs are interleaved buffers, so that in the s-rate Code section of O2DPHardClip inlet_in1 comes before inlet_in0 (see the objects code).

O2DPHardClip is a x2 oversampled Differentiated Polynomial Hard Clipper

dec2_Bellanger is a x2 decimator (19 taps half band downsampler) it takes a pair of interleaved upsampled audio inputs and outputs an ordinary audio signal.

int2_3float.axo (967 Bytes) ~500 cycles

O2DPHardClip.axo (2.9 KB)

dec2_Bellanger.axo (1.1 KB) ~1000 cycles

In the near future, i plan to code a bunch of O2DP objects.


#16

Interesting...

Another Axoloti custom object coder came up with a slightly different 2x oversampling method, using twin buffers, but not interleaved.

Let me find the GitHub URL.

a|x


#17

Have a look in

It's also available from within the Axoloti Patcher, of course.

a|x


#18

There was also talk on the forum of a possible new in/outlet port type for oversampled audio signals. Might be worth looking that discussion up.

a|x


#19

Thanks for the link @toneburst, it is quite similar :smiley:

I'll give it a try and compare the performance issues:
he uses int32_t while i use floats.

As far as i experimented floats on the Axoloti are quite efficient !!

I think that this discussion talks about inlet/outlet port types: https://sebiik.github.io/community.axoloti.com.backup/t/up-downsampling/1950/3 .


#20

It would be good to have a standard method for this, that everyone can use.

a|x