mirror of
https://codeberg.org/uzu/strudel
synced 2026-07-21 20:55:12 -04:00
tests: reverb
This commit is contained in:
Generated
+20
@@ -109,9 +109,12 @@ checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8"
|
||||
name = "app"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"hound",
|
||||
"midir",
|
||||
"mini-moka",
|
||||
"quick_cache",
|
||||
"rand 0.8.5",
|
||||
"rand_distr",
|
||||
"reqwest",
|
||||
"rosc",
|
||||
"serde",
|
||||
@@ -1848,6 +1851,12 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libm"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4"
|
||||
|
||||
[[package]]
|
||||
name = "line-wrap"
|
||||
version = "0.1.1"
|
||||
@@ -2192,6 +2201,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"libm",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2724,6 +2734,16 @@ dependencies = [
|
||||
"getrandom 0.2.10",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_distr"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
"rand 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_hc"
|
||||
version = "0.2.0"
|
||||
|
||||
@@ -25,6 +25,9 @@ web-audio-api = { git = "https://github.com/orottier/web-audio-api-rs.git", bran
|
||||
reqwest = "0.11.20"
|
||||
quick_cache = "0.4.0"
|
||||
mini-moka = "0.10.2"
|
||||
hound = "3.4.0"
|
||||
rand = "0.8.5"
|
||||
rand_distr = "0.4.3"
|
||||
|
||||
[features]
|
||||
# this feature is used for production builds or when `devPath` points to the filesystem and the built-in dev server is disabled.
|
||||
@@ -35,5 +38,5 @@ custom-protocol = ["tauri/custom-protocol"]
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
codegen-units = 1
|
||||
lto = true
|
||||
lto = "thin"
|
||||
opt-level = "s"
|
||||
|
||||
+100
-17
@@ -1,22 +1,105 @@
|
||||
use web_audio_api::AudioBuffer;
|
||||
use web_audio_api::context::{AudioContext, BaseAudioContext};
|
||||
use rand_distr::{Distribution, Normal};
|
||||
|
||||
pub fn generate_reverb(context: &AudioContext) {
|
||||
let sample_rate = 44100.0;
|
||||
let num_channels = 2.0;
|
||||
let total_time = 2.0 * 1.5;
|
||||
let decay_samples = (2.0_f64 * sample_rate).round() as usize;
|
||||
let num_samples = (total_time * sample_rate).round() as usize;
|
||||
let fade_samples = 0.0;
|
||||
let decay_base = (1.0_f64 / 1000.0_f64).powf(1.0 / decay_samples as f64) as f32;
|
||||
let mut reverb_ir = context.create_buffer(2, num_samples, 44100.0);
|
||||
fn apply_decay(input: &mut [f32], decay_factor: f32) {
|
||||
let len = input.len();
|
||||
|
||||
for i in 0..num_channels as usize {
|
||||
let mut chan = reverb_ir.get_channel_data_mut(i);
|
||||
for j in 0..num_samples {
|
||||
chan[j] = -0.3 * decay_base.powf(j as f32);
|
||||
}
|
||||
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;
|
||||
}
|
||||
println!("{:?}", reverb_ir);
|
||||
}
|
||||
|
||||
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<f32> {
|
||||
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<f32> {
|
||||
let mut rng = rand::thread_rng();
|
||||
let mut 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<f32> = (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_wav(filename: &str, data: &[f32]) {
|
||||
let spec = hound::WavSpec {
|
||||
channels: 1,
|
||||
sample_rate: 44100,
|
||||
bits_per_sample: 16,
|
||||
sample_format: hound::SampleFormat::Int,
|
||||
};
|
||||
|
||||
let mut writer = hound::WavWriter::create(filename, spec).unwrap();
|
||||
for &sample_float in data {
|
||||
// Assuming 16-bit audio
|
||||
let amplitude_scale = i16::MAX as f32;
|
||||
let sample = (sample_float * amplitude_scale).round() as i16;
|
||||
writer.write_sample(sample).unwrap();
|
||||
}
|
||||
writer.finalize().unwrap();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -7,7 +7,6 @@ use web_audio_api::{
|
||||
node::BiquadFilterType::{Bandpass, Highpass, Lowpass},
|
||||
};
|
||||
use web_audio_api::node::{ConvolverNode, DelayNode, DynamicsCompressorNode};
|
||||
use crate::reverbgen::generate_reverb;
|
||||
use crate::webaudiobridge::WebAudioMessage;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
|
||||
@@ -22,6 +22,7 @@ use web_audio_api::{
|
||||
};
|
||||
use web_audio_api::context::OfflineAudioContext;
|
||||
use web_audio_api::node::{ConvolverNode, ConvolverOptions, DelayNode};
|
||||
use crate::reverbgen::{create_impulse_response, generate_coefficients_lp, write_to_wav};
|
||||
use crate::superdough::{ADSRMessage, apply_filter_adsr, BPFMessage, DelayMessage, Delay, FilterADSRMessage, HPFMessage, LoopMessage, LPFMessage, Sampler, Synth, WebAudioInstrument, ReverbMessage, Reverb};
|
||||
|
||||
|
||||
@@ -228,6 +229,31 @@ pub fn init(
|
||||
};
|
||||
|
||||
// REVERB
|
||||
if message.reverb.room.is_some() {
|
||||
let file_path = "/Users/vasiliymilovidov/samples/ir3.wav".to_string();
|
||||
if let Some(ir) = cache.get(&file_path) {
|
||||
let mut reverb = audio_context.create_convolver();
|
||||
reverb.set_buffer(ir);
|
||||
reverb.connect(&compressor);
|
||||
sampler.envelope.connect(&reverb);
|
||||
} else {
|
||||
let len = (audio_context.sample_rate() * message.reverb.size.unwrap_or(0.5)) as usize;
|
||||
let decay = 5.0;
|
||||
let num_coeffs = 101;
|
||||
let fade = 0.1;
|
||||
let cutoff = 4000.0 / audio_context.sample_rate();
|
||||
let cache_cache = cache.clone();
|
||||
tokio::spawn(async move {
|
||||
let fir = generate_coefficients_lp(num_coeffs, cutoff);
|
||||
let ir = create_impulse_response(len, decay, &fir, 44100.0, fade);
|
||||
write_to_wav("/Users/vasiliymilovidov/samples/ir3.wav", &ir);
|
||||
let context = OfflineAudioContext::new(2, 400, 44100.0);
|
||||
let file = File::open("/Users/vasiliymilovidov/samples/ir3.wav").expect("File booom!");
|
||||
let buf = context.decode_audio_data_sync(file).unwrap();
|
||||
cache_cache.insert("/Users/vasiliymilovidov/samples/ir3.wav".to_string(), buf);
|
||||
});
|
||||
}
|
||||
}
|
||||
if message.reverb.ir.is_some() {
|
||||
let (ir_url, ir_file_path, ir_file_path_clone) = create_ir_filepath(&message);
|
||||
|
||||
@@ -248,8 +274,6 @@ pub fn init(
|
||||
|
||||
let requested_size = message.reverb.size.unwrap_or(2.0).clone();
|
||||
if let Some(ir_buffer) = ir_cache.get(&ir_file_path_clone) {
|
||||
let t = audio_context.current_time();
|
||||
let ir_buffer_duration = ir_buffer.duration();
|
||||
let mut reverb = audio_context.create_convolver();
|
||||
reverb.set_buffer(ir_buffer);
|
||||
reverb.connect(&compressor);
|
||||
@@ -278,9 +302,9 @@ pub fn init(
|
||||
}
|
||||
}
|
||||
|
||||
// CONNECT SAMPLER TO OUTPUT AND PLAY
|
||||
sampler.envelope.connect(&compressor);
|
||||
sampler.play(t, &message, message.adsr.release.unwrap_or(0.001));
|
||||
// CONNECT SAMPLER TO OUTPUT AND PLAY
|
||||
// IF FILE EXIST - DECODE IT
|
||||
} else if let Ok(file) = File::open(&file_path_clone) {
|
||||
let cache_clone = cache.clone();
|
||||
|
||||
Reference in New Issue
Block a user