/* * Copyright (C) 2010-2019 Fabio Cavallo (aka FHorse) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef CPU_INLINE_H_ #define CPU_INLINE_H_ #include #include "apu.h" #include "fds.h" #define mod_cycles_op(op, vl) cpu.cycles op vl #define r2006_during_rendering()\ if (!ppu.vblank && r2001.visible && (ppu.frame_y > ppu_sclines.vint) &&\ (ppu.screen_y < SCR_LINES)) {\ _r2006_during_rendering()\ } else {\ r2006.value += r2000.r2006_inc;\ } #define _r2006_during_rendering()\ r2006_inc()\ if ((r2006.value & 0x1F) == 0x1F) {\ r2006.value ^= 0x41F;\ } else {\ r2006.value++;\ } INLINE static void apu_wr_reg(struct NESAPU* a, WORD address, BYTE value) { if (!(address & 0x0010)) { /* -------------------- square 1 --------------------*/ if (address <= 0x4003) { if (address == 0x4000) { square_reg0(a->S1); return; } if (address == 0x4001) { square_reg1(a->S1); sweep_silence(a->S1) return; } if (address == 0x4002) { square_reg2(a->S1); sweep_silence(a->S1) return; } if (address == 0x4003) { square_reg3(a->S1,a->apu.length_clocked); sweep_silence(a->S1) return; } return; } /* -------------------- square 2 --------------------*/ if (address <= 0x4007) { if (address == 0x4004) { square_reg0(a->S2); return; } if (address == 0x4005) { square_reg1(a->S2); sweep_silence(a->S2) return; } if (address == 0x4006) { square_reg2(a->S2); sweep_silence(a->S2) return; } if (address == 0x4007) { square_reg3(a->S2,a->apu.length_clocked); sweep_silence(a->S2) return; } return; } /* -------------------- triangle --------------------*/ if (address <= 0x400B) { if (address == 0x4008) { /* length counter */ /* * il triangle ha una posizione diversa per il * flag LCHalt. */ a->TR.length.halt = value & 0x80; /* linear counter */ a->TR.linear.reload = value & 0x7F; return; } if (address == 0x400A) { /* timer (low 8 bits) */ a->TR.timer = (a->TR.timer & 0x0700) | value; return; } if (address == 0x400B) { /* length counter */ /* * se non disabilitato, una scrittura in * questo registro, carica immediatamente il * length counter del canale, tranne nel caso * in cui la scrittura avvenga nello stesso * momento del clock di un length counter e * con il length diverso da zero. */ if (a->TR.length.enabled && !(a->apu.length_clocked && a->TR.length.value)) { a->TR.length.value = length_table[value >> 3]; } /* timer (high 3 bits) */ a->TR.timer = (a->TR.timer & 0x00FF) | ((value & 0x07) << 8); /* * scrivendo in questo registro si setta * automaticamente l'halt flag del triangle. */ a->TR.linear.halt = TRUE; return; } return; } /* --------------------- noise ----------------------*/ if (address <= 0x400F) { if (address == 0x400C) { a->NS.length.halt = value & 0x20; /* envelope */ a->NS.envelope.constant_volume = value & 0x10; a->NS.envelope.divider = value & 0x0F; return; } if (address == 0x400E) { a->NS.mode = value & 0x80; a->NS.timer = value & 0x0F; return; } if (address == 0x400F) { /* * se non disabilitato, una scrittura in * questo registro, carica immediatamente il * length counter del canale, tranne nel caso * in cui la scrittura avvenga nello stesso * momento del clock di un length counter e * con il length diverso da zero. */ if (a->NS.length.enabled && !(a->apu.length_clocked && a->NS.length.value)) { a->NS.length.value = length_table[value >> 3]; } /* envelope */ a->NS.envelope.enabled = TRUE; return; } return; } return; } else { /* ---------------------- DMC -----------------------*/ if (address <= 0x4013) { if (address == 0x4010) { a->DMC.irq_enabled = value & 0x80; /* se l'irq viene disabilitato allora... */ if (!a->DMC.irq_enabled) { /* ...azzero l'interrupt flag del DMC */ a->r4015.value &= 0x7F; } a->DMC.loop = value & 0x40; a->DMC.rate_index = value & 0x0F; return; } if (address == 0x4011) { BYTE save = a->DMC.counter; value &= 0x7F; /* * questa lo faccio perche' in alcuni giochi come Batman, * Ninja Gaiden 3, Castlevania II ed altri, producono * un popping del suono fastidioso; * from Fceu doc: * Why do some games make a popping sound (Batman, Ninja Gaiden 3, * Castlevania II etc.)? These games do a very crude drum imitation * by causing a large jump in the output level for a short period of * time via the register at $4011. The analog filters on a real * Famicom make it sound decent(better). I have not completely * emulated these filters. * (Xodnizel) */ if (a->r4011.frames > 1) { a->r4011.output = (value - save) >> 3; a->DMC.counter = a->DMC.output = save + a->r4011.output; } else { a->DMC.counter = a->DMC.output = value; } a->apu.clocked = TRUE; a->r4011.cycles = a->r4011.frames = 0; a->r4011.value = value; return; } if (address == 0x4012) { a->DMC.address_start = (value << 6) | 0xC000; return; } if (address == 0x4013) { /* sample length */ a->DMC.length = (value << 4) | 0x01; return; } return; } /* --------------------------------------------------*/ if (address == 0x4015) { /* * 76543210 * || ||||| * || ||||+- Pulse channel 1's length counter enabled flag * || |||+-- Pulse channel 2's length counter enabled flag * || ||+--- Triangle channel's length counter enabled flag * || |+---- Noise channel's length counter enabled flag * || +----- If clear, the DMC's bytes remaining is set to 0, * || otherwise the DMC sample is restarted only if the * || DMC's bytes remaining is 0 * |+------- Frame interrupt flag * +-------- DMC interrupt flag */ /* * dopo la write il bit 7 (dmc flag) deve * essere azzerato mentre lascio inalterati * i bit 5 e 6. */ a->r4015.value = (a->r4015.value & 0x60) | (value & 0x1F); /* * quando il flag di abilitazione del length * counter di ogni canale e' a 0, il counter * dello stesso canale e' immediatamente azzerato. */ if (!(a->S1.length.enabled = a->r4015.value & 0x01)) { a->S1.length.value = 0; } if (!(a->S2.length.enabled = a->r4015.value & 0x02)) { a->S2.length.value = 0; } if (!(a->TR.length.enabled = a->r4015.value & 0x04)) { a->TR.length.value = 0; } if (!(a->NS.length.enabled = a->r4015.value & 0x08)) { a->NS.length.value = 0; } /* * se il bit 4 e' 0 allora devo azzerare i bytes * rimanenti del DMC, alrimenti devo riavviare * la lettura dei sample DMC solo nel caso che * in cui i bytes rimanenti siano a 0. */ if (!(a->r4015.value & 0x10)) { a->DMC.remain = 0; a->DMC.empty = TRUE; } else if (!a->DMC.remain) { a->DMC.remain = a->DMC.length; a->DMC.address = a->DMC.address_start; } return; } #if defined (VECCHIA_GESTIONE_JITTER) if (address == 0x4017) { /* APU frame counter */ r4017.jitter.value = value; /* * nell'2A03 se la scrittura del $4017 avviene * in un ciclo pari, allora l'effettiva modifica * avverra' nel ciclo successivo. */ if (cpu.odd_cycle) { r4017.jitter.delay = TRUE; } else { r4017.jitter.delay = FALSE; r4017_jitter(); } return; } #else if (address == 0x4017) { /* APU frame counter */ a->r4017.jitter.value = value; /* * nell'2A03 se la scrittura del $4017 avviene * in un ciclo pari, allora l'effettiva modifica * avverra' nel ciclo successivo. */ if (a->apu.odd_cycle) { a->r4017.jitter.delay = TRUE; } else { a->r4017.jitter.delay = FALSE; r4017_jitter(1) r4017_reset_frame() } return; } #endif } #if defined (DEBUG) //fprintf(stderr, "Alert: Attempt to write APU port %04X\n", address); #endif return; } INLINE static BYTE fds_wr_mem(struct _fds* fds, WORD address, BYTE value) { if (address == 0x4023) { fds->enabled_snd_reg=value&0x02; } if ((address >= 0x4040) && (address <= 0x408A)) { if (fds->enabled_snd_reg) { if ((address >= 0x4040) && (address <= 0x407F)) { fds->snd.wave.data[address & 0x003F] = value & 0x3F; return (TRUE); } if (address == 0x4080) { fds->snd.volume.speed = value & 0x3F; fds->snd.volume.increase = value & 0x40; fds->snd.volume.mode = value & 0x80; return (TRUE); } if (address == 0x4082) { fds->snd.main.frequency = (fds->snd.main.frequency & 0xFF00) | value; return (TRUE); } if (address == 0x4083) { fds->snd.main.frequency = ((value & 0x0F) << 8) | (fds->snd.main.frequency & 0x00FF); fds->snd.envelope.disabled = value & 0x40; fds->snd.main.silence = value & 0x80; return (TRUE); } if (address == 0x4084) { fds->snd.sweep.speed = value & 0x3F; fds->snd.sweep.increase = value & 0x40; fds->snd.sweep.mode = value & 0x80; return (TRUE); } if (address == 0x4085) { fds->snd.sweep.bias = ((SBYTE) (value << 1)) / 2; fds->snd.modulation.index = 0; return (TRUE); } if (address == 0x4086) { fds->snd.modulation.frequency = (fds->snd.modulation.frequency & 0xFF00) | value; return (TRUE); } if (address == 0x4087) { fds->snd.modulation.frequency = ((value & 0x0F) << 8) | (fds->snd.modulation.frequency & 0x00FF); fds->snd.modulation.disabled = value & 0x80; return (TRUE); } if (address == 0x4088) { BYTE i; // 0,2,4,6,-8,-6,-4,-2 for (i = 0; i < 32; i++) { BYTE a = i << 1; if (i < 31) { fds->snd.modulation.data[a] = fds->snd.modulation.data[a + 2]; } else { BYTE tmp = ((value & 0x03) | (0x3F * (value & 0x04))); fds->snd.modulation.data[a] = (SBYTE) tmp; } fds->snd.modulation.data[a + 1] = fds->snd.modulation.data[a]; } return (TRUE); } if (address == 0x4089) { fds->snd.wave.writable = value & 0x80; fds->snd.wave.volume = value & 0x03; return (TRUE); } if (address == 0x408A) { fds->snd.envelope.speed = value; return (TRUE); } } } return (FALSE); } #endif /* CPU_INLINE_H_ */