Thanks for all the help guys! I’ve decided to apply two of the fixes suggested: For my per-oscillator filters I decided to switch to the state variable filters found in the new dsp classes. For any other new users, the code to start one up goes like this:
dsp::StateVariableFilter::Filter<double> filter = new dsp::StateVariableFilter::Filter<double>();
filter->parameters->type = dsp::StateVariableFilter::Parameters<double>::Type::lowPass; //or whatever other type of filter you want
filter->parameters->setCutOffFrequency(getSampleRate(), FILTER_FREQUENCY);
For my main section filter, which I don’t expect to be automated as often, and instead am more in need of the added functionality the IIR filter class provides (like the low shelf and high shelf filters) I’ve made a class that divides a range of values into a number of steps (as defined by a function, like a linear function or logarithmic one) and steps from the maximum value to the minimum one. It’s more or less eliminated pops when I don’t have any resonance applied, and I’ve decided that the pop that happens with a high resonance filter going from high filter frequencies to low filter frequencies is a feature rather than a bug, because I happens predictably and can probably be used to make kicks or subs or something. (Lazy, I know.)
If anyone is interested in these classes, feel free to use them under CC-BY-SA 2.0. which basically means you can do whatever with them. Note that these classes are a little dangerous, in that I don’t do much checking for unexpected values.
RangeStepper.h
/*
==============================================================================
RangeStepper.h
Created: 21 Jul 2017 2:11:43pm
Author: Gabriel
==============================================================================
*/
#pragma once
#include "../JuceLibraryCode/JuceHeader.h"
class RangeStepper {
public:
friend class SubRange;
enum RangeType {
Linear,
Exponential,
Logarithmic,
};
class SubRange {
public:
virtual double stepVal() = 0;
bool isFinished() { return finished; };
protected:
SubRange(RangeStepper* parent) : p(parent) {};
~SubRange() {};
virtual void startRange(int start, int end, float endVal) = 0;
bool finished;
RangeStepper* p;
};
RangeStepper(double min, double max, int steps, RangeType t, int LogarithmOrExponentBase = 2);
~RangeStepper();
SubRange* returnSubRange(double curr, double target);
private:
void generateRange(RangeType t);
int mapInputToRange(double in);
class SubRangeStepper : public SubRange {
public:
SubRangeStepper(RangeStepper* parent);
~SubRangeStepper();
void startRange(int start, int end, float endVal) override;
double stepVal() override;
private:
int startIndex, endIndex, currIndex, direction;
double endValue;
};
double minimum, maximum;
int stepsInRange;
int base;
ScopedPointer<SubRangeStepper> sR;
double* range;
RangeType thisType;
};
RangeStepper.cpp
/*
==============================================================================
RangeStepper.cpp
Created: 21 Jul 2017 2:11:43pm
Author: Gabriel
==============================================================================
*/
#include "RangeStepper.h"
/**
* Creates a RangeStepper, which will contain an array of doubles from "min" to "max" in "steps" steps, using the RangeType "t"
* For this RangeStepper to work in logarithmic mode, min and max must be greater than zero
* LogarithmOrExponentBase is the base used for generating exponential and logarithmic functions. It must be greater than 0.
*/
RangeStepper::RangeStepper(double min, double max, int steps, RangeType t, int LogarithmOrExponentBase) {
jassert(min != max);
jassert(steps > 0);
jassert(LogarithmOrExponentBase > 0);
if (min != max && min > 0 && max > 0 && steps > 0 && LogarithmOrExponentBase > 0) {
range = new double[steps + 1];
minimum = min;
maximum = max;
stepsInRange = steps;
base = LogarithmOrExponentBase;
generateRange(t);
thisType = t;
sR = new SubRangeStepper(this);
}
}
RangeStepper::~RangeStepper(){
if (range) {
delete[] range;
}
}
/**
* Generates the range of value for this RangeStepper
*/
void RangeStepper::generateRange(RangeType t) {
double holdval;
switch (t) {
//generate a linear range
case(Linear):
for (int i = 0; i <= stepsInRange; i++) {
range[i] = i * (maximum - minimum) / stepsInRange + minimum;
}
break;
//generate an exponential range
case(Exponential):
for (int i = 0; i < stepsInRange; i++) {
range[i] = pow((double)i / (double)stepsInRange, base) * (maximum - minimum) + minimum;
}
break;
case(Logarithmic):
jassert(maximum > 0 && minimum > 0);
if (maximum <= 0 || minimum <= 0) {
return;
}
holdval = log(maximum / minimum) / log(base);
for (int i = 0; i < stepsInRange; i++) {
range[i] = minimum * pow(base, i * holdval / stepsInRange);
}
break;
default:
break;
}
}
/**
* Quantises an input to a member of this RangeStepper's range
*/
int RangeStepper::mapInputToRange(double in) {
double temp = in; //In my personal code I use a utility function to return a "in" within a range from min to max. That would require code I have in another class so I removed it, but I'd suggest doing the same to avoid unexpected behavior/
double tempVal;
switch (thisType) {
//maps in to the closest member of this RangeStepper's linear range
case(Linear):
return (int) ((temp - minimum) * stepsInRange / (maximum - minimum));
break;
case(Exponential):
return (int) (pow((temp - minimum) / (maximum - minimum), 1 / base) * stepsInRange);
break;
case(Logarithmic):
jassert(maximum > 0 && minimum > 0);
tempVal = log(maximum / minimum) / log(base);
return (int)(log(temp / minimum) / log(base) * stepsInRange / tempVal);
break;
default:
return 0;
break;
}
}
/**
* Returns a SubRange that will step from curr to target across the intervals found in this RangeStepper
*/
RangeStepper::SubRange* RangeStepper::returnSubRange(double curr, double target) {
sR->startRange(mapInputToRange(curr), mapInputToRange(target), target);
return (SubRange*) sR.get();
}
//==============================================================================
/**
* A subset of a RangeStepper with a direction to iterate through the parent range
*/
RangeStepper::SubRangeStepper::SubRangeStepper(RangeStepper* parent) : SubRange(parent){
finished = true;
}
/**
* Destructor
*/
RangeStepper::SubRangeStepper::~SubRangeStepper() {
}
/**
* Starts a SubRange
*/
void RangeStepper::SubRangeStepper::startRange(int start, int end, float endVal) {
if (start == end) {
finished = true;
return;
}
finished = false;
currIndex = startIndex = start;
endIndex = end;
direction = start < end ? 1 : -1;
endValue = endVal;
}
/**
* Steps forward across the subrange
*/
double RangeStepper::SubRangeStepper::stepVal() {
if (finished) {
return endValue;
}
currIndex += direction;
if ((currIndex - endIndex) * direction < 0) {
return p->range[currIndex];
}
else {
finished = true;
return endValue;
}
}