From 6f64c843b69caa1b26e7a21cd0c0b48004f232d1 Mon Sep 17 00:00:00 2001 From: B0ney <40839054+B0ney@users.noreply.github.com> Date: Wed, 10 Jan 2024 12:46:01 +0000 Subject: [PATCH] complete refactor --- src/screen/sample_player/instance.rs | 170 ++++-- src/screen/sample_player/instance/sample.rs | 34 +- src/screen/sample_player/preview_manager.rs | 7 +- src/screen/sample_player/preview_window.rs | 496 ------------------ .../preview_window/sample_info.rs | 61 --- .../preview_window/wave_cache.rs | 35 -- 6 files changed, 155 insertions(+), 648 deletions(-) delete mode 100644 src/screen/sample_player/preview_window.rs delete mode 100644 src/screen/sample_player/preview_window/sample_info.rs delete mode 100644 src/screen/sample_player/preview_window/wave_cache.rs diff --git a/src/screen/sample_player/instance.rs b/src/screen/sample_player/instance.rs index 3337d3ec..e4ea27d1 100644 --- a/src/screen/sample_player/instance.rs +++ b/src/screen/sample_player/instance.rs @@ -8,8 +8,8 @@ use iced::widget::{button, checkbox, column, progress_bar, row, scrollable, slid use iced::{command, Alignment, Command, Length}; use crate::screen::main_panel::Entries; -use crate::widget::helpers::{fill_container, warning}; -use crate::widget::waveform_view::Marker; +use crate::widget::helpers::{centered_container, fill_container, warning}; +use crate::widget::waveform_view::{Marker, WaveData}; use crate::widget::{Button, Collection, Container, Element, Row, WaveformViewer}; use crate::{icon, theme}; @@ -27,7 +27,7 @@ pub enum Message { SetPlayOnSelection(bool), SetVolume(f32), AddEntry(PathBuf), - Loaded(Result), + Loaded(Result), Progress(Option), } @@ -41,20 +41,28 @@ pub enum State { Failed { path: PathBuf, reason: String }, /// Successfully loaded samples Loaded { - path: PathBuf, - module_name: String, selected: Option, samples: SamplePack, }, } -#[derive(Debug, Default)] +#[derive(Debug, Clone, Copy)] pub struct MediaSettings { pub volume: f32, pub play_on_selection: bool, pub enable_looping: bool, } +impl Default for MediaSettings { + fn default() -> Self { + Self { + volume: 1.0, + play_on_selection: true, + enable_looping: false, + } + } +} + /// Sample player instance pub struct Instance { state: State, @@ -89,26 +97,46 @@ impl Instance { pub fn update(&mut self, message: Message, entries: &mut Entries) -> Command { match message { - Message::Select(index) => match &mut self.state { - State::Loaded { selected, .. } => *selected = Some(index), - _ => (), - }, + Message::Select(index) => { + if let State::Loaded { selected, .. } = &mut self.state { + *selected = Some(index); + + self.player.stop(); + + if self.settings.play_on_selection { + return self.play_selected(); + } + } + } Message::Play => return self.play_selected(), - Message::Pause => todo!(), - Message::Stop => todo!(), - Message::SetPlayOnSelection(_) => todo!(), - Message::AddEntry(_) => todo!(), - Message::Loaded(_) => todo!(), - Message::SetVolume(_) => todo!(), + Message::Pause => self.player.pause(), + Message::Stop => self.player.stop(), + Message::SetPlayOnSelection(toggle) => self.settings.play_on_selection = toggle, + Message::AddEntry(path) => entries.add(path), + Message::Loaded(result) => { + self.state = match result { + Ok(samples) => State::Loaded { + selected: None, + samples, + }, + Err((path, reason)) => State::Failed { path, reason }, + } + } + Message::SetVolume(volume) => { + self.player.set_volume(volume); + self.settings.volume = volume; + } Message::Progress(p) => self.progress = p, } Command::none() } pub fn view(&self, entries: &Entries) -> Element { - let top_left = column![self.view_sample_info(), self.media_buttons()] - .spacing(5) - .width(Length::Fill); + let info = fill_container(self.view_sample_info()) + .padding(8) + .style(theme::Container::Black); + + let top_left = column![info, self.media_buttons()].spacing(5).width(Length::Fill); let top_right_controls = { let add_path_button = self.loaded_path().and_then(|path| { @@ -127,14 +155,20 @@ impl Instance { ) .style(theme::CheckBox::Inverted); - row![play_on_selection_checkbox] + row![play_on_selection_checkbox, Space::with_width(Length::Fill)] .push_maybe(no_button_spacing) .push_maybe(add_path_button) .spacing(5) .align_items(Alignment::Center) }; - let top_right = column![self.view_samples(), top_right_controls]; + let sample_list = fill_container(self.view_samples()) + .padding(8) + .style(theme::Container::Black); + + let top_right = column![sample_list, top_right_controls] + .spacing(5) + .width(Length::Fill); let waveform_viewer = self .view_waveform() @@ -148,16 +182,11 @@ impl Instance { let warning = warning(|| false, "WARNING - This sample is most likely static noise."); - let top_half = row![] - .push(top_left) - .push(top_right) + let top_half = row![top_left, top_right] .height(Length::FillPortion(3)) .spacing(5); - let main = column![] - .push(top_half) - .push(waveform_viewer) - .push(progress) + let main = column![top_half, waveform_viewer, progress] .push_maybe(warning) .spacing(5); @@ -169,24 +198,25 @@ impl Instance { pub fn matches_path(&self, module_path: &Path) -> bool { match &self.state { - State::Failed { path, .. } | - State::Loaded { path, .. } => path == module_path, - _ => false + State::Failed { path, .. } => path == module_path, + State::Loaded { samples, .. } => samples.path() == module_path, + _ => false, } } pub fn load_samples(&mut self, module_path: PathBuf) -> Command { - let load = |state: &mut State, path: &PathBuf| { + let load = |state: &mut State, path: PathBuf| { *state = State::Loading; - return todo!(); + self.player.stop(); + return load_samples(path); }; match &self.state { - State::None => load(&mut self.state, &module_path), + State::None => load(&mut self.state, module_path), State::Loading => Command::none(), - State::Failed { path, .. } | State::Loaded { path, .. } => match path == &module_path { + _ => match self.matches_path(&module_path) { true => Command::none(), - false => load(&mut self.state, &module_path), + false => load(&mut self.state, module_path), }, } } @@ -195,8 +225,8 @@ impl Instance { match &self.state { State::None => "No samples loaded!".into(), State::Loading => "Loading...".into(), - State::Failed { reason, path } => "Failed to open...".into(), - State::Loaded { module_name, .. } => format!("Loaded: \"{}\"", module_name), + State::Failed { path, .. } => format!("Failed to open {}", path.display()), + State::Loaded { samples, .. } => format!("Loaded: \"{}\"", samples.name()), } } @@ -214,7 +244,7 @@ impl Instance { pub fn loaded_path(&self) -> Option<&Path> { match &self.state { - State::Loaded { path, .. } => Some(path), + State::Loaded { samples, .. } => Some(samples.path()), _ => None, } } @@ -222,14 +252,14 @@ impl Instance { /// top left quadrant fn view_sample_info(&self) -> Element { match &self.state { - State::None => todo!(), - State::Loading => todo!(), - State::Failed { reason, .. } => todo!(), + State::None => centered_container("Nothing Loaded...").into(), + State::Loading => centered_container("Loading...").into(), + State::Failed { reason, .. } => centered_container(text(reason)).into(), State::Loaded { selected, samples, .. } => match selected { - Some(_) => todo!(), - None => todo!(), + Some(index) => samples.view_sample_info(*index), + None => centered_container("Nothing selected...").into(), }, } } @@ -237,9 +267,9 @@ impl Instance { /// List out the samples fn view_samples(&self) -> Element { match &self.state { - State::None => todo!(), - State::Loading => todo!(), - State::Failed { path, reason } => todo!(), + State::None => centered_container("Drag and drop a module to preview").into(), + State::Loading => centered_container("Loading...").into(), + State::Failed { .. } => centered_container("ERROR").into(), State::Loaded { samples, .. } => { let samples = samples .inner() @@ -316,7 +346,7 @@ const PLAY_CURSOR_FPS: f32 = 60.0; fn play_sample(handle: &PlayerHandle, source: TrackerSample) -> Command { let (sender, mut receiver) = tokio::sync::mpsc::unbounded_channel::(); - + handle.stop(); handle.play_with_callback(source, move |sample: &TrackerSample, duration: &mut Instant| { let fps_interval = Duration::from_millis(((1.0 / PLAY_CURSOR_FPS) * 1000.0).round() as u64); @@ -335,3 +365,47 @@ fn play_sample(handle: &PlayerHandle, source: TrackerSample) -> Command let _ = s.try_send(Message::Progress(None)); }) } + +fn load_samples(path: PathBuf) -> Command { + Command::perform( + async { + let path_copy = path.clone(); + + tokio::task::spawn_blocking(move || { + const MAX_SIZE: u64 = 40 * 1024 * 1024; + + let mut file = std::fs::File::open(&path)?; + + if file.metadata()?.len() > MAX_SIZE { + return Err(xmodits_lib::Error::io_error("File size exceeds 40 MB").unwrap_err()); + } + + let module = xmodits_lib::load_module(&mut file)?; + let sample_pack = audio_engine::SamplePack::build(&*module); + let name = sample_pack.name; + + let samples = sample_pack + .samples + .into_iter() + .map(|result| match result { + Ok((metadata, buffer)) => { + let peaks = buffer.buf.peaks(Duration::from_millis(5)); + let waveform = WaveData::from(peaks); + SampleResult::Valid { + metadata, + buffer, + waveform, + } + } + Err(error) => SampleResult::Invalid(error.to_string()), + }) + .collect(); + Ok(SamplePack::new(name, path, samples)) + }) + .await + .unwrap() + .map_err(|e| (path_copy, e.to_string())) + }, + Message::Loaded, + ) +} diff --git a/src/screen/sample_player/instance/sample.rs b/src/screen/sample_player/instance/sample.rs index 055f1eb1..806a3c05 100644 --- a/src/screen/sample_player/instance/sample.rs +++ b/src/screen/sample_player/instance/sample.rs @@ -1,3 +1,5 @@ +use std::path::Path; +use std::path::PathBuf; use std::time::Duration; use crate::icon; @@ -16,15 +18,27 @@ use super::Message; #[derive(Debug, Clone)] -pub struct SamplePack(Vec); +pub struct SamplePack { + name: String, + path: PathBuf, + samples: Vec +} impl SamplePack { + pub fn new(name: String, path: PathBuf, samples: Vec) -> Self { + Self { + name, + path, + samples + } + } + pub fn inner(&self) -> &[SampleResult] { - &self.0 + &self.samples } pub fn waveform(&self, index: usize) -> Option<&WaveData> { - self.0.get(index).and_then(SampleResult::waveform) + self.samples.get(index).and_then(SampleResult::waveform) } pub fn view_sample_info(&self, index: usize) -> Element { @@ -34,6 +48,14 @@ impl SamplePack { pub fn tracker_sample(&self, index: usize) -> Option { self.inner().get(index).and_then(SampleResult::tracker_sample) } + + pub fn path(&self) -> &Path { + &self.path + } + + pub fn name(&self) -> &str { + &self.name + } } #[derive(Debug, Clone)] @@ -56,8 +78,8 @@ impl SampleResult { pub fn title(&self) -> String { match self { - SampleResult::Invalid(_) => todo!(), - SampleResult::Valid { metadata, .. } => todo!(), + SampleResult::Invalid(_) => "ERROR".into(), + SampleResult::Valid { metadata, .. } => metadata.filename_pretty().into(), } } @@ -105,7 +127,7 @@ impl SampleResult { pub fn view_sample_info(&self) -> Element { match self { - SampleResult::Invalid(_) => todo!(), + SampleResult::Invalid(reason) => centered_container(text(reason)).into(), SampleResult::Valid { metadata, .. } => { let smp = metadata; diff --git a/src/screen/sample_player/preview_manager.rs b/src/screen/sample_player/preview_manager.rs index a3f6e563..db25d617 100644 --- a/src/screen/sample_player/preview_manager.rs +++ b/src/screen/sample_player/preview_manager.rs @@ -1,4 +1,4 @@ -use super::instance::{self, Instance}; +use super::instance::{self, Instance, MediaSettings}; use iced::window::{self, Id}; use iced::{Command, Size}; @@ -25,6 +25,7 @@ pub struct SamplePreview { audio_engine: SamplePlayer, windows: HashMap, singleton: bool, + default_settings: MediaSettings, } impl Default for SamplePreview { @@ -33,6 +34,7 @@ impl Default for SamplePreview { audio_engine: Default::default(), windows: Default::default(), singleton: false, + default_settings: Default::default(), } } } @@ -96,7 +98,7 @@ impl SamplePreview { let (instance, load_samples) = Instance::new(self.audio_engine.create_handle(), path); - self.windows.insert(id, instance); + self.windows.insert(id, instance.settings(self.default_settings)); Command::batch([ spawn_window, @@ -112,6 +114,7 @@ impl SamplePreview { self.get_window_mut(id).hovered = hovered; } + // TODO: flash window of already loaded sample if user attempts to load duplicate pub fn load_samples(&mut self, id: Id, path: PathBuf) -> Command { self.get_window_mut(id) .load_samples(path) diff --git a/src/screen/sample_player/preview_window.rs b/src/screen/sample_player/preview_window.rs deleted file mode 100644 index dd8bcdef..00000000 --- a/src/screen/sample_player/preview_window.rs +++ /dev/null @@ -1,496 +0,0 @@ -mod sample_info; -mod wave_cache; - -use std::fs::File; -use std::path::{Path, PathBuf}; -use std::sync::Arc; -use std::time::{Duration, Instant}; - -use audio_engine::{PlayerHandle, Sample, SamplePack, TrackerSample}; -use iced::widget::{ - button, checkbox, column, horizontal_rule, progress_bar, row, scrollable, slider, text, Space, -}; -use iced::window::Id; -use iced::{command, Alignment, Command, Length}; -use tokio::sync::mpsc; - -use crate::screen::main_panel::Entries; -use crate::widget::helpers::{centered_container, fill_container, warning}; -use crate::widget::waveform_view::{Marker, WaveData, WaveformViewer}; -use crate::widget::{Button, Collection, Container, Element, Row}; -use crate::{icon, theme}; - -use sample_info::SampleInfo; -use wave_cache::WaveCache; - -const MAX_VOLUME: f32 = 1.25; -const MIN_VOLUME: f32 = 0.0; - -#[derive(Debug, Clone)] -pub enum Message { - Play, - Pause, - Stop, - Volume(f32), - Progress(Option), - Loaded(Arc>), - Load(PathBuf), - Info((usize, SampleInfo)), - SetPlayOnSelect(bool), - AddEntry(PathBuf), -} - -#[derive(Default)] -enum State { - #[default] - None, - Loading, - Failed { - reason: String, - path: PathBuf, - }, - Loaded { - // path: PathBuf, - sample_pack: SamplePack, - wave_data: WaveCache, - selected: SampleInfo, - play_progress: Option, - }, -} - -impl State { - fn wave_cache(&self) -> Option<&WaveData> { - match &self { - State::Loaded { selected, .. } => selected.waveform(), - _ => None, - } - } -} - -#[derive(Debug, Clone, Copy)] -pub struct MediaSettings { - pub volume: f32, - pub play_on_selection: bool, - pub enable_looping: bool, -} - -impl Default for MediaSettings { - fn default() -> Self { - Self { - volume: 1.0, - play_on_selection: true, - enable_looping: false, - } - } -} - -pub struct SamplePreviewWindow { - state: State, - player: PlayerHandle, - settings: MediaSettings, - pub hovered: bool, -} - -impl SamplePreviewWindow { - pub fn create(id: Id, player: PlayerHandle) -> Self { - Self { - player, - hovered: false, - state: State::None, - settings: Default::default(), - } - } - - pub fn can_load(&self, new_path: &Path) -> bool { - match &self.state { - State::None => true, - State::Loading => false, - State::Failed { path, .. } => path != new_path, - State::Loaded { sample_pack, .. } => !sample_pack.matches_path(new_path), - } - } - - pub fn play(&mut self) -> Command { - self.player.stop(); - - match self.get_selected() { - Some(sample) => play_sample(&self.player, sample), - None => Command::none(), - } - } - - pub fn update(&mut self, msg: Message, entries: &mut Entries) -> Command { - match msg { - Message::Play => return self.play(), - Message::Pause => self.player.pause(), - Message::Stop => self.player.stop(), - Message::Volume(vol) => { - self.settings.volume = vol.clamp(MIN_VOLUME, MAX_VOLUME); - self.player.set_volume(self.settings.volume) - } - Message::Load(path) => { - return match self.can_load(&path) { - true => { - self.state = State::Loading; - tracing::info!("loading"); - load_sample_pack(path) - } - false => Command::none(), - }; - } - Message::Loaded(result) => match Arc::into_inner(result).unwrap() { - Ok((sample_pack, wave_data)) => { - self.player.stop(); - self.state = State::Loaded { - sample_pack, - wave_data, - selected: SampleInfo::None, - play_progress: None, - } - } - Err(err) => tracing::error!("{}", err), - }, - Message::Info(smp) => { - if let State::Loaded { selected, .. } = &mut self.state { - *selected = smp.1; - - match self.settings.play_on_selection { - true => return self.play(), - false => self.player.stop(), - } - }; - } - Message::SetPlayOnSelect(play_on_select) => self.settings.play_on_selection = play_on_select, - Message::Progress(progress) => { - if let State::Loaded { play_progress, .. } = &mut self.state { - *play_progress = progress - } - } - Message::AddEntry(path) => entries.add(path), - } - Command::none() - } - - pub fn view(&self, entries: &Entries) -> Element { - let media_controls = media_button([ - (icon::play().size(18), Message::Play), - (icon::stop().size(18), Message::Stop), - (icon::pause().size(18), Message::Pause), - // (icon::repeat().size(18), Message::Stop), - ]); - - let volume_slider = column![ - text(format!("Volume: {}%", (self.settings.volume * 100.0).round())), - slider(MIN_VOLUME..=MAX_VOLUME, self.settings.volume, Message::Volume).step(0.01) - ] - .align_items(Alignment::Start); - - let control_panel = Container::new(row![media_controls, volume_slider].spacing(8)) - .padding(8) - .style(theme::Container::Black) - .width(Length::Fill) - .height(Length::Shrink) - .center_x(); - - let top_left = column![ - fill_container(view_sample_info(self.get_selected_info())) - .padding(8) - .style(theme::Container::Black), - control_panel - ] - .spacing(5) - .width(Length::Fill); - - let sample_list = view_sample_list(&self.state); - - let add_path_button = self.path().and_then(|path| { - let button = || button("Add to Entries").on_press(Message::AddEntry(path.to_owned())); - (!entries.contains(path)).then(button) - }); - - let no_button_spacing = add_path_button - .is_none() - .then_some(Space::with_height(Length::Fixed(27.0))); - - let top_right = column![ - fill_container(sample_list) - .padding(8) - .style(theme::Container::Black), - row![ - checkbox( - "Play on Selection", - self.settings.play_on_selection, - Message::SetPlayOnSelect - ) - .style(theme::CheckBox::Inverted), - Space::with_width(Length::Fill) - ] - .push_maybe(no_button_spacing) - .push_maybe(add_path_button) - .spacing(5) - .align_items(Alignment::Center) - ] - .spacing(5) - .width(Length::Fill); - - let waveform_viewer = WaveformViewer::new_maybe(self.state.wave_cache()) - .marker_maybe(self.progress().map(Marker)) - .width(Length::Fill) - .height(Length::FillPortion(2)); - - let warning = warning(|| false, "WARNING - This sample is most likely static noise."); - - let main = column![ - row![top_left, top_right] - .height(Length::FillPortion(3)) - .spacing(5), - waveform_viewer, - progress_bar(0.0..=1.0, self.progress().unwrap_or_default()) - .height(5.0) - .style(theme::ProgressBar::Dark) - ] - .push_maybe(warning) - .spacing(5); - - fill_container(main) - .style(theme::Container::Hovered(self.hovered)) - .padding(15) - .into() - } - - pub fn progress(&self) -> Option { - match &self.state { - State::Loaded { play_progress, .. } => *play_progress, - _ => None, - } - } - - pub fn title(&self) -> String { - match &self.state { - State::None => "No samples loaded!".into(), - State::Loading => "Loading...".into(), - State::Failed { reason, path } => "Failed to open...".into(), - State::Loaded { sample_pack, .. } => format!("Loaded: \"{}\"", sample_pack.name), - } - } - - pub fn matches_path(&self, path: &Path) -> bool { - self.path().is_some_and(|s| s == path) - } - - pub fn path(&self) -> Option<&Path> { - match &self.state { - State::Loaded { sample_pack, .. } => sample_pack.path.as_deref(), - _ => None, - } - } - - pub fn load_sample_pack(&self, path: PathBuf) -> Command { - match self.can_load(&path) { - true => load_sample_pack(path), - false => Command::none(), - } - } - - fn get_selected(&self) -> Option { - let State::Loaded { selected, .. } = &self.state else { - return None; - }; - - match selected { - SampleInfo::Sample { data, .. } => Some(data.clone()), - _ => None, - } - } - - fn get_selected_info(&self) -> Option<&SampleInfo> { - match &self.state { - State::Loaded { selected, .. } => Some(selected), - _ => None, - } - } -} - -fn media_button<'a, Label, R, Message>(rows: R) -> Element<'a, Message> -where - Message: Clone + 'a, - Label: Into>, - R: IntoIterator, -{ - let mut media_row: Row<'a, Message> = Row::new().spacing(4.0); - let elements: Vec<(Label, Message)> = rows.into_iter().collect(); - let end_indx = elements.len() - 1; - - for (idx, (label, message)) in elements.into_iter().enumerate() { - let style = if idx == 0 { - theme::Button::MediaStart - } else if idx == end_indx { - theme::Button::MediaEnd - } else { - theme::Button::MediaMiddle - }; - let button = Button::new(label).padding(8.0).on_press(message).style(style); - media_row = media_row.push(button); - } - - media_row.into() -} - -fn view_sample_list(state: &State) -> Element { - match state { - State::None => centered_container("Nothing Loaded").into(), - State::Loading => centered_container("Loading...").into(), - State::Failed { reason, path } => centered_container("").into(), - State::Loaded { sample_pack, .. } => view_samples(&sample_pack.samples), - } -} - -const PLAY_CURSOR_FPS: f32 = 60.0; - -fn play_sample(handle: &PlayerHandle, source: TrackerSample) -> Command { - let (sender, mut receiver) = mpsc::unbounded_channel::(); - - let callback = move |sample: &TrackerSample, duration: &mut Instant| { - let fps_interval = Duration::from_millis(((1.0 / PLAY_CURSOR_FPS) * 1000.0).round() as u64); - - if duration.elapsed() > fps_interval { - *duration = Instant::now(); - let progress = sample.frame() as f32 / sample.buf.frames() as f32; - let _ = sender.send(progress); - } - }; - - handle.play_with_callback(source, callback); - - command::channel(256, |mut s| async move { - while let Some(new_progress) = receiver.recv().await { - let _ = s.try_send(Message::Progress(Some(new_progress))); - } - let _ = s.try_send(Message::Progress(None)); - }) -} - -fn load_sample_pack(path: PathBuf) -> Command { - Command::perform( - async { - tokio::task::spawn_blocking(move || { - const MAX_SIZE: u64 = 40 * 1024 * 1024; - - let mut file = File::open(&path)?; - - if file.metadata()?.len() > MAX_SIZE { - return Err(xmodits_lib::Error::io_error("File size exceeds 40 MB").unwrap_err()); - } - - let module = xmodits_lib::load_module(&mut file)?; - let sample_pack = SamplePack::build(&*module).with_path(path); - let wave_cache = WaveCache::from_sample_pack(&sample_pack); - - Ok((sample_pack, wave_cache)) - }) - .await - .map(Arc::new) - .unwrap() - }, - Message::Loaded, - ) -} - -fn view_samples(a: &[Result<(Sample, TrackerSample), xmodits_lib::Error>]) -> Element { - let samples = a.iter().enumerate().map(view_sample).collect(); - scrollable(column(samples).spacing(10).padding(4)).into() -} - -fn view_sample( - (index, result): (usize, &Result<(Sample, TrackerSample), xmodits_lib::Error>), -) -> Element { - let info = SampleInfo::new(index, result); - - let error_icon = || { - row![] - .push(Space::with_width(Length::Fill)) - .push(icon::warning()) - .align_items(iced::Alignment::Center) - }; - - let title = row![] - .push(text(match info.title() { - title if title.is_empty() => format!("{}", index + 1), - title => format!("{} - {}", index + 1, title), - })) - .push_maybe(info.is_error().then_some(error_icon())) - .spacing(5); - - let theme = match info.is_error() { - true => theme::Button::EntryError, - false => theme::Button::Entry, - }; - - row![ - button(title) - .width(Length::Fill) - .style(theme) - .on_press(Message::Info((index, info))), - Space::with_width(15) - ] - .into() -} - -fn view_sample_info(info: Option<&SampleInfo>) -> Element { - match info { - None => centered_container("Nothing selected...").into(), - Some(info) => match info { - SampleInfo::None => centered_container("Nothing selected...").into(), - SampleInfo::Invalid { reason, .. } => centered_container(text(reason)).into(), - SampleInfo::Sample { metadata, .. } => { - let smp = metadata; - - let sample_name = - (!smp.name.trim().is_empty()).then_some(text(format!("Name: {}", smp.name.trim()))); - - let sample_filename = smp - .filename - .as_ref() - .map(|s| s.trim()) - .and_then(|s| (!s.is_empty()).then_some(text(format!("File Name: {}", s)))); - - let metadata = text(format!( - "{} Hz, {}-bit ({}), {}", - smp.rate, - smp.bits(), - if smp.is_signed() { "Signed" } else { "Unsigned" }, - if smp.is_stereo() { "Stereo" } else { "Mono" }, - )); - - let round_100th = |x: f32| (x * 100.0).round() / 100.0; - - let duration = Duration::from_micros( - ((smp.length_frames() as f64 / smp.rate as f64) * 1_000_000.0) as u64, - ); - let duration_secs = round_100th(duration.as_secs_f32()); - let plural = if duration_secs == 1.0 { "" } else { "s" }; - let duration = text(format!("Duration: {} sec{plural}", duration_secs)); - - let size = match smp.length { - l if l < 1000 => format!("{} bytes", l), - l if l < 1_000_000 => format!("{} KB", round_100th(l as f32 / 1000.0)), - l => format!("{} MB", round_100th(l as f32 / 1_000_000.0)), - }; - - let info = column![] - .push_maybe(sample_name) - .push_maybe(sample_filename) - .push(duration) - .push(text(format!("Size: {}", size))) - .push(text(format!("Loop type: {:#?}", smp.looping.kind()))) - .push(text(format!("Internal Index: {}", smp.index_raw()))) - .push(horizontal_rule(1)) - .push(metadata) - .push(horizontal_rule(1)) - .spacing(5) - .align_items(Alignment::Center); - centered_container(info).into() - } - }, - } -} diff --git a/src/screen/sample_player/preview_window/sample_info.rs b/src/screen/sample_player/preview_window/sample_info.rs deleted file mode 100644 index 75de9de9..00000000 --- a/src/screen/sample_player/preview_window/sample_info.rs +++ /dev/null @@ -1,61 +0,0 @@ -use std::{rc::Rc, sync::Arc}; - -use audio_engine::{Sample, TrackerSample}; - -use crate::widget::waveform_view::WaveData; - -#[derive(Debug, Clone)] -pub enum SampleInfo { - None, - Invalid { - reason: String, - }, - Sample { - metadata: Sample, - data: TrackerSample, - waveform: Arc, - }, -} - -impl SampleInfo { - pub fn new( - result: &Result<(Sample, TrackerSample), xmodits_lib::Error>, - wavedata: Arc, - ) -> Self { - match result { - Ok((metadata, data)) => Self::Sample { - metadata: metadata.clone(), - data: data.clone(), - waveform: wavedata - }, - Err(error) => Self::Invalid { - reason: error.to_string(), - }, - } - } - pub fn title(&self) -> String { - match &self { - Self::Sample { metadata, .. } => { - let smp = metadata; - let filename = smp.filename_pretty(); - match filename.is_empty() { - true => smp.name_pretty().into(), - false => filename.into(), - } - } - Self::Invalid { .. } => "ERROR".into(), - Self::None => "None Selected".into(), - } - } - - pub fn is_error(&self) -> bool { - matches!(self, Self::Invalid { .. }) - } - - pub fn waveform(&self) -> Option<&WaveData> { - match &self { - SampleInfo::Sample { waveform, .. } => Some(waveform.as_ref()), - _ => None - } - } -} diff --git a/src/screen/sample_player/preview_window/wave_cache.rs b/src/screen/sample_player/preview_window/wave_cache.rs deleted file mode 100644 index 36f0d072..00000000 --- a/src/screen/sample_player/preview_window/wave_cache.rs +++ /dev/null @@ -1,35 +0,0 @@ -use std::{collections::HashMap, sync::Arc}; -use std::time::Duration; - -use audio_engine::{SamplePack, TrackerSample}; - -use crate::widget::waveform_view::WaveData; - -#[derive(Debug, Default)] -pub struct WaveCache { - pub cache: HashMap>, -} - -impl WaveCache { - pub fn from_sample_pack(sample_pack: &SamplePack) -> Self { - let mut wave_cache = Self::default(); - - for (idx, result) in sample_pack.samples.iter().enumerate() { - if let Ok((_, sample)) = result { - wave_cache.generate(idx, sample) - } - } - - wave_cache - } - - pub fn generate(&mut self, index: usize, sample: &TrackerSample) { - let peaks = sample.buf.peaks(Duration::from_millis(5)); - self.cache.insert(index, Arc::new(WaveData::from(peaks))); - } - - pub fn get(&self, index: usize) -> Option<&WaveData> { - Some(self.cache.get(&index)?.as_ref()) - - } -} \ No newline at end of file