tests: reverb

This commit is contained in:
Vasilii Milovidov
2023-10-04 01:30:20 +04:00
parent 56d992def9
commit f6fc55b43e
5 changed files with 151 additions and 22 deletions
+20
View File
@@ -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"
+4 -1
View File
@@ -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
View File
@@ -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();
}
-1
View File
@@ -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)]
+27 -3
View File
@@ -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();