Skip to content

Instantly share code, notes, and snippets.

@soxofaan
Last active August 15, 2019 21:55
Show Gist options
  • Save soxofaan/8a2ab706cbae8fb9c85eb02bf379bf12 to your computer and use it in GitHub Desktop.
Save soxofaan/8a2ab706cbae8fb9c85eb02bf379bf12 to your computer and use it in GitHub Desktop.
Microphone Spectrum Analyser
license: mit
height: 500
border: no
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font-family: sans-serif;
}
</style>
<body>
<div id="target">
<p>Access to microphone audio is required.</p>
</div>
<script src="https://d3js.org/d3.v5.min.js"></script>
<script>
function main() {
var target = d3.select("#target")
var AudioContext = window.AudioContext || window.webkitAudioContext
navigator.mediaDevices.getUserMedia({ audio: true })
.then(function(stream) { init(stream); })
.catch(function(reason) {
console.error(reason)
target.append("p").text("Failed to capture microphone audio.")
target.append("p").append("code").text(reason)
})
function init(stream) {
// Set up audio source and analyser
console.log("Start capturing microphone audio")
var ctx = new AudioContext();
var source = ctx.createMediaStreamSource(stream)
var analyser = ctx.createAnalyser()
var fftSize = 2048
analyser.fftSize = fftSize
analyser.smoothingTimeConstant = 0.7
analyser.maxDecibels = -20
analyser.minDecibels = -100
source.connect(analyser)
var maxFrequency = 5000
var limit = Math.floor(fftSize * maxFrequency / ctx.sampleRate)
var bins = analyser.frequencyBinCount
var data = new Uint8Array(limit);
// Set up D3 elements
var width = 960
var height = 500
var svg = target.html("").append("svg").attr("width", width).attr("height", height)
var padding = 30
var plot = svg.append("g")
.attr("class", "plot")
.attr("transform", "translate(" + padding + "," + (height - padding) + ")")
var xScale = d3.scaleLinear().domain([0, limit - 1]).range([0, width - 2 * padding])
var yScale = d3.scaleLinear().domain([0, 255]).range([0, -height + 2 * padding])
var colorScale = d3.interpolateRainbow
var axes = plot.append("g")
.attr("class", "axes")
.attr("font-size", 10)
axes.append("g").call(d3.axisBottom(xScale.copy().domain([0, limit * ctx.sampleRate / fftSize])))
axes.append("text")
.attr("class", "label")
.attr("text-anchor", "end")
.attr("x", width - 2 * padding).attr("y", -5)
.text("Frequency (Hz)")
axes.append("g").call(d3.axisLeft(yScale.copy().domain([analyser.minDecibels, analyser.maxDecibels])))
axes.append("text")
.attr("class", "label")
.attr("text-anchor", "start")
.attr("x", 5).attr("y", -height + 2 * padding)
.attr("dy", "0.5em")
.text("dB")
var chart = plot.append("g").attr("class", "chart")
// Draw functions
function fequencyColor(d, i) {
var x = Math.log2(i * ctx.sampleRate / fftSize)
return colorScale(x - Math.floor(x))
}
function draw(data) {
var lollipops = chart.selectAll("g.lollipop")
.data(data)
var enter = lollipops.enter()
.append("g").attr("class", "lollipop")
enter.append("line")
.attr("x1", function (d, i) { return xScale(i); })
.attr("y1", 0)
.attr("x2", function (d, i) { return xScale(i); })
.attr("stroke", fequencyColor)
enter.append("circle")
.attr("r", 2)
.attr("cx", function (d, i) { return xScale(i); })
.attr("fill", fequencyColor)
lollipops = lollipops.merge(enter)
lollipops
.select("circle")
.attr("cy", function (d, i) { return yScale(d); })
lollipops
.select("line")
.attr("y2", function (d, i) { return yScale(d); })
}
// Set up animation toggling
var timeout = 100
function animate() {
analyser.getByteFrequencyData(data)
draw(data)
setTimeout(animate, timeout)
}
animate()
}
}
document.addEventListener('DOMContentLoaded', main)
</script>
</body>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment