pub const CONSONANT_AMP: i32 = 26;
pub const GENERAL_AMPLITUDE: i32 = 55;
pub fn parse_wav_sample(
addr: u32,
phondata: &[u8],
speed_factor: f64,
wav_param: 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 amp2 = {
let p = if wav_param == 0 { 100 } else { wav_param };
p * 32 / 100
};
let target_len = (((raw.len() as f64) * speed_factor) as usize).max(1);
let wav_length = raw.len();
let len4 = wav_length / 4;
let mut seq: Vec<i32> = Vec::with_capacity(target_len);
if len4 == 0 || target_len <= wav_length {
for i in 0..target_len {
seq.push(raw[i % wav_length]);
}
} else {
let mut remaining = target_len;
let c1 = len4 * 3;
seq.extend_from_slice(&raw[..c1]);
remaining -= c1;
while remaining > len4 * 3 {
seq.extend_from_slice(&raw[len4..len4 * 3]);
remaining -= len4 * 2;
}
if remaining > 0 {
seq.extend_from_slice(&raw[wav_length - remaining..]);
}
}
let out: Vec<i16> = seq
.iter()
.map(|&s| {
let value = (s * CONSONANT_AMP * GENERAL_AMPLITUDE) >> 10;
(value * amp2 / 32).clamp(-32768, 32767) as i16
})
.collect();
Some(out)
}
pub fn compile_wav_sample(samples: &[i16]) -> Vec<u8> {
let wav_length = samples.len() * 2;
let mut out = Vec::with_capacity(4 + wav_length);
out.extend_from_slice(&(wav_length as u16).to_le_bytes());
out.push(0); out.push(0); for &s in samples {
out.extend_from_slice(&s.to_le_bytes());
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn wav_sample_compile_round_trip() {
let samples = vec![1000i16, -2000, 500, 12000, -8000];
let bytes = compile_wav_sample(&samples);
assert_eq!(u16::from_le_bytes([bytes[0], bytes[1]]) as usize, samples.len() * 2);
assert_eq!(bytes[2], 0, "16-bit → wav_scale 0");
for (i, &s) in samples.iter().enumerate() {
assert_eq!(i16::from_le_bytes([bytes[4 + i * 2], bytes[4 + i * 2 + 1]]), s);
}
let decoded = parse_wav_sample(0, &bytes, 1.0, 100).expect("decode compiled wav");
assert_eq!(decoded.len(), samples.len());
}
#[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 long_target_loops_the_middle_not_resamples() {
let n = 16usize;
let mut data = vec![((n * 2) & 0xff) as u8, ((n * 2) >> 8) as u8, 0, 0]; for i in 0..n {
data.extend_from_slice(&((i as i16) * 1000).to_le_bytes());
}
let out = parse_wav_sample(0, &data, 2.5, 100).unwrap();
assert!(out.len() > n, "should be stretched: {}", out.len());
let resets = out.windows(2).filter(|w| w[1] < w[0]).count();
assert!(resets >= 2, "middle must loop (sawtooth), not resample: resets={resets}");
}
#[test]
fn short_target_truncates() {
let n = 16usize;
let mut data = vec![((n * 2) & 0xff) as u8, ((n * 2) >> 8) as u8, 0, 0];
for i in 0..n {
data.extend_from_slice(&((i as i16) * 1000).to_le_bytes());
}
let out = parse_wav_sample(0, &data, 0.5, 100).unwrap();
assert_eq!(out.len(), n / 2, "0.5× → half length");
assert!(out.windows(2).all(|w| w[1] >= w[0]), "truncated ramp stays monotonic: {out:?}");
}
#[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");
}
}