--- /dev/null
+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()
+}
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},
};
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::*;
is_looping: bool,
loop_len: usize,
step_num: usize,
+ probability: f32,
+ randomness: f32,
+ velocity_min: u8,
+ velocity_max: u8,
+ channel: u8,
}
#[derive(Debug, Clone)]
SpeedChanged(usize),
ToggleLoop,
LoopLength(usize),
+ ProbChanged(f32),
+ RandChanged(f32),
+ NewVMin(u8),
+ NewVMax(u8),
+ ChannelChange(u8),
Quit,
}
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)
}
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 => {
}
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()
}
fn view(&self) -> Element<Message> {
- 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)
.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)
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()
-}
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 (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);
..Settings::default()
})?;
- jack.deactivate()?;
+ // jack.deactivate()?;
Ok(())
}
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::<f32>() > 0.5 {
+ if random::<f32>() < level {
self.cells.insert(Cell { i, j });
}
}