diff --git a/Cargo.lock b/Cargo.lock index 2080d83..531b6a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2730,6 +2730,7 @@ version = "0.1.0" dependencies = [ "iced", "kira", + "regex", "serde", "serde_json", "tokio", diff --git a/Cargo.toml b/Cargo.toml index e8b6a4a..f2e3604 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,3 +9,4 @@ kira = "0.10.6" tokio = {version = "1.45.1", features = ["time"]} serde = { version = "1.0", features = ["derive"] } serde_json = "1.0.140" +regex = "1.11.1" diff --git a/src/main.rs b/src/main.rs index 77f46d4..5d0920b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,12 @@ mod polygon_draw; +use iced::widget::text_input::Style; use polygon_draw::Polygon; mod music; use music::Music; mod utils; +use utils::str_to_sec; use std::fs; @@ -17,6 +19,7 @@ use iced::{ }, }; +use regex::Regex; use std::f32::consts::PI; use std::time::Instant; @@ -24,6 +27,7 @@ use kira::{ AudioManager, AudioManagerSettings, DefaultBackend, sound::static_sound::StaticSoundData, }; +use crate::utils::delta_to_string; fn main() -> iced::Result { iced::application("My App", MyApp::update, MyApp::view) .theme(|_| Theme::Dark) @@ -46,6 +50,10 @@ enum Message { Save, Load, FileNameChanged(String), + TogglePaused, + SetMusicLength, + LengthChange(String), + ChangeDelta(f32), } struct MyApp { @@ -57,6 +65,7 @@ struct MyApp { all_sounds: Vec, all_saves: Vec, current_delta: f32, + str_music_length: String, } impl MyApp { @@ -67,12 +76,13 @@ impl MyApp { Self { time_last_frame: Instant::now(), audio_manager: manager, - show_save_panel: true, - paused: false, + show_save_panel: false, + paused: true, all_sounds: load_path_sounds(), all_saves: load_path_saves(), music: Music::default(), current_delta: 0.0, + str_music_length: "01:00:00".to_string(), }, Task::none(), ) @@ -133,6 +143,22 @@ impl MyApp { } Message::ToggleSavePanel => self.show_save_panel = !self.show_save_panel, Message::FileNameChanged(s) => self.music.file_name = s, + Message::LengthChange(s) => self.str_music_length = s, + Message::TogglePaused => { + self.paused = !self.paused; + if !self.paused { + self.time_last_frame = Instant::now(); + } + } + Message::SetMusicLength => { + if self.is_length_valid() { + self.music.length = str_to_sec(&self.str_music_length) + } + } + Message::ChangeDelta(f) => { + self.current_delta = f; + self.music.fix_teta(self.current_delta); + } _ => {} } } @@ -175,7 +201,7 @@ impl MyApp { }) .step(PI / 84f32), // 84 | 4 for do PI / 4 ] - .spacing(20) + .spacing(10) .into() }) .collect(); @@ -198,7 +224,7 @@ impl MyApp { .chain(ngon_options) .collect(); - let polygon_column = scrollable(Column::with_children(polygon_rows)); + let polygon_column = scrollable(Column::with_children(polygon_rows).spacing(20)); let mut save_panel: Vec> = vec![ button("Toggle Save Panel") .on_press(Message::ToggleSavePanel) @@ -242,7 +268,7 @@ impl MyApp { pick_list(all_options, Some("Choose polygon".to_string()), |s| { Message::AddPolygon(s) }), - polygon_column.spacing(10), + polygon_column, ] .spacing(10) .height(Length::FillPortion(1)) @@ -250,17 +276,42 @@ impl MyApp { ] .height(Length::FillPortion(2)) .spacing(20), - row![text("futur time line")] - .height(Length::FillPortion(1)) - .width(Length::FillPortion(1)), + column![ + text("TimeLine"), + row![ + button("Toggle Play").on_press(Message::TogglePaused), + text(format!( + "{}/{}", + delta_to_string(self.current_delta), + delta_to_string(self.music.length) + )) + ], + row![ + text("Music Length"), + TextInput::new("MM:SS:CS", &self.str_music_length) + .on_input(|new_value| Message::LengthChange(new_value)), + button("Valid").on_press(Message::SetMusicLength), + ], + slider(0.0..=self.music.length, self.current_delta, move |f| { + Message::ChangeDelta(f) + }) + .step(&self.music.length / 10_000.), + ] + .height(Length::FillPortion(1)) + .width(Length::FillPortion(1)), ] .spacing(25) .padding(25) .into() } + + fn is_length_valid(&self) -> bool { + let re = Regex::new(r"^\d{2}:\d{2}:\d{2}$").unwrap(); + re.is_match(self.str_music_length.as_str()) + } fn subscription(&self) -> iced::Subscription { iced::Subscription::batch([ - window::events().map(|(_id, event)| Message::WindowEvent(event)), + //window::events().map(|(_id, event)| Message::WindowEvent(event)), iced::time::every(std::time::Duration::from_millis(16)).map(|_| Message::Tick), ]) } diff --git a/src/music.rs b/src/music.rs index 894c0be..c000b03 100644 --- a/src/music.rs +++ b/src/music.rs @@ -12,6 +12,8 @@ pub struct Music { pub poly_frame: Vec<(f32, PolygonFrame)>, pub nb_sec_for_rev: f32, pub file_name: String, + pub length: f32, + teta: f32, } impl Music { @@ -23,7 +25,7 @@ impl Music { { &mut self.poly_frame[i].1 } else { - &mut self.poly_frame[0].1 + &mut self.poly_frame.last_mut().unwrap().1 } } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~PUBLIC~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -35,15 +37,20 @@ impl Music { { &self.poly_frame[i].1 } else { - &self.poly_frame[0].1 + &self.poly_frame.last().unwrap().1 } } pub fn default() -> Music { Music { - poly_frame: vec![(0.0, PolygonFrame::default())], + poly_frame: vec![ + (0.0, PolygonFrame::default()), + (10.0, PolygonFrame::default()), + ], nb_sec_for_rev: 1.0, file_name: "Polymusic.json".to_string(), + length: 60.0, + teta: 0., } } @@ -51,13 +58,20 @@ impl Music { self.find_poly_frame(delta).update(); } + pub fn fix_teta(&mut self, delta: f32) { + let new_teta = delta % self.nb_sec_for_rev * 2.0 * PI / self.nb_sec_for_rev; + self.teta = new_teta; + } + pub fn apply_tick(&mut self, delta: f32, time_btw: Duration, audio_manager: &mut AudioManager) { - let nb_sec_for_rev = self.nb_sec_for_rev; + let teta_temp = self.teta; + self.teta += + 2.0 * PI * (1.0 / self.nb_sec_for_rev) * (time_btw.as_millis() as f32 / 1_000.0); + self.teta %= 2.0 * PI; + let currrent_teta = self.teta; let current_frame = self.find_poly_frame(delta); - let teta_temp = current_frame.teta; - current_frame.teta += - 2.0 * PI * (1.0 / nb_sec_for_rev) * (time_btw.as_millis() as f32 / 1_000.0); - current_frame.teta %= 2.0 * PI; + + current_frame.teta = currrent_teta; let sound_to_play = current_frame.all_sound_to_play_btw(teta_temp, current_frame.teta); for sound in sound_to_play { audio_manager diff --git a/src/utils.rs b/src/utils.rs index 7157d8f..781c3dc 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,5 +1,5 @@ use crate::Polygon; -use iced::Color; +use iced::{Color, widget::canvas::path::lyon_path::geom::euclid::num::Floor}; use kira::sound::static_sound::StaticSoundData; pub fn string_to_color>(s: S) -> Color { match s.as_ref() { @@ -11,65 +11,82 @@ pub fn string_to_color>(s: S) -> Color { _ => Color::BLACK, } } + +pub fn delta_to_string(delta: f32) -> String { + let time = [ + ((delta / 60.0) as u32), + ((delta % 60.0) as u32), + ((delta * 100.0) % 100.0) as u32, + ]; + let mut time_str = [String::new(), String::new(), String::new()]; + for i in 0..3 { + if time[i] < 10 { + time_str[i] = format!("0{}", time[i]); + } else { + time_str[i] = time[i].to_string(); + } + } + + format!("{}:{}:{}", time_str[0], time_str[1], time_str[2]) +} +pub fn str_to_sec(str: &str) -> f32 { + let parts: Vec<&str> = str.split(':').collect(); + + let min: f32 = parts[0].parse().expect("str of music length not valid"); + let sec: f32 = parts[1].parse().expect("str of music length not valid"); + let cs: f32 = parts[2].parse().expect("str of music length not valid"); + min * 60.0 + sec + (cs / 100.0) +} + pub fn string_to_polygon>(str: S) -> Polygon { let s = str.as_ref(); let mut poly: Polygon; if s.starts_with("Ngon") { if let Ok(sides) = s.trim_start_matches("Ngon").parse::() { - return Polygon::n_gon(0.0, sides, dummy_sound()); + poly = Polygon::n_gon(0.0, sides, dummy_sound()); } else { - return Polygon::n_gon(0.0, 0, dummy_sound()); + poly = Polygon::n_gon(0.0, 0, dummy_sound()); } } else { match s { "Segment" => { poly = Polygon::segment(0.0, dummy_sound()); - poly.name = "Segment".to_string(); } "Triangle" => { poly = Polygon::triangle(0.0, dummy_sound()); - poly.name = "Triangle".to_string(); } "Square" => { poly = Polygon::square(0.0, dummy_sound()); - poly.name = "Square".to_string(); } "Nr6In30" => { poly = Polygon::nr_6_in_30(0.0, dummy_sound()); - poly.name = "Nr6In30".to_string(); } "Nr7In30" => { poly = Polygon::nr_7_in_30(0.0, dummy_sound()); - poly.name = "Nr7In30".to_string(); } "Nr8In30" => { poly = Polygon::nr_8_in_30(0.0, dummy_sound()); - poly.name = "Nr8In30".to_string(); } "Nr9In30" => { poly = Polygon::nr_9_in_30(0.0, dummy_sound()); - poly.name = "Nr9In30".to_string(); } "Nr8In42" => { poly = Polygon::nr_8_in_42(0.0, dummy_sound()); - poly.name = "Nr8In42".to_string(); } "Nr9In42" => { poly = Polygon::nr_9_in_42(0.0, dummy_sound()); - poly.name = "Nr9In42".to_string(); } "Nr10aIn42" => { poly = Polygon::nr_10a_in_42(0.0, dummy_sound()); - poly.name = "Nr10aIn42".to_string(); } "Nr10bIn42" => { poly = Polygon::nr_10b_in_42(0.0, dummy_sound()); - poly.name = "Nr10bIn42".to_string(); } _ => poly = Polygon::n_gon(0.0, 0, dummy_sound()), } - poly } + poly.name = s.to_string(); + poly } fn dummy_sound() -> StaticSoundData { StaticSoundData::from_file("assets/tick.ogg").expect("Fail to load audio")