@grame/faustwasm 0.16.6 → 0.16.7

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grame/faustwasm",
3
- "version": "0.16.6",
3
+ "version": "0.16.7",
4
4
  "description": "WebAssembly version of Faust Compiler",
5
5
  "main": "dist/cjs/index.js",
6
6
  "types": "dist/esm/index.d.ts",
@@ -166,15 +166,46 @@ const getFaustAudioWorkletProcessor = <Poly extends boolean = false>(
166
166
 
167
167
  static get parameterDescriptors() {
168
168
  const params = [] as AudioParamDescriptor[];
169
- // Analyse voice JSON to generate AudioParam parameters
170
- const callback = (item: FaustUIItem) => {
169
+ // A path may appear once across the voice and the effect together.
170
+ // `AudioWorkletGlobalScope.registerProcessor` rejects a duplicate
171
+ // descriptor name, and its own message names neither the side the
172
+ // path came from nor the fact that the two DSPs are what collided,
173
+ // so the duplicate is caught here and reported with both.
174
+ //
175
+ // Deduplicating instead would compile but not work:
176
+ // `FaustPolyWebAudioDsp.setParamValue` routes a path to the effect
177
+ // *or* to the voices, never both, so a shared control would drive
178
+ // one side and silently leave the other at its default. A DSP that
179
+ // needs one value on both sides has to declare it twice, under two
180
+ // paths, and the host must write both.
181
+ const origin = new Map<string, string>();
182
+ const collect = (side: string) => (item: FaustUIItem) => {
171
183
  const param = analysePolyParameters(item);
172
- if (param) params.push(param);
184
+ if (!param) return;
185
+ const previous = origin.get(param.name);
186
+ if (previous === side) {
187
+ throw new Error(
188
+ `Faust: control "${param.name}" is declared twice in the ${side}. ` +
189
+ `Two widgets resolved to the same path, so one of them would be ` +
190
+ `unreachable. Give them distinct labels or groups.`
191
+ );
192
+ }
193
+ if (previous !== undefined) {
194
+ throw new Error(
195
+ `Faust: control "${param.name}" is declared both in the ${previous} and in the ${side}. ` +
196
+ `A polyphonic DSP cannot share a control path between \`process\` and \`effect\`: ` +
197
+ `setParamValue routes a path to one side only, so the other would keep its default. ` +
198
+ `Declare it under a distinct path on each side and have the host write both.`
199
+ );
200
+ }
201
+ origin.set(param.name, side);
202
+ params.push(param);
173
203
  };
174
- FaustBaseWebAudioDsp.parseUI(dspMeta.ui, callback);
204
+ // Analyse voice JSON to generate AudioParam parameters
205
+ FaustBaseWebAudioDsp.parseUI(dspMeta.ui, collect('voice'));
175
206
  // Analyse effect JSON to generate AudioParam parameters
176
207
  if (effectMeta)
177
- FaustBaseWebAudioDsp.parseUI(effectMeta.ui, callback);
208
+ FaustBaseWebAudioDsp.parseUI(effectMeta.ui, collect('effect'));
178
209
  return params;
179
210
  }
180
211
 
@@ -499,13 +530,29 @@ const getFaustAudioWorkletProcessor = <Poly extends boolean = false>(
499
530
  ? FaustPolyAudioWorkletProcessor
500
531
  : FaustMonoAudioWorkletProcessor;
501
532
  if (register) {
502
- try {
503
- registerProcessor(
504
- processorName || dspName || (poly ? 'mydsp_poly' : 'mydsp'),
505
- Processor
506
- );
507
- } catch (error) {
508
- console.warn(error);
533
+ const name = processorName || dspName || (poly ? 'mydsp_poly' : 'mydsp');
534
+ // Registering the same processor twice in one AudioWorkletGlobalScope
535
+ // is benign a host may add the module more than once for a given
536
+ // context — so that case stays tolerated, tracked on the scope itself
537
+ // rather than by matching a browser-specific error message.
538
+ //
539
+ // Anything else must propagate. `registerProcessor` is what gives the
540
+ // processor its name: swallowing a failure leaves `addModule` resolving
541
+ // successfully and the caller believing registration worked, so the
542
+ // problem only resurfaces later as
543
+ // "the node name '<name>' is not defined in AudioWorkletGlobalScope"
544
+ // from the AudioWorkletNode constructor — a symptom several steps
545
+ // removed from the cause, pointing at the name rather than at whatever
546
+ // actually made registration fail.
547
+ const scope = globalThis as unknown as {
548
+ __faustRegisteredProcessors?: Set<string>;
549
+ };
550
+ if (!scope.__faustRegisteredProcessors) {
551
+ scope.__faustRegisteredProcessors = new Set<string>();
552
+ }
553
+ if (!scope.__faustRegisteredProcessors.has(name)) {
554
+ registerProcessor(name, Processor);
555
+ scope.__faustRegisteredProcessors.add(name);
509
556
  }
510
557
  }
511
558
 
@@ -928,17 +928,36 @@ const dependencies = {
928
928
  }
929
929
  }
930
930
  // Create the AWN
931
- const node = new FaustPolyAudioWorkletNode(context, {
932
- processorOptions: {
933
- name: processorName,
934
- voiceFactory,
935
- mixerModule,
936
- voices,
937
- sampleSize,
938
- effectFactory: effectFactory || undefined,
939
- ...processorOptions
940
- }
941
- });
931
+ // `addModule` above resolves even when the module's top-level code
932
+ // throws — the browser reports that as an unhandled error inside
933
+ // the AudioWorkletGlobalScope rather than as a load failure. So a
934
+ // processor that failed to register only shows up here, as
935
+ // "the node name '<name>' is not defined", which reads as a naming
936
+ // or loading problem and says nothing about the real cause. Point
937
+ // the caller at the worklet console, where the actual error is.
938
+ let node: FaustPolyAudioWorkletNode;
939
+ try {
940
+ node = new FaustPolyAudioWorkletNode(context, {
941
+ processorOptions: {
942
+ name: processorName,
943
+ voiceFactory,
944
+ mixerModule,
945
+ voices,
946
+ sampleSize,
947
+ effectFactory: effectFactory || undefined,
948
+ ...processorOptions
949
+ }
950
+ });
951
+ } catch (error) {
952
+ throw new Error(
953
+ `Faust: the AudioWorkletProcessor "${processorName}" did not register. ` +
954
+ `The module loaded, so the failure happened inside registerProcessor — ` +
955
+ `its error is reported in the AudioWorklet scope and appears in the ` +
956
+ `browser console above this one. A duplicate control path shared ` +
957
+ `between \`process\` and \`effect\` is the usual cause. ` +
958
+ `Original error: ${error instanceof Error ? error.message : String(error)}`
959
+ );
960
+ }
942
961
 
943
962
  return node as SP extends true
944
963
  ? FaustPolyScriptProcessorNode
@@ -809,13 +809,29 @@ const getFaustFFTAudioWorkletProcessor = (
809
809
 
810
810
  const Processor = FaustFFTAudioWorkletProcessor;
811
811
  if (register) {
812
- try {
813
- registerProcessor(
814
- processorName || dspName || 'myfftdsp',
815
- Processor
816
- );
817
- } catch (error) {
818
- console.warn(error);
812
+ const name = processorName || dspName || 'myfftdsp';
813
+ // Registering the same processor twice in one AudioWorkletGlobalScope
814
+ // is benign a host may add the module more than once for a given
815
+ // context — so that case stays tolerated, tracked on the scope itself
816
+ // rather than by matching a browser-specific error message.
817
+ //
818
+ // Anything else must propagate. `registerProcessor` is what gives the
819
+ // processor its name: swallowing a failure leaves `addModule` resolving
820
+ // successfully and the caller believing registration worked, so the
821
+ // problem only resurfaces later as
822
+ // "the node name '<name>' is not defined in AudioWorkletGlobalScope"
823
+ // from the AudioWorkletNode constructor — a symptom several steps
824
+ // removed from the cause, pointing at the name rather than at whatever
825
+ // actually made registration fail.
826
+ const scope = globalThis as unknown as {
827
+ __faustRegisteredProcessors?: Set<string>;
828
+ };
829
+ if (!scope.__faustRegisteredProcessors) {
830
+ scope.__faustRegisteredProcessors = new Set<string>();
831
+ }
832
+ if (!scope.__faustRegisteredProcessors.has(name)) {
833
+ registerProcessor(name, Processor);
834
+ scope.__faustRegisteredProcessors.add(name);
819
835
  }
820
836
  }
821
837
 
@@ -120,13 +120,27 @@ var getFaustAudioWorkletProcessor = (dependencies, faustData, register = true) =
120
120
  }
121
121
  static get parameterDescriptors() {
122
122
  const params = [];
123
- const callback = (item) => {
123
+ const origin = /* @__PURE__ */ new Map();
124
+ const collect = (side) => (item) => {
124
125
  const param = analysePolyParameters(item);
125
- if (param) params.push(param);
126
+ if (!param) return;
127
+ const previous = origin.get(param.name);
128
+ if (previous === side) {
129
+ throw new Error(
130
+ `Faust: control "${param.name}" is declared twice in the ${side}. Two widgets resolved to the same path, so one of them would be unreachable. Give them distinct labels or groups.`
131
+ );
132
+ }
133
+ if (previous !== void 0) {
134
+ throw new Error(
135
+ `Faust: control "${param.name}" is declared both in the ${previous} and in the ${side}. A polyphonic DSP cannot share a control path between \`process\` and \`effect\`: setParamValue routes a path to one side only, so the other would keep its default. Declare it under a distinct path on each side and have the host write both.`
136
+ );
137
+ }
138
+ origin.set(param.name, side);
139
+ params.push(param);
126
140
  };
127
- FaustBaseWebAudioDsp2.parseUI(dspMeta.ui, callback);
141
+ FaustBaseWebAudioDsp2.parseUI(dspMeta.ui, collect("voice"));
128
142
  if (effectMeta)
129
- FaustBaseWebAudioDsp2.parseUI(effectMeta.ui, callback);
143
+ FaustBaseWebAudioDsp2.parseUI(effectMeta.ui, collect("effect"));
130
144
  return params;
131
145
  }
132
146
  setupWamEventHandler() {
@@ -384,13 +398,14 @@ var getFaustAudioWorkletProcessor = (dependencies, faustData, register = true) =
384
398
  }
385
399
  const Processor = poly ? FaustPolyAudioWorkletProcessor : FaustMonoAudioWorkletProcessor;
386
400
  if (register) {
387
- try {
388
- registerProcessor(
389
- processorName || dspName || (poly ? "mydsp_poly" : "mydsp"),
390
- Processor
391
- );
392
- } catch (error) {
393
- console.warn(error);
401
+ const name = processorName || dspName || (poly ? "mydsp_poly" : "mydsp");
402
+ const scope = globalThis;
403
+ if (!scope.__faustRegisteredProcessors) {
404
+ scope.__faustRegisteredProcessors = /* @__PURE__ */ new Set();
405
+ }
406
+ if (!scope.__faustRegisteredProcessors.has(name)) {
407
+ registerProcessor(name, Processor);
408
+ scope.__faustRegisteredProcessors.add(name);
394
409
  }
395
410
  }
396
411
  return poly ? FaustPolyAudioWorkletProcessor : FaustMonoAudioWorkletProcessor;
@@ -931,13 +946,14 @@ var getFaustFFTAudioWorkletProcessor = (dependencies, faustData, register = true
931
946
  }
932
947
  const Processor = FaustFFTAudioWorkletProcessor;
933
948
  if (register) {
934
- try {
935
- registerProcessor(
936
- processorName || dspName || "myfftdsp",
937
- Processor
938
- );
939
- } catch (error) {
940
- console.warn(error);
949
+ const name = processorName || dspName || "myfftdsp";
950
+ const scope = globalThis;
951
+ if (!scope.__faustRegisteredProcessors) {
952
+ scope.__faustRegisteredProcessors = /* @__PURE__ */ new Set();
953
+ }
954
+ if (!scope.__faustRegisteredProcessors.has(name)) {
955
+ registerProcessor(name, Processor);
956
+ scope.__faustRegisteredProcessors.add(name);
941
957
  }
942
958
  }
943
959
  return FaustFFTAudioWorkletProcessor;
@@ -5927,17 +5943,24 @@ const dependencies = {
5927
5943
  throw e;
5928
5944
  }
5929
5945
  }
5930
- const node = new FaustPolyAudioWorkletNode(context, {
5931
- processorOptions: {
5932
- name: processorName,
5933
- voiceFactory,
5934
- mixerModule,
5935
- voices,
5936
- sampleSize,
5937
- effectFactory: effectFactory || void 0,
5938
- ...processorOptions
5939
- }
5940
- });
5946
+ let node;
5947
+ try {
5948
+ node = new FaustPolyAudioWorkletNode(context, {
5949
+ processorOptions: {
5950
+ name: processorName,
5951
+ voiceFactory,
5952
+ mixerModule,
5953
+ voices,
5954
+ sampleSize,
5955
+ effectFactory: effectFactory || void 0,
5956
+ ...processorOptions
5957
+ }
5958
+ });
5959
+ } catch (error) {
5960
+ throw new Error(
5961
+ `Faust: the AudioWorkletProcessor "${processorName}" did not register. The module loaded, so the failure happened inside registerProcessor \u2014 its error is reported in the AudioWorklet scope and appears in the browser console above this one. A duplicate control path shared between \`process\` and \`effect\` is the usual cause. Original error: ${error instanceof Error ? error.message : String(error)}`
5962
+ );
5963
+ }
5941
5964
  return node;
5942
5965
  }
5943
5966
  }