export mode

This commit is contained in:
brevalferrari 2025-06-05 17:47:28 +02:00
parent da38edbfd1
commit c6cde8ffbf
3 changed files with 96 additions and 50 deletions

7
.gitignore vendored
View file

@ -28,4 +28,9 @@ out/
*.data *.data
# Samply # Samply
*.json.gz *.json.gz
# audio files
*.mp3
*.raw
*.wav

View file

@ -268,6 +268,7 @@ pub(super) struct ExportOpts {
#[arg(short, long, value_parser = audio_format_parser)] #[arg(short, long, value_parser = audio_format_parser)]
pub(super) format: AudioFormat, pub(super) format: AudioFormat,
/// Output file [default: stdout] /// Output file [default: stdout]
#[arg(short, long)]
pub(super) output: Option<ClonableFile<true>>, pub(super) output: Option<ClonableFile<true>>,
} }

View file

@ -1,5 +1,10 @@
mod cli; mod cli;
use std::{collections::HashMap, io::read_to_string, iter::once}; use std::{
collections::HashMap,
fs::File,
io::{Cursor, Write, read_to_string, stdout},
iter::once,
};
use anyhow::{Context as _, anyhow}; use anyhow::{Context as _, anyhow};
use bliplib::{ use bliplib::{
@ -9,9 +14,12 @@ use bliplib::{
use clap::Parser as _; use clap::Parser as _;
use cli::Cli; use cli::Cli;
use dasp_sample::Sample; use dasp_sample::Sample;
use hound::{SampleFormat, WavSpec, WavWriter};
use log::{debug, warn}; use log::{debug, warn};
use rodio::{OutputStream, Sink, buffer::SamplesBuffer}; use rodio::{OutputStream, Sink, buffer::SamplesBuffer};
use crate::cli::{ExportOpts, PlayOpts};
fn main() -> anyhow::Result<()> { fn main() -> anyhow::Result<()> {
env_logger::init(); env_logger::init();
let cli = Cli::parse(); let cli = Cli::parse();
@ -25,53 +33,7 @@ fn main() -> anyhow::Result<()> {
let sink = Sink::try_new(&stream_handle).context("Epic audio playback failure")?; let sink = Sink::try_new(&stream_handle).context("Epic audio playback failure")?;
debug!("audio sink acquired"); debug!("audio sink acquired");
let default_variables = [ let samples: Vec<f32> = parse_and_compile(&opts)?
('l', 4f64),
('L', 0.0),
('t', 0.0),
('T', 60.0),
('N', opts.notes().len() as f64),
];
debug!("building parser");
let parser = Parser::new(
opts.notes(),
opts.slopes()
.map(|(s, (v, e))| (s, VariableChange(*v, e.clone())))
.collect::<Vec<_>>(),
opts.variables()
.chain(HashMap::from(default_variables).iter())
.map(|(v, _)| *v)
.collect::<Vec<_>>(),
);
debug!("reading input");
let input = read_to_string(opts.input().get()).context("Failed to read input")?;
debug!("parsing tokens");
let tokens = parser
.parse_all(&input)
.map_err(|e| anyhow!("{e}"))
.context("Failed to parse input")?;
debug!("found {} tokens", tokens.as_ref().len());
if tokens.as_ref().is_empty() {
warn!("0 tokens parsed");
}
debug!("building compiler");
let compiler = Compiler::from(Context::new(
'L'.to_string(),
'n'.to_string(),
opts.variables()
.map(|(a, b)| (a.to_string(), *b))
.chain(default_variables.map(|(c, v)| (c.to_string(), v))),
opts.instrument().clone(),
opts.slopes()
.map(|(_, (a, b))| (a.to_string(), b.clone()))
.chain(once(('L'.to_string(), opts.length().clone()))),
));
debug!("compiling to samples");
let samples: Vec<f32> = compiler
.compile_all(tokens)
.context("Failed to process input tokens")?
.into_iter() .into_iter()
.map(Sample::to_sample) .map(Sample::to_sample)
.collect(); .collect();
@ -85,8 +47,86 @@ fn main() -> anyhow::Result<()> {
debug!("sleeping until end of sink"); debug!("sleeping until end of sink");
sink.sleep_until_end(); sink.sleep_until_end();
} }
Export(_opts) => todo!(), Export(ExportOpts {
playopts,
format: _format,
output,
}) => {
let samples = parse_and_compile(&playopts)?;
let mut buff = Cursor::new(Vec::with_capacity(samples.len() * 8));
{
let mut writer = WavWriter::new(
&mut buff,
WavSpec {
channels: 1,
sample_rate: SAMPLE_RATE as u32,
bits_per_sample: 32,
sample_format: SampleFormat::Float,
},
)
.context("Failed to create WAV writer")?;
for sample in samples {
writer.write_sample(sample.to_sample::<f32>())?;
}
}
let mut writer: Box<dyn Write> = output
.map(File::from)
.map(Box::new)
.map(|b| b as Box<dyn Write>)
.unwrap_or(Box::new(stdout()));
writer.write_all(buff.get_ref())?;
}
Memo(_opts) => todo!(), Memo(_opts) => todo!(),
} }
Ok(()) Ok(())
} }
fn parse_and_compile(opts: &PlayOpts) -> anyhow::Result<Vec<f64>> {
let default_variables = [
('l', 4f64),
('L', 0.0),
('t', 0.0),
('T', 60.0),
('N', opts.notes().len() as f64),
];
debug!("building parser");
let parser = Parser::new(
opts.notes(),
opts.slopes()
.map(|(s, (v, e))| (s, VariableChange(*v, e.clone())))
.collect::<Vec<_>>(),
opts.variables()
.chain(HashMap::from(default_variables).iter())
.map(|(v, _)| *v)
.collect::<Vec<_>>(),
);
debug!("reading input");
let input = read_to_string(opts.input().get()).context("Failed to read input")?;
debug!("parsing tokens");
let tokens = parser
.parse_all(&input)
.map_err(|e| anyhow!("{e}"))
.context("Failed to parse input")?;
debug!("found {} tokens", tokens.as_ref().len());
if tokens.as_ref().is_empty() {
warn!("0 tokens parsed");
}
debug!("building compiler");
let compiler = Compiler::from(Context::new(
'L'.to_string(),
'n'.to_string(),
opts.variables()
.map(|(a, b)| (a.to_string(), *b))
.chain(default_variables.map(|(c, v)| (c.to_string(), v))),
opts.instrument().clone(),
opts.slopes()
.map(|(_, (a, b))| (a.to_string(), b.clone()))
.chain(once(('L'.to_string(), opts.length().clone()))),
));
debug!("compiling to samples");
compiler
.compile_all(tokens)
.context("Failed to process input tokens")
}