use std::fs::File;
use std::io::{self, BufReader, BufWriter, Read, Seek, SeekFrom, Write};
use std::path::Path;
trait WriteSeek: Write + Seek + Send {}
impl<T: Write + Seek + Send> WriteSeek for T {}
trait ReadSeek: Read + Seek + Send {}
impl<T: Read + Seek + Send> ReadSeek for T {}
pub struct WavWriter {
writer: BufWriter<Box<dyn WriteSeek>>,
sample_rate: u32,
channels: u16,
bits_per_sample: u16,
data_size: u32,
}
impl WavWriter {
pub fn create<P: AsRef<Path>>(
path: P,
sample_rate: u32,
channels: u16,
bits_per_sample: u16,
) -> io::Result<Self> {
let file = File::create(path)?;
Self::create_with_writer(Box::new(file), sample_rate, channels, bits_per_sample)
}
fn create_with_writer(
writer: Box<dyn WriteSeek>,
sample_rate: u32,
channels: u16,
bits_per_sample: u16,
) -> io::Result<Self> {
let mut writer = BufWriter::with_capacity(0, writer);
let header = build_wav_header(sample_rate, channels, bits_per_sample, 0);
writer.write_all(&header)?;
Ok(Self {
writer,
sample_rate,
channels,
bits_per_sample,
data_size: 0,
})
}
pub fn create_mono<P: AsRef<Path>>(path: P, sample_rate: u32) -> io::Result<Self> {
Self::create(path, sample_rate, 1, 16)
}
pub fn write_samples(&mut self, samples: &[i16]) -> io::Result<()> {
for &sample in samples {
self.writer.write_all(&sample.to_le_bytes())?;
}
self.data_size += (samples.len() * 2) as u32;
Ok(())
}
pub fn write_bytes(&mut self, data: &[u8]) -> io::Result<()> {
self.writer.write_all(data)?;
self.data_size += data.len() as u32;
Ok(())
}
pub fn duration_secs(&self) -> f64 {
let bytes_per_sample = (self.bits_per_sample / 8) as u32;
let bytes_per_second = self.sample_rate * self.channels as u32 * bytes_per_sample;
self.data_size as f64 / bytes_per_second as f64
}
pub fn finish(mut self) -> io::Result<()> {
self.writer.flush()?;
let file = self.writer.get_mut();
file.seek(SeekFrom::Start(0))?;
let header = build_wav_header(
self.sample_rate,
self.channels,
self.bits_per_sample,
self.data_size,
);
file.write_all(&header)?;
Ok(())
}
}
pub struct WavReader {
reader: BufReader<Box<dyn ReadSeek>>,
pub sample_rate: u32,
pub channels: u16,
pub bits_per_sample: u16,
pub num_samples: u32,
position: u32,
}
impl WavReader {
pub fn open<P: AsRef<Path>>(path: P) -> io::Result<Self> {
let file = File::open(path)?;
Self::open_with_reader(Box::new(file))
}
fn open_with_reader(reader: Box<dyn ReadSeek>) -> io::Result<Self> {
let mut reader = BufReader::new(reader);
let (sample_rate, channels, bits_per_sample, data_size) = parse_wav_header(&mut reader)?;
let bytes_per_sample = (bits_per_sample / 8) as u32;
let num_samples = data_size / (channels as u32 * bytes_per_sample);
Ok(Self {
reader,
sample_rate,
channels,
bits_per_sample,
num_samples,
position: 0,
})
}
pub fn read_samples(&mut self, count: usize) -> io::Result<Vec<i16>> {
let mut samples = Vec::with_capacity(count);
let mut buf = [0u8; 2];
for _ in 0..count {
if self.position >= self.num_samples * self.channels as u32 {
break;
}
if self.reader.read_exact(&mut buf).is_err() {
break;
}
samples.push(i16::from_le_bytes(buf));
self.position += 1;
}
Ok(samples)
}
pub fn read_frame(&mut self, duration_ms: u32) -> io::Result<Vec<i16>> {
let samples_per_frame = (self.sample_rate * duration_ms / 1000) as usize;
self.read_samples(samples_per_frame * self.channels as usize)
}
pub fn duration_secs(&self) -> f64 {
self.num_samples as f64 / self.sample_rate as f64
}
pub fn position_secs(&self) -> f64 {
let samples_read = self.position / self.channels as u32;
samples_read as f64 / self.sample_rate as f64
}
pub fn is_eof(&self) -> bool {
self.position >= self.num_samples * self.channels as u32
}
pub fn seek_secs(&mut self, secs: f64) -> io::Result<()> {
let sample_pos = (secs * self.sample_rate as f64) as u32;
let sample_pos = sample_pos.min(self.num_samples);
let bytes_per_sample = (self.bits_per_sample / 8) as u64;
let byte_pos = sample_pos as u64 * self.channels as u64 * bytes_per_sample;
self.reader.seek(SeekFrom::Start(44 + byte_pos))?;
self.position = sample_pos * self.channels as u32;
Ok(())
}
pub fn rewind(&mut self) -> io::Result<()> {
self.reader.seek(SeekFrom::Start(44))?;
self.position = 0;
Ok(())
}
}
fn build_wav_header(
sample_rate: u32,
channels: u16,
bits_per_sample: u16,
data_size: u32,
) -> Vec<u8> {
let byte_rate = sample_rate * channels as u32 * (bits_per_sample / 8) as u32;
let block_align = channels * (bits_per_sample / 8);
let file_size = 36 + data_size;
let mut header = Vec::with_capacity(44);
header.extend_from_slice(b"RIFF");
header.extend_from_slice(&file_size.to_le_bytes());
header.extend_from_slice(b"WAVE");
header.extend_from_slice(b"fmt ");
header.extend_from_slice(&16u32.to_le_bytes()); header.extend_from_slice(&1u16.to_le_bytes()); header.extend_from_slice(&channels.to_le_bytes());
header.extend_from_slice(&sample_rate.to_le_bytes());
header.extend_from_slice(&byte_rate.to_le_bytes());
header.extend_from_slice(&block_align.to_le_bytes());
header.extend_from_slice(&bits_per_sample.to_le_bytes());
header.extend_from_slice(b"data");
header.extend_from_slice(&data_size.to_le_bytes());
header
}
fn parse_wav_header<R: Read + Seek>(reader: &mut R) -> io::Result<(u32, u16, u16, u32)> {
let mut buf = [0u8; 44];
reader.read_exact(&mut buf)?;
if &buf[0..4] != b"RIFF" {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"Not a RIFF file",
));
}
if &buf[8..12] != b"WAVE" {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"Not a WAVE file",
));
}
if &buf[12..16] != b"fmt " {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"Missing fmt chunk",
));
}
let audio_format = u16::from_le_bytes([buf[20], buf[21]]);
if audio_format != 1 {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"Only PCM format supported",
));
}
let channels = u16::from_le_bytes([buf[22], buf[23]]);
let sample_rate = u32::from_le_bytes([buf[24], buf[25], buf[26], buf[27]]);
let bits_per_sample = u16::from_le_bytes([buf[34], buf[35]]);
if &buf[36..40] != b"data" {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
"Missing data chunk",
));
}
let data_size = u32::from_le_bytes([buf[40], buf[41], buf[42], buf[43]]);
Ok((sample_rate, channels, bits_per_sample, data_size))
}
pub fn generate_tone(
frequency: f64,
duration_ms: u32,
sample_rate: u32,
amplitude: f64,
) -> Vec<i16> {
let num_samples = (sample_rate * duration_ms / 1000) as usize;
let amplitude = (amplitude * i16::MAX as f64) as i16;
(0..num_samples)
.map(|i| {
let t = i as f64 / sample_rate as f64;
let value = (2.0 * std::f64::consts::PI * frequency * t).sin();
(value * amplitude as f64) as i16
})
.collect()
}
pub fn generate_dtmf_tone(digit: char, duration_ms: u32, sample_rate: u32) -> Vec<i16> {
let (low, high) = match digit {
'1' => (697.0, 1209.0),
'2' => (697.0, 1336.0),
'3' => (697.0, 1477.0),
'A' => (697.0, 1633.0),
'4' => (770.0, 1209.0),
'5' => (770.0, 1336.0),
'6' => (770.0, 1477.0),
'B' => (770.0, 1633.0),
'7' => (852.0, 1209.0),
'8' => (852.0, 1336.0),
'9' => (852.0, 1477.0),
'C' => (852.0, 1633.0),
'*' => (941.0, 1209.0),
'0' => (941.0, 1336.0),
'#' => (941.0, 1477.0),
'D' => (941.0, 1633.0),
_ => return Vec::new(),
};
let num_samples = (sample_rate * duration_ms / 1000) as usize;
let amplitude = 0.4 * i16::MAX as f64;
(0..num_samples)
.map(|i| {
let t = i as f64 / sample_rate as f64;
let low_val = (2.0 * std::f64::consts::PI * low * t).sin();
let high_val = (2.0 * std::f64::consts::PI * high * t).sin();
((low_val + high_val) * amplitude / 2.0) as i16
})
.collect()
}
pub fn generate_silence(duration_ms: u32, sample_rate: u32) -> Vec<i16> {
let num_samples = (sample_rate * duration_ms / 1000) as usize;
vec![0i16; num_samples]
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::{BufReader, Cursor, Seek, SeekFrom, Write};
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
struct FailingWriter {
inner: Cursor<Vec<u8>>,
fail_on_write_after: Option<usize>,
fail_on_flush: bool,
fail_on_seek: bool,
write_calls: usize,
}
impl FailingWriter {
fn new(
fail_on_write_after: Option<usize>,
fail_on_flush: bool,
fail_on_seek: bool,
) -> Self {
Self {
inner: Cursor::new(Vec::new()),
fail_on_write_after,
fail_on_flush,
fail_on_seek,
write_calls: 0,
}
}
}
impl Write for FailingWriter {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
if let Some(limit) = self.fail_on_write_after {
if self.write_calls >= limit {
return Err(io::Error::other("forced write error"));
}
}
self.write_calls += 1;
self.inner.write(buf)
}
fn flush(&mut self) -> io::Result<()> {
if self.fail_on_flush {
return Err(io::Error::other("forced flush error"));
}
Ok(())
}
}
impl Seek for FailingWriter {
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
if self.fail_on_seek {
return Err(io::Error::other("forced seek error"));
}
self.inner.seek(pos)
}
}
struct ToggleSeekReader {
inner: Cursor<Vec<u8>>,
fail_seek: Arc<AtomicBool>,
}
impl ToggleSeekReader {
fn new(data: Vec<u8>, fail_seek: Arc<AtomicBool>) -> Self {
Self {
inner: Cursor::new(data),
fail_seek,
}
}
}
impl Read for ToggleSeekReader {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
self.inner.read(buf)
}
}
impl Seek for ToggleSeekReader {
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
if self.fail_seek.load(Ordering::SeqCst) {
return Err(io::Error::other("forced seek error"));
}
self.inner.seek(pos)
}
}
struct ToggleReadReader {
inner: Cursor<Vec<u8>>,
fail_read: Arc<AtomicBool>,
}
impl ToggleReadReader {
fn new(data: Vec<u8>, fail_read: Arc<AtomicBool>) -> Self {
Self {
inner: Cursor::new(data),
fail_read,
}
}
}
impl Read for ToggleReadReader {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
if self.fail_read.load(Ordering::SeqCst) {
return Err(io::Error::other("forced read error"));
}
self.inner.read(buf)
}
}
impl Seek for ToggleReadReader {
fn seek(&mut self, pos: SeekFrom) -> io::Result<u64> {
self.inner.seek(pos)
}
}
fn build_seek_reader() -> (WavReader, Arc<AtomicBool>) {
let header = build_wav_header(8000, 1, 16, 0);
let flag = Arc::new(AtomicBool::new(false));
let reader = ToggleSeekReader::new(header, flag.clone());
let wav_reader = WavReader::open_with_reader(Box::new(reader)).unwrap();
(wav_reader, flag)
}
fn build_read_error_reader() -> (WavReader, Arc<AtomicBool>) {
let data = build_wav_header(8000, 1, 16, 4);
let flag = Arc::new(AtomicBool::new(false));
let reader = ToggleReadReader::new(data, flag.clone());
let wav_reader = WavReader::open_with_reader(Box::new(reader)).unwrap();
(wav_reader, flag)
}
#[test]
fn test_wav_header_build_parse() {
let header = build_wav_header(8000, 1, 16, 16000);
assert_eq!(header.len(), 44);
assert_eq!(&header[0..4], b"RIFF");
assert_eq!(&header[8..12], b"WAVE");
let mut cursor = Cursor::new(header);
let (sample_rate, channels, bits, data_size) = parse_wav_header(&mut cursor).unwrap();
assert_eq!(sample_rate, 8000);
assert_eq!(channels, 1);
assert_eq!(bits, 16);
assert_eq!(data_size, 16000);
}
#[test]
fn test_wav_writer_create_invalid_path() {
let result = WavWriter::create(std::env::temp_dir(), 8000, 1, 16);
assert!(result.is_err());
}
#[test]
fn test_wav_writer_create_pathbuf_success() {
let temp_path = std::env::temp_dir().join("wav_create_pathbuf_success.wav");
std::fs::remove_file(&temp_path).ok();
let writer = WavWriter::create(temp_path.clone(), 8000, 1, 16).unwrap();
writer.finish().unwrap();
std::fs::remove_file(&temp_path).ok();
}
#[test]
fn test_wav_writer_create_header_write_error() {
let writer = FailingWriter::new(Some(0), false, false);
let result = WavWriter::create_with_writer(Box::new(writer), 8000, 1, 16);
assert!(result.is_err());
}
#[test]
fn test_wav_writer_write_samples_error() {
let writer = FailingWriter::new(Some(1), false, false);
let mut wav = WavWriter::create_with_writer(Box::new(writer), 8000, 1, 16).unwrap();
let result = wav.write_samples(&[1, 2]);
assert!(result.is_err());
}
#[test]
fn test_wav_writer_write_bytes_error() {
let writer = FailingWriter::new(Some(1), false, false);
let mut wav = WavWriter::create_with_writer(Box::new(writer), 8000, 1, 16).unwrap();
let result = wav.write_bytes(&[0x01, 0x02]);
assert!(result.is_err());
}
#[test]
fn test_wav_writer_finish_flush_error() {
let writer = FailingWriter::new(None, true, false);
let wav = WavWriter::create_with_writer(Box::new(writer), 8000, 1, 16).unwrap();
let result = wav.finish();
assert!(result.is_err());
}
#[test]
fn test_wav_writer_finish_seek_error() {
let writer = FailingWriter::new(None, false, true);
let wav = WavWriter::create_with_writer(Box::new(writer), 8000, 1, 16).unwrap();
let result = wav.finish();
assert!(result.is_err());
}
#[test]
fn test_wav_writer_finish_write_error() {
let writer = FailingWriter::new(Some(1), false, false);
let wav = WavWriter::create_with_writer(Box::new(writer), 8000, 1, 16).unwrap();
let result = wav.finish();
assert!(result.is_err());
}
#[test]
fn test_wav_reader_open_invalid_path() {
let temp_path = std::env::temp_dir().join("wav_missing_file.wav");
let result = WavReader::open(&temp_path);
assert!(result.is_err());
}
#[test]
fn test_wav_reader_open_invalid_header() {
let temp_path = std::env::temp_dir().join("wav_invalid_header.wav");
{
let mut file = std::fs::File::create(&temp_path).unwrap();
file.write_all(b"NOTWAV").unwrap();
}
let result = WavReader::open(&temp_path);
assert!(result.is_err());
std::fs::remove_file(&temp_path).ok();
}
#[test]
fn test_wav_reader_seek_secs_error() {
let (mut reader, flag) = build_seek_reader();
flag.store(true, Ordering::SeqCst);
let result = reader.seek_secs(0.1);
assert!(result.is_err());
}
#[test]
fn test_wav_reader_seek_secs_success_toggle_reader() {
let (mut reader, _flag) = build_seek_reader();
let result = reader.seek_secs(0.1);
assert!(result.is_ok());
}
#[test]
fn test_wav_reader_read_samples_error() {
let (mut reader, flag) = build_read_error_reader();
reader.rewind().unwrap();
flag.store(true, Ordering::SeqCst);
let result = reader.read_samples(1);
assert!(result.unwrap().is_empty());
}
#[test]
fn test_wav_reader_rewind_error() {
let (mut reader, flag) = build_seek_reader();
flag.store(true, Ordering::SeqCst);
let result = reader.rewind();
assert!(result.is_err());
}
#[test]
fn test_generate_tone() {
let tone = generate_tone(440.0, 100, 8000, 0.5);
assert_eq!(tone.len(), 800);
assert!(tone.iter().any(|&s| s != 0));
let sum: i64 = tone.iter().map(|&s| s as i64).sum();
let avg = sum / tone.len() as i64;
assert!(avg.abs() < 1000); }
#[test]
fn test_generate_dtmf() {
let tone = generate_dtmf_tone('5', 100, 8000);
assert_eq!(tone.len(), 800);
assert!(tone.iter().any(|&s| s != 0));
}
#[test]
fn test_generate_silence() {
let silence = generate_silence(100, 8000);
assert_eq!(silence.len(), 800);
assert!(silence.iter().all(|&s| s == 0));
}
#[test]
fn test_wav_roundtrip() {
let temp_path = std::env::temp_dir().join("test_wav_roundtrip.wav");
{
let mut writer = WavWriter::create_mono(&temp_path, 8000).unwrap();
let samples: Vec<i16> = (0..160).map(|i| (i * 100) as i16).collect();
writer.write_samples(&samples).unwrap();
writer.finish().unwrap();
}
{
let mut reader = WavReader::open(&temp_path).unwrap();
assert_eq!(reader.sample_rate, 8000);
assert_eq!(reader.channels, 1);
assert_eq!(reader.bits_per_sample, 16);
let samples = reader.read_samples(160).unwrap();
assert_eq!(samples.len(), 160);
assert_eq!(samples[0], 0);
assert_eq!(samples[1], 100);
}
std::fs::remove_file(&temp_path).ok();
}
#[test]
fn test_wav_reader_eof() {
let temp_path = std::env::temp_dir().join("test_wav_eof.wav");
{
let mut writer = WavWriter::create_mono(&temp_path, 8000).unwrap();
writer.write_samples(&[1, 2, 3, 4, 5]).unwrap();
writer.finish().unwrap();
}
{
let mut reader = WavReader::open(&temp_path).unwrap();
assert!(!reader.is_eof());
let samples = reader.read_samples(10).unwrap();
assert_eq!(samples.len(), 5);
assert!(reader.is_eof());
}
std::fs::remove_file(&temp_path).ok();
}
#[test]
fn test_wav_reader_truncated_data() {
let temp_path = std::env::temp_dir().join("test_wav_truncated.wav");
{
let mut file = std::fs::File::create(&temp_path).unwrap();
let header = build_wav_header(8000, 1, 16, 2);
file.write_all(&header).unwrap();
file.write_all(&[0xAA]).unwrap();
}
{
let mut reader = WavReader::open(&temp_path).unwrap();
let samples = reader.read_samples(1).unwrap();
assert!(samples.is_empty());
}
std::fs::remove_file(&temp_path).ok();
}
#[test]
fn test_wav_seek() {
let temp_path = std::env::temp_dir().join("test_wav_seek.wav");
{
let mut writer = WavWriter::create_mono(&temp_path, 8000).unwrap();
let samples: Vec<i16> = (0..8000).map(|i| i as i16).collect();
writer.write_samples(&samples).unwrap();
writer.finish().unwrap();
}
{
let mut reader = WavReader::open(&temp_path).unwrap();
assert_eq!(reader.duration_secs(), 1.0);
reader.seek_secs(0.5).unwrap();
assert!((reader.position_secs() - 0.5).abs() < 0.001);
let samples = reader.read_samples(1).unwrap();
assert_eq!(samples[0], 4000);
reader.rewind().unwrap();
assert_eq!(reader.position_secs(), 0.0);
}
std::fs::remove_file(&temp_path).ok();
}
#[test]
fn test_wav_parse_invalid_riff() {
let mut data = vec![0u8; 44];
data[0..4].copy_from_slice(b"XXXX"); data[8..12].copy_from_slice(b"WAVE");
let mut cursor = Cursor::new(data);
let result = parse_wav_header(&mut cursor);
assert!(result.is_err());
}
#[test]
fn test_wav_parse_header_with_boxed_reader() {
let header = build_wav_header(8000, 1, 16, 320);
let reader: Box<dyn ReadSeek> = Box::new(Cursor::new(header));
let mut reader = BufReader::new(reader);
let (rate, channels, bits, data_size) = parse_wav_header(&mut reader).unwrap();
assert_eq!(rate, 8000);
assert_eq!(channels, 1);
assert_eq!(bits, 16);
assert_eq!(data_size, 320);
}
#[test]
fn test_wav_reader_open_with_reader_invalid_riff() {
let mut data = vec![0u8; 44];
data[0..4].copy_from_slice(b"XXXX");
data[8..12].copy_from_slice(b"WAVE");
let reader = Cursor::new(data);
let result = WavReader::open_with_reader(Box::new(reader));
assert!(result.is_err());
}
#[test]
fn test_wav_reader_open_with_reader_invalid_wave() {
let mut data = vec![0u8; 44];
data[0..4].copy_from_slice(b"RIFF");
data[8..12].copy_from_slice(b"XXXX");
let reader = Cursor::new(data);
let result = WavReader::open_with_reader(Box::new(reader));
assert!(result.is_err());
}
#[test]
fn test_wav_reader_open_with_reader_missing_fmt() {
let mut data = vec![0u8; 44];
data[0..4].copy_from_slice(b"RIFF");
data[8..12].copy_from_slice(b"WAVE");
data[12..16].copy_from_slice(b"xxxx");
let reader = Cursor::new(data);
let result = WavReader::open_with_reader(Box::new(reader));
assert!(result.is_err());
}
#[test]
fn test_wav_reader_open_with_reader_non_pcm_format() {
let mut header = build_wav_header(8000, 1, 16, 1000);
header[20] = 3;
header[21] = 0;
let reader = Cursor::new(header);
let result = WavReader::open_with_reader(Box::new(reader));
assert!(result.is_err());
}
#[test]
fn test_wav_reader_open_with_reader_missing_data() {
let mut header = build_wav_header(8000, 1, 16, 1000);
header[36..40].copy_from_slice(b"xxxx");
let reader = Cursor::new(header);
let result = WavReader::open_with_reader(Box::new(reader));
assert!(result.is_err());
}
#[test]
fn test_wav_parse_invalid_wave() {
let mut data = vec![0u8; 44];
data[0..4].copy_from_slice(b"RIFF");
data[8..12].copy_from_slice(b"XXXX"); let mut cursor = Cursor::new(data);
let result = parse_wav_header(&mut cursor);
assert!(result.is_err());
}
#[test]
fn test_wav_parse_missing_fmt() {
let mut data = vec![0u8; 44];
data[0..4].copy_from_slice(b"RIFF");
data[8..12].copy_from_slice(b"WAVE");
data[12..16].copy_from_slice(b"xxxx"); let mut cursor = Cursor::new(data);
let result = parse_wav_header(&mut cursor);
assert!(result.is_err());
}
#[test]
fn test_wav_parse_non_pcm_format() {
let mut header = build_wav_header(8000, 1, 16, 1000);
header[20] = 3; header[21] = 0;
let mut cursor = Cursor::new(header);
let result = parse_wav_header(&mut cursor);
assert!(result.is_err());
}
#[test]
fn test_wav_parse_missing_data() {
let mut data = vec![0u8; 44];
data[0..4].copy_from_slice(b"RIFF");
data[4..8].copy_from_slice(&40u32.to_le_bytes());
data[8..12].copy_from_slice(b"WAVE");
data[12..16].copy_from_slice(b"fmt ");
data[16..20].copy_from_slice(&16u32.to_le_bytes()); data[20..22].copy_from_slice(&1u16.to_le_bytes()); data[22..24].copy_from_slice(&1u16.to_le_bytes()); data[24..28].copy_from_slice(&8000u32.to_le_bytes()); data[28..32].copy_from_slice(&16000u32.to_le_bytes()); data[32..34].copy_from_slice(&2u16.to_le_bytes()); data[34..36].copy_from_slice(&16u16.to_le_bytes()); data[36..40].copy_from_slice(b"xxxx"); let mut cursor = Cursor::new(data);
let result = parse_wav_header(&mut cursor);
assert!(result.is_err());
}
#[test]
fn test_wav_parse_truncated_header() {
let data = vec![0u8; 20];
let mut cursor = Cursor::new(data);
let result = parse_wav_header(&mut cursor);
assert!(result.is_err());
}
#[test]
fn test_wav_stereo_roundtrip() {
let temp_path = std::env::temp_dir().join("test_wav_stereo.wav");
{
let mut writer = WavWriter::create(&temp_path, 44100, 2, 16).unwrap();
let samples: Vec<i16> = (0..200).map(|i| (i * 50) as i16).collect();
writer.write_samples(&samples).unwrap();
assert!(writer.duration_secs() > 0.0);
writer.finish().unwrap();
}
{
let mut reader = WavReader::open(&temp_path).unwrap();
assert_eq!(reader.sample_rate, 44100);
assert_eq!(reader.channels, 2);
assert_eq!(reader.bits_per_sample, 16);
let samples = reader.read_samples(200).unwrap();
assert_eq!(samples.len(), 200);
}
std::fs::remove_file(&temp_path).ok();
}
#[test]
fn test_wav_read_frame() {
let temp_path = std::env::temp_dir().join("test_wav_frame.wav");
{
let mut writer = WavWriter::create_mono(&temp_path, 8000).unwrap();
let samples: Vec<i16> = (0..1600).map(|i| i as i16).collect();
writer.write_samples(&samples).unwrap();
writer.finish().unwrap();
}
{
let mut reader = WavReader::open(&temp_path).unwrap();
let frame = reader.read_frame(20).unwrap();
assert_eq!(frame.len(), 160);
assert_eq!(frame[0], 0);
assert_eq!(frame[159], 159);
}
std::fs::remove_file(&temp_path).ok();
}
#[test]
fn test_wav_write_bytes() {
let temp_path = std::env::temp_dir().join("test_wav_bytes.wav");
{
let mut writer = WavWriter::create_mono(&temp_path, 8000).unwrap();
let bytes: Vec<u8> = vec![0x00, 0x01, 0x00, 0x02]; writer.write_bytes(&bytes).unwrap();
writer.finish().unwrap();
}
{
let mut reader = WavReader::open(&temp_path).unwrap();
let samples = reader.read_samples(2).unwrap();
assert_eq!(samples[0], 256);
assert_eq!(samples[1], 512);
}
std::fs::remove_file(&temp_path).ok();
}
#[test]
fn test_wav_seek_beyond_end() {
let temp_path = std::env::temp_dir().join("test_wav_seek_beyond.wav");
{
let mut writer = WavWriter::create_mono(&temp_path, 8000).unwrap();
let samples: Vec<i16> = vec![100; 800]; writer.write_samples(&samples).unwrap();
writer.finish().unwrap();
}
{
let mut reader = WavReader::open(&temp_path).unwrap();
reader.seek_secs(10.0).unwrap();
assert!(reader.position_secs() <= reader.duration_secs() + 0.001);
}
std::fs::remove_file(&temp_path).ok();
}
#[test]
fn test_generate_dtmf_invalid_digit() {
let tone = generate_dtmf_tone('X', 100, 8000);
assert!(tone.is_empty());
}
#[test]
fn test_generate_dtmf_all_digits() {
let digits = [
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '*', '#', 'A', 'B', 'C', 'D',
];
for digit in digits {
let tone = generate_dtmf_tone(digit, 50, 8000);
assert_eq!(tone.len(), 400); assert!(tone.iter().any(|&s| s != 0));
}
}
#[test]
fn test_generate_tone_zero_amplitude() {
let tone = generate_tone(440.0, 100, 8000, 0.0);
assert_eq!(tone.len(), 800);
assert!(tone.iter().all(|&s| s == 0));
}
#[test]
fn test_generate_tone_various_rates() {
for &rate in &[8000u32, 16000, 44100, 48000] {
let tone = generate_tone(440.0, 100, rate, 0.5);
let expected_len = (rate * 100 / 1000) as usize;
assert_eq!(tone.len(), expected_len);
}
}
}