Adding structs and traits

This commit is contained in:
Vasilii Milovidov
2023-09-29 13:09:27 +04:00
parent 69a3e27eec
commit 38af4af4ff
5 changed files with 419 additions and 325 deletions
+148 -248
View File
@@ -54,206 +54,170 @@ pub struct FilterADSR {
pub env: f64,
}
pub fn superdough_sample(
message: &WebAudioMessage,
context: &mut AudioContext,
audio_buffer: AudioBuffer,
) {
let now = context.current_time();
let mut chain: Vec<&dyn AudioNode> = Vec::new();
let compressor = context.create_dynamics_compressor();
compressor.connect(&context.destination());
compressor.threshold().set_value(-50.0);
let delay = context.create_delay(1.);
let audio_buffer_duration = audio_buffer.duration();
let mut src = context.create_buffer_source();
src.set_buffer(audio_buffer);
chain.push(&src);
let filters = create_filters(context, message);
for f in &filters {
chain.push(f);
}
let env = context.create_gain();
chain.push(&env);
if message.delay.wet > 0.0 {
create_delay(message, context, &env, &delay, now, &compressor);
}
// Connect nodes and play sample
connect_nodes(chain, &compressor);
src.playback_rate().set_value(message.speed);
let src = play_sample(message, now, audio_buffer_duration, src, filters, &env);
src.set_onended(move |_| {
delay.disconnect();
});
pub trait WebAudioPlayer {
fn set_adsr(&mut self, t: f64, adsr: &ADSR, velocity: f32, duration: f64);
fn play(&mut self, t: f64, message: &WebAudioMessage, duration: f64,);
fn set_filters(&mut self, context: &mut AudioContext, message: &WebAudioMessage) -> Vec<BiquadFilterNode>;
}
pub struct Synth {
pub oscillator: OscillatorNode,
pub envelope: GainNode,
}
pub fn superdough_synth(
message: &WebAudioMessage,
context: &mut AudioContext,
) {
let now = context.current_time();
let mut chain: Vec<&dyn AudioNode> = Vec::new();
let compressor = context.create_dynamics_compressor();
compressor.connect(&context.destination());
compressor.threshold().set_value(-50.0);
let env = context.create_gain();
let delay = context.create_delay(1.);
let osc = context.create_oscillator();
// Create nodes for synth playback
chain.push(&osc);
let filters = create_filters(context, message);
for f in &filters {
chain.push(f);
impl Synth {
pub fn new(context: &mut AudioContext, message: &WebAudioMessage) -> Self {
let oscillator = context.create_oscillator();
let envelope = context.create_gain();
Self { oscillator, envelope }
}
chain.push(&env);
if message.delay.wet > 0.0 {
create_delay(message, context, &env, &delay, now, &compressor);
};
// Connect nodes and play synth
connect_nodes(chain, &compressor);
let osc = play_synth(&message, now, osc, filters, &env);
osc.set_onended(move |_| {
delay.disconnect();
});
}
fn create_delay(message: &WebAudioMessage, context: &mut AudioContext, env: &GainNode, delay: &DelayNode, now: f64, compressor: &DynamicsCompressorNode) {
let output = context.create_gain();
output.connect(compressor);
delay.delay_time().set_value(message.delay.delay_time);
delay.connect(&output);
let feedback = context.create_gain();
feedback.gain().set_value(message.delay.feedback);
feedback.connect(delay);
delay.connect(&feedback);
let pre_gain = context.create_gain();
pre_gain.gain().set_value_at_time(message.delay.wet, now + message.delay.delay_time as f64);
pre_gain.connect(&feedback);
let input = context.create_gain();
input.connect(&pre_gain);
env.connect(&input);
}
fn play_synth(message: &&WebAudioMessage, now: f64, mut osc: OscillatorNode, filters: Vec<BiquadFilterNode>, env: &GainNode) -> OscillatorNode {
osc.set_type(create_osc_type(&message));
osc.frequency().set_value(message.note);
osc.start();
match message.adsr.adsr_on {
1 => apply_synth_adsr(&env, message, now),
_ => apply_default_synth_adsr(&env, message, now),
};
for f in filters {
apply_filter_adsr(&f, message, &f.type_(), now);
pub fn set_frequency(&mut self, frequency: &f32) {
self.oscillator.frequency().set_value(*frequency);
}
osc.stop_at(now + message.duration + message.adsr.release + 2.);
osc
}
fn create_osc_type(message: &WebAudioMessage) -> OscillatorType {
match message.waveform.as_str() {
"sine" => OscillatorType::Sine,
"square" => OscillatorType::Square,
"triangle" => OscillatorType::Triangle,
"saw" => OscillatorType::Sawtooth,
"sawtooth" => OscillatorType::Sawtooth,
_ => panic!("Invalid oscillator type"),
}
}
fn play_sample(message: &WebAudioMessage, now: f64, audio_buffer_duration: f64, mut src: AudioBufferSourceNode, filters: Vec<BiquadFilterNode>, env: &GainNode) -> AudioBufferSourceNode {
let (start_at, stop_at) = if message.speed < 0.0 {
(audio_buffer_duration, now + message.duration + 0.2)
} else {
(message.begin * audio_buffer_duration, now + message.duration + message.adsr.release)
};
if message.looper.is_loop > 0 {
src.set_loop(true);
src.set_loop_start(message.looper.loop_start);
src.set_loop_end(message.looper.loop_end);
src.start_at_with_offset(
now,
src.loop_start(),
);
match message.adsr.adsr_on {
1 => apply_drum_adsr(&env, message, now),
_ => apply_default_drum_adsr(&env, message, now),
pub fn set_waveform(&mut self, waveform: &str) {
match waveform {
"sine" => self.oscillator.set_type(OscillatorType::Sine),
"square" => self.oscillator.set_type(OscillatorType::Square),
"triangle" => self.oscillator.set_type(OscillatorType::Triangle),
"saw" | "sawtooth" => self.oscillator.set_type(OscillatorType::Sawtooth),
_ => {}
}
for f in filters {
apply_filter_adsr(&f, message, &f.type_(), now);
}
}
impl WebAudioPlayer for Synth {
fn set_adsr(&mut self, t: f64, adsr: &ADSR, velocity: f32, duration: f64) {
self.envelope.gain()
.set_value_at_time(0.0, t)
.linear_ramp_to_value_at_time(velocity, t + adsr.attack)
.linear_ramp_to_value_at_time((adsr.sustain + 0.001) * velocity, t + adsr.attack + adsr.decay)
.set_value_at_time(adsr.sustain * velocity, t + duration)
.linear_ramp_to_value_at_time(0.0, t + duration + adsr.release);
}
fn play(&mut self, t: f64, message: &WebAudioMessage, release: f64) {
self.oscillator.start();
self.oscillator.stop_at(t + message.duration + release);
}
fn set_filters(&mut self, context: &mut AudioContext, message: &WebAudioMessage) -> Vec<BiquadFilterNode> {
let mut filters = Vec::new();
if message.bpf.frequency > 0.0 {
let mut bpf = context.create_biquad_filter();
bpf.set_type(Bandpass);
bpf.frequency().set_value(message.bpf.frequency);
bpf.q().set_value(message.bpf.resonance);
filters.push(bpf);
} else if message.lpf.frequency > 0.0 {
let mut lpf = context.create_biquad_filter();
lpf.set_type(Lowpass);
lpf.frequency().set_value(message.lpf.frequency);
lpf.q().set_value(message.lpf.resonance);
filters.push(lpf);
} else if message.hpf.frequency > 0.0 {
let mut hpf = context.create_biquad_filter();
hpf.set_type(Highpass);
hpf.frequency().set_value(message.hpf.frequency);
hpf.q().set_value(message.hpf.resonance);
filters.push(hpf);
}
src.stop_at(now + message.duration + message.adsr.release);
} else {
src.start_at_with_offset(
now,
start_at,
);
match message.adsr.adsr_on {
1 => apply_drum_adsr(&env, message, now),
_ => apply_default_drum_adsr(&env, message, now),
}
for f in filters {
apply_filter_adsr(&f, message, &f.type_(), now);
if !filters.is_empty() {
self.oscillator.connect(filters.first().unwrap());
filters.last().unwrap().connect(&self.envelope);
} else {
self.oscillator.connect(&self.envelope);
};
src.stop_at(stop_at);
filters
}
src
}
fn create_filters(context: &mut AudioContext, message: &WebAudioMessage) -> Vec<BiquadFilterNode> {
let mut filters = Vec::new();
if message.bpf.frequency > 0.0 {
let mut bpf = context.create_biquad_filter();
bpf.set_type(Bandpass);
bpf.frequency().set_value(message.bpf.frequency);
bpf.q().set_value(message.bpf.resonance);
filters.push(bpf);
} else if message.lpf.frequency > 0.0 && message.hpf.frequency > 0.0 {
let mut lpf = context.create_biquad_filter();
let mut hpf = context.create_biquad_filter();
lpf.set_type(Lowpass);
lpf.frequency().set_value(message.lpf.frequency);
lpf.q().set_value(message.lpf.resonance);
hpf.set_type(Highpass);
hpf.frequency().set_value(message.hpf.frequency);
hpf.q().set_value(message.hpf.resonance);
lpf.connect(&hpf);
filters.push(lpf);
filters.push(hpf);
} else if message.lpf.frequency > 0.0 {
let mut lpf = context.create_biquad_filter();
lpf.set_type(Lowpass);
lpf.frequency().set_value(message.lpf.frequency);
lpf.q().set_value(message.lpf.resonance);
filters.push(lpf);
} else if message.hpf.frequency > 0.0 {
let mut hpf = context.create_biquad_filter();
hpf.set_type(Highpass);
hpf.frequency().set_value(message.hpf.frequency);
hpf.q().set_value(message.hpf.resonance);
filters.push(hpf);
}
filters
pub struct Sampler {
pub sample: AudioBufferSourceNode,
pub envelope: GainNode,
}
fn apply_filter_adsr(filter_node: &BiquadFilterNode, message: &WebAudioMessage, filter: &BiquadFilterType, now: f64) {
impl Sampler {
pub fn new(context: &mut AudioContext, message: &WebAudioMessage, audio_buffer: AudioBuffer) -> Self {
let mut sample = context.create_buffer_source();
sample.set_buffer(audio_buffer);
let envelope = context.create_gain();
Self { sample, envelope }
}
}
impl WebAudioPlayer for Sampler {
fn set_adsr(&mut self, t: f64, adsr: &ADSR, velocity: f32, duration: f64) {
self.envelope.gain()
.set_value_at_time(0.0, t)
.linear_ramp_to_value_at_time(velocity, t + adsr.attack)
.linear_ramp_to_value_at_time((adsr.sustain + 0.00001) * velocity, t + adsr.attack + adsr.decay)
.set_value_at_time(adsr.sustain * velocity, t + duration)
.linear_ramp_to_value_at_time(0.0, t + duration + adsr.release);
}
fn play(&mut self, t: f64, message: &WebAudioMessage, release: f64) {
let buffer_duration = release;
let (start_at, stop_at) = if message.speed < 0.0 {
(buffer_duration, t + message.duration + 0.2)
} else {
(message.begin * buffer_duration, t + message.duration + message.adsr.release)
};
if message.looper.is_loop > 0 {
self.sample.set_loop(true);
self.sample.set_loop_start(message.looper.loop_start);
self.sample.set_loop_end(message.looper.loop_end);
self.sample.start_at_with_offset(
t,
self.sample.loop_start(),
);
self.sample.stop_at(t + message.duration + message.adsr.release);
} else {
self.sample.start_at_with_offset(
t,
start_at,
);
self.sample.stop_at(stop_at);
}
}
fn set_filters(&mut self, context: &mut AudioContext, message: &WebAudioMessage) -> Vec<BiquadFilterNode> {
let mut filters = Vec::new();
if message.bpf.frequency > 0.0 {
let mut bpf = context.create_biquad_filter();
bpf.set_type(Bandpass);
bpf.frequency().set_value(message.bpf.frequency);
bpf.q().set_value(message.bpf.resonance);
filters.push(bpf);
} else if message.lpf.frequency > 0.0 {
let mut lpf = context.create_biquad_filter();
lpf.set_type(Lowpass);
lpf.frequency().set_value(message.lpf.frequency);
lpf.q().set_value(message.lpf.resonance);
filters.push(lpf);
} else if message.hpf.frequency > 0.0 {
let mut hpf = context.create_biquad_filter();
hpf.set_type(Highpass);
hpf.frequency().set_value(message.hpf.frequency);
hpf.q().set_value(message.hpf.resonance);
filters.push(hpf);
}
if !filters.is_empty() {
self.sample.connect(filters.first().unwrap());
filters.last().unwrap().connect(&self.envelope);
} else {
self.sample.connect(&self.envelope);
};
filters
}
}
pub fn apply_filter_adsr(filter_node: &BiquadFilterNode, message: &WebAudioMessage, filter: &BiquadFilterType, now: f64) {
let env = match filter {
Lowpass => message.lpenv,
Highpass => message.hpenv,
@@ -281,67 +245,3 @@ fn apply_filter_adsr(filter_node: &BiquadFilterNode, message: &WebAudioMessage,
filter_node.frequency().set_value_at_time(sustain_level, now + message.duration);
filter_node.frequency().linear_ramp_to_value_at_time(min, now + message.duration + env.release.max(0.1));
}
//TODO fix envelopes
fn apply_adsr(
envelope: &GainNode,
message: &WebAudioMessage,
now: f64,
attack: f64,
sustain: f32,
decay: f64,
release: f64,
) {
envelope
.gain()
.set_value_at_time(0.0, now)
.linear_ramp_to_value_at_time(message.velocity, now + attack)
.exponential_ramp_to_value_at_time(
(sustain + 0.00001) * message.velocity,
now + attack + decay,
)
.set_value_at_time(sustain * message.velocity, now + message.duration)
.linear_ramp_to_value_at_time(0.00001, now + message.duration + release);
}
fn apply_synth_adsr(envelope: &GainNode, message: &WebAudioMessage, now: f64) {
apply_adsr(
envelope,
message,
now,
message.adsr.attack,
message.adsr.sustain,
message.adsr.decay,
message.adsr.release,
);
}
fn apply_default_synth_adsr(envelope: &GainNode, message: &WebAudioMessage, now: f64) {
apply_adsr(envelope, message, now, 0.001, message.velocity, 0.05, 0.07);
}
fn apply_drum_adsr(envelope: &GainNode, message: &WebAudioMessage, now: f64) {
apply_adsr(
envelope,
message,
now,
message.adsr.attack,
message.adsr.sustain,
message.adsr.decay,
message.adsr.release,
);
}
fn apply_default_drum_adsr(envelope: &GainNode, message: &WebAudioMessage, now: f64) {
apply_adsr(envelope, message, now, 0.001, message.velocity, 0.5, 0.5);
}
fn connect_nodes<'a>(nodes: Vec<&'a (dyn AudioNode + 'a)>, compressor: &DynamicsCompressorNode) -> Vec<&'a (dyn AudioNode)> {
for i in 0..nodes.len() - 1 {
nodes[i].connect(nodes[i + 1]);
}
if let Some(last_node) = nodes.last() {
last_node.connect(compressor);
}
nodes
}
+129 -58
View File
@@ -4,19 +4,22 @@ use std::{
};
use std::fs::File;
use std::path::Path;
use quick_cache::sync::Cache;
// use mini_moka::sync::Cache;
use std::sync::mpsc::{RecvError, SendError};
use std::time::{Instant, SystemTime};
use mini_moka::sync::Cache;
use reqwest::Url;
use tokio::{fs, sync::{mpsc, Mutex}, time::Instant};
use tokio::{fs, sync::{mpsc, Mutex}};
use serde::Deserialize;
use tokio::io::AsyncWriteExt;
use web_audio_api::{AudioBuffer, context::{AudioContext, AudioContextLatencyCategory, AudioContextOptions}};
use web_audio_api::context::BaseAudioContext;
use crate::superdough::{ADSR, BPF, Delay, FilterADSR, HPF, Loop, LPF, superdough_sample, superdough_synth};
use web_audio_api::node::{AudioBufferSourceNode, AudioNode, AudioScheduledSourceNode, BiquadFilterNode, DelayNode, DynamicsCompressorNode, GainNode, OscillatorNode, OscillatorType};
use web_audio_api::node::BiquadFilterType::{Bandpass, Highpass, Lowpass};
use crate::superdough::{ADSR, apply_filter_adsr, BPF, Delay, FilterADSR, HPF, Loop, LPF, Sampler, Synth, WebAudioPlayer};
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct WebAudioMessage {
pub note: f32,
pub instant: Instant,
@@ -52,6 +55,8 @@ pub fn init(
async_output_transmitter: mpsc::Sender<Vec<WebAudioMessage>>,
) {
tauri::async_runtime::spawn(async move { async_process_model(async_input_receiver, async_output_transmitter).await });
let (sender, receiver) = std::sync::mpsc::channel();
let message_queue: Arc<Mutex<Vec<WebAudioMessage>>> = Arc::new(Mutex::new(Vec::new()));
/* ...........................................................
Listen For incoming messages and add to queue
@@ -70,20 +75,7 @@ pub fn init(
});
let message_queue_clone = Arc::clone(&message_queue);
/* ...........................................................
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()
});
tauri::async_runtime::spawn(async move {
let cache: Cache<String, AudioBuffer> = Cache::new(10_000);
/* ...........................................................
Process queued messages
............................................................*/
@@ -96,51 +88,130 @@ pub fn init(
return true;
};
match message.waveform.as_str() {
"sine" | "square" | "triangle" | "saw" | "sawtooth" => {
superdough_synth(message.clone(), &mut audio_context);
}
_ => {
let url = Url::parse(&*message.sampleurl).expect("failed to parse url");
let filename = url.path_segments()
.and_then(Iterator::last)
.and_then(|name| if name.is_empty() { None } else { Some(name) })
.unwrap_or("tmp.ben");
let file_path = format!("samples/{}{}", message.dirname, filename);
match sender.send(message.clone()) {
Ok(_) => {}
Err(_) => {}
};
if let Some(audio_buffer) = cache.get(&file_path) {
superdough_sample(
message.clone(),
&mut audio_context,
audio_buffer.clone(),
);
} else if let Ok(file) = File::open(&file_path) {
let audio_buffer = audio_context.decode_audio_data_sync(file)
.unwrap_or_else(|_| panic!("Failed to decode audio data"));
cache.insert(file_path.clone(), audio_buffer.clone());
}
tokio::spawn(async move {
if tokio::fs::metadata(&file_path).await.is_err() {
let response = reqwest::get(url)
.await
.unwrap_or_else(|_| panic!("Failed to send GET request"));
let bytes = response.bytes().await.unwrap();
let path = Path::new(&file_path);
let mut file = create_file_and_dirs(path).await;
file.write_all(&bytes)
.await
.unwrap_or_else(|_| panic!("Failed to write to file"));
}
});
}
}
return false;
});
tokio::time::sleep(Duration::from_millis(1)).await;
}
});
/* ...........................................................
Create and setup WebAudio
............................................................*/
tauri::async_runtime::spawn(async move {
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()
});
let mut temp_src = audio_context.create_buffer_source();
let compressor = audio_context.create_dynamics_compressor();
compressor.threshold().set_value(-50.0);
compressor.connect(&audio_context.destination());
let delay = audio_context.create_delay(1.);
let output = audio_context.create_gain();
output.connect(&compressor);
delay.connect(&output);
let feedback = audio_context.create_gain();
feedback.connect(&delay);
delay.connect(&feedback);
let pre_gain = audio_context.create_gain();
pre_gain.connect(&feedback);
let input = audio_context.create_gain();
input.connect(&pre_gain);
let cache: Cache<String, AudioBuffer> = Cache::builder()
.max_capacity(31 * 1024 * 1024)
.time_to_idle(Duration::from_secs(40))
.build();
loop {
match receiver.recv() {
Ok(message) => {
match message.waveform.as_str() {
"sine" | "square" | "triangle" | "saw" | "sawtooth" => {
let t = audio_context.current_time();
delay.delay_time().set_value(message.delay.delay_time);
feedback.gain().set_value(message.delay.feedback);
pre_gain.gain().set_value_at_time(message.delay.wet, t + message.delay.delay_time as f64);
let mut synth = Synth::new(&mut audio_context, &message);
synth.set_frequency(&message.note);
synth.set_waveform(&message.waveform);
let filters = synth.set_filters(&mut audio_context, &message);
if message.delay.wet > 0.0 {
synth.envelope.connect(&input);
}
synth.envelope.connect(&compressor);
synth.play(t, &message, message.adsr.release);
synth.set_adsr(t, &message.adsr, message.velocity, message.duration);
if message.lpenv.env > 0.0 || message.hpenv.env > 0.0 || message.bpenv.env > 0.0 {
for f in filters {
apply_filter_adsr(&f, &message, &f.type_(), t);
}
}
}
_ => {
let url = Url::parse(&*message.sampleurl).expect("failed to parse url");
let filename = url.path_segments()
.and_then(Iterator::last)
.and_then(|name| if name.is_empty() { None } else { Some(name) })
.unwrap_or("tmp.ben");
let file_path = format!("/Users/vasiliymilovidov/samples/{}{}", message.dirname, filename);
let file_path_clone = file_path.clone();
tokio::spawn(async move {
if tokio::fs::metadata(&file_path.clone()).await.is_err() {
let response = reqwest::get(url)
.await
.unwrap_or_else(|_| panic!("Failed to send GET request"));
let bytes = response.bytes().await.unwrap();
let path = Path::new(&file_path);
let mut file = create_file_and_dirs(path).await;
file.write_all(&bytes)
.await
.unwrap_or_else(|_| panic!("Failed to write to file"));
}
});
if let Some(audio_buffer) = cache.get(&file_path_clone) {
let t = audio_context.current_time();
delay.delay_time().set_value(message.delay.delay_time);
feedback.gain().set_value(message.delay.feedback);
pre_gain.gain().set_value_at_time(message.delay.wet, t + message.delay.delay_time as f64);
let audio_buffer_duration = audio_buffer.duration();
let mut sampler = Sampler::new(&mut audio_context, &message, audio_buffer);
sampler.sample.playback_rate().set_value(message.speed);
sampler.set_adsr(t, &message.adsr, message.velocity, message.duration);
let filters = sampler.set_filters(&mut audio_context, &message);
if message.lpenv.env > 0.0 || message.hpenv.env > 0.0 || message.bpenv.env > 0.0 {
for f in filters {
apply_filter_adsr(&f, &message, &f.type_(), t);
}
}
if message.delay.wet > 0.0 {
sampler.envelope.connect(&input);
};
sampler.envelope.connect(&compressor);
sampler.play(t, &message, audio_buffer_duration);
} else if let Ok(file) = File::open(&file_path_clone) {
let audio_buffer = audio_context.decode_audio_data_sync(file)
.unwrap_or_else(|_| panic!("Failed to decode audio data"));
cache.insert(file_path_clone.clone(), audio_buffer);
}
}
}
}
Err(_) => {}
}
}
});
}
pub async fn async_process_model(
@@ -266,4 +337,4 @@ async fn create_file_and_dirs(path: &Path) -> fs::File {
}
let file = fs::File::create(path).await.unwrap();
file
}
}