Oversampling in VMD

User avatar
ChR_is
Posts: 116
Joined: Wed Sep 22, 2021 5:48 pm

Oversampling in VMD

Post by ChR_is »

i always thought of oversampling as a standout feature of my R_Ware modules, but i am open to share.
here's an IIR-based oversampling class for use in VMD. there's a usage example vmod in there as well.

it's MIT licensed, so you can use it privately and commercially.

https://github.com/NeverKnewTheName/R_OpenLib

the up- and downsampling filters are biquad fitlers that have carefully chosen resonance values for maximum steepness. if you want to know more about stacking resonances have a look at this amazing blog: https://www.earlevel.com/main/2016/09/2 ... g-filters/

this is the start of me open sourcing my dsp library. but for now i'll keep the high-performance, linear-phase polyphase FIR oversamplers exclusive to R_Ware modules. stay tuned for future updates on this repo though. ;)

be aware that oversampling is not the answer to all aliasing problems. there are other ways to anti-alias procedures as well. i'll share more in the future.

Let me know if you like this project, if you find any issues, if you have any request, etc.
User avatar
ChR_is
Posts: 116
Joined: Wed Sep 22, 2021 5:48 pm

Re: Oversampling in VMD

Post by ChR_is »

Here's a tutorial on how to use R_OpenLib Oversampling in VMD

https://youtu.be/SXhzCwCaXpU
ColinP
Posts: 998
Joined: Mon Aug 03, 2020 7:46 pm

Re: Oversampling in VMD

Post by ColinP »

Hi Chris,

Very nice of you to post this but I'm struggling to understand the working of the main process() method.

Code: Select all

public double process( double value, R_IOversampledProcessor processor )
    {
        double[] os = new double[m_osFactor];
        os[0] = value; // first value is valid, others are zero-stuffed

        //////// OVERSAMPLING
        for( int i = 0; i < m_osFactor; ++i )
        {
            os[i] = m_upSampler.process( os[i] );
            os[i] = processor.process( m_osFactor * os[i] );
            os[i] = m_downSampler.process( os[i] );
        }
        //////// OVERSAMPLING

        return os[0];
    }
I'm a little worn out (coping with flooding) so might be missing something blindingly obvious but I don't understand how information flows in the loop. How is os[ 0 ] at the end of the method affected by anything going on in the zero-stuffed parts of the array?
User avatar
ChR_is
Posts: 116
Joined: Wed Sep 22, 2021 5:48 pm

Re: Oversampling in VMD

Post by ChR_is »

ColinP wrote: Wed Jan 11, 2023 7:28 pm Hi Chris,

Very nice of you to post this but I'm struggling to understand the working of the main process() method.

Code: Select all

public double process( double value, R_IOversampledProcessor processor )
    {
        double[] os = new double[m_osFactor];
        os[0] = value; // first value is valid, others are zero-stuffed

        //////// OVERSAMPLING
        for( int i = 0; i < m_osFactor; ++i )
        {
            os[i] = m_upSampler.process( os[i] );
            os[i] = processor.process( m_osFactor * os[i] );
            os[i] = m_downSampler.process( os[i] );
        }
        //////// OVERSAMPLING

        return os[0];
    }
I'm a little worn out (coping with flooding) so might be missing something blindingly obvious but I don't understand how information flows in the loop. How is os[ 0 ] at the end of the method affected by anything going on in the zero-stuffed parts of the array?
os is an array that keeps track of all oversampled values. initially it is filled with 0s (from initializing). the first index is set to the input value from the parameter list.
so the array now consists of the actual input and lots of 0 for the zero stuffing. each array element, the actual value and the stuffing 0s, are then put into the upsampler to produce the actual sampling values to feed into the processing function. the array os keeps track of these values.
after processing the whole array needs to be put through the downsampler one by one. the first array element now holds the actual value. we can discard all other values that resulted from the zero stuffing. these are only needed for the up- and down-sampling filters.

so for 2x oversampling it would look like this

Code: Select all

os[0] = value
os[1] = 0

os[0] = m_upSampler.process( os[0] );
os[0] = processor.process( m_osFactor * os[0] );
os[0] = m_downSampler.process( os[0] );
os[1] = m_upSampler.process( os[1] );
os[1] = processor.process( m_osFactor * os[1] );
os[1] = m_downSampler.process( os[1] );

value = os[0];
so at first os[0] holds the actual value and os[1] is 0. after the upsampling both os[0] and os[1] hold valid samples at the new samplerate that are a result from the interpolation and filtering from the upsampler. these values can now be processed. the downsampler takes these two values and converts them to a single value at the base samplerate. os[1] can now be discarded and os[0] holds the result of the downsampling process.

The zero stuffing is needed for the upsampling and downsampling filters. filters have 'memory'. you are basically tricking the filter into thinking it runs at a higher sample rate. and to accommodate for that you need to feed it more values to get back to the actual frequency. e.g. 20kHz at 48kHz sampling rate are now 40kHz at 96kHz sampling rate. by putting twice the values through you get back to 20kHz. adding 0s reflects the whole spectrum at nyquist. the filter is then removing that part alongside any frequency that would have reflected anyways.

hope this helps! :)
ColinP
Posts: 998
Joined: Mon Aug 03, 2020 7:46 pm

Re: Oversampling in VMD

Post by ColinP »

Thanks Chris.

I agree that what the code is doing (with m_osFactor = 2) is the following...

Code: Select all

os[0] = value
os[1] = 0

os[0] = m_upSampler.process( os[0] );
os[0] = processor.process( m_osFactor * os[0] );
os[0] = m_downSampler.process( os[0] );
os[1] = m_upSampler.process( os[1] );
os[1] = processor.process( m_osFactor * os[1] );
os[1] = m_downSampler.process( os[1] );

value = os[0];
But isn't this equivalent to...

Code: Select all

return m_downSampler.process( processor.process( m_osFactor * m_upSampler.process( value ) ) );
What I don't understand is how anything in os[ 1 ] impacts on os[ 0 ].
User avatar
ChR_is
Posts: 116
Joined: Wed Sep 22, 2021 5:48 pm

Re: Oversampling in VMD

Post by ChR_is »

Think of the filters as interpolators. the upsampler turns 1 sample into X samples and the downsampler turns X samples into 1 sample with X being the oversampling factor.

so picture this at 48kHz samplerate your samples look like this:

Code: Select all

V V V V V V V V ...
you get a value for every sample (V represents a sample here).
when you want to oversample 2x you need 2 samples for every sample you had at 48kHz:

Code: Select all

VxVxVxVxVxVxVxVx...
what value does the x have? it's literally 0. between the sampling points there are no values. so that's what we're giving the upsampling filter to work with. you can think of a lowpass filter like an interpolating filter. it smooths out the values you put in. and if you feed a succession of values and 0s it will turn these 0s into in-between-values.
in theory you can use any interpolator to get the in-between values. but since we need to filter the frequencies above the nyquist frequency anyway we can use the IIR filter to do the upsampling and the filtering in one go.
ColinP
Posts: 998
Joined: Mon Aug 03, 2020 7:46 pm

Re: Oversampling in VMD

Post by ColinP »

I figured out eventually that the information flows via the state of the m_biquadCoeffs array inside the filters.
User avatar
utdgrant
Posts: 624
Joined: Wed Apr 07, 2021 8:58 am
Location: Scotland
Contact:

Re: Oversampling in VMD

Post by utdgrant »

Can't thank you enough for this, Chris. It's exactly what I've been seeking for quite some time, now. What a fantastic, generous gesture.

I'm currently investigating how it can be retrofitted to some of the non-linear Dome Music Technologies audio modules. Of course, any modifications I make to the core library will be shared with the community in the same spirit as the original.
______________________
Dome Music Technologies
User avatar
ChR_is
Posts: 116
Joined: Wed Sep 22, 2021 5:48 pm

Re: Oversampling in VMD

Post by ChR_is »

ColinP wrote: Thu Jan 12, 2023 11:09 am I figured out eventually that the information flows via the state of the m_biquadCoeffs array inside the filters.
that's what i meant by filter 'memory'. each biquad in the stack remembers two states. these are processed past values.

to elaborate on why this works as an interpolator:
the perfect lowpass filter has an infinitely steep cutoff. in the frequency domain this is represented as a box. translating this box into the time domain results in a sinc impulse. the sinc impulse catches each sample perfectly and 'ripples' in-between the sample points. if you overlay each sample with a sinc you get a theoretical perfect reproduction of the signal. but implementing perfect sincs is not possible as they ring out infinitely. so we need to window them. that's a tradeoff to make it work. the result is ripple in the frequency response and less steepness (depending on the type of window you chose). yet it is still a lowpass. my FIR oversampling filters use windowed sincs, but in practice any lowpass works (with varying quality). the steeper the cutoff the better ofc. that's why the stacked resonance lowpasses i use in R_OpenLib work quite well. in comparison to their FIR counterparts they are not linear-phase, but therefor a lot cheaper to compute. everything has its ups and downs. ;)
Post Reply

Return to “Module Designer”