diff --git a/src/grid.rs b/src/grid.rs index b7ca1fe..a80adf5 100644 --- a/src/grid.rs +++ b/src/grid.rs @@ -1,7 +1,6 @@ use std::ops::RangeInclusive; use eframe::egui; -use eframe::egui::{Color32, Context, PointerButton, Pos2, Rangef, Sense, Ui, Vec2}; -use serde::{Deserialize, Serialize}; +use eframe::egui::{Color32, Context, Id, PointerButton, Pos2, Rangef, Sense, Ui, Vec2}; use crate::gui::Showable; use crate::noise::NoteType; use crate::scale::Scale; @@ -17,27 +16,41 @@ impl NoteType { NoteType::Noise => Color32::from_rgb(240, 240, 240), } } + + fn name(&self) -> &'static str { + match self { + NoteType::Sine => "Sine", + NoteType::Triangle => "Triangle", + NoteType::Sawtooth => "Sawtooth", + NoteType::Square => "Square", + NoteType::Noise => "Noise" + } + } } -#[derive(Serialize, Deserialize, Clone)] +#[derive(Clone)] pub struct Grid { pub note_type: NoteType, pub volume: f32, pub length: u64, - scale: Scale, - notes: Vec, - id: String + pub scale: Scale, + pub notes: Vec, + pub id: Id, + pub name: String, + pub open: bool } impl Grid { - pub fn for_note_type(note_type: NoteType, counter: usize) -> Self { + pub fn for_note_type(note_type: NoteType, id: Id) -> Self { Self { note_type, volume: 1.0, length: 250, + open: true, scale: Scale::CMajor, notes: vec![false; (LOOP_LENGTH * LOOP_LENGTH) as usize], - id: format!("Track {}", counter) + name: format!("{} Track", note_type.name()), + id } } @@ -91,7 +104,12 @@ impl Grid { impl Showable for Grid { fn show(&mut self, ctx: &Context, cursor: &f32) { - let win = egui::Window::new(&self.id).resizable(false).scroll([false, false]); + let mut open = true; + let win = egui::Window::new(&self.name) + .id(self.id) + .resizable(false) + .scroll([false, false]) + .open(&mut open); win.show(ctx, |ui| { egui::MenuBar::new().ui(ui, |ui| { if ui.button("Clear").clicked() { @@ -128,5 +146,7 @@ impl Showable for Grid { self.draw_grid(ui, *cursor) }); }); + + self.open = open; } } \ No newline at end of file diff --git a/src/gui.rs b/src/gui.rs index ed6a41e..e729e8c 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -2,7 +2,7 @@ use std::fs; use std::ops::RangeInclusive; use std::path::Path; use eframe::egui; -use eframe::egui::{Context, TopBottomPanel}; +use eframe::egui::{Context, Id, TopBottomPanel}; use crate::noise::NoteType; use crate::saveload::PersistedTenori; use crate::Tenori; @@ -90,11 +90,7 @@ impl Tenori { .pick_file() { let serialized = fs::read_to_string(path).map_err(|e| e.to_string())?; let persisted = toml::from_str::(serialized.as_str()).map_err(|e| e.to_string())?; - - self.grids = persisted.grids; - self.tempo = persisted.tempo; - self.playing = false; - self.timer = 0.0; + persisted.apply_to(self); } Ok(()) } @@ -103,6 +99,13 @@ impl Tenori { for g in self.grids.iter_mut() { g.show(ctx, cursor) } + self.grids.retain(|g| g.open); + } + + /// Return a unique (among all the windows created since a file load) id string for a window. + pub fn window_id(&mut self) -> Id { + self.window_counter += 1; + format!("window {}", self.window_counter).into() } } diff --git a/src/saveload.rs b/src/saveload.rs index 4433034..ca03d7a 100644 --- a/src/saveload.rs +++ b/src/saveload.rs @@ -1,18 +1,70 @@ +use eframe::egui::Id; use serde::{Deserialize, Serialize}; use crate::grid::Grid; +use crate::noise::NoteType; +use crate::scale::Scale; use crate::tenori::Tenori; #[derive(Serialize, Deserialize)] pub struct PersistedTenori { - pub tempo: u32, - pub grids: Vec + tempo: u32, + grids: Vec } impl From<&Tenori> for PersistedTenori { fn from(value: &Tenori) -> Self { Self { tempo: value.tempo, - grids: value.grids.iter().map(Grid::clone).collect() + grids: value.grids.iter().map(PersistedGrid::from).collect() + } + } +} + +impl PersistedTenori { + pub fn apply_to(self, tenori: &mut Tenori) { + tenori.grids = self.grids.into_iter().map(|g| g.into_grid(tenori.window_id())).collect(); + tenori.tempo = self.tempo; + tenori.playing = false; // Start paused + tenori.timer = 0.0; // Start at the beginning of the loop + } +} + +#[derive(Serialize, Deserialize)] +struct PersistedGrid { + note_type: NoteType, + volume: f32, + length: u64, + scale: Scale, + notes: String, + name: String +} + +impl From<&Grid> for PersistedGrid { + fn from(value: &Grid) -> Self { + let notes: String = value.notes.iter().map(|n| if *n { '1' } else { '0' }).collect(); + Self { + note_type: value.note_type, + volume: value.volume, + length: value.length, + scale: value.scale, + name: value.name.clone(), + notes + } + } +} + +impl PersistedGrid { + pub fn into_grid(self, id: Id) -> Grid { + let notes: Vec<_> = self.notes.chars().map(|c| c == '1').collect(); + Grid { + note_type: self.note_type, + volume: self.volume, + length: self.length, + scale: self.scale, + name: self.name, + open: true, + notes, + id } } } \ No newline at end of file diff --git a/src/tenori.rs b/src/tenori.rs index fe91adc..6a277ab 100644 --- a/src/tenori.rs +++ b/src/tenori.rs @@ -22,8 +22,8 @@ pub struct Tenori { /// The grids that we currently have going pub grids: Vec, - // Running count of windows created (for ids) - window_counter: usize, + /// Running count of windows created (for ids) + pub window_counter: usize, // The audio output stream to which we will play notes output_stream: OutputStream, @@ -91,8 +91,8 @@ impl Tenori { } pub fn add_window(&mut self, note_type: NoteType) { - self.window_counter += 1; - self.grids.push(Grid::for_note_type(note_type, self.window_counter)); + let id = self.window_id(); + self.grids.push(Grid::for_note_type(note_type, id)); } pub fn notes_for_beat(&self) -> Vec {