aether_timbre/
transfer.rs1use rustfft::{FftPlanner, num_complex::Complex};
4use crate::analysis::SpectralEnvelope;
5
6pub struct TimbreTransfer {
8 fft_size: usize,
9 hop_size: usize,
10 target_envelope: Option<SpectralEnvelope>,
12 pub amount: f32,
14 input_buffer: Vec<f32>,
16 output_buffer: Vec<f32>,
18 window: Vec<f32>,
20 planner: FftPlanner<f32>,
21}
22
23impl TimbreTransfer {
24 pub fn new(fft_size: usize) -> Self {
25 let fft_size = fft_size.next_power_of_two();
26 let hop_size = fft_size / 4;
27 let window: Vec<f32> = (0..fft_size)
28 .map(|i| 0.5 * (1.0 - (2.0 * std::f32::consts::PI * i as f32 / (fft_size - 1) as f32).cos()))
29 .collect();
30 Self {
31 fft_size,
32 hop_size,
33 target_envelope: None,
34 amount: 1.0,
35 input_buffer: vec![0.0; fft_size * 2],
36 output_buffer: vec![0.0; fft_size * 2],
37 window,
38 planner: FftPlanner::new(),
39 }
40 }
41
42 pub fn set_target(&mut self, envelope: SpectralEnvelope) {
44 self.target_envelope = Some(envelope);
45 }
46
47 pub fn clear_target(&mut self) {
49 self.target_envelope = None;
50 }
51
52 pub fn process_block(&mut self, input: &[f32]) -> Vec<f32> {
55 if self.target_envelope.is_none() || self.amount < 0.001 {
56 return input.to_vec();
57 }
58
59 let target = self.target_envelope.as_ref().unwrap();
60 let fft = self.planner.plan_fft_forward(self.fft_size);
61 let ifft = self.planner.plan_fft_inverse(self.fft_size);
62
63 let mut output = vec![0.0f32; input.len()];
64
65 let mut pos = 0;
68 while pos + self.fft_size <= input.len() {
69 let mut buf: Vec<Complex<f32>> = input[pos..pos + self.fft_size]
71 .iter()
72 .zip(self.window.iter())
73 .map(|(&s, &w)| Complex::new(s * w, 0.0))
74 .collect();
75
76 fft.process(&mut buf);
78
79 let n_bins = self.fft_size / 2 + 1;
81 let mut source_env = vec![0.0f32; n_bins];
82 for i in 0..n_bins {
83 source_env[i] = buf[i].norm().max(1e-10);
84 }
85 let smoothed_source = smooth(&source_env, 4);
86
87 for i in 0..n_bins {
88 let src_mag = smoothed_source[i];
89 let tgt_mag = target.magnitudes.get(i).copied().unwrap_or(1.0).max(1e-10);
90 let ratio = (tgt_mag / src_mag).powf(self.amount);
91 buf[i] = buf[i] * ratio;
93 if i > 0 && i < self.fft_size - i {
94 buf[self.fft_size - i] = buf[self.fft_size - i] * ratio;
95 }
96 }
97
98 ifft.process(&mut buf);
100
101 let norm = 1.0 / self.fft_size as f32;
103 for (j, s) in buf.iter().enumerate().take(self.fft_size) {
104 if pos + j < output.len() {
105 let dry = input[pos + j];
106 let wet = s.re * norm;
107 output[pos + j] = dry * (1.0 - self.amount) + wet * self.amount;
108 }
109 }
110
111 pos += self.hop_size;
112 }
113
114 for i in pos..input.len() {
116 output[i] = input[i];
117 }
118
119 output
120 }
121}
122
123fn smooth(v: &[f32], w: usize) -> Vec<f32> {
124 let n = v.len();
125 let mut out = vec![0.0f32; n];
126 for i in 0..n {
127 let s = i.saturating_sub(w / 2);
128 let e = (i + w / 2 + 1).min(n);
129 out[i] = v[s..e].iter().sum::<f32>() / (e - s) as f32;
130 }
131 out
132}