Merge branch 'master' of https://github.com/tildearrow/furnace into snes
This commit is contained in:
commit
0ee6d761f5
1085 changed files with 31936 additions and 7804 deletions
|
|
@ -90,8 +90,8 @@ bool DivDispatch::getDCOffRequired() {
|
|||
return false;
|
||||
}
|
||||
|
||||
const char* DivDispatch::getEffectName(unsigned char effect) {
|
||||
return NULL;
|
||||
bool DivDispatch::getWantPreNote() {
|
||||
return false;
|
||||
}
|
||||
|
||||
void DivDispatch::setFlags(unsigned int flags) {
|
||||
|
|
|
|||
|
|
@ -64,21 +64,6 @@ const char** DivPlatformAmiga::getRegisterSheet() {
|
|||
return regCheatSheetAmiga;
|
||||
}
|
||||
|
||||
const char* DivPlatformAmiga::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x10:
|
||||
return "10xx: Toggle filter (0 disables; 1 enables)";
|
||||
break;
|
||||
case 0x11:
|
||||
return "11xx: Toggle AM with next channel";
|
||||
break;
|
||||
case 0x12:
|
||||
return "12xx: Toggle period modulation with next channel";
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#define writeAudDat(x) \
|
||||
chan[i].audDat=x; \
|
||||
if (i<3 && chan[i].useV) { \
|
||||
|
|
@ -114,12 +99,10 @@ void DivPlatformAmiga::acquire(short* bufL, short* bufR, size_t start, size_t le
|
|||
if (chan[i].audPos<s->samples) {
|
||||
writeAudDat(s->data8[chan[i].audPos++]);
|
||||
}
|
||||
if (chan[i].audPos>=s->samples || chan[i].audPos>=131071) {
|
||||
if (s->loopStart>=0 && s->loopStart<(int)s->samples) {
|
||||
chan[i].audPos=s->loopStart;
|
||||
} else {
|
||||
chan[i].sample=-1;
|
||||
}
|
||||
if (s->isLoopable() && chan[i].audPos>=MIN(131071,s->getEndPosition())) {
|
||||
chan[i].audPos=s->loopStart;
|
||||
} else if (chan[i].audPos>=MIN(131071,s->samples)) {
|
||||
chan[i].sample=-1;
|
||||
}
|
||||
} else {
|
||||
chan[i].sample=-1;
|
||||
|
|
@ -180,19 +163,9 @@ void DivPlatformAmiga::tick(bool sysTick) {
|
|||
}
|
||||
}
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=round(off*NOTE_PERIODIC_NOROUND(chan[i].std.arp.val));
|
||||
} else {
|
||||
chan[i].baseFreq=round(off*NOTE_PERIODIC_NOROUND(chan[i].note+chan[i].std.arp.val));
|
||||
}
|
||||
}
|
||||
// TODO: why the off mult? this may be a bug!
|
||||
chan[i].baseFreq=round(off*NOTE_PERIODIC_NOROUND(parent->calcArp(chan[i].note,chan[i].std.arp.val)));
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=round(off*NOTE_PERIODIC_NOROUND(chan[i].note));
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].useWave && chan[i].std.wave.had) {
|
||||
if (chan[i].wave!=chan[i].std.wave.val || chan[i].ws.activeChanged()) {
|
||||
|
|
@ -355,6 +328,7 @@ int DivPlatformAmiga::dispatch(DivCommand c) {
|
|||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA));
|
||||
}
|
||||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note);
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_SAMPLE_POS:
|
||||
|
|
|
|||
|
|
@ -105,7 +105,6 @@ class DivPlatformAmiga: public DivDispatch {
|
|||
void notifyWaveChange(int wave);
|
||||
void notifyInsDeletion(void* ins);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -22,38 +22,6 @@
|
|||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "fmshared_OPM.h"
|
||||
|
||||
static unsigned short chanOffs[8]={
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07
|
||||
};
|
||||
static unsigned short opOffs[4]={
|
||||
0x00, 0x08, 0x10, 0x18
|
||||
};
|
||||
static bool isOutput[8][4]={
|
||||
// 1 3 2 4
|
||||
{false,false,false,true},
|
||||
{false,false,false,true},
|
||||
{false,false,false,true},
|
||||
{false,false,false,true},
|
||||
{false,false,true ,true},
|
||||
{false,true ,true ,true},
|
||||
{false,true ,true ,true},
|
||||
{true ,true ,true ,true},
|
||||
};
|
||||
static unsigned char dtTable[8]={
|
||||
7,6,5,0,1,2,3,4
|
||||
};
|
||||
|
||||
static int orderedOps[4]={
|
||||
0,2,1,3
|
||||
};
|
||||
|
||||
#define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;}
|
||||
#define immWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} }
|
||||
|
||||
#define NOTE_LINEAR(x) (((x)<<6)+baseFreqOff+log2(parent->song.tuning/440.0)*12.0*64.0)
|
||||
|
||||
const char* regCheatSheetOPM[]={
|
||||
"Test", "00",
|
||||
"NoteCtl", "08",
|
||||
|
|
@ -82,111 +50,6 @@ const char** DivPlatformArcade::getRegisterSheet() {
|
|||
return regCheatSheetOPM;
|
||||
}
|
||||
|
||||
const char* DivPlatformArcade::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x10:
|
||||
return "10xx: Set noise frequency (xx: value; 0 disables noise)";
|
||||
break;
|
||||
case 0x11:
|
||||
return "11xx: Set feedback (0 to 7)";
|
||||
break;
|
||||
case 0x12:
|
||||
return "12xx: Set level of operator 1 (0 highest, 7F lowest)";
|
||||
break;
|
||||
case 0x13:
|
||||
return "13xx: Set level of operator 2 (0 highest, 7F lowest)";
|
||||
break;
|
||||
case 0x14:
|
||||
return "14xx: Set level of operator 3 (0 highest, 7F lowest)";
|
||||
break;
|
||||
case 0x15:
|
||||
return "15xx: Set level of operator 4 (0 highest, 7F lowest)";
|
||||
break;
|
||||
case 0x16:
|
||||
return "16xy: Set operator multiplier (x: operator from 1 to 4; y: multiplier)";
|
||||
break;
|
||||
case 0x17:
|
||||
return "17xx: Set LFO speed";
|
||||
break;
|
||||
case 0x18:
|
||||
return "18xx: Set LFO waveform (0 saw, 1 square, 2 triangle, 3 noise)";
|
||||
break;
|
||||
case 0x19:
|
||||
return "19xx: Set attack of all operators (0 to 1F)";
|
||||
break;
|
||||
case 0x1a:
|
||||
return "1Axx: Set attack of operator 1 (0 to 1F)";
|
||||
break;
|
||||
case 0x1b:
|
||||
return "1Bxx: Set attack of operator 2 (0 to 1F)";
|
||||
break;
|
||||
case 0x1c:
|
||||
return "1Cxx: Set attack of operator 3 (0 to 1F)";
|
||||
break;
|
||||
case 0x1d:
|
||||
return "1Dxx: Set attack of operator 4 (0 to 1F)";
|
||||
break;
|
||||
case 0x1e:
|
||||
return "1Exx: Set AM depth (0 to 7F)";
|
||||
break;
|
||||
case 0x1f:
|
||||
return "1Fxx: Set PM depth (0 to 7F)";
|
||||
break;
|
||||
case 0x30:
|
||||
return "30xx: Toggle hard envelope reset on new notes";
|
||||
break;
|
||||
case 0x50:
|
||||
return "50xy: Set AM (x: operator from 1 to 4 (0 for all ops); y: AM)";
|
||||
break;
|
||||
case 0x51:
|
||||
return "51xy: Set sustain level (x: operator from 1 to 4 (0 for all ops); y: sustain)";
|
||||
break;
|
||||
case 0x52:
|
||||
return "52xy: Set release (x: operator from 1 to 4 (0 for all ops); y: release)";
|
||||
break;
|
||||
case 0x53:
|
||||
return "53xy: Set detune (x: operator from 1 to 4 (0 for all ops); y: detune where 3 is center)";
|
||||
break;
|
||||
case 0x54:
|
||||
return "54xy: Set envelope scale (x: operator from 1 to 4 (0 for all ops); y: scale from 0 to 3)";
|
||||
break;
|
||||
case 0x55:
|
||||
return "55xy: Set detune 2 (x: operator from 1 to 4 (0 for all ops); y: detune from 0 to 3)";
|
||||
break;
|
||||
case 0x56:
|
||||
return "56xx: Set decay of all operators (0 to 1F)";
|
||||
break;
|
||||
case 0x57:
|
||||
return "57xx: Set decay of operator 1 (0 to 1F)";
|
||||
break;
|
||||
case 0x58:
|
||||
return "58xx: Set decay of operator 2 (0 to 1F)";
|
||||
break;
|
||||
case 0x59:
|
||||
return "59xx: Set decay of operator 3 (0 to 1F)";
|
||||
break;
|
||||
case 0x5a:
|
||||
return "5Axx: Set decay of operator 4 (0 to 1F)";
|
||||
break;
|
||||
case 0x5b:
|
||||
return "5Bxx: Set decay 2 of all operators (0 to 1F)";
|
||||
break;
|
||||
case 0x5c:
|
||||
return "5Cxx: Set decay 2 of operator 1 (0 to 1F)";
|
||||
break;
|
||||
case 0x5d:
|
||||
return "5Dxx: Set decay 2 of operator 2 (0 to 1F)";
|
||||
break;
|
||||
case 0x5e:
|
||||
return "5Exx: Set decay 2 of operator 3 (0 to 1F)";
|
||||
break;
|
||||
case 0x5f:
|
||||
return "5Fxx: Set decay 2 of operator 4 (0 to 1F)";
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DivPlatformArcade::acquire_nuked(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
static int o[2];
|
||||
|
||||
|
|
@ -198,13 +61,13 @@ void DivPlatformArcade::acquire_nuked(short* bufL, short* bufR, size_t start, si
|
|||
OPM_Write(&fm,1,w.val);
|
||||
regPool[w.addr&0xff]=w.val;
|
||||
//printf("write: %x = %.2x\n",w.addr,w.val);
|
||||
writes.pop();
|
||||
writes.pop_front();
|
||||
} else {
|
||||
OPM_Write(&fm,0,w.addr);
|
||||
w.addrOrVal=true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
OPM_Clock(&fm,NULL,NULL,NULL,NULL);
|
||||
OPM_Clock(&fm,NULL,NULL,NULL,NULL);
|
||||
OPM_Clock(&fm,NULL,NULL,NULL,NULL);
|
||||
|
|
@ -214,13 +77,13 @@ void DivPlatformArcade::acquire_nuked(short* bufL, short* bufR, size_t start, si
|
|||
for (int i=0; i<8; i++) {
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=fm.ch_out[i];
|
||||
}
|
||||
|
||||
|
||||
if (o[0]<-32768) o[0]=-32768;
|
||||
if (o[0]>32767) o[0]=32767;
|
||||
|
||||
if (o[1]<-32768) o[1]=-32768;
|
||||
if (o[1]>32767) o[1]=32767;
|
||||
|
||||
|
||||
bufL[h]=o[0];
|
||||
bufR[h]=o[1];
|
||||
}
|
||||
|
|
@ -239,11 +102,11 @@ void DivPlatformArcade::acquire_ymfm(short* bufL, short* bufR, size_t start, siz
|
|||
fm_ymfm->write(0x0+((w.addr>>8)<<1),w.addr);
|
||||
fm_ymfm->write(0x1+((w.addr>>8)<<1),w.val);
|
||||
regPool[w.addr&0xff]=w.val;
|
||||
writes.pop();
|
||||
writes.pop_front();
|
||||
delay=1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fm_ymfm->generate(&out_ymfm);
|
||||
|
||||
for (int i=0; i<8; i++) {
|
||||
|
|
@ -257,7 +120,7 @@ void DivPlatformArcade::acquire_ymfm(short* bufL, short* bufR, size_t start, siz
|
|||
os[1]=out_ymfm.data[1];
|
||||
if (os[1]<-32768) os[1]=-32768;
|
||||
if (os[1]>32767) os[1]=32767;
|
||||
|
||||
|
||||
bufL[h]=os[0];
|
||||
bufR[h]=os[1];
|
||||
}
|
||||
|
|
@ -298,18 +161,9 @@ void DivPlatformArcade::tick(bool sysTick) {
|
|||
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_LINEAR(chan[i].std.arp.val);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_LINEAR(chan[i].note+(signed char)chan[i].std.arp.val);
|
||||
}
|
||||
chan[i].baseFreq=NOTE_LINEAR(parent->calcArp(chan[i].note,chan[i].std.arp.val));
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_LINEAR(chan[i].note);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
|
||||
if (chan[i].std.duty.had) {
|
||||
|
|
@ -401,6 +255,10 @@ void DivPlatformArcade::tick(bool sysTick) {
|
|||
chan[i].state.ams=chan[i].std.ams.val;
|
||||
rWrite(chanOffs[i]+ADDR_FMS_AMS,((chan[i].state.fms&7)<<4)|(chan[i].state.ams&3));
|
||||
}
|
||||
if (chan[i].std.ex4.had && chan[i].active) {
|
||||
chan[i].opMask=chan[i].std.ex4.val&15;
|
||||
chan[i].opMaskChanged=true;
|
||||
}
|
||||
for (int j=0; j<4; j++) {
|
||||
unsigned short baseAddr=chanOffs[i]|opOffs[j];
|
||||
DivInstrumentFM::Operator& op=chan[i].state.op[j];
|
||||
|
|
@ -493,8 +351,9 @@ void DivPlatformArcade::tick(bool sysTick) {
|
|||
immWrite(i+0x30,chan[i].freq<<2);
|
||||
chan[i].freqChanged=false;
|
||||
}
|
||||
if (chan[i].keyOn) {
|
||||
immWrite(0x08,0x78|i);
|
||||
if (chan[i].keyOn || chan[i].opMaskChanged) {
|
||||
immWrite(0x08,(chan[i].opMask<<3)|i);
|
||||
chan[i].opMaskChanged=false;
|
||||
chan[i].keyOn=false;
|
||||
}
|
||||
}
|
||||
|
|
@ -516,6 +375,11 @@ int DivPlatformArcade::dispatch(DivCommand c) {
|
|||
|
||||
if (chan[c.chan].insChanged) {
|
||||
chan[c.chan].state=ins->fm;
|
||||
chan[c.chan].opMask=
|
||||
(chan[c.chan].state.op[0].enable?1:0)|
|
||||
(chan[c.chan].state.op[2].enable?2:0)|
|
||||
(chan[c.chan].state.op[1].enable?4:0)|
|
||||
(chan[c.chan].state.op[3].enable?8:0);
|
||||
}
|
||||
|
||||
chan[c.chan].macroInit(ins);
|
||||
|
|
@ -648,6 +512,12 @@ int DivPlatformArcade::dispatch(DivCommand c) {
|
|||
break;
|
||||
}
|
||||
case DIV_CMD_FM_LFO: {
|
||||
if(c.value==0) {
|
||||
rWrite(0x01,0x02);
|
||||
}
|
||||
else {
|
||||
rWrite(0x01,0x00);
|
||||
}
|
||||
rWrite(0x18,c.value);
|
||||
break;
|
||||
}
|
||||
|
|
@ -859,6 +729,7 @@ int DivPlatformArcade::dispatch(DivCommand c) {
|
|||
return 127;
|
||||
break;
|
||||
case DIV_CMD_PRE_PORTA:
|
||||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_LINEAR(chan[c.chan].note);
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_PRE_NOTE:
|
||||
|
|
@ -938,7 +809,7 @@ void DivPlatformArcade::poke(std::vector<DivRegWrite>& wlist) {
|
|||
}
|
||||
|
||||
void DivPlatformArcade::reset() {
|
||||
while (!writes.empty()) writes.pop();
|
||||
while (!writes.empty()) writes.pop_front();
|
||||
memset(regPool,0,256);
|
||||
if (useYMFM) {
|
||||
fm_ymfm->reset();
|
||||
|
|
@ -970,6 +841,8 @@ void DivPlatformArcade::reset() {
|
|||
pmDepth=0x7f;
|
||||
|
||||
//rWrite(0x18,0x10);
|
||||
immWrite(0x01,0x02); // LFO Off
|
||||
immWrite(0x18,0x00); // LFO Freq Off
|
||||
immWrite(0x19,amDepth);
|
||||
immWrite(0x19,0x80|pmDepth);
|
||||
//rWrite(0x1b,0x00);
|
||||
|
|
@ -978,15 +851,20 @@ void DivPlatformArcade::reset() {
|
|||
}
|
||||
|
||||
void DivPlatformArcade::setFlags(unsigned int flags) {
|
||||
if (flags==2) {
|
||||
chipClock=4000000.0;
|
||||
baseFreqOff=-122;
|
||||
} else if (flags==1) {
|
||||
chipClock=COLOR_PAL*4.0/5.0;
|
||||
baseFreqOff=12;
|
||||
} else {
|
||||
chipClock=COLOR_NTSC;
|
||||
baseFreqOff=0;
|
||||
switch (flags&0xff) {
|
||||
default:
|
||||
case 0:
|
||||
chipClock=COLOR_NTSC;
|
||||
baseFreqOff=0;
|
||||
break;
|
||||
case 1:
|
||||
chipClock=COLOR_PAL*4.0/5.0;
|
||||
baseFreqOff=12;
|
||||
break;
|
||||
case 2:
|
||||
chipClock=4000000.0;
|
||||
baseFreqOff=-122;
|
||||
break;
|
||||
}
|
||||
rate=chipClock/64;
|
||||
for (int i=0; i<8; i++) {
|
||||
|
|
|
|||
|
|
@ -19,19 +19,23 @@
|
|||
|
||||
#ifndef _ARCADE_H
|
||||
#define _ARCADE_H
|
||||
#include "../dispatch.h"
|
||||
#include "fmshared_OPM.h"
|
||||
#include "../macroInt.h"
|
||||
#include "../instrument.h"
|
||||
#include <queue>
|
||||
#include "../../../extern/opm/opm.h"
|
||||
#include "sound/ymfm/ymfm_opm.h"
|
||||
#include "../macroInt.h"
|
||||
|
||||
class DivArcadeInterface: public ymfm::ymfm_interface {
|
||||
|
||||
};
|
||||
|
||||
class DivPlatformArcade: public DivDispatch {
|
||||
class DivPlatformArcade: public DivPlatformOPM {
|
||||
protected:
|
||||
const unsigned short chanOffs[8]={
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07
|
||||
};
|
||||
|
||||
struct Channel {
|
||||
DivInstrumentFM state;
|
||||
DivMacroInt std;
|
||||
|
|
@ -39,9 +43,9 @@ class DivPlatformArcade: public DivDispatch {
|
|||
int freq, baseFreq, pitch, pitch2, note;
|
||||
int ins;
|
||||
signed char konCycles;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, portaPause, furnacePCM, hardReset;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, portaPause, furnacePCM, hardReset, opMaskChanged;
|
||||
int vol, outVol;
|
||||
unsigned char chVolL, chVolR;
|
||||
unsigned char chVolL, chVolR, opMask;
|
||||
void macroInit(DivInstrument* which) {
|
||||
std.init(which);
|
||||
pitch2=0;
|
||||
|
|
@ -64,38 +68,27 @@ class DivPlatformArcade: public DivDispatch {
|
|||
portaPause(false),
|
||||
furnacePCM(false),
|
||||
hardReset(false),
|
||||
opMaskChanged(false),
|
||||
vol(0),
|
||||
outVol(0),
|
||||
chVolL(127),
|
||||
chVolR(127) {}
|
||||
chVolR(127),
|
||||
opMask(15) {}
|
||||
};
|
||||
Channel chan[8];
|
||||
DivDispatchOscBuffer* oscBuf[8];
|
||||
struct QueuedWrite {
|
||||
unsigned short addr;
|
||||
unsigned char val;
|
||||
bool addrOrVal;
|
||||
QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {}
|
||||
};
|
||||
std::queue<QueuedWrite> writes;
|
||||
opm_t fm;
|
||||
int delay, baseFreqOff;
|
||||
int baseFreqOff;
|
||||
int pcmL, pcmR, pcmCycles;
|
||||
unsigned char lastBusy;
|
||||
unsigned char amDepth, pmDepth;
|
||||
|
||||
ymfm::ym2151* fm_ymfm;
|
||||
ymfm::ym2151::output_data out_ymfm;
|
||||
DivArcadeInterface iface;
|
||||
|
||||
unsigned char regPool[256];
|
||||
|
||||
bool extMode, useYMFM;
|
||||
|
||||
bool isMuted[8];
|
||||
|
||||
short oldWrites[256];
|
||||
short pendingWrites[256];
|
||||
|
||||
int octave(int freq);
|
||||
int toFreq(int freq);
|
||||
|
|
@ -124,7 +117,6 @@ class DivPlatformArcade: public DivDispatch {
|
|||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
~DivPlatformArcade();
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@
|
|||
#define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;}
|
||||
#define immWrite(a,v) if (!skipRegisterWrites) {writes.emplace(regRemap(a),v); if (dumpWrites) {addWrite(regRemap(a),v);} }
|
||||
|
||||
#define CHIP_DIVIDER ((sunsoft||clockSel)?16:8)
|
||||
#define CHIP_DIVIDER (extMode?extDiv:((sunsoft||clockSel)?16:8))
|
||||
|
||||
const char* regCheatSheetAY[]={
|
||||
"FreqL_A", "0",
|
||||
|
|
@ -69,46 +69,18 @@ const char* regCheatSheetAY8914[]={
|
|||
NULL
|
||||
};
|
||||
|
||||
// taken from ay8910.cpp
|
||||
const int sunsoftVolTable[32]={
|
||||
103350, 73770, 52657, 37586, 32125, 27458, 24269, 21451,
|
||||
18447, 15864, 14009, 12371, 10506, 8922, 7787, 6796,
|
||||
5689, 4763, 4095, 3521, 2909, 2403, 2043, 1737,
|
||||
1397, 1123, 925, 762, 578, 438, 332, 251
|
||||
};
|
||||
|
||||
const char** DivPlatformAY8910::getRegisterSheet() {
|
||||
return intellivision?regCheatSheetAY8914:regCheatSheetAY;
|
||||
}
|
||||
|
||||
const char* DivPlatformAY8910::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x20:
|
||||
return "20xx: Set channel mode (bit 0: square; bit 1: noise; bit 2: envelope)";
|
||||
break;
|
||||
case 0x21:
|
||||
return "21xx: Set noise frequency (0 to 1F)";
|
||||
break;
|
||||
case 0x22:
|
||||
return "22xy: Set envelope mode (x: shape, y: enable for this channel)";
|
||||
break;
|
||||
case 0x23:
|
||||
return "23xx: Set envelope period low byte";
|
||||
break;
|
||||
case 0x24:
|
||||
return "24xx: Set envelope period high byte";
|
||||
break;
|
||||
case 0x25:
|
||||
return "25xx: Envelope slide up";
|
||||
break;
|
||||
case 0x26:
|
||||
return "26xx: Envelope slide down";
|
||||
break;
|
||||
case 0x29:
|
||||
return "29xy: Set auto-envelope (x: numerator; y: denominator)";
|
||||
break;
|
||||
case 0x2e:
|
||||
return "2Exx: Write to I/O port A";
|
||||
break;
|
||||
case 0x2f:
|
||||
return "2Fxx: Write to I/O port B";
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DivPlatformAY8910::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
if (ayBufLen<len) {
|
||||
ayBufLen=len;
|
||||
|
|
@ -129,27 +101,33 @@ void DivPlatformAY8910::acquire(short* bufL, short* bufR, size_t start, size_t l
|
|||
regPool[w.addr&0x0f]=w.val;
|
||||
writes.pop();
|
||||
}
|
||||
ay->sound_stream_update(ayBuf,len);
|
||||
if (sunsoft) {
|
||||
for (size_t i=0; i<len; i++) {
|
||||
bufL[i+start]=ayBuf[0][i];
|
||||
ay->sound_stream_update(ayBuf,1);
|
||||
bufL[i+start]=ayBuf[0][0];
|
||||
bufR[i+start]=bufL[i+start];
|
||||
}
|
||||
} else if (stereo) {
|
||||
for (size_t i=0; i<len; i++) {
|
||||
bufL[i+start]=ayBuf[0][i]+ayBuf[1][i];
|
||||
bufR[i+start]=ayBuf[1][i]+ayBuf[2][i];
|
||||
|
||||
oscBuf[0]->data[oscBuf[0]->needle++]=sunsoftVolTable[31-(ay->lastIndx&31)]>>3;
|
||||
oscBuf[1]->data[oscBuf[1]->needle++]=sunsoftVolTable[31-((ay->lastIndx>>5)&31)]>>3;
|
||||
oscBuf[2]->data[oscBuf[2]->needle++]=sunsoftVolTable[31-((ay->lastIndx>>10)&31)]>>3;
|
||||
}
|
||||
} else {
|
||||
for (size_t i=0; i<len; i++) {
|
||||
bufL[i+start]=ayBuf[0][i]+ayBuf[1][i]+ayBuf[2][i];
|
||||
bufR[i+start]=bufL[i+start];
|
||||
ay->sound_stream_update(ayBuf,len);
|
||||
if (stereo) {
|
||||
for (size_t i=0; i<len; i++) {
|
||||
bufL[i+start]=ayBuf[0][i]+ayBuf[1][i];
|
||||
bufR[i+start]=ayBuf[1][i]+ayBuf[2][i];
|
||||
}
|
||||
} else {
|
||||
for (size_t i=0; i<len; i++) {
|
||||
bufL[i+start]=ayBuf[0][i]+ayBuf[1][i]+ayBuf[2][i];
|
||||
bufR[i+start]=bufL[i+start];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int ch=0; ch<3; ch++) {
|
||||
for (size_t i=0; i<len; i++) {
|
||||
oscBuf[ch]->data[oscBuf[ch]->needle++]=ayBuf[ch][i];
|
||||
for (int ch=0; ch<3; ch++) {
|
||||
for (size_t i=0; i<len; i++) {
|
||||
oscBuf[ch]->data[oscBuf[ch]->needle++]=ayBuf[ch][i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -195,18 +173,9 @@ void DivPlatformAY8910::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp.val);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp.val);
|
||||
}
|
||||
chan[i].baseFreq=NOTE_PERIODIC(parent->calcArp(chan[i].note,chan[i].std.arp.val));
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.duty.had) {
|
||||
rWrite(0x06,31-chan[i].std.duty.val);
|
||||
|
|
@ -471,9 +440,12 @@ int DivPlatformAY8910::dispatch(DivCommand c) {
|
|||
return 15;
|
||||
break;
|
||||
case DIV_CMD_PRE_PORTA:
|
||||
// TODO: FIX wtr_envelope.dmf
|
||||
// the brokenPortaArp update broke it
|
||||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_AY));
|
||||
}
|
||||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note);
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_PRE_NOTE:
|
||||
|
|
@ -489,9 +461,9 @@ void DivPlatformAY8910::muteChannel(int ch, bool mute) {
|
|||
isMuted[ch]=mute;
|
||||
if (isMuted[ch]) {
|
||||
rWrite(0x08+ch,0);
|
||||
} else if (intellivision && (chan[ch].psgMode&4)) {
|
||||
} else if (intellivision && (chan[ch].psgMode&4) && chan[ch].active) {
|
||||
rWrite(0x08+ch,(chan[ch].vol&0xc)<<2);
|
||||
} else {
|
||||
} else if (chan[ch].active) {
|
||||
rWrite(0x08+ch,(chan[ch].outVol&15)|((chan[ch].psgMode&4)<<2));
|
||||
}
|
||||
}
|
||||
|
|
@ -565,8 +537,6 @@ void DivPlatformAY8910::reset() {
|
|||
|
||||
delay=0;
|
||||
|
||||
extMode=false;
|
||||
|
||||
ioPortA=false;
|
||||
ioPortB=false;
|
||||
portAVal=0;
|
||||
|
|
@ -595,50 +565,69 @@ void DivPlatformAY8910::poke(std::vector<DivRegWrite>& wlist) {
|
|||
for (DivRegWrite& i: wlist) immWrite(i.addr,i.val);
|
||||
}
|
||||
|
||||
void DivPlatformAY8910::setFlags(unsigned int flags) {
|
||||
clockSel=(flags>>7)&1;
|
||||
switch (flags&15) {
|
||||
case 1:
|
||||
chipClock=COLOR_PAL*2.0/5.0;
|
||||
break;
|
||||
case 2:
|
||||
chipClock=1750000;
|
||||
break;
|
||||
case 3:
|
||||
chipClock=2000000;
|
||||
break;
|
||||
case 4:
|
||||
chipClock=1500000;
|
||||
break;
|
||||
case 5:
|
||||
chipClock=1000000;
|
||||
break;
|
||||
case 6:
|
||||
chipClock=COLOR_NTSC/4.0;
|
||||
break;
|
||||
case 7:
|
||||
chipClock=COLOR_PAL*3.0/8.0;
|
||||
break;
|
||||
case 8:
|
||||
chipClock=COLOR_PAL*3.0/16.0;
|
||||
break;
|
||||
case 9:
|
||||
chipClock=COLOR_PAL/4.0;
|
||||
break;
|
||||
case 10:
|
||||
chipClock=2097152;
|
||||
break;
|
||||
case 11:
|
||||
chipClock=COLOR_NTSC;
|
||||
break;
|
||||
case 12:
|
||||
chipClock=3600000;
|
||||
break;
|
||||
default:
|
||||
chipClock=COLOR_NTSC/2.0;
|
||||
break;
|
||||
void DivPlatformAY8910::setExtClockDiv(unsigned int eclk, unsigned char ediv) {
|
||||
if (extMode) {
|
||||
extClock=eclk;
|
||||
extDiv=ediv;
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformAY8910::setFlags(unsigned int flags) {
|
||||
if (extMode) {
|
||||
chipClock=extClock;
|
||||
rate=chipClock/extDiv;
|
||||
} else {
|
||||
clockSel=(flags>>7)&1;
|
||||
switch (flags&15) {
|
||||
default:
|
||||
case 0:
|
||||
chipClock=COLOR_NTSC/2.0;
|
||||
break;
|
||||
case 1:
|
||||
chipClock=COLOR_PAL*2.0/5.0;
|
||||
break;
|
||||
case 2:
|
||||
chipClock=1750000;
|
||||
break;
|
||||
case 3:
|
||||
chipClock=2000000;
|
||||
break;
|
||||
case 4:
|
||||
chipClock=1500000;
|
||||
break;
|
||||
case 5:
|
||||
chipClock=1000000;
|
||||
break;
|
||||
case 6:
|
||||
chipClock=COLOR_NTSC/4.0;
|
||||
break;
|
||||
case 7:
|
||||
chipClock=COLOR_PAL*3.0/8.0;
|
||||
break;
|
||||
case 8:
|
||||
chipClock=COLOR_PAL*3.0/16.0;
|
||||
break;
|
||||
case 9:
|
||||
chipClock=COLOR_PAL/4.0;
|
||||
break;
|
||||
case 10:
|
||||
chipClock=2097152;
|
||||
break;
|
||||
case 11:
|
||||
chipClock=COLOR_NTSC;
|
||||
break;
|
||||
case 12:
|
||||
chipClock=3600000;
|
||||
break;
|
||||
case 13:
|
||||
chipClock=20000000/16;
|
||||
break;
|
||||
case 14:
|
||||
chipClock=1536000;
|
||||
break;
|
||||
}
|
||||
rate=chipClock/8;
|
||||
}
|
||||
rate=chipClock/8;
|
||||
for (int i=0; i<3; i++) {
|
||||
oscBuf[i]->rate=rate;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,6 +70,9 @@ class DivPlatformAY8910: public DivDispatch {
|
|||
int delay;
|
||||
|
||||
bool extMode;
|
||||
unsigned int extClock;
|
||||
unsigned char extDiv;
|
||||
|
||||
bool stereo, sunsoft, intellivision, clockSel;
|
||||
bool ioPortA, ioPortB;
|
||||
unsigned char portAVal, portBVal;
|
||||
|
|
@ -88,6 +91,7 @@ class DivPlatformAY8910: public DivDispatch {
|
|||
friend void putDispatchChan(void*,int,int);
|
||||
|
||||
public:
|
||||
void setExtClockDiv(unsigned int eclk=COLOR_NTSC, unsigned char ediv=8);
|
||||
void acquire(short* bufL, short* bufR, size_t start, size_t len);
|
||||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
|
|
@ -108,8 +112,12 @@ class DivPlatformAY8910: public DivDispatch {
|
|||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
DivPlatformAY8910(bool useExtMode=false, unsigned int eclk=COLOR_NTSC, unsigned char ediv=8):
|
||||
DivDispatch(),
|
||||
extMode(useExtMode),
|
||||
extClock(eclk),
|
||||
extDiv(ediv) {}
|
||||
};
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -77,54 +77,6 @@ const char** DivPlatformAY8930::getRegisterSheet() {
|
|||
return regCheatSheetAY8930;
|
||||
}
|
||||
|
||||
const char* DivPlatformAY8930::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x12:
|
||||
return "12xx: Set duty cycle (0 to 8)";
|
||||
break;
|
||||
case 0x20:
|
||||
return "20xx: Set channel mode (bit 0: square; bit 1: noise; bit 2: envelope)";
|
||||
break;
|
||||
case 0x21:
|
||||
return "21xx: Set noise frequency (0 to 1F)";
|
||||
break;
|
||||
case 0x22:
|
||||
return "22xy: Set envelope mode (x: shape, y: enable for this channel)";
|
||||
break;
|
||||
case 0x23:
|
||||
return "23xx: Set envelope period low byte";
|
||||
break;
|
||||
case 0x24:
|
||||
return "24xx: Set envelope period high byte";
|
||||
break;
|
||||
case 0x25:
|
||||
return "25xx: Envelope slide up";
|
||||
break;
|
||||
case 0x26:
|
||||
return "26xx: Envelope slide down";
|
||||
break;
|
||||
case 0x27:
|
||||
return "27xx: Set noise AND mask";
|
||||
break;
|
||||
case 0x28:
|
||||
return "28xx: Set noise OR mask";
|
||||
break;
|
||||
case 0x29:
|
||||
return "29xy: Set auto-envelope (x: numerator; y: denominator)";
|
||||
break;
|
||||
case 0x2d:
|
||||
return "2Dxx: NOT TO BE EMPLOYED BY THE COMPOSER";
|
||||
break;
|
||||
case 0x2e:
|
||||
return "2Exx: Write to I/O port A";
|
||||
break;
|
||||
case 0x2f:
|
||||
return "2Fxx: Write to I/O port B";
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DivPlatformAY8930::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
if (ayBufLen<len) {
|
||||
ayBufLen=len;
|
||||
|
|
@ -215,18 +167,9 @@ void DivPlatformAY8930::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp.val);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp.val);
|
||||
}
|
||||
chan[i].baseFreq=NOTE_PERIODIC(parent->calcArp(chan[i].note,chan[i].std.arp.val));
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.duty.had) {
|
||||
rWrite(0x06,chan[i].std.duty.val);
|
||||
|
|
@ -506,6 +449,7 @@ int DivPlatformAY8930::dispatch(DivCommand c) {
|
|||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_AY8930));
|
||||
}
|
||||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note);
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_PRE_NOTE:
|
||||
|
|
@ -521,7 +465,7 @@ void DivPlatformAY8930::muteChannel(int ch, bool mute) {
|
|||
isMuted[ch]=mute;
|
||||
if (isMuted[ch]) {
|
||||
rWrite(0x08+ch,0);
|
||||
} else {
|
||||
} else if (chan[ch].active) {
|
||||
rWrite(0x08+ch,(chan[ch].outVol&31)|((chan[ch].psgMode&4)<<3));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -103,7 +103,6 @@ class DivPlatformAY8930: public DivDispatch {
|
|||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -39,15 +39,6 @@ const char** DivPlatformBubSysWSG::getRegisterSheet() {
|
|||
return regCheatSheetBubSysWSG;
|
||||
}
|
||||
|
||||
const char* DivPlatformBubSysWSG::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x10:
|
||||
return "10xx: Change waveform";
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DivPlatformBubSysWSG::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
int chanOut=0;
|
||||
for (size_t h=start; h<start+len; h++) {
|
||||
|
|
@ -101,18 +92,9 @@ void DivPlatformBubSysWSG::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp.val);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp.val);
|
||||
}
|
||||
chan[i].baseFreq=NOTE_PERIODIC(parent->calcArp(chan[i].note,chan[i].std.arp.val));
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.wave.had) {
|
||||
if (chan[i].wave!=chan[i].std.wave.val || chan[i].ws.activeChanged()) {
|
||||
|
|
@ -250,6 +232,7 @@ int DivPlatformBubSysWSG::dispatch(DivCommand c) {
|
|||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_SCC));
|
||||
}
|
||||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note);
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_GET_VOLMAX:
|
||||
|
|
|
|||
|
|
@ -85,7 +85,6 @@ class DivPlatformBubSysWSG: public DivDispatch {
|
|||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
~DivPlatformBubSysWSG();
|
||||
|
|
|
|||
|
|
@ -19,9 +19,10 @@
|
|||
|
||||
#include "c64.h"
|
||||
#include "../engine.h"
|
||||
#include "sound/c64_fp/siddefs-fp.h"
|
||||
#include <math.h>
|
||||
|
||||
#define rWrite(a,v) if (!skipRegisterWrites) {sid.write(a,v); regPool[(a)&0x1f]=v; if (dumpWrites) {addWrite(a,v);} }
|
||||
#define rWrite(a,v) if (!skipRegisterWrites) {if (isFP) {sid_fp.write(a,v);} else {sid.write(a,v);}; regPool[(a)&0x1f]=v; if (dumpWrites) {addWrite(a,v);} }
|
||||
|
||||
#define CHIP_FREQBASE 524288
|
||||
|
||||
|
|
@ -62,61 +63,26 @@ const char** DivPlatformC64::getRegisterSheet() {
|
|||
return regCheatSheetSID;
|
||||
}
|
||||
|
||||
const char* DivPlatformC64::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x10:
|
||||
return "10xx: Set waveform (bit 0: triangle; bit 1: saw; bit 2: pulse; bit 3: noise)";
|
||||
break;
|
||||
case 0x11:
|
||||
return "11xx: Set coarse cutoff (not recommended; use 4xxx instead)";
|
||||
break;
|
||||
case 0x12:
|
||||
return "12xx: Set coarse pulse width (not recommended; use 3xxx instead)";
|
||||
break;
|
||||
case 0x13:
|
||||
return "13xx: Set resonance (0 to F)";
|
||||
break;
|
||||
case 0x14:
|
||||
return "14xx: Set filter mode (bit 0: low pass; bit 1: band pass; bit 2: high pass)";
|
||||
break;
|
||||
case 0x15:
|
||||
return "15xx: Set envelope reset time";
|
||||
break;
|
||||
case 0x1a:
|
||||
return "1Axx: Disable envelope reset for this channel (1 disables; 0 enables)";
|
||||
break;
|
||||
case 0x1b:
|
||||
return "1Bxy: Reset cutoff (x: on new note; y: now)";
|
||||
break;
|
||||
case 0x1c:
|
||||
return "1Cxy: Reset pulse width (x: on new note; y: now)";
|
||||
break;
|
||||
case 0x1e:
|
||||
return "1Exy: Change additional parameters";
|
||||
break;
|
||||
case 0x30: case 0x31: case 0x32: case 0x33:
|
||||
case 0x34: case 0x35: case 0x36: case 0x37:
|
||||
case 0x38: case 0x39: case 0x3a: case 0x3b:
|
||||
case 0x3c: case 0x3d: case 0x3e: case 0x3f:
|
||||
return "3xxx: Set pulse width (0 to FFF)";
|
||||
break;
|
||||
case 0x40: case 0x41: case 0x42: case 0x43:
|
||||
case 0x44: case 0x45: case 0x46: case 0x47:
|
||||
return "4xxx: Set cutoff (0 to 7FF)";
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DivPlatformC64::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
int dcOff=isFP?0:sid.get_dc(0);
|
||||
for (size_t i=start; i<start+len; i++) {
|
||||
sid.clock();
|
||||
bufL[i]=sid.output();
|
||||
if (++writeOscBuf>=8) {
|
||||
writeOscBuf=0;
|
||||
oscBuf[0]->data[oscBuf[0]->needle++]=sid.last_chan_out[0]>>5;
|
||||
oscBuf[1]->data[oscBuf[1]->needle++]=sid.last_chan_out[1]>>5;
|
||||
oscBuf[2]->data[oscBuf[2]->needle++]=sid.last_chan_out[2]>>5;
|
||||
if (isFP) {
|
||||
sid_fp.clock(4,&bufL[i]);
|
||||
if (++writeOscBuf>=4) {
|
||||
writeOscBuf=0;
|
||||
oscBuf[0]->data[oscBuf[0]->needle++]=(sid_fp.lastChanOut[0]-dcOff)>>5;
|
||||
oscBuf[1]->data[oscBuf[1]->needle++]=(sid_fp.lastChanOut[1]-dcOff)>>5;
|
||||
oscBuf[2]->data[oscBuf[2]->needle++]=(sid_fp.lastChanOut[2]-dcOff)>>5;
|
||||
}
|
||||
} else {
|
||||
sid.clock();
|
||||
bufL[i]=sid.output();
|
||||
if (++writeOscBuf>=16) {
|
||||
writeOscBuf=0;
|
||||
oscBuf[0]->data[oscBuf[0]->needle++]=(sid.last_chan_out[0]-dcOff)>>5;
|
||||
oscBuf[1]->data[oscBuf[1]->needle++]=(sid.last_chan_out[1]-dcOff)>>5;
|
||||
oscBuf[2]->data[oscBuf[2]->needle++]=(sid.last_chan_out[2]-dcOff)>>5;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -149,18 +115,9 @@ void DivPlatformC64::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+(signed char)chan[i].std.arp.val);
|
||||
}
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(parent->calcArp(chan[i].note,chan[i].std.arp.val));
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.duty.had) {
|
||||
DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_C64);
|
||||
|
|
@ -368,6 +325,7 @@ int DivPlatformC64::dispatch(DivCommand c) {
|
|||
chan[c.chan].keyOn=true;
|
||||
}
|
||||
}
|
||||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_FREQUENCY(chan[c.chan].note);
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_PRE_NOTE:
|
||||
|
|
@ -458,7 +416,11 @@ int DivPlatformC64::dispatch(DivCommand c) {
|
|||
|
||||
void DivPlatformC64::muteChannel(int ch, bool mute) {
|
||||
isMuted[ch]=mute;
|
||||
sid.set_is_muted(ch,mute);
|
||||
if (isFP) {
|
||||
sid_fp.mute(ch,mute);
|
||||
} else {
|
||||
sid.set_is_muted(ch,mute);
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformC64::forceIns() {
|
||||
|
|
@ -511,13 +473,25 @@ bool DivPlatformC64::getDCOffRequired() {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool DivPlatformC64::getWantPreNote() {
|
||||
return true;
|
||||
}
|
||||
|
||||
float DivPlatformC64::getPostAmp() {
|
||||
return isFP?3.0f:1.0f;
|
||||
}
|
||||
|
||||
void DivPlatformC64::reset() {
|
||||
for (int i=0; i<3; i++) {
|
||||
chan[i]=DivPlatformC64::Channel();
|
||||
chan[i].std.setEngine(parent);
|
||||
}
|
||||
|
||||
sid.reset();
|
||||
if (isFP) {
|
||||
sid_fp.reset();
|
||||
} else {
|
||||
sid.reset();
|
||||
}
|
||||
memset(regPool,0,32);
|
||||
|
||||
rWrite(0x18,0x0f);
|
||||
|
|
@ -539,12 +513,24 @@ void DivPlatformC64::poke(std::vector<DivRegWrite>& wlist) {
|
|||
|
||||
void DivPlatformC64::setChipModel(bool is6581) {
|
||||
if (is6581) {
|
||||
sid.set_chip_model(MOS6581);
|
||||
if (isFP) {
|
||||
sid_fp.setChipModel(reSIDfp::MOS6581);
|
||||
} else {
|
||||
sid.set_chip_model(MOS6581);
|
||||
}
|
||||
} else {
|
||||
sid.set_chip_model(MOS8580);
|
||||
if (isFP) {
|
||||
sid_fp.setChipModel(reSIDfp::MOS8580);
|
||||
} else {
|
||||
sid.set_chip_model(MOS8580);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformC64::setFP(bool fp) {
|
||||
isFP=fp;
|
||||
}
|
||||
|
||||
void DivPlatformC64::setFlags(unsigned int flags) {
|
||||
switch (flags&0xf) {
|
||||
case 0x0: // NTSC C64
|
||||
|
|
@ -562,6 +548,10 @@ void DivPlatformC64::setFlags(unsigned int flags) {
|
|||
for (int i=0; i<3; i++) {
|
||||
oscBuf[i]->rate=rate/16;
|
||||
}
|
||||
if (isFP) {
|
||||
rate/=4;
|
||||
sid_fp.setSamplingParameters(chipClock,reSIDfp::DECIMATE,rate,0);
|
||||
}
|
||||
}
|
||||
|
||||
int DivPlatformC64::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
#include "../dispatch.h"
|
||||
#include "../macroInt.h"
|
||||
#include "sound/c64/sid.h"
|
||||
#include "sound/c64_fp/SID.h"
|
||||
|
||||
class DivPlatformC64: public DivDispatch {
|
||||
struct Channel {
|
||||
|
|
@ -76,12 +77,17 @@ class DivPlatformC64: public DivDispatch {
|
|||
unsigned char filtControl, filtRes, vol;
|
||||
unsigned char writeOscBuf;
|
||||
int filtCut, resetTime;
|
||||
bool isFP;
|
||||
|
||||
SID sid;
|
||||
reSIDfp::SID sid_fp;
|
||||
unsigned char regPool[32];
|
||||
|
||||
friend void putDispatchChan(void*,int,int);
|
||||
|
||||
void acquire_classic(short* bufL, short* bufR, size_t start, size_t len);
|
||||
void acquire_fp(short* bufL, short* bufR, size_t start, size_t len);
|
||||
|
||||
void updateFilter();
|
||||
public:
|
||||
void acquire(short* bufL, short* bufR, size_t start, size_t len);
|
||||
|
|
@ -97,14 +103,16 @@ class DivPlatformC64: public DivDispatch {
|
|||
void setFlags(unsigned int flags);
|
||||
void notifyInsChange(int ins);
|
||||
bool getDCOffRequired();
|
||||
bool getWantPreNote();
|
||||
float getPostAmp();
|
||||
DivMacroInt* getChanMacroInt(int ch);
|
||||
void notifyInsDeletion(void* ins);
|
||||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void setChipModel(bool is6581);
|
||||
void setFP(bool fp);
|
||||
void quit();
|
||||
~DivPlatformC64();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -55,30 +55,6 @@ const char** DivPlatformFDS::getRegisterSheet() {
|
|||
return regCheatSheetFDS;
|
||||
}
|
||||
|
||||
const char* DivPlatformFDS::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x10:
|
||||
return "10xx: Change waveform";
|
||||
break;
|
||||
case 0x11:
|
||||
return "11xx: Set modulation depth";
|
||||
break;
|
||||
case 0x12:
|
||||
return "12xy: Set modulation speed high byte (x: enable; y: value)";
|
||||
break;
|
||||
case 0x13:
|
||||
return "13xx: Set modulation speed low byte";
|
||||
break;
|
||||
case 0x14:
|
||||
return "14xx: Set modulator position";
|
||||
break;
|
||||
case 0x15:
|
||||
return "15xx: Set modulator table to waveform";
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DivPlatformFDS::acquire_puNES(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
for (size_t i=start; i<start+len; i++) {
|
||||
extcl_apu_tick_FDS(fds);
|
||||
|
|
@ -145,18 +121,9 @@ void DivPlatformFDS::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+chan[i].std.arp.val);
|
||||
}
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(parent->calcArp(chan[i].note,chan[i].std.arp.val));
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
/*
|
||||
if (chan[i].std.duty.had) {
|
||||
|
|
@ -406,6 +373,7 @@ int DivPlatformFDS::dispatch(DivCommand c) {
|
|||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_FDS));
|
||||
}
|
||||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_FREQUENCY(chan[c.chan].note);
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_GET_VOLMAX:
|
||||
|
|
|
|||
|
|
@ -104,7 +104,6 @@ class DivPlatformFDS: public DivDispatch {
|
|||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
~DivPlatformFDS();
|
||||
|
|
|
|||
|
|
@ -17,13 +17,32 @@
|
|||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#define ADDR_MULT_DT 0x40
|
||||
#define ADDR_TL 0x60
|
||||
#define ADDR_RS_AR 0x80
|
||||
#define ADDR_AM_DR 0xa0
|
||||
#define ADDR_DT2_D2R 0xc0
|
||||
#define ADDR_SL_RR 0xe0
|
||||
#define ADDR_NOTE 0x28
|
||||
#define ADDR_KF 0x30
|
||||
#define ADDR_FMS_AMS 0x38
|
||||
#define ADDR_LR_FB_ALG 0x20
|
||||
#ifndef _FMSHARED_OPM_H
|
||||
#define _FMSHARED_OPM_H
|
||||
|
||||
#include "fmsharedbase.h"
|
||||
|
||||
#define NOTE_LINEAR(x) (((x)<<6)+baseFreqOff+log2(parent->song.tuning/440.0)*12.0*64.0)
|
||||
|
||||
class DivPlatformOPM: public DivPlatformFMBase {
|
||||
protected:
|
||||
const unsigned short ADDR_MULT_DT=0x40;
|
||||
const unsigned short ADDR_TL=0x60;
|
||||
const unsigned short ADDR_RS_AR=0x80;
|
||||
const unsigned short ADDR_AM_DR=0xa0;
|
||||
const unsigned short ADDR_DT2_D2R=0xc0;
|
||||
const unsigned short ADDR_SL_RR=0xe0;
|
||||
const unsigned short ADDR_NOTE=0x28;
|
||||
const unsigned short ADDR_KF=0x30;
|
||||
const unsigned short ADDR_FMS_AMS=0x38;
|
||||
const unsigned short ADDR_LR_FB_ALG=0x20;
|
||||
|
||||
const unsigned short opOffs[4]={
|
||||
0x00, 0x08, 0x10, 0x18
|
||||
};
|
||||
|
||||
DivPlatformOPM():
|
||||
DivPlatformFMBase() {}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -20,17 +20,7 @@
|
|||
#ifndef _FMSHARED_OPN_H
|
||||
#define _FMSHARED_OPN_H
|
||||
|
||||
#define ADDR_MULT_DT 0x30
|
||||
#define ADDR_TL 0x40
|
||||
#define ADDR_RS_AR 0x50
|
||||
#define ADDR_AM_DR 0x60
|
||||
#define ADDR_DT2_D2R 0x70
|
||||
#define ADDR_SL_RR 0x80
|
||||
#define ADDR_SSG 0x90
|
||||
#define ADDR_FREQ 0xa0
|
||||
#define ADDR_FREQH 0xa4
|
||||
#define ADDR_FB_ALG 0xb0
|
||||
#define ADDR_LRAF 0xb4
|
||||
#include "fmsharedbase.h"
|
||||
|
||||
#define PLEASE_HELP_ME(_targetChan) \
|
||||
int boundaryBottom=parent->calcBaseFreq(chipClock,CHIP_FREQBASE,0,false); \
|
||||
|
|
@ -93,4 +83,36 @@
|
|||
return 2; \
|
||||
}
|
||||
|
||||
#endif
|
||||
class DivPlatformOPN: public DivPlatformFMBase {
|
||||
protected:
|
||||
const unsigned short ADDR_MULT_DT=0x30;
|
||||
const unsigned short ADDR_TL=0x40;
|
||||
const unsigned short ADDR_RS_AR=0x50;
|
||||
const unsigned short ADDR_AM_DR=0x60;
|
||||
const unsigned short ADDR_DT2_D2R=0x70;
|
||||
const unsigned short ADDR_SL_RR=0x80;
|
||||
const unsigned short ADDR_SSG=0x90;
|
||||
const unsigned short ADDR_FREQ=0xa0;
|
||||
const unsigned short ADDR_FREQH=0xa4;
|
||||
const unsigned short ADDR_FB_ALG=0xb0;
|
||||
const unsigned short ADDR_LRAF=0xb4;
|
||||
|
||||
const unsigned short opOffs[4]={
|
||||
0x00, 0x04, 0x08, 0x0c
|
||||
};
|
||||
|
||||
double fmFreqBase;
|
||||
unsigned int fmDivBase;
|
||||
unsigned int ayDiv;
|
||||
bool extSys;
|
||||
|
||||
DivPlatformOPN(double f=9440540.0, unsigned int d=72, unsigned int a=32, bool isExtSys=false):
|
||||
DivPlatformFMBase(),
|
||||
fmFreqBase(f),
|
||||
fmDivBase(d),
|
||||
ayDiv(a),
|
||||
extSys(isExtSys) {}
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
|||
96
src/engine/platform/fmsharedbase.h
Normal file
96
src/engine/platform/fmsharedbase.h
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
/**
|
||||
* 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 _FMSHARED_BASE_H
|
||||
#define _FMSHARED_BASE_H
|
||||
|
||||
#include "../dispatch.h"
|
||||
#include <deque>
|
||||
|
||||
class DivPlatformFMBase: public DivDispatch {
|
||||
protected:
|
||||
const bool isOutput[8][4]={
|
||||
// 1 3 2 4
|
||||
{false,false,false,true},
|
||||
{false,false,false,true},
|
||||
{false,false,false,true},
|
||||
{false,false,false,true},
|
||||
{false,false,true ,true},
|
||||
{false,true ,true ,true},
|
||||
{false,true ,true ,true},
|
||||
{true ,true ,true ,true},
|
||||
};
|
||||
const unsigned char dtTable[8]={
|
||||
7,6,5,0,1,2,3,4
|
||||
};
|
||||
|
||||
const int orderedOps[4]={
|
||||
0,2,1,3
|
||||
};
|
||||
|
||||
struct QueuedWrite {
|
||||
unsigned short addr;
|
||||
unsigned char val;
|
||||
bool addrOrVal;
|
||||
QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {}
|
||||
};
|
||||
std::deque<QueuedWrite> writes;
|
||||
|
||||
unsigned char lastBusy;
|
||||
int delay;
|
||||
|
||||
unsigned char regPool[512];
|
||||
short oldWrites[512];
|
||||
short pendingWrites[512];
|
||||
|
||||
inline void rWrite(unsigned short a, short v) {
|
||||
if (!skipRegisterWrites) {
|
||||
pendingWrites[a]=v;
|
||||
}
|
||||
}
|
||||
inline void immWrite(unsigned short a, unsigned char v) {
|
||||
if (!skipRegisterWrites) {
|
||||
writes.push_back(QueuedWrite(a,v));
|
||||
if (dumpWrites) {
|
||||
addWrite(a,v);
|
||||
}
|
||||
}
|
||||
}
|
||||
inline void urgentWrite(unsigned short a, unsigned char v) {
|
||||
if (!skipRegisterWrites) {
|
||||
if (writes.empty()) {
|
||||
writes.push_back(QueuedWrite(a,v));
|
||||
} else if (writes.size()>16 || writes.front().addrOrVal) {
|
||||
writes.push_back(QueuedWrite(a,v));
|
||||
} else {
|
||||
writes.push_front(QueuedWrite(a,v));
|
||||
}
|
||||
if (dumpWrites) {
|
||||
addWrite(a,v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DivPlatformFMBase():
|
||||
DivDispatch(),
|
||||
lastBusy(0),
|
||||
delay(0) {}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -21,8 +21,8 @@
|
|||
#include "../engine.h"
|
||||
#include <math.h>
|
||||
|
||||
#define rWrite(a,v) if (!skipRegisterWrites) {GB_apu_write(gb,a,v); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} }
|
||||
#define immWrite(a,v) {GB_apu_write(gb,a,v); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} }
|
||||
#define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} }
|
||||
#define immWrite(a,v) {writes.emplace(a,v); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} }
|
||||
|
||||
#define CHIP_DIVIDER 16
|
||||
|
||||
|
|
@ -61,29 +61,14 @@ const char** DivPlatformGB::getRegisterSheet() {
|
|||
return regCheatSheetGB;
|
||||
}
|
||||
|
||||
const char* DivPlatformGB::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x10:
|
||||
return "10xx: Change waveform";
|
||||
break;
|
||||
case 0x11:
|
||||
return "11xx: Set noise length (0: long; 1: short)";
|
||||
break;
|
||||
case 0x12:
|
||||
return "12xx: Set duty cycle (0 to 3)";
|
||||
break;
|
||||
case 0x13:
|
||||
return "13xy: Setup sweep (x: time; y: shift)";
|
||||
break;
|
||||
case 0x14:
|
||||
return "14xx: Set sweep direction (0: up; 1: down)";
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DivPlatformGB::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
for (size_t i=start; i<start+len; i++) {
|
||||
if (!writes.empty()) {
|
||||
QueuedWrite& w=writes.front();
|
||||
GB_apu_write(gb,w.addr,w.val);
|
||||
writes.pop();
|
||||
}
|
||||
|
||||
GB_advance_cycles(gb,16);
|
||||
bufL[i]=gb->apu_output.final_sample.left;
|
||||
bufR[i]=gb->apu_output.final_sample.right;
|
||||
|
|
@ -97,10 +82,11 @@ void DivPlatformGB::acquire(short* bufL, short* bufR, size_t start, size_t len)
|
|||
void DivPlatformGB::updateWave() {
|
||||
rWrite(0x1a,0);
|
||||
for (int i=0; i<16; i++) {
|
||||
int nibble1=15-ws.output[i<<1];
|
||||
int nibble2=15-ws.output[1+(i<<1)];
|
||||
int nibble1=15-ws.output[((i<<1)+antiClickWavePos-1)&31];
|
||||
int nibble2=15-ws.output[((1+(i<<1))+antiClickWavePos-1)&31];
|
||||
rWrite(0x30+i,(nibble1<<4)|nibble2);
|
||||
}
|
||||
antiClickWavePos&=31;
|
||||
}
|
||||
|
||||
static unsigned char chanMuteMask[4]={
|
||||
|
|
@ -151,39 +137,47 @@ static unsigned char noiseTable[256]={
|
|||
};
|
||||
|
||||
void DivPlatformGB::tick(bool sysTick) {
|
||||
if (antiClickEnabled && sysTick && chan[2].freq>0) {
|
||||
antiClickPeriodCount+=((chipClock>>1)/MAX(parent->getCurHz(),1.0f));
|
||||
antiClickWavePos+=antiClickPeriodCount/chan[2].freq;
|
||||
antiClickPeriodCount%=chan[2].freq;
|
||||
}
|
||||
|
||||
for (int i=0; i<4; i++) {
|
||||
chan[i].std.next();
|
||||
if (chan[i].softEnv) {
|
||||
if (chan[i].std.vol.had) {
|
||||
chan[i].outVol=VOL_SCALE_LINEAR(chan[i].vol&15,MIN(15,chan[i].std.vol.val),15);
|
||||
if (chan[i].outVol<0) chan[i].outVol=0;
|
||||
|
||||
if (i==2) {
|
||||
rWrite(16+i*5+2,gbVolMap[chan[i].outVol]);
|
||||
chan[i].soundLen=64;
|
||||
} else {
|
||||
chan[i].envLen=0;
|
||||
chan[i].envDir=1;
|
||||
chan[i].envVol=chan[i].outVol;
|
||||
chan[i].soundLen=64;
|
||||
|
||||
if (!chan[i].keyOn) chan[i].killIt=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chan[i].std.arp.had) {
|
||||
if (i==3) { // noise
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=chan[i].std.arp.val+24;
|
||||
} else {
|
||||
chan[i].baseFreq=chan[i].note+chan[i].std.arp.val;
|
||||
}
|
||||
chan[i].baseFreq=parent->calcArp(chan[i].note,chan[i].std.arp.val,24);
|
||||
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.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp.val+24);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp.val);
|
||||
}
|
||||
}
|
||||
chan[i].baseFreq=NOTE_PERIODIC(parent->calcArp(chan[i].note,chan[i].std.arp.val,24));
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.duty.had) {
|
||||
chan[i].duty=chan[i].std.duty.val;
|
||||
DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_GB);
|
||||
if (i!=2) {
|
||||
rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(ins->gb.soundLen&63)));
|
||||
} else {
|
||||
rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(chan[i].soundLen&63)));
|
||||
} else if (!chan[i].softEnv) {
|
||||
if (parent->song.waveDutyIsVol) {
|
||||
rWrite(16+i*5+2,gbVolMap[(chan[i].std.duty.val&3)<<2]);
|
||||
}
|
||||
|
|
@ -213,6 +207,10 @@ void DivPlatformGB::tick(bool sysTick) {
|
|||
if (chan[i].std.phaseReset.had) {
|
||||
if (chan[i].std.phaseReset.val==1) {
|
||||
chan[i].keyOn=true;
|
||||
if (i==2) {
|
||||
antiClickWavePos=0;
|
||||
antiClickPeriodCount=0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (i==2) {
|
||||
|
|
@ -223,14 +221,64 @@ void DivPlatformGB::tick(bool sysTick) {
|
|||
}
|
||||
}
|
||||
}
|
||||
// run hardware sequence
|
||||
if (chan[i].active) {
|
||||
if (--chan[i].hwSeqDelay<=0) {
|
||||
chan[i].hwSeqDelay=0;
|
||||
DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_GB);
|
||||
int hwSeqCount=0;
|
||||
while (chan[i].hwSeqPos<ins->gb.hwSeqLen && hwSeqCount<4) {
|
||||
bool leave=false;
|
||||
unsigned short data=ins->gb.hwSeq[chan[i].hwSeqPos].data;
|
||||
switch (ins->gb.hwSeq[chan[i].hwSeqPos].cmd) {
|
||||
case DivInstrumentGB::DIV_GB_HWCMD_ENVELOPE:
|
||||
if (!chan[i].softEnv) {
|
||||
chan[i].envLen=data&7;
|
||||
chan[i].envDir=(data&8)?1:0;
|
||||
chan[i].envVol=(data>>4)&15;
|
||||
chan[i].soundLen=data>>8;
|
||||
chan[i].keyOn=true;
|
||||
}
|
||||
break;
|
||||
case DivInstrumentGB::DIV_GB_HWCMD_SWEEP:
|
||||
chan[i].sweep=data;
|
||||
chan[i].sweepChanged=true;
|
||||
break;
|
||||
case DivInstrumentGB::DIV_GB_HWCMD_WAIT:
|
||||
chan[i].hwSeqDelay=data+1;
|
||||
leave=true;
|
||||
break;
|
||||
case DivInstrumentGB::DIV_GB_HWCMD_WAIT_REL:
|
||||
if (!chan[i].released) {
|
||||
chan[i].hwSeqPos--;
|
||||
leave=true;
|
||||
}
|
||||
break;
|
||||
case DivInstrumentGB::DIV_GB_HWCMD_LOOP:
|
||||
chan[i].hwSeqPos=data-1;
|
||||
break;
|
||||
case DivInstrumentGB::DIV_GB_HWCMD_LOOP_REL:
|
||||
if (!chan[i].released) {
|
||||
chan[i].hwSeqPos=data-1;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
chan[i].hwSeqPos++;
|
||||
if (leave) break;
|
||||
hwSeqCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,DIV_INS_GB);
|
||||
if (i==3) { // noise
|
||||
int ntPos=chan[i].baseFreq;
|
||||
if (ntPos<0) ntPos=0;
|
||||
|
|
@ -244,10 +292,11 @@ void DivPlatformGB::tick(bool sysTick) {
|
|||
if (chan[i].keyOn) {
|
||||
if (i==2) { // wave
|
||||
rWrite(16+i*5,0x80);
|
||||
rWrite(16+i*5+2,gbVolMap[chan[i].vol]);
|
||||
rWrite(16+i*5+2,gbVolMap[chan[i].outVol]);
|
||||
} 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));
|
||||
rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(chan[i].soundLen&63)));
|
||||
rWrite(16+i*5+2,((chan[i].envVol<<4))|(chan[i].envLen&7)|((chan[i].envDir&1)<<3));
|
||||
chan[i].lastKill=chan[i].envVol;
|
||||
}
|
||||
}
|
||||
if (chan[i].keyOff) {
|
||||
|
|
@ -259,15 +308,35 @@ void DivPlatformGB::tick(bool sysTick) {
|
|||
}
|
||||
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));
|
||||
rWrite(16+i*5+4,((chan[i].keyOn||chan[i].keyOff)?0x80:0x00)|((chan[i].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));
|
||||
rWrite(16+i*5+4,(((2048-chan[i].freq)>>8)&7)|((chan[i].keyOn||chan[i].keyOff)?0x80:0x00)|((chan[i].soundLen<63)<<6));
|
||||
}
|
||||
if (chan[i].keyOn) chan[i].keyOn=false;
|
||||
if (chan[i].keyOff) chan[i].keyOff=false;
|
||||
chan[i].freqChanged=false;
|
||||
}
|
||||
if (chan[i].killIt) {
|
||||
if (i!=2) {
|
||||
//rWrite(16+i*5+2,8);
|
||||
int killDelta=chan[i].lastKill-chan[i].outVol+1;
|
||||
if (killDelta<0) killDelta+=16;
|
||||
chan[i].lastKill=chan[i].outVol;
|
||||
|
||||
if (killDelta!=1) {
|
||||
rWrite(16+i*5+2,((chan[i].envVol<<4))|8);
|
||||
for (int j=0; j<killDelta; j++) {
|
||||
rWrite(16+i*5+2,0x09);
|
||||
rWrite(16+i*5+2,0x11);
|
||||
rWrite(16+i*5+2,0x08);
|
||||
}
|
||||
}
|
||||
}
|
||||
chan[i].killIt=false;
|
||||
}
|
||||
|
||||
chan[i].soManyHacksToMakeItDefleCompatible=false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -291,6 +360,10 @@ int DivPlatformGB::dispatch(DivCommand c) {
|
|||
}
|
||||
chan[c.chan].active=true;
|
||||
chan[c.chan].keyOn=true;
|
||||
chan[c.chan].hwSeqPos=0;
|
||||
chan[c.chan].hwSeqDelay=0;
|
||||
chan[c.chan].released=false;
|
||||
chan[c.chan].softEnv=ins->gb.softEnv;
|
||||
chan[c.chan].macroInit(ins);
|
||||
if (c.chan==2) {
|
||||
if (chan[c.chan].wave<0) {
|
||||
|
|
@ -299,17 +372,35 @@ int DivPlatformGB::dispatch(DivCommand c) {
|
|||
}
|
||||
ws.init(ins,32,15,chan[c.chan].insChanged);
|
||||
}
|
||||
if ((chan[c.chan].insChanged || ins->gb.alwaysInit) && !chan[c.chan].softEnv) {
|
||||
if (!chan[c.chan].soManyHacksToMakeItDefleCompatible && c.chan!=2) {
|
||||
chan[c.chan].envVol=ins->gb.envVol;
|
||||
}
|
||||
chan[c.chan].envLen=ins->gb.envLen;
|
||||
chan[c.chan].envDir=ins->gb.envDir;
|
||||
chan[c.chan].soundLen=ins->gb.soundLen;
|
||||
if (!chan[c.chan].soManyHacksToMakeItDefleCompatible && c.chan!=2) {
|
||||
chan[c.chan].vol=chan[c.chan].envVol;
|
||||
chan[c.chan].outVol=chan[c.chan].envVol;
|
||||
}
|
||||
}
|
||||
if (c.chan==2 && chan[c.chan].softEnv) {
|
||||
chan[c.chan].soundLen=64;
|
||||
}
|
||||
chan[c.chan].insChanged=false;
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_NOTE_OFF:
|
||||
chan[c.chan].active=false;
|
||||
chan[c.chan].keyOff=true;
|
||||
chan[c.chan].hwSeqPos=0;
|
||||
chan[c.chan].hwSeqDelay=0;
|
||||
chan[c.chan].macroInit(NULL);
|
||||
break;
|
||||
case DIV_CMD_NOTE_OFF_ENV:
|
||||
case DIV_CMD_ENV_RELEASE:
|
||||
chan[c.chan].std.release();
|
||||
chan[c.chan].released=true;
|
||||
break;
|
||||
case DIV_CMD_INSTRUMENT:
|
||||
if (chan[c.chan].ins!=c.value || c.value2==1) {
|
||||
|
|
@ -317,17 +408,33 @@ int DivPlatformGB::dispatch(DivCommand c) {
|
|||
chan[c.chan].insChanged=true;
|
||||
if (c.chan!=2) {
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_GB);
|
||||
chan[c.chan].vol=ins->gb.envVol;
|
||||
if (parent->song.gbInsAffectsEnvelope) {
|
||||
rWrite(16+c.chan*5+2,((chan[c.chan].vol<<4))|(ins->gb.envLen&7)|((ins->gb.envDir&1)<<3));
|
||||
if (!ins->gb.softEnv) {
|
||||
chan[c.chan].envVol=ins->gb.envVol;
|
||||
chan[c.chan].envLen=ins->gb.envLen;
|
||||
chan[c.chan].envDir=ins->gb.envDir;
|
||||
chan[c.chan].soundLen=ins->gb.soundLen;
|
||||
chan[c.chan].vol=chan[c.chan].envVol;
|
||||
chan[c.chan].outVol=chan[c.chan].vol;
|
||||
if (parent->song.gbInsAffectsEnvelope) {
|
||||
rWrite(16+c.chan*5+2,((chan[c.chan].vol<<4))|(chan[c.chan].envLen&7)|((chan[c.chan].envDir&1)<<3));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_VOLUME:
|
||||
chan[c.chan].vol=c.value;
|
||||
chan[c.chan].outVol=c.value;
|
||||
if (c.chan==2) {
|
||||
rWrite(16+c.chan*5+2,gbVolMap[chan[c.chan].vol]);
|
||||
rWrite(16+c.chan*5+2,gbVolMap[chan[c.chan].outVol]);
|
||||
}
|
||||
if (!chan[c.chan].softEnv) {
|
||||
chan[c.chan].envVol=chan[c.chan].vol;
|
||||
chan[c.chan].soManyHacksToMakeItDefleCompatible=true;
|
||||
} else if (c.chan!=2) {
|
||||
chan[c.chan].envVol=chan[c.chan].vol;
|
||||
if (!chan[c.chan].keyOn) chan[c.chan].killIt=true;
|
||||
chan[c.chan].freqChanged=true;
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_GET_VOLUME:
|
||||
|
|
@ -393,6 +500,7 @@ int DivPlatformGB::dispatch(DivCommand c) {
|
|||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_GB));
|
||||
}
|
||||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note);
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_GB_SWEEP_DIR:
|
||||
|
|
@ -461,7 +569,7 @@ void DivPlatformGB::reset() {
|
|||
}
|
||||
memset(gb,0,sizeof(GB_gameboy_t));
|
||||
memset(regPool,0,128);
|
||||
gb->model=GB_MODEL_DMG_B;
|
||||
gb->model=model;
|
||||
GB_apu_init(gb);
|
||||
GB_set_sample_rate(gb,rate);
|
||||
// enable all channels
|
||||
|
|
@ -470,12 +578,23 @@ void DivPlatformGB::reset() {
|
|||
lastPan=0xff;
|
||||
immWrite(0x25,procMute());
|
||||
immWrite(0x24,0x77);
|
||||
|
||||
antiClickPeriodCount=0;
|
||||
antiClickWavePos=0;
|
||||
}
|
||||
|
||||
int DivPlatformGB::getPortaFloor(int ch) {
|
||||
return 24;
|
||||
}
|
||||
|
||||
bool DivPlatformGB::isStereo() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DivPlatformGB::getDCOffRequired() {
|
||||
return (model==GB_MODEL_AGB);
|
||||
}
|
||||
|
||||
void DivPlatformGB::notifyInsChange(int ins) {
|
||||
for (int i=0; i<4; i++) {
|
||||
if (chan[i].ins==ins) {
|
||||
|
|
@ -488,7 +607,7 @@ void DivPlatformGB::notifyWaveChange(int wave) {
|
|||
if (chan[2].wave==wave) {
|
||||
ws.changeWave1(wave);
|
||||
updateWave();
|
||||
if (!chan[2].keyOff) chan[2].keyOn=true;
|
||||
if (!chan[2].keyOff && chan[2].active) chan[2].keyOn=true;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -506,6 +625,24 @@ void DivPlatformGB::poke(std::vector<DivRegWrite>& wlist) {
|
|||
for (DivRegWrite& i: wlist) immWrite(i.addr,i.val);
|
||||
}
|
||||
|
||||
void DivPlatformGB::setFlags(unsigned int flags) {
|
||||
antiClickEnabled=!(flags&8);
|
||||
switch (flags&3) {
|
||||
case 0:
|
||||
model=GB_MODEL_DMG_B;
|
||||
break;
|
||||
case 1:
|
||||
model=GB_MODEL_CGB_C;
|
||||
break;
|
||||
case 2:
|
||||
model=GB_MODEL_CGB_E;
|
||||
break;
|
||||
case 3:
|
||||
model=GB_MODEL_AGB;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int DivPlatformGB::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
||||
chipClock=4194304;
|
||||
rate=chipClock/16;
|
||||
|
|
@ -517,7 +654,9 @@ int DivPlatformGB::init(DivEngine* p, int channels, int sugRate, unsigned int fl
|
|||
parent=p;
|
||||
dumpWrites=false;
|
||||
skipRegisterWrites=false;
|
||||
model=GB_MODEL_DMG_B;
|
||||
gb=new GB_gameboy_t;
|
||||
setFlags(flags);
|
||||
reset();
|
||||
return 4;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,13 +24,18 @@
|
|||
#include "../macroInt.h"
|
||||
#include "../waveSynth.h"
|
||||
#include "sound/gb/gb.h"
|
||||
#include <queue>
|
||||
|
||||
class DivPlatformGB: public DivDispatch {
|
||||
struct Channel {
|
||||
int freq, baseFreq, pitch, pitch2, note, ins;
|
||||
unsigned char duty, sweep;
|
||||
bool active, insChanged, freqChanged, sweepChanged, keyOn, keyOff, inPorta;
|
||||
signed char vol, outVol, wave;
|
||||
bool active, insChanged, freqChanged, sweepChanged, keyOn, keyOff, inPorta, released, softEnv, killIt;
|
||||
bool soManyHacksToMakeItDefleCompatible;
|
||||
signed char vol, outVol, wave, lastKill;
|
||||
unsigned char envVol, envDir, envLen, soundLen;
|
||||
unsigned short hwSeqPos;
|
||||
short hwSeqDelay;
|
||||
DivMacroInt std;
|
||||
void macroInit(DivInstrument* which) {
|
||||
std.init(which);
|
||||
|
|
@ -52,17 +57,38 @@ class DivPlatformGB: public DivDispatch {
|
|||
keyOn(false),
|
||||
keyOff(false),
|
||||
inPorta(false),
|
||||
released(false),
|
||||
softEnv(false),
|
||||
killIt(false),
|
||||
soManyHacksToMakeItDefleCompatible(false),
|
||||
vol(15),
|
||||
outVol(15),
|
||||
wave(-1) {}
|
||||
wave(-1),
|
||||
lastKill(0),
|
||||
envVol(0),
|
||||
envDir(0),
|
||||
envLen(0),
|
||||
soundLen(0),
|
||||
hwSeqPos(0),
|
||||
hwSeqDelay(0) {}
|
||||
};
|
||||
Channel chan[4];
|
||||
DivDispatchOscBuffer* oscBuf[4];
|
||||
bool isMuted[4];
|
||||
bool antiClickEnabled;
|
||||
unsigned char lastPan;
|
||||
DivWaveSynth ws;
|
||||
struct QueuedWrite {
|
||||
unsigned char addr;
|
||||
unsigned char val;
|
||||
QueuedWrite(unsigned char a, unsigned char v): addr(a), val(v) {}
|
||||
};
|
||||
std::queue<QueuedWrite> writes;
|
||||
|
||||
int antiClickPeriodCount, antiClickWavePos;
|
||||
|
||||
GB_gameboy_t* gb;
|
||||
GB_model_t model;
|
||||
unsigned char regPool[128];
|
||||
|
||||
unsigned char procMute();
|
||||
|
|
@ -80,14 +106,16 @@ class DivPlatformGB: public DivDispatch {
|
|||
void forceIns();
|
||||
void tick(bool sysTick=true);
|
||||
void muteChannel(int ch, bool mute);
|
||||
int getPortaFloor(int ch);
|
||||
bool isStereo();
|
||||
bool getDCOffRequired();
|
||||
void notifyInsChange(int ins);
|
||||
void notifyWaveChange(int wave);
|
||||
void notifyInsDeletion(void* ins);
|
||||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
void setFlags(unsigned int flags);
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
~DivPlatformGB();
|
||||
|
|
|
|||
|
|
@ -22,119 +22,11 @@
|
|||
#include <string.h>
|
||||
#include <math.h>
|
||||
|
||||
#include "genesisshared.h"
|
||||
#define CHIP_FREQBASE fmFreqBase
|
||||
#define CHIP_DIVIDER fmDivBase
|
||||
|
||||
#define IS_REALLY_MUTED(x) (isMuted[x] && (x<5 || !softPCM || (isMuted[5] && isMuted[6])))
|
||||
|
||||
static unsigned char konOffs[6]={
|
||||
0, 1, 2, 4, 5, 6
|
||||
};
|
||||
|
||||
#define CHIP_DIVIDER 72
|
||||
#define CHIP_FREQBASE 9440540
|
||||
|
||||
const char* DivPlatformGenesis::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x10:
|
||||
return "10xy: Setup LFO (x: enable; y: speed)";
|
||||
break;
|
||||
case 0x11:
|
||||
return "11xx: Set feedback (0 to 7)";
|
||||
break;
|
||||
case 0x12:
|
||||
return "12xx: Set level of operator 1 (0 highest, 7F lowest)";
|
||||
break;
|
||||
case 0x13:
|
||||
return "13xx: Set level of operator 2 (0 highest, 7F lowest)";
|
||||
break;
|
||||
case 0x14:
|
||||
return "14xx: Set level of operator 3 (0 highest, 7F lowest)";
|
||||
break;
|
||||
case 0x15:
|
||||
return "15xx: Set level of operator 4 (0 highest, 7F lowest)";
|
||||
break;
|
||||
case 0x16:
|
||||
return "16xy: Set operator multiplier (x: operator from 1 to 4; y: multiplier)";
|
||||
break;
|
||||
case 0x17:
|
||||
return "17xx: Enable channel 6 DAC";
|
||||
break;
|
||||
case 0x18:
|
||||
return "18xx: Toggle extended channel 3 mode";
|
||||
break;
|
||||
case 0x19:
|
||||
return "19xx: Set attack of all operators (0 to 1F)";
|
||||
break;
|
||||
case 0x1a:
|
||||
return "1Axx: Set attack of operator 1 (0 to 1F)";
|
||||
break;
|
||||
case 0x1b:
|
||||
return "1Bxx: Set attack of operator 2 (0 to 1F)";
|
||||
break;
|
||||
case 0x1c:
|
||||
return "1Cxx: Set attack of operator 3 (0 to 1F)";
|
||||
break;
|
||||
case 0x1d:
|
||||
return "1Dxx: Set attack of operator 4 (0 to 1F)";
|
||||
break;
|
||||
case 0x30:
|
||||
return "30xx: Toggle hard envelope reset on new notes";
|
||||
break;
|
||||
case 0x50:
|
||||
return "50xy: Set AM (x: operator from 1 to 4 (0 for all ops); y: AM)";
|
||||
break;
|
||||
case 0x51:
|
||||
return "51xy: Set sustain level (x: operator from 1 to 4 (0 for all ops); y: sustain)";
|
||||
break;
|
||||
case 0x52:
|
||||
return "52xy: Set release (x: operator from 1 to 4 (0 for all ops); y: release)";
|
||||
break;
|
||||
case 0x53:
|
||||
return "53xy: Set detune (x: operator from 1 to 4 (0 for all ops); y: detune where 3 is center)";
|
||||
break;
|
||||
case 0x54:
|
||||
return "54xy: Set envelope scale (x: operator from 1 to 4 (0 for all ops); y: scale from 0 to 3)";
|
||||
break;
|
||||
case 0x55:
|
||||
return "55xy: Set SSG envelope (x: operator from 1 to 4 (0 for all ops); y: 0-7 on, 8 off)";
|
||||
break;
|
||||
case 0x56:
|
||||
return "56xx: Set decay of all operators (0 to 1F)";
|
||||
break;
|
||||
case 0x57:
|
||||
return "57xx: Set decay of operator 1 (0 to 1F)";
|
||||
break;
|
||||
case 0x58:
|
||||
return "58xx: Set decay of operator 2 (0 to 1F)";
|
||||
break;
|
||||
case 0x59:
|
||||
return "59xx: Set decay of operator 3 (0 to 1F)";
|
||||
break;
|
||||
case 0x5a:
|
||||
return "5Axx: Set decay of operator 4 (0 to 1F)";
|
||||
break;
|
||||
case 0x5b:
|
||||
return "5Bxx: Set decay 2 of all operators (0 to 1F)";
|
||||
break;
|
||||
case 0x5c:
|
||||
return "5Cxx: Set decay 2 of operator 1 (0 to 1F)";
|
||||
break;
|
||||
case 0x5d:
|
||||
return "5Dxx: Set decay 2 of operator 2 (0 to 1F)";
|
||||
break;
|
||||
case 0x5e:
|
||||
return "5Exx: Set decay 2 of operator 3 (0 to 1F)";
|
||||
break;
|
||||
case 0x5f:
|
||||
return "5Fxx: Set decay 2 of operator 4 (0 to 1F)";
|
||||
break;
|
||||
case 0xdf:
|
||||
return "DFxx: Set sample playback direction (0: normal; 1: reverse)";
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DivPlatformGenesis::processDAC() {
|
||||
if (softPCM) {
|
||||
softPCMTimer+=chipClock/576;
|
||||
|
|
@ -159,14 +51,13 @@ void DivPlatformGenesis::processDAC() {
|
|||
if (chan[i].dacPeriod>=(chipClock/576)) {
|
||||
if (s->samples>0) {
|
||||
while (chan[i].dacPeriod>=(chipClock/576)) {
|
||||
if (++chan[i].dacPos>=s->samples) {
|
||||
if (s->loopStart>=0 && s->loopStart<(int)s->samples && !chan[i].dacDirection) {
|
||||
chan[i].dacPos=s->loopStart;
|
||||
} else {
|
||||
chan[i].dacSample=-1;
|
||||
chan[i].dacPeriod=0;
|
||||
break;
|
||||
}
|
||||
++chan[i].dacPos;
|
||||
if (!chan[i].dacDirection && (s->isLoopable() && chan[i].dacPos>=s->getEndPosition())) {
|
||||
chan[i].dacPos=s->loopStart;
|
||||
} else if (chan[i].dacPos>=s->samples) {
|
||||
chan[i].dacSample=-1;
|
||||
chan[i].dacPeriod=0;
|
||||
break;
|
||||
}
|
||||
chan[i].dacPeriod-=(chipClock/576);
|
||||
}
|
||||
|
|
@ -206,14 +97,13 @@ void DivPlatformGenesis::processDAC() {
|
|||
chan[5].dacReady=false;
|
||||
}
|
||||
}
|
||||
if (++chan[5].dacPos>=s->samples) {
|
||||
if (s->loopStart>=0 && s->loopStart<(int)s->samples && !chan[5].dacDirection) {
|
||||
chan[5].dacPos=s->loopStart;
|
||||
} else {
|
||||
chan[5].dacSample=-1;
|
||||
if (parent->song.brokenDACMode) {
|
||||
rWrite(0x2b,0);
|
||||
}
|
||||
chan[5].dacPos++;
|
||||
if (!chan[5].dacDirection && (s->isLoopable() && chan[5].dacPos>=s->getEndPosition())) {
|
||||
chan[5].dacPos=s->loopStart;
|
||||
} else if (chan[5].dacPos>=s->samples) {
|
||||
chan[5].dacSample=-1;
|
||||
if (parent->song.brokenDACMode) {
|
||||
rWrite(0x2b,0);
|
||||
}
|
||||
}
|
||||
while (chan[5].dacPeriod>=rate) chan[5].dacPeriod-=rate;
|
||||
|
|
@ -348,13 +238,17 @@ void DivPlatformGenesis::acquire(short* bufL, short* bufR, size_t start, size_t
|
|||
}
|
||||
|
||||
void DivPlatformGenesis::tick(bool sysTick) {
|
||||
for (int i=0; i<6; i++) {
|
||||
for (int i=0; i<(softPCM?7:6); i++) {
|
||||
if (i==2 && extMode) continue;
|
||||
chan[i].std.next();
|
||||
|
||||
if (chan[i].std.vol.had) {
|
||||
chan[i].outVol=VOL_SCALE_LOG(chan[i].vol,MIN(127,chan[i].std.vol.val),127);
|
||||
for (int j=0; j<4; j++) {
|
||||
int inVol=chan[i].std.vol.val;
|
||||
if (chan[i].furnaceDac && inVol>0) {
|
||||
inVol+=63;
|
||||
}
|
||||
chan[i].outVol=VOL_SCALE_LOG(chan[i].vol,MIN(127,inVol),127);
|
||||
if (i<6) for (int j=0; j<4; j++) {
|
||||
unsigned short baseAddr=chanOffs[i]|opOffs[j];
|
||||
DivInstrumentFM::Operator& op=chan[i].state.op[j];
|
||||
if (isMuted[i]) {
|
||||
|
|
@ -369,25 +263,41 @@ void DivPlatformGenesis::tick(bool sysTick) {
|
|||
}
|
||||
}
|
||||
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].std.arp.val,11);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note+(signed char)chan[i].std.arp.val,11);
|
||||
if (i>=5 && chan[i].furnaceDac) {
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
chan[i].baseFreq=parent->calcBaseFreq(1,1,parent->calcArp(chan[i].note,chan[i].std.arp.val),false);
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_FNUM_BLOCK(chan[i].note,11);
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
chan[i].baseFreq=NOTE_FNUM_BLOCK(parent->calcArp(chan[i].note,chan[i].std.arp.val),11);
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
|
||||
if (chan[i].std.panL.had) {
|
||||
chan[i].pan=chan[i].std.panL.val&3;
|
||||
rWrite(chanOffs[i]+ADDR_LRAF,(IS_REALLY_MUTED(i)?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
|
||||
if (i>=5 && chan[i].furnaceDac) {
|
||||
if (chan[i].std.panL.had) {
|
||||
chan[5].pan&=1;
|
||||
chan[5].pan|=chan[i].std.panL.val?2:0;
|
||||
}
|
||||
if (chan[i].std.panR.had) {
|
||||
chan[5].pan&=2;
|
||||
chan[5].pan|=chan[i].std.panR.val?1:0;
|
||||
}
|
||||
if (chan[i].std.panL.had || chan[i].std.panR.had) {
|
||||
rWrite(chanOffs[5]+ADDR_LRAF,(IS_REALLY_MUTED(i)?0:(chan[5].pan<<6))|(chan[5].state.fms&7)|((chan[5].state.ams&3)<<4));
|
||||
}
|
||||
} else {
|
||||
if (chan[i].std.panL.had) {
|
||||
chan[i].pan=chan[i].std.panL.val&3;
|
||||
if (i<6) {
|
||||
rWrite(chanOffs[i]+ADDR_LRAF,(IS_REALLY_MUTED(i)?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (chan[i].std.pitch.had) {
|
||||
|
|
@ -400,6 +310,8 @@ void DivPlatformGenesis::tick(bool sysTick) {
|
|||
chan[i].freqChanged=true;
|
||||
}
|
||||
|
||||
if (i>=6) continue;
|
||||
|
||||
if (chan[i].std.phaseReset.had) {
|
||||
if (chan[i].std.phaseReset.val==1 && chan[i].active) {
|
||||
chan[i].keyOn=true;
|
||||
|
|
@ -435,6 +347,10 @@ void DivPlatformGenesis::tick(bool sysTick) {
|
|||
chan[i].state.ams=chan[i].std.ams.val;
|
||||
rWrite(chanOffs[i]+ADDR_LRAF,(IS_REALLY_MUTED(i)?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
|
||||
}
|
||||
if (chan[i].std.ex4.had && chan[i].active) {
|
||||
chan[i].opMask=chan[i].std.ex4.val&15;
|
||||
chan[i].opMaskChanged=true;
|
||||
}
|
||||
for (int j=0; j<4; j++) {
|
||||
unsigned short baseAddr=chanOffs[i]|opOffs[j];
|
||||
DivInstrumentFM::Operator& op=chan[i].state.op[j];
|
||||
|
|
@ -567,8 +483,9 @@ void DivPlatformGenesis::tick(bool sysTick) {
|
|||
}
|
||||
chan[i].freqChanged=false;
|
||||
}
|
||||
if (chan[i].keyOn) {
|
||||
if (i<6) immWrite(0x28,0xf0|konOffs[i]);
|
||||
if (chan[i].keyOn || chan[i].opMaskChanged) {
|
||||
if (i<6) immWrite(0x28,(chan[i].opMask<<4)|konOffs[i]);
|
||||
chan[i].opMaskChanged=false;
|
||||
chan[i].keyOn=false;
|
||||
}
|
||||
}
|
||||
|
|
@ -640,9 +557,20 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
|
|||
chan[c.chan].dacPeriod=0;
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].baseFreq=parent->calcBaseFreq(1,1,c.value,false);
|
||||
chan[c.chan].portaPause=false;
|
||||
chan[c.chan].note=c.value;
|
||||
chan[c.chan].freqChanged=true;
|
||||
}
|
||||
chan[c.chan].furnaceDac=true;
|
||||
|
||||
chan[c.chan].macroInit(ins);
|
||||
if (!chan[c.chan].std.vol.will) {
|
||||
chan[c.chan].outVol=chan[c.chan].vol;
|
||||
}
|
||||
|
||||
// ???
|
||||
//chan[c.chan].keyOn=true;
|
||||
chan[c.chan].active=true;
|
||||
} else { // compatible mode
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].note=c.value;
|
||||
|
|
@ -668,6 +596,11 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
|
|||
|
||||
if (chan[c.chan].insChanged) {
|
||||
chan[c.chan].state=ins->fm;
|
||||
chan[c.chan].opMask=
|
||||
(chan[c.chan].state.op[0].enable?1:0)|
|
||||
(chan[c.chan].state.op[2].enable?2:0)|
|
||||
(chan[c.chan].state.op[1].enable?4:0)|
|
||||
(chan[c.chan].state.op[3].enable?8:0);
|
||||
}
|
||||
|
||||
chan[c.chan].macroInit(ins);
|
||||
|
|
@ -892,6 +825,13 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
|
|||
chan[c.chan].freqChanged=true;
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_EXTCH: {
|
||||
if (extSys) {
|
||||
extMode=c.value;
|
||||
immWrite(0x27,extMode?0x40:0);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_LFO: {
|
||||
if (c.chan>=6) break;
|
||||
lfoValue=(c.value&7)|((c.value>>4)<<3);
|
||||
|
|
@ -1247,12 +1187,13 @@ void DivPlatformGenesis::setSoftPCM(bool value) {
|
|||
}
|
||||
|
||||
void DivPlatformGenesis::setFlags(unsigned int flags) {
|
||||
switch (flags) {
|
||||
switch (flags&(~0x80000000)) {
|
||||
default:
|
||||
case 0: chipClock=COLOR_NTSC*15.0/7.0; break;
|
||||
case 1: chipClock=COLOR_PAL*12.0/7.0; break;
|
||||
case 2: chipClock=8000000.0; break;
|
||||
case 3: chipClock=COLOR_NTSC*12.0/7.0; break;
|
||||
case 4: chipClock=COLOR_NTSC*9.0/4.0; break;
|
||||
default: chipClock=COLOR_NTSC*15.0/7.0; break;
|
||||
}
|
||||
ladder=flags&0x80000000;
|
||||
OPN2_SetChipType(ladder?ym3438_mode_ym2612:0);
|
||||
|
|
|
|||
|
|
@ -19,28 +19,35 @@
|
|||
|
||||
#ifndef _GENESIS_H
|
||||
#define _GENESIS_H
|
||||
#include "../dispatch.h"
|
||||
#include <deque>
|
||||
#include "fmshared_OPN.h"
|
||||
#include "../macroInt.h"
|
||||
#include "../../../extern/Nuked-OPN2/ym3438.h"
|
||||
#include "sound/ymfm/ymfm_opn.h"
|
||||
|
||||
#include "sms.h"
|
||||
|
||||
class DivYM2612Interface: public ymfm::ymfm_interface {
|
||||
|
||||
};
|
||||
|
||||
class DivPlatformGenesis: public DivDispatch {
|
||||
class DivPlatformGenesis: public DivPlatformOPN {
|
||||
protected:
|
||||
const unsigned short chanOffs[6]={
|
||||
0x00, 0x01, 0x02, 0x100, 0x101, 0x102
|
||||
};
|
||||
|
||||
const unsigned char konOffs[6]={
|
||||
0, 1, 2, 4, 5, 6
|
||||
};
|
||||
|
||||
struct Channel {
|
||||
DivInstrumentFM state;
|
||||
DivMacroInt std;
|
||||
unsigned char freqH, freqL;
|
||||
int freq, baseFreq, pitch, pitch2, portaPauseFreq, note;
|
||||
int ins;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnaceDac, inPorta, hardReset;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnaceDac, inPorta, hardReset, opMaskChanged;
|
||||
int vol, outVol;
|
||||
unsigned char pan;
|
||||
unsigned char pan, opMask;
|
||||
|
||||
bool dacMode;
|
||||
int dacPeriod;
|
||||
|
|
@ -75,9 +82,11 @@ class DivPlatformGenesis: public DivDispatch {
|
|||
furnaceDac(false),
|
||||
inPorta(false),
|
||||
hardReset(false),
|
||||
opMaskChanged(false),
|
||||
vol(0),
|
||||
outVol(0),
|
||||
pan(3),
|
||||
opMask(15),
|
||||
dacMode(false),
|
||||
dacPeriod(0),
|
||||
dacRate(0),
|
||||
|
|
@ -92,21 +101,11 @@ class DivPlatformGenesis: public DivDispatch {
|
|||
Channel chan[10];
|
||||
DivDispatchOscBuffer* oscBuf[10];
|
||||
bool isMuted[10];
|
||||
struct QueuedWrite {
|
||||
unsigned short addr;
|
||||
unsigned char val;
|
||||
bool addrOrVal;
|
||||
QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {}
|
||||
};
|
||||
std::deque<QueuedWrite> writes;
|
||||
ym3438_t fm;
|
||||
int delay;
|
||||
unsigned char lastBusy;
|
||||
|
||||
ymfm::ym2612* fm_ymfm;
|
||||
ymfm::ym2612::output_data out_ymfm;
|
||||
DivYM2612Interface iface;
|
||||
unsigned char regPool[512];
|
||||
|
||||
unsigned char lfoValue;
|
||||
|
||||
|
|
@ -115,9 +114,6 @@ class DivPlatformGenesis: public DivDispatch {
|
|||
bool extMode, softPCM, useYMFM;
|
||||
bool ladder;
|
||||
|
||||
short oldWrites[512];
|
||||
short pendingWrites[512];
|
||||
|
||||
unsigned char dacVolTable[128];
|
||||
|
||||
friend void putDispatchChan(void*,int,int);
|
||||
|
|
@ -150,9 +146,10 @@ class DivPlatformGenesis: public DivDispatch {
|
|||
int getPortaFloor(int ch);
|
||||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char* getEffectName(unsigned char effect);
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
DivPlatformGenesis():
|
||||
DivPlatformOPN(9440540.0, 72, 32) {}
|
||||
~DivPlatformGenesis();
|
||||
};
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -21,10 +21,11 @@
|
|||
#include "../engine.h"
|
||||
#include <math.h>
|
||||
|
||||
#include "genesisshared.h"
|
||||
#define CHIP_FREQBASE fmFreqBase
|
||||
#define CHIP_DIVIDER fmDivBase
|
||||
|
||||
#define CHIP_DIVIDER 72
|
||||
#define CHIP_FREQBASE 9440540
|
||||
#define IS_REALLY_MUTED(x) (isMuted[x] && (x<5 || !softPCM || (isMuted[5] && isMuted[6])))
|
||||
#define IS_EXTCH_MUTED (isOpMuted[0] && isOpMuted[1] && isOpMuted[2] && isOpMuted[3])
|
||||
|
||||
int DivPlatformGenesisExt::dispatch(DivCommand c) {
|
||||
if (c.chan<2) {
|
||||
|
|
@ -36,6 +37,10 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) {
|
|||
}
|
||||
int ch=c.chan-2;
|
||||
int ordch=orderedOps[ch];
|
||||
if (!extMode) {
|
||||
c.chan=2;
|
||||
return DivPlatformGenesis::dispatch(c);
|
||||
}
|
||||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON: {
|
||||
DivInstrument* ins=parent->getIns(opChan[ch].ins,DIV_INS_FM);
|
||||
|
|
@ -65,10 +70,11 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) {
|
|||
rWrite(baseAddr+0x70,op.d2r&31);
|
||||
rWrite(baseAddr+0x80,(op.rr&15)|(op.sl<<4));
|
||||
rWrite(baseAddr+0x90,op.ssgEnv&15);
|
||||
opChan[ch].mask=op.enable;
|
||||
}
|
||||
if (opChan[ch].insChanged) { // TODO how does this work?
|
||||
rWrite(chanOffs[2]+0xb0,(chan[2].state.alg&7)|(chan[2].state.fb<<3));
|
||||
rWrite(chanOffs[2]+0xb4,(opChan[ch].pan<<6)|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4));
|
||||
rWrite(chanOffs[2]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch].pan<<6))|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4));
|
||||
}
|
||||
opChan[ch].insChanged=false;
|
||||
|
||||
|
|
@ -119,7 +125,7 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) {
|
|||
opChan[i].pan=opChan[ch].pan;
|
||||
}
|
||||
}
|
||||
rWrite(chanOffs[2]+0xb4,(opChan[ch].pan<<6)|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4));
|
||||
rWrite(chanOffs[2]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch].pan<<6))|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4));
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_PITCH: {
|
||||
|
|
@ -175,6 +181,11 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) {
|
|||
opChan[ch].freqChanged=true;
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_EXTCH: {
|
||||
extMode=c.value;
|
||||
immWrite(0x27,extMode?0x40:0);
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_FM_LFO: {
|
||||
lfoValue=(c.value&7)|((c.value>>4)<<3);
|
||||
rWrite(0x22,lfoValue);
|
||||
|
|
@ -388,6 +399,8 @@ void DivPlatformGenesisExt::muteChannel(int ch, bool mute) {
|
|||
rWrite(baseAddr+0x40,op.tl);
|
||||
immWrite(baseAddr+0x40,op.tl);
|
||||
}
|
||||
|
||||
rWrite(chanOffs[2]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch-2].pan<<6))|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4));
|
||||
}
|
||||
|
||||
static int opChanOffsL[4]={
|
||||
|
|
@ -403,7 +416,7 @@ void DivPlatformGenesisExt::tick(bool sysTick) {
|
|||
bool writeSomething=false;
|
||||
unsigned char writeMask=2;
|
||||
for (int i=0; i<4; i++) {
|
||||
writeMask|=opChan[i].active<<(4+i);
|
||||
writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i);
|
||||
if (opChan[i].keyOn || opChan[i].keyOff) {
|
||||
writeSomething=true;
|
||||
writeMask&=~(1<<(4+i));
|
||||
|
|
@ -411,6 +424,16 @@ void DivPlatformGenesisExt::tick(bool sysTick) {
|
|||
}
|
||||
}
|
||||
if (writeSomething) {
|
||||
if (chan[7].active) { // CSM
|
||||
writeMask^=0xf0;
|
||||
}
|
||||
/*printf(
|
||||
"Mask: %c %c %c %c\n",
|
||||
(writeMask&0x10)?'1':'-',
|
||||
(writeMask&0x20)?'2':'-',
|
||||
(writeMask&0x40)?'3':'-',
|
||||
(writeMask&0x80)?'4':'-'
|
||||
);*/
|
||||
immWrite(0x28,writeMask);
|
||||
}
|
||||
}
|
||||
|
|
@ -440,10 +463,12 @@ void DivPlatformGenesisExt::tick(bool sysTick) {
|
|||
immWrite(opChanOffsH[i],opChan[i].freq>>8);
|
||||
immWrite(opChanOffsL[i],opChan[i].freq&0xff);
|
||||
}
|
||||
writeMask|=opChan[i].active<<(4+i);
|
||||
writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i);
|
||||
if (opChan[i].keyOn) {
|
||||
writeNoteOn=true;
|
||||
writeMask|=1<<(4+i);
|
||||
if (opChan[i].mask) {
|
||||
writeMask|=1<<(4+i);
|
||||
}
|
||||
opChan[i].keyOn=false;
|
||||
}
|
||||
}
|
||||
|
|
@ -471,6 +496,13 @@ void DivPlatformGenesisExt::tick(bool sysTick) {
|
|||
if (chan[7].active) { // CSM
|
||||
writeMask^=0xf0;
|
||||
}
|
||||
/*printf(
|
||||
"Mask: %c %c %c %c\n",
|
||||
(writeMask&0x10)?'1':'-',
|
||||
(writeMask&0x20)?'2':'-',
|
||||
(writeMask&0x40)?'3':'-',
|
||||
(writeMask&0x80)?'4':'-'
|
||||
);*/
|
||||
immWrite(0x28,writeMask);
|
||||
}
|
||||
|
||||
|
|
@ -491,7 +523,7 @@ void DivPlatformGenesisExt::forceIns() {
|
|||
for (int j=0; j<4; j++) {
|
||||
unsigned short baseAddr=chanOffs[i]|opOffs[j];
|
||||
DivInstrumentFM::Operator& op=chan[i].state.op[j];
|
||||
if (i==2) { // extended channel
|
||||
if (i==2 && extMode) { // extended channel
|
||||
if (isOpMuted[j]) {
|
||||
rWrite(baseAddr+0x40,127);
|
||||
} else if (isOutput[chan[i].state.alg][j]) {
|
||||
|
|
@ -518,7 +550,11 @@ void DivPlatformGenesisExt::forceIns() {
|
|||
rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15);
|
||||
}
|
||||
rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3));
|
||||
rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
|
||||
if (i==2) {
|
||||
rWrite(chanOffs[i]+ADDR_LRAF,(IS_EXTCH_MUTED?0:(opChan[0].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
|
||||
} else {
|
||||
rWrite(chanOffs[i]+ADDR_LRAF,(IS_REALLY_MUTED(i)?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4));
|
||||
}
|
||||
if (chan[i].active) {
|
||||
chan[i].keyOn=true;
|
||||
chan[i].freqChanged=true;
|
||||
|
|
@ -535,6 +571,11 @@ void DivPlatformGenesisExt::forceIns() {
|
|||
opChan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (extMode && softPCM && chan[7].active) { // CSM
|
||||
chan[7].insChanged=true;
|
||||
chan[7].freqChanged=true;
|
||||
chan[7].keyOn=true;
|
||||
}
|
||||
}
|
||||
|
||||
void* DivPlatformGenesisExt::getChanState(int ch) {
|
||||
|
|
@ -594,6 +635,7 @@ int DivPlatformGenesisExt::init(DivEngine* parent, int channels, int sugRate, un
|
|||
for (int i=0; i<4; i++) {
|
||||
isOpMuted[i]=false;
|
||||
}
|
||||
extSys=true;
|
||||
|
||||
reset();
|
||||
return 13;
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ class DivPlatformGenesisExt: public DivPlatformGenesis {
|
|||
unsigned char freqH, freqL;
|
||||
int freq, baseFreq, pitch, pitch2, portaPauseFreq, ins;
|
||||
signed char konCycles;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, mask;
|
||||
int vol;
|
||||
unsigned char pan;
|
||||
OpChannel():
|
||||
|
|
@ -46,6 +46,7 @@ class DivPlatformGenesisExt: public DivPlatformGenesis {
|
|||
keyOff(false),
|
||||
portaPause(false),
|
||||
inPorta(false),
|
||||
mask(true),
|
||||
vol(0),
|
||||
pan(3) {}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,60 +0,0 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
static unsigned short chanOffs[6]={
|
||||
0x00, 0x01, 0x02, 0x100, 0x101, 0x102
|
||||
};
|
||||
static unsigned short opOffs[4]={
|
||||
0x00, 0x04, 0x08, 0x0c
|
||||
};
|
||||
static bool isOutput[8][4]={
|
||||
// 1 3 2 4
|
||||
{false,false,false,true},
|
||||
{false,false,false,true},
|
||||
{false,false,false,true},
|
||||
{false,false,false,true},
|
||||
{false,false,true ,true},
|
||||
{false,true ,true ,true},
|
||||
{false,true ,true ,true},
|
||||
{true ,true ,true ,true},
|
||||
};
|
||||
static unsigned char dtTable[8]={
|
||||
7,6,5,0,1,2,3,4
|
||||
};
|
||||
|
||||
static int orderedOps[4]={
|
||||
0,2,1,3
|
||||
};
|
||||
|
||||
#define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;}
|
||||
#define immWrite(a,v) if (!skipRegisterWrites) {writes.push_back(QueuedWrite(a,v)); if (dumpWrites) {addWrite(a,v);} }
|
||||
#define urgentWrite(a,v) if (!skipRegisterWrites) { \
|
||||
if (writes.empty()) { \
|
||||
writes.push_back(QueuedWrite(a,v)); \
|
||||
} else if (writes.size()>16 || writes.front().addrOrVal) { \
|
||||
writes.push_back(QueuedWrite(a,v)); \
|
||||
} else { \
|
||||
writes.push_front(QueuedWrite(a,v)); \
|
||||
} \
|
||||
if (dumpWrites) { \
|
||||
addWrite(a,v); \
|
||||
} \
|
||||
}
|
||||
|
||||
#include "fmshared_OPN.h"
|
||||
|
|
@ -129,19 +129,6 @@ const char** DivPlatformLynx::getRegisterSheet() {
|
|||
return regCheatSheetLynx;
|
||||
}
|
||||
|
||||
const char* DivPlatformLynx::getEffectName(unsigned char effect) {
|
||||
switch (effect)
|
||||
{
|
||||
case 0x30: case 0x31: case 0x32: case 0x33:
|
||||
case 0x34: case 0x35: case 0x36: case 0x37:
|
||||
case 0x38: case 0x39: case 0x3a: case 0x3b:
|
||||
case 0x3c: case 0x3d: case 0x3e: case 0x3f:
|
||||
return "3xxx: Load LFSR (0 to FFF)";
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DivPlatformLynx::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
for (size_t h=start; h<start+len; h++) {
|
||||
for (int i=0; i<4; i++) {
|
||||
|
|
@ -158,12 +145,10 @@ void DivPlatformLynx::acquire(short* bufL, short* bufR, size_t start, size_t len
|
|||
WRITE_OUTPUT(i,(s->data8[chan[i].samplePos++]*chan[i].outVol)>>7);
|
||||
}
|
||||
|
||||
if (chan[i].samplePos>=(int)s->samples) {
|
||||
if (s->loopStart>=0 && s->loopStart<(int)s->samples) {
|
||||
chan[i].samplePos=s->loopStart;
|
||||
} else {
|
||||
chan[i].sample=-1;
|
||||
}
|
||||
if (s->isLoopable() && chan[i].samplePos>=(int)s->getEndPosition()) {
|
||||
chan[i].samplePos=s->loopStart;
|
||||
} else if (chan[i].samplePos>=(int)s->samples) {
|
||||
chan[i].sample=-1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -187,22 +172,9 @@ void DivPlatformLynx::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp.val);
|
||||
if (chan[i].pcm) chan[i].sampleBaseFreq=parent->calcBaseFreq(1.0,1.0,chan[i].std.arp.val,false);
|
||||
chan[i].actualNote=chan[i].std.arp.val;
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp.val);
|
||||
if (chan[i].pcm) chan[i].sampleBaseFreq=parent->calcBaseFreq(1.0,1.0,chan[i].note+chan[i].std.arp.val,false);
|
||||
chan[i].actualNote=chan[i].note+chan[i].std.arp.val;
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note);
|
||||
if (chan[i].pcm) chan[i].sampleBaseFreq=parent->calcBaseFreq(1.0,1.0,chan[i].note,false);
|
||||
chan[i].actualNote=chan[i].note;
|
||||
chan[i].actualNote=parent->calcArp(chan[i].note,chan[i].std.arp.val);
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].actualNote);
|
||||
if (chan[i].pcm) chan[i].sampleBaseFreq=parent->calcBaseFreq(1.0,1.0,chan[i].actualNote,false);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
|
|
@ -387,6 +359,7 @@ int DivPlatformLynx::dispatch(DivCommand c) {
|
|||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_MIKEY));
|
||||
}
|
||||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note);
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_GET_VOLMAX:
|
||||
|
|
|
|||
|
|
@ -104,7 +104,6 @@ class DivPlatformLynx: public DivDispatch {
|
|||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName( unsigned char effect );
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
~DivPlatformLynx();
|
||||
|
|
|
|||
|
|
@ -43,15 +43,6 @@ const char** DivPlatformMMC5::getRegisterSheet() {
|
|||
return regCheatSheetMMC5;
|
||||
}
|
||||
|
||||
const char* DivPlatformMMC5::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x12:
|
||||
return "12xx: Set duty cycle/noise mode (pulse: 0 to 3; noise: 0 or 1)";
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DivPlatformMMC5::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
for (size_t i=start; i<start+len; i++) {
|
||||
if (dacSample!=-1) {
|
||||
|
|
@ -62,12 +53,11 @@ void DivPlatformMMC5::acquire(short* bufL, short* bufR, size_t start, size_t len
|
|||
if (!isMuted[2]) {
|
||||
rWrite(0x5011,((unsigned char)s->data8[dacPos]+0x80));
|
||||
}
|
||||
if (++dacPos>=s->samples) {
|
||||
if (s->loopStart>=0 && s->loopStart<(int)s->samples) {
|
||||
dacPos=s->loopStart;
|
||||
} else {
|
||||
dacSample=-1;
|
||||
}
|
||||
dacPos++;
|
||||
if (s->isLoopable() && dacPos>=s->getEndPosition()) {
|
||||
dacPos=s->loopStart;
|
||||
} else if (dacPos>=s->samples) {
|
||||
dacSample=-1;
|
||||
}
|
||||
dacPeriod-=rate;
|
||||
} else {
|
||||
|
|
@ -111,20 +101,12 @@ void DivPlatformMMC5::tick(bool sysTick) {
|
|||
if (chan[i].outVol<0) chan[i].outVol=0;
|
||||
rWrite(0x5000+i*4,0x30|chan[i].outVol|((chan[i].duty&3)<<6));
|
||||
}
|
||||
// TODO: arp macros on NES PCM?
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp.val);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp.val);
|
||||
}
|
||||
chan[i].baseFreq=NOTE_PERIODIC(parent->calcArp(chan[i].note,chan[i].std.arp.val));
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.duty.had) {
|
||||
chan[i].duty=chan[i].std.duty.val;
|
||||
|
|
@ -172,7 +154,7 @@ void DivPlatformMMC5::tick(bool sysTick) {
|
|||
|
||||
// PCM
|
||||
if (chan[2].freqChanged) {
|
||||
chan[2].freq=parent->calcFreq(chan[2].baseFreq,chan[2].pitch,false);
|
||||
chan[2].freq=parent->calcFreq(chan[2].baseFreq,chan[2].pitch,false,0,chan[2].pitch2,1,1);
|
||||
if (chan[2].furnaceDac) {
|
||||
double off=1.0;
|
||||
if (dacSample>=0 && dacSample<parent->song.sampleLen) {
|
||||
|
|
@ -202,7 +184,7 @@ int DivPlatformMMC5::dispatch(DivCommand c) {
|
|||
}
|
||||
dacPos=0;
|
||||
dacPeriod=0;
|
||||
chan[c.chan].baseFreq=parent->song.tuning*pow(2.0f,((float)(c.value+3)/12.0f));
|
||||
chan[c.chan].baseFreq=parent->calcBaseFreq(1,1,c.value,false);
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].freqChanged=true;
|
||||
chan[c.chan].note=c.value;
|
||||
|
|
@ -283,7 +265,7 @@ int DivPlatformMMC5::dispatch(DivCommand c) {
|
|||
chan[c.chan].freqChanged=true;
|
||||
break;
|
||||
case DIV_CMD_NOTE_PORTA: {
|
||||
int destFreq=NOTE_PERIODIC(c.value2);
|
||||
int destFreq=(c.chan==2)?(parent->calcBaseFreq(1,1,c.value2,false)):(NOTE_PERIODIC(c.value2));
|
||||
bool return2=false;
|
||||
if (destFreq>chan[c.chan].baseFreq) {
|
||||
chan[c.chan].baseFreq+=c.value;
|
||||
|
|
@ -316,7 +298,11 @@ int DivPlatformMMC5::dispatch(DivCommand c) {
|
|||
}
|
||||
break;
|
||||
case DIV_CMD_LEGATO:
|
||||
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0)));
|
||||
if (c.chan==2) {
|
||||
chan[c.chan].baseFreq=parent->calcBaseFreq(1,1,c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0)),false);
|
||||
} else {
|
||||
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0)));
|
||||
}
|
||||
chan[c.chan].freqChanged=true;
|
||||
chan[c.chan].note=c.value;
|
||||
break;
|
||||
|
|
@ -324,6 +310,7 @@ int DivPlatformMMC5::dispatch(DivCommand c) {
|
|||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_STD));
|
||||
}
|
||||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note);
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_GET_VOLMAX:
|
||||
|
|
|
|||
|
|
@ -89,7 +89,6 @@ class DivPlatformMMC5: public DivDispatch {
|
|||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
~DivPlatformMMC5();
|
||||
|
|
|
|||
|
|
@ -30,18 +30,6 @@ const char** DivPlatformMSM6258::getRegisterSheet() {
|
|||
return NULL;
|
||||
}
|
||||
|
||||
const char* DivPlatformMSM6258::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x20:
|
||||
return "20xx: Set frequency divider (0-2)";
|
||||
break;
|
||||
case 0x21:
|
||||
return "21xx: Select clock rate (0: full; 1: half)";
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DivPlatformMSM6258::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
short* outs[2]={
|
||||
&msmOut,
|
||||
|
|
@ -121,6 +109,7 @@ int DivPlatformMSM6258::dispatch(DivCommand c) {
|
|||
chan[c.chan].outVol=chan[c.chan].vol;
|
||||
}
|
||||
sample=ins->amiga.getSample(c.value);
|
||||
samplePos=0;
|
||||
if (sample>=0 && sample<parent->song.sampleLen) {
|
||||
//DivSample* s=parent->getSample(chan[c.chan].sample);
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
|
|
@ -144,8 +133,8 @@ int DivPlatformMSM6258::dispatch(DivCommand c) {
|
|||
//DivSample* s=parent->getSample(12*sampleBank+c.value%12);
|
||||
sample=12*sampleBank+c.value%12;
|
||||
samplePos=0;
|
||||
msm->ctrl_w(1);
|
||||
msm->ctrl_w(2);
|
||||
rWrite(0,1);
|
||||
rWrite(0,2);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
@ -380,7 +369,7 @@ void DivPlatformMSM6258::setFlags(unsigned int flags) {
|
|||
chipClock=4000000;
|
||||
break;
|
||||
}
|
||||
rate=chipClock/128;
|
||||
rate=chipClock/256;
|
||||
for (int i=0; i<1; i++) {
|
||||
oscBuf[i]->rate=rate;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,10 +26,6 @@
|
|||
|
||||
class DivPlatformMSM6258: public DivDispatch {
|
||||
protected:
|
||||
const unsigned short chanOffs[6]={
|
||||
0x00, 0x01, 0x02, 0x100, 0x101, 0x102
|
||||
};
|
||||
|
||||
struct Channel {
|
||||
unsigned char freqH, freqL;
|
||||
int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins;
|
||||
|
|
@ -77,12 +73,10 @@ class DivPlatformMSM6258: public DivDispatch {
|
|||
struct QueuedWrite {
|
||||
unsigned short addr;
|
||||
unsigned char val;
|
||||
bool addrOrVal;
|
||||
QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {}
|
||||
QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v) {}
|
||||
};
|
||||
std::queue<QueuedWrite> writes;
|
||||
okim6258_device* msm;
|
||||
unsigned char regPool[512];
|
||||
unsigned char lastBusy;
|
||||
|
||||
unsigned char* adpcmMem;
|
||||
|
|
@ -93,11 +87,6 @@ class DivPlatformMSM6258: public DivDispatch {
|
|||
|
||||
int delay, updateOsc, sample, samplePos;
|
||||
|
||||
bool extMode;
|
||||
|
||||
short oldWrites[512];
|
||||
short pendingWrites[512];
|
||||
|
||||
friend void putDispatchChan(void*,int,int);
|
||||
|
||||
public:
|
||||
|
|
@ -120,7 +109,6 @@ class DivPlatformMSM6258: public DivDispatch {
|
|||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
void setFlags(unsigned int flags);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
const void* getSampleMem(int index);
|
||||
size_t getSampleMemCapacity(int index);
|
||||
size_t getSampleMemUsage(int index);
|
||||
|
|
|
|||
|
|
@ -24,22 +24,17 @@
|
|||
#include <math.h>
|
||||
|
||||
#define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} }
|
||||
#define rWriteDelay(a,v,d) if (!skipRegisterWrites) {writes.emplace(a,v,d); if (dumpWrites) {addWrite(a,v);} }
|
||||
|
||||
const char** DivPlatformMSM6295::getRegisterSheet() {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const char* DivPlatformMSM6295::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x20:
|
||||
return "20xx: Set chip output rate (0: clock/132; 1: clock/165)";
|
||||
break;
|
||||
u8 DivPlatformMSM6295::read_byte(u32 address) {
|
||||
if (adpcmMem==NULL || address>=getSampleMemCapacity(0)) {
|
||||
return 0;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
u8 DivMSM6295Interface::read_byte(u32 address) {
|
||||
return adpcmMem[address&0xffff];
|
||||
return adpcmMem[address&0x3ffff];
|
||||
}
|
||||
|
||||
void DivPlatformMSM6295::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
|
|
@ -49,7 +44,7 @@ void DivPlatformMSM6295::acquire(short* bufL, short* bufR, size_t start, size_t
|
|||
QueuedWrite& w=writes.front();
|
||||
switch (w.addr) {
|
||||
case 0: // command
|
||||
msm->command_w(w.val);
|
||||
msm.command_w(w.val);
|
||||
break;
|
||||
case 8: // chip clock select (VGM)
|
||||
case 9:
|
||||
|
|
@ -57,7 +52,7 @@ void DivPlatformMSM6295::acquire(short* bufL, short* bufR, size_t start, size_t
|
|||
case 11:
|
||||
break;
|
||||
case 12: // rate select
|
||||
msm->ss_w(!w.val);
|
||||
msm.ss_w(!w.val);
|
||||
break;
|
||||
case 14: // enable bankswitch
|
||||
break;
|
||||
|
|
@ -70,21 +65,21 @@ void DivPlatformMSM6295::acquire(short* bufL, short* bufR, size_t start, size_t
|
|||
break;
|
||||
}
|
||||
writes.pop();
|
||||
delay=32;
|
||||
delay=w.delay;
|
||||
}
|
||||
} else {
|
||||
delay--;
|
||||
}
|
||||
|
||||
msm->tick();
|
||||
msm.tick();
|
||||
|
||||
bufL[h]=msm->out()<<4;
|
||||
bufL[h]=msm.out()<<4;
|
||||
|
||||
if (++updateOsc>=22) {
|
||||
updateOsc=0;
|
||||
// TODO: per-channel osc
|
||||
for (int i=0; i<4; i++) {
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=msm->m_voice[i].m_muted?0:(msm->m_voice[i].m_out<<6);
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=msm.m_voice[i].m_muted?0:(msm.m_voice[i].m_out<<6);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -118,7 +113,7 @@ int DivPlatformMSM6295::dispatch(DivCommand c) {
|
|||
}
|
||||
chan[c.chan].active=true;
|
||||
chan[c.chan].keyOn=true;
|
||||
rWrite(0,(8<<c.chan)); // turn off
|
||||
rWriteDelay(0,(8<<c.chan),60); // turn off
|
||||
rWrite(0,0x80|chan[c.chan].sample); // set phrase
|
||||
rWrite(0,(16<<c.chan)|(8-chan[c.chan].outVol)); // turn on
|
||||
} else {
|
||||
|
|
@ -133,7 +128,7 @@ int DivPlatformMSM6295::dispatch(DivCommand c) {
|
|||
}
|
||||
//DivSample* s=parent->getSample(12*sampleBank+c.value%12);
|
||||
chan[c.chan].sample=12*sampleBank+c.value%12;
|
||||
rWrite(0,(8<<c.chan)); // turn off
|
||||
rWriteDelay(0,(8<<c.chan),60); // turn off
|
||||
rWrite(0,0x80|chan[c.chan].sample); // set phrase
|
||||
rWrite(0,(16<<c.chan)|(8-chan[c.chan].outVol)); // turn on
|
||||
}
|
||||
|
|
@ -143,14 +138,14 @@ int DivPlatformMSM6295::dispatch(DivCommand c) {
|
|||
chan[c.chan].keyOff=true;
|
||||
chan[c.chan].keyOn=false;
|
||||
chan[c.chan].active=false;
|
||||
rWrite(0,(8<<c.chan)); // turn off
|
||||
rWriteDelay(0,(8<<c.chan),60); // turn off
|
||||
chan[c.chan].macroInit(NULL);
|
||||
break;
|
||||
case DIV_CMD_NOTE_OFF_ENV:
|
||||
chan[c.chan].keyOff=true;
|
||||
chan[c.chan].keyOn=false;
|
||||
chan[c.chan].active=false;
|
||||
rWrite(0,(8<<c.chan)); // turn off
|
||||
rWriteDelay(0,(8<<c.chan),60); // turn off
|
||||
chan[c.chan].std.release();
|
||||
break;
|
||||
case DIV_CMD_ENV_RELEASE:
|
||||
|
|
@ -188,7 +183,6 @@ int DivPlatformMSM6295::dispatch(DivCommand c) {
|
|||
if (sampleBank>(parent->song.sample.size()/12)) {
|
||||
sampleBank=parent->song.sample.size()/12;
|
||||
}
|
||||
iface.sampleBank=sampleBank;
|
||||
break;
|
||||
case DIV_CMD_LEGATO: {
|
||||
break;
|
||||
|
|
@ -212,7 +206,7 @@ int DivPlatformMSM6295::dispatch(DivCommand c) {
|
|||
|
||||
void DivPlatformMSM6295::muteChannel(int ch, bool mute) {
|
||||
isMuted[ch]=mute;
|
||||
msm->m_voice[ch].m_muted=mute;
|
||||
msm.m_voice[ch].m_muted=mute;
|
||||
}
|
||||
|
||||
void DivPlatformMSM6295::forceIns() {
|
||||
|
|
@ -253,8 +247,8 @@ void DivPlatformMSM6295::poke(std::vector<DivRegWrite>& wlist) {
|
|||
|
||||
void DivPlatformMSM6295::reset() {
|
||||
while (!writes.empty()) writes.pop();
|
||||
msm->reset();
|
||||
msm->ss_w(false);
|
||||
msm.reset();
|
||||
msm.ss_w(rateSelInit);
|
||||
if (dumpWrites) {
|
||||
addWrite(0xffffffff,0);
|
||||
}
|
||||
|
|
@ -268,7 +262,8 @@ void DivPlatformMSM6295::reset() {
|
|||
}
|
||||
|
||||
sampleBank=0;
|
||||
rateSel=false;
|
||||
rateSel=rateSelInit;
|
||||
rWrite(12,!rateSelInit);
|
||||
|
||||
delay=0;
|
||||
}
|
||||
|
|
@ -343,7 +338,9 @@ void DivPlatformMSM6295::renderSamples() {
|
|||
}
|
||||
|
||||
void DivPlatformMSM6295::setFlags(unsigned int flags) {
|
||||
switch (flags) {
|
||||
rateSelInit=(flags>>7)&1;
|
||||
switch (flags&0x7f) {
|
||||
default:
|
||||
case 0:
|
||||
chipClock=4000000/4;
|
||||
break;
|
||||
|
|
@ -383,22 +380,27 @@ void DivPlatformMSM6295::setFlags(unsigned int flags) {
|
|||
case 12:
|
||||
chipClock=1500000;
|
||||
break;
|
||||
default:
|
||||
chipClock=4000000/4;
|
||||
case 13:
|
||||
chipClock=3000000;
|
||||
break;
|
||||
case 14:
|
||||
chipClock=COLOR_NTSC/3.0;
|
||||
break;
|
||||
}
|
||||
rate=chipClock/3;
|
||||
for (int i=0; i<4; i++) {
|
||||
oscBuf[i]->rate=rate/22;
|
||||
}
|
||||
if (rateSel!=rateSelInit) {
|
||||
rWrite(12,!rateSelInit);
|
||||
rateSel=rateSelInit;
|
||||
}
|
||||
}
|
||||
|
||||
int DivPlatformMSM6295::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
||||
parent=p;
|
||||
adpcmMem=new unsigned char[getSampleMemCapacity(0)];
|
||||
adpcmMemLen=0;
|
||||
iface.adpcmMem=adpcmMem;
|
||||
iface.sampleBank=0;
|
||||
dumpWrites=false;
|
||||
skipRegisterWrites=false;
|
||||
updateOsc=0;
|
||||
|
|
@ -406,7 +408,6 @@ int DivPlatformMSM6295::init(DivEngine* p, int channels, int sugRate, unsigned i
|
|||
isMuted[i]=false;
|
||||
oscBuf[i]=new DivDispatchOscBuffer;
|
||||
}
|
||||
msm=new msm6295_core(iface);
|
||||
setFlags(flags);
|
||||
reset();
|
||||
return 4;
|
||||
|
|
@ -416,7 +417,6 @@ void DivPlatformMSM6295::quit() {
|
|||
for (int i=0; i<4; i++) {
|
||||
delete oscBuf[i];
|
||||
}
|
||||
delete msm;
|
||||
delete[] adpcmMem;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,60 +24,31 @@
|
|||
#include <queue>
|
||||
#include "sound/oki/msm6295.hpp"
|
||||
|
||||
class DivMSM6295Interface: public vgsound_emu_mem_intf {
|
||||
public:
|
||||
unsigned char* adpcmMem;
|
||||
int sampleBank;
|
||||
u8 read_byte(u32 address);
|
||||
DivMSM6295Interface(): adpcmMem(NULL), sampleBank(0) {}
|
||||
};
|
||||
|
||||
class DivPlatformMSM6295: public DivDispatch {
|
||||
class DivPlatformMSM6295: public DivDispatch, public vgsound_emu_mem_intf {
|
||||
protected:
|
||||
const unsigned short chanOffs[6]={
|
||||
0x00, 0x01, 0x02, 0x100, 0x101, 0x102
|
||||
};
|
||||
|
||||
struct Channel {
|
||||
unsigned char freqH, freqL;
|
||||
int freq, baseFreq, pitch, pitch2, portaPauseFreq, note, ins;
|
||||
unsigned char psgMode, autoEnvNum, autoEnvDen;
|
||||
int note, ins;
|
||||
signed char konCycles;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, furnacePCM, hardReset;
|
||||
int vol, outVol;
|
||||
int sample;
|
||||
unsigned char pan;
|
||||
DivMacroInt std;
|
||||
void macroInit(DivInstrument* which) {
|
||||
std.init(which);
|
||||
pitch2=0;
|
||||
}
|
||||
Channel():
|
||||
freqH(0),
|
||||
freqL(0),
|
||||
freq(0),
|
||||
baseFreq(0),
|
||||
pitch(0),
|
||||
pitch2(0),
|
||||
portaPauseFreq(0),
|
||||
note(0),
|
||||
ins(-1),
|
||||
psgMode(1),
|
||||
autoEnvNum(0),
|
||||
autoEnvDen(0),
|
||||
active(false),
|
||||
insChanged(true),
|
||||
freqChanged(false),
|
||||
keyOn(false),
|
||||
keyOff(false),
|
||||
portaPause(false),
|
||||
inPorta(false),
|
||||
furnacePCM(false),
|
||||
hardReset(false),
|
||||
vol(0),
|
||||
outVol(15),
|
||||
sample(-1),
|
||||
pan(3) {}
|
||||
sample(-1) {}
|
||||
};
|
||||
Channel chan[4];
|
||||
DivDispatchOscBuffer* oscBuf[4];
|
||||
|
|
@ -85,57 +56,58 @@ class DivPlatformMSM6295: public DivDispatch {
|
|||
struct QueuedWrite {
|
||||
unsigned short addr;
|
||||
unsigned char val;
|
||||
bool addrOrVal;
|
||||
QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {}
|
||||
unsigned short delay;
|
||||
QueuedWrite(unsigned short a, unsigned char v, unsigned short d=32):
|
||||
addr(a),
|
||||
val(v),
|
||||
delay(d) {}
|
||||
};
|
||||
std::queue<QueuedWrite> writes;
|
||||
msm6295_core* msm;
|
||||
unsigned char regPool[512];
|
||||
msm6295_core msm;
|
||||
unsigned char lastBusy;
|
||||
|
||||
unsigned char* adpcmMem;
|
||||
size_t adpcmMemLen;
|
||||
DivMSM6295Interface iface;
|
||||
unsigned char sampleBank;
|
||||
|
||||
int delay, updateOsc;
|
||||
|
||||
bool extMode;
|
||||
bool rateSel;
|
||||
bool rateSel=false, rateSelInit=false;
|
||||
|
||||
short oldWrites[512];
|
||||
short pendingWrites[512];
|
||||
|
||||
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);
|
||||
DivMacroInt* getChanMacroInt(int ch);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
void reset();
|
||||
void forceIns();
|
||||
void tick(bool sysTick=true);
|
||||
void muteChannel(int ch, bool mute);
|
||||
bool keyOffAffectsArp(int ch);
|
||||
float getPostAmp();
|
||||
void notifyInsChange(int ins);
|
||||
void notifyInsDeletion(void* ins);
|
||||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
void setFlags(unsigned int flags);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
const void* getSampleMem(int index);
|
||||
size_t getSampleMemCapacity(int index);
|
||||
size_t getSampleMemUsage(int index);
|
||||
void renderSamples();
|
||||
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
virtual u8 read_byte(u32 address) override;
|
||||
virtual void acquire(short* bufL, short* bufR, size_t start, size_t len) override;
|
||||
virtual int dispatch(DivCommand c) override;
|
||||
virtual void* getChanState(int chan) override;
|
||||
virtual DivMacroInt* getChanMacroInt(int ch) override;
|
||||
virtual DivDispatchOscBuffer* getOscBuffer(int chan) override;
|
||||
virtual unsigned char* getRegisterPool() override;
|
||||
virtual int getRegisterPoolSize() override;
|
||||
virtual void reset() override;
|
||||
virtual void forceIns() override;
|
||||
virtual void tick(bool sysTick=true) override;
|
||||
virtual void muteChannel(int ch, bool mute) override;
|
||||
virtual bool keyOffAffectsArp(int ch) override;
|
||||
virtual float getPostAmp() override;
|
||||
virtual void notifyInsChange(int ins) override;
|
||||
virtual void notifyInsDeletion(void* ins) override;
|
||||
virtual void poke(unsigned int addr, unsigned short val) override;
|
||||
virtual void poke(std::vector<DivRegWrite>& wlist) override;
|
||||
virtual void setFlags(unsigned int flags) override;
|
||||
virtual const char** getRegisterSheet() override;
|
||||
virtual const void* getSampleMem(int index) override;
|
||||
virtual size_t getSampleMemCapacity(int index) override;
|
||||
virtual size_t getSampleMemUsage(int index) override;
|
||||
virtual void renderSamples() override;
|
||||
|
||||
virtual int init(DivEngine* parent, int channels, int sugRate, unsigned int flags) override;
|
||||
virtual void quit() override;
|
||||
DivPlatformMSM6295():
|
||||
DivDispatch(),
|
||||
vgsound_emu_mem_intf(),
|
||||
msm(*this) {}
|
||||
~DivPlatformMSM6295();
|
||||
};
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -108,51 +108,6 @@ const char** DivPlatformN163::getRegisterSheet() {
|
|||
return regCheatSheetN163;
|
||||
}
|
||||
|
||||
const char* DivPlatformN163::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x10:
|
||||
return "10xx: Select waveform";
|
||||
break;
|
||||
case 0x11:
|
||||
return "11xx: Set waveform position in RAM (single nibble unit)";
|
||||
break;
|
||||
case 0x12:
|
||||
return "12xx: Set waveform length in RAM (04 to FC, 4 nibble unit)";
|
||||
break;
|
||||
case 0x13:
|
||||
return "130x: Change waveform update mode (0: off, bit 0: update now, bit 1: update when every waveform changes)";
|
||||
break;
|
||||
case 0x14:
|
||||
return "14xx: Select waveform for load to RAM";
|
||||
break;
|
||||
case 0x15:
|
||||
return "15xx: Set waveform position for load to RAM (single nibble unit)";
|
||||
break;
|
||||
case 0x16:
|
||||
return "16xx: Set waveform length for load to RAM (04 to FC, 4 nibble unit)";
|
||||
break;
|
||||
case 0x17:
|
||||
return "170x: Change waveform load mode (0: off, bit 0: load now, bit 1: load when every waveform changes)";
|
||||
break;
|
||||
case 0x18:
|
||||
return "180x: Change channel limits (0 to 7, x + 1)";
|
||||
break;
|
||||
case 0x20:
|
||||
return "20xx: (Global) Select waveform for load to RAM";
|
||||
break;
|
||||
case 0x21:
|
||||
return "21xx: (Global) Set waveform position for load to RAM (single nibble unit)";
|
||||
break;
|
||||
case 0x22:
|
||||
return "22xx: (Global) Set waveform length for load to RAM (04 to FC, 4 nibble unit)";
|
||||
break;
|
||||
case 0x23:
|
||||
return "230x: (Global) Change waveform load mode (0: off, bit 0: load now, bit 1: load when every waveform changes)";
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DivPlatformN163::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
for (size_t i=start; i<start+len; i++) {
|
||||
n163.tick();
|
||||
|
|
@ -234,18 +189,9 @@ void DivPlatformN163::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+(signed char)chan[i].std.arp.val);
|
||||
}
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(parent->calcArp(chan[i].note,chan[i].std.arp.val));
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.duty.had) {
|
||||
if (chan[i].wavePos!=chan[i].std.duty.val) {
|
||||
|
|
@ -562,6 +508,7 @@ int DivPlatformN163::dispatch(DivCommand c) {
|
|||
chan[c.chan].keyOn=true;
|
||||
}
|
||||
}
|
||||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_FREQUENCY(chan[c.chan].note);
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_GET_VOLMAX:
|
||||
|
|
@ -695,6 +642,9 @@ void DivPlatformN163::setFlags(unsigned int flags) {
|
|||
for (int i=0; i<8; i++) {
|
||||
oscBuf[i]->rate=rate/(initChanMax+1);
|
||||
}
|
||||
|
||||
// needed to make sure changing channel count won't trigger glitches
|
||||
reset();
|
||||
}
|
||||
|
||||
int DivPlatformN163::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
||||
|
|
|
|||
|
|
@ -110,7 +110,6 @@ class DivPlatformN163: public DivDispatch {
|
|||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
~DivPlatformN163();
|
||||
|
|
|
|||
|
|
@ -151,22 +151,7 @@ const char** DivPlatformNamcoWSG::getRegisterSheet() {
|
|||
return regCheatSheetNamcoWSG;
|
||||
}
|
||||
|
||||
const char* DivPlatformNamcoWSG::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x10:
|
||||
return "10xx: Change waveform";
|
||||
break;
|
||||
case 0x11:
|
||||
return "11xx: Toggle noise mode";
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DivPlatformNamcoWSG::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
short* buf[2]={
|
||||
bufL+start, bufR+start
|
||||
};
|
||||
while (!writes.empty()) {
|
||||
QueuedWrite w=writes.front();
|
||||
switch (devType) {
|
||||
|
|
@ -186,7 +171,15 @@ void DivPlatformNamcoWSG::acquire(short* bufL, short* bufR, size_t start, size_t
|
|||
regPool[w.addr&0x3f]=w.val;
|
||||
writes.pop();
|
||||
}
|
||||
namco->sound_stream_update(buf,len);
|
||||
for (size_t h=start; h<start+len; h++) {
|
||||
short* buf[2]={
|
||||
bufL+h, bufR+h
|
||||
};
|
||||
namco->sound_stream_update(buf,1);
|
||||
for (int i=0; i<chans; i++) {
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=namco->m_channel_list[i].last_out*chans;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformNamcoWSG::updateWave(int ch) {
|
||||
|
|
@ -213,18 +206,9 @@ void DivPlatformNamcoWSG::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+chan[i].std.arp.val);
|
||||
}
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(parent->calcArp(chan[i].note,chan[i].std.arp.val));
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.wave.had) {
|
||||
if (chan[i].wave!=chan[i].std.wave.val || chan[i].ws.activeChanged()) {
|
||||
|
|
@ -317,7 +301,7 @@ void DivPlatformNamcoWSG::tick(bool sysTick) {
|
|||
}
|
||||
rWrite((i<<3)+0x04,chan[i].freq&0xff);
|
||||
rWrite((i<<3)+0x05,(chan[i].freq>>8)&0xff);
|
||||
rWrite((i<<3)+0x06,((chan[i].freq>>15)&15)|(i<<4));
|
||||
rWrite((i<<3)+0x06,((chan[i].freq>>16)&15)|(i<<4));
|
||||
}
|
||||
break;
|
||||
case 30:
|
||||
|
|
@ -331,7 +315,7 @@ void DivPlatformNamcoWSG::tick(bool sysTick) {
|
|||
}
|
||||
rWrite((i<<3)+0x103,chan[i].freq&0xff);
|
||||
rWrite((i<<3)+0x102,(chan[i].freq>>8)&0xff);
|
||||
rWrite((i<<3)+0x101,((chan[i].freq>>15)&15)|(i<<4));
|
||||
rWrite((i<<3)+0x101,((chan[i].freq>>16)&15)|(i<<4));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
@ -437,6 +421,7 @@ int DivPlatformNamcoWSG::dispatch(DivCommand c) {
|
|||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_PCE));
|
||||
}
|
||||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_FREQUENCY(chan[c.chan].note);
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_GET_VOLMAX:
|
||||
|
|
|
|||
|
|
@ -96,7 +96,6 @@ class DivPlatformNamcoWSG: public DivDispatch {
|
|||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
~DivPlatformNamcoWSG();
|
||||
|
|
|
|||
|
|
@ -62,27 +62,6 @@ const char** DivPlatformNES::getRegisterSheet() {
|
|||
return regCheatSheetNES;
|
||||
}
|
||||
|
||||
const char* DivPlatformNES::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x11:
|
||||
return "Write to delta modulation counter (0 to 7F)";
|
||||
break;
|
||||
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;
|
||||
case 0x18:
|
||||
return "18xx: Select PCM/DPCM mode (0: PCM; 1: DPCM)";
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DivPlatformNES::doWrite(unsigned short addr, unsigned char data) {
|
||||
if (useNP) {
|
||||
nes1_NP->Write(addr,data);
|
||||
|
|
@ -108,12 +87,11 @@ void DivPlatformNES::doWrite(unsigned short addr, unsigned char data) {
|
|||
rWrite(0x4011,next); \
|
||||
} \
|
||||
} \
|
||||
if (++dacPos>=s->samples) { \
|
||||
if (s->loopStart>=0 && s->loopStart<(int)s->samples) { \
|
||||
dacPos=s->loopStart; \
|
||||
} else { \
|
||||
dacSample=-1; \
|
||||
} \
|
||||
dacPos++; \
|
||||
if (s->isLoopable() && dacPos>=s->getEndPosition()) { \
|
||||
dacPos=s->loopStart; \
|
||||
} else if (dacPos>=s->samples) { \
|
||||
dacSample=-1; \
|
||||
} \
|
||||
dacPeriod-=rate; \
|
||||
} else { \
|
||||
|
|
@ -137,11 +115,11 @@ void DivPlatformNES::acquire_puNES(short* bufL, short* bufR, size_t start, size_
|
|||
bufL[i]=sample;
|
||||
if (++writeOscBuf>=32) {
|
||||
writeOscBuf=0;
|
||||
oscBuf[0]->data[oscBuf[0]->needle++]=nes->S1.output<<11;
|
||||
oscBuf[1]->data[oscBuf[1]->needle++]=nes->S2.output<<11;
|
||||
oscBuf[2]->data[oscBuf[2]->needle++]=nes->TR.output<<11;
|
||||
oscBuf[3]->data[oscBuf[3]->needle++]=nes->NS.output<<11;
|
||||
oscBuf[4]->data[oscBuf[4]->needle++]=nes->DMC.output<<8;
|
||||
oscBuf[0]->data[oscBuf[0]->needle++]=isMuted[0]?0:(nes->S1.output<<11);
|
||||
oscBuf[1]->data[oscBuf[1]->needle++]=isMuted[1]?0:(nes->S2.output<<11);
|
||||
oscBuf[2]->data[oscBuf[2]->needle++]=isMuted[2]?0:(nes->TR.output<<11);
|
||||
oscBuf[3]->data[oscBuf[3]->needle++]=isMuted[3]?0:(nes->NS.output<<11);
|
||||
oscBuf[4]->data[oscBuf[4]->needle++]=isMuted[4]?0:(nes->DMC.output<<8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -241,28 +219,15 @@ void DivPlatformNES::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[i].std.arp.had) {
|
||||
if (i==3) { // noise
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=chan[i].std.arp.val;
|
||||
} else {
|
||||
chan[i].baseFreq=chan[i].note+chan[i].std.arp.val;
|
||||
}
|
||||
chan[i].baseFreq=parent->calcArp(chan[i].note,chan[i].std.arp.val);
|
||||
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.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp.val);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp.val);
|
||||
}
|
||||
chan[i].baseFreq=NOTE_PERIODIC(parent->calcArp(chan[i].note,chan[i].std.arp.val));
|
||||
}
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.duty.had) {
|
||||
chan[i].duty=chan[i].std.duty.val;
|
||||
|
|
@ -573,6 +538,7 @@ int DivPlatformNES::dispatch(DivCommand c) {
|
|||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_STD));
|
||||
}
|
||||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note);
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_GET_VOLMAX:
|
||||
|
|
|
|||
|
|
@ -106,7 +106,6 @@ class DivPlatformNES: public DivDispatch {
|
|||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
const void* getSampleMem(int index);
|
||||
size_t getSampleMemCapacity(int index);
|
||||
size_t getSampleMemUsage(int index);
|
||||
|
|
|
|||
|
|
@ -152,98 +152,6 @@ const int orderedOpsL[4]={
|
|||
#define ADDR_FREQH 0xb0
|
||||
#define ADDR_LR_FB_ALG 0xc0
|
||||
|
||||
const char* DivPlatformOPL::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x10:
|
||||
return "10xx: Set global AM depth (0: 1dB, 1: 4.8dB)";
|
||||
break;
|
||||
case 0x11:
|
||||
return "11xx: Set feedback (0 to 7)";
|
||||
break;
|
||||
case 0x12:
|
||||
return "12xx: Set level of operator 1 (0 highest, 3F lowest)";
|
||||
break;
|
||||
case 0x13:
|
||||
return "13xx: Set level of operator 2 (0 highest, 3F lowest)";
|
||||
break;
|
||||
case 0x14:
|
||||
return "14xx: Set level of operator 3 (0 highest, 3F lowest; 4-op only)";
|
||||
break;
|
||||
case 0x15:
|
||||
return "15xx: Set level of operator 4 (0 highest, 3F lowest; 4-op only)";
|
||||
break;
|
||||
case 0x16:
|
||||
return "16xy: Set operator multiplier (x: operator from 1 to 4; y: multiplier)";
|
||||
break;
|
||||
case 0x17:
|
||||
return "17xx: Set global vibrato depth (0: normal, 1: double)";
|
||||
break;
|
||||
case 0x18:
|
||||
if (properDrumsSys) {
|
||||
return "18xx: Toggle drums mode (1: enabled; 0: disabled)";
|
||||
}
|
||||
break;
|
||||
case 0x19:
|
||||
return "19xx: Set attack of all operators (0 to F)";
|
||||
break;
|
||||
case 0x1a:
|
||||
return "1Axx: Set attack of operator 1 (0 to F)";
|
||||
break;
|
||||
case 0x1b:
|
||||
return "1Bxx: Set attack of operator 2 (0 to F)";
|
||||
break;
|
||||
case 0x1c:
|
||||
return "1Cxx: Set attack of operator 3 (0 to F; 4-op only)";
|
||||
break;
|
||||
case 0x1d:
|
||||
return "1Dxx: Set attack of operator 4 (0 to F; 4-op only)";
|
||||
break;
|
||||
case 0x2a:
|
||||
return "2Axy: Set waveform (x: operator from 1 to 4 (0 for all ops); y: waveform from 0 to 3 in OPL2 and 0 to 7 in OPL3)";
|
||||
break;
|
||||
case 0x30:
|
||||
return "30xx: Toggle hard envelope reset on new notes";
|
||||
break;
|
||||
case 0x50:
|
||||
return "50xy: Set AM (x: operator from 1 to 4 (0 for all ops); y: AM)";
|
||||
break;
|
||||
case 0x51:
|
||||
return "51xy: Set sustain level (x: operator from 1 to 4 (0 for all ops); y: sustain)";
|
||||
break;
|
||||
case 0x52:
|
||||
return "52xy: Set release (x: operator from 1 to 4 (0 for all ops); y: release)";
|
||||
break;
|
||||
case 0x53:
|
||||
return "53xy: Set vibrato (x: operator from 1 to 4 (0 for all ops); y: enabled)";
|
||||
break;
|
||||
case 0x54:
|
||||
return "54xy: Set key scale level (x: operator from 1 to 4 (0 for all ops); y: level from 0 to 3)";
|
||||
break;
|
||||
case 0x55:
|
||||
return "55xy: Set envelope sustain (x: operator from 1 to 4 (0 for all ops); y: enabled)";
|
||||
break;
|
||||
case 0x56:
|
||||
return "56xx: Set decay of all operators (0 to F)";
|
||||
break;
|
||||
case 0x57:
|
||||
return "57xx: Set decay of operator 1 (0 to F)";
|
||||
break;
|
||||
case 0x58:
|
||||
return "58xx: Set decay of operator 2 (0 to F)";
|
||||
break;
|
||||
case 0x59:
|
||||
return "59xx: Set decay of operator 3 (0 to F; 4-op only)";
|
||||
break;
|
||||
case 0x5a:
|
||||
return "5Axx: Set decay of operator 4 (0 to F; 4-op only)";
|
||||
break;
|
||||
case 0x5b:
|
||||
return "5Bxy: Set whether key will scale envelope (x: operator from 1 to 4 (0 for all ops); y: enabled)";
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DivPlatformOPL::acquire_nuked(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
static short o[2];
|
||||
static int os[2];
|
||||
|
|
@ -277,8 +185,13 @@ void DivPlatformOPL::acquire_nuked(short* bufL, short* bufR, size_t start, size_
|
|||
regPool[w.addr&511]=w.val;
|
||||
writes.pop();
|
||||
}
|
||||
|
||||
OPL3_Generate(&fm,o); os[0]+=o[0]; os[1]+=o[1];
|
||||
|
||||
if (downsample) {
|
||||
OPL3_GenerateResampled(&fm,o);
|
||||
} else {
|
||||
OPL3_Generate(&fm,o);
|
||||
}
|
||||
os[0]+=o[0]; os[1]+=o[1];
|
||||
|
||||
if (adpcmChan>=0) {
|
||||
adpcmB->clock();
|
||||
|
|
@ -288,24 +201,45 @@ void DivPlatformOPL::acquire_nuked(short* bufL, short* bufR, size_t start, size_
|
|||
if (!isMuted[adpcmChan]) {
|
||||
os[0]-=aOut.data[0]>>3;
|
||||
os[1]-=aOut.data[0]>>3;
|
||||
oscBuf[adpcmChan]->data[oscBuf[adpcmChan]->needle++]+=aOut.data[0];
|
||||
oscBuf[adpcmChan]->data[oscBuf[adpcmChan]->needle++]=aOut.data[0];
|
||||
} else {
|
||||
oscBuf[adpcmChan]->data[oscBuf[adpcmChan]->needle++]=0;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i=0; i<chans; i++) {
|
||||
unsigned char ch=outChanMap[i];
|
||||
if (ch==255) continue;
|
||||
oscBuf[i]->data[oscBuf[i]->needle]=0;
|
||||
if (fm.channel[i].out[0]!=NULL) {
|
||||
oscBuf[i]->data[oscBuf[i]->needle]+=*fm.channel[ch].out[0];
|
||||
if (fm.rhy&0x20) {
|
||||
for (int i=0; i<melodicChans+1; i++) {
|
||||
unsigned char ch=outChanMap[i];
|
||||
if (ch==255) continue;
|
||||
oscBuf[i]->data[oscBuf[i]->needle]=0;
|
||||
if (fm.channel[i].out[0]!=NULL) {
|
||||
oscBuf[i]->data[oscBuf[i]->needle]+=*fm.channel[ch].out[0];
|
||||
}
|
||||
if (fm.channel[i].out[1]!=NULL) {
|
||||
oscBuf[i]->data[oscBuf[i]->needle]+=*fm.channel[ch].out[1];
|
||||
}
|
||||
oscBuf[i]->data[oscBuf[i]->needle]<<=1;
|
||||
oscBuf[i]->needle++;
|
||||
}
|
||||
if (fm.channel[i].out[1]!=NULL) {
|
||||
oscBuf[i]->data[oscBuf[i]->needle]+=*fm.channel[ch].out[1];
|
||||
// special
|
||||
oscBuf[melodicChans+1]->data[oscBuf[melodicChans+1]->needle++]=fm.slot[16].out*6;
|
||||
oscBuf[melodicChans+2]->data[oscBuf[melodicChans+2]->needle++]=fm.slot[14].out*6;
|
||||
oscBuf[melodicChans+3]->data[oscBuf[melodicChans+3]->needle++]=fm.slot[17].out*6;
|
||||
oscBuf[melodicChans+4]->data[oscBuf[melodicChans+4]->needle++]=fm.slot[13].out*6;
|
||||
} else {
|
||||
for (int i=0; i<chans; i++) {
|
||||
unsigned char ch=outChanMap[i];
|
||||
if (ch==255) continue;
|
||||
oscBuf[i]->data[oscBuf[i]->needle]=0;
|
||||
if (fm.channel[i].out[0]!=NULL) {
|
||||
oscBuf[i]->data[oscBuf[i]->needle]+=*fm.channel[ch].out[0];
|
||||
}
|
||||
if (fm.channel[i].out[1]!=NULL) {
|
||||
oscBuf[i]->data[oscBuf[i]->needle]+=*fm.channel[ch].out[1];
|
||||
}
|
||||
oscBuf[i]->data[oscBuf[i]->needle]<<=1;
|
||||
oscBuf[i]->needle++;
|
||||
}
|
||||
oscBuf[i]->data[oscBuf[i]->needle]<<=1;
|
||||
oscBuf[i]->needle++;
|
||||
}
|
||||
|
||||
if (os[0]<-32768) os[0]=-32768;
|
||||
|
|
@ -315,7 +249,9 @@ void DivPlatformOPL::acquire_nuked(short* bufL, short* bufR, size_t start, size_
|
|||
if (os[1]>32767) os[1]=32767;
|
||||
|
||||
bufL[h]=os[0];
|
||||
bufR[h]=os[1];
|
||||
if (oplType==3 || oplType==759) {
|
||||
bufR[h]=os[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -363,18 +299,9 @@ void DivPlatformOPL::tick(bool sysTick) {
|
|||
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+(signed char)chan[i].std.arp.val);
|
||||
}
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(parent->calcArp(chan[i].note,chan[i].std.arp.val));
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
|
||||
if (oplType==3 && chan[i].std.panL.had) {
|
||||
|
|
@ -557,18 +484,9 @@ void DivPlatformOPL::tick(bool sysTick) {
|
|||
|
||||
if (chan[adpcmChan].std.arp.had) {
|
||||
if (!chan[adpcmChan].inPorta) {
|
||||
if (chan[adpcmChan].std.arp.mode) {
|
||||
chan[adpcmChan].baseFreq=NOTE_ADPCMB(chan[adpcmChan].std.arp.val);
|
||||
} else {
|
||||
chan[adpcmChan].baseFreq=NOTE_ADPCMB(chan[adpcmChan].note+(signed char)chan[adpcmChan].std.arp.val);
|
||||
}
|
||||
chan[adpcmChan].baseFreq=NOTE_ADPCMB(parent->calcArp(chan[adpcmChan].note,chan[adpcmChan].std.arp.val));
|
||||
}
|
||||
chan[adpcmChan].freqChanged=true;
|
||||
} else {
|
||||
if (chan[adpcmChan].std.arp.mode && chan[adpcmChan].std.arp.finished) {
|
||||
chan[adpcmChan].baseFreq=NOTE_ADPCMB(chan[adpcmChan].note);
|
||||
chan[adpcmChan].freqChanged=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chan[adpcmChan].freqChanged) {
|
||||
|
|
@ -596,8 +514,9 @@ void DivPlatformOPL::tick(bool sysTick) {
|
|||
if (chan[i].freqChanged) {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq)*2,chan[i].pitch2,chipClock,CHIP_FREQBASE);
|
||||
if (chan[i].fixedFreq>0) chan[i].freq=chan[i].fixedFreq;
|
||||
if (chan[i].freq<0) chan[i].freq=0;
|
||||
if (chan[i].freq>131071) chan[i].freq=131071;
|
||||
int freqt=toFreq(chan[i].freq)+chan[i].pitch2;
|
||||
int freqt=toFreq(chan[i].freq);
|
||||
chan[i].freqH=freqt>>8;
|
||||
chan[i].freqL=freqt&0xff;
|
||||
immWrite(chanMap[i]+ADDR_FREQ,chan[i].freqL);
|
||||
|
|
@ -677,6 +596,9 @@ void DivPlatformOPL::muteChannel(int ch, bool mute) {
|
|||
fm.channel[outChanMap[ch]].muted=mute;
|
||||
}
|
||||
int ops=(slots[3][ch]!=255 && chan[ch].state.ops==4 && oplType==3)?4:2;
|
||||
if (ch&1 && ch<12) {
|
||||
if (chan[ch-1].fourOp) return;
|
||||
}
|
||||
chan[ch].fourOp=(ops==4);
|
||||
update4OpMask=true;
|
||||
for (int i=0; i<ops; i++) {
|
||||
|
|
@ -741,7 +663,7 @@ int DivPlatformOPL::dispatch(DivCommand c) {
|
|||
int end=s->offB+s->lengthB-1;
|
||||
immWrite(11,(end>>2)&0xff);
|
||||
immWrite(12,(end>>10)&0xff);
|
||||
immWrite(7,(s->loopStart>=0)?0xb0:0xa0); // start/repeat
|
||||
immWrite(7,(s->isLoopable())?0xb0:0xa0); // start/repeat
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].note=c.value;
|
||||
chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note);
|
||||
|
|
@ -777,8 +699,8 @@ int DivPlatformOPL::dispatch(DivCommand c) {
|
|||
int end=s->offB+s->lengthB-1;
|
||||
immWrite(11,(end>>2)&0xff);
|
||||
immWrite(12,(end>>10)&0xff);
|
||||
immWrite(7,(s->loopStart>=0)?0xb0:0xa0); // start/repeat
|
||||
int freq=(65536.0*(double)s->rate)/(double)rate;
|
||||
immWrite(7,(s->isLoopable())?0xb0:0xa0); // start/repeat
|
||||
int freq=(65536.0*(double)s->rate)/(double)chipRateBase;
|
||||
immWrite(16,freq&0xff);
|
||||
immWrite(17,(freq>>8)&0xff);
|
||||
}
|
||||
|
|
@ -842,6 +764,13 @@ int DivPlatformOPL::dispatch(DivCommand c) {
|
|||
int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2;
|
||||
chan[c.chan].fourOp=(ops==4);
|
||||
if (chan[c.chan].fourOp) {
|
||||
/*
|
||||
if (chan[c.chan+1].active) {
|
||||
chan[c.chan+1].keyOff=true;
|
||||
chan[c.chan+1].keyOn=false;
|
||||
chan[c.chan+1].active=false;
|
||||
}*/
|
||||
chan[c.chan+1].insChanged=true;
|
||||
chan[c.chan+1].macroInit(NULL);
|
||||
}
|
||||
update4OpMask=true;
|
||||
|
|
@ -1404,6 +1333,9 @@ int DivPlatformOPL::dispatch(DivCommand c) {
|
|||
return 63;
|
||||
break;
|
||||
case DIV_CMD_PRE_PORTA:
|
||||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) {
|
||||
chan[c.chan].baseFreq=(c.chan==adpcmChan)?(NOTE_ADPCMB(chan[c.chan].note)):(NOTE_FREQUENCY(chan[c.chan].note));
|
||||
}
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_PRE_NOTE:
|
||||
|
|
@ -1508,7 +1440,12 @@ void DivPlatformOPL::reset() {
|
|||
fm_ymfm->reset();
|
||||
}
|
||||
*/
|
||||
OPL3_Reset(&fm,rate);
|
||||
if (downsample) {
|
||||
const unsigned int downsampledRate=(unsigned int)((double)rate*rate/chipRateBase);
|
||||
OPL3_Reset(&fm,downsampledRate);
|
||||
} else {
|
||||
OPL3_Reset(&fm,rate);
|
||||
}
|
||||
if (dumpWrites) {
|
||||
addWrite(0xffffffff,0);
|
||||
}
|
||||
|
|
@ -1585,7 +1522,7 @@ void DivPlatformOPL::reset() {
|
|||
}
|
||||
|
||||
bool DivPlatformOPL::isStereo() {
|
||||
return true;
|
||||
return (oplType==3 || oplType==759);
|
||||
}
|
||||
|
||||
bool DivPlatformOPL::keyOffAffectsArp(int ch) {
|
||||
|
|
@ -1625,6 +1562,7 @@ void DivPlatformOPL::setYMFM(bool use) {
|
|||
|
||||
void DivPlatformOPL::setOPLType(int type, bool drums) {
|
||||
pretendYMU=false;
|
||||
downsample=false;
|
||||
adpcmChan=-1;
|
||||
switch (type) {
|
||||
case 1: case 2: case 8950:
|
||||
|
|
@ -1633,7 +1571,7 @@ void DivPlatformOPL::setOPLType(int type, bool drums) {
|
|||
slots=drums?slotsDrums:slotsNonDrums;
|
||||
chanMap=drums?chanMapOPL2Drums:chanMapOPL2;
|
||||
outChanMap=outChanMapOPL2;
|
||||
chipFreqBase=9440540*0.25;
|
||||
chipFreqBase=32768*72;
|
||||
chans=9;
|
||||
melodicChans=drums?6:9;
|
||||
totalChans=drums?11:9;
|
||||
|
|
@ -1641,23 +1579,27 @@ void DivPlatformOPL::setOPLType(int type, bool drums) {
|
|||
adpcmChan=drums?11:9;
|
||||
}
|
||||
break;
|
||||
case 3: case 759:
|
||||
case 3: case 4: case 759:
|
||||
slotsNonDrums=slotsOPL3;
|
||||
slotsDrums=slotsOPL3Drums;
|
||||
slots=drums?slotsDrums:slotsNonDrums;
|
||||
chanMap=drums?chanMapOPL3Drums:chanMapOPL3;
|
||||
outChanMap=outChanMapOPL3;
|
||||
chipFreqBase=9440540;
|
||||
chipFreqBase=32768*288;
|
||||
chans=18;
|
||||
melodicChans=drums?15:18;
|
||||
totalChans=drums?20:18;
|
||||
if (type==759) {
|
||||
pretendYMU=true;
|
||||
adpcmChan=16;
|
||||
} else if (type==4) {
|
||||
chipFreqBase=32768*684;
|
||||
downsample=true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (type==759) {
|
||||
chipType=type;
|
||||
if (type==759 || type==4) {
|
||||
oplType=3;
|
||||
} else if (type==8950) {
|
||||
oplType=1;
|
||||
|
|
@ -1692,20 +1634,76 @@ void DivPlatformOPL::setFlags(unsigned int flags) {
|
|||
rate=chipClock/36;
|
||||
}*/
|
||||
|
||||
if (oplType==3) {
|
||||
chipClock=COLOR_NTSC*4.0;
|
||||
rate=chipClock/288;
|
||||
} else {
|
||||
chipClock=COLOR_NTSC;
|
||||
rate=chipClock/72;
|
||||
switch (chipType) {
|
||||
default:
|
||||
case 1: case 2: case 8950:
|
||||
switch (flags&0xff) {
|
||||
case 0x01:
|
||||
chipClock=COLOR_PAL*4.0/5.0;
|
||||
break;
|
||||
case 0x02:
|
||||
chipClock=4000000.0;
|
||||
break;
|
||||
case 0x03:
|
||||
chipClock=3000000.0;
|
||||
break;
|
||||
case 0x04:
|
||||
chipClock=38400*13*8; // 31948800/8
|
||||
break;
|
||||
case 0x05:
|
||||
chipClock=3500000.0;
|
||||
break;
|
||||
default:
|
||||
chipClock=COLOR_NTSC;
|
||||
break;
|
||||
}
|
||||
rate=chipClock/72;
|
||||
chipRateBase=rate;
|
||||
break;
|
||||
case 3:
|
||||
switch (flags&0xff) {
|
||||
case 0x01:
|
||||
chipClock=COLOR_PAL*16.0/5.0;
|
||||
break;
|
||||
case 0x02:
|
||||
chipClock=14000000.0;
|
||||
break;
|
||||
case 0x03:
|
||||
chipClock=16000000.0;
|
||||
break;
|
||||
case 0x04:
|
||||
chipClock=15000000.0;
|
||||
break;
|
||||
default:
|
||||
chipClock=COLOR_NTSC*4.0;
|
||||
break;
|
||||
}
|
||||
rate=chipClock/288;
|
||||
chipRateBase=rate;
|
||||
break;
|
||||
case 4:
|
||||
switch (flags&0xff) {
|
||||
case 0x01:
|
||||
chipClock=COLOR_PAL*32.0/5.0;
|
||||
break;
|
||||
case 0x02:
|
||||
chipClock=33868800.0;
|
||||
break;
|
||||
default:
|
||||
chipClock=COLOR_NTSC*8.0;
|
||||
break;
|
||||
}
|
||||
rate=chipClock/768;
|
||||
chipRateBase=chipClock/684;
|
||||
break;
|
||||
case 759:
|
||||
rate=48000;
|
||||
chipRateBase=rate;
|
||||
chipClock=rate*288;
|
||||
break;
|
||||
}
|
||||
|
||||
if (pretendYMU) {
|
||||
rate=48000;
|
||||
chipClock=rate*288;
|
||||
}
|
||||
|
||||
for (int i=0; i<18; i++) {
|
||||
for (int i=0; i<20; i++) {
|
||||
oscBuf[i]->rate=rate;
|
||||
}
|
||||
}
|
||||
|
|
@ -1756,7 +1754,7 @@ int DivPlatformOPL::init(DivEngine* p, int channels, int sugRate, unsigned int f
|
|||
for (int i=0; i<20; i++) {
|
||||
isMuted[i]=false;
|
||||
}
|
||||
for (int i=0; i<18; i++) {
|
||||
for (int i=0; i<20; i++) {
|
||||
oscBuf[i]=new DivDispatchOscBuffer;
|
||||
}
|
||||
setFlags(flags);
|
||||
|
|
@ -1774,7 +1772,7 @@ int DivPlatformOPL::init(DivEngine* p, int channels, int sugRate, unsigned int f
|
|||
}
|
||||
|
||||
void DivPlatformOPL::quit() {
|
||||
for (int i=0; i<18; i++) {
|
||||
for (int i=0; i<20; i++) {
|
||||
delete oscBuf[i];
|
||||
}
|
||||
if (adpcmChan>=0) {
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ class DivPlatformOPL: public DivDispatch {
|
|||
}
|
||||
};
|
||||
Channel chan[20];
|
||||
DivDispatchOscBuffer* oscBuf[18];
|
||||
DivDispatchOscBuffer* oscBuf[20];
|
||||
bool isMuted[20];
|
||||
struct QueuedWrite {
|
||||
unsigned short addr;
|
||||
|
|
@ -95,8 +95,8 @@ class DivPlatformOPL: public DivDispatch {
|
|||
const unsigned char** slots;
|
||||
const unsigned short* chanMap;
|
||||
const unsigned char* outChanMap;
|
||||
double chipFreqBase;
|
||||
int delay, oplType, chans, melodicChans, totalChans, adpcmChan, sampleBank;
|
||||
int chipFreqBase, chipRateBase;
|
||||
int delay, chipType, oplType, chans, melodicChans, totalChans, adpcmChan, sampleBank;
|
||||
unsigned char lastBusy;
|
||||
unsigned char drumState;
|
||||
unsigned char drumVol[5];
|
||||
|
|
@ -107,7 +107,7 @@ class DivPlatformOPL: public DivDispatch {
|
|||
|
||||
unsigned char lfoValue;
|
||||
|
||||
bool useYMFM, update4OpMask, pretendYMU;
|
||||
bool useYMFM, update4OpMask, pretendYMU, downsample;
|
||||
|
||||
short oldWrites[512];
|
||||
short pendingWrites[512];
|
||||
|
|
@ -145,7 +145,6 @@ class DivPlatformOPL: public DivDispatch {
|
|||
int getPortaFloor(int ch);
|
||||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char* getEffectName(unsigned char effect);
|
||||
const void* getSampleMem(int index);
|
||||
size_t getSampleMemCapacity(int index);
|
||||
size_t getSampleMemUsage(int index);
|
||||
|
|
|
|||
|
|
@ -27,68 +27,6 @@
|
|||
|
||||
#define CHIP_FREQBASE 1180068
|
||||
|
||||
const char* DivPlatformOPLL::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x11:
|
||||
return "11xx: Set feedback (0 to 7)";
|
||||
break;
|
||||
case 0x12:
|
||||
return "12xx: Set level of operator 1 (0 highest, 3F lowest)";
|
||||
break;
|
||||
case 0x13:
|
||||
return "13xx: Set level of operator 2 (0 highest, F lowest)";
|
||||
break;
|
||||
case 0x16:
|
||||
return "16xy: Set operator multiplier (x: operator from 1 to 2; y: multiplier)";
|
||||
break;
|
||||
case 0x18:
|
||||
if (properDrumsSys) {
|
||||
return "18xx: Toggle drums mode (1: enabled; 0: disabled)";
|
||||
}
|
||||
break;
|
||||
case 0x19:
|
||||
return "19xx: Set attack of all operators (0 to F)";
|
||||
break;
|
||||
case 0x1a:
|
||||
return "1Axx: Set attack of operator 1 (0 to F)";
|
||||
break;
|
||||
case 0x1b:
|
||||
return "1Bxx: Set attack of operator 2 (0 to F)";
|
||||
break;
|
||||
case 0x50:
|
||||
return "50xy: Set AM (x: operator from 1 to 2 (0 for all ops); y: AM)";
|
||||
break;
|
||||
case 0x51:
|
||||
return "51xy: Set sustain level (x: operator from 1 to 2 (0 for all ops); y: sustain)";
|
||||
break;
|
||||
case 0x52:
|
||||
return "52xy: Set release (x: operator from 1 to 2 (0 for all ops); y: release)";
|
||||
break;
|
||||
case 0x53:
|
||||
return "53xy: Set vibrato (x: operator from 1 to 2 (0 for all ops); y: enabled)";
|
||||
break;
|
||||
case 0x54:
|
||||
return "54xy: Set key scale level (x: operator from 1 to 2 (0 for all ops); y: level from 0 to 3)";
|
||||
break;
|
||||
case 0x55:
|
||||
return "55xy: Set envelope sustain (x: operator from 1 to 2 (0 for all ops); y: enabled)";
|
||||
break;
|
||||
case 0x56:
|
||||
return "56xx: Set decay of all operators (0 to F)";
|
||||
break;
|
||||
case 0x57:
|
||||
return "57xx: Set decay of operator 1 (0 to F)";
|
||||
break;
|
||||
case 0x58:
|
||||
return "58xx: Set decay of operator 2 (0 to F)";
|
||||
break;
|
||||
case 0x5b:
|
||||
return "5Bxy: Set whether key will scale envelope (x: operator from 1 to 2 (0 for all ops); y: enabled)";
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const unsigned char cycleMapOPLL[18]={
|
||||
8, 7, 6, 7, 8, 7, 8, 6, 0, 1, 2, 7, 8, 9, 3, 4, 5, 9
|
||||
};
|
||||
|
|
@ -97,6 +35,10 @@ const unsigned char drumSlot[11]={
|
|||
0, 0, 0, 0, 0, 0, 6, 7, 8, 8, 7
|
||||
};
|
||||
|
||||
const unsigned char visMapOPLL[9]={
|
||||
6, 7, 8, 3, 4, 5, 0, 1, 2
|
||||
};
|
||||
|
||||
void DivPlatformOPLL::acquire_nuked(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
static int o[2];
|
||||
static int os;
|
||||
|
|
@ -124,10 +66,18 @@ void DivPlatformOPLL::acquire_nuked(short* bufL, short* bufR, size_t start, size
|
|||
OPLL_Clock(&fm,o);
|
||||
unsigned char nextOut=cycleMapOPLL[fm.cycles];
|
||||
if ((nextOut>=6 && properDrums) || !isMuted[nextOut]) {
|
||||
oscBuf[nextOut]->data[oscBuf[nextOut]->needle++]=(o[0]+o[1])<<6;
|
||||
os+=(o[0]+o[1]);
|
||||
if (vrc7 || (fm.rm_enable&0x20)) oscBuf[nextOut]->data[oscBuf[nextOut]->needle++]=(o[0]+o[1])<<6;
|
||||
} else {
|
||||
oscBuf[nextOut]->data[oscBuf[nextOut]->needle++]=0;
|
||||
if (vrc7 || (fm.rm_enable&0x20)) oscBuf[nextOut]->data[oscBuf[nextOut]->needle++]=0;
|
||||
}
|
||||
}
|
||||
if (!(vrc7 || (fm.rm_enable&0x20))) for (int i=0; i<9; i++) {
|
||||
unsigned char ch=visMapOPLL[i];
|
||||
if ((i>=6 && properDrums) || !isMuted[ch]) {
|
||||
oscBuf[ch]->data[oscBuf[ch]->needle++]=(fm.output_ch[i])<<6;
|
||||
} else {
|
||||
oscBuf[ch]->data[oscBuf[ch]->needle++]=0;
|
||||
}
|
||||
}
|
||||
os*=50;
|
||||
|
|
@ -157,18 +107,9 @@ void DivPlatformOPLL::tick(bool sysTick) {
|
|||
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+(signed char)chan[i].std.arp.val);
|
||||
}
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(parent->calcArp(chan[i].note,chan[i].std.arp.val));
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
|
||||
if (chan[i].std.wave.had && chan[i].state.opllPreset!=16) {
|
||||
|
|
@ -819,6 +760,7 @@ int DivPlatformOPLL::dispatch(DivCommand c) {
|
|||
break;
|
||||
case DIV_CMD_PRE_PORTA:
|
||||
if (c.chan>=9 && !properDrums) return 0;
|
||||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_FREQUENCY(chan[c.chan].note);
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_PRE_NOTE:
|
||||
|
|
|
|||
|
|
@ -122,7 +122,6 @@ class DivPlatformOPLL: public DivDispatch {
|
|||
int getPortaFloor(int ch);
|
||||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char* getEffectName(unsigned char effect);
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
~DivPlatformOPLL();
|
||||
|
|
|
|||
|
|
@ -53,27 +53,6 @@ const char** DivPlatformPCE::getRegisterSheet() {
|
|||
return regCheatSheetPCE;
|
||||
}
|
||||
|
||||
const char* DivPlatformPCE::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x10:
|
||||
return "10xx: Change waveform";
|
||||
break;
|
||||
case 0x11:
|
||||
return "11xx: Toggle noise mode";
|
||||
break;
|
||||
case 0x12:
|
||||
return "12xx: Setup LFO (0: disabled; 1: 1x depth; 2: 16x depth; 3: 256x depth)";
|
||||
break;
|
||||
case 0x13:
|
||||
return "13xx: Set LFO speed";
|
||||
break;
|
||||
case 0x17:
|
||||
return "17xx: Toggle PCM mode";
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DivPlatformPCE::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
for (size_t h=start; h<start+len; h++) {
|
||||
// PCM part
|
||||
|
|
@ -90,12 +69,10 @@ void DivPlatformPCE::acquire(short* bufL, short* bufR, size_t start, size_t len)
|
|||
chWrite(i,0x04,0xdf);
|
||||
chWrite(i,0x06,(((unsigned char)s->data8[chan[i].dacPos]+0x80)>>3));
|
||||
chan[i].dacPos++;
|
||||
if (chan[i].dacPos>=s->samples) {
|
||||
if (s->loopStart>=0 && s->loopStart<(int)s->samples) {
|
||||
chan[i].dacPos=s->loopStart;
|
||||
} else {
|
||||
chan[i].dacSample=-1;
|
||||
}
|
||||
if (s->isLoopable() && chan[i].dacPos>=s->getEndPosition()) {
|
||||
chan[i].dacPos=s->loopStart;
|
||||
} else if (chan[i].dacPos>=s->samples) {
|
||||
chan[i].dacSample=-1;
|
||||
}
|
||||
chan[i].dacPeriod-=rate;
|
||||
}
|
||||
|
|
@ -117,7 +94,7 @@ void DivPlatformPCE::acquire(short* bufL, short* bufR, size_t start, size_t len)
|
|||
pce->ResetTS(0);
|
||||
|
||||
for (int i=0; i<6; i++) {
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=(pce->channel[i].blip_prev_samp[0]+pce->channel[i].blip_prev_samp[1])<<1;
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP((pce->channel[i].blip_prev_samp[0]+pce->channel[i].blip_prev_samp[1])<<1,-32768,32767);
|
||||
}
|
||||
|
||||
tempL[0]=(tempL[0]>>1)+(tempL[0]>>2);
|
||||
|
|
@ -135,14 +112,22 @@ void DivPlatformPCE::acquire(short* bufL, short* bufR, size_t start, size_t len)
|
|||
}
|
||||
|
||||
void DivPlatformPCE::updateWave(int ch) {
|
||||
if (chan[ch].pcm) {
|
||||
chan[ch].deferredWaveUpdate=true;
|
||||
return;
|
||||
}
|
||||
chWrite(ch,0x04,0x5f);
|
||||
chWrite(ch,0x04,0x1f);
|
||||
for (int i=0; i<32; i++) {
|
||||
chWrite(ch,0x06,chan[ch].ws.output[i]);
|
||||
chWrite(ch,0x06,chan[ch].ws.output[(i+chan[ch].antiClickWavePos)&31]);
|
||||
}
|
||||
chan[ch].antiClickWavePos&=31;
|
||||
if (chan[ch].active) {
|
||||
chWrite(ch,0x04,0x80|chan[ch].outVol);
|
||||
}
|
||||
if (chan[ch].deferredWaveUpdate) {
|
||||
chan[ch].deferredWaveUpdate=false;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: in octave 6 the noise table changes to a tonal one
|
||||
|
|
@ -152,6 +137,13 @@ static unsigned char noiseFreq[12]={
|
|||
|
||||
void DivPlatformPCE::tick(bool sysTick) {
|
||||
for (int i=0; i<6; i++) {
|
||||
// anti-click
|
||||
if (antiClickEnabled && sysTick && chan[i].freq>0) {
|
||||
chan[i].antiClickPeriodCount+=(chipClock/MAX(parent->getCurHz(),1.0f));
|
||||
chan[i].antiClickWavePos+=chan[i].antiClickPeriodCount/chan[i].freq;
|
||||
chan[i].antiClickPeriodCount%=chan[i].freq;
|
||||
}
|
||||
|
||||
chan[i].std.next();
|
||||
if (chan[i].std.vol.had) {
|
||||
chan[i].outVol=VOL_SCALE_LOG(chan[i].vol&31,MIN(31,chan[i].std.vol.val),31);
|
||||
|
|
@ -170,28 +162,12 @@ void DivPlatformPCE::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp.val);
|
||||
// noise
|
||||
int noiseSeek=chan[i].std.arp.val;
|
||||
if (noiseSeek<0) noiseSeek=0;
|
||||
chWrite(i,0x07,chan[i].noise?(0x80|(parent->song.properNoiseLayout?(noiseSeek&31):noiseFreq[noiseSeek%12])):0);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp.val);
|
||||
int noiseSeek=chan[i].note+chan[i].std.arp.val;
|
||||
if (noiseSeek<0) noiseSeek=0;
|
||||
chWrite(i,0x07,chan[i].noise?(0x80|(parent->song.properNoiseLayout?(noiseSeek&31):noiseFreq[noiseSeek%12])):0);
|
||||
}
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note);
|
||||
int noiseSeek=chan[i].note;
|
||||
int noiseSeek=parent->calcArp(chan[i].note,chan[i].std.arp.val);
|
||||
chan[i].baseFreq=NOTE_PERIODIC(noiseSeek);
|
||||
if (noiseSeek<0) noiseSeek=0;
|
||||
chWrite(i,0x07,chan[i].noise?(0x80|(parent->song.properNoiseLayout?(noiseSeek&31):noiseFreq[noiseSeek%12])):0);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
if (chan[i].std.wave.had && !chan[i].pcm) {
|
||||
if (chan[i].wave!=chan[i].std.wave.val || chan[i].ws.activeChanged()) {
|
||||
|
|
@ -220,8 +196,12 @@ void DivPlatformPCE::tick(bool sysTick) {
|
|||
}
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
if (chan[i].std.phaseReset.had && chan[i].std.phaseReset.val==1) {
|
||||
chan[i].antiClickWavePos=0;
|
||||
chan[i].antiClickPeriodCount=0;
|
||||
}
|
||||
if (chan[i].active) {
|
||||
if (chan[i].ws.tick() || (chan[i].std.phaseReset.had && chan[i].std.phaseReset.val==1)) {
|
||||
if (chan[i].ws.tick() || (chan[i].std.phaseReset.had && chan[i].std.phaseReset.val==1) || chan[i].deferredWaveUpdate) {
|
||||
updateWave(i);
|
||||
}
|
||||
}
|
||||
|
|
@ -445,6 +425,7 @@ int DivPlatformPCE::dispatch(DivCommand c) {
|
|||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_PCE));
|
||||
}
|
||||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note);
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_GET_VOLMAX:
|
||||
|
|
@ -556,10 +537,18 @@ void DivPlatformPCE::setFlags(unsigned int flags) {
|
|||
} else {
|
||||
chipClock=COLOR_NTSC;
|
||||
}
|
||||
// flags&4 will be chip revision
|
||||
antiClickEnabled=!(flags&8);
|
||||
rate=chipClock/12;
|
||||
for (int i=0; i<6; i++) {
|
||||
oscBuf[i]->rate=rate;
|
||||
}
|
||||
|
||||
if (pce!=NULL) {
|
||||
delete pce;
|
||||
pce=NULL;
|
||||
}
|
||||
pce=new PCE_PSG(tempL,tempR,(flags&4)?PCE_PSG::REVISION_HUC6280A:PCE_PSG::REVISION_HUC6280);
|
||||
}
|
||||
|
||||
void DivPlatformPCE::poke(unsigned int addr, unsigned short val) {
|
||||
|
|
@ -578,8 +567,8 @@ int DivPlatformPCE::init(DivEngine* p, int channels, int sugRate, unsigned int f
|
|||
isMuted[i]=false;
|
||||
oscBuf[i]=new DivDispatchOscBuffer;
|
||||
}
|
||||
pce=NULL;
|
||||
setFlags(flags);
|
||||
pce=new PCE_PSG(tempL,tempR,PCE_PSG::REVISION_HUC6280A);
|
||||
reset();
|
||||
return 6;
|
||||
}
|
||||
|
|
@ -588,7 +577,10 @@ void DivPlatformPCE::quit() {
|
|||
for (int i=0; i<6; i++) {
|
||||
delete oscBuf[i];
|
||||
}
|
||||
delete pce;
|
||||
if (pce!=NULL) {
|
||||
delete pce;
|
||||
pce=NULL;
|
||||
}
|
||||
}
|
||||
|
||||
DivPlatformPCE::~DivPlatformPCE() {
|
||||
|
|
|
|||
|
|
@ -28,12 +28,12 @@
|
|||
|
||||
class DivPlatformPCE: public DivDispatch {
|
||||
struct Channel {
|
||||
int freq, baseFreq, pitch, pitch2, note;
|
||||
int freq, baseFreq, pitch, pitch2, note, antiClickPeriodCount, antiClickWavePos;
|
||||
int dacPeriod, dacRate;
|
||||
unsigned int dacPos;
|
||||
int dacSample, ins;
|
||||
unsigned char pan;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, noise, pcm, furnaceDac;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, noise, pcm, furnaceDac, deferredWaveUpdate;
|
||||
signed char vol, outVol, wave;
|
||||
DivMacroInt std;
|
||||
DivWaveSynth ws;
|
||||
|
|
@ -47,6 +47,8 @@ class DivPlatformPCE: public DivDispatch {
|
|||
pitch(0),
|
||||
pitch2(0),
|
||||
note(0),
|
||||
antiClickPeriodCount(0),
|
||||
antiClickWavePos(0),
|
||||
dacPeriod(0),
|
||||
dacRate(0),
|
||||
dacPos(0),
|
||||
|
|
@ -62,6 +64,7 @@ class DivPlatformPCE: public DivDispatch {
|
|||
noise(false),
|
||||
pcm(false),
|
||||
furnaceDac(false),
|
||||
deferredWaveUpdate(false),
|
||||
vol(31),
|
||||
outVol(31),
|
||||
wave(-1) {}
|
||||
|
|
@ -69,6 +72,7 @@ class DivPlatformPCE: public DivDispatch {
|
|||
Channel chan[6];
|
||||
DivDispatchOscBuffer* oscBuf[6];
|
||||
bool isMuted[6];
|
||||
bool antiClickEnabled;
|
||||
struct QueuedWrite {
|
||||
unsigned char addr;
|
||||
unsigned char val;
|
||||
|
|
@ -105,7 +109,6 @@ class DivPlatformPCE: public DivDispatch {
|
|||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
~DivPlatformPCE();
|
||||
|
|
|
|||
359
src/engine/platform/pcmdac.cpp
Normal file
359
src/engine/platform/pcmdac.cpp
Normal file
|
|
@ -0,0 +1,359 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#define _USE_MATH_DEFINES
|
||||
#include "pcmdac.h"
|
||||
#include "../engine.h"
|
||||
#include <math.h>
|
||||
|
||||
// to ease the driver, freqency register is a 8.16 counter relative to output sample rate
|
||||
#define CHIP_FREQBASE 65536
|
||||
|
||||
void DivPlatformPCMDAC::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
const int depthScale=(15-outDepth);
|
||||
int output=0;
|
||||
for (size_t h=start; h<start+len; h++) {
|
||||
if (!chan.active || isMuted) {
|
||||
bufL[h]=0;
|
||||
bufR[h]=0;
|
||||
oscBuf->data[oscBuf->needle++]=0;
|
||||
continue;
|
||||
}
|
||||
if (chan.useWave || (chan.sample>=0 && chan.sample<parent->song.sampleLen)) {
|
||||
chan.audPos+=chan.freq>>16;
|
||||
chan.audSub+=(chan.freq&0xffff);
|
||||
if (chan.audSub>=0x10000) {
|
||||
chan.audSub-=0x10000;
|
||||
chan.audPos+=1;
|
||||
}
|
||||
if (chan.useWave) {
|
||||
if (chan.audPos>=(unsigned int)(chan.audLen<<1)) {
|
||||
chan.audPos=0;
|
||||
}
|
||||
output=(chan.ws.output[chan.audPos]^0x80)<<8;
|
||||
} else {
|
||||
DivSample* s=parent->getSample(chan.sample);
|
||||
if (s->samples>0) {
|
||||
if (s->isLoopable() && chan.audPos>=s->getEndPosition()) {
|
||||
chan.audPos=s->loopStart;
|
||||
} else if (chan.audPos>=s->samples) {
|
||||
chan.sample=-1;
|
||||
}
|
||||
if (chan.audPos<s->samples) {
|
||||
output=s->data16[chan.audPos];
|
||||
}
|
||||
} else {
|
||||
chan.sample=-1;
|
||||
}
|
||||
}
|
||||
}
|
||||
output=output*chan.vol*chan.envVol/16384;
|
||||
oscBuf->data[oscBuf->needle++]=output;
|
||||
if (outStereo) {
|
||||
bufL[h]=((output*chan.panL)>>(depthScale+8))<<depthScale;
|
||||
bufR[h]=((output*chan.panR)>>(depthScale+8))<<depthScale;
|
||||
} else {
|
||||
output=(output>>depthScale)<<depthScale;
|
||||
bufL[h]=output;
|
||||
bufR[h]=output;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformPCMDAC::tick(bool sysTick) {
|
||||
chan.std.next();
|
||||
if (chan.std.vol.had) {
|
||||
chan.envVol=chan.std.vol.val;
|
||||
}
|
||||
if (chan.std.arp.had) {
|
||||
if (!chan.inPorta) {
|
||||
chan.baseFreq=NOTE_FREQUENCY(parent->calcArp(chan.note,chan.std.arp.val));
|
||||
}
|
||||
chan.freqChanged=true;
|
||||
}
|
||||
if (chan.useWave && chan.std.wave.had) {
|
||||
if (chan.wave!=chan.std.wave.val || chan.ws.activeChanged()) {
|
||||
chan.wave=chan.std.wave.val;
|
||||
chan.ws.changeWave1(chan.wave);
|
||||
if (!chan.keyOff) chan.keyOn=true;
|
||||
}
|
||||
}
|
||||
if (chan.useWave && chan.active) {
|
||||
chan.ws.tick();
|
||||
}
|
||||
if (chan.std.pitch.had) {
|
||||
if (chan.std.pitch.mode) {
|
||||
chan.pitch2+=chan.std.pitch.val;
|
||||
CLAMP_VAR(chan.pitch2,-32768,32767);
|
||||
} else {
|
||||
chan.pitch2=chan.std.pitch.val;
|
||||
}
|
||||
chan.freqChanged=true;
|
||||
}
|
||||
if (chan.std.panL.had) {
|
||||
int val=chan.std.panL.val&0x7f;
|
||||
chan.panL=val*2;
|
||||
}
|
||||
if (chan.std.panR.had) {
|
||||
int val=chan.std.panR.val&0x7f;
|
||||
chan.panR=val*2;
|
||||
}
|
||||
if (chan.std.phaseReset.had) {
|
||||
if (chan.std.phaseReset.val==1) {
|
||||
chan.audPos=0;
|
||||
}
|
||||
}
|
||||
if (chan.freqChanged || chan.keyOn || chan.keyOff) {
|
||||
//DivInstrument* ins=parent->getIns(chan.ins,DIV_INS_AMIGA);
|
||||
double off=1.0;
|
||||
if (!chan.useWave && chan.sample>=0 && chan.sample<parent->song.sampleLen) {
|
||||
DivSample* s=parent->getSample(chan.sample);
|
||||
off=(s->centerRate>=1)?((double)s->centerRate/8363.0):1.0;
|
||||
}
|
||||
chan.freq=off*parent->calcFreq(chan.baseFreq,chan.pitch,false,2,chan.pitch2,chipClock,CHIP_FREQBASE);
|
||||
if (chan.freq>16777215) chan.freq=16777215;
|
||||
if (chan.keyOn) {
|
||||
if (!chan.std.vol.had) {
|
||||
chan.envVol=64;
|
||||
}
|
||||
chan.keyOn=false;
|
||||
}
|
||||
if (chan.keyOff) {
|
||||
chan.keyOff=false;
|
||||
}
|
||||
chan.freqChanged=false;
|
||||
}
|
||||
}
|
||||
|
||||
int DivPlatformPCMDAC::dispatch(DivCommand c) {
|
||||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON: {
|
||||
DivInstrument* ins=parent->getIns(chan.ins,DIV_INS_AMIGA);
|
||||
if (ins->amiga.useWave) {
|
||||
chan.useWave=true;
|
||||
chan.audLen=(ins->amiga.waveLen+1)>>1;
|
||||
if (chan.insChanged) {
|
||||
if (chan.wave<0) {
|
||||
chan.wave=0;
|
||||
chan.ws.setWidth(chan.audLen<<1);
|
||||
chan.ws.changeWave1(chan.wave);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
chan.sample=ins->amiga.getSample(c.value);
|
||||
chan.useWave=false;
|
||||
}
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan.baseFreq=round(NOTE_FREQUENCY(c.value));
|
||||
}
|
||||
if (chan.useWave || chan.sample<0 || chan.sample>=parent->song.sampleLen) {
|
||||
chan.sample=-1;
|
||||
}
|
||||
if (chan.setPos) {
|
||||
chan.setPos=false;
|
||||
} else {
|
||||
chan.audPos=0;
|
||||
}
|
||||
chan.audSub=0;
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan.freqChanged=true;
|
||||
chan.note=c.value;
|
||||
}
|
||||
chan.active=true;
|
||||
chan.keyOn=true;
|
||||
chan.macroInit(ins);
|
||||
if (!parent->song.brokenOutVol && !chan.std.vol.will) {
|
||||
chan.envVol=64;
|
||||
}
|
||||
if (chan.useWave) {
|
||||
chan.ws.init(ins,chan.audLen<<1,255,chan.insChanged);
|
||||
}
|
||||
chan.insChanged=false;
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_NOTE_OFF:
|
||||
chan.sample=-1;
|
||||
chan.active=false;
|
||||
chan.keyOff=true;
|
||||
chan.macroInit(NULL);
|
||||
break;
|
||||
case DIV_CMD_NOTE_OFF_ENV:
|
||||
case DIV_CMD_ENV_RELEASE:
|
||||
chan.std.release();
|
||||
break;
|
||||
case DIV_CMD_INSTRUMENT:
|
||||
if (chan.ins!=c.value || c.value2==1) {
|
||||
chan.ins=c.value;
|
||||
chan.insChanged=true;
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_VOLUME:
|
||||
if (chan.vol!=c.value) {
|
||||
chan.vol=c.value;
|
||||
if (!chan.std.vol.has) {
|
||||
chan.envVol=64;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_GET_VOLUME:
|
||||
return chan.vol;
|
||||
break;
|
||||
case DIV_CMD_PANNING:
|
||||
chan.panL=c.value;
|
||||
chan.panR=c.value2;
|
||||
break;
|
||||
case DIV_CMD_PITCH:
|
||||
chan.pitch=c.value;
|
||||
chan.freqChanged=true;
|
||||
break;
|
||||
case DIV_CMD_WAVE:
|
||||
if (!chan.useWave) break;
|
||||
chan.wave=c.value;
|
||||
chan.keyOn=true;
|
||||
chan.ws.changeWave1(chan.wave);
|
||||
break;
|
||||
case DIV_CMD_NOTE_PORTA: {
|
||||
DivInstrument* ins=parent->getIns(chan.ins,DIV_INS_AMIGA);
|
||||
chan.sample=ins->amiga.getSample(c.value2);
|
||||
int destFreq=round(NOTE_FREQUENCY(c.value2));
|
||||
bool return2=false;
|
||||
if (destFreq>chan.baseFreq) {
|
||||
chan.baseFreq+=c.value;
|
||||
if (chan.baseFreq>=destFreq) {
|
||||
chan.baseFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
} else {
|
||||
chan.baseFreq-=c.value;
|
||||
if (chan.baseFreq<=destFreq) {
|
||||
chan.baseFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
}
|
||||
chan.freqChanged=true;
|
||||
if (return2) {
|
||||
chan.inPorta=false;
|
||||
return 2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_LEGATO: {
|
||||
chan.baseFreq=round(NOTE_FREQUENCY(c.value+((chan.std.arp.will && !chan.std.arp.mode)?(chan.std.arp.val):(0))));
|
||||
chan.freqChanged=true;
|
||||
chan.note=c.value;
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_PRE_PORTA:
|
||||
if (chan.active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan.macroInit(parent->getIns(chan.ins,DIV_INS_AMIGA));
|
||||
}
|
||||
chan.inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_SAMPLE_POS:
|
||||
if (chan.useWave) break;
|
||||
chan.audPos=c.value;
|
||||
chan.setPos=true;
|
||||
break;
|
||||
case DIV_CMD_GET_VOLMAX:
|
||||
return 255;
|
||||
break;
|
||||
case DIV_ALWAYS_SET_VOLUME:
|
||||
return 1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void DivPlatformPCMDAC::muteChannel(int ch, bool mute) {
|
||||
isMuted=mute;
|
||||
}
|
||||
|
||||
void DivPlatformPCMDAC::forceIns() {
|
||||
chan.insChanged=true;
|
||||
chan.freqChanged=true;
|
||||
chan.audPos=0;
|
||||
chan.sample=-1;
|
||||
}
|
||||
|
||||
void* DivPlatformPCMDAC::getChanState(int ch) {
|
||||
return &chan;
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformPCMDAC::getOscBuffer(int ch) {
|
||||
return oscBuf;
|
||||
}
|
||||
|
||||
void DivPlatformPCMDAC::reset() {
|
||||
chan=DivPlatformPCMDAC::Channel();
|
||||
chan.std.setEngine(parent);
|
||||
chan.ws.setEngine(parent);
|
||||
chan.ws.init(NULL,32,255);
|
||||
}
|
||||
|
||||
bool DivPlatformPCMDAC::isStereo() {
|
||||
return true;
|
||||
}
|
||||
|
||||
DivMacroInt* DivPlatformPCMDAC::getChanMacroInt(int ch) {
|
||||
return &chan.std;
|
||||
}
|
||||
|
||||
void DivPlatformPCMDAC::notifyInsChange(int ins) {
|
||||
if (chan.ins==ins) {
|
||||
chan.insChanged=true;
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformPCMDAC::notifyWaveChange(int wave) {
|
||||
if (chan.useWave && chan.wave==wave) {
|
||||
chan.ws.changeWave1(wave);
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformPCMDAC::notifyInsDeletion(void* ins) {
|
||||
chan.std.notifyInsDeletion((DivInstrument*)ins);
|
||||
}
|
||||
|
||||
void DivPlatformPCMDAC::setFlags(unsigned int flags) {
|
||||
// default to 44100Hz 16-bit stereo
|
||||
if (!flags) flags=0x1f0000|44099;
|
||||
rate=(flags&0xffff)+1;
|
||||
// rate can't be too low or the resampler will break
|
||||
if (rate<1000) rate=1000;
|
||||
chipClock=rate;
|
||||
outDepth=(flags>>16)&0xf;
|
||||
outStereo=(flags>>20)&1;
|
||||
}
|
||||
|
||||
int DivPlatformPCMDAC::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
||||
parent=p;
|
||||
dumpWrites=false;
|
||||
skipRegisterWrites=false;
|
||||
oscBuf=new DivDispatchOscBuffer;
|
||||
isMuted=false;
|
||||
setFlags(flags);
|
||||
reset();
|
||||
return 1;
|
||||
}
|
||||
|
||||
void DivPlatformPCMDAC::quit() {
|
||||
delete oscBuf;
|
||||
}
|
||||
99
src/engine/platform/pcmdac.h
Normal file
99
src/engine/platform/pcmdac.h
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
/**
|
||||
* 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 _PCM_DAC_H
|
||||
#define _PCM_DAC_H
|
||||
|
||||
#include "../dispatch.h"
|
||||
#include <queue>
|
||||
#include "../macroInt.h"
|
||||
#include "../waveSynth.h"
|
||||
|
||||
class DivPlatformPCMDAC: public DivDispatch {
|
||||
struct Channel {
|
||||
int freq, baseFreq, pitch, pitch2;
|
||||
unsigned int audLoc;
|
||||
unsigned short audLen;
|
||||
unsigned int audPos;
|
||||
int audSub;
|
||||
int sample, wave, ins;
|
||||
int note;
|
||||
int panL, panR;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, useWave, setPos;
|
||||
int vol, envVol;
|
||||
DivMacroInt std;
|
||||
DivWaveSynth ws;
|
||||
void macroInit(DivInstrument* which) {
|
||||
std.init(which);
|
||||
pitch2=0;
|
||||
}
|
||||
Channel():
|
||||
freq(0),
|
||||
baseFreq(0),
|
||||
pitch(0),
|
||||
pitch2(0),
|
||||
audLoc(0),
|
||||
audLen(0),
|
||||
audPos(0),
|
||||
audSub(0),
|
||||
sample(-1),
|
||||
wave(-1),
|
||||
ins(-1),
|
||||
note(0),
|
||||
panL(255),
|
||||
panR(255),
|
||||
active(false),
|
||||
insChanged(true),
|
||||
freqChanged(false),
|
||||
keyOn(false),
|
||||
keyOff(false),
|
||||
inPorta(false),
|
||||
useWave(false),
|
||||
setPos(false),
|
||||
vol(255),
|
||||
envVol(64) {}
|
||||
};
|
||||
Channel chan;
|
||||
DivDispatchOscBuffer* oscBuf;
|
||||
bool isMuted;
|
||||
int outDepth;
|
||||
bool outStereo;
|
||||
|
||||
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);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
void reset();
|
||||
void forceIns();
|
||||
void tick(bool sysTick=true);
|
||||
void muteChannel(int ch, bool mute);
|
||||
bool isStereo();
|
||||
DivMacroInt* getChanMacroInt(int ch);
|
||||
void setFlags(unsigned int flags);
|
||||
void notifyInsChange(int ins);
|
||||
void notifyWaveChange(int wave);
|
||||
void notifyInsDeletion(void* ins);
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -27,11 +27,17 @@
|
|||
#include <sys/select.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#ifdef HAVE_LINUX_INPUT
|
||||
#include <linux/input.h>
|
||||
#endif
|
||||
#ifdef HAVE_LINUX_KD
|
||||
#include <linux/kd.h>
|
||||
#endif
|
||||
#include <time.h>
|
||||
#ifdef HAVE_SYS_IO
|
||||
#include <sys/io.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#define PCSPKR_DIVIDER 4
|
||||
#define CHIP_DIVIDER 1
|
||||
|
|
@ -80,6 +86,7 @@ void DivPlatformPCSpeaker::pcSpeakerThread() {
|
|||
}
|
||||
if (beepFD>=0) {
|
||||
switch (realOutMethod) {
|
||||
#ifdef HAVE_LINUX_INPUT
|
||||
case 0: { // evdev
|
||||
static struct input_event ie;
|
||||
ie.time.tv_sec=r.tv_sec;
|
||||
|
|
@ -98,11 +105,14 @@ void DivPlatformPCSpeaker::pcSpeakerThread() {
|
|||
}
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
#ifdef HAVE_LINUX_KD
|
||||
case 1: // KIOCSOUND (on tty)
|
||||
if (ioctl(beepFD,KIOCSOUND,r.val)<0) {
|
||||
logW("ioctl error! %s",strerror(errno));
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
case 2: { // /dev/port
|
||||
unsigned char bOut;
|
||||
bOut=0;
|
||||
|
|
@ -144,11 +154,14 @@ void DivPlatformPCSpeaker::pcSpeakerThread() {
|
|||
}
|
||||
break;
|
||||
}
|
||||
#ifdef HAVE_LINUX_KD
|
||||
case 3: // KIOCSOUND (on stdout)
|
||||
if (ioctl(beepFD,KIOCSOUND,r.val)<0) {
|
||||
logW("ioctl error! %s",strerror(errno));
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#ifdef HAVE_SYS_IO
|
||||
case 4: // outb()
|
||||
if (r.val==0) {
|
||||
outb(inb(0x61)&(~3),0x61);
|
||||
|
|
@ -163,6 +176,7 @@ void DivPlatformPCSpeaker::pcSpeakerThread() {
|
|||
}
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
} else {
|
||||
//logV("not writing because fd is less than 0");
|
||||
|
|
@ -176,10 +190,6 @@ const char** DivPlatformPCSpeaker::getRegisterSheet() {
|
|||
return regCheatSheetPCSpeaker;
|
||||
}
|
||||
|
||||
const char* DivPlatformPCSpeaker::getEffectName(unsigned char effect) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const float cut=0.05;
|
||||
const float reso=0.06;
|
||||
|
||||
|
|
@ -337,18 +347,9 @@ void DivPlatformPCSpeaker::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp.val);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp.val);
|
||||
}
|
||||
chan[i].baseFreq=NOTE_PERIODIC(parent->calcArp(chan[i].note,chan[i].std.arp.val));
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.pitch.had) {
|
||||
if (chan[i].std.pitch.mode) {
|
||||
|
|
@ -457,6 +458,7 @@ int DivPlatformPCSpeaker::dispatch(DivCommand c) {
|
|||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_BEEPER));
|
||||
}
|
||||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note);
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_GET_VOLMAX:
|
||||
|
|
@ -544,6 +546,7 @@ void DivPlatformPCSpeaker::reset() {
|
|||
break;
|
||||
case 4: // outb()
|
||||
beepFD=-1;
|
||||
#ifdef HAVE_SYS_IO
|
||||
if (ioperm(0x61,8,1)<0) {
|
||||
logW("ioperm 0x61: %s",strerror(errno));
|
||||
break;
|
||||
|
|
@ -557,6 +560,9 @@ void DivPlatformPCSpeaker::reset() {
|
|||
break;
|
||||
}
|
||||
beepFD=STDOUT_FILENO;
|
||||
#else
|
||||
errno=ENOSYS;
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
if (beepFD<0) {
|
||||
|
|
|
|||
|
|
@ -113,7 +113,6 @@ class DivPlatformPCSpeaker: public DivDispatch {
|
|||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
~DivPlatformPCSpeaker();
|
||||
|
|
|
|||
|
|
@ -21,15 +21,15 @@
|
|||
#include "../engine.h"
|
||||
#include <math.h>
|
||||
|
||||
#define rWrite(a,v) {regPool[(a)]=(v)&0xff; if((a)==10) {chan.sreg=(v); chan.cnt=2;}}
|
||||
|
||||
#define CHIP_DIVIDER 16
|
||||
#define SAMP_DIVIDER 4
|
||||
|
||||
const char* regCheatSheet6522[]={
|
||||
"T2L", "08",
|
||||
"T2H", "09",
|
||||
"SR", "0A",
|
||||
"ACR", "0B",
|
||||
"PCR", "0C",
|
||||
NULL
|
||||
};
|
||||
|
||||
|
|
@ -37,35 +37,45 @@ const char** DivPlatformPET::getRegisterSheet() {
|
|||
return regCheatSheet6522;
|
||||
}
|
||||
|
||||
const char* DivPlatformPET::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x10:
|
||||
return "10xx: Change waveform";
|
||||
// high-level emulation of 6522 shift register and driver software for now
|
||||
void DivPlatformPET::rWrite(unsigned int addr, unsigned char val) {
|
||||
bool hwSROutput=((regPool[11]>>2)&7)==4;
|
||||
switch (addr) {
|
||||
case 9:
|
||||
// simulate phase reset from switching between hw/sw shift registers
|
||||
if ((regPool[9]==0)^(val==0)) {
|
||||
chan.sreg=chan.wave;
|
||||
}
|
||||
break;
|
||||
case 10:
|
||||
chan.sreg=val;
|
||||
if (hwSROutput) chan.cnt=2;
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
regPool[addr]=val;
|
||||
}
|
||||
|
||||
void DivPlatformPET::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
// high-level emulation of 6522 shift register for now
|
||||
int t2=regPool[8]*2+4;
|
||||
if (((regPool[11]>>2)&7)==4) {
|
||||
bool hwSROutput=((regPool[11]>>2)&7)==4;
|
||||
if (chan.enable) {
|
||||
int reload=regPool[8]*2+4;
|
||||
if (!hwSROutput) {
|
||||
reload+=regPool[9]*512;
|
||||
}
|
||||
for (size_t h=start; h<start+len; h++) {
|
||||
int cycs=SAMP_DIVIDER;
|
||||
while (cycs>0) {
|
||||
int adv=MIN(cycs,chan.cnt);
|
||||
chan.cnt-=adv;
|
||||
cycs-=adv;
|
||||
if (chan.cnt==0) {
|
||||
chan.out=(chan.sreg&1)*32767;
|
||||
chan.sreg=(chan.sreg>>1)|((chan.sreg&1)<<7);
|
||||
chan.cnt=t2;
|
||||
}
|
||||
if (SAMP_DIVIDER>chan.cnt) {
|
||||
chan.out=(chan.sreg&1)*32767;
|
||||
chan.sreg=(chan.sreg>>1)|((chan.sreg&1)<<7);
|
||||
chan.cnt+=reload-SAMP_DIVIDER;
|
||||
} else {
|
||||
chan.cnt-=SAMP_DIVIDER;
|
||||
}
|
||||
bufL[h]=chan.out;
|
||||
bufR[h]=chan.out;
|
||||
oscBuf->data[oscBuf->needle++]=chan.out;
|
||||
}
|
||||
// emulate driver writes to PCR
|
||||
if (!hwSROutput) regPool[12]=chan.out?0xe0:0xc0;
|
||||
} else {
|
||||
chan.out=0;
|
||||
for (size_t h=start; h<start+len; h++) {
|
||||
|
|
@ -78,11 +88,10 @@ void DivPlatformPET::acquire(short* bufL, short* bufR, size_t start, size_t len)
|
|||
|
||||
void DivPlatformPET::writeOutVol() {
|
||||
if (chan.active && !isMuted && chan.outVol>0) {
|
||||
if (regPool[11]!=16) {
|
||||
rWrite(11,16);
|
||||
rWrite(10,chan.wave);
|
||||
}
|
||||
chan.enable=true;
|
||||
rWrite(11,regPool[9]==0?16:0);
|
||||
} else {
|
||||
chan.enable=false;
|
||||
rWrite(11,0);
|
||||
}
|
||||
}
|
||||
|
|
@ -95,18 +104,9 @@ void DivPlatformPET::tick(bool sysTick) {
|
|||
}
|
||||
if (chan.std.arp.had) {
|
||||
if (!chan.inPorta) {
|
||||
if (chan.std.arp.mode) {
|
||||
chan.baseFreq=NOTE_PERIODIC(chan.std.arp.val);
|
||||
} else {
|
||||
chan.baseFreq=NOTE_PERIODIC(chan.note+chan.std.arp.val);
|
||||
}
|
||||
chan.baseFreq=NOTE_PERIODIC(parent->calcArp(chan.note,chan.std.arp.val));
|
||||
}
|
||||
chan.freqChanged=true;
|
||||
} else {
|
||||
if (chan.std.arp.mode && chan.std.arp.finished) {
|
||||
chan.baseFreq=NOTE_PERIODIC(chan.note);
|
||||
chan.freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan.std.wave.had) {
|
||||
if (chan.wave!=chan.std.wave.val) {
|
||||
|
|
@ -115,24 +115,31 @@ void DivPlatformPET::tick(bool sysTick) {
|
|||
}
|
||||
}
|
||||
if (chan.std.pitch.had) {
|
||||
chan.freqChanged=true;
|
||||
if (chan.std.pitch.mode) {
|
||||
chan.pitch2+=chan.std.pitch.val;
|
||||
CLAMP_VAR(chan.pitch2,-32768,32767);
|
||||
} else {
|
||||
chan.pitch2=chan.std.pitch.val;
|
||||
}
|
||||
chan.freqChanged=true;
|
||||
}
|
||||
if (chan.freqChanged || chan.keyOn || chan.keyOff) {
|
||||
chan.freq=parent->calcFreq(chan.baseFreq,chan.pitch,true,0,chan.pitch2,chipClock,CHIP_DIVIDER);
|
||||
if (chan.freq>257) chan.freq=257;
|
||||
if (chan.freq<2) chan.freq=2;
|
||||
rWrite(8,chan.freq-2);
|
||||
chan.freq=parent->calcFreq(chan.baseFreq,chan.pitch,true,0,chan.pitch2,chipClock,CHIP_DIVIDER)-2;
|
||||
if (chan.freq>65535) chan.freq=65535;
|
||||
if (chan.freq<0) chan.freq=0;
|
||||
rWrite(8,chan.freq&0xff);
|
||||
rWrite(9,chan.freq>>8);
|
||||
if (chan.keyOn) {
|
||||
if (!chan.std.vol.will) {
|
||||
chan.outVol=chan.vol;
|
||||
writeOutVol();
|
||||
}
|
||||
chan.keyOn=false;
|
||||
}
|
||||
if (chan.keyOff) {
|
||||
rWrite(11,0);
|
||||
chan.keyOff=false;
|
||||
}
|
||||
// update mode setting and channel enable
|
||||
writeOutVol();
|
||||
chan.freqChanged=false;
|
||||
}
|
||||
}
|
||||
|
|
@ -220,6 +227,7 @@ int DivPlatformPET::dispatch(DivCommand c) {
|
|||
if (chan.active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan.macroInit(parent->getIns(chan.ins,DIV_INS_PET));
|
||||
}
|
||||
if (!chan.inPorta && c.value && !parent->song.brokenPortaArp && chan.std.arp.will) chan.baseFreq=NOTE_PERIODIC(chan.note);
|
||||
chan.inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_GET_VOLMAX:
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
class DivPlatformPET: public DivDispatch {
|
||||
struct Channel {
|
||||
int freq, baseFreq, pitch, pitch2, note, ins;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, enable;
|
||||
int vol, outVol, wave;
|
||||
unsigned char sreg;
|
||||
int cnt;
|
||||
|
|
@ -49,6 +49,7 @@ class DivPlatformPET: public DivDispatch {
|
|||
keyOn(false),
|
||||
keyOff(false),
|
||||
inPorta(false),
|
||||
enable(false),
|
||||
vol(1),
|
||||
outVol(1),
|
||||
wave(0b00001111),
|
||||
|
|
@ -79,12 +80,12 @@ class DivPlatformPET: public DivDispatch {
|
|||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
~DivPlatformPET();
|
||||
private:
|
||||
void writeOutVol();
|
||||
void rWrite(unsigned int addr, unsigned char val);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -249,24 +249,6 @@ const char** DivPlatformQSound::getRegisterSheet() {
|
|||
return regCheatSheetQSound;
|
||||
}
|
||||
|
||||
const char* DivPlatformQSound::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x10:
|
||||
return "10xx: Set echo feedback level (00 to FF)";
|
||||
break;
|
||||
case 0x11:
|
||||
return "11xx: Set channel echo level (00 to FF)";
|
||||
break;
|
||||
case 0x12:
|
||||
return "12xx: Toggle QSound algorithm (0: disabled; 1: enabled)";
|
||||
break;
|
||||
default:
|
||||
if ((effect & 0xf0) == 0x30) {
|
||||
return "3xxx: Set echo delay buffer length (000 to AA5)";
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
void DivPlatformQSound::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
for (size_t h=start; h<start+len; h++) {
|
||||
qsound_update(&chip);
|
||||
|
|
@ -301,7 +283,7 @@ void DivPlatformQSound::tick(bool sysTick) {
|
|||
qsound_bank = 0x8000 | (s->offQSound >> 16);
|
||||
qsound_addr = s->offQSound & 0xffff;
|
||||
|
||||
int length = s->samples;
|
||||
int length = s->getEndPosition();
|
||||
if (length > 65536 - 16) {
|
||||
length = 65536 - 16;
|
||||
}
|
||||
|
|
@ -315,18 +297,9 @@ void DivPlatformQSound::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=QS_NOTE_FREQUENCY(chan[i].std.arp.val);
|
||||
} else {
|
||||
chan[i].baseFreq=QS_NOTE_FREQUENCY(chan[i].note+chan[i].std.arp.val);
|
||||
}
|
||||
chan[i].baseFreq=QS_NOTE_FREQUENCY(parent->calcArp(chan[i].note,chan[i].std.arp.val));
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=QS_NOTE_FREQUENCY(chan[i].note);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.pitch.had) {
|
||||
if (chan[i].std.pitch.mode) {
|
||||
|
|
@ -358,7 +331,7 @@ void DivPlatformQSound::tick(bool sysTick) {
|
|||
}
|
||||
}
|
||||
chan[i].freq=off*parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,440.0,4096.0);
|
||||
if (chan[i].freq>0xffff) chan[i].freq=0xffff;
|
||||
if (chan[i].freq>0xefff) chan[i].freq=0xefff;
|
||||
if (chan[i].keyOn) {
|
||||
rWrite(q1_reg_map[Q1V_BANK][i], qsound_bank);
|
||||
rWrite(q1_reg_map[Q1V_END][i], qsound_end);
|
||||
|
|
@ -496,6 +469,7 @@ int DivPlatformQSound::dispatch(DivCommand c) {
|
|||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA));
|
||||
}
|
||||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=QS_NOTE_FREQUENCY(chan[c.chan].note);
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_GET_VOLMAX:
|
||||
|
|
|
|||
|
|
@ -96,7 +96,6 @@ class DivPlatformQSound: public DivDispatch {
|
|||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
const void* getSampleMem(int index = 0);
|
||||
size_t getSampleMemCapacity(int index = 0);
|
||||
size_t getSampleMemUsage(int index = 0);
|
||||
|
|
|
|||
|
|
@ -43,10 +43,6 @@ const char** DivPlatformRF5C68::getRegisterSheet() {
|
|||
return regCheatSheetRF5C68;
|
||||
}
|
||||
|
||||
const char* DivPlatformRF5C68::getEffectName(unsigned char effect) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DivPlatformRF5C68::chWrite(unsigned char ch, unsigned int addr, unsigned char val) {
|
||||
if (!skipRegisterWrites) {
|
||||
if (curChan!=ch) {
|
||||
|
|
@ -88,18 +84,9 @@ void DivPlatformRF5C68::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+chan[i].std.arp.val);
|
||||
}
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(parent->calcArp(chan[i].note,chan[i].std.arp.val));
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.pitch.had) {
|
||||
if (chan[i].std.pitch.mode) {
|
||||
|
|
@ -120,7 +107,7 @@ void DivPlatformRF5C68::tick(bool sysTick) {
|
|||
chan[i].panning|=(chan[i].std.panR.val&15)<<4;
|
||||
}
|
||||
if (chan[i].std.panL.had || chan[i].std.panR.had) {
|
||||
chWrite(i,0x05,isMuted[i]?0:chan[i].panning);
|
||||
chWrite(i,1,isMuted[i]?0:chan[i].panning);
|
||||
}
|
||||
if (chan[i].setPos) {
|
||||
// force keyon
|
||||
|
|
@ -142,7 +129,7 @@ void DivPlatformRF5C68::tick(bool sysTick) {
|
|||
if (chan[i].audPos>0) {
|
||||
start=start+MIN(chan[i].audPos,s->length8);
|
||||
}
|
||||
if (s->loopStart>=0) {
|
||||
if (s->isLoopable()) {
|
||||
loop=start+s->loopStart;
|
||||
}
|
||||
start=MIN(start,getSampleMemCapacity()-31);
|
||||
|
|
@ -265,6 +252,7 @@ int DivPlatformRF5C68::dispatch(DivCommand c) {
|
|||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA));
|
||||
}
|
||||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_FREQUENCY(chan[c.chan].note);
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_SAMPLE_POS:
|
||||
|
|
@ -392,7 +380,7 @@ void DivPlatformRF5C68::renderSamples() {
|
|||
size_t memPos=0;
|
||||
for (int i=0; i<parent->song.sampleLen; i++) {
|
||||
DivSample* s=parent->song.sample[i];
|
||||
int length=s->length8;
|
||||
int length=s->getEndPosition(DIV_SAMPLE_DEPTH_8BIT);
|
||||
int actualLength=MIN((int)(getSampleMemCapacity()-memPos)-31,length);
|
||||
if (actualLength>0) {
|
||||
s->offRF5C68=memPos;
|
||||
|
|
|
|||
|
|
@ -92,7 +92,6 @@ class DivPlatformRF5C68: public DivDispatch {
|
|||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
const void* getSampleMem(int index = 0);
|
||||
size_t getSampleMemCapacity(int index = 0);
|
||||
size_t getSampleMemUsage(int index = 0);
|
||||
|
|
|
|||
|
|
@ -56,21 +56,6 @@ const char** DivPlatformSAA1099::getRegisterSheet() {
|
|||
return regCheatSheetSAA;
|
||||
}
|
||||
|
||||
const char* DivPlatformSAA1099::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x10:
|
||||
return "10xy: Set channel mode (x: noise; y: tone)";
|
||||
break;
|
||||
case 0x11:
|
||||
return "11xx: Set noise frequency";
|
||||
break;
|
||||
case 0x12:
|
||||
return "12xx: Setup envelope (refer to docs for more information)";
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DivPlatformSAA1099::acquire_saaSound(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
if (saaBufLen<len*2) {
|
||||
saaBufLen=len*2;
|
||||
|
|
@ -114,18 +99,9 @@ void DivPlatformSAA1099::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp.val);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp.val);
|
||||
}
|
||||
chan[i].baseFreq=NOTE_PERIODIC(parent->calcArp(chan[i].note,chan[i].std.arp.val));
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.duty.had) {
|
||||
saaNoise[i/3]=chan[i].std.duty.val&3;
|
||||
|
|
@ -335,6 +311,7 @@ int DivPlatformSAA1099::dispatch(DivCommand c) {
|
|||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_SAA1099));
|
||||
}
|
||||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note);
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_PRE_NOTE:
|
||||
|
|
|
|||
|
|
@ -96,7 +96,6 @@ class DivPlatformSAA1099: public DivDispatch {
|
|||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -80,15 +80,6 @@ const char** DivPlatformSCC::getRegisterSheet() {
|
|||
return isPlus ? regCheatSheetSCCPlus : regCheatSheetSCC;
|
||||
}
|
||||
|
||||
const char* DivPlatformSCC::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x10:
|
||||
return "10xx: Change waveform";
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DivPlatformSCC::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
for (size_t h=start; h<start+len; h++) {
|
||||
for (int i=0; i<16; i++) {
|
||||
|
|
@ -124,18 +115,9 @@ void DivPlatformSCC::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp.val);
|
||||
} else {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp.val);
|
||||
}
|
||||
chan[i].baseFreq=NOTE_PERIODIC(parent->calcArp(chan[i].note,chan[i].std.arp.val));
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.wave.had) {
|
||||
if (chan[i].wave!=chan[i].std.wave.val || chan[i].ws.activeChanged()) {
|
||||
|
|
@ -267,6 +249,7 @@ int DivPlatformSCC::dispatch(DivCommand c) {
|
|||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_SCC));
|
||||
}
|
||||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note);
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_GET_VOLMAX:
|
||||
|
|
@ -377,6 +360,27 @@ void DivPlatformSCC::setChipModel(bool isplus) {
|
|||
isPlus=isplus;
|
||||
}
|
||||
|
||||
void DivPlatformSCC::setFlags(unsigned int flags) {
|
||||
switch (flags&0x7f) {
|
||||
case 0x00:
|
||||
chipClock=COLOR_NTSC/2.0;
|
||||
break;
|
||||
case 0x01:
|
||||
chipClock=COLOR_PAL*2.0/5.0;
|
||||
break;
|
||||
case 0x02:
|
||||
chipClock=3000000.0/2.0;
|
||||
break;
|
||||
case 0x03:
|
||||
chipClock=4000000.0/2.0;
|
||||
break;
|
||||
}
|
||||
rate=chipClock/8;
|
||||
for (int i=0; i<5; i++) {
|
||||
oscBuf[i]->rate=rate;
|
||||
}
|
||||
}
|
||||
|
||||
int DivPlatformSCC::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
|
||||
parent=p;
|
||||
dumpWrites=false;
|
||||
|
|
@ -386,11 +390,7 @@ int DivPlatformSCC::init(DivEngine* p, int channels, int sugRate, unsigned int f
|
|||
isMuted[i]=false;
|
||||
oscBuf[i]=new DivDispatchOscBuffer;
|
||||
}
|
||||
chipClock=COLOR_NTSC/2.0;
|
||||
rate=chipClock/8;
|
||||
for (int i=0; i<5; i++) {
|
||||
oscBuf[i]->rate=rate;
|
||||
}
|
||||
setFlags(flags);
|
||||
if (isPlus) {
|
||||
scc=new k052539_scc_core;
|
||||
regBase=0xa0;
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@ class DivPlatformSCC: public DivDispatch {
|
|||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
void setFlags(unsigned int flags);
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void setChipModel(bool isPlus);
|
||||
void quit();
|
||||
|
|
|
|||
|
|
@ -26,15 +26,6 @@
|
|||
//#define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;}
|
||||
//#define immWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} }
|
||||
|
||||
const char* DivPlatformSegaPCM::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x20:
|
||||
return "20xx: Set PCM frequency";
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DivPlatformSegaPCM::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
static int os[2];
|
||||
|
||||
|
|
@ -56,12 +47,10 @@ void DivPlatformSegaPCM::acquire(short* bufL, short* bufR, size_t start, size_t
|
|||
pcmR+=(s->data8[chan[i].pcm.pos>>8]*chan[i].chVolR);
|
||||
}
|
||||
chan[i].pcm.pos+=chan[i].pcm.freq;
|
||||
if (chan[i].pcm.pos>=(s->samples<<8)) {
|
||||
if (s->loopStart>=0 && s->loopStart<(int)s->samples) {
|
||||
chan[i].pcm.pos=s->loopStart<<8;
|
||||
} else {
|
||||
chan[i].pcm.sample=-1;
|
||||
}
|
||||
if (s->isLoopable() && chan[i].pcm.pos>=(s->getEndPosition()<<8)) {
|
||||
chan[i].pcm.pos=s->loopStart<<8;
|
||||
} else if (chan[i].pcm.pos>=(s->samples<<8)) {
|
||||
chan[i].pcm.sample=-1;
|
||||
}
|
||||
} else {
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=0;
|
||||
|
|
@ -99,18 +88,9 @@ void DivPlatformSegaPCM::tick(bool sysTick) {
|
|||
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=(chan[i].std.arp.val<<6);
|
||||
} else {
|
||||
chan[i].baseFreq=((chan[i].note+(signed char)chan[i].std.arp.val)<<6);
|
||||
}
|
||||
chan[i].baseFreq=(parent->calcArp(chan[i].note,chan[i].std.arp.val)<<6);
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=(chan[i].note<<6);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
|
||||
if (chan[i].std.panL.had) {
|
||||
|
|
@ -202,7 +182,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) {
|
|||
chan[c.chan].macroInit(ins);
|
||||
if (dumpWrites) { // Sega PCM writes
|
||||
DivSample* s=parent->getSample(chan[c.chan].pcm.sample);
|
||||
int actualLength=(int)s->length8;
|
||||
int actualLength=(int)(s->getEndPosition(DIV_SAMPLE_DEPTH_8BIT));
|
||||
if (actualLength>0xfeff) actualLength=0xfeff;
|
||||
addWrite(0x10086+(c.chan<<3),3+((s->offSegaPCM>>16)<<3));
|
||||
addWrite(0x10084+(c.chan<<3),(s->offSegaPCM)&0xff);
|
||||
|
|
@ -235,7 +215,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) {
|
|||
chan[c.chan].furnacePCM=false;
|
||||
if (dumpWrites) { // Sega PCM writes
|
||||
DivSample* s=parent->getSample(chan[c.chan].pcm.sample);
|
||||
int actualLength=(int)s->length8;
|
||||
int actualLength=(int)(s->getEndPosition(DIV_SAMPLE_DEPTH_8BIT));
|
||||
if (actualLength>65536) actualLength=65536;
|
||||
addWrite(0x10086+(c.chan<<3),3+((s->offSegaPCM>>16)<<3));
|
||||
addWrite(0x10084+(c.chan<<3),(s->offSegaPCM)&0xff);
|
||||
|
|
@ -365,6 +345,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) {
|
|||
return 127;
|
||||
break;
|
||||
case DIV_CMD_PRE_PORTA:
|
||||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=(chan[c.chan].note<<6);
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_PRE_NOTE:
|
||||
|
|
|
|||
|
|
@ -114,7 +114,6 @@ class DivPlatformSegaPCM: public DivDispatch {
|
|||
bool isStereo();
|
||||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char* getEffectName(unsigned char effect);
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
~DivPlatformSegaPCM();
|
||||
|
|
|
|||
|
|
@ -21,32 +21,34 @@
|
|||
#include "../engine.h"
|
||||
#include <math.h>
|
||||
|
||||
#define rWrite(v) {if (!skipRegisterWrites) {writes.push(v); if (dumpWrites) {addWrite(0x200,v);}}}
|
||||
#define rWrite(a,v) {if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(0x200+a,v);}}}
|
||||
|
||||
const char* regCheatSheetSN[]={
|
||||
"DATA", "0",
|
||||
NULL
|
||||
};
|
||||
|
||||
const char** DivPlatformSMS::getRegisterSheet() {
|
||||
return regCheatSheetSN;
|
||||
}
|
||||
const char* regCheatSheetGG[]={
|
||||
"DATA", "0",
|
||||
"Stereo", "1",
|
||||
NULL
|
||||
};
|
||||
|
||||
const char* DivPlatformSMS::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x20:
|
||||
return "20xy: Set noise mode (x: preset freq/ch3 freq; y: thin pulse/noise)";
|
||||
break;
|
||||
}
|
||||
return NULL;
|
||||
const char** DivPlatformSMS::getRegisterSheet() {
|
||||
return stereo?regCheatSheetGG:regCheatSheetSN;
|
||||
}
|
||||
|
||||
void DivPlatformSMS::acquire_nuked(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
int o=0;
|
||||
int oL=0;
|
||||
int oR=0;
|
||||
for (size_t h=start; h<start+len; h++) {
|
||||
if (!writes.empty()) {
|
||||
unsigned char w=writes.front();
|
||||
YMPSG_Write(&sn_nuked,w);
|
||||
QueuedWrite w=writes.front();
|
||||
if (w.addr==0) {
|
||||
YMPSG_Write(&sn_nuked,w.val);
|
||||
} else if (w.addr==1) {
|
||||
YMPSG_WriteStereo(&sn_nuked,w.val);
|
||||
}
|
||||
writes.pop();
|
||||
}
|
||||
YMPSG_Clock(&sn_nuked);
|
||||
|
|
@ -65,10 +67,13 @@ void DivPlatformSMS::acquire_nuked(short* bufL, short* bufR, size_t start, size_
|
|||
YMPSG_Clock(&sn_nuked);
|
||||
YMPSG_Clock(&sn_nuked);
|
||||
YMPSG_Clock(&sn_nuked);
|
||||
o=YMPSG_GetOutput(&sn_nuked);
|
||||
if (o<-32768) o=-32768;
|
||||
if (o>32767) o=32767;
|
||||
bufL[h]=o;
|
||||
YMPSG_GetOutput(&sn_nuked,&oL,&oR);
|
||||
if (oL<-32768) oL=-32768;
|
||||
if (oL>32767) oL=32767;
|
||||
if (oR<-32768) oR=-32768;
|
||||
if (oR>32767) oR=32767;
|
||||
bufL[h]=oL;
|
||||
bufR[h]=oR;
|
||||
for (int i=0; i<4; i++) {
|
||||
if (isMuted[i]) {
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=0;
|
||||
|
|
@ -81,12 +86,20 @@ void DivPlatformSMS::acquire_nuked(short* bufL, short* bufR, size_t start, size_
|
|||
|
||||
void DivPlatformSMS::acquire_mame(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
while (!writes.empty()) {
|
||||
unsigned char w=writes.front();
|
||||
sn->write(w);
|
||||
QueuedWrite w=writes.front();
|
||||
if (stereo && (w.addr==1))
|
||||
sn->stereo_w(w.val);
|
||||
else if (w.addr==0) {
|
||||
sn->write(w.val);
|
||||
}
|
||||
writes.pop();
|
||||
}
|
||||
for (size_t h=start; h<start+len; h++) {
|
||||
sn->sound_stream_update(bufL+h,1);
|
||||
short* outs[2]={
|
||||
&bufL[h],
|
||||
&bufR[h]
|
||||
};
|
||||
sn->sound_stream_update(outs,1);
|
||||
for (int i=0; i<4; i++) {
|
||||
if (isMuted[i]) {
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=0;
|
||||
|
|
@ -105,42 +118,26 @@ void DivPlatformSMS::acquire(short* bufL, short* bufR, size_t start, size_t len)
|
|||
}
|
||||
}
|
||||
|
||||
int DivPlatformSMS::acquireOne() {
|
||||
short v;
|
||||
sn->sound_stream_update(&v,1);
|
||||
return v;
|
||||
}
|
||||
|
||||
void DivPlatformSMS::tick(bool sysTick) {
|
||||
for (int i=0; i<4; i++) {
|
||||
int CHIP_DIVIDER=64;
|
||||
if (i==3 && isRealSN) CHIP_DIVIDER=60;
|
||||
double CHIP_DIVIDER=toneDivider;
|
||||
if (i==3) CHIP_DIVIDER=noiseDivider;
|
||||
chan[i].std.next();
|
||||
if (chan[i].std.vol.had) {
|
||||
chan[i].outVol=MIN(15,chan[i].std.vol.val)-(15-(chan[i].vol&15));
|
||||
chan[i].outVol=VOL_SCALE_LOG(chan[i].std.vol.val,chan[i].vol,15);
|
||||
if (chan[i].outVol<0) chan[i].outVol=0;
|
||||
// old formula
|
||||
// ((chan[i].vol&15)*MIN(15,chan[i].std.vol.val))>>4;
|
||||
rWrite(0x90|(i<<5)|(isMuted[i]?15:(15-(chan[i].outVol&15))));
|
||||
rWrite(0,0x90|(i<<5)|(isMuted[i]?15:(15-(chan[i].outVol&15))));
|
||||
}
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
if (chan[i].std.arp.mode) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp.val);
|
||||
chan[i].actualNote=chan[i].std.arp.val;
|
||||
} else {
|
||||
// TODO: check whether this weird octave boundary thing applies to other systems as well
|
||||
int areYouSerious=chan[i].note+chan[i].std.arp.val;
|
||||
while (areYouSerious>0x60) areYouSerious-=12;
|
||||
chan[i].baseFreq=NOTE_PERIODIC(areYouSerious);
|
||||
chan[i].actualNote=areYouSerious;
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
} else {
|
||||
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(chan[i].note);
|
||||
chan[i].actualNote=chan[i].note;
|
||||
// TODO: check whether this weird octave boundary thing applies to other systems as well
|
||||
// TODO: add compatibility flag. this is horrible.
|
||||
int areYouSerious=parent->calcArp(chan[i].note,chan[i].std.arp.val);
|
||||
while (areYouSerious>0x60) areYouSerious-=12;
|
||||
chan[i].baseFreq=NOTE_PERIODIC(areYouSerious);
|
||||
chan[i].actualNote=areYouSerious;
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
|
|
@ -160,6 +157,13 @@ void DivPlatformSMS::tick(bool sysTick) {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (stereo) {
|
||||
if (chan[i].std.panL.had) {
|
||||
lastPan&=~(0x11<<i);
|
||||
lastPan|=((chan[i].std.panL.val&1)<<i)|(((chan[i].std.panL.val>>1)&1)<<(i+4));
|
||||
rWrite(1,lastPan);
|
||||
}
|
||||
}
|
||||
if (chan[i].std.pitch.had) {
|
||||
if (chan[i].std.pitch.mode) {
|
||||
chan[i].pitch2+=chan[i].std.pitch.val;
|
||||
|
|
@ -172,12 +176,16 @@ void DivPlatformSMS::tick(bool sysTick) {
|
|||
}
|
||||
for (int i=0; i<3; i++) {
|
||||
if (chan[i].freqChanged) {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,64);
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,toneDivider);
|
||||
if (chan[i].freq>1023) chan[i].freq=1023;
|
||||
if (chan[i].freq<8) chan[i].freq=1;
|
||||
if (parent->song.snNoLowPeriods) {
|
||||
if (chan[i].freq<8) chan[i].freq=1;
|
||||
} else {
|
||||
if (chan[i].freq<0) chan[i].freq=0;
|
||||
}
|
||||
//if (chan[i].actualNote>0x5d) chan[i].freq=0x01;
|
||||
rWrite(0x80|i<<5|(chan[i].freq&15));
|
||||
rWrite(chan[i].freq>>4);
|
||||
rWrite(0,0x80|i<<5|(chan[i].freq&15));
|
||||
rWrite(0,chan[i].freq>>4);
|
||||
// what?
|
||||
/*if (i==2 && snNoiseMode&2) {
|
||||
chan[3].baseFreq=chan[2].baseFreq;
|
||||
|
|
@ -187,41 +195,39 @@ void DivPlatformSMS::tick(bool sysTick) {
|
|||
}
|
||||
}
|
||||
if (chan[3].freqChanged || updateSNMode) {
|
||||
chan[3].freq=parent->calcFreq(chan[3].baseFreq,chan[3].pitch,true,0,chan[3].pitch2,chipClock,isRealSN?60:64);
|
||||
chan[3].freq=parent->calcFreq(chan[3].baseFreq,chan[3].pitch,true,0,chan[3].pitch2,chipClock,noiseDivider);
|
||||
if (chan[3].freq>1023) chan[3].freq=1023;
|
||||
if (chan[3].actualNote>0x5d) chan[3].freq=0x01;
|
||||
if (parent->song.snNoLowPeriods) {
|
||||
if (chan[3].actualNote>0x5d) chan[3].freq=0x01;
|
||||
}
|
||||
if (snNoiseMode&2) { // take period from channel 3
|
||||
if (updateSNMode || resetPhase) {
|
||||
if (snNoiseMode&1) {
|
||||
rWrite(0xe7);
|
||||
rWrite(0,0xe7);
|
||||
} else {
|
||||
rWrite(0xe3);
|
||||
rWrite(0,0xe3);
|
||||
}
|
||||
if (updateSNMode) {
|
||||
rWrite(0xdf);
|
||||
rWrite(0,0xdf);
|
||||
}
|
||||
}
|
||||
|
||||
if (chan[3].freqChanged) {
|
||||
rWrite(0xc0|(chan[3].freq&15));
|
||||
rWrite(chan[3].freq>>4);
|
||||
rWrite(0,0xc0|(chan[3].freq&15));
|
||||
rWrite(0,chan[3].freq>>4);
|
||||
}
|
||||
} else { // 3 fixed values
|
||||
unsigned char value;
|
||||
if (chan[3].std.arp.had) {
|
||||
if (chan[3].std.arp.mode) {
|
||||
value=chan[3].std.arp.val%12;
|
||||
} else {
|
||||
value=(chan[3].note+chan[3].std.arp.val)%12;
|
||||
}
|
||||
} else {
|
||||
value=parent->calcArp(chan[3].note,chan[3].std.arp.val)%12;
|
||||
} else { // pardon?
|
||||
value=chan[3].note%12;
|
||||
}
|
||||
if (value<3) {
|
||||
value=2-value;
|
||||
if (value!=oldValue || updateSNMode || resetPhase) {
|
||||
oldValue=value;
|
||||
rWrite(0xe0|value|((snNoiseMode&1)<<2));
|
||||
rWrite(0,0xe0|value|((snNoiseMode&1)<<2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -231,8 +237,8 @@ void DivPlatformSMS::tick(bool sysTick) {
|
|||
}
|
||||
|
||||
int DivPlatformSMS::dispatch(DivCommand c) {
|
||||
int CHIP_DIVIDER=64;
|
||||
if (c.chan==3 && isRealSN) CHIP_DIVIDER=60;
|
||||
double CHIP_DIVIDER=toneDivider;
|
||||
if (c.chan==3) CHIP_DIVIDER=noiseDivider;
|
||||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON:
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
|
|
@ -242,7 +248,7 @@ int DivPlatformSMS::dispatch(DivCommand c) {
|
|||
chan[c.chan].actualNote=c.value;
|
||||
}
|
||||
chan[c.chan].active=true;
|
||||
rWrite(0x90|c.chan<<5|(isMuted[c.chan]?15:(15-(chan[c.chan].vol&15))));
|
||||
rWrite(0,0x90|c.chan<<5|(isMuted[c.chan]?15:(15-(chan[c.chan].vol&15))));
|
||||
chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_STD));
|
||||
if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) {
|
||||
chan[c.chan].outVol=chan[c.chan].vol;
|
||||
|
|
@ -250,7 +256,7 @@ int DivPlatformSMS::dispatch(DivCommand c) {
|
|||
break;
|
||||
case DIV_CMD_NOTE_OFF:
|
||||
chan[c.chan].active=false;
|
||||
rWrite(0x9f|c.chan<<5);
|
||||
rWrite(0,0x9f|c.chan<<5);
|
||||
chan[c.chan].macroInit(NULL);
|
||||
break;
|
||||
case DIV_CMD_NOTE_OFF_ENV:
|
||||
|
|
@ -267,7 +273,7 @@ int DivPlatformSMS::dispatch(DivCommand c) {
|
|||
if (!chan[c.chan].std.vol.has) {
|
||||
chan[c.chan].outVol=c.value;
|
||||
}
|
||||
if (chan[c.chan].active) rWrite(0x90|c.chan<<5|(isMuted[c.chan]?15:(15-(chan[c.chan].vol&15))));
|
||||
if (chan[c.chan].active) rWrite(0,0x90|c.chan<<5|(isMuted[c.chan]?15:(15-(chan[c.chan].vol&15))));
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_GET_VOLUME:
|
||||
|
|
@ -307,6 +313,19 @@ int DivPlatformSMS::dispatch(DivCommand c) {
|
|||
snNoiseMode=(c.value&1)|((c.value&16)>>3);
|
||||
updateSNMode=true;
|
||||
break;
|
||||
case DIV_CMD_PANNING: {
|
||||
if (stereo) {
|
||||
if (c.chan>3) c.chan=3;
|
||||
lastPan&=~(0x11<<c.chan);
|
||||
int pan=0;
|
||||
if (c.value>0) pan|=0x10;
|
||||
if (c.value2>0) pan|=0x01;
|
||||
if (pan==0) pan=0x11;
|
||||
lastPan|=pan<<c.chan;
|
||||
rWrite(1,lastPan);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_LEGATO:
|
||||
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0)));
|
||||
chan[c.chan].freqChanged=true;
|
||||
|
|
@ -317,9 +336,8 @@ int DivPlatformSMS::dispatch(DivCommand c) {
|
|||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_STD));
|
||||
}
|
||||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note);
|
||||
chan[c.chan].inPorta=c.value;
|
||||
// TODO: pre porta cancel arp compat flag
|
||||
//if (chan[c.chan].inPorta) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note);
|
||||
break;
|
||||
case DIV_CMD_GET_VOLMAX:
|
||||
return 15;
|
||||
|
|
@ -335,7 +353,7 @@ int DivPlatformSMS::dispatch(DivCommand c) {
|
|||
|
||||
void DivPlatformSMS::muteChannel(int ch, bool mute) {
|
||||
isMuted[ch]=mute;
|
||||
if (chan[ch].active) rWrite(0x90|ch<<5|(isMuted[ch]?15:(15-(chan[ch].outVol&15))));
|
||||
if (chan[ch].active) rWrite(0,0x90|ch<<5|(isMuted[ch]?15:(15-(chan[ch].outVol&15))));
|
||||
}
|
||||
|
||||
void DivPlatformSMS::forceIns() {
|
||||
|
|
@ -370,11 +388,19 @@ void DivPlatformSMS::reset() {
|
|||
addWrite(0xffffffff,0);
|
||||
}
|
||||
sn->device_start();
|
||||
YMPSG_Init(&sn_nuked,isRealSN);
|
||||
YMPSG_Init(&sn_nuked,isRealSN,12,isRealSN?13:15,isRealSN?16383:32767);
|
||||
snNoiseMode=3;
|
||||
rWrite(0xe7);
|
||||
rWrite(0,0xe7);
|
||||
updateSNMode=false;
|
||||
oldValue=0xff;
|
||||
lastPan=0xff;
|
||||
if (stereo) {
|
||||
rWrite(1,0xff);
|
||||
}
|
||||
}
|
||||
|
||||
bool DivPlatformSMS::isStereo() {
|
||||
return stereo;
|
||||
}
|
||||
|
||||
bool DivPlatformSMS::keyOffAffectsArp(int ch) {
|
||||
|
|
@ -396,45 +422,109 @@ void DivPlatformSMS::notifyInsDeletion(void* ins) {
|
|||
}
|
||||
|
||||
void DivPlatformSMS::poke(unsigned int addr, unsigned short val) {
|
||||
rWrite(val);
|
||||
rWrite(addr,val);
|
||||
}
|
||||
|
||||
void DivPlatformSMS::poke(std::vector<DivRegWrite>& wlist) {
|
||||
for (DivRegWrite& i: wlist) rWrite(i.val);
|
||||
for (DivRegWrite& i: wlist) rWrite(i.addr,i.val);
|
||||
}
|
||||
|
||||
void DivPlatformSMS::setFlags(unsigned int flags) {
|
||||
if ((flags&3)==3) {
|
||||
chipClock=COLOR_NTSC/2.0;
|
||||
} else if ((flags&3)==2) {
|
||||
chipClock=4000000;
|
||||
} else if ((flags&3)==1) {
|
||||
chipClock=COLOR_PAL*4.0/5.0;
|
||||
} else {
|
||||
chipClock=COLOR_NTSC;
|
||||
switch (flags&0xff03) {
|
||||
default:
|
||||
case 0x0000:
|
||||
chipClock=COLOR_NTSC;
|
||||
break;
|
||||
case 0x0001:
|
||||
chipClock=COLOR_PAL*4.0/5.0;
|
||||
break;
|
||||
case 0x0002:
|
||||
chipClock=4000000;
|
||||
break;
|
||||
case 0x0003:
|
||||
chipClock=COLOR_NTSC/2.0;
|
||||
break;
|
||||
case 0x0100:
|
||||
chipClock=3000000;
|
||||
break;
|
||||
case 0x0101:
|
||||
chipClock=2000000;
|
||||
break;
|
||||
case 0x0102:
|
||||
chipClock=COLOR_NTSC/8.0;
|
||||
break;
|
||||
}
|
||||
resetPhase=!(flags&16);
|
||||
|
||||
divider=16;
|
||||
toneDivider=64.0;
|
||||
noiseDivider=64.0;
|
||||
if (sn!=NULL) delete sn;
|
||||
switch ((flags>>2)&3) {
|
||||
case 1: // TI
|
||||
sn=new sn76496_base_device(0x4000, 0x4000, 0x01, 0x02, true, 1, false, true);
|
||||
isRealSN=true;
|
||||
break;
|
||||
case 2: // TI+Atari
|
||||
sn=new sn76496_base_device(0x4000, 0x0f35, 0x01, 0x02, true, 1, false, true);
|
||||
isRealSN=true;
|
||||
break;
|
||||
case 3: // Game Gear (not fully emulated yet!)
|
||||
sn=new sn76496_base_device(0x8000, 0x8000, 0x01, 0x08, false, 1, false, false);
|
||||
isRealSN=false;
|
||||
break;
|
||||
switch (flags&0xcc) {
|
||||
default: // Sega
|
||||
sn=new sn76496_base_device(0x8000, 0x8000, 0x01, 0x08, false, 1, false, false);
|
||||
case 0x00:
|
||||
sn=new segapsg_device();
|
||||
isRealSN=false;
|
||||
stereo=false;
|
||||
break;
|
||||
case 0x04: // TI SN76489
|
||||
sn=new sn76489_device();
|
||||
isRealSN=true;
|
||||
stereo=false;
|
||||
noiseDivider=60.0; // 64 for match to tone frequency on non-Sega PSG but compatibility
|
||||
break;
|
||||
case 0x08: // TI+Atari
|
||||
sn=new sn76496_base_device(0x4000, 0x0f35, 0x01, 0x02, true, false, 1/*8*/, false, true);
|
||||
isRealSN=true;
|
||||
stereo=false;
|
||||
noiseDivider=60.0;
|
||||
break;
|
||||
case 0x0c: // Game Gear (not fully emulated yet!)
|
||||
sn=new gamegear_device();
|
||||
isRealSN=false;
|
||||
stereo=true;
|
||||
break;
|
||||
case 0x40: // TI SN76489A
|
||||
sn=new sn76489a_device();
|
||||
isRealSN=false; // TODO
|
||||
stereo=false;
|
||||
noiseDivider=60.0;
|
||||
break;
|
||||
case 0x44: // TI SN76496
|
||||
sn=new sn76496_device();
|
||||
isRealSN=false; // TODO
|
||||
stereo=false;
|
||||
noiseDivider=60.0;
|
||||
break;
|
||||
case 0x48: // NCR 8496
|
||||
sn=new ncr8496_device();
|
||||
isRealSN=false;
|
||||
stereo=false;
|
||||
noiseDivider=60.0;
|
||||
break;
|
||||
case 0x4c: // Tandy PSSJ 3-voice sound
|
||||
sn=new pssj3_device();
|
||||
isRealSN=false;
|
||||
stereo=false;
|
||||
noiseDivider=60.0;
|
||||
break;
|
||||
case 0x80: // TI SN94624
|
||||
sn=new sn94624_device();
|
||||
isRealSN=true;
|
||||
stereo=false;
|
||||
divider=2;
|
||||
toneDivider=8.0;
|
||||
noiseDivider=7.5;
|
||||
break;
|
||||
case 0x84: // TI SN76494
|
||||
sn=new sn76494_device();
|
||||
isRealSN=false; // TODO
|
||||
stereo=false;
|
||||
divider=2;
|
||||
toneDivider=8.0;
|
||||
noiseDivider=7.5;
|
||||
break;
|
||||
}
|
||||
rate=chipClock/16;
|
||||
rate=chipClock/divider;
|
||||
for (int i=0; i<4; i++) {
|
||||
oscBuf[i]->rate=rate;
|
||||
}
|
||||
|
|
@ -450,6 +540,7 @@ int DivPlatformSMS::init(DivEngine* p, int channels, int sugRate, unsigned int f
|
|||
skipRegisterWrites=false;
|
||||
resetPhase=false;
|
||||
oldValue=0xff;
|
||||
lastPan=0xff;
|
||||
for (int i=0; i<4; i++) {
|
||||
isMuted[i]=false;
|
||||
oscBuf[i]=new DivDispatchOscBuffer;
|
||||
|
|
|
|||
|
|
@ -58,21 +58,31 @@ class DivPlatformSMS: public DivDispatch {
|
|||
Channel chan[4];
|
||||
DivDispatchOscBuffer* oscBuf[4];
|
||||
bool isMuted[4];
|
||||
unsigned char lastPan;
|
||||
unsigned char oldValue;
|
||||
unsigned char snNoiseMode;
|
||||
int divider=16;
|
||||
double toneDivider=64.0;
|
||||
double noiseDivider=64.0;
|
||||
bool updateSNMode;
|
||||
bool resetPhase;
|
||||
bool isRealSN;
|
||||
bool stereo;
|
||||
bool nuked;
|
||||
sn76496_base_device* sn;
|
||||
ympsg_t sn_nuked;
|
||||
std::queue<unsigned char> writes;
|
||||
struct QueuedWrite {
|
||||
unsigned short addr;
|
||||
unsigned char val;
|
||||
bool addrOrVal;
|
||||
QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {}
|
||||
};
|
||||
std::queue<QueuedWrite> writes;
|
||||
friend void putDispatchChan(void*,int,int);
|
||||
|
||||
void acquire_nuked(short* bufL, short* bufR, size_t start, size_t len);
|
||||
void acquire_mame(short* bufL, short* bufR, size_t start, size_t len);
|
||||
public:
|
||||
int acquireOne();
|
||||
void acquire(short* bufL, short* bufR, size_t start, size_t len);
|
||||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
|
|
@ -82,6 +92,7 @@ class DivPlatformSMS: public DivDispatch {
|
|||
void forceIns();
|
||||
void tick(bool sysTick=true);
|
||||
void muteChannel(int ch, bool mute);
|
||||
bool isStereo();
|
||||
bool keyOffAffectsArp(int ch);
|
||||
bool keyOffAffectsPorta(int ch);
|
||||
int getPortaFloor(int ch);
|
||||
|
|
@ -90,7 +101,6 @@ class DivPlatformSMS: public DivDispatch {
|
|||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
void setNuked(bool value);
|
||||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
|
|
|
|||
|
|
@ -924,6 +924,7 @@ float ay8910_device::mix_3D()
|
|||
indx |= tone_mask | (m_vol_enabled[chan] ? tone_volume(tone) << (chan*5) : 0);
|
||||
}
|
||||
}
|
||||
lastIndx=indx;
|
||||
return m_vol3d_table[indx];
|
||||
}
|
||||
|
||||
|
|
@ -1359,6 +1360,7 @@ unsigned char ay8910_device::ay8910_read_ym()
|
|||
|
||||
void ay8910_device::device_reset()
|
||||
{
|
||||
lastIndx=0;
|
||||
ay8910_reset_ym();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -146,6 +146,8 @@ public:
|
|||
double m_Kn[32];
|
||||
};
|
||||
|
||||
int lastIndx;
|
||||
|
||||
// internal interface for PSG component of YM device
|
||||
// FIXME: these should be private, but vector06 accesses them directly
|
||||
|
||||
|
|
|
|||
|
|
@ -60,6 +60,13 @@ SID::~SID()
|
|||
delete[] fir;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Get DC offset of channel.
|
||||
// ----------------------------------------------------------------------------
|
||||
sound_sample SID::get_dc(int ch) {
|
||||
return voice[ch].getDC();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Mute/unmute channel.
|
||||
// ----------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ public:
|
|||
|
||||
sound_sample last_chan_out[3];
|
||||
|
||||
sound_sample get_dc(int ch);
|
||||
void set_is_muted(int ch, bool val);
|
||||
void set_chip_model(chip_model model);
|
||||
void enable_filter(bool enable);
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ public:
|
|||
// Amplitude modulated waveform output.
|
||||
// Range [-2048*255, 2047*255].
|
||||
RESID_INLINE sound_sample output();
|
||||
RESID_INLINE sound_sample getDC();
|
||||
|
||||
protected:
|
||||
WaveformGenerator wave;
|
||||
|
|
@ -72,6 +73,12 @@ sound_sample Voice::output()
|
|||
return (wave.output() - wave_zero)*envelope.output() + voice_DC;
|
||||
}
|
||||
|
||||
RESID_INLINE
|
||||
sound_sample Voice::getDC()
|
||||
{
|
||||
return voice_DC;
|
||||
}
|
||||
|
||||
#endif // RESID_INLINING || defined(__VOICE_CC__)
|
||||
|
||||
#endif // not __VOICE_H__
|
||||
|
|
|
|||
6
src/engine/platform/sound/c64_fp/AUTHORS
Normal file
6
src/engine/platform/sound/c64_fp/AUTHORS
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
Authors of reSIDfp.
|
||||
|
||||
Dag Lem: Designed and programmed complete emulation engine.
|
||||
Antti S. Lankila: Distortion simulation and calculation of combined waveforms
|
||||
Ken Händel: source code conversion to Java
|
||||
Leandro Nini: port to c++, merge with reSID 1.0
|
||||
339
src/engine/platform/sound/c64_fp/COPYING
Normal file
339
src/engine/platform/sound/c64_fp/COPYING
Normal file
|
|
@ -0,0 +1,339 @@
|
|||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
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.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
||||
123
src/engine/platform/sound/c64_fp/Dac.cpp
Normal file
123
src/engine/platform/sound/c64_fp/Dac.cpp
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2016 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
* Copyright 2004,2010 Dag Lem <resid@nimrod.no>
|
||||
*
|
||||
* 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 "Dac.h"
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
Dac::Dac(unsigned int bits) :
|
||||
dac(new double[bits]),
|
||||
dacLength(bits)
|
||||
{}
|
||||
|
||||
Dac::~Dac()
|
||||
{
|
||||
delete [] dac;
|
||||
}
|
||||
|
||||
double Dac::getOutput(unsigned int input) const
|
||||
{
|
||||
double dacValue = 0.;
|
||||
|
||||
for (unsigned int i = 0; i < dacLength; i++)
|
||||
{
|
||||
if ((input & (1 << i)) != 0)
|
||||
{
|
||||
dacValue += dac[i];
|
||||
}
|
||||
}
|
||||
|
||||
return dacValue;
|
||||
}
|
||||
|
||||
void Dac::kinkedDac(ChipModel chipModel)
|
||||
{
|
||||
const double R_INFINITY = 1e6;
|
||||
|
||||
// Non-linearity parameter, 8580 DACs are perfectly linear
|
||||
const double _2R_div_R = chipModel == MOS6581 ? 2.20 : 2.00;
|
||||
|
||||
// 6581 DACs are not terminated by a 2R resistor
|
||||
const bool term = chipModel == MOS8580;
|
||||
|
||||
// Calculate voltage contribution by each individual bit in the R-2R ladder.
|
||||
for (unsigned int set_bit = 0; set_bit < dacLength; set_bit++)
|
||||
{
|
||||
double Vn = 1.; // Normalized bit voltage.
|
||||
double R = 1.; // Normalized R
|
||||
const double _2R = _2R_div_R * R; // 2R
|
||||
double Rn = term ? // Rn = 2R for correct termination,
|
||||
_2R : R_INFINITY; // INFINITY for missing termination.
|
||||
|
||||
unsigned int bit;
|
||||
|
||||
// Calculate DAC "tail" resistance by repeated parallel substitution.
|
||||
for (bit = 0; bit < set_bit; bit++)
|
||||
{
|
||||
Rn = (Rn == R_INFINITY) ?
|
||||
R + _2R :
|
||||
R + (_2R * Rn) / (_2R + Rn); // R + 2R || Rn
|
||||
}
|
||||
|
||||
// Source transformation for bit voltage.
|
||||
if (Rn == R_INFINITY)
|
||||
{
|
||||
Rn = _2R;
|
||||
}
|
||||
else
|
||||
{
|
||||
Rn = (_2R * Rn) / (_2R + Rn); // 2R || Rn
|
||||
Vn = Vn * Rn / _2R;
|
||||
}
|
||||
|
||||
// Calculate DAC output voltage by repeated source transformation from
|
||||
// the "tail".
|
||||
|
||||
for (++bit; bit < dacLength; bit++)
|
||||
{
|
||||
Rn += R;
|
||||
const double I = Vn / Rn;
|
||||
Rn = (_2R * Rn) / (_2R + Rn); // 2R || Rn
|
||||
Vn = Rn * I;
|
||||
}
|
||||
|
||||
dac[set_bit] = Vn;
|
||||
}
|
||||
|
||||
// Normalize to integerish behavior
|
||||
double Vsum = 0.;
|
||||
|
||||
for (unsigned int i = 0; i < dacLength; i++)
|
||||
{
|
||||
Vsum += dac[i];
|
||||
}
|
||||
|
||||
Vsum /= 1 << dacLength;
|
||||
|
||||
for (unsigned int i = 0; i < dacLength; i++)
|
||||
{
|
||||
dac[i] /= Vsum;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace reSIDfp
|
||||
111
src/engine/platform/sound/c64_fp/Dac.h
Normal file
111
src/engine/platform/sound/c64_fp/Dac.h
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2016 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
* Copyright 2004,2010 Dag Lem <resid@nimrod.no>
|
||||
*
|
||||
* 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 DAC_H
|
||||
#define DAC_H
|
||||
|
||||
#include "siddefs-fp.h"
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
/**
|
||||
* Estimate DAC nonlinearity.
|
||||
* The SID DACs are built up as R-2R ladder as follows:
|
||||
*
|
||||
* n n-1 2 1 0 VGND
|
||||
* | | | | | | Termination
|
||||
* 2R 2R 2R 2R 2R 2R only for
|
||||
* | | | | | | MOS 8580
|
||||
* Vo -o-R-o-R-...-o-R-o-R-- --+
|
||||
*
|
||||
*
|
||||
* All MOS 6581 DACs are missing a termination resistor at bit 0. This causes
|
||||
* pronounced errors for the lower 4 - 5 bits (e.g. the output for bit 0 is
|
||||
* actually equal to the output for bit 1), resulting in DAC discontinuities
|
||||
* for the lower bits.
|
||||
* In addition to this, the 6581 DACs exhibit further severe discontinuities
|
||||
* for higher bits, which may be explained by a less than perfect match between
|
||||
* the R and 2R resistors, or by output impedance in the NMOS transistors
|
||||
* providing the bit voltages. A good approximation of the actual DAC output is
|
||||
* achieved for 2R/R ~ 2.20.
|
||||
*
|
||||
* The MOS 8580 DACs, on the other hand, do not exhibit any discontinuities.
|
||||
* These DACs include the correct termination resistor, and also seem to have
|
||||
* very accurately matched R and 2R resistors (2R/R = 2.00).
|
||||
*
|
||||
* On the 6581 the output of the waveform and envelope DACs go through
|
||||
* a voltage follower built with two NMOS:
|
||||
*
|
||||
* Vdd
|
||||
*
|
||||
* |
|
||||
* |-+
|
||||
* Vin -------| T1 (enhancement-mode)
|
||||
* |-+
|
||||
* |
|
||||
* o-------- Vout
|
||||
* |
|
||||
* |-+
|
||||
* +---| T2 (depletion-mode)
|
||||
* | |-+
|
||||
* | |
|
||||
*
|
||||
* GND GND
|
||||
*/
|
||||
class Dac
|
||||
{
|
||||
private:
|
||||
/// analog values
|
||||
double * const dac;
|
||||
|
||||
/// the dac array length
|
||||
const unsigned int dacLength;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Initialize DAC model.
|
||||
*
|
||||
* @param bits the number of input bits
|
||||
*/
|
||||
Dac(unsigned int bits);
|
||||
~Dac();
|
||||
|
||||
/**
|
||||
* Build DAC model for specific chip.
|
||||
*
|
||||
* @param chipModel 6581 or 8580
|
||||
*/
|
||||
void kinkedDac(ChipModel chipModel);
|
||||
|
||||
/**
|
||||
* Get the Vo output for a given combination of input bits.
|
||||
*
|
||||
* @param input the digital input
|
||||
* @return the analog output value
|
||||
*/
|
||||
double getOutput(unsigned int input) const;
|
||||
};
|
||||
|
||||
} // namespace reSIDfp
|
||||
|
||||
#endif
|
||||
155
src/engine/platform/sound/c64_fp/EnvelopeGenerator.cpp
Normal file
155
src/engine/platform/sound/c64_fp/EnvelopeGenerator.cpp
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2020 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2018 VICE Project
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
* Copyright 2004,2010 Dag Lem <resid@nimrod.no>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#define ENVELOPEGENERATOR_CPP
|
||||
|
||||
#include "EnvelopeGenerator.h"
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
/**
|
||||
* Lookup table to convert from attack, decay, or release value to rate
|
||||
* counter period.
|
||||
*
|
||||
* The rate counter is a 15 bit register which is left shifted each cycle.
|
||||
* When the counter reaches a specific comparison value,
|
||||
* the envelope counter is incremented (attack) or decremented
|
||||
* (decay/release) and the rate counter is resetted.
|
||||
*
|
||||
* see [kevtris.org](http://blog.kevtris.org/?p=13)
|
||||
*/
|
||||
const unsigned int EnvelopeGenerator::adsrtable[16] =
|
||||
{
|
||||
0x007f,
|
||||
0x3000,
|
||||
0x1e00,
|
||||
0x0660,
|
||||
0x0182,
|
||||
0x5573,
|
||||
0x000e,
|
||||
0x3805,
|
||||
0x2424,
|
||||
0x2220,
|
||||
0x090c,
|
||||
0x0ecd,
|
||||
0x010e,
|
||||
0x23f7,
|
||||
0x5237,
|
||||
0x64a8
|
||||
};
|
||||
|
||||
void EnvelopeGenerator::reset()
|
||||
{
|
||||
// counter is not changed on reset
|
||||
envelope_pipeline = 0;
|
||||
|
||||
state_pipeline = 0;
|
||||
|
||||
attack = 0;
|
||||
decay = 0;
|
||||
sustain = 0;
|
||||
release = 0;
|
||||
|
||||
gate = false;
|
||||
|
||||
resetLfsr = true;
|
||||
|
||||
exponential_counter = 0;
|
||||
exponential_counter_period = 1;
|
||||
new_exponential_counter_period = 0;
|
||||
|
||||
state = RELEASE;
|
||||
counter_enabled = true;
|
||||
rate = adsrtable[release];
|
||||
}
|
||||
|
||||
void EnvelopeGenerator::writeCONTROL_REG(unsigned char control)
|
||||
{
|
||||
const bool gate_next = (control & 0x01) != 0;
|
||||
|
||||
if (gate_next != gate)
|
||||
{
|
||||
gate = gate_next;
|
||||
|
||||
// The rate counter is never reset, thus there will be a delay before the
|
||||
// envelope counter starts counting up (attack) or down (release).
|
||||
|
||||
if (gate_next)
|
||||
{
|
||||
// Gate bit on: Start attack, decay, sustain.
|
||||
next_state = ATTACK;
|
||||
state_pipeline = 2;
|
||||
|
||||
if (resetLfsr || (exponential_pipeline == 2))
|
||||
{
|
||||
envelope_pipeline = (exponential_counter_period == 1) || (exponential_pipeline == 2) ? 2 : 4;
|
||||
}
|
||||
else if (exponential_pipeline == 1)
|
||||
{
|
||||
state_pipeline = 3;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Gate bit off: Start release.
|
||||
next_state = RELEASE;
|
||||
state_pipeline = envelope_pipeline > 0 ? 3 : 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void EnvelopeGenerator::writeATTACK_DECAY(unsigned char attack_decay)
|
||||
{
|
||||
attack = (attack_decay >> 4) & 0x0f;
|
||||
decay = attack_decay & 0x0f;
|
||||
|
||||
if (state == ATTACK)
|
||||
{
|
||||
rate = adsrtable[attack];
|
||||
}
|
||||
else if (state == DECAY_SUSTAIN)
|
||||
{
|
||||
rate = adsrtable[decay];
|
||||
}
|
||||
}
|
||||
|
||||
void EnvelopeGenerator::writeSUSTAIN_RELEASE(unsigned char sustain_release)
|
||||
{
|
||||
// From the sustain levels it follows that both the low and high 4 bits
|
||||
// of the envelope counter are compared to the 4-bit sustain value.
|
||||
// This has been verified by sampling ENV3.
|
||||
//
|
||||
// For a detailed description see:
|
||||
// http://ploguechipsounds.blogspot.it/2010/11/new-research-on-sid-adsr.html
|
||||
sustain = (sustain_release & 0xf0) | ((sustain_release >> 4) & 0x0f);
|
||||
|
||||
release = sustain_release & 0x0f;
|
||||
|
||||
if (state == RELEASE)
|
||||
{
|
||||
rate = adsrtable[release];
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace reSIDfp
|
||||
419
src/engine/platform/sound/c64_fp/EnvelopeGenerator.h
Normal file
419
src/engine/platform/sound/c64_fp/EnvelopeGenerator.h
Normal file
|
|
@ -0,0 +1,419 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2022 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2018 VICE Project
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
* Copyright 2004,2010 Dag Lem <resid@nimrod.no>
|
||||
*
|
||||
* 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 ENVELOPEGENERATOR_H
|
||||
#define ENVELOPEGENERATOR_H
|
||||
|
||||
#include "siddefs-fp.h"
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
/**
|
||||
* A 15 bit [LFSR] is used to implement the envelope rates, in effect dividing
|
||||
* the clock to the envelope counter by the currently selected rate period.
|
||||
*
|
||||
* In addition, another 5 bit counter is used to implement the exponential envelope decay,
|
||||
* in effect further dividing the clock to the envelope counter.
|
||||
* The period of this counter is set to 1, 2, 4, 8, 16, 30 at the envelope counter
|
||||
* values 255, 93, 54, 26, 14, 6, respectively.
|
||||
*
|
||||
* [LFSR]: https://en.wikipedia.org/wiki/Linear_feedback_shift_register
|
||||
*/
|
||||
class EnvelopeGenerator
|
||||
{
|
||||
private:
|
||||
/**
|
||||
* The envelope state machine's distinct states. In addition to this,
|
||||
* envelope has a hold mode, which freezes envelope counter to zero.
|
||||
*/
|
||||
enum State
|
||||
{
|
||||
ATTACK, DECAY_SUSTAIN, RELEASE
|
||||
};
|
||||
|
||||
private:
|
||||
/// XOR shift register for ADSR prescaling.
|
||||
unsigned int lfsr;
|
||||
|
||||
/// Comparison value (period) of the rate counter before next event.
|
||||
unsigned int rate;
|
||||
|
||||
/**
|
||||
* During release mode, the SID approximates envelope decay via piecewise
|
||||
* linear decay rate.
|
||||
*/
|
||||
unsigned int exponential_counter;
|
||||
|
||||
/**
|
||||
* Comparison value (period) of the exponential decay counter before next
|
||||
* decrement.
|
||||
*/
|
||||
unsigned int exponential_counter_period;
|
||||
unsigned int new_exponential_counter_period;
|
||||
|
||||
unsigned int state_pipeline;
|
||||
|
||||
///
|
||||
unsigned int envelope_pipeline;
|
||||
|
||||
unsigned int exponential_pipeline;
|
||||
|
||||
/// Current envelope state
|
||||
State state;
|
||||
State next_state;
|
||||
|
||||
/// Whether counter is enabled. Only switching to ATTACK can release envelope.
|
||||
bool counter_enabled;
|
||||
|
||||
/// Gate bit
|
||||
bool gate;
|
||||
|
||||
///
|
||||
bool resetLfsr;
|
||||
|
||||
/// The current digital value of envelope output.
|
||||
unsigned char envelope_counter;
|
||||
|
||||
/// Attack register
|
||||
unsigned char attack;
|
||||
|
||||
/// Decay register
|
||||
unsigned char decay;
|
||||
|
||||
/// Sustain register
|
||||
unsigned char sustain;
|
||||
|
||||
/// Release register
|
||||
unsigned char release;
|
||||
|
||||
/// The ENV3 value, sampled at the first phase of the clock
|
||||
unsigned char env3;
|
||||
|
||||
private:
|
||||
static const unsigned int adsrtable[16];
|
||||
|
||||
private:
|
||||
void set_exponential_counter();
|
||||
|
||||
void state_change();
|
||||
|
||||
public:
|
||||
/**
|
||||
* SID clocking.
|
||||
*/
|
||||
void clock();
|
||||
|
||||
/**
|
||||
* Get the Envelope Generator digital output.
|
||||
*/
|
||||
unsigned int output() const { return envelope_counter; }
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
EnvelopeGenerator() :
|
||||
lfsr(0x7fff),
|
||||
rate(0),
|
||||
exponential_counter(0),
|
||||
exponential_counter_period(1),
|
||||
new_exponential_counter_period(0),
|
||||
state_pipeline(0),
|
||||
envelope_pipeline(0),
|
||||
exponential_pipeline(0),
|
||||
state(RELEASE),
|
||||
next_state(RELEASE),
|
||||
counter_enabled(true),
|
||||
gate(false),
|
||||
resetLfsr(false),
|
||||
envelope_counter(0xaa),
|
||||
attack(0),
|
||||
decay(0),
|
||||
sustain(0),
|
||||
release(0),
|
||||
env3(0)
|
||||
{}
|
||||
|
||||
/**
|
||||
* SID reset.
|
||||
*/
|
||||
void reset();
|
||||
|
||||
/**
|
||||
* Write control register.
|
||||
*
|
||||
* @param control
|
||||
* control register value
|
||||
*/
|
||||
void writeCONTROL_REG(unsigned char control);
|
||||
|
||||
/**
|
||||
* Write Attack/Decay register.
|
||||
*
|
||||
* @param attack_decay
|
||||
* attack/decay value
|
||||
*/
|
||||
void writeATTACK_DECAY(unsigned char attack_decay);
|
||||
|
||||
/**
|
||||
* Write Sustain/Release register.
|
||||
*
|
||||
* @param sustain_release
|
||||
* sustain/release value
|
||||
*/
|
||||
void writeSUSTAIN_RELEASE(unsigned char sustain_release);
|
||||
|
||||
/**
|
||||
* Return the envelope current value.
|
||||
*
|
||||
* @return envelope counter value
|
||||
*/
|
||||
unsigned char readENV() const { return env3; }
|
||||
};
|
||||
|
||||
} // namespace reSIDfp
|
||||
|
||||
#if RESID_INLINING || defined(ENVELOPEGENERATOR_CPP)
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
RESID_INLINE
|
||||
void EnvelopeGenerator::clock()
|
||||
{
|
||||
env3 = envelope_counter;
|
||||
|
||||
if (unlikely(new_exponential_counter_period > 0))
|
||||
{
|
||||
exponential_counter_period = new_exponential_counter_period;
|
||||
new_exponential_counter_period = 0;
|
||||
}
|
||||
|
||||
if (unlikely(state_pipeline))
|
||||
{
|
||||
state_change();
|
||||
}
|
||||
|
||||
if (unlikely(envelope_pipeline != 0) && (--envelope_pipeline == 0))
|
||||
{
|
||||
if (likely(counter_enabled))
|
||||
{
|
||||
if (state == ATTACK)
|
||||
{
|
||||
if (++envelope_counter==0xff)
|
||||
{
|
||||
next_state = DECAY_SUSTAIN;
|
||||
state_pipeline = 3;
|
||||
}
|
||||
}
|
||||
else if ((state == DECAY_SUSTAIN) || (state == RELEASE))
|
||||
{
|
||||
if (--envelope_counter==0x00)
|
||||
{
|
||||
counter_enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
set_exponential_counter();
|
||||
}
|
||||
}
|
||||
else if (unlikely(exponential_pipeline != 0) && (--exponential_pipeline == 0))
|
||||
{
|
||||
exponential_counter = 0;
|
||||
|
||||
if (((state == DECAY_SUSTAIN) && (envelope_counter != sustain))
|
||||
|| (state == RELEASE))
|
||||
{
|
||||
// The envelope counter can flip from 0x00 to 0xff by changing state to
|
||||
// attack, then to release. The envelope counter will then continue
|
||||
// counting down in the release state.
|
||||
// This has been verified by sampling ENV3.
|
||||
|
||||
envelope_pipeline = 1;
|
||||
}
|
||||
}
|
||||
else if (unlikely(resetLfsr))
|
||||
{
|
||||
lfsr = 0x7fff;
|
||||
resetLfsr = false;
|
||||
|
||||
if (state == ATTACK)
|
||||
{
|
||||
// The first envelope step in the attack state also resets the exponential
|
||||
// counter. This has been verified by sampling ENV3.
|
||||
exponential_counter = 0; // NOTE this is actually delayed one cycle, not modeled
|
||||
|
||||
// The envelope counter can flip from 0xff to 0x00 by changing state to
|
||||
// release, then to attack. The envelope counter is then frozen at
|
||||
// zero; to unlock this situation the state must be changed to release,
|
||||
// then to attack. This has been verified by sampling ENV3.
|
||||
|
||||
envelope_pipeline = 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (counter_enabled && (++exponential_counter == exponential_counter_period))
|
||||
exponential_pipeline = exponential_counter_period != 1 ? 2 : 1;
|
||||
}
|
||||
}
|
||||
|
||||
// ADSR delay bug.
|
||||
// If the rate counter comparison value is set below the current value of the
|
||||
// rate counter, the counter will continue counting up until it wraps around
|
||||
// to zero at 2^15 = 0x8000, and then count rate_period - 1 before the
|
||||
// envelope can constly be stepped.
|
||||
// This has been verified by sampling ENV3.
|
||||
|
||||
// check to see if LFSR matches table value
|
||||
if (likely(lfsr != rate))
|
||||
{
|
||||
// it wasn't a match, clock the LFSR once
|
||||
// by performing XOR on last 2 bits
|
||||
const unsigned int feedback = ((lfsr << 14) ^ (lfsr << 13)) & 0x4000;
|
||||
lfsr = (lfsr >> 1) | feedback;
|
||||
}
|
||||
else
|
||||
{
|
||||
resetLfsr = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is what happens on chip during state switching,
|
||||
* based on die reverse engineering and transistor level
|
||||
* emulation.
|
||||
*
|
||||
* Attack
|
||||
*
|
||||
* 0 - Gate on
|
||||
* 1 - Counting direction changes
|
||||
* During this cycle the decay rate is "accidentally" activated
|
||||
* 2 - Counter is being inverted
|
||||
* Now the attack rate is correctly activated
|
||||
* Counter is enabled
|
||||
* 3 - Counter will be counting upward from now on
|
||||
*
|
||||
* Decay
|
||||
*
|
||||
* 0 - Counter == $ff
|
||||
* 1 - Counting direction changes
|
||||
* The attack state is still active
|
||||
* 2 - Counter is being inverted
|
||||
* During this cycle the decay state is activated
|
||||
* 3 - Counter will be counting downward from now on
|
||||
*
|
||||
* Release
|
||||
*
|
||||
* 0 - Gate off
|
||||
* 1 - During this cycle the release state is activated if coming from sustain/decay
|
||||
* *2 - Counter is being inverted, the release state is activated
|
||||
* *3 - Counter will be counting downward from now on
|
||||
*
|
||||
* (* only if coming directly from Attack state)
|
||||
*
|
||||
* Freeze
|
||||
*
|
||||
* 0 - Counter == $00
|
||||
* 1 - Nothing
|
||||
* 2 - Counter is disabled
|
||||
*/
|
||||
RESID_INLINE
|
||||
void EnvelopeGenerator::state_change()
|
||||
{
|
||||
state_pipeline--;
|
||||
|
||||
switch (next_state)
|
||||
{
|
||||
case ATTACK:
|
||||
if (state_pipeline == 1)
|
||||
{
|
||||
// The decay rate is "accidentally" enabled during first cycle of attack phase
|
||||
rate = adsrtable[decay];
|
||||
}
|
||||
else if (state_pipeline == 0)
|
||||
{
|
||||
state = ATTACK;
|
||||
// The attack rate is correctly enabled during second cycle of attack phase
|
||||
rate = adsrtable[attack];
|
||||
counter_enabled = true;
|
||||
}
|
||||
break;
|
||||
case DECAY_SUSTAIN:
|
||||
if (state_pipeline == 0)
|
||||
{
|
||||
state = DECAY_SUSTAIN;
|
||||
rate = adsrtable[decay];
|
||||
}
|
||||
break;
|
||||
case RELEASE:
|
||||
if (((state == ATTACK) && (state_pipeline == 0))
|
||||
|| ((state == DECAY_SUSTAIN) && (state_pipeline == 1)))
|
||||
{
|
||||
state = RELEASE;
|
||||
rate = adsrtable[release];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
RESID_INLINE
|
||||
void EnvelopeGenerator::set_exponential_counter()
|
||||
{
|
||||
// Check for change of exponential counter period.
|
||||
//
|
||||
// For a detailed description see:
|
||||
// http://ploguechipsounds.blogspot.it/2010/03/sid-6581r3-adsr-tables-up-close.html
|
||||
switch (envelope_counter)
|
||||
{
|
||||
case 0xff:
|
||||
case 0x00:
|
||||
new_exponential_counter_period = 1;
|
||||
break;
|
||||
|
||||
case 0x5d:
|
||||
new_exponential_counter_period = 2;
|
||||
break;
|
||||
|
||||
case 0x36:
|
||||
new_exponential_counter_period = 4;
|
||||
break;
|
||||
|
||||
case 0x1a:
|
||||
new_exponential_counter_period = 8;
|
||||
break;
|
||||
|
||||
case 0x0e:
|
||||
new_exponential_counter_period = 16;
|
||||
break;
|
||||
|
||||
case 0x06:
|
||||
new_exponential_counter_period = 30;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace reSIDfp
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
68
src/engine/platform/sound/c64_fp/ExternalFilter.cpp
Normal file
68
src/engine/platform/sound/c64_fp/ExternalFilter.cpp
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2020 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
* Copyright 2004 Dag Lem <resid@nimrod.no>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#define EXTERNALFILTER_CPP
|
||||
|
||||
#include "ExternalFilter.h"
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
/**
|
||||
* Get the 3 dB attenuation point.
|
||||
*
|
||||
* @param res the resistance value in Ohms
|
||||
* @param cap the capacitance value in Farads
|
||||
*/
|
||||
inline double getRC(double res, double cap)
|
||||
{
|
||||
return res * cap;
|
||||
}
|
||||
|
||||
ExternalFilter::ExternalFilter() :
|
||||
w0lp_1_s7(0),
|
||||
w0hp_1_s17(0)
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
void ExternalFilter::setClockFrequency(double frequency)
|
||||
{
|
||||
const double dt = 1. / frequency;
|
||||
|
||||
// Low-pass: R = 10kOhm, C = 1000pF; w0l = dt/(dt+RC) = 1e-6/(1e-6+1e4*1e-9) = 0.091
|
||||
// Cutoff 1/2*PI*RC = 1/2*PI*1e4*1e-9 = 15915.5 Hz
|
||||
w0lp_1_s7 = static_cast<int>((dt / (dt + getRC(10e3, 1000e-12))) * (1 << 7) + 0.5);
|
||||
|
||||
// High-pass: R = 10kOhm, C = 10uF; w0h = dt/(dt+RC) = 1e-6/(1e-6+1e4*1e-5) = 0.00000999
|
||||
// Cutoff 1/2*PI*RC = 1/2*PI*1e4*1e-5 = 1.59155 Hz
|
||||
w0hp_1_s17 = static_cast<int>((dt / (dt + getRC(10e3, 10e-6))) * (1 << 17) + 0.5);
|
||||
}
|
||||
|
||||
void ExternalFilter::reset()
|
||||
{
|
||||
// State of filter.
|
||||
Vlp = 0; //1 << (15 + 11);
|
||||
Vhp = 0;
|
||||
}
|
||||
|
||||
} // namespace reSIDfp
|
||||
125
src/engine/platform/sound/c64_fp/ExternalFilter.h
Normal file
125
src/engine/platform/sound/c64_fp/ExternalFilter.h
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2020 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
* Copyright 2004 Dag Lem <resid@nimrod.no>
|
||||
*
|
||||
* 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 EXTERNALFILTER_H
|
||||
#define EXTERNALFILTER_H
|
||||
|
||||
#include "siddefs-fp.h"
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
/**
|
||||
* The audio output stage in a Commodore 64 consists of two STC networks, a
|
||||
* low-pass RC filter with 3 dB frequency 16kHz followed by a DC-blocker which
|
||||
* acts as a high-pass filter with a cutoff dependent on the attached audio
|
||||
* equipment impedance. Here we suppose an impedance of 10kOhm resulting
|
||||
* in a 3 dB attenuation at 1.6Hz.
|
||||
* To operate properly the 6581 audio output needs a pull-down resistor
|
||||
*(1KOhm recommended, not needed on 8580)
|
||||
*
|
||||
* ~~~
|
||||
* 9/12V
|
||||
* -----+
|
||||
* audio| 10k |
|
||||
* +---o----R---o--------o-----(K) +-----
|
||||
* out | | | | | |audio
|
||||
* -----+ R 1k C 1000 | | 10 uF |
|
||||
* | | pF +-C----o-----C-----+ 10k
|
||||
* 470 | |
|
||||
* GND GND pF R 1K | amp
|
||||
* * * | +-----
|
||||
*
|
||||
* GND
|
||||
* ~~~
|
||||
*
|
||||
* The STC networks are connected with a [BJT] based [common collector]
|
||||
* used as a voltage follower (featuring a 2SC1815 NPN transistor).
|
||||
* * The C64c board additionally includes a [bootstrap] condenser to increase
|
||||
* the input impedance of the common collector.
|
||||
*
|
||||
* [BJT]: https://en.wikipedia.org/wiki/Bipolar_junction_transistor
|
||||
* [common collector]: https://en.wikipedia.org/wiki/Common_collector
|
||||
* [bootstrap]: https://en.wikipedia.org/wiki/Bootstrapping_(electronics)
|
||||
*/
|
||||
class ExternalFilter
|
||||
{
|
||||
private:
|
||||
/// Lowpass filter voltage
|
||||
int Vlp;
|
||||
|
||||
/// Highpass filter voltage
|
||||
int Vhp;
|
||||
|
||||
int w0lp_1_s7;
|
||||
|
||||
int w0hp_1_s17;
|
||||
|
||||
public:
|
||||
/**
|
||||
* SID clocking.
|
||||
*
|
||||
* @param input
|
||||
*/
|
||||
int clock(unsigned short input);
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
ExternalFilter();
|
||||
|
||||
/**
|
||||
* Setup of the external filter sampling parameters.
|
||||
*
|
||||
* @param frequency the main system clock frequency
|
||||
*/
|
||||
void setClockFrequency(double frequency);
|
||||
|
||||
/**
|
||||
* SID reset.
|
||||
*/
|
||||
void reset();
|
||||
};
|
||||
|
||||
} // namespace reSIDfp
|
||||
|
||||
#if RESID_INLINING || defined(EXTERNALFILTER_CPP)
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
RESID_INLINE
|
||||
int ExternalFilter::clock(unsigned short input)
|
||||
{
|
||||
const int Vi = (static_cast<unsigned int>(input)<<11) - (1 << (11+15));
|
||||
const int dVlp = (w0lp_1_s7 * (Vi - Vlp) >> 7);
|
||||
const int dVhp = (w0hp_1_s17 * (Vlp - Vhp) >> 17);
|
||||
Vlp += dVlp;
|
||||
Vhp += dVhp;
|
||||
return (Vlp - Vhp) >> 11;
|
||||
}
|
||||
|
||||
} // namespace reSIDfp
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
90
src/engine/platform/sound/c64_fp/Filter.cpp
Normal file
90
src/engine/platform/sound/c64_fp/Filter.cpp
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2013 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
* Copyright 2004 Dag Lem <resid@nimrod.no>
|
||||
*
|
||||
* 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 "Filter.h"
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
void Filter::enable(bool enable)
|
||||
{
|
||||
enabled = enable;
|
||||
|
||||
if (enabled)
|
||||
{
|
||||
writeRES_FILT(filt);
|
||||
}
|
||||
else
|
||||
{
|
||||
filt1 = filt2 = filt3 = filtE = false;
|
||||
}
|
||||
}
|
||||
|
||||
void Filter::reset()
|
||||
{
|
||||
writeFC_LO(0);
|
||||
writeFC_HI(0);
|
||||
writeMODE_VOL(0);
|
||||
writeRES_FILT(0);
|
||||
}
|
||||
|
||||
void Filter::writeFC_LO(unsigned char fc_lo)
|
||||
{
|
||||
fc = (fc & 0x7f8) | (fc_lo & 0x007);
|
||||
updatedCenterFrequency();
|
||||
}
|
||||
|
||||
void Filter::writeFC_HI(unsigned char fc_hi)
|
||||
{
|
||||
fc = (fc_hi << 3 & 0x7f8) | (fc & 0x007);
|
||||
updatedCenterFrequency();
|
||||
}
|
||||
|
||||
void Filter::writeRES_FILT(unsigned char res_filt)
|
||||
{
|
||||
filt = res_filt;
|
||||
|
||||
updateResonance((res_filt >> 4) & 0x0f);
|
||||
|
||||
if (enabled)
|
||||
{
|
||||
filt1 = (filt & 0x01) != 0;
|
||||
filt2 = (filt & 0x02) != 0;
|
||||
filt3 = (filt & 0x04) != 0;
|
||||
filtE = (filt & 0x08) != 0;
|
||||
}
|
||||
|
||||
updatedMixing();
|
||||
}
|
||||
|
||||
void Filter::writeMODE_VOL(unsigned char mode_vol)
|
||||
{
|
||||
vol = mode_vol & 0x0f;
|
||||
lp = (mode_vol & 0x10) != 0;
|
||||
bp = (mode_vol & 0x20) != 0;
|
||||
hp = (mode_vol & 0x40) != 0;
|
||||
voice3off = (mode_vol & 0x80) != 0;
|
||||
|
||||
updatedMixing();
|
||||
}
|
||||
|
||||
} // namespace reSIDfp
|
||||
177
src/engine/platform/sound/c64_fp/Filter.h
Normal file
177
src/engine/platform/sound/c64_fp/Filter.h
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2017 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
* Copyright 2004 Dag Lem <resid@nimrod.no>
|
||||
*
|
||||
* 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 FILTER_H
|
||||
#define FILTER_H
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
/**
|
||||
* SID filter base class
|
||||
*/
|
||||
class Filter
|
||||
{
|
||||
protected:
|
||||
/// Current volume amplifier setting.
|
||||
unsigned short* currentGain;
|
||||
|
||||
/// Current filter/voice mixer setting.
|
||||
unsigned short* currentMixer;
|
||||
|
||||
/// Filter input summer setting.
|
||||
unsigned short* currentSummer;
|
||||
|
||||
/// Filter resonance value.
|
||||
unsigned short* currentResonance;
|
||||
|
||||
/// Filter highpass state.
|
||||
int Vhp;
|
||||
|
||||
/// Filter bandpass state.
|
||||
int Vbp;
|
||||
|
||||
/// Filter lowpass state.
|
||||
int Vlp;
|
||||
|
||||
/// Filter external input.
|
||||
int ve;
|
||||
|
||||
/// Filter cutoff frequency.
|
||||
unsigned int fc;
|
||||
|
||||
/// Routing to filter or outside filter
|
||||
bool filt1, filt2, filt3, filtE;
|
||||
|
||||
/// Switch voice 3 off.
|
||||
bool voice3off;
|
||||
|
||||
/// Highpass, bandpass, and lowpass filter modes.
|
||||
bool hp, bp, lp;
|
||||
|
||||
/// Current volume.
|
||||
unsigned char vol;
|
||||
|
||||
private:
|
||||
/// Filter enabled.
|
||||
bool enabled;
|
||||
|
||||
/// Selects which inputs to route through filter.
|
||||
unsigned char filt;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Set filter cutoff frequency.
|
||||
*/
|
||||
virtual void updatedCenterFrequency() = 0;
|
||||
|
||||
/**
|
||||
* Set filter resonance.
|
||||
*/
|
||||
virtual void updateResonance(unsigned char res) = 0;
|
||||
|
||||
/**
|
||||
* Mixing configuration modified (offsets change)
|
||||
*/
|
||||
virtual void updatedMixing() = 0;
|
||||
|
||||
public:
|
||||
Filter() :
|
||||
currentGain(nullptr),
|
||||
currentMixer(nullptr),
|
||||
currentSummer(nullptr),
|
||||
currentResonance(nullptr),
|
||||
Vhp(0),
|
||||
Vbp(0),
|
||||
Vlp(0),
|
||||
ve(0),
|
||||
fc(0),
|
||||
filt1(false),
|
||||
filt2(false),
|
||||
filt3(false),
|
||||
filtE(false),
|
||||
voice3off(false),
|
||||
hp(false),
|
||||
bp(false),
|
||||
lp(false),
|
||||
vol(0),
|
||||
enabled(true),
|
||||
filt(0) {}
|
||||
|
||||
virtual ~Filter() {}
|
||||
|
||||
/**
|
||||
* SID clocking - 1 cycle
|
||||
*
|
||||
* @param v1 voice 1 in
|
||||
* @param v2 voice 2 in
|
||||
* @param v3 voice 3 in
|
||||
* @return filtered output
|
||||
*/
|
||||
virtual unsigned short clock(int v1, int v2, int v3) = 0;
|
||||
|
||||
/**
|
||||
* Enable filter.
|
||||
*
|
||||
* @param enable
|
||||
*/
|
||||
void enable(bool enable);
|
||||
|
||||
/**
|
||||
* SID reset.
|
||||
*/
|
||||
void reset();
|
||||
|
||||
/**
|
||||
* Write Frequency Cutoff Low register.
|
||||
*
|
||||
* @param fc_lo Frequency Cutoff Low-Byte
|
||||
*/
|
||||
void writeFC_LO(unsigned char fc_lo);
|
||||
|
||||
/**
|
||||
* Write Frequency Cutoff High register.
|
||||
*
|
||||
* @param fc_hi Frequency Cutoff High-Byte
|
||||
*/
|
||||
void writeFC_HI(unsigned char fc_hi);
|
||||
|
||||
/**
|
||||
* Write Resonance/Filter register.
|
||||
*
|
||||
* @param res_filt Resonance/Filter
|
||||
*/
|
||||
void writeRES_FILT(unsigned char res_filt);
|
||||
|
||||
/**
|
||||
* Write filter Mode/Volume register.
|
||||
*
|
||||
* @param mode_vol Filter Mode/Volume
|
||||
*/
|
||||
void writeMODE_VOL(unsigned char mode_vol);
|
||||
|
||||
virtual void input(int input) = 0;
|
||||
};
|
||||
|
||||
} // namespace reSIDfp
|
||||
|
||||
#endif
|
||||
75
src/engine/platform/sound/c64_fp/Filter6581.cpp
Normal file
75
src/engine/platform/sound/c64_fp/Filter6581.cpp
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2015 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
* Copyright 2004,2010 Dag Lem <resid@nimrod.no>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#define FILTER6581_CPP
|
||||
|
||||
#include "Filter6581.h"
|
||||
|
||||
#include "Integrator6581.h"
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
Filter6581::~Filter6581()
|
||||
{
|
||||
delete [] f0_dac;
|
||||
}
|
||||
|
||||
void Filter6581::updatedCenterFrequency()
|
||||
{
|
||||
const unsigned short Vw = f0_dac[fc];
|
||||
hpIntegrator->setVw(Vw);
|
||||
bpIntegrator->setVw(Vw);
|
||||
}
|
||||
|
||||
void Filter6581::updatedMixing()
|
||||
{
|
||||
currentGain = gain_vol[vol];
|
||||
|
||||
unsigned int ni = 0;
|
||||
unsigned int no = 0;
|
||||
|
||||
(filt1 ? ni : no)++;
|
||||
(filt2 ? ni : no)++;
|
||||
|
||||
if (filt3) ni++;
|
||||
else if (!voice3off) no++;
|
||||
|
||||
(filtE ? ni : no)++;
|
||||
|
||||
currentSummer = summer[ni];
|
||||
|
||||
if (lp) no++;
|
||||
if (bp) no++;
|
||||
if (hp) no++;
|
||||
|
||||
currentMixer = mixer[no];
|
||||
}
|
||||
|
||||
void Filter6581::setFilterCurve(double curvePosition)
|
||||
{
|
||||
delete [] f0_dac;
|
||||
f0_dac = FilterModelConfig6581::getInstance()->getDAC(curvePosition);
|
||||
updatedCenterFrequency();
|
||||
}
|
||||
|
||||
} // namespace reSIDfp
|
||||
425
src/engine/platform/sound/c64_fp/Filter6581.h
Normal file
425
src/engine/platform/sound/c64_fp/Filter6581.h
Normal file
|
|
@ -0,0 +1,425 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2022 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
* Copyright 2004,2010 Dag Lem <resid@nimrod.no>
|
||||
*
|
||||
* 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 FILTER6581_H
|
||||
#define FILTER6581_H
|
||||
|
||||
#include "siddefs-fp.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "Filter.h"
|
||||
#include "FilterModelConfig6581.h"
|
||||
|
||||
#include "sidcxx11.h"
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
class Integrator6581;
|
||||
|
||||
/**
|
||||
* The SID filter is modeled with a two-integrator-loop biquadratic filter,
|
||||
* which has been confirmed by Bob Yannes to be the actual circuit used in
|
||||
* the SID chip.
|
||||
*
|
||||
* Measurements show that excellent emulation of the SID filter is achieved,
|
||||
* except when high resonance is combined with high sustain levels.
|
||||
* In this case the SID op-amps are performing less than ideally and are
|
||||
* causing some peculiar behavior of the SID filter. This however seems to
|
||||
* have more effect on the overall amplitude than on the color of the sound.
|
||||
*
|
||||
* The theory for the filter circuit can be found in "Microelectric Circuits"
|
||||
* by Adel S. Sedra and Kenneth C. Smith.
|
||||
* The circuit is modeled based on the explanation found there except that
|
||||
* an additional inverter is used in the feedback from the bandpass output,
|
||||
* allowing the summer op-amp to operate in single-ended mode. This yields
|
||||
* filter outputs with levels independent of Q, which corresponds with the
|
||||
* results obtained from a real SID.
|
||||
*
|
||||
* We have been able to model the summer and the two integrators of the circuit
|
||||
* to form components of an IIR filter.
|
||||
* Vhp is the output of the summer, Vbp is the output of the first integrator,
|
||||
* and Vlp is the output of the second integrator in the filter circuit.
|
||||
*
|
||||
* According to Bob Yannes, the active stages of the SID filter are not really
|
||||
* op-amps. Rather, simple NMOS inverters are used. By biasing an inverter
|
||||
* into its region of quasi-linear operation using a feedback resistor from
|
||||
* input to output, a MOS inverter can be made to act like an op-amp for
|
||||
* small signals centered around the switching threshold.
|
||||
*
|
||||
* In 2008, Michael Huth facilitated closer investigation of the SID 6581
|
||||
* filter circuit by publishing high quality microscope photographs of the die.
|
||||
* Tommi Lempinen has done an impressive work on re-vectorizing and annotating
|
||||
* the die photographs, substantially simplifying further analysis of the
|
||||
* filter circuit.
|
||||
*
|
||||
* The filter schematics below are reverse engineered from these re-vectorized
|
||||
* and annotated die photographs. While the filter first depicted in reSID 0.9
|
||||
* is a correct model of the basic filter, the schematics are now completed
|
||||
* with the audio mixer and output stage, including details on intended
|
||||
* relative resistor values. Also included are schematics for the NMOS FET
|
||||
* voltage controlled resistors (VCRs) used to control cutoff frequency, the
|
||||
* DAC which controls the VCRs, the NMOS op-amps, and the output buffer.
|
||||
*
|
||||
*
|
||||
* SID filter / mixer / output
|
||||
* ---------------------------
|
||||
* ~~~
|
||||
* +---------------------------------------------------+
|
||||
* | |
|
||||
* | +--1R1-- \--+ D7 |
|
||||
* | +---R1--+ | | |
|
||||
* | | | o--2R1-- \--o D6 |
|
||||
* | +---------o--<A]--o--o | $17 |
|
||||
* | | o--4R1-- \--o D5 1=open | (3.5R1)
|
||||
* | | | | |
|
||||
* | | +--8R1-- \--o D4 | (7.0R1)
|
||||
* | | | |
|
||||
* $17 | | (CAP2B) | (CAP1B) |
|
||||
* 0=to mixer | +--R8--+ +---R8--+ +---C---o +---C---o
|
||||
* 1=to filter | | | | | | | |
|
||||
* ------R8--o--o--[A>--o--Rw--o--[A>--o--Rw--o--[A>--o
|
||||
* ve (EXT IN) | | | |
|
||||
* D3 \ ---------------R8--o | | (CAP2A) | (CAP1A)
|
||||
* | v3 | | vhp | vbp | vlp
|
||||
* D2 | \ -----------R8--o +-----+ | |
|
||||
* | | v2 | | | |
|
||||
* D1 | | \ -------R8--o | +----------------+ |
|
||||
* | | | v1 | | | |
|
||||
* D0 | | | \ ---R8--+ | | +---------------------------+
|
||||
* | | | | | | |
|
||||
* R6 R6 R6 R6 R6 R6 R6
|
||||
* | | | | $18 | | | $18
|
||||
* | \ | | D7: 1=open \ \ \ D6 - D4: 0=open
|
||||
* | | | | | | |
|
||||
* +---o---o---o-------------o---o---+ 12V
|
||||
* |
|
||||
* | D3 +--/ --1R2--+ |
|
||||
* | +---R8--+ | | +---R2--+ |
|
||||
* | | | D2 o--/ --2R2--o | | ||--+
|
||||
* +---o--[A>--o------o o--o--[A>--o--||
|
||||
* D1 o--/ --4R2--o (4.25R2) ||--+
|
||||
* $18 | | |
|
||||
* 0=open D0 +--/ --8R2--+ (8.75R2) |
|
||||
*
|
||||
* vo (AUDIO
|
||||
* OUT)
|
||||
*
|
||||
*
|
||||
* v1 - voice 1
|
||||
* v2 - voice 2
|
||||
* v3 - voice 3
|
||||
* ve - ext in
|
||||
* vhp - highpass output
|
||||
* vbp - bandpass output
|
||||
* vlp - lowpass output
|
||||
* vo - audio out
|
||||
* [A> - single ended inverting op-amp (self-biased NMOS inverter)
|
||||
* Rn - "resistors", implemented with custom NMOS FETs
|
||||
* Rw - cutoff frequency resistor (VCR)
|
||||
* C - capacitor
|
||||
* ~~~
|
||||
* Notes:
|
||||
*
|
||||
* R2 ~ 2.0*R1
|
||||
* R6 ~ 6.0*R1
|
||||
* R8 ~ 8.0*R1
|
||||
* R24 ~ 24.0*R1
|
||||
*
|
||||
* The Rn "resistors" in the circuit are implemented with custom NMOS FETs,
|
||||
* probably because of space constraints on the SID die. The silicon substrate
|
||||
* is laid out in a narrow strip or "snake", with a strip length proportional
|
||||
* to the intended resistance. The polysilicon gate electrode covers the entire
|
||||
* silicon substrate and is fixed at 12V in order for the NMOS FET to operate
|
||||
* in triode mode (a.k.a. linear mode or ohmic mode).
|
||||
*
|
||||
* Even in "linear mode", an NMOS FET is only an approximation of a resistor,
|
||||
* as the apparant resistance increases with increasing drain-to-source
|
||||
* voltage. If the drain-to-source voltage should approach the gate voltage
|
||||
* of 12V, the NMOS FET will enter saturation mode (a.k.a. active mode), and
|
||||
* the NMOS FET will not operate anywhere like a resistor.
|
||||
*
|
||||
*
|
||||
*
|
||||
* NMOS FET voltage controlled resistor (VCR)
|
||||
* ------------------------------------------
|
||||
* ~~~
|
||||
* Vw
|
||||
*
|
||||
* |
|
||||
* |
|
||||
* R1
|
||||
* |
|
||||
* +--R1--o
|
||||
* | __|__
|
||||
* | -----
|
||||
* | | |
|
||||
* vi -----o----+ +--o----- vo
|
||||
* | |
|
||||
* +----R24----+
|
||||
*
|
||||
*
|
||||
* vi - input
|
||||
* vo - output
|
||||
* Rn - "resistors", implemented with custom NMOS FETs
|
||||
* Vw - voltage from 11-bit DAC (frequency cutoff control)
|
||||
* ~~~
|
||||
* Notes:
|
||||
*
|
||||
* An approximate value for R24 can be found by using the formula for the
|
||||
* filter cutoff frequency:
|
||||
*
|
||||
* FCmin = 1/(2*pi*Rmax*C)
|
||||
*
|
||||
* Assuming that a the setting for minimum cutoff frequency in combination with
|
||||
* a low level input signal ensures that only negligible current will flow
|
||||
* through the transistor in the schematics above, values for FCmin and C can
|
||||
* be substituted in this formula to find Rmax.
|
||||
* Using C = 470pF and FCmin = 220Hz (measured value), we get:
|
||||
*
|
||||
* FCmin = 1/(2*pi*Rmax*C)
|
||||
* Rmax = 1/(2*pi*FCmin*C) = 1/(2*pi*220*470e-12) ~ 1.5MOhm
|
||||
*
|
||||
* From this it follows that:
|
||||
* R24 = Rmax ~ 1.5MOhm
|
||||
* R1 ~ R24/24 ~ 64kOhm
|
||||
* R2 ~ 2.0*R1 ~ 128kOhm
|
||||
* R6 ~ 6.0*R1 ~ 384kOhm
|
||||
* R8 ~ 8.0*R1 ~ 512kOhm
|
||||
*
|
||||
* Note that these are only approximate values for one particular SID chip,
|
||||
* due to process variations the values can be substantially different in
|
||||
* other chips.
|
||||
*
|
||||
*
|
||||
*
|
||||
* Filter frequency cutoff DAC
|
||||
* ---------------------------
|
||||
*
|
||||
* ~~~
|
||||
* 12V 10 9 8 7 6 5 4 3 2 1 0 VGND
|
||||
* | | | | | | | | | | | | | Missing
|
||||
* 2R 2R 2R 2R 2R 2R 2R 2R 2R 2R 2R 2R 2R termination
|
||||
* | | | | | | | | | | | | |
|
||||
* Vw --o-R-o-R-o-R-o-R-o-R-o-R-o-R-o-R-o-R-o-R-o-R-o- -+
|
||||
*
|
||||
*
|
||||
* Bit on: 12V
|
||||
* Bit off: 5V (VGND)
|
||||
* ~~~
|
||||
* As is the case with all MOS 6581 DACs, the termination to (virtual) ground
|
||||
* at bit 0 is missing.
|
||||
*
|
||||
* Furthermore, the control of the two VCRs imposes a load on the DAC output
|
||||
* which varies with the input signals to the VCRs. This can be seen from the
|
||||
* VCR figure above.
|
||||
*
|
||||
*
|
||||
*
|
||||
* "Op-amp" (self-biased NMOS inverter)
|
||||
* ------------------------------------
|
||||
* ~~~
|
||||
*
|
||||
* 12V
|
||||
*
|
||||
* |
|
||||
* +-----------o
|
||||
* | |
|
||||
* | +------o
|
||||
* | | |
|
||||
* | | ||--+
|
||||
* | +--||
|
||||
* | ||--+
|
||||
* ||--+ |
|
||||
* vi -----|| o---o----- vo
|
||||
* ||--+ | |
|
||||
* | ||--+ |
|
||||
* |-------|| |
|
||||
* | ||--+ |
|
||||
* ||--+ | |
|
||||
* +--|| | |
|
||||
* | ||--+ | |
|
||||
* | | | |
|
||||
* | +-----------o |
|
||||
* | | |
|
||||
* | |
|
||||
* | GND |
|
||||
* | |
|
||||
* +----------------------+
|
||||
*
|
||||
*
|
||||
* vi - input
|
||||
* vo - output
|
||||
* ~~~
|
||||
* Notes:
|
||||
*
|
||||
* The schematics above are laid out to show that the "op-amp" logically
|
||||
* consists of two building blocks; a saturated load NMOS inverter (on the
|
||||
* right hand side of the schematics) with a buffer / bias input stage
|
||||
* consisting of a variable saturated load NMOS inverter (on the left hand
|
||||
* side of the schematics).
|
||||
*
|
||||
* Provided a reasonably high input impedance and a reasonably low output
|
||||
* impedance, the "op-amp" can be modeled as a voltage transfer function
|
||||
* mapping input voltage to output voltage.
|
||||
*
|
||||
*
|
||||
*
|
||||
* Output buffer (NMOS voltage follower)
|
||||
* -------------------------------------
|
||||
* ~~~
|
||||
*
|
||||
* 12V
|
||||
*
|
||||
* |
|
||||
* |
|
||||
* ||--+
|
||||
* vi -----||
|
||||
* ||--+
|
||||
* |
|
||||
* o------ vo
|
||||
* | (AUDIO
|
||||
* Rext OUT)
|
||||
* |
|
||||
* |
|
||||
*
|
||||
* GND
|
||||
*
|
||||
* vi - input
|
||||
* vo - output
|
||||
* Rext - external resistor, 1kOhm
|
||||
* ~~~
|
||||
* Notes:
|
||||
*
|
||||
* The external resistor Rext is needed to complete the NMOS voltage follower,
|
||||
* this resistor has a recommended value of 1kOhm.
|
||||
*
|
||||
* Die photographs show that actually, two NMOS transistors are used in the
|
||||
* voltage follower. However the two transistors are coupled in parallel (all
|
||||
* terminals are pairwise common), which implies that we can model the two
|
||||
* transistors as one.
|
||||
*/
|
||||
class Filter6581 final : public Filter
|
||||
{
|
||||
private:
|
||||
const unsigned short* f0_dac;
|
||||
|
||||
unsigned short** mixer;
|
||||
unsigned short** summer;
|
||||
unsigned short** gain_res;
|
||||
unsigned short** gain_vol;
|
||||
|
||||
const int voiceScaleS11;
|
||||
const int voiceDC;
|
||||
|
||||
/// VCR + associated capacitor connected to highpass output.
|
||||
std::unique_ptr<Integrator6581> const hpIntegrator;
|
||||
|
||||
/// VCR + associated capacitor connected to bandpass output.
|
||||
std::unique_ptr<Integrator6581> const bpIntegrator;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Set filter cutoff frequency.
|
||||
*/
|
||||
void updatedCenterFrequency() override;
|
||||
|
||||
/**
|
||||
* Set filter resonance.
|
||||
*
|
||||
* In the MOS 6581, 1/Q is controlled linearly by res.
|
||||
*/
|
||||
void updateResonance(unsigned char res) override { currentResonance = gain_res[res]; }
|
||||
|
||||
void updatedMixing() override;
|
||||
|
||||
public:
|
||||
Filter6581() :
|
||||
f0_dac(FilterModelConfig6581::getInstance()->getDAC(0.5)),
|
||||
mixer(FilterModelConfig6581::getInstance()->getMixer()),
|
||||
summer(FilterModelConfig6581::getInstance()->getSummer()),
|
||||
gain_res(FilterModelConfig6581::getInstance()->getGainRes()),
|
||||
gain_vol(FilterModelConfig6581::getInstance()->getGainVol()),
|
||||
voiceScaleS11(FilterModelConfig6581::getInstance()->getVoiceScaleS11()),
|
||||
voiceDC(FilterModelConfig6581::getInstance()->getNormalizedVoiceDC()),
|
||||
hpIntegrator(FilterModelConfig6581::getInstance()->buildIntegrator()),
|
||||
bpIntegrator(FilterModelConfig6581::getInstance()->buildIntegrator())
|
||||
{
|
||||
input(0);
|
||||
}
|
||||
|
||||
~Filter6581();
|
||||
|
||||
unsigned short clock(int voice1, int voice2, int voice3) override;
|
||||
|
||||
void input(int sample) override { ve = (sample * voiceScaleS11 * 3 >> 11) + mixer[0][0]; }
|
||||
|
||||
/**
|
||||
* Set filter curve type based on single parameter.
|
||||
*
|
||||
* @param curvePosition 0 .. 1, where 0 sets center frequency high ("light") and 1 sets it low ("dark"), default is 0.5
|
||||
*/
|
||||
void setFilterCurve(double curvePosition);
|
||||
};
|
||||
|
||||
} // namespace reSIDfp
|
||||
|
||||
#if RESID_INLINING || defined(FILTER6581_CPP)
|
||||
|
||||
#include "Integrator6581.h"
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
RESID_INLINE
|
||||
unsigned short Filter6581::clock(int voice1, int voice2, int voice3)
|
||||
{
|
||||
voice1 = (voice1 * voiceScaleS11 >> 15) + voiceDC;
|
||||
voice2 = (voice2 * voiceScaleS11 >> 15) + voiceDC;
|
||||
// Voice 3 is silenced by voice3off if it is not routed through the filter.
|
||||
voice3 = (filt3 || !voice3off) ? (voice3 * voiceScaleS11 >> 15) + voiceDC : 0;
|
||||
|
||||
int Vi = 0;
|
||||
int Vo = 0;
|
||||
|
||||
(filt1 ? Vi : Vo) += voice1;
|
||||
(filt2 ? Vi : Vo) += voice2;
|
||||
(filt3 ? Vi : Vo) += voice3;
|
||||
(filtE ? Vi : Vo) += ve;
|
||||
|
||||
Vhp = currentSummer[currentResonance[Vbp] + Vlp + Vi];
|
||||
Vbp = hpIntegrator->solve(Vhp);
|
||||
Vlp = bpIntegrator->solve(Vbp);
|
||||
|
||||
if (lp) Vo += Vlp;
|
||||
if (bp) Vo += Vbp;
|
||||
if (hp) Vo += Vhp;
|
||||
|
||||
return currentGain[currentMixer[Vo]];
|
||||
}
|
||||
|
||||
} // namespace reSIDfp
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
101
src/engine/platform/sound/c64_fp/Filter8580.cpp
Normal file
101
src/engine/platform/sound/c64_fp/Filter8580.cpp
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2019 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
* Copyright 2004,2010 Dag Lem <resid@nimrod.no>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#define FILTER8580_CPP
|
||||
|
||||
#include "Filter8580.h"
|
||||
|
||||
#include "Integrator8580.h"
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
/**
|
||||
* W/L ratio of frequency DAC bit 0,
|
||||
* other bit are proportional.
|
||||
* When no bit are selected a resistance with half
|
||||
* W/L ratio is selected.
|
||||
*/
|
||||
const double DAC_WL0 = 0.00615;
|
||||
|
||||
Filter8580::~Filter8580() {}
|
||||
|
||||
void Filter8580::updatedCenterFrequency()
|
||||
{
|
||||
double wl;
|
||||
double dacWL = DAC_WL0;
|
||||
if (fc)
|
||||
{
|
||||
wl = 0.;
|
||||
for (unsigned int i = 0; i < 11; i++)
|
||||
{
|
||||
if (fc & (1 << i))
|
||||
{
|
||||
wl += dacWL;
|
||||
}
|
||||
dacWL *= 2.;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
wl = dacWL/2.;
|
||||
}
|
||||
|
||||
hpIntegrator->setFc(wl);
|
||||
bpIntegrator->setFc(wl);
|
||||
}
|
||||
|
||||
void Filter8580::updatedMixing()
|
||||
{
|
||||
currentGain = gain_vol[vol];
|
||||
|
||||
unsigned int ni = 0;
|
||||
unsigned int no = 0;
|
||||
|
||||
(filt1 ? ni : no)++;
|
||||
(filt2 ? ni : no)++;
|
||||
|
||||
if (filt3) ni++;
|
||||
else if (!voice3off) no++;
|
||||
|
||||
(filtE ? ni : no)++;
|
||||
|
||||
currentSummer = summer[ni];
|
||||
|
||||
if (lp) no++;
|
||||
if (bp) no++;
|
||||
if (hp) no++;
|
||||
|
||||
currentMixer = mixer[no];
|
||||
}
|
||||
|
||||
void Filter8580::setFilterCurve(double curvePosition)
|
||||
{
|
||||
// Adjust cp
|
||||
// 1.2 <= cp <= 1.8
|
||||
cp = 1.8 - curvePosition * 3./5.;
|
||||
|
||||
hpIntegrator->setV(cp);
|
||||
bpIntegrator->setV(cp);
|
||||
}
|
||||
|
||||
} // namespace reSIDfp
|
||||
383
src/engine/platform/sound/c64_fp/Filter8580.h
Normal file
383
src/engine/platform/sound/c64_fp/Filter8580.h
Normal file
|
|
@ -0,0 +1,383 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2022 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
* Copyright 2004,2010 Dag Lem <resid@nimrod.no>
|
||||
*
|
||||
* 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 FILTER8580_H
|
||||
#define FILTER8580_H
|
||||
|
||||
#include "siddefs-fp.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "Filter.h"
|
||||
#include "FilterModelConfig8580.h"
|
||||
#include "Integrator8580.h"
|
||||
|
||||
#include "sidcxx11.h"
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
class Integrator8580;
|
||||
|
||||
/**
|
||||
* Filter for 8580 chip
|
||||
* --------------------
|
||||
* The 8580 filter stage had been redesigned to be more linear and robust
|
||||
* against temperature change. It also features real op-amps and a
|
||||
* revisited resonance model.
|
||||
* The filter schematics below are reverse engineered from re-vectorized
|
||||
* and annotated die photographs. Credits to Michael Huth for the microscope
|
||||
* photographs of the die, Tommi Lempinen for re-vectorizating and annotating
|
||||
* the images and ttlworks from forum.6502.org for the circuit analysis.
|
||||
*
|
||||
* ~~~
|
||||
*
|
||||
* +---------------------------------------------------+
|
||||
* | $17 +----Rf-+ |
|
||||
* | | | |
|
||||
* | D4&!D5 o- \-R3-o |
|
||||
* | | | $17 |
|
||||
* | !D4&!D5 o- \-R2-o |
|
||||
* | | | +---R8-- \--+ !D6&D7 |
|
||||
* | D4&!D5 o- \-R1-o | | |
|
||||
* | | | o---RC-- \--o D6&D7 |
|
||||
* | +---------o--<A]--o--o | |
|
||||
* | | o---R4-- \--o D6&!D7 |
|
||||
* | | | | |
|
||||
* | | +---Ri-- \--o !D6&!D7 |
|
||||
* | | | |
|
||||
* $17 | | (CAP2B) | (CAP1B) |
|
||||
* 0=to mixer | +--R7--+ +---R7--+ +---C---o +---C---o
|
||||
* 1=to filter | | | | | | | |
|
||||
* +------R7--o--o--[A>--o--Rfc-o--[A>--o--Rfc-o--[A>--o
|
||||
* ve (EXT IN) | | | |
|
||||
* D3 \ --------------R12--o | | (CAP2A) | (CAP1A)
|
||||
* | v3 | | vhp | vbp | vlp
|
||||
* D2 | \ -----------R7--o +-----+ | |
|
||||
* | | v2 | | | |
|
||||
* D1 | | \ -------R7--o | +----------------+ |
|
||||
* | | | v1 | | | |
|
||||
* D0 | | | \ ---R7--+ | | +---------------------------+
|
||||
* | | | | | | |
|
||||
* R9 R5 R5 R5 R5 R5 R5
|
||||
* | | | | $18 | | | $18
|
||||
* | \ | | D7: 1=open \ \ \ D6 - D4: 0=open
|
||||
* | | | | | | |
|
||||
* +---o---o---o-------------o---o---+
|
||||
* |
|
||||
* | D3 +--/ --1R4--+
|
||||
* | +---R8--+ | | +---R2--+
|
||||
* | | | D2 o--/ --2R4--o | |
|
||||
* +---o--[A>--o------o o--o--[A>--o-- vo (AUDIO OUT)
|
||||
* D1 o--/ --4R4--o
|
||||
* $18 | |
|
||||
* 0=open D0 +--/ --8R4--+
|
||||
*
|
||||
*
|
||||
*
|
||||
* Resonance
|
||||
* ---------
|
||||
* For resonance, we have two tiny DACs that controls both the input
|
||||
* and feedback resistances.
|
||||
*
|
||||
* The "resistors" are switched in as follows by bits in register $17:
|
||||
*
|
||||
* feedback:
|
||||
* R1: bit4&!bit5
|
||||
* R2: !bit4&bit5
|
||||
* R3: bit4&bit5
|
||||
* Rf: always on
|
||||
*
|
||||
* input:
|
||||
* R4: bit6&!bit7
|
||||
* R8: !bit6&bit7
|
||||
* RC: bit6&bit7
|
||||
* Ri: !(R4|R8|RC) = !(bit6|bit7) = !bit6&!bit7
|
||||
*
|
||||
*
|
||||
* The relative "resistor" values are approximately (using channel length):
|
||||
*
|
||||
* R1 = 15.3*Ri
|
||||
* R2 = 7.3*Ri
|
||||
* R3 = 4.7*Ri
|
||||
* Rf = 1.4*Ri
|
||||
* R4 = 1.4*Ri
|
||||
* R8 = 2.0*Ri
|
||||
* RC = 2.8*Ri
|
||||
*
|
||||
*
|
||||
* Approximate values for 1/Q can now be found as follows (assuming an
|
||||
* ideal op-amp):
|
||||
*
|
||||
* res feedback input -gain (1/Q)
|
||||
* --- -------- ----- ----------
|
||||
* 0 Rf Ri Rf/Ri = 1/(Ri*(1/Rf)) = 1/0.71
|
||||
* 1 Rf|R1 Ri (Rf|R1)/Ri = 1/(Ri*(1/Rf+1/R1)) = 1/0.78
|
||||
* 2 Rf|R2 Ri (Rf|R2)/Ri = 1/(Ri*(1/Rf+1/R2)) = 1/0.85
|
||||
* 3 Rf|R3 Ri (Rf|R3)/Ri = 1/(Ri*(1/Rf+1/R3)) = 1/0.92
|
||||
* 4 Rf R4 Rf/R4 = 1/(R4*(1/Rf)) = 1/1.00
|
||||
* 5 Rf|R1 R4 (Rf|R1)/R4 = 1/(R4*(1/Rf+1/R1)) = 1/1.10
|
||||
* 6 Rf|R2 R4 (Rf|R2)/R4 = 1/(R4*(1/Rf+1/R2)) = 1/1.20
|
||||
* 7 Rf|R3 R4 (Rf|R3)/R4 = 1/(R4*(1/Rf+1/R3)) = 1/1.30
|
||||
* 8 Rf R8 Rf/R8 = 1/(R8*(1/Rf)) = 1/1.43
|
||||
* 9 Rf|R1 R8 (Rf|R1)/R8 = 1/(R8*(1/Rf+1/R1)) = 1/1.56
|
||||
* A Rf|R2 R8 (Rf|R2)/R8 = 1/(R8*(1/Rf+1/R2)) = 1/1.70
|
||||
* B Rf|R3 R8 (Rf|R3)/R8 = 1/(R8*(1/Rf+1/R3)) = 1/1.86
|
||||
* C Rf RC Rf/RC = 1/(RC*(1/Rf)) = 1/2.00
|
||||
* D Rf|R1 RC (Rf|R1)/RC = 1/(RC*(1/Rf+1/R1)) = 1/2.18
|
||||
* E Rf|R2 RC (Rf|R2)/RC = 1/(RC*(1/Rf+1/R2)) = 1/2.38
|
||||
* F Rf|R3 RC (Rf|R3)/RC = 1/(RC*(1/Rf+1/R3)) = 1/2.60
|
||||
*
|
||||
*
|
||||
* These data indicate that the following function for 1/Q has been
|
||||
* modeled in the MOS 8580:
|
||||
*
|
||||
* 1/Q = 2^(1/2)*2^(-x/8) = 2^(1/2 - x/8) = 2^((4 - x)/8)
|
||||
*
|
||||
*
|
||||
*
|
||||
* Op-amps
|
||||
* -------
|
||||
* Unlike the 6581, the 8580 has real OpAmps.
|
||||
*
|
||||
* Temperature compensated differential amplifier:
|
||||
*
|
||||
* 9V
|
||||
*
|
||||
* |
|
||||
* +-------o-o-o-------+
|
||||
* | | | |
|
||||
* | R R |
|
||||
* +--|| | | ||--+
|
||||
* ||---o o---||
|
||||
* +--|| | | ||--+
|
||||
* | | | |
|
||||
* o-----+ | | o--- Va
|
||||
* | | | | |
|
||||
* +--|| | | | ||--+
|
||||
* ||-o-+---+---||
|
||||
* +--|| | | ||--+
|
||||
* | | | |
|
||||
* | |
|
||||
* GND | | GND
|
||||
* ||--+ +--||
|
||||
* in- -----|| ||------ in+
|
||||
* ||----o----||
|
||||
* |
|
||||
* 8 Current sink
|
||||
* |
|
||||
*
|
||||
* GND
|
||||
*
|
||||
* Inverter + non-inverting output amplifier:
|
||||
*
|
||||
* Va ---o---||-------------------o--------------------+
|
||||
* | | 9V |
|
||||
* | +----------+----------+ | |
|
||||
* | 9V | | 9V | ||--+ |
|
||||
* | | | 9V | | +-|| |
|
||||
* | R | | | ||--+ ||--+ |
|
||||
* | | | ||--+ +--|| o---o--- Vout
|
||||
* | o---o---|| ||--+ ||--+
|
||||
* | | ||--+ o-----||
|
||||
* | ||--+ | ||--+ ||--+
|
||||
* +-----|| o-----|| |
|
||||
* ||--+ | ||--+
|
||||
* | R | GND
|
||||
* |
|
||||
* GND GND
|
||||
* GND
|
||||
*
|
||||
*
|
||||
*
|
||||
* Virtual ground
|
||||
* --------------
|
||||
* A PolySi resitive voltage divider provides the voltage
|
||||
* for the positive input of the filter op-amps.
|
||||
*
|
||||
* 5V
|
||||
* +----------+
|
||||
* | | |\ |
|
||||
* R1 +---|-\ |
|
||||
* 5V | |A >---o--- Vref
|
||||
* o-------|+/
|
||||
* | | |/
|
||||
* R10 R4
|
||||
* | |
|
||||
* o---+
|
||||
* |
|
||||
* R10
|
||||
* |
|
||||
*
|
||||
* GND
|
||||
*
|
||||
* Rn = n*R1
|
||||
*
|
||||
*
|
||||
*
|
||||
* Rfc - freq control DAC resistance ladder
|
||||
* ----------------------------------------
|
||||
* The 8580 has 11 bits for frequency control, but 12 bit DACs.
|
||||
* If those 11 bits would be '0', the impedance of the DACs would be "infinitely high".
|
||||
* To get around this, there is an 11 input NOR gate below the DACs sensing those 11 bits.
|
||||
* If all are 0, the NOR gate gives the gate control voltage to the 12 bit DAC LSB.
|
||||
*
|
||||
* ----o---o--...--o---o---o---
|
||||
* | | | | |
|
||||
* Rb10 Rb9 ... Rb1 Rb0 R0
|
||||
* | | | | |
|
||||
* ----o---o--...--o---o---o---
|
||||
*
|
||||
*
|
||||
*
|
||||
* Crystal stabilized precision switched capacitor voltage divider
|
||||
* ---------------------------------------------------------------
|
||||
* There is a FET working as a temperature sensor close to the DACs which changes the gate voltage
|
||||
* of the frequency control DACs according to the temperature of the DACs,
|
||||
* to reduce the effects of temperature on the filter curve.
|
||||
* An asynchronous 3 bit binary counter, running at the speed of PHI2, drives two big capacitors
|
||||
* whose AC resistance is then used as a voltage divider.
|
||||
* This implicates that frequency difference between PAL and NTSC might shift the filter curve by 4% or such.
|
||||
*
|
||||
* |\ OpAmp has a smaller capacitor than the other OPs
|
||||
* Vref ---|+\
|
||||
* |A >---o--- Vdac
|
||||
* +-------|-/ |
|
||||
* | |/ |
|
||||
* | |
|
||||
* C1 | C2 |
|
||||
* +---||---o---+ +---o-----||-------o
|
||||
* | | | | | |
|
||||
* o----+ | ----- | |
|
||||
* | | | ----- +----+ +-----o
|
||||
* | ----- | | | |
|
||||
* | ----- | ----- |
|
||||
* | | | ----- |
|
||||
* | +-----------+ | |
|
||||
* | /Q Q | +-------+
|
||||
* GND +-----------+ FET close to DAC
|
||||
* | clk/8 | working as temperature sensor
|
||||
* +-----------+
|
||||
*/
|
||||
class Filter8580 final : public Filter
|
||||
{
|
||||
private:
|
||||
unsigned short** mixer;
|
||||
unsigned short** summer;
|
||||
unsigned short** gain_res;
|
||||
unsigned short** gain_vol;
|
||||
|
||||
const int voiceScaleS11;
|
||||
const int voiceDC;
|
||||
|
||||
double cp;
|
||||
|
||||
/// VCR + associated capacitor connected to highpass output.
|
||||
std::unique_ptr<Integrator8580> const hpIntegrator;
|
||||
|
||||
/// VCR + associated capacitor connected to bandpass output.
|
||||
std::unique_ptr<Integrator8580> const bpIntegrator;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* Set filter cutoff frequency.
|
||||
*/
|
||||
void updatedCenterFrequency() override;
|
||||
|
||||
/**
|
||||
* Set filter resonance.
|
||||
*
|
||||
* @param res the new resonance value
|
||||
*/
|
||||
void updateResonance(unsigned char res) override { currentResonance = gain_res[res]; }
|
||||
|
||||
void updatedMixing() override;
|
||||
|
||||
public:
|
||||
Filter8580() :
|
||||
mixer(FilterModelConfig8580::getInstance()->getMixer()),
|
||||
summer(FilterModelConfig8580::getInstance()->getSummer()),
|
||||
gain_res(FilterModelConfig8580::getInstance()->getGainRes()),
|
||||
gain_vol(FilterModelConfig8580::getInstance()->getGainVol()),
|
||||
voiceScaleS11(FilterModelConfig8580::getInstance()->getVoiceScaleS11()),
|
||||
voiceDC(FilterModelConfig8580::getInstance()->getNormalizedVoiceDC()),
|
||||
cp(0.5),
|
||||
hpIntegrator(FilterModelConfig8580::getInstance()->buildIntegrator()),
|
||||
bpIntegrator(FilterModelConfig8580::getInstance()->buildIntegrator())
|
||||
{
|
||||
setFilterCurve(cp);
|
||||
input(0);
|
||||
}
|
||||
|
||||
~Filter8580();
|
||||
|
||||
unsigned short clock(int voice1, int voice2, int voice3) override;
|
||||
|
||||
void input(int sample) override { ve = (sample * voiceScaleS11 * 3 >> 11) + mixer[0][0]; }
|
||||
|
||||
/**
|
||||
* Set filter curve type based on single parameter.
|
||||
*
|
||||
* @param curvePosition 0 .. 1, where 0 sets center frequency high ("light") and 1 sets it low ("dark"), default is 0.5
|
||||
*/
|
||||
void setFilterCurve(double curvePosition);
|
||||
};
|
||||
|
||||
} // namespace reSIDfp
|
||||
|
||||
#if RESID_INLINING || defined(FILTER8580_CPP)
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
RESID_INLINE
|
||||
unsigned short Filter8580::clock(int voice1, int voice2, int voice3)
|
||||
{
|
||||
voice1 = (voice1 * voiceScaleS11 >> 15) + voiceDC;
|
||||
voice2 = (voice2 * voiceScaleS11 >> 15) + voiceDC;
|
||||
// Voice 3 is silenced by voice3off if it is not routed through the filter.
|
||||
voice3 = (filt3 || !voice3off) ? (voice3 * voiceScaleS11 >> 15) + voiceDC : 0;
|
||||
|
||||
int Vi = 0;
|
||||
int Vo = 0;
|
||||
|
||||
(filt1 ? Vi : Vo) += voice1;
|
||||
(filt2 ? Vi : Vo) += voice2;
|
||||
(filt3 ? Vi : Vo) += voice3;
|
||||
(filtE ? Vi : Vo) += ve;
|
||||
|
||||
Vhp = currentSummer[currentResonance[Vbp] + Vlp + Vi];
|
||||
Vbp = hpIntegrator->solve(Vhp);
|
||||
Vlp = bpIntegrator->solve(Vbp);
|
||||
|
||||
if (lp) Vo += Vlp;
|
||||
if (bp) Vo += Vbp;
|
||||
if (hp) Vo += Vhp;
|
||||
|
||||
return currentGain[currentMixer[Vo]];
|
||||
}
|
||||
|
||||
} // namespace reSIDfp
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
79
src/engine/platform/sound/c64_fp/FilterModelConfig.cpp
Normal file
79
src/engine/platform/sound/c64_fp/FilterModelConfig.cpp
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2022 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
* Copyright 2004,2010 Dag Lem
|
||||
*
|
||||
* 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 "FilterModelConfig.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
FilterModelConfig::FilterModelConfig(
|
||||
double vvr,
|
||||
double vdv,
|
||||
double c,
|
||||
double vdd,
|
||||
double vth,
|
||||
double ucox,
|
||||
const Spline::Point *opamp_voltage,
|
||||
int opamp_size
|
||||
) :
|
||||
voice_voltage_range(vvr),
|
||||
voice_DC_voltage(vdv),
|
||||
C(c),
|
||||
Vdd(vdd),
|
||||
Vth(vth),
|
||||
Ut(26.0e-3),
|
||||
uCox(ucox),
|
||||
Vddt(Vdd - Vth),
|
||||
vmin(opamp_voltage[0].x),
|
||||
vmax(std::max(Vddt, opamp_voltage[0].y)),
|
||||
denorm(vmax - vmin),
|
||||
norm(1.0 / denorm),
|
||||
N16(norm * ((1 << 16) - 1)),
|
||||
currFactorCoeff(denorm * (uCox / 2. * 1.0e-6 / C))
|
||||
{
|
||||
// Convert op-amp voltage transfer to 16 bit values.
|
||||
|
||||
std::vector<Spline::Point> scaled_voltage(opamp_size);
|
||||
|
||||
for (int i = 0; i < opamp_size; i++)
|
||||
{
|
||||
scaled_voltage[i].x = N16 * (opamp_voltage[i].x - opamp_voltage[i].y + denorm) / 2.;
|
||||
scaled_voltage[i].y = N16 * (opamp_voltage[i].x - vmin);
|
||||
}
|
||||
|
||||
// Create lookup table mapping capacitor voltage to op-amp input voltage:
|
||||
|
||||
Spline s(scaled_voltage);
|
||||
|
||||
for (int x = 0; x < (1 << 16); x++)
|
||||
{
|
||||
const Spline::Point out = s.evaluate(x);
|
||||
// If Vmax > max opamp_voltage the first elements may be negative
|
||||
double tmp = out.x > 0. ? out.x : 0.;
|
||||
assert(tmp < 65535.5);
|
||||
opamp_rev[x] = static_cast<unsigned short>(tmp + 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace reSIDfp
|
||||
166
src/engine/platform/sound/c64_fp/FilterModelConfig.h
Normal file
166
src/engine/platform/sound/c64_fp/FilterModelConfig.h
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2022 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
* Copyright 2004,2010 Dag Lem
|
||||
*
|
||||
* 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 FILTERMODELCONFIG_H
|
||||
#define FILTERMODELCONFIG_H
|
||||
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
|
||||
#include "Spline.h"
|
||||
|
||||
#include "sidcxx11.h"
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
class FilterModelConfig
|
||||
{
|
||||
protected:
|
||||
const double voice_voltage_range;
|
||||
const double voice_DC_voltage;
|
||||
|
||||
/// Capacitor value.
|
||||
const double C;
|
||||
|
||||
/// Transistor parameters.
|
||||
//@{
|
||||
const double Vdd;
|
||||
const double Vth; ///< Threshold voltage
|
||||
const double Ut; ///< Thermal voltage: Ut = kT/q = 8.61734315e-5*T ~ 26mV
|
||||
const double uCox; ///< Transconductance coefficient: u*Cox
|
||||
const double Vddt; ///< Vdd - Vth
|
||||
//@}
|
||||
|
||||
// Derived stuff
|
||||
const double vmin, vmax;
|
||||
const double denorm, norm;
|
||||
|
||||
/// Fixed point scaling for 16 bit op-amp output.
|
||||
const double N16;
|
||||
|
||||
/// Current factor coefficient for op-amp integrators.
|
||||
const double currFactorCoeff;
|
||||
|
||||
/// Lookup tables for gain and summer op-amps in output stage / filter.
|
||||
//@{
|
||||
unsigned short* mixer[8]; //-V730_NOINIT this is initialized in the derived class constructor
|
||||
unsigned short* summer[5]; //-V730_NOINIT this is initialized in the derived class constructor
|
||||
unsigned short* gain_vol[16]; //-V730_NOINIT this is initialized in the derived class constructor
|
||||
unsigned short* gain_res[16]; //-V730_NOINIT this is initialized in the derived class constructor
|
||||
//@}
|
||||
|
||||
/// Reverse op-amp transfer function.
|
||||
unsigned short opamp_rev[1 << 16]; //-V730_NOINIT this is initialized in the derived class constructor
|
||||
|
||||
private:
|
||||
FilterModelConfig (const FilterModelConfig&) DELETE;
|
||||
FilterModelConfig& operator= (const FilterModelConfig&) DELETE;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @param vvr voice voltage range
|
||||
* @param vdv voice DC voltage
|
||||
* @param c capacitor value
|
||||
* @param vdd Vdd
|
||||
* @param vth threshold voltage
|
||||
* @param ucox u*Cox
|
||||
* @param ominv opamp min voltage
|
||||
* @param omaxv opamp max voltage
|
||||
*/
|
||||
FilterModelConfig(
|
||||
double vvr,
|
||||
double vdv,
|
||||
double c,
|
||||
double vdd,
|
||||
double vth,
|
||||
double ucox,
|
||||
const Spline::Point *opamp_voltage,
|
||||
int opamp_size
|
||||
);
|
||||
|
||||
~FilterModelConfig()
|
||||
{
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
delete [] mixer[i];
|
||||
}
|
||||
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
delete [] summer[i];
|
||||
}
|
||||
|
||||
for (int i = 0; i < 16; i++)
|
||||
{
|
||||
delete [] gain_vol[i];
|
||||
delete [] gain_res[i];
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
unsigned short** getGainVol() { return gain_vol; }
|
||||
unsigned short** getGainRes() { return gain_res; }
|
||||
unsigned short** getSummer() { return summer; }
|
||||
unsigned short** getMixer() { return mixer; }
|
||||
|
||||
/**
|
||||
* The digital range of one voice is 20 bits; create a scaling term
|
||||
* for multiplication which fits in 11 bits.
|
||||
*/
|
||||
int getVoiceScaleS11() const { return static_cast<int>((norm * ((1 << 11) - 1)) * voice_voltage_range); }
|
||||
|
||||
/**
|
||||
* The "zero" output level of the voices.
|
||||
*/
|
||||
int getNormalizedVoiceDC() const { return static_cast<int>(N16 * (voice_DC_voltage - vmin)); }
|
||||
|
||||
inline unsigned short getOpampRev(int i) const { return opamp_rev[i]; }
|
||||
inline double getVddt() const { return Vddt; }
|
||||
inline double getVth() const { return Vth; }
|
||||
inline double getVoiceDCVoltage() const { return voice_DC_voltage; }
|
||||
|
||||
// helper functions
|
||||
inline unsigned short getNormalizedValue(double value) const
|
||||
{
|
||||
const double tmp = N16 * (value - vmin);
|
||||
assert(tmp > -0.5 && tmp < 65535.5);
|
||||
return static_cast<unsigned short>(tmp + 0.5);
|
||||
}
|
||||
|
||||
inline unsigned short getNormalizedCurrentFactor(double wl) const
|
||||
{
|
||||
const double tmp = (1 << 13) * currFactorCoeff * wl;
|
||||
assert(tmp > -0.5 && tmp < 65535.5);
|
||||
return static_cast<unsigned short>(tmp + 0.5);
|
||||
}
|
||||
|
||||
inline unsigned short getNVmin() const {
|
||||
const double tmp = N16 * vmin;
|
||||
assert(tmp > -0.5 && tmp < 65535.5);
|
||||
return static_cast<unsigned short>(tmp + 0.5);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace reSIDfp
|
||||
|
||||
#endif
|
||||
263
src/engine/platform/sound/c64_fp/FilterModelConfig6581.cpp
Normal file
263
src/engine/platform/sound/c64_fp/FilterModelConfig6581.cpp
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2022 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
* Copyright 2010 Dag Lem
|
||||
*
|
||||
* 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 "FilterModelConfig6581.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "Integrator6581.h"
|
||||
#include "OpAmp.h"
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
#ifndef HAVE_CXX11
|
||||
/**
|
||||
* Compute log(1+x) without losing precision for small values of x
|
||||
*
|
||||
* @note when compiling with -ffastm-math the compiler will
|
||||
* optimize the expression away leaving a plain log(1. + x)
|
||||
*/
|
||||
inline double log1p(double x)
|
||||
{
|
||||
return log(1. + x) - (((1. + x) - 1.) - x) / (1. + x);
|
||||
}
|
||||
#endif
|
||||
|
||||
const unsigned int OPAMP_SIZE = 33;
|
||||
|
||||
/**
|
||||
* This is the SID 6581 op-amp voltage transfer function, measured on
|
||||
* CAP1B/CAP1A on a chip marked MOS 6581R4AR 0687 14.
|
||||
* All measured chips have op-amps with output voltages (and thus input
|
||||
* voltages) within the range of 0.81V - 10.31V.
|
||||
*/
|
||||
const Spline::Point opamp_voltage[OPAMP_SIZE] =
|
||||
{
|
||||
{ 0.81, 10.31 }, // Approximate start of actual range
|
||||
{ 2.40, 10.31 },
|
||||
{ 2.60, 10.30 },
|
||||
{ 2.70, 10.29 },
|
||||
{ 2.80, 10.26 },
|
||||
{ 2.90, 10.17 },
|
||||
{ 3.00, 10.04 },
|
||||
{ 3.10, 9.83 },
|
||||
{ 3.20, 9.58 },
|
||||
{ 3.30, 9.32 },
|
||||
{ 3.50, 8.69 },
|
||||
{ 3.70, 8.00 },
|
||||
{ 4.00, 6.89 },
|
||||
{ 4.40, 5.21 },
|
||||
{ 4.54, 4.54 }, // Working point (vi = vo)
|
||||
{ 4.60, 4.19 },
|
||||
{ 4.80, 3.00 },
|
||||
{ 4.90, 2.30 }, // Change of curvature
|
||||
{ 4.95, 2.03 },
|
||||
{ 5.00, 1.88 },
|
||||
{ 5.05, 1.77 },
|
||||
{ 5.10, 1.69 },
|
||||
{ 5.20, 1.58 },
|
||||
{ 5.40, 1.44 },
|
||||
{ 5.60, 1.33 },
|
||||
{ 5.80, 1.26 },
|
||||
{ 6.00, 1.21 },
|
||||
{ 6.40, 1.12 },
|
||||
{ 7.00, 1.02 },
|
||||
{ 7.50, 0.97 },
|
||||
{ 8.50, 0.89 },
|
||||
{ 10.00, 0.81 },
|
||||
{ 10.31, 0.81 }, // Approximate end of actual range
|
||||
};
|
||||
|
||||
std::unique_ptr<FilterModelConfig6581> FilterModelConfig6581::instance(nullptr);
|
||||
|
||||
FilterModelConfig6581* FilterModelConfig6581::getInstance()
|
||||
{
|
||||
if (!instance.get())
|
||||
{
|
||||
instance.reset(new FilterModelConfig6581());
|
||||
}
|
||||
|
||||
return instance.get();
|
||||
}
|
||||
|
||||
FilterModelConfig6581::FilterModelConfig6581() :
|
||||
FilterModelConfig(
|
||||
1.5, // voice voltage range
|
||||
5.075, // voice DC voltage
|
||||
470e-12, // capacitor value
|
||||
12.18, // Vdd
|
||||
1.31, // Vth
|
||||
20e-6, // uCox
|
||||
opamp_voltage,
|
||||
OPAMP_SIZE
|
||||
),
|
||||
WL_vcr(9.0 / 1.0),
|
||||
WL_snake(1.0 / 115.0),
|
||||
dac_zero(6.65),
|
||||
dac_scale(2.63),
|
||||
dac(DAC_BITS)
|
||||
{
|
||||
dac.kinkedDac(MOS6581);
|
||||
|
||||
// Create lookup tables for gains / summers.
|
||||
|
||||
OpAmp opampModel(std::vector<Spline::Point>(std::begin(opamp_voltage), std::end(opamp_voltage)), Vddt);
|
||||
|
||||
// The filter summer operates at n ~ 1, and has 5 fundamentally different
|
||||
// input configurations (2 - 6 input "resistors").
|
||||
//
|
||||
// Note that all "on" transistors are modeled as one. This is not
|
||||
// entirely accurate, since the input for each transistor is different,
|
||||
// and transistors are not linear components. However modeling all
|
||||
// transistors separately would be extremely costly.
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
const int idiv = 2 + i; // 2 - 6 input "resistors".
|
||||
const int size = idiv << 16;
|
||||
const double n = idiv;
|
||||
opampModel.reset();
|
||||
summer[i] = new unsigned short[size];
|
||||
|
||||
for (int vi = 0; vi < size; vi++)
|
||||
{
|
||||
const double vin = vmin + vi / N16 / idiv; /* vmin .. vmax */
|
||||
summer[i][vi] = getNormalizedValue(opampModel.solve(n, vin));
|
||||
}
|
||||
}
|
||||
|
||||
// The audio mixer operates at n ~ 8/6, and has 8 fundamentally different
|
||||
// input configurations (0 - 7 input "resistors").
|
||||
//
|
||||
// All "on", transistors are modeled as one - see comments above for
|
||||
// the filter summer.
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
const int idiv = (i == 0) ? 1 : i;
|
||||
const int size = (i == 0) ? 1 : i << 16;
|
||||
const double n = i * 8.0 / 6.0;
|
||||
opampModel.reset();
|
||||
mixer[i] = new unsigned short[size];
|
||||
|
||||
for (int vi = 0; vi < size; vi++)
|
||||
{
|
||||
const double vin = vmin + vi / N16 / idiv; /* vmin .. vmax */
|
||||
mixer[i][vi] = getNormalizedValue(opampModel.solve(n, vin));
|
||||
}
|
||||
}
|
||||
|
||||
// 4 bit "resistor" ladders in the audio
|
||||
// output gain necessitate 16 gain tables.
|
||||
// From die photographs of the bandpass and volume "resistor" ladders
|
||||
// it follows that gain ~ vol/12 (assuming ideal
|
||||
// op-amps and ideal "resistors").
|
||||
for (int n8 = 0; n8 < 16; n8++)
|
||||
{
|
||||
const int size = 1 << 16;
|
||||
const double n = n8 / 12.0;
|
||||
opampModel.reset();
|
||||
gain_vol[n8] = new unsigned short[size];
|
||||
|
||||
for (int vi = 0; vi < size; vi++)
|
||||
{
|
||||
const double vin = vmin + vi / N16; /* vmin .. vmax */
|
||||
gain_vol[n8][vi] = getNormalizedValue(opampModel.solve(n, vin));
|
||||
}
|
||||
}
|
||||
|
||||
// 4 bit "resistor" ladders in the bandpass resonance gain
|
||||
// necessitate 16 gain tables.
|
||||
// From die photographs of the bandpass and volume "resistor" ladders
|
||||
// it follows that 1/Q ~ ~res/8 (assuming ideal
|
||||
// op-amps and ideal "resistors").
|
||||
for (int n8 = 0; n8 < 16; n8++)
|
||||
{
|
||||
const int size = 1 << 16;
|
||||
const double n = (~n8 & 0xf) / 8.0;
|
||||
opampModel.reset();
|
||||
gain_res[n8] = new unsigned short[size];
|
||||
|
||||
for (int vi = 0; vi < size; vi++)
|
||||
{
|
||||
const double vin = vmin + vi / N16; /* vmin .. vmax */
|
||||
gain_res[n8][vi] = getNormalizedValue(opampModel.solve(n, vin));
|
||||
}
|
||||
}
|
||||
|
||||
const double nVddt = N16 * (Vddt - vmin);
|
||||
|
||||
for (unsigned int i = 0; i < (1 << 16); i++)
|
||||
{
|
||||
// The table index is right-shifted 16 times in order to fit in
|
||||
// 16 bits; the argument to sqrt is thus multiplied by (1 << 16).
|
||||
const double tmp = nVddt - sqrt(static_cast<double>(i << 16));
|
||||
assert(tmp > -0.5 && tmp < 65535.5);
|
||||
vcr_nVg[i] = static_cast<unsigned short>(tmp + 0.5);
|
||||
}
|
||||
|
||||
// EKV model:
|
||||
//
|
||||
// Ids = Is * (if - ir)
|
||||
// Is = (2 * u*Cox * Ut^2)/k * W/L
|
||||
// if = ln^2(1 + e^((k*(Vg - Vt) - Vs)/(2*Ut))
|
||||
// ir = ln^2(1 + e^((k*(Vg - Vt) - Vd)/(2*Ut))
|
||||
|
||||
// moderate inversion characteristic current
|
||||
const double Is = (2. * uCox * Ut * Ut) * WL_vcr;
|
||||
|
||||
// Normalized current factor for 1 cycle at 1MHz.
|
||||
const double N15 = norm * ((1 << 15) - 1);
|
||||
const double n_Is = N15 * 1.0e-6 / C * Is;
|
||||
|
||||
// kVgt_Vx = k*(Vg - Vt) - Vx
|
||||
// I.e. if k != 1.0, Vg must be scaled accordingly.
|
||||
for (int kVgt_Vx = 0; kVgt_Vx < (1 << 16); kVgt_Vx++)
|
||||
{
|
||||
const double log_term = log1p(exp((kVgt_Vx / N16) / (2. * Ut)));
|
||||
// Scaled by m*2^15
|
||||
const double tmp = n_Is * log_term * log_term;
|
||||
assert(tmp > -0.5 && tmp < 65535.5);
|
||||
vcr_n_Ids_term[kVgt_Vx] = static_cast<unsigned short>(tmp + 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned short* FilterModelConfig6581::getDAC(double adjustment) const
|
||||
{
|
||||
const double dac_zero = getDacZero(adjustment);
|
||||
|
||||
unsigned short* f0_dac = new unsigned short[1 << DAC_BITS];
|
||||
|
||||
for (unsigned int i = 0; i < (1 << DAC_BITS); i++)
|
||||
{
|
||||
const double fcd = dac.getOutput(i);
|
||||
f0_dac[i] = getNormalizedValue(dac_zero + fcd * dac_scale / (1 << DAC_BITS));
|
||||
}
|
||||
|
||||
return f0_dac;
|
||||
}
|
||||
|
||||
std::unique_ptr<Integrator6581> FilterModelConfig6581::buildIntegrator()
|
||||
{
|
||||
return MAKE_UNIQUE(Integrator6581, this, WL_snake);
|
||||
}
|
||||
|
||||
} // namespace reSIDfp
|
||||
112
src/engine/platform/sound/c64_fp/FilterModelConfig6581.h
Normal file
112
src/engine/platform/sound/c64_fp/FilterModelConfig6581.h
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2020 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
* Copyright 2004,2010 Dag Lem
|
||||
*
|
||||
* 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 FILTERMODELCONFIG6581_H
|
||||
#define FILTERMODELCONFIG6581_H
|
||||
|
||||
#include "FilterModelConfig.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "Dac.h"
|
||||
|
||||
#include "sidcxx14.h"
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
class Integrator6581;
|
||||
|
||||
/**
|
||||
* Calculate parameters for 6581 filter emulation.
|
||||
*/
|
||||
class FilterModelConfig6581 final : public FilterModelConfig
|
||||
{
|
||||
private:
|
||||
static const unsigned int DAC_BITS = 11;
|
||||
|
||||
private:
|
||||
static std::unique_ptr<FilterModelConfig6581> instance;
|
||||
// This allows access to the private constructor
|
||||
#ifdef HAVE_CXX11
|
||||
friend std::unique_ptr<FilterModelConfig6581>::deleter_type;
|
||||
#else
|
||||
friend class std::auto_ptr<FilterModelConfig6581>;
|
||||
#endif
|
||||
|
||||
/// Transistor parameters.
|
||||
//@{
|
||||
const double WL_vcr; ///< W/L for VCR
|
||||
const double WL_snake; ///< W/L for "snake"
|
||||
//@}
|
||||
|
||||
/// DAC parameters.
|
||||
//@{
|
||||
const double dac_zero;
|
||||
const double dac_scale;
|
||||
//@}
|
||||
|
||||
/// DAC lookup table
|
||||
Dac dac;
|
||||
|
||||
/// VCR - 6581 only.
|
||||
//@{
|
||||
unsigned short vcr_nVg[1 << 16];
|
||||
unsigned short vcr_n_Ids_term[1 << 16];
|
||||
//@}
|
||||
|
||||
private:
|
||||
double getDacZero(double adjustment) const { return dac_zero + (1. - adjustment); }
|
||||
|
||||
FilterModelConfig6581();
|
||||
~FilterModelConfig6581() DEFAULT;
|
||||
|
||||
public:
|
||||
static FilterModelConfig6581* getInstance();
|
||||
|
||||
/**
|
||||
* Construct an 11 bit cutoff frequency DAC output voltage table.
|
||||
* Ownership is transferred to the requester which becomes responsible
|
||||
* of freeing the object when done.
|
||||
*
|
||||
* @param adjustment
|
||||
* @return the DAC table
|
||||
*/
|
||||
unsigned short* getDAC(double adjustment) const;
|
||||
|
||||
/**
|
||||
* Construct an integrator solver.
|
||||
*
|
||||
* @return the integrator
|
||||
*/
|
||||
std::unique_ptr<Integrator6581> buildIntegrator();
|
||||
|
||||
inline unsigned short getVcr_nVg(int i) const { return vcr_nVg[i]; }
|
||||
inline unsigned short getVcr_n_Ids_term(int i) const { return vcr_n_Ids_term[i]; }
|
||||
// only used if SLOPE_FACTOR is defined
|
||||
inline double getUt() const { return Ut; }
|
||||
inline double getN16() const { return N16; }
|
||||
};
|
||||
|
||||
} // namespace reSIDfp
|
||||
|
||||
#endif
|
||||
222
src/engine/platform/sound/c64_fp/FilterModelConfig8580.cpp
Normal file
222
src/engine/platform/sound/c64_fp/FilterModelConfig8580.cpp
Normal file
|
|
@ -0,0 +1,222 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2020 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
* Copyright 2010 Dag Lem
|
||||
*
|
||||
* 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 "FilterModelConfig8580.h"
|
||||
|
||||
#include "Integrator8580.h"
|
||||
#include "OpAmp.h"
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
/*
|
||||
* R1 = 15.3*Ri
|
||||
* R2 = 7.3*Ri
|
||||
* R3 = 4.7*Ri
|
||||
* Rf = 1.4*Ri
|
||||
* R4 = 1.4*Ri
|
||||
* R8 = 2.0*Ri
|
||||
* RC = 2.8*Ri
|
||||
*
|
||||
* res feedback input
|
||||
* --- -------- -----
|
||||
* 0 Rf Ri
|
||||
* 1 Rf|R1 Ri
|
||||
* 2 Rf|R2 Ri
|
||||
* 3 Rf|R3 Ri
|
||||
* 4 Rf R4
|
||||
* 5 Rf|R1 R4
|
||||
* 6 Rf|R2 R4
|
||||
* 7 Rf|R3 R4
|
||||
* 8 Rf R8
|
||||
* 9 Rf|R1 R8
|
||||
* A Rf|R2 R8
|
||||
* B Rf|R3 R8
|
||||
* C Rf RC
|
||||
* D Rf|R1 RC
|
||||
* E Rf|R2 RC
|
||||
* F Rf|R3 RC
|
||||
*/
|
||||
const double resGain[16] =
|
||||
{
|
||||
1.4/1.0, // Rf/Ri 1.4
|
||||
((1.4*15.3)/(1.4+15.3))/1.0, // (Rf|R1)/Ri 1.28263
|
||||
((1.4*7.3)/(1.4+7.3))/1.0, // (Rf|R2)/Ri 1.17471
|
||||
((1.4*4.7)/(1.4+4.7))/1.0, // (Rf|R3)/Ri 1.07869
|
||||
1.4/1.4, // Rf/R4 1
|
||||
((1.4*15.3)/(1.4+15.3))/1.4, // (Rf|R1)/R4 0.916168
|
||||
((1.4*7.3)/(1.4+7.3))/1.4, // (Rf|R2)/R4 0.83908
|
||||
((1.4*4.7)/(1.4+4.7))/1.4, // (Rf|R3)/R4 0.770492
|
||||
1.4/2.0, // Rf/R8 0.7
|
||||
((1.4*15.3)/(1.4+15.3))/2.0, // (Rf|R1)/R8 0.641317
|
||||
((1.4*7.3)/(1.4+7.3))/2.0, // (Rf|R2)/R8 0.587356
|
||||
((1.4*4.7)/(1.4+4.7))/2.0, // (Rf|R3)/R8 0.539344
|
||||
1.4/2.8, // Rf/RC 0.5
|
||||
((1.4*15.3)/(1.4+15.3))/2.8, // (Rf|R1)/RC 0.458084
|
||||
((1.4*7.3)/(1.4+7.3))/2.8, // (Rf|R2)/RC 0.41954
|
||||
((1.4*4.7)/(1.4+4.7))/2.8, // (Rf|R3)/RC 0.385246
|
||||
};
|
||||
|
||||
const unsigned int OPAMP_SIZE = 21;
|
||||
|
||||
/**
|
||||
* This is the SID 8580 op-amp voltage transfer function, measured on
|
||||
* CAP1B/CAP1A on a chip marked CSG 8580R5 1690 25.
|
||||
*/
|
||||
const Spline::Point opamp_voltage[OPAMP_SIZE] =
|
||||
{
|
||||
{ 1.30, 8.91 }, // Approximate start of actual range
|
||||
{ 4.76, 8.91 },
|
||||
{ 4.77, 8.90 },
|
||||
{ 4.78, 8.88 },
|
||||
{ 4.785, 8.86 },
|
||||
{ 4.79, 8.80 },
|
||||
{ 4.795, 8.60 },
|
||||
{ 4.80, 8.25 },
|
||||
{ 4.805, 7.50 },
|
||||
{ 4.81, 6.10 },
|
||||
{ 4.815, 4.05 }, // Change of curvature
|
||||
{ 4.82, 2.27 },
|
||||
{ 4.825, 1.65 },
|
||||
{ 4.83, 1.55 },
|
||||
{ 4.84, 1.47 },
|
||||
{ 4.85, 1.43 },
|
||||
{ 4.87, 1.37 },
|
||||
{ 4.90, 1.34 },
|
||||
{ 5.00, 1.30 },
|
||||
{ 5.10, 1.30 },
|
||||
{ 8.91, 1.30 }, // Approximate end of actual range
|
||||
};
|
||||
|
||||
std::unique_ptr<FilterModelConfig8580> FilterModelConfig8580::instance(nullptr);
|
||||
|
||||
FilterModelConfig8580* FilterModelConfig8580::getInstance()
|
||||
{
|
||||
if (!instance.get())
|
||||
{
|
||||
instance.reset(new FilterModelConfig8580());
|
||||
}
|
||||
|
||||
return instance.get();
|
||||
}
|
||||
|
||||
FilterModelConfig8580::FilterModelConfig8580() :
|
||||
FilterModelConfig(
|
||||
0.25, // voice voltage range FIXME measure
|
||||
4.80, // voice DC voltage FIXME was 4.76
|
||||
22e-9, // capacitor value
|
||||
9.09, // Vdd
|
||||
0.80, // Vth
|
||||
100e-6, // uCox
|
||||
opamp_voltage,
|
||||
OPAMP_SIZE
|
||||
)
|
||||
{
|
||||
// Create lookup tables for gains / summers.
|
||||
|
||||
OpAmp opampModel(std::vector<Spline::Point>(std::begin(opamp_voltage), std::end(opamp_voltage)), Vddt);
|
||||
|
||||
// The filter summer operates at n ~ 1, and has 5 fundamentally different
|
||||
// input configurations (2 - 6 input "resistors").
|
||||
//
|
||||
// Note that all "on" transistors are modeled as one. This is not
|
||||
// entirely accurate, since the input for each transistor is different,
|
||||
// and transistors are not linear components. However modeling all
|
||||
// transistors separately would be extremely costly.
|
||||
for (int i = 0; i < 5; i++)
|
||||
{
|
||||
const int idiv = 2 + i; // 2 - 6 input "resistors".
|
||||
const int size = idiv << 16;
|
||||
const double n = idiv;
|
||||
opampModel.reset();
|
||||
summer[i] = new unsigned short[size];
|
||||
|
||||
for (int vi = 0; vi < size; vi++)
|
||||
{
|
||||
const double vin = vmin + vi / N16 / idiv; /* vmin .. vmax */
|
||||
summer[i][vi] = getNormalizedValue(opampModel.solve(n, vin));
|
||||
}
|
||||
}
|
||||
|
||||
// The audio mixer operates at n ~ 8/5, and has 8 fundamentally different
|
||||
// input configurations (0 - 7 input "resistors").
|
||||
//
|
||||
// All "on", transistors are modeled as one - see comments above for
|
||||
// the filter summer.
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
const int idiv = (i == 0) ? 1 : i;
|
||||
const int size = (i == 0) ? 1 : i << 16;
|
||||
const double n = i * 8.0 / 5.0;
|
||||
opampModel.reset();
|
||||
mixer[i] = new unsigned short[size];
|
||||
|
||||
for (int vi = 0; vi < size; vi++)
|
||||
{
|
||||
const double vin = vmin + vi / N16 / idiv; /* vmin .. vmax */
|
||||
mixer[i][vi] = getNormalizedValue(opampModel.solve(n, vin));
|
||||
}
|
||||
}
|
||||
|
||||
// 4 bit "resistor" ladders in the audio output gain
|
||||
// necessitate 16 gain tables.
|
||||
// From die photographs of the volume "resistor" ladders
|
||||
// it follows that gain ~ vol/16 (assuming ideal op-amps
|
||||
for (int n8 = 0; n8 < 16; n8++)
|
||||
{
|
||||
const int size = 1 << 16;
|
||||
const double n = n8 / 16.0;
|
||||
opampModel.reset();
|
||||
gain_vol[n8] = new unsigned short[size];
|
||||
|
||||
for (int vi = 0; vi < size; vi++)
|
||||
{
|
||||
const double vin = vmin + vi / N16; /* vmin .. vmax */
|
||||
gain_vol[n8][vi] = getNormalizedValue(opampModel.solve(n, vin));
|
||||
}
|
||||
}
|
||||
|
||||
// 4 bit "resistor" ladders in the bandpass resonance gain
|
||||
// necessitate 16 gain tables.
|
||||
// From die photographs of the bandpass and volume "resistor" ladders
|
||||
// it follows that 1/Q ~ 2^((4 - res)/8) (assuming ideal
|
||||
// op-amps and ideal "resistors").
|
||||
for (int n8 = 0; n8 < 16; n8++)
|
||||
{
|
||||
const int size = 1 << 16;
|
||||
opampModel.reset();
|
||||
gain_res[n8] = new unsigned short[size];
|
||||
|
||||
for (int vi = 0; vi < size; vi++)
|
||||
{
|
||||
const double vin = vmin + vi / N16; /* vmin .. vmax */
|
||||
gain_res[n8][vi] = getNormalizedValue(opampModel.solve(resGain[n8], vin));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<Integrator8580> FilterModelConfig8580::buildIntegrator()
|
||||
{
|
||||
return MAKE_UNIQUE(Integrator8580, this);
|
||||
}
|
||||
|
||||
} // namespace reSIDfp
|
||||
68
src/engine/platform/sound/c64_fp/FilterModelConfig8580.h
Normal file
68
src/engine/platform/sound/c64_fp/FilterModelConfig8580.h
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2020 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
* Copyright 2004,2010 Dag Lem
|
||||
*
|
||||
* 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 FILTERMODELCONFIG8580_H
|
||||
#define FILTERMODELCONFIG8580_H
|
||||
|
||||
#include "FilterModelConfig.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "sidcxx14.h"
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
class Integrator8580;
|
||||
|
||||
/**
|
||||
* Calculate parameters for 8580 filter emulation.
|
||||
*/
|
||||
class FilterModelConfig8580 final : public FilterModelConfig
|
||||
{
|
||||
private:
|
||||
static std::unique_ptr<FilterModelConfig8580> instance;
|
||||
// This allows access to the private constructor
|
||||
#ifdef HAVE_CXX11
|
||||
friend std::unique_ptr<FilterModelConfig8580>::deleter_type;
|
||||
#else
|
||||
friend class std::auto_ptr<FilterModelConfig8580>;
|
||||
#endif
|
||||
|
||||
private:
|
||||
FilterModelConfig8580();
|
||||
~FilterModelConfig8580() DEFAULT;
|
||||
|
||||
public:
|
||||
static FilterModelConfig8580* getInstance();
|
||||
|
||||
/**
|
||||
* Construct an integrator solver.
|
||||
*
|
||||
* @return the integrator
|
||||
*/
|
||||
std::unique_ptr<Integrator8580> buildIntegrator();
|
||||
};
|
||||
|
||||
} // namespace reSIDfp
|
||||
|
||||
#endif
|
||||
25
src/engine/platform/sound/c64_fp/Integrator6581.cpp
Normal file
25
src/engine/platform/sound/c64_fp/Integrator6581.cpp
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2014 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#define INTEGRATOR_CPP
|
||||
|
||||
#include "Integrator6581.h"
|
||||
|
||||
// This is needed when compiling with --disable-inline
|
||||
285
src/engine/platform/sound/c64_fp/Integrator6581.h
Normal file
285
src/engine/platform/sound/c64_fp/Integrator6581.h
Normal file
|
|
@ -0,0 +1,285 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2022 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
* Copyright 2004, 2010 Dag Lem <resid@nimrod.no>
|
||||
*
|
||||
* 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 INTEGRATOR6581_H
|
||||
#define INTEGRATOR6581_H
|
||||
|
||||
#include "FilterModelConfig6581.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <cassert>
|
||||
|
||||
// uncomment to enable use of the slope factor
|
||||
// in the EKV model
|
||||
// actually produces worse results, needs investigation
|
||||
//#define SLOPE_FACTOR
|
||||
|
||||
#ifdef SLOPE_FACTOR
|
||||
# include <cmath>
|
||||
#endif
|
||||
|
||||
#include "siddefs-fp.h"
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
/**
|
||||
* Find output voltage in inverting integrator SID op-amp circuits, using a
|
||||
* single fixpoint iteration step.
|
||||
*
|
||||
* A circuit diagram of a MOS 6581 integrator is shown below.
|
||||
*
|
||||
* +---C---+
|
||||
* | |
|
||||
* vi --o--Rw--o-o--[A>--o-- vo
|
||||
* | | vx
|
||||
* +--Rs--+
|
||||
*
|
||||
* From Kirchoff's current law it follows that
|
||||
*
|
||||
* IRw + IRs + ICr = 0
|
||||
*
|
||||
* Using the formula for current through a capacitor, i = C*dv/dt, we get
|
||||
*
|
||||
* IRw + IRs + C*(vc - vc0)/dt = 0
|
||||
* dt/C*(IRw + IRs) + vc - vc0 = 0
|
||||
* vc = vc0 - n*(IRw(vi,vx) + IRs(vi,vx))
|
||||
*
|
||||
* which may be rewritten as the following iterative fixpoint function:
|
||||
*
|
||||
* vc = vc0 - n*(IRw(vi,g(vc)) + IRs(vi,g(vc)))
|
||||
*
|
||||
* To accurately calculate the currents through Rs and Rw, we need to use
|
||||
* transistor models. Rs has a gate voltage of Vdd = 12V, and can be
|
||||
* assumed to always be in triode mode. For Rw, the situation is rather
|
||||
* more complex, as it turns out that this transistor will operate in
|
||||
* both subthreshold, triode, and saturation modes.
|
||||
*
|
||||
* The Shichman-Hodges transistor model routinely used in textbooks may
|
||||
* be written as follows:
|
||||
*
|
||||
* Ids = 0 , Vgst < 0 (subthreshold mode)
|
||||
* Ids = K*W/L*(2*Vgst - Vds)*Vds , Vgst >= 0, Vds < Vgst (triode mode)
|
||||
* Ids = K*W/L*Vgst^2 , Vgst >= 0, Vds >= Vgst (saturation mode)
|
||||
*
|
||||
* where
|
||||
* K = u*Cox/2 (transconductance coefficient)
|
||||
* W/L = ratio between substrate width and length
|
||||
* Vgst = Vg - Vs - Vt (overdrive voltage)
|
||||
*
|
||||
* This transistor model is also called the quadratic model.
|
||||
*
|
||||
* Note that the equation for the triode mode can be reformulated as
|
||||
* independent terms depending on Vgs and Vgd, respectively, by the
|
||||
* following substitution:
|
||||
*
|
||||
* Vds = Vgst - (Vgst - Vds) = Vgst - Vgdt
|
||||
*
|
||||
* Ids = K*W/L*(2*Vgst - Vds)*Vds
|
||||
* = K*W/L*(2*Vgst - (Vgst - Vgdt)*(Vgst - Vgdt)
|
||||
* = K*W/L*(Vgst + Vgdt)*(Vgst - Vgdt)
|
||||
* = K*W/L*(Vgst^2 - Vgdt^2)
|
||||
*
|
||||
* This turns out to be a general equation which covers both the triode
|
||||
* and saturation modes (where the second term is 0 in saturation mode).
|
||||
* The equation is also symmetrical, i.e. it can calculate negative
|
||||
* currents without any change of parameters (since the terms for drain
|
||||
* and source are identical except for the sign).
|
||||
*
|
||||
* FIXME: Subthreshold as function of Vgs, Vgd.
|
||||
*
|
||||
* Ids = I0*W/L*e^(Vgst/(Ut/k)) , Vgst < 0 (subthreshold mode)
|
||||
*
|
||||
* where
|
||||
* I0 = (2 * uCox * Ut^2) / k
|
||||
*
|
||||
* The remaining problem with the textbook model is that the transition
|
||||
* from subthreshold the triode/saturation is not continuous.
|
||||
*
|
||||
* Realizing that the subthreshold and triode/saturation modes may both
|
||||
* be defined by independent (and equal) terms of Vgs and Vds,
|
||||
* respectively, the corresponding terms can be blended into (equal)
|
||||
* continuous functions suitable for table lookup.
|
||||
*
|
||||
* The EKV model (Enz, Krummenacher and Vittoz) essentially performs this
|
||||
* blending using an elegant mathematical formulation:
|
||||
*
|
||||
* Ids = Is * (if - ir)
|
||||
* Is = ((2 * u*Cox * Ut^2)/k) * W/L
|
||||
* if = ln^2(1 + e^((k*(Vg - Vt) - Vs)/(2*Ut))
|
||||
* ir = ln^2(1 + e^((k*(Vg - Vt) - Vd)/(2*Ut))
|
||||
*
|
||||
* For our purposes, the EKV model preserves two important properties
|
||||
* discussed above:
|
||||
*
|
||||
* - It consists of two independent terms, which can be represented by
|
||||
* the same lookup table.
|
||||
* - It is symmetrical, i.e. it calculates current in both directions,
|
||||
* facilitating a branch-free implementation.
|
||||
*
|
||||
* Rw in the circuit diagram above is a VCR (voltage controlled resistor),
|
||||
* as shown in the circuit diagram below.
|
||||
*
|
||||
*
|
||||
* Vdd
|
||||
* |
|
||||
* Vdd _|_
|
||||
* | +---+ +---- Vw
|
||||
* _|_ |
|
||||
* +--+ +---o Vg
|
||||
* | __|__
|
||||
* | ----- Rw
|
||||
* | | |
|
||||
* vi -----o------+ +-------- vo
|
||||
*
|
||||
*
|
||||
* In order to calculalate the current through the VCR, its gate voltage
|
||||
* must be determined.
|
||||
*
|
||||
* Assuming triode mode and applying Kirchoff's current law, we get the
|
||||
* following equation for Vg:
|
||||
*
|
||||
* u*Cox/2*W/L*((nVddt - Vg)^2 - (nVddt - vi)^2 + (nVddt - Vg)^2 - (nVddt - Vw)^2) = 0
|
||||
* 2*(nVddt - Vg)^2 - (nVddt - vi)^2 - (nVddt - Vw)^2 = 0
|
||||
* (nVddt - Vg) = sqrt(((nVddt - vi)^2 + (nVddt - Vw)^2)/2)
|
||||
*
|
||||
* Vg = nVddt - sqrt(((nVddt - vi)^2 + (nVddt - Vw)^2)/2)
|
||||
*/
|
||||
class Integrator6581
|
||||
{
|
||||
private:
|
||||
unsigned int nVddt_Vw_2;
|
||||
mutable int vx;
|
||||
mutable int vc;
|
||||
|
||||
#ifdef SLOPE_FACTOR
|
||||
// Slope factor n = 1/k
|
||||
// where k is the gate coupling coefficient
|
||||
// k = Cox/(Cox+Cdep) ~ 0.7 (depends on gate voltage)
|
||||
mutable double n;
|
||||
#endif
|
||||
const unsigned short nVddt;
|
||||
const unsigned short nVt;
|
||||
const unsigned short nVmin;
|
||||
const unsigned short nSnake;
|
||||
|
||||
const FilterModelConfig6581* fmc;
|
||||
|
||||
public:
|
||||
Integrator6581(const FilterModelConfig6581* fmc,
|
||||
double WL_snake) :
|
||||
nVddt_Vw_2(0),
|
||||
vx(0),
|
||||
vc(0),
|
||||
#ifdef SLOPE_FACTOR
|
||||
n(1.4),
|
||||
#endif
|
||||
nVddt(fmc->getNormalizedValue(fmc->getVddt())),
|
||||
nVt(fmc->getNormalizedValue(fmc->getVth())),
|
||||
nVmin(fmc->getNVmin()),
|
||||
nSnake(fmc->getNormalizedCurrentFactor(WL_snake)),
|
||||
fmc(fmc) {}
|
||||
|
||||
void setVw(unsigned short Vw) { nVddt_Vw_2 = ((nVddt - Vw) * (nVddt - Vw)) >> 1; }
|
||||
|
||||
int solve(int vi) const;
|
||||
};
|
||||
|
||||
} // namespace reSIDfp
|
||||
|
||||
#if RESID_INLINING || defined(INTEGRATOR_CPP)
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
RESID_INLINE
|
||||
int Integrator6581::solve(int vi) const
|
||||
{
|
||||
// Make sure Vgst>0 so we're not in subthreshold mode
|
||||
assert(vx < nVddt);
|
||||
|
||||
// Check that transistor is actually in triode mode
|
||||
// Vds < Vgs - Vth
|
||||
assert(vi < nVddt);
|
||||
|
||||
// "Snake" voltages for triode mode calculation.
|
||||
const unsigned int Vgst = nVddt - vx;
|
||||
const unsigned int Vgdt = nVddt - vi;
|
||||
|
||||
const unsigned int Vgst_2 = Vgst * Vgst;
|
||||
const unsigned int Vgdt_2 = Vgdt * Vgdt;
|
||||
|
||||
// "Snake" current, scaled by (1/m)*2^13*m*2^16*m*2^16*2^-15 = m*2^30
|
||||
const int n_I_snake = nSnake * (static_cast<int>(Vgst_2 - Vgdt_2) >> 15);
|
||||
|
||||
// VCR gate voltage. // Scaled by m*2^16
|
||||
// Vg = Vddt - sqrt(((Vddt - Vw)^2 + Vgdt^2)/2)
|
||||
const int nVg = static_cast<int>(fmc->getVcr_nVg((nVddt_Vw_2 + (Vgdt_2 >> 1)) >> 16));
|
||||
#ifdef SLOPE_FACTOR
|
||||
const double nVp = static_cast<double>(nVg - nVt) / n; // Pinch-off voltage
|
||||
const int kVg = static_cast<int>(nVp + 0.5) - nVmin;
|
||||
#else
|
||||
const int kVg = (nVg - nVt) - nVmin;
|
||||
#endif
|
||||
|
||||
// VCR voltages for EKV model table lookup.
|
||||
const int kVgt_Vs = (vx < kVg) ? kVg - vx : 0;
|
||||
assert(kVgt_Vs < (1 << 16));
|
||||
const int kVgt_Vd = (vi < kVg) ? kVg - vi : 0;
|
||||
assert(kVgt_Vd < (1 << 16));
|
||||
|
||||
// VCR current, scaled by m*2^15*2^15 = m*2^30
|
||||
const unsigned int If = static_cast<unsigned int>(fmc->getVcr_n_Ids_term(kVgt_Vs)) << 15;
|
||||
const unsigned int Ir = static_cast<unsigned int>(fmc->getVcr_n_Ids_term(kVgt_Vd)) << 15;
|
||||
#ifdef SLOPE_FACTOR
|
||||
const double iVcr = static_cast<double>(If - Ir);
|
||||
const int n_I_vcr = static_cast<int>((iVcr * n) + 0.5);
|
||||
#else
|
||||
const int n_I_vcr = If - Ir;
|
||||
#endif
|
||||
|
||||
#ifdef SLOPE_FACTOR
|
||||
// estimate new slope factor based on gate voltage
|
||||
const double gamma = 1.0; // body effect factor
|
||||
const double phi = 0.8; // bulk Fermi potential
|
||||
const double Vp = nVp / fmc->getN16();
|
||||
n = 1. + (gamma / (2. * sqrt(Vp + phi + 4. * fmc->getUt())));
|
||||
assert((n > 1.2) && (n < 1.8));
|
||||
#endif
|
||||
|
||||
// Change in capacitor charge.
|
||||
vc += n_I_snake + n_I_vcr;
|
||||
|
||||
// vx = g(vc)
|
||||
const int tmp = (vc >> 15) + (1 << 15);
|
||||
assert(tmp < (1 << 16));
|
||||
vx = fmc->getOpampRev(tmp);
|
||||
|
||||
// Return vo.
|
||||
return vx - (vc >> 14);
|
||||
}
|
||||
|
||||
} // namespace reSIDfp
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
25
src/engine/platform/sound/c64_fp/Integrator8580.cpp
Normal file
25
src/engine/platform/sound/c64_fp/Integrator8580.cpp
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2014-2016 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#define INTEGRATOR8580_CPP
|
||||
|
||||
#include "Integrator8580.h"
|
||||
|
||||
// This is needed when compiling with --disable-inline
|
||||
142
src/engine/platform/sound/c64_fp/Integrator8580.h
Normal file
142
src/engine/platform/sound/c64_fp/Integrator8580.h
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2022 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
* Copyright 2004, 2010 Dag Lem <resid@nimrod.no>
|
||||
*
|
||||
* 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 INTEGRATOR8580_H
|
||||
#define INTEGRATOR8580_H
|
||||
|
||||
#include "FilterModelConfig8580.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <cassert>
|
||||
|
||||
#include "siddefs-fp.h"
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
/**
|
||||
* 8580 integrator
|
||||
*
|
||||
* +---C---+
|
||||
* | |
|
||||
* vi -----Rfc---o--[A>--o-- vo
|
||||
* vx
|
||||
*
|
||||
* IRfc + ICr = 0
|
||||
* IRfc + C*(vc - vc0)/dt = 0
|
||||
* dt/C*(IRfc) + vc - vc0 = 0
|
||||
* vc = vc0 - n*(IRfc(vi,vx))
|
||||
* vc = vc0 - n*(IRfc(vi,g(vc)))
|
||||
*
|
||||
* IRfc = K*W/L*(Vgst^2 - Vgdt^2) = n*((Vddt - vx)^2 - (Vddt - vi)^2)
|
||||
*
|
||||
* Rfc gate voltage is generated by an OP Amp and depends on chip temperature.
|
||||
*/
|
||||
class Integrator8580
|
||||
{
|
||||
private:
|
||||
mutable int vx;
|
||||
mutable int vc;
|
||||
|
||||
unsigned short nVgt;
|
||||
unsigned short n_dac;
|
||||
|
||||
const FilterModelConfig8580* fmc;
|
||||
|
||||
public:
|
||||
Integrator8580(const FilterModelConfig8580* fmc) :
|
||||
vx(0),
|
||||
vc(0),
|
||||
fmc(fmc)
|
||||
{
|
||||
setV(1.5);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set Filter Cutoff resistor ratio.
|
||||
*/
|
||||
void setFc(double wl)
|
||||
{
|
||||
// Normalized current factor, 1 cycle at 1MHz.
|
||||
// Fit in 5 bits.
|
||||
n_dac = fmc->getNormalizedCurrentFactor(wl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set FC gate voltage multiplier.
|
||||
*/
|
||||
void setV(double v)
|
||||
{
|
||||
// Gate voltage is controlled by the switched capacitor voltage divider
|
||||
// Ua = Ue * v = 4.76v 1<v<2
|
||||
assert(v > 1.0 && v < 2.0);
|
||||
const double Vg = fmc->getVoiceDCVoltage() * v;
|
||||
const double Vgt = Vg - fmc->getVth();
|
||||
|
||||
// Vg - Vth, normalized so that translated values can be subtracted:
|
||||
// Vgt - x = (Vgt - t) - (x - t)
|
||||
nVgt = fmc->getNormalizedValue(Vgt);
|
||||
}
|
||||
|
||||
int solve(int vi) const;
|
||||
};
|
||||
|
||||
} // namespace reSIDfp
|
||||
|
||||
#if RESID_INLINING || defined(INTEGRATOR8580_CPP)
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
RESID_INLINE
|
||||
int Integrator8580::solve(int vi) const
|
||||
{
|
||||
// Make sure we're not in subthreshold mode
|
||||
assert(vx < nVgt);
|
||||
|
||||
// DAC voltages
|
||||
const unsigned int Vgst = nVgt - vx;
|
||||
const unsigned int Vgdt = (vi < nVgt) ? nVgt - vi : 0; // triode/saturation mode
|
||||
|
||||
const unsigned int Vgst_2 = Vgst * Vgst;
|
||||
const unsigned int Vgdt_2 = Vgdt * Vgdt;
|
||||
|
||||
// DAC current, scaled by (1/m)*2^13*m*2^16*m*2^16*2^-15 = m*2^30
|
||||
const int n_I_dac = n_dac * (static_cast<int>(Vgst_2 - Vgdt_2) >> 15);
|
||||
|
||||
// Change in capacitor charge.
|
||||
vc += n_I_dac;
|
||||
|
||||
// vx = g(vc)
|
||||
const int tmp = (vc >> 15) + (1 << 15);
|
||||
assert(tmp < (1 << 16));
|
||||
vx = fmc->getOpampRev(tmp);
|
||||
|
||||
// Return vo.
|
||||
return vx - (vc >> 14);
|
||||
}
|
||||
|
||||
} // namespace reSIDfp
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
84
src/engine/platform/sound/c64_fp/OpAmp.cpp
Normal file
84
src/engine/platform/sound/c64_fp/OpAmp.cpp
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2015 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
*
|
||||
* 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 "OpAmp.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "siddefs-fp.h"
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
const double EPSILON = 1e-8;
|
||||
|
||||
double OpAmp::solve(double n, double vi) const
|
||||
{
|
||||
// Start off with an estimate of x and a root bracket [ak, bk].
|
||||
// f is decreasing, so that f(ak) > 0 and f(bk) < 0.
|
||||
double ak = vmin;
|
||||
double bk = vmax;
|
||||
|
||||
const double a = n + 1.;
|
||||
const double b = Vddt;
|
||||
const double b_vi = (b > vi) ? (b - vi) : 0.;
|
||||
const double c = n * (b_vi * b_vi);
|
||||
|
||||
for (;;)
|
||||
{
|
||||
const double xk = x;
|
||||
|
||||
// Calculate f and df.
|
||||
|
||||
Spline::Point out = opamp->evaluate(x);
|
||||
const double vo = out.x;
|
||||
const double dvo = out.y;
|
||||
|
||||
const double b_vx = (b > x) ? b - x : 0.;
|
||||
const double b_vo = (b > vo) ? b - vo : 0.;
|
||||
|
||||
// f = a*(b - vx)^2 - c - (b - vo)^2
|
||||
const double f = a * (b_vx * b_vx) - c - (b_vo * b_vo);
|
||||
|
||||
// df = 2*((b - vo)*dvo - a*(b - vx))
|
||||
const double df = 2. * (b_vo * dvo - a * b_vx);
|
||||
|
||||
// Newton-Raphson step: xk1 = xk - f(xk)/f'(xk)
|
||||
x -= f / df;
|
||||
|
||||
if (unlikely(fabs(x - xk) < EPSILON))
|
||||
{
|
||||
out = opamp->evaluate(x);
|
||||
return out.x;
|
||||
}
|
||||
|
||||
// Narrow down root bracket.
|
||||
(f < 0. ? bk : ak) = xk;
|
||||
|
||||
if (unlikely(x <= ak) || unlikely(x >= bk))
|
||||
{
|
||||
// Bisection step (ala Dekker's method).
|
||||
x = (ak + bk) * 0.5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace reSIDfp
|
||||
113
src/engine/platform/sound/c64_fp/OpAmp.h
Normal file
113
src/engine/platform/sound/c64_fp/OpAmp.h
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2015 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
* Copyright 2004,2010 Dag Lem
|
||||
*
|
||||
* 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 OPAMP_H
|
||||
#define OPAMP_H
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "Spline.h"
|
||||
|
||||
#include "sidcxx11.h"
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
/**
|
||||
* Find output voltage in inverting gain and inverting summer SID op-amp
|
||||
* circuits, using a combination of Newton-Raphson and bisection.
|
||||
*
|
||||
* +---R2--+
|
||||
* | |
|
||||
* vi ---R1--o--[A>--o-- vo
|
||||
* vx
|
||||
*
|
||||
* From Kirchoff's current law it follows that
|
||||
*
|
||||
* IR1f + IR2r = 0
|
||||
*
|
||||
* Substituting the triode mode transistor model K*W/L*(Vgst^2 - Vgdt^2)
|
||||
* for the currents, we get:
|
||||
*
|
||||
* n*((Vddt - vx)^2 - (Vddt - vi)^2) + (Vddt - vx)^2 - (Vddt - vo)^2 = 0
|
||||
*
|
||||
* Our root function f can thus be written as:
|
||||
*
|
||||
* f = (n + 1)*(Vddt - vx)^2 - n*(Vddt - vi)^2 - (Vddt - vo)^2 = 0
|
||||
*
|
||||
* Using substitution constants
|
||||
*
|
||||
* a = n + 1
|
||||
* b = Vddt
|
||||
* c = n*(Vddt - vi)^2
|
||||
*
|
||||
* the equations for the root function and its derivative can be written as:
|
||||
*
|
||||
* f = a*(b - vx)^2 - c - (b - vo)^2
|
||||
* df = 2*((b - vo)*dvo - a*(b - vx))
|
||||
*/
|
||||
class OpAmp
|
||||
{
|
||||
private:
|
||||
/// Current root position (cached as guess to speed up next iteration)
|
||||
mutable double x;
|
||||
|
||||
const double Vddt;
|
||||
const double vmin;
|
||||
const double vmax;
|
||||
|
||||
std::unique_ptr<Spline> const opamp;
|
||||
|
||||
public:
|
||||
/**
|
||||
* Opamp input -> output voltage conversion
|
||||
*
|
||||
* @param opamp opamp mapping table as pairs of points (in -> out)
|
||||
* @param opamplength length of the opamp array
|
||||
* @param kVddt transistor dt parameter (in volts)
|
||||
*/
|
||||
OpAmp(const std::vector<Spline::Point> &opamp, double Vddt) :
|
||||
x(0.),
|
||||
Vddt(Vddt),
|
||||
vmin(opamp.front().x),
|
||||
vmax(opamp.back().x),
|
||||
opamp(new Spline(opamp)) {}
|
||||
|
||||
void reset() const
|
||||
{
|
||||
x = vmin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Solve the opamp equation for input vi in loading context n
|
||||
*
|
||||
* @param n the ratio of input/output loading
|
||||
* @param vi input
|
||||
* @return vo
|
||||
*/
|
||||
double solve(double n, double vi) const;
|
||||
};
|
||||
|
||||
} // namespace reSIDfp
|
||||
|
||||
#endif
|
||||
50
src/engine/platform/sound/c64_fp/Potentiometer.h
Normal file
50
src/engine/platform/sound/c64_fp/Potentiometer.h
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2013 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
* Copyright (C) 2004 Dag Lem <resid@nimrod.no>
|
||||
*
|
||||
* 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 POTENTIOMETER_H
|
||||
#define POTENTIOMETER_H
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
/**
|
||||
* Potentiometer representation.
|
||||
*
|
||||
* This class will probably never be implemented in any real way.
|
||||
*
|
||||
* @author Ken Händel
|
||||
* @author Dag Lem
|
||||
*/
|
||||
class Potentiometer
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Read paddle value. Not modeled.
|
||||
*
|
||||
* @return paddle value (always 0xff)
|
||||
*/
|
||||
unsigned char readPOT() const { return 0xff; }
|
||||
};
|
||||
|
||||
} // namespace reSIDfp
|
||||
|
||||
#endif
|
||||
20
src/engine/platform/sound/c64_fp/README
Normal file
20
src/engine/platform/sound/c64_fp/README
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
reSIDfp is a fork of Dag Lem's reSID 0.16, a reverse engineered software emulation
|
||||
of the MOS6581/8580 SID (Sound Interface Device).
|
||||
|
||||
The project was started by Antti S. Lankila in order to improve SID emulation
|
||||
with special focus on the 6581 filter.
|
||||
The codebase has been later on ported to java by Ken Händel within the jsidplay2 project
|
||||
and has seen further work by Antti Lankila.
|
||||
It was then ported back to c++ and integrated with improvements from reSID 1.0 by Leandro Nini.
|
||||
|
||||
|
||||
Main differences from reSID:
|
||||
|
||||
* combined waveforms are emulated by a parametrized model based on samplings from Kevtris;
|
||||
* envelope generator is implemented like in the real machine with a shift register;
|
||||
* high quality resampling is done in two steps to allow computational savings using lower order filters;
|
||||
* part of the calculations are done with floats instead of fixed point;
|
||||
* interpolation is accomplished with Fritsch-Carlson method to preserve monotonicity.
|
||||
|
||||
|
||||
reSIDfp is free software. See the file COPYING for copying permission.
|
||||
504
src/engine/platform/sound/c64_fp/SID.cpp
Normal file
504
src/engine/platform/sound/c64_fp/SID.cpp
Normal file
|
|
@ -0,0 +1,504 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2016 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
* Copyright 2004 Dag Lem <resid@nimrod.no>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#define SID_CPP
|
||||
|
||||
#include "SID.h"
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include "array.h"
|
||||
#include "Dac.h"
|
||||
#include "Filter6581.h"
|
||||
#include "Filter8580.h"
|
||||
#include "Potentiometer.h"
|
||||
#include "WaveformCalculator.h"
|
||||
#include "resample/TwoPassSincResampler.h"
|
||||
#include "resample/ZeroOrderResampler.h"
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
const unsigned int ENV_DAC_BITS = 8;
|
||||
const unsigned int OSC_DAC_BITS = 12;
|
||||
|
||||
/**
|
||||
* The waveform D/A converter introduces a DC offset in the signal
|
||||
* to the envelope multiplying D/A converter. The "zero" level of
|
||||
* the waveform D/A converter can be found as follows:
|
||||
*
|
||||
* Measure the "zero" voltage of voice 3 on the SID audio output
|
||||
* pin, routing only voice 3 to the mixer ($d417 = $0b, $d418 =
|
||||
* $0f, all other registers zeroed).
|
||||
*
|
||||
* Then set the sustain level for voice 3 to maximum and search for
|
||||
* the waveform output value yielding the same voltage as found
|
||||
* above. This is done by trying out different waveform output
|
||||
* values until the correct value is found, e.g. with the following
|
||||
* program:
|
||||
*
|
||||
* lda #$08
|
||||
* sta $d412
|
||||
* lda #$0b
|
||||
* sta $d417
|
||||
* lda #$0f
|
||||
* sta $d418
|
||||
* lda #$f0
|
||||
* sta $d414
|
||||
* lda #$21
|
||||
* sta $d412
|
||||
* lda #$01
|
||||
* sta $d40e
|
||||
*
|
||||
* ldx #$00
|
||||
* lda #$38 ; Tweak this to find the "zero" level
|
||||
*l cmp $d41b
|
||||
* bne l
|
||||
* stx $d40e ; Stop frequency counter - freeze waveform output
|
||||
* brk
|
||||
*
|
||||
* The waveform output range is 0x000 to 0xfff, so the "zero"
|
||||
* level should ideally have been 0x800. In the measured chip, the
|
||||
* waveform output "zero" level was found to be 0x380 (i.e. $d41b
|
||||
* = 0x38) at an audio output voltage of 5.94V.
|
||||
*
|
||||
* With knowledge of the mixer op-amp characteristics, further estimates
|
||||
* of waveform voltages can be obtained by sampling the EXT IN pin.
|
||||
* From EXT IN samples, the corresponding waveform output can be found by
|
||||
* using the model for the mixer.
|
||||
*
|
||||
* Such measurements have been done on a chip marked MOS 6581R4AR
|
||||
* 0687 14, and the following results have been obtained:
|
||||
* * The full range of one voice is approximately 1.5V.
|
||||
* * The "zero" level rides at approximately 5.0V.
|
||||
*
|
||||
*
|
||||
* zero-x did the measuring on the 8580 (https://sourceforge.net/p/vice-emu/bugs/1036/#c5b3):
|
||||
* When it sits on basic from powerup it's at 4.72
|
||||
* Run 1.prg and check the output pin level.
|
||||
* Then run 2.prg andadjust it until the output level is the same...
|
||||
* 0x94-0xA8 gives me the same 4.72 1.prg shows.
|
||||
* On another 8580 it's 0x90-0x9C
|
||||
* Third chip 0x94-0xA8
|
||||
* Fourth chip 0x90-0xA4
|
||||
* On the 8580 that plays digis the output is 4.66 and 0x93 is the only value to reach that.
|
||||
* To me that seems as regular 8580s have somewhat wide 0-level range,
|
||||
* whereas that digi-compatible 8580 has it very narrow.
|
||||
* On my 6581R4AR has 0x3A as the only value giving the same output level as 1.prg
|
||||
*/
|
||||
//@{
|
||||
unsigned int constexpr OFFSET_6581 = 0x380;
|
||||
unsigned int constexpr OFFSET_8580 = 0x9c0;
|
||||
//@}
|
||||
|
||||
/**
|
||||
* Bus value stays alive for some time after each operation.
|
||||
* Values differs between chip models, the timings used here
|
||||
* are taken from VICE [1].
|
||||
* See also the discussion "How do I reliably detect 6581/8580 sid?" on CSDb [2].
|
||||
*
|
||||
* Results from real C64 (testprogs/SID/bitfade/delayfrq0.prg):
|
||||
*
|
||||
* (new SID) (250469/8580R5) (250469/8580R5)
|
||||
* delayfrq0 ~7a000 ~108000
|
||||
*
|
||||
* (old SID) (250407/6581)
|
||||
* delayfrq0 ~01d00
|
||||
*
|
||||
* [1]: http://sourceforge.net/p/vice-emu/patches/99/
|
||||
* [2]: http://noname.c64.org/csdb/forums/?roomid=11&topicid=29025&showallposts=1
|
||||
*/
|
||||
//@{
|
||||
int constexpr BUS_TTL_6581 = 0x01d00;
|
||||
int constexpr BUS_TTL_8580 = 0xa2000;
|
||||
//@}
|
||||
|
||||
SID::SID() :
|
||||
filter6581(new Filter6581()),
|
||||
filter8580(new Filter8580()),
|
||||
externalFilter(new ExternalFilter()),
|
||||
resampler(nullptr),
|
||||
potX(new Potentiometer()),
|
||||
potY(new Potentiometer())
|
||||
{
|
||||
voice[0].reset(new Voice());
|
||||
voice[1].reset(new Voice());
|
||||
voice[2].reset(new Voice());
|
||||
|
||||
muted[0] = muted[1] = muted[2] = false;
|
||||
|
||||
reset();
|
||||
setChipModel(MOS8580);
|
||||
}
|
||||
|
||||
SID::~SID()
|
||||
{
|
||||
// Needed to delete auto_ptr with complete type
|
||||
}
|
||||
|
||||
void SID::setFilter6581Curve(double filterCurve)
|
||||
{
|
||||
filter6581->setFilterCurve(filterCurve);
|
||||
}
|
||||
|
||||
void SID::setFilter8580Curve(double filterCurve)
|
||||
{
|
||||
filter8580->setFilterCurve(filterCurve);
|
||||
}
|
||||
|
||||
void SID::enableFilter(bool enable)
|
||||
{
|
||||
filter6581->enable(enable);
|
||||
filter8580->enable(enable);
|
||||
}
|
||||
|
||||
void SID::voiceSync(bool sync)
|
||||
{
|
||||
if (sync)
|
||||
{
|
||||
// Synchronize the 3 waveform generators.
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
voice[i]->wave()->synchronize(voice[(i + 1) % 3]->wave(), voice[(i + 2) % 3]->wave());
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the time to next voice sync
|
||||
nextVoiceSync = std::numeric_limits<int>::max();
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
WaveformGenerator* const wave = voice[i]->wave();
|
||||
const unsigned int freq = wave->readFreq();
|
||||
|
||||
if (wave->readTest() || freq == 0 || !voice[(i + 1) % 3]->wave()->readSync())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const unsigned int accumulator = wave->readAccumulator();
|
||||
const unsigned int thisVoiceSync = ((0x7fffff - accumulator) & 0xffffff) / freq + 1;
|
||||
|
||||
if (thisVoiceSync < nextVoiceSync)
|
||||
{
|
||||
nextVoiceSync = thisVoiceSync;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SID::setChipModel(ChipModel model)
|
||||
{
|
||||
switch (model)
|
||||
{
|
||||
case MOS6581:
|
||||
filter = filter6581.get();
|
||||
modelTTL = BUS_TTL_6581;
|
||||
break;
|
||||
|
||||
case MOS8580:
|
||||
filter = filter8580.get();
|
||||
modelTTL = BUS_TTL_8580;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw SIDError("Unknown chip type");
|
||||
}
|
||||
|
||||
this->model = model;
|
||||
|
||||
// calculate waveform-related tables
|
||||
matrix_t* tables = WaveformCalculator::getInstance()->buildTable(model);
|
||||
|
||||
// calculate envelope DAC table
|
||||
{
|
||||
Dac dacBuilder(ENV_DAC_BITS);
|
||||
dacBuilder.kinkedDac(model);
|
||||
|
||||
for (unsigned int i = 0; i < (1 << ENV_DAC_BITS); i++)
|
||||
{
|
||||
envDAC[i] = static_cast<float>(dacBuilder.getOutput(i));
|
||||
}
|
||||
}
|
||||
|
||||
// calculate oscillator DAC table
|
||||
const bool is6581 = model == MOS6581;
|
||||
|
||||
{
|
||||
Dac dacBuilder(OSC_DAC_BITS);
|
||||
dacBuilder.kinkedDac(model);
|
||||
|
||||
const double offset = dacBuilder.getOutput(is6581 ? OFFSET_6581 : OFFSET_8580);
|
||||
|
||||
for (unsigned int i = 0; i < (1 << OSC_DAC_BITS); i++)
|
||||
{
|
||||
const double dacValue = dacBuilder.getOutput(i);
|
||||
oscDAC[i] = static_cast<float>(dacValue - offset);
|
||||
}
|
||||
}
|
||||
|
||||
// set voice tables
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
voice[i]->setEnvDAC(envDAC);
|
||||
voice[i]->setWavDAC(oscDAC);
|
||||
voice[i]->wave()->setModel(is6581);
|
||||
voice[i]->wave()->setWaveformModels(tables);
|
||||
}
|
||||
}
|
||||
|
||||
void SID::reset()
|
||||
{
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
voice[i]->reset();
|
||||
}
|
||||
|
||||
filter6581->reset();
|
||||
filter8580->reset();
|
||||
externalFilter->reset();
|
||||
|
||||
if (resampler.get())
|
||||
{
|
||||
resampler->reset();
|
||||
}
|
||||
|
||||
busValue = 0;
|
||||
busValueTtl = 0;
|
||||
voiceSync(false);
|
||||
}
|
||||
|
||||
void SID::input(int value)
|
||||
{
|
||||
filter6581->input(value);
|
||||
filter8580->input(value);
|
||||
}
|
||||
|
||||
unsigned char SID::read(int offset)
|
||||
{
|
||||
switch (offset)
|
||||
{
|
||||
case 0x19: // X value of paddle
|
||||
busValue = potX->readPOT();
|
||||
busValueTtl = modelTTL;
|
||||
break;
|
||||
|
||||
case 0x1a: // Y value of paddle
|
||||
busValue = potY->readPOT();
|
||||
busValueTtl = modelTTL;
|
||||
break;
|
||||
|
||||
case 0x1b: // Voice #3 waveform output
|
||||
busValue = voice[2]->wave()->readOSC();
|
||||
busValueTtl = modelTTL;
|
||||
break;
|
||||
|
||||
case 0x1c: // Voice #3 ADSR output
|
||||
busValue = voice[2]->envelope()->readENV();
|
||||
busValueTtl = modelTTL;
|
||||
break;
|
||||
|
||||
default:
|
||||
// Reading from a write-only or non-existing register
|
||||
// makes the bus discharge faster.
|
||||
// Emulate this by halving the residual TTL.
|
||||
busValueTtl /= 2;
|
||||
break;
|
||||
}
|
||||
|
||||
return busValue;
|
||||
}
|
||||
|
||||
void SID::write(int offset, unsigned char value)
|
||||
{
|
||||
busValue = value;
|
||||
busValueTtl = modelTTL;
|
||||
|
||||
switch (offset)
|
||||
{
|
||||
case 0x00: // Voice #1 frequency (Low-byte)
|
||||
voice[0]->wave()->writeFREQ_LO(value);
|
||||
break;
|
||||
|
||||
case 0x01: // Voice #1 frequency (High-byte)
|
||||
voice[0]->wave()->writeFREQ_HI(value);
|
||||
break;
|
||||
|
||||
case 0x02: // Voice #1 pulse width (Low-byte)
|
||||
voice[0]->wave()->writePW_LO(value);
|
||||
break;
|
||||
|
||||
case 0x03: // Voice #1 pulse width (bits #8-#15)
|
||||
voice[0]->wave()->writePW_HI(value);
|
||||
break;
|
||||
|
||||
case 0x04: // Voice #1 control register
|
||||
voice[0]->writeCONTROL_REG(muted[0] ? 0 : value);
|
||||
break;
|
||||
|
||||
case 0x05: // Voice #1 Attack and Decay length
|
||||
voice[0]->envelope()->writeATTACK_DECAY(value);
|
||||
break;
|
||||
|
||||
case 0x06: // Voice #1 Sustain volume and Release length
|
||||
voice[0]->envelope()->writeSUSTAIN_RELEASE(value);
|
||||
break;
|
||||
|
||||
case 0x07: // Voice #2 frequency (Low-byte)
|
||||
voice[1]->wave()->writeFREQ_LO(value);
|
||||
break;
|
||||
|
||||
case 0x08: // Voice #2 frequency (High-byte)
|
||||
voice[1]->wave()->writeFREQ_HI(value);
|
||||
break;
|
||||
|
||||
case 0x09: // Voice #2 pulse width (Low-byte)
|
||||
voice[1]->wave()->writePW_LO(value);
|
||||
break;
|
||||
|
||||
case 0x0a: // Voice #2 pulse width (bits #8-#15)
|
||||
voice[1]->wave()->writePW_HI(value);
|
||||
break;
|
||||
|
||||
case 0x0b: // Voice #2 control register
|
||||
voice[1]->writeCONTROL_REG(muted[1] ? 0 : value);
|
||||
break;
|
||||
|
||||
case 0x0c: // Voice #2 Attack and Decay length
|
||||
voice[1]->envelope()->writeATTACK_DECAY(value);
|
||||
break;
|
||||
|
||||
case 0x0d: // Voice #2 Sustain volume and Release length
|
||||
voice[1]->envelope()->writeSUSTAIN_RELEASE(value);
|
||||
break;
|
||||
|
||||
case 0x0e: // Voice #3 frequency (Low-byte)
|
||||
voice[2]->wave()->writeFREQ_LO(value);
|
||||
break;
|
||||
|
||||
case 0x0f: // Voice #3 frequency (High-byte)
|
||||
voice[2]->wave()->writeFREQ_HI(value);
|
||||
break;
|
||||
|
||||
case 0x10: // Voice #3 pulse width (Low-byte)
|
||||
voice[2]->wave()->writePW_LO(value);
|
||||
break;
|
||||
|
||||
case 0x11: // Voice #3 pulse width (bits #8-#15)
|
||||
voice[2]->wave()->writePW_HI(value);
|
||||
break;
|
||||
|
||||
case 0x12: // Voice #3 control register
|
||||
voice[2]->writeCONTROL_REG(muted[2] ? 0 : value);
|
||||
break;
|
||||
|
||||
case 0x13: // Voice #3 Attack and Decay length
|
||||
voice[2]->envelope()->writeATTACK_DECAY(value);
|
||||
break;
|
||||
|
||||
case 0x14: // Voice #3 Sustain volume and Release length
|
||||
voice[2]->envelope()->writeSUSTAIN_RELEASE(value);
|
||||
break;
|
||||
|
||||
case 0x15: // Filter cut off frequency (bits #0-#2)
|
||||
filter6581->writeFC_LO(value);
|
||||
filter8580->writeFC_LO(value);
|
||||
break;
|
||||
|
||||
case 0x16: // Filter cut off frequency (bits #3-#10)
|
||||
filter6581->writeFC_HI(value);
|
||||
filter8580->writeFC_HI(value);
|
||||
break;
|
||||
|
||||
case 0x17: // Filter control
|
||||
filter6581->writeRES_FILT(value);
|
||||
filter8580->writeRES_FILT(value);
|
||||
break;
|
||||
|
||||
case 0x18: // Volume and filter modes
|
||||
filter6581->writeMODE_VOL(value);
|
||||
filter8580->writeMODE_VOL(value);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Update voicesync just in case.
|
||||
voiceSync(false);
|
||||
}
|
||||
|
||||
void SID::setSamplingParameters(double clockFrequency, SamplingMethod method, double samplingFrequency, double highestAccurateFrequency)
|
||||
{
|
||||
externalFilter->setClockFrequency(clockFrequency);
|
||||
|
||||
switch (method)
|
||||
{
|
||||
case DECIMATE:
|
||||
resampler.reset(new ZeroOrderResampler(clockFrequency, samplingFrequency));
|
||||
break;
|
||||
|
||||
case RESAMPLE:
|
||||
resampler.reset(TwoPassSincResampler::create(clockFrequency, samplingFrequency, highestAccurateFrequency));
|
||||
break;
|
||||
|
||||
default:
|
||||
throw SIDError("Unknown sampling method");
|
||||
}
|
||||
}
|
||||
|
||||
void SID::clockSilent(unsigned int cycles)
|
||||
{
|
||||
ageBusValue(cycles);
|
||||
|
||||
while (cycles != 0)
|
||||
{
|
||||
int delta_t = std::min(nextVoiceSync, cycles);
|
||||
|
||||
if (delta_t > 0)
|
||||
{
|
||||
for (int i = 0; i < delta_t; i++)
|
||||
{
|
||||
// clock waveform generators (can affect OSC3)
|
||||
voice[0]->wave()->clock();
|
||||
voice[1]->wave()->clock();
|
||||
voice[2]->wave()->clock();
|
||||
|
||||
voice[0]->wave()->output(voice[2]->wave());
|
||||
voice[1]->wave()->output(voice[0]->wave());
|
||||
voice[2]->wave()->output(voice[1]->wave());
|
||||
|
||||
// clock ENV3 only
|
||||
voice[2]->envelope()->clock();
|
||||
}
|
||||
|
||||
cycles -= delta_t;
|
||||
nextVoiceSync -= delta_t;
|
||||
}
|
||||
|
||||
if (nextVoiceSync == 0)
|
||||
{
|
||||
voiceSync(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace reSIDfp
|
||||
378
src/engine/platform/sound/c64_fp/SID.h
Normal file
378
src/engine/platform/sound/c64_fp/SID.h
Normal file
|
|
@ -0,0 +1,378 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2016 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
* Copyright 2004 Dag Lem <resid@nimrod.no>
|
||||
*
|
||||
* 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 SIDFP_H
|
||||
#define SIDFP_H
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "siddefs-fp.h"
|
||||
|
||||
#include "sidcxx11.h"
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
class Filter;
|
||||
class Filter6581;
|
||||
class Filter8580;
|
||||
class ExternalFilter;
|
||||
class Potentiometer;
|
||||
class Voice;
|
||||
class Resampler;
|
||||
|
||||
/**
|
||||
* SID error exception.
|
||||
*/
|
||||
class SIDError
|
||||
{
|
||||
private:
|
||||
const char* message;
|
||||
|
||||
public:
|
||||
SIDError(const char* msg) :
|
||||
message(msg) {}
|
||||
const char* getMessage() const { return message; }
|
||||
};
|
||||
|
||||
/**
|
||||
* MOS6581/MOS8580 emulation.
|
||||
*/
|
||||
class SID
|
||||
{
|
||||
private:
|
||||
/// Currently active filter
|
||||
Filter* filter;
|
||||
|
||||
/// Filter used, if model is set to 6581
|
||||
std::unique_ptr<Filter6581> const filter6581;
|
||||
|
||||
/// Filter used, if model is set to 8580
|
||||
std::unique_ptr<Filter8580> const filter8580;
|
||||
|
||||
/**
|
||||
* External filter that provides high-pass and low-pass filtering
|
||||
* to adjust sound tone slightly.
|
||||
*/
|
||||
std::unique_ptr<ExternalFilter> const externalFilter;
|
||||
|
||||
/// Resampler used by audio generation code.
|
||||
std::unique_ptr<Resampler> resampler;
|
||||
|
||||
/// Paddle X register support
|
||||
std::unique_ptr<Potentiometer> const potX;
|
||||
|
||||
/// Paddle Y register support
|
||||
std::unique_ptr<Potentiometer> const potY;
|
||||
|
||||
/// SID voices
|
||||
std::unique_ptr<Voice> voice[3];
|
||||
|
||||
/// Time to live for the last written value
|
||||
int busValueTtl;
|
||||
|
||||
/// Current chip model's bus value TTL
|
||||
int modelTTL;
|
||||
|
||||
/// Time until #voiceSync must be run.
|
||||
unsigned int nextVoiceSync;
|
||||
|
||||
/// Currently active chip model.
|
||||
ChipModel model;
|
||||
|
||||
/// Last written value
|
||||
unsigned char busValue;
|
||||
|
||||
/// Flags for muted channels
|
||||
bool muted[3];
|
||||
|
||||
/**
|
||||
* Emulated nonlinearity of the envelope DAC.
|
||||
*
|
||||
* @See Dac
|
||||
*/
|
||||
float envDAC[256];
|
||||
|
||||
/**
|
||||
* Emulated nonlinearity of the oscillator DAC.
|
||||
*
|
||||
* @See Dac
|
||||
*/
|
||||
float oscDAC[4096];
|
||||
|
||||
private:
|
||||
/**
|
||||
* Age the bus value and zero it if it's TTL has expired.
|
||||
*
|
||||
* @param n the number of cycles
|
||||
*/
|
||||
void ageBusValue(unsigned int n);
|
||||
|
||||
/**
|
||||
* Get output sample.
|
||||
*
|
||||
* @return the output sample
|
||||
*/
|
||||
int output();
|
||||
|
||||
/**
|
||||
* Calculate the numebr of cycles according to current parameters
|
||||
* that it takes to reach sync.
|
||||
*
|
||||
* @param sync whether to do the actual voice synchronization
|
||||
*/
|
||||
void voiceSync(bool sync);
|
||||
|
||||
public:
|
||||
SID();
|
||||
~SID();
|
||||
|
||||
int lastChanOut[3];
|
||||
|
||||
/**
|
||||
* Set chip model.
|
||||
*
|
||||
* @param model chip model to use
|
||||
* @throw SIDError
|
||||
*/
|
||||
void setChipModel(ChipModel model);
|
||||
|
||||
/**
|
||||
* Get currently emulated chip model.
|
||||
*/
|
||||
ChipModel getChipModel() const { return model; }
|
||||
|
||||
/**
|
||||
* SID reset.
|
||||
*/
|
||||
void reset();
|
||||
|
||||
/**
|
||||
* 16-bit input (EXT IN). Write 16-bit sample to audio input. NB! The caller
|
||||
* is responsible for keeping the value within 16 bits. Note that to mix in
|
||||
* an external audio signal, the signal should be resampled to 1MHz first to
|
||||
* avoid sampling noise.
|
||||
*
|
||||
* @param value input level to set
|
||||
*/
|
||||
void input(int value);
|
||||
|
||||
/**
|
||||
* Read registers.
|
||||
*
|
||||
* Reading a write only register returns the last char written to any SID register.
|
||||
* The individual bits in this value start to fade down towards zero after a few cycles.
|
||||
* All bits reach zero within approximately $2000 - $4000 cycles.
|
||||
* It has been claimed that this fading happens in an orderly fashion,
|
||||
* however sampling of write only registers reveals that this is not the case.
|
||||
* NOTE: This is not correctly modeled.
|
||||
* The actual use of write only registers has largely been made
|
||||
* in the belief that all SID registers are readable.
|
||||
* To support this belief the read would have to be done immediately
|
||||
* after a write to the same register (remember that an intermediate write
|
||||
* to another register would yield that value instead).
|
||||
* With this in mind we return the last value written to any SID register
|
||||
* for $2000 cycles without modeling the bit fading.
|
||||
*
|
||||
* @param offset SID register to read
|
||||
* @return value read from chip
|
||||
*/
|
||||
unsigned char read(int offset);
|
||||
|
||||
/**
|
||||
* Write registers.
|
||||
*
|
||||
* @param offset chip register to write
|
||||
* @param value value to write
|
||||
*/
|
||||
void write(int offset, unsigned char value);
|
||||
|
||||
/**
|
||||
* SID voice muting.
|
||||
*
|
||||
* @param channel channel to modify
|
||||
* @param enable is muted?
|
||||
*/
|
||||
void mute(int channel, bool enable) { muted[channel] = enable; }
|
||||
|
||||
/**
|
||||
* Setting of SID sampling parameters.
|
||||
*
|
||||
* Use a clock freqency of 985248Hz for PAL C64, 1022730Hz for NTSC C64.
|
||||
* The default end of passband frequency is pass_freq = 0.9*sample_freq/2
|
||||
* for sample frequencies up to ~ 44.1kHz, and 20kHz for higher sample frequencies.
|
||||
*
|
||||
* For resampling, the ratio between the clock frequency and the sample frequency
|
||||
* is limited as follows: 125*clock_freq/sample_freq < 16384
|
||||
* E.g. provided a clock frequency of ~ 1MHz, the sample frequency can not be set
|
||||
* lower than ~ 8kHz. A lower sample frequency would make the resampling code
|
||||
* overfill its 16k sample ring buffer.
|
||||
*
|
||||
* The end of passband frequency is also limited: pass_freq <= 0.9*sample_freq/2
|
||||
*
|
||||
* E.g. for a 44.1kHz sampling rate the end of passband frequency
|
||||
* is limited to slightly below 20kHz.
|
||||
* This constraint ensures that the FIR table is not overfilled.
|
||||
*
|
||||
* @param clockFrequency System clock frequency at Hz
|
||||
* @param method sampling method to use
|
||||
* @param samplingFrequency Desired output sampling rate
|
||||
* @param highestAccurateFrequency
|
||||
* @throw SIDError
|
||||
*/
|
||||
void setSamplingParameters(double clockFrequency, SamplingMethod method, double samplingFrequency, double highestAccurateFrequency);
|
||||
|
||||
/**
|
||||
* Clock SID forward using chosen output sampling algorithm.
|
||||
*
|
||||
* @param cycles c64 clocks to clock
|
||||
* @param buf audio output buffer
|
||||
* @return number of samples produced
|
||||
*/
|
||||
int clock(unsigned int cycles, short* buf);
|
||||
|
||||
/**
|
||||
* Clock SID forward with no audio production.
|
||||
*
|
||||
* _Warning_:
|
||||
* You can't mix this method of clocking with the audio-producing
|
||||
* clock() because components that don't affect OSC3/ENV3 are not
|
||||
* emulated.
|
||||
*
|
||||
* @param cycles c64 clocks to clock.
|
||||
*/
|
||||
void clockSilent(unsigned int cycles);
|
||||
|
||||
/**
|
||||
* Set filter curve parameter for 6581 model.
|
||||
*
|
||||
* @see Filter6581::setFilterCurve(double)
|
||||
*/
|
||||
void setFilter6581Curve(double filterCurve);
|
||||
|
||||
/**
|
||||
* Set filter curve parameter for 8580 model.
|
||||
*
|
||||
* @see Filter8580::setFilterCurve(double)
|
||||
*/
|
||||
void setFilter8580Curve(double filterCurve);
|
||||
|
||||
/**
|
||||
* Enable filter emulation.
|
||||
*
|
||||
* @param enable false to turn off filter emulation
|
||||
*/
|
||||
void enableFilter(bool enable);
|
||||
};
|
||||
|
||||
} // namespace reSIDfp
|
||||
|
||||
#if RESID_INLINING || defined(SID_CPP)
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "Filter.h"
|
||||
#include "ExternalFilter.h"
|
||||
#include "Voice.h"
|
||||
#include "resample/Resampler.h"
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
RESID_INLINE
|
||||
void SID::ageBusValue(unsigned int n)
|
||||
{
|
||||
if (likely(busValueTtl != 0))
|
||||
{
|
||||
busValueTtl -= n;
|
||||
|
||||
if (unlikely(busValueTtl <= 0))
|
||||
{
|
||||
busValue = 0;
|
||||
busValueTtl = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RESID_INLINE
|
||||
int SID::output()
|
||||
{
|
||||
const int v1 = voice[0]->output(voice[2]->wave());
|
||||
const int v2 = voice[1]->output(voice[0]->wave());
|
||||
const int v3 = voice[2]->output(voice[1]->wave());
|
||||
|
||||
lastChanOut[0]=v1;
|
||||
lastChanOut[1]=v2;
|
||||
lastChanOut[2]=v3;
|
||||
|
||||
return externalFilter->clock(filter->clock(v1, v2, v3));
|
||||
}
|
||||
|
||||
|
||||
RESID_INLINE
|
||||
int SID::clock(unsigned int cycles, short* buf)
|
||||
{
|
||||
ageBusValue(cycles);
|
||||
int s = 0;
|
||||
|
||||
while (cycles != 0)
|
||||
{
|
||||
unsigned int delta_t = std::min(nextVoiceSync, cycles);
|
||||
|
||||
if (likely(delta_t > 0))
|
||||
{
|
||||
for (unsigned int i = 0; i < delta_t; i++)
|
||||
{
|
||||
// clock waveform generators
|
||||
voice[0]->wave()->clock();
|
||||
voice[1]->wave()->clock();
|
||||
voice[2]->wave()->clock();
|
||||
|
||||
// clock envelope generators
|
||||
voice[0]->envelope()->clock();
|
||||
voice[1]->envelope()->clock();
|
||||
voice[2]->envelope()->clock();
|
||||
|
||||
if (unlikely(resampler->input(output())))
|
||||
{
|
||||
buf[s++] = resampler->getOutput();
|
||||
}
|
||||
}
|
||||
|
||||
cycles -= delta_t;
|
||||
nextVoiceSync -= delta_t;
|
||||
}
|
||||
|
||||
if (unlikely(nextVoiceSync == 0))
|
||||
{
|
||||
voiceSync(true);
|
||||
}
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
} // namespace reSIDfp
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
119
src/engine/platform/sound/c64_fp/Spline.cpp
Normal file
119
src/engine/platform/sound/c64_fp/Spline.cpp
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2015 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
*
|
||||
* 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 "Spline.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <limits>
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
Spline::Spline(const std::vector<Point> &input) :
|
||||
params(input.size()),
|
||||
c(¶ms[0])
|
||||
{
|
||||
assert(input.size() > 2);
|
||||
|
||||
const size_t coeffLength = input.size() - 1;
|
||||
|
||||
std::vector<double> dxs(coeffLength);
|
||||
std::vector<double> ms(coeffLength);
|
||||
|
||||
// Get consecutive differences and slopes
|
||||
for (size_t i = 0; i < coeffLength; i++)
|
||||
{
|
||||
assert(input[i].x < input[i + 1].x);
|
||||
|
||||
const double dx = input[i + 1].x - input[i].x;
|
||||
const double dy = input[i + 1].y - input[i].y;
|
||||
dxs[i] = dx;
|
||||
ms[i] = dy/dx;
|
||||
}
|
||||
|
||||
// Get degree-1 coefficients
|
||||
params[0].c = ms[0];
|
||||
for (size_t i = 1; i < coeffLength; i++)
|
||||
{
|
||||
const double m = ms[i - 1];
|
||||
const double mNext = ms[i];
|
||||
if (m * mNext <= 0)
|
||||
{
|
||||
params[i].c = 0.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
const double dx = dxs[i - 1];
|
||||
const double dxNext = dxs[i];
|
||||
const double common = dx + dxNext;
|
||||
params[i].c = 3.0 * common / ((common + dxNext) / m + (common + dx) / mNext);
|
||||
}
|
||||
}
|
||||
params[coeffLength].c = ms[coeffLength - 1];
|
||||
|
||||
// Get degree-2 and degree-3 coefficients
|
||||
for (size_t i = 0; i < coeffLength; i++)
|
||||
{
|
||||
params[i].x1 = input[i].x;
|
||||
params[i].x2 = input[i + 1].x;
|
||||
params[i].d = input[i].y;
|
||||
|
||||
const double c1 = params[i].c;
|
||||
const double m = ms[i];
|
||||
const double invDx = 1.0 / dxs[i];
|
||||
const double common = c1 + params[i + 1].c - m - m;
|
||||
params[i].b = (m - c1 - common) * invDx;
|
||||
params[i].a = common * invDx * invDx;
|
||||
}
|
||||
|
||||
// Fix the upper range, because we interpolate outside original bounds if necessary.
|
||||
params[coeffLength - 1].x2 = std::numeric_limits<double>::max();
|
||||
}
|
||||
|
||||
Spline::Point Spline::evaluate(double x) const
|
||||
{
|
||||
if ((x < c->x1) || (x > c->x2))
|
||||
{
|
||||
for (size_t i = 0; i < params.size(); i++)
|
||||
{
|
||||
if (x <= params[i].x2)
|
||||
{
|
||||
c = ¶ms[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Interpolate
|
||||
const double diff = x - c->x1;
|
||||
|
||||
Point out;
|
||||
|
||||
// y = a*x^3 + b*x^2 + c*x + d
|
||||
out.x = ((c->a * diff + c->b) * diff + c->c) * diff + c->d;
|
||||
|
||||
// dy = 3*a*x^2 + 2*b*x + c
|
||||
out.y = (3.0 * c->a * diff + 2.0 * c->b) * diff + c->c;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace reSIDfp
|
||||
78
src/engine/platform/sound/c64_fp/Spline.h
Normal file
78
src/engine/platform/sound/c64_fp/Spline.h
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* This file is part of libsidplayfp, a SID player engine.
|
||||
*
|
||||
* Copyright 2011-2015 Leandro Nini <drfiemost@users.sourceforge.net>
|
||||
* Copyright 2007-2010 Antti Lankila
|
||||
*
|
||||
* 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 SPLINE_H
|
||||
#define SPLINE_H
|
||||
|
||||
#include <cstddef>
|
||||
#include <vector>
|
||||
|
||||
namespace reSIDfp
|
||||
{
|
||||
|
||||
/**
|
||||
* Fritsch-Carlson monotone cubic spline interpolation.
|
||||
*
|
||||
* Based on the implementation from the [Monotone cubic interpolation] wikipedia page.
|
||||
*
|
||||
* [Monotone cubic interpolation]: https://en.wikipedia.org/wiki/Monotone_cubic_interpolation
|
||||
*/
|
||||
class Spline
|
||||
{
|
||||
public:
|
||||
typedef struct
|
||||
{
|
||||
double x;
|
||||
double y;
|
||||
} Point;
|
||||
|
||||
private:
|
||||
typedef struct
|
||||
{
|
||||
double x1;
|
||||
double x2;
|
||||
double a;
|
||||
double b;
|
||||
double c;
|
||||
double d;
|
||||
} Param;
|
||||
|
||||
typedef std::vector<Param> ParamVector;
|
||||
|
||||
private:
|
||||
/// Interpolation parameters
|
||||
ParamVector params;
|
||||
|
||||
/// Last used parameters, cached for speed up
|
||||
mutable ParamVector::const_pointer c;
|
||||
|
||||
public:
|
||||
Spline(const std::vector<Point> &input);
|
||||
|
||||
/**
|
||||
* Evaluate y and its derivative at given point x.
|
||||
*/
|
||||
Point evaluate(double x) const;
|
||||
};
|
||||
|
||||
} // namespace reSIDfp
|
||||
|
||||
#endif
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue