Public
Edited
Oct 1, 2023
1 fork
13 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// these setting values were derived using a 2013 macbook built-in microphone and playing music through the built-in speakers
settings = ({
// size
width: 900,
height: 425,
samples: 512, // number of frequency samples
frequency: { min: 10, max: 12500 }, // frequency range
frequencyBias: { min: 5, max: 50 }, // size of samples in hz, scaled by frequency
strokeWeight: { min: 3, max: 4 }, // thickness of each sample
scaling: { min: 3.25, max: 3.33 }, // multiply the value of each frequency sample by this weighted number
// fft
smoothing: { min: 0.7, max: 0.76, step: 0.05 }, // multiple FFT instances with different smoothing settings
bins: 2048, // fidelity of each FFT instance (must be power of 2)
history: Math.round(60 * 1.5), // number of frames to remember activity for scaling
useLowPass: false // filter-out lower-frequency sounds
})
Insert cell
Insert cell
function setup(p, p5, context) {
const { width, height, bins, useLowPass, smoothing, samples } = settings;
p.createCanvas(width, height);
p.colorMode(p.HSB, 360, 100, 100, 1.0);
p.textAlign(p.RIGHT, p.TOP);
context.audioIn = new p5.AudioIn();
context.audioIn.start();
if (useLowPass) {
context.lowPass = new p5.LowPass();
context.lowPass.disconnect();
context.audioIn.connect(context.lowPass);
}
//
context.ffts = [];
for (let s = smoothing.min; s <= smoothing.max; s += smoothing.step) {
let fft = new p5.FFT(s, bins);
fft.setInput(context.audioIn);
context.ffts.push(fft);
console.log({ smooth: s });
}
context.data = context.ffts.map(fft => Array(samples).fill(0));
context.maxs = Array(settings.history).fill(0);
//
console.log(context);
}
Insert cell
function update(p, p5, context) {
const { frequency, samples, scaling, history, frequencyBias } = settings;
const { ffts, data, maxs } = context;
p.getAudioContext().resume();
ffts.forEach(fft => fft.analyze());
let fr = (frequency.max - frequency.min) / samples / 2;
let m = 0;
ffts.forEach((fft, i) => {
data[i].forEach((x, i, a) => {
let f = p.map(i, 0, samples, frequency.min, frequency.max);
let fr = p.map(i, 0, samples, frequencyBias.min, frequencyBias.max);
let s = p.map(f, frequency.min, frequency.max, scaling.min, scaling.max);
let e = fft.getEnergy(f - fr, f + fr) ** s;
a[i] = e;
if (e > m) m = e;
});
});
data.forEach(d => normalize(d, true));
maxs[p.frameCount % history] = m;
context.current = m;
}
Insert cell
function draw(p, p5, context) {
const { strokeWeight: sw } = settings;
const { data, maxs, current } = context;
p.background(0);
const maxY = p.height / 2.25;
const a = 1 / data.length;
const r = current / max(maxs);
let pow = 1.25;
let powMax = p.pow(1, 1 / pow);
data.forEach(d => {
d.forEach((v, i) => {
let n = p.map(i, 0, d.length, 0, 1);
let pv = p.pow(n, 1 / pow);
let x = p.map(pv, 0, powMax, 5, p.width - 5);
//let x = p.map(i, 0, d.length, 5, p.width - 5);
let y1 = p.map(v, 0, 1, 0, maxY);
let y2 = r * y1;
let y = (y1 + y2) / 2;
let h = p.map(i, 0, d.length, 0, 360);
let s = 100;
let b = p.map(p.sqrt(v), 0, 1, 25, 100);
p.strokeWeight(p.map(r, 0, 1, sw.min, sw.max) + p.map(n, 0, 1, 1, 0));
p.stroke(h, s, b, a);
p.line(x, p.height / 2 + y, x, p.height / 2 - y);
});
});
p.noStroke();
p.fill(0, 0, 100);
p.text(`FPS: ${Math.round(p.frameRate())}`, p.width - 5, 5);
p.text(`LVL: ${Math.min(99, Math.round(100 * r))}`, p.width - 5, 15);
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more