pub const CONSONANT_AMP: i32 = 26;
pub const GENERAL_AMPLITUDE: i32 = 55;
pub fn parse_wav_sample(
addr: u32,
phondata: &[u8],
speed_factor: f64,
amp_override: i32,
) -> Option<Vec<i16>> {
let idx = (addr as usize) & 0x7f_ffff;
if idx + 4 > phondata.len() {
return None;
}
let wav_length = (phondata[idx] as usize) | ((phondata[idx + 1] as usize) << 8);
let wav_scale = phondata[idx + 2] as u16;
if wav_length == 0 {
return None;
}
let data = &phondata[idx + 4..];
if data.len() < wav_length {
return None;
}
let raw: Vec<i32> = if wav_scale == 0 {
let n = wav_length / 2;
(0..n)
.map(|i| {
let lo = data[i * 2] as i32;
let hi = (data[i * 2 + 1] as i8) as i32;
lo | (hi << 8)
})
.collect()
} else {
(0..wav_length)
.map(|i| (data[i] as i8) as i32 * wav_scale as i32)
.collect()
};
if raw.is_empty() {
return None;
}
let amp = if amp_override > 0 {
amp_override
} else {
CONSONANT_AMP * GENERAL_AMPLITUDE / 16
};
let target_len = ((raw.len() as f64) * speed_factor) as usize;
let target_len = target_len.max(1);
let resampled: Vec<i16> = (0..target_len)
.map(|i| {
let src_idx = i * raw.len() / target_len;
let s = raw[src_idx.min(raw.len() - 1)];
let out = (s * amp) >> 8;
out.clamp(-32767, 32767) as i16
})
.collect();
Some(resampled)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_wav_data_returns_none() {
let data = vec![0u8, 0u8, 0u8, 0u8]; assert!(parse_wav_sample(0, &data, 1.0, 0).is_none());
}
#[test]
fn parse_8bit_sample() {
let mut data = vec![3u8, 0, 1, 0]; data.extend_from_slice(&[100u8, 200u8, 50u8]); let result = parse_wav_sample(0, &data, 1.0, 0);
assert!(result.is_some());
let pcm = result.unwrap();
assert!(!pcm.is_empty());
}
#[test]
fn parse_16bit_sample() {
let mut data = vec![4u8, 0, 0, 0]; data.extend_from_slice(&(1000i16).to_le_bytes());
data.extend_from_slice(&(-1000i16).to_le_bytes());
let result = parse_wav_sample(0, &data, 1.0, 0);
assert!(result.is_some());
let pcm = result.unwrap();
assert_eq!(pcm.len(), 2);
}
#[test]
fn speed_factor_changes_length() {
let mut data = vec![4u8, 0, 0, 0]; data.extend_from_slice(&(1000i16).to_le_bytes());
data.extend_from_slice(&(-1000i16).to_le_bytes());
let slow = parse_wav_sample(0, &data, 2.0, 0).unwrap();
let fast = parse_wav_sample(0, &data, 0.5, 0).unwrap();
assert!(slow.len() > fast.len(), "slower playback → more samples");
}
}