#[macro_use]
mod cli;
use std::{
borrow::Cow,
collections::{HashMap, HashSet},
fs::File,
io::{Cursor, Write, read_to_string, stdout},
iter::once,
};
use anyhow::{Context as _, anyhow, bail};
use bliplib::{
compiler::{Compiler, Context, SAMPLE_RATE, VariableChange},
parser::{LocatedVerboseError, Parser},
};
use clap::Parser as _;
use cli::Cli;
use dasp_sample::{I24, Sample};
use flacenc::{component::BitRepr, error::Verify};
use hound::{SampleFormat, WavSpec, WavWriter};
use log::{debug, error, info, warn};
use mp3lame_encoder::{Builder as Mp3EncoderBuilder, FlushNoGap, MonoPcm};
use rodio::{OutputStream, Sink, buffer::SamplesBuffer};
use strum::IntoEnumIterator;
use crate::cli::{
AudioFormatDiscriminants, ExportOpts, FastEvalSyntaxSection, MemoKind, PlayOpts,
RawAudioFormat, SyntaxTarget,
};
fn main() -> anyhow::Result<()> {
env_logger::init();
let cli = Cli::parse();
debug!("options: {cli:#?}");
use Cli::*;
match cli {
Check(opts) => {
parse_and_compile(&opts)?;
}
Play(opts) => {
let (_stream, stream_handle) = OutputStream::try_default()
.context("Failed to find (or use) default audio device")?;
info!("output stream acquired");
let sink = Sink::try_new(&stream_handle).context("Epic audio playback failure")?;
info!("audio sink acquired");
let samples: Vec<f32> = parse_and_compile(&opts)?
.into_iter()
.map(Sample::to_sample)
.collect();
info!("result: {} samples", samples.len());
if samples.is_empty() {
warn!("0 samples generated");
}
info!("appending samples to sink");
sink.append(SamplesBuffer::new(1, SAMPLE_RATE as u32, samples));
info!("sleeping until end of sink");
sink.sleep_until_end();
}
Export(ExportOpts {
playopts,
format,
output,
}) => {
let samples = parse_and_compile(&playopts)?;
info!("result: {} samples", samples.len());
use cli::AudioFormat::*;
match format {
Wav { bps, sample_format } => {
let mut buff = Cursor::new(Vec::with_capacity(samples.len() * 8));
{
if sample_format == SampleFormat::Float {
if bps != 32 {
bail!(
"Sorry, only 32 bps is supported for float samples. Use \"wav32\""
);
}
let mut writer = WavWriter::new(
&mut buff,
WavSpec {
channels: 1,
sample_rate: SAMPLE_RATE as u32,
bits_per_sample: bps,
sample_format,
},
)
.context("Failed to create WAV writer")?;
for sample in samples {
let sample_f32: f32 = sample.to_sample();
writer.write_sample(sample_f32)?;
}
} else {
let mut writer = WavWriter::new(
&mut buff,
WavSpec {
channels: 1,
sample_rate: SAMPLE_RATE as u32,
bits_per_sample: bps,
sample_format,
},
)
.context("Failed to create WAV writer")?;
match bps {
32 => {
for sample in samples {
let sample_i32: i32 = sample.to_sample();
writer.write_sample(sample_i32)?;
}
}
16 => {
for sample in samples {
let sample_i32: i16 = sample.to_sample();
writer.write_sample(sample_i32)?;
}
}
8 => {
for sample in samples {
let sample_i32: i8 = sample.to_sample();
writer.write_sample(sample_i32)?;
}
}
_ => bail!(
"for ints, the only valid bps for the wav backend are 8, 16 or 32"
),
}
}
}
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()));
info!("writing samples to output");
writer.write_all(buff.get_ref())?;
}
Flac { bps } => {
let config = flacenc::config::Encoder::default()
.into_verified()
.expect("Config data error.");
let source = flacenc::source::MemSource::from_samples(
samples
.into_iter()
.map(|sample| match bps {
8 => sample.to_sample::<i8>() as i32,
16 => sample.to_sample::<i16>() as i32,
24 => sample.to_sample::<I24>().inner(),
_=> unimplemented!("sorry, the current implementation for the flac encoder doesn't support any other bitrate than 8, 16 or 24.")
})
.collect::<Vec<i32>>()
.as_slice(),
1,
bps,
SAMPLE_RATE.into(),
);
let flac_stream =
flacenc::encode_with_fixed_block_size(&config, source, config.block_size)
.expect("Encode failed.");
let mut sink = flacenc::bitsink::ByteSink::new();
flac_stream
.write(&mut sink)
.context("Failed to write samples to FLAC byte sink")?;
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(sink.as_slice())
.context("Failed to write samples to output")?;
}
Mp3 { bitrate, quality } => {
let buff = {
let mut encoder = Mp3EncoderBuilder::new()
.context("Failed to create MP3 encoder builder")?;
encoder
.set_num_channels(1)
.context("Failed to set MP3 encoder channels")?;
encoder
.set_sample_rate(SAMPLE_RATE.into())
.context("Failed to set MP3 encoder sample rate")?;
encoder
.set_brate(bitrate)
.context("Failed to set MP3 encoder bitrate")?;
encoder
.set_quality(quality)
.context("Failed to set MP3 encoder quality")?;
let mut encoder = encoder
.build()
.context("Failed to initialize MP3 encoder")?;
let input = MonoPcm(samples.as_slice());
let mut output = Vec::with_capacity(
mp3lame_encoder::max_required_buffer_size(input.0.len()),
);
let encoded_size = encoder
.encode(input, output.spare_capacity_mut())
.context("Failed MP3 encoding")?;
unsafe {
output.set_len(output.len().wrapping_add(encoded_size));
}
let encoded_size = encoder
.flush::<FlushNoGap>(output.spare_capacity_mut())
.context("Failed MP3 flushing (don't know what that means)")?;
unsafe {
output.set_len(output.len().wrapping_add(encoded_size));
}
output
};
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()));
info!("writing samples to output");
writer.write_all(&buff)?;
}
Raw(format) => match format {
RawAudioFormat::ALaw => raw_audio::Encoder::new(
output
.map(File::from)
.map(Box::new)
.map(|b| b as Box<dyn Write>)
.unwrap_or(Box::new(stdout())),
raw_audio::pcm::ALaw,
)
.encode(
fon::Audio::<fon::mono::Mono64>::with_f64_buffer(SAMPLE_RATE, samples)
.drain(),
)
.context("Failed to encode to raw audio")?,
RawAudioFormat::F32Be => raw_audio::Encoder::new(
output
.map(File::from)
.map(Box::new)
.map(|b| b as Box<dyn Write>)
.unwrap_or(Box::new(stdout())),
raw_audio::pcm::F32Be,
)
.encode(
fon::Audio::<fon::mono::Mono64>::with_f64_buffer(SAMPLE_RATE, samples)
.drain(),
)
.context("Failed to encode to raw audio")?,
RawAudioFormat::F32Le => raw_audio::Encoder::new(
output
.map(File::from)
.map(Box::new)
.map(|b| b as Box<dyn Write>)
.unwrap_or(Box::new(stdout())),
raw_audio::pcm::F32Le,
)
.encode(
fon::Audio::<fon::mono::Mono64>::with_f64_buffer(SAMPLE_RATE, samples)
.drain(),
)
.context("Failed to encode to raw audio")?,
RawAudioFormat::F64Be => raw_audio::Encoder::new(
output
.map(File::from)
.map(Box::new)
.map(|b| b as Box<dyn Write>)
.unwrap_or(Box::new(stdout())),
raw_audio::pcm::F64Be,
)
.encode(
fon::Audio::<fon::mono::Mono64>::with_f64_buffer(SAMPLE_RATE, samples)
.drain(),
)
.context("Failed to encode to raw audio")?,
RawAudioFormat::F64Le => raw_audio::Encoder::new(
output
.map(File::from)
.map(Box::new)
.map(|b| b as Box<dyn Write>)
.unwrap_or(Box::new(stdout())),
raw_audio::pcm::F64Le,
)
.encode(
fon::Audio::<fon::mono::Mono64>::with_f64_buffer(SAMPLE_RATE, samples)
.drain(),
)
.context("Failed to encode to raw audio")?,
RawAudioFormat::MuLaw => raw_audio::Encoder::new(
output
.map(File::from)
.map(Box::new)
.map(|b| b as Box<dyn Write>)
.unwrap_or(Box::new(stdout())),
raw_audio::pcm::MuLaw,
)
.encode(
fon::Audio::<fon::mono::Mono64>::with_f64_buffer(SAMPLE_RATE, samples)
.drain(),
)
.context("Failed to encode to raw audio")?,
RawAudioFormat::S8 => raw_audio::Encoder::new(
output
.map(File::from)
.map(Box::new)
.map(|b| b as Box<dyn Write>)
.unwrap_or(Box::new(stdout())),
raw_audio::pcm::S8,
)
.encode(
fon::Audio::<fon::mono::Mono64>::with_f64_buffer(SAMPLE_RATE, samples)
.drain(),
)
.context("Failed to encode to raw audio")?,
RawAudioFormat::S16Be => raw_audio::Encoder::new(
output
.map(File::from)
.map(Box::new)
.map(|b| b as Box<dyn Write>)
.unwrap_or(Box::new(stdout())),
raw_audio::pcm::S16Be,
)
.encode(
fon::Audio::<fon::mono::Mono64>::with_f64_buffer(SAMPLE_RATE, samples)
.drain(),
)
.context("Failed to encode to raw audio")?,
RawAudioFormat::S16Le => raw_audio::Encoder::new(
output
.map(File::from)
.map(Box::new)
.map(|b| b as Box<dyn Write>)
.unwrap_or(Box::new(stdout())),
raw_audio::pcm::S16Le,
)
.encode(
fon::Audio::<fon::mono::Mono64>::with_f64_buffer(SAMPLE_RATE, samples)
.drain(),
)
.context("Failed to encode to raw audio")?,
RawAudioFormat::S24Be => raw_audio::Encoder::new(
output
.map(File::from)
.map(Box::new)
.map(|b| b as Box<dyn Write>)
.unwrap_or(Box::new(stdout())),
raw_audio::pcm::S24Be,
)
.encode(
fon::Audio::<fon::mono::Mono64>::with_f64_buffer(SAMPLE_RATE, samples)
.drain(),
)
.context("Failed to encode to raw audio")?,
RawAudioFormat::S24Le => raw_audio::Encoder::new(
output
.map(File::from)
.map(Box::new)
.map(|b| b as Box<dyn Write>)
.unwrap_or(Box::new(stdout())),
raw_audio::pcm::S24Le,
)
.encode(
fon::Audio::<fon::mono::Mono64>::with_f64_buffer(SAMPLE_RATE, samples)
.drain(),
)
.context("Failed to encode to raw audio")?,
RawAudioFormat::S32Be => raw_audio::Encoder::new(
output
.map(File::from)
.map(Box::new)
.map(|b| b as Box<dyn Write>)
.unwrap_or(Box::new(stdout())),
raw_audio::pcm::S32Be,
)
.encode(
fon::Audio::<fon::mono::Mono64>::with_f64_buffer(SAMPLE_RATE, samples)
.drain(),
)
.context("Failed to encode to raw audio")?,
RawAudioFormat::S32Le => raw_audio::Encoder::new(
output
.map(File::from)
.map(Box::new)
.map(|b| b as Box<dyn Write>)
.unwrap_or(Box::new(stdout())),
raw_audio::pcm::S32Le,
)
.encode(
fon::Audio::<fon::mono::Mono64>::with_f64_buffer(SAMPLE_RATE, samples)
.drain(),
)
.context("Failed to encode to raw audio")?,
RawAudioFormat::U8 => raw_audio::Encoder::new(
output
.map(File::from)
.map(Box::new)
.map(|b| b as Box<dyn Write>)
.unwrap_or(Box::new(stdout())),
raw_audio::pcm::U8,
)
.encode(
fon::Audio::<fon::mono::Mono64>::with_f64_buffer(SAMPLE_RATE, samples)
.drain(),
)
.context("Failed to encode to raw audio")?,
RawAudioFormat::U16Be => raw_audio::Encoder::new(
output
.map(File::from)
.map(Box::new)
.map(|b| b as Box<dyn Write>)
.unwrap_or(Box::new(stdout())),
raw_audio::pcm::U16Be,
)
.encode(
fon::Audio::<fon::mono::Mono64>::with_f64_buffer(SAMPLE_RATE, samples)
.drain(),
)
.context("Failed to encode to raw audio")?,
RawAudioFormat::U16Le => raw_audio::Encoder::new(
output
.map(File::from)
.map(Box::new)
.map(|b| b as Box<dyn Write>)
.unwrap_or(Box::new(stdout())),
raw_audio::pcm::U16Le,
)
.encode(
fon::Audio::<fon::mono::Mono64>::with_f64_buffer(SAMPLE_RATE, samples)
.drain(),
)
.context("Failed to encode to raw audio")?,
RawAudioFormat::U24Be => raw_audio::Encoder::new(
output
.map(File::from)
.map(Box::new)
.map(|b| b as Box<dyn Write>)
.unwrap_or(Box::new(stdout())),
raw_audio::pcm::U24Be,
)
.encode(
fon::Audio::<fon::mono::Mono64>::with_f64_buffer(SAMPLE_RATE, samples)
.drain(),
)
.context("Failed to encode to raw audio")?,
RawAudioFormat::U24Le => raw_audio::Encoder::new(
output
.map(File::from)
.map(Box::new)
.map(|b| b as Box<dyn Write>)
.unwrap_or(Box::new(stdout())),
raw_audio::pcm::U24Le,
)
.encode(
fon::Audio::<fon::mono::Mono64>::with_f64_buffer(SAMPLE_RATE, samples)
.drain(),
)
.context("Failed to encode to raw audio")?,
RawAudioFormat::U32Be => raw_audio::Encoder::new(
output
.map(File::from)
.map(Box::new)
.map(|b| b as Box<dyn Write>)
.unwrap_or(Box::new(stdout())),
raw_audio::pcm::U32Be,
)
.encode(
fon::Audio::<fon::mono::Mono64>::with_f64_buffer(SAMPLE_RATE, samples)
.drain(),
)
.context("Failed to encode to raw audio")?,
RawAudioFormat::U32Le => raw_audio::Encoder::new(
output
.map(File::from)
.map(Box::new)
.map(|b| b as Box<dyn Write>)
.unwrap_or(Box::new(stdout())),
raw_audio::pcm::U32Le,
)
.encode(
fon::Audio::<fon::mono::Mono64>::with_f64_buffer(SAMPLE_RATE, samples)
.drain(),
)
.context("Failed to encode to raw audio")?,
},
}
}
Memo(MemoKind::Syntax(s)) => println!(
"{}",
match s {
SyntaxTarget::Blip => include_str!("../../doc/language-design.txt"),
SyntaxTarget::Expressions(FastEvalSyntaxSection::Functions) =>
include_str!("../../doc/fasteval/functions.txt"),
SyntaxTarget::Expressions(FastEvalSyntaxSection::Literals) =>
include_str!("../../doc/fasteval/literals.txt"),
SyntaxTarget::Expressions(FastEvalSyntaxSection::Ops) =>
include_str!("../../doc/fasteval/operators.txt"),
}
),
Memo(MemoKind::Examples { n }) => {
let mut examples = include_str!("../../doc/examples.txt")
.lines()
.filter_map(|l| l.split_once(':'));
match n {
None => {
for (id, (name, _)) in examples.enumerate() {
println!("{id}\t{name}")
}
}
Some(id) => println!(
"{}",
examples
.nth(id.into())
.context("example not found")?
.1
.trim_start()
),
}
}
Memo(MemoKind::Formats) => {
for discriminant in AudioFormatDiscriminants::iter() {
use AudioFormatDiscriminants::*;
let ext: Cow<'static, str> = match discriminant {
Mp3 => {
"\t[int, bitrate]\t\t[str, quality from 'w' (\"worst\") to 'b' (\"best\")]\t(default: mp3320g)".into()
}
Wav => {
"\t[int, bytes per sample]\t['i', set sample format to int instead of float]\t(default: wav32)".into()
}
Flac => "\t[int, bits per sample]\t\t\t\t\t\t\t\t(default: flac320000)".into(),
Raw => format!(
"\t[str, subformat]\t\t\t\t\t\t\t\t(default: rawmulaw)\nraw subformats include: {:#?}",
RawAudioFormat::iter()
.map(Into::into)
.collect::<Vec<&'static str>>()
).into(),
};
println!("* {}{}", Into::<&'static str>::into(discriminant), ext)
}
}
}
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),
];
info!("building parser");
let parser = Parser::new(
opts.notes(),
opts.slopes()
.map(|(s, (v, e))| (s, VariableChange(*v, e.clone())))
.collect::<Vec<_>>(),
default_variables
.iter()
.map(|(v, _)| *v)
.chain(opts.variables().map(|(v, _)| *v))
.collect::<HashSet<_>>()
.into_iter()
.collect::<Vec<_>>(),
);
info!("reading input");
let input = read_to_string(opts.input().get()).context("Failed to read input")?;
info!("parsing tokens");
let tokens = parser
.parse_all(&input)
.map_err(|e| match e {
nom::Err::Incomplete(n) => {
anyhow!("nom parsers said the input was incomplete and needed {n:?} bytes")
}
nom::Err::Error(LocatedVerboseError { location, error })
| nom::Err::Failure(LocatedVerboseError { location, error }) => error
.unwrap_or(anyhow!("input did not match any known grammar (typo?)"))
.context(format!(
"line {line} column {column} (at \"{at}\")",
line = location.location_line(),
column = location.get_utf8_column(),
at = {
if location.len() > 10 {
location
.chars()
.take(10)
.chain("...".chars())
.collect::<String>()
} else if location.is_empty() {
String::from("EOF")
} else {
location.to_string()
}
}
)),
})
.context("Failed to parse input")?;
info!("found {} tokens", tokens.as_ref().len());
if tokens.as_ref().is_empty() {
warn!("0 tokens parsed");
}
debug!("tokens: {:#?}", tokens.as_ref());
info!("building compiler");
let compiler = Compiler::from(Context::new(
'L'.to_string(),
'n'.to_string(),
default_variables
.into_iter()
.chain(opts.variables().map(|(a, b)| (*a, *b)))
.map(|(c, v)| (c.to_string(), v))
.collect::<HashMap<_, _>>(),
opts.instrument().clone(),
opts.slopes()
.map(|(_, (a, b))| (a.to_string(), b.clone()))
.chain(once(('L'.to_string(), opts.length().clone()))),
));
info!("compiling to samples");
compiler
.compile_all(tokens)
.inspect(|v| {
let is_nan = |sample: &f64| sample.abs().is_nan();
if v.iter().all(is_nan) {
error!("🎉 All your samples are NaN, you got yourself a \"Not a Song\" (NaS)!")
} else if v.iter().any(is_nan) {
error!("Waiter! There's a NaN in my samples!");
}
})
.context("Failed to compile tokens to samples")
}