proper wav + mp3 export
This commit is contained in:
parent
7e5120586f
commit
2eb06b32d0
2 changed files with 122 additions and 27 deletions
|
@ -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 }),
|
||||
)
|
||||
}
|
||||
|
|
145
src/cli/main.rs
145
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<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!(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue