From: Huck Boles Date: Fri, 7 Jul 2023 01:00:53 +0000 (-0500) Subject: edited: improving gui interface X-Git-Url: https://git.huck.website/?a=commitdiff_plain;h=b2fbd4b08d9b1b1e9fef3306fc74018e729eb185;p=cellseq.git edited: improving gui interface --- diff --git a/src/display.rs b/src/display.rs index eb9021b..9484002 100644 --- a/src/display.rs +++ b/src/display.rs @@ -1,6 +1,8 @@ use iced::{ theme, - widget::{button, column, pick_list, row, slider, text, Column, Row}, + widget::{ + button, checkbox, column, pick_list, row, slider, text, vertical_slider, Column, Row, + }, Alignment, Element, Length, }; @@ -19,11 +21,17 @@ pub struct ControlMessage { pub fn top_controls<'a>(is_playing: bool) -> Element<'a, Message> { let play_button = row![ button(if is_playing { "stop" } else { "play" }).on_press(Message::TogglePlayback), - button("save") + button("save map") .on_press(Message::Save) .style(theme::Button::Positive), - button("clear") - .on_press(Message::Clear) + button("reset map") + .on_press(Message::Reset) + .style(theme::Button::Secondary), + button("clear map") + .on_press(Message::ClearMap) + .style(theme::Button::Destructive), + button("clear mask") + .on_press(Message::ClearMask) .style(theme::Button::Destructive), ] .width(Length::Fill) @@ -33,6 +41,7 @@ pub fn top_controls<'a>(is_playing: bool) -> Element<'a, Message> { .on_press(Message::Quit) .style(theme::Button::Destructive),] .width(Length::Fill) + .align_items(Alignment::End) .spacing(10); row![play_button, other_controls] @@ -45,8 +54,9 @@ pub fn top_controls<'a>(is_playing: bool) -> Element<'a, Message> { pub fn bottom_controls<'a>(message: ControlMessage) -> Element<'a, Message> { column![ map_section(&message), - probability_section(message.info.probability).width(Length::Fixed(600.0)), - midi_section(&message), + probability_section(&message).width(Length::Fixed(600.0)), + scale_selector(&message), + music_controls(&message) ] .width(Length::Fill) .height(Length::Fill) @@ -56,180 +66,201 @@ pub fn bottom_controls<'a>(message: ControlMessage) -> Element<'a, Message> { .into() } -fn map_section<'a>(message: &ControlMessage) -> Row<'a, Message> { +fn music_controls<'a>(message: &ControlMessage) -> Row<'a, Message> { row![ - map_buttons(), - randomize_section(message.randomness).width(Length::Fixed(500.0)) + song_section(message), + midi_section(message), + velocity_sliders(message) ] - .spacing(10) + .height(Length::Fill) + .padding(10) + .spacing(40) + .into() } -fn probability_section<'a>(p: f32) -> Row<'a, Message> { - row![ - text("probability a cell gets triggered") - .style(theme::Text::Color(iced::Color::from_rgb8(0x60, 0x60, 0x60))), - slider(0.0..=100.0, p * 100.0, |x| { - Message::ProbChanged(x / 100.0) - }), - text(format!("{p}")), - ] - .spacing(10) +fn song_section<'a>(message: &ControlMessage) -> Row<'a, Message> { + row![song_params(), song_vals(message)] + .height(Length::Fill) + .padding(10) + .spacing(20) + .into() } -fn randomize_section<'a>(r: f32) -> Row<'a, Message> { - row![ - text("percent of board to fill on randomize") - .style(theme::Text::Color(iced::Color::from_rgb8(0x60, 0x60, 0x60))), - slider(0.0..=100.0, r * 100.0, |x| { - Message::RandChanged(x / 100.0) - }), - text(format!("{r}")), - ] - .spacing(10) +fn midi_section<'a>(message: &ControlMessage) -> Row<'a, Message> { + row![midi_params(), midi_vals(message)] + .height(Length::Fill) + .padding(10) + .spacing(20) + .into() } -fn map_buttons<'a>() -> Column<'a, Message> { - column![row![ - button("reset") - .on_press(Message::Reset) - .style(theme::Button::Secondary), - button("random") - .on_press(Message::Randomize) - .style(theme::Button::Primary), +fn song_params<'a>() -> Column<'a, Message> { + column![ + text("loop section"), + text("number of steps"), + text("bpm"), + text("note division"), ] - .spacing(10)] - .spacing(10) + .height(Length::Fill) + .padding(10) + .spacing(20) + .into() } -fn velocity_sliders<'a>(min: u8, max: u8) -> Column<'a, Message> { +fn song_vals<'a>(message: &ControlMessage) -> Column<'a, Message> { column![ + checkbox("", message.song.is_looping, |_| { Message::ToggleLoop }), row![ - text("maximum velocity") - .style(theme::Text::Color(iced::Color::from_rgb8(0x60, 0x60, 0x60))), - slider(0..=127, max, Message::NewVMax), - text(format!("{max}")), - ] - .spacing(10), + button("-").on_press(Message::LoopLength(message.song.loop_len.saturating_sub(1))), + text(if message.song.is_looping { + format!("{}/{}", message.song.step_num, message.song.loop_len) + } else { + format!("{}", message.song.loop_len) + }), + button("+").on_press(Message::LoopLength(message.song.loop_len.saturating_add(1))), + ], row![ - text("minimum velocity") - .style(theme::Text::Color(iced::Color::from_rgb8(0x60, 0x60, 0x60))), - slider(0..=127, min, Message::NewVMin), - text(format!("{min}")), + button("-").on_press(Message::SpeedChanged(message.song.bpm.saturating_sub(1))), + text(format!("{}", message.song.bpm)), + button("+").on_press(Message::SpeedChanged(message.song.bpm.saturating_add(1))), + ], + row![ + button("-").on_press(Message::NewDivision(message.song.divisor.saturating_sub(1))), + text(format!("{}", message.song.divisor)), + button("+").on_press(Message::NewDivision(message.song.divisor.saturating_add(1))), ] - .spacing(10), ] - .spacing(10) + .height(Length::Fill) + .padding(10) + .spacing(20) + .into() } -fn midi_section<'a>(message: &ControlMessage) -> Column<'a, Message> { +fn midi_params<'a>() -> Column<'a, Message> { column![ - song_section(message), - velocity_sliders(message.info.velocity.min(), message.info.velocity.max()) - .width(Length::Fixed(500.0)) + text("center octave"), + text("octave range"), + text("number of voices"), + text("midi channel"), ] - .spacing(10) + .height(Length::Fill) + .padding(10) + .spacing(20) + .into() } -fn song_section<'a>(message: &ControlMessage) -> Row<'a, Message> { - row![ - column![ - loop_controls( - message.song.is_looping, - message.song.loop_len, - message.song.step_num - ), - speed_controls(message.song.bpm), - division_controls(message.song.divisor), - ] - .padding(10) - .spacing(10) - .align_items(Alignment::Center), - column![ - voice_controls(message.info.voices, message.info.channel), - octave_selector(message.info.octave.center(), message.info.octave.range()), - scale_selector( - message.info.scale, - message.info.root.get_note(), - message.info.root.get_accidental() - ) +fn midi_vals<'a>(message: &ControlMessage) -> Column<'a, Message> { + column![ + row![ + button("-").on_press(Message::NewOctave( + message.info.octave.center.saturating_sub(1) + )), + text(format!("{}", message.info.octave.center)), + button("+").on_press(Message::NewOctave( + message.info.octave.center.saturating_add(1) + )), + ], + row![ + button("-").on_press(Message::OctaveRange( + message.info.octave.range.saturating_sub(1) + )), + text(format!("{}", message.info.octave.range)), + button("+").on_press(Message::OctaveRange( + message.info.octave.range.saturating_add(1) + )), + ], + row![ + button("-").on_press(Message::Voices(message.info.voices.saturating_sub(1))), + text(format!("{}", message.info.voices)), + button("+").on_press(Message::Voices(message.info.voices.saturating_add(1))), + ], + row![ + button("-").on_press(Message::ChannelChange( + message.info.channel.saturating_sub(1) + )), + text(format!("{}", message.info.channel)), + button("+").on_press(Message::ChannelChange( + message.info.channel.saturating_add(1) + )), ] - .padding(10) - .spacing(10) - .align_items(Alignment::Center) ] - .spacing(10) + .height(Length::Fill) + .padding(10) + .spacing(20) + .into() } -fn octave_selector<'a>(oct: u8, range: u8) -> Row<'a, Message> { +fn map_section<'a>(message: &ControlMessage) -> Row<'a, Message> { row![ - button("-").on_press(Message::NewOctave(oct.saturating_sub(1))), - text(format!("octave: {oct}")), - button("+").on_press(Message::NewOctave(oct.saturating_add(1))), - button("-").on_press(Message::OctaveRange(range.saturating_sub(1))), - text(format!("range: +/-{range}")), - button("+").on_press(Message::OctaveRange(range.saturating_add(1))) + map_buttons(), + randomize_section(message.randomness).width(Length::Fixed(500.0)) ] .spacing(10) } -fn loop_controls<'a>(looping: bool, len: usize, step: usize) -> Row<'a, Message> { +fn probability_section<'a>(message: &ControlMessage) -> Row<'a, Message> { row![ - button(if looping { "free" } else { "loop" }).on_press(Message::ToggleLoop), - button("-").on_press(Message::LoopLength(len.saturating_sub(1))), - text(if looping { - format!("{step}/{len}") - } else { - format!("{len}") + text("probability a cell triggers a note"), + slider(0.0..=100.0, message.info.probability * 100.0, |x| { + Message::ProbChanged(x / 100.0) }), - button("+").on_press(Message::LoopLength(len.saturating_add(1))) + text(format!("{}", message.info.probability)), ] .spacing(10) } -fn speed_controls<'a>(bpm: usize) -> Row<'a, Message> { +fn randomize_section<'a>(r: f32) -> Row<'a, Message> { row![ - button("<<").on_press(Message::SpeedChanged(bpm.saturating_sub(5))), - button("<").on_press(Message::SpeedChanged(bpm.saturating_sub(1))), - text(format!("{bpm}")).size(16), - button(">").on_press(Message::SpeedChanged(bpm.saturating_add(1))), - button(">>").on_press(Message::SpeedChanged(bpm.saturating_add(5))), + slider(0.0..=100.0, r * 100.0, |x| { + Message::RandChanged(x / 100.0) + }), + text(format!("{r}")), ] .spacing(10) } -fn division_controls<'a>(divisor: usize) -> Row<'a, Message> { +fn map_buttons<'a>() -> Row<'a, Message> { row![ - button("-").on_press(if divisor > 1 { - Message::NewDivision(divisor.saturating_sub(1)) - } else { - Message::None - }), - text(format!("note division: {divisor}")), - button("+").on_press(Message::NewDivision(divisor.saturating_add(1))) + button("randomize map") + .on_press(Message::RandomizeMap) + .style(theme::Button::Primary), + button("randomize mask") + .on_press(Message::RandomizeMask) + .style(theme::Button::Primary), ] .spacing(10) } -fn voice_controls<'a>(voices: u8, channel: u8) -> Row<'a, Message> { - row![ - button("-").on_press(Message::ChannelChange(channel.saturating_sub(1))), - text(format!("channel: {channel}")), - button("+").on_press(Message::ChannelChange(channel.saturating_add(1))), - button("-").on_press(Message::Voices(voices.saturating_sub(1))), - text(format!("voices: {voices}")), - button("+").on_press(Message::Voices(voices.saturating_add(1))), +fn velocity_sliders<'a>(message: &ControlMessage) -> Column<'a, Message> { + column![ + text("velocity range"), + row![ + column![ + text(format!("{}", message.info.velocity.max())), + vertical_slider(0..=127, message.info.velocity.max(), Message::NewVMax), + text("max") + ], + column![ + text(format!("{}", message.info.velocity.min())), + vertical_slider(0..=127, message.info.velocity.min(), Message::NewVMin), + text("min") + ], + ] ] .spacing(10) } -fn scale_selector<'a>(scale: Scale, note: RootNote, acc: Accidental) -> Row<'a, Message> { +fn scale_selector<'a>(message: &ControlMessage) -> Row<'a, Message> { + let scale = message.info.scale; + let note = message.info.root.note; + let accidental = message.info.root.accidental; row![ pick_list(&RootNote::ALL[..], Some(note), move |note| { - Message::NewNote(Root::new(note, acc)) + Message::NewNote(Root { note, accidental }) }) .width(Length::Fixed(50.0)), - pick_list(&Accidental::ALL[..], Some(acc), move |acc| { - Message::NewNote(Root::new(note, acc)) + pick_list(&Accidental::ALL[..], Some(accidental), move |accidental| { + Message::NewNote(Root { note, accidental }) }) .width(Length::Fixed(90.0)), pick_list(&Scale::ALL[..], Some(scale), Message::Scale).width(Length::Fixed(160.0)), diff --git a/src/lib.rs b/src/lib.rs index 345c09d..ba7430f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -96,9 +96,11 @@ pub enum Message { NewMap(CellMap), HitCount(u8), Tick(Instant), - Randomize, + RandomizeMap, + RandomizeMask, Reset, - Clear, + ClearMap, + ClearMask, Save, TogglePlayback, SpeedChanged(usize), @@ -180,6 +182,9 @@ impl Application for CellSeq { let mut commands = Vec::new(); commands.push(Command::perform(async move { map }, Message::NewMap)); commands.push(Command::perform(midi, |_| Message::None)); + commands.push(Command::perform(async move {}, |_| { + Message::MaskMessage(mask::Message::Ticked) + })); return Command::batch(commands); } @@ -195,21 +200,26 @@ impl Application for CellSeq { self.song.step_num = 0; } } - Message::Clear => self.map.clear(), - Message::Randomize => self.map.randomize(), + Message::RandChanged(r) => { + self.map.set_randomness(r); + self.mask.set_randomness(r); + } + Message::RandomizeMap => self.map.randomize(), + Message::RandomizeMask => self.mask.randomize(), + Message::ClearMap => self.map.clear(), + Message::ClearMask => self.mask.clear(), Message::Reset => self.map.reset(), Message::Save => self.map.save(), Message::SpeedChanged(b) => self.song.bpm = b, Message::NewDivision(d) => self.song.divisor = d, Message::LoopLength(l) => self.song.loop_len = l, Message::ProbChanged(p) => self.info.probability = p, - Message::RandChanged(r) => self.map.set_randomness(r), Message::NewVMin(v) => self.info.velocity.set_min(v), Message::NewVMax(v) => self.info.velocity.set_max(v), Message::ChannelChange(c) => self.info.channel = c, Message::Scale(s) => self.info.scale = s, - Message::NewOctave(o) => self.info.octave.set_center(o), - Message::OctaveRange(r) => self.info.octave.set_range(r), + Message::NewOctave(o) => self.info.octave.center = o, + Message::OctaveRange(r) => self.info.octave.range = r, Message::NewNote(r) => self.info.root = r, Message::Voices(v) => self.info.voices = v, Message::Quit => return window::close(), @@ -236,9 +246,10 @@ impl Application for CellSeq { self.map.view().map(Message::MapMessage), self.mask.view().map(Message::MaskMessage) ] + .width(Length::Fill) + .height(Length::Fill) .align_items(Alignment::Center) - .spacing(40) - .padding(40); + .spacing(40); let bottom = bottom_controls(self.control_message()); diff --git a/src/mask.rs b/src/mask.rs index e6b2569..e5b28ff 100644 --- a/src/mask.rs +++ b/src/mask.rs @@ -7,6 +7,7 @@ use iced::{ }, {Color, Element, Length, Point, Rectangle, Size, Theme}, }; +use rand::random; use crate::{Cell, CellMap}; use itertools::Itertools; @@ -16,12 +17,26 @@ use rustc_hash::FxHashSet; pub enum Message { Check(Cell), Uncheck(Cell), + Ticked, } -#[derive(Default, Debug)] +#[derive(Debug)] pub struct Mask { cells: FxHashSet, + hits: FxHashSet, mask_cache: Cache, + randomness: f32, +} + +impl Default for Mask { + fn default() -> Self { + Self { + cells: FxHashSet::default(), + mask_cache: Cache::default(), + randomness: 0.5, + hits: FxHashSet::default(), + } + } } impl Mask { @@ -35,6 +50,7 @@ impl Mask { self.cells.remove(&cell); self.mask_cache.clear(); } + Message::Ticked => self.mask_cache.clear(), } } @@ -46,13 +62,32 @@ impl Mask { } pub fn tick(&mut self, life: CellMap) -> u8 { - let mut hits = 0; + self.hits.clear(); for cell in self.cells.iter() { if life.contains(cell) { - hits += 1; + self.hits.insert(*cell); + } + } + self.hits.len().try_into().unwrap_or_default() + } + + pub fn randomize(&mut self) { + self.cells.clear(); + for (i, j) in (-32..=32).cartesian_product(-32..=32) { + if random::() < self.randomness { + self.cells.insert(Cell { i, j }); } } - hits + self.mask_cache.clear(); + } + + pub fn set_randomness(&mut self, value: f32) { + self.randomness = value; + } + + pub fn clear(&mut self) { + self.cells.clear(); + self.mask_cache.clear(); } } @@ -79,7 +114,11 @@ impl Program for Mask { frame.fill_rectangle( Point::new(x.0 as f32, x.1 as f32), Size::UNIT, - Color::WHITE, + if self.hits.contains(&Cell { i: x.1, j: x.0 }) { + Color::from_rgb8(0xFF, 0x00, 0x00) + } else { + Color::WHITE + }, ); }) }); diff --git a/src/midi.rs b/src/midi.rs index 2b97ba9..74a8b6b 100644 --- a/src/midi.rs +++ b/src/midi.rs @@ -25,9 +25,12 @@ impl Default for MidiInfo { Self { channel: 0, velocity: Velocity::new(64, 127), - octave: Octave::new(4, 1), + octave: Octave::default(), scale: Scale::Chromatic, - root: Root::new(RootNote::C, Accidental::Natural), + root: Root { + note: RootNote::C, + accidental: Accidental::Natural, + }, voices: 6, probability: 0.5, } @@ -71,6 +74,7 @@ impl MidiLink { if count > info.voices { break; } else if random::() < info.probability { + count += 1; continue; } else { count += 1; diff --git a/src/music.rs b/src/music.rs index 6a89cdc..eeb3e96 100644 --- a/src/music.rs +++ b/src/music.rs @@ -24,30 +24,8 @@ pub enum Scale { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Octave { - center: u8, - range: u8, -} - -impl Octave { - pub fn new(center: u8, range: u8) -> Self { - Self { center, range } - } - - pub fn set_center(&mut self, center: u8) { - self.center = center; - } - - pub fn set_range(&mut self, range: u8) { - self.range = range; - } - - pub fn center(&self) -> u8 { - self.center - } - - pub fn range(&self) -> u8 { - self.range - } + pub center: u8, + pub range: u8, } impl Default for Octave { @@ -89,22 +67,8 @@ impl Velocity { #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub struct Root { - note: RootNote, - accidental: Accidental, -} - -impl Root { - pub fn new(note: RootNote, accidental: Accidental) -> Self { - Self { note, accidental } - } - - pub fn get_note(&self) -> RootNote { - self.note - } - - pub fn get_accidental(&self) -> Accidental { - self.accidental - } + pub note: RootNote, + pub accidental: Accidental, } impl Display for Root {