use super::*; use std::fs::File; use symphonia::core::codecs::CODEC_TYPE_NULL; use symphonia::core::errors::Error; use symphonia::core::io::MediaSourceStream; use symphonia::core::probe::Hint; use symphonia::core::audio::SampleBuffer; use symphonia::default::get_codecs; pub struct AddSampleModal { exited: bool, dir: PathBuf, subdirs: Vec, files: Vec, cursor: usize, offset: usize, sample: Arc>, voices: Arc>>, _search: Option, } exit!(AddSampleModal); impl<'a> Render, Rect> for AddSampleModal { fn render (&self, to: &mut TuiOutput<'a>) -> Usually { make_dim(to.buffer); let area = center_box( to.area, 64.max(to.area.width.saturating_sub(8)), 20.max(to.area.width.saturating_sub(8)), ); fill_fg(buf, area, Color::Reset); fill_bg(buf, area, Nord::bg_lo(true, true)); fill_char(buf, area, ' '); format!("{}", &self.dir.to_string_lossy()) .blit(buf, area.x+2, area.y+1, Some(Style::default().bold()))?; "Select sample:" .blit(buf, area.x+2, area.y+2, Some(Style::default().bold()))?; for (i, (is_dir, name)) in self.subdirs.iter() .map(|path|(true, path)) .chain(self.files.iter().map(|path|(false, path))) .enumerate() .skip(self.offset) { if i >= area.height as usize - 4 { break } let t = if is_dir { "" } else { "" }; let line = format!("{t} {}", name.to_string_lossy()); let line = &line[..line.len().min(area.width as usize - 4)]; line.blit(buf, area.x + 2, area.y + 3 + i as u16, Some(if i == self.cursor { Style::default().green() } else { Style::default().white() }))?; } Lozenge(Style::default()).draw(buf, area) } } handle!(AddSampleModal |self,e|{ if handle_keymap(self, e, KEYMAP_ADD_SAMPLE)? { return Ok(true) } Ok(true) }); impl AddSampleModal { pub fn new ( sample: &Arc>, voices: &Arc>> ) -> Usually { let dir = std::env::current_dir()?; let (subdirs, files) = scan(&dir)?; Ok(Self { exited: false, dir, subdirs, files, cursor: 0, offset: 0, sample: sample.clone(), voices: voices.clone(), _search: None }) } fn rescan (&mut self) -> Usually<()> { scan(&self.dir).map(|(subdirs, files)|{ self.subdirs = subdirs; self.files = files; }) } fn prev (&mut self) { self.cursor = self.cursor.saturating_sub(1); } fn next (&mut self) { self.cursor = self.cursor + 1; } fn try_preview (&mut self) -> Usually<()> { if let Some(path) = self.cursor_file() { if let Ok(sample) = Sample::from_file(&path) { *self.sample.write().unwrap() = sample; self.voices.write().unwrap().push( Sample::play(&self.sample, 0, &u7::from(100u8)) ); } //load_sample(&path)?; //let src = std::fs::File::open(&path)?; //let mss = MediaSourceStream::new(Box::new(src), Default::default()); //let mut hint = Hint::new(); //if let Some(ext) = path.extension() { //hint.with_extension(&ext.to_string_lossy()); //} //let meta_opts: MetadataOptions = Default::default(); //let fmt_opts: FormatOptions = Default::default(); //if let Ok(mut probed) = symphonia::default::get_probe() //.format(&hint, mss, &fmt_opts, &meta_opts) //{ //panic!("{:?}", probed.format.metadata()); //}; } Ok(()) } fn cursor_dir (&self) -> Option { if self.cursor < self.subdirs.len() { Some(self.dir.join(&self.subdirs[self.cursor])) } else { None } } fn cursor_file (&self) -> Option { if self.cursor < self.subdirs.len() { return None } let index = self.cursor.saturating_sub(self.subdirs.len()); if index < self.files.len() { Some(self.dir.join(&self.files[index])) } else { None } } fn pick (&mut self) -> Usually { if self.cursor == 0 { if let Some(parent) = self.dir.parent() { self.dir = parent.into(); self.rescan()?; self.cursor = 0; return Ok(false) } } if let Some(dir) = self.cursor_dir() { self.dir = dir; self.rescan()?; self.cursor = 0; return Ok(false) } if let Some(path) = self.cursor_file() { let (end, channels) = read_sample_data(&path.to_string_lossy())?; let mut sample = self.sample.write().unwrap(); sample.name = path.file_name().unwrap().to_string_lossy().into(); sample.end = end; sample.channels = channels; return Ok(true) } return Ok(false) } } pub const KEYMAP_ADD_SAMPLE: &'static [KeyBinding] = keymap!(AddSampleModal { [Esc, NONE, "sampler/add/close", "close help dialog", |modal: &mut AddSampleModal|{ modal.exit(); Ok(true) }], [Up, NONE, "sampler/add/prev", "select previous entry", |modal: &mut AddSampleModal|{ modal.prev(); Ok(true) }], [Down, NONE, "sampler/add/next", "select next entry", |modal: &mut AddSampleModal|{ modal.next(); Ok(true) }], [Enter, NONE, "sampler/add/enter", "activate selected entry", |modal: &mut AddSampleModal|{ if modal.pick()? { modal.exit(); } Ok(true) }], [Char('p'), NONE, "sampler/add/preview", "preview selected entry", |modal: &mut AddSampleModal|{ modal.try_preview()?; Ok(true) }] }); fn scan (dir: &PathBuf) -> Usually<(Vec, Vec)> { let (mut subdirs, mut files) = read_dir(dir)? .fold((vec!["..".into()], vec![]), |(mut subdirs, mut files), entry|{ let entry = entry.expect("failed to read drectory entry"); let meta = entry.metadata().expect("failed to read entry metadata"); if meta.is_file() { files.push(entry.file_name()); } else if meta.is_dir() { subdirs.push(entry.file_name()); } (subdirs, files) }); subdirs.sort(); files.sort(); Ok((subdirs, files)) } impl Sample { fn from_file (path: &PathBuf) -> Usually { let mut sample = Self::default(); sample.name = path.file_name().unwrap().to_string_lossy().into(); // Use file extension if present let mut hint = Hint::new(); if let Some(ext) = path.extension() { hint.with_extension(&ext.to_string_lossy()); } let probed = symphonia::default::get_probe().format( &hint, MediaSourceStream::new( Box::new(File::open(path)?), Default::default(), ), &Default::default(), &Default::default() )?; let mut format = probed.format; let mut decoder = get_codecs().make( &format.tracks().iter() .find(|t| t.codec_params.codec != CODEC_TYPE_NULL) .expect("no tracks found") .codec_params, &Default::default() )?; loop { match format.next_packet() { Ok(packet) => { // Decode a packet let decoded = match decoder.decode(&packet) { Ok(decoded) => decoded, Err(err) => { return Err(err.into()); } }; // Determine sample rate let spec = *decoded.spec(); if let Some(rate) = sample.rate { if rate != spec.rate as usize { panic!("sample rate changed"); } } else { sample.rate = Some(spec.rate as usize); } // Determine channel count while sample.channels.len() < spec.channels.count() { sample.channels.push(vec![]); } // Load sample let mut samples = SampleBuffer::new( decoded.frames() as u64, spec ); if samples.capacity() > 0 { samples.copy_interleaved_ref(decoded); for frame in samples.samples().chunks(spec.channels.count()) { for (chan, frame) in frame.iter().enumerate() { sample.channels[chan].push(*frame) } } } }, Err(Error::IoError(_)) => break decoder.last_decoded(), Err(err) => return Err(err.into()), }; }; sample.end = sample.channels.iter().fold(0, |l, c|l + c.len()); Ok(sample) } }