Public
Edited
Oct 5, 2024
2 stars
Insert cell
Insert cell
Insert cell
Insert cell
settings = ({
width: width,
height: width / 2,
smoothing: 0.945, // 0.939, 0.925, 0.91
bins: 2048,
samples: 1024,
scaling: 6.7, // 5.1, 3.9, 3.33, 4.1, 4.25,
history: Math.round(60 * 0.25), // 1.25, 1.5, 0.25, 0.66, 0.33, 3
maxFrequency: 16000
})
Insert cell
Insert cell
Insert cell
Insert cell
function draw(sketch = mutable sketch, p5 = mutable p5) {
updateFps();

sketch.getAudioContext().resume();
mutable fft.analyze();

updateMelScaledSamples();
updateLoudnessCorrection();
sketch.background(0);
sketch.strokeWeight(2);

mutable maxEnergy[sketch.frameCount % mutable maxEnergy.length] =
mutable data.reduce(
(max, sample) => Math.max(max, sample.correctedEnergy),
0
);
const maxCorrectedEnergy = Math.max(...mutable maxEnergy);
const avgCorrectedEnergy = mutable maxEnergy.reduce(
(a, c, i, arr) => a + c / arr.length,
0
);
const lowCorrectedEnergy = Math.min(maxCorrectedEnergy, avgCorrectedEnergy);
const highCorrectedEnergy = Math.max(maxCorrectedEnergy, avgCorrectedEnergy);
const midCorrectedEnergy = (lowCorrectedEnergy + highCorrectedEnergy) / 2;
const avgCorrectedEnergySqrt = Math.sqrt(avgCorrectedEnergy);

const c = sketch.height / 2;
for (let i = 0; i < mutable data.length; i++) {
const { correctedEnergy, startHz } = mutable data[i];
const x = sketch.map(i, 0, mutable data.length, 0, sketch.width);
const r = sketch.map(correctedEnergy, 0, lowCorrectedEnergy, 0, c);

if (r < 1) continue;

sketch.strokeWeight(
sketch.map(correctedEnergy, 0, avgCorrectedEnergy, 1, 3)
);

sketch.stroke(
sketch.map(startHz, 0, settings.maxFrequency, 0, 360),
100,
sketch.map(Math.sqrt(correctedEnergy), 0, avgCorrectedEnergySqrt, 33, 100)
);

sketch.line(x, c - r, x, c + r);
}
}
Insert cell
Insert cell
function updateMelScaledSamples(
sketch = mutable sketch,
fft = mutable fft,
data = mutable data,
samples = settings.samples,
scaling = settings.scaling,
maxFrequency = settings.maxFrequency
) {
const maxMel = hzToMel(maxFrequency);
const melStep = maxMel / samples;

for (let i = 0; i < samples; i++) {
if (!data[i].startMel) data[i].startMel = i * melStep;
if (!data[i].endMel) data[i].endMel = (i + 1) * melStep;

if (!data[i].startHz) {
data[i].startHz = Math.min(maxFrequency, melToHz(data[i].startMel));
data[i].startHz = Math.max(1, data[i].startHz);
}

if (!data[i].endHz) {
data[i].endHz = Math.min(maxFrequency, melToHz(data[i].endMel));
data[i].endHz = Math.max(1, data[i].endHz);
}

data[i].energy = Math.pow(
fft.getEnergy(data[i].startHz, data[i].endHz),
scaling
);
}
}
Insert cell
function updateLoudnessCorrection(data = mutable data) {
for (let i = 0; i < data.length; i++) {
// disabled
data[i].correctedEnergy = data[i].energy;
continue;

const centerFreq = (data[i].startHz + data[i].endHz) / 2;
const correction = aWeighting(centerFreq);
data[i].correctedEnergy = data[i].energy * Math.pow(10, correction / 20);
}
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function hzToBark(hz) {
return 13 * Math.atan(0.00076 * hz) + 3.5 * Math.atan(Math.pow(hz / 7500, 2));
}
Insert cell
function barkToHz(bark) {
return 600 * Math.sinh(bark / 6);
}
Insert cell
function hzToMel(hz) {
return 2595 * Math.log10(1 + hz / 700);
}
Insert cell
function melToHz(mel) {
return 700 * (Math.pow(10, mel / 2595) - 1);
}
Insert cell
function aWeighting(freq) {
const c1 = 12194.217 ** 2;
const c2 = 20.598997 ** 2;
const c3 = 107.7172 ** 2;
const c4 = 737.86223 ** 2;
const freqSq = freq ** 2;

const numerator = c1 * freqSq * freqSq;
const denominator = (freqSq + c2) * Math.sqrt(freqSq + c3) * (freqSq + c4);
const gain = numerator / denominator;

return 2.0 + 20 * Math.log10(gain);
}
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