hacky web audio api + d3.
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> |