diff --git a/CMakeLists.txt b/CMakeLists.txt index 52fdbd0fd..ad8fea933 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,6 +33,7 @@ extern/Nuked-OPN2/ym3438.c src/engine/platform/sound/sn76496.cpp src/engine/platform/sound/gb/apu.c src/engine/platform/sound/gb/timing.c +src/engine/platform/sound/pce_psg.cpp src/engine/blip_buf.c src/engine/safeReader.cpp @@ -44,6 +45,7 @@ src/engine/platform/genesis.cpp src/engine/platform/genesisext.cpp src/engine/platform/sms.cpp src/engine/platform/gb.cpp +src/engine/platform/pce.cpp src/engine/platform/dummy.cpp) #imgui/imgui.cpp diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 1130d6a59..698f4d3b9 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -6,6 +6,7 @@ #include "platform/genesisext.h" #include "platform/sms.h" #include "platform/gb.h" +#include "platform/pce.h" #include "platform/dummy.h" #include #include @@ -709,6 +710,9 @@ bool DivEngine::init() { case DIV_SYSTEM_GB: dispatch=new DivPlatformGB; break; + case DIV_SYSTEM_PCE: + dispatch=new DivPlatformPCE; + break; default: dispatch=new DivPlatformDummy; break; diff --git a/src/engine/platform/pce.cpp b/src/engine/platform/pce.cpp new file mode 100644 index 000000000..d9f327169 --- /dev/null +++ b/src/engine/platform/pce.cpp @@ -0,0 +1,280 @@ +#include "pce.h" +#include "../engine.h" +#include + +//#define rWrite(a,v) pendingWrites[a]=v; +#define rWrite(a,v) pce->Write(cycles,a,v); + +#define FREQ_BASE 8015.85f + +void DivPlatformPCE::acquire(int& l, int& r) { + pce->Update(++cycles); + l=tempL; + r=tempR; +} + +void DivPlatformPCE::updateWave() { + DivWavetable* wt=parent->getWave(chan[2].wave); + rWrite(0x1a,0); + for (int i=0; i<16; i++) { + unsigned char next=((wt->data[i*2]&15)<<4)|(wt->data[1+i*2]&15); + rWrite(0x30+i,next); + } +} + +static unsigned char gbVolMap[16]={ + 0x00, 0x00, 0x00, 0x00, + 0x60, 0x60, 0x60, 0x60, + 0x40, 0x40, 0x40, 0x40, + 0x20, 0x20, 0x20, 0x20 +}; + +static unsigned char noiseTable[256]={ + 0, + 0xf7, 0xf6, 0xf5, 0xf4, + 0xe7, 0xe6, 0xe5, 0xe4, + 0xd7, 0xd6, 0xd5, 0xd4, + 0xc7, 0xc6, 0xc5, 0xc4, + 0xb7, 0xb6, 0xb5, 0xb4, + 0xa7, 0xa6, 0xa5, 0xa4, + 0x97, 0x96, 0x95, 0x94, + 0x87, 0x86, 0x85, 0x84, + 0x77, 0x76, 0x75, 0x74, + 0x67, 0x66, 0x65, 0x64, + 0x57, 0x56, 0x55, 0x54, + 0x47, 0x46, 0x45, 0x44, + 0x37, 0x36, 0x35, 0x34, + 0x27, 0x26, 0x25, 0x24, + 0x17, 0x16, 0x15, 0x14, + 0x07, 0x06, 0x05, 0x04, + 0x03, 0x02, 0x01, 0x00, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; + +void DivPlatformPCE::tick() { + for (int i=0; i<4; i++) { + chan[i].std.next(); + 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) { + rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(ins->gb.soundLen&63))); + } + } + if (chan[i].std.hadWave) { + if (chan[i].wave!=chan[i].std.wave) { + chan[i].wave=chan[i].std.wave; + if (i==2) { + 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) { + 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) { + if (i==2) { // wave + if (chan[i].wave<0) { + chan[i].wave=0; + updateWave(); + } + rWrite(16+i*5,0x80); + rWrite(16+i*5+2,gbVolMap[chan[i].vol]); + } else { + 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) { + if (i==2) { + rWrite(16+i*5+2,0); + } else { + rWrite(16+i*5+2,8); + } + } + if (i==3) { // noise + rWrite(16+i*5+3,(chan[i].freq&0xff)|(chan[i].duty?8:0)); + rWrite(16+i*5+4,((chan[i].keyOn||chan[i].keyOff)?0x80:0x00)|((ins->gb.soundLen<64)<<6)); + } else { + rWrite(16+i*5+3,(2048-chan[i].freq)&0xff); + rWrite(16+i*5+4,(((2048-chan[i].freq)>>8)&7)|((chan[i].keyOn||chan[i].keyOff)?0x80:0x00)|((ins->gb.soundLen<63)<<6)); + } + if (chan[i].keyOn) chan[i].keyOn=false; + if (chan[i].keyOff) chan[i].keyOff=false; + chan[i].freqChanged=false; + } + } +} + +int DivPlatformPCE::dispatch(DivCommand c) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: + 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)); + 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; + if (c.chan!=2) { + chan[c.chan].vol=parent->getIns(chan[c.chan].ins)->gb.envVol; + } + } + break; + case DIV_CMD_VOLUME: + chan[c.chan].vol=c.value; + if (c.chan==2) { + rWrite(16+c.chan*5+2,gbVolMap[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 (c.chan!=2) break; + chan[c.chan].wave=c.value; + updateWave(); + chan[c.chan].keyOn=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; + rWrite(16+c.chan*5+1,((chan[c.chan].duty&3)<<6)|(63-(parent->getIns(chan[c.chan].ins)->gb.soundLen&63))); + } + break; + case DIV_CMD_PANNING: { + lastPan&=~(0x11<getIns(chan[c.chan].ins)); + chan[c.chan].inPorta=c.value; + break; + case DIV_CMD_GB_SWEEP_DIR: + chan[c.chan].sweep&=0xf7; + if (c.value&1) { + chan[c.chan].sweep|=8; + } + chan[c.chan].sweepChanged=true; + break; + case DIV_CMD_GB_SWEEP_TIME: + chan[c.chan].sweep&=8; + chan[c.chan].sweep|=c.value&0x77; + chan[c.chan].sweepChanged=true; + break; + case DIV_CMD_GET_VOLMAX: + return 15; + break; + case DIV_ALWAYS_SET_VOLUME: + return 1; + break; + default: + break; + } + return 1; +} + +int DivPlatformPCE::init(DivEngine* p, int channels, int sugRate) { + parent=p; + rate=1789773; + pce=new PCE_PSG(&tempL,&tempR,PCE_PSG::REVISION_HUC6280); + lastPan=0xff; + tempL=0; + tempR=0; + cycles=0; + return 6; +} diff --git a/src/engine/platform/pce.h b/src/engine/platform/pce.h new file mode 100644 index 000000000..6ac6db819 --- /dev/null +++ b/src/engine/platform/pce.h @@ -0,0 +1,46 @@ +#ifndef _PCE_H +#define _PCE_H + +#include "../dispatch.h" +#include "../macroInt.h" +#include "sound/pce_psg.h" + +class DivPlatformPCE: public DivDispatch { + struct Channel { + int freq, baseFreq, pitch; + 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), + 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[4]; + unsigned char lastPan; + + int tempL, tempR, cycles; + PCE_PSG* pce; + 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/sound/pce_psg.cpp b/src/engine/platform/sound/pce_psg.cpp new file mode 100644 index 000000000..4561ba14c --- /dev/null +++ b/src/engine/platform/sound/pce_psg.cpp @@ -0,0 +1,822 @@ +/* Mednafen - Multi-system Emulator + * + * Original skeleton write handler and PSG structure definition: + * Copyright (C) 2001 Charles MacDonald + * + * 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 + */ + +// additional modifications by tildearrow for furnace + +#include "pce_psg.h" +#include +#include +#include + +// Frequency cache cutoff optimization threshold (<= FREQC7M_COT) +#define FREQC7M_COT 0x7 //0xA + +void PCE_PSG::SetVolume(double new_volume) +{ + for(int vl = 0; vl < 32; vl++) + { + double flub = 1.0 * new_volume * 8 / 6; + + if(vl) + flub /= pow(2, (double)1 / 4 * vl); // ~1.5dB reduction per increment of vl + + if(vl == 0x1F) + flub = 0; + + for(int samp = 0; samp < 32; samp++) + { + int eff_samp; + + if(revision == REVISION_HUC6280) + eff_samp = samp * 2; + else + eff_samp = samp * 2 - 0x1F; + + dbtable[vl][samp] = (int32_t)(flub * eff_samp * 128); // * 256); + dbtable_volonly[vl] = (int32_t)(flub * 65536); + + // dbtable[vl][samp] = (int32_t)(flub * eff_samp * 128); + // dbtable_volonly[vl] = (int32_t)(flub * 65536); + } + } +} + +// Note: Changing the 0x1F(not that there should be) would require changing the channel pseudo-off volume check logic later on. +static const int scale_tab[] = +{ + 0x00, 0x03, 0x05, 0x07, 0x09, 0x0B, 0x0D, 0x0F, + 0x10, 0x13, 0x15, 0x17, 0x19, 0x1B, 0x1D, 0x1F +}; + +#define CLOCK_LFSR(lfsr) { unsigned int newbit = ((lfsr >> 0) ^ (lfsr >> 1) ^ (lfsr >> 11) ^ (lfsr >> 12) ^ (lfsr >> 17)) & 1; lfsr = (lfsr >> 1) | (newbit << 17); } + +static const int16_t Phase_Filter[2][7] = +{ + /* 0 */ { 35, 250, 579, 641, 425, 112, 6 }, // 2048 + /* 1 */ { 6, 112, 425, 641, 579, 250, 35 }, // 2048 +}; + +inline void PCE_PSG::UpdateOutputSub(const int32_t timestamp, psg_channel *ch, const int32_t samp0, const int32_t samp1) +{ + int32_t delta[2]; + + delta[0] = samp0 - ch->blip_prev_samp[0]; + delta[1] = samp1 - ch->blip_prev_samp[1]; + + const int16_t* c = Phase_Filter[(timestamp >> 1) & 1]; + const int32_t l = (timestamp >> 2) & 0xFFFF; + + HRBufs[0][l + 0] += delta[0] * c[0]; + HRBufs[0][l + 1] += delta[0] * c[1]; + HRBufs[0][l + 2] += delta[0] * c[2]; + HRBufs[0][l + 3] += delta[0] * c[3]; + HRBufs[0][l + 4] += delta[0] * c[4]; + HRBufs[0][l + 5] += delta[0] * c[5]; + HRBufs[0][l + 6] += delta[0] * c[6]; + + HRBufs[1][l + 0] += delta[1] * c[0]; + HRBufs[1][l + 1] += delta[1] * c[1]; + HRBufs[1][l + 2] += delta[1] * c[2]; + HRBufs[1][l + 3] += delta[1] * c[3]; + HRBufs[1][l + 4] += delta[1] * c[4]; + HRBufs[1][l + 5] += delta[1] * c[5]; + HRBufs[1][l + 6] += delta[1] * c[6]; + + ch->blip_prev_samp[0] = samp0; + ch->blip_prev_samp[1] = samp1; +} + +void PCE_PSG::UpdateOutput_Norm(const int32_t timestamp, psg_channel *ch) +{ + int sv = ch->dda; + + UpdateOutputSub(timestamp, ch, dbtable[ch->vl[0]][sv], + dbtable[ch->vl[1]][sv]); +} + +void PCE_PSG::UpdateOutput_Noise(const int32_t timestamp, psg_channel *ch) +{ + int sv = ((ch->lfsr & 1) << 5) - (ch->lfsr & 1); //(ch->lfsr & 0x1) ? 0x1F : 0; + + UpdateOutputSub(timestamp, ch, dbtable[ch->vl[0]][sv], + dbtable[ch->vl[1]][sv]); +} + +void PCE_PSG::UpdateOutput_Off(const int32_t timestamp, psg_channel *ch) +{ + UpdateOutputSub(timestamp, ch, 0, 0); +} + +void PCE_PSG::UpdateOutput_Accum_HuC6280A(const int32_t timestamp, psg_channel *ch) +{ + int32_t samp[2]; + + // 31(5-bit max) * 32 samples = 992 + // 992 / 2 = 496 + // + // 8 + 5 = 13 + // 13 - 12 = 1 + + samp[0] = ((int32_t)dbtable_volonly[ch->vl[0]] * ((int32_t)ch->samp_accum - 496)) >> (8 + 5); + samp[1] = ((int32_t)dbtable_volonly[ch->vl[1]] * ((int32_t)ch->samp_accum - 496)) >> (8 + 5); + + UpdateOutputSub(timestamp, ch, samp[0], samp[1]); +} + +void PCE_PSG::UpdateOutput_Accum_HuC6280(const int32_t timestamp, psg_channel *ch) +{ + int32_t samp[2]; + + samp[0] = ((int32_t)dbtable_volonly[ch->vl[0]] * (int32_t)ch->samp_accum) >> (8 + 5); + samp[1] = ((int32_t)dbtable_volonly[ch->vl[1]] * (int32_t)ch->samp_accum) >> (8 + 5); + + UpdateOutputSub(timestamp, ch, samp[0], samp[1]); +} + + +// This function should always be called after RecalcFreqCache() (it's not called from RecalcFreqCache to avoid redundant code) +void PCE_PSG::RecalcUOFunc(int chnum) +{ + psg_channel *ch = &channel[chnum]; + + //printf("UO Update: %d, %02x\n", chnum, ch->control); + + if((revision != REVISION_HUC6280 && !(ch->control & 0xC0)) || (revision == REVISION_HUC6280 && !(ch->control & 0x80))) + ch->UpdateOutput = &PCE_PSG::UpdateOutput_Off; + else if(ch->noisectrl & ch->control & 0x80) + ch->UpdateOutput = &PCE_PSG::UpdateOutput_Noise; + // If the control for the channel is in waveform play mode, and the (real) playback frequency is too high, and the channel is either not the LFO modulator channel or + // if the LFO trigger bit(which halts the LFO modulator channel's waveform incrementing when set) is clear + else if((ch->control & 0xC0) == 0x80 && ch->freq_cache <= FREQC7M_COT && (chnum != 1 || !(lfoctrl & 0x80)) ) + ch->UpdateOutput = UpdateOutput_Accum; + else + ch->UpdateOutput = &PCE_PSG::UpdateOutput_Norm; +} + + +void PCE_PSG::RecalcFreqCache(int chnum) +{ + psg_channel *ch = &channel[chnum]; + + if(chnum == 0 && (lfoctrl & 0x03)) + { + const uint32_t shift = (((lfoctrl & 0x3) - 1) << 1); + uint8_t la = channel[1].dda; + uint32_t tmp_freq = (ch->frequency + ((uint32_t)(la - 0x10) << shift)) & 0xFFF; + + ch->freq_cache = (tmp_freq ? tmp_freq : 4096) << 1; + } + else + { + ch->freq_cache = (ch->frequency ? ch->frequency : 4096) << 1; + + if(chnum == 1 && (lfoctrl & 0x03)) + ch->freq_cache *= lfofreq ? lfofreq : 256; + } +} + +void PCE_PSG::RecalcNoiseFreqCache(int chnum) +{ + psg_channel *ch = &channel[chnum]; + int32_t freq = 0x1F - (ch->noisectrl & 0x1F); + + if(!freq) + freq = 0x20; + else + freq <<= 6; + + freq <<= 1; + + ch->noise_freq_cache = freq; +} + +void PCE_PSG::PeekWave(const unsigned int ch, uint32_t Address, uint32_t Length, uint8_t *Buffer) +{ + assert(ch <= 5); + + while(Length--) + { + Address &= 0x1F; + *Buffer = channel[ch].waveform[Address]; + Address++; + Buffer++; + } +} + +void PCE_PSG::PokeWave(const unsigned int ch, uint32_t Address, uint32_t Length, const uint8_t *Buffer) +{ + assert(ch <= 5); + + while(Length--) + { + Address &= 0x1F; + channel[ch].samp_accum -= channel[ch].waveform[Address]; + channel[ch].waveform[Address] = *Buffer & 0x1F; + channel[ch].samp_accum += channel[ch].waveform[Address]; + Address++; + Buffer++; + } +} + +uint32_t PCE_PSG::GetRegister(const unsigned int id, char *special, const uint32_t special_len) +{ + uint32_t value = 0xDEADBEEF; + const int ch = (id >> 8) & 0xF; + + switch(id & 0xF0FF) + { + default: break; + + case PSG_GSREG_SELECT: + value = select; + break; + + case PSG_GSREG_GBALANCE: + value = globalbalance; + break; + + case PSG_GSREG_LFOFREQ: + value = lfofreq; + break; + + case PSG_GSREG_LFOCTRL: + value = lfoctrl; + break; + + case PSG_GSREG_CH0_FREQ: + value = channel[ch].frequency; + break; + + case PSG_GSREG_CH0_CTRL: + value = channel[ch].control; + break; + + case PSG_GSREG_CH0_BALANCE: + value = channel[ch].balance; + break; + + case PSG_GSREG_CH0_WINDEX: + value = channel[ch].waveform_index; + break; + + case PSG_GSREG_CH0_SCACHE: + value = channel[ch].dda; + break; + + case PSG_GSREG_CH0_NCTRL: + value = channel[ch].noisectrl; + break; + + case PSG_GSREG_CH0_LFSR: + value = channel[ch].lfsr & 0x3FFFF; + break; + } + return(value); +} + + +void PCE_PSG::SetRegister(const unsigned int id, const uint32_t value) +{ + const int ch = (id >> 8) & 0xF; + + switch(id & 0xF0FF) + { + default: break; + + case PSG_GSREG_SELECT: + select = value & 0x07; + break; + + case PSG_GSREG_GBALANCE: + globalbalance = value & 0xFF; + break; + + case PSG_GSREG_LFOFREQ: + lfofreq = value & 0xFF; + break; + + case PSG_GSREG_LFOCTRL: + lfoctrl = value & 0x83; + RecalcFreqCache(0); + RecalcUOFunc(0); + RecalcFreqCache(1); + RecalcUOFunc(1); + break; + + case PSG_GSREG_CH0_FREQ: + channel[ch].frequency = value & 0xFFF; + RecalcFreqCache(ch); + RecalcUOFunc(ch); + break; + + case PSG_GSREG_CH0_CTRL: + channel[ch].control = value & 0xFF; + RecalcFreqCache(ch); + RecalcUOFunc(ch); + break; + + case PSG_GSREG_CH0_BALANCE: + channel[ch].balance = value & 0xFF; + break; + + case PSG_GSREG_CH0_WINDEX: + channel[ch].waveform_index = value & 0x1F; + break; + + case PSG_GSREG_CH0_SCACHE: + channel[ch].dda = value & 0x1F; + break; + + case PSG_GSREG_CH0_NCTRL: + channel[ch].noisectrl = value & 0xFF; + RecalcNoiseFreqCache(ch); + RecalcUOFunc(ch); + break; + + case PSG_GSREG_CH0_LFSR: + channel[ch].lfsr = value & 0x3FFFF; + break; + } +} + + +#if 0 +void PSG_SetRegister(const unsigned int id, const uint32_t value) +{ + + + if(name == "Select") + PSG_Write(0x00, V); + else if(name == "GBalance") + PSG_Write(0x01, V); + else if(name == "LFOFreq") + { + PSG_Write(0x08, V); + } + else if(name == "LFOCtrl") + PSG_Write(0x09, V); + else if(!strncmp(name.c_str(), "CH", 2)) + { + unsigned int psg_sel_save = select; + int ch = name[2] - '0'; + char moomoo[64]; + strncpy(moomoo, name.c_str() + 3, 63); + + PSG_Write(0x00, ch); + + if(!strcmp(moomoo, "Freq")) + { + PSG_Write(0x02, V); + PSG_Write(0x03, V >> 8); + } + else if(!strcmp(moomoo, "Ctrl")) + PSG_Write(0x04, V); + else if(!strcmp(moomoo, "Balance")) + PSG_Write(0x05, V); + else if(!strcmp(moomoo, "WIndex")) + psg.channel[ch].waveform_index = V & 0x1F; + else if(!strcmp(moomoo, "SCache")) + psg.channel[ch].dda = V & 0x1F; + else if(!strcmp(moomoo, "NCtrl") && ch < 4) + psg.channel[ch].noisectrl = V; + else if(!strcmp(moomoo, "LFSR") && ch < 4) + psg.channel[ch].lfsr = V & 0x3FFFF; + + PSG_Write(0x00, psg_sel_save); + } +} +#endif + +PCE_PSG::PCE_PSG(int32_t* hr_l, int32_t* hr_r, int want_revision) +{ + //printf("Test: %u, %u\n", sizeof(psg_channel), (uint8_t*)&channel[0].balance - (uint8_t*)&channel[0].waveform[0]); + + revision = want_revision; + switch(revision) + { + default: + abort(); + break; + + case REVISION_HUC6280: + UpdateOutput_Accum = &PCE_PSG::UpdateOutput_Accum_HuC6280; + break; + + case REVISION_HUC6280A: + UpdateOutput_Accum = &PCE_PSG::UpdateOutput_Accum_HuC6280A; + break; + } + HRBufs[0] = hr_l; + HRBufs[1] = hr_r; + + lastts = 0; + for(int ch = 0; ch < 6; ch++) + { + channel[ch].blip_prev_samp[0] = 0; + channel[ch].blip_prev_samp[1] = 0; + channel[ch].lastts = 0; + } + + SetVolume(1.0); // Will build dbtable in the process. + Power(0); +} + +PCE_PSG::~PCE_PSG() +{ + + +} + +int32_t PCE_PSG::GetVL(const int chnum, const int lr) +{ + psg_channel *ch = &channel[chnum]; + + const int gbal = 0x1F - scale_tab[(globalbalance >> (lr ? 0 : 4)) & 0xF]; + const int bal = 0x1F - scale_tab[(ch->balance >> (lr ? 0 : 4)) & 0xF]; + const int al = 0x1F - (ch->control & 0x1F); + int vol_reduction; + + vol_reduction = gbal + bal + al; + + if(vol_reduction > 0x1F) + vol_reduction = 0x1F; + + return(vol_reduction); +} + +void PCE_PSG::Write(int32_t timestamp, uint8_t A, uint8_t V) +{ + A &= 0x0F; + + if(A == 0x00) + { + select = (V & 0x07); + return; + } + + Update(timestamp); + + psg_channel *ch = &channel[select]; + + //if(A == 0x01 || select == 5) + // printf("Write Ch: %d %04x %02x, %d\n", select, A, V, timestamp); + + switch(A) + { + default: break; + + case 0x01: /* Global sound balance */ + globalbalance = V; + vol_pending = true; + break; + + case 0x02: /* Channel frequency (LSB) */ + if(select > 5) return; // no more than 6 channels, silly game. + + ch->frequency = (ch->frequency & 0x0F00) | V; + RecalcFreqCache(select); + RecalcUOFunc(select); + break; + + case 0x03: /* Channel frequency (MSB) */ + if(select > 5) return; // no more than 6 channels, silly game. + + ch->frequency = (ch->frequency & 0x00FF) | ((V & 0x0F) << 8); + RecalcFreqCache(select); + RecalcUOFunc(select); + break; + + case 0x04: /* Channel enable, DDA, volume */ + if(select > 5) return; // no more than 6 channels, silly game. + + if((ch->control & 0x40) && !(V & 0x40)) + { + ch->waveform_index = 0; + ch->dda = ch->waveform[ch->waveform_index]; + ch->counter = ch->freq_cache; + } + + if(!(ch->control & 0x80) && (V & 0x80)) + { + if(!(V & 0x40)) + { + ch->waveform_index = (ch->waveform_index + 1) & 0x1F; + ch->dda = ch->waveform[ch->waveform_index]; + } + } + + ch->control = V; + RecalcFreqCache(select); + RecalcUOFunc(select); + + vol_pending = true; + break; + + case 0x05: /* Channel balance */ + if(select > 5) return; // no more than 6 channels, silly game. + ch->balance = V; + + vol_pending = true; + break; + + case 0x06: /* Channel waveform data */ + if(select > 5) return; // no more than 6 channels, silly game. + V &= 0x1F; + + if(!(ch->control & 0x40)) + { + ch->samp_accum -= ch->waveform[ch->waveform_index]; + ch->waveform[ch->waveform_index] = V; + ch->samp_accum += ch->waveform[ch->waveform_index]; + } + + if((ch->control & 0xC0) == 0x00) + ch->waveform_index = ((ch->waveform_index + 1) & 0x1F); + + if(ch->control & 0x80) + { + // According to my tests(on SuperGrafx), writing to this channel + // will update the waveform value cache/latch regardless of DDA mode being enabled. + ch->dda = V; + } + break; + + case 0x07: /* Noise enable and frequency */ + if(select > 5) return; // no more than 6 channels, silly game. + if(select >= 4) + { + ch->noisectrl = V; + RecalcNoiseFreqCache(select); + RecalcUOFunc(select); + } + break; + + case 0x08: /* LFO frequency */ + lfofreq = V & 0xFF; + //printf("LFO Freq: %02x\n", V); + break; + + case 0x09: /* LFO trigger and control */ + //printf("LFO Ctrl: %02x\n", V); + if(V & 0x80) + { + channel[1].waveform_index = 0; + channel[1].dda = channel[1].waveform[channel[1].waveform_index]; + channel[1].counter = channel[1].freq_cache; + } + lfoctrl = V; + RecalcFreqCache(0); + RecalcUOFunc(0); + RecalcFreqCache(1); + RecalcUOFunc(1); + break; + } +} + +// Don't use inline, which has always_inline in it, due to gcc's inability to cope with the type of recursion +// used in this function. +void PCE_PSG::RunChannel(int chc, int32_t timestamp, const bool LFO_On) +{ + psg_channel *ch = &channel[chc]; + int32_t running_timestamp = ch->lastts; + int32_t run_time = timestamp - ch->lastts; + + ch->lastts = timestamp; + + if(!run_time) + return; + + (this->*ch->UpdateOutput)(running_timestamp, ch); + + if(chc >= 4) + { + int32_t freq = ch->noise_freq_cache; + + ch->noisecount -= run_time; + + if(&PCE_PSG::UpdateOutput_Noise == ch->UpdateOutput) + while(ch->noisecount <= 0) + { + CLOCK_LFSR(ch->lfsr); + UpdateOutput_Noise(timestamp + ch->noisecount, ch); + ch->noisecount += freq; + } + else + while(ch->noisecount <= 0) + { + CLOCK_LFSR(ch->lfsr); + ch->noisecount += freq; + } + } + + // D7 of control is 0, don't clock the counter at all. + // D7 of lfocontrol is 1(and chc == 1), don't clock the counter at all(not sure about this) + // In DDA mode, don't clock the counter. + // (Noise being enabled isn't handled here since AFAIK it doesn't disable clocking of the waveform portion, its sound just overrides the sound from + // the waveform portion when the noise enable bit is set, which is handled in our RecalcUOFunc). + if(!(ch->control & 0x80) || (chc == 1 && (lfoctrl & 0x80)) || (ch->control & 0x40)) + return; + + ch->counter -= run_time; + + if(!LFO_On && ch->freq_cache <= FREQC7M_COT) + { + if(ch->counter <= 0) + { + const int32_t inc_count = ((0 - ch->counter) / ch->freq_cache) + 1; + + ch->counter += inc_count * ch->freq_cache; + + ch->waveform_index = (ch->waveform_index + inc_count) & 0x1F; + ch->dda = ch->waveform[ch->waveform_index]; + } + } + + while(ch->counter <= 0) + { + ch->waveform_index = (ch->waveform_index + 1) & 0x1F; + ch->dda = ch->waveform[ch->waveform_index]; + + (this->*ch->UpdateOutput)(timestamp + ch->counter, ch); + + if(LFO_On) + { + RunChannel(1, timestamp + ch->counter, false); + RecalcFreqCache(0); + RecalcUOFunc(0); + + ch->counter += (ch->freq_cache <= FREQC7M_COT) ? FREQC7M_COT : ch->freq_cache; // Not particularly accurate, but faster. + } + else + ch->counter += ch->freq_cache; + } +} + +void PCE_PSG::UpdateSubLFO(int32_t timestamp) +{ + for(int chc = 0; chc < 6; chc++) + RunChannel(chc, timestamp, chc == 0); +} + +void PCE_PSG::UpdateSubNonLFO(int32_t timestamp) +{ + for(int chc = 0; chc < 6; chc++) + RunChannel(chc, timestamp, false); +} + +void PCE_PSG::Update(int32_t timestamp) +{ + int32_t run_time = timestamp - lastts; + + if(vol_pending && !vol_update_counter && !vol_update_which) + { + vol_update_counter = 1; + vol_pending = false; + } + + bool lfo_on = (bool)(lfoctrl & 0x03); + + if(lfo_on) + { + if(!(channel[1].control & 0x80) || (lfoctrl & 0x80)) + { + lfo_on = 0; + RecalcFreqCache(0); + RecalcUOFunc(0); + } + } + + int32_t clocks = run_time; + int32_t running_timestamp = lastts; + + while(clocks > 0) + { + int32_t chunk_clocks = clocks; + + if(vol_update_counter > 0 && chunk_clocks > vol_update_counter) + chunk_clocks = vol_update_counter; + + running_timestamp += chunk_clocks; + clocks -= chunk_clocks; + + if(lfo_on) + UpdateSubLFO(running_timestamp); + else + UpdateSubNonLFO(running_timestamp); + + if(vol_update_counter > 0) + { + vol_update_counter -= chunk_clocks; + if(!vol_update_counter) + { + const int phase = vol_update_which & 1; + const int lr = ((vol_update_which >> 1) & 1) ^ 1; + const int chnum = vol_update_which >> 2; + + if(!phase) + { + //printf("Volume update(Read, %d since last): ch=%d, lr=%d, ts=%d\n", running_timestamp - last_read, chnum, lr, running_timestamp); + + if(chnum < 6) + { + vol_update_vllatch = GetVL(chnum, lr); + } + //last_read = running_timestamp; + } + else + { + // printf("Volume update(Apply): ch=%d, lr=%d, ts=%d\n", chnum, lr, running_timestamp); + if(chnum < 6) + { + channel[chnum].vl[lr] = vol_update_vllatch; + } + //last_apply = running_timestamp; + } + vol_update_which = (vol_update_which + 1) & 0x1F; + + if(vol_update_which) + vol_update_counter = phase ? 1 : 255; + else if(vol_pending) + { + vol_update_counter = phase ? 1 : 255; + vol_pending = false; + } + } + } + + lastts = running_timestamp; + } +} + +void PCE_PSG::ResetTS(int32_t ts_base) +{ + lastts = ts_base; + + for(int chc = 0; chc < 6; chc++) + channel[chc].lastts = ts_base; +} + +void PCE_PSG::Power(const int32_t timestamp) +{ + // Not sure about power-on values, these are mostly just intuitive guesses(with some laziness thrown in). + if(timestamp != lastts) + Update(timestamp); + + // Don't memset channel to 0, there's stuff like lastts and blip_prev_samp that shouldn't be altered on Power(). + + select = 0; + globalbalance = 0; + lfofreq = 0; + lfoctrl = 0; + + for(int ch = 0; ch < 6; ch++) + { + channel[ch].frequency = 0; + channel[ch].control = 0x00; + channel[ch].balance = 0; + memset(channel[ch].waveform, 0, 32); + channel[ch].samp_accum = 0; + + channel[ch].waveform_index = 0; + channel[ch].dda = 0x00; + channel[ch].noisectrl = 0x00; + + channel[ch].vl[0] = 0x1F; + channel[ch].vl[1] = 0x1F; + + channel[ch].samp_accum = 0; + + RecalcFreqCache(ch); + RecalcUOFunc(ch); + + channel[ch].counter = channel[ch].freq_cache; + + if(ch >= 4) + { + RecalcNoiseFreqCache(ch); + } + channel[ch].noisecount = 1; + channel[ch].lfsr = 1; + } + + vol_pending = false; + vol_update_counter = 0; + vol_update_which = 0; +} \ No newline at end of file diff --git a/src/engine/platform/sound/pce_psg.h b/src/engine/platform/sound/pce_psg.h new file mode 100644 index 000000000..595494036 --- /dev/null +++ b/src/engine/platform/sound/pce_psg.h @@ -0,0 +1,193 @@ +/* Mednafen - Multi-system Emulator + * + * Original skeleton write handler and PSG structure definition: + * Copyright (C) 2001 Charles MacDonald + * + * 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 + */ + +// additional modifications by tildearrow for furnace + +#ifndef __MDFN_HW_SOUND_PCE_PSG_PCE_PSG_H +#define __MDFN_HW_SOUND_PCE_PSG_PCE_PSG_H + +#include + +class PCE_PSG; + +struct psg_channel +{ + uint8_t waveform[32]; /* Waveform data */ + uint8_t waveform_index; /* Waveform data index */ + uint8_t dda; + uint8_t control; /* Channel enable, DDA, volume */ + uint8_t noisectrl; /* Noise enable/ctrl (channels 4,5 only) */ + + int32_t vl[2]; //vll, vlr; + + int32_t counter; + + void (PCE_PSG::*UpdateOutput)(const int32_t timestamp, psg_channel *ch); + + uint32_t freq_cache; + uint32_t noise_freq_cache; // Channel 4,5 only + int32_t noisecount; + uint32_t lfsr; + + int32_t samp_accum; // The result of adding up all the samples in the waveform buffer(part of an optimization for high-frequency playback). + int32_t blip_prev_samp[2]; + int32_t lastts; + + uint16_t frequency; /* Channel frequency */ + uint8_t balance; /* Channel balance */ +}; + +// Only CH4 and CH5 have NCTRL and LFSR, but it's here for the other channels for "consistency". +enum +{ + PSG_GSREG_CH0_FREQ = 0x000, +// PSG_GSREG_CH0_COUNTER, + PSG_GSREG_CH0_CTRL, + PSG_GSREG_CH0_BALANCE, + PSG_GSREG_CH0_WINDEX, + PSG_GSREG_CH0_SCACHE, + PSG_GSREG_CH0_NCTRL, + PSG_GSREG_CH0_LFSR, + + PSG_GSREG_CH1_FREQ = 0x100, +// PSG_GSREG_CH1_COUNTER, + PSG_GSREG_CH1_CTRL, + PSG_GSREG_CH1_BALANCE, + PSG_GSREG_CH1_WINDEX, + PSG_GSREG_CH1_SCACHE, + PSG_GSREG_CH1_NCTRL, + PSG_GSREG_CH1_LFSR, + + PSG_GSREG_CH2_FREQ = 0x200, +// PSG_GSREG_CH2_COUNTER, + PSG_GSREG_CH2_CTRL, + PSG_GSREG_CH2_BALANCE, + PSG_GSREG_CH2_WINDEX, + PSG_GSREG_CH2_SCACHE, + PSG_GSREG_CH2_NCTRL, + PSG_GSREG_CH2_LFSR, + + PSG_GSREG_CH3_FREQ = 0x300, +// PSG_GSREG_CH3_COUNTER, + PSG_GSREG_CH3_CTRL, + PSG_GSREG_CH3_BALANCE, + PSG_GSREG_CH3_WINDEX, + PSG_GSREG_CH3_SCACHE, + PSG_GSREG_CH3_NCTRL, + PSG_GSREG_CH3_LFSR, + + PSG_GSREG_CH4_FREQ = 0x400, +// PSG_GSREG_CH4_COUNTER, + PSG_GSREG_CH4_CTRL, + PSG_GSREG_CH4_BALANCE, + PSG_GSREG_CH4_WINDEX, + PSG_GSREG_CH4_SCACHE, + PSG_GSREG_CH4_NCTRL, + PSG_GSREG_CH4_LFSR, + + PSG_GSREG_CH5_FREQ = 0x500, +// PSG_GSREG_CH5_COUNTER, + PSG_GSREG_CH5_CTRL, + PSG_GSREG_CH5_BALANCE, + PSG_GSREG_CH5_WINDEX, + PSG_GSREG_CH5_SCACHE, + PSG_GSREG_CH5_NCTRL, + PSG_GSREG_CH5_LFSR, + + PSG_GSREG_SELECT = 0x1000, + PSG_GSREG_GBALANCE, + PSG_GSREG_LFOFREQ, + PSG_GSREG_LFOCTRL, + _PSG_GSREG_COUNT +}; + +class PCE_PSG +{ + public: + + enum + { + REVISION_HUC6280 = 0, + REVISION_HUC6280A, + _REVISION_COUNT + }; + + + PCE_PSG(int32_t* hr_l, int32_t* hr_r, int want_revision); + ~PCE_PSG(); + + void Power(const int32_t timestamp); + void Write(int32_t timestamp, uint8_t A, uint8_t V); + + void SetVolume(double new_volume); + + void Update(int32_t timestamp); + void ResetTS(int32_t ts_base = 0); + + // TODO: timestamp + uint32_t GetRegister(const unsigned int id, char *special, const uint32_t special_len); + void SetRegister(const unsigned int id, const uint32_t value); + + void PeekWave(const unsigned int ch, uint32_t Address, uint32_t Length, uint8_t *Buffer); + void PokeWave(const unsigned int ch, uint32_t Address, uint32_t Length, const uint8_t *Buffer); + + private: + + void UpdateSubLFO(int32_t timestamp); + void UpdateSubNonLFO(int32_t timestamp); + + void RecalcUOFunc(int chnum); + void UpdateOutputSub(const int32_t timestamp, psg_channel *ch, const int32_t samp0, const int32_t samp1); + void UpdateOutput_Off(const int32_t timestamp, psg_channel *ch); + void UpdateOutput_Accum_HuC6280(const int32_t timestamp, psg_channel *ch); + void UpdateOutput_Accum_HuC6280A(const int32_t timestamp, psg_channel *ch); + void UpdateOutput_Norm(const int32_t timestamp, psg_channel *ch); + void UpdateOutput_Noise(const int32_t timestamp, psg_channel *ch); + void (PCE_PSG::*UpdateOutput_Accum)(const int32_t timestamp, psg_channel *ch); + + int32_t GetVL(const int chnum, const int lr); + + void RecalcFreqCache(int chnum); + void RecalcNoiseFreqCache(int chnum); + void RunChannel(int chc, int32_t timestamp, bool LFO_On); + + uint8_t select; /* Selected channel (0-5) */ + uint8_t globalbalance; /* Global sound balance */ + uint8_t lfofreq; /* LFO frequency */ + uint8_t lfoctrl; /* LFO control */ + + int32_t vol_update_counter; + int32_t vol_update_which; + int32_t vol_update_vllatch; + bool vol_pending; + + psg_channel channel[6]; + + int32_t lastts; + int revision; + + int32_t* HRBufs[2]; + + int32_t dbtable_volonly[32]; + + int32_t dbtable[32][32]; +}; + +#endif diff --git a/src/engine/platform/sound/sn76496.cpp b/src/engine/platform/sound/sn76496.cpp index 08c4cc383..6e584e1ab 100644 --- a/src/engine/platform/sound/sn76496.cpp +++ b/src/engine/platform/sound/sn76496.cpp @@ -128,6 +128,7 @@ 10/12/2019: Michael Zapf * READY line handling by own emu_timer, not depending on sound_stream_update + additional modifications by tildearrow for furnace TODO: * Implement the TMS9919 - any difference to sn94624? * Implement the T6W28; has registers in a weird order, needs writes diff --git a/src/engine/platform/sound/sn76496.h b/src/engine/platform/sound/sn76496.h index 4311766d0..0dda93ecf 100644 --- a/src/engine/platform/sound/sn76496.h +++ b/src/engine/platform/sound/sn76496.h @@ -1,7 +1,7 @@ // license:BSD-3-Clause // copyright-holders:Nicola Salmoria -// additional modifications by tildearrow to a +// additional modifications by tildearrow for furnace #ifndef MAME_SOUND_SN76496_H #define MAME_SOUND_SN76496_H