diff --git a/CMakeLists.txt b/CMakeLists.txt index 353ca9e97..7648cb588 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -237,6 +237,7 @@ src/engine/platform/sound/gb/timing.c src/engine/platform/sound/pce_psg.cpp src/engine/platform/sound/nes/apu.c src/engine/platform/sound/nes/fds.c +src/engine/platform/sound/nes/mmc5.c src/engine/platform/sound/vera_psg.c src/engine/platform/sound/vera_pcm.c @@ -309,6 +310,7 @@ src/engine/platform/sms.cpp src/engine/platform/opll.cpp src/engine/platform/gb.cpp src/engine/platform/pce.cpp +src/engine/platform/mmc5.cpp src/engine/platform/nes.cpp src/engine/platform/c64.cpp src/engine/platform/arcade.cpp diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 2658c426c..bbcf3073d 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -51,6 +51,7 @@ #include "platform/vic20.h" #include "platform/vrc6.h" #include "platform/fds.h" +#include "platform/mmc5.h" #include "platform/dummy.h" #include "../ta-log.h" #include "song.h" @@ -295,6 +296,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_VRC6: dispatch=new DivPlatformVRC6; break; + case DIV_SYSTEM_MMC5: + dispatch=new DivPlatformMMC5; + break; default: logW("this system is not supported yet! using dummy platform.\n"); dispatch=new DivPlatformDummy; diff --git a/src/engine/platform/mmc5.cpp b/src/engine/platform/mmc5.cpp new file mode 100644 index 000000000..3ccf95499 --- /dev/null +++ b/src/engine/platform/mmc5.cpp @@ -0,0 +1,411 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "mmc5.h" +#include "sound/nes/mmc5.h" +#include "../engine.h" +#include + +#define CHIP_DIVIDER 16 + +#define rWrite(a,v) if (!skipRegisterWrites) {extcl_cpu_wr_mem_MMC5(mmc5,a,v); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} } + +const char* regCheatSheetMMC5[]={ + "S0Volume", "5000", + "S0PeriodL", "5002", + "S0PeriodH", "5003", + "S1Volume", "5004", + "S1PeriodL", "5006", + "S1PeriodH", "5007", + "PCMControl", "4010", + "PCMWrite", "4011", + "APUControl", "4015", + NULL +}; + +const char** DivPlatformMMC5::getRegisterSheet() { + return regCheatSheetMMC5; +} + +const char* DivPlatformMMC5::getEffectName(unsigned char effect) { + switch (effect) { + case 0x12: + return "12xx: Set duty cycle/noise mode (pulse: 0 to 3; noise: 0 or 1)"; + break; + } + return NULL; +} + +void DivPlatformMMC5::acquire(short* bufL, short* bufR, size_t start, size_t len) { + for (size_t i=start; i=rate) { + DivSample* s=parent->getSample(dacSample); + if (s->samples>0) { + if (!isMuted[4]) { + rWrite(0x5011,((unsigned char)s->data8[dacPos]+0x80)); + } + if (++dacPos>=s->samples) { + if (s->loopStart>=0 && s->loopStart<(int)s->samples) { + dacPos=s->loopStart; + } else { + dacSample=-1; + } + } + dacPeriod-=rate; + } else { + dacSample=-1; + } + } + } + + extcl_envelope_clock_MMC5(mmc5); + extcl_length_clock_MMC5(mmc5); + extcl_apu_tick_MMC5(mmc5); + if (mmc5->clocked) { + mmc5->clocked=false; + } + int sample=isMuted[0]?0:(mmc5->S3.output*10); + if (!isMuted[1]) { + sample+=mmc5->S4.output*10; + } + if (!isMuted[2]) { + sample+=mmc5->pcm.output*2; + } + sample=(sample-128)<<6; + if (sample>32767) sample=32767; + if (sample<-32768) sample=-32768; + bufL[i]=sample; + } +} + +void DivPlatformMMC5::tick() { + for (int i=0; i<2; i++) { + chan[i].std.next(); + if (chan[i].std.hadVol) { + // ok, why are the volumes like that? + chan[i].outVol=MIN(15,chan[i].std.vol)-(15-(chan[i].vol&15)); + if (chan[i].outVol<0) chan[i].outVol=0; + rWrite(0x5000+i*4,0x30|chan[i].outVol|((chan[i].duty&3)<<6)); + } + if (chan[i].std.hadArp) { + if (!chan[i].inPorta) { + if (chan[i].std.arpMode) { + chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp); + } else { + chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp); + } + } + chan[i].freqChanged=true; + } else { + if (chan[i].std.arpMode && chan[i].std.finishedArp) { + chan[i].baseFreq=NOTE_PERIODIC(chan[i].note); + chan[i].freqChanged=true; + } + } + if (chan[i].std.hadDuty) { + chan[i].duty=chan[i].std.duty; + rWrite(0x5000+i*4,0x30|chan[i].outVol|((chan[i].duty&3)<<6)); + } + if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true)-1; + if (chan[i].freq>2047) chan[i].freq=2047; + if (chan[i].freq<0) chan[i].freq=0; + 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); + rWrite(0x5000+i*4,0x30); + } + rWrite(0x5002+i*4,chan[i].freq&0xff); + if ((chan[i].prevFreq>>8)!=(chan[i].freq>>8)) { + rWrite(0x5003+i*4,0xf8|(chan[i].freq>>8)); + } + if (chan[i].freq!=65535 && chan[i].freq!=0) { + 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; + } + } + + // PCM + if (chan[4].freqChanged) { + chan[4].freq=parent->calcFreq(chan[4].baseFreq,chan[4].pitch,false); + if (chan[4].furnaceDac) { + double off=1.0; + if (dacSample>=0 && dacSamplesong.sampleLen) { + DivSample* s=parent->getSample(dacSample); + off=(double)s->centerRate/8363.0; + } + dacRate=MIN(chan[4].freq*off,32000); + if (dumpWrites) addWrite(0xffff0001,dacRate); + } + chan[4].freqChanged=false; + } +} + +int DivPlatformMMC5::dispatch(DivCommand c) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: + if (c.chan==2) { // PCM + DivInstrument* ins=parent->getIns(chan[c.chan].ins); + if (ins->type==DIV_INS_AMIGA) { + dacSample=ins->amiga.initSample; + if (dacSample<0 || dacSample>=parent->song.sampleLen) { + dacSample=-1; + if (dumpWrites) addWrite(0xffff0002,0); + break; + } else { + if (dumpWrites) addWrite(0xffff0000,dacSample); + } + dacPos=0; + dacPeriod=0; + chan[c.chan].baseFreq=parent->song.tuning*pow(2.0f,((float)(c.value+3)/12.0f)); + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + } + chan[c.chan].active=true; + chan[c.chan].keyOn=true; + chan[c.chan].furnaceDac=true; + } else { + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].note=c.value; + } + dacSample=12*sampleBank+chan[c.chan].note%12; + if (dacSample>=parent->song.sampleLen) { + dacSample=-1; + if (dumpWrites) addWrite(0xffff0002,0); + break; + } else { + if (dumpWrites) addWrite(0xffff0000,dacSample); + } + dacPos=0; + dacPeriod=0; + dacRate=parent->getSample(dacSample)->rate; + if (dumpWrites) addWrite(0xffff0001,dacRate); + chan[c.chan].furnaceDac=false; + } + break; + } else { + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); + } + } + if (c.value!=DIV_NOTE_NULL) { + 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)); + rWrite(0x5000+c.chan*4,0x30|chan[c.chan].vol|((chan[c.chan].duty&3)<<6)); + break; + case DIV_CMD_NOTE_OFF: + if (c.chan==2) { + dacSample=-1; + if (dumpWrites) addWrite(0xffff0002,0); + } + chan[c.chan].active=false; + chan[c.chan].keyOff=true; + chan[c.chan].std.init(NULL); + break; + case DIV_CMD_NOTE_OFF_ENV: + case DIV_CMD_ENV_RELEASE: + chan[c.chan].std.release(); + break; + case DIV_CMD_INSTRUMENT: + if (chan[c.chan].ins!=c.value || c.value2==1) { + 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 (chan[c.chan].active) { + rWrite(0x5000+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=NOTE_PERIODIC(c.value2); + 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; + break; + case DIV_CMD_SAMPLE_BANK: + sampleBank=c.value; + if (sampleBank>(parent->song.sample.size()/12)) { + sampleBank=parent->song.sample.size()/12; + } + break; + case DIV_CMD_LEGATO: + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.willArp && !chan[c.chan].std.arpMode)?(chan[c.chan].std.arp):(0))); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + break; + case DIV_CMD_PRE_PORTA: + if (chan[c.chan].active && c.value2) { + if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->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; +} + +void DivPlatformMMC5::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; +} + +void DivPlatformMMC5::forceIns() { + for (int i=0; i<3; i++) { + chan[i].insChanged=true; + chan[i].prevFreq=65535; + } +} + +void* DivPlatformMMC5::getChanState(int ch) { + return &chan[ch]; +} + +unsigned char* DivPlatformMMC5::getRegisterPool() { + return regPool; +} + +int DivPlatformMMC5::getRegisterPoolSize() { + return 32; +} + +void DivPlatformMMC5::reset() { + for (int i=0; i<3; i++) { + chan[i]=DivPlatformMMC5::Channel(); + } + if (dumpWrites) { + addWrite(0xffffffff,0); + } + + dacPeriod=0; + dacPos=0; + dacRate=0; + dacSample=-1; + sampleBank=0; + + map_init_MMC5(mmc5); + memset(regPool,0,128); + + rWrite(0x5015,0x03); + rWrite(0x5010,0x00); +} + +bool DivPlatformMMC5::keyOffAffectsArp(int ch) { + return true; +} + +void DivPlatformMMC5::setFlags(unsigned int flags) { + if (flags==2) { // Dendy + rate=COLOR_PAL*2.0/5.0; + } else if (flags==1) { // PAL + rate=COLOR_PAL*3.0/8.0; + } else { // NTSC + rate=COLOR_NTSC/2.0; + } + chipClock=rate; +} + +void DivPlatformMMC5::notifyInsDeletion(void* ins) { + for (int i=0; i<3; i++) { + chan[i].std.notifyInsDeletion((DivInstrument*)ins); + } +} + +void DivPlatformMMC5::poke(unsigned int addr, unsigned short val) { + rWrite(addr,val); +} + +void DivPlatformMMC5::poke(std::vector& wlist) { + for (DivRegWrite& i: wlist) rWrite(i.addr,i.val); +} + +int DivPlatformMMC5::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { + parent=p; + apuType=flags; + dumpWrites=false; + skipRegisterWrites=false; + mmc5=new struct _mmc5; + for (int i=0; i<3; i++) { + isMuted[i]=false; + //mmc5->muted[i]=false; // TODO + } + setFlags(flags); + + init_nla_table(500,500); + reset(); + return 5; +} + +void DivPlatformMMC5::quit() { + delete mmc5; +} + +DivPlatformMMC5::~DivPlatformMMC5() { +} diff --git a/src/engine/platform/mmc5.h b/src/engine/platform/mmc5.h new file mode 100644 index 000000000..a29ffc7d4 --- /dev/null +++ b/src/engine/platform/mmc5.h @@ -0,0 +1,88 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * 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., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _MMC5_H +#define _MMC5_H + +#include "../dispatch.h" +#include "../macroInt.h" + +class DivPlatformMMC5: public DivDispatch { + struct Channel { + int freq, baseFreq, pitch, prevFreq, note; + unsigned char ins, duty, sweep; + bool active, insChanged, freqChanged, sweepChanged, keyOn, keyOff, inPorta, furnaceDac; + signed char vol, outVol, wave; + DivMacroInt std; + Channel(): + freq(0), + baseFreq(0), + pitch(0), + prevFreq(65535), + note(0), + ins(-1), + duty(0), + sweep(8), + active(false), + insChanged(true), + freqChanged(false), + sweepChanged(false), + keyOn(false), + keyOff(false), + inPorta(false), + furnaceDac(false), + vol(15), + outVol(15), + wave(-1) {} + }; + Channel chan[5]; + bool isMuted[5]; + int dacPeriod, dacRate; + unsigned int dacPos; + int dacSample; + unsigned char sampleBank; + unsigned char apuType; + struct _mmc5* mmc5; + unsigned char regPool[128]; + + friend void putDispatchChan(void*,int,int); + + public: + void acquire(short* bufL, short* bufR, size_t start, size_t len); + int dispatch(DivCommand c); + void* getChanState(int chan); + unsigned char* getRegisterPool(); + int getRegisterPoolSize(); + void reset(); + void forceIns(); + void tick(); + void muteChannel(int ch, bool mute); + bool keyOffAffectsArp(int ch); + void setFlags(unsigned int flags); + void notifyInsDeletion(void* ins); + void poke(unsigned int addr, unsigned short val); + void poke(std::vector& wlist); + const char** getRegisterSheet(); + const char* getEffectName(unsigned char effect); + int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); + void quit(); + ~DivPlatformMMC5(); +}; + +#endif diff --git a/src/engine/platform/sound/nes/apu.c b/src/engine/platform/sound/nes/apu.c index 70eddc8e7..e9ceb79a1 100644 --- a/src/engine/platform/sound/nes/apu.c +++ b/src/engine/platform/sound/nes/apu.c @@ -21,6 +21,8 @@ #include #include "apu.h" +struct _nla_table nla_table; + void apu_tick(struct NESAPU* a, BYTE *hwtick) { /* sottraggo il numero di cicli eseguiti */ a->apu.cycles--; @@ -177,8 +179,8 @@ void apu_tick(struct NESAPU* a, BYTE *hwtick) { * eseguo un ticket per ogni canale * valorizzandone l'output. */ - square_tick(a->S1, 0, a->apu) - square_tick(a->S2, 0, a->apu) + square_tick(a->S1, 0, a->apu.clocked) + square_tick(a->S2, 0, a->apu.clocked) triangle_tick() noise_tick() dmc_tick() diff --git a/src/engine/platform/sound/nes/apu.h b/src/engine/platform/sound/nes/apu.h index 93cf72fef..224ea2a04 100644 --- a/src/engine/platform/sound/nes/apu.h +++ b/src/engine/platform/sound/nes/apu.h @@ -137,12 +137,12 @@ enum apu_mode { APU_60HZ, APU_48HZ }; #define dmc_output()\ a->DMC.output = a->DMC.counter & 0x7F /* tick */ -#define square_tick(square, swap, type)\ +#define square_tick(square, swap, type_clocked)\ if (!(--square.frequency)) {\ square_output(square, swap)\ square.frequency = (square.timer + 1) << 1;\ square.sequencer = (square.sequencer + 1) & 0x07;\ - type.clocked = TRUE;\ + type_clocked = TRUE;\ } #define triangle_tick()\ if (!(--a->TR.frequency)) {\ @@ -302,7 +302,7 @@ enum apu_mode { APU_60HZ, APU_48HZ }; #define square_reg2(square)\ /* timer (low 8 bits) */\ square.timer = (square.timer & 0x0700) | value -#define square_reg3(square)\ +#define square_reg3(square,length_clocked)\ /* length counter */\ /*\ * se non disabilitato, una scrittura in\ @@ -312,7 +312,7 @@ enum apu_mode { APU_60HZ, APU_48HZ }; * momento del clock di un length counter e\ * con il length diverso da zero.\ */\ - if (square.length.enabled && !(a->apu.length_clocked && square.length.value)) {\ + if (square.length.enabled && !(length_clocked && square.length.value)) {\ square.length.value = length_table[value >> 3];\ }\ /* envelope */\ @@ -510,9 +510,11 @@ typedef struct _apuDMC { #endif EXTERNC struct _nla_table { - SWORD pulse[32]; - SWORD tnd[203]; -} nla_table; + SWORD pulse[32]; + SWORD tnd[203]; +}; + +extern struct _nla_table nla_table; EXTERNC struct NESAPU { _apu apu; diff --git a/src/engine/platform/sound/nes/cpu_inline.h b/src/engine/platform/sound/nes/cpu_inline.h index ee762f17b..c9af64e9e 100644 --- a/src/engine/platform/sound/nes/cpu_inline.h +++ b/src/engine/platform/sound/nes/cpu_inline.h @@ -58,7 +58,7 @@ INLINE static void apu_wr_reg(struct NESAPU* a, WORD address, BYTE value) { return; } if (address == 0x4003) { - square_reg3(a->S1); + square_reg3(a->S1,a->apu.length_clocked); sweep_silence(a->S1) return; } @@ -81,7 +81,7 @@ INLINE static void apu_wr_reg(struct NESAPU* a, WORD address, BYTE value) { return; } if (address == 0x4007) { - square_reg3(a->S2); + square_reg3(a->S2,a->apu.length_clocked); sweep_silence(a->S2) return; } diff --git a/src/engine/platform/sound/nes/mmc5.c b/src/engine/platform/sound/nes/mmc5.c new file mode 100644 index 000000000..9272bc44b --- /dev/null +++ b/src/engine/platform/sound/nes/mmc5.c @@ -0,0 +1,107 @@ +/* + * 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. + */ + +#include +#include "apu.h" +#include "mmc5.h" + +enum { MODE0, MODE1, MODE2, MODE3 }; +enum { CHR_S, CHR_B }; +enum { SPLIT_LEFT, SPLIT_RIGHT = 0x40 }; + +const BYTE filler_attrib[4] = {0x00, 0x55, 0xAA, 0xFF}; +BYTE prg_ram_mode; + +void map_init_MMC5(struct _mmc5* mmc5) { + memset(mmc5,0,sizeof(struct _mmc5)); + + mmc5->S3.frequency = 1; + mmc5->S4.frequency = 1; + mmc5->S3.length.enabled = 0; + mmc5->S3.length.value = 0; + mmc5->S4.length.enabled = 0; + mmc5->S4.length.value = 0; +} +void extcl_cpu_wr_mem_MMC5(struct _mmc5* mmc5, WORD address, BYTE value) { + if (address < 0x5000) { + return; + } + + switch (address) { + case 0x5000: + square_reg0(mmc5->S3); + return; + case 0x5001: + /* lo sweep non e' utilizzato */ + return; + case 0x5002: + square_reg2(mmc5->S3); + return; + case 0x5003: + square_reg3(mmc5->S3,0); + return; + case 0x5004: + square_reg0(mmc5->S4); + return; + case 0x5005: + /* lo sweep non e' utilizzato */ + return; + case 0x5006: + square_reg2(mmc5->S4); + return; + case 0x5007: + square_reg3(mmc5->S4,0); + return; + case 0x5010: + mmc5->pcm.enabled = ~value & 0x01; + mmc5->pcm.output = 0; + if (mmc5->pcm.enabled) { + mmc5->pcm.output = mmc5->pcm.amp; + } + mmc5->clocked = TRUE; + return; + case 0x5011: + mmc5->pcm.amp = value; + mmc5->pcm.output = 0; + if (mmc5->pcm.enabled) { + mmc5->pcm.output = mmc5->pcm.amp; + } + mmc5->clocked = TRUE; + return; + case 0x5015: + if (!(mmc5->S3.length.enabled = value & 0x01)) { + mmc5->S3.length.value = 0; + } + if (!(mmc5->S4.length.enabled = value & 0x02)) { + mmc5->S4.length.value = 0; + } + return; + } +} +void extcl_length_clock_MMC5(struct _mmc5* mmc5) { + length_run(mmc5->S3) + length_run(mmc5->S4) +} +void extcl_envelope_clock_MMC5(struct _mmc5* mmc5) { + envelope_run(mmc5->S3) + envelope_run(mmc5->S4) +} +void extcl_apu_tick_MMC5(struct _mmc5* mmc5) { + square_tick(mmc5->S3, 0, mmc5->clocked) + square_tick(mmc5->S4, 0, mmc5->clocked) +} \ No newline at end of file diff --git a/src/engine/platform/sound/nes/mmc5.h b/src/engine/platform/sound/nes/mmc5.h new file mode 100644 index 000000000..3d1fbff93 --- /dev/null +++ b/src/engine/platform/sound/nes/mmc5.h @@ -0,0 +1,80 @@ +/* + * 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 MAPPER_MMC5_H_ +#define MAPPER_MMC5_H_ + +#include "apu.h" + +#if defined (__cplusplus) +#define EXTERNC extern "C" +#else +#define EXTERNC +#endif + +EXTERNC struct _mmc5 { + BYTE prg_mode; + BYTE chr_mode; + BYTE ext_mode; + BYTE nmt_mode[4]; + BYTE prg_ram_write[2]; + BYTE prg_bank[4]; + uint32_t prg_ram_bank[4][2]; + BYTE chr_last; + WORD chr_high; + WORD chr_s[8]; + WORD chr_b[4]; + BYTE ext_ram[0x400]; + BYTE fill_table[0x400]; + BYTE fill_tile; + BYTE fill_attr; + BYTE split; + BYTE split_st_tile; + BYTE split_side; + BYTE split_scrl; + BYTE split_in_reg; + BYTE split_x; + BYTE split_y; + WORD split_tile; + uint32_t split_bank; + BYTE factor[2]; + WORD product; + _apuSquare S3, S4; + struct _mmc5_pcm { + BYTE enabled; + BYTE output; + BYTE amp; + } pcm; + BYTE filler[50]; + + /* ------------------------------------------------------- */ + /* questi valori non e' necessario salvarli nei savestates */ + /* ------------------------------------------------------- */ + /* */ BYTE clocked; /* */ + /* ------------------------------------------------------- */ +}; + +EXTERNC void map_init_MMC5(struct _mmc5* mmc5); +EXTERNC void extcl_cpu_wr_mem_MMC5(struct _mmc5* mmc5, WORD address, BYTE value); +EXTERNC void extcl_length_clock_MMC5(struct _mmc5* mmc5); +EXTERNC void extcl_envelope_clock_MMC5(struct _mmc5* mmc5); +EXTERNC void extcl_apu_tick_MMC5(struct _mmc5* mmc5); + +#undef EXTERNC + +#endif /* MAPPER_MMC5_H_ */