]> git.huck.website - cellseq.git/commitdiff
added: midi message channel
authorHuck Boles <huck@huck.website>
Tue, 27 Jun 2023 00:10:58 +0000 (19:10 -0500)
committerHuck Boles <huck@huck.website>
Tue, 27 Jun 2023 00:10:58 +0000 (19:10 -0500)
src/lib.rs
src/main.rs
src/mask.rs
src/midi.rs

index 8737153fe57e2cf0f12196deda7ac64653645afa..aacac03d397de1438197b499e5ea63a6c815400e 100644 (file)
@@ -16,7 +16,7 @@ mod midi;
 
 use map::*;
 use mask::*;
-use midi::*;
+pub use midi::*;
 
 pub type CellMap = FxHashSet<Cell>;
 
@@ -65,7 +65,7 @@ pub struct CellSeq {
 
 #[derive(Debug, Clone)]
 pub enum Message {
-    MidiMessage(MidiMessage),
+    Midi(MidiMessage),
     Map(map::Message),
     Mask(mask::Message),
     Tick(Instant),
@@ -77,19 +77,21 @@ pub enum Message {
     SpeedChanged(usize),
     ToggleLoop,
     LoopLength(usize),
+    Quit,
 }
 
 impl Application for CellSeq {
     type Message = Message;
     type Theme = Theme;
     type Executor = executor::Default;
-    type Flags = ();
+    type Flags = MidiLink;
 
-    fn new(_flags: ()) -> (Self, Command<Message>) {
+    fn new(flags: Self::Flags) -> (Self, Command<Message>) {
         (
             Self {
                 bpm: 120,
                 loop_len: 16,
+                midi: flags,
                 ..Self::default()
             },
             Command::none(),
@@ -104,9 +106,9 @@ impl Application for CellSeq {
         match message {
             Message::Map(message) => self.map.update(message),
             Message::Mask(message) => self.mask.update(message),
-            Message::MidiMessage(message) => {}
+            Message::Midi(message) => self.midi.update(message),
             Message::Tick(_) => {
-                let life = if self.step_num == self.loop_len && self.is_looping {
+                let map = if self.is_looping && self.step_num > self.loop_len {
                     self.step_num = 0;
                     self.map.reset_loop()
                 } else {
@@ -114,8 +116,15 @@ impl Application for CellSeq {
                     self.map.tick()
                 };
 
-                self.map.update(map::Message::Ticked(life.clone()));
-                self.mask.update(mask::Message::Tick(life));
+                let midi = self.mask.tick(map);
+                let mut commands = Vec::new();
+                for message in midi {
+                    commands.push(Command::perform(async move { message }, |m| {
+                        Message::Midi(m)
+                    }));
+                }
+
+                return Command::batch(commands);
             }
             Message::TogglePlayback => {
                 self.is_playing = !self.is_playing;
@@ -135,6 +144,7 @@ impl Application for CellSeq {
                 }
             }
             Message::LoopLength(len) => self.loop_len = len,
+            Message::Quit => todo!(),
         }
 
         Command::none()
@@ -220,6 +230,9 @@ fn view_controls<'a>(
         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)
index 7f42c61f50cc439c2ae8ada0ff2cd466e999c9bf..950a6322a111af45243a7c4af57bc55dd1fe22af 100644 (file)
@@ -4,23 +4,38 @@ use iced::{window, Application, Settings};
 
 use eyre::Result;
 use jack::{Client, ClientOptions, ClosureProcessHandler, Control, MidiOut, ProcessScope, RawMidi};
-use tokio::sync::mpsc::{channel, Receiver, Sender};
+use tokio::sync::mpsc::channel;
 
 pub fn main() -> Result<()> {
-    let (midi_snd, midi_rcv) = channel::<u8>(256);
+    let (midi_snd, mut midi_rcv) = channel::<Option<u8>>(256);
 
     // setting up jack client
-    let (jack_client, jack_status) = Client::new("cellseq", ClientOptions::empty())?;
+    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 writer = midi_port.writer(scope);
+        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 = MidiLink::new(midi_snd);
+
     // running the graphics window
     CellSeq::run(Settings {
         antialiasing: true,
@@ -28,9 +43,9 @@ pub fn main() -> Result<()> {
             position: window::Position::Centered,
             ..window::Settings::default()
         },
+        flags: midi,
         ..Settings::default()
-    })
-    .map_err(|_| Ok(()));
+    })?;
 
     jack.deactivate()?;
 
index cc3d4ab3129a1b226c526614a99045d24e29759f..c89920cdf7db52aac5d4c409cdc78960c9185af3 100644 (file)
@@ -16,11 +16,12 @@ use rustc_hash::FxHashMap;
 pub enum Message {
     Check(Cell),
     Uncheck(Cell),
+    SetNote((Cell, Note)),
     Tick(CellMap),
 }
 
 #[derive(Default, Debug, Clone, Copy, Eq, PartialEq, Hash)]
-pub enum State {
+enum OnOff {
     #[default]
     Off,
     On,
@@ -28,22 +29,23 @@ pub enum State {
 
 #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
 pub struct Note {
-    value: usize,
-    state: State,
+    value: u8,
+    velocity: u8,
+    state: OnOff,
 }
 
 impl Note {
     pub fn is_on(&self) -> bool {
         match self.state {
-            State::On => true,
-            State::Off => false,
+            OnOff::On => true,
+            OnOff::Off => false,
         }
     }
 
     pub fn switch(&mut self) {
         self.state = match self.state {
-            State::On => State::Off,
-            State::Off => State::On,
+            OnOff::On => OnOff::Off,
+            OnOff::Off => OnOff::On,
         }
     }
 }
@@ -75,6 +77,9 @@ impl Mask {
 
                 self.mask_cache.clear();
             }
+            Message::SetNote((cell, note)) => {
+                self.cells.insert(cell, note);
+            }
         }
     }
 
@@ -92,6 +97,27 @@ impl Mask {
             .height(Length::Fixed(Cell::SIZE as f32 * 24.0))
             .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);
+            }
+        }
+
+        for (note, vel) in note_map {
+            messages.push(MidiMessage::On {
+                note,
+                velocity: vel,
+                channel: 0,
+            })
+        }
+
+        messages
+    }
 }
 
 impl Program<Message> for Mask {
index 3ee5fd951d2ad732c9eb5ee48b3e03cdefb01970..4caf545f4de4e73c94a1fe143ed04448fb6af804 100644 (file)
@@ -6,11 +6,21 @@ use tokio::sync::mpsc::Sender;
 #[derive(Clone, Debug)]
 pub struct MidiLink {
     buffer: VecDeque<u8>,
-    channel: Sender<u8>,
+    channel: Sender<Option<u8>>,
 }
 
-impl MidiLink {
-    pub fn new(channel: Sender<u8>) -> Self {
+impl Default for MidiLink {
+    fn default() -> Self {
+        let (send, _) = tokio::sync::mpsc::channel(128);
+        Self {
+            buffer: VecDeque::default(),
+            channel: send,
+        }
+    }
+}
+
+impl<'a> MidiLink {
+    pub fn new(channel: Sender<Option<u8>>) -> Self {
         Self {
             buffer: VecDeque::default(),
             channel,
@@ -27,9 +37,10 @@ impl MidiLink {
 
     pub async fn tick(&mut self) {
         for byte in self.buffer.iter() {
-            self.channel.send(*byte).await.unwrap();
+            self.channel.send(Some(*byte)).await.unwrap();
         }
 
+        self.channel.send(None).await.unwrap();
         self.buffer.clear();
     }
 }