use bevy::prelude::*;
mod settings;
use midix::{UMicros, events::LiveEvent, prelude::ChannelVoiceMessage};
pub use settings::*;
mod error;
pub use error::*;
mod state;
mod plugin;
pub use plugin::*;
use midir::MidiInputPort;
use trotcast::prelude::*;
use crate::{
data::MidiData,
input::state::{MidiInputConnectionHandler, MidiInputState},
};
pub trait FromMidiInputData: Send + Sync + Clone + 'static {
type Settings: Send + Sync;
fn from_midi_data(timestamp: UMicros, event: LiveEvent<'static>) -> Self;
#[cfg(feature = "synth")]
fn to_channel_voice_message(&self) -> Option<ChannelVoiceMessage>;
#[allow(unused_variables)]
fn configure_plugin(settings: &Self::Settings, app: &mut App) {}
}
#[derive(Resource)]
pub struct MidiInput<D: FromMidiInputData = MidiData> {
channel: Channel<D>,
state: Option<MidiInputState>,
ports: Vec<MidiInputPort>,
client_name: String,
port_name: String,
ignore: Ignore,
}
impl<D: FromMidiInputData> MidiInput<D> {
pub fn new(settings: MidiInputSettings) -> Self {
let mut listener = match midir::MidiInput::new(&settings.client_name) {
Ok(input) => input,
Err(e) => {
panic!("Error initializing midi input! {e:?}");
}
};
listener.ignore(settings.ignore);
let ports = listener.ports();
Self {
channel: Channel::new(settings.channel_size),
state: Some(MidiInputState::Listening(listener)),
client_name: settings.client_name,
port_name: settings.port_name,
ignore: settings.ignore,
ports,
}
}
pub fn channel(&self) -> &Channel<D> {
&self.channel
}
pub fn ports(&self) -> &[MidiInputPort] {
&self.ports
}
pub fn connect_to_index(&mut self, index: usize) -> Result<(), MidiInputError> {
if self
.state
.as_ref()
.is_none_or(|s| matches!(s, MidiInputState::Active(_)))
{
return Err(MidiInputError::invalid(
"Cannot connect: not currently active!",
));
}
let Some(port) = self.ports.get(index) else {
return Err(MidiInputError::port_not_found(
"Port was not found at {index}!",
));
};
let MidiInputState::Listening(listener) = self.state.take().unwrap() else {
unreachable!()
};
let handler =
MidiInputConnectionHandler::new(listener, port, &self.port_name, self.channel.clone())
.unwrap();
self.state = Some(MidiInputState::Active(handler));
Ok(())
}
pub fn reset(&mut self) {
let mut listener = match midir::MidiInput::new(&self.client_name) {
Ok(input) => input,
Err(e) => {
error!("Failed to reset listening state! {e:?}");
return;
}
};
listener.ignore(self.ignore);
self.state = Some(MidiInputState::Listening(listener));
}
pub fn connect_to_port(&mut self, port: &MidiInputPort) -> Result<(), MidiInputError> {
if self
.state
.as_ref()
.is_none_or(|s| matches!(s, MidiInputState::Active(_)))
{
return Err(MidiInputError::invalid(
"Cannot connect: not currently active!",
));
}
let MidiInputState::Listening(listener) = self.state.take().unwrap() else {
unreachable!()
};
self.state = Some(MidiInputState::Active(
MidiInputConnectionHandler::new(listener, port, &self.port_name, self.channel.clone())
.unwrap(),
));
Ok(())
}
pub fn connect_to_id(&mut self, id: String) -> Result<(), MidiInputError> {
if self
.state
.as_ref()
.is_none_or(|s| matches!(s, MidiInputState::Active(_)))
{
return Err(MidiInputError::invalid(
"Cannot connect: not currently active!",
));
}
let MidiInputState::Listening(listener) = self.state.take().unwrap() else {
unreachable!()
};
let Some(port) = listener.find_port_by_id(id.clone()) else {
return Err(MidiInputError::port_not_found(id));
};
self.state = Some(MidiInputState::Active(
MidiInputConnectionHandler::new(listener, &port, &self.port_name, self.channel.clone())
.unwrap(),
));
Ok(())
}
pub fn is_active(&self) -> bool {
self.state
.as_ref()
.is_some_and(|s| matches!(s, MidiInputState::Active(_)))
}
pub fn is_listening(&self) -> bool {
self.state
.as_ref()
.is_some_and(|s| matches!(s, MidiInputState::Listening(_)))
}
pub fn refresh_ports(&mut self) {
let Some(MidiInputState::Listening(listener)) = &self.state else {
return;
};
self.ports = listener.ports();
}
pub fn disconnect(&mut self) {
if self
.state
.as_ref()
.is_none_or(|s| matches!(s, MidiInputState::Listening(_)))
{
return;
}
let MidiInputState::Active(conn) = self.state.take().unwrap() else {
unreachable!()
};
let listener = conn.close();
self.state = Some(MidiInputState::Listening(listener));
}
}