summaryrefslogtreecommitdiff
path: root/client
diff options
context:
space:
mode:
authorJules Laplace <julescarbon@gmail.com>2018-03-12 01:42:41 +0100
committerJules Laplace <julescarbon@gmail.com>2018-03-12 01:42:41 +0100
commit6a83c9b84f49d541f39726712b8a0331590555d2 (patch)
tree51f5f29e35fa3af1f483ce8b1bb9829d281c13f0 /client
parent48f66e675f632d4ae837b3f8474569b3cfbceb56 (diff)
basic linear spectral manipulation!
Diffstat (limited to 'client')
-rw-r--r--client/draw.js2
-rw-r--r--client/index.js184
-rw-r--r--client/lib/hall.js4
-rw-r--r--client/lib/mouse.js11
-rw-r--r--client/lib/sampler.js51
-rw-r--r--client/lib/spectrum.js184
-rw-r--r--client/lib/util.js3
7 files changed, 307 insertions, 132 deletions
diff --git a/client/draw.js b/client/draw.js
index bed5f80..ef66e22 100644
--- a/client/draw.js
+++ b/client/draw.js
@@ -54,7 +54,7 @@ function triangle(px,py,r){
function tri(px, py, r) {
ctx.save()
ctx.globalCompositeOperation = 'multiply'
- ctx.fillStyle = color.color((px+py)/(w+h), 0, 1, 1)
+ ctx.fillStyle = color.color((px+py)/(w+h), 0, 1, 0.2)
function p(){
let theta = randrange(0, Math.PI*2)
let x = px + Math.cos(theta) * r
diff --git a/client/index.js b/client/index.js
index e0f44bd..5ad002e 100644
--- a/client/index.js
+++ b/client/index.js
@@ -4,7 +4,6 @@ import Sampler from './lib/sampler'
import draw from './draw'
import keys from './lib/keys'
-import color from './lib/color'
import mouse from './lib/mouse'
import output from './lib/output'
import spectrum from './lib/spectrum'
@@ -12,34 +11,27 @@ import spectrum from './lib/spectrum'
import { Hall } from './lib/hall'
import {
- browser, requestAudioContext,
- randint, randrange, choice, clamp, lerp, dist, shuffle,
- isMobile,
+ requestAudioContext,
+ lerp,
} from './lib/util'
-const root = 440
-const s = 50
-const w = window.innerWidth
-const h = window.innerHeight
-const ws = w/s, hs = h/s
+// const root = 440
const HALLWAY_LENGTH = 147
-const SPEAKER_COUNT = 16
+// const SPEAKER_COUNT = 16
-let notes = [299, 336, 374, 399, 449, 498, 561, 598].map(i => i/2)
-notes = notes.concat(notes.map(i => i/2))
-notes = notes.concat(notes.map(i => i*2))
-notes = shuffle(notes)
+// let notes = [299, 336, 374, 399, 449, 498, 561, 598].map(i => i/2)
+// notes = notes.concat(notes.map(i => i/2))
let samplers = {}
let sampler
requestAudioContext( () => {
- // samplers.smash = new Sampler('samples/smash/g{}.mp3', 12)
- samplers.earth = new Sampler('samples/earth/earth{}.mp3', 20)
- // samplers.glass = new Sampler('samples/glass/0{}Particle.mp3', 20)
- // samplers.kalimba = new Sampler('samples/kalimba/380731__cabled-mess__sansula-08-c-raw.wav', 10)
- sampler = samplers.earth
+ sampler = samplers.smash = new Sampler('samples/smash/g{}.mp3', 12)
+ // sampler = samplers.earth = new Sampler('samples/earth/earth{}.mp3', 20)
+ // sampler = samplers.glass = new Sampler('samples/glass/0{}Particle.mp3', 20)
+ // sampler = samplers.bong = new Sampler('samples/bong/bong{}.mp3', 10)
+ // sampler = samplers.kalimba = new Sampler('samples/kalimba/380731__cabled-mess__sansula-08-c-raw.wav', 10)
samplers.choice = (m,n) => {
const r = Math.random()
if (r < m) return samplers.smash
@@ -52,111 +44,103 @@ requestAudioContext( () => {
})
})
-
-const hall = new Hall ({
- length: HALLWAY_LENGTH,
- speakers: SPEAKER_COUNT,
-})
+// const hall = new Hall ({
+// length: HALLWAY_LENGTH,
+// speakers: SPEAKER_COUNT,
+// })
function redraw(){
draw.clear()
}
-function manipulate(spec){
- const data = spec.data
- const sr = spec.sr
- const fft_size = spec.fft_size
- const fft_overlap = spec.fft_overlap
- const spec_len = data.length
+// let last_lin_bins, last_sam
+let timeout
+function manipulate(x, y, pcm, spec){
+ if (timeout) return null
+ timeout = setTimeout( () => { timeout = null }, 20 )
- let i, j, u, v, _r, _i
+ // reverseSpectrum,
+ // shuffleSpectrum,
+ // invertSpectrum
+ // const pcm_rev = pcm.slice().reverse()
+ // const spec_rev = spectrum.toSpectrum(pcm_rev, spec.sr)
+ // return spec_rev
+ // return spectrum.reverseSpectrum(spec)
+ // const log_bins = spectrum.logarithmicBins(spec)
- let aa = []
- for (i = 0; i < fft_size; i++) {
- aa[i] = i
- }
- shuffle(aa)
+ // let lin_bins = (x * x * x * spec.fft_size/6)|0
+ // let sam = (y * sampler.length)|0 + 1
+ // if (lin_bins == last_lin_bins && sam == last_sam) return null
+ // last_lin_bins = lin_bins
+ // last_sam = sam
+ // const log_bins = spectrum.linearBins(spec, lin_bins)
+ // const new_bins = spectrum.reverseBins(log_bins)
+ // const concat_bins = spectrum.concatBins(new_bins)
+ // const new_spec = spectrum.reorderBins(spec, concat_bins)
+ // console.log(spec, new_spec)
- let new_data = [], new_col, col
- let bands = 2 << 4
- let band, band_index
- let band_size = Math.floor(fft_size / bands)
- for (i = 0; i < spec_len; i++) {
- col = data[i]
- new_col = new_data[i] = data[i].concat()
- data[i][2] = 0
- for (j = 0; j < fft_size; j++) {
- band = Math.floor(j / band_size) * band_size
- band_index = j % band_size
+ // let new_spec = spectrum.cloneSpectrum(spec)
+ // new_spec = spectrum.rotatePhase(new_spec, x * Math.PI)
- new_col[j] = col[ band + (band_size - band_index) ]
+ let new_spec = spectrum.rotateSpectrum(spec, x)
- // spectrum inversion
- // new_col[j] = data[i][ fft_size - j - 1]
-
- // erase mirrored half of fft
- new_col[j + fft_size] = 0
- }
- new_col[2] = 0
- }
-
- spec.data = new_data //.reverse()
- // col = data[i]
- // for (j = 0; j < fft_size; j++) {
- // _r = j*2
- // _i = j*2+1
- // col[_r] = col[_r] / 2
- // col[_i] = col[_i]
- // }
- // }
+ return new_spec
}
-keys.listen(index => {
- // trigger(Math.random(), ((index+7) % SPEAKER_COUNT) / SPEAKER_COUNT, 0, samplers.smash)
- const sample = sampler.play(100, Tone.now(), output)
- const buf = sample._buffer.get()
- if (! buf) return
- const pcm = buf.getChannelData(0)
- const sr = buf.sampleRate
- const duration = buf.duration
- const spec = spectrum.toSpectrum(pcm, sr)
-
- draw.clear()
- draw.waveform(pcm)
- draw.spectrum(spec, 0, window.innerHeight/4 + 20)
-
- manipulate(spec)
-
- const audioBuffer = spectrum.fromSpectrum(spec)
- const player = new Tone.Player(audioBuffer)
- player.connect(output)
- player.start(Tone.now() + pcm.length / sr)
-
- // const new_spec = spectrum.toSpectrum(audioBuffer.getChannelData(0), sr)
- draw.spectrum(spec, 0, window.innerHeight * 1/2 + 40)
+keys.listen(i => {
+ trigger(xx + i/50, yy, 0, sampler)
})
+let xx = 0.5, yy = 0.5
mouse.register({
down: (x, y) => {
redraw()
+ clearTimeout(timeout)
+ timeout = null
+ trigger(x, y, 0, sampler)
},
- move: (x, y, dx, dy) => {
+ move: (x, y) => {
+ xx = x
+ yy = y
},
- up: (x, y) => {
+ drag: (x, y) => {
+ trigger(x, y, 0, sampler)
},
+ // up: (x, y) => {
+ // },
})
-let timeout, px = 0, py = 0
-function play(x, y){
-}
-function trigger(x, y, t, sampler){
+function trigger(x, y, t, source){
+ x = x || 0
+ y = y || 0
t = t || 0
t += Tone.now()
- sampler = sampler || last_dist > 40
- ? samplers.choice(0.2, 0.2)
- : samplers.choice((1-y) * 0.2, y*0.02)
- const freq = notes[Math.floor(x * notes.length)]
- const speaker = hall.play(sampler, y, freq, x, t)
+ source = source || sampler
+ // source = source || last_dist > 40
+ // ? samplers.choice(0.2, 0.2)
+ // : samplers.choice((1-y) * 0.2, y*0.02)
+ // const freq = notes[Math.floor(x * notes.length)]
+ // const { speaker, player } = hall.play(source, y, freq, x, t)
+
+ const { pcm, spec } = source.getWaveAndSpectrum(y)
+ if (! pcm) return
+
+ const new_spec = manipulate(x, y, pcm, spec)
+ if (! new_spec) return
+
+ draw.clear()
+ // draw.waveform(pcm)
+ draw.spectrum(spec, 0, window.innerHeight/4 + 20)
+
+ const audioBuffer = spectrum.fromSpectrum(new_spec)
+ draw.waveform(audioBuffer.getChannelData(0))
+
+ const player = new Tone.Player(audioBuffer)
+ player.connect(output)
+ player.start(Tone.now())
+
+ // const new_spec = spectrum.toSpectrum(audioBuffer.getChannelData(0), sr)
+ draw.spectrum(new_spec, 0, window.innerHeight * 1/2 + 40)
draw.triangle(
lerp(x, 0, 1) * window.innerWidth,
diff --git a/client/lib/hall.js b/client/lib/hall.js
index e07db80..3fe390f 100644
--- a/client/lib/hall.js
+++ b/client/lib/hall.js
@@ -37,8 +37,8 @@ class Hall {
}
play(sound, i, freq, pan, time){
const speaker = this.getSpeaker(i)
- sound.play(freq, time || 0, pan < 0.5 ? speaker.panLeft : speaker.panRight)
- return speaker
+ const player = sound.play(freq, time || 0, pan < 0.5 ? speaker.panLeft : speaker.panRight)
+ return { speaker, player }
}
}
diff --git a/client/lib/mouse.js b/client/lib/mouse.js
index 990b8ef..809f209 100644
--- a/client/lib/mouse.js
+++ b/client/lib/mouse.js
@@ -15,6 +15,7 @@ mouse.register({
let fns = {
down: [],
move: [],
+ drag: [],
up: [],
}
@@ -27,6 +28,7 @@ export default {
register: (callbacks) => {
callbacks.down && fns.down.push(callbacks.down)
callbacks.move && fns.move.push(callbacks.move)
+ callbacks.drag && fns.drag.push(callbacks.drag)
callbacks.up && fns.up.push(callbacks.up)
}
}
@@ -36,17 +38,20 @@ let x, y, dragging = false
function down(e){
x = e.pageX
y = e.pageY
+ x /= window.innerWidth
+ y /= window.innerHeight
dragging = true
fns.down.map(f => f(x, y))
}
function move(e){
- if (!dragging) return
let dx = e.pageX - x
let dy = e.pageY - y
x = e.pageX
y = e.pageY
- dragging = true
+ x /= window.innerWidth
+ y /= window.innerHeight
fns.move.map(f => f(x, y, dx, dy))
+ dragging && fns.drag.map(f => f(x, y, dx, dy))
}
function up(e){
dragging = false
@@ -61,7 +66,7 @@ function touch(f){
}
document.body.addEventListener("mousedown", down)
document.body.addEventListener("mousemove", move)
-document.body.addEventListener("mouseup", up)
+window.addEventListener("mouseup", up)
document.body.addEventListener("touchstart", touch(down))
document.body.addEventListener("touchmove", touch(move))
document.body.addEventListener("touchup", touch(up))
diff --git a/client/lib/sampler.js b/client/lib/sampler.js
index 481a940..9a1b1fc 100644
--- a/client/lib/sampler.js
+++ b/client/lib/sampler.js
@@ -1,21 +1,20 @@
import Tone from 'tone'
-import { lerp, choice } from './util'
+import { choice, clamp } from './util'
+import spectrum from './spectrum'
-const player_count = 2
-const filter_count = 3
-
-const crossfaders = []
+const player_count = 1
export default class Sampler {
constructor(path, count){
this.samples = (() => {
let s = '', a = []
for (let i = 1; i < count; i++) {
- const s = i < 10 ? '0' + i : i;
+ s = i < 10 ? '0' + i : i;
a.push({ root: 100, fn: path.replace(/{}/, s) })
}
return a
})()
+ this.length = this.samples.length
this.samples.forEach((sample) => {
sample.players = []
@@ -38,24 +37,52 @@ export default class Sampler {
return choice(this.samples)
}
play(freq, time, output) {
- const best = this.choice()
- best.index = (best.index + 1) % player_count
-
- const player = best.players[ best.index ]
+ const { player, best } = this.getPlayer()
freq = freq || best.root
+ player.playbackRate = freq / best.root
+
time = time || Tone.now()
- player.playbackRate = freq / best.root
if (player.loaded) {
player.stop()
player.disconnect()
player.connect(output)
player.start(time)
} else {
- console.log('loading')
+ // console.log('loading')
}
return player
}
+ getRandomPlayer(){
+ const best = this.choice()
+ best.index = (best.index + 1) % player_count
+
+ const player = best.players[ best.index ]
+ return { player, best }
+ }
+ getPlayer(n){
+ n = n || 0
+ if (n < 1) n *= this.samples.length
+ const best = this.samples[clamp(n|0, 0, this.samples.length-1)]
+ best.index = (best.index + 1) % player_count
+
+ const player = best.players[ best.index ]
+ return { player, best }
+ }
+ getWaveAndSpectrum(n){
+ const { player } = this.getPlayer(n)
+ const buf = player._buffer.get()
+ if (! buf) return { pcm: null, spec: null }
+ const pcm = buf.getChannelData(0)
+ if (! player._spectrum) {
+ const sr = buf.sampleRate
+ player._spectrum = spectrum.toSpectrum(pcm, sr)
+ }
+ return {
+ pcm: pcm,
+ spec: player._spectrum,
+ }
+ }
}
diff --git a/client/lib/spectrum.js b/client/lib/spectrum.js
index 2b30792..1dd9619 100644
--- a/client/lib/spectrum.js
+++ b/client/lib/spectrum.js
@@ -1,13 +1,12 @@
import Tone from 'tone'
-import output from './output'
+import { shuffle, quantize, mod } from './util'
-const signalWindows = require('signal-windows').windows
-const FFTJS = require('fft.js')
+import { windows as signalWindows } from 'signal-windows'
+import FFTJS from 'fft.js'
-const fft_size = 2 << 10
+const fft_size = 1024
const fft_overlap = fft_size / 4
-const half_fft_size = fft_size / 2
const fft = new FFTJS(fft_size)
@@ -49,15 +48,15 @@ function fromSpectrum(spec){
const audioBuffer = Tone.context.createBuffer(1, pcm_length, sr)
const pcm = audioBuffer.getChannelData(0);
- let i, j, u, v, _r, _i, col
+ let i, j, u, col
for (i = 0; i < spec_len; i++) {
col = data[i]
+ // for (j = fft_size; j < fft_size << 1; j++) {
+ // col[j] = 0
+ // }
fft.inverseTransform(out, col)
u = i * (fft_overlap)
- // for (j = fft_size * 1/4; j < fft_size * 3/4; j++) {
- // pcm[u+j] = out[j*2] / ham[j] || 0
- // }
for (j = 0; j < fft_size; j++) {
pcm[u+j] += out[j*2] * ham[j] || 0
}
@@ -78,12 +77,171 @@ function fadeInOut(pcm, fade_size){
pcm[pcm_length - i] *= fade
}
}
-function fadeOut(pcm){
-
+function rotatePhase(spec, theta){
+ let { data, fft_size } = spec
+ let i, j, col, len = data.length
+ for (i = 0; i < len; i++) {
+ col = data[i]
+ for (j = 0; j < fft_size; j++) {
+ col[j*2+1] += theta
+ }
+ }
+ return spec
+}
+
+function linearBins(spec, n){
+ n = n || 1
+
+ let bins = [], i, q_i
+ for (q_i = 0; q_i < n; q_i++) {
+ bins[q_i] = []
+ }
+ const step = Math.floor(spec.fft_size / n)
+ const len_quantize_n = quantize(spec.fft_size, n)
+ for (i = 0; i < len_quantize_n; i++) {
+ q_i = Math.floor(i/step)
+ bins[q_i] = bins[q_i] || []
+ bins[q_i].push(i)
+ }
+ // leftover bins get put at end
+ for (; i < spec.fft_size; i++) {
+ bins[q_i].push(i)
+ }
+ return bins
+}
+function logarithmicBins(spec){
+ let bins = [], i, j, q_i
+ let binCount = Math.log2(spec.fft_size) - 1
+ for (i = 0, q_i = 0, j = 0; i < binCount; i++) {
+ j += 1 << i
+ bins[i] = []
+ for (; q_i < j; q_i++) {
+ bins[i].push(q_i)
+ }
+ }
+ return bins
+}
+function concatBins(bins){
+ return bins.reduce((acc, cv) => acc.concat(cv), [])
+}
+function reverseBins(bins){
+ return bins.map( bin => bin.reverse() )
+}
+function minBins(bins){
+ return bins.map( bin => {
+ const b = bin[0]
+ return bin.map(() => b)
+ })
+}
+function maxBins(bins){
+ return bins.map( bin => {
+ const b = bin[bin.length-1]
+ return bin.map(() => b)
+ })
+}
+function rotateSpectrum(spec, n){
+ const { fft_size } = spec
+ if (n && n < 1) {
+ n -= 0.5
+ n *= fft_size
+ }
+ n = Math.floor(n)
+ let order = new Array(fft_size), i
+ for (i = 0; i < fft_size; i++) {
+ order[i] = mod(i + n, fft_size/2)
+ }
+ return reorderBins(spec, order)
+}
+function cloneSpectrum(spec){
+ const {
+ data,
+ fft_size,
+ sr, fft_overlap
+ } = spec
+ const spec_len = data.length
+
+ let new_data = new Array(spec_len)
+ let i
+ for (i = 0; i < spec_len; i++) {
+ new_data[i] = data[i].concat()
+ new_data[i][2] = 0
+ }
+
+ return {
+ data: new_data,
+ fft_size,
+ sr, fft_overlap,
+ }
+}
+function reverseSpectrum(spec){
+ let new_spec = cloneSpectrum(spec)
+ new_spec.data = new_spec.data.reverse()
+ return new_spec
+}
+function shuffleSpectrum(spec){
+ const { fft_size } = spec
+ let order = new Array(fft_size), i
+ for (i = 0; i < fft_size; i++) {
+ order[i] = i
+ }
+ shuffle(order)
+ return reorderBins(spec, order)
+}
+function invertSpectrum(spec){
+ const { fft_size } = spec
+ let order = new Array(fft_size), i
+ for (i = 0; i < fft_size; i++) {
+ order[i] = fft_size - i - 1
+ }
+ return reorderBins(spec, order)
+}
+function reorderBins(spec, order){
+ let new_spec = cloneSpectrum(spec)
+ const {
+ data,
+ sr,
+ fft_size,
+ fft_overlap,
+ } = spec
+ const spec_len = data.length
+ const { data: new_data } = new_spec
+
+ let i, j, col, new_col
+ for (j = order.length; j < fft_size; j++) {
+ order[j] = j
+ }
+
+ for (i = 0; i < spec_len; i++) {
+ col = data[i]
+ new_col = new_data[i] = data[i].concat()
+ col[0] = 0
+ // col[2] = 0
+ // col[4] = 0
+ for (j = 0; j < fft_size/2; j++) {
+ new_col[j*2] = col[order[j]*2]
+ new_col[j*2+1] = col[order[j]*2+1]
+ }
+ for (; j < fft_size; j++) {
+ new_col[j*2] = 0
+ new_col[j*2+1] = 0
+ }
+ }
+
+ return {
+ data: new_data,
+ sr, fft_size, fft_overlap,
+ }
}
export default {
- toSpectrum,
- fromSpectrum
+ toSpectrum, fromSpectrum,
+ fadeInOut,
+ cloneSpectrum,
+ reverseSpectrum, shuffleSpectrum, invertSpectrum, rotateSpectrum,
+ reorderBins,
+ linearBins, logarithmicBins,
+ concatBins,
+ reverseBins, minBins, maxBins,
+ rotatePhase,
}
diff --git a/client/lib/util.js b/client/lib/util.js
index fd48768..1bbee9d 100644
--- a/client/lib/util.js
+++ b/client/lib/util.js
@@ -21,6 +21,7 @@ function lerp(n,a,b){ return (b-a)*n+a }
function angle(x0,y0,x1,y1){ return Math.atan2(y1-y0,x1-x0) }
function dist(x0,y0,x1,y1){ return Math.sqrt(Math.pow(x1-x0,2)+Math.pow(y1-y0,2)) }
function xor(a,b){ a=!!a; b=!!b; return (a||b) && !(a&&b) }
+function quantize(a,b){ return Math.floor(a/b)*b }
function shuffle(a){
for (var i = a.length; i > 0; i--){
var r = randint(i)
@@ -104,7 +105,7 @@ function requestAudioContext (fn) {
}
export {
- clamp, choice, mod, lerp, angle, dist, xor,
+ clamp, choice, mod, lerp, angle, dist, xor, quantize,
randint, randrange, randsign, shuffle, gaussian,
browser, requestAudioContext,
}