260 lines
7.5 KiB
HTML
260 lines
7.5 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>{{=it.product.name}}</title>
|
|
<script type="module">
|
|
import * as demo from "./{{=it.product.bundleName}}.js";
|
|
window.demo = demo;
|
|
</script>
|
|
<script>
|
|
var audioCtx, midi;
|
|
var module, node;
|
|
var hasAudioInput, hasMidiInput;
|
|
|
|
var Player = {
|
|
sourceBuffer: null,
|
|
playing: false,
|
|
started: false,
|
|
|
|
load: function (buffer, successCb, errorCb) {
|
|
let t = this;
|
|
audioCtx.decodeAudioData(buffer,
|
|
function (data) {
|
|
if (t.started)
|
|
t.sourceBuffer.stop();
|
|
if (t.playing)
|
|
t.sourceBuffer.disconnect();
|
|
t.sourceBuffer = audioCtx.createBufferSource();
|
|
t.sourceBuffer.buffer = data;
|
|
t.sourceBuffer.loop = true;
|
|
if (t.started)
|
|
t.sourceBuffer.start();
|
|
if (t.playing) {
|
|
t.started = true;
|
|
t.sourceBuffer.connect(node);
|
|
}
|
|
successCb();
|
|
},
|
|
function () { errorCb(); });
|
|
},
|
|
|
|
togglePlayPause: function () {
|
|
if (this.playing) {
|
|
this.sourceBuffer.disconnect();
|
|
this.playing = false;
|
|
} else {
|
|
if (!this.started) {
|
|
this.sourceBuffer.start();
|
|
this.started = true;
|
|
}
|
|
this.sourceBuffer.connect(node);
|
|
this.playing = true;
|
|
}
|
|
}
|
|
};
|
|
|
|
function map(index, value) {
|
|
var p = demo.Module.data.product.parameters[index];
|
|
return p.map == "logarithmic" ? p.minimum * Math.exp((2.0 * Math.log(Math.sqrt(p.maximum * p.minimum) / Math.abs(p.minimum))) * value) : p.minimum + (p.maximum - p.minimum) * value;
|
|
}
|
|
|
|
function unmap(index, value) {
|
|
var p = demo.Module.data.product.parameters[index];
|
|
return p.map == "logarithmic" ? Math.log(value / p.minimum) / (2.0 * Math.log(Math.sqrt(p.maximum * p.minimum) / Math.abs(p.minimum))) : (value - p.minimum) / (p.maximum - p.minimum);
|
|
}
|
|
|
|
var initState = 0; // 0 = not inited, 1 = in progress, 2 = inited
|
|
|
|
window.addEventListener("load", function (e) {
|
|
var start = document.getElementById("start");
|
|
var starting = document.getElementById("starting");
|
|
var main = document.getElementById("main");
|
|
var player = document.getElementById("player");
|
|
var file = document.getElementById("file");
|
|
var playPause = document.getElementById("playPause");
|
|
var controls = document.getElementById("controls");
|
|
|
|
hasAudioInput = demo.Module.data.product.buses.filter(x => x.type == "audio" && x.direction == "input").length > 0;
|
|
hasMidiInput = demo.Module.data.product.buses.filter(x => x.type == "midi" && x.direction == "input").length > 0;
|
|
|
|
if (hasMidiInput && !navigator.requestMIDIAccess)
|
|
alert("Your browser doesn't support the Web MIDI API");
|
|
|
|
player.hidden = !hasAudioInput;
|
|
|
|
// reset on refresh
|
|
file.value = "";
|
|
playPause.disabled = true;
|
|
|
|
var parameters = demo.Module.data.product.parameters;
|
|
for (var i = 0; i < parameters.length; i++) {
|
|
var div = document.createElement("div");
|
|
|
|
var label = document.createElement("label");
|
|
label.setAttribute("for", "p" + i);
|
|
label.innerText = parameters[i].name;
|
|
|
|
var value = document.createElement("span");
|
|
value.setAttribute("id", "v" + i);
|
|
value.innerText = parameters[i].defaultValue;
|
|
|
|
var range = document.createElement("input");
|
|
range.setAttribute("type", "range");
|
|
range.setAttribute("id", "p" + i);
|
|
range.setAttribute("name", "p" + i);
|
|
if (parameters[i].isBypass || parameters[i].toggled) {
|
|
range.setAttribute("min", 0);
|
|
range.setAttribute("max", 1);
|
|
range.setAttribute("step", 1);
|
|
} else {
|
|
range.setAttribute("min", 0);
|
|
range.setAttribute("max", 1);
|
|
range.setAttribute("step", parameters[i].integer ? 1 / (parameters[i].maximum - parameters[i].minimum) : "any");
|
|
}
|
|
range.value = unmap(i, parameters[i].defaultValue);
|
|
if (parameters[i].direction == "output")
|
|
range.setAttribute("readonly", "true");
|
|
else {
|
|
let index = i;
|
|
let v = value;
|
|
range.addEventListener("input", function (e) {
|
|
var p = node.parameters.get(parameters[index].name);
|
|
var val = map(index, e.target.value);
|
|
p.setValueAtTime(val, 0);
|
|
v.innerText = val;
|
|
});
|
|
}
|
|
|
|
div.appendChild(label);
|
|
div.appendChild(range);
|
|
div.appendChild(value);
|
|
controls.appendChild(div);
|
|
}
|
|
|
|
start.addEventListener("click", async function () {
|
|
initState = 1;
|
|
start.disabled = true;
|
|
starting.hidden = false;
|
|
|
|
try {
|
|
if (!audioCtx)
|
|
audioCtx = new AudioContext();
|
|
if (!module)
|
|
module = new demo.Module();
|
|
if (!midi && hasMidiInput)
|
|
midi = await navigator.requestMIDIAccess();
|
|
await module.init(audioCtx, "{{=it.product.bundleName}}_processor.js", "{{=it.product.bundleName}}.wasm");
|
|
node = new demo.Node(module);
|
|
node.connect(audioCtx.destination);
|
|
|
|
node.addEventListener("processorerror", function (e) {
|
|
initState = 0;
|
|
start.hidden = false;
|
|
start.disabled = false;
|
|
starting.hidden = true;
|
|
main.hidden = true;
|
|
|
|
alert("Processor error" + (e.message ? ": " + e.message : ""));
|
|
});
|
|
|
|
node.port.onmessage = function (e) {
|
|
if (e.data.type == "paramOutChange")
|
|
document.getElementById("p" + e.data.index).value = unmap(e.data.index, e.data.value);
|
|
document.getElementById("v" + e.data.index).innerText = e.data.value;
|
|
};
|
|
|
|
if (midi) {
|
|
function forwardMIDIMessage(msg) {
|
|
for (var i = 0; i < demo.Module.data.product.buses.length; i++) {
|
|
var b = demo.Module.data.product.buses[i];
|
|
if (b.type != "midi" || b.direction != "input")
|
|
continue;
|
|
var m = {};
|
|
for (p in msg)
|
|
m[p] = msg[p];
|
|
m.index = i;
|
|
node.port.postMessage(m);
|
|
}
|
|
}
|
|
|
|
function onMIDIMessage(e) {
|
|
switch (e.data[0] & 0xf0) {
|
|
case 0x90:
|
|
if (e.data[2] != 0)
|
|
forwardMIDIMessage({ type: "noteOn", note: e.data[1], velocity: e.data[2] / 127 });
|
|
else
|
|
forwardMIDIMessage({ type: "noteOff", note: e.data[1], velocity: 64 / 127 });
|
|
break;
|
|
case 0x80:
|
|
forwardMIDIMessage({ type: "noteOff", note: e.data[1], velocity: e.data[2] / 127 });
|
|
break;
|
|
case 0xb0:
|
|
switch(e.data[1]) {
|
|
case 120:
|
|
forwardMIDIMessage({ type: "allSoundsOff" });
|
|
break;
|
|
case 123:
|
|
forwardMIDIMessage({ type: "allNotesOff" });
|
|
break;
|
|
}
|
|
break;
|
|
case 0xd0:
|
|
forwardMIDIMessage({ type: "channelPressure", value: e.data[1] / 127 });
|
|
break;
|
|
case 0xe0:
|
|
forwardMIDIMessage({ type: "pitchBendChange", value: (e.data[2] << 7 | e.data[1]) / 8192 - 1 });
|
|
break;
|
|
}
|
|
}
|
|
midi.inputs.forEach(x => { x.onmidimessage = onMIDIMessage; });
|
|
}
|
|
|
|
initState = 2;
|
|
start.hidden = true;
|
|
starting.hidden = true;
|
|
main.hidden = false;
|
|
} catch (err) {
|
|
alert("Colud not initialize: " + err);
|
|
|
|
initState = 0;
|
|
start.disabled = false;
|
|
starting.hidden = true;
|
|
}
|
|
});
|
|
|
|
file.addEventListener("change", function () {
|
|
var fileReader = new FileReader();
|
|
fileReader.readAsArrayBuffer(this.files[0]);
|
|
fileReader.onload = function (e) {
|
|
Player.load(e.target.result,
|
|
function () { playPause.disabled = false; },
|
|
function () { alert("Could not decode the chosen file"); });
|
|
};
|
|
fileReader.onerror = function (e) { alert("Could not read file"); };
|
|
});
|
|
|
|
playPause.addEventListener("click", function () {
|
|
Player.togglePlayPause();
|
|
playPause.innerText = Player.playing ? "Pause" : "Play";
|
|
});
|
|
});
|
|
</script>
|
|
</head>
|
|
<body>
|
|
<h1>{{=it.product.name}}</h1>
|
|
<input id="start" type="button" value="Start">
|
|
<p id="starting" hidden>Starting...</p>
|
|
<div id="main" hidden>
|
|
<div id="player" hidden>
|
|
<h2>Player</h2>
|
|
<label for="file">Choose a file:</label>
|
|
<input type="file" id="file" name="file" accept="audio/*">
|
|
</div>
|
|
<button id="playPause" disabled>Play</button>
|
|
<h2>Controls</h2>
|
|
<div id="controls">
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html>
|