From bb8e150a30dd33c6a49b220f3d6f48b6c0e99d31 Mon Sep 17 00:00:00 2001 From: brevalferrari Date: Mon, 2 Jun 2025 21:00:54 +0200 Subject: [PATCH] simple playback feature --- Cargo.toml | 6 ++++-- src/cli/cli.rs | 14 ++++++++------ src/cli/main.rs | 50 +++++++++++++++++++++++++++++++++++++++++++++---- src/compiler.rs | 26 ++++++++++++++++++++++--- 4 files changed, 81 insertions(+), 15 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 40bcf44..fec5756 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ name = "bliplib" [dependencies] anyhow = { version = "1.0", optional = true } 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_builder = "0.20" derive_wrapper = "0.1" @@ -29,10 +29,12 @@ nom_locate = "5.0" raw_audio = { version = "0.0", optional = true } strum = { version = "0.27", features = ["derive"] } thiserror = "2.0" +rodio = { version = "0.20", default-features = false, optional = true } +dasp_sample = { version = "0", optional = true } [features] default = ["bin", "all-formats"] -bin = ["anyhow", "clap"] +bin = ["anyhow", "clap", "rodio", "dasp_sample"] all-formats = ["mp3", "wav", "flac", "raw"] mp3 = ["mp3lame-encoder"] wav = ["hound"] diff --git a/src/cli/cli.rs b/src/cli/cli.rs index eaeb75f..8c331dd 100644 --- a/src/cli/cli.rs +++ b/src/cli/cli.rs @@ -6,7 +6,7 @@ use std::{ fs::File, io::{self, Cursor, Read, stdin}, ops::Not, - str::{Bytes, FromStr}, + str::FromStr, }; use anyhow::anyhow; @@ -47,7 +47,7 @@ pub(super) enum Cli { #[derive(Parser, Clone, Getters)] #[cfg_attr(debug_assertions, derive(Debug))] -#[getset(get)] +#[getset(get = "pub(super)")] pub(super) struct PlayOpts { /// Use this sheet music [default: stdin] #[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 #[arg(short, long = "slope", value_name = "NAME:VARIABLE=EXPR", value_parser = parse_key_tuple::)] #[getset(skip)] - slopes: Vec<(String, (LetterString, Expression))>, + slopes: Vec<(LetterString, (char, Expression))>, } impl PlayOpts { @@ -84,13 +84,15 @@ impl PlayOpts { self.macros.iter().map(|(c, s)| (c.as_ref(), s)) } - pub(super) fn slopes(&self) -> impl Iterator { - self.slopes.iter().map(|(id, (s, e))| (id, (s.as_ref(), e))) + pub(super) fn slopes(&self) -> impl Iterator { + self.slopes + .iter() + .map(|(name, (v, e))| (name.as_ref(), (v, e))) } } impl InputGroup { - pub(super) fn get<'a>(&'a self) -> Box { + pub(super) fn get(&self) -> Box { self.input .as_ref() .map(|i| Box::new(i.clone().0) as Box) diff --git a/src/cli/main.rs b/src/cli/main.rs index e0307e7..a793424 100644 --- a/src/cli/main.rs +++ b/src/cli/main.rs @@ -1,13 +1,55 @@ mod cli; -use clap::Parser; -use cli::Cli; +use std::io::read_to_string; -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(); use 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::>(), + opts.variables().map(|(v, _)| *v).collect::>(), + ); + 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 = 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!(), Memo(memo) => todo!(), } + Ok(()) } diff --git a/src/compiler.rs b/src/compiler.rs index 26893cf..782f7a4 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -14,9 +14,9 @@ use thiserror::Error; cfg_if! { if #[cfg(test)] { - const SAMPLE_RATE: u16 = 10; + pub const SAMPLE_RATE: u16 = 10; } else { - const SAMPLE_RATE: u16 = 48000; + pub const SAMPLE_RATE: u16 = 48000; } } @@ -24,6 +24,26 @@ cfg_if! { #[cfg_attr(test, derive(Debug))] pub struct TokenVec(pub(crate) Vec>); +impl IntoIterator for TokenVec { + type Item = Box; + type IntoIter = > as IntoIterator>::IntoIter; + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl Token for Box { + fn apply(&self, context: Context) -> Result { + self.as_ref().apply(context) + } +} + +impl Type for Box { + fn type_id(&self) -> TypeId { + self.as_ref().type_id() + } +} + pub trait Type { fn type_id(&self) -> TypeId; } @@ -386,7 +406,7 @@ impl Context { #[derive(From)] #[cfg_attr(test, derive(Debug, PartialEq))] -struct Compiler(Context); +pub struct Compiler(Context); impl Compiler { pub fn step(self, token: impl Token) -> Result {