diff --git a/src/engine/brrUtils.c b/src/engine/brrUtils.c index 123ec165..e4463571 100644 --- a/src/engine/brrUtils.c +++ b/src/engine/brrUtils.c @@ -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>13); + if (s<0) s=-s; + while (range<12 && s>((8<=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; diff --git a/src/engine/brrUtils.h b/src/engine/brrUtils.h index ba3a6f53..5cc898e1 100644 --- a/src/engine/brrUtils.h +++ b/src/engine/brrUtils.h @@ -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. diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index dd0c2054..3412d09b 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -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 diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 9fc8b1af..e8a28512 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -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)); diff --git a/src/engine/platform/ay.cpp b/src/engine/platform/ay.cpp index 9f4e0dc2..ad133c08 100644 --- a/src/engine/platform/ay.cpp +++ b/src/engine/platform/ay.cpp @@ -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) { diff --git a/src/engine/platform/ay.h b/src/engine/platform/ay.h index ec9a14fe..e359b3d7 100644 --- a/src/engine/platform/ay.h +++ b/src/engine/platform/ay.h @@ -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 { diff --git a/src/engine/platform/ay8930.cpp b/src/engine/platform/ay8930.cpp index a1182a80..412d53f9 100644 --- a/src/engine/platform/ay8930.cpp +++ b/src/engine/platform/ay8930.cpp @@ -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) { diff --git a/src/engine/platform/ay8930.h b/src/engine/platform/ay8930.h index 441e8214..fdfaa7a6 100644 --- a/src/engine/platform/ay8930.h +++ b/src/engine/platform/ay8930.h @@ -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 { diff --git a/src/engine/platform/snes.cpp b/src/engine/platform/snes.cpp index ca722a41..7e0996d9 100644 --- a/src/engine/platform/snes.cpp +++ b/src/engine/platform/snes.cpp @@ -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<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>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>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; } diff --git a/src/engine/platform/snes.h b/src/engine/platform/snes.h index 5deb30c5..7ac5d524 100644 --- a/src/engine/platform/snes.h +++ b/src/engine/platform/snes.h @@ -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& wlist); diff --git a/src/engine/platform/sound/snes/SPC_DSP.cpp b/src/engine/platform/sound/snes/SPC_DSP.cpp index 6510f460..1a937558 100644 --- a/src/engine/platform/sound/snes/SPC_DSP.cpp +++ b/src/engine/platform/sound/snes/SPC_DSP.cpp @@ -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: diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 1890befc..e11d07c0 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -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" }; diff --git a/src/engine/sample.cpp b/src/engine/sample.cpp index eb3f32c8..72075df9 100644 --- a/src/engine/sample.cpp +++ b/src/engine/sample.cpp @@ -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; diff --git a/src/engine/song.h b/src/engine/song.h index 128b953c..e4c3770f 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -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; diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index ed163e4d..cd8d369a 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -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( diff --git a/src/engine/zsmOps.cpp b/src/engine/zsmOps.cpp index 47e81932..21ffd5ec 100644 --- a/src/engine/zsmOps.cpp +++ b/src/engine/zsmOps.cpp @@ -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 diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 190fdec0..edc89ac1 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -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 ); diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index 7ffd1ba3..7ea82d42 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -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! diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 7a1ea922..383ec974 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -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(); diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index b52b648e..cc3f044b 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -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); diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index ec3235ba..6e90f453 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -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;