use crate::header::ModHeader;
#[derive(Clone, Debug, Default)]
pub struct SampleBody {
pub pcm: Vec<i8>,
pub loop_start: u32,
pub loop_length: u32,
pub volume: u8,
pub finetune: i8,
}
impl SampleBody {
pub fn is_looped(&self) -> bool {
self.loop_length > 2
}
}
impl crate::mixer::SampleSource for SampleBody {
fn len(&self) -> usize {
self.pcm.len()
}
fn loop_start(&self) -> usize {
if self.is_looped() {
self.loop_start as usize
} else {
0
}
}
fn loop_end(&self) -> usize {
if self.is_looped() {
(self.loop_start + self.loop_length) as usize
} else {
self.pcm.len()
}
}
fn loop_kind(&self) -> crate::mixer::LoopKind {
if self.is_looped() {
crate::mixer::LoopKind::Forward
} else {
crate::mixer::LoopKind::None
}
}
fn at(&self, idx: usize) -> f32 {
self.pcm.get(idx).copied().unwrap_or(0) as f32 / 128.0
}
}
pub fn extract_samples(header: &ModHeader, bytes: &[u8]) -> Vec<SampleBody> {
let mut out = Vec::with_capacity(header.samples.len());
let mut cursor = header.sample_data_offset();
let end = bytes.len();
for sample in &header.samples {
let declared = sample.length as usize;
let available = end.saturating_sub(cursor);
let take = declared.min(available);
let pcm: Vec<i8> = if take == 0 {
Vec::new()
} else {
bytes[cursor..cursor + take]
.iter()
.map(|&b| b as i8)
.collect()
};
cursor += take;
let (loop_start, loop_length) = if sample.repeat_length > 2 {
(sample.repeat_start, sample.repeat_length)
} else {
(0, 0)
};
out.push(SampleBody {
pcm,
loop_start,
loop_length,
volume: sample.volume,
finetune: sample.finetune,
});
}
out
}
#[cfg(test)]
mod tests {
use super::*;
use crate::header::parse_header;
fn build_minimal_mod_with_sample(pcm: &[i8]) -> Vec<u8> {
let mut out = vec![0u8; crate::header::HEADER_FIXED_SIZE];
out[0..4].copy_from_slice(b"test");
let len_words = (pcm.len() / 2) as u16;
out[20 + 22..20 + 24].copy_from_slice(&len_words.to_be_bytes());
out[20 + 25] = 64;
out[20 + 26..20 + 28].copy_from_slice(&0u16.to_be_bytes());
out[20 + 28..20 + 30].copy_from_slice(&0u16.to_be_bytes());
out[950] = 1;
out[951] = 0x7F;
out[952] = 0; out[1080..1084].copy_from_slice(b"M.K.");
out.extend(std::iter::repeat_n(0u8, 64 * 4 * 4));
out.extend(pcm.iter().map(|&s| s as u8));
out
}
#[test]
fn extracts_signed_bytes() {
let pcm = [10i8, -10, 40, -40, 127, -128];
let bytes = build_minimal_mod_with_sample(&pcm);
let header = parse_header(&bytes).unwrap();
let samples = extract_samples(&header, &bytes);
assert_eq!(samples.len(), 31);
assert_eq!(samples[0].pcm, pcm);
for s in &samples[1..] {
assert!(s.pcm.is_empty());
}
}
#[test]
fn handles_truncated_body() {
let pcm = [1i8, 2, 3, 4];
let mut bytes = build_minimal_mod_with_sample(&pcm);
bytes.truncate(bytes.len() - 2);
let header = parse_header(&bytes).unwrap();
let samples = extract_samples(&header, &bytes);
assert_eq!(samples[0].pcm, [1, 2]);
}
}