MIDI 信号を送信する

Recipe ID: hw-015

PC から外部のシンセサイザーや音源モジュールを制御するために MIDI 信号を送信します。

1. 依存関係の追加

src-tauri ディレクトリで以下のコマンドを実行します。

cargo add midir

2. 実装コード

use midir::{MidiOutput, MidiOutputConnection};
use std::sync::Mutex;
use tauri::State;

struct MidiOutState {
    conn: Mutex<Option<MidiOutputConnection>>,
}

#[tauri::command]
fn open_midi_out(state: State<MidiOutState>, port_index: usize) -> Result<String, String> {
    let midi_out = MidiOutput::new("My MIDI Output").map_err(|e| e.to_string())?;
    
    let ports = midi_out.ports();
    let port = ports.get(port_index).ok_or("Invalid port index")?;
    let port_name = midi_out.port_name(port).unwrap_or_default();

    let conn_out = midi_out.connect(port, "tauri-midi-output").map_err(|e| e.to_string())?;
    
    *state.conn.lock().unwrap() = Some(conn_out);
    
    Ok(format!("Opened {}", port_name))
}

#[tauri::command]
fn send_midi_note(state: State<MidiOutState>, note: u8, velocity: u8) -> Result<(), String> {
    let mut guard = state.conn.lock().unwrap();
    if let Some(conn) = guard.as_mut() {
        // Note On メッセージ (Channel 1)
        // [Status Byte (0x90 = Note On, Ch1), Note Number, Velocity]
        const NOTE_ON: u8 = 0x90;
        conn.send(&[NOTE_ON, note, velocity]).map_err(|e| e.to_string())?;
        Ok(())
    } else {
        Err("Port not open".to_string())
    }
}

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    tauri::Builder::default()
        .manage(MidiOutState {
            conn: Mutex::new(None),
        })
        .invoke_handler(tauri::generate_handler![open_midi_out, send_midi_note])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

3. 注意点

  • Windows では一部の MIDI ポートが「排他モード」でしか開けない場合があり、他のアプリ(DAWなど)が使用中だと接続に失敗することがあります。
  • 仮想 MIDI ポート(loopBe1など)を使えば、同じPC内の DAW に対して MIDI を送ることが可能です。