furnace/src/engine/platform/sound/nes/cpu_inline.h
2022-04-06 00:34:12 -05:00

398 lines
11 KiB
C

/*
* 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 <stdlib.h>
#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_ */