SNES: Get wavesynth and envelope working
No samples, inverted volumes and E/P/N yet It's been 3 months...
This commit is contained in:
parent
73c6adb821
commit
7956d41f1b
6 changed files with 224 additions and 139 deletions
|
|
@ -22,9 +22,12 @@
|
|||
#include "../../ta-log.h"
|
||||
#include <math.h>
|
||||
|
||||
#define CHIP_FREQBASE 4096
|
||||
#define CHIP_FREQBASE 131072
|
||||
|
||||
#define rWrite(a,v) {dsp.write(a,v); regPool[(a)&0x7f]=v; }
|
||||
#define chWrite(c,a,v) {rWrite((a)+(c)*16,v)}
|
||||
#define sampleTableAddr(c) (sampleTableBase+(c)*4)
|
||||
#define waveTableAddr(c) (sampleTableBase+8*4+(c)*9*16)
|
||||
|
||||
const char* regCheatSheetSNESDSP[]={
|
||||
"VxVOLL", "x0",
|
||||
|
|
@ -62,16 +65,6 @@ const char** DivPlatformSNES::getRegisterSheet() {
|
|||
return regCheatSheetSNESDSP;
|
||||
}
|
||||
|
||||
const char* DivPlatformSNES::getEffectName(unsigned char effect) {
|
||||
switch (effect) {
|
||||
case 0x10:
|
||||
return "10xx: Set echo feedback level (signed 8-bit)";
|
||||
break;
|
||||
// TODO
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void DivPlatformSNES::acquire(short* bufL, short* bufR, size_t start, size_t len) {
|
||||
short out[2];
|
||||
short chOut[16];
|
||||
|
|
@ -90,12 +83,23 @@ void DivPlatformSNES::acquire(short* bufL, short* bufR, size_t start, size_t len
|
|||
void DivPlatformSNES::tick(bool sysTick) {
|
||||
// KON/KOFF can't be written several times per one sample
|
||||
// so they have to be accumulated
|
||||
// TODO due to pipelining, KON/KOFF writes need to be delayed to accomodate sample address changes in the table
|
||||
unsigned char kon=0;
|
||||
unsigned char koff=0;
|
||||
for (int i=0; i<8; i++) {
|
||||
DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_AMIGA);
|
||||
bool hadGain=chan[i].std.vol.had || chan[i].std.ex1.had || chan[i].std.ex2.had;
|
||||
chan[i].std.next();
|
||||
if (chan[i].std.vol.had) {
|
||||
// TODO handle gain writes
|
||||
if (ins->type==DIV_INS_AMIGA && chan[i].std.vol.had) {
|
||||
chWrite(i,7,MIN(127,chan[i].std.vol.val*2));
|
||||
} else if (!chan[i].useEnv && hadGain) {
|
||||
if (chan[i].std.ex1.val==0) {
|
||||
// direct gain
|
||||
chWrite(i,7,chan[i].std.vol.val);
|
||||
} else {
|
||||
// inc/dec
|
||||
chWrite(i,7,chan[i].std.ex2.val|((chan[i].std.ex1.val-1)<<5)|0x80);
|
||||
}
|
||||
}
|
||||
if (chan[i].std.arp.had) {
|
||||
if (!chan[i].inPorta) {
|
||||
|
|
@ -112,6 +116,12 @@ void DivPlatformSNES::tick(bool sysTick) {
|
|||
chan[i].freqChanged=true;
|
||||
}
|
||||
}
|
||||
if (chan[i].useWave && chan[i].std.wave.had) {
|
||||
if (chan[i].wave!=chan[i].std.wave.val || chan[i].ws.activeChanged()) {
|
||||
chan[i].wave=chan[i].std.wave.val;
|
||||
chan[i].ws.changeWave1(chan[i].wave);
|
||||
}
|
||||
}
|
||||
if (chan[i].std.pitch.had) {
|
||||
if (chan[i].std.pitch.mode) {
|
||||
chan[i].pitch2+=chan[i].std.pitch.val;
|
||||
|
|
@ -139,13 +149,46 @@ void DivPlatformSNES::tick(bool sysTick) {
|
|||
} else {
|
||||
chan[i].audPos=0;
|
||||
}
|
||||
if (chan[i].useWave && chan[i].active) {
|
||||
if (chan[i].ws.tick()) {
|
||||
updateWave(i);
|
||||
}
|
||||
}
|
||||
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;
|
||||
chan[i].freq=(unsigned int)(off*parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,chipClock,CHIP_FREQBASE));
|
||||
if (chan[i].freq>16383) chan[i].freq=16383;
|
||||
if (chan[i].keyOn) {
|
||||
// TODO handle sample offsets
|
||||
unsigned int start, end, loop;
|
||||
size_t tabAddr=sampleTableAddr(i);
|
||||
if (chan[i].useEnv) {
|
||||
chWrite(i,5,ins->snes.a|(ins->snes.d<<4)|0x80);
|
||||
chWrite(i,6,ins->snes.r|(ins->snes.s<<5));
|
||||
} else {
|
||||
chWrite(i,5,0);
|
||||
}
|
||||
if (chan[i].useWave) {
|
||||
start=waveTableAddr(i);
|
||||
loop=start;
|
||||
} else {
|
||||
start=s->offSNES;
|
||||
end=MIN(start+MAX(s->lengthBRR,1),getSampleMemCapacity());
|
||||
loop=MAX(start,end-1);
|
||||
if (chan[i].audPos>0) {
|
||||
start=start+MIN(chan[i].audPos,s->lengthBRR-1)/16*9;
|
||||
}
|
||||
if (s->loopStart>=0) {
|
||||
loop=start+s->loopStart/16*9;
|
||||
}
|
||||
}
|
||||
sampleMem[tabAddr+0]=start&0xff;
|
||||
sampleMem[tabAddr+1]=start>>8;
|
||||
sampleMem[tabAddr+2]=loop&0xff;
|
||||
sampleMem[tabAddr+3]=loop>>8;
|
||||
if (!hadGain) {
|
||||
chWrite(i,7,0x7f);
|
||||
}
|
||||
kon|=(1<<i);
|
||||
chan[i].keyOn=false;
|
||||
}
|
||||
|
|
@ -154,8 +197,8 @@ void DivPlatformSNES::tick(bool sysTick) {
|
|||
chan[i].keyOff=false;
|
||||
}
|
||||
if (chan[i].freqChanged) {
|
||||
rWrite(2+i*16,chan[i].freq&0xff);
|
||||
rWrite(3+i*16,chan[i].freq>>8);
|
||||
chWrite(i,2,chan[i].freq&0xff);
|
||||
chWrite(i,3,chan[i].freq>>8);
|
||||
chan[i].freqChanged=false;
|
||||
}
|
||||
}
|
||||
|
|
@ -168,20 +211,36 @@ int DivPlatformSNES::dispatch(DivCommand c) {
|
|||
switch (c.cmd) {
|
||||
case DIV_CMD_NOTE_ON: {
|
||||
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA);
|
||||
chan[c.chan].sample=ins->amiga.getSample(c.value);
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].baseFreq=round(NOTE_FREQUENCY(c.value));
|
||||
if (ins->amiga.useWave) {
|
||||
chan[c.chan].useWave=true;
|
||||
chan[c.chan].wtLen=(unsigned int)(ins->amiga.waveLen)+1;
|
||||
if (chan[c.chan].insChanged) {
|
||||
if (chan[c.chan].wave<0) {
|
||||
chan[c.chan].wave=0;
|
||||
chan[c.chan].ws.setWidth(chan[c.chan].wtLen);
|
||||
chan[c.chan].ws.changeWave1(chan[c.chan].wave);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
chan[c.chan].sample=ins->amiga.getSample(c.value);
|
||||
chan[c.chan].useWave=false;
|
||||
}
|
||||
if (chan[c.chan].useWave || chan[c.chan].sample<0 || chan[c.chan].sample>=parent->song.sampleLen) {
|
||||
chan[c.chan].sample=-1;
|
||||
}
|
||||
if (c.value!=DIV_NOTE_NULL) {
|
||||
chan[c.chan].baseFreq=round(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);
|
||||
if (ins->type==DIV_INS_SNES) {
|
||||
// initialize to max gain in case of direct gain mode macro without gain level macro
|
||||
chan[c.chan].std.vol.val=0x7f;
|
||||
chan[c.chan].useEnv=ins->snes.useEnv;
|
||||
}
|
||||
chan[c.chan].insChanged=false;
|
||||
break;
|
||||
}
|
||||
|
|
@ -274,6 +333,20 @@ int DivPlatformSNES::dispatch(DivCommand c) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
void DivPlatformSNES::updateWave(int ch) {
|
||||
// Due to the overflow bug in hardware's resampler, the written amplitude here is half of maximum
|
||||
size_t pos=waveTableAddr(ch);
|
||||
for (int i=0; i<chan[ch].wtLen/16; i++) {
|
||||
sampleMem[pos++]=0xb0;
|
||||
for (int j=0; j<8; j++) {
|
||||
int nibble1=(chan[ch].ws.output[i*16+j*2]-8)&15;
|
||||
int nibble2=(chan[ch].ws.output[i*16+j*2+1]-8)&15;
|
||||
sampleMem[pos++]=(nibble1<<4)|nibble2;
|
||||
}
|
||||
}
|
||||
sampleMem[pos-9]=0xb3; // mark loop
|
||||
}
|
||||
|
||||
void DivPlatformSNES::writeOutVol(int ch) {
|
||||
// TODO negative (inverted) panning support
|
||||
int outL=0;
|
||||
|
|
@ -282,8 +355,8 @@ void DivPlatformSNES::writeOutVol(int ch) {
|
|||
outL=chan[ch].vol*chan[ch].panL/255;
|
||||
outR=chan[ch].vol*chan[ch].panR/255;
|
||||
}
|
||||
rWrite(0+ch*16,outL);
|
||||
rWrite(1+ch*16,outR);
|
||||
chWrite(ch,0,outL);
|
||||
chWrite(ch,1,outR);
|
||||
}
|
||||
|
||||
void DivPlatformSNES::muteChannel(int ch, bool mute) {
|
||||
|
|
@ -296,6 +369,7 @@ void DivPlatformSNES::forceIns() {
|
|||
chan[i].insChanged=true;
|
||||
chan[i].freqChanged=true;
|
||||
chan[i].sample=-1;
|
||||
updateWave(i);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -334,10 +408,14 @@ void DivPlatformSNES::reset() {
|
|||
rWrite(0x5d,sampleTableBase>>8);
|
||||
rWrite(0x0c,127); // global volume left
|
||||
rWrite(0x1c,127); // global volume right
|
||||
rWrite(0x6c,0); // get DSP out of reset
|
||||
for (int i=0; i<8; i++) {
|
||||
chan[i]=Channel();
|
||||
chan[i].std.setEngine(parent);
|
||||
chan[i].ws.setEngine(parent);
|
||||
chan[i].ws.init(NULL,32,255);
|
||||
writeOutVol(i);
|
||||
chWrite(i,4,i); // source number
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -353,6 +431,17 @@ void DivPlatformSNES::notifyInsChange(int ins) {
|
|||
}
|
||||
}
|
||||
|
||||
void DivPlatformSNES::notifyWaveChange(int wave) {
|
||||
for (int i=0; i<8; i++) {
|
||||
if (chan[i].useWave && chan[i].wave==wave) {
|
||||
chan[i].ws.changeWave1(wave);
|
||||
if (chan[i].active) {
|
||||
updateWave(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DivPlatformSNES::notifyInsDeletion(void* ins) {
|
||||
for (int i=0; i<8; i++) {
|
||||
chan[i].std.notifyInsDeletion((DivInstrument*)ins);
|
||||
|
|
@ -383,21 +472,14 @@ size_t DivPlatformSNES::getSampleMemUsage(int index) {
|
|||
void DivPlatformSNES::renderSamples() {
|
||||
memset(sampleMem,0,getSampleMemCapacity());
|
||||
|
||||
// skip past sample table
|
||||
size_t memPos=sampleTableBase+0x400;
|
||||
// skip past sample table and wavetable buffer
|
||||
size_t memPos=sampleTableBase+8*4+8*9*16;
|
||||
for (int i=0; i<parent->song.sampleLen; i++) {
|
||||
DivSample* s=parent->song.sample[i];
|
||||
int length=s->lengthBRR;
|
||||
int actualLength=MIN((int)(getSampleMemCapacity()-memPos)/9*9,length);
|
||||
if (actualLength>0) {
|
||||
size_t tabAddr=sampleTableBase+i*4;
|
||||
size_t loopPos=memPos;
|
||||
if (s->loopStart>=0) loopPos+=s->loopStart;
|
||||
s->offSNES=memPos;
|
||||
sampleMem[tabAddr+0]=memPos&0xff;
|
||||
sampleMem[tabAddr+1]=memPos>>8;
|
||||
sampleMem[tabAddr+2]=loopPos&0xff;
|
||||
sampleMem[tabAddr+3]=loopPos>>8;
|
||||
memcpy(&sampleMem[memPos],s->data8,actualLength);
|
||||
memPos+=actualLength;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,19 +22,22 @@
|
|||
|
||||
#include "../dispatch.h"
|
||||
#include "../macroInt.h"
|
||||
#include "../waveSynth.h"
|
||||
#include <queue>
|
||||
#include "sound/snes/SPC_DSP.h"
|
||||
|
||||
class DivPlatformSNES: public DivDispatch {
|
||||
struct Channel {
|
||||
int freq, baseFreq, pitch, pitch2;
|
||||
unsigned int audPos;
|
||||
int sample, ins;
|
||||
unsigned int audPos, wtLen;
|
||||
int sample, wave, ins;
|
||||
int note;
|
||||
int panL, panR;
|
||||
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, useWave, setPos;
|
||||
signed char vol;
|
||||
bool useEnv;
|
||||
DivMacroInt std;
|
||||
DivWaveSynth ws;
|
||||
void macroInit(DivInstrument* which) {
|
||||
std.init(which);
|
||||
pitch2=0;
|
||||
|
|
@ -45,7 +48,9 @@ class DivPlatformSNES: public DivDispatch {
|
|||
pitch(0),
|
||||
pitch2(0),
|
||||
audPos(0),
|
||||
wtLen(16),
|
||||
sample(-1),
|
||||
wave(-1),
|
||||
ins(-1),
|
||||
note(0),
|
||||
panL(255),
|
||||
|
|
@ -58,7 +63,8 @@ class DivPlatformSNES: public DivDispatch {
|
|||
inPorta(false),
|
||||
useWave(false),
|
||||
setPos(false),
|
||||
vol(127) {}
|
||||
vol(127),
|
||||
useEnv(false) {}
|
||||
};
|
||||
Channel chan[8];
|
||||
DivDispatchOscBuffer* oscBuf[8];
|
||||
|
|
@ -86,11 +92,11 @@ class DivPlatformSNES: public DivDispatch {
|
|||
void muteChannel(int ch, bool mute);
|
||||
bool isStereo();
|
||||
void notifyInsChange(int ins);
|
||||
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();
|
||||
const char* getEffectName(unsigned char effect);
|
||||
const void* getSampleMem(int index = 0);
|
||||
size_t getSampleMemCapacity(int index = 0);
|
||||
size_t getSampleMemUsage(int index = 0);
|
||||
|
|
@ -98,6 +104,7 @@ class DivPlatformSNES: public DivDispatch {
|
|||
int init(DivEngine* parent, int channels, int sugRate, unsigned int flags);
|
||||
void quit();
|
||||
private:
|
||||
void updateWave(int ch);
|
||||
void writeOutVol(int ch);
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue