use map::*;
use mask::*;
-use midi::*;
+pub use midi::*;
pub type CellMap = FxHashSet<Cell>;
#[derive(Debug, Clone)]
pub enum Message {
- MidiMessage(MidiMessage),
+ Midi(MidiMessage),
Map(map::Message),
Mask(mask::Message),
Tick(Instant),
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(),
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 {
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;
}
}
Message::LoopLength(len) => self.loop_len = len,
+ Message::Quit => todo!(),
}
Command::none()
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)
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,
position: window::Position::Centered,
..window::Settings::default()
},
+ flags: midi,
..Settings::default()
- })
- .map_err(|_| Ok(()));
+ })?;
jack.deactivate()?;
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,
#[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,
}
}
}
self.mask_cache.clear();
}
+ Message::SetNote((cell, note)) => {
+ self.cells.insert(cell, note);
+ }
}
}
.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 {
#[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,
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();
}
}