diff --git a/papers/format.md b/papers/format.md index 899fc601a..fa26de4e5 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 @@ -202,6 +204,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 diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 95e139180..d7f52b917 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -444,46 +444,67 @@ void DivEngine::renderSamples() { sPreview.sample=-1; sPreview.pos=0; + // step 1: render samples for (int i=0; irender(); } - /* - // 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); + // step 3: allocate ADPCM-B samples + if (adpcmBMem==NULL) adpcmBMem=new unsigned char[16777216]; memPos=0; for (int i=0; irendLength; - if (length > 65536-16) { - length = 65536-16; + int paddedLen=(s->lengthB+255)&(~0xff); + if (memPos>=16777216) { + logW("out of ADPCM-B memory for sample %d!\n",i); + break; + } + if (memPos+paddedLen>=16777216) { + memcpy(adpcmBMem+memPos,s->dataB,16777216-memPos); + logW("out of ADPCM-B memory for sample %d!\n",i); + } else { + memcpy(adpcmBMem+memPos,s->dataB,paddedLen); + } + s->offB=memPos; + memPos+=paddedLen; + } + adpcmBMemLen=memPos+256; + + // step 4: allocate qsound pcm samples + if (qsoundMem==NULL) qsoundMem=new unsigned char[16777216]; + + memPos=0; + for (int i=0; ilength8; + if (length>65536-16) { + length=65536-16; } if ((memPos&0xff0000)!=((memPos+length)&0xff0000)) { memPos=(memPos+0xffff)&0xff0000; @@ -494,41 +515,18 @@ 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; - - // step 5: allocate ADPCM-B samples - if (adpcmBMem==NULL) adpcmBMem=new unsigned char[16777216]; - - memPos=0; - for (int i=0; i=16777216) { - logW("out of ADPCM-B memory for sample %d!\n",i); - break; - } - if (memPos+s->adpcmBRendLength>=16777216) { - memcpy(adpcmBMem+memPos,s->adpcmBRendData,16777216-memPos); - logW("out of ADPCM-B memory for sample %d!\n",i); - } else { - memcpy(adpcmBMem+memPos,s->adpcmBRendData,s->adpcmBRendLength); - } - s->rendOff=memPos; - memPos+=s->adpcmBRendLength; - } - adpcmBMemLen=memPos+256; - - */ } void DivEngine::createNew() { diff --git a/src/engine/engine.h b/src/engine/engine.h index 96594a3ca..f4324fb6c 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -37,8 +37,8 @@ warnings+=(String("\n")+x); \ } -#define DIV_VERSION "dev58" -#define DIV_ENGINE_VERSION 58 +#define DIV_VERSION "dev59" +#define DIV_ENGINE_VERSION 59 enum DivStatusView { DIV_STATUS_NOTHING=0, @@ -621,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,11 +684,15 @@ class DivEngine { totalProcessed(0), 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 5177916fd..51d2e43d1 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -845,7 +845,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(); @@ -1003,6 +1008,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; iwriteString(song.notes,false); + w->writeF(song.masterVol); + /// INSTRUMENT for (int i=0; i>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 aacadd866..10bb38fec 100644 --- a/src/engine/platform/nes.cpp +++ b/src/engine/platform/nes.cpp @@ -99,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/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/ym2610Interface.cpp b/src/engine/platform/ym2610Interface.cpp index 29a186ee1..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 /*s->dataB[address&0xffffff];*/0; - return parent->adpcmMem[address&0xffffff];/*s->dataA[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/platform/ym2610b.cpp b/src/engine/platform/ym2610b.cpp index bb3559e94..eb854546e 100644 --- a/src/engine/platform/ym2610b.cpp +++ b/src/engine/platform/ym2610b.cpp @@ -237,7 +237,7 @@ void DivPlatformYM2610B::tick() { // FM for (int i=0; i<6; i++) { - if (i==1 && extMode) continue; + if (i==2 && extMode) continue; chan[i].std.next(); if (chan[i].std.hadVol) { @@ -366,7 +366,7 @@ void DivPlatformYM2610B::tick() { } for (int i=0; i<6; i++) { - if (i==1 && extMode) continue; + if (i==2 && extMode) continue; if (chan[i].freqChanged) { chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq)); if (chan[i].freq>262143) chan[i].freq=262143; diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 099daadd6..c07965e58 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -1314,17 +1314,17 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi } for (int i=0; iisStereo()) { for (size_t j=0; j0) { + 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 (writeADPCM && adpcmBMemLen>0) { + w->writeC(0x67); + w->writeC(0x66); + w->writeC(0x83); + w->writeI(adpcmBMemLen+8); + w->writeI(adpcmBMemLen); + w->writeI(0); + w->write(adpcmBMem,adpcmBMemLen); } if (writeQSound && qsoundMemLen>0) { diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index f0a9bc7ec..f1ae553b6 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1345,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; @@ -1898,12 +1902,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()); @@ -4542,6 +4546,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);