From 747ea8e6a35bda001828891ac405a67e6f99e81b Mon Sep 17 00:00:00 2001 From: Vasilii Milovidov Date: Fri, 22 Sep 2023 15:28:08 +0400 Subject: [PATCH] wip --- packages/desktopbridge/webaudiobridge.mjs | 277 ++++++++++++---------- src-tauri/Cargo.lock | 130 +++++++++- src-tauri/Cargo.toml | 1 + src-tauri/src/webaudiobridge.rs | 32 ++- 4 files changed, 312 insertions(+), 128 deletions(-) diff --git a/packages/desktopbridge/webaudiobridge.mjs b/packages/desktopbridge/webaudiobridge.mjs index 868462c69..a226cdd9e 100644 --- a/packages/desktopbridge/webaudiobridge.mjs +++ b/packages/desktopbridge/webaudiobridge.mjs @@ -1,141 +1,176 @@ -import { Invoke } from './utils.mjs'; -import { duration, isNote, midiToFreq, noteToMidi, Pattern } from '@strudel.cycles/core'; -import { getAudioContext, getEnvelope } from '@strudel.cycles/webaudio'; +import {Invoke} from './utils.mjs'; +import {duration, isNote, midiToFreq, noteToMidi, Pattern} from '@strudel.cycles/core'; +import {getAudioContext, getEnvelope, getSound} 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', - ); - } + 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 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, - attack = 0.001, - decay = 0.05, - sustain = 1, - release = 0.1, - lpattack = 0.0001, - lpdecay = 0.2, - lpsustain = 0.6, - lprelease = 0.2, - lpenv = 1, - hpattack = 0.0001, - hpdecay = 0.2, - hpsustain = 0.6, - hprelease = 0.2, - hpenv = 1, - bpattack = 0.0001, - bpdecay = 0.2, - bpsustain = 0.6, - bprelease = 0.2, - bpenv = 1, - } = value; + 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, + attack = 0.001, + decay = 0.05, + sustain = 1, + release = 0.1, + lpattack = 0.0001, + lpdecay = 0.2, + lpsustain = 0.6, + lprelease = 0.2, + lpenv = 1, + hpattack = 0.0001, + hpdecay = 0.2, + hpsustain = 0.6, + hprelease = 0.2, + hpenv = 1, + bpattack = 0.0001, + bpdecay = 0.2, + bpsustain = 0.6, + bprelease = 0.2, + bpenv = 1, + n = 0, + } = value; + bank = getSound(s).data.samples; - value.duration = hapDuration; - if (isNote(note)) { - note = noteToMidi(note); - } + // SAMPLES + value.duration = hapDuration; + if (isNote(note)) { + note = noteToMidi(note); + } + const baseUrl = getSound(s).data.baseUrl; + let path; + if (baseUrl === './piano/') { + path = 'https://strudel.tidalcycles.org/'; + } else if (baseUrl === './EmuSP12/') { + path = 'https://strudel.tidalcycles.org/'; + } - if (delay !== 0) { - delay = Math.abs(delay); - delayfeedback = Math.abs(delayfeedback); - delaytime = Math.abs(delaytime); - } + let transpose = 0; + transpose = note - 36; + let sampleUrl; + let key; + if (Array.isArray(bank)) { + sampleUrl = path !== undefined ? path + bank[n % bank.length].replace('./', '') : bank[n % bank.length].replace('./', ''); + } else { + const midiDiff = (noteA) => noteToMidi(noteA) - note; + // object format will expect keys as notes + const closest = Object.keys(bank) + .filter((k) => !k.startsWith('_')) + .reduce( + (closest, key, j) => (!closest || Math.abs(midiDiff(key)) < Math.abs(midiDiff(closest)) ? key : closest), + null, + ); + transpose = -midiDiff(closest); // semitones to repitch + sampleUrl = path !== undefined ? path + bank[closest][n % bank[closest].length].replace('./', '') : bank[closest][n % bank[closest].length].replace('./', ''); + } - let adsr_on = attack !== 0.001 || decay !== 0.05 || sustain !== 1 || release !== 0.01 ? 1 : 0; + const playbackRate = 1.0 * Math.pow(2, transpose / 12); + // const sound = getSoundData(s); - const packages = { - loop: [loop, loopBegin, loopEnd], - delay: [delay, delaytime, delayfeedback], - lpf: [cutoff, resonance], - hpf: [hcutoff, hresonance], - bpf: [bandf, bandq], - adsr: [attack, decay, sustain, release, adsr_on], - lpenv: [lpattack, lpdecay, lpsustain, lprelease, lpenv], - hpenv: [hpattack, hpdecay, hpsustain, hprelease, hpenv], - bpenv: [bpattack, bpdecay, bpsustain, bprelease, bpenv], - }; + // SYNTHS + if (delay !== 0) { + delay = Math.abs(delay); + delayfeedback = Math.abs(delayfeedback); + delaytime = Math.abs(delaytime); + } - const offset = (t - getAudioContext().currentTime) * 1000; - const roundedOffset = Math.round(offset); - const messagesfromjs = []; + let adsr_on = attack !== 0.001 || decay !== 0.05 || sustain !== 1 || release !== 0.01 ? 1 : 0; - messagesfromjs.push({ - note: midiToFreq(note), - offset: roundedOffset, - waveform: s, - bank: bank, - lpf: packages.lpf, - hpf: packages.hpf, - bpf: packages.bpf, - duration: hapDuration, - velocity: velocity, - delay: packages.delay, - orbit: orbit, - speed: speed, - begin: begin, - end: end, - looper: packages.loop, - adsr: packages.adsr, - lpenv: packages.lpenv, - hpenv: packages.hpenv, - bpenv: packages.bpenv, - }); + const packages = { + loop: [loop, loopBegin, loopEnd], + delay: [delay, delaytime, delayfeedback], + lpf: [cutoff, resonance], + hpf: [hcutoff, hresonance], + bpf: [bandf, bandq], + adsr: [attack, decay, sustain, release, adsr_on], + lpenv: [lpattack, lpdecay, lpsustain, lprelease, lpenv], + hpenv: [hpattack, hpdecay, hpsustain, hprelease, hpenv], + bpenv: [bpattack, bpdecay, bpsustain, bprelease, bpenv], + }; - if (messagesfromjs.length) { - setTimeout(() => { - Invoke('sendwebaudio', { messagesfromjs }); + const offset = (t - getAudioContext().currentTime) * 1000; + const roundedOffset = Math.round(offset); + const messagesfromjs = []; + + messagesfromjs.push({ + note: midiToFreq(note), + offset: roundedOffset, + waveform: s, + bank: bank, + lpf: packages.lpf, + hpf: packages.hpf, + bpf: packages.bpf, + duration: hapDuration, + velocity: velocity, + delay: packages.delay, + orbit: orbit, + speed: speed, + begin: begin, + end: end, + looper: packages.loop, + adsr: packages.adsr, + lpenv: packages.lpenv, + hpenv: packages.hpenv, + bpenv: packages.bpenv, + n: n, + sampleurl: sampleUrl, }); - } + + if (messagesfromjs.length) { + setTimeout(() => { + Invoke('sendwebaudio', {messagesfromjs}); + }); + } }; const hap2value = (hap) => { - hap.ensureObjectValue(); - return { ...hap.value, velocity: hap.context.velocity }; + 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); + desktopAudio(hap2value(hap), t - ct, hap.duration / cps, cps); export const webaudioDesktopOutput = (hap, deadline, hapDuration) => - desktopAudio(hap2value(hap), deadline, hapDuration); + desktopAudio(hap2value(hap), deadline, hapDuration); Pattern.prototype.webaudio = function () { - return this.onTrigger(webaudioDesktopOutputTrigger); + return this.onTrigger(webaudioDesktopOutputTrigger); }; diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index a32671315..ca9fabc79 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -98,6 +98,7 @@ name = "app" version = "0.1.0" dependencies = [ "midir", + "mini-moka", "rosc", "serde", "serde_json", @@ -260,6 +261,12 @@ version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" +[[package]] +name = "bytecount" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c676a478f63e9fa2dd5368a42f28bba0d6c560b775f38583c8bbaa7fcd67c9c" + [[package]] name = "bytemuck" version = "1.13.1" @@ -308,6 +315,37 @@ dependencies = [ "system-deps 6.1.1", ] +[[package]] +name = "camino" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cfa25e60aea747ec7e1124f238816749faa93759c6ff5b31f1ccdda137f4479" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", +] + [[package]] name = "cargo_toml" version = "0.15.3" @@ -726,6 +764,19 @@ dependencies = [ "syn 2.0.18", ] +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.0", + "lock_api", + "once_cell", + "parking_lot_core", +] + [[package]] name = "dasp_sample" version = "0.11.0" @@ -852,6 +903,15 @@ dependencies = [ "libc", ] +[[package]] +name = "error-chain" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d2f06b9cac1506ece98fe3231e3cc9c4410ec3d5b1f24ae1c8946f0742cdefc" +dependencies = [ + "version_check", +] + [[package]] name = "fastrand" version = "1.9.0" @@ -1323,6 +1383,12 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" + [[package]] name = "heck" version = "0.3.3" @@ -1493,7 +1559,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.3", "serde", ] @@ -1801,6 +1867,21 @@ dependencies = [ "windows 0.43.0", ] +[[package]] +name = "mini-moka" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23e0b72e7c9042467008b10279fc732326bd605459ae03bda88825909dd19b56" +dependencies = [ + "crossbeam-channel", + "crossbeam-utils", + "dashmap", + "skeptic", + "smallvec", + "tagptr", + "triomphe", +] + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -2354,6 +2435,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "pulldown-cmark" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a1a2f1f0a7ecff9c31abbe177637be0e97a0aef46cf8738ece09327985d998" +dependencies = [ + "bitflags", + "memchr", + "unicase", +] + [[package]] name = "quick-xml" version = "0.28.2" @@ -2858,6 +2950,21 @@ version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" +[[package]] +name = "skeptic" +version = "0.13.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16d23b015676c90a0f01c197bfdc786c20342c73a0afdda9025adb0bc42940a8" +dependencies = [ + "bytecount", + "cargo_metadata", + "error-chain", + "glob", + "pulldown-cmark", + "tempfile", + "walkdir", +] + [[package]] name = "slab" version = "0.4.8" @@ -3195,6 +3302,12 @@ dependencies = [ "version-compare 0.1.1", ] +[[package]] +name = "tagptr" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" + [[package]] name = "tao" version = "0.16.2" @@ -3708,12 +3821,27 @@ dependencies = [ "serde_json", ] +[[package]] +name = "triomphe" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee8098afad3fb0c54a9007aab6804558410503ad676d4633f9c2559a00ac0f" + [[package]] name = "typenum" version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" +[[package]] +name = "unicase" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" +dependencies = [ + "version_check", +] + [[package]] name = "unicode-bidi" version = "0.3.13" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 1a9cdb72e..23499cb18 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -22,6 +22,7 @@ midir = "0.9.1" tokio = { version = "1.29.0", features = ["full"] } rosc = "0.10.1" web-audio-api = { git = "https://github.com/orottier/web-audio-api-rs.git", branch = "main" } +mini-moka = "0.10.2" [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/webaudiobridge.rs b/src-tauri/src/webaudiobridge.rs index 38857c6f4..6d84721b1 100644 --- a/src-tauri/src/webaudiobridge.rs +++ b/src-tauri/src/webaudiobridge.rs @@ -3,6 +3,7 @@ use std::{ time::Duration, thread::sleep }; +use mini_moka::sync::Cache; use tokio::{ sync::{mpsc, Mutex}, @@ -38,6 +39,8 @@ pub struct WebAudioMessage { pub lpenv: FilterADSR, pub hpenv: FilterADSR, pub bpenv: FilterADSR, + pub n: usize, + pub sampleurl: String, } pub struct AsyncInputTransmitWebAudio { @@ -70,21 +73,24 @@ pub fn init( }); let message_queue_clone = Arc::clone(&message_queue); - tauri::async_runtime::spawn(async move { - /* ........................................................... - Prepare audio context - ............................................................*/ + // Create 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() }); + // Create audio context + tauri::async_runtime::spawn(async move { + /* ........................................................... + Prepare audio context + ............................................................*/ +// let cache = Cache::new(10_000); +// let cache_clone_1 = cache.clone(); /* ........................................................... Process queued messages ............................................................*/ @@ -97,7 +103,17 @@ pub fn init( return true; }; - superdough(message.clone(), &mut audio_context); + let file_path = message.sampleurl.clone(); + + + + // superdough(message.clone(), &mut audio_context); + + + tokio::spawn(async move { + + }); + return false; }); @@ -138,6 +154,8 @@ pub struct MessageFromJS { lpenv: (f64, f64, f64, f64, f64), hpenv: (f64, f64, f64, f64, f64), bpenv: (f64, f64, f64, f64, f64), + n: usize, + sampleurl: String, } // Called from JS @@ -212,6 +230,8 @@ pub async fn sendwebaudio( release: m.bpenv.3, env: m.bpenv.4, }, + n: m.n, + sampleurl: m.sampleurl, }; messages_to_process.push(message_to_process); }