use std::{
fs::{self, File},
io::{BufWriter, Write},
path::{Path, PathBuf},
};
use symphonia::core::{
audio::{Audio, GenericAudioBufferRef},
codecs::audio::AudioDecoderOptions,
errors::Error as SymphoniaError,
formats::{
FormatOptions, TrackType,
probe::Hint,
well_known::{FORMAT_ID_FLAC, FORMAT_ID_WAVE},
},
io::MediaSourceStream,
meta::MetadataOptions,
};
use smol_str::format_smolstr;
use crate::error::{
AllocFailurePayload, ArithmeticOverflowPayload, BoundedDecodePayload, CapExceededPayload, Error,
FileIoPayload, FileOp, InvariantViolationPayload, LayerKeyedPayload, LengthMismatchPayload,
MissingKeyPayload, NonFiniteScalarPayload, OutOfRangePayload, ParsePayload, Result,
};
const I16_DIV: f32 = 32768.0;
pub const MAX_DECODED_SAMPLES: usize = 64 * 1024 * 1024;
pub const MAX_RESAMPLED_SAMPLES: usize = MAX_DECODED_SAMPLES;
pub fn load_audio(path: &Path) -> Result<(Vec<f32>, u32)> {
load_audio_with_cap(path, MAX_DECODED_SAMPLES)
}
pub fn load_audio_into(path: &Path, out: &mut Vec<f32>) -> Result<u32> {
load_audio_into_with_cap(path, out, MAX_DECODED_SAMPLES)
}
pub fn load_audio_with_cap(path: &Path, max_samples: usize) -> Result<(Vec<f32>, u32)> {
let mut out: Vec<f32> = Vec::new();
let sample_rate = load_audio_into_with_cap(path, &mut out, max_samples)?;
Ok((out, sample_rate))
}
pub fn load_audio_with_max_seconds(path: &Path, max_seconds: f32) -> Result<(Vec<f32>, u32)> {
if !max_seconds.is_finite() || max_seconds <= 0.0 {
return Err(Error::OutOfRange(OutOfRangePayload::new(
"load_audio_with_max_seconds: max_seconds",
"must be a finite value > 0",
format!("{max_seconds}"),
)));
}
let mut out: Vec<f32> = Vec::new();
let sr = load_audio_into_unified(path, &mut out, CapStrategy::SrcRateMaxSeconds(max_seconds))?;
Ok((out, sr))
}
#[derive(Debug, Clone, Copy)]
enum CapStrategy {
MaxSamples(usize),
SrcRateMaxSeconds(f32),
}
pub fn load_audio_into_with_cap(
path: &Path,
out: &mut Vec<f32>,
max_samples: usize,
) -> Result<u32> {
load_audio_into_unified(path, out, CapStrategy::MaxSamples(max_samples))
}
fn load_audio_into_unified(path: &Path, out: &mut Vec<f32>, strategy: CapStrategy) -> Result<u32> {
let file = File::open(path).map_err(|e| {
Error::FileIo(FileIoPayload::new(
"load_audio",
FileOp::Open,
::std::path::PathBuf::from(path),
e,
))
})?;
let mss = MediaSourceStream::new(Box::new(file), Default::default());
let mut hint = Hint::new();
if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
hint.with_extension(ext);
}
let mut format = symphonia::default::get_probe()
.probe(
&hint,
mss,
FormatOptions::default(),
MetadataOptions::default(),
)
.map_err(|e| {
Error::Parse(ParsePayload::new(
"load_audio: container probe (unsupported or corrupt format; \
WAV/MP3/FLAC/OGG-Vorbis are supported, M4A/AAC/Opus/WebM are not)",
"audio container",
e,
))
})?;
let format_id = format.format_info().format;
let is_wav = format_id == FORMAT_ID_WAVE;
let is_flac = format_id == FORMAT_ID_FLAC;
let track = format.default_track(TrackType::Audio).ok_or_else(|| {
Error::MissingKey(MissingKeyPayload::new(
"load_audio: container has no audio track",
format_smolstr!("{}", path.display()),
))
})?;
let track_id = track.id;
let track_num_frames = track.num_frames;
let exact_count = is_wav || (is_flac && track_num_frames.is_some());
let codec_params = track.codec_params.as_ref().ok_or_else(|| {
Error::MissingKey(MissingKeyPayload::new(
"load_audio: track has no codec parameters",
format_smolstr!("{}", path.display()),
))
})?;
let audio_params = codec_params.audio().ok_or_else(|| {
Error::InvariantViolation(InvariantViolationPayload::new(
"load_audio: default track",
"must be an audio track (codec_params.audio() returned None)",
))
})?;
let nchannels = audio_params
.channels
.as_ref()
.map(|c| c.count())
.ok_or_else(|| {
Error::MissingKey(MissingKeyPayload::new(
"load_audio: container has no channel layout",
format_smolstr!("{}", path.display()),
))
})?;
if nchannels == 0 {
return Err(Error::OutOfRange(OutOfRangePayload::new(
"load_audio: container channel count",
"must be exactly 1 (mono input required)",
"0",
)));
}
if nchannels != 1 {
return Err(Error::OutOfRange(OutOfRangePayload::new(
"load_audio: container channel count (multi-channel input not supported; \
this API returns mono Vec<f32>; downmix or split channels before calling)",
"must be exactly 1 (mono)",
format_smolstr!("{nchannels}"),
)));
}
let sample_rate = audio_params.sample_rate.ok_or_else(|| {
Error::MissingKey(MissingKeyPayload::new(
"load_audio: container has no sample_rate",
format_smolstr!("{}", path.display()),
))
})?;
let effective_cap = strategy.resolve(sample_rate);
let mut decoder = symphonia::default::get_codecs()
.make_audio_decoder(audio_params, &AudioDecoderOptions::default())
.map_err(|e| {
Error::Parse(ParsePayload::new(
"load_audio: make_audio_decoder failed",
"audio codec",
e,
))
})?;
let header_len_opt = track_num_frames.and_then(|n| usize::try_from(n).ok());
if exact_count
&& let Some(header_len) = header_len_opt
&& header_len > effective_cap
{
return Err(Error::CapExceeded(CapExceededPayload::new(
"load_audio: container-declared sample count exceeds cap (refuse to allocate; \
oversized / crafted inputs require a streaming decoder API, planned follow-up; \
exact-count format: WAV or FLAC-with-STREAMINFO)",
"effective_cap (= min(max_samples, MAX_DECODED_SAMPLES))",
effective_cap as u64,
header_len as u64,
)));
}
let reserve_len = header_len_opt.unwrap_or(0).min(effective_cap);
out.clear();
out.try_reserve_exact(reserve_len).map_err(|e| {
Error::AllocFailure(AllocFailurePayload::new(
"load_audio: reservation",
"samples",
reserve_len as u64,
e,
))
})?;
let cap = if exact_count {
header_len_opt.unwrap_or(effective_cap).min(effective_cap)
} else {
effective_cap
};
loop {
let packet = match format.next_packet() {
Ok(Some(p)) => p,
Ok(None) => break,
Err(SymphoniaError::IoError(e)) if e.kind() == std::io::ErrorKind::UnexpectedEof => {
break;
}
Err(SymphoniaError::ResetRequired) => {
return Err(Error::OutOfRange(OutOfRangePayload::new(
"load_audio: container kind (only single-stream audio is supported; \
chained/multi-segment streams surfaced as ResetRequired)",
"must be a single-stream container",
format_smolstr!("{}", path.display()),
)));
}
Err(e) => {
return Err(Error::Parse(ParsePayload::new(
"load_audio: next_packet failed",
"audio packet",
e,
)));
}
};
if packet.track_id != track_id {
continue;
}
let audio_buf = decoder.decode(&packet).map_err(|e| {
Error::Parse(ParsePayload::new(
"load_audio: decode failed",
"audio packet",
e,
))
})?;
push_samples(&audio_buf, out, cap)?;
}
if exact_count
&& let Some(header_len) = header_len_opt
&& out.len() != header_len
{
return Err(Error::LengthMismatch(LengthMismatchPayload::new(
"load_audio: decoded vs container-header sample count \
(truncated or malformed exact-count file: WAV or FLAC)",
header_len,
out.len(),
)));
}
Ok(sample_rate)
}
impl CapStrategy {
fn resolve(self, sample_rate: u32) -> usize {
match self {
Self::MaxSamples(n) => n.min(MAX_DECODED_SAMPLES),
Self::SrcRateMaxSeconds(max_seconds) => {
let raw = f64::from(sample_rate) * f64::from(max_seconds);
let max_samples = if raw.is_finite() && raw >= 0.0 {
raw.min(usize::MAX as f64) as usize
} else {
MAX_DECODED_SAMPLES
};
max_samples.min(MAX_DECODED_SAMPLES)
}
}
}
}
fn reserve_under_cap(out: &mut Vec<f32>, n: usize, cap: usize) -> Result<()> {
if n > cap - out.len() {
let observed = (out.len() as u64).saturating_add(n as u64);
return Err(Error::BoundedDecode(BoundedDecodePayload::new(
"load_audio: stream produced more than the sample cap",
cap as u64,
observed,
)));
}
let needed = out.len() + n;
if out.capacity() < needed {
let target = needed.max(out.capacity().saturating_mul(2).min(cap));
out
.try_reserve_exact(target - out.len())
.map_err(|_| Error::OutOfMemory)?;
}
Ok(())
}
fn push_samples(buf: &GenericAudioBufferRef<'_>, out: &mut Vec<f32>, cap: usize) -> Result<()> {
fn check_finite(s: f32) -> Result<f32> {
if s.is_finite() {
Ok(s)
} else {
Err(Error::NonFiniteScalar(NonFiniteScalarPayload::new(
"load_audio: non-finite f32 PCM sample",
s as f64,
)))
}
}
fn int_divisor(bits: u32) -> Result<f32> {
Ok(match bits {
8 => 128.0,
16 => I16_DIV,
24 => 8_388_608.0,
32 => 2_147_483_648.0,
n => {
return Err(Error::OutOfRange(OutOfRangePayload::new(
"load_audio: int_divisor bit-width (programmer error)",
"must be one of {8, 16, 24, 32}",
format_smolstr!("{n}"),
)));
}
})
}
match buf {
GenericAudioBufferRef::F32(b) => {
reserve_under_cap(out, b.samples_interleaved(), cap)?;
for s in b.iter_interleaved() {
out.push(check_finite(s)?);
}
}
GenericAudioBufferRef::F64(b) => {
reserve_under_cap(out, b.samples_interleaved(), cap)?;
for s in b.iter_interleaved() {
out.push(check_finite(s as f32)?);
}
}
GenericAudioBufferRef::U8(b) => {
let divisor = int_divisor(8)?;
reserve_under_cap(out, b.samples_interleaved(), cap)?;
for s in b.iter_interleaved() {
let signed = i16::from(s) - 128;
out.push(f32::from(signed) / divisor);
}
}
GenericAudioBufferRef::U16(b) => {
let divisor = int_divisor(16)?;
reserve_under_cap(out, b.samples_interleaved(), cap)?;
for s in b.iter_interleaved() {
let signed = i32::from(s) - 32_768;
out.push(signed as f32 / divisor);
}
}
GenericAudioBufferRef::U24(b) => {
let divisor = int_divisor(24)?;
reserve_under_cap(out, b.samples_interleaved(), cap)?;
for s in b.iter_interleaved() {
let signed = s.inner() as i32 - 0x80_0000;
out.push(signed as f32 / divisor);
}
}
GenericAudioBufferRef::U32(b) => {
let divisor = int_divisor(32)?;
reserve_under_cap(out, b.samples_interleaved(), cap)?;
for s in b.iter_interleaved() {
let signed = s.wrapping_sub(0x8000_0000) as i32;
out.push(signed as f32 / divisor);
}
}
GenericAudioBufferRef::S8(b) => {
let divisor = int_divisor(8)?;
reserve_under_cap(out, b.samples_interleaved(), cap)?;
for s in b.iter_interleaved() {
out.push(f32::from(s) / divisor);
}
}
GenericAudioBufferRef::S16(b) => {
let _ = int_divisor(16)?; reserve_under_cap(out, b.samples_interleaved(), cap)?;
let n = b.samples_interleaved();
let mut buf_i16: Vec<i16> = Vec::new();
buf_i16
.try_reserve_exact(n)
.map_err(|_| Error::OutOfMemory)?;
buf_i16.extend(b.iter_interleaved());
let spare: &mut [core::mem::MaybeUninit<f32>] = out.spare_capacity_mut();
crate::simd::audio::pcm_decode::s16_to_f32_normalize(&mut spare[..n], &buf_i16);
unsafe { out.set_len(out.len() + n) };
}
GenericAudioBufferRef::S24(b) => {
let divisor = int_divisor(24)?;
reserve_under_cap(out, b.samples_interleaved(), cap)?;
let n = b.samples_interleaved();
let mut buf_i32: Vec<i32> = Vec::new();
buf_i32
.try_reserve_exact(n)
.map_err(|_| Error::OutOfMemory)?;
buf_i32.extend(b.iter_interleaved().map(|s| s.inner()));
let inv_scale = 1.0_f32 / divisor;
let spare: &mut [core::mem::MaybeUninit<f32>] = out.spare_capacity_mut();
crate::simd::audio::pcm_decode::s32_to_f32_normalize(&mut spare[..n], &buf_i32, inv_scale);
unsafe { out.set_len(out.len() + n) };
}
GenericAudioBufferRef::S32(b) => {
let divisor = int_divisor(32)?;
reserve_under_cap(out, b.samples_interleaved(), cap)?;
let n = b.samples_interleaved();
let mut buf_i32: Vec<i32> = Vec::new();
buf_i32
.try_reserve_exact(n)
.map_err(|_| Error::OutOfMemory)?;
buf_i32.extend(b.iter_interleaved());
let inv_scale = 1.0_f32 / divisor;
let spare: &mut [core::mem::MaybeUninit<f32>] = out.spare_capacity_mut();
crate::simd::audio::pcm_decode::s32_to_f32_normalize(&mut spare[..n], &buf_i32, inv_scale);
unsafe { out.set_len(out.len() + n) };
}
}
Ok(())
}
#[cfg(test)]
thread_local! {
static FORCE_META_FSYNC_FAILURE: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
}
#[cfg(test)]
pub(crate) fn set_force_meta_fsync_failure(b: bool) {
FORCE_META_FSYNC_FAILURE.with(|cell| cell.set(b));
}
#[inline]
fn save_wav_post_metadata_fsync(meta_file: &File) -> std::io::Result<()> {
#[cfg(test)]
if FORCE_META_FSYNC_FAILURE.with(|cell| cell.get()) {
return Err(std::io::Error::other(
"test-injected meta fsync failure (FORCE_META_FSYNC_FAILURE)",
));
}
meta_file.sync_all()
}
pub fn save_wav(path: &Path, samples: &[f32], sample_rate: u32) -> Result<()> {
let mut scratch: Vec<i16> = Vec::new();
save_wav_into(path, samples, sample_rate, &mut scratch)
}
pub trait Quantizer<Source, Target> {
fn quantize_into(&self, dst: &mut [core::mem::MaybeUninit<Target>], src: &[Source]);
}
#[derive(Debug, Default, Clone, Copy)]
pub struct I16Quantizer;
impl Quantizer<f32, i16> for I16Quantizer {
fn quantize_into(&self, dst: &mut [core::mem::MaybeUninit<i16>], src: &[f32]) {
crate::simd::audio::quantize::f32_to_i16_quantize(dst, src);
}
}
pub fn save_wav_into(
path: &Path,
samples: &[f32],
sample_rate: u32,
scratch: &mut Vec<i16>,
) -> Result<()> {
if sample_rate == 0 {
return Err(Error::InvariantViolation(InvariantViolationPayload::new(
"save_wav: sample_rate",
"must be > 0",
)));
}
const MAX_MONO_I16_SAMPLES: usize = ((u32::MAX - 36) as usize) / 2;
if samples.len() > MAX_MONO_I16_SAMPLES {
return Err(Error::CapExceeded(CapExceededPayload::new(
"save_wav: sample count exceeds the 16-bit WAV total-file-size limit \
(split into multiple files or use a large-WAV variant; \
RF64 / W64 planned follow-up)",
"MAX_MONO_I16_SAMPLES (= (u32::MAX - 36) bytes / 2-bytes-per-sample)",
MAX_MONO_I16_SAMPLES as u64,
samples.len() as u64,
)));
}
const MAX_SAMPLE_RATE_FOR_MONO_I16: u32 = u32::MAX / 2;
if sample_rate > MAX_SAMPLE_RATE_FOR_MONO_I16 {
return Err(Error::OutOfRange(OutOfRangePayload::new(
"save_wav: sample_rate (must keep byte_rate = sample_rate * 2 within u32)",
"must be <= MAX_SAMPLE_RATE_FOR_MONO_I16 (= u32::MAX / 2 = 2147483647)",
format_smolstr!("{sample_rate}"),
)));
}
for (i, &s) in samples.iter().enumerate() {
if !s.is_finite() {
return Err(Error::LayerKeyed(LayerKeyedPayload::new(
format_smolstr!("save_wav: sample index {i}"),
Error::NonFiniteScalar(NonFiniteScalarPayload::new(
"save_wav: sample (cannot quantize)",
s as f64,
)),
)));
}
}
let existing_perms = fs::metadata(path).ok().map(|m| m.permissions());
let existing_xattrs: Option<Vec<(std::ffi::OsString, Vec<u8>)>> = capture_xattrs(path);
const MAX_TEMPFILE_OPEN_RETRIES: u32 = 16;
let (tmp_path, file) = open_excl_tempfile(path, MAX_TEMPFILE_OPEN_RETRIES)?;
let write_result = (|| -> Result<File> {
let mut writer = BufWriter::new(file);
const BITS_PER_SAMPLE: u16 = 16;
const CHANNELS: u16 = 1;
const BLOCK_ALIGN: u16 = CHANNELS * (BITS_PER_SAMPLE / 8);
let data_size: u32 = u32::try_from(samples.len())
.ok()
.and_then(|n| n.checked_mul(u32::from(BLOCK_ALIGN)))
.expect("save_wav: data_size overflow despite MAX_MONO_I16_SAMPLES cap");
let file_size_minus_8: u32 = 36u32
.checked_add(data_size)
.expect("save_wav: file_size overflow despite MAX_MONO_I16_SAMPLES cap");
let byte_rate: u32 = sample_rate
.checked_mul(u32::from(CHANNELS))
.and_then(|n| n.checked_mul(u32::from(BITS_PER_SAMPLE / 8)))
.expect("save_wav: byte_rate overflow despite MAX_SAMPLE_RATE_FOR_MONO_I16 cap");
let mut header = [0u8; 44];
header[0..4].copy_from_slice(b"RIFF");
header[4..8].copy_from_slice(&file_size_minus_8.to_le_bytes());
header[8..12].copy_from_slice(b"WAVE");
header[12..16].copy_from_slice(b"fmt ");
header[16..20].copy_from_slice(&16u32.to_le_bytes());
header[20..22].copy_from_slice(&1u16.to_le_bytes());
header[22..24].copy_from_slice(&CHANNELS.to_le_bytes());
header[24..28].copy_from_slice(&sample_rate.to_le_bytes());
header[28..32].copy_from_slice(&byte_rate.to_le_bytes());
header[32..34].copy_from_slice(&BLOCK_ALIGN.to_le_bytes());
header[34..36].copy_from_slice(&BITS_PER_SAMPLE.to_le_bytes());
header[36..40].copy_from_slice(b"data");
header[40..44].copy_from_slice(&data_size.to_le_bytes());
writer.write_all(&header).map_err(|e| {
Error::FileIo(FileIoPayload::new(
"save_wav: header write failed",
FileOp::Write,
tmp_path.clone(),
e,
))
})?;
scratch.clear();
scratch
.try_reserve_exact(samples.len())
.map_err(|_| Error::OutOfMemory)?;
{
let spare: &mut [core::mem::MaybeUninit<i16>] = scratch.spare_capacity_mut();
debug_assert!(spare.len() >= samples.len());
I16Quantizer.quantize_into(&mut spare[..samples.len()], samples);
}
unsafe { scratch.set_len(samples.len()) };
let quantized: &Vec<i16> = scratch;
if cfg!(target_endian = "little") {
let byte_view: &[u8] = unsafe {
core::slice::from_raw_parts(
quantized.as_ptr().cast::<u8>(),
quantized.len().checked_mul(2).expect(
"save_wav: byte-view length overflow (cap was MAX_MONO_I16_SAMPLES, * 2 ≤ u32::MAX - 36)",
),
)
};
writer.write_all(byte_view).map_err(|e| {
Error::FileIo(FileIoPayload::new(
"save_wav: bulk sample write failed",
FileOp::Write,
tmp_path.clone(),
e,
))
})?;
} else {
const CHUNK: usize = 1024;
let mut buf = [0u8; CHUNK * 2];
let mut idx = 0;
while idx < quantized.len() {
let n = (quantized.len() - idx).min(CHUNK);
for (i, &q) in quantized[idx..idx + n].iter().enumerate() {
buf[i * 2..i * 2 + 2].copy_from_slice(&q.to_le_bytes());
}
writer.write_all(&buf[..n * 2]).map_err(|e| {
Error::FileIo(FileIoPayload::new(
"save_wav: bulk sample write failed",
FileOp::Write,
tmp_path.clone(),
e,
))
})?;
idx += n;
}
}
writer.flush().map_err(|e| {
Error::FileIo(FileIoPayload::new(
"save_wav: flush failed",
FileOp::Flush,
tmp_path.clone(),
e,
))
})?;
let inner = writer.into_inner().map_err(|e| {
Error::FileIo(FileIoPayload::new(
"save_wav: BufWriter::into_inner failed (final flush did not reach underlying File)",
FileOp::Flush,
tmp_path.clone(),
e.into_error(),
))
})?;
inner.sync_all().map_err(|e| {
Error::FileIo(FileIoPayload::new(
"save_wav: sync_all failed",
FileOp::Fsync,
tmp_path.clone(),
e,
))
})?;
Ok(inner)
})();
let meta_file = match write_result {
Ok(f) => f,
Err(err) => {
let _ = fs::remove_file(&tmp_path);
return Err(err);
}
};
if let Some(perms) = existing_perms.clone()
&& let Err(e) = fs::set_permissions(&tmp_path, perms)
{
let _ = fs::remove_file(&tmp_path);
return Err(Error::FileIo(FileIoPayload::new(
"save_wav: set_permissions on tempfile failed",
FileOp::Other("set_permissions"),
tmp_path,
e,
)));
}
if let Some(xattrs) = &existing_xattrs {
restore_xattrs(&tmp_path, xattrs);
}
let sync_result = save_wav_post_metadata_fsync(&meta_file);
if (existing_perms.is_some() || existing_xattrs.is_some())
&& let Err(e) = sync_result
{
drop(meta_file);
let _ = fs::remove_file(&tmp_path);
return Err(Error::FileIo(FileIoPayload::new(
"save_wav: post-metadata sync_all on tempfile failed \
(perms/xattrs were restored but metadata is not yet durable; \
NOT renaming to final destination — see #138)",
FileOp::Fsync,
tmp_path,
e,
)));
}
drop(meta_file);
if let Err(e) = fs::rename(&tmp_path, path) {
let _ = fs::remove_file(&tmp_path);
return Err(Error::FileIo(FileIoPayload::new(
"save_wav: rename tempfile -> destination failed",
FileOp::Rename,
tmp_path,
e,
)));
}
fsync_parent_dir(path);
Ok(())
}
#[cfg(unix)]
fn capture_xattrs(path: &Path) -> Option<Vec<(std::ffi::OsString, Vec<u8>)>> {
if !path.exists() {
return None;
}
let names = xattr::list(path).ok()?;
let mut out: Vec<(std::ffi::OsString, Vec<u8>)> = Vec::new();
let upsert =
|out: &mut Vec<(std::ffi::OsString, Vec<u8>)>, name: std::ffi::OsString, value: Vec<u8>| {
if let Some(slot) = out.iter_mut().find(|(n, _)| n == &name) {
slot.1 = value;
} else {
out.push((name, value));
}
};
for name in names {
if let Ok(Some(value)) = xattr::get(path, &name) {
upsert(&mut out, name, value);
}
}
const EXPLICIT_PROBES: &[&str] = &[
"system.posix_acl_access",
"system.posix_acl_default",
"security.selinux",
"security.capability",
"security.ima",
"security.evm",
];
for &probe in EXPLICIT_PROBES {
if let Ok(Some(value)) = xattr::get(path, probe) {
upsert(&mut out, std::ffi::OsString::from(probe), value);
}
}
Some(out)
}
#[cfg(not(unix))]
fn capture_xattrs(_path: &Path) -> Option<Vec<(std::ffi::OsString, Vec<u8>)>> {
None
}
#[cfg(unix)]
fn restore_xattrs(path: &Path, xattrs: &[(std::ffi::OsString, Vec<u8>)]) {
for (name, value) in xattrs {
let _ = xattr::set(path, name, value);
}
}
#[cfg(not(unix))]
fn restore_xattrs(_path: &Path, _xattrs: &[(std::ffi::OsString, Vec<u8>)]) {}
fn fsync_parent_dir(path: &Path) {
let Some(parent) = path.parent() else {
return;
};
let parent_path: &Path = if parent.as_os_str().is_empty() {
Path::new(".")
} else {
parent
};
if let Ok(dir) = std::fs::OpenOptions::new().read(true).open(parent_path) {
let _ = dir.sync_all();
}
}
fn open_excl_tempfile(final_path: &Path, max_retries: u32) -> Result<(PathBuf, File)> {
use std::{
fs::OpenOptions,
io::ErrorKind,
sync::atomic::{AtomicU64, Ordering},
time::{SystemTime, UNIX_EPOCH},
};
static COUNTER: AtomicU64 = AtomicU64::new(0);
let parent = final_path.parent().unwrap_or_else(|| Path::new("."));
let file_name = final_path
.file_name()
.ok_or_else(|| {
Error::OutOfRange(OutOfRangePayload::new(
"save_wav: destination path",
"must have a file_name component (not a bare directory or `..`)",
format_smolstr!("{}", final_path.display()),
))
})?
.to_string_lossy()
.into_owned();
let pid = std::process::id();
let mut last_err: Option<std::io::Error> = None;
for _ in 0..max_retries {
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0);
let counter = COUNTER.fetch_add(1, Ordering::Relaxed);
let rand = nanos ^ counter.rotate_left(17);
let candidate = parent.join(format!("{file_name}.{pid}.{rand:016x}.tmp"));
match OpenOptions::new()
.write(true)
.create_new(true)
.open(&candidate)
{
Ok(file) => return Ok((candidate, file)),
Err(e) if e.kind() == ErrorKind::AlreadyExists => {
last_err = Some(e);
continue;
}
Err(e) => {
return Err(Error::FileIo(FileIoPayload::new(
"save_wav: tempfile create_new failed",
FileOp::Create,
candidate,
e,
)));
}
}
}
Err(Error::FileIo(FileIoPayload::new(
"save_wav: exhausted tempfile retry budget (all candidate names collided \
with AlreadyExists)",
FileOp::Create,
final_path.to_path_buf(),
last_err.unwrap_or_else(|| {
std::io::Error::new(
std::io::ErrorKind::AlreadyExists,
"tempfile retries exhausted",
)
}),
)))
}
pub fn resample_linear(samples: &[f32], from_rate: u32, to_rate: u32) -> Result<Vec<f32>> {
if from_rate == 0 {
return Err(Error::InvariantViolation(InvariantViolationPayload::new(
"resample_linear: from_rate",
"must be > 0",
)));
}
if to_rate == 0 {
return Err(Error::InvariantViolation(InvariantViolationPayload::new(
"resample_linear: to_rate",
"must be > 0",
)));
}
if samples.is_empty() {
return Ok(Vec::new());
}
if from_rate == to_rate {
if samples.len() > MAX_RESAMPLED_SAMPLES {
return Err(Error::CapExceeded(CapExceededPayload::new(
"resample_linear: output length exceeds cap \
(use chunked resampling or raise the cap manually)",
"MAX_RESAMPLED_SAMPLES",
MAX_RESAMPLED_SAMPLES as u64,
samples.len() as u64,
)));
}
let mut out: Vec<f32> = Vec::new();
out.try_reserve_exact(samples.len()).map_err(|e| {
Error::AllocFailure(AllocFailurePayload::new(
"resample_linear: reservation",
"samples",
samples.len() as u64,
e,
))
})?;
out.extend_from_slice(samples);
return Ok(out);
}
let in_len = samples.len() as u64;
let out_len_u64 = in_len.checked_mul(u64::from(to_rate)).ok_or_else(|| {
Error::ArithmeticOverflow(ArithmeticOverflowPayload::with_operands(
"resample_linear: in_len * to_rate",
"u64",
[("in_len", in_len), ("to_rate", u64::from(to_rate))],
))
})?
/ u64::from(from_rate);
let out_len = usize::try_from(out_len_u64).map_err(|_| {
Error::OutOfRange(OutOfRangePayload::new(
"resample_linear: output length",
"must fit in usize",
format_smolstr!("{out_len_u64}"),
))
})?;
if out_len == 0 {
return Ok(Vec::new());
}
if out_len > MAX_RESAMPLED_SAMPLES {
return Err(Error::CapExceeded(CapExceededPayload::new(
"resample_linear: output length exceeds cap \
(use chunked resampling or raise the cap manually)",
"MAX_RESAMPLED_SAMPLES",
MAX_RESAMPLED_SAMPLES as u64,
out_len as u64,
)));
}
let mut out: Vec<f32> = Vec::new();
out.try_reserve_exact(out_len).map_err(|e| {
Error::AllocFailure(AllocFailurePayload::new(
"resample_linear: reservation",
"samples",
out_len as u64,
e,
))
})?;
let ratio = f64::from(from_rate) / f64::from(to_rate);
let spare = out.spare_capacity_mut();
crate::simd::audio::resample::resample_linear(&mut spare[..out_len], samples, ratio);
unsafe { out.set_len(out_len) };
Ok(out)
}
#[cfg(test)]
mod tests;