From 3286c3c0c528e0736f36799d14325c14a27855b1 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 4 Dec 2021 01:19:54 -0500 Subject: [PATCH] NES system! almost --- CMakeLists.txt | 1 + README.md | 4 +- src/engine/engine.cpp | 4 + src/engine/platform/nes.cpp | 305 ++++++++++++++++++++ src/engine/platform/nes.h | 45 +++ src/engine/platform/pce.h | 4 + src/engine/platform/sound/nes/apu.c | 1 + src/engine/platform/sound/nes/apu.h | 17 +- src/engine/platform/sound/nes/cpu_inline.h | 317 +++++++++++++++++++++ 9 files changed, 688 insertions(+), 10 deletions(-) create mode 100644 src/engine/platform/nes.cpp create mode 100644 src/engine/platform/nes.h create mode 100644 src/engine/platform/sound/nes/cpu_inline.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 7169c2395..814717bb5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,6 +47,7 @@ src/engine/platform/genesisext.cpp src/engine/platform/sms.cpp src/engine/platform/gb.cpp src/engine/platform/pce.cpp +src/engine/platform/nes.cpp src/engine/platform/dummy.cpp) #imgui/imgui.cpp diff --git a/README.md b/README.md index 5af3f2e5f..3985c6197 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,10 @@ this is a work-in-progress chip music player (currently) for the .dmf format. ## features -- supports Sega Genesis, Master System, Game Boy and PC Engine (for now, with more systems coming soon) +- supports Sega Genesis, Master System, Game Boy, PC Engine and NES (for now, with more systems coming soon) - clean-room design (zero reverse-engineered code and zero decompilation; using official DMF specs, guesswork and ABX tests only) - bug/quirk implementation for increased playback accuracy -- accurate emulation cores (Nuked, MAME, SameBoy and Mednafen PCE) +- accurate emulation cores (Nuked, MAME, SameBoy, Mednafen PCE and puNES) - open-source. GPLv2. ## dependencies diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 6a01ab758..260524363 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -10,6 +10,7 @@ #include "platform/sms.h" #include "platform/gb.h" #include "platform/pce.h" +#include "platform/nes.h" #include "platform/dummy.h" #include #include @@ -756,6 +757,9 @@ bool DivEngine::init() { case DIV_SYSTEM_PCE: dispatch=new DivPlatformPCE; break; + case DIV_SYSTEM_NES: + dispatch=new DivPlatformNES; + break; default: logW("this system is not supported yet! using dummy platform.\n"); dispatch=new DivPlatformDummy; diff --git a/src/engine/platform/nes.cpp b/src/engine/platform/nes.cpp new file mode 100644 index 000000000..f25be9ff5 --- /dev/null +++ b/src/engine/platform/nes.cpp @@ -0,0 +1,305 @@ +#include "nes.h" +#include "sound/nes/cpu_inline.h" +#include "../engine.h" +#include + +#define FREQ_BASE 3424.0f + +void DivPlatformNES::acquire(int& l, int& r) { + if (dacSample!=-1) { + dacPeriod+=dacRate; + if (dacPeriod>=rate) { + DivSample* s=parent->song.sample[dacSample]; + if (s->depth==8) { + apu_wr_reg(0x4011,((unsigned char)s->rendData[dacPos++]+0x80)>>1); + } else { + apu_wr_reg(0x4011,((unsigned short)s->rendData[dacPos++]+0x8000)>>9); + } + if (dacPos>=s->rendLength) { + dacSample=-1; + } + dacPeriod-=rate; + } + } + + apu_tick(NULL); + apu.odd_cycle=!apu.odd_cycle; + if (apu.clocked) { + apu.clocked=false; + l=(pulse_output()+tnd_output())*30; + r=l; + //printf("output value: %d\n",l); + } +} + +static int dacRates[6]={ + 4000, 8000, 11025, 16000, 22050, 32000 +}; + +static unsigned char noiseTable[256]={ + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 7, 6, + 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, + 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, + 3, 2, 1, 0, 12, 11, 10, 9, 8, 7, 6, 5, 4, + 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, + 3, 2, 1, 0, 12, 11, 10, 9, 8, 7, 6, 5, 4, + 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, + 3, 2, 1, 0, 12, 11, 10, 9, 8, 7, 6, 5, 4, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15 +}; + +void DivPlatformNES::tick() { + for (int i=0; i<4; i++) { + chan[i].std.next(); + if (chan[i].std.hadVol) { + chan[i].outVol=(chan[i].vol*chan[i].std.vol)/15; + if (i==2) { // triangle + apu_wr_reg(0x4000+i*4,(chan[i].outVol==0)?0:255); + } else { + apu_wr_reg(0x4000+i*4,0x30|chan[i].outVol|((chan[i].duty&3)<<6)); + } + } + if (chan[i].std.hadArp) { + if (i==3) { // noise + if (chan[i].std.arpMode) { + chan[i].baseFreq=chan[i].std.arp+24; + } else { + chan[i].baseFreq=chan[i].note+chan[i].std.arp-12; + } + if (chan[i].baseFreq>255) chan[i].baseFreq=255; + if (chan[i].baseFreq<0) chan[i].baseFreq=0; + } else { + if (!chan[i].inPorta) { + if (chan[i].std.arpMode) { + chan[i].baseFreq=round(FREQ_BASE/pow(2.0f,((float)(chan[i].std.arp+24)/12.0f))); + } else { + chan[i].baseFreq=round(FREQ_BASE/pow(2.0f,((float)(chan[i].note+chan[i].std.arp-12)/12.0f))); + } + } + } + chan[i].freqChanged=true; + } else { + if (chan[i].std.arpMode && chan[i].std.finishedArp) { + chan[i].baseFreq=round(FREQ_BASE/pow(2.0f,((float)(chan[i].note)/12.0f))); + chan[i].freqChanged=true; + } + } + if (chan[i].std.hadDuty) { + chan[i].duty=chan[i].std.duty; + DivInstrument* ins=parent->getIns(chan[i].ins); + if (i!=2) { + apu_wr_reg(0x4000+i*4,0x30|chan[i].outVol|((chan[i].duty&3)<<6)); + } + } + if (chan[i].std.hadWave) { + if (chan[i].wave!=chan[i].std.wave) { + chan[i].wave=chan[i].std.wave; + if (i==2) { + if (!chan[i].keyOff) chan[i].keyOn=true; + } + } + } + if (chan[i].sweepChanged) { + chan[i].sweepChanged=false; + if (i==0) { + //rWrite(16+i*5,chan[i].sweep); + } + } + if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { + DivInstrument* ins=parent->getIns(chan[i].ins); + if (i==3) { // noise + chan[i].freq=noiseTable[chan[i].baseFreq]; + } else { + chan[i].freq=(chan[i].baseFreq*(ONE_SEMITONE-chan[i].pitch))/ONE_SEMITONE; + if (chan[i].freq>2047) chan[i].freq=2047; + } + if (chan[i].note>0x5d) chan[i].freq=0x01; + if (chan[i].keyOn) { + //rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(ins->gb.soundLen&63))); + //rWrite(16+i*5+2,((chan[i].vol<<4))|(ins->gb.envLen&7)|((ins->gb.envDir&1)<<3)); + } + if (chan[i].keyOff) { + //rWrite(16+i*5+2,8); + if (i==2) { // triangle + apu_wr_reg(0x4000+i*4,0x00); + } else { + apu_wr_reg(0x4000+i*4,0x30); + } + } + if (i==3) { // noise + apu_wr_reg(0x4002+i*4,chan[i].freq); + apu_wr_reg(0x4003+i*4,0xf0); + } else { + apu_wr_reg(0x4002+i*4,chan[i].freq&0xff); + if ((chan[i].prevFreq>>8)!=(chan[i].freq>>8) || i==2) { + apu_wr_reg(0x4003+i*4,0xf8|(chan[i].freq>>8)); + } + chan[i].prevFreq=chan[i].freq; + } + if (chan[i].keyOn) chan[i].keyOn=false; + if (chan[i].keyOff) chan[i].keyOff=false; + chan[i].freqChanged=false; + } + } +} + +int DivPlatformNES::dispatch(DivCommand c) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: + if (c.chan==4) { // PCM + dacSample=c.value%12; + if (dacSample>=parent->song.sampleLen) { + dacSample=-1; + break; + } + dacPos=0; + dacPeriod=0; + dacRate=dacRates[parent->song.sample[dacSample]->rate]; + break; + } else if (c.chan==3) { // noise + chan[c.chan].baseFreq=c.value; + } else { + chan[c.chan].baseFreq=round(FREQ_BASE/pow(2.0f,((float)c.value/12.0f))); + } + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + chan[c.chan].active=true; + chan[c.chan].keyOn=true; + chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + if (c.chan==2) { + apu_wr_reg(0x4000+c.chan*4,0xff); + } else { + apu_wr_reg(0x4000+c.chan*4,0x30|chan[c.chan].vol|((chan[c.chan].duty&3)<<6)); + } + break; + case DIV_CMD_NOTE_OFF: + chan[c.chan].active=false; + chan[c.chan].keyOff=true; + chan[c.chan].std.init(NULL); + break; + case DIV_CMD_INSTRUMENT: + if (chan[c.chan].ins!=c.value) { + chan[c.chan].ins=c.value; + } + break; + case DIV_CMD_VOLUME: + if (chan[c.chan].vol!=c.value) { + chan[c.chan].vol=c.value; + if (!chan[c.chan].std.hasVol) { + chan[c.chan].outVol=c.value; + } + if (c.chan==2) { + apu_wr_reg(0x4000+c.chan*4,0xff); + } else { + apu_wr_reg(0x4000+c.chan*4,0x30|chan[c.chan].vol|((chan[c.chan].duty&3)<<6)); + } + } + break; + case DIV_CMD_GET_VOLUME: + return chan[c.chan].vol; + break; + case DIV_CMD_PITCH: + chan[c.chan].pitch=c.value; + chan[c.chan].freqChanged=true; + break; + case DIV_CMD_NOTE_PORTA: { + int destFreq=round(FREQ_BASE/pow(2.0f,((float)c.value2/12.0f))); + bool return2=false; + if (destFreq>chan[c.chan].baseFreq) { + chan[c.chan].baseFreq+=c.value; + if (chan[c.chan].baseFreq>=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } else { + chan[c.chan].baseFreq-=c.value; + if (chan[c.chan].baseFreq<=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } + chan[c.chan].freqChanged=true; + if (return2) { + chan[c.chan].inPorta=false; + return 2; + } + break; + } + case DIV_CMD_STD_NOISE_MODE: + chan[c.chan].duty=c.value; + if (c.chan!=2) { + chan[c.chan].freqChanged=true; + } + break; + case DIV_CMD_PANNING: { + lastPan&=~(0x11<getIns(chan[c.chan].ins)); + chan[c.chan].inPorta=c.value; + break; + case DIV_CMD_GET_VOLMAX: + return 15; + break; + case DIV_ALWAYS_SET_VOLUME: + return 1; + break; + default: + break; + } + return 1; +} + +int DivPlatformNES::init(DivEngine* p, int channels, int sugRate) { + parent=p; + rate=1789773; + + dacPeriod=0; + dacPos=0; + dacRate=0; + dacSample=-1; + + init_nla_table(500,500); + + apu_turn_on(); + apu.addrSpace=new unsigned char[65536]; + apu.cpu_cycles=0; + apu.cpu_opcode_cycle=0; + + apu_wr_reg(0x4015,0x1f); + apu_wr_reg(0x4001,0x08); + apu_wr_reg(0x4005,0x08); + /*apu_wr_reg(0x4000,0x3f); + apu_wr_reg(0x4001,0x00); + apu_wr_reg(0x4002,0xff); + apu_wr_reg(0x4003,0x10); + apu_wr_reg(0x4004,0x3f); + apu_wr_reg(0x4005,0x00); + apu_wr_reg(0x4006,0xfe); + apu_wr_reg(0x4007,0x10);*/ + + lastPan=0xff; + return 5; +} diff --git a/src/engine/platform/nes.h b/src/engine/platform/nes.h new file mode 100644 index 000000000..e84ce5abf --- /dev/null +++ b/src/engine/platform/nes.h @@ -0,0 +1,45 @@ +#ifndef _APU_H +#define _APU_H + +#include "../dispatch.h" +#include "../macroInt.h" + +class DivPlatformNES: public DivDispatch { + struct Channel { + int freq, baseFreq, pitch, prevFreq; + unsigned char ins, note, duty, sweep; + bool active, insChanged, freqChanged, sweepChanged, keyOn, keyOff, inPorta; + signed char vol, outVol, wave; + DivMacroInt std; + Channel(): + freq(0), + baseFreq(0), + pitch(0), + prevFreq(-1), + ins(-1), + note(0), + duty(0), + sweep(0), + active(false), + insChanged(true), + freqChanged(false), + sweepChanged(false), + keyOn(false), + keyOff(false), + inPorta(false), + vol(15), + wave(-1) {} + }; + Channel chan[5]; + int dacPeriod, dacRate, dacPos, dacSample; + unsigned char lastPan; + + void updateWave(); + public: + void acquire(int& l, int& r); + int dispatch(DivCommand c); + void tick(); + int init(DivEngine* parent, int channels, int sugRate); +}; + +#endif diff --git a/src/engine/platform/pce.h b/src/engine/platform/pce.h index 0d981d9dc..689c04955 100644 --- a/src/engine/platform/pce.h +++ b/src/engine/platform/pce.h @@ -18,6 +18,10 @@ class DivPlatformPCE: public DivDispatch { freq(0), baseFreq(0), pitch(0), + dacPeriod(0), + dacRate(0), + dacPos(0), + dacSample(0), ins(-1), note(0), pan(255), diff --git a/src/engine/platform/sound/nes/apu.c b/src/engine/platform/sound/nes/apu.c index d1ef33221..bcfe45778 100644 --- a/src/engine/platform/sound/nes/apu.c +++ b/src/engine/platform/sound/nes/apu.c @@ -224,4 +224,5 @@ void apu_turn_on(void) { // http://forums.nesdev.com/viewtopic.php?f=3&t=18278 DMC.length = 1; DMC.address_start = 0xC000; + apu.odd_cycle = 0; } diff --git a/src/engine/platform/sound/nes/apu.h b/src/engine/platform/sound/nes/apu.h index 5089220ee..6d8f9b2f0 100644 --- a/src/engine/platform/sound/nes/apu.h +++ b/src/engine/platform/sound/nes/apu.h @@ -27,10 +27,6 @@ enum dmc_types_of_dma { DMC_NORMAL, DMC_CPU_WRITE, DMC_R4014, DMC_NNL_DMA }; enum apu_channels { APU_S1, APU_S2, APU_TR, APU_NS, APU_DMC, APU_EXTRA, APU_MASTER }; enum apu_mode { APU_60HZ, APU_48HZ }; -int cpu_cycles; -int cpu_opcode_cycle; -unsigned char* addrSpace; - #define apu_pre_amp 1.4f /* length counter */ @@ -211,14 +207,14 @@ unsigned char* addrSpace; break;\ }\ {\ - DMC.buffer = addrSpace[DMC.address];\ + DMC.buffer = apu.addrSpace[DMC.address];\ }\ /* incremento gli hwtick da compiere */\ if (hwtick) { hwtick[0] += tick; }\ /* e naturalmente incremento anche quelli eseguiti dall'opcode */\ - cpu_cycles += tick;\ + apu.cpu_cycles += tick;\ /* salvo a che ciclo dell'istruzione avviene il dma */\ - DMC.dma_cycle = cpu_opcode_cycle;\ + DMC.dma_cycle = apu.cpu_opcode_cycle;\ /* il DMC non e' vuoto */\ DMC.empty = FALSE;\ if (++DMC.address > 0xFFFF) {\ @@ -340,7 +336,7 @@ unsigned char* addrSpace; }\ } #define _apu_channel_volume_adjust(ch, index)\ - ((ch * cfg->apu.channel[index]) * ch_gain_ptnd(index)) + ((ch)) #define s1_out\ _apu_channel_volume_adjust(S1.output, APU_S1) #define s2_out\ @@ -370,6 +366,11 @@ typedef struct _apu { BYTE DMC; SWORD cycles; + int cpu_cycles; + int cpu_opcode_cycle; + unsigned char* addrSpace; + BYTE odd_cycle; + /* ------------------------------------------------------- */ /* questi valori non e' necessario salvarli nei savestates */ /* ------------------------------------------------------- */ diff --git a/src/engine/platform/sound/nes/cpu_inline.h b/src/engine/platform/sound/nes/cpu_inline.h new file mode 100644 index 000000000..3cb85e824 --- /dev/null +++ b/src/engine/platform/sound/nes/cpu_inline.h @@ -0,0 +1,317 @@ +/* + * 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" + +#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(WORD address, BYTE value) { + if (!(address & 0x0010)) { + /* -------------------- square 1 --------------------*/ + if (address <= 0x4003) { + if (address == 0x4000) { + square_reg0(S1); + return; + } + if (address == 0x4001) { + square_reg1(S1); + sweep_silence(S1) + return; + } + if (address == 0x4002) { + square_reg2(S1); + sweep_silence(S1) + return; + } + if (address == 0x4003) { + square_reg3(S1); + sweep_silence(S1) + return; + } + return; + } + /* -------------------- square 2 --------------------*/ + if (address <= 0x4007) { + if (address == 0x4004) { + square_reg0(S2); + return; + } + if (address == 0x4005) { + square_reg1(S2); + sweep_silence(S2) + return; + } + if (address == 0x4006) { + square_reg2(S2); + sweep_silence(S2) + return; + } + if (address == 0x4007) { + square_reg3(S2); + sweep_silence(S2) + return; + } + return; + } + /* -------------------- triangle --------------------*/ + if (address <= 0x400B) { + if (address == 0x4008) { + /* length counter */ + /* + * il triangle ha una posizione diversa per il + * flag LCHalt. + */ + TR.length.halt = value & 0x80; + /* linear counter */ + TR.linear.reload = value & 0x7F; + return; + } + if (address == 0x400A) { + /* timer (low 8 bits) */ + TR.timer = (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 (TR.length.enabled && !(apu.length_clocked && TR.length.value)) { + TR.length.value = length_table[value >> 3]; + } + /* timer (high 3 bits) */ + TR.timer = (TR.timer & 0x00FF) | ((value & 0x07) << 8); + /* + * scrivendo in questo registro si setta + * automaticamente l'halt flag del triangle. + */ + TR.linear.halt = TRUE; + return; + } + return; + } + /* --------------------- noise ----------------------*/ + if (address <= 0x400F) { + if (address == 0x400C) { + NS.length.halt = value & 0x20; + /* envelope */ + NS.envelope.constant_volume = value & 0x10; + NS.envelope.divider = value & 0x0F; + return; + } + if (address == 0x400E) { + NS.mode = value & 0x80; + 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 (NS.length.enabled && !(apu.length_clocked && NS.length.value)) { + NS.length.value = length_table[value >> 3]; + } + /* envelope */ + NS.envelope.enabled = TRUE; + return; + } + return; + } + return; + } else { + /* ---------------------- DMC -----------------------*/ + if (address <= 0x4013) { + if (address == 0x4010) { + DMC.irq_enabled = value & 0x80; + /* se l'irq viene disabilitato allora... */ + if (!DMC.irq_enabled) { + /* ...azzero l'interrupt flag del DMC */ + r4015.value &= 0x7F; + } + DMC.loop = value & 0x40; + DMC.rate_index = value & 0x0F; + return; + } + if (address == 0x4011) { + BYTE save = 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 (r4011.frames > 1) { + r4011.output = (value - save) >> 3; + DMC.counter = DMC.output = save + r4011.output; + } else { + DMC.counter = DMC.output = value; + } + apu.clocked = TRUE; + + r4011.cycles = r4011.frames = 0; + r4011.value = value; + return; + } + if (address == 0x4012) { + DMC.address_start = (value << 6) | 0xC000; + return; + } + if (address == 0x4013) { + /* sample length */ + 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. + */ + r4015.value = (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 (!(S1.length.enabled = r4015.value & 0x01)) { + S1.length.value = 0; + } + if (!(S2.length.enabled = r4015.value & 0x02)) { + S2.length.value = 0; + } + if (!(TR.length.enabled = r4015.value & 0x04)) { + TR.length.value = 0; + } + if (!(NS.length.enabled = r4015.value & 0x08)) { + 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 (!(r4015.value & 0x10)) { + DMC.remain = 0; + DMC.empty = TRUE; + } else if (!DMC.remain) { + DMC.remain = DMC.length; + DMC.address = 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 */ + 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 (apu.odd_cycle) { + r4017.jitter.delay = TRUE; + } else { + 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; +} +#endif /* CPU_INLINE_H_ */