Alignment, Element, Length,
};
-use crate::{music::Scale, Message};
+use crate::{
+ music::{Accidental, Root, RootNote, Scale},
+ Message,
+};
pub struct ControlMessage {
probability: f32,
octave: u8,
range: u8,
scale: Scale,
+ root: Root,
+ voices: u8,
}
pub fn top_controls<'a>(is_playing: bool) -> Element<'a, Message> {
.on_press(Message::Quit)
.style(theme::Button::Destructive),]
.width(Length::Fill)
- .align_items(Alignment::Center)
+ .align_items(Alignment::End)
.spacing(10);
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()
+ 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()
}
fn map_section<'a>(message: &ControlMessage) -> Row<'a, Message> {
.align_items(Alignment::Center)
}
-fn song_section<'a>(message: &ControlMessage) -> Column<'a, Message> {}
+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)
+ ]
+ .padding(10)
+ .spacing(10)
+ .align_items(Alignment::Center),
+ column![
+ voice_controls(message.voices),
+ octave_selector(message.octave, message.range),
+ scale_selector(
+ message.scale,
+ message.root.get_note(),
+ message.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![
.spacing(10)
}
-fn scale_selector<'a>(scale: Scale) -> Row<'a, Message> {
- row![pick_list()]
- .width(Length::Fill)
- .align_items(Alignment::Center)
- .spacing(10)
+fn voice_controls<'a>(voices: u8) -> Row<'a, Message> {
+ row![
+ button("-")
+ .on_press(Message::Voices(voices.saturating_sub(1)))
+ .style(theme::Button::Destructive),
+ text(format!("voices: {voices}")),
+ button("+")
+ .on_press(Message::Voices(voices.saturating_add(1)))
+ .style(theme::Button::Positive),
+ ]
+ .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))
+ })
+ ]
+ .width(Length::Fill)
+ .align_items(Alignment::Center)
+ .spacing(10)
}
map: Map,
mask: Mask,
midi: MidiLink,
+ info: MidiInfo,
is_playing: bool,
bpm: usize,
is_looping: bool,
loop_len: usize,
step_num: usize,
- probability: f32,
randomness: f32,
- velocity_min: u8,
- velocity_max: u8,
- channel: u8,
- octave: u8,
- oct_range: u8,
- scale: Scale,
}
#[derive(Debug, Clone)]
pub enum Message {
- Midi(MidiMessage),
- Map(map::Message),
- Mask(mask::Message),
+ None,
+ MapMessage(map::Message),
+ MaskMessage(mask::Message),
+ NewMap(CellMap),
+ HitCount(u8),
Tick(Instant),
Randomize,
Reset,
Scale(Scale),
NewOctave(u8),
OctaveRange(u8),
+ NewNote(Root),
+ Voices(u8),
Quit,
}
fn update(&mut self, message: Message) -> Command<Message> {
match message {
- Message::Map(message) => self.map.update(message),
- Message::Mask(message) => self.mask.update(message),
- Message::Midi(message) => self.midi.update(message),
+ Message::None => {}
+ 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;
self.map.tick()
};
- let midi = self.mask.tick(map.clone());
-
- let mut commands = Vec::new();
+ let channel = self.midi.channel_handle();
+ let bytes = self.midi.tick();
- commands.push(Command::perform(async move { map }, |m| {
- Message::Map(map::Message::Ticked(m))
- }));
+ let midi = tokio::spawn(async move {
+ for byte in bytes {
+ channel.send(byte).await.unwrap()
+ }
+ });
- for message in midi {
- commands.push(Command::perform(async move { message }, |m| {
- Message::Midi(m)
- }));
- }
+ let mut commands = Vec::new();
+ commands.push(Command::perform(async move { map }, Message::NewMap));
+ commands.push(Command::perform(midi, |_| Message::None));
return Command::batch(commands);
}
}
Message::LoopLength(len) => self.loop_len = len,
Message::Quit => todo!(),
- Message::ProbChanged(p) => self.probability = p,
+ Message::ProbChanged(p) => self.info.probability = p,
Message::RandChanged(r) => self.randomness = r,
- Message::NewVMin(v) => self.velocity_min = v,
- Message::NewVMax(v) => self.velocity_max = v,
- 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,
+ 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,
+ Message::Scale(s) => self.info.scale = s,
+ Message::NewOctave(o) => self.info.octave.set_center(o),
+ Message::OctaveRange(r) => self.info.octave.set_range(r),
+ Message::NewNote(r) => self.info.root = r,
+ Message::Voices(v) => self.info.voices = v,
}
Command::none()
let top = top_controls(self.is_playing);
let map = row![
- self.map.view().map(Message::Map),
- self.mask.view().map(Message::Mask)
+ self.map.view().map(Message::MapMessage),
+ self.mask.view().map(Message::MaskMessage)
]
.align_items(Alignment::Center)
.width(Length::Fill)
use tokio::sync::mpsc::channel;
pub fn main() -> Result<()> {
- let (midi_snd, _midi_rcv) = channel::<Option<u8>>(256);
+ let (midi_snd, _midi_rcv) = channel::<u8>(256);
let midi = MidiLink::new(midi_snd);
{Color, Element, Length, Point, Rectangle, Size, Theme},
};
-use crate::{Cell, CellMap, MidiMessage};
+use crate::{Cell, CellMap};
use itertools::Itertools;
-use rustc_hash::FxHashMap;
+use rustc_hash::FxHashSet;
#[derive(Debug, Clone)]
pub enum Message {
Check(Cell),
Uncheck(Cell),
- SetNote((Cell, Note)),
- Tick(CellMap),
-}
-
-#[derive(Default, Debug, Clone, Copy, Eq, PartialEq, Hash)]
-enum OnOff {
- #[default]
- Off,
- On,
-}
-
-#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
-pub struct Note {
- value: u8,
- velocity: u8,
- state: OnOff,
-}
-
-impl Note {
- pub fn is_on(&self) -> bool {
- match self.state {
- OnOff::On => true,
- OnOff::Off => false,
- }
- }
-
- pub fn switch(&mut self) {
- self.state = match self.state {
- OnOff::On => OnOff::Off,
- OnOff::Off => OnOff::On,
- }
- }
}
#[derive(Default, Debug)]
pub struct Mask {
- cells: FxHashMap<Cell, Note>,
+ cells: FxHashSet<Cell>,
mask_cache: Cache,
}
pub fn update(&mut self, message: Message) {
match message {
Message::Check(cell) => {
- self.cells.insert(cell, Note::default());
+ self.cells.insert(cell);
self.mask_cache.clear()
}
Message::Uncheck(cell) => {
self.cells.remove(&cell);
self.mask_cache.clear();
}
- Message::Tick(life) => {
- for cell in life.iter() {
- if self.cells.contains_key(cell) {
- let note = self.cells.entry(*cell).or_default();
- note.switch()
- }
- }
-
- self.mask_cache.clear();
- }
- Message::SetNote((cell, note)) => {
- self.cells.insert(cell, note);
- }
- }
- }
-
- pub fn is_on(&self, cell: &Cell) -> bool {
- if let Some(note) = self.cells.get(cell) {
- note.is_on()
- } else {
- false
}
}
.into()
}
- pub fn tick(&mut self, life: CellMap) -> Vec<MidiMessage> {
- let mut note_map: FxHashMap<u8, u8> = FxHashMap::default();
- let mut messages = Vec::new();
-
- for cell in life.iter() {
- if let Some(note) = self.cells.get(cell) {
- note_map.insert(note.value, note.velocity);
+ pub fn tick(&mut self, life: CellMap) -> u8 {
+ let mut hits = 0;
+ for cell in self.cells.iter() {
+ if life.contains(cell) {
+ hits += 1;
}
}
-
- for (note, vel) in note_map {
- messages.push(MidiMessage::On {
- note,
- velocity: vel,
- channel: 0,
- })
- }
-
- messages
+ hits
}
}
(0..24)
.cartesian_product(0..24)
- .filter(|x| self.cells.contains_key(&Cell { i: x.1, j: x.0 }))
+ .filter(|x| self.cells.contains(&Cell { i: x.1, j: x.0 }))
.for_each(|x| {
- let color = if self.is_on(&Cell { i: x.0, j: x.1 }) {
- Color::from_rgb8(0xF0, 0xC0, 0xC0)
- } else {
- Color::WHITE
- };
-
- frame.fill_rectangle(Point::new(x.0 as f32, x.1 as f32), Size::UNIT, color);
+ frame.fill_rectangle(
+ Point::new(x.0 as f32, x.1 as f32),
+ Size::UNIT,
+ Color::WHITE,
+ );
})
});
})]
let cell = Cell::at(position);
return (
event::Status::Captured,
- if self.cells.contains_key(&cell) {
+ if self.cells.contains(&cell) {
Some(Message::Uncheck(cell))
} else {
Some(Message::Check(cell))
-use std::{collections::VecDeque, fmt::Display};
+use std::fmt::Display;
+use eyre::Result;
+use rand::random;
use thiserror::Error;
use tokio::sync::mpsc::Sender;
+use crate::music::{generate_note, generate_velocity, Octave, Root, Scale, Velocity};
+
+#[derive(Debug, Clone, Copy, Default)]
+pub struct MidiInfo {
+ pub channel: u8,
+ pub velocity: Velocity,
+ pub octave: Octave,
+ pub scale: Scale,
+ pub root: Root,
+ pub voices: u8,
+ pub probability: f32,
+}
+
#[derive(Clone, Debug)]
pub struct MidiLink {
- buffer: VecDeque<u8>,
- channel: Sender<Option<u8>>,
+ buffer: Vec<MidiMessage>,
+ channel: Sender<u8>,
}
impl Default for MidiLink {
fn default() -> Self {
let (send, _) = tokio::sync::mpsc::channel(128);
Self {
- buffer: VecDeque::default(),
channel: send,
+ buffer: Vec::default(),
}
}
}
-impl<'a> MidiLink {
- pub fn new(channel: Sender<Option<u8>>) -> Self {
+impl MidiLink {
+ pub fn new(channel: Sender<u8>) -> Self {
Self {
- buffer: VecDeque::default(),
channel,
+ ..Self::default()
}
}
- pub fn update(&mut self, message: MidiMessage) {
- let bytes = message.as_bytes().unwrap();
+ pub fn channel_handle(&self) -> Sender<u8> {
+ self.channel.clone()
+ }
- for byte in bytes.iter().filter_map(|x| *x) {
- self.buffer.push_back(byte);
+ pub fn update(&mut self, hits: u8, info: &MidiInfo) {
+ let mut count = 0;
+
+ for _ in 0..hits {
+ if count > info.voices {
+ break;
+ } else if random::<f32>() < info.probability {
+ continue;
+ } else {
+ count += 1;
+ self.buffer.push(MidiMessage::On {
+ note: generate_note(info),
+ velocity: generate_velocity(info.velocity),
+ channel: info.channel,
+ });
+ }
}
}
- pub async fn tick(&mut self) {
- for byte in self.buffer.iter() {
- self.channel.send(Some(*byte)).await.unwrap();
- }
+ pub fn tick(&mut self) -> Vec<u8> {
+ let vec: Vec<u8> = self
+ .buffer
+ .iter()
+ .flat_map(|m| m.as_bytes().unwrap())
+ .flatten()
+ .collect();
- self.channel.send(None).await.unwrap();
self.buffer.clear();
+ vec
}
}
}
}
-static DATA_BIT: u8 = 0b0111_1111;
-static STATUS_BIT: u8 = 0b1111_1111;
+static DATA_MASK: u8 = 0b0111_1111;
+static STATUS_MASK: u8 = 0b1111_1111;
impl MidiMessage {
pub fn as_bytes(&self) -> Result<[Option<u8>; 3], MidiError> {
} else if *channel > 15 {
return Err(MidiError::ChannelOverflow { message: *self });
}
- bytes[0] = Some(STATUS_BIT & (0x90 + channel));
- bytes[1] = Some(DATA_BIT & note);
- bytes[2] = Some(DATA_BIT & velocity);
+ bytes[0] = Some(STATUS_MASK & (0x90 + channel));
+ bytes[1] = Some(DATA_MASK & note);
+ bytes[2] = Some(DATA_MASK & velocity);
}
MidiMessage::Off {
note,
} else if *channel > 15 {
return Err(MidiError::ChannelOverflow { message: *self });
}
- bytes[0] = Some(STATUS_BIT & (0x80 + channel));
- bytes[1] = Some(DATA_BIT & note);
- bytes[2] = Some(DATA_BIT & velocity);
+ bytes[0] = Some(STATUS_MASK & (0x80 + channel));
+ bytes[1] = Some(DATA_MASK & note);
+ bytes[2] = Some(DATA_MASK & velocity);
}
MidiMessage::Cc {
controller,
} else if *channel > 15 {
return Err(MidiError::ChannelOverflow { message: *self });
}
- bytes[0] = Some(STATUS_BIT & (0xD0 + channel));
- bytes[1] = Some(DATA_BIT & controller);
- bytes[2] = Some(DATA_BIT & value);
+ bytes[0] = Some(STATUS_MASK & (0xD0 + channel));
+ bytes[1] = Some(DATA_MASK & controller);
+ bytes[2] = Some(DATA_MASK & value);
}
MidiMessage::TimingTick => bytes[0] = Some(0xF8),
MidiMessage::StartSong => bytes[0] = Some(0xFA),
-use std::fmt::display;
+use std::fmt::Display;
+
+use rand::random;
+
+use crate::MidiInfo;
#[derive(Clone, Copy, Eq, PartialEq, Default, Debug)]
pub enum Scale {
WholeTone,
}
-impl Into<[bool; 12]> for Scale {
- fn into(self) -> [bool; 12] {
- match self {
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct Octave {
+ center: u8,
+ range: u8,
+}
+
+impl Octave {
+ pub fn set_center(&mut self, center: u8) {
+ self.center = center;
+ }
+
+ pub fn set_range(&mut self, range: u8) {
+ self.range = range;
+ }
+}
+
+impl Default for Octave {
+ fn default() -> Self {
+ Self {
+ center: 4,
+ range: 0,
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
+pub struct Velocity {
+ min: u8,
+ max: u8,
+}
+
+impl Velocity {
+ pub fn set_min(&mut self, min: u8) {
+ self.min = min;
+ }
+
+ pub fn set_max(&mut self, max: u8) {
+ self.max = max;
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
+pub struct Root {
+ note: RootNote,
+ accidental: Accidental,
+}
+
+impl Root {
+ pub fn new(note: RootNote, accidental: Accidental) -> Self {
+ Self { note, accidental }
+ }
+
+ pub fn get_note(&self) -> RootNote {
+ self.note
+ }
+
+ pub fn get_accidental(&self) -> Accidental {
+ self.accidental
+ }
+}
+
+impl From<Root> for u8 {
+ fn from(val: Root) -> Self {
+ let n = match val.note {
+ RootNote::A => 21,
+ RootNote::B => 22,
+ RootNote::C => 23,
+ RootNote::D => 24,
+ RootNote::E => 25,
+ RootNote::F => 26,
+ RootNote::G => 27,
+ };
+
+ match val.accidental {
+ Accidental::Natural => n,
+ Accidental::Sharp => n + 1,
+ Accidental::Flat => n - 1,
+ }
+ }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
+pub enum RootNote {
+ A,
+ B,
+ #[default]
+ C,
+ D,
+ E,
+ F,
+ G,
+}
+
+impl Display for RootNote {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ let str = match self {
+ RootNote::A => "A",
+ RootNote::B => "B",
+ RootNote::C => "C",
+ RootNote::D => "D",
+ RootNote::E => "E",
+ RootNote::F => "F",
+ RootNote::G => "G",
+ };
+
+ write!(f, "{str}")
+ }
+}
+
+impl RootNote {
+ pub const ALL: [RootNote; 7] = [
+ RootNote::A,
+ RootNote::B,
+ RootNote::C,
+ RootNote::D,
+ RootNote::E,
+ RootNote::F,
+ RootNote::G,
+ ];
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
+pub enum Accidental {
+ #[default]
+ Natural,
+ Sharp,
+ Flat,
+}
+
+impl Display for Accidental {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ let str = match self {
+ Accidental::Natural => "",
+ Accidental::Sharp => "#",
+ Accidental::Flat => "b",
+ };
+ write!(f, "{str}")
+ }
+}
+
+impl Accidental {
+ pub const ALL: [Accidental; 3] = [Accidental::Natural, Accidental::Sharp, Accidental::Flat];
+}
+
+impl From<Scale> for [bool; 12] {
+ fn from(val: Scale) -> Self {
+ match val {
Scale::Chromatic => [true; 12],
Scale::Major => [
true, false, true, false, true, true, false, true, false, true, false, true,
Scale::MinorPentatonic => [
true, false, false, true, false, true, false, true, false, false, true, false,
],
- Self::WholeTone => [
+ Scale::WholeTone => [
true, false, true, false, true, false, true, false, true, false, true, false,
],
}
}
}
-impl std::fmt::Display for Scale {
+impl Scale {
+ pub const ALL: [Scale; 13] = [
+ Scale::Chromatic,
+ Scale::Major,
+ Scale::Minor,
+ Scale::Dorian,
+ Scale::Phrygian,
+ Scale::Lydian,
+ Scale::Mixolydian,
+ Scale::Locrian,
+ Scale::MinorPentatonic,
+ Scale::MajorPentatonic,
+ Scale::MelodicMinor,
+ Scale::HarmonicMinor,
+ Scale::WholeTone,
+ ];
+}
+
+impl Display for Scale {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let str = match self {
Scale::Chromatic => "chromatic",
write!(f, "{str}")
}
}
+
+pub fn generate_note(info: &MidiInfo) -> u8 {
+ let root: u8 = info.root.into();
+
+ let oct_mod = random::<u8>() % info.octave.range;
+ let octave = if random::<bool>() {
+ info.octave.center.saturating_add(oct_mod)
+ } else {
+ info.octave.center.saturating_sub(oct_mod)
+ };
+
+ let scale: [bool; 12] = info.scale.into();
+
+ let degree = loop {
+ let r = random::<usize>() % 12;
+ if scale[r] {
+ break r.try_into().unwrap();
+ } else {
+ continue;
+ }
+ };
+
+ octave
+ .saturating_mul(12)
+ .saturating_add(root)
+ .saturating_add(degree)
+}
+
+pub fn generate_velocity(v: Velocity) -> u8 {
+ let range = v.max - v.min;
+ v.min + (random::<u8>() % range)
+}