From 1388793d92ab20f71b19d99c3b015451c7acaa5f Mon Sep 17 00:00:00 2001 From: Huck Boles Date: Sat, 1 Jul 2023 21:59:01 -0500 Subject: [PATCH] added: all display features operational --- src/display.rs | 203 ++++++++++++++++++++----------------------------- src/lib.rs | 87 +++++++++++++-------- src/map.rs | 13 +++- src/midi.rs | 20 ++++- src/music.rs | 24 ++++++ 5 files changed, 192 insertions(+), 155 deletions(-) diff --git a/src/display.rs b/src/display.rs index c370398..9aafdbe 100644 --- a/src/display.rs +++ b/src/display.rs @@ -6,24 +6,14 @@ use iced::{ use crate::{ music::{Accidental, Root, RootNote, Scale}, - Message, + Message, MidiInfo, SongInfo, }; +#[derive(Default, Copy, Clone, Debug)] pub struct ControlMessage { - probability: f32, - randomness: f32, - velocity_min: u8, - velocity_max: u8, - channel: u8, - bpm: usize, - is_looping: bool, - loop_len: usize, - step_num: usize, - octave: u8, - range: u8, - scale: Scale, - root: Root, - voices: u8, + pub randomness: f32, + pub info: MidiInfo, + pub song: SongInfo, } pub fn top_controls<'a>(is_playing: bool) -> Element<'a, Message> { @@ -38,39 +28,34 @@ pub fn top_controls<'a>(is_playing: bool) -> Element<'a, Message> { .spacing(10); row![play_button, other_controls] + .width(Length::Fill) .padding(10) .spacing(40) .into() } pub fn bottom_controls<'a>(message: ControlMessage) -> Element<'a, Message> { - column![ - map_section(&message), - midi_section(&message), - song_section(&message) - ] - .width(Length::Fill) - .height(Length::Fill) - .padding(10) - .spacing(40) - .align_items(Alignment::Center) - .into() + column![map_section(&message), midi_section(&message),] + .width(Length::Fill) + .height(Length::Fill) + .padding(10) + .spacing(20) + .align_items(Alignment::Center) + .into() } fn map_section<'a>(message: &ControlMessage) -> Row<'a, Message> { row![ map_buttons(), column![ - probability_section(message.probability), + probability_section(message.info.probability), randomize_section(message.randomness) ] .padding(10) .spacing(10) .align_items(Alignment::Center) ] - .padding(10) .spacing(10) - .align_items(Alignment::Center) } fn probability_section<'a>(p: f32) -> Row<'a, Message> { @@ -82,9 +67,7 @@ fn probability_section<'a>(p: f32) -> Row<'a, Message> { 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) } fn randomize_section<'a>(r: f32) -> Row<'a, Message> { @@ -96,125 +79,101 @@ fn randomize_section<'a>(r: f32) -> Row<'a, Message> { 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) } -fn map_buttons<'a>() -> Row<'a, Message> { - 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), +fn map_buttons<'a>() -> Column<'a, Message> { + column![ + row![ + button("save") + .on_press(Message::Save) + .style(theme::Button::Positive), + button("clear") + .on_press(Message::Clear) + .style(theme::Button::Destructive), + ] + .spacing(10), + row![ + button("reset") + .on_press(Message::Reset) + .style(theme::Button::Secondary), + button("random") + .on_press(Message::Randomize) + .style(theme::Button::Primary), + ] + .spacing(10) ] - .padding(10) .spacing(10) - .align_items(Alignment::Center) } fn velocity_sliders<'a>(min: u8, max: u8) -> Column<'a, Message> { column![ row![ - slider(0..=127, min, Message::NewVMin), - text(format!("{min}")), - text("minimum velocity") + text(format!("{max}")), + slider(0..=127, max, Message::NewVMax), + text("maximum velocity") .style(theme::Text::Color(iced::Color::from_rgb8(0x40, 0x40, 0x40))) ] - .padding(10) - .spacing(10) - .align_items(Alignment::Center), + .spacing(10), row![ - slider(0..=127, max, Message::NewVMax), - text(format!("{max}")), - text("maximum velocity") + text(format!("{min}")), + slider(0..=127, min, Message::NewVMin), + text("minimum velocity") .style(theme::Text::Color(iced::Color::from_rgb8(0x40, 0x40, 0x40))) ] - .padding(10) - .spacing(10) - .align_items(Alignment::Center) + .spacing(10), ] - .padding(10) .spacing(10) - .align_items(Alignment::Center) } -fn midi_section<'a>(message: &ControlMessage) -> Row<'a, Message> { - row![ - channel_selector(message.channel), - velocity_sliders(message.velocity_min, message.velocity_max) - ] - .padding(10) - .spacing(10) - .align_items(Alignment::Center) -} - -fn channel_selector<'a>(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))), +fn midi_section<'a>(message: &ControlMessage) -> Column<'a, Message> { + column![ + song_section(message), + velocity_sliders(message.info.velocity.min(), message.info.velocity.max()) ] - .padding(10) .spacing(10) - .align_items(Alignment::Center) } fn song_section<'a>(message: &ControlMessage) -> Row<'a, Message> { row![ column![ - loop_controls(message.is_looping, message.loop_len, message.step_num), - speed_controls(message.bpm) + 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.voices), - octave_selector(message.octave, message.range), + voice_controls(message.info.voices, message.info.channel), + octave_selector(message.info.octave.center(), message.info.octave.range()), scale_selector( - message.scale, - message.root.get_note(), - message.root.get_accidental() + message.info.scale, + message.info.root.get_note(), + message.info.root.get_accidental() ) ] .padding(10) .spacing(10) .align_items(Alignment::Center) ] - .padding(10) .spacing(10) - .align_items(Alignment::Center) } fn octave_selector<'a>(oct: u8, range: u8) -> Row<'a, Message> { row![ - button("-") - .on_press(Message::NewOctave(oct.saturating_sub(1))) - .style(theme::Button::Destructive), + button("-").on_press(Message::NewOctave(oct.saturating_sub(1))), text(format!("octave: {oct}")), - button("+") - .on_press(Message::NewOctave(oct.saturating_add(1))) - .style(theme::Button::Positive), - button("-") - .on_press(Message::OctaveRange(range.saturating_sub(1))) - .style(theme::Button::Destructive), + 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))) - .style(theme::Button::Positive), + button("+").on_press(Message::OctaveRange(range.saturating_add(1))) ] - .padding(10) .spacing(10) - .align_items(Alignment::Center) } fn loop_controls<'a>(looping: bool, len: usize, step: usize) -> Row<'a, Message> { @@ -228,9 +187,7 @@ fn loop_controls<'a>(looping: bool, len: usize, step: usize) -> Row<'a, Message> }), button("+").on_press(Message::LoopLength(len.saturating_add(1))) ] - .padding(10) .spacing(10) - .align_items(Alignment::Center) } fn speed_controls<'a>(bpm: usize) -> Row<'a, Message> { @@ -241,37 +198,43 @@ fn speed_controls<'a>(bpm: usize) -> Row<'a, Message> { 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) } -fn voice_controls<'a>(voices: u8) -> Row<'a, Message> { +fn division_controls<'a>(divisor: usize) -> Row<'a, Message> { row![ - button("-") - .on_press(Message::Voices(voices.saturating_sub(1))) - .style(theme::Button::Destructive), + 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))) + ] + .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))) - .style(theme::Button::Positive), + button("+").on_press(Message::Voices(voices.saturating_add(1))), ] - .padding(10) .spacing(10) - .align_items(Alignment::Center) } fn scale_selector<'a>(scale: Scale, note: RootNote, acc: Accidental) -> Row<'a, Message> { row![ - pick_list(&Scale::ALL[..], Some(scale), Message::Scale), pick_list(&RootNote::ALL[..], Some(note), move |note| { Message::NewNote(Root::new(note, acc)) }), pick_list(&Accidental::ALL[..], Some(acc), move |acc| { Message::NewNote(Root::new(note, acc)) - }) + }), + pick_list(&Scale::ALL[..], Some(scale), Message::Scale), ] - .width(Length::Fill) .align_items(Alignment::Center) - .spacing(10) } diff --git a/src/lib.rs b/src/lib.rs index bb93f49..61e9b16 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -61,13 +61,31 @@ pub struct CellSeq { map: Map, mask: Mask, midi: MidiLink, + song: SongInfo, info: MidiInfo, - is_playing: bool, - bpm: usize, - is_looping: bool, - loop_len: usize, - step_num: usize, - randomness: f32, +} + +#[derive(Copy, Clone, Debug)] +pub struct SongInfo { + pub is_playing: bool, + pub bpm: usize, + pub divisor: usize, + pub is_looping: bool, + pub loop_len: usize, + pub step_num: usize, +} + +impl Default for SongInfo { + fn default() -> Self { + Self { + is_playing: false, + bpm: 120, + divisor: 1, + is_looping: false, + loop_len: 16, + step_num: 0, + } + } } #[derive(Debug, Clone)] @@ -84,6 +102,7 @@ pub enum Message { Save, TogglePlayback, SpeedChanged(usize), + NewDivision(usize), ToggleLoop, LoopLength(usize), ProbChanged(f32), @@ -101,7 +120,11 @@ pub enum Message { impl CellSeq { fn control_message(&self) -> ControlMessage { - todo!() + ControlMessage { + randomness: self.map.randomness(), + info: self.info.clone(), + song: self.song.clone(), + } } } @@ -114,8 +137,6 @@ impl Application for CellSeq { fn new(flags: Self::Flags) -> (Self, Command) { ( Self { - bpm: 120, - loop_len: 16, midi: flags, ..Self::default() }, @@ -129,21 +150,22 @@ impl Application for CellSeq { fn update(&mut self, message: Message) -> Command { match message { + Message::Quit => todo!(), // TODO: figure out how to cleanly quit Message::None => {} + Message::MapMessage(message) => self.map.update(message), + Message::MaskMessage(message) => self.mask.update(message), + Message::HitCount(x) => self.midi.update(x, &self.info), Message::NewMap(m) => { self.map.update(map::Message::Ticked(m.clone())); let hits = self.mask.tick(m); return Command::perform(async move { hits }, Message::HitCount); } - Message::HitCount(x) => self.midi.update(x, &self.info), - Message::MapMessage(message) => self.map.update(message), - Message::MaskMessage(message) => self.mask.update(message), Message::Tick(_) => { - let map = if self.is_looping && self.step_num > self.loop_len { - self.step_num = 0; + let map = if self.song.is_looping && self.song.step_num > self.song.loop_len { + self.song.step_num = 0; self.map.reset_loop() } else { - self.step_num += 1; + self.song.step_num += 1; self.map.tick() }; @@ -163,26 +185,26 @@ impl Application for CellSeq { return Command::batch(commands); } Message::TogglePlayback => { - self.is_playing = !self.is_playing; - if self.is_playing { + self.song.is_playing = !self.song.is_playing; + if self.song.is_playing { self.map.save() } } - Message::SpeedChanged(bpm) => self.bpm = bpm, - Message::Clear => self.map.clear(), - Message::Randomize => self.map.randomize(self.randomness), - Message::Reset => self.map.reset(), - Message::Save => self.map.save(), Message::ToggleLoop => { - self.is_looping = !self.is_looping; - if self.is_looping { - self.step_num = 0; + self.song.is_looping = !self.song.is_looping; + if self.song.is_looping { + self.song.step_num = 0; } } - Message::LoopLength(len) => self.loop_len = len, - Message::Quit => todo!(), + Message::Clear => self.map.clear(), + Message::Randomize => self.map.randomize(), + 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.randomness = r, + 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, @@ -197,15 +219,18 @@ impl Application for CellSeq { } fn subscription(&self) -> Subscription { - if self.is_playing { - time::every(Duration::from_millis(60000 / self.bpm as u64)).map(Message::Tick) + if self.song.is_playing { + time::every(Duration::from_millis( + 60000 / (self.song.bpm * self.song.divisor) as u64, + )) + .map(Message::Tick) } else { Subscription::none() } } fn view(&self) -> Element { - let top = top_controls(self.is_playing); + let top = top_controls(self.song.is_playing); let map = row![ self.map.view().map(Message::MapMessage), diff --git a/src/map.rs b/src/map.rs index 2b6a559..1bcf23e 100644 --- a/src/map.rs +++ b/src/map.rs @@ -19,6 +19,7 @@ pub struct Map { seed: CellMap, cells: CellMap, life_cache: Cache, + randomness: f32, } #[derive(Debug, Clone)] @@ -100,16 +101,24 @@ impl Map { self.seed = self.cells.clone(); } - pub fn randomize(&mut self, level: f32) { + pub fn randomize(&mut self) { self.cells.clear(); for (i, j) in (-32..=32).cartesian_product(-32..=32) { - if random::() < level { + if random::() < self.randomness { self.cells.insert(Cell { i, j }); } } self.seed = self.cells.clone(); self.life_cache.clear(); } + + pub fn randomness(&self) -> f32 { + self.randomness + } + + pub fn set_randomness(&mut self, value: f32) { + self.randomness = value; + } } impl Program for Map { diff --git a/src/midi.rs b/src/midi.rs index 20cfaa4..dd5cbe8 100644 --- a/src/midi.rs +++ b/src/midi.rs @@ -5,9 +5,11 @@ use rand::random; use thiserror::Error; use tokio::sync::mpsc::Sender; -use crate::music::{generate_note, generate_velocity, Octave, Root, Scale, Velocity}; +use crate::music::{ + generate_note, generate_velocity, Accidental, Octave, Root, RootNote, Scale, Velocity, +}; -#[derive(Debug, Clone, Copy, Default)] +#[derive(Debug, Clone, Copy)] pub struct MidiInfo { pub channel: u8, pub velocity: Velocity, @@ -18,6 +20,20 @@ pub struct MidiInfo { pub probability: f32, } +impl Default for MidiInfo { + fn default() -> Self { + Self { + channel: 0, + velocity: Velocity::new(64, 127), + octave: Octave::new(4, 1), + scale: Scale::Chromatic, + root: Root::new(RootNote::C, Accidental::Natural), + voices: 6, + probability: 0.5, + } + } +} + #[derive(Clone, Debug)] pub struct MidiLink { buffer: Vec, diff --git a/src/music.rs b/src/music.rs index 4ab6df1..d1ffe54 100644 --- a/src/music.rs +++ b/src/music.rs @@ -29,6 +29,10 @@ pub struct Octave { } impl Octave { + pub fn new(center: u8, range: u8) -> Self { + Self { center, range } + } + pub fn set_center(&mut self, center: u8) { self.center = center; } @@ -36,6 +40,14 @@ impl Octave { 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 + } } impl Default for Octave { @@ -54,6 +66,10 @@ pub struct Velocity { } impl Velocity { + pub fn new(min: u8, max: u8) -> Self { + Self { min, max } + } + pub fn set_min(&mut self, min: u8) { self.min = min; } @@ -61,6 +77,14 @@ impl Velocity { pub fn set_max(&mut self, max: u8) { self.max = max; } + + pub fn min(&self) -> u8 { + self.min + } + + pub fn max(&self) -> u8 { + self.max + } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -- 2.45.2