|
<!DOCTYPE html> |
|
<head> |
|
<meta charset="utf-8"> |
|
<script src="https://d3js.org/d3-path.v0.1.min.js"></script> |
|
<script src="https://d3js.org/d3-timer.v0.2.min.js"></script> |
|
</head> |
|
|
|
<body> |
|
<canvas></canvas> |
|
<script> |
|
|
|
// Grid 2 |
|
// Web Audio works w/ Chrome & FF |
|
// Put https:// before the codepen URL |
|
// as WebAudio needs SSL |
|
|
|
/* |
|
* D3 lookalike functionality |
|
*/ |
|
|
|
// We don't have a d3 object as we selectively import plugins |
|
const d3 = Object.assign( |
|
{}, |
|
d3_path, |
|
d3_timer |
|
) |
|
|
|
// We have no d3.select or d3.selectAll, so... no data binding |
|
function select(s) { |
|
return document.querySelector(s) |
|
} |
|
|
|
function attr(element, attribute, value) { |
|
element.setAttribute(attribute, value) |
|
} |
|
|
|
function style(element, property, value) { |
|
element.style[property] = value |
|
} |
|
|
|
/* |
|
* Setup the canvas |
|
*/ |
|
|
|
const width = document.documentElement.clientWidth |
|
const height = document.documentElement.clientHeight |
|
const aaMultiple = window.devicePixelRatio |
|
|
|
const canvas = select('canvas') |
|
|
|
attr(canvas, 'width', width * aaMultiple) |
|
attr(canvas, 'height', height * aaMultiple) |
|
|
|
style(canvas, 'width', 100 + '%') |
|
style(canvas, 'height', 100 + '%') |
|
|
|
const canvasContext = canvas.getContext('2d') |
|
|
|
/* |
|
* Render a circle |
|
*/ |
|
|
|
const lineWidth = 0.5 |
|
const radius = 31 |
|
|
|
function render(context, x, y, radius, s) { |
|
const r = radius * s |
|
context.moveTo(x * s + r, y * s) |
|
context.arc(x * s , y * s, r, 0, 2 * Math.PI) |
|
} |
|
|
|
/* |
|
* Render the audio visualisation |
|
*/ |
|
|
|
canvasContext.strokeStyle = 'rgba(0,0,0,1)' |
|
canvasContext.fillStyle = 'rgba(255,255,255,.1)' |
|
|
|
function renderOnCanvas(context, frequencyData) { |
|
context.lineWidth = aaMultiple * lineWidth |
|
context.fillRect( |
|
0, 0, |
|
width * aaMultiple, height * aaMultiple |
|
) |
|
context.beginPath() |
|
for(let j = 0; j < 10; j++) { |
|
for(let i = 0; i < 10; i++) { |
|
render( |
|
context, |
|
(i + 1 ) * 2 * radius //, |
|
+ (j % 2) * radius, // hex |
|
-radius / 2 + (9 - j + 1) * 2 // * radius, |
|
* (radius ** 2 - (radius / 2) ** 2) ** 0.5, |
|
frequencyData[(i + 10 * j) * 2] * radius / 255, |
|
aaMultiple |
|
) |
|
} |
|
} |
|
context.stroke() |
|
} |
|
|
|
/* |
|
* Audio input and analyser |
|
*/ |
|
|
|
navigator.getUserMedia = MediaDevices.getUserMedia |
|
|| navigator.webkitGetUserMedia |
|
|| navigator.mozGetUserMedia |
|
const audioApi = new AudioContext() |
|
|
|
function makeAnalyser(source) { |
|
const analyser = audioApi.createAnalyser() |
|
analyser.fftSize = 2048 |
|
analyser.smoothingTimeConstant = 0.5 |
|
source.connect(analyser) |
|
return analyser |
|
} |
|
|
|
function onStream(stream) { |
|
const source = audioApi.createMediaStreamSource(stream) |
|
const analyser = makeAnalyser(source) |
|
const frequencyData = new Uint8Array(1024) |
|
d3.timer(t => { |
|
// side effect: refresh frequencyData |
|
analyser.getByteFrequencyData(frequencyData) |
|
// side effect: rerender on canvas |
|
renderOnCanvas(canvasContext, frequencyData) |
|
}) |
|
} |
|
|
|
navigator.getUserMedia( |
|
{audio:true}, |
|
onStream, |
|
function(error) { |
|
console.log("Error: " + error) |
|
} |
|
) |
|
|
|
|
|
</script> |
|
</body> |