]> git.huck.website - cellseq.git/commitdiff
refactored: midi implementation
authorHuck Boles <huck@huck.website>
Sat, 1 Jul 2023 19:32:57 +0000 (14:32 -0500)
committerHuck Boles <huck@huck.website>
Sat, 1 Jul 2023 19:32:57 +0000 (14:32 -0500)
src/display.rs
src/lib.rs
src/main.rs
src/mask.rs
src/midi.rs
src/music.rs

index 172cca2ff2a0ca34703e719e68aa69be9f540a18..c370398ff60897af9b060e775d24addbdcd38cb8 100644 (file)
@@ -4,7 +4,10 @@ use iced::{
     Alignment, Element, Length,
 };
 
-use crate::{music::Scale, Message};
+use crate::{
+    music::{Accidental, Root, RootNote, Scale},
+    Message,
+};
 
 pub struct ControlMessage {
     probability: f32,
@@ -19,6 +22,8 @@ pub struct ControlMessage {
     octave: u8,
     range: u8,
     scale: Scale,
+    root: Root,
+    voices: u8,
 }
 
 pub fn top_controls<'a>(is_playing: bool) -> Element<'a, Message> {
@@ -29,29 +34,27 @@ 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> {
@@ -165,7 +168,32 @@ fn channel_selector<'a>(channel: u8) -> 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![
@@ -218,9 +246,32 @@ fn speed_controls<'a>(bpm: usize) -> Row<'a, Message> {
     .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)
 }
index 433feef735d1732da4ca49d85f3b297f44afc1fe..bb93f4998a009d4be1481dd91c8344d93c5ed78e 100644 (file)
@@ -61,26 +61,22 @@ pub struct CellSeq {
     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,
@@ -98,6 +94,8 @@ pub enum Message {
     Scale(Scale),
     NewOctave(u8),
     OctaveRange(u8),
+    NewNote(Root),
+    Voices(u8),
     Quit,
 }
 
@@ -131,9 +129,15 @@ impl Application for CellSeq {
 
     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;
@@ -143,19 +147,18 @@ impl Application for CellSeq {
                     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);
             }
@@ -178,14 +181,16 @@ impl Application for CellSeq {
             }
             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()
@@ -203,8 +208,8 @@ impl Application for CellSeq {
         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)
index ff826aa8e8596a837240aeec3b80eec0e6878bd6..3db3f9f75c839f0c8782f94fdc897ed5a3af980f 100644 (file)
@@ -6,7 +6,7 @@ use eyre::Result;
 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);
 
index c89920cdf7db52aac5d4c409cdc78960c9185af3..e6b256958e55bad7f954e2eb3cac6733347d1ec8 100644 (file)
@@ -8,51 +8,19 @@ use iced::{
     {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,
 }
 
@@ -60,34 +28,13 @@ impl Mask {
     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
         }
     }
 
@@ -98,25 +45,14 @@ impl Mask {
             .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
     }
 }
 
@@ -138,15 +74,13 @@ impl Program<Message> for Mask {
 
                 (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,
+                        );
                     })
             });
         })]
@@ -164,7 +98,7 @@ impl Program<Message> for Mask {
                 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))
index 4caf545f4de4e73c94a1fe143ed04448fb6af804..20cfaa46a915e817cccd7a15243d410e8bfd9b10 100644 (file)
@@ -1,47 +1,80 @@
-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
     }
 }
 
@@ -105,8 +138,8 @@ impl Display for MidiMessage {
     }
 }
 
-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> {
@@ -122,9 +155,9 @@ impl MidiMessage {
                 } 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,
@@ -136,9 +169,9 @@ impl MidiMessage {
                 } 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,
@@ -150,9 +183,9 @@ impl MidiMessage {
                 } 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),
index 2ef742a566f8eeb2cd35005d01303eb976ee7857..4ab6df14d2c596ed77013dd3a6888aa199adc811 100644 (file)
@@ -1,4 +1,8 @@
-use std::fmt::display;
+use std::fmt::Display;
+
+use rand::random;
+
+use crate::MidiInfo;
 
 #[derive(Clone, Copy, Eq, PartialEq, Default, Debug)]
 pub enum Scale {
@@ -18,9 +22,153 @@ 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,
@@ -55,14 +203,32 @@ impl Into<[bool; 12]> for Scale {
             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",
@@ -83,3 +249,35 @@ impl std::fmt::Display for Scale {
         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)
+}