@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 +2 -2
- package/dist/faust-web-component.js +156 -55
- package/index.html +1 -0
- package/package.json +2 -2
- package/src/common.ts +102 -45
- package/src/faust-editor.ts +95 -96
- package/src/faust-widget.ts +45 -46
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.
|
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.
|
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
|
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
|
-
|
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
|
-
|
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 [
|
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
|
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
|
13683
|
-
|
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(/\$([
|
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
|
33919
|
-
space = { left: 0, top: 0, right:
|
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
|
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
|
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
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
|
+
"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.
|
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
|
-
|
2
|
-
import
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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
|
-
|
13
|
-
export let
|
14
|
-
export
|
15
|
-
export const
|
16
|
-
export const
|
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
|
-
|
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
|
-
|
27
|
-
export const
|
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: (
|
31
|
-
let devices: MediaDeviceInfo[] = []
|
32
|
-
|
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
|
-
|
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
|
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
|
-
|
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)
|
87
|
-
|
88
|
-
|
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
|
+
}
|
package/src/faust-editor.ts
CHANGED
@@ -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
|
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
|
}
|
package/src/faust-widget.ts
CHANGED
@@ -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
|
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(() => {
|