diff options
| author | julian laplace <julescarbon@gmail.com> | 2025-06-30 23:35:13 +0200 |
|---|---|---|
| committer | julian laplace <julescarbon@gmail.com> | 2025-06-30 23:35:13 +0200 |
| commit | e5ec8893d18aa21f771ab56fc5ded42602125f94 (patch) | |
| tree | f9b95619cfa86ff6ea72302defbee5367f2d3307 /client | |
| parent | dfcdf790e3879678d8b3a9b729cca03174b32d55 (diff) | |
fixes
Diffstat (limited to 'client')
| -rw-r--r-- | client/index.js | 355 | ||||
| -rw-r--r-- | client/lib/color.js | 78 | ||||
| -rw-r--r-- | client/lib/kalimba.js | 90 | ||||
| -rw-r--r-- | client/lib/life.js | 312 | ||||
| -rw-r--r-- | client/lib/organ.js | 39 | ||||
| -rw-r--r-- | client/lib/output.js | 19 | ||||
| -rw-r--r-- | client/lib/startAudioContext.js | 74 | ||||
| -rw-r--r-- | client/lib/util.js | 110 |
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 }; |
