Skip to main content

mimium_symphonia/
lib.rs

1use std::collections::HashMap;
2use std::sync::Arc;
3
4use mimium_lang::ast::{Expr, Literal};
5use mimium_lang::function;
6use mimium_lang::interner::{ToSymbol, TypeNodeId};
7use mimium_lang::interpreter::Value;
8use mimium_lang::numeric;
9use mimium_lang::pattern::TypedId;
10use mimium_lang::plugin::{
11    SysPluginSignature, SystemPlugin, SystemPluginFnType, SystemPluginMacroType,
12};
13use mimium_lang::runtime::vm::{Machine, ReturnCode};
14use mimium_lang::string_t;
15use mimium_lang::types::{PType, Type};
16use symphonia::core::audio::{Layout, SampleBuffer, SignalSpec};
17use symphonia::core::codecs::{CODEC_TYPE_NULL, CodecParameters, Decoder, DecoderOptions};
18use symphonia::core::errors::Error as SymphoniaError;
19use symphonia::core::formats::{FormatOptions, SeekMode, SeekTo};
20use symphonia::core::io::{MediaSource, MediaSourceStream, MediaSourceStreamOptions};
21use symphonia::core::meta::MetadataOptions;
22use symphonia::core::probe::{Hint, ProbeResult};
23use symphonia::core::units::Time;
24type DecoderSet = (Box<dyn Decoder>, ProbeResult, u32);
25mod filemanager;
26use filemanager::FileManager;
27
28fn get_default_decoder(path: &str) -> Result<DecoderSet, Box<dyn std::error::Error>> {
29    let flmgr = filemanager::get_global_file_manager();
30    let src = flmgr.open_file_stream(path).expect("failed to open file");
31    let ms: Box<dyn MediaSource> = Box::new(src);
32    let ext = std::path::PathBuf::from(path);
33
34    let mss_opts = MediaSourceStreamOptions::default();
35    let mss = MediaSourceStream::new(ms, mss_opts);
36
37    let hint = ext.extension().map_or_else(Hint::new, |ext| {
38        ext.to_str().map_or_else(Hint::new, |s| {
39            let mut hint = Hint::new();
40            hint.with_extension(s);
41            hint
42        })
43    });
44
45    //  hint.with_extension("mp3");
46    // Use the default options for metadata and format readers.
47    let meta_opts: MetadataOptions = Default::default();
48    let fmt_opts: FormatOptions = Default::default();
49
50    // Probe the media source.
51    let probed = symphonia::default::get_probe().format(&hint, mss, &fmt_opts, &meta_opts)?;
52
53    let format = probed.format.as_ref();
54    let track = format
55        .tracks()
56        .iter()
57        .find(|t| t.codec_params.codec != CODEC_TYPE_NULL)
58        .ok_or(SymphoniaError::Unsupported("no supported audio tracks"))?;
59    let id = track.id;
60    // Use the default options for the decoder.
61    let dec_opts: DecoderOptions = Default::default();
62    // Create a decoder for the track.
63    let decoder = symphonia::default::get_codecs().make(&track.codec_params, &dec_opts)?;
64
65    Ok((decoder, probed, id))
66}
67
68fn load_wavfile_to_vec(path: &str) -> Result<Vec<f64>, SymphoniaError> {
69    let (mut decoder, mut probed, id) = get_default_decoder(path).expect("failed to find file");
70    let max_frames = decoder.codec_params().max_frames_per_packet.unwrap();
71    let _ = probed.format.seek(
72        SeekMode::Accurate,
73        SeekTo::Time {
74            time: Time::from_ss(0, 0).unwrap(),
75            track_id: Some(id),
76        },
77    );
78    let CodecParameters {
79        channels,
80        sample_rate,
81        channel_layout,
82        ..
83    } = probed.format.default_track().unwrap().codec_params;
84    let mut res = Vec::<f64>::new();
85    let mut buf = SampleBuffer::<f64>::new(
86        max_frames,
87        SignalSpec::new_with_layout(
88            sample_rate.unwrap_or(48000),
89            channel_layout.unwrap_or(Layout::Mono),
90        ),
91    );
92    if channels.is_some_and(|c| c.count() != 1) {
93        panic!("gen_sampler_mono only supports mono format.")
94    }
95    loop {
96        match probed.format.next_packet() {
97            Ok(packet) => {
98                // Decode the packet into audio samples.
99                let _ = decoder.decode(&packet).map(|decoded| {
100                    buf.copy_interleaved_ref(decoded.clone());
101                    res.extend_from_slice(buf.samples());
102                });
103            }
104            Err(e) => match e {
105                SymphoniaError::IoError(ioerror)
106                    if ioerror.kind() == std::io::ErrorKind::UnexpectedEof =>
107                {
108                    break; //file reached to the end of stream.
109                }
110                SymphoniaError::DecodeError(_) => {
111                    //contains broken packet but recoverable.
112                    continue;
113                }
114                _ => return Err(e),
115            },
116        }
117    }
118    Ok(res)
119}
120
121/// helper function that does bilinear interpolation
122fn interpolate_vec(vec: &[f64], pos: f64) -> f64 {
123    let bound = vec.len();
124    let pos_u = pos.floor() as usize;
125    //todo: efficient boundary check
126    match pos_u {
127        _ if pos >= 0.0 && (pos_u + 1) < bound => {
128            let frac = pos.fract();
129            let frac_rem = 1.0 - frac;
130            vec[pos_u] * frac_rem + vec[pos_u + 1] * frac
131        }
132        _ if pos_u + 1 == bound => vec[pos_u],
133        _ => 0.0,
134    }
135}
136
137#[derive(Default)]
138pub struct SamplerPlugin {
139    sample_cache: HashMap<String, Arc<Vec<f64>>>,
140    sample_namemap: HashMap<String, usize>,
141}
142
143impl SamplerPlugin {
144    const GET_SAMPLER: &'static str = "__get_sampler";
145
146    /// Returns a fallback lambda that ignores input and always returns 0.0 (silence)
147    fn error_fallback() -> Value {
148        Value::Code(
149            Expr::Lambda(
150                vec![TypedId::new(
151                    "_".to_symbol(),
152                    Type::Primitive(PType::Numeric).into_id(),
153                )],
154                None,
155                Expr::Literal(Literal::Float("0.0".to_symbol())).into_id_without_span(),
156            )
157            .into_id_without_span(),
158        )
159    }
160
161    pub fn make_sampler_mono(&mut self, v: &[(Value, TypeNodeId)]) -> Value {
162        assert_eq!(v.len(), 1);
163        let rel_path_str = match &v[0].0 {
164            Value::String(s) => s.to_string(),
165            _ => {
166                unreachable!("Sampler_mono! expects a string argument, guaranteed by type checker")
167            }
168        };
169
170        // Resolve the path (absolute or relative to CWD)
171        let abs_path = match std::fs::canonicalize(&rel_path_str) {
172            Ok(p) => p.to_string_lossy().to_string(),
173            Err(e) => {
174                mimium_lang::log::error!("Failed to resolve audio file path '{rel_path_str}': {e}");
175                return Self::error_fallback();
176            }
177        };
178
179        // Get the sampler index, loading the file if necessary
180        let idx = if let Some(&idx) = self.sample_namemap.get(&abs_path) {
181            idx
182        } else {
183            // Load the audio file at compile time
184            let vec = match load_wavfile_to_vec(&abs_path) {
185                Ok(v) => Arc::new(v),
186                Err(e) => {
187                    mimium_lang::log::error!("Failed to load audio file '{abs_path}': {e}");
188                    return Self::error_fallback();
189                }
190            };
191
192            let idx = self.sample_cache.len();
193            self.sample_cache.insert(abs_path.clone(), vec);
194            self.sample_namemap.insert(abs_path, idx);
195            idx
196        };
197
198        // Generate code that creates a closure for sampling
199        Value::Code(
200            Expr::Lambda(
201                vec![TypedId::new(
202                    "pos".to_symbol(),
203                    Type::Primitive(PType::Numeric).into_id(),
204                )],
205                None,
206                Expr::Apply(
207                    Expr::Var(Self::GET_SAMPLER.to_symbol()).into_id_without_span(),
208                    vec![
209                        Expr::Var("pos".to_symbol()).into_id_without_span(),
210                        Expr::Literal(Literal::Float(idx.to_string().to_symbol()))
211                            .into_id_without_span(),
212                    ],
213                )
214                .into_id_without_span(),
215            )
216            .into_id_without_span(),
217        )
218    }
219
220    pub fn get_sampler(&mut self, vm: &mut Machine) -> ReturnCode {
221        let pos = Machine::get_as::<f64>(vm.get_stack(0));
222        let sample_idx = Machine::get_as::<f64>(vm.get_stack(1)) as usize;
223
224        // Get the sample data from cache
225        let samples = self.sample_cache.values().nth(sample_idx);
226
227        match samples {
228            Some(vec) => {
229                let val = interpolate_vec(vec, pos);
230                vm.set_stack(0, Machine::to_value(val));
231            }
232            None => {
233                mimium_lang::log::error!("Invalid sample index: {sample_idx}");
234                vm.set_stack(0, Machine::to_value(0.0));
235            }
236        }
237
238        1
239    }
240}
241
242impl SystemPlugin for SamplerPlugin {
243    fn gen_interfaces(&self) -> Vec<SysPluginSignature> {
244        let sampler_macrof: SystemPluginMacroType<Self> = Self::make_sampler_mono;
245        let sampler_macro = SysPluginSignature::new_macro(
246            "Sampler_mono",
247            sampler_macrof,
248            function!(
249                vec![string_t!()],
250                Type::Code(function!(vec![numeric!()], numeric!())).into_id()
251            ),
252        );
253
254        let get_samplerf: SystemPluginFnType<Self> = Self::get_sampler;
255        let get_sampler = SysPluginSignature::new(
256            Self::GET_SAMPLER,
257            get_samplerf,
258            function!(vec![numeric!(), numeric!()], numeric!()),
259        );
260
261        vec![sampler_macro, get_sampler]
262    }
263}