1use crate::envelope::Envelope;
10use crate::length::LengthCounter;
11
12const DUTY_TABLE: [[u8; 8]; 4] = [
17 [0, 1, 0, 0, 0, 0, 0, 0], [0, 1, 1, 0, 0, 0, 0, 0], [0, 1, 1, 1, 1, 0, 0, 0], [1, 0, 0, 1, 1, 1, 1, 1], ];
22
23#[derive(Debug, Clone, Copy)]
25pub struct Pulse {
26 pub(crate) duty: u8,
28 pub(crate) step: u8,
30 pub(crate) timer_period: u16,
32 pub(crate) timer: u16,
34 pub envelope: Envelope,
36 pub length: LengthCounter,
38 pub(crate) sweep_enabled: bool,
40 pub(crate) sweep_period: u8,
42 pub(crate) sweep_negate: bool,
44 pub(crate) sweep_shift: u8,
46 pub(crate) sweep_reload: bool,
48 pub(crate) sweep_divider: u8,
50 pub(crate) is_pulse1: bool,
52}
53
54impl Pulse {
55 #[must_use]
58 pub const fn new(is_pulse1: bool) -> Self {
59 Self {
60 duty: 0,
61 step: 0,
62 timer_period: 0,
63 timer: 0,
64 envelope: Envelope {
65 start: false,
66 loop_flag: false,
67 constant: false,
68 volume_or_period: 0,
69 divider: 0,
70 decay: 0,
71 },
72 length: LengthCounter {
73 count: 0,
74 halt: false,
75 new_halt: false,
76 enabled: false,
77 reload_val: 0,
78 previous_count: 0,
79 },
80 sweep_enabled: false,
81 sweep_period: 0,
82 sweep_negate: false,
83 sweep_shift: 0,
84 sweep_reload: false,
85 sweep_divider: 0,
86 is_pulse1,
87 }
88 }
89
90 pub fn write_ctrl(&mut self, value: u8) {
92 self.duty = (value >> 6) & 0x03;
93 let halt = (value & 0x20) != 0;
94 self.length.set_halt(halt);
97 self.envelope.loop_flag = halt;
98 self.envelope.constant = (value & 0x10) != 0;
99 self.envelope.volume_or_period = value & 0x0F;
100 }
101
102 pub fn write_sweep(&mut self, value: u8) {
104 self.sweep_enabled = (value & 0x80) != 0;
105 self.sweep_period = (value >> 4) & 0x07;
106 self.sweep_negate = (value & 0x08) != 0;
107 self.sweep_shift = value & 0x07;
108 self.sweep_reload = true;
109 }
110
111 pub fn write_timer_lo(&mut self, value: u8) {
113 self.timer_period = (self.timer_period & 0xFF00) | u16::from(value);
114 }
115
116 pub fn write_timer_hi(&mut self, value: u8) {
118 self.timer_period = (self.timer_period & 0x00FF) | (u16::from(value & 0x07) << 8);
119 self.length.load(value);
120 self.step = 0;
121 self.envelope.start = true;
122 }
123
124 pub fn clock_timer(&mut self) {
126 if self.timer == 0 {
127 self.timer = self.timer_period;
128 self.step = (self.step + 1) & 0x07;
130 } else {
131 self.timer -= 1;
132 }
133 }
134
135 pub fn clock_half_frame(&mut self) {
137 let target = self.sweep_target();
139 if self.sweep_divider == 0 && self.sweep_enabled && self.sweep_shift > 0 && !self.muted() {
140 self.timer_period = target;
142 }
143 if self.sweep_divider == 0 || self.sweep_reload {
144 self.sweep_divider = self.sweep_period;
145 self.sweep_reload = false;
146 } else {
147 self.sweep_divider -= 1;
148 }
149 self.length.clock();
150 }
151
152 pub fn clock_quarter_frame(&mut self) {
154 self.envelope.clock();
155 }
156
157 fn sweep_target(&self) -> u16 {
169 let shifted = self.timer_period >> self.sweep_shift;
170 if self.sweep_negate {
171 if self.is_pulse1 {
172 self.timer_period.saturating_sub(shifted).saturating_sub(1)
173 } else {
174 self.timer_period.saturating_sub(shifted)
175 }
176 } else {
177 self.timer_period.wrapping_add(shifted)
178 }
179 }
180
181 #[must_use]
183 pub fn muted(&self) -> bool {
184 self.timer_period < 8 || self.sweep_target() > 0x7FF
185 }
186
187 #[must_use]
189 pub fn output(&self) -> u8 {
190 if self.length.count == 0
191 || self.muted()
192 || DUTY_TABLE[self.duty as usize][self.step as usize] == 0
193 {
194 0
195 } else {
196 self.envelope.output()
197 }
198 }
199}
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204
205 #[test]
206 fn write_ctrl_sets_duty_and_envelope_period() {
207 let mut p = Pulse::new(true);
208 p.write_ctrl(0b1011_0101); assert_eq!(p.duty, 2);
210 assert!(p.length.new_halt);
213 p.length.reload();
214 assert!(p.length.halt);
215 assert!(p.envelope.loop_flag);
216 assert!(p.envelope.constant);
217 assert_eq!(p.envelope.volume_or_period, 5);
218 }
219
220 #[test]
221 fn timer_underflow_advances_duty_step() {
222 let mut p = Pulse::new(true);
223 p.timer_period = 1;
224 p.timer = 0;
225 p.clock_timer();
226 assert_eq!(p.step, 1);
227 }
228
229 #[test]
230 fn pulse1_sweep_negation_is_ones_complement() {
231 let mut p = Pulse::new(true);
232 p.timer_period = 0x100;
233 p.sweep_negate = true;
234 p.sweep_shift = 1;
235 assert_eq!(p.sweep_target(), 0x7F);
237 }
238
239 #[test]
240 fn pulse2_sweep_negation_is_twos_complement() {
241 let mut p = Pulse::new(false);
242 p.timer_period = 0x100;
243 p.sweep_negate = true;
244 p.sweep_shift = 1;
245 assert_eq!(p.sweep_target(), 0x80);
247 }
248
249 #[test]
259 fn pulse1_negate_shift0_clamps_to_zero_and_does_not_mute() {
260 let mut p = Pulse::new(true);
261 p.write_sweep(0x08); p.timer_period = 0x100;
263 assert_eq!(p.sweep_target(), 0, "negative target clamps to zero");
264 assert!(!p.muted(), "$4001=$08 must leave pulse 1 audible");
265 }
266
267 #[test]
270 fn pulse2_negate_shift0_targets_zero_and_does_not_mute() {
271 let mut p = Pulse::new(false);
272 p.write_sweep(0x08);
273 p.timer_period = 0x100;
274 assert_eq!(p.sweep_target(), 0);
275 assert!(!p.muted());
276 }
277
278 #[test]
282 fn pulse1_negate_clamp_is_inert_for_nonzero_shift() {
283 let mut p = Pulse::new(true);
284 for shift in 1..=7u8 {
285 for period in 8..=0x7FFu16 {
286 p.write_sweep(0x08 | shift);
287 p.timer_period = period;
288 let expect = period - (period >> shift) - 1;
289 assert_eq!(p.sweep_target(), expect, "shift {shift} period {period:#x}");
290 }
291 }
292 }
293
294 #[test]
295 fn sweep_mutes_when_period_too_low() {
296 let mut p = Pulse::new(true);
297 p.timer_period = 7;
298 assert!(p.muted());
299 p.timer_period = 8;
300 assert!(!p.muted());
301 }
302
303 #[test]
304 fn sweep_mutes_when_target_above_7ff() {
305 let mut p = Pulse::new(true);
306 p.timer_period = 0x780;
307 p.sweep_negate = false;
308 p.sweep_shift = 1; assert!(p.muted());
310 }
311
312 #[test]
313 fn output_zero_when_length_zero() {
314 let mut p = Pulse::new(true);
315 p.length.count = 0;
316 p.envelope.constant = true;
317 p.envelope.volume_or_period = 15;
318 assert_eq!(p.output(), 0);
319 }
320
321 #[test]
322 fn timer_hi_write_resets_duty_phase_not_divider() {
323 let mut p = Pulse::new(true);
326 p.step = 5;
327 p.timer = 42;
328 p.write_timer_hi(0x03);
329 assert_eq!(p.step, 0, "duty sequencer phase must reset to 0");
330 assert_eq!(p.timer, 42, "timer divider must be preserved");
331 assert!(p.envelope.start, "envelope restart flag must be set");
332 }
333
334 #[test]
335 fn length_load_only_when_enabled() {
336 let mut p = Pulse::new(true);
337 p.length.enabled = false;
338 p.write_timer_hi(0x08);
339 p.length.reload();
342 assert_eq!(p.length.count, 0);
343 p.length.enabled = true;
344 p.write_timer_hi(0x08);
345 p.length.reload();
346 assert_ne!(p.length.count, 0);
347 }
348}