launchkey-sdk 0.1.5

A type-safe Rust SDK for Novation Launchkey MIDI controllers. Enables full control over pads, encoders, faders, displays, and DAW integration with support for RGB colors, bitmaps, and cross-platform development.
Documentation
use crate::midi::to_hex::ToHexString;
use midir::{Ignore, MidiInput, MidiInputConnection};
use std::io;
use std::io::Write;
use std::sync::mpsc;
use wmidi::{ControlFunction, MidiMessage, U7};

pub fn get_named_midi_ports(midi_in: &MidiInput) -> Vec<(usize, String)> {
    midi_in
        .ports()
        .iter()
        .enumerate()
        .filter_map(|(i, port)| midi_in.port_name(port).ok().map(|name| (i, name)))
        .collect()
}

pub fn select_port(ports: &[(usize, String)]) -> Option<usize> {
    println!("Select a MIDI input port:");
    for (i, name) in ports.iter().enumerate() {
        println!("{}: {}", i, name.1);
    }

    print!("Enter port index: ");
    io::stdout().flush().unwrap();

    let mut input = String::new();
    io::stdin().read_line(&mut input).unwrap();

    input.trim().parse::<usize>().ok().and_then(
        |idx| {
            if idx < ports.len() {
                Some(idx)
            } else {
                None
            }
        },
    )
}

pub fn connect_to_port(
    midi_in: MidiInput,
    port_index: usize,
    tx: mpsc::Sender<Vec<u8>>,
) -> Result<MidiInputConnection<()>, String> {
    let ports = midi_in.ports();
    let port = ports
        .get(port_index)
        .ok_or_else(|| "Invalid port index".to_string())?;

    // Connect to the MIDI port
    let conn_in = midi_in
        .connect(
            port,
            "midi-input",
            move |_timestamp, message, _context| {
                tx.send(message.to_vec()).unwrap();
            },
            (),
        )
        .map_err(|_| "Failed to connect to MIDI port".to_string())?;

    Ok(conn_in)
}

pub fn connect_all_midi_ports(
    ports: &[(usize, String)],
    tx: mpsc::Sender<Vec<u8>>,
) -> Vec<MidiInputConnection<()>> {
    ports
        .iter()
        .filter_map(|(i, name)| {
            let mut midi_in = MidiInput::new(&format!("{} Listener", name)).ok()?;
            midi_in.ignore(Ignore::None);
            connect_to_port(midi_in, *i, tx.clone()).ok()
        })
        .collect()
}

pub fn log_midi_message(message: MidiMessage) {
    match message {
        MidiMessage::NoteOff(channel, note, _) => println!("Note Off: {:?} {}", channel, note),
        MidiMessage::NoteOn(channel, note, velocity) => {
            println!("Note On: {:?} {} (Velocity: {:?})", channel, note, velocity)
        }
        MidiMessage::PolyphonicKeyPressure(_, note, velocity) => println!(
            "Polyphonic Key Pressure: {} (Velocity: {:?})",
            note, velocity
        ),
        MidiMessage::ControlChange(channel, control_function, value) => println!(
            "Control Change: {:?} Controller {:?} Value {:?}",
            channel, control_function, value
        ),
        MidiMessage::ProgramChange(_, program_number) => {
            println!("Program Change: Program {:?}", program_number)
        }
        MidiMessage::ChannelPressure(channel, velocity) => {
            println!("Channel Pressure: {:?} (Velocity: {:?})", channel, velocity)
        }
        MidiMessage::PitchBendChange(_, pitch_bend) => println!("Pitch Bend: {:?}", pitch_bend),
        MidiMessage::SysEx(data) => {
            println!("SysEx Message: {}", data.to_hex_string());
        }
        MidiMessage::Start => println!("Start"),
        MidiMessage::Continue => println!("Continue"),
        MidiMessage::Stop => println!("Stop"),
        _ => {}
    }
}

pub trait ControlFunctionExt {
    fn equals_u8(&self, value: u8) -> bool;
}

impl ControlFunctionExt for ControlFunction {
    fn equals_u8(&self, value: u8) -> bool {
        self.0 == U7::from_u8_lossy(value)
    }
}