@grame/faustwasm 0.4.5 → 0.4.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/COPYING.txt +520 -0
- package/README.md +4 -4
- package/assets/standalone/create-node.js +78 -0
- package/assets/standalone/faust-ui/index.css +8 -0
- package/assets/standalone/faust-ui/index.css.map +1 -1
- package/assets/standalone/faust-ui/index.js +56 -30
- package/assets/standalone/faust-ui/index.js.map +1 -1
- package/assets/standalone/faustwasm/index.d.ts +18 -10
- package/assets/standalone/faustwasm/index.js +81 -56
- package/assets/standalone/faustwasm/index.js.map +2 -2
- package/assets/standalone/index-template.html +17 -0
- package/assets/standalone/index-template.js +48 -0
- package/assets/standalone/index.html +4 -3
- package/assets/standalone/index.js +14 -71
- package/assets/standalone/manifest.json +5 -6
- package/assets/standalone/service-worker.js +51 -27
- package/dist/cjs/index.d.ts +18 -10
- package/dist/cjs/index.js +81 -56
- package/dist/cjs/index.js.map +2 -2
- package/dist/cjs-bundle/index.d.ts +18 -10
- package/dist/cjs-bundle/index.js +81 -56
- package/dist/cjs-bundle/index.js.map +2 -2
- package/dist/esm/index.d.ts +18 -10
- package/dist/esm/index.js +81 -56
- package/dist/esm/index.js.map +2 -2
- package/dist/esm-bundle/index.d.ts +18 -10
- package/dist/esm-bundle/index.js +81 -56
- package/dist/esm-bundle/index.js.map +2 -2
- package/fileutils.js +19 -21
- package/package.json +4 -2
- package/scripts/faust2wasm.js +10 -5
- package/src/FaustAudioWorkletNode.ts +18 -38
- package/src/FaustAudioWorkletProcessor.ts +28 -1
- package/src/FaustDspGenerator.ts +18 -9
- package/src/FaustFFTAudioWorkletProcessor.ts +27 -0
- package/src/FaustScriptProcessorNode.ts +5 -14
- package/src/copyWebStandaloneAssets.d.ts +2 -2
- package/src/copyWebStandaloneAssets.js +30 -19
- package/src/faust2wasmFiles.js +5 -5
- package/test/faustlive-wasm/faust-ui/index.css +8 -0
- package/test/faustlive-wasm/faust-ui/index.css.map +1 -1
- package/test/faustlive-wasm/faust-ui/index.js +56 -30
- package/test/faustlive-wasm/faust-ui/index.js.map +1 -1
- package/test/faustlive-wasm/faustwasm/index.d.ts +18 -10
- package/test/faustlive-wasm/faustwasm/index.js +81 -56
- package/test/faustlive-wasm/faustwasm/index.js.map +2 -2
- package/assets/standalone/index-poly.js +0 -234
- package/assets/standalone/service-worker-poly.js +0 -98
- package/assets/standalone/template-poly.html +0 -60
- package/assets/standalone/template.html +0 -50
- package/assets/standalone/template.js +0 -63
- package/assets/standalone/types.d.ts +0 -9
package/fileutils.js
CHANGED
|
@@ -15,7 +15,10 @@ const __filename = fileURLToPath(import.meta.url);
|
|
|
15
15
|
* @param {string} dest - The destination path.
|
|
16
16
|
*/
|
|
17
17
|
const cpSync = (src, dest) => {
|
|
18
|
-
if (!fs.existsSync(src))
|
|
18
|
+
if (!fs.existsSync(src)) {
|
|
19
|
+
console.error(`${src} does not exist.`)
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
19
22
|
if (fs.lstatSync(src).isDirectory()) {
|
|
20
23
|
if (!fs.existsSync(dest)) fs.mkdirSync(dest);
|
|
21
24
|
fs.readdirSync(src).forEach(child => cpSync(path.join(src, child), path.join(dest, child)));
|
|
@@ -27,29 +30,24 @@ const cpSync = (src, dest) => {
|
|
|
27
30
|
/**
|
|
28
31
|
* @param {string} src - The source path of the file to modify and copy.
|
|
29
32
|
* @param {string} dest - The destination path.
|
|
30
|
-
* @param {string}
|
|
31
|
-
* @param {string} replace - The string to replace.
|
|
33
|
+
* @param {string[]} findAndReplace - The string to find and to replace pairs.
|
|
32
34
|
*/
|
|
33
|
-
const cpSyncModify = (src, dest,
|
|
34
|
-
fs.
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
const cpSyncModify = (src, dest, ...findAndReplace) => {
|
|
36
|
+
if (!fs.existsSync(src)) {
|
|
37
|
+
console.error(`${src} does not exist.`)
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
let data = fs.readFileSync(src, "utf-8");
|
|
41
|
+
for (let i = 0; i < findAndReplace.length; i += 2) {
|
|
42
|
+
const find = findAndReplace[i];
|
|
43
|
+
const replace = findAndReplace[i + 1];
|
|
40
44
|
// Create a regular expression from the 'find' string
|
|
41
|
-
const regex = new RegExp(find,
|
|
42
|
-
|
|
45
|
+
const regex = new RegExp(find, "g");
|
|
43
46
|
// Replace 'find' with 'replace'
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if (err) {
|
|
49
|
-
console.error(err);
|
|
50
|
-
}
|
|
51
|
-
});
|
|
52
|
-
});
|
|
47
|
+
data = data.replace(regex, replace);
|
|
48
|
+
}
|
|
49
|
+
// Write the modified data to a new file
|
|
50
|
+
fs.writeFileSync(dest, data, "utf-8");
|
|
53
51
|
}
|
|
54
52
|
/**
|
|
55
53
|
* @param {string} dir - The directory to remove.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@grame/faustwasm",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.8",
|
|
4
4
|
"description": "WebAssembly version of Faust Compiler",
|
|
5
5
|
"main": "dist/cjs/index.js",
|
|
6
6
|
"types": "dist/esm/index.d.ts",
|
|
@@ -39,9 +39,11 @@
|
|
|
39
39
|
"homepage": "https://github.com/grame-cncm/faustwasm#readme",
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@aws-crypto/sha256-js": "^5.2.0",
|
|
42
|
-
"@shren/faust-ui": "^1.1.
|
|
42
|
+
"@shren/faust-ui": "^1.1.12",
|
|
43
43
|
"@types/node": "^20.12.7",
|
|
44
44
|
"@types/webmidi": "^2.0.10",
|
|
45
|
+
"@webaudiomodules/api": "^2.0.0-alpha.6",
|
|
46
|
+
"@webaudiomodules/sdk-parammgr": "^0.0.13",
|
|
45
47
|
"dts-bundle-generator": "^9.5.1",
|
|
46
48
|
"esbuild": "^0.20.2",
|
|
47
49
|
"typescript": "^5.4.5"
|
package/scripts/faust2wasm.js
CHANGED
|
@@ -8,7 +8,7 @@ const argv = process.argv.slice(2);
|
|
|
8
8
|
|
|
9
9
|
if (argv[0] === "-help" || argv[0] === "-h") {
|
|
10
10
|
console.log(`
|
|
11
|
-
faust2wasm.js <file.dsp> <outputDir> [-poly] [-standalone]
|
|
11
|
+
faust2wasm.js <file.dsp> <outputDir> [-poly] [-standalone] [-no-template]
|
|
12
12
|
Generates WebAssembly and metadata JSON files of a given Faust DSP.
|
|
13
13
|
`);
|
|
14
14
|
process.exit();
|
|
@@ -22,15 +22,20 @@ const $standalone = argv.indexOf("-standalone");
|
|
|
22
22
|
const standalone = $standalone !== -1;
|
|
23
23
|
if (standalone) argv.splice($standalone, 1);
|
|
24
24
|
|
|
25
|
+
const $noTemplate = argv.indexOf("-no-template");
|
|
26
|
+
const noTemplate = $noTemplate !== -1;
|
|
27
|
+
if (noTemplate) argv.splice($noTemplate, 1);
|
|
28
|
+
|
|
25
29
|
const [inputFile, outputDir, ...argvFaust] = argv;
|
|
26
30
|
const fileName = inputFile.split('/').pop();
|
|
31
|
+
if (!fileName) throw new Error("No input DSP file");
|
|
27
32
|
const dspName = fileName.replace(/\.dsp$/, '');
|
|
28
33
|
|
|
29
34
|
(async () => {
|
|
30
|
-
await faust2wasmFiles(inputFile, outputDir, argvFaust, poly);
|
|
35
|
+
const { dspMeta, effectMeta } = await faust2wasmFiles(inputFile, outputDir, argvFaust, poly);
|
|
31
36
|
if (standalone) {
|
|
32
|
-
copyWebStandaloneAssets(outputDir, dspName, poly);
|
|
33
|
-
} else {
|
|
34
|
-
copyWebTemplateAssets(outputDir, dspName, poly);
|
|
37
|
+
copyWebStandaloneAssets(outputDir, dspName, poly, !!effectMeta);
|
|
38
|
+
} else if (!noTemplate) {
|
|
39
|
+
copyWebTemplateAssets(outputDir, dspName, poly, !!effectMeta);
|
|
35
40
|
}
|
|
36
41
|
})();
|
|
@@ -18,7 +18,7 @@ export class FaustAudioWorkletNode<Poly extends boolean = false> extends (global
|
|
|
18
18
|
#hasAccInput = false;
|
|
19
19
|
#hasGyrInput = false;
|
|
20
20
|
|
|
21
|
-
constructor(context: BaseAudioContext, name: string, factory: LooseFaustDspFactory, options: FaustAudioWorkletNodeOptions<Poly
|
|
21
|
+
constructor(context: BaseAudioContext, name: string, factory: LooseFaustDspFactory, options: Partial<FaustAudioWorkletNodeOptions<Poly>> = {}) {
|
|
22
22
|
|
|
23
23
|
// Create JSON object
|
|
24
24
|
const JSONObj: FaustDspMeta = JSON.parse(factory.json);
|
|
@@ -31,8 +31,8 @@ export class FaustAudioWorkletNode<Poly extends boolean = false> extends (global
|
|
|
31
31
|
outputChannelCount: [JSONObj.outputs],
|
|
32
32
|
channelCountMode: "explicit",
|
|
33
33
|
channelInterpretation: "speakers",
|
|
34
|
-
processorOptions: options,
|
|
35
|
-
...
|
|
34
|
+
processorOptions: options.processorOptions,
|
|
35
|
+
...options
|
|
36
36
|
});
|
|
37
37
|
|
|
38
38
|
this.fJSONDsp = JSONObj;
|
|
@@ -76,20 +76,11 @@ export class FaustAudioWorkletNode<Poly extends boolean = false> extends (global
|
|
|
76
76
|
async listenSensors() {
|
|
77
77
|
if (this.hasAccInput) {
|
|
78
78
|
const isAndroid: boolean = /Android/i.test(navigator.userAgent);
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
this.propagateAcc({ x, y, z }, true);
|
|
85
|
-
}
|
|
86
|
-
} else {
|
|
87
|
-
handleDeviceMotion = ({ accelerationIncludingGravity }: DeviceMotionEvent) => {
|
|
88
|
-
if (!accelerationIncludingGravity) return;
|
|
89
|
-
const { x, y, z } = accelerationIncludingGravity;
|
|
90
|
-
this.propagateAcc({ x, y, z });
|
|
91
|
-
}
|
|
92
|
-
}
|
|
79
|
+
const handleDeviceMotion = ({ accelerationIncludingGravity }: DeviceMotionEvent) => {
|
|
80
|
+
if (!accelerationIncludingGravity) return;
|
|
81
|
+
const { x, y, z } = accelerationIncludingGravity;
|
|
82
|
+
this.propagateAcc({ x, y, z }, isAndroid);
|
|
83
|
+
};
|
|
93
84
|
if (window.DeviceMotionEvent) {
|
|
94
85
|
if (typeof (window.DeviceMotionEvent as any).requestPermission === "function") { // for iOS 13+
|
|
95
86
|
try {
|
|
@@ -156,6 +147,9 @@ export class FaustAudioWorkletNode<Poly extends boolean = false> extends (global
|
|
|
156
147
|
getPlotHandler(): PlotHandler | null {
|
|
157
148
|
return this.fPlotHandler;
|
|
158
149
|
}
|
|
150
|
+
setupWamEventHandler() {
|
|
151
|
+
this.port.postMessage({ type: "setupWamEventHandler" });
|
|
152
|
+
}
|
|
159
153
|
|
|
160
154
|
getNumInputs() {
|
|
161
155
|
return this.fJSONDsp.inputs;
|
|
@@ -252,8 +246,8 @@ export class FaustMonoAudioWorkletNode extends FaustAudioWorkletNode<false> impl
|
|
|
252
246
|
throw e;
|
|
253
247
|
}
|
|
254
248
|
|
|
255
|
-
constructor(context: BaseAudioContext,
|
|
256
|
-
super(context, name, factory,
|
|
249
|
+
constructor(context: BaseAudioContext, options: Partial<FaustAudioWorkletNodeOptions<false>> & Pick<FaustAudioWorkletNodeOptions<false>, "processorOptions">) {
|
|
250
|
+
super(context, options.processorOptions.name, options.processorOptions.factory, options);
|
|
257
251
|
}
|
|
258
252
|
}
|
|
259
253
|
|
|
@@ -269,30 +263,16 @@ export class FaustPolyAudioWorkletNode extends FaustAudioWorkletNode<true> imple
|
|
|
269
263
|
throw e;
|
|
270
264
|
}
|
|
271
265
|
|
|
272
|
-
constructor(context: BaseAudioContext,
|
|
273
|
-
name: string,
|
|
274
|
-
voiceFactory: LooseFaustDspFactory,
|
|
275
|
-
mixerModule: WebAssembly.Module,
|
|
276
|
-
voices: number,
|
|
277
|
-
sampleSize: number,
|
|
278
|
-
effectFactory?: LooseFaustDspFactory, nodeOptions: Partial<FaustAudioWorkletNodeOptions> = {}) {
|
|
266
|
+
constructor(context: BaseAudioContext, options: Partial<FaustAudioWorkletNodeOptions<true>> & Pick<FaustAudioWorkletNodeOptions<true>, "processorOptions">) {
|
|
279
267
|
|
|
280
268
|
super(
|
|
281
269
|
context,
|
|
282
|
-
name,
|
|
283
|
-
voiceFactory,
|
|
284
|
-
|
|
285
|
-
name,
|
|
286
|
-
voiceFactory,
|
|
287
|
-
mixerModule,
|
|
288
|
-
voices,
|
|
289
|
-
sampleSize,
|
|
290
|
-
effectFactory
|
|
291
|
-
},
|
|
292
|
-
nodeOptions
|
|
270
|
+
options.processorOptions.name,
|
|
271
|
+
options.processorOptions.voiceFactory,
|
|
272
|
+
options
|
|
293
273
|
);
|
|
294
274
|
|
|
295
|
-
this.fJSONEffect = effectFactory ? JSON.parse(effectFactory.json) : null;
|
|
275
|
+
this.fJSONEffect = options.processorOptions.effectFactory ? JSON.parse(options.processorOptions.effectFactory.json) : null;
|
|
296
276
|
|
|
297
277
|
if (this.fJSONEffect) {
|
|
298
278
|
FaustBaseWebAudioDsp.parseUI(this.fJSONEffect.ui, this.fUICallback);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type FaustWasmInstantiator from "./FaustWasmInstantiator";
|
|
2
2
|
import type { FaustBaseWebAudioDsp, FaustWebAudioDspVoice, FaustMonoWebAudioDsp, FaustPolyWebAudioDsp } from "./FaustWebAudioDsp";
|
|
3
|
-
import type { AudioParamDescriptor, AudioWorkletGlobalScope, LooseFaustDspFactory, FaustDspMeta, FaustUIItem
|
|
3
|
+
import type { AudioParamDescriptor, AudioWorkletGlobalScope, LooseFaustDspFactory, FaustDspMeta, FaustUIItem } from "./types";
|
|
4
|
+
import type { AudioWorkletGlobalScope as WamAudioWorkletGlobalScope, WamParamMgrSDKBaseModuleScope } from "@webaudiomodules/sdk-parammgr";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Injected in the string to be compiled on AudioWorkletProcessor side
|
|
@@ -31,6 +32,9 @@ export interface FaustPolyAudioWorkletNodeOptions extends AudioWorkletNodeOption
|
|
|
31
32
|
export interface FaustAudioWorkletProcessorOptions {
|
|
32
33
|
name: string;
|
|
33
34
|
sampleSize: number;
|
|
35
|
+
// for WAMs
|
|
36
|
+
moduleId?: string;
|
|
37
|
+
instanceId?: string;
|
|
34
38
|
}
|
|
35
39
|
export interface FaustMonoAudioWorkletProcessorOptions extends FaustAudioWorkletProcessorOptions {
|
|
36
40
|
factory: LooseFaustDspFactory;
|
|
@@ -82,6 +86,8 @@ const getFaustAudioWorkletProcessor = <Poly extends boolean = false>(dependencie
|
|
|
82
86
|
|
|
83
87
|
protected paramValuesCache: Record<string, number> = {};
|
|
84
88
|
|
|
89
|
+
protected wamInfo?: { moduleId: string; instanceId: string };
|
|
90
|
+
|
|
85
91
|
constructor(options: FaustAudioWorkletNodeOptions<Poly>) {
|
|
86
92
|
super(options);
|
|
87
93
|
|
|
@@ -92,6 +98,10 @@ const getFaustAudioWorkletProcessor = <Poly extends boolean = false>(dependencie
|
|
|
92
98
|
parameterDescriptors.forEach((pd) => {
|
|
93
99
|
this.paramValuesCache[pd.name] = pd.defaultValue || 0;
|
|
94
100
|
})
|
|
101
|
+
|
|
102
|
+
const { moduleId, instanceId } = options.processorOptions;
|
|
103
|
+
if (!moduleId || !instanceId) return;
|
|
104
|
+
this.wamInfo = { moduleId, instanceId };
|
|
95
105
|
}
|
|
96
106
|
|
|
97
107
|
static get parameterDescriptors() {
|
|
@@ -107,6 +117,19 @@ const getFaustAudioWorkletProcessor = <Poly extends boolean = false>(dependencie
|
|
|
107
117
|
return params;
|
|
108
118
|
}
|
|
109
119
|
|
|
120
|
+
setupWamEventHandler() {
|
|
121
|
+
if (!this.wamInfo) return;
|
|
122
|
+
const { moduleId, instanceId } = this.wamInfo;
|
|
123
|
+
const { webAudioModules } = (globalThis as unknown as WamAudioWorkletGlobalScope);
|
|
124
|
+
const ModuleScope = webAudioModules.getModuleScope(moduleId) as WamParamMgrSDKBaseModuleScope;
|
|
125
|
+
const paramMgrProcessor = ModuleScope?.paramMgrProcessors?.[instanceId];
|
|
126
|
+
if (!paramMgrProcessor) return;
|
|
127
|
+
if (paramMgrProcessor.handleEvent) return;
|
|
128
|
+
paramMgrProcessor.handleEvent = (event) => {
|
|
129
|
+
if (event.type === "wam-midi") this.midiMessage(event.data.bytes);
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
110
133
|
process(inputs: Float32Array[][], outputs: Float32Array[][], parameters: { [key: string]: Float32Array }) {
|
|
111
134
|
|
|
112
135
|
// Update controls (possibly needed for sample accurate control)
|
|
@@ -162,6 +185,10 @@ const getFaustAudioWorkletProcessor = <Poly extends boolean = false>(dependencie
|
|
|
162
185
|
}
|
|
163
186
|
break;
|
|
164
187
|
}
|
|
188
|
+
case "setupWamEventHandler": {
|
|
189
|
+
this.setupWamEventHandler();
|
|
190
|
+
break;
|
|
191
|
+
}
|
|
165
192
|
case "start": {
|
|
166
193
|
this.fDSPCode.start();
|
|
167
194
|
break;
|
package/src/FaustDspGenerator.ts
CHANGED
|
@@ -52,6 +52,7 @@ export interface IFaustMonoDspGenerator extends GeneratorSupportingSoundfiles {
|
|
|
52
52
|
* @param sp - whether to compile a ScriptProcessorNode or an AudioWorkletNode
|
|
53
53
|
* @param bufferSize - the buffer size in frames to be used in ScriptProcessorNode only, since AudioWorkletNode always uses 128 frames
|
|
54
54
|
* @param processorName - AudioWorklet Processor name
|
|
55
|
+
* @param processorOptions - Additional AudioWorklet Processor options
|
|
55
56
|
* @returns the compiled WebAudio node or 'null' if failure
|
|
56
57
|
*/
|
|
57
58
|
createNode(
|
|
@@ -60,7 +61,8 @@ export interface IFaustMonoDspGenerator extends GeneratorSupportingSoundfiles {
|
|
|
60
61
|
factory?: LooseFaustDspFactory,
|
|
61
62
|
sp?: boolean,
|
|
62
63
|
bufferSize?: number,
|
|
63
|
-
processorName?: string
|
|
64
|
+
processorName?: string,
|
|
65
|
+
processorOptions?: Record<string, any>
|
|
64
66
|
): Promise<IFaustMonoWebAudioNode | null>;
|
|
65
67
|
|
|
66
68
|
/**
|
|
@@ -72,6 +74,7 @@ export interface IFaustMonoDspGenerator extends GeneratorSupportingSoundfiles {
|
|
|
72
74
|
* @param factory - default is the compiled factory
|
|
73
75
|
* @param fftOptions - initial FFT options
|
|
74
76
|
* @param processorName - AudioWorklet Processor name
|
|
77
|
+
* @param processorOptions - Additional AudioWorklet Processor options
|
|
75
78
|
* @returns the compiled WebAudio node or 'null' if failure
|
|
76
79
|
*/
|
|
77
80
|
createFFTNode(
|
|
@@ -80,7 +83,8 @@ export interface IFaustMonoDspGenerator extends GeneratorSupportingSoundfiles {
|
|
|
80
83
|
name?: string,
|
|
81
84
|
factory?: LooseFaustDspFactory,
|
|
82
85
|
fftOptions?: Partial<FaustFFTOptionsData>,
|
|
83
|
-
processorName?: string
|
|
86
|
+
processorName?: string,
|
|
87
|
+
processorOptions?: Record<string, any>
|
|
84
88
|
): Promise<FaustMonoAudioWorkletNode | null>;
|
|
85
89
|
|
|
86
90
|
/**
|
|
@@ -148,6 +152,7 @@ export interface IFaustPolyDspGenerator extends GeneratorSupportingSoundfiles {
|
|
|
148
152
|
* @param effectFactory - the Faust factory for the effect, either obtained with a compiler (createDSPFactory) or loaded from files (loadDSPFactory)
|
|
149
153
|
* @param sp - whether to compile a ScriptProcessorNode or an AudioWorkletNode
|
|
150
154
|
* @param bufferSize - the buffer size in frames to be used in ScriptProcessorNode only, since AudioWorkletNode always uses 128 frames
|
|
155
|
+
* @param processorOptions - Additional AudioWorklet Processor options
|
|
151
156
|
* @returns the compiled WebAudio node or 'null' if failure
|
|
152
157
|
*/
|
|
153
158
|
createNode(
|
|
@@ -159,7 +164,8 @@ export interface IFaustPolyDspGenerator extends GeneratorSupportingSoundfiles {
|
|
|
159
164
|
effectFactory?: LooseFaustDspFactory | null,
|
|
160
165
|
sp?: boolean,
|
|
161
166
|
bufferSize?: number,
|
|
162
|
-
processorName?: string
|
|
167
|
+
processorName?: string,
|
|
168
|
+
processorOptions?: Record<string, any>
|
|
163
169
|
): Promise<IFaustPolyWebAudioNode | null>;
|
|
164
170
|
|
|
165
171
|
/**
|
|
@@ -244,7 +250,8 @@ export class FaustMonoDspGenerator implements IFaustMonoDspGenerator {
|
|
|
244
250
|
factory = this.factory as LooseFaustDspFactory,
|
|
245
251
|
sp = false as SP,
|
|
246
252
|
bufferSize = 1024,
|
|
247
|
-
processorName = factory?.shaKey || name
|
|
253
|
+
processorName = factory?.shaKey || name,
|
|
254
|
+
processorOptions: Record<string, any> = {}
|
|
248
255
|
): Promise<SP extends true ? FaustMonoScriptProcessorNode | null : FaustMonoAudioWorkletNode | null> {
|
|
249
256
|
if (!factory) throw new Error("Code is not compiled, please define the factory or call `await this.compile()` first.");
|
|
250
257
|
|
|
@@ -307,7 +314,7 @@ const dependencies = {
|
|
|
307
314
|
}
|
|
308
315
|
}
|
|
309
316
|
// Create the AWN
|
|
310
|
-
const node = new FaustMonoAudioWorkletNode(context, processorName, factory, sampleSize);
|
|
317
|
+
const node = new FaustMonoAudioWorkletNode(context, { processorOptions: { name: processorName, factory, sampleSize, ...processorOptions } });
|
|
311
318
|
|
|
312
319
|
return node as SP extends true ? FaustMonoScriptProcessorNode : FaustMonoAudioWorkletNode;
|
|
313
320
|
}
|
|
@@ -319,7 +326,8 @@ const dependencies = {
|
|
|
319
326
|
name = this.name,
|
|
320
327
|
factory = this.factory as LooseFaustDspFactory,
|
|
321
328
|
fftOptions: Partial<FaustFFTOptionsData> = {},
|
|
322
|
-
processorName = factory?.shaKey ? `${factory.shaKey}_fft` : name
|
|
329
|
+
processorName = factory?.shaKey ? `${factory.shaKey}_fft` : name,
|
|
330
|
+
processorOptions: Record<string, any> = {}
|
|
323
331
|
): Promise<FaustMonoAudioWorkletNode | null> {
|
|
324
332
|
if (!factory) throw new Error("Code is not compiled, please define the factory or call `await this.compile()` first.");
|
|
325
333
|
|
|
@@ -375,7 +383,7 @@ const dependencies = {
|
|
|
375
383
|
}
|
|
376
384
|
}
|
|
377
385
|
// Create the AWN
|
|
378
|
-
const node = new FaustMonoAudioWorkletNode(context,
|
|
386
|
+
const node = new FaustMonoAudioWorkletNode(context, { channelCount: Math.max(1, Math.ceil(meta.inputs / 3)), outputChannelCount: [Math.ceil(meta.outputs / 2)], processorOptions: { name: processorName, factory, sampleSize, ...processorOptions } });
|
|
379
387
|
if (fftOptions.fftSize) {
|
|
380
388
|
const param = node.parameters.get("fftSize");
|
|
381
389
|
if (param) param.value = fftOptions.fftSize;
|
|
@@ -573,7 +581,8 @@ process = adaptorIns(dsp_code.process) : dsp_code.effect : adaptorOuts;
|
|
|
573
581
|
effectFactory = this.effectFactory as LooseFaustDspFactory | null,
|
|
574
582
|
sp = false as SP,
|
|
575
583
|
bufferSize = 1024,
|
|
576
|
-
processorName = ((voiceFactory?.shaKey || "") + (effectFactory?.shaKey || "")) || `${name}_poly
|
|
584
|
+
processorName = ((voiceFactory?.shaKey || "") + (effectFactory?.shaKey || "")) || `${name}_poly`,
|
|
585
|
+
processorOptions = {}
|
|
577
586
|
): Promise<SP extends true ? FaustPolyScriptProcessorNode | null : FaustPolyAudioWorkletNode | null> {
|
|
578
587
|
if (!voiceFactory) throw new Error("Code is not compiled, please define the factory or call `await this.compile()` first.");
|
|
579
588
|
|
|
@@ -642,7 +651,7 @@ const dependencies = {
|
|
|
642
651
|
}
|
|
643
652
|
}
|
|
644
653
|
// Create the AWN
|
|
645
|
-
const node = new FaustPolyAudioWorkletNode(context, processorName, voiceFactory, mixerModule, voices, sampleSize, effectFactory || undefined);
|
|
654
|
+
const node = new FaustPolyAudioWorkletNode(context, { processorOptions: { name: processorName, voiceFactory, mixerModule, voices, sampleSize, effectFactory: effectFactory || undefined, ...processorOptions } });
|
|
646
655
|
|
|
647
656
|
return node as SP extends true ? FaustPolyScriptProcessorNode : FaustPolyAudioWorkletNode;
|
|
648
657
|
}
|
|
@@ -2,6 +2,7 @@ import type { FaustMonoDspInstance } from "./FaustDspInstance";
|
|
|
2
2
|
import type FaustWasmInstantiator from "./FaustWasmInstantiator";
|
|
3
3
|
import type { FaustBaseWebAudioDsp, FaustMonoWebAudioDsp, PlotHandler } from "./FaustWebAudioDsp";
|
|
4
4
|
import type { AudioParamDescriptor, AudioWorkletGlobalScope, LooseFaustDspFactory, FaustDspMeta, FaustUIItem, InterfaceFFT, TWindowFunction, Writeable, TypedArray, FFTUtils } from "./types";
|
|
5
|
+
import type { AudioWorkletGlobalScope as WamAudioWorkletGlobalScope, WamParamMgrSDKBaseModuleScope } from "@webaudiomodules/sdk-parammgr";
|
|
5
6
|
|
|
6
7
|
export interface FaustFFTOptionsData {
|
|
7
8
|
fftSize: number;
|
|
@@ -33,6 +34,9 @@ export interface FaustFFTAudioWorkletProcessorOptions {
|
|
|
33
34
|
name: string;
|
|
34
35
|
sampleSize: number;
|
|
35
36
|
factory: LooseFaustDspFactory;
|
|
37
|
+
// for WAMs
|
|
38
|
+
moduleId?: string;
|
|
39
|
+
instanceId?: string;
|
|
36
40
|
}
|
|
37
41
|
|
|
38
42
|
|
|
@@ -122,6 +126,8 @@ const getFaustFFTAudioWorkletProcessor = (dependencies: FaustFFTAudioWorkletProc
|
|
|
122
126
|
|
|
123
127
|
protected paramValuesCache: Record<string, number> = {};
|
|
124
128
|
|
|
129
|
+
protected wamInfo?: { moduleId: string; instanceId: string };
|
|
130
|
+
|
|
125
131
|
private dspInstance!: FaustMonoDspInstance;
|
|
126
132
|
private sampleSize!: number;
|
|
127
133
|
|
|
@@ -187,6 +193,10 @@ const getFaustFFTAudioWorkletProcessor = (dependencies: FaustFFTAudioWorkletProc
|
|
|
187
193
|
|
|
188
194
|
// Init the FFT constructor and the Faust FFT Processor
|
|
189
195
|
this.initFFT();
|
|
196
|
+
|
|
197
|
+
const { moduleId, instanceId } = options.processorOptions;
|
|
198
|
+
if (!moduleId || !instanceId) return;
|
|
199
|
+
this.wamInfo = { moduleId, instanceId };
|
|
190
200
|
}
|
|
191
201
|
|
|
192
202
|
async initFFT(): Promise<true> {
|
|
@@ -232,6 +242,19 @@ const getFaustFFTAudioWorkletProcessor = (dependencies: FaustFFTAudioWorkletProc
|
|
|
232
242
|
];
|
|
233
243
|
}
|
|
234
244
|
|
|
245
|
+
setupWamEventHandler() {
|
|
246
|
+
if (!this.wamInfo) return;
|
|
247
|
+
const { moduleId, instanceId } = this.wamInfo;
|
|
248
|
+
const { webAudioModules } = (globalThis as unknown as WamAudioWorkletGlobalScope);
|
|
249
|
+
const ModuleScope = webAudioModules.getModuleScope(moduleId) as WamParamMgrSDKBaseModuleScope;
|
|
250
|
+
const paramMgrProcessor = ModuleScope?.paramMgrProcessors?.[instanceId];
|
|
251
|
+
if (!paramMgrProcessor) return;
|
|
252
|
+
if (paramMgrProcessor.handleEvent) return;
|
|
253
|
+
paramMgrProcessor.handleEvent = (event) => {
|
|
254
|
+
if (event.type === "wam-midi") this.midiMessage(event.data.bytes);
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
235
258
|
processFFT() {
|
|
236
259
|
// Get the number of samples that need to proceed, from the input r/w pointers
|
|
237
260
|
let samplesForFFT = mod(this.$inputWrite - this.$inputRead, this.fftBufferSize) || this.fftBufferSize;
|
|
@@ -400,6 +423,10 @@ const getFaustFFTAudioWorkletProcessor = (dependencies: FaustFFTAudioWorkletProc
|
|
|
400
423
|
this.fDSPCode?.setPlotHandler(this.fPlotHandler);
|
|
401
424
|
break;
|
|
402
425
|
}
|
|
426
|
+
case "setupWamEventHandler": {
|
|
427
|
+
this.setupWamEventHandler();
|
|
428
|
+
break;
|
|
429
|
+
}
|
|
403
430
|
case "start": {
|
|
404
431
|
this.fDSPCode?.start();
|
|
405
432
|
break;
|
|
@@ -40,20 +40,11 @@ export class FaustScriptProcessorNode<Poly extends boolean = false> extends (glo
|
|
|
40
40
|
async listenSensors() {
|
|
41
41
|
if (this.hasAccInput) {
|
|
42
42
|
const isAndroid: boolean = /Android/i.test(navigator.userAgent);
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
this.propagateAcc({ x, y, z }, true);
|
|
49
|
-
}
|
|
50
|
-
} else {
|
|
51
|
-
handleDeviceMotion = ({ accelerationIncludingGravity }: DeviceMotionEvent) => {
|
|
52
|
-
if (!accelerationIncludingGravity) return;
|
|
53
|
-
const { x, y, z } = accelerationIncludingGravity;
|
|
54
|
-
this.propagateAcc({ x, y, z });
|
|
55
|
-
}
|
|
56
|
-
}
|
|
43
|
+
const handleDeviceMotion = ({ accelerationIncludingGravity }: DeviceMotionEvent) => {
|
|
44
|
+
if (!accelerationIncludingGravity) return;
|
|
45
|
+
const { x, y, z } = accelerationIncludingGravity;
|
|
46
|
+
this.propagateAcc({ x, y, z }, isAndroid);
|
|
47
|
+
};
|
|
57
48
|
if (window.DeviceMotionEvent) {
|
|
58
49
|
if (typeof (window.DeviceMotionEvent as any).requestPermission === "function") { // for iOS 13+
|
|
59
50
|
try {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
declare const copyWebStandaloneAssets: (outputDir: string, dspName: string, poly
|
|
2
|
-
declare const copyWebTemplateAssets: (outputDir: string, dspName: string, poly
|
|
1
|
+
declare const copyWebStandaloneAssets: (outputDir: string, dspName: string, poly?: boolean, effect?: boolean) => void;
|
|
2
|
+
declare const copyWebTemplateAssets: (outputDir: string, dspName: string, poly?: boolean, effect?: boolean) => void;
|
|
3
3
|
|
|
4
4
|
export { copyWebStandaloneAssets, copyWebTemplateAssets };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
//@ts-check
|
|
2
2
|
import * as fs from "fs";
|
|
3
3
|
import * as path from "path";
|
|
4
|
-
import { cpSync, cpSyncModify
|
|
4
|
+
import { cpSync, cpSyncModify } from "../fileutils.js";
|
|
5
5
|
import { fileURLToPath } from "url";
|
|
6
6
|
|
|
7
7
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -11,27 +11,30 @@ const __filename = fileURLToPath(import.meta.url);
|
|
|
11
11
|
* @param {string} outputDir - The output directory.
|
|
12
12
|
* @param {string} dspName - The name of the DSP to be loaded.
|
|
13
13
|
* @param {boolean} [poly] - Whether the DSP is polyphonic.
|
|
14
|
+
* @param {boolean} [effect] - Whether the DSP has an effect module.
|
|
14
15
|
*/
|
|
15
|
-
const copyWebStandaloneAssets = (outputDir, dspName, poly = false) => {
|
|
16
|
+
const copyWebStandaloneAssets = (outputDir, dspName, poly = false, effect = false) => {
|
|
16
17
|
console.log(`Writing assets files.`)
|
|
17
18
|
const assetsPath = path.join(__dirname, "../assets/standalone");
|
|
18
19
|
if (!fs.existsSync(outputDir)) fs.mkdirSync(outputDir);
|
|
19
20
|
|
|
21
|
+
const findAndReplace = ["FAUST_DSP_NAME", dspName];
|
|
22
|
+
if (poly) findAndReplace.push("FAUST_DSP_VOICES = 0", "FAUST_DSP_VOICES = 16");
|
|
23
|
+
if (poly && effect) findAndReplace.push("FAUST_DSP_HAS_EFFECT = false", "FAUST_DSP_HAS_EFFECT = true");
|
|
24
|
+
|
|
20
25
|
// Copy some files
|
|
21
|
-
const
|
|
22
|
-
cpSyncModify(
|
|
26
|
+
const createNodeJSPath = path.join(__dirname, "../assets/standalone/create-node.js");
|
|
27
|
+
cpSyncModify(createNodeJSPath, outputDir + `/create-node.js`, ...findAndReplace);
|
|
28
|
+
|
|
29
|
+
const templateJSPath = path.join(__dirname, "../assets/standalone/index.js");
|
|
30
|
+
cpSyncModify(templateJSPath, outputDir + `/index.js`, ...findAndReplace);
|
|
23
31
|
|
|
24
32
|
const templateHTMLPath = path.join(__dirname, "../assets/standalone/index.html");
|
|
25
|
-
//cpSyncModify(templateHTMLPath, outputDir + `/${dspName}.html`, "
|
|
26
|
-
cpSyncModify(templateHTMLPath, outputDir + `/index.html`,
|
|
33
|
+
//cpSyncModify(templateHTMLPath, outputDir + `/${dspName}.html`, "FAUST_DSP_NAME", dspName);
|
|
34
|
+
cpSyncModify(templateHTMLPath, outputDir + `/index.html`, ...findAndReplace);
|
|
27
35
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
cpSyncModify(templateWorkerPath, outputDir + `/service-worker.js`, "FAUST_DSP", dspName);
|
|
31
|
-
} else {
|
|
32
|
-
const templateWorkerPath = path.join(__dirname, "../assets/standalone/service-worker.js");
|
|
33
|
-
cpSyncModify(templateWorkerPath, outputDir + `/service-worker.js`, "FAUST_DSP", dspName);
|
|
34
|
-
}
|
|
36
|
+
const templateWorkerPath = path.join(__dirname, "../assets/standalone/service-worker.js");
|
|
37
|
+
cpSyncModify(templateWorkerPath, outputDir + `/service-worker.js`, ...findAndReplace);
|
|
35
38
|
|
|
36
39
|
const templateIconPath = path.join(__dirname, "../assets/standalone/icon.png");
|
|
37
40
|
cpSync(templateIconPath, outputDir + `/icon.png`);
|
|
@@ -43,25 +46,33 @@ const copyWebStandaloneAssets = (outputDir, dspName, poly = false) => {
|
|
|
43
46
|
cpSync(faustuiPath, outputDir + "/faust-ui");
|
|
44
47
|
|
|
45
48
|
const templateManifestPath = path.join(__dirname, "../assets/standalone/manifest.json");
|
|
46
|
-
cpSyncModify(templateManifestPath, outputDir + `/manifest.json`,
|
|
49
|
+
cpSyncModify(templateManifestPath, outputDir + `/manifest.json`, ...findAndReplace);
|
|
47
50
|
};
|
|
48
51
|
|
|
49
52
|
/**
|
|
50
53
|
* @param {string} outputDir - The output directory.
|
|
51
54
|
* @param {string} dspName - The name of the DSP to be loaded.
|
|
52
55
|
* @param {boolean} [poly] - Whether the DSP is polyphonic.
|
|
56
|
+
* @param {boolean} [effect] - Whether the DSP has an effect module.
|
|
53
57
|
*/
|
|
54
|
-
const copyWebTemplateAssets = (outputDir, dspName, poly = false) => {
|
|
58
|
+
const copyWebTemplateAssets = (outputDir, dspName, poly = false, effect = false) => {
|
|
55
59
|
|
|
56
60
|
// Create output directory if it doesn't exist
|
|
57
61
|
if (!fs.existsSync(outputDir)) fs.mkdirSync(outputDir);
|
|
58
62
|
|
|
63
|
+
const findAndReplace = ["FAUST_DSP_NAME", dspName];
|
|
64
|
+
if (poly) findAndReplace.push("FAUST_DSP_VOICES = 0", "FAUST_DSP_VOICES = 16");
|
|
65
|
+
if (poly && effect) findAndReplace.push("FAUST_DSP_HAS_EFFECT = false", "FAUST_DSP_HAS_EFFECT = true");
|
|
66
|
+
|
|
59
67
|
// Copy some files
|
|
60
|
-
const
|
|
61
|
-
|
|
68
|
+
const createNodeJSPath = path.join(__dirname, "../assets/standalone/create-node.js");
|
|
69
|
+
cpSyncModify(createNodeJSPath, outputDir + `/create-node.js`, ...findAndReplace);
|
|
70
|
+
|
|
71
|
+
const templateJSPath = path.join(__dirname, "../assets/standalone/index-template.js");
|
|
72
|
+
cpSyncModify(templateJSPath, outputDir + `/index.js`, ...findAndReplace);
|
|
62
73
|
|
|
63
|
-
const templateHTMLPath =
|
|
64
|
-
cpSyncModify(templateHTMLPath, outputDir +
|
|
74
|
+
const templateHTMLPath = path.join(__dirname, "../assets/standalone/index-template.html");
|
|
75
|
+
cpSyncModify(templateHTMLPath, outputDir + `/index.html`, "index-template.js", "index.js", ...findAndReplace);
|
|
65
76
|
|
|
66
77
|
const faustwasmPath = path.join(__dirname, "../assets/standalone/faustwasm");
|
|
67
78
|
cpSync(faustwasmPath, outputDir + "/faustwasm");
|
package/src/faust2wasmFiles.js
CHANGED
|
@@ -34,11 +34,11 @@ const faust2wasmFiles = async (inputFile, outputDir, argv = [], poly = false) =>
|
|
|
34
34
|
|
|
35
35
|
if (!argv.find(a => a === "-I")) argv.push("-I", "libraries/");
|
|
36
36
|
argv.push("-ftz", "2");
|
|
37
|
-
const dspModulePath = path.join(outputDir,
|
|
38
|
-
const dspMetaPath = path.join(outputDir,
|
|
39
|
-
const effectModulePath = path.join(outputDir,
|
|
40
|
-
const effectMetaPath = path.join(outputDir,
|
|
41
|
-
const mixerModulePath = path.join(outputDir, "
|
|
37
|
+
const dspModulePath = path.join(outputDir, `dsp-module.wasm`);
|
|
38
|
+
const dspMetaPath = path.join(outputDir, `dsp-meta.json`);
|
|
39
|
+
const effectModulePath = path.join(outputDir, `effect-module.wasm`);
|
|
40
|
+
const effectMetaPath = path.join(outputDir, `effect-meta.json`);
|
|
41
|
+
const mixerModulePath = path.join(outputDir, "mixer-module.wasm");
|
|
42
42
|
/** @type {Uint8Array} */
|
|
43
43
|
let dspModule;
|
|
44
44
|
/** @type {import("./types").FaustDspMeta} */
|