Skip to content

Commit

Permalink
rewrite
Browse files Browse the repository at this point in the history
  • Loading branch information
B0ney committed Jan 9, 2024
1 parent 86681d8 commit 0f0aaed
Show file tree
Hide file tree
Showing 4 changed files with 376 additions and 0 deletions.
2 changes: 2 additions & 0 deletions audio_engine/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ mod sample_pack;
pub use player::{PlayerHandle, SamplePlayer};
pub use sample::{SampleBuffer, TrackerSample};
pub use sample_pack::SamplePack;
pub use xmodits_lib::Sample as Metadata;
pub use xmodits_lib::Sample;

1 change: 1 addition & 0 deletions src/screen/sample_player.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
mod preview_manager;
#[cfg(feature = "audio")]
mod preview_window;
pub mod instance;

#[cfg(feature = "audio")]
pub use preview_manager::*;
Expand Down
225 changes: 225 additions & 0 deletions src/screen/sample_player/instance.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
mod sample;

use std::path::PathBuf;

use audio_engine::PlayerHandle;
use iced::widget::{column, row, scrollable, slider, text, Space};
use iced::{Alignment, Command, Length};

use crate::screen::main_panel::Entries;
use crate::widget::{waveform_view::WaveformViewer, Element};
use crate::widget::{Button, Container, Row};
use crate::{icon, theme};

use sample::{SamplePack, SampleResult};

const MAX_VOLUME: f32 = 1.25;
const MIN_VOLUME: f32 = 0.0;

#[derive(Debug, Clone)]
pub enum Message {
Select(usize),
Play,
Pause,
Stop,
SetPlayOnSelection(bool),
SetVolume(f32),
AddEntry(PathBuf),
Loaded(Result<SamplePack, String>),
}

/// The state of the sample player
pub enum State {
/// Nothing has been loaded
None,
/// Currently loading
Loading,
/// Could not load samples
Failed { path: PathBuf, reason: String },
/// Successfully loaded samples
Loaded {
path: PathBuf,
module_name: String,
selected: Option<usize>,
samples: SamplePack,
},
}

#[derive(Debug, Default)]
pub struct MediaSettings {
pub volume: f32,
pub play_on_selection: bool,
pub enable_looping: bool,
}

/// Sample player instance
pub struct Instance {
state: State,
player: PlayerHandle,
settings: MediaSettings,
hovered: bool,
progress: Option<f32>,
}

impl Instance {
pub fn new(player: PlayerHandle, path: PathBuf) -> (Self, Command<Message>) {
let mut instance = Self::new_empty(player);
let command = instance.load_samples(path);

(instance, command)
}

pub fn new_empty(player: PlayerHandle) -> Self {
Self {
state: State::None,
player,
settings: MediaSettings::default(),
hovered: false,
progress: None,
}
}

pub fn settings(mut self, settings: MediaSettings) -> Self {
self.settings = settings;
self
}

pub fn update(&mut self, entries: &mut Entries, message: Message) -> Command<Message> {
match message {
Message::Select(index) => match &self.state {
State::Loaded { selected, .. } => *selected = Some(index),
_ => (),
},
Message::Play => todo!(),
Message::Pause => todo!(),
Message::Stop => todo!(),
Message::SetPlayOnSelection(_) => todo!(),
Message::AddEntry(_) => todo!(),
Message::Loaded(_) => todo!(),
Message::SetVolume(_) => todo!(),
}
Command::none()
}

pub fn load_samples(&mut self, module_path: PathBuf) -> Command<Message> {
let mut load = |path: &PathBuf| {
self.state = State::Loading;
return todo!();
};

match &self.state {
State::None => load(&module_path),
State::Loading => Command::none(),
State::Failed { path, .. } | State::Loaded { path, .. } => match path == &module_path {
true => Command::none(),
false => load(&module_path),
},
}
}

pub fn view(&self, entries: &Entries) -> Element<Message> {
todo!()
}

pub fn title(&self) -> String {
todo!()
}

/// top left quadrant
fn view_sample_info(&self) -> Element<Message> {
match &self.state {
State::None => todo!(),
State::Loading => todo!(),
State::Failed { reason, .. } => todo!(),
State::Loaded {
selected, samples, ..
} => match selected {
Some(_) => todo!(),
None => todo!(),
},
}
}

/// List out the samples
fn view_samples(&self) -> Element<Message> {
match self.state {
State::None => todo!(),
State::Loading => todo!(),
State::Failed { path, reason } => todo!(),
State::Loaded { samples, .. } => {
let samples = samples
.inner()
.iter()
.enumerate()
.map(|(index, result)| result.view_sample(index))
.collect();

let content = column(samples).spacing(10).padding(4);

scrollable(content).into()
}
}
}

fn view_waveform(&self) -> Element<Message> {
WaveformViewer::new_maybe(match &self.state {
State::Loaded {
path,
module_name,
selected,
samples,
} => selected.and_then(|index| samples.waveform(index)),
_ => None,
})
.into()
}

fn media_buttons(&self) -> Element<Message> {
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::SetVolume).step(0.01)
]
.align_items(Alignment::Start);

Container::new(row![media_controls, volume_slider].spacing(8))
.padding(8)
.style(theme::Container::Black)
.width(Length::Fill)
.height(Length::Shrink)
.center_x()
.into()
}
}


fn media_button<'a, Label, R, Message>(rows: R) -> Element<'a, Message>
where
Message: Clone + 'a,
Label: Into<Element<'a, Message>>,
R: IntoIterator<Item = (Label, Message)>,
{
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()
}
148 changes: 148 additions & 0 deletions src/screen/sample_player/instance/sample.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
use std::time::Duration;

use crate::icon;
use crate::theme;
use crate::widget::helpers::centered_container;
use crate::widget::waveform_view::WaveData;
use crate::widget::{Button, Collection, Container, Element, Row};

use audio_engine;

use iced::widget::{button, horizontal_rule, row, text, Space, column};
use iced::{Alignment, Length};

use super::Message;


#[derive(Debug, Clone)]
pub struct SamplePack(Vec<SampleResult>);

impl SamplePack {
pub fn waveform(&self, index: usize) -> Option<&WaveData> {
self.0.get(index).and_then(SampleResult::waveform)
}

pub fn inner(&self) -> &[SampleResult] {
&self.0
}

pub fn view_sample_info(&self, index: usize) -> Element<Message> {
self.inner()[index].view_sample_info()
}
}

#[derive(Debug, Clone)]
pub enum SampleResult {
Invalid(String),
Valid {
metadata: audio_engine::Metadata,
buffer: (),
waveform: WaveData,
},
}

impl SampleResult {
pub fn waveform(&self) -> Option<&WaveData> {
match self {
SampleResult::Invalid(_) => None,
SampleResult::Valid { waveform, .. } => Some(waveform),
}
}

pub fn title(&self) -> String {
match self {
SampleResult::Invalid(_) => todo!(),
SampleResult::Valid { metadata, .. } => todo!(),
}
}

pub fn is_invalid(&self) -> bool {
matches!(self, Self::Invalid(_))
}

pub fn view_sample(&self, index: usize) -> Element<Message> {
let error_icon = || {
row![]
.push(Space::with_width(Length::Fill))
.push(icon::warning())
.align_items(iced::Alignment::Center)
};

let title = row![]
.push(text(match self.title() {
title if title.is_empty() => format!("{}", index + 1),
title => format!("{} - {}", index + 1, title),
}))
.push_maybe(self.is_invalid().then_some(error_icon()))
.spacing(5);

let theme = match self.is_invalid() {
true => theme::Button::EntryError,
false => theme::Button::Entry,
};

row![
button(title)
.width(Length::Fill)
.style(theme)
.on_press(Message::Select(index)),
Space::with_width(15)
]
.into()
}

pub fn view_sample_info(&self) -> Element<Message> {
match self {
SampleResult::Invalid(_) => todo!(),
SampleResult::Valid { 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()
}
}
}
}

0 comments on commit 0f0aaed

Please sign in to comment.