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>>>(
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 }),
)
}

View file

@ -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<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!(
"{}",
@ -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!(