diff --git a/Cargo.lock b/Cargo.lock index 7708fb6..c6940dc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1029,6 +1029,15 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c297a1c74b71ae29df00c3e22dd9534821d60eb9af5a0192823fa2acea70c2a" +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + [[package]] name = "derivative" version = "2.2.0" @@ -2319,6 +2328,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-derive" version = "0.4.2" @@ -2962,6 +2977,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22686f4785f02a4fcc856d3b3bb19bf6c8160d103f7a99cc258bddd0251dc7f2" +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -4037,6 +4058,25 @@ dependencies = [ "weezl", ] +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde", + "time-core", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + [[package]] name = "tiny-skia" version = "0.8.4" @@ -4524,6 +4564,7 @@ dependencies = [ "serde", "serde_derive", "serde_json", + "time", "toml 0.8.19", "vulkano", "vulkano-shaders", diff --git a/Cargo.toml b/Cargo.toml index acb15b0..56b3f9a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,12 +33,12 @@ atomic_float = "1.1.0" ico = "0.3.0" rfd = "0.15.0" open = "5.3.0" +midir = "0.10.0" +time = "0.3.36" num_enum = "0.7.3" palette = "0.7.6" colors-transform = "0.2" -midir = "0.10.0" - [build-dependencies] resvg = { version = "0.31.0", default-features = false } diff --git a/src/gui/window.rs b/src/gui/window.rs index 3501d65..0b76aa4 100644 --- a/src/gui/window.rs +++ b/src/gui/window.rs @@ -10,15 +10,16 @@ mod settings; mod shortcuts; use std::path::Path; +use std::path::PathBuf; use std::sync::{Arc, RwLock}; use std::thread; -use std::{path::PathBuf, time::Duration}; use crossbeam_channel::{Receiver, Sender}; use egui::FontFamily::{Monospace, Proportional}; use egui::FontId; use egui::Frame; use settings::SettingsWindow; +use time::Duration; use crate::audio_playback::{EmptyPlayer, MidiDevicePlayer}; use crate::{ @@ -198,7 +199,7 @@ impl GuiWasabiWindow { // Render the panel let height_prev = ctx.available_rect().height(); - self.show_playback_panel(&ctx, state); + self.show_playback_panel(&ctx, settings, state); // Calculate available space left for keyboard and notes // We must render notes before keyboard because the notes @@ -230,7 +231,7 @@ impl GuiWasabiWindow { .show_separator_line(false) .show(&ctx, |ui| { if let Some(midi_file) = self.midi_file.as_mut() { - let skip_dur = Duration::from_secs_f64(settings.gui.skip_control); + let skip_dur = Duration::seconds_f64(settings.gui.skip_control); let time = midi_file.timer().get_time(); // Set playback keyboard shortcuts @@ -252,7 +253,7 @@ impl GuiWasabiWindow { if midi_file.allows_seeking_backward() { midi_file.timer_mut().seek(if time <= skip_dur { // FIXME: Start deplay - Duration::from_secs(0) + Duration::seconds(0) } else { time - skip_dur }) diff --git a/src/gui/window/playback_panel.rs b/src/gui/window/playback_panel.rs index 53741ce..e80315f 100644 --- a/src/gui/window/playback_panel.rs +++ b/src/gui/window/playback_panel.rs @@ -1,12 +1,20 @@ -use std::time::Duration; +use time::Duration; use egui::{popup_below_widget, PopupCloseBehavior}; use super::{GuiWasabiWindow, SPACE, WIN_MARGIN}; -use crate::{midi::MIDIFileBase, state::WasabiState, utils::convert_seconds_to_time_string}; +use crate::{ + midi::MIDIFileBase, settings::WasabiSettings, state::WasabiState, + utils::convert_seconds_to_time_string, +}; impl GuiWasabiWindow { - pub fn show_playback_panel(&mut self, ctx: &egui::Context, state: &mut WasabiState) { + pub fn show_playback_panel( + &mut self, + ctx: &egui::Context, + settings: &WasabiSettings, + state: &mut WasabiState, + ) { let mut mouse_over_panel = false; if let Some(mouse) = ctx.pointer_latest_pos() { if mouse.y < 60.0 { @@ -109,7 +117,7 @@ impl GuiWasabiWindow { if let Some(midi) = self.midi_file.as_ref() { time_total = midi.midi_length().unwrap_or(0.0); - time_passed = midi.timer().get_time().as_secs_f64(); + time_passed = midi.timer().get_time().as_seconds_f64(); } let mut timeid = ui @@ -146,16 +154,20 @@ impl GuiWasabiWindow { || ui.add(egui::Slider::new(&mut 0.0, 0.0..=1.0).show_value(false)); if let Some(midi_file) = self.midi_file.as_mut() { if let Some(length) = midi_file.midi_length() { - let mut time = midi_file.timer().get_time().as_secs_f64(); + let mut time = midi_file.timer().get_time().as_seconds_f64(); let time_prev = time; ui.add( - egui::Slider::new(&mut time, 0.0..=length).show_value(false), + egui::Slider::new( + &mut time, + -settings.midi.start_delay..=length, + ) + .show_value(false), ); if (time_prev != time) && (midi_file.allows_seeking_backward() || time_prev < time) { - midi_file.timer_mut().seek(Duration::from_secs_f64(time)); + midi_file.timer_mut().seek(Duration::seconds_f64(time)); } } else { empty_slider(); diff --git a/src/gui/window/settings/midi.rs b/src/gui/window/settings/midi.rs index 5eeea9c..638231d 100644 --- a/src/gui/window/settings/midi.rs +++ b/src/gui/window/settings/midi.rs @@ -55,9 +55,17 @@ impl SettingsWindow { ); }); ui.end_row(); + + ui.label("Start Delay (s):"); + ui.add( + egui::DragValue::new(&mut settings.midi.start_delay) + .speed(1.0) + .range(0.0..=100.0), + ); + ui.end_row(); }); ui.vertical_centered(|ui| { - ui.small("Changes to this setting will be applied when a new MIDI is loaded."); + ui.small("Changes to the above settings will be applied when a new MIDI is loaded."); }); ui.horizontal(|ui| ui.add_space(width + 40.0)); diff --git a/src/gui/window/stats.rs b/src/gui/window/stats.rs index 2ac93d9..7b0971b 100644 --- a/src/gui/window/stats.rs +++ b/src/gui/window/stats.rs @@ -82,7 +82,7 @@ pub fn draw_stats( .collapsible(false) .title_bar(false) .scroll([false, false]) - .enabled(true) + .enabled(false) .frame(stats_frame) .fixed_pos(pos) .fixed_size(egui::Vec2::new(200.0, 128.0)) @@ -94,10 +94,6 @@ pub fn draw_stats( stats.time_total = midi_file.midi_length().unwrap_or(0.0); let time = midi_file.timer().get_time().as_seconds_f64(); - length_millis = (stats.time_total * 10.0) as u64 % 10; - length_sec = stats.time_total as u64 % 60; - length_min = stats.time_total as u64 / 60; - if time > stats.time_total { stats.time_passed = stats.time_total; } else { diff --git a/src/midi/cake/mod.rs b/src/midi/cake/mod.rs index ef5c07d..edd3cf3 100644 --- a/src/midi/cake/mod.rs +++ b/src/midi/cake/mod.rs @@ -155,7 +155,7 @@ impl CakeMIDIFile { let (keys, note_count) = key_join_handle.join().unwrap(); let audio = audio_join_handle.join().unwrap(); - let mut timer = TimeKeeper::new(); + let mut timer = TimeKeeper::new(settings.midi.start_delay); InRamAudioPlayer::new(audio, timer.get_listener(), player).spawn_playback(); diff --git a/src/midi/live/mod.rs b/src/midi/live/mod.rs index 406c0cc..c0034df 100644 --- a/src/midi/live/mod.rs +++ b/src/midi/live/mod.rs @@ -60,7 +60,7 @@ impl LiveLoadMIDIFile { } }); - let mut timer = TimeKeeper::new(); + let mut timer = TimeKeeper::new(settings.midi.start_delay); let colors = MIDIColor::new_vec_from_settings(midi.track_count(), settings); diff --git a/src/midi/mod.rs b/src/midi/mod.rs index 4df0360..6ff26de 100644 --- a/src/midi/mod.rs +++ b/src/midi/mod.rs @@ -17,7 +17,6 @@ use rand::Rng; pub use cake::{blocks::CakeBlock, intvec4::IntVector4, CakeMIDIFile, CakeSignature}; pub use live::LiveLoadMIDIFile; pub use ram::InRamMIDIFile; -pub use shared::timer::START_DELAY; use crate::settings::{Colors, WasabiSettings}; @@ -124,6 +123,7 @@ impl MIDIColor { pub fn new_vec_from_palette(tracks: usize, path: impl Into) -> Vec { let path: PathBuf = path.into(); if path.exists() { + // TODO return Vec::new(); } diff --git a/src/midi/ram/parse.rs b/src/midi/ram/parse.rs index 9f86b54..9599aa7 100644 --- a/src/midi/ram/parse.rs +++ b/src/midi/ram/parse.rs @@ -185,7 +185,7 @@ impl InRamMIDIFile { let (keys, note_count) = key_join_handle.join().unwrap(); let audio = audio_join_handle.join().unwrap(); - let mut timer = TimeKeeper::new(); + let mut timer = TimeKeeper::new(settings.midi.start_delay); InRamAudioPlayer::new(audio, timer.get_listener(), player).spawn_playback(); diff --git a/src/midi/shared/timer.rs b/src/midi/shared/timer.rs index 7f081e0..ad953fe 100644 --- a/src/midi/shared/timer.rs +++ b/src/midi/shared/timer.rs @@ -3,8 +3,6 @@ use std::time::Instant; use time::Duration; -pub const START_DELAY: Duration = Duration::seconds(2); - struct NotifySignal { new_state: TimerState, has_seeked: bool, @@ -22,13 +20,13 @@ enum TimerState { } impl TimerState { - fn get_time(&self) -> Duration { + fn get_time(&self, start_delay: Duration) -> Duration { match self { TimerState::Running { continue_time, time_offset, - } => continue_time.elapsed() + *time_offset - START_DELAY, - TimerState::Paused { time_offset } => *time_offset - START_DELAY, + } => continue_time.elapsed() + *time_offset - start_delay, + TimerState::Paused { time_offset } => *time_offset - start_delay, } } @@ -41,20 +39,23 @@ impl TimerState { pub struct TimeKeeper { current_state: TimerState, listeners: Vec>, + start_delay: Duration, } impl TimeKeeper { - pub fn new() -> Self { + pub fn new(start_delay: f64) -> Self { + let start_delay = Duration::seconds_f64(start_delay); Self { current_state: TimerState::Paused { - time_offset: -START_DELAY, + time_offset: -start_delay, }, listeners: Vec::new(), + start_delay, } } pub fn get_time(&self) -> Duration { - self.current_state.get_time() + self.current_state.get_time(self.start_delay) } pub fn is_paused(&self) -> bool { @@ -67,6 +68,7 @@ impl TimeKeeper { TimeListener { reciever: rcv, current: self.current_state.clone(), + start_delay: self.start_delay, } } @@ -91,7 +93,7 @@ impl TimeKeeper { } pub fn toggle_pause(&mut self) { - let now = self.get_time() + START_DELAY; + let now = self.get_time() + self.start_delay; match self.current_state { TimerState::Paused { .. } => { self.current_state = TimerState::Running { @@ -108,22 +110,22 @@ impl TimeKeeper { } pub fn pause(&mut self) { - let now = self.get_time() + START_DELAY; + let now = self.get_time() + self.start_delay; self.current_state = TimerState::Paused { time_offset: now }; self.notify_listeners(false); } pub fn play(&mut self) { - let now = self.get_time() + START_DELAY; + let now = self.get_time() + self.start_delay; self.current_state = TimerState::Running { continue_time: Instant::now(), - time_offset: now + START_DELAY, + time_offset: now + self.start_delay, }; self.notify_listeners(false); } pub fn seek(&mut self, time: Duration) { - let time = time + START_DELAY; + let time = time + self.start_delay; if self.current_state.is_paused() { self.current_state = TimerState::Paused { time_offset: time }; } else { @@ -139,6 +141,7 @@ impl TimeKeeper { pub struct TimeListener { reciever: crossbeam_channel::Receiver, current: TimerState, + start_delay: Duration, } #[must_use] @@ -168,7 +171,7 @@ impl TimeListener { } pub fn wait_until(&mut self, time: Duration) -> WaitResult { - let curr_time = self.current.get_time(); + let curr_time = self.current.get_time(self.start_delay); if curr_time >= time { return WaitResult::Ok; } @@ -182,7 +185,7 @@ impl TimeListener { Ok(signal) => { self.current = signal.new_state; if signal.has_seeked { - WaitResult::Seeked(self.current.get_time()) + WaitResult::Seeked(self.current.get_time(self.start_delay)) } else if self.current.is_paused() { WaitResult::Paused } else { @@ -210,7 +213,7 @@ impl TimeListener { Ok(signal) => { self.current = signal.new_state; if signal.has_seeked { - seeked = Some(self.current.get_time()); + seeked = Some(self.current.get_time(self.start_delay)); } if !self.current.is_paused() { @@ -239,7 +242,9 @@ impl TimeListener { } if seeked && !self.current.is_paused() { - return SeekWaitResult::UnpausedAndSeeked(self.current.get_time()); + return SeekWaitResult::UnpausedAndSeeked( + self.current.get_time(self.start_delay), + ); } } Err(_) => return SeekWaitResult::Killed, @@ -248,6 +253,6 @@ impl TimeListener { } pub fn get_time(&self) -> Duration { - self.current.get_time() + self.current.get_time(self.start_delay) } } diff --git a/src/settings/mod.rs b/src/settings/mod.rs index 1429424..e676500 100644 --- a/src/settings/mod.rs +++ b/src/settings/mod.rs @@ -88,6 +88,7 @@ impl Default for SceneSettings { #[serde(default)] pub struct MidiSettings { pub parsing: MidiParsing, + pub start_delay: f64, pub colors: Colors, pub palette_path: PathBuf, } @@ -96,6 +97,7 @@ impl Default for MidiSettings { fn default() -> Self { Self { parsing: MidiParsing::Cake, + start_delay: 2.0, colors: Colors::Rainbow, palette_path: PathBuf::new(), }