diff options
| author | julian laplace <julescarbon@gmail.com> | 2025-07-07 19:54:02 +0200 |
|---|---|---|
| committer | julian laplace <julescarbon@gmail.com> | 2025-07-07 19:54:02 +0200 |
| commit | dfbd36be4341f633cb51d187d3245efbc9d500a8 (patch) | |
| tree | 15a1006ba51a9c4b2773161bae888cc0522fcdff /client/index.js | |
| parent | 95a494a5570ba7933943cfe2093f1357c5f087f4 (diff) | |
transitions, fix colors, add help
Diffstat (limited to 'client/index.js')
| -rw-r--r-- | client/index.js | 194 |
1 files changed, 130 insertions, 64 deletions
diff --git a/client/index.js b/client/index.js index 2c986dd..df127e4 100644 --- a/client/index.js +++ b/client/index.js @@ -11,20 +11,23 @@ import kalimba from "./lib/kalimba"; import sampler from "./lib/sampler"; import organ from "./lib/organ"; import midi from "./lib/midi"; +import oktransition from "./vendor/oktransition"; import { getOutput } from "./lib/output"; import { browser, requestAudioContext, + clamp, choice, - roundFreq, - frequencyInRange, + roundInterval, + intervalInRange, mod, } from "./lib/util"; import { scales } from "./lib/scales"; let instrument = kalimba; -const root = 440; +let grid = document.createElement("grid"); +let root = 440; const s = 50; let w, h, ws, hs; @@ -35,16 +38,17 @@ const mul_off = 0.9; let dragging = false; let erasing = false; -let lastFreq = 0; +let lastNote = 0; let notes = []; let base_x = 0; let base_y = 0; let scaleMode = 0; let is_split = false; -let frequencies; +let intervals; requestAudioContext(() => { const output = getOutput(); + document.body.appendChild(grid); kalimba.load(output); organ.load(output); sampler.load(output, function ready() { @@ -80,28 +84,28 @@ function log() { // console.log(notes); for (let i = 0; i < 8; i++) { for (let j = 0; j < 8; j++) { - const frequency = notes[i][j].frequency; - const rounded = roundFreq(frequency); - if (!seen[rounded] && frequencyInRange(frequency)) { - seen[rounded] = notes[i][j].frequency; + const interval = notes[i][j].interval; + const rounded = roundInterval(interval); + if (!seen[rounded] && intervalInRange(interval, root)) { + seen[rounded] = notes[i][j].interval; } } } - frequencies = Object.values(seen).sort((a, b) => a - b); - // console.log(frequencies); - console.log(frequencies.length, "unique frequencies in 8x8"); + intervals = Object.values(seen).sort((a, b) => a - b); + // console.log(intervals); + console.log(intervals.length, "unique intervals in 8x8"); } -function play(freq) { - if (!organ.isPlaying(freq.frequency)) { - let frequency = freq.frequency; - // while (frequency < root) { - // frequency *= 2; +function play(note) { + if (!organ.isPlaying(note.interval)) { + let interval = note.interval; + // while (interval < root) { + // interval *= 2; // } - // while (frequency > root) { - // frequency /= 2; + // while (interval > root) { + // interval /= 2; // } - const rounded = roundFreq(freq.frequency); - organ.play(frequency); + const rounded = roundInterval(note.interval); + organ.play(interval); notes.forEach((row) => row.forEach( (note) => note.rounded === rounded && note.div.classList.add("playing"), @@ -109,20 +113,20 @@ function play(freq) { ); } } -function trigger(freq) { - if (frequencyInRange(freq.frequency)) { - instrument.play(freq.frequency); +function trigger(note) { + if (intervalInRange(note.interval, root)) { + instrument.play(note.interval, root); } } function trigger_index(index) { - const frequency = frequencies[index]; - if (frequency) { - instrument.play(frequency); + const interval = intervals[index]; + if (interval) { + instrument.play(interval, root); } } -function pause(freq) { - organ.pause(freq.frequency); - const rounded = roundFreq(freq.frequency); +function pause(note) { + organ.pause(note.interval); + const rounded = roundInterval(note.interval); notes.forEach((row) => row.forEach( (note) => @@ -130,11 +134,11 @@ function pause(freq) { ), ); } -function toggle(freq) { - if (organ.isPlaying(freq.rounded) || freq.div.classList.contains("playing")) { - pause(freq); +function toggle(note) { + if (organ.isPlaying(note.rounded) || note.div.classList.contains("playing")) { + pause(note); } else { - play(freq); + play(note); } } @@ -146,16 +150,16 @@ function add(i, j) { const [a, b] = scale.get(ii, jj, i, j, base_x, base_y); const div = document.createElement("div"); - const frequency = (root * a) / b; - // const frequency = root * Math.pow(2, ((b / a) % 1) + 1); + const interval = a / b; + // const interval = root * Math.pow(2, ((b / a) % 1) + 1); let add = 0; let frac; div.style.left = i * s + "px"; div.style.top = j * s + "px"; - const freq = { - frequency, - rounded: roundFreq(frequency), + const note = { + interval, + rounded: roundInterval(interval, root), div, i, j, @@ -192,19 +196,19 @@ function add(i, j) { } div.innerHTML = `<div>${a_disp}</div><div>/</div><div>${b_disp}</div>`; - if (freq.playing) { + if (note.playing) { div.style.backgroundColor = color(frac, add + add_on, mul_on); } else { div.style.backgroundColor = color(frac, add + add_off, mul_off); } - if (organ.isPlaying(frequency)) { + if (organ.isPlaying(interval)) { div.classList.add("playing"); } }, }; - freq.recolor(1, 1); + note.recolor(1, 1); if (browser.isDesktop) { div.addEventListener("mousedown", function (event) { @@ -213,17 +217,17 @@ function add(i, j) { event.preventDefault(); // notes.forEach((row) => row.forEach((note) => note.recolor(a, b))); is_split = [a, b]; - toggle(freq); + toggle(note); return; } div.style.backgroundColor = color(frac, add + add_on, mul_on); dragging = true; - trigger(freq); + trigger(note); }); div.addEventListener("mouseenter", function () { div.style.backgroundColor = color(frac, add + add_on, mul_on); if (dragging) { - trigger(freq); + trigger(note); } }); div.addEventListener("mouseleave", function () { @@ -237,17 +241,16 @@ function add(i, j) { } else { div.addEventListener("touchstart", function (e) { e.preventDefault(); - toggle(freq); - erasing = !freq.playing; - lastFreq = freq; + toggle(note); + erasing = !note.playing; + lastNote = note; }); } - document.body.appendChild(div); - return freq; + grid.appendChild(div); + return note; } function bind() { - window.addEventListener("resize", build); if (browser.isDesktop) { document.addEventListener("mousedown", (event) => { if (event.button !== 2) { @@ -267,61 +270,124 @@ function bind() { const x = Math.floor(e.touches[0].pageX / s); const y = Math.floor(e.touches[0].pageY / s); if (!(x in notes) || !(y in notes[x])) return; - const freq = notes[x][y]; - if (freq !== lastFreq) { + const note = notes[x][y]; + if (note !== lastNote) { if (dragging) { if (erasing) { - pause(freq); + pause(note); } else { - toggle(freq); + toggle(note); } } - lastFreq = freq; + lastNote = note; } }); document.addEventListener("touchend", () => { dragging = false; }); } + window.addEventListener("resize", build); + window.addEventListener("keydown", keydown, true); + keys.listen(trigger_index); + document + .querySelector("#help .close") + .addEventListener("click", () => + document.querySelector("#help").classList.remove("visible"), + ); + document + .querySelector("#help-button") + .addEventListener("click", () => + document.querySelector("#help").classList.toggle("visible"), + ); } +let isReset = false; function keydown(e) { - if (e.altKey || e.ctrlKey || e.metaKey) return; let step = 1; if (e.shiftKey) { step += 4; } - // console.log(e.keyCode); + console.log(e.keyCode); switch (e.keyCode) { + case 27: // esc - PANIC + if (isReset) { + base_x = 0; + base_y = 0; + showMessage(`reset!`); + } + organ.stop(); + sampler.stop(); + rebuild(); + isReset = true; + setTimeout(() => (isReset = false), 500); + break; case 37: // left + if (e.altKey || e.ctrlKey || e.metaKey) return; base_x = Math.max(0, base_x - step); rebuild(); break; case 38: // up + if (e.altKey || e.ctrlKey || e.metaKey) return; base_y = Math.max(0, base_y - step); rebuild(); break; case 39: // right + if (e.altKey || e.ctrlKey || e.metaKey) return; base_x += step; rebuild(); break; case 40: // down + if (e.altKey || e.ctrlKey || e.metaKey) return; base_y += step; rebuild(); break; case 220: // \ midi.enable(trigger_index); break; + case 191: // ? + document.querySelector("#help").classList.toggle("visible"); + break; case 189: // - - scaleMode = mod(scaleMode - 1, scales.length); - rebuild(); + e.preventDefault(); + if (e.altKey || e.metaKey) { + root = clamp(root - (e.shiftKey ? 10 : 1), 1, 200000); + organ.setRoot(root); + showMessage(`Root: ${root} hz`); + } else { + scaleMode = mod(scaleMode - 1, scales.length); + rebuild(); + showMessage(scales[scaleMode].name); + } break; case 187: // = - scaleMode = mod(scaleMode + 1, scales.length); - rebuild(); + e.preventDefault(); + if (e.altKey || e.metaKey) { + root = clamp(root + (e.shiftKey ? 10 : 1), 1, 200000); + organ.setRoot(root); + showMessage(`Root: ${root} hz`); + } else { + scaleMode = mod(scaleMode + 1, scales.length); + rebuild(); + showMessage(scales[scaleMode].name); + } break; } } -window.addEventListener("keydown", keydown, true); -keys.listen(trigger_index); +let messageTransition; +function showMessage(message) { + const el = document.getElementById("message"); + el.innerHTML = message; + el.style.opacity = 1; + if (messageTransition) { + messageTransition.cancel(); + } + messageTransition = oktransition.add({ + obj: el.style, + from: { opacity: 1 }, + to: { opacity: 0 }, + delay: 1500, + duration: 2000, + easing: oktransition.easing.circ_out, + }); +} |
