mod models;
mod routes;
mod websocket;
use axum::{
routing::{delete, get, patch, post, put},
Router,
};
use std::net::SocketAddr;
use std::sync::Arc;
use tokio::sync::broadcast;
use tower_http::cors::{Any, CorsLayer};
use vibelang_core::RuntimeHandle;
pub use models::*;
pub use routes::eval::{EvalJob, EvalResult};
pub use websocket::WebSocketEvent;
pub type EvalSender = std::sync::mpsc::Sender<EvalJob>;
pub struct AppState {
pub handle: RuntimeHandle,
pub ws_tx: broadcast::Sender<WebSocketEvent>,
pub eval_tx: Option<EvalSender>,
}
pub async fn start_server(handle: RuntimeHandle, port: u16, eval_tx: Option<EvalSender>) {
let (ws_tx, _) = broadcast::channel::<WebSocketEvent>(1024);
let state = Arc::new(AppState {
handle: handle.clone(),
ws_tx: ws_tx.clone(),
eval_tx,
});
let broadcast_handle = handle.clone();
let broadcast_tx = ws_tx.clone();
tokio::spawn(async move {
websocket::run_event_broadcaster(broadcast_handle, broadcast_tx).await;
});
let app = Router::new()
.route("/transport", get(routes::transport::get_transport))
.route("/transport", patch(routes::transport::update_transport))
.route("/transport/start", post(routes::transport::start_transport))
.route("/transport/stop", post(routes::transport::stop_transport))
.route("/transport/seek", post(routes::transport::seek_transport))
.route("/groups", get(routes::groups::list_groups))
.route("/groups", post(routes::groups::create_group))
.route("/groups/:path", get(routes::groups::get_group))
.route("/groups/:path", patch(routes::groups::update_group))
.route("/groups/:path", delete(routes::groups::delete_group))
.route("/groups/:path/mute", post(routes::groups::mute_group))
.route("/groups/:path/unmute", post(routes::groups::unmute_group))
.route("/groups/:path/solo", post(routes::groups::solo_group))
.route("/groups/:path/unsolo", post(routes::groups::unsolo_group))
.route(
"/groups/:path/params/:param",
put(routes::groups::set_group_param),
)
.route("/voices", get(routes::voices::list_voices))
.route("/voices", post(routes::voices::create_voice))
.route("/voices/:name", get(routes::voices::get_voice))
.route("/voices/:name", patch(routes::voices::update_voice))
.route("/voices/:name", delete(routes::voices::delete_voice))
.route("/voices/:name/trigger", post(routes::voices::trigger_voice))
.route("/voices/:name/stop", post(routes::voices::stop_voice))
.route("/voices/:name/note-on", post(routes::voices::note_on))
.route("/voices/:name/note-off", post(routes::voices::note_off))
.route(
"/voices/:name/params/:param",
put(routes::voices::set_voice_param),
)
.route("/voices/:name/mute", post(routes::voices::mute_voice))
.route("/voices/:name/unmute", post(routes::voices::unmute_voice))
.route("/patterns", get(routes::patterns::list_patterns))
.route("/patterns", post(routes::patterns::create_pattern))
.route("/patterns/:name", get(routes::patterns::get_pattern))
.route("/patterns/:name", patch(routes::patterns::update_pattern))
.route("/patterns/:name", delete(routes::patterns::delete_pattern))
.route("/patterns/:name/start", post(routes::patterns::start_pattern))
.route("/patterns/:name/stop", post(routes::patterns::stop_pattern))
.route("/melodies", get(routes::melodies::list_melodies))
.route("/melodies", post(routes::melodies::create_melody))
.route("/melodies/:name", get(routes::melodies::get_melody))
.route("/melodies/:name", patch(routes::melodies::update_melody))
.route("/melodies/:name", delete(routes::melodies::delete_melody))
.route("/melodies/:name/start", post(routes::melodies::start_melody))
.route("/melodies/:name/stop", post(routes::melodies::stop_melody))
.route("/sequences", get(routes::sequences::list_sequences))
.route("/sequences", post(routes::sequences::create_sequence))
.route("/sequences/:name", get(routes::sequences::get_sequence))
.route("/sequences/:name", patch(routes::sequences::update_sequence))
.route("/sequences/:name", delete(routes::sequences::delete_sequence))
.route(
"/sequences/:name/start",
post(routes::sequences::start_sequence),
)
.route(
"/sequences/:name/stop",
post(routes::sequences::stop_sequence),
)
.route(
"/sequences/:name/pause",
post(routes::sequences::pause_sequence),
)
.route("/effects", get(routes::effects::list_effects))
.route("/effects", post(routes::effects::create_effect))
.route("/effects/:id", get(routes::effects::get_effect))
.route("/effects/:id", patch(routes::effects::update_effect))
.route("/effects/:id", delete(routes::effects::delete_effect))
.route(
"/effects/:id/params/:param",
put(routes::effects::set_effect_param),
)
.route("/samples", get(routes::samples::list_samples))
.route("/samples", post(routes::samples::load_sample))
.route("/samples/:id", get(routes::samples::get_sample))
.route("/samples/:id", delete(routes::samples::free_sample))
.route("/synthdefs", get(routes::synthdefs::list_synthdefs))
.route("/synthdefs/:name", get(routes::synthdefs::get_synthdef))
.route("/eval", post(routes::eval::eval_code))
.route("/fades", get(routes::fades::list_fades))
.route("/fades", post(routes::fades::create_fade))
.route("/fades/:id", delete(routes::fades::cancel_fade))
.route("/midi/devices", get(routes::midi::list_devices))
.route("/midi/devices/:id", post(routes::midi::connect_device))
.route("/midi/devices/:id", delete(routes::midi::disconnect_device))
.route("/midi/routing", get(routes::midi::get_routing))
.route("/midi/routing/keyboard", get(routes::midi::list_keyboard_routes))
.route("/midi/routing/keyboard", post(routes::midi::add_keyboard_route))
.route("/midi/routing/note", get(routes::midi::list_note_routes))
.route("/midi/routing/note", post(routes::midi::add_note_route))
.route("/midi/routing/cc", get(routes::midi::list_cc_routes))
.route("/midi/routing/cc", post(routes::midi::add_cc_route))
.route("/midi/callbacks", get(routes::midi::list_callbacks))
.route("/midi/recording", get(routes::midi::get_recording_state))
.route("/midi/recording", patch(routes::midi::update_recording_settings))
.route("/midi/recording/notes", get(routes::midi::get_recorded_notes))
.route("/midi/recording/export", get(routes::midi::export_recording))
.route("/midi/monitor", post(routes::midi::set_monitor))
.route("/live", get(routes::live::get_live_state))
.route("/live/synths", get(routes::live::get_active_synths))
.route("/live/sequences", get(routes::live::get_active_sequences))
.route("/live/notes", get(routes::live::get_active_notes))
.route("/live/meters", get(routes::live::get_meters))
.route("/ws", get(websocket::ws_handler))
.with_state(state)
.layer(
CorsLayer::new()
.allow_origin(Any)
.allow_methods(Any)
.allow_headers(Any),
);
let addr = SocketAddr::from(([0, 0, 0, 0], port));
log::info!(
"HTTP API server starting on http://{}:{}",
addr.ip(),
addr.port()
);
let listener = tokio::net::TcpListener::bind(addr).await.unwrap();
axum::serve(listener, app).await.unwrap();
}