]> git.huck.website - cellseq.git/commitdiff
added: all display features operational
authorHuck Boles <huck@huck.website>
Sun, 2 Jul 2023 02:59:01 +0000 (21:59 -0500)
committerHuck Boles <huck@huck.website>
Sun, 2 Jul 2023 02:59:01 +0000 (21:59 -0500)
src/display.rs
src/lib.rs
src/map.rs
src/midi.rs
src/music.rs

index c370398ff60897af9b060e775d24addbdcd38cb8..9aafdbea06ce99e2f5eabc642a5edd96723a72ca 100644 (file)
@@ -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)
 }
index bb93f4998a009d4be1481dd91c8344d93c5ed78e..61e9b162ffe2e39e69198e7680b1c1515e309d4c 100644 (file)
@@ -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<Message>) {
         (
             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<Message> {
         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<Message> {
-        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<Message> {
-        let top = top_controls(self.is_playing);
+        let top = top_controls(self.song.is_playing);
 
         let map = row![
             self.map.view().map(Message::MapMessage),
index 2b6a559e32094ccadc2da16a43466f8b81b9634f..1bcf23ebf397ff296e5f951b7b4f45de64c6bae2 100644 (file)
@@ -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::<f32>() < level {
+            if random::<f32>() < 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<Message> for Map {
index 20cfaa46a915e817cccd7a15243d410e8bfd9b10..dd5cbe823d1a71402d9fd1401b4baf86138a0500 100644 (file)
@@ -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<MidiMessage>,
index 4ab6df14d2c596ed77013dd3a6888aa199adc811..d1ffe54ed40b9cd05ec1860ac5d19267691024ef 100644 (file)
@@ -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)]