Skip to content

Commit 2748be4

Browse files
author
devanshu
committed
fix: load waveform and octave in presets
1 parent 01af1cc commit 2748be4

File tree

6 files changed

+202
-93
lines changed

6 files changed

+202
-93
lines changed

index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
</head>
99
<body>
1010
<div id="root"></div>
11-
<script src="./dsp.js"></script>
11+
<script src="/dsp.js"></script>
1212
<script type="module" src="/src/main.jsx"></script>
1313
</body>
1414
</html>

src/audio/Voice.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33
* from note-on to note-off, including the ADSR envelope.
44
*/
55
export class Voice {
6-
constructor(audioContext, wasmModule, frequencies, adsr) {
6+
constructor(audioContext, wasmModule, frequencies, adsr, waveform = 'sine', octave = 4) {
77
this.audioContext = audioContext;
88
this.wasmModule = wasmModule;
99
this.frequencies = frequencies;
1010
this.adsr = adsr;
11+
this.waveform = waveform;
12+
this.octave = octave;
1113

1214
// Create the GainNode for ADSR volume control
1315
this.gainNode = this.audioContext.createGain();
@@ -26,6 +28,7 @@ export class Voice {
2628
const freqAmpPairs = new this.wasmModule.VectorVectorDouble();
2729
this.frequencies.forEach(([freq, amp]) => {
2830
const pair = new this.wasmModule.VectorDouble();
31+
const adjustedFreq = freq * Math.pow(2, this.octave - 4);
2932
pair.push_back(freq);
3033
pair.push_back(amp);
3134
freqAmpPairs.push_back(pair);

src/components/Equalizers.jsx

Lines changed: 78 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useState } from 'react';
1+
import { useState, useEffect, useRef } from 'react';
22
import Display from './Display';
33

44
/**
@@ -10,63 +10,92 @@ import Display from './Display';
1010
* width: number,
1111
* height: number}}
1212
*/
13-
function EQ({ wasmModule, width, height, freqs: liveFreqs }) {
13+
function EQ({ wasmModule, width, height, freqs: liveFreqs, eq, setEq }) {
1414
const xRange = [20, 20000];
1515

16-
// Initialize nodes and curves for the EQ
17-
const logXMin = Math.log(xRange[0]);
18-
const logXMax = Math.log(xRange[1]);
19-
const initialNodes = Array.from({ length: 5 }, (_, i) => ({
20-
x: Math.exp(logXMin + (i / 4) * (logXMax - logXMin)),
21-
y: 0.8
22-
}));
23-
const initialCurves = Array(initialNodes.length - 1).fill(0);
16+
const initialNodes = useRef(
17+
eq?.nodes?.length
18+
? eq.nodes
19+
: Array.from({ length: 5 }, (_, i) => ({
20+
x: Math.exp(Math.log(xRange[0]) + (i / 4) * (Math.log(xRange[1]) - Math.log(xRange[0]))),
21+
y: 0.8,
22+
}))
23+
).current;
2424

25-
const [nodes, setNodes] = useState(initialNodes);
26-
const [curves, setCurves] = useState(initialCurves);
25+
const initialCurves = useRef(eq?.curves?.length ? eq.curves : Array(initialNodes.length - 1).fill(0)).current;
2726

28-
return (
29-
<div className='EQ'>
30-
<h3>Frequency EQ</h3>
31-
<Display
32-
width={width}
33-
height={height}
34-
nodes={nodes}
35-
xRange={xRange}
36-
curves={curves}
37-
onNodesChange={setNodes}
38-
onCurvesChange={setCurves}
39-
freqs={wasmModule ? (() => {
40-
// Manually convert JS arrays to the Embind Vector types.
41-
const nodesVec = new wasmModule.VectorNode();
42-
nodes.forEach(node => nodesVec.push_back(node));
27+
const [nodes, setNodesLocal] = useState(initialNodes);
28+
const [curves, setCurvesLocal] = useState(initialCurves);
4329

44-
const curvesVec = new wasmModule.VectorDouble();
45-
curves.forEach(curve => curvesVec.push_back(curve));
30+
const lastParentEqRef = useRef(JSON.stringify(eq || {}));
4631

47-
const freqsVec = new wasmModule.VectorVectorDouble();
48-
(liveFreqs || []).forEach(freqPair => {
49-
const pair = new wasmModule.VectorDouble();
50-
pair.push_back(freqPair[0]);
51-
pair.push_back(freqPair[1]);
52-
freqsVec.push_back(pair);
53-
pair.delete();
54-
});
32+
// When local nodes/curves change (user edits), push to parent only if different
33+
useEffect(() => {
34+
const parentSerialized = lastParentEqRef.current;
35+
const localSerialized = JSON.stringify({ nodes, curves });
36+
if (parentSerialized !== localSerialized) {
37+
setEq({ nodes, curves });
38+
lastParentEqRef.current = localSerialized;
39+
}
40+
}, [nodes, curves, setEq]);
5541

56-
const result = wasmModule.applyEnvelope(nodesVec, curvesVec, freqsVec);
42+
// When parent eq prop changes (preset load), update local nodes/curves but only if really different
43+
useEffect(() => {
44+
if (!eq) return;
45+
const parentSerialized = JSON.stringify(eq);
46+
const localSerialized = JSON.stringify({ nodes, curves });
47+
if (parentSerialized !== localSerialized) {
48+
if (eq.nodes) setNodesLocal(eq.nodes);
49+
if (eq.curves) setCurvesLocal(eq.curves);
50+
lastParentEqRef.current = parentSerialized;
51+
}
52+
// eslint-disable-next-line react-hooks/exhaustive-deps
53+
}, [eq]);
5754

58-
// Clean up the memory allocated by Embind
59-
nodesVec.delete();
60-
curvesVec.delete();
61-
freqsVec.delete();
55+
const processedFreqs = wasmModule
56+
? (() => {
57+
const nodesVec = new wasmModule.VectorNode();
58+
nodes.forEach((node) => nodesVec.push_back(node));
6259

63-
return result;
64-
})() : []}
65-
isLogarithmic={true}
66-
wasmModule={wasmModule}
67-
/>
68-
</div>
69-
);
60+
const curvesVec = new wasmModule.VectorDouble();
61+
curves.forEach((curve) => curvesVec.push_back(curve));
62+
63+
const freqsVec = new wasmModule.VectorVectorDouble();
64+
(liveFreqs || []).forEach(([f, a]) => {
65+
const pair = new wasmModule.VectorDouble();
66+
pair.push_back(f);
67+
pair.push_back(a);
68+
freqsVec.push_back(pair);
69+
pair.delete();
70+
});
71+
72+
const result = wasmModule.applyEnvelope(nodesVec, curvesVec, freqsVec);
73+
74+
nodesVec.delete();
75+
curvesVec.delete();
76+
freqsVec.delete();
77+
78+
return result;
79+
})()
80+
: [];
81+
82+
return (
83+
<div className="EQ">
84+
<h3>Frequency EQ</h3>
85+
<Display
86+
width={width}
87+
height={height}
88+
nodes={nodes}
89+
xRange={xRange}
90+
curves={curves}
91+
onNodesChange={setNodesLocal}
92+
onCurvesChange={setCurvesLocal}
93+
freqs={processedFreqs}
94+
isLogarithmic={true}
95+
wasmModule={wasmModule}
96+
/>
97+
</div>
98+
);
7099
}
71100

72101
export default EQ;

src/components/Keys.jsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,8 @@ import { createWaveform } from '../utils/createWaveform';
33
import { noteFrequencies, keyMap, notes } from '../constants/keys';
44
import { waveimages } from '../constants/path';
55

6-
function Keys({ onNoteDown, onNoteUp }) {
7-
const [waveform, setWaveform] = useState('sawtooth');
8-
const [octave, setOctave] = useState(4);
6+
function Keys({ onNoteDown, onNoteUp, waveform, setWaveform, octave, setOctave }) {
7+
98
const [activeKeys, setActiveKeys] = useState(new Set());
109

1110
// Handle key press to play a note

src/components/PresetControls.jsx

Lines changed: 76 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,81 @@ export default function PresetControls({ synthState, onPresetLoad }) {
1919
);
2020
};
2121

22+
const handleReset = () => {
23+
const defaultPreset = {
24+
waveform: "sine",
25+
octave: 4,
26+
adsr: { attack: 0.1, decay: 0.2, sustain: 0.7, release: 0.3 },
27+
eq: {
28+
nodes: [
29+
{ x: 20, y: 0.8 },
30+
{ x: 200, y: 0.8 },
31+
{ x: 1000, y: 0.8 },
32+
{ x: 5000, y: 0.8 },
33+
{ x: 20000, y: 0.8 }
34+
],
35+
curves: [0, 0, 0, 0]
36+
}
37+
};
38+
onPresetLoad(defaultPreset);
39+
alert("🔄 Reset to default settings!");
40+
};
41+
2242
return (
23-
<div className="preset-controls" style={{ marginTop: "1rem" }}>
24-
<button onClick={() => savePreset(synthState)}>💾 Save Preset</button>
25-
<button onClick={handleLoadClick}>📂 Load Preset</button>
26-
<input
27-
type="file"
28-
accept="application/json"
29-
ref={fileInputRef}
30-
onChange={handleFileChange}
31-
style={{ display: "none" }}
32-
/>
33-
</div>
34-
);
43+
<div
44+
className="preset-controls"
45+
style={{ marginTop: "1rem", display: "flex", gap: "10px", flexWrap: "wrap" }}
46+
>
47+
<button
48+
onClick={() => savePreset(synthState)}
49+
style={{
50+
padding: "8px 12px",
51+
borderRadius: "6px",
52+
border: "1px solid #444",
53+
background: "#1e1e1e",
54+
color: "#fff",
55+
cursor: "pointer",
56+
}}
57+
>
58+
💾 Save Preset
59+
</button>
60+
61+
<button
62+
onClick={handleLoadClick}
63+
style={{
64+
padding: "8px 12px",
65+
borderRadius: "6px",
66+
border: "1px solid #444",
67+
background: "#1e1e1e",
68+
color: "#fff",
69+
cursor: "pointer",
70+
}}
71+
>
72+
📂 Load Preset
73+
</button>
74+
75+
<button
76+
onClick={handleReset}
77+
style={{
78+
padding: "8px 12px",
79+
borderRadius: "6px",
80+
border: "1px solid #444",
81+
background: "#1e1e1e",
82+
color: "#fff",
83+
cursor: "pointer",
84+
}}
85+
>
86+
🔄 Reset
87+
</button>
88+
89+
<input
90+
type="file"
91+
accept="application/json"
92+
ref={fileInputRef}
93+
onChange={handleFileChange}
94+
style={{ display: "none" }}
95+
/>
96+
</div>
97+
);
98+
3599
}

0 commit comments

Comments
 (0)