use std::path::PathBuf;
use flexaudio_encode::{EncodeError, FlacWriter};
fn tmp_path(name: &str) -> PathBuf {
let dir = PathBuf::from(env!("CARGO_TARGET_TMPDIR"));
std::fs::create_dir_all(&dir).unwrap();
dir.join(name)
}
fn quantize(x: f32) -> i32 {
(x * 32768.0).round().clamp(-32768.0, 32767.0) as i32
}
fn sine_440(rate: u32) -> Vec<f32> {
(0..rate as usize)
.map(|t| 0.5 * (2.0 * std::f32::consts::PI * 440.0 * t as f32 / rate as f32).sin())
.collect()
}
fn stereo_test_signal(rate: u32) -> Vec<f32> {
let sine = sine_440(rate);
let mut out = Vec::with_capacity(sine.len() * 2);
for s in sine {
out.push(s);
out.push(0.25);
}
out
}
fn assert_roundtrip(path: &PathBuf, signal: &[f32], rate: u32, channels: u32) {
let mut reader = claxon::FlacReader::open(path).unwrap();
let info = reader.streaminfo();
assert_eq!(info.sample_rate, rate);
assert_eq!(info.channels, channels);
assert_eq!(info.bits_per_sample, 16);
assert_eq!(
info.samples,
Some((signal.len() / channels as usize) as u64),
"STREAMINFO の総サンプル数"
);
let decoded: Vec<i32> = reader.samples().map(|s| s.unwrap()).collect();
assert_eq!(decoded.len(), signal.len(), "デコードしたサンプル総数");
let tol = 1.0 / 32768.0 + 1e-6;
for (i, (&orig, &dec)) in signal.iter().zip(decoded.iter()).enumerate() {
assert_eq!(dec, quantize(orig), "sample {i}: 量子化値の完全一致");
let restored = dec as f32 / 32768.0;
assert!(
(restored - orig).abs() <= tol,
"sample {i}: orig={orig} restored={restored}"
);
}
}
#[test]
fn roundtrip_stereo_sine_and_constant() {
let rate = 48_000u32;
let path = tmp_path("roundtrip_stereo.flac");
let signal = stereo_test_signal(rate);
let mut writer = FlacWriter::create(&path, rate, 2).unwrap();
for chunk in signal.chunks(960 * 2) {
writer.write_chunk(chunk).unwrap();
}
writer.finalize().unwrap();
assert_roundtrip(&path, &signal, rate, 2);
}
#[test]
fn roundtrip_mono_sine() {
let rate = 44_100u32;
let path = tmp_path("roundtrip_mono.flac");
let signal = sine_440(rate);
let mut writer = FlacWriter::create(&path, rate, 1).unwrap();
for chunk in signal.chunks(1000) {
writer.write_chunk(chunk).unwrap();
}
writer.finalize().unwrap();
assert_roundtrip(&path, &signal, rate, 1);
}
#[test]
fn roundtrip_exact_block_multiple() {
let rate = 16_000u32;
let path = tmp_path("roundtrip_exact.flac");
let signal: Vec<f32> = (0..4096 * 2)
.map(|t| if t % 2 == 0 { 0.125 } else { -0.125 })
.collect();
let mut writer = FlacWriter::create(&path, rate, 1).unwrap();
writer.write_chunk(&signal).unwrap();
writer.finalize().unwrap();
assert_roundtrip(&path, &signal, rate, 1);
}
#[test]
fn roundtrip_tail_shorter_than_16_samples() {
let rate = 16_000u32;
let path = tmp_path("roundtrip_tiny_tail.flac");
let signal: Vec<f32> = (0..4096 + 7)
.map(|t| ((t % 100) as f32 - 50.0) / 128.0)
.collect();
let mut writer = FlacWriter::create(&path, rate, 1).unwrap();
writer.write_chunk(&signal).unwrap();
writer.finalize().unwrap();
assert_roundtrip(&path, &signal, rate, 1);
}
#[test]
fn empty_stream_is_valid_flac() {
let path = tmp_path("empty.flac");
let writer = FlacWriter::create(&path, 48_000, 2).unwrap();
writer.finalize().unwrap();
let mut reader = claxon::FlacReader::open(&path).unwrap();
let info = reader.streaminfo();
assert_eq!(info.sample_rate, 48_000);
assert_eq!(info.channels, 2);
assert_eq!(info.samples, None);
assert_eq!(reader.samples().count(), 0);
}
#[test]
fn drop_without_finalize_closes_best_effort() {
let rate = 16_000u32;
let path = tmp_path("drop_finalize.flac");
let signal: Vec<f32> = vec![0.1; 5000];
{
let mut writer = FlacWriter::create(&path, rate, 1).unwrap();
writer.write_chunk(&signal).unwrap();
}
assert_roundtrip(&path, &signal, rate, 1);
}
#[test]
fn compresses_below_90_percent_of_raw_pcm() {
let rate = 48_000u32;
let path = tmp_path("compression.flac");
let signal = stereo_test_signal(rate);
let mut writer = FlacWriter::create(&path, rate, 2).unwrap();
writer.write_chunk(&signal).unwrap();
writer.finalize().unwrap();
let raw_bytes = signal.len() * 2;
let flac_bytes = std::fs::metadata(&path).unwrap().len() as usize;
assert!(
flac_bytes * 10 < raw_bytes * 9,
"flac={flac_bytes} bytes, raw 16bit PCM={raw_bytes} bytes"
);
}
#[test]
fn rejects_zero_channels() {
let err = FlacWriter::create("/nonexistent-dir/never.flac", 48_000, 0).unwrap_err();
assert!(matches!(err, EncodeError::Unsupported(_)), "{err:?}");
}
#[test]
fn rejects_three_channels() {
let err = FlacWriter::create("/nonexistent-dir/never.flac", 48_000, 3).unwrap_err();
assert!(matches!(err, EncodeError::Unsupported(_)), "{err:?}");
}
#[test]
fn rejects_unsupported_sample_rate() {
let err = FlacWriter::create("/nonexistent-dir/never.flac", 0, 2).unwrap_err();
assert!(matches!(err, EncodeError::Unsupported(_)), "{err:?}");
let err = FlacWriter::create("/nonexistent-dir/never.flac", 192_000, 2).unwrap_err();
assert!(matches!(err, EncodeError::Unsupported(_)), "{err:?}");
}
#[test]
fn rejects_chunk_length_not_multiple_of_channels() {
let path = tmp_path("bad_chunk_len.flac");
let mut writer = FlacWriter::create(&path, 48_000, 2).unwrap();
let err = writer.write_chunk(&[0.0; 3]).unwrap_err();
assert!(matches!(err, EncodeError::Unsupported(_)), "{err:?}");
writer.write_chunk(&[0.0; 4]).unwrap();
writer.finalize().unwrap();
let mut reader = claxon::FlacReader::open(&path).unwrap();
assert_eq!(reader.samples().count(), 4);
}