tibia/templates/web-demo/src/index.html
2024-12-09 16:07:06 +01:00

303 lines
8.8 KiB
HTML

<!DOCTYPE html>
<!--
Tibia
Copyright (C) 2022-2024 Orastron Srl unipersonale
Tibia is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, version 3 of the License.
Tibia is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Tibia. If not, see <http://www.gnu.org/licenses/>.
File authors: Stefano D'Angelo, Paolo Marrone
-->
<html>
<head>
<title>{{=it.product.name}}</title>
<script type="module">
import * as demo from "./{{=it.product.bundleName}}/module.js";
window.demo = demo;
</script>
<script>
var audioCtx, midi;
var module, node;
var audioInputIndex, audioOutputIndex, 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, 0, audioInputIndex);
}
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, 0, audioInputIndex);
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 units = {
"bar": "bars",
"beat": "beats",
"bpm": "BPM",
"cent": "ct",
"cm": "cm",
"coef": "",
"db": "dB",
"degree": "deg",
"frame": "frames",
"hz": "Hz",
"inch": "\"",
"khz": "kHz",
"km": "km",
"m": "m",
"mhz": "MHz",
"midiNote": "MIDI note",
"mile": "mi",
"min": "mins",
"mm": "mm",
"ms": "ms",
"oct": "octaves",
"pc": "%",
"s": "s",
"semitone12TET": "semi"
};
function displayValue(elem, index, value) {
var param = demo.Module.data.product.parameters[index];
var unit = param.unit;
if (param.integer)
value = Math.round(value);
else
value = (0.01 * Math.round(100 * value)).toFixed(2);
elem.innerText = value + (unit in units ? " " + units[unit] : "");
}
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");
audioInputIndex = demo.Module.data.product.buses.filter(x => x.type == "audio" && x.direction == "input").findIndex(x => !x.cv && !x.sidechain);
audioOutputIndex = demo.Module.data.product.buses.filter(x => x.type == "audio" && x.direction == "output").findIndex(x => !x.cv && !x.sidechain);
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 = audioInputIndex < 0;
// 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);
displayValue(value, i, 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);
range.setAttribute("data-mapped", "false");
range.value = unmap(i, parameters[i].defaultValue);
} else if (parameters[i].integer) {
range.setAttribute("min", parameters[i].minimum);
range.setAttribute("max", parameters[i].maximum);
range.setAttribute("step", 1);
range.setAttribute("data-mapped", "true");
range.value = parameters[i].defaultValue;
} else {
range.setAttribute("min", 0);
range.setAttribute("max", 1);
range.setAttribute("step", "any");
range.setAttribute("data-mapped", "false");
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 m = e.target.getAttribute("data-mapped") == "true";
var val = m ? e.target.value : map(index, e.target.value);
p.setValueAtTime(val, 0);
displayValue(v, index, 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}}/module.wasm");
node = new demo.Node(module);
node.connect(audioCtx.destination, audioOutputIndex);
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") {
var r = document.getElementById("p" + e.data.index);
var v = document.getElementById("v" + e.data.index);
displayValue(v, e.data.index, e.data.value);
var m = r.getAttribute("data-mapped") == "true";
r.value = m ? e.data.value : unmap(e.data.index, e.data.value);
}
};
if (midi) {
function onMIDIMessage(e) {
if ((e.data[0] & 0xf0) == 0xf0)
return;
var msg = { type: "midi", data: e.data };
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;
msg.index = i;
node.port.postMessage(msg);
}
}
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>