diff options
| author | julian laplace <julescarbon@gmail.com> | 2025-07-14 16:58:49 +0200 |
|---|---|---|
| committer | julian laplace <julescarbon@gmail.com> | 2025-07-14 16:58:49 +0200 |
| commit | 6e53264b8fc0ed94282bcce022f07c551e925547 (patch) | |
| tree | 32694b45fbe228d5f905615755a56ceb24f87a84 /client | |
| parent | edaae6d07fa1abb1e3a9ae8e113bddd663c89c5b (diff) | |
pitched noise mode
Diffstat (limited to 'client')
| -rw-r--r-- | client/index.js | 66 | ||||
| -rw-r--r-- | client/lib/bandpass.js | 97 | ||||
| -rw-r--r-- | client/lib/kalimba.js | 2 | ||||
| -rw-r--r-- | client/lib/sine.js (renamed from client/lib/organ.js) | 31 |
4 files changed, 178 insertions, 18 deletions
diff --git a/client/index.js b/client/index.js index a7bdd9e..7fdaa9b 100644 --- a/client/index.js +++ b/client/index.js @@ -9,7 +9,8 @@ import keys from "./lib/keys"; import color from "./lib/color"; import kalimba from "./lib/kalimba"; import sampler from "./lib/sampler"; -import organ from "./lib/organ"; +import sine from "./lib/sine"; +import bandpass from "./lib/bandpass"; import midi from "./lib/midi"; import oktransition from "./vendor/oktransition"; import { getOutput } from "./lib/output"; @@ -25,6 +26,7 @@ import { import { scales } from "./lib/scales"; let instrument = kalimba; +let organ = sine; let grid = document.createElement("grid"); let root = 440; @@ -47,18 +49,6 @@ let scaleMode = 0; let is_split = false; let intervals; -requestAudioContext(() => { - const output = getOutput(); - document.body.appendChild(grid); - kalimba.load(output); - organ.load(output); - sampler.load(output, function ready() { - instrument = sampler; - }); - build(); - bind(); -}); - function build() { w = window.innerWidth; h = window.innerHeight; @@ -115,6 +105,12 @@ function play(note) { } } function trigger(note) { + if (organ === bandpass) { + toggle(note); + if (instrument === kalimba) { + return; + } + } if (intervalInRange(note.interval, root)) { instrument.play(note.interval, root); } @@ -127,7 +123,6 @@ function trigger_index(index) { } function pause(note) { organ.pause(note.interval); - trigger(note); const rounded = roundInterval(note.interval); notes.forEach((row) => row.forEach( @@ -144,6 +139,29 @@ function toggle(note) { } } +const modes = { + sine, + bandpass, +}; + +let modus = null; +function toggleModus() { + let intervals; + if (modes[modus]) { + intervals = modes[modus].getPlaying(); + modes[modus].stop(); + document.querySelector(`#modus .${modus}`).classList.remove("visible"); + // rebuild(); + } + + modus = modus === "bandpass" ? "sine" : "bandpass"; + organ = modes[modus]; + document.querySelector(`#modus .${modus}`).classList.add("visible"); + if (intervals) { + intervals.forEach((interval) => modes[modus].play(interval)); + } +} + function add(i, j) { const ii = i + Math.round(base_x); const jj = j + Math.round(base_y); @@ -334,6 +352,9 @@ function bind() { .addEventListener("click", () => document.querySelector("#help").classList.toggle("visible"), ); + // toggleModus(); + toggleModus(); + document.querySelector("#modus").addEventListener("click", toggleModus); Array.from(document.querySelectorAll(".mode")).forEach((el) => { console.log(el.getAttribute("name")); el.addEventListener("click", (event) => { @@ -410,7 +431,9 @@ function keydown(e) { document.querySelector("#help").classList.toggle("visible"); break; case 189: // - + case 173: // - e.preventDefault(); + e.stopPropagation(); if (e.altKey || e.metaKey) { root = clamp(root - (e.shiftKey ? 10 : 1), 1, 200000); organ.setRoot(root); @@ -422,7 +445,9 @@ function keydown(e) { } break; case 187: // = + case 61: // = e.preventDefault(); + e.stopPropagation(); if (e.altKey || e.metaKey) { root = clamp(root + (e.shiftKey ? 10 : 1), 1, 200000); organ.setRoot(root); @@ -453,3 +478,16 @@ function showMessage(message) { easing: oktransition.easing.circ_out, }); } + +requestAudioContext(() => { + const output = getOutput(); + document.body.appendChild(grid); + kalimba.load(output); + sine.load(output); + bandpass.load(output); + sampler.load(output, function ready() { + instrument = sampler; + }); + build(); + bind(); +}); diff --git a/client/lib/bandpass.js b/client/lib/bandpass.js new file mode 100644 index 0000000..3e1ed16 --- /dev/null +++ b/client/lib/bandpass.js @@ -0,0 +1,97 @@ +/** + * Bandpassed noise organ + * @module lib/bandpass.js; + */ + +import Tone from "tone"; +import { roundInterval } from "./util"; + +let root = 440; + +let oscillators = {}; +let output; +let lastPlayed; +let noise; + +function load(out) { + output = out; +} +function isPlaying(interval) { + const rounded = roundInterval(interval); + const osc = oscillators[rounded]; + return osc && osc.playing; +} +function play(interval) { + if (!output) { + return; + } + if (!noise) { + noise = new Tone.Noise().start(); + } + const rounded = roundInterval(interval); + const osc = (oscillators[rounded] = oscillators[rounded] || {}); + if (!osc.el) { + osc.interval = interval; + osc.el = new Tone.Filter(interval * root, "bandpass"); + osc.el.Q.value = 100; + } + noise.connect(osc.el); + osc.el.connect(output); + osc.playing = true; + lastPlayed = osc; + return osc; +} + +function pause(interval) { + const rounded = roundInterval(interval); + if (!oscillators[rounded]) return; + const osc = (oscillators[rounded] = oscillators[rounded] || {}); + osc.el.disconnect(output); + noise.disconnect(osc.el); + osc.playing = false; + return osc; +} +function toggle(interval) { + const rounded = roundInterval(interval); + const osc = (oscillators[rounded] = oscillators[rounded] || {}); + if (osc && osc.playing) { + pause(interval); + } else { + play(interval); + } +} + +function setRoot(newRoot) { + root = newRoot; + for (const osc of Object.values(oscillators)) { + osc.el.frequency.value = osc.interval * newRoot; + } +} +function stop() { + for (const osc of Object.values(oscillators)) { + if (!osc.playing) { + continue; + } + osc.el.disconnect(output); + noise.disconnect(osc.el); + osc.playing = false; + delete osc.el; + } + oscillators = {}; +} +function getPlaying() { + return Object.values(oscillators) + .filter((osc) => osc.playing) + .map((osc) => osc.interval); +} + +export default { + load, + isPlaying, + getPlaying, + play, + pause, + toggle, + stop, + setRoot, +}; diff --git a/client/lib/kalimba.js b/client/lib/kalimba.js index 53fcb99..c3ee30f 100644 --- a/client/lib/kalimba.js +++ b/client/lib/kalimba.js @@ -33,7 +33,7 @@ function load({ output }) { playbackRate: 1, }); player.name = fn; - const gain = new Tone.Gain(1.6); + const gain = new Tone.Gain(0.15); player.connect(gain); gain.connect(output); sample.players.push(player); diff --git a/client/lib/organ.js b/client/lib/sine.js index e66f89d..b414b27 100644 --- a/client/lib/organ.js +++ b/client/lib/sine.js @@ -1,6 +1,6 @@ /** * Sine wave organ - * @module lib/organ.js; + * @module lib/sine.js; */ import Tone from "tone"; @@ -29,7 +29,9 @@ function play(interval) { if (!osc.el) { osc.interval = interval; osc.el = new Tone.Oscillator(interval * root, "sine"); - osc.el.connect(output); + osc.gain = new Tone.Gain(0.025); + osc.el.connect(osc.gain); + osc.gain.connect(output); } osc.el.start(); osc.playing = true; @@ -47,6 +49,15 @@ function pause(interval) { osc.playing = false; return osc; } +function toggle(interval) { + const rounded = roundInterval(interval); + const osc = (oscillators[rounded] = oscillators[rounded] || {}); + if (osc && osc.playing) { + pause(interval); + } else { + play(interval); + } +} function setRoot(newRoot) { root = newRoot; @@ -63,5 +74,19 @@ function stop() { } oscillators = {}; } +function getPlaying() { + return Object.values(oscillators) + .filter((osc) => osc.playing) + .map((osc) => osc.interval); +} -export default { load, isPlaying, play, pause, stop, setRoot }; +export default { + load, + isPlaying, + getPlaying, + play, + pause, + toggle, + stop, + setRoot, +}; |
