I am writing a simple allpass filter for myself and it’s going fairly well.
I have followed the formula y[n] = (-g * x[n]) + x[n - D] + (g * y[n - D])
from Curtis Roads’ computer music tutorial and I am pretty sure I got it right, but the filter is acting weirdly and so I fear it is something SC specific that I misunderstood. I might have gotten something misunderstood when reading C code and convering it for the “new” .hpp header style.
Here is the code:
// PluginMKAllpass.cpp
// Mads Kjeldgaard (mail@madskjeldgaard.dk)
#include "SC_PlugIn.hpp"
#include "MKAllpass.hpp"
static InterfaceTable* ft;
namespace MKAllpass {
MKAllpass::MKAllpass() {
mCalcFunc = make_calc_function<MKAllpass, &MKAllpass::next>();
writephase = 0;
maxdelay = 1.0; // Seconds
bufsize = NEXTPOWEROFTWO((float)sampleRate() * maxdelay);
// Don't forget to free this in the destructor
delaybuffer = (float *)RTAlloc(mWorld, bufsize * sizeof(float));
historybuffer = (float *)RTAlloc(mWorld, bufsize * sizeof(float));
// Used to wrap the phase using bit masking
mask = bufsize - 1;
// This check makes sure that RTAlloc succeeded. (It might fail if there's
// not enough memory.) If you don't do this check properly then YOU CAN
// CRASH THE SERVER! A lot of ugens in core and sc3-plugins fail to do this.
// Don't follow their example.
if (delaybuffer == NULL || historybuffer == NULL) {
mCalcFunc = make_calc_function<MKAllpass, &MKAllpass::clear>();
// Clear outputs of the ugen, just in case
clear(1);
if (mWorld->mVerbosity > -2) {
Print("Failed to allocate memory for MKAllpass ugen.\n");
}
return;
}
// Fill the buffer with zeros.
memset(delaybuffer, 0, bufsize * sizeof(float));
memset(historybuffer, 0, bufsize * sizeof(float));
next(1);
}
void MKAllpass::clear(int inNumSamples) {
ClearUnitOutputs(this, inNumSamples);
}
MKAllpass::~MKAllpass() {
RTFree(mWorld, delaybuffer);
RTFree(mWorld, historybuffer);
}
void MKAllpass::next(int nSamples) {
// Ugen ins and outs
const float *input = in(0);
float *output = out(0);
const float feedback = in0(1);
float delay = in0(2);
/* Print("feedback: %f\n", feedback); */
/* Print("delay: %f\n", delay); */
/* Print("mask: %d\n", mask); */
// Cap the delay
if (delay > maxdelay) {
delay = maxdelay;
}
// Calculate delay phase
int delayPhaseOffset = delay * (float)sampleRate();
/**
* Simple allpass filter with a flat long-term frequency response
* that delays various frequencies with various amounts
*
* y[n] = (-g * x[n]) + x[n - D] + (g * y[n - D])
*
* The delayed input x[n - D] is represented by a historybuffer
* The delayed output y[n - D] that is fed back is delayed by a
* delaybuffer
*
*/
for (int i = 0; i < nSamples; ++i) {
historybuffer[writephase] = input[i];
// The actual allpass calculation
const int historyphase = i - delayPhaseOffset;
const int delayphase = i - delayPhaseOffset;
const float filtered = ((-1.0 * feedback) * input[i]) +
historybuffer[historyphase & mask] +
(feedback * delaybuffer[delayphase & mask]);
// Calculate output
output[i] = zapgremlins(filtered);
// Write to buffer
delaybuffer[writephase] = output[i];
// the mask wraps the phase to the max delay size in samples:
writephase = (writephase + 1) & mask;
}
}
} // namespace MKAllpass
PluginLoad(MKAllpassUGens) {
// Plugin magic
ft = inTable;
registerUnit<MKAllpass::MKAllpass>(ft, "MKAllpass", false);
}