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

This commit is contained in:
cam900 2022-09-25 18:34:36 +09:00
commit 5a6cde65ed
21 changed files with 433 additions and 134 deletions

View File

@ -22,39 +22,122 @@
#include "brrUtils.h"
long brrEncode(short* buf, unsigned char* out, long len) {
long brrEncode(short* buf, unsigned char* out, long len, long loopStart) {
if (len==0) return 0;
// TODO
return 0;
// encoding process:
// 1. read next group of 16 samples
// 2. is this the first block?
// - if yes, don't filter. output and then go to 1
// 3. is this the loop block?
// - if yes, don't filter. output and then go to 1
// 4. try encoding using 4 filters
// 5. which one of these yields the least amount of error?
// 6. output the one which does
// 7. is this the last block?
// - if yes, mark end and finish
// 8. go to 1
long total=0;
unsigned char next[9];
unsigned char filter=0;
unsigned char range=0;
unsigned char o=0;
short o0=0;
len&=~15;
loopStart&=~15;
for (long i=0; i<len; i+=16) {
// don't filter on the first or loop block
if (i && i!=loopStart) {
} else {
filter=0;
}
range=0;
for (int j=0; j<16; j++) {
short s=buf[j]-(buf[j]>>13);
if (s<0) s=-s;
while (range<12 && s>((8<<range)-1)) range++;
}
next[0]=(range<<4)|(filter<<2)|((i+16>=len)?((loopStart>=0)?3:1):0);
switch (filter) {
case 0:
for (int j=0; j<16; j++) {
short s=buf[j]-(buf[j]>>13);
o0=s>>range;
if (o0>7) o0=7;
if (o0<-8) o0=-8;
if (range>=12) if (o0<-7) o0=-7;
o=o0&15;
if (j&1) {
next[1+(j>>1)]|=o;
} else {
next[1+(j>>1)]=o<<4;
}
}
break;
case 1:
break;
case 2:
break;
case 3:
break;
}
out[0]=next[0];
out[1]=next[1];
out[2]=next[2];
out[3]=next[3];
out[4]=next[4];
out[5]=next[5];
out[6]=next[6];
out[7]=next[7];
out[8]=next[8];
buf+=16;
out+=9;
total+=9;
}
return total;
}
#define DO_ONE_SAMPLE \
if (next&8) next|=0xfffffff8; \
if (next&8) next|=0xfffffff0; \
\
next<<=(buf[0]>>4); /* range */ \
if (buf[0]>=0xd0) { /* invalid shift */ \
next=(next<0)?0xfffff800:0; \
} else { \
next<<=(buf[0]>>4); /* range */ \
next>>=1; \
} \
\
switch (control&0xc) { /* filter */ \
case 0: \
break; \
case 4: \
next+=(last1*15)/16; \
next+=last1+((-last1)>>4); \
break; \
case 8: \
next+=((last1*61)/32)-((last2*15)/16); \
next+=last1*2+((-last1*3)>>5)-last2+(last2>>4); \
break; \
case 12: \
next+=((last1*115)/64)-((last2*13)/16); \
next+=last1*2+((-last1*13)>>6)-last2+((last2*3)>>4); \
break; \
} \
\
if (next>32767) next=32767; \
if (next<-32768) next=-32768; \
next&=0x7fff; \
if (next&0x4000) next|=0xffff8000; \
\
last2=last1; \
last1=next; \
*out=next; \
*out=next<<1; \
out++;
// TODO:
// - what happens during overflow?
// - what happens when range values 12 to 15 are used?
long brrDecode(unsigned char* buf, short* out, long len) {
if (len==0) return 0;

View File

@ -32,9 +32,10 @@ extern "C" {
* @param buf input data.
* @param out output buffer. shall be at least 9*(len/16) shorts in size.
* @param len input length (should be a multiple of 16. if it isn't, the output will be padded).
* @param loopStart beginning of loop area (may be -1 for no loop). this is used to ensure the respective block has no filter in order to loop properly.
* @return number of written samples.
*/
long brrEncode(short* buf, unsigned char* out, long len);
long brrEncode(short* buf, unsigned char* out, long len, long loopStart);
/**
* read len bytes from buf, decode BRR and output to out.

View File

@ -206,6 +206,18 @@ enum DivDispatchCmds {
DIV_CMD_ADPCMA_GLOBAL_VOLUME,
DIV_CMD_SNES_ECHO,
DIV_CMD_SNES_PITCH_MOD,
DIV_CMD_SNES_INVERT,
DIV_CMD_SNES_GAIN_MODE,
DIV_CMD_SNES_GAIN,
DIV_CMD_SNES_ECHO_ENABLE,
DIV_CMD_SNES_ECHO_DELAY,
DIV_CMD_SNES_ECHO_VOL_LEFT,
DIV_CMD_SNES_ECHO_VOL_RIGHT,
DIV_CMD_SNES_ECHO_FEEDBACK,
DIV_CMD_SNES_ECHO_FIR,
DIV_ALWAYS_SET_VOLUME, // () -> alwaysSetVol
DIV_CMD_MAX

View File

@ -2651,7 +2651,7 @@ DivSample* DivEngine::sampleFromFile(const char* path) {
}
extS+=i;
}
if (extS==".dmc") { // read as .dmc
if (extS==".dmc" || extS==".brr") { // read as .dmc or .brr
size_t len=0;
DivSample* sample=new DivSample;
sample->name=stripPath;
@ -2698,12 +2698,48 @@ DivSample* DivEngine::sampleFromFile(const char* path) {
return NULL;
}
sample->rate=33144;
sample->centerRate=33144;
sample->depth=DIV_SAMPLE_DEPTH_1BIT_DPCM;
sample->init(len*8);
if (extS==".dmc") {
sample->rate=33144;
sample->centerRate=33144;
sample->depth=DIV_SAMPLE_DEPTH_1BIT_DPCM;
sample->init(len*8);
} else if (extS==".brr") {
sample->rate=32000;
sample->centerRate=32000;
sample->depth=DIV_SAMPLE_DEPTH_BRR;
sample->init(16*(len/9));
} else {
fclose(f);
BUSY_END;
lastError="wait... is that right? no I don't think so...";
delete sample;
return NULL;
}
if (fread(sample->dataDPCM,1,len,f)==0) {
unsigned char* dataBuf=sample->dataDPCM;
if (extS==".brr") {
dataBuf=sample->dataBRR;
if ((len%9)==2) {
// ignore loop position
fseek(f,2,SEEK_SET);
len-=2;
if (len==0) {
fclose(f);
BUSY_END;
lastError="BRR sample is empty!";
delete sample;
return NULL;
}
} else if ((len%9)!=0) {
fclose(f);
BUSY_END;
lastError="possibly corrupt BRR sample!";
delete sample;
return NULL;
}
}
if (fread(dataBuf,1,len,f)==0) {
fclose(f);
BUSY_END;
lastError=fmt::sprintf("could not read file! (%s)",strerror(errno));

View File

@ -115,7 +115,7 @@ const unsigned char dacLogTableAY[256]={
void DivPlatformAY8910::runDAC() {
for (int i=0; i<3; i++) {
if (chan[i].psgMode.dac && chan[i].dac.sample!=-1) {
if (chan[i].active && chan[i].psgMode.dac && chan[i].dac.sample!=-1) {
chan[i].dac.period+=chan[i].dac.rate;
bool end=false;
bool changed=false;
@ -129,7 +129,7 @@ void DivPlatformAY8910::runDAC() {
break;
}
unsigned char dacData=dacLogTableAY[(unsigned char)s->data8[chan[i].dac.pos]^0x80];
chan[i].dac.out=MAX(0,dacData-(15-chan[i].outVol));
chan[i].dac.out=(chan[i].active && !isMuted[i])?MAX(0,dacData-(15-chan[i].outVol)):0;
if (prevOut!=chan[i].dac.out) {
prevOut=chan[i].dac.out;
changed=true;
@ -264,7 +264,7 @@ void DivPlatformAY8910::tick(bool sysTick) {
}
if (chan[i].std.wave.had) {
if (!chan[i].psgMode.dac) {
chan[i].psgMode=(chan[i].std.wave.val+1)&7;
chan[i].psgMode.val=(chan[i].std.wave.val+1)&7;
if (isMuted[i]) {
rWrite(0x08+i,0);
} else if (intellivision && (chan[i].psgMode.getEnvelope())) {
@ -336,8 +336,12 @@ void DivPlatformAY8910::tick(bool sysTick) {
if (chan[i].keyOn) {
//rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(ins->gb.soundLen&63)));
//rWrite(16+i*5+2,((chan[i].vol<<4))|(ins->gb.envLen&7)|((ins->gb.envDir&1)<<3));
if (chan[i].psgMode.val==0) {
chan[i].psgMode.val=1;
}
}
if (chan[i].keyOff) {
chan[i].psgMode.val=0;
rWrite(0x08+i,0);
}
rWrite((i)<<1,chan[i].freq&0xff);
@ -543,7 +547,7 @@ int DivPlatformAY8910::dispatch(DivCommand c) {
case DIV_CMD_STD_NOISE_MODE:
if (!chan[c.chan].psgMode.dac) {
if (c.value<16) {
chan[c.chan].psgMode=(c.value+1)&7;
chan[c.chan].psgMode.val=(c.value+1)&7;
if (isMuted[c.chan]) {
rWrite(0x08+c.chan,0);
} else if (chan[c.chan].active) {

View File

@ -32,10 +32,15 @@ class DivPlatformAY8910: public DivDispatch {
inline unsigned char regRemap(unsigned char reg) { return intellivision?AY8914RegRemap[reg&0x0f]:reg&0x0f; }
struct Channel {
struct PSGMode {
unsigned char tone: 1;
unsigned char noise: 1;
unsigned char envelope: 1;
unsigned char dac: 1;
union {
struct {
unsigned char tone: 1;
unsigned char noise: 1;
unsigned char envelope: 1;
unsigned char dac: 1;
};
unsigned char val=1;
};
unsigned char getTone() {
return dac?0:(tone<<0);
@ -49,19 +54,8 @@ class DivPlatformAY8910: public DivDispatch {
return dac?0:(envelope<<2);
}
PSGMode& operator=(unsigned char s) {
tone=(s>>0)&1;
noise=(s>>1)&1;
envelope=(s>>2)&1;
dac=(s>>3)&1;
return *this;
}
PSGMode():
tone(1),
noise(0),
envelope(0),
dac(0) {}
val(1) {}
} psgMode;
struct DAC {

View File

@ -110,8 +110,8 @@ const unsigned char dacLogTableAY8930[256]={
};
void DivPlatformAY8930::runDAC() {
for (int i=0; i<3; i++) {
if (chan[i].psgMode.dac && chan[i].dac.sample!=-1) {
for (int i=0; i<3; i++) {
if (chan[i].active && chan[i].psgMode.dac && chan[i].dac.sample!=-1) {
chan[i].dac.period+=chan[i].dac.rate;
bool end=false;
bool changed=false;
@ -125,7 +125,7 @@ void DivPlatformAY8930::runDAC() {
break;
}
unsigned char dacData=dacLogTableAY8930[(unsigned char)s->data8[chan[i].dac.pos]^0x80];
chan[i].dac.out=MAX(0,dacData-(31-chan[i].outVol));
chan[i].dac.out=(chan[i].active && !isMuted[i])?MAX(0,dacData-(31-chan[i].outVol)):0;
if (prevOut!=chan[i].dac.out) {
prevOut=chan[i].dac.out;
changed=true;
@ -254,7 +254,7 @@ void DivPlatformAY8930::tick(bool sysTick) {
}
if (chan[i].std.wave.had) {
if (!chan[i].psgMode.dac) {
chan[i].psgMode=(chan[i].std.wave.val+1)&7;
chan[i].psgMode.val=(chan[i].std.wave.val+1)&7;
if (isMuted[i]) {
rWrite(0x08+i,0);
} else {
@ -333,12 +333,16 @@ void DivPlatformAY8930::tick(bool sysTick) {
}
if (chan[i].freq>65535) chan[i].freq=65535;
if (chan[i].keyOn) {
if (chan[i].psgMode.val==0) {
chan[i].psgMode.val=1;
}
if (chan[i].insChanged) {
if (!chan[i].std.ex1.will) immWrite(0x16+i,chan[i].duty);
chan[i].insChanged=false;
}
}
if (chan[i].keyOff) {
chan[i].psgMode.val=0;
rWrite(0x08+i,0);
}
rWrite((i)<<1,chan[i].freq&0xff);
@ -537,7 +541,7 @@ int DivPlatformAY8930::dispatch(DivCommand c) {
case DIV_CMD_STD_NOISE_MODE:
if (c.value<0x10) {
if (!chan[c.chan].psgMode.dac) {
chan[c.chan].psgMode=(c.value+1)&7;
chan[c.chan].psgMode.val=(c.value+1)&7;
if (isMuted[c.chan]) {
rWrite(0x08+c.chan,0);
} else if (chan[c.chan].active) {

View File

@ -40,10 +40,15 @@ class DivPlatformAY8930: public DivDispatch {
} envelope;
struct PSGMode {
unsigned char tone: 1;
unsigned char noise: 1;
unsigned char envelope: 1;
unsigned char dac: 1;
union {
struct {
unsigned char tone: 1;
unsigned char noise: 1;
unsigned char envelope: 1;
unsigned char dac: 1;
};
unsigned char val=1;
};
unsigned char getTone() {
return dac?0:(tone<<0);
@ -57,19 +62,8 @@ class DivPlatformAY8930: public DivDispatch {
return dac?0:(envelope<<2);
}
PSGMode& operator=(unsigned char s) {
tone=(s>>0)&1;
noise=(s>>1)&1;
envelope=(s>>2)&1;
dac=(s>>3)&1;
return *this;
}
PSGMode():
tone(1),
noise(0),
envelope(0),
dac(0) {}
val(1) {}
} psgMode;
struct DAC {

View File

@ -24,7 +24,7 @@
#define CHIP_FREQBASE 131072
#define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} }
#define rWrite(a,v) if (!skipRegisterWrites) {writes.push(QueuedWrite(a,v)); if (dumpWrites) {addWrite(a,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)
@ -94,19 +94,9 @@ void DivPlatformSNES::tick(bool sysTick) {
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 (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.vol.had) {
chan[i].outVol=VOL_SCALE_LOG(chan[i].vol&127,MIN(127,chan[i].std.vol.val),127);
}
if (chan[i].std.arp.had) {
if (!chan[i].inPorta) {
@ -123,6 +113,10 @@ void DivPlatformSNES::tick(bool sysTick) {
chan[i].freqChanged=true;
}
}
if (chan[i].std.duty.had) {
noiseFreq=chan[i].std.duty.val;
writeControl=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;
@ -146,7 +140,30 @@ void DivPlatformSNES::tick(bool sysTick) {
int val=chan[i].std.panR.val&0x7f;
chan[i].panR=(val<<1)|(val>>6);
}
if (chan[i].std.panL.had || chan[i].std.panR.had) {
bool hasInverted=false;
if (chan[i].std.ex1.had) {
if (chan[i].invertL!=(bool)(chan[i].std.ex1.val&16)) {
chan[i].invertL=chan[i].std.ex1.val&16;
hasInverted=true;
}
if (chan[i].invertR!=(bool)(chan[i].std.ex1.val&8)) {
chan[i].invertR=chan[i].std.ex1.val&8;
hasInverted=true;
}
if (chan[i].pitchMod!=(bool)(chan[i].std.ex1.val&4)) {
chan[i].pitchMod=chan[i].std.ex1.val&4;
writePitchMod=true;
}
if (chan[i].echo!=(bool)(chan[i].std.ex1.val&2)) {
chan[i].echo=chan[i].std.ex1.val&2;
writeEcho=true;
}
if (chan[i].noise!=(bool)(chan[i].std.ex1.val&1)) {
chan[i].noise=chan[i].std.ex1.val&1;
writeNoise=true;
}
}
if (chan[i].std.vol.had || chan[i].std.panL.had || chan[i].std.panR.had || hasInverted) {
writeOutVol(i);
}
if (chan[i].setPos) {
@ -164,17 +181,12 @@ void DivPlatformSNES::tick(bool sysTick) {
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;
if (chan[i].useWave) off=(double)chan[i].wtLen/32.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) {
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);
}
unsigned short tabAddr=sampleTableAddr(i);
if (chan[i].useWave) {
start=waveTableAddr(i);
loop=start;
@ -193,9 +205,6 @@ void DivPlatformSNES::tick(bool sysTick) {
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;
}
@ -210,6 +219,53 @@ void DivPlatformSNES::tick(bool sysTick) {
}
}
}
if (writeControl) {
unsigned char control=noiseFreq&0x1f;
rWrite(0x6c,control);
writeControl=false;
}
if (writeNoise) {
unsigned char noiseBits=(
(chan[0].noise?1:0)|
(chan[1].noise?2:0)|
(chan[2].noise?4:0)|
(chan[3].noise?8:0)|
(chan[4].noise?0x10:0)|
(chan[5].noise?0x20:0)|
(chan[6].noise?0x40:0)|
(chan[7].noise?0x80:0)
);
rWrite(0x3d,noiseBits);
writeNoise=false;
}
if (writePitchMod) {
unsigned char pitchModBits=(
(chan[0].pitchMod?1:0)|
(chan[1].pitchMod?2:0)|
(chan[2].pitchMod?4:0)|
(chan[3].pitchMod?8:0)|
(chan[4].pitchMod?0x10:0)|
(chan[5].pitchMod?0x20:0)|
(chan[6].pitchMod?0x40:0)|
(chan[7].pitchMod?0x80:0)
);
rWrite(0x2d,pitchModBits);
writePitchMod=false;
}
if (writeEcho) {
unsigned char echoBits=(
(chan[0].echo?1:0)|
(chan[1].echo?2:0)|
(chan[2].echo?4:0)|
(chan[3].echo?8:0)|
(chan[4].echo?0x10:0)|
(chan[5].echo?0x20:0)|
(chan[6].echo?0x40:0)|
(chan[7].echo?0x80:0)
);
rWrite(0x4d,echoBits);
writeEcho=false;
}
if (kon!=0) {
rWrite(0x4c,kon);
}
@ -220,7 +276,7 @@ void DivPlatformSNES::tick(bool sysTick) {
int DivPlatformSNES::dispatch(DivCommand c) {
switch (c.cmd) {
case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA);
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_SNES);
if (ins->amiga.useWave) {
chan[c.chan].useWave=true;
chan[c.chan].wtLen=ins->amiga.waveLen+1;
@ -239,6 +295,32 @@ int DivPlatformSNES::dispatch(DivCommand c) {
if (chan[c.chan].useWave || chan[c.chan].sample<0 || chan[c.chan].sample>=parent->song.sampleLen) {
chan[c.chan].sample=-1;
}
if (chan[c.chan].insChanged) {
chan[c.chan].state=ins->snes;
}
if (chan[c.chan].state.useEnv) {
chWrite(c.chan,5,chan[c.chan].state.a|(chan[c.chan].state.d<<4)|0x80);
chWrite(c.chan,6,chan[c.chan].state.r|(chan[c.chan].state.s<<5));
} else {
chWrite(c.chan,5,0);
switch (chan[c.chan].state.gainMode) {
case DivInstrumentSNES::GAIN_MODE_DIRECT:
chWrite(c.chan,7,chan[c.chan].state.gain&127);
break;
case DivInstrumentSNES::GAIN_MODE_DEC_LINEAR:
chWrite(c.chan,7,0x80|(chan[c.chan].state.gain&31));
break;
case DivInstrumentSNES::GAIN_MODE_INC_LINEAR:
chWrite(c.chan,7,0xc0|(chan[c.chan].state.gain&31));
break;
case DivInstrumentSNES::GAIN_MODE_DEC_LOG:
chWrite(c.chan,7,0xa0|(chan[c.chan].state.gain&31));
break;
case DivInstrumentSNES::GAIN_MODE_INC_INVLOG:
chWrite(c.chan,7,0xe0|(chan[c.chan].state.gain&31));
break;
}
}
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=round(NOTE_FREQUENCY(c.value));
chan[c.chan].freqChanged=true;
@ -247,11 +329,6 @@ int DivPlatformSNES::dispatch(DivCommand c) {
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;
}
@ -277,12 +354,6 @@ int DivPlatformSNES::dispatch(DivCommand c) {
writeOutVol(c.chan);
}
break;
// case DIV_CMD_GLOBAL_VOLUME:
// gblVolL=MIN(c.value,127);
// gblVolR=MIN(c.value,127);
// rWrite(0x0c,gblVolL);
// rWrite(0x1c,gblVolR);
// break;
case DIV_CMD_GET_VOLUME:
return chan[c.chan].vol;
break;
@ -326,7 +397,7 @@ int DivPlatformSNES::dispatch(DivCommand c) {
}
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_AMIGA));
if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_SNES));
}
chan[c.chan].inPorta=c.value;
break;
@ -346,7 +417,7 @@ int DivPlatformSNES::dispatch(DivCommand c) {
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);
unsigned short pos=waveTableAddr(ch);
for (int i=0; i<chan[ch].wtLen/16; i++) {
sampleMem[pos++]=0xb0;
for (int j=0; j<8; j++) {
@ -359,12 +430,13 @@ void DivPlatformSNES::updateWave(int ch) {
}
void DivPlatformSNES::writeOutVol(int ch) {
// TODO negative (inverted) panning support
int outL=0;
int outR=0;
if (!isMuted[ch]) {
outL=chan[ch].vol*chan[ch].panL/255;
outR=chan[ch].vol*chan[ch].panR/255;
outL=(globalVolL*((chan[ch].outVol*chan[ch].panL)/127))/127;
outR=(globalVolR*((chan[ch].outVol*chan[ch].panR)/127))/127;
if (chan[ch].invertL) outL=-outL;
if (chan[ch].invertR) outR=-outR;
}
chWrite(ch,0,outL);
chWrite(ch,1,outR);
@ -380,8 +452,15 @@ void DivPlatformSNES::forceIns() {
chan[i].insChanged=true;
chan[i].freqChanged=true;
chan[i].sample=-1;
updateWave(i);
if (chan[i].active && chan[i].useWave) {
updateWave(i);
}
writeOutVol(i);
}
writeControl=true;
writeNoise=true;
writePitchMod=true;
writeEcho=true;
}
void* DivPlatformSNES::getChanState(int ch) {
@ -414,9 +493,9 @@ void DivPlatformSNES::reset() {
dsp.init(sampleMem);
dsp.set_output(NULL,0);
memset(regPool,0,128);
// TODO more initial values
// this can't be 0 or channel 1 won't play
// this can't be 0x100 either as that's used by SPC700 page 1 and the stack
// this may not even be 0x200 as some space will be taken by the playback routine and variables
sampleTableBase=0x200;
rWrite(0x5d,sampleTableBase>>8);
rWrite(0x0c,127); // global volume left
@ -430,6 +509,10 @@ void DivPlatformSNES::reset() {
writeOutVol(i);
chWrite(i,4,i); // source number
}
writeControl=false;
writeNoise=false;
writePitchMod=false;
writeEcho=false;
}
bool DivPlatformSNES::isStereo() {
@ -493,7 +576,7 @@ void DivPlatformSNES::renderSamples() {
int actualLength=MIN((int)(getSampleMemCapacity()-memPos)/9*9,length);
if (actualLength>0) {
s->offSNES=memPos;
memcpy(&sampleMem[memPos],s->data8,actualLength);
memcpy(&sampleMem[memPos],s->dataBRR,actualLength);
memPos+=actualLength;
}
if (actualLength<length) {
@ -506,6 +589,11 @@ void DivPlatformSNES::renderSamples() {
sampleMemLen=memPos;
}
void DivPlatformSNES::setFlags(unsigned int flags) {
globalVolL=127-(flags&127);
globalVolR=127-((flags>>8)&127);
}
int DivPlatformSNES::init(DivEngine* p, int channels, int sugRate, unsigned int flags) {
parent=p;
dumpWrites=false;
@ -517,6 +605,7 @@ int DivPlatformSNES::init(DivEngine* p, int channels, int sugRate, unsigned int
sampleMemLen=0;
chipClock=1024000;
rate=chipClock/32;
setFlags(flags);
reset();
return 8;
}

View File

@ -33,10 +33,10 @@ class DivPlatformSNES: public DivDispatch {
int sample, wave, ins;
int note;
int panL, panR;
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, useWave, setPos;
signed char vol;
bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, useWave, setPos, noise, echo, pitchMod, invertL, invertR;
int vol, outVol;
int wtLen;
bool useEnv;
DivInstrumentSNES state;
DivMacroInt std;
DivWaveSynth ws;
void macroInit(DivInstrument* which) {
@ -53,8 +53,8 @@ class DivPlatformSNES: public DivDispatch {
wave(-1),
ins(-1),
note(0),
panL(255),
panR(255),
panL(127),
panR(127),
active(false),
insChanged(true),
freqChanged(false),
@ -63,15 +63,25 @@ class DivPlatformSNES: public DivDispatch {
inPorta(false),
useWave(false),
setPos(false),
noise(false),
echo(false),
pitchMod(false),
invertL(false),
invertR(false),
vol(127),
wtLen(16),
useEnv(false) {}
outVol(127),
wtLen(16) {}
};
Channel chan[8];
DivDispatchOscBuffer* oscBuf[8];
bool isMuted[8];
signed char gblVolL, gblVolR;
signed char globalVolL, globalVolR;
unsigned char noiseFreq;
size_t sampleTableBase;
bool writeControl;
bool writeNoise;
bool writePitchMod;
bool writeEcho;
struct QueuedWrite {
unsigned char addr;
@ -101,6 +111,7 @@ class DivPlatformSNES: public DivDispatch {
bool isStereo();
void notifyInsChange(int ins);
void notifyWaveChange(int wave);
void setFlags(unsigned int flags);
void notifyInsDeletion(void* ins);
void poke(unsigned int addr, unsigned short val);
void poke(std::vector<DivRegWrite>& wlist);

View File

@ -803,7 +803,7 @@ void SPC_DSP::run( int clocks_remain )
{
loop:
// GCC, why
#ifdef __GNUC__
#if defined(__GNUC__) && !defined(__clang__)
#define PHASE( n ) if ( n && !--clocks_remain ) break; __attribute__ ((fallthrough)); case n:
#else
#define PHASE( n ) if ( n && !--clocks_remain ) break; case n:

View File

@ -199,15 +199,25 @@ const char* cmdName[]={
"ES5506_ENVELOPE_K2RAMP",
"ES5506_PAUSE",
"DIV_CMD_SU_SWEEP_PERIOD_LOW",
"DIV_CMD_SU_SWEEP_PERIOD_HIGH",
"DIV_CMD_SU_SWEEP_BOUND",
"DIV_CMD_SU_SWEEP_ENABLE",
"DIV_CMD_SU_SYNC_PERIOD_LOW",
"DIV_CMD_SU_SYNC_PERIOD_HIGH",
"SU_SWEEP_PERIOD_LOW",
"SU_SWEEP_PERIOD_HIGH",
"SU_SWEEP_BOUND",
"SU_SWEEP_ENABLE",
"SU_SYNC_PERIOD_LOW",
"ADPCMA_GLOBAL_VOLUME",
"SNES_ECHO",
"SNES_PITCH_MOD",
"SNES_GAIN_MODE",
"SNES_GAIN",
"SNES_ECHO_ENABLE",
"SNES_ECHO_DELAY",
"SNES_ECHO_VOL_LEFT",
"SNES_ECHO_VOL_RIGHT",
"SNES_ECHO_FEEDBACK",
"SNES_ECHO_FIR",
"ALWAYS_SET_VOLUME"
};

View File

@ -243,7 +243,7 @@ bool DivSample::save(const char* path) {
break;
}
default:
sf_write_raw(f,data16,length16);
sf_writef_short(f,data16,samples);
break;
}
@ -852,7 +852,7 @@ void DivSample::render() {
}
break;
case DIV_SAMPLE_DEPTH_BRR: // BRR
brrDecode(dataBRR,data16,samples);
brrDecode(dataBRR,data16,lengthBRR);
break;
case DIV_SAMPLE_DEPTH_VOX: // VOX
oki_decode(dataVOX,data16,samples);
@ -911,7 +911,7 @@ void DivSample::render() {
}
if (depth!=DIV_SAMPLE_DEPTH_BRR) { // BRR
if (!initInternal(DIV_SAMPLE_DEPTH_BRR,samples)) return;
brrEncode(data16,dataBRR,samples);
brrEncode(data16,dataBRR,(samples+15)&(~15),loop?loopStart:-1);
}
if (depth!=DIV_SAMPLE_DEPTH_VOX) { // VOX
if (!initInternal(DIV_SAMPLE_DEPTH_VOX,samples)) return;

View File

@ -618,7 +618,7 @@ struct DivSong {
e1e2StopOnSameNote(false),
brokenPortaArp(false),
snNoLowPeriods(false),
disableSampleMacro(true),
disableSampleMacro(false),
autoSystem(true) {
for (int i=0; i<32; i++) {
system[i]=DIV_SYSTEM_NULL;

View File

@ -871,7 +871,35 @@ void DivEngine::registerSystems() {
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8"},
{"CH1", "CH2", "CH3", "CH4", "CH5", "CH6", "CH7", "CH8"},
{DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM, DIV_CH_PCM},
{DIV_INS_SNES, DIV_INS_SNES, DIV_INS_SNES, DIV_INS_SNES, DIV_INS_SNES, DIV_INS_SNES, DIV_INS_SNES, DIV_INS_SNES}
{DIV_INS_SNES, DIV_INS_SNES, DIV_INS_SNES, DIV_INS_SNES, DIV_INS_SNES, DIV_INS_SNES, DIV_INS_SNES, DIV_INS_SNES},
{},
{
{0x10, {DIV_CMD_WAVE, "10xx: Set waveform"}},
{0x11, {DIV_CMD_STD_NOISE_MODE, "11xx: Toggle noise mode"}},
{0x12, {DIV_CMD_SNES_ECHO, "12xx: Toggle echo on this channel"}},
{0x13, {DIV_CMD_SNES_PITCH_MOD, "13xx: Toggle pitch modulation"}},
{0x14, {DIV_CMD_SNES_INVERT, "14xy: Toggle invert (x: left; y: right)"}},
{0x15, {DIV_CMD_SNES_GAIN_MODE, "15xx: Set gain mode"}},
{0x16, {DIV_CMD_SNES_GAIN, "16xx: Set gain"}},
{0x18, {DIV_CMD_SNES_ECHO_ENABLE, "18xx: Enable echo buffer"}},
{0x19, {DIV_CMD_SNES_ECHO_DELAY, "19xx: Set echo delay"}},
{0x1a, {DIV_CMD_SNES_ECHO_VOL_LEFT, "1Axx: Set left echo volume"}},
{0x1b, {DIV_CMD_SNES_ECHO_VOL_RIGHT, "1Bxx: Set right echo volume"}},
{0x1c, {DIV_CMD_SNES_ECHO_FEEDBACK, "1Cxx: Set echo feedback"}},
{0x1d, {DIV_CMD_STD_NOISE_FREQ, "1Dxx: Set noise frequency"}},
{0x20, {DIV_CMD_FM_AR, "20xx: Set attack"}},
{0x21, {DIV_CMD_FM_DR, "21xx: Set decay"}},
{0x22, {DIV_CMD_FM_SL, "22xx: Set sustain"}},
{0x23, {DIV_CMD_FM_RR, "23xx: Set release"}},
{0x30, {DIV_CMD_SNES_ECHO_FIR, "30xx: Set echo filter coefficient 0",constVal<0>,effectVal}},
{0x31, {DIV_CMD_SNES_ECHO_FIR, "31xx: Set echo filter coefficient 1",constVal<1>,effectVal}},
{0x32, {DIV_CMD_SNES_ECHO_FIR, "32xx: Set echo filter coefficient 2",constVal<2>,effectVal}},
{0x33, {DIV_CMD_SNES_ECHO_FIR, "33xx: Set echo filter coefficient 3",constVal<3>,effectVal}},
{0x34, {DIV_CMD_SNES_ECHO_FIR, "34xx: Set echo filter coefficient 4",constVal<4>,effectVal}},
{0x35, {DIV_CMD_SNES_ECHO_FIR, "35xx: Set echo filter coefficient 5",constVal<5>,effectVal}},
{0x36, {DIV_CMD_SNES_ECHO_FIR, "36xx: Set echo filter coefficient 6",constVal<6>,effectVal}},
{0x37, {DIV_CMD_SNES_ECHO_FIR, "37xx: Set echo filter coefficient 7",constVal<7>,effectVal}},
}
);
sysDefs[DIV_SYSTEM_VRC6]=new DivSysDef(

View File

@ -86,7 +86,7 @@ SafeWriter* DivEngine::saveZSM(unsigned int zsmrate, bool loop) {
// Prepare to write song data
playSub(false);
size_t tickCount=0;
//size_t tickCount=0;
bool done=false;
int loopPos=-1;
int writeCount=0;
@ -155,7 +155,7 @@ SafeWriter* DivEngine::saveZSM(unsigned int zsmrate, bool loop) {
fracWait &= MASTER_CLOCK_MASK;
if (totalWait>0) {
zsm.tick(totalWait);
tickCount+=totalWait;
//tickCount+=totalWait;
}
}
// end of song

View File

@ -1556,9 +1556,9 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
if (!dirExists(workingDirSample)) workingDirSample=getHomeDir();
hasOpened=fileDialog->openLoad(
"Load Sample",
{"compatible files", "*.wav *.dmc",
{"compatible files", "*.wav *.dmc *.brr",
"all files", ".*"},
"compatible files{.wav,.dmc},.*",
"compatible files{.wav,.dmc,.brr},.*",
workingDirSample,
dpiScale
);

View File

@ -1043,6 +1043,7 @@ const int chipsSample[]={
DIV_SYSTEM_MSM6258,
DIV_SYSTEM_MSM6295,
DIV_SYSTEM_RF5C68,
DIV_SYSTEM_SNES,
DIV_SYSTEM_PCM_DAC,
DIV_SYSTEM_ES5506,
0 // don't remove this last one!

View File

@ -227,6 +227,10 @@ const char* saaEnvBits[9]={
"mirror", "loop", "cut", "direction", "resolution", "fixed", "N/A","enabled", NULL
};
const char* snesModeBits[6]={
"noise", "echo", "pitch mod", "invert right", "invert left", NULL
};
const char* filtModeBits[5]={
"low", "band", "high", "ch3off", NULL
};
@ -4499,7 +4503,9 @@ void FurnaceGUI::drawInsEdit() {
if (ins->type==DIV_INS_AMIGA) {
volMax=64;
}
if (ins->type==DIV_INS_FM || ins->type==DIV_INS_SEGAPCM || ins->type==DIV_INS_MIKEY || ins->type==DIV_INS_MULTIPCM || ins->type==DIV_INS_SU || ins->type==DIV_INS_OPZ || ins->type==DIV_INS_OPM) {
if (ins->type==DIV_INS_FM || ins->type==DIV_INS_SEGAPCM || ins->type==DIV_INS_MIKEY ||
ins->type==DIV_INS_MULTIPCM || ins->type==DIV_INS_SU || ins->type==DIV_INS_OPZ ||
ins->type==DIV_INS_OPM || ins->type==DIV_INS_SNES) {
volMax=127;
}
if (ins->type==DIV_INS_GB) {
@ -4582,9 +4588,12 @@ void FurnaceGUI::drawInsEdit() {
dutyLabel="Noise";
dutyMax=ins->amiga.useSample?0:8;
}
if (ins->type==DIV_INS_SNES) {
dutyLabel="Noise Freq";
dutyMax=31;
}
if (ins->type==DIV_INS_OPLL || ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPL_DRUMS ||
ins->type==DIV_INS_VRC6_SAW || ins->type==DIV_INS_FDS || ins->type==DIV_INS_MULTIPCM ||
ins->type==DIV_INS_SNES) {
ins->type==DIV_INS_VRC6_SAW || ins->type==DIV_INS_FDS || ins->type==DIV_INS_MULTIPCM) {
dutyMax=0;
}
if (ins->type==DIV_INS_VERA) {
@ -4730,9 +4739,9 @@ void FurnaceGUI::drawInsEdit() {
ex1Max=65535;
ex2Max=65535;
}
if (ins->type==DIV_INS_SNES && !ins->snes.useEnv) {
ex1Max=4;
ex2Max=31;
if (ins->type==DIV_INS_SNES) {
ex1Max=5;
ex2Max=5;
}
int panMin=0;
@ -4783,6 +4792,10 @@ void FurnaceGUI::drawInsEdit() {
panMax=127;
panSingleNoBit=true;
}
if (ins->type==DIV_INS_SNES) {
panMin=0;
panMax=127;
}
if (ins->type==DIV_INS_ES5506) {
panMax=65535;
}
@ -4875,7 +4888,7 @@ void FurnaceGUI::drawInsEdit() {
} else if (ins->type==DIV_INS_QSOUND) {
macroList.push_back(FurnaceGUIMacroDesc("Echo Feedback",&ins->std.ex1Macro,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER]));
} else if (ins->type==DIV_INS_SNES) {
macroList.push_back(FurnaceGUIMacroDesc("Gain Mode",&ins->std.ex1Macro,0,ex1Max,64,uiColors[GUI_COLOR_MACRO_VOLUME],false,NULL,NULL,false,snesGainModes));
macroList.push_back(FurnaceGUIMacroDesc("Special",&ins->std.ex1Macro,0,ex1Max,96,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,snesModeBits));
} else {
macroList.push_back(FurnaceGUIMacroDesc("Duty",&ins->std.ex1Macro,0,ex1Max,160,uiColors[GUI_COLOR_MACRO_OTHER]));
}
@ -4894,7 +4907,7 @@ void FurnaceGUI::drawInsEdit() {
} else if (ins->type==DIV_INS_QSOUND) {
macroList.push_back(FurnaceGUIMacroDesc("Echo Length",&ins->std.ex2Macro,0,ex2Max,160,uiColors[GUI_COLOR_MACRO_OTHER]));
} else if (ins->type==DIV_INS_SNES) {
macroList.push_back(FurnaceGUIMacroDesc("Gain Rate",&ins->std.ex2Macro,0,ex2Max,160,uiColors[GUI_COLOR_MACRO_VOLUME]));
macroList.push_back(FurnaceGUIMacroDesc("Gain Mode",&ins->std.ex2Macro,0,ex2Max,64,uiColors[GUI_COLOR_MACRO_VOLUME],false,NULL,NULL,false,snesGainModes));
} else {
macroList.push_back(FurnaceGUIMacroDesc("Envelope",&ins->std.ex2Macro,0,ex2Max,ex2Bit?64:160,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,ex2Bit,ayEnvBits));
}
@ -4934,6 +4947,9 @@ void FurnaceGUI::drawInsEdit() {
macroList.push_back(FurnaceGUIMacroDesc("Envelope mode",&ins->std.ex8Macro,0,2,64,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,es5506EnvelopeModes));
macroList.push_back(FurnaceGUIMacroDesc("Control",&ins->std.algMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,es5506ControlModes));
}
if (ins->type==DIV_INS_SNES) {
macroList.push_back(FurnaceGUIMacroDesc("Gain Rate",&ins->std.ex3Macro,0,127,160,uiColors[GUI_COLOR_MACRO_VOLUME]));
}
drawMacros(macroList);
ImGui::EndTabItem();

View File

@ -3296,6 +3296,7 @@ void FurnaceGUI::applyUISettings(bool updateFonts) {
ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".dmw",uiColors[GUI_COLOR_FILE_WAVE],ICON_FA_FILE);
ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".wav",uiColors[GUI_COLOR_FILE_AUDIO],ICON_FA_FILE_AUDIO_O);
ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".dmc",uiColors[GUI_COLOR_FILE_AUDIO],ICON_FA_FILE_AUDIO_O);
ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".brr",uiColors[GUI_COLOR_FILE_AUDIO],ICON_FA_FILE_AUDIO_O);
ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".vgm",uiColors[GUI_COLOR_FILE_VGM],ICON_FA_FILE_AUDIO_O);
ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".zsm",uiColors[GUI_COLOR_FILE_ZSM],ICON_FA_FILE_AUDIO_O);
ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".ttf",uiColors[GUI_COLOR_FILE_FONT],ICON_FA_FONT);

View File

@ -764,12 +764,27 @@ void FurnaceGUI::drawSysConf(int chan, DivSystem type, unsigned int& flags, bool
}
break;
}
case DIV_SYSTEM_SNES: {
ImGui::Text("Volume scale:");
int vsL=127-(flags&127);
int vsR=127-((flags>>8)&127);
if (CWSliderInt("Left##VolScaleL",&vsL,0,127)) {
if (vsL<0) vsL=0;
if (vsL>127) vsL=127;
copyOfFlags=(flags&(~0x7f))|(127-vsL);
} rightClickable
if (CWSliderInt("Right##VolScaleL",&vsR,0,127)) {
if (vsR<0) vsR=0;
if (vsR>127) vsR=127;
copyOfFlags=(flags&(~0x7f00))|((127-vsR)<<8);
} rightClickable
break;
}
case DIV_SYSTEM_SWAN:
case DIV_SYSTEM_VERA:
case DIV_SYSTEM_BUBSYS_WSG:
case DIV_SYSTEM_YMU759:
case DIV_SYSTEM_PET:
case DIV_SYSTEM_SNES:
case DIV_SYSTEM_T6W28:
ImGui::Text("nothing to configure");
break;