diff --git a/src-tauri/src/superdough.rs b/src-tauri/src/superdough.rs index 86bef3798..70c56b9c9 100644 --- a/src-tauri/src/superdough.rs +++ b/src-tauri/src/superdough.rs @@ -120,6 +120,69 @@ impl Reverb { } } +pub struct ReverbGen { + pub generated_ir: Option, + pub sample_ir: Option, +} + +pub struct SampleIR { + pub buffer: AudioBuffer, + pub convolver: ConvolverNode, + pub room: Option, + pub roomsize: Option, +} + +pub struct GeneratedIR { + pub buffer: AudioBuffer, + pub convolver: ConvolverNode, + pub room: Option, + pub roomsize: Option, + pub roomfade: Option, + pub roomlp: Option, + pub roomdim: Option, +} + +impl GeneratedIR { + pub fn new(context: &AudioContext, room: Option, roomsize: Option, roomfade: Option, roomlp: Option, roomdim: Option, compressor: &DynamicsCompressorNode) -> Self { + let mut convolver = context.create_convolver(); + let buffer = context.create_buffer(2, 44100, 44100.0); + convolver.set_buffer(buffer.clone()); + convolver.connect(compressor); + Self { buffer, convolver, room, roomsize, roomfade, roomlp, roomdim } + } +} + +pub trait IR { + fn set_ir(&mut self, context: &AudioContext, ir: &AudioBuffer); + + fn set_params(&mut self, room: Option, roomsize: Option, roomfade: Option, roomlp: Option, roomdim: Option); + + fn set_convolver(&mut self, context: &AudioContext, compressor: &DynamicsCompressorNode); + + fn set_convolver_buffer(&mut self, context: &AudioContext, compressor: &DynamicsCompressorNode, ir: &AudioBuffer); +} + +impl IR for SampleIR { + fn set_ir(&mut self, context: &AudioContext, ir: &AudioBuffer) { + self.buffer = ir.clone(); + self.convolver.set_buffer(ir.clone()); + } + + fn set_params(&mut self, room: Option, roomsize: Option, roomfade: Option, roomlp: Option, roomdim: Option) { + self.room = room; + self.roomsize = roomsize; + } + + fn set_convolver(&mut self, context: &AudioContext, compressor: &DynamicsCompressorNode) { + self.convolver.connect(compressor); + } + + fn set_convolver_buffer(&mut self, context: &AudioContext, compressor: &DynamicsCompressorNode, ir: &AudioBuffer) { + self.convolver.set_buffer(ir.clone()); + self.convolver.connect(compressor); + } +} + pub struct Synth { pub oscillator: OscillatorNode, pub envelope: GainNode, diff --git a/src-tauri/src/webaudiobridge.rs b/src-tauri/src/webaudiobridge.rs index 49d92bfd5..48a924dcd 100644 --- a/src-tauri/src/webaudiobridge.rs +++ b/src-tauri/src/webaudiobridge.rs @@ -165,7 +165,6 @@ pub fn init( } } _ => { - let t = audio_context.current_time(); // CREATE FILEPATHS let (url, file_path, file_path_clone) = create_filepath(&message); @@ -214,14 +213,7 @@ pub fn init( // DELAY if message.delay.wet.is_some() { - 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); + apply_delay(&mut audio_context, &mut delays, &compressor, &message, t, &mut sampler); }; if message.reverb.room.is_some() && message.reverb.room.unwrap() > 0.0 { @@ -250,19 +242,26 @@ 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) { - // IR GENERATION if message.reverb.ir.is_none() { - // CACHE KEY 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)); - // IS BUFFER CACHED? if let Some(ir) = cache.get(&key) { - // IF REVERB STRUCT IS IN HASHMAP - PLAY IT, ELSE CREATE IT 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()); @@ -270,7 +269,6 @@ fn apply_reverb(mut audio_context: &AudioContext, compressor: &DynamicsCompresso reverb }); - // IF BUFFER IS CACHED BUT PARAMETERS HAS CHANGED - GENERATE NEW BUFFER 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) @@ -319,32 +317,10 @@ fn apply_reverb(mut audio_context: &AudioContext, compressor: &DynamicsCompresso }); } } else if message.reverb.ir.is_some() { - - // IR FROM SAMPLES let (ir_url, ir_file_path, ir_file_path_clone) = create_ir_filepath(&message); - - // DOWNLOAD IR IF IT DOESN'T EXIST - tokio::spawn(async move { - if tokio::fs::metadata(&ir_file_path.clone()).await.is_err() { - 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")); - } - }); - + let ir_cache_clone = cache.clone(); let requested_size = message.reverb.size.unwrap_or(2.0).clone(); - - // IF BUFFER IS CACHED - PLAY IT if let Some(ir_buffer) = cache.get(&ir_file_path_clone) { - - // IF REVERB STRUCT IS IN HASHMAP - PLAY IT, ELSE CREATE IT 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()); @@ -352,59 +328,97 @@ fn apply_reverb(mut audio_context: &AudioContext, compressor: &DynamicsCompresso reverb }); - // IF BUFFER IS CACHED BUT PARAMETERS HAS CHANGED - GENERATE NEW BUFFER if reverb.roomsize.unwrap_or(0.0) != message.reverb.size.unwrap_or(0.0) { - 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); + 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); - - // IF FILE IS DOWNLOADED BUT NOT CACHED - DECODE IT - } else if let Ok(file) = File::open(&ir_file_path_clone) { - let ir_cache_clone = cache.clone(); + } else { tokio::spawn(async move { - let context = OfflineAudioContext::new(2, 400, 44100.0); - let ir_buffer = context.decode_audio_data_sync(file).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; - } - } + 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; } - ir_cache_clone.insert(ir_file_path_clone.clone(), new_buffer); }); } } } +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()