Merge branch 'master' of https://github.com/tildearrow/furnace into ymf278b

This commit is contained in:
cam900 2024-08-25 12:50:51 +09:00
commit 3e1e2fc2a6
73 changed files with 1657 additions and 663 deletions

View file

@ -761,6 +761,12 @@ int DivPlatformArcade::dispatch(DivCommand c) {
immWrite(0x19,0x80|pmDepth);
break;
}
case DIV_CMD_FM_OPMASK:
chan[c.chan].opMask=c.value&15;
if (chan[c.chan].active) {
chan[c.chan].opMaskChanged=true;
}
break;
case DIV_CMD_FM_HARD_RESET:
chan[c.chan].hardReset=c.value;
break;

View file

@ -423,11 +423,11 @@ void DivPlatformAY8910::tick(bool sysTick) {
chan[i].tfx.counter = 0;
chan[i].tfx.out = 0;
if (chan[i].nextPSGMode.val&8) {
if (dumpWrites) addWrite(0xffff0002+(i<<8),0);
//if (dumpWrites) addWrite(0xffff0002+(i<<8),0);
if (chan[i].dac.sample<0 || chan[i].dac.sample>=parent->song.sampleLen) {
if (dumpWrites) {
rWrite(0x08+i,0);
addWrite(0xffff0000+(i<<8),chan[i].dac.sample);
//addWrite(0xffff0000+(i<<8),chan[i].dac.sample);
}
if (chan[i].dac.setPos) {
chan[i].dac.setPos=false;
@ -517,7 +517,7 @@ void DivPlatformAY8910::tick(bool sysTick) {
}
}
chan[i].dac.rate=((double)rate*((sunsoft||clockSel)?8.0:16.0))/(double)(MAX(1,off*chan[i].freq));
if (dumpWrites) addWrite(0xffff0001+(i<<8),chan[i].dac.rate);
//if (dumpWrites) addWrite(0xffff0001+(i<<8),chan[i].dac.rate);
}
if (chan[i].freq<0) chan[i].freq=0;
if (chan[i].freq>4095) chan[i].freq=4095;
@ -604,12 +604,12 @@ int DivPlatformAY8910::dispatch(DivCommand c) {
}
if (chan[c.chan].dac.sample<0 || chan[c.chan].dac.sample>=parent->song.sampleLen) {
chan[c.chan].dac.sample=-1;
if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0);
//if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0);
break;
} else {
if (dumpWrites) {
rWrite(0x08+c.chan,0);
addWrite(0xffff0000+(c.chan<<8),chan[c.chan].dac.sample);
//addWrite(0xffff0000+(c.chan<<8),chan[c.chan].dac.sample);
}
}
if (chan[c.chan].dac.setPos) {
@ -637,10 +637,10 @@ int DivPlatformAY8910::dispatch(DivCommand c) {
chan[c.chan].dac.sample=12*sampleBank+chan[c.chan].note%12;
if (chan[c.chan].dac.sample>=parent->song.sampleLen) {
chan[c.chan].dac.sample=-1;
if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0);
//if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0);
break;
} else {
if (dumpWrites) addWrite(0xffff0000+(c.chan<<8),chan[c.chan].dac.sample);
//if (dumpWrites) addWrite(0xffff0000+(c.chan<<8),chan[c.chan].dac.sample);
}
if (chan[c.chan].dac.setPos) {
chan[c.chan].dac.setPos=false;
@ -651,7 +651,7 @@ int DivPlatformAY8910::dispatch(DivCommand c) {
chan[c.chan].dac.rate=parent->getSample(chan[c.chan].dac.sample)->rate*2048;
if (dumpWrites) {
rWrite(0x08+c.chan,0);
addWrite(0xffff0001+(c.chan<<8),chan[c.chan].dac.rate);
//addWrite(0xffff0001+(c.chan<<8),chan[c.chan].dac.rate);
}
chan[c.chan].dac.furnaceDAC=false;
}
@ -686,7 +686,7 @@ int DivPlatformAY8910::dispatch(DivCommand c) {
}
case DIV_CMD_NOTE_OFF:
chan[c.chan].dac.sample=-1;
if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0);
//if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0);
chan[c.chan].nextPSGMode.val&=~8;
chan[c.chan].keyOff=true;
chan[c.chan].active=false;
@ -867,7 +867,7 @@ int DivPlatformAY8910::dispatch(DivCommand c) {
case DIV_CMD_SAMPLE_POS:
chan[c.chan].dac.pos=c.value;
chan[c.chan].dac.setPos=true;
if (dumpWrites) addWrite(0xffff0005,chan[c.chan].dac.pos);
//if (dumpWrites) addWrite(0xffff0005,chan[c.chan].dac.pos);
break;
case DIV_CMD_MACRO_OFF:
chan[c.chan].std.mask(c.value,true);

View file

@ -1463,6 +1463,13 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
}
break;
}
case DIV_CMD_FM_OPMASK:
if (c.chan>=psgChanOffs) break;
chan[c.chan].opMask=c.value&15;
if (chan[c.chan].active) {
chan[c.chan].opMaskChanged=true;
}
break;
case DIV_CMD_FM_HARD_RESET:
if (c.chan>=6) break;
chan[c.chan].hardReset=c.value;

View file

@ -28,7 +28,7 @@ struct _nla_table nla_table;
#define CHIP_DIVIDER 16
#define rWrite(a,v) if (!skipRegisterWrites) {doWrite(a,v); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} }
#define rWrite(a,v) if (!skipRegisterWrites) {writes.push(QueuedWrite((a),v)); if (dumpWrites) {addWrite((a),v);} }
const char* regCheatSheetNES[]={
"S0Volume", "4000",
@ -86,10 +86,10 @@ void DivPlatformNES::doWrite(unsigned short addr, unsigned char data) {
unsigned char next=((unsigned char)s->data8[dacPos]+0x80)>>1; \
if (dacAntiClickOn && dacAntiClick<next) { \
dacAntiClick+=8; \
rWrite(0x4011,dacAntiClick); \
doWrite(0x4011,dacAntiClick); \
} else { \
dacAntiClickOn=false; \
rWrite(0x4011,next); \
doWrite(0x4011,next); \
} \
} \
dacPos++; \
@ -108,6 +108,13 @@ void DivPlatformNES::doWrite(unsigned short addr, unsigned char data) {
void DivPlatformNES::acquire_puNES(short** buf, size_t len) {
for (size_t i=0; i<len; i++) {
doPCM;
if (!writes.empty()) {
QueuedWrite w=writes.front();
doWrite(w.addr,w.val);
regPool[w.addr&0x1f]=w.val;
writes.pop();
}
apu_tick(nes,NULL);
nes->apu.odd_cycle=!nes->apu.odd_cycle;
@ -134,6 +141,13 @@ void DivPlatformNES::acquire_NSFPlay(short** buf, size_t len) {
int out2[2];
for (size_t i=0; i<len; i++) {
doPCM;
if (!writes.empty()) {
QueuedWrite w=writes.front();
doWrite(w.addr,w.val);
regPool[w.addr&0x1f]=w.val;
writes.pop();
}
nes1_NP->Tick(8);
nes2_NP->TickFrameSequence(8);
@ -297,7 +311,7 @@ void DivPlatformNES::tick(bool sysTick) {
if (chan[i].sweepChanged) {
chan[i].sweepChanged=false;
if (i==0) {
//rWrite(16+i*5,chan[i].sweep);
// rWrite(16+i*5,chan[i].sweep);
}
}
if (i<3) if (chan[i].std.phaseReset.had) {
@ -338,7 +352,7 @@ void DivPlatformNES::tick(bool sysTick) {
}
}
if (chan[i].keyOff) {
//rWrite(16+i*5+2,8);
// rWrite(16+i*5+2,8);
if (i==2) { // triangle
rWrite(0x4000+i*4,0x00);
} else {
@ -400,6 +414,34 @@ void DivPlatformNES::tick(bool sysTick) {
logV("switching bank to %d",dpcmBank);
if (dumpWrites) addWrite(0xffff0004,dpcmBank);
}
// sample custom loop point...
DivSample* lsamp=parent->getSample(dacSample);
// how it works:
// when the initial sample info is written (see above) and playback is launched,
// the parameters (start point in memory and length) are locked until sample end
// is reached.
// thus, if we write new data after just several APU clock cycles, it will be used only when
// sample finishes one full loop.
// thus we can write sample's loop point as "start address" and sample's looped part length
// as "full sample length".
// APU will play full sample once and then repeatedly cycle through the looped part.
// sources:
// https://www.nesdev.org/wiki/APU_DMC
// https://www.youtube.com/watch?v=vB4P8x2Am6Y
if (lsamp->loopEnd>lsamp->loopStart && goingToLoop) {
int loopStartAddr=(sampleOffDPCM[dacSample]+lsamp->loopStart)>>3;
int loopLen=(lsamp->loopEnd-lsamp->loopStart)>>3;
rWrite(0x4012,(loopStartAddr>>6)&0xff);
rWrite(0x4013,(loopLen>>4)&0xff);
}
}
} else {
if (nextDPCMFreq>=0) {
@ -790,6 +832,7 @@ float DivPlatformNES::getPostAmp() {
}
void DivPlatformNES::reset() {
while (!writes.empty()) writes.pop();
for (int i=0; i<5; i++) {
chan[i]=DivPlatformNES::Channel();
chan[i].std.setEngine(parent);

View file

@ -24,6 +24,7 @@
#include "sound/nes_nsfplay/nes_apu.h"
#include "sound/nes_nsfplay/5e01_apu.h"
#include "../../fixedQueue.h"
class DivPlatformNES: public DivDispatch {
struct Channel: public SharedChannel<signed char> {
@ -44,6 +45,13 @@ class DivPlatformNES: public DivDispatch {
Channel chan[5];
DivDispatchOscBuffer* oscBuf[5];
bool isMuted[5];
struct QueuedWrite {
unsigned short addr;
unsigned char val;
QueuedWrite(): addr(0), val(0) {}
QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v) {}
};
FixedQueue<QueuedWrite,128> writes;
int dacPeriod, dacRate, dpcmPos;
unsigned int dacPos, dacAntiClick;
int dacSample;

View file

@ -165,8 +165,12 @@ int DivPlatformSMS::snCalcFreq(int ch) {
if (ch==3) CHIP_DIVIDER=noiseDivider;
int easyStartingPeriod=16;
int easyThreshold=round(128.0*12.0*log((chipClock/(easyStartingPeriod*CHIP_DIVIDER))/(0.0625*parent->song.tuning))/log(2.0))-384+64;
if (parent->song.linearPitch==2 && easyNoise && chan[ch].baseFreq+chan[ch].pitch+chan[ch].pitch2>(easyThreshold)) {
int ret=(((easyStartingPeriod<<7))-(chan[ch].baseFreq+chan[ch].pitch+chan[ch].pitch2-(easyThreshold)))>>7;
int curFreq=chan[ch].baseFreq+chan[ch].pitch+chan[ch].pitch2+(chan[ch].arpOff<<7);
if (chan[ch].fixedArp) {
curFreq=chan[ch].baseNoteOverride<<7;
}
if (parent->song.linearPitch==2 && easyNoise && curFreq>easyThreshold) {
int ret=(((easyStartingPeriod<<7))-(curFreq-(easyThreshold)))>>7;
if (ret<0) ret=0;
return ret;
}

View file

@ -841,6 +841,8 @@ void DivPlatformSNES::reset() {
memcpy(sampleMem,copyOfSampleMem,65536);
dsp.init(sampleMem);
dsp.set_output(NULL,0);
dsp.setupInterpolation(!interpolationOff);
memset(regPool,0,128);
// this can't be 0 or channel 1 won't play
// this can't be 0x100 either as that's used by SPC700 page 1 and the stack
@ -1023,6 +1025,8 @@ void DivPlatformSNES::setFlags(const DivConfig& flags) {
initEchoFIR[7]=flags.getInt("echoFilter7",0);
initEchoMask=flags.getInt("echoMask",0);
interpolationOff=flags.getBool("interpolationOff",false);
}
int DivPlatformSNES::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) {

View file

@ -69,6 +69,7 @@ class DivPlatformSNES: public DivDispatch {
bool writeEcho;
bool writeDryVol;
bool echoOn;
bool interpolationOff;
bool initEchoOn;
signed char initEchoVolL;

View file

@ -135,24 +135,30 @@ static short const gauss [512] =
1299,1300,1300,1301,1302,1302,1303,1303,1303,1304,1304,1304,1304,1304,1305,1305,
};
void SPC_DSP::setupInterpolation(bool interpolate){for(int i=0;i<voice_count;i++){m.voices[i].interpolate=interpolate;}}
inline int SPC_DSP::interpolate( voice_t const* v )
{
// Make pointers into gaussian based on fractional position between samples
int offset = v->interp_pos >> 4 & 0xFF;
short const* fwd = gauss + 255 - offset;
short const* rev = gauss + offset; // mirror left half of gaussian
if (v->interpolate) {
int offset = v->interp_pos >> 4 & 0xFF;
short const* fwd = gauss + 255 - offset;
short const* rev = gauss + offset; // mirror left half of gaussian
int const* in = &v->buf [(v->interp_pos >> 12) + v->buf_pos];
int out;
out = (fwd [ 0] * in [0]) >> 11;
out += (fwd [256] * in [1]) >> 11;
out += (rev [256] * in [2]) >> 11;
out = (int16_t) out;
out += (rev [ 0] * in [3]) >> 11;
int const* in = &v->buf [(v->interp_pos >> 12) + v->buf_pos];
int out;
out = (fwd [ 0] * in [0]) >> 11;
out += (fwd [256] * in [1]) >> 11;
out += (rev [256] * in [2]) >> 11;
out = (int16_t) out;
out += (rev [ 0] * in [3]) >> 11;
CLAMP16( out );
out &= ~1;
return out;
CLAMP16( out );
out &= ~1;
return out;
} else {
return v->buf [(v->interp_pos >> 12) + v->buf_pos]; //Furnace addition -- no interpolation
}
}

View file

@ -27,6 +27,9 @@ public:
// output buffer could hold.
int sample_count() const;
// Furnace addition: disable/enable Gaussian interpolation
void setupInterpolation(bool interpolate);
// Emulation
// Resets DSP to power-on state
@ -122,6 +125,7 @@ public:
int hidden_env; // used by GAIN mode 7, very obscure quirk
uint8_t t_envx_out;
sample_t out[2]; // Furnace addition, for per-channel oscilloscope
bool interpolate; // Furnace addition, to disable interpolation
};
// Furnace addition, gets a voice

View file

@ -858,6 +858,9 @@ int DivPlatformTX81Z::dispatch(DivCommand c) {
immWrite(0x17,0x80|pmDepth);
break;
}
case DIV_CMD_FM_OPMASK:
// TODO: if OPZ supports op mask
break;
case DIV_CMD_FM_HARD_RESET:
chan[c.chan].hardReset=c.value;
break;

View file

@ -1004,6 +1004,13 @@ int DivPlatformYM2203::dispatch(DivCommand c) {
}
break;
}
case DIV_CMD_FM_OPMASK:
if (c.chan>=psgChanOffs) break;
chan[c.chan].opMask=c.value&15;
if (chan[c.chan].active) {
chan[c.chan].opMaskChanged=true;
}
break;
case DIV_CMD_FM_HARD_RESET:
chan[c.chan].hardReset=c.value;
break;

View file

@ -1537,6 +1537,13 @@ int DivPlatformYM2608::dispatch(DivCommand c) {
}
break;
}
case DIV_CMD_FM_OPMASK:
if (c.chan>=psgChanOffs) break;
chan[c.chan].opMask=c.value&15;
if (chan[c.chan].active) {
chan[c.chan].opMaskChanged=true;
}
break;
case DIV_CMD_FM_HARD_RESET:
chan[c.chan].hardReset=c.value;
break;

View file

@ -1507,6 +1507,13 @@ int DivPlatformYM2610::dispatch(DivCommand c) {
}
break;
}
case DIV_CMD_FM_OPMASK:
if (c.chan>=psgChanOffs) break;
chan[c.chan].opMask=c.value&15;
if (chan[c.chan].active) {
chan[c.chan].opMaskChanged=true;
}
break;
case DIV_CMD_FM_HARD_RESET:
chan[c.chan].hardReset=c.value;
break;

View file

@ -1576,6 +1576,13 @@ int DivPlatformYM2610B::dispatch(DivCommand c) {
}
break;
}
case DIV_CMD_FM_OPMASK:
if (c.chan>=psgChanOffs) break;
chan[c.chan].opMask=c.value&15;
if (chan[c.chan].active) {
chan[c.chan].opMaskChanged=true;
}
break;
case DIV_CMD_FM_HARD_RESET:
chan[c.chan].hardReset=c.value;
break;