moont-live 1.0.0

Real-time CM-32L MIDI sink using ALSA
// Copyright (C) 2021-2026 Geoff Hill <geoff@geoffhill.org>
//
// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 2.1 of the License, or (at
// your option) any later version. Read COPYING.LESSER.txt for details.

use alsa::seq::{ClientIter, EventType, PortCap, PortIter, PortType, Seq};
use std::sync::mpsc::SyncSender;

pub enum MidiEvent {
    Msg(u32),
    Sysex(Vec<u8>),
}

pub fn list_ports() {
    let seq = Seq::open(None, None, false).expect("failed to open sequencer");
    for ci in ClientIter::new(&seq) {
        let cid = ci.get_client();
        let cname = ci.get_name().unwrap_or_default();
        for pi in PortIter::new(&seq, cid) {
            let pid = pi.get_port();
            let pname = pi.get_name().unwrap_or_default();
            let caps = pi.get_capability();
            if caps.contains(PortCap::READ) || caps.contains(PortCap::SUBS_READ)
            {
                println!("{cid}:{pid}\t{cname} - {pname}");
            }
        }
    }
}

pub fn open_port(tx: SyncSender<MidiEvent>) -> std::thread::JoinHandle<()> {
    let seq = Seq::open(None, None, false).expect("failed to open sequencer");
    seq.set_client_name(&std::ffi::CString::new("moont").unwrap())
        .expect("failed to set client name");

    let caps = PortCap::WRITE | PortCap::SUBS_WRITE;
    let ptype = PortType::MIDI_GENERIC | PortType::APPLICATION;
    let port = seq
        .create_simple_port(
            &std::ffi::CString::new("moont").unwrap(),
            caps,
            ptype,
        )
        .expect("failed to create ALSA sequencer port");

    let cid = seq.client_id().expect("failed to get client id");
    eprintln!("ALSA MIDI port: {cid}:{port}");
    eprintln!("Connect with: aconnect <source> {cid}:{port}");

    std::thread::spawn(move || {
        let mut input = seq.input();
        loop {
            let ev = match input.event_input() {
                Ok(ev) => ev,
                Err(_) => continue,
            };
            let evt = match ev.get_type() {
                EventType::Noteon => {
                    let d: alsa::seq::EvNote = ev.get_data().unwrap();
                    let status = 0x90 | (d.channel as u32);
                    let msg = status
                        | ((d.note as u32) << 8)
                        | ((d.velocity as u32) << 16);
                    Some(MidiEvent::Msg(msg))
                }
                EventType::Noteoff => {
                    let d: alsa::seq::EvNote = ev.get_data().unwrap();
                    let status = 0x80 | (d.channel as u32);
                    let msg = status | ((d.note as u32) << 8);
                    Some(MidiEvent::Msg(msg))
                }
                EventType::Controller => {
                    let d: alsa::seq::EvCtrl = ev.get_data().unwrap();
                    let status = 0xB0 | (d.channel as u32);
                    let msg = status
                        | ((d.param as u32) << 8)
                        | ((d.value as u32) << 16);
                    Some(MidiEvent::Msg(msg))
                }
                EventType::Pgmchange => {
                    let d: alsa::seq::EvCtrl = ev.get_data().unwrap();
                    let status = 0xC0 | (d.channel as u32);
                    let msg = status | ((d.value as u32) << 8);
                    Some(MidiEvent::Msg(msg))
                }
                EventType::Pitchbend => {
                    let d: alsa::seq::EvCtrl = ev.get_data().unwrap();
                    let bend = (d.value + 8192) as u16;
                    let status = 0xE0 | (d.channel as u32);
                    let msg = status
                        | (((bend & 0x7F) as u32) << 8)
                        | (((bend >> 7) as u32) << 16);
                    Some(MidiEvent::Msg(msg))
                }
                EventType::Sysex => {
                    if let Some(data) = ev.get_ext() {
                        Some(MidiEvent::Sysex(data.to_vec()))
                    } else {
                        None
                    }
                }
                _ => None,
            };
            if let Some(e) = evt {
                if tx.send(e).is_err() {
                    break;
                }
            }
        }
    })
}