Fewer clicky noises

This commit is contained in:
Paul Mathieu 2025-04-11 10:57:18 -07:00
parent eea0e3f879
commit 953f33f023

104
index.js
View File

@ -9,7 +9,8 @@ const audioCtx = new AudioContext();
const frequencies = []; const frequencies = [];
const holds = []; const holds = [];
let globalVolume = 1.0; let globalVolume = new GainNode(audioCtx, {gain: 1.0});
globalVolume.connect(audioCtx.destination);
/* /*
* TODO: * TODO:
@ -19,48 +20,90 @@ let globalVolume = 1.0;
* - keyboard playing (qwerty) * - keyboard playing (qwerty)
*/ */
function playNote(frequency, duration=86400) { class Envelope {
let currentGain = globalVolume; constructor() {
this.envelope = new GainNode(audioCtx);
}
const bassBoost = f => { hold(attack=0.040, decay=0.110, sustain=0.8) {
this.envelope.gain.setValueAtTime(0, audioCtx.currentTime);
this.envelope.gain.linearRampToValueAtTime(1.0, audioCtx.currentTime + attack);
this.envelope.gain.linearRampToValueAtTime(sustain, audioCtx.currentTime + decay);
}
release(release=0.200) {
this.envelope.gain.setTargetAtTime(0.0, audioCtx.currentTime + release, release/3);
}
}
function bassBoost(f) {
const t = Math.pow(f / 20000, 1/10); const t = Math.pow(f / 20000, 1/10);
const a = 1.0; const a = 1.0;
const b = 0.05; const b = 0.05;
return b * t + a * (1 - t); return b * t + a * (1 - t);
}; }
const oscillators = []; class Voice {
constructor(baseFrequency) {
this.baseFrequency = baseFrequency;
this.oscillators = [];
this.envelope = new Envelope();
let frequency = baseFrequency;
let gain = 1.0;
while (frequency < 20000) { while (frequency < 20000) {
const oscillator = new OscillatorNode(audioCtx, {frequency}); const oscillator = new OscillatorNode(audioCtx, {frequency});
const gain = new GainNode(audioCtx); const volume = new GainNode(audioCtx, {gain: bassBoost(frequency) * gain});
oscillator
.connect(volume)
.connect(this.envelope.envelope)
.connect(globalVolume);
const g = bassBoost(frequency) * currentGain; this.oscillators.push(oscillator);
gain.gain.setValueAtTime(0, audioCtx.currentTime);
gain.gain.linearRampToValueAtTime(g, audioCtx.currentTime + 0.010);
gain.gain.linearRampToValueAtTime(0, audioCtx.currentTime + duration);
oscillator.connect(gain).connect(audioCtx.destination);
oscillator.start();
oscillators.push(oscillator);
// break; // only one frequency for now
frequency *= 2; frequency *= 2;
currentGain *= 0.3; gain *= 0.3;
}
} }
const stop = () => { play(duration=86400) {
for (let oscillator of oscillators) { this.start();
oscillator.stop();
setTimeout(() => {
this.stop();
}, duration * 1000);
} }
};
setTimeout(stop, duration*1000); start() {
this.envelope.hold();
for (let oscillator of this.oscillators) {
oscillator.start();
}
}
return stop; stop() {
this.envelope.release();
for (let oscillator of this.oscillators) {
//oscillator.stop(0.200);
}
}
tune(newBaseFrequency) {
const adjust = newBaseFrequency / this.baseFrequency;
this.baseFrequency = newBaseFrequency;
for (let oscillator of this.oscillators) {
oscillator.frequency.value *= adjust;
}
}
}
function playNote(frequency, duration=86400) {
const voice = new Voice(frequency);
voice.play(duration);
return voice;
} }
function keyIndex(key) { function keyIndex(key) {
@ -104,7 +147,9 @@ function updateOptions(key) {
function onKeyClick(event) { function onKeyClick(event) {
const keySpan = event.srcElement; const keySpan = event.srcElement;
const key = keySpan.id; const key = keySpan.id;
if (holds[keyIndex(key)] === undefined) {
playNote(noteFrequency(key), 0.5); playNote(noteFrequency(key), 0.5);
}
updateOptions(key); updateOptions(key);
keySpan.classList.add('playing'); keySpan.classList.add('playing');
@ -157,7 +202,7 @@ function addHold(key) {
function removeHold(key) { function removeHold(key) {
const index = keyIndex(key); const index = keyIndex(key);
if (holds[index] !== undefined) { if (holds[index] !== undefined) {
holds[index](); holds[index].stop();
} }
delete holds[index]; delete holds[index];
document.getElementById(key).classList.remove('held'); document.getElementById(key).classList.remove('held');
@ -166,8 +211,7 @@ function removeHold(key) {
function refreshHolds() { function refreshHolds() {
for (let i = 0; i < holds.length; i++) { for (let i = 0; i < holds.length; i++) {
if (holds[i] !== undefined) { if (holds[i] !== undefined) {
holds[i](); holds[i].tune(frequencies[i]);
holds[i] = playNote(frequencies[i]);
} }
} }
} }
@ -191,9 +235,9 @@ function setupListeners() {
} }
} }
const range = document.getElementById("range"); const range = document.getElementById("range");
range.onchange = e => { range.oninput = e => {
const val = parseFloat(range.value); const val = parseFloat(range.value);
globalVolume = val; globalVolume.gain.value = val;
refreshHolds(); refreshHolds();
} }
} }