add symphony and dasp for sample preview

This commit is contained in:
🪞👃🪞 2024-07-23 00:45:00 +03:00
parent ced391d104
commit d61f2e3c20
5 changed files with 517 additions and 24 deletions

View file

@ -143,7 +143,7 @@ mod draw_vertical {
pub fn draw_compact (state: &Arranger, buf: &mut Buffer, area: Rect) -> Usually<Rect> {
let track_cols = track_clip_name_lengths(state.tracks.as_slice());
let scene_rows = (0..=state.scenes.len()).map(|i|(96, 96*(i+1))).collect::<Vec<_>>();
let scene_rows = (0..=state.scenes.len()+3).map(|i|(96, 96*i)).collect::<Vec<_>>();
draw(state, buf, area, track_cols.as_slice(), scene_rows.as_slice())
}
@ -301,7 +301,7 @@ mod draw_vertical {
if y > height {
break
}
let h = (pulses / 96) as u16;
let h = 1.max((pulses / 96) as u16);
let area = Rect { x: area.x, y, width: area.width, height: h.min(area.height - y) };
scene_row(state, buf, area, scene, track_cols, offset)?;
y = y + h

View file

@ -2,6 +2,13 @@
use crate::{core::*, view::*, model::MODAL};
use symphonia::core::codecs::{DecoderOptions, CODEC_TYPE_NULL};
use symphonia::core::errors::Error;
use symphonia::core::formats::FormatOptions;
use symphonia::core::io::MediaSourceStream;
use symphonia::core::meta::MetadataOptions;
use symphonia::core::probe::Hint;
/// The sampler plugin plays sounds.
pub struct Sampler {
pub name: String,
@ -292,7 +299,11 @@ exit!(AddSampleModal);
render!(AddSampleModal |self,buf,area|{
make_dim(buf);
let area = center_box(area, 64, 20);
let area = center_box(
area,
64.max(area.width.saturating_sub(8)),
20.max(area.width.saturating_sub(8)),
);
fill_fg(buf, area, Color::Reset);
fill_bg(buf, area, Nord::bg_lo(true, true));
fill_char(buf, area, ' ');
@ -306,13 +317,17 @@ render!(AddSampleModal |self,buf,area|{
.enumerate()
.skip(self.offset)
{
if i >= area.height as usize - 4 {
break
}
let t = if is_dir { "" } else { "" };
format!("{t} {}", name.to_string_lossy())
.blit(buf, area.x + 2, area.y + 3 + i as u16, Some(if i == self.cursor {
Style::default().green()
} else {
Style::default().white()
}))?;
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)
});
@ -351,6 +366,42 @@ impl AddSampleModal {
fn next (&mut self) {
self.cursor = self.cursor + 1;
}
fn try_preview (&mut self) -> Usually<()> {
if let Some(path) = self.cursor_file() {
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<PathBuf> {
if self.cursor < self.subdirs.len() {
Some(self.dir.join(&self.subdirs[self.cursor]))
} else {
None
}
}
fn cursor_file (&self) -> Option<PathBuf> {
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<bool> {
if self.cursor == 0 {
if let Some(parent) = self.dir.parent() {
@ -360,18 +411,16 @@ impl AddSampleModal {
return Ok(false)
}
}
if self.cursor < self.subdirs.len() {
self.dir = self.dir.join(&self.subdirs[self.cursor]);
if let Some(dir) = self.cursor_dir() {
self.dir = dir;
self.rescan()?;
self.cursor = 0;
return Ok(false)
}
if (self.cursor - self.subdirs.len()) < self.files.len() {
let file = &self.files[self.cursor - self.subdirs.len()];
let path = self.dir.join(file);
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 = file.to_string_lossy().into();
sample.name = path.file_name().unwrap().to_string_lossy().into();
sample.end = end;
sample.channels = channels;
return Ok(true)
@ -381,30 +430,33 @@ impl AddSampleModal {
}
pub const KEYMAP_ADD_SAMPLE: &'static [KeyBinding<AddSampleModal>] = keymap!(AddSampleModal {
[Esc, NONE, "add_sample_close", "close help dialog", |modal: &mut AddSampleModal|{
[Esc, NONE, "sampler/add/close", "close help dialog", |modal: &mut AddSampleModal|{
modal.exit();
Ok(true)
}],
[Up, NONE, "add_sample_prev", "select previous entry", |modal: &mut AddSampleModal|{
[Up, NONE, "sampler/add/prev", "select previous entry", |modal: &mut AddSampleModal|{
modal.prev();
Ok(true)
}],
[Down, NONE, "add_sample_next", "select next entry", |modal: &mut AddSampleModal|{
[Down, NONE, "sampler/add/next", "select next entry", |modal: &mut AddSampleModal|{
modal.next();
Ok(true)
}],
[Enter, NONE, "add_sample_enter", "activate selected entry", |modal: &mut AddSampleModal|{
[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<OsString>, Vec<OsString>)> {
Ok(read_dir(dir)?.fold(
(vec!["..".into()], vec![]),
|(mut subdirs, mut files), entry|{
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() {
@ -413,5 +465,105 @@ fn scan (dir: &PathBuf) -> Usually<(Vec<OsString>, Vec<OsString>)> {
subdirs.push(entry.file_name());
}
(subdirs, files)
}))
});
subdirs.sort();
files.sort();
Ok((subdirs, files))
}
fn load_sample () {
// Get the first command line argument.
let args: Vec<String> = std::env::args().collect();
let path = args.get(1).expect("file path not provided");
// Open the media source.
let src = std::fs::File::open(path).expect("failed to open media");
// Create the media source stream.
let mss = MediaSourceStream::new(Box::new(src), Default::default());
// Create a probe hint using the file's extension. [Optional]
let mut hint = Hint::new();
hint.with_extension("mp3");
// Use the default options for metadata and format readers.
let meta_opts: MetadataOptions = Default::default();
let fmt_opts: FormatOptions = Default::default();
// Probe the media source.
let probed = symphonia::default::get_probe()
.format(&hint, mss, &fmt_opts, &meta_opts)
.expect("unsupported format");
// Get the instantiated format reader.
let mut format = probed.format;
// Find the first audio track with a known (decodeable) codec.
let track = format
.tracks()
.iter()
.find(|t| t.codec_params.codec != CODEC_TYPE_NULL)
.expect("no supported audio tracks");
// Use the default options for the decoder.
let dec_opts: DecoderOptions = Default::default();
// Create a decoder for the track.
let mut decoder = symphonia::default::get_codecs()
.make(&track.codec_params, &dec_opts)
.expect("unsupported codec");
// Store the track identifier, it will be used to filter packets.
let track_id = track.id;
// The decode loop.
loop {
// Get the next packet from the media format.
let packet = match format.next_packet() {
Ok(packet) => packet,
Err(Error::ResetRequired) => {
// The track list has been changed. Re-examine it and create a new set of decoders,
// then restart the decode loop. This is an advanced feature and it is not
// unreasonable to consider this "the end." As of v0.5.0, the only usage of this is
// for chained OGG physical streams.
unimplemented!();
}
Err(err) => {
// A unrecoverable error occurred, halt decoding.
panic!("{}", err);
}
};
// Consume any new metadata that has been read since the last packet.
while !format.metadata().is_latest() {
// Pop the old head of the metadata queue.
format.metadata().pop();
// Consume the new metadata at the head of the metadata queue.
}
// If the packet does not belong to the selected track, skip over it.
if packet.track_id() != track_id {
continue;
}
// Decode the packet into audio samples.
match decoder.decode(&packet) {
Ok(_decoded) => {
// Consume the decoded audio samples (see below).
}
Err(Error::IoError(_)) => {
// The packet failed to decode due to an IO error, skip the packet.
continue;
}
Err(Error::DecodeError(_)) => {
// The packet failed to decode due to invalid data, skip the packet.
continue;
}
Err(err) => {
// An unrecoverable error occurred, halt decoding.
panic!("{}", err);
}
}
}
}

View file

@ -108,7 +108,9 @@ impl App {
.collect();
Ok(self)
}
pub fn activate (mut self, init: Option<impl FnOnce(&Arc<RwLock<Self>>)->Usually<()>>) -> Usually<Arc<RwLock<Self>>> {
pub fn activate (
mut self, init: Option<impl FnOnce(&Arc<RwLock<Self>>)->Usually<()>>
) -> Usually<Arc<RwLock<Self>>> {
let jack = self.jack.take().expect("no jack client");
let app = Arc::new(RwLock::new(self));
app.write().unwrap().jack = Some(jack.activate(&app.clone(), |state, client, scope|{