Wednesday 9 July 2014

Bringing the Delay classes closer to STK

I'm pretty pleased with where things are going with the Silverlight KS dabbling and want to continue putting in some more improvements. Before taking this further as I'm using STK as a basis if only for a commentary reference it seems sensible to harmonise some of the terms a little more with it so that going forwards I can keep the description of the code at least using familiar terms. So, this morning I've added a generic Filter class and then derived the Delay classes from it and changed some of the terms to match those used in STK. Here is a run through of the changes: New Filter class added as below. Some of the attributes and methods don't get used much at the moment, but we'll use them in the building blocks to put some more of the filters later on:

Filter.cs 

using System;
using System.Net;
using System.Collections.Generic;

namespace AgSynth
{
    public class Filter
    {
        protected double gain = 1.0;
        protected double last = 0.0;
        protected List<double> a = new List<double>();
        protected List<double> b = new List<double>();

        protected List<double> outputs = new List<double>();
        protected List<double> inputs = new List<double>();


        public double Gain
        {
            set
            {
                gain = value;
            }

            get
            {
                return gain;
            }
        }

        public virtual double phaseDelay(double frequency)
        {
            if (frequency <= 0.0 || frequency > 0.5 * PcmStreamSource.SampleRate)
            {
                throw new Exception();
            }

            double omegaT = 2 * Math.PI * frequency / PcmStreamSource.SampleRate;
            double reala = 0.0;
            double imaga = 0.0;
            double realb = 0.0;
            double imagb = 0.0;

            for (int i = 0; i < a.Count; i++)
            {
                reala += a[i] * Math.Cos(i * omegaT);
                imaga -= a[i] * Math.Sin(i * omegaT);
            }

            for (int i = 0; i < b.Count; i++)
            {
                realb += b[i] * Math.Cos(i * omegaT);
                imagb -= b[i] * Math.Sin(i * omegaT);
            }


            reala *= gain;
            imaga *= gain;
            realb *= gain;
            imagb *= gain;

            double phase = Math.Atan2(imagb, realb) - Math.Atan2(imaga, reala);
            phase = -phase % (2 * Math.PI);

            return phase / omegaT;
        }



        public double Last
        {
            get
            {
                return last;
            }
        }

        public virtual double tick(double input)
        {
            return 0.0;
        }
    }
}

The majority of this is the phaseDelay method which is calculating the phase delay for the filter at a particular frequency. We're not using that at all at the moment, but will become useful a bit later. I'll maybe blog about that separately if I get a chance and keep this just to the code changes.

The STK contains Delay, DelayL and DelayA classes which follow the code so far. However, as I wanted to use Delay as the attribute for the Delay instead of Length as I've been using (I don't like using _xxx for attributes, it just makes code messy IMHO), it means I could not use Delay for the class name.I decided instead to use DelayN for the integer delay following the pattern used in SuperCollider.

DelayN
This replaces the previous Delay class with the following major changes:

  • get has been changed to tapOut
  • set has been changed to tapIn
  • Length has been changed to Delay and made to set a double. For DelayN this now truncates the value to make the delay an integer length
  • the tick operation has been added which calculates the next sample from the filter and pushes the next sample in which is much closer to how STK works
This is how it now looks:


using System;
using System.Net;


namespace AgSynth
{
    public class DelayN : Filter
    {
        protected const int MAXLENGTH = 1000;
        private double[] buffer = new double[MAXLENGTH + 1];

        private int idx = 0;
        protected int length = 0;

        public DelayN()
        {
            for (int i = 0; i < MAXLENGTH + 1; i++)
            {
                buffer[i] = 0;
            } 
        }

        public void increment()
        {
            lock (buffer)
            {
                idx = (idx + 1 > length) ? 0 : idx + 1;
            }
        }

        public override double tick(double input)
        {
            double last = pop();
            push(input * gain);

            return last;
        }

        public virtual double pop()
        {
            double value = tapOut(length);
            

            return value;
        }

        // increment now on pushing into delay
        public void push(double value)
        {
            tapIn(0, value);
            increment();
        }

        public double tapOut(int index)
        {
            double result = 0.0;

            if (buffer != null)
            {
                lock (buffer)
                {
                    int pos = (idx + index >= length) ? idx + index - length : index + idx;
                    result = buffer[pos];
                }
            }

            return result;
        }

        public void tapIn(int index, double value)
        {
            if (buffer != null)
            {
                lock (buffer)
                {
                    int pos = (idx + index >= length) ? idx + index - length : index + idx;
                    buffer[pos] = value;
                }
            }
        }

        public virtual double Delay
        {
            set
            {
                length = Helpers.Truncate(value);

                if (length < MAXLENGTH)
                {
                    lock (buffer)
                    {
                        double[] newbuffer = new double[MAXLENGTH];

                        for (int i = 0; i < length; i++)
                        {
                            newbuffer[i] = tapOut(i);
                        }

                        buffer = newbuffer;
                        idx = 0;
                    }
                }
            }

            get
            {
                return length;
            }
        }

    }
}

And on to the two fractional delays:

DelayL
Only needs some relatively small tweaks to the change in new method names:


using System;
using System.Net;


namespace AgSynth
{
    public class DelayL : DelayN
    {

        private double alpha = 0;

        public DelayL() : base()
        {
        }

        double xdel = 0;

        public override double pop()
        {
            double x = tapOut(length);

            double y = ((1-alpha) * x) + (alpha*xdel);
            xdel = x;

            last = y;

            return y;
        }

        public override double Delay
        {
            set
            {
                base.Delay = value;
                alpha = value - length;
            }
        }

    }
}

DelayA
And pretty much similar modifications for DelayA too:


using System;
using System.Net;


namespace AgSynth
{
    public class DelayA : DelayN
    {
        private double alpha = 0;
        private double coeff = 0;

        public DelayA() : base()
        {
        }

 
        double ydel = 0;
        double xdel = 0;

        public override double pop()
        {
            double x = tapOut(length);

            double y = (-coeff * ydel) + xdel + (coeff * x);

            xdel = x;
            ydel = y;

            last = y;

            return y;
        }

        public override double Delay
        {
            set
            {
                int l = Helpers.Truncate(value);
                alpha = 1.0 + value - l;

                // optimal allpass range is between  0.5 - 1.5 to achieve flattest phase delay response
                if (alpha < 0.5)
                {
                    l++;
                    alpha += 1.0;
                }

                // coefficient for filter
                coeff = (1.0 - alpha) / (1.0 + alpha);

                base.Delay = l;
            }
        }

    }
}


And given our current implementation of the Karp Generator no changes are needed there. et-voila, we're much closer to talking like STK.

No comments:

Post a Comment