use crate::Configuration; use crate::client::Client; use crate::client::ClientEvent; use crate::client::Note; use crate::client::Player; use crate::commands::Command; use crate::commands::MidiState; use crate::commands::argument::{ArgumentSpec, ArgumentType, ParsedArguments}; use crate::commands::number_to_midi; use crate::commands::play_midi_file; use crate::commands::simple_hash; use crate::midi_helper::MidiEvent; use async_trait::async_trait; use std::sync::Arc; use tokio::sync::Mutex; pub struct PlayCommand { midi_state: Arc>, conf: Configuration, } impl PlayCommand { pub fn new(midi_state: Arc>, conf: Configuration) -> Self { Self { midi_state, conf } } } #[async_trait] impl Command for PlayCommand { fn name(&self) -> &'static str { "play" } fn aliases(&self) -> &[&'static str] { &["p"] } fn category(&self) -> &'static str { "midi" } fn description(&self) -> &'static str { "plays a midi file from a local path, URL, or files.sad.ovh" } fn argument_spec(&self) -> &'static [ArgumentSpec] { &[ArgumentSpec { name: "file", arg_type: ArgumentType::String, required: true, default: None, }] } async fn constructed(&mut self, client: Client) { let ntm = number_to_midi(); let midi_rx = { let midi_state = self.midi_state.lock().await; midi_state.midi_rx.clone() }; tokio::spawn(async move { while let Ok(event) = midi_rx.recv_async().await { match event { MidiEvent::NoteOn { key, velocity } => { client .add_note_buffer(Note { n: ntm.get(&key).unwrap_or(&"").to_string(), v: Some(velocity as f64 / 127.0), d: None, s: None, }) .await; } MidiEvent::NoteOff { key } => { client .add_note_buffer(Note { n: ntm.get(&key).unwrap_or(&"").to_string(), v: None, d: None, s: Some(1), }) .await; } MidiEvent::Info { num_tracks, time_div: _, events_count, note_count, total_ticks: _, minutes, seconds, millis, parse_time, } => { client .message(format!( "Tracks: `{}` Events: `{}` Total Duration: `{:02}:{:02}.{:03}` Note Count: `{}` Parse time: `{:.2?}`", num_tracks, events_count, minutes, seconds, millis, note_count, parse_time )) .await; } } } }); } async fn event(&mut self, _: Client, _: ClientEvent) {} async fn execute(&mut self, client: Client, _: Player, args: ParsedArguments) { let file_arg = match args.get("file") { Some(crate::commands::argument::ParsedArgument::String(s)) => s, _ => "", }; let joined_args = file_arg.to_string(); let mut filename_to_play: String = "".to_string(); let mut filename_beautiful: String = "".to_string(); if joined_args.starts_with("https://") { let hashed = simple_hash(joined_args.as_str()); filename_to_play = format!("midis/{}.mid", hashed); filename_beautiful = joined_args.clone(); let file_exists = tokio::fs::try_exists(&filename_to_play) .await .unwrap_or(false); if !file_exists { match reqwest::get(&joined_args).await { Ok(resp) => { if resp.status().is_success() { match resp.bytes().await { Ok(bytes) => { match tokio::fs::write(&filename_to_play, &bytes).await { Ok(_) => { client .message(format!( "Downloaded midi from {}, into: {}", joined_args, filename_to_play )) .await; } Err(e) => { client .message(format!("Failed to write file: {}", e)) .await; return; } } } Err(e) => { client .message(format!("Failed to read response bytes: {}", e)) .await; return; } } } else { client .message(format!("Failed to download file: HTTP {}", resp.status())) .await; return; } } Err(e) => { client .message(format!("Failed to download file: {}", e)) .await; return; } } } } else if joined_args.starts_with("files:") { if let Some(caps) = joined_args.strip_prefix("files:") { let first_capture = caps.trim(); if first_capture.ends_with('/') { client .message("Use the 'playlist' command to play a directory as a playlist.") .await; return; } else { let hashed = simple_hash(first_capture); filename_to_play = format!("midis/{}.mid", hashed); filename_beautiful = first_capture.to_string(); let url = format!("{}/{}", self.conf.commands.copyparty, first_capture); let file_exists = tokio::fs::try_exists(&filename_to_play) .await .unwrap_or(false); if !file_exists { match reqwest::get(&url).await { Ok(resp) => { if resp.status().is_success() { match resp.bytes().await { Ok(bytes) => { match tokio::fs::write(&filename_to_play, &bytes).await { Ok(_) => { client .message(format!( "Downloaded {} from files.sad.ovh.", filename_beautiful )) .await; } Err(e) => { client .message(format!( "Failed to write file: {}", e )) .await; return; } } } Err(e) => { client .message(format!( "Failed to read response bytes: {}", e )) .await; return; } } } else { client .message(format!( "Failed to download file: HTTP {}", resp.status() )) .await; return; } } Err(e) => { client .message(format!("Failed to download file: {}", e)) .await; return; } } } } } } else { filename_to_play = joined_args.clone(); filename_beautiful = joined_args.clone(); } play_midi_file( filename_to_play, filename_beautiful.clone(), false, self.midi_state.clone(), client.clone(), ) .await; client .message(format!("Started playing {}.", filename_beautiful)) .await; } }