tetanes_core/mapper/
m069_sunsoft_fme7.rs

1//! `Sunsoft FME7` (Mapper 069).
2//!
3//! <https://www.nesdev.org/wiki/Sunsoft_FME-7>
4
5use 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/// `Sunsoft FME7` registers.
19#[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/// `Sunsoft FME7` (Mapper 069).
31#[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    // PPU $0000..=$03FF 1K CHR-ROM Bank 1 Switchable
76    // PPU $0400..=$07FF 1K CHR-ROM Bank 2 Switchable
77    // PPU $0800..=$0BFF 1K CHR-ROM Bank 3 Switchable
78    // PPU $0C00..=$0FFF 1K CHR-ROM Bank 4 Switchable
79    // PPU $1000..=$13FF 1K CHR-ROM Bank 5 Switchable
80    // PPU $1400..=$17FF 1K CHR-ROM Bank 6 Switchable
81    // PPU $1800..=$1BFF 1K CHR-ROM Bank 7 Switchable
82    // PPU $1C00..=$1FFF 1K CHR-ROM Bank 8 Switchable
83
84    // CPU $6000..=$7FFF 8K PRG-ROM or PRG-RAM Bank 1 Switchable
85    // CPU $8000..=$9FFF 8K PRG-ROM Bank 1 Switchable
86    // CPU $A000..=$BFFF 8K PRG-ROM Bank 2 Switchable
87    // CPU $C000..=$DFFF 8K PRG-ROM Bank 3 Switchable
88    // CPU $E000..=$FFFF 8K PRG-ROM Bank 4 fixed to last
89
90    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            // +1.5dB 2x for every 1 step in volume
211            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}