Overampling Code for Daisy - WIP

Hello everyone!

First off - here is a port of the JUCE oversampling class that does not work - I am wondering if it’s a filter design problem, implementation issue or both.

This is my first post in the forum: I am a self taught coder and just got a Daisy Field 3 weeks ago, but am finding it to be big fun! Right now, I’m working on porting over an old JUCE delay project that I am really excited about, but I am also creating a personal template system for the Daisy Field so I can quickly add DSP code to it without worring too much about coding for the human interface (The OLED and the knobs and the CV jacks) over and over again. That’ll be a seperate post, eventually.

Any-how, to get to the meat of the matter: below is some code basically ported from the JUCE oversampling class. I thought it would be useful for waveshaping and distortion applications, as well as taming delay-head tweaks and twists that push frequencies over Nyquist.

This runs on my Daisy Field, but for whatever reason, it does filter, but poorly - in the downsampling I am getting some rejection of aliasing, but it’s not what I’d like to see. I am using the same FIR coeffcients for the upsampling and downsampling, which could just be me having a lack of understanding essential concepts of this particular algorithm.

I am using https://fiiir.com/ to generate the table, and being very conservative about the stopband.

I’m also using a 47-coefficient FIR table currently, which is taking approximately 15% of the cpu of the Daisy Field, just running a single oscillator, FWIW:

Code below!

#ifndef OVERSAMPLING_H
#define OVERSAMPLING_H

#include <stddef.h>

template<size_t audio_block_size, size_t NUM_CHANS>
class Oversampling
{
    public:
        Oversampling(){};
        ~Oversampling(){};

        auto getArray()
        {
            return oversample_;
        }

        void Upsample(const float* const* in, float** out, size_t size)
    {
        for (int chan = 0; chan < NUM_CHANS; chan++)
        {
            // 3 buffers - ring buffer for input, fir buffer and output buffer

            // loop through input buffer
            for (size_t i = 0; i < size; i++)
            {
                // Move current input sample to 2nd to last index in ring buffer
                // also does 0 padding
                ring_buffer_[chan][N - 1] = 2 * in[chan][i];

                // Convolution
                float out = 0.;

                for (size_t k = 0; k < Ndiv2; k += 2) // Every other sample
                    out += (ring_buffer_[chan][k] + ring_buffer_[chan][N - k - 1]) * fir[k]; // Convolve 2 samples
                
                // Outputs
                oversample_[chan][i << 1] = out;
                oversample_[chan][(i << 1) + 1] = ring_buffer_[chan][Ndiv2 + 1] * fir[Ndiv2];

                // Shift data in ring buffer
                for (size_t k = 0; k < N -2; k +=2)
                    ring_buffer_[chan][k] = ring_buffer_[chan][k + 2];
            }

        }
    }

    void Downsample(const float* const* in, float** out, size_t size)
    {
        /*
        for (int chan = 0; chan < NUM_CHANS; chan++)
        {
            for (size_t j = 0, k = 0; j < audio_block_size; j++, k+=2)
            {

                out[chan][j] = oversample_[chan][k];
            }

        }
        */

        for (int chan = 0; chan < NUM_CHANS; chan++)
        {
            // 3 buffers - ring buffer for input, fir buffer and output buffer
            int pos = position[chan];
            // loop through input buffer
            for (size_t i = 0; i < size; ++i)
            {
                // Go through ever other sample of the oversampled block, move to ring buffer
                ring_buffer2_[chan][N - 1] = oversample_[chan][i << 1];

                // Convolution
                float output = 0.;

                for (size_t k = 0; k < Ndiv2; k += 2) // Every other sample
                    output += (ring_buffer2_[chan][k] + ring_buffer2_[chan][N - k - 1]) * fir[k]; // Convolve 2 samples
                
                // Outputs
                output += ring_buffer3_[chan][pos] * fir[Ndiv2];
                ring_buffer3_[chan][pos] = oversample_[chan][(i << 1) + 1];

                out[chan][i] = output;

                // Shift data in ring buffer
                for (size_t k = 0; k < N -2; k +=2)
                    ring_buffer2_[chan][k] = ring_buffer2_[chan][k + 2];

                pos = (pos == 0? Ndiv4 : pos -1);
            }

            position[chan] = pos;
        }
    };

    private:
    
    int N = 47;
    int filter_order = N - 1;
    int Ndiv2 = N / 2;
    int Ndiv4 = N / 4;

    float ring_buffer_[2][47];
    float ring_buffer2_[2][47];
    float ring_buffer3_[2][12]; // NDIV4 + 1

    float oversample_[2][audio_block_size * 2];
    int position[2];

    float fir [47] = 
    {0.000000000000000000,
    -0.000007557788881071,
    -0.000103221978462192,
    0.000000000000000000,
    0.000489705572593109,
    0.000264597153084575,
    -0.001222266266451664,
    -0.001226283269580699,
    0.002158218826490908,
    0.003504088919578970,
    -0.002722240662191572,
    -0.007735246094119957,
    0.001696911603050828,
    0.014271225531182942,
    0.002906758125478506,
    -0.022851937327185167,
    -0.014069845936793281,
    0.032438904580599219,
    0.037098373517579816,
    -0.041348647561233541,
    -0.088223035243884654,
    0.047696118455589541,
    0.311989058091958482,
    0.449992643503193734,
    0.311989058091958538,
    0.047696118455589541,
    -0.088223035243884640,
    -0.041348647561233555,
    0.037098373517579823,
    0.032438904580599219,
    -0.014069845936793281,
    -0.022851937327185184,
    0.002906758125478507,
    0.014271225531182938,
    0.001696911603050827,
    -0.007735246094119960,
    -0.002722240662191574,
    0.003504088919578971,
    0.002158218826490911,
    -0.001226283269580700,
    -0.001222266266451664,
    0.000264597153084576,
    0.000489705572593109,
    0.000000000000000000,
    -0.000103221978462193,
    -0.000007557788881071,
    0.000000000000000000};
};


#endif

Have you any test code to work on? Just plain console program it’s enough.

Hello! an update here - a forum member over on the audioprogrammer was kind enough to update this with new coefficients that work - I still need to add some initialization to loop through and zero out the ring buffers, but this does work!

#ifndef OVERSAMPLING_H
#define OVERSAMPLING_H

#include <stddef.h>

template<size_t audio_block_size, size_t NUM_CHANS>
class Oversampling
{
    public:
        Oversampling(){};
        ~Oversampling(){};

        auto getArray()
        {
            return oversample_;
        }

        void Upsample(const float* const* in, float** out, size_t size)
    {
        for (int chan = 0; chan < NUM_CHANS; chan++)
        {
            // 3 buffers - ring buffer for input, fir buffer and output buffer

            // loop through input buffer
            for (size_t i = 0; i < size; i++)
            {
                // Move current input sample to 2nd to last index in ring buffer
                // also does 0 padding
                ring_buffer_[chan][N - 1] = 2 * in[chan][i];

                // Convolution
                float out = 0.;

                for (size_t k = 0; k < Ndiv2; k += 2) // Every other sample
                    out += (ring_buffer_[chan][k] + ring_buffer_[chan][N - k - 1]) * fir[k]; // Convolve 2 samples
                
                // Outputs
                oversample_[chan][i << 1] = out;
                oversample_[chan][(i << 1) + 1] = ring_buffer_[chan][Ndiv2 + 1] * fir[Ndiv2];

                // Shift data in ring buffer
                for (size_t k = 0; k < N -2; k +=2)
                    ring_buffer_[chan][k] = ring_buffer_[chan][k + 2];
            }

        }
    }

    void Downsample(const float* const* in, float** out, size_t size)
    {
        /*
        for (int chan = 0; chan < NUM_CHANS; chan++)
        {
            for (size_t j = 0, k = 0; j < audio_block_size; j++, k+=2)
            {

                out[chan][j] = oversample_[chan][k];
            }

        }
        */

        for (int chan = 0; chan < NUM_CHANS; chan++)
        {
            // 3 buffers - ring buffer for input, fir buffer and output buffer
            int pos = position[chan];
            // loop through input buffer
            for (size_t i = 0; i < size; ++i)
            {
                // Go through ever other sample of the oversampled block, move to ring buffer
                ring_buffer2_[chan][N - 1] = oversample_[chan][i << 1];

                // Convolution
                float output = 0.;

                for (size_t k = 0; k < Ndiv2; k += 2) // Every other sample
                    output += (ring_buffer2_[chan][k] + ring_buffer2_[chan][N - k - 1]) * fir[k]; // Convolve 2 samples
                
                // Outputs
                output += ring_buffer3_[chan][pos] * fir[Ndiv2];
                ring_buffer3_[chan][pos] = oversample_[chan][(i << 1) + 1];

                out[chan][i] = output;

                // Shift data in ring buffer
                for (size_t k = 0; k < N -2; k +=2)
                    ring_buffer2_[chan][k] = ring_buffer2_[chan][k + 2];

                pos = (pos == 0? Ndiv4 : pos -1);
            }

            position[chan] = pos;
        }
    };

    private:
    
    int N = 47;
    int filter_order = N - 1;
    int Ndiv2 = N / 2;
    int Ndiv4 = N / 4;

    float ring_buffer_[2][47];
    float ring_buffer2_[2][47];
    float ring_buffer3_[2][12]; // NDIV4 + 1

    float oversample_[2][audio_block_size * 2];
    int position[2];

    float fir [47] = 
    {-0.000116310803,
	0,
	0.000455625792,
	0,
	-0.001175144454,
	0,
	0.002495617373,
	0,
	-0.004704627208,
	0,
	0.008179944940,
	0,
	-0.013453543186,
	0,
	0.021387722343,
	0,
	-0.033690825105,
	0,
	0.054717078805,
	0,
	-0.100511685014,
	0,
	0.316407203674,
	0.500000000000,
	0.316407203674,
	0,
	-0.100511685014,
	0,
	0.054717078805,
	0,
	-0.033690825105,
	0,
	0.021387722343,
	0,
	-0.013453543186,
	0,
	0.008179944940,
	0,
	-0.004704627208,
	0,
	0.002495617373,
	0,
	-0.001175144454,
	0,
	0.000455625792,
	0,
	-0.000116310803};
};


#endif
1 Like

here the setting to obtain the coefficients

When finished and if you like, it would be nice to have it complete and a usage example, so we could add it to the DSP library.

Will do @polyclash - I’m not sure why yet, but I did have to zero-pad any coefficients here to make the code perform the desired results… that particular website is great, but does not do that!

I’m curious - what exactly do you mean by zero-padding?

Quiet interested in using this class provided by @Tristan. Thank you for sharing! :slight_smile:

I have some questions regarding how this works in practice. I see that the upsampling/downsampling is processed by the block. What effect does the audio block size have on this? (if any at all).

I assume we are oversampling by a factor of 2? Is the method of increasing the oversampling factor by simply adding a second stage, with a second Oversampling object or is this inadvisable?

Thirdly, I’m a little confused by what what the out argument is for in the Upsample() function. From what I can tell, it is passed as a pointer to a pointer but then is redeclared as a float and assigned 0. What does this acheive? What should i pass in for this argument?