From 4c844050404680f4388af49287b38832d56d3a18 Mon Sep 17 00:00:00 2001 From: Huck Boles Date: Wed, 28 Jun 2023 15:34:18 -0500 Subject: [PATCH] added: new display and controls --- src/display.rs | 148 +++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 101 +++++++++++++-------------------- src/main.rs | 38 ++++++------- src/map.rs | 4 +- 4 files changed, 207 insertions(+), 84 deletions(-) create mode 100644 src/display.rs diff --git a/src/display.rs b/src/display.rs new file mode 100644 index 0000000..48219e9 --- /dev/null +++ b/src/display.rs @@ -0,0 +1,148 @@ +use iced::{ + theme, + widget::{button, column, row, slider, text}, + Alignment, Element, Length, +}; + +use crate::Message; + +pub fn top_controls<'a>( + is_playing: bool, + bpm: usize, + is_looping: bool, + loop_len: usize, + step_num: usize, +) -> Element<'a, Message> { + let play_button = + button(if is_playing { "pause" } else { "play" }).on_press(Message::TogglePlayback); + + let loop_controls = row![ + button(if is_looping { "free" } else { "loop" }).on_press(Message::ToggleLoop), + button("-").on_press(Message::LoopLength(loop_len.saturating_sub(1))), + text(if is_looping { + format!("{step_num}/{loop_len}") + } else { + format!("{loop_len}") + }), + button("+").on_press(Message::LoopLength(loop_len.saturating_add(1))) + ] + .spacing(10); + + let speed_controls = 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))), + ] + .width(Length::Fill) + .align_items(Alignment::Center) + .spacing(10); + + let other_controls = row![button("quit") + .on_press(Message::Quit) + .style(theme::Button::Destructive),] + .width(Length::Fill) + .align_items(Alignment::Center) + .spacing(10); + + row![play_button, loop_controls, speed_controls, other_controls] + .padding(10) + .spacing(40) + .align_items(Alignment::Center) + .into() +} + +pub fn bottom_controls<'a>( + prob: f32, + rand: f32, + vmin: u8, + vmax: u8, + chan: u8, +) -> Element<'a, Message> { + let probability = row![ + slider(0.0..=100.0, prob * 100.0, |x| Message::ProbChanged( + x / 100.0 + )), + text(format!("{prob}")), + text("probability a cell gets triggered") + .style(theme::Text::Color(iced::Color::from_rgb8(0x40, 0x40, 0x40))) + ] + .padding(10) + .spacing(10) + .align_items(Alignment::Center); + + let randomize = row![ + slider(0.0..=100.0, rand * 100.0, |x| Message::RandChanged( + x / 100.0 + )), + text(format!("{rand}")), + text("percent of board to fill on randomize") + .style(theme::Text::Color(iced::Color::from_rgb8(0x40, 0x40, 0x40))) + ] + .padding(10) + .spacing(10) + .align_items(Alignment::Center); + + let map_controls = row![ + button("save") + .on_press(Message::Save) + .style(theme::Button::Positive), + button("clear") + .on_press(Message::Clear) + .style(theme::Button::Destructive), + button("reset") + .on_press(Message::Reset) + .style(theme::Button::Secondary), + button("random") + .on_press(Message::Randomize) + .style(theme::Button::Primary), + ] + .padding(10) + .spacing(10) + .align_items(Alignment::Center); + + let map = column![map_controls, randomize]; + + let velocity = column![ + row![ + slider(0..=127, vmin, Message::NewVMin), + text(format!("{vmin}")), + text("minimum velocity") + .style(theme::Text::Color(iced::Color::from_rgb8(0x40, 0x40, 0x40))) + ] + .padding(10) + .spacing(10) + .align_items(Alignment::Center), + row![ + slider(0..=127, vmax, Message::NewVMax), + text(format!("{vmax}")), + text("maximum velocity") + .style(theme::Text::Color(iced::Color::from_rgb8(0x40, 0x40, 0x40))) + ] + .padding(10) + .spacing(10) + .align_items(Alignment::Center) + ] + .padding(10) + .spacing(10) + .align_items(Alignment::Center); + + let midi = row![ + button("-").on_press(Message::ChannelChange(chan.saturating_sub(1))), + text(format!("{chan}")), + button("+").on_press(Message::ChannelChange(chan.saturating_add(1))), + velocity + ] + .padding(10) + .spacing(10) + .align_items(Alignment::Center); + + column![probability, map, midi] + .width(Length::Fill) + .height(Length::Fill) + .padding(10) + .spacing(40) + .align_items(Alignment::Center) + .into() +} diff --git a/src/lib.rs b/src/lib.rs index aacac03..09525af 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,8 @@ use iced::{ executor, - theme::{self, Theme}, + theme::Theme, time, - widget::{button, column, container, row, text}, + widget::{column, container, row}, {Alignment, Application, Command, Element, Length, Point, Subscription}, }; @@ -10,10 +10,12 @@ use itertools::Itertools; use rustc_hash::FxHashSet; use std::time::{Duration, Instant}; +mod display; mod map; mod mask; mod midi; +use display::*; use map::*; use mask::*; pub use midi::*; @@ -61,6 +63,11 @@ pub struct CellSeq { is_looping: bool, loop_len: usize, step_num: usize, + probability: f32, + randomness: f32, + velocity_min: u8, + velocity_max: u8, + channel: u8, } #[derive(Debug, Clone)] @@ -77,6 +84,11 @@ pub enum Message { SpeedChanged(usize), ToggleLoop, LoopLength(usize), + ProbChanged(f32), + RandChanged(f32), + NewVMin(u8), + NewVMax(u8), + ChannelChange(u8), Quit, } @@ -116,8 +128,14 @@ impl Application for CellSeq { self.map.tick() }; - let midi = self.mask.tick(map); + let midi = self.mask.tick(map.clone()); + let mut commands = Vec::new(); + + commands.push(Command::perform(async move { map }, |m| { + Message::Map(map::Message::Ticked(m)) + })); + for message in midi { commands.push(Command::perform(async move { message }, |m| { Message::Midi(m) @@ -134,7 +152,7 @@ impl Application for CellSeq { } Message::SpeedChanged(bpm) => self.bpm = bpm, Message::Clear => self.map.clear(), - Message::Randomize => self.map.randomize(), + Message::Randomize => self.map.randomize(self.randomness), Message::Reset => self.map.reset(), Message::Save => self.map.save(), Message::ToggleLoop => { @@ -145,6 +163,11 @@ impl Application for CellSeq { } Message::LoopLength(len) => self.loop_len = len, Message::Quit => todo!(), + Message::ProbChanged(prob) => self.probability = prob, + Message::RandChanged(rand) => self.randomness = rand, + Message::NewVMin(v) => self.velocity_min = v, + Message::NewVMax(v) => self.velocity_max = v, + Message::ChannelChange(chan) => self.channel = chan, } Command::none() @@ -159,13 +182,14 @@ impl Application for CellSeq { } fn view(&self) -> Element { - let controls = view_controls( + let top = top_controls( self.is_playing, self.bpm, self.is_looping, self.loop_len, self.step_num, ); + let map = row![ self.map.view().map(Message::Map), self.mask.view().map(Message::Mask) @@ -175,7 +199,15 @@ impl Application for CellSeq { .spacing(40) .padding(20); - let content = column![controls, map,]; + let bottom = bottom_controls( + self.probability, + self.randomness, + self.velocity_min, + self.velocity_max, + self.channel, + ); + + let content = column![top, map, bottom]; container(content) .width(Length::Fill) @@ -187,60 +219,3 @@ impl Application for CellSeq { Theme::Dark } } - -fn view_controls<'a>( - is_playing: bool, - bpm: usize, - is_looping: bool, - loop_len: usize, - step_num: usize, -) -> Element<'a, Message> { - let playback_controls = row![ - button(if is_playing { "pause" } else { "play" }).on_press(Message::TogglePlayback), - button(if is_looping { "free" } else { "loop" }).on_press(Message::ToggleLoop), - button("-").on_press(Message::LoopLength(loop_len.saturating_sub(1))), - text(if is_looping { - format!("{step_num}/{loop_len}") - } else { - format!("{loop_len}") - }), - button("+").on_press(Message::LoopLength(loop_len.saturating_add(1))) - ] - .spacing(10); - - let speed_controls = 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(1))), - ] - .width(Length::Fill) - .align_items(Alignment::Center) - .spacing(10); - - let other_controls = row![ - button("save").on_press(Message::Save), - button("reset") - .on_press(Message::Reset) - .style(theme::Button::Secondary), - button("random") - .on_press(Message::Randomize) - .style(theme::Button::Positive), - button("clear") - .on_press(Message::Clear) - .style(theme::Button::Destructive), - button("quit") - .on_press(Message::Quit) - .style(theme::Button::Destructive), - ] - .width(Length::Fill) - .align_items(Alignment::Center) - .spacing(10); - - row![playback_controls, speed_controls, other_controls] - .padding(10) - .spacing(40) - .align_items(Alignment::Center) - .into() -} diff --git a/src/main.rs b/src/main.rs index 950a632..13582ca 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,29 +10,29 @@ pub fn main() -> Result<()> { let (midi_snd, mut midi_rcv) = channel::>(256); // setting up jack client - let (jack_client, _status) = Client::new("cellseq", ClientOptions::empty())?; - let mut midi_port = jack_client.register_port("cellseq_midi", MidiOut::default())?; + // let (jack_client, _status) = Client::new("cellseq", ClientOptions::empty())?; + // let mut midi_port = jack_client.register_port("cellseq_midi", MidiOut::default())?; - let process_handler = ClosureProcessHandler::new(move |_: &Client, scope: &ProcessScope| { - let mut writer = midi_port.writer(scope); - let mut bytes = Vec::new(); + // let process_handler = ClosureProcessHandler::new(move |_: &Client, scope: &ProcessScope| { + // let mut writer = midi_port.writer(scope); + // let mut bytes = Vec::new(); - while let Ok(Some(byte)) = midi_rcv.try_recv() { - bytes.push(byte); - } + // while let Ok(Some(byte)) = midi_rcv.try_recv() { + // bytes.push(byte); + // } - let time = scope.frames_since_cycle_start(); - writer - .write(&RawMidi { - time, - bytes: &bytes[0..bytes.len()], - }) - .unwrap(); + // let time = scope.frames_since_cycle_start(); + // writer + // .write(&RawMidi { + // time, + // bytes: &bytes[0..bytes.len()], + // }) + // .unwrap(); - Control::Continue - }); + // Control::Continue + // }); - let jack = jack_client.activate_async((), process_handler)?; + // let jack = jack_client.activate_async((), process_handler)?; let midi = MidiLink::new(midi_snd); @@ -47,7 +47,7 @@ pub fn main() -> Result<()> { ..Settings::default() })?; - jack.deactivate()?; + // jack.deactivate()?; Ok(()) } diff --git a/src/map.rs b/src/map.rs index 259c3ab..2b6a559 100644 --- a/src/map.rs +++ b/src/map.rs @@ -100,10 +100,10 @@ impl Map { self.seed = self.cells.clone(); } - pub fn randomize(&mut self) { + pub fn randomize(&mut self, level: f32) { self.cells.clear(); for (i, j) in (-32..=32).cartesian_product(-32..=32) { - if random::() > 0.5 { + if random::() < level { self.cells.insert(Cell { i, j }); } } -- 2.45.2