1use oxideav_core::{
19 AudioFrame, CodecCapabilities, CodecId, CodecParameters, Error, Frame, Packet, Result,
20};
21use oxideav_core::{CodecInfo, CodecRegistry, Decoder};
22
23use crate::container::OUTPUT_SAMPLE_RATE;
24use crate::header::parse_header;
25use crate::player::{parse_patterns, PlayerState};
26use crate::samples::extract_samples;
27
28pub fn register(reg: &mut CodecRegistry) {
29 let mixed_caps = CodecCapabilities::audio("mod_sw")
30 .with_lossy(false)
31 .with_lossless(true)
32 .with_intra_only(false)
33 .with_max_channels(32)
34 .with_max_sample_rate(OUTPUT_SAMPLE_RATE);
35 reg.register(
36 CodecInfo::new(CodecId::new(crate::CODEC_ID_STR))
37 .capabilities(mixed_caps)
38 .decoder(make_mixed_decoder),
39 );
40
41 let planar_caps = CodecCapabilities::audio("mod_sw_planar")
42 .with_lossy(false)
43 .with_lossless(true)
44 .with_intra_only(false)
45 .with_max_channels(32)
46 .with_max_sample_rate(OUTPUT_SAMPLE_RATE);
47 reg.register(
48 CodecInfo::new(CodecId::new(crate::CODEC_ID_PLANAR_STR))
49 .capabilities(planar_caps)
50 .decoder(make_planar_decoder),
51 );
52
53 let stm_caps = CodecCapabilities::audio("stm_sw")
57 .with_lossy(false)
58 .with_lossless(true)
59 .with_intra_only(false)
60 .with_max_channels(4)
61 .with_max_sample_rate(OUTPUT_SAMPLE_RATE);
62 reg.register(
63 CodecInfo::new(CodecId::new(crate::CODEC_ID_STM_STR))
64 .capabilities(stm_caps)
65 .decoder(make_stm_stub_decoder),
66 );
67
68 let xm_caps = CodecCapabilities::audio("xm_sw")
75 .with_lossy(false)
76 .with_lossless(true)
77 .with_intra_only(false)
78 .with_max_channels(32)
79 .with_max_sample_rate(OUTPUT_SAMPLE_RATE);
80 reg.register(
81 CodecInfo::new(CodecId::new(crate::CODEC_ID_XM_STR))
82 .capabilities(xm_caps)
83 .decoder(make_xm_stub_decoder),
84 );
85}
86
87fn make_mixed_decoder(_params: &CodecParameters) -> Result<Box<dyn Decoder>> {
88 Ok(Box::new(ModDecoder {
89 codec_id: CodecId::new(crate::CODEC_ID_STR),
90 state: DecoderState::AwaitingPacket,
91 }))
92}
93
94fn make_planar_decoder(_params: &CodecParameters) -> Result<Box<dyn Decoder>> {
95 Ok(Box::new(ModPlanarDecoder {
96 codec_id: CodecId::new(crate::CODEC_ID_PLANAR_STR),
97 state: DecoderState::AwaitingPacket,
98 }))
99}
100
101fn make_stm_stub_decoder(_params: &CodecParameters) -> Result<Box<dyn Decoder>> {
102 Ok(Box::new(StmStubDecoder {
103 codec_id: CodecId::new(crate::CODEC_ID_STM_STR),
104 }))
105}
106
107fn make_xm_stub_decoder(_params: &CodecParameters) -> Result<Box<dyn Decoder>> {
108 Ok(Box::new(XmStubDecoder {
109 codec_id: CodecId::new(crate::CODEC_ID_XM_STR),
110 }))
111}
112
113struct XmStubDecoder {
121 codec_id: CodecId,
122}
123
124impl Decoder for XmStubDecoder {
125 fn codec_id(&self) -> &CodecId {
126 &self.codec_id
127 }
128
129 fn send_packet(&mut self, packet: &Packet) -> Result<()> {
130 if !crate::xm::is_xm(&packet.data) {
131 return Err(Error::invalid(
132 "XM: packet does not start with the 'Extended Module: ' banner",
133 ));
134 }
135 crate::xm::parse_header(&packet.data)?;
139 Err(Error::unsupported(
140 "XM playback is not yet wired through the MOD mixer; use \
141 oxideav_mod::xm::parse_header() / parse_patterns() / \
142 parse_instruments() / extract_sample_bodies() directly for \
143 structural access",
144 ))
145 }
146
147 fn receive_frame(&mut self) -> Result<Frame> {
148 Err(Error::Eof)
149 }
150
151 fn flush(&mut self) -> Result<()> {
152 Ok(())
153 }
154
155 fn reset(&mut self) -> Result<()> {
156 Ok(())
157 }
158}
159
160struct StmStubDecoder {
170 codec_id: CodecId,
171}
172
173impl Decoder for StmStubDecoder {
174 fn codec_id(&self) -> &CodecId {
175 &self.codec_id
176 }
177
178 fn send_packet(&mut self, packet: &Packet) -> Result<()> {
179 if !crate::stm::is_stm(&packet.data) {
183 return Err(Error::invalid(
184 "STM: packet does not carry a valid Scream Tracker v1 header",
185 ));
186 }
187 Err(Error::unsupported(
188 "STM playback is not yet wired through the MOD mixer; use \
189 oxideav_mod::stm::parse_header() / parse_patterns() / extract_samples() \
190 directly for structural access",
191 ))
192 }
193
194 fn receive_frame(&mut self) -> Result<Frame> {
195 Err(Error::Eof)
196 }
197
198 fn flush(&mut self) -> Result<()> {
199 Ok(())
200 }
201
202 fn reset(&mut self) -> Result<()> {
203 Ok(())
204 }
205}
206
207struct ModDecoder {
208 codec_id: CodecId,
209 state: DecoderState,
210}
211
212enum DecoderState {
213 AwaitingPacket,
215 Playing {
217 player: Box<PlayerState>,
218 emit_pts: i64,
219 },
220 Done,
222}
223
224const CHUNK_FRAMES: u32 = 1024;
225
226impl Decoder for ModDecoder {
227 fn codec_id(&self) -> &CodecId {
228 &self.codec_id
229 }
230
231 fn send_packet(&mut self, packet: &Packet) -> Result<()> {
232 if !matches!(self.state, DecoderState::AwaitingPacket) {
234 return Err(Error::other(
235 "MOD decoder received a second packet; only one is expected per song",
236 ));
237 }
238 let header = parse_header(&packet.data)?;
239 let samples = extract_samples(&header, &packet.data);
240 let patterns = parse_patterns(&header, &packet.data);
241 let player = PlayerState::new(&header, samples, patterns, OUTPUT_SAMPLE_RATE);
242 self.state = DecoderState::Playing {
243 player: Box::new(player),
244 emit_pts: 0,
245 };
246 Ok(())
247 }
248
249 fn receive_frame(&mut self) -> Result<Frame> {
250 match &mut self.state {
251 DecoderState::AwaitingPacket => Err(Error::NeedMore),
252 DecoderState::Done => Err(Error::Eof),
253 DecoderState::Playing { player, emit_pts } => {
254 let mut pcm = vec![0i16; CHUNK_FRAMES as usize * 2];
256 let produced = player.render(&mut pcm);
257 if produced == 0 {
258 self.state = DecoderState::Done;
259 return Err(Error::Eof);
260 }
261 pcm.truncate(produced * 2);
263
264 let mut bytes = Vec::with_capacity(pcm.len() * 2);
266 for s in &pcm {
267 bytes.extend_from_slice(&s.to_le_bytes());
268 }
269
270 let pts = *emit_pts;
271 *emit_pts += produced as i64;
272 Ok(Frame::Audio(AudioFrame {
273 samples: produced as u32,
274 pts: Some(pts),
275 data: vec![bytes],
276 }))
277 }
278 }
279 }
280
281 fn flush(&mut self) -> Result<()> {
282 if let DecoderState::Playing { .. } = self.state {
283 }
286 Ok(())
287 }
288
289 fn reset(&mut self) -> Result<()> {
290 self.state = DecoderState::AwaitingPacket;
297 Ok(())
298 }
299}
300
301struct ModPlanarDecoder {
306 codec_id: CodecId,
307 state: DecoderState,
308}
309
310impl Decoder for ModPlanarDecoder {
311 fn codec_id(&self) -> &CodecId {
312 &self.codec_id
313 }
314
315 fn send_packet(&mut self, packet: &Packet) -> Result<()> {
316 if !matches!(self.state, DecoderState::AwaitingPacket) {
317 return Err(Error::other(
318 "MOD decoder received a second packet; only one is expected per song",
319 ));
320 }
321 let header = parse_header(&packet.data)?;
322 let samples = extract_samples(&header, &packet.data);
323 let patterns = parse_patterns(&header, &packet.data);
324 let player = PlayerState::new(&header, samples, patterns, OUTPUT_SAMPLE_RATE);
325 self.state = DecoderState::Playing {
326 player: Box::new(player),
327 emit_pts: 0,
328 };
329 Ok(())
330 }
331
332 fn receive_frame(&mut self) -> Result<Frame> {
333 match &mut self.state {
334 DecoderState::AwaitingPacket => Err(Error::NeedMore),
335 DecoderState::Done => Err(Error::Eof),
336 DecoderState::Playing { player, emit_pts } => {
337 let n_ch = player.channels.len();
338 let n_frames = CHUNK_FRAMES as usize;
339
340 let mut bufs: Vec<Vec<i16>> = (0..n_ch).map(|_| vec![0i16; n_frames]).collect();
342 let produced = {
343 let mut views: Vec<&mut [i16]> =
344 bufs.iter_mut().map(|b| b.as_mut_slice()).collect();
345 player.render_per_channel(&mut views, n_frames)
346 };
347 if produced == 0 {
348 self.state = DecoderState::Done;
349 return Err(Error::Eof);
350 }
351
352 let mut planes: Vec<Vec<u8>> = Vec::with_capacity(n_ch);
354 for buf in &bufs {
355 let mut bytes = Vec::with_capacity(produced * 2);
356 for &s in &buf[..produced] {
357 bytes.extend_from_slice(&s.to_le_bytes());
358 }
359 planes.push(bytes);
360 }
361
362 let pts = *emit_pts;
363 *emit_pts += produced as i64;
364 Ok(Frame::Audio(AudioFrame {
365 samples: produced as u32,
366 pts: Some(pts),
367 data: planes,
368 }))
369 }
370 }
371 }
372
373 fn flush(&mut self) -> Result<()> {
374 Ok(())
375 }
376
377 fn reset(&mut self) -> Result<()> {
378 self.state = DecoderState::AwaitingPacket;
379 Ok(())
380 }
381}
382
383#[cfg(test)]
384mod tests {
385 use super::*;
386 use crate::player::tests::synth_square_mod;
387 use oxideav_core::TimeBase;
388
389 #[test]
390 fn decoder_emits_nonsilent_pcm() {
391 let bytes = synth_square_mod();
392 let params = CodecParameters::audio(CodecId::new(crate::CODEC_ID_STR));
393 let mut dec = make_mixed_decoder(¶ms).unwrap();
394 let pkt = Packet::new(0, TimeBase::new(1, OUTPUT_SAMPLE_RATE as i64), bytes);
395 dec.send_packet(&pkt).unwrap();
396
397 let mut total_samples = 0u64;
398 let mut total_nonzero = 0u64;
399 loop {
400 match dec.receive_frame() {
401 Ok(Frame::Audio(a)) => {
402 total_samples += a.samples as u64;
403 let plane = &a.data[0];
405 for chunk in plane.chunks_exact(2) {
406 let s = i16::from_le_bytes([chunk[0], chunk[1]]);
407 if s != 0 {
408 total_nonzero += 1;
409 }
410 }
411 }
412 Ok(_) => unreachable!("MOD emits audio only"),
413 Err(Error::Eof) => break,
414 Err(e) => panic!("unexpected decode error: {e:?}"),
415 }
416 }
417 assert!(
418 total_samples > 1000,
419 "expected substantial sample output, got {total_samples}"
420 );
421 assert!(
422 total_nonzero > 100,
423 "expected non-silent PCM, got {total_nonzero} non-zero samples"
424 );
425 }
426
427 #[test]
428 fn planar_decoder_emits_one_plane_per_channel() {
429 let bytes = synth_square_mod();
430 let params = CodecParameters::audio(CodecId::new(crate::CODEC_ID_PLANAR_STR));
431 let mut dec = make_planar_decoder(¶ms).unwrap();
432 let pkt = Packet::new(0, TimeBase::new(1, OUTPUT_SAMPLE_RATE as i64), bytes);
433 dec.send_packet(&pkt).unwrap();
434
435 let mut got_frame = false;
436 let mut ch0_nonzero = 0u64;
437 let mut other_nonzero = 0u64;
438 loop {
439 match dec.receive_frame() {
440 Ok(Frame::Audio(a)) => {
441 got_frame = true;
442 assert_eq!(a.data.len(), 4, "one plane per MOD channel");
444 let expected_plane_len = a.samples as usize * 2;
445 for plane in &a.data {
446 assert_eq!(plane.len(), expected_plane_len);
447 }
448 for (idx, plane) in a.data.iter().enumerate() {
449 for chunk in plane.chunks_exact(2) {
450 let s = i16::from_le_bytes([chunk[0], chunk[1]]);
451 if s != 0 {
452 if idx == 0 {
453 ch0_nonzero += 1;
454 } else {
455 other_nonzero += 1;
456 }
457 }
458 }
459 }
460 }
461 Ok(_) => unreachable!("MOD emits audio only"),
462 Err(Error::Eof) => break,
463 Err(e) => panic!("unexpected decode error: {e:?}"),
464 }
465 }
466 assert!(got_frame, "planar decoder produced no frames");
467 assert!(
470 ch0_nonzero > 100,
471 "expected channel-0 signal, got {ch0_nonzero} non-zero samples"
472 );
473 assert_eq!(
474 other_nonzero, 0,
475 "expected silence on channels 1..=3 (got {other_nonzero} non-zero samples)"
476 );
477 }
478}