Merge branch 'master' of https://github.com/tildearrow/furnace into nmk112
This commit is contained in:
commit
b427bab4b6
76 changed files with 12670 additions and 165 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
2139
src/engine/platform/sound/ymfm/ymfm_opl.cpp
Normal file
2139
src/engine/platform/sound/ymfm/ymfm_opl.cpp
Normal file
File diff suppressed because it is too large
Load diff
911
src/engine/platform/sound/ymfm/ymfm_opl.h
Normal file
911
src/engine/platform/sound/ymfm/ymfm_opl.h
Normal 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
|
||||
714
src/engine/platform/sound/ymfm/ymfm_pcm.cpp
Normal file
714
src/engine/platform/sound/ymfm/ymfm_pcm.cpp
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
347
src/engine/platform/sound/ymfm/ymfm_pcm.h
Normal file
347
src/engine/platform/sound/ymfm/ymfm_pcm.h
Normal 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 ®s() { 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
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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) {}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue