diff --git a/demos/a2600/TIADeepIntoCode.fur b/demos/a2600/TIADeepIntoCode.fur new file mode 100644 index 000000000..0a952a9c9 Binary files /dev/null and b/demos/a2600/TIADeepIntoCode.fur differ diff --git a/demos/a2600/Watch My Tone.fur b/demos/a2600/Watch My Tone.fur new file mode 100644 index 000000000..8bfe4df17 Binary files /dev/null and b/demos/a2600/Watch My Tone.fur differ diff --git a/demos/genesis/imaginarium.fur b/demos/genesis/imaginarium.fur new file mode 100644 index 000000000..842ec8575 Binary files /dev/null and b/demos/genesis/imaginarium.fur differ diff --git a/demos/opl/I won't be there again (Extended Mix).fur b/demos/opl/I won't be there again (Extended Mix).fur new file mode 100644 index 000000000..81e6621db Binary files /dev/null and b/demos/opl/I won't be there again (Extended Mix).fur differ diff --git a/src/engine/engine.h b/src/engine/engine.h index 6c2d67dda..a97957d0f 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -697,9 +697,8 @@ class DivEngine { // save as .fur. // if notPrimary is true then the song will not be altered SafeWriter* saveFur(bool notPrimary=false, bool newPatternFormat=true); - // build a ROM file (TODO). - // specify system to build ROM for. - std::vector buildROM(DivROMExportOptions sys); + // return a ROM exporter. + DivROMExport* buildROM(DivROMExportOptions sys); // dump to VGM. // set trailingTicks to: // - 0 to add one tick of trailing diff --git a/src/engine/export.cpp b/src/engine/export.cpp index 2ea35327f..68794069e 100644 --- a/src/engine/export.cpp +++ b/src/engine/export.cpp @@ -21,7 +21,7 @@ #include "export/amigaValidation.h" -std::vector DivEngine::buildROM(DivROMExportOptions sys) { +DivROMExport* DivEngine::buildROM(DivROMExportOptions sys) { DivROMExport* exporter=NULL; switch (sys) { case DIV_ROM_AMIGA_VALIDATION: @@ -31,7 +31,5 @@ std::vector DivEngine::buildROM(DivROMExportOptions sys) { exporter=new DivROMExport; break; } - std::vector ret=exporter->go(this); - delete exporter; - return ret; + return exporter; } diff --git a/src/engine/export.h b/src/engine/export.h index d8e1878f1..9445ecfe9 100644 --- a/src/engine/export.h +++ b/src/engine/export.h @@ -45,9 +45,25 @@ struct DivROMExportOutput { data(NULL) {} }; +struct DivROMExportProgress { + String name; + float amount; +}; + class DivROMExport { + protected: + std::vector exportLog; + std::vector output; + std::mutex logLock; + void logAppend(String what); public: - virtual std::vector go(DivEngine* e); + virtual bool go(DivEngine* eng); + virtual void abort(); + virtual void wait(); + std::vector& getResult(); + virtual bool hasFailed(); + virtual bool isRunning(); + virtual DivROMExportProgress getProgress(); virtual ~DivROMExport() {} }; diff --git a/src/engine/export/abstract.cpp b/src/engine/export/abstract.cpp index bd39e3aa0..c33c38502 100644 --- a/src/engine/export/abstract.cpp +++ b/src/engine/export/abstract.cpp @@ -20,7 +20,38 @@ #include "../export.h" #include "../../ta-log.h" -std::vector DivROMExport::go(DivEngine* e) { +bool DivROMExport::go(DivEngine* eng) { logW("what's this? the null ROM export?"); - return std::vector(); + return false; +} + +void DivROMExport::abort() { +} + +std::vector& DivROMExport::getResult() { + return output; +} + +bool DivROMExport::hasFailed() { + return true; +} + +DivROMExportProgress DivROMExport::getProgress() { + DivROMExportProgress ret; + ret.name="Test"; + ret.amount=0.0f; + return ret; +} + +void DivROMExport::logAppend(String what) { + logLock.lock(); + exportLog.push_back(what); + logLock.unlock(); +} + +void DivROMExport::wait() { +} + +bool DivROMExport::isRunning() { + return false; } diff --git a/src/engine/export/amigaValidation.cpp b/src/engine/export/amigaValidation.cpp index a613ee422..e9a8a8b84 100644 --- a/src/engine/export/amigaValidation.cpp +++ b/src/engine/export/amigaValidation.cpp @@ -40,8 +40,7 @@ struct SampleBookEntry { len(0) {} }; -std::vector DivExportAmigaValidation::go(DivEngine* e) { - std::vector ret; +void DivExportAmigaValidation::run() { std::vector waves; std::vector sampleBook; unsigned int wavesDataPtr=0; @@ -266,12 +265,34 @@ std::vector DivExportAmigaValidation::go(DivEngine* e) { } // finish - ret.reserve(5); - ret.push_back(DivROMExportOutput("sbook.bin",sbook)); - ret.push_back(DivROMExportOutput("wbook.bin",wbook)); - ret.push_back(DivROMExportOutput("sample.bin",sample)); - ret.push_back(DivROMExportOutput("wave.bin",wave)); - ret.push_back(DivROMExportOutput("seq.bin",seq)); + output.reserve(5); + output.push_back(DivROMExportOutput("sbook.bin",sbook)); + output.push_back(DivROMExportOutput("wbook.bin",wbook)); + output.push_back(DivROMExportOutput("sample.bin",sample)); + output.push_back(DivROMExportOutput("wave.bin",wave)); + output.push_back(DivROMExportOutput("seq.bin",seq)); - return ret; + running=false; +} + +bool DivExportAmigaValidation::go(DivEngine* eng) { + e=eng; + running=true; + exportThread=new std::thread(&DivExportAmigaValidation::run,this); + return true; +} + +void DivExportAmigaValidation::wait() { + if (exportThread!=NULL) { + exportThread->join(); + delete exportThread; + } +} + +void DivExportAmigaValidation::abort() { + wait(); +} + +bool DivExportAmigaValidation::isRunning() { + return running; } diff --git a/src/engine/export/amigaValidation.h b/src/engine/export/amigaValidation.h index 11a4ecd48..42703a36e 100644 --- a/src/engine/export/amigaValidation.h +++ b/src/engine/export/amigaValidation.h @@ -19,8 +19,17 @@ #include "../export.h" +#include + class DivExportAmigaValidation: public DivROMExport { + DivEngine* e; + std::thread* exportThread; + bool running; + void run(); public: - std::vector go(DivEngine* e); + bool go(DivEngine* e); + bool isRunning(); + void abort(); + void wait(); ~DivExportAmigaValidation() {} }; diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp index 57860b97a..8c648612a 100644 --- a/src/engine/platform/gb.cpp +++ b/src/engine/platform/gb.cpp @@ -651,7 +651,7 @@ void DivPlatformGB::reset() { immWrite(0x26,0x8f); lastPan=0xff; immWrite(0x25,procMute()); - immWrite(0x24,0x77); + immWrite(0x24,0xff); antiClickPeriodCount=0; antiClickWavePos=0; diff --git a/src/engine/tiunaOps.cpp b/src/engine/tiunaOps.cpp index 942822cdb..8a045cdf8 100644 --- a/src/engine/tiunaOps.cpp +++ b/src/engine/tiunaOps.cpp @@ -26,53 +26,107 @@ #include "../ta-log.h" struct TiunaNew { - short pitch=-1; - signed char ins=-1; - signed char vol=-1; - short sync=-1; + short pitch; + signed char ins; + signed char vol; + short sync; + TiunaNew(): + pitch(-1), + ins(-1), + vol(-1), + sync(-1) {} }; + struct TiunaLast { - short pitch=0; - signed char ins=0; - signed char vol=0; - int tick=1; - bool forcePitch=true; + short pitch; + signed char ins; + signed char vol; + int tick; + bool forcePitch; + TiunaLast(): + pitch(0), + ins(0), + vol(0), + tick(1), + forcePitch(true) {} }; + struct TiunaCmd { - signed char pitchChange=-1; - short pitchSet=-1; - signed char ins=-1; - signed char vol=-1; - short sync=-1; - short wait=-1; + signed char pitchChange; + short pitchSet; + signed char ins; + signed char vol; + short sync; + short wait; + TiunaCmd(): + pitchChange(-1), + pitchSet(-1), + ins(-1), + vol(-1), + sync(-1), + wait(-1) {} }; + struct TiunaBytes { - unsigned char ch=0; - int ticks=0; - unsigned char size=0; + unsigned char ch; + int ticks; + unsigned char size; unsigned char buf[16]; friend bool operator==(const TiunaBytes& l, const TiunaBytes& r) { if (l.size!=r.size) return false; if (l.ticks!=r.ticks) return false; return memcmp(l.buf,r.buf,l.size)==0; } + TiunaBytes(unsigned char c, int t, unsigned char s, std::initializer_list b): + ch(c), + ticks(t), + size(s) { + // because C++14 does not support data() on initializer_list + unsigned char p=0; + for (unsigned char i: b) { + buf[p++]=i; + } + } + TiunaBytes(): + ch(0), + ticks(0), + size(0) { + memset(buf,0,16); + } }; + struct TiunaMatch { int pos; int endPos; int size; int id; + TiunaMatch(int p, int ep, int s, int _i): + pos(p), + endPos(ep), + size(s), + id(_i) {} + TiunaMatch(): + pos(0), + endPos(0), + size(0), + id(0) {} }; + struct TiunaMatches { - int bytesSaved=INT32_MIN; - int length=0; - int ticks=0; + int bytesSaved; + int length; + int ticks; std::vector pos; + TiunaMatches(): + bytesSaved(INT32_MIN), + length(0), + ticks(0) {} }; static void writeCmd(std::vector& cmds, TiunaCmd& cmd, unsigned char ch, int& lastWait, int fromTick, int toTick) { while (fromTick0); if (lastWait!=val) { cmd.wait=val; lastWait=val; @@ -293,9 +347,13 @@ SafeWriter* DivEngine::saveTiuna(const bool* sysToExport, const char* baseLabel, std::vector callTicks; int cmId=0; int cmdSize=renderedCmds.size(); - std::vector processed=std::vector(cmdSize,false); + bool* processed=new bool[cmdSize]; + memset(processed,0,cmdSize*sizeof(bool)); + logI("max cmId: %d",(MAX(firstBankSize/1024,1))*256); while (firstBankSize>768 && cmId<(MAX(firstBankSize/1024,1))*256) { + logI("start CM %04x...",cmId); std::map potentialMatches; + logD("scan %d size...",cmdSize-1); for (int i=0; i match; int ch=renderedCmds[i].ch; for (int j=i+1; j=cmdSize) break; int k=0; int ticks=0; @@ -322,7 +381,7 @@ SafeWriter* DivEngine::saveTiuna(const bool* sysToExport, const char* baseLabel, size+=renderedCmds[i+k].size; k++; } - if (size>2) match.push_back({j,j+k,size,0}); + if (size>2) match.push_back(TiunaMatch(j,j+k,size,0)); if (k==0) k++; j+=k; } @@ -367,9 +426,13 @@ SafeWriter* DivEngine::saveTiuna(const bool* sysToExport, const char* baseLabel, } i++; } - if (potentialMatches.empty()) break; + if (potentialMatches.empty()) { + logV("potentialMatches is empty"); + break; + } int maxPMIdx=0; int maxPMVal=0; + logV("looking through potentialMatches..."); for (const auto& i: potentialMatches) { if (i.second.bytesSaved>maxPMVal) { maxPMVal=i.second.bytesSaved; @@ -377,14 +440,17 @@ SafeWriter* DivEngine::saveTiuna(const bool* sysToExport, const char* baseLabel, } } int maxPMLen=potentialMatches[maxPMIdx].length; + logV("the other step..."); for (const int i: potentialMatches[maxPMIdx].pos) { confirmedMatches.push_back({i,i+maxPMLen,0,cmId}); - std::fill(processed.begin()+i,processed.begin()+(i+maxPMLen),true); + memset(processed+i,1,maxPMLen); + //std::fill(processed.begin()+i,processed.begin()+(i+maxPMLen),true); } callTicks.push_back(potentialMatches[maxPMIdx].ticks); logI("CM %04x added: pos=%d,len=%d,matches=%d,saved=%d",cmId,maxPMIdx,maxPMLen,potentialMatches[maxPMIdx].pos.size(),maxPMVal); cmId++; } + delete[] processed; std::sort(confirmedMatches.begin(),confirmedMatches.end(),[](const TiunaMatch& l, const TiunaMatch& r){ return l.posid]) { unsigned char idLo=cmIter->id&0xff; unsigned char idHi=cmIter->id>>8; - cmd={cmd.ch,0,2,{idHi,idLo}}; + cmd=TiunaBytes(cmd.ch,0,2,{idHi,idLo}); i=cmIter->endPos-1; } else { writeCall=cmIter->id; @@ -508,11 +574,11 @@ SafeWriter* DivEngine::saveTiuna(const bool* sysToExport, const char* baseLabel, totalSize++; logI("total size: %d bytes (%d banks)",totalSize,curBank+1); - FILE* f=ps_fopen("confirmedMatches.txt","wb"); - if (f!=NULL) { - fwrite(dbg.getFinalBuf(),1,dbg.size(),f); - fclose(f); - } + //FILE* f=ps_fopen("confirmedMatches.txt","wb"); + //if (f!=NULL) { + // fwrite(dbg.getFinalBuf(),1,dbg.size(),f); + // fclose(f); + //} return w; } diff --git a/src/gui/editControls.cpp b/src/gui/editControls.cpp index 5527f5598..3eba0298c 100644 --- a/src/gui/editControls.cpp +++ b/src/gui/editControls.cpp @@ -619,41 +619,6 @@ void FurnaceGUI::drawMobileControls() { if (ImGui::Button(_("Switch to Desktop Mode"))) { toggleMobileUI(!mobileUI); } - - int numAmiga=0; - for (int i=0; isong.systemLen; i++) { - if (e->song.system[i]==DIV_SYSTEM_AMIGA) numAmiga++; - } - - if (numAmiga) { - ImGui::Text(_( - "this is NOT ROM export! only use for making sure the\n" - "Furnace Amiga emulator is working properly by\n" - "comparing it with real Amiga output." - )); - ImGui::AlignTextToFramePadding(); - ImGui::Text(_("Directory")); - ImGui::SameLine(); - ImGui::InputText("##AVDPath",&workingDirROMExport); - if (ImGui::Button(_("Bake Data"))) { - std::vector out=e->buildROM(DIV_ROM_AMIGA_VALIDATION); - if (workingDirROMExport.size()>0) { - if (workingDirROMExport[workingDirROMExport.size()-1]!=DIR_SEPARATOR) workingDirROMExport+=DIR_SEPARATOR_STR; - } - for (DivROMExportOutput& i: out) { - String path=workingDirROMExport+i.name; - FILE* outFile=ps_fopen(path.c_str(),"wb"); - if (outFile!=NULL) { - fwrite(i.data->getFinalBuf(),1,i.data->size(),outFile); - fclose(outFile); - } - i.data->finish(); - delete i.data; - } - showError(fmt::sprintf(_("Done! Baked %d files."),(int)out.size())); - } - } - break; } } diff --git a/src/gui/exportOptions.cpp b/src/gui/exportOptions.cpp index 19077c13c..4b4360839 100644 --- a/src/gui/exportOptions.cpp +++ b/src/gui/exportOptions.cpp @@ -329,21 +329,31 @@ void FurnaceGUI::drawExportAmigaVal(bool onWindow) { ImGui::SameLine(); } if (ImGui::Button(_("Bake Data"),ImVec2(200.0f*dpiScale,0))) { - std::vector out=e->buildROM(DIV_ROM_AMIGA_VALIDATION); - if (workingDirROMExport.size()>0) { - if (workingDirROMExport[workingDirROMExport.size()-1]!=DIR_SEPARATOR) workingDirROMExport+=DIR_SEPARATOR_STR; - } - for (DivROMExportOutput& i: out) { - String path=workingDirROMExport+i.name; - FILE* outFile=ps_fopen(path.c_str(),"wb"); - if (outFile!=NULL) { - fwrite(i.data->getFinalBuf(),1,i.data->size(),outFile); - fclose(outFile); + DivROMExport* ex=e->buildROM(DIV_ROM_AMIGA_VALIDATION); + if (ex->go(e)) { + ex->wait(); + if (ex->hasFailed()) { + showError("error!"); + } else { + if (workingDirROMExport.size()>0) { + if (workingDirROMExport[workingDirROMExport.size()-1]!=DIR_SEPARATOR) workingDirROMExport+=DIR_SEPARATOR_STR; + } + for (DivROMExportOutput& i: ex->getResult()) { + String path=workingDirROMExport+i.name; + FILE* outFile=ps_fopen(path.c_str(),"wb"); + if (outFile!=NULL) { + fwrite(i.data->getFinalBuf(),1,i.data->size(),outFile); + fclose(outFile); + } + i.data->finish(); + delete i.data; + } + showError(fmt::sprintf(_("Done! Baked %d files."),(int)ex->getResult().size())); } - i.data->finish(); - delete i.data; + } else { + showError("error!"); } - showError(fmt::sprintf(_("Done! Baked %d files."),(int)out.size())); + delete ex; ImGui::CloseCurrentPopup(); } } diff --git a/src/main.cpp b/src/main.cpp index 137ec29eb..94d81307c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -87,6 +87,7 @@ String outName; String vgmOutName; String zsmOutName; String cmdOutName; +String tiunaOutName; int benchMode=0; int subsong=-1; DivAudioExportOptions exportOptions; @@ -109,6 +110,11 @@ bool safeModeWithAudio=false; bool infoMode=false; +bool noReportError=false; + +int tiunaFirstBankSize=3072; +int tiunaOtherBankSize=4096-48; + std::vector params; #ifdef HAVE_LOCALE @@ -199,6 +205,11 @@ TAParamResult pConsole(String val) { return TA_PARAM_SUCCESS; } +TAParamResult pQuiet(String val) { + noReportError=true; + return TA_PARAM_SUCCESS; +} + TAParamResult pNoStatus(String val) { consoleNoStatus=true; return TA_PARAM_SUCCESS; @@ -436,6 +447,12 @@ TAParamResult pCmdOut(String val) { return TA_PARAM_SUCCESS; } +TAParamResult pTiunaOut(String val) { + tiunaOutName=val; + e.setAudio(DIV_AUDIO_DUMMY); + return TA_PARAM_SUCCESS; +} + bool needsValue(String param) { for (size_t i=0; i","output .zsm data for Commander X16 Zsound")); params.push_back(TAParam("C","cmdout",true,pCmdOut,"","output command stream")); + params.push_back(TAParam("T","tiunaout",true,pTiunaOut,"","output .asm data with TIunA sound data (TIA only)")); params.push_back(TAParam("L","loglevel",true,pLogLevel,"debug|info|warning|error","set the log level (info by default)")); params.push_back(TAParam("v","view",true,pView,"pattern|commands|nothing","set visualization (nothing by default)")); params.push_back(TAParam("i","info",false,pInfo,"","get info about a song")); params.push_back(TAParam("c","console",false,pConsole,"","enable console mode")); + params.push_back(TAParam("q","noreport",false,pQuiet,"","do not display message box on error")); params.push_back(TAParam("n","nostatus",false,pNoStatus,"","disable playback status in console mode")); params.push_back(TAParam("N","nocontrols",false,pNoControls,"","disable standard input controls in console mode")); @@ -476,18 +495,25 @@ void initParams() { #ifdef _WIN32 void reportError(String what) { logE("%s",what); - MessageBox(NULL,what.c_str(),"Furnace",MB_OK|MB_ICONERROR); + if (!noReportError) { + MessageBox(NULL,what.c_str(),"Furnace",MB_OK|MB_ICONERROR); + } } #elif defined(ANDROID) || defined(__APPLE__) void reportError(String what) { logE("%s",what); #ifdef HAVE_SDL2 - SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR,"Error",what.c_str(),NULL); + if (!noReportError) { + SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR,"Error",what.c_str(),NULL); + } #endif } #else void reportError(String what) { logE("%s",what); + if (!noReportError) { + // dummy + } } #endif @@ -549,6 +575,7 @@ int main(int argc, char** argv) { vgmOutName=""; zsmOutName=""; cmdOutName=""; + tiunaOutName=""; // load config for locale e.prePreInit(); @@ -716,14 +743,14 @@ int main(int argc, char** argv) { return 1; } - if (fileName.empty() && (benchMode || infoMode || outName!="" || vgmOutName!="" || cmdOutName!="")) { + if (fileName.empty() && (benchMode || infoMode || outName!="" || vgmOutName!="" || zsmOutName!="" || cmdOutName!="" || tiunaOutName!="")) { logE("provide a file!"); return 1; } #ifdef HAVE_GUI - if (e.preInit(consoleMode || benchMode || infoMode || outName!="" || vgmOutName!="" || cmdOutName!="")) { - if (consoleMode || benchMode || infoMode || outName!="" || vgmOutName!="" || cmdOutName!="") { + if (e.preInit(consoleMode || benchMode || infoMode || outName!="" || vgmOutName!="" || zsmOutName!="" || cmdOutName!="" || tiunaOutName!="")) { + if (consoleMode || benchMode || infoMode || outName!="" || vgmOutName!="" || zsmOutName!="" || cmdOutName!="" || tiunaOutName!="") { logW("engine wants safe mode, but Furnace GUI is not going to start."); } else { safeMode=true; @@ -735,7 +762,7 @@ int main(int argc, char** argv) { } #endif - if (safeMode && (consoleMode || benchMode || infoMode || outName!="" || vgmOutName!="" || cmdOutName!="")) { + if (safeMode && (consoleMode || benchMode || infoMode || outName!="" || vgmOutName!="" || zsmOutName!="" || cmdOutName!="" || tiunaOutName!="")) { logE("you can't use safe mode and console/export mode together."); return 1; } @@ -744,7 +771,7 @@ int main(int argc, char** argv) { e.setAudio(DIV_AUDIO_DUMMY); } - if (!fileName.empty() && ((!e.getConfBool("tutIntroPlayed",TUT_INTRO_PLAYED)) || e.getConfInt("alwaysPlayIntro",0)!=3 || consoleMode || benchMode || infoMode || outName!="" || vgmOutName!="" || cmdOutName!="")) { + if (!fileName.empty() && ((!e.getConfBool("tutIntroPlayed",TUT_INTRO_PLAYED)) || e.getConfInt("alwaysPlayIntro",0)!=3 || consoleMode || benchMode || infoMode || outName!="" || vgmOutName!="" || zsmOutName!="" || cmdOutName!="" || tiunaOutName!="")) { logI("loading module..."); FILE* f=ps_fopen(fileName.c_str(),"rb"); if (f==NULL) { @@ -836,7 +863,7 @@ int main(int argc, char** argv) { return 0; } - if (outName!="" || vgmOutName!="" || cmdOutName!="") { + if (outName!="" || vgmOutName!="" || zsmOutName!="" || cmdOutName!="" || tiunaOutName!="") { if (cmdOutName!="") { SafeWriter* w=e.saveCommand(); if (w!=NULL) { @@ -869,6 +896,39 @@ int main(int argc, char** argv) { reportError(_("could not write VGM!")); } } + if (zsmOutName!="") { + // TODO: changing parameters + SafeWriter* w=e.saveZSM(60,true,true); + if (w!=NULL) { + FILE* f=ps_fopen(zsmOutName.c_str(),"wb"); + if (f!=NULL) { + fwrite(w->getFinalBuf(),1,w->size(),f); + fclose(f); + } else { + reportError(fmt::sprintf(_("could not open file! (%s)"),e.getLastError())); + } + w->finish(); + delete w; + } else { + reportError(fmt::sprintf(_("could not write ZSM! (%s)"),e.getLastError())); + } + } + if (tiunaOutName!="") { + SafeWriter* w=e.saveTiuna(NULL,"asmBaseLabel",tiunaFirstBankSize,tiunaOtherBankSize); + if (w!=NULL) { + FILE* f=ps_fopen(tiunaOutName.c_str(),"wb"); + if (f!=NULL) { + fwrite(w->getFinalBuf(),1,w->size(),f); + fclose(f); + } else { + reportError(fmt::sprintf(_("could not open file! (%s)"),e.getLastError())); + } + w->finish(); + delete w; + } else { + reportError(fmt::sprintf("could not write TIunA! (%s)",e.getLastError())); + } + } if (outName!="") { e.setConsoleMode(true); e.saveAudio(outName.c_str(),exportOptions); diff --git a/wavetables/32x32/32x32sam.fuw b/wavetables/32x32/32x32sam.fuw new file mode 100644 index 000000000..4df39259c Binary files /dev/null and b/wavetables/32x32/32x32sam.fuw differ