1use crate::header::ModHeader;
10
11#[derive(Clone, Debug, Default)]
13pub struct SampleBody {
14 pub pcm: Vec<i8>,
16 pub loop_start: u32,
18 pub loop_length: u32,
21 pub volume: u8,
23 pub finetune: i8,
25}
26
27impl SampleBody {
28 pub fn is_looped(&self) -> bool {
30 self.loop_length > 2
31 }
32}
33
34impl crate::mixer::SampleSource for SampleBody {
35 fn len(&self) -> usize {
36 self.pcm.len()
37 }
38 fn loop_start(&self) -> usize {
39 if self.is_looped() {
40 self.loop_start as usize
41 } else {
42 0
43 }
44 }
45 fn loop_end(&self) -> usize {
46 if self.is_looped() {
47 (self.loop_start + self.loop_length) as usize
48 } else {
49 self.pcm.len()
50 }
51 }
52 fn loop_kind(&self) -> crate::mixer::LoopKind {
53 if self.is_looped() {
54 crate::mixer::LoopKind::Forward
55 } else {
56 crate::mixer::LoopKind::None
57 }
58 }
59 fn at(&self, idx: usize) -> f32 {
60 self.pcm.get(idx).copied().unwrap_or(0) as f32 / 128.0
61 }
62}
63
64pub fn extract_samples(header: &ModHeader, bytes: &[u8]) -> Vec<SampleBody> {
69 let mut out = Vec::with_capacity(header.samples.len());
70 let mut cursor = header.sample_data_offset();
71 let end = bytes.len();
72
73 for sample in &header.samples {
74 let declared = sample.length as usize;
75 let available = end.saturating_sub(cursor);
76 let take = declared.min(available);
77
78 let pcm: Vec<i8> = if take == 0 {
79 Vec::new()
80 } else {
81 bytes[cursor..cursor + take]
83 .iter()
84 .map(|&b| b as i8)
85 .collect()
86 };
87
88 cursor += take;
89
90 let (loop_start, loop_length) = if sample.repeat_length > 2 {
96 let pcm_len = pcm.len() as u32;
97 let start = sample.repeat_start.min(pcm_len);
98 let len = sample.repeat_length.min(pcm_len.saturating_sub(start));
99 if len > 2 {
100 (start, len)
101 } else {
102 (0, 0)
103 }
104 } else {
105 (0, 0)
106 };
107
108 out.push(SampleBody {
109 pcm,
110 loop_start,
111 loop_length,
112 volume: sample.volume,
113 finetune: sample.finetune,
114 });
115 }
116
117 out
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123 use crate::header::parse_header;
124
125 fn build_minimal_mod_with_sample(pcm: &[i8]) -> Vec<u8> {
126 let mut out = vec![0u8; crate::header::HEADER_FIXED_SIZE];
127 out[0..4].copy_from_slice(b"test");
129 let len_words = (pcm.len() / 2) as u16;
131 out[20 + 22..20 + 24].copy_from_slice(&len_words.to_be_bytes());
132 out[20 + 25] = 64;
134 out[20 + 26..20 + 28].copy_from_slice(&0u16.to_be_bytes());
136 out[20 + 28..20 + 30].copy_from_slice(&0u16.to_be_bytes());
138 out[950] = 1;
140 out[951] = 0x7F;
141 out[952] = 0; out[1080..1084].copy_from_slice(b"M.K.");
143 out.extend(std::iter::repeat_n(0u8, 64 * 4 * 4));
145 out.extend(pcm.iter().map(|&s| s as u8));
147 out
148 }
149
150 #[test]
151 fn extracts_signed_bytes() {
152 let pcm = [10i8, -10, 40, -40, 127, -128];
153 let bytes = build_minimal_mod_with_sample(&pcm);
154 let header = parse_header(&bytes).unwrap();
155 let samples = extract_samples(&header, &bytes);
156 assert_eq!(samples.len(), 31);
157 assert_eq!(samples[0].pcm, pcm);
158 for s in &samples[1..] {
160 assert!(s.pcm.is_empty());
161 }
162 }
163
164 #[test]
165 fn handles_truncated_body() {
166 let pcm = [1i8, 2, 3, 4];
167 let mut bytes = build_minimal_mod_with_sample(&pcm);
168 bytes.truncate(bytes.len() - 2);
170 let header = parse_header(&bytes).unwrap();
171 let samples = extract_samples(&header, &bytes);
172 assert_eq!(samples[0].pcm, [1, 2]);
173 }
174}