@grame/faust-web-component 0.4.5 → 0.5.0

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/README.md CHANGED
@@ -82,7 +82,7 @@ The HTML [index.html](./index.html) example page can be copied and tested in `di
82
82
 
83
83
  ## NPM package
84
84
 
85
- A [npm package](https://www.npmjs.com/package/@grame/faust-web-component) can be used with the CDN link: https://cdn.jsdelivr.net/npm/@grame/faust-web-component@0.4.5/dist/faust-web-component.js (possibly update the version number).
85
+ A [npm package](https://www.npmjs.com/package/@grame/faust-web-component) can be used with the CDN link: https://cdn.jsdelivr.net/npm/@grame/faust-web-component@0.5.0/dist/faust-web-component.js (possibly update the version number).
86
86
 
87
87
  Here is an HTML example using this model:
88
88
 
@@ -101,7 +101,7 @@ process = vgroup("Oscillator", os.osc(freq1) * vol, os.osc(freq2) * vol);
101
101
  -->
102
102
  </faust-editor>
103
103
 
104
- <script src="https://cdn.jsdelivr.net/npm/@grame/faust-web-component@0.4.5/dist/faust-web-component.js"></script>
104
+ <script src="https://cdn.jsdelivr.net/npm/@grame/faust-web-component@0.5.0/dist/faust-web-component.js"></script>
105
105
  ```
106
106
 
107
107
  ## Demo
@@ -41,7 +41,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
41
41
  if ("object" != typeof t2 || !t2) return t2;
42
42
  var e = t2[Symbol.toPrimitive];
43
43
  if (void 0 !== e) {
44
- var i2 = e.call(t2, r2 || "default");
44
+ var i2 = e.call(t2, r2);
45
45
  if ("object" != typeof i2) return i2;
46
46
  throw new TypeError("@@toPrimitive must return a primitive value.");
47
47
  }
@@ -128,7 +128,6 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
128
128
  }
129
129
  }, A = {
130
130
  GROUP: "duotone-group",
131
- SWAP_OPACITY: "swap-opacity",
132
131
  PRIMARY: "primary",
133
132
  SECONDARY: "secondary"
134
133
  }, P = ["fa-classic", "fa-duotone", "fa-sharp", "fa-sharp-duotone"];
@@ -262,17 +261,11 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
262
261
  var Ct = {
263
262
  kit: {
264
263
  "fa-kit": "fak"
265
- },
266
- "kit-duotone": {
267
- "fa-kit-duotone": "fakd"
268
264
  }
269
265
  };
270
266
  var Lt = ["fak", "fakd"], Wt = {
271
267
  kit: {
272
268
  fak: "fa-kit"
273
- },
274
- "kit-duotone": {
275
- fakd: "fa-kit-duotone"
276
269
  }
277
270
  };
278
271
  var Et = {
@@ -1557,7 +1550,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
1557
1550
  }
1558
1551
  return new Promise((resolve, reject) => {
1559
1552
  if (givenPrefix === "fa") {
1560
- const shim = byOldName(iconName);
1553
+ const shim = byOldName(iconName) || {};
1561
1554
  iconName = shim.iconName || iconName;
1562
1555
  prefix = shim.prefix || prefix;
1563
1556
  }
@@ -9429,6 +9422,14 @@ export default ${(_b = jsCode.match(jsCodeHead)) == null ? void 0 : _b[1]};
9429
9422
  this.pitchWheel(msg.data[0], msg.data[1]);
9430
9423
  break;
9431
9424
  }
9425
+ case "keyOn": {
9426
+ this.keyOn(msg.data[0], msg.data[1], msg.data[2]);
9427
+ break;
9428
+ }
9429
+ case "keyOff": {
9430
+ this.keyOff(msg.data[0], msg.data[1], msg.data[2]);
9431
+ break;
9432
+ }
9432
9433
  case "param": {
9433
9434
  this.setParamValue(msg.data.path, msg.data.value);
9434
9435
  break;
@@ -9473,6 +9474,12 @@ export default ${(_b = jsCode.match(jsCodeHead)) == null ? void 0 : _b[1]};
9473
9474
  pitchWheel(channel, wheel) {
9474
9475
  this.fDSPCode.pitchWheel(channel, wheel);
9475
9476
  }
9477
+ keyOn(channel, pitch, velocity) {
9478
+ this.fDSPCode.keyOn(channel, pitch, velocity);
9479
+ }
9480
+ keyOff(channel, pitch, velocity) {
9481
+ this.fDSPCode.keyOff(channel, pitch, velocity);
9482
+ }
9476
9483
  propagateAcc(accelerationIncludingGravity, invert = false) {
9477
9484
  this.fDSPCode.propagateAcc(accelerationIncludingGravity, invert);
9478
9485
  }
@@ -11313,6 +11320,9 @@ export default ${(_b = jsCode.match(jsCodeHead)) == null ? void 0 : _b[1]};
11313
11320
  this.fSoundfileBuffers = {};
11314
11321
  this.fPitchwheelLabel = [];
11315
11322
  this.fCtrlLabel = new Array(128).fill(null).map(() => []);
11323
+ this.fMidiKeyLabel = new Array(128).fill(null).map(() => []);
11324
+ this.fMidiKeyOnLabel = new Array(128).fill(null).map(() => []);
11325
+ this.fMidiKeyOffLabel = new Array(128).fill(null).map(() => []);
11316
11326
  this.fPathTable = {};
11317
11327
  this.fUICallback = (item) => {
11318
11328
  if (item.type === "hbargraph" || item.type === "vbargraph") {
@@ -11325,6 +11335,7 @@ export default ${(_b = jsCode.match(jsCodeHead)) == null ? void 0 : _b[1]};
11325
11335
  if (!item.meta)
11326
11336
  return;
11327
11337
  item.meta.forEach((meta2) => {
11338
+ var _a2, _b, _c, _d, _e, _f;
11328
11339
  const { midi, acc, gyr } = meta2;
11329
11340
  if (midi) {
11330
11341
  const strMidi = midi.trim();
@@ -11338,10 +11349,25 @@ export default ${(_b = jsCode.match(jsCodeHead)) == null ? void 0 : _b[1]};
11338
11349
  } else {
11339
11350
  const matched2 = strMidi.match(/^ctrl\s(\d+)\s(\d+)/);
11340
11351
  const matched1 = strMidi.match(/^ctrl\s(\d+)/);
11352
+ const matchedKey = strMidi.match(/^key\s+(\d+)(?:\s+(\d+))?$/);
11353
+ const matchedKeyOn = strMidi.match(/^keyon\s+(\d+)(?:\s+(\d+))?$/);
11354
+ const matchedKeyOff = strMidi.match(/^keyoff\s+(\d+)(?:\s+(\d+))?$/);
11341
11355
  if (matched2) {
11342
11356
  this.fCtrlLabel[parseInt(matched2[1])].push({ path: item.address, chan: parseInt(matched2[2]), min: item.min, max: item.max });
11343
11357
  } else if (matched1) {
11344
11358
  this.fCtrlLabel[parseInt(matched1[1])].push({ path: item.address, chan: 0, min: item.min, max: item.max });
11359
+ } else if (matchedKey) {
11360
+ const note = parseInt(matchedKey[1]);
11361
+ const channel = matchedKey[2] ? parseInt(matchedKey[2]) : 0;
11362
+ this.fMidiKeyLabel[note].push({ path: item.address, chan: channel, min: (_a2 = item.min) != null ? _a2 : 0, max: (_b = item.max) != null ? _b : 1 });
11363
+ } else if (matchedKeyOn) {
11364
+ const note = parseInt(matchedKeyOn[1]);
11365
+ const channel = matchedKeyOn[2] ? parseInt(matchedKeyOn[2]) : 0;
11366
+ this.fMidiKeyOnLabel[note].push({ path: item.address, chan: channel, min: (_c = item.min) != null ? _c : 0, max: (_d = item.max) != null ? _d : 1 });
11367
+ } else if (matchedKeyOff) {
11368
+ const note = parseInt(matchedKeyOff[1]);
11369
+ const channel = matchedKeyOff[2] ? parseInt(matchedKeyOff[2]) : 0;
11370
+ this.fMidiKeyOffLabel[note].push({ path: item.address, chan: channel, min: (_e = item.min) != null ? _e : 0, max: (_f = item.max) != null ? _f : 1 });
11345
11371
  }
11346
11372
  }
11347
11373
  }
@@ -11601,6 +11627,15 @@ export default ${(_b = jsCode.match(jsCodeHead)) == null ? void 0 : _b[1]};
11601
11627
  return this.ctrlChange(channel, data1, data2);
11602
11628
  if (cmd2 === 14)
11603
11629
  return this.pitchWheel(channel, data2 * 128 + data1);
11630
+ if (cmd2 === 9) {
11631
+ if (data2 > 0)
11632
+ return this.keyOn(channel, data1, data2);
11633
+ else
11634
+ return this.keyOff(channel, data1, data2);
11635
+ }
11636
+ if (cmd2 === 8) {
11637
+ return this.keyOff(channel, data1, data2);
11638
+ }
11604
11639
  }
11605
11640
  ctrlChange(channel, ctrl, value) {
11606
11641
  if (this.fPlotHandler)
@@ -11616,6 +11651,46 @@ export default ${(_b = jsCode.match(jsCodeHead)) == null ? void 0 : _b[1]};
11616
11651
  });
11617
11652
  }
11618
11653
  }
11654
+ keyOn(channel, pitch, velocity) {
11655
+ if (this.fPlotHandler)
11656
+ this.fCachedEvents.push({ type: "keyOn", data: [channel, pitch, velocity] });
11657
+ this.fMidiKeyOnLabel[pitch].forEach((key) => {
11658
+ const { path, chan } = key;
11659
+ if (chan === 0 || channel === chan - 1) {
11660
+ this.setParamValue(path, _FaustBaseWebAudioDsp.remap(velocity, 0, 127, key.min, key.max));
11661
+ if (this.fOutputHandler)
11662
+ this.fOutputHandler(path, this.getParamValue(path));
11663
+ }
11664
+ });
11665
+ this.fMidiKeyLabel[pitch].forEach((key) => {
11666
+ const { path, chan } = key;
11667
+ if (chan === 0 || channel === chan - 1) {
11668
+ this.setParamValue(path, _FaustBaseWebAudioDsp.remap(velocity, 0, 127, key.min, key.max));
11669
+ if (this.fOutputHandler)
11670
+ this.fOutputHandler(path, this.getParamValue(path));
11671
+ }
11672
+ });
11673
+ }
11674
+ keyOff(channel, pitch, velocity) {
11675
+ if (this.fPlotHandler)
11676
+ this.fCachedEvents.push({ type: "keyOff", data: [channel, pitch, velocity] });
11677
+ this.fMidiKeyOffLabel[pitch].forEach((key) => {
11678
+ const { path, chan } = key;
11679
+ if (chan === 0 || channel === chan - 1) {
11680
+ this.setParamValue(path, _FaustBaseWebAudioDsp.remap(velocity, 0, 127, key.min, key.max));
11681
+ if (this.fOutputHandler)
11682
+ this.fOutputHandler(path, this.getParamValue(path));
11683
+ }
11684
+ });
11685
+ this.fMidiKeyLabel[pitch].forEach((key) => {
11686
+ const { path, chan } = key;
11687
+ if (chan === 0 || channel === chan - 1) {
11688
+ this.setParamValue(path, 0);
11689
+ if (this.fOutputHandler)
11690
+ this.fOutputHandler(path, this.getParamValue(path));
11691
+ }
11692
+ });
11693
+ }
11619
11694
  pitchWheel(channel, wheel) {
11620
11695
  if (this.fPlotHandler)
11621
11696
  this.fCachedEvents.push({ type: "pitchWheel", data: [channel, wheel] });
@@ -12248,6 +12323,12 @@ export default ${(_b = jsCode.match(jsCodeHead)) == null ? void 0 : _b[1]};
12248
12323
  pitchWheel(chan, value) {
12249
12324
  this.fDSPCode.pitchWheel(chan, value);
12250
12325
  }
12326
+ keyOn(channel, pitch, velocity) {
12327
+ this.fDSPCode.keyOn(channel, pitch, velocity);
12328
+ }
12329
+ keyOff(channel, pitch, velocity) {
12330
+ this.fDSPCode.keyOff(channel, pitch, velocity);
12331
+ }
12251
12332
  setParamValue(path, value) {
12252
12333
  this.fDSPCode.setParamValue(path, value);
12253
12334
  }
@@ -12792,6 +12873,10 @@ export default ${(_b = jsCode.match(jsCodeHead)) == null ? void 0 : _b[1]};
12792
12873
  this.ctrlChange(channel, data1, data2);
12793
12874
  else if (cmd2 === 14)
12794
12875
  this.pitchWheel(channel, data2 * 128 + data1);
12876
+ if (cmd2 === 8 || cmd2 === 9 && data2 === 0)
12877
+ this.keyOff(channel, data1, data2);
12878
+ else if (cmd2 === 9)
12879
+ this.keyOn(channel, data1, data2);
12795
12880
  else
12796
12881
  this.port.postMessage({ type: "midi", data });
12797
12882
  }
@@ -12803,6 +12888,14 @@ export default ${(_b = jsCode.match(jsCodeHead)) == null ? void 0 : _b[1]};
12803
12888
  const e = { type: "pitchWheel", data: [channel, wheel] };
12804
12889
  this.port.postMessage(e);
12805
12890
  }
12891
+ keyOn(channel, pitch, velocity) {
12892
+ const e = { type: "keyOn", data: [channel, pitch, velocity] };
12893
+ this.port.postMessage(e);
12894
+ }
12895
+ keyOff(channel, pitch, velocity) {
12896
+ const e = { type: "keyOff", data: [channel, pitch, velocity] };
12897
+ this.port.postMessage(e);
12898
+ }
12806
12899
  get hasAccInput() {
12807
12900
  return __privateGet(this, _hasAccInput);
12808
12901
  }
@@ -12964,42 +13057,14 @@ export default ${(_b = jsCode.match(jsCodeHead)) == null ? void 0 : _b[1]};
12964
13057
  async startSensors() {
12965
13058
  if (this.hasAccInput) {
12966
13059
  if (window.DeviceMotionEvent) {
12967
- if (typeof window.DeviceMotionEvent.requestPermission === "function") {
12968
- try {
12969
- const response = await window.DeviceMotionEvent.requestPermission();
12970
- if (response === "granted") {
12971
- window.addEventListener("devicemotion", this.handleDeviceMotion, true);
12972
- } else if (response === "denied") {
12973
- alert("You have denied access to motion and orientation data. To enable it, go to Settings > Safari > Motion & Orientation Access.");
12974
- throw new Error("Unable to access the accelerometer.");
12975
- }
12976
- } catch (error) {
12977
- console.error(error);
12978
- }
12979
- } else {
12980
- window.addEventListener("devicemotion", this.handleDeviceMotion, true);
12981
- }
13060
+ window.addEventListener("devicemotion", this.handleDeviceMotion, true);
12982
13061
  } else {
12983
13062
  console.log("Cannot set the accelerometer handler.");
12984
13063
  }
12985
13064
  }
12986
13065
  if (this.hasGyrInput) {
12987
13066
  if (window.DeviceMotionEvent) {
12988
- if (typeof window.DeviceOrientationEvent.requestPermission === "function") {
12989
- try {
12990
- const response = await window.DeviceOrientationEvent.requestPermission();
12991
- if (response === "granted") {
12992
- window.addEventListener("deviceorientation", this.handleDeviceOrientation, true);
12993
- } else if (response === "denied") {
12994
- alert("You have denied access to motion and orientation data. To enable it, go to Settings > Safari > Motion & Orientation Access.");
12995
- throw new Error("Unable to access the gyroscope.");
12996
- }
12997
- } catch (error) {
12998
- console.error(error);
12999
- }
13000
- } else {
13001
- window.addEventListener("deviceorientation", this.handleDeviceOrientation, true);
13002
- }
13067
+ window.addEventListener("deviceorientation", this.handleDeviceOrientation, true);
13003
13068
  } else {
13004
13069
  console.log("Cannot set the gyroscope handler.");
13005
13070
  }
@@ -13052,6 +13117,12 @@ export default ${(_b = jsCode.match(jsCodeHead)) == null ? void 0 : _b[1]};
13052
13117
  pitchWheel(chan, value) {
13053
13118
  this.fDSPCode.pitchWheel(chan, value);
13054
13119
  }
13120
+ keyOn(channel, pitch, velocity) {
13121
+ this.fDSPCode.keyOn(channel, pitch, velocity);
13122
+ }
13123
+ keyOff(channel, pitch, velocity) {
13124
+ this.fDSPCode.keyOff(channel, pitch, velocity);
13125
+ }
13055
13126
  setParamValue(path, value) {
13056
13127
  this.fDSPCode.setParamValue(path, value);
13057
13128
  }
@@ -13635,7 +13706,19 @@ const dependencies = {
13635
13706
  iconName: "angles-left",
13636
13707
  icon: [512, 512, [171, "angle-double-left"], "f100", "M41.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l160 160c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L109.3 256 246.6 118.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-160 160zm352-160l-160 160c-12.5 12.5-12.5 32.8 0 45.3l160 160c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L301.3 256 438.6 118.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0z"]
13637
13708
  };
13638
- for (const icon2 of [faPlay, faStop, faUpRightFromSquare, faSquareCaretLeft, faAnglesLeft, faAnglesRight, faSliders, faDiagramProject, faWaveSquare, faChartLine, faPowerOff]) {
13709
+ for (const icon2 of [
13710
+ faPlay,
13711
+ faStop,
13712
+ faUpRightFromSquare,
13713
+ faSquareCaretLeft,
13714
+ faAnglesLeft,
13715
+ faAnglesRight,
13716
+ faSliders,
13717
+ faDiagramProject,
13718
+ faWaveSquare,
13719
+ faChartLine,
13720
+ faPowerOff
13721
+ ]) {
13639
13722
  library$1.add(icon2);
13640
13723
  }
13641
13724
  let compiler;
@@ -13668,7 +13751,7 @@ const dependencies = {
13668
13751
  }
13669
13752
  let getInputDevicesPromise;
13670
13753
  async function getInputDevices() {
13671
- if (getInputDevicesPromise === void 0) {
13754
+ if (!getInputDevicesPromise) {
13672
13755
  getInputDevicesPromise = _getInputDevices();
13673
13756
  }
13674
13757
  await getInputDevicesPromise;
@@ -13679,8 +13762,8 @@ const dependencies = {
13679
13762
  if (navigator.requestMIDIAccess) {
13680
13763
  navigator.requestMIDIAccess().then((midiAccess) => {
13681
13764
  const inputDevices = midiAccess.inputs.values();
13682
- for (const midiInput2 of inputDevices) {
13683
- midiInput2.onmidimessage = (event) => {
13765
+ for (const midiInput of inputDevices) {
13766
+ midiInput.onmidimessage = (event) => {
13684
13767
  onMIDIMessage(event.data);
13685
13768
  };
13686
13769
  resolve();
@@ -13700,14 +13783,14 @@ const dependencies = {
13700
13783
  };
13701
13784
  function extractMidiAndNvoices(jsonData) {
13702
13785
  const optionsMetadata = jsonData.meta.find((meta2) => meta2.options);
13703
- if (optionsMetadata) {
13786
+ if (optionsMetadata && optionsMetadata.options) {
13704
13787
  const options = optionsMetadata.options;
13705
13788
  const midiRegex = /\[midi:(on|off)\]/;
13706
13789
  const nvoicesRegex = /\[nvoices:(\d+)\]/;
13707
13790
  const midiMatch = options.match(midiRegex);
13708
13791
  const nvoicesMatch = options.match(nvoicesRegex);
13709
13792
  const midi = midiMatch ? midiMatch[1] === "on" : false;
13710
- const nvoices = nvoicesMatch ? parseInt(nvoicesMatch[1]) : -1;
13793
+ const nvoices = nvoicesMatch ? parseInt(nvoicesMatch[1], 10) : -1;
13711
13794
  return { midi, nvoices };
13712
13795
  } else {
13713
13796
  return { midi: false, nvoices: -1 };
@@ -15414,6 +15497,11 @@ const dependencies = {
15414
15497
  return 1;
15415
15498
  },
15416
15499
  reconfigure: (state, oldState) => {
15500
+ let init = state.facet(initField), oldInit = oldState.facet(initField), reInit;
15501
+ if ((reInit = init.find((i2) => i2.field == this)) && reInit != oldInit.find((i2) => i2.field == this)) {
15502
+ state.values[idx] = reInit.create(state);
15503
+ return 1;
15504
+ }
15417
15505
  if (oldState.config.address[this.id] != null) {
15418
15506
  state.values[idx] = oldState.field(this);
15419
15507
  return 0;
@@ -30315,7 +30403,7 @@ const dependencies = {
30315
30403
  }
30316
30404
  function delimitedStrategy(context, align, units, closing2, closedAt) {
30317
30405
  let after = context.textAfter, space = after.match(/^\s*/)[0].length;
30318
- let closed = closedAt == context.pos + space;
30406
+ let closed = closing2 && after.slice(space, space + closing2.length) == closing2 || closedAt == context.pos + space;
30319
30407
  let aligned = bracketedAligned(context);
30320
30408
  if (aligned)
30321
30409
  return closed ? context.column(aligned.from) : context.column(aligned.to);
@@ -31208,8 +31296,7 @@ const dependencies = {
31208
31296
  }
31209
31297
  ({
31210
31298
  rtl: /* @__PURE__ */ Decoration.mark({ class: "cm-iso", inclusive: true, attributes: { dir: "rtl" }, bidiIsolate: Direction.RTL }),
31211
- ltr: /* @__PURE__ */ Decoration.mark({ class: "cm-iso", inclusive: true, attributes: { dir: "ltr" }, bidiIsolate: Direction.LTR }),
31212
- auto: /* @__PURE__ */ Decoration.mark({ class: "cm-iso", inclusive: true, attributes: { dir: "auto" }, bidiIsolate: null })
31299
+ ltr: /* @__PURE__ */ Decoration.mark({ class: "cm-iso", inclusive: true, attributes: { dir: "ltr" }, bidiIsolate: Direction.LTR })
31213
31300
  });
31214
31301
  const toggleComment = (target) => {
31215
31302
  let { state } = target, line = state.doc.lineAt(state.selection.main.from), config2 = getConfig(target.state, line.from);
@@ -32930,7 +33017,18 @@ const dependencies = {
32930
33017
  return this.prevMatchInRange(state, 0, curFrom) || this.prevMatchInRange(state, curTo, state.doc.length);
32931
33018
  }
32932
33019
  getReplacement(result) {
32933
- return this.spec.unquote(this.spec.replace).replace(/\$([$&\d+])/g, (m, i2) => i2 == "$" ? "$" : i2 == "&" ? result.match[0] : i2 != "0" && +i2 < result.match.length ? result.match[i2] : m);
33020
+ return this.spec.unquote(this.spec.replace).replace(/\$([$&]|\d+)/g, (m, i2) => {
33021
+ if (i2 == "&")
33022
+ return result.match[0];
33023
+ if (i2 == "$")
33024
+ return "$";
33025
+ for (let l = i2.length; l > 0; l--) {
33026
+ let n = +i2.slice(0, l);
33027
+ if (n > 0 && n < result.match.length)
33028
+ return result.match[n] + i2.slice(l);
33029
+ }
33030
+ return m;
33031
+ });
32934
33032
  }
32935
33033
  matchAll(state, limit) {
32936
33034
  let cursor = regexpCursor(this.spec, state, 0, state.doc.length), ranges = [];
@@ -33915,8 +34013,8 @@ const dependencies = {
33915
34013
  let selRect = sel.getBoundingClientRect();
33916
34014
  let space = this.space;
33917
34015
  if (!space) {
33918
- let win = this.dom.ownerDocument.defaultView || window;
33919
- space = { left: 0, top: 0, right: win.innerWidth, bottom: win.innerHeight };
34016
+ let docElt = this.dom.ownerDocument.documentElement;
34017
+ space = { left: 0, top: 0, right: docElt.clientWidth, bottom: docElt.clientHeight };
33920
34018
  }
33921
34019
  if (selRect.top > Math.min(space.bottom, listRect.bottom) - 10 || selRect.bottom < Math.max(space.top, listRect.top) + 10)
33922
34020
  return null;
@@ -33939,6 +34037,10 @@ const dependencies = {
33939
34037
  ul.setAttribute("role", "listbox");
33940
34038
  ul.setAttribute("aria-expanded", "true");
33941
34039
  ul.setAttribute("aria-label", this.view.state.phrase("Completions"));
34040
+ ul.addEventListener("mousedown", (e) => {
34041
+ if (e.target == ul)
34042
+ e.preventDefault();
34043
+ });
33942
34044
  let curSection = null;
33943
34045
  for (let i2 = range.from; i2 < range.to; i2++) {
33944
34046
  let { completion, match } = options[i2], { section } = completion;
@@ -34699,7 +34801,7 @@ const dependencies = {
34699
34801
  function closeBrackets() {
34700
34802
  return [inputHandler, bracketState];
34701
34803
  }
34702
- const definedClosing = "()[]{}<>";
34804
+ const definedClosing = "()[]{}<>«»»«[]{}";
34703
34805
  function closing(ch) {
34704
34806
  for (let i2 = 0; i2 < definedClosing.length; i2 += 2)
34705
34807
  if (definedClosing.charCodeAt(i2) == ch)
@@ -35979,7 +36081,6 @@ const dependencies = {
35979
36081
  /* package java.lang */
35980
36082
  "Boolean Byte Character CharSequence Class ClassLoader Cloneable Comparable Compiler Double Exception Float Integer Long Math Number Object Package Pair Process Runtime Runnable SecurityManager Short StackTraceElement StrictMath String StringBuffer System Thread ThreadGroup ThreadLocal Throwable Triple Void Annotation Any BooleanArray ByteArray Char CharArray DeprecationLevel DoubleArray Enum FloatArray Function Int IntArray Lazy LazyThreadSafetyMode LongArray Nothing ShortArray Unit"
35981
36083
  ),
35982
- intendSwitch: false,
35983
36084
  indentStatements: false,
35984
36085
  multiLineStrings: true,
35985
36086
  number: /^(?:0x[a-f\d_]+|0b[01_]+|(?:[\d_]+(\.\d+)?|\.\d+)(?:e[-+]?[\d_]+)?)(u|ll?|l|f)?/i,
@@ -36747,7 +36848,7 @@ const dependencies = {
36747
36848
  let spectrum;
36748
36849
  let gmidi = false;
36749
36850
  let gnvoices = -1;
36750
- let sourceNode = void 0;
36851
+ let sourceNode;
36751
36852
  runButton.onclick = async () => {
36752
36853
  if (audioCtx.state === "suspended") {
36753
36854
  await audioCtx.resume();
@@ -37040,7 +37141,7 @@ const dependencies = {
37040
37141
  let input;
37041
37142
  let faustUI;
37042
37143
  let generator;
37043
- let sourceNode = void 0;
37144
+ let sourceNode;
37044
37145
  const setup = async () => {
37045
37146
  await faustPromise;
37046
37147
  await default_generator.compile(compiler, "main", code2, "-ftz 2");
package/index.html CHANGED
@@ -14,6 +14,7 @@ declare license "MIT";
14
14
  declare copyright "(c)Mike Olsen, CCRMA (Stanford University)";
15
15
 
16
16
  import("stdfaust.lib");
17
+ declare options "[midi:on][nvoices:12]";
17
18
 
18
19
  process = pm.SFFormantModelFofSmooth_ui_MIDI <: _,_;
19
20
  -->
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@grame/faust-web-component",
3
3
  "description": "Web component embedding the Faust Compiler",
4
- "version": "0.4.5",
4
+ "version": "0.5.0",
5
5
  "module": "dist/faust-web-component.js",
6
6
  "files": [
7
7
  "src/",
@@ -42,7 +42,7 @@
42
42
  "@codemirror/legacy-modes": "^6.3.3",
43
43
  "@fortawesome/fontawesome-svg-core": "^6.4.2",
44
44
  "@fortawesome/free-solid-svg-icons": "^6.4.2",
45
- "@grame/faustwasm": "^0.8.2",
45
+ "@grame/faustwasm": "^0.9.1",
46
46
  "@shren/faust-ui": "^1.1.16",
47
47
  "codemirror": "^6.0.1",
48
48
  "split.js": "^1.6.5"
package/src/common.ts CHANGED
@@ -1,58 +1,107 @@
1
- import { IFaustMonoWebAudioNode, IFaustPolyWebAudioNode, FaustCompiler, FaustMonoDspGenerator, FaustPolyDspGenerator, FaustSvgDiagrams, LibFaust, instantiateFaustModuleFromFile } from "@grame/faustwasm"
2
- import jsURL from "@grame/faustwasm/libfaust-wasm/libfaust-wasm.js?url"
3
- import dataURL from "@grame/faustwasm/libfaust-wasm/libfaust-wasm.data?url"
4
- import wasmURL from "@grame/faustwasm/libfaust-wasm/libfaust-wasm.wasm?url"
5
- import { library } from "@fortawesome/fontawesome-svg-core"
6
- import { faPlay, faStop, faUpRightFromSquare, faSquareCaretLeft, faAnglesLeft, faAnglesRight, faSliders, faDiagramProject, faWaveSquare, faChartLine, faPowerOff } from "@fortawesome/free-solid-svg-icons"
7
-
8
- for (const icon of [faPlay, faStop, faUpRightFromSquare, faSquareCaretLeft, faAnglesLeft, faAnglesRight, faSliders, faDiagramProject, faWaveSquare, faChartLine, faPowerOff]) {
9
- library.add(icon)
1
+ // Import Faust Web Audio API
2
+ import {
3
+ IFaustMonoWebAudioNode,
4
+ IFaustPolyWebAudioNode,
5
+ FaustCompiler,
6
+ FaustMonoDspGenerator,
7
+ FaustPolyDspGenerator,
8
+ FaustSvgDiagrams,
9
+ LibFaust,
10
+ instantiateFaustModuleFromFile
11
+ } from "@grame/faustwasm";
12
+
13
+ // Import Faust module URLs
14
+ import jsURL from "@grame/faustwasm/libfaust-wasm/libfaust-wasm.js?url";
15
+ import dataURL from "@grame/faustwasm/libfaust-wasm/libfaust-wasm.data?url";
16
+ import wasmURL from "@grame/faustwasm/libfaust-wasm/libfaust-wasm.wasm?url";
17
+
18
+ // Import FontAwesome icons
19
+ import { library } from "@fortawesome/fontawesome-svg-core";
20
+ import {
21
+ faPlay,
22
+ faStop,
23
+ faUpRightFromSquare,
24
+ faSquareCaretLeft,
25
+ faAnglesLeft,
26
+ faAnglesRight,
27
+ faSliders,
28
+ faDiagramProject,
29
+ faWaveSquare,
30
+ faChartLine,
31
+ faPowerOff
32
+ } from "@fortawesome/free-solid-svg-icons";
33
+
34
+ // Add icons to FontAwesome library
35
+ for (const icon of [
36
+ faPlay,
37
+ faStop,
38
+ faUpRightFromSquare,
39
+ faSquareCaretLeft,
40
+ faAnglesLeft,
41
+ faAnglesRight,
42
+ faSliders,
43
+ faDiagramProject,
44
+ faWaveSquare,
45
+ faChartLine,
46
+ faPowerOff
47
+ ]) {
48
+ library.add(icon);
10
49
  }
11
50
 
12
- export let compiler: FaustCompiler
13
- export let svgDiagrams: FaustSvgDiagrams
14
- export const default_generator = new FaustMonoDspGenerator()
15
- export const get_mono_generator = () => new FaustMonoDspGenerator()
16
- export const get_poly_generator = () => new FaustPolyDspGenerator()
51
+ // Global variables for Faust
52
+ export let compiler: FaustCompiler;
53
+ export let svgDiagrams: FaustSvgDiagrams;
54
+ export const default_generator = new FaustMonoDspGenerator();
55
+ export const get_mono_generator = (): FaustMonoDspGenerator => new FaustMonoDspGenerator();
56
+ export const get_poly_generator = (): FaustPolyDspGenerator => new FaustPolyDspGenerator();
17
57
 
18
- async function loadFaust() {
58
+ // Load Faust module
59
+ async function loadFaust(): Promise<void> {
19
60
  // Setup Faust
20
- const module = await instantiateFaustModuleFromFile(jsURL, dataURL, wasmURL)
21
- const libFaust = new LibFaust(module)
22
- compiler = new FaustCompiler(libFaust)
23
- svgDiagrams = new FaustSvgDiagrams(compiler)
61
+ const module = await instantiateFaustModuleFromFile(jsURL, dataURL, wasmURL);
62
+ const libFaust = new LibFaust(module);
63
+ compiler = new FaustCompiler(libFaust);
64
+ svgDiagrams = new FaustSvgDiagrams(compiler);
24
65
  }
25
66
 
26
- export const faustPromise = loadFaust()
27
- export const audioCtx = new AudioContext({ latencyHint: 0.00001 })
67
+ // Initialize Faust
68
+ export const faustPromise: Promise<void> = loadFaust();
69
+
70
+ // Create an AudioContext
71
+ export const audioCtx: AudioContext = new AudioContext({ latencyHint: 0.00001 });
28
72
  audioCtx.destination.channelInterpretation = "discrete";
29
73
 
30
- export const deviceUpdateCallbacks: ((d: MediaDeviceInfo[]) => void)[] = []
31
- let devices: MediaDeviceInfo[] = []
32
- async function _getInputDevices() {
74
+ export const deviceUpdateCallbacks: Array<(devices: MediaDeviceInfo[]) => void> = [];
75
+ let devices: MediaDeviceInfo[] = [];
76
+
77
+ // Get input devices
78
+ async function _getInputDevices(): Promise<void> {
33
79
  if (navigator.mediaDevices) {
34
- navigator.mediaDevices.ondevicechange = _getInputDevices
80
+ navigator.mediaDevices.ondevicechange = _getInputDevices;
35
81
  try {
36
- await navigator.mediaDevices.getUserMedia({ audio: true })
37
- } catch (e) { }
38
- devices = await navigator.mediaDevices.enumerateDevices()
82
+ await navigator.mediaDevices.getUserMedia({ audio: true });
83
+ } catch (e) {
84
+ // Ignore permission errors
85
+ }
86
+ devices = await navigator.mediaDevices.enumerateDevices();
39
87
  for (const callback of deviceUpdateCallbacks) {
40
- callback(devices)
88
+ callback(devices);
41
89
  }
42
90
  }
43
91
  }
44
92
 
45
- let getInputDevicesPromise: Promise<void> | undefined
46
- export async function getInputDevices() {
47
- if (getInputDevicesPromise === undefined) {
48
- getInputDevicesPromise = _getInputDevices()
93
+ let getInputDevicesPromise: Promise<void> | undefined;
94
+ export async function getInputDevices(): Promise<MediaDeviceInfo[]> {
95
+ if (!getInputDevicesPromise) {
96
+ getInputDevicesPromise = _getInputDevices();
49
97
  }
50
- await getInputDevicesPromise
51
- return devices
98
+ await getInputDevicesPromise;
99
+ return devices;
52
100
  }
53
101
 
102
+ // Access MIDI device
54
103
  export async function accessMIDIDevice(
55
- onMIDIMessage: (data) => void
104
+ onMIDIMessage: (data: Uint8Array) => void
56
105
  ): Promise<void> {
57
106
  return new Promise<void>((resolve, reject) => {
58
107
  if (navigator.requestMIDIAccess) {
@@ -60,7 +109,6 @@ export async function accessMIDIDevice(
60
109
  .requestMIDIAccess()
61
110
  .then((midiAccess) => {
62
111
  const inputDevices = midiAccess.inputs.values();
63
- let midiInput: WebMidi.MIDIInput | null = null;
64
112
  for (const midiInput of inputDevices) {
65
113
  midiInput.onmidimessage = (event) => {
66
114
  onMIDIMessage(event.data);
@@ -78,14 +126,23 @@ export async function accessMIDIDevice(
78
126
  }
79
127
 
80
128
  // Set up MIDI input callback
81
- export const midiInputCallback = (node: IFaustMonoWebAudioNode | IFaustPolyWebAudioNode) => {
82
- return (data) => { node.midiMessage(data); }
129
+ export const midiInputCallback = (node: IFaustMonoWebAudioNode | IFaustPolyWebAudioNode)
130
+ : ((data: Uint8Array) => void) => {
131
+ return (data: Uint8Array) => {
132
+ node.midiMessage(data);
133
+ };
134
+ };
135
+
136
+ // Define type for JSON data with metadata
137
+ interface JSONData {
138
+ meta: Array<{ options?: string; }>;
83
139
  }
84
140
 
85
- // Analyze the metadata of a Faust JSON file extract the [midi:on] and [nvoices:n] options
86
- export function extractMidiAndNvoices(jsonData: JSONData): { midi: boolean, nvoices: number } {
87
- const optionsMetadata = jsonData.meta.find(meta => meta.options);
88
- if (optionsMetadata) {
141
+ // Analyze the metadata of a Faust JSON file and extract the [midi:on] and [nvoices:n] options
142
+ export function extractMidiAndNvoices(jsonData: JSONData)
143
+ : { midi: boolean; nvoices: number } {
144
+ const optionsMetadata = jsonData.meta.find((meta) => meta.options);
145
+ if (optionsMetadata && optionsMetadata.options) {
89
146
  const options = optionsMetadata.options;
90
147
 
91
148
  const midiRegex = /\[midi:(on|off)\]/;
@@ -95,10 +152,10 @@ export function extractMidiAndNvoices(jsonData: JSONData): { midi: boolean, nvoi
95
152
  const nvoicesMatch = options.match(nvoicesRegex);
96
153
 
97
154
  const midi = midiMatch ? midiMatch[1] === "on" : false;
98
- const nvoices = nvoicesMatch ? parseInt(nvoicesMatch[1]) : -1;
155
+ const nvoices = nvoicesMatch ? parseInt(nvoicesMatch[1], 10) : -1;
99
156
 
100
157
  return { midi, nvoices };
101
158
  } else {
102
159
  return { midi: false, nvoices: -1 };
103
160
  }
104
- }
161
+ }
@@ -10,7 +10,6 @@ import {
10
10
  compiler,
11
11
  svgDiagrams,
12
12
  default_generator,
13
- get_mono_generator,
14
13
  get_poly_generator,
15
14
  getInputDevices,
16
15
  deviceUpdateCallbacks,
@@ -230,34 +229,34 @@ template.innerHTML = `
230
229
  // FaustEditor Web Component
231
230
  export default class FaustEditor extends HTMLElement {
232
231
  constructor() {
233
- super()
232
+ super();
234
233
  }
235
234
 
236
235
  connectedCallback() {
237
236
  // Initial setup when the component is attached to the DOM
238
- const code = this.innerHTML.replace("<!--", "").replace("-->", "").trim()
239
- this.attachShadow({ mode: "open" }).appendChild(template.content.cloneNode(true))
237
+ const code = this.innerHTML.replace("<!--", "").replace("-->", "").trim();
238
+ this.attachShadow({ mode: "open" }).appendChild(template.content.cloneNode(true));
240
239
 
241
240
  // Set up links, buttons, and editor
242
- const ideLink = this.shadowRoot!.querySelector("#ide") as HTMLAnchorElement
243
- const editorEl = this.shadowRoot!.querySelector("#editor") as HTMLDivElement
244
- const editor = createEditor(editorEl, code)
241
+ const ideLink = this.shadowRoot!.querySelector("#ide") as HTMLAnchorElement;
242
+ const editorEl = this.shadowRoot!.querySelector("#editor") as HTMLDivElement;
243
+ const editor = createEditor(editorEl, code);
245
244
 
246
245
  ideLink.onfocus = () => {
247
246
  // Open current contents of editor in IDE
248
- const urlParams = new URLSearchParams()
249
- urlParams.set("inline", btoa(editor.state.doc.toString()).replace("+", "-").replace("/", "_"))
250
- ideLink.href = `https://faustide.grame.fr/?${urlParams.toString()}`
247
+ const urlParams = new URLSearchParams();
248
+ urlParams.set("inline", btoa(editor.state.doc.toString()).replace("+", "-").replace("/", "_"));
249
+ ideLink.href = `https://faustide.grame.fr/?${urlParams.toString()}`;
251
250
  }
252
251
 
253
- const runButton = this.shadowRoot!.querySelector("#run") as HTMLButtonElement
254
- const stopButton = this.shadowRoot!.querySelector("#stop") as HTMLButtonElement
255
- const faustUIRoot = this.shadowRoot!.querySelector("#faust-ui") as HTMLDivElement
256
- const faustDiagram = this.shadowRoot!.querySelector("#faust-diagram") as HTMLDivElement
257
- const sidebar = this.shadowRoot!.querySelector("#sidebar") as HTMLDivElement
258
- const sidebarContent = this.shadowRoot!.querySelector("#sidebar-content") as HTMLDivElement
259
- const tabButtons = [...this.shadowRoot!.querySelectorAll(".tab")] as HTMLButtonElement[]
260
- const tabContents = [...sidebarContent.querySelectorAll("div")] as HTMLDivElement[]
252
+ const runButton = this.shadowRoot!.querySelector("#run") as HTMLButtonElement;
253
+ const stopButton = this.shadowRoot!.querySelector("#stop") as HTMLButtonElement;
254
+ const faustUIRoot = this.shadowRoot!.querySelector("#faust-ui") as HTMLDivElement;
255
+ const faustDiagram = this.shadowRoot!.querySelector("#faust-diagram") as HTMLDivElement;
256
+ const sidebar = this.shadowRoot!.querySelector("#sidebar") as HTMLDivElement;
257
+ const sidebarContent = this.shadowRoot!.querySelector("#sidebar-content") as HTMLDivElement;
258
+ const tabButtons = [...this.shadowRoot!.querySelectorAll(".tab")] as HTMLButtonElement[];
259
+ const tabContents = [...sidebarContent.querySelectorAll("div")] as HTMLDivElement[];
261
260
 
262
261
  // Initialize split.js for resizable editor and sidebar
263
262
  const split = Split([editorEl, sidebar], {
@@ -268,44 +267,44 @@ export default class FaustEditor extends HTMLElement {
268
267
  onDragEnd: () => { scope?.onResize(); spectrum?.onResize() },
269
268
  })
270
269
 
271
- faustPromise.then(() => runButton.disabled = false)
270
+ faustPromise.then(() => runButton.disabled = false);
272
271
 
273
272
  // Default sizes for sidebar
274
- const defaultSizes = [70, 30]
275
- let sidebarOpen = false
273
+ const defaultSizes = [70, 30];
274
+ let sidebarOpen = false;
276
275
 
277
276
  // Function to open the sidebar with predefined sizes
278
277
  const openSidebar = () => {
279
278
  if (!sidebarOpen) {
280
- split.setSizes(defaultSizes)
279
+ split.setSizes(defaultSizes);
281
280
  }
282
- sidebarOpen = true
281
+ sidebarOpen = true;
283
282
  }
284
283
 
285
284
  // Variables for audio and visualization nodes
286
- let node: IFaustMonoWebAudioNode | undefined
287
- let input: MediaStreamAudioSourceNode | undefined
288
- let analyser: AnalyserNode | undefined
289
- let scope: Scope | undefined
290
- let spectrum: Scope | undefined
291
- let gmidi = false
292
- let gnvoices = -1
293
- let sourceNode: AudioBufferSourceNode = undefined;
285
+ let node: IFaustMonoWebAudioNode | undefined;
286
+ let input: MediaStreamAudioSourceNode | undefined;
287
+ let analyser: AnalyserNode | undefined;
288
+ let scope: Scope | undefined;
289
+ let spectrum: Scope | undefined;
290
+ let gmidi = false;
291
+ let gnvoices = -1;
292
+ let sourceNode: AudioBufferSourceNode | undefined;
294
293
 
295
294
  // Event handler for the run button
296
295
  runButton.onclick = async () => {
297
296
  if (audioCtx.state === "suspended") {
298
- await audioCtx.resume()
297
+ await audioCtx.resume();
299
298
  }
300
- await faustPromise
299
+ await faustPromise;
301
300
 
302
301
  // Compile Faust code
303
- const code = editor.state.doc.toString()
304
- let generator = null
302
+ const code = editor.state.doc.toString();
303
+ let generator = null;
305
304
  try {
306
305
  // Compile Faust code to access JSON metadata
307
- await default_generator.compile(compiler, "main", code, "-ftz 2")
308
- const json = default_generator.getMeta()
306
+ await default_generator.compile(compiler, "main", code, "-ftz 2");
307
+ const json = default_generator.getMeta();
309
308
  let { midi, nvoices } = extractMidiAndNvoices(json);
310
309
  gmidi = midi;
311
310
  gnvoices = nvoices;
@@ -315,34 +314,34 @@ export default class FaustEditor extends HTMLElement {
315
314
  await generator.compile(compiler, "main", code, "-ftz 2");
316
315
 
317
316
  } catch (e: any) {
318
- setError(editor, e)
317
+ setError(editor, e);
319
318
  return
320
319
  }
321
320
 
322
321
  // Clear any old errors
323
- clearError(editor)
322
+ clearError(editor);
324
323
 
325
324
  // Create an audio node from compiled Faust
326
- if (node !== undefined) node.disconnect()
325
+ if (node !== undefined) node.disconnect();
327
326
  if (gnvoices > 0) {
328
- node = (await (generator as FaustPolyDspGenerator).createNode(audioCtx, gnvoices))!
327
+ node = (await (generator as FaustPolyDspGenerator).createNode(audioCtx, gnvoices))!;
329
328
  } else {
330
- node = (await (generator as FaustMonoDspGenerator).createNode(audioCtx))!
329
+ node = (await (generator as FaustMonoDspGenerator).createNode(audioCtx))!;
331
330
  }
332
331
 
333
332
  // Set up audio input if necessary
334
333
  if (node.numberOfInputs > 0) {
335
- audioInputSelector.disabled = false
336
- updateInputDevices(await getInputDevices())
337
- await connectInput()
334
+ audioInputSelector.disabled = false;
335
+ updateInputDevices(await getInputDevices());
336
+ await connectInput();
338
337
  } else {
339
- audioInputSelector.disabled = true
340
- audioInputSelector.innerHTML = "<option>Audio input</option>"
338
+ audioInputSelector.disabled = true;
339
+ audioInputSelector.innerHTML = "<option>Audio input</option>";
341
340
  }
342
- node.connect(audioCtx.destination)
343
- stopButton.disabled = false
341
+ node.connect(audioCtx.destination);
342
+ stopButton.disabled = false;
344
343
  for (const tabButton of tabButtons) {
345
- tabButton.disabled = false
344
+ tabButton.disabled = false;
346
345
  }
347
346
 
348
347
  // Start sensors if available
@@ -359,50 +358,50 @@ export default class FaustEditor extends HTMLElement {
359
358
  });
360
359
  }
361
360
 
362
- openSidebar()
361
+ openSidebar();
363
362
 
364
363
  // Clear old tab contents
365
364
  for (const tab of tabContents) {
366
- while (tab.lastChild) tab.lastChild.remove()
365
+ while (tab.lastChild) tab.lastChild.remove();
367
366
  }
368
367
  // Create scope & spectrum plots
369
368
  analyser = new AnalyserNode(audioCtx, {
370
369
  fftSize: Math.pow(2, 11), minDecibels: -96, maxDecibels: 0, smoothingTimeConstant: 0.85
371
- })
372
- node.connect(analyser)
373
- scope = new Scope(tabContents[2])
374
- spectrum = new Scope(tabContents[3])
370
+ });
371
+ node.connect(analyser);
372
+ scope = new Scope(tabContents[2]);
373
+ spectrum = new Scope(tabContents[3]);
375
374
 
376
375
  // If there are UI elements, open Faust UI (controls tab); otherwise open spectrum analyzer.
377
- const ui = node.getUI()
378
- openTab(ui.length > 1 || ui[0].items.length > 0 ? 0 : 3)
376
+ const ui = node.getUI();
377
+ openTab(ui.length > 1 || ui[0].items.length > 0 ? 0 : 3);
379
378
 
380
379
  // Create controls via Faust UI
381
- const faustUI = new FaustUI({ ui, root: faustUIRoot })
382
- faustUI.paramChangeByUI = (path, value) => node?.setParamValue(path, value)
383
- node.setOutputParamHandler((path, value) => faustUI.paramChangeByDSP(path, value))
380
+ const faustUI = new FaustUI({ ui, root: faustUIRoot });
381
+ faustUI.paramChangeByUI = (path, value) => node?.setParamValue(path, value);
382
+ node.setOutputParamHandler((path, value) => faustUI.paramChangeByDSP(path, value));
384
383
 
385
384
  // Set editor size to fit UI size
386
385
  editorEl.style.height = `${Math.max(125, faustUI.minHeight)}px`;
387
- faustUIRoot.style.width = faustUI.minWidth * 1.25 + "px"
388
- faustUIRoot.style.height = faustUI.minHeight * 1.25 + "px"
386
+ faustUIRoot.style.width = faustUI.minWidth * 1.25 + "px";
387
+ faustUIRoot.style.height = faustUI.minHeight * 1.25 + "px";
389
388
  }
390
389
 
391
390
  // Function to set SVG in the block diagram tab
392
391
  const setSVG = (svgString: string) => {
393
- faustDiagram.innerHTML = svgString
392
+ faustDiagram.innerHTML = svgString;
394
393
 
395
394
  for (const a of faustDiagram.querySelectorAll("a")) {
396
395
  a.onclick = e => {
397
- e.preventDefault()
398
- const filename = (a.href as any as SVGAnimatedString).baseVal
399
- const svgString = compiler.fs().readFile("main-svg/" + filename, { encoding: "utf8" }) as string
400
- setSVG(svgString)
396
+ e.preventDefault();
397
+ const filename = (a.href as any as SVGAnimatedString).baseVal;
398
+ const svgString = compiler.fs().readFile("main-svg/" + filename, { encoding: "utf8" }) as string;
399
+ setSVG(svgString);
401
400
  }
402
401
  }
403
402
  }
404
403
 
405
- let animPlot: number | undefined
404
+ let animPlot: number | undefined;
406
405
 
407
406
  // Function to render the scope
408
407
  const drawScope = () => {
@@ -411,24 +410,24 @@ export default class FaustEditor extends HTMLElement {
411
410
  style: "rgb(212, 100, 100)",
412
411
  edgeThreshold: 0.09,
413
412
  }])
414
- animPlot = requestAnimationFrame(drawScope)
413
+ animPlot = requestAnimationFrame(drawScope);
415
414
  }
416
415
 
417
416
  // Function to render the spectrum
418
417
  const drawSpectrum = () => {
419
- spectrum!.renderSpectrum(analyser!)
420
- animPlot = requestAnimationFrame(drawSpectrum)
418
+ spectrum!.renderSpectrum(analyser!);
419
+ animPlot = requestAnimationFrame(drawSpectrum);
421
420
  }
422
421
 
423
422
  // Function to switch between tabs
424
423
  const openTab = (i: number) => {
425
424
  for (const [j, tab] of tabButtons.entries()) {
426
425
  if (i === j) {
427
- tab.classList.add("active")
428
- tabContents[j].classList.add("active")
426
+ tab.classList.add("active");
427
+ tabContents[j].classList.add("active");
429
428
  } else {
430
- tab.classList.remove("active")
431
- tabContents[j].classList.remove("active")
429
+ tab.classList.remove("active");
430
+ tabContents[j].classList.remove("active");
432
431
  }
433
432
  }
434
433
  // Check if the clicked tab is the "Block Diagram" tab (index 1)
@@ -445,22 +444,22 @@ export default class FaustEditor extends HTMLElement {
445
444
  }, 0);
446
445
  }
447
446
  } else if (i === 2) {
448
- scope!.onResize()
449
- if (animPlot !== undefined) cancelAnimationFrame(animPlot)
450
- animPlot = requestAnimationFrame(drawScope)
447
+ scope!.onResize();
448
+ if (animPlot !== undefined) cancelAnimationFrame(animPlot);
449
+ animPlot = requestAnimationFrame(drawScope);
451
450
  } else if (i === 3) {
452
- spectrum!.onResize()
453
- if (animPlot !== undefined) cancelAnimationFrame(animPlot)
454
- animPlot = requestAnimationFrame(drawSpectrum)
451
+ spectrum!.onResize();
452
+ if (animPlot !== undefined) cancelAnimationFrame(animPlot);
453
+ animPlot = requestAnimationFrame(drawSpectrum);
455
454
  } else if (animPlot !== undefined) {
456
- cancelAnimationFrame(animPlot)
457
- animPlot = undefined
455
+ cancelAnimationFrame(animPlot);
456
+ animPlot = undefined;
458
457
  }
459
458
  }
460
459
 
461
460
  // Attach event listeners to tab buttons
462
461
  for (const [i, tabButton] of tabButtons.entries()) {
463
- tabButton.onclick = () => openTab(i)
462
+ tabButton.onclick = () => openTab(i);
464
463
  }
465
464
 
466
465
  // Event handler for the stop button
@@ -476,28 +475,28 @@ export default class FaustEditor extends HTMLElement {
476
475
  }
477
476
 
478
477
  // Audio input selector element
479
- const audioInputSelector = this.shadowRoot!.querySelector("#audio-input") as HTMLSelectElement
478
+ const audioInputSelector = this.shadowRoot!.querySelector("#audio-input") as HTMLSelectElement;
480
479
 
481
480
  // Update the audio input device list
482
481
  const updateInputDevices = (devices: MediaDeviceInfo[]) => {
483
- if (audioInputSelector.disabled) return
484
- while (audioInputSelector.lastChild) audioInputSelector.lastChild.remove()
482
+ if (audioInputSelector.disabled) return;
483
+ while (audioInputSelector.lastChild) audioInputSelector.lastChild.remove();
485
484
  for (const device of devices) {
486
485
  if (device.kind === "audioinput") {
487
- audioInputSelector.appendChild(new Option(device.label || device.deviceId, device.deviceId))
486
+ audioInputSelector.appendChild(new Option(device.label || device.deviceId, device.deviceId));
488
487
  }
489
488
  }
490
- audioInputSelector.appendChild(new Option("Audio File", "Audio File"))
489
+ audioInputSelector.appendChild(new Option("Audio File", "Audio File"));
491
490
  }
492
- deviceUpdateCallbacks.push(updateInputDevices)
491
+ deviceUpdateCallbacks.push(updateInputDevices);
493
492
 
494
493
  // Connect the selected audio input device
495
494
  const connectInput = async () => {
496
- const deviceId = audioInputSelector.value
497
- const stream = await navigator.mediaDevices.getUserMedia({ audio: { deviceId, echoCancellation: false, noiseSuppression: false, autoGainControl: false } })
495
+ const deviceId = audioInputSelector.value;
496
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: { deviceId, echoCancellation: false, noiseSuppression: false, autoGainControl: false } });
498
497
  if (input) {
499
- input.disconnect()
500
- input = undefined
498
+ input.disconnect();
499
+ input = undefined;
501
500
  }
502
501
  if (node && node.numberOfInputs > 0) {
503
502
  if (deviceId == "Audio File") {
@@ -531,6 +530,6 @@ export default class FaustEditor extends HTMLElement {
531
530
  }
532
531
  }
533
532
 
534
- audioInputSelector.onchange = connectInput
533
+ audioInputSelector.onchange = connectInput;
535
534
  }
536
535
  }
@@ -12,7 +12,6 @@ import { FaustUI } from "@shren/faust-ui";
12
12
  import {
13
13
  faustPromise,
14
14
  audioCtx,
15
- get_mono_generator,
16
15
  get_poly_generator,
17
16
  compiler,
18
17
  getInputDevices,
@@ -117,40 +116,40 @@ template.innerHTML = `
117
116
  // Define the FaustWidget web component
118
117
  export default class FaustWidget extends HTMLElement {
119
118
  constructor() {
120
- super()
119
+ super();
121
120
  }
122
121
 
123
122
  // Called when the component is connected to the DOM
124
123
  connectedCallback() {
125
124
  // Extract the Faust code from the inner HTML
126
- const code = this.innerHTML.replace("<!--", "").replace("-->", "").trim()
127
- this.attachShadow({ mode: "open" }).appendChild(template.content.cloneNode(true))
125
+ const code = this.innerHTML.replace("<!--", "").replace("-->", "").trim();
126
+ this.attachShadow({ mode: "open" }).appendChild(template.content.cloneNode(true));
128
127
 
129
128
  // Query and initialize various elements in the shadow DOM
130
- const powerButton = this.shadowRoot!.querySelector("#power") as HTMLButtonElement
131
- const faustUIRoot = this.shadowRoot!.querySelector("#faust-ui") as HTMLDivElement
132
- const audioInputSelector = this.shadowRoot!.querySelector("#audio-input") as HTMLSelectElement
129
+ const powerButton = this.shadowRoot!.querySelector("#power") as HTMLButtonElement;
130
+ const faustUIRoot = this.shadowRoot!.querySelector("#faust-ui") as HTMLDivElement;
131
+ const audioInputSelector = this.shadowRoot!.querySelector("#audio-input") as HTMLSelectElement;
133
132
 
134
133
  // Enable the power button once Faust is ready
135
- faustPromise.then(() => powerButton.disabled = false)
134
+ faustPromise.then(() => powerButton.disabled = false);
136
135
 
137
136
  // State variables
138
- let on = false
139
- let gmidi = false
140
- let gnvoices = -1
141
- let node: IFaustMonoWebAudioNode | IFaustPolyWebAudioNode
142
- let input: MediaStreamAudioSourceNode | undefined
143
- let faustUI: FaustUI
144
- let generator: FaustMonoDspGenerator | FaustPolyDspGenerator
145
- let sourceNode: AudioBufferSourceNode = undefined;
137
+ let on = false;
138
+ let gmidi = false;
139
+ let gnvoices = -1;
140
+ let node: IFaustMonoWebAudioNode | IFaustPolyWebAudioNode;
141
+ let input: MediaStreamAudioSourceNode | undefined;
142
+ let faustUI: FaustUI;
143
+ let generator: FaustMonoDspGenerator | FaustPolyDspGenerator;
144
+ let sourceNode: AudioBufferSourceNode | undefined;
146
145
 
147
146
  // Function to setup the Faust environment
148
147
  const setup = async () => {
149
- await faustPromise
148
+ await faustPromise;
150
149
 
151
150
  // Compile Faust code to access JSON metadata
152
- await default_generator.compile(compiler, "main", code, "-ftz 2")
153
- const json = default_generator.getMeta()
151
+ await default_generator.compile(compiler, "main", code, "-ftz 2");
152
+ const json = default_generator.getMeta();
154
153
  let { midi, nvoices } = extractMidiAndNvoices(json);
155
154
  gmidi = midi;
156
155
  gnvoices = nvoices;
@@ -171,15 +170,15 @@ export default class FaustWidget extends HTMLElement {
171
170
  // Function to start the Faust node and audio context
172
171
  const start = async () => {
173
172
  if (audioCtx.state === "suspended") {
174
- await audioCtx.resume()
173
+ await audioCtx.resume();
175
174
  }
176
175
 
177
176
  // Create an audio node from compiled Faust if not already created
178
177
  if (node === undefined) {
179
178
  if (gnvoices > 0) {
180
- node = (await (generator as FaustPolyDspGenerator).createNode(audioCtx, gnvoices))!
179
+ node = (await (generator as FaustPolyDspGenerator).createNode(audioCtx, gnvoices))!;
181
180
  } else {
182
- node = (await (generator as FaustMonoDspGenerator).createNode(audioCtx))!
181
+ node = (await (generator as FaustMonoDspGenerator).createNode(audioCtx))!;
183
182
  }
184
183
  }
185
184
 
@@ -198,61 +197,61 @@ export default class FaustWidget extends HTMLElement {
198
197
  }
199
198
 
200
199
  // Set up parameter handling for Faust UI
201
- faustUI.paramChangeByUI = (path, value) => node?.setParamValue(path, value)
202
- node.setOutputParamHandler((path, value) => faustUI.paramChangeByDSP(path, value))
200
+ faustUI.paramChangeByUI = (path, value) => node?.setParamValue(path, value);
201
+ node.setOutputParamHandler((path, value) => faustUI.paramChangeByDSP(path, value));
203
202
 
204
203
  // Enable audio input if necessary
205
204
  if (node.numberOfInputs > 0) {
206
- audioInputSelector.disabled = false
207
- updateInputDevices(await getInputDevices())
208
- await connectInput()
205
+ audioInputSelector.disabled = false;
206
+ updateInputDevices(await getInputDevices());
207
+ await connectInput();
209
208
  } else {
210
- audioInputSelector.disabled = true
211
- audioInputSelector.innerHTML = "<option>Audio input</option>"
209
+ audioInputSelector.disabled = true;
210
+ audioInputSelector.innerHTML = "<option>Audio input</option>";
212
211
  }
213
212
 
214
213
  // Connect Faust node to the audio context destination
215
- node.connect(audioCtx.destination)
216
- powerButton.style.color = "#ffa500"
214
+ node.connect(audioCtx.destination);
215
+ powerButton.style.color = "#ffa500";
217
216
  }
218
217
 
219
218
  // Function to stop the Faust node
220
219
  const stop = () => {
221
- node?.disconnect()
220
+ node?.disconnect();
222
221
  node?.stopSensors();
223
- powerButton.style.color = "#fff"
222
+ powerButton.style.color = "#fff";
224
223
  }
225
224
 
226
225
  // Toggle the Faust node on/off
227
226
  powerButton.onclick = () => {
228
227
  if (on) {
229
- stop()
228
+ stop();
230
229
  } else {
231
- start()
230
+ start();
232
231
  }
233
- on = !on
232
+ on = !on;
234
233
  }
235
234
 
236
235
  // Function to update available audio input devices
237
236
  const updateInputDevices = (devices: MediaDeviceInfo[]) => {
238
- if (audioInputSelector.disabled) return
239
- while (audioInputSelector.lastChild) audioInputSelector.lastChild.remove()
237
+ if (audioInputSelector.disabled) return;
238
+ while (audioInputSelector.lastChild) audioInputSelector.lastChild.remove();
240
239
  for (const device of devices) {
241
240
  if (device.kind === "audioinput") {
242
- audioInputSelector.appendChild(new Option(device.label || device.deviceId, device.deviceId))
241
+ audioInputSelector.appendChild(new Option(device.label || device.deviceId, device.deviceId));
243
242
  }
244
243
  }
245
- audioInputSelector.appendChild(new Option("Audio File", "Audio File"))
244
+ audioInputSelector.appendChild(new Option("Audio File", "Audio File"));
246
245
  }
247
- deviceUpdateCallbacks.push(updateInputDevices)
246
+ deviceUpdateCallbacks.push(updateInputDevices);
248
247
 
249
248
  // Function to connect selected audio input device
250
249
  const connectInput = async () => {
251
- const deviceId = audioInputSelector.value
252
- const stream = await navigator.mediaDevices.getUserMedia({ audio: { deviceId, echoCancellation: false, noiseSuppression: false, autoGainControl: false } })
250
+ const deviceId = audioInputSelector.value;
251
+ const stream = await navigator.mediaDevices.getUserMedia({ audio: { deviceId, echoCancellation: false, noiseSuppression: false, autoGainControl: false } });
253
252
  if (input) {
254
- input.disconnect()
255
- input = undefined
253
+ input.disconnect();
254
+ input = undefined;
256
255
  }
257
256
  if (node && node.numberOfInputs > 0) {
258
257
  if (deviceId == "Audio File") {
@@ -287,7 +286,7 @@ export default class FaustWidget extends HTMLElement {
287
286
  }
288
287
 
289
288
  // Set input change handler
290
- audioInputSelector.onchange = connectInput
289
+ audioInputSelector.onchange = connectInput;
291
290
 
292
291
  // Initial setup
293
292
  setTimeout(() => {