From ba7fda2b5cf131939eee4b8f15ec033f50207b3d Mon Sep 17 00:00:00 2001 From: Ross Andrews Date: Sat, 27 Sep 2025 01:13:01 -0500 Subject: [PATCH] basic save load --- Cargo.lock | 207 +++++++++++++++++++++++++++++++++++++++++++----- Cargo.toml | 5 +- src/grid.rs | 2 + src/gui.rs | 55 +++++++++---- src/main.rs | 3 +- src/noise.rs | 3 +- src/saveload.rs | 18 +++++ src/scale.rs | 3 +- src/tenori.rs | 2 +- 9 files changed, 257 insertions(+), 41 deletions(-) create mode 100644 src/saveload.rs diff --git a/Cargo.lock b/Cargo.lock index c23ea83..4c58bdb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -241,6 +241,28 @@ dependencies = [ "libloading", ] +[[package]] +name = "ashpd" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cbdf310d77fd3aaee6ea2093db7011dc2d35d2eb3481e5607f1f8d942ed99df" +dependencies = [ + "async-fs", + "async-net", + "enumflags2", + "futures-channel", + "futures-util", + "rand", + "raw-window-handle", + "serde", + "serde_repr", + "url", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "zbus", +] + [[package]] name = "async-broadcast" version = "0.7.2" @@ -279,6 +301,17 @@ dependencies = [ "slab", ] +[[package]] +name = "async-fs" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8034a681df4aed8b8edbd7fbe472401ecf009251c8b40556b304567052e294c5" +dependencies = [ + "async-lock", + "blocking", + "futures-lite", +] + [[package]] name = "async-io" version = "2.5.0" @@ -308,6 +341,17 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "async-net" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b948000fad4873c1c9339d60f2623323a0cfd3816e5181033c6a5cb68b2accf7" +dependencies = [ + "async-io", + "blocking", + "futures-lite", +] + [[package]] name = "async-process" version = "2.4.0" @@ -494,6 +538,15 @@ dependencies = [ "objc2 0.5.2", ] +[[package]] +name = "block2" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "340d2f0bdb2a43c1d3cd40513185b2bd7def0aa1052f956455114bc98f82dcf2" +dependencies = [ + "objc2 0.6.2", +] + [[package]] name = "blocking" version = "1.6.2" @@ -785,6 +838,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89a09f22a6c6069a18470eb92d2298acf25463f14256d24778e1230d789a2aec" dependencies = [ "bitflags 2.9.4", + "block2 0.6.1", + "libc", "objc2 0.6.2", ] @@ -1162,6 +1217,15 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures-channel" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +dependencies = [ + "futures-core", +] + [[package]] name = "futures-core" version = "0.3.31" @@ -1211,8 +1275,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-core", + "futures-io", "futures-macro", "futures-task", + "memchr", "pin-project-lite", "pin-utils", "slab", @@ -1553,9 +1619,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.11.1" +version = "2.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "206a8042aec68fa4a62e8d3f7aa4ceb508177d9324faf261e1959e495b7a1921" +checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5" dependencies = [ "equivalent", "hashbrown", @@ -1995,7 +2061,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" dependencies = [ "bitflags 2.9.4", - "block2", + "block2 0.5.1", "libc", "objc2 0.5.2", "objc2-core-data", @@ -2011,6 +2077,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6f29f568bec459b0ddff777cec4fe3fd8666d82d5a40ebd0ff7e66134f89bcc" dependencies = [ "bitflags 2.9.4", + "block2 0.6.1", "objc2 0.6.2", "objc2-core-foundation", "objc2-core-graphics", @@ -2039,7 +2106,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" dependencies = [ "bitflags 2.9.4", - "block2", + "block2 0.5.1", "objc2 0.5.2", "objc2-core-location", "objc2-foundation 0.2.2", @@ -2051,7 +2118,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" dependencies = [ - "block2", + "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", ] @@ -2085,7 +2152,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" dependencies = [ "bitflags 2.9.4", - "block2", + "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", ] @@ -2120,7 +2187,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" dependencies = [ - "block2", + "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", "objc2-metal", @@ -2132,7 +2199,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" dependencies = [ - "block2", + "block2 0.5.1", "objc2 0.5.2", "objc2-contacts", "objc2-foundation 0.2.2", @@ -2151,7 +2218,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" dependencies = [ "bitflags 2.9.4", - "block2", + "block2 0.5.1", "dispatch", "libc", "objc2 0.5.2", @@ -2185,7 +2252,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" dependencies = [ - "block2", + "block2 0.5.1", "objc2 0.5.2", "objc2-app-kit 0.2.2", "objc2-foundation 0.2.2", @@ -2198,7 +2265,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" dependencies = [ "bitflags 2.9.4", - "block2", + "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", ] @@ -2210,7 +2277,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" dependencies = [ "bitflags 2.9.4", - "block2", + "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", "objc2-metal", @@ -2233,7 +2300,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" dependencies = [ "bitflags 2.9.4", - "block2", + "block2 0.5.1", "objc2 0.5.2", "objc2-cloud-kit", "objc2-core-data", @@ -2253,7 +2320,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" dependencies = [ - "block2", + "block2 0.5.1", "objc2 0.5.2", "objc2-foundation 0.2.2", ] @@ -2265,7 +2332,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" dependencies = [ "bitflags 2.9.4", - "block2", + "block2 0.5.1", "objc2 0.5.2", "objc2-core-location", "objc2-foundation 0.2.2", @@ -2440,6 +2507,12 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "pollster" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" + [[package]] name = "portable-atomic" version = "1.11.1" @@ -2618,6 +2691,30 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b30a45b0cd0bcca8037f3d0dc3421eaf95327a17cad11964fb8179b4fc4832" +[[package]] +name = "rfd" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef2bee61e6cffa4635c72d7d81a84294e28f0930db0ddcb0f66d10244674ebed" +dependencies = [ + "ashpd", + "block2 0.6.1", + "dispatch2", + "js-sys", + "log", + "objc2 0.6.2", + "objc2-app-kit 0.3.1", + "objc2-core-foundation", + "objc2-foundation 0.3.1", + "pollster", + "raw-window-handle", + "urlencoding", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-sys 0.59.0", +] + [[package]] name = "rodio" version = "0.21.1" @@ -2718,18 +2815,28 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.219" +version = "1.0.227" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "80ece43fc6fbed4eb5392ab50c07334d3e577cbf40997ee896fe7af40bba4245" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.227" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a576275b607a2c86ea29e410193df32bc680303c82f31e275bbfcafe8b33be5" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.219" +version = "1.0.227" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "51e694923b8824cf0e9b382adf0f60d4e05f348f357b38833a3fa5ed7c2ede04" dependencies = [ "proc-macro2", "quote", @@ -2747,6 +2854,15 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_spanned" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5417783452c2be558477e104686f7de5dae53dba813c28435e0e70f82d9b04ee" +dependencies = [ + "serde_core", +] + [[package]] name = "shlex" version = "1.3.0" @@ -3070,8 +3186,11 @@ name = "tenori-ish" version = "0.1.0" dependencies = [ "eframe", + "rfd", "rodio", + "serde", "tokio", + "toml", ] [[package]] @@ -3198,12 +3317,36 @@ dependencies = [ "syn", ] +[[package]] +name = "toml" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00e5e5d9bf2475ac9d4f0d9edab68cc573dc2fd644b0dba36b0c30a92dd9eaa0" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned", + "toml_datetime 0.7.2", + "toml_parser", + "toml_writer", + "winnow", +] + [[package]] name = "toml_datetime" version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +[[package]] +name = "toml_datetime" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32f1085dec27c2b6632b04c80b3bb1b4300d6495d1e129693bdda7d91e72eec1" +dependencies = [ + "serde_core", +] + [[package]] name = "toml_edit" version = "0.22.27" @@ -3211,10 +3354,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ "indexmap", - "toml_datetime", + "toml_datetime 0.6.11", "winnow", ] +[[package]] +name = "toml_parser" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cf893c33be71572e0e9aa6dd15e6677937abd686b066eac3f8cd3531688a627" +dependencies = [ + "winnow", +] + +[[package]] +name = "toml_writer" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d163a63c116ce562a22cda521fcc4d79152e7aba014456fb5eb442f6d6a10109" + [[package]] name = "tracing" version = "0.1.41" @@ -3302,6 +3460,12 @@ dependencies = [ "serde", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "utf8_iter" version = "1.0.4" @@ -4200,7 +4364,7 @@ dependencies = [ "android-activity", "atomic-waker", "bitflags 2.9.4", - "block2", + "block2 0.5.1", "bytemuck", "calloop", "cfg_aliases", @@ -4545,6 +4709,7 @@ dependencies = [ "endi", "enumflags2", "serde", + "url", "winnow", "zvariant_derive", "zvariant_utils", diff --git a/Cargo.toml b/Cargo.toml index 23240e1..545861b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,4 +6,7 @@ edition = "2024" [dependencies] eframe = "0.32.3" tokio = { version = "1.47.1", features = ["rt", "rt-multi-thread", "macros"] } -rodio = { version = "0.21.1", features = ["noise"] } \ No newline at end of file +rodio = { version = "0.21.1", features = ["noise"] } +toml = "0.9.7" +serde = { version = "1.0.227", features = ["derive"] } +rfd = "0.15.4" \ No newline at end of file diff --git a/src/grid.rs b/src/grid.rs index 395c27a..00c9180 100644 --- a/src/grid.rs +++ b/src/grid.rs @@ -1,6 +1,7 @@ use std::ops::RangeInclusive; use eframe::egui; use eframe::egui::{Color32, Context, Id, PointerButton, Pos2, Rangef, RichText, Sense, Ui, Vec2, Widget}; +use serde::{Deserialize, Serialize}; use crate::noise::NoteType; use crate::scale::Scale; use crate::tenori::LOOP_LENGTH; @@ -17,6 +18,7 @@ impl NoteType { } } +#[derive(Serialize, Deserialize, Clone)] pub struct Grid { pub note_type: NoteType, pub volume: f32, diff --git a/src/gui.rs b/src/gui.rs index 88960b7..aff8f90 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -1,28 +1,53 @@ +use std::fs; use std::ops::RangeInclusive; use eframe::egui; use eframe::egui::{Context, TopBottomPanel}; use crate::noise::NoteType; +use crate::saveload::PersistedTenori; use crate::Tenori; impl Tenori { pub fn menu(&mut self, ctx: &Context) { TopBottomPanel::top("menu_panel").show(ctx, |ui| { egui::MenuBar::new().ui(ui, |ui| { - if ui.button("Square").clicked() { - self.add_window(NoteType::Square) - } - if ui.button("Sine").clicked() { - self.add_window(NoteType::Sine) - } - if ui.button("Triangle").clicked() { - self.add_window(NoteType::Triangle) - } - if ui.button("Sawtooth").clicked() { - self.add_window(NoteType::Sawtooth) - } - if ui.button("Noise").clicked() { - self.add_window(NoteType::Noise) - } + ui.menu_button("File", |ui| { + if ui.button("Save").clicked() && + let Ok(serialized) = toml::to_string(&PersistedTenori::from(&*self)) && + let Some(path) = rfd::FileDialog::new().add_filter("Tenori files", &["tenori"]).set_file_name("song.tenori").save_file() { + match fs::write(path, serialized) { + Ok(_) => {} + Err(e) => { dbg!(e); } + } + } + + if ui.button("Load").clicked() && + let Some(path) = rfd::FileDialog::new().add_filter("Tenori files", &["tenori"]).pick_file() && + let Ok(serialized) = fs::read_to_string(path) && + let Ok(persisted) = toml::from_str::(serialized.as_str()) { + self.grids = persisted.grids; + self.tempo = persisted.tempo; + self.playing = false; + self.timer = 0.0; + } + }); + + ui.menu_button("Add track", |ui| { + if ui.button("Square").clicked() { + self.add_window(NoteType::Square) + } + if ui.button("Sine").clicked() { + self.add_window(NoteType::Sine) + } + if ui.button("Triangle").clicked() { + self.add_window(NoteType::Triangle) + } + if ui.button("Sawtooth").clicked() { + self.add_window(NoteType::Sawtooth) + } + if ui.button("Noise").clicked() { + self.add_window(NoteType::Noise) + } + }); ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { ui.add(egui::Slider::new(&mut self.tempo, RangeInclusive::new(20, 180))); diff --git a/src/main.rs b/src/main.rs index 382c405..5c569c0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,7 @@ mod tenori; mod grid; mod noise; mod scale; +mod saveload; use std::time::Duration; use eframe::{App, Frame}; @@ -21,7 +22,7 @@ impl App for Tenori { fn update(&mut self, ctx: &Context, frame: &mut Frame) { let play = self.tick(); let cursor = self.ratio(); - self.menu(&ctx); + self.menu(ctx); for g in self.grids.iter_mut() { g.show(ctx, cursor) } diff --git a/src/noise.rs b/src/noise.rs index a7a7b23..f2ba2b3 100644 --- a/src/noise.rs +++ b/src/noise.rs @@ -1,8 +1,9 @@ use std::time::Duration; use rodio::mixer::Mixer; use rodio::Source; +use serde::{Deserialize, Serialize}; -#[derive(Copy, Clone, Debug, PartialEq)] +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum NoteType { Sine, Triangle, diff --git a/src/saveload.rs b/src/saveload.rs new file mode 100644 index 0000000..4433034 --- /dev/null +++ b/src/saveload.rs @@ -0,0 +1,18 @@ +use serde::{Deserialize, Serialize}; +use crate::grid::Grid; +use crate::tenori::Tenori; + +#[derive(Serialize, Deserialize)] +pub struct PersistedTenori { + pub tempo: u32, + pub grids: Vec +} + +impl From<&Tenori> for PersistedTenori { + fn from(value: &Tenori) -> Self { + Self { + tempo: value.tempo, + grids: value.grids.iter().map(Grid::clone).collect() + } + } +} \ No newline at end of file diff --git a/src/scale.rs b/src/scale.rs index cc9a483..d8794dc 100644 --- a/src/scale.rs +++ b/src/scale.rs @@ -1,6 +1,7 @@ use eframe::egui::RichText; +use serde::{Deserialize, Serialize}; -#[derive(Copy, Clone, Debug, PartialEq)] +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum Scale { CMajor, CMinor, diff --git a/src/tenori.rs b/src/tenori.rs index 4357ebb..7d6333e 100644 --- a/src/tenori.rs +++ b/src/tenori.rs @@ -10,7 +10,7 @@ pub struct Tenori { pub tempo: u32, // A count _in beats_ of where we are in the loop - timer: f32, + pub timer: f32, /// Whether or not we're playing; false == paused pub playing: bool,