ym2149_ay_replayer/
lib.rs

1//! AY file parser and replayer utilities.
2//!
3//! This crate provides building blocks for loading Project AY (`.ay`) files:
4//! - Robust parser that understands the ZXAY/EMUL container format
5//! - Structured representation of metadata, song entries, and memory blocks
6//! - (Upcoming) high-level player that can execute the embedded Z80 players
7
8#![warn(missing_docs)]
9
10pub mod error;
11pub mod format;
12mod machine;
13mod parser;
14pub mod player;
15
16pub use crate::error::{AyError, Result};
17pub use crate::format::{AyBlock, AyFile, AyHeader, AyPoints, AySong, AySongData};
18pub use crate::parser::load_ay;
19pub use crate::player::{AyMetadata, AyPlayer, CPC_UNSUPPORTED_MSG};
20
21// Re-export unified player trait from ym2149-common
22pub use ym2149_common::{ChiptunePlayer, PlaybackMetadata, PlaybackState};
23
24// Backwards compatibility - deprecated alias
25#[allow(deprecated)]
26pub use crate::player::AyPlaybackState;
27
28#[cfg(test)]
29mod tests {
30    use super::*;
31
32    const SPACE_MADNESS: &[u8] = include_bytes!(concat!(
33        env!("CARGO_MANIFEST_DIR"),
34        "/../../examples/ay/SpaceMadness.AY"
35    ));
36    const SHORT_MODULE: &[u8] = include_bytes!(concat!(
37        env!("CARGO_MANIFEST_DIR"),
38        "/../../examples/ay/Short.ay"
39    ));
40
41    #[test]
42    fn parse_space_madness_metadata() {
43        let ay = load_ay(SPACE_MADNESS).expect("failed to parse Space Madness");
44        assert_eq!(ay.header.author, "Alberto Gonzales");
45        assert!(
46            ay.header.misc.starts_with("Unreleased Track"),
47            "unexpected misc '{}'",
48            ay.header.misc
49        );
50        assert_eq!(ay.header.song_count, 1);
51        assert_eq!(ay.songs.len(), 1);
52
53        let song = &ay.songs[0];
54        println!("blocks loaded:");
55        for block in &song.data.blocks {
56            println!("  0x{:04x} ({} bytes)", block.address, block.length);
57        }
58        let cwd = std::env::current_dir().unwrap();
59        println!("cwd: {}", cwd.display());
60        std::fs::write("impact_block.bin", &song.data.blocks[0].data).unwrap();
61        assert_eq!(song.name, "Space Madness");
62        assert_eq!(song.data.channel_map, [0, 1, 2, 3]);
63        assert_eq!(song.data.song_length_50hz, 0);
64        let points = song.data.points.as_ref().expect("points missing");
65        assert_eq!(points.stack, 0x0000);
66        assert_eq!(points.init, 0x801a);
67        assert_eq!(points.interrupt, 0x80ab);
68        assert!(!song.data.blocks.is_empty());
69        assert_eq!(song.data.blocks[0].address, 0x8000);
70    }
71
72    #[test]
73    fn parse_short_module_song_list() {
74        let ay = load_ay(SHORT_MODULE).expect("failed to parse Short.ay");
75        assert_eq!(ay.header.author, "Ivan Roshin, Moscow, 15.09.2000");
76        assert!(
77            ay.header.misc.starts_with("Non-standard note table"),
78            "unexpected misc '{}'",
79            ay.header.misc
80        );
81        assert_eq!(ay.header.song_count, ay.songs.len() as u8);
82        assert_eq!(ay.songs.len(), 1);
83
84        let song = &ay.songs[0];
85        assert_eq!(song.name, "Short module for PHAT0     (ABC)");
86        assert!(
87            song.data.song_length_50hz > 0,
88            "expected non-zero length metadata"
89        );
90        assert!(
91            song.data.blocks.iter().all(|blk| !blk.data.is_empty()),
92            "every block should have data payload"
93        );
94    }
95
96    #[test]
97    fn ay_player_generates_audio() {
98        let (mut player, meta) =
99            crate::player::AyPlayer::load_from_bytes(SPACE_MADNESS, 0).unwrap();
100        assert_eq!(meta.song_name, "Space Madness");
101        player.play().unwrap();
102        let samples = player.generate_samples(1_764);
103        assert_eq!(samples.len(), 1_764);
104        // Ensure we actually produced non-zero samples after running at least one frame.
105        assert!(
106            samples.iter().any(|s| s.abs() > f32::EPSILON),
107            "expected waveform data"
108        );
109    }
110}