thetascape-sound-engine
Version:
A sound engine by Jason Snell
358 lines (357 loc) • 12.3 kB
JavaScript
import * as m from "tone";
import B, { useRef as x, useMemo as V, useEffect as T } from "react";
import W from "react-p5";
const h = Object.freeze({
INTRO: 0,
DELTA: 1,
THETA: 2,
ALPHA: 3,
BETA: 4,
GAMMA: 5
}), R = Object.freeze(
[2.5, 2.5, 5, 10, 20, 40]
);
class E {
constructor(t, i, e, r) {
console.log("VisualizerBar: constructor", t, i, e, r), this.brainwaveID = t, this.barWidth = i, this.containerHeight = e, this.colors = r, this.carrierValue = 0, this.carrierAmp = 1, this.modulatorValue = 0.5, this.currentHeight = e, this.currentOffset = 0, this.gradient = null, this.lastGradientHeight = null, this._configureBrainwave(t);
}
// Just factor out the switch into a helper
_configureBrainwave(t) {
switch (t) {
case 1:
this.carrierAmp = 1.1, this.animSmoothing = 5, this.minWaveHeight = this.containerHeight * 0.8, this.maxWaveHeight = this.containerHeight * 1.1, this.modulationStrength = 0.12;
break;
case 2:
this.carrierAmp = 1.1, this.animSmoothing = 4, this.minWaveHeight = this.containerHeight * 0.85, this.maxWaveHeight = this.containerHeight * 1.1, this.modulationStrength = 0.05;
break;
case 3:
this.carrierAmp = 1.45, this.animSmoothing = 2, this.minWaveHeight = this.containerHeight * 1.25, this.maxWaveHeight = this.containerHeight * 1.55, this.modulationStrength = 0.075;
break;
case 4:
this.carrierAmp = 1.2, this.animSmoothing = 3, this.minWaveHeight = this.containerHeight * 1.55, this.maxWaveHeight = this.containerHeight * 4, this.modulationStrength = 0.2;
break;
case 5:
this.carrierAmp = 1.2, this.animSmoothing = 1, this.minWaveHeight = this.containerHeight * 1.1, this.maxWaveHeight = this.containerHeight * 2.75, this.modulationStrength = 0.115;
break;
//intro state
default:
this.carrierAmp = 1, this.animSmoothing = 1, this.minWaveHeight = this.containerHeight, this.maxWaveHeight = this.containerHeight, this.modulationStrength = 0;
break;
}
}
reset(t, i, e) {
this.brainwaveID = t, this.containerHeight = i, this.colors = e, this.carrierValue = 0, this.modulatorValue = 0.5, this.currentHeight = i, this.currentOffset = 0, this.gradient = null, this.lastGradientHeight = null, this._configureBrainwave(t);
}
setValues(t, i) {
this.carrierValue = t * this.carrierAmp, this.modulatorValue = i;
}
updateColors(t) {
this.colors = t, this.gradient = null;
}
update(t) {
const i = 1 / this.animSmoothing, e = this.maxWaveHeight - this.carrierValue * (this.maxWaveHeight - this.minWaveHeight);
this.currentHeight += (e - this.currentHeight) * i;
const r = this.modulatorValue - 0.5, a = this.containerHeight * this.modulationStrength, u = r * 2 * a;
this.currentOffset += (u - this.currentOffset) * i;
}
draw(t, i, e) {
if (this.currentHeight < 1) return;
t.push(), t.translate(i, e + this.currentOffset);
const r = Math.abs(this.currentHeight - this.lastGradientHeight);
(!this.gradient || r > 5) && (this.gradient = t.drawingContext.createLinearGradient(
0,
-this.currentHeight / 2,
0,
this.currentHeight / 2
), this.gradient.addColorStop(0, this.colors[0]), this.gradient.addColorStop(0.25, this.colors[1]), this.gradient.addColorStop(0.5, this.colors[2]), this.gradient.addColorStop(0.75, this.colors[3]), this.gradient.addColorStop(1, this.colors[4]), this.lastGradientHeight = this.currentHeight), t.drawingContext.fillStyle = this.gradient, t.noStroke(), t.rect(-this.barWidth / 2, -this.currentHeight / 2, this.barWidth, this.currentHeight), t.pop();
}
}
class k {
constructor(t, i, e, r, a, u, M) {
this.p5 = t, this.bars = [];
const f = e * (r + a), g = (t.width - f) / 2 + a / 2;
for (let s = 0; s < e; s++) {
const d = g + s * (r + a), c = new E(i, r, u, M);
this.bars.push({ bar: c, x: d });
}
}
resetAllBars(t, i, e) {
for (const { bar: r } of this.bars)
r.reset(t, i, e);
}
updateColors(t) {
for (const { bar: i } of this.bars)
i.updateColors?.(t);
}
updateAndDraw(t, i) {
if (t.length !== i.length) return;
const { p5: e } = this;
for (let r = 0; r < this.bars.length; r++) {
const { bar: a, x: u } = this.bars[r];
a.setValues(t[r] || 0, i[r] || 0.5), a.update(e), a.draw(e, u, e.height / 2);
}
}
}
const P = {
getColors(l) {
switch (l) {
case h.DELTA:
return {
barColors: [
"#1d262d",
"#0468b8",
"#bbd0e0",
"#43b3d3",
"#202025"
],
bgColors: [
"#1d262d",
"#15445d",
"#202025"
]
};
case h.THETA:
return {
barColors: [
"#22242f",
"#7d82a6",
"#f8f8fa",
"#5d6393",
"#1f1f24"
],
bgColors: [
"#22242f",
"#373e73",
"#1f1f24"
]
};
case h.ALPHA:
return {
barColors: [
"#01a2cb",
"#45bbd9",
"#ffffff",
"#038ecf",
"#0398cf"
],
bgColors: [
"#01a2cb",
"#e1eff3",
"#0398cf"
]
};
case h.BETA:
return {
barColors: [
"#0072CE",
"#127cd1",
"#ffffff",
"#00a2cb",
"#022d97"
],
bgColors: [
"#0072CE",
"#a9cfee",
"#022d97"
]
};
case h.GAMMA:
return {
barColors: [
"#168cb9",
"#168cb9",
"#ffffff",
"#5e6ac9",
"#5e6ac9"
],
bgColors: [
"#168cb9",
"#ffffff",
"#5e6ac9"
]
};
default:
return {
barColors: [
"#8A8FB6",
"#aab5bf",
"#D7E4F0",
"#aab5bf",
"#8A8FB6"
],
bgColors: [
"#8A8FB6",
"#D7E4F0",
"#8A8FB6"
]
};
}
}
}, q = {
processMeters: (l, t, i = [0, 16], e = !1) => {
const r = l(), a = Object.values(r), [u, M] = i, f = a.slice(u, M), g = [], s = f.length - 1, d = -135, c = -80, H = (o) => Math.max(0, Math.min(1, o)), n = (o) => H((o - d) / (c - d));
if (e) {
const o = Math.ceil(t / 2);
for (let p = 0; p < o; p++) {
const w = p / (o - 1 || 1) * s, A = Math.floor(w), S = Math.min(A + 1, s), C = w - A, z = f[A] * (1 - C) + f[S] * C, v = n(z);
g[p] = v, g[t - 1 - p] = v;
}
return g;
} else {
for (let o = 0; o < t; o++) {
const b = o / (t - 1 || 1) * s, w = Math.floor(b), A = Math.min(w + 1, s), S = b - w, C = f[w] * (1 - S) + f[A] * S, z = n(C);
g.push(z);
}
return g;
}
}
};
class O {
constructor(t) {
this.barTotal = t, this.phase = 0, this.freqModPhase = 0, this.ampModPhase = 0, this.widthModPhase = 0, this.freqModSpeed = 0, this.ampModSpeed = 0, this.widthModSpeed = 0, this.ampMultiplier = 1, this.widthMultiplier = 1, this.minHz = 1, this.maxHz = 1, this.speed = 0, this._ampModValue = 1, this._widthModValue = 1, this.active = !1, this._idleArray = new Array(this.barTotal).fill(0.5);
}
setBrainwave(t) {
const e = {
[h.DELTA]: {
minHz: 0.075,
maxHz: 0.175,
amp: 0.8,
width: 0.04,
freq: 3e-3,
ampS: 4e-3,
widthS: 3e-3
},
[h.THETA]: {
minHz: 0.175,
maxHz: 0.3,
amp: 1.2,
width: 0.05,
freq: 4e-3,
ampS: 5e-3,
widthS: 4e-3
},
[h.ALPHA]: {
minHz: 0.2,
maxHz: 0.4,
amp: 1.5,
width: 0.05,
freq: 5e-3,
ampS: 1e-3,
widthS: 25e-4
},
[h.BETA]: {
minHz: 2.2,
maxHz: 3.3,
amp: 1.5,
width: 1.5,
freq: 1e-3,
ampS: 0,
widthS: 1e-3
},
[h.GAMMA]: {
minHz: 1.1,
maxHz: 1.6,
amp: 1.025,
width: 0.75,
freq: 1e-3,
ampS: 1e-3,
widthS: 0
}
}[t];
if (!e) {
console.warn("Unknown brainwave ID:", t);
return;
}
this.minHz = e.minHz, this.maxHz = e.maxHz, this.ampMultiplier = e.amp, this.widthMultiplier = e.width, this.freqModSpeed = e.freq, this.ampModSpeed = e.ampS, this.widthModSpeed = e.widthS, this.active = !0;
}
_computeSpeed(t) {
const i = 8 / t;
return 2 * Math.PI / i;
}
update() {
if (!this.active) return;
this.freqModPhase += this.freqModSpeed, this.ampModPhase += this.ampModSpeed, this.widthModPhase += this.widthModSpeed, this.freqModPhase > Math.PI * 2 && (this.freqModPhase -= Math.PI * 2), this.ampModPhase > Math.PI * 2 && (this.ampModPhase -= Math.PI * 2), this.widthModPhase > Math.PI * 2 && (this.widthModPhase -= Math.PI * 2);
const t = Math.sin(this.freqModPhase) * 0.5 + 0.5, i = Math.sin(this.ampModPhase) * 0.5 + 0.5, e = Math.sin(this.widthModPhase) * 0.5 + 0.5, r = this.minHz + t * (this.maxHz - this.minHz);
this.speed = this._computeSpeed(r), this._ampModValue = i * this.ampMultiplier, this._widthModValue = 1 + e * this.widthMultiplier, this.phase += this.speed, this.phase > Math.PI * 2 && (this.phase -= Math.PI * 2);
}
getModulatorWave() {
if (!this.active) return this._idleArray;
const t = this._ampModValue, i = this._widthModValue, e = new Array(this.barTotal), r = Math.PI * 2;
for (let a = 0; a < this.barTotal; a++) {
const M = a / this.barTotal * r * i;
e[a] = Math.sin(this.phase + M) * 0.5 * t + 0.5;
}
return e;
}
}
const L = ({ getLevels: l, brainwaveID: t }) => {
console.log("Visualizer: brainwaveID", t);
const i = x(null), e = x(null), r = x(null), a = x(!1), u = V(() => P.getColors(t), [t]);
T(() => {
if (e.current && e.current.setBrainwave(t), a.current = !1, i.current) {
const { p5: s } = i.current, d = s.height, c = P.getColors(t).barColors;
i.current.resetAllBars(t, d, c);
}
}, [t]);
const M = (s, d) => {
s.createCanvas(s.windowWidth, s.windowHeight).parent(d), s.frameRate(8), s.noSmooth();
const c = 2, H = 11, n = Math.floor(s.width / (c + H));
r.current = n, i.current = new k(
s,
t,
n,
c,
H,
s.height,
u.barColors
), e.current || (e.current = new O(n), e.current.setBrainwave(t));
}, f = (s) => {
const d = P.getColors(t).bgColors, c = s.drawingContext.createLinearGradient(0, 0, 0, s.height);
if (c.addColorStop(0, d[0]), c.addColorStop(0.5, d[1]), c.addColorStop(1, d[2]), s.drawingContext.fillStyle = c, s.noStroke(), s.rect(0, 0, s.width, s.height), a.current = !0, !i.current || !l || !e.current) return;
const H = l();
let n = [0, 16], o = !1;
switch (t) {
case h.DELTA:
n = [0, 4], o = !0;
break;
case h.THETA:
n = [2, 8], o = !0;
break;
case h.ALPHA:
n = [2, 10], o = !0;
break;
case h.BETA:
n = [4, 12], o = !1;
break;
case h.GAMMA:
n = [6, 16], o = !0;
break;
default:
n = [0, 16], o = !0;
break;
}
const p = q.processMeters(() => H, i.current.bars.length, n, o);
e.current.update();
const b = e.current.getModulatorWave();
i.current.updateAndDraw(p, b);
}, g = (s) => {
s.resizeCanvas(s.windowWidth, s.windowHeight);
};
return /* @__PURE__ */ B.createElement(W, { setup: M, draw: f, windowResized: g });
}, _ = async () => {
console.log("APP: Init ToneJS and Audio Engine");
const l = new m.Context(
{ latencyHint: "playback" }
);
m.setContext(l), console.log("AUDIO: Tone.Context: Latency Hint:", m.getContext().latencyHint, "| Sample Rate:", m.getContext().sampleRate), m.getContext().rawContext.suspend(), await m.start(), console.log("AUDIO: Tone started", m.getContext().state, m);
const { default: t } = await import("./AudioEngine-BGZy8HUE.js");
return new t(m);
}, F = {
init: _
};
export {
R as SongFrequencies,
h as SongIDs,
L as Visualizer,
F as default
};