-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathindex.html
140 lines (131 loc) · 3.84 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
<!DOCTYPE html>
<html style="height: 100vh">
<body style="margin: 0; padding: 0; height: 100%; width: 100%; display: flex">
<textarea
id="code"
spellcheck="false"
style="
width: 100%;
font-size: 24px;
color: white;
background: black;
border: 0;
outline: none;
padding: 10px;
"
></textarea>
<button
style="position: fixed; right: 0; bottom: 0; font-size: 30px"
id="playButton"
>
play
</button>
<button
style="
display: none;
position: fixed;
right: 0;
bottom: 0;
font-size: 30px;
"
id="stopButton"
>
stop
</button>
<script>
// audio init
console.log(
`Welcome to doughbeat, a very minimal bytebeat livecoding editor..
Click into the page and press ctrl+enter to evaluate the code!
There are no further instructions. Read https://github.com/felixroos/doughbeat to find out more!
`
);
let ac;
document.addEventListener("click", function initAudio() {
ac = new AudioContext();
ac.resume();
document.removeEventListener("click", initAudio);
});
async function getSimpleDynamicWorklet(ac, code, hz = ac.sampleRate) {
const name = `simple-custom-${Date.now()}`;
let srcSampleRate = hz || ac.sampleRate;
// let sampleRatio = srcSampleRate / ac.sampleRate;
const workletCode = `${code}
class MyProcessor extends AudioWorkletProcessor {
constructor() {
super();
this.t = 0;
this.stopped = false;
this.port.onmessage = (e) => {
if(e.data==='stop') {
this.stopped = true;
}
};
}
process(inputs, outputs, parameters) {
const output = outputs[0];
for (let i = 0; i < output[0].length; i++) {
const out = dsp(this.t / ${ac.sampleRate});
output.forEach((channel) => {
channel[i] = out;
});
this.t++;
}
return !this.stopped;
}
}
registerProcessor('${name}', MyProcessor);
`;
const base64String = btoa(workletCode);
const dataURL = `data:text/javascript;base64,${base64String}`;
await ac.audioWorklet.addModule(dataURL);
const node = new AudioWorkletNode(ac, name);
const stop = () => node.port.postMessage("stop");
return { node, stop };
}
// control
let worklet,
hz = 44100;
const stop = async () => {
worklet?.stop();
worklet?.node?.disconnect();
stopButton.style.display = "none";
playButton.style.display = "block";
};
const update = async (code) => {
ac = ac || new AudioContext();
await ac.resume();
stop();
worklet = await getSimpleDynamicWorklet(ac, code, hz);
worklet.node.connect(ac.destination);
window.location.hash = "#" + btoa(code);
stopButton.style.display = "block";
playButton.style.display = "none";
};
// ui
const input = document.getElementById("code");
const playButton = document.getElementById("playButton");
const stopButton = document.getElementById("stopButton");
let urlCode = window.location.hash.slice(1);
if (urlCode) {
urlCode = atob(urlCode);
console.log("loaded code from url!");
}
const initialCode =
urlCode ||
`function dsp(t) {
return (110 * t % 1 - 0.5) / 10
}`;
input.value = initialCode;
input.addEventListener("keydown", (e) => {
if ((e.ctrlKey || e.altKey) && e.key === "Enter") {
update(input.value);
} else if ((e.ctrlKey || e.altKey) && e.code === "Period") {
stop();
}
});
playButton.addEventListener("click", () => update(input.value));
stopButton.addEventListener("click", () => stop());
</script>
</body>
</html>