use crate::MidiController;
use bevy::prelude::*;
use log::{info, warn};
pub struct MidiParamsPlugin {
pub auto_connect: bool,
pub preferred_controller: Option<String>,
}
impl Default for MidiParamsPlugin {
fn default() -> Self {
Self {
auto_connect: true,
preferred_controller: None,
}
}
}
impl MidiParamsPlugin {
pub fn new() -> Self {
Self::default()
}
pub fn with_controller(mut self, controller_name: impl Into<String>) -> Self {
self.preferred_controller = Some(controller_name.into());
self
}
pub fn no_auto_connect(mut self) -> Self {
self.auto_connect = false;
self
}
}
impl Plugin for MidiParamsPlugin {
fn build(&self, app: &mut App) {
app.insert_resource(MidiController::new(self.preferred_controller.clone()));
for registration in inventory::iter::<MidiParamsRegistration> {
info!("Auto-registering MIDI type: {}", registration.type_name);
(registration.register_fn)(app);
}
if self.auto_connect {
app.add_systems(Startup, setup_midi_input);
}
app.add_systems(PreUpdate, update_midi_controller);
}
}
#[derive(Debug)]
pub struct MidiParamsRegistration {
pub type_name: &'static str,
pub register_fn: fn(&mut App),
}
inventory::collect!(MidiParamsRegistration);
pub trait MidiControllable {
fn update_from_midi(&mut self, cc: u8, value: f32) -> bool;
fn get_midi_mappings() -> Vec<crate::MidiMapping>;
fn get_type_name() -> &'static str;
}
#[derive(Event, Debug, Clone)]
pub struct MidiParamChanged {
pub type_name: String,
pub cc: u8,
pub value: f32,
}
pub fn register_midi_type<T: Resource + MidiControllable + Default>(app: &mut App) {
let type_name = T::get_type_name();
let world = app.world_mut();
if !world.contains_resource::<T>() {
world.init_resource::<T>();
}
if let Some(mut midi_controller) = world.get_resource_mut::<MidiController>() {
for mapping in T::get_midi_mappings() {
midi_controller.register_mapping(mapping);
}
midi_controller.register_type(type_name);
}
if !app.world().contains_resource::<Events<MidiParamChanged>>() {
app.add_event::<MidiParamChanged>();
}
app.add_systems(Update, update_params_from_midi::<T>);
}
fn setup_midi_input(mut midi_controller: ResMut<MidiController>) {
match midi_controller.connect_midi() {
Ok(()) => info!("MIDI connection established"),
Err(e) => warn!("Failed to connect MIDI: {}", e),
}
}
fn update_midi_controller(mut midi_controller: ResMut<MidiController>) {
midi_controller.update_values();
}
fn update_params_from_midi<T: Resource + MidiControllable>(
midi_controller: Res<MidiController>,
mut params: ResMut<T>,
mut events: EventWriter<MidiParamChanged>,
) {
for mapping in T::get_midi_mappings() {
if let Some(normalized_value) = midi_controller.values.get(&mapping.cc).copied() {
let scaled_value = mapping.scale_value(normalized_value);
let value_to_pass = match mapping.control_type {
crate::ControlType::Range { .. } => scaled_value,
crate::ControlType::Button => normalized_value,
};
if params.update_from_midi(mapping.cc, value_to_pass) {
events.write(MidiParamChanged {
type_name: T::get_type_name().to_string(),
cc: mapping.cc,
value: value_to_pass,
});
}
}
}
}