diff options
Diffstat (limited to 'client')
| -rw-r--r-- | client/index.js | 108 | ||||
| -rw-r--r-- | client/lib/kalimba.js | 19 | ||||
| -rw-r--r-- | client/lib/life.js | 75 | ||||
| -rw-r--r-- | client/lib/organ.js | 27 | ||||
| -rw-r--r-- | client/lib/output.js | 5 |
5 files changed, 212 insertions, 22 deletions
diff --git a/client/index.js b/client/index.js index a1e0828..409ab8e 100644 --- a/client/index.js +++ b/client/index.js @@ -1,8 +1,16 @@ 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 } from './lib/util' +let instrument = kalimba +let hash = window.location.hash || window.location.search +if (hash.match('sin') || hash.match('organ')) { + instrument = organ +} + const root = 440 const s = 50 const w = window.innerWidth @@ -15,6 +23,7 @@ const add_off = 0.1 const mul_off = 0.9 let dragging = false +let erasing = false let lastFreq = 0 let notes = [] @@ -25,43 +34,73 @@ requestAudioContext( () => { notes[i][j] = add(i, j) } } + life.init(notes, assign) }) -function add (x, y) { - const i = x + 1 - const j = y + 1 +function play(freq) { + freq.playing = true + instrument.play(freq.frequency) + freq.div.classList.add('playing') + life.assign_item(freq, true) +} +function pause(freq) { + freq.playing = false + instrument.pause(freq.frequency) + freq.div.classList.remove('playing') + life.assign_item(freq, false) +} +function assign(freq, state) { + if (state) { + play(freq) + } else { + pause(freq) + } +} +function toggle(freq) { + assign(freq, freq.playing = !freq.playing) +} + +function add (i, j) { + const a = i + 1 + const b = j + 1 const div = document.createElement('div') - const freq = root * i/j + const frequency = root * a/b let add = 0 - let frac = Math.log2(i/j) % 1 - div.style.left = (x * s) + 'px' - div.style.top = (y * s) + 'px' - div.innerHTML = `<div>${i}<\/div><div>\/</div><div>${j}<\/div>` + let frac = Math.log2(a/b) % 1 + div.style.left = (i * s) + 'px' + div.style.top = (j * s) + 'px' + div.innerHTML = `<div>${a}<\/div><div>\/</div><div>${b}<\/div>` + const freq = { frequency, div, i, j, playing: false } if (frac < 0) { frac += 1 } - if (i < j) { - add = -Math.log(j/i) / 3.5 + if (a < b) { + add = -Math.log(b/a) / 3.5 } else { - add = Math.log(i/j) / 6 + add = Math.log(a/b) / 6 } if ( frac === 0) { div.style.fontWeight = '900' - div.style.left = (x * s) + 'px' - div.style.top = (y * s) + 'px' + div.style.left = (i * s) + 'px' + div.style.top = (j * s) + 'px' } div.style.backgroundColor = color(frac, add_off + add, mul_off) if (browser.isDesktop) { div.addEventListener('mousedown', function(){ div.style.backgroundColor = color(frac, add + add_on, mul_on) - kalimba.play( freq ) + toggle( freq ) + erasing = !freq.playing }) div.addEventListener('mouseenter', function(){ div.style.backgroundColor = color(frac, add + add_on, mul_on) if (dragging) { - kalimba.play( freq ) + if (erasing) { + pause( freq ) + } else { + toggle( freq ) + } } }) div.addEventListener('mouseleave', function(){ @@ -71,7 +110,8 @@ function add (x, y) { else { div.addEventListener('touchstart', function(e){ e.preventDefault() - kalimba.play( freq ) + toggle( freq ) + erasing = !freq.playing lastFreq = freq }) } @@ -92,16 +132,48 @@ else { if (! (x in notes) || ! (y in notes[x])) return const freq = notes[x][y] if (freq !== lastFreq) { - kalimba.play( freq ) + if (dragging) { + if (erasing) { + pause( freq ) + } else { + toggle( freq ) + } + } lastFreq = freq } }) 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) + switch (e.keyCode){ + case 32: // space + life.toggle() + break + case 38: // up + life_bpm += e.shiftKey ? 1 : 5 + life_bpm = Math.max(1, life_bpm) + life.setTempo(life_bpm) + break + case 40: // down + life_bpm -= e.shiftKey ? 1 : 5 + life.setTempo(life_bpm) + break + case 83: // s + swap_instrument() + break + } +} keys.listen(function(index){ // const freq = scales.current().index(index) // document.body.style.backgroundColor = color( index / scales.current().scale.length ) - // kalimba.play(freq) + // instrument.toggle(freq) }) diff --git a/client/lib/kalimba.js b/client/lib/kalimba.js index d3cafbb..249560f 100644 --- a/client/lib/kalimba.js +++ b/client/lib/kalimba.js @@ -1,10 +1,9 @@ import Tone from 'tone' import { choice } from './util' +import output from './output' const player_count = 4 -const compressor = new Tone.Compressor(-30, 3).toMaster() - 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', }, @@ -28,12 +27,21 @@ samples.forEach((sample) => { retrigger: true, playbackRate: 1, }) - player.connect(compressor) + player.connect(output) sample.players.push(player) } }) 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 @@ -41,6 +49,9 @@ function play (freq) { player.playbackRate = freq / best.sample.root player.start() } +function pause () { + // no-op +} -export default { play } +export default { play, pause } diff --git a/client/lib/life.js b/client/lib/life.js new file mode 100644 index 0000000..26ff0bf --- /dev/null +++ b/client/lib/life.js @@ -0,0 +1,75 @@ + +let w, h, a, b, notes, assign +function init(z, fn){ + // really bad + 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) + for (var i = 0; i < w; i++) { + 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 + } + } +} +let timeout, delay = 1200 // ~120 bpm +function toggle(){ + build() + if (timeout) { + clearTimeout(timeout) + timeout = null + } + else { + step() + } +} +function assign_item(freq,state){ + b[freq.i][freq.j] = state ? 1 : 0 +} +function setTempo(bpm){ + console.log('bpm:', bpm) + delay = 60000/bpm +} +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 + 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 + if (a[i][j] !== state) { + assign(notes[i][j], state) + } + } + } +} +function fitness (old, score) { + if (old === 1) { + if (score === 2 || score === 3) return 1 + } else { + if (score === 3) return 1 + } + return 0 +} + +export default { init, step, assign_item, toggle, setTempo }
\ No newline at end of file diff --git a/client/lib/organ.js b/client/lib/organ.js new file mode 100644 index 0000000..fe2315c --- /dev/null +++ b/client/lib/organ.js @@ -0,0 +1,27 @@ +import Tone from 'tone' +import { choice } from './util' +import output from './output' + +const player_count = 4 + +const oscillators = {} + +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.start() + osc.playing = true + return osc +} +function pause(freq) { + const osc = oscillators[freq] = oscillators[freq] || {} + osc.el && osc.el.stop() + osc.playing = false + return osc +} + +export default { play, pause, oscillators } + diff --git a/client/lib/output.js b/client/lib/output.js new file mode 100644 index 0000000..2155009 --- /dev/null +++ b/client/lib/output.js @@ -0,0 +1,5 @@ +import Tone from 'tone' + +const compressor = new Tone.Compressor(-30, 3).toMaster() + +export default compressor |
