summaryrefslogtreecommitdiff
path: root/client
diff options
context:
space:
mode:
Diffstat (limited to 'client')
-rw-r--r--client/index.js108
-rw-r--r--client/lib/kalimba.js19
-rw-r--r--client/lib/life.js75
-rw-r--r--client/lib/organ.js27
-rw-r--r--client/lib/output.js5
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