spectrusty_formats/
ay.rs

1/*
2    Copyright (C) 2020-2022  Rafal Michalski
3
4    This file is part of SPECTRUSTY, a Rust library for building emulators.
5
6    For the full copyright notice, see the lib.rs file.
7*/
8//! **AY** file format parser and player initializer. See: [ProjectAY](https://www.worldofspectrum.org/projectay/).
9use core::fmt::{self, Write};
10use core::num::NonZeroU16;
11use core::ops::Deref;
12use core::convert::{TryFrom, AsRef};
13use core::ptr::NonNull;
14use core::marker::PhantomPinned;
15use std::borrow::Cow;
16use std::pin::Pin;
17use std::io;
18
19#[allow(unused_imports)]
20use log::{error, warn, info, debug, trace};
21
22use memchr::memchr;
23use nom::bytes::complete::tag;
24use nom::combinator::{iterator, map};
25use nom::error::{ErrorKind, context, ParseError, ContextError};
26use nom::multi::many1;
27use nom::number::complete::{be_u16, be_u8};
28use nom::sequence::tuple;
29use nom::{Offset, IResult, Err};
30
31use spectrusty_core::z80emu::{Cpu, Prefix, Reg8, CpuFlags, InterruptMode};
32// use crate::cpu_debug::print_debug_memory;
33use spectrusty_core::memory::ZxMemory;
34
35/// This is the main type produced by methods of this module.
36pub type PinAyFile = Pin<Box<AyFile>>;
37
38/// This type is being ever created only as [PinAyFile] to prevent destructing and moving data out.
39///
40/// The `AyFile` struct uses pointers referencing self-owned data under the hood.
41/// Users may inspect its content safely and initialize the player with it.
42/// See: [AyFile::initialize_player].
43///
44/// This struct can't be used to construct *AY* files.
45pub struct AyFile {
46    /// The original parsed data owned by this struct.
47    pub raw: Box<[u8]>,
48    /// *AY* file meta-data.
49    pub meta: AyMeta,
50    /// The list of *AY* songs.
51    pub songs: Box<[AySong]>,
52    _pin: PhantomPinned,
53}
54
55/// The type of a parsed *AY* file meta-data.
56#[derive(Debug)]
57pub struct AyMeta {
58    /// *AY* file version.
59    pub file_version:   u8,
60    /// Required player minimum version.
61    pub player_version: u8,
62    /// Does the *AY* file require a special player written in MC68k code?
63    pub special_player: bool,
64    /// The *AY* file author's name.
65    pub author:         AyString,
66    /// Miscellaneous text.
67    pub misc:           AyString,
68    /// Index of the first song.
69    pub first_song:     u8,
70}
71
72/// The type of a parsed *AY* file's song.
73#[derive(Debug)]
74pub struct AySong {
75    /// The name of the song.
76    pub name:        AyString,
77    /// Mapping of AY-3-891x channel A to the Amiga channel.
78    pub chan_a:      u8,
79    /// Mapping of AY-3-891x channel B to the Amiga channel.
80    pub chan_b:      u8,
81    /// Mapping of AY-3-891x channel C to the Amiga channel.
82    pub chan_c:      u8,
83    /// Mapping of AY-3-891x noise to the Amiga channel.
84    pub noise:       u8,
85    /// The duration of the song in frames.
86    pub song_duration: u16,
87    /// The fade duration of the song in frames.
88    pub fade_duration: u16,
89    /// The content loaded into the `Z80` registers: `A,B,D,H,IXh,IYh`.
90    pub hi_reg:      u8,
91    /// The content loaded into the `Z80` registers: `F,C,E,L,IXl,IYl`.
92    pub lo_reg:      u8,
93    /// The content loaded into the `Z80` `SP` register.
94    pub stack:       u16,
95    /// The address in 64kb memory of the initialization routine.
96    pub init:        u16,
97    /// The address in 64kb memory of the interrupt routine.
98    pub interrupt:   u16,
99    /// The list of song's memory blocks.
100    pub blocks:      Box<[AySongBlock]>
101}
102
103/// The type of a parsed *AY* song's memory block.
104#[derive(Debug)]
105pub struct AySongBlock {
106    /// A target 64kb memory address of this block.
107    pub address: u16,
108    /// A block of data.
109    pub data: AyBlob,
110}
111
112/// The type of a parsed *AY* song's blocks of data.
113pub struct AyBlob(NonNull<[u8]>);
114/// The type of a parsed *AY* file's strings.
115pub struct AyString(NonNull<[u8]>);
116
117impl fmt::Debug for AyBlob {
118    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
119        write!(f, "AyBlob: {{ {} }}", self.len())
120    }
121}
122
123impl fmt::Debug for AyString {
124    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
125        write!(f, "AyString: {:?}", self.to_str_lossy())
126    }
127}
128
129impl fmt::Display for AyString {
130    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
131        write!(f, "{}", self.to_str_lossy())
132    }
133}
134
135impl fmt::Debug for AyFile {
136    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
137        write!(f, "AyFile {{ raw: ({:?}), meta: {:?}, songs: {:?} }}", self.raw.len(), self.meta, self.songs)
138    }
139}
140
141impl AsRef<[u8]> for AyBlob {
142    fn as_ref(&self) -> &[u8] {
143        self.as_slice()
144    }
145}
146
147impl AsRef<[u8]> for AyString {
148    fn as_ref(&self) -> &[u8] {
149        self.as_slice()
150    }
151}
152
153impl Deref for AyBlob {
154    type Target = [u8];
155    fn deref(&self) -> &[u8] {
156        self.as_slice()
157    }
158}
159
160impl AyBlob {
161    fn new(slice: &[u8]) -> Self {
162        AyBlob(NonNull::from(slice))
163    }
164
165    /// Returns a reference to data.
166    pub fn as_slice(&self) -> &[u8] {
167        unsafe { self.0.as_ref() } // creating of this type is private so it will be only made pinned
168    }
169}
170
171impl AyString {
172    fn new(slice: &[u8]) -> Self {
173        AyString(NonNull::from(slice))
174    }
175
176    /// Returns a reference to the string as an array of bytes.
177    pub fn as_slice(&self) -> &[u8] {
178        unsafe { self.0.as_ref() } // creating of this type is private so it will be only made pinned
179    }
180
181    /// Returns a string reference if the underlying bytes can form a proper UTF-8 string.
182    pub fn to_str(&self) -> Result<&str, core::str::Utf8Error> {
183        core::str::from_utf8(self.as_slice())
184    }
185
186    /// Returns a reference to a `str` if the underlying bytes can form a proper UTF-8 string.
187    /// Otherwise, returns a [String], including invalid characters.
188    pub fn to_str_lossy(&self) -> Cow<str> {
189        String::from_utf8_lossy(self.as_slice())
190    }
191}
192
193static PLAYER_ONE: &[u8] = &[
194/* 0000:*/ 0xF3,           /* di                  */
195/* 0001:*/ 0xCD,0x00,0x00, /* call 0000H -> init  */
196/* 0004:*/ 0xED,0x5E,      /* im   2     :loop1   */
197/* 0006:*/ 0xFB,           /* ei                  */
198/* 0007:*/ 0x76,           /* halt                */
199/* 0008:*/ 0x18,0xFA,      /* jr   0004H -> loop1 */
200];
201static PLAYER_TWO: &[u8] = &[
202/* 0000:*/ 0xF3,           /* di                      */
203/* 0001:*/ 0xCD,0x00,0x00, /* call 0000H -> init      */
204/* 0004:*/ 0xED,0x56,      /* im   1     :loop1       */
205/* 0006:*/ 0xFB,           /* ei                      */
206/* 0007:*/ 0x76,           /* halt                    */
207/* 0008:*/ 0xCD,0x00,0x00, /* call 0000H -> interrupt */
208/* 000B:*/ 0x18,0xF7,      /* jr   0004H -> loop1     */
209];
210const PLAYER_INIT_OFFSET: usize = 2;
211const PLAYER_TWO_INTERRUPT_OFFSET: usize = 9;
212
213impl AyFile {
214    /// Initializes `memory` and the `cpu` registers, creates a player routine, and loads song data into `memory`.
215    /// Provide `song_index` of the desired song from this file to be played.
216    ///
217    /// # Panics
218    /// * If `song_index` is larger or equal to the number of contained songs.
219    ///   You may get the number of songs by invoking `.songs.len()` method.
220    /// * If a special player is required. See [AyMeta].
221    /// * If the capacity of provided memory is less than 64kb.
222    pub fn initialize_player<C, M>(&self, cpu: &mut C, memory: &mut M, song_index: usize)
223    where C: Cpu, M: ZxMemory
224    {
225        if self.meta.special_player {
226            panic!("can't initialize file with a special player");
227        }
228        let song = &self.songs[song_index];
229        debug!("loading song: ({}) {}", song_index, song.name.to_str_lossy());
230        let rawmem = memory.mem_mut();
231        for p in rawmem[0x0000..0x0100].iter_mut() { *p = 0xC9 };
232        for p in rawmem[0x0100..0x4000].iter_mut() { *p = 0xFF };
233        for p in rawmem[0x4000..].iter_mut() { *p = 0x00 };
234        rawmem[0x0038] = 0xFB;
235        debug!("INIT: ${:x}", song.init);
236        let init = if song.init == 0 {
237            debug!("INIT using: ${:x}", song.blocks[0].address);
238            song.blocks[0].address
239        }
240        else {
241            song.init
242        }.to_le_bytes();
243        debug!("INTERRUPT: ${:x}", song.interrupt);
244        let player = if song.interrupt == 0 {
245            debug!("PLAYER ONE");
246            PLAYER_ONE
247        }
248        else {
249            debug!("PLAYER TWO");
250            PLAYER_TWO
251        };
252        rawmem[0x0000..player.len()].copy_from_slice(player);
253        if song.interrupt != 0 {
254            let intr = song.interrupt.to_le_bytes();
255            rawmem[PLAYER_TWO_INTERRUPT_OFFSET..PLAYER_TWO_INTERRUPT_OFFSET+intr.len()].copy_from_slice(&intr);
256        }
257        rawmem[PLAYER_INIT_OFFSET..PLAYER_INIT_OFFSET+init.len()].copy_from_slice(&init);
258        for block in song.blocks.iter() {
259            debug!("Block: ${:x} <- {:?}", block.address, block.data);
260            let address = block.address as usize;
261            let mut data = block.data.as_slice();
262            if address + data.len() > (u16::max_value() as usize) + 1 {
263                debug!("Block too large: ${:x}", address + data.len());
264                data = &data[..u16::max_value() as usize - address];
265            }
266            rawmem[address..address+data.len()].copy_from_slice(data);
267        }
268        debug!("STACK: ${:x}", song.stack);
269        // debug_memory(0x0000, &rawmem[0x0000..player.len()]);
270        cpu.reset();
271        cpu.set_i(self.meta.player_version);
272        cpu.set_reg(Reg8::H, None, song.hi_reg);
273        cpu.set_reg(Reg8::L, None, song.lo_reg);
274        cpu.set_reg(Reg8::D, None, song.hi_reg);
275        cpu.set_reg(Reg8::E, None, song.lo_reg);
276        cpu.set_reg(Reg8::B, None, song.hi_reg);
277        cpu.set_reg(Reg8::C, None, song.lo_reg);
278        cpu.exx();
279        cpu.set_acc(song.hi_reg);
280        cpu.set_flags(CpuFlags::from_bits_truncate(song.lo_reg));
281        cpu.ex_af_af();
282        cpu.set_reg(Reg8::H, None, song.hi_reg);
283        cpu.set_reg(Reg8::L, None, song.lo_reg);
284        cpu.set_reg(Reg8::D, None, song.hi_reg);
285        cpu.set_reg(Reg8::E, None, song.lo_reg);
286        cpu.set_reg(Reg8::B, None, song.hi_reg);
287        cpu.set_reg(Reg8::C, None, song.lo_reg);
288        cpu.set_reg(Reg8::H, Some(Prefix::Yfd), song.hi_reg);
289        cpu.set_reg(Reg8::L, Some(Prefix::Yfd), song.lo_reg);
290        cpu.set_reg(Reg8::H, Some(Prefix::Xdd), song.hi_reg);
291        cpu.set_reg(Reg8::L, Some(Prefix::Xdd), song.lo_reg);
292        cpu.set_acc(song.hi_reg);
293        cpu.set_flags(CpuFlags::from_bits_truncate(song.lo_reg));
294        cpu.disable_interrupts();
295        cpu.set_sp(song.stack);
296        cpu.set_im(InterruptMode::Mode0);
297        cpu.set_pc(0x0000);
298    }
299}
300
301/****************************************************************************/
302/*                        OM NOM NOM NOM NOM NOM NOM                        */
303/****************************************************************************/
304#[derive(Clone, Debug, PartialEq, Eq)]
305struct VerboseBytesError<'a> {
306  errors: Vec<(&'a [u8], VerboseBytesErrorKind)>,
307}
308
309trait UnwrapErr<E> {
310    fn unwrap(self) -> E;
311}
312
313impl<E> UnwrapErr<E> for Err<E> {
314    fn unwrap(self) -> E {
315        match self {
316            Err::Failure(e)|Err::Error(e) => e,
317            Err::Incomplete(..) => panic!("can't unwrap an incomplete error")
318        }
319    }
320}
321
322impl<'a> VerboseBytesError<'a> {
323    fn describe(&self, input: &'a[u8]) -> String {
324        let mut res = String::new();
325        for (i, (subs, kind)) in self.errors.iter().enumerate() {
326            let offset = input.offset(subs);
327            match kind {
328                VerboseBytesErrorKind::Context(s) => writeln!(&mut res,
329                    "{i}: at byte {offset} of {len}, {context}",
330                    i = i,
331                    context = s,
332                    len = input.len(),
333                    offset = offset
334                ),
335                VerboseBytesErrorKind::Nom(e) => writeln!(&mut res,
336                    "{i}: at byte {offset} of {len}, in {nom_err:?}",
337                    i = i,
338                    len = input.len(),
339                    offset = offset,
340                    nom_err = e
341                ),
342            }.unwrap()
343        }
344        res
345    }
346}
347
348#[derive(Clone, Debug, PartialEq, Eq)]
349/// error context for `VerboseBytesError`
350enum VerboseBytesErrorKind {
351  /// static string added by the `context` function
352  Context(&'static str),
353  /// error kind given by various nom parsers
354  Nom(ErrorKind),
355}
356
357impl<'a> ParseError<&'a[u8]> for VerboseBytesError<'a> {
358  fn from_error_kind(input: &'a[u8], kind: ErrorKind) -> Self {
359    VerboseBytesError {
360      errors: vec![(input, VerboseBytesErrorKind::Nom(kind))],
361    }
362  }
363
364  fn append(input: &'a[u8], kind: ErrorKind, mut other: Self) -> Self {
365    other.errors.push((input, VerboseBytesErrorKind::Nom(kind)));
366    other
367  }
368}
369
370impl<'a> ContextError<&'a[u8]> for VerboseBytesError<'a> {
371  fn add_context(input: &'a[u8], ctx: &'static str, mut other: Self) -> Self {
372    other.errors.push((input, VerboseBytesErrorKind::Context(ctx)));
373    other
374  }
375}
376
377fn c_string<'a, E: ParseError<&'a [u8]>>()
378    -> impl Fn(&'a[u8]) -> IResult<&'a[u8], &'a[u8], E>
379{
380    move |input| {
381        match memchr(0, input) {
382            Some(offs) => Ok((&input[offs..], &input[..offs])),
383            None => Err(Err::Error(E::from_error_kind(input, ErrorKind::Eof)))
384        }
385    }
386}
387
388fn parse_at<'a, T: 'a, E: ParseError<&'a[u8]>, F>(
389        offset: usize,
390        f: F
391    ) -> impl FnOnce(&'a[u8]) -> Result<T, Err<E>>
392    where F: FnOnce(&'a[u8])->Result<T, Err<E>>
393{
394    move |input| {
395        input.get(offset..).ok_or_else(|| Err::Failure(
396                    E::from_error_kind(input, ErrorKind::Eof)
397        )).and_then(f)
398    }
399}
400
401fn fail_err<I: Clone, O, E, F>(
402        mut f: F
403    ) -> impl FnMut(I) -> IResult<I, O, E>
404    where F: FnMut(I) -> IResult<I, O, E>
405{
406    move |input: I| {
407        f(input).map_err(|e| e.into_failure())
408    }
409}
410
411trait IntoFailure<E> {
412    fn into_failure(self) -> Self;
413}
414
415impl<E> IntoFailure<E> for Err<E> {
416    fn into_failure(self) -> Self {
417        if let Err::Error(e) = self {
418            Err::Failure(e)
419        }
420        else {
421            self
422        }
423    }
424}
425
426fn parse_ay_raw<'a, E: Clone + ContextError<&'a [u8]> + ParseError<&'a [u8]>>(
427        raw: &'a [u8]
428    ) -> Result<(AyMeta, Box<[AySong]>), Err<E>>
429{
430
431    let ay_blob = |offset: usize, len: usize| {
432        if offset+len > raw.len() {
433            debug!("truncating data off: {} len: {} end: {} > {}", offset, len, offset+len, raw.len());
434            raw.get(offset..)
435        }
436        else {
437            raw.get(offset..offset+len)
438        }.map(|blob| {
439            AyBlob::new(blob)
440        }).ok_or_else(|| {
441            let (inp, context) = (&raw[raw.len()..], "data offset exceeding file size");
442            Err::Failure(E::add_context(inp, context,
443                E::from_error_kind(inp, ErrorKind::Eof)
444            ))
445        })
446    };
447
448    let ay_string = |ctx, offset: Option<usize>| {
449        offset.map(|offset|
450            parse_at(offset,  context(ctx, c_string()))( raw )
451        ).unwrap_or_else(||
452            Ok( (raw, b"") )
453        ).map(|(_,s)| AyString::new(s))
454    };
455
456    let maybe_offs_to = |inp| {
457        let offset = isize::try_from(raw.offset(inp)).unwrap();
458        let (inp, offs) = map(be_u16, NonZeroU16::new)(inp)?;
459        let res = offs.map(|offs| {
460            usize::try_from(offset + offs.get() as isize).unwrap()
461        });
462        Ok((inp, res))
463    };
464
465    let offset_to = |inp| {
466        let (inp, offs) = maybe_offs_to(inp)?;
467        match offs {
468            None => Err(Err::Error(E::add_context(inp, "offset",
469                        E::from_error_kind(inp, ErrorKind::NonEmpty)
470                    ))),
471            Some(offs) => Ok((inp, offs))
472        }
473    };
474
475    let parse_address = |inp| {
476        let (inp, address) = context("address", fail_err(be_u16))(inp)?;
477        if address == 0 {
478            Err(Err::Error(E::from_error_kind(inp, ErrorKind::Complete)))
479        }
480        else {
481            let (inp, (len, offset)) = fail_err(tuple(
482                (context("data length", be_u16), offset_to)
483            ))( inp )?;
484            Ok((inp, AySongBlock {
485                address, data: ay_blob(offset, len.into())?
486            }))
487        }
488    };
489
490    let parse_song_data = |data_offs| {
491        parse_at(data_offs, 
492            context("song data", 
493                map(many1(parse_address), |vec| vec.into_boxed_slice()))
494        )( raw ).map(|(_,s)| s)
495    };
496
497    let parse_song_info = |info_offs, name: AyString| {
498        let (_, (chan_a, chan_b, chan_c, noise,
499        song_duration, fade_duration,
500        hi_reg, lo_reg,
501        init_offs, data_offs)) = parse_at(info_offs, context("missing song metadata", tuple(
502            (be_u8, be_u8, be_u8, be_u8,
503             be_u16, be_u16,
504             be_u8, be_u8,
505             offset_to, offset_to))
506        ))( raw )?;
507
508        let (_, (stack, init, interrupt)) = parse_at(init_offs, context("play routines",
509            tuple((context("stack", be_u16), be_u16, be_u16)))
510        )( raw )?;
511        let blocks = parse_song_data(data_offs)?;
512        Ok(AySong {
513            name, chan_a, chan_b, chan_c, noise,
514            song_duration, fade_duration,
515            hi_reg, lo_reg,
516            stack, init, interrupt,
517            blocks
518        })
519    };
520
521    let parse_song = |inp| {
522        let (inp, (name_offs, info_offs)) = tuple((maybe_offs_to, offset_to))(inp)?;
523        let name = ay_string("song name", name_offs)?;
524        let song = parse_song_info(info_offs, name)?;
525        Ok((inp, song))
526    };
527
528    let parse_songs = |songs_offs, num| {
529        parse_at(songs_offs, |inp| {
530            let mut it = iterator(inp, parse_song);
531            let songs = it.take(num).collect::<Box<[_]>>();
532            let (_,_) = it.finish()?;
533            Ok(songs)
534        })( raw )
535    };
536
537
538    let (_, (
539        _tag1, _tag2, file_version, player_version,
540        special_player_offs, author_offs, misc_offs,
541        num_of_songs, first_song,
542        songs_offs)
543    ) = context("missing header parts",
544            tuple((
545                context("tag ZXAY", tag(b"ZXAY")),
546                context("tag ZXAY", tag(b"EMUL")),
547                context("file version", be_u8),
548                context("player version", be_u8),
549                context("spec. player", maybe_offs_to),
550                context("author offs.", maybe_offs_to),
551                context("misc. offs.", maybe_offs_to),
552                context("num. of songs", be_u8),
553                context("firs song index", be_u8),
554                context("songs offs.", offset_to)
555            ))
556    )( raw )?;
557    let special_player = special_player_offs.is_some();
558    let author = ay_string("author", author_offs)?;
559    let misc = ay_string("misc", misc_offs)?;
560    let songs = parse_songs(songs_offs, num_of_songs as usize + 1)?;
561    let meta = AyMeta {
562        file_version, player_version,
563        special_player, author, misc, first_song
564    };
565
566    Ok((meta, songs))
567}
568
569/// The type of error returned by the *AY* file parser.
570#[derive(Clone, Debug, PartialEq, Eq)]
571pub struct AyParseError {
572    /// Data parsed.
573    pub data: Box<[u8]>,
574    /// *AY* file parser backtrace and error messages.
575    pub description: String,
576}
577
578impl std::error::Error for AyParseError {}
579impl fmt::Display for AyParseError {
580    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
581        write!(f, "{}", self.description)
582    }
583}
584
585impl From<(Box<[u8]>, String)> for AyParseError {
586    fn from((data, description): (Box<[u8]>, String)) -> Self {
587        AyParseError { data, description }
588    }
589}
590
591
592/// Parses given `data` and returns a pinned [AyFile] owning `data`.
593///
594/// # Errors
595/// On error returns the `data` wrapped in [AyParseError].
596pub fn parse_ay<B: Into<Box<[u8]>>>(
597        data: B
598    ) -> Result<PinAyFile, AyParseError>
599{
600    let raw: Box<_> = data.into();
601    let res = parse_ay_raw::<VerboseBytesError>(&raw)
602                        .map_err(|e|
603                            e.unwrap().describe(&raw)
604                        );
605
606    let (meta, songs) = match res {
607        Ok(ok) => ok,
608        Err(err) => return Err(AyParseError::from((raw, err)))
609    };
610
611    Ok(Box::pin(AyFile {
612        raw, meta, songs,
613        _pin: PhantomPinned 
614    }))
615}
616
617/// Reads data from `rd` parses data and returns a pinned [AyFile] owning data on success.
618///
619/// # Errors
620/// When there was a parse error returns `Err` with [AyParseError] wrapped in [io::Error]
621/// with [io::ErrorKind::InvalidData]. To get to the inner [AyParseError] you need either
622/// to downcast it yourself or use one of the convenient [IoErrorExt] methods.
623pub fn read_ay<R: io::Read>(
624        mut rd: R
625    ) -> io::Result<PinAyFile>
626{
627    let mut data = Vec::new();
628    rd.read_to_end(&mut data)?;
629    parse_ay(data).map_err(|e|
630        io::Error::new(io::ErrorKind::InvalidData, e)
631    )
632}
633
634/// A trait with helpers for extracting [AyParseError] from [io::Error].
635pub trait IoErrorExt: Sized {
636    fn is_ay_parse(&self) -> bool {
637        self.ay_parse_ref().is_some()
638    }
639    fn into_ay_parse(self) -> Option<Box<AyParseError>>;
640    fn ay_parse_ref(&self) -> Option<&AyParseError>;
641    fn ay_parse_mut(&mut self) -> Option<&mut AyParseError>;
642    fn into_ay_parse_data(self) -> Option<Box<[u8]>> {
643        self.into_ay_parse().map(|ay| ay.data)
644    }
645}
646
647impl IoErrorExt for io::Error {
648    fn into_ay_parse(self) -> Option<Box<AyParseError>> {
649        if let Some(inner) = self.into_inner() {
650            if let Ok(ay_err) = inner.downcast::<AyParseError>() {
651                return Some(ay_err)
652            }
653        }
654        None
655    }
656    fn ay_parse_ref(&self) -> Option<&AyParseError> {
657        if let Some(inner) = self.get_ref() {
658            if let Some(ay_err) = inner.downcast_ref::<AyParseError>() {
659                return Some(ay_err)
660            }
661        }
662        None
663    }
664    fn ay_parse_mut(&mut self) -> Option<&mut AyParseError> {
665        if let Some(inner) = self.get_mut() {
666            if let Some(ay_err) = inner.downcast_mut::<AyParseError>() {
667                return Some(ay_err)
668            }
669        }
670        None
671    }
672}
673/*
674Kudos to Sergey Bulba for reverse engeneering the format.
675
676There is but one bug in Bulba's specification, all offsets are U16 not I16.
677E.g. ProjectAY/Spectrum/Demos/SMC1.AY
678
679struct AYFileHeader {
680    // file_id:          [u8;4], // b"ZXAY"
681    // type_id:          [u8;4], // b"EMUL"
682    file_version:     u8,
683    player_version:   u8,
684    special_player_offs:  AYOffset, // u16
685    author_offs:          AYOffset, // u16
686    misc_offs:            AYOffset, // u16
687    num_of_songs:     u8,
688    first_song:       u8,
689    songs_info_offs:  AYOffset<[Song]>, // u16
690}
691
692struct Song {
693    song_name_offs:        AYOffset, // u16
694    song_data_offs:        AYOffset<[SongInfo]>, // u16
695}
696
697struct SongInfo {
698    chan_a: u8,
699    chan_b: u8,
700    chan_c: u8,
701    noise: u8,
702    song_duration: AYWord, // u16
703    fade_duration: AYWord, // u16
704    hi_reg: u8,
705    lo_reg: u8,
706    points_offs:    AYOffset<Pointers>, // u16,
707    addresses_offs: AYOffset<[AYWord]>, // u16, // SongData
708}
709
710struct Pointers {
711    stack: AYWord,
712    init: AYWord,
713    interrupt: AYWord
714}
715
716struct SongData {
717    address: AYWord,
718    length:  AYWord,
719    offset:  AYOffset,
720}
721*/