From 6d457f51c98ddcbbb108cd6b97a51a452fbcca81 Mon Sep 17 00:00:00 2001 From: Huck Boles Date: Fri, 30 Jun 2023 10:33:47 -0500 Subject: [PATCH] added: music interactions --- Cargo.lock | 28 ------- Cargo.toml | 1 - src/display.rs | 216 +++++++++++++++++++++++++++++++++---------------- src/lib.rs | 40 +++++---- src/main.rs | 30 +------ src/music.rs | 85 +++++++++++++++++++ 6 files changed, 256 insertions(+), 144 deletions(-) create mode 100644 src/music.rs diff --git a/Cargo.lock b/Cargo.lock index fe00ab3..1f28b2b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml index b74337b..8f6ab03 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/src/display.rs b/src/display.rs index 48219e9..172cca2 100644 --- a/src/display.rs +++ b/src/display.rs @@ -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) } diff --git a/src/lib.rs b/src/lib.rs index 09525af..433feef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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; @@ -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 { - 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]; diff --git a/src/main.rs b/src/main.rs index 13582ca..ff826aa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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::>(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::>(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 index 0000000..2ef742a --- /dev/null +++ b/src/music.rs @@ -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}") + } +} -- 2.44.2