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 let meta_opts: MetadataOptions = Default::default();
48 let fmt_opts: FormatOptions = Default::default();
49
50 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 let dec_opts: DecoderOptions = Default::default();
62 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 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; }
110 SymphoniaError::DecodeError(_) => {
111 continue;
113 }
114 _ => return Err(e),
115 },
116 }
117 }
118 Ok(res)
119}
120
121fn interpolate_vec(vec: &[f64], pos: f64) -> f64 {
123 let bound = vec.len();
124 let pos_u = pos.floor() as usize;
125 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 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 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 let idx = if let Some(&idx) = self.sample_namemap.get(&abs_path) {
181 idx
182 } else {
183 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 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 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}