From ac3772c024b5401b7e61fc3d01ab58b6efa62249 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 3 Apr 2022 22:37:16 -0500 Subject: [PATCH] initial FDS bring-up --- CMakeLists.txt | 2 + src/engine/dispatch.h | 6 + src/engine/dispatchContainer.cpp | 4 + src/engine/fileOps.cpp | 2 +- src/engine/instrument.h | 10 + src/engine/platform/fds.cpp | 383 +++++++++++++++++++++ src/engine/platform/fds.h | 92 +++++ src/engine/platform/sound/nes/cpu_inline.h | 80 +++++ src/engine/platform/sound/nes/fds.c | 138 ++++++++ src/engine/platform/sound/nes/fds.h | 94 +++++ src/engine/playback.cpp | 30 ++ src/engine/sysDef.cpp | 1 + src/engine/vgmOps.cpp | 26 ++ src/gui/insEdit.cpp | 3 + 14 files changed, 870 insertions(+), 1 deletion(-) create mode 100644 src/engine/platform/fds.cpp create mode 100644 src/engine/platform/fds.h create mode 100644 src/engine/platform/sound/nes/fds.c create mode 100644 src/engine/platform/sound/nes/fds.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 1a0909633..353ca9e97 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -236,6 +236,7 @@ src/engine/platform/sound/gb/apu.c 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/vera_psg.c src/engine/platform/sound/vera_pcm.c @@ -318,6 +319,7 @@ src/engine/platform/ym2610bext.cpp src/engine/platform/ay.cpp src/engine/platform/ay8930.cpp src/engine/platform/opl.cpp +src/engine/platform/fds.cpp src/engine/platform/tia.cpp src/engine/platform/saa.cpp src/engine/platform/amiga.cpp diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index 02c9da241..dd7cbc268 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -104,6 +104,12 @@ enum DivDispatchCmds { DIV_CMD_AY_IO_WRITE, DIV_CMD_AY_AUTO_PWM, + DIV_CMD_FDS_MOD_DEPTH, + DIV_CMD_FDS_MOD_HIGH, + DIV_CMD_FDS_MOD_LOW, + DIV_CMD_FDS_MOD_POS, + DIV_CMD_FDS_MOD_WAVE, + DIV_CMD_SAA_ENVELOPE, DIV_CMD_AMIGA_FILTER, diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 498630cc8..2658c426c 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -50,6 +50,7 @@ #include "platform/pet.h" #include "platform/vic20.h" #include "platform/vrc6.h" +#include "platform/fds.h" #include "platform/dummy.h" #include "../ta-log.h" #include "song.h" @@ -213,6 +214,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_AY8930: dispatch=new DivPlatformAY8930; break; + case DIV_SYSTEM_FDS: + dispatch=new DivPlatformFDS; + break; case DIV_SYSTEM_TIA: dispatch=new DivPlatformTIA; break; diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 4e758c2de..976b2c32f 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -529,7 +529,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { for (int i=0; ilen=(unsigned char)reader.readI(); - if (ds.system[0]==DIV_SYSTEM_GB) { + if (ds.system[0]==DIV_SYSTEM_GB || ds.system[0]==DIV_SYSTEM_NES_FDS) { wave->max=15; } if (wave->len>65) { diff --git a/src/engine/instrument.h b/src/engine/instrument.h index 22b3bb320..e8ec9d318 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -397,6 +397,16 @@ struct DivInstrumentN163 { waveMode(3) {} }; +struct DivInstrumentFDS { + signed char modTable[32]; + unsigned char modSpeed, modLevel; + DivInstrumentFDS(): + modSpeed(0), + modLevel(0) { + memset(modTable,0,32); + } +}; + struct DivInstrument { String name; bool mode; diff --git a/src/engine/platform/fds.cpp b/src/engine/platform/fds.cpp new file mode 100644 index 000000000..1ec264d10 --- /dev/null +++ b/src/engine/platform/fds.cpp @@ -0,0 +1,383 @@ +/** + * 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 "fds.h" +#include "sound/nes/cpu_inline.h" +#include "../engine.h" +#include + +#define CHIP_FREQBASE 262144 + +#define rWrite(a,v) if (!skipRegisterWrites) {fds_wr_mem(fds,a,v); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} } + +const char** DivPlatformFDS::getRegisterSheet() { + return NULL; +} + +const char* DivPlatformFDS::getEffectName(unsigned char effect) { + switch (effect) { + case 0x12: + return "12xx: Set duty cycle/noise mode (pulse: 0 to 3; noise: 0 or 1)"; + break; + case 0x13: + return "13xy: Sweep up (x: time; y: shift)"; + break; + case 0x14: + return "14xy: Sweep down (x: time; y: shift)"; + break; + } + return NULL; +} + +void DivPlatformFDS::acquire(short* bufL, short* bufR, size_t start, size_t len) { + for (size_t i=start; isnd.main.output; + if (sample>32767) sample=32767; + if (sample<-32768) sample=-32768; + bufL[i]=sample; + } +} + +void DivPlatformFDS::updateWave() { + DivWavetable* wt=parent->getWave(chan[0].wave); + // TODO: master volume + rWrite(0x4089,0x80); + for (int i=0; i<64; i++) { + if (wt->max<1 || wt->len<1) { + rWrite(0x4040+i,0); + } else { + int data=wt->data[i*wt->len/64]*63/wt->max; + if (data<0) data=0; + if (data>63) data=63; + rWrite(0x4040+i,data); + } + } + rWrite(0x4089,0); +} + +void DivPlatformFDS::tick() { + for (int i=0; i<1; i++) { + chan[i].std.next(); + if (chan[i].std.hadVol) { + // ok, why are the volumes like that? + chan[i].outVol=MIN(32,chan[i].std.vol)-(32-MIN(32,chan[i].vol)); + if (chan[i].outVol<0) chan[i].outVol=0; + rWrite(0x4080,0x80|chan[i].outVol); + } + if (chan[i].std.hadArp) { + if (i==3) { // noise + if (chan[i].std.arpMode) { + chan[i].baseFreq=chan[i].std.arp; + } else { + chan[i].baseFreq=chan[i].note+chan[i].std.arp; + } + 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=NOTE_FREQUENCY(chan[i].std.arp); + } else { + chan[i].baseFreq=NOTE_FREQUENCY(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_FREQUENCY(chan[i].note); + chan[i].freqChanged=true; + } + } + /* + if (chan[i].std.hadDuty) { + chan[i].duty=chan[i].std.duty; + if (i==3) { + if (parent->song.properNoiseLayout) { + chan[i].duty&=1; + } else if (chan[i].duty>1) { + chan[i].duty=1; + } + } + if (i!=2) { + rWrite(0x4000+i*4,0x30|chan[i].outVol|((chan[i].duty&3)<<6)); + } + if (i==3) { // noise + chan[i].freqChanged=true; + } + }*/ + if (chan[i].std.hadWave) { + if (chan[i].wave!=chan[i].std.wave) { + chan[i].wave=chan[i].std.wave; + updateWave(); + //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) { + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false); + if (chan[i].freq>4095) chan[i].freq=4095; + if (chan[i].freq<0) chan[i].freq=0; + if (chan[i].keyOn) { + if (chan[i].wave<0) { + chan[i].wave=0; + updateWave(); + } + } + if (chan[i].keyOff) { + rWrite(0x4080,0x80); + } + rWrite(0x4082,chan[i].freq&0xff); + rWrite(0x4083,(chan[i].freq>>8)&15); + if (chan[i].keyOn) chan[i].keyOn=false; + if (chan[i].keyOff) chan[i].keyOff=false; + chan[i].freqChanged=false; + } + } +} + +int DivPlatformFDS::dispatch(DivCommand c) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); + 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(0x4080,0x80|chan[c.chan].vol); + 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_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; + } + rWrite(0x4080,0x80|chan[c.chan].vol); + } + 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_WAVE: + if (chan[c.chan].wave!=c.value) { + chan[c.chan].wave=c.value; + updateWave(); + } + break; + case DIV_CMD_FDS_MOD_DEPTH: + chan[c.chan].modDepth=c.value; + rWrite(0x4084,(chan[c.chan].modOn<<7)|0x40|chan[c.chan].modDepth); + break; + case DIV_CMD_FDS_MOD_HIGH: + chan[c.chan].modOn=c.value>>4; + chan[c.chan].modFreq=((c.value&15)<<8)|(chan[c.chan].modFreq&0xff); + rWrite(0x4084,(chan[c.chan].modOn<<7)|0x40|chan[c.chan].modDepth); + rWrite(0x4087,chan[c.chan].modFreq>>8); + break; + case DIV_CMD_FDS_MOD_LOW: + chan[c.chan].modFreq=(chan[c.chan].modFreq&0xf00)|c.value; + rWrite(0x4086,chan[c.chan].modFreq&0xff); + break; + case DIV_CMD_FDS_MOD_POS: + chan[c.chan].modPos=c.value&0x7f; + // TODO + break; + case DIV_CMD_FDS_MOD_WAVE: { + DivWavetable* wt=parent->getWave(c.value); + for (int i=0; i<32; i++) { + if (wt->max<1 || wt->len<1) { + rWrite(0x4040+i,0); + } else { + int data=wt->data[i*MIN(32,wt->len)/32]*7/wt->max; + if (data<0) data=0; + if (data>7) data=7; + chan[c.chan].modTable[i]=data; + } + } + rWrite(0x4087,0x80|chan[c.chan].modFreq>>8); + for (int i=0; i<32; i++) { + rWrite(0x4088,chan[c.chan].modTable[i]); + } + rWrite(0x4087,chan[c.chan].modFreq>>8); + break; + } + case DIV_CMD_NOTE_PORTA: { + int destFreq=NOTE_FREQUENCY(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_LEGATO: + if (c.chan==3) break; + chan[c.chan].baseFreq=NOTE_FREQUENCY(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 32; + break; + case DIV_ALWAYS_SET_VOLUME: + return 1; + break; + default: + break; + } + return 1; +} + +void DivPlatformFDS::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; +} + +void DivPlatformFDS::forceIns() { + for (int i=0; i<1; i++) { + chan[i].insChanged=true; + chan[i].prevFreq=65535; + } + updateWave(); +} + +void* DivPlatformFDS::getChanState(int ch) { + return &chan[ch]; +} + +unsigned char* DivPlatformFDS::getRegisterPool() { + return regPool; +} + +int DivPlatformFDS::getRegisterPoolSize() { + return 128; +} + +void DivPlatformFDS::reset() { + for (int i=0; i<1; i++) { + chan[i]=DivPlatformFDS::Channel(); + } + if (dumpWrites) { + addWrite(0xffffffff,0); + } + + fds_reset(fds); + memset(regPool,0,128); + + rWrite(0x4023,0); + rWrite(0x4023,0x83); + rWrite(0x4089,0); +} + +bool DivPlatformFDS::keyOffAffectsArp(int ch) { + return true; +} + +void DivPlatformFDS::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 DivPlatformFDS::notifyInsDeletion(void* ins) { + for (int i=0; i<5; i++) { + chan[i].std.notifyInsDeletion((DivInstrument*)ins); + } +} + +void DivPlatformFDS::poke(unsigned int addr, unsigned short val) { + rWrite(addr,val); +} + +void DivPlatformFDS::poke(std::vector& wlist) { + for (DivRegWrite& i: wlist) rWrite(i.addr,i.val); +} + +int DivPlatformFDS::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { + parent=p; + apuType=flags; + dumpWrites=false; + skipRegisterWrites=false; + fds=new struct _fds; + for (int i=0; i<1; i++) { + isMuted[i]=false; + } + setFlags(flags); + + reset(); + return 5; +} + +void DivPlatformFDS::quit() { + delete fds; +} + +DivPlatformFDS::~DivPlatformFDS() { +} diff --git a/src/engine/platform/fds.h b/src/engine/platform/fds.h new file mode 100644 index 000000000..d9f145e1c --- /dev/null +++ b/src/engine/platform/fds.h @@ -0,0 +1,92 @@ +/** + * 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 _FDS_H +#define _FDS_H + +#include "../dispatch.h" +#include "../macroInt.h" + +class DivPlatformFDS: public DivDispatch { + struct Channel { + int freq, baseFreq, pitch, prevFreq, note, modFreq; + unsigned char ins, duty, sweep, modDepth, modPos; + bool active, insChanged, freqChanged, sweepChanged, keyOn, keyOff, inPorta, modOn; + signed char vol, outVol, wave; + signed char modTable[32]; + DivMacroInt std; + Channel(): + freq(0), + baseFreq(0), + pitch(0), + prevFreq(65535), + note(0), + modFreq(0), + ins(-1), + duty(0), + sweep(8), + modDepth(0), + modPos(0), + active(false), + insChanged(true), + freqChanged(false), + sweepChanged(false), + keyOn(false), + keyOff(false), + inPorta(false), + modOn(false), + vol(32), + outVol(32), + wave(-1) { + memset(modTable,0,32); + } + }; + Channel chan[1]; + bool isMuted[1]; + unsigned char apuType; + struct _fds* fds; + unsigned char regPool[128]; + + void updateWave(); + + 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(); + ~DivPlatformFDS(); +}; + +#endif diff --git a/src/engine/platform/sound/nes/cpu_inline.h b/src/engine/platform/sound/nes/cpu_inline.h index 665fa01ee..ee762f17b 100644 --- a/src/engine/platform/sound/nes/cpu_inline.h +++ b/src/engine/platform/sound/nes/cpu_inline.h @@ -21,6 +21,7 @@ #include #include "apu.h" +#include "fds.h" #define mod_cycles_op(op, vl) cpu.cycles op vl #define r2006_during_rendering()\ @@ -314,4 +315,83 @@ INLINE static void apu_wr_reg(struct NESAPU* a, WORD address, BYTE value) { 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_ */ diff --git a/src/engine/platform/sound/nes/fds.c b/src/engine/platform/sound/nes/fds.c new file mode 100644 index 000000000..e72e90389 --- /dev/null +++ b/src/engine/platform/sound/nes/fds.c @@ -0,0 +1,138 @@ +/* + * 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 +#include "fds.h" + +enum { TRANSFERED_8BIT = 0x02, END_OF_HEAD = 0x40 }; + +static const BYTE volume_wave[4] = { 39, 26, 19, 15 }; + +void fds_reset(struct _fds* fds) { + memset(fds,0,sizeof(struct _fds)); +} + +void extcl_apu_tick_FDS(struct _fds* fds) { + SWORD freq; + + /* volume unit */ + if (fds->snd.volume.mode) { + fds->snd.volume.gain = fds->snd.volume.speed; + } else if (!fds->snd.envelope.disabled && fds->snd.envelope.speed) { + if (fds->snd.volume.counter) { + fds->snd.volume.counter--; + } else { + fds->snd.volume.counter = (fds->snd.envelope.speed << 3) * (fds->snd.volume.speed + 1); + if (fds->snd.volume.increase) { + if (fds->snd.volume.gain < 32) { + fds->snd.volume.gain++; + } + } else if (fds->snd.volume.gain) { + fds->snd.volume.gain--; + } + } + } + + /* sweep unit */ + if (fds->snd.sweep.mode) { + fds->snd.sweep.gain = fds->snd.sweep.speed; + } else if (!fds->snd.envelope.disabled && fds->snd.envelope.speed) { + if (fds->snd.sweep.counter) { + fds->snd.sweep.counter--; + } else { + fds->snd.sweep.counter = (fds->snd.envelope.speed << 3) * (fds->snd.sweep.speed + 1); + if (fds->snd.sweep.increase) { + if (fds->snd.sweep.gain < 32) { + fds->snd.sweep.gain++; + } + } else if (fds->snd.sweep.gain) { + fds->snd.sweep.gain--; + } + } + } + + /* modulation unit */ + freq = fds->snd.main.frequency; + + if (!fds->snd.modulation.disabled && fds->snd.modulation.frequency) { + if ((fds->snd.modulation.counter -= fds->snd.modulation.frequency) < 0) { + SWORD temp, temp2, a, d; + SBYTE adj = fds->snd.modulation.data[fds->snd.modulation.index]; + + fds->snd.modulation.counter += 65536; + + if (++fds->snd.modulation.index == 64) { + fds->snd.modulation.index = 0; + } + + if (adj == -4) { + fds->snd.sweep.bias = 0; + } else { + fds->snd.sweep.bias += adj; + } + + temp = fds->snd.sweep.bias * ((fds->snd.sweep.gain < 32) ? fds->snd.sweep.gain : 32); + + a = 64; + d = 0; + + if (temp <= 0) { + d = 15; + } else if (temp < 3040) { //95 * 32 + a = 66; + d = -31; + } + + temp2 = a + (SBYTE) ((temp - d) / 16 - a); + + fds->snd.modulation.mod = freq * temp2 / 64; + } + + if (freq) { + freq += fds->snd.modulation.mod; + } + } + + /* main unit */ + if (fds->snd.main.silence) { + fds->snd.main.output = 0; + return; + } + + if (freq && !fds->snd.wave.writable) { + if ((fds->snd.wave.counter -= freq) < 0) { + WORD level; + + fds->snd.wave.counter += 65536; + + level = (fds->snd.volume.gain < 32 ? fds->snd.volume.gain : 32) + * volume_wave[fds->snd.wave.volume]; + + /* valore massimo dell'output (63 * (39 * 32)) = 78624 */ + /*fds->snd.main.output = (fds->snd.wave.data[fds->snd.wave.index] * level) >> 4;*/ + fds->snd.main.output = (fds->snd.wave.data[fds->snd.wave.index] * level) >> 3; + + if (++fds->snd.wave.index == 64) { + fds->snd.wave.index = 0; + } + + fds->snd.wave.clocked = TRUE; + } + } +} diff --git a/src/engine/platform/sound/nes/fds.h b/src/engine/platform/sound/nes/fds.h new file mode 100644 index 000000000..f1ef45e72 --- /dev/null +++ b/src/engine/platform/sound/nes/fds.h @@ -0,0 +1,94 @@ +/* + * 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 FDS_H_ +#define FDS_H_ + +#include "common.h" + +enum fds_operations { FDS_OP_NONE, FDS_OP_READ, FDS_OP_WRITE }; + +#if defined (__cplusplus) +#define EXTERNC extern "C" +#else +#define EXTERNC +#endif + +EXTERNC struct _fds { + // snd + BYTE enabled_snd_reg; + struct _fds_snd { + struct _fds_snd_wave { + BYTE data[64]; + BYTE writable; + BYTE volume; + + BYTE index; + int32_t counter; + + /* ------------------------------------------------------- */ + /* questi valori non e' necessario salvarli nei savestates */ + /* ------------------------------------------------------- */ + /* */ BYTE clocked; /* */ + /* ------------------------------------------------------- */ + } wave; + struct _fds_snd_envelope { + BYTE speed; + BYTE disabled; + } envelope; + struct _fds_snd_main { + BYTE silence; + WORD frequency; + + SWORD output; + } main; + struct _fds_snd_volume { + BYTE speed; + BYTE mode; + BYTE increase; + + BYTE gain; + uint32_t counter; + } volume; + struct _fds_snd_sweep { + SBYTE bias; + BYTE mode; + BYTE increase; + BYTE speed; + + BYTE gain; + uint32_t counter; + } sweep; + struct _fds_snd_modulation { + SBYTE data[64]; + WORD frequency; + BYTE disabled; + + BYTE index; + int32_t counter; + SWORD mod; + } modulation; + } snd; +}; + +EXTERNC void extcl_apu_tick_FDS(struct _fds* fds); +EXTERNC void fds_reset(struct _fds* fds); + +#undef EXTERNC + +#endif /* FDS_H_ */ diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index bd6c5f370..52ea991af 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -112,6 +112,12 @@ const char* cmdName[DIV_CMD_MAX]={ "AY_IO_WRITE", "AY_AUTO_PWM", + "FDS_MOD_DEPTH", + "FDS_MOD_HIGH", + "FDS_MOD_LOW", + "FDS_MOD_POS", + "FDS_MOD_WAVE", + "SAA_ENVELOPE", "AMIGA_FILTER", @@ -296,6 +302,30 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe return false; } break; + case DIV_SYSTEM_FDS: + switch (effect) { + case 0x10: // select waveform + dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal)); + break; + case 0x11: // modulation depth + dispatchCmd(DivCommand(DIV_CMD_FDS_MOD_DEPTH,ch,effectVal)); + break; + case 0x12: // modulation enable/high + dispatchCmd(DivCommand(DIV_CMD_FDS_MOD_HIGH,ch,effectVal)); + break; + case 0x13: // modulation low + dispatchCmd(DivCommand(DIV_CMD_FDS_MOD_LOW,ch,effectVal)); + break; + case 0x14: // modulation pos + dispatchCmd(DivCommand(DIV_CMD_FDS_MOD_POS,ch,effectVal)); + break; + case 0x15: // modulation wave + dispatchCmd(DivCommand(DIV_CMD_FDS_MOD_WAVE,ch,effectVal)); + break; + default: + return false; + } + break; case DIV_SYSTEM_OPLL_DRUMS: case DIV_SYSTEM_OPL_DRUMS: case DIV_SYSTEM_OPL2_DRUMS: diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index b3f9e6ab5..084504fe1 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -1782,6 +1782,7 @@ int DivEngine::minVGMVersion(DivSystem which) { case DIV_SYSTEM_GB: case DIV_SYSTEM_PCE: case DIV_SYSTEM_NES: + case DIV_SYSTEM_FDS: case DIV_SYSTEM_QSOUND: return 0x161; case DIV_SYSTEM_SAA1099: diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index d06bb195f..68df49c50 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -481,6 +481,18 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write case DIV_SYSTEM_NES: w->writeC(0xb4); w->writeC(baseAddr2|(write.addr&0xff)); + w->writeC(write.val); + break; + case DIV_SYSTEM_FDS: // yeah + w->writeC(0xb4); + if ((write.addr&0xff)==0x23) { + w->writeC(baseAddr2|0x3f); + } else if ((write.addr&0xff)>=0x80) { + w->writeC(baseAddr2|(0x20+(write.addr&0x7f))); + } else { + w->writeC(baseAddr2|(write.addr&0xff)); + } + w->writeC(write.val); break; case DIV_SYSTEM_YM2151: @@ -882,6 +894,20 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) { howManyChips++; } break; + case DIV_SYSTEM_FDS: + if (!hasNES) { + hasNES=0x80000000|disCont[i].dispatch->chipClock; + willExport[i]=true; + } else if (!(hasNES&0x80000000)) { + hasNES|=0x80000000; + willExport[i]=true; + } else if (!(hasNES&0x40000000)) { + isSecond[i]=true; + willExport[i]=true; + hasNES|=0xc0000000; + howManyChips++; + } + break; case DIV_SYSTEM_LYNX: if (!hasLynx) { hasLynx=disCont[i].dispatch->chipClock; diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 2e4489378..27ba597f9 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -2152,6 +2152,9 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_PET) { volMax=1; } + if (ins->type==DIV_INS_FDS) { + volMax=32; + } bool arpMode=ins->std.arpMacroMode;