parent
a96285501d
commit
1a81192cc3
3 changed files with 103 additions and 61 deletions
14
src/midi.rs
14
src/midi.rs
|
@ -1,7 +1,19 @@
|
|||
pub mod daemon;
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Clone)]
|
||||
pub enum Message {
|
||||
pub struct Message {
|
||||
pub raw: Vec<u8>,
|
||||
pub parsed: ParsedMessage,
|
||||
}
|
||||
|
||||
impl Message {
|
||||
pub fn new(raw: Vec<u8>, parsed: ParsedMessage) -> Self {
|
||||
Message { raw, parsed }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Clone)]
|
||||
pub enum ParsedMessage {
|
||||
Voice(VoiceMessage),
|
||||
System(SystemCommon),
|
||||
Realtime(SystemRealtime),
|
||||
|
|
|
@ -3,9 +3,10 @@ use nom::{
|
|||
IResult,
|
||||
};
|
||||
|
||||
use crate::midi::{Message, SystemCommon, SystemRealtime, VoiceCategory, VoiceMessage};
|
||||
use crate::midi::{Message, ParsedMessage, SystemCommon, SystemRealtime, VoiceCategory, VoiceMessage};
|
||||
|
||||
pub fn parse_message(bytes: &[u8]) -> IResult<&[u8], Message> {
|
||||
let raw = Vec::from(bytes);
|
||||
let (bytes, status_byte) = take(1usize)(bytes)?;
|
||||
let status_byte = status_byte[0];
|
||||
|
||||
|
@ -14,13 +15,13 @@ pub fn parse_message(bytes: &[u8]) -> IResult<&[u8], Message> {
|
|||
|
||||
if status_byte < 0xF0 {
|
||||
let (bytes, vm) = parse_voice_message(status_byte, bytes)?;
|
||||
Ok((bytes, Message::Voice(vm)))
|
||||
Ok((bytes, Message::new(raw, ParsedMessage::Voice(vm))))
|
||||
} else if status_byte < 0xf8 {
|
||||
let (bytes, sc) = parse_system_common(status_byte, bytes)?;
|
||||
Ok((bytes, Message::System(sc)))
|
||||
Ok((bytes, Message::new(raw, ParsedMessage::System(sc))))
|
||||
} else {
|
||||
let sr = parse_system_realtime(status_byte);
|
||||
Ok((bytes, Message::Realtime(sr)))
|
||||
Ok((bytes, Message::new(raw, ParsedMessage::Realtime(sr))))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
141
src/ui.rs
141
src/ui.rs
|
@ -11,7 +11,7 @@ use crossbeam::{channel::Receiver, select};
|
|||
use egui::{mutex::Mutex, Color32, Frame, Grid, RichText, Rounding, ScrollArea, SelectableLabel};
|
||||
use midir::{MidiInput, MidiInputPort};
|
||||
|
||||
use crate::midi::{daemon::CTM, Message, VoiceCategory, VoiceMessage};
|
||||
use crate::midi::{daemon::CTM, Message, ParsedMessage, VoiceCategory, VoiceMessage};
|
||||
|
||||
/// State used to display the UI. It's intended to be shared between the
|
||||
/// renderer and the daemon which updates the state.
|
||||
|
@ -22,6 +22,7 @@ pub struct DisplayState {
|
|||
pub selected_ports: HashMap<String, bool>,
|
||||
pub max_messages: usize,
|
||||
pub only_note_on_off: Arc<AtomicBool>,
|
||||
pub show_raw: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl DisplayState {
|
||||
|
@ -31,7 +32,8 @@ impl DisplayState {
|
|||
midi_messages: Arc::new(Mutex::new(VecDeque::new())),
|
||||
selected_ports: HashMap::new(),
|
||||
max_messages: 10_000_usize,
|
||||
only_note_on_off: Arc::new(AtomicBool::new(false)),
|
||||
only_note_on_off: Arc::new(AtomicBool::new(true)),
|
||||
show_raw: Arc::new(AtomicBool::new(true)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -159,7 +161,7 @@ impl eframe::App for MidiKeysApp {
|
|||
|
||||
let conn_id = port.id();
|
||||
|
||||
let selected = self.state.selected_ports.get(&conn_id).unwrap_or(&false);
|
||||
let selected = self.state.selected_ports.get(&conn_id).unwrap_or(&true);
|
||||
|
||||
if ui
|
||||
.add(SelectableLabel::new(*selected, &port_name))
|
||||
|
@ -171,73 +173,80 @@ impl eframe::App for MidiKeysApp {
|
|||
});
|
||||
|
||||
let mut only_note_on_off = self.state.only_note_on_off.load(Ordering::Relaxed);
|
||||
let mut show_raw = self.state.show_raw.load(Ordering::Relaxed);
|
||||
|
||||
egui::TopBottomPanel::top("filter_panel").show(ctx, |ui| {
|
||||
ui.checkbox(&mut only_note_on_off, "Only note on/off");
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
ui.checkbox(&mut only_note_on_off, "Only note on/off");
|
||||
ui.checkbox(&mut show_raw, "Display raw bytes");
|
||||
});
|
||||
});
|
||||
|
||||
self.state
|
||||
.only_note_on_off
|
||||
.store(only_note_on_off, Ordering::Relaxed);
|
||||
self.state.show_raw.store(show_raw, Ordering::Relaxed);
|
||||
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
ScrollArea::vertical().show(ui, |ui| {
|
||||
for (idx, (conn, _ts, msg)) in messages.iter().rev().enumerate() {
|
||||
if only_note_on_off
|
||||
&& !matches!(
|
||||
msg,
|
||||
Message::Voice(VoiceMessage {
|
||||
category: VoiceCategory::NoteOn { .. }
|
||||
| VoiceCategory::NoteOff { .. },
|
||||
..
|
||||
})
|
||||
)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if !self
|
||||
.state
|
||||
.selected_ports
|
||||
.get(conn.as_str())
|
||||
.unwrap_or(&false)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let mut frame = Frame::default()
|
||||
.inner_margin(4.0)
|
||||
.stroke((1.0, Color32::BLACK))
|
||||
.rounding(Rounding::same(2.0))
|
||||
.begin(ui);
|
||||
ScrollArea::vertical()
|
||||
.max_width(f32::INFINITY)
|
||||
.show(ui, |ui| {
|
||||
ui.vertical_centered_justified(|ui| {
|
||||
for (idx, (conn, _ts, msg)) in messages.iter().rev().enumerate() {
|
||||
if only_note_on_off
|
||||
&& !matches!(
|
||||
&msg.parsed,
|
||||
ParsedMessage::Voice(VoiceMessage {
|
||||
category: VoiceCategory::NoteOn { .. }
|
||||
| VoiceCategory::NoteOff { .. },
|
||||
..
|
||||
})
|
||||
)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if !self
|
||||
.state
|
||||
.selected_ports
|
||||
.get(conn.as_str())
|
||||
.unwrap_or(&true)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let port = match ports.iter().find(|p| &p.id() == conn) {
|
||||
Some(p) => p,
|
||||
None => continue,
|
||||
};
|
||||
ui.set_width(ui.available_width());
|
||||
let mut frame = Frame::default()
|
||||
.inner_margin(4.0)
|
||||
.outer_margin(4.0)
|
||||
.stroke((1.0, Color32::BLACK))
|
||||
.rounding(Rounding::same(2.0))
|
||||
.begin(ui);
|
||||
|
||||
let port = match ports.iter().find(|p| &p.id() == conn) {
|
||||
Some(p) => p,
|
||||
None => continue,
|
||||
};
|
||||
let port_name = self
|
||||
.midi_in
|
||||
.port_name(port)
|
||||
.unwrap_or("(disconnected_device)".into());
|
||||
let port_name = self
|
||||
.midi_in
|
||||
.port_name(port)
|
||||
.unwrap_or("(disconnected_device)".into());
|
||||
|
||||
frame.content_ui.label(RichText::new(port_name).strong());
|
||||
frame.content_ui.label(RichText::new(port_name).strong());
|
||||
|
||||
display_midi_message(idx, msg, &mut frame.content_ui);
|
||||
display_midi_message(idx, msg, &mut frame.content_ui, show_raw);
|
||||
frame.end(ui);
|
||||
}
|
||||
});
|
||||
|
||||
frame.end(ui);
|
||||
}
|
||||
});
|
||||
|
||||
ctx.request_repaint();
|
||||
ctx.request_repaint();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn display_midi_message(idx: usize, msg: &Message, ui: &mut egui::Ui) {
|
||||
match msg {
|
||||
Message::Voice(vm) => {
|
||||
fn display_midi_message(idx: usize, msg: &Message, ui: &mut egui::Ui, raw: bool) {
|
||||
match &msg.parsed {
|
||||
ParsedMessage::Voice(vm) => {
|
||||
Grid::new(format!("message_grid_{idx}")).show(ui, |ui| {
|
||||
let voice_label = format!("Voice (channel={})", vm.channel);
|
||||
ui.label(RichText::new(voice_label).italics());
|
||||
|
||||
let (name, fields) = match vm.category {
|
||||
VoiceCategory::NoteOff { note, velocity } => (
|
||||
"NoteOff",
|
||||
|
@ -280,16 +289,36 @@ fn display_midi_message(idx: usize, msg: &Message, ui: &mut egui::Ui) {
|
|||
VoiceCategory::Unknown => ("Unknown", vec![]),
|
||||
};
|
||||
|
||||
ui.label(name);
|
||||
ui.end_row();
|
||||
let voice_label = format!("Voice:{}", name);
|
||||
|
||||
ui.label(RichText::new(voice_label).italics());
|
||||
ui.label(format!("channel = {}", vm.channel));
|
||||
|
||||
for (name, value) in fields {
|
||||
ui.label(format!("{name} = {value}"));
|
||||
}
|
||||
});
|
||||
}
|
||||
Message::System(_system_common) => {}
|
||||
Message::Realtime(_system_realtime) => {}
|
||||
ParsedMessage::System(_system_common) => {}
|
||||
ParsedMessage::Realtime(_system_realtime) => {}
|
||||
}
|
||||
|
||||
if raw {
|
||||
ui.horizontal_top(|ui| {
|
||||
ui.label("Bytes:");
|
||||
Frame::none().show(ui, |ui| {
|
||||
ui.spacing_mut().item_spacing = (0.0, 0.0).into();
|
||||
for byte in &msg.raw {
|
||||
Frame::none()
|
||||
.inner_margin(2.0)
|
||||
.stroke((0.5, Color32::GRAY))
|
||||
.outer_margin(0.0)
|
||||
.show(ui, |ui| {
|
||||
ui.label(format!("{:02X}", byte));
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue