hacky web audio api + d3.
instructions
allow mic access, talk at computer.
| <!DOCTYPE html> | |
| <html lang='en'> | |
| <head> | |
| <meta charset='utf-8'> | |
| <style> | |
| body, #map, svg, canvas { | |
| margin: 0; | |
| padding: 0; | |
| } | |
| #stroke-text { | |
| stroke: #1a1a1a; | |
| stroke-width: 0.5; | |
| fill: none; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <script src='//d3js.org/d3.v3.min.js' charset='utf-8'></script> | |
| <script> | |
| var w = 900, | |
| h = 650, | |
| halfH = h/2; | |
| var svg = d3.select('body').append('svg') | |
| .attr({width: w, height: h}); | |
| var clipPath = svg.append('defs') | |
| .append('clipPath').attr({id: 'text-clip'}); | |
| var text = clipPath.append('text') | |
| .attr({ | |
| x: w/2, | |
| y: halfH, | |
| // right side of the data is rarely active | |
| // there's cleaner ways to do this but meh | |
| textLength: w * 0.8 + 'px' | |
| }) | |
| .style({ | |
| 'text-anchor': 'middle', | |
| font: '200px sans-serif' | |
| }) | |
| .text('d3.unconf'); | |
| var g = svg.append('g').style('clip-path', 'url(#text-clip)'); | |
| var strokeText = text.node().cloneNode(true); | |
| strokeText.id = 'stroke-text'; | |
| svg.node().appendChild(strokeText); | |
| var audioContext = new AudioContext(), | |
| analyser = audioContext.createAnalyser(); | |
| analyser.fftSize = 256; | |
| var size = analyser.frequencyBinCount, | |
| barw = w / size, | |
| barw = Math.ceil((w - 2 * barw) / size), | |
| min = 1, | |
| mod = (h-5*min)/2+min, | |
| bottom = halfH + (5*min); | |
| // mod = h - 20; | |
| // overshoot hue on purpose | |
| // var hue = d3.scale.linear().domain([0, 128]).range([140, 0]); | |
| var color = d3.scale.linear() | |
| .domain([0, 255]) | |
| .range(['#ffba00', '#007eff']) | |
| .interpolate(d3.interpolateHcl); | |
| var bfBuf = new Uint8Array(size), | |
| tdBuf = new Uint8Array(size); | |
| navigator.webkitGetUserMedia({audio: true}, function (stream) { | |
| var microphone = audioContext.createMediaStreamSource(stream); | |
| microphone.connect(analyser); | |
| go(); | |
| }, function (err) { console.log(err); }); | |
| function go () { | |
| requestAnimationFrame(go); | |
| render(); | |
| } | |
| function render () { | |
| analyser.getByteFrequencyData(bfBuf); | |
| var rect = g.selectAll('rect') | |
| .data(bfBuf); | |
| rect.enter().append('rect') | |
| .attr({ | |
| x: function (d, i) { | |
| return i * barw + barw; | |
| }, | |
| width: barw | |
| }) | |
| .style({'stroke-width': 1}); | |
| // inefficient | |
| rect.attr({ | |
| height: function (d) { | |
| return d/255 * mod + min; | |
| }, | |
| y: function (d) { | |
| return bottom - (d/255 * mod + min); | |
| }, | |
| fill: function (d) { | |
| // return 'hsl(' + ~~hue(d) + ', 100%, 70%)'; | |
| return color(d); | |
| }, | |
| stroke: function (d) { | |
| return color(d); | |
| } | |
| }); | |
| rect.exit().remove(); | |
| } | |
| </script> | |
| </body> | |
| </html> |