Merge branch 'master' into ymf289b
This commit is contained in:
commit
25eb720631
230 changed files with 69242 additions and 87659 deletions
|
|
@ -37,6 +37,10 @@ DivMacroInt* DivDispatch::getChanMacroInt(int chan) {
|
|||
return NULL;
|
||||
}
|
||||
|
||||
DivSamplePos DivDispatch::getSamplePos(int chan) {
|
||||
return DivSamplePos();
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivDispatch::getOscBuffer(int chan) {
|
||||
return NULL;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,11 +20,15 @@
|
|||
#define _USE_MATH_DEFINES
|
||||
#include "amiga.h"
|
||||
#include "../engine.h"
|
||||
#include "../../ta-log.h"
|
||||
#include <math.h>
|
||||
|
||||
#define AMIGA_DIVIDER 8
|
||||
#define AMIGA_VPMASK 7
|
||||
#define CHIP_DIVIDER 16
|
||||
|
||||
#define chWrite(c,a,v) rWrite(((c)<<4)+0xa0+(a),(v));
|
||||
|
||||
const char* regCheatSheetAmiga[]={
|
||||
"DMACON", "96",
|
||||
"INTENA", "9A",
|
||||
|
|
@ -78,54 +82,85 @@ const char** DivPlatformAmiga::getRegisterSheet() {
|
|||
void DivPlatformAmiga::acquire(short** buf, size_t len) {
|
||||
static int outL, outR, output;
|
||||
for (size_t h=0; h<len; h++) {
|
||||
bool hsync=bypassLimits;
|
||||
outL=0;
|
||||
outR=0;
|
||||
for (int i=0; i<4; i++) {
|
||||
if (!chan[i].active) {
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=0;
|
||||
continue;
|
||||
|
||||
// TODO:
|
||||
// - improve DMA overrun behavior
|
||||
// - does V/P mod really work like that?
|
||||
amiga.volPos=(amiga.volPos+1)&AMIGA_VPMASK;
|
||||
if (!bypassLimits) {
|
||||
amiga.hPos+=AMIGA_DIVIDER;
|
||||
if (amiga.hPos>=228) {
|
||||
amiga.hPos-=228;
|
||||
hsync=true;
|
||||
}
|
||||
if (chan[i].useWave || (chan[i].sample>=0 && chan[i].sample<parent->song.sampleLen)) {
|
||||
chan[i].audSub-=AMIGA_DIVIDER;
|
||||
if (chan[i].audSub<0) {
|
||||
if (chan[i].useWave) {
|
||||
writeAudDat(chan[i].ws.output[(chan[i].audPos++)&255]^0x80);
|
||||
if (chan[i].audPos>=(unsigned int)(chan[i].audLen<<1)) {
|
||||
chan[i].audPos=0;
|
||||
}
|
||||
} else {
|
||||
DivSample* s=parent->getSample(chan[i].sample);
|
||||
if (s->samples>0) {
|
||||
if (chan[i].audPos<s->samples) {
|
||||
writeAudDat(s->data8[chan[i].audPos++]);
|
||||
}
|
||||
if (s->isLoopable() && chan[i].audPos>=MIN(131071,(unsigned int)s->loopEnd)) {
|
||||
chan[i].audPos=s->loopStart;
|
||||
} else if (chan[i].audPos>=MIN(131071,s->samples)) {
|
||||
chan[i].sample=-1;
|
||||
}
|
||||
} else {
|
||||
chan[i].sample=-1;
|
||||
}
|
||||
for (int i=0; i<4; i++) {
|
||||
// run DMA
|
||||
if (amiga.dmaEn && amiga.audEn[i] && !amiga.audIr[i]) {
|
||||
amiga.audTick[i]-=AMIGA_DIVIDER;
|
||||
if (amiga.audTick[i]<0) {
|
||||
amiga.audTick[i]+=MAX(AMIGA_DIVIDER,amiga.audPer[i]);
|
||||
if (amiga.audByte[i]) {
|
||||
// read next samples
|
||||
if (!amiga.incLoc[i]) {
|
||||
amiga.audDat[0][i]=sampleMem[(amiga.dmaLoc[i])&chipMask];
|
||||
amiga.audDat[1][i]=sampleMem[(amiga.dmaLoc[i]+1)&chipMask];
|
||||
amiga.incLoc[i]=true;
|
||||
}
|
||||
|
||||
amiga.audWord[i]=!amiga.audWord[i];
|
||||
}
|
||||
/*if (chan[i].freq<124) {
|
||||
if (++chan[i].busClock>=512) {
|
||||
unsigned int rAmount=(124-chan[i].freq)*2;
|
||||
if (chan[i].audPos>=rAmount) {
|
||||
chan[i].audPos-=rAmount;
|
||||
|
||||
amiga.audByte[i]=!amiga.audByte[i];
|
||||
if (!amiga.audByte[i] && (amiga.useV[i] || amiga.useP[i])) {
|
||||
amiga.nextOut2[i]=((unsigned char)amiga.audDat[0][i])<<8|((unsigned char)amiga.audDat[1][i]);
|
||||
if (i<3) {
|
||||
if (amiga.useV[i] && amiga.useP[i]) {
|
||||
if (amiga.audWord[i]) {
|
||||
amiga.audPer[i+1]=amiga.nextOut2[i];
|
||||
} else {
|
||||
amiga.audVol[i+1]=amiga.nextOut2[i];
|
||||
}
|
||||
} else if (amiga.useV[i]) {
|
||||
amiga.audVol[i+1]=amiga.nextOut2[i];
|
||||
} else {
|
||||
amiga.audPer[i+1]=amiga.nextOut2[i];
|
||||
}
|
||||
chan[i].busClock=0;
|
||||
}
|
||||
}*/
|
||||
if (bypassLimits) {
|
||||
chan[i].audSub+=MAX(AMIGA_DIVIDER,chan[i].freq);
|
||||
} else {
|
||||
chan[i].audSub+=MAX(114,chan[i].freq);
|
||||
amiga.nextOut[i]=amiga.audDat[amiga.audByte[i]][i];
|
||||
}
|
||||
}
|
||||
|
||||
if (hsync) {
|
||||
if (amiga.incLoc[i]) {
|
||||
amiga.incLoc[i]=false;
|
||||
amiga.dmaLoc[i]+=2;
|
||||
// check for length
|
||||
if ((--amiga.dmaLen[i])==0) {
|
||||
if (amiga.audInt[i]) {
|
||||
amiga.audIr[i]=true;
|
||||
irq(i);
|
||||
}
|
||||
amiga.dmaLoc[i]=amiga.audLoc[i];
|
||||
amiga.dmaLen[i]=amiga.audLen[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// output
|
||||
if (!isMuted[i]) {
|
||||
output=chan[i].audDat*chan[i].outVol;
|
||||
if (amiga.audVol[i]>=64) {
|
||||
output=amiga.nextOut[i]<<6;
|
||||
} else if (amiga.audVol[i]<=0) {
|
||||
output=0;
|
||||
} else {
|
||||
output=amiga.nextOut[i]*volTable[amiga.audVol[i]][amiga.volPos];
|
||||
}
|
||||
if (i==0 || i==3) {
|
||||
outL+=(output*sep1)>>7;
|
||||
outR+=(output*sep2)>>7;
|
||||
|
|
@ -133,11 +168,12 @@ void DivPlatformAmiga::acquire(short** buf, size_t len) {
|
|||
outL+=(output*sep2)>>7;
|
||||
outR+=(output*sep1)>>7;
|
||||
}
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=output<<2;
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=(amiga.nextOut[i]*MIN(64,amiga.audVol[i]))<<2;
|
||||
} else {
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=0;
|
||||
}
|
||||
}
|
||||
|
||||
filter[0][0]+=(filtConst*(outL-filter[0][0]))>>12;
|
||||
filter[0][1]+=(filtConst*(filter[0][0]-filter[0][1]))>>12;
|
||||
filter[1][0]+=(filtConst*(outR-filter[1][0]))>>12;
|
||||
|
|
@ -147,11 +183,184 @@ void DivPlatformAmiga::acquire(short** buf, size_t len) {
|
|||
}
|
||||
}
|
||||
|
||||
void DivPlatformAmiga::irq(int ch) {
|
||||
// disable interrupt
|
||||
rWrite(0x9a,128<<ch);
|
||||
|
||||
if (chan[ch].irLocL==0x400 && chan[ch].irLocH==0 && chan[ch].irLen==1) {
|
||||
// turn off DMA
|
||||
rWrite(0x96,1<<ch);
|
||||
} else {
|
||||
// write latched loc/len
|
||||
chWrite(ch,0,chan[ch].irLocH);
|
||||
chWrite(ch,2,chan[ch].irLocL);
|
||||
chWrite(ch,4,chan[ch].irLen);
|
||||
}
|
||||
|
||||
// acknowledge interrupt
|
||||
rWrite(0x9c,128<<ch);
|
||||
}
|
||||
|
||||
#define UPDATE_DMA(x) \
|
||||
amiga.dmaLen[x]=amiga.audLen[x]; \
|
||||
amiga.dmaLoc[x]=amiga.audLoc[x]; \
|
||||
amiga.audByte[x]=true; \
|
||||
amiga.audTick[x]=0;
|
||||
|
||||
void DivPlatformAmiga::rWrite(unsigned short addr, unsigned short val) {
|
||||
if (addr&1) return;
|
||||
|
||||
//logV("%.3x = %.4x",addr,val);
|
||||
regPool[addr>>1]=val;
|
||||
|
||||
if (!skipRegisterWrites && dumpWrites) {
|
||||
addWrite(addr,val);
|
||||
}
|
||||
|
||||
switch (addr&0x1fe) {
|
||||
case 0x96: { // DMACON
|
||||
if (val&32768) {
|
||||
if (val&1) amiga.audEn[0]=true;
|
||||
if (val&2) amiga.audEn[1]=true;
|
||||
if (val&4) amiga.audEn[2]=true;
|
||||
if (val&8) amiga.audEn[3]=true;
|
||||
if (val&512) amiga.dmaEn=true;
|
||||
} else {
|
||||
if (val&1) {
|
||||
amiga.audEn[0]=false;
|
||||
UPDATE_DMA(0);
|
||||
}
|
||||
if (val&2) {
|
||||
amiga.audEn[1]=false;
|
||||
UPDATE_DMA(1);
|
||||
}
|
||||
if (val&4) {
|
||||
amiga.audEn[2]=false;
|
||||
UPDATE_DMA(2);
|
||||
}
|
||||
if (val&8) {
|
||||
amiga.audEn[3]=false;
|
||||
UPDATE_DMA(3);
|
||||
}
|
||||
if (val&512) {
|
||||
amiga.dmaEn=false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0x9a: { // INTENA
|
||||
if (val&32768) {
|
||||
if (val&128) amiga.audInt[0]=true;
|
||||
if (val&256) amiga.audInt[1]=true;
|
||||
if (val&512) amiga.audInt[2]=true;
|
||||
if (val&1024) amiga.audInt[3]=true;
|
||||
} else {
|
||||
if (val&128) amiga.audInt[0]=false;
|
||||
if (val&256) amiga.audInt[1]=false;
|
||||
if (val&512) amiga.audInt[2]=false;
|
||||
if (val&1024) amiga.audInt[3]=false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0x9c: { // INTREQ
|
||||
if (val&32768) {
|
||||
if (val&128) {
|
||||
amiga.audIr[0]=true;
|
||||
irq(0);
|
||||
}
|
||||
if (val&256) {
|
||||
amiga.audIr[1]=true;
|
||||
irq(1);
|
||||
}
|
||||
if (val&512) {
|
||||
amiga.audIr[2]=true;
|
||||
irq(2);
|
||||
}
|
||||
if (val&1024) {
|
||||
amiga.audIr[3]=true;
|
||||
irq(3);
|
||||
}
|
||||
} else {
|
||||
if (val&128) amiga.audIr[0]=false;
|
||||
if (val&256) amiga.audIr[1]=false;
|
||||
if (val&512) amiga.audIr[2]=false;
|
||||
if (val&1024) amiga.audIr[3]=false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 0x9e: { // ADKCON
|
||||
if (val&32768) {
|
||||
if (val&1) amiga.useV[0]=true;
|
||||
if (val&2) amiga.useV[1]=true;
|
||||
if (val&4) amiga.useV[2]=true;
|
||||
if (val&8) amiga.useV[3]=true;
|
||||
if (val&16) amiga.useP[0]=true;
|
||||
if (val&32) amiga.useP[1]=true;
|
||||
if (val&64) amiga.useP[2]=true;
|
||||
if (val&128) amiga.useP[3]=true;
|
||||
} else {
|
||||
if (val&1) amiga.useV[0]=false;
|
||||
if (val&2) amiga.useV[1]=false;
|
||||
if (val&4) amiga.useV[2]=false;
|
||||
if (val&8) amiga.useV[3]=false;
|
||||
if (val&16) amiga.useP[0]=false;
|
||||
if (val&32) amiga.useP[1]=false;
|
||||
if (val&64) amiga.useP[2]=false;
|
||||
if (val&128) amiga.useP[3]=false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: { // AUDx
|
||||
if (addr>=0xa0 && addr<0xe0) {
|
||||
const unsigned char ch=((addr-0xa0)>>4)&3;
|
||||
bool updateDMA=false;
|
||||
switch (addr&15) {
|
||||
case 0: // LCH
|
||||
amiga.audLoc[ch]&=0xffff;
|
||||
amiga.audLoc[ch]|=val<<16;
|
||||
updateDMA=true;
|
||||
break;
|
||||
case 2: // LCL
|
||||
amiga.audLoc[ch]&=0xffff0000;
|
||||
amiga.audLoc[ch]|=val&0xfffe;
|
||||
updateDMA=true;
|
||||
break;
|
||||
case 4: // LEN
|
||||
amiga.audLen[ch]=val;
|
||||
updateDMA=true;
|
||||
break;
|
||||
case 6: // PER
|
||||
amiga.audPer[ch]=val;
|
||||
break;
|
||||
case 8: // VOL
|
||||
amiga.audVol[ch]=val;
|
||||
break;
|
||||
case 10: // DAT
|
||||
amiga.audDat[0][ch]=val&0xff;
|
||||
amiga.audDat[1][ch]=val>>8;
|
||||
break;
|
||||
}
|
||||
if (updateDMA && !amiga.audEn[ch]) {
|
||||
UPDATE_DMA(ch);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformAmiga::updateWave(int ch) {
|
||||
for (int i=0; i<MIN(256,(chan[ch].audLen<<1)); i++) {
|
||||
sampleMem[(ch<<8)|i]=chan[ch].ws.output[i]^0x80;
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformAmiga::tick(bool sysTick) {
|
||||
for (int i=0; i<4; i++) {
|
||||
chan[i].std.next();
|
||||
if (chan[i].std.vol.had) {
|
||||
chan[i].outVol=((chan[i].vol%65)*MIN(64,chan[i].std.vol.val))>>6;
|
||||
chan[i].writeVol=true;
|
||||
}
|
||||
double off=1.0;
|
||||
if (!chan[i].useWave && chan[i].sample>=0 && chan[i].sample<parent->song.sampleLen) {
|
||||
|
|
@ -173,11 +382,13 @@ void DivPlatformAmiga::tick(bool sysTick) {
|
|||
if (chan[i].wave!=chan[i].std.wave.val || chan[i].ws.activeChanged()) {
|
||||
chan[i].wave=chan[i].std.wave.val;
|
||||
chan[i].ws.changeWave1(chan[i].wave);
|
||||
if (!chan[i].keyOff) chan[i].keyOn=true;
|
||||
chan[i].updateWave=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].useWave && chan[i].active) {
|
||||
chan[i].ws.tick();
|
||||
if (chan[i].ws.tick()) {
|
||||
chan[i].updateWave=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].std.pitch.had) {
|
||||
if (chan[i].std.pitch.mode) {
|
||||
|
|
@ -189,8 +400,31 @@ void DivPlatformAmiga::tick(bool sysTick) {
|
|||
chan[i].freqChanged=true;
|
||||
}
|
||||
if (chan[i].std.phaseReset.had) {
|
||||
if (chan[i].std.phaseReset.val==1) {
|
||||
chan[i].audPos=0;
|
||||
if (chan[i].std.phaseReset.val==1 && chan[i].active) {
|
||||
chan[i].keyOn=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsigned short dmaOff=0;
|
||||
unsigned short dmaOn=0;
|
||||
for (int i=0; i<4; i++) {
|
||||
if (chan[i].keyOn || chan[i].keyOff) {
|
||||
chWrite(i,6,1);
|
||||
dmaOff|=1<<i;
|
||||
}
|
||||
}
|
||||
|
||||
if (dmaOff) rWrite(0x96,dmaOff);
|
||||
|
||||
for (int i=0; i<4; i++) {
|
||||
double off=1.0;
|
||||
if (!chan[i].useWave && chan[i].sample>=0 && chan[i].sample<parent->song.sampleLen) {
|
||||
DivSample* s=parent->getSample(chan[i].sample);
|
||||
if (s->centerRate<1) {
|
||||
off=1.0;
|
||||
} else {
|
||||
off=8363.0/(double)s->centerRate;
|
||||
}
|
||||
}
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
|
|
@ -198,15 +432,115 @@ void DivPlatformAmiga::tick(bool sysTick) {
|
|||
chan[i].freq=off*parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER);
|
||||
if (chan[i].freq>4095) chan[i].freq=4095;
|
||||
if (chan[i].freq<0) chan[i].freq=0;
|
||||
|
||||
chWrite(i,6,chan[i].freq);
|
||||
|
||||
if (chan[i].keyOn) {
|
||||
}
|
||||
if (chan[i].keyOff) {
|
||||
if (chan[i].useWave) {
|
||||
rWrite(0x9a,(128<<i));
|
||||
chWrite(i,0,0);
|
||||
chWrite(i,2,i<<8);
|
||||
chWrite(i,4,chan[i].audLen);
|
||||
if (dumpWrites) {
|
||||
addWrite(0x200+i,i<<8);
|
||||
addWrite(0x204+i,chan[i].audLen);
|
||||
}
|
||||
dmaOn|=1<<i;
|
||||
} else {
|
||||
if (chan[i].sample>=0 && chan[i].sample<parent->song.sampleLen) {
|
||||
DivSample* s=parent->getSample(chan[i].sample);
|
||||
int start=chan[i].audPos&(~1);
|
||||
if (start>s->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT)) start=s->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT);
|
||||
int len=s->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT)-start;
|
||||
if (len<0) len=0;
|
||||
if (len>131070) len=131070;
|
||||
len>>=1;
|
||||
|
||||
start+=sampleOff[chan[i].sample];
|
||||
|
||||
if (len<1) {
|
||||
chWrite(i,0,0);
|
||||
chWrite(i,2,0x400);
|
||||
chWrite(i,4,1);
|
||||
if (dumpWrites) {
|
||||
addWrite(0x200+i,0x400);
|
||||
addWrite(0x204+i,1);
|
||||
}
|
||||
} else {
|
||||
chWrite(i,0,start>>16);
|
||||
chWrite(i,2,start);
|
||||
chWrite(i,4,len);
|
||||
if (dumpWrites) {
|
||||
addWrite(0x200+i,start);
|
||||
addWrite(0x204+i,len);
|
||||
}
|
||||
}
|
||||
|
||||
dmaOn|=1<<i;
|
||||
if (s->isLoopable()) {
|
||||
int loopPos=(sampleOff[chan[i].sample]+s->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT))&(~1);
|
||||
int loopEnd=(s->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT)-s->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT))>>1;
|
||||
chan[i].irLocH=loopPos>>16;
|
||||
chan[i].irLocL=loopPos;
|
||||
chan[i].irLen=MIN(65535,loopEnd);
|
||||
} else {
|
||||
chan[i].irLocH=0;
|
||||
chan[i].irLocL=0x400;
|
||||
chan[i].irLen=1;
|
||||
}
|
||||
rWrite(0x9a,0x8000|(128<<i));
|
||||
} else {
|
||||
chWrite(i,0,0);
|
||||
chWrite(i,2,0x400);
|
||||
chWrite(i,4,1);
|
||||
if (dumpWrites) {
|
||||
addWrite(0x200+i,0x400);
|
||||
addWrite(0x204+i,1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chan[i].keyOn) chan[i].keyOn=false;
|
||||
if (chan[i].keyOff) chan[i].keyOff=false;
|
||||
chan[i].freqChanged=false;
|
||||
}
|
||||
}
|
||||
|
||||
if (dmaOn) rWrite(0x96,0x8000|dmaOn);
|
||||
|
||||
for (int i=0; i<4; i++) {
|
||||
if ((dmaOn&(1<<i)) && !chan[i].useWave && dumpWrites) {
|
||||
addWrite(0x200+i,(chan[i].irLocH<<16)|chan[i].irLocL);
|
||||
addWrite(0x204+i,chan[i].irLen);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i=0; i<4; i++) {
|
||||
if (chan[i].writeVol) {
|
||||
chan[i].writeVol=false;
|
||||
chWrite(i,8,chan[i].outVol);
|
||||
}
|
||||
if (chan[i].updateWave) {
|
||||
chan[i].updateWave=false;
|
||||
updateWave(i);
|
||||
}
|
||||
}
|
||||
|
||||
if (updateADKCon) {
|
||||
updateADKCon=false;
|
||||
rWrite(0x9e,0xff);
|
||||
rWrite(0x9e,(
|
||||
0x8000|
|
||||
(chan[0].useV?1:0)|
|
||||
(chan[1].useV?2:0)|
|
||||
(chan[2].useV?4:0)|
|
||||
(chan[3].useV?8:0)|
|
||||
(chan[0].useP?16:0)|
|
||||
(chan[1].useP?32:0)|
|
||||
(chan[2].useP?64:0)|
|
||||
(chan[3].useP?128:0)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
int DivPlatformAmiga::dispatch(DivCommand c) {
|
||||
|
|
@ -214,6 +548,7 @@ int DivPlatformAmiga::dispatch(DivCommand c) {
|
|||
case DIV_CMD_NOTE_ON: {
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA);
|
||||
if (ins->amiga.useWave) {
|
||||
if (!chan[c.chan].useWave) chan[c.chan].updateWave=true;
|
||||
chan[c.chan].useWave=true;
|
||||
chan[c.chan].audLen=(ins->amiga.waveLen+1)>>1;
|
||||
if (chan[c.chan].insChanged) {
|
||||
|
|
@ -221,10 +556,14 @@ int DivPlatformAmiga::dispatch(DivCommand c) {
|
|||
chan[c.chan].wave=0;
|
||||
chan[c.chan].ws.setWidth(chan[c.chan].audLen<<1);
|
||||
chan[c.chan].ws.changeWave1(chan[c.chan].wave);
|
||||
chan[c.chan].updateWave=true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (c.value!=DIV_NOTE_NULL) chan[c.chan].sample=ins->amiga.getSample(c.value);
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].sample=ins->amiga.getSample(c.value);
|
||||
c.value=ins->amiga.getFreq(c.value);
|
||||
}
|
||||
chan[c.chan].useWave=false;
|
||||
}
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
|
|
@ -248,9 +587,11 @@ int DivPlatformAmiga::dispatch(DivCommand c) {
|
|||
chan[c.chan].macroInit(ins);
|
||||
if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) {
|
||||
chan[c.chan].outVol=chan[c.chan].vol;
|
||||
chan[c.chan].writeVol=true;
|
||||
}
|
||||
if (chan[c.chan].useWave) {
|
||||
chan[c.chan].ws.init(ins,chan[c.chan].audLen<<1,255,chan[c.chan].insChanged);
|
||||
chan[c.chan].updateWave=true;
|
||||
}
|
||||
chan[c.chan].insChanged=false;
|
||||
break;
|
||||
|
|
@ -276,6 +617,7 @@ int DivPlatformAmiga::dispatch(DivCommand c) {
|
|||
chan[c.chan].vol=c.value;
|
||||
if (!chan[c.chan].std.vol.has) {
|
||||
chan[c.chan].outVol=c.value;
|
||||
chan[c.chan].writeVol=true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
|
@ -294,6 +636,7 @@ int DivPlatformAmiga::dispatch(DivCommand c) {
|
|||
chan[c.chan].wave=c.value;
|
||||
chan[c.chan].keyOn=true;
|
||||
chan[c.chan].ws.changeWave1(chan[c.chan].wave);
|
||||
chan[c.chan].updateWave=true;
|
||||
break;
|
||||
case DIV_CMD_NOTE_PORTA: {
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA);
|
||||
|
|
@ -336,6 +679,7 @@ int DivPlatformAmiga::dispatch(DivCommand c) {
|
|||
case DIV_CMD_SAMPLE_POS:
|
||||
if (chan[c.chan].useWave) break;
|
||||
chan[c.chan].audPos=c.value;
|
||||
if (chan[c.chan].active) chan[c.chan].keyOn=true;
|
||||
chan[c.chan].setPos=true;
|
||||
break;
|
||||
case DIV_CMD_AMIGA_FILTER:
|
||||
|
|
@ -344,9 +688,11 @@ int DivPlatformAmiga::dispatch(DivCommand c) {
|
|||
break;
|
||||
case DIV_CMD_AMIGA_AM:
|
||||
chan[c.chan].useV=c.value;
|
||||
updateADKCon=true;
|
||||
break;
|
||||
case DIV_CMD_AMIGA_PM:
|
||||
chan[c.chan].useP=c.value;
|
||||
updateADKCon=true;
|
||||
break;
|
||||
case DIV_CMD_GET_VOLMAX:
|
||||
return 64;
|
||||
|
|
@ -374,9 +720,12 @@ void DivPlatformAmiga::forceIns() {
|
|||
for (int i=0; i<4; i++) {
|
||||
chan[i].insChanged=true;
|
||||
chan[i].freqChanged=true;
|
||||
chan[i].audPos=131072;
|
||||
chan[i].audDat=0;
|
||||
chan[i].sample=-1;
|
||||
/*chan[i].keyOn=false;
|
||||
chan[i].keyOff=false;
|
||||
chan[i].sample=-1;*/
|
||||
if (!chan[i].useWave) {
|
||||
rWrite(0x96,1<<i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -389,6 +738,7 @@ DivDispatchOscBuffer* DivPlatformAmiga::getOscBuffer(int ch) {
|
|||
}
|
||||
|
||||
void DivPlatformAmiga::reset() {
|
||||
memset(regPool,0,256*sizeof(unsigned short));
|
||||
for (int i=0; i<4; i++) {
|
||||
chan[i]=DivPlatformAmiga::Channel();
|
||||
chan[i].std.setEngine(parent);
|
||||
|
|
@ -399,6 +749,11 @@ void DivPlatformAmiga::reset() {
|
|||
}
|
||||
filterOn=false;
|
||||
filtConst=filterOn?filtConstOn:filtConstOff;
|
||||
updateADKCon=true;
|
||||
|
||||
amiga=Amiga();
|
||||
// enable DMA
|
||||
rWrite(0x96,0x8200);
|
||||
}
|
||||
|
||||
int DivPlatformAmiga::getOutputCount() {
|
||||
|
|
@ -413,6 +768,18 @@ DivMacroInt* DivPlatformAmiga::getChanMacroInt(int ch) {
|
|||
return &chan[ch].std;
|
||||
}
|
||||
|
||||
DivSamplePos DivPlatformAmiga::getSamplePos(int ch) {
|
||||
if (ch>=4) return DivSamplePos();
|
||||
if (chan[ch].sample<0 || chan[ch].sample>=parent->song.sampleLen) return DivSamplePos();
|
||||
int audPer=amiga.audPer[ch];
|
||||
if (audPer<1) audPer=1;
|
||||
return DivSamplePos(
|
||||
chan[ch].sample,
|
||||
amiga.dmaLoc[ch]-sampleOff[chan[ch].sample],
|
||||
chipClock/audPer
|
||||
);
|
||||
}
|
||||
|
||||
void DivPlatformAmiga::notifyInsChange(int ins) {
|
||||
for (int i=0; i<4; i++) {
|
||||
if (chan[i].ins==ins) {
|
||||
|
|
@ -425,6 +792,7 @@ void DivPlatformAmiga::notifyWaveChange(int wave) {
|
|||
for (int i=0; i<4; i++) {
|
||||
if (chan[i].useWave && chan[i].wave==wave) {
|
||||
chan[i].ws.changeWave1(wave);
|
||||
chan[i].updateWave=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -449,6 +817,10 @@ void DivPlatformAmiga::setFlags(const DivConfig& flags) {
|
|||
sep1=sep+127;
|
||||
sep2=127-sep;
|
||||
amigaModel=flags.getInt("chipType",0);
|
||||
chipMem=flags.getInt("chipMem",21);
|
||||
if (chipMem<18) chipMem=18;
|
||||
if (chipMem>21) chipMem=21;
|
||||
chipMask=(1<<chipMem)-1;
|
||||
bypassLimits=flags.getBool("bypassLimits",false);
|
||||
if (amigaModel) {
|
||||
filtConstOff=4000;
|
||||
|
|
@ -459,6 +831,116 @@ void DivPlatformAmiga::setFlags(const DivConfig& flags) {
|
|||
}
|
||||
}
|
||||
|
||||
void DivPlatformAmiga::poke(unsigned int addr, unsigned short val) {
|
||||
rWrite(addr,val);
|
||||
}
|
||||
|
||||
void DivPlatformAmiga::poke(std::vector<DivRegWrite>& wlist) {
|
||||
for (DivRegWrite& i: wlist) rWrite(i.addr,i.val);
|
||||
}
|
||||
|
||||
unsigned char* DivPlatformAmiga::getRegisterPool() {
|
||||
// update DMACONR
|
||||
regPool[1]=(
|
||||
(amiga.audEn[0]?1:0)|
|
||||
(amiga.audEn[1]?2:0)|
|
||||
(amiga.audEn[2]?4:0)|
|
||||
(amiga.audEn[3]?8:0)|
|
||||
(amiga.dmaEn?512:0)
|
||||
);
|
||||
|
||||
// update ADKCONR
|
||||
regPool[0x10>>1]=(
|
||||
(amiga.useV[0]?1:0)|
|
||||
(amiga.useV[1]?2:0)|
|
||||
(amiga.useV[2]?4:0)|
|
||||
(amiga.useV[3]?8:0)|
|
||||
(amiga.useP[0]?16:0)|
|
||||
(amiga.useP[1]?32:0)|
|
||||
(amiga.useP[2]?64:0)|
|
||||
(amiga.useP[3]?128:0)
|
||||
);
|
||||
|
||||
// update INTENAR
|
||||
regPool[0x1c>>1]=(
|
||||
(amiga.audInt[0]?128:0)|
|
||||
(amiga.audInt[1]?256:0)|
|
||||
(amiga.audInt[2]?512:0)|
|
||||
(amiga.audInt[3]?1024:0)|
|
||||
16384 // INTEN
|
||||
);
|
||||
|
||||
// update INTREQR
|
||||
regPool[0x1e>>1]=(
|
||||
(amiga.audIr[0]?128:0)|
|
||||
(amiga.audIr[1]?256:0)|
|
||||
(amiga.audIr[2]?512:0)|
|
||||
(amiga.audIr[3]?1024:0)
|
||||
);
|
||||
|
||||
return (unsigned char*)regPool;
|
||||
}
|
||||
|
||||
int DivPlatformAmiga::getRegisterPoolSize() {
|
||||
return 128;
|
||||
}
|
||||
|
||||
int DivPlatformAmiga::getRegisterPoolDepth() {
|
||||
return 16;
|
||||
}
|
||||
|
||||
const void* DivPlatformAmiga::getSampleMem(int index) {
|
||||
return index == 0 ? sampleMem : NULL;
|
||||
}
|
||||
|
||||
size_t DivPlatformAmiga::getSampleMemCapacity(int index) {
|
||||
return index == 0 ? (1<<chipMem) : 0;
|
||||
}
|
||||
|
||||
size_t DivPlatformAmiga::getSampleMemUsage(int index) {
|
||||
return index == 0 ? sampleMemLen : 0;
|
||||
}
|
||||
|
||||
bool DivPlatformAmiga::isSampleLoaded(int index, int sample) {
|
||||
if (index!=0) return false;
|
||||
if (sample<0 || sample>255) return false;
|
||||
return sampleLoaded[sample];
|
||||
}
|
||||
|
||||
void DivPlatformAmiga::renderSamples(int sysID) {
|
||||
memset(sampleMem,0,2097152);
|
||||
memset(sampleOff,0,256*sizeof(unsigned int));
|
||||
memset(sampleLoaded,0,256*sizeof(bool));
|
||||
|
||||
// first 1024 bytes reserved for wavetable
|
||||
// the next 2 bytes are reserved for end of sample
|
||||
size_t memPos=1026;
|
||||
for (int i=0; i<parent->song.sampleLen; i++) {
|
||||
DivSample* s=parent->song.sample[i];
|
||||
if (!s->renderOn[0][sysID]) {
|
||||
sampleOff[i]=0;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (memPos>=getSampleMemCapacity()) {
|
||||
logW("out of Amiga memory for sample %d!",i);
|
||||
break;
|
||||
}
|
||||
|
||||
int length=s->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT);
|
||||
int actualLength=MIN((int)(getSampleMemCapacity()-memPos),length);
|
||||
if (actualLength>0) {
|
||||
sampleOff[i]=memPos;
|
||||
memcpy(&sampleMem[memPos],s->data8,actualLength);
|
||||
memPos+=actualLength;
|
||||
}
|
||||
// align memPos to short
|
||||
if (memPos&1) memPos++;
|
||||
sampleLoaded[i]=true;
|
||||
}
|
||||
sampleMemLen=memPos;
|
||||
}
|
||||
|
||||
int DivPlatformAmiga::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) {
|
||||
parent=p;
|
||||
dumpWrites=false;
|
||||
|
|
@ -467,12 +949,28 @@ int DivPlatformAmiga::init(DivEngine* p, int channels, int sugRate, const DivCon
|
|||
oscBuf[i]=new DivDispatchOscBuffer;
|
||||
isMuted[i]=false;
|
||||
}
|
||||
|
||||
// Paula volume is implemented using PWM rather than a multiplication.
|
||||
// sources:
|
||||
// - https://www.youtube.com/watch?v=xyQlmsD7PAg
|
||||
// - https://linusakesson.net/music/paulimba/index.php
|
||||
memset(volTable,0,64*64);
|
||||
for (int i=0; i<64; i++) {
|
||||
for (int j=0; j<64; j++) {
|
||||
volTable[i][j/AMIGA_DIVIDER]+=(j<i)*(64/AMIGA_DIVIDER);
|
||||
}
|
||||
}
|
||||
|
||||
sampleMem=new unsigned char[2097152];
|
||||
sampleMemLen=0;
|
||||
|
||||
setFlags(flags);
|
||||
reset();
|
||||
return 6;
|
||||
}
|
||||
|
||||
void DivPlatformAmiga::quit() {
|
||||
delete[] sampleMem;
|
||||
for (int i=0; i<4; i++) {
|
||||
delete oscBuf[i];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,29 +26,32 @@
|
|||
|
||||
class DivPlatformAmiga: public DivDispatch {
|
||||
struct Channel: public SharedChannel<signed char> {
|
||||
unsigned int audLoc;
|
||||
unsigned short audLen;
|
||||
unsigned short audLen, irLocL, irLocH, irLen;
|
||||
unsigned int audPos;
|
||||
int audSub;
|
||||
signed char audDat;
|
||||
unsigned char volPos;
|
||||
int sample, wave;
|
||||
int busClock;
|
||||
bool useWave, setPos, useV, useP;
|
||||
bool useWave, setPos, useV, useP, dmaOn, audDatClock, writeVol, updateWave;
|
||||
DivWaveSynth ws;
|
||||
Channel():
|
||||
SharedChannel<signed char>(64),
|
||||
audLoc(0),
|
||||
audLen(0),
|
||||
irLocL(0),
|
||||
irLocH(0),
|
||||
irLen(2),
|
||||
audPos(0),
|
||||
audSub(0),
|
||||
audDat(0),
|
||||
volPos(0),
|
||||
sample(-1),
|
||||
wave(-1),
|
||||
busClock(0),
|
||||
useWave(false),
|
||||
setPos(false),
|
||||
useV(false),
|
||||
useP(false) {}
|
||||
useP(false),
|
||||
dmaOn(false),
|
||||
audDatClock(false),
|
||||
writeVol(true),
|
||||
updateWave(true) {}
|
||||
};
|
||||
Channel chan[4];
|
||||
DivDispatchOscBuffer* oscBuf[4];
|
||||
|
|
@ -56,21 +59,77 @@ class DivPlatformAmiga: public DivDispatch {
|
|||
bool bypassLimits;
|
||||
bool amigaModel;
|
||||
bool filterOn;
|
||||
bool updateADKCon;
|
||||
|
||||
struct Amiga {
|
||||
// register state
|
||||
bool audInt[4]; // interrupt on
|
||||
bool audIr[4]; // interrupt request
|
||||
bool audEn[4]; // audio DMA on
|
||||
bool useP[4]; // period modulation
|
||||
bool useV[4]; // volume modulation
|
||||
|
||||
bool dmaEn;
|
||||
|
||||
unsigned int audLoc[4]; // address
|
||||
unsigned short audLen[4]; // length
|
||||
unsigned short audPer[4]; // period
|
||||
unsigned char audVol[4]; // volume
|
||||
signed char audDat[2][4]; // data
|
||||
signed char nextOut[4];
|
||||
unsigned short nextOut2[4];
|
||||
|
||||
|
||||
// internal state
|
||||
int audTick[4]; // tick of period
|
||||
unsigned int dmaLoc[4]; // address
|
||||
unsigned short dmaLen[4]; // position
|
||||
|
||||
bool audByte[4]; // which byte of audDat to output
|
||||
bool audWord[4]; // for P/V
|
||||
bool incLoc[4]; // whether dmaLoc/dmaLen should be updated
|
||||
unsigned char volPos; // position of volume PWM
|
||||
unsigned short hPos; // horizontal position of beam
|
||||
unsigned char state[4]; // current channel state
|
||||
|
||||
Amiga() {
|
||||
memset(this,0,sizeof(*this));
|
||||
}
|
||||
} amiga;
|
||||
|
||||
int filter[2][4];
|
||||
int filtConst;
|
||||
int filtConstOff, filtConstOn;
|
||||
int chipMem, chipMask;
|
||||
|
||||
unsigned char volTable[64][64];
|
||||
|
||||
unsigned int sampleOff[256];
|
||||
bool sampleLoaded[256];
|
||||
|
||||
unsigned short regPool[256];
|
||||
|
||||
unsigned char* sampleMem;
|
||||
size_t sampleMemLen;
|
||||
|
||||
int sep1, sep2;
|
||||
|
||||
friend void putDispatchChip(void*,int);
|
||||
friend void putDispatchChan(void*,int,int);
|
||||
friend class DivExportAmigaValidation;
|
||||
|
||||
void irq(int ch);
|
||||
void rWrite(unsigned short addr, unsigned short val);
|
||||
void updateWave(int ch);
|
||||
|
||||
public:
|
||||
void acquire(short** buf, size_t len);
|
||||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
int getRegisterPoolDepth();
|
||||
void reset();
|
||||
void forceIns();
|
||||
void tick(bool sysTick=true);
|
||||
|
|
@ -78,11 +137,19 @@ class DivPlatformAmiga: public DivDispatch {
|
|||
int getOutputCount();
|
||||
bool keyOffAffectsArp(int ch);
|
||||
DivMacroInt* getChanMacroInt(int ch);
|
||||
DivSamplePos getSamplePos(int ch);
|
||||
void setFlags(const DivConfig& flags);
|
||||
void notifyInsChange(int ins);
|
||||
void notifyWaveChange(int wave);
|
||||
void notifyInsDeletion(void* ins);
|
||||
void renderSamples(int chipID);
|
||||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
const void* getSampleMem(int index=0);
|
||||
size_t getSampleMemCapacity(int index=0);
|
||||
size_t getSampleMemUsage(int index=0);
|
||||
bool isSampleLoaded(int index, int sample);
|
||||
int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags);
|
||||
void quit();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ void DivPlatformAY8910::runDAC() {
|
|||
bool end=false;
|
||||
bool changed=false;
|
||||
int prevOut=chan[i].dac.out;
|
||||
while (chan[i].dac.period>rate && !end) {
|
||||
while (chan[i].dac.period>dacRate && !end) {
|
||||
DivSample* s=parent->getSample(chan[i].dac.sample);
|
||||
if (s->samples<=0) {
|
||||
chan[i].dac.sample=-1;
|
||||
|
|
@ -143,7 +143,7 @@ void DivPlatformAY8910::runDAC() {
|
|||
end=true;
|
||||
break;
|
||||
}
|
||||
chan[i].dac.period-=rate;
|
||||
chan[i].dac.period-=dacRate;
|
||||
}
|
||||
if (changed && !end) {
|
||||
if (!isMuted[i]) {
|
||||
|
|
@ -292,8 +292,6 @@ void DivPlatformAY8910::tick(bool sysTick) {
|
|||
if (chan[i].std.phaseReset.val==1) {
|
||||
if (chan[i].nextPSGMode.dac) {
|
||||
if (dumpWrites) addWrite(0xffff0002+(i<<8),0);
|
||||
DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_AY);
|
||||
chan[i].dac.sample=ins->amiga.getSample(chan[i].note);
|
||||
if (chan[i].dac.sample<0 || chan[i].dac.sample>=parent->song.sampleLen) {
|
||||
if (dumpWrites) {
|
||||
rWrite(0x08+i,0);
|
||||
|
|
@ -405,7 +403,10 @@ int DivPlatformAY8910::dispatch(DivCommand c) {
|
|||
if (chan[c.chan].nextPSGMode.dac) {
|
||||
if (skipRegisterWrites) break;
|
||||
if (!parent->song.disableSampleMacro && (ins->type==DIV_INS_AMIGA || ins->amiga.useSample)) {
|
||||
if (c.value!=DIV_NOTE_NULL) chan[c.chan].dac.sample=ins->amiga.getSample(c.value);
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].dac.sample=ins->amiga.getSample(c.value);
|
||||
c.value=ins->amiga.getFreq(c.value);
|
||||
}
|
||||
if (chan[c.chan].dac.sample<0 || chan[c.chan].dac.sample>=parent->song.sampleLen) {
|
||||
chan[c.chan].dac.sample=-1;
|
||||
if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0);
|
||||
|
|
@ -686,6 +687,7 @@ void DivPlatformAY8910::muteChannel(int ch, bool mute) {
|
|||
void DivPlatformAY8910::forceIns() {
|
||||
for (int i=0; i<3; i++) {
|
||||
chan[i].insChanged=true;
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
immWrite(0x0b,ayEnvPeriod);
|
||||
immWrite(0x0c,ayEnvPeriod>>8);
|
||||
|
|
@ -700,6 +702,15 @@ DivMacroInt* DivPlatformAY8910::getChanMacroInt(int ch) {
|
|||
return &chan[ch].std;
|
||||
}
|
||||
|
||||
DivSamplePos DivPlatformAY8910::getSamplePos(int ch) {
|
||||
if (ch>=3) return DivSamplePos();
|
||||
return DivSamplePos(
|
||||
chan[ch].dac.sample,
|
||||
chan[ch].dac.pos,
|
||||
chan[ch].dac.rate
|
||||
);
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformAY8910::getOscBuffer(int ch) {
|
||||
return oscBuf[ch];
|
||||
}
|
||||
|
|
@ -786,6 +797,7 @@ void DivPlatformAY8910::setFlags(const DivConfig& flags) {
|
|||
chipClock=extClock;
|
||||
rate=chipClock/extDiv;
|
||||
clockSel=false;
|
||||
dacRate=chipClock/dacRateDiv;
|
||||
} else {
|
||||
clockSel=flags.getBool("halfClock",false);
|
||||
switch (flags.getInt("clockSel",0)) {
|
||||
|
|
@ -840,6 +852,7 @@ void DivPlatformAY8910::setFlags(const DivConfig& flags) {
|
|||
}
|
||||
CHECK_CUSTOM_CLOCK;
|
||||
rate=chipClock/8;
|
||||
dacRate=rate;
|
||||
}
|
||||
for (int i=0; i<3; i++) {
|
||||
oscBuf[i]->rate=rate;
|
||||
|
|
|
|||
|
|
@ -104,7 +104,9 @@ class DivPlatformAY8910: public DivDispatch {
|
|||
|
||||
bool extMode;
|
||||
unsigned int extClock;
|
||||
int dacRate;
|
||||
unsigned char extDiv;
|
||||
unsigned char dacRateDiv;
|
||||
|
||||
bool stereo, sunsoft, intellivision, clockSel;
|
||||
bool ioPortA, ioPortB;
|
||||
|
|
@ -119,7 +121,6 @@ class DivPlatformAY8910: public DivDispatch {
|
|||
short* ayBuf[3];
|
||||
size_t ayBufLen;
|
||||
|
||||
void runDAC();
|
||||
void checkWrites();
|
||||
void updateOutSel(bool immediate=false);
|
||||
|
||||
|
|
@ -127,6 +128,7 @@ class DivPlatformAY8910: public DivDispatch {
|
|||
friend void putDispatchChan(void*,int,int);
|
||||
|
||||
public:
|
||||
void runDAC();
|
||||
void setExtClockDiv(unsigned int eclk=COLOR_NTSC, unsigned char ediv=8);
|
||||
void acquire(short** buf, size_t len);
|
||||
int dispatch(DivCommand c);
|
||||
|
|
@ -143,6 +145,7 @@ class DivPlatformAY8910: public DivDispatch {
|
|||
int getOutputCount();
|
||||
bool keyOffAffectsArp(int ch);
|
||||
DivMacroInt* getChanMacroInt(int ch);
|
||||
DivSamplePos getSamplePos(int ch);
|
||||
bool getDCOffRequired();
|
||||
void notifyInsDeletion(void* ins);
|
||||
void poke(unsigned int addr, unsigned short val);
|
||||
|
|
@ -150,10 +153,11 @@ class DivPlatformAY8910: public DivDispatch {
|
|||
const char** getRegisterSheet();
|
||||
int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags);
|
||||
void quit();
|
||||
DivPlatformAY8910(bool useExtMode=false, unsigned int eclk=COLOR_NTSC, unsigned char ediv=8):
|
||||
DivPlatformAY8910(bool useExtMode=false, unsigned int eclk=COLOR_NTSC, unsigned char ediv=8, unsigned char ddiv=24):
|
||||
DivDispatch(),
|
||||
extMode(useExtMode),
|
||||
extClock(eclk),
|
||||
extDiv(ediv) {}
|
||||
extDiv(ediv),
|
||||
dacRateDiv(ddiv) {}
|
||||
};
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -280,8 +280,6 @@ void DivPlatformAY8930::tick(bool sysTick) {
|
|||
if (chan[i].std.phaseReset.val==1) {
|
||||
if (chan[i].nextPSGMode.dac) {
|
||||
if (dumpWrites) addWrite(0xffff0002+(i<<8),0);
|
||||
DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_AY8930);
|
||||
chan[i].dac.sample=ins->amiga.getSample(chan[i].note);
|
||||
if (chan[i].dac.sample<0 || chan[i].dac.sample>=parent->song.sampleLen) {
|
||||
if (dumpWrites) {
|
||||
rWrite(0x08+i,0);
|
||||
|
|
@ -406,7 +404,10 @@ int DivPlatformAY8930::dispatch(DivCommand c) {
|
|||
if (chan[c.chan].nextPSGMode.dac) {
|
||||
if (skipRegisterWrites) break;
|
||||
if (ins->type==DIV_INS_AMIGA || ins->amiga.useSample) {
|
||||
if (c.value!=DIV_NOTE_NULL) chan[c.chan].dac.sample=ins->amiga.getSample(c.value);
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].dac.sample=ins->amiga.getSample(c.value);
|
||||
c.value=ins->amiga.getFreq(c.value);
|
||||
}
|
||||
if (chan[c.chan].dac.sample<0 || chan[c.chan].dac.sample>=parent->song.sampleLen) {
|
||||
chan[c.chan].dac.sample=-1;
|
||||
if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0);
|
||||
|
|
@ -696,6 +697,15 @@ DivMacroInt* DivPlatformAY8930::getChanMacroInt(int ch) {
|
|||
return &chan[ch].std;
|
||||
}
|
||||
|
||||
DivSamplePos DivPlatformAY8930::getSamplePos(int ch) {
|
||||
if (ch>=3) return DivSamplePos();
|
||||
return DivSamplePos(
|
||||
chan[ch].dac.sample,
|
||||
chan[ch].dac.pos,
|
||||
chan[ch].dac.rate
|
||||
);
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformAY8930::getOscBuffer(int ch) {
|
||||
return oscBuf[ch];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -145,6 +145,7 @@ class DivPlatformAY8930: public DivDispatch {
|
|||
int getOutputCount();
|
||||
bool keyOffAffectsArp(int ch);
|
||||
DivMacroInt* getChanMacroInt(int ch);
|
||||
DivSamplePos getSamplePos(int ch);
|
||||
void notifyInsDeletion(void* ins);
|
||||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
|
|
|
|||
|
|
@ -410,7 +410,7 @@ void DivPlatformES5506::tick(bool sysTick) {
|
|||
if (chan[i].pcmChanged.changed) {
|
||||
if (chan[i].pcmChanged.index) {
|
||||
const int next=chan[i].pcm.next;
|
||||
bool sampleVaild=false;
|
||||
bool sampleValid=false;
|
||||
if (((ins->amiga.useNoteMap) && (next>=0 && next<120)) ||
|
||||
((!ins->amiga.useNoteMap) && (next>=0 && next<parent->song.sampleLen))) {
|
||||
DivInstrumentAmiga::SampleMap& noteMapind=ins->amiga.noteMap[next];
|
||||
|
|
@ -420,7 +420,7 @@ void DivPlatformES5506::tick(bool sysTick) {
|
|||
}
|
||||
if (sample>=0 && sample<parent->song.sampleLen) {
|
||||
const unsigned int offES5506=sampleOffES5506[sample];
|
||||
sampleVaild=true;
|
||||
sampleValid=true;
|
||||
chan[i].pcm.index=sample;
|
||||
chan[i].pcm.isNoteMap=ins->amiga.useNoteMap;
|
||||
DivSample* s=parent->getSample(sample);
|
||||
|
|
@ -433,7 +433,6 @@ void DivPlatformES5506::tick(bool sysTick) {
|
|||
off=(double)center/8363.0;
|
||||
}
|
||||
if (ins->amiga.useNoteMap) {
|
||||
off*=(double)noteMapind.freq/((double)MAX(1,center)*pow(2.0,((double)next-48.0)/12.0));
|
||||
chan[i].pcm.note=next;
|
||||
}
|
||||
// get loop mode
|
||||
|
|
@ -459,7 +458,7 @@ void DivPlatformES5506::tick(bool sysTick) {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (sampleVaild) {
|
||||
if (sampleValid) {
|
||||
if (!chan[i].keyOn) {
|
||||
pageWrite(0x20|i,0x03,(chan[i].pcm.direction)?chan[i].pcm.end:chan[i].pcm.start);
|
||||
}
|
||||
|
|
@ -618,10 +617,6 @@ void DivPlatformES5506::tick(bool sysTick) {
|
|||
} else {
|
||||
off=(double)center/8363.0;
|
||||
}
|
||||
if (ins->amiga.useNoteMap) {
|
||||
DivInstrumentAmiga::SampleMap& noteMapind=ins->amiga.noteMap[chan[i].pcm.note];
|
||||
off*=(double)noteMapind.freq/((double)MAX(1,center)*pow(2.0,((double)chan[i].pcm.note-48.0)/12.0));
|
||||
}
|
||||
chan[i].pcm.loopStart=(chan[i].pcm.start+(s->loopStart<<11))&0xfffff800;
|
||||
chan[i].pcm.loopEnd=(chan[i].pcm.start+((s->loopEnd-1)<<11))&0xffffff80;
|
||||
chan[i].pcm.freqOffs=PITCH_OFFSET*off;
|
||||
|
|
@ -750,24 +745,22 @@ int DivPlatformES5506::dispatch(DivCommand c) {
|
|||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON: {
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_ES5506);
|
||||
bool sampleVaild=false;
|
||||
bool sampleValid=false;
|
||||
if (((ins->amiga.useNoteMap) && (c.value>=0 && c.value<120)) ||
|
||||
((!ins->amiga.useNoteMap) && (ins->amiga.initSample>=0 && ins->amiga.initSample<parent->song.sampleLen))) {
|
||||
DivInstrumentAmiga::SampleMap& noteMapind=ins->amiga.noteMap[c.value];
|
||||
int sample=ins->amiga.initSample;
|
||||
if (ins->amiga.useNoteMap) {
|
||||
sample=noteMapind.map;
|
||||
}
|
||||
int sample=ins->amiga.getSample(c.value);
|
||||
c.value=ins->amiga.getFreq(c.value);
|
||||
if (sample>=0 && sample<parent->song.sampleLen) {
|
||||
sampleVaild=true;
|
||||
sampleValid=true;
|
||||
chan[c.chan].volMacroMax=ins->type==DIV_INS_AMIGA?64:0xfff;
|
||||
chan[c.chan].panMacroMax=ins->type==DIV_INS_AMIGA?127:0xfff;
|
||||
chan[c.chan].pcm.next=sample;
|
||||
chan[c.chan].pcm.note=c.value;
|
||||
chan[c.chan].pcm.next=ins->amiga.useNoteMap?c.value:sample;
|
||||
chan[c.chan].filter=ins->es5506.filter;
|
||||
chan[c.chan].envelope=ins->es5506.envelope;
|
||||
}
|
||||
}
|
||||
if (!sampleVaild) {
|
||||
if (!sampleValid) {
|
||||
chan[c.chan].pcm.index=chan[c.chan].pcm.next=-1;
|
||||
chan[c.chan].filter=DivInstrumentES5506::Filter();
|
||||
chan[c.chan].envelope=DivInstrumentES5506::Envelope();
|
||||
|
|
|
|||
|
|
@ -200,7 +200,10 @@ int DivPlatformGA20::dispatch(DivCommand c) {
|
|||
case DIV_CMD_NOTE_ON: {
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA);
|
||||
chan[c.chan].macroVolMul=ins->type==DIV_INS_AMIGA?64:255;
|
||||
if (c.value!=DIV_NOTE_NULL) chan[c.chan].sample=ins->amiga.getSample(c.value);
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].sample=ins->amiga.getSample(c.value);
|
||||
c.value=ins->amiga.getFreq(c.value);
|
||||
}
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value);
|
||||
}
|
||||
|
|
@ -336,6 +339,18 @@ DivMacroInt* DivPlatformGA20::getChanMacroInt(int ch) {
|
|||
return &chan[ch].std;
|
||||
}
|
||||
|
||||
DivSamplePos DivPlatformGA20::getSamplePos(int ch) {
|
||||
if (ch>=4) return DivSamplePos();
|
||||
if (chan[ch].sample<0 || chan[ch].sample>=parent->song.sampleLen) return DivSamplePos();
|
||||
if (!ga20.is_playing(ch)) return DivSamplePos();
|
||||
unsigned char f=chan[ch].freq;
|
||||
return DivSamplePos(
|
||||
chan[ch].sample,
|
||||
ga20.get_position(ch)-sampleOffGA20[chan[ch].sample],
|
||||
chipClock/(4*(0x100-(int)f))
|
||||
);
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformGA20::getOscBuffer(int ch) {
|
||||
return oscBuf[ch];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@ class DivPlatformGA20: public DivDispatch, public iremga20_intf {
|
|||
virtual int dispatch(DivCommand c) override;
|
||||
virtual void* getChanState(int chan) override;
|
||||
virtual DivMacroInt* getChanMacroInt(int ch) override;
|
||||
virtual DivSamplePos getSamplePos(int ch) override;
|
||||
virtual DivDispatchOscBuffer* getOscBuffer(int chan) override;
|
||||
virtual unsigned char* getRegisterPool() override;
|
||||
virtual int getRegisterPoolSize() override;
|
||||
|
|
|
|||
|
|
@ -82,8 +82,12 @@ void DivPlatformGB::acquire(short** buf, size_t len) {
|
|||
void DivPlatformGB::updateWave() {
|
||||
rWrite(0x1a,0);
|
||||
for (int i=0; i<16; i++) {
|
||||
int nibble1=15-ws.output[((i<<1)+antiClickWavePos-1)&31];
|
||||
int nibble2=15-ws.output[((1+(i<<1))+antiClickWavePos-1)&31];
|
||||
int nibble1=ws.output[((i<<1)+antiClickWavePos)&31];
|
||||
int nibble2=ws.output[((1+(i<<1))+antiClickWavePos)&31];
|
||||
if (invertWave) {
|
||||
nibble1^=15;
|
||||
nibble2^=15;
|
||||
}
|
||||
rWrite(0x30+i,(nibble1<<4)|nibble2);
|
||||
}
|
||||
antiClickWavePos&=31;
|
||||
|
|
@ -658,6 +662,7 @@ void DivPlatformGB::setFlags(const DivConfig& flags) {
|
|||
model=GB_MODEL_AGB;
|
||||
break;
|
||||
}
|
||||
invertWave=flags.getBool("invertWave",true);
|
||||
enoughAlready=flags.getBool("enoughAlready",false);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ class DivPlatformGB: public DivDispatch {
|
|||
DivDispatchOscBuffer* oscBuf[4];
|
||||
bool isMuted[4];
|
||||
bool antiClickEnabled;
|
||||
bool invertWave;
|
||||
bool enoughAlready;
|
||||
unsigned char lastPan;
|
||||
DivWaveSynth ws;
|
||||
|
|
|
|||
|
|
@ -172,7 +172,14 @@ void DivPlatformGenesis::acquire_nuked(short** buf, size_t len) {
|
|||
flushFirst=false;
|
||||
}
|
||||
|
||||
OPN2_Clock(&fm,o); os[0]+=o[0]; os[1]+=o[1];
|
||||
OPN2_Clock(&fm,o);
|
||||
if (chipType==2) {
|
||||
os[0]+=CLAMP(o[0],-8192,8191);
|
||||
os[1]+=CLAMP(o[1],-8192,8191);
|
||||
} else {
|
||||
os[0]+=o[0];
|
||||
os[1]+=o[1];
|
||||
}
|
||||
//OPN2_Write(&fm,0,0);
|
||||
if (i==5) {
|
||||
if (fm.dacen) {
|
||||
|
|
@ -674,7 +681,10 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
|
|||
if (c.chan>=5 && chan[c.chan].dacMode) {
|
||||
//if (skipRegisterWrites) break;
|
||||
if (ins->type==DIV_INS_AMIGA) { // Furnace mode
|
||||
if (c.value!=DIV_NOTE_NULL) chan[c.chan].dacSample=ins->amiga.getSample(c.value);
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].dacSample=ins->amiga.getSample(c.value);
|
||||
c.value=ins->amiga.getFreq(c.value);
|
||||
}
|
||||
if (chan[c.chan].dacSample<0 || chan[c.chan].dacSample>=parent->song.sampleLen) {
|
||||
chan[c.chan].dacSample=-1;
|
||||
if (dumpWrites) addWrite(0xffff0002,0);
|
||||
|
|
@ -1202,6 +1212,17 @@ DivMacroInt* DivPlatformGenesis::getChanMacroInt(int ch) {
|
|||
return &chan[ch].std;
|
||||
}
|
||||
|
||||
DivSamplePos DivPlatformGenesis::getSamplePos(int ch) {
|
||||
if (!chan[5].dacMode) return DivSamplePos();
|
||||
if (ch<5) return DivSamplePos();
|
||||
if (ch>5 && !softPCM) return DivSamplePos();
|
||||
return DivSamplePos(
|
||||
chan[ch].dacSample,
|
||||
chan[ch].dacPos,
|
||||
chan[ch].dacRate
|
||||
);
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformGenesis::getOscBuffer(int ch) {
|
||||
return oscBuf[ch];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -105,6 +105,7 @@ class DivPlatformGenesis: public DivPlatformOPN {
|
|||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
DivMacroInt* getChanMacroInt(int ch);
|
||||
DivSamplePos getSamplePos(int ch);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
|
|
|
|||
|
|
@ -433,13 +433,8 @@ void DivPlatformGenesisExt::muteChannel(int ch, bool mute) {
|
|||
DivInstrumentFM::Operator op=chan[2].state.op[ordch];
|
||||
if (isOpMuted[ch-2] || !op.enable) {
|
||||
rWrite(baseAddr+0x40,127);
|
||||
immWrite(baseAddr+0x40,127);
|
||||
} else if (KVS(2,ordch)) {
|
||||
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-2].outVol&0x7f,127));
|
||||
immWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-2].outVol&0x7f,127));
|
||||
} else {
|
||||
rWrite(baseAddr+0x40,op.tl);
|
||||
immWrite(baseAddr+0x40,op.tl);
|
||||
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-2].outVol&0x7f,127));
|
||||
}
|
||||
|
||||
rWrite(chanOffs[2]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch-2].pan<<6))|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4));
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ const char* regCheatSheetK007232[]={
|
|||
"CHX_StartM", "X*6+3",
|
||||
"CHX_StartH", "X*6+4",
|
||||
"CHX_Keyon", "X*6+5",
|
||||
"SLEV", "C", // external IO
|
||||
"SLEV", "C", // external IO (Volume for Mono speaker)
|
||||
"Loop", "D",
|
||||
// off-chip
|
||||
"CHX_Volume", "X*2+10",
|
||||
|
|
@ -157,8 +157,7 @@ void DivPlatformK007232::tick(bool sysTick) {
|
|||
rWrite(0x10+i,(chan[i].lvol&0xf)|((chan[i].rvol&0xf)<<4));
|
||||
chan[i].prevPan=newPan;
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
const unsigned char prevVolume=lastVolume;
|
||||
lastVolume=(lastVolume&~(0xf<<(i<<2)))|((chan[i].resVol&0xf)<<(i<<2));
|
||||
if (prevVolume!=lastVolume) {
|
||||
|
|
@ -274,7 +273,10 @@ int DivPlatformK007232::dispatch(DivCommand c) {
|
|||
case DIV_CMD_NOTE_ON: {
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA);
|
||||
chan[c.chan].macroVolMul=ins->type==DIV_INS_AMIGA?64:15;
|
||||
if (c.value!=DIV_NOTE_NULL) chan[c.chan].sample=ins->amiga.getSample(c.value);
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].sample=ins->amiga.getSample(c.value);
|
||||
c.value=ins->amiga.getFreq(c.value);
|
||||
}
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value);
|
||||
}
|
||||
|
|
@ -477,6 +479,7 @@ void DivPlatformK007232::setFlags(const DivConfig& flags) {
|
|||
rate=chipClock/4;
|
||||
stereo=flags.getBool("stereo",false);
|
||||
for (int i=0; i<2; i++) {
|
||||
chan[i].volumeChanged=true;
|
||||
oscBuf[i]->rate=rate;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -261,13 +261,14 @@ int DivPlatformLynx::dispatch(DivCommand c) {
|
|||
chan[c.chan].macroVolMul=ins->type==DIV_INS_AMIGA?64:127;
|
||||
chan[c.chan].pcm=(ins->type==DIV_INS_AMIGA || ins->amiga.useSample);
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value);
|
||||
if (chan[c.chan].pcm) {
|
||||
chan[c.chan].sample=ins->amiga.getSample(c.value);
|
||||
c.value=ins->amiga.getFreq(c.value);
|
||||
chan[c.chan].sampleBaseFreq=NOTE_FREQUENCY(c.value);
|
||||
if (c.value!=DIV_NOTE_NULL) chan[c.chan].sample=ins->amiga.getSample(c.value);
|
||||
chan[c.chan].sampleAccum=0;
|
||||
chan[c.chan].samplePos=0;
|
||||
}
|
||||
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value);
|
||||
chan[c.chan].freqChanged=true;
|
||||
chan[c.chan].note=c.value;
|
||||
chan[c.chan].actualNote=c.value;
|
||||
|
|
@ -415,6 +416,16 @@ DivMacroInt* DivPlatformLynx::getChanMacroInt(int ch) {
|
|||
return &chan[ch].std;
|
||||
}
|
||||
|
||||
DivSamplePos DivPlatformLynx::getSamplePos(int ch) {
|
||||
if (ch>=4) return DivSamplePos();
|
||||
if (!chan[ch].pcm) return DivSamplePos();
|
||||
return DivSamplePos(
|
||||
chan[ch].sample,
|
||||
chan[ch].samplePos,
|
||||
chan[ch].sampleFreq
|
||||
);
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformLynx::getOscBuffer(int ch) {
|
||||
return oscBuf[ch];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ class DivPlatformLynx: public DivDispatch {
|
|||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
DivMacroInt* getChanMacroInt(int ch);
|
||||
DivSamplePos getSamplePos(int ch);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
|
|
|
|||
|
|
@ -176,7 +176,10 @@ int DivPlatformMMC5::dispatch(DivCommand c) {
|
|||
if (c.chan==2) { // PCM
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_STD);
|
||||
if (ins->type==DIV_INS_AMIGA) {
|
||||
if (c.value!=DIV_NOTE_NULL) dacSample=ins->amiga.getSample(c.value);
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
dacSample=ins->amiga.getSample(c.value);
|
||||
c.value=ins->amiga.getFreq(c.value);
|
||||
}
|
||||
if (dacSample<0 || dacSample>=parent->song.sampleLen) {
|
||||
dacSample=-1;
|
||||
if (dumpWrites) addWrite(0xffff0002,0);
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
#include "n163.h"
|
||||
#include "../engine.h"
|
||||
#include "../../ta-log.h"
|
||||
#include <math.h>
|
||||
|
||||
#define rRead(a,v) n163.addr_w(a); n163.data_r(v);
|
||||
|
|
@ -166,6 +167,7 @@ void DivPlatformN163::updateWave(int ch, int wave, int pos, int len) {
|
|||
|
||||
void DivPlatformN163::updateWaveCh(int ch) {
|
||||
if (ch<=chanMax) {
|
||||
logV("updateWave with pos %d and len %d",chan[ch].wavePos,chan[ch].waveLen);
|
||||
updateWave(ch,-1,chan[ch].wavePos,chan[ch].waveLen);
|
||||
if (chan[ch].active && !isMuted[ch]) {
|
||||
chan[ch].volumeChanged=true;
|
||||
|
|
@ -337,15 +339,15 @@ int DivPlatformN163::dispatch(DivCommand c) {
|
|||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_N163);
|
||||
if (chan[c.chan].insChanged) {
|
||||
chan[c.chan].wave=ins->n163.wave;
|
||||
chan[c.chan].ws.changeWave1(chan[c.chan].wave);
|
||||
chan[c.chan].wavePos=ins->n163.wavePos;
|
||||
chan[c.chan].waveLen=ins->n163.waveLen;
|
||||
chan[c.chan].waveMode=ins->n163.waveMode;
|
||||
chan[c.chan].ws.init(NULL,chan[c.chan].waveLen,15,false);
|
||||
chan[c.chan].ws.changeWave1(chan[c.chan].wave);
|
||||
chan[c.chan].waveChanged=true;
|
||||
if (chan[c.chan].waveMode&0x3 || ins->ws.enabled) {
|
||||
chan[c.chan].waveUpdated=true;
|
||||
}
|
||||
chan[c.chan].insChanged=false;
|
||||
}
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
|
||||
|
|
@ -360,6 +362,7 @@ int DivPlatformN163::dispatch(DivCommand c) {
|
|||
}
|
||||
chan[c.chan].macroInit(ins);
|
||||
chan[c.chan].ws.init(ins,chan[c.chan].waveLen,15,chan[c.chan].insChanged);
|
||||
chan[c.chan].insChanged=false;
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_NOTE_OFF:
|
||||
|
|
|
|||
|
|
@ -183,6 +183,7 @@ void DivPlatformNamcoWSG::acquire(short** buf, size_t len) {
|
|||
}
|
||||
|
||||
void DivPlatformNamcoWSG::updateWave(int ch) {
|
||||
if (romMode) return;
|
||||
if (devType==30) {
|
||||
for (int i=0; i<32; i++) {
|
||||
((namco_cus30_device*)namco)->namcos1_cus30_w(i+ch*32,chan[ch].ws.output[i]);
|
||||
|
|
@ -291,9 +292,9 @@ void DivPlatformNamcoWSG::tick(bool sysTick) {
|
|||
rWrite(0x1d,(chan[2].freq>>12)&15);
|
||||
rWrite(0x1e,(chan[2].freq>>16)&15);
|
||||
|
||||
rWrite(0x05,0);
|
||||
rWrite(0x0a,1);
|
||||
rWrite(0x0f,2);
|
||||
rWrite(0x05,romMode?(chan[0].wave&7):0);
|
||||
rWrite(0x0a,romMode?(chan[1].wave&7):1);
|
||||
rWrite(0x0f,romMode?(chan[2].wave&7):2);
|
||||
break;
|
||||
case 15:
|
||||
for (int i=0; i<8; i++) {
|
||||
|
|
@ -304,7 +305,7 @@ void DivPlatformNamcoWSG::tick(bool sysTick) {
|
|||
}
|
||||
rWrite((i<<3)+0x04,chan[i].freq&0xff);
|
||||
rWrite((i<<3)+0x05,(chan[i].freq>>8)&0xff);
|
||||
rWrite((i<<3)+0x06,((chan[i].freq>>16)&15)|(i<<4));
|
||||
rWrite((i<<3)+0x06,((chan[i].freq>>16)&15)|((romMode?(chan[i].wave&7):i)<<4));
|
||||
}
|
||||
break;
|
||||
case 30:
|
||||
|
|
@ -316,9 +317,16 @@ void DivPlatformNamcoWSG::tick(bool sysTick) {
|
|||
rWrite((i<<3)+0x100,0);
|
||||
rWrite((i<<3)+0x104,(chan[(i+1)&7].noise?0x80:0));
|
||||
}
|
||||
rWrite((i<<3)+0x103,chan[i].freq&0xff);
|
||||
rWrite((i<<3)+0x102,(chan[i].freq>>8)&0xff);
|
||||
rWrite((i<<3)+0x101,((chan[i].freq>>16)&15)|(i<<4));
|
||||
if (chan[i].noise && newNoise) {
|
||||
int noiseFreq=chan[i].freq>>9;
|
||||
if (noiseFreq<0) noiseFreq=0;
|
||||
if (noiseFreq>255) noiseFreq=255;
|
||||
rWrite((i<<3)+0x103,noiseFreq);
|
||||
} else {
|
||||
rWrite((i<<3)+0x103,chan[i].freq&0xff);
|
||||
rWrite((i<<3)+0x102,(chan[i].freq>>8)&0xff);
|
||||
rWrite((i<<3)+0x101,((chan[i].freq>>16)&15)|(i<<4));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
@ -489,10 +497,11 @@ void DivPlatformNamcoWSG::reset() {
|
|||
if (dumpWrites) {
|
||||
addWrite(0xffffffff,0);
|
||||
}
|
||||
// TODO: wave memory
|
||||
namco->set_voices(chans);
|
||||
namco->set_stereo((devType==2 || devType==30));
|
||||
namco->device_start(NULL);
|
||||
|
||||
updateROMWaves();
|
||||
}
|
||||
|
||||
int DivPlatformNamcoWSG::getOutputCount() {
|
||||
|
|
@ -503,6 +512,27 @@ bool DivPlatformNamcoWSG::keyOffAffectsArp(int ch) {
|
|||
return true;
|
||||
}
|
||||
|
||||
void DivPlatformNamcoWSG::updateROMWaves() {
|
||||
if (romMode) {
|
||||
// copy wavetables
|
||||
for (int i=0; i<8; 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]*15/w->max;
|
||||
if (data<0) data=0;
|
||||
if (data>15) data=15;
|
||||
}
|
||||
namco->update_namco_waveform(i*32+j,data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformNamcoWSG::notifyWaveChange(int wave) {
|
||||
for (int i=0; i<chans; i++) {
|
||||
if (chan[i].wave==wave) {
|
||||
|
|
@ -510,6 +540,7 @@ void DivPlatformNamcoWSG::notifyWaveChange(int wave) {
|
|||
updateWave(i);
|
||||
}
|
||||
}
|
||||
updateROMWaves();
|
||||
}
|
||||
|
||||
void DivPlatformNamcoWSG::notifyInsDeletion(void* ins) {
|
||||
|
|
@ -544,6 +575,9 @@ void DivPlatformNamcoWSG::setFlags(const DivConfig& flags) {
|
|||
for (int i=0; i<chans; i++) {
|
||||
oscBuf[i]->rate=rate;
|
||||
}
|
||||
newNoise=flags.getBool("newNoise",true);
|
||||
romMode=flags.getBool("romMode",false);
|
||||
if (devType==30) romMode=false;
|
||||
}
|
||||
|
||||
void DivPlatformNamcoWSG::poke(unsigned int addr, unsigned short val) {
|
||||
|
|
|
|||
|
|
@ -49,8 +49,11 @@ class DivPlatformNamcoWSG: public DivDispatch {
|
|||
|
||||
namco_audio_device* namco;
|
||||
int devType, chans;
|
||||
bool newNoise;
|
||||
bool romMode;
|
||||
unsigned char regPool[512];
|
||||
void updateWave(int ch);
|
||||
void updateROMWaves();
|
||||
friend void putDispatchChip(void*,int);
|
||||
friend void putDispatchChan(void*,int,int);
|
||||
public:
|
||||
|
|
|
|||
|
|
@ -211,7 +211,7 @@ void DivPlatformNES::tick(bool sysTick) {
|
|||
chan[i].outVol=VOL_SCALE_LINEAR_BROKEN(chan[i].vol&15,MIN(15,chan[i].std.vol.val),15);
|
||||
if (chan[i].outVol<0) chan[i].outVol=0;
|
||||
if (i==2) { // triangle
|
||||
rWrite(0x4000+i*4,(chan[i].outVol==0)?0:255);
|
||||
rWrite(0x4000+i*4,(chan[i].outVol==0)?0:linearCount);
|
||||
chan[i].freqChanged=true;
|
||||
} else {
|
||||
rWrite(0x4000+i*4,(chan[i].envMode<<4)|chan[i].outVol|((chan[i].duty&3)<<6));
|
||||
|
|
@ -262,7 +262,7 @@ void DivPlatformNES::tick(bool sysTick) {
|
|||
//rWrite(16+i*5,chan[i].sweep);
|
||||
}
|
||||
}
|
||||
if (i<2) if (chan[i].std.phaseReset.had) {
|
||||
if (i<3) if (chan[i].std.phaseReset.had) {
|
||||
if (chan[i].std.phaseReset.val==1) {
|
||||
chan[i].freqChanged=true;
|
||||
chan[i].prevFreq=-1;
|
||||
|
|
@ -337,14 +337,22 @@ void DivPlatformNES::tick(bool sysTick) {
|
|||
goingToLoop=parent->getSample(dacSample)->isLoopable();
|
||||
// write DPCM
|
||||
rWrite(0x4015,15);
|
||||
rWrite(0x4010,calcDPCMRate(dacRate)|(goingToLoop?0x40:0));
|
||||
if (nextDPCMFreq>=0) {
|
||||
rWrite(0x4010,nextDPCMFreq|(goingToLoop?0x40:0));
|
||||
nextDPCMFreq=-1;
|
||||
} else {
|
||||
rWrite(0x4010,calcDPCMRate(dacRate)|(goingToLoop?0x40:0));
|
||||
}
|
||||
rWrite(0x4012,(dpcmAddr>>6)&0xff);
|
||||
rWrite(0x4013,dpcmLen&0xff);
|
||||
rWrite(0x4015,31);
|
||||
dpcmBank=dpcmAddr>>14;
|
||||
}
|
||||
} else {
|
||||
if (dpcmMode) {
|
||||
if (nextDPCMFreq>=0) {
|
||||
rWrite(0x4010,nextDPCMFreq|(goingToLoop?0x40:0));
|
||||
nextDPCMFreq=-1;
|
||||
} else {
|
||||
rWrite(0x4010,calcDPCMRate(dacRate)|(goingToLoop?0x40:0));
|
||||
}
|
||||
}
|
||||
|
|
@ -353,6 +361,8 @@ void DivPlatformNES::tick(bool sysTick) {
|
|||
if (chan[4].keyOn) chan[4].keyOn=false;
|
||||
chan[4].freqChanged=false;
|
||||
}
|
||||
|
||||
nextDPCMFreq=-1;
|
||||
}
|
||||
|
||||
int DivPlatformNES::dispatch(DivCommand c) {
|
||||
|
|
@ -361,7 +371,10 @@ int DivPlatformNES::dispatch(DivCommand c) {
|
|||
if (c.chan==4) { // PCM
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_STD);
|
||||
if (ins->type==DIV_INS_AMIGA) {
|
||||
if (c.value!=DIV_NOTE_NULL) dacSample=ins->amiga.getSample(c.value);
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
dacSample=ins->amiga.getSample(c.value);
|
||||
c.value=ins->amiga.getFreq(c.value);
|
||||
}
|
||||
if (dacSample<0 || dacSample>=parent->song.sampleLen) {
|
||||
dacSample=-1;
|
||||
if (dumpWrites && !dpcmMode) addWrite(0xffff0002,0);
|
||||
|
|
@ -398,12 +411,17 @@ int DivPlatformNES::dispatch(DivCommand c) {
|
|||
chan[c.chan].furnaceDac=false;
|
||||
if (dpcmMode && !skipRegisterWrites) {
|
||||
unsigned int dpcmAddr=sampleOffDPCM[dacSample];
|
||||
unsigned int dpcmLen=(parent->getSample(dacSample)->lengthDPCM+15)>>4;
|
||||
unsigned int dpcmLen=parent->getSample(dacSample)->lengthDPCM>>4;
|
||||
if (dpcmLen>255) dpcmLen=255;
|
||||
goingToLoop=parent->getSample(dacSample)->isLoopable();
|
||||
// write DPCM
|
||||
rWrite(0x4015,15);
|
||||
rWrite(0x4010,calcDPCMRate(dacRate)|(goingToLoop?0x40:0));
|
||||
if (nextDPCMFreq>=0) {
|
||||
rWrite(0x4010,nextDPCMFreq|(goingToLoop?0x40:0));
|
||||
nextDPCMFreq=-1;
|
||||
} else {
|
||||
rWrite(0x4010,calcDPCMRate(dacRate)|(goingToLoop?0x40:0));
|
||||
}
|
||||
rWrite(0x4012,(dpcmAddr>>6)&0xff);
|
||||
rWrite(0x4013,dpcmLen&0xff);
|
||||
rWrite(0x4015,31);
|
||||
|
|
@ -431,7 +449,7 @@ int DivPlatformNES::dispatch(DivCommand c) {
|
|||
chan[c.chan].outVol=chan[c.chan].vol;
|
||||
}
|
||||
if (c.chan==2) {
|
||||
rWrite(0x4000+c.chan*4,0xff);
|
||||
rWrite(0x4000+c.chan*4,linearCount);
|
||||
} else if (!parent->song.brokenOutVol2) {
|
||||
rWrite(0x4000+c.chan*4,(chan[c.chan].envMode<<4)|chan[c.chan].vol|((chan[c.chan].duty&3)<<6));
|
||||
}
|
||||
|
|
@ -463,7 +481,7 @@ int DivPlatformNES::dispatch(DivCommand c) {
|
|||
}
|
||||
if (chan[c.chan].active) {
|
||||
if (c.chan==2) {
|
||||
rWrite(0x4000+c.chan*4,0xff);
|
||||
rWrite(0x4000+c.chan*4,linearCount);
|
||||
} else {
|
||||
rWrite(0x4000+c.chan*4,(chan[c.chan].envMode<<4)|chan[c.chan].vol|((chan[c.chan].duty&3)<<6));
|
||||
}
|
||||
|
|
@ -539,6 +557,16 @@ int DivPlatformNES::dispatch(DivCommand c) {
|
|||
countMode=c.value;
|
||||
rWrite(0x4017,countMode?0x80:0);
|
||||
break;
|
||||
case DIV_CMD_NES_LINEAR_LENGTH:
|
||||
if (c.chan==2) {
|
||||
linearCount=c.value;
|
||||
if (chan[c.chan].active) {
|
||||
rWrite(0x4000+c.chan*4,(chan[c.chan].outVol==0)?0:linearCount);
|
||||
chan[c.chan].freqChanged=true;
|
||||
chan[c.chan].prevFreq=-1;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_NES_DMC:
|
||||
rWrite(0x4011,c.value&0x7f);
|
||||
break;
|
||||
|
|
@ -552,6 +580,14 @@ int DivPlatformNES::dispatch(DivCommand c) {
|
|||
rWrite(0x4013,0);
|
||||
rWrite(0x4015,31);
|
||||
break;
|
||||
case DIV_CMD_SAMPLE_FREQ: {
|
||||
bool goingToLoop=parent->getSample(dacSample)->isLoopable();
|
||||
if (dpcmMode) {
|
||||
nextDPCMFreq=c.value&15;
|
||||
rWrite(0x4010,(c.value&15)|(goingToLoop?0x40:0));
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_SAMPLE_BANK:
|
||||
sampleBank=c.value;
|
||||
if (sampleBank>(parent->song.sample.size()/12)) {
|
||||
|
|
@ -652,9 +688,11 @@ void DivPlatformNES::reset() {
|
|||
dacSample=-1;
|
||||
sampleBank=0;
|
||||
dpcmBank=0;
|
||||
dpcmMode=false;
|
||||
dpcmMode=dpcmModeDefault;
|
||||
goingToLoop=false;
|
||||
countMode=false;
|
||||
nextDPCMFreq=-1;
|
||||
linearCount=255;
|
||||
|
||||
if (useNP) {
|
||||
nes1_NP->Reset();
|
||||
|
|
@ -706,6 +744,8 @@ void DivPlatformNES::setFlags(const DivConfig& flags) {
|
|||
for (int i=0; i<5; i++) {
|
||||
oscBuf[i]->rate=rate/32;
|
||||
}
|
||||
|
||||
dpcmModeDefault=flags.getBool("dpcmMode",true);
|
||||
}
|
||||
|
||||
void DivPlatformNES::notifyInsDeletion(void* ins) {
|
||||
|
|
|
|||
|
|
@ -52,7 +52,10 @@ class DivPlatformNES: public DivDispatch {
|
|||
unsigned char sampleBank;
|
||||
unsigned char writeOscBuf;
|
||||
unsigned char apuType;
|
||||
unsigned char linearCount;
|
||||
signed char nextDPCMFreq;
|
||||
bool dpcmMode;
|
||||
bool dpcmModeDefault;
|
||||
bool dacAntiClickOn;
|
||||
bool useNP;
|
||||
bool goingToLoop;
|
||||
|
|
|
|||
|
|
@ -865,7 +865,10 @@ int DivPlatformOPL::dispatch(DivCommand c) {
|
|||
chan[c.chan].outVol=chan[c.chan].vol;
|
||||
immWrite(18,chan[c.chan].outVol);
|
||||
}
|
||||
if (c.value!=DIV_NOTE_NULL) chan[c.chan].sample=ins->amiga.getSample(c.value);
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].sample=ins->amiga.getSample(c.value);
|
||||
c.value=ins->amiga.getFreq(c.value);
|
||||
}
|
||||
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
|
||||
DivSample* s=parent->getSample(chan[c.chan].sample);
|
||||
immWrite(8,0);
|
||||
|
|
@ -1564,7 +1567,7 @@ DivMacroInt* DivPlatformOPL::getChanMacroInt(int ch) {
|
|||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformOPL::getOscBuffer(int ch) {
|
||||
if (oplType==759) {
|
||||
if (oplType==759 || chipType==8950) {
|
||||
if (ch>=totalChans+1) return NULL;
|
||||
} else {
|
||||
if (ch>=totalChans) return NULL;
|
||||
|
|
|
|||
|
|
@ -282,7 +282,10 @@ int DivPlatformPCE::dispatch(DivCommand c) {
|
|||
if (ins->type==DIV_INS_AMIGA || ins->amiga.useSample) {
|
||||
chan[c.chan].furnaceDac=true;
|
||||
if (skipRegisterWrites) break;
|
||||
if (c.value!=DIV_NOTE_NULL) chan[c.chan].dacSample=ins->amiga.getSample(c.value);
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].dacSample=ins->amiga.getSample(c.value);
|
||||
c.value=ins->amiga.getFreq(c.value);
|
||||
}
|
||||
if (chan[c.chan].dacSample<0 || chan[c.chan].dacSample>=parent->song.sampleLen) {
|
||||
chan[c.chan].dacSample=-1;
|
||||
if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0);
|
||||
|
|
@ -505,6 +508,16 @@ DivMacroInt* DivPlatformPCE::getChanMacroInt(int ch) {
|
|||
return &chan[ch].std;
|
||||
}
|
||||
|
||||
DivSamplePos DivPlatformPCE::getSamplePos(int ch) {
|
||||
if (ch>=6) return DivSamplePos();
|
||||
if (!chan[ch].pcm) return DivSamplePos();
|
||||
return DivSamplePos(
|
||||
chan[ch].dacSample,
|
||||
chan[ch].dacPos,
|
||||
chan[ch].dacRate
|
||||
);
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformPCE::getOscBuffer(int ch) {
|
||||
return oscBuf[ch];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ class DivPlatformPCE: public DivDispatch {
|
|||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
DivMacroInt* getChanMacroInt(int ch);
|
||||
DivSamplePos getSamplePos(int ch);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ void DivPlatformPCMDAC::acquire(short** buf, size_t len) {
|
|||
const int depthScale=(15-outDepth);
|
||||
int output=0;
|
||||
for (size_t h=0; h<len; h++) {
|
||||
if (!chan[0].active || isMuted) {
|
||||
if (!chan[0].active) {
|
||||
buf[0][h]=0;
|
||||
buf[1][h]=0;
|
||||
oscBuf->data[oscBuf->needle++]=0;
|
||||
|
|
@ -171,7 +171,11 @@ void DivPlatformPCMDAC::acquire(short** buf, size_t len) {
|
|||
}
|
||||
}
|
||||
}
|
||||
output=output*chan[0].vol*chan[0].envVol/16384;
|
||||
if (isMuted) {
|
||||
output=0;
|
||||
} else {
|
||||
output=output*chan[0].vol*chan[0].envVol/16384;
|
||||
}
|
||||
oscBuf->data[oscBuf->needle++]=output;
|
||||
if (outStereo) {
|
||||
buf[0][h]=((output*chan[0].panL)>>(depthScale+8))<<depthScale;
|
||||
|
|
@ -267,7 +271,10 @@ int DivPlatformPCMDAC::dispatch(DivCommand c) {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
if (c.value!=DIV_NOTE_NULL) chan[0].sample=ins->amiga.getSample(c.value);
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[0].sample=ins->amiga.getSample(c.value);
|
||||
c.value=ins->amiga.getFreq(c.value);
|
||||
}
|
||||
chan[0].useWave=false;
|
||||
}
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
|
|
@ -437,6 +444,15 @@ DivMacroInt* DivPlatformPCMDAC::getChanMacroInt(int ch) {
|
|||
return &chan[0].std;
|
||||
}
|
||||
|
||||
DivSamplePos DivPlatformPCMDAC::getSamplePos(int ch) {
|
||||
if (ch>=1) return DivSamplePos();
|
||||
return DivSamplePos(
|
||||
chan[ch].sample,
|
||||
chan[ch].audPos,
|
||||
chan[ch].freq
|
||||
);
|
||||
}
|
||||
|
||||
void DivPlatformPCMDAC::notifyInsChange(int ins) {
|
||||
if (chan[0].ins==ins) {
|
||||
chan[0].insChanged=true;
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ class DivPlatformPCMDAC: public DivDispatch {
|
|||
void muteChannel(int ch, bool mute);
|
||||
int getOutputCount();
|
||||
DivMacroInt* getChanMacroInt(int ch);
|
||||
DivSamplePos getSamplePos(int ch);
|
||||
void setFlags(const DivConfig& flags);
|
||||
void notifyInsChange(int ins);
|
||||
void notifyWaveChange(int wave);
|
||||
|
|
|
|||
302
src/engine/platform/pv1000.cpp
Normal file
302
src/engine/platform/pv1000.cpp
Normal file
|
|
@ -0,0 +1,302 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2023 tildearrow and contributors
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "pv1000.h"
|
||||
#include "../engine.h"
|
||||
#include <math.h>
|
||||
|
||||
#define rWrite(a,v) {regPool[(a)]=(v)&0xff; d65010g031_write(&d65010g031,a,v);}
|
||||
|
||||
#define CHIP_DIVIDER 1024
|
||||
|
||||
const char* regCheatSheetPV1000[]={
|
||||
"CH1_Pitch", "00",
|
||||
"CH2_Pitch", "01",
|
||||
"CH3_Pitch", "02",
|
||||
"Control", "03",
|
||||
NULL
|
||||
};
|
||||
|
||||
const char** DivPlatformPV1000::getRegisterSheet() {
|
||||
return regCheatSheetPV1000;
|
||||
}
|
||||
|
||||
void DivPlatformPV1000::acquire(short** buf, size_t len) {
|
||||
for (size_t h=0; h<len; h++) {
|
||||
short samp=d65010g031_sound_tick(&d65010g031,1);
|
||||
buf[0][h]=samp;
|
||||
for (int i=0; i<3; i++) {
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=(d65010g031.out[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformPV1000::tick(bool sysTick) {
|
||||
for (int i=0; i<3; i++) {
|
||||
chan[i].std.next();
|
||||
if (chan[i].std.vol.had) {
|
||||
chan[i].outVol=(chan[i].vol && chan[i].std.vol.val);
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
if (NEW_ARP_STRAT) {
|
||||
chan[i].handleArp();
|
||||
} else if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
chan[i].baseFreq=NOTE_PERIODIC(parent->calcArp(chan[i].note,chan[i].std.arp.val));
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
if (chan[i].std.pitch.had) {
|
||||
if (chan[i].std.pitch.mode) {
|
||||
chan[i].pitch2+=chan[i].std.pitch.val;
|
||||
CLAMP_VAR(chan[i].pitch2,-32768,32767);
|
||||
} else {
|
||||
chan[i].pitch2=chan[i].std.pitch.val;
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
chan[i].freq=0x3f-parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER);
|
||||
if (chan[i].freq<0) chan[i].freq=0;
|
||||
if (chan[i].freq>62) chan[i].freq=62;
|
||||
if (isMuted[i]) chan[i].keyOn=false;
|
||||
if (chan[i].keyOn) {
|
||||
rWrite(i,(isMuted[i] || (chan[i].outVol<=0)) ? 0x3f : chan[i].freq);
|
||||
chan[i].keyOn=false;
|
||||
} else if (chan[i].freqChanged && chan[i].active && !isMuted[i]) {
|
||||
rWrite(i,(isMuted[i] || (chan[i].outVol<=0)) ? 0x3f : chan[i].freq);
|
||||
}
|
||||
if (chan[i].keyOff) {
|
||||
rWrite(i,0x3f);
|
||||
chan[i].keyOff=false;
|
||||
}
|
||||
chan[i].freqChanged=false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int DivPlatformPV1000::dispatch(DivCommand c) {
|
||||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON: {
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_PV1000);
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value);
|
||||
chan[c.chan].freqChanged=true;
|
||||
chan[c.chan].note=c.value;
|
||||
}
|
||||
chan[c.chan].active=true;
|
||||
chan[c.chan].keyOn=true;
|
||||
chan[c.chan].macroInit(ins);
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_NOTE_OFF:
|
||||
chan[c.chan].active=false;
|
||||
chan[c.chan].keyOff=true;
|
||||
chan[c.chan].macroInit(NULL);
|
||||
break;
|
||||
case DIV_CMD_NOTE_OFF_ENV:
|
||||
case DIV_CMD_ENV_RELEASE:
|
||||
chan[c.chan].std.release();
|
||||
break;
|
||||
case DIV_CMD_INSTRUMENT:
|
||||
if (chan[c.chan].ins!=c.value || c.value2==1) {
|
||||
chan[c.chan].ins=c.value;
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_VOLUME:
|
||||
if (chan[c.chan].vol!=c.value) {
|
||||
chan[c.chan].vol=c.value;
|
||||
if (!chan[c.chan].std.vol.has) {
|
||||
chan[c.chan].outVol=c.value;
|
||||
}
|
||||
if (chan[c.chan].active) {
|
||||
chan[c.chan].freqChanged=true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_GET_VOLUME:
|
||||
return chan[c.chan].vol;
|
||||
break;
|
||||
case DIV_CMD_PITCH:
|
||||
chan[c.chan].pitch=c.value;
|
||||
chan[c.chan].freqChanged=true;
|
||||
break;
|
||||
case DIV_CMD_STD_NOISE_MODE: // ring modulation
|
||||
if (c.value&1) {
|
||||
rWrite(3,3);
|
||||
} else {
|
||||
rWrite(3,2);
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_NOTE_PORTA: {
|
||||
int destFreq=NOTE_PERIODIC(c.value2);
|
||||
bool return2=false;
|
||||
if (destFreq>chan[c.chan].baseFreq) {
|
||||
chan[c.chan].baseFreq+=c.value;
|
||||
if (chan[c.chan].baseFreq>=destFreq) {
|
||||
chan[c.chan].baseFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
} else {
|
||||
chan[c.chan].baseFreq-=c.value;
|
||||
if (chan[c.chan].baseFreq<=destFreq) {
|
||||
chan[c.chan].baseFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
}
|
||||
chan[c.chan].freqChanged=true;
|
||||
if (return2) {
|
||||
chan[c.chan].inPorta=false;
|
||||
return 2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_LEGATO:
|
||||
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((HACKY_LEGATO_MESS)?(chan[c.chan].std.arp.val):(0)));
|
||||
chan[c.chan].freqChanged=true;
|
||||
chan[c.chan].note=c.value;
|
||||
break;
|
||||
case DIV_CMD_PRE_PORTA:
|
||||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_PV1000));
|
||||
}
|
||||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note);
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_GET_VOLMAX:
|
||||
return 1;
|
||||
break;
|
||||
case DIV_CMD_MACRO_OFF:
|
||||
chan[c.chan].std.mask(c.value,true);
|
||||
break;
|
||||
case DIV_CMD_MACRO_ON:
|
||||
chan[c.chan].std.mask(c.value,false);
|
||||
break;
|
||||
case DIV_ALWAYS_SET_VOLUME:
|
||||
return 1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void DivPlatformPV1000::muteChannel(int ch, bool mute) {
|
||||
isMuted[ch]=mute;
|
||||
if (mute) {
|
||||
chan[ch].keyOff=true;
|
||||
} else if (chan[ch].active) {
|
||||
chan[ch].keyOn=true;
|
||||
chan[ch].freqChanged=true;
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformPV1000::forceIns() {
|
||||
for (int i=0; i<3; i++) {
|
||||
chan[i].insChanged=true;
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
|
||||
void* DivPlatformPV1000::getChanState(int ch) {
|
||||
return &chan[ch];
|
||||
}
|
||||
|
||||
DivMacroInt* DivPlatformPV1000::getChanMacroInt(int ch) {
|
||||
return &chan[ch].std;
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformPV1000::getOscBuffer(int ch) {
|
||||
return oscBuf[ch];
|
||||
}
|
||||
|
||||
unsigned char* DivPlatformPV1000::getRegisterPool() {
|
||||
return regPool;
|
||||
}
|
||||
|
||||
int DivPlatformPV1000::getRegisterPoolSize() {
|
||||
return 4;
|
||||
}
|
||||
|
||||
void DivPlatformPV1000::reset() {
|
||||
memset(regPool,0,4);
|
||||
for (int i=0; i<3; i++) {
|
||||
chan[i]=Channel();
|
||||
chan[i].std.setEngine(parent);
|
||||
}
|
||||
d65010g031_reset(&d65010g031);
|
||||
// mute
|
||||
rWrite(0,0x3f);
|
||||
rWrite(1,0x3f);
|
||||
rWrite(2,0x3f);
|
||||
rWrite(3,2);
|
||||
}
|
||||
|
||||
int DivPlatformPV1000::getOutputCount() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
void DivPlatformPV1000::notifyInsDeletion(void* ins) {
|
||||
for (int i=0; i<3; i++) {
|
||||
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformPV1000::setFlags(const DivConfig& flags) {
|
||||
chipClock=COLOR_NTSC*5.0;
|
||||
CHECK_CUSTOM_CLOCK;
|
||||
rate=chipClock/1024;
|
||||
for (int i=0; i<3; i++) {
|
||||
oscBuf[i]->rate=rate;
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformPV1000::poke(unsigned int addr, unsigned short val) {
|
||||
rWrite(addr,val);
|
||||
}
|
||||
|
||||
void DivPlatformPV1000::poke(std::vector<DivRegWrite>& wlist) {
|
||||
for (DivRegWrite& i: wlist) rWrite(i.addr,i.val);
|
||||
}
|
||||
|
||||
bool DivPlatformPV1000::getDCOffRequired() {
|
||||
return true;
|
||||
}
|
||||
|
||||
int DivPlatformPV1000::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) {
|
||||
parent=p;
|
||||
dumpWrites=false;
|
||||
skipRegisterWrites=false;
|
||||
for (int i=0; i<3; i++) {
|
||||
isMuted[i]=false;
|
||||
oscBuf[i]=new DivDispatchOscBuffer;
|
||||
}
|
||||
setFlags(flags);
|
||||
reset();
|
||||
return 4;
|
||||
}
|
||||
|
||||
void DivPlatformPV1000::quit() {
|
||||
for (int i=0; i<3; i++) {
|
||||
delete oscBuf[i];
|
||||
}
|
||||
}
|
||||
|
||||
DivPlatformPV1000::~DivPlatformPV1000() {
|
||||
}
|
||||
64
src/engine/platform/pv1000.h
Normal file
64
src/engine/platform/pv1000.h
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2023 tildearrow and contributors
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef _PV1000_H
|
||||
#define _PV1000_H
|
||||
|
||||
#include "../dispatch.h"
|
||||
#include "sound/d65modified.h"
|
||||
#include <queue>
|
||||
|
||||
class DivPlatformPV1000: public DivDispatch {
|
||||
struct Channel: public SharedChannel<int> {
|
||||
Channel():
|
||||
SharedChannel<int>(1) {}
|
||||
};
|
||||
Channel chan[3];
|
||||
DivDispatchOscBuffer* oscBuf[3];
|
||||
bool isMuted[3];
|
||||
|
||||
unsigned char regPool[4];
|
||||
d65010g031_t d65010g031;
|
||||
friend void putDispatchChip(void*,int);
|
||||
friend void putDispatchChan(void*,int,int);
|
||||
public:
|
||||
void acquire(short** buf, size_t len);
|
||||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
DivMacroInt* getChanMacroInt(int ch);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
void reset();
|
||||
void forceIns();
|
||||
void tick(bool sysTick=true);
|
||||
void muteChannel(int ch, bool mute);
|
||||
void setFlags(const DivConfig& flags);
|
||||
void notifyInsDeletion(void* ins);
|
||||
int getOutputCount();
|
||||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
bool getDCOffRequired();
|
||||
int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags);
|
||||
void quit();
|
||||
~DivPlatformPV1000();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -450,7 +450,10 @@ int DivPlatformQSound::dispatch(DivCommand c) {
|
|||
case DIV_CMD_NOTE_ON: {
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA);
|
||||
chan[c.chan].isNewQSound=(ins->type==DIV_INS_QSOUND);
|
||||
if (c.value!=DIV_NOTE_NULL) chan[c.chan].sample=ins->amiga.getSample(c.value);
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].sample=ins->amiga.getSample(c.value);
|
||||
c.value=ins->amiga.getFreq(c.value);
|
||||
}
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].baseFreq=QS_NOTE_FREQUENCY(c.value);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -151,8 +151,8 @@ void DivPlatformRF5C68::tick(bool sysTick) {
|
|||
if (s->isLoopable()) {
|
||||
loop=start+s->loopStart;
|
||||
}
|
||||
start=MIN(start,getSampleMemCapacity()-31);
|
||||
loop=MIN(loop,getSampleMemCapacity()-31);
|
||||
start=MIN(start,getSampleMemCapacity()-32);
|
||||
loop=MIN(loop,getSampleMemCapacity()-32);
|
||||
rWrite(8,keyoff); // force keyoff first
|
||||
chWrite(i,6,start>>8);
|
||||
chWrite(i,4,loop&0xff);
|
||||
|
|
@ -182,7 +182,10 @@ int DivPlatformRF5C68::dispatch(DivCommand c) {
|
|||
case DIV_CMD_NOTE_ON: {
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA);
|
||||
chan[c.chan].macroVolMul=ins->type==DIV_INS_AMIGA?64:255;
|
||||
if (c.value!=DIV_NOTE_NULL) chan[c.chan].sample=ins->amiga.getSample(c.value);
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].sample=ins->amiga.getSample(c.value);
|
||||
c.value=ins->amiga.getFreq(c.value);
|
||||
}
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
|
||||
}
|
||||
|
|
@ -307,6 +310,7 @@ void DivPlatformRF5C68::forceIns() {
|
|||
chan[i].insChanged=true;
|
||||
chan[i].freqChanged=true;
|
||||
chan[i].sample=-1;
|
||||
chWrite(i,1,isMuted[i]?0:chan[i].panning);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -421,7 +425,7 @@ void DivPlatformRF5C68::renderSamples(int sysID) {
|
|||
}
|
||||
|
||||
int length=s->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT);
|
||||
int actualLength=MIN((int)(getSampleMemCapacity()-memPos)-31,length);
|
||||
int actualLength=MIN((int)(getSampleMemCapacity()-memPos)-32,length);
|
||||
if (actualLength>0) {
|
||||
sampleOffRFC[i]=memPos;
|
||||
for (int j=0; j<actualLength; j++) {
|
||||
|
|
@ -431,8 +435,8 @@ void DivPlatformRF5C68::renderSamples(int sysID) {
|
|||
sampleMem[memPos++]=(val>0)?(val|0x80):(0-val);
|
||||
}
|
||||
// write end of sample marker
|
||||
memset(&sampleMem[memPos],0xff,31);
|
||||
memPos+=31;
|
||||
memset(&sampleMem[memPos],0xff,32);
|
||||
memPos+=32;
|
||||
}
|
||||
if (actualLength<length) {
|
||||
logW("out of RF5C68 PCM memory for sample %d!",i);
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ void DivPlatformSegaPCM::tick(bool sysTick) {
|
|||
chan[i].handleArp();
|
||||
} else if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
chan[i].baseFreq=(parent->calcArp(chan[i].note,chan[i].std.arp.val)<<6);
|
||||
chan[i].baseFreq=(parent->calcArp(chan[i].note,chan[i].std.arp.val)<<7);
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
|
|
@ -106,21 +106,22 @@ void DivPlatformSegaPCM::tick(bool sysTick) {
|
|||
}
|
||||
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
chan[i].freq=chan[i].baseFreq+(chan[i].pitch>>1)-64;
|
||||
chan[i].freq=chan[i].baseFreq+(chan[i].pitch)-128+(oldSlides?0:chan[i].pitch2);
|
||||
if (!parent->song.oldArpStrategy) {
|
||||
if (chan[i].fixedArp) {
|
||||
chan[i].freq=(chan[i].baseNoteOverride<<6)+(chan[i].pitch>>1)-64+chan[i].pitch2;
|
||||
chan[i].freq=(chan[i].baseNoteOverride<<7)+chan[i].pitch-128+(chan[i].pitch2<<(oldSlides?1:0));
|
||||
} else {
|
||||
chan[i].freq+=chan[i].arpOff<<6;
|
||||
chan[i].freq+=chan[i].arpOff<<7;
|
||||
}
|
||||
}
|
||||
if (oldSlides) chan[i].freq&=~1;
|
||||
if (chan[i].furnacePCM) {
|
||||
double off=1.0;
|
||||
if (chan[i].pcm.sample>=0 && chan[i].pcm.sample<parent->song.sampleLen) {
|
||||
DivSample* s=parent->getSample(chan[i].pcm.sample);
|
||||
off=(double)s->centerRate/8363.0;
|
||||
}
|
||||
chan[i].pcm.freq=MIN(255,(15625+(off*parent->song.tuning*pow(2.0,double(chan[i].freq+256)/(64.0*12.0)))*255)/31250)+chan[i].pitch2;
|
||||
chan[i].pcm.freq=MIN(255,((rate*0.5)+(off*parent->song.tuning*pow(2.0,double(chan[i].freq+512)/(128.0*12.0)))*255)/rate)+(oldSlides?chan[i].pitch2:0);
|
||||
rWrite(7+(i<<3),chan[i].pcm.freq);
|
||||
}
|
||||
chan[i].freqChanged=false;
|
||||
|
|
@ -136,11 +137,12 @@ void DivPlatformSegaPCM::tick(bool sysTick) {
|
|||
rWrite(0x86+(i<<3),3+((sampleOffSegaPCM[chan[i].pcm.sample]>>16)<<3));
|
||||
rWrite(0x84+(i<<3),(sampleOffSegaPCM[chan[i].pcm.sample])&0xff);
|
||||
rWrite(0x85+(i<<3),(sampleOffSegaPCM[chan[i].pcm.sample]>>8)&0xff);
|
||||
rWrite(6+(i<<3),MIN(255,((sampleOffSegaPCM[chan[i].pcm.sample]&0xffff)+actualLength-1)>>8));
|
||||
rWrite(6+(i<<3),sampleEndSegaPCM[chan[i].pcm.sample]);
|
||||
if (loopStart<0 || loopStart>=actualLength) {
|
||||
rWrite(0x86+(i<<3),2+((sampleOffSegaPCM[chan[i].pcm.sample]>>16)<<3));
|
||||
} else {
|
||||
int loopPos=(sampleOffSegaPCM[chan[i].pcm.sample]&0xffff)+loopStart+sampleLoopOff[chan[i].pcm.sample];
|
||||
int loopPos=(sampleOffSegaPCM[chan[i].pcm.sample]&0xffff)+loopStart;
|
||||
logV("sampleOff: %x loopPos: %x",sampleOffSegaPCM[chan[i].pcm.sample],loopPos);
|
||||
rWrite(4+(i<<3),loopPos&0xff);
|
||||
rWrite(5+(i<<3),(loopPos>>8)&0xff);
|
||||
rWrite(0x86+(i<<3),((sampleOffSegaPCM[chan[i].pcm.sample]>>16)<<3));
|
||||
|
|
@ -153,11 +155,11 @@ void DivPlatformSegaPCM::tick(bool sysTick) {
|
|||
rWrite(0x86+(i<<3),3+((sampleOffSegaPCM[chan[i].pcm.sample]>>16)<<3));
|
||||
rWrite(0x84+(i<<3),(sampleOffSegaPCM[chan[i].pcm.sample])&0xff);
|
||||
rWrite(0x85+(i<<3),(sampleOffSegaPCM[chan[i].pcm.sample]>>8)&0xff);
|
||||
rWrite(6+(i<<3),MIN(255,((sampleOffSegaPCM[chan[i].pcm.sample]&0xffff)+actualLength-1)>>8));
|
||||
rWrite(6+(i<<3),sampleEndSegaPCM[chan[i].pcm.sample]);
|
||||
if (loopStart<0 || loopStart>=actualLength) {
|
||||
rWrite(0x86+(i<<3),2+((sampleOffSegaPCM[chan[i].pcm.sample]>>16)<<3));
|
||||
} else {
|
||||
int loopPos=(sampleOffSegaPCM[chan[i].pcm.sample]&0xffff)+loopStart+sampleLoopOff[chan[i].pcm.sample];
|
||||
int loopPos=(sampleOffSegaPCM[chan[i].pcm.sample]&0xffff)+loopStart;
|
||||
rWrite(4+(i<<3),loopPos&0xff);
|
||||
rWrite(5+(i<<3),(loopPos>>8)&0xff);
|
||||
rWrite(0x86+(i<<3),((sampleOffSegaPCM[chan[i].pcm.sample]>>16)<<3));
|
||||
|
|
@ -185,7 +187,10 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) {
|
|||
if (ins->type==DIV_INS_AMIGA || ins->type==DIV_INS_SEGAPCM) {
|
||||
chan[c.chan].macroVolMul=(ins->type==DIV_INS_AMIGA)?64:127;
|
||||
chan[c.chan].isNewSegaPCM=(ins->type==DIV_INS_SEGAPCM);
|
||||
if (c.value!=DIV_NOTE_NULL) chan[c.chan].pcm.sample=ins->amiga.getSample(c.value);
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].pcm.sample=ins->amiga.getSample(c.value);
|
||||
c.value=ins->amiga.getFreq(c.value);
|
||||
}
|
||||
if (chan[c.chan].pcm.sample<0 || chan[c.chan].pcm.sample>=parent->song.sampleLen) {
|
||||
chan[c.chan].pcm.sample=-1;
|
||||
rWrite(0x86+(c.chan<<3),3);
|
||||
|
|
@ -197,7 +202,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) {
|
|||
}
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].note=c.value;
|
||||
chan[c.chan].baseFreq=(c.value<<6);
|
||||
chan[c.chan].baseFreq=(c.value<<7);
|
||||
chan[c.chan].freqChanged=true;
|
||||
}
|
||||
chan[c.chan].furnacePCM=true;
|
||||
|
|
@ -215,7 +220,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) {
|
|||
rWrite(0x86+(c.chan<<3),3);
|
||||
break;
|
||||
}
|
||||
chan[c.chan].pcm.freq=MIN(255,(parent->getSample(chan[c.chan].pcm.sample)->rate*255)/31250);
|
||||
chan[c.chan].pcm.freq=MIN(255,(parent->getSample(chan[c.chan].pcm.sample)->rate*255)/rate);
|
||||
chan[c.chan].furnacePCM=false;
|
||||
chan[c.chan].active=true;
|
||||
chan[c.chan].keyOn=true;
|
||||
|
|
@ -285,17 +290,18 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) {
|
|||
break;
|
||||
}
|
||||
case DIV_CMD_NOTE_PORTA: {
|
||||
int destFreq=(c.value2<<6);
|
||||
int destFreq=(c.value2<<7);
|
||||
int newFreq;
|
||||
int mul=(oldSlides || parent->song.linearPitch!=2)?8:1;
|
||||
bool return2=false;
|
||||
if (destFreq>chan[c.chan].baseFreq) {
|
||||
newFreq=chan[c.chan].baseFreq+c.value*4;
|
||||
newFreq=chan[c.chan].baseFreq+c.value*mul;
|
||||
if (newFreq>=destFreq) {
|
||||
newFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
} else {
|
||||
newFreq=chan[c.chan].baseFreq-c.value*4;
|
||||
newFreq=chan[c.chan].baseFreq-c.value*mul;
|
||||
if (newFreq<=destFreq) {
|
||||
newFreq=destFreq;
|
||||
return2=true;
|
||||
|
|
@ -310,7 +316,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) {
|
|||
break;
|
||||
}
|
||||
case DIV_CMD_LEGATO: {
|
||||
chan[c.chan].baseFreq=(c.value<<6);
|
||||
chan[c.chan].baseFreq=(c.value<<7);
|
||||
chan[c.chan].freqChanged=true;
|
||||
break;
|
||||
}
|
||||
|
|
@ -333,7 +339,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) {
|
|||
return 127;
|
||||
break;
|
||||
case DIV_CMD_PRE_PORTA:
|
||||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=(chan[c.chan].note<<6);
|
||||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=(chan[c.chan].note<<7);
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_PRE_NOTE:
|
||||
|
|
@ -381,6 +387,17 @@ DivMacroInt* DivPlatformSegaPCM::getChanMacroInt(int ch) {
|
|||
return &chan[ch].std;
|
||||
}
|
||||
|
||||
DivSamplePos DivPlatformSegaPCM::getSamplePos(int ch) {
|
||||
if (ch>=16) return DivSamplePos();
|
||||
if (chan[ch].pcm.sample<0 || chan[ch].pcm.sample>=parent->song.sampleLen) return DivSamplePos();
|
||||
if (!pcm.is_playing(ch)) return DivSamplePos();
|
||||
return DivSamplePos(
|
||||
chan[ch].pcm.sample,
|
||||
pcm.get_addr(ch)-sampleOffSegaPCM[chan[ch].pcm.sample],
|
||||
122*(chan[ch].pcm.freq+1)
|
||||
);
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformSegaPCM::getOscBuffer(int ch) {
|
||||
return oscBuf[ch];
|
||||
}
|
||||
|
|
@ -445,39 +462,38 @@ void DivPlatformSegaPCM::reset() {
|
|||
}
|
||||
}
|
||||
|
||||
void DivPlatformSegaPCM::renderSamples(int sysID) {
|
||||
void DivPlatformSegaPCM::renderSamples(int sysID) {
|
||||
size_t memPos=0;
|
||||
|
||||
memset(sampleMem,0,16777216);
|
||||
memset(sampleLoaded,0,256*sizeof(bool));
|
||||
memset(sampleOffSegaPCM,0,256*sizeof(unsigned int));
|
||||
memset(sampleEndSegaPCM,0,256);
|
||||
|
||||
for (int i=0; i<parent->song.sampleLen; i++) {
|
||||
DivSample* sample=parent->getSample(i);
|
||||
unsigned int alignedSize=(sample->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT)+0xff)&(~0xff);
|
||||
if (alignedSize>65536) alignedSize=65536;
|
||||
if ((memPos&0xff0000)!=((memPos+alignedSize)&0xff0000)) {
|
||||
memPos=(memPos+0xffff)&0xff0000;
|
||||
unsigned int alignedSize=sample->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT);
|
||||
if (alignedSize>=65279) alignedSize=65279;
|
||||
if ((memPos&(~0xffff))!=((memPos+alignedSize)&(~0xffff))) {
|
||||
memPos=(memPos+0xffff)&(~0xffff);
|
||||
}
|
||||
if (alignedSize&0xff) {
|
||||
memPos=((memPos+255)&(~0xff))+256-(alignedSize&0xff);
|
||||
}
|
||||
logV("- sample %d will be at %x with length %x",i,memPos,alignedSize);
|
||||
sampleLoaded[i]=true;
|
||||
if (memPos>=16777216) break;
|
||||
sampleOffSegaPCM[i]=memPos;
|
||||
unsigned int readPos=0;
|
||||
for (unsigned int j=0; j<alignedSize; j++) {
|
||||
if (readPos>=(unsigned int)sample->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT)) {
|
||||
if (sample->isLoopable()) {
|
||||
readPos=sample->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT);
|
||||
sampleMem[memPos++]=((unsigned char)sample->data8[readPos]+0x80);
|
||||
} else {
|
||||
sampleMem[memPos++]=0x80;
|
||||
}
|
||||
if (j>=sample->samples) {
|
||||
sampleMem[memPos++]=0;
|
||||
} else {
|
||||
sampleMem[memPos++]=((unsigned char)sample->data8[readPos]+0x80);
|
||||
sampleMem[memPos++]=((unsigned char)sample->data8[j]+0x80);
|
||||
}
|
||||
readPos++;
|
||||
sampleEndSegaPCM[i]=((memPos+0xff)>>8)-1;
|
||||
if (memPos>=16777216) break;
|
||||
}
|
||||
sampleLoopOff[i]=readPos-sample->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT);
|
||||
logV(" and it ends in %d",sampleEndSegaPCM[i]);
|
||||
if (memPos>=16777216) break;
|
||||
}
|
||||
sampleMemLen=memPos;
|
||||
|
|
@ -490,6 +506,8 @@ void DivPlatformSegaPCM::setFlags(const DivConfig& flags) {
|
|||
for (int i=0; i<16; i++) {
|
||||
oscBuf[i]->rate=rate;
|
||||
}
|
||||
|
||||
oldSlides=flags.getBool("oldSlides",false);
|
||||
}
|
||||
|
||||
int DivPlatformSegaPCM::getOutputCount() {
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ class DivPlatformSegaPCM: public DivDispatch {
|
|||
segapcm_device pcm;
|
||||
int delay;
|
||||
int pcmL, pcmR, pcmCycles;
|
||||
bool oldSlides;
|
||||
unsigned char sampleBank;
|
||||
unsigned char lastBusy;
|
||||
|
||||
|
|
@ -76,7 +77,7 @@ class DivPlatformSegaPCM: public DivDispatch {
|
|||
short pendingWrites[256];
|
||||
|
||||
unsigned int sampleOffSegaPCM[256];
|
||||
unsigned int sampleLoopOff[256];
|
||||
unsigned char sampleEndSegaPCM[256];
|
||||
bool sampleLoaded[256];
|
||||
|
||||
friend void putDispatchChip(void*,int);
|
||||
|
|
@ -87,6 +88,7 @@ class DivPlatformSegaPCM: public DivDispatch {
|
|||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
DivMacroInt* getChanMacroInt(int ch);
|
||||
DivSamplePos getSamplePos(int ch);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
|
|
|
|||
|
|
@ -84,11 +84,13 @@ void DivPlatformSM8521::tick(bool sysTick) {
|
|||
unsigned char keyState=0x80;
|
||||
for (int i=0; i<3; i++) {
|
||||
// anti-click
|
||||
/*
|
||||
if (antiClickEnabled && sysTick && chan[i].freq>0) {
|
||||
chan[i].antiClickPeriodCount+=(chipClock/MAX(parent->getCurHz(),1.0f));
|
||||
chan[i].antiClickWavePos+=chan[i].antiClickPeriodCount/chan[i].freq;
|
||||
chan[i].antiClickPeriodCount%=chan[i].freq;
|
||||
}
|
||||
*/
|
||||
|
||||
chan[i].std.next();
|
||||
if (chan[i].std.vol.had) {
|
||||
|
|
@ -167,7 +169,7 @@ void DivPlatformSM8521::tick(bool sysTick) {
|
|||
int DivPlatformSM8521::dispatch(DivCommand c) {
|
||||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON: {
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_PCE);
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_SM8521);
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value);
|
||||
chan[c.chan].freqChanged=true;
|
||||
|
|
@ -261,7 +263,7 @@ int DivPlatformSM8521::dispatch(DivCommand c) {
|
|||
break;
|
||||
case DIV_CMD_PRE_PORTA:
|
||||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_PCE));
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_SM8521));
|
||||
}
|
||||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note);
|
||||
chan[c.chan].inPorta=c.value;
|
||||
|
|
@ -362,9 +364,9 @@ void DivPlatformSM8521::notifyInsDeletion(void* ins) {
|
|||
}
|
||||
|
||||
void DivPlatformSM8521::setFlags(const DivConfig& flags) {
|
||||
antiClickEnabled=!flags.getBool("noAntiClick",false);
|
||||
chipClock=11059200;
|
||||
CHECK_CUSTOM_CLOCK;
|
||||
antiClickEnabled=!flags.getBool("noAntiClick",false);
|
||||
rate=chipClock/4/8; // CKIN -> fCLK(/2) -> Function blocks (/2)
|
||||
for (int i=0; i<3; i++) {
|
||||
oscBuf[i]->rate=rate;
|
||||
|
|
|
|||
|
|
@ -43,6 +43,23 @@ float DivPlatformSMS::getPostAmp() {
|
|||
return 1.5f;
|
||||
}
|
||||
|
||||
void DivPlatformSMS::poolWrite(unsigned short a, unsigned char v) {
|
||||
if (a) {
|
||||
regPool[9]=v;
|
||||
} else {
|
||||
if (v>=0x80) {
|
||||
regPool[(v>>4)&7]&=~15;
|
||||
regPool[(v>>4)&7]|=v&15;
|
||||
chanLatch=(v>>5)&3;
|
||||
} else {
|
||||
regPool[chanLatch<<1]&=15;
|
||||
regPool[chanLatch<<1]|=((v&15)<<4);
|
||||
regPool[1+(chanLatch<<1)]&=15;
|
||||
regPool[1+(chanLatch<<1)]|=v&0xf0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformSMS::acquire_nuked(short** buf, size_t len) {
|
||||
int oL=0;
|
||||
int oR=0;
|
||||
|
|
@ -54,6 +71,9 @@ void DivPlatformSMS::acquire_nuked(short** buf, size_t len) {
|
|||
} else if (w.addr==1) {
|
||||
YMPSG_WriteStereo(&sn_nuked,w.val);
|
||||
}
|
||||
|
||||
poolWrite(w.addr,w.val);
|
||||
|
||||
writes.pop();
|
||||
}
|
||||
YMPSG_Clock(&sn_nuked);
|
||||
|
|
@ -97,6 +117,9 @@ void DivPlatformSMS::acquire_mame(short** buf, size_t len) {
|
|||
else if (w.addr==0) {
|
||||
sn->write(w.val);
|
||||
}
|
||||
|
||||
poolWrite(w.addr,w.val);
|
||||
|
||||
writes.pop();
|
||||
}
|
||||
for (size_t h=0; h<len; h++) {
|
||||
|
|
@ -428,7 +451,17 @@ DivDispatchOscBuffer* DivPlatformSMS::getOscBuffer(int ch) {
|
|||
return oscBuf[ch];
|
||||
}
|
||||
|
||||
unsigned char* DivPlatformSMS::getRegisterPool() {
|
||||
return regPool;
|
||||
}
|
||||
|
||||
int DivPlatformSMS::getRegisterPoolSize() {
|
||||
return stereo?9:8;
|
||||
}
|
||||
|
||||
void DivPlatformSMS::reset() {
|
||||
memset(regPool,0,16);
|
||||
chanLatch=0;
|
||||
while (!writes.empty()) writes.pop();
|
||||
for (int i=0; i<4; i++) {
|
||||
chan[i]=DivPlatformSMS::Channel();
|
||||
|
|
|
|||
|
|
@ -42,6 +42,8 @@ class DivPlatformSMS: public DivDispatch {
|
|||
unsigned char lastPan;
|
||||
unsigned char oldValue;
|
||||
unsigned char snNoiseMode;
|
||||
unsigned char regPool[16];
|
||||
unsigned char chanLatch;
|
||||
int divider=16;
|
||||
double toneDivider=64.0;
|
||||
double noiseDivider=64.0;
|
||||
|
|
@ -65,6 +67,7 @@ class DivPlatformSMS: public DivDispatch {
|
|||
|
||||
double NOTE_SN(int ch, int note);
|
||||
int snCalcFreq(int ch);
|
||||
void poolWrite(unsigned short a, unsigned char v);
|
||||
|
||||
void acquire_nuked(short** buf, size_t len);
|
||||
void acquire_mame(short** buf, size_t len);
|
||||
|
|
@ -74,6 +77,8 @@ class DivPlatformSMS: public DivDispatch {
|
|||
void* getChanState(int chan);
|
||||
DivMacroInt* getChanMacroInt(int ch);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
void reset();
|
||||
void forceIns();
|
||||
void tick(bool sysTick=true);
|
||||
|
|
|
|||
|
|
@ -202,6 +202,7 @@ void DivPlatformSNES::tick(bool sysTick) {
|
|||
}
|
||||
}
|
||||
for (int i=0; i<8; i++) {
|
||||
// TODO: if wavetable length is higher than 32, we lose precision!
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
DivSample* s=parent->getSample(chan[i].sample);
|
||||
double off=(s->centerRate>=1)?((double)s->centerRate/8363.0):1.0;
|
||||
|
|
@ -300,6 +301,11 @@ void DivPlatformSNES::tick(bool sysTick) {
|
|||
rWrite(0x4d,echoBits);
|
||||
writeEcho=false;
|
||||
}
|
||||
if (writeDryVol) {
|
||||
rWrite(0x0c,dryVolL);
|
||||
rWrite(0x1c,dryVolR);
|
||||
writeDryVol=false;
|
||||
}
|
||||
for (int i=0; i<8; i++) {
|
||||
if (chan[i].shallWriteEnv) {
|
||||
writeEnv(i);
|
||||
|
|
@ -336,7 +342,10 @@ int DivPlatformSNES::dispatch(DivCommand c) {
|
|||
}
|
||||
chan[c.chan].ws.init(ins,chan[c.chan].wtLen,15,chan[c.chan].insChanged);
|
||||
} else {
|
||||
if (c.value!=DIV_NOTE_NULL) chan[c.chan].sample=ins->amiga.getSample(c.value);
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].sample=ins->amiga.getSample(c.value);
|
||||
c.value=ins->amiga.getFreq(c.value);
|
||||
}
|
||||
chan[c.chan].useWave=false;
|
||||
}
|
||||
if (chan[c.chan].useWave || chan[c.chan].sample<0 || chan[c.chan].sample>=parent->song.sampleLen) {
|
||||
|
|
@ -560,6 +569,14 @@ int DivPlatformSNES::dispatch(DivCommand c) {
|
|||
rWrite(0x3c,echoVolR);
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_SNES_GLOBAL_VOL_LEFT:
|
||||
dryVolL=c.value;
|
||||
writeDryVol=true;
|
||||
break;
|
||||
case DIV_CMD_SNES_GLOBAL_VOL_RIGHT:
|
||||
dryVolR=c.value;
|
||||
writeDryVol=true;
|
||||
break;
|
||||
case DIV_CMD_GET_VOLMAX:
|
||||
return 127;
|
||||
break;
|
||||
|
|
@ -670,6 +687,7 @@ void DivPlatformSNES::forceIns() {
|
|||
writeNoise=true;
|
||||
writePitchMod=true;
|
||||
writeEcho=true;
|
||||
writeDryVol=true;
|
||||
initEcho();
|
||||
}
|
||||
|
||||
|
|
@ -681,6 +699,20 @@ DivMacroInt* DivPlatformSNES::getChanMacroInt(int ch) {
|
|||
return &chan[ch].std;
|
||||
}
|
||||
|
||||
DivSamplePos DivPlatformSNES::getSamplePos(int ch) {
|
||||
if (ch>=8) return DivSamplePos();
|
||||
if (!chan[ch].active) return DivSamplePos();
|
||||
if (chan[ch].sample<0 || chan[ch].sample>=parent->song.sampleLen) return DivSamplePos();
|
||||
const SPC_DSP::voice_t* v=dsp.get_voice(ch);
|
||||
// TODO: fix?
|
||||
if (sampleMem[v->brr_addr&0xffff]==0) return DivSamplePos();
|
||||
return DivSamplePos(
|
||||
chan[ch].sample,
|
||||
((v->brr_addr-sampleOff[chan[ch].sample])*16/9)+v->brr_offset,
|
||||
(chan[ch].freq*125)/16
|
||||
);
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformSNES::getOscBuffer(int ch) {
|
||||
return oscBuf[ch];
|
||||
}
|
||||
|
|
@ -744,6 +776,10 @@ void DivPlatformSNES::reset() {
|
|||
writeNoise=false;
|
||||
writePitchMod=false;
|
||||
writeEcho=true;
|
||||
writeDryVol=false;
|
||||
|
||||
dryVolL=127;
|
||||
dryVolR=127;
|
||||
|
||||
echoDelay=initEchoDelay;
|
||||
echoFeedback=initEchoFeedback;
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ class DivPlatformSNES: public DivDispatch {
|
|||
unsigned char noiseFreq;
|
||||
signed char delay;
|
||||
signed char echoVolL, echoVolR, echoFeedback;
|
||||
signed char dryVolL, dryVolR;
|
||||
signed char echoFIR[8];
|
||||
unsigned char echoDelay;
|
||||
size_t sampleTableBase;
|
||||
|
|
@ -66,6 +67,7 @@ class DivPlatformSNES: public DivDispatch {
|
|||
bool writeNoise;
|
||||
bool writePitchMod;
|
||||
bool writeEcho;
|
||||
bool writeDryVol;
|
||||
bool echoOn;
|
||||
|
||||
bool initEchoOn;
|
||||
|
|
@ -97,6 +99,7 @@ class DivPlatformSNES: public DivDispatch {
|
|||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
DivMacroInt* getChanMacroInt(int ch);
|
||||
DivSamplePos getSamplePos(int ch);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
|
|
|
|||
151
src/engine/platform/sound/d65modified.c
Normal file
151
src/engine/platform/sound/d65modified.c
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
/*
|
||||
|
||||
============================================================================
|
||||
|
||||
NEC D65010G031 sound emulator
|
||||
by cam900
|
||||
|
||||
This file is licensed under zlib license.
|
||||
|
||||
============================================================================
|
||||
|
||||
zlib License
|
||||
|
||||
(C) 2023-present cam900 and contributors
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
============================================================================
|
||||
|
||||
TODO:
|
||||
- needs hardware test
|
||||
|
||||
ALTERED VERSION!!!
|
||||
ALTERED VERSION!!!
|
||||
ALTERED VERSION!!!
|
||||
ALTERED VERSION!!!
|
||||
ALTERED VERSION!!!
|
||||
ALTERED VERSION!!!
|
||||
ALTERED VERSION!!!
|
||||
ALTERED VERSION!!!
|
||||
ALTERED VERSION!!!
|
||||
ALTERED VERSION!!!
|
||||
ALTERED VERSION!!!
|
||||
ALTERED VERSION!!!
|
||||
ALTERED VERSION!!!
|
||||
ALTERED VERSION!!!
|
||||
|
||||
|
||||
THIS IS **NOT** NOT NOT NOT!!!! THE ORIGINAL SOFTWARE
|
||||
IT ISN'T
|
||||
THE MODIFICATIONS THAT WERE MADE ARE:
|
||||
|
||||
1. FIX VOLUMES - APPARENTLY THE SQUARES HAVE DIFFERENT VOLUMES (thanks forple)
|
||||
|
||||
*/
|
||||
|
||||
#include "d65modified.h"
|
||||
#include <stdlib.h>
|
||||
|
||||
static int d65010g031_max(int a, int b) { return (a > b) ? a : b; }
|
||||
|
||||
int d65010g031_square_tick(struct d65010g031_square_t *square, const int cycle)
|
||||
{
|
||||
if (square->period > 0)
|
||||
{
|
||||
const int period = square->period;
|
||||
square->counter += cycle;
|
||||
while (square->counter >= period)
|
||||
{
|
||||
square->counter -= period;
|
||||
square->out ^= 1;
|
||||
}
|
||||
return square->out;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// this is the bit I altered
|
||||
// THIS IS **NOT** THE ORIGINAL SOFTWARE! I am plainly marking it as such!
|
||||
const int d65Volumes[3]={
|
||||
3840, // -6dB
|
||||
5120, // -3dB
|
||||
8192 // 0dB
|
||||
};
|
||||
|
||||
int d65010g031_sound_tick(struct d65010g031_t *d65010g031, const int cycle)
|
||||
{
|
||||
int out = 0;
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
d65010g031->out[i] = 0;
|
||||
}
|
||||
if (d65010g031->ctrl & 2)
|
||||
{
|
||||
if (d65010g031->ctrl & 1) // ring modulation
|
||||
{
|
||||
int sout[3] = {
|
||||
d65010g031_square_tick(&d65010g031->square[0], cycle),
|
||||
d65010g031_square_tick(&d65010g031->square[1], cycle),
|
||||
d65010g031_square_tick(&d65010g031->square[2], cycle),
|
||||
};
|
||||
d65010g031->out[0] = (sout[0] ^ sout[1]) ? d65Volumes[0] : -d65Volumes[0];
|
||||
d65010g031->out[1] = (sout[1] ^ sout[2]) ? d65Volumes[1] : -d65Volumes[1];
|
||||
d65010g031->out[2] = (sout[2] ? d65Volumes[2] : -d65Volumes[2]);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
d65010g031->out[i] = d65010g031_square_tick(&d65010g031->square[i], cycle)?d65Volumes[i]:-d65Volumes[i];
|
||||
}
|
||||
}
|
||||
out = d65010g031->out[0] + d65010g031->out[1] + d65010g031->out[2];
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
void d65010g031_reset(struct d65010g031_t *d65010g031)
|
||||
{
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
d65010g031->square[i].period = 0;
|
||||
d65010g031->square[i].counter = 0;
|
||||
d65010g031->square[i].out = 0;
|
||||
}
|
||||
d65010g031->ctrl = 0;
|
||||
}
|
||||
|
||||
void d65010g031_write(struct d65010g031_t *d65010g031, const unsigned char a, const unsigned char d)
|
||||
{
|
||||
switch (a)
|
||||
{
|
||||
case 3:
|
||||
d65010g031->ctrl = d;
|
||||
break;
|
||||
default:
|
||||
{
|
||||
const unsigned char per = (unsigned char)(~d) & 0x3f;
|
||||
if ((per == 0) && (d65010g031->square[a].period != 0))
|
||||
{
|
||||
d65010g031->square[a].out ^= 1;
|
||||
}
|
||||
d65010g031->square[a].period = per;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
73
src/engine/platform/sound/d65modified.h
Normal file
73
src/engine/platform/sound/d65modified.h
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
|
||||
============================================================================
|
||||
|
||||
NEC D65010G031 sound emulator
|
||||
by cam900
|
||||
|
||||
This file is licensed under zlib license.
|
||||
|
||||
============================================================================
|
||||
|
||||
zlib License
|
||||
|
||||
(C) 2023-present cam900 and contributors
|
||||
|
||||
This software is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the authors be held liable for any damages
|
||||
arising from the use of this software.
|
||||
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not
|
||||
claim that you wrote the original software. If you use this software
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
============================================================================
|
||||
|
||||
TODO:
|
||||
- needs hardware test
|
||||
|
||||
*/
|
||||
|
||||
#ifndef _D65010G031_EMU_H
|
||||
#define _D65010G031_EMU_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
struct d65010g031_square_t
|
||||
{
|
||||
unsigned char period; // Period (0 = Off)
|
||||
int counter; // clock counter
|
||||
signed short out; // output
|
||||
};
|
||||
|
||||
struct d65010g031_t
|
||||
{
|
||||
struct d65010g031_square_t square[3];
|
||||
signed short out[3];
|
||||
unsigned char ctrl;
|
||||
};
|
||||
|
||||
int d65010g031_square_tick(struct d65010g031_square_t *square, const int cycle);
|
||||
|
||||
int d65010g031_sound_tick(struct d65010g031_t *d65010g031, const int cycle);
|
||||
|
||||
void d65010g031_reset(struct d65010g031_t *d65010g031);
|
||||
|
||||
void d65010g031_write(struct d65010g031_t *d65010g031, const unsigned char a, const unsigned char d);
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern "C"
|
||||
#endif
|
||||
|
||||
#endif // _D65010G031_EMU_H
|
||||
|
|
@ -39,12 +39,19 @@ public:
|
|||
u8 read(u32 offset);
|
||||
|
||||
inline void set_mute(const int ch, const bool mute) { m_channel[ch & 3].mute = mute; }
|
||||
inline unsigned int get_position(const int ch) {
|
||||
return m_channel[ch&3].pos;
|
||||
}
|
||||
inline bool is_playing(const int ch) {
|
||||
return m_channel[ch&3].play;
|
||||
}
|
||||
|
||||
// device-level overrides
|
||||
void device_reset();
|
||||
|
||||
// sound stream update overrides
|
||||
void sound_stream_update(short** outputs, int len);
|
||||
|
||||
|
||||
private:
|
||||
struct channel_def
|
||||
|
|
|
|||
|
|
@ -178,7 +178,7 @@ static signed interference(GB_gameboy_t *gb)
|
|||
ret /= 4;
|
||||
}
|
||||
|
||||
ret += rand() % (MAX_CH_AMP / 12);
|
||||
//ret += rand() % (MAX_CH_AMP / 12);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
|
|||
820
src/engine/platform/sound/m114s.c
Normal file
820
src/engine/platform/sound/m114s.c
Normal file
|
|
@ -0,0 +1,820 @@
|
|||
// license:BSD-3-Clause
|
||||
|
||||
/**********************************************************************************************
|
||||
*
|
||||
* SGS-Thomson Microelectronics M114S/M114A/M114AF Digital Sound Generator
|
||||
* by Steve Ellenoff
|
||||
* 09/02/2004
|
||||
*
|
||||
* Thanks to R.Belmont for Support & Agreeing to read through that nasty data sheet with me..
|
||||
* Big thanks to Destruk for help in tracking down the data sheet.. Could have never done it
|
||||
* without it!!
|
||||
*
|
||||
* A note about the word "table" as used by the M114S datasheet. A table refers to rom data
|
||||
* representing 1 full period/cycle of a sound. The chip always reads from 2 different tables
|
||||
* and intermixes them during playback for smoother sound. Different table lengths simply allow
|
||||
* the chip to play lower frequencies due to the fact that the table represents 1 full period,ie
|
||||
* 1 full sine wave, if that's what the rom data happens to contain.
|
||||
*
|
||||
* It would seem that the chip comes in two versions - the M114A 4Mhz & M114AF 6Mhz versions. Unlike other sound chips which
|
||||
* allow for clock variations on the same chip, this chip uses hard coded frequency tables
|
||||
* in an internal ROM based on which version of the chip is being used.
|
||||
*
|
||||
* Some ideas were taken from the source from BSMT2000 & AY8910
|
||||
*
|
||||
* Heavily modified/extended by Carsten Waechter later-on
|
||||
*
|
||||
* The mixer can optionally mix every (internal) channel into a separate stream for testing (set M114S_OUTPUT_CHANNELS to 16)
|
||||
*
|
||||
* TODO: - No full/tested support for the M114AF 6Mhz version of the chip (but seems at least to work 'good enough')
|
||||
* - Maybe also low pass filter heavily/some channels only?? (some channels sound too high frequency heavy (clicks, pops), for example on Dakar)
|
||||
**********************************************************************************************/
|
||||
|
||||
#include "m114s.h"
|
||||
#include <stdbool.h>
|
||||
|
||||
#if defined(_MSC_VER) && (_MSC_VER <= 1500)
|
||||
#define llabs _abs64
|
||||
#endif
|
||||
|
||||
#define SAMPLE_RATE 48000
|
||||
|
||||
#define USE_FREQTABLE_FROM_MANUAL // undef to use a generated freqtable with 'correct' semitones (i.e. unlike the real chip) for the M114A 4MHz
|
||||
|
||||
//!! unsure how the actual vol envelope actually works internally and at what precision -> spec says 'either immediate' (=no envelope) or 'gradual increments of 1/256 of maximum amplitude'
|
||||
// BUT THEN also mentions: only 8 MSBits from the 10 of v_linear are influenced! -> simple increase/decrease of the 8 MSBits with same frequency as table read (if diff > 128, every 2nd read if diff > 64, every 4th read if diff > 32 or every 8th read if diff <= 32)
|
||||
#define USE_VOL_ENVELOPE // to test no volume envelope (=no smooth blending in of new volume that is set), set to 0
|
||||
|
||||
#define DO_FULL_PRECISION_MIXING // unclear how the mixing of channels and interpolation of channels works internally, so this allows to switch between two modes (use highest precision vs a lower precision/interpretation of the datasheet text)
|
||||
#define USE_LERP_FOR_REPEATED_SAMPLES // unclear how repeated table reads really work, datasheet can be interpreted either way
|
||||
|
||||
#define MR_GAME_VOLUME_HACK // rather do this for specific machines only!? (i.e. is this only due to the filter network of output channels of Mr.Game??!)
|
||||
|
||||
#if 0
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#define LOG(x) printf x
|
||||
#else
|
||||
#define LOG(x) logerror x
|
||||
#endif
|
||||
|
||||
/**********************************************************************************************
|
||||
|
||||
CONSTANTS
|
||||
|
||||
***********************************************************************************************/
|
||||
|
||||
#define FRAC_BITS 16
|
||||
#define FRAC_ONE (1 << FRAC_BITS)
|
||||
#define FRAC_MASK (FRAC_ONE - 1)
|
||||
|
||||
/**********************************************************************************************
|
||||
|
||||
GLOBALS
|
||||
|
||||
***********************************************************************************************/
|
||||
|
||||
/* Table 1 & Table 2 Repetition Values based on Mode */
|
||||
static const int mode_to_rep[8][2] = {
|
||||
{2,2}, //Mode 0
|
||||
{1,1}, //Mode 1
|
||||
{4,4}, //Mode 2
|
||||
{1,1}, //Mode 3
|
||||
{1,2}, //Mode 4
|
||||
{1,1}, //Mode 5
|
||||
{1,4}, //Mode 6
|
||||
{1,1} //Mode 7
|
||||
};
|
||||
|
||||
/* Table 1 Length Values based on Mode */ // these are always larger as table 2 values! (thus mixing code below makes sense to not handle the other case)
|
||||
static const int mode_to_len_t1[8][8] = {
|
||||
{16,32,64,128,256,512,1024,2048 }, //Mode 0
|
||||
{16,32,64,128,256,512,1024,2048 }, //Mode 1
|
||||
{16,32,64,128,256,512,1024,1024 }, //Mode 2
|
||||
{16,32,64,128,256,512,1024,2048 }, //Mode 3
|
||||
{16,32,64,128,256,512,1024,2048 }, //Mode 4
|
||||
{16,32,64,128,256,512,1024,2048 }, //Mode 5
|
||||
{16,32,64,128,256,512,1024,2048 }, //Mode 6
|
||||
{16,32,64,128,256,512,1024,2048 }, //Mode 7
|
||||
};
|
||||
|
||||
/* Table 2 Length Values based on Mode */
|
||||
static const int mode_to_len_t2[8][8] = {
|
||||
{16,32,64,128,256,512,1024,2048 }, //Mode 0 //datasheet has last value = 1048 //!! -> try 1024 instead??
|
||||
{16,32,64,128,256,512,1024,2048 }, //Mode 1
|
||||
{16,32,64,128,256,512,1024,1024 }, //Mode 2
|
||||
{16,32,64,128,256,512,1024,2048 }, //Mode 3
|
||||
{ 8,16,32, 64,128,256, 512,1024 }, //Mode 4
|
||||
{16,16,16, 32, 64,128, 256, 512 }, //Mode 5
|
||||
{ 4, 8,16, 32, 64,128, 256, 512 }, //Mode 6
|
||||
{16,16,16, 16, 32, 64, 128, 256 }, //Mode 7
|
||||
};
|
||||
|
||||
/* Attenuation Table */
|
||||
static const int v_linear[32] = {
|
||||
1023,939,863,791,727,667,611,559,515,471,431,
|
||||
395,363,335,307,283,259,235,215,199,183,166,
|
||||
152,140,128,117,107, 98, 90, 83, 76, 69
|
||||
};
|
||||
// a_decibel = 20 * log((v_linear+1)/1024)
|
||||
/*static const double a_decibel[32] = {
|
||||
0.00, 0.74, 1.48, 2.23, 2.96, 3.71, 4.47, 5.24, 5.95,
|
||||
6.73, 7.50, 8.25, 8.98, 9.68,10.43,11.14,11.91,12.75,
|
||||
13.52,14.19,14.91,15.75,16.51,17.22,17.99,18.77,19.54,
|
||||
20.29,21.03,21.72,22.48,23.30
|
||||
};*/
|
||||
|
||||
#ifdef USE_FREQTABLE_FROM_MANUAL
|
||||
/* Frequency Table for a 4Mhz Clocked Chip */
|
||||
static const double freqtable4Mhz[256] = {
|
||||
1016.78,1021.45,1026.69,1031.46,1036.27,1041.67,1044.39,1045.48,
|
||||
1046.57,1047.67,1048.77,1051.52,1056.52,1061.57,1066.67,1071.81, //0x00 - 0x0F
|
||||
1077.01,1082.25,1087.55,1092.90,1098.30,1103.14,1106.81,1107.42,
|
||||
1108.65,1109.88,1111.11,1114.21,1119.19,1124.86,1130.58,1135.72, //0x10 - 0x1F
|
||||
1140.90,1146.79,1152.07,1158.08,1163.47,1168.91,1172.33,1173.71,
|
||||
1174.40,1175.78,1177.16,1180.64,1186.24,1191.90,1197.60,1203.37, //0x20 - 0x2F
|
||||
1209.19,1215.07,1221.00,1226.99,1232.29,1238.39,1242.24,1243.78,
|
||||
1244.56,1245.33,1246.88,1250.78,1256.28,1262.63,1269.04,1274.70, //0x30 - 0x3F
|
||||
1281.23,1287.00,1293.66,1299.55,1305.48,1312.34,1315.79,1317.52,
|
||||
1318.39,1319.26,1321.00,1324.50,1331.56,1337.79,1344.09,1350.44, //0x40 - 0x4F
|
||||
1356.85,1363.33,1369.86,1376.46,1383.13,1389.85,1393.73,1395.67,
|
||||
1396.65,1397.62,1398.60,1403.51,1410.44,1417.43,1424.50,1430.62, //0x50 - 0x5F
|
||||
1437.81,1445.09,1451.38,1458.79,1466.28,1472.75,1478.20,1479.29,
|
||||
1480.38,1481.48,1482.58,1486.99,1494.77,1501.50,1508.30,1516.30, //0x60 - 0x6F
|
||||
1523.23,1530.22,1538.46,1545.60,1552.80,1560.06,1564.95,1566.17,
|
||||
1567.40,1568.63,1569.86,1576.04,1583.53,1591.09,1598.72,1606.43, //0x70 - 0x7F
|
||||
1614.21,1622.06,1629.99,1638.00,1644.74,1652.89,1658.37,1659.75,
|
||||
1661.13,1662.51,1663.89,1669.45,1677.85,1684.92,1693.48,1702.13, //0x80 - 0x8F
|
||||
1709.40,1718.21,1727.12,1734.61,1743.68,1751.31,1757.47,1759.01, //(2nd entry in manual shows 1781.21 but that's cleary wrong)
|
||||
1760.56,1762.11,1763.89,1768.35,1777.78,1785.71,1793.72,1803.43, //0x90 - 0x9F
|
||||
1811.59,1819.84,1829.83,1838.24,1846.72,1855.29,1860.47,1862.20,
|
||||
1863.93,1865.67,1867.41,1874.41,1883.24,1892.15,1901.14,1910.22, //0xA0 - 0xAF
|
||||
1919.39,1928.64,1937.98,1947.42,1956.95,1966.57,1972.39,1974.33,
|
||||
1976.28,1978.24,1980.20,1984.13,1994.02,2004.01,2014.10,2024.29, //0xB0 - 0xBF
|
||||
2032.52,2042.90,2053.39,2063.98,2072.54,2083.33,2087.68,2089.86,
|
||||
2092.05,2094.24,2096.44,2103.05,2114.16,2123.14,2134.47,2143.62, //0xC0 - 0xCF
|
||||
2155.17,2164.50,2176.28,2185.79,2195.39,2207.51,2212.39,2214.84,
|
||||
2217.29,2219.76,2222.22,2227.17,2239.64,2249.72,2259.89,2272.73, //0xD0 - 0xDF
|
||||
2283.11,2293.58,2304.15,2314.81,2325.58,2339.18,2344.67,2347.42,
|
||||
2350.18,2352.94,2355.71,2361.28,2372.48,2383.79,2395.21,2406.74, //0xE0 - 0xEF
|
||||
0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0, //0xF0 - 0xFF (Special codes)
|
||||
};
|
||||
|
||||
/* Frequency Table for a 6Mhz Clocked Chip */
|
||||
static const double freqtable6Mhz[256] = {
|
||||
1523.78,1523.79,1538.64,1545.79,1552.99,1561.08,1565.16,1566.80,
|
||||
1568.44,1570.08,1571.73,1575.86,1583.35,1590.91,1598.55,1606.26, //0x00 - 0x0F
|
||||
1614.04,1621.90,1629.84,1637.86,1645.95,1653.22,1658.71,1659.62,
|
||||
1661.46,1663.31,1665.16,1669.79,1677.27,1685.76,1694.34,1702.03, //0x10 - 0x1F
|
||||
1709.80,1718.62,1726.54,1735.54,1743.62,1751.77,1758.91,1758.97,
|
||||
1760.00,1762.07,1764.14,1769.35,1777.75,1786.22,1794.78,1803.42, //0x20 - 0x2F
|
||||
1812.14,1820.95,1829.84,1838.82,1846.75,1855.90,1861.66,1863.98,
|
||||
1865.14,1866.30,1868.63,1874.47,1882.71,1892.22,1901.83,1910.31, //0x30 - 0x3F
|
||||
1920.10,1928.75,1938.73,1947.55,1956.45,1966.72,1971.89,1974.49,
|
||||
1975.79,1977.10,1979.71,1984.95,1995.53,2004.87,2014.30,2023.82, //0x40 - 0x4F
|
||||
2033.43,2043.14,2052.93,2062.82,2072.82,2082.89,2088.70,2091.61,
|
||||
2093.07,2094.54,2096.00,2103.35,2113.74,2124.22,2134.81,2143.98, //0x50 - 0x5F
|
||||
2154.77,2165.66,2175.09,2186.20,2197.42,2207.13,2215.28,2216.92,
|
||||
2218.56,2220.21,2221.85,2228.46,2240.12,2250.21,2260.39,2272.39, //0x60 - 0x6F
|
||||
2282.77,2293.25,2305.60,2316.29,2327.08,2337.97,2345.29,2347.13,
|
||||
2348.97,2350.81,2352.65,2361.92,2373.14,2384.47,2395.91,2407.45, //0x70 - 0x7F
|
||||
2419.11,2430.88,2442.77,2454.77,2464.87,2477.09,2485.31,2487.37,
|
||||
2489.44,2491.50,2493.58,2501.90,2514.50,2525.09,2537.92,2550.88, //0x80 - 0x8F
|
||||
2561.78,2574.98,2588.32,2599.55,2613.15,2624.59,2633.81,2636.13,
|
||||
2638.45,2640.78,2643.10,2650.11,2664.25,2676.14,2688.14,2702.69, //0x90 - 0x9F
|
||||
2714.93,2727.28,2742.25,2754.85,2767.57,2780.41,2788.17,2790.76,
|
||||
2793.06,2795.97,2798.58,2809.07,2822.30,2835.65,2849.13,2862.73, //0xA0 - 0xAF
|
||||
2876.47,2890.34,2904.34,2918.48,2932.76,2947.18,2955.90,2958.82,
|
||||
2961.74,2964.67,2967.60,2973.49,2988.31,3003.29,3018.41,3033.68, //0xB0 - 0xBF
|
||||
3046.02,3061.57,3077.29,3093.17,3105.99,3122.17,3128.68,3131.95,
|
||||
3135.23,3138.51,3141.80,3151.71,3168.37,3181.83,3198.80,3212.52, //0xC0 - 0xCF
|
||||
3229.83,3243.81,3261.46,3275.72,3290.10,3308.26,3315.58,3319.25,
|
||||
3322.93,3326.61,3330.31,3337.73,3356.42,3371.52,3386.76,3406.00, //0xD0 - 0xDF
|
||||
3421.55,3437.25,3453.09,3469.07,3485.21,3505.59,3513.81,3517.93,
|
||||
3522.07,3526.21,3530.37,3538.70,3555.49,3572.44,3589.56,3606.84, //0xE0 - 0xEF
|
||||
0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0, //0xF0 - 0xFF (Special codes)
|
||||
};
|
||||
#else
|
||||
/* Frequency Table for a 4Mhz Clocked Chip
|
||||
Note: Only the 1st set of frequencies came from the manual, the rest were calculated using 1 semitone increments
|
||||
using a program called Test Tone Generator 3.91
|
||||
*/
|
||||
static const double freqtable4Mhz[256] = {
|
||||
1016.78,1021.45,1026.69,1031.46,1036.27,1041.67,1044.39,1045.48,
|
||||
1046.57,1047.67,1048.77,1051.52,1056.52,1061.57,1066.67,1071.81, //0x00 - 0x0F
|
||||
1077.24,1082.19,1087.74,1092.79,1097.89,1103.61,1106.49,1107.65,
|
||||
1108.80,1109.97,1111.13,1114.05,1119.34,1124.69,1130.10,1135.54, //0x10 - 0x1F
|
||||
1141.29,1146.54,1152.42,1157.77,1163.17,1169.24,1172.29,1173.51,
|
||||
1174.74,1175.97,1177.20,1180.29,1185.90,1191.57,1197.30,1203.07, //0x20 - 0x2F
|
||||
1209.16,1214.72,1220.95,1226.62,1232.34,1238.76,1242.00,1243.29,
|
||||
1244.59,1245.90,1247.20,1250.47,1256.42,1262.43,1268.49,1274.60, //0x30 - 0x3F
|
||||
1281.06,1286.95,1293.55,1299.56,1305.62,1312.42,1315.85,1317.22,
|
||||
1318.60,1319.98,1321.37,1324.83,1331.13,1337.49,1343.92,1350.40, //0x40 - 0x4F
|
||||
1357.24,1363.47,1370.46,1376.83,1383.25,1390.46,1394.09,1395.55,
|
||||
1397.00,1398.47,1399.94,1403.61,1410.29,1417.03,1423.83,1430.70, //0x50 - 0x5F
|
||||
1437.94,1444.55,1451.96,1457.07,1465.51,1473.14,1477.00,1478.53,
|
||||
1480.07,1481.63,1483.18,1486.99,1494.14,1501.29,1508.50,1515.77, //0x60 - 0x6F
|
||||
1523.45,1530.45,1538.30,1545.44,1552.65,1560.74,1564.82,1566.45,
|
||||
1568.08,1569.73,1571.38,1575.50,1583.00,1590.56,1598.20,1605.90, //0x70 - 0x7F
|
||||
1614.04,1621.45,1629.77,1637.34,1644.98,1653.55,1657.87,1659.60,
|
||||
1661.33,1663.07,1664.82,1669.18,1677.12,1685.14,1693.23,1701.39, //0x80 - 0x8F
|
||||
1710.01,1717.87,1726.69,1734.70,1742.79,1751.87,1756.45,1758.28,
|
||||
1760.11,1761.96,1763.81,1768.44,1776.85,1785.34,1793.92,1802.56, //0x90 - 0x9F
|
||||
1811.70,1820.02,1829.35,1837.85,1846.42,1856.05,1860.89,1862.83,
|
||||
1864.78,1866.74,1868.70,1873.60,1882.50,1891.50,1900.59,1909.75, //0xA0 - 0xAF
|
||||
1919.43,1928.24,1938.13,1947.14,1956.22,1966.41,1971.55,1973.60,
|
||||
1975.66,1977.74,1979.81,1985.01,1994.44,2003.98,2013.61,2023.31, //0xB0 - 0xBF
|
||||
2033.56,2042.90,2053.38,2062.92,2072.54,2083.34,2088.78,2090.96,
|
||||
2093.14,2095.34,2097.54,2103.04,2113.04,2123.14,2133.34,2143.62, //0xC0 - 0xCF
|
||||
2154.48,2164.38,2175.48,2185.59,2195.78,2207.22,2212.99,2215.30,
|
||||
2217.60,2219.94,2222.27,2228.09,2238.69,2249.39,2260.20,2271.09, //0xD0 - 0xDF
|
||||
2282.59,2293.08,2304.84,2315.55,2326.35,2338.47,2344.58,2347.02,
|
||||
2349.47,2351.94,2354.41,2360.58,2371.80,2383.14,2394.59,2406.13, //0xE0 - 0xEF
|
||||
0,0,0,0,0,0,0,0,
|
||||
0,0,0,0,0,0,0,0, //0xF0 - 0xFF (Special codes)
|
||||
};
|
||||
#endif
|
||||
|
||||
/**********************************************************************************************
|
||||
|
||||
read_sample -- returns 1 sample from the channel's output buffer, but upsamples/interpolates the data
|
||||
as necessary to match the Machine driver's output sample rate.
|
||||
|
||||
***********************************************************************************************/
|
||||
int16_t read_sample(struct M114SChannel * const channel, const uint32_t length)
|
||||
{
|
||||
const uint32_t pos = channel->outpos >> FRAC_BITS;
|
||||
if (pos < length)
|
||||
{
|
||||
const int32_t frac = channel->outpos & FRAC_MASK;
|
||||
|
||||
// interpolate
|
||||
const int16_t val1 = channel->output[pos];
|
||||
const int16_t val2 = channel->output[pos+1 < length ? pos : 0]; // wrap around to 0 (see code below!)?
|
||||
const int16_t sample = (val1 * ((int32_t)FRAC_ONE - frac) + val2 * frac) >> FRAC_BITS;
|
||||
|
||||
channel->outpos += channel->incr;
|
||||
return sample;
|
||||
}
|
||||
else {
|
||||
//LOG(("End of Table\n"));
|
||||
//channel->end_of_table++;
|
||||
channel->outpos = 0; // related to cyclic reading of tables //!! rather wrap over including exact FRAC_BITS instead of just nulling it?
|
||||
return channel->output[0]; //!! dto. would need interpolation then
|
||||
}
|
||||
}
|
||||
|
||||
/**********************************************************************************************
|
||||
|
||||
read_table -- Reads the two tables of rom data into a temporary buffer,
|
||||
mixes the samples using the chip's internal interpolation equation,
|
||||
applies the volume, and writes the single mixed sample to the output buffer for the channel.
|
||||
It processes the entire table1 length of data.
|
||||
|
||||
Note: Eventually this should flag an End of Table, and should process new table data
|
||||
|
||||
***********************************************************************************************/
|
||||
void read_table(struct M114SChip * const chip, struct M114SChannel * const channel) // get rid of this and write directly to output buffer?!
|
||||
{
|
||||
int i;
|
||||
#ifndef USE_LERP_FOR_REPEATED_SAMPLES
|
||||
int j;
|
||||
#endif
|
||||
const int8_t * const rom = &chip->region_base[0];
|
||||
const int t1start = channel->table1.start_address;
|
||||
const int t2start = channel->table2.start_address;
|
||||
const int lent1 = channel->table1.length;
|
||||
const int lent2 = channel->table2.length;
|
||||
const int rep1 = channel->table1.reread;
|
||||
const int rep2 = channel->table2.reread;
|
||||
const int intp = channel->regs.interp;
|
||||
|
||||
int8_t tb1[4096]; // Temp copy buffer for Table 1 // long enough to hold max sizes of 2048*2 or 1024*4
|
||||
int8_t tb2[4096]; // Temp copy buffer for Table 2 // dto.
|
||||
memset(&tb1,0,sizeof(tb1));
|
||||
memset(&tb2,0,sizeof(tb2));
|
||||
|
||||
//LOG(("t1s = %d t2s = %d, l1=%d l2=%d, r1=%d, r2=%d, int = %d\n",t1start,t2start,lent1,lent2,rep1,rep2,intp));
|
||||
|
||||
// datasheet says: multiple reading permits interpolation between two adjoining samples on the same table, so do we really need to lerp instead of just repeating same values here?
|
||||
#ifdef USE_LERP_FOR_REPEATED_SAMPLES
|
||||
//Scan Table 1
|
||||
if(rep1 == 1)
|
||||
memcpy(tb1,&rom[t1start],lent1);
|
||||
else if(rep1 == 2)
|
||||
for(i=0; i<lent1; i++)
|
||||
{
|
||||
tb1[i*2+0] = rom[i+t1start];
|
||||
tb1[i*2+1] = ((int)rom[i+t1start] + (int)rom[t1start + ((i < lent1-1) ? i+1 : i)])/2;
|
||||
}
|
||||
else //if(rep1 == 4)
|
||||
for(i=0; i<lent1; i++)
|
||||
{
|
||||
int val1 = (int)rom[i+t1start];
|
||||
int val2 = (int)rom[t1start + ((i < lent1-1) ? i+1 : i)];
|
||||
tb1[i*4+0] = rom[i+t1start];
|
||||
tb1[i*4+1] = (val1*3 + val2 )/4;
|
||||
tb1[i*4+2] = (val1 + val2 )/2;
|
||||
tb1[i*4+3] = (val1 + val2*3)/4;
|
||||
}
|
||||
|
||||
//Scan Table 2
|
||||
if (rep2 == 1)
|
||||
memcpy(tb2,&rom[t2start+0x2000],lent2); //A13 is toggled high on Table 2 reading (Implementation specific - ie, Mr. Game)
|
||||
else if(rep2 == 2)
|
||||
for(i=0; i<lent2; i++)
|
||||
{
|
||||
tb2[i*2+0] = rom[i+t2start+0x2000];
|
||||
tb2[i*2+1] = ((int)rom[i+t2start+0x2000] + (int)rom[t2start+0x2000 + ((i < lent2-1) ? i+1 : i)])/2;
|
||||
}
|
||||
else //if(rep2 == 4)
|
||||
for(i=0; i<lent2; i++)
|
||||
{
|
||||
int val1 = (int)rom[i+t2start+0x2000];
|
||||
int val2 = (int)rom[t2start+0x2000 + ((i < lent2-1) ? i+1 : i)];
|
||||
tb2[i*4+0] = rom[i+t2start+0x2000];
|
||||
tb2[i*4+1] = (val1*3 + val2 )/4;
|
||||
tb2[i*4+2] = (val1 + val2 )/2;
|
||||
tb2[i*4+3] = (val1 + val2*3)/4;
|
||||
}
|
||||
#else
|
||||
//Scan Table 1
|
||||
for(i=0; i<lent1; i++)
|
||||
for(j=0; j<rep1; j++)
|
||||
tb1[j+(i*rep1)] = rom[i+t1start];
|
||||
//Scan Table 2
|
||||
for(i=0; i<lent2; i++)
|
||||
for(j=0; j<rep2; j++)
|
||||
tb2[j+(i*rep2)] = rom[i+t2start+0x2000]; //A13 is toggled high on Table 2 reading (Implementation specific - ie, Mr. Game)
|
||||
#endif
|
||||
|
||||
// Table1 is always larger, so use that as the size
|
||||
|
||||
// How to make up difference (table 2 can be shorter than table 1)? -> was memset above already, so zero for now
|
||||
/*for(i=lent2*rep2; i<lent1*rep1; i++)
|
||||
tb2[i] = ?;*/
|
||||
|
||||
// Now Mix based on Interpolation Bits
|
||||
for(i=0; i<lent1*rep1; i++) {
|
||||
int l;
|
||||
//Apply volume - If envelope - inc/dec volume to calculate sample volume (only 8 most significant bits from the 10 bits, thus +/-4), otherwise, apply directly
|
||||
#ifdef USE_VOL_ENVELOPE
|
||||
if(channel->regs.env_enable && ((i & (abs(channel->step_rate_volume_env)-1)) == abs(channel->step_rate_volume_env)/2)) { // check if we match the frequency that the value must be updated
|
||||
channel->current_volume += channel->step_rate_volume_env > 0 ? 4 : -4; // dependent on sign inc/dec by 4
|
||||
if(channel->step_rate_volume_env > 0)
|
||||
{
|
||||
if(channel->current_volume > channel->target_volume)
|
||||
channel->current_volume = channel->target_volume;
|
||||
}
|
||||
else if(channel->current_volume < channel->target_volume)
|
||||
channel->current_volume = channel->target_volume;
|
||||
}
|
||||
#endif
|
||||
//write to output buffer
|
||||
#ifdef DO_FULL_PRECISION_MIXING
|
||||
l = (int)tb1[i] * (intp + 1) + (int)tb2[i] * (15 - intp);
|
||||
channel->output[i] = (int16_t)(0x6f * l * (channel->current_volume + 1) / (1024*16)); // Max Volume would be 256 for an int16_t value (so why was 0x6f chosen??)
|
||||
#else
|
||||
l = ((int)tb1[i] * (intp + 1) / 16) + ((int)tb2[i] * (15 - intp) / 16); // formula seen in datasheet, but unclear what this means precision wise (i.e. is this only meant as real number pseudo code?)
|
||||
channel->output[i] = (int16_t)(0x6f * l * (channel->current_volume + 1) / 1024); // Max Volume would be 256 for an int16_t value (so why was 0x6f chosen??) //!! do 0x6f scale AFTER division??
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/**********************************************************************************************
|
||||
|
||||
m114s_update -- update the sound chip so that it is in sync with CPU execution
|
||||
|
||||
***********************************************************************************************/
|
||||
//Seems this sometimes still produces some static, but I don't know why!
|
||||
void m114s_update(struct M114SChip* chip,
|
||||
int16_t **buffer,
|
||||
int samples)
|
||||
{
|
||||
|
||||
while (samples > 0)
|
||||
{
|
||||
#if M114S_OUTPUT_CHANNELS == 1
|
||||
int32_t accum = 0;
|
||||
#else
|
||||
int32_t accum[M114S_OUTPUT_CHANNELS];
|
||||
#endif
|
||||
int c;
|
||||
|
||||
#if M114S_OUTPUT_CHANNELS != 1
|
||||
/* clear accum */
|
||||
for(c = 0; c < M114S_OUTPUT_CHANNELS; c++)
|
||||
accum[c] = 0;
|
||||
#endif
|
||||
|
||||
/* loop over channels */
|
||||
for (c = 0; c < M114S_CHANNELS; c++)
|
||||
{
|
||||
struct M114SChannel * const channel = &chip->channels[c];
|
||||
/* Grab the next sample from the table data if the channel is active */
|
||||
if (channel->active)
|
||||
{
|
||||
//We use Table 1 to drive everything, as Table 2 is really for mixing into Table 1..
|
||||
int32_t sample = read_sample(channel, channel->table1.total_length); //!! int16_t if MR_GAME_VOLUME_HACK would be off
|
||||
|
||||
#ifdef MR_GAME_VOLUME_HACK
|
||||
sample = sample*chip->channel_volume[channel->regs.outputs] / 100; // boost percussion on Dakar, penalty some of the other instruments
|
||||
#endif
|
||||
//Mix the output of this channel to the appropriate output channel
|
||||
#if M114S_OUTPUT_CHANNELS == 1
|
||||
accum
|
||||
#elif M114S_OUTPUT_CHANNELS == 4
|
||||
accum[channel->regs.outputs]
|
||||
#else
|
||||
accum[c]
|
||||
#endif
|
||||
+= sample;
|
||||
}
|
||||
}
|
||||
|
||||
/* Update the buffer & Ensure we don't clip */
|
||||
#if M114S_OUTPUT_CHANNELS == 1
|
||||
accum /= (int32_t)4;
|
||||
*buffer++ = (accum < -32768) ? -32768 : ((accum > 32767) ? 32767 : accum);
|
||||
#else
|
||||
for (c = 0; c < M114S_OUTPUT_CHANNELS; c++)
|
||||
*buffer[c]++ = (accum[c] < -32768) ? -32768 : ((accum[c] > 32767) ? 32767 : accum[c]);
|
||||
#endif
|
||||
|
||||
samples--;
|
||||
}
|
||||
}
|
||||
|
||||
/**********************************************************************************************
|
||||
|
||||
M114S_sh_start -- start emulation of the M114S
|
||||
|
||||
***********************************************************************************************/
|
||||
|
||||
INLINE void init_channel(struct M114SChannel * const channel)
|
||||
{
|
||||
//set all internal registers to 0!
|
||||
channel->active = 0;
|
||||
channel->outpos = 0;
|
||||
channel->prev_volume = 0;
|
||||
memset(&channel->output,0,sizeof(channel->output));
|
||||
memset(&channel->regs, 0,sizeof(channel->regs));
|
||||
memset(&channel->table1,0,sizeof(channel->table1));
|
||||
memset(&channel->table2,0,sizeof(channel->table2));
|
||||
}
|
||||
|
||||
|
||||
INLINE void init_all_channels(struct M114SChip * const chip)
|
||||
{
|
||||
int i;
|
||||
|
||||
/* init the channels */
|
||||
for (i = 0; i < M114S_CHANNELS; i++)
|
||||
init_channel(&chip->channels[i]);
|
||||
|
||||
//Chip init stuff
|
||||
memset(&chip->tempch_regs,0,sizeof(chip->tempch_regs));
|
||||
chip->channel = 0;
|
||||
chip->bytes_read = 0;
|
||||
}
|
||||
|
||||
|
||||
int M114S_sh_start(const struct MachineSound *msound)
|
||||
{
|
||||
const struct M114Sinterface *intf = msound->sound_interface;
|
||||
#if M114S_OUTPUT_CHANNELS == 1
|
||||
#else
|
||||
int vol[M114S_OUTPUT_CHANNELS];
|
||||
#endif
|
||||
int i,j;
|
||||
|
||||
/* initialize the chips */
|
||||
memset(&m114schip, 0, sizeof(m114schip));
|
||||
for (i = 0; i < intf->num; i++)
|
||||
{
|
||||
/* Chip specific setup based on clock speed */
|
||||
switch(intf->baseclock[i]) {
|
||||
// M114A 4 Mhz
|
||||
case 4000000:
|
||||
m114schip[i].reset_cycles = 4000000 * 0.000128; // Chip resets in 128us (microseconds)
|
||||
m114schip[i].is_M114A = 1;
|
||||
break;
|
||||
// M114AF 6 Mhz
|
||||
case 6000000:
|
||||
case 5994560: // from datasheet: 5.99456 MHz
|
||||
m114schip[i].reset_cycles = intf->baseclock[i] * 0.000085; // Chip resets in 85us (microseconds)
|
||||
m114schip[i].is_M114A = 0;
|
||||
LOG(("M114S Chip #%d - 6Mhz chip clock not fully supported/tested at this time!\n", i));
|
||||
return 1;
|
||||
default:
|
||||
LOG(("M114S Chip #%d - Invalid Base Clock value specified! Only 4Mhz & 6Mhz values allowed!\n",i));
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* initialize the region & interface info */
|
||||
m114schip[i].cpu_num = intf->cpunum[i];
|
||||
m114schip[i].region_base = (int8_t *)memory_region(intf->region[i]);
|
||||
m114schip[i].intf = (struct M114Sinterface *)intf;
|
||||
|
||||
/* init the channels */
|
||||
init_all_channels(&m114schip[i]);
|
||||
for(j = 0; j < 4; j++)
|
||||
m114schip[i].channel_volume[j] = intf->mixing_level[i][j];
|
||||
}
|
||||
|
||||
/* success */
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**********************************************************************************************
|
||||
|
||||
M114S_sh_stop -- stop emulation of the M114S
|
||||
|
||||
***********************************************************************************************/
|
||||
|
||||
void M114S_sh_stop(void)
|
||||
{
|
||||
}
|
||||
|
||||
/**********************************************************************************************
|
||||
|
||||
M114S_sh_reset -- reset emulation of the M114S
|
||||
|
||||
***********************************************************************************************/
|
||||
|
||||
void M114S_sh_reset(void)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < 1; i++) {
|
||||
/* reset all channels */
|
||||
init_all_channels(&m114schip[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/**********************************************************************************************
|
||||
|
||||
process_freq_codes -- There are up to 16 special values for frequency that signify a code
|
||||
|
||||
***********************************************************************************************/
|
||||
void process_freq_codes(struct M114SChip * const chip)
|
||||
{
|
||||
//Grab pointer to channel being programmed
|
||||
struct M114SChannel * const channel = &chip->channels[chip->channel];
|
||||
switch(channel->regs.frequency)
|
||||
{
|
||||
//ROMID - ROM Identification (Are you kidding me?)
|
||||
case 0xf8:
|
||||
LOG(("* * Channel: %02d: Frequency Code: %02x - ROMID * * \n",chip->channel,channel->regs.frequency));
|
||||
break;
|
||||
//SSG - Set Syncro Global
|
||||
case 0xf9:
|
||||
LOG(("* * Channel: %02d: Frequency Code: %02x - SSG * * \n",chip->channel,channel->regs.frequency));
|
||||
break;
|
||||
//RSS - Reverse Syncro Status
|
||||
case 0xfa:
|
||||
// check if used, as this will force frequency changes to wait until table ends
|
||||
LOG(("* * Channel: %02d: Frequency Code: %02x - RSS * * \n",chip->channel,channel->regs.frequency));
|
||||
break;
|
||||
//RSG - Reset Syncro Global
|
||||
case 0xfb:
|
||||
LOG(("* * Channel: %02d: Frequency Code: %02x - RSG * * \n",chip->channel,channel->regs.frequency));
|
||||
break;
|
||||
//PSF - Previously Selected Frequency
|
||||
case 0xfc:
|
||||
// seems to be unused by Mr.Game
|
||||
LOG(("* * Channel: %02d: Frequency Code: %02x - PSF * * \n",chip->channel,channel->regs.frequency));
|
||||
break;
|
||||
//FFT - Forced Table Termination
|
||||
case 0xff:
|
||||
//Stop whatever output from playing by simulating an end of table event!
|
||||
channel->outpos = 0; // but this will just put the cyclic counter back to the beginning!?!
|
||||
//LOG(("* * Channel: %02d: Frequency Code: %02x - FFT * * \n",chip->channel,channel->regs.frequency));
|
||||
break;
|
||||
default:
|
||||
LOG(("* * Channel: %02d: Frequency Code: %02x - UNKNOWN * * \n",chip->channel,channel->regs.frequency));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**********************************************************************************************
|
||||
|
||||
process_channel_data -- complete programming for a channel now exists, process it!
|
||||
|
||||
***********************************************************************************************/
|
||||
|
||||
void process_channel_data(struct M114SChip * const chip)
|
||||
{
|
||||
//Grab pointer to channel being programmed
|
||||
struct M114SChannel * const channel = &chip->channels[chip->channel];
|
||||
|
||||
//Reset # of bytes for next group
|
||||
chip->bytes_read = 0;
|
||||
|
||||
//Copy data to the appropriate channel registers from our temp channel registers
|
||||
memcpy(&channel->regs,&chip->tempch_regs,sizeof(chip->tempch_regs));
|
||||
|
||||
//Look for the 16 special frequency codes
|
||||
if(channel->regs.frequency >= 0xf0) {
|
||||
process_freq_codes(chip);
|
||||
//FFT & PSF are the only codes that should continue to process channel data AFAIK
|
||||
if(channel->regs.frequency != 0xff && channel->regs.frequency != 0xfc)
|
||||
return;
|
||||
}
|
||||
|
||||
//If Attenuation set to 0x3F - The channel becomes inactive
|
||||
if(channel->regs.atten == 0x3f) {
|
||||
channel->active = 0;
|
||||
return;
|
||||
}
|
||||
else
|
||||
//Process this channel
|
||||
{
|
||||
//Calculate # of repetitions for Table 1 & Table 2
|
||||
const int rep1 = mode_to_rep[channel->regs.read_meth][0];
|
||||
const int rep2 = mode_to_rep[channel->regs.read_meth][1];
|
||||
//Calculate Table Length for Table 1 & Table 2
|
||||
const int lent1 = mode_to_len_t1[channel->regs.read_meth][channel->regs.table_len];
|
||||
const int lent2 = mode_to_len_t2[channel->regs.read_meth][channel->regs.table_len];
|
||||
//Start & Stop Address - Note the special case for table length of 16 - Bit 5 always 1 in this case // but also for special cases 8 and 4 for table 2?!?
|
||||
//Calculate Table 1 Start & End Address in ROM
|
||||
const int t1start = ((channel->regs.table1_addr<<5) & (~(lent1-1)&0x1fff)) | (lent1 == 16 ? 0x10 : 0); //T1 Addr is only upper 8 bits, but masked by length
|
||||
//const int t1end = t1start | (lent1-1);
|
||||
//Calculate Table 2 Start & End Address in ROM
|
||||
const int t2start = ((channel->regs.table2_addr<<5) & (~(lent2-1)&0x1fff)) | (lent2 <= 16 ? 0x10 : 0); //T2 Addr is only upper 8 bits, but masked by length
|
||||
//const int t2end = t2start | (lent2-1);
|
||||
|
||||
//Calculate initial frequency of both tables
|
||||
double freq = chip->is_M114A ? freqtable4Mhz[channel->regs.frequency] : freqtable6Mhz[channel->regs.frequency];
|
||||
|
||||
//Calculate new volume
|
||||
channel->target_volume = channel->regs.atten < 32 ? v_linear[channel->regs.atten] : 0; // channel must be kept active, but is it really 0? Or is the last value used (v_linear[31])? Or something inbetween??
|
||||
|
||||
//Adjust frequency if octave divisor set
|
||||
if (channel->regs.oct_divisor)
|
||||
freq /= 2.; // maybe new frequency must be clamped to freqtable[0]??
|
||||
|
||||
//Channel is now active!
|
||||
channel->active = 1;
|
||||
|
||||
// Setup Sample Rate/Step size increase
|
||||
channel->incr = (uint32_t)(freq * ((double)(16u << FRAC_BITS) / SAMPLE_RATE));
|
||||
|
||||
//Assign start & stop address offsets to ROM
|
||||
channel->table1.start_address = t1start;
|
||||
channel->table2.start_address = t2start;
|
||||
//channel->table1.stop_address = t1end;
|
||||
//channel->table2.stop_address = t2end;
|
||||
|
||||
//Assign # of times to re-read & Length
|
||||
channel->table1.reread = rep1;
|
||||
channel->table2.reread = rep2;
|
||||
channel->table1.length = lent1;
|
||||
channel->table2.length = lent2;
|
||||
channel->table1.total_length = lent1*rep1;
|
||||
channel->table2.total_length = lent2*rep2;
|
||||
|
||||
//Calculate Sample Volume
|
||||
//- If Envelope should be used, Take the difference in new volume & current volume, and break it into the # of samples in the table
|
||||
//- If No Envelope should be used, Volume is simply based on the Volume Table
|
||||
#ifdef USE_VOL_ENVELOPE
|
||||
if(channel->regs.env_enable) {
|
||||
const int diff = channel->target_volume - channel->prev_volume;
|
||||
channel->step_rate_volume_env = abs(diff) > 128 ? 1 : abs(diff) > 64 ? 2 : abs(diff) > 32 ? 4 : 8;
|
||||
if(channel->target_volume < channel->prev_volume)
|
||||
channel->step_rate_volume_env = -channel->step_rate_volume_env;
|
||||
channel->current_volume = channel->prev_volume;
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
channel->current_volume = channel->target_volume;
|
||||
}
|
||||
//Update Last Volume
|
||||
channel->prev_volume = channel->target_volume;
|
||||
|
||||
//Temp hack to ensure we only generate the ouput data 1x - this is WRONG and needs to be addressed eventually!
|
||||
//if(channel->output[0] == 0)
|
||||
read_table(chip,channel);
|
||||
|
||||
#if 0
|
||||
//if(chip->channel == 2) {
|
||||
if(channel->regs.outputs == 2) {
|
||||
if(channel->regs.frequency == 0x70)
|
||||
{
|
||||
LOG(("orig: = %0f, freq = %0f, incr = %0d \n",freqtable4Mhz[channel->regs.frequency],freq,channel->incr));
|
||||
}
|
||||
//LOG(("EOT=%d\n",channel->end_of_table));
|
||||
LOG(("C:%02d V:%02d FQ:%03x TS1:%02x TS2:%02x T1L:%04d T1R:%01d T2L:%04d T2R:%01d OD=%01d I:%02d E:%01d\n",
|
||||
chip->channel,
|
||||
channel->regs.atten,
|
||||
channel->regs.frequency,
|
||||
t1start,t2start,
|
||||
lent1,rep1,
|
||||
lent2,rep2,
|
||||
channel->regs.oct_divisor,
|
||||
channel->regs.interp,
|
||||
channel->regs.env_enable
|
||||
));
|
||||
}
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**********************************************************************************************
|
||||
|
||||
m114s_data_write -- handle a write to the data bus of the M114S
|
||||
|
||||
The chip has a data bus width of 6 bits, and must be fed 8 consecutive bytes
|
||||
- thus 48 bits of programming! All data must be fed in order, so we make a few assumptions.
|
||||
|
||||
***********************************************************************************************/
|
||||
void m114s_data_write(struct M114SChip* chip, data8_t data)
|
||||
{
|
||||
/* Check if the chip needs to 'auto-reset' - this occurs if during the programming sequence (ie before all 8 bytes read)
|
||||
a certain amount of time elapses without receiving another byte of programming...
|
||||
128us in the 4Mhz chip, 85us in the 6Mhz chip.
|
||||
*/
|
||||
static UINT64 last_totcyc = 0;
|
||||
UINT64 curr_totcyc = cpu_gettotalcycles64(chip->cpu_num);
|
||||
double diff = (double)llabs((INT64)(curr_totcyc-last_totcyc));
|
||||
last_totcyc = curr_totcyc;
|
||||
if(chip->bytes_read && diff > chip->reset_cycles) {
|
||||
LOG(("M114S: Auto Reset - bytes read=%0d - data=%0x, elapsed cycles = %f\n",chip->bytes_read,data&0x3f,diff));
|
||||
M114S_sh_reset();
|
||||
}
|
||||
|
||||
data &= 0x3f; //Strip off bits 7-8 (only 6 bits for the data bus to the chip)
|
||||
chip->bytes_read++;
|
||||
switch(chip->bytes_read)
|
||||
{
|
||||
/* BYTE #1 -
|
||||
Bits 0-5: Attenuation Value (0-63) - 0 = No Attenuation, 3E = Max, 3F = Silence active channel */
|
||||
case 1:
|
||||
chip->tempch_regs.atten = data;
|
||||
break;
|
||||
|
||||
/* BYTE #2 -
|
||||
Bits 0-1: Table 2 Address (Bits 6-7)
|
||||
Bits 2-3: Table 1 Address (Bits 6-7)
|
||||
Bits 4-5: Output Pin Selection (0-3) */
|
||||
case 2:
|
||||
chip->tempch_regs.table2_addr = (data & 0x03)<<6;
|
||||
chip->tempch_regs.table1_addr = (data & 0x0c)<<4;
|
||||
chip->tempch_regs.outputs = (data & 0x30)>>4;
|
||||
break;
|
||||
|
||||
/* BYTE #3 -
|
||||
Bits 0-5: Table 2 Address (Bits 0-5) */
|
||||
case 3:
|
||||
chip->tempch_regs.table2_addr |= data;
|
||||
break;
|
||||
|
||||
/* BYTE #4 -
|
||||
Bits 0-5: Table 1 Address (Bits 0-5) */
|
||||
case 4:
|
||||
chip->tempch_regs.table1_addr |= data;
|
||||
break;
|
||||
|
||||
/* BYTE #5 -
|
||||
Bits 0-2: Reading Method
|
||||
Bits 3-5: Table Length */
|
||||
case 5:
|
||||
chip->tempch_regs.read_meth = data & 0x07;
|
||||
chip->tempch_regs.table_len = (data & 0x38)>>3;
|
||||
break;
|
||||
|
||||
/* BYTE #6 -
|
||||
Bits 0 : Octave Divisor
|
||||
Bits 1 : Envelope Enable/Disable
|
||||
Bits 2-5: Interpolation Value (0-15) */
|
||||
case 6:
|
||||
chip->tempch_regs.oct_divisor = (data & 0x01);
|
||||
chip->tempch_regs.env_enable = (data & 0x02);
|
||||
chip->tempch_regs.interp = (data & 0x3c)>>2;
|
||||
break;
|
||||
|
||||
/* BYTE #7 -
|
||||
Bits 0-1: Frequency (Bits 0-1)
|
||||
Bits 2-5: Channel */
|
||||
case 7:
|
||||
chip->tempch_regs.frequency = (data & 0x03);
|
||||
chip->channel = (data & 0x3c)>>2;
|
||||
break;
|
||||
|
||||
/* BYTE #8 -
|
||||
Bits 0-5: Frequency (Bits 2-7) */
|
||||
case 8:
|
||||
chip->tempch_regs.frequency |= (data<<2);
|
||||
/* Process the channel data */
|
||||
process_channel_data(chip);
|
||||
break;
|
||||
|
||||
default:
|
||||
LOG(("M114S.C - logic error - too many bytes processed: %x\n",chip->bytes_read));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
106
src/engine/platform/sound/m114s.h
Normal file
106
src/engine/platform/sound/m114s.h
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
/**********************************************************************************************
|
||||
*
|
||||
* SGS-Thomson Microelectronics M114S/M114A/M114AF Digital Sound Generator
|
||||
* by Steve Ellenoff
|
||||
* 09/02/2004
|
||||
*
|
||||
* Thanks to R.Belmont for Support & Agreeing to read through that nasty data sheet with me..
|
||||
* Big thanks to Destruk for help in tracking down the data sheet.. Could have never done it
|
||||
* without it!!
|
||||
*
|
||||
*
|
||||
* Code based largely on Aaron Gile's BSMT2000 driver.
|
||||
**********************************************************************************************/
|
||||
|
||||
#ifndef M114S_H
|
||||
#define M114S_H
|
||||
#if !defined(__GNUC__) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4) || (__GNUC__ >= 4) // GCC supports "pragma once" correctly since 3.4
|
||||
#pragma once
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// by default output all 4 channels because Furnace supports that
|
||||
#define M114S_OUTPUT_CHANNELS 4 // HW uses 4, but if set to 16 instead, we simply output each (internal) channel independently (which is not how the real chip works, as that one mixes the 16 down to 4 outputs)
|
||||
|
||||
#define M114S_CHANNELS 16 // Chip has 16 internal channels for sound output
|
||||
|
||||
|
||||
/* struct describing a single table */
|
||||
struct M114STable
|
||||
{
|
||||
uint8_t reread; /* # of times to re-read each byte from table */
|
||||
//uint32_t position; /* current reading position for table */
|
||||
uint32_t start_address; /* start address (offset into ROM) for table */
|
||||
//uint32_t stop_address; /* stop address (offset into ROM) for table */
|
||||
uint16_t length; /* length in bytes of the table */
|
||||
uint16_t total_length; /* total length in bytes of the table (including repetitions) */
|
||||
};
|
||||
|
||||
/* struct describing the registers for a single channel */
|
||||
struct M114SChannelRegs
|
||||
{
|
||||
uint8_t atten; /* Attenuation Register */
|
||||
uint8_t outputs; /* Output Pin Register */
|
||||
uint8_t table1_addr; /* Table 1 MSB Starting Address Register */
|
||||
uint8_t table2_addr; /* Table 2 MSB Starting Address Register */
|
||||
uint8_t table_len; /* Table Length Register */
|
||||
uint8_t read_meth; /* Read Method Register */
|
||||
uint8_t interp; /* Interpolation Register */
|
||||
bool env_enable; /* Envelope Enable Register */
|
||||
bool oct_divisor; /* Octave Divisor Register */
|
||||
uint8_t frequency; /* Frequency Register */
|
||||
};
|
||||
|
||||
/* struct describing a single playing channel */
|
||||
struct M114SChannel
|
||||
{
|
||||
/* registers */
|
||||
struct M114SChannelRegs regs; /* register data for the channel */
|
||||
/* internal state */
|
||||
bool active; /* is the channel active */
|
||||
int16_t output[4096]; /* Holds output samples mixed from table 1 & 2 */
|
||||
uint32_t outpos; /* Index into output samples */
|
||||
int prev_volume; /* Holds the last volume code set for the channel */
|
||||
int current_volume; /* Volume to apply to each sample output */
|
||||
int target_volume; /* Target to match for volume envelope */
|
||||
int step_rate_volume_env; /* Volume step for volume envelope */
|
||||
//int end_of_table; /* End of Table Flag */
|
||||
struct M114STable table1; /* Table 1 Data */
|
||||
struct M114STable table2; /* Table 2 Data */
|
||||
uint32_t incr; /* Current Sample Rate/Step size increase to play back table */
|
||||
};
|
||||
|
||||
/* struct describing the entire M114S chip */
|
||||
struct M114SChip
|
||||
{
|
||||
/* vars to be reset when the chip is reset */
|
||||
int bytes_read; /* # of bytes read */
|
||||
int channel; /* Which channel is being programmed via the data bus */
|
||||
struct M114SChannelRegs tempch_regs; /* temporary channel register data for gathering the data programming */
|
||||
struct M114SChannel channels[M114S_CHANNELS]; /* All the chip's internal channels */
|
||||
int channel_volume[4]; /* scale for the HW/chip outputs */
|
||||
int8_t * region_base; /* pointer to the base of the ROM region */
|
||||
struct M114Sinterface* intf; /* Pointer to the interface */
|
||||
double reset_cycles; /* # of cycles that must pass between programming bytes to auto reset the chip */
|
||||
int cpu_num; /* # of the cpu controlling the M114S */
|
||||
bool is_M114A; /* M114A 4MHz or M114AF 6MHz */
|
||||
};
|
||||
|
||||
struct M114Sinterface
|
||||
{
|
||||
int num; /* total number of chips */
|
||||
int baseclock[MAX_M114S]; /* input clock - Allowed values are 4Mhz & 6Mhz only! */
|
||||
int region[MAX_M114S]; /* memory region where the sample ROM lives */
|
||||
int mixing_level[MAX_M114S][4]; /* master volume, one for each output sample */
|
||||
int cpunum[MAX_M114S]; /* # of the cpu controlling the M114S */
|
||||
};
|
||||
|
||||
int M114S_sh_start(const struct MachineSound *msound);
|
||||
void M114S_sh_stop(void);
|
||||
void M114S_sh_reset(void);
|
||||
|
||||
void m114s_data_write(struct M114SChip* chip, data8_t data);
|
||||
void m114s_update(struct M114SChip* chip, int16_t **buffer, int samples);
|
||||
|
||||
#endif
|
||||
|
|
@ -493,16 +493,6 @@ namespace xgm
|
|||
noise = 1;
|
||||
noise_tap = (1<<1);
|
||||
|
||||
if (option[OPT_RANDOMIZE_NOISE])
|
||||
{
|
||||
noise |= ::rand();
|
||||
counter[1] = -(rand() & 511);
|
||||
}
|
||||
if (option[OPT_RANDOMIZE_TRI])
|
||||
{
|
||||
tphase = ::rand() & 31;
|
||||
counter[0] = -(rand() & 2047);
|
||||
}
|
||||
|
||||
SetRate(rate);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -134,6 +134,18 @@ uint8_t* segapcm_device::get_ram() {
|
|||
return m_ram;
|
||||
}
|
||||
|
||||
unsigned int segapcm_device::get_addr(int ch) {
|
||||
uint8_t *regs = &m_ram[8*ch];
|
||||
int offset = (regs[0x86] & m_bankmask) << m_bankshift;
|
||||
uint32_t addr = (regs[0x85] << 8) | (regs[0x84]) | offset;
|
||||
return addr;
|
||||
}
|
||||
|
||||
bool segapcm_device::is_playing(int ch) {
|
||||
uint8_t *regs = &m_ram[8*ch];
|
||||
return !(regs[0x86]&1);
|
||||
}
|
||||
|
||||
void segapcm_device::mute(int ch, bool doMute) {
|
||||
m_muted[ch&15]=doMute;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,6 +34,8 @@ public:
|
|||
void write(unsigned int offset, uint8_t data);
|
||||
uint8_t read(unsigned int offset);
|
||||
uint8_t* get_ram();
|
||||
unsigned int get_addr(int ch);
|
||||
bool is_playing(int ch);
|
||||
void mute(int ch, bool doMute);
|
||||
|
||||
// device-level overrides
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@
|
|||
SM8521 sound emulator
|
||||
by cam900
|
||||
|
||||
MODIFIED BY TILDEARROW
|
||||
THIS IS NOT THE ORIGINAL VERSION
|
||||
|
||||
This file is licensed under zlib license.
|
||||
|
||||
============================================================================
|
||||
|
|
@ -69,7 +72,8 @@ void sm8521_noise_tick(struct sm8521_noise_t *noise, const int cycle)
|
|||
noise->base.counter += cycle;
|
||||
while (noise->base.counter >= (noise->base.t + 1))
|
||||
{
|
||||
noise->lfsr = rand() & 0x1; // unknown algorithm
|
||||
// unknown algorithm, but don't use rand()
|
||||
noise->lfsr = ( noise->lfsr>>1|(((noise->lfsr) ^ (noise->lfsr >> 5) ^ (noise->lfsr >> 8) ^ (noise->lfsr >> 13) ) & 1)<<31);
|
||||
noise->base.counter -= (noise->base.t + 1);
|
||||
}
|
||||
noise->base.out = (((noise->lfsr & 0x1) ? 7 : -8) * noise->base.level) >> 1; // scale out to 8bit
|
||||
|
|
@ -125,7 +129,7 @@ void sm8521_reset(struct sm8521_t *sm8521)
|
|||
sm8521->noise.base.level = 0;
|
||||
sm8521->noise.base.out = 0;
|
||||
sm8521->noise.base.counter = 0;
|
||||
sm8521->noise.lfsr = 0;
|
||||
sm8521->noise.lfsr = 0x89abcdef;
|
||||
sm8521->out = 0;
|
||||
sm8521->sgda = 0;
|
||||
sm8521->sgc = 0;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@
|
|||
SM8521 sound emulator
|
||||
by cam900
|
||||
|
||||
MODIFIED BY TILDEARROW
|
||||
THIS IS NOT THE ORIGINAL VERSION
|
||||
|
||||
This file is licensed under zlib license.
|
||||
|
||||
============================================================================
|
||||
|
|
|
|||
|
|
@ -123,6 +123,9 @@ public:
|
|||
uint8_t t_envx_out;
|
||||
sample_t out[2]; // Furnace addition, for per-channel oscilloscope
|
||||
};
|
||||
|
||||
// Furnace addition, gets a voice
|
||||
const voice_t* get_voice(int n);
|
||||
private:
|
||||
enum { brr_block_size = 9 };
|
||||
|
||||
|
|
@ -298,6 +301,10 @@ inline void SPC_DSP::get_voice_outputs( sample_t* outs )
|
|||
}
|
||||
}
|
||||
|
||||
inline const SPC_DSP::voice_t* SPC_DSP::get_voice(int n) {
|
||||
return &m.voices[n];
|
||||
}
|
||||
|
||||
#if !SPC_NO_COPY_STATE_FUNCS
|
||||
|
||||
class SPC_State_Copier {
|
||||
|
|
|
|||
|
|
@ -138,9 +138,7 @@ void DivPlatformSoundUnit::tick(bool sysTick) {
|
|||
//DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU);
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,chan[i].switchRoles,2,chan[i].pitch2,chipClock,chan[i].switchRoles?CHIP_DIVIDER:CHIP_FREQBASE);
|
||||
if (chan[i].pcm) {
|
||||
DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU);
|
||||
// TODO: sample map?
|
||||
DivSample* sample=parent->getSample(ins->amiga.getSample(chan[i].note));
|
||||
DivSample* sample=parent->getSample(chan[i].sample);
|
||||
if (sample!=NULL) {
|
||||
double off=0.25;
|
||||
if (sample->centerRate<1) {
|
||||
|
|
@ -209,6 +207,12 @@ int DivPlatformSoundUnit::dispatch(DivCommand c) {
|
|||
writeControlUpper(c.chan);
|
||||
}
|
||||
chan[c.chan].pcm=(ins->type==DIV_INS_AMIGA || ins->amiga.useSample);
|
||||
if (chan[c.chan].pcm) {
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].sample=ins->amiga.getSample(c.value);
|
||||
c.value=ins->amiga.getFreq(c.value);
|
||||
}
|
||||
}
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].baseFreq=NOTE_SU(c.chan,c.value);
|
||||
chan[c.chan].freqChanged=true;
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
class DivPlatformSoundUnit: public DivDispatch {
|
||||
struct Channel: public SharedChannel<signed char> {
|
||||
int cutoff, baseCutoff, res, control, hasOffset;
|
||||
int cutoff, baseCutoff, res, control, hasOffset, sample;
|
||||
signed char pan;
|
||||
unsigned char duty;
|
||||
bool noise, pcm, phaseReset, filterPhaseReset, switchRoles;
|
||||
|
|
@ -43,6 +43,7 @@ class DivPlatformSoundUnit: public DivDispatch {
|
|||
res(0),
|
||||
control(0),
|
||||
hasOffset(0),
|
||||
sample(-1),
|
||||
pan(0),
|
||||
duty(63),
|
||||
noise(false),
|
||||
|
|
|
|||
|
|
@ -261,7 +261,10 @@ int DivPlatformSwan::dispatch(DivCommand c) {
|
|||
dacPos=0;
|
||||
dacPeriod=0;
|
||||
if (ins->type==DIV_INS_AMIGA || ins->amiga.useSample) {
|
||||
if (c.value!=DIV_NOTE_NULL) dacSample=ins->amiga.getSample(c.value);
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
dacSample=ins->amiga.getSample(c.value);
|
||||
c.value=ins->amiga.getFreq(c.value);
|
||||
}
|
||||
if (dacSample<0 || dacSample>=parent->song.sampleLen) {
|
||||
dacSample=-1;
|
||||
if (dumpWrites) postWrite(0xffff0002,0);
|
||||
|
|
|
|||
|
|
@ -36,7 +36,6 @@ class DivPlatformT6W28: public DivDispatch {
|
|||
Channel chan[4];
|
||||
DivDispatchOscBuffer* oscBuf[4];
|
||||
bool isMuted[4];
|
||||
bool antiClickEnabled;
|
||||
bool easyNoise;
|
||||
struct QueuedWrite {
|
||||
unsigned char addr;
|
||||
|
|
|
|||
|
|
@ -121,10 +121,8 @@ void DivPlatformTIA::tick(bool sysTick) {
|
|||
}
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
int bf=chan[i].baseFreq;
|
||||
if (!parent->song.oldArpStrategy) {
|
||||
if (!chan[i].fixedArp) {
|
||||
bf+=chan[i].arpOff<<8;
|
||||
}
|
||||
if (!chan[i].fixedArp) {
|
||||
bf+=chan[i].arpOff<<8;
|
||||
}
|
||||
if (chan[i].fixedArp) {
|
||||
chan[i].freq=chan[i].baseNoteOverride&31;
|
||||
|
|
|
|||
|
|
@ -509,12 +509,8 @@ int DivPlatformTX81Z::dispatch(DivCommand c) {
|
|||
chan[c.chan].chVolL=(c.value>0);
|
||||
chan[c.chan].chVolR=(c.value2>0);
|
||||
chan[c.chan].freqChanged=true;
|
||||
/*
|
||||
if (isMuted[c.chan]) {
|
||||
rWrite(chanOffs[c.chan]+ADDR_LR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3));
|
||||
} else {
|
||||
rWrite(chanOffs[c.chan]+ADDR_LR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3)|((chan[c.chan].chVolL&1)<<6)|((chan[c.chan].chVolR&1)<<7));
|
||||
}*/
|
||||
|
||||
immWrite(chanOffs[c.chan]+ADDR_LR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3)|(chan[c.chan].active?0x40:0)|(chan[c.chan].chVolR<<7));
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_PITCH: {
|
||||
|
|
|
|||
|
|
@ -235,7 +235,11 @@ int DivPlatformVERA::dispatch(DivCommand c) {
|
|||
if (c.chan<16) {
|
||||
rWriteLo(c.chan,2,chan[c.chan].vol);
|
||||
} else {
|
||||
if (c.value!=DIV_NOTE_NULL) chan[16].pcm.sample=parent->getIns(chan[16].ins,DIV_INS_VERA)->amiga.getSample(c.value);
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
DivInstrument* ins=parent->getIns(chan[16].ins,DIV_INS_VERA);
|
||||
chan[16].pcm.sample=ins->amiga.getSample(c.value);
|
||||
c.value=ins->amiga.getFreq(c.value);
|
||||
}
|
||||
if (chan[16].pcm.sample<0 || chan[16].pcm.sample>=parent->song.sampleLen) {
|
||||
chan[16].pcm.sample=-1;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
#include "vic20.h"
|
||||
#include "../engine.h"
|
||||
#include "../../ta-log.h"
|
||||
#include <math.h>
|
||||
|
||||
#define rWrite(a,v) {regPool[(a)]=(v)&0xff; vic_sound_machine_store(vic,a,(v)&0xff);}
|
||||
|
|
@ -79,9 +80,7 @@ void DivPlatformVIC20::calcAndWriteOutVol(int ch, int env) {
|
|||
}
|
||||
|
||||
void DivPlatformVIC20::writeOutVol(int ch) {
|
||||
if (!isMuted[ch] && chan[ch].active) {
|
||||
rWrite(14,chan[ch].outVol);
|
||||
}
|
||||
rWrite(14,chan[ch].outVol);
|
||||
}
|
||||
|
||||
void DivPlatformVIC20::tick(bool sysTick) {
|
||||
|
|
@ -99,6 +98,20 @@ void DivPlatformVIC20::tick(bool sysTick) {
|
|||
}
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
if (chan[i].std.duty.had) {
|
||||
if (chan[i].onOff!=(bool)chan[i].std.duty.val) {
|
||||
chan[i].onOff=(bool)chan[i].std.duty.val;
|
||||
if (chan[i].active) {
|
||||
if (chan[i].onOff) {
|
||||
chan[i].keyOn=true;
|
||||
chan[i].keyOff=false;
|
||||
} else {
|
||||
chan[i].keyOn=false;
|
||||
chan[i].keyOff=true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (chan[i].std.wave.had) {
|
||||
if (chan[i].wave!=chan[i].std.wave.val) {
|
||||
chan[i].wave=chan[i].std.wave.val&0x0f;
|
||||
|
|
@ -156,6 +169,7 @@ int DivPlatformVIC20::dispatch(DivCommand c) {
|
|||
chan[c.chan].freqChanged=true;
|
||||
chan[c.chan].note=c.value;
|
||||
}
|
||||
chan[c.chan].onOff=true;
|
||||
chan[c.chan].active=true;
|
||||
chan[c.chan].keyOn=true;
|
||||
chan[c.chan].macroInit(ins);
|
||||
|
|
|
|||
|
|
@ -27,10 +27,12 @@
|
|||
class DivPlatformVIC20: public DivDispatch {
|
||||
struct Channel: public SharedChannel<int> {
|
||||
int wave, waveWriteCycle;
|
||||
bool onOff;
|
||||
Channel():
|
||||
SharedChannel<int>(15),
|
||||
wave(0),
|
||||
waveWriteCycle(-1) {}
|
||||
waveWriteCycle(-1),
|
||||
onOff(true) {}
|
||||
};
|
||||
Channel chan[4];
|
||||
DivDispatchOscBuffer* oscBuf[4];
|
||||
|
|
|
|||
|
|
@ -179,8 +179,6 @@ void DivPlatformVRC6::tick(bool sysTick) {
|
|||
if (chan[i].std.phaseReset.val && chan[i].active) {
|
||||
if ((i!=2) && (!chan[i].pcm)) {
|
||||
if (dumpWrites) addWrite(0xffff0002+(i<<8),0);
|
||||
DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_VRC6);
|
||||
chan[i].dacSample=ins->amiga.getSample(chan[i].note);
|
||||
if (chan[i].dacSample<0 || chan[i].dacSample>=parent->song.sampleLen) {
|
||||
if (dumpWrites) {
|
||||
chWrite(i,2,0x80);
|
||||
|
|
@ -242,7 +240,10 @@ int DivPlatformVRC6::dispatch(DivCommand c) {
|
|||
if (chan[c.chan].pcm) {
|
||||
if (skipRegisterWrites) break;
|
||||
if (ins->type==DIV_INS_AMIGA || ins->amiga.useSample) {
|
||||
if (c.value!=DIV_NOTE_NULL) chan[c.chan].dacSample=ins->amiga.getSample(c.value);
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].dacSample=ins->amiga.getSample(c.value);
|
||||
c.value=ins->amiga.getFreq(c.value);
|
||||
}
|
||||
if (chan[c.chan].dacSample<0 || chan[c.chan].dacSample>=parent->song.sampleLen) {
|
||||
chan[c.chan].dacSample=-1;
|
||||
if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0);
|
||||
|
|
@ -448,6 +449,16 @@ DivMacroInt* DivPlatformVRC6::getChanMacroInt(int ch) {
|
|||
return &chan[ch].std;
|
||||
}
|
||||
|
||||
DivSamplePos DivPlatformVRC6::getSamplePos(int ch) {
|
||||
if (ch>=2) return DivSamplePos();
|
||||
if (!chan[ch].pcm) return DivSamplePos();
|
||||
return DivSamplePos(
|
||||
chan[ch].dacSample,
|
||||
chan[ch].dacPos,
|
||||
chan[ch].dacRate
|
||||
);
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformVRC6::getOscBuffer(int ch) {
|
||||
return oscBuf[ch];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ class DivPlatformVRC6: public DivDispatch, public vrcvi_intf {
|
|||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
DivMacroInt* getChanMacroInt(int ch);
|
||||
DivSamplePos getSamplePos(int ch);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
|
|
|
|||
|
|
@ -222,7 +222,8 @@ void DivPlatformX1_010::acquire(short** buf, size_t len) {
|
|||
if (stereo) buf[1][h]=tempR;
|
||||
|
||||
for (int i=0; i<16; i++) {
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=(x1_010.voice_out(i,0)+x1_010.voice_out(i,1))>>1;
|
||||
int vo=(x1_010.voice_out(i,0)+x1_010.voice_out(i,1))<<3;
|
||||
oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP(vo,-32768,32767);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -540,7 +541,10 @@ int DivPlatformX1_010::dispatch(DivCommand c) {
|
|||
if (chan[c.chan].furnacePCM) {
|
||||
chan[c.chan].pcm=true;
|
||||
chan[c.chan].macroInit(ins);
|
||||
if (c.value!=DIV_NOTE_NULL) chan[c.chan].sample=ins->amiga.getSample(c.value);
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].sample=ins->amiga.getSample(c.value);
|
||||
c.value=ins->amiga.getFreq(c.value);
|
||||
}
|
||||
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
|
||||
DivSample* s=parent->getSample(chan[c.chan].sample);
|
||||
if (isBanked) {
|
||||
|
|
@ -924,14 +928,14 @@ void DivPlatformX1_010::notifyInsDeletion(void* ins) {
|
|||
|
||||
void DivPlatformX1_010::setFlags(const DivConfig& flags) {
|
||||
switch (flags.getInt("clockSel",0)) {
|
||||
case 0: // 16MHz (earlier hardwares)
|
||||
chipClock=16000000;
|
||||
break;
|
||||
case 1: // 16.67MHz (later hardwares)
|
||||
chipClock=50000000.0/3.0;
|
||||
break;
|
||||
case 2: // 14.32MHz (see https://github.com/mamedev/mame/blob/master/src/mame/taito/champbwl.cpp#L620)
|
||||
chipClock=COLOR_NTSC*4.0;
|
||||
break;
|
||||
// Other clock is used
|
||||
default:
|
||||
default: // 16MHz (earlier hardwares)
|
||||
chipClock=16000000;
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -169,6 +169,15 @@ void DivPlatformYM2203::acquire_combo(short** buf, size_t len) {
|
|||
static short ignored[2];
|
||||
|
||||
for (size_t h=0; h<len; h++) {
|
||||
// AY -> OPN
|
||||
ay->runDAC();
|
||||
ay->flushWrites();
|
||||
for (DivRegWrite& i: ay->getRegisterWrites()) {
|
||||
if (i.addr>15) continue;
|
||||
immWrite(i.addr&15,i.val);
|
||||
}
|
||||
ay->getRegisterWrites().clear();
|
||||
|
||||
os=0;
|
||||
// Nuked part
|
||||
for (unsigned int i=0; i<nukedMult; i++) {
|
||||
|
|
@ -242,6 +251,15 @@ void DivPlatformYM2203::acquire_ymfm(short** buf, size_t len) {
|
|||
}
|
||||
|
||||
for (size_t h=0; h<len; h++) {
|
||||
// AY -> OPN
|
||||
ay->runDAC();
|
||||
ay->flushWrites();
|
||||
for (DivRegWrite& i: ay->getRegisterWrites()) {
|
||||
if (i.addr>15) continue;
|
||||
immWrite(i.addr&15,i.val);
|
||||
}
|
||||
ay->getRegisterWrites().clear();
|
||||
|
||||
os=0;
|
||||
if (!writes.empty()) {
|
||||
if (--delay<1) {
|
||||
|
|
@ -567,7 +585,7 @@ int DivPlatformYM2203::dispatch(DivCommand c) {
|
|||
chan[c.chan].keyOff=true;
|
||||
chan[c.chan].keyOn=false;
|
||||
chan[c.chan].active=false;
|
||||
chan[c.chan].macroInit(NULL);
|
||||
if (parent->song.brokenFMOff) chan[c.chan].macroInit(NULL);
|
||||
break;
|
||||
case DIV_CMD_NOTE_OFF_ENV:
|
||||
chan[c.chan].keyOff=true;
|
||||
|
|
|
|||
|
|
@ -542,13 +542,8 @@ void DivPlatformYM2203Ext::muteChannel(int ch, bool mute) {
|
|||
DivInstrumentFM::Operator op=chan[2].state.op[ordch];
|
||||
if (isOpMuted[ch-2] || !op.enable) {
|
||||
rWrite(baseAddr+0x40,127);
|
||||
immWrite(baseAddr+0x40,127);
|
||||
} else if (KVS(2,ordch)) {
|
||||
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-2].outVol&0x7f,127));
|
||||
immWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-2].outVol&0x7f,127));
|
||||
} else {
|
||||
rWrite(baseAddr+0x40,op.tl);
|
||||
immWrite(baseAddr+0x40,op.tl);
|
||||
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-2].outVol&0x7f,127));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -321,6 +321,15 @@ void DivPlatformYM2608::acquire_combo(short** buf, size_t len) {
|
|||
}
|
||||
|
||||
for (size_t h=0; h<len; h++) {
|
||||
// AY -> OPN
|
||||
ay->runDAC();
|
||||
ay->flushWrites();
|
||||
for (DivRegWrite& i: ay->getRegisterWrites()) {
|
||||
if (i.addr>15) continue;
|
||||
immWrite(i.addr&15,i.val);
|
||||
}
|
||||
ay->getRegisterWrites().clear();
|
||||
|
||||
os[0]=0; os[1]=0;
|
||||
// Nuked part
|
||||
for (int i=0; i<nukedMult; i++) {
|
||||
|
|
@ -427,6 +436,15 @@ void DivPlatformYM2608::acquire_ymfm(short** buf, size_t len) {
|
|||
}
|
||||
|
||||
for (size_t h=0; h<len; h++) {
|
||||
// AY -> OPN
|
||||
ay->runDAC();
|
||||
ay->flushWrites();
|
||||
for (DivRegWrite& i: ay->getRegisterWrites()) {
|
||||
if (i.addr>15) continue;
|
||||
immWrite(i.addr&15,i.val);
|
||||
}
|
||||
ay->getRegisterWrites().clear();
|
||||
|
||||
os[0]=0; os[1]=0;
|
||||
if (!writes.empty()) {
|
||||
if (--delay<1) {
|
||||
|
|
@ -896,7 +914,10 @@ int DivPlatformYM2608::dispatch(DivCommand c) {
|
|||
chan[c.chan].outVol=chan[c.chan].vol;
|
||||
immWrite(0x10b,chan[c.chan].outVol);
|
||||
}
|
||||
if (c.value!=DIV_NOTE_NULL) chan[c.chan].sample=ins->amiga.getSample(c.value);
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].sample=ins->amiga.getSample(c.value);
|
||||
c.value=ins->amiga.getFreq(c.value);
|
||||
}
|
||||
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
|
||||
DivSample* s=parent->getSample(chan[c.chan].sample);
|
||||
immWrite(0x100,0x01); // reset
|
||||
|
|
@ -1004,7 +1025,7 @@ int DivPlatformYM2608::dispatch(DivCommand c) {
|
|||
chan[c.chan].keyOff=true;
|
||||
chan[c.chan].keyOn=false;
|
||||
chan[c.chan].active=false;
|
||||
chan[c.chan].macroInit(NULL);
|
||||
if (parent->song.brokenFMOff) chan[c.chan].macroInit(NULL);
|
||||
break;
|
||||
case DIV_CMD_NOTE_OFF_ENV:
|
||||
chan[c.chan].keyOff=true;
|
||||
|
|
@ -1672,7 +1693,7 @@ int DivPlatformYM2608::init(DivEngine* p, int channels, int sugRate, const DivCo
|
|||
fm=new ymfm::ym2608(iface);
|
||||
fm->set_fidelity(ymfm::OPN_FIDELITY_MIN);
|
||||
// YM2149, 2MHz
|
||||
ay=new DivPlatformAY8910(true,chipClock,ayDiv);
|
||||
ay=new DivPlatformAY8910(true,chipClock,ayDiv,48);
|
||||
ay->init(p,3,sugRate,ayFlags);
|
||||
ay->toggleRegisterDump(true);
|
||||
setFlags(flags);
|
||||
|
|
|
|||
|
|
@ -564,13 +564,8 @@ void DivPlatformYM2608Ext::muteChannel(int ch, bool mute) {
|
|||
DivInstrumentFM::Operator op=chan[2].state.op[ordch];
|
||||
if (isOpMuted[ch-2] || !op.enable) {
|
||||
rWrite(baseAddr+0x40,127);
|
||||
immWrite(baseAddr+0x40,127);
|
||||
} else if (KVS(2,ordch)) {
|
||||
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-2].outVol&0x7f,127));
|
||||
immWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-2].outVol&0x7f,127));
|
||||
} else {
|
||||
rWrite(baseAddr+0x40,op.tl);
|
||||
immWrite(baseAddr+0x40,op.tl);
|
||||
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-2].outVol&0x7f,127));
|
||||
}
|
||||
|
||||
rWrite(chanOffs[2]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch-2].pan<<6))|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4));
|
||||
|
|
|
|||
|
|
@ -256,6 +256,15 @@ void DivPlatformYM2610::acquire_combo(short** buf, size_t len) {
|
|||
}
|
||||
|
||||
for (size_t h=0; h<len; h++) {
|
||||
// AY -> OPN
|
||||
ay->runDAC();
|
||||
ay->flushWrites();
|
||||
for (DivRegWrite& i: ay->getRegisterWrites()) {
|
||||
if (i.addr>15) continue;
|
||||
immWrite(i.addr&15,i.val);
|
||||
}
|
||||
ay->getRegisterWrites().clear();
|
||||
|
||||
os[0]=0; os[1]=0;
|
||||
// Nuked part
|
||||
for (int i=0; i<24; i++) {
|
||||
|
|
@ -360,6 +369,15 @@ void DivPlatformYM2610::acquire_ymfm(short** buf, size_t len) {
|
|||
}
|
||||
|
||||
for (size_t h=0; h<len; h++) {
|
||||
// AY -> OPN
|
||||
ay->runDAC();
|
||||
ay->flushWrites();
|
||||
for (DivRegWrite& i: ay->getRegisterWrites()) {
|
||||
if (i.addr>15) continue;
|
||||
immWrite(i.addr&15,i.val);
|
||||
}
|
||||
ay->getRegisterWrites().clear();
|
||||
|
||||
os[0]=0; os[1]=0;
|
||||
if (!writes.empty()) {
|
||||
if (--delay<1 && !(fm->read(0)&0x80)) {
|
||||
|
|
@ -827,7 +845,10 @@ int DivPlatformYM2610::dispatch(DivCommand c) {
|
|||
chan[c.chan].outVol=chan[c.chan].vol;
|
||||
immWrite(0x1b,chan[c.chan].outVol);
|
||||
}
|
||||
if (c.value!=DIV_NOTE_NULL) chan[c.chan].sample=ins->amiga.getSample(c.value);
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].sample=ins->amiga.getSample(c.value);
|
||||
c.value=ins->amiga.getFreq(c.value);
|
||||
}
|
||||
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
|
||||
DivSample* s=parent->getSample(chan[c.chan].sample);
|
||||
immWrite(0x12,(sampleOffB[chan[c.chan].sample]>>8)&0xff);
|
||||
|
|
@ -976,7 +997,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) {
|
|||
chan[c.chan].keyOff=true;
|
||||
chan[c.chan].keyOn=false;
|
||||
chan[c.chan].active=false;
|
||||
chan[c.chan].macroInit(NULL);
|
||||
if (parent->song.brokenFMOff) chan[c.chan].macroInit(NULL);
|
||||
break;
|
||||
case DIV_CMD_NOTE_OFF_ENV:
|
||||
chan[c.chan].keyOff=true;
|
||||
|
|
|
|||
|
|
@ -320,7 +320,16 @@ void DivPlatformYM2610B::acquire_combo(short** buf, size_t len) {
|
|||
}
|
||||
|
||||
for (size_t h=0; h<len; h++) {
|
||||
// AY -> OPN
|
||||
ay->runDAC();
|
||||
ay->flushWrites();
|
||||
for (DivRegWrite& i: ay->getRegisterWrites()) {
|
||||
if (i.addr>15) continue;
|
||||
immWrite(i.addr&15,i.val);
|
||||
}
|
||||
ay->getRegisterWrites().clear();
|
||||
os[0]=0; os[1]=0;
|
||||
|
||||
// Nuked part
|
||||
for (int i=0; i<24; i++) {
|
||||
if (!writes.empty()) {
|
||||
|
|
@ -426,6 +435,15 @@ void DivPlatformYM2610B::acquire_ymfm(short** buf, size_t len) {
|
|||
}
|
||||
|
||||
for (size_t h=0; h<len; h++) {
|
||||
// AY -> OPN
|
||||
ay->runDAC();
|
||||
ay->flushWrites();
|
||||
for (DivRegWrite& i: ay->getRegisterWrites()) {
|
||||
if (i.addr>15) continue;
|
||||
immWrite(i.addr&15,i.val);
|
||||
}
|
||||
ay->getRegisterWrites().clear();
|
||||
|
||||
os[0]=0; os[1]=0;
|
||||
if (!writes.empty()) {
|
||||
if (--delay<1 && !(fm->read(0)&0x80)) {
|
||||
|
|
@ -894,7 +912,10 @@ int DivPlatformYM2610B::dispatch(DivCommand c) {
|
|||
chan[c.chan].outVol=chan[c.chan].vol;
|
||||
immWrite(0x1b,chan[c.chan].outVol);
|
||||
}
|
||||
if (c.value!=DIV_NOTE_NULL) chan[c.chan].sample=ins->amiga.getSample(c.value);
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].sample=ins->amiga.getSample(c.value);
|
||||
c.value=ins->amiga.getFreq(c.value);
|
||||
}
|
||||
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
|
||||
DivSample* s=parent->getSample(chan[c.chan].sample);
|
||||
immWrite(0x12,(sampleOffB[chan[c.chan].sample]>>8)&0xff);
|
||||
|
|
@ -1043,7 +1064,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) {
|
|||
chan[c.chan].keyOff=true;
|
||||
chan[c.chan].keyOn=false;
|
||||
chan[c.chan].active=false;
|
||||
chan[c.chan].macroInit(NULL);
|
||||
if (parent->song.brokenFMOff) chan[c.chan].macroInit(NULL);
|
||||
break;
|
||||
case DIV_CMD_NOTE_OFF_ENV:
|
||||
chan[c.chan].keyOff=true;
|
||||
|
|
|
|||
|
|
@ -560,13 +560,8 @@ void DivPlatformYM2610BExt::muteChannel(int ch, bool mute) {
|
|||
DivInstrumentFM::Operator op=chan[extChanOffs].state.op[ordch];
|
||||
if (isOpMuted[ch-extChanOffs] || !op.enable) {
|
||||
rWrite(baseAddr+0x40,127);
|
||||
immWrite(baseAddr+0x40,127);
|
||||
} else if (KVS(2,ordch)) {
|
||||
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-extChanOffs].outVol&0x7f,127));
|
||||
immWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-extChanOffs].outVol&0x7f,127));
|
||||
} else {
|
||||
rWrite(baseAddr+0x40,op.tl);
|
||||
immWrite(baseAddr+0x40,op.tl);
|
||||
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-extChanOffs].outVol&0x7f,127));
|
||||
}
|
||||
|
||||
rWrite(chanOffs[extChanOffs]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch-extChanOffs].pan<<6))|(chan[extChanOffs].state.fms&7)|((chan[extChanOffs].state.ams&3)<<4));
|
||||
|
|
|
|||
|
|
@ -560,13 +560,8 @@ void DivPlatformYM2610Ext::muteChannel(int ch, bool mute) {
|
|||
DivInstrumentFM::Operator op=chan[extChanOffs].state.op[ordch];
|
||||
if (isOpMuted[ch-extChanOffs] || !op.enable) {
|
||||
rWrite(baseAddr+0x40,127);
|
||||
immWrite(baseAddr+0x40,127);
|
||||
} else if (KVS(2,ordch)) {
|
||||
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-extChanOffs].outVol&0x7f,127));
|
||||
immWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-extChanOffs].outVol&0x7f,127));
|
||||
} else {
|
||||
rWrite(baseAddr+0x40,op.tl);
|
||||
immWrite(baseAddr+0x40,op.tl);
|
||||
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-extChanOffs].outVol&0x7f,127));
|
||||
}
|
||||
|
||||
rWrite(chanOffs[extChanOffs]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch-extChanOffs].pan<<6))|(chan[extChanOffs].state.fms&7)|((chan[extChanOffs].state.ams&3)<<4));
|
||||
|
|
|
|||
|
|
@ -250,7 +250,7 @@ class DivPlatformYM2610Base: public DivPlatformOPN {
|
|||
fm->set_fidelity(ymfm::OPN_FIDELITY_MED);
|
||||
setFlags(flags);
|
||||
// YM2149, 2MHz
|
||||
ay=new DivPlatformAY8910(true,chipClock,32);
|
||||
ay=new DivPlatformAY8910(true,chipClock,32,144);
|
||||
ay->init(p,3,sugRate,ayFlags);
|
||||
ay->toggleRegisterDump(true);
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -212,7 +212,10 @@ int DivPlatformYMZ280B::dispatch(DivCommand c) {
|
|||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA);
|
||||
chan[c.chan].isNewYMZ=ins->type==DIV_INS_YMZ280B;
|
||||
chan[c.chan].macroVolMul=ins->type==DIV_INS_AMIGA?64:255;
|
||||
if (c.value!=DIV_NOTE_NULL) chan[c.chan].sample=ins->amiga.getSample(c.value);
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].sample=ins->amiga.getSample(c.value);
|
||||
c.value=ins->amiga.getFreq(c.value);
|
||||
}
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
|
||||
}
|
||||
|
|
|
|||
407
src/engine/platform/zxbeeperquadtone.cpp
Normal file
407
src/engine/platform/zxbeeperquadtone.cpp
Normal file
|
|
@ -0,0 +1,407 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2023 tildearrow and contributors
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#include "zxbeeperquadtone.h"
|
||||
#include "../engine.h"
|
||||
#include <math.h>
|
||||
|
||||
#define rWrite(a,v) regPool[a]=v
|
||||
#define CHIP_FREQBASE 2048*320
|
||||
#define CHIP_DIVIDER 16*13
|
||||
|
||||
const char** DivPlatformZXBeeperQuadTone::getRegisterSheet() {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DivPlatformZXBeeperQuadTone::acquire(short** buf, size_t len) {
|
||||
bool o=false;
|
||||
for (size_t h=0; h<len; h++) {
|
||||
if (curSample>=0 && curSample<parent->song.sampleLen && !isMuted[4]) {
|
||||
while (curSamplePeriod>=chan[4].freq) {
|
||||
DivSample* s=parent->getSample(curSample);
|
||||
if (s->samples>0) {
|
||||
if (!isMuted[4]) o=(s->data8[curSamplePos++]>0);
|
||||
if (curSamplePos>=s->samples) curSample=-1;
|
||||
// (theoretical) 32KiB limit
|
||||
if (curSamplePos>=32768*8) curSample=-1;
|
||||
} else {
|
||||
curSample=-1;
|
||||
}
|
||||
curSamplePeriod-=chan[4].freq;
|
||||
}
|
||||
curSamplePeriod+=40;
|
||||
if ((outputClock&3)==0) {
|
||||
oscBuf[0]->data[oscBuf[0]->needle++]=0;
|
||||
oscBuf[1]->data[oscBuf[1]->needle++]=0;
|
||||
oscBuf[2]->data[oscBuf[2]->needle++]=0;
|
||||
oscBuf[3]->data[oscBuf[3]->needle++]=0;
|
||||
oscBuf[4]->data[oscBuf[4]->needle++]=o?32767:0;
|
||||
}
|
||||
} else {
|
||||
int ch=outputClock/2;
|
||||
int b=ch*4;
|
||||
if ((outputClock&1)==0) {
|
||||
chan[ch].sPosition+=(regPool[1+b]<<8)|regPool[0+b];
|
||||
chan[ch].out=regPool[3+b]+((((chan[ch].sPosition>>8)&0xff)<regPool[2+b])?1:0);
|
||||
if (isMuted[ch]) chan[ch].out=0;
|
||||
}
|
||||
if ((outputClock&3)==0) {
|
||||
oscBuf[4]->data[oscBuf[4]->needle++]=0;
|
||||
}
|
||||
o=chan[ch].out&0x10;
|
||||
oscBuf[ch]->data[oscBuf[ch]->needle++]=o?32767:0;
|
||||
chan[ch].out<<=1;
|
||||
|
||||
// if muted, ztill run sample
|
||||
if (curSample>=0 && curSample<parent->song.sampleLen && isMuted[4]) {
|
||||
while (curSamplePeriod>=chan[4].freq) {
|
||||
DivSample* s=parent->getSample(curSample);
|
||||
if (s->samples>0) {
|
||||
if (curSamplePos>=s->samples) curSample=-1;
|
||||
// (theoretical) 32KiB limit
|
||||
if (curSamplePos>=32768*8) curSample=-1;
|
||||
} else {
|
||||
curSample=-1;
|
||||
}
|
||||
curSamplePeriod-=chan[4].freq;
|
||||
}
|
||||
curSamplePeriod+=40;
|
||||
}
|
||||
}
|
||||
outputClock=(outputClock+1)&7;
|
||||
buf[0][h]=o?32767:0;
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformZXBeeperQuadTone::tick(bool sysTick) {
|
||||
for (int i=0; i<4; i++) {
|
||||
chan[i].std.next();
|
||||
if (chan[i].std.vol.had) {
|
||||
chan[i].outVol=(MIN(chan[i].vol,2)*MIN(chan[i].std.vol.val,2)/2);
|
||||
writeOutVol(i);
|
||||
}
|
||||
if (chan[i].std.duty.had) {
|
||||
chan[i].duty=chan[i].std.duty.val;
|
||||
rWrite(2+i*4,chan[i].duty^0xff);
|
||||
}
|
||||
if (NEW_ARP_STRAT) {
|
||||
chan[i].handleArp();
|
||||
} else if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
chan[i].baseFreq=NOTE_FREQUENCY(parent->calcArp(chan[i].note,chan[i].std.arp.val));
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
if (chan[i].std.pitch.had) {
|
||||
if (chan[i].std.pitch.mode) {
|
||||
chan[i].pitch2+=chan[i].std.pitch.val;
|
||||
CLAMP_VAR(chan[i].pitch2,-65535,65535);
|
||||
} else {
|
||||
chan[i].pitch2=chan[i].std.pitch.val;
|
||||
}
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
|
||||
if (chan[i].active) {
|
||||
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE);
|
||||
if (chan[i].freq<0) chan[i].freq=0;
|
||||
if (chan[i].freq>32768) chan[i].freq=32768;
|
||||
rWrite(0+i*4,chan[i].freq&0xff);
|
||||
rWrite(1+i*4,chan[i].freq>>8);
|
||||
}
|
||||
if (chan[i].keyOn) chan[i].keyOn=false;
|
||||
if (chan[i].keyOff) chan[i].keyOff=false;
|
||||
chan[i].freqChanged=false;
|
||||
}
|
||||
}
|
||||
if (NEW_ARP_STRAT) {
|
||||
chan[4].handleArp();
|
||||
} else if (chan[4].std.arp.had) {
|
||||
if (!chan[4].inPorta) {
|
||||
chan[4].baseFreq=NOTE_PERIODIC(parent->calcArp(chan[4].note,chan[4].std.arp.val));
|
||||
}
|
||||
chan[4].freqChanged=true;
|
||||
}
|
||||
if (chan[4].std.pitch.had) {
|
||||
if (chan[4].std.pitch.mode) {
|
||||
chan[4].pitch2+=chan[4].std.pitch.val;
|
||||
CLAMP_VAR(chan[4].pitch2,-65535,65535);
|
||||
} else {
|
||||
chan[4].pitch2=chan[4].std.pitch.val;
|
||||
}
|
||||
chan[4].freqChanged=true;
|
||||
}
|
||||
if (chan[4].freqChanged || chan[4].keyOn || chan[4].keyOff) {
|
||||
if (chan[4].active) {
|
||||
double off=CHIP_DIVIDER;
|
||||
if (curSample>=0 && curSample<parent->song.sampleLen) {
|
||||
DivSample* s=parent->getSample(curSample);
|
||||
off=(s->centerRate>=1)?(CHIP_DIVIDER*(double)s->centerRate/8363.0):CHIP_DIVIDER;
|
||||
}
|
||||
chan[4].freq=parent->calcFreq(chan[4].baseFreq,chan[4].pitch,chan[4].fixedArp?chan[4].baseNoteOverride:chan[4].arpOff,chan[4].fixedArp,true,2,chan[4].pitch2,chipClock,off);
|
||||
if (chan[4].freq>258) chan[4].freq=258;
|
||||
if (chan[4].freq<3) chan[4].freq=3;
|
||||
chan[4].freq*=13;
|
||||
}
|
||||
if (chan[4].keyOn) chan[4].keyOn=false;
|
||||
if (chan[4].keyOff) chan[4].keyOff=false;
|
||||
chan[4].freqChanged=false;
|
||||
}
|
||||
}
|
||||
|
||||
int DivPlatformZXBeeperQuadTone::dispatch(DivCommand c) {
|
||||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON: {
|
||||
if (c.chan<4) {
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_POKEMINI);
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
|
||||
chan[c.chan].freqChanged=true;
|
||||
chan[c.chan].note=c.value;
|
||||
}
|
||||
chan[c.chan].active=true;
|
||||
chan[c.chan].keyOn=true;
|
||||
chan[c.chan].macroInit(ins);
|
||||
chan[c.chan].insChanged=false;
|
||||
if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) {
|
||||
chan[c.chan].outVol=chan[c.chan].vol;
|
||||
writeOutVol(c.chan);
|
||||
}
|
||||
} else {
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA);
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
curSample=ins->amiga.getSample(c.value);
|
||||
c.value=ins->amiga.getFreq(c.value);
|
||||
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value);
|
||||
chan[c.chan].freqChanged=true;
|
||||
chan[c.chan].note=c.value;
|
||||
// TODO support offset commands
|
||||
curSamplePos=0;
|
||||
curSamplePeriod=0;
|
||||
}
|
||||
chan[c.chan].active=true;
|
||||
chan[c.chan].keyOn=true;
|
||||
chan[c.chan].macroInit(ins);
|
||||
chan[c.chan].insChanged=false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_NOTE_OFF:
|
||||
chan[c.chan].active=false;
|
||||
chan[c.chan].keyOff=true;
|
||||
chan[c.chan].macroInit(NULL);
|
||||
writeOutVol(c.chan);
|
||||
if (c.chan>=4) {
|
||||
curSample=-1;
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_NOTE_OFF_ENV:
|
||||
case DIV_CMD_ENV_RELEASE:
|
||||
chan[c.chan].std.release();
|
||||
break;
|
||||
case DIV_CMD_INSTRUMENT:
|
||||
if (chan[c.chan].ins!=c.value || c.value2==1) {
|
||||
chan[c.chan].ins=c.value;
|
||||
chan[c.chan].insChanged=true;
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_VOLUME:
|
||||
if (chan[c.chan].vol!=c.value) {
|
||||
chan[c.chan].vol=c.value;
|
||||
if (!chan[c.chan].std.vol.has) {
|
||||
chan[c.chan].outVol=c.value;
|
||||
writeOutVol(c.chan);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case DIV_CMD_GET_VOLUME:
|
||||
if (chan[c.chan].std.vol.has) {
|
||||
return chan[c.chan].vol;
|
||||
}
|
||||
return chan[c.chan].outVol;
|
||||
break;
|
||||
case DIV_CMD_PITCH:
|
||||
chan[c.chan].pitch=c.value;
|
||||
chan[c.chan].freqChanged=true;
|
||||
break;
|
||||
case DIV_CMD_NOTE_PORTA: {
|
||||
int destFreq=NOTE_FREQUENCY(c.value2);
|
||||
bool return2=false;
|
||||
if (destFreq>chan[c.chan].baseFreq) {
|
||||
chan[c.chan].baseFreq+=c.value;
|
||||
if (chan[c.chan].baseFreq>=destFreq) {
|
||||
chan[c.chan].baseFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
} else {
|
||||
chan[c.chan].baseFreq-=c.value;
|
||||
if (chan[c.chan].baseFreq<=destFreq) {
|
||||
chan[c.chan].baseFreq=destFreq;
|
||||
return2=true;
|
||||
}
|
||||
}
|
||||
chan[c.chan].freqChanged=true;
|
||||
if (return2) {
|
||||
chan[c.chan].inPorta=false;
|
||||
return 2;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case DIV_CMD_STD_NOISE_MODE:
|
||||
chan[c.chan].duty=c.value;
|
||||
if (c.chan<4) rWrite(2+c.chan*4,chan[c.chan].duty^0xff);
|
||||
break;
|
||||
case DIV_CMD_LEGATO:
|
||||
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value+((HACKY_LEGATO_MESS)?(chan[c.chan].std.arp.val):(0)));
|
||||
chan[c.chan].freqChanged=true;
|
||||
chan[c.chan].note=c.value;
|
||||
break;
|
||||
case DIV_CMD_PRE_PORTA:
|
||||
if (chan[c.chan].active && c.value2) {
|
||||
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_POKEMINI));
|
||||
}
|
||||
if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will && !NEW_ARP_STRAT) chan[c.chan].baseFreq=NOTE_FREQUENCY(chan[c.chan].note);
|
||||
chan[c.chan].inPorta=c.value;
|
||||
break;
|
||||
case DIV_CMD_GET_VOLMAX:
|
||||
return 2;
|
||||
break;
|
||||
case DIV_CMD_MACRO_OFF:
|
||||
chan[c.chan].std.mask(c.value,true);
|
||||
break;
|
||||
case DIV_CMD_MACRO_ON:
|
||||
chan[c.chan].std.mask(c.value,false);
|
||||
break;
|
||||
case DIV_ALWAYS_SET_VOLUME:
|
||||
return 1;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void DivPlatformZXBeeperQuadTone::writeOutVol(int ch) {
|
||||
if (ch>=4) return;
|
||||
unsigned char val=(chan[ch].outVol>=1)?((chan[ch].outVol>=2)?31:7):0;
|
||||
rWrite(3+ch*4,(chan[ch].active)?val:0);
|
||||
}
|
||||
|
||||
void DivPlatformZXBeeperQuadTone::muteChannel(int ch, bool mute) {
|
||||
isMuted[ch]=mute;
|
||||
writeOutVol(ch);
|
||||
}
|
||||
|
||||
void DivPlatformZXBeeperQuadTone::forceIns() {
|
||||
for (int i=0; i<5; i++) {
|
||||
chan[i].insChanged=true;
|
||||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
|
||||
void* DivPlatformZXBeeperQuadTone::getChanState(int ch) {
|
||||
return &chan[ch];
|
||||
}
|
||||
|
||||
DivMacroInt* DivPlatformZXBeeperQuadTone::getChanMacroInt(int ch) {
|
||||
return &chan[ch].std;
|
||||
}
|
||||
|
||||
DivDispatchOscBuffer* DivPlatformZXBeeperQuadTone::getOscBuffer(int ch) {
|
||||
return oscBuf[ch];
|
||||
}
|
||||
|
||||
unsigned char* DivPlatformZXBeeperQuadTone::getRegisterPool() {
|
||||
return regPool;
|
||||
}
|
||||
|
||||
int DivPlatformZXBeeperQuadTone::getRegisterPoolSize() {
|
||||
return 16;
|
||||
}
|
||||
|
||||
void DivPlatformZXBeeperQuadTone::reset() {
|
||||
memset(regPool,0,16);
|
||||
for (int i=0; i<5; i++) {
|
||||
chan[i]=DivPlatformZXBeeperQuadTone::Channel();
|
||||
chan[i].std.setEngine(parent);
|
||||
if (i<4) rWrite(2+i*4,128);
|
||||
}
|
||||
cycles=0;
|
||||
curChan=0;
|
||||
sOffTimer=0;
|
||||
ulaOut=0;
|
||||
curSample=-1;
|
||||
curSamplePos=0;
|
||||
curSamplePeriod=0;
|
||||
outputClock=0;
|
||||
}
|
||||
|
||||
bool DivPlatformZXBeeperQuadTone::keyOffAffectsArp(int ch) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void DivPlatformZXBeeperQuadTone::notifyWaveChange(int wave) {
|
||||
}
|
||||
|
||||
void DivPlatformZXBeeperQuadTone::notifyInsDeletion(void* ins) {
|
||||
for (int i=0; i<5; i++) {
|
||||
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformZXBeeperQuadTone::setFlags(const DivConfig& flags) {
|
||||
if (flags.getInt("clockSel",0)) {
|
||||
chipClock=COLOR_PAL*4.0/5.0;
|
||||
} else {
|
||||
chipClock=COLOR_NTSC;
|
||||
}
|
||||
CHECK_CUSTOM_CLOCK;
|
||||
rate=chipClock/40;
|
||||
for (int i=0; i<4; i++) {
|
||||
oscBuf[i]->rate=rate/4;
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformZXBeeperQuadTone::poke(unsigned int addr, unsigned short val) {
|
||||
rWrite(addr,val);
|
||||
}
|
||||
|
||||
void DivPlatformZXBeeperQuadTone::poke(std::vector<DivRegWrite>& wlist) {
|
||||
for (DivRegWrite& i: wlist) rWrite(i.addr,i.val);
|
||||
}
|
||||
|
||||
int DivPlatformZXBeeperQuadTone::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) {
|
||||
parent=p;
|
||||
for (int i=0; i<5; i++) {
|
||||
isMuted[i]=false;
|
||||
oscBuf[i]=new DivDispatchOscBuffer;
|
||||
}
|
||||
setFlags(flags);
|
||||
reset();
|
||||
return 5;
|
||||
}
|
||||
|
||||
void DivPlatformZXBeeperQuadTone::quit() {
|
||||
for (int i=0; i<5; i++) {
|
||||
delete oscBuf[i];
|
||||
}
|
||||
}
|
||||
|
||||
DivPlatformZXBeeperQuadTone::~DivPlatformZXBeeperQuadTone() {
|
||||
}
|
||||
73
src/engine/platform/zxbeeperquadtone.h
Normal file
73
src/engine/platform/zxbeeperquadtone.h
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
/**
|
||||
* Furnace Tracker - multi-system chiptune tracker
|
||||
* Copyright (C) 2021-2023 tildearrow and contributors
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*/
|
||||
|
||||
#ifndef _ZXBEEPERQUADTONE_H
|
||||
#define _ZXBEEPERQUADTONE_H
|
||||
|
||||
#include "../dispatch.h"
|
||||
|
||||
class DivPlatformZXBeeperQuadTone: public DivDispatch {
|
||||
struct Channel: public SharedChannel<unsigned char> {
|
||||
unsigned short sPosition;
|
||||
unsigned char duty;
|
||||
unsigned char out;
|
||||
Channel():
|
||||
SharedChannel<unsigned char>(2),
|
||||
sPosition(0),
|
||||
duty(128),
|
||||
out(0) {}
|
||||
};
|
||||
Channel chan[5];
|
||||
DivDispatchOscBuffer* oscBuf[5];
|
||||
bool isMuted[5];
|
||||
unsigned char ulaOut;
|
||||
|
||||
int cycles, curChan, sOffTimer, delay, curSample, curSamplePeriod;
|
||||
unsigned int curSamplePos;
|
||||
unsigned int outputClock;
|
||||
unsigned char regPool[16];
|
||||
friend void putDispatchChip(void*,int);
|
||||
friend void putDispatchChan(void*,int,int);
|
||||
public:
|
||||
void acquire(short** buf, size_t len);
|
||||
int dispatch(DivCommand c);
|
||||
void* getChanState(int chan);
|
||||
DivMacroInt* getChanMacroInt(int ch);
|
||||
DivDispatchOscBuffer* getOscBuffer(int chan);
|
||||
unsigned char* getRegisterPool();
|
||||
int getRegisterPoolSize();
|
||||
void reset();
|
||||
void forceIns();
|
||||
void tick(bool sysTick=true);
|
||||
void muteChannel(int ch, bool mute);
|
||||
bool keyOffAffectsArp(int ch);
|
||||
void setFlags(const DivConfig& flags);
|
||||
void notifyWaveChange(int wave);
|
||||
void notifyInsDeletion(void* ins);
|
||||
void poke(unsigned int addr, unsigned short val);
|
||||
void poke(std::vector<DivRegWrite>& wlist);
|
||||
const char** getRegisterSheet();
|
||||
int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags);
|
||||
void quit();
|
||||
~DivPlatformZXBeeperQuadTone();
|
||||
private:
|
||||
void writeOutVol(int ch);
|
||||
};
|
||||
|
||||
#endif
|
||||
Loading…
Add table
Add a link
Reference in a new issue