spectrusty_peripherals/bus/
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-3-8910` programmable sound generator.
9use core::fmt::{self, Debug};
10use core::num::NonZeroU16;
11use core::marker::PhantomData;
12
13pub mod serial128;
14#[cfg(feature = "snapshot")] mod serde;
15#[cfg(feature = "snapshot")] use ::serde::Serialize;
16
17use spectrusty_core::{
18    audio::{Blep, AmpLevels},
19    bus::{
20        BusDevice, NullDevice,
21        OptionalBusDevice, DynamicBus, DynamicSerdeBus, NamedBusDevice
22    },
23    clock::FTs
24};
25
26pub use crate::ay::{
27    audio::Ay3_891xAudio,
28    Ay3_8910Io, Ay3_8912Io, Ay3_8913Io, AyIoPort, AyIoNullPort, AyRegister,
29    AyPortDecode, Ay128kPortDecode, AyFullerBoxPortDecode
30};
31
32/// Implement this empty trait for [BusDevice] so methods from [AyAudioBusDevice]
33/// will get auto-implemented to pass method calls to the downstream devices.
34pub trait PassByAyAudioBusDevice {}
35
36/// A convenient [Ay3_891xBusDevice] type emulating a device with a `Melodik` port configuration.
37pub type Ay3_891xMelodik<D> = Ay3_891xBusDevice<Ay128kPortDecode,
38                                                AyIoNullPort<<D as BusDevice>::Timestamp>,
39                                                AyIoNullPort<<D as BusDevice>::Timestamp>,
40                                                D>;
41/// A convenient [Ay3_891xBusDevice] type emulating a device with a `Fuller Box` port configuration.
42pub type Ay3_891xFullerBox<D> = Ay3_891xBusDevice<AyFullerBoxPortDecode,
43                                                AyIoNullPort<<D as BusDevice>::Timestamp>,
44                                                AyIoNullPort<<D as BusDevice>::Timestamp>,
45                                                D>;
46
47impl<D: BusDevice> fmt::Display for Ay3_891xMelodik<D> {
48    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49        f.write_str("AY-3-8913 (Melodik)")
50    }
51}
52
53impl<D: BusDevice> fmt::Display for Ay3_891xFullerBox<D> {
54    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55        f.write_str("AY-3-8913 (Fuller Box)")
56    }
57}
58/// This trait is being used by [AyAudioFrame] implementations to render `AY-3-8910` audio with bus devices.
59///
60/// Allows for rendering audio frame using [AyAudioFrame] directly on the [ControlUnit] without the
61/// need to "locate" the [Ay3_891xBusDevice] in the daisy chain.
62///
63/// This trait is implemented autmatically for all [BusDevice]s which implement [PassByAyAudioBusDevice].
64///
65/// [AyAudioFrame]: crate::ay::audio::AyAudioFrame
66/// [ControlUnit]: spectrusty_core::chip::ControlUnit
67pub trait AyAudioBusDevice: BusDevice {
68    /// Renders square-wave pulses via [Blep] interface.
69    ///
70    /// Provide [AmpLevels] that can handle `level` values from 0 to 15 (4-bits).
71    /// `channels` - target [Blep] audio channels for `[A, B, C]` AY-3-8910 channels.
72    fn render_ay_audio<L: AmpLevels<B::SampleDelta>, B: Blep>(
73            &mut self,
74            blep: &mut B,
75            end_ts: <Self as BusDevice>::Timestamp,
76            frame_tstates: FTs,
77            chans: [usize; 3]
78        );
79}
80
81/// `AY-3-8910/8912/8913` programmable sound generator as a [BusDevice].
82///
83/// Envelops [Ay3_891xAudio] sound generator and [Ay3_8910Io] I/O ports peripherals.
84///
85/// Provides a helper method to produce sound generated by the last emulated frame.
86#[derive(Clone, Default, Debug)]
87#[cfg_attr(feature = "snapshot", derive(Serialize))]
88#[cfg_attr(feature = "snapshot", serde(rename_all = "camelCase"))]
89pub struct Ay3_891xBusDevice<P, A, B, D: BusDevice> {
90    /// Provides direct access to the sound generator.
91    pub ay_sound: Ay3_891xAudio,
92    /// Provides direct access to the I/O ports.
93    pub ay_io: Ay3_8910Io<D::Timestamp, A, B>,
94        bus: D,
95        #[cfg_attr(feature = "snapshot", serde(skip))]
96        _port_decode: PhantomData<P>
97}
98
99impl<D> PassByAyAudioBusDevice for Box<D> where D: PassByAyAudioBusDevice {}
100
101impl<D, N> AyAudioBusDevice for D
102    where D::Timestamp: Into<FTs>,
103          D: BusDevice<NextDevice=N> + PassByAyAudioBusDevice,
104          N: BusDevice<Timestamp=D::Timestamp> + AyAudioBusDevice
105{
106    fn render_ay_audio<L, B>(&mut self, blep: &mut B, end_ts: D::Timestamp, frame_tstates: FTs, chans: [usize; 3])
107        where B: Blep,
108              L: AmpLevels<B::SampleDelta>
109    {
110        self.next_device_mut().render_ay_audio::<L, B>(blep, end_ts, frame_tstates, chans)
111    }
112}
113
114macro_rules! impl_ay_audio_boxed_bus_device {
115    ($device:ident<$($ty:ident),*>) => {
116        impl_ay_audio_boxed_bus_device!($device<$($ty),*> where );
117    };
118    ($device:ident<$($ty:ident),*> where $($cond:tt)*) => {
119        impl<$($ty),*> AyAudioBusDevice for Box<$device<$($ty),*>>
120            where $device<$($ty),*>: AyAudioBusDevice + BusDevice,
121                  $($cond)*
122        {
123            fn render_ay_audio<L, B>(&mut self, blep: &mut B, end_ts: <Self as BusDevice>::Timestamp, frame_tstates: FTs, chans: [usize; 3])
124                where B: Blep,
125                      L: AmpLevels<B::SampleDelta>
126            {
127                (**self).render_ay_audio::<L, B>(blep, end_ts, frame_tstates, chans)
128            }
129        }
130    };
131}
132
133impl_ay_audio_boxed_bus_device!(OptionalBusDevice<D, N>);
134impl<D, N> AyAudioBusDevice for OptionalBusDevice<D, N>
135    where <D as BusDevice>::Timestamp: Into<FTs> + Copy,
136          D: AyAudioBusDevice + BusDevice,
137          N: AyAudioBusDevice + BusDevice<Timestamp=D::Timestamp>
138{
139    /// # Note
140    /// If a device is being attached to an optional device the call will be forwarded to
141    /// both: an optional device and to the next bus device.
142    #[inline(always)]
143    fn render_ay_audio<L, B>(&mut self, blep: &mut B, end_ts: D::Timestamp, frame_tstates: FTs, chans: [usize; 3])
144        where B: Blep,
145              L: AmpLevels<B::SampleDelta>
146    {
147        if let Some(ref mut device) = self.device {
148            device.render_ay_audio::<L, B>(blep, end_ts, frame_tstates, chans)
149        }
150        self.next_device.render_ay_audio::<L, B>(blep, end_ts, frame_tstates, chans)
151    }
152}
153
154impl<T> AyAudioBusDevice for dyn NamedBusDevice<T>
155    where T: Into<FTs> + Copy + fmt::Debug + 'static
156{
157    /// # Note
158    /// Because we need to guess the concrete type of the dynamic `BusDevice` we can currently handle
159    /// only the most common cases: [Ay3_891xMelodik] and [Ay3_891xFullerBox]. If you use a customized
160    /// [Ay3_891xBusDevice] for a dynamic `BusDevice` you need to render audio directly on the device
161    /// downcasted to your custom type.
162    #[inline]
163    fn render_ay_audio<L, B>(&mut self, blep: &mut B, end_ts: T, frame_tstates: FTs, chans: [usize; 3])
164        where L: AmpLevels<B::SampleDelta>,
165              B: Blep
166    {
167        if let Some(ay_dev) = self.downcast_mut::<Ay3_891xMelodik<NullDevice<T>>>() {
168            ay_dev.render_ay_audio::<L, B>(blep, end_ts, frame_tstates, chans)
169        }
170        else if let Some(ay_dev) = self.downcast_mut::<Ay3_891xFullerBox<NullDevice<T>>>() {
171            ay_dev.render_ay_audio::<L, B>(blep, end_ts, frame_tstates, chans)
172        }
173    }
174}
175
176impl_ay_audio_boxed_bus_device!(DynamicBus<D> where D: BusDevice);
177impl<D> AyAudioBusDevice for DynamicBus<D>
178    where <D as BusDevice>::Timestamp: Into<FTs> + Copy + fmt::Debug + 'static,
179          D: AyAudioBusDevice + BusDevice
180{
181    /// # Note
182    /// This implementation forwards a call to any recognizable [Ay3_891xBusDevice] device in a
183    /// dynamic daisy-chain as well as to an upstream device.
184    #[inline]
185    fn render_ay_audio<L, B>(&mut self, blep: &mut B, end_ts: D::Timestamp, frame_tstates: FTs, chans: [usize; 3])
186        where B: Blep,
187              L: AmpLevels<B::SampleDelta>
188    {
189        for dev in self.into_iter() {
190            dev.render_ay_audio::<L, B>(blep, end_ts, frame_tstates, chans)
191        }
192        self.next_device_mut().render_ay_audio::<L, B>(blep, end_ts, frame_tstates, chans)
193    }
194}
195
196impl_ay_audio_boxed_bus_device!(DynamicSerdeBus<SD, D> where D: BusDevice);
197impl<SD, D> AyAudioBusDevice for DynamicSerdeBus<SD, D>
198    where <D as BusDevice>::Timestamp: Into<FTs> + Copy + fmt::Debug + 'static,
199          D: AyAudioBusDevice + BusDevice
200{
201    /// # Note
202    /// This implementation forwards a call to any recognizable [Ay3_891xBusDevice] device in a
203    /// dynamic daisy-chain as well as to an upstream device.
204    #[inline]
205    fn render_ay_audio<L, B>(&mut self, blep: &mut B, end_ts: D::Timestamp, frame_tstates: FTs, chans: [usize; 3])
206        where B: Blep,
207              L: AmpLevels<B::SampleDelta>
208    {
209        (**self).render_ay_audio::<L, B>(blep, end_ts, frame_tstates, chans)
210    }
211}
212
213impl_ay_audio_boxed_bus_device!(Ay3_891xBusDevice<P, PA, PB, D> where D: BusDevice);
214impl<P, PA, PB, D> AyAudioBusDevice for Ay3_891xBusDevice<P, PA, PB, D>
215    where Self: BusDevice<Timestamp=D::Timestamp>,
216          D: BusDevice,
217          D::Timestamp: Into<FTs>
218{
219    #[inline(always)]
220    fn render_ay_audio<L, B>(&mut self, blep: &mut B, end_ts: D::Timestamp, frame_tstates: FTs, chans: [usize; 3])
221        where B: Blep,
222              L: AmpLevels<B::SampleDelta>
223    {
224        let end_ts = end_ts.into();
225        let changes = self.ay_io.recorder.drain_ay_reg_changes();
226        self.ay_sound.render_audio::<L,_,_>(changes, blep, end_ts, frame_tstates, chans)
227    }
228}
229
230impl_ay_audio_boxed_bus_device!(NullDevice<T>);
231impl<T: Into<FTs> + fmt::Debug> AyAudioBusDevice for NullDevice<T> {
232    #[inline(always)]
233    fn render_ay_audio<L, B>(&mut self, _blep: &mut B, _end_ts: T, _frame_tstates: FTs, _chans: [usize; 3])
234        where L: AmpLevels<B::SampleDelta>,
235              B: Blep
236    {}
237}
238
239impl<P, A, B, D> BusDevice for Ay3_891xBusDevice<P, A, B, D>
240    where P: AyPortDecode,
241          A: AyIoPort<Timestamp=D::Timestamp> + Debug,
242          B: AyIoPort<Timestamp=D::Timestamp> + Debug,
243          D: BusDevice,
244          D::Timestamp: Debug + Copy
245{
246    type Timestamp = D::Timestamp;
247    type NextDevice = D;
248
249    #[inline]
250    fn next_device_mut(&mut self) -> &mut Self::NextDevice {
251        &mut self.bus
252    }
253
254    #[inline]
255    fn next_device_ref(&self) -> &Self::NextDevice {
256        &self.bus
257    }
258
259    #[inline]
260    fn into_next_device(self) -> Self::NextDevice {
261        self.bus
262    }
263
264    #[inline]
265    fn reset(&mut self, timestamp: Self::Timestamp) {
266        self.ay_sound.reset();
267        self.ay_io.reset(timestamp);
268        self.bus.reset(timestamp);
269    }
270
271    #[inline]
272    fn read_io(&mut self, port: u16, timestamp: Self::Timestamp) -> Option<(u8, Option<NonZeroU16>)> {
273        if P::is_data_read(port) {
274            return Some((self.ay_io.data_port_read(port, timestamp), None))
275        }
276        self.bus.read_io(port, timestamp)
277    }
278
279    #[inline]
280    fn write_io(&mut self, port: u16, data: u8, timestamp: Self::Timestamp) -> Option<u16> {
281        if P::write_ay_io(&mut self.ay_io, port, data, timestamp) {
282            return Some(0)
283        }
284        self.bus.write_io(port, data, timestamp)
285    }
286
287    #[inline]
288    fn next_frame(&mut self, timestamp: Self::Timestamp) {
289        // ensure sound register state is equal to the i/o state only if there are some unused register changes
290        // in the recorder, meaning the audio wasn't rendered for the past frame;
291        // this is not ideal, because when changes are applied in time they may affect the state of the sound
292        // generator in a slightly different way in comparison to just setting their value to the current state;
293        // it is better still than to just leave them completely unsynchronized
294        if !self.ay_io.recorder.is_empty() {
295            for (reg, val) in self.ay_io.iter_sound_gen_regs() {
296                self.ay_sound.update_register(reg, val);
297            }
298        }
299        self.ay_io.next_frame(timestamp);
300        self.bus.next_frame(timestamp)
301    }
302}