├── .gitignore ├── LICENSE ├── README.md ├── line-party ├── index.js ├── package.json └── site │ └── index.html ├── sphere-party ├── index.js ├── package.json └── site │ └── index.html └── unknown-party ├── index.js ├── package.json └── site └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | node_modules 28 | 29 | # Optional npm cache directory 30 | .npm 31 | 32 | # Optional REPL history 33 | .node_repl_history 34 | 35 | bundle.js -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Jeremy Freeman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sound-party 2 | 3 | > visual + audio experiments 4 | 5 | `#1` [sphere-party](https://sphere-party.surge.sh/) 6 | 7 | `#2` [line-party](https://line-party.surge.sh/) 8 | 9 | more coming soon 10 | -------------------------------------------------------------------------------- /line-party/index.js: -------------------------------------------------------------------------------- 1 | var regl = require('regl')() 2 | var mat4 = require('gl-mat4') 3 | var hsv2rgb = require('hsv2rgb') 4 | var Analyser = require('web-audio-analyser') 5 | 6 | var analyser 7 | 8 | navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia 9 | 10 | navigator.getUserMedia({audio: true, video: false, muted: true}, function(stream) { 11 | analyser = Analyser(stream, {audible: false, stereo: false}) 12 | }, function (err) { 13 | }) 14 | 15 | var count = 2 16 | 17 | var draw = regl({ 18 | frag: [ 19 | 'precision mediump float;', 20 | 'varying vec3 vcolor;', 21 | 'void main() {', 22 | ' gl_FragColor = vec4(vcolor, 1.0);', 23 | '}' 24 | ].join('\n'), 25 | 26 | vert: [ 27 | 'precision mediump float;', 28 | 'attribute vec2 position;', 29 | 'uniform vec2 offset;', 30 | 'uniform float depth;', 31 | 'uniform mat4 proj;', 32 | 'uniform vec3 color;', 33 | 'varying vec3 vcolor;', 34 | 'void main() {', 35 | ' vcolor = color;', 36 | ' gl_Position = proj * vec4(position.x + offset.x, position.y + offset.y, -0.5 + depth * 8.0, 1.0);', 37 | ' gl_PointSize = 20.0;', 38 | '}' 39 | ].join('\n'), 40 | 41 | attributes: { 42 | position: regl.buffer([[0.0, -0.2], [0.0, 0.2]]) 43 | }, 44 | 45 | uniforms: { 46 | proj: mat4.perspective([], Math.PI/2, window.innerWidth / window.innerHeight, 0.01, 1000), 47 | offset: regl.prop('offset'), 48 | depth: regl.prop('depth'), 49 | color: regl.prop('color') 50 | }, 51 | 52 | lineWidth: 10, 53 | 54 | primitive: 'lines', 55 | 56 | count: count 57 | }) 58 | 59 | var updates = [] 60 | var i, freq, wave, total, calc1, calc2, calc3 61 | 62 | function bin (array, count) { 63 | var out = sub = [] 64 | var width = Math.round(array.length / count) 65 | for (i = 0; i < count; i++) { 66 | sub = array.slice(i * width, (i + 1) * width) 67 | out[i] = sub.reduce(function (x, y) {return x + y}) / sub.length 68 | } 69 | return out 70 | } 71 | 72 | regl.frame(function (count) { 73 | regl.clear({ 74 | color: [0, 0, 0, 1] 75 | }) 76 | 77 | if (analyser) { 78 | freq = bin(analyser.frequencies().slice(5, 800), 100) 79 | wave = bin(analyser.waveform(), 100) 80 | total = freq.reduce(function (x, y) {return x + y}) / 100 81 | for (i = 0; i < 100; i++) { 82 | calc1 = (0.25 * (freq[i] / 150 - 1) + 0.15) 83 | calc2 = ((1.5 * (wave[i] / 128 - 1))) 84 | if (updates[i]) { 85 | updates[i] = { 86 | offset: [(i - 50)/50, 0.5 * updates[i].offset[1] + 0.5 * calc1], 87 | depth: 0.9 * updates[i].depth + 0.1 * calc2 88 | } 89 | } else { 90 | updates[i] = { 91 | offset: [(i - 50)/50, calc1], 92 | depth: calc2 93 | } 94 | } 95 | calc3 = hsv2rgb(((i / 100) * 360 + Math.cos(count * 0.1) * total) % 360, 0.65, Math.max((calc1 + 0.1) * 5, 0.12)) 96 | updates[i].color = [calc3[0] / 255, calc3[1] / 255, calc3[2] / 255] 97 | } 98 | } 99 | 100 | draw(updates) 101 | }) -------------------------------------------------------------------------------- /line-party/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "line-party", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "browserify index.js -o site/bundle.js", 8 | "deploy": "surge site line-party.surge.sh" 9 | }, 10 | "author": "freeman-lab", 11 | "license": "MIT", 12 | "dependencies": { 13 | "hsv2rgb": "^1.1.0", 14 | "regl": "^0.1.0", 15 | "right-now": "^1.0.0", 16 | "web-audio-analyser": "^2.0.1" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /line-party/site/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /sphere-party/index.js: -------------------------------------------------------------------------------- 1 | var context = require('gl-context') 2 | var fit = require('canvas-fit') 3 | var orbit = require('canvas-orbit-camera') 4 | var icosphere = require('icosphere') 5 | var tinycolor = require('tinycolor2') 6 | var Analyser = require('web-audio-analyser') 7 | var time = require('right-now') 8 | 9 | var canvas = document.body.appendChild(document.createElement('canvas')) 10 | window.addEventListener('resize', fit(canvas), false) 11 | var gl = context(canvas, tick) 12 | 13 | var analyser 14 | 15 | navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia 16 | 17 | navigator.getUserMedia({audio: true, video: false, muted: true}, function(stream) { 18 | analyser = Analyser(stream, {audible: false}) 19 | }, function (err) { 20 | console.log(err) 21 | }) 22 | 23 | var scene = require('gl-scene')(gl, {background: [0, 0, 0]}) 24 | 25 | var phases = [] 26 | var positions = [] 27 | var i 28 | for (i = 0; i < 200; i++) { 29 | phases[i] = Math.random() * Math.PI * 2 30 | positions[i] = [Math.random() * 20 - 10, Math.random() * 20 - 10, Math.random() * 20 - 10] 31 | } 32 | 33 | var basecolors = [] 34 | var color 35 | for (i = 0; i < 200; i++) { 36 | color = tinycolor.fromRatio({h: i / 200, s: 0.8, v: 0.7}).toRgb() 37 | basecolors[i] = [color.r / 255, color.g / 255, color.b / 255] 38 | } 39 | 40 | var shapes = [] 41 | for (i = 0; i < 200; i++) { 42 | shapes.push({ 43 | class: 'sphere', 44 | complex: icosphere(3), 45 | position: swirl(positions[i], phases[i], 0), 46 | style: { 47 | emissive: [Math.max(Math.random(), 0.3), 0.01, Math.max(Math.random(), 0.3)], 48 | diffuse: [0.9, 0.9, 0.9] 49 | } 50 | }) 51 | } 52 | 53 | var lights = [ 54 | { 55 | position: [0, 0, 0], 56 | style: {intensity: 2.0, ambient: 1, attenuation: 0.01} 57 | } 58 | ] 59 | 60 | scene.lights(lights) 61 | scene.shapes(shapes) 62 | scene.init() 63 | 64 | var camera = orbit(canvas) 65 | 66 | var t = 0 67 | 68 | var freq, hsv, rgb, fullfreq 69 | var rotate = 0.005 70 | var updates = [] 71 | 72 | function tick () { 73 | var now = time() * 0.001 74 | var axis = Math.sin(now) * 2 75 | 76 | camera.rotate([0, 0, 0], [axis * rotate, -rotate, 0]) 77 | 78 | if (analyser) { 79 | freq = analyser.frequencies().reduce(function (x, y) { return x + y }) 80 | fullfreq = analyser.frequencies() 81 | for (i=0; i < 50; i++) { 82 | if (updates[i]) { 83 | updates[i] = 0.1 * fullfreq.slice(i*8, (i+1)*8).reduce(function (x, y) { return x + y }) / 8 + 0.9 * updates[i] 84 | } else { 85 | updates[i] = fullfreq.slice(i*8, (i+1)*8).reduce(function (x, y) { return x + y }) / 8 86 | } 87 | } 88 | rotate = freq / 10000000 89 | } 90 | scene.draw(camera) 91 | scene.selectAll('.sphere').each(function (d, i) { 92 | d.position(swirl(positions[i], phases[i], t, freq / 500)) 93 | }) 94 | scene.selectAll('.sphere').each(function (d, i) { 95 | hsv = tinycolor.fromRatio({r: basecolors[i][0], g: basecolors[i][1], b: basecolors[i][2]}).toHsv() 96 | hsv.h = hsv.h + Math.sin(t) * 100 97 | hsv.v = Math.min(4 + updates[Math.floor(i / 4)] / 1.5, 100) 98 | rgb = tinycolor(hsv).toRgb() 99 | d.style.emissive = [rgb.r / 255, rgb.g / 255, rgb.b / 255] 100 | }) 101 | t += 0.01 102 | } 103 | 104 | function swirl (p, r, t, s) { 105 | var dx = Math.sin(t + r) 106 | var dy = Math.cos(t + r) 107 | return [p[0] + dx, p[1] + dy, p[2] + dx + dy] 108 | } 109 | -------------------------------------------------------------------------------- /sphere-party/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sphere-party", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "browserify index.js -o site/bundle.js" 8 | }, 9 | "author": "freeman-lab", 10 | "license": "MIT", 11 | "dependencies": { 12 | "canvas-fit": "^1.5.0", 13 | "canvas-orbit-camera": "^1.0.2", 14 | "gl-context": "^0.1.1", 15 | "gl-scene": "^1.0.1", 16 | "icosphere": "^1.0.0", 17 | "right-now": "^1.0.0", 18 | "tinycolor2": "^1.3.0", 19 | "web-audio-analyser": "^2.0.1" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /sphere-party/site/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /unknown-party/index.js: -------------------------------------------------------------------------------- 1 | var regl = require('regl')() 2 | var mat4 = require('gl-mat4') 3 | var hsv2rgb = require('hsv2rgb') 4 | var ndarray = require('ndarray') 5 | var Analyser = require('web-audio-analyser') 6 | var blur = require('ndarray-gaussian-filter') 7 | 8 | var analyser 9 | 10 | navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia 11 | 12 | navigator.getUserMedia({audio: true, video: false, muted: true}, function(stream) { 13 | analyser = Analyser(stream, {audible: false, stereo: false}) 14 | }, function (err) { 15 | }) 16 | 17 | var count = 60 18 | 19 | var draw = regl({ 20 | frag: [ 21 | 'precision mediump float;', 22 | 'void main() {', 23 | ' gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);', 24 | '}' 25 | ].join('\n'), 26 | 27 | vert: [ 28 | 'precision mediump float;', 29 | 'attribute vec2 position;', 30 | 'uniform float offset;', 31 | 'void main() {', 32 | ' gl_Position = vec4(position.x, position.y - offset, 0.0, 1.0);', 33 | ' gl_PointSize = 20.0;', 34 | '}' 35 | ].join('\n'), 36 | 37 | attributes: { 38 | position: regl.prop('position') 39 | }, 40 | 41 | uniforms: { 42 | offset: regl.prop('offset') 43 | }, 44 | 45 | lineWidth: 4, 46 | 47 | primitive: 'line strip', 48 | 49 | count: count 50 | }) 51 | 52 | var out = [] 53 | var updates = [] 54 | var xy = [] 55 | var tmp = [] 56 | var i, j, raw, blurred, background, freq 57 | 58 | var color = [] 59 | var oldchoice = 0 60 | var choice = 0 61 | var interval = 400 62 | var flag = 0 63 | color[0] = hsv2rgb(192, 0.8, 0.0) 64 | color[1] = hsv2rgb(192, 0.8, 0.7) 65 | color[2] = hsv2rgb(100, 0.8, 0.7) 66 | color[3] = hsv2rgb(335, 0.8, 0.7) 67 | 68 | var buffers = [] 69 | for (j = 0; j < 20; j++) { 70 | buffers[j] = regl.buffer(60) 71 | } 72 | 73 | regl.frame(function (count) { 74 | 75 | if (analyser) { 76 | freq = analyser.frequencies().slice(5, 605) 77 | 78 | if ((count % interval) == 0) { 79 | oldchoice = choice 80 | flag = 0 81 | while (flag == 0) { 82 | choice = Math.round(Math.random() * 3) 83 | if (!(choice == oldchoice)) flag = 1 84 | } 85 | } 86 | 87 | regl.clear({ 88 | color: [color[choice][0] / 255, color[choice][1] / 255, color[choice][2] / 255, 1] 89 | }) 90 | 91 | for (j = 0; j < 20; j++) { 92 | raw = Array(15).fill(0).concat(Array.prototype.slice.call(freq.slice(j * 30, (j + 1) * 30))).concat(Array(15).fill(0)) 93 | blur(ndarray(raw), 1) 94 | for (i = 0; i < 60; i++) { 95 | out[i] = [((i - (60 / 2)) / (60 / 2) + 1 / 60) * 0.9, Math.random() / 100 + raw[i] / 300] 96 | } 97 | updates[j] = {position: buffers[j](out), offset: -j / (10 + 1.1) + 0.9} 98 | } 99 | 100 | for (j = 0; j < 20; j++) { 101 | draw(updates[j]) 102 | } 103 | } 104 | }) -------------------------------------------------------------------------------- /unknown-party/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "unknown-party", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "browserify index.js -o site/bundle.js", 8 | "deploy": "surge site unknown-party.surge.sh" 9 | }, 10 | "author": "freeman-lab", 11 | "license": "MIT", 12 | "dependencies": { 13 | "hsv2rgb": "^1.1.0", 14 | "ndarray": "^1.0.18", 15 | "ndarray-gaussian-filter": "^1.0.0", 16 | "regl": "^0.3.0", 17 | "web-audio-analyser": "^2.0.1" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /unknown-party/site/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | --------------------------------------------------------------------------------