diff --git a/CMakeLists.txt b/CMakeLists.txt index 9095a2a1..5d193315 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -89,10 +89,13 @@ src/engine/platform/ym2610Interface.cpp src/engine/blip_buf.c src/engine/safeReader.cpp +src/engine/safeWriter.cpp src/engine/engine.cpp src/engine/macroInt.cpp src/engine/pattern.cpp src/engine/playback.cpp +src/engine/sample.cpp +src/engine/song.cpp src/engine/platform/abstract.cpp src/engine/platform/genesis.cpp src/engine/platform/genesisext.cpp diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index f5cea28a..30095697 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -805,6 +805,7 @@ bool DivEngine::load(void* f, size_t slen) { } } + song.unload(); song=ds; chans=getChannelCount(song.system); renderSamples(); @@ -815,166 +816,162 @@ bool DivEngine::load(void* f, size_t slen) { return true; } -#define ERR_CHECK(x) x; if (ferror(f)) return false; - -bool DivEngine::save(FILE* f) { +SafeWriter* DivEngine::save() { + SafeWriter* w=new SafeWriter; + w->init(); // write magic - ERR_CHECK(fwrite(DIV_DMF_MAGIC,1,16,f)); + w->write(DIV_DMF_MAGIC,16); // version - ERR_CHECK(fputc(24,f)); - ERR_CHECK(fputc(systemToFile(song.system),f)); + w->writeC(24); + w->writeC(systemToFile(song.system)); // song info - ERR_CHECK(fputc(song.name.size(),f)); - ERR_CHECK(fwrite(song.name.c_str(),1,song.name.size(),f)); - ERR_CHECK(fputc(song.author.size(),f)); - ERR_CHECK(fwrite(song.author.c_str(),1,song.author.size(),f)); - ERR_CHECK(fputc(song.hilightA,f)); - ERR_CHECK(fputc(song.hilightB,f)); + w->writeString(song.name,true); + w->writeString(song.author,true); + w->writeC(song.hilightA); + w->writeC(song.hilightB); - ERR_CHECK(fputc(song.timeBase,f)); - ERR_CHECK(fputc(song.speed1,f)); - ERR_CHECK(fputc(song.speed2,f)); - ERR_CHECK(fputc(song.pal,f)); - ERR_CHECK(fputc(song.customTempo,f)); + w->writeC(song.timeBase); + w->writeC(song.speed1); + w->writeC(song.speed2); + w->writeC(song.pal); + w->writeC(song.customTempo); char customHz[4]; memset(customHz,0,4); snprintf(customHz,4,"%d",song.hz); - ERR_CHECK(fwrite(customHz,1,3,f)); - ERR_CHECK(fwrite(&song.patLen,4,1,f)); - ERR_CHECK(fputc(song.ordersLen,f)); + w->write(customHz,3); + w->writeI(song.patLen); + w->writeC(song.ordersLen); for (int i=0; iwriteC(song.orders.ord[i][j]); } } - ERR_CHECK(fputc(song.ins.size(),f)); + w->writeC(song.ins.size()); for (DivInstrument* i: song.ins) { - ERR_CHECK(fputc(i->name.size(),f)); - ERR_CHECK(fwrite(i->name.c_str(),1,i->name.size(),f)); - ERR_CHECK(fputc(i->mode,f)); + w->writeString(i->name,true); + w->writeC(i->mode); if (i->mode) { // FM - ERR_CHECK(fputc(i->fm.alg,f)); - ERR_CHECK(fputc(i->fm.fb,f)); - ERR_CHECK(fputc(i->fm.fms,f)); - ERR_CHECK(fputc(i->fm.ams,f)); + w->writeC(i->fm.alg); + w->writeC(i->fm.fb); + w->writeC(i->fm.fms); + w->writeC(i->fm.ams); for (int j=0; j<4; j++) { DivInstrumentFM::Operator& op=i->fm.op[j]; - ERR_CHECK(fputc(op.am,f)); - ERR_CHECK(fputc(op.ar,f)); - ERR_CHECK(fputc(op.dr,f)); - ERR_CHECK(fputc(op.mult,f)); - ERR_CHECK(fputc(op.rr,f)); - ERR_CHECK(fputc(op.sl,f)); - ERR_CHECK(fputc(op.tl,f)); - ERR_CHECK(fputc(op.dt2,f)); - ERR_CHECK(fputc(op.rs,f)); - ERR_CHECK(fputc(op.dt,f)); - ERR_CHECK(fputc(op.d2r,f)); - ERR_CHECK(fputc(op.ssgEnv,f)); + w->writeC(op.am); + w->writeC(op.ar); + w->writeC(op.dr); + w->writeC(op.mult); + w->writeC(op.rr); + w->writeC(op.sl); + w->writeC(op.tl); + w->writeC(op.dt2); + w->writeC(op.rs); + w->writeC(op.dt); + w->writeC(op.d2r); + w->writeC(op.ssgEnv); } } else { // STD if (song.system!=DIV_SYSTEM_GB) { - ERR_CHECK(fputc(i->std.volMacroLen,f)); - ERR_CHECK(fwrite(i->std.volMacro,4,i->std.volMacroLen,f)); + w->writeC(i->std.volMacroLen); + w->write(i->std.volMacro,4*i->std.volMacroLen); if (i->std.volMacroLen>0) { - ERR_CHECK(fputc(i->std.volMacroLoop,f)); + w->writeC(i->std.volMacroLoop); } } - ERR_CHECK(fputc(i->std.arpMacroLen,f)); - ERR_CHECK(fwrite(i->std.arpMacro,4,i->std.arpMacroLen,f)); + w->writeC(i->std.arpMacroLen); + w->write(i->std.arpMacro,4*i->std.arpMacroLen); if (i->std.arpMacroLen>0) { - ERR_CHECK(fputc(i->std.arpMacroLoop,f)); + w->writeC(i->std.arpMacroLoop); } - ERR_CHECK(fputc(i->std.arpMacroMode,f)); + w->writeC(i->std.arpMacroMode); - ERR_CHECK(fputc(i->std.dutyMacroLen,f)); - ERR_CHECK(fwrite(i->std.dutyMacro,4,i->std.dutyMacroLen,f)); + w->writeC(i->std.dutyMacroLen); + w->write(i->std.dutyMacro,4*i->std.dutyMacroLen); if (i->std.dutyMacroLen>0) { - ERR_CHECK(fputc(i->std.dutyMacroLoop,f)); + w->writeC(i->std.dutyMacroLoop); } - ERR_CHECK(fputc(i->std.waveMacroLen,f)); - ERR_CHECK(fwrite(i->std.waveMacro,4,i->std.waveMacroLen,f)); + w->writeC(i->std.waveMacroLen); + w->write(i->std.waveMacro,4*i->std.waveMacroLen); if (i->std.waveMacroLen>0) { - ERR_CHECK(fputc(i->std.waveMacroLoop,f)); + w->writeC(i->std.waveMacroLoop); } if (song.system==DIV_SYSTEM_C64_6581 || song.system==DIV_SYSTEM_C64_8580) { - ERR_CHECK(fputc(i->c64.triOn,f)); - ERR_CHECK(fputc(i->c64.sawOn,f)); - ERR_CHECK(fputc(i->c64.pulseOn,f)); - ERR_CHECK(fputc(i->c64.noiseOn,f)); + w->writeC(i->c64.triOn); + w->writeC(i->c64.sawOn); + w->writeC(i->c64.pulseOn); + w->writeC(i->c64.noiseOn); - ERR_CHECK(fputc(i->c64.a,f)); - ERR_CHECK(fputc(i->c64.d,f)); - ERR_CHECK(fputc(i->c64.s,f)); - ERR_CHECK(fputc(i->c64.r,f)); + w->writeC(i->c64.a); + w->writeC(i->c64.d); + w->writeC(i->c64.s); + w->writeC(i->c64.r); - ERR_CHECK(fputc(i->c64.duty,f)); + w->writeC(i->c64.duty); - ERR_CHECK(fputc(i->c64.ringMod,f)); - ERR_CHECK(fputc(i->c64.oscSync,f)); + w->writeC(i->c64.ringMod); + w->writeC(i->c64.oscSync); - ERR_CHECK(fputc(i->c64.toFilter,f)); - ERR_CHECK(fputc(i->c64.volIsCutoff,f)); - ERR_CHECK(fputc(i->c64.initFilter,f)); + w->writeC(i->c64.toFilter); + w->writeC(i->c64.volIsCutoff); + w->writeC(i->c64.initFilter); - ERR_CHECK(fputc(i->c64.res,f)); - ERR_CHECK(fputc(i->c64.cut,f)); - ERR_CHECK(fputc(i->c64.hp,f)); - ERR_CHECK(fputc(i->c64.bp,f)); - ERR_CHECK(fputc(i->c64.lp,f)); - ERR_CHECK(fputc(i->c64.ch3off,f)); + w->writeC(i->c64.res); + w->writeC(i->c64.cut); + w->writeC(i->c64.hp); + w->writeC(i->c64.bp); + w->writeC(i->c64.lp); + w->writeC(i->c64.ch3off); } if (song.system==DIV_SYSTEM_GB) { - ERR_CHECK(fputc(i->gb.envVol,f)); - ERR_CHECK(fputc(i->gb.envDir,f)); - ERR_CHECK(fputc(i->gb.envLen,f)); - ERR_CHECK(fputc(i->gb.soundLen,f)); + w->writeC(i->gb.envVol); + w->writeC(i->gb.envDir); + w->writeC(i->gb.envLen); + w->writeC(i->gb.soundLen); } } } - ERR_CHECK(fputc(song.wave.size(),f)); + w->writeC(song.wave.size()); for (DivWavetable* i: song.wave) { - ERR_CHECK(fwrite(&i->len,4,1,f)); - ERR_CHECK(fwrite(&i->data,4,i->len,f)); + w->writeI(i->len); + w->write(i->data,4*i->len); } for (int i=0; iwriteC(song.pat[i].effectRows); for (int j=0; jdata[k][0],2,1,f)); // note - ERR_CHECK(fwrite(&pat->data[k][1],2,1,f)); // octave - ERR_CHECK(fwrite(&pat->data[k][3],2,1,f)); // volume - ERR_CHECK(fwrite(&pat->data[k][4],2,song.pat[i].effectRows*2,f)); // volume - ERR_CHECK(fwrite(&pat->data[k][2],2,1,f)); // instrument + w->writeS(pat->data[k][0]); // note + w->writeS(pat->data[k][1]); // octave + w->writeS(pat->data[k][3]); // volume + w->write(&pat->data[k][4],2*song.pat[i].effectRows*2); // effects + w->writeS(pat->data[k][2]); // instrument } } } - ERR_CHECK(fputc(song.sample.size(),f)); + w->writeC(song.sample.size()); for (DivSample* i: song.sample) { - ERR_CHECK(fwrite(&i->length,4,1,f)); - ERR_CHECK(fputc(i->name.size(),f)); - ERR_CHECK(fwrite(i->name.c_str(),1,i->name.size(),f)); - ERR_CHECK(fputc(i->rate,f)); - ERR_CHECK(fputc(i->pitch,f)); - ERR_CHECK(fputc(i->vol,f)); - ERR_CHECK(fputc(i->depth,f)); - ERR_CHECK(fwrite(i->data,2,i->length,f)); + w->writeI(i->length); + w->writeString(i->name,true); + w->writeC(i->rate); + w->writeC(i->pitch); + w->writeC(i->vol); + w->writeC(i->depth); + w->write(i->data,2*i->length); } - return true; + return w; } // ADPCM code attribution: https://wiki.neogeodev.org/index.php?title=ADPCM_codecs diff --git a/src/engine/engine.h b/src/engine/engine.h index 038338d0..a08a675e 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -2,6 +2,7 @@ #define _ENGINE_H #include "song.h" #include "dispatch.h" +#include "safeWriter.h" #include "../audio/taAudio.h" #include "blip_buf.h" #include @@ -104,7 +105,7 @@ class DivEngine { // load a .dmf. bool load(void* f, size_t length); // save as .dmf. - bool save(FILE* f); + SafeWriter* save(); // play void play(); diff --git a/src/engine/safeWriter.cpp b/src/engine/safeWriter.cpp new file mode 100644 index 00000000..643e3a45 --- /dev/null +++ b/src/engine/safeWriter.cpp @@ -0,0 +1,100 @@ +#include "safeWriter.h" + +#define WRITER_BUF_SIZE 16384 + +unsigned char* SafeWriter::getFinalBuf() { + return buf; +} + +void SafeWriter::checkSize(size_t amount) { + if (curSeek+amount>=bufLen) { + unsigned char* newBuf=new unsigned char[bufLen+WRITER_BUF_SIZE]; + memcpy(newBuf,buf,bufLen); + delete[] buf; + buf=newBuf; + bufLen+=WRITER_BUF_SIZE; + } +} + +bool SafeWriter::seek(ssize_t where, int whence) { + ssize_t supposed; + switch (whence) { + case SEEK_SET: + supposed=where; + break; + case SEEK_CUR: + supposed=curSeek+where; + break; + case SEEK_END: + supposed=len+where; + break; + default: + return false; + } + if (supposed<0) supposed=0; + if (supposed>(ssize_t)len) supposed=len; + curSeek=supposed; + return true; +} + +size_t SafeWriter::tell() { + return curSeek; +} + +size_t SafeWriter::size() { + return len; +} + +int SafeWriter::write(const void* what, size_t count) { + if (!operative) return 0; + checkSize(count); + memcpy(buf+curSeek,what,count); + curSeek+=count; + if (curSeek>len) len=curSeek; + return count; +} + +int SafeWriter::writeC(signed char val) { + return write(&val,1); +} + +int SafeWriter::writeS(short val) { + return write(&val,2); +} + +int SafeWriter::writeI(int val) { + return write(&val,4); +} +int SafeWriter::writeL(int64_t val) { + return write(&val,8); +} +int SafeWriter::writeF(float val) { + return write(&val,4); +} +int SafeWriter::writeD(double val) { + return write(&val,8); +} +int SafeWriter::writeString(String val, bool pascal) { + if (pascal) { + writeC((unsigned char)val.size()); + return write(val.c_str(),val.size())+1; + } else { + return write(val.c_str(),val.size()+1); + } +} + +void SafeWriter::init() { + if (operative) return; + buf=new unsigned char[WRITER_BUF_SIZE]; + bufLen=WRITER_BUF_SIZE; + len=0; + curSeek=0; + operative=true; +} + +void SafeWriter::finish() { + if (!operative) return; + delete[] buf; + buf=NULL; + operative=false; +} diff --git a/src/engine/safeWriter.h b/src/engine/safeWriter.h new file mode 100644 index 00000000..1056d500 --- /dev/null +++ b/src/engine/safeWriter.h @@ -0,0 +1,51 @@ +#ifndef _SAFEWRITER_H +#define _SAFEWRITER_H +#include +#include +#include +#include "../ta-utils.h" + +class SafeWriter { + bool operative; + unsigned char* buf; + size_t bufLen; + size_t len; + + size_t curSeek; + + void checkSize(size_t amount); + + public: + unsigned char* getFinalBuf(); + + bool seek(ssize_t where, int whence); + size_t tell(); + size_t size(); + + int write(const void* what, size_t count); + + int writeC(signed char val); + int writeS(short val); + int writeS_BE(short val); + int writeI(int val); + int writeI_BE(int val); + int writeL(int64_t val); + int writeL_BE(int64_t val); + int writeF(float val); + int writeF_BE(float val); + int writeD(double val); + int writeD_BE(double val); + int writeString(String val, bool pascal); + + void init(); + void finish(); + + SafeWriter(): + operative(false), + buf(NULL), + bufLen(0), + len(0), + curSeek(0) {} +}; + +#endif diff --git a/src/engine/sample.cpp b/src/engine/sample.cpp new file mode 100644 index 00000000..a07068ee --- /dev/null +++ b/src/engine/sample.cpp @@ -0,0 +1,7 @@ +#include "sample.h" + +DivSample::~DivSample() { + if (data) delete data; + if (rendData) delete rendData; + if (adpcmRendData) delete adpcmRendData; +} diff --git a/src/engine/sample.h b/src/engine/sample.h index b899df09..e4923fd6 100644 --- a/src/engine/sample.h +++ b/src/engine/sample.h @@ -1,3 +1,5 @@ +#include "../ta-utils.h" + struct DivSample { String name; int length, rate; @@ -21,4 +23,5 @@ struct DivSample { rendOff(0), rendData(NULL), adpcmRendData(NULL) {} + ~DivSample(); }; diff --git a/src/engine/song.cpp b/src/engine/song.cpp new file mode 100644 index 00000000..9e49d729 --- /dev/null +++ b/src/engine/song.cpp @@ -0,0 +1,18 @@ +#include "song.h" + +void DivSong::unload() { + for (DivInstrument* i: ins) { + delete i; + } + ins.clear(); + + for (DivWavetable* i: wave) { + delete i; + } + wave.clear(); + + for (DivSample* i: sample) { + delete i; + } + sample.clear(); +} diff --git a/src/engine/song.h b/src/engine/song.h index 76b3a7d0..98392f0a 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -98,6 +98,8 @@ struct DivSong { DivInstrument nullIns; DivWavetable nullWave; + void unload(); + DivSong(): version(24), system(DIV_SYSTEM_GENESIS), diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 266f7542..f358954a 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -9,6 +9,7 @@ #include "imgui_internal.h" #include "ImGuiFileDialog.h" #include "misc/cpp/imgui_stdlib.h" +#include #include #include @@ -870,6 +871,81 @@ void FurnaceGUI::keyUp(SDL_Event& ev) { } +#define FURNACE_ZLIB_COMPRESS + +int FurnaceGUI::save(String path) { + FILE* outFile=fopen(path.c_str(),"wb"); + if (outFile==NULL) { + return 1; + } + SafeWriter* w=e->save(); +#ifdef FURNACE_ZLIB_COMPRESS + unsigned char zbuf[131072]; + int ret; + z_stream zl; + memset(&zl,0,sizeof(z_stream)); + ret=deflateInit(&zl,Z_DEFAULT_COMPRESSION); + if (ret!=Z_OK) { + logE("zlib error!\n"); + fclose(outFile); + w->finish(); + return 2; + } + zl.avail_in=w->size(); + zl.next_in=w->getFinalBuf(); + while (zl.avail_in>0) { + zl.avail_out=131072; + zl.next_out=zbuf; + if ((ret=deflate(&zl,Z_NO_FLUSH))==Z_STREAM_ERROR) { + logE("zlib stream error!\n"); + deflateEnd(&zl); + fclose(outFile); + w->finish(); + return 2; + } + size_t amount=131072-zl.avail_out; + if (amount>0) { + if (fwrite(zbuf,1,amount,outFile)!=amount) { + logE("did not write entirely: %s!\n",strerror(errno)); + deflateEnd(&zl); + fclose(outFile); + w->finish(); + return 1; + } + } + } + zl.avail_out=131072; + zl.next_out=zbuf; + if ((ret=deflate(&zl,Z_FINISH))==Z_STREAM_ERROR) { + logE("zlib finish stream error!\n"); + deflateEnd(&zl); + fclose(outFile); + w->finish(); + return 2; + } + if (131072-zl.avail_out>0) { + if (fwrite(zbuf,1,131072-zl.avail_out,outFile)!=(131072-zl.avail_out)) { + logE("did not write entirely: %s!\n",strerror(errno)); + deflateEnd(&zl); + fclose(outFile); + w->finish(); + return 1; + } + } + deflateEnd(&zl); +#else + if (fwrite(w->getFinalBuf(),1,w->size(),outFile)!=w->size()) { + logE("did not write entirely: %s!\n",strerror(errno)); + fclose(outFile); + w->finish(); + return 1; + } +#endif + fclose(outFile); + w->finish(); + return 0; +} + int FurnaceGUI::load(String path) { if (!path.empty()) { logI("loading module...\n"); @@ -989,10 +1065,14 @@ bool FurnaceGUI::loop() { ImGui::MenuItem("new"); if (ImGui::MenuItem("open...")) { ImGuiFileDialog::Instance()->OpenDialog("FileDialog","Open File","DefleMask module{.dmf},.*",workingDir); + isSaving=false; } ImGui::Separator(); ImGui::MenuItem("save"); - ImGui::MenuItem("save as..."); + if (ImGui::MenuItem("save as...")) { + ImGuiFileDialog::Instance()->OpenDialog("FileDialog","Save File","DefleMask module{.dmf}",workingDir); + isSaving=true; + } ImGui::Separator(); if (ImGui::MenuItem("exit")) { quit=true; @@ -1029,7 +1109,7 @@ bool FurnaceGUI::loop() { String copyOfName=fileName; if (isSaving) { printf("saving: %s\n",copyOfName.c_str()); - //SaveFile(copyOfName.c_str()); + save(copyOfName); isSaving=false; } else { load(copyOfName); diff --git a/src/gui/gui.h b/src/gui/gui.h index 17d38058..7869eadc 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -111,6 +111,7 @@ class FurnaceGUI { void keyDown(SDL_Event& ev); void keyUp(SDL_Event& ev); + int save(String path); int load(String path); public: diff --git a/src/main.cpp b/src/main.cpp index 66290e61..b0cb5a7d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -283,13 +283,6 @@ int main(int argc, char** argv) { return 1; } } - /*FILE* outFile=fopen("testout.dmf","wb"); - if (outFile!=NULL) { - if (!e.save(outFile)) { - logE("could not save file!\n"); - } - fclose(outFile); - }*/ if (!e.init(outName)) { logE("could not initialize engine!\n"); return 1;