prepare for PC Engine platform

using Mednafen core
This commit is contained in:
tildearrow 2021-06-06 14:02:38 -05:00
parent 0b8fcc6e8d
commit d74fa698af
8 changed files with 1349 additions and 1 deletions

View File

@ -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

View File

@ -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 <math.h>
#include <zlib.h>
@ -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;

280
src/engine/platform/pce.cpp Normal file
View File

@ -0,0 +1,280 @@
#include "pce.h"
#include "../engine.h"
#include <math.h>
//#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<<c.chan);
if (c.value==0) c.value=0x11;
lastPan|=c.value<<c.chan;
rWrite(0x25,lastPan);
break;
}
case DIV_CMD_LEGATO:
if (c.chan==3) break;
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;
break;
case DIV_CMD_PRE_PORTA:
chan[c.chan].std.init(parent->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;
}

46
src/engine/platform/pce.h Normal file
View File

@ -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

View File

@ -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 <math.h>
#include <string.h>
#include <assert.h>
// 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;
}

View File

@ -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 <stdint.h>
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

View File

@ -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

View File

@ -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