Skip to content

Instantly share code, notes, and snippets.

@ChunMinChang
Created October 19, 2022 18:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ChunMinChang/b2c1bf4c15f255c4c4a86f0f90bad0c9 to your computer and use it in GitHub Desktop.
Save ChunMinChang/b2c1bf4c15f255c4c4a86f0f90bad0c9 to your computer and use it in GitHub Desktop.
Test page for multi-mics in WebAudio, created by Paul Adenot
<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