summaryrefslogtreecommitdiff
path: root/client
diff options
context:
space:
mode:
authorjulian laplace <julescarbon@gmail.com>2025-06-30 23:35:13 +0200
committerjulian laplace <julescarbon@gmail.com>2025-06-30 23:35:13 +0200
commite5ec8893d18aa21f771ab56fc5ded42602125f94 (patch)
treef9b95619cfa86ff6ea72302defbee5367f2d3307 /client
parentdfcdf790e3879678d8b3a9b729cca03174b32d55 (diff)
fixes
Diffstat (limited to 'client')
-rw-r--r--client/index.js355
-rw-r--r--client/lib/color.js78
-rw-r--r--client/lib/kalimba.js90
-rw-r--r--client/lib/life.js312
-rw-r--r--client/lib/organ.js39
-rw-r--r--client/lib/output.js19
-rw-r--r--client/lib/startAudioContext.js74
-rw-r--r--client/lib/util.js110
8 files changed, 600 insertions, 477 deletions
diff --git a/client/index.js b/client/index.js
index 87cf113..527d8db 100644
--- a/client/index.js
+++ b/client/index.js
@@ -3,18 +3,16 @@ import gcd from "compute-gcd";
import keys from "./lib/keys";
import color from "./lib/color";
import kalimba from "./lib/kalimba";
-import life from "./lib/life";
import organ from "./lib/organ";
import { browser, requestAudioContext, choice } from "./lib/util";
+import { PRIMES } from "./lib/primes";
+// import life from "./lib/life";
let instrument = kalimba;
const root = 440;
const s = 50;
-const w = window.innerWidth;
-const h = window.innerHeight;
-const ws = Math.ceil(w / s),
- hs = Math.ceil(h / s);
+let w, h, ws, hs;
const add_on = 0;
const mul_on = 1.0;
@@ -25,32 +23,57 @@ let dragging = false;
let erasing = false;
let lastFreq = 0;
let notes = [];
+let base_x = 0;
+let base_y = 0;
+let is_split = false;
requestAudioContext(() => {
+ kalimba.load();
+ build();
+ bind();
+});
+
+function build() {
+ w = window.innerWidth;
+ h = window.innerHeight;
+ ws = Math.ceil(w / s);
+ hs = Math.ceil(h / s);
for (var i = 0; i < ws; i++) {
notes[i] = [];
for (var j = 0; j < hs; j++) {
notes[i][j] = add(i, j);
}
}
- life.init(notes, assign);
-});
-
+}
+function rebuild() {
+ notes.forEach((row) => row.forEach((note) => note.destroy()));
+ build();
+}
function play(freq) {
- if (freq.playing) return;
+ if (instrument === organ && freq.playing) return;
freq.playing = true;
- instrument.play(freq.frequency);
- if (instrument === organ || hash || life.isRunning()) {
+ let frequency = freq.frequency;
+
+ // while (frequency < root) {
+ // frequency *= 2;
+ // }
+ // while (frequency > root) {
+ // frequency /= 2;
+ // }
+
+ instrument.play(frequency);
+ if (instrument === organ || hash) {
+ // || life.isRunning()) {
freq.div.classList.add("playing");
}
- life.assign_item(freq, true);
+ // life.assign_item(freq, true);
}
function pause(freq) {
if (!freq.playing) return;
freq.playing = false;
instrument.pause(freq.frequency);
freq.div.classList.remove("playing");
- life.assign_item(freq, false);
+ // life.assign_item(freq, false);
}
function assign(freq, state) {
if (state) {
@@ -62,104 +85,17 @@ function assign(freq, state) {
function toggle(freq) {
assign(freq, !freq.playing);
}
-const gliderShape = [
- [0, 0, 0, 0, 0],
- [0, 1, 1, 1, 0],
- [0, 0, 0, 1, 0],
- [0, 0, 1, 0, 0],
- [0, 0, 0, 0, 0],
-];
-const gliderShapeFlip = gliderShape.map((a) => a.slice(0).reverse());
-const gliderShapes = [
- gliderShape,
- gliderShapeFlip,
- gliderShape.slice(0).reverse(),
- gliderShapeFlip.slice(0).reverse(),
-];
-function glider() {
- const x = Math.floor(Math.random() * ws);
- const y = Math.floor(Math.random() * hs);
- const shape = choice(gliderShapes);
- weave(x, y, shape);
-}
-function weave(x, y, shape) {
- const xmag = shape.length;
- const ymag = shape[0].length;
- let i, j, px, py;
- for (i = 0; i < xmag; i++) {
- for (j = 0; j < ymag; j++) {
- px = (x + i) % ws;
- py = (y + j) % hs;
- assign(notes[px][py], shape[i][j]);
- }
- }
-}
-function forEach(f) {
- let i, j, note, s;
- for (i = 0; i < ws; i++) {
- for (j = 0; j < hs; j++) {
- note = notes[i][j];
- s = f(i, j, note.playing);
- assign(note, s);
- }
- }
-}
-function clone() {
- let i, j;
- let a = [];
- for (i = 0; i < ws; i++) {
- a[i] = [];
- for (j = 0; j < hs; j++) {
- a[i][j] = notes[i][j].playing;
- }
- }
- return a;
-}
-function move(dx, dy) {
- let a = clone();
- forEach((x, y, state) => {
- x = (x + dx + ws) % ws;
- y = (y + dy + hs) % hs;
- return a[x][y];
- });
-}
-function clear() {
- forEach(() => {
- return false;
- });
-}
-function stripex(odd) {
- odd = !!odd;
- forEach((x) => {
- return x % 2 ? odd : !odd;
- });
-}
-function stripey(odd) {
- odd = !!odd;
- forEach((x, y) => {
- return y % 2 ? odd : !odd;
- });
-}
-function checker(odd, n) {
- odd = !!odd;
- n = n || 1;
- forEach((x, y) => {
- return Math.floor(x / n) % 2 ^ Math.floor(y / n) % 2 ? odd : !odd;
- });
-}
-function noise(n) {
- n = n || 0.5;
- n = n * n;
- forEach(() => {
- return Math.random() < n;
- });
-}
function add(i, j) {
- const a = i + 1;
- const b = j + 1;
+ const a = i + 1 + base_x;
+ const b = j + 1 + base_y;
+ // const a = i + 1;
+ // const b = i + j + 2;
+ // const a = PRIMES[i];
+ // const b = PRIMES[i + j + 1];
const div = document.createElement("div");
const frequency = (root * a) / b;
+ // const frequency = root * Math.pow(2, ((b / a) % 1) + 1);
let add = 0;
let frac;
div.style.left = i * s + "px";
@@ -171,6 +107,9 @@ function add(i, j) {
i,
j,
playing: false,
+ destroy: () => {
+ div.parentNode.removeChild(div);
+ },
recolor: (numerator, denominator) => {
let aa = a / numerator;
let bb = b / denominator;
@@ -215,20 +154,30 @@ function add(i, j) {
if (event.button === 2) {
// rightclick
event.preventDefault();
- notes.forEach((row) => row.forEach((note) => note.recolor(a, b)));
+ // notes.forEach((row) => row.forEach((note) => note.recolor(a, b)));
+ is_split = [a, b];
return;
}
div.style.backgroundColor = color(frac, add + add_on, mul_on);
- toggle(freq);
- erasing = !freq.playing;
+ dragging = true;
+ if (instrument === organ) {
+ toggle(freq);
+ erasing = !freq.playing;
+ } else {
+ play(freq);
+ }
});
div.addEventListener("mouseenter", function () {
div.style.backgroundColor = color(frac, add + add_on, mul_on);
if (dragging) {
- if (erasing) {
- pause(freq);
+ if (instrument === organ) {
+ if (erasing) {
+ pause(freq);
+ } else {
+ toggle(freq);
+ }
} else {
- toggle(freq);
+ play(freq);
}
}
});
@@ -252,154 +201,81 @@ function add(i, j) {
return freq;
}
-if (browser.isDesktop) {
- document.addEventListener("mousedown", (event) => {
- if (event.button !== 2) {
+function bind() {
+ window.addEventListener("resize", build);
+ if (browser.isDesktop) {
+ document.addEventListener("mousedown", (event) => {
+ if (event.button !== 2) {
+ dragging = true;
+ }
+ });
+ document.addEventListener("mouseup", () => {
+ dragging = false;
+ });
+ } else {
+ document.addEventListener("touchstart", (e) => {
+ e.preventDefault();
dragging = true;
- }
- });
- document.addEventListener("mouseup", () => {
- dragging = false;
- });
-} else {
- document.addEventListener("touchstart", (e) => {
- e.preventDefault();
- dragging = true;
- });
- document.addEventListener("touchmove", (e) => {
- e.preventDefault();
- 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) {
- if (dragging) {
- if (erasing) {
- pause(freq);
- } else {
- toggle(freq);
+ });
+ document.addEventListener("touchmove", (e) => {
+ e.preventDefault();
+ 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) {
+ if (dragging) {
+ if (erasing) {
+ pause(freq);
+ } else {
+ toggle(freq);
+ }
}
+ lastFreq = freq;
}
- lastFreq = freq;
- }
- });
- document.addEventListener("touchend", () => {
- dragging = false;
- });
+ });
+ document.addEventListener("touchend", () => {
+ dragging = false;
+ });
+ }
}
function swap_instrument() {
instrument = instrument === kalimba ? organ : kalimba;
}
-let life_bpm = 50;
window.addEventListener("keydown", keydown, true);
function keydown(e) {
// console.log(e.keyCode)
if (e.altKey || e.ctrlKey || e.metaKey) return;
switch (e.keyCode) {
- case 32: // space
- life.toggle();
- break;
- case 188: // comma
- life_bpm += e.shiftKey ? 1 : 5;
- life.setTempo(life_bpm);
- break;
- case 190: // period
- life_bpm -= e.shiftKey ? 1 : 5;
- life_bpm = Math.max(1, life_bpm);
- life.setTempo(life_bpm);
- break;
case 37: // left
- move(1, 0);
+ base_x = Math.max(0, base_x - 1);
+ rebuild();
break;
case 38: // up
- move(0, 1);
+ base_y = Math.max(0, base_y - 1);
+ rebuild();
break;
case 39: // right
- move(-1, 0);
+ base_x += 1;
+ rebuild();
break;
case 40: // down
- move(0, -1);
- break;
- case 71: // g
- glider();
- break;
- case 83: // s
- swap_instrument();
- break;
- case 67: // c
- clear();
- break;
- case 87: // w
- clear();
- break;
- case 78: // n
- noise(0.5);
- break;
- case 69: // e
- stripex(Math.random() < 0.5);
- break;
- case 82: // r
- stripey(Math.random() < 0.5);
- break;
- case 84: // t
- checker(Math.random() < 0.5, 1);
- break;
- case 89: // y
- checker(Math.random() < 0.5, 2);
- break;
- case 85: // u
- checker(Math.random() < 0.5, 3);
- break;
- case 73: // i
- checker(Math.random() < 0.5, 4);
- break;
- case 79: // o
- checker(Math.random() < 0.5, 5);
- break;
- case 80: // p
- checker(Math.random() < 0.5, 6);
- break;
- case 219: // [
- checker(Math.random() < 0.5, 7);
- break;
- case 221: // ]
- checker(Math.random() < 0.5, 11);
- break;
- case 49: // 1
- noise(0.1);
- break;
- case 50: // 2
- noise(0.2);
- break;
- case 51: // 3
- noise(0.3);
- break;
- case 52: // 4
- noise(0.4);
- break;
- case 53: // 5
- noise(0.5);
- break;
- case 54: // 6
- noise(0.6);
- break;
- case 55: // 7
- noise(0.7);
- break;
- case 56: // 8
- noise(0.8);
- break;
- case 57: // 9
- noise(0.9);
- break;
- case 48: // 0
- noise(1);
+ base_y += 1;
+ rebuild();
break;
}
}
keys.listen(function (index) {
+ index += 7;
+ const x = index % 7;
+ const y = Math.floor(index / 7);
+ const a = x;
+ const b = y + 1;
+ const freq = notes[a][b];
+ console.log(a, b, freq.frequency);
+ play(freq);
// const freq = scales.current().index(index)
// document.body.style.backgroundColor = color( index / scales.current().scale.length )
// instrument.toggle(freq)
@@ -409,10 +285,3 @@ let hash = window.location.hash || window.location.search;
if (hash.match("sin") || hash.match("organ")) {
instrument = organ;
}
-if (hash.match("glider")) {
- instrument = organ;
- clear();
- glider();
- life.setTempo((life_bpm = 120 * 8));
- life.toggle();
-}
diff --git a/client/lib/color.js b/client/lib/color.js
index bd5b7ce..d0fdc24 100644
--- a/client/lib/color.js
+++ b/client/lib/color.js
@@ -1,31 +1,65 @@
-
const palettes = [
- [[0.5, 0.5, 0.5], [0.5, 0.5, 0.5], [1.0, 1.0, 1.0], [0.00, 0.33, 0.67]],
- [[0.5, 0.5, 0.5], [0.5, 0.5, 0.5], [1.0, 1.0, 1.0], [0.00, 0.10, 0.20]],
- [[0.5, 0.5, 0.5], [0.5, 0.5, 0.5], [1.0, 1.0, 1.0], [0.30, 0.20, 0.20]],
- [[0.5, 0.5, 0.5], [0.5, 0.5, 0.5], [1.0, 1.0, 0.5], [0.80, 0.90, 0.30]],
- [[0.5, 0.5, 0.5], [0.5, 0.5, 0.5], [1.0, 0.7, 0.4], [0.00, 0.15, 0.20]],
- [[0.5, 0.5, 0.5], [0.5, 0.5, 0.5], [2.0, 1.0, 0.0], [0.50, 0.20, 0.25]],
- [[0.8, 0.5, 0.4], [0.2, 0.4, 0.2], [2.0, 1.0, 1.0], [0.00, 0.25, 0.25]],
-]
+ [
+ [0.5, 0.5, 0.5],
+ [0.5, 0.5, 0.5],
+ [1.0, 1.0, 1.0],
+ [0.0, 0.33, 0.67],
+ ],
+ [
+ [0.5, 0.5, 0.5],
+ [0.5, 0.5, 0.5],
+ [1.0, 1.0, 1.0],
+ [0.0, 0.1, 0.2],
+ ],
+ [
+ [0.5, 0.5, 0.5],
+ [0.5, 0.5, 0.5],
+ [1.0, 1.0, 1.0],
+ [0.3, 0.2, 0.2],
+ ],
+ [
+ [0.5, 0.5, 0.5],
+ [0.5, 0.5, 0.5],
+ [1.0, 1.0, 0.5],
+ [0.8, 0.9, 0.3],
+ ],
+ [
+ [0.5, 0.5, 0.5],
+ [0.5, 0.5, 0.5],
+ [1.0, 0.7, 0.4],
+ [0.0, 0.15, 0.2],
+ ],
+ [
+ [0.5, 0.5, 0.5],
+ [0.5, 0.5, 0.5],
+ [2.0, 1.0, 0.0],
+ [0.5, 0.2, 0.25],
+ ],
+ [
+ [0.8, 0.5, 0.4],
+ [0.2, 0.4, 0.2],
+ [2.0, 1.0, 1.0],
+ [0.0, 0.25, 0.25],
+ ],
+];
-let palette = palettes[0]
+let palette = palettes[0];
-function channel (t, a, b, c, d, add, mul) {
- return a + b * Math.cos(2 * Math.PI * (c * t + d)) * mul + add
+function channel(t, a, b, c, d, add, mul) {
+ return a + b * Math.cos(2 * Math.PI * (c * t + d)) * mul + add;
}
-function color (t, add, mul) {
- let a, b, c, d
- const rgb = []
+function color(t, add, mul) {
+ let a, b, c, d;
+ const rgb = [];
for (var i = 0; i < 3; i++) {
- a = palette[0][i]
- b = palette[1][i]
- c = palette[2][i]
- d = palette[3][i]
- rgb[i] = Math.round(channel(t, a, b, c, d, add, mul) * 255)
+ a = palette[0][i];
+ b = palette[1][i];
+ c = palette[2][i];
+ d = palette[3][i];
+ rgb[i] = Math.round(channel(t, a, b, c, d, add, mul) * 255);
}
- return 'rgb(' + rgb + ')'
+ return "rgb(" + rgb + ")";
}
-export default color
+export default color;
diff --git a/client/lib/kalimba.js b/client/lib/kalimba.js
index ec224c0..e79e5f7 100644
--- a/client/lib/kalimba.js
+++ b/client/lib/kalimba.js
@@ -1,57 +1,55 @@
-import Tone from 'tone'
-import { choice } from './util'
-import output from './output'
+import Tone from "tone";
+import { choice } from "./util";
+import { getOutput } from "./output";
-const player_count = 4
+const player_count = 4;
const samples = [
- { root: 226, fn: 'samples/380737__cabled-mess__sansula-01-a-raw.wav', },
- { root: 267, fn: 'samples/380736__cabled-mess__sansula-02-c-raw.wav', },
- { root: 340, fn: 'samples/380735__cabled-mess__sansula-03-e-raw.wav', },
- { root: 452, fn: 'samples/380733__cabled-mess__sansula-06-a-02-raw.wav', },
-// { root: 507, fn: 'samples/380734__cabled-mess__sansula-07-b-h-raw.wav', },
-// { root: 535, fn: 'samples/380731__cabled-mess__sansula-08-c-raw.wav', },
-// { root: 671, fn: 'samples/380732__cabled-mess__sansula-09-e-raw.wav', },
-]
+ { root: 226, fn: "samples/380737__cabled-mess__sansula-01-a-raw.wav" },
+ // { root: 267, fn: "samples/380736__cabled-mess__sansula-02-c-raw.wav" },
+ // { root: 340, fn: "samples/380735__cabled-mess__sansula-03-e-raw.wav" },
+ // { root: 452, fn: "samples/380733__cabled-mess__sansula-06-a-02-raw.wav" },
+ // { root: 226, fn: "samples/380737__cabled-mess__sansula-01-a-raw.wav" },
+ // { root: 267, fn: "samples/380736__cabled-mess__sansula-02-c-raw.wav" },
+ // { root: 340, fn: "samples/380735__cabled-mess__sansula-03-e-raw.wav" },
+ // { root: 452, fn: "samples/380733__cabled-mess__sansula-06-a-02-raw.wav" },
+ // { root: 507, fn: 'samples/380734__cabled-mess__sansula-07-b-h-raw.wav', },
+ // { root: 535, fn: 'samples/380731__cabled-mess__sansula-08-c-raw.wav', },
+ // { root: 671, fn: 'samples/380732__cabled-mess__sansula-09-e-raw.wav', },
+];
-samples.forEach((sample) => {
- sample.players = []
- sample.index = -1
- for (let i = 0; i < player_count; i++) {
- let fn = sample.fn
- if (window.location.href.match(/asdf.us/)) {
- fn = '//asdf.us/kalimba/' + fn.replace('wav','mp3')
+function load() {
+ const output = getOutput();
+ samples.forEach((sample) => {
+ sample.players = [];
+ sample.index = -1;
+ for (let i = 0; i < player_count; i++) {
+ let fn = sample.fn;
+ if (window.location.href.match(/asdf.us/)) {
+ fn = "//asdf.us/kalimba/" + fn.replace("wav", "mp3");
+ }
+ let player = new Tone.Player({
+ url: fn,
+ retrigger: true,
+ playbackRate: 1,
+ });
+ player.connect(output);
+ sample.players.push(player);
}
- let player = new Tone.Player({
- url: fn,
- retrigger: true,
- playbackRate: 1,
- })
- player.connect(output)
- sample.players.push(player)
- }
-})
+ });
+}
+
+function play(freq) {
+ const best = choice(samples);
+ best.index = (best.index + 1) % player_count;
-function play (freq) {
-/*
- while (freq < 440) {
- freq *= 2
- }
- while (freq > 880) {
- freq /= 2
- }
- freq /= 2
-*/
- const best = { sample: choice(samples) }
- best.sample.index = (best.sample.index + 1) % player_count
+ const player = best.players[best.index];
+ player.playbackRate = freq / best.root;
- const player = best.sample.players[ best.sample.index ]
- player.playbackRate = freq / best.sample.root
- player.start()
+ player.start();
}
-function pause () {
+function pause() {
// no-op
}
-export default { play, pause }
-
+export default { load, play, pause };
diff --git a/client/lib/life.js b/client/lib/life.js
index e5df868..301535d 100644
--- a/client/lib/life.js
+++ b/client/lib/life.js
@@ -1,78 +1,290 @@
-
-let w, h, a, b, notes, assign
-function init(z, fn){
+let w, h, a, b, notes, assign;
+function init(z, fn) {
// really bad
- notes = z
- assign = fn
- build()
- setTempo(50)
+ notes = z;
+ assign = fn;
+ build();
+ setTempo(50);
}
-function build(){
- w = notes.length
- h = notes[0].length
- a = a || new Array(w)
- b = b || new Array(w)
+function build() {
+ w = notes.length;
+ h = notes[0].length;
+ a = a || new Array(w);
+ b = b || new Array(w);
for (var i = 0; i < w; i++) {
- a[i] = a[i] || new Array(h)
- b[i] = b[i] || new Array(h)
+ a[i] = a[i] || new Array(h);
+ b[i] = b[i] || new Array(h);
for (var j = 0; j < h; j++) {
- a[i][j] = b[i][j] = (notes[i][j] && notes[i][j].playing) ? 1 : 0
+ a[i][j] = b[i][j] = notes[i][j] && notes[i][j].playing ? 1 : 0;
}
}
}
-let timeout, delay = 1200 // ~120 bpm
-function toggle(){
- build()
+let timeout,
+ delay = 1200; // ~120 bpm
+function toggle() {
+ build();
if (timeout) {
- clearTimeout(timeout)
- timeout = null
- }
- else {
- step()
+ clearTimeout(timeout);
+ timeout = null;
+ } else {
+ step();
}
}
-function assign_item(freq,state){
- b[freq.i][freq.j] = state ? 1 : 0
+function assign_item(freq, state) {
+ b[freq.i][freq.j] = state ? 1 : 0;
}
-function setTempo(bpm){
- console.log('bpm:', bpm)
- delay = 60000/bpm
+function setTempo(bpm) {
+ console.log("bpm:", bpm);
+ delay = 60000 / bpm;
}
-function swap () {
- var tmp = a
- a = b
- b = tmp
+function swap() {
+ var tmp = a;
+ a = b;
+ b = tmp;
}
function step() {
- clearTimeout(timeout)
- timeout = setTimeout(step, delay)
- swap()
- let i, j, ni, pi, nj, pj, score, state
+ clearTimeout(timeout);
+ timeout = setTimeout(step, delay);
+ swap();
+ let i, j, ni, pi, nj, pj, score, state;
for (i = 0; i < w; i++) {
for (j = 0; j < h; j++) {
- ni = i === 0 ? w-1 : i-1
- pi = i === w-1 ? 0 : i+1
- nj = j === 0 ? h-1 : j-1
- pj = j === h-1 ? 0 : j+1
- score = a[ni][nj] + a[ni][j] + a[ni][pj] + a[i][nj] + a[i][pj] + a[pi][nj] + a[pi][j] + a[pi][pj]
- state = fitness(a[i][j], score)
- b[i][j] = state
+ ni = i === 0 ? w - 1 : i - 1;
+ pi = i === w - 1 ? 0 : i + 1;
+ nj = j === 0 ? h - 1 : j - 1;
+ pj = j === h - 1 ? 0 : j + 1;
+ score =
+ a[ni][nj] +
+ a[ni][j] +
+ a[ni][pj] +
+ a[i][nj] +
+ a[i][pj] +
+ a[pi][nj] +
+ a[pi][j] +
+ a[pi][pj];
+ state = fitness(a[i][j], score);
+ b[i][j] = state;
if (a[i][j] !== state) {
- assign(notes[i][j], state)
+ assign(notes[i][j], state);
}
}
}
}
-function fitness (old, score) {
+function fitness(old, score) {
if (old === 1) {
- if (score === 2 || score === 3) return 1
+ if (score === 2 || score === 3) return 1;
} else {
- if (score === 3) return 1
+ if (score === 3) return 1;
}
- return 0
+ return 0;
}
function isRunning() {
- return !!timeout
+ return !!timeout;
}
-export default { init, step, assign_item, toggle, setTempo, isRunning } \ No newline at end of file
+export default { init, step, assign_item, toggle, setTempo, isRunning };
+
+///// grid functions
+
+const gliderShape = [
+ [0, 0, 0, 0, 0],
+ [0, 1, 1, 1, 0],
+ [0, 0, 0, 1, 0],
+ [0, 0, 1, 0, 0],
+ [0, 0, 0, 0, 0],
+];
+const gliderShapeFlip = gliderShape.map((a) => a.slice(0).reverse());
+const gliderShapes = [
+ gliderShape,
+ gliderShapeFlip,
+ gliderShape.slice(0).reverse(),
+ gliderShapeFlip.slice(0).reverse(),
+];
+function glider() {
+ const x = Math.floor(Math.random() * ws);
+ const y = Math.floor(Math.random() * hs);
+ const shape = choice(gliderShapes);
+ weave(x, y, shape);
+}
+function weave(x, y, shape) {
+ const xmag = shape.length;
+ const ymag = shape[0].length;
+ let i, j, px, py;
+ for (i = 0; i < xmag; i++) {
+ for (j = 0; j < ymag; j++) {
+ px = (x + i) % ws;
+ py = (y + j) % hs;
+ assign(notes[px][py], shape[i][j]);
+ }
+ }
+}
+function forEach(f) {
+ let i, j, note, s;
+ for (i = 0; i < ws; i++) {
+ for (j = 0; j < hs; j++) {
+ note = notes[i][j];
+ s = f(i, j, note.playing);
+ assign(note, s);
+ }
+ }
+}
+function clone() {
+ let i, j;
+ let a = [];
+ for (i = 0; i < ws; i++) {
+ a[i] = [];
+ for (j = 0; j < hs; j++) {
+ a[i][j] = notes[i][j].playing;
+ }
+ }
+ return a;
+}
+function move(dx, dy) {
+ let a = clone();
+ forEach((x, y, state) => {
+ x = (x + dx + ws) % ws;
+ y = (y + dy + hs) % hs;
+ return a[x][y];
+ });
+}
+function clear() {
+ forEach(() => {
+ return false;
+ });
+}
+function stripex(odd) {
+ odd = !!odd;
+ forEach((x) => {
+ return x % 2 ? odd : !odd;
+ });
+}
+function stripey(odd) {
+ odd = !!odd;
+ forEach((x, y) => {
+ return y % 2 ? odd : !odd;
+ });
+}
+function checker(odd, n) {
+ odd = !!odd;
+ n = n || 1;
+ forEach((x, y) => {
+ return Math.floor(x / n) % 2 ^ Math.floor(y / n) % 2 ? odd : !odd;
+ });
+}
+function noise(n) {
+ n = n || 0.5;
+ n = n * n;
+ forEach(() => {
+ return Math.random() < n;
+ });
+}
+
+/////////////////////////
+
+let life_bpm = 50;
+window.addEventListener("keydown", keydown, true);
+function keydown(e) {
+ // console.log(e.keyCode)
+ if (e.altKey || e.ctrlKey || e.metaKey) return;
+ switch (e.keyCode) {
+ case 32: // space
+ life.toggle();
+ break;
+ case 188: // comma
+ life_bpm += e.shiftKey ? 1 : 5;
+ life.setTempo(life_bpm);
+ break;
+ case 190: // period
+ life_bpm -= e.shiftKey ? 1 : 5;
+ life_bpm = Math.max(1, life_bpm);
+ life.setTempo(life_bpm);
+ break;
+ case 37: // left
+ move(1, 0);
+ break;
+ case 38: // up
+ move(0, 1);
+ break;
+ case 39: // right
+ move(-1, 0);
+ break;
+ case 40: // down
+ move(0, -1);
+ break;
+ case 71: // g
+ glider();
+ break;
+ case 83: // s
+ swap_instrument();
+ break;
+ case 67: // c
+ clear();
+ break;
+ case 87: // w
+ clear();
+ break;
+ case 78: // n
+ noise(0.5);
+ break;
+ case 69: // e
+ stripex(Math.random() < 0.5);
+ break;
+ case 82: // r
+ stripey(Math.random() < 0.5);
+ break;
+ case 84: // t
+ checker(Math.random() < 0.5, 1);
+ break;
+ case 89: // y
+ checker(Math.random() < 0.5, 2);
+ break;
+ case 85: // u
+ checker(Math.random() < 0.5, 3);
+ break;
+ case 73: // i
+ checker(Math.random() < 0.5, 4);
+ break;
+ case 79: // o
+ checker(Math.random() < 0.5, 5);
+ break;
+ case 80: // p
+ checker(Math.random() < 0.5, 6);
+ break;
+ case 219: // [
+ checker(Math.random() < 0.5, 7);
+ break;
+ case 221: // ]
+ checker(Math.random() < 0.5, 11);
+ break;
+ case 49: // 1
+ noise(0.1);
+ break;
+ case 50: // 2
+ noise(0.2);
+ break;
+ case 51: // 3
+ noise(0.3);
+ break;
+ case 52: // 4
+ noise(0.4);
+ break;
+ case 53: // 5
+ noise(0.5);
+ break;
+ case 54: // 6
+ noise(0.6);
+ break;
+ case 55: // 7
+ noise(0.7);
+ break;
+ case 56: // 8
+ noise(0.8);
+ break;
+ case 57: // 9
+ noise(0.9);
+ break;
+ case 48: // 0
+ noise(1);
+ break;
+ }
+}
diff --git a/client/lib/organ.js b/client/lib/organ.js
index 0048abf..1e45e68 100644
--- a/client/lib/organ.js
+++ b/client/lib/organ.js
@@ -1,27 +1,26 @@
-import Tone from 'tone'
-import output from './output'
+import Tone from "tone";
+import output from "./output";
-const oscillators = {}
+const oscillators = {};
-let lastPlayed
-function play (freq) {
- const osc = oscillators[freq] = oscillators[freq] || {}
+let lastPlayed;
+function play(freq) {
+ const osc = (oscillators[freq] = oscillators[freq] || {});
if (!osc.el) {
- osc.el = new Tone.Oscillator(freq, "sine")
- osc.el.connect(output)
+ osc.el = new Tone.Oscillator(freq, "sine");
+ osc.el.connect(output);
}
- osc.el.start()
- osc.playing = true
- lastPlayed = osc
- return osc
+ osc.el.start();
+ osc.playing = true;
+ lastPlayed = osc;
+ return osc;
}
-function pause (freq) {
- if (!oscillators[freq]) return
- const osc = oscillators[freq] = oscillators[freq] || {}
- if (osc.el) osc.el.stop()
- osc.playing = false
- return osc
+function pause(freq) {
+ if (!oscillators[freq]) return;
+ const osc = (oscillators[freq] = oscillators[freq] || {});
+ if (osc.el) osc.el.stop();
+ osc.playing = false;
+ return osc;
}
-export default { play, pause, oscillators }
-
+export default { play, pause, oscillators };
diff --git a/client/lib/output.js b/client/lib/output.js
index 9947327..e67b4d4 100644
--- a/client/lib/output.js
+++ b/client/lib/output.js
@@ -1,8 +1,17 @@
import Tone from "tone";
-const compressor = new Tone.Compressor(-30, 3);
-const gain = new Tone.Gain(0.3);
-compressor.connect(gain);
-gain.toMaster();
+let output = null;
-export default compressor;
+export function getOutput() {
+ if (!output) output = makeOutput();
+ return output;
+}
+
+function makeOutput() {
+ const compressor = new Tone.Compressor(-30, 3);
+ const gain = new Tone.Gain(0.3);
+ compressor.connect(gain);
+ gain.toMaster();
+
+ return compressor;
+}
diff --git a/client/lib/startAudioContext.js b/client/lib/startAudioContext.js
index f3a9793..56d4cae 100644
--- a/client/lib/startAudioContext.js
+++ b/client/lib/startAudioContext.js
@@ -7,13 +7,12 @@
(function (root, factory) {
if (typeof define === "function" && define.amd) {
define([], factory);
- } else if (typeof module === 'object' && module.exports) {
- module.exports = factory();
+ } else if (typeof module === "object" && module.exports) {
+ module.exports = factory();
} else {
root.StartAudioContext = factory();
}
-}(this, function () {
-
+})(this, function () {
/**
* The StartAudioContext object
*/
@@ -22,28 +21,27 @@
* The audio context passed in by the user
* @type {AudioContext}
*/
- context : null,
+ context: null,
/**
* The TapListeners bound to the elements
* @type {Array}
* @private
*/
- _tapListeners : [],
+ _tapListeners: [],
/**
* Callbacks to invoke when the audio context is started
* @type {Array}
* @private
*/
- _onStarted : [],
+ _onStarted: [],
};
-
/**
* Set the context
* @param {AudioContext} ctx
* @returns {StartAudioContext}
*/
- StartAudioContext.setContext = function(ctx){
+ StartAudioContext.setContext = function (ctx) {
StartAudioContext.context = ctx;
return StartAudioContext;
};
@@ -53,31 +51,31 @@
* @param {Array|Element|String|jQuery} element
* @returns {StartAudioContext}
*/
- StartAudioContext.on = function(element){
- if (Array.isArray(element) || (NodeList && element instanceof NodeList)){
- for (var i = 0; i < element.length; i++){
+ StartAudioContext.on = function (element) {
+ if (Array.isArray(element) || (NodeList && element instanceof NodeList)) {
+ for (var i = 0; i < element.length; i++) {
StartAudioContext.on(element[i]);
}
- } else if (typeof element === "string"){
+ } else if (typeof element === "string") {
StartAudioContext.on(document.querySelectorAll(element));
- } else if (element.jquery && typeof element.toArray === "function"){
+ } else if (element.jquery && typeof element.toArray === "function") {
StartAudioContext.on(element.toArray());
- } else if (Element && element instanceof Element){
+ } else if (Element && element instanceof Element) {
//if it's an element, create a TapListener
var tap = new TapListener(element, onTap);
StartAudioContext._tapListeners.push(tap);
- }
+ }
return StartAudioContext;
};
/**
- * Bind a callback to when the audio context is started.
+ * Bind a callback to when the audio context is started.
* @param {Function} cb
* @return {StartAudioContext}
*/
- StartAudioContext.onStarted = function(cb){
+ StartAudioContext.onStarted = function (cb) {
//if it's already started, invoke the callback
- if (StartAudioContext.isStarted()){
+ if (StartAudioContext.isStarted()) {
cb();
} else {
StartAudioContext._onStarted.push(cb);
@@ -89,8 +87,11 @@
* returns true if the context is started
* @return {Boolean}
*/
- StartAudioContext.isStarted = function(){
- return (StartAudioContext.context !== null && StartAudioContext.context.state === "running");
+ StartAudioContext.isStarted = function () {
+ return (
+ StartAudioContext.context !== null &&
+ StartAudioContext.context.state === "running"
+ );
};
/**
@@ -98,8 +99,7 @@
* @param {Element} element
* @internal
*/
- var TapListener = function(element){
-
+ var TapListener = function (element) {
this._dragged = false;
this._element = element;
@@ -115,15 +115,15 @@
/**
* drag move event
*/
- TapListener.prototype._moved = function(e){
+ TapListener.prototype._moved = function (e) {
this._dragged = true;
};
/**
* tap ended listener
*/
- TapListener.prototype._ended = function(e){
- if (!this._dragged){
+ TapListener.prototype._ended = function (e) {
+ if (!this._dragged) {
onTap();
}
this._dragged = false;
@@ -132,7 +132,7 @@
/**
* remove all the bound events
*/
- TapListener.prototype.dispose = function(){
+ TapListener.prototype.dispose = function () {
this._element.removeEventListener("touchmove", this._bindedMove);
this._element.removeEventListener("touchend", this._bindedEnd);
this._element.removeEventListener("mouseup", this._bindedEnd);
@@ -143,12 +143,12 @@
/**
* Invoked the first time of the elements is tapped.
- * Creates a silent oscillator when a non-dragging touchend
+ * Creates a silent oscillator when a non-dragging touchend
* event has been triggered.
*/
- function onTap(){
+ function onTap() {
//start the audio context with a silent oscillator
- if (StartAudioContext.context && !StartAudioContext.isStarted()){
+ if (StartAudioContext.context && !StartAudioContext.isStarted()) {
var osc = StartAudioContext.context.createOscillator();
var silent = StartAudioContext.context.createGain();
silent.gain.value = 0;
@@ -156,19 +156,19 @@
silent.connect(StartAudioContext.context.destination);
var now = StartAudioContext.context.currentTime;
osc.start(now);
- osc.stop(now+0.5);
+ osc.stop(now + 0.5);
}
//dispose all the tap listeners
- if (StartAudioContext._tapListeners){
- for (var i = 0; i < StartAudioContext._tapListeners.length; i++){
+ if (StartAudioContext._tapListeners) {
+ for (var i = 0; i < StartAudioContext._tapListeners.length; i++) {
StartAudioContext._tapListeners[i].dispose();
}
StartAudioContext._tapListeners = null;
}
//the onstarted callbacks
- if (StartAudioContext._onStarted){
- for (var j = 0; j < StartAudioContext._onStarted.length; j++){
+ if (StartAudioContext._onStarted) {
+ for (var j = 0; j < StartAudioContext._onStarted.length; j++) {
StartAudioContext._onStarted[j]();
}
StartAudioContext._onStarted = null;
@@ -176,6 +176,4 @@
}
return StartAudioContext;
-}));
-
-
+});
diff --git a/client/lib/util.js b/client/lib/util.js
index 4d9d038..b1ce162 100644
--- a/client/lib/util.js
+++ b/client/lib/util.js
@@ -1,61 +1,65 @@
-import Tone from 'tone'
-import StartAudioContext from './startAudioContext'
+import Tone from "tone";
+import StartAudioContext from "./startAudioContext";
-const isIphone = (navigator.userAgent.match(/iPhone/i)) || (navigator.userAgent.match(/iPod/i))
-const isIpad = (navigator.userAgent.match(/iPad/i))
-const isAndroid = (navigator.userAgent.match(/Android/i))
-const isMobile = isIphone || isIpad || isAndroid
-const isDesktop = ! isMobile
+const isIphone =
+ navigator.userAgent.match(/iPhone/i) || navigator.userAgent.match(/iPod/i);
+const isIpad = navigator.userAgent.match(/iPad/i);
+const isAndroid = navigator.userAgent.match(/Android/i);
+const isMobile = isIphone || isIpad || isAndroid;
+const isDesktop = !isMobile;
-document.body.classList.add(isMobile ? 'mobile' : 'desktop')
+document.body.classList.add(isMobile ? "mobile" : "desktop");
-const browser = { isIphone, isIpad, isMobile, isDesktop }
+const browser = { isIphone, isIpad, isMobile, isDesktop };
-function choice (a){ return a[ Math.floor(Math.random() * a.length) ] }
-function mod(n,m){ return n-(m * Math.floor(n/m)) }
+function choice(a) {
+ return a[Math.floor(Math.random() * a.length)];
+}
+function mod(n, m) {
+ return n - m * Math.floor(n / m);
+}
-function requestAudioContext (fn) {
- if (isMobile) {
- const container = document.createElement('div')
- const button = document.createElement('div')
- button.innerHTML = 'Tap to start - please unmute your phone'
+function requestAudioContext(fn) {
+ if (window.location.protocol !== "https:") {
+ const container = document.createElement("div");
+ const button = document.createElement("div");
+ button.innerHTML = "Tap to start - please unmute your phone";
Object.assign(container.style, {
- display: 'block',
- position: 'absolute',
- width: '100%',
- height: '100%',
- zIndex: '10000',
- top: '0px',
- left: '0px',
- backgroundColor: 'rgba(0, 0, 0, 0.8)',
- })
- Object.assign(button.style, {
- display: 'block',
- position: 'absolute',
- left: '50%',
- top: '50%',
- padding: '20px',
- backgroundColor: '#7F33ED',
- color: 'white',
- fontFamily: 'monospace',
- borderRadius: '3px',
- transform: 'translate3D(-50%,-50%,0)',
- textAlign: 'center',
- lineHeight: '1.5',
- width: '150px',
- })
- container.appendChild(button)
- document.body.appendChild(container)
- StartAudioContext.setContext(Tone.context)
- StartAudioContext.on(button)
- StartAudioContext.onStarted(_ => {
- container.remove()
- fn()
- })
- } else {
- fn()
- }
+ display: "block",
+ position: "absolute",
+ width: "100%",
+ height: "100%",
+ zIndex: "10000",
+ top: "0px",
+ left: "0px",
+ backgroundColor: "rgba(0, 0, 0, 0.8)",
+ });
+ Object.assign(button.style, {
+ display: "block",
+ position: "absolute",
+ left: "50%",
+ top: "50%",
+ padding: "20px",
+ backgroundColor: "#7F33ED",
+ color: "white",
+ fontFamily: "monospace",
+ borderRadius: "3px",
+ transform: "translate3D(-50%,-50%,0)",
+ textAlign: "center",
+ lineHeight: "1.5",
+ width: "150px",
+ });
+ container.appendChild(button);
+ document.body.appendChild(container);
+ StartAudioContext.setContext(Tone.context);
+ StartAudioContext.on(button);
+ StartAudioContext.onStarted((_) => {
+ container.remove();
+ fn();
+ });
+ } else {
+ fn();
+ }
}
-export { choice, mod, browser, requestAudioContext }
-
+export { choice, mod, browser, requestAudioContext };