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

This commit is contained in:
cam900 2023-12-01 20:07:35 +09:00
commit b427bab4b6
76 changed files with 12670 additions and 165 deletions

View file

@ -427,34 +427,74 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do
case DIV_SYSTEM_OPL:
dispatch=new DivPlatformOPL;
((DivPlatformOPL*)dispatch)->setOPLType(1,false);
if (isRender) {
((DivPlatformOPL*)dispatch)->setCore(eng->getConfInt("opl2CoreRender",0));
} else {
((DivPlatformOPL*)dispatch)->setCore(eng->getConfInt("opl2Core",0));
}
break;
case DIV_SYSTEM_OPL_DRUMS:
dispatch=new DivPlatformOPL;
((DivPlatformOPL*)dispatch)->setOPLType(1,true);
if (isRender) {
((DivPlatformOPL*)dispatch)->setCore(eng->getConfInt("opl2CoreRender",0));
} else {
((DivPlatformOPL*)dispatch)->setCore(eng->getConfInt("opl2Core",0));
}
break;
case DIV_SYSTEM_OPL2:
dispatch=new DivPlatformOPL;
((DivPlatformOPL*)dispatch)->setOPLType(2,false);
if (isRender) {
((DivPlatformOPL*)dispatch)->setCore(eng->getConfInt("opl2CoreRender",0));
} else {
((DivPlatformOPL*)dispatch)->setCore(eng->getConfInt("opl2Core",0));
}
break;
case DIV_SYSTEM_OPL2_DRUMS:
dispatch=new DivPlatformOPL;
((DivPlatformOPL*)dispatch)->setOPLType(2,true);
if (isRender) {
((DivPlatformOPL*)dispatch)->setCore(eng->getConfInt("opl2CoreRender",0));
} else {
((DivPlatformOPL*)dispatch)->setCore(eng->getConfInt("opl2Core",0));
}
break;
case DIV_SYSTEM_OPL3:
dispatch=new DivPlatformOPL;
((DivPlatformOPL*)dispatch)->setOPLType(3,false);
if (isRender) {
((DivPlatformOPL*)dispatch)->setCore(eng->getConfInt("opl3CoreRender",0));
} else {
((DivPlatformOPL*)dispatch)->setCore(eng->getConfInt("opl3Core",0));
}
break;
case DIV_SYSTEM_OPL3_DRUMS:
dispatch=new DivPlatformOPL;
((DivPlatformOPL*)dispatch)->setOPLType(3,true);
if (isRender) {
((DivPlatformOPL*)dispatch)->setCore(eng->getConfInt("opl3CoreRender",0));
} else {
((DivPlatformOPL*)dispatch)->setCore(eng->getConfInt("opl3Core",0));
}
break;
case DIV_SYSTEM_Y8950:
dispatch=new DivPlatformOPL;
((DivPlatformOPL*)dispatch)->setOPLType(8950,false);
if (isRender) {
((DivPlatformOPL*)dispatch)->setCore(eng->getConfInt("opl2CoreRender",0));
} else {
((DivPlatformOPL*)dispatch)->setCore(eng->getConfInt("opl2Core",0));
}
break;
case DIV_SYSTEM_Y8950_DRUMS:
dispatch=new DivPlatformOPL;
((DivPlatformOPL*)dispatch)->setOPLType(8950,true);
if (isRender) {
((DivPlatformOPL*)dispatch)->setCore(eng->getConfInt("opl2CoreRender",0));
} else {
((DivPlatformOPL*)dispatch)->setCore(eng->getConfInt("opl2Core",0));
}
break;
case DIV_SYSTEM_OPZ:
dispatch=new DivPlatformTX81Z;

View file

@ -294,6 +294,9 @@ struct DivSysDef {
unsigned char id_DMF;
int channels;
bool isFM, isSTD, isCompound;
// width 0: variable
// height 0: no wavetable support
unsigned short waveWidth, waveHeight;
unsigned int vgmVersion;
unsigned int sampleFormatMask;
const char* chanNames[DIV_MAX_CHANS];
@ -307,7 +310,8 @@ struct DivSysDef {
const EffectHandlerMap preEffectHandlers;
DivSysDef(
const char* sysName, const char* sysNameJ, unsigned char fileID, unsigned char fileID_DMF, int chans,
bool isFMChip, bool isSTDChip, unsigned int vgmVer, bool compound, unsigned int formatMask, const char* desc,
bool isFMChip, bool isSTDChip, unsigned int vgmVer, bool compound, unsigned int formatMask, unsigned short waveWid, unsigned short waveHei,
const char* desc,
std::initializer_list<const char*> chNames,
std::initializer_list<const char*> chShortNames,
std::initializer_list<int> chTypes,
@ -325,6 +329,8 @@ struct DivSysDef {
isFM(isFMChip),
isSTD(isSTDChip),
isCompound(compound),
waveWidth(waveWid),
waveHeight(waveHei),
vgmVersion(vgmVer),
sampleFormatMask(formatMask),
effectHandlers(fxHandlers_),

View file

@ -289,8 +289,14 @@ void DivPlatformGenesis::acquire_ymfm(short** buf, size_t len) {
}
}
void DivPlatformGenesis::acquire_nuked276(short** buf, size_t len) {
// TODO
}
void DivPlatformGenesis::acquire(short** buf, size_t len) {
if (useYMFM) {
if (useYMFM==2) {
acquire_nuked276(buf,len);
} else if (useYMFM==1) {
acquire_ymfm(buf,len);
} else {
acquire_nuked(buf,len);
@ -1309,7 +1315,9 @@ float DivPlatformGenesis::getPostAmp() {
void DivPlatformGenesis::reset() {
writes.clear();
memset(regPool,0,512);
if (useYMFM) {
if (useYMFM==2) {
memset(&fm_276,0,sizeof(fmopn2_t));
} else if (useYMFM==1) {
fm_ymfm->reset();
}
OPN2_Reset(&fm);
@ -1396,7 +1404,7 @@ int DivPlatformGenesis::getPortaFloor(int ch) {
return 0;
}
void DivPlatformGenesis::setYMFM(bool use) {
void DivPlatformGenesis::setYMFM(unsigned char use) {
useYMFM=use;
}
@ -1441,7 +1449,7 @@ void DivPlatformGenesis::setFlags(const DivConfig& flags) {
break;
}
CHECK_CUSTOM_CLOCK;
if (useYMFM) {
if (useYMFM==1) {
if (fm_ymfm!=NULL) delete fm_ymfm;
if (chipType==1) {
fm_ymfm=new ymfm::ym2612(iface);

View file

@ -22,7 +22,7 @@
#include "fmshared_OPN.h"
#include "sound/ymfm/ymfm_opn.h"
#include "../../../extern/YMF276-LLE/fmopn2.h"
class DivYM2612Interface: public ymfm::ymfm_interface {
int setA, setB;
@ -77,6 +77,7 @@ class DivPlatformGenesis: public DivPlatformOPN {
DivDispatchOscBuffer* oscBuf[10];
bool isMuted[10];
ym3438_t fm;
fmopn2_t fm_276;
ymfm::ym2612* fm_ymfm;
ymfm::ym2612::output_data out_ymfm;
@ -84,7 +85,8 @@ class DivPlatformGenesis: public DivPlatformOPN {
int softPCMTimer;
bool extMode, softPCM, noExtMacros, useYMFM, canWriteDAC;
bool extMode, softPCM, noExtMacros, canWriteDAC;
unsigned char useYMFM;
unsigned char chipType;
short dacWrite;
@ -96,6 +98,7 @@ class DivPlatformGenesis: public DivPlatformOPN {
inline void processDAC(int iRate);
inline void commitState(int ch, DivInstrument* ins);
void acquire_nuked(short** buf, size_t len);
void acquire_nuked276(short** buf, size_t len);
void acquire_ymfm(short** buf, size_t len);
friend void putDispatchChip(void*,int);
@ -116,7 +119,7 @@ class DivPlatformGenesis: public DivPlatformOPN {
void tick(bool sysTick=true);
void muteChannel(int ch, bool mute);
int getOutputCount();
void setYMFM(bool use);
void setYMFM(unsigned char use);
bool keyOffAffectsArp(int ch);
bool keyOffAffectsPorta(int ch);
float getPostAmp();

View file

@ -292,12 +292,507 @@ void DivPlatformOPL::acquire_nuked(short** buf, size_t len) {
}
}
void DivPlatformOPL::acquire_ymfm1(short** buf, size_t len) {
ymfm::ymfm_output<1> out;
ymfm::ym3526::fm_engine* fme=fm_ymfm1->debug_fm_engine();
ymfm::fm_channel<ymfm::opl_registers_base<1>>* fmChan[9];
for (int i=0; i<9; i++) {
fmChan[i]=fme->debug_channel(i);
}
for (size_t h=0; h<len; h++) {
if (!writes.empty() && --delay<0) {
delay=1;
QueuedWrite& w=writes.front();
fm_ymfm1->write(0,w.addr);
fm_ymfm1->write(1,w.val);
regPool[w.addr&511]=w.val;
writes.pop();
}
fm_ymfm1->generate(&out,1);
buf[0][h]=out.data[0];
if (properDrums) {
for (int i=0; i<7; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP(fmChan[i]->debug_output(0)<<2,-32768,32767);
}
oscBuf[7]->data[oscBuf[7]->needle++]=CLAMP(fmChan[7]->debug_special1()<<2,-32768,32767);
oscBuf[8]->data[oscBuf[8]->needle++]=CLAMP(fmChan[8]->debug_special1()<<2,-32768,32767);
oscBuf[9]->data[oscBuf[9]->needle++]=CLAMP(fmChan[8]->debug_special2()<<2,-32768,32767);
oscBuf[10]->data[oscBuf[10]->needle++]=CLAMP(fmChan[7]->debug_special2()<<2,-32768,32767);
} else {
for (int i=0; i<9; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP(fmChan[i]->debug_output(0)<<2,-32768,32767);
}
}
}
}
void DivPlatformOPL::acquire_ymfm2(short** buf, size_t len) {
ymfm::ymfm_output<1> out;
ymfm::ym3812::fm_engine* fme=fm_ymfm2->debug_fm_engine();
ymfm::fm_channel<ymfm::opl_registers_base<2>>* fmChan[9];
for (int i=0; i<9; i++) {
fmChan[i]=fme->debug_channel(i);
}
for (size_t h=0; h<len; h++) {
if (!writes.empty() && --delay<0) {
delay=1;
QueuedWrite& w=writes.front();
fm_ymfm2->write(0,w.addr);
fm_ymfm2->write(1,w.val);
regPool[w.addr&511]=w.val;
writes.pop();
}
fm_ymfm2->generate(&out,1);
buf[0][h]=out.data[0];
if (properDrums) {
for (int i=0; i<7; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP(fmChan[i]->debug_output(0)<<2,-32768,32767);
}
oscBuf[7]->data[oscBuf[7]->needle++]=CLAMP(fmChan[7]->debug_special1()<<2,-32768,32767);
oscBuf[8]->data[oscBuf[8]->needle++]=CLAMP(fmChan[8]->debug_special1()<<2,-32768,32767);
oscBuf[9]->data[oscBuf[9]->needle++]=CLAMP(fmChan[8]->debug_special2()<<2,-32768,32767);
oscBuf[10]->data[oscBuf[10]->needle++]=CLAMP(fmChan[7]->debug_special2()<<2,-32768,32767);
} else {
for (int i=0; i<9; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP(fmChan[i]->debug_output(0)<<2,-32768,32767);
}
}
}
}
void DivPlatformOPL::acquire_ymfm8950(short** buf, size_t len) {
ymfm::ymfm_output<1> out;
ymfm::y8950::fm_engine* fme=fm_ymfm8950->debug_fm_engine();
ymfm::adpcm_b_engine* abe=fm_ymfm8950->debug_adpcm_b_engine();
ymfm::fm_channel<ymfm::opl_registers_base<1>>* fmChan[9];
for (int i=0; i<9; i++) {
fmChan[i]=fme->debug_channel(i);
}
for (size_t h=0; h<len; h++) {
if (!writes.empty() && --delay<0) {
delay=1;
QueuedWrite& w=writes.front();
fm_ymfm8950->write(0,w.addr);
fm_ymfm8950->write(1,w.val);
regPool[w.addr&511]=w.val;
writes.pop();
}
fm_ymfm8950->generate(&out,1);
buf[0][h]=out.data[0];
if (properDrums) {
for (int i=0; i<7; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP(fmChan[i]->debug_output(0)<<2,-32768,32767);
}
oscBuf[7]->data[oscBuf[7]->needle++]=CLAMP(fmChan[7]->debug_special1()<<2,-32768,32767);
oscBuf[8]->data[oscBuf[8]->needle++]=CLAMP(fmChan[8]->debug_special1()<<2,-32768,32767);
oscBuf[9]->data[oscBuf[9]->needle++]=CLAMP(fmChan[8]->debug_special2()<<2,-32768,32767);
oscBuf[10]->data[oscBuf[10]->needle++]=CLAMP(fmChan[7]->debug_special2()<<2,-32768,32767);
oscBuf[11]->data[oscBuf[11]->needle++]=CLAMP(abe->get_last_out(0),-32768,32767);
} else {
for (int i=0; i<9; i++) {
oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP(fmChan[i]->debug_output(0)<<2,-32768,32767);
}
oscBuf[9]->data[oscBuf[9]->needle++]=CLAMP(abe->get_last_out(0),-32768,32767);
}
}
}
void DivPlatformOPL::acquire_ymfm3(short** buf, size_t len) {
ymfm::ymfm_output<4> out;
ymfm::ymf262::fm_engine* fme=fm_ymfm3->debug_fm_engine();
ymfm::fm_channel<ymfm::opl_registers_base<3>>* fmChan[18];
for (int i=0; i<18; i++) {
fmChan[i]=fme->debug_channel(i);
}
for (size_t h=0; h<len; h++) {
if (!writes.empty() && --delay<0) {
delay=1;
QueuedWrite& w=writes.front();
fm_ymfm3->write((w.addr&0x100)?2:0,w.addr);
fm_ymfm3->write(1,w.val);
regPool[w.addr&511]=w.val;
writes.pop();
}
fm_ymfm3->generate(&out,1);
buf[0][h]=out.data[0]>>1;
if (totalOutputs>1) {
buf[1][h]=out.data[1]>>1;
}
if (totalOutputs>2) {
buf[2][h]=out.data[2]>>1;
}
if (totalOutputs>3) {
buf[3][h]=out.data[3]>>1;
}
if (totalOutputs==6) {
// placeholder for OPL4
buf[4][h]=0;
buf[5][h]=0;
}
if (properDrums) {
for (int i=0; i<16; i++) {
unsigned char ch=(i<12 && chan[i&(~1)].fourOp)?outChanMap[i^1]:outChanMap[i];
if (ch==255) continue;
int chOut=fmChan[ch]->debug_output(0);
if (chOut==0) {
chOut=fmChan[ch]->debug_output(1);
}
if (chOut==0) {
chOut=fmChan[ch]->debug_output(2);
}
if (chOut==0) {
chOut=fmChan[ch]->debug_output(3);
}
if (i==15) {
oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP(chOut,-32768,32767);
} else {
oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP(chOut<<1,-32768,32767);
}
}
oscBuf[16]->data[oscBuf[16]->needle++]=CLAMP(fmChan[7]->debug_special2()<<1,-32768,32767);
oscBuf[17]->data[oscBuf[17]->needle++]=CLAMP(fmChan[8]->debug_special1()<<1,-32768,32767);
oscBuf[18]->data[oscBuf[18]->needle++]=CLAMP(fmChan[8]->debug_special2()<<1,-32768,32767);
oscBuf[19]->data[oscBuf[19]->needle++]=CLAMP(fmChan[7]->debug_special1()<<1,-32768,32767);
} else {
for (int i=0; i<18; i++) {
unsigned char ch=outChanMap[i];
if (ch==255) continue;
int chOut=fmChan[ch]->debug_output(0);
if (chOut==0) {
chOut=fmChan[ch]->debug_output(1);
}
if (chOut==0) {
chOut=fmChan[ch]->debug_output(2);
}
if (chOut==0) {
chOut=fmChan[ch]->debug_output(3);
}
oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP(chOut<<1,-32768,32767);
}
}
}
}
static const int cycleMap[18]={
6, 7, 8, 6, 7, 8, 0, 1, 2,
0, 1, 2, 3, 4, 5, 3, 4, 5,
};
static const int cycleMapDrums[18]={
6, 10, 8, 6, 7, 9, 0, 1, 2,
0, 1, 2, 3, 4, 5, 3, 4, 5,
};
void DivPlatformOPL::acquire_nukedLLE2(short** buf, size_t len) {
int chOut[11];
thread_local ymfm::ymfm_output<2> aOut;
for (size_t h=0; h<len; h++) {
int curCycle=0;
unsigned char subCycle=0;
for (int i=0; i<11; i++) {
chOut[i]=0;
}
while (true) {
lastSH=fm_lle2.o_sh;
lastSY=fm_lle2.o_sy;
// register control
if (waitingBusy) {
fm_lle2.input.cs=0;
fm_lle2.input.rd=0;
fm_lle2.input.wr=1;
fm_lle2.input.address=0;
} else {
if (!writes.empty()) {
QueuedWrite& w=writes.front();
if (w.addrOrVal) {
regPool[w.addr&511]=w.val;
fm_lle2.input.cs=0;
fm_lle2.input.rd=1;
fm_lle2.input.wr=0;
fm_lle2.input.address=1;
fm_lle2.input.data_i=w.val;
writes.pop();
delay=84;
} else {
if (chipType==8950) {
switch (w.addr) {
case 8:
adpcmB->write(w.addr-7,(w.val&15)|0x80);
fm_lle2.input.cs=0;
fm_lle2.input.rd=1;
fm_lle2.input.wr=0;
fm_lle2.input.address=0;
fm_lle2.input.data_i=w.addr;
w.addrOrVal=true;
// weird. wasn't it 12?
delay=24;
break;
case 7: case 9: case 10: case 11: case 12: case 13: case 14: case 15: case 16: case 17: case 18: case 21: case 22: case 23:
adpcmB->write(w.addr-7,w.val);
regPool[w.addr&511]=w.val;
writes.pop();
delay=108;
break;
default:
fm_lle2.input.cs=0;
fm_lle2.input.rd=1;
fm_lle2.input.wr=0;
fm_lle2.input.address=0;
fm_lle2.input.data_i=w.addr;
w.addrOrVal=true;
// weird. wasn't it 12?
delay=24;
break;
}
} else {
fm_lle2.input.cs=0;
fm_lle2.input.rd=1;
fm_lle2.input.wr=0;
fm_lle2.input.address=0;
fm_lle2.input.data_i=w.addr;
w.addrOrVal=true;
// weird. wasn't it 12?
delay=24;
}
}
waitingBusy=true;
}
}
fm_lle2.input.mclk=1;
FMOPL2_Clock(&fm_lle2);
fm_lle2.input.mclk=0;
FMOPL2_Clock(&fm_lle2);
if (waitingBusy) {
if (--delay<0) waitingBusy=false;
}
if (!(++subCycle&3)) {
if (properDrums) {
chOut[cycleMapDrums[curCycle]]+=fm_lle2.op_value_debug;
} else {
chOut[cycleMap[curCycle]]+=fm_lle2.op_value_debug;
}
curCycle++;
}
if (fm_lle2.o_sy && !lastSY) {
dacVal>>=1;
dacVal|=(fm_lle2.o_mo&1)<<17;
}
if (!fm_lle2.o_sh && lastSH) {
int e=(dacVal>>15)&7;
int m=(dacVal>>5)&1023;
m-=512;
dacOut=(m<<e)>>1;
break;
}
}
for (int i=0; i<11; i++) {
if (i>=6 && properDrums) {
chOut[i]<<=1;
} else {
chOut[i]<<=2;
}
if (chOut[i]<-32768) chOut[i]=-32768;
if (chOut[i]>32767) chOut[i]=32767;
oscBuf[i]->data[oscBuf[i]->needle++]=chOut[i];
}
if (chipType==8950) {
adpcmB->clock();
aOut.clear();
adpcmB->output<2>(aOut,0);
if (!isMuted[adpcmChan]) {
dacOut-=aOut.data[0]>>3;
oscBuf[adpcmChan]->data[oscBuf[adpcmChan]->needle++]=aOut.data[0]>>1;
} else {
oscBuf[adpcmChan]->data[oscBuf[adpcmChan]->needle++]=0;
}
}
if (dacOut<-32768) dacOut=-32768;
if (dacOut>32767) dacOut=32767;
buf[0][h]=dacOut;
}
}
void DivPlatformOPL::acquire_nukedLLE3(short** buf, size_t len) {
int chOut[20];
for (size_t h=0; h<len; h++) {
//int curCycle=0;
//unsigned char subCycle=0;
for (int i=0; i<20; i++) {
chOut[i]=0;
}
while (true) {
lastSH=fm_lle3.o_smpac;
lastSH2=fm_lle3.o_smpbd;
lastSY=fm_lle3.o_sy;
// register control
if (waitingBusy) {
if (delay<15) {
fm_lle3.input.cs=0;
fm_lle3.input.rd=0;
fm_lle3.input.wr=1;
fm_lle3.input.address=0;
}
} else {
if (!writes.empty()) {
QueuedWrite& w=writes.front();
if (w.addrOrVal) {
regPool[w.addr&511]=w.val;
fm_lle3.input.cs=0;
fm_lle3.input.rd=1;
fm_lle3.input.wr=0;
fm_lle3.input.address=(w.addr&0x100)?3:1;
fm_lle3.input.data_i=w.val;
writes.pop();
delay=16;
} else {
fm_lle3.input.cs=0;
fm_lle3.input.rd=1;
fm_lle3.input.wr=0;
fm_lle3.input.address=(w.addr&0x100)?2:0;
fm_lle3.input.data_i=w.addr&0xff;
w.addrOrVal=true;
// weird. wasn't it 12?
delay=16;
}
waitingBusy=true;
}
}
fm_lle3.input.mclk=1;
FMOPL3_Clock(&fm_lle3);
fm_lle3.input.mclk=0;
FMOPL3_Clock(&fm_lle3);
if (waitingBusy) {
if (--delay<0) waitingBusy=false;
}
/*if (!(++subCycle&3)) {
// TODO: chan osc
curCycle++;
}*/
if (fm_lle3.o_sy && !lastSY) {
dacVal>>=1;
dacVal|=(fm_lle3.o_doab&1)<<17;
dacVal2>>=1;
dacVal2|=(fm_lle3.o_docd&1)<<17;
}
if (!fm_lle3.o_smpbd && lastSH2) {
dacOut3[0]=((dacVal>>1)&0xffff)-0x8000;
dacOut3[2]=((dacVal2>>1)&0xffff)-0x8000;
}
if (!fm_lle3.o_smpac && lastSH) {
dacOut3[1]=((dacVal>>1)&0xffff)-0x8000;
dacOut3[3]=((dacVal2>>1)&0xffff)-0x8000;
break;
}
}
for (int i=0; i<20; i++) {
if (i>=15 && properDrums) {
chOut[i]<<=1;
} else {
chOut[i]<<=2;
}
if (chOut[i]<-32768) chOut[i]=-32768;
if (chOut[i]>32767) chOut[i]=32767;
oscBuf[i]->data[oscBuf[i]->needle++]=chOut[i];
}
for (int i=0; i<MIN(4,totalOutputs); i++) {
if (dacOut3[i]<-32768) dacOut3[i]=-32768;
if (dacOut3[i]>32767) dacOut3[i]=32767;
buf[i][h]=dacOut3[i];
}
}
}
void DivPlatformOPL::acquire(short** buf, size_t len) {
//if (useYMFM) {
// acquire_ymfm(buf,len);
//} else {
if (emuCore==2) { // LLE
switch (chipType) {
case 1: case 2: case 8950:
acquire_nukedLLE2(buf,len);
break;
case 3: case 759:
acquire_nukedLLE3(buf,len);
break;
}
} else if (emuCore==1) { // ymfm
switch (chipType) {
case 1:
acquire_ymfm1(buf,len);
break;
case 2:
acquire_ymfm2(buf,len);
break;
case 8950:
acquire_ymfm8950(buf,len);
break;
case 3: case 759:
acquire_ymfm3(buf,len);
break;
}
} else { // OPL3
acquire_nuked(buf,len);
//}
}
}
double DivPlatformOPL::NOTE_ADPCMB(int note) {
@ -1620,17 +2115,68 @@ int DivPlatformOPL::getRegisterPoolSize() {
void DivPlatformOPL::reset() {
while (!writes.empty()) writes.pop();
memset(regPool,0,512);
/*
if (useYMFM) {
fm_ymfm->reset();
}
*/
if (downsample) {
const unsigned int downsampledRate=(unsigned int)((double)rate*round(COLOR_NTSC/72.0)/(double)chipRateBase);
OPL3_Reset(&fm,downsampledRate);
dacVal=0;
dacVal2=0;
dacOut=0;
dacOut3[0]=0;
dacOut3[1]=0;
dacOut3[2]=0;
dacOut3[3]=0;
lastSH=false;
lastSH2=false;
lastSY=false;
waitingBusy=true;
const unsigned int downsampledRate=(unsigned int)((double)rate*round(COLOR_NTSC/72.0)/(double)chipRateBase);
if (emuCore==2) {
if (chipType==3 || chipType==759 || chipType==4) {
// reset 3
memset(&fm_lle3,0,sizeof(fmopl3_t));
fm_lle3.input.ic=0;
for (int i=0; i<400; i++) {
fm_lle3.input.mclk=1;
FMOPL3_Clock(&fm_lle3);
fm_lle3.input.mclk=0;
FMOPL3_Clock(&fm_lle3);
}
fm_lle3.input.ic=1;
} else {
// reset 2
memset(&fm_lle2,0,sizeof(fmopl2_t));
fm_lle2.input.ic=0;
for (int i=0; i<80; i++) {
fm_lle2.input.mclk=1;
FMOPL2_Clock(&fm_lle2);
fm_lle2.input.mclk=0;
FMOPL2_Clock(&fm_lle2);
}
fm_lle2.input.ic=1;
}
} else if (emuCore==1) {
switch (chipType) {
case 1:
fm_ymfm1->reset();
break;
case 2:
fm_ymfm2->reset();
break;
case 8950:
fm_ymfm8950->reset();
break;
case 3: case 759:
fm_ymfm3->reset();
break;
}
} else {
OPL3_Reset(&fm,rate);
if (downsample) {
OPL3_Reset(&fm,downsampledRate);
} else {
OPL3_Reset(&fm,rate);
}
}
if (dumpWrites) {
addWrite(0xffffffff,0);
}
@ -1744,8 +2290,8 @@ int DivPlatformOPL::getPortaFloor(int ch) {
return (ch>5)?12:0;
}
void DivPlatformOPL::setYMFM(bool use) {
useYMFM=use;
void DivPlatformOPL::setCore(unsigned char which) {
emuCore=which;
}
void DivPlatformOPL::setOPLType(int type, bool drums) {
@ -1891,11 +2437,13 @@ void DivPlatformOPL::setFlags(const DivConfig& flags) {
totalOutputs=4;
break;
}
if (downsample) {
const unsigned int downsampledRate=(unsigned int)((double)rate*round(COLOR_NTSC/72.0)/(double)chipRateBase);
OPL3_Resample(&fm,downsampledRate);
} else {
OPL3_Resample(&fm,rate);
if (emuCore!=1 && emuCore!=2) {
if (downsample) {
const unsigned int downsampledRate=(unsigned int)((double)rate*round(COLOR_NTSC/72.0)/(double)chipRateBase);
OPL3_Resample(&fm,downsampledRate);
} else {
OPL3_Resample(&fm,rate);
}
}
break;
case 4:
@ -1990,6 +2538,29 @@ int DivPlatformOPL::init(DivEngine* p, int channels, int sugRate, const DivConfi
for (int i=0; i<20; i++) {
oscBuf[i]=new DivDispatchOscBuffer;
}
fm_ymfm1=NULL;
fm_ymfm2=NULL;
fm_ymfm8950=NULL;
fm_ymfm3=NULL;
if (emuCore==1) {
switch (chipType) {
case 1:
fm_ymfm1=new ymfm::ym3526(iface);
break;
case 2:
fm_ymfm2=new ymfm::ym3812(iface);
break;
case 8950:
fm_ymfm8950=new ymfm::y8950(iface);
break;
case 3: case 759:
fm_ymfm3=new ymfm::ymf262(iface);
break;
}
}
setFlags(flags);
if (adpcmChan>=0) {
@ -2012,6 +2583,22 @@ void DivPlatformOPL::quit() {
delete adpcmB;
delete[] adpcmBMem;
}
if (fm_ymfm1!=NULL) {
delete fm_ymfm1;
fm_ymfm1=NULL;
}
if (fm_ymfm2!=NULL) {
delete fm_ymfm2;
fm_ymfm2=NULL;
}
if (fm_ymfm8950!=NULL) {
delete fm_ymfm8950;
fm_ymfm8950=NULL;
}
if (fm_ymfm3!=NULL) {
delete fm_ymfm3;
fm_ymfm3=NULL;
}
}
DivPlatformOPL::~DivPlatformOPL() {

View file

@ -23,7 +23,12 @@
#include "../dispatch.h"
#include "../../fixedQueue.h"
#include "../../../extern/opl/opl3.h"
extern "C" {
#include "../../../extern/YM3812-LLE/fmopl2.h"
#include "../../../extern/YMF262-LLE/fmopl3.h"
}
#include "sound/ymfm/ymfm_adpcm.h"
#include "sound/ymfm/ymfm_opl.h"
class DivOPLAInterface: public ymfm::ymfm_interface {
public:
@ -68,7 +73,16 @@ class DivPlatformOPL: public DivDispatch {
QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {}
};
FixedQueue<QueuedWrite,2048> writes;
opl3_chip fm;
unsigned int dacVal;
unsigned int dacVal2;
int dacOut;
int dacOut3[4];
bool lastSH;
bool lastSH2;
bool lastSY;
bool waitingBusy;
unsigned char* adpcmBMem;
size_t adpcmBMemLen;
DivOPLAInterface iface;
@ -93,11 +107,25 @@ class DivPlatformOPL: public DivDispatch {
unsigned char lfoValue;
bool useYMFM, update4OpMask, pretendYMU, downsample, compatPan;
// 0: Nuked-OPL3
// 1: ymfm
// 2: YM3812-LLE/YMF262-LLE
unsigned char emuCore;
bool update4OpMask, pretendYMU, downsample, compatPan;
short oldWrites[512];
short pendingWrites[512];
// chips
opl3_chip fm;
ymfm::ym3526* fm_ymfm1;
ymfm::ym3812* fm_ymfm2;
ymfm::y8950* fm_ymfm8950;
ymfm::ymf262* fm_ymfm3;
fmopl2_t fm_lle2;
fmopl3_t fm_lle3;
int octave(int freq);
int toFreq(int freq);
double NOTE_ADPCMB(int note);
@ -106,8 +134,13 @@ class DivPlatformOPL: public DivDispatch {
friend void putDispatchChip(void*,int);
friend void putDispatchChan(void*,int,int);
void acquire_nukedLLE2(short** buf, size_t len);
void acquire_nukedLLE3(short** buf, size_t len);
void acquire_nuked(short** buf, size_t len);
//void acquire_ymfm(short** buf, size_t len);
void acquire_ymfm3(short** buf, size_t len);
void acquire_ymfm8950(short** buf, size_t len);
void acquire_ymfm2(short** buf, size_t len);
void acquire_ymfm1(short** buf, size_t len);
public:
void acquire(short** buf, size_t len);
@ -124,7 +157,7 @@ class DivPlatformOPL: public DivDispatch {
void tick(bool sysTick=true);
void muteChannel(int ch, bool mute);
int getOutputCount();
void setYMFM(bool use);
void setCore(unsigned char which);
void setOPLType(int type, bool drums);
bool keyOffAffectsArp(int ch);
bool keyOffAffectsPorta(int ch);

View file

@ -67,7 +67,7 @@ void DivPlatformPCMDAC::acquire(short** buf, size_t len) {
switch (interp) {
case 1: // linear
output=s6+((s7-s6)*(chan[0].audSub&0xffff)>>16);
output=s6+(((int)((int)s7-(int)s6)*((chan[0].audSub>>1)&0x7fff))>>15);
break;
case 2: { // cubic
float* cubicTable=DivFilterTables::getCubicTable();
@ -188,7 +188,7 @@ void DivPlatformPCMDAC::acquire(short** buf, size_t len) {
switch (interp) {
case 1: // linear
output=s6+((s7-s6)*(chan[0].audSub&0xffff)>>16);
output=s6+(((int)((int)s7-(int)s6)*((chan[0].audSub>>1)&0x7fff))>>15);
break;
case 2: { // cubic
float* cubicTable=DivFilterTables::getCubicTable();

View file

@ -305,6 +305,8 @@ public:
// simple getters for debugging
fm_operator<RegisterType> *debug_operator(uint32_t index) const { return m_op[index]; }
int32_t debug_output(uint32_t index) const { return m_output[index]; }
int32_t debug_special1() const { return m_special1; }
int32_t debug_special2() const { return m_special2; }
private:
// helper to add values to the outputs based on channel enables
@ -343,6 +345,8 @@ private:
RegisterType &m_regs; // direct reference to registers
fm_engine_base<RegisterType> &m_owner; // reference to the owning engine
mutable int32_t m_output[4];
mutable int32_t m_special1;
mutable int32_t m_special2;
};

View file

@ -808,7 +808,9 @@ fm_channel<RegisterType>::fm_channel(fm_engine_base<RegisterType> &owner, uint32
m_op{ nullptr, nullptr, nullptr, nullptr },
m_regs(owner.regs()),
m_owner(owner),
m_output{ 0, 0, 0, 0 }
m_output{ 0, 0, 0, 0 },
m_special1(0),
m_special2(0)
{
}
@ -1139,13 +1141,13 @@ void fm_channel<RegisterType>::output_rhythm_ch7(uint32_t phase_select, output_d
// and a combination of noise and the operator 13/17 phase select
// to compute the phase
uint32_t phase = (phase_select << 9) | (0xd0 >> (2 * (noise_state ^ phase_select)));
int32_t result = m_op[0]->compute_volume(phase, am_offset) >> rshift;
int32_t result = m_special1 = m_op[0]->compute_volume(phase, am_offset) >> rshift;
// Snare Drum: this uses the envelope from operator 16 (channel 7),
// and a combination of noise and operator 13 phase to pick a phase
uint32_t op13phase = m_op[0]->phase();
phase = (0x100 << bitfield(op13phase, 8)) ^ (noise_state << 8);
result += m_op[1]->compute_volume(phase, am_offset) >> rshift;
result += m_special2 = m_op[1]->compute_volume(phase, am_offset) >> rshift;
result = clamp(result, -clipmax - 1, clipmax);
// add to the output
@ -1166,12 +1168,12 @@ void fm_channel<RegisterType>::output_rhythm_ch8(uint32_t phase_select, output_d
uint32_t am_offset = m_regs.lfo_am_offset(m_choffs);
// Tom Tom: this is just a single operator processed normally
int32_t result = m_op[0]->compute_volume(m_op[0]->phase(), am_offset) >> rshift;
int32_t result = m_special1 = m_op[0]->compute_volume(m_op[0]->phase(), am_offset) >> rshift;
// Top Cymbal: this uses the envelope from operator 17 (channel 8),
// and the operator 13/17 phase select to compute the phase
uint32_t phase = 0x100 | (phase_select << 9);
result += m_op[1]->compute_volume(phase, am_offset) >> rshift;
result += m_special2 = m_op[1]->compute_volume(phase, am_offset) >> rshift;
result = clamp(result, -clipmax - 1, clipmax);
// add to the output

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,911 @@
// BSD 3-Clause License
//
// Copyright (c) 2021, Aaron Giles
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef YMFM_OPL_H
#define YMFM_OPL_H
#pragma once
#include "ymfm.h"
#include "ymfm_adpcm.h"
#include "ymfm_fm.h"
#include "ymfm_pcm.h"
namespace ymfm
{
//*********************************************************
// REGISTER CLASSES
//*********************************************************
// ======================> opl_registers_base
//
// OPL/OPL2/OPL3/OPL4 register map:
//
// System-wide registers:
// 01 xxxxxxxx Test register
// --x----- Enable OPL compatibility mode [OPL2 only] (1 = enable)
// 02 xxxxxxxx Timer A value (4 * OPN)
// 03 xxxxxxxx Timer B value
// 04 x------- RST
// -x------ Mask timer A
// --x----- Mask timer B
// ------x- Load timer B
// -------x Load timer A
// 08 x------- CSM mode [OPL/OPL2 only]
// -x------ Note select
// BD x------- AM depth
// -x------ PM depth
// --x----- Rhythm enable
// ---x---- Bass drum key on
// ----x--- Snare drum key on
// -----x-- Tom key on
// ------x- Top cymbal key on
// -------x High hat key on
// 101 --xxxxxx Test register 2 [OPL3 only]
// 104 --x----- Channel 6 4-operator mode [OPL3 only]
// ---x---- Channel 5 4-operator mode [OPL3 only]
// ----x--- Channel 4 4-operator mode [OPL3 only]
// -----x-- Channel 3 4-operator mode [OPL3 only]
// ------x- Channel 2 4-operator mode [OPL3 only]
// -------x Channel 1 4-operator mode [OPL3 only]
// 105 -------x New [OPL3 only]
// ------x- New2 [OPL4 only]
//
// Per-channel registers (channel in address bits 0-3)
// Note that all these apply to address+100 as well on OPL3+
// A0-A8 xxxxxxxx F-number (low 8 bits)
// B0-B8 --x----- Key on
// ---xxx-- Block (octvate, 0-7)
// ------xx F-number (high two bits)
// C0-C8 x------- CHD output (to DO0 pin) [OPL3+ only]
// -x------ CHC output (to DO0 pin) [OPL3+ only]
// --x----- CHB output (mixed right, to DO2 pin) [OPL3+ only]
// ---x---- CHA output (mixed left, to DO2 pin) [OPL3+ only]
// ----xxx- Feedback level for operator 1 (0-7)
// -------x Operator connection algorithm
//
// Per-operator registers (operator in bits 0-5)
// Note that all these apply to address+100 as well on OPL3+
// 20-35 x------- AM enable
// -x------ PM enable (VIB)
// --x----- EG type
// ---x---- Key scale rate
// ----xxxx Multiple value (0-15)
// 40-55 xx------ Key scale level (0-3)
// --xxxxxx Total level (0-63)
// 60-75 xxxx---- Attack rate (0-15)
// ----xxxx Decay rate (0-15)
// 80-95 xxxx---- Sustain level (0-15)
// ----xxxx Release rate (0-15)
// E0-F5 ------xx Wave select (0-3) [OPL2 only]
// -----xxx Wave select (0-7) [OPL3+ only]
//
template<int Revision>
class opl_registers_base : public fm_registers_base
{
static constexpr bool IsOpl2 = (Revision == 2);
static constexpr bool IsOpl2Plus = (Revision >= 2);
static constexpr bool IsOpl3Plus = (Revision >= 3);
static constexpr bool IsOpl4Plus = (Revision >= 4);
public:
// constants
static constexpr uint32_t OUTPUTS = IsOpl3Plus ? 4 : 1;
static constexpr uint32_t CHANNELS = IsOpl3Plus ? 18 : 9;
static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1;
static constexpr uint32_t OPERATORS = CHANNELS * 2;
static constexpr uint32_t WAVEFORMS = IsOpl3Plus ? 8 : (IsOpl2Plus ? 4 : 1);
static constexpr uint32_t REGISTERS = IsOpl3Plus ? 0x200 : 0x100;
static constexpr uint32_t REG_MODE = 0x04;
static constexpr uint32_t DEFAULT_PRESCALE = IsOpl4Plus ? 19 : (IsOpl3Plus ? 8 : 4);
static constexpr uint32_t EG_CLOCK_DIVIDER = 1;
static constexpr uint32_t CSM_TRIGGER_MASK = ALL_CHANNELS;
static constexpr bool DYNAMIC_OPS = IsOpl3Plus;
static constexpr bool MODULATOR_DELAY = !IsOpl3Plus;
static constexpr uint8_t STATUS_TIMERA = 0x40;
static constexpr uint8_t STATUS_TIMERB = 0x20;
static constexpr uint8_t STATUS_BUSY = 0;
static constexpr uint8_t STATUS_IRQ = 0x80;
// constructor
opl_registers_base();
// reset to initial state
void reset();
// save/restore
void save_restore(ymfm_saved_state &state);
// map channel number to register offset
static constexpr uint32_t channel_offset(uint32_t chnum)
{
assert(chnum < CHANNELS);
if (!IsOpl3Plus)
return chnum;
else
return (chnum % 9) + 0x100 * (chnum / 9);
}
// map operator number to register offset
static constexpr uint32_t operator_offset(uint32_t opnum)
{
assert(opnum < OPERATORS);
if (!IsOpl3Plus)
return opnum + 2 * (opnum / 6);
else
return (opnum % 18) + 2 * ((opnum % 18) / 6) + 0x100 * (opnum / 18);
}
// return an array of operator indices for each channel
struct operator_mapping { uint32_t chan[CHANNELS]; };
void operator_map(operator_mapping &dest) const;
// OPL4 apparently can read back FM registers?
uint8_t read(uint16_t index) const { return m_regdata[index]; }
// handle writes to the register array
bool write(uint16_t index, uint8_t data, uint32_t &chan, uint32_t &opmask);
// clock the noise and LFO, if present, returning LFO PM value
int32_t clock_noise_and_lfo();
// reset the LFO
void reset_lfo() { m_lfo_am_counter = m_lfo_pm_counter = 0; }
// return the AM offset from LFO for the given channel
// on OPL this is just a fixed value
uint32_t lfo_am_offset(uint32_t choffs) const { return m_lfo_am; }
// return LFO/noise states
uint32_t noise_state() const { return m_noise_lfsr >> 23; }
// caching helpers
void cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache);
// compute the phase step, given a PM value
uint32_t compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm);
// log a key-on event
std::string log_keyon(uint32_t choffs, uint32_t opoffs);
// system-wide registers
uint32_t test() const { return byte(0x01, 0, 8); }
uint32_t waveform_enable() const { return IsOpl2 ? byte(0x01, 5, 1) : (IsOpl3Plus ? 1 : 0); }
uint32_t timer_a_value() const { return byte(0x02, 0, 8) * 4; } // 8->10 bits
uint32_t timer_b_value() const { return byte(0x03, 0, 8); }
uint32_t status_mask() const { return byte(0x04, 0, 8) & 0x78; }
uint32_t irq_reset() const { return byte(0x04, 7, 1); }
uint32_t reset_timer_b() const { return byte(0x04, 7, 1) | byte(0x04, 5, 1); }
uint32_t reset_timer_a() const { return byte(0x04, 7, 1) | byte(0x04, 6, 1); }
uint32_t enable_timer_b() const { return 1; }
uint32_t enable_timer_a() const { return 1; }
uint32_t load_timer_b() const { return byte(0x04, 1, 1); }
uint32_t load_timer_a() const { return byte(0x04, 0, 1); }
uint32_t csm() const { return IsOpl3Plus ? 0 : byte(0x08, 7, 1); }
uint32_t note_select() const { return byte(0x08, 6, 1); }
uint32_t lfo_am_depth() const { return byte(0xbd, 7, 1); }
uint32_t lfo_pm_depth() const { return byte(0xbd, 6, 1); }
uint32_t rhythm_enable() const { return byte(0xbd, 5, 1); }
uint32_t rhythm_keyon() const { return byte(0xbd, 4, 0); }
uint32_t newflag() const { return IsOpl3Plus ? byte(0x105, 0, 1) : 0; }
uint32_t new2flag() const { return IsOpl4Plus ? byte(0x105, 1, 1) : 0; }
uint32_t fourop_enable() const { return IsOpl3Plus ? byte(0x104, 0, 6) : 0; }
// per-channel registers
uint32_t ch_block_freq(uint32_t choffs) const { return word(0xb0, 0, 5, 0xa0, 0, 8, choffs); }
uint32_t ch_feedback(uint32_t choffs) const { return byte(0xc0, 1, 3, choffs); }
uint32_t ch_algorithm(uint32_t choffs) const { return byte(0xc0, 0, 1, choffs) | (IsOpl3Plus ? (8 | (byte(0xc3, 0, 1, choffs) << 1)) : 0); }
uint32_t ch_output_any(uint32_t choffs) const { return newflag() ? byte(0xc0 + choffs, 4, 4) : 1; }
uint32_t ch_output_0(uint32_t choffs) const { return newflag() ? byte(0xc0 + choffs, 4, 1) : 1; }
uint32_t ch_output_1(uint32_t choffs) const { return newflag() ? byte(0xc0 + choffs, 5, 1) : (IsOpl3Plus ? 1 : 0); }
uint32_t ch_output_2(uint32_t choffs) const { return newflag() ? byte(0xc0 + choffs, 6, 1) : 0; }
uint32_t ch_output_3(uint32_t choffs) const { return newflag() ? byte(0xc0 + choffs, 7, 1) : 0; }
// per-operator registers
uint32_t op_lfo_am_enable(uint32_t opoffs) const { return byte(0x20, 7, 1, opoffs); }
uint32_t op_lfo_pm_enable(uint32_t opoffs) const { return byte(0x20, 6, 1, opoffs); }
uint32_t op_eg_sustain(uint32_t opoffs) const { return byte(0x20, 5, 1, opoffs); }
uint32_t op_ksr(uint32_t opoffs) const { return byte(0x20, 4, 1, opoffs); }
uint32_t op_multiple(uint32_t opoffs) const { return byte(0x20, 0, 4, opoffs); }
uint32_t op_ksl(uint32_t opoffs) const { uint32_t temp = byte(0x40, 6, 2, opoffs); return bitfield(temp, 1) | (bitfield(temp, 0) << 1); }
uint32_t op_total_level(uint32_t opoffs) const { return byte(0x40, 0, 6, opoffs); }
uint32_t op_attack_rate(uint32_t opoffs) const { return byte(0x60, 4, 4, opoffs); }
uint32_t op_decay_rate(uint32_t opoffs) const { return byte(0x60, 0, 4, opoffs); }
uint32_t op_sustain_level(uint32_t opoffs) const { return byte(0x80, 4, 4, opoffs); }
uint32_t op_release_rate(uint32_t opoffs) const { return byte(0x80, 0, 4, opoffs); }
uint32_t op_waveform(uint32_t opoffs) const { return IsOpl2Plus ? byte(0xe0, 0, newflag() ? 3 : 2, opoffs) : 0; }
protected:
// return a bitfield extracted from a byte
uint32_t byte(uint32_t offset, uint32_t start, uint32_t count, uint32_t extra_offset = 0) const
{
return bitfield(m_regdata[offset + extra_offset], start, count);
}
// return a bitfield extracted from a pair of bytes, MSBs listed first
uint32_t word(uint32_t offset1, uint32_t start1, uint32_t count1, uint32_t offset2, uint32_t start2, uint32_t count2, uint32_t extra_offset = 0) const
{
return (byte(offset1, start1, count1, extra_offset) << count2) | byte(offset2, start2, count2, extra_offset);
}
// helper to determine if the this channel is an active rhythm channel
bool is_rhythm(uint32_t choffs) const
{
return rhythm_enable() && (choffs >= 6 && choffs <= 8);
}
// internal state
uint16_t m_lfo_am_counter; // LFO AM counter
uint16_t m_lfo_pm_counter; // LFO PM counter
uint32_t m_noise_lfsr; // noise LFSR state
uint8_t m_lfo_am; // current LFO AM value
uint8_t m_regdata[REGISTERS]; // register data
uint16_t m_waveform[WAVEFORMS][WAVEFORM_LENGTH]; // waveforms
};
using opl_registers = opl_registers_base<1>;
using opl2_registers = opl_registers_base<2>;
using opl3_registers = opl_registers_base<3>;
using opl4_registers = opl_registers_base<4>;
// ======================> opll_registers
//
// OPLL register map:
//
// System-wide registers:
// 0E --x----- Rhythm enable
// ---x---- Bass drum key on
// ----x--- Snare drum key on
// -----x-- Tom key on
// ------x- Top cymbal key on
// -------x High hat key on
// 0F xxxxxxxx Test register
//
// Per-channel registers (channel in address bits 0-3)
// 10-18 xxxxxxxx F-number (low 8 bits)
// 20-28 --x----- Sustain on
// ---x---- Key on
// --- xxx- Block (octvate, 0-7)
// -------x F-number (high bit)
// 30-38 xxxx---- Instrument selection
// ----xxxx Volume
//
// User instrument registers (for carrier, modulator operators)
// 00-01 x------- AM enable
// -x------ PM enable (VIB)
// --x----- EG type
// ---x---- Key scale rate
// ----xxxx Multiple value (0-15)
// 02 xx------ Key scale level (carrier, 0-3)
// --xxxxxx Total level (modulator, 0-63)
// 03 xx------ Key scale level (modulator, 0-3)
// ---x---- Rectified wave (carrier)
// ----x--- Rectified wave (modulator)
// -----xxx Feedback level for operator 1 (0-7)
// 04-05 xxxx---- Attack rate (0-15)
// ----xxxx Decay rate (0-15)
// 06-07 xxxx---- Sustain level (0-15)
// ----xxxx Release rate (0-15)
//
// Internal (fake) registers:
// 40-48 xxxxxxxx Current instrument base address
// 4E-5F xxxxxxxx Current instrument base address + operator slot (0/1)
// 70-FF xxxxxxxx Data for instruments (1-16 plus 3 drums)
//
class opll_registers : public fm_registers_base
{
public:
static constexpr uint32_t OUTPUTS = 2;
static constexpr uint32_t CHANNELS = 9;
static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1;
static constexpr uint32_t OPERATORS = CHANNELS * 2;
static constexpr uint32_t WAVEFORMS = 2;
static constexpr uint32_t REGISTERS = 0x40;
static constexpr uint32_t REG_MODE = 0x3f;
static constexpr uint32_t DEFAULT_PRESCALE = 4;
static constexpr uint32_t EG_CLOCK_DIVIDER = 1;
static constexpr uint32_t CSM_TRIGGER_MASK = 0;
static constexpr bool EG_HAS_DEPRESS = true;
static constexpr bool MODULATOR_DELAY = true;
static constexpr uint8_t STATUS_TIMERA = 0;
static constexpr uint8_t STATUS_TIMERB = 0;
static constexpr uint8_t STATUS_BUSY = 0;
static constexpr uint8_t STATUS_IRQ = 0;
// OPLL-specific constants
static constexpr uint32_t INSTDATA_SIZE = 0x90;
// constructor
opll_registers();
// reset to initial state
void reset();
// save/restore
void save_restore(ymfm_saved_state &state);
// map channel number to register offset
static constexpr uint32_t channel_offset(uint32_t chnum)
{
assert(chnum < CHANNELS);
return chnum;
}
// map operator number to register offset
static constexpr uint32_t operator_offset(uint32_t opnum)
{
assert(opnum < OPERATORS);
return opnum;
}
// return an array of operator indices for each channel
struct operator_mapping { uint32_t chan[CHANNELS]; };
void operator_map(operator_mapping &dest) const;
// read a register value
uint8_t read(uint16_t index) const { return m_regdata[index]; }
// handle writes to the register array
bool write(uint16_t index, uint8_t data, uint32_t &chan, uint32_t &opmask);
// clock the noise and LFO, if present, returning LFO PM value
int32_t clock_noise_and_lfo();
// reset the LFO
void reset_lfo() { m_lfo_am_counter = m_lfo_pm_counter = 0; }
// return the AM offset from LFO for the given channel
// on OPL this is just a fixed value
uint32_t lfo_am_offset(uint32_t choffs) const { return m_lfo_am; }
// return LFO/noise states
uint32_t noise_state() const { return m_noise_lfsr >> 23; }
// caching helpers
void cache_operator_data(uint32_t choffs, uint32_t opoffs, opdata_cache &cache);
// compute the phase step, given a PM value
uint32_t compute_phase_step(uint32_t choffs, uint32_t opoffs, opdata_cache const &cache, int32_t lfo_raw_pm);
// log a key-on event
std::string log_keyon(uint32_t choffs, uint32_t opoffs);
// set the instrument data
void set_instrument_data(uint8_t const *data)
{
std::copy_n(data, INSTDATA_SIZE, &m_instdata[0]);
}
// system-wide registers
uint32_t rhythm_enable() const { return byte(0x0e, 5, 1); }
uint32_t rhythm_keyon() const { return byte(0x0e, 4, 0); }
uint32_t test() const { return byte(0x0f, 0, 8); }
uint32_t waveform_enable() const { return 1; }
uint32_t timer_a_value() const { return 0; }
uint32_t timer_b_value() const { return 0; }
uint32_t status_mask() const { return 0; }
uint32_t irq_reset() const { return 0; }
uint32_t reset_timer_b() const { return 0; }
uint32_t reset_timer_a() const { return 0; }
uint32_t enable_timer_b() const { return 0; }
uint32_t enable_timer_a() const { return 0; }
uint32_t load_timer_b() const { return 0; }
uint32_t load_timer_a() const { return 0; }
uint32_t csm() const { return 0; }
// per-channel registers
uint32_t ch_block_freq(uint32_t choffs) const { return word(0x20, 0, 4, 0x10, 0, 8, choffs); }
uint32_t ch_sustain(uint32_t choffs) const { return byte(0x20, 5, 1, choffs); }
uint32_t ch_total_level(uint32_t choffs) const { return instchbyte(0x02, 0, 6, choffs); }
uint32_t ch_feedback(uint32_t choffs) const { return instchbyte(0x03, 0, 3, choffs); }
uint32_t ch_algorithm(uint32_t choffs) const { return 0; }
uint32_t ch_instrument(uint32_t choffs) const { return byte(0x30, 4, 4, choffs); }
uint32_t ch_output_any(uint32_t choffs) const { return 1; }
uint32_t ch_output_0(uint32_t choffs) const { return !is_rhythm(choffs); }
uint32_t ch_output_1(uint32_t choffs) const { return is_rhythm(choffs); }
uint32_t ch_output_2(uint32_t choffs) const { return 0; }
uint32_t ch_output_3(uint32_t choffs) const { return 0; }
// per-operator registers
uint32_t op_lfo_am_enable(uint32_t opoffs) const { return instopbyte(0x00, 7, 1, opoffs); }
uint32_t op_lfo_pm_enable(uint32_t opoffs) const { return instopbyte(0x00, 6, 1, opoffs); }
uint32_t op_eg_sustain(uint32_t opoffs) const { return instopbyte(0x00, 5, 1, opoffs); }
uint32_t op_ksr(uint32_t opoffs) const { return instopbyte(0x00, 4, 1, opoffs); }
uint32_t op_multiple(uint32_t opoffs) const { return instopbyte(0x00, 0, 4, opoffs); }
uint32_t op_ksl(uint32_t opoffs) const { return instopbyte(0x02, 6, 2, opoffs); }
uint32_t op_waveform(uint32_t opoffs) const { return instchbyte(0x03, 3 + bitfield(opoffs, 0), 1, opoffs >> 1); }
uint32_t op_attack_rate(uint32_t opoffs) const { return instopbyte(0x04, 4, 4, opoffs); }
uint32_t op_decay_rate(uint32_t opoffs) const { return instopbyte(0x04, 0, 4, opoffs); }
uint32_t op_sustain_level(uint32_t opoffs) const { return instopbyte(0x06, 4, 4, opoffs); }
uint32_t op_release_rate(uint32_t opoffs) const { return instopbyte(0x06, 0, 4, opoffs); }
uint32_t op_volume(uint32_t opoffs) const { return byte(0x30, 4 * bitfield(~opoffs, 0), 4, opoffs >> 1); }
private:
// return a bitfield extracted from a byte
uint32_t byte(uint32_t offset, uint32_t start, uint32_t count, uint32_t extra_offset = 0) const
{
return bitfield(m_regdata[offset + extra_offset], start, count);
}
// return a bitfield extracted from a pair of bytes, MSBs listed first
uint32_t word(uint32_t offset1, uint32_t start1, uint32_t count1, uint32_t offset2, uint32_t start2, uint32_t count2, uint32_t extra_offset = 0) const
{
return (byte(offset1, start1, count1, extra_offset) << count2) | byte(offset2, start2, count2, extra_offset);
}
// helpers to read from instrument channel/operator data
uint32_t instchbyte(uint32_t offset, uint32_t start, uint32_t count, uint32_t choffs) const { return bitfield(m_chinst[choffs][offset], start, count); }
uint32_t instopbyte(uint32_t offset, uint32_t start, uint32_t count, uint32_t opoffs) const { return bitfield(m_opinst[opoffs][offset], start, count); }
// helper to determine if the this channel is an active rhythm channel
bool is_rhythm(uint32_t choffs) const
{
return rhythm_enable() && choffs >= 6;
}
// internal state
uint16_t m_lfo_am_counter; // LFO AM counter
uint16_t m_lfo_pm_counter; // LFO PM counter
uint32_t m_noise_lfsr; // noise LFSR state
uint8_t m_lfo_am; // current LFO AM value
uint8_t const *m_chinst[CHANNELS]; // pointer to instrument data for each channel
uint8_t const *m_opinst[OPERATORS]; // pointer to instrument data for each operator
uint8_t m_regdata[REGISTERS]; // register data
uint8_t m_instdata[INSTDATA_SIZE]; // instrument data
uint16_t m_waveform[WAVEFORMS][WAVEFORM_LENGTH]; // waveforms
};
//*********************************************************
// OPL IMPLEMENTATION CLASSES
//*********************************************************
// ======================> ym3526
class ym3526
{
public:
using fm_engine = fm_engine_base<opl_registers>;
using output_data = fm_engine::output_data;
static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS;
// constructor
ym3526(ymfm_interface &intf);
// reset
void reset();
// save/restore
void save_restore(ymfm_saved_state &state);
// pass-through helpers
uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); }
void invalidate_caches() { m_fm.invalidate_caches(); }
// read access
uint8_t read_status();
uint8_t read(uint32_t offset);
// write access
void write_address(uint8_t data);
void write_data(uint8_t data);
void write(uint32_t offset, uint8_t data);
// generate samples of sound
void generate(output_data *output, uint32_t numsamples = 1);
fm_engine* debug_fm_engine() { return &m_fm; }
protected:
// internal state
uint8_t m_address; // address register
fm_engine m_fm; // core FM engine
};
// ======================> y8950
class y8950
{
public:
using fm_engine = fm_engine_base<opl_registers>;
using output_data = fm_engine::output_data;
static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS;
static constexpr uint8_t STATUS_ADPCM_B_PLAYING = 0x01;
static constexpr uint8_t STATUS_ADPCM_B_BRDY = 0x08;
static constexpr uint8_t STATUS_ADPCM_B_EOS = 0x10;
static constexpr uint8_t ALL_IRQS = STATUS_ADPCM_B_BRDY | STATUS_ADPCM_B_EOS | fm_engine::STATUS_TIMERA | fm_engine::STATUS_TIMERB;
// constructor
y8950(ymfm_interface &intf);
// reset
void reset();
// save/restore
void save_restore(ymfm_saved_state &state);
// pass-through helpers
uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); }
void invalidate_caches() { m_fm.invalidate_caches(); }
// read access
uint8_t read_status();
uint8_t read_data();
uint8_t read(uint32_t offset);
// write access
void write_address(uint8_t data);
void write_data(uint8_t data);
void write(uint32_t offset, uint8_t data);
// generate samples of sound
void generate(output_data *output, uint32_t numsamples = 1);
fm_engine* debug_fm_engine() { return &m_fm; }
adpcm_b_engine* debug_adpcm_b_engine() { return &m_adpcm_b; }
protected:
// internal state
uint8_t m_address; // address register
uint8_t m_io_ddr; // data direction register for I/O
fm_engine m_fm; // core FM engine
adpcm_b_engine m_adpcm_b; // ADPCM-B engine
};
//*********************************************************
// OPL2 IMPLEMENTATION CLASSES
//*********************************************************
// ======================> ym3812
class ym3812
{
public:
using fm_engine = fm_engine_base<opl2_registers>;
using output_data = fm_engine::output_data;
static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS;
// constructor
ym3812(ymfm_interface &intf);
// reset
void reset();
// save/restore
void save_restore(ymfm_saved_state &state);
// pass-through helpers
uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); }
void invalidate_caches() { m_fm.invalidate_caches(); }
// read access
uint8_t read_status();
uint8_t read(uint32_t offset);
// write access
void write_address(uint8_t data);
void write_data(uint8_t data);
void write(uint32_t offset, uint8_t data);
// generate samples of sound
void generate(output_data *output, uint32_t numsamples = 1);
fm_engine* debug_fm_engine() { return &m_fm; }
protected:
// internal state
uint8_t m_address; // address register
fm_engine m_fm; // core FM engine
};
//*********************************************************
// OPL3 IMPLEMENTATION CLASSES
//*********************************************************
// ======================> ymf262
class ymf262
{
public:
using fm_engine = fm_engine_base<opl3_registers>;
using output_data = fm_engine::output_data;
static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS;
// constructor
ymf262(ymfm_interface &intf);
// reset
void reset();
// save/restore
void save_restore(ymfm_saved_state &state);
// pass-through helpers
uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); }
void invalidate_caches() { m_fm.invalidate_caches(); }
// read access
uint8_t read_status();
uint8_t read(uint32_t offset);
// write access
void write_address(uint8_t data);
void write_data(uint8_t data);
void write_address_hi(uint8_t data);
void write(uint32_t offset, uint8_t data);
// generate samples of sound
void generate(output_data *output, uint32_t numsamples = 1);
fm_engine* debug_fm_engine() { return &m_fm; }
protected:
// internal state
uint16_t m_address; // address register
fm_engine m_fm; // core FM engine
};
// ======================> ymf289b
class ymf289b
{
static constexpr uint8_t STATUS_BUSY_FLAGS = 0x05;
public:
using fm_engine = fm_engine_base<opl3_registers>;
using output_data = fm_engine::output_data;
static constexpr uint32_t OUTPUTS = 2;
// constructor
ymf289b(ymfm_interface &intf);
// reset
void reset();
// save/restore
void save_restore(ymfm_saved_state &state);
// pass-through helpers
uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); }
void invalidate_caches() { m_fm.invalidate_caches(); }
// read access
uint8_t read_status();
uint8_t read_data();
uint8_t read(uint32_t offset);
// write access
void write_address(uint8_t data);
void write_data(uint8_t data);
void write_address_hi(uint8_t data);
void write(uint32_t offset, uint8_t data);
// generate samples of sound
void generate(output_data *output, uint32_t numsamples = 1);
protected:
// internal helpers
bool ymf289b_mode() { return ((m_fm.regs().read(0x105) & 0x04) != 0); }
// internal state
uint16_t m_address; // address register
fm_engine m_fm; // core FM engine
};
//*********************************************************
// OPL4 IMPLEMENTATION CLASSES
//*********************************************************
// ======================> ymf278b
class ymf278b
{
// Using the nominal datasheet frequency of 33.868MHz, the output of the
// chip will be clock/768 = 44.1kHz. However, the FM engine is clocked
// internally at clock/(19*36), or 49.515kHz, so the FM output needs to
// be downsampled. We treat this as needing to clock the FM engine an
// extra tick every few samples. The exact ratio is 768/(19*36) or
// 768/684 = 192/171. So if we always clock the FM once, we'll have
// 192/171 - 1 = 21/171 left. Thus we count 21 for each sample and when
// it gets above 171, we tick an extra time.
static constexpr uint32_t FM_EXTRA_SAMPLE_THRESH = 171;
static constexpr uint32_t FM_EXTRA_SAMPLE_STEP = 192 - FM_EXTRA_SAMPLE_THRESH;
public:
using fm_engine = fm_engine_base<opl4_registers>;
static constexpr uint32_t OUTPUTS = 6;
using output_data = ymfm_output<OUTPUTS>;
static constexpr uint8_t STATUS_BUSY = 0x01;
static constexpr uint8_t STATUS_LD = 0x02;
// constructor
ymf278b(ymfm_interface &intf);
// reset
void reset();
// save/restore
void save_restore(ymfm_saved_state &state);
// pass-through helpers
uint32_t sample_rate(uint32_t input_clock) const { return input_clock / 768; }
void invalidate_caches() { m_fm.invalidate_caches(); }
// read access
uint8_t read_status();
uint8_t read_data_pcm();
uint8_t read(uint32_t offset);
// write access
void write_address(uint8_t data);
void write_data(uint8_t data);
void write_address_hi(uint8_t data);
void write_address_pcm(uint8_t data);
void write_data_pcm(uint8_t data);
void write(uint32_t offset, uint8_t data);
// generate samples of sound
void generate(output_data *output, uint32_t numsamples = 1);
protected:
// internal state
uint16_t m_address; // address register
uint32_t m_fm_pos; // FM resampling position
uint32_t m_load_remaining; // how many more samples until LD flag clears
bool m_next_status_id; // flag to track which status ID to return
fm_engine m_fm; // core FM engine
pcm_engine m_pcm; // core PCM engine
};
//*********************************************************
// OPLL IMPLEMENTATION CLASSES
//*********************************************************
// ======================> opll_base
class opll_base
{
public:
using fm_engine = fm_engine_base<opll_registers>;
using output_data = fm_engine::output_data;
static constexpr uint32_t OUTPUTS = fm_engine::OUTPUTS;
// constructor
opll_base(ymfm_interface &intf, uint8_t const *data);
// configuration
void set_instrument_data(uint8_t const *data) { m_fm.regs().set_instrument_data(data); }
// reset
void reset();
// save/restore
void save_restore(ymfm_saved_state &state);
// pass-through helpers
uint32_t sample_rate(uint32_t input_clock) const { return m_fm.sample_rate(input_clock); }
void invalidate_caches() { m_fm.invalidate_caches(); }
// read access -- doesn't really have any, but provide these for consistency
uint8_t read_status() { return 0x00; }
uint8_t read(uint32_t offset) { return 0x00; }
// write access
void write_address(uint8_t data);
void write_data(uint8_t data);
void write(uint32_t offset, uint8_t data);
// generate samples of sound
void generate(output_data *output, uint32_t numsamples = 1);
protected:
// internal state
uint8_t m_address; // address register
fm_engine m_fm; // core FM engine
};
// ======================> ym2413
class ym2413 : public opll_base
{
public:
// constructor
ym2413(ymfm_interface &intf, uint8_t const *instrument_data = nullptr);
private:
// internal state
static uint8_t const s_default_instruments[];
};
// ======================> ym2413
class ym2423 : public opll_base
{
public:
// constructor
ym2423(ymfm_interface &intf, uint8_t const *instrument_data = nullptr);
private:
// internal state
static uint8_t const s_default_instruments[];
};
// ======================> ymf281
class ymf281 : public opll_base
{
public:
// constructor
ymf281(ymfm_interface &intf, uint8_t const *instrument_data = nullptr);
private:
// internal state
static uint8_t const s_default_instruments[];
};
// ======================> ds1001
class ds1001 : public opll_base
{
public:
// constructor
ds1001(ymfm_interface &intf, uint8_t const *instrument_data = nullptr);
private:
// internal state
static uint8_t const s_default_instruments[];
};
}
#endif // YMFM_OPL_H

View file

@ -0,0 +1,714 @@
// BSD 3-Clause License
//
// Copyright (c) 2021, Aaron Giles
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "ymfm_pcm.h"
#include "ymfm_fm.h"
#include "ymfm_fm.ipp"
namespace ymfm
{
//*********************************************************
// PCM REGISTERS
//*********************************************************
//-------------------------------------------------
// reset - reset the register state
//-------------------------------------------------
void pcm_registers::reset()
{
std::fill_n(&m_regdata[0], REGISTERS, 0);
m_regdata[0xf8] = 0x1b;
}
//-------------------------------------------------
// save_restore - save or restore the data
//-------------------------------------------------
void pcm_registers::save_restore(ymfm_saved_state &state)
{
state.save_restore(m_regdata);
}
//-------------------------------------------------
// cache_channel_data - update the cache with
// data from the registers
//-------------------------------------------------
void pcm_registers::cache_channel_data(uint32_t choffs, pcm_cache &cache)
{
// compute step from octave and fnumber; the math here implies
// a .18 fraction but .16 should be perfectly fine
int32_t octave = int8_t(ch_octave(choffs) << 4) >> 4;
uint32_t fnum = ch_fnumber(choffs);
cache.step = ((0x400 | fnum) << (octave + 7)) >> 2;
// total level is computed as a .10 value for interpolation
cache.total_level = ch_total_level(choffs) << 10;
// compute panning values in terms of envelope attenuation
int32_t panpot = int8_t(ch_panpot(choffs) << 4) >> 4;
if (panpot >= 0)
{
cache.pan_left = (panpot == 7) ? 0x3ff : 0x20 * panpot;
cache.pan_right = 0;
}
else if (panpot >= -7)
{
cache.pan_left = 0;
cache.pan_right = (panpot == -7) ? 0x3ff : -0x20 * panpot;
}
else
cache.pan_left = cache.pan_right = 0x3ff;
// determine the LFO stepping value; this how much to add to a running
// x.18 value for the LFO; steps were derived from frequencies in the
// manual and come out very close with these values
static const uint8_t s_lfo_steps[8] = { 1, 12, 19, 25, 31, 35, 37, 42 };
cache.lfo_step = s_lfo_steps[ch_lfo_speed(choffs)];
// AM LFO depth values, derived from the manual; note each has at most
// 2 bits to make the "multiply" easy in hardware
static const uint8_t s_am_depth[8] = { 0, 0x14, 0x20, 0x28, 0x30, 0x40, 0x50, 0x80 };
cache.am_depth = s_am_depth[ch_am_depth(choffs)];
// PM LFO depth values; these are converted from the manual's cents values
// into f-numbers; the computations come out quite cleanly so pretty sure
// these are correct
static const uint8_t s_pm_depth[8] = { 0, 2, 3, 4, 6, 12, 24, 48 };
cache.pm_depth = s_pm_depth[ch_vibrato(choffs)];
// 4-bit sustain level, but 15 means 31 so effectively 5 bits
cache.eg_sustain = ch_sustain_level(choffs);
cache.eg_sustain |= (cache.eg_sustain + 1) & 0x10;
cache.eg_sustain <<= 5;
// compute the key scaling correction factor; 15 means don't do any correction
int32_t correction = ch_rate_correction(choffs);
if (correction == 15)
correction = 0;
else
correction = (octave + correction) * 2 + bitfield(fnum, 9);
// compute the envelope generator rates
cache.eg_rate[EG_ATTACK] = effective_rate(ch_attack_rate(choffs), correction);
cache.eg_rate[EG_DECAY] = effective_rate(ch_decay_rate(choffs), correction);
cache.eg_rate[EG_SUSTAIN] = effective_rate(ch_sustain_rate(choffs), correction);
cache.eg_rate[EG_RELEASE] = effective_rate(ch_release_rate(choffs), correction);
cache.eg_rate[EG_REVERB] = 5;
// if damping is on, override some things; essentially decay at a hardcoded
// rate of 48 until -12db (0x80), then at maximum rate for the rest
if (ch_damp(choffs) != 0)
{
cache.eg_rate[EG_DECAY] = 48;
cache.eg_rate[EG_SUSTAIN] = 63;
cache.eg_rate[EG_RELEASE] = 63;
cache.eg_sustain = 0x80;
}
}
//-------------------------------------------------
// effective_rate - return the effective rate,
// clamping and applying corrections as needed
//-------------------------------------------------
uint32_t pcm_registers::effective_rate(uint32_t raw, uint32_t correction)
{
// raw rates of 0 and 15 just pin to min/max
if (raw == 0)
return 0;
if (raw == 15)
return 63;
// otherwise add the correction and clamp to range
return clamp(raw * 4 + correction, 0, 63);
}
//*********************************************************
// PCM CHANNEL
//*********************************************************
//-------------------------------------------------
// pcm_channel - constructor
//-------------------------------------------------
pcm_channel::pcm_channel(pcm_engine &owner, uint32_t choffs) :
m_choffs(choffs),
m_baseaddr(0),
m_endpos(0),
m_looppos(0),
m_curpos(0),
m_nextpos(0),
m_lfo_counter(0),
m_eg_state(EG_RELEASE),
m_env_attenuation(0x3ff),
m_total_level(0x7f << 10),
m_format(0),
m_key_state(0),
m_regs(owner.regs()),
m_owner(owner)
{
}
//-------------------------------------------------
// reset - reset the channel state
//-------------------------------------------------
void pcm_channel::reset()
{
m_baseaddr = 0;
m_endpos = 0;
m_looppos = 0;
m_curpos = 0;
m_nextpos = 0;
m_lfo_counter = 0;
m_eg_state = EG_RELEASE;
m_env_attenuation = 0x3ff;
m_total_level = 0x7f << 10;
m_format = 0;
m_key_state = 0;
}
//-------------------------------------------------
// save_restore - save or restore the data
//-------------------------------------------------
void pcm_channel::save_restore(ymfm_saved_state &state)
{
state.save_restore(m_baseaddr);
state.save_restore(m_endpos);
state.save_restore(m_looppos);
state.save_restore(m_curpos);
state.save_restore(m_nextpos);
state.save_restore(m_lfo_counter);
state.save_restore(m_eg_state);
state.save_restore(m_env_attenuation);
state.save_restore(m_total_level);
state.save_restore(m_format);
state.save_restore(m_key_state);
}
//-------------------------------------------------
// prepare - prepare for clocking
//-------------------------------------------------
bool pcm_channel::prepare()
{
// cache the data
m_regs.cache_channel_data(m_choffs, m_cache);
// clock the key state
if ((m_key_state & KEY_PENDING) != 0)
{
uint8_t oldstate = m_key_state;
m_key_state = (m_key_state >> 1) & KEY_ON;
if (((oldstate ^ m_key_state) & KEY_ON) != 0)
{
if ((m_key_state & KEY_ON) != 0)
start_attack();
else
start_release();
}
}
// set the total level directly if not interpolating
if (m_regs.ch_level_direct(m_choffs))
m_total_level = m_cache.total_level;
// we're active until we're quiet after the release
return (m_eg_state < EG_RELEASE || m_env_attenuation < EG_QUIET);
}
//-------------------------------------------------
// clock - master clocking function
//-------------------------------------------------
void pcm_channel::clock(uint32_t env_counter)
{
// clock the LFO, which is an x.18 value incremented based on the
// LFO speed value
m_lfo_counter += m_cache.lfo_step;
// clock the envelope
clock_envelope(env_counter);
// determine the step after applying vibrato
uint32_t step = m_cache.step;
if (m_cache.pm_depth != 0)
{
// shift the LFO by 1/4 cycle for PM so that it starts at 0
uint32_t lfo_shifted = m_lfo_counter + (1 << 16);
int32_t lfo_value = bitfield(lfo_shifted, 10, 7);
if (bitfield(lfo_shifted, 17) != 0)
lfo_value ^= 0x7f;
lfo_value -= 0x40;
step += (lfo_value * int32_t(m_cache.pm_depth)) >> 7;
}
// advance the sample step and loop as needed
m_curpos = m_nextpos;
m_nextpos = m_curpos + step;
if (m_nextpos >= m_endpos)
m_nextpos += m_looppos - m_endpos;
// interpolate total level if needed
if (m_total_level != m_cache.total_level)
{
// max->min volume takes 156.4ms, or pretty close to 19/1024 per 44.1kHz sample
// min->max volume is half that, so advance by 38/1024 per sample
if (m_total_level < m_cache.total_level)
m_total_level = std::min<int32_t>(m_total_level + 19, m_cache.total_level);
else
m_total_level = std::max<int32_t>(m_total_level - 38, m_cache.total_level);
}
}
//-------------------------------------------------
// output - return the computed output value, with
// panning applied
//-------------------------------------------------
void pcm_channel::output(output_data &output) const
{
// early out if the envelope is effectively off
uint32_t envelope = m_env_attenuation;
if (envelope > EG_QUIET)
return;
// add in LFO AM modulation
if (m_cache.am_depth != 0)
{
uint32_t lfo_value = bitfield(m_lfo_counter, 10, 7);
if (bitfield(m_lfo_counter, 17) != 0)
lfo_value ^= 0x7f;
envelope += (lfo_value * m_cache.am_depth) >> 7;
}
// add in the current interpolated total level value, which is a .10
// value shifted left by 2
envelope += m_total_level >> 8;
// add in panning effect and clamp
uint32_t lenv = std::min<uint32_t>(envelope + m_cache.pan_left, 0x3ff);
uint32_t renv = std::min<uint32_t>(envelope + m_cache.pan_right, 0x3ff);
// convert to volume as a .11 fraction
int32_t lvol = attenuation_to_volume(lenv << 2);
int32_t rvol = attenuation_to_volume(renv << 2);
// fetch current sample and add
int16_t sample = fetch_sample();
uint32_t outnum = m_regs.ch_output_channel(m_choffs) * 2;
output.data[outnum + 0] += (lvol * sample) >> 15;
output.data[outnum + 1] += (rvol * sample) >> 15;
}
//-------------------------------------------------
// keyonoff - signal key on/off
//-------------------------------------------------
void pcm_channel::keyonoff(bool on)
{
// mark the key state as pending
m_key_state |= KEY_PENDING | (on ? KEY_PENDING_ON : 0);
// don't log masked channels
if ((m_key_state & (KEY_PENDING_ON | KEY_ON)) == KEY_PENDING_ON && ((debug::GLOBAL_PCM_CHANNEL_MASK >> m_choffs) & 1) != 0)
{
debug::log_keyon("KeyOn PCM-%02d: num=%3d oct=%2d fnum=%03X level=%02X%c ADSR=%X/%X/%X/%X SL=%X",
m_choffs,
m_regs.ch_wave_table_num(m_choffs),
int8_t(m_regs.ch_octave(m_choffs) << 4) >> 4,
m_regs.ch_fnumber(m_choffs),
m_regs.ch_total_level(m_choffs),
m_regs.ch_level_direct(m_choffs) ? '!' : '/',
m_regs.ch_attack_rate(m_choffs),
m_regs.ch_decay_rate(m_choffs),
m_regs.ch_sustain_rate(m_choffs),
m_regs.ch_release_rate(m_choffs),
m_regs.ch_sustain_level(m_choffs));
if (m_regs.ch_rate_correction(m_choffs) != 15)
debug::log_keyon(" RC=%X", m_regs.ch_rate_correction(m_choffs));
if (m_regs.ch_pseudo_reverb(m_choffs) != 0)
debug::log_keyon(" %s", "REV");
if (m_regs.ch_damp(m_choffs) != 0)
debug::log_keyon(" %s", "DAMP");
if (m_regs.ch_vibrato(m_choffs) != 0 || m_regs.ch_am_depth(m_choffs) != 0)
{
if (m_regs.ch_vibrato(m_choffs) != 0)
debug::log_keyon(" VIB=%d", m_regs.ch_vibrato(m_choffs));
if (m_regs.ch_am_depth(m_choffs) != 0)
debug::log_keyon(" AM=%d", m_regs.ch_am_depth(m_choffs));
debug::log_keyon(" LFO=%d", m_regs.ch_lfo_speed(m_choffs));
}
debug::log_keyon("%s", "\n");
}
}
//-------------------------------------------------
// load_wavetable - load a wavetable by fetching
// its data from external memory
//-------------------------------------------------
void pcm_channel::load_wavetable()
{
// determine the address of the wave table header
uint32_t wavnum = m_regs.ch_wave_table_num(m_choffs);
uint32_t wavheader = 12 * wavnum;
// above 384 it may be in a different bank
if (wavnum >= 384)
{
uint32_t bank = m_regs.wave_table_header();
if (bank != 0)
wavheader = 512*1024 * bank + (wavnum - 384) * 12;
}
// fetch the 22-bit base address and 2-bit format
uint8_t byte = read_pcm(wavheader + 0);
m_format = bitfield(byte, 6, 2);
m_baseaddr = bitfield(byte, 0, 6) << 16;
m_baseaddr |= read_pcm(wavheader + 1) << 8;
m_baseaddr |= read_pcm(wavheader + 2) << 0;
// fetch the 16-bit loop position
m_looppos = read_pcm(wavheader + 3) << 8;
m_looppos |= read_pcm(wavheader + 4);
m_looppos <<= 16;
// fetch the 16-bit end position, which is stored as a negative value
// for some reason that is unclear
m_endpos = read_pcm(wavheader + 5) << 8;
m_endpos |= read_pcm(wavheader + 6);
m_endpos = -int32_t(m_endpos) << 16;
// remaining data values set registers
m_owner.write(0x80 + m_choffs, read_pcm(wavheader + 7));
m_owner.write(0x98 + m_choffs, read_pcm(wavheader + 8));
m_owner.write(0xb0 + m_choffs, read_pcm(wavheader + 9));
m_owner.write(0xc8 + m_choffs, read_pcm(wavheader + 10));
m_owner.write(0xe0 + m_choffs, read_pcm(wavheader + 11));
// reset the envelope so we don't continue playing mid-sample from previous key ons
m_env_attenuation = 0x3ff;
}
//-------------------------------------------------
// read_pcm - read a byte from the external PCM
// memory interface
//-------------------------------------------------
uint8_t pcm_channel::read_pcm(uint32_t address) const
{
return m_owner.intf().ymfm_external_read(ACCESS_PCM, address);
}
//-------------------------------------------------
// start_attack - start the attack phase
//-------------------------------------------------
void pcm_channel::start_attack()
{
// don't change anything if already in attack state
if (m_eg_state == EG_ATTACK)
return;
m_eg_state = EG_ATTACK;
// reset the LFO if requested
if (m_regs.ch_lfo_reset(m_choffs))
m_lfo_counter = 0;
// if the attack rate == 63 then immediately go to max attenuation
if (m_cache.eg_rate[EG_ATTACK] == 63)
m_env_attenuation = 0;
// reset the positions
m_curpos = m_nextpos = 0;
}
//-------------------------------------------------
// start_release - start the release phase
//-------------------------------------------------
void pcm_channel::start_release()
{
// don't change anything if already in release or reverb state
if (m_eg_state >= EG_RELEASE)
return;
m_eg_state = EG_RELEASE;
}
//-------------------------------------------------
// clock_envelope - clock the envelope generator
//-------------------------------------------------
void pcm_channel::clock_envelope(uint32_t env_counter)
{
// handle attack->decay transitions
if (m_eg_state == EG_ATTACK && m_env_attenuation == 0)
m_eg_state = EG_DECAY;
// handle decay->sustain transitions
if (m_eg_state == EG_DECAY && m_env_attenuation >= m_cache.eg_sustain)
m_eg_state = EG_SUSTAIN;
// fetch the appropriate 6-bit rate value from the cache
uint32_t rate = m_cache.eg_rate[m_eg_state];
// compute the rate shift value; this is the shift needed to
// apply to the env_counter such that it becomes a 5.11 fixed
// point number
uint32_t rate_shift = rate >> 2;
env_counter <<= rate_shift;
// see if the fractional part is 0; if not, it's not time to clock
if (bitfield(env_counter, 0, 11) != 0)
return;
// determine the increment based on the non-fractional part of env_counter
uint32_t relevant_bits = bitfield(env_counter, (rate_shift <= 11) ? 11 : rate_shift, 3);
uint32_t increment = attenuation_increment(rate, relevant_bits);
// attack is the only one that increases
if (m_eg_state == EG_ATTACK)
m_env_attenuation += (~m_env_attenuation * increment) >> 4;
// all other cases are similar
else
{
// apply the increment
m_env_attenuation += increment;
// clamp the final attenuation
if (m_env_attenuation >= 0x400)
m_env_attenuation = 0x3ff;
// transition to reverb at -18dB if enabled
if (m_env_attenuation >= 0xc0 && m_eg_state < EG_REVERB && m_regs.ch_pseudo_reverb(m_choffs))
m_eg_state = EG_REVERB;
}
}
//-------------------------------------------------
// fetch_sample - fetch a sample at the current
// position
//-------------------------------------------------
int16_t pcm_channel::fetch_sample() const
{
uint32_t addr = m_baseaddr;
uint32_t pos = m_curpos >> 16;
// 8-bit PCM: shift up by 8
if (m_format == 0)
return read_pcm(addr + pos) << 8;
// 16-bit PCM: assemble from 2 halves
if (m_format == 2)
{
addr += pos * 2;
return (read_pcm(addr) << 8) | read_pcm(addr + 1);
}
// 12-bit PCM: assemble out of half of 3 bytes
addr += (pos / 2) * 3;
if ((pos & 1) == 0)
return (read_pcm(addr + 0) << 8) | ((read_pcm(addr + 1) << 4) & 0xf0);
else
return (read_pcm(addr + 2) << 8) | ((read_pcm(addr + 1) << 0) & 0xf0);
}
//*********************************************************
// PCM ENGINE
//*********************************************************
//-------------------------------------------------
// pcm_engine - constructor
//-------------------------------------------------
pcm_engine::pcm_engine(ymfm_interface &intf) :
m_intf(intf),
m_env_counter(0),
m_modified_channels(ALL_CHANNELS),
m_active_channels(ALL_CHANNELS)
{
// create the channels
for (int chnum = 0; chnum < CHANNELS; chnum++)
m_channel[chnum] = std::make_unique<pcm_channel>(*this, chnum);
}
//-------------------------------------------------
// reset - reset the engine state
//-------------------------------------------------
void pcm_engine::reset()
{
// reset register state
m_regs.reset();
// reset each channel
for (auto &chan : m_channel)
chan->reset();
}
//-------------------------------------------------
// save_restore - save or restore the data
//-------------------------------------------------
void pcm_engine::save_restore(ymfm_saved_state &state)
{
// save our data
state.save_restore(m_env_counter);
// save channel state
for (int chnum = 0; chnum < CHANNELS; chnum++)
m_channel[chnum]->save_restore(state);
}
//-------------------------------------------------
// clock - master clocking function
//-------------------------------------------------
void pcm_engine::clock(uint32_t chanmask)
{
// if something was modified, prepare
// also prepare every 4k samples to catch ending notes
if (m_modified_channels != 0 || m_prepare_count++ >= 4096)
{
// call each channel to prepare
m_active_channels = 0;
for (int chnum = 0; chnum < CHANNELS; chnum++)
if (bitfield(chanmask, chnum))
if (m_channel[chnum]->prepare())
m_active_channels |= 1 << chnum;
// reset the modified channels and prepare count
m_modified_channels = m_prepare_count = 0;
}
// increment the envelope counter; the envelope generator
// only clocks every other sample in order to make the PCM
// envelopes line up with the FM envelopes (after taking into
// account the different FM sampling rate)
m_env_counter++;
// now update the state of all the channels and operators
for (int chnum = 0; chnum < CHANNELS; chnum++)
if (bitfield(chanmask, chnum))
m_channel[chnum]->clock(m_env_counter >> 1);
}
//-------------------------------------------------
// update - master update function
//-------------------------------------------------
void pcm_engine::output(output_data &output, uint32_t chanmask)
{
// mask out some channels for debug purposes
chanmask &= debug::GLOBAL_PCM_CHANNEL_MASK;
// compute the output of each channel
for (int chnum = 0; chnum < CHANNELS; chnum++)
if (bitfield(chanmask, chnum))
m_channel[chnum]->output(output);
}
//-------------------------------------------------
// read - handle reads from the PCM registers
//-------------------------------------------------
uint8_t pcm_engine::read(uint32_t regnum)
{
// handle reads from the data register
if (regnum == 0x06 && m_regs.memory_access_mode() != 0)
return m_intf.ymfm_external_read(ACCESS_PCM, m_regs.memory_address_autoinc());
return m_regs.read(regnum);
}
//-------------------------------------------------
// write - handle writes to the PCM registers
//-------------------------------------------------
void pcm_engine::write(uint32_t regnum, uint8_t data)
{
// handle reads to the data register
if (regnum == 0x06 && m_regs.memory_access_mode() != 0)
{
m_intf.ymfm_external_write(ACCESS_PCM, m_regs.memory_address_autoinc(), data);
return;
}
// for now just mark all channels as modified
m_modified_channels = ALL_CHANNELS;
// most writes are passive, consumed only when needed
m_regs.write(regnum, data);
// however, process keyons immediately
if (regnum >= 0x68 && regnum <= 0x7f)
m_channel[regnum - 0x68]->keyonoff(bitfield(data, 7));
// and also wavetable writes
else if (regnum >= 0x08 && regnum <= 0x1f)
m_channel[regnum - 0x08]->load_wavetable();
}
}

View file

@ -0,0 +1,347 @@
// BSD 3-Clause License
//
// Copyright (c) 2021, Aaron Giles
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef YMFM_PCM_H
#define YMFM_PCM_H
#pragma once
#include "ymfm.h"
namespace ymfm
{
/*
Note to self: Sega "Multi-PCM" is almost identical to this
28 channels
Writes:
00 = data reg, causes write
01 = target slot = data - (data / 8)
02 = address (clamped to 7)
Slot data (registers with ADSR/KSR seem to be inaccessible):
0: xxxx---- panpot
1: xxxxxxxx wavetable low
2: xxxxxx-- pitch low
-------x wavetable high
3: xxxx---- octave
----xxxx pitch hi
4: x------- key on
5: xxxxxxx- total level
-------x level direct (0=interpolate)
6: --xxx--- LFO frequency
-----xxx PM sensitivity
7: -----xxx AM sensitivity
Sample data:
+00: start hi
+01: start mid
+02: start low
+03: loop hi
+04: loop low
+05: -end hi
+06: -end low
+07: vibrato (reg 6)
+08: attack/decay
+09: sustain level/rate
+0A: ksr/release
+0B: LFO amplitude (reg 7)
*/
//*********************************************************
// INTERFACE CLASSES
//*********************************************************
class pcm_engine;
// ======================> pcm_cache
// this class holds data that is computed once at the start of clocking
// and remains static during subsequent sound generation
struct pcm_cache
{
uint32_t step; // sample position step, as a .16 value
uint32_t total_level; // target total level, as a .10 value
uint32_t pan_left; // left panning attenuation
uint32_t pan_right; // right panning attenuation
uint32_t eg_sustain; // sustain level, shifted up to envelope values
uint8_t eg_rate[EG_STATES]; // envelope rate, including KSR
uint8_t lfo_step; // stepping value for LFO
uint8_t am_depth; // scale value for AM LFO
uint8_t pm_depth; // scale value for PM LFO
};
// ======================> pcm_registers
//
// PCM register map:
//
// System-wide registers:
// 00-01 xxxxxxxx LSI Test
// 02 -------x Memory access mode (0=sound gen, 1=read/write)
// ------x- Memory type (0=ROM, 1=ROM+SRAM)
// ---xxx-- Wave table header
// xxx----- Device ID (=1 for YMF278B)
// 03 --xxxxxx Memory address high
// 04 xxxxxxxx Memory address mid
// 05 xxxxxxxx Memory address low
// 06 xxxxxxxx Memory data
// F8 --xxx--- Mix control (FM_R)
// -----xxx Mix control (FM_L)
// F9 --xxx--- Mix control (PCM_R)
// -----xxx Mix control (PCM_L)
//
// Channel-specific registers:
// 08-1F xxxxxxxx Wave table number low
// 20-37 -------x Wave table number high
// xxxxxxx- F-number low
// 38-4F -----xxx F-number high
// ----x--- Pseudo-reverb
// xxxx---- Octave
// 50-67 xxxxxxx- Total level
// -------x Level direct
// 68-7F x------- Key on
// -x------ Damp
// --x----- LFO reset
// ---x---- Output channel
// ----xxxx Panpot
// 80-97 --xxx--- LFO speed
// -----xxx Vibrato
// 98-AF xxxx---- Attack rate
// ----xxxx Decay rate
// B0-C7 xxxx---- Sustain level
// ----xxxx Sustain rate
// C8-DF xxxx---- Rate correction
// ----xxxx Release rate
// E0-F7 -----xxx AM depth
class pcm_registers
{
public:
// constants
static constexpr uint32_t OUTPUTS = 4;
static constexpr uint32_t CHANNELS = 24;
static constexpr uint32_t REGISTERS = 0x100;
static constexpr uint32_t ALL_CHANNELS = (1 << CHANNELS) - 1;
// constructor
pcm_registers() { }
// save/restore
void save_restore(ymfm_saved_state &state);
// reset to initial state
void reset();
// update cache information
void cache_channel_data(uint32_t choffs, pcm_cache &cache);
// direct read/write access
uint8_t read(uint32_t index ) { return m_regdata[index]; }
void write(uint32_t index, uint8_t data) { m_regdata[index] = data; }
// system-wide registers
uint32_t memory_access_mode() const { return bitfield(m_regdata[0x02], 0); }
uint32_t memory_type() const { return bitfield(m_regdata[0x02], 1); }
uint32_t wave_table_header() const { return bitfield(m_regdata[0x02], 2, 3); }
uint32_t device_id() const { return bitfield(m_regdata[0x02], 5, 3); }
uint32_t memory_address() const { return (bitfield(m_regdata[0x03], 0, 6) << 16) | (m_regdata[0x04] << 8) | m_regdata[0x05]; }
uint32_t memory_data() const { return m_regdata[0x06]; }
uint32_t mix_fm_r() const { return bitfield(m_regdata[0xf8], 3, 3); }
uint32_t mix_fm_l() const { return bitfield(m_regdata[0xf8], 0, 3); }
uint32_t mix_pcm_r() const { return bitfield(m_regdata[0xf9], 3, 3); }
uint32_t mix_pcm_l() const { return bitfield(m_regdata[0xf9], 0, 3); }
// per-channel registers
uint32_t ch_wave_table_num(uint32_t choffs) const { return m_regdata[choffs + 0x08] | (bitfield(m_regdata[choffs + 0x20], 0) << 8); }
uint32_t ch_fnumber(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x20], 1, 7) | (bitfield(m_regdata[choffs + 0x38], 0, 3) << 7); }
uint32_t ch_pseudo_reverb(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x38], 3); }
uint32_t ch_octave(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x38], 4, 4); }
uint32_t ch_total_level(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x50], 1, 7); }
uint32_t ch_level_direct(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x50], 0); }
uint32_t ch_keyon(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x68], 7); }
uint32_t ch_damp(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x68], 6); }
uint32_t ch_lfo_reset(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x68], 5); }
uint32_t ch_output_channel(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x68], 4); }
uint32_t ch_panpot(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x68], 0, 4); }
uint32_t ch_lfo_speed(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x80], 3, 3); }
uint32_t ch_vibrato(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x80], 0, 3); }
uint32_t ch_attack_rate(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x98], 4, 4); }
uint32_t ch_decay_rate(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0x98], 0, 4); }
uint32_t ch_sustain_level(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0xb0], 4, 4); }
uint32_t ch_sustain_rate(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0xb0], 0, 4); }
uint32_t ch_rate_correction(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0xc8], 4, 4); }
uint32_t ch_release_rate(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0xc8], 0, 4); }
uint32_t ch_am_depth(uint32_t choffs) const { return bitfield(m_regdata[choffs + 0xe0], 0, 3); }
// return the memory address and increment it
uint32_t memory_address_autoinc()
{
uint32_t result = memory_address();
uint32_t newval = result + 1;
m_regdata[0x05] = newval >> 0;
m_regdata[0x04] = newval >> 8;
m_regdata[0x03] = (newval >> 16) & 0x3f;
return result;
}
private:
// internal helpers
uint32_t effective_rate(uint32_t raw, uint32_t correction);
// internal state
uint8_t m_regdata[REGISTERS]; // register data
};
// ======================> pcm_channel
class pcm_channel
{
static constexpr uint8_t KEY_ON = 0x01;
static constexpr uint8_t KEY_PENDING_ON = 0x02;
static constexpr uint8_t KEY_PENDING = 0x04;
// "quiet" value, used to optimize when we can skip doing working
static constexpr uint32_t EG_QUIET = 0x200;
public:
using output_data = ymfm_output<pcm_registers::OUTPUTS>;
// constructor
pcm_channel(pcm_engine &owner, uint32_t choffs);
// save/restore
void save_restore(ymfm_saved_state &state);
// reset the channel state
void reset();
// return the channel offset
uint32_t choffs() const { return m_choffs; }
// prepare prior to clocking
bool prepare();
// master clocking function
void clock(uint32_t env_counter);
// return the computed output value, with panning applied
void output(output_data &output) const;
// signal key on/off
void keyonoff(bool on);
// load a new wavetable entry
void load_wavetable();
private:
// internal helpers
void start_attack();
void start_release();
void clock_envelope(uint32_t env_counter);
int16_t fetch_sample() const;
uint8_t read_pcm(uint32_t address) const;
// internal state
uint32_t const m_choffs; // channel offset
uint32_t m_baseaddr; // base address
uint32_t m_endpos; // ending position
uint32_t m_looppos; // loop position
uint32_t m_curpos; // current position
uint32_t m_nextpos; // next position
uint32_t m_lfo_counter; // LFO counter
envelope_state m_eg_state; // envelope state
uint16_t m_env_attenuation; // computed envelope attenuation
uint32_t m_total_level; // total level with as 7.10 for interp
uint8_t m_format; // sample format
uint8_t m_key_state; // current key state
pcm_cache m_cache; // cached data
pcm_registers &m_regs; // reference to registers
pcm_engine &m_owner; // reference to our owner
};
// ======================> pcm_engine
class pcm_engine
{
public:
static constexpr int OUTPUTS = pcm_registers::OUTPUTS;
static constexpr int CHANNELS = pcm_registers::CHANNELS;
static constexpr uint32_t ALL_CHANNELS = pcm_registers::ALL_CHANNELS;
using output_data = pcm_channel::output_data;
// constructor
pcm_engine(ymfm_interface &intf);
// reset our status
void reset();
// save/restore
void save_restore(ymfm_saved_state &state);
// master clocking function
void clock(uint32_t chanmask);
// compute sum of channel outputs
void output(output_data &output, uint32_t chanmask);
// read from the PCM registers
uint8_t read(uint32_t regnum);
// write to the PCM registers
void write(uint32_t regnum, uint8_t data);
// return a reference to our interface
ymfm_interface &intf() { return m_intf; }
// return a reference to our registers
pcm_registers &regs() { return m_regs; }
private:
// internal state
ymfm_interface &m_intf; // reference to the interface
uint32_t m_env_counter; // envelope counter
uint32_t m_modified_channels; // bitmask of modified channels
uint32_t m_active_channels; // bitmask of active channels
uint32_t m_prepare_count; // counter to do periodic prepare sweeps
std::unique_ptr<pcm_channel> m_channel[CHANNELS]; // array of channels
pcm_registers m_regs; // registers
};
}
#endif // YMFM_PCM_H

View file

@ -96,7 +96,7 @@ const char** DivPlatformVB::getRegisterSheet() {
void DivPlatformVB::acquire(short** buf, size_t len) {
for (size_t h=0; h<len; h++) {
cycles=0;
while (!writes.empty()) {
if (!writes.empty()) {
QueuedWrite w=writes.front();
vb->Write(cycles,w.addr,w.val);
regPool[w.addr>>2]=w.val;
@ -123,6 +123,7 @@ void DivPlatformVB::acquire(short** buf, size_t len) {
}
void DivPlatformVB::updateWave(int ch) {
if (romMode) return;
if (ch>=5) return;
for (int i=0; i<32; i++) {
@ -162,6 +163,9 @@ void DivPlatformVB::tick(bool sysTick) {
if (chan[i].std.wave.had) {
if (chan[i].wave!=chan[i].std.wave.val || chan[i].ws.activeChanged()) {
chan[i].wave=chan[i].std.wave.val;
if (romMode) {
chWrite(i,0x06,chan[i].wave);
}
chan[i].ws.changeWave1(chan[i].wave);
if (!chan[i].keyOff) chan[i].keyOn=true;
}
@ -282,6 +286,9 @@ int DivPlatformVB::dispatch(DivCommand c) {
case DIV_CMD_WAVE:
chan[c.chan].wave=c.value;
chan[c.chan].ws.changeWave1(chan[c.chan].wave);
if (romMode) {
chWrite(c.chan,0x06,chan[c.chan].wave);
}
chan[c.chan].keyOn=true;
break;
case DIV_CMD_NOTE_PORTA: {
@ -464,8 +471,13 @@ void DivPlatformVB::reset() {
chWrite(i,0x01,isMuted[i]?0:chan[i].pan);
chWrite(i,0x05,0x00);
chWrite(i,0x00,0x80);
chWrite(i,0x06,i);
if (romMode) {
chWrite(i,0x06,0);
} else {
chWrite(i,0x06,i);
}
}
updateROMWaves();
delay=500;
}
@ -481,6 +493,27 @@ float DivPlatformVB::getPostAmp() {
return 6.0f;
}
void DivPlatformVB::updateROMWaves() {
if (romMode) {
// copy wavetables
for (int i=0; i<5; i++) {
int data=0;
DivWavetable* w=parent->getWave(i);
for (int j=0; j<32; j++) {
if (w->max<1 || w->len<1) {
data=0;
} else {
data=w->data[j*w->len/32]*63/w->max;
if (data<0) data=0;
if (data>63) data=63;
}
rWrite((i<<7)+(j<<2),data);
}
}
}
}
void DivPlatformVB::notifyWaveChange(int wave) {
for (int i=0; i<6; i++) {
if (chan[i].wave==wave) {
@ -488,6 +521,7 @@ void DivPlatformVB::notifyWaveChange(int wave) {
updateWave(i);
}
}
updateROMWaves();
}
void DivPlatformVB::notifyInsDeletion(void* ins) {
@ -504,6 +538,8 @@ void DivPlatformVB::setFlags(const DivConfig& flags) {
oscBuf[i]->rate=rate;
}
romMode=flags.getBool("romMode",false);
if (vb!=NULL) {
delete vb;
vb=NULL;

View file

@ -57,10 +57,12 @@ class DivPlatformVB: public DivDispatch {
int tempR;
unsigned char modulation;
bool modType;
bool romMode;
signed char modTable[32];
VSU* vb;
unsigned char regPool[0x600];
void updateWave(int ch);
void updateROMWaves();
void writeEnv(int ch, bool upperByteToo=false);
friend void putDispatchChip(void*,int);
friend void putDispatchChan(void*,int,int);

View file

@ -70,10 +70,18 @@ void DivPlatformVERA::acquire(short** buf, size_t len) {
if (!isMuted[16]) {
// TODO stereo samples once DivSample has a support for it
if (chan[16].pcm.depth16) {
tmp_l=s->data16[chan[16].pcm.pos];
if (chan[16].pcm.pos<s->samples) {
tmp_l=s->data16[chan[16].pcm.pos];
} else {
tmp_l=0;
}
tmp_r=tmp_l;
} else {
tmp_l=s->data8[chan[16].pcm.pos];
if (chan[16].pcm.pos<s->samples) {
tmp_l=s->data8[chan[16].pcm.pos];
} else {
tmp_l=0;
}
tmp_r=tmp_l;
}
if (!(chan[16].pan&1)) tmp_l=0;

View file

@ -30,13 +30,13 @@ class DivPlatformVERA: public DivDispatch {
protected:
struct Channel: public SharedChannel<int> {
unsigned char pan;
unsigned accum;
unsigned int accum;
int noiseval;
struct PCMChannel {
int sample;
unsigned pos;
unsigned len;
unsigned int pos;
unsigned int len;
unsigned char freq;
bool depth16;
PCMChannel(): sample(-1), pos(0), len(0), freq(0), depth16(false) {}

View file

@ -370,7 +370,8 @@ int DivEngine::minVGMVersion(DivSystem which) {
// define systems like:
// sysDefs[DIV_SYSTEM_ID]=new DivSysDef(
// "Name", "Name (japanese, optional)", fileID, fileID_DMF, channels, isFM, isSTD, vgmVersion,
// "Name", "Name (japanese, optional)", fileID, fileID_DMF, channels, isFM, isSTD, vgmVersion, waveWidth, waveHeight,
// "Description",
// {"Channel Names", ...},
// {"Channel Short Names", ...},
// {chanTypes, ...},
@ -618,7 +619,7 @@ void DivEngine::registerSystems() {
// this chip uses YMZ ADPCM, but the emulator uses ADPCM-B because I got it wrong back then.
sysDefs[DIV_SYSTEM_YMU759]=new DivSysDef(
"Yamaha YMU759 (MA-2)", NULL, 0x01, 0x01, 17, true, false, 0, false, (1U<<DIV_SAMPLE_DEPTH_YMZ_ADPCM)|(1U<<DIV_SAMPLE_DEPTH_ADPCM_B),
"Yamaha YMU759 (MA-2)", NULL, 0x01, 0x01, 17, true, false, 0, false, (1U<<DIV_SAMPLE_DEPTH_YMZ_ADPCM)|(1U<<DIV_SAMPLE_DEPTH_ADPCM_B), 0, 0,
"a chip which found its way inside mobile phones in the 2000's.\nas proprietary as it is, it passed away after losing to MP3 in the mobile hardware battle.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "PCM" }, // name
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "PCM" }, // short
@ -627,19 +628,19 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_GENESIS]=new DivSysDef(
"Sega Genesis/Mega Drive", "セガメガドライブ", 0x02, 0x02, 10, true, true, 0, true, 0,
"Sega Genesis/Mega Drive", "セガメガドライブ", 0x02, 0x02, 10, true, true, 0, true, 0, 0, 0,
"<COMPOUND SYSTEM!>",
{}, {}, {}, {}
);
sysDefs[DIV_SYSTEM_GENESIS_EXT]=new DivSysDef(
"Sega Genesis Extended Channel 3", NULL, 0x42, 0x42, 13, true, true, 0, true, 0,
"Sega Genesis Extended Channel 3", NULL, 0x42, 0x42, 13, true, true, 0, true, 0, 0, 0,
"<COMPOUND SYSTEM!>",
{}, {}, {}, {}
);
sysDefs[DIV_SYSTEM_SMS]=new DivSysDef(
"TI SN76489", NULL, 0x03, 0x03, 4, false, true, 0x150, false, 0,
"TI SN76489", NULL, 0x03, 0x03, 4, false, true, 0x150, false, 0, 0, 0,
"a square/noise sound chip found on the Sega Master System, ColecoVision, Tandy, TI's own 99/4A and a few other places.",
{"Square 1", "Square 2", "Square 3", "Noise"},
{"S1", "S2", "S3", "NO"},
@ -652,13 +653,13 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_SMS_OPLL]=new DivSysDef(
"Sega Master System + FM Expansion", NULL, 0x43, 0x43, 13, true, true, 0, true, 0,
"Sega Master System + FM Expansion", NULL, 0x43, 0x43, 13, true, true, 0, true, 0, 0, 0,
"<COMPOUND SYSTEM!>",
{}, {}, {}, {}
);
sysDefs[DIV_SYSTEM_GB]=new DivSysDef(
"Game Boy", NULL, 0x04, 0x04, 4, false, true, 0x161, false, 0,
"Game Boy", NULL, 0x04, 0x04, 4, false, true, 0x161, false, 0, 32, 16,
"the most popular portable game console of the era.",
{"Pulse 1", "Pulse 2", "Wavetable", "Noise"},
{"S1", "S2", "WA", "NO"},
@ -675,7 +676,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_PCE]=new DivSysDef(
"PC Engine/TurboGrafx-16", NULL, 0x05, 0x05, 6, false, true, 0x161, false, 1U<<DIV_SAMPLE_DEPTH_8BIT,
"PC Engine/TurboGrafx-16", NULL, 0x05, 0x05, 6, false, true, 0x161, false, 1U<<DIV_SAMPLE_DEPTH_8BIT, 32, 32,
"an '80s game console with a wavetable sound chip, popular in Japan.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6"},
{"CH1", "CH2", "CH3", "CH4", "CH5", "CH6"},
@ -692,7 +693,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_NES]=new DivSysDef(
"NES (Ricoh 2A03)", NULL, 0x06, 0x06, 5, false, true, 0x161, false, (1U<<DIV_SAMPLE_DEPTH_1BIT_DPCM)|(1U<<DIV_SAMPLE_DEPTH_8BIT),
"NES (Ricoh 2A03)", NULL, 0x06, 0x06, 5, false, true, 0x161, false, (1U<<DIV_SAMPLE_DEPTH_1BIT_DPCM)|(1U<<DIV_SAMPLE_DEPTH_8BIT), 0, 0,
"also known as Famicom in Japan, it's the most well-known game console of the '80s.",
{"Pulse 1", "Pulse 2", "Triangle", "Noise", "DPCM"},
{"S1", "S2", "TR", "NO", "DMC"},
@ -714,19 +715,19 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_NES_VRC7]=new DivSysDef(
"NES + Konami VRC7", NULL, 0x46, 0x46, 11, true, true, 0, true, 0,
"NES + Konami VRC7", NULL, 0x46, 0x46, 11, true, true, 0, true, 0, 0, 0,
"<COMPOUND SYSTEM!>",
{}, {}, {}, {}
);
sysDefs[DIV_SYSTEM_NES_FDS]=new DivSysDef(
"Famicom Disk System", NULL, 0, 0x86, 6, false, true, 0, true, 0,
"Famicom Disk System", NULL, 0, 0x86, 6, false, true, 0, true, 0, 0, 0,
"<COMPOUND SYSTEM!>",
{}, {}, {}, {}
);
sysDefs[DIV_SYSTEM_C64_6581]=new DivSysDef(
"Commodore 64 (6581)", NULL, 0x47, 0x47, 3, false, true, 0, false, 0,
"Commodore 64 (6581)", NULL, 0x47, 0x47, 3, false, true, 0, false, 0, 0, 0,
"this computer is powered by the SID chip, which had synthesizer features like a filter and ADSR.",
{"Channel 1", "Channel 2", "Channel 3"},
{"CH1", "CH2", "CH3"},
@ -738,7 +739,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_C64_8580]=new DivSysDef(
"Commodore 64 (8580)", NULL, 0x07, 0x07, 3, false, true, 0, false, 0,
"Commodore 64 (8580)", NULL, 0x07, 0x07, 3, false, true, 0, false, 0, 0, 0,
"this computer is powered by the SID chip, which had synthesizer features like a filter and ADSR.\nthis is the newer revision of the chip.",
{"Channel 1", "Channel 2", "Channel 3"},
{"CH1", "CH2", "CH3"},
@ -750,13 +751,13 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_ARCADE]=new DivSysDef(
"DefleCade", NULL, 0x08, 0x08, 13, true, false, 0, true, 0,
"DefleCade", NULL, 0x08, 0x08, 13, true, false, 0, true, 0, 0, 0,
"<COMPOUND SYSTEM!>",
{}, {}, {}, {}
);
sysDefs[DIV_SYSTEM_YM2610]=new DivSysDef(
"Neo Geo CD", NULL, 0x09, 0x09, 13, true, true, 0x151, false, (1U<<DIV_SAMPLE_DEPTH_ADPCM_A)|(1U<<DIV_SAMPLE_DEPTH_8BIT),
"Neo Geo CD", NULL, 0x09, 0x09, 13, true, true, 0x151, false, (1U<<DIV_SAMPLE_DEPTH_ADPCM_A)|(1U<<DIV_SAMPLE_DEPTH_8BIT), 0, 0,
"like Neo Geo, but lacking the ADPCM-B channel since they couldn't connect the pins.",
{"FM 1", "FM 2", "FM 3", "FM 4", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6"},
{"F1", "F2", "F3", "F4", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6"},
@ -768,7 +769,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_YM2610_EXT]=new DivSysDef(
"Neo Geo CD Extended Channel 2", NULL, 0x49, 0x49, 16, true, true, 0x151, false, (1U<<DIV_SAMPLE_DEPTH_ADPCM_A)|(1U<<DIV_SAMPLE_DEPTH_8BIT),
"Neo Geo CD Extended Channel 2", NULL, 0x49, 0x49, 16, true, true, 0x151, false, (1U<<DIV_SAMPLE_DEPTH_ADPCM_A)|(1U<<DIV_SAMPLE_DEPTH_8BIT), 0, 0,
"like Neo Geo, but lacking the ADPCM-B channel since they couldn't connect the pins.\nthis one is in Extended Channel mode, which turns the second FM channel into four operators with independent notes/frequencies.",
{"FM 1", "FM 2 OP1", "FM 2 OP2", "FM 2 OP3", "FM 2 OP4", "FM 3", "FM 4", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6"},
{"F1", "O1", "O2", "O3", "O4", "F3", "F4", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6"},
@ -781,7 +782,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_AY8910]=new DivSysDef(
"AY-3-8910", NULL, 0x80, 0, 3, false, true, 0x151, false, 1U<<DIV_SAMPLE_DEPTH_8BIT,
"AY-3-8910", NULL, 0x80, 0, 3, false, true, 0x151, false, 1U<<DIV_SAMPLE_DEPTH_8BIT, 0, 0,
"this chip is everywhere! ZX Spectrum, MSX, Amstrad CPC, Intellivision, Vectrex...\nthe discovery of envelope bass helped it beat the SN76489 with ease.",
{"PSG 1", "PSG 2", "PSG 3"},
{"S1", "S2", "S3"},
@ -793,7 +794,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_AMIGA]=new DivSysDef(
"Amiga", NULL, 0x81, 0, 4, false, true, 0, false, 1U<<DIV_SAMPLE_DEPTH_8BIT,
"Amiga", NULL, 0x81, 0, 4, false, true, 0, false, 1U<<DIV_SAMPLE_DEPTH_8BIT, 0, 256,
"a computer from the '80s with full sampling capabilities, giving it a sound ahead of its time.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4"},
{"CH1", "CH2", "CH3", "CH4"},
@ -810,7 +811,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_YM2151]=new DivSysDef(
"Yamaha YM2151 (OPM)", NULL, 0x82, 0, 8, true, false, 0x150, false, 0,
"Yamaha YM2151 (OPM)", NULL, 0x82, 0, 8, true, false, 0x150, false, 0, 0, 0,
"this was Yamaha's first integrated FM chip.\nit was used in several synthesizers, computers and arcade boards.",
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8"},
{"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8"},
@ -822,7 +823,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_YM2612]=new DivSysDef(
"Yamaha YM2612 (OPN2)", NULL, 0x83, 0, 6, true, false, 0x150, false, 1U<<DIV_SAMPLE_DEPTH_8BIT,
"Yamaha YM2612 (OPN2)", NULL, 0x83, 0, 6, true, false, 0x150, false, 1U<<DIV_SAMPLE_DEPTH_8BIT, 0, 0,
"this chip is mostly known for being in the Sega Genesis (but it also was on the FM Towns computer).",
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6"},
{"F1", "F2", "F3", "F4", "F5", "F6"},
@ -834,7 +835,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_TIA]=new DivSysDef(
"Atari 2600", NULL, 0x84, 0, 2, false, true, 0, false, 0,
"Atari 2600", NULL, 0x84, 0, 2, false, true, 0, false, 0, 0, 0,
"it's a challenge to make music on this chip which barely has musical capabilities...",
{"Channel 1", "Channel 2"},
{"CH1", "CH2"},
@ -846,7 +847,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_SAA1099]=new DivSysDef(
"Philips SAA1099", NULL, 0x97, 0, 6, false, true, 0x171, false, 0,
"Philips SAA1099", NULL, 0x97, 0, 6, false, true, 0x171, false, 0, 0, 0,
"supposedly an upgrade from the AY-3-8910, this was present on the Creative Music System (Game Blaster) and SAM Coupé.",
{"PSG 1", "PSG 2", "PSG 3", "PSG 4", "PSG 5", "PSG 6"},
{"S1", "S2", "S3", "S4", "S5", "S6"},
@ -862,7 +863,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_AY8930]=new DivSysDef(
"Microchip AY8930", NULL, 0x9a, 0, 3, false, true, 0x151, false, 1U<<DIV_SAMPLE_DEPTH_8BIT,
"Microchip AY8930", NULL, 0x9a, 0, 3, false, true, 0x151, false, 1U<<DIV_SAMPLE_DEPTH_8BIT, 0, 0,
"an improved version of the AY-3-8910 with a bigger frequency range, duty cycles, configurable noise and per-channel envelopes!",
{"PSG 1", "PSG 2", "PSG 3"},
{"S1", "S2", "S3"},
@ -874,7 +875,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_VIC20]=new DivSysDef(
"Commodore VIC-20", NULL, 0x85, 0, 4, false, true, 0, false, 0,
"Commodore VIC-20", NULL, 0x85, 0, 4, false, true, 0, false, 0, 0, 0,
"Commodore's successor to the PET.\nits square wave channels are more than just square...",
{"Low", "Mid", "High", "Noise"},
{"LO", "MID", "HI", "NO"},
@ -885,7 +886,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_PET]=new DivSysDef(
"Commodore PET", NULL, 0x86, 0, 1, false, true, 0, false, 0,
"Commodore PET", NULL, 0x86, 0, 1, false, true, 0, false, 0, 0, 0,
"one channel of 1-bit wavetable which is better (and worse) than the PC Speaker.",
{"Wave"},
{"PET"},
@ -896,7 +897,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_SNES]=new DivSysDef(
"SNES", NULL, 0x87, 0, 8, false, true, 0, false, 1U<<DIV_SAMPLE_DEPTH_BRR,
"SNES", NULL, 0x87, 0, 8, false, true, 0, false, 1U<<DIV_SAMPLE_DEPTH_BRR, 0, 16,
"FM? nah... samples! Nintendo's answer to Sega.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8"},
{"CH1", "CH2", "CH3", "CH4", "CH5", "CH6", "CH7", "CH8"},
@ -937,7 +938,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_VRC6]=new DivSysDef(
"Konami VRC6", NULL, 0x88, 0, 3, false, true, 0, false, 1U<<DIV_SAMPLE_DEPTH_8BIT,
"Konami VRC6", NULL, 0x88, 0, 3, false, true, 0, false, 1U<<DIV_SAMPLE_DEPTH_8BIT, 0, 0,
"an expansion chip for the Famicom, featuring a quirky sawtooth channel.",
{"VRC6 1", "VRC6 2", "VRC6 Saw"},
{"V1", "V2", "VS"},
@ -951,7 +952,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_OPLL]=new DivSysDef(
"Yamaha YM2413 (OPLL)", NULL, 0x89, 0, 9, true, false, 0x150, false, 0,
"Yamaha YM2413 (OPLL)", NULL, 0x89, 0, 9, true, false, 0x150, false, 0, 0, 0,
"cost-reduced version of the OPL with 16 patches and only one of them is user-configurable.",
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8", "FM 9"},
{"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9"},
@ -963,7 +964,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_FDS]=new DivSysDef(
"Famicom Disk System (chip)", NULL, 0x8a, 0, 1, false, true, 0x161, false, 0,
"Famicom Disk System (chip)", NULL, 0x8a, 0, 1, false, true, 0x161, false, 0, 64, 64,
"a disk drive for the Famicom which also contains one wavetable channel.",
{"FDS"},
{"FDS"},
@ -981,7 +982,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_MMC5]=new DivSysDef(
"MMC5", NULL, 0x8b, 0, 3, false, true, 0, false, 1U<<DIV_SAMPLE_DEPTH_8BIT,
"MMC5", NULL, 0x8b, 0, 3, false, true, 0, false, 1U<<DIV_SAMPLE_DEPTH_8BIT, 0, 0,
"an expansion chip for the Famicom, featuring a little-known PCM channel.",
{"Pulse 1", "Pulse 2", "PCM"},
{"S1", "S2", "PCM"},
@ -994,7 +995,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_N163]=new DivSysDef(
"Namco 163", NULL, 0x8c, 0, 8, false, true, 0, false, 0,
"Namco 163", NULL, 0x8c, 0, 8, false, true, 0, false, 0, 0, 16,
"an expansion chip for the Famicom, with full wavetable.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8"},
{"CH1", "CH2", "CH3", "CH4", "CH5", "CH6", "CH7", "CH8"},
@ -1016,7 +1017,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_YM2203]=new DivSysDef(
"Yamaha YM2203 (OPN)", NULL, 0x8d, 0, 6, true, true, 0x151, false, 1U<<DIV_SAMPLE_DEPTH_8BIT,
"Yamaha YM2203 (OPN)", NULL, 0x8d, 0, 6, true, true, 0x151, false, 1U<<DIV_SAMPLE_DEPTH_8BIT, 0, 0,
"cost-reduced version of the OPM with a different register layout and no stereo...\n...but it has a built-in AY-3-8910! (actually an YM2149)",
{"FM 1", "FM 2", "FM 3", "PSG 1", "PSG 2", "PSG 3"},
{"F1", "F2", "F3", "S1", "S2", "S3"},
@ -1028,7 +1029,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_YM2203_EXT]=new DivSysDef(
"Yamaha YM2203 (OPN) Extended Channel 3", NULL, 0xb6, 0, 9, true, true, 0x151, false, 1U<<DIV_SAMPLE_DEPTH_8BIT,
"Yamaha YM2203 (OPN) Extended Channel 3", NULL, 0xb6, 0, 9, true, true, 0x151, false, 1U<<DIV_SAMPLE_DEPTH_8BIT, 0, 0,
"cost-reduced version of the OPM with a different register layout and no stereo...\n...but it has a built-in AY-3-8910! (actually an YM2149)\nthis one is in Extended Channel mode, which turns the third FM channel into four operators with independent notes/frequencies",
{"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "PSG 1", "PSG 2", "PSG 3"},
{"F1", "F2", "O1", "O2", "O3", "O4", "S1", "S2", "S3"},
@ -1041,7 +1042,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_YM2203_CSM]=new DivSysDef(
"Yamaha YM2203 (OPN) CSM", NULL, 0xc3, 0, 10, true, true, 0x151, false, 1U<<DIV_SAMPLE_DEPTH_8BIT,
"Yamaha YM2203 (OPN) CSM", NULL, 0xc3, 0, 10, true, true, 0x151, false, 1U<<DIV_SAMPLE_DEPTH_8BIT, 0, 0,
"cost-reduced version of the OPM with a different register layout and no stereo...\n...but it has a built-in AY-3-8910! (actually an YM2149)\nCSM blah blah",
{"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "CSM Timer", "PSG 1", "PSG 2", "PSG 3"},
{"F1", "F2", "O1", "O2", "O3", "O4", "CSM", "S1", "S2", "S3"},
@ -1054,7 +1055,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_YM2608]=new DivSysDef(
"Yamaha YM2608 (OPNA)", NULL, 0x8e, 0, 16, true, true, 0x151, false, (1U<<DIV_SAMPLE_DEPTH_ADPCM_B)|(1U<<DIV_SAMPLE_DEPTH_8BIT),
"Yamaha YM2608 (OPNA)", NULL, 0x8e, 0, 16, true, true, 0x151, false, (1U<<DIV_SAMPLE_DEPTH_ADPCM_B)|(1U<<DIV_SAMPLE_DEPTH_8BIT), 0, 0,
"OPN but twice the FM channels, stereo makes a come-back and has rhythm and ADPCM channels.",
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Square 1", "Square 2", "Square 3", "Kick", "Snare", "Top", "HiHat", "Tom", "Rim", "ADPCM"},
{"F1", "F2", "F3", "F4", "F5", "F6", "S1", "S2", "S3", "BD", "SD", "TP", "HH", "TM", "RM", "P"},
@ -1066,7 +1067,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_YM2608_EXT]=new DivSysDef(
"Yamaha YM2608 (OPNA) Extended Channel 3", NULL, 0xb7, 0, 19, true, true, 0x151, false, (1U<<DIV_SAMPLE_DEPTH_ADPCM_B)|(1U<<DIV_SAMPLE_DEPTH_8BIT),
"Yamaha YM2608 (OPNA) Extended Channel 3", NULL, 0xb7, 0, 19, true, true, 0x151, false, (1U<<DIV_SAMPLE_DEPTH_ADPCM_B)|(1U<<DIV_SAMPLE_DEPTH_8BIT), 0, 0,
"OPN but twice the FM channels, stereo makes a come-back and has rhythm and ADPCM channels.\nthis one is in Extended Channel mode, which turns the third FM channel into four operators with independent notes/frequencies",
{"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6", "Square 1", "Square 2", "Square 3", "Kick", "Snare", "Top", "HiHat", "Tom", "Rim", "ADPCM"},
{"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6", "S1", "S2", "S3", "BD", "SD", "TP", "HH", "TM", "RM", "P"},
@ -1079,7 +1080,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_YM2608_CSM]=new DivSysDef(
"Yamaha YM2608 (OPNA) CSM", NULL, 0xc4, 0, 20, true, true, 0x151, false, (1U<<DIV_SAMPLE_DEPTH_ADPCM_B)|(1U<<DIV_SAMPLE_DEPTH_8BIT),
"Yamaha YM2608 (OPNA) CSM", NULL, 0xc4, 0, 20, true, true, 0x151, false, (1U<<DIV_SAMPLE_DEPTH_ADPCM_B)|(1U<<DIV_SAMPLE_DEPTH_8BIT), 0, 0,
"OPN but twice the FM channels, stereo makes a come-back and has rhythm and ADPCM channels.\nCSM blah blah",
{"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6", "CSM Timer", "Square 1", "Square 2", "Square 3", "Kick", "Snare", "Top", "HiHat", "Tom", "Rim", "ADPCM"},
{"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6", "CSM", "S1", "S2", "S3", "BD", "SD", "TP", "HH", "TM", "RM", "P"},
@ -1092,7 +1093,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_OPL]=new DivSysDef(
"Yamaha YM3526 (OPL)", NULL, 0x8f, 0, 9, true, false, 0x151, false, 0,
"Yamaha YM3526 (OPL)", NULL, 0x8f, 0, 9, true, false, 0x151, false, 0, 0, 0,
"OPN, but what if you only had two operators, no stereo, no detune and a lower ADSR parameter range?",
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8", "FM 9"},
{"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9"},
@ -1104,7 +1105,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_OPL2]=new DivSysDef(
"Yamaha YM3812 (OPL2)", NULL, 0x90, 0, 9, true, false, 0x151, false, 0,
"Yamaha YM3812 (OPL2)", NULL, 0x90, 0, 9, true, false, 0x151, false, 0, 0, 0,
"OPL, but what if you had more waveforms to choose than the normal sine?",
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8", "FM 9"},
{"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9"},
@ -1116,7 +1117,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_OPL3]=new DivSysDef(
"Yamaha YMF262 (OPL3)", NULL, 0x91, 0, 18, true, false, 0x151, false, 0,
"Yamaha YMF262 (OPL3)", NULL, 0x91, 0, 18, true, false, 0x151, false, 0, 0, 0,
"OPL2, but what if you had twice the channels, 4-op mode, stereo and even more waveforms?",
{"4OP 1", "FM 2", "4OP 3", "FM 4", "4OP 5", "FM 6", "4OP 7", "FM 8", "4OP 9", "FM 10", "4OP 11", "FM 12", "FM 13", "FM 14", "FM 15", "FM 16", "FM 17", "FM 18"},
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18"},
@ -1129,7 +1130,7 @@ void DivEngine::registerSystems() {
// TODO: add 12-bit and 16-bit big endian formats
sysDefs[DIV_SYSTEM_MULTIPCM]=new DivSysDef(
"MultiPCM", NULL, 0x92, 0, 28, false, true, 0, false, (1U<<DIV_SAMPLE_DEPTH_8BIT)|(1U<<DIV_SAMPLE_DEPTH_16BIT),
"MultiPCM", NULL, 0x92, 0, 28, false, true, 0, false, (1U<<DIV_SAMPLE_DEPTH_8BIT)|(1U<<DIV_SAMPLE_DEPTH_16BIT), 0, 0,
"how many channels of PCM do you want?\nMultiPCM: yes",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "Channel 17", "Channel 18", "Channel 19", "Channel 20", "Channel 21", "Channel 22", "Channel 23", "Channel 24", "Channel 25", "Channel 26", "Channel 27", "Channel 28"},
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28"},
@ -1138,7 +1139,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_PCSPKR]=new DivSysDef(
"PC Speaker", NULL, 0x93, 0, 1, false, true, 0, false, 0,
"PC Speaker", NULL, 0x93, 0, 1, false, true, 0, false, 0, 0, 0,
"good luck! you get one square and no volume control.",
{"Square"},
{"SQ"},
@ -1147,7 +1148,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_PONG]=new DivSysDef(
"Pong", NULL, 0xfc, 0, 1, false, true, 0, false, 0,
"Pong", NULL, 0xfc, 0, 1, false, true, 0, false, 0, 0, 0,
"please don't use this chip. it was added as a joke.",
{"Square"},
{"SQ"},
@ -1156,7 +1157,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_POKEY]=new DivSysDef(
"POKEY", NULL, 0x94, 0, 4, false, true, 0x161, false, 0,
"POKEY", NULL, 0x94, 0, 4, false, true, 0x161, false, 0, 0, 0,
"TIA, but better and more flexible.\nused in the Atari 8-bit family of computers (400/800/XL/XE).",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4"},
{"CH1", "CH2", "CH3", "CH4"},
@ -1171,7 +1172,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_RF5C68]=new DivSysDef(
"Ricoh RF5C68", NULL, 0x95, 0, 8, false, true, 0x151, false, 1U<<DIV_SAMPLE_DEPTH_8BIT,
"Ricoh RF5C68", NULL, 0x95, 0, 8, false, true, 0x151, false, 1U<<DIV_SAMPLE_DEPTH_8BIT, 0, 0,
"this is like SNES' sound chip but without interpolation and the rest of nice bits.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8"},
{"CH1", "CH2", "CH3", "CH4", "CH5", "CH6", "CH7", "CH8"},
@ -1181,7 +1182,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_SWAN]=new DivSysDef(
"WonderSwan", NULL, 0x96, 0, 4, false, true, 0x171, false, 1U<<DIV_SAMPLE_DEPTH_8BIT,
"WonderSwan", NULL, 0x96, 0, 4, false, true, 0x171, false, 1U<<DIV_SAMPLE_DEPTH_8BIT, 32, 16,
"developed by the makers of the Game Boy and the Virtual Boy...",
{"Wave", "Wave/PCM", "Wave/Sweep", "Wave/Noise"},
{"CH1", "CH2", "CH3", "CH4"},
@ -1198,7 +1199,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_OPZ]=new DivSysDef(
"Yamaha YM2414 (OPZ)", NULL, 0x98, 0, 8, true, false, 0, false, 0,
"Yamaha YM2414 (OPZ)", NULL, 0x98, 0, 8, true, false, 0, false, 0, 0, 0,
"like OPM, but with more waveforms, fixed frequency mode and totally... undocumented.\nused in the Yamaha TX81Z and some other synthesizers.",
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8"},
{"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8"},
@ -1212,7 +1213,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_POKEMINI]=new DivSysDef(
"Pokémon Mini", NULL, 0x99, 0, 1, false, true, 0, false, 0,
"Pokémon Mini", NULL, 0x99, 0, 1, false, true, 0, false, 0, 0, 0,
"this one is like PC Speaker but has duty cycles.",
{"Pulse"},
{"P"},
@ -1221,7 +1222,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_SEGAPCM]=new DivSysDef(
"SegaPCM", NULL, 0x9b, 0, 16, false, true, 0x151, false, 1U<<DIV_SAMPLE_DEPTH_8BIT,
"SegaPCM", NULL, 0x9b, 0, 16, false, true, 0x151, false, 1U<<DIV_SAMPLE_DEPTH_8BIT, 0, 0,
"used in some Sega arcade boards (like OutRun), and usually paired with a YM2151.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16"},
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16"},
@ -1233,7 +1234,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_VBOY]=new DivSysDef(
"Virtual Boy", NULL, 0x9c, 0, 6, false, true, 0x171, false, 1U<<DIV_SAMPLE_DEPTH_8BIT,
"Virtual Boy", NULL, 0x9c, 0, 6, false, true, 0x171, false, 1U<<DIV_SAMPLE_DEPTH_8BIT, 32, 64,
"a console which failed to sell well due to its headache-inducing features.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Noise"},
{"CH1", "CH2", "CH3", "CH4", "CH5", "NO"},
@ -1251,7 +1252,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_VRC7]=new DivSysDef(
"Konami VRC7", NULL, 0x9d, 0, 6, true, false, 0x151, false, 0,
"Konami VRC7", NULL, 0x9d, 0, 6, true, false, 0x151, false, 0, 0, 0,
"like OPLL, but even more cost reductions applied. three less FM channels, and no drums mode...",
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6"},
{"F1", "F2", "F3", "F4", "F5", "F6"},
@ -1263,7 +1264,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_YM2610B]=new DivSysDef(
"Yamaha YM2610B (OPNB2)", NULL, 0x9e, 0, 16, true, false, 0x151, false, (1U<<DIV_SAMPLE_DEPTH_ADPCM_A)|(1U<<DIV_SAMPLE_DEPTH_ADPCM_B)|(1U<<DIV_SAMPLE_DEPTH_8BIT),
"Yamaha YM2610B (OPNB2)", NULL, 0x9e, 0, 16, true, false, 0x151, false, (1U<<DIV_SAMPLE_DEPTH_ADPCM_A)|(1U<<DIV_SAMPLE_DEPTH_ADPCM_B)|(1U<<DIV_SAMPLE_DEPTH_8BIT), 0, 0,
"so Taito asked Yamaha if they could get the two missing FM channels back, and Yamaha gladly provided them with this chip.",
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6", "ADPCM-B"},
{"F1", "F2", "F3", "F4", "F5", "F6", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"},
@ -1275,7 +1276,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_SFX_BEEPER]=new DivSysDef(
"ZX Spectrum Beeper", NULL, 0x9f, 0, 6, false, true, 0, false, 1U<<DIV_SAMPLE_DEPTH_8BIT,
"ZX Spectrum Beeper", NULL, 0x9f, 0, 6, false, true, 0, false, 1U<<DIV_SAMPLE_DEPTH_8BIT, 0, 0,
"the ZX Spectrum only had a basic beeper capable of...\n...a bunch of thin pulses and tons of other interesting stuff!\nFurnace provides a thin pulse system.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6"},
{"CH1", "CH2", "CH3", "CH4", "CH5", "CH6"},
@ -1289,7 +1290,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_YM2612_EXT]=new DivSysDef(
"Yamaha YM2612 (OPN2) Extended Channel 3", NULL, 0xa0, 0, 9, true, false, 0x150, false, 1U<<DIV_SAMPLE_DEPTH_8BIT,
"Yamaha YM2612 (OPN2) Extended Channel 3", NULL, 0xa0, 0, 9, true, false, 0x150, false, 1U<<DIV_SAMPLE_DEPTH_8BIT, 0, 0,
"this chip is mostly known for being in the Sega Genesis (but it also was on the FM Towns computer).\nthis one is in Extended Channel mode, which turns the third FM channel into four operators with independent notes/frequencies.",
{"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6"},
{"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6"},
@ -1302,7 +1303,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_YM2612_CSM]=new DivSysDef(
"Yamaha YM2612 (OPN2) CSM", NULL, 0xc1, 0, 10, true, false, 0x150, false, 1U<<DIV_SAMPLE_DEPTH_8BIT,
"Yamaha YM2612 (OPN2) CSM", NULL, 0xc1, 0, 10, true, false, 0x150, false, 1U<<DIV_SAMPLE_DEPTH_8BIT, 0, 0,
"this chip is mostly known for being in the Sega Genesis (but it also was on the FM Towns computer).\nthis one includes CSM mode control for special effects on Channel 3.",
{"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6", "CSM Timer"},
{"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6", "CSM"},
@ -1315,7 +1316,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_SCC]=new DivSysDef(
"Konami SCC", NULL, 0xa1, 0, 5, false, true, 0x161, false, 0,
"Konami SCC", NULL, 0xa1, 0, 5, false, true, 0x161, false, 0, 32, 256,
"a wavetable chip made by Konami for use with the MSX.\nthe last channel shares its wavetable with the previous one though.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5"},
{"CH1", "CH2", "CH3", "CH4", "CH5"},
@ -1326,7 +1327,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_OPL_DRUMS]=new DivSysDef(
"Yamaha YM3526 (OPL) with drums", NULL, 0xa2, 0, 11, true, false, 0x151, false, 0,
"Yamaha YM3526 (OPL) with drums", NULL, 0xa2, 0, 11, true, false, 0x151, false, 0, 0, 0,
"the OPL chip but with drums mode enabled.",
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Kick/FM 7", "Snare", "Tom", "Top", "HiHat"},
{"F1", "F2", "F3", "F4", "F5", "F6", "BD", "SD", "TM", "TP", "HH"},
@ -1338,7 +1339,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_OPL2_DRUMS]=new DivSysDef(
"Yamaha YM3812 (OPL2) with drums", NULL, 0xa3, 0, 11, true, false, 0x151, false, 0,
"Yamaha YM3812 (OPL2) with drums", NULL, 0xa3, 0, 11, true, false, 0x151, false, 0, 0, 0,
"the OPL2 chip but with drums mode enabled.",
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Kick/FM 7", "Snare", "Tom", "Top", "HiHat"},
{"F1", "F2", "F3", "F4", "F5", "F6", "BD", "SD", "TM", "TP", "HH"},
@ -1350,7 +1351,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_OPL3_DRUMS]=new DivSysDef(
"Yamaha YMF262 (OPL3) with drums", NULL, 0xa4, 0, 20, true, false, 0x151, false, 0,
"Yamaha YMF262 (OPL3) with drums", NULL, 0xa4, 0, 20, true, false, 0x151, false, 0, 0, 0,
"the OPL3 chip but with drums mode enabled.",
{"4OP 1", "FM 2", "4OP 3", "FM 4", "4OP 5", "FM 6", "4OP 7", "FM 8", "4OP 9", "FM 10", "4OP 11", "FM 12", "FM 13", "FM 14", "FM 15", "Kick/FM 16", "Snare", "Tom", "Top", "HiHat"},
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "BD", "SD", "TM", "TP", "HH"},
@ -1362,7 +1363,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_YM2610_FULL]=new DivSysDef(
"Yamaha YM2610 (OPNB)", NULL, 0xa5, 0, 14, true, false, 0x151, false, (1U<<DIV_SAMPLE_DEPTH_ADPCM_A)|(1U<<DIV_SAMPLE_DEPTH_ADPCM_B)|(1U<<DIV_SAMPLE_DEPTH_8BIT),
"Yamaha YM2610 (OPNB)", NULL, 0xa5, 0, 14, true, false, 0x151, false, (1U<<DIV_SAMPLE_DEPTH_ADPCM_A)|(1U<<DIV_SAMPLE_DEPTH_ADPCM_B)|(1U<<DIV_SAMPLE_DEPTH_8BIT), 0, 0,
"this chip was used in SNK's Neo Geo arcade board and video game console.\nit's like OPNA but the rhythm channels are ADPCM channels and two FM channels went missing.",
{"FM 1", "FM 2", "FM 3", "FM 4", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6", "ADPCM-B"},
{"F1", "F2", "F3", "F4", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"},
@ -1374,7 +1375,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_YM2610_FULL_EXT]=new DivSysDef(
"Yamaha YM2610 (OPNB) Extended Channel 2", NULL, 0xa6, 0, 17, true, false, 0x151, false, (1U<<DIV_SAMPLE_DEPTH_ADPCM_A)|(1U<<DIV_SAMPLE_DEPTH_ADPCM_B)|(1U<<DIV_SAMPLE_DEPTH_8BIT),
"Yamaha YM2610 (OPNB) Extended Channel 2", NULL, 0xa6, 0, 17, true, false, 0x151, false, (1U<<DIV_SAMPLE_DEPTH_ADPCM_A)|(1U<<DIV_SAMPLE_DEPTH_ADPCM_B)|(1U<<DIV_SAMPLE_DEPTH_8BIT), 0, 0,
"this chip was used in SNK's Neo Geo arcade board and video game console.\nit's like OPNA but the rhythm channels are ADPCM channels and two FM channels went missing.\nthis one is in Extended Channel mode, which turns the second FM channel into four operators with independent notes/frequencies.",
{"FM 1", "FM 2 OP1", "FM 2 OP2", "FM 2 OP3", "FM 2 OP4", "FM 3", "FM 4", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6", "ADPCM-B"},
{"F1", "O1", "O2", "O3", "O4", "F3", "F4", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"},
@ -1387,7 +1388,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_YM2610_CSM]=new DivSysDef(
"Yamaha YM2610 (OPNB) CSM", NULL, 0xc2, 0, 18, true, false, 0x151, false, (1U<<DIV_SAMPLE_DEPTH_ADPCM_A)|(1U<<DIV_SAMPLE_DEPTH_ADPCM_B)|(1U<<DIV_SAMPLE_DEPTH_8BIT),
"Yamaha YM2610 (OPNB) CSM", NULL, 0xc2, 0, 18, true, false, 0x151, false, (1U<<DIV_SAMPLE_DEPTH_ADPCM_A)|(1U<<DIV_SAMPLE_DEPTH_ADPCM_B)|(1U<<DIV_SAMPLE_DEPTH_8BIT), 0, 0,
"this chip was used in SNK's Neo Geo arcade board and video game console.\nit's like OPNA but the rhythm channels are ADPCM channels and two FM channels went missing.\nthis one includes CSM mode control for special effects on Channel 2.",
{"FM 1", "FM 2 OP1", "FM 2 OP2", "FM 2 OP3", "FM 2 OP4", "FM 3", "FM 4", "CSM Timer", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6", "ADPCM-B"},
{"F1", "O1", "O2", "O3", "O4", "F3", "F4", "CSM", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"},
@ -1400,7 +1401,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_OPLL_DRUMS]=new DivSysDef(
"Yamaha YM2413 (OPLL) with drums", NULL, 0xa7, 0, 11, true, false, 0x150, false, 0,
"Yamaha YM2413 (OPLL) with drums", NULL, 0xa7, 0, 11, true, false, 0x150, false, 0, 0, 0,
"the OPLL chips but with drums mode turned on.",
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Kick", "Snare", "Tom", "Top", "HiHat"},
{"F1", "F2", "F3", "F4", "F5", "F6", "BD", "SD", "TM", "TP", "HH"},
@ -1418,7 +1419,7 @@ void DivEngine::registerSystems() {
}
sysDefs[DIV_SYSTEM_LYNX]=new DivSysDef(
"Atari Lynx", NULL, 0xa8, 0, 4, false, true, 0x172, false, 1U<<DIV_SAMPLE_DEPTH_8BIT,
"Atari Lynx", NULL, 0xa8, 0, 4, false, true, 0x172, false, 1U<<DIV_SAMPLE_DEPTH_8BIT, 0, 0,
"a portable console made by Atari. it has all of Atari's trademark waveforms.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4"},
{"CH1", "CH2", "CH3", "CH4"},
@ -1440,7 +1441,7 @@ void DivEngine::registerSystems() {
}
sysDefs[DIV_SYSTEM_QSOUND]=new DivSysDef(
"Capcom QSound", NULL, 0xe0, 0, 19, false, true, 0x161, false, (1U<<DIV_SAMPLE_DEPTH_QSOUND_ADPCM)|(1U<<DIV_SAMPLE_DEPTH_8BIT),
"Capcom QSound", NULL, 0xe0, 0, 19, false, true, 0x161, false, (1U<<DIV_SAMPLE_DEPTH_QSOUND_ADPCM)|(1U<<DIV_SAMPLE_DEPTH_8BIT), 0, 0,
"used in some of Capcom's arcade boards. surround-like sampled sound with echo.",
{"PCM 1", "PCM 2", "PCM 3", "PCM 4", "PCM 5", "PCM 6", "PCM 7", "PCM 8", "PCM 9", "PCM 10", "PCM 11", "PCM 12", "PCM 13", "PCM 14", "PCM 15", "PCM 16", "ADPCM 1", "ADPCM 2", "ADPCM 3"},
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "A1", "A2", "A3"},
@ -1451,7 +1452,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_VERA]=new DivSysDef(
"VERA", NULL, 0xac, 0, 17, false, true, 0, false, (1U<<DIV_SAMPLE_DEPTH_8BIT)|(1U<<DIV_SAMPLE_DEPTH_16BIT),
"VERA", NULL, 0xac, 0, 17, false, true, 0, false, (1U<<DIV_SAMPLE_DEPTH_8BIT)|(1U<<DIV_SAMPLE_DEPTH_16BIT), 0, 0,
"the chip used in a computer design created by The 8-Bit Guy.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "PCM"},
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "PCM"},
@ -1465,7 +1466,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_YM2610B_EXT]=new DivSysDef(
"Yamaha YM2610B (OPNB2) Extended Channel 3", NULL, 0xde, 0, 19, true, false, 0x151, false, (1U<<DIV_SAMPLE_DEPTH_ADPCM_A)|(1U<<DIV_SAMPLE_DEPTH_ADPCM_B)|(1U<<DIV_SAMPLE_DEPTH_8BIT),
"Yamaha YM2610B (OPNB2) Extended Channel 3", NULL, 0xde, 0, 19, true, false, 0x151, false, (1U<<DIV_SAMPLE_DEPTH_ADPCM_A)|(1U<<DIV_SAMPLE_DEPTH_ADPCM_B)|(1U<<DIV_SAMPLE_DEPTH_8BIT), 0, 0,
"so Taito asked Yamaha if they could get the two missing FM channels back, and Yamaha gladly provided them with this chip.\nthis one is in Extended Channel mode, which turns the third FM channel into four operators with independent notes/frequencies.",
{"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6", "ADPCM-B"},
{"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"},
@ -1478,7 +1479,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_YM2610B_CSM]=new DivSysDef(
"Yamaha YM2610B (OPNB2) CSM", NULL, 0xc5, 0, 20, true, false, 0x151, false, (1U<<DIV_SAMPLE_DEPTH_ADPCM_A)|(1U<<DIV_SAMPLE_DEPTH_ADPCM_B)|(1U<<DIV_SAMPLE_DEPTH_8BIT),
"Yamaha YM2610B (OPNB2) CSM", NULL, 0xc5, 0, 20, true, false, 0x151, false, (1U<<DIV_SAMPLE_DEPTH_ADPCM_A)|(1U<<DIV_SAMPLE_DEPTH_ADPCM_B)|(1U<<DIV_SAMPLE_DEPTH_8BIT), 0, 0,
"so Taito asked Yamaha if they could get the two missing FM channels back, and Yamaha gladly provided them with this chip.\nCSM blah blah",
{"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6", "CSM Timer", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6", "ADPCM-B"},
{"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6", "CSM", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"},
@ -1491,7 +1492,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_SEGAPCM_COMPAT]=new DivSysDef(
"SegaPCM (compatible 5-channel mode)", NULL, 0xa9, 0, 5, false, true, 0x151, false, 1U<<DIV_SAMPLE_DEPTH_8BIT,
"SegaPCM (compatible 5-channel mode)", NULL, 0xa9, 0, 5, false, true, 0x151, false, 1U<<DIV_SAMPLE_DEPTH_8BIT, 0, 0,
"this is the same thing as SegaPCM, but only exposes 5 of the channels for compatibility with DefleMask.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5"},
{"P1", "P2", "P3", "P4", "P5"},
@ -1503,7 +1504,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_X1_010]=new DivSysDef(
"Seta/Allumer X1-010", NULL, 0xb0, 0, 16, false, true, 0x171, false, 1U<<DIV_SAMPLE_DEPTH_8BIT,
"Seta/Allumer X1-010", NULL, 0xb0, 0, 16, false, true, 0x171, false, 1U<<DIV_SAMPLE_DEPTH_8BIT, 128, 256,
"a sound chip used in several Seta/Allumer-manufactured arcade boards with too many channels of wavetable sound, which also are capable of sampled sound.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16"},
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16"},
@ -1527,7 +1528,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_BUBSYS_WSG]=new DivSysDef(
"Konami Bubble System WSG", NULL, 0xad, 0, 2, false, true, 0, false, 0,
"Konami Bubble System WSG", NULL, 0xad, 0, 2, false, true, 0, false, 0, 32, 16,
"this is the wavetable part of the Bubble System, which also had two AY-3-8910s.",
{"Channel 1", "Channel 2"},
{"CH1", "CH2"},
@ -1540,7 +1541,7 @@ void DivEngine::registerSystems() {
// to Grauw: feel free to change this to 24 during development of OPL4's PCM part.
// TODO: add 12-bit and 16-bit big-endian sample formats
sysDefs[DIV_SYSTEM_OPL4]=new DivSysDef(
"Yamaha YMF278B (OPL4)", NULL, 0xae, 0, 42, true, true, 0, false, (1U<<DIV_SAMPLE_DEPTH_8BIT)|(1U<<DIV_SAMPLE_DEPTH_16BIT),
"Yamaha YMF278B (OPL4)", NULL, 0xae, 0, 42, true, true, 0, false, (1U<<DIV_SAMPLE_DEPTH_8BIT)|(1U<<DIV_SAMPLE_DEPTH_16BIT), 0, 0,
"like OPL3, but this time it also has a 24-channel version of MultiPCM.",
{"4OP 1", "FM 2", "4OP 3", "FM 4", "4OP 5", "FM 6", "4OP 7", "FM 8", "4OP 9", "FM 10", "4OP 11", "FM 12", "FM 13", "FM 14", "FM 15", "FM 16", "FM 17", "FM 18", "PCM 1", "PCM 2", "PCM 3", "PCM 4", "PCM 5", "PCM 6", "PCM 7", "PCM 8", "PCM 9", "PCM 10", "PCM 11", "PCM 12", "PCM 13", "PCM 14", "PCM 15", "PCM 16", "PCM 17", "PCM 18", "PCM 19", "PCM 20", "PCM 21", "PCM 22", "PCM 23", "PCM 24"},
{"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "F13", "F14", "F15", "F16", "F17", "F18", "P1", "P2", "P3", "P4", "P5", "P6", "P7", "P8", "P8", "P10", "P11", "P12", "P13", "P14", "P15", "P16", "P17", "P18", "P19", "P20", "P21", "P22", "P23", "P24"},
@ -1550,7 +1551,7 @@ void DivEngine::registerSystems() {
// TODO: same here
sysDefs[DIV_SYSTEM_OPL4_DRUMS]=new DivSysDef(
"Yamaha YMF278B (OPL4) with drums", NULL, 0xaf, 0, 44, true, true, 0, false, (1U<<DIV_SAMPLE_DEPTH_8BIT)|(1U<<DIV_SAMPLE_DEPTH_16BIT),
"Yamaha YMF278B (OPL4) with drums", NULL, 0xaf, 0, 44, true, true, 0, false, (1U<<DIV_SAMPLE_DEPTH_8BIT)|(1U<<DIV_SAMPLE_DEPTH_16BIT), 0, 0,
"the OPL4 but with drums mode turned on.",
{"4OP 1", "FM 2", "4OP 3", "FM 4", "4OP 5", "FM 6", "4OP 7", "FM 8", "4OP 9", "FM 10", "4OP 11", "FM 12", "FM 13", "FM 14", "FM 15", "Kick/FM 16", "Snare", "Tom", "Top", "HiHat", "PCM 1", "PCM 2", "PCM 3", "PCM 4", "PCM 5", "PCM 6", "PCM 7", "PCM 8", "PCM 9", "PCM 10", "PCM 11", "PCM 12", "PCM 13", "PCM 14", "PCM 15", "PCM 16", "PCM 17", "PCM 18", "PCM 19", "PCM 20", "PCM 21", "PCM 22", "PCM 23", "PCM 24"},
{"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", "F11", "F12", "F13", "F14", "F15", "BD", "SD", "TM", "TP", "HH", "P1", "P2", "P3", "P4", "P5", "P6", "P7", "P8", "P8", "P10", "P11", "P12", "P13", "P14", "P15", "P16", "P17", "P18", "P19", "P20", "P21", "P22", "P23", "P24"},
@ -1588,7 +1589,7 @@ void DivEngine::registerSystems() {
// TODO: custom sample format
sysDefs[DIV_SYSTEM_ES5506]=new DivSysDef(
"Ensoniq ES5506", NULL, 0xb1, 0, 32, false, true, 0/*0x171*/, false, (1U<<DIV_SAMPLE_DEPTH_8BIT)|(1U<<DIV_SAMPLE_DEPTH_16BIT),
"Ensoniq ES5506", NULL, 0xb1, 0, 32, false, true, 0/*0x171*/, false, (1U<<DIV_SAMPLE_DEPTH_16BIT), 0, 0,
"a sample chip made by Ensoniq, which is the basis for the GF1 chip found in Gravis' Ultrasound cards.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "Channel 17", "Channel 18", "Channel 19", "Channel 20", "Channel 21", "Channel 22", "Channel 23", "Channel 24", "Channel 25", "Channel 26", "Channel 27", "Channel 28", "Channel 29", "Channel 30", "Channel 31", "Channel 32"},
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28", "29", "30", "31", "32"},
@ -1600,7 +1601,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_Y8950]=new DivSysDef(
"Yamaha Y8950", NULL, 0xb2, 0, 10, true, false, 0x151, false, 1U<<DIV_SAMPLE_DEPTH_ADPCM_B,
"Yamaha Y8950", NULL, 0xb2, 0, 10, true, false, 0x151, false, 1U<<DIV_SAMPLE_DEPTH_ADPCM_B, 0, 0,
"like OPL but with an ADPCM channel.",
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8", "FM 9", "ADPCM"},
{"F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "P"},
@ -1612,7 +1613,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_Y8950_DRUMS]=new DivSysDef(
"Yamaha Y8950 with drums", NULL, 0xb3, 0, 12, true, false, 0x151, false, 1U<<DIV_SAMPLE_DEPTH_ADPCM_B,
"Yamaha Y8950 with drums", NULL, 0xb3, 0, 12, true, false, 0x151, false, 1U<<DIV_SAMPLE_DEPTH_ADPCM_B, 0, 0,
"the Y8950 chip, in drums mode.",
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Kick/FM 7", "Snare", "Tom", "Top", "HiHat", "ADPCM"},
{"F1", "F2", "F3", "F4", "F5", "F6", "BD", "SD", "TM", "TP", "HH", "P"},
@ -1624,7 +1625,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_SCC_PLUS]=new DivSysDef(
"Konami SCC+", NULL, 0xb4, 0, 5, false, true, 0x161, false, 0,
"Konami SCC+", NULL, 0xb4, 0, 5, false, true, 0x161, false, 0, 32, 256,
"this is a variant of Konami's SCC chip with the last channel's wavetable being independent.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5"},
{"CH1", "CH2", "CH3", "CH4", "CH5"},
@ -1660,7 +1661,7 @@ void DivEngine::registerSystems() {
}
sysDefs[DIV_SYSTEM_SOUND_UNIT]=new DivSysDef(
"tildearrow Sound Unit", NULL, 0xb5, 0, 8, false, true, 0, false, 1U<<DIV_SAMPLE_DEPTH_8BIT,
"tildearrow Sound Unit", NULL, 0xb5, 0, 8, false, true, 0, false, 1U<<DIV_SAMPLE_DEPTH_8BIT, 0, 0,
"tildearrow's fantasy sound chip. put SID, AY and VERA in a blender, and you get this!",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8"},
{"CH1", "CH2", "CH3", "CH4", "CH5", "CH6", "CH7", "CH8"},
@ -1672,7 +1673,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_MSM6295]=new DivSysDef(
"OKI MSM6295", NULL, 0xaa, 0, 4, false, true, 0x161, false, 1U<<DIV_SAMPLE_DEPTH_VOX,
"OKI MSM6295", NULL, 0xaa, 0, 4, false, true, 0x161, false, 1U<<DIV_SAMPLE_DEPTH_VOX, 0, 0,
"an ADPCM sound chip manufactured by OKI and used in many arcade boards.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4"},
{"CH1", "CH2", "CH3", "CH4"},
@ -1685,7 +1686,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_MSM6258]=new DivSysDef(
"OKI MSM6258", NULL, 0xab, 0, 1, false, true, 0x150, false, 1U<<DIV_SAMPLE_DEPTH_VOX,
"OKI MSM6258", NULL, 0xab, 0, 1, false, true, 0x150, false, 1U<<DIV_SAMPLE_DEPTH_VOX, 0, 0,
"an ADPCM sound chip manufactured by OKI and used in the Sharp X68000.",
{"Sample"},
{"PCM"},
@ -1699,7 +1700,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_YMZ280B]=new DivSysDef(
"Yamaha YMZ280B (PCMD8)", NULL, 0xb8, 0, 8, false, true, 0x151, false, 1U<<DIV_SAMPLE_DEPTH_YMZ_ADPCM,
"Yamaha YMZ280B (PCMD8)", NULL, 0xb8, 0, 8, false, true, 0x151, false, 1U<<DIV_SAMPLE_DEPTH_YMZ_ADPCM, 0, 0,
"used in some arcade boards. Can play back either 4-bit ADPCM, 8-bit PCM or 16-bit PCM.",
{"PCM 1", "PCM 2", "PCM 3", "PCM 4", "PCM 5", "PCM 6", "PCM 7", "PCM 8"},
{"1", "2", "3", "4", "5", "6", "7", "8"},
@ -1718,7 +1719,7 @@ void DivEngine::registerSystems() {
};
sysDefs[DIV_SYSTEM_NAMCO]=new DivSysDef(
"Namco WSG", NULL, 0xb9, 0, 3, false, true, 0, false, 0,
"Namco WSG", NULL, 0xb9, 0, 3, false, true, 0, false, 0, 32, 16,
"a wavetable sound chip used in Pac-Man, among other early Namco arcade games.",
{"Channel 1", "Channel 2", "Channel 3"},
{"CH1", "CH2", "CH3"},
@ -1729,7 +1730,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_NAMCO_15XX]=new DivSysDef(
"Namco C15 WSG", NULL, 0xba, 0, 8, false, true, 0, false, 0,
"Namco C15 WSG", NULL, 0xba, 0, 8, false, true, 0, false, 0, 32, 16,
"successor of the original Namco WSG chip, used in later Namco arcade games.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8"},
{"CH1", "CH2", "CH3", "CH4", "CH5", "CH6", "CH7", "CH8"},
@ -1740,7 +1741,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_NAMCO_CUS30]=new DivSysDef(
"Namco C30 WSG", NULL, 0xbb, 0, 8, false, true, 0, false, 0,
"Namco C30 WSG", NULL, 0xbb, 0, 8, false, true, 0, false, 0, 32, 16,
"like Namco C15 but with stereo sound.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8"},
{"CH1", "CH2", "CH3", "CH4", "CH5", "CH6", "CH7", "CH8"},
@ -1751,7 +1752,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_MSM5232]=new DivSysDef(
"OKI MSM5232", NULL, 0xbc, 0, 8, false, true, 0, false, 0,
"OKI MSM5232", NULL, 0xbc, 0, 8, false, true, 0, false, 0, 0, 0,
"a square wave additive synthesis chip made by OKI. used in some arcade machines and instruments.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8"},
{"CH1", "CH2", "CH3", "CH4", "CH5", "CH6", "CH7", "CH8"},
@ -1768,7 +1769,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_YM2612_DUALPCM]=new DivSysDef(
"Yamaha YM2612 (OPN2) with DualPCM", NULL, 0xbe, 0, 7, true, false, 0x150, false, 1U<<DIV_SAMPLE_DEPTH_8BIT,
"Yamaha YM2612 (OPN2) with DualPCM", NULL, 0xbe, 0, 7, true, false, 0x150, false, 1U<<DIV_SAMPLE_DEPTH_8BIT, 0, 0,
"this chip is mostly known for being in the Sega Genesis (but it also was on the FM Towns computer).\nthis system uses software mixing to provide two sample channels.",
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6/PCM 1", "PCM 2"},
{"F1", "F2", "F3", "F4", "F5", "P1", "P2"},
@ -1780,7 +1781,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_YM2612_DUALPCM_EXT]=new DivSysDef(
"Yamaha YM2612 (OPN2) Extended Channel 3 with DualPCM and CSM", NULL, 0xbd, 0, 11, true, false, 0x150, false, 1U<<DIV_SAMPLE_DEPTH_8BIT,
"Yamaha YM2612 (OPN2) Extended Channel 3 with DualPCM and CSM", NULL, 0xbd, 0, 11, true, false, 0x150, false, 1U<<DIV_SAMPLE_DEPTH_8BIT, 0, 0,
"this chip is mostly known for being in the Sega Genesis (but it also was on the FM Towns computer).\nthis system uses software mixing to provide two sample channels.\nthis one is in Extended Channel mode, which turns the third FM channel into four operators with independent notes/frequencies.",
{"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6/PCM 1", "PCM 2", "CSM Timer"},
{"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "P1", "P2", "CSM"},
@ -1793,7 +1794,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_T6W28]=new DivSysDef(
"T6W28", NULL, 0xbf, 0, 4, false, true, 0x160, false, 0,
"T6W28", NULL, 0xbf, 0, 4, false, true, 0x160, false, 0, 0, 0,
"an SN76489 derivative used in Neo Geo Pocket, has independent stereo volume and noise channel frequency.",
{"Square 1", "Square 2", "Square 3", "Noise"},
{"S1", "S2", "S3", "NO"},
@ -1806,7 +1807,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_PCM_DAC]=new DivSysDef(
"Generic PCM DAC", NULL, 0xc0, 0, 1, false, true, 0, false, 1U<<DIV_SAMPLE_DEPTH_16BIT,
"Generic PCM DAC", NULL, 0xc0, 0, 1, false, true, 0, false, 1U<<DIV_SAMPLE_DEPTH_16BIT, 0, 256,
"as generic sample playback as it gets.",
{"Sample"},
{"PCM"},
@ -1815,7 +1816,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_K007232]=new DivSysDef(
"Konami K007232", NULL, 0xc6, 0, 2, false, true, 0, false, 1U<<DIV_SAMPLE_DEPTH_8BIT,
"Konami K007232", NULL, 0xc6, 0, 2, false, true, 0, false, 1U<<DIV_SAMPLE_DEPTH_8BIT, 0, 0,
"this PCM chip was widely used at Konami arcade boards in 1986-1990.",
{"Channel 1", "Channel 2"},
{"CH1", "CH2"},
@ -1825,7 +1826,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_GA20]=new DivSysDef(
"Irem GA20", NULL, 0xc7, 0, 4, false, true, 0x171, false, 1U<<DIV_SAMPLE_DEPTH_8BIT,
"Irem GA20", NULL, 0xc7, 0, 4, false, true, 0x171, false, 1U<<DIV_SAMPLE_DEPTH_8BIT, 0, 0,
"yet another PCM chip from Irem. like Amiga, but less pitch resolution and no sample loop.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4"},
{"CH1", "CH2", "CH3", "CH4"},
@ -1835,7 +1836,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_SM8521]=new DivSysDef(
"Sharp SM8521", NULL, 0xc8, 0, 3, false, true, 0, false, 0,
"Sharp SM8521", NULL, 0xc8, 0, 3, false, true, 0, false, 0, 32, 16,
"a SoC with wavetable sound hardware.",
{"Channel 1", "Channel 2", "Noise"},
{"CH1", "CH2", "NS"},
@ -1846,7 +1847,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_PV1000]=new DivSysDef(
"Casio PV-1000", NULL, 0xcb, 0, 3, false, true, 0, false, 0,
"Casio PV-1000", NULL, 0xcb, 0, 3, false, true, 0, false, 0, 0, 0,
"a game console with 3 channels of square wave. it's what happens after fusing TIA and VIC together.",
{"Square 1", "Square 2", "Square 3"},
{"S1", "S2", "S3"},
@ -1859,7 +1860,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_SFX_BEEPER_QUADTONE]=new DivSysDef(
"ZX Spectrum Beeper (QuadTone Engine)", NULL, 0xca, 0, 5, false, true, 0, false, 1U<<DIV_SAMPLE_DEPTH_8BIT,
"ZX Spectrum Beeper (QuadTone Engine)", NULL, 0xca, 0, 5, false, true, 0, false, 1U<<DIV_SAMPLE_DEPTH_8BIT, 0, 0,
"another ZX Spectrum beeper system with full PWM pulses and 3-level volume per channel. it also has a pitchable overlay sample channel.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "PCM"},
{"CH1", "CH2", "CH3", "CH4", "PCM"},
@ -1872,7 +1873,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_K053260]=new DivSysDef(
"Konami K053260", NULL, 0xcc, 0, 4, false, true, 0x161, false, (1U<<DIV_SAMPLE_DEPTH_ADPCM_K)|(1U<<DIV_SAMPLE_DEPTH_8BIT),
"Konami K053260", NULL, 0xcc, 0, 4, false, true, 0x161, false, (1U<<DIV_SAMPLE_DEPTH_ADPCM_K)|(1U<<DIV_SAMPLE_DEPTH_8BIT), 0, 0,
"this PCM chip was widely used at Konami arcade boards in 1990-1992.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4"},
{"CH1", "CH2", "CH3", "CH4"},
@ -1885,7 +1886,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_TED]=new DivSysDef(
"MOS Technology TED", NULL, 0xcd, 0, 2, false, true, 0, false, 0,
"MOS Technology TED", NULL, 0xcd, 0, 2, false, true, 0, false, 0, 0, 0,
"two square waves (one may be turned into noise). used in the Commodore Plus/4, 16 and 116.",
{"Channel 1", "Channel 2"},
{"CH1", "CH2"},
@ -1895,7 +1896,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_C140]=new DivSysDef(
"Namco C140", NULL, 0xce, 0, 24, false, true, 0x161, false, (1U<<DIV_SAMPLE_DEPTH_MULAW)|(1U<<DIV_SAMPLE_DEPTH_8BIT),
"Namco C140", NULL, 0xce, 0, 24, false, true, 0x161, false, (1U<<DIV_SAMPLE_DEPTH_MULAW)|(1U<<DIV_SAMPLE_DEPTH_8BIT), 0, 0,
"Namco's first PCM chip from 1987. it's pretty good for being so.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "Channel 17", "Channel 18", "Channel 19", "Channel 20", "Channel 21", "Channel 22", "Channel 23", "Channel 24"},
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24"},
@ -1906,7 +1907,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_C219]=new DivSysDef(
"Namco C219", NULL, 0xcf, 0, 16, false, true, 0x161, false, (1U<<DIV_SAMPLE_DEPTH_C219)|(1U<<DIV_SAMPLE_DEPTH_8BIT),
"Namco C219", NULL, 0xcf, 0, 16, false, true, 0x161, false, (1U<<DIV_SAMPLE_DEPTH_C219)|(1U<<DIV_SAMPLE_DEPTH_8BIT), 0, 0,
"Namco's PCM chip used in their NA-1/2 hardware.\nvery similar to C140, but has noise generator.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16"},
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16"},
@ -1921,7 +1922,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_DUMMY]=new DivSysDef(
"Dummy System", NULL, 0xfd, 0, 8, false, true, 0, false, 0,
"Dummy System", NULL, 0xfd, 0, 8, false, true, 0, false, 0, 0, 0,
"this is a system designed for testing purposes.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8"},
{"CH1", "CH2", "CH3", "CH4", "CH5", "CH6", "CH7", "CH8"},