vladg/sound

https://www.tokyodawn.net/tokyo-dawn-labs-discontinued-products/

Using SoX resampler in VST plugins

If you hear about SoX resampler and what SoX library is for the first time go and see the links.

Using SoX resampler inside plugin is quite simple. First, append these files of libsox (see the image, sox-14.3.2 was used in my example):

Next, SoX interface uses integer sample type. But SoX resampler uses floating point. To avoid multiple conversion it’s better to use not libsox interface but functions in rate.c directly. And also to add interface to access internal functions outside of rate.c file. This can be done by directly including “rate.c” file in your file:

/*
 * the only way to access private SoX rate functions is to include rate file here
 */
#include “rate.c”

/*
 * now implement our interface to access private SoX rate functions
 */

#include “soxrate.h”

struct soxrate {
    rate_t          rate;
    rate_shared_t   shared;

    int             linear_phase;
    double          bandwidth;
    int             allow_aliasing;
};

static int      g_libsox_init = 0;

struct soxrate *
soxrate_create(double factor, int linear_phase, double bandwidth, int allow_aliasing)
{
    struct soxrate *r;

    if (g_libsox_init++ == 0) {
        // init globals
        init_fft_cache();
    }

    r = calloc(1, sizeof(*r));
    if (r == NULL)
        return NULL;
   
    r->linear_phase = linear_phase;
    r->bandwidth = bandwidth;
    r->allow_aliasing = allow_aliasing;

    rate_init(&r->rate, &r->shared, factor, Very, -1, linear_phase ? 50 : 0, bandwidth, allow_aliasing ? sox_true : sox_false);

    return r;
}

See complete implementation in soxrate.c file in [archive]. This archive contains Molot source code cutted in such a way so only saturator has left. Project “molotsat.vcproj” uses standart Molot oversampler. Project “molotsat2.vcproj” uses SoX resampler. You can compare how they sounds.

The “C” interface h-file for soxrate.c is quite simple:

#ifndef _soxrate_h_
#define _soxrate_h_

/*
 * abstract interface to SoX rate internal interface
 */

struct soxrate;

struct soxrate  *soxrate_create(double factor, int linear_phase, double bandwidth, int allow_aliasing);
struct soxrate  *soxrate_clone(struct soxrate *param_r);

void    soxrate_delete(struct soxrate *r);

void    soxrate_process(struct soxrate *r);
void    soxrate_flush(struct soxrate *r);

const double    *soxrate_output(struct soxrate *r, size_t *n);

double  *soxrate_input(struct soxrate *r, size_t n);

#endif

But VST SDK is a set of C++ classes. So it’s better to implement Upsampler/Downsampler C++ classes. Upsampler’s main function looks like this:

void
Upsampler::processSample(double x, double *y)
{
    double *input_fifo;
    const double *output_fifo;
    size_t size;

    // feed input
    input_fifo = soxrate_input(m_rate, 1);

    _ASSERT(input_fifo != NULL);

    *input_fifo = x;

    // process!
    soxrate_process(m_rate);

    // get new fifo
    size = m_xN;

    output_fifo = soxrate_output(m_rate, &size);

    if (size < m_xN) {
        _ASSERT(size == 0);

        memset(y, 0, m_xN * sizeof(double));
   
    } else {
        _ASSERT(size == m_xN);

        memcpy(y, output_fifo, m_xN * sizeof(double));
    }
}

The downsampler’s function is not complex too:

double
Downsampler::processSample(double *x)
{
    double *input_fifo, y;
    const double *output_fifo;
    size_t size;

    // feed input
    input_fifo = soxrate_input(m_rate, m_xN);

    _ASSERT(input_fifo != NULL);

    memcpy(input_fifo, x, m_xN * sizeof(double));

    // process!
    soxrate_process(m_rate);

    // get new fifo
    size = 1;

    output_fifo = soxrate_output(m_rate, &size);

    if (size < 1) {
        y = 0.0;
    } else {
        y = *output_fifo;
    }

    return y;
}

See moloteng.h and moloteng2.cpp files in archive. Also I used dithering there but you can remove it.

Usage of these upsampler/downsampler classes is simple:

double
ChannelCompressor::processSample(double x)
{
    double over[MAX_OVERSAMPLING];
    unsigned int i;

    m_upsampler.processSample(x * m_gain, over);

    for (i = 0; i < m_oversampling; i++) {
        //over[i] = m_saturator.processSample(over[i]) * m_master;
    }
    return m_downsampler.processSample(over);
}

It works! But too-o-o-o slow! Why? Because SoX resampler is optimized not for easy 2x or 4x upsampling/downsampling but for fractional resampling (44.1 -> 48 kHz for example). It uses DFT for filter implementation regardless of filter type. There’s a comment in rate.c:

       /* Start setting up post-stage; TODO don’t use dft for short filters */

How to make it faster? The answer is not to use DFT but to use simple FIR filter. The idea:

  1. Use SoX library only to get FIR responses for filters
  2. Implement FIR filters convolution

I did FIR responses for upsampler and downsampler for cases of 2x, 4x and so on oversampling in this way:

            char str[256];
            double *f;
            int i, num_taps;

            f = lsx_design_lpf(0.931, 1.0, 2.0, sox_true, 34.0 / 33.0 * 170.0, &num_taps, 0);
            //f = lsx_design_lpf(0.931, 1.0, 2.0, sox_false, 170.0, &num_taps, 0);
            //f = lsx_design_lpf(0.931, 1.0, 4.0, sox_true, 34.0 / 33.0 * 170.0, &num_taps, 0);
            //f = lsx_design_lpf(0.931, 1.0, 4.0, sox_false, 170.0, &num_taps, 0);
            //f = lsx_design_lpf(0.931, 1.0, 8.0, sox_true, 34.0 / 33.0 * 170.0, &num_taps, 0);
            //f = lsx_design_lpf(0.931, 1.0, 8.0, sox_false, 170.0, &num_taps, 0);
            //f = lsx_design_lpf(0.931, 1.0, 16.0, sox_true, 34.0 / 33.0 * 170.0, &num_taps, 0);
            //f = lsx_design_lpf(0.931, 1.0, 16.0, sox_false, 170.0, &num_taps, 0);

            for (i = 0; i < num_taps; i++) {
                if (f[i] != 0.0)
                    sprintf(str, “%0.15e,\n”, f[i]);
                else
                    strcpy(str, “0,\n”);

                printf(“%s”, str);
            }

            free(f);

Parameters for functions were get like sox utility used in maximum quality mode. Aliasing for upsampling is allowed to make FIR shorter but is not allowed for downsampling.

Finally I’ve got files with numbers. For example fir4_dn.dat (used for 4x downsampling):

/*
 * generated using libsox library
 * lsx_design_lpf(0.931, 1.0, 4.0, sox_false, 170.0, &num_taps, 0);
 */
7.591330618410162e-011,
1.315100930944295e-010,
1.176132593954115e-010,
6.273241512488595e-012,

Now all I have to do is to write fast convolution implementation and some helpers. For speed up I used SSE and 32-bit floating point to make 4 multiplications per time. I’m not a great programmer for speed and optimizations so my implementation is far from perfect but fast enough.

// fir must be aligned! fir_size must be %16!
double
FirFilter::fast_convolve(float *x)
{
    unsigned int i;
    double y;

    // convolution

    __m128 xy1, xy2, xy3, xy4;

    xy1 = _mm_setzero_ps();
    xy2 = _mm_setzero_ps();
    xy3 = _mm_setzero_ps();
    xy4 = _mm_setzero_ps();

    for (i = 0; i < m_fir_size; i += 16) {
        xy1 = _mm_add_ps(xy1,
            _mm_mul_ps(
                _mm_loadu_ps(x + i),
                _mm_load_ps(m_fir + i)
            )
        );
        xy2 = _mm_add_ps(xy2,
            _mm_mul_ps(
                _mm_loadu_ps(x + i + 4),
                _mm_load_ps(m_fir + i + 4)
            )
        );
        xy3 = _mm_add_ps(xy3,
            _mm_mul_ps(
                _mm_loadu_ps(x + i + 8),
                _mm_load_ps(m_fir + i + 8)
            )
        );
        xy4 = _mm_add_ps(xy4,
            _mm_mul_ps(
                _mm_loadu_ps(x + i + 12),
                _mm_load_ps(m_fir + i + 12)
            )
        );
    }

    xy1 = _mm_add_ps(
            _mm_add_ps(xy1, xy2),
            _mm_add_ps(xy3, xy4));

    float xy_flt[4];

    _mm_storeu_ps(xy_flt, xy1);

    y = xy_flt[0] + xy_flt[1] + xy_flt[2] + xy_flt[3];

    return y;
}

Some more details for source code. As upsampling is done by zero inserting, e.g. append (x, 0, 0, 0) for case of 4x oversampling in the input of the filter and get (y1, y2, y3, y4) to get oversampled output some optimization was used. The FIR is splitted into 4 parts to avoid multiplications on zero (see FirFilterN class). UpsamplerNx/DownsamplerNx are the classes for SoX FIR filters. Upsampler/Downsampler are the classes for standart Molot oversampling.

See the complete source code in this [archive]. This archive contains source code for simple acceleration limiter plugin. The idea for plugin is to limit vinyl cutter speed. But in this plugin simple waveshaping is used so it’s just an example and not intended for real work.

The original Molot oversampler also included in both archives so you can compare how the different oversampling implementations differs in sound.

Hope it helps.

2 responses to “Using SoX resampler in VST plugins

  1. Pingback: 不不不,Sox 明显好了一大截 | RSA1

  2. roo 2018/09/07 at 18:04

    can you create vst with ssrc and pphs resamplers inside?

Leave a comment