rill_core_model/string/
mod.rs1mod params;
8
9pub use params::StringParams;
10
11use rill_core::traits::algorithm::{
12 Algorithm, AlgorithmCategory, AlgorithmMetadata, ParameterizedAlgorithm,
13};
14use rill_core::traits::ParamValue;
15use rill_core::Transcendental;
16
17#[derive(Debug, Clone)]
22pub struct StringModel<T: Transcendental> {
23 params: StringParams<T>,
24 delay_line: Vec<T>,
25 write_ptr: usize,
26 delay_len: usize,
27 frac: T,
28 prev_allpass: T,
29 prev_input: T,
30 sample_rate: f32,
31}
32
33impl<T: Transcendental> StringModel<T> {
34 pub fn new(params: StringParams<T>, sample_rate: f32, capacity: usize) -> Self {
38 let delay_len = (sample_rate as f64 / params.frequency.to_f64()) as usize;
39 let delay_len = delay_len.min(capacity).max(2);
40 let frac = T::from_f64(sample_rate as f64 / params.frequency.to_f64() - delay_len as f64);
41 Self {
42 params,
43 delay_line: vec![T::ZERO; capacity],
44 write_ptr: 0,
45 delay_len,
46 frac,
47 prev_allpass: T::ZERO,
48 prev_input: T::ZERO,
49 sample_rate,
50 }
51 }
52
53 pub fn pluck(&mut self, strength: T) {
55 let two = T::from_f32(2.0);
56 let half = T::from_f32(0.5);
57 for i in 0..self.delay_len.min(self.delay_line.len()) {
58 let pos = (self.write_ptr + self.delay_line.len() - 1 - i) % self.delay_line.len();
60 let phase = T::from_f64(i as f64 / self.delay_len as f64);
61 let noise = (T::random() - half) * two * strength;
62 self.delay_line[pos] = noise * (T::ONE - phase);
63 }
64 }
65
66 pub fn excite(&mut self, excitation: &[T]) {
68 for (i, &sample) in excitation.iter().enumerate() {
69 let pos = (self.write_ptr + i) % self.delay_line.len();
70 self.delay_line[pos] = sample;
71 }
72 }
73
74 fn process_sample(&mut self, input: T) -> T {
75 let read_ptr =
76 (self.write_ptr + self.delay_line.len() - self.delay_len) % self.delay_line.len();
77 let read_next = (read_ptr + 1) % self.delay_line.len();
78
79 let s0 = self.delay_line[read_ptr];
80 let s1 = self.delay_line[read_next];
81
82 let c = (T::ONE - self.frac) / (T::ONE + self.frac);
84 let delayed = -c * s0 + s1 + c * self.prev_allpass;
85 self.prev_allpass = delayed;
86
87 let b = self.params.brightness;
89 let filtered = (T::ONE - b) * self.prev_input + b * delayed;
90
91 let stiff = self.params.stiffness;
93 let dispersed = if stiff > T::ZERO {
94 -stiff * filtered + self.prev_input + stiff * self.prev_input
95 } else {
96 filtered
97 };
98 self.prev_input = filtered;
99
100 let output = dispersed * self.params.decay + input;
101
102 self.delay_line[self.write_ptr] = output;
103 self.write_ptr = (self.write_ptr + 1) % self.delay_line.len();
104
105 output
106 }
107}
108
109impl<T: Transcendental> Algorithm<T> for StringModel<T> {
110 fn process(
111 &mut self,
112 input: Option<&[T]>,
113 output: &mut [T],
114 ) -> rill_core::traits::ProcessResult<()> {
115 for (i, out) in output.iter_mut().enumerate() {
116 let inp = input
117 .map(|s| s.get(i).copied().unwrap_or(T::ZERO))
118 .unwrap_or(T::ZERO);
119 *out = self.process_sample(inp);
120 }
121 Ok(())
122 }
123
124 fn reset(&mut self) {
125 self.delay_line.fill(T::ZERO);
126 self.write_ptr = 0;
127 self.prev_allpass = T::ZERO;
128 self.prev_input = T::ZERO;
129 }
130
131 fn init(&mut self, sample_rate: f32) {
132 self.sample_rate = sample_rate;
133 let delay_len = (sample_rate as f64 / self.params.frequency.to_f64()) as usize;
134 self.delay_len = delay_len.min(self.delay_line.len()).max(2);
135 self.frac =
136 T::from_f64(sample_rate as f64 / self.params.frequency.to_f64() - delay_len as f64);
137 }
138
139 fn metadata(&self) -> AlgorithmMetadata {
140 AlgorithmMetadata {
141 name: "String Model",
142 category: AlgorithmCategory::Generator,
143 description: "1D digital waveguide with stiffness, damping, and brightness control",
144 author: "Rill",
145 version: "0.5",
146 }
147 }
148}
149
150impl<T: Transcendental> ParameterizedAlgorithm<T> for StringModel<T> {
151 type Params = StringParams<T>;
152
153 fn params(&self) -> &Self::Params {
154 &self.params
155 }
156
157 fn set_params(&mut self, params: Self::Params) {
158 let freq_changed = params.frequency != self.params.frequency;
159 self.params = params;
160 if freq_changed && self.sample_rate > 0.0 {
161 let delay_len = (self.sample_rate as f64 / self.params.frequency.to_f64()) as usize;
162 self.delay_len = delay_len.min(self.delay_line.len()).max(2);
163 self.frac = T::from_f64(
164 self.sample_rate as f64 / self.params.frequency.to_f64() - delay_len as f64,
165 );
166 }
167 }
168
169 fn set_parameter(&mut self, name: &str, value: ParamValue) -> Result<(), &'static str> {
170 match name {
171 "frequency" => {
172 let mut p = self.params.clone();
173 p.frequency = T::from_f32(value.as_f32().unwrap_or(440.0));
174 self.set_params(p);
175 Ok(())
176 }
177 "decay" => {
178 let mut p = self.params.clone();
179 p.decay = T::from_f32(value.as_f32().unwrap_or(0.9995));
180 self.set_params(p);
181 Ok(())
182 }
183 "stiffness" => {
184 let mut p = self.params.clone();
185 p.stiffness = T::from_f32(value.as_f32().unwrap_or(0.0));
186 self.set_params(p);
187 Ok(())
188 }
189 "brightness" => {
190 let mut p = self.params.clone();
191 p.brightness = T::from_f32(value.as_f32().unwrap_or(0.95));
192 self.set_params(p);
193 Ok(())
194 }
195 _ => Err("Unknown parameter"),
196 }
197 }
198}
199
200#[cfg(test)]
201mod tests {
202 use super::*;
203
204 #[test]
205 fn test_string_creation() {
206 let params = StringParams::default();
207 let model = StringModel::<f64>::new(params, 44100.0, 4096);
208 assert!(model.delay_len >= 2);
209 assert!(model.delay_len <= 4096);
210 }
211
212 #[test]
213 fn test_string_algorithm_process() {
214 let params = StringParams::default();
215 let mut model = StringModel::<f64>::new(params, 44100.0, 4096);
216 model.pluck(0.5.into());
217 let mut output = [0.0f64; 64];
218 model.process(None, &mut output).unwrap();
219 let max_abs = output.iter().map(|x| x.abs()).fold(0.0, f64::max);
220 assert!(max_abs > 0.0);
221 }
222
223 #[test]
224 fn test_string_decay() {
225 let params = StringParams {
226 decay: 0.5.into(),
227 ..Default::default()
228 };
229 let mut model = StringModel::<f64>::new(params, 44100.0, 4096);
230 model.pluck(1.0.into());
231 let mut blocks = Vec::new();
232 for _ in 0..20 {
233 let mut out = [0.0f64; 64];
234 model.process(None, &mut out).unwrap();
235 blocks.push(out.iter().map(|x| x.abs()).fold(0.0, f64::max));
236 }
237 assert!(blocks[19] < blocks[0] * 0.5);
239 }
240
241 #[test]
242 fn test_string_params() {
243 let params = StringParams::default();
244 let mut model = StringModel::<f64>::new(params, 44100.0, 4096);
245 let new_params = StringParams {
246 frequency: 220.0.into(),
247 ..StringParams::default()
248 };
249 model.set_params(new_params);
250 assert!((model.params.frequency - 220.0).abs() < 1e-6);
251 }
252
253 #[test]
254 fn test_string_set_parameter() {
255 let params = StringParams::default();
256 let mut model = StringModel::<f64>::new(params, 44100.0, 4096);
257 model
258 .set_parameter("frequency", ParamValue::Float(220.0))
259 .unwrap();
260 assert!((model.params.frequency - 220.0).abs() < 1e-6);
261 assert!(model
262 .set_parameter("unknown", ParamValue::Float(1.0))
263 .is_err());
264 }
265}