proteus_lib/dsp/effects/convolution_reverb/
spec.rs1use crate::container::play_settings::{
4 ConvolutionReverbSettings, EffectSettings, PlaySettingsFile,
5};
6
7#[derive(Debug, Clone, PartialEq, Eq)]
9pub enum ImpulseResponseSpec {
10 Attachment(String),
11 FilePath(String),
12}
13
14pub(crate) fn parse_impulse_response_spec(
16 play_settings: &PlaySettingsFile,
17) -> Option<ImpulseResponseSpec> {
18 if let Some(settings) = parse_convolution_settings(play_settings) {
19 if let Some(spec) = parse_impulse_response_string_or_struct(&settings) {
20 return Some(spec);
21 }
22 }
23
24 None
25}
26
27pub(crate) fn parse_impulse_response_tail_db(play_settings: &PlaySettingsFile) -> Option<f32> {
29 if let Some(settings) = parse_convolution_settings(play_settings) {
30 if let Some(value) = settings.impulse_response_tail_db {
31 return Some(value);
32 }
33 if let Some(value) = settings.impulse_response_tail {
34 return Some(value);
35 }
36 }
37
38 None
39}
40
41fn parse_convolution_settings(
42 play_settings: &PlaySettingsFile,
43) -> Option<ConvolutionReverbSettings> {
44 let effects = match play_settings {
45 PlaySettingsFile::V1(file) => &file.settings.inner().effects,
46 PlaySettingsFile::V2(file) => &file.settings.inner().effects,
47 PlaySettingsFile::V3(file) => &file.settings.inner().effects,
48 _ => return None,
49 };
50
51 for effect in effects {
52 if let EffectSettings::ConvolutionReverb(effect) = effect {
53 return Some(effect.settings.clone());
54 }
55 }
56
57 None
58}
59
60pub(crate) fn parse_impulse_response_string_or_struct(
61 settings: &ConvolutionReverbSettings,
62) -> Option<ImpulseResponseSpec> {
63 if let Some(value) = settings.impulse_response.as_deref() {
64 return parse_impulse_response_string(value);
65 }
66 if let Some(value) = settings.impulse_response_attachment.as_deref() {
67 return parse_impulse_response_string(value);
68 }
69 if let Some(value) = settings.impulse_response_path.as_deref() {
70 return parse_impulse_response_string(value);
71 }
72 None
73}
74
75pub fn parse_impulse_response_string(value: &str) -> Option<ImpulseResponseSpec> {
81 if let Some(attachment) = value.strip_prefix("attachment:") {
82 return Some(ImpulseResponseSpec::Attachment(
83 attachment.trim().to_string(),
84 ));
85 }
86
87 if let Some(path) = value.strip_prefix("file:") {
88 return Some(ImpulseResponseSpec::FilePath(path.trim().to_string()));
89 }
90
91 Some(ImpulseResponseSpec::FilePath(value.trim().to_string()))
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97 use crate::container::play_settings::{
98 PlaySettingsContainer, PlaySettingsFile, PlaySettingsV2, PlaySettingsV2File,
99 };
100 use crate::dsp::effects::{AudioEffect, ConvolutionReverbEffect};
101
102 #[test]
103 fn parse_impulse_response_string_variants() {
104 assert_eq!(
105 parse_impulse_response_string("attachment:foo.wav"),
106 Some(ImpulseResponseSpec::Attachment("foo.wav".to_string()))
107 );
108 assert_eq!(
109 parse_impulse_response_string("file:/tmp/bar.wav"),
110 Some(ImpulseResponseSpec::FilePath("/tmp/bar.wav".to_string()))
111 );
112 assert_eq!(
113 parse_impulse_response_string("plain.wav"),
114 Some(ImpulseResponseSpec::FilePath("plain.wav".to_string()))
115 );
116 }
117
118 #[test]
119 fn parse_impulse_response_from_play_settings() {
120 let mut effect = ConvolutionReverbEffect::default();
121 effect.settings.impulse_response = Some("attachment:ir.wav".to_string());
122 effect.settings.impulse_response_tail_db = Some(-42.0);
123
124 let settings = PlaySettingsV2 {
125 tracks: Vec::new(),
126 effects: vec![AudioEffect::ConvolutionReverb(effect)],
127 };
128 let file = PlaySettingsV2File {
129 settings: PlaySettingsContainer::Flat(settings),
130 };
131
132 let play_settings = PlaySettingsFile::V2(file);
133 let spec = parse_impulse_response_spec(&play_settings);
134 assert_eq!(
135 spec,
136 Some(ImpulseResponseSpec::Attachment("ir.wav".to_string()))
137 );
138 let tail_db = parse_impulse_response_tail_db(&play_settings);
139 assert_eq!(tail_db, Some(-42.0));
140 }
141}