Skip to main content

proteus_lib/dsp/effects/convolution_reverb/
spec.rs

1//! Impulse response specification and play-settings parsing helpers.
2
3use crate::container::play_settings::{
4    ConvolutionReverbSettings, EffectSettings, PlaySettingsFile,
5};
6
7/// Location of an impulse response used for convolution reverb.
8#[derive(Debug, Clone, PartialEq, Eq)]
9pub enum ImpulseResponseSpec {
10    Attachment(String),
11    FilePath(String),
12}
13
14/// Parse a convolution reverb impulse response spec from play settings.
15pub(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
27/// Parse the convolution reverb tail trim (dB) from play settings.
28pub(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
75/// Parse an impulse response spec string into a concrete location.
76///
77/// Supported prefixes:
78/// - `attachment:` for container attachments
79/// - `file:` for explicit file paths
80pub 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}