Add some built-in and code documentation

This commit is contained in:
NGnius (Graham) 2022-01-03 21:15:28 -05:00
parent 5ef1b4a2b8
commit e6e52ddb58
20 changed files with 329 additions and 198 deletions

6
Cargo.lock generated
View file

@ -634,7 +634,7 @@ dependencies = [
[[package]] [[package]]
name = "mps" name = "mps"
version = "0.1.0" version = "0.2.0"
dependencies = [ dependencies = [
"clap", "clap",
"mps-interpreter", "mps-interpreter",
@ -643,7 +643,7 @@ dependencies = [
[[package]] [[package]]
name = "mps-interpreter" name = "mps-interpreter"
version = "0.1.0" version = "0.2.0"
dependencies = [ dependencies = [
"dirs", "dirs",
"regex", "regex",
@ -654,7 +654,7 @@ dependencies = [
[[package]] [[package]]
name = "mps-player" name = "mps-player"
version = "0.1.0" version = "0.2.0"
dependencies = [ dependencies = [
"m3u8-rs", "m3u8-rs",
"mpris-player", "mpris-player",

View file

@ -1,6 +1,6 @@
[package] [package]
name = "mps" name = "mps"
version = "0.1.0" version = "0.2.0"
edition = "2021" edition = "2021"
authors = ["NGnius (Graham) <ngniusness@gmail.com>"] authors = ["NGnius (Graham) <ngniusness@gmail.com>"]
description = "Music Playlist Scripting language (MPS)" description = "Music Playlist Scripting language (MPS)"
@ -15,7 +15,7 @@ members = [
[dependencies] [dependencies]
# local # local
mps-interpreter = { version = "0.1.0", path = "./mps-interpreter" } mps-interpreter = { version = "0.2.0", path = "./mps-interpreter" }
mps-player = { version = "0.1.0", path = "./mps-player" } mps-player = { version = "0.2.0", path = "./mps-player" }
# external # external
clap = { version = "3.0", features = ["derive"] } clap = { version = "3.0", features = ["derive"] }

View file

@ -1,9 +1,9 @@
# mps # mps
An MPS program which plays music. An MPS program which plays music.
This doesn't do much yet, since mps-interpreter is still under construction. This project implements the interpreter (mps-interpreter), music player (mps-player), and CLI interface for MPS (root).
The CLI interface includes a REPL for running scripts.
Future home of a MPS REPL for playing music ergonomically through a CLI. The REPL interactive mode also provides more details about using MPS through the `?help` command.
License: LGPL-2.1-only OR GPL-2.0-or-later License: LGPL-2.1-only OR GPL-2.0-or-later

View file

@ -1,6 +1,6 @@
[package] [package]
name = "mps-interpreter" name = "mps-interpreter"
version = "0.1.0" version = "0.2.0"
edition = "2021" edition = "2021"
license = "LGPL-2.1-only OR GPL-2.0-or-later" license = "LGPL-2.1-only OR GPL-2.0-or-later"
readme = "README.md" readme = "README.md"

View file

@ -8,6 +8,8 @@ use super::tokens::MpsToken;
use super::MpsContext; use super::MpsContext;
use super::MpsMusicItem; use super::MpsMusicItem;
/// The script interpreter.
/// Use MpsRunner for a better interface.
pub struct MpsInterpretor<T> pub struct MpsInterpretor<T>
where where
T: crate::tokens::MpsTokenReader, T: crate::tokens::MpsTokenReader,
@ -151,6 +153,7 @@ fn box_error_with_ctx<E: MpsLanguageError + 'static>(
Box::new(error) as Box<dyn MpsLanguageError> Box::new(error) as Box<dyn MpsLanguageError>
} }
/// Builder function to add the standard statements of MPS.
pub(crate) fn standard_vocab(vocabulary: &mut MpsLanguageDictionary) { pub(crate) fn standard_vocab(vocabulary: &mut MpsLanguageDictionary) {
vocabulary vocabulary
.add(crate::lang::vocabulary::filters::empty_filter()) .add(crate::lang::vocabulary::filters::empty_filter())

View file

@ -2,7 +2,7 @@ use std::collections::VecDeque;
//use std::fmt::{Debug, Display, Error, Formatter}; //use std::fmt::{Debug, Display, Error, Formatter};
use std::marker::PhantomData; use std::marker::PhantomData;
use crate::lang::utility::{assert_token, assert_token_raw, assert_token_raw_back, assert_empty}; use crate::lang::utility::{assert_empty, assert_token, assert_token_raw, assert_token_raw_back};
use crate::lang::MpsLanguageDictionary; use crate::lang::MpsLanguageDictionary;
use crate::lang::SyntaxError; use crate::lang::SyntaxError;
use crate::lang::{BoxedMpsOpFactory, MpsOp}; use crate::lang::{BoxedMpsOpFactory, MpsOp};

View file

@ -23,6 +23,7 @@ impl<T: MpsTokenReader> MpsRunnerSettings<T> {
} }
} }
/// A wrapper around MpsInterpretor which provides a simpler (and more powerful) interface.
pub struct MpsRunner<T: MpsTokenReader> { pub struct MpsRunner<T: MpsTokenReader> {
interpretor: MpsInterpretor<T>, interpretor: MpsInterpretor<T>,
new_statement: bool, new_statement: bool,

View file

@ -1,6 +1,6 @@
[package] [package]
name = "mps-player" name = "mps-player"
version = "0.1.0" version = "0.2.0"
edition = "2021" edition = "2021"
license = "LGPL-2.1-only OR GPL-2.0-or-later" license = "LGPL-2.1-only OR GPL-2.0-or-later"
readme = "README.md" readme = "README.md"
@ -10,7 +10,7 @@ rodio = { version = "^0.14"}
m3u8-rs = { version = "^3.0.0" } m3u8-rs = { version = "^3.0.0" }
# local # local
mps-interpreter = { path = "../mps-interpreter" } mps-interpreter = { path = "../mps-interpreter", version = "0.2.0" }
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
#dbus = { version = "^0.9" } #dbus = { version = "^0.9" }

View file

@ -1,6 +1,8 @@
# mps-player # mps-player
An MPS playback library with support for Linux media controls (D-Bus). An MPS playback library with support for media controls (Linux & D-Bus only atm).
This handles the output from interpreting a script.
Music playback and m3u8 playlist generation are implemented in this part of the project.
License: LGPL-2.1-only OR GPL-2.0-or-later License: LGPL-2.1-only OR GPL-2.0-or-later

View file

@ -1,18 +1,20 @@
use std::sync::mpsc::{Sender, Receiver, channel}; use std::sync::mpsc::{channel, Receiver, Sender};
use std::thread::JoinHandle; use std::thread::JoinHandle;
use mps_interpreter::tokens::MpsTokenReader; use mps_interpreter::tokens::MpsTokenReader;
use super::os_controls::SystemControlWrapper;
use super::player_wrapper::{ControlAction, MpsPlayerServer, PlayerAction};
use super::MpsPlayer; use super::MpsPlayer;
use super::PlaybackError; use super::PlaybackError;
use super::player_wrapper::{ControlAction, PlayerAction, MpsPlayerServer};
use super::os_controls::SystemControlWrapper;
/// A controller for a MpsPlayer running on another thread.
/// This receives and sends events like media buttons and script errors for the MpsPlayer.
pub struct MpsController { pub struct MpsController {
control: Sender<ControlAction>, control: Sender<ControlAction>,
event: Receiver<PlayerAction>, event: Receiver<PlayerAction>,
handle: JoinHandle<()>, handle: JoinHandle<()>,
sys_ctrl: SystemControlWrapper sys_ctrl: SystemControlWrapper,
} }
impl MpsController { impl MpsController {
@ -23,7 +25,8 @@ impl MpsController {
let (event_tx, event_rx) = channel(); let (event_tx, event_rx) = channel();
let mut sys_ctrl = SystemControlWrapper::new(control_tx.clone()); let mut sys_ctrl = SystemControlWrapper::new(control_tx.clone());
sys_ctrl.init(); sys_ctrl.init();
let handle = MpsPlayerServer::spawn(player_gen, control_tx.clone(), control_rx, event_tx, false); let handle =
MpsPlayerServer::spawn(player_gen, control_tx.clone(), control_rx, event_tx, false);
Self { Self {
control: control_tx, control: control_tx,
event: event_rx, event: event_rx,
@ -39,7 +42,8 @@ impl MpsController {
let (event_tx, event_rx) = channel(); let (event_tx, event_rx) = channel();
let mut sys_ctrl = SystemControlWrapper::new(control_tx.clone()); let mut sys_ctrl = SystemControlWrapper::new(control_tx.clone());
sys_ctrl.init(); sys_ctrl.init();
let handle = MpsPlayerServer::spawn(player_gen, control_tx.clone(), control_rx, event_tx, true); let handle =
MpsPlayerServer::spawn(player_gen, control_tx.clone(), control_rx, event_tx, true);
Self { Self {
control: control_tx, control: control_tx,
event: event_rx, event: event_rx,
@ -49,7 +53,9 @@ impl MpsController {
} }
fn send_confirm(&self, to_send: ControlAction) -> Result<(), PlaybackError> { fn send_confirm(&self, to_send: ControlAction) -> Result<(), PlaybackError> {
self.control.send(to_send.clone()).map_err(PlaybackError::from_err)?; self.control
.send(to_send.clone())
.map_err(PlaybackError::from_err)?;
let mut response = self.event.recv().map_err(PlaybackError::from_err)?; let mut response = self.event.recv().map_err(PlaybackError::from_err)?;
while !response.is_acknowledgement() { while !response.is_acknowledgement() {
Self::handle_event(response)?; Self::handle_event(response)?;
@ -60,12 +66,13 @@ impl MpsController {
Ok(()) Ok(())
} else { } else {
Err(PlaybackError { Err(PlaybackError {
msg: "Incorrect acknowledgement received for MpsController control action".into() msg: "Incorrect acknowledgement received for MpsController control action"
.into(),
}) })
} }
} else { } else {
Err(PlaybackError { Err(PlaybackError {
msg: "Invalid acknowledgement received for MpsController control action".into() msg: "Invalid acknowledgement received for MpsController control action".into(),
}) })
} }
} }
@ -80,41 +87,44 @@ impl MpsController {
} }
pub fn next(&self) -> Result<(), PlaybackError> { pub fn next(&self) -> Result<(), PlaybackError> {
self.send_confirm(ControlAction::Next{ack: true}) self.send_confirm(ControlAction::Next { ack: true })
} }
pub fn previous(&self) -> Result<(), PlaybackError> { pub fn previous(&self) -> Result<(), PlaybackError> {
self.send_confirm(ControlAction::Previous{ack: true}) self.send_confirm(ControlAction::Previous { ack: true })
} }
pub fn play(&self) -> Result<(), PlaybackError> { pub fn play(&self) -> Result<(), PlaybackError> {
self.send_confirm(ControlAction::Play{ack: true}) self.send_confirm(ControlAction::Play { ack: true })
} }
pub fn pause(&self) -> Result<(), PlaybackError> { pub fn pause(&self) -> Result<(), PlaybackError> {
self.send_confirm(ControlAction::Pause{ack: true}) self.send_confirm(ControlAction::Pause { ack: true })
} }
pub fn stop(&self) -> Result<(), PlaybackError> { pub fn stop(&self) -> Result<(), PlaybackError> {
self.send_confirm(ControlAction::Stop{ack: true}) self.send_confirm(ControlAction::Stop { ack: true })
} }
pub fn enqueue(&self, count: usize) -> Result<(), PlaybackError> { pub fn enqueue(&self, count: usize) -> Result<(), PlaybackError> {
self.send_confirm(ControlAction::Enqueue{amount: count, ack: true}) self.send_confirm(ControlAction::Enqueue {
amount: count,
ack: true,
})
} }
pub fn ping(&self) -> Result<(), PlaybackError> { pub fn ping(&self) -> Result<(), PlaybackError> {
self.send_confirm(ControlAction::NoOp{ack: true}) self.send_confirm(ControlAction::NoOp { ack: true })
} }
pub fn exit(self) -> Result<(), PlaybackError> { pub fn exit(self) -> Result<(), PlaybackError> {
self.send_confirm(ControlAction::Exit{ack: true})?; self.send_confirm(ControlAction::Exit { ack: true })?;
self.sys_ctrl.exit(); self.sys_ctrl.exit();
match self.handle.join() { match self.handle.join() {
Ok(x) => Ok(x), Ok(x) => Ok(x),
Err(_) => Err(PlaybackError { Err(_) => Err(PlaybackError {
msg: "MpsPlayerServer did not exit correctly".into() msg: "MpsPlayerServer did not exit correctly".into(),
}) }),
} }
} }
@ -134,7 +144,9 @@ impl MpsController {
for msg in self.event.try_iter() { for msg in self.event.try_iter() {
Self::handle_event(msg)?; Self::handle_event(msg)?;
} }
self.control.send(ControlAction::CheckEmpty{ack: true}).map_err(PlaybackError::from_err)?; self.control
.send(ControlAction::CheckEmpty { ack: true })
.map_err(PlaybackError::from_err)?;
loop { loop {
let msg = self.event.recv().map_err(PlaybackError::from_err)?; let msg = self.event.recv().map_err(PlaybackError::from_err)?;
if let PlayerAction::Empty = msg { if let PlayerAction::Empty = msg {
@ -161,8 +173,12 @@ impl MpsController {
/// Like check(), but it also waits for an acknowledgement to ensure it gets the latest events. /// Like check(), but it also waits for an acknowledgement to ensure it gets the latest events.
pub fn check_ack(&self) -> Vec<PlaybackError> { pub fn check_ack(&self) -> Vec<PlaybackError> {
let mut result = Vec::new(); let mut result = Vec::new();
let to_send = ControlAction::NoOp{ack: true}; let to_send = ControlAction::NoOp { ack: true };
if let Err(e) = self.control.send(to_send.clone()).map_err(PlaybackError::from_err) { if let Err(e) = self
.control
.send(to_send.clone())
.map_err(PlaybackError::from_err)
{
result.push(e); result.push(e);
} }
for msg in self.event.iter() { for msg in self.event.iter() {
@ -171,7 +187,8 @@ impl MpsController {
break; break;
} else { } else {
result.push(PlaybackError { result.push(PlaybackError {
msg: "Incorrect acknowledgement received for MpsController control action".into() msg: "Incorrect acknowledgement received for MpsController control action"
.into(),
}); });
} }
} else if let Err(e) = Self::handle_event(msg) { } else if let Err(e) = Self::handle_event(msg) {

View file

@ -1,8 +1,8 @@
use std::fmt::{Debug, Display, Formatter, Error}; use std::fmt::{Debug, Display, Error, Formatter};
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct PlaybackError { pub struct PlaybackError {
pub(crate) msg: String pub(crate) msg: String,
} }
impl PlaybackError { impl PlaybackError {

View file

@ -1,4 +1,6 @@
//! An MPS playback library with support for Linux media controls (D-Bus). //! An MPS playback library with support for media controls (Linux & D-Bus only atm).
//! This handles the output from interpreting a script.
//! Music playback and m3u8 playlist generation are implemented in this part of the project.
//! //!
mod controller; mod controller;

View file

@ -1,5 +1,5 @@
#[cfg(unix)] #[cfg(unix)]
use std::sync::mpsc::{Sender, channel}; use std::sync::mpsc::{channel, Sender};
#[cfg(unix)] #[cfg(unix)]
use std::thread::JoinHandle; use std::thread::JoinHandle;
@ -9,10 +9,12 @@ use mpris_player::{MprisPlayer, PlaybackStatus};
//use super::MpsController; //use super::MpsController;
use super::player_wrapper::ControlAction; use super::player_wrapper::ControlAction;
/// OS-specific APIs for media controls.
/// Currently only Linux (dbus) is supported.
pub struct SystemControlWrapper { pub struct SystemControlWrapper {
control: Sender<ControlAction>, control: Sender<ControlAction>,
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
dbus_handle: Option<JoinHandle<()>>,//std::sync::Arc<MprisPlayer>, dbus_handle: Option<JoinHandle<()>>, //std::sync::Arc<MprisPlayer>,
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
dbus_die: Option<Sender<()>>, dbus_die: Option<Sender<()>>,
} }
@ -22,7 +24,7 @@ impl SystemControlWrapper {
pub fn new(control: Sender<ControlAction>) -> Self { pub fn new(control: Sender<ControlAction>) -> Self {
Self { Self {
control: control, control: control,
dbus_handle: None,//MprisPlayer::new("mps".into(), "mps".into(), "null".into()) dbus_handle: None, //MprisPlayer::new("mps".into(), "mps".into(), "null".into())
dbus_die: None, dbus_die: None,
} }
} }
@ -44,58 +46,67 @@ impl SystemControlWrapper {
dbus_conn.set_can_go_next(true); dbus_conn.set_can_go_next(true);
let control_clone = control_clone1.clone(); let control_clone = control_clone1.clone();
dbus_conn.connect_next( dbus_conn.connect_next(move || {
move || { //println!("Got next signal");
//println!("Got next signal"); control_clone
control_clone.send(ControlAction::Next{ack: false}).unwrap_or(()) .send(ControlAction::Next { ack: false })
} .unwrap_or(())
); });
let control_clone = control_clone1.clone(); let control_clone = control_clone1.clone();
dbus_conn.connect_previous( dbus_conn.connect_previous(move || {
move || control_clone.send(ControlAction::Previous{ack: false}).unwrap_or(()) control_clone
); .send(ControlAction::Previous { ack: false })
.unwrap_or(())
});
let control_clone = control_clone1.clone(); let control_clone = control_clone1.clone();
let dbus_conn_clone = dbus_conn.clone(); let dbus_conn_clone = dbus_conn.clone();
dbus_conn.connect_pause( dbus_conn.connect_pause(move || {
move || { //println!("Got pause signal");
//println!("Got pause signal"); dbus_conn_clone.set_playback_status(PlaybackStatus::Paused);
control_clone
.send(ControlAction::Pause { ack: false })
.unwrap_or(());
});
let control_clone = control_clone1.clone();
let dbus_conn_clone = dbus_conn.clone();
dbus_conn.connect_play(move || {
//println!("Got play signal");
dbus_conn_clone.set_playback_status(PlaybackStatus::Playing);
control_clone
.send(ControlAction::Play { ack: false })
.unwrap_or(())
});
let control_clone = control_clone1.clone();
let dbus_conn_clone = dbus_conn.clone();
dbus_conn.connect_play_pause(move || {
//println!("Got play_pause signal (was playing? {})", is_playing);
if is_playing {
dbus_conn_clone.set_playback_status(PlaybackStatus::Paused); dbus_conn_clone.set_playback_status(PlaybackStatus::Paused);
control_clone.send(ControlAction::Pause{ack: false}).unwrap_or(()); control_clone
} .send(ControlAction::Pause { ack: false })
); .unwrap_or(());
} else {
let control_clone = control_clone1.clone();
let dbus_conn_clone = dbus_conn.clone();
dbus_conn.connect_play(
move || {
//println!("Got play signal");
dbus_conn_clone.set_playback_status(PlaybackStatus::Playing); dbus_conn_clone.set_playback_status(PlaybackStatus::Playing);
control_clone.send(ControlAction::Play{ack: false}).unwrap_or(()) control_clone
.send(ControlAction::Play { ack: false })
.unwrap_or(());
} }
); is_playing = !is_playing;
});
let control_clone = control_clone1.clone(); let control_clone = control_clone1.clone();
let dbus_conn_clone = dbus_conn.clone(); dbus_conn.connect_volume(move |v| {
dbus_conn.connect_play_pause( control_clone
move || { .send(ControlAction::SetVolume {
//println!("Got play_pause signal (was playing? {})", is_playing); ack: false,
if is_playing { volume: (v * (u32::MAX as f64)) as _,
dbus_conn_clone.set_playback_status(PlaybackStatus::Paused); })
control_clone.send(ControlAction::Pause{ack: false}).unwrap_or(()); .unwrap_or(())
} else { });
dbus_conn_clone.set_playback_status(PlaybackStatus::Playing);
control_clone.send(ControlAction::Play{ack: false}).unwrap_or(());
}
is_playing = !is_playing;
}
);
let control_clone = control_clone1.clone();
dbus_conn.connect_volume(
move |v| control_clone.send(ControlAction::SetVolume{ack: false, volume: (v * (u32::MAX as f64)) as _}).unwrap_or(())
);
// poll loop, using my custom mpris lib because original did it wrong // poll loop, using my custom mpris lib because original did it wrong
loop { loop {
@ -120,9 +131,7 @@ impl SystemControlWrapper {
#[cfg(not(any(target_os = "linux")))] #[cfg(not(any(target_os = "linux")))]
impl SystemControlWrapper { impl SystemControlWrapper {
pub fn new(control: Sender<ControlAction>) -> Self { pub fn new(control: Sender<ControlAction>) -> Self {
Self { Self { control: control }
control: control,
}
} }
pub fn init(&mut self) {} pub fn init(&mut self) {}

View file

@ -1,14 +1,16 @@
use std::io;
use std::fs; use std::fs;
use std::io;
use rodio::{decoder::Decoder, OutputStream, Sink, OutputStreamHandle}; use rodio::{decoder::Decoder, OutputStream, OutputStreamHandle, Sink};
use m3u8_rs::{MediaPlaylist, MediaSegment}; use m3u8_rs::{MediaPlaylist, MediaSegment};
use mps_interpreter::{MpsRunner, tokens::MpsTokenReader}; use mps_interpreter::{tokens::MpsTokenReader, MpsRunner};
use super::PlaybackError; use super::PlaybackError;
/// Playback functionality for a script.
/// This takes the output of the runner and plays or saves it.
pub struct MpsPlayer<T: MpsTokenReader> { pub struct MpsPlayer<T: MpsTokenReader> {
runner: MpsRunner<T>, runner: MpsRunner<T>,
sink: Sink, sink: Sink,
@ -19,12 +21,13 @@ pub struct MpsPlayer<T: MpsTokenReader> {
impl<T: MpsTokenReader> MpsPlayer<T> { impl<T: MpsTokenReader> MpsPlayer<T> {
pub fn new(runner: MpsRunner<T>) -> Result<Self, PlaybackError> { pub fn new(runner: MpsRunner<T>) -> Result<Self, PlaybackError> {
let (stream, output_handle) = OutputStream::try_default().map_err(PlaybackError::from_err)?; let (stream, output_handle) =
Ok(Self{ OutputStream::try_default().map_err(PlaybackError::from_err)?;
Ok(Self {
runner: runner, runner: runner,
sink: Sink::try_new(&output_handle).map_err(PlaybackError::from_err)?, sink: Sink::try_new(&output_handle).map_err(PlaybackError::from_err)?,
output_stream: stream, output_stream: stream,
output_handle: output_handle output_handle: output_handle,
}) })
} }
@ -39,8 +42,8 @@ impl<T: MpsTokenReader> MpsPlayer<T> {
self.sink.append(source); self.sink.append(source);
//self.sink.play(); // idk if this is necessary //self.sink.play(); // idk if this is necessary
Ok(()) Ok(())
}, }
Err(e) => Err(PlaybackError::from_err(e)) Err(e) => Err(PlaybackError::from_err(e)),
}?; }?;
} }
self.sink.sleep_until_end(); self.sink.sleep_until_end();
@ -57,8 +60,8 @@ impl<T: MpsTokenReader> MpsPlayer<T> {
self.sink.append(source); self.sink.append(source);
//self.sink.play(); // idk if this is necessary //self.sink.play(); // idk if this is necessary
Ok(()) Ok(())
}, }
Err(e) => Err(PlaybackError::from_err(e)) Err(e) => Err(PlaybackError::from_err(e)),
}?; }?;
} }
Ok(()) Ok(())
@ -66,7 +69,9 @@ impl<T: MpsTokenReader> MpsPlayer<T> {
pub fn enqueue(&mut self, count: usize) -> Result<(), PlaybackError> { pub fn enqueue(&mut self, count: usize) -> Result<(), PlaybackError> {
let mut items_left = count; let mut items_left = count;
if items_left == 0 { return Ok(()); } if items_left == 0 {
return Ok(());
}
for item in &mut self.runner { for item in &mut self.runner {
match item { match item {
Ok(music) => { Ok(music) => {
@ -77,11 +82,13 @@ impl<T: MpsTokenReader> MpsPlayer<T> {
self.sink.append(source); self.sink.append(source);
//self.sink.play(); // idk if this is necessary //self.sink.play(); // idk if this is necessary
Ok(()) Ok(())
}, }
Err(e) => Err(PlaybackError::from_err(e)) Err(e) => Err(PlaybackError::from_err(e)),
}?; }?;
items_left -= 1; items_left -= 1;
if items_left == 0 { break; } if items_left == 0 {
break;
}
} }
//println!("Enqueued {} items", count - items_left); //println!("Enqueued {} items", count - items_left);
Ok(()) Ok(())
@ -120,16 +127,14 @@ impl<T: MpsTokenReader> MpsPlayer<T> {
for item in &mut self.runner { for item in &mut self.runner {
match item { match item {
Ok(music) => { Ok(music) => {
playlist.segments.push( playlist.segments.push(MediaSegment {
MediaSegment { uri: music.filename,
uri: music.filename, title: Some(music.title),
title: Some(music.title), ..Default::default()
..Default::default() });
}
);
Ok(()) Ok(())
}, }
Err(e) => Err(PlaybackError::from_err(e)) Err(e) => Err(PlaybackError::from_err(e)),
}?; }?;
} }
playlist.write_to(w).map_err(PlaybackError::from_err) playlist.write_to(w).map_err(PlaybackError::from_err)
@ -160,9 +165,9 @@ impl<T: MpsTokenReader> MpsPlayer<T> {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::io;
use mps_interpreter::MpsRunner;
use super::*; use super::*;
use mps_interpreter::MpsRunner;
use std::io;
#[allow(dead_code)] #[allow(dead_code)]
#[test] #[test]

View file

@ -1,4 +1,4 @@
use std::sync::mpsc::{Sender, Receiver}; use std::sync::mpsc::{Receiver, Sender};
use std::{thread, thread::JoinHandle}; use std::{thread, thread::JoinHandle};
use mps_interpreter::tokens::MpsTokenReader; use mps_interpreter::tokens::MpsTokenReader;
@ -6,6 +6,10 @@ use mps_interpreter::tokens::MpsTokenReader;
use super::MpsPlayer; use super::MpsPlayer;
use super::PlaybackError; use super::PlaybackError;
/// A wrapper around MpsPlayer so that playback can occur on a different thread.
/// This allows for message passing between the threads.
///
/// You will probably never directly interact with this, instead using MpsController to communicate.
pub struct MpsPlayerServer<T: MpsTokenReader> { pub struct MpsPlayerServer<T: MpsTokenReader> {
player: MpsPlayer<T>, player: MpsPlayer<T>,
control: Receiver<ControlAction>, control: Receiver<ControlAction>,
@ -14,7 +18,12 @@ pub struct MpsPlayerServer<T: MpsTokenReader> {
} }
impl<T: MpsTokenReader> MpsPlayerServer<T> { impl<T: MpsTokenReader> MpsPlayerServer<T> {
pub fn new(player: MpsPlayer<T>, ctrl: Receiver<ControlAction>, event: Sender<PlayerAction>, keep_alive: bool) -> Self { pub fn new(
player: MpsPlayer<T>,
ctrl: Receiver<ControlAction>,
event: Sender<PlayerAction>,
keep_alive: bool,
) -> Self {
Self { Self {
player: player, player: player,
control: ctrl, control: ctrl,
@ -39,7 +48,7 @@ impl<T: MpsTokenReader> MpsPlayerServer<T> {
// process command // process command
match command { match command {
ControlAction::Next{..} => { ControlAction::Next { .. } => {
//println!("Executing next command (queue_len: {})", self.player.queue_len()); //println!("Executing next command (queue_len: {})", self.player.queue_len());
if let Err(e) = self.player.new_sink() { if let Err(e) = self.player.new_sink() {
self.event.send(PlayerAction::Exception(e)).unwrap(); self.event.send(PlayerAction::Exception(e)).unwrap();
@ -47,32 +56,32 @@ impl<T: MpsTokenReader> MpsPlayerServer<T> {
if !self.player.is_paused() { if !self.player.is_paused() {
self.player.enqueue(1).unwrap(); self.player.enqueue(1).unwrap();
} }
}, }
ControlAction::Previous{..} => {}, // TODO ControlAction::Previous { .. } => {} // TODO
ControlAction::Play{..} => self.player.resume(), ControlAction::Play { .. } => self.player.resume(),
ControlAction::Pause{..} => self.player.pause(), ControlAction::Pause { .. } => self.player.pause(),
ControlAction::PlayPause{..} => { ControlAction::PlayPause { .. } => {
if self.player.is_paused() { if self.player.is_paused() {
self.player.resume(); self.player.resume();
} else { } else {
self.player.pause(); self.player.pause();
} }
}, }
ControlAction::Stop{..} => self.player.stop(), ControlAction::Stop { .. } => self.player.stop(),
ControlAction::Exit{..} => { ControlAction::Exit { .. } => {
self.player.stop(); self.player.stop();
is_exiting = true; is_exiting = true;
}, }
ControlAction::Enqueue{amount,..} => { ControlAction::Enqueue { amount, .. } => {
if let Err(e) = self.player.enqueue(amount) { if let Err(e) = self.player.enqueue(amount) {
self.event.send(PlayerAction::Exception(e)).unwrap(); self.event.send(PlayerAction::Exception(e)).unwrap();
} }
}, }
ControlAction::NoOp{..} => {}, // empty by design ControlAction::NoOp { .. } => {} // empty by design
ControlAction::SetVolume{volume,..} => { ControlAction::SetVolume { volume, .. } => {
self.player.set_volume((volume as f32) / (u32::MAX as f32)); self.player.set_volume((volume as f32) / (u32::MAX as f32));
}, }
ControlAction::CheckEmpty{..} => { ControlAction::CheckEmpty { .. } => {
check_empty = true; check_empty = true;
} }
} }
@ -82,7 +91,8 @@ impl<T: MpsTokenReader> MpsPlayerServer<T> {
if let Err(e) = self.player.enqueue(1) { if let Err(e) = self.player.enqueue(1) {
self.event.send(PlayerAction::Exception(e)).unwrap(); self.event.send(PlayerAction::Exception(e)).unwrap();
} }
if self.player.queue_len() == 0 { // no more music to add if self.player.queue_len() == 0 {
// no more music to add
is_exiting = !self.keep_alive || is_exiting; is_exiting = !self.keep_alive || is_exiting;
} }
} }
@ -92,10 +102,12 @@ impl<T: MpsTokenReader> MpsPlayerServer<T> {
} }
// always check for empty state change // always check for empty state change
if self.player.queue_len() == 0 && !is_empty { // just became empty if self.player.queue_len() == 0 && !is_empty {
// just became empty
is_empty = true; is_empty = true;
self.event.send(PlayerAction::Empty).unwrap(); self.event.send(PlayerAction::Empty).unwrap();
} else if self.player.queue_len() != 0 && is_empty { // just got filled } else if self.player.queue_len() != 0 && is_empty {
// just got filled
is_empty = false; is_empty = false;
} }
@ -103,7 +115,9 @@ impl<T: MpsTokenReader> MpsPlayerServer<T> {
self.event.send(PlayerAction::Empty).unwrap(); self.event.send(PlayerAction::Empty).unwrap();
} }
if is_exiting { break; } if is_exiting {
break;
}
} }
println!("Exiting playback server"); println!("Exiting playback server");
self.event.send(PlayerAction::End).unwrap(); self.event.send(PlayerAction::End).unwrap();
@ -114,7 +128,7 @@ impl<T: MpsTokenReader> MpsPlayerServer<T> {
ctrl_tx: Sender<ControlAction>, ctrl_tx: Sender<ControlAction>,
ctrl_rx: Receiver<ControlAction>, ctrl_rx: Receiver<ControlAction>,
event: Sender<PlayerAction>, event: Sender<PlayerAction>,
keep_alive: bool keep_alive: bool,
) -> JoinHandle<()> { ) -> JoinHandle<()> {
thread::spawn(move || Self::unblocking_timer_loop(ctrl_tx, 50)); thread::spawn(move || Self::unblocking_timer_loop(ctrl_tx, 50));
thread::spawn(move || { thread::spawn(move || {
@ -127,7 +141,7 @@ impl<T: MpsTokenReader> MpsPlayerServer<T> {
pub fn unblocking_timer_loop(ctrl_tx: Sender<ControlAction>, sleep_ms: u64) { pub fn unblocking_timer_loop(ctrl_tx: Sender<ControlAction>, sleep_ms: u64) {
let dur = std::time::Duration::from_millis(sleep_ms); let dur = std::time::Duration::from_millis(sleep_ms);
loop { loop {
if let Err(_) = ctrl_tx.send(ControlAction::NoOp{ack: false}) { if let Err(_) = ctrl_tx.send(ControlAction::NoOp { ack: false }) {
break; break;
} }
thread::sleep(dur); thread::sleep(dur);
@ -139,33 +153,33 @@ impl<T: MpsTokenReader> MpsPlayerServer<T> {
#[allow(dead_code)] #[allow(dead_code)]
#[derive(Clone, PartialEq, Eq, Debug)] #[derive(Clone, PartialEq, Eq, Debug)]
pub enum ControlAction { pub enum ControlAction {
Next{ack: bool}, Next { ack: bool },
Previous{ack: bool}, Previous { ack: bool },
Play{ack: bool}, Play { ack: bool },
Pause{ack: bool}, Pause { ack: bool },
PlayPause{ack: bool}, PlayPause { ack: bool },
Stop{ack: bool}, Stop { ack: bool },
Exit{ack: bool}, Exit { ack: bool },
Enqueue {amount: usize, ack: bool}, Enqueue { amount: usize, ack: bool },
NoOp{ack: bool}, NoOp { ack: bool },
SetVolume{ack: bool, volume: u32}, SetVolume { ack: bool, volume: u32 },
CheckEmpty{ack: bool}, CheckEmpty { ack: bool },
} }
impl ControlAction { impl ControlAction {
fn needs_ack(&self) -> bool { fn needs_ack(&self) -> bool {
*match self { *match self {
Self::Next{ack} => ack, Self::Next { ack } => ack,
Self::Previous{ack} => ack, Self::Previous { ack } => ack,
Self::Play{ack} => ack, Self::Play { ack } => ack,
Self::Pause{ack} => ack, Self::Pause { ack } => ack,
Self::PlayPause{ack} => ack, Self::PlayPause { ack } => ack,
Self::Stop{ack} => ack, Self::Stop { ack } => ack,
Self::Exit{ack} => ack, Self::Exit { ack } => ack,
Self::Enqueue{ack,..} => ack, Self::Enqueue { ack, .. } => ack,
Self::NoOp{ack,..} => ack, Self::NoOp { ack, .. } => ack,
Self::SetVolume{ack,..} => ack, Self::SetVolume { ack, .. } => ack,
Self::CheckEmpty{ack} => ack, Self::CheckEmpty { ack } => ack,
} }
} }
} }
@ -183,7 +197,7 @@ impl PlayerAction {
pub fn is_acknowledgement(&self) -> bool { pub fn is_acknowledgement(&self) -> bool {
match self { match self {
Self::Acknowledge(_) => true, Self::Acknowledge(_) => true,
_ => false _ => false,
} }
} }
} }

View file

@ -1,5 +1,5 @@
use std::sync::mpsc::{channel, Sender, Receiver}; use std::io::{self, Read, Write};
use std::io::{Read, Write, self}; use std::sync::mpsc::{channel, Receiver, Sender};
pub struct ChannelWriter { pub struct ChannelWriter {
tx: Sender<u8>, tx: Sender<u8>,
@ -29,34 +29,36 @@ pub struct ChannelReader {
} }
impl Read for ChannelReader { impl Read for ChannelReader {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> { fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
let mut count = 0; let mut count = 0;
if self.blocking { if self.blocking {
for b in self.rx.iter() { for b in self.rx.iter() {
buf[count] = b; buf[count] = b;
count += 1; count += 1;
if count >= buf.len() {break;} if count >= buf.len() {
break;
}
} }
} else { } else {
for b in self.rx.try_iter() { for b in self.rx.try_iter() {
buf[count] = b; buf[count] = b;
count += 1; count += 1;
if count >= buf.len() {break;} if count >= buf.len() {
break;
}
} }
} }
Ok(count) Ok(count)
} }
} }
pub fn channel_io() -> (ChannelWriter, ChannelReader) { pub fn channel_io() -> (ChannelWriter, ChannelReader) {
let (sender, receiver) = channel(); let (sender, receiver) = channel();
( (
ChannelWriter { ChannelWriter { tx: sender },
tx: sender,
},
ChannelReader { ChannelReader {
rx: receiver, rx: receiver,
blocking: false, blocking: false,
} },
) )
} }

View file

@ -1,4 +1,4 @@
use clap::{Parser}; use clap::Parser;
#[derive(Parser)] #[derive(Parser)]
#[clap(author, version)] #[clap(author, version)]
@ -17,7 +17,7 @@ pub struct CliArgs {
/// In REPL mode, the prompt to display /// In REPL mode, the prompt to display
#[clap(long, default_value_t = String::from("|"))] #[clap(long, default_value_t = String::from("|"))]
pub prompt: String pub prompt: String,
} }
pub fn parse() -> CliArgs { pub fn parse() -> CliArgs {

50
src/help.rs Normal file
View file

@ -0,0 +1,50 @@
/// Standard help string containing usage information for MPS.
pub fn help() -> String {
"This language is all about iteration. Almost everything is an iterator or operates on iterators. By default, any operation which is not an assignment will cause the script runner to handle (play/save) the items which that statement contains.
To view the currently-supported operations, try ?functions and ?filters".to_string()
}
pub fn functions() -> String {
"FUNCTIONS (?functions)
Similar to most other languages: function_name(param1, param2, etc.)
sql_init(generate = true|false, folder = `path/to/music`)
Initialize the SQLite database connection using the provided parameters. This must be performed before any other database operation (otherwise the database will be connected with default settings).
sql(`SQL query here`)
Perform a raw SQLite query on the database which MPS auto-generates. An iterator of the results is returned.
song(`something`)
Retrieve all songs in the database with a title like something.
album(`something`)
Retrieve all songs in the database with an album title like something.
artist(`something`)
Retrieve all songs in the database with an artist name like something.
genre(`something`)
Retrieve all songs in the database with a genre title like something.
repeat(iterable, count)
Repeat the iterable count times, or infinite times if count is omitted.
files(folder = `path/to/music`, recursive = true|false, regex = `pattern`)
Retrieve all files from a folder, matching a regex pattern.".to_string()
}
pub fn filters() -> String {
"FILTERS (?filters)
Operations to reduce the items in an iterable: iterable.(filter)
field == something
field >= something
field > something
field <= something
field < something
Compare all items, keeping only those that match the condition.
[empty]
Matches all items".to_string()
}

View file

@ -1,18 +1,19 @@
//! An MPS program which plays music. //! An MPS program which plays music.
//! This doesn't do much yet, since mps-interpreter is still under construction. //! This project implements the interpreter (mps-interpreter), music player (mps-player), and CLI interface for MPS (root).
//! //! The CLI interface includes a REPL for running scripts.
//! Future home of a MPS REPL for playing music ergonomically through a CLI. //! The REPL interactive mode also provides more details about using MPS through the `?help` command.
//! //!
mod channel_io; mod channel_io;
mod cli; mod cli;
mod help;
mod repl; mod repl;
use std::io; use std::io;
use std::path::PathBuf; use std::path::PathBuf;
use mps_interpreter::MpsRunner; use mps_interpreter::MpsRunner;
use mps_player::{MpsPlayer, PlaybackError, MpsController}; use mps_player::{MpsController, MpsPlayer, PlaybackError};
#[allow(dead_code)] #[allow(dead_code)]
fn play_cursor() -> Result<(), PlaybackError> { fn play_cursor() -> Result<(), PlaybackError> {
@ -36,7 +37,7 @@ fn main() {
let player_builder = move || { let player_builder = move || {
let script_reader = io::BufReader::new( let script_reader = io::BufReader::new(
std::fs::File::open(&script_file2) std::fs::File::open(&script_file2)
.expect(&format!("Abort: Cannot open file `{}`", &script_file2)) .expect(&format!("Abort: Cannot open file `{}`", &script_file2)),
); );
let runner = MpsRunner::with_stream(script_reader); let runner = MpsRunner::with_stream(script_reader);
let player = MpsPlayer::new(runner).unwrap(); let player = MpsPlayer::new(runner).unwrap();
@ -45,12 +46,14 @@ fn main() {
if let Some(playlist_file) = &args.playlist { if let Some(playlist_file) = &args.playlist {
// generate playlist // generate playlist
let mut player = player_builder(); let mut player = player_builder();
let mut writer = io::BufWriter::new( let mut writer = io::BufWriter::new(std::fs::File::create(playlist_file).expect(
std::fs::File::create(playlist_file) &format!("Abort: Cannot create writeable file `{}`", playlist_file),
.expect(&format!("Abort: Cannot create writeable file `{}`", playlist_file)) ));
);
match player.save_m3u8(&mut writer) { match player.save_m3u8(&mut writer) {
Ok(_) => println!("Succes: Finished playlist `{}` from script `{}`", playlist_file, script_file), Ok(_) => println!(
"Succes: Finished playlist `{}` from script `{}`",
playlist_file, script_file
),
Err(e) => eprintln!("{}", e), Err(e) => eprintln!("{}", e),
} }
} else { } else {
@ -64,6 +67,7 @@ fn main() {
} else { } else {
// start REPL // start REPL
println!("Welcome to MPS interactive mode!"); println!("Welcome to MPS interactive mode!");
println!("Run ?help for usage instructions.");
println!("End a statement with ; to execute it."); println!("End a statement with ; to execute it.");
repl::repl(args) repl::repl(args)
} }

View file

@ -1,12 +1,12 @@
//! Read, Execute, Print Loop functionality //! Read, Execute, Print Loop functionality
use std::io::{self, Write, Read, Stdin}; use std::io::{self, Read, Stdin, Write};
use mps_interpreter::MpsRunner; use mps_interpreter::MpsRunner;
use mps_player::{MpsPlayer, MpsController}; use mps_player::{MpsController, MpsPlayer};
use super::cli::CliArgs;
use super::channel_io::{channel_io, ChannelWriter}; use super::channel_io::{channel_io, ChannelWriter};
use super::cli::CliArgs;
struct ReplState { struct ReplState {
stdin: Stdin, stdin: Stdin,
@ -37,16 +37,17 @@ pub fn repl(args: CliArgs) {
if let Some(playlist_file) = &args.playlist { if let Some(playlist_file) = &args.playlist {
println!("Playlist mode (output: `{}`)", playlist_file); println!("Playlist mode (output: `{}`)", playlist_file);
let mut player = player_builder(); let mut player = player_builder();
let mut playlist_writer = io::BufWriter::new( let mut playlist_writer = io::BufWriter::new(std::fs::File::create(playlist_file).expect(
std::fs::File::create(playlist_file) &format!("Abort: Cannot create writeable file `{}`", playlist_file),
.expect(&format!("Abort: Cannot create writeable file `{}`", playlist_file)) ));
);
read_loop(&args, &mut state, || { read_loop(&args, &mut state, || {
match player.save_m3u8(&mut playlist_writer) { match player.save_m3u8(&mut playlist_writer) {
Ok(_) => {}, Ok(_) => {}
Err(e) => eprintln!("{}", e.message()), Err(e) => eprintln!("{}", e.message()),
} }
playlist_writer.flush().expect("Failed to flush playlist to file"); playlist_writer
.flush()
.expect("Failed to flush playlist to file");
}); });
} else { } else {
println!("Playback mode (output: audio device)"); println!("Playback mode (output: audio device)");
@ -54,7 +55,7 @@ pub fn repl(args: CliArgs) {
read_loop(&args, &mut state, || { read_loop(&args, &mut state, || {
if args.wait { if args.wait {
match ctrl.wait_for_empty() { match ctrl.wait_for_empty() {
Ok(_) => {}, Ok(_) => {}
Err(e) => eprintln!("{}", e.message()), Err(e) => eprintln!("{}", e.message()),
} }
} else { } else {
@ -67,23 +68,34 @@ pub fn repl(args: CliArgs) {
} }
fn read_loop<F: FnMut()>(args: &CliArgs, state: &mut ReplState, mut execute: F) -> ! { fn read_loop<F: FnMut()>(args: &CliArgs, state: &mut ReplState, mut execute: F) -> ! {
let mut read_buf: [u8;1] = [0]; let mut read_buf: [u8; 1] = [0];
prompt(&mut state.line_number, args); prompt(&mut state.line_number, args);
loop { loop {
read_buf[0] = 0; read_buf[0] = 0;
while read_buf[0] == 0 { while read_buf[0] == 0 {
// TODO: enable raw mode (char by char) reading of stdin // TODO: enable raw mode (char by char) reading of stdin
state.stdin.read(&mut read_buf).expect("Failed to read stdin"); state
.stdin
.read(&mut read_buf)
.expect("Failed to read stdin");
} }
match read_buf[0] as char { match read_buf[0] as char {
'\n' => { '\n' => {
state.statement_buf.push(read_buf[0]); state.statement_buf.push(read_buf[0]);
state.writer.write(state.statement_buf.as_slice()) let statement_result = std::str::from_utf8(state.statement_buf.as_slice());
.expect("Failed to write to MPS interpreter"); if statement_result.is_ok() && statement_result.unwrap().starts_with("?") {
execute(); repl_commands(statement_result.unwrap());
state.writer.write(&[';' as u8]).unwrap_or(0);
} else {
state
.writer
.write(state.statement_buf.as_slice())
.expect("Failed to write to MPS interpreter");
execute();
}
state.statement_buf.clear(); state.statement_buf.clear();
prompt(&mut state.line_number, args); prompt(&mut state.line_number, args);
}, }
_ => state.statement_buf.push(read_buf[0]), _ => state.statement_buf.push(read_buf[0]),
} }
} }
@ -95,3 +107,13 @@ fn prompt(line: &mut usize, args: &CliArgs) {
*line += 1; *line += 1;
std::io::stdout().flush().expect("Failed to flush stdout"); std::io::stdout().flush().expect("Failed to flush stdout");
} }
fn repl_commands(command_str: &str) {
let words: Vec<&str> = command_str.split(" ").map(|s| s.trim()).collect();
match words[0] {
"?help" => println!("{}", super::help::help()),
"?function" | "?functions" => println!("{}", super::help::functions()),
"?filter" | "?filters" => println!("{}", super::help::filters()),
_ => println!("Unknown command, try ?help"),
}
}