proper wav + mp3 export

This commit is contained in:
brevalferrari 2025-06-09 01:11:50 +02:00
parent 7e5120586f
commit 2eb06b32d0
2 changed files with 122 additions and 27 deletions

View file

@ -371,8 +371,8 @@ fn audio_format_parser(input: &str) -> Result<AudioFormat, anyhow::Error> {
tag::<&'a str, LocatedSpan<&'a str>, nom::error::Error<LocatedSpan<&'a str>>>( tag::<&'a str, LocatedSpan<&'a str>, nom::error::Error<LocatedSpan<&'a str>>>(
AudioFormatDiscriminants::Wav.into(), AudioFormatDiscriminants::Wav.into(),
), ),
u16.or(success(16)) u16.or(success(32))
.and(value(SampleFormat::Float, char('f')).or(success(SampleFormat::Float))) .and(value(SampleFormat::Int, char('i')).or(success(SampleFormat::Float)))
.map(|(bps, sample_format)| AudioFormat::Wav { bps, sample_format }), .map(|(bps, sample_format)| AudioFormat::Wav { bps, sample_format }),
) )
} }

View file

@ -9,7 +9,7 @@ use std::{
iter::once, iter::once,
}; };
use anyhow::{Context as _, anyhow}; use anyhow::{Context as _, anyhow, bail};
use bliplib::{ use bliplib::{
compiler::{Compiler, Context, SAMPLE_RATE, VariableChange}, compiler::{Compiler, Context, SAMPLE_RATE, VariableChange},
parser::{LocatedVerboseError, Parser}, parser::{LocatedVerboseError, Parser},
@ -19,6 +19,7 @@ use cli::Cli;
use dasp_sample::Sample; use dasp_sample::Sample;
use hound::{SampleFormat, WavSpec, WavWriter}; use hound::{SampleFormat, WavSpec, WavWriter};
use log::{debug, error, info, warn}; use log::{debug, error, info, warn};
use mp3lame_encoder::{Builder as Mp3EncoderBuilder, FlushNoGap, MonoPcm};
use rodio::{OutputStream, Sink, buffer::SamplesBuffer}; use rodio::{OutputStream, Sink, buffer::SamplesBuffer};
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
@ -59,35 +60,129 @@ fn main() -> anyhow::Result<()> {
} }
Export(ExportOpts { Export(ExportOpts {
playopts, playopts,
format: _format, format,
output, output,
}) => { }) => {
let samples = parse_and_compile(&playopts)?; let samples = parse_and_compile(&playopts)?;
info!("result: {} samples", samples.len()); info!("result: {} samples", samples.len());
let mut buff = Cursor::new(Vec::with_capacity(samples.len() * 8)); use cli::AudioFormat::*;
{ match format {
let mut writer = WavWriter::new( Wav { bps, sample_format } => {
&mut buff, let mut buff = Cursor::new(Vec::with_capacity(samples.len() * 8));
WavSpec { {
channels: 1, if sample_format == SampleFormat::Float {
sample_rate: SAMPLE_RATE as u32, if bps != 32 {
bits_per_sample: 32, bail!(
sample_format: SampleFormat::Float, "Sorry, only 32 bps is supported for float samples. Use \"wav32\""
}, );
) }
.context("Failed to create WAV writer")?; let mut writer = WavWriter::new(
for sample in samples { &mut buff,
let sample_f32: f32 = sample.to_sample(); WavSpec {
writer.write_sample(sample_f32)?; 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())?;
} }
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)?;
}
_ => todo!(),
} }
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())?;
} }
Memo(MemoKind::Syntax(s)) => println!( 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() "\t[int, bitrate]\t\t[str, quality from 'w' (\"worst\") to 'b' (\"best\")]\t(default: mp3320g)".into()
} }
Wav => { 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(), Flac => "\t[int, bits per sample]\t\t\t\t\t\t\t\t(default: flac320000)".into(),
Raw => format!( Raw => format!(