copper/src/commands/midi/play.rs
2025-09-10 07:19:57 +03:00

265 lines
10 KiB
Rust

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<Mutex<MidiState>>,
conf: Configuration,
}
impl PlayCommand {
pub fn new(midi_state: Arc<Mutex<MidiState>>, 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;
}
}