From bfb32d89a975f871d74233b1e15c7049ec077646 Mon Sep 17 00:00:00 2001 From: Vasilii Milovidov Date: Mon, 16 Oct 2023 14:24:47 +0400 Subject: [PATCH] Various improvements + reverb tests --- src-tauri/src/main.rs | 5 +- src-tauri/src/reverbgen.rs | 99 --------- src-tauri/src/webaudio/mod.rs | 5 + src-tauri/src/webaudio/reverbgen.rs | 241 ++++++++++++++++++++ src-tauri/src/{ => webaudio}/superdough.rs | 82 ++++++- src-tauri/src/webaudiobridge.rs | 246 ++------------------- 6 files changed, 347 insertions(+), 331 deletions(-) delete mode 100644 src-tauri/src/reverbgen.rs create mode 100644 src-tauri/src/webaudio/mod.rs create mode 100644 src-tauri/src/webaudio/reverbgen.rs rename src-tauri/src/{ => webaudio}/superdough.rs (76%) diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 040db4db3..c308bf288 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -5,8 +5,9 @@ mod midibridge; mod oscbridge; mod loggerbridge; mod webaudiobridge; -mod superdough; -mod reverbgen; +// mod superdough; +// mod reverbgen; +mod webaudio; use std::sync::Arc; diff --git a/src-tauri/src/reverbgen.rs b/src-tauri/src/reverbgen.rs deleted file mode 100644 index 52660c957..000000000 --- a/src-tauri/src/reverbgen.rs +++ /dev/null @@ -1,99 +0,0 @@ -use rand_distr::{Distribution, Normal}; -use web_audio_api::AudioBuffer; - -fn apply_decay(input: &mut [f32], decay_factor: f32) { - let len = input.len(); - - for i in 0..len { - // Multiply each sample by an exponentially decreasing factor - let decay_amt = (-(i as f32) / (len as f32) * decay_factor).exp(); - input[i] *= decay_amt; - } -} - -fn apply_fir_filter(input: &mut [f32], coefficients: &[f32]) { - let num_coeffs = coefficients.len(); - let len = input.len(); - - let input_copy = input.to_vec(); - - for i in num_coeffs..len { - let mut acc = 0.0; - for j in 0..num_coeffs { - acc += input_copy[i - j] * coefficients[j]; - } - input[i] = acc; - } -} - -fn sinc(x: f32, fc: f32) -> f32 { - let pi = std::f32::consts::PI; - if x == 0.0 { - 2.0 * fc - } else { - let arg = 2.0 * pi * fc * x; - arg.sin() / arg - } -} - -fn hamming(n: usize, size: usize) -> f32 { - 0.54 - 0.46 * ((2. * std::f32::consts::PI * n as f32) / ((size - 1) as f32)).cos() -} - -pub fn generate_coefficients_lp(size: usize, fc: f32) -> Vec { - let mut h = vec![0.0; size]; - let m = (size - 1) / 2; - - for n in 0..size { - h[n] = sinc((n as f32 - m as f32), fc); - h[n] *= hamming(n, size); - } - - let sum: f32 = h.iter().sum(); - for n in 0..size { - h[n] /= sum; - } - - h -} - -pub fn create_impulse_response( - len: usize, - decay_factor: f32, - fir_coeffs: &[f32], - sample_rate: f32, - fade: f32, -) -> Vec { - let mut rng = rand::thread_rng(); - let normal = Normal::new(0.0, 1.0).unwrap(); - let fade_in_samples = (fade * sample_rate) as usize; // Compute the number of samples for the fade-in duration - - let mut impulse_response: Vec = (0..len) - .map(|_| normal.sample(&mut rng) as f32) - .map(|v| v.max(-1.0).min(1.0)) // Clamp values to the -1.0 to 1.0 range - .collect(); - - // Apply fade-in to the starting samples - for i in 0..fade_in_samples.min(len) { - impulse_response[i] *= i as f32 / fade_in_samples as f32; - } - - apply_decay(&mut impulse_response, decay_factor); - apply_fir_filter(&mut impulse_response, fir_coeffs); - - impulse_response -} - -pub fn write_to_buffer(buffer: &mut AudioBuffer, data: &[f32]) { - let num_chans = buffer.number_of_channels(); - - for i in 0..num_chans { - let chan = buffer.get_channel_data_mut(i); - for j in 0..chan.len() { - chan[j] = data[j]; - } - } -} - - - diff --git a/src-tauri/src/webaudio/mod.rs b/src-tauri/src/webaudio/mod.rs new file mode 100644 index 000000000..22a914c96 --- /dev/null +++ b/src-tauri/src/webaudio/mod.rs @@ -0,0 +1,5 @@ +mod superdough; +mod reverbgen; + +pub use self::superdough::*; +pub use self::reverbgen::*; \ No newline at end of file diff --git a/src-tauri/src/webaudio/reverbgen.rs b/src-tauri/src/webaudio/reverbgen.rs new file mode 100644 index 000000000..dd87ab976 --- /dev/null +++ b/src-tauri/src/webaudio/reverbgen.rs @@ -0,0 +1,241 @@ +use std::collections::HashMap; +use std::fs::File; +use mini_moka::sync::Cache; +use rand_distr::{Distribution, Normal}; +use web_audio_api::AudioBuffer; +use web_audio_api::context::{AudioContext, BaseAudioContext, OfflineAudioContext}; +use web_audio_api::node::{AudioNode, DynamicsCompressorNode}; +use crate::webaudio::{create_filepath_generic, download_file, Reverb, Sampler, WebAudioInstrument}; +use crate::webaudiobridge::WebAudioMessage; + +fn apply_decay(input: &mut [f32], decay_factor: f32) { + let len = input.len(); + + for i in 0..len { + // Multiply each sample by an exponentially decreasing factor + let decay_amt = (-(i as f32) / (len as f32) * decay_factor).exp(); + input[i] *= decay_amt; + } +} + +fn apply_fir_filter(input: &mut [f32], coefficients: &[f32]) { + let num_coeffs = coefficients.len(); + let len = input.len(); + + let input_copy = input.to_vec(); + + for i in num_coeffs..len { + let mut acc = 0.0; + for j in 0..num_coeffs { + acc += input_copy[i - j] * coefficients[j]; + } + input[i] = acc; + } +} + +fn sinc(x: f32, fc: f32) -> f32 { + let pi = std::f32::consts::PI; + if x == 0.0 { + 2.0 * fc + } else { + let arg = 2.0 * pi * fc * x; + arg.sin() / arg + } +} + +fn hamming(n: usize, size: usize) -> f32 { + 0.54 - 0.46 * ((2. * std::f32::consts::PI * n as f32) / ((size - 1) as f32)).cos() +} + +pub fn generate_coefficients_lp(size: usize, fc: f32) -> Vec { + let mut h = vec![0.0; size]; + let m = (size - 1) / 2; + + for n in 0..size { + h[n] = sinc((n as f32 - m as f32), fc); + h[n] *= hamming(n, size); + } + + let sum: f32 = h.iter().sum(); + for n in 0..size { + h[n] /= sum; + } + + h +} + +pub fn create_impulse_response( + len: usize, + decay_factor: f32, + fir_coeffs: &[f32], + sample_rate: f32, + fade: f32, +) -> Vec { + let mut rng = rand::thread_rng(); + let normal = Normal::new(0.0, 1.0).unwrap(); + let fade_in_samples = (fade * sample_rate) as usize; // Compute the number of samples for the fade-in duration + + let mut impulse_response: Vec = (0..len) + .map(|_| normal.sample(&mut rng) as f32) + .map(|v| v.max(-1.0).min(1.0)) // Clamp values to the -1.0 to 1.0 range + .collect(); + + // Apply fade-in to the starting samples + for i in 0..fade_in_samples.min(len) { + impulse_response[i] *= i as f32 / fade_in_samples as f32; + } + + apply_decay(&mut impulse_response, decay_factor); + apply_fir_filter(&mut impulse_response, fir_coeffs); + + impulse_response +} + +pub fn write_to_buffer(buffer: &mut AudioBuffer, data: &[f32]) { + let num_chans = buffer.number_of_channels(); + + for i in 0..num_chans { + let chan = buffer.get_channel_data_mut(i); + for j in 0..chan.len() { + chan[j] = data[j]; + } + } +} + +pub fn apply_reverb(mut audio_context: &AudioContext, compressor: &DynamicsCompressorNode, cache: &Cache, message: &WebAudioMessage, instrument: &mut T, reverbs: &mut HashMap) { + if message.reverb.ir.is_none() { + let key = format!("reverb: {}{}{}{}{}", message.reverb.room.unwrap_or(0.0), message.reverb.size.unwrap_or(1.0), message.reverb.roomlp.unwrap_or(0.0), message.reverb.roomfade.unwrap_or(0.0), message.reverb.roomdim.unwrap_or(0.0)); + if let Some(ir) = cache.get(&key) { + let reverb = reverbs.entry(message.orbit).or_insert({ + let mut reverb = Reverb::new(&audio_context, message.reverb.room, message.reverb.size, message.reverb.roomlp, message.reverb.roomfade, message.reverb.roomdim, &compressor); + reverb.convolver.set_buffer(ir.clone()); + instrument.connect_reverb(&reverb.convolver); + reverb + }); + + if reverb.room.unwrap_or(0.0) != message.reverb.room.unwrap_or(0.0) + || reverb.roomlp.unwrap_or(0.0) != message.reverb.roomlp.unwrap_or(0.0) + || reverb.roomfade.unwrap_or(0.0) != message.reverb.roomfade.unwrap_or(0.0) + || reverb.roomdim.unwrap_or(0.3) != message.reverb.roomdim.unwrap_or(0.0) + || reverb.roomsize.unwrap_or(0.0) != message.reverb.size.unwrap_or(0.0) + || reverb.roomfade.unwrap_or(0.0) != message.reverb.roomfade.unwrap_or(0.0) { + reverb.room = message.reverb.room; + reverb.roomlp = message.reverb.roomlp; + reverb.roomfade = message.reverb.roomfade; + reverb.roomdim = message.reverb.roomdim; + reverb.roomsize = message.reverb.size; + + let len = (audio_context.sample_rate() * message.reverb.size.unwrap_or(0.6)) as usize; + let decay = message.reverb.roomdim.unwrap_or(2.0); + let num_coeffs = 101; + let fade = message.reverb.roomfade.unwrap_or(0.01); + let cutoff = message.reverb.roomlp.unwrap_or(1000.0) / audio_context.sample_rate(); + let cache_cache = cache.clone(); + let mut buffer = audio_context.create_buffer(2, len, 44100.0); + tokio::spawn(async move { + // GENERATE IR + let fir = generate_coefficients_lp(num_coeffs, cutoff); + let ir = create_impulse_response(len, decay, &fir, 44100.0, fade); + write_to_buffer(&mut buffer, &ir); + cache_cache.insert(key, buffer); + }); + } + reverb.convolver.set_buffer(ir); + instrument.connect_reverb(&reverb.convolver); + + // Generate new IR buffer if it doesn't exist + } else { + let len = (audio_context.sample_rate() * message.reverb.size.unwrap_or(1.0)) as usize; + let decay = 4.0; + let num_coeffs = 101; + let fade = 2.4; + let cutoff = 500.0 / audio_context.sample_rate(); + let cache_cache = cache.clone(); + let mut buffer = audio_context.create_buffer(2, len, 44100.0); + tokio::spawn(async move { + // Generate IR and write to buffer + let fir = generate_coefficients_lp(num_coeffs, cutoff); + let ir = create_impulse_response(len, decay, &fir, 44100.0, fade); + write_to_buffer(&mut buffer, &ir); + cache_cache.insert(key, buffer); + }); + } + } else if message.reverb.ir.is_some() { + let message_url = &match message.reverb.url.clone() { + Some(url) => url, + None => "todo".to_string(), + }; + let (ir_url, ir_file_path, ir_file_path_clone) = create_filepath_generic(message_url, &message.dirname); + let ir_cache_clone = cache.clone(); + let requested_size = message.reverb.size.unwrap_or(2.0).clone(); + if let Some(ir_buffer) = cache.get(&ir_file_path_clone) { + let reverb = reverbs.entry(message.orbit).or_insert({ + let mut reverb = Reverb::new(&audio_context, message.reverb.room, message.reverb.size, message.reverb.roomlp, message.reverb.roomfade, message.reverb.roomdim, &compressor); + reverb.convolver.set_buffer(ir_buffer.clone()); + instrument.connect_reverb(&reverb.convolver); + reverb + }); + + if reverb.roomsize.unwrap_or(0.0) != message.reverb.size.unwrap_or(0.0) { + adjust_length(&mut audio_context, cache, message, ir_file_path_clone, requested_size, reverb); + } + + reverb.convolver.set_buffer(ir_buffer); + instrument.connect_reverb(&reverb.convolver); + } else { + tokio::spawn(async move { + if let Ok(file1) = File::open(&ir_file_path) { + create_new_ir_buffer_from_file(&ir_file_path, ir_cache_clone, requested_size, file1); + } else if tokio::fs::metadata(&ir_file_path.clone()).await.is_err() { + download_file(ir_url, &ir_file_path).await; + } + }); + } + } +} + +fn create_new_ir_buffer_from_file(ir_file_path: &String, ir_cache_clone: Cache, requested_size: f32, file1: File) { + let context = OfflineAudioContext::new(2, 400, 44100.0); + let ir_buffer = context.decode_audio_data_sync(file1).expect("Failed to decode file."); + let new_length = ir_buffer.sample_rate() * requested_size; + let mut new_buffer = context.create_buffer(ir_buffer.number_of_channels(), new_length as usize, ir_buffer.sample_rate()); + for ch in 0..ir_buffer.number_of_channels() { + let old_data = ir_buffer.get_channel_data(ch); + let new_data = new_buffer.get_channel_data_mut(ch); + + for i in 0..new_length as usize { + if i < old_data.len() { + new_data[i] = old_data[i]; + } else { + new_data[i] = 0.0; + } + } + } + ir_cache_clone.insert(ir_file_path.clone(), new_buffer); +} + +fn adjust_length(audio_context: &mut &AudioContext, cache: &Cache, message: &WebAudioMessage, ir_file_path_clone: String, requested_size: f32, reverb: &mut Reverb) { + let buf = reverb.convolver.buffer().unwrap(); + reverb.roomsize = message.reverb.size; + let new_length = buf.sample_rate() * requested_size; + let mut new_buffer = audio_context.create_buffer(buf.number_of_channels(), new_length as usize, buf.sample_rate()); + + for ch in 0..buf.number_of_channels() { + let old_data = buf.get_channel_data(ch); + let new_data = new_buffer.get_channel_data_mut(ch); + + for i in 0..new_length as usize { + if i < old_data.len() { + new_data[i] = old_data[i]; + } else { + new_data[i] = 0.0; + } + } + } + + reverb.convolver.set_buffer(new_buffer.clone()); + cache.insert(ir_file_path_clone.clone(), new_buffer); +} + + + diff --git a/src-tauri/src/superdough.rs b/src-tauri/src/webaudio/superdough.rs similarity index 76% rename from src-tauri/src/superdough.rs rename to src-tauri/src/webaudio/superdough.rs index 86bef3798..4506072e3 100644 --- a/src-tauri/src/superdough.rs +++ b/src-tauri/src/webaudio/superdough.rs @@ -1,5 +1,8 @@ use std::collections::HashMap; -use std::sync::{Arc, Mutex}; +use std::path::Path; +use reqwest::Url; +use tokio::fs; +use tokio::io::AsyncWriteExt; use web_audio_api::{ context::{AudioContext, BaseAudioContext}, AudioBuffer, @@ -74,6 +77,8 @@ pub trait WebAudioInstrument { fn set_adsr(&mut self, t: f64, adsr: &ADSRMessage, 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; + fn connect_delay(&mut self, delay: &DelayNode); + fn connect_reverb(&mut self, reverb: &ConvolverNode); } @@ -114,7 +119,6 @@ pub struct Reverb { impl Reverb { pub(crate) fn new(context: &AudioContext, room: Option, roomsize: Option, roomfade: Option, roomlp: Option, roomdim: Option, compressor: &DynamicsCompressorNode) -> Self { let mut convolver = context.create_convolver(); - // convolver.set_buffer(ir.clone()); convolver.connect(compressor); Self { convolver, room, roomsize, roomfade, roomlp, roomdim } } @@ -198,6 +202,14 @@ impl WebAudioInstrument for Synth { filters } + + fn connect_delay(&mut self, delay: &DelayNode) { + self.envelope.connect(delay); + } + + fn connect_reverb(&mut self, reverb: &ConvolverNode) { + self.envelope.connect(reverb); + } } pub struct Sampler { @@ -283,6 +295,18 @@ impl WebAudioInstrument for Sampler { }; filters } + + fn connect_delay(&mut self, delay: &DelayNode) { + self.envelope.connect(delay); + } + + fn connect_reverb(&mut self, reverb: &ConvolverNode) { + self.envelope.connect(reverb); + } +} + +pub fn is_filter_envelope_on(adsr: &FilterADSRMessage) -> bool { + adsr.env.is_some() || adsr.attack.is_some() || adsr.decay.is_some() || adsr.sustain.is_some() || adsr.release.is_some() } pub fn apply_filter_adsr(filter_node: &BiquadFilterNode, message: &WebAudioMessage, filter: &BiquadFilterType, now: f64) { @@ -313,3 +337,57 @@ pub fn apply_filter_adsr(filter_node: &BiquadFilterNode, message: &WebAudioMessa // .set_value_at_time(sustain_level, now + message.duration) .linear_ramp_to_value_at_time(min, now + message.duration + env.release.unwrap_or(0.01)); } + +pub fn apply_delay(audio_context: &mut AudioContext, delays: &mut HashMap, compressor: &DynamicsCompressorNode, message: &WebAudioMessage, t: f64, instrument: &mut T) { + delays.entry(message.orbit).or_insert({ + Delay::new(&audio_context, &compressor) + }); + let delay = delays.get(&message.orbit).unwrap(); + instrument.connect_delay(&delay.delay); + delay.delay.delay_time().set_value(message.delay.delay_time.unwrap_or(0.25)); + delay.feedback.gain().set_value(message.delay.feedback.unwrap_or(0.5)); + delay.pre_gain.gain().set_value_at_time(message.delay.wet.unwrap_or(0.25), t + message.delay.delay_time.unwrap_or(0.5) as f64); +} + +pub fn apply_delay_synth(audio_context: &mut AudioContext, delays: &mut HashMap, compressor: &DynamicsCompressorNode, message: &WebAudioMessage, t: f64, synth: &mut Synth) { + delays.entry(message.orbit).or_insert({ + Delay::new(&audio_context, &compressor) + }); + let delay = delays.get(&message.orbit).unwrap(); + synth.envelope.connect(&delay.input); + delay.delay.delay_time().set_value(message.delay.delay_time.unwrap_or(0.25)); + delay.feedback.gain().set_value(message.delay.feedback.unwrap_or(0.5)); + delay.pre_gain.gain().set_value_at_time(message.delay.wet.unwrap_or(0.25), t + message.delay.delay_time.unwrap_or(0.5) as f64); +} + +pub async fn download_file(ir_url: Url, ir_file_path: &String) { + let response = reqwest::get(ir_url) + .await + .unwrap_or_else(|_| panic!("Failed to send GET request")); + let bytes = response.bytes().await.unwrap(); + let path = Path::new(&ir_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")); +} + +pub fn create_filepath_generic(url_string: &str, dirname: &str) -> (Url, String, String) { + let url = Url::parse(url_string).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/{}{}", dirname, filename); + let file_path_clone = file_path.clone(); + + (url, file_path, file_path_clone) +} + +pub async fn create_file_and_dirs(path: &Path) -> fs::File { + if let Some(parent) = path.parent() { + fs::create_dir_all(parent).await.unwrap(); + } + let file = fs::File::create(path).await.unwrap(); + file +} \ No newline at end of file diff --git a/src-tauri/src/webaudiobridge.rs b/src-tauri/src/webaudiobridge.rs index ff1f4ad5a..f9d14975e 100644 --- a/src-tauri/src/webaudiobridge.rs +++ b/src-tauri/src/webaudiobridge.rs @@ -7,9 +7,7 @@ use std::{ }; use std::collections::HashMap; use mini_moka::sync::Cache; -use reqwest::Url; use tokio::{ - fs, sync::{mpsc, Mutex}, io::AsyncWriteExt, }; @@ -21,9 +19,7 @@ use web_audio_api::{ node::AudioNode, }; use web_audio_api::context::OfflineAudioContext; -use web_audio_api::node::{DynamicsCompressorNode}; -use crate::reverbgen::{create_impulse_response, generate_coefficients_lp, write_to_buffer}; -use crate::superdough::{ADSRMessage, apply_filter_adsr, BPFMessage, DelayMessage, Delay, FilterADSRMessage, HPFMessage, LoopMessage, LPFMessage, Sampler, Synth, WebAudioInstrument, ReverbMessage, Reverb}; +use crate::webaudio::*; #[derive(Debug, Clone)] pub struct WebAudioMessage { @@ -145,14 +141,10 @@ pub fn init( synth.set_waveform(&message.waveform); let filters = synth.set_filters(&mut audio_context, &message); if message.delay.wet.is_some() { - delays.entry(message.orbit).or_insert({ - Delay::new(&audio_context, &compressor) - }); - let delay = delays.get(&message.orbit).unwrap(); - synth.envelope.connect(&delay.input); - delay.delay.delay_time().set_value(message.delay.delay_time.unwrap_or(0.25)); - delay.feedback.gain().set_value(message.delay.feedback.unwrap_or(0.5)); - delay.pre_gain.gain().set_value_at_time(message.delay.wet.unwrap_or(0.25), t + message.delay.delay_time.unwrap_or(0.5) as f64); + apply_delay(&mut audio_context, &mut delays, &compressor, &message, t, &mut synth); + } + if message.reverb.room.is_some() && message.reverb.room.unwrap() > 0.0 { + apply_reverb(&audio_context, &compressor, &ir_cache, &message, &mut synth, &mut reverbs); } synth.envelope.connect(&compressor); synth.play(t, &message, message.adsr.release.unwrap_or(0.001)); @@ -165,10 +157,12 @@ pub fn init( } _ => { let t = audio_context.current_time(); - // CREATE FILEPATHS - let (url, file_path, file_path_clone) = create_filepath(&message); - // DOWNLOAD FILE IS IT DOESN'T EXIST + // Create filepaths + let message_url = &message.sampleurl.clone(); + let (url, file_path, file_path_clone) = create_filepath_generic(message_url, &message.dirname); + + // Download file if it doesn't exist tokio::spawn(async move { if tokio::fs::metadata(&file_path.clone()).await.is_err() { let response = reqwest::get(url) @@ -184,12 +178,12 @@ pub fn init( } }); - // IF BUFFER IS CACHED - PLAY IT + // Play the buffer, if exists if let Some(audio_buffer) = cache.get(&file_path_clone) { let audio_buffer_duration = audio_buffer.duration(); let mut sampler = Sampler::new(&mut audio_context, audio_buffer); - // PLAYBACK SPEED + // Playback speed match message.unit { Some(_) => { sampler.sample.playback_rate().set_value(message.speed * audio_buffer_duration as f32 * 1.0); @@ -202,7 +196,7 @@ pub fn init( // ADSR sampler.set_adsr(t, &message.adsr, message.velocity, message.duration); - // FILTERS + // Filters let filters = sampler.set_filters(&mut audio_context, &message); if is_filter_envelope_on(&message.lpenv) || is_filter_envelope_on(&message.hpenv) || is_filter_envelope_on(&message.bpenv) { for f in &filters { @@ -210,25 +204,26 @@ pub fn init( } } - // DELAY + // Delay if message.delay.wet.is_some() { apply_delay(&mut audio_context, &mut delays, &compressor, &message, t, &mut sampler); }; + // Reverb if message.reverb.room.is_some() && message.reverb.room.unwrap() > 0.0 { apply_reverb(&audio_context, &compressor, &ir_cache, &message, &mut sampler, &mut reverbs); } - // CONNECT SAMPLER TO OUTPUT AND PLAY + // Connect to output sampler.envelope.connect(&compressor); sampler.play(t, &message, message.adsr.release.unwrap_or(0.001)); - // IF FILE EXIST - DECODE IT + // Decode file, if it exists } else if let Ok(file) = File::open(&file_path_clone) { let cache_clone = cache.clone(); tokio::spawn(async move { let context = OfflineAudioContext::new(2, 88200, 44100.0); - let audio_buffer = context.decode_audio_data_sync(file).expect("OOOPS!"); + let audio_buffer = context.decode_audio_data_sync(file).expect("Failed to decode file"); cache_clone.insert(file_path_clone.clone(), audio_buffer); }); } @@ -241,200 +236,6 @@ pub fn init( }); } -fn apply_delay(audio_context: &mut AudioContext, delays: &mut HashMap, compressor: &DynamicsCompressorNode, message: &WebAudioMessage, t: f64, mut sampler: &mut Sampler) { - delays.entry(message.orbit).or_insert({ - Delay::new(&audio_context, &compressor) - }); - let delay = delays.get(&message.orbit).unwrap(); - sampler.envelope.connect(&delay.input); - delay.delay.delay_time().set_value(message.delay.delay_time.unwrap_or(0.25)); - delay.feedback.gain().set_value(message.delay.feedback.unwrap_or(0.5)); - delay.pre_gain.gain().set_value_at_time(message.delay.wet.unwrap_or(0.25), t + message.delay.delay_time.unwrap_or(0.5) as f64); -} - -fn apply_reverb(mut audio_context: &AudioContext, compressor: &DynamicsCompressorNode, cache: &Cache, message: &WebAudioMessage, mut sampler: &mut Sampler, reverbs: &mut HashMap) { - if message.reverb.ir.is_none() { - let key = format!("reverb: {}{}{}{}{}", message.reverb.room.unwrap_or(0.0), message.reverb.size.unwrap_or(1.0), message.reverb.roomlp.unwrap_or(0.0), message.reverb.roomfade.unwrap_or(0.0), message.reverb.roomdim.unwrap_or(0.0)); - if let Some(ir) = cache.get(&key) { - let reverb = reverbs.entry(message.orbit).or_insert({ - let mut reverb = Reverb::new(&audio_context, message.reverb.room, message.reverb.size, message.reverb.roomlp, message.reverb.roomfade, message.reverb.roomdim, &compressor); - reverb.convolver.set_buffer(ir.clone()); - sampler.envelope.connect(&reverb.convolver); - reverb - }); - - if reverb.room.unwrap_or(0.0) != message.reverb.room.unwrap_or(0.0) - || reverb.roomlp.unwrap_or(0.0) != message.reverb.roomlp.unwrap_or(0.0) - || reverb.roomfade.unwrap_or(0.0) != message.reverb.roomfade.unwrap_or(0.0) - || reverb.roomdim.unwrap_or(0.3) != message.reverb.roomdim.unwrap_or(0.0) - || reverb.roomsize.unwrap_or(0.0) != message.reverb.size.unwrap_or(0.0) - || reverb.roomfade.unwrap_or(0.0) != message.reverb.roomfade.unwrap_or(0.0) { - reverb.room = message.reverb.room; - reverb.roomlp = message.reverb.roomlp; - reverb.roomfade = message.reverb.roomfade; - reverb.roomdim = message.reverb.roomdim; - reverb.roomsize = message.reverb.size; - - let len = (audio_context.sample_rate() * message.reverb.size.unwrap_or(1.0)) as usize; - let decay = 4.0; - let num_coeffs = 101; - let fade = 2.4; - let cutoff = 500.0 / audio_context.sample_rate(); - let cache_cache = cache.clone(); - let mut buffer = audio_context.create_buffer(2, len, 44100.0); - tokio::spawn(async move { - // GENERATE IR - let fir = generate_coefficients_lp(num_coeffs, cutoff); - let ir = create_impulse_response(len, decay, &fir, 44100.0, fade); - write_to_buffer(&mut buffer, &ir); - cache_cache.insert(key, buffer); - }); - } - reverb.convolver.set_buffer(ir); - sampler.envelope.connect(&reverb.convolver); - - // IF BUFFER IS NOT CACHED - GENERATE NEW BUFFER - } else { - let len = (audio_context.sample_rate() * message.reverb.size.unwrap_or(1.0)) as usize; - let decay = 4.0; - let num_coeffs = 101; - let fade = 2.4; - let cutoff = 500.0 / audio_context.sample_rate(); - let cache_cache = cache.clone(); - let mut buffer = audio_context.create_buffer(2, len, 44100.0); - tokio::spawn(async move { - // GENERATE IR - let fir = generate_coefficients_lp(num_coeffs, cutoff); - let ir = create_impulse_response(len, decay, &fir, 44100.0, fade); - write_to_buffer(&mut buffer, &ir); - cache_cache.insert(key, buffer); - }); - } - } else if message.reverb.ir.is_some() { - let (ir_url, ir_file_path, ir_file_path_clone) = create_ir_filepath(&message); - let ir_cache_clone = cache.clone(); - let requested_size = message.reverb.size.unwrap_or(2.0).clone(); - if let Some(ir_buffer) = cache.get(&ir_file_path_clone) { - let reverb = reverbs.entry(message.orbit).or_insert({ - let mut reverb = Reverb::new(&audio_context, message.reverb.room, message.reverb.size, message.reverb.roomlp, message.reverb.roomfade, message.reverb.roomdim, &compressor); - reverb.convolver.set_buffer(ir_buffer.clone()); - sampler.envelope.connect(&reverb.convolver); - reverb - }); - - if reverb.roomsize.unwrap_or(0.0) != message.reverb.size.unwrap_or(0.0) { - adjust_length(&mut audio_context, cache, message, ir_file_path_clone, requested_size, reverb); - } - - reverb.convolver.set_buffer(ir_buffer); - sampler.envelope.connect(&reverb.convolver); - } else { - tokio::spawn(async move { - if let Ok(file1) = File::open(&ir_file_path) { - create_new_ir_buffer_from_file(&ir_file_path, ir_cache_clone, requested_size, file1); - } else if tokio::fs::metadata(&ir_file_path.clone()).await.is_err() { - download_file(ir_url, &ir_file_path).await; - } - }); - } - } -} - -async fn download_file(ir_url: Url, ir_file_path: &String) { - let response = reqwest::get(ir_url) - .await - .unwrap_or_else(|_| panic!("Failed to send GET request")); - let bytes = response.bytes().await.unwrap(); - let path = Path::new(&ir_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")); -} - -fn create_new_ir_buffer_from_file(ir_file_path: &String, ir_cache_clone: Cache, requested_size: f32, file1: File) { - let context = OfflineAudioContext::new(2, 400, 44100.0); - let ir_buffer = context.decode_audio_data_sync(file1).expect("IR OOOOPS"); - let new_length = ir_buffer.sample_rate() * requested_size; - let mut new_buffer = context.create_buffer(ir_buffer.number_of_channels(), new_length as usize, ir_buffer.sample_rate()); - for ch in 0..ir_buffer.number_of_channels() { - let old_data = ir_buffer.get_channel_data(ch); - let new_data = new_buffer.get_channel_data_mut(ch); - - for i in 0..new_length as usize { - if i < old_data.len() { - new_data[i] = old_data[i]; - } else { - new_data[i] = 0.0; - } - } - } - ir_cache_clone.insert(ir_file_path.clone(), new_buffer); -} - -fn adjust_length(audio_context: &mut &AudioContext, cache: &Cache, message: &WebAudioMessage, ir_file_path_clone: String, requested_size: f32, reverb: &mut Reverb) { - let buf = reverb.convolver.buffer().unwrap(); - reverb.roomsize = message.reverb.size; - let new_length = buf.sample_rate() * requested_size; - let mut new_buffer = audio_context.create_buffer(buf.number_of_channels(), new_length as usize, buf.sample_rate()); - - for ch in 0..buf.number_of_channels() { - let old_data = buf.get_channel_data(ch); - let new_data = new_buffer.get_channel_data_mut(ch); - - for i in 0..new_length as usize { - if i < old_data.len() { - new_data[i] = old_data[i]; - } else { - new_data[i] = 0.0; - } - } - } - - reverb.convolver.set_buffer(new_buffer.clone()); - cache.insert(ir_file_path_clone.clone(), new_buffer); -} - - -async fn generate_buffer_and_update_cache(audio_context: &AudioContext, size: f32, key: String, cache: Cache) { - let len = (audio_context.sample_rate() * size) as usize; - let decay = 4.0; - let num_coeffs = 101; - let fade = 2.4; - let cutoff = 500.0 / audio_context.sample_rate(); - - let mut buffer = audio_context.create_buffer(2, len, 44100.0); - - let fir = generate_coefficients_lp(num_coeffs, cutoff); - let ir = create_impulse_response(len, decay, &fir, 44100.0, fade); - write_to_buffer(&mut buffer, &ir); - - cache.insert(key, buffer); -} - - -fn create_filepath(message: &WebAudioMessage) -> (Url, String, String) { - 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(); - (url, file_path, file_path_clone) -} - -fn create_ir_filepath(message: &WebAudioMessage) -> (Url, String, String) { - let url = Url::parse(&message.reverb.url.clone().unwrap()).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(); - (url, file_path, file_path_clone) -} - pub async fn async_process_model( mut input_receiver: mpsc::Receiver>, output_transmitter: mpsc::Sender>, @@ -565,14 +366,3 @@ pub async fn sendwebaudio( async_proc_input_tx.send(messages_to_process).await.map_err(|e| e.to_string()) } -async fn create_file_and_dirs(path: &Path) -> fs::File { - if let Some(parent) = path.parent() { - fs::create_dir_all(parent).await.unwrap(); - } - let file = fs::File::create(path).await.unwrap(); - file -} - -fn is_filter_envelope_on(adsr: &FilterADSRMessage) -> bool { - adsr.env.is_some() || adsr.attack.is_some() || adsr.decay.is_some() || adsr.sustain.is_some() || adsr.release.is_some() -} \ No newline at end of file