]> git.huck.website - cellseq.git/commitdiff
added: music interactions
authorHuck Boles <huck@huck.website>
Fri, 30 Jun 2023 15:33:47 +0000 (10:33 -0500)
committerHuck Boles <huck@huck.website>
Fri, 30 Jun 2023 15:33:47 +0000 (10:33 -0500)
Cargo.lock
Cargo.toml
src/display.rs
src/lib.rs
src/main.rs
src/music.rs [new file with mode: 0644]

index fe00ab36a032ec253f501c7c8212fae917a4bdd0..1f28b2b39e64fa2fca2e029b938f513f1d72c0a1 100644 (file)
@@ -209,7 +209,6 @@ dependencies = [
  "eyre",
  "iced",
  "itertools",
- "jack",
  "rand",
  "rustc-hash",
  "thiserror",
@@ -1170,33 +1169,6 @@ dependencies = [
  "either",
 ]
 
-[[package]]
-name = "jack"
-version = "0.11.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0e5a18a3c2aefb354fb77111ade228b20267bdc779de84e7a4ccf7ea96b9a6cd"
-dependencies = [
- "bitflags",
- "jack-sys",
- "lazy_static",
- "libc",
- "log",
-]
-
-[[package]]
-name = "jack-sys"
-version = "0.5.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6013b7619b95a22b576dfb43296faa4ecbe40abbdb97dfd22ead520775fc86ab"
-dependencies = [
- "bitflags",
- "lazy_static",
- "libc",
- "libloading 0.7.4",
- "log",
- "pkg-config",
-]
-
 [[package]]
 name = "jni-sys"
 version = "0.3.0"
index b74337b3c6eeca47d6789867adaea4922e0f4ede..8f6ab030f2e959e06e47bc8cb422292dbbcb1a2d 100644 (file)
@@ -13,4 +13,3 @@ array2d = "0.3.0"
 iced = { version = "0.9", features = ["canvas", "tokio", "debug"] }
 itertools = "0.10"
 rustc-hash = "1.1"
-jack = "0.11"
index 48219e9fbd3d90e01c0e8d3093db231ebf1439b8..172cca2ff2a0ca34703e719e68aa69be9f540a18 100644 (file)
@@ -1,44 +1,30 @@
 use iced::{
     theme,
-    widget::{button, column, row, slider, text},
+    widget::{button, column, pick_list, row, slider, text, Column, Row},
     Alignment, Element, Length,
 };
 
-use crate::Message;
+use crate::{music::Scale, Message};
 
-pub fn top_controls<'a>(
-    is_playing: bool,
+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,
-) -> Element<'a, Message> {
+    octave: u8,
+    range: u8,
+    scale: Scale,
+}
+
+pub fn top_controls<'a>(is_playing: bool) -> 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),]
@@ -46,45 +32,74 @@ pub fn top_controls<'a>(
     .align_items(Alignment::Center)
     .spacing(10);
 
-    row![play_button, loop_controls, speed_controls, other_controls]
+    row![play_button, other_controls]
+        .padding(10)
+        .spacing(40)
+        .align_items(Alignment::Center)
+        .into()
+}
+
+pub fn bottom_controls<'a>(message: ControlMessage) -> Element<'a, Message> {
+    let song = row![]
+        .padding(10)
+        .spacing(10)
+        .align_items(Alignment::Center);
+
+    column![map_section(&message), midi_section(&message), song]
+        .width(Length::Fill)
+        .height(Length::Fill)
         .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}")),
+fn map_section<'a>(message: &ControlMessage) -> Row<'a, Message> {
+    row![
+        map_buttons(),
+        column![
+            probability_section(message.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> {
+    row![
+        slider(0.0..=100.0, p * 100.0, |x| {
+            Message::ProbChanged(x / 100.0)
+        }),
+        text(format!("{p}")),
         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);
+    .align_items(Alignment::Center)
+}
 
-    let randomize = row![
-        slider(0.0..=100.0, rand * 100.0, |x| Message::RandChanged(
-            x / 100.0
-        )),
-        text(format!("{rand}")),
+fn randomize_section<'a>(r: f32) -> Row<'a, Message> {
+    row![
+        slider(0.0..=100.0, r * 100.0, |x| {
+            Message::RandChanged(x / 100.0)
+        }),
+        text(format!("{r}")),
         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);
+    .align_items(Alignment::Center)
+}
 
-    let map_controls = row![
+fn map_buttons<'a>() -> Row<'a, Message> {
+    row![
         button("save")
             .on_press(Message::Save)
             .style(theme::Button::Positive),
@@ -100,14 +115,14 @@ pub fn bottom_controls<'a>(
     ]
     .padding(10)
     .spacing(10)
-    .align_items(Alignment::Center);
-
-    let map = column![map_controls, randomize];
+    .align_items(Alignment::Center)
+}
 
-    let velocity = column![
+fn velocity_sliders<'a>(min: u8, max: u8) -> Column<'a, Message> {
+    column![
         row![
-            slider(0..=127, vmin, Message::NewVMin),
-            text(format!("{vmin}")),
+            slider(0..=127, min, Message::NewVMin),
+            text(format!("{min}")),
             text("minimum velocity")
                 .style(theme::Text::Color(iced::Color::from_rgb8(0x40, 0x40, 0x40)))
         ]
@@ -115,8 +130,8 @@ pub fn bottom_controls<'a>(
         .spacing(10)
         .align_items(Alignment::Center),
         row![
-            slider(0..=127, vmax, Message::NewVMax),
-            text(format!("{vmax}")),
+            slider(0..=127, max, Message::NewVMax),
+            text(format!("{max}")),
             text("maximum velocity")
                 .style(theme::Text::Color(iced::Color::from_rgb8(0x40, 0x40, 0x40)))
         ]
@@ -126,23 +141,86 @@ pub fn bottom_controls<'a>(
     ]
     .padding(10)
     .spacing(10)
-    .align_items(Alignment::Center);
+    .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))),
+    ]
+    .padding(10)
+    .spacing(10)
+    .align_items(Alignment::Center)
+}
+
+fn song_section<'a>(message: &ControlMessage) -> Column<'a, Message> {}
+
+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),
+        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),
+        text(format!("range: +/-{range}")),
+        button("+")
+            .on_press(Message::OctaveRange(range.saturating_add(1)))
+            .style(theme::Button::Positive),
+    ]
+    .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
+fn loop_controls<'a>(looping: bool, len: usize, step: usize) -> 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}")
+        }),
+        button("+").on_press(Message::LoopLength(len.saturating_add(1)))
     ]
     .padding(10)
     .spacing(10)
-    .align_items(Alignment::Center);
+    .align_items(Alignment::Center)
+}
 
-    column![probability, map, midi]
+fn speed_controls<'a>(bpm: usize) -> 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))),
+    ]
+    .width(Length::Fill)
+    .align_items(Alignment::Center)
+    .spacing(10)
+}
+
+fn scale_selector<'a>(scale: Scale) -> Row<'a, Message> {
+    row![pick_list()]
         .width(Length::Fill)
-        .height(Length::Fill)
-        .padding(10)
-        .spacing(40)
         .align_items(Alignment::Center)
-        .into()
+        .spacing(10)
 }
index 09525af576fafa8f594f59790ad84392a7b07c44..433feef735d1732da4ca49d85f3b297f44afc1fe 100644 (file)
@@ -7,6 +7,7 @@ use iced::{
 };
 
 use itertools::Itertools;
+use music::Scale;
 use rustc_hash::FxHashSet;
 use std::time::{Duration, Instant};
 
@@ -14,11 +15,13 @@ mod display;
 mod map;
 mod mask;
 mod midi;
+mod music;
 
 use display::*;
 use map::*;
 use mask::*;
 pub use midi::*;
+use music::*;
 
 pub type CellMap = FxHashSet<Cell>;
 
@@ -68,6 +71,9 @@ pub struct CellSeq {
     velocity_min: u8,
     velocity_max: u8,
     channel: u8,
+    octave: u8,
+    oct_range: u8,
+    scale: Scale,
 }
 
 #[derive(Debug, Clone)]
@@ -89,9 +95,18 @@ pub enum Message {
     NewVMin(u8),
     NewVMax(u8),
     ChannelChange(u8),
+    Scale(Scale),
+    NewOctave(u8),
+    OctaveRange(u8),
     Quit,
 }
 
+impl CellSeq {
+    fn control_message(&self) -> ControlMessage {
+        todo!()
+    }
+}
+
 impl Application for CellSeq {
     type Message = Message;
     type Theme = Theme;
@@ -163,11 +178,14 @@ 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::ProbChanged(p) => self.probability = p,
+            Message::RandChanged(r) => self.randomness = r,
             Message::NewVMin(v) => self.velocity_min = v,
             Message::NewVMax(v) => self.velocity_max = v,
-            Message::ChannelChange(chan) => self.channel = chan,
+            Message::ChannelChange(c) => self.channel = c,
+            Message::Scale(s) => self.scale = s,
+            Message::NewOctave(o) => self.octave = o,
+            Message::OctaveRange(r) => self.oct_range = r,
         }
 
         Command::none()
@@ -182,13 +200,7 @@ impl Application for CellSeq {
     }
 
     fn view(&self) -> Element<Message> {
-        let top = top_controls(
-            self.is_playing,
-            self.bpm,
-            self.is_looping,
-            self.loop_len,
-            self.step_num,
-        );
+        let top = top_controls(self.is_playing);
 
         let map = row![
             self.map.view().map(Message::Map),
@@ -199,13 +211,7 @@ impl Application for CellSeq {
         .spacing(40)
         .padding(20);
 
-        let bottom = bottom_controls(
-            self.probability,
-            self.randomness,
-            self.velocity_min,
-            self.velocity_max,
-            self.channel,
-        );
+        let bottom = bottom_controls(self.control_message());
 
         let content = column![top, map, bottom];
 
index 13582ca14b4e45c6164c96b0430f8c3b5f61b12a..ff826aa8e8596a837240aeec3b80eec0e6878bd6 100644 (file)
@@ -3,36 +3,10 @@ use cellseq::*;
 use iced::{window, Application, Settings};
 
 use eyre::Result;
-use jack::{Client, ClientOptions, ClosureProcessHandler, Control, MidiOut, ProcessScope, RawMidi};
 use tokio::sync::mpsc::channel;
 
 pub fn main() -> Result<()> {
-    let (midi_snd, mut midi_rcv) = channel::<Option<u8>>(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 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);
-    //     }
-
-    //     let time = scope.frames_since_cycle_start();
-    //     writer
-    //         .write(&RawMidi {
-    //             time,
-    //             bytes: &bytes[0..bytes.len()],
-    //         })
-    //         .unwrap();
-
-    //     Control::Continue
-    // });
-
-    // let jack = jack_client.activate_async((), process_handler)?;
+    let (midi_snd, _midi_rcv) = channel::<Option<u8>>(256);
 
     let midi = MidiLink::new(midi_snd);
 
@@ -47,7 +21,5 @@ pub fn main() -> Result<()> {
         ..Settings::default()
     })?;
 
-    // jack.deactivate()?;
-
     Ok(())
 }
diff --git a/src/music.rs b/src/music.rs
new file mode 100644 (file)
index 0000000..2ef742a
--- /dev/null
@@ -0,0 +1,85 @@
+use std::fmt::display;
+
+#[derive(Clone, Copy, Eq, PartialEq, Default, Debug)]
+pub enum Scale {
+    #[default]
+    Chromatic,
+    Major,
+    Minor,
+    Dorian,
+    Phrygian,
+    Lydian,
+    Mixolydian,
+    Locrian,
+    MinorPentatonic,
+    MajorPentatonic,
+    MelodicMinor,
+    HarmonicMinor,
+    WholeTone,
+}
+
+impl Into<[bool; 12]> for Scale {
+    fn into(self) -> [bool; 12] {
+        match self {
+            Scale::Chromatic => [true; 12],
+            Scale::Major => [
+                true, false, true, false, true, true, false, true, false, true, false, true,
+            ],
+            Scale::Minor => [
+                true, false, true, true, false, true, false, true, true, false, true, false,
+            ],
+            Scale::HarmonicMinor => [
+                true, false, true, true, false, true, false, true, true, false, false, true,
+            ],
+            Scale::MelodicMinor => [
+                true, false, true, true, false, true, false, true, false, true, false, true,
+            ],
+            Scale::Dorian => [
+                true, false, true, true, false, true, false, true, false, true, true, false,
+            ],
+            Scale::Phrygian => [
+                true, true, false, true, false, true, false, true, true, false, true, false,
+            ],
+            Scale::Lydian => [
+                true, false, true, false, true, false, true, true, false, true, false, true,
+            ],
+            Scale::Mixolydian => [
+                true, false, true, false, true, true, false, true, false, true, true, false,
+            ],
+            Scale::Locrian => [
+                true, true, false, true, false, true, true, false, true, false, true, false,
+            ],
+            Scale::MajorPentatonic => [
+                true, false, true, false, true, false, false, true, false, true, false, false,
+            ],
+            Scale::MinorPentatonic => [
+                true, false, false, true, false, true, false, true, false, false, true, false,
+            ],
+            Self::WholeTone => [
+                true, false, true, false, true, false, true, false, true, false, true, false,
+            ],
+        }
+    }
+}
+
+impl std::fmt::Display for Scale {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        let str = match self {
+            Scale::Chromatic => "chromatic",
+            Scale::Major => "major",
+            Scale::Minor => "minor",
+            Scale::Dorian => "dorian",
+            Scale::Phrygian => "phrygian",
+            Scale::Lydian => "lydian",
+            Scale::Mixolydian => "mixolydian",
+            Scale::Locrian => "locrian",
+            Scale::MinorPentatonic => "minor pentatonic",
+            Scale::MajorPentatonic => "major pentatonic",
+            Scale::MelodicMinor => "melodic minor",
+            Scale::HarmonicMinor => "harmonic minor",
+            Scale::WholeTone => "whole tone",
+        };
+
+        write!(f, "{str}")
+    }
+}