mirror of
https://codeberg.org/uzu/strudel
synced 2026-07-26 23:21:37 -04:00
212 lines
6.4 KiB
Rust
212 lines
6.4 KiB
Rust
use std::{
|
|
sync::Arc,
|
|
time::Duration,
|
|
thread::sleep
|
|
};
|
|
use tokio::{
|
|
sync::{mpsc, Mutex},
|
|
time::Instant
|
|
};
|
|
use serde::Deserialize;
|
|
use web_audio_api::{
|
|
context::{AudioContext, AudioContextLatencyCategory, AudioContextOptions},
|
|
node::AudioNode
|
|
};
|
|
use crate::superdough::{ADSR, BPF, Delay, FilterADSR, HPF, Loop, LPF, superdough};
|
|
|
|
|
|
#[derive(Debug)]
|
|
pub struct WebAudioMessage {
|
|
pub note: f32,
|
|
pub instant: Instant,
|
|
pub offset: f64,
|
|
pub waveform: String,
|
|
pub bank: String,
|
|
pub lpf: LPF,
|
|
pub hpf: HPF,
|
|
pub bpf: BPF,
|
|
pub duration: f64,
|
|
pub velocity: f32,
|
|
pub delay: Delay,
|
|
pub speed: f32,
|
|
pub begin: f64,
|
|
pub end: f64,
|
|
pub looper: Loop,
|
|
pub adsr: ADSR,
|
|
pub lpenv: FilterADSR,
|
|
pub hpenv: FilterADSR,
|
|
pub bpenv: FilterADSR,
|
|
}
|
|
|
|
pub struct AsyncInputTransmitWebAudio {
|
|
pub inner: Mutex<mpsc::Sender<Vec<WebAudioMessage>>>,
|
|
}
|
|
|
|
pub fn init(
|
|
async_input_receiver: mpsc::Receiver<Vec<WebAudioMessage>>,
|
|
mut async_output_receiver: mpsc::Receiver<Vec<WebAudioMessage>>,
|
|
async_output_transmitter: mpsc::Sender<Vec<WebAudioMessage>>
|
|
) {
|
|
tauri::async_runtime::spawn(async move { async_process_model(async_input_receiver, async_output_transmitter).await });
|
|
let message_queue: Arc<Mutex<Vec<WebAudioMessage>>> = 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_receiver: mpsc::Receiver<Vec<WebAudioMessage>>,
|
|
output_transmitter: mpsc::Sender<Vec<WebAudioMessage>>
|
|
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|
while let Some(input) = input_receiver.recv().await {
|
|
let output = input;
|
|
output_transmitter.send(output).await?;
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
pub struct MessageFromJS {
|
|
note: f32,
|
|
offset: f64,
|
|
waveform: String,
|
|
bank: String,
|
|
lpf: (f32, f32),
|
|
hpf: (f32, f32),
|
|
bpf: (f32, f32),
|
|
duration: f64,
|
|
velocity: f32,
|
|
delay: (f64, f64, f64),
|
|
speed: f32,
|
|
begin: f64,
|
|
end: f64,
|
|
looper: (u8, f64, f64),
|
|
adsr: (f64, f64, f32, f64),
|
|
lpenv: (f64, f64, f64, f64, f64),
|
|
hpenv: (f64, f64, f64, f64, f64),
|
|
bpenv: (f64, f64, f64, f64, f64),
|
|
}
|
|
|
|
// Called from JS
|
|
#[tauri::command]
|
|
pub async fn sendwebaudio(
|
|
messagesfromjs: Vec<MessageFromJS>,
|
|
state: tauri::State<'_, AsyncInputTransmitWebAudio>
|
|
) -> Result<(), String> {
|
|
let async_proc_input_tx = state.inner.lock().await;
|
|
let mut messages_to_process: Vec<WebAudioMessage> = 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,
|
|
lpf: LPF {
|
|
frequency: m.lpf.0,
|
|
resonance: m.lpf.1,
|
|
},
|
|
hpf: HPF {
|
|
frequency: m.hpf.0,
|
|
resonance: m.hpf.1,
|
|
},
|
|
bpf: BPF {
|
|
frequency: m.bpf.0,
|
|
resonance: m.bpf.1,
|
|
},
|
|
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,
|
|
looper: Loop {
|
|
is_loop: m.looper.0,
|
|
loop_start: m.looper.1,
|
|
loop_end: m.looper.2,
|
|
},
|
|
adsr: ADSR {
|
|
attack: m.adsr.0,
|
|
decay: m.adsr.1,
|
|
sustain: m.adsr.2,
|
|
release: m.adsr.3,
|
|
},
|
|
lpenv: FilterADSR {
|
|
attack: m.lpenv.0,
|
|
decay: m.lpenv.1,
|
|
sustain: m.lpenv.2,
|
|
release: m.lpenv.3,
|
|
env: m.lpenv.4,
|
|
},
|
|
hpenv: FilterADSR {
|
|
attack: m.hpenv.0,
|
|
decay: m.hpenv.1,
|
|
sustain: m.hpenv.2,
|
|
release: m.hpenv.3,
|
|
env: m.hpenv.4,
|
|
},
|
|
bpenv: FilterADSR {
|
|
attack: m.bpenv.0,
|
|
decay: m.bpenv.1,
|
|
sustain: m.bpenv.2,
|
|
release: m.bpenv.3,
|
|
env: m.bpenv.4,
|
|
},
|
|
};
|
|
messages_to_process.push(message_to_process);
|
|
}
|
|
|
|
async_proc_input_tx.send(messages_to_process).await.map_err(|e| e.to_string())
|
|
} |