1use crate::{
6 apu::PULSE_TABLE,
7 cart::Cart,
8 common::{Clock, Regional, Reset, Sample, Sram},
9 cpu::{Cpu, Irq},
10 mapper::{
11 self, MapRead, MapWrite, MappedRead, MappedWrite, Mapper, Mirrored, OnBusRead, OnBusWrite,
12 },
13 mem::Banks,
14 ppu::Mirroring,
15};
16use serde::{Deserialize, Serialize};
17
18#[derive(Default, Debug, Clone, Serialize, Deserialize)]
20#[must_use]
21pub struct Regs {
22 command: u8,
23 parameter: u8,
24 prg_ram_enabled: bool,
25 irq_enabled: bool,
26 irq_counter_enabled: bool,
27 irq_counter: u16,
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
32#[must_use]
33pub struct SunsoftFme7 {
34 pub regs: Regs,
35 pub mirroring: Mirroring,
36 pub audio: Audio,
37 pub chr_banks: Banks,
38 pub prg_banks: Banks,
39 pub prg_rom_banks: Banks,
40}
41
42impl SunsoftFme7 {
43 const PRG_WINDOW: usize = 8 * 1024;
44 const PRG_RAM_SIZE: usize = 32 * 1024;
45 const CHR_WINDOW: usize = 1024;
46
47 pub fn load(cart: &mut Cart) -> Result<Mapper, mapper::Error> {
48 cart.add_prg_ram(Self::PRG_RAM_SIZE);
49 let mut sunsoft_fme7 = Self {
50 regs: Regs::default(),
51 mirroring: cart.mirroring(),
52 audio: Audio::new(),
53 chr_banks: Banks::new(0x0000, 0x1FFF, cart.chr_rom.len(), Self::CHR_WINDOW)?,
54 prg_banks: Banks::new(0x6000, 0x7FFF, cart.prg_ram.len(), Self::PRG_WINDOW)?,
55 prg_rom_banks: Banks::new(0x8000, 0xFFFF, cart.prg_rom.len(), Self::PRG_WINDOW)?,
56 };
57 sunsoft_fme7
58 .prg_rom_banks
59 .set(3, sunsoft_fme7.prg_rom_banks.last());
60 Ok(sunsoft_fme7.into())
61 }
62}
63
64impl Mirrored for SunsoftFme7 {
65 fn mirroring(&self) -> Mirroring {
66 self.mirroring
67 }
68
69 fn set_mirroring(&mut self, mirroring: Mirroring) {
70 self.mirroring = mirroring;
71 }
72}
73
74impl MapRead for SunsoftFme7 {
75 fn map_peek(&self, addr: u16) -> MappedRead {
91 match addr {
92 0x0000..=0x1FFF => MappedRead::Chr(self.chr_banks.translate(addr)),
93 0x6000..=0x7FFF => {
94 if self.regs.prg_ram_enabled {
95 MappedRead::PrgRam(self.prg_banks.translate(addr))
96 } else {
97 MappedRead::PrgRom(self.prg_banks.translate(addr))
98 }
99 }
100 0x8000..=0xFFFF => MappedRead::PrgRom(self.prg_rom_banks.translate(addr)),
101 _ => MappedRead::Bus,
102 }
103 }
104}
105
106impl MapWrite for SunsoftFme7 {
107 fn map_write(&mut self, addr: u16, val: u8) -> MappedWrite {
108 match addr {
109 0x6000..=0x7FFF => {
110 if self.regs.prg_ram_enabled {
111 return MappedWrite::PrgRam(self.prg_banks.translate(addr), val);
112 }
113 }
114 0x8000..=0x9FFF => self.regs.command = val & 0x0F,
115 0xA000..=0xBFFF => match self.regs.command {
116 0..=7 => self.chr_banks.set(self.regs.command.into(), val.into()),
117 8 => {
118 self.regs.parameter = val;
119 self.regs.prg_ram_enabled = val & 0x80 == 0x80;
120 self.prg_banks.set(0, (val & 0x3F).into());
121 }
122 9..=0xB => {
123 let bank = self.regs.command - 9;
124 self.prg_rom_banks.set(bank.into(), (val & 0x3F).into());
125 }
126 0xC => self.set_mirroring(match val & 0x03 {
127 0 => Mirroring::Vertical,
128 1 => Mirroring::Horizontal,
129 2 => Mirroring::SingleScreenA,
130 _ => Mirroring::SingleScreenB,
131 }),
132 0xD => {
133 self.regs.irq_enabled = (val & 0x01) == 0x01;
134 self.regs.irq_counter_enabled = (val & 0x80) == 0x80;
135 Cpu::clear_irq(Irq::MAPPER);
136 }
137 0xE => self.regs.irq_counter = (self.regs.irq_counter & 0xFF00) | u16::from(val),
138 0xF => {
139 self.regs.irq_counter = (self.regs.irq_counter & 0xFF) | (u16::from(val) << 8);
140 }
141 _ => (),
142 },
143 0xC000..=0xFFFF => self.audio.write_register(addr, val),
144 _ => return MappedWrite::Bus,
145 }
146 MappedWrite::None
147 }
148}
149
150impl OnBusRead for SunsoftFme7 {}
151impl OnBusWrite for SunsoftFme7 {}
152impl Reset for SunsoftFme7 {}
153
154impl Clock for SunsoftFme7 {
155 fn clock(&mut self) -> u64 {
156 let cycles = if self.regs.irq_counter_enabled {
157 self.regs.irq_counter = self.regs.irq_counter.wrapping_sub(1);
158 if self.regs.irq_counter == 0xFFFF && self.regs.irq_enabled {
159 Cpu::set_irq(Irq::MAPPER);
160 }
161 1
162 } else {
163 0
164 };
165 self.audio.clock();
166 cycles
167 }
168}
169
170impl Regional for SunsoftFme7 {}
171impl Sram for SunsoftFme7 {}
172
173impl Sample for SunsoftFme7 {
174 fn output(&self) -> f32 {
175 self.audio.output()
176 }
177}
178
179#[derive(Debug, Copy, Clone, Serialize, Deserialize)]
180#[must_use]
181pub struct Audio {
182 clock_timer: u8,
183 register: u8,
184 registers: [u8; 16],
185 volumes: [u8; 16],
186 timers: [i16; 3],
187 steps: [u8; 3],
188 out: f32,
189}
190
191impl Default for Audio {
192 fn default() -> Self {
193 Self::new()
194 }
195}
196
197impl Audio {
198 pub fn new() -> Self {
199 let mut audio = Self {
200 clock_timer: 1,
201 register: 0,
202 registers: [0; 16],
203 volumes: [0; 16],
204 timers: [0; 3],
205 steps: [0; 3],
206 out: 0.0,
207 };
208 let mut output = 1.0;
209 for volume in audio.volumes.iter_mut().skip(1) {
210 output *= 1.188_502_227_437_018_5;
212 output *= 1.188_502_227_437_018_5;
213 *volume = output as u8;
214 }
215 audio
216 }
217
218 #[must_use]
219 #[inline]
220 pub fn output(&self) -> f32 {
221 let pulse_scale = PULSE_TABLE[PULSE_TABLE.len() - 1] / 15.0;
222 pulse_scale * self.out
223 }
224
225 #[must_use]
226 #[inline]
227 pub fn period(&self, channel: usize) -> u16 {
228 let register = channel * 2;
229 u16::from(self.registers[register]) | (u16::from(self.registers[register + 1]) << 8)
230 }
231
232 #[must_use]
233 #[inline]
234 pub fn envelope_period(&self) -> u16 {
235 u16::from(self.registers[0x0B]) | (u16::from(self.registers[0x0C]) << 8)
236 }
237
238 #[must_use]
239 #[inline]
240 pub const fn noise_period(&self) -> u8 {
241 self.registers[0x06]
242 }
243
244 #[must_use]
245 #[inline]
246 pub const fn volume(&self, channel: usize) -> u8 {
247 self.volumes[(self.registers[channel + 8] & 0x0F) as usize]
248 }
249
250 #[must_use]
251 #[inline]
252 pub const fn envelope_enabled(&self, channel: usize) -> bool {
253 self.registers[channel + 8] & 0x10 == 0x10
254 }
255
256 #[must_use]
257 #[inline]
258 pub const fn square_enabled(&self, channel: usize) -> bool {
259 (self.registers[0x07] >> channel) & 0x01 == 0x00
260 }
261
262 #[must_use]
263 #[inline]
264 pub const fn noise_enabled(&self, channel: usize) -> bool {
265 (self.registers[0x07] >> (channel + 3)) & 0x01 == 0x00
266 }
267
268 const fn write_register(&mut self, addr: u16, val: u8) {
269 match addr {
270 0xC000..=0xDFFF => self.register = val,
271 0xE000..=0xFFFF => {
272 if self.register <= 0x0F {
273 self.registers[self.register as usize] = val;
274 }
275 }
276 _ => (),
277 }
278 }
279}
280
281impl Clock for Audio {
282 fn clock(&mut self) -> u64 {
283 if self.clock_timer == 0 {
284 self.clock_timer = 1;
285 for channel in 0..3 {
286 self.timers[channel] -= 1;
287 if self.timers[channel] <= 0 {
288 self.timers[channel] = self.period(channel) as i16;
289 self.steps[channel] = (self.steps[channel] + 1) & 0x0F;
290 }
291 }
292 self.out = [0, 1, 2]
293 .into_iter()
294 .filter(|&channel| self.square_enabled(channel) && self.steps[channel] < 0x08)
295 .map(|channel| self.volume(channel) as f32)
296 .sum();
297 return 1;
298 }
299 self.clock_timer -= 1;
300 0
301 }
302}