jevsnes.git / third-party / rust / jgenesis / backend / gba-core / src / apu / psg / wavetable.rs

PSG channel 3 (wavetable / custom wave)

3use bincode::{Decode, Encode};
4use gb_core::apu::components::{TimerTickEffect, WavetableLengthCounter, WavetableTimer};
5use jgenesis_common::define_bit_enum;
6use jgenesis_common::num::GetBit;
7use std::array;
9const WAVE_RAM_BANK_LEN: usize = 16;
10
11define_bit_enum!(WaveRamSizeBanks, [One, Two]);
12
13#[derive(Debug, Clone, Copy, Encode, Decode)]
14pub struct WavetableChannel {
15    wave_ram: [[u8; WAVE_RAM_BANK_LEN]; 2],
16    sample_buffer: u8,
17    wave_ram_size: WaveRamSizeBanks,
18    selected_bank: bool,
19    timer: WavetableTimer,
20    length_counter: WavetableLengthCounter,
21    volume: u8,
22    force_volume_75: bool,
23    enabled: bool,
24    active: bool,
25}
26
27impl WavetableChannel {
28    pub fn new() -> Self {
29        Self {
30            wave_ram: array::from_fn(|_| array::from_fn(|_| 0)),
31            sample_buffer: 0,
32            wave_ram_size: WaveRamSizeBanks::default(),
33            selected_bank: false,
34            timer: WavetableTimer::new(),
35            length_counter: WavetableLengthCounter::new(),
36            volume: 0,
37            force_volume_75: false,
38            enabled: false,
39            active: false,
40        }
41    }
42
43    pub fn tick_2mhz(&mut self) {
44        if !self.active {
45            return;
46        }
47
48        if self.timer.tick() == TimerTickEffect::Clocked {
49            if self.wave_ram_size == WaveRamSizeBanks::Two && self.timer.phase == 0 {
50                self.selected_bank = !self.selected_bank;
51            }
52
53            self.sample_buffer =
54                self.wave_ram[usize::from(self.selected_bank)][(self.timer.phase >> 1) as usize];
55        }
56    }
57
58    pub fn clock_length_counter(&mut self) {
59        let prev_active = self.active;
60        self.length_counter.clock(&mut self.active);
61
62        if prev_active && !self.active {
63            // Explicitly clear sample buffer when length counter disables channel
64            self.sample_buffer = 0;
65        }
66    }
67
68    pub fn sample(&self) -> u8 {
69        if !self.enabled {
70            return 0;
71        }
72
73        let sample = if !self.timer.phase.bit(0) {
74            self.sample_buffer >> 4
75        } else {
76            self.sample_buffer & 0xF
77        };
78
79        if self.force_volume_75 {
80            (3 * sample) >> 2
81        } else if self.volume == 0 {
82            0
83        } else {
84            sample >> (self.volume - 1)
85        }
86    }
87
88    pub fn active(&self) -> bool {
89        self.active
90    }
91
92    pub fn write_nr30(&mut self, value: u8) {
93        self.wave_ram_size = WaveRamSizeBanks::from_bit(value.bit(5));
94        self.selected_bank = value.bit(6);
95        self.enabled = value.bit(7);
96
97        self.active &= self.enabled;
98
99        log::trace!("NR30 write: {value:02X}");
100        log::trace!("  Wave RAM banks: {:?}", self.wave_ram_size);
101        log::trace!("  Playback wave RAM bank: {}", u8::from(self.selected_bank));
102        log::trace!("  Channel enabled: {}", self.enabled);
103    }
104
105    pub fn read_nr30(&self) -> u8 {
106        0x1F | ((self.wave_ram_size as u8) << 5)
107            | (u8::from(self.selected_bank) << 6)
108            | (u8::from(self.enabled) << 7)
109    }
110
111    pub fn write_nr31(&mut self, value: u8) {
112        self.length_counter.load(value);
113
114        log::trace!("NR31 write: {value:02X} (length counter load)");
115    }
116
117    pub fn write_nr32(&mut self, value: u8) {
118        self.volume = (value >> 5) & 3;
119        self.force_volume_75 = value.bit(7);
120
121        log::trace!("NR32 write: {value:02X}");
122        log::trace!("  Volume: {}", ["0%", "100%", "50%", "25%"][self.volume as usize]);
123        log::trace!("  Force volume to 75%: {}", self.volume);
124    }
125
126    pub fn read_nr32(&self) -> u8 {
127        0x1F | (self.volume << 5) | (u8::from(self.force_volume_75) << 7)
128    }
129
130    pub fn write_nr33(&mut self, value: u8) {
131        self.timer.write_frequency_low(value);
132
133        log::trace!("NR33 write: {value:02X}");
134        log::trace!("  Timer frequency: {}", self.timer.frequency());
135    }
136
137    pub fn read_nr34(&self) -> u8 {
138        0xBF | (u8::from(self.length_counter.enabled) << 6)
139    }
140
141    pub fn write_nr34(&mut self, value: u8, frame_sequencer_step: u8) {
142        self.timer.write_frequency_high(value);
143        self.length_counter.set_enabled(value.bit(4), frame_sequencer_step, &mut self.active);
144
145        if value.bit(7) {
146            // Channel triggered
147            self.timer.trigger();
148            self.timer.phase = 0;
149
150            self.length_counter.trigger(frame_sequencer_step);
151
152            self.active = self.enabled;
153        }
154
155        log::trace!("NR34 write: {value:02X}");
156        log::trace!("  Timer frequency: {}", self.timer.frequency());
157        log::trace!("  Length counter enabled: {}", self.length_counter.enabled);
158        log::trace!("  Triggered: {}", value.bit(7));
159    }
160
161    pub fn read_wave_ram(&self, address: u32) -> u8 {
162        // Reads always go to the inactive bank
163        let wave_ram_addr = (address & 0xF) as usize;
164        let bank: usize = (!self.selected_bank).into();
165        self.wave_ram[bank][wave_ram_addr]
166    }
167
168    pub fn write_wave_ram(&mut self, address: u32, value: u8) {
169        // Writes always go to the inactive bank
170        let wave_ram_addr = (address & 0xF) as usize;
171        let bank: usize = (!self.selected_bank).into();
172        self.wave_ram[bank][wave_ram_addr] = value;
173    }
174}