use axum::{extract::State, http::StatusCode, Json};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use vibelang_core::types::MidiDeviceId;
use crate::AppState;
#[derive(Debug, Serialize)]
pub struct MidiDeviceDto {
pub id: u32,
pub name: String,
pub has_input: bool,
pub has_output: bool,
}
#[derive(Debug, Deserialize)]
pub struct OpenDeviceRequest {
pub device_id: u32,
}
#[derive(Debug, Deserialize)]
pub struct SendNoteRequest {
pub device_id: u32,
pub channel: u8,
pub note: u8,
pub velocity: Option<u8>,
}
#[derive(Debug, Deserialize)]
pub struct SendCcRequest {
pub device_id: u32,
pub channel: u8,
pub cc: u8,
pub value: u8,
}
#[derive(Debug, Deserialize)]
pub struct StartRecordingRequest {
pub device_id: u32,
pub channel: Option<u8>,
}
#[derive(Debug, Deserialize)]
pub struct StopRecordingRequest {
pub device_id: u32,
#[allow(dead_code)]
pub quantize: Option<f64>,
}
#[derive(Debug, Serialize)]
#[allow(dead_code)]
pub struct RecordedNoteDto {
pub beat: f64,
pub note: u8,
pub velocity: u8,
pub duration: Option<f64>,
}
#[derive(Debug, Serialize)]
#[allow(dead_code)]
pub struct RecordingResultDto {
pub device_id: u32,
pub note_count: usize,
pub cc_count: usize,
pub duration_beats: f64,
pub notes: Vec<RecordedNoteDto>,
}
#[derive(Debug, Deserialize)]
#[allow(dead_code)]
pub struct ClockOutputRequest {
pub device_id: u32,
pub enabled: bool,
}
#[derive(Debug, Serialize)]
#[allow(dead_code)]
pub struct ClockStatusDto {
pub device_id: u32,
pub enabled: bool,
}
#[derive(Debug, Serialize)]
pub struct ErrorResponse {
pub error: String,
}
pub async fn list_devices(State(_state): State<Arc<AppState>>) -> Json<Vec<MidiDeviceDto>> {
use midir::{MidiInput, MidiOutput};
let mut devices = Vec::new();
let mut seen_names = std::collections::HashMap::new();
if let Ok(midi_in) = MidiInput::new("vibelang-http2-list") {
for (idx, port) in midi_in.ports().iter().enumerate() {
if let Ok(name) = midi_in.port_name(port) {
devices.push(MidiDeviceDto {
id: idx as u32,
name: name.clone(),
has_input: true,
has_output: false,
});
seen_names.insert(name, idx);
}
}
}
if let Ok(midi_out) = MidiOutput::new("vibelang-http2-list") {
for port in midi_out.ports().iter() {
if let Ok(name) = midi_out.port_name(port) {
if let Some(&existing_idx) = seen_names.get(&name) {
devices[existing_idx].has_output = true;
} else {
let id = devices.len() as u32;
devices.push(MidiDeviceDto {
id,
name,
has_input: false,
has_output: true,
});
}
}
}
}
Json(devices)
}
pub async fn open_input(
State(state): State<Arc<AppState>>,
Json(req): Json<OpenDeviceRequest>,
) -> Result<StatusCode, (StatusCode, Json<ErrorResponse>)> {
use vibelang_core::message::MidiMessage;
use vibelang_core::Message;
let device_id = MidiDeviceId::new(req.device_id);
state
.handle
.send(Message::Midi(MidiMessage::OpenInput { device: device_id }))
.await
.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(ErrorResponse {
error: format!("Failed to open MIDI input: {}", e),
}),
)
})?;
Ok(StatusCode::OK)
}
pub async fn open_output(
State(state): State<Arc<AppState>>,
Json(req): Json<OpenDeviceRequest>,
) -> Result<StatusCode, (StatusCode, Json<ErrorResponse>)> {
use vibelang_core::message::MidiMessage;
use vibelang_core::Message;
let device_id = MidiDeviceId::new(req.device_id);
state
.handle
.send(Message::Midi(MidiMessage::OpenOutput { device: device_id }))
.await
.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(ErrorResponse {
error: format!("Failed to open MIDI output: {}", e),
}),
)
})?;
Ok(StatusCode::OK)
}
pub async fn close_device(
State(state): State<Arc<AppState>>,
Json(req): Json<OpenDeviceRequest>,
) -> Result<StatusCode, (StatusCode, Json<ErrorResponse>)> {
use vibelang_core::message::MidiMessage;
use vibelang_core::Message;
let device_id = MidiDeviceId::new(req.device_id);
state
.handle
.send(Message::Midi(MidiMessage::CloseDevice {
device: device_id,
}))
.await
.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(ErrorResponse {
error: format!("Failed to close MIDI device: {}", e),
}),
)
})?;
Ok(StatusCode::OK)
}
pub async fn send_note_on(
State(state): State<Arc<AppState>>,
Json(req): Json<SendNoteRequest>,
) -> Result<StatusCode, (StatusCode, Json<ErrorResponse>)> {
use vibelang_core::message::MidiMessage;
use vibelang_core::Message;
let device_id = MidiDeviceId::new(req.device_id);
state
.handle
.send(Message::Midi(MidiMessage::SendNoteOn {
device: device_id,
channel: req.channel,
note: req.note,
velocity: req.velocity.unwrap_or(100),
}))
.await
.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(ErrorResponse {
error: format!("Failed to send note on: {}", e),
}),
)
})?;
Ok(StatusCode::OK)
}
pub async fn send_note_off(
State(state): State<Arc<AppState>>,
Json(req): Json<SendNoteRequest>,
) -> Result<StatusCode, (StatusCode, Json<ErrorResponse>)> {
use vibelang_core::message::MidiMessage;
use vibelang_core::Message;
let device_id = MidiDeviceId::new(req.device_id);
state
.handle
.send(Message::Midi(MidiMessage::SendNoteOff {
device: device_id,
channel: req.channel,
note: req.note,
}))
.await
.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(ErrorResponse {
error: format!("Failed to send note off: {}", e),
}),
)
})?;
Ok(StatusCode::OK)
}
pub async fn send_cc(
State(state): State<Arc<AppState>>,
Json(req): Json<SendCcRequest>,
) -> Result<StatusCode, (StatusCode, Json<ErrorResponse>)> {
use vibelang_core::message::MidiMessage;
use vibelang_core::Message;
let device_id = MidiDeviceId::new(req.device_id);
state
.handle
.send(Message::Midi(MidiMessage::SendCC {
device: device_id,
channel: req.channel,
cc: req.cc,
value: req.value,
}))
.await
.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(ErrorResponse {
error: format!("Failed to send CC: {}", e),
}),
)
})?;
Ok(StatusCode::OK)
}
pub async fn start_recording(
State(state): State<Arc<AppState>>,
Json(req): Json<StartRecordingRequest>,
) -> Result<StatusCode, (StatusCode, Json<ErrorResponse>)> {
use vibelang_core::message::MidiMessage;
use vibelang_core::Message;
let device_id = MidiDeviceId::new(req.device_id);
let msg = if let Some(channel) = req.channel {
Message::Midi(MidiMessage::StartRecordingChannel {
device: device_id,
channel,
})
} else {
Message::Midi(MidiMessage::StartRecording { device: device_id })
};
state.handle.send(msg).await.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(ErrorResponse {
error: format!("Failed to start recording: {}", e),
}),
)
})?;
Ok(StatusCode::OK)
}
pub async fn stop_recording(
State(state): State<Arc<AppState>>,
Json(req): Json<StopRecordingRequest>,
) -> Result<StatusCode, (StatusCode, Json<ErrorResponse>)> {
use vibelang_core::message::MidiMessage;
use vibelang_core::Message;
let device_id = MidiDeviceId::new(req.device_id);
state
.handle
.send(Message::Midi(MidiMessage::StopRecording {
device: device_id,
}))
.await
.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(ErrorResponse {
error: format!("Failed to stop recording: {}", e),
}),
)
})?;
Ok(StatusCode::OK)
}
pub async fn enable_clock_output(
State(state): State<Arc<AppState>>,
Json(req): Json<OpenDeviceRequest>,
) -> Result<StatusCode, (StatusCode, Json<ErrorResponse>)> {
use vibelang_core::message::MidiMessage;
use vibelang_core::Message;
let device_id = MidiDeviceId::new(req.device_id);
state
.handle
.send(Message::Midi(MidiMessage::EnableClockOutput {
device: device_id,
}))
.await
.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(ErrorResponse {
error: format!("Failed to enable clock output: {}", e),
}),
)
})?;
Ok(StatusCode::OK)
}
pub async fn disable_clock_output(
State(state): State<Arc<AppState>>,
Json(req): Json<OpenDeviceRequest>,
) -> Result<StatusCode, (StatusCode, Json<ErrorResponse>)> {
use vibelang_core::message::MidiMessage;
use vibelang_core::Message;
let device_id = MidiDeviceId::new(req.device_id);
state
.handle
.send(Message::Midi(MidiMessage::DisableClockOutput {
device: device_id,
}))
.await
.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(ErrorResponse {
error: format!("Failed to disable clock output: {}", e),
}),
)
})?;
Ok(StatusCode::OK)
}
pub async fn send_midi_start(
State(state): State<Arc<AppState>>,
Json(req): Json<OpenDeviceRequest>,
) -> Result<StatusCode, (StatusCode, Json<ErrorResponse>)> {
use vibelang_core::message::MidiMessage;
use vibelang_core::Message;
let device_id = MidiDeviceId::new(req.device_id);
state
.handle
.send(Message::Midi(MidiMessage::SendStart { device: device_id }))
.await
.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(ErrorResponse {
error: format!("Failed to send MIDI start: {}", e),
}),
)
})?;
Ok(StatusCode::OK)
}
pub async fn send_midi_stop(
State(state): State<Arc<AppState>>,
Json(req): Json<OpenDeviceRequest>,
) -> Result<StatusCode, (StatusCode, Json<ErrorResponse>)> {
use vibelang_core::message::MidiMessage;
use vibelang_core::Message;
let device_id = MidiDeviceId::new(req.device_id);
state
.handle
.send(Message::Midi(MidiMessage::SendStop { device: device_id }))
.await
.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(ErrorResponse {
error: format!("Failed to send MIDI stop: {}", e),
}),
)
})?;
Ok(StatusCode::OK)
}
pub async fn send_midi_continue(
State(state): State<Arc<AppState>>,
Json(req): Json<OpenDeviceRequest>,
) -> Result<StatusCode, (StatusCode, Json<ErrorResponse>)> {
use vibelang_core::message::MidiMessage;
use vibelang_core::Message;
let device_id = MidiDeviceId::new(req.device_id);
state
.handle
.send(Message::Midi(MidiMessage::SendContinue {
device: device_id,
}))
.await
.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(ErrorResponse {
error: format!("Failed to send MIDI continue: {}", e),
}),
)
})?;
Ok(StatusCode::OK)
}
#[derive(Debug, Deserialize)]
pub struct AddKeyboardRouteRequest {
pub device_id: u32,
pub voice_id: u32,
pub channel: Option<u8>,
pub note_min: Option<u8>,
pub note_max: Option<u8>,
pub transpose: Option<i8>,
}
#[derive(Debug, Serialize)]
pub struct AddKeyboardRouteResponse {
pub message: String,
}
#[derive(Debug, Serialize)]
pub struct RouteInfoDto {
pub message: String,
pub keyboard_route_count: usize,
}
pub async fn list_routes(State(_state): State<Arc<AppState>>) -> Json<RouteInfoDto> {
Json(RouteInfoDto {
message: "MIDI routes are managed via Rhai scripts. Use POST /midi/route/keyboard to add a simple route.".to_string(),
keyboard_route_count: 0, })
}
pub async fn add_keyboard_route(
State(state): State<Arc<AppState>>,
Json(req): Json<AddKeyboardRouteRequest>,
) -> Result<Json<AddKeyboardRouteResponse>, (StatusCode, Json<ErrorResponse>)> {
use vibelang_core::message::MidiMessage;
use vibelang_core::types::VoiceId;
use vibelang_core::Message;
let device_id = MidiDeviceId::new(req.device_id);
let voice_id = VoiceId::new(req.voice_id);
state
.handle
.send(Message::Midi(MidiMessage::AddKeyboardRoute {
device: device_id,
voice: voice_id,
channel: req.channel,
note_min: req.note_min,
note_max: req.note_max,
transpose: req.transpose,
}))
.await
.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(ErrorResponse {
error: format!("Failed to add keyboard route: {}", e),
}),
)
})?;
Ok(Json(AddKeyboardRouteResponse {
message: format!(
"Keyboard route added: device {} -> voice {}",
req.device_id, req.voice_id
),
}))
}
pub async fn remove_keyboard_route(
State(state): State<Arc<AppState>>,
axum::extract::Path(index): axum::extract::Path<usize>,
) -> Result<StatusCode, (StatusCode, Json<ErrorResponse>)> {
use vibelang_core::message::MidiMessage;
use vibelang_core::Message;
state
.handle
.send(Message::Midi(MidiMessage::RemoveKeyboardRoute { index }))
.await
.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(ErrorResponse {
error: format!("Failed to remove keyboard route: {}", e),
}),
)
})?;
Ok(StatusCode::OK)
}
pub async fn clear_routes(
State(state): State<Arc<AppState>>,
) -> Result<StatusCode, (StatusCode, Json<ErrorResponse>)> {
use vibelang_core::message::MidiMessage;
use vibelang_core::Message;
state
.handle
.send(Message::Midi(MidiMessage::ClearRoutes))
.await
.map_err(|e| {
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(ErrorResponse {
error: format!("Failed to clear routes: {}", e),
}),
)
})?;
Ok(StatusCode::OK)
}