From 89fc0d08d7ad3c9cf4cb907d61b0f16ca98c19ec Mon Sep 17 00:00:00 2001 From: ergz Date: Fri, 6 Oct 2023 15:33:06 -0700 Subject: [PATCH] adds individual octave selection for each voice --- synth.html | 142 +++++++++++++++++++++++++++------------ synth.js | 194 ++++++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 269 insertions(+), 67 deletions(-) diff --git a/synth.html b/synth.html index c6224bf..5e1a085 100644 --- a/synth.html +++ b/synth.html @@ -22,21 +22,103 @@ - + +
+
+ Octave Selection + + + + + + + + + + + + + + + +
+
+ + + 0 -
- Voice 2 - - - - - - - - -
- +
+ +
+ Voice 2 + + + + + + + + +
+ +
+
+ Octave Selection + + + + + + + + + + + + + + + +
+
+
+ +
+ +
+ Voice 2 + + + + + + + + +
+ +
+
+ Octave Selection + + + + + + + + + + + + + + + +
+
+
@@ -67,36 +149,8 @@
-
-
- Octave Selection - - - - - - - - - - - - - - - -
-
-
- - - -
- - - .5 -
- - \ No newline at end of file + + + \ No newline at end of file diff --git a/synth.js b/synth.js index 6c4dcbf..e2568c6 100644 --- a/synth.js +++ b/synth.js @@ -1,8 +1,33 @@ +function noteToHz(note) { + switch (note) { + case "C": + return 261.63; + case "D": + return 293.66; + case "E": + return 329.63; + case "F": + return 349.23; + case "G": + return 392; + case "A": + return 440; + case "B": + return 493.88; + default: + return 0; + } +} + class Synth { constructor() { this.audioContext = new AudioContext(); - this.oscillators = []; this.gain = this.audioContext.createGain(); + this.oscillators = [ + this.createOscillator("sawtooth", hzoctave(440, -1)), + this.createOscillator("sawtooth", hzoctave(435, -1)), + this.createOscillator("sawtooth", hzoctave(440, -3)), + ]; this.gain.connect(this.audioContext.destination); } @@ -10,22 +35,38 @@ class Synth { const osc = this.audioContext.createOscillator(); osc.type = type; osc.frequency.setValueAtTime(freq, this.audioContext.currentTime); - this.gain.gain.setTargetAtTime(0.5, this.audioContext.currentTime, 0); + // connect it to the gain node + this.gain.gain.setTargetAtTime(0.1, this.audioContext.currentTime, 0); osc.connect(this.gain); - this.gain.connect(this.audioContext.destination); - this.oscillators.push(osc); - return osc; + + // wrap around the container and add to array + const oscContainer = { osc, isPlaying: false }; + return oscContainer; } - startOsc(osc) { - osc.start(); + startOsc(oscContainer) { + if (!oscContainer.isPlaying) { + oscContainer.osc.start(); + oscContainer.isPlaying = true; + } } - stopOsc(osc) { - osc.stop(); - const index = this.oscillators.indexOf(osc); - if (index !== -1) { - this.oscillators.splice(index, 1); + stopOsc(oscContainer) { + if (oscContainer.isPlaying) { + let currentFreq = oscContainer.osc.frequency.value; + console.log("the current frequency: " + currentFreq); + let currentType = oscContainer.osc.type; + console.log("the current type: " + currentType); + oscContainer.osc.stop(); + oscContainer.isPlaying = false; + oscContainer.osc = this.audioContext.createOscillator(); + oscContainer.osc.type = currentType; + oscContainer.osc.frequency.setValueAtTime( + currentFreq, + this.audioContext.currentTime + ); + oscContainer.osc.connect(this.gain); + console.log(oscContainer); } } @@ -42,19 +83,126 @@ function hzoctave(freq, octave) { window.onload = function () { let synth = new Synth(); - let osc1 = synth.createOscillator("square", hzoctave(440, -1)); - let osc2 = synth.createOscillator("square", hzoctave(440, -1) - 2); // start button - document.getElementById("activateVoice1").addEventListener("click", () => { - console.log("voice 1 start clicked"); - synth.audioContext.resume(); - synth.startOsc(osc1); - }); + function updateFrequency(event, oscContainer, octaveid, detuneAmount) { + console.log("the value of octaveid: " + octaveid); + console.log("the value of detuneAmount: " + detuneAmount); + let selectedNote = document.querySelector( + "input[name='notechoice']:checked" + ).value; - document.getElementById("activateVoice2").addEventListener("click", () => { - console.log("voice 2 start clicked"); - synth.audioContext.resume(); - synth.startOsc(osc2); + if (octaveid) { + console.log("in the octave update"); + let selectedOctave = document.querySelector( + `input[name='${octaveid}']:checked` + ).value; + + oscContainer.osc.frequency.setValueAtTime( + hzoctave(noteToHz(selectedNote), parseFloat(selectedOctave)), + synth.audioContext.currentTime + ); + } + + if (detuneAmount) { + let currFreq = oscContainer.osc.frequency.value; + oscContainer.osc.frequency.setValueAtTime( + currFreq + detuneAmount, + synth.audioContext.currentTime + ); + } + } + + document + .getElementById("activateVoice1") + .addEventListener("click", (event) => { + console.log("voice 1 start clicked"); + synth.audioContext.resume(); + let osc1 = synth.oscillators[0]; + if (osc1.isPlaying) { + synth.stopOsc(osc1); + event.target.textContent = "Start"; + event.target.style.backgroundColor = "greenyellow"; + } else { + synth.startOsc(osc1); + event.target.textContent = "Stop"; + event.target.style.backgroundColor = "red"; + } + }); + + document + .getElementById("activateVoice2") + .addEventListener("click", (event) => { + console.log("voice 2 start clicked"); + synth.audioContext.resume(); + let osc2 = synth.oscillators[1]; + if (osc2.isPlaying) { + synth.stopOsc(osc2); + event.target.textContent = "Start"; + event.target.style.backgroundColor = "greenyellow"; + } else { + synth.startOsc(osc2); + event.target.textContent = "Stop"; + event.target.style.backgroundColor = "red"; + } + }); + + document + .getElementById("activateVoice3") + .addEventListener("click", (event) => { + console.log("voice 3 start clicked"); + synth.audioContext.resume(); + let osc3 = synth.oscillators[2]; + if (osc3.isPlaying) { + synth.stopOsc(osc3); + event.target.textContent = "Start"; + event.target.style.backgroundColor = "greenyellow"; + } else { + synth.startOsc(osc3); + event.target.textContent = "Stop"; + event.target.style.backgroundColor = "red"; + } + }); + + document + .querySelectorAll("input[name='notechoice']") + .forEach((radioButton) => { + console.log("note changed"); + radioButton.addEventListener("change", updateFrequency, null, null); + }); + + document + .querySelectorAll("input[name='octavechoice1']") + .forEach((radioButton) => { + console.log("octave changed"); + radioButton.addEventListener("change", (event) => { + updateFrequency(event, synth.oscillators[0], "octavechoice1", null); + }); + }); + + document + .querySelectorAll("input[name='octavechoice2']") + .forEach((radioButton) => { + console.log("octave changed"); + radioButton.addEventListener("change", (event) => { + updateFrequency(event, synth.oscillators[1], "octavechoice2", null); + }); + }); + + document + .querySelectorAll("input[name='octavechoice3']") + .forEach((radioButton) => { + console.log("octave changed"); + radioButton.addEventListener("change", (event) => { + updateFrequency(event, synth.oscillators[2], "octavechoice3", null); + }); + }); + + // detune + const detuneSliderVoice1 = document.getElementById("detunevoice1"); + const detunevoice1Display = document.getElementById("detunevoice1display"); + detuneSliderVoice1.addEventListener("input", (event) => { + let osc = synth.oscillators[0]; + updateFrequency(event, osc, null, parseFloat(detuneSliderVoice1.value)); }); };