-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathaudioworkletnode-clap.mjs
128 lines (111 loc) · 3.52 KB
/
audioworkletnode-clap.mjs
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
let moduleAdded = Symbol();
function addRemoteMethods(node) {
}
export default class ClapModule {
url;
#m_modulePromise;
constructor(moduleOptions) {
if (typeof moduleOptions === 'string') moduleOptions = {url: moduleOptions};
let url = moduleOptions?.url;
// If we specify a directory (ends in `/`) then use `module.wasm`
if (/\.(wasm-clap|wclap)$/.test(url)) url += '/'; // Assume .wasm-clap is a bundle directory
if (/\/$/.test(url)) url += "module.wasm";
this.url = new URL(url, location.href).href;
this.#m_modulePromise = moduleOptions.module || WebAssembly.compileStreaming(fetch(this.url));
}
async createNode(audioContext, pluginId, nodeOptions) {
if (!nodeOptions && typeof pluginId === 'object') {
nodeOptions = pluginId;
pluginId = null;
}
nodeOptions = nodeOptions || {
numberOfInputs: 1,
numberOfOutputs: 1,
outputChannelCount: [2],
};
nodeOptions.processorOptions = {
url: this.url,
module: await this.#m_modulePromise,
pluginId: pluginId
};
if (!audioContext[moduleAdded]) {
await audioContext.audioWorklet.addModule('./audioworkletprocessor-clap.mjs');
}
audioContext[moduleAdded] = true;
let effectNode = new AudioWorkletNode(audioContext, 'audioworkletprocessor-clap', nodeOptions);
let responseMap = Object.create(null);
let idCounter = 0;
function addRemoteMethod(name) {
effectNode[name] = (...args) => {
let requestId = idCounter++;
effectNode.port.postMessage([requestId, name, args]);
return new Promise((pass, fail) => {
responseMap[requestId] = {m_pass: pass, m_fail: fail};
});
};
}
// Hacky event-handling
effectNode.events = {};
return new Promise(resolve => {
effectNode.port.onmessage = e => {
let {desc, methods, web} = e.data;
effectNode.descriptor = desc;
methods.forEach(addRemoteMethod);
let iframe = null;
effectNode.port.onmessage = e => {
let data = e.data;
if (data instanceof ArrayBuffer) {
// it's a message from the plugin
if (iframe) iframe.contentWindow.postMessage(data, '*');
return;
}
if (typeof data[0] === 'string') {
// it's an event - call a handler if there is one
let handler = effectNode.events[data[0]];
if (handler) {
handler(data[1]);
} else {
console.error("unhandled event:", ...data);
}
return;
}
let response = responseMap[data[0]];
if (data[1]) {
response.m_fail(data[1]);
} else {
response.m_pass(data[2]);
}
};
if (web) {
let messageHandler = e => {
if (e.source === iframe?.contentWindow) {
let data = e.data;
if (!(data instanceof ArrayBuffer)) throw Error("messages must be ArrayBuffers");
effectNode.port.postMessage(data);
}
};
let visibilityHandler;
effectNode.openInterface = () => {
iframe = document.createElement('iframe');
window.addEventListener('message', messageHandler);
window.addEventListener('visibilitychange', visibilityHandler = () => {
effectNode.webOpen(true, !document.hidden);
});
iframe.src = new URL(web.startPage, this.url);
effectNode.webOpen(true, !document.hidden);
return iframe;
};
effectNode.closeInterface = () => {
effectNode.webOpen(false);
if (iframe) {
window.removeEventListener('message', messageHandler);
window.removeEventListener('visibilitychange', visibilityHandler);
}
iframe = null;
}
}
resolve(effectNode);
};
});
}
}