simple playback feature
This commit is contained in:
parent
3168370a37
commit
bb8e150a30
4 changed files with 81 additions and 15 deletions
|
@ -14,7 +14,7 @@ name = "bliplib"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = { version = "1.0", optional = true }
|
anyhow = { version = "1.0", optional = true }
|
||||||
cfg-if = "1"
|
cfg-if = "1"
|
||||||
clap = { version = "4.5.38", features = ["derive"], optional = true }
|
clap = { version = "4.5.39", features = ["derive"], optional = true }
|
||||||
derive-new = "0.7"
|
derive-new = "0.7"
|
||||||
derive_builder = "0.20"
|
derive_builder = "0.20"
|
||||||
derive_wrapper = "0.1"
|
derive_wrapper = "0.1"
|
||||||
|
@ -29,10 +29,12 @@ nom_locate = "5.0"
|
||||||
raw_audio = { version = "0.0", optional = true }
|
raw_audio = { version = "0.0", optional = true }
|
||||||
strum = { version = "0.27", features = ["derive"] }
|
strum = { version = "0.27", features = ["derive"] }
|
||||||
thiserror = "2.0"
|
thiserror = "2.0"
|
||||||
|
rodio = { version = "0.20", default-features = false, optional = true }
|
||||||
|
dasp_sample = { version = "0", optional = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["bin", "all-formats"]
|
default = ["bin", "all-formats"]
|
||||||
bin = ["anyhow", "clap"]
|
bin = ["anyhow", "clap", "rodio", "dasp_sample"]
|
||||||
all-formats = ["mp3", "wav", "flac", "raw"]
|
all-formats = ["mp3", "wav", "flac", "raw"]
|
||||||
mp3 = ["mp3lame-encoder"]
|
mp3 = ["mp3lame-encoder"]
|
||||||
wav = ["hound"]
|
wav = ["hound"]
|
||||||
|
|
|
@ -6,7 +6,7 @@ use std::{
|
||||||
fs::File,
|
fs::File,
|
||||||
io::{self, Cursor, Read, stdin},
|
io::{self, Cursor, Read, stdin},
|
||||||
ops::Not,
|
ops::Not,
|
||||||
str::{Bytes, FromStr},
|
str::FromStr,
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
|
@ -47,7 +47,7 @@ pub(super) enum Cli {
|
||||||
|
|
||||||
#[derive(Parser, Clone, Getters)]
|
#[derive(Parser, Clone, Getters)]
|
||||||
#[cfg_attr(debug_assertions, derive(Debug))]
|
#[cfg_attr(debug_assertions, derive(Debug))]
|
||||||
#[getset(get)]
|
#[getset(get = "pub(super)")]
|
||||||
pub(super) struct PlayOpts {
|
pub(super) struct PlayOpts {
|
||||||
/// Use this sheet music [default: stdin]
|
/// Use this sheet music [default: stdin]
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
|
@ -72,7 +72,7 @@ pub(super) struct PlayOpts {
|
||||||
/// Add a slope expression named NAME which mutates the VARIABLE with the result of EXPR each frame
|
/// Add a slope expression named NAME which mutates the VARIABLE with the result of EXPR each frame
|
||||||
#[arg(short, long = "slope", value_name = "NAME:VARIABLE=EXPR", value_parser = parse_key_tuple::<LetterString, Letter, Expression>)]
|
#[arg(short, long = "slope", value_name = "NAME:VARIABLE=EXPR", value_parser = parse_key_tuple::<LetterString, Letter, Expression>)]
|
||||||
#[getset(skip)]
|
#[getset(skip)]
|
||||||
slopes: Vec<(String, (LetterString, Expression))>,
|
slopes: Vec<(LetterString, (char, Expression))>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlayOpts {
|
impl PlayOpts {
|
||||||
|
@ -84,13 +84,15 @@ impl PlayOpts {
|
||||||
self.macros.iter().map(|(c, s)| (c.as_ref(), s))
|
self.macros.iter().map(|(c, s)| (c.as_ref(), s))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn slopes(&self) -> impl Iterator<Item = (&String, (&String, &Expression))> {
|
pub(super) fn slopes(&self) -> impl Iterator<Item = (&String, (&char, &Expression))> {
|
||||||
self.slopes.iter().map(|(id, (s, e))| (id, (s.as_ref(), e)))
|
self.slopes
|
||||||
|
.iter()
|
||||||
|
.map(|(name, (v, e))| (name.as_ref(), (v, e)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl InputGroup {
|
impl InputGroup {
|
||||||
pub(super) fn get<'a>(&'a self) -> Box<dyn Read> {
|
pub(super) fn get(&self) -> Box<dyn Read> {
|
||||||
self.input
|
self.input
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|i| Box::new(i.clone().0) as Box<dyn Read>)
|
.map(|i| Box::new(i.clone().0) as Box<dyn Read>)
|
||||||
|
|
|
@ -1,13 +1,55 @@
|
||||||
mod cli;
|
mod cli;
|
||||||
use clap::Parser;
|
use std::io::read_to_string;
|
||||||
use cli::Cli;
|
|
||||||
|
|
||||||
fn main() {
|
use anyhow::{Context as _, anyhow};
|
||||||
|
use bliplib::{
|
||||||
|
compiler::{Compiler, Context, SAMPLE_RATE, VariableChange},
|
||||||
|
parser::Parser,
|
||||||
|
};
|
||||||
|
use clap::Parser as _;
|
||||||
|
use cli::Cli;
|
||||||
|
use dasp_sample::Sample;
|
||||||
|
use rodio::{OutputStream, Sink, buffer::SamplesBuffer};
|
||||||
|
|
||||||
|
fn main() -> anyhow::Result<()> {
|
||||||
let cli = Cli::parse();
|
let cli = Cli::parse();
|
||||||
use Cli::*;
|
use Cli::*;
|
||||||
match cli {
|
match cli {
|
||||||
Play(play_opts) => todo!(),
|
Play(opts) => {
|
||||||
|
let (_stream, stream_handle) = OutputStream::try_default()
|
||||||
|
.context("Failed to find (or use) default audio device")?;
|
||||||
|
let sink = Sink::try_new(&stream_handle).context("Epic audio playback failure")?;
|
||||||
|
|
||||||
|
let parser = Parser::new(
|
||||||
|
opts.notes(),
|
||||||
|
opts.slopes()
|
||||||
|
.map(|(s, (v, e))| (s, VariableChange(*v, e.clone())))
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
opts.variables().map(|(v, _)| *v).collect::<Vec<_>>(),
|
||||||
|
);
|
||||||
|
let input = read_to_string(opts.input().get()).context("Failed to read input")?;
|
||||||
|
let tokens = parser
|
||||||
|
.parse_all(&input)
|
||||||
|
.map_err(|e| anyhow!("{e}"))
|
||||||
|
.context("Failed to parse input")?;
|
||||||
|
|
||||||
|
let compiler = Compiler::from(Context::new(
|
||||||
|
opts.variables().map(|(a, b)| (*a, *b)),
|
||||||
|
opts.instrument().clone(),
|
||||||
|
opts.slopes().map(|(_, (a, b))| (*a, b.clone())),
|
||||||
|
));
|
||||||
|
let samples: Vec<f32> = compiler
|
||||||
|
.compile_all(tokens)
|
||||||
|
.context("Failed to process input tokens")?
|
||||||
|
.into_iter()
|
||||||
|
.map(Sample::to_sample)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
sink.append(SamplesBuffer::new(1, SAMPLE_RATE as u32, samples));
|
||||||
|
sink.sleep_until_end();
|
||||||
|
}
|
||||||
Export(export_opts) => todo!(),
|
Export(export_opts) => todo!(),
|
||||||
Memo(memo) => todo!(),
|
Memo(memo) => todo!(),
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,9 +14,9 @@ use thiserror::Error;
|
||||||
|
|
||||||
cfg_if! {
|
cfg_if! {
|
||||||
if #[cfg(test)] {
|
if #[cfg(test)] {
|
||||||
const SAMPLE_RATE: u16 = 10;
|
pub const SAMPLE_RATE: u16 = 10;
|
||||||
} else {
|
} else {
|
||||||
const SAMPLE_RATE: u16 = 48000;
|
pub const SAMPLE_RATE: u16 = 48000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,6 +24,26 @@ cfg_if! {
|
||||||
#[cfg_attr(test, derive(Debug))]
|
#[cfg_attr(test, derive(Debug))]
|
||||||
pub struct TokenVec(pub(crate) Vec<Box<dyn Token>>);
|
pub struct TokenVec(pub(crate) Vec<Box<dyn Token>>);
|
||||||
|
|
||||||
|
impl IntoIterator for TokenVec {
|
||||||
|
type Item = Box<dyn Token>;
|
||||||
|
type IntoIter = <Vec<Box<(dyn Token + 'static)>> as IntoIterator>::IntoIter;
|
||||||
|
fn into_iter(self) -> Self::IntoIter {
|
||||||
|
self.0.into_iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Token for Box<dyn Token> {
|
||||||
|
fn apply(&self, context: Context) -> Result<Context, CompilerError> {
|
||||||
|
self.as_ref().apply(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Type for Box<dyn Token> {
|
||||||
|
fn type_id(&self) -> TypeId {
|
||||||
|
self.as_ref().type_id()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub trait Type {
|
pub trait Type {
|
||||||
fn type_id(&self) -> TypeId;
|
fn type_id(&self) -> TypeId;
|
||||||
}
|
}
|
||||||
|
@ -386,7 +406,7 @@ impl Context {
|
||||||
|
|
||||||
#[derive(From)]
|
#[derive(From)]
|
||||||
#[cfg_attr(test, derive(Debug, PartialEq))]
|
#[cfg_attr(test, derive(Debug, PartialEq))]
|
||||||
struct Compiler(Context);
|
pub struct Compiler(Context);
|
||||||
|
|
||||||
impl Compiler {
|
impl Compiler {
|
||||||
pub fn step(self, token: impl Token) -> Result<Self, CompilerError> {
|
pub fn step(self, token: impl Token) -> Result<Self, CompilerError> {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue