Created
October 19, 2022 18:03
-
-
Save ChunMinChang/b2c1bf4c15f255c4c4a86f0f90bad0c9 to your computer and use it in GitHub Desktop.
Test page for multi-mics in WebAudio, created by Paul Adenot
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<html><head> | |
<meta http-equiv="content-type" content="text/html; charset=UTF-8"><meta charset="utf-8"> | |
<style> | |
* { | |
box-sizing: border-box; | |
} | |
.wrapper { | |
border: 1px dashed black; | |
padding: 1em; | |
max-width: 50vw; | |
} | |
canvas { | |
border: 1px solid gray; | |
} | |
.error { | |
color: red; | |
font-weight: bold; | |
} | |
.ok { | |
color: darkgreen; | |
font-weight: bold; | |
} | |
#log { | |
width: 40vw; | |
float: right; | |
border-left: 1px dotted gray; | |
padding-left: 0.5em; | |
} | |
pre > span { | |
border-left: 1px solid lightgray; | |
padding-left: 0.5em; | |
} | |
</style> | |
</head><body data-new-gr-c-s-check-loaded="8.904.0" data-gr-ext-installed=""><button id="newgum">New gUM request</button> | |
<pre id="log">Event log: | |
</pre> | |
<form> | |
<label for="aec">AEC</label> | |
<input id="aec" type="checkbox"> | |
<label for="agc">AGC</label> | |
<input id="agc" type="checkbox"> | |
<label for="ns">NS</label> | |
<input id="ns" type="checkbox"> | |
<label for="channelCount">ChannelCount</label> | |
<input id="channelCount" type="number" min="1" max="32"> | |
<select id="devicelist"> | |
</select> | |
</form> | |
<script> | |
function error(str) { | |
log.innerHTML+=`<span class=error>${str}</span></br>`; | |
} | |
function info(str) { | |
log.innerHTML+=`<span class=info>${str}</span></br>`; | |
} | |
function ok(str) { | |
log.innerHTML+=`<span class=ok>${str}</span></br>`; | |
} | |
var ac = new AudioContext(); | |
function $(selector, root) { | |
if (!root) { | |
return document.querySelector(selector); | |
} | |
return root.querySelector(selector); | |
} | |
function ce(name, classes, html) { | |
var e = document.createElement(name); | |
e.className = classes; | |
e.innerHTML = html; | |
return e; | |
} | |
function constraintsFromDOM(root) { | |
var constraints = {}; | |
constraints.echoCancellation = $("#aec", root).checked; | |
constraints.autoGainControl = $("#agc", root).checked; | |
constraints.noiseSuppression = $("#ns", root).checked; | |
if (devicelist.length) { | |
constraints.deviceId = { | |
"exact": $("#devicelist", root).options[devicelist.selectedIndex].dataset.id | |
}; | |
} | |
var channelCount = $("#channelCount", root).value; | |
if (channelCount) { | |
constraints.channelCount = channelCount; | |
} | |
return constraints; | |
} | |
function constraintsToDOM(constraints, root) { | |
$("#aec", root).checked = constraints.echoCancellation; | |
$("#agc", root).checked = constraints.autoGainControl; | |
$("#ns", root).checked = constraints.noiseSuppression; | |
if (channelCount) { | |
$("#channelCount", root).value = constraints.channelCount; | |
} | |
} | |
var newGum = $('#newgum'); | |
var gums = []; | |
newGum.onclick = function() { | |
ac.resume(); | |
var constraints = constraintsFromDOM(document.body); | |
info(`new gUM (${JSON.stringify(constraints, null, 2)})`); | |
navigator.mediaDevices.getUserMedia({audio: constraints}).then(setUpNewGum).then(function() { | |
navigator.mediaDevices.enumerateDevices({audio: true}).then(function(e) { | |
devicelist.innerHTML = ""; | |
for (var i = 0; i < e.length; i++) { | |
if (e[i].kind != "audioinput") { | |
continue; | |
} | |
var o = document.createElement("option"); | |
o.dataset.id = e[i].deviceId; | |
o.innerHTML = e[i].label; | |
devicelist.appendChild(o); | |
} | |
}); | |
} | |
); | |
} | |
navigator.mediaDevices.ondevicechange = function() { | |
info("devicechange event fired"); | |
} | |
function setUpCanvas(root, gum, count) { | |
var canvases = root.querySelector("canvas"); | |
if (canvases) { | |
for (var i = 0; i < canvases.length; i++) { | |
canvases.remove(); | |
} | |
} | |
gum.canvases = []; | |
gum.contexts = []; | |
for (var i = 0; i < count; i++) { | |
var cvs = document.createElement("canvas"); | |
cvs.width = 512; | |
cvs.height = 256; | |
gum.canvases.push(cvs); | |
gum.contexts.push(cvs.getContext("2d")); | |
root.appendChild(cvs); | |
} | |
} | |
function setUpAnalysers(gum, count) { | |
if (gum.splitter) { | |
gum.analysers.forEach((e) => e.disconnect()); | |
gum.splitter.disconnect(); | |
gum.sourceNode.disconnect(); | |
} | |
gum.splitter = null; | |
gum.analysers = []; | |
gum.analysisBuffers = []; | |
gum.splitter = ac.createChannelSplitter(count); | |
gum.sourceNode = ac.createMediaStreamSource(gum.stream); | |
gum.sourceNode.connect(gum.splitter); | |
for (var i = 0; i < count; i++) { | |
var an = ac.createAnalyser(); | |
an.fftSize = gum.canvases[0].width * 2; | |
gum.splitter.connect(an, i, 0); | |
gum.analysers.push(an); | |
gum.analysisBuffers.push(new Uint8Array(an.frequencyBinCount)); | |
} | |
} | |
function setUpNewGum(mediaStream) { | |
ok(`gUM succeeded on device ${mediaStream.getAudioTracks()[0].label}`); | |
var oneMic = ` | |
<button id=newgum>applySettings</button> | |
<form> | |
<label for='aec'>AEC</label> | |
<input id='aec' type=checkbox> | |
<label for='agc'>AGC</label> | |
<input id='agc' type=checkbox> | |
<label for='ns'>NS</label> | |
<input id='ns' type=checkbox> | |
<label for='channelCount'>ChannelCount</label> | |
<input id='channelCount' type=number min=1 max=4> | |
</form>`; | |
var root = ce("div", "wrapper", oneMic); | |
var gum = {}; | |
gum.stream = mediaStream; | |
gum.channelCount = | |
gum.stream.getAudioTracks()[0].getSettings().channelCount; | |
gum.analysers = []; | |
setUpCanvas(root, gum, gum.channelCount); | |
setUpAnalysers(gum, gum.channelCount); | |
gum.sourceNode.connect(ac.destination); | |
root.querySelector("button").onclick = function() { | |
var c = constraintsFromDOM(root); | |
gum.stream.getAudioTracks()[0].applyConstraints(c).then(() => { | |
var actualConstraints = gum.stream.getAudioTracks()[0].getSettings(); | |
constraintsToDOM(actualConstraints, root); | |
}); | |
} | |
document.body.appendChild(root); | |
var actualConstraints = gum.stream.getAudioTracks()[0].getSettings(); | |
constraintsToDOM(actualConstraints, root); | |
gums.push(gum); | |
return Promise.resolve(); | |
} | |
function render() { | |
for (var i = 0; i < gums.length; i++) { | |
if (gums[i].contexts.length != gums[i].analysers.length) { | |
throw "meh"; | |
} | |
for (var j = 0; j < gums[i].contexts.length; j++) { | |
var c = gums[i].contexts[j]; | |
var cvs = gums[i].canvases[j]; | |
var an = gums[i].analysers[j]; | |
var buf = gums[i].analysisBuffers[j]; | |
c.clearRect(0, 0, cvs.width, cvs.height); | |
an.getByteFrequencyData(buf); | |
for (var k = 0; k < cvs.width; k++) { | |
c.fillRect(k * 2, cvs.height, 1, -buf[k]); | |
} | |
} | |
} | |
requestAnimationFrame(render); | |
} | |
render(); | |
</script> | |
</body></html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment