synthahol_phase_plant/effect/
convolver.rs1use std::any::{type_name, Any};
14use std::io;
15use std::io::{Error, ErrorKind, Read, Seek, Write};
16
17use crate::effect::EffectVersion;
18use uom::num::Zero;
19use uom::si::f32::{Ratio, Time};
20use uom::si::ratio::percent;
21
22use crate::Snapin;
23
24use super::super::io::*;
25use super::{Effect, EffectMode};
26
27#[derive(Clone, Debug, PartialEq)]
28pub struct Convolver {
29 pub ir_name: Option<String>,
30 pub ir_path: Vec<String>,
31
32 pub start: Ratio,
34 pub end: Ratio,
35 pub fade_in: Ratio,
36 pub fade_out: Ratio,
37 pub stretch: Ratio,
38
39 pub delay: Time,
41
42 pub sync: bool,
43 pub tone: Ratio,
44 pub feedback: Ratio,
45 pub mix: Ratio,
46 pub reverse: bool,
47}
48
49impl Convolver {
50 pub fn default_version() -> EffectVersion {
51 1018
52 }
53}
54
55impl Default for Convolver {
56 fn default() -> Self {
57 Self {
58 ir_name: None,
59 ir_path: Vec::new(),
60 start: Ratio::zero(),
61 end: Ratio::new::<percent>(100.0),
62 fade_in: Ratio::zero(),
63 fade_out: Ratio::zero(),
64 stretch: Ratio::new::<percent>(100.0),
65 delay: Time::zero(),
66 sync: false,
67 tone: Ratio::zero(),
68 feedback: Ratio::zero(),
69 mix: Ratio::new::<percent>(100.0),
70 reverse: false,
71 }
72 }
73}
74
75impl dyn Effect {
76 #[must_use]
77 pub fn as_convolver(&self) -> Option<&Convolver> {
78 self.downcast_ref::<Convolver>()
79 }
80}
81
82impl Effect for Convolver {
83 fn box_eq(&self, other: &dyn Any) -> bool {
84 other
85 .downcast_ref::<Self>()
86 .map_or(false, |other| self == other)
87 }
88
89 fn mode(&self) -> EffectMode {
90 EffectMode::Convolver
91 }
92}
93
94impl EffectRead for Convolver {
95 fn read<R: Read + Seek>(
96 reader: &mut PhasePlantReader<R>,
97 effect_version: u32,
98 ) -> io::Result<EffectReadReturn> {
99 if effect_version < 1007 {
100 return Err(Error::new(
101 ErrorKind::InvalidData,
102 format!(
103 "Version {effect_version} of {} is not supported",
104 type_name::<Self>()
105 ),
106 ));
107 }
108
109 let group_id = None;
111
112 let mix = reader.read_ratio()?;
113 let stretch = reader.read_ratio()?;
114 let enabled = reader.read_bool32()?;
115 let minimized = reader.read_bool32()?;
116
117 reader.expect_u32(0, "convolver_unknown_5")?;
118 reader.expect_u32(0, "convolver_unknown_6")?;
119
120 let end = reader.read_ratio()?;
121 let fade_out = reader.read_ratio()?;
122 let feedback = reader.read_ratio()?;
123 let tone = reader.read_ratio()?;
124 let start = reader.read_ratio()?;
125 let fade_in = reader.read_ratio()?;
126 let delay = reader.read_seconds()?;
127
128 reader.expect_u32(0, "convolver_unknown_7")?;
129 reader.expect_u32(4, "convolver_unknown_8")?;
130
131 let sync = reader.read_bool32()?;
132 let reverse = reader.read_bool32()?;
133
134 reader.expect_u32(0, "convolver_unknown_9")?;
135
136 let ir_name = reader.read_string_and_length()?;
137 let mut ir_path = Vec::new();
138 let path_header = reader.read_block_header()?;
139 if path_header.is_used() {
140 ir_path = match reader.read_string_and_length()? {
141 None => Vec::new(),
142 Some(path) => vec![path],
143 };
144 let header_mode_id = path_header.mode_id().expect("convolver IR header mode");
145 match header_mode_id {
146 3 => reader.expect_u8(0, "convolver_block_unknown_1")?,
147 2 => (),
148 _ => {
149 return Err(Error::new(
150 ErrorKind::InvalidData,
151 format!("Unsupported convolver IR block mode {header_mode_id}"),
152 ))
153 }
154 }
155 }
156
157 let effect = Convolver {
158 ir_name,
159 ir_path,
160 start,
161 end,
162 fade_in,
163 fade_out,
164 stretch,
165 delay,
166 sync,
167 tone,
168 feedback,
169 mix,
170 reverse,
171 };
172 Ok(EffectReadReturn::new(
173 Box::new(effect),
174 enabled,
175 minimized,
176 group_id,
177 ))
178 }
179}
180
181impl EffectWrite for Convolver {
182 fn write<W: Write + Seek>(
183 &self,
184 _writer: &mut PhasePlantWriter<W>,
185 _snapin: &Snapin,
186 ) -> io::Result<()> {
187 todo!()
188 }
189}
190
191#[cfg(test)]
192mod test {
193 use approx::assert_relative_eq;
194 use uom::si::f32::{Ratio, Time};
195 use uom::si::ratio::percent;
196 use uom::si::time::millisecond;
197
198 use crate::effect::Filter;
199 use crate::test::read_effect_preset;
200
201 use super::*;
202
203 #[test]
204 fn art_museum() {
205 let preset =
206 read_effect_preset("convolver", "convolver-art_museum-2.0.12.phaseplant").unwrap();
207 let snapin = &preset.lanes[0].snapins[0];
208 assert!(snapin.enabled);
209 assert!(!snapin.minimized);
210 assert!(!snapin.preset_edited);
211 assert!(snapin.preset_name.is_empty());
212 assert!(snapin.preset_path.is_empty());
213 let effect = snapin.effect.as_convolver().unwrap();
214 assert_eq!(effect.ir_name, Some("Art Museum".to_owned()));
215 assert_eq!(
216 effect.ir_path,
217 vec!["factory/Impulse Responses/Spaces Real/Art Museum.flac"]
218 );
219 assert_eq!(effect.start, Ratio::zero());
220 assert_eq!(effect.end, Ratio::new::<percent>(100.0));
221 assert_eq!(effect.fade_in, Ratio::zero());
222 assert_eq!(effect.fade_out, Ratio::zero());
223 assert_eq!(effect.stretch, Ratio::new::<percent>(100.0));
224 assert_eq!(effect.delay, Time::zero());
225 assert!(!effect.sync);
226 assert_eq!(effect.tone.get::<percent>(), 0.0);
227 assert_eq!(effect.feedback.get::<percent>(), 0.0);
228 assert_eq!(effect.mix.get::<percent>(), 100.0);
229 assert!(!effect.reverse);
230 }
231
232 #[test]
233 fn default() {
234 let effect = Convolver::default();
235 assert_eq!(effect.start, Ratio::zero());
236 assert_eq!(effect.end, Ratio::new::<percent>(100.0));
237 assert_eq!(effect.fade_in, Ratio::zero());
238 assert_eq!(effect.fade_out, Ratio::zero());
239 assert_eq!(effect.stretch, Ratio::new::<percent>(100.0));
240 assert_eq!(effect.delay, Time::zero());
241 assert!(!effect.sync);
242 assert_eq!(effect.tone.get::<percent>(), 0.0);
243 assert_eq!(effect.feedback.get::<percent>(), 0.0);
244 assert_eq!(effect.mix.get::<percent>(), 100.0);
245 assert!(!effect.reverse);
246 }
247
248 #[test]
249 fn disabled() {
250 let preset =
251 read_effect_preset("convolver", "convolver-disabled-2.0.16.phaseplant").unwrap();
252 let snapin = &preset.lanes[0].snapins[0];
253 assert!(!snapin.enabled);
254 assert!(!snapin.minimized);
255 }
256
257 #[test]
258 fn eq() {
259 let effect = Convolver::default();
260 assert_eq!(effect, effect);
261 assert_eq!(effect, Convolver::default());
262 assert!(!effect.box_eq(&Filter::default()));
263 }
264
265 #[test]
266 fn init() {
267 for file in &[
268 "convolver-2.0.12.phaseplant",
269 "convolver-2.0.16.phaseplant",
270 "convolver-2.1.0.phaseplant",
271 ] {
272 let preset = read_effect_preset("convolver", file).unwrap();
273 let snapin = &preset.lanes[0].snapins[0];
274 assert!(snapin.enabled);
275 assert!(!snapin.minimized);
276 assert_eq!(snapin.preset_name, "".to_string());
277 assert_eq!(snapin.preset_path, Vec::<String>::new());
278 let effect = snapin.effect.as_convolver().unwrap();
279 assert_eq!(effect, &Default::default());
280 }
281 }
282
283 #[test]
284 fn minimized() {
285 let preset =
286 read_effect_preset("convolver", "convolver-minimized-2.0.16.phaseplant").unwrap();
287 let snapin = &preset.lanes[0].snapins[0];
288 assert!(snapin.enabled);
289 assert!(snapin.minimized);
290 }
291
292 #[test]
293 fn parts() {
294 let preset =
295 read_effect_preset("convolver", "convolver-delay50-tone25-2.0.12.phaseplant").unwrap();
296 let snapin = &preset.lanes[0].snapins[0];
297 assert!(snapin.enabled);
298 assert!(!snapin.minimized);
299 let effect = snapin.effect.as_convolver().unwrap();
300 assert_relative_eq!(effect.delay.get::<millisecond>(), 50.0);
301 assert_eq!(effect.tone.get::<percent>(), 25.0);
302 assert!(!effect.sync);
303
304 let preset = read_effect_preset(
305 "convolver",
306 "convolver-fade_in25-stretch45-fade_out75-2.0.12.phaseplant",
307 )
308 .unwrap();
309 let snapin = &preset.lanes[0].snapins[0];
310 let effect = snapin.effect.as_convolver().unwrap();
311 assert_relative_eq!(effect.fade_in.get::<percent>(), 25.0, epsilon = 0.01);
312 assert_relative_eq!(effect.fade_out.get::<percent>(), 75.0, epsilon = 0.01);
313 assert_eq!(effect.stretch.get::<percent>(), 45.0);
314
315 let preset =
316 read_effect_preset("convolver", "convolver-feedback25-mix50-2.0.12.phaseplant")
317 .unwrap();
318 let snapin = &preset.lanes[0].snapins[0];
319 let effect = snapin.effect.as_convolver().unwrap();
320 assert_eq!(effect.feedback.get::<percent>(), 25.0);
321 assert_eq!(effect.mix.get::<percent>(), 50.0);
322
323 let preset = read_effect_preset(
324 "convolver",
325 "convolver-feedback75-delay25-reverse-2.0.16.phaseplant",
326 )
327 .unwrap();
328 let snapin = &preset.lanes[0].snapins[0];
329 let effect = snapin.effect.as_convolver().unwrap();
330 assert!(effect.reverse);
331 assert_relative_eq!(effect.feedback.get::<percent>(), 75.4, epsilon = 0.1);
332 assert_relative_eq!(effect.delay.get::<millisecond>(), 25.0, epsilon = 0.1);
333
334 let preset =
335 read_effect_preset("convolver", "convolver-start5-end80-2.0.12.phaseplant").unwrap();
336 let snapin = &preset.lanes[0].snapins[0];
337 let effect = snapin.effect.as_convolver().unwrap();
338 assert_relative_eq!(effect.start.get::<percent>(), 5.0, epsilon = 0.001);
339 assert_relative_eq!(effect.end.get::<percent>(), 80.0, epsilon = 0.001);
340 }
341
342 #[test]
343 fn reverse_reverb() {
344 let preset =
345 read_effect_preset("convolver", "convolver-reverse_reverb-2.1.0.phaseplant").unwrap();
346 let snapin = &preset.lanes[0].snapins[0];
347 assert!(snapin.enabled);
348 assert!(!snapin.minimized);
349 assert!(!snapin.preset_edited);
350 assert_eq!(snapin.preset_name, "Reverse Reverb");
351 assert_eq!(snapin.preset_path, vec!["factory", "Reverse Reverb.ksco"]);
352
353 let effect = snapin.effect.as_convolver().unwrap();
354 assert_eq!(effect.ir_name, Some("Modern Church".to_string()));
355 assert_eq!(
356 effect.ir_path,
357 vec!["factory/Impulse Responses/Spaces Real/Modern Church.flac"]
358 );
359 assert_relative_eq!(effect.start.get::<percent>(), 50.0, epsilon = 0.0001);
360 assert_relative_eq!(effect.end.get::<percent>(), 100.0, epsilon = 0.0001);
361 assert_relative_eq!(effect.fade_in.get::<percent>(), 10.0, epsilon = 0.0001);
362 assert_relative_eq!(effect.fade_out.get::<percent>(), 10.0, epsilon = 0.0001);
363 assert_relative_eq!(effect.stretch.get::<percent>(), 100.0, epsilon = 0.0001);
364 assert_eq!(effect.delay, Time::zero());
365 assert!(!effect.sync);
366 assert_eq!(effect.tone.get::<percent>(), 0.0);
367 assert_eq!(effect.feedback.get::<percent>(), 0.0);
368 assert_eq!(effect.mix.get::<percent>(), 100.0);
369 assert!(effect.reverse);
370 }
371
372 #[test]
373 fn sync() {
374 let preset = read_effect_preset("convolver", "convolver-sync-2.0.16.phaseplant").unwrap();
375 let snapin = &preset.lanes[0].snapins[0];
376 let effect = snapin.effect.as_convolver().unwrap();
377 assert!(effect.sync);
378 }
379}