mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 19:56:42 +01:00
add symphony and dasp for sample preview
This commit is contained in:
parent
ced391d104
commit
d61f2e3c20
5 changed files with 517 additions and 24 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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|{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue