spectrusty_peripherals/
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//! The **AY-3-8910** programmable sound generator chipset family.
9//!
10//! This module contains chipset I/O interface protocol traits and helper types.
11//!
12//! The sound emulation is in a separate module, please see [audio].
13use core::fmt;
14use core::ops::{Deref, DerefMut};
15use core::marker::PhantomData;
16
17#[cfg(feature = "snapshot")]
18use serde::{Serialize, Deserialize};
19
20pub mod audio;
21pub mod serial128;
22
23use spectrusty_core::clock::FTs;
24
25/// An enumeration of AY-3-8910 registers.
26#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
27#[cfg_attr(feature = "snapshot", derive(Serialize, Deserialize))]
28#[repr(u8)]
29pub enum AyRegister {
30      ToneFineA      =  0,
31      ToneCoarseA    =  1,
32      ToneFineB      =  2,
33      ToneCoarseB    =  3,
34      ToneFineC      =  4,
35      ToneCoarseC    =  5,
36      NoisePeriod    =  6,
37      MixerControl   =  7,
38      AmpLevelA      =  8,
39      AmpLevelB      =  9,
40      AmpLevelC      = 10,
41      EnvPerFine     = 11,
42      EnvPerCoarse   = 12,
43      EnvShape       = 13,
44      IoA            = 14,
45      IoB            = 15,
46}
47
48pub const NUM_SOUND_GEN_REGISTERS: usize = 14;
49
50const REG_MASKS: [u8;16] = [
51    0xff, 0x0f, 0xff, 0x0f, 0xff, 0x0f, 0x1f, 0xff,
52    0x1f, 0x1f, 0x1f, 0xff, 0xff, 0x0f, 0xff, 0xff
53];
54
55/// A helper trait for matching I/O port addresses for AY-3-8910.
56pub trait AyPortDecode: fmt::Debug {
57    /// A mask of significant address bus bits for port decoding.
58    const PORT_MASK: u16;
59    /// A mask of address bus bit values - for the register selection function.
60    const PORT_SELECT: u16;
61    /// A mask of address bus bit values - for the reading from the selected register function.
62    const PORT_DATA_READ: u16;
63    /// A mask of address bus bit values - for the writing to the selected register function.
64    const PORT_DATA_WRITE: u16;
65    /// Return `true` if the port matches the register selection function.
66    #[inline]
67    fn is_select(port: u16) -> bool {
68        port & Self::PORT_MASK == Self::PORT_SELECT & Self::PORT_MASK
69    }
70    /// Return `true` if the port matches the register reading function.
71    #[inline]
72    fn is_data_read(port: u16) -> bool {
73        port & Self::PORT_MASK == Self::PORT_DATA_READ & Self::PORT_MASK
74    }
75    /// Return `true` if the port matches the register writing function.
76    #[inline]
77    fn is_data_write(port: u16) -> bool {
78        port & Self::PORT_MASK == Self::PORT_DATA_WRITE & Self::PORT_MASK
79    }
80    /// A helper for writing data to one of the functions decoded from `port` address.
81    #[inline]
82    fn write_ay_io<T,R,A,B>(
83                ay_io: &mut Ay3_891xIo<T,R,A,B>,
84                port: u16,
85                data: u8,
86                timestamp: T
87            ) -> bool
88        where A: AyIoPort<Timestamp=T>,
89              B: AyIoPort<Timestamp=T>,
90              R: AyRegRecorder<Timestamp=T>
91    {
92        match port & Self::PORT_MASK {
93            p if p == Self::PORT_SELECT => {
94                ay_io.select_port_write(data);
95                true
96            }
97            p if p == Self::PORT_DATA_WRITE => {
98                ay_io.data_port_write(port, data, timestamp);
99                true
100            }
101            _ => false
102        }
103    }
104}
105
106/// Matches I/O port addresses for AY-3-8912 used by the **ZX Spectrum+ 128k** or a *Melodik* interface.
107#[derive(Clone, Copy, Default, Debug)]
108pub struct Ay128kPortDecode;
109impl AyPortDecode for Ay128kPortDecode {
110    const PORT_MASK      : u16 = 0b1100_0000_0000_0010;
111    const PORT_SELECT    : u16 = 0b1100_0000_0000_0000;
112    const PORT_DATA_READ : u16 = 0b1100_0000_0000_0000;
113    const PORT_DATA_WRITE: u16 = 0b1000_0000_0000_0000;
114}
115
116/// Matches I/O port addresses for AY-3-8912 used by the *Fuller Box* interface.
117#[derive(Clone, Copy, Default, Debug)]
118pub struct AyFullerBoxPortDecode;
119impl AyPortDecode for AyFullerBoxPortDecode {
120    const PORT_MASK      : u16 = 0x00ff;
121    const PORT_SELECT    : u16 = 0x003f;
122    const PORT_DATA_READ : u16 = 0x003f;
123    const PORT_DATA_WRITE: u16 = 0x005f;
124}
125
126/// Matches I/O port addresses for AY-3-8912 used by the *Timex TC2068* computer series.
127#[derive(Clone, Copy, Default, Debug)]
128pub struct AyTC2068PortDecode;
129impl AyPortDecode for AyTC2068PortDecode {
130    const PORT_MASK      : u16 = 0x00ff;
131    const PORT_SELECT    : u16 = 0x00f5;
132    const PORT_DATA_READ : u16 = 0x00f6;
133    const PORT_DATA_WRITE: u16 = 0x00f6;
134}
135
136/// A type for recording timestamped changes to one of AY-3-8910 audio registers.
137///
138/// Instances of this type are being used by [Ay3_891xAudio][audio::Ay3_891xAudio]
139/// for sound generation. See also [AyRegRecorder].
140#[derive(Clone, Copy, Debug)]
141pub struct AyRegChange {
142    /// A timestamp in `CPU` cycles (T-states), relative to the beginning of the current frame.
143    pub time: FTs,
144    /// Which register is being changed.
145    pub reg: AyRegister,
146    /// A new value loaded into the register.
147    ///
148    /// *NOTE*: may contain bits set to 1 that are unused by the indicated register.
149    pub val: u8
150}
151
152/// This trait should be implemented by emulators of devices attached to one of two [Ay3_891xIo] I/O ports.
153pub trait AyIoPort {
154    type Timestamp: Sized;
155    /// Resets the device at the given `timestamp`.
156    #[inline]
157    fn ay_io_reset(&mut self, _timestamp: Self::Timestamp) {}
158    /// Writes data to the device at the given `timestamp`.
159    #[inline]
160    fn ay_io_write(&mut self, _addr: u16, _data: u8, _timestamp: Self::Timestamp) {}
161    /// Reads data from the device at the given `timestamp`.
162    #[inline]
163    fn ay_io_read(&mut self, _addr: u16, _timestamp: Self::Timestamp) -> u8 { 0xff }
164    /// Signals the device when the current frame ends, providing the value of the cycle clock after
165    /// the frame execution stopped.
166    #[inline]
167    fn end_frame(&mut self, _timestamp: Self::Timestamp) {}
168}
169
170/// An [Ay3_891xIo] I/O device implementation that does nothing.
171#[derive(Default, Clone, Copy, Debug)]
172#[cfg_attr(feature = "snapshot", derive(Serialize, Deserialize))]
173pub struct AyIoNullPort<T>(PhantomData<T>);
174
175/// Allows recording of changes to AY-3-8910 audio registers with timestamps.
176pub trait AyRegRecorder {
177    type Timestamp;
178    /// Should record a new value of indicated register with the given `timestamp`.
179    ///
180    /// *NOTE*: It is up to the caller to ensure the timestamps are added in an ascending order.
181    fn record_ay_reg_change(&mut self, reg: AyRegister, val: u8, timestamp: Self::Timestamp);
182    /// Should remove data from the recorder.
183    fn clear_ay_reg_changes(&mut self);
184}
185
186/// The type of [Ay3_891xIo] with two optional I/O ports (A and B) and [AyRegVecRecorder].
187pub type Ay3_8910Io<T,A=AyIoNullPort<T>,B=AyIoNullPort<T>> = Ay3_891xIo<T, AyRegVecRecorder<T>, A, B>;
188/// The type of [Ay3_891xIo] with one I/O port (A only) and [AyRegVecRecorder].
189pub type Ay3_8912Io<T,A> = Ay3_8910Io<T, A>;
190/// The type of [Ay3_891xIo] without I/O ports and [AyRegVecRecorder].
191pub type Ay3_8913Io<T> = Ay3_8910Io<T>;
192
193/// Implements a communication protocol with programmable sound generator AY-3-8910 / 8912 / 8913 and
194/// its I/O ports.
195///
196/// Records values written to audio registers which can be used to produce sound using
197/// [audio::Ay3_891xAudio].
198///
199/// The `recorder` type `R` needs to implement [AyRegRecorder] trait.
200/// The port types `A` and `B` need to implement [AyIoPort] trait.
201#[derive(Default, Clone, Debug)]
202#[cfg_attr(feature = "snapshot", derive(Serialize, Deserialize))]
203#[cfg_attr(feature = "snapshot", serde(rename_all = "camelCase"))]
204pub struct Ay3_891xIo<T, R, A, B> {
205    /// Provides access to the recorded changes of sound generator registers.
206    /// The changes are required to generate sound with [audio::Ay3_891xAudio].
207    #[cfg_attr(feature = "snapshot", serde(skip))]
208    pub recorder: R,
209    /// An instance of port `A` [AyIoPort] I/O device implementation.
210    #[cfg_attr(feature = "snapshot", serde(default))]
211    pub port_a: A,
212    /// An instance of port `B` [AyIoPort] I/O device implementation.
213    #[cfg_attr(feature = "snapshot", serde(default))]
214    pub port_b: B,
215    regs: [u8; 16],
216    selected_reg: AyRegister,
217    #[cfg_attr(feature = "snapshot", serde(skip))]
218    _ts: PhantomData<T>
219}
220
221/// A recorder for [Ay3_891xIo] that records nothing.
222///
223/// Useful when no sound will be generated.
224#[derive(Default, Clone, Debug)]
225pub struct AyRegNullRecorder<T>(PhantomData<T>);
226
227/// A convenient recorder for [Ay3_891xIo] that records changes in a [Vec].
228#[derive(Default, Clone, Debug)]
229pub struct AyRegVecRecorder<T>(pub Vec<(T,AyRegister,u8)>);
230
231impl<T,R,A,B> Ay3_891xIo<T,R,A,B>
232where A: AyIoPort<Timestamp=T>,
233      B: AyIoPort<Timestamp=T>,
234      R: AyRegRecorder<Timestamp=T>
235{
236    /// Resets the state of internal registers, port devices, and which register is being
237    /// currently selected for reading and writing.
238    ///
239    /// *NOTE*: does nothing to the recorded register changes.
240    pub fn reset(&mut self, timestamp: T) where T: Copy {
241        self.regs = Default::default();
242        self.selected_reg = Default::default();
243        self.port_a.ay_io_reset(timestamp);
244        self.port_b.ay_io_reset(timestamp);
245    }
246    /// Clears recorder data and forwards the call to I/O port implementations.
247    /// It can be used to indicate an end-of-frame.
248    pub fn next_frame(&mut self, timestamp: T) where T: Copy {
249        self.recorder.clear_ay_reg_changes();
250        self.port_a.end_frame(timestamp);
251        self.port_b.end_frame(timestamp);
252    }
253    /// Retrieves a current value of the indicated register.
254    #[inline]
255    pub fn get(&self, reg: AyRegister) -> u8 {
256        let index = usize::from(reg);
257        self.regs[index]
258    }
259    /// Returns a reference to all registers as an array of their current values.
260    #[inline]
261    pub fn registers(&self) -> &[u8;16] {
262        &self.regs
263    }
264    /// Returns an iterator of `(register, value)` pairs over current registers.
265    #[inline]
266    pub fn iter_regs(&'_ self) -> impl Iterator<Item=(AyRegister, u8)> + '_ {
267        self.regs.iter().enumerate().map(|(n, val)| ((n as u8).into(), *val))
268    }
269    /// Returns an iterator of `(register, value)` pairs over current registers used by the
270    /// sound generator.
271    #[inline]
272    pub fn iter_sound_gen_regs(&'_ self) -> impl Iterator<Item=(AyRegister, u8)> + '_ {
273        self.iter_regs().take(NUM_SOUND_GEN_REGISTERS)
274    }
275    /// Sets a current value of the indicated register.
276    #[inline]
277    pub fn set(&mut self, reg: AyRegister, val: u8) {
278        let index = usize::from(reg);
279        self.regs[index] = val & REG_MASKS[index];
280    }
281    /// Returns `true` if the control register bit controlling I/O port `A` input is reset.
282    #[inline]
283    pub fn is_ioa_input(&self) -> bool {
284        self.get(AyRegister::MixerControl) & 0x40 == 0
285    }
286    /// Returns `true` if the control register bit controlling I/O port `B` input is reset.
287    #[inline]
288    pub fn is_iob_input(&self) -> bool {
289        self.get(AyRegister::MixerControl) & 0x80 == 0
290    }
291    /// Returns `true` if the control register bit controlling I/O port `A` input is set.
292    #[inline]
293    pub fn is_ioa_output(&self) -> bool {
294        !self.is_ioa_input()
295    }
296    /// Returns `true` if the control register bit controlling I/O port `B` input is reset.
297    #[inline]
298    pub fn is_iob_output(&self) -> bool {
299        !self.is_iob_input()
300    }
301    /// Returns a previously selected register with [Ay3_891xIo::select_port_write].
302    #[inline]
303    pub fn selected_register(&self) -> AyRegister {
304        self.selected_reg
305    }
306    /// Bits 0-3 of `data` selects a register to be read from or written to.
307    ///
308    /// This method is being used to interface the host controller I/O operation.
309    #[inline]
310    pub fn select_port_write(&mut self, data: u8) {
311        self.selected_reg = AyRegister::from(data)
312    }
313    /// Writes data to a previously selected register and records a change
314    /// in the attached recorder unless a write is performed to one of the I/O ports.
315    ///
316    /// This method is being used to interface the host controller I/O operation.
317    #[inline]
318    pub fn data_port_write(&mut self, port: u16, data: u8, timestamp: T) {
319        self.set(self.selected_reg, data); // What to do when control is set to output for IO?
320        match self.selected_reg {
321            AyRegister::IoA => {
322                self.port_a.ay_io_write(port, data, timestamp)
323            }
324            AyRegister::IoB => {
325                self.port_b.ay_io_write(port, data, timestamp)
326            }
327            reg => self.recorder.record_ay_reg_change(reg, data, timestamp)
328        }
329    }
330    /// Reads data from a previously selected register.
331    ///
332    /// This method is being used to interface the host controller I/O operation.
333    #[inline]
334    pub fn data_port_read(&mut self, port: u16, timestamp: T) -> u8 {
335        match self.selected_reg {
336            AyRegister::IoA => {
337                let port_input = self.port_a.ay_io_read(port, timestamp);
338                if self.is_ioa_input() {
339                    port_input
340                }
341                else {
342                    port_input & self.get(AyRegister::IoA)
343                }
344            }
345            AyRegister::IoB => {
346                let port_input = self.port_b.ay_io_read(port, timestamp);
347                if self.is_iob_input() {
348                    port_input
349                }
350                else {
351                    port_input & self.get(AyRegister::IoB)
352                }
353            }
354            reg => self.get(reg)
355        }
356    }
357}
358
359impl AyRegister {
360    /// Returns an iterator of all [AyRegister] values in an ascending order.
361    pub fn enumerate() -> impl Iterator<Item=AyRegister> {
362        (0..15).map(AyRegister::from)
363    }
364}
365
366impl Default for AyRegister {
367    fn default() -> Self {
368        AyRegister::ToneFineA
369    }
370}
371
372impl From<u8> for AyRegister {
373    fn from(value: u8) -> Self {
374        unsafe { core::mem::transmute(value & 0x0F) }
375    }
376}
377
378macro_rules! impl_from_ay_reg {
379    ($($ty:ty),*) => { $(
380        impl From<AyRegister> for $ty {
381            #[inline(always)]
382            fn from(reg: AyRegister) -> $ty {
383                reg as $ty
384            }
385        }        
386    )* };
387}
388impl_from_ay_reg!(u8, u16, u32, u64, usize);
389
390impl AyRegChange {
391    /// Creates a new `AyRegChange` from the given arguments.
392    #[inline]
393    pub const fn new(time: FTs, reg: AyRegister, val: u8) -> Self {
394        AyRegChange { time, reg, val }
395    }
396    /// Creates a new `AyRegChange` from the given arguments, converting a provided `timestamp` first.
397    pub fn new_from_ts<T: Into<FTs>>(timestamp: T, reg: AyRegister, val: u8) -> Self {
398        let time = timestamp.into();
399        Self::new(time, reg, val)
400    }
401}
402
403impl<T> AyIoPort for AyIoNullPort<T> {
404    type Timestamp = T;
405}
406
407impl<T> AyRegRecorder for AyRegNullRecorder<T> {
408    type Timestamp = T;
409    #[inline]
410    fn record_ay_reg_change(&mut self, _reg: AyRegister, _val: u8, _timestamp: T) {}
411    #[inline]
412    fn clear_ay_reg_changes(&mut self) {}
413}
414
415impl<T> AyRegRecorder for AyRegVecRecorder<T> {
416    type Timestamp = T;
417    #[inline]
418    fn record_ay_reg_change(&mut self, reg: AyRegister, val: u8, timestamp: T) {
419        self.0.push((timestamp, reg, val));
420    }
421    #[inline]
422    fn clear_ay_reg_changes(&mut self) {
423        self.0.clear()
424    }
425}
426
427impl<T: Into<FTs>> AyRegVecRecorder<T> {
428    /// Constructs a draining iterator of [AyRegChange] items from an inner [Vec].
429    pub fn drain_ay_reg_changes(&'_ mut self) -> impl Iterator<Item=AyRegChange> + '_ {
430        self.0.drain(..).map(|(timestamp,reg,val)| AyRegChange::new_from_ts(timestamp,reg,val))
431    }
432}
433
434impl<T> Deref for AyRegVecRecorder<T> {
435    type Target = Vec<(T,AyRegister,u8)>;
436    fn deref(&self) -> &Self::Target {
437        &self.0
438    }
439}
440
441impl<T> DerefMut for AyRegVecRecorder<T> {
442    fn deref_mut(&mut self) -> &mut Self::Target {
443        &mut self.0
444    }
445}