diff --git a/.gitmodules b/.gitmodules index d27443da9..435f43c75 100644 --- a/.gitmodules +++ b/.gitmodules @@ -16,3 +16,6 @@ [submodule "extern/fmt"] path = extern/fmt url = https://github.com/fmtlib/fmt.git +[submodule "extern/adpcm"] + path = extern/adpcm + url = https://github.com/superctr/adpcm diff --git a/CMakeLists.txt b/CMakeLists.txt index d4305e28d..3b7a669aa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -236,6 +236,12 @@ extern/SAASound/src/SAANoise.cpp extern/SAASound/src/SAASndC.cpp extern/SAASound/src/SAASound.cpp +extern/adpcm/bs_codec.c +extern/adpcm/oki_codec.c +extern/adpcm/yma_codec.c +extern/adpcm/ymb_codec.c +extern/adpcm/ymz_codec.c + extern/Nuked-OPN2/ym3438.c extern/opm/opm.c src/engine/platform/sound/sn76496.cpp diff --git a/demos/spamton.fur b/demos/spamton.fur new file mode 100644 index 000000000..d702c6b57 Binary files /dev/null and b/demos/spamton.fur differ diff --git a/extern/adpcm b/extern/adpcm new file mode 160000 index 000000000..ef7a21715 --- /dev/null +++ b/extern/adpcm @@ -0,0 +1 @@ +Subproject commit ef7a217154badc3b99978ac481b268c8aab67bd8 diff --git a/papers/format.md b/papers/format.md index 181d886a3..1f44f9ac6 100644 --- a/papers/format.md +++ b/papers/format.md @@ -25,6 +25,8 @@ furthermore, an `or reserved` indicates this field is always present, but is res the format versions are: +- 59: Furnace dev59 +- 58: Furnace dev58 - 57: Furnace dev57 - 53: Furnace 0.5.7 @@ -100,17 +102,18 @@ size | description | - possible soundchips: | - 0x00: end of list | - 0x01: YMU759 - 17 channels - | - 0x02: Genesis - 10 channels + | - 0x02: Genesis - 10 channels (compound!) | - 0x03: SMS (SN76489) - 4 channels | - 0x04: Game Boy - 4 channels | - 0x05: PC Engine - 6 channels | - 0x06: NES - 5 channels | - 0x07: C64 (8580) - 3 channels - | - 0x08: Arcade (YM2151) - 13 channels + | - 0x08: Arcade (YM2151+SegaPCM) - 13 channels (compound!) | - 0x09: Neo Geo (YM2610) - 13 channels | - bit 6 enables alternate mode: | - 0x42: Genesis extended - 13 channels - | - 0x43: SMS (SN76489) + OPLL (YM2413) - 13 channels + | - 0x43: SMS (SN76489) + OPLL (YM2413) - 13 channels (compound!) + | - 0x46: NES + VRC7 - 11 channels (compound!) | - 0x47: C64 (6581) - 3 channels | - 0x49: Neo Geo extended - 16 channels | - bit 7 for non-DefleMask chips: @@ -156,6 +159,8 @@ size | description | - 0xa7: OPLL drums (YM2413) - 11 channels | - 0xa8: Atari Lynx - 4 channels | - 0xe0: QSound - 19 channels + | - (compound!) means that the system is composed of two or more chips, + | and has to be flattened. 32 | sound chip volumes | - signed char, 64=1.0, 127=~2.0 32 | sound chip panning @@ -198,6 +203,8 @@ size | description S?? | channel short names | - same as above STR | song comment + 4f | master volume, 1.0f=100% (>=59) + | this is 2.0f for modules before 59 # instrument @@ -426,14 +433,26 @@ size | description STR | sample name 4 | length 4 | rate - 2 | volume - 2 | pitch + 2 | volume (<58) or reserved + 2 | pitch (<58) or reserved 1 | depth + | - 0: ZX Spectrum overlay drum (1-bit) + | - 1: 1-bit NES DPCM (1-bit) + | - 4: QSound ADPCM + | - 5: ADPCM-A + | - 6: ADPCM-B + | - 7: X68000 ADPCM + | - 8: 8-bit PCM + | - 9: BRR (SNES) + | - 10: VOX + | - 16: 16-bit PCM 1 | reserved 2 | C-4 rate (>=32) or reserved 4 | loop point (>=19) or reserved | - -1 means no loop - 2?? | sample data (always 16-bit) + ??? | sample data + | - version<58 size is length*2 + | - version>=58 size is length # pattern diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 8b4883e92..29c293e86 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -434,26 +434,6 @@ void DivEngine::notifyWaveChange(int wave) { isBusy.unlock(); } -// ADPCM code attribution: https://wiki.neogeodev.org/index.php?title=ADPCM_codecs - -static short adSteps[49]={ - 16, 17, 19, 21, 23, 25, 28, 31, 34, 37, - 41, 45, 50, 55, 60, 66, 73, 80, 88, 97, - 107, 118, 130, 143, 157, 173, 190, 209, 230, 253, - 279, 307, 337, 371, 408, 449, 494, 544, 598, 658, - 724, 796, 876, 963, 1060, 1166, 1282, 1411, 1552 -}; - -static int adStepSeek[16]={ - -1, -1, -1, -1, 2, 5, 7, 9, -1, -1, -1, -1, 2, 5, 7, 9 -}; - -static double samplePitches[11]={ - 0.1666666666, 0.2, 0.25, 0.333333333, 0.5, - 1, - 2, 3, 4, 5, 6 -}; - void DivEngine::renderSamplesP() { isBusy.lock(); renderSamples(); @@ -463,152 +443,46 @@ void DivEngine::renderSamplesP() { void DivEngine::renderSamples() { sPreview.sample=-1; sPreview.pos=0; - if (jediTable==NULL) { - jediTable=new int[16*49]; - for (int step=0; step<49; step++) { - for (int nib=0; nib<16; nib++) { - int value=(2*(nib&0x07)+1)*adSteps[step]/8; - jediTable[step*16+nib]=((nib&0x08)!=0)?-value:value; - } - } - } + // step 1: render samples for (int i=0; irendLength!=0) { - delete[] s->rendData; - delete[] s->adpcmRendData; - } - s->rendLength=(double)s->length/samplePitches[s->pitch]; - if (s->rendLength==0) { - s->adpcmRendLength=0; - continue; - } - s->rendData=new short[s->rendLength]; - size_t adpcmLen=((s->rendLength>>1)+255)&0xffffff00; - if (adpcmLen>1048576) adpcmLen=1048576; - s->adpcmRendLength=adpcmLen; - s->adpcmRendData=new unsigned char[adpcmLen]; - memset(s->adpcmRendData,0,adpcmLen); - - // step 1: render to PCM - unsigned int k=0; - float mult=(float)(s->vol)/50.0f; - for (double j=0; jlength; j+=samplePitches[s->pitch]) { - if (k>=s->rendLength) { - break; - } - if (s->depth==8) { - float next=(float)(s->data[(unsigned int)j]-0x80)*mult; - s->rendData[k++]=fmin(fmax(next,-128),127); - } else { - float next=(float)s->data[(unsigned int)j]*mult; - s->rendData[k++]=fmin(fmax(next,-32768),32767); - } - } - - // step 2: render to ADPCM - int acc=0; - int decstep=0; - int diff=0; - int step=0; - int predsample=0; - int index=0; - int prevsample=0; - int previndex=0; - for (unsigned int j=0; jadpcmRendLength*2; j++) { - unsigned char encoded=0; - int tempstep=0; - - predsample=prevsample; - index=previndex; - step=adSteps[index]; - - short sample=(jrendLength)?((s->depth==16)?(s->rendData[j]>>4):(s->rendData[j]<<4)):0; - if (sample>0x7d0) sample=0x7d0; - if (sample<-0x7d0) sample=-0x7d0; - diff=sample-predsample; - if (diff>=0) { - encoded=0; - } else { - encoded=8; - diff=-diff; - } - - tempstep=step; - if (diff>=tempstep) { - encoded|=4; - diff-=tempstep; - } - tempstep>>=1; - if (diff>=tempstep) { - encoded|=2; - diff-=tempstep; - } - tempstep>>=1; - if (diff>=tempstep) encoded|=1; - - acc+=jediTable[decstep+encoded]; - /*if (acc>0x7ff || acc<-0x800) { - logW("clipping! %d\n",acc); - }*/ - acc&=0xfff; - if (acc&0x800) acc|=~0xfff; - decstep+=adStepSeek[encoded&7]*16; - if (decstep<0) decstep=0; - if (decstep>48*16) decstep=48*16; - predsample=(short)acc; - - index+=adStepSeek[encoded]; - if (index<0) index=0; - if (index>48) index=48; - - prevsample=predsample; - previndex=index; - - if (j&1) { - s->adpcmRendData[j>>1]|=encoded; - } else { - s->adpcmRendData[j>>1]=encoded<<4; - } - } + song.sample[i]->render(); } - // step 3: allocate ADPCM samples - if (adpcmMem==NULL) adpcmMem=new unsigned char[16777216]; + // step 2: allocate ADPCM-A samples + if (adpcmAMem==NULL) adpcmAMem=new unsigned char[16777216]; size_t memPos=0; for (int i=0; iadpcmRendLength)&0xf00000)) { + int paddedLen=(s->lengthA+255)&(~0xff); + if ((memPos&0xf00000)!=((memPos+paddedLen)&0xf00000)) { memPos=(memPos+0xfffff)&0xf00000; } if (memPos>=16777216) { logW("out of ADPCM memory for sample %d!\n",i); break; } - if (memPos+s->adpcmRendLength>=16777216) { - memcpy(adpcmMem+memPos,s->adpcmRendData,16777216-memPos); + if (memPos+paddedLen>=16777216) { + memcpy(adpcmAMem+memPos,s->dataA,16777216-memPos); logW("out of ADPCM memory for sample %d!\n",i); } else { - memcpy(adpcmMem+memPos,s->adpcmRendData,s->adpcmRendLength); + memcpy(adpcmAMem+memPos,s->dataA,paddedLen); } - s->rendOff=memPos; - memPos+=s->adpcmRendLength; + s->offA=memPos; + memPos+=paddedLen; } - adpcmMemLen=memPos+256; + adpcmAMemLen=memPos+256; // step 4: allocate qsound pcm samples if (qsoundMem==NULL) qsoundMem=new unsigned char[16777216]; - memset(qsoundMem, 0, 16777216); - memPos=0; for (int i=0; irendLength; - if (length > 65536-16) { - length = 65536-16; + int length=s->length8; + if (length>65536-16) { + length=65536-16; } if ((memPos&0xff0000)!=((memPos+length)&0xff0000)) { memPos=(memPos+0xffff)&0xff0000; @@ -619,15 +493,15 @@ void DivEngine::renderSamples() { } if (memPos+length>=16777216) { for (unsigned int i=0; i<16777216-(memPos+length); i++) { - qsoundMem[(memPos + i) ^ 0x8000] = s->rendData[i] >> ((s->depth == 16) ? 8 : 0); + qsoundMem[(memPos+i)^0x8000]=s->data8[i]; } logW("out of QSound PCM memory for sample %d!\n",i); } else { for (int i=0; irendData[i] >> ((s->depth == 16) ? 8 : 0); + qsoundMem[(memPos+i)^0x8000]=s->data8[i]; } } - s->rendOffQsound=memPos ^ 0x8000; + s->offQSound=memPos^0x8000; memPos+=length+16; } qsoundMemLen=memPos+256; @@ -1890,7 +1764,6 @@ bool DivEngine::addSampleFromFile(const char* path) { if (sf_readf_short(f,buf,si.frames)!=si.frames) { logW("sample read size mismatch!\n"); } - sf_close(f); DivSample* sample=new DivSample; int sampleCount=(int)song.sample.size(); const char* sName=strrchr(path,DIR_SEPARATOR); @@ -1902,28 +1775,50 @@ bool DivEngine::addSampleFromFile(const char* path) { sample->name=sName; int index=0; - sample->length=si.frames; - sample->data=new short[si.frames]; - sample->depth=16; - sample->vol=50; - sample->pitch=5; + if ((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_PCM_U8) { + sample->depth=8; + } else { + sample->depth=16; + } + sample->init(si.frames); for (int i=0; idata[index++]=averaged; + if (((si.format&SF_FORMAT_SUBMASK)==SF_FORMAT_PCM_U8)) { + sample->data8[index++]=averaged; + } else { + sample->data16[index++]=averaged; + } } delete[] buf; sample->rate=si.samplerate; if (sample->rate<4000) sample->rate=4000; if (sample->rate>96000) sample->rate=96000; + sample->centerRate=si.samplerate; + SF_INSTRUMENT inst; + if (sf_command(f, SFC_GET_INSTRUMENT, &inst, sizeof(inst)) == SF_TRUE) + { + // There's no documentation on libsndfile detune range, but the code + // implies -50..50. Yet when loading a file you can get a >50 value. + if(inst.detune > 50) + inst.detune = inst.detune - 100; + short pitch = ((0x3c-inst.basenote)*100) + inst.detune; + sample->centerRate=si.samplerate*pow(2.0,pitch/(12.0 * 100.0)); + if(inst.loop_count && inst.loops[0].mode == SF_LOOP_FORWARD) + { + sample->loopStart=inst.loops[0].start; + if(inst.loops[0].end < (unsigned int)sampleCount) + sampleCount=inst.loops[0].end; + } + } + + if (sample->centerRate<4000) sample->centerRate=4000; + if (sample->centerRate>64000) sample->centerRate=64000; + sf_close(f); song.sample.push_back(sample); song.sampleLen=sampleCount+1; renderSamples(); diff --git a/src/engine/engine.h b/src/engine/engine.h index 6e7eefcf5..f4324fb6c 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -37,8 +37,8 @@ warnings+=(String("\n")+x); \ } -#define DIV_VERSION "dev57" -#define DIV_ENGINE_VERSION 57 +#define DIV_VERSION "dev59" +#define DIV_ENGINE_VERSION 59 enum DivStatusView { DIV_STATUS_NOTHING=0, @@ -223,8 +223,6 @@ class DivEngine { size_t totalProcessed; - private: int* jediTable; - DivSystem systemFromFile(unsigned char val); unsigned char systemToFile(DivSystem val); int dispatchCmd(DivCommand c); @@ -623,12 +621,16 @@ class DivEngine { // terminate the engine. bool quit(); - unsigned char* adpcmMem; - size_t adpcmMemLen; + unsigned char* adpcmAMem; + size_t adpcmAMemLen; unsigned char* adpcmBMem; size_t adpcmBMemLen; unsigned char* qsoundMem; size_t qsoundMemLen; + unsigned char* qsoundAMem; + size_t qsoundAMemLen; + unsigned char* dpcmMem; + size_t dpcmMemLen; DivEngine(): output(NULL), @@ -680,14 +682,17 @@ class DivEngine { metroPos(0), metroAmp(0.0f), totalProcessed(0), - jediTable(NULL), oscBuf{NULL,NULL}, oscSize(1), - adpcmMem(NULL), - adpcmMemLen(0), + adpcmAMem(NULL), + adpcmAMemLen(0), adpcmBMem(NULL), adpcmBMemLen(0), qsoundMem(NULL), - qsoundMemLen(0) {} + qsoundMemLen(0), + qsoundAMem(NULL), + qsoundAMemLen(0), + dpcmMem(NULL), + dpcmMemLen(0) {} }; #endif diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 743d55866..b8c57c5b3 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -42,6 +42,12 @@ struct InflateBlock { } }; +static double samplePitches[11]={ + 0.1666666666, 0.2, 0.25, 0.333333333, 0.5, + 1, + 2, 3, 4, 5, 6 +}; + bool DivEngine::loadDMF(unsigned char* file, size_t len) { SafeReader reader=SafeReader(file,len); warnings=""; @@ -219,6 +225,10 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { addWarning("Master System FM expansion is not emulated yet. wait for 0.6!"); } + if (ds.system[0]==DIV_SYSTEM_NES_VRC7) { + addWarning("Konami VRC7 is not emulated yet. wait for 0.6!"); + } + logI("reading pattern matrix (%d)...\n",ds.ordersLen); for (int i=0; itype=DIV_INS_PCE; ins->std.volMacroHeight=31; } - if (ds.system[0]==DIV_SYSTEM_SMS_OPLL) { + if ((ds.system[0]==DIV_SYSTEM_SMS_OPLL || ds.system[0]==DIV_SYSTEM_NES_VRC7) && ins->type==DIV_INS_FM) { ins->type=DIV_INS_OPLL; } @@ -320,7 +330,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { ins->fm.op[j].vib=reader.readC(); ins->fm.op[j].ws=reader.readC(); } else { - if (ds.system[0]==DIV_SYSTEM_SMS_OPLL) { + if (ds.system[0]==DIV_SYSTEM_SMS_OPLL || ds.system[0]==DIV_SYSTEM_NES_VRC7) { if (j==0) { ins->fm.opllPreset=reader.readC(); } else { @@ -331,7 +341,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { } } if (ds.version>0x03) { - if (ds.system[0]==DIV_SYSTEM_SMS_OPLL) { + if (ds.system[0]==DIV_SYSTEM_SMS_OPLL || ds.system[0]==DIV_SYSTEM_NES_VRC7) { ins->fm.op[j].ksr=reader.readC(); ins->fm.op[j].vib=reader.readC(); ins->fm.op[j].ksl=reader.readC(); @@ -603,9 +613,12 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { } for (int i=0; ilength=reader.readI(); - if (sample->length<0) { - logE("invalid sample length %d. are we doing something wrong?\n",sample->length); + int length=reader.readI(); + int pitch=5; + int vol=50; + short* data; + if (length<0) { + logE("invalid sample length %d. are we doing something wrong?\n",length); lastError="file is corrupt or unreadable at samples"; delete[] file; return false; @@ -615,30 +628,57 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { } else { sample->name=""; } - logD("%d name %s (%d)\n",i,sample->name.c_str(),sample->length); - if (ds.version<0x0b) { - sample->rate=22050; - sample->pitch=0; - sample->vol=0; - } else { + logD("%d name %s (%d)\n",i,sample->name.c_str(),length); + sample->rate=22050; + if (ds.version>=0x0b) { sample->rate=fileToDivRate(reader.readC()); - sample->pitch=reader.readC(); - sample->vol=reader.readC(); + pitch=reader.readC(); + vol=reader.readC(); } if (ds.version>0x15) { sample->depth=reader.readC(); + if (sample->depth!=8 && sample->depth!=16) { + logW("%d: sample depth is wrong! (%d)\n",i,sample->depth); + sample->depth=16; + } } else { sample->depth=16; } - if (sample->length>0) { + if (length>0) { if (ds.version<0x0b) { - sample->data=new short[1+(sample->length/2)]; - reader.read(sample->data,sample->length); - sample->length/=2; + data=new short[1+(length/2)]; + reader.read(data,length); + length/=2; } else { - sample->data=new short[sample->length]; - reader.read(sample->data,sample->length*2); + data=new short[length]; + reader.read(data,length*2); } + + if (pitch!=5) { + logD("%d: scaling from %d...\n",i,pitch); + } + + // render data + if (!sample->init((double)length/samplePitches[pitch])) { + logE("%d: error while initializing sample!\n",i); + } + + unsigned int k=0; + float mult=(float)(vol)/50.0f; + for (double j=0; j=sample->samples) { + break; + } + if (sample->depth==8) { + float next=(float)(data[(unsigned int)j]-0x80)*mult; + sample->data8[k++]=fmin(fmax(next,-128),127); + } else { + float next=(float)data[(unsigned int)j]*mult; + sample->data16[k++]=fmin(fmax(next,-32768),32767); + } + } + + delete[] data; } ds.sample.push_back(sample); } @@ -672,6 +712,11 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { ds.system[0]=DIV_SYSTEM_SMS; ds.system[1]=DIV_SYSTEM_OPLL; } + if (ds.system[0]==DIV_SYSTEM_NES_VRC7) { + ds.systemLen=2; + ds.system[0]=DIV_SYSTEM_NES; + ds.system[1]=DIV_SYSTEM_VRC7; + } if (active) quitDispatch(); isBusy.lock(); @@ -796,7 +841,12 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { if (tchans>DIV_MAX_CHANS) tchans=DIV_MAX_CHANS; // system volume - for (int i=0; i<32; i++) ds.systemVol[i]=reader.readC(); + for (int i=0; i<32; i++) { + ds.systemVol[i]=reader.readC(); + if (ds.version<59 && ds.system[i]==DIV_SYSTEM_NES) { + ds.systemVol[i]/=4; + } + } // system panning for (int i=0; i<32; i++) ds.systemPan[i]=reader.readC(); @@ -954,6 +1004,12 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { ds.notes=reader.readString(); } + if (ds.version>=59) { + ds.masterVol=reader.readF(); + } else { + ds.masterVol=2.0f; + } + // read instruments for (int i=0; iname=reader.readString(); - sample->length=reader.readI(); + sample->samples=reader.readI(); sample->rate=reader.readI(); - sample->vol=reader.readS(); - sample->pitch=reader.readS(); + if (ds.version<58) { + vol=reader.readS(); + pitch=reader.readS(); + } else { + reader.readI(); + } sample->depth=reader.readC(); // reserved @@ -1020,8 +1083,42 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { reader.readI(); } - sample->data=new short[sample->length]; - reader.read(sample->data,2*sample->length); + if (ds.version>=58) { // modern sample + sample->init(sample->samples); + reader.read(sample->getCurBuf(),sample->getCurBufLen()); + } else { // legacy sample + int length=sample->samples; + short* data=new short[length]; + reader.read(data,2*length); + + if (pitch!=5) { + logD("%d: scaling from %d...\n",i,pitch); + } + + // render data + if (sample->depth!=8 && sample->depth!=16) { + logW("%d: sample depth is wrong! (%d)\n",i,sample->depth); + sample->depth=16; + } + sample->init(sample->samples); + + unsigned int k=0; + float mult=(float)(vol)/50.0f; + for (double j=0; j=sample->samples) { + break; + } + if (sample->depth==8) { + float next=(float)(data[(unsigned int)j]-0x80)*mult; + sample->data8[k++]=fmin(fmax(next,-128),127); + } else { + float next=(float)data[(unsigned int)j]*mult; + sample->data16[k++]=fmin(fmax(next,-32768),32767); + } + } + + delete[] data; + } ds.sample.push_back(sample); } @@ -1358,6 +1455,8 @@ SafeWriter* DivEngine::saveFur() { w->writeString(song.notes,false); + w->writeF(song.masterVol); + /// INSTRUMENT for (int i=0; iwriteI(0); w->writeString(sample->name,false); - w->writeI(sample->length); + w->writeI(sample->samples); w->writeI(sample->rate); - w->writeS(sample->vol); - w->writeS(sample->pitch); + w->writeI(0); // reserved (for now) w->writeC(sample->depth); w->writeC(0); w->writeS(sample->centerRate); w->writeI(sample->loopStart); - w->write(sample->data,sample->length*2); + w->write(sample->getCurBuf(),sample->getCurBufLen()); } /// PATTERN @@ -1462,6 +1560,9 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { if (song.system[0]==DIV_SYSTEM_SMS && song.system[1]==DIV_SYSTEM_OPLL) { isFlat=true; } + if (song.system[0]==DIV_SYSTEM_NES && song.system[1]==DIV_SYSTEM_VRC7) { + isFlat=true; + } } // fail if more than one system if (!isFlat && song.systemLen!=1) { @@ -1481,6 +1582,12 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { lastError="Master System FM expansion not supported in 1.0/legacy .dmf!"; return NULL; } + // fail if the system is NES+VRC7 and version<25 + if (version<25 && song.system[0]==DIV_SYSTEM_NES && song.system[1]==DIV_SYSTEM_VRC7) { + logE("NES + VRC7 not supported in 1.0/legacy .dmf!\n"); + lastError="NES + VRC7 not supported in 1.0/legacy .dmf!"; + return NULL; + } // fail if the system is Furnace-exclusive if (!isFlat && systemToFile(song.system[0])&0x80) { logE("cannot save Furnace-exclusive system song!\n"); @@ -1510,6 +1617,9 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { } else if (song.system[0]==DIV_SYSTEM_SMS && song.system[1]==DIV_SYSTEM_OPLL) { w->writeC(systemToFile(DIV_SYSTEM_SMS_OPLL)); sys=DIV_SYSTEM_SMS_OPLL; + } else if (song.system[0]==DIV_SYSTEM_NES && song.system[1]==DIV_SYSTEM_VRC7) { + w->writeC(systemToFile(DIV_SYSTEM_NES_VRC7)); + sys=DIV_SYSTEM_NES_VRC7; } else { w->writeC(systemToFile(song.system[0])); sys=song.system[0]; @@ -1590,12 +1700,12 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { w->writeC(op.rr); w->writeC(op.sl); w->writeC(op.tl); - if (sys==DIV_SYSTEM_SMS_OPLL && j==0) { + if ((sys==DIV_SYSTEM_SMS_OPLL || sys==DIV_SYSTEM_NES_VRC7) && j==0) { w->writeC(i->fm.opllPreset); } else { w->writeC(op.dt2); } - if (sys==DIV_SYSTEM_SMS_OPLL) { + if (sys==DIV_SYSTEM_SMS_OPLL || sys==DIV_SYSTEM_NES_VRC7) { w->writeC(op.ksr); w->writeC(op.vib); w->writeC(op.ksl); @@ -1706,13 +1816,14 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { w->writeC(song.sample.size()); for (DivSample* i: song.sample) { - w->writeI(i->length); + w->writeI(i->samples); w->writeString(i->name,true); w->writeC(divToFileRate(i->rate)); - w->writeC(i->pitch); - w->writeC(i->vol); - w->writeC(i->depth); - w->write(i->data,2*i->length); + w->writeC(5); + w->writeC(50); + // i'm too lazy to deal with .dmf's weird way of storing 8-bit samples + w->writeC(16); + w->write(i->data16,i->length16); } return w; diff --git a/src/engine/platform/amiga.cpp b/src/engine/platform/amiga.cpp index 67cb85734..98a95a33d 100644 --- a/src/engine/platform/amiga.cpp +++ b/src/engine/platform/amiga.cpp @@ -72,14 +72,10 @@ void DivPlatformAmiga::acquire(short* bufL, short* bufR, size_t start, size_t le chan[i].audSub-=AMIGA_DIVIDER; if (chan[i].audSub<0) { DivSample* s=parent->song.sample[chan[i].sample]; - if (s->rendLength>0) { - if (s->depth==8) { - chan[i].audDat=s->rendData[chan[i].audPos++]; - } else { - chan[i].audDat=s->rendData[chan[i].audPos++]>>8; - } - if (chan[i].audPos>=s->rendLength || chan[i].audPos>=131071) { - if (s->loopStart>=0 && s->loopStart<=(int)s->rendLength) { + if (s->samples>0) { + chan[i].audDat=s->data8[chan[i].audPos++]; + if (chan[i].audPos>=s->samples || chan[i].audPos>=131071) { + if (s->loopStart>=0 && s->loopStart<=(int)s->samples) { chan[i].audPos=s->loopStart; } else { chan[i].sample=-1; diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index 811726625..9314c7b6f 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -90,16 +90,12 @@ void DivPlatformGenesis::acquire_nuked(short* bufL, short* bufR, size_t start, s dacPeriod-=6; if (dacPeriod<1) { DivSample* s=parent->song.sample[dacSample]; - if (s->rendLength>0) { + if (s->samples>0) { if (!isMuted[5]) { - if (s->depth==8) { - immWrite(0x2a,(unsigned char)s->rendData[dacPos]+0x80); - } else { - immWrite(0x2a,((unsigned short)s->rendData[dacPos]+0x8000)>>8); - } + immWrite(0x2a,(unsigned char)s->data8[dacPos]+0x80); } - if (++dacPos>=s->rendLength) { - if (s->loopStart>=0 && s->loopStart<=(int)s->rendLength) { + if (++dacPos>=s->samples) { + if (s->loopStart>=0 && s->loopStart<=(int)s->samples) { dacPos=s->loopStart; } else { dacSample=-1; @@ -164,16 +160,12 @@ void DivPlatformGenesis::acquire_ymfm(short* bufL, short* bufR, size_t start, si dacPeriod-=24; if (dacPeriod<1) { DivSample* s=parent->song.sample[dacSample]; - if (s->rendLength>0) { + if (s->samples>0) { if (!isMuted[5]) { - if (s->depth==8) { - immWrite(0x2a,(unsigned char)s->rendData[dacPos]+0x80); - } else { - immWrite(0x2a,((unsigned short)s->rendData[dacPos]+0x8000)>>8); - } + immWrite(0x2a,(unsigned char)s->data8[dacPos]+0x80); } - if (++dacPos>=s->rendLength) { - if (s->loopStart>=0 && s->loopStart<=(int)s->rendLength) { + if (++dacPos>=s->samples) { + if (s->loopStart>=0 && s->loopStart<=(int)s->samples) { dacPos=s->loopStart; } else { dacSample=-1; diff --git a/src/engine/platform/lynx.cpp b/src/engine/platform/lynx.cpp index b49dd3620..856950f95 100644 --- a/src/engine/platform/lynx.cpp +++ b/src/engine/platform/lynx.cpp @@ -30,6 +30,7 @@ #define WRITE_CONTROL(ch,v) rWrite(0x25+(ch<<3),(v)) #define WRITE_OTHER(ch,v) rWrite(0x27+(ch<<3),(v)) #define WRITE_ATTEN(ch,v) rWrite((0x40+ch),(v)) +#define WRITE_STEREO(v) rWrite(0x50,(v)) #define CHIP_DIVIDER 64 @@ -52,7 +53,7 @@ static int bsr(uint16_t v) { static int bsr(uint16_t v) { if (v) { - return 16 - __builtin_clz(v); + return 32 - __builtin_clz(v); } else{ return -1; @@ -64,7 +65,7 @@ static int bsr(uint16_t v) static int bsr(uint16_t v) { uint16_t mask = 0x8000; - for (int i = 31; i >= 0; --i) { + for (int i = 15; i >= 0; --i) { if (v&mask) return (int)i; mask>>=1; @@ -88,9 +89,9 @@ const char* regCheatSheetLynx[]={ const char** DivPlatformLynx::getRegisterSheet() { return regCheatSheetLynx; -} - -const char* DivPlatformLynx::getEffectName(unsigned char effect) { +} + +const char* DivPlatformLynx::getEffectName(unsigned char effect) { switch (effect) { case 0x30: case 0x31: case 0x32: case 0x33: @@ -197,7 +198,8 @@ int DivPlatformLynx::dispatch(DivCommand c) { } break; case DIV_CMD_PANNING: - WRITE_ATTEN(c.chan, c.value); + chan[c.chan].pan=((c.value&0x0f)<<4)|((c.value&0xf0)>>4); + WRITE_ATTEN(c.chan,chan[c.chan].pan); break; case DIV_CMD_GET_VOLUME: if (chan[c.chan].std.hasVol) { @@ -261,6 +263,10 @@ void DivPlatformLynx::muteChannel(int ch, bool mute) { if (chan[ch].active) WRITE_VOLUME(ch,(isMuted[ch]?0:(chan[ch].outVol&127))); } +bool DivPlatformLynx::isStereo() { + return true; +} + void DivPlatformLynx::forceIns() { for (int i=0; i<4; i++) { if (chan[i].active) { @@ -272,16 +278,16 @@ void DivPlatformLynx::forceIns() { void* DivPlatformLynx::getChanState(int ch) { return &chan[ch]; -} - -unsigned char* DivPlatformLynx::getRegisterPool() -{ - return const_cast( mikey->getRegisterPool() ); -} - -int DivPlatformLynx::getRegisterPoolSize() -{ - return 4*8+4; +} + +unsigned char* DivPlatformLynx::getRegisterPool() +{ + return const_cast( mikey->getRegisterPool() ); +} + +int DivPlatformLynx::getRegisterPoolSize() +{ + return 4*8+4; } void DivPlatformLynx::reset() { @@ -294,6 +300,7 @@ void DivPlatformLynx::reset() { if (dumpWrites) { addWrite(0xffffffff,0); } + WRITE_STEREO(0); } bool DivPlatformLynx::keyOffAffectsArp(int ch) { diff --git a/src/engine/platform/lynx.h b/src/engine/platform/lynx.h index 8ad2bf2ce..536a874a8 100644 --- a/src/engine/platform/lynx.h +++ b/src/engine/platform/lynx.h @@ -45,7 +45,7 @@ class DivPlatformLynx: public DivDispatch { MikeyFreqDiv fd; MikeyDuty duty; int baseFreq, pitch, note, actualNote, lfsr; - unsigned char ins; + unsigned char ins, pan; bool active, insChanged, freqChanged, keyOn, keyOff, inPorta; signed char vol, outVol; Channel(): @@ -58,6 +58,7 @@ class DivPlatformLynx: public DivDispatch { actualNote(0), lfsr(-1), ins(-1), + pan(0xff), active(false), insChanged(true), freqChanged(false), @@ -81,6 +82,7 @@ class DivPlatformLynx: public DivDispatch { void forceIns(); void tick(); void muteChannel(int ch, bool mute); + bool isStereo(); bool keyOffAffectsArp(int ch); bool keyOffAffectsPorta(int ch); //int getPortaFloor(int ch); diff --git a/src/engine/platform/nes.cpp b/src/engine/platform/nes.cpp index 773a58f25..10bb38fec 100644 --- a/src/engine/platform/nes.cpp +++ b/src/engine/platform/nes.cpp @@ -76,16 +76,12 @@ void DivPlatformNES::acquire(short* bufL, short* bufR, size_t start, size_t len) dacPeriod+=dacRate; if (dacPeriod>=rate) { DivSample* s=parent->song.sample[dacSample]; - if (s->rendLength>0) { + if (s->samples>0) { if (!isMuted[4]) { - if (s->depth==8) { - rWrite(0x4011,((unsigned char)s->rendData[dacPos]+0x80)>>1); - } else { - rWrite(0x4011,((unsigned short)s->rendData[dacPos]+0x8000)>>9); - } + rWrite(0x4011,((unsigned char)s->data8[dacPos]+0x80)>>1); } - if (++dacPos>=s->rendLength) { - if (s->loopStart>=0 && s->loopStart<=(int)s->rendLength) { + if (++dacPos>=s->samples) { + if (s->loopStart>=0 && s->loopStart<=(int)s->samples) { dacPos=s->loopStart; } else { dacSample=-1; @@ -103,7 +99,10 @@ void DivPlatformNES::acquire(short* bufL, short* bufR, size_t start, size_t len) if (nes->apu.clocked) { nes->apu.clocked=false; } - bufL[i]=(pulse_output(nes)+tnd_output(nes))*30; + int sample=(pulse_output(nes)+tnd_output(nes)-128)<<7; + if (sample>32767) sample=32767; + if (sample<-32768) sample=-32768; + bufL[i]=sample; } } diff --git a/src/engine/platform/pce.cpp b/src/engine/platform/pce.cpp index ede1bd667..0ffe5043d 100644 --- a/src/engine/platform/pce.cpp +++ b/src/engine/platform/pce.cpp @@ -82,21 +82,16 @@ void DivPlatformPCE::acquire(short* bufL, short* bufR, size_t start, size_t len) chan[i].dacPeriod+=chan[i].dacRate; if (chan[i].dacPeriod>rate) { DivSample* s=parent->song.sample[chan[i].dacSample]; - if (s->rendLength<=0) { + if (s->samples<=0) { chan[i].dacSample=-1; continue; } chWrite(i,0x07,0); - if (s->depth==8) { - chWrite(i,0x04,0xdf); - chWrite(i,0x06,(((unsigned char)s->rendData[chan[i].dacPos]+0x80)>>3)); - } else { - chWrite(i,0x04,0xdf); - chWrite(i,0x06,(((unsigned short)s->rendData[chan[i].dacPos]+0x8000)>>11)); - } + chWrite(i,0x04,0xdf); + chWrite(i,0x06,(((unsigned char)s->data8[chan[i].dacPos]+0x80)>>3)); chan[i].dacPos++; - if (chan[i].dacPos>=s->rendLength) { - if (s->loopStart>=0 && s->loopStart<=(int)s->rendLength) { + if (chan[i].dacPos>=s->samples) { + if (s->loopStart>=0 && s->loopStart<=(int)s->samples) { chan[i].dacPos=s->loopStart; } else { chan[i].dacSample=-1; diff --git a/src/engine/platform/qsound.cpp b/src/engine/platform/qsound.cpp index 02067ffd8..3364e4764 100644 --- a/src/engine/platform/qsound.cpp +++ b/src/engine/platform/qsound.cpp @@ -296,18 +296,18 @@ void DivPlatformQSound::tick() { } else { off=(double)s->centerRate/24038.0/16.0; } - qsound_bank = 0x8000 | (s->rendOffQsound >> 16); - qsound_addr = s->rendOffQsound & 0xffff; + qsound_bank = 0x8000 | (s->offQSound >> 16); + qsound_addr = s->offQSound & 0xffff; - int length = s->length; + int length = s->samples; if (length > 65536 - 16) { length = 65536 - 16; } if (s->loopStart == -1 || s->loopStart >= length) { - qsound_end = s->rendOffQsound + length + 15; + qsound_end = s->offQSound + length + 15; qsound_loop = 15; } else { - qsound_end = s->rendOffQsound + length; + qsound_end = s->offQSound + length; qsound_loop = length - s->loopStart; } } diff --git a/src/engine/platform/segapcm.cpp b/src/engine/platform/segapcm.cpp index 6a5276c5d..ba04c0996 100644 --- a/src/engine/platform/segapcm.cpp +++ b/src/engine/platform/segapcm.cpp @@ -44,22 +44,17 @@ void DivPlatformSegaPCM::acquire(short* bufL, short* bufR, size_t start, size_t for (int i=0; i<16; i++) { if (chan[i].pcm.sample>=0 && chan[i].pcm.samplesong.sampleLen) { DivSample* s=parent->song.sample[chan[i].pcm.sample]; - if (s->rendLength<=0) { + if (s->samples<=0) { chan[i].pcm.sample=-1; continue; } if (!isMuted[i]) { - if (s->depth==8) { - pcmL+=(s->rendData[chan[i].pcm.pos>>8]*chan[i].chVolL); - pcmR+=(s->rendData[chan[i].pcm.pos>>8]*chan[i].chVolR); - } else { - pcmL+=(s->rendData[chan[i].pcm.pos>>8]*chan[i].chVolL)>>8; - pcmR+=(s->rendData[chan[i].pcm.pos>>8]*chan[i].chVolR)>>8; - } + pcmL+=(s->data8[chan[i].pcm.pos>>8]*chan[i].chVolL); + pcmR+=(s->data8[chan[i].pcm.pos>>8]*chan[i].chVolR); } chan[i].pcm.pos+=chan[i].pcm.freq; - if (chan[i].pcm.pos>=(s->rendLength<<8)) { - if (s->loopStart>=0 && s->loopStart<=(int)s->rendLength) { + if (chan[i].pcm.pos>=(s->samples<<8)) { + if (s->loopStart>=0 && s->loopStart<=(int)s->samples) { chan[i].pcm.pos=s->loopStart<<8; } else { chan[i].pcm.sample=-1; @@ -152,17 +147,17 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) { chan[c.chan].furnacePCM=true; if (dumpWrites) { // Sega PCM writes DivSample* s=parent->song.sample[chan[c.chan].pcm.sample]; - addWrite(0x10086+(c.chan<<3),3+((s->rendOffP>>16)<<3)); - addWrite(0x10084+(c.chan<<3),(s->rendOffP)&0xff); - addWrite(0x10085+(c.chan<<3),(s->rendOffP>>8)&0xff); - addWrite(0x10006+(c.chan<<3),MIN(255,((s->rendOffP&0xffff)+s->rendLength-1)>>8)); - if (s->loopStart<0 || s->loopStart>=(int)s->rendLength) { - addWrite(0x10086+(c.chan<<3),2+((s->rendOffP>>16)<<3)); + addWrite(0x10086+(c.chan<<3),3+((s->offSegaPCM>>16)<<3)); + addWrite(0x10084+(c.chan<<3),(s->offSegaPCM)&0xff); + addWrite(0x10085+(c.chan<<3),(s->offSegaPCM>>8)&0xff); + addWrite(0x10006+(c.chan<<3),MIN(255,((s->offSegaPCM&0xffff)+s->length8-1)>>8)); + if (s->loopStart<0 || s->loopStart>=(int)s->length8) { + addWrite(0x10086+(c.chan<<3),2+((s->offSegaPCM>>16)<<3)); } else { - int loopPos=(s->rendOffP&0xffff)+s->loopStart+s->loopOffP; + int loopPos=(s->offSegaPCM&0xffff)+s->loopStart+s->loopOffP; addWrite(0x10004+(c.chan<<3),loopPos&0xff); addWrite(0x10005+(c.chan<<3),(loopPos>>8)&0xff); - addWrite(0x10086+(c.chan<<3),((s->rendOffP>>16)<<3)); + addWrite(0x10086+(c.chan<<3),((s->offSegaPCM>>16)<<3)); } } } else { @@ -182,17 +177,17 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) { chan[c.chan].furnacePCM=false; if (dumpWrites) { // Sega PCM writes DivSample* s=parent->song.sample[chan[c.chan].pcm.sample]; - addWrite(0x10086+(c.chan<<3),3+((s->rendOffP>>16)<<3)); - addWrite(0x10084+(c.chan<<3),(s->rendOffP)&0xff); - addWrite(0x10085+(c.chan<<3),(s->rendOffP>>8)&0xff); - addWrite(0x10006+(c.chan<<3),MIN(255,((s->rendOffP&0xffff)+s->rendLength-1)>>8)); - if (s->loopStart<0 || s->loopStart>=(int)s->rendLength) { - addWrite(0x10086+(c.chan<<3),2+((s->rendOffP>>16)<<3)); + addWrite(0x10086+(c.chan<<3),3+((s->offSegaPCM>>16)<<3)); + addWrite(0x10084+(c.chan<<3),(s->offSegaPCM)&0xff); + addWrite(0x10085+(c.chan<<3),(s->offSegaPCM>>8)&0xff); + addWrite(0x10006+(c.chan<<3),MIN(255,((s->offSegaPCM&0xffff)+s->length8-1)>>8)); + if (s->loopStart<0 || s->loopStart>=(int)s->length8) { + addWrite(0x10086+(c.chan<<3),2+((s->offSegaPCM>>16)<<3)); } else { - int loopPos=(s->rendOffP&0xffff)+s->loopStart+s->loopOffP; + int loopPos=(s->offSegaPCM&0xffff)+s->loopStart+s->loopOffP; addWrite(0x10004+(c.chan<<3),loopPos&0xff); addWrite(0x10005+(c.chan<<3),(loopPos>>8)&0xff); - addWrite(0x10086+(c.chan<<3),((s->rendOffP>>16)<<3)); + addWrite(0x10086+(c.chan<<3),((s->offSegaPCM>>16)<<3)); } addWrite(0x10007+(c.chan<<3),chan[c.chan].pcm.freq); } diff --git a/src/engine/platform/sms.cpp b/src/engine/platform/sms.cpp index 325665003..114d67b05 100644 --- a/src/engine/platform/sms.cpp +++ b/src/engine/platform/sms.cpp @@ -301,7 +301,9 @@ void DivPlatformSMS::poke(std::vector& wlist) { } void DivPlatformSMS::setFlags(unsigned int flags) { - if ((flags&3)==2) { + if ((flags&3)==3) { + chipClock=COLOR_NTSC/2.0; + } else if ((flags&3)==2) { chipClock=4000000; } else if ((flags&3)==1) { chipClock=COLOR_PAL*4.0/5.0; diff --git a/src/engine/platform/sound/qsound.c b/src/engine/platform/sound/qsound.c index 838ece1aa..b52475b36 100644 --- a/src/engine/platform/sound/qsound.c +++ b/src/engine/platform/sound/qsound.c @@ -162,7 +162,7 @@ void qsound_reset(struct qsound_chip *chip) chip->state_counter = 0; } -uint8_t qsound_stream_update(struct qsound_chip *chip, int16_t **outputs, int samples) +void qsound_stream_update(struct qsound_chip *chip, int16_t **outputs, int samples) { // Clear the buffers memset(outputs[0], 0, samples * sizeof(*outputs[0])); @@ -174,10 +174,9 @@ uint8_t qsound_stream_update(struct qsound_chip *chip, int16_t **outputs, int sa outputs[0][i] = chip->out[0]; outputs[1][i] = chip->out[1]; } - return 0; } -uint8_t qsound_w(struct qsound_chip *chip, uint8_t offset, uint8_t data) +void qsound_w(struct qsound_chip *chip, uint8_t offset, uint8_t data) { switch (offset) { @@ -193,7 +192,6 @@ uint8_t qsound_w(struct qsound_chip *chip, uint8_t offset, uint8_t data) default: break; } - return 0; } uint8_t qsound_r(struct qsound_chip *chip) @@ -614,10 +612,14 @@ static void state_normal_update(struct qsound_chip *chip) pan_index = 97; // Apply different volume tables on the dry and wet inputs. - dry -= (chip->voice_output[v] * chip->pan_tables[ch][PANTBL_DRY][pan_index])<<2; - wet -= (chip->voice_output[v] * chip->pan_tables[ch][PANTBL_WET][pan_index])<<2; + dry -= (chip->voice_output[v] * chip->pan_tables[ch][PANTBL_DRY][pan_index]); + wet -= (chip->voice_output[v] * chip->pan_tables[ch][PANTBL_WET][pan_index]); } + // Saturate accumulated voices + dry = CLAMP(dry, -0x1fffffff, 0x1fffffff) << 2; + wet = CLAMP(wet, -0x1fffffff, 0x1fffffff) << 2; + // Apply FIR filter on 'wet' input wet = fir(&chip->filter[ch], wet >> 16); diff --git a/src/engine/platform/sound/qsound.h b/src/engine/platform/sound/qsound.h index 7c04850ae..ed05dd65a 100644 --- a/src/engine/platform/sound/qsound.h +++ b/src/engine/platform/sound/qsound.h @@ -109,8 +109,8 @@ long qsound_start(struct qsound_chip *chip, int clock); void qsound_reset(struct qsound_chip *chip); void qsound_update(struct qsound_chip *chip); -uint8_t qsound_stream_update(struct qsound_chip *chip, int16_t **outputs, int samples); -uint8_t qsound_w(struct qsound_chip *chip, uint8_t offset, uint8_t data); +void qsound_stream_update(struct qsound_chip *chip, int16_t **outputs, int samples); +void qsound_w(struct qsound_chip *chip, uint8_t offset, uint8_t data); uint8_t qsound_r(struct qsound_chip *chip); void qsound_write_data(struct qsound_chip *chip, uint8_t address, uint16_t data); uint16_t qsound_read_data(struct qsound_chip *chip, uint8_t address); diff --git a/src/engine/platform/ym2610.cpp b/src/engine/platform/ym2610.cpp index 378a7b9c4..eb2b43f90 100644 --- a/src/engine/platform/ym2610.cpp +++ b/src/engine/platform/ym2610.cpp @@ -437,9 +437,9 @@ int DivPlatformYM2610::dispatch(DivCommand c) { break; } DivSample* s=parent->song.sample[12*sampleBank+c.value%12]; - immWrite(0x110+c.chan-7,(s->rendOff>>8)&0xff); - immWrite(0x118+c.chan-7,s->rendOff>>16); - int end=s->rendOff+s->adpcmRendLength-1; + immWrite(0x110+c.chan-7,(s->offA>>8)&0xff); + immWrite(0x118+c.chan-7,s->offA>>16); + int end=s->offA+s->lengthA-1; immWrite(0x120+c.chan-7,(end>>8)&0xff); immWrite(0x128+c.chan-7,end>>16); immWrite(0x108+(c.chan-7),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].vol)); diff --git a/src/engine/platform/ym2610Interface.cpp b/src/engine/platform/ym2610Interface.cpp index 8d53e48ce..9154b40e7 100644 --- a/src/engine/platform/ym2610Interface.cpp +++ b/src/engine/platform/ym2610Interface.cpp @@ -22,12 +22,20 @@ #include "../engine.h" uint8_t DivYM2610Interface::ymfm_external_read(ymfm::access_class type, uint32_t address) { - //printf("wants to read from %x\n",address); - if (type!=ymfm::ACCESS_ADPCM_A) return 0; - return parent->adpcmMem[address&0xffffff]; - /*if (12*sampleBank+(address>>16)>=parent->song.sampleLen) return 0; - return parent->song.sample[12*sampleBank+(address>>16)]->adpcmRendData[(address&0xffff)];*/ + switch (type) { + case ymfm::ACCESS_ADPCM_A: + if (parent->adpcmAMem==NULL) return 0; + if ((address&0xffffff)>=parent->adpcmAMemLen) return 0; + return parent->adpcmAMem[address&0xffffff]; + case ymfm::ACCESS_ADPCM_B: + if (parent->adpcmBMem==NULL) return 0; + if ((address&0xffffff)>=parent->adpcmBMemLen) return 0; + return parent->adpcmBMem[address&0xffffff]; + default: + return 0; + } + return 0; } void DivYM2610Interface::ymfm_external_write(ymfm::access_class type, uint32_t address, uint8_t data) { -} \ No newline at end of file +} diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index e5a4d9206..5fb0c4e86 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -1132,24 +1132,24 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi DivSample* s=song.sample[sPreview.sample]; for (size_t i=0; i=s->rendLength) { + if (sPreview.pos>=s->samples) { samp_temp=0; } else { - samp_temp=s->rendData[sPreview.pos++]; + samp_temp=s->data16[sPreview.pos++]; } if (s->depth==8) samp_temp<<=8; blip_add_delta(samp_bb,i,samp_temp-samp_prevSample); samp_prevSample=samp_temp; - if (sPreview.pos>=s->rendLength) { - if (s->loopStart>=0 && s->loopStart<(int)s->rendLength) { + if (sPreview.pos>=s->samples) { + if (s->loopStart>=0 && s->loopStart<(int)s->samples) { sPreview.pos=s->loopStart; } } } - if (sPreview.pos>=s->rendLength) { - if (s->loopStart>=0 && s->loopStart<(int)s->rendLength) { + if (sPreview.pos>=s->samples) { + if (s->loopStart>=0 && s->loopStart<(int)s->samples) { sPreview.pos=s->loopStart; } else { sPreview.sample=-1; @@ -1294,17 +1294,17 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi } for (int i=0; iisStereo()) { for (size_t j=0; j #include +#include + +extern "C" { +#include "../../extern/adpcm/bs_codec.h" +#include "../../extern/adpcm/oki_codec.h" +#include "../../extern/adpcm/yma_codec.h" +#include "../../extern/adpcm/ymb_codec.h" +#include "../../extern/adpcm/ymz_codec.h" +} bool DivSample::save(const char* path) { SNDFILE* f; SF_INFO si; memset(&si,0,sizeof(SF_INFO)); - if (length<1) return false; + if (length16<1) return false; si.channels=1; si.samplerate=rate; - if (depth==16) { - si.format=SF_FORMAT_PCM_16|SF_FORMAT_WAV; - } else { - si.format=SF_FORMAT_PCM_U8|SF_FORMAT_WAV; - } + si.format=SF_FORMAT_PCM_16|SF_FORMAT_WAV; f=sf_open(path,SFM_WRITE,&si); @@ -44,23 +49,268 @@ bool DivSample::save(const char* path) { return false; } - if (depth==16) { - sf_writef_short(f,data,length); - } else { - short* cbuf=new short[length]; - for (int i=0; i>3]>>(i&7))&1)?0x7fff:-0x7fff; + } + break; + case 1: { // DPCM + int accum=0; + for (unsigned int i=0; i>3]>>(i&7))&1)?1:-1; + if (accum>63) accum=63; + if (accum<-64) accum=-64; + data16[i]=accum*512; + } + break; + } + case 4: // QSound ADPCM + bs_decode(dataQSoundA,data16,samples); + break; + case 5: // ADPCM-A + yma_decode(dataA,data16,samples); + break; + case 6: // ADPCM-B + ymb_decode(dataB,data16,samples); + break; + case 7: // X6800 ADPCM + oki6258_decode(dataX68,data16,samples); + break; + case 8: // 8-bit PCM + for (unsigned int i=0; i0) { + data1[i>>3]|=1<<(i&7); + } + } + } + if (depth!=1) { // DPCM + if (!initInternal(1,samples)) return; + int accum=63; + for (unsigned int i=0; i>9; + if (next>accum) { + dataDPCM[i>>3]|=1<<(i&7); + accum++; + } else { + accum--; + } + if (accum<0) accum=0; + if (accum>127) accum=127; + } + } + if (depth!=4) { // QSound ADPCM + if (!initInternal(4,samples)) return; + bs_encode(data16,dataQSoundA,samples); + } + // TODO: pad to 256. + if (depth!=5) { // ADPCM-A + if (!initInternal(5,samples)) return; + yma_encode(data16,dataA,(samples+511)&(~0x1ff)); + } + if (depth!=6) { // ADPCM-B + if (!initInternal(6,samples)) return; + ymb_encode(data16,dataB,(samples+511)&(~0x1ff)); + } + if (depth!=7) { // X68000 ADPCM + if (!initInternal(7,samples)) return; + oki6258_encode(data16,dataX68,samples); + } + if (depth!=8) { // 8-bit PCM + if (!initInternal(8,samples)) return; + for (unsigned int i=0; i>8; + } + } + // TODO: BRR! + if (depth!=10) { // VOX + if (!initInternal(10,samples)) return; + oki_encode(data16,dataVOX,samples); + } +} + +void* DivSample::getCurBuf() { + switch (depth) { + case 0: + return data1; + case 1: + return dataDPCM; + case 4: + return dataQSoundA; + case 5: + return dataA; + case 6: + return dataB; + case 7: + return dataX68; + case 8: + return data8; + case 9: + return dataBRR; + case 10: + return dataVOX; + case 16: + return data16; + } + return NULL; +} + +unsigned int DivSample::getCurBufLen() { + switch (depth) { + case 0: + return length1; + case 1: + return lengthDPCM; + case 4: + return lengthQSoundA; + case 5: + return lengthA; + case 6: + return lengthB; + case 7: + return lengthX68; + case 8: + return length8; + case 9: + return lengthBRR; + case 10: + return lengthVOX; + case 16: + return length16; + } + return 0; +} + +DivSample::~DivSample() { + if (data8) delete[] data8; + if (data16) delete[] data16; + if (data1) delete[] data1; + if (dataDPCM) delete[] dataDPCM; + if (dataQSoundA) delete[] dataQSoundA; + if (dataA) delete[] dataA; + if (dataB) delete[] dataB; + if (dataX68) delete[] dataX68; + if (dataBRR) delete[] dataBRR; + if (dataVOX) delete[] dataVOX; } diff --git a/src/engine/sample.h b/src/engine/sample.h index c61ba868c..2915f2cef 100644 --- a/src/engine/sample.h +++ b/src/engine/sample.h @@ -21,41 +21,83 @@ struct DivSample { String name; - int length, rate, centerRate, loopStart, loopOffP; - signed char vol, pitch; + int rate, centerRate, loopStart, loopOffP; // valid values are: - // - 0: ZX Spectrum overlay drum (1-bit PCM) - // - 1: 1-bit NES DPCM - // - 4: BRR - // - 5: raw ADPCM-A - // - 6: raw ADPCM-B + // - 0: ZX Spectrum overlay drum (1-bit) + // - 1: 1-bit NES DPCM (1-bit) + // - 4: QSound ADPCM + // - 5: ADPCM-A + // - 6: ADPCM-B + // - 7: X68000 ADPCM // - 8: 8-bit PCM + // - 9: BRR (SNES) + // - 10: VOX // - 16: 16-bit PCM unsigned char depth; - short* data; - unsigned int rendLength, adpcmRendLength, rendOff, rendOffP, rendOffContiguous, rendOffQsound; - short* rendData; - unsigned char* adpcmRendData; + + // these are the new data structures. + signed char* data8; // 8 + short* data16; // 16 + unsigned char* data1; // 0 + unsigned char* dataDPCM; // 1 + unsigned char* dataQSoundA; // 4 + unsigned char* dataA; // 5 + unsigned char* dataB; // 6 + unsigned char* dataX68; // 7 + unsigned char* dataBRR; // 9 + unsigned char* dataVOX; // 10 + + unsigned int length8, length16, length1, lengthDPCM, lengthQSoundA, lengthA, lengthB, lengthX68, lengthBRR, lengthVOX; + unsigned int off8, off16, off1, offDPCM, offQSoundA, offA, offB, offX68, offBRR, offVOX; + unsigned int offSegaPCM, offQSound; + + unsigned int samples; bool save(const char* path); + bool initInternal(unsigned char d, int count); + bool init(unsigned int count); + void render(); + void* getCurBuf(); + unsigned int getCurBufLen(); DivSample(): name(""), - length(0), rate(32000), centerRate(8363), loopStart(-1), loopOffP(0), - vol(0), - pitch(0), depth(16), - data(NULL), - rendLength(0), - adpcmRendLength(0), - rendOff(0), - rendOffP(0), - rendOffContiguous(0), - rendOffQsound(0), - rendData(NULL), - adpcmRendData(NULL) {} + data8(NULL), + data16(NULL), + data1(NULL), + dataDPCM(NULL), + dataQSoundA(NULL), + dataA(NULL), + dataB(NULL), + dataX68(NULL), + dataBRR(NULL), + dataVOX(NULL), + length8(0), + length16(0), + length1(0), + lengthDPCM(0), + lengthQSoundA(0), + lengthA(0), + lengthB(0), + lengthX68(0), + lengthBRR(0), + lengthVOX(0), + off8(0), + off16(0), + off1(0), + offDPCM(0), + offQSoundA(0), + offA(0), + offB(0), + offX68(0), + offBRR(0), + offVOX(0), + offSegaPCM(0), + offQSound(0), + samples(0) {} ~DivSample(); }; diff --git a/src/engine/song.h b/src/engine/song.h index 384f8674c..c80bb6ca8 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -41,6 +41,7 @@ enum DivSystem { DIV_SYSTEM_GB, DIV_SYSTEM_PCE, DIV_SYSTEM_NES, + DIV_SYSTEM_NES_VRC7, // ** COMPOUND SYSTEM - DO NOT USE! ** DIV_SYSTEM_C64_6581, DIV_SYSTEM_C64_8580, DIV_SYSTEM_ARCADE, // ** COMPOUND SYSTEM - DO NOT USE! ** @@ -240,6 +241,7 @@ struct DivSong { bool pal; bool customTempo; int hz, patLen, ordersLen, insLen, waveLen, sampleLen; + float masterVol; float tuning; // compatibility flags @@ -307,6 +309,7 @@ struct DivSong { insLen(0), waveLen(0), sampleLen(0), + masterVol(1.0f), tuning(440.0f), limitSlides(false), linearPitch(true), diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 6b1351622..54589e37d 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -44,6 +44,8 @@ DivSystem DivEngine::systemFromFile(unsigned char val) { return DIV_SYSTEM_GENESIS_EXT; case 0x43: return DIV_SYSTEM_SMS_OPLL; + case 0x46: + return DIV_SYSTEM_NES_VRC7; case 0x47: return DIV_SYSTEM_C64_6581; case 0x49: @@ -163,6 +165,8 @@ unsigned char DivEngine::systemToFile(DivSystem val) { return 0x42; case DIV_SYSTEM_SMS_OPLL: return 0x43; + case DIV_SYSTEM_NES_VRC7: + return 0x46; case DIV_SYSTEM_C64_6581: return 0x47; case DIV_SYSTEM_YM2610_EXT: @@ -283,7 +287,9 @@ int DivEngine::getChannelCount(DivSystem sys) { case DIV_SYSTEM_GENESIS_EXT: case DIV_SYSTEM_YM2610: case DIV_SYSTEM_SMS_OPLL: - return 13; + return 13; + case DIV_SYSTEM_NES_VRC7: + return 11; case DIV_SYSTEM_YM2610_EXT: return 16; // Furnace-specific systems @@ -524,6 +530,8 @@ const char* DivEngine::getSystemName(DivSystem sys) { return "PC Engine/TurboGrafx-16"; case DIV_SYSTEM_NES: return "NES"; + case DIV_SYSTEM_NES_VRC7: + return "NES + Konami VRC7"; case DIV_SYSTEM_C64_6581: return "Commodore 64 with 6581"; case DIV_SYSTEM_C64_8580: @@ -645,6 +653,8 @@ const char* DivEngine::getSystemChips(DivSystem sys) { return "Hudson Soft HuC6280"; case DIV_SYSTEM_NES: return "Ricoh 2A03"; + case DIV_SYSTEM_NES_VRC7: + return "Ricoh 2A03 + Konami VRC7"; case DIV_SYSTEM_C64_6581: return "SID 6581"; case DIV_SYSTEM_C64_8580: @@ -806,6 +816,7 @@ bool DivEngine::isFMSystem(DivSystem sys) { return (sys==DIV_SYSTEM_GENESIS || sys==DIV_SYSTEM_GENESIS_EXT || sys==DIV_SYSTEM_SMS_OPLL || + sys==DIV_SYSTEM_NES_VRC7 || sys==DIV_SYSTEM_ARCADE || sys==DIV_SYSTEM_YM2610 || sys==DIV_SYSTEM_YM2610_EXT || @@ -1007,6 +1018,7 @@ const char* DivEngine::getChannelName(int chan) { return chanNames[3][dispatchChanOfChan[chan]]; break; case DIV_SYSTEM_SMS_OPLL: // this is flattened to SMS + OPLL. + case DIV_SYSTEM_NES_VRC7: // this is flattened to NES + VRC7. return "??"; break; case DIV_SYSTEM_GB: @@ -1142,6 +1154,7 @@ const char* DivEngine::getChannelShortName(int chan) { return chanShortNames[3][dispatchChanOfChan[chan]]; break; case DIV_SYSTEM_SMS_OPLL: // this is flattened to SMS + OPLL. + case DIV_SYSTEM_NES_VRC7: // this is flattened to NES + VRC7. return "??"; break; case DIV_SYSTEM_GB: @@ -1275,6 +1288,7 @@ int DivEngine::getChannelType(int chan) { return chanTypes[3][dispatchChanOfChan[chan]]; break; case DIV_SYSTEM_SMS_OPLL: // this is flattened to SMS + OPLL. + case DIV_SYSTEM_NES_VRC7: // this is flattened to NES + VRC7. return 0; break; case DIV_SYSTEM_GB: @@ -1408,6 +1422,7 @@ DivInstrumentType DivEngine::getPreferInsType(int chan) { return chanPrefType[3][dispatchChanOfChan[chan]]; break; case DIV_SYSTEM_SMS_OPLL: // this is flattened to SMS + OPLL. + case DIV_SYSTEM_NES_VRC7: // this is flattened to NES + VRC7. return DIV_INS_OPLL; break; case DIV_SYSTEM_GB: diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index 0d5eb521c..e51217dc4 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -310,7 +310,7 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write w->writeS(write.val); // sample number w->writeC((sample->loopStart==0)); // flags if (sample->loopStart>0) { - loopTimer[streamID]=sample->rendLength; + loopTimer[streamID]=sample->length8; loopSample[streamID]=write.val; } } @@ -848,8 +848,8 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) { for (int i=0; irendOffContiguous=sampleSeek; - sampleSeek+=sample->rendLength; + sample->off8=sampleSeek; + sampleSeek+=sample->length8; } if (writeDACSamples) for (int i=0; iwriteC(0x67); w->writeC(0x66); w->writeC(0); - w->writeI(sample->rendLength); - if (sample->depth==8) { - for (unsigned int j=0; jrendLength; j++) { - w->writeC((unsigned char)sample->rendData[j]+0x80); - } - } else { - for (unsigned int j=0; jrendLength; j++) { - w->writeC(((unsigned short)sample->rendData[j]+0x8000)>>8); - } + w->writeI(sample->length8); + for (unsigned int j=0; jlength8; j++) { + w->writeC((unsigned char)sample->data8[j]+0x80); } } @@ -874,15 +868,9 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) { w->writeC(0x67); w->writeC(0x66); w->writeC(7); - w->writeI(sample->rendLength); - if (sample->depth==8) { - for (unsigned int j=0; jrendLength; j++) { - w->writeC(((unsigned char)sample->rendData[j]+0x80)>>1); - } - } else { - for (unsigned int j=0; jrendLength; j++) { - w->writeC(((unsigned short)sample->rendData[j]+0x8000)>>9); - } + w->writeI(sample->length8); + for (unsigned int j=0; jlength8; j++) { + w->writeC(((unsigned char)sample->data8[j]+0x80)>>1); } } @@ -891,15 +879,9 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) { w->writeC(0x67); w->writeC(0x66); w->writeC(5); - w->writeI(sample->rendLength); - if (sample->depth==8) { - for (unsigned int j=0; jrendLength; j++) { - w->writeC(((unsigned char)sample->rendData[j]+0x80)>>3); - } - } else { - for (unsigned int j=0; jrendLength; j++) { - w->writeC(((unsigned short)sample->rendData[j]+0x8000)>>11); - } + w->writeI(sample->length8); + for (unsigned int j=0; jlength8; j++) { + w->writeC(((unsigned char)sample->data8[j]+0x80)>>3); } } @@ -909,47 +891,29 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) { size_t memPos=0; for (int i=0; irendLength)&0xff0000)) { + if ((memPos&0xff0000)!=((memPos+sample->length8)&0xff0000)) { memPos=(memPos+0xffff)&0xff0000; } if (memPos>=16777216) break; - sample->rendOffP=memPos; - unsigned int alignedSize=(sample->rendLength+0xff)&(~0xff); + sample->offSegaPCM=memPos; + unsigned int alignedSize=(sample->length8+0xff)&(~0xff); unsigned int readPos=0; if (alignedSize>65536) alignedSize=65536; - if (sample->depth==8) { - for (unsigned int j=0; j=sample->rendLength) { - if (sample->loopStart>=0 && sample->loopStart<(int)sample->rendLength) { - readPos=sample->loopStart; - pcmMem[memPos++]=((unsigned char)sample->rendData[readPos]+0x80); - } else { - pcmMem[memPos++]=0x80; - } + for (unsigned int j=0; j=sample->length8) { + if (sample->loopStart>=0 && sample->loopStart<(int)sample->length8) { + readPos=sample->loopStart; + pcmMem[memPos++]=((unsigned char)sample->data8[readPos]+0x80); } else { - pcmMem[memPos++]=((unsigned char)sample->rendData[readPos]+0x80); + pcmMem[memPos++]=0x80; } - readPos++; - if (memPos>=16777216) break; + } else { + pcmMem[memPos++]=((unsigned char)sample->data8[readPos]+0x80); } - sample->loopOffP=readPos-sample->loopStart; - } else { - for (unsigned int j=0; j=sample->rendLength) { - if (sample->loopStart>=0 && sample->loopStart<(int)sample->rendLength) { - readPos=sample->loopStart; - pcmMem[memPos++]=(((unsigned short)sample->rendData[readPos]+0x8000)>>8); - } else { - pcmMem[memPos++]=0x80; - } - } else { - pcmMem[memPos++]=(((unsigned short)sample->rendData[readPos]+0x8000)>>8); - } - readPos++; - if (memPos>=16777216) break; - } - sample->loopOffP=readPos-sample->loopStart; + readPos++; + if (memPos>=16777216) break; } + sample->loopOffP=readPos-sample->loopStart; if (memPos>=16777216) break; } @@ -964,14 +928,14 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) { delete[] pcmMem; } - if (writeADPCM && adpcmMemLen>0) { + if (writeADPCM && adpcmAMemLen>0) { w->writeC(0x67); w->writeC(0x66); w->writeC(0x82); - w->writeI(adpcmMemLen+8); - w->writeI(adpcmMemLen); + w->writeI(adpcmAMemLen+8); + w->writeI(adpcmAMemLen); w->writeI(0); - w->write(adpcmMem,adpcmMemLen); + w->write(adpcmAMem,adpcmAMemLen); } if (writeQSound && qsoundMemLen>0) { @@ -1137,12 +1101,12 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) { if (loopSample[nextToTouch]loopStart<(int)sample->rendLength) { + if (sample->loopStart<(int)sample->length8) { w->writeC(0x93); w->writeC(nextToTouch); - w->writeI(sample->rendOffContiguous+sample->loopStart); + w->writeI(sample->off8+sample->loopStart); w->writeC(0x81); - w->writeI(sample->rendLength-sample->loopStart); + w->writeI(sample->length8-sample->loopStart); } } loopSample[nextToTouch]=-1; diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 494d1c7cc..f90102f11 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1266,7 +1266,8 @@ void FurnaceGUI::drawSampleEdit() { } else { DivSample* sample=e->song.sample[curSample]; ImGui::InputText("Name",&sample->name); - ImGui::Text("Length: %d",sample->length); + ImGui::Text("Length: %d",sample->samples); + ImGui::Text("Type: %d-bit",sample->depth); if (ImGui::InputInt("Rate (Hz)",&sample->rate,10,200)) { if (sample->rate<100) sample->rate=100; if (sample->rate>96000) sample->rate=96000; @@ -1287,19 +1288,11 @@ void FurnaceGUI::drawSampleEdit() { if (doLoop) { ImGui::SameLine(); if (ImGui::InputInt("##LoopPosition",&sample->loopStart,1,10)) { - if (sample->loopStart<0 || sample->loopStart>=sample->length) { + if (sample->loopStart<0 || sample->loopStart>=(int)sample->samples) { sample->loopStart=0; } } } - if (ImGui::SliderScalar("Volume",ImGuiDataType_S8,&sample->vol,&_ZERO,&_ONE_HUNDRED,fmt::sprintf("%d%%%%",sample->vol*2).c_str())) { - if (sample->vol<0) sample->vol=0; - if (sample->vol>100) sample->vol=100; - } - if (ImGui::SliderScalar("Pitch",ImGuiDataType_S8,&sample->pitch,&_ZERO,&_TEN,pitchLabel[sample->pitch])) { - if (sample->pitch<0) sample->pitch=0; - if (sample->pitch>10) sample->pitch=10; - } if (ImGui::Button("Apply")) { e->renderSamplesP(); } @@ -1321,15 +1314,15 @@ void FurnaceGUI::drawSampleEdit() { ImGui::Text("- sample loop start will be aligned to the nearest even sample on Amiga"); } } - if (sample->length&1) { + if (sample->samples&1) { considerations=true; ImGui::Text("- sample length will be aligned to the nearest even sample on Amiga"); } - if (sample->length>65535) { + if (sample->samples>65535) { considerations=true; ImGui::Text("- maximum sample length on Sega PCM is 65536 samples"); } - if (sample->length>2097151) { + if (sample->samples>2097151) { considerations=true; ImGui::Text("- maximum sample length on Neo Geo ADPCM is 2097152 samples"); } @@ -1352,6 +1345,10 @@ void FurnaceGUI::drawMixer() { ImGui::SetNextWindowSizeConstraints(ImVec2(400.0f*dpiScale,200.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); if (ImGui::Begin("Mixer",&mixerOpen,settings.allowEditDocking?0:ImGuiWindowFlags_NoDocking)) { char id[32]; + if (ImGui::SliderFloat("Master Volume",&e->song.masterVol,0,3,"%.2fx")) { + if (e->song.masterVol<0) e->song.masterVol=0; + if (e->song.masterVol>3) e->song.masterVol=3; + } for (int i=0; isong.systemLen; i++) { snprintf(id,31,"MixS%d",i); bool doInvert=e->song.systemVol[i]&128; @@ -1527,6 +1524,7 @@ const char* aboutLine[]={ "0x5066", "breakthetargets", "kleeder", + "nicco1690", "NikonTeen", "SuperJet Spade", "TheDuccinator", @@ -1908,12 +1906,12 @@ void FurnaceGUI::drawStats() { } if (!statsOpen) return; if (ImGui::Begin("Statistics",&statsOpen)) { - String adpcmUsage=fmt::sprintf("%d/16384KB",e->adpcmMemLen/1024); + String adpcmAUsage=fmt::sprintf("%d/16384KB",e->adpcmAMemLen/1024); String adpcmBUsage=fmt::sprintf("%d/16384KB",e->adpcmBMemLen/1024); String qsoundUsage=fmt::sprintf("%d/16384KB",e->qsoundMemLen/1024); ImGui::Text("ADPCM-A"); ImGui::SameLine(); - ImGui::ProgressBar(((float)e->adpcmMemLen)/16777216.0f,ImVec2(-FLT_MIN,0),adpcmUsage.c_str()); + ImGui::ProgressBar(((float)e->adpcmAMemLen)/16777216.0f,ImVec2(-FLT_MIN,0),adpcmAUsage.c_str()); ImGui::Text("ADPCM-B"); ImGui::SameLine(); ImGui::ProgressBar(((float)e->adpcmBMemLen)/16777216.0f,ImVec2(-FLT_MIN,0),adpcmBUsage.c_str()); @@ -4550,6 +4548,10 @@ bool FurnaceGUI::loop() { e->setSysFlags(i,(flags&(~3))|2,restart); updateWindowTitle(); } + if (ImGui::RadioButton("Half NTSC (1.79MHz)",(flags&3)==3)) { + e->setSysFlags(i,(flags&(~3))|3,restart); + updateWindowTitle(); + } ImGui::Text("Chip type:"); if (ImGui::RadioButton("Sega VDP/Master System",((flags>>2)&3)==0)) { e->setSysFlags(i,(flags&(~12))|0,restart);