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

* 'master' of https://github.com/tildearrow/furnace: (26 commits)
  prepare for #38
  NES: DPCM work!
  fix bug caused by new renderSamples approach
  NES: prepare for DPCM
  OPZ: more fixed frequency mode fixes
  OPZ: possibly fix fixed freq emulation
  OPZ: fix muting
  OPL: fix panning
  damn it
  per-channel oscilloscope, part 10
  NES: NSFPlay per-channel osc
  NES: NSFPlay muting
  NES: now fix tri_mute
  NES: finally
  fix typo in playback engine
  NES: wire up NSFplay
  now fix it damn it
  now move these files
  add readme
  convertir de Shift-JIS a UTF-8
  ...

# Conflicts:
#	src/engine/engine.cpp
#	src/engine/engine.h
#	src/engine/platform/nes.cpp
#	src/engine/vgmOps.cpp
#	src/gui/stats.cpp
This commit is contained in:
cam900 2022-05-03 11:07:50 +09:00
commit e136db6d7a
53 changed files with 3990 additions and 393 deletions

View File

@ -250,6 +250,13 @@ src/engine/platform/sound/nes/mmc5.c
src/engine/platform/sound/vera_psg.c
src/engine/platform/sound/vera_pcm.c
src/engine/platform/sound/nes_nsfplay/nes_apu.cpp
src/engine/platform/sound/nes_nsfplay/nes_dmc.cpp
src/engine/platform/sound/nes_nsfplay/nes_fds.cpp
src/engine/platform/sound/nes_nsfplay/nes_mmc5.cpp
src/engine/platform/sound/nes_nsfplay/nes_n106.cpp
src/engine/platform/sound/nes_nsfplay/nes_vrc6.cpp
src/engine/platform/sound/c64/sid.cc
src/engine/platform/sound/c64/voice.cc
src/engine/platform/sound/c64/wave.cc

View File

@ -36,7 +36,6 @@
- store edit/followOrders/followPattern state in config
- add ability to select a column by double clicking
- add ability to move selection by dragging
- NSFPlay core for NES
- settings: OK/Cancel buttons should be always visible
- Apply button in settings
- better FM chip names (number and codename)

View File

@ -300,6 +300,9 @@ bool ImGui_ImplSDL2_ProcessEvent(const SDL_Event* event)
{
float wheel_x = (event->wheel.x > 0) ? 1.0f : (event->wheel.x < 0) ? -1.0f : 0.0f;
float wheel_y = (event->wheel.y > 0) ? 1.0f : (event->wheel.y < 0) ? -1.0f : 0.0f;
#ifdef __APPLE__
wheel_x = -wheel_x;
#endif
io.AddMouseWheelEvent(wheel_x, wheel_y);
return true;
}

View File

@ -18,4 +18,8 @@ also known as Famicom. It is a five-channel PSG: first two channels play pulse w
- `14xy`: setup sweep down.
- `x` is the time.
- `y` is the shift.
- set to 0 to disable it.
- set to 0 to disable it.
- `18xx`: set PCM channel mode.
- `00`: PCM (software).
- `01`: DPCM (hardware).
- when in DPCM mode, samples will sound muffled (due to its nature), availables pitches are limited and loop point is ignored.

View File

@ -675,6 +675,26 @@ size | description
1 | parameter 2
1 | parameter 3
1 | parameter 4
--- | **additional macro mode flags** (>=84)
1 | volume macro mode
1 | duty macro mode
1 | wave macro mode
1 | pitch macro mode
1 | extra 1 macro mode
1 | extra 2 macro mode
1 | extra 3 macro mode
1 | alg macro mode
1 | fb macro mode
1 | fms macro mode
1 | ams macro mode
1 | left panning macro mode
1 | right panning macro mode
1 | phase reset macro mode
1 | extra 4 macro mode
1 | extra 5 macro mode
1 | extra 6 macro mode
1 | extra 7 macro mode
1 | extra 8 macro mode
--- | **extra C64 data** (>=89)
1 | don't test/gate before new note
```

View File

@ -64,9 +64,24 @@ enum DivDispatchCmds {
DIV_CMD_FM_LFO, // (speed)
DIV_CMD_FM_LFO_WAVE, // (waveform)
DIV_CMD_FM_TL, // (op, value)
DIV_CMD_FM_AM, // (op, value)
DIV_CMD_FM_AR, // (op, value)
DIV_CMD_FM_DR, // (op, value)
DIV_CMD_FM_SL, // (op, value)
DIV_CMD_FM_D2R, // (op, value)
DIV_CMD_FM_RR, // (op, value)
DIV_CMD_FM_DT, // (op, value)
DIV_CMD_FM_DT2, // (op, value)
DIV_CMD_FM_RS, // (op, value)
DIV_CMD_FM_KSR, // (op, value)
DIV_CMD_FM_VIB, // (op, value)
DIV_CMD_FM_SUS, // (op, value)
DIV_CMD_FM_WS, // (op, value)
DIV_CMD_FM_SSG, // (op, value)
DIV_CMD_FM_FB, // (value)
DIV_CMD_FM_MULT, // (op, value)
DIV_CMD_FM_FINE, // (op, value)
DIV_CMD_FM_FIXFREQ, // (op, value)
DIV_CMD_FM_EXTCH, // (enabled)
DIV_CMD_FM_AM_DEPTH, // (depth)
DIV_CMD_FM_PM_DEPTH, // (depth)
@ -155,6 +170,8 @@ enum DivDispatchCmds {
DIV_CMD_ES5506_FILTER_MODE, // (value)
DIV_CMD_ES5506_FILTER_K1, // (value)
DIV_CMD_ES5506_FILTER_K2, // (value)
DIV_CMD_ES5506_FILTER_K1_SLIDE, // (value, negative)
DIV_CMD_ES5506_FILTER_K2_SLIDE, // (value, negative)
DIV_CMD_ES5506_ENVELOPE_COUNT, // (count)
DIV_CMD_ES5506_ENVELOPE_LVRAMP, // (ramp)
DIV_CMD_ES5506_ENVELOPE_RVRAMP, // (ramp)
@ -218,11 +235,13 @@ struct DivRegWrite {
struct DivDispatchOscBuffer {
unsigned int rate;
unsigned short needle;
unsigned short readNeedle;
short data[65536];
DivDispatchOscBuffer():
rate(65536),
needle(0) {
needle(0),
readNeedle(0) {
memset(data,0,65536*sizeof(short));
}
};
@ -447,6 +466,26 @@ class DivDispatch {
*/
virtual const char** getRegisterSheet();
/**
* Get sample memory buffer.
*/
virtual const void* getSampleMem(int index = 0);
/**
* Get sample memory capacity.
*/
virtual size_t getSampleMemCapacity(int index = 0);
/**
* Get sample memory usage.
*/
virtual size_t getSampleMemUsage(int index = 0);
/**
* Render samples into sample memory.
*/
virtual void renderSamples();
/**
* initialize this DivDispatch.
* @param parent the parent DivEngine.

View File

@ -190,6 +190,7 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
break;
case DIV_SYSTEM_NES:
dispatch=new DivPlatformNES;
((DivPlatformNES*)dispatch)->setNSFPlay(eng->getConfInt("nesCore",0)==1);
break;
case DIV_SYSTEM_C64_6581:
dispatch=new DivPlatformC64;

View File

@ -507,148 +507,12 @@ void DivEngine::renderSamples() {
song.sample[i]->render();
}
// step 2: allocate ADPCM-A samples
if (adpcmAMem==NULL) adpcmAMem=new unsigned char[16777216];
size_t memPos=0;
for (int i=0; i<song.sampleLen; i++) {
DivSample* s=song.sample[i];
int paddedLen=(s->lengthA+255)&(~0xff);
if ((memPos&0xf00000)!=((memPos+paddedLen)&0xf00000)) {
memPos=(memPos+0xfffff)&0xf00000;
// step 2: render samples to dispatch
for (int i=0; i<song.systemLen; i++) {
if (disCont[i].dispatch!=NULL) {
disCont[i].dispatch->renderSamples();
}
if (memPos>=16777216) {
logW("out of ADPCM-A memory for sample %d!",i);
break;
}
if (memPos+paddedLen>=16777216) {
memcpy(adpcmAMem+memPos,s->dataA,16777216-memPos);
logW("out of ADPCM-A memory for sample %d!",i);
} else {
memcpy(adpcmAMem+memPos,s->dataA,paddedLen);
}
s->offA=memPos;
memPos+=paddedLen;
}
adpcmAMemLen=memPos+256;
// step 2: allocate ADPCM-B samples
if (adpcmBMem==NULL) adpcmBMem=new unsigned char[16777216];
memPos=0;
for (int i=0; i<song.sampleLen; i++) {
DivSample* s=song.sample[i];
int paddedLen=(s->lengthB+255)&(~0xff);
if ((memPos&0xf00000)!=((memPos+paddedLen)&0xf00000)) {
memPos=(memPos+0xfffff)&0xf00000;
}
if (memPos>=16777216) {
logW("out of ADPCM-B memory for sample %d!",i);
break;
}
if (memPos+paddedLen>=16777216) {
memcpy(adpcmBMem+memPos,s->dataB,16777216-memPos);
logW("out of ADPCM-B memory for sample %d!",i);
} else {
memcpy(adpcmBMem+memPos,s->dataB,paddedLen);
}
s->offB=memPos;
memPos+=paddedLen;
}
adpcmBMemLen=memPos+256;
// step 4: allocate qsound pcm samples
if (qsoundMem==NULL) qsoundMem=new unsigned char[16777216];
memset(qsoundMem,0,16777216);
memPos=0;
for (int i=0; i<song.sampleLen; i++) {
DivSample* s=song.sample[i];
int length=s->length8;
if (length>65536-16) {
length=65536-16;
}
if ((memPos&0xff0000)!=((memPos+length)&0xff0000)) {
memPos=(memPos+0xffff)&0xff0000;
}
if (memPos>=16777216) {
logW("out of QSound PCM memory for sample %d!",i);
break;
}
if (memPos+length>=16777216) {
for (unsigned int i=0; i<16777216-(memPos+length); i++) {
qsoundMem[(memPos+i)^0x8000]=s->data8[i];
}
logW("out of QSound PCM memory for sample %d!",i);
} else {
for (int i=0; i<length; i++) {
qsoundMem[(memPos+i)^0x8000]=s->data8[i];
}
}
s->offQSound=memPos^0x8000;
memPos+=length+16;
}
qsoundMemLen=memPos+256;
// step 4: allocate x1-010 pcm samples
if (x1_010Mem==NULL) x1_010Mem=new unsigned char[1048576];
memset(x1_010Mem,0,1048576);
memPos=0;
for (int i=0; i<song.sampleLen; i++) {
DivSample* s=song.sample[i];
int paddedLen=(s->length8+4095)&(~0xfff);
// fit sample bank size to 128KB for Seta 2 external bankswitching logic (not emulated yet!)
if (paddedLen>131072) {
paddedLen=131072;
}
if ((memPos&0xfe0000)!=((memPos+paddedLen)&0xfe0000)) {
memPos=(memPos+0x1ffff)&0xfe0000;
}
if (memPos>=1048576) {
logW("out of X1-010 memory for sample %d!",i);
break;
}
if (memPos+paddedLen>=1048576) {
memcpy(x1_010Mem+memPos,s->data8,1048576-memPos);
logW("out of X1-010 memory for sample %d!",i);
} else {
memcpy(x1_010Mem+memPos,s->data8,paddedLen);
}
s->offX1_010=memPos;
memPos+=paddedLen;
}
x1_010MemLen=memPos+256;
// step 5: allocate ES5506 pcm samples (forces depth for all samples to 16 bit due to chip limitation, compressed sample just LSB disconnected)
if (es5506Mem==NULL) es5506Mem=new signed short[16777216/sizeof(short)]; // 2Mword * 4 banks
memset(es5506Mem,0,16777216);
memPos=128;
for (int i=0; i<song.sampleLen; i++) {
DivSample* s=song.sample[i];
unsigned int length=s->length16;
// fit sample size to single bank size
if (length>(2097152-64)*sizeof(short)) {
length=(2097152-64)*sizeof(short);
}
if ((memPos&0xc00000)!=((memPos+length+128)&0xc00000)) {
memPos=((memPos+0x3fffff)&0xc00000)+128;
}
if (memPos>=(16777216-128)) {
logW("out of ES5506 memory for sample %d!",i);
break;
}
if (memPos+length>=(16777216-128)) {
memcpy(es5506Mem+(memPos/sizeof(short)),s->data16,16777216-memPos-128);
logW("out of ES5506 memory for sample %d!",i);
} else {
memcpy(es5506Mem+(memPos/sizeof(short)),s->data16,length);
}
s->offES5506=memPos;
memPos+=length;
}
es5506MemLen=memPos+256;
}
String DivEngine::encodeSysDesc(std::vector<int>& desc) {
@ -756,11 +620,11 @@ void DivEngine::createNew(const int* description) {
initSongWithDesc(description);
}
recalcChans();
renderSamples();
saveLock.unlock();
BUSY_END;
initDispatch();
BUSY_BEGIN;
renderSamples();
reset();
BUSY_END;
}
@ -998,6 +862,11 @@ DivSample* DivEngine::getSample(int index) {
return song.sample[index];
}
DivDispatch* DivEngine::getDispatch(int index) {
if (index<0 || index>=song.systemLen) return NULL;
return disCont[index].dispatch;
}
void DivEngine::setLoops(int loops) {
remainingLoops=loops;
}
@ -2511,6 +2380,7 @@ bool DivEngine::switchMaster() {
} else {
return false;
}
renderSamples();
return true;
}
@ -2808,9 +2678,9 @@ bool DivEngine::init() {
// set default system preset
if (!hasLoadedSomething) {
logI("setting");
logD("setting default preset");
std::vector<int> preset=decodeSysDesc(getConfString("initialSys",""));
logI("preset size %ld",preset.size());
logD("preset size %ld",preset.size());
if (preset.size()>0 && (preset.size()&3)==0) {
preset.push_back(0);
initSongWithDesc(preset.data());
@ -2856,6 +2726,7 @@ bool DivEngine::init() {
oscBuf[1]=new float[32768];
initDispatch();
renderSamples();
reset();
active=true;

View File

@ -424,6 +424,7 @@ class DivEngine {
DivInstrument* getIns(int index, DivInstrumentType fallbackType=DIV_INS_FM);
DivWavetable* getWave(int index);
DivSample* getSample(int index);
DivDispatch* getDispatch(int index);
// parse system setup description
String encodeSysDesc(std::vector<int>& desc);
std::vector<int> decodeSysDesc(String desc);
@ -851,21 +852,6 @@ class DivEngine {
// terminate the engine.
bool quit();
unsigned char* adpcmAMem;
size_t adpcmAMemLen;
unsigned char* adpcmBMem;
size_t adpcmBMemLen;
unsigned char* qsoundMem;
size_t qsoundMemLen;
unsigned char* qsoundAMem;
size_t qsoundAMemLen;
unsigned char* dpcmMem;
size_t dpcmMemLen;
unsigned char* x1_010Mem;
size_t x1_010MemLen;
signed short* es5506Mem;
size_t es5506MemLen;
DivEngine():
output(NULL),
exportThread(NULL),
@ -939,21 +925,7 @@ class DivEngine {
oscSize(1),
oscReadPos(0),
oscWritePos(0),
tickMult(1),
adpcmAMem(NULL),
adpcmAMemLen(0),
adpcmBMem(NULL),
adpcmBMemLen(0),
qsoundMem(NULL),
qsoundMemLen(0),
qsoundAMem(NULL),
qsoundAMemLen(0),
dpcmMem(NULL),
dpcmMemLen(0),
x1_010Mem(NULL),
x1_010MemLen(0),
es5506Mem(NULL),
es5506MemLen(0) {
tickMult(1) {
memset(isMuted,0,DIV_MAX_CHANS*sizeof(bool));
memset(keyHit,0,DIV_MAX_CHANS*sizeof(bool));
memset(dispatchChanOfChan,0,DIV_MAX_CHANS*sizeof(int));

View File

@ -899,12 +899,14 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
song.unload();
song=ds;
recalcChans();
renderSamples();
saveLock.unlock();
BUSY_END;
if (active) {
initDispatch();
syncReset();
BUSY_BEGIN;
renderSamples();
reset();
BUSY_END;
}
} catch (EndOfFileException& e) {
logE("premature end of file!");
@ -1603,12 +1605,14 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
song.unload();
song=ds;
recalcChans();
renderSamples();
saveLock.unlock();
BUSY_END;
if (active) {
initDispatch();
syncReset();
BUSY_BEGIN;
renderSamples();
reset();
BUSY_END;
}
} catch (EndOfFileException& e) {
logE("premature end of file!");
@ -2018,12 +2022,14 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
song.unload();
song=ds;
recalcChans();
renderSamples();
saveLock.unlock();
BUSY_END;
if (active) {
initDispatch();
syncReset();
BUSY_BEGIN;
renderSamples();
reset();
BUSY_END;
}
success=true;
} catch (EndOfFileException& e) {

View File

@ -141,6 +141,22 @@ const char** DivDispatch::getRegisterSheet() {
return NULL;
}
const void* DivDispatch::getSampleMem(int index) {
return NULL;
}
size_t DivDispatch::getSampleMemCapacity(int index) {
return 0;
}
size_t DivDispatch::getSampleMemUsage(int index) {
return 0;
}
void DivDispatch::renderSamples() {
}
int DivDispatch::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
return 0;
}

View File

@ -135,6 +135,18 @@ const char* DivPlatformES5506::getEffectName(unsigned char effect) {
case 0x27:
return "27xx: Set envelope k2 ramp (signed, slower)";
break;
case 0x28:
return "28xx: Set filter K1 slide up";
break;
case 0x29:
return "29xx: Set filter K1 slide down";
break;
case 0x2a:
return "28xx: Set filter K2 slide up";
break;
case 0x2b:
return "29xx: Set filter K2 slide down";
break;
default:
if ((effect&0xf0)==0x30) {
return "3xxx: Set filter K1";
@ -254,6 +266,7 @@ void DivPlatformES5506::tick(bool sysTick) {
for (int i=0; i<=chanMax; i++) {
chan[i].std.next();
DivInstrument* ins=parent->getIns(chan[i].ins);
signed int k1=chan[i].k1Prev,k2=chan[i].k2Prev;
// volume/panning macros
if (chan[i].std.vol.had) {
const unsigned int nextVol=((chan[i].vol&0xff)*MIN(0xffff,chan[i].std.vol.val))/0xff;
@ -413,6 +426,23 @@ void DivPlatformES5506::tick(bool sysTick) {
chan[i].envChanged.k2Ramp=1;
}
}
// filter slide
if (!chan[i].keyOn) {
if (chan[i].k1Slide!=0 && chan[i].filter.k1>0 && chan[i].filter.k1<65535) {
signed int next=CLAMP_VAL(chan[i].filter.k1+chan[i].k1Slide,0,65535);
if (chan[i].filter.k1!=next) {
chan[i].filter.k1=next;
chan[i].filterChanged.k1=1;
}
}
if (chan[i].k2Slide!=0 && chan[i].filter.k2>0 && chan[i].filter.k2<65535) {
signed int next=CLAMP_VAL(chan[i].filter.k2+chan[i].k2Slide,0,65535);
if (chan[i].filter.k2!=next) {
chan[i].filter.k2=next;
chan[i].filterChanged.k2=1;
}
}
}
// update registers
if (chan[i].volChanged.changed) {
if (!isMuted[i]) { // calculate volume (16 bit)
@ -470,16 +500,16 @@ void DivPlatformES5506::tick(bool sysTick) {
}
if (chan[i].filterChanged.k2) {
if (chan[i].std.ex2.mode!=1) { // Relative
pageWrite(0x00|i,0x07,CLAMP_VAL(chan[i].filter.k2+chan[i].k2Offs,0,65535));
k2=CLAMP_VAL(chan[i].filter.k2+chan[i].k2Offs,0,65535);
} else {
pageWrite(0x00|i,0x07,chan[i].filter.k2);
k2=chan[i].filter.k2;
}
}
if (chan[i].filterChanged.k1) {
if (chan[i].std.ex1.mode!=1) { // Relative
pageWrite(0x00|i,0x09,CLAMP_VAL(chan[i].filter.k1+chan[i].k1Offs,0,65535));
k1=CLAMP_VAL(chan[i].filter.k1+chan[i].k1Offs,0,65535);
} else {
pageWrite(0x00|i,0x09,chan[i].filter.k1);
k1=chan[i].filter.k1;
}
}
}
@ -511,6 +541,8 @@ void DivPlatformES5506::tick(bool sysTick) {
if (chan[i].freq>0x1ffff) chan[i].freq=0x1ffff;
if (chan[i].keyOn) {
if (chan[i].pcm.index>=0) {
chan[i].k1Prev=0xffff;
chan[i].k2Prev=0xffff;
pageWriteMask(0x00|i,0x5f,0x00,0x0303); // Wipeout CR
pageWrite(0x00|i,0x06,0); // Clear ECOUNT
pageWrite(0x20|i,0x03,chan[i].pcm.reversed?chan[i].pcm.end:chan[i].pcm.start); // Set ACCUM to start address
@ -527,15 +559,19 @@ void DivPlatformES5506::tick(bool sysTick) {
// initialize filter
pageWriteMask(0x00|i,0x5f,0x00,(chan[i].pcm.bank<<14)|(chan[i].filter.mode<<8),0xc300);
if ((chan[i].std.ex2.mode!=1) && (chan[i].std.ex2.had)) {
pageWrite(0x00|i,0x07,CLAMP_VAL(chan[i].filter.k2+chan[i].k2Offs,0,65535));
k2=CLAMP_VAL(chan[i].filter.k2+chan[i].k2Offs,0,65535);
} else {
pageWrite(0x00|i,0x07,chan[i].filter.k2);
k2=chan[i].filter.k2;
}
pageWrite(0x00|i,0x07,k2);
chan[i].k2Prev=k2;
if ((chan[i].std.ex1.mode!=1) && (chan[i].std.ex1.had)) {
pageWrite(0x00|i,0x09,CLAMP_VAL(chan[i].filter.k1+chan[i].k1Offs,0,65535));
k1=CLAMP_VAL(chan[i].filter.k1+chan[i].k1Offs,0,65535);
} else {
pageWrite(0x00|i,0x09,chan[i].filter.k1);
k1=chan[i].filter.k1;
}
pageWrite(0x00|i,0x09,k1);
chan[i].k1Prev=k1;
pageWrite(0x00|i,0x02,chan[i].resLVol);
pageWrite(0x00|i,0x04,chan[i].resRVol);
unsigned int loopFlag=chan[i].pcm.reversed?0x0040:0x0000;
@ -569,6 +605,16 @@ void DivPlatformES5506::tick(bool sysTick) {
if (chan[i].keyOff) chan[i].keyOff=false;
chan[i].freqChanged=false;
}
if (!chan[i].keyOn) {
if (chan[i].k2Prev!=k2) {
pageWrite(0x00|i,0x07,k2);
chan[i].k2Prev=k2;
}
if (chan[i].k1Prev!=k1) {
pageWrite(0x00|i,0x09,k1);
chan[i].k1Prev=k1;
}
}
}
}
@ -704,6 +750,12 @@ int DivPlatformES5506::dispatch(DivCommand c) {
chan[c.chan].filter.k2=(chan[c.chan].filter.k2&0xf)|((c.value&0xfff)<<4);
chan[c.chan].filterChanged.k2=1;
break;
case DIV_CMD_ES5506_FILTER_K1_SLIDE:
chan[c.chan].k1Slide=c.value2?(-c.value):c.value;
break;
case DIV_CMD_ES5506_FILTER_K2_SLIDE:
chan[c.chan].k2Slide=c.value2?(-c.value):c.value;
break;
// Envelope commands
case DIV_CMD_ES5506_ENVELOPE_COUNT:
chan[c.chan].envelope.ecount=c.value&0x1ff;
@ -896,8 +948,51 @@ unsigned char* DivPlatformES5506::getRegisterPool() {
int DivPlatformES5506::getRegisterPoolSize() {
return 4*16*128; // 7 bit page x 16 registers per page x 32 bit per registers
}
const void* DivPlatformES5506::getSampleMem(int index) {
return index == 0 ? sampleMem : NULL;
}
size_t DivPlatformES5506::getSampleMemCapacity(int index) {
return index == 0 ? 16777216 : 0; // 2Mword x 16bit * 4 banks
}
size_t DivPlatformES5506::getSampleMemUsage(int index) {
return index == 0 ? sampleMemLen : 0;
}
void DivPlatformES5506::renderSamples() {
memset(sampleMem,0,getSampleMemCapacity());
int memPos=128;
for (int i=0; i<parent->song.sampleLen; i++) {
DivSample* s=parent->song.sample[i];
unsigned int length=s->length16;
// fit sample size to single bank size
if (length>(2097152-64)*sizeof(short)) {
length=(2097152-64)*sizeof(short);
}
if ((memPos&0xc00000)!=((memPos+length+128)&0xc00000)) {
memPos=((memPos+0x3fffff)&0xc00000)+128;
}
if (memPos>=(getSampleMemCapacity()-128)) {
logW("out of ES5506 memory for sample %d!",i);
break;
}
if (memPos+length>=(getSampleMemCapacity()-128)) {
memcpy(sampleMem+(memPos/sizeof(short)),s->data16,getSampleMemCapacity()-memPos-128);
logW("out of ES5506 memory for sample %d!",i);
} else {
memcpy(sampleMem+(memPos/sizeof(short)),s->data16,length);
}
s->offES5506=memPos;
memPos+=length;
}
sampleMemLen=memPos+256;
}
int DivPlatformES5506::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
sampleMem=new signed short[getSampleMemCapacity()/sizeof(short)];
sampleMemLen=0;
parent=p;
dumpWrites=false;
skipRegisterWrites=false;
@ -916,6 +1011,7 @@ int DivPlatformES5506::init(DivEngine* p, int channels, int sugRate, unsigned in
}
void DivPlatformES5506::quit() {
delete[] sampleMem;
for (int i=0; i<32; i++) {
delete oscBuf[i];
}

View File

@ -105,6 +105,8 @@ class DivPlatformES5506: public DivDispatch, public es550x_intf {
} envChanged;
signed int k1Offs, k2Offs;
signed int k1Slide, k2Slide;
signed int k1Prev, k2Prev;
unsigned int vol, lVol, rVol;
unsigned int outVol, outLVol, outRVol;
unsigned int resLVol, resRVol;
@ -121,6 +123,8 @@ class DivPlatformES5506: public DivDispatch, public es550x_intf {
if (std.ex1.mode==2) {
k2Offs=0;
}
k1Prev=0xffff;
k2Prev=0xffff;
}
Channel():
freq(0),
@ -142,6 +146,10 @@ class DivPlatformES5506: public DivDispatch, public es550x_intf {
isReverseLoop(false),
k1Offs(0),
k2Offs(0),
k1Slide(0),
k2Slide(0),
k1Prev(0xffff),
k2Prev(0xffff),
vol(0xff),
lVol(0xff),
rVol(0xff),
@ -157,6 +165,8 @@ class DivPlatformES5506: public DivDispatch, public es550x_intf {
Channel chan[32];
DivDispatchOscBuffer* oscBuf[32];
bool isMuted[32];
signed short* sampleMem; // ES5506 uses 16 bit data bus for samples
size_t sampleMemLen;
struct QueuedHostIntf {
unsigned char step;
unsigned char addr;
@ -203,8 +213,8 @@ class DivPlatformES5506: public DivDispatch, public es550x_intf {
virtual void irqb(bool state) override; // IRQB output
virtual s16 read_sample(u8 voice, u8 bank, u32 address) override {
if (parent->es5506Mem==NULL) return 0;
return parent->es5506Mem[((bank&3)<<21)|(address&0x1fffff)];
if (sampleMem==NULL) return 0;
return sampleMem[((bank&3)<<21)|(address&0x1fffff)];
}
virtual void acquire(short* bufL, short* bufR, size_t start, size_t len) override;
@ -225,6 +235,10 @@ class DivPlatformES5506: public DivDispatch, public es550x_intf {
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 const void* getSampleMem(int index = 0) override;
virtual size_t getSampleMemCapacity(int index = 0) override;
virtual size_t getSampleMemUsage(int index = 0) override;
virtual void renderSamples() override;
virtual const char** getRegisterSheet() override;
virtual const char* getEffectName(unsigned char effect) override;
virtual int init(DivEngine* parent, int channels, int sugRate, unsigned int flags) override;

View File

@ -20,14 +20,15 @@
#include "nes.h"
#include "sound/nes/cpu_inline.h"
#include "../engine.h"
#include <cstddef>
#include "../../ta-log.h"
#include <stddef.h>
#include <math.h>
struct _nla_table nla_table;
#define CHIP_DIVIDER 16
#define rWrite(a,v) if (!skipRegisterWrites) {apu_wr_reg(nes,a,v); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} }
#define rWrite(a,v) if (!skipRegisterWrites) {doWrite(a,v); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} }
const char* regCheatSheetNES[]={
"S0Volume", "4000",
@ -53,6 +54,10 @@ const char* regCheatSheetNES[]={
NULL
};
unsigned char _readDMC(void* user, unsigned short addr) {
return ((DivPlatformNES*)user)->readDMC(addr);
}
const char** DivPlatformNES::getRegisterSheet() {
return regCheatSheetNES;
}
@ -71,41 +76,56 @@ const char* DivPlatformNES::getEffectName(unsigned char effect) {
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::acquire(short* bufL, short* bufR, size_t start, size_t len) {
void DivPlatformNES::doWrite(unsigned short addr, unsigned char data) {
if (useNP) {
nes1_NP->Write(addr,data);
nes2_NP->Write(addr,data);
} else {
apu_wr_reg(nes,addr,data);
}
}
#define doPCM \
if (!dpcmMode && dacSample!=-1) { \
dacPeriod+=dacRate; \
if (dacPeriod>=rate) { \
DivSample* s=parent->getSample(dacSample); \
if (s->samples>0) { \
if (!isMuted[4]) { \
unsigned char next=((unsigned char)s->data8[dacPos]+0x80)>>1; \
if (dacAntiClickOn && dacAntiClick<next) { \
dacAntiClick+=8; \
rWrite(0x4011,dacAntiClick); \
} else { \
dacAntiClickOn=false; \
rWrite(0x4011,next); \
} \
} \
dacPos++; \
if (((s->loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) && dacPos>=s->loopEnd) || (dacPos>=s->samples)) { \
if (s->isLoopable()) { \
dacPos=s->loopStart; \
} else { \
dacSample=-1; \
} \
} \
dacPeriod-=rate; \
} else { \
dacSample=-1; \
} \
} \
}
void DivPlatformNES::acquire_puNES(short* bufL, short* bufR, size_t start, size_t len) {
for (size_t i=start; i<start+len; i++) {
if (dacSample!=-1) {
dacPeriod+=dacRate;
if (dacPeriod>=rate) {
DivSample* s=parent->getSample(dacSample);
if (s->samples>0) {
if (!isMuted[4]) {
unsigned char next=((unsigned char)s->data8[dacPos]+0x80)>>1;
if (dacAntiClickOn && dacAntiClick<next) {
dacAntiClick+=8;
rWrite(0x4011,dacAntiClick);
} else {
dacAntiClickOn=false;
rWrite(0x4011,next);
}
}
dacPos++;
if (((s->loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) && dacPos>=s->loopEnd) || (dacPos>=s->samples)) {
if (s->isLoopable()) {
dacPos=s->loopStart;
} else {
dacSample=-1;
}
}
dacPeriod-=rate;
} else {
dacSample=-1;
}
}
}
doPCM;
apu_tick(nes,NULL);
nes->apu.odd_cycle=!nes->apu.odd_cycle;
@ -127,6 +147,41 @@ void DivPlatformNES::acquire(short* bufL, short* bufR, size_t start, size_t len)
}
}
void DivPlatformNES::acquire_NSFPlay(short* bufL, short* bufR, size_t start, size_t len) {
int out1[2];
int out2[2];
for (size_t i=start; i<start+len; i++) {
doPCM;
nes1_NP->Tick(1);
nes2_NP->TickFrameSequence(1);
nes2_NP->Tick(1);
nes1_NP->Render(out1);
nes2_NP->Render(out2);
int sample=(out1[0]+out1[1]+out2[0]+out2[1])<<1;
if (sample>32767) sample=32767;
if (sample<-32768) sample=-32768;
bufL[i]=sample;
if (++writeOscBuf>=32) {
writeOscBuf=0;
oscBuf[0]->data[oscBuf[0]->needle++]=nes1_NP->out[0]<<11;
oscBuf[1]->data[oscBuf[1]->needle++]=nes1_NP->out[1]<<11;
oscBuf[2]->data[oscBuf[2]->needle++]=nes2_NP->out[0]<<11;
oscBuf[3]->data[oscBuf[3]->needle++]=nes2_NP->out[1]<<11;
oscBuf[4]->data[oscBuf[4]->needle++]=nes2_NP->out[2]<<8;
}
}
}
void DivPlatformNES::acquire(short* bufL, short* bufR, size_t start, size_t len) {
if (useNP) {
acquire_NSFPlay(bufL,bufR,start,len);
} else {
acquire_puNES(bufL,bufR,start,len);
}
}
static unsigned char noiseTable[253]={
6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 5, 4,
15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4,
@ -152,6 +207,25 @@ static unsigned char noiseTable[253]={
15
};
unsigned char DivPlatformNES::calcDPCMRate(int inRate) {
if (inRate<4450) return 0;
if (inRate<5000) return 1;
if (inRate<5400) return 2;
if (inRate<5900) return 3;
if (inRate<6650) return 4;
if (inRate<7450) return 5;
if (inRate<8100) return 6;
if (inRate<8800) return 7;
if (inRate<10200) return 8;
if (inRate<11700) return 9;
if (inRate<13300) return 10;
if (inRate<15900) return 11;
if (inRate<18900) return 12;
if (inRate<23500) return 13;
if (inRate<29000) return 14;
return 15;
}
void DivPlatformNES::tick(bool sysTick) {
for (int i=0; i<4; i++) {
chan[i].std.next();
@ -279,6 +353,9 @@ void DivPlatformNES::tick(bool sysTick) {
off=(double)s->centerRate/8363.0;
}
dacRate=MIN(chan[4].freq*off,32000);
if (dpcmMode && !skipRegisterWrites) {
rWrite(0x4010,calcDPCMRate(dacRate));
}
if (dumpWrites) addWrite(0xffff0001,dacRate);
}
chan[4].freqChanged=false;
@ -294,10 +371,10 @@ int DivPlatformNES::dispatch(DivCommand c) {
dacSample=ins->amiga.initSample;
if (dacSample<0 || dacSample>=parent->song.sampleLen) {
dacSample=-1;
if (dumpWrites) addWrite(0xffff0002,0);
if (dumpWrites && !dpcmMode) addWrite(0xffff0002,0);
break;
} else {
if (dumpWrites) addWrite(0xffff0000,dacSample);
if (dumpWrites && !dpcmMode) addWrite(0xffff0000,dacSample);
}
dacPos=0;
dacPeriod=0;
@ -309,6 +386,18 @@ int DivPlatformNES::dispatch(DivCommand c) {
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
chan[c.chan].furnaceDac=true;
if (dpcmMode && !skipRegisterWrites) {
unsigned int dpcmAddr=parent->getSample(dacSample)->offDPCM;
unsigned int dpcmLen=(parent->getSample(dacSample)->lengthDPCM+15)>>4;
if (dpcmLen>255) dpcmLen=255;
// write DPCM
rWrite(0x4015,15);
rWrite(0x4010,calcDPCMRate(chan[c.chan].baseFreq));
rWrite(0x4012,(dpcmAddr>>6)&0xff);
rWrite(0x4013,dpcmLen&0xff);
rWrite(0x4015,31);
dpcmBank=dpcmAddr>>14;
}
} else {
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].note=c.value;
@ -316,16 +405,28 @@ int DivPlatformNES::dispatch(DivCommand c) {
dacSample=12*sampleBank+chan[c.chan].note%12;
if (dacSample>=parent->song.sampleLen) {
dacSample=-1;
if (dumpWrites) addWrite(0xffff0002,0);
if (dumpWrites && !dpcmMode) addWrite(0xffff0002,0);
break;
} else {
if (dumpWrites) addWrite(0xffff0000,dacSample);
if (dumpWrites && !dpcmMode) addWrite(0xffff0000,dacSample);
}
dacPos=0;
dacPeriod=0;
dacRate=parent->getSample(dacSample)->rate;
if (dumpWrites) addWrite(0xffff0001,dacRate);
if (dumpWrites && !dpcmMode) addWrite(0xffff0001,dacRate);
chan[c.chan].furnaceDac=false;
if (dpcmMode && !skipRegisterWrites) {
unsigned int dpcmAddr=parent->getSample(dacSample)->offDPCM;
unsigned int dpcmLen=(parent->getSample(dacSample)->lengthDPCM+15)>>4;
if (dpcmLen>255) dpcmLen=255;
// write DPCM
rWrite(0x4015,15);
rWrite(0x4010,calcDPCMRate(dacRate));
rWrite(0x4012,(dpcmAddr>>6)&0xff);
rWrite(0x4013,dpcmLen&0xff);
rWrite(0x4015,31);
dpcmBank=dpcmAddr>>14;
}
}
break;
} else if (c.chan==3) { // noise
@ -354,6 +455,7 @@ int DivPlatformNES::dispatch(DivCommand c) {
if (c.chan==4) {
dacSample=-1;
if (dumpWrites) addWrite(0xffff0002,0);
if (dpcmMode && !skipRegisterWrites) rWrite(0x4015,15);
}
chan[c.chan].active=false;
chan[c.chan].keyOff=true;
@ -435,6 +537,16 @@ int DivPlatformNES::dispatch(DivCommand c) {
case DIV_CMD_NES_DMC:
rWrite(0x4011,c.value&0x7f);
break;
case DIV_CMD_SAMPLE_MODE:
dpcmMode=c.value;
if (dumpWrites && dpcmMode) addWrite(0xffff0002,0);
dacSample=-1;
rWrite(0x4015,15);
rWrite(0x4010,0);
rWrite(0x4012,0);
rWrite(0x4013,0);
rWrite(0x4015,31);
break;
case DIV_CMD_SAMPLE_BANK:
sampleBank=c.value;
if (sampleBank>(parent->song.sample.size()/12)) {
@ -467,7 +579,12 @@ int DivPlatformNES::dispatch(DivCommand c) {
void DivPlatformNES::muteChannel(int ch, bool mute) {
isMuted[ch]=mute;
nes->muted[ch]=mute;
if (useNP) {
nes1_NP->SetMask(((int)isMuted[0])|(isMuted[1]<<1));
nes2_NP->SetMask(((int)isMuted[2])|(isMuted[3]<<1)|(isMuted[4]<<2));
} else {
nes->muted[ch]=mute;
}
}
void DivPlatformNES::forceIns() {
@ -513,11 +630,20 @@ void DivPlatformNES::reset() {
dacRate=0;
dacSample=-1;
sampleBank=0;
dpcmBank=0;
dpcmMode=false;
apu_turn_on(nes,apuType);
if (useNP) {
nes1_NP->Reset();
nes2_NP->Reset();
nes1_NP->SetMask(((int)isMuted[0])|(isMuted[1]<<1));
nes2_NP->SetMask(((int)isMuted[2])|(isMuted[3]<<1)|(isMuted[4]<<2));
} else {
apu_turn_on(nes,apuType);
nes->apu.cpu_cycles=0;
nes->apu.cpu_opcode_cycle=0;
}
memset(regPool,0,128);
nes->apu.cpu_cycles=0;
nes->apu.cpu_opcode_cycle=0;
rWrite(0x4015,0x1f);
rWrite(0x4001,chan[0].sweep);
@ -535,14 +661,20 @@ void DivPlatformNES::setFlags(unsigned int flags) {
if (flags==2) { // Dendy
rate=COLOR_PAL*2.0/5.0;
apuType=2;
nes->apu.type=apuType;
} else if (flags==1) { // PAL
rate=COLOR_PAL*3.0/8.0;
apuType=1;
nes->apu.type=apuType;
} else { // NTSC
rate=COLOR_NTSC/2.0;
apuType=0;
}
if (useNP) {
nes1_NP->SetClock(rate);
nes1_NP->SetRate(rate);
nes2_NP->SetClock(rate);
nes2_NP->SetRate(rate);
nes2_NP->SetPal(apuType==1);
} else {
nes->apu.type=apuType;
}
chipClock=rate;
@ -565,20 +697,87 @@ void DivPlatformNES::poke(std::vector<DivRegWrite>& wlist) {
for (DivRegWrite& i: wlist) rWrite(i.addr,i.val);
}
void DivPlatformNES::setNSFPlay(bool use) {
useNP=use;
}
unsigned char DivPlatformNES::readDMC(unsigned short addr) {
return dpcmMem[(addr&0x3fff)|((dpcmBank&15)<<14)];
}
const void* DivPlatformNES::getSampleMem(int index) {
return index==0?dpcmMem:NULL;
}
size_t DivPlatformNES::getSampleMemCapacity(int index) {
return index==0?262144:0;
}
size_t DivPlatformNES::getSampleMemUsage(int index) {
return index==0?dpcmMemLen:0;
}
void DivPlatformNES::renderSamples() {
memset(dpcmMem,0,getSampleMemCapacity(0));
size_t memPos=0;
for (int i=0; i<parent->song.sampleLen; i++) {
DivSample* s=parent->song.sample[i];
unsigned int paddedLen=(s->lengthDPCM+63)&(~0x3f);
logV("%d padded length: %d",i,paddedLen);
if ((memPos&(~0x3fff))!=((memPos+paddedLen)&(~0x3fff))) {
memPos=(memPos+0x3fff)&(~0x3fff);
}
if (paddedLen>4081) {
paddedLen=4096;
}
if (memPos>=getSampleMemCapacity(0)) {
logW("out of DPCM memory for sample %d!",i);
break;
}
if (memPos+paddedLen>=getSampleMemCapacity(0)) {
memcpy(dpcmMem+memPos,s->dataDPCM,getSampleMemCapacity(0)-memPos);
logW("out of DPCM memory for sample %d!",i);
} else {
memcpy(dpcmMem+memPos,s->dataDPCM,MIN(s->lengthDPCM,paddedLen));
}
s->offDPCM=memPos;
memPos+=paddedLen;
}
dpcmMemLen=memPos;
}
int DivPlatformNES::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
parent=p;
apuType=flags;
dumpWrites=false;
skipRegisterWrites=false;
nes=new struct NESAPU;
if (useNP) {
nes1_NP=new xgm::NES_APU;
nes1_NP->SetOption(xgm::NES_APU::OPT_NONLINEAR_MIXER,1);
nes2_NP=new xgm::NES_DMC;
nes2_NP->SetOption(xgm::NES_DMC::OPT_NONLINEAR_MIXER,1);
nes2_NP->SetMemory([this](unsigned short addr, unsigned int& data) {
data=readDMC(addr);
});
nes2_NP->SetAPU(nes1_NP);
} else {
nes=new struct NESAPU;
nes->readDMC=_readDMC;
nes->readDMCUser=this;
}
writeOscBuf=0;
for (int i=0; i<5; i++) {
isMuted[i]=false;
nes->muted[i]=false;
if (!useNP) nes->muted[i]=false;
oscBuf[i]=new DivDispatchOscBuffer;
}
setFlags(flags);
dpcmMem=new unsigned char[262144];
dpcmMemLen=0;
dpcmBank=0;
init_nla_table(500,500);
reset();
return 5;
@ -588,7 +787,12 @@ void DivPlatformNES::quit() {
for (int i=0; i<5; i++) {
delete oscBuf[i];
}
delete nes;
if (useNP) {
delete nes1_NP;
delete nes2_NP;
} else {
delete nes;
}
}
DivPlatformNES::~DivPlatformNES() {

View File

@ -23,6 +23,8 @@
#include "../dispatch.h"
#include "../macroInt.h"
#include "sound/nes_nsfplay/nes_apu.h"
class DivPlatformNES: public DivDispatch {
struct Channel {
int freq, baseFreq, pitch, pitch2, prevFreq, note, ins;
@ -62,15 +64,27 @@ class DivPlatformNES: public DivDispatch {
int dacPeriod, dacRate;
unsigned int dacPos, dacAntiClick;
int dacSample;
unsigned char* dpcmMem;
size_t dpcmMemLen;
unsigned char dpcmBank;
unsigned char sampleBank;
unsigned char writeOscBuf;
unsigned char apuType;
bool dpcmMode;
bool dacAntiClickOn;
bool useNP;
struct NESAPU* nes;
xgm::NES_APU* nes1_NP;
xgm::NES_DMC* nes2_NP;
unsigned char regPool[128];
friend void putDispatchChan(void*,int,int);
void doWrite(unsigned short addr, unsigned char data);
unsigned char calcDPCMRate(int inRate);
void acquire_puNES(short* bufL, short* bufR, size_t start, size_t len);
void acquire_NSFPlay(short* bufL, short* bufR, size_t start, size_t len);
public:
void acquire(short* bufL, short* bufR, size_t start, size_t len);
int dispatch(DivCommand c);
@ -84,12 +98,18 @@ class DivPlatformNES: public DivDispatch {
void muteChannel(int ch, bool mute);
bool keyOffAffectsArp(int ch);
float getPostAmp();
unsigned char readDMC(unsigned short addr);
void setNSFPlay(bool use);
void setFlags(unsigned int flags);
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);
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();
~DivPlatformNES();

View File

@ -705,7 +705,7 @@ int DivPlatformOPL::dispatch(DivCommand c) {
if (c.value==0 && c.value2==0) {
chan[c.chan].pan=3;
} else {
chan[c.chan].pan=(c.value2>0)|((c.value>0)<<1);
chan[c.chan].pan=(c.value>0)|((c.value2>0)<<1);
}
int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2;
if (isMuted[c.chan]) {

View File

@ -268,8 +268,6 @@ const char* DivPlatformQSound::getEffectName(unsigned char effect) {
return NULL;
}
void DivPlatformQSound::acquire(short* bufL, short* bufR, size_t start, size_t len) {
chip.rom_data = parent->qsoundMem;
chip.rom_mask = 0xffffff;
for (size_t h=start; h<start+len; h++) {
qsound_update(&chip);
bufL[h]=chip.out[0];
@ -638,6 +636,51 @@ int DivPlatformQSound::getRegisterPoolDepth() {
return 16;
}
const void* DivPlatformQSound::getSampleMem(int index) {
return index == 0 ? sampleMem : NULL;
}
size_t DivPlatformQSound::getSampleMemCapacity(int index) {
return index == 0 ? 16777216 : 0;
}
size_t DivPlatformQSound::getSampleMemUsage(int index) {
return index == 0 ? sampleMemLen : 0;
}
void DivPlatformQSound::renderSamples() {
memset(sampleMem,0,getSampleMemCapacity());
size_t memPos=0;
for (int i=0; i<parent->song.sampleLen; i++) {
DivSample* s=parent->song.sample[i];
int length=s->length8;
if (length>65536-16) {
length=65536-16;
}
if ((memPos&0xff0000)!=((memPos+length)&0xff0000)) {
memPos=(memPos+0xffff)&0xff0000;
}
if (memPos>=getSampleMemCapacity()) {
logW("out of QSound PCM memory for sample %d!",i);
break;
}
if (memPos+length>=getSampleMemCapacity()) {
for (unsigned int i=0; i<getSampleMemCapacity()-(memPos+length); i++) {
sampleMem[(memPos+i)^0x8000]=s->data8[i];
}
logW("out of QSound PCM memory for sample %d!",i);
} else {
for (int i=0; i<length; i++) {
sampleMem[(memPos+i)^0x8000]=s->data8[i];
}
}
s->offQSound=memPos^0x8000;
memPos+=length+16;
}
sampleMemLen=memPos+256;
}
int DivPlatformQSound::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
parent=p;
dumpWrites=false;
@ -651,8 +694,10 @@ int DivPlatformQSound::init(DivEngine* p, int channels, int sugRate, unsigned in
chipClock=60000000;
rate = qsound_start(&chip, chipClock);
chip.rom_data = (unsigned char*)&chip.rom_mask;
chip.rom_mask = 0;
sampleMem=new unsigned char[getSampleMemCapacity()];
sampleMemLen=0;
chip.rom_data=sampleMem;
chip.rom_mask=0xffffff;
reset();
for (int i=0; i<19; i++) {
@ -662,6 +707,7 @@ int DivPlatformQSound::init(DivEngine* p, int channels, int sugRate, unsigned in
}
void DivPlatformQSound::quit() {
delete[] sampleMem;
for (int i=0; i<19; i++) {
delete oscBuf[i];
}

View File

@ -67,6 +67,8 @@ class DivPlatformQSound: public DivDispatch {
int echoDelay;
int echoFeedback;
unsigned char* sampleMem;
size_t sampleMemLen;
struct qsound_chip chip;
unsigned short regPool[512];
@ -94,6 +96,10 @@ class DivPlatformQSound: public DivDispatch {
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);
void renderSamples();
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
void quit();
};

View File

@ -207,7 +207,7 @@ enum apu_mode { APU_60HZ, APU_48HZ };
break;\
}\
{\
a->DMC.buffer = 0;\
a->DMC.buffer = a->readDMC(a->readDMCUser,a->DMC.address);\
}\
/* incremento gli hwtick da compiere */\
if (hwtick) { hwtick[0] += tick; }\
@ -525,6 +525,8 @@ EXTERNC struct NESAPU {
_apuTriangle TR;
_apuNoise NS;
_apuDMC DMC;
void* readDMCUser;
unsigned char (*readDMC)(void*,unsigned short);
unsigned char muted[5];
};

View File

@ -0,0 +1,4 @@
namespace xgm {
const unsigned int DEFAULT_CLOCK=1789773;
const unsigned int DEFAULT_RATE=1789773;
};

View File

@ -0,0 +1,380 @@
//
// NES 2A03
//
#include <assert.h>
#include "nes_apu.h"
#include "common.h"
namespace xgm
{
void NES_APU::sweep_sqr (int i)
{
int shifted = freq[i] >> sweep_amount[i];
if (i == 0 && sweep_mode[i]) shifted += 1;
sfreq[i] = freq[i] + (sweep_mode[i] ? -shifted : shifted);
//DEBUG_OUT("shifted[%d] = %d (%d >> %d)¥n",i,shifted,freq[i],sweep_amount[i]);
}
void NES_APU::FrameSequence(int s)
{
//DEBUG_OUT("FrameSequence(%d)¥n",s);
if (s > 3) return; // no operation in step 4
// 240hz clock
for (int i=0; i < 2; ++i)
{
bool divider = false;
if (envelope_write[i])
{
envelope_write[i] = false;
envelope_counter[i] = 15;
envelope_div[i] = 0;
}
else
{
++envelope_div[i];
if (envelope_div[i] > envelope_div_period[i])
{
divider = true;
envelope_div[i] = 0;
}
}
if (divider)
{
if (envelope_loop[i] && envelope_counter[i] == 0)
envelope_counter[i] = 15;
else if (envelope_counter[i] > 0)
--envelope_counter[i];
}
}
// 120hz clock
if ((s&1) == 0)
for (int i=0; i < 2; ++i)
{
if (!envelope_loop[i] && (length_counter[i] > 0))
--length_counter[i];
if (sweep_enable[i])
{
//DEBUG_OUT("Clock sweep: %d¥n", i);
--sweep_div[i];
if (sweep_div[i] <= 0)
{
sweep_sqr(i); // calculate new sweep target
//DEBUG_OUT("sweep_div[%d] (0/%d)¥n",i,sweep_div_period[i]);
//DEBUG_OUT("freq[%d]=%d > sfreq[%d]=%d¥n",i,freq[i],i,sfreq[i]);
if (freq[i] >= 8 && sfreq[i] < 0x800 && sweep_amount[i] > 0) // update frequency if appropriate
{
freq[i] = sfreq[i] < 0 ? 0 : sfreq[i];
}
sweep_div[i] = sweep_div_period[i] + 1;
//DEBUG_OUT("freq[%d]=%d¥n",i,freq[i]);
}
if (sweep_write[i])
{
sweep_div[i] = sweep_div_period[i] + 1;
sweep_write[i] = false;
}
}
}
}
int NES_APU::calc_sqr (int i, unsigned int clocks)
{
static const short sqrtbl[4][16] = {
{0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0},
{1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
};
scounter[i] -= clocks;
while (scounter[i] < 0)
{
sphase[i] = (sphase[i] + 1) & 15;
scounter[i] += freq[i] + 1;
}
int ret = 0;
if (length_counter[i] > 0 &&
freq[i] >= 8 &&
sfreq[i] < 0x800
)
{
int v = envelope_disable[i] ? volume[i] : envelope_counter[i];
ret = sqrtbl[duty[i]][sphase[i]] ? v : 0;
}
return ret;
}
bool NES_APU::Read (unsigned int adr, unsigned int & val, unsigned int id)
{
if (0x4000 <= adr && adr < 0x4008)
{
val |= reg[adr&0x7];
return true;
}
else if(adr==0x4015)
{
val |= (length_counter[1]?2:0)|(length_counter[0]?1:0);
return true;
}
else
return false;
}
void NES_APU::Tick (unsigned int clocks)
{
out[0] = calc_sqr(0, clocks);
out[1] = calc_sqr(1, clocks);
}
// ツ青カツ青ャツつウツづェツづゥツ波ツ形ツづ個振ツ閉敖づ0-8191
unsigned int NES_APU::Render (int b[2])
{
out[0] = (mask & 1) ? 0 : out[0];
out[1] = (mask & 2) ? 0 : out[1];
int m[2];
if(option[OPT_NONLINEAR_MIXER])
{
int voltage = square_table[out[0] + out[1]];
m[0] = out[0] << 6;
m[1] = out[1] << 6;
int ref = m[0] + m[1];
if (ref > 0)
{
m[0] = (m[0] * voltage) / ref;
m[1] = (m[1] * voltage) / ref;
}
else
{
m[0] = voltage;
m[1] = voltage;
}
}
else
{
m[0] = (out[0] * square_linear) / 15;
m[1] = (out[1] * square_linear) / 15;
}
b[0] = m[0] * sm[0][0];
b[0] += m[1] * sm[0][1];
b[0] >>= 7;
b[1] = m[0] * sm[1][0];
b[1] += m[1] * sm[1][1];
b[1] >>= 7;
return 2;
}
NES_APU::NES_APU ()
{
SetClock (DEFAULT_CLOCK);
SetRate (DEFAULT_RATE);
option[OPT_UNMUTE_ON_RESET] = true;
option[OPT_PHASE_REFRESH] = true;
option[OPT_NONLINEAR_MIXER] = true;
option[OPT_DUTY_SWAP] = false;
option[OPT_NEGATE_SWEEP_INIT] = false;
square_table[0] = 0;
for(int i=1;i<32;i++)
square_table[i]=(int)((8192.0*95.88)/(8128.0/i+100));
square_linear = square_table[15]; // match linear scale to one full volume square of nonlinear
for(int c=0;c<2;++c)
for(int t=0;t<2;++t)
sm[c][t] = 128;
}
NES_APU::~NES_APU ()
{
}
void NES_APU::Reset ()
{
int i;
gclock = 0;
mask = 0;
for (int i=0; i<2; ++i)
{
scounter[i] = 0;
sphase[i] = 0;
duty[i] = 0;
volume[i] = 0;
freq[i] = 0;
sfreq[i] = 0;
sweep_enable[i] = 0;
sweep_mode[i] = 0;
sweep_write[i] = 0;
sweep_div_period[i] = 0;
sweep_div[i] = 1;
sweep_amount[i] = 0;
envelope_disable[i] = 0;
envelope_loop[i] = 0;
envelope_write[i] = 0;
envelope_div_period[i] = 0;
envelope_div[0] = 0;
envelope_counter[i] = 0;
length_counter[i] = 0;
enable[i] = 0;
}
for (i = 0x4000; i < 0x4008; i++)
Write (i, 0);
Write (0x4015, 0);
if (option[OPT_UNMUTE_ON_RESET])
Write (0x4015, 0x0f);
if (option[OPT_NEGATE_SWEEP_INIT])
{
Write (0x4001, 0x08);
Write (0x4005, 0x08);
}
for (i = 0; i < 2; i++)
out[i] = 0;
SetRate(rate);
}
void NES_APU::SetOption (int id, int val)
{
if(id<OPT_END) option[id] = val;
}
void NES_APU::SetClock (double c)
{
clock = c;
}
void NES_APU::SetRate (double r)
{
rate = r ? r : DEFAULT_RATE;
}
void NES_APU::SetStereoMix(int trk, short mixl, short mixr)
{
if (trk < 0) return;
if (trk > 1) return;
sm[0][trk] = mixl;
sm[1][trk] = mixr;
}
bool NES_APU::Write (unsigned int adr, unsigned int val, unsigned int id)
{
int ch;
static const unsigned char length_table[32] = {
0x0A, 0xFE,
0x14, 0x02,
0x28, 0x04,
0x50, 0x06,
0xA0, 0x08,
0x3C, 0x0A,
0x0E, 0x0C,
0x1A, 0x0E,
0x0C, 0x10,
0x18, 0x12,
0x30, 0x14,
0x60, 0x16,
0xC0, 0x18,
0x48, 0x1A,
0x10, 0x1C,
0x20, 0x1E
};
if (0x4000 <= adr && adr < 0x4008)
{
//DEBUG_OUT("$%04X = %02X¥n",adr,val);
adr &= 0xf;
ch = adr >> 2;
switch (adr)
{
case 0x0:
case 0x4:
volume[ch] = val & 15;
envelope_disable[ch] = (val >> 4) & 1;
envelope_loop[ch] = (val >> 5) & 1;
envelope_div_period[ch] = (val & 15);
duty[ch] = (val >> 6) & 3;
if (option[OPT_DUTY_SWAP])
{
if (duty[ch] == 1) duty[ch] = 2;
else if (duty[ch] == 2) duty[ch] = 1;
}
break;
case 0x1:
case 0x5:
sweep_enable[ch] = (val >> 7) & 1;
sweep_div_period[ch] = (((val >> 4) & 7));
sweep_mode[ch] = (val >> 3) & 1;
sweep_amount[ch] = val & 7;
sweep_write[ch] = true;
sweep_sqr(ch);
break;
case 0x2:
case 0x6:
freq[ch] = val | (freq[ch] & 0x700) ;
sweep_sqr(ch);
break;
case 0x3:
case 0x7:
freq[ch] = (freq[ch] & 0xFF) | ((val & 0x7) << 8) ;
if (option[OPT_PHASE_REFRESH])
sphase[ch] = 0;
envelope_write[ch] = true;
if (enable[ch])
{
length_counter[ch] = length_table[(val >> 3) & 0x1f];
}
sweep_sqr(ch);
break;
default:
return false;
}
reg[adr] = val;
return true;
}
else if (adr == 0x4015)
{
enable[0] = (val & 1) ? true : false;
enable[1] = (val & 2) ? true : false;
if (!enable[0])
length_counter[0] = 0;
if (!enable[1])
length_counter[1] = 0;
reg[adr-0x4000] = val;
return true;
}
// 4017 is handled in nes_dmc.cpp
//else if (adr == 0x4017)
//{
//}
return false;
}
} // namespace xgm;

View File

@ -0,0 +1,85 @@
#ifndef _NES_APU_H_
#define _NES_APU_H_
#include "nes_dmc.h"
namespace xgm
{
/** Upper half of APU **/
class NES_APU
{
public:
enum
{
OPT_UNMUTE_ON_RESET=0,
OPT_PHASE_REFRESH,
OPT_NONLINEAR_MIXER,
OPT_DUTY_SWAP,
OPT_NEGATE_SWEEP_INIT,
OPT_END };
enum
{ SQR0_MASK = 1, SQR1_MASK = 2, };
protected:
int option[OPT_END]; // 各種オプション
int mask;
int sm[2][2];
unsigned int gclock;
unsigned char reg[0x20];
double rate, clock;
int square_table[32]; // nonlinear mixer
int square_linear; // linear mix approximation
int scounter[2]; // frequency divider
int sphase[2]; // phase counter
int duty[2];
int volume[2];
int freq[2];
int sfreq[2];
bool sweep_enable[2];
bool sweep_mode[2];
bool sweep_write[2];
int sweep_div_period[2];
int sweep_div[2];
int sweep_amount[2];
bool envelope_disable[2];
bool envelope_loop[2];
bool envelope_write[2];
int envelope_div_period[2];
int envelope_div[2];
int envelope_counter[2];
int length_counter[2];
bool enable[2];
void sweep_sqr (int ch); // calculates target sweep frequency
int calc_sqr (int ch, unsigned int clocks);
public:
int out[2];
NES_APU ();
~NES_APU ();
void FrameSequence(int s);
void Reset ();
void Tick (unsigned int clocks);
unsigned int Render (int b[2]);
bool Read (unsigned int adr, unsigned int & val, unsigned int id=0);
bool Write (unsigned int adr, unsigned int val, unsigned int id=0);
void SetRate (double rate);
void SetClock (double clock);
void SetOption (int id, int b);
void SetMask(int m){ mask = m; }
void SetStereoMix (int trk, short mixl, short mixr);
};
} // namespace
#endif

View File

@ -0,0 +1,717 @@
#include "nes_dmc.h"
#include "nes_apu.h"
#include "common.h"
#include <assert.h>
#include <cstdlib>
namespace xgm
{
const unsigned int NES_DMC::wavlen_table[2][16] = {
{ // NTSC
4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068
},
{ // PAL
4, 8, 14, 30, 60, 88, 118, 148, 188, 236, 354, 472, 708, 944, 1890, 3778
}};
const unsigned int NES_DMC::freq_table[2][16] = {
{ // NTSC
428, 380, 340, 320, 286, 254, 226, 214, 190, 160, 142, 128, 106, 84, 72, 54
},
{ // PAL
398, 354, 316, 298, 276, 236, 210, 198, 176, 148, 132, 118, 98, 78, 66, 50
}};
const unsigned int BITREVERSE[256] = {
0x00, 0x80, 0x40, 0xC0, 0x20, 0xA0, 0x60, 0xE0, 0x10, 0x90, 0x50, 0xD0, 0x30, 0xB0, 0x70, 0xF0,
0x08, 0x88, 0x48, 0xC8, 0x28, 0xA8, 0x68, 0xE8, 0x18, 0x98, 0x58, 0xD8, 0x38, 0xB8, 0x78, 0xF8,
0x04, 0x84, 0x44, 0xC4, 0x24, 0xA4, 0x64, 0xE4, 0x14, 0x94, 0x54, 0xD4, 0x34, 0xB4, 0x74, 0xF4,
0x0C, 0x8C, 0x4C, 0xCC, 0x2C, 0xAC, 0x6C, 0xEC, 0x1C, 0x9C, 0x5C, 0xDC, 0x3C, 0xBC, 0x7C, 0xFC,
0x02, 0x82, 0x42, 0xC2, 0x22, 0xA2, 0x62, 0xE2, 0x12, 0x92, 0x52, 0xD2, 0x32, 0xB2, 0x72, 0xF2,
0x0A, 0x8A, 0x4A, 0xCA, 0x2A, 0xAA, 0x6A, 0xEA, 0x1A, 0x9A, 0x5A, 0xDA, 0x3A, 0xBA, 0x7A, 0xFA,
0x06, 0x86, 0x46, 0xC6, 0x26, 0xA6, 0x66, 0xE6, 0x16, 0x96, 0x56, 0xD6, 0x36, 0xB6, 0x76, 0xF6,
0x0E, 0x8E, 0x4E, 0xCE, 0x2E, 0xAE, 0x6E, 0xEE, 0x1E, 0x9E, 0x5E, 0xDE, 0x3E, 0xBE, 0x7E, 0xFE,
0x01, 0x81, 0x41, 0xC1, 0x21, 0xA1, 0x61, 0xE1, 0x11, 0x91, 0x51, 0xD1, 0x31, 0xB1, 0x71, 0xF1,
0x09, 0x89, 0x49, 0xC9, 0x29, 0xA9, 0x69, 0xE9, 0x19, 0x99, 0x59, 0xD9, 0x39, 0xB9, 0x79, 0xF9,
0x05, 0x85, 0x45, 0xC5, 0x25, 0xA5, 0x65, 0xE5, 0x15, 0x95, 0x55, 0xD5, 0x35, 0xB5, 0x75, 0xF5,
0x0D, 0x8D, 0x4D, 0xCD, 0x2D, 0xAD, 0x6D, 0xED, 0x1D, 0x9D, 0x5D, 0xDD, 0x3D, 0xBD, 0x7D, 0xFD,
0x03, 0x83, 0x43, 0xC3, 0x23, 0xA3, 0x63, 0xE3, 0x13, 0x93, 0x53, 0xD3, 0x33, 0xB3, 0x73, 0xF3,
0x0B, 0x8B, 0x4B, 0xCB, 0x2B, 0xAB, 0x6B, 0xEB, 0x1B, 0x9B, 0x5B, 0xDB, 0x3B, 0xBB, 0x7B, 0xFB,
0x07, 0x87, 0x47, 0xC7, 0x27, 0xA7, 0x67, 0xE7, 0x17, 0x97, 0x57, 0xD7, 0x37, 0xB7, 0x77, 0xF7,
0x0F, 0x8F, 0x4F, 0xCF, 0x2F, 0xAF, 0x6F, 0xEF, 0x1F, 0x9F, 0x5F, 0xDF, 0x3F, 0xBF, 0x7F, 0xFF,
};
NES_DMC::NES_DMC () : GETA_BITS (20)
{
SetClock (DEFAULT_CLOCK);
SetRate (DEFAULT_RATE);
SetPal (false);
option[OPT_ENABLE_4011] = 1;
option[OPT_ENABLE_PNOISE] = 1;
option[OPT_UNMUTE_ON_RESET] = 1;
option[OPT_DPCM_ANTI_CLICK] = 0;
option[OPT_NONLINEAR_MIXER] = 1;
option[OPT_RANDOMIZE_NOISE] = 1;
option[OPT_RANDOMIZE_TRI] = 1;
option[OPT_TRI_MUTE] = 1;
option[OPT_DPCM_REVERSE] = 0;
tnd_table[0][0][0][0] = 0;
tnd_table[1][0][0][0] = 0;
apu = NULL;
frame_sequence_count = 0;
frame_sequence_length = 7458;
frame_sequence_steps = 4;
for(int c=0;c<2;++c)
for(int t=0;t<3;++t)
sm[c][t] = 128;
}
NES_DMC::~NES_DMC ()
{
}
void NES_DMC::SetStereoMix(int trk, short mixl, short mixr)
{
if (trk < 0) return;
if (trk > 2) return;
sm[0][trk] = mixl;
sm[1][trk] = mixr;
}
void NES_DMC::FrameSequence(int s)
{
//DEBUG_OUT("FrameSequence: %d¥n",s);
if (s > 3) return; // no operation in step 4
if (apu)
{
apu->FrameSequence(s);
}
if (s == 0 && (frame_sequence_steps == 4))
{
if (frame_irq_enable) frame_irq = true;
}
// 240hz clock
{
// triangle linear counter
if (linear_counter_halt)
{
linear_counter = linear_counter_reload;
}
else
{
if (linear_counter > 0) --linear_counter;
}
if (!linear_counter_control)
{
linear_counter_halt = false;
}
// noise envelope
bool divider = false;
if (envelope_write)
{
envelope_write = false;
envelope_counter = 15;
envelope_div = 0;
}
else
{
++envelope_div;
if (envelope_div > envelope_div_period)
{
divider = true;
envelope_div = 0;
}
}
if (divider)
{
if (envelope_loop && envelope_counter == 0)
envelope_counter = 15;
else if (envelope_counter > 0)
--envelope_counter;
}
}
// 120hz clock
if ((s&1) == 0)
{
// triangle length counter
if (!linear_counter_control && (length_counter[0] > 0))
--length_counter[0];
// noise length counter
if (!envelope_loop && (length_counter[1] > 0))
--length_counter[1];
}
}
// 三角波チャンネルの計算 戻り値は0-15
unsigned int NES_DMC::calc_tri (unsigned int clocks)
{
static unsigned int tritbl[32] =
{
15,14,13,12,11,10, 9, 8,
7, 6, 5, 4, 3, 2, 1, 0,
0, 1, 2, 3, 4, 5, 6, 7,
8, 9,10,11,12,13,14,15,
};
if (linear_counter > 0 && length_counter[0] > 0
&& (!option[OPT_TRI_MUTE] || tri_freq > 0))
{
counter[0] -= clocks;
while (counter[0] < 0)
{
tphase = (tphase + 1) & 31;
counter[0] += (tri_freq + 1);
}
}
unsigned int ret = tritbl[tphase];
return ret;
}
// ノイズチャンネルの計算 戻り値は0-127
// 低サンプリングレートで合成するとエイリアスノイズが激しいので
// ノイズだけはこの関数内で高クロック合成し、簡易なサンプリングレート
// 変換を行っている。
unsigned int NES_DMC::calc_noise(unsigned int clocks)
{
unsigned int env = envelope_disable ? noise_volume : envelope_counter;
if (length_counter[1] < 1) env = 0;
unsigned int last = (noise & 0x4000) ? 0 : env;
if (clocks < 1) return last;
// simple anti-aliasing (noise requires it, even when oversampling is off)
unsigned int count = 0;
unsigned int accum = counter[1] * last; // samples pending from previous calc
unsigned int accum_clocks = counter[1];
#ifdef _DEBUG
int start_clocks = counter[1];
#endif
if (counter[1] < 0) // only happens on startup when using the randomize noise option
{
accum = 0;
accum_clocks = 0;
}
counter[1] -= clocks;
assert (nfreq > 0); // prevent infinite loop
while (counter[1] < 0)
{
// tick the noise generator
unsigned int feedback = (noise&1) ^ ((noise&noise_tap)?1:0);
noise = (noise>>1) | (feedback<<14);
last = (noise & 0x4000) ? 0 : env;
accum += (last * nfreq);
counter[1] += nfreq;
++count;
accum_clocks += nfreq;
}
if (count < 1) // no change over interval, don't anti-alias
{
return last;
}
accum -= (last * counter[1]); // remove these samples which belong in the next calc
accum_clocks -= counter[1];
#ifdef _DEBUG
if (start_clocks >= 0) assert(accum_clocks == clocks); // these should be equal
#endif
unsigned int average = accum / accum_clocks;
assert(average <= 15); // above this would indicate overflow
return average;
}
// Tick the DMC for the number of clocks, and return output counter;
unsigned int NES_DMC::calc_dmc (unsigned int clocks)
{
counter[2] -= clocks;
assert (dfreq > 0); // prevent infinite loop
while (counter[2] < 0)
{
counter[2] += dfreq;
if ( data > 0x100 ) // data = 0x100 when shift register is empty
{
if (!empty)
{
if ((data & 1) && (damp < 63))
damp++;
else if (!(data & 1) && (0 < damp))
damp--;
}
data >>=1;
}
if ( data <= 0x100 ) // shift register is empty
{
if (dlength > 0)
{
memory (daddress, data);
// (checking for the 3-cycle case would require sub-instruction emulation)
data &= 0xFF; // read 8 bits
if (option[OPT_DPCM_REVERSE]) data = BITREVERSE[data];
data |= 0x10000; // use an extra bit to signal end of data
empty = false;
daddress = ((daddress+1)&0xFFFF)|0x8000 ;
--dlength;
if (dlength == 0)
{
if (mode & 1) // looped DPCM = auto-reload
{
daddress = ((adr_reg<<6)|0xC000);
dlength = (len_reg<<4)+1;
}
else if (mode & 2) // IRQ and not looped
{
irq = true;
}
}
}
else
{
data = 0x10000; // DMC will do nothing
empty = true;
}
}
}
return (damp<<1) + dac_lsb;
}
void NES_DMC::TickFrameSequence (unsigned int clocks)
{
frame_sequence_count += clocks;
while (frame_sequence_count > frame_sequence_length)
{
FrameSequence(frame_sequence_step);
frame_sequence_count -= frame_sequence_length;
++frame_sequence_step;
if(frame_sequence_step >= frame_sequence_steps)
frame_sequence_step = 0;
}
}
void NES_DMC::Tick (unsigned int clocks)
{
out[0] = calc_tri(clocks);
out[1] = calc_noise(clocks);
out[2] = calc_dmc(clocks);
}
unsigned int NES_DMC::Render (int b[2])
{
out[0] = (mask & 1) ? 0 : out[0];
out[1] = (mask & 2) ? 0 : out[1];
out[2] = (mask & 4) ? 0 : out[2];
int m[3];
m[0] = tnd_table[0][out[0]][0][0];
m[1] = tnd_table[0][0][out[1]][0];
m[2] = tnd_table[0][0][0][out[2]];
if (option[OPT_NONLINEAR_MIXER])
{
int ref = m[0] + m[1] + m[2];
int voltage = tnd_table[1][out[0]][out[1]][out[2]];
if (ref)
{
for (int i=0; i < 3; ++i)
m[i] = (m[i] * voltage) / ref;
}
else
{
for (int i=0; i < 3; ++i)
m[i] = voltage;
}
}
// anti-click nullifies any 4011 write but preserves nonlinearity
if (option[OPT_DPCM_ANTI_CLICK])
{
if (dmc_pop) // $4011 will cause pop this frame
{
// adjust offset to counteract pop
dmc_pop_offset += dmc_pop_follow - m[2];
dmc_pop = false;
// prevent overflow, keep headspace at edges
const int OFFSET_MAX = (1 << 30) - (4 << 16);
if (dmc_pop_offset > OFFSET_MAX) dmc_pop_offset = OFFSET_MAX;
if (dmc_pop_offset < -OFFSET_MAX) dmc_pop_offset = -OFFSET_MAX;
}
dmc_pop_follow = m[2]; // remember previous position
m[2] += dmc_pop_offset; // apply offset
// TODO implement this in a better way
// roll off offset (not ideal, but prevents overflow)
if (dmc_pop_offset > 0) --dmc_pop_offset;
else if (dmc_pop_offset < 0) ++dmc_pop_offset;
}
b[0] = m[0] * sm[0][0];
b[0] += m[1] * sm[0][1];
b[0] += m[2] * sm[0][2];
b[0] >>= 7;
b[1] = m[0] * sm[1][0];
b[1] += m[1] * sm[1][1];
b[1] += m[2] * sm[1][2];
b[1] >>= 7;
return 2;
}
void NES_DMC::SetClock (double c)
{
clock = c;
}
void NES_DMC::SetRate (double r)
{
rate = (unsigned int)(r?r:DEFAULT_RATE);
}
void NES_DMC::SetPal (bool is_pal)
{
pal = (is_pal ? 1 : 0);
// set CPU cycles in frame_sequence
frame_sequence_length = is_pal ? 8314 : 7458;
}
void NES_DMC::SetAPU (NES_APU* apu_)
{
apu = apu_;
}
// Initializing TRI, NOISE, DPCM mixing table
void NES_DMC::InitializeTNDTable(double wt, double wn, double wd) {
// volume adjusted by 0.95 based on empirical measurements
const double MASTER = 8192.0 * 0.95;
// truthfully, the nonlinear curve does not appear to match well
// with my tests. Do more testing of the APU/DMC DAC later.
// this value keeps the triangle consistent with measured levels,
// but not necessarily the rest of this APU channel,
// because of the lack of a good DAC model, currently.
{ // Linear Mixer
for(int t=0; t<16 ; t++) {
for(int n=0; n<16; n++) {
for(int d=0; d<128; d++) {
tnd_table[0][t][n][d] = (unsigned int)(MASTER*(3.0*t+2.0*n+d)/208.0);
}
}
}
}
{ // Non-Linear Mixer
tnd_table[1][0][0][0] = 0;
for(int t=0; t<16 ; t++) {
for(int n=0; n<16; n++) {
for(int d=0; d<128; d++) {
if(t!=0||n!=0||d!=0)
tnd_table[1][t][n][d] = (unsigned int)((MASTER*159.79)/(100.0+1.0/((double)t/wt+(double)n/wn+(double)d/wd)));
}
}
}
}
}
void NES_DMC::Reset ()
{
int i;
mask = 0;
InitializeTNDTable(8227,12241,22638);
counter[0] = 0;
counter[1] = 0;
counter[2] = 0;
tphase = 0;
nfreq = wavlen_table[0][0];
dfreq = freq_table[0][0];
tri_freq = 0;
linear_counter = 0;
linear_counter_reload = 0;
linear_counter_halt = 0;
linear_counter_control = 0;
noise_volume = 0;
noise = 0;
noise_tap = 0;
envelope_loop = 0;
envelope_disable = 0;
envelope_write = 0;
envelope_div_period = 0;
envelope_div = 0;
envelope_counter = 0;
enable[0] = 0;
enable[1] = 0;
length_counter[0] = 0;
length_counter[1] = 0;
frame_irq = false;
frame_irq_enable = false;
frame_sequence_count = 0;
frame_sequence_steps = 4;
frame_sequence_step = 0;
for (i = 0; i < 0x0F; i++)
Write (0x4008 + i, 0);
Write (0x4017, 0x40);
irq = false;
Write (0x4015, 0x00);
if (option[OPT_UNMUTE_ON_RESET])
Write (0x4015, 0x0f);
out[0] = out[1] = out[2] = 0;
damp = 0;
dmc_pop = false;
dmc_pop_offset = 0;
dmc_pop_follow = 0;
dac_lsb = 0;
data = 0x100;
empty = true;
adr_reg = 0;
dlength = 0;
len_reg = 0;
daddress = 0;
noise = 1;
noise_tap = (1<<1);
if (option[OPT_RANDOMIZE_NOISE])
{
noise |= ::rand();
counter[1] = -(rand() & 511);
}
if (option[OPT_RANDOMIZE_TRI])
{
tphase = ::rand() & 31;
counter[0] = -(rand() & 2047);
}
SetRate(rate);
}
void NES_DMC::SetMemory (std::function<void(unsigned short, unsigned int&)> r)
{
memory = r;
}
void NES_DMC::SetOption (int id, int val)
{
if(id<OPT_END)
{
option[id] = val;
if(id==OPT_NONLINEAR_MIXER)
InitializeTNDTable(8227,12241,22638);
}
}
bool NES_DMC::Write (unsigned int adr, unsigned int val, unsigned int id)
{
static const unsigned char length_table[32] = {
0x0A, 0xFE,
0x14, 0x02,
0x28, 0x04,
0x50, 0x06,
0xA0, 0x08,
0x3C, 0x0A,
0x0E, 0x0C,
0x1A, 0x0E,
0x0C, 0x10,
0x18, 0x12,
0x30, 0x14,
0x60, 0x16,
0xC0, 0x18,
0x48, 0x1A,
0x10, 0x1C,
0x20, 0x1E
};
if (adr == 0x4015)
{
enable[0] = (val & 4) ? true : false;
enable[1] = (val & 8) ? true : false;
if (!enable[0])
{
length_counter[0] = 0;
}
if (!enable[1])
{
length_counter[1] = 0;
}
if ((val & 16) && dlength == 0)
{
daddress = (0xC000 | (adr_reg << 6));
dlength = (len_reg << 4) + 1;
}
else if (!(val & 16))
{
dlength = 0;
}
irq = false;
reg[adr-0x4008] = val;
return true;
}
if (adr == 0x4017)
{
//DEBUG_OUT("4017 = %02X¥n", val);
frame_irq_enable = ((val & 0x40) != 0x40);
if (frame_irq_enable) frame_irq = false;
frame_sequence_count = 0;
if (val & 0x80)
{
frame_sequence_steps = 5;
frame_sequence_step = 0;
FrameSequence(frame_sequence_step);
++frame_sequence_step;
}
else
{
frame_sequence_steps = 4;
frame_sequence_step = 1;
}
}
if (adr<0x4008||0x4013<adr)
return false;
reg[adr-0x4008] = val&0xff;
//DEBUG_OUT("$%04X %02X¥n", adr, val);
switch (adr)
{
// tri
case 0x4008:
linear_counter_control = (val >> 7) & 1;
linear_counter_reload = val & 0x7F;
break;
case 0x4009:
break;
case 0x400a:
tri_freq = val | (tri_freq & 0x700) ;
break;
case 0x400b:
tri_freq = (tri_freq & 0xff) | ((val & 0x7) << 8) ;
linear_counter_halt = true;
if (enable[0])
{
length_counter[0] = length_table[(val >> 3) & 0x1f];
}
break;
// noise
case 0x400c:
noise_volume = val & 15;
envelope_div_period = val & 15;
envelope_disable = (val >> 4) & 1;
envelope_loop = (val >> 5) & 1;
break;
case 0x400d:
break;
case 0x400e:
if (option[OPT_ENABLE_PNOISE])
noise_tap = (val & 0x80) ? (1<<6) : (1<<1);
else
noise_tap = (1<<1);
nfreq = wavlen_table[pal][val&15];
break;
case 0x400f:
if (enable[1])
{
length_counter[1] = length_table[(val >> 3) & 0x1f];
}
envelope_write = true;
break;
// dmc
case 0x4010:
mode = (val >> 6) & 3;
if (!(mode & 2))
{
irq = false;
}
dfreq = freq_table[pal][val&15];
break;
case 0x4011:
if (option[OPT_ENABLE_4011])
{
damp = (val >> 1) & 0x3f;
dac_lsb = val & 1;
dmc_pop = true;
}
break;
case 0x4012:
adr_reg = val&0xff;
// ここでdaddressは更新されない
break;
case 0x4013:
len_reg = val&0xff;
// ここでlengthは更新されない
break;
default:
return false;
}
return true;
}
bool NES_DMC::Read (unsigned int adr, unsigned int & val, unsigned int id)
{
if (adr == 0x4015)
{
val |=(irq ? 0x80 : 0)
| (frame_irq ? 0x40 : 0)
| ((dlength>0) ? 0x10 : 0)
| (length_counter[1] ? 0x08 : 0)
| (length_counter[0] ? 0x04 : 0)
;
frame_irq = false;
return true;
}
else if (0x4008<=adr&&adr<=0x4014)
{
val |= reg[adr-0x4008];
return true;
}
else
return false;
}
} // namespace

View File

@ -0,0 +1,119 @@
#ifndef _NES_DMC_H_
#define _NES_DMC_H_
#include <functional>
namespace xgm
{
class NES_APU; // forward declaration
/** Bottom Half of APU **/
class NES_DMC
{
public:
enum
{
OPT_ENABLE_4011=0,
OPT_ENABLE_PNOISE,
OPT_UNMUTE_ON_RESET,
OPT_DPCM_ANTI_CLICK,
OPT_NONLINEAR_MIXER,
OPT_RANDOMIZE_NOISE,
OPT_TRI_MUTE,
OPT_RANDOMIZE_TRI,
OPT_DPCM_REVERSE,
OPT_END
};
protected:
const int GETA_BITS;
static const unsigned int freq_table[2][16];
static const unsigned int wavlen_table[2][16];
unsigned int tnd_table[2][16][16][128];
int option[OPT_END];
int mask;
int sm[2][3];
unsigned int reg[0x10];
unsigned int len_reg;
unsigned int adr_reg;
std::function<void(unsigned short, unsigned int&)> memory;
unsigned int daddress;
unsigned int dlength;
unsigned int data;
bool empty;
short damp;
int dac_lsb;
bool dmc_pop;
int dmc_pop_offset;
int dmc_pop_follow;
double clock;
unsigned int rate;
int pal;
int mode;
bool irq;
int counter[3]; // frequency dividers
int tphase; // triangle phase
unsigned int nfreq; // noise frequency
unsigned int dfreq; // DPCM frequency
unsigned int tri_freq;
int linear_counter;
int linear_counter_reload;
bool linear_counter_halt;
bool linear_counter_control;
int noise_volume;
unsigned int noise, noise_tap;
// noise envelope
bool envelope_loop;
bool envelope_disable;
bool envelope_write;
int envelope_div_period;
int envelope_div;
int envelope_counter;
bool enable[2]; // tri/noise enable
int length_counter[2]; // 0=tri, 1=noise
// frame sequencer
NES_APU* apu; // apu is clocked by DMC's frame sequencer
int frame_sequence_count; // current cycle count
int frame_sequence_length; // CPU cycles per FrameSequence
int frame_sequence_step; // current step of frame sequence
int frame_sequence_steps; // 4/5 steps per frame
bool frame_irq;
bool frame_irq_enable;
inline unsigned int calc_tri (unsigned int clocks);
inline unsigned int calc_dmc (unsigned int clocks);
inline unsigned int calc_noise (unsigned int clocks);
public:
unsigned int out[3];
NES_DMC ();
~NES_DMC ();
void InitializeTNDTable(double wt, double wn, double wd);
void SetPal (bool is_pal);
void SetAPU (NES_APU* apu_);
void SetMemory (std::function<void(unsigned short, unsigned int&)> r);
void FrameSequence(int s);
int GetDamp(){ return (damp<<1)|dac_lsb ; }
void TickFrameSequence (unsigned int clocks);
void Reset ();
void Tick (unsigned int clocks);
unsigned int Render (int b[2]);
bool Write (unsigned int adr, unsigned int val, unsigned int id=0);
bool Read (unsigned int adr, unsigned int & val, unsigned int id=0);
void SetRate (double rate);
void SetClock (double rate);
void SetOption (int, int);
void SetMask(int m){ mask = m; }
void SetStereoMix (int trk, short mixl, short mixr);
};
}
#endif

View File

@ -0,0 +1,385 @@
#include <cstring>
#include <math.h>
#include "nes_fds.h"
#include "common.h"
namespace xgm {
const int RC_BITS = 12;
NES_FDS::NES_FDS ()
{
option[OPT_CUTOFF] = 2000;
option[OPT_4085_RESET] = 0;
option[OPT_WRITE_PROTECT] = 0; // not used here, see nsfplay.cpp
rc_k = 0;
rc_l = (1<<RC_BITS);
SetClock (DEFAULT_CLOCK);
SetRate (DEFAULT_RATE);
sm[0] = 128;
sm[1] = 128;
Reset();
}
NES_FDS::~NES_FDS ()
{
}
void NES_FDS::SetStereoMix(int trk, short mixl, short mixr)
{
if (trk < 0) return;
if (trk > 1) return;
sm[0] = mixl;
sm[1] = mixr;
}
void NES_FDS::SetClock (double c)
{
clock = c;
}
void NES_FDS::SetRate (double r)
{
rate = r;
// configure lowpass filter
double cutoff = double(option[OPT_CUTOFF]);
double leak = 0.0;
if (cutoff > 0)
leak = exp(-2.0 * 3.14159 * cutoff / rate);
rc_k = int(leak * double(1<<RC_BITS));
rc_l = (1<<RC_BITS) - rc_k;
}
void NES_FDS::SetOption (int id, int val)
{
if(id<OPT_END) option[id] = val;
// update cutoff immediately
if (id == OPT_CUTOFF) SetRate(rate);
}
void NES_FDS::Reset ()
{
master_io = true;
master_vol = 0;
last_freq = 0;
last_vol = 0;
rc_accum = 0;
for (int i=0; i<2; ++i)
{
::memset(wave[i], 0, sizeof(wave[i]));
freq[i] = 0;
phase[i] = 0;
}
wav_write = false;
wav_halt = true;
env_halt = true;
mod_halt = true;
mod_pos = 0;
mod_write_pos = 0;
for (int i=0; i<2; ++i)
{
env_mode[i] = false;
env_disable[i] = true;
env_timer[i] = 0;
env_speed[i] = 0;
env_out[i] = 0;
}
master_env_speed = 0xFF;
// NOTE: the FDS BIOS reset only does the following related to audio:
// $4023 = $00
// $4023 = $83 enables master_io
// $4080 = $80 output volume = 0, envelope disabled
// $408A = $E8 master envelope speed
Write(0x4023, 0x00);
Write(0x4023, 0x83);
Write(0x4080, 0x80);
Write(0x408A, 0xE8);
// reset other stuff
Write(0x4082, 0x00); // wav freq 0
Write(0x4083, 0x80); // wav disable
Write(0x4084, 0x80); // mod strength 0
Write(0x4085, 0x00); // mod position 0
Write(0x4086, 0x00); // mod freq 0
Write(0x4087, 0x80); // mod disable
Write(0x4089, 0x00); // wav write disable, max global volume}
}
void NES_FDS::Tick (unsigned int clocks)
{
// clock envelopes
if (!env_halt && !wav_halt && (master_env_speed != 0))
{
for (int i=0; i<2; ++i)
{
if (!env_disable[i])
{
env_timer[i] += clocks;
unsigned int period = ((env_speed[i]+1) * master_env_speed) << 3;
while (env_timer[i] >= period)
{
// clock the envelope
if (env_mode[i])
{
if (env_out[i] < 32) ++env_out[i];
}
else
{
if (env_out[i] > 0 ) --env_out[i];
}
env_timer[i] -= period;
}
}
}
}
// clock the mod table
if (!mod_halt)
{
// advance phase, adjust for modulator
unsigned int start_pos = phase[TMOD] >> 16;
phase[TMOD] += (clocks * freq[TMOD]);
unsigned int end_pos = phase[TMOD] >> 16;
// wrap the phase to the 64-step table (+ 16 bit accumulator)
phase[TMOD] = phase[TMOD] & 0x3FFFFF;
// execute all clocked steps
for (unsigned int p = start_pos; p < end_pos; ++p)
{
int wv = wave[TMOD][p & 0x3F];
if (wv == 4) // 4 resets mod position
mod_pos = 0;
else
{
const int BIAS[8] = { 0, 1, 2, 4, 0, -4, -2, -1 };
mod_pos += BIAS[wv];
mod_pos &= 0x7F; // 7-bit clamp
}
}
}
// clock the wav table
if (!wav_halt)
{
// complex mod calculation
int mod = 0;
if (env_out[EMOD] != 0) // skip if modulator off
{
// convert mod_pos to 7-bit signed
int pos = (mod_pos < 64) ? mod_pos : (mod_pos-128);
// multiply pos by gain,
// shift off 4 bits but with odd "rounding" behaviour
int temp = pos * env_out[EMOD];
int rem = temp & 0x0F;
temp >>= 4;
if ((rem > 0) && ((temp & 0x80) == 0))
{
if (pos < 0) temp -= 1;
else temp += 2;
}
// wrap if range is exceeded
while (temp >= 192) temp -= 256;
while (temp < -64) temp += 256;
// multiply result by pitch,
// shift off 6 bits, round to nearest
temp = freq[TWAV] * temp;
rem = temp & 0x3F;
temp >>= 6;
if (rem >= 32) temp += 1;
mod = temp;
}
// advance wavetable position
int f = freq[TWAV] + mod;
phase[TWAV] = phase[TWAV] + (clocks * f);
phase[TWAV] = phase[TWAV] & 0x3FFFFF; // wrap
// store for trackinfo
last_freq = f;
}
// output volume caps at 32
int vol_out = env_out[EVOL];
if (vol_out > 32) vol_out = 32;
// final output
if (!wav_write)
fout = wave[TWAV][(phase[TWAV]>>16)&0x3F] * vol_out;
// NOTE: during wav_halt, the unit still outputs (at phase 0)
// and volume can affect it if the first sample is nonzero.
// haven't worked out 100% of the conditions for volume to
// effect (vol envelope does not seem to run, but am unsure)
// but this implementation is very close to correct
// store for trackinfo
last_vol = vol_out;
}
unsigned int NES_FDS::Render (int b[2])
{
// 8 bit approximation of master volume
const double MASTER_VOL = 2.4 * 1223.0; // max FDS vol vs max APU square (arbitrarily 1223)
const double MAX_OUT = 32.0f * 63.0f; // value that should map to master vol
const int MASTER[4] = {
int((MASTER_VOL / MAX_OUT) * 256.0 * 2.0f / 2.0f),
int((MASTER_VOL / MAX_OUT) * 256.0 * 2.0f / 3.0f),
int((MASTER_VOL / MAX_OUT) * 256.0 * 2.0f / 4.0f),
int((MASTER_VOL / MAX_OUT) * 256.0 * 2.0f / 5.0f) };
int v = fout * MASTER[master_vol] >> 8;
// lowpass RC filter
int rc_out = ((rc_accum * rc_k) + (v * rc_l)) >> RC_BITS;
rc_accum = rc_out;
v = rc_out;
// output mix
int m = mask ? 0 : v;
b[0] = (m * sm[0]) >> 7;
b[1] = (m * sm[1]) >> 7;
return 2;
}
bool NES_FDS::Write (unsigned int adr, unsigned int val, unsigned int id)
{
// $4023 master I/O enable/disable
if (adr == 0x4023)
{
master_io = ((val & 2) != 0);
return true;
}
if (!master_io)
return false;
if (adr < 0x4040 || adr > 0x408A)
return false;
if (adr < 0x4080) // $4040-407F wave table write
{
if (wav_write)
wave[TWAV][adr - 0x4040] = val & 0x3F;
return true;
}
switch (adr & 0x00FF)
{
case 0x80: // $4080 volume envelope
env_disable[EVOL] = ((val & 0x80) != 0);
env_mode[EVOL] = ((val & 0x40) != 0);
env_timer[EVOL] = 0;
env_speed[EVOL] = val & 0x3F;
if (env_disable[EVOL])
env_out[EVOL] = env_speed[EVOL];
return true;
case 0x81: // $4081 ---
return false;
case 0x82: // $4082 wave frequency low
freq[TWAV] = (freq[TWAV] & 0xF00) | val;
return true;
case 0x83: // $4083 wave frequency high / enables
freq[TWAV] = (freq[TWAV] & 0x0FF) | ((val & 0x0F) << 8);
wav_halt = ((val & 0x80) != 0);
env_halt = ((val & 0x40) != 0);
if (wav_halt)
phase[TWAV] = 0;
if (env_halt)
{
env_timer[EMOD] = 0;
env_timer[EVOL] = 0;
}
return true;
case 0x84: // $4084 mod envelope
env_disable[EMOD] = ((val & 0x80) != 0);
env_mode[EMOD] = ((val & 0x40) != 0);
env_timer[EMOD] = 0;
env_speed[EMOD] = val & 0x3F;
if (env_disable[EMOD])
env_out[EMOD] = env_speed[EMOD];
return true;
case 0x85: // $4085 mod position
mod_pos = val & 0x7F;
// not hardware accurate., but prevents detune due to cycle inaccuracies
// (notably in Bio Miracle Bokutte Upa)
if (option[OPT_4085_RESET])
phase[TMOD] = mod_write_pos << 16;
return true;
case 0x86: // $4086 mod frequency low
freq[TMOD] = (freq[TMOD] & 0xF00) | val;
return true;
case 0x87: // $4087 mod frequency high / enable
freq[TMOD] = (freq[TMOD] & 0x0FF) | ((val & 0x0F) << 8);
mod_halt = ((val & 0x80) != 0);
if (mod_halt)
phase[TMOD] = phase[TMOD] & 0x3F0000; // reset accumulator phase
return true;
case 0x88: // $4088 mod table write
if (mod_halt)
{
// writes to current playback position (there is no direct way to set phase)
wave[TMOD][(phase[TMOD] >> 16) & 0x3F] = val & 0x07;
phase[TMOD] = (phase[TMOD] + 0x010000) & 0x3FFFFF;
wave[TMOD][(phase[TMOD] >> 16) & 0x3F] = val & 0x07;
phase[TMOD] = (phase[TMOD] + 0x010000) & 0x3FFFFF;
mod_write_pos = phase[TMOD] >> 16; // used by OPT_4085_RESET
}
return true;
case 0x89: // $4089 wave write enable, master volume
wav_write = ((val & 0x80) != 0);
master_vol = val & 0x03;
return true;
case 0x8A: // $408A envelope speed
master_env_speed = val;
// haven't tested whether this register resets phase on hardware,
// but this ensures my inplementation won't spam envelope clocks
// if this value suddenly goes low.
env_timer[EMOD] = 0;
env_timer[EVOL] = 0;
return true;
default:
return false;
}
return false;
}
bool NES_FDS::Read (unsigned int adr, unsigned int & val, unsigned int id)
{
if (adr >= 0x4040 && adr <= 0x407F)
{
// TODO: if wav_write is not enabled, the
// read address may not be reliable? need
// to test this on hardware.
val = wave[TWAV][adr - 0x4040];
return true;
}
if (adr == 0x4090) // $4090 read volume envelope
{
val = env_out[EVOL] | 0x40;
return true;
}
if (adr == 0x4092) // $4092 read mod envelope
{
val = env_out[EMOD] | 0x40;
return true;
}
return false;
}
} // namespace

View File

@ -0,0 +1,73 @@
#ifndef _NES_FDS_H_
#define _NES_FDS_H_
namespace xgm {
class NES_FDS
{
public:
enum
{
OPT_CUTOFF=0,
OPT_4085_RESET,
OPT_WRITE_PROTECT,
OPT_END
};
protected:
double rate, clock;
int mask;
int sm[2]; // stereo mix
int fout; // current output
int option[OPT_END];
bool master_io;
unsigned int master_vol;
unsigned int last_freq; // for trackinfo
unsigned int last_vol; // for trackinfo
// two wavetables
enum { TMOD=0, TWAV=1 };
int wave[2][64];
unsigned int freq[2];
unsigned int phase[2];
bool wav_write;
bool wav_halt;
bool env_halt;
bool mod_halt;
unsigned int mod_pos;
unsigned int mod_write_pos;
// two ramp envelopes
enum { EMOD=0, EVOL=1 };
bool env_mode[2];
bool env_disable[2];
unsigned int env_timer[2];
unsigned int env_speed[2];
unsigned int env_out[2];
unsigned int master_env_speed;
// 1-pole RC lowpass filter
int rc_accum;
int rc_k;
int rc_l;
public:
NES_FDS ();
~NES_FDS ();
void Reset ();
void Tick (unsigned int clocks);
unsigned int Render (int b[2]);
bool Write (unsigned int adr, unsigned int val, unsigned int id=0);
bool Read (unsigned int adr, unsigned int & val, unsigned int id=0);
void SetRate (double);
void SetClock (double);
void SetOption (int, int);
void SetMask(int m){ mask = m&1; }
void SetStereoMix (int trk, short mixl, short mixr);
};
} // namespace xgm
#endif

View File

@ -0,0 +1,372 @@
#include "nes_mmc5.h"
#include "common.h"
namespace xgm
{
NES_MMC5::NES_MMC5 ()
{
SetClock (DEFAULT_CLOCK);
SetRate (DEFAULT_RATE);
option[OPT_NONLINEAR_MIXER] = true;
option[OPT_PHASE_REFRESH] = true;
frame_sequence_count = 0;
// square nonlinear mix, same as 2A03
square_table[0] = 0;
for(int i=1;i<32;i++)
square_table[i]=(int)((8192.0*95.88)/(8128.0/i+100));
// 2A03 style nonlinear pcm mix with double the bits
//pcm_table[0] = 0;
//int wd = 22638;
//for(int d=1;d<256; ++d)
// pcm_table[d] = (int)((8192.0*159.79)/(100.0+1.0/((double)d/wd)));
// linear pcm mix (actual hardware seems closer to this)
pcm_table[0] = 0;
double pcm_scale = 32.0;
for (int d=1; d<256; ++d)
pcm_table[d] = (int)(double(d) * pcm_scale);
// stereo mix
for(int c=0;c<2;++c)
for(int t=0;t<3;++t)
sm[c][t] = 128;
}
NES_MMC5::~NES_MMC5 ()
{
}
void NES_MMC5::Reset ()
{
int i;
scounter[0] = 0;
scounter[1] = 0;
sphase[0] = 0;
sphase[1] = 0;
envelope_div[0] = 0;
envelope_div[1] = 0;
length_counter[0] = 0;
length_counter[1] = 0;
envelope_counter[0] = 0;
envelope_counter[1] = 0;
frame_sequence_count = 0;
for (i = 0; i < 8; i++)
Write (0x5000 + i, 0);
Write(0x5015, 0);
for (i = 0; i < 3; ++i)
out[i] = 0;
mask = 0;
pcm = 0; // PCM channel
pcm_mode = false; // write mode
SetRate(rate);
}
void NES_MMC5::SetOption (int id, int val)
{
if(id<OPT_END) option[id] = val;
}
void NES_MMC5::SetClock (double c)
{
this->clock = c;
}
void NES_MMC5::SetRate (double r)
{
rate = r ? r : DEFAULT_RATE;
}
void NES_MMC5::FrameSequence ()
{
// 240hz clock
for (int i=0; i < 2; ++i)
{
bool divider = false;
if (envelope_write[i])
{
envelope_write[i] = false;
envelope_counter[i] = 15;
envelope_div[i] = 0;
}
else
{
++envelope_div[i];
if (envelope_div[i] > envelope_div_period[i])
{
divider = true;
envelope_div[i] = 0;
}
}
if (divider)
{
if (envelope_loop[i] && envelope_counter[i] == 0)
envelope_counter[i] = 15;
else if (envelope_counter[i] > 0)
--envelope_counter[i];
}
}
// MMC5 length counter is clocked at 240hz, unlike 2A03
for (int i=0; i < 2; ++i)
{
if (!envelope_loop[i] && (length_counter[i] > 0))
--length_counter[i];
}
}
int NES_MMC5::calc_sqr (int i, unsigned int clocks)
{
static const short sqrtbl[4][16] = {
{0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
{0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0},
{1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}
};
scounter[i] += clocks;
while (scounter[i] > freq[i])
{
sphase[i] = (sphase[i] + 1) & 15;
scounter[i] -= (freq[i] + 1);
}
int ret = 0;
if (length_counter[i] > 0)
{
// note MMC5 does not silence the highest 8 frequencies like APU,
// because this is done by the sweep unit.
int v = envelope_disable[i] ? volume[i] : envelope_counter[i];
ret = sqrtbl[duty[i]][sphase[i]] ? v : 0;
}
return ret;
}
void NES_MMC5::TickFrameSequence (unsigned int clocks)
{
frame_sequence_count += clocks;
while (frame_sequence_count > 7458)
{
FrameSequence();
frame_sequence_count -= 7458;
}
}
void NES_MMC5::Tick (unsigned int clocks)
{
out[0] = calc_sqr(0, clocks);
out[1] = calc_sqr(1, clocks);
out[2] = pcm;
}
unsigned int NES_MMC5::Render (int b[2])
{
out[0] = (mask & 1) ? 0 : out[0];
out[1] = (mask & 2) ? 0 : out[1];
out[2] = (mask & 4) ? 0 : out[2];
int m[3];
if(option[OPT_NONLINEAR_MIXER])
{
// squares nonlinear
int voltage = square_table[out[0] + out[1]];
m[0] = out[0] << 6;
m[1] = out[1] << 6;
int ref = m[0] + m[1];
if (ref > 0)
{
m[0] = (m[0] * voltage) / ref;
m[1] = (m[1] * voltage) / ref;
}
else
{
m[0] = voltage;
m[1] = voltage;
}
// pcm nonlinear
m[2] = pcm_table[out[2]];
}
else
{
// squares
m[0] = out[0] << 6;
m[1] = out[1] << 6;
// pcm channel
m[2] = out[2] << 5;
}
// note polarity is flipped on output
b[0] = m[0] * -sm[0][0];
b[0] += m[1] * -sm[0][1];
b[0] += m[2] * -sm[0][2];
b[0] >>= 7;
b[1] = m[0] * -sm[1][0];
b[1] += m[1] * -sm[1][1];
b[1] += m[2] * -sm[1][2];
b[1] >>= 7;
return 2;
}
bool NES_MMC5::Write (unsigned int adr, unsigned int val, unsigned int id)
{
int ch;
static const unsigned char length_table[32] = {
0x0A, 0xFE,
0x14, 0x02,
0x28, 0x04,
0x50, 0x06,
0xA0, 0x08,
0x3C, 0x0A,
0x0E, 0x0C,
0x1A, 0x0E,
0x0C, 0x10,
0x18, 0x12,
0x30, 0x14,
0x60, 0x16,
0xC0, 0x18,
0x48, 0x1A,
0x10, 0x1C,
0x20, 0x1E
};
if ((0x5c00 <= adr) && (adr < 0x5ff0))
{
ram[adr & 0x3ff] = val;
return true;
}
else if ((0x5000 <= adr) && (adr < 0x5008))
{
reg[adr & 0x7] = val;
}
switch (adr)
{
case 0x5000:
case 0x5004:
ch = (adr >> 2) & 1;
volume[ch] = val & 15;
envelope_disable[ch] = (val >> 4) & 1;
envelope_loop[ch] = (val >> 5) & 1;
envelope_div_period[ch] = (val & 15);
duty[ch] = (val >> 6) & 3;
break;
case 0x5002:
case 0x5006:
ch = (adr >> 2) & 1;
freq[ch] = val + (freq[ch] & 0x700);
if (scounter[ch] > freq[ch]) scounter[ch] = freq[ch];
break;
case 0x5003:
case 0x5007:
ch = (adr >> 2) & 1;
freq[ch] = (freq[ch] & 0xff) + ((val & 7) << 8);
if (scounter[ch] > freq[ch]) scounter[ch] = freq[ch];
// phase reset
if (option[OPT_PHASE_REFRESH])
sphase[ch] = 0;
envelope_write[ch] = true;
if (enable[ch])
{
length_counter[ch] = length_table[(val >> 3) & 0x1f];
}
break;
// PCM channel control
case 0x5010:
pcm_mode = ((val & 1) != 0); // 0 = write, 1 = read
break;
// PCM channel control
case 0x5011:
if (!pcm_mode)
{
val &= 0xFF;
if (val != 0) pcm = val;
}
break;
case 0x5015:
enable[0] = (val & 1) ? true : false;
enable[1] = (val & 2) ? true : false;
if (!enable[0])
length_counter[0] = 0;
if (!enable[1])
length_counter[1] = 0;
break;
case 0x5205:
mreg[0] = val;
break;
case 0x5206:
mreg[1] = val;
break;
default:
return false;
}
return true;
}
bool NES_MMC5::Read (unsigned int adr, unsigned int & val, unsigned int id)
{
if ((0x5000 <= adr) && (adr < 0x5008))
{
val = reg[adr&0x7];
return true;
}
else if(adr == 0x5015)
{
val = (enable[1]?2:0)|(enable[0]?1:0);
return true;
}
if ((0x5c00 <= adr) && (adr < 0x5ff0))
{
val = ram[adr & 0x3ff];
return true;
}
else if (adr == 0x5205)
{
val = (mreg[0] * mreg[1]) & 0xff;
return true;
}
else if (adr == 0x5206)
{
val = (mreg[0] * mreg[1]) >> 8;
return true;
}
return false;
}
void NES_MMC5::SetStereoMix(int trk, short mixl, short mixr)
{
if (trk < 0) return;
if (trk > 2) return;
sm[0][trk] = mixl;
sm[1][trk] = mixr;
}
}// namespace

View File

@ -0,0 +1,67 @@
#ifndef _NES_MMC5_H_
#define _NES_MMC5_H_
namespace xgm
{
class NES_MMC5
{
public:
enum
{ OPT_NONLINEAR_MIXER=0, OPT_PHASE_REFRESH, OPT_END };
protected:
int option[OPT_END];
int mask;
int sm[2][3]; // stereo panning
unsigned char ram[0x6000 - 0x5c00];
unsigned char reg[8];
unsigned char mreg[2];
unsigned char pcm; // PCM channel
bool pcm_mode; // PCM channel
unsigned int scounter[2]; // frequency divider
unsigned int sphase[2]; // phase counter
unsigned int duty[2];
unsigned int volume[2];
unsigned int freq[2];
int out[3];
bool enable[2];
bool envelope_disable[2]; // エンベロープ有効フラグ
bool envelope_loop[2]; // エンベロープループ
bool envelope_write[2];
int envelope_div_period[2];
int envelope_div[2];
int envelope_counter[2];
int length_counter[2];
int frame_sequence_count;
double clock, rate;
int calc_sqr (int i, unsigned int clocks);
int square_table[32];
int pcm_table[256];
public:
NES_MMC5 ();
~NES_MMC5 ();
void FrameSequence ();
void TickFrameSequence (unsigned int clocks);
void Reset ();
void Tick (unsigned int clocks);
unsigned int Render (int b[2]);
bool Write (unsigned int adr, unsigned int val, unsigned int id=0);
bool Read (unsigned int adr, unsigned int & val, unsigned int id=0);
void SetOption (int id, int b);
void SetClock (double);
void SetRate (double);
void SetMask (int m){ mask = m; }
void SetStereoMix (int trk, short mixl, short mixr);
};
}
#endif

View File

@ -0,0 +1,332 @@
#include <cstring>
#include "nes_n106.h"
#include "common.h"
namespace xgm {
NES_N106::NES_N106 ()
{
option[OPT_SERIAL] = 0;
option[OPT_PHASE_READ_ONLY] = 0;
option[OPT_LIMIT_WAVELENGTH] = 0;
SetClock (DEFAULT_CLOCK);
SetRate (DEFAULT_RATE);
for (int i=0; i < 8; ++i)
{
sm[0][i] = 128;
sm[1][i] = 128;
}
Reset();
}
NES_N106::~NES_N106 ()
{
}
void NES_N106::SetStereoMix (int trk, short mixl, short mixr)
{
if (trk < 0 || trk >= 8) return;
trk = 7-trk; // displayed channels are inverted
sm[0][trk] = mixl;
sm[1][trk] = mixr;
}
void NES_N106::SetClock (double c)
{
clock = c;
}
void NES_N106::SetRate (double r)
{
rate = r;
}
void NES_N106::SetMask (int m)
{
// bit reverse the mask,
// N163 waves are displayed in reverse order
mask = 0
| ((m & (1<<0)) ? (1<<7) : 0)
| ((m & (1<<1)) ? (1<<6) : 0)
| ((m & (1<<2)) ? (1<<5) : 0)
| ((m & (1<<3)) ? (1<<4) : 0)
| ((m & (1<<4)) ? (1<<3) : 0)
| ((m & (1<<5)) ? (1<<2) : 0)
| ((m & (1<<6)) ? (1<<1) : 0)
| ((m & (1<<7)) ? (1<<0) : 0);
}
void NES_N106::SetOption (int id, int val)
{
if (id<OPT_END) option[id] = val;
}
void NES_N106::Reset ()
{
master_disable = false;
::memset(reg, 0, sizeof(reg));
reg_select = 0;
reg_advance = false;
tick_channel = 0;
tick_clock = 0;
render_channel = 0;
render_clock = 0;
render_subclock = 0;
for (int i=0; i<8; ++i) fout[i] = 0;
Write(0xE000, 0x00); // master disable off
Write(0xF800, 0x80); // select $00 with auto-increment
for (unsigned int i=0; i<0x80; ++i) // set all regs to 0
{
Write(0x4800, 0x00);
}
Write(0xF800, 0x00); // select $00 without auto-increment
}
void NES_N106::Tick (unsigned int clocks)
{
if (master_disable) return;
int channels = get_channels();
tick_clock += clocks;
render_clock += clocks; // keep render in sync
while (tick_clock > 0)
{
int channel = 7-tick_channel;
unsigned int phase = get_phase(channel);
unsigned int freq = get_freq(channel);
unsigned int len = get_len(channel);
unsigned int off = get_off(channel);
int vol = get_vol(channel);
// accumulate 24-bit phase
phase = (phase + freq) & 0x00FFFFFF;
// wrap phase if wavelength exceeded
unsigned int hilen = len << 16;
while (phase >= hilen) phase -= hilen;
// write back phase
set_phase(phase, channel);
// fetch sample (note: N163 output is centred at 8, and inverted w.r.t 2A03)
int sample = 8 - get_sample(((phase >> 16) + off) & 0xFF);
fout[channel] = sample * vol;
// cycle to next channel every 15 clocks
tick_clock -= 15;
++tick_channel;
if (tick_channel >= channels)
tick_channel = 0;
}
}
unsigned int NES_N106::Render (int b[2])
{
b[0] = 0;
b[1] = 0;
if (master_disable) return 2;
int channels = get_channels();
if (option[OPT_SERIAL]) // hardware accurate serial multiplexing
{
// this could be made more efficient than going clock-by-clock
// but this way is simpler
int clocks = render_clock;
while (clocks > 0)
{
int c = 7-render_channel;
if (0 == ((mask >> c) & 1))
{
b[0] += fout[c] * sm[0][c];
b[1] += fout[c] * sm[1][c];
}
++render_subclock;
if (render_subclock >= 15) // each channel gets a 15-cycle slice
{
render_subclock = 0;
++render_channel;
if (render_channel >= channels)
render_channel = 0;
}
--clocks;
}
// increase output level by 1 bits (7 bits already added from sm)
b[0] <<= 1;
b[1] <<= 1;
// average the output
if (render_clock > 0)
{
b[0] /= render_clock;
b[1] /= render_clock;
}
render_clock = 0;
}
else // just mix all channels
{
for (int i = (8-channels); i<8; ++i)
{
if (0 == ((mask >> i) & 1))
{
b[0] += fout[i] * sm[0][i];
b[1] += fout[i] * sm[1][i];
}
}
// mix together, increase output level by 8 bits, roll off 7 bits from sm
int MIX[9] = { 256/1, 256/1, 256/2, 256/3, 256/4, 256/5, 256/6, 256/6, 256/6 };
b[0] = (b[0] * MIX[channels]) >> 7;
b[1] = (b[1] * MIX[channels]) >> 7;
// when approximating the serial multiplex as a straight mix, once the
// multiplex frequency gets below the nyquist frequency an average mix
// begins to sound too quiet. To approximate this effect, I don't attenuate
// any further after 6 channels are active.
}
// 8 bit approximation of master volume
// max N163 vol vs max APU square
// unfortunately, games have been measured as low as 3.4x and as high as 8.5x
// with higher volumes on Erika, King of Kings, and Rolling Thunder
// and lower volumes on others. Using 6.0x as a rough "one size fits all".
const double MASTER_VOL = 6.0 * 1223.0;
const double MAX_OUT = 15.0 * 15.0 * 256.0; // max digital value
const int GAIN = int((MASTER_VOL / MAX_OUT) * 256.0f);
b[0] = (b[0] * GAIN) >> 8;
b[1] = (b[1] * GAIN) >> 8;
return 2;
}
bool NES_N106::Write (unsigned int adr, unsigned int val, unsigned int id)
{
if (adr == 0xE000) // master disable
{
master_disable = ((val & 0x40) != 0);
return true;
}
else if (adr == 0xF800) // register select
{
reg_select = (val & 0x7F);
reg_advance = (val & 0x80) != 0;
return true;
}
else if (adr == 0x4800) // register write
{
if (option[OPT_PHASE_READ_ONLY]) // old emulators didn't know phase was stored here
{
int c = 15 - (reg_select/8);
int r = reg_select & 7;
if (c < get_channels() &&
(r == 1 ||
r == 3 ||
r == 5))
{
if (reg_advance)
reg_select = (reg_select + 1) & 0x7F;
return true;
}
}
if (option[OPT_LIMIT_WAVELENGTH]) // old emulators ignored top 3 bits of length
{
int c = 15 - (reg_select/8);
int r = reg_select & 7;
if (c < get_channels() && r == 4)
{
val |= 0xE0;
}
}
reg[reg_select] = val;
if (reg_advance)
reg_select = (reg_select + 1) & 0x7F;
return true;
}
return false;
}
bool NES_N106::Read (unsigned int adr, unsigned int & val, unsigned int id)
{
if (adr == 0x4800) // register read
{
val = reg[reg_select];
if (reg_advance)
reg_select = (reg_select + 1) & 0x7F;
return true;
}
return false;
}
//
// register decoding/encoding functions
//
inline unsigned int NES_N106::get_phase (int channel)
{
// 24-bit phase stored in channel regs 1/3/5
channel = channel << 3;
return (reg[0x41 + channel] )
+ (reg[0x43 + channel] << 8 )
+ (reg[0x45 + channel] << 16);
}
inline unsigned int NES_N106::get_freq (int channel)
{
// 19-bit frequency stored in channel regs 0/2/4
channel = channel << 3;
return ( reg[0x40 + channel] )
+ ( reg[0x42 + channel] << 8 )
+ ((reg[0x44 + channel] & 0x03) << 16);
}
inline unsigned int NES_N106::get_off (int channel)
{
// 8-bit offset stored in channel reg 6
channel = channel << 3;
return reg[0x46 + channel];
}
inline unsigned int NES_N106::get_len (int channel)
{
// 6-bit<<3 length stored obscurely in channel reg 4
channel = channel << 3;
return 256 - (reg[0x44 + channel] & 0xFC);
}
inline int NES_N106::get_vol (int channel)
{
// 4-bit volume stored in channel reg 7
channel = channel << 3;
return reg[0x47 + channel] & 0x0F;
}
inline int NES_N106::get_sample (unsigned int index)
{
// every sample becomes 2 samples in regs
return (index&1) ?
((reg[index>>1] >> 4) & 0x0F) :
( reg[index>>1] & 0x0F) ;
}
inline int NES_N106::get_channels ()
{
// 3-bit channel count stored in reg 0x7F
return ((reg[0x7F] >> 4) & 0x07) + 1;
}
inline void NES_N106::set_phase (unsigned int phase, int channel)
{
// 24-bit phase stored in channel regs 1/3/5
channel = channel << 3;
reg[0x41 + channel] = phase & 0xFF;
reg[0x43 + channel] = (phase >> 8 ) & 0xFF;
reg[0x45 + channel] = (phase >> 16) & 0xFF;
}
} //namespace

View File

@ -0,0 +1,64 @@
#ifndef _NES_N106_H_
#define _NES_N106_H_
namespace xgm {
class NES_N106
{
public:
enum
{
OPT_SERIAL = 0,
OPT_PHASE_READ_ONLY = 1,
OPT_LIMIT_WAVELENGTH = 2,
OPT_END
};
protected:
double rate, clock;
int mask;
int sm[2][8]; // stereo mix
int fout[8]; // current output
int option[OPT_END];
bool master_disable;
unsigned int reg[0x80]; // all state is contained here
unsigned int reg_select;
bool reg_advance;
int tick_channel;
int tick_clock;
int render_channel;
int render_clock;
int render_subclock;
// convenience functions to interact with regs
inline unsigned int get_phase (int channel);
inline unsigned int get_freq (int channel);
inline unsigned int get_off (int channel);
inline unsigned int get_len (int channel);
inline int get_vol (int channel);
inline int get_sample (unsigned int index);
inline int get_channels ();
// for storing back the phase after modifying
inline void set_phase (unsigned int phase, int channel);
public:
NES_N106 ();
~NES_N106 ();
void Reset ();
void Tick (unsigned int clocks);
unsigned int Render (int b[2]);
bool Write (unsigned int adr, unsigned int val, unsigned int id=0);
bool Read (unsigned int adr, unsigned int & val, unsigned int id=0);
void SetRate (double);
void SetClock (double);
void SetOption (int, int);
void SetMask (int m);
void SetStereoMix (int trk, short mixl, short mixr);
};
} // namespace xgm
#endif

View File

@ -0,0 +1,239 @@
#include "nes_vrc6.h"
#include "common.h"
namespace xgm
{
NES_VRC6::NES_VRC6 ()
{
SetClock (DEFAULT_CLOCK);
SetRate (DEFAULT_RATE);
halt = false;
freq_shift = 0;
for(int c=0;c<2;++c)
for(int t=0;t<3;++t)
sm[c][t] = 128;
}
NES_VRC6::~NES_VRC6 ()
{
}
void NES_VRC6::SetStereoMix(int trk, short mixl, short mixr)
{
if (trk < 0) return;
if (trk > 2) return;
sm[0][trk] = mixl;
sm[1][trk] = mixr;
}
void NES_VRC6::SetClock (double c)
{
clock = c;
}
void NES_VRC6::SetRate (double r)
{
rate = r ? r : DEFAULT_RATE;
}
void NES_VRC6::SetOption (int id, int val)
{
if(id<OPT_END)
{
//option[id] = val;
}
}
void NES_VRC6::Reset ()
{
Write (0x9003, 0);
for (int i = 0; i < 3; i++)
{
Write (0x9000 + i, 0);
Write (0xa000 + i, 0);
Write (0xb000 + i, 0);
}
count14 = 0;
mask = 0;
counter[0] = 0;
counter[1] = 0;
counter[2] = 0;
phase[0] = 0;
phase[0] = 1;
phase[0] = 2;
}
short NES_VRC6::calc_sqr (int i, unsigned int clocks)
{
static const short sqrtbl[8][16] = {
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1},
{0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1},
{0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1}
};
if (!enable[i])
return 0;
if (!halt)
{
counter[i] += clocks;
while(counter[i] > freq2[i])
{
phase[i] = (phase[i] + 1) & 15;
counter[i] -= (freq2[i] + 1);
}
}
return (gate[i]
|| sqrtbl[duty[i]][phase[i]])? volume[i] : 0;
}
short NES_VRC6::calc_saw (unsigned int clocks)
{
if (!enable[2])
return 0;
if (!halt)
{
counter[2] += clocks;
while(counter[2] > freq2[2])
{
counter[2] -= (freq2[2] + 1);
// accumulate saw
++count14;
if (count14 >= 14)
{
count14 = 0;
phase[2] = 0;
}
else if (0 == (count14 & 1)) // only accumulate on even ticks
{
phase[2] = (phase[2] + volume[2]) & 0xFF; // note 8-bit wrapping behaviour
}
}
}
// only top 5 bits of saw are output
return phase[2] >> 3;
}
void NES_VRC6::Tick (unsigned int clocks)
{
out[0] = calc_sqr(0,clocks);
out[1] = calc_sqr(1,clocks);
out[2] = calc_saw(clocks);
}
unsigned int NES_VRC6::Render (int b[2])
{
int m[3];
m[0] = out[0];
m[1] = out[1];
m[2] = out[2];
// note: signal is inverted compared to 2A03
m[0] = (mask & 1) ? 0 : -m[0];
m[1] = (mask & 2) ? 0 : -m[1];
m[2] = (mask & 4) ? 0 : -m[2];
b[0] = m[0] * sm[0][0];
b[0] += m[1] * sm[0][1];
b[0] += m[2] * sm[0][2];
//b[0] >>= (7 - 7);
b[1] = m[0] * sm[1][0];
b[1] += m[1] * sm[1][1];
b[1] += m[2] * sm[1][2];
//b[1] >>= (7 - 7);
// master volume adjustment
const int MASTER = int(256.0 * 1223.0 / 1920.0);
b[0] = (b[0] * MASTER) >> 8;
b[1] = (b[1] * MASTER) >> 8;
return 2;
}
bool NES_VRC6::Write (unsigned int adr, unsigned int val, unsigned int id)
{
int ch, cmap[4] = { 0, 0, 1, 2 };
switch (adr)
{
case 0x9000:
case 0xa000:
ch = cmap[(adr >> 12) & 3];
volume[ch] = val & 15;
duty[ch] = (val >> 4) & 7;
gate[ch] = (val >> 7) & 1;
break;
case 0xb000:
volume[2] = val & 63;
break;
case 0x9001:
case 0xa001:
case 0xb001:
ch = cmap[(adr >> 12) & 3];
freq[ch] = (freq[ch] & 0xf00) | val;
freq2[ch] = (freq[ch] >> freq_shift);
if (counter[ch] > freq2[ch]) counter[ch] = freq2[ch];
break;
case 0x9002:
case 0xa002:
case 0xb002:
ch = cmap[(adr >> 12) & 3];
freq[ch] = ((val & 0xf) << 8) + (freq[ch] & 0xff);
freq2[ch] = (freq[ch] >> freq_shift);
if (counter[ch] > freq2[ch]) counter[ch] = freq2[ch];
if (!enable[ch]) // if enable is being turned on, phase should be reset
{
if (ch == 2)
{
count14 = 0; // reset saw
}
phase[ch] = 0;
}
enable[ch] = (val >> 7) & 1;
break;
case 0x9003:
halt = val & 1;
freq_shift =
(val & 4) ? 8 :
(val & 2) ? 4 :
0;
freq2[0] = (freq[0] >> freq_shift);
freq2[1] = (freq[1] >> freq_shift);
freq2[2] = (freq[2] >> freq_shift);
if (counter[0] > freq2[0]) counter[0] = freq2[0];
if (counter[1] > freq2[1]) counter[1] = freq2[1];
if (counter[2] > freq2[2]) counter[2] = freq2[2];
break;
default:
return false;
}
return true;
}
bool NES_VRC6::Read (unsigned int adr, unsigned int & val, unsigned int id)
{
return false;
}
} // namespace

View File

@ -0,0 +1,53 @@
#ifndef _NES_VRC6_H_
#define _NES_VRC6_H_
namespace xgm
{
class NES_VRC6
{
public:
enum
{
OPT_END
};
protected:
unsigned int counter[3]; // frequency divider
unsigned int phase[3]; // phase counter
unsigned int freq2[3]; // adjusted frequency
int count14; // saw 14-stage counter
//int option[OPT_END];
int mask;
int sm[2][3]; // stereo mix
int duty[2];
int volume[3];
int enable[3];
int gate[3];
unsigned int freq[3];
short calc_sqr (int i, unsigned int clocks);
short calc_saw (unsigned int clocks);
bool halt;
int freq_shift;
double clock, rate;
int out[3];
public:
NES_VRC6 ();
~NES_VRC6 ();
void Reset ();
void Tick (unsigned int clocks);
unsigned int Render (int b[2]);
bool Read (unsigned int adr, unsigned int & val, unsigned int id=0);
bool Write (unsigned int adr, unsigned int val, unsigned int id=0);
void SetClock (double);
void SetRate (double);
void SetOption (int, int);
void SetMask (int m){ mask = m; }
void SetStereoMix (int trk, short mixl, short mixr);
};
} // namespace
#endif

View File

@ -0,0 +1,60 @@
MODIFIED
this is a modified version of the NES audio emulation core.
it converts the files to UTF-8 and Unix line endings.
XGM SOURCE ARCHIVE
This source archive is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY. You can reuse these source code freely. However,
we possibly change the structure and interface of the program code without
any advance notice.
HOW TO COMPILE
Open the workspace file top.sln with Visual C++ 7.0 or later
version. We can compile both KbMediaPlayer and Winamp version of
NSFplug on this workspace.
To make a KbMediaPlayer version of NSFplug, that is, in_nsf.kpi,
Please choose 'kbnsf' project as an active project. Then, you can
build in_nsf.kpi by build menu.
On the other hand, to make a Winamp version of NSFplug, activate
'wa2nsf' project and build it.
Note that after the build process, VC++ copies the plugin files to:
C:\Program Files\KbMediaPlayer\Plugins\OK\in_nsf\in_nsf.kpi
C:\Program Files\Windamp\Plugins\in_nsf.dll
If you don't need to have these copies, please remove or modify the
custom build settings.
ACKNOWLEDGEMENT
I thank Mamiya and Kobarin and Nullsoft for their great source code.
I thank Norix and Izumi for the fruitful discussions and the NSFplug
users for their comments and bug reports.
COPYRIGHTS
NSFplug is built on KM6502, KbMediaPlayer plugin SDK and Winamp2
plugin SDK.
NSFplug uses KM6502 in emulating a 6502 cpu. KM6502 code is stored in
devices\CPU\km6502 folder of this source archive. KM6502 is a public
domain software. See the document of KM6502 stored in the folder.
KbMediaPlayer Plugin SDK is provided by Kobarin. The SDK code is also
packed in the kbmedia\sdk folder of this archive. The copyright of
the source code remains with Kobarin.
The files in winamp/sdk folder of this archive are the header files
from Winamp2 Plugin SDK provided by Nullsoft. The copyright of these
header files remains with Nullsoft.
CONTACT
Digital Sound Antiques
http://dsa.sakura.ne.jp/

View File

@ -498,7 +498,7 @@ uint32_t opz_registers::compute_phase_step(uint32_t choffs, uint32_t opoffs, opd
// additional 12 bits of resolution; this calculation gives us, for
// example, a frequency of 8.0009Hz when 8Hz is requested
uint32_t substep = m_phase_substep[opoffs];
substep += 75 * freq;
substep += 75 * 1024 * freq;
phase_step = substep >> 12;
m_phase_substep[opoffs] = substep & 0xfff;

View File

@ -320,6 +320,9 @@ public:
// generate one sample of sound
void generate(output_data *output, uint32_t numsamples = 1);
// get the engine
fm_engine* debug_engine() { return &m_fm; }
protected:
// internal state
uint8_t m_address; // address register

View File

@ -147,6 +147,8 @@ const char* DivPlatformTX81Z::getEffectName(unsigned char effect) {
void DivPlatformTX81Z::acquire(short* bufL, short* bufR, size_t start, size_t len) {
static int os[2];
ymfm::ym2414::fm_engine* fme=fm_ymfm->debug_engine();
for (size_t h=start; h<start+len; h++) {
os[0]=0; os[1]=0;
if (!writes.empty()) {
@ -162,6 +164,10 @@ void DivPlatformTX81Z::acquire(short* bufL, short* bufR, size_t start, size_t le
fm_ymfm->generate(&out_ymfm);
for (int i=0; i<8; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=(fme->debug_channel(i)->debug_output(0)+fme->debug_channel(i)->debug_output(1));
}
os[0]=out_ymfm.data[0];
if (os[0]<-32768) os[0]=-32768;
if (os[0]>32767) os[0]=32767;
@ -192,10 +198,14 @@ void DivPlatformTX81Z::tick(bool sysTick) {
for (int j=0; j<4; j++) {
unsigned short baseAddr=chanOffs[i]|opOffs[j];
DivInstrumentFM::Operator& op=chan[i].state.op[j];
if (isOutput[chan[i].state.alg][j]) {
rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127));
if (isMuted[i]) {
rWrite(baseAddr+ADDR_TL,127);
} else {
rWrite(baseAddr+ADDR_TL,op.tl);
if (isOutput[chan[i].state.alg][j]) {
rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127));
} else {
rWrite(baseAddr+ADDR_TL,op.tl);
}
}
}
}
@ -260,11 +270,7 @@ void DivPlatformTX81Z::tick(bool sysTick) {
if (chan[i].std.alg.had) {
chan[i].state.alg=chan[i].std.alg.val;
if (isMuted[i]) {
immWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|0x40);
} else {
immWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|(chan[i].active?0:0x40)|(chan[i].chVolR<<7));
}
immWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|(chan[i].active?0:0x40)|(chan[i].chVolR<<7));
if (!parent->song.algMacroBehavior) for (int j=0; j<4; j++) {
unsigned short baseAddr=chanOffs[i]|opOffs[j];
DivInstrumentFM::Operator& op=chan[i].state.op[j];
@ -281,11 +287,7 @@ void DivPlatformTX81Z::tick(bool sysTick) {
}
if (chan[i].std.fb.had) {
chan[i].state.fb=chan[i].std.fb.val;
if (isMuted[i]) {
immWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|0x40);
} else {
immWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|(chan[i].active?0:0x40)|(chan[i].chVolR<<7));
}
immWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|(chan[i].active?0:0x40)|(chan[i].chVolR<<7));
}
if (chan[i].std.fms.had) {
chan[i].state.fms=chan[i].std.fms.val;
@ -313,7 +315,7 @@ void DivPlatformTX81Z::tick(bool sysTick) {
}
if (m.mult.had) {
op.mult=m.mult.val;
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4));
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|((op.egt?(op.dt&7):dtTable[op.dt&7])<<4));
}
if (m.rr.had) {
op.rr=m.rr.val;
@ -325,10 +327,14 @@ void DivPlatformTX81Z::tick(bool sysTick) {
}
if (m.tl.had) {
op.tl=127-m.tl.val;
if (isOutput[chan[i].state.alg][j]) {
rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127));
if (isMuted[i]) {
rWrite(baseAddr+ADDR_TL,127);
} else {
rWrite(baseAddr+ADDR_TL,op.tl);
if (isOutput[chan[i].state.alg][j]) {
rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127));
} else {
rWrite(baseAddr+ADDR_TL,op.tl);
}
}
}
if (m.rs.had) {
@ -337,7 +343,7 @@ void DivPlatformTX81Z::tick(bool sysTick) {
}
if (m.dt.had) {
op.dt=m.dt.val;
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4));
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|((op.egt?(op.dt&7):dtTable[op.dt&7])<<4));
}
if (m.d2r.had) {
op.d2r=m.d2r.val;
@ -358,12 +364,8 @@ void DivPlatformTX81Z::tick(bool sysTick) {
oldWrites[baseAddr+ADDR_TL]=-1;
}
}
if (isMuted[i]) {
immWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|0x00);
} else {
//if (chan[i].keyOn) immWrite(0x08,i);
immWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|0x00|(chan[i].chVolR<<7));
}
//if (chan[i].keyOn) immWrite(0x08,i);
immWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|0x00|(chan[i].chVolR<<7));
if (chan[i].hardReset && chan[i].keyOn) {
for (int j=0; j<4; j++) {
unsigned short baseAddr=chanOffs[i]|opOffs[j];
@ -405,12 +407,8 @@ void DivPlatformTX81Z::tick(bool sysTick) {
chan[i].freqChanged=false;
}
if (chan[i].keyOn) {
if (isMuted[i]) {
immWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3));
} else {
//immWrite(0x08,i);
immWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|0x40|(chan[i].chVolR<<7));
}
//immWrite(0x08,i);
immWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|0x40|(chan[i].chVolR<<7));
chan[i].keyOn=false;
}
}
@ -418,13 +416,19 @@ void DivPlatformTX81Z::tick(bool sysTick) {
void DivPlatformTX81Z::muteChannel(int ch, bool mute) {
isMuted[ch]=mute;
// TODO: use volume registers!
/*
if (isMuted[ch]) {
immWrite(chanOffs[ch]+ADDR_LR_FB_ALG,(chan[ch].state.alg&7)|(chan[ch].state.fb<<3));
} else {
immWrite(chanOffs[ch]+ADDR_LR_FB_ALG,(chan[ch].state.alg&7)|(chan[ch].state.fb<<3)|((chan[ch].chVolL&1)<<6)|((chan[ch].chVolR&1)<<7));
}*/
for (int i=0; i<4; i++) {
unsigned short baseAddr=chanOffs[ch]|opOffs[i];
DivInstrumentFM::Operator op=chan[ch].state.op[i];
if (isMuted[ch]) {
rWrite(baseAddr+ADDR_TL,127);
} else {
if (isOutput[chan[ch].state.alg][i]) {
rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[ch].outVol&0x7f))/127));
} else {
rWrite(baseAddr+ADDR_TL,op.tl);
}
}
}
}
int DivPlatformTX81Z::dispatch(DivCommand c) {
@ -444,17 +448,21 @@ int DivPlatformTX81Z::dispatch(DivCommand c) {
for (int i=0; i<4; i++) {
unsigned short baseAddr=chanOffs[c.chan]|opOffs[i];
DivInstrumentFM::Operator op=chan[c.chan].state.op[i];
if (isOutput[chan[c.chan].state.alg][i]) {
if (!chan[c.chan].active || chan[c.chan].insChanged) {
rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127));
}
if (isMuted[c.chan]) {
rWrite(baseAddr+ADDR_TL,127);
} else {
if (chan[c.chan].insChanged) {
rWrite(baseAddr+ADDR_TL,op.tl);
if (isOutput[chan[c.chan].state.alg][i]) {
if (!chan[c.chan].active || chan[c.chan].insChanged) {
rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127));
}
} else {
if (chan[c.chan].insChanged) {
rWrite(baseAddr+ADDR_TL,op.tl);
}
}
}
if (chan[c.chan].insChanged) {
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4));
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|((op.egt?(op.dt&7):dtTable[op.dt&7])<<4));
rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.egt<<5)|(op.rs<<6));
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
rWrite(baseAddr+ADDR_DT2_D2R,(op.d2r&31)|(op.dt2<<6));
@ -506,10 +514,14 @@ int DivPlatformTX81Z::dispatch(DivCommand c) {
for (int i=0; i<4; i++) {
unsigned short baseAddr=chanOffs[c.chan]|opOffs[i];
DivInstrumentFM::Operator& op=chan[c.chan].state.op[i];
if (isOutput[chan[c.chan].state.alg][i]) {
rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127));
if (isMuted[c.chan]) {
rWrite(baseAddr+ADDR_TL,127);
} else {
rWrite(baseAddr+ADDR_TL,op.tl);
if (isOutput[chan[c.chan].state.alg][i]) {
rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127));
} else {
rWrite(baseAddr+ADDR_TL,op.tl);
}
}
}
break;
@ -593,17 +605,21 @@ int DivPlatformTX81Z::dispatch(DivCommand c) {
unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]];
DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]];
op.mult=c.value2&15;
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4));
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|((op.egt?(op.dt&7):dtTable[op.dt&7])<<4));
break;
}
case DIV_CMD_FM_TL: {
unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]];
DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]];
op.tl=c.value2;
if (isOutput[chan[c.chan].state.alg][c.value]) {
rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127));
if (isMuted[c.chan]) {
rWrite(baseAddr+ADDR_TL,127);
} else {
rWrite(baseAddr+ADDR_TL,op.tl);
if (isOutput[chan[c.chan].state.alg][c.value]) {
rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127));
} else {
rWrite(baseAddr+ADDR_TL,op.tl);
}
}
break;
}
@ -672,12 +688,16 @@ void DivPlatformTX81Z::forceIns() {
for (int j=0; j<4; j++) {
unsigned short baseAddr=chanOffs[i]|opOffs[j];
DivInstrumentFM::Operator op=chan[i].state.op[j];
if (isOutput[chan[i].state.alg][j]) {
rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127));
if (isMuted[i]) {
rWrite(baseAddr+ADDR_TL,127);
} else {
rWrite(baseAddr+ADDR_TL,op.tl);
if (isOutput[chan[i].state.alg][j]) {
rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127));
} else {
rWrite(baseAddr+ADDR_TL,op.tl);
}
}
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4));
rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|((op.egt?(op.dt&7):dtTable[op.dt&7])<<4));
rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.egt<<5)|(op.rs<<6));
rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7));
rWrite(baseAddr+ADDR_DT2_D2R,(op.d2r&31)|(op.dt2<<6));
@ -714,6 +734,10 @@ void* DivPlatformTX81Z::getChanState(int ch) {
return &chan[ch];
}
DivDispatchOscBuffer* DivPlatformTX81Z::getOscBuffer(int ch) {
return oscBuf[ch];
}
unsigned char* DivPlatformTX81Z::getRegisterPool() {
return regPool;
}
@ -777,6 +801,9 @@ void DivPlatformTX81Z::setFlags(unsigned int flags) {
baseFreqOff=0;
}
rate=chipClock/64;
for (int i=0; i<8; i++) {
oscBuf[i]->rate=rate;
}
}
bool DivPlatformTX81Z::isStereo() {
@ -789,6 +816,7 @@ int DivPlatformTX81Z::init(DivEngine* p, int channels, int sugRate, unsigned int
skipRegisterWrites=false;
for (int i=0; i<8; i++) {
isMuted[i]=false;
oscBuf[i]=new DivDispatchOscBuffer;
}
setFlags(flags);
fm_ymfm=new ymfm::ym2414(iface);
@ -798,6 +826,9 @@ int DivPlatformTX81Z::init(DivEngine* p, int channels, int sugRate, unsigned int
}
void DivPlatformTX81Z::quit() {
for (int i=0; i<8; i++) {
delete oscBuf[i];
}
delete fm_ymfm;
}

View File

@ -68,6 +68,7 @@ class DivPlatformTX81Z: public DivDispatch {
chVolR(127) {}
};
Channel chan[8];
DivDispatchOscBuffer* oscBuf[8];
struct QueuedWrite {
unsigned short addr;
unsigned char val;
@ -102,6 +103,7 @@ class DivPlatformTX81Z: public DivDispatch {
void acquire(short* bufL, short* bufR, size_t start, size_t len);
int dispatch(DivCommand c);
void* getChanState(int chan);
DivDispatchOscBuffer* getOscBuffer(int chan);
unsigned char* getRegisterPool();
int getRegisterPoolSize();
void reset();

View File

@ -19,6 +19,7 @@
#include "x1_010.h"
#include "../engine.h"
#include "../../ta-log.h"
#include <math.h>
//#define rWrite(a,v) pendingWrites[a]=v;
@ -909,6 +910,48 @@ void DivPlatformX1_010::poke(std::vector<DivRegWrite>& wlist) {
for (DivRegWrite& i: wlist) rWrite(i.addr,i.val);
}
const void* DivPlatformX1_010::getSampleMem(int index) {
return index == 0 ? sampleMem : 0;
}
size_t DivPlatformX1_010::getSampleMemCapacity(int index) {
return index == 0 ? 1048576 : 0;
}
size_t DivPlatformX1_010::getSampleMemUsage(int index) {
return index == 0 ? sampleMemLen : 0;
}
void DivPlatformX1_010::renderSamples() {
memset(sampleMem,0,getSampleMemCapacity());
size_t memPos=0;
for (int i=0; i<parent->song.sampleLen; i++) {
DivSample* s=parent->song.sample[i];
int paddedLen=(s->length8+4095)&(~0xfff);
// fit sample bank size to 128KB for Seta 2 external bankswitching logic (not emulated yet!)
if (paddedLen>131072) {
paddedLen=131072;
}
if ((memPos&0xfe0000)!=((memPos+paddedLen)&0xfe0000)) {
memPos=(memPos+0x1ffff)&0xfe0000;
}
if (memPos>=getSampleMemCapacity()) {
logW("out of X1-010 memory for sample %d!",i);
break;
}
if (memPos+paddedLen>=getSampleMemCapacity()) {
memcpy(sampleMem+memPos,s->data8,getSampleMemCapacity()-memPos);
logW("out of X1-010 memory for sample %d!",i);
} else {
memcpy(sampleMem+memPos,s->data8,paddedLen);
}
s->offX1_010=memPos;
memPos+=paddedLen;
}
sampleMemLen=memPos+256;
}
int DivPlatformX1_010::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
parent=p;
dumpWrites=false;
@ -919,7 +962,9 @@ int DivPlatformX1_010::init(DivEngine* p, int channels, int sugRate, unsigned in
oscBuf[i]=new DivDispatchOscBuffer;
}
setFlags(flags);
intf.parent=parent;
sampleMem=new unsigned char[getSampleMemCapacity()];
sampleMemLen=0;
intf.memory=sampleMem;
x1_010=new x1_010_core(intf);
x1_010->reset();
reset();
@ -931,6 +976,7 @@ void DivPlatformX1_010::quit() {
delete oscBuf[i];
}
delete x1_010;
delete[] sampleMem;
}
DivPlatformX1_010::~DivPlatformX1_010() {

View File

@ -28,13 +28,13 @@
class DivX1_010Interface: public x1_010_mem_intf {
public:
DivEngine* parent;
unsigned char* memory;
int sampleBank;
virtual u8 read_byte(u32 address) override {
if (parent->x1_010Mem==NULL) return 0;
return parent->x1_010Mem[address & 0xfffff];
if (memory==NULL) return 0;
return memory[address & 0xfffff];
}
DivX1_010Interface(): parent(NULL), sampleBank(0) {}
DivX1_010Interface(): memory(NULL), sampleBank(0) {}
};
class DivPlatformX1_010: public DivDispatch {
@ -115,6 +115,8 @@ class DivPlatformX1_010: public DivDispatch {
DivDispatchOscBuffer* oscBuf[16];
bool isMuted[16];
bool stereo=false;
unsigned char* sampleMem;
size_t sampleMemLen;
unsigned char sampleBank;
DivX1_010Interface intf;
x1_010_core* x1_010;
@ -141,6 +143,10 @@ class DivPlatformX1_010: public DivDispatch {
void notifyInsDeletion(void* ins);
void poke(unsigned int addr, unsigned short val);
void poke(std::vector<DivRegWrite>& wlist);
const void* getSampleMem(int index = 0);
size_t getSampleMemCapacity(int index = 0);
size_t getSampleMemUsage(int index = 0);
void renderSamples();
const char** getRegisterSheet();
const char* getEffectName(unsigned char effect);
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);

View File

@ -20,6 +20,7 @@
#include "ym2610.h"
#include "sound/ymfm/ymfm.h"
#include "../engine.h"
#include "../../ta-log.h"
#include <string.h>
#include <math.h>
@ -245,6 +246,85 @@ const char* regCheatSheetYM2610[]={
NULL
};
const void* DivPlatformYM2610Base::getSampleMem(int index) {
return index == 0 ? adpcmAMem : index == 1 ? adpcmBMem : NULL;
}
size_t DivPlatformYM2610Base::getSampleMemCapacity(int index) {
return index == 0 ? 16777216 : index == 1 ? 16777216 : 0;
}
size_t DivPlatformYM2610Base::getSampleMemUsage(int index) {
return index == 0 ? adpcmAMemLen : index == 1 ? adpcmBMemLen : 0;
}
void DivPlatformYM2610Base::renderSamples() {
memset(adpcmAMem,0,getSampleMemCapacity(0));
size_t memPos=0;
for (int i=0; i<parent->song.sampleLen; i++) {
DivSample* s=parent->song.sample[i];
int paddedLen=(s->lengthA+255)&(~0xff);
if ((memPos&0xf00000)!=((memPos+paddedLen)&0xf00000)) {
memPos=(memPos+0xfffff)&0xf00000;
}
if (memPos>=getSampleMemCapacity(0)) {
logW("out of ADPCM-A memory for sample %d!",i);
break;
}
if (memPos+paddedLen>=getSampleMemCapacity(0)) {
memcpy(adpcmAMem+memPos,s->dataA,getSampleMemCapacity(0)-memPos);
logW("out of ADPCM-A memory for sample %d!",i);
} else {
memcpy(adpcmAMem+memPos,s->dataA,paddedLen);
}
s->offA=memPos;
memPos+=paddedLen;
}
adpcmAMemLen=memPos+256;
memset(adpcmBMem,0,getSampleMemCapacity(1));
memPos=0;
for (int i=0; i<parent->song.sampleLen; i++) {
DivSample* s=parent->song.sample[i];
int paddedLen=(s->lengthB+255)&(~0xff);
if ((memPos&0xf00000)!=((memPos+paddedLen)&0xf00000)) {
memPos=(memPos+0xfffff)&0xf00000;
}
if (memPos>=getSampleMemCapacity(1)) {
logW("out of ADPCM-B memory for sample %d!",i);
break;
}
if (memPos+paddedLen>=getSampleMemCapacity(1)) {
memcpy(adpcmBMem+memPos,s->dataB,getSampleMemCapacity(1)-memPos);
logW("out of ADPCM-B memory for sample %d!",i);
} else {
memcpy(adpcmBMem+memPos,s->dataB,paddedLen);
}
s->offB=memPos;
memPos+=paddedLen;
}
adpcmBMemLen=memPos+256;
}
int DivPlatformYM2610Base::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
parent=p;
adpcmAMem=new unsigned char[getSampleMemCapacity(0)];
adpcmAMemLen=0;
adpcmBMem=new unsigned char[getSampleMemCapacity(1)];
adpcmBMemLen=0;
iface.adpcmAMem=adpcmAMem;
iface.adpcmBMem=adpcmBMem;
iface.sampleBank=0;
return 0;
}
void DivPlatformYM2610Base::quit() {
delete[] adpcmAMem;
delete[] adpcmBMem;
}
const char** DivPlatformYM2610::getRegisterSheet() {
return regCheatSheetYM2610;
}
@ -1185,7 +1265,7 @@ void DivPlatformYM2610::setSkipRegisterWrites(bool value) {
}
int DivPlatformYM2610::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
parent=p;
DivPlatformYM2610Base::init(p, channels, sugRate, flags);
dumpWrites=false;
skipRegisterWrites=false;
for (int i=0; i<14; i++) {
@ -1197,8 +1277,6 @@ int DivPlatformYM2610::init(DivEngine* p, int channels, int sugRate, unsigned in
for (int i=0; i<14; i++) {
oscBuf[i]->rate=rate;
}
iface.parent=parent;
iface.sampleBank=0;
fm=new ymfm::ym2610(iface);
// YM2149, 2MHz
ay=new DivPlatformAY8910;
@ -1215,6 +1293,7 @@ void DivPlatformYM2610::quit() {
ay->quit();
delete ay;
delete fm;
DivPlatformYM2610Base::quit();
}
DivPlatformYM2610::~DivPlatformYM2610() {

View File

@ -27,14 +27,32 @@
class DivYM2610Interface: public ymfm::ymfm_interface {
public:
DivEngine* parent;
unsigned char* adpcmAMem;
unsigned char* adpcmBMem;
int sampleBank;
uint8_t ymfm_external_read(ymfm::access_class type, uint32_t address);
void ymfm_external_write(ymfm::access_class type, uint32_t address, uint8_t data);
DivYM2610Interface(): parent(NULL), sampleBank(0) {}
DivYM2610Interface(): adpcmAMem(NULL), adpcmBMem(NULL), sampleBank(0) {}
};
class DivPlatformYM2610: public DivDispatch {
class DivPlatformYM2610Base: public DivDispatch {
protected:
unsigned char* adpcmAMem;
size_t adpcmAMemLen;
unsigned char* adpcmBMem;
size_t adpcmBMemLen;
DivYM2610Interface iface;
public:
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();
};
class DivPlatformYM2610: public DivPlatformYM2610Base {
protected:
const unsigned short chanOffs[4]={
0x01, 0x02, 0x101, 0x102
@ -93,7 +111,6 @@ class DivPlatformYM2610: public DivDispatch {
std::queue<QueuedWrite> writes;
ymfm::ym2610* fm;
ymfm::ym2610::output_data fmout;
DivYM2610Interface iface;
DivPlatformAY8910* ay;
unsigned char regPool[512];

View File

@ -24,13 +24,11 @@
uint8_t DivYM2610Interface::ymfm_external_read(ymfm::access_class type, uint32_t address) {
switch (type) {
case ymfm::ACCESS_ADPCM_A:
if (parent->adpcmAMem==NULL) return 0;
if ((address&0xffffff)>=parent->adpcmAMemLen) return 0;
return parent->adpcmAMem[address&0xffffff];
if (adpcmAMem==NULL) return 0;
return adpcmAMem[address&0xffffff];
case ymfm::ACCESS_ADPCM_B:
if (parent->adpcmBMem==NULL) return 0;
if ((address&0xffffff)>=parent->adpcmBMemLen) return 0;
return parent->adpcmBMem[address&0xffffff];
if (adpcmBMem==NULL) return 0;
return adpcmBMem[address&0xffffff];
default:
return 0;
}

View File

@ -1243,7 +1243,7 @@ void DivPlatformYM2610B::setSkipRegisterWrites(bool value) {
}
int DivPlatformYM2610B::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
parent=p;
DivPlatformYM2610Base::init(p, channels, sugRate, flags);
dumpWrites=false;
skipRegisterWrites=false;
for (int i=0; i<16; i++) {
@ -1255,8 +1255,6 @@ int DivPlatformYM2610B::init(DivEngine* p, int channels, int sugRate, unsigned i
for (int i=0; i<16; i++) {
oscBuf[i]->rate=rate;
}
iface.parent=parent;
iface.sampleBank=0;
fm=new ymfm::ym2610b(iface);
// YM2149, 2MHz
ay=new DivPlatformAY8910;
@ -1273,6 +1271,7 @@ void DivPlatformYM2610B::quit() {
ay->quit();
delete ay;
delete fm;
DivPlatformYM2610Base::quit();
}
DivPlatformYM2610B::~DivPlatformYM2610B() {

View File

@ -26,7 +26,7 @@
#include "ym2610.h"
class DivPlatformYM2610B: public DivDispatch {
class DivPlatformYM2610B: public DivPlatformYM2610Base {
protected:
const unsigned short chanOffs[6]={
0x00, 0x01, 0x02, 0x100, 0x101, 0x102
@ -85,7 +85,6 @@ class DivPlatformYM2610B: public DivDispatch {
std::queue<QueuedWrite> writes;
ymfm::ym2610b* fm;
ymfm::ym2610b::output_data fmout;
DivYM2610Interface iface;
unsigned char regPool[512];
unsigned char lastBusy;

View File

@ -65,9 +65,24 @@ const char* cmdName[]={
"FM_LFO",
"FM_LFO_WAVE",
"FM_TL",
"FM_AM",
"FM_AR",
"FM_DR",
"FM_SL",
"FM_D2R",
"FM_RR",
"FM_DT",
"FM_DT2",
"FM_RS",
"FM_KSR",
"FM_VIB",
"FM_SUS",
"FM_WS",
"FM_SSG",
"FM_FB",
"FM_MULT",
"FM_FINE",
"FM_FIXFREQ",
"FM_EXTCH",
"FM_AM_DEPTH",
"FM_PM_DEPTH",
@ -156,6 +171,8 @@ const char* cmdName[]={
"ES5506_FILTER_MODE",
"ES5506_FILTER_K1",
"ES5506_FILTER_K2",
"ES5506_FILTER_K1_SLIDE",
"ES5506_FILTER_K2_SLIDE",
"ES5506_ENVELOPE_COUNT",
"ES5506_ENVELOPE_LVRAMP",
"ES5506_ENVELOPE_RVRAMP",
@ -339,6 +356,9 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe
case 0x14: // sweep down
dispatchCmd(DivCommand(DIV_CMD_NES_SWEEP,ch,1,effectVal));
break;
case 0x18: // DPCM mode
dispatchCmd(DivCommand(DIV_CMD_SAMPLE_MODE,ch,effectVal));
break;
default:
return false;
}
@ -557,6 +577,14 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe
case 0x27: // envelope K2RAMP
dispatchCmd(DivCommand(DIV_CMD_ES5506_ENVELOPE_K2RAMP,ch,effectVal,effect&0x01));
break;
case 0x28: // filter K1 slide up
case 0x29: // filter K1 slide down
dispatchCmd(DivCommand(DIV_CMD_ES5506_FILTER_K1_SLIDE,ch,effectVal,effect&0x01));
break;
case 0x2a: // filter K2 slide up
case 0x2b: // filter K2 slide down
dispatchCmd(DivCommand(DIV_CMD_ES5506_FILTER_K2_SLIDE,ch,effectVal,effect&0x01));
break;
default:
if ((effect&0xf0)==0x30) {
dispatchCmd(DivCommand(DIV_CMD_ES5506_FILTER_K1,ch,((effect&0x0f)<<8)|effectVal));
@ -1978,8 +2006,8 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi
}
runtotal[i]=blip_clocks_needed(disCont[i].bb[0],size-lastAvail[i]);
if (runtotal[i]>disCont[i].bbInLen) {
delete disCont[i].bbIn[0];
delete disCont[i].bbIn[1];
delete[] disCont[i].bbIn[0];
delete[] disCont[i].bbIn[1];
disCont[i].bbIn[0]=new short[runtotal[i]+256];
disCont[i].bbIn[1]=new short[runtotal[i]+256];
disCont[i].bbInLen=runtotal[i]+256;

View File

@ -752,11 +752,11 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
bool writeDACSamples=false;
bool writeNESSamples=false;
bool writePCESamples=false;
unsigned char writeADPCM=0;
unsigned char writeSegaPCM=0;
unsigned char writeX1010=0;
unsigned char writeQSound=0;
unsigned char writeES5506=0;
DivDispatch* writeADPCM[2]={NULL,NULL};
int writeSegaPCM=0;
DivDispatch* writeX1010[2]={NULL,NULL};
DivDispatch* writeQSound[2]={NULL,NULL};
DivDispatch* writeES5506[2]={NULL,NULL};
for (int i=0; i<song.systemLen; i++) {
willExport[i]=false;
@ -845,11 +845,11 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
if (!hasX1) {
hasX1=disCont[i].dispatch->chipClock;
willExport[i]=true;
writeX1010=1;
writeX1010[0]=disCont[i].dispatch;
} else if (!(hasX1&0x40000000)) {
isSecond[i]=true;
willExport[i]=true;
writeX1010=2;
writeX1010[1]=disCont[i].dispatch;
hasX1|=0x40000000;
howManyChips++;
}
@ -863,11 +863,11 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
if (!hasOPNB) {
hasOPNB=disCont[i].dispatch->chipClock;
willExport[i]=true;
writeADPCM=1;
writeADPCM[0]=disCont[i].dispatch;
} else if (!(hasOPNB&0x40000000)) {
isSecond[i]=true;
willExport[i]=true;
writeADPCM=2;
writeADPCM[1]=disCont[i].dispatch;
hasOPNB|=0x40000000;
howManyChips++;
}
@ -969,11 +969,11 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
// not be able to handle the 64kb sample bank trick
hasQSound=disCont[i].dispatch->chipClock;
willExport[i]=true;
writeQSound=1;
writeQSound[0]=disCont[i].dispatch;
} else if (!(hasQSound&0x40000000)) {
isSecond[i]=true;
willExport[i]=false;
writeQSound=2;
writeQSound[1]=disCont[i].dispatch;
addWarning("dual QSound is not supported by the VGM format");
}
break;
@ -997,12 +997,12 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
// VGM identifies ES5506 if highest bit sets, otherwise ES5505
hasES5505=0x80000000|disCont[i].dispatch->chipClock;
willExport[i]=true;
writeES5506=1;
writeES5506[0]=disCont[i].dispatch;
} else if (!(hasES5505&0x40000000)) {
isSecond[i]=true;
willExport[i]=false;
hasES5505|=0xc0000000;
writeES5506=2;
writeES5506[1]=disCont[i].dispatch;
howManyChips++;
}
break;
@ -1303,68 +1303,68 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
delete[] pcmMem;
}
if (adpcmAMemLen>0) {
for (unsigned char i=0; i<writeADPCM; i++) {
for (int i=0; i<2; i++) {
if (writeADPCM[i]!=NULL && writeADPCM[i]->getSampleMemUsage(0)>0) {
w->writeC(0x67);
w->writeC(0x66);
w->writeC(0x82);
w->writeI((adpcmAMemLen+8)|(i*0x80000000));
w->writeI(adpcmAMemLen);
w->writeI((writeADPCM[i]->getSampleMemUsage(0)+8)|(i*0x80000000));
w->writeI(writeADPCM[i]->getSampleMemCapacity(0));
w->writeI(0);
w->write(adpcmAMem,adpcmAMemLen);
w->write(writeADPCM[i]->getSampleMem(0),writeADPCM[i]->getSampleMemUsage(0));
}
}
if (adpcmBMemLen>0) {
for (unsigned char i=0; i<writeADPCM; i++) {
for (int i=0; i<2; i++) {
if (writeADPCM[i]!=NULL && writeADPCM[i]->getSampleMemUsage(1)>0) {
w->writeC(0x67);
w->writeC(0x66);
w->writeC(0x83);
w->writeI((adpcmBMemLen+8)|(i*0x80000000));
w->writeI(adpcmBMemLen);
w->writeI((writeADPCM[i]->getSampleMemUsage(1)+8)|(i*0x80000000));
w->writeI(writeADPCM[i]->getSampleMemCapacity(1));
w->writeI(0);
w->write(adpcmBMem,adpcmBMemLen);
w->write(writeADPCM[i]->getSampleMem(1),writeADPCM[i]->getSampleMemUsage(1));
}
}
if (qsoundMemLen>0) {
// always write a whole bank
unsigned int blockSize=(qsoundMemLen+0xffff)&(~0xffff);
if (blockSize > 0x1000000) {
blockSize = 0x1000000;
}
for (unsigned char i=0; i<writeQSound; i++) {
for (int i=0; i<2; i++) {
if (writeQSound[i]!=NULL && writeQSound[i]->getSampleMemUsage()>0) {
unsigned int blockSize=(writeQSound[i]->getSampleMemUsage()+0xffff)&(~0xffff);
if (blockSize > 0x1000000) {
blockSize = 0x1000000;
}
w->writeC(0x67);
w->writeC(0x66);
w->writeC(0x8F);
w->writeI((blockSize+8)|(i*0x80000000));
w->writeI(0x1000000);
w->writeI(writeQSound[i]->getSampleMemCapacity());
w->writeI(0);
w->write(qsoundMem,blockSize);
w->write(writeQSound[i]->getSampleMem(),blockSize);
}
}
if (x1_010MemLen>0) {
for (unsigned char i=0; i<writeX1010; i++) {
for (int i=0; i<2; i++) {
if (writeX1010[i]!=NULL && writeX1010[i]->getSampleMemUsage()>0) {
w->writeC(0x67);
w->writeC(0x66);
w->writeC(0x91);
w->writeI((x1_010MemLen+8)|(i*0x80000000));
w->writeI(x1_010MemLen);
w->writeI((writeX1010[i]->getSampleMemUsage()+8)|(i*0x80000000));
w->writeI(writeX1010[i]->getSampleMemCapacity());
w->writeI(0);
w->write(x1_010Mem,x1_010MemLen);
w->write(writeX1010[i]->getSampleMem(),writeX1010[i]->getSampleMemUsage());
}
}
if (es5506MemLen>0) {
for (unsigned char i=0; i<writeES5506; i++) {
// TODO
for (int i=0; i<2; i++) {
if (writeES5506[i]!=NULL && writeES5506[i]->getSampleMemUsage()>0) {
w->writeC(0x67);
w->writeC(0x66);
w->writeC(0x8F);
w->writeI((es5506MemLen+8)|(i*0x80000000));
w->writeI(es5506MemLen);
w->writeI((writeES5506[i]->getSampleMemUsage()+8)|(i*0x80000000));
w->writeI(writeES5506[i]->getSampleMemCapacity());
w->writeI(0);
w->write(es5506Mem,es5506MemLen);
w->write(writeES5506[i]->getSampleMem(),writeES5506[i]->getSampleMemUsage());
}
}

View File

@ -30,15 +30,38 @@ void FurnaceGUI::drawChanOsc() {
if (!chanOscOpen) return;
ImGui::SetNextWindowSizeConstraints(ImVec2(64.0f*dpiScale,32.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale));
if (ImGui::Begin("Oscilloscope (per-channel)",&chanOscOpen)) {
if (ImGui::InputInt("Columns",&chanOscCols,1,1)) {
if (chanOscCols<1) chanOscCols=1;
if (chanOscCols>64) chanOscCols=64;
if (ImGui::BeginTable("ChanOscSettings",3)) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::Text("Columns");
ImGui::SameLine();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
if (ImGui::InputInt("##COSColumns",&chanOscCols,1,1)) {
if (chanOscCols<1) chanOscCols=1;
if (chanOscCols>64) chanOscCols=64;
}
ImGui::TableNextColumn();
ImGui::Text("Size (ms)");
ImGui::SameLine();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
if (ImGui::InputFloat("##COSWinSize",&chanOscWindowSize,1.0f,1.0f)) {
if (chanOscWindowSize<1.0f) chanOscWindowSize=1.0f;
if (chanOscWindowSize>50.0f) chanOscWindowSize=50.0f;
}
ImGui::TableNextColumn();
ImGui::Checkbox("Center waveform",&chanOscWaveCorr);
ImGui::EndTable();
}
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding,ImVec2(0.0f,0.0f));
float availY=ImGui::GetContentRegionAvail().y;
if (ImGui::BeginTable("ChanOsc",chanOscCols,ImGuiTableFlags_Borders)) {
std::vector<DivDispatchOscBuffer*> oscBufs;
std::vector<int> oscChans;
int chans=e->getTotalChannelCount();
ImDrawList* dl=ImGui::GetWindowDrawList();
ImGuiWindow* window=ImGui::GetCurrentWindow();
@ -49,7 +72,10 @@ void FurnaceGUI::drawChanOsc() {
for (int i=0; i<chans; i++) {
DivDispatchOscBuffer* buf=e->getOscBuffer(i);
if (buf!=NULL) oscBufs.push_back(buf);
if (buf!=NULL) {
oscBufs.push_back(buf);
oscChans.push_back(i);
}
}
int rows=(oscBufs.size()+(chanOscCols-1))/chanOscCols;
@ -58,13 +84,14 @@ void FurnaceGUI::drawChanOsc() {
ImGui::TableNextColumn();
DivDispatchOscBuffer* buf=oscBufs[i];
int ch=oscChans[i];
if (buf==NULL) {
ImGui::Text("Error!");
} else {
ImVec2 size=ImGui::GetContentRegionAvail();
size.y=availY/rows;
int displaySize=(buf->rate)/30;
int displaySize=(float)(buf->rate)*(chanOscWindowSize/1000.0f);
ImVec2 minArea=window->DC.CursorPos;
ImVec2 maxArea=ImVec2(
@ -85,7 +112,26 @@ void FurnaceGUI::drawChanOsc() {
waveform[i]=ImLerp(inRect.Min,inRect.Max,ImVec2(x,0.5f));
}
} else {
unsigned short needlePos=buf->needle-displaySize;
unsigned short needlePos=buf->needle;
if (chanOscWaveCorr) {
float cutoff=0.01f;
while (buf->readNeedle!=needlePos) {
//float old=chanOscLP1[ch];
chanOscLP0[ch]+=cutoff*((float)buf->data[buf->readNeedle]-chanOscLP0[ch]);
chanOscLP1[ch]+=cutoff*(chanOscLP0[ch]-chanOscLP1[ch]);
if (chanOscLP1[ch]>=20) {
lastCorrPos[ch]=buf->readNeedle;
}
buf->readNeedle++;
}
needlePos=lastCorrPos[ch];
/*
for (unsigned short i=0; i<displaySize; i++) {
short old=buf->data[needlePos--];
if (buf->data[needlePos]>old) break;
}*/
}
needlePos-=displaySize;
for (unsigned short i=0; i<512; i++) {
float x=(float)i/512.0f;
float y=(float)buf->data[(unsigned short)(needlePos+(i*displaySize/512))]/65536.0f;

View File

@ -367,6 +367,10 @@ void putDispatchChan(void* data, int chanNum, int type) {
ImGui::Text(" - K2Ramp: %d",ch->envelope.k2Ramp);
ImGui::Text(" - K1Offs: %d",ch->k1Offs);
ImGui::Text(" - K2Offs: %d",ch->k2Offs);
ImGui::Text(" - K1Slide: %d",ch->k1Slide);
ImGui::Text(" - K2Slide: %d",ch->k2Slide);
ImGui::Text(" - K1Prev: %d",ch->k1Prev);
ImGui::Text(" - K2Prev: %d",ch->k2Prev);
ImGui::Text("- vol: %.2x",ch->vol);
ImGui::Text("- LVol: %.2x",ch->lVol);
ImGui::Text("- RVol: %.2x",ch->rVol);

View File

@ -21,6 +21,7 @@
#include "debug.h"
#include "IconsFontAwesome4.h"
#include <fmt/printf.h>
#include <imgui.h>
void FurnaceGUI::drawDebug() {
static int bpOrder;
@ -141,6 +142,51 @@ void FurnaceGUI::drawDebug() {
ImGui::Columns();
ImGui::TreePop();
}
if (ImGui::TreeNode("Sample Debug")) {
for (int i=0; i<e->song.sampleLen; i++) {
DivSample* sample=e->getSample(i);
if (sample==NULL) {
ImGui::Text("%d: <NULL!>",i);
continue;
}
ImGui::Text("%d: %s",i,sample->name.c_str());
ImGui::Indent();
ImGui::Text("rate: %d",sample->rate);
ImGui::Text("centerRate: %d",sample->centerRate);
ImGui::Text("loopStart: %d",sample->loopStart);
ImGui::Text("loopOffP: %d",sample->loopOffP);
ImGui::Text("depth: %d",sample->depth);
ImGui::Text("length8: %d",sample->length8);
ImGui::Text("length16: %d",sample->length16);
ImGui::Text("length1: %d",sample->length1);
ImGui::Text("lengthDPCM: %d",sample->lengthDPCM);
ImGui::Text("lengthQSoundA: %d",sample->lengthQSoundA);
ImGui::Text("lengthA: %d",sample->lengthA);
ImGui::Text("lengthB: %d",sample->lengthB);
ImGui::Text("lengthX68: %d",sample->lengthX68);
ImGui::Text("lengthBRR: %d",sample->lengthBRR);
ImGui::Text("lengthVOX: %d",sample->lengthVOX);
ImGui::Text("off8: %x",sample->off8);
ImGui::Text("off16: %x",sample->off16);
ImGui::Text("off1: %x",sample->off1);
ImGui::Text("offDPCM: %x",sample->offDPCM);
ImGui::Text("offQSoundA: %x",sample->offQSoundA);
ImGui::Text("offA: %x",sample->offA);
ImGui::Text("offB: %x",sample->offB);
ImGui::Text("offX68: %x",sample->offX68);
ImGui::Text("offBRR: %x",sample->offBRR);
ImGui::Text("offVOX: %x",sample->offVOX);
ImGui::Text("offSegaPCM: %x",sample->offSegaPCM);
ImGui::Text("offQSound: %x",sample->offQSound);
ImGui::Text("offX1_010: %x",sample->offX1_010);
ImGui::Text("offES5506: %x",sample->offES5506);
ImGui::Text("samples: %d",sample->samples);
ImGui::Unindent();
}
ImGui::TreePop();
}
if (ImGui::TreeNode("Playground")) {
if (pgSys<0 || pgSys>=e->song.systemLen) pgSys=0;
if (ImGui::BeginCombo("System",fmt::sprintf("%d. %s",pgSys+1,e->getSystemName(e->song.system[pgSys])).c_str())) {

View File

@ -4000,6 +4000,8 @@ FurnaceGUI::FurnaceGUI():
oscZoom(0.5f),
oscZoomSlider(false),
chanOscCols(3),
chanOscWindowSize(20.0f),
chanOscWaveCorr(true),
followLog(true),
pianoOctaves(7),
pianoOptions(false),
@ -4057,4 +4059,8 @@ FurnaceGUI::FurnaceGUI():
memset(patChanSlideY,0,sizeof(float)*(DIV_MAX_CHANS+1));
memset(lastIns,-1,sizeof(int)*DIV_MAX_CHANS);
memset(oscValues,0,sizeof(float)*512);
memset(chanOscLP0,0,sizeof(float)*DIV_MAX_CHANS);
memset(chanOscLP1,0,sizeof(float)*DIV_MAX_CHANS);
memset(lastCorrPos,0,sizeof(short)*DIV_MAX_CHANS);
}

View File

@ -788,6 +788,7 @@ class FurnaceGUI {
int arcadeCore;
int ym2612Core;
int saaCore;
int nesCore;
int mainFont;
int patFont;
int audioRate;
@ -870,6 +871,7 @@ class FurnaceGUI {
arcadeCore(0),
ym2612Core(0),
saaCore(1),
nesCore(0),
mainFont(0),
patFont(0),
audioRate(44100),
@ -1101,6 +1103,12 @@ class FurnaceGUI {
// per-channel oscilloscope
int chanOscCols;
float chanOscWindowSize;
bool chanOscWaveCorr;
float chanOscLP0[DIV_MAX_CHANS];
float chanOscLP1[DIV_MAX_CHANS];
unsigned short lastNeedlePos[DIV_MAX_CHANS];
unsigned short lastCorrPos[DIV_MAX_CHANS];
// visualizer
float keyHit[DIV_MAX_CHANS];

View File

@ -84,6 +84,11 @@ const char* saaCores[]={
"SAASound"
};
const char* nesCores[]={
"puNES",
"NSFplay"
};
const char* valueInputStyles[]={
"Disabled/custom",
"Two octaves (0 is C-4, F is D#5)",
@ -859,6 +864,10 @@ void FurnaceGUI::drawSettings() {
ImGui::SameLine();
ImGui::Combo("##SAACore",&settings.saaCore,saaCores,2);
ImGui::Text("NES core");
ImGui::SameLine();
ImGui::Combo("##NESCore",&settings.nesCore,nesCores,2);
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Appearance")) {
@ -1731,6 +1740,7 @@ void FurnaceGUI::syncSettings() {
settings.arcadeCore=e->getConfInt("arcadeCore",0);
settings.ym2612Core=e->getConfInt("ym2612Core",0);
settings.saaCore=e->getConfInt("saaCore",1);
settings.nesCore=e->getConfInt("nesCore",0);
settings.mainFont=e->getConfInt("mainFont",0);
settings.patFont=e->getConfInt("patFont",0);
settings.mainFontPath=e->getConfString("mainFontPath","");
@ -1805,6 +1815,7 @@ void FurnaceGUI::syncSettings() {
clampSetting(settings.arcadeCore,0,1);
clampSetting(settings.ym2612Core,0,1);
clampSetting(settings.saaCore,0,1);
clampSetting(settings.nesCore,0,1);
clampSetting(settings.mainFont,0,6);
clampSetting(settings.patFont,0,6);
clampSetting(settings.patRowsBase,0,1);
@ -1907,6 +1918,7 @@ void FurnaceGUI::commitSettings() {
e->setConf("arcadeCore",settings.arcadeCore);
e->setConf("ym2612Core",settings.ym2612Core);
e->setConf("saaCore",settings.saaCore);
e->setConf("nesCore",settings.nesCore);
e->setConf("mainFont",settings.mainFont);
e->setConf("patFont",settings.patFont);
e->setConf("mainFontPath",settings.mainFontPath);

View File

@ -28,26 +28,17 @@ void FurnaceGUI::drawStats() {
}
if (!statsOpen) return;
if (ImGui::Begin("Statistics",&statsOpen)) {
String adpcmAUsage=fmt::sprintf("%d/16384KB",e->adpcmAMemLen/1024);
String adpcmBUsage=fmt::sprintf("%d/16384KB",e->adpcmBMemLen/1024);
String qsoundUsage=fmt::sprintf("%d/16384KB",e->qsoundMemLen/1024);
String x1_010Usage=fmt::sprintf("%d/1024KB",e->x1_010MemLen/1024);
String es5506Usage=fmt::sprintf("%d/16384KB",e->es5506MemLen/1024);
ImGui::Text("ADPCM-A");
ImGui::SameLine();
ImGui::ProgressBar(((float)e->adpcmAMemLen)/16777216.0f,ImVec2(-FLT_MIN,0),adpcmAUsage.c_str());
ImGui::Text("ADPCM-B");
ImGui::SameLine();
ImGui::ProgressBar(((float)e->adpcmBMemLen)/16777216.0f,ImVec2(-FLT_MIN,0),adpcmBUsage.c_str());
ImGui::Text("QSound");
ImGui::SameLine();
ImGui::ProgressBar(((float)e->qsoundMemLen)/16777216.0f,ImVec2(-FLT_MIN,0),qsoundUsage.c_str());
ImGui::Text("X1-010");
ImGui::SameLine();
ImGui::ProgressBar(((float)e->x1_010MemLen)/1048576.0f,ImVec2(-FLT_MIN,0),x1_010Usage.c_str());
ImGui::Text("ES5506");
ImGui::SameLine();
ImGui::ProgressBar(((float)e->es5506MemLen)/16777216.0f,ImVec2(-FLT_MIN,0),es5506Usage.c_str());
for (int i=0; i<e->song.systemLen; i++) {
DivDispatch* dispatch=e->getDispatch(i);
for (int j=0; dispatch!=NULL && dispatch->getSampleMemCapacity(j)>0; j++) {
size_t capacity=dispatch->getSampleMemCapacity(j);
size_t usage=dispatch->getSampleMemUsage(j);
String usageStr=fmt::sprintf("%d/%dKB",usage/1024,capacity/1024);
ImGui::Text("%s [%d]", e->getSystemName(e->song.system[i]), j);
ImGui::SameLine();
ImGui::ProgressBar(((float)usage)/((float)capacity),ImVec2(-FLT_MIN,0),usageStr.c_str());
}
}
}
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_STATS;
ImGui::End();