1use rill_core::traits::algorithm::{Algorithm, AlgorithmCategory, AlgorithmMetadata};
9use rill_core::traits::ProcessResult;
10use rill_core::Transcendental;
11
12pub struct DirectConvolver<T: Transcendental, const IR_LEN: usize, const BUF_SIZE: usize> {
23 ir: [T; IR_LEN],
24 delay_line: [T; IR_LEN],
25 write_head: usize,
26}
27
28impl<T: Transcendental, const IR_LEN: usize, const BUF_SIZE: usize>
29 DirectConvolver<T, IR_LEN, BUF_SIZE>
30{
31 pub fn new() -> Self {
33 Self {
34 ir: [T::ZERO; IR_LEN],
35 delay_line: [T::ZERO; IR_LEN],
36 write_head: 0,
37 }
38 }
39
40 pub fn set_ir(&mut self, ir: &[T]) {
45 let len = ir.len().min(IR_LEN);
46 self.ir[..len].copy_from_slice(&ir[..len]);
47 for i in len..IR_LEN {
48 self.ir[i] = T::ZERO;
49 }
50 }
51
52 pub fn ir_len(&self) -> usize {
54 IR_LEN
55 }
56}
57
58impl<T: Transcendental, const IR_LEN: usize, const BUF_SIZE: usize> Default
59 for DirectConvolver<T, IR_LEN, BUF_SIZE>
60{
61 fn default() -> Self {
62 Self::new()
63 }
64}
65
66impl<T: Transcendental, const IR_LEN: usize, const BUF_SIZE: usize> Algorithm<T>
67 for DirectConvolver<T, IR_LEN, BUF_SIZE>
68{
69 fn process(&mut self, input: Option<&[T]>, output: &mut [T]) -> ProcessResult<()> {
70 match input {
71 Some(samples) => {
72 for (out, &inp) in output.iter_mut().zip(samples.iter()) {
73 self.delay_line[self.write_head] = inp;
74 let mut acc = T::ZERO;
75 for k in 0..IR_LEN {
76 let idx = if self.write_head >= k {
77 self.write_head - k
78 } else {
79 IR_LEN + self.write_head - k
80 };
81 acc += self.delay_line[idx] * self.ir[k];
82 }
83 *out = acc;
84 self.write_head = (self.write_head + 1) % IR_LEN;
85 }
86 Ok(())
87 }
88 None => {
89 output.fill(T::ZERO);
90 Ok(())
91 }
92 }
93 }
94
95 fn reset(&mut self) {
96 self.delay_line.fill(T::ZERO);
97 self.write_head = 0;
98 self.ir.fill(T::ZERO);
99 }
100
101 fn metadata(&self) -> AlgorithmMetadata {
102 AlgorithmMetadata {
103 name: "DirectConvolver",
104 category: AlgorithmCategory::Effect,
105 description: "Direct time-domain convolution",
106 author: "Rill",
107 version: env!("CARGO_PKG_VERSION"),
108 }
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115
116 #[test]
117 fn test_unit_impulse_is_passthrough() {
118 let mut conv = DirectConvolver::<f32, 4, 8>::new();
119 conv.set_ir(&[1.0, 0.0, 0.0, 0.0]);
120
121 let input = [0.5f32, 0.3, -0.2, 0.8, 0.1, -0.5, 0.4, 0.0];
122 let mut output = [0.0f32; 8];
123 conv.process(Some(&input), &mut output).unwrap();
124
125 for (i, o) in input.iter().zip(output.iter()) {
126 assert!((i - o).abs() < 1e-6, "expected {i}, got {o}");
127 }
128 }
129
130 #[test]
131 fn test_delayed_impulse_is_delay() {
132 let mut conv = DirectConvolver::<f32, 4, 8>::new();
133 conv.set_ir(&[0.0, 0.0, 1.0, 0.0]);
134
135 let input = [1.0f32, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
136 let mut output = [0.0f32; 8];
137 conv.process(Some(&input), &mut output).unwrap();
138
139 assert!((output[0] - 0.0).abs() < 1e-6);
140 assert!((output[1] - 0.0).abs() < 1e-6);
141 assert!((output[2] - 1.0).abs() < 1e-6);
142 assert!((output[3] - 2.0).abs() < 1e-6);
143 assert!((output[4] - 3.0).abs() < 1e-6);
144 assert!((output[5] - 4.0).abs() < 1e-6);
145 assert!((output[6] - 5.0).abs() < 1e-6);
146 assert!((output[7] - 6.0).abs() < 1e-6);
147 }
148
149 #[test]
150 fn test_identity_convolution() {
151 let mut conv = DirectConvolver::<f32, 3, 4>::new();
152 conv.set_ir(&[1.0, 0.0, 0.0]);
153
154 let input = [2.0f32, 3.0, 4.0, 5.0];
155 let mut output = [0.0f32; 4];
156 conv.process(Some(&input), &mut output).unwrap();
157
158 for (i, o) in input.iter().zip(output.iter()) {
159 assert!((i - o).abs() < 1e-6);
160 }
161 }
162
163 #[test]
164 fn test_averaging_ir() {
165 let mut conv = DirectConvolver::<f32, 2, 4>::new();
166 conv.set_ir(&[0.5, 0.5]);
167
168 let input = [1.0f32, 0.0, 0.0, 0.0];
169 let mut output = [0.0f32; 4];
170 conv.process(Some(&input), &mut output).unwrap();
171
172 assert!((output[0] - 0.5).abs() < 1e-6);
173 assert!((output[1] - 0.5).abs() < 1e-6);
174 assert!((output[2] - 0.0).abs() < 1e-6);
175 }
176
177 #[test]
178 fn test_no_input_zeroes_output() {
179 let mut conv = DirectConvolver::<f32, 4, 4>::new();
180 conv.set_ir(&[1.0, 0.5, 0.25, 0.125]);
181 let mut output = [1.0f32; 4];
182 conv.process(None, &mut output).unwrap();
183
184 for o in output.iter() {
185 assert!((o - 0.0).abs() < 1e-6);
186 }
187 }
188
189 #[test]
190 fn test_reset_clears_state() {
191 let mut conv = DirectConvolver::<f32, 4, 4>::new();
192 conv.set_ir(&[1.0, 1.0, 1.0, 1.0]);
193
194 let input = [1.0f32; 4];
195 let mut output = [0.0f32; 4];
196 conv.process(Some(&input), &mut output).unwrap();
197
198 conv.reset();
199 let input2 = [0.0f32; 4];
200 conv.process(Some(&input2), &mut output).unwrap();
201
202 for o in output.iter() {
203 assert!((o - 0.0).abs() < 1e-6);
204 }
205 }
206
207 #[test]
208 fn test_ir_shorter_than_ir_len_zeros_remainder() {
209 let mut conv = DirectConvolver::<f32, 4, 4>::new();
210 conv.set_ir(&[1.0, 2.0]);
211
212 let input = [1.0f32, 2.0, 3.0, 4.0];
213 let mut output = [0.0f32; 4];
214 conv.process(Some(&input), &mut output).unwrap();
215
216 let mut conv2 = DirectConvolver::<f32, 4, 4>::new();
217 conv2.set_ir(&[1.0, 2.0, 0.0, 0.0]);
218
219 let mut output2 = [0.0f32; 4];
220 conv2.process(Some(&input), &mut output2).unwrap();
221
222 for (o1, o2) in output.iter().zip(output2.iter()) {
223 assert!((o1 - o2).abs() < 1e-6);
224 }
225 }
226}