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
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