Skip to content

Instantly share code, notes, and snippets.

@soxofaan

soxofaan/.block

Last active Aug 20, 2019
Embed
What would you like to do?
Microphone pitch detection
license: mit
height: 400
border: no
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font-family: sans-serif;
}
.piano rect.key {
stroke: #111111;
}
.piano rect.key.white {
fill: #ffffff;
}
.piano rect.key.black {
fill: #000000;
}
</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 = 1024 * 8
analyser.fftSize = fftSize
analyser.smoothingTimeConstant = 0.7
source.connect(analyser)
function binFromFreq(f) {
return Math.round(fftSize * f / ctx.sampleRate);
}
function FreqFromBin(b) {
return b * ctx.sampleRate / fftSize;
}
function PitchFromFreq(f) {
return (Math.round(12 * (Math.log2(f) - Math.log2(tuning))) % 12 + 12) % 12
}
var bins = analyser.frequencyBinCount
var tuning = 440
var minFrequency = tuning / 4
var maxFrequency = tuning * 6
var minBin = binFromFreq(minFrequency)
var maxBin = binFromFreq(maxFrequency)
var frequencyData = new Float32Array(maxBin);
console.log(minFrequency, maxFrequency, minBin, maxBin)
function getPitchIntensities() {
// Get spectrum data from analyser
analyser.getFloatFrequencyData(frequencyData)
// Average energy for each pitch
var intensities = new Float64Array(12)
var counts = new Uint32Array(12)
for (var i=minBin; i <= maxBin; i++) {
var p = PitchFromFreq(FreqFromBin(i))
var db = frequencyData[i]
if (!isNaN(db)) {
intensities[p] += Math.pow(10, frequencyData[i] / 10)
counts[p] += 1
}
}
for (var p=0; p<12; p++) {
intensities[p] = 10 * Math.log10(intensities[p] / counts[p])
}
return intensities;
}
// Set up D3 elements
var width = 960
var height = 400
var svg = target.html("").append("svg").attr("width", width).attr("height", height)
var padding = 30
var round = 8
var whiteHeight = height - 2 * padding
var blackHeight = 0.8 * whiteHeight
var plot = svg.append("g")
.attr("class", "plot")
.attr("transform", "translate(" + padding + "," + (padding) + ")")
var xScale = d3.scaleLinear().domain([-0.5, 11.5]).range([0, width - 2 * padding])
var yScale = d3.scaleLinear().domain([-60, -40]).range([0, blackHeight]).clamp(true)
var wScale = yScale.copy().range([0, xScale(1) - xScale(0)]).clamp(true)
var colorScale = d3.scaleLinear().domain([-60, -45, -30]).range(["#bb0000", "#ffee00", "#66bb00"]).clamp(true)
var bandWidth = xScale(1) - xScale(0)
var pianoBack = plot.append("g").attr("class", "piano")
var white = pianoBack.append("g").attr("class", "white")
var whiteDims = [[-0.5, 1.5], [1, 2], [3, 1.5], [4.5, 1.5], [6, 2], [8, 2], [10, 1.5]]
whiteDims.forEach(function(d) {
white.append("rect")
.attr("class", "key white")
.attr("x", xScale(d[0])).attr("y", -round)
.attr("width", d[1] * bandWidth).attr("height", whiteHeight + round)
.attr("rx", round).attr("ry", round)
})
var black = pianoBack.append("g").attr("class", "black")
var blackDims = [0.5, 2.5, 5.5, 7.5, 9.5]
blackDims.forEach(function (d) {
black.append("rect")
.attr("class", "key black")
.attr("x", xScale(d)).attr("y", -round)
.attr("width", bandWidth).attr("height", blackHeight + round)
.attr("rx", round).attr("ry", round)
})
var barPlot = plot.append("g")
.attr("class", "bars")
var pianoFront = plot.append("g").attr("class", "piano")
pianoFront.append("rect")
.attr("x", xScale(-1)).attr("y", -padding)
.attr("width", xScale(12) - xScale(-1)).attr("height", padding)
.attr("fill", "#333333")
// Draw function
function draw(data) {
var bars = barPlot.selectAll("rect")
.data(data)
var enter = bars.enter()
.append("rect")
.attr("y", 0)
.attr("stroke", "#888888")
bars = bars.merge(enter)
bars
.attr("x", function (d, i) { return xScale((i + 9) % 12) - 0.5 * wScale(d); })
.attr("width", wScale)
.attr("height", yScale)
.attr("fill", colorScale)
}
// Set up animation toggling
var timeout = 50
function animate() {
data = getPitchIntensities()
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
You can’t perform that action at this time.