diff --git a/src/cli/cli.rs b/src/cli/cli.rs index 9bda8ee..07ae387 100644 --- a/src/cli/cli.rs +++ b/src/cli/cli.rs @@ -371,8 +371,8 @@ fn audio_format_parser(input: &str) -> Result { tag::<&'a str, LocatedSpan<&'a str>, nom::error::Error>>( AudioFormatDiscriminants::Wav.into(), ), - u16.or(success(16)) - .and(value(SampleFormat::Float, char('f')).or(success(SampleFormat::Float))) + u16.or(success(32)) + .and(value(SampleFormat::Int, char('i')).or(success(SampleFormat::Float))) .map(|(bps, sample_format)| AudioFormat::Wav { bps, sample_format }), ) } diff --git a/src/cli/main.rs b/src/cli/main.rs index 4107167..cb95932 100644 --- a/src/cli/main.rs +++ b/src/cli/main.rs @@ -9,7 +9,7 @@ use std::{ iter::once, }; -use anyhow::{Context as _, anyhow}; +use anyhow::{Context as _, anyhow, bail}; use bliplib::{ compiler::{Compiler, Context, SAMPLE_RATE, VariableChange}, parser::{LocatedVerboseError, Parser}, @@ -19,6 +19,7 @@ use cli::Cli; use dasp_sample::Sample; 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; @@ -59,35 +60,129 @@ fn main() -> anyhow::Result<()> { } Export(ExportOpts { playopts, - format: _format, + format, output, }) => { let samples = parse_and_compile(&playopts)?; info!("result: {} samples", samples.len()); - 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 { - let sample_f32: f32 = sample.to_sample(); - writer.write_sample(sample_f32)?; + 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 = output + .map(File::from) + .map(Box::new) + .map(|b| b as Box) + .unwrap_or(Box::new(stdout())); + info!("writing samples to output"); + writer.write_all(buff.get_ref())?; } + 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::(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 = output + .map(File::from) + .map(Box::new) + .map(|b| b as Box) + .unwrap_or(Box::new(stdout())); + info!("writing samples to output"); + writer.write_all(&buff)?; + } + _ => todo!(), } - let mut writer: Box = output - .map(File::from) - .map(Box::new) - .map(|b| b as Box) - .unwrap_or(Box::new(stdout())); - info!("writing samples to output"); - writer.write_all(buff.get_ref())?; } Memo(MemoKind::Syntax(s)) => println!( "{}", @@ -129,7 +224,7 @@ fn main() -> anyhow::Result<()> { "\t[int, bitrate]\t\t[str, quality from 'w' (\"worst\") to 'b' (\"best\")]\t(default: mp3320g)".into() } Wav => { - "\t[int, bytes per sample]\t['f', set sample format to float instead of int]\t(default: wav16f)".into() + "\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!(