Merge branch 'tildearrow:master' into master

This commit is contained in:
Eknous 2023-07-06 17:47:44 +04:00 committed by GitHub
commit 00031c4658
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 690 additions and 93 deletions

View file

@ -455,6 +455,8 @@ src/engine/platform/sound/c64_fp/WaveformCalculator.cpp
src/engine/platform/sound/c64_fp/WaveformGenerator.cpp
src/engine/platform/sound/c64_fp/resample/SincResampler.cpp
src/engine/platform/sound/c64_d/dsid.c
src/engine/platform/sound/tia/AudioChannel.cpp
src/engine/platform/sound/tia/Audio.cpp
@ -860,7 +862,7 @@ endif()
string(REPLACE ";" " " WARNING_FLAGS_STRING "${WARNING_FLAGS}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WARNING_FLAGS_STRING}")
if (WARNINGS_ARE_ERRORS)
message(WARNING
message(STATUS
"Treating all warnings in furnace's C++ code as errors! "
"Please report any errors you encounter on the bug tracker."
)

View file

@ -13,3 +13,5 @@ currently Furnace does not support the PCM channel's stereo mode, though (except
- `2`: triangle
- `3`: noise
- `22xx`: **set duty cycle.** range is `0` to `3F`.
- `EExx`: **ZSM synchronization event.**
- Where `xx` is the event payload. This has no effect in how the music is played in Furnace, but the ZSMKit library for the Commander X16 interprets these events inside ZSM files and optionally triggers a callback routine. This can be used, for instance, to cause game code to respond to beats or at certain points in the music.

View file

@ -99,7 +99,7 @@ Any offset values contained in the PCM data header block are relative to the beg
### PCM Sample Data
This is blob of PCM data with no internal formatting. Offsets into this blob are provided via the PCM header. The end of this blob will be the end of the ZSM file.
This is a blob of PCM data with no internal formatting. Offsets into this blob are provided via the PCM header. The end of this blob will be the end of the ZSM file.
## EXTCMD Channel Scifications
@ -149,7 +149,7 @@ Players implementing this channel should implement detection routines during ini
An expansion HW write will contain the following data:
Chip ID|Nuber of writes (`N`)| `N` tuples of data
Chip ID|Number of writes (`N`)| `N` tuples of data
--|--|--
one byte|one byte|N * tuple_size bytes
@ -162,7 +162,14 @@ There are currently no supported expansion HW IDs assigned.
The purpose of this channel is to provide for music synchronization cues that applications may use to perform operations in sync with the music (such as when the Goombas jump in New Super Mario Bros in time with the BOP! BOP! notes in the music). It is intended for the reference player to provide a sync channel callback, passing the data bytes to the callback function, and then to proceed with playback.
The data structure within this channel is not yet defined. It is our intention to work with the community in order to collaborate on a useful structure.
The synchronization format currently defines this one event type:
Event Type|Description|Message Format
--|--|--
`0x00`|Generic sync message|`xx` (any value from `0x00`-`0xff`)
An example of an EXTCMD containing one sync event might look as follows: `0x40 0x82 0x00 0x05`
#### 3: Custom

View file

@ -236,6 +236,8 @@ enum DivDispatchCmds {
DIV_CMD_NES_LINEAR_LENGTH,
DIV_CMD_EXTERNAL, // (value)
DIV_ALWAYS_SET_VOLUME, // () -> alwaysSetVol
DIV_CMD_MAX

View file

@ -172,6 +172,7 @@ void DivDispatchContainer::fillBuf(size_t runtotal, size_t offset, size_t size)
if (bbIn[i]==NULL) continue;
if (bb[i]==NULL) continue;
for (size_t j=0; j<runtotal; j++) {
if (bbIn[i][j]==temp[i]) continue;
temp[i]=bbIn[i][j];
blip_add_delta_fast(bb[i],j,temp[i]-prevSample[i]);
prevSample[i]=temp[i];
@ -182,6 +183,7 @@ void DivDispatchContainer::fillBuf(size_t runtotal, size_t offset, size_t size)
if (bbIn[i]==NULL) continue;
if (bb[i]==NULL) continue;
for (size_t j=0; j<runtotal; j++) {
if (bbIn[i][j]==temp[i]) continue;
temp[i]=bbIn[i][j];
blip_add_delta(bb[i],j,temp[i]-prevSample[i]);
prevSample[i]=temp[i];
@ -273,12 +275,12 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
break;
case DIV_SYSTEM_C64_6581:
dispatch=new DivPlatformC64;
((DivPlatformC64*)dispatch)->setFP(eng->getConfInt("c64Core",1)==1);
((DivPlatformC64*)dispatch)->setCore(eng->getConfInt("c64Core",0));
((DivPlatformC64*)dispatch)->setChipModel(true);
break;
case DIV_SYSTEM_C64_8580:
dispatch=new DivPlatformC64;
((DivPlatformC64*)dispatch)->setFP(eng->getConfInt("c64Core",1)==1);
((DivPlatformC64*)dispatch)->setCore(eng->getConfInt("c64Core",0));
((DivPlatformC64*)dispatch)->setChipModel(false);
break;
case DIV_SYSTEM_YM2151:

View file

@ -65,34 +65,45 @@ const char** DivPlatformC64::getRegisterSheet() {
}
void DivPlatformC64::acquire(short** buf, size_t len) {
int dcOff=isFP?0:sid.get_dc(0);
int dcOff=(sidCore)?0:sid->get_dc(0);
for (size_t i=0; i<len; i++) {
if (!writes.empty()) {
QueuedWrite w=writes.front();
if (isFP) {
sid_fp.write(w.addr,w.val);
if (sidCore==2) {
dSID_write(sid_d,w.addr,w.val);
} else if (sidCore==1) {
sid_fp->write(w.addr,w.val);
} else {
sid.write(w.addr,w.val);
};
sid->write(w.addr,w.val);
}
regPool[w.addr&0x1f]=w.val;
writes.pop();
}
if (isFP) {
sid_fp.clock(4,&buf[0][i]);
if (sidCore==2) {
double o=dSID_render(sid_d);
buf[0][i]=32767*CLAMP(o,-1.0,1.0);
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;
oscBuf[0]->data[oscBuf[0]->needle++]=sid_d->lastOut[0];
oscBuf[1]->data[oscBuf[1]->needle++]=sid_d->lastOut[1];
oscBuf[2]->data[oscBuf[2]->needle++]=sid_d->lastOut[2];
}
} else if (sidCore==1) {
sid_fp->clock(4,&buf[0][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();
buf[0][i]=sid.output();
sid->clock();
buf[0][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;
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;
}
}
}
@ -366,7 +377,7 @@ int DivPlatformC64::dispatch(DivCommand c) {
break;
case DIV_CMD_C64_CUTOFF:
if (c.value>100) c.value=100;
filtCut=c.value*2047/100;
filtCut=(c.value+2)*2047/102;
updateFilter();
break;
case DIV_CMD_C64_FINE_CUTOFF:
@ -452,10 +463,17 @@ int DivPlatformC64::dispatch(DivCommand c) {
void DivPlatformC64::muteChannel(int ch, bool mute) {
isMuted[ch]=mute;
if (isFP) {
sid_fp.mute(ch,mute);
if (sidCore==2) {
dSID_setMuteMask(
sid_d,
(isMuted[0]?0:1)|
(isMuted[1]?0:2)|
(isMuted[2]?0:4)
);
} else if (sidCore==1) {
sid_fp->mute(ch,mute);
} else {
sid.set_is_muted(ch,mute);
sid->set_is_muted(ch,mute);
}
}
@ -514,7 +532,7 @@ bool DivPlatformC64::getWantPreNote() {
}
float DivPlatformC64::getPostAmp() {
return isFP?3.0f:1.0f;
return (sidCore==1)?3.0f:1.0f;
}
void DivPlatformC64::reset() {
@ -524,11 +542,20 @@ void DivPlatformC64::reset() {
chan[i].std.setEngine(parent);
}
if (isFP) {
sid_fp.reset();
sid_fp.clockSilent(16000);
if (sidCore==2) {
dSID_init(sid_d,chipClock,rate,sidIs6581?6581:8580,needInitTables);
dSID_setMuteMask(
sid_d,
(isMuted[0]?0:1)|
(isMuted[1]?0:2)|
(isMuted[2]?0:4)
);
needInitTables=false;
} else if (sidCore==1) {
sid_fp->reset();
sid_fp->clockSilent(16000);
} else {
sid.reset();
sid->reset();
}
memset(regPool,0,32);
@ -554,23 +581,11 @@ void DivPlatformC64::poke(std::vector<DivRegWrite>& wlist) {
}
void DivPlatformC64::setChipModel(bool is6581) {
if (is6581) {
if (isFP) {
sid_fp.setChipModel(reSIDfp::MOS6581);
} else {
sid.set_chip_model(MOS6581);
}
} else {
if (isFP) {
sid_fp.setChipModel(reSIDfp::MOS8580);
} else {
sid.set_chip_model(MOS8580);
}
}
sidIs6581=is6581;
}
void DivPlatformC64::setFP(bool fp) {
isFP=fp;
void DivPlatformC64::setCore(unsigned char which) {
sidCore=which;
}
void DivPlatformC64::setFlags(const DivConfig& flags) {
@ -591,9 +606,9 @@ void DivPlatformC64::setFlags(const DivConfig& flags) {
for (int i=0; i<3; i++) {
oscBuf[i]->rate=rate/16;
}
if (isFP) {
if (sidCore>0) {
rate/=4;
sid_fp.setSamplingParameters(chipClock,reSIDfp::DECIMATE,rate,0);
if (sidCore==1) sid_fp->setSamplingParameters(chipClock,reSIDfp::DECIMATE,rate,0);
}
keyPriority=flags.getBool("keyPriority",true);
testAD=((flags.getInt("testAttack",0)&15)<<4)|(flags.getInt("testDecay",0)&15);
@ -604,11 +619,45 @@ int DivPlatformC64::init(DivEngine* p, int channels, int sugRate, const DivConfi
parent=p;
dumpWrites=false;
skipRegisterWrites=false;
needInitTables=true;
writeOscBuf=0;
for (int i=0; i<3; i++) {
isMuted[i]=false;
oscBuf[i]=new DivDispatchOscBuffer;
}
if (sidCore==2) {
sid=NULL;
sid_fp=NULL;
sid_d=new struct SID_chip;
} else if (sidCore==1) {
sid=NULL;
sid_fp=new reSIDfp::SID;
sid_d=NULL;
} else {
sid=new SID;
sid_fp=NULL;
sid_d=NULL;
}
if (sidIs6581) {
if (sidCore==2) {
// do nothing
} else if (sidCore==1) {
sid_fp->setChipModel(reSIDfp::MOS6581);
} else {
sid->set_chip_model(MOS6581);
}
} else {
if (sidCore==2) {
// do nothing
} else if (sidCore==1) {
sid_fp->setChipModel(reSIDfp::MOS8580);
} else {
sid->set_chip_model(MOS8580);
}
}
setFlags(flags);
reset();
@ -620,6 +669,9 @@ void DivPlatformC64::quit() {
for (int i=0; i<3; i++) {
delete oscBuf[i];
}
if (sid!=NULL) delete sid;
if (sid_fp!=NULL) delete sid_fp;
if (sid_d!=NULL) delete sid_d;
}
DivPlatformC64::~DivPlatformC64() {

View file

@ -24,6 +24,7 @@
#include <queue>
#include "sound/c64/sid.h"
#include "sound/c64_fp/SID.h"
#include "sound/c64_d/dsid.h"
class DivPlatformC64: public DivDispatch {
struct Channel: public SharedChannel<signed char> {
@ -64,15 +65,16 @@ class DivPlatformC64: public DivDispatch {
unsigned char filtControl, filtRes, vol;
unsigned char writeOscBuf;
unsigned char sidCore;
int filtCut, resetTime;
bool isFP;
bool keyPriority;
bool keyPriority, sidIs6581, needInitTables;
unsigned char chanOrder[3];
unsigned char testAD, testSR;
SID sid;
reSIDfp::SID sid_fp;
SID* sid;
reSIDfp::SID* sid_fp;
struct SID_chip* sid_d;
unsigned char regPool[32];
friend void putDispatchChip(void*,int);
@ -105,7 +107,7 @@ class DivPlatformC64: public DivDispatch {
const char** getRegisterSheet();
int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags);
void setChipModel(bool is6581);
void setFP(bool fp);
void setCore(unsigned char which);
void quit();
~DivPlatformC64();
};

View file

@ -0,0 +1,19 @@
Copyright (c) 2021 DefleMask Team
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,11 @@
dSID
===
This is the SID core used in DefleMask.
The project started as a very careful port from jsSID, comparing the wave
output from both, ensuring they were exactly the same.
## License
MIT License

View file

@ -0,0 +1,367 @@
#include "dsid.h"
#include <stdio.h>
#include <math.h> // INFINITY
#include <stdlib.h>
#include <string.h> // memset, memcpy
#define SID_OUT_SCALE (0x10000 * 3 * 16)
// CONTROL
#define GAT 0x01
#define SYN 0x02
#define RNG 0x04
#define TST 0x08
#define TRI 0x10
#define SAW 0x20
#define PUL 0x40
#define NOI 0x80
#define _HZ 0x10
#define DECSUS 0x40
#define ATK 0x80
// filter mode (high)
#define LP 0x10
#define BP 0x20
#define HP 0x40
#define OFF3 0x80
#define waveforms_add_sample(_id,_s) \
sid->lastOut[_id]=(_s);
const int Aexp[256] = {
1, 30, 30, 30, 30, 30, 30, 16, 16, 16, 16, 16, 16, 16, 16, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,
8, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
4, 4, 4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
double cmbWF(int chn, int *wfa, int index, int differ6581, struct SID_globals *g) {
if (differ6581 && g->model == 6581)
index &= 0x7FF;
return wfa[index];
}
void cCmbWF(int *wfa, double bitmul, double bstr, double trh) {
for (int i = 0; i < 4096; i++) {
wfa[i] = 0;
for (int j = 0; j < 12; j++) {
double blvl = 0;
for (int k = 0; k < 12; k++) {
blvl += (bitmul / pow(bstr, abs(k - j))) * (((i >> k) & 1) - 0.5);
}
wfa[i] += (blvl >= trh) ? pow(2, j) : 0;
}
wfa[i] *= 12;
}
}
void dSID_init(struct SID_chip* sid, double clockRate, double samplingRate, int model, unsigned char init_wf) {
if (model == 6581) {
sid->g.model = 6581;
} else {
sid->g.model = 8580;
}
memset(sid->M,0,MemLen);
memset(sid->SIDct, 0, sizeof(sid->SIDct));
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
sid->SIDct[i].ch[j].Ast = _HZ;
sid->SIDct[i].ch[j].nLFSR = 0x7FFFF8;
sid->SIDct[i].ch[j].prevwfout = 0;
}
sid->SIDct[i].ch[0].FSW = 1;
sid->SIDct[i].ch[1].FSW = 2;
sid->SIDct[i].ch[2].FSW = 4;
}
sid->g.ctfr = -2.0 * 3.14 * (12500.0 / 256.0) / samplingRate,
sid->g.ctf_ratio_6581 = -2.0 * 3.14 * (samplingRate / 44100.0) * (20000.0 / 256.0) / samplingRate;
sid->g.ckr = clockRate / samplingRate;
const double bAprd[16] = {9, 32 * 1, 63 * 1, 95 * 1, 149 * 1, 220 * 1,
267 * 1, 313 * 1, 392 * 1, 977 * 1, 1954 * 1, 3126 * 1,
3907 * 1, 11720 * 1, 19532 * 1, 31251 * 1};
const int bAstp[16] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
memcpy(&sid->g.Aprd, &bAprd, sizeof(bAprd));
memcpy(&sid->g.Astp, &bAstp, sizeof(bAstp));
if (init_wf) {
cCmbWF(sid->g.trsaw, 0.8, 2.4, 0.64);
cCmbWF(sid->g.pusaw, 1.4, 1.9, 0.68);
cCmbWF(sid->g.Pulsetrsaw, 0.8, 2.5, 0.64);
for (int i = 0; i < 2048; i++) {
double ctf = (double) i / 8.0 + 0.2;
if (model == 8580) {
ctf = 1 - exp(ctf * sid->g.ctfr);
} else {
if (ctf < 24) {
ctf = 2.0 * sin(771.78 / samplingRate);
} else {
ctf = (44100.0 / samplingRate) - 1.263 * (44100.0 / samplingRate) * exp(ctf * sid->g.ctf_ratio_6581);
}
}
sid->g.ctf_table[i] = ctf;
}
}
double prd0 = sid->g.ckr > 9 ? sid->g.ckr : 9;
sid->g.Aprd[0] = prd0;
sid->g.Astp[0] = ceil(prd0 / 9);
}
double dSID_render(struct SID_chip* sid) {
double flin = 0, output = 0;
double wfout = 0;
for (int chn = 0; chn < 3; chn++) {
struct SIDVOICE *voic = &((struct SIDMEM *) (sid->M))->v[chn];
double pgt = (sid->SIDct->ch[chn].Ast & GAT);
uint8_t ctrl = voic->control;
uint8_t wf = ctrl & 0xF0;
uint8_t test = ctrl & TST;
uint8_t SR = voic->susres;
double tmp = 0;
if (pgt != (ctrl & GAT)) {
if (pgt) {
sid->SIDct->ch[chn].Ast &= 0xFF - (GAT | ATK | DECSUS);
} else {
sid->SIDct->ch[chn].Ast = (GAT | ATK | DECSUS);
if ((SR & 0xF) > (sid->SIDct->ch[chn].pSR & 0xF))
tmp = 1;
}
}
sid->SIDct->ch[chn].pSR = SR;
sid->SIDct->ch[chn].rcnt += sid->g.ckr;
if (sid->SIDct->ch[chn].rcnt >= 0x8000)
sid->SIDct->ch[chn].rcnt -= 0x8000;
static double step;
double prd;
if (sid->SIDct->ch[chn].Ast & ATK) {
step = voic->attack;
prd = sid->g.Aprd[(int) step];
} else if (sid->SIDct->ch[chn].Ast & DECSUS) {
step = voic->decay;
prd = sid->g.Aprd[(int) step];
} else {
step = SR & 0xF;
prd = sid->g.Aprd[(int) step];
}
step = sid->g.Astp[(int) step];
if (sid->SIDct->ch[chn].rcnt >= prd && sid->SIDct->ch[chn].rcnt < prd + sid->g.ckr &&
tmp == 0) {
sid->SIDct->ch[chn].rcnt -= prd;
if ((sid->SIDct->ch[chn].Ast & ATK) ||
++sid->SIDct->ch[chn].expcnt == Aexp[(int) sid->SIDct->ch[chn].envcnt]) {
if (!(sid->SIDct->ch[chn].Ast & _HZ)) {
if (sid->SIDct->ch[chn].Ast & ATK) {
sid->SIDct->ch[chn].envcnt += step;
if (sid->SIDct->ch[chn].envcnt >= 0xFF) {
sid->SIDct->ch[chn].envcnt = 0xFF;
sid->SIDct->ch[chn].Ast &= 0xFF - ATK;
}
} else if (!(sid->SIDct->ch[chn].Ast & DECSUS) ||
sid->SIDct->ch[chn].envcnt > (SR >> 4) + (SR & 0xF0)) {
sid->SIDct->ch[chn].envcnt -= step;
if (sid->SIDct->ch[chn].envcnt <= 0 &&
sid->SIDct->ch[chn].envcnt + step != 0) {
sid->SIDct->ch[chn].envcnt = 0;
sid->SIDct->ch[chn].Ast |= _HZ;
}
}
}
sid->SIDct->ch[chn].expcnt = 0;
} else {
}
}
sid->SIDct->ch[chn].envcnt = (int) sid->SIDct->ch[chn].envcnt & 0xFF;
double aAdd = (voic->freq_low + voic->freq_high * 256) * sid->g.ckr;
if (test || ((ctrl & SYN) && sid->SIDct->sMSBrise)) {
sid->SIDct->ch[chn].pacc = 0;
} else {
sid->SIDct->ch[chn].pacc += aAdd;
if (sid->SIDct->ch[chn].pacc > 0xFFFFFF)
sid->SIDct->ch[chn].pacc -= 0x1000000;
}
double MSB = (int) sid->SIDct->ch[chn].pacc & 0x800000;
sid->SIDct->sMSBrise = (MSB > ((int) sid->SIDct->ch[chn].pracc & 0x800000)) ? 1 : 0;
if (wf & NOI) {
tmp = sid->SIDct->ch[chn].nLFSR;
if ((((int) sid->SIDct->ch[chn].pacc & 0x100000) !=
((int) sid->SIDct->ch[chn].pracc & 0x100000)) ||
aAdd >= 0x100000) {
step = ((int) tmp & 0x400000) ^ (((int) tmp & 0x20000) << 5);
tmp = (((int) tmp << 1) + (step > 0 || test)) & 0x7FFFFF;
sid->SIDct->ch[chn].nLFSR = tmp;
}
wfout = (wf & 0x70) ? 0
: (((int) tmp & 0x100000) >> 5) + (((int) tmp & 0x40000) >> 4) +
(((int) tmp & 0x4000) >> 1) + (((int) tmp & 0x800) << 1) +
(((int) tmp & 0x200) << 2) + (((int) tmp & 0x20) << 5) +
(((int) tmp & 0x04) << 7) + (((int) tmp & 0x01) << 8);
} else if (wf & PUL) {
double pw = (voic->pw_low + (voic->pw_high) * 256) * 16;
tmp = (int) aAdd >> 9;
if (0 < pw && pw < tmp)
pw = tmp;
tmp = (int) tmp ^ 0xFFFF;
if (pw > tmp)
pw = tmp;
tmp = (int) sid->SIDct->ch[chn].pacc >> 8;
if (wf == PUL) {
int lel = ((int) aAdd >> 16);
if (lel > 0) {
step = 256.0 / (double) lel;
} else {
step = INFINITY;
}
if (test)
wfout = 0xFFFF;
else if (tmp < pw) {
double lim = (0xFFFF - pw) * step;
if (lim > 0xFFFF)
lim = 0xFFFF;
wfout = lim - (pw - tmp) * step;
if (wfout < 0)
wfout = 0;
} else {
double lim = pw * step;
if (lim > 0xFFFF)
lim = 0xFFFF;
wfout = (0xFFFF - tmp) * step - lim;
if (wfout >= 0)
wfout = 0xFFFF;
wfout = (int) wfout & 0xFFFF;
}
} else {
wfout = (tmp >= pw || test) ? 0xFFFF : 0;
if (wf & TRI) {
if (wf & SAW) {
wfout =
(wfout) ? cmbWF(chn, sid->g.Pulsetrsaw, (int) tmp >> 4, 1, &sid->g) : 0;
} else {
tmp = (int) sid->SIDct->ch[chn].pacc ^ (ctrl & RNG ? sid->SIDct->sMSB : 0);
wfout =
(wfout)
? cmbWF(chn, sid->g.pusaw,
((int) tmp ^ ((int) tmp & 0x800000 ? 0xFFFFFF : 0)) >> 11,
0, &sid->g)
: 0;
}
} else if (wf & SAW)
wfout = (wfout) ? cmbWF(chn, sid->g.pusaw, (int) tmp >> 4, 1, &sid->g) : 0;
}
} else if (wf & SAW) {
wfout = (int) sid->SIDct->ch[chn].pacc >> 8;
if (wf & TRI)
wfout = cmbWF(chn, sid->g.trsaw, (int) wfout >> 4, 1, &sid->g);
else {
step = aAdd / 0x1200000;
wfout += wfout * step;
if (wfout > 0xFFFF)
wfout = 0xFFFF - (wfout - 0x10000) / step;
}
} else if (wf & TRI) {
tmp = (int) sid->SIDct->ch[chn].pacc ^ (ctrl & RNG ? sid->SIDct->sMSB : 0);
wfout = ((int) tmp ^ ((int) tmp & 0x800000 ? 0xFFFFFF : 0)) >> 7;
}
if (wf)
sid->SIDct->ch[chn].prevwfout = wfout;
else {
wfout = sid->SIDct->ch[chn].prevwfout;
}
sid->SIDct->ch[chn].pracc = sid->SIDct->ch[chn].pacc;
sid->SIDct->sMSB = MSB;
// double preflin = flin;
if ((sid->mute_mask & (1 << chn))) {
if (sid->M[0x17] & sid->SIDct->ch[chn].FSW) {
double chnout = (wfout - 0x8000) * (sid->SIDct->ch[chn].envcnt / 256);
flin += chnout;
// fake filter for solo waveform ahead
// mostly copypasted from below
double fakeflin = chnout;
double fakeflout = 0;
static double fakeplp[3] = {0};
static double fakepbp[3] = {0};
double ctf = sid->g.ctf_table[((sid->M[0x15]&7)|(sid->M[0x16]<<3))&0x7ff];
double reso;
if (sid->g.model == 8580) {
reso = pow(2, ((double) (4 - (double) (sid->M[0x17] >> 4)) / 8));
} else {
reso = (sid->M[0x17] > 0x5F) ? 8.0 / (double) (sid->M[0x17] >> 4) : 1.41;
}
double tmp = fakeflin + fakepbp[chn] * reso + fakeplp[chn];
if (sid->M[0x18] & HP)
fakeflout -= tmp;
tmp = fakepbp[chn] - tmp * ctf;
fakepbp[chn] = tmp;
if (sid->M[0x18] & BP)
fakeflout -= tmp;
tmp = fakeplp[chn] + tmp * ctf;
fakeplp[chn] = tmp;
if (sid->M[0x18] & LP)
fakeflout += tmp;
double wf_out = (fakeflout / SID_OUT_SCALE) * (sid->M[0x18] & 0xF) * 65535;
waveforms_add_sample(chn, wf_out);
} else if ((chn % 3) != 2 || !(sid->M[0x18] & OFF3)) {
double chnout = (wfout - 0x8000) * (sid->SIDct->ch[chn].envcnt / 256);
output += chnout;
double wf_out = (chnout / SID_OUT_SCALE) * (sid->M[0x18] & 0xF) * 65535;
waveforms_add_sample(chn, wf_out);
}
} else {
waveforms_add_sample(chn, 0);
}
}
int M1 = 0;
if (M1 & 3)
sid->M[0x1B] = (int) wfout >> 8;
sid->M[0x1C] = sid->SIDct->ch[2].envcnt;
double ctf = sid->g.ctf_table[((sid->M[0x15]&7)|(sid->M[0x16]<<3))&0x7ff];
double reso;
if (sid->g.model == 8580) {
reso = pow(2, ((double) (4 - (double) (sid->M[0x17] >> 4)) / 8));
} else {
reso = (sid->M[0x17] > 0x5F) ? 8.0 / (double) (sid->M[0x17] >> 4) : 1.41;
}
double tmp = flin + sid->SIDct->pbp * reso + sid->SIDct->plp;
if (sid->M[0x18] & HP)
output -= tmp;
tmp = sid->SIDct->pbp - tmp * ctf;
sid->SIDct->pbp = tmp;
if (sid->M[0x18] & BP)
output -= tmp;
tmp = sid->SIDct->plp + tmp * ctf;
sid->SIDct->plp = tmp;
if (sid->M[0x18] & LP)
output += tmp;
return (output / SID_OUT_SCALE) * (sid->M[0x18] & 0xF);
}
void dSID_setMuteMask(struct SID_chip* sid, int mute_mask) {
sid->mute_mask = mute_mask;
}
float dSID_getVolume(struct SID_chip* sid, int channel) {
if ((sid->M[0x18] & 0xF) == 0)
return 0;
return sid->SIDct[0].ch[channel].envcnt / 256.0f;
}
void dSID_write(struct SID_chip* sid, unsigned char addr, unsigned char val) {
sid->M[addr&0x1f]=val;
}

View file

@ -0,0 +1,97 @@
#ifndef DSID_H
#define DSID_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
struct SID_ctx_chan {
double rcnt;
double envcnt;
double expcnt;
double pacc;
double pracc;
int FSW;
int nLFSR;
double prevwfout;
uint8_t pSR;
int Ast;
};
struct SID_ctx {
int sMSBrise;
int sMSB;
double plp;
double pbp;
struct SID_ctx_chan ch[3];
};
struct SIDVOICE {
uint8_t freq_low;
uint8_t freq_high;
uint8_t pw_low;
uint8_t pw_high : 4;
uint8_t UNUSED : 4;
uint8_t control;
uint8_t decay : 4;
uint8_t attack : 4;
uint8_t susres;
// uint8_t release : 4;
// uint8_t sustain : 4;
};
struct SIDMEM {
struct SIDVOICE v[3];
uint8_t UNUSED : 4;
uint8_t cutoff_low : 4;
uint8_t cutoff_high;
uint8_t reso_rt : 4;
uint8_t reso : 4;
uint8_t volume : 4;
uint8_t filter_mode : 4;
uint8_t paddlex;
uint8_t paddley;
uint8_t osc3;
uint8_t env3;
};
struct SID_globals {
double ckr;
double ctfr;
double ctf_ratio_6581;
double ctf_table[2048];
int trsaw[4096];
int pusaw[4096];
int Pulsetrsaw[4096];
double Aprd[16];
int Astp[16];
int model;
};
#define MemLen 65536
struct SID_chip {
struct SID_globals g;
struct SID_ctx SIDct[3];
uint8_t M[MemLen];
int16_t lastOut[3];
int mute_mask;
};
double dSID_render(struct SID_chip* sid);
void dSID_init(struct SID_chip* sid, double clockRate, double samplingRate, int model, unsigned char init_wf);
float dSID_getVolume(struct SID_chip* sid, int channel);
void dSID_setMuteMask(struct SID_chip* sid, int mute_mask);
void dSID_write(struct SID_chip* sid, unsigned char addr, unsigned char val);
#ifdef __cplusplus
}
#endif
#endif

View file

@ -37,6 +37,7 @@ extern "C" {
#define rWritePCMRate(d) {regPool[65]=(d); pcm_write_rate(pcm,d);if (dumpWrites) addWrite(65,(d));}
#define rWritePCMData(d) {regPool[66]=(d); pcm_write_fifo(pcm,d);}
#define rWritePCMVol(d) rWritePCMCtrl((regPool[64]&(~0x8f))|((d)&15))
#define rWriteZSMSync(d) {if (dumpWrites) addWrite(68,(d));}
const char* regCheatSheetVERA[]={
"CHxFreq", "00+x*4",
@ -46,6 +47,7 @@ const char* regCheatSheetVERA[]={
"AUDIO_CTRL", "40",
"AUDIO_RATE", "41",
"AUDIO_DATA", "42",
"ZSM_SYNC", "44",
NULL
};
@ -414,6 +416,9 @@ int DivPlatformVERA::dispatch(DivCommand c) {
case DIV_CMD_MACRO_ON:
chan[c.chan].std.mask(c.value,false);
break;
case DIV_CMD_EXTERNAL:
rWriteZSMSync(c.value);
break;
case DIV_ALWAYS_SET_VOLUME:
return 0;
break;

View file

@ -51,7 +51,7 @@ class DivPlatformVERA: public DivDispatch {
Channel chan[17];
DivDispatchOscBuffer* oscBuf[17];
bool isMuted[17];
unsigned char regPool[67];
unsigned char regPool[69];
struct VERA_PSG* psg;
struct VERA_PCM* pcm;

View file

@ -236,6 +236,8 @@ const char* cmdName[]={
"NES_LINEAR_LENGTH",
"EXTERNAL",
"ALWAYS_SET_VOLUME"
};
@ -913,6 +915,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
//printf("\x1b[1;36m%d: extern command %d\x1b[m\n",i,effectVal);
extValue=effectVal;
extValuePresent=true;
dispatchCmd(DivCommand(DIV_CMD_EXTERNAL,effectVal));
break;
case 0xef: // global pitch
globalPitch+=(signed char)(effectVal-0x80);
@ -1980,14 +1983,10 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi
} else {
// 3. run MIDI clock
int midiTotal=MIN(cycles,runLeftG);
for (int i=0; i<midiTotal; i++) {
runMidiClock();
}
runMidiClock(midiTotal);
// 4. run MIDI timecode
for (int i=0; i<midiTotal; i++) {
runMidiTime();
}
runMidiTime(midiTotal);
// 5. tick the clock and fill buffers as needed
if (cycles<runLeftG) {

View file

@ -118,9 +118,13 @@ void DivZSM::writePSG(unsigned char a, unsigned char v) {
// TODO: suppress writes to PSG voice that is not audible (volume=0)
// ^ Let's leave these alone, ZSMKit has a feature that can benefit
// from silent channels.
if (a>=67) {
logD ("ZSM: ignoring VERA PSG write a=%02x v=%02x",a,v);
if (a>=69) {
logD("ZSM: ignoring VERA PSG write a=%02x v=%02x",a,v);
return;
} else if (a==68) {
// Sync event
numWrites++;
return syncCache.push_back(v);
} else if (a>=64) {
return writePCM(a-64,v);
}
@ -259,7 +263,7 @@ SafeWriter* DivZSM::finish() {
}
void DivZSM::flushWrites() {
logD("ZSM: flushWrites.... numwrites=%d ticks=%d ymwrites=%d pcmMeta=%d pcmCache=%d pcmData=%d",numWrites,ticks,ymwrites.size(),pcmMeta.size(),pcmCache.size(),pcmData.size());
logD("ZSM: flushWrites.... numwrites=%d ticks=%d ymwrites=%d pcmMeta=%d pcmCache=%d pcmData=%d syncCache=%d",numWrites,ticks,ymwrites.size(),pcmMeta.size(),pcmCache.size(),pcmData.size(),syncCache.size());
if (numWrites==0) return;
flushTicks(); // only flush ticks if there are writes pending.
for (unsigned char i=0; i<64; i++) {
@ -287,43 +291,43 @@ void DivZSM::flushWrites() {
unsigned int pcmInst=0;
int pcmOff=0;
int pcmLen=0;
int extCmdLen=pcmMeta.size()*2;
int extCmd0Len=pcmMeta.size()*2;
if (pcmCache.size()) {
// collapse stereo data to mono if both channels are fully identical
// which cuts PCM data size in half for center-panned PCM events
if (pcmCtrlDCCache & 0x10) { // stereo bit is on
if (pcmCtrlDCCache&0x10) { // stereo bit is on
unsigned int e;
if (pcmCtrlDCCache & 0x20) { // 16-bit
if (pcmCtrlDCCache&0x20) { // 16-bit
// for 16-bit PCM data, the size must be a multiple of 4
if (pcmCache.size()%4==0) {
// check for identical L+R channels
for (e=0;e<pcmCache.size();e+=4) {
for (e=0; e<pcmCache.size(); e+=4) {
if (pcmCache[e]!=pcmCache[e+2] || pcmCache[e+1]!=pcmCache[e+3]) break;
}
if (e==pcmCache.size()) { // did not find a mismatch
// collapse the data to mono 16-bit
for (e=0;e<pcmCache.size()>>1;e+=2) {
for (e=0; e<pcmCache.size()>>1; e+=2) {
pcmCache[e]=pcmCache[e<<1];
pcmCache[e+1]=pcmCache[(e<<1)+1];
}
pcmCache.resize(pcmCache.size()>>1);
pcmCtrlDCCache &= ~0x10; // clear stereo bit
pcmCtrlDCCache&=(unsigned char)~0x10; // clear stereo bit
}
}
} else { // 8-bit
// for 8-bit PCM data, the size must be a multiple of 2
if (pcmCache.size()%2==0) {
// check for identical L+R channels
for (e=0;e<pcmCache.size();e+=2) {
for (e=0; e<pcmCache.size(); e+=2) {
if (pcmCache[e]!=pcmCache[e+1]) break;
}
if (e==pcmCache.size()) { // did not find a mismatch
// collapse the data to mono 8-bit
for (e=0;e<pcmCache.size()>>1;e++) {
for (e=0; e<pcmCache.size()>>1; e++) {
pcmCache[e]=pcmCache[e<<1];
}
pcmCache.resize(pcmCache.size()>>1);
pcmCtrlDCCache &= ~0x10; // clear stereo bit
pcmCtrlDCCache&=(unsigned char)~0x10; // clear stereo bit
}
}
}
@ -340,7 +344,7 @@ void DivZSM::flushWrites() {
pcmData.insert(pcmData.end(),pcmCache.begin(),pcmCache.end());
}
pcmCache.clear();
extCmdLen+=2;
extCmd0Len+=2;
// search for a matching PCM instrument definition
for (S_pcmInst& inst: pcmInsts) {
if (inst.offset==pcmOff && inst.length==pcmLen && inst.geometry==pcmCtrlDCCache)
@ -355,14 +359,14 @@ void DivZSM::flushWrites() {
pcmInsts.push_back(inst);
}
}
if (extCmdLen>63) { // this would be bad, but will almost certainly never happen
logE("ZSM: extCmd exceeded maximum length of 63: %d",extCmdLen);
extCmdLen=0;
if (extCmd0Len>63) { // this would be bad, but will almost certainly never happen
logE("ZSM: extCmd 0 exceeded maximum length of 63: %d",extCmd0Len);
extCmd0Len=0;
pcmMeta.clear();
}
if (extCmdLen) { // we have some PCM events to write
w->writeC(0x40);
w->writeC((unsigned char)extCmdLen); // the high two bits are guaranteed to be zero, meaning this is a PCM command
if (extCmd0Len) { // we have some PCM events to write
w->writeC(ZSM_EXT);
w->writeC(ZSM_EXT_PCM|(unsigned char)extCmd0Len);
for (DivRegWrite& write: pcmMeta) {
w->writeC(write.addr);
w->writeC(write.val);
@ -373,6 +377,18 @@ void DivZSM::flushWrites() {
w->writeC((unsigned char)pcmInst&0xff);
}
}
n=0;
while (n<(long)syncCache.size()) { // we have one or more sync events to write
int writes=syncCache.size()-n;
w->writeC(ZSM_EXT);
if (writes>ZSM_SYNC_MAX_WRITES) writes=ZSM_SYNC_MAX_WRITES;
w->writeC(ZSM_EXT_SYNC|(writes<<1));
for (; writes>0; writes--) {
w->writeC(0x00); // 0x00 = Arbitrary sync message
w->writeC(syncCache[n++]);
}
}
syncCache.clear();
numWrites=0;
}

View file

@ -30,9 +30,16 @@
#define ZSM_YM_CMD 0x40
#define ZSM_DELAY_CMD 0x80
#define ZSM_YM_MAX_WRITES 63
#define ZSM_SYNC_MAX_WRITES 31
#define ZSM_DELAY_MAX 127
#define ZSM_EOF ZSM_DELAY_CMD
#define ZSM_EXT ZSM_YM_CMD
#define ZSM_EXT_PCM 0x00
#define ZSM_EXT_CHIP 0x40
#define ZSM_EXT_SYNC 0x80
#define ZSM_EXT_CUSTOM 0xC0
enum YM_STATE { ym_PREV, ym_NEW, ym_STATES };
enum PSG_STATE { psg_PREV, psg_NEW, psg_STATES };
@ -52,6 +59,7 @@ class DivZSM {
std::vector<unsigned char> pcmData;
std::vector<unsigned char> pcmCache;
std::vector<S_pcmInst> pcmInsts;
std::vector<unsigned char> syncCache;
int loopOffset;
int numWrites;
int ticks;

View file

@ -151,7 +151,7 @@ SafeWriter* DivEngine::saveZSM(unsigned int zsmrate, bool loop) {
for (DivRegWrite& write: writes) {
if (i==YM) zsm.writeYM(write.addr&0xff,write.val);
if (i==VERA) {
if (done && write.addr >= 64) continue; // don't process any PCM events on the loop lookahead
if (done && write.addr>=64) continue; // don't process any PCM or sync events on the loop lookahead
zsm.writePSG(write.addr&0xff,write.val);
}
}

View file

@ -173,6 +173,7 @@ const char* aboutLine[]={
"reSID by Dag Lem",
"reSIDfp by Dag Lem, Antti Lankila",
"and Leandro Nini",
"dSID by DefleMask Team based on jsSID",
"Stella by Stella Team",
"QSound emulator by superctr and Valley Bell",
"VICE VIC-20 sound core by Rami Rasanen and viznut",

View file

@ -356,7 +356,6 @@ void putDispatchChip(void* data, int type) {
ImGui::Text("- filtCut: %d",ch->filtCut);
ImGui::Text("- resetTime: %d",ch->resetTime);
COMMON_CHIP_DEBUG_BOOL;
ImGui::TextColored(ch->isFP?colorOn:colorOff,">> IsFP");
break;
}
case DIV_SYSTEM_ARCADE:

View file

@ -6347,8 +6347,8 @@ bool FurnaceGUI::init() {
logD("starting render backend...");
if (!rend->init(sdlWin)) {
if (settings.renderBackend!="SDL" && !settings.renderBackend.empty()) {
settings.renderBackend="";
if (settings.renderBackend!="SDL") {
settings.renderBackend="SDL";
//e->setConf("renderBackend","");
//e->saveConf();
//lastError=fmt::sprintf("\r\nthe render backend has been set to a safe value. please restart Furnace.");

View file

@ -75,16 +75,20 @@ enum FurnaceGUIRenderBackend {
GUI_BACKEND_DX11
};
#ifdef HAVE_RENDER_SDL
#define GUI_BACKEND_DEFAULT GUI_BACKEND_SDL
#define GUI_BACKEND_DEFAULT_NAME "SDL"
#else
#ifdef HAVE_RENDER_DX11
#define GUI_BACKEND_DEFAULT GUI_BACKEND_DX11
#define GUI_BACKEND_DEFAULT_NAME "DirectX 11"
#else
#ifdef HAVE_RENDER_GL
#define GUI_BACKEND_DEFAULT GUI_BACKEND_GL
#define GUI_BACKEND_DEFAULT_NAME "OpenGL"
#else
#ifdef HAVE_RENDER_SDL
#define GUI_BACKEND_DEFAULT GUI_BACKEND_SDL
#define GUI_BACKEND_DEFAULT_NAME "SDL"
#else
#error how did you manage to do that?
#endif
#endif
#endif
@ -1544,7 +1548,7 @@ class FurnaceGUI {
snCore(0),
nesCore(0),
fdsCore(0),
c64Core(1),
c64Core(0),
pokeyCore(1),
opnCore(1),
pcSpeakerOutMethod(0),

View file

@ -116,7 +116,8 @@ const char* nesCores[]={
const char* c64Cores[]={
"reSID",
"reSIDfp"
"reSIDfp",
"dSID"
};
const char* pokeyCores[]={
@ -1254,7 +1255,7 @@ void FurnaceGUI::drawSettings() {
ImGui::Text("SID core");
ImGui::SameLine();
ImGui::Combo("##C64Core",&settings.c64Core,c64Cores,2);
ImGui::Combo("##C64Core",&settings.c64Core,c64Cores,3);
ImGui::Text("POKEY core");
ImGui::SameLine();
@ -2645,7 +2646,7 @@ void FurnaceGUI::syncSettings() {
settings.snCore=e->getConfInt("snCore",0);
settings.nesCore=e->getConfInt("nesCore",0);
settings.fdsCore=e->getConfInt("fdsCore",0);
settings.c64Core=e->getConfInt("c64Core",1);
settings.c64Core=e->getConfInt("c64Core",0);
settings.pokeyCore=e->getConfInt("pokeyCore",1);
settings.opnCore=e->getConfInt("opnCore",1);
settings.pcSpeakerOutMethod=e->getConfInt("pcSpeakerOutMethod",0);
@ -2771,7 +2772,7 @@ void FurnaceGUI::syncSettings() {
settings.orderButtonPos=e->getConfInt("orderButtonPos",2);
settings.compress=e->getConfInt("compress",1);
settings.newPatternFormat=e->getConfInt("newPatternFormat",1);
settings.renderBackend=e->getConfString("renderBackend","SDL");
settings.renderBackend=e->getConfString("renderBackend",GUI_BACKEND_DEFAULT_NAME);
settings.renderClearPos=e->getConfInt("renderClearPos",0);
settings.insertBehavior=e->getConfInt("insertBehavior",1);
settings.pullDeleteRow=e->getConfInt("pullDeleteRow",1);
@ -2791,7 +2792,7 @@ void FurnaceGUI::syncSettings() {
clampSetting(settings.snCore,0,1);
clampSetting(settings.nesCore,0,1);
clampSetting(settings.fdsCore,0,1);
clampSetting(settings.c64Core,0,1);
clampSetting(settings.c64Core,0,2);
clampSetting(settings.pokeyCore,0,1);
clampSetting(settings.opnCore,0,1);
clampSetting(settings.pcSpeakerOutMethod,0,4);
@ -2971,7 +2972,7 @@ void FurnaceGUI::commitSettings() {
settings.snCore!=e->getConfInt("snCore",0) ||
settings.nesCore!=e->getConfInt("nesCore",0) ||
settings.fdsCore!=e->getConfInt("fdsCore",0) ||
settings.c64Core!=e->getConfInt("c64Core",1) ||
settings.c64Core!=e->getConfInt("c64Core",0) ||
settings.pokeyCore!=e->getConfInt("pokeyCore",1) ||
settings.opnCore!=e->getConfInt("opnCore",1)
);

View file

@ -198,6 +198,7 @@ TAParamResult pVersion(String) {
printf("- NSFPlay by Brad Smith and Brezza (unknown open-source license)\n");
printf("- reSID by Dag Lem (GPLv2)\n");
printf("- reSIDfp by Dag Lem, Antti Lankila and Leandro Nini (GPLv2)\n");
printf("- dSID by DefleMask Team (based on jsSID by Hermit) (MIT)\n");
printf("- Stella by Stella Team (GPLv2)\n");
printf("- vgsound_emu (second version, modified version) by cam900 (zlib license)\n");
printf("- MAME GA20 core by Acho A. Tang, R. Belmont, Valley Bell (BSD 3-clause)\n");