--- /dev/null
+/**
+ * This is a reusable equalizer class that lets you get averaged
+ * bands with dB scaling and smoothing.
+ */
+public static class GraphicEQ {
+
+ private final HeronLX lx;
+
+ public final BasicParameter level = new BasicParameter("LVL", 0.5);
+ public final BasicParameter range = new BasicParameter("RNGE", 0.5);
+ public final BasicParameter slope = new BasicParameter("SLOP", 0.5);
+ public final BasicParameter attack = new BasicParameter("ATK", 0.5);
+ public final BasicParameter release = new BasicParameter("REL", 0.5);
+
+ private final FFT fft;
+ private final int numBands;
+
+ private final LinearEnvelope[] bandVals;
+
+ public final static int DEFAULT_NUM_BANDS = 16;
+
+ public GraphicEQ(HeronLX lx) {
+ this(lx, DEFAULT_NUM_BANDS);
+ }
+
+ /**
+ * Note that the number of bands is a suggestion. Due to the FFT implementation
+ * the actual number may be slightly different.
+ */
+ public GraphicEQ(HeronLX lx, int num) {
+ this.lx = lx;
+ fft = new FFT(lx.audioInput().bufferSize(), lx.audioInput().sampleRate());
+ fft.window(FFT.HAMMING);
+ fft.logAverages(50, num/8);
+ numBands = this.fft.avgSize();
+ bandVals = new LinearEnvelope[numBands];
+ for (int i = 0; i < bandVals.length; ++i) {
+ bandVals[i] = new LinearEnvelope(0, 0, 500).trigger();
+ }
+ }
+
+ final float logTen = log(10);
+ public float log10(float val) {
+ return log(val) / logTen;
+ }
+
+ public float getLevel(int band) {
+ return bandVals[band].getValuef();
+ }
+
+ public float getAverageLevel(int minBand, int numBands) {
+ float avg = 0;
+ for (int i = minBand; i < minBand + numBands; ++i) {
+ avg += bandVals[i].getValuef();
+ }
+ avg /= numBands;
+ return avg;
+ }
+
+ public void run(int deltaMs) {
+ fft.forward(lx.audioInput().mix);
+ float zeroDBReference = pow(10, 100*(1-level.getValuef())/20.);
+ float decibelRange = 12 + range.getValuef() * 60;
+ float decibelSlope = slope.getValuef() * 60.f / numBands;
+ for (int i = 0; i < numBands; ++i) {
+ float raw = fft.getAvg(i);
+ float decibels = 20*log10(raw / zeroDBReference);
+ float positiveDecibels = decibels + decibelRange;
+ positiveDecibels += i*decibelSlope;
+ float value = constrain(positiveDecibels / decibelRange, 0, 1);
+
+ if (value > bandVals[i].getValuef()) {
+ bandVals[i].setEndVal(value, attack.getValuef() * 20).trigger();
+ }
+ }
+ for (LinearEnvelope band : bandVals) {
+ band.run(deltaMs);
+ if (!band.isRunning() && band.getValuef() > 0) {
+ band.setEndVal(0, release.getValuef() * 1600).trigger();
+ }
+ }
+ }
+}
+
+
}
}
+class BassPod extends SCPattern {
+
+ private GraphicEQ eq = null;
+
+ public BassPod(GLucose glucose) {
+ super(glucose);
+ }
+
+ protected void onActive() {
+ if (eq == null) {
+ eq = new GraphicEQ(lx, 16);
+ eq.slope.setValue(0.6);
+ addParameter(eq.level);
+ addParameter(eq.range);
+ addParameter(eq.attack);
+ addParameter(eq.release);
+ addParameter(eq.slope);
+ }
+ }
+
+ public void run(int deltaMs) {
+ eq.run(deltaMs);
+
+ float bassLevel = eq.getAverageLevel(0, 5);
+
+ for (Point p : model.points) {
+ int avgIndex = (int) constrain(1 + abs(p.fx-model.xMax/2.)/(model.xMax/2.)*(eq.numBands-5), 0, eq.numBands-5);
+ float value = 0;
+ for (int i = avgIndex; i < avgIndex + 5; ++i) {
+ value += eq.getLevel(i);
+ }
+ value /= 5.;
+
+ float b = constrain(8 * (value*model.yMax - abs(p.fy-model.yMax/2.)), 0, 100);
+ colors[p.index] = color(
+ (lx.getBaseHuef() + abs(p.fy - model.cy) + abs(p.fx - model.cx)) % 360,
+ constrain(bassLevel*240 - .6*dist(p.fx, p.fy, model.cx, model.cy), 0, 100),
+ b
+ );
+ }
+ }
+}
+
+
class CubeEQ extends SCPattern {
- private FFT fft = null;
- private LinearEnvelope[] bandVals = null;
- private int avgSize;
+ private GraphicEQ eq = null;
- private final BasicParameter thrsh = new BasicParameter("LVL", 0.35);
- private final BasicParameter range = new BasicParameter("RANG", 0.45);
private final BasicParameter edge = new BasicParameter("EDGE", 0.5);
- private final BasicParameter speed = new BasicParameter("SPD", 0.5);
- private final BasicParameter tone = new BasicParameter("TONE", 0.5);
private final BasicParameter clr = new BasicParameter("CLR", 0.5);
+ private final BasicParameter blockiness = new BasicParameter("BLK", 0.5);
public CubeEQ(GLucose glucose) {
super(glucose);
- addParameter(thrsh);
- addParameter(range);
- addParameter(edge);
- addParameter(speed);
- addParameter(tone);
- addParameter(clr);
}
protected void onActive() {
- if (this.fft == null) {
- this.fft = new FFT(lx.audioInput().bufferSize(), lx.audioInput().sampleRate());
- this.fft.window(FFT.HAMMING);
- this.fft.logAverages(40, 1);
- this.avgSize = this.fft.avgSize();
- this.bandVals = new LinearEnvelope[this.avgSize];
- for (int i = 0; i < this.bandVals.length; ++i) {
- this.addModulator(this.bandVals[i] = (new LinearEnvelope(0, 0, 700+i*4))).trigger();
- }
+ if (eq == null) {
+ eq = new GraphicEQ(lx, 16);
+ addParameter(eq.level);
+ addParameter(eq.range);
+ addParameter(eq.attack);
+ addParameter(eq.release);
+ addParameter(eq.slope);
+ addParameter(edge);
+ addParameter(clr);
+ addParameter(blockiness);
}
}
public void run(int deltaMs) {
- this.fft.forward(this.lx.audioInput().mix);
- float toneConst = .35 + .4 * (tone.getValuef() - 0.5);
- float edgeConst = 2 + 30*(edge.getValuef()*edge.getValuef()*edge.getValuef());
-
- for (int i = 0; i < avgSize; ++i) {
- float value = this.fft.getAvg(i);
- value = 20*log(1 + sqrt(value));
- float sqdist = avgSize - i;
- value -= toneConst*sqdist*sqdist + .5*sqdist;
- value *= 6;
- if (value > this.bandVals[i].getValue()) {
- this.bandVals[i].setEndVal(value, 40).trigger();
- }
- else {
- this.bandVals[i].setEndVal(value, 1000 - 900*speed.getValuef()).trigger();
- }
- }
+ eq.run(deltaMs);
- float jBase = 120 - 360*thrsh.getValuef();
- float jConst = 300.*(1-range.getValuef());
+ float edgeConst = 2 + 30*edge.getValuef();
float clrConst = 1.1 + clr.getValuef();
for (Point p : model.points) {
- float avgIndex = constrain((p.fx / model.xMax * avgSize), 0, avgSize-2);
+ float avgIndex = constrain(2 + p.fx / model.xMax * (eq.numBands-4), 0, eq.numBands-4);
int avgFloor = (int) avgIndex;
- float j = jBase + jConst * (p.fy / model.yMax);
- float value = lerp(
- this.bandVals[avgFloor].getValuef(),
- this.bandVals[avgFloor+1].getValuef(),
- avgIndex-avgFloor
- );
- float b = constrain(edgeConst * (value - j), 0, 100);
+ float leftVal = eq.getLevel(avgFloor);
+ float rightVal = eq.getLevel(avgFloor+1);
+ float smoothValue = lerp(leftVal, rightVal, avgIndex-avgFloor);
+
+ float chunkyValue = (
+ eq.getLevel(avgFloor/4*4) +
+ eq.getLevel(avgFloor/4*4 + 1) +
+ eq.getLevel(avgFloor/4*4 + 2) +
+ eq.getLevel(avgFloor/4*4 + 3)
+ ) / 4.;
+
+ float value = lerp(smoothValue, chunkyValue, blockiness.getValuef());
+
+ float b = constrain(edgeConst * (value*model.yMax - p.fy), 0, 100);
colors[p.index] = color(
- (480 + lx.getBaseHuef() - min(clrConst*p.fy, 120)) % 360,
- 100,
- b);
+ (480 + lx.getBaseHuef() - min(clrConst*p.fy, 120)) % 360,
+ 100,
+ b
+ );
}
}
}
}
}
+class Traktor extends SCPattern {
+
+ final int FRAME_WIDTH = 60;
+
+ final BasicParameter speed = new BasicParameter("SPD", 0.5);
+
+ private float[] bass = new float[FRAME_WIDTH];
+ private float[] treble = new float[FRAME_WIDTH];
+
+ private int index = 0;
+ private GraphicEQ eq = null;
+
+ public Traktor(GLucose glucose) {
+ super(glucose);
+ for (int i = 0; i < FRAME_WIDTH; ++i) {
+ bass[i] = 0;
+ treble[i] = 0;
+ }
+ addParameter(speed);
+ }
+
+ public void onActive() {
+ if (eq == null) {
+ eq = new GraphicEQ(lx, 16);
+ eq.slope.setValue(0.6);
+ eq.level.setValue(0.65);
+ eq.range.setValue(0.35);
+ eq.release.setValue(0.4);
+ addParameter(eq.level);
+ addParameter(eq.range);
+ addParameter(eq.attack);
+ addParameter(eq.release);
+ addParameter(eq.slope);
+ }
+ }
+
+ int counter = 0;
+
+ public void run(int deltaMs) {
+ eq.run(deltaMs);
+
+ int stepThresh = (int) (40 - 39*speed.getValuef());
+ counter += deltaMs;
+ if (counter < stepThresh) {
+ return;
+ }
+ counter = counter % stepThresh;
+
+ index = (index + 1) % FRAME_WIDTH;
+
+ float rawBass = eq.getAverageLevel(0, 4);
+ float rawTreble = eq.getAverageLevel(eq.numBands-7, 7);
+
+ bass[index] = rawBass * rawBass * rawBass * rawBass;
+ treble[index] = rawTreble * rawTreble;
+
+ for (Point p : model.points) {
+ int i = (int) constrain((model.xMax - p.x) / model.xMax * FRAME_WIDTH, 0, FRAME_WIDTH-1);
+ int pos = (index + FRAME_WIDTH - i) % FRAME_WIDTH;
+
+ colors[p.index] = color(
+ (360 + lx.getBaseHuef() + .8*abs(p.x-model.cx)) % 360,
+ 100,
+ constrain(9 * (bass[pos]*model.cy - abs(p.fy - model.cy)), 0, 100)
+ );
+ colors[p.index] = blendColor(colors[p.index], color(
+ (400 + lx.getBaseHuef() + .5*abs(p.x-model.cx)) % 360,
+ 60,
+ constrain(5 * (treble[pos]*.6*model.cy - abs(p.fy - model.cy)), 0, 100)
+
+ ), ADD);
+ }
+ }
+}