jevsnes.git / third-party / rust / jgenesis / backend / gb-core / src / apu / components.rs
1use bincode::{Decode, Encode};
2use jgenesis_common::num::GetBit;
3
4#[derive(Debug, Clone, Copy, Encode, Decode)]
5pub struct LengthCounter<const MAX: u16> {
6    pub enabled: bool,
7    pub counter: u16,
8}
9
10impl<const MAX: u16> Default for LengthCounter<MAX> {
11    fn default() -> Self {
12        Self::new()
13    }
14}
15
16impl<const MAX: u16> LengthCounter<MAX> {
17    #[must_use]
18    pub fn new() -> Self {
19        Self { enabled: false, counter: MAX }
20    }
21
22    pub fn load(&mut self, value: u8) {
23        let masked_value = u16::from(value) & (MAX - 1);
24        self.counter = MAX - masked_value;
25    }
26
27    pub fn trigger(&mut self, frame_sequencer_step: u8) {
28        if self.counter == 0 {
29            self.counter = MAX;
30
31            // Quirk: Immediately clock if enabled during trigger and this is a length counter cycle
32            if self.enabled && !frame_sequencer_step.bit(0) {
33                self.counter -= 1;
34            }
35        }
36    }
37
38    pub fn clock(&mut self, channel_enabled: &mut bool) {
39        if !self.enabled || self.counter == 0 {
40            return;
41        }
42
43        self.counter -= 1;
44        if self.counter == 0 {
45            *channel_enabled = false;
46        }
47    }
48
49    pub fn set_enabled(
50        &mut self,
51        enabled: bool,
52        frame_sequencer_step: u8,
53        channel_enabled: &mut bool,
54    ) {
55        let prev_enabled = self.enabled;
56        self.enabled = enabled;
57
58        // Quirk: Immediately clock if newly enabled and this is a length counter cycle
59        if !prev_enabled && self.enabled && !frame_sequencer_step.bit(0) {
60            self.clock(channel_enabled);
61        }
62    }
63}
64
65pub type StandardLengthCounter = LengthCounter<64>;
66pub type WavetableLengthCounter = LengthCounter<256>;
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Encode, Decode)]
69pub enum EnvelopeDirection {
70    Increasing,
71    #[default]
72    Decreasing,
73}
74
75impl EnvelopeDirection {
76    fn from_bit(bit: bool) -> Self {
77        if bit { Self::Increasing } else { Self::Decreasing }
78    }
79
80    fn to_bit(self) -> bool {
81        self == Self::Increasing
82    }
83}
84
85#[derive(Debug, Clone, Copy, Encode, Decode)]
86pub struct Envelope {
87    pub volume: u8,
88    enabled: bool,
89    period: u8,
90    counter: u8,
91    direction: EnvelopeDirection,
92    initial_volume: u8,
93    configured_direction: EnvelopeDirection,
94    configured_period: u8,
95}
96
97impl Default for Envelope {
98    fn default() -> Self {
99        Self::new()
100    }
101}
102
103impl Envelope {
104    #[must_use]
105    pub fn new() -> Self {
106        Self {
107            volume: 0,
108            enabled: false,
109            period: 0,
110            counter: 0,
111            direction: EnvelopeDirection::default(),
112            initial_volume: 0,
113            configured_direction: EnvelopeDirection::default(),
114            configured_period: 0,
115        }
116    }
117
118    #[must_use]
119    pub fn read_register(self) -> u8 {
120        (self.initial_volume << 4)
121            | (u8::from(self.configured_direction.to_bit()) << 3)
122            | self.configured_period
123    }
124
125    pub fn write_register(&mut self, value: u8) {
126        let direction = EnvelopeDirection::from_bit(value.bit(3));
127
128        // "Zombie mode" hardware glitch: If the envelope register is written to with bit 3 set while the
129        // current period is 0, immediately increment volume while wrapping around from 15 to 0.
130        // Exact behavior seems to vary between hardware revisions, but this implementation seems to
131        // work for games that depend on "zombie mode" (e.g. Prehistorik Man)
132        if self.enabled && self.period == 0 && direction == EnvelopeDirection::Increasing {
133            self.volume = (self.volume + 1) & 0x0F;
134        }
135
136        self.initial_volume = value >> 4;
137        self.configured_direction = direction;
138        self.configured_period = value & 0x07;
139    }
140
141    pub fn trigger(&mut self) {
142        self.volume = self.initial_volume;
143        self.direction = self.configured_direction;
144        self.period = self.configured_period;
145
146        self.enabled = true;
147        self.counter = self.period;
148    }
149
150    pub fn clock(&mut self) {
151        if self.period == 0 || !self.enabled {
152            return;
153        }
154
155        self.counter -= 1;
156        if self.counter == 0 {
157            self.counter = self.period;
158
159            match (self.direction, self.volume) {
160                (EnvelopeDirection::Decreasing, 0) | (EnvelopeDirection::Increasing, 15) => {
161                    // Volume cannot decrease past 0 or increase past 15
162                    // Disable the envelope until next trigger
163                    self.enabled = false;
164                }
165                (EnvelopeDirection::Decreasing, _) => {
166                    self.volume -= 1;
167                }
168                (EnvelopeDirection::Increasing, _) => {
169                    self.volume += 1;
170                }
171            }
172        }
173    }
174}
175
176#[derive(Debug, Clone, Copy, PartialEq, Eq)]
177pub enum TimerTickEffect {
178    None,
179    Clocked,
180}
181
182#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)]
183pub struct PhaseTimer<const MAX_PHASE: u8> {
184    pub phase: u8,
185    frequency: u16,
186    pub counter: u16,
187    period: u16,
188}
189
190impl<const MAX_PHASE: u8> Default for PhaseTimer<MAX_PHASE> {
191    fn default() -> Self {
192        Self::new()
193    }
194}
195
196impl<const MAX_PHASE: u8> PhaseTimer<MAX_PHASE> {

Panics

If (MAX_PHASE + 1) is not a power of 2

200    #[must_use]
201    pub fn new() -> Self {
202        // Sanity check that (MAX_PHASE + 1) is a power of 2
203        assert_eq!(MAX_PHASE.trailing_ones() + MAX_PHASE.leading_zeros(), u8::BITS);
204
205        Self { phase: 0, counter: 2048, period: 2048, frequency: 0 }
206    }
208    #[must_use]
209    pub fn just_reloaded(self) -> bool {
210        self.counter == self.period
211    }
212
213    #[must_use]
214    pub fn frequency(self) -> u16 {
215        self.frequency
216    }
217
218    pub fn write_frequency_low(&mut self, value: u8) {
219        self.write_frequency((self.frequency & 0xFF00) | u16::from(value));
220    }
221
222    pub fn write_frequency_high(&mut self, value: u8) {
223        self.write_frequency((self.frequency & 0x00FF) | (u16::from(value & 0x07) << 8));
224    }
225
226    pub fn write_frequency(&mut self, value: u16) {
227        self.frequency = value;
228        self.period = 2048 - value;
229    }
230
231    pub fn trigger(&mut self) {
232        self.counter = self.period;
233    }
234
235    pub fn tick(&mut self) -> TimerTickEffect {
236        self.counter -= 1;
237        if self.counter == 0 {
238            self.counter = self.period;
239            self.phase = (self.phase + 1) & MAX_PHASE;
240            return TimerTickEffect::Clocked;
241        }
242
243        TimerTickEffect::None
244    }
245}
246
247pub type PulseTimer = PhaseTimer<7>;
248pub type WavetableTimer = PhaseTimer<31>;