export mode
This commit is contained in:
parent
da38edbfd1
commit
c6cde8ffbf
3 changed files with 96 additions and 50 deletions
7
.gitignore
vendored
7
.gitignore
vendored
|
@ -28,4 +28,9 @@ out/
|
||||||
*.data
|
*.data
|
||||||
|
|
||||||
# Samply
|
# Samply
|
||||||
*.json.gz
|
*.json.gz
|
||||||
|
|
||||||
|
# audio files
|
||||||
|
*.mp3
|
||||||
|
*.raw
|
||||||
|
*.wav
|
|
@ -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>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
138
src/cli/main.rs
138
src/cli/main.rs
|
@ -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")
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue