diff --git a/packages/desktopbridge/index.mjs b/packages/desktopbridge/index.mjs index 591bbe34f..ba3051ece 100644 --- a/packages/desktopbridge/index.mjs +++ b/packages/desktopbridge/index.mjs @@ -7,3 +7,4 @@ This program is free software: you can redistribute it and/or modify it under th export * from './midibridge.mjs'; export * from './utils.mjs'; export * from './oscbridge.mjs'; +export * from './webaudiobridge.mjs'; \ No newline at end of file diff --git a/packages/desktopbridge/package.json b/packages/desktopbridge/package.json index d750a82ba..01d712ffd 100644 --- a/packages/desktopbridge/package.json +++ b/packages/desktopbridge/package.json @@ -22,6 +22,7 @@ "url": "https://github.com/tidalcycles/strudel/issues" }, "dependencies": { + "@strudel.cycles/webaudio": "^0.9.0", "@strudel.cycles/core": "workspace:*", "@tauri-apps/api": "^1.4.0" }, diff --git a/packages/desktopbridge/webaudiobridge.mjs b/packages/desktopbridge/webaudiobridge.mjs new file mode 100644 index 000000000..0ef179533 --- /dev/null +++ b/packages/desktopbridge/webaudiobridge.mjs @@ -0,0 +1,109 @@ +import { Invoke } from './utils.mjs'; +import {duration, isNote, midiToFreq, noteToMidi, Pattern} from '@strudel.cycles/core'; +import {getAudioContext} from "@strudel.cycles/webaudio"; + +export const desktopAudio = async (value, deadline, hapDuration) => { + const ac = getAudioContext(); + if (typeof value !== 'object') { + throw new Error( + `expected hap.value to be an object, but got "${value}". Hint: append .note() or .s() to the end`, + 'error', + ); + } + + let t = ac.currentTime + deadline; + + let { + note = "C3", + s = 'triangle', + bank = '', + source, + gain = 0.8, + // low pass + cutoff = 8000, + resonance = 1, + // high pass + hcutoff = 0, + hresonance = 1, + // band pass + bandf = 0, + bandq = 1, + // + coarse, + crush, + shape, + pan, + vowel, + delay = 0, + delayfeedback = 0.5, + delaytime = 0.25, + orbit = 1, + room, + size = 2, + velocity = 1, + analyze, // analyser wet + fft = 8, // fftSize 0 - 10 + speed = 1, // sample playback speed + begin = 0, + end = 1, + loop = 0, + loopBegin = 0, + loopEnd = 1, + } = value; + + console.log(value); + value.duration = hapDuration; + if (isNote(note)) { + note = noteToMidi(note); + } + + if (delay !== 0) { + delay = Math.abs(delay); + delayfeedback = Math.abs(delayfeedback); + delaytime = Math.abs(delaytime); + } + + const loop_packaged = [loop, loopBegin, loopEnd]; + const delay_packaged = [delay, delayfeedback, delaytime]; + + const offset = (t - getAudioContext().currentTime) * 1000; + const roundedOffset = Math.round(offset); + const messagesfromjs = []; + + messagesfromjs.push({ + note: midiToFreq(note), + offset: roundedOffset, + waveform: s, + bank: bank, + cutoff: cutoff, + resonance: resonance, + hcutoff: hcutoff, + hresonance: hresonance, + bandf: bandf, + bandq: bandq, + duration: hapDuration, + velocity: velocity, + delay: delay_packaged, + speed: speed, + begin: begin, + end: end, + loop_packaged: loop_packaged, + + }); + + if (messagesfromjs.length) { + setTimeout(() => { + Invoke('sendwebaudio', { messagesfromjs }); + }); + } +} +const hap2value = (hap) => { + hap.ensureObjectValue(); + return { ...hap.value, velocity: hap.context.velocity }; +}; +export const webaudioDesktopOutputTrigger = (t, hap, ct, cps) => desktopAudio(hap2value(hap), t - ct, hap.duration / cps, cps); +export const webaudioDesktopOutput = (hap, deadline, hapDuration) => desktopAudio(hap2value(hap), deadline, hapDuration); + +Pattern.prototype.webaudio = function () { + return this.onTrigger(webaudioDesktopOutputTrigger); +}; \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8b48a93fe..437058f98 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1,5 +1,9 @@ lockfileVersion: '6.0' +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + importers: .: @@ -181,6 +185,9 @@ importers: '@strudel.cycles/core': specifier: workspace:* version: link:../core + '@strudel.cycles/webaudio': + specifier: ^0.9.0 + version: link:../webaudio '@tauri-apps/api': specifier: ^1.4.0 version: 1.4.0 @@ -1426,6 +1433,7 @@ packages: /@babel/plugin-proposal-async-generator-functions@7.20.7(@babel/core@7.21.5): resolution: {integrity: sha512-xMbiLsn/8RK7Wq7VeVytytS2L6qE69bXPB10YCmMdDZbKF4okCqY74pI/jJQ/8U0b/F6NrT2+14b8/P9/3AMGA==} engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-async-generator-functions instead. peerDependencies: '@babel/core': ^7.0.0-0 dependencies: @@ -1441,6 +1449,7 @@ packages: /@babel/plugin-proposal-class-properties@7.18.6(@babel/core@7.21.5): resolution: {integrity: sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==} engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead. peerDependencies: '@babel/core': ^7.0.0-0 dependencies: @@ -1454,6 +1463,7 @@ packages: /@babel/plugin-proposal-class-static-block@7.20.7(@babel/core@7.21.5): resolution: {integrity: sha512-AveGOoi9DAjUYYuUAG//Ig69GlazLnoyzMw68VCDux+c1tsnnH/OkYcpz/5xzMkEFC6UxjR5Gw1c+iY2wOGVeQ==} engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-static-block instead. peerDependencies: '@babel/core': ^7.12.0 dependencies: @@ -1468,6 +1478,7 @@ packages: /@babel/plugin-proposal-dynamic-import@7.18.6(@babel/core@7.21.5): resolution: {integrity: sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==} engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-dynamic-import instead. peerDependencies: '@babel/core': ^7.0.0-0 dependencies: @@ -1479,6 +1490,7 @@ packages: /@babel/plugin-proposal-export-namespace-from@7.18.9(@babel/core@7.21.5): resolution: {integrity: sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==} engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-export-namespace-from instead. peerDependencies: '@babel/core': ^7.0.0-0 dependencies: @@ -1490,6 +1502,7 @@ packages: /@babel/plugin-proposal-json-strings@7.18.6(@babel/core@7.21.5): resolution: {integrity: sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==} engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-json-strings instead. peerDependencies: '@babel/core': ^7.0.0-0 dependencies: @@ -1501,6 +1514,7 @@ packages: /@babel/plugin-proposal-logical-assignment-operators@7.20.7(@babel/core@7.21.5): resolution: {integrity: sha512-y7C7cZgpMIjWlKE5T7eJwp+tnRYM89HmRvWM5EQuB5BoHEONjmQ8lSNmBUwOyy/GFRsohJED51YBF79hE1djug==} engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-logical-assignment-operators instead. peerDependencies: '@babel/core': ^7.0.0-0 dependencies: @@ -1512,6 +1526,7 @@ packages: /@babel/plugin-proposal-nullish-coalescing-operator@7.18.6(@babel/core@7.21.5): resolution: {integrity: sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==} engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-nullish-coalescing-operator instead. peerDependencies: '@babel/core': ^7.0.0-0 dependencies: @@ -1523,6 +1538,7 @@ packages: /@babel/plugin-proposal-numeric-separator@7.18.6(@babel/core@7.21.5): resolution: {integrity: sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==} engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-numeric-separator instead. peerDependencies: '@babel/core': ^7.0.0-0 dependencies: @@ -1534,6 +1550,7 @@ packages: /@babel/plugin-proposal-object-rest-spread@7.20.7(@babel/core@7.21.5): resolution: {integrity: sha512-d2S98yCiLxDVmBmE8UjGcfPvNEUbA1U5q5WxaWFUGRzJSVAZqm5W6MbPct0jxnegUZ0niLeNX+IOzEs7wYg9Dg==} engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-object-rest-spread instead. peerDependencies: '@babel/core': ^7.0.0-0 dependencies: @@ -1548,6 +1565,7 @@ packages: /@babel/plugin-proposal-optional-catch-binding@7.18.6(@babel/core@7.21.5): resolution: {integrity: sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==} engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-catch-binding instead. peerDependencies: '@babel/core': ^7.0.0-0 dependencies: @@ -1559,6 +1577,7 @@ packages: /@babel/plugin-proposal-optional-chaining@7.20.7(@babel/core@7.21.5): resolution: {integrity: sha512-T+A7b1kfjtRM51ssoOfS1+wbyCVqorfyZhT99TvxxLMirPShD8CzKMRepMlCBGM5RpHMbn8s+5MMHnPstJH6mQ==} engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-chaining instead. peerDependencies: '@babel/core': ^7.0.0-0 dependencies: @@ -1571,6 +1590,7 @@ packages: /@babel/plugin-proposal-private-methods@7.18.6(@babel/core@7.21.5): resolution: {integrity: sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==} engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-methods instead. peerDependencies: '@babel/core': ^7.0.0-0 dependencies: @@ -1584,6 +1604,7 @@ packages: /@babel/plugin-proposal-private-property-in-object@7.20.5(@babel/core@7.21.5): resolution: {integrity: sha512-Vq7b9dUA12ByzB4EjQTPo25sFhY+08pQDBSZRtUAkj7lb7jahaHR5igera16QZ+3my1nYR4dKsNdYj5IjPHilQ==} engines: {node: '>=6.9.0'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-property-in-object instead. peerDependencies: '@babel/core': ^7.0.0-0 dependencies: @@ -1599,6 +1620,7 @@ packages: /@babel/plugin-proposal-unicode-property-regex@7.18.6(@babel/core@7.21.5): resolution: {integrity: sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==} engines: {node: '>=4'} + deprecated: This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-unicode-property-regex instead. peerDependencies: '@babel/core': ^7.0.0-0 dependencies: diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 38fce20bf..c6688467f 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -104,8 +104,21 @@ dependencies = [ "tauri", "tauri-build", "tokio", + "web-audio-api", ] +[[package]] +name = "arc-swap" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + [[package]] name = "atk" version = "0.15.1" @@ -163,6 +176,26 @@ version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" +[[package]] +name = "bindgen" +version = "0.64.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4243e6031260db77ede97ad86c27e501d646a27ab57b59a574f725d98ab1fb4" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 1.0.109", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -221,6 +254,12 @@ version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" +[[package]] +name = "byte-slice-cast" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" + [[package]] name = "bytemuck" version = "1.13.1" @@ -239,6 +278,12 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +[[package]] +name = "cache-padded" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "981520c98f422fcc584dc1a95c334e6953900b9106bc47a9839b81790009eb21" + [[package]] name = "cairo-rs" version = "0.15.12" @@ -278,6 +323,9 @@ name = "cc" version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +dependencies = [ + "jobserver", +] [[package]] name = "cesu8" @@ -285,6 +333,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfb" version = "0.7.3" @@ -334,6 +391,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "clang-sys" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "cocoa" version = "0.24.1" @@ -393,10 +461,16 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" dependencies = [ - "core-foundation-sys", + "core-foundation-sys 0.8.4", "libc", ] +[[package]] +name = "core-foundation-sys" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b" + [[package]] name = "core-foundation-sys" version = "0.8.4" @@ -427,6 +501,26 @@ dependencies = [ "libc", ] +[[package]] +name = "coreaudio-rs" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb17e2d1795b1996419648915df94bc7103c28f7b48062d7acf4652fc371b2ff" +dependencies = [ + "bitflags", + "core-foundation-sys 0.6.2", + "coreaudio-sys", +] + +[[package]] +name = "coreaudio-sys" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f034b2258e6c4ade2f73bf87b21047567fb913ee9550837c2316d139b0262b24" +dependencies = [ + "bindgen", +] + [[package]] name = "coremidi" version = "0.6.0" @@ -434,7 +528,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a7847ca018a67204508b77cb9e6de670125075f7464fff5f673023378fa34f5" dependencies = [ "core-foundation", - "core-foundation-sys", + "core-foundation-sys 0.8.4", "coremidi-sys", ] @@ -444,7 +538,32 @@ version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79a6deed0c97b2d40abbab77e4c97f81d71e162600423382c277dd640019116c" dependencies = [ - "core-foundation-sys", + "core-foundation-sys 0.8.4", +] + +[[package]] +name = "cpal" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d959d90e938c5493000514b446987c07aed46c668faaa7d34d6c7a67b1a578c" +dependencies = [ + "alsa", + "core-foundation-sys 0.8.4", + "coreaudio-rs", + "dasp_sample", + "jni 0.19.0", + "js-sys", + "libc", + "mach2", + "ndk 0.7.0", + "ndk-context", + "oboe", + "once_cell", + "parking_lot", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows 0.46.0", ] [[package]] @@ -465,6 +584,47 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "creek" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b01a32e142cd462c493f706a208780b3f90bed74b55c4ee9f6a67d3f4fe292bd" +dependencies = [ + "creek-core", + "creek-decode-symphonia", + "creek-encode-wav", +] + +[[package]] +name = "creek-core" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "324fdebf340780c68b62d526f524a4cad41495db651d9c1333d2930ba189d6e4" +dependencies = [ + "rtrb", +] + +[[package]] +name = "creek-decode-symphonia" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8371cf1deee7e3df88125c6c54e95e7966ba62e508b041db238f48a383119706" +dependencies = [ + "creek-core", + "log", + "symphonia", +] + +[[package]] +name = "creek-encode-wav" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063e2422d455bb48879d04b0787b1e697e24c6fd3f3a3fa54554636e1595f316" +dependencies = [ + "byte-slice-cast", + "creek-core", +] + [[package]] name = "crossbeam-channel" version = "0.5.8" @@ -566,6 +726,12 @@ dependencies = [ "syn 2.0.18", ] +[[package]] +name = "dasp_sample" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f" + [[package]] name = "derive_more" version = "0.99.17" @@ -736,6 +902,12 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "float_eq" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28a80e3145d8ad11ba0995949bbcf48b9df2be62772b3d351ef017dff6ecb853" + [[package]] name = "fnv" version = "1.0.7" @@ -1187,6 +1359,23 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hound" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d13cdbd5dbb29f9c88095bbdc2590c9cba0d0a1269b983fef6b2cdd7e9f4db1" + +[[package]] +name = "hrtf" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73088c4a5d7c9d21ee942a54611696f247c4bafc85e9261bf8c409b6857e75be" +dependencies = [ + "byteorder", + "rubato 0.10.1", + "rustfft", +] + [[package]] name = "html5ever" version = "0.25.2" @@ -1225,7 +1414,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" dependencies = [ "android_system_properties", - "core-foundation-sys", + "core-foundation-sys 0.8.4", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", @@ -1372,6 +1561,20 @@ dependencies = [ "system-deps 5.0.0", ] +[[package]] +name = "jni" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec" +dependencies = [ + "cesu8", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", +] + [[package]] name = "jni" version = "0.20.0" @@ -1392,6 +1595,15 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" +[[package]] +name = "jobserver" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" +dependencies = [ + "libc", +] + [[package]] name = "js-sys" version = "0.3.64" @@ -1431,12 +1643,28 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "libc" version = "0.2.146" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f92be4933c13fd498862a9e02a3055f8a8d9c039ce33db97306fd5a6caa7f29b" +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + [[package]] name = "line-wrap" version = "0.1.1" @@ -1452,6 +1680,12 @@ version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" +[[package]] +name = "llq" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22edcb39a6fc7e511be7ba578a719af09125391ecde44e0bd61ad3c8e003a4a3" + [[package]] name = "lock_api" version = "0.4.10" @@ -1489,6 +1723,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" +[[package]] +name = "mach2" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d0d1830bcd151a6fc4aea1369af235b36c1528fe976b8ff678683c9995eade8" +dependencies = [ + "libc", +] + [[package]] name = "malloc_buf" version = "0.0.6" @@ -1593,11 +1836,25 @@ checksum = "2032c77e030ddee34a6787a64166008da93f6a352b629261d0fee232b8742dd4" dependencies = [ "bitflags", "jni-sys", - "ndk-sys", + "ndk-sys 0.3.0", "num_enum", "thiserror", ] +[[package]] +name = "ndk" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "451422b7e4718271c8b5b3aadf5adedba43dc76312454b387e98fae0fc951aa0" +dependencies = [ + "bitflags", + "jni-sys", + "ndk-sys 0.4.1+23.1.7779620", + "num_enum", + "raw-window-handle", + "thiserror", +] + [[package]] name = "ndk-context" version = "0.1.1" @@ -1613,6 +1870,15 @@ dependencies = [ "jni-sys", ] +[[package]] +name = "ndk-sys" +version = "0.4.1+23.1.7779620" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cf2aae958bd232cac5069850591667ad422d263686d75b52a065f9badeee5a3" +dependencies = [ + "jni-sys", +] + [[package]] name = "new_debug_unreachable" version = "1.0.4" @@ -1656,6 +1922,26 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-complex" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-derive" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "num-integer" version = "0.1.45" @@ -1754,6 +2040,29 @@ dependencies = [ "memchr", ] +[[package]] +name = "oboe" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8868cc237ee02e2d9618539a23a8d228b9bb3fc2e7a5b11eed3831de77c395d0" +dependencies = [ + "jni 0.20.0", + "ndk 0.7.0", + "ndk-context", + "num-derive", + "num-traits", + "oboe-sys", +] + +[[package]] +name = "oboe-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f44155e7fb718d3cfddcf70690b2b51ac4412f347cd9e4fbe511abe9cd7b5f2" +dependencies = [ + "cc", +] + [[package]] name = "once_cell" version = "1.18.0" @@ -1811,9 +2120,15 @@ dependencies = [ "libc", "redox_syscall 0.3.5", "smallvec", - "windows-targets", + "windows-targets 0.48.0", ] +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + [[package]] name = "percent-encoding" version = "2.3.0" @@ -1930,6 +2245,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "piston-float" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad78bf43dcf80e8f950c92b84f938a0fc7590b7f6866fbcbeca781609c115590" + [[package]] name = "pkg-config" version = "0.3.27" @@ -1975,6 +2296,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" +[[package]] +name = "primal-check" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9df7f93fd637f083201473dab4fee2db4c429d32e55e3299980ab3957ab916a0" +dependencies = [ + "num-integer", +] + [[package]] name = "proc-macro-crate" version = "1.3.1" @@ -2129,6 +2459,24 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" +[[package]] +name = "realfft" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7695c87f31dc3644760f23fb59a3fed47659703abf76cf2d111f03b9e712342" +dependencies = [ + "rustfft", +] + +[[package]] +name = "realfft" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953d9f7e5cdd80963547b456251296efc2626ed4e3cbf36c869d9564e0220571" +dependencies = [ + "rustfft", +] + [[package]] name = "redox_syscall" version = "0.2.16" @@ -2200,12 +2548,52 @@ dependencies = [ "nom", ] +[[package]] +name = "rtrb" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99e704dd104faf2326a320140f70f0b736d607c1caa1b1748a6c568a79819109" +dependencies = [ + "cache-padded", +] + +[[package]] +name = "rubato" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "610bbefcfef7f43cbe275995b6077b74f34480efce293c30327bf44fdad871ed" +dependencies = [ + "log", + "num-complex", + "num-integer", + "num-traits", + "realfft 2.0.1", +] + +[[package]] +name = "rubato" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6dd52e80cfc21894deadf554a5673002938ae4625f7a283e536f9cf7c17b0d5" +dependencies = [ + "num-complex", + "num-integer", + "num-traits", + "realfft 3.3.0", +] + [[package]] name = "rustc-demangle" version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc_version" version = "0.4.0" @@ -2215,6 +2603,21 @@ dependencies = [ "semver", ] +[[package]] +name = "rustfft" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17d4f6cbdb180c9f4b2a26bbf01c4e647f1e1dea22fe8eb9db54198b32f9434" +dependencies = [ + "num-complex", + "num-integer", + "num-traits", + "primal-check", + "strength_reduce", + "transpose", + "version_check", +] + [[package]] name = "rustix" version = "0.37.20" @@ -2428,6 +2831,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380" + [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -2460,9 +2869,9 @@ dependencies = [ [[package]] name = "smallvec" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +checksum = "62bb4feee49fdd9f707ef802e22365a35de4b7b299de4763d44bfea899442ff9" [[package]] name = "socket2" @@ -2517,6 +2926,12 @@ dependencies = [ "loom", ] +[[package]] +name = "strength_reduce" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82" + [[package]] name = "string_cache" version = "0.8.7" @@ -2549,6 +2964,189 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "symphonia" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62e48dba70095f265fdb269b99619b95d04c89e619538138383e63310b14d941" +dependencies = [ + "lazy_static", + "symphonia-bundle-flac", + "symphonia-bundle-mp3", + "symphonia-codec-aac", + "symphonia-codec-adpcm", + "symphonia-codec-alac", + "symphonia-codec-pcm", + "symphonia-codec-vorbis", + "symphonia-core", + "symphonia-format-isomp4", + "symphonia-format-mkv", + "symphonia-format-ogg", + "symphonia-format-wav", + "symphonia-metadata", +] + +[[package]] +name = "symphonia-bundle-flac" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f23b0482a7cb18fcdf9981ab0b78df800ef0080187d294650023c462439058d" +dependencies = [ + "log", + "symphonia-core", + "symphonia-metadata", + "symphonia-utils-xiph", +] + +[[package]] +name = "symphonia-bundle-mp3" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f31d7fece546f1e6973011a9eceae948133bbd18fd3d52f6073b1e38ae6368a" +dependencies = [ + "bitflags", + "lazy_static", + "log", + "symphonia-core", + "symphonia-metadata", +] + +[[package]] +name = "symphonia-codec-aac" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68bdd75b25ce4b84b12a4bd20bfea2460c2dbd7fc1d227ef5533504d3168109d" +dependencies = [ + "lazy_static", + "log", + "symphonia-core", +] + +[[package]] +name = "symphonia-codec-adpcm" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "870e7dc1865d818c7b6318879d060553a73a3b2a3b8443dff90910f10ac41150" +dependencies = [ + "log", + "symphonia-core", +] + +[[package]] +name = "symphonia-codec-alac" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a27e8763d1c9eff666faf903e73a99d4de2f7a93fca4e3c214c1d68432903b9" +dependencies = [ + "log", + "symphonia-core", +] + +[[package]] +name = "symphonia-codec-pcm" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47f1fbd220a06a641c8ce2ddad10f5ef6ee5cc0c54d9044d25d43b0d3119deaa" +dependencies = [ + "log", + "symphonia-core", +] + +[[package]] +name = "symphonia-codec-vorbis" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3953397e3506aa01350c4205817e4f95b58d476877a42f0458d07b665749e203" +dependencies = [ + "log", + "symphonia-core", + "symphonia-utils-xiph", +] + +[[package]] +name = "symphonia-core" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c73eb88fee79705268cc7b742c7bc93a7b76e092ab751d0833866970754142" +dependencies = [ + "arrayvec", + "bitflags", + "bytemuck", + "lazy_static", + "log", +] + +[[package]] +name = "symphonia-format-isomp4" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdf14bae5cf352032416bc64151e5d6242d29d33cbf3238513b44d4427a1efb" +dependencies = [ + "encoding_rs", + "log", + "symphonia-core", + "symphonia-metadata", + "symphonia-utils-xiph", +] + +[[package]] +name = "symphonia-format-mkv" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5c61dfc851ad25d4043d8c231d8617e8f7cd02a6cc0edad21ade21848d58895" +dependencies = [ + "lazy_static", + "log", + "symphonia-core", + "symphonia-metadata", + "symphonia-utils-xiph", +] + +[[package]] +name = "symphonia-format-ogg" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bf1a00ccd11452d44048a0368828040f778ae650418dbd9d8765b7ee2574c8d" +dependencies = [ + "log", + "symphonia-core", + "symphonia-metadata", + "symphonia-utils-xiph", +] + +[[package]] +name = "symphonia-format-wav" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da76614728fa27c003bdcdfbac51396bd8fcbf94c95fe8e62f1d2bac58ef03a4" +dependencies = [ + "log", + "symphonia-core", + "symphonia-metadata", +] + +[[package]] +name = "symphonia-metadata" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89c3e1937e31d0e068bbe829f66b2f2bfaa28d056365279e0ef897172c3320c0" +dependencies = [ + "encoding_rs", + "lazy_static", + "log", + "symphonia-core", +] + +[[package]] +name = "symphonia-utils-xiph" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a450ca645b80d69aff8b35576cbfdc7f20940b29998202aab910045714c951f8" +dependencies = [ + "symphonia-core", + "symphonia-metadata", +] + [[package]] name = "syn" version = "1.0.109" @@ -2622,13 +3220,13 @@ dependencies = [ "gtk", "image", "instant", - "jni", + "jni 0.20.0", "lazy_static", "libc", "log", - "ndk", + "ndk 0.6.0", "ndk-context", - "ndk-sys", + "ndk-sys 0.3.0", "objc", "once_cell", "parking_lot", @@ -3091,6 +3689,16 @@ dependencies = [ "tracing-log", ] +[[package]] +name = "transpose" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6522d49d03727ffb138ae4cbc1283d3774f0d10aa7f9bf52e6784c45daf9b23" +dependencies = [ + "num-integer", + "strength_reduce", +] + [[package]] name = "treediff" version = "4.0.2" @@ -3166,6 +3774,15 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "vecmath" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "956ae1e0d85bca567dee1dcf87fb1ca2e792792f66f87dced8381f99cd91156a" +dependencies = [ + "piston-float", +] + [[package]] name = "version-compare" version = "0.0.11" @@ -3251,6 +3868,18 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.87" @@ -3280,6 +3909,32 @@ version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +[[package]] +name = "web-audio-api" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b600725f86c3caa0f8823ff5e3c42aecbdfcd4a390db2be9278eb06d16a98c1a" +dependencies = [ + "arc-swap", + "arrayvec", + "cpal", + "creek", + "crossbeam-channel", + "dasp_sample", + "float_eq", + "hound", + "hrtf", + "llq", + "log", + "num-complex", + "realfft 3.3.0", + "rubato 0.14.1", + "rustc-hash", + "smallvec", + "symphonia", + "vecmath", +] + [[package]] name = "web-sys" version = "0.3.64" @@ -3435,13 +4090,22 @@ dependencies = [ "windows_x86_64_msvc 0.42.2", ] +[[package]] +name = "windows" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdacb41e6a96a052c6cb63a144f24900236121c6f63f4f8219fef5977ecb0c25" +dependencies = [ + "windows-targets 0.42.2", +] + [[package]] name = "windows" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ - "windows-targets", + "windows-targets 0.48.0", ] [[package]] @@ -3476,7 +4140,22 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.0", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", ] [[package]] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 35aab489e..f251a56e6 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -21,6 +21,7 @@ tauri = { version = "1.4.0", features = ["fs-all"] } midir = "0.9.1" tokio = { version = "1.29.0", features = ["full"] } rosc = "0.10.1" +web-audio-api = "0.33.0" [features] # this feature is used for production builds or when `devPath` points to the filesystem and the built-in dev server is disabled. diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 6f97b8c98..acc39824f 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -4,6 +4,8 @@ mod midibridge; mod oscbridge; mod loggerbridge; +mod webaudiobridge; + use std::sync::Arc; use loggerbridge::Logger; @@ -21,27 +23,33 @@ fn main() { let (async_output_transmitter_midi, async_output_receiver_midi) = mpsc::channel(1); let (async_input_transmitter_osc, async_input_receiver_osc) = mpsc::channel(1); let (async_output_transmitter_osc, async_output_receiver_osc) = mpsc::channel(1); + let (async_input_transmitter_webaudio, async_input_receiver_webaudio) = mpsc::channel(1); + let (async_output_transmitter_webaudio, async_output_receiver_webaudio) = mpsc::channel(1); tauri::Builder - ::default() - .manage(midibridge::AsyncInputTransmit { - inner: Mutex::new(async_input_transmitter_midi), - }) - .manage(oscbridge::AsyncInputTransmit { - inner: Mutex::new(async_input_transmitter_osc), - }) - .invoke_handler(tauri::generate_handler![midibridge::sendmidi, oscbridge::sendosc]) - .setup(|app| { - let window = Arc::new(app.get_window("main").unwrap()); - let logger = Logger { window }; - midibridge::init( - logger.clone(), - async_input_receiver_midi, - async_output_receiver_midi, - async_output_transmitter_midi - ); - oscbridge::init(logger, async_input_receiver_osc, async_output_receiver_osc, async_output_transmitter_osc); - Ok(()) - }) - .run(tauri::generate_context!()) - .expect("error while running tauri application"); + ::default() + .manage(midibridge::AsyncInputTransmit { + inner: Mutex::new(async_input_transmitter_midi), + }) + .manage(oscbridge::AsyncInputTransmit { + inner: Mutex::new(async_input_transmitter_osc), + }) + .manage(webaudiobridge::AsyncInputTransmitWebAudio { + inner: Mutex::new(async_input_transmitter_webaudio), + }) + .invoke_handler(tauri::generate_handler![midibridge::sendmidi, oscbridge::sendosc, webaudiobridge::sendwebaudio]) + .setup(|app| { + let window = Arc::new(app.get_window("main").unwrap()); + let logger = Logger { window }; + midibridge::init( + logger.clone(), + async_input_receiver_midi, + async_output_receiver_midi, + async_output_transmitter_midi + ); + oscbridge::init(logger, async_input_receiver_osc, async_output_receiver_osc, async_output_transmitter_osc); + webaudiobridge::init(async_input_receiver_webaudio, async_output_receiver_webaudio, async_output_transmitter_webaudio); + Ok(()) + }) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); } diff --git a/src-tauri/src/webaudiobridge.rs b/src-tauri/src/webaudiobridge.rs new file mode 100644 index 000000000..95eb993a3 --- /dev/null +++ b/src-tauri/src/webaudiobridge.rs @@ -0,0 +1,264 @@ +use std::fs::File; +use std::sync::Arc; +use std::time::Duration; +use tokio::sync::{ mpsc, Mutex }; +use tokio::time::Instant; +use serde::Deserialize; +use std::thread::sleep; +use web_audio_api::context::{AudioContext, AudioContextLatencyCategory, AudioContextOptions, BaseAudioContext}; +use web_audio_api::node::{AudioNode, AudioScheduledSourceNode, BiquadFilterNode, GainNode, OscillatorType}; +use web_audio_api::node::BiquadFilterType::{Bandpass, Highpass, Lowpass}; + +#[derive(Clone, Copy, Debug)] +pub struct Delay { + pub wet: f64, + pub delay_time: f64, + pub feedback: f64, +} + +#[derive(Debug)] +pub struct WebAudioMessage { + pub note: f32, + pub instant: Instant, + pub offset: f64, + pub waveform: String, + pub bank: String, + pub cutoff: f32, + pub resonance: f32, + pub hcutoff: f32, + pub hresonance: f32, + pub bandf: f32, + pub bandq: f32, + pub duration: f64, + pub velocity: f32, + pub delay: Delay, + pub speed: f32, + pub begin: f64, + pub end: f64, + pub loop_packaged: (u8, f64, f64), +} + +pub struct AsyncInputTransmitWebAudio { + pub inner: Mutex>>, +} + +pub fn init( + async_input_receiver: mpsc::Receiver>, + mut async_output_receiver: mpsc::Receiver>, + async_output_transmitter: mpsc::Sender> +) { + tauri::async_runtime::spawn(async move { async_process_model(async_input_receiver, async_output_transmitter).await }); + let message_queue: Arc>> = Arc::new(Mutex::new(Vec::new())); + /* ........................................................... + Listen For incoming messages and add to queue + ............................................................*/ + let message_queue_clone = Arc::clone(&message_queue); + tauri::async_runtime::spawn(async move { + loop { + if let Some(package) = async_output_receiver.recv().await { + let mut message_queue = message_queue_clone.lock().await; + let messages = package; + for message in messages { + (*message_queue).push(message); + } + } + } + }); + + let message_queue_clone = Arc::clone(&message_queue); + tauri::async_runtime::spawn(async move { + /* ........................................................... + Prepare audio context + ............................................................*/ + let latency_hint = match std::env::var("WEB_AUDIO_LATENCY").as_deref() { + Ok("playback") => AudioContextLatencyCategory::Playback, + _ => AudioContextLatencyCategory::default(), + }; + + let mut audio_context = AudioContext::new(AudioContextOptions { + latency_hint, + ..AudioContextOptions::default() + }); + + /* ........................................................... + Process queued messages + ............................................................*/ + loop { + let mut message_queue = message_queue_clone.lock().await; + + // Iterate over each message, play and remove messages when they are ready + message_queue.retain(|message| { + if message.instant.elapsed().as_millis() < message.offset as u128 { + return true; + }; + + superdough(message.clone(), &mut audio_context); + + return false; + }); + sleep(Duration::from_millis(1)); + } + }); +} + +pub async fn async_process_model( + mut input_reciever: mpsc::Receiver>, + output_transmitter: mpsc::Sender> +) -> Result<(), Box> { + while let Some(input) = input_reciever.recv().await { + let output = input; + output_transmitter.send(output).await?; + } + Ok(()) +} +#[derive(Deserialize)] +pub struct MessageFromJS { + note: f32, + offset: f64, + waveform: String, + bank: String, + cutoff: f32, + resonance: f32, + hcutoff: f32, + hresonance: f32, + bandf: f32, + bandq: f32, + duration: f64, + velocity: f32, + delay: Vec, + speed: f32, + begin: f64, + end: f64, + loop_packaged: (u8, f64, f64), +} +// Called from JS +#[tauri::command] +pub async fn sendwebaudio( + messagesfromjs: Vec, + state: tauri::State<'_, AsyncInputTransmitWebAudio> +) -> Result<(), String> { + let async_proc_input_tx = state.inner.lock().await; + let mut messages_to_process: Vec = Vec::new(); + + for m in messagesfromjs { + let message_to_process = WebAudioMessage { + note: m.note, + instant: Instant::now(), + offset: m.offset, + waveform: m.waveform, + bank: m.bank, + cutoff: m.cutoff, + resonance: m.resonance, + hcutoff: m.hcutoff, + hresonance: m.hresonance, + bandf: m.bandf, + bandq: m.bandq, + duration: m.duration, + velocity: m.velocity, + delay: Delay { + wet: m.delay[0], + delay_time: m.delay[1], + feedback: m.delay[2], + }, + speed: m.speed, + begin: m.begin, + end: m.end, + loop_packaged: m.loop_packaged, + }; + messages_to_process.push(message_to_process); + } + + async_proc_input_tx.send(messages_to_process).await.map_err(|e| e.to_string()) +} + + +fn superdough(message: &WebAudioMessage, context: &mut AudioContext) { + let now = context.current_time(); + let env = context.create_gain(); + env.gain().set_value(0.); + env.connect(&context.destination()); + let (lpf, hpf, bandpass) = create_filters(context, message); + + match message.waveform.as_str() { + "sine" | "square" | "triangle" | "saw" => { + let osc = context.create_oscillator(); + osc.set_type(create_osc_type(&message)); + osc.frequency().set_value(message.note); + + osc.connect(&lpf); + connect_filters_to_envelope(&env, message, &hpf, &bandpass, now); + osc.start_at(now); + osc.stop_at(now + 2.0); + } + _ => { + match File::open(format!("samples/{}/{}.wav", message.bank, message.waveform)) { + Ok(file) => { + let audio_buffer = context.decode_audio_data_sync(file).unwrap(); + let audio_buffer_duration = audio_buffer.duration(); + let src = context.create_buffer_source(); + src.set_buffer(audio_buffer); + src.connect(&lpf); + connect_filters_to_envelope(&env, message, &hpf, &bandpass, now); + src.playback_rate().set_value(message.speed); + if message.loop_packaged.0 == 1 { + src.set_loop(true); + src.set_loop_start(message.loop_packaged.1); + src.set_loop_end(message.loop_packaged.2); + src.start_at_with_offset_and_duration(now, src.loop_start(), audio_buffer_duration / message.speed as f64); + }; + // println!("{}", message.is_loop); + // src.start_at_with_offset_and_duration(now, message.begin * audio_buffer_duration, audio_buffer_duration / message.speed as f64); + src.stop_at(now + 2.0); + }, + Err(e) => eprintln!("Failed to open file: {:?}", e), + } + } + } +} + +fn create_osc_type(message: &WebAudioMessage) -> OscillatorType { + match message.waveform.as_str() { + "sine" => OscillatorType::Sine, + "square" => OscillatorType::Square, + "triangle" => OscillatorType::Triangle, + "saw" => OscillatorType::Sawtooth, + _ => panic!("Invalid oscillator type"), + } +} + +fn create_filters(context: &mut AudioContext, message: &WebAudioMessage) -> (BiquadFilterNode, BiquadFilterNode, BiquadFilterNode) { + let lpf = context.create_biquad_filter(); + lpf.set_type(Lowpass); + lpf.frequency().set_value(message.cutoff); + lpf.q().set_value(message.resonance); + + let hpf = context.create_biquad_filter(); + hpf.set_type(Highpass); + hpf.frequency().set_value(message.hcutoff); + hpf.q().set_value(message.hresonance); + + let bandpass = context.create_biquad_filter(); + bandpass.set_type(Bandpass); + + lpf.connect(&hpf); + + (lpf, hpf, bandpass) +} + +fn connect_filters_to_envelope(envelope: &GainNode, message: &WebAudioMessage, hpf: &BiquadFilterNode, bandpass: &BiquadFilterNode, now: f64) { + if message.bandf > 0.0 { + bandpass.frequency().set_value(message.bandf); + bandpass.q().set_value(message.bandq); + hpf.connect(bandpass); + bandpass.connect(envelope); + } else { + hpf.connect(envelope); + } + + envelope.gain() + .set_value_at_time(0., now) + .linear_ramp_to_value_at_time(message.velocity, now + 0.001) + .exponential_ramp_to_value_at_time(0.0001, now + message.duration * 2.0); +} + + diff --git a/website/src/repl/Repl.jsx b/website/src/repl/Repl.jsx index 173bb4556..4e05f067e 100644 --- a/website/src/repl/Repl.jsx +++ b/website/src/repl/Repl.jsx @@ -22,6 +22,7 @@ import Loader from './Loader'; import { settingPatterns } from '../settings.mjs'; import { code2hash, hash2code } from './helpers.mjs'; import { isTauri } from '../tauri.mjs'; +import {webaudioDesktopOutput} from "@strudel/desktopbridge"; const { latestCode } = settingsMap.get(); @@ -132,7 +133,7 @@ export function Repl({ embedded = false }) { const { code, setCode, scheduler, evaluate, activateCode, isDirty, activeCode, pattern, started, stop, error } = useStrudel({ initialCode: '// LOADING...', - defaultOutput: webaudioOutput, + defaultOutput: isTauri() ? webaudioDesktopOutput : webaudioOutput, getTime, beforeEval: async () => { setPending(true);