pulse.rsannotatedpulse.rssource235 lines · 6.9 KB · raw

One of the APU's square wave generator channels, which generates a square wave with a configurable duty cycle and period. These channels also have sweep units which can automatically increase or decrease the period over time.

Channel output values are between 0 and 15 (inclusive).

This code is also used in the MMC5 mapper's expansion audio implementation.

9use crate::apu::units::{Envelope, LengthCounter, LengthCounterChannel, PhaseTimer};
10use bincode::{Decode, Encode};
11use jgenesis_common::num::GetBit;
13type PulsePhaseTimer = PhaseTimer<8, 2, 11, true>;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)]
16enum DutyCycle {
17    OneEighth,
18    OneFourth,
19    OneHalf,
20    ThreeFourths,
21}
22
23impl DutyCycle {
24    fn from_vol(vol_value: u8) -> Self {
25        match vol_value & 0xC0 {
26            0x00 => Self::OneEighth,
27            0x40 => Self::OneFourth,
28            0x80 => Self::OneHalf,
29            0xC0 => Self::ThreeFourths,
30            _ => unreachable!("{vol_value} & 0xC0 was not 0x00/0x40/0x80/0xC0"),
31        }
32    }
33
34    fn waveform(self) -> [u8; 8] {
35        match self {
36            Self::OneEighth => [0, 1, 0, 0, 0, 0, 0, 0],
37            Self::OneFourth => [0, 1, 1, 0, 0, 0, 0, 0],
38            Self::OneHalf => [0, 1, 1, 1, 1, 0, 0, 0],
39            Self::ThreeFourths => [1, 0, 0, 1, 1, 1, 1, 1],
40        }
41    }
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)]
45enum SweepNegateBehavior {
46    OnesComplement,
47    TwosComplement,
48}
49
50impl SweepNegateBehavior {
51    fn negate(self, value: u16) -> u16 {
52        match self {
53            Self::OnesComplement => !value,
54            Self::TwosComplement => (!value).wrapping_add(1),
55        }
56    }
57}
58
59#[derive(Debug, Clone, Encode, Decode)]
60struct PulseSweep {
61    enabled: bool,
62    divider: u8,
63    divider_period: u8,
64    negate_flag: bool,
65    negate_behavior: SweepNegateBehavior,
66    shift: u8,
67    reload_flag: bool,
68    target_period: u16,
69}
70
71impl PulseSweep {
72    fn new(negate_behavior: SweepNegateBehavior) -> Self {
73        Self {
74            enabled: false,
75            divider: 0,
76            divider_period: 0,
77            negate_flag: false,
78            negate_behavior,
79            shift: 0,
80            reload_flag: false,
81            target_period: 0,
82        }
83    }
84
85    fn process_sweep_update(&mut self, sweep_value: u8, timer_period: u16) {
86        self.reload_flag = true;
87
88        self.enabled = sweep_value.bit(7);
89        self.divider_period = (sweep_value >> 4) & 0x07;
90        self.negate_flag = sweep_value.bit(3);
91        self.shift = sweep_value & 0x07;
92
93        self.target_period = self.compute_target_period(timer_period);
94    }
95
96    fn process_timer_period_update(&mut self, timer_period: u16) {
97        self.target_period = self.compute_target_period(timer_period);
98    }
99
100    fn compute_target_period(&self, timer_period: u16) -> u16 {
101        if self.shift == 0 && self.negate_flag {
102            // Always return 0 when negate is set and shift is 0.
103            // Not doing this will cause channel 1 to incorrectly silence during some games because
104            // of its weird one's complement behavior.
105            return 0;
106        }
107
108        let delta = timer_period >> self.shift;
109        let signed_delta =
110            if self.negate_flag { self.negate_behavior.negate(delta) } else { delta };
111
112        timer_period.wrapping_add(signed_delta)
113    }
114
115    fn is_channel_muted(&self, timer_period: u16) -> bool {
116        timer_period < 8 || self.target_period > 0x07FF
117    }
118
119    fn clock(&mut self, timer_period: &mut u16) {
120        if self.divider == 0
121            && self.enabled
122            && self.shift > 0
123            && !self.is_channel_muted(*timer_period)
124        {
125            *timer_period = self.target_period;
126            self.target_period = self.compute_target_period(self.target_period);
127        }
128
129        if self.divider == 0 || self.reload_flag {
130            self.divider = self.divider_period;
131            self.reload_flag = false;
132        } else {
133            self.divider -= 1;
134        }
135    }
136}
137
138#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode)]
139pub enum SweepStatus {
140    Enabled,
141    Disabled,
142}
143
144#[derive(Debug, Clone, Encode, Decode)]
145pub struct PulseChannel {
146    timer: PulsePhaseTimer,
147    duty_cycle: DutyCycle,
148    length_counter: LengthCounter,
149    envelope: Envelope,
150    sweep: PulseSweep,
151    sweep_status: SweepStatus,
152}
153
154impl PulseChannel {
155    pub fn new_channel_1(sweep_status: SweepStatus) -> Self {
156        Self {
157            timer: PulsePhaseTimer::new(),
158            duty_cycle: DutyCycle::OneEighth,
159            length_counter: LengthCounter::new(LengthCounterChannel::Pulse1),
160            envelope: Envelope::new(),
161            sweep: PulseSweep::new(SweepNegateBehavior::OnesComplement),
162            sweep_status,
163        }
164    }
165
166    pub fn new_channel_2(sweep_status: SweepStatus) -> Self {
167        Self {
168            timer: PulsePhaseTimer::new(),
169            duty_cycle: DutyCycle::OneEighth,
170            length_counter: LengthCounter::new(LengthCounterChannel::Pulse2),
171            envelope: Envelope::new(),
172            sweep: PulseSweep::new(SweepNegateBehavior::TwosComplement),
173            sweep_status,
174        }
175    }
176
177    pub fn process_vol_update(&mut self, vol_value: u8) {
178        self.duty_cycle = DutyCycle::from_vol(vol_value);
179        self.length_counter.process_vol_update(vol_value);
180        self.envelope.process_vol_update(vol_value);
181    }
182
183    pub fn process_sweep_update(&mut self, sweep_value: u8) {
184        self.sweep.process_sweep_update(sweep_value, self.timer.divider_period);
185    }
186
187    pub fn process_lo_update(&mut self, lo_value: u8) {
188        self.timer.process_lo_update(lo_value);
189        self.sweep.process_timer_period_update(self.timer.divider_period);
190    }
191
192    pub fn process_hi_update(&mut self, hi_value: u8) {
193        self.timer.process_hi_update(hi_value);
194        self.sweep.process_timer_period_update(self.timer.divider_period);
195
196        self.length_counter.process_hi_update(hi_value);
197        self.envelope.process_hi_update();
198    }
199
200    pub fn process_snd_chn_update(&mut self, snd_chn_value: u8) {
201        self.length_counter.process_snd_chn_update(snd_chn_value);
202    }
203
204    pub fn clock_quarter_frame(&mut self) {
205        self.envelope.clock();
206    }
207
208    pub fn clock_half_frame(&mut self) {
209        self.length_counter.clock();
210
211        if self.sweep_status == SweepStatus::Enabled {
212            self.sweep.clock(&mut self.timer.divider_period);
213        }
214    }
215
216    pub fn tick_cpu(&mut self) {
217        self.timer.tick(true);
218    }
219
220    pub fn sample(&self) -> u8 {
221        if self.length_counter.counter == 0
222            || (self.sweep_status == SweepStatus::Enabled
223                && self.sweep.is_channel_muted(self.timer.divider_period))
224        {
225            return 0;
226        }
227
228        let wave_step = self.duty_cycle.waveform()[self.timer.phase as usize];
229        wave_step * self.envelope.volume()
230    }
231
232    pub fn length_counter(&self) -> u8 {
233        self.length_counter.counter
234    }
235}