summaryrefslogtreecommitdiff
path: root/client/index.js
diff options
context:
space:
mode:
authorjulian laplace <julescarbon@gmail.com>2025-07-07 19:54:02 +0200
committerjulian laplace <julescarbon@gmail.com>2025-07-07 19:54:02 +0200
commitdfbd36be4341f633cb51d187d3245efbc9d500a8 (patch)
tree15a1006ba51a9c4b2773161bae888cc0522fcdff /client/index.js
parent95a494a5570ba7933943cfe2093f1357c5f087f4 (diff)
transitions, fix colors, add help
Diffstat (limited to 'client/index.js')
-rw-r--r--client/index.js194
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,
+ });
+}