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.
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}