Skip to content

Commit

Permalink
add reverb
Browse files Browse the repository at this point in the history
  • Loading branch information
noah-lee committed May 5, 2022
1 parent 7779091 commit ca0c352
Show file tree
Hide file tree
Showing 12 changed files with 239 additions and 20 deletions.
2 changes: 2 additions & 0 deletions src/components/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import Filter from "./Filter";
import Keyboard from "./Keyboard";
import Adsr from "./Adsr";
import Lfo from "./Lfo";
import Reverb from './Reverb';

function App() {
return (
Expand All @@ -20,6 +21,7 @@ function App() {
<Adsr id="adsrA" />
<Adsr id="adsrB" />
<Lfo />
<Reverb />
<Keyboard />
</Main>
</>
Expand Down
2 changes: 1 addition & 1 deletion src/components/Oscillator/Node.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useContext, useState, useEffect } from "react";
import { AudioContext } from "../../contexts/AudioContext";
import { SettingsContext } from "../../contexts/SettingsContext";

import { play, stop } from "../../utils/adsr";
import { play, stop } from "../../utils/audio";

const Node = ({ note, oscId, oscGroup, oscConfig }) => {
// Audio context
Expand Down
13 changes: 1 addition & 12 deletions src/components/Oscillator/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,12 @@ import Filter from "./Filter";
import Lfo from "./Lfo";

import notes from "../../data/notes";
import { createImpulseResponse } from "../../utils/audio";

const Oscillator = ({ id: oscId }) => {
const { actx, master } = useContext(AudioContext);
const { lfoConfig, oscConfig, setOscConfig } = useContext(SettingsContext);

// // Oscillator default settings
// const [oscConfig, setOscConfig] = useState({
// id: id,
// waveform: "triangle",
// pitch: 0,
// gain: 0.5,
// max: 1,
// adsr: "none",
// filter: false,
// lfo: true,
// });

// Oscillator group node
const [oscGroup] = useState(
new GainNode(actx, { gain: oscConfig[oscId].gain })
Expand Down
27 changes: 27 additions & 0 deletions src/components/Reverb/Decay.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { linToLog, logToLin } from "../../utils/conversion";

const Decay = ({ state, setState }) => {
const time = {
min: 0.1,
max: 10,
};

const handleChange = (ev) => {
const linValue = ev.target.value;
const logValue = linToLog(linValue, time.min, time.max);
setState((prevState) => ({
...prevState,
decay: +logValue.toFixed(2),
}));
};

return (
<input
type="range"
onChange={handleChange}
value={logToLin(state.decay, time.min, time.max)}
/>
);
};

export default Decay;
11 changes: 11 additions & 0 deletions src/components/Reverb/Toggle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
const Toggle = ({ state, setState }) => {
const handleClick = () => {
setState((prevState) => ({
...prevState,
on: !prevState.on,
}));
};
return <button onClick={handleClick}>{state.on ? "On" : "Off"}</button>;
};

export default Toggle;
19 changes: 19 additions & 0 deletions src/components/Reverb/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { useContext } from "react";

import { SettingsContext } from "../../contexts/SettingsContext";

import Toggle from "./Toggle";
import Decay from "./Decay";

const Reverb = () => {
const { reverbConfig, setReverbConfig } = useContext(SettingsContext);
return (
<div>
Reverb
<Toggle state={reverbConfig} setState={setReverbConfig} />
<Decay state={reverbConfig} setState={setReverbConfig}/>
</div>
);
};

export default Reverb;
3 changes: 0 additions & 3 deletions src/contexts/AudioContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,6 @@ export const AudioContextProvider = ({ children }) => {
// Master gain node
const [master] = useState(new GainNode(actx, { gain: masterConfig.gain }));

// Routing
master.connect(actx.destination);

return (
<AudioContext.Provider
value={{
Expand Down
32 changes: 32 additions & 0 deletions src/contexts/EffectsContext.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { createContext, useState, useContext } from "react";

import { SettingsContext } from "./SettingsContext";
import { AudioContext } from "./AudioContext";

import { createImpulseResponse } from "../utils/audio";

export const EffectsContext = createContext();

export const EffectsContextProvider = ({ children }) => {
const { reverbConfig } = useContext(SettingsContext);
const { actx, master } = useContext(AudioContext);

console.log(reverbConfig);

// Reverb node
const [reverb] = useState(new ConvolverNode(actx));
reverb.buffer = createImpulseResponse(actx, 3, reverbConfig.decay);

// Routing
master.connect(actx.destination);
if (reverbConfig.on) {
master.connect(reverb);
reverb.connect(actx.destination);
} else {
reverb.disconnect();
}

return (
<EffectsContext.Provider value={{}}>{children}</EffectsContext.Provider>
);
};
13 changes: 10 additions & 3 deletions src/contexts/SettingsContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ export const SettingsContextProvider = ({ children }) => {
pitch: 0,
gain: 0.75,
max: 1,
adsr: "adsrA",
adsr: "none",
filter: false,
lfo: true,
lfo: false,
},
oscB: {
waveform: "triangle",
Expand All @@ -27,7 +27,7 @@ export const SettingsContextProvider = ({ children }) => {
max: 1,
adsr: "none",
filter: false,
lfo: true,
lfo: false,
},
});

Expand Down Expand Up @@ -66,6 +66,11 @@ export const SettingsContextProvider = ({ children }) => {
max: 0.5,
});

const [reverbConfig, setReverbConfig] = useState({
on: true,
decay: 1,
});

return (
<SettingsContext.Provider
value={{
Expand All @@ -79,6 +84,8 @@ export const SettingsContextProvider = ({ children }) => {
setFilterConfig,
lfoConfig,
setLfoConfig,
reverbConfig,
setReverbConfig,
}}
>
{children}
Expand Down
6 changes: 5 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,17 @@ import reportWebVitals from "./reportWebVitals";

import { SettingsContextProvider } from "./contexts/SettingsContext";
import { AudioContextProvider } from "./contexts/AudioContext";
import { EffectsContextProvider } from "./contexts/EffectsContext";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
//<React.StrictMode>

<SettingsContextProvider>
<AudioContextProvider>
<App />
<EffectsContextProvider>
<App />
</EffectsContextProvider>
</AudioContextProvider>
</SettingsContextProvider>

Expand Down
16 changes: 16 additions & 0 deletions src/utils/adsr.js → src/utils/audio.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,19 @@ export const stop = (ctx, node, parameter, min, release) => {
node[parameter].setTargetAtTime(min, now, release / 4);
node[parameter].setValueAtTime(min, now + release + 0.5);
};

// Impulse response for convolver node
export const createImpulseResponse = (actx, duration, decay) => {
const rate = actx.sampleRate;
const length = rate * decay;
const impulse = actx.createBuffer(2, length, rate);
const impulseL = impulse.getChannelData(0);
const impulseR = impulse.getChannelData(1);

for (let i = 0; i < length; i++) {
impulseL[i] = (Math.random() * 2 - 1) * Math.pow(1 - i / length, duration);
impulseR[i] = (Math.random() * 2 - 1) * Math.pow(1 - i / length, duration);
}

return impulse;
};
115 changes: 115 additions & 0 deletions src/utils/reverb.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/**
* Simple Reverb constructor.
*
* @param {AudioContext} context
* @param {object} opts
* @param {number} opts.seconds
* @param {number} opts.decay
* @param {boolean} opts.reverse
*/

export function SimpleReverb(context, opts) {
this.input = this.output = context.createConvolver();
this._context = context;

var p = this.meta.params;
opts = opts || {};
this._seconds = opts.seconds || p.seconds.defaultValue;
this._decay = opts.decay || p.decay.defaultValue;
this._reverse = opts.reverse || p.reverse.defaultValue;
this._buildImpulse();
}

SimpleReverb.prototype = Object.create(null, {
connect: {
value: function (dest) {
this.output.connect(dest.input ? dest.input : dest);
},
},

disconnect: {
value: function () {
this.output.disconnect();
},
},

_buildImpulse: {
value: function () {
var rate = this._context.sampleRate,
length = rate * this.seconds,
decay = this.decay,
impulse = this._context.createBuffer(2, length, rate),
impulseL = impulse.getChannelData(0),
impulseR = impulse.getChannelData(1),
n,
i;

for (i = 0; i < length; i++) {
n = this.reverse ? length - i : i;
impulseL[i] = (Math.random() * 2 - 1) * Math.pow(1 - n / length, decay);
impulseR[i] = (Math.random() * 2 - 1) * Math.pow(1 - n / length, decay);
}

this.input.buffer = impulse;
},
},

meta: {
value: {
name: "SimpleReverb",
params: {
seconds: {
min: 1,
max: 50,
defaultValue: 3,
type: "float",
},
decay: {
min: 0,
max: 100,
defaultValue: 2,
type: "float",
},
reverse: {
min: 0,
max: 1,
defaultValue: 0,
type: "bool",
},
},
},
},

seconds: {
enumerable: true,
get: function () {
return this._seconds;
},
set: function (value) {
this._seconds = value;
this._buildImpulse();
},
},

decay: {
enumerable: true,
get: function () {
return this._decay;
},
set: function (value) {
this._decay = value;
this._buildImpulse();
},
},

reverse: {
enumerable: true,
get: function () {
return this._reverse;
},
set: function (value) {
this._reverse = value;
this._buildImpulse();
},
},
});

0 comments on commit ca0c352

Please sign in to comment.