265 lines
10 KiB
Rust
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;
|
|
}
|
|
}
|