diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c2d61c32b..20e267370 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,7 +24,7 @@ jobs: #- { name: 'Windows MinGW x86_64', os: ubuntu-20.04, compiler: mingw, arch: x86_64 } - { name: 'macOS x86_64', os: macos-latest, arch: x86_64 } - { name: 'macOS ARM', os: macos-latest, arch: arm64 } - - { name: 'Linux x86_64', os: ubuntu-18.04, arch: x86_64 } + - { name: 'Linux x86_64', os: ubuntu-20.04, arch: x86_64 } #- { name: 'Linux ARM', os: ubuntu-18.04, arch: armhf } fail-fast: false diff --git a/CMakeLists.txt b/CMakeLists.txt index 7105990b1..c8d61667c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -793,7 +793,7 @@ endif() if(ANDROID AND NOT TERMUX) add_library(furnace SHARED ${USED_SOURCES}) elseif(WIN32) - add_executable(furnace WIN32 ${USED_SOURCES}) + add_executable(furnace ${USED_SOURCES}) else() add_executable(furnace ${USED_SOURCES}) endif() diff --git a/TODO.md b/TODO.md new file mode 100644 index 000000000..0b7f1cd9b --- /dev/null +++ b/TODO.md @@ -0,0 +1,16 @@ +# to-do for 0.6pre5 + +- tutorial +- ease-of-use improvements... ideas: + - preset compat flags + - setting to toggle the Choose a System screen on new project + - maybe reduced set of presets for the sake of simplicity + - a more preferable highlight/drag system + - some speed/intuitive workflow improvements that go a long way + - Had a hard time finding the docs on github and in Furnace's folder. + - make .pdf manual out of papers/doc/ + - you're going too fast; please slow down + - break compatibility if it relieves complexity +- ins/wave/sample organization (folders and all) +- multi-key binds +- bug fixes diff --git a/demos/multichip/colab.fur b/demos/multichip/colab.fur deleted file mode 100644 index 4d104da1a..000000000 Binary files a/demos/multichip/colab.fur and /dev/null differ diff --git a/demos/multichip/collab.fur b/demos/multichip/collab.fur new file mode 100644 index 000000000..3d20717ef Binary files /dev/null and b/demos/multichip/collab.fur differ diff --git a/extern/imgui_patched/imgui.cpp b/extern/imgui_patched/imgui.cpp index 612a88d2d..4d57efc02 100644 --- a/extern/imgui_patched/imgui.cpp +++ b/extern/imgui_patched/imgui.cpp @@ -837,6 +837,8 @@ CODE #include // intptr_t #endif +#include "../../src/fileutils.h" + // [Windows] On non-Visual Studio compilers, we default to IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS unless explicitly enabled #if defined(_WIN32) && !defined(_MSC_VER) && !defined(IMGUI_ENABLE_WIN32_DEFAULT_IME_FUNCTIONS) && !defined(IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS) #define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS @@ -12457,14 +12459,47 @@ void ImGui::ClearIniSettings() g.SettingsHandlers[handler_n].ClearAllFn(&g, &g.SettingsHandlers[handler_n]); } -void ImGui::LoadIniSettingsFromDisk(const char* ini_filename) +bool ImGui::LoadIniSettingsFromDisk(const char* ini_filename, bool redundancy) { size_t file_data_size = 0; - char* file_data = (char*)ImFileLoadToMemory(ini_filename, "rb", &file_data_size); + char* file_data = NULL; + + if (redundancy) { + char fileName[4096]; + + for (int i=0; i<5; i++) { + bool viable=false; + if (i>0) { + snprintf(fileName,4095,"%s.%d",ini_filename,i); + } else { + strncpy(fileName,ini_filename,4095); + } + file_data=(char*)ImFileLoadToMemory(fileName, "rb", &file_data_size); + if (!file_data) continue; + + for (size_t j=0; j=0; i--) { + if (i>0) { + snprintf(oldPath,4095,"%s.%d",ini_filename,i); + } else { + strncpy(oldPath,ini_filename,4095); + } + snprintf(newPath,4095,"%s.%d",ini_filename,i+1); + + if (i>=4) { + deleteFile(oldPath); + } else { + moveFiles(oldPath,newPath); + } + } + } + } size_t ini_data_size = 0; const char* ini_data = SaveIniSettingsToMemory(&ini_data_size); ImFileHandle f = ImFileOpen(ini_filename, "wt"); if (!f) - return; - ImFileWrite(ini_data, sizeof(char), ini_data_size, f); + return false; + bool areEqual=ImFileWrite(ini_data, sizeof(char), ini_data_size, f)==ini_data_size; + IM_ASSERT_USER_ERROR(areEqual, "ImFileWrite failed to write file!"); ImFileClose(f); + if (!areEqual && redundancy) deleteFile(ini_filename); + return areEqual; } // Call registered handlers (e.g. SettingsHandlerWindow_WriteAll() + custom handlers) to write their stuff into a text buffer diff --git a/extern/imgui_patched/imgui.h b/extern/imgui_patched/imgui.h index 131784f8f..7aa62aa6a 100644 --- a/extern/imgui_patched/imgui.h +++ b/extern/imgui_patched/imgui.h @@ -950,9 +950,9 @@ namespace ImGui // - The disk functions are automatically called if io.IniFilename != NULL (default is "imgui.ini"). // - Set io.IniFilename to NULL to load/save manually. Read io.WantSaveIniSettings description about handling .ini saving manually. // - Important: default value "imgui.ini" is relative to current working dir! Most apps will want to lock this to an absolute path (e.g. same path as executables). - IMGUI_API void LoadIniSettingsFromDisk(const char* ini_filename); // call after CreateContext() and before the first call to NewFrame(). NewFrame() automatically calls LoadIniSettingsFromDisk(io.IniFilename). + IMGUI_API bool LoadIniSettingsFromDisk(const char* ini_filename, bool redundancy=false); // call after CreateContext() and before the first call to NewFrame(). NewFrame() automatically calls LoadIniSettingsFromDisk(io.IniFilename). IMGUI_API void LoadIniSettingsFromMemory(const char* ini_data, size_t ini_size=0); // call after CreateContext() and before the first call to NewFrame() to provide .ini data from your own data source. - IMGUI_API void SaveIniSettingsToDisk(const char* ini_filename); // this is automatically called (if io.IniFilename is not empty) a few seconds after any modification that should be reflected in the .ini file (and also by DestroyContext). + IMGUI_API bool SaveIniSettingsToDisk(const char* ini_filename, bool redundancy=false); // this is automatically called (if io.IniFilename is not empty) a few seconds after any modification that should be reflected in the .ini file (and also by DestroyContext). IMGUI_API const char* SaveIniSettingsToMemory(size_t* out_ini_size = NULL); // return a zero-terminated string with the .ini data which you can save by your own mean. call when io.WantSaveIniSettings is set, then save data by your own mean and clear io.WantSaveIniSettings. // Debug Utilities diff --git a/papers/format.md b/papers/format.md index f166d43ef..a2c42f04d 100644 --- a/papers/format.md +++ b/papers/format.md @@ -32,6 +32,15 @@ these fields are 0 in format versions prior to 100 (0.6pre1). the format versions are: +- 152: Furnace dev152 +- 151: Furnace dev151 +- 150: Furnace dev150 +- 149: Furnace dev149 +- 148: Furnace dev148 +- 147: Furnace dev147 +- 146: Furnace Pro (joke version) +- 145: Furnace dev145 +- 144: Furnace dev144 - 143: Furnace 0.6pre4 - 142: Furnace dev142 - 141: Furnace Tournament Edition (for intro tune contest) diff --git a/papers/newIns.md b/papers/newIns.md index 9176bd1ac..84369a6c7 100644 --- a/papers/newIns.md +++ b/papers/newIns.md @@ -389,7 +389,7 @@ the sample map format: ``` size | description -----|------------------------------------ - 2 | note to play + 2 | note to play (>=152) or reserved 2 | sample to play ``` diff --git a/src/engine/config.cpp b/src/engine/config.cpp index 3e1f86179..137dbdaff 100644 --- a/src/engine/config.cpp +++ b/src/engine/config.cpp @@ -23,21 +23,54 @@ #include "../fileutils.h" #include -bool DivConfig::save(const char* path) { +#define REDUNDANCY_NUM_ATTEMPTS 5 +#define CHECK_BUF_SIZE 8192 + +bool DivConfig::save(const char* path, bool redundancy) { + if (redundancy) { + char oldPath[4096]; + char newPath[4096]; + + if (fileExists(path)==1) { + logD("rotating config files..."); + for (int i=4; i>=0; i--) { + if (i>0) { + snprintf(oldPath,4095,"%s.%d",path,i); + } else { + strncpy(oldPath,path,4095); + } + snprintf(newPath,4095,"%s.%d",path,i+1); + + if (i>=4) { + logV("remove %s",oldPath); + deleteFile(oldPath); + } else { + logV("move %s to %s",oldPath,newPath); + moveFiles(oldPath,newPath); + } + } + } + } + logD("opening config for write: %s",path); FILE* f=ps_fopen(path,"wb"); if (f==NULL) { logW("could not write config file! %s",strerror(errno)); + reportError(fmt::sprintf("could not write config file! %s",strerror(errno))); return false; } for (auto& i: conf) { String toWrite=fmt::sprintf("%s=%s\n",i.first,i.second); if (fwrite(toWrite.c_str(),1,toWrite.size(),f)!=toWrite.size()) { logW("could not write config file! %s",strerror(errno)); + reportError(fmt::sprintf("could not write config file! %s",strerror(errno))); + logV("removing config file"); fclose(f); + deleteFile(path); return false; } } fclose(f); + logD("config file written successfully."); return true; } @@ -63,6 +96,7 @@ void DivConfig::parseLine(const char* line) { String value=""; bool keyOrValue=false; for (const char* i=line; *i; i++) { + if (*i=='\r') continue; if (*i=='\n') continue; if (keyOrValue) { value+=*i; @@ -79,17 +113,94 @@ void DivConfig::parseLine(const char* line) { } } -bool DivConfig::loadFromFile(const char* path, bool createOnFail) { +bool DivConfig::loadFromFile(const char* path, bool createOnFail, bool redundancy) { char line[4096]; - FILE* f=ps_fopen(path,"rb"); - if (f==NULL) { - if (createOnFail) { - logI("creating default config."); - return save(path); - } else { - return false; + logD("opening config for read: %s",path); + + FILE* f=NULL; + + if (redundancy) { + unsigned char* readBuf=new unsigned char[CHECK_BUF_SIZE]; + size_t readBufLen=0; + for (int i=0; i0) { + snprintf(line,4095,"%s.%d",path,i); + } else { + strncpy(line,path,4095); + } + logV("trying: %s",line); + + // try to open config + f=ps_fopen(line,"rb"); + // check whether we could open it + if (f==NULL) { + logV("fopen(): %s",strerror(errno)); + continue; + } + + // check whether there's something + while (!feof(f)) { + readBufLen=fread(readBuf,1,CHECK_BUF_SIZE,f); + if (ferror(f)) { + logV("fread(): %s",strerror(errno)); + break; + } + + for (size_t j=0; j DivConfig::getIntList(String key, std::initializer_list fallback) const { + String next; + std::vector ret; + try { + String val=conf.at(key); + + for (char i: val) { + if (i==',') { + int num=std::stoi(next); + ret.push_back(num); + next=""; + } else { + next+=i; + } + } + if (!next.empty()) { + int num=std::stoi(next); + ret.push_back(num); + } + + return ret; + } catch (std::out_of_range& e) { + } catch (std::invalid_argument& e) { + } + return fallback; +} + bool DivConfig::has(String key) const { try { String test=conf.at(key); @@ -212,6 +351,17 @@ void DivConfig::set(String key, String value) { conf[key]=value; } +void DivConfig::set(String key, const std::vector& value) { + String val; + bool comma=false; + for (int i: value) { + if (comma) val+=','; + val+=fmt::sprintf("%d",i); + comma=true; + } + conf[key]=val; +} + bool DivConfig::remove(String key) { return conf.erase(key); } diff --git a/src/engine/config.h b/src/engine/config.h index 58ce83708..12105d696 100644 --- a/src/engine/config.h +++ b/src/engine/config.h @@ -22,6 +22,8 @@ #include "../ta-utils.h" #include +#include +#include class DivConfig { std::map conf; @@ -30,10 +32,10 @@ class DivConfig { // config loading/saving bool loadFromMemory(const char* buf); bool loadFromBase64(const char* buf); - bool loadFromFile(const char* path, bool createOnFail=true); + bool loadFromFile(const char* path, bool createOnFail=true, bool redundancy=false); String toString(); String toBase64(); - bool save(const char* path); + bool save(const char* path, bool redundancy=false); // get the map const std::map& configMap(); @@ -44,6 +46,7 @@ class DivConfig { float getFloat(String key, float fallback) const; double getDouble(String key, double fallback) const; String getString(String key, String fallback) const; + std::vector getIntList(String key, std::initializer_list fallback) const; // check for existence bool has(String key) const; @@ -55,6 +58,7 @@ class DivConfig { void set(String key, double value); void set(String key, const char* value); void set(String key, String value); + void set(String key, const std::vector& value); // remove a config value bool remove(String key); diff --git a/src/engine/configEngine.cpp b/src/engine/configEngine.cpp index eccf3986e..f793d35c2 100644 --- a/src/engine/configEngine.cpp +++ b/src/engine/configEngine.cpp @@ -110,12 +110,12 @@ void DivEngine::initConfDir() { bool DivEngine::saveConf() { configFile=configPath+String(CONFIG_FILE); - return conf.save(configFile.c_str()); + return conf.save(configFile.c_str(),true); } bool DivEngine::loadConf() { configFile=configPath+String(CONFIG_FILE); - return conf.loadFromFile(configFile.c_str()); + return conf.loadFromFile(configFile.c_str(),true,true); } bool DivEngine::getConfBool(String key, bool fallback) { diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 008054114..847824fd7 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -2441,6 +2441,16 @@ void DivEngine::stop() { } } } + + // reset all chan oscs + for (int i=0; igetOscBuffer(dispatchChanOfChan[i]); + if (buf!=NULL) { + memset(buf->data,0,65536*sizeof(short)); + buf->needle=0; + buf->readNeedle=0; + } + } BUSY_END; } diff --git a/src/engine/engine.h b/src/engine/engine.h index 76614a847..85da26ea9 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -53,8 +53,8 @@ #define EXTERN_BUSY_BEGIN_SOFT e->softLocked=true; e->isBusy.lock(); #define EXTERN_BUSY_END e->isBusy.unlock(); e->softLocked=false; -#define DIV_VERSION "dev147" -#define DIV_ENGINE_VERSION 147 +#define DIV_VERSION "dev152" +#define DIV_ENGINE_VERSION 152 // for imports #define DIV_VERSION_MOD 0xff01 #define DIV_VERSION_FC 0xff02 diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index e1ae97b13..c9e51bf0b 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -696,6 +696,13 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { } ds.wave.push_back(wave); } + + // sometimes there's a single length 0 wavetable in the file. I don't know why. + if (ds.waveLen==1) { + if (ds.wave[0]->len==0) { + ds.clearWavetables(); + } + } } logV("%x",reader.tell()); @@ -850,6 +857,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { sample->rate=22050; if (ds.version>=0x0b) { sample->rate=fileToDivRate(reader.readC()); + sample->centerRate=sample->rate; pitch=reader.readC(); vol=reader.readC(); } @@ -874,24 +882,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { // what the hell man... cutStart=reader.readI(); cutEnd=reader.readI(); - if (cutStart<0 || cutStart>length) { - logE("cutStart is out of range! (%d)",cutStart); - lastError="file is corrupt or unreadable at samples"; - delete[] file; - return false; - } - if (cutEnd<0 || cutEnd>length) { - logE("cutEnd is out of range! (%d)",cutEnd); - lastError="file is corrupt or unreadable at samples"; - delete[] file; - return false; - } - if (cutEnd0) { if (ds.version>0x08) { @@ -903,19 +894,6 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { data=new short[length]; reader.read(data,length*2); } - - if (ds.version>0x1b) { - if (cutStart!=0 || cutEnd!=length) { - // cut data - short* newData=new short[cutEnd-cutStart]; - memcpy(newData,&data[cutStart],(cutEnd-cutStart)*sizeof(short)); - delete[] data; - data=newData; - length=cutEnd-cutStart; - cutStart=0; - cutEnd=length; - } - } #ifdef TA_BIG_ENDIAN // convert to big-endian @@ -924,27 +902,73 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { } #endif - if (pitch!=5) { + int scaledLen=(double)length/samplePitches[pitch]; + + if (scaledLen>0) { + // resample logD("%d: scaling from %d...",i,pitch); - } - - // render data - if (!sample->init((double)length/samplePitches[pitch])) { - logE("%d: error while initializing sample!",i); - } - - unsigned int k=0; - float mult=(float)(vol)/50.0f; - for (double j=0; j=sample->samples) { - break; + + short* newData=new short[scaledLen]; + int k=0; + float mult=(float)(vol)/50.0f; + for (double j=0; j=scaledLen) { + break; + } + if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) { + float next=(float)(data[(unsigned int)j]-0x80)*mult; + newData[k++]=fmin(fmax(next,-128),127); + } else { + float next=(float)data[(unsigned int)j]*mult; + newData[k++]=fmin(fmax(next,-32768),32767); + } } - if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) { - 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; + data=newData; + } + + if (ds.version>=0x1b) { + if (cutStart<0 || cutStart>scaledLen) { + logE("cutStart is out of range! (%d)",cutStart); + lastError="file is corrupt or unreadable at samples"; + delete[] file; + return false; + } + if (cutEnd<0 || cutEnd>scaledLen) { + logE("cutEnd is out of range! (%d)",cutEnd); + lastError="file is corrupt or unreadable at samples"; + delete[] file; + return false; + } + if (cutEndinit(scaledLen)) { + logE("%d: error while initializing sample!",i); + } else { + for (int i=0; idepth==DIV_SAMPLE_DEPTH_8BIT) { + sample->data8[i]=data[i]; + } else { + sample->data16[i]=data[i]; + } } } @@ -5183,7 +5207,6 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { for (int i=0; itell()); - logV("writing instrument %d...",i); ins->putInsData2(w,false); } diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index c51805ab5..76c410f65 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -214,7 +214,6 @@ bool DivInstrumentSNES::operator==(const DivInstrumentSNES& other) { #undef _C #define FEATURE_BEGIN(x) \ - logV("- %s",x); \ w->write(x,2); \ size_t featStartSeek=w->tell(); \ w->writeS(0); @@ -2103,6 +2102,12 @@ void DivInstrument::readFeatureSM(SafeReader& reader, short version) { amiga.noteMap[note].freq=reader.readS(); amiga.noteMap[note].map=reader.readS(); } + + if (version<152) { + for (int note=0; note<120; note++) { + amiga.noteMap[note].freq=note; + } + } } READ_FEAT_END; @@ -2970,6 +2975,12 @@ DivDataErrors DivInstrument::readInsDataOld(SafeReader &reader, short version) { for (int note=0; note<120; note++) { amiga.noteMap[note].map=reader.readS(); } + + if (version<152) { + for (int note=0; note<120; note++) { + amiga.noteMap[note].freq=note; + } + } } } diff --git a/src/engine/instrument.h b/src/engine/instrument.h index 932d44ffb..ab2bc5e05 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -415,7 +415,7 @@ struct DivInstrumentAmiga { if (note>119) note=119; return noteMap[note].freq; } - return -1; + return note; } DivInstrumentAmiga(): @@ -424,8 +424,9 @@ struct DivInstrumentAmiga { useSample(false), useWave(false), waveLen(31) { - for (SampleMap& elem: noteMap) { - elem=SampleMap(); + for (int i=0; i<120; i++) { + noteMap[i].map=-1; + noteMap[i].freq=i; } } }; diff --git a/src/engine/platform/amiga.cpp b/src/engine/platform/amiga.cpp index b04eefbeb..7c0b8c243 100644 --- a/src/engine/platform/amiga.cpp +++ b/src/engine/platform/amiga.cpp @@ -560,7 +560,10 @@ int DivPlatformAmiga::dispatch(DivCommand c) { } } } else { - if (c.value!=DIV_NOTE_NULL) chan[c.chan].sample=ins->amiga.getSample(c.value); + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].sample=ins->amiga.getSample(c.value); + c.value=ins->amiga.getFreq(c.value); + } chan[c.chan].useWave=false; } if (c.value!=DIV_NOTE_NULL) { diff --git a/src/engine/platform/ay.cpp b/src/engine/platform/ay.cpp index 145ab5622..76637f38e 100644 --- a/src/engine/platform/ay.cpp +++ b/src/engine/platform/ay.cpp @@ -292,8 +292,6 @@ void DivPlatformAY8910::tick(bool sysTick) { if (chan[i].std.phaseReset.val==1) { if (chan[i].nextPSGMode.dac) { if (dumpWrites) addWrite(0xffff0002+(i<<8),0); - DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_AY); - chan[i].dac.sample=ins->amiga.getSample(chan[i].note); if (chan[i].dac.sample<0 || chan[i].dac.sample>=parent->song.sampleLen) { if (dumpWrites) { rWrite(0x08+i,0); @@ -405,7 +403,10 @@ int DivPlatformAY8910::dispatch(DivCommand c) { if (chan[c.chan].nextPSGMode.dac) { if (skipRegisterWrites) break; if (!parent->song.disableSampleMacro && (ins->type==DIV_INS_AMIGA || ins->amiga.useSample)) { - if (c.value!=DIV_NOTE_NULL) chan[c.chan].dac.sample=ins->amiga.getSample(c.value); + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].dac.sample=ins->amiga.getSample(c.value); + c.value=ins->amiga.getFreq(c.value); + } if (chan[c.chan].dac.sample<0 || chan[c.chan].dac.sample>=parent->song.sampleLen) { chan[c.chan].dac.sample=-1; if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0); diff --git a/src/engine/platform/ay8930.cpp b/src/engine/platform/ay8930.cpp index ba4e8099a..8561548d0 100644 --- a/src/engine/platform/ay8930.cpp +++ b/src/engine/platform/ay8930.cpp @@ -280,8 +280,6 @@ void DivPlatformAY8930::tick(bool sysTick) { if (chan[i].std.phaseReset.val==1) { if (chan[i].nextPSGMode.dac) { if (dumpWrites) addWrite(0xffff0002+(i<<8),0); - DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_AY8930); - chan[i].dac.sample=ins->amiga.getSample(chan[i].note); if (chan[i].dac.sample<0 || chan[i].dac.sample>=parent->song.sampleLen) { if (dumpWrites) { rWrite(0x08+i,0); @@ -406,7 +404,10 @@ int DivPlatformAY8930::dispatch(DivCommand c) { if (chan[c.chan].nextPSGMode.dac) { if (skipRegisterWrites) break; if (ins->type==DIV_INS_AMIGA || ins->amiga.useSample) { - if (c.value!=DIV_NOTE_NULL) chan[c.chan].dac.sample=ins->amiga.getSample(c.value); + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].dac.sample=ins->amiga.getSample(c.value); + c.value=ins->amiga.getFreq(c.value); + } if (chan[c.chan].dac.sample<0 || chan[c.chan].dac.sample>=parent->song.sampleLen) { chan[c.chan].dac.sample=-1; if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0); diff --git a/src/engine/platform/es5506.cpp b/src/engine/platform/es5506.cpp index 7244426b7..28ad45eb2 100644 --- a/src/engine/platform/es5506.cpp +++ b/src/engine/platform/es5506.cpp @@ -410,7 +410,7 @@ void DivPlatformES5506::tick(bool sysTick) { if (chan[i].pcmChanged.changed) { if (chan[i].pcmChanged.index) { const int next=chan[i].pcm.next; - bool sampleVaild=false; + bool sampleValid=false; if (((ins->amiga.useNoteMap) && (next>=0 && next<120)) || ((!ins->amiga.useNoteMap) && (next>=0 && nextsong.sampleLen))) { DivInstrumentAmiga::SampleMap& noteMapind=ins->amiga.noteMap[next]; @@ -420,7 +420,7 @@ void DivPlatformES5506::tick(bool sysTick) { } if (sample>=0 && samplesong.sampleLen) { const unsigned int offES5506=sampleOffES5506[sample]; - sampleVaild=true; + sampleValid=true; chan[i].pcm.index=sample; chan[i].pcm.isNoteMap=ins->amiga.useNoteMap; DivSample* s=parent->getSample(sample); @@ -459,7 +459,7 @@ void DivPlatformES5506::tick(bool sysTick) { } } } - if (sampleVaild) { + if (sampleValid) { if (!chan[i].keyOn) { pageWrite(0x20|i,0x03,(chan[i].pcm.direction)?chan[i].pcm.end:chan[i].pcm.start); } @@ -750,12 +750,13 @@ int DivPlatformES5506::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_ES5506); - bool sampleVaild=false; + bool sampleValid=false; if (((ins->amiga.useNoteMap) && (c.value>=0 && c.value<120)) || ((!ins->amiga.useNoteMap) && (ins->amiga.initSample>=0 && ins->amiga.initSamplesong.sampleLen))) { int sample=ins->amiga.getSample(c.value); + c.value=ins->amiga.getFreq(c.value); if (sample>=0 && samplesong.sampleLen) { - sampleVaild=true; + sampleValid=true; chan[c.chan].volMacroMax=ins->type==DIV_INS_AMIGA?64:0xfff; chan[c.chan].panMacroMax=ins->type==DIV_INS_AMIGA?127:0xfff; chan[c.chan].pcm.note=c.value; @@ -764,7 +765,7 @@ int DivPlatformES5506::dispatch(DivCommand c) { chan[c.chan].envelope=ins->es5506.envelope; } } - if (!sampleVaild) { + if (!sampleValid) { chan[c.chan].pcm.index=chan[c.chan].pcm.next=-1; chan[c.chan].filter=DivInstrumentES5506::Filter(); chan[c.chan].envelope=DivInstrumentES5506::Envelope(); diff --git a/src/engine/platform/ga20.cpp b/src/engine/platform/ga20.cpp index 527e80f1d..7b9d86a19 100644 --- a/src/engine/platform/ga20.cpp +++ b/src/engine/platform/ga20.cpp @@ -200,7 +200,10 @@ int DivPlatformGA20::dispatch(DivCommand c) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA); chan[c.chan].macroVolMul=ins->type==DIV_INS_AMIGA?64:255; - if (c.value!=DIV_NOTE_NULL) chan[c.chan].sample=ins->amiga.getSample(c.value); + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].sample=ins->amiga.getSample(c.value); + c.value=ins->amiga.getFreq(c.value); + } if (c.value!=DIV_NOTE_NULL) { chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); } diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index 089d33e27..e32ec528b 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -681,7 +681,10 @@ int DivPlatformGenesis::dispatch(DivCommand c) { if (c.chan>=5 && chan[c.chan].dacMode) { //if (skipRegisterWrites) break; if (ins->type==DIV_INS_AMIGA) { // Furnace mode - if (c.value!=DIV_NOTE_NULL) chan[c.chan].dacSample=ins->amiga.getSample(c.value); + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].dacSample=ins->amiga.getSample(c.value); + c.value=ins->amiga.getFreq(c.value); + } if (chan[c.chan].dacSample<0 || chan[c.chan].dacSample>=parent->song.sampleLen) { chan[c.chan].dacSample=-1; if (dumpWrites) addWrite(0xffff0002,0); diff --git a/src/engine/platform/k007232.cpp b/src/engine/platform/k007232.cpp index daa661f6f..04e1bde5e 100644 --- a/src/engine/platform/k007232.cpp +++ b/src/engine/platform/k007232.cpp @@ -274,7 +274,10 @@ int DivPlatformK007232::dispatch(DivCommand c) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA); chan[c.chan].macroVolMul=ins->type==DIV_INS_AMIGA?64:15; - if (c.value!=DIV_NOTE_NULL) chan[c.chan].sample=ins->amiga.getSample(c.value); + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].sample=ins->amiga.getSample(c.value); + c.value=ins->amiga.getFreq(c.value); + } if (c.value!=DIV_NOTE_NULL) { chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); } diff --git a/src/engine/platform/lynx.cpp b/src/engine/platform/lynx.cpp index a8b2cb88d..6e87fea78 100644 --- a/src/engine/platform/lynx.cpp +++ b/src/engine/platform/lynx.cpp @@ -261,13 +261,14 @@ int DivPlatformLynx::dispatch(DivCommand c) { chan[c.chan].macroVolMul=ins->type==DIV_INS_AMIGA?64:127; chan[c.chan].pcm=(ins->type==DIV_INS_AMIGA || ins->amiga.useSample); if (c.value!=DIV_NOTE_NULL) { - chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); if (chan[c.chan].pcm) { + chan[c.chan].sample=ins->amiga.getSample(c.value); + c.value=ins->amiga.getFreq(c.value); chan[c.chan].sampleBaseFreq=NOTE_FREQUENCY(c.value); - if (c.value!=DIV_NOTE_NULL) chan[c.chan].sample=ins->amiga.getSample(c.value); chan[c.chan].sampleAccum=0; chan[c.chan].samplePos=0; } + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); chan[c.chan].freqChanged=true; chan[c.chan].note=c.value; chan[c.chan].actualNote=c.value; diff --git a/src/engine/platform/mmc5.cpp b/src/engine/platform/mmc5.cpp index b130f2cbf..11b04e815 100644 --- a/src/engine/platform/mmc5.cpp +++ b/src/engine/platform/mmc5.cpp @@ -176,7 +176,10 @@ int DivPlatformMMC5::dispatch(DivCommand c) { if (c.chan==2) { // PCM DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_STD); if (ins->type==DIV_INS_AMIGA) { - if (c.value!=DIV_NOTE_NULL) dacSample=ins->amiga.getSample(c.value); + if (c.value!=DIV_NOTE_NULL) { + dacSample=ins->amiga.getSample(c.value); + c.value=ins->amiga.getFreq(c.value); + } if (dacSample<0 || dacSample>=parent->song.sampleLen) { dacSample=-1; if (dumpWrites) addWrite(0xffff0002,0); diff --git a/src/engine/platform/nes.cpp b/src/engine/platform/nes.cpp index 88c7253ee..67cdc620c 100644 --- a/src/engine/platform/nes.cpp +++ b/src/engine/platform/nes.cpp @@ -361,7 +361,10 @@ int DivPlatformNES::dispatch(DivCommand c) { if (c.chan==4) { // PCM DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_STD); if (ins->type==DIV_INS_AMIGA) { - if (c.value!=DIV_NOTE_NULL) dacSample=ins->amiga.getSample(c.value); + if (c.value!=DIV_NOTE_NULL) { + dacSample=ins->amiga.getSample(c.value); + c.value=ins->amiga.getFreq(c.value); + } if (dacSample<0 || dacSample>=parent->song.sampleLen) { dacSample=-1; if (dumpWrites && !dpcmMode) addWrite(0xffff0002,0); diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index ff57f9afb..6fea25c3c 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -856,7 +856,10 @@ int DivPlatformOPL::dispatch(DivCommand c) { chan[c.chan].outVol=chan[c.chan].vol; immWrite(18,chan[c.chan].outVol); } - if (c.value!=DIV_NOTE_NULL) chan[c.chan].sample=ins->amiga.getSample(c.value); + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].sample=ins->amiga.getSample(c.value); + c.value=ins->amiga.getFreq(c.value); + } if (chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { DivSample* s=parent->getSample(chan[c.chan].sample); immWrite(8,0); diff --git a/src/engine/platform/pce.cpp b/src/engine/platform/pce.cpp index 95d483264..a920a4d06 100644 --- a/src/engine/platform/pce.cpp +++ b/src/engine/platform/pce.cpp @@ -282,7 +282,10 @@ int DivPlatformPCE::dispatch(DivCommand c) { if (ins->type==DIV_INS_AMIGA || ins->amiga.useSample) { chan[c.chan].furnaceDac=true; if (skipRegisterWrites) break; - if (c.value!=DIV_NOTE_NULL) chan[c.chan].dacSample=ins->amiga.getSample(c.value); + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].dacSample=ins->amiga.getSample(c.value); + c.value=ins->amiga.getFreq(c.value); + } if (chan[c.chan].dacSample<0 || chan[c.chan].dacSample>=parent->song.sampleLen) { chan[c.chan].dacSample=-1; if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0); diff --git a/src/engine/platform/pcmdac.cpp b/src/engine/platform/pcmdac.cpp index f7b3af715..0b59accb1 100644 --- a/src/engine/platform/pcmdac.cpp +++ b/src/engine/platform/pcmdac.cpp @@ -271,7 +271,10 @@ int DivPlatformPCMDAC::dispatch(DivCommand c) { } } } else { - if (c.value!=DIV_NOTE_NULL) chan[0].sample=ins->amiga.getSample(c.value); + if (c.value!=DIV_NOTE_NULL) { + chan[0].sample=ins->amiga.getSample(c.value); + c.value=ins->amiga.getFreq(c.value); + } chan[0].useWave=false; } if (c.value!=DIV_NOTE_NULL) { diff --git a/src/engine/platform/qsound.cpp b/src/engine/platform/qsound.cpp index f73f85dab..2e23abb4e 100644 --- a/src/engine/platform/qsound.cpp +++ b/src/engine/platform/qsound.cpp @@ -450,7 +450,10 @@ int DivPlatformQSound::dispatch(DivCommand c) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA); chan[c.chan].isNewQSound=(ins->type==DIV_INS_QSOUND); - if (c.value!=DIV_NOTE_NULL) chan[c.chan].sample=ins->amiga.getSample(c.value); + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].sample=ins->amiga.getSample(c.value); + c.value=ins->amiga.getFreq(c.value); + } if (c.value!=DIV_NOTE_NULL) { chan[c.chan].baseFreq=QS_NOTE_FREQUENCY(c.value); } diff --git a/src/engine/platform/rf5c68.cpp b/src/engine/platform/rf5c68.cpp index 149098b8c..fbf8e00dd 100644 --- a/src/engine/platform/rf5c68.cpp +++ b/src/engine/platform/rf5c68.cpp @@ -182,7 +182,10 @@ int DivPlatformRF5C68::dispatch(DivCommand c) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA); chan[c.chan].macroVolMul=ins->type==DIV_INS_AMIGA?64:255; - if (c.value!=DIV_NOTE_NULL) chan[c.chan].sample=ins->amiga.getSample(c.value); + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].sample=ins->amiga.getSample(c.value); + c.value=ins->amiga.getFreq(c.value); + } if (c.value!=DIV_NOTE_NULL) { chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); } diff --git a/src/engine/platform/segapcm.cpp b/src/engine/platform/segapcm.cpp index a4003a2be..9c7f6a631 100644 --- a/src/engine/platform/segapcm.cpp +++ b/src/engine/platform/segapcm.cpp @@ -186,7 +186,10 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) { if (ins->type==DIV_INS_AMIGA || ins->type==DIV_INS_SEGAPCM) { chan[c.chan].macroVolMul=(ins->type==DIV_INS_AMIGA)?64:127; chan[c.chan].isNewSegaPCM=(ins->type==DIV_INS_SEGAPCM); - if (c.value!=DIV_NOTE_NULL) chan[c.chan].pcm.sample=ins->amiga.getSample(c.value); + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].pcm.sample=ins->amiga.getSample(c.value); + c.value=ins->amiga.getFreq(c.value); + } if (chan[c.chan].pcm.sample<0 || chan[c.chan].pcm.sample>=parent->song.sampleLen) { chan[c.chan].pcm.sample=-1; rWrite(0x86+(c.chan<<3),3); @@ -480,7 +483,11 @@ void DivPlatformSegaPCM::renderSamples(int sysID) { if (memPos>=16777216) break; sampleOffSegaPCM[i]=memPos; for (unsigned int j=0; jdata8[j]+0x80); + if (j>=sample->samples) { + sampleMem[memPos++]=0; + } else { + sampleMem[memPos++]=((unsigned char)sample->data8[j]+0x80); + } sampleEndSegaPCM[i]=((memPos+0xff)>>8)-1; if (memPos>=16777216) break; } diff --git a/src/engine/platform/snes.cpp b/src/engine/platform/snes.cpp index 1fb5a12cd..48351493c 100644 --- a/src/engine/platform/snes.cpp +++ b/src/engine/platform/snes.cpp @@ -336,7 +336,10 @@ int DivPlatformSNES::dispatch(DivCommand c) { } chan[c.chan].ws.init(ins,chan[c.chan].wtLen,15,chan[c.chan].insChanged); } else { - if (c.value!=DIV_NOTE_NULL) chan[c.chan].sample=ins->amiga.getSample(c.value); + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].sample=ins->amiga.getSample(c.value); + c.value=ins->amiga.getFreq(c.value); + } chan[c.chan].useWave=false; } if (chan[c.chan].useWave || chan[c.chan].sample<0 || chan[c.chan].sample>=parent->song.sampleLen) { diff --git a/src/engine/platform/swan.cpp b/src/engine/platform/swan.cpp index 4e0b83724..209e2fc2c 100644 --- a/src/engine/platform/swan.cpp +++ b/src/engine/platform/swan.cpp @@ -261,7 +261,10 @@ int DivPlatformSwan::dispatch(DivCommand c) { dacPos=0; dacPeriod=0; if (ins->type==DIV_INS_AMIGA || ins->amiga.useSample) { - if (c.value!=DIV_NOTE_NULL) dacSample=ins->amiga.getSample(c.value); + if (c.value!=DIV_NOTE_NULL) { + dacSample=ins->amiga.getSample(c.value); + c.value=ins->amiga.getFreq(c.value); + } if (dacSample<0 || dacSample>=parent->song.sampleLen) { dacSample=-1; if (dumpWrites) postWrite(0xffff0002,0); diff --git a/src/engine/platform/vera.cpp b/src/engine/platform/vera.cpp index 820f399a5..6b2dd56cb 100644 --- a/src/engine/platform/vera.cpp +++ b/src/engine/platform/vera.cpp @@ -235,7 +235,11 @@ int DivPlatformVERA::dispatch(DivCommand c) { if (c.chan<16) { rWriteLo(c.chan,2,chan[c.chan].vol); } else { - if (c.value!=DIV_NOTE_NULL) chan[16].pcm.sample=parent->getIns(chan[16].ins,DIV_INS_VERA)->amiga.getSample(c.value); + if (c.value!=DIV_NOTE_NULL) { + DivInstrument* ins=parent->getIns(chan[16].ins,DIV_INS_VERA); + chan[16].pcm.sample=ins->amiga.getSample(c.value); + c.value=ins->amiga.getFreq(c.value); + } if (chan[16].pcm.sample<0 || chan[16].pcm.sample>=parent->song.sampleLen) { chan[16].pcm.sample=-1; } diff --git a/src/engine/platform/vrc6.cpp b/src/engine/platform/vrc6.cpp index 912a589ea..2aeb3897d 100644 --- a/src/engine/platform/vrc6.cpp +++ b/src/engine/platform/vrc6.cpp @@ -179,8 +179,6 @@ void DivPlatformVRC6::tick(bool sysTick) { if (chan[i].std.phaseReset.val && chan[i].active) { if ((i!=2) && (!chan[i].pcm)) { if (dumpWrites) addWrite(0xffff0002+(i<<8),0); - DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_VRC6); - chan[i].dacSample=ins->amiga.getSample(chan[i].note); if (chan[i].dacSample<0 || chan[i].dacSample>=parent->song.sampleLen) { if (dumpWrites) { chWrite(i,2,0x80); @@ -242,7 +240,10 @@ int DivPlatformVRC6::dispatch(DivCommand c) { if (chan[c.chan].pcm) { if (skipRegisterWrites) break; if (ins->type==DIV_INS_AMIGA || ins->amiga.useSample) { - if (c.value!=DIV_NOTE_NULL) chan[c.chan].dacSample=ins->amiga.getSample(c.value); + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].dacSample=ins->amiga.getSample(c.value); + c.value=ins->amiga.getFreq(c.value); + } if (chan[c.chan].dacSample<0 || chan[c.chan].dacSample>=parent->song.sampleLen) { chan[c.chan].dacSample=-1; if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0); diff --git a/src/engine/platform/x1_010.cpp b/src/engine/platform/x1_010.cpp index 66e4d7bb3..638001740 100644 --- a/src/engine/platform/x1_010.cpp +++ b/src/engine/platform/x1_010.cpp @@ -540,7 +540,10 @@ int DivPlatformX1_010::dispatch(DivCommand c) { if (chan[c.chan].furnacePCM) { chan[c.chan].pcm=true; chan[c.chan].macroInit(ins); - if (c.value!=DIV_NOTE_NULL) chan[c.chan].sample=ins->amiga.getSample(c.value); + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].sample=ins->amiga.getSample(c.value); + c.value=ins->amiga.getFreq(c.value); + } if (chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { DivSample* s=parent->getSample(chan[c.chan].sample); if (isBanked) { diff --git a/src/engine/platform/ym2608.cpp b/src/engine/platform/ym2608.cpp index 523ee701b..92c1caa13 100644 --- a/src/engine/platform/ym2608.cpp +++ b/src/engine/platform/ym2608.cpp @@ -896,7 +896,10 @@ int DivPlatformYM2608::dispatch(DivCommand c) { chan[c.chan].outVol=chan[c.chan].vol; immWrite(0x10b,chan[c.chan].outVol); } - if (c.value!=DIV_NOTE_NULL) chan[c.chan].sample=ins->amiga.getSample(c.value); + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].sample=ins->amiga.getSample(c.value); + c.value=ins->amiga.getFreq(c.value); + } if (chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { DivSample* s=parent->getSample(chan[c.chan].sample); immWrite(0x100,0x01); // reset diff --git a/src/engine/platform/ym2610.cpp b/src/engine/platform/ym2610.cpp index 044fe2c2e..cbc2847c9 100644 --- a/src/engine/platform/ym2610.cpp +++ b/src/engine/platform/ym2610.cpp @@ -827,7 +827,10 @@ int DivPlatformYM2610::dispatch(DivCommand c) { chan[c.chan].outVol=chan[c.chan].vol; immWrite(0x1b,chan[c.chan].outVol); } - if (c.value!=DIV_NOTE_NULL) chan[c.chan].sample=ins->amiga.getSample(c.value); + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].sample=ins->amiga.getSample(c.value); + c.value=ins->amiga.getFreq(c.value); + } if (chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { DivSample* s=parent->getSample(chan[c.chan].sample); immWrite(0x12,(sampleOffB[chan[c.chan].sample]>>8)&0xff); diff --git a/src/engine/platform/ym2610b.cpp b/src/engine/platform/ym2610b.cpp index 20d83dc1f..28ded57d9 100644 --- a/src/engine/platform/ym2610b.cpp +++ b/src/engine/platform/ym2610b.cpp @@ -894,7 +894,10 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { chan[c.chan].outVol=chan[c.chan].vol; immWrite(0x1b,chan[c.chan].outVol); } - if (c.value!=DIV_NOTE_NULL) chan[c.chan].sample=ins->amiga.getSample(c.value); + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].sample=ins->amiga.getSample(c.value); + c.value=ins->amiga.getFreq(c.value); + } if (chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { DivSample* s=parent->getSample(chan[c.chan].sample); immWrite(0x12,(sampleOffB[chan[c.chan].sample]>>8)&0xff); diff --git a/src/engine/platform/ymz280b.cpp b/src/engine/platform/ymz280b.cpp index e10dda43f..a8838ae9a 100644 --- a/src/engine/platform/ymz280b.cpp +++ b/src/engine/platform/ymz280b.cpp @@ -212,7 +212,10 @@ int DivPlatformYMZ280B::dispatch(DivCommand c) { DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA); chan[c.chan].isNewYMZ=ins->type==DIV_INS_YMZ280B; chan[c.chan].macroVolMul=ins->type==DIV_INS_AMIGA?64:255; - if (c.value!=DIV_NOTE_NULL) chan[c.chan].sample=ins->amiga.getSample(c.value); + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].sample=ins->amiga.getSample(c.value); + c.value=ins->amiga.getFreq(c.value); + } if (c.value!=DIV_NOTE_NULL) { chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); } diff --git a/src/engine/platform/zxbeeperquadtone.cpp b/src/engine/platform/zxbeeperquadtone.cpp index 114c8b66d..8bfce1701 100644 --- a/src/engine/platform/zxbeeperquadtone.cpp +++ b/src/engine/platform/zxbeeperquadtone.cpp @@ -169,10 +169,11 @@ int DivPlatformZXBeeperQuadTone::dispatch(DivCommand c) { } else { DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA); if (c.value!=DIV_NOTE_NULL) { + curSample=ins->amiga.getSample(c.value); + c.value=ins->amiga.getFreq(c.value); chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); chan[c.chan].freqChanged=true; chan[c.chan].note=c.value; - curSample=ins->amiga.getSample(c.value); // TODO support offset commands curSamplePos=0; curSamplePeriod=0; diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 6c3db8053..1ca9df58c 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -1412,6 +1412,15 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { ret=true; shallStop=false; shallStopSched=false; + // reset all chan oscs + for (int i=0; igetOscBuffer(dispatchChanOfChan[i]); + if (buf!=NULL) { + memset(buf->data,0,65536*sizeof(short)); + buf->needle=0; + buf->readNeedle=0; + } + } return ret; } diff --git a/src/engine/waveSynth.cpp b/src/engine/waveSynth.cpp index caa7294b8..25f68eac2 100644 --- a/src/engine/waveSynth.cpp +++ b/src/engine/waveSynth.cpp @@ -212,7 +212,6 @@ void DivWaveSynth::setWidth(int val) { void DivWaveSynth::changeWave1(int num) { DivWavetable* w1=e->getWave(num); - logV("changeWave1 (%d)",width); if (width<1) return; for (int i=0; imax<1 || w1->len<1) { diff --git a/src/fileutils.cpp b/src/fileutils.cpp index a58f92a0a..f729bd5d6 100644 --- a/src/fileutils.cpp +++ b/src/fileutils.cpp @@ -20,6 +20,13 @@ #include "fileutils.h" #ifdef _WIN32 #include "utfutils.h" +#include +#include +#include +#else +#include +#include +#include #endif FILE* ps_fopen(const char* path, const char* mode) { @@ -29,3 +36,67 @@ FILE* ps_fopen(const char* path, const char* mode) { return fopen(path,mode); #endif } + +// TODO: copy in case of failure +bool moveFiles(const char* src, const char* dest) { +#ifdef _WIN32 + return MoveFileW(utf8To16(src).c_str(),utf8To16(dest).c_str()); +#else + if (rename(src,dest)==-1) { + return false; + } + return true; +#endif +} + +bool deleteFile(const char* path) { +#ifdef _WIN32 + return DeleteFileW(utf8To16(path).c_str()); +#else + return (unlink(path)==0); +#endif +} + +int fileExists(const char* path) { +#ifdef _WIN32 + if (PathFileExistsW(utf8To16(path).c_str())) return 1; + // which errors could PathFileExists possibly throw? + switch (GetLastError()) { + case ERROR_FILE_EXISTS: + return 1; + break; + case ERROR_FILE_NOT_FOUND: + case ERROR_PATH_NOT_FOUND: + case ERROR_INVALID_DRIVE: + case ERROR_DEV_NOT_EXIST: + case ERROR_NETNAME_DELETED: + case ERROR_BAD_NET_NAME: + return 0; + break; + } + return -1; +#else + if (access(path,F_OK)==0) return 1; + if (errno==ENOENT) return 0; + return -1; +#endif +} + +bool dirExists(const char* what) { +#ifdef _WIN32 + WString ws=utf8To16(what); + return (PathIsDirectoryW(ws.c_str())!=FALSE); +#else + struct stat st; + if (stat(what,&st)<0) return false; + return (st.st_mode&S_IFDIR); +#endif +} + +bool makeDir(const char* path) { +#ifdef _WIN32 + return (SHCreateDirectory(NULL,utf8To16(path).c_str())==ERROR_SUCCESS); +#else + return (mkdir(path,0755)==0); +#endif +} diff --git a/src/fileutils.h b/src/fileutils.h index ded2bfb41..17ff89883 100644 --- a/src/fileutils.h +++ b/src/fileutils.h @@ -22,5 +22,11 @@ #include FILE* ps_fopen(const char* path, const char* mode); +bool moveFiles(const char* src, const char* dest); +bool deleteFile(const char* path); +// returns 1 if file exists, 0 if it doesn't and -1 on error. +int fileExists(const char* path); +bool dirExists(const char* what); +bool makeDir(const char* path); #endif diff --git a/src/gui/doAction.cpp b/src/gui/doAction.cpp index ff67818b9..9345181b1 100644 --- a/src/gui/doAction.cpp +++ b/src/gui/doAction.cpp @@ -50,9 +50,7 @@ void FurnaceGUI::doAction(int what) { if (modified) { showWarning("Unsaved changes! Save changes before opening backup?",GUI_WARN_OPEN_BACKUP); } else { - if (load(backupPath)>0) { - showError("No backup available! (or unable to open it)"); - } + openFileDialog(GUI_FILE_OPEN_BACKUP); } break; case GUI_ACTION_SAVE: diff --git a/src/gui/editControls.cpp b/src/gui/editControls.cpp index 98babf574..2193ef609 100644 --- a/src/gui/editControls.cpp +++ b/src/gui/editControls.cpp @@ -524,6 +524,11 @@ void FurnaceGUI::drawMobileControls() { openFileDialog(GUI_FILE_EXPORT_CMDSTREAM); } + if (ImGui::Button("Restore Backup")) { + mobileMenuOpen=false; + doAction(GUI_ACTION_OPEN_BACKUP); + } + ImGui::Separator(); if (ImGui::BeginTabBar("MobileSong")) { diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 6213bdfde..8947d566e 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -50,13 +50,15 @@ #include #include "../utfutils.h" #define LAYOUT_INI "\\layout.ini" -#define BACKUP_FUR "\\backup.fur" +#define BACKUPS_DIR "\\backups" #else +#include +#include #include #include #include #define LAYOUT_INI "/layout.ini" -#define BACKUP_FUR "/backup.fur" +#define BACKUPS_DIR "/backups" #endif #ifdef IS_MOBILE @@ -528,18 +530,22 @@ void FurnaceGUI::setFileName(String name) { if (index>=4095) break; } ret[index]=0; + backupLock.lock(); if (GetFullPathNameW(ws.c_str(),4095,ret,NULL)==0) { curFileName=name; } else { curFileName=utf16To8(ret); } + backupLock.unlock(); #else char ret[4096]; + backupLock.lock(); if (realpath(name.c_str(),ret)==NULL) { curFileName=name; } else { curFileName=ret; } + backupLock.unlock(); #endif updateWindowTitle(); pushRecentFile(curFileName); @@ -1076,10 +1082,6 @@ void FurnaceGUI::stop() { e->stop(); curNibble=false; orderNibble=false; - activeNotes.clear(); - memset(chanOscVol,0,DIV_MAX_CHANS*sizeof(float)); - memset(chanOscPitch,0,DIV_MAX_CHANS*sizeof(float)); - memset(chanOscBright,0,DIV_MAX_CHANS*sizeof(float)); } void FurnaceGUI::previewNote(int refChan, int note, bool autoNote) { @@ -1458,15 +1460,8 @@ void FurnaceGUI::keyUp(SDL_Event& ev) { // nothing for now } -bool dirExists(String what) { -#ifdef _WIN32 - WString ws=utf8To16(what.c_str()); - return (PathIsDirectoryW(ws.c_str())!=FALSE); -#else - struct stat st; - if (stat(what.c_str(),&st)<0) return false; - return (st.st_mode&S_IFDIR); -#endif +bool dirExists(String s) { + return dirExists(s.c_str()); } void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { @@ -1483,6 +1478,19 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { dpiScale ); break; + case GUI_FILE_OPEN_BACKUP: + if (!dirExists(backupPath)) { + showError("no backups made yet!"); + break; + } + hasOpened=fileDialog->openLoad( + "Restore Backup", + {"Furnace song", "*.fur"}, + "Furnace song{.fur}", + backupPath+String(DIR_SEPARATOR_STR), + dpiScale + ); + break; case GUI_FILE_SAVE: if (!dirExists(workingDirSong)) workingDirSong=getHomeDir(); hasOpened=fileDialog->openSave( @@ -2009,7 +2017,9 @@ int FurnaceGUI::save(String path, int dmfVersion) { #endif fclose(outFile); w->finish(); + backupLock.lock(); curFileName=path; + backupLock.unlock(); modified=false; updateWindowTitle(); if (!e->getWarnings().empty()) { @@ -2074,7 +2084,9 @@ int FurnaceGUI::load(String path) { return 1; } } + backupLock.lock(); curFileName=path; + backupLock.unlock(); modified=false; curNibble=false; orderNibble=false; @@ -2100,7 +2112,7 @@ int FurnaceGUI::load(String path) { void FurnaceGUI::pushRecentFile(String path) { if (path.empty()) return; - if (path==backupPath) return; + if (path.find(backupPath)==0) return; for (int i=0; i<(int)recentFile.size(); i++) { if (recentFile[i]==path) { recentFile.erase(recentFile.begin()+i); @@ -2114,6 +2126,44 @@ void FurnaceGUI::pushRecentFile(String path) { } } +void FurnaceGUI::delFirstBackup(String name) { + std::vector listOfFiles; +#ifdef _WIN32 + String findPath=backupPath+String(DIR_SEPARATOR_STR)+name+String("*.fur"); + WIN32_FIND_DATAW next; + HANDLE backDir=FindFirstFileW(utf8To16(findPath.c_str()).c_str(),&next); + if (backDir!=INVALID_HANDLE_VALUE) { + do { + listOfFiles.push_back(utf16To8(next.cFileName)); + } while (FindNextFileW(backDir,&next)!=0); + FindClose(backDir); + } +#else + DIR* backDir=opendir(backupPath.c_str()); + if (backDir==NULL) { + logW("could not open backups dir!"); + return; + } + while (true) { + struct dirent* next=readdir(backDir); + if (next==NULL) break; + if (strstr(next->d_name,name.c_str())!=next->d_name) continue; + listOfFiles.push_back(String(next->d_name)); + } + closedir(backDir); +#endif + + std::sort(listOfFiles.begin(),listOfFiles.end(),[](const String& a, const String& b) -> bool { + return strcmp(a.c_str(),b.c_str())<0; + }); + + int totalDelete=((int)listOfFiles.size())-5; + for (int i=0; imobileUI=true; } else { ImGui::GetIO().IniFilename=NULL; - ImGui::LoadIniSettingsFromDisk(finalLayoutPath); + if (!ImGui::LoadIniSettingsFromDisk(finalLayoutPath,true)) { + reportError(fmt::sprintf("could NOT load layout! %s",strerror(errno))); + ImGui::LoadIniSettingsFromMemory(defaultLayout); + } ImGui::GetIO().ConfigFlags&=~ImGuiConfigFlags_InertialScrollEnable; ImGui::GetIO().ConfigFlags&=~ImGuiConfigFlags_NoHoverColors; fileDialog->mobileUI=false; @@ -3568,6 +3623,23 @@ bool FurnaceGUI::loop() { } } + if (!e->isRunning()) { + activeNotes.clear(); + memset(chanOscVol,0,DIV_MAX_CHANS*sizeof(float)); + memset(chanOscPitch,0,DIV_MAX_CHANS*sizeof(float)); + memset(chanOscBright,0,DIV_MAX_CHANS*sizeof(float)); + + e->synchronized([this]() { + for (int i=0; igetTotalChannelCount(); i++) { + DivDispatchOscBuffer* buf=e->getOscBuffer(i); + if (buf!=NULL) { + buf->needle=0; + buf->readNeedle=0; + } + } + }); + } + layoutTimeBegin=SDL_GetPerformanceCounter(); ImGui_ImplSDLRenderer_NewFrame(); @@ -3623,7 +3695,7 @@ bool FurnaceGUI::loop() { } ImGui::Separator(); if (ImGui::MenuItem("save",BIND_FOR(GUI_ACTION_SAVE))) { - if (curFileName=="" || curFileName==backupPath || e->song.version>=0xff00) { + if (curFileName=="" || (curFileName.find(backupPath)==0) || e->song.version>=0xff00) { openFileDialog(GUI_FILE_SAVE); } else { if (save(curFileName,e->song.isDMF?e->song.version:0)>0) { @@ -4318,6 +4390,8 @@ bool FurnaceGUI::loop() { case GUI_FILE_TEST_SAVE: workingDirTest=fileDialog->getPath()+DIR_SEPARATOR_STR; break; + case GUI_FILE_OPEN_BACKUP: + break; } if (fileDialog->isError()) { #if defined(_WIN32) || defined(__APPLE__) @@ -4394,6 +4468,7 @@ bool FurnaceGUI::loop() { String copyOfName=fileName; switch (curFileDialog) { case GUI_FILE_OPEN: + case GUI_FILE_OPEN_BACKUP: if (load(copyOfName)>0) { showError(fmt::sprintf("Error while loading file! (%s)",lastError)); } @@ -4422,9 +4497,7 @@ bool FurnaceGUI::loop() { nextFile=""; break; case GUI_WARN_OPEN_BACKUP: - if (load(backupPath)>0) { - showError("No backup available! (or unable to open it)"); - } + openFileDialog(GUI_FILE_OPEN_BACKUP); break; default: break; @@ -4891,7 +4964,7 @@ bool FurnaceGUI::loop() { case GUI_WARN_QUIT: if (ImGui::Button("Yes")) { ImGui::CloseCurrentPopup(); - if (curFileName=="" || curFileName==backupPath || e->song.version>=0xff00) { + if (curFileName=="" || curFileName.find(backupPath)==0 || e->song.version>=0xff00) { openFileDialog(GUI_FILE_SAVE); postWarnAction=GUI_WARN_QUIT; } else { @@ -4915,7 +4988,7 @@ bool FurnaceGUI::loop() { case GUI_WARN_NEW: if (ImGui::Button("Yes")) { ImGui::CloseCurrentPopup(); - if (curFileName=="" || curFileName==backupPath || e->song.version>=0xff00) { + if (curFileName=="" || curFileName.find(backupPath)==0 || e->song.version>=0xff00) { openFileDialog(GUI_FILE_SAVE); postWarnAction=GUI_WARN_NEW; } else { @@ -4939,7 +5012,7 @@ bool FurnaceGUI::loop() { case GUI_WARN_OPEN: if (ImGui::Button("Yes")) { ImGui::CloseCurrentPopup(); - if (curFileName=="" || curFileName==backupPath || e->song.version>=0xff00) { + if (curFileName=="" || curFileName.find(backupPath)==0 || e->song.version>=0xff00) { openFileDialog(GUI_FILE_SAVE); postWarnAction=GUI_WARN_OPEN; } else { @@ -4963,25 +5036,21 @@ bool FurnaceGUI::loop() { case GUI_WARN_OPEN_BACKUP: if (ImGui::Button("Yes")) { ImGui::CloseCurrentPopup(); - if (curFileName=="" || curFileName==backupPath || e->song.version>=0xff00) { + if (curFileName=="" || curFileName.find(backupPath)==0 || e->song.version>=0xff00) { openFileDialog(GUI_FILE_SAVE); postWarnAction=GUI_WARN_OPEN_BACKUP; } else { if (save(curFileName,e->song.isDMF?e->song.version:0)>0) { showError(fmt::sprintf("Error while saving file! (%s)",lastError)); } else { - if (load(backupPath)>0) { - showError("No backup available! (or unable to open it)"); - } + openFileDialog(GUI_FILE_OPEN_BACKUP); } } } ImGui::SameLine(); if (ImGui::Button("No")) { ImGui::CloseCurrentPopup(); - if (load(backupPath)>0) { - showError("No backup available! (or unable to open it)"); - } + openFileDialog(GUI_FILE_OPEN_BACKUP); } ImGui::SameLine(); if (ImGui::Button("Cancel")) { @@ -4991,7 +5060,7 @@ bool FurnaceGUI::loop() { case GUI_WARN_OPEN_DROP: if (ImGui::Button("Yes")) { ImGui::CloseCurrentPopup(); - if (curFileName=="" || curFileName==backupPath || e->song.version>=0xff00) { + if (curFileName=="" || curFileName.find(backupPath)==0 || e->song.version>=0xff00) { openFileDialog(GUI_FILE_SAVE); postWarnAction=GUI_WARN_OPEN_DROP; } else { @@ -5025,7 +5094,9 @@ bool FurnaceGUI::loop() { ImGui::CloseCurrentPopup(); if (!mobileUI) { ImGui::LoadIniSettingsFromMemory(defaultLayout); - ImGui::SaveIniSettingsToDisk(finalLayoutPath); + if (!ImGui::SaveIniSettingsToDisk(finalLayoutPath,true)) { + reportError(fmt::sprintf("could NOT save layout! %s",strerror(errno))); + } } } ImGui::SameLine(); @@ -5401,16 +5472,73 @@ bool FurnaceGUI::loop() { backupTimer=(backupTimer-ImGui::GetIO().DeltaTime); if (backupTimer<=0) { backupTask=std::async(std::launch::async,[this]() -> bool { - if (backupPath==curFileName) { + backupLock.lock(); + logV("backupPath: %s",backupPath); + logV("curFileName: %s",curFileName); + if (curFileName.find(backupPath)==0) { logD("backup file open. not saving backup."); + backupTimer=30.0; + backupLock.unlock(); return true; } + if (!dirExists(backupPath.c_str())) { + if (!makeDir(backupPath.c_str())) { + logW("could not create backup directory!"); + backupTimer=30.0; + backupLock.unlock(); + return false; + } + } logD("saving backup..."); SafeWriter* w=e->saveFur(true); logV("writing file..."); if (w!=NULL) { - FILE* outFile=ps_fopen(backupPath.c_str(),"wb"); + size_t sepPos=curFileName.rfind(DIR_SEPARATOR); + String backupPreBaseName; + String backupBaseName; + String backupFileName; + if (sepPos==String::npos) { + backupPreBaseName=curFileName; + } else { + backupPreBaseName=curFileName.substr(sepPos+1); + } + + size_t dotPos=backupPreBaseName.rfind('.'); + if (dotPos!=String::npos) { + backupPreBaseName=backupPreBaseName.substr(0,dotPos); + } + + for (char i: backupPreBaseName) { + if (backupBaseName.size()>=48) break; + if ((i>='0' && i<='9') || (i>='A' && i<='Z') || (i>='a' && i<='z') || i=='_' || i=='-' || i==' ') backupBaseName+=i; + } + + if (backupBaseName.empty()) backupBaseName="untitled"; + + backupFileName=backupBaseName; + + time_t curTime=time(NULL); + struct tm curTM; +#ifdef _WIN32 + struct tm* tempTM=localtime(&curTime); + if (tempTM==NULL) { + backupFileName+="-unknownTime.fur"; + } else { + curTM=*tempTM; + backupFileName+=fmt::sprintf("-%d%.2d%.2d-%.2d%.2d%.2d.fur",curTM.tm_year+1900,curTM.tm_mon+1,curTM.tm_mday,curTM.tm_hour,curTM.tm_min,curTM.tm_sec); + } +#else + if (localtime_r(&curTime,&curTM)==NULL) { + backupFileName+="-unknownTime.fur"; + } else { + backupFileName+=fmt::sprintf("-%d%.2d%.2d-%.2d%.2d%.2d.fur",curTM.tm_year+1900,curTM.tm_mon+1,curTM.tm_mday,curTM.tm_hour,curTM.tm_min,curTM.tm_sec); + } +#endif + + String finalPath=backupPath+String(DIR_SEPARATOR_STR)+backupFileName; + + FILE* outFile=ps_fopen(finalPath.c_str(),"wb"); if (outFile!=NULL) { if (fwrite(w->getFinalBuf(),1,w->size(),outFile)!=w->size()) { logW("did not write backup entirely: %s!",strerror(errno)); @@ -5421,9 +5549,13 @@ bool FurnaceGUI::loop() { logW("could not save backup: %s!",strerror(errno)); w->finish(); } + + // delete previous backup if there are too many + delFirstBackup(backupBaseName); } logD("backup saved."); backupTimer=30.0; + backupLock.unlock(); return true; }); } @@ -5837,7 +5969,11 @@ bool FurnaceGUI::init() { } strncpy(finalLayoutPath,(e->getConfigPath()+String(LAYOUT_INI)).c_str(),4095); - backupPath=e->getConfigPath()+String(BACKUP_FUR); + backupPath=e->getConfigPath(); + if (backupPath.size()>0) { + if (backupPath[backupPath.size()-1]==DIR_SEPARATOR) backupPath.resize(backupPath.size()-1); + } + backupPath+=String(BACKUPS_DIR); prepareLayout(); ImGui::GetIO().ConfigFlags|=ImGuiConfigFlags_DockingEnable; @@ -5907,7 +6043,9 @@ bool FurnaceGUI::init() { void FurnaceGUI::commitState() { if (!mobileUI) { - ImGui::SaveIniSettingsToDisk(finalLayoutPath); + if (!ImGui::SaveIniSettingsToDisk(finalLayoutPath,true)) { + reportError(fmt::sprintf("could NOT save layout! %s",strerror(errno))); + } } e->setConf("configVersion",(int)DIV_ENGINE_VERSION); diff --git a/src/gui/gui.h b/src/gui/gui.h index 3433527e3..c712b531a 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -347,6 +347,7 @@ enum FurnaceGUIMobileScenes { enum FurnaceGUIFileDialogs { GUI_FILE_OPEN, + GUI_FILE_OPEN_BACKUP, GUI_FILE_SAVE, GUI_FILE_SAVE_DMF, GUI_FILE_SAVE_DMF_LEGACY, @@ -2080,6 +2081,7 @@ class FurnaceGUI { int loadStream(String path); void pushRecentFile(String path); void exportAudio(String path, DivAudioExportModes mode); + void delFirstBackup(String name); bool parseSysEx(unsigned char* data, size_t len); diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 92692ce5d..86abb7817 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -4406,11 +4406,10 @@ void FurnaceGUI::drawInsEdit() { ImGui::BeginDisabled(ins->amiga.useWave); P(ImGui::Checkbox("Use sample map",&ins->amiga.useNoteMap)); if (ins->amiga.useNoteMap) { - // TODO: frequency map? - if (ImGui::BeginTable("NoteMap",2/*3*/,ImGuiTableFlags_ScrollY|ImGuiTableFlags_Borders|ImGuiTableFlags_SizingStretchSame)) { + if (ImGui::BeginTable("NoteMap",3,ImGuiTableFlags_ScrollY|ImGuiTableFlags_Borders|ImGuiTableFlags_SizingStretchSame)) { ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch); - //ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch); ImGui::TableSetupScrollFreeze(0,1); @@ -4418,8 +4417,8 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); ImGui::TableNextColumn(); ImGui::Text("Sample"); - /*ImGui::TableNextColumn(); - ImGui::Text("Frequency");*/ + ImGui::TableNextColumn(); + ImGui::Text("Note"); for (int i=0; i<120; i++) { DivInstrumentAmiga::SampleMap& sampleMap=ins->amiga.noteMap[i]; ImGui::TableNextRow(); @@ -4443,17 +4442,28 @@ void FurnaceGUI::drawInsEdit() { id=fmt::sprintf("%d: %s",j,e->song.sample[j]->name); if (ImGui::Selectable(id.c_str(),sampleMap.map==j)) { PARAMETER sampleMap.map=j; - if (sampleMap.freq<=0) sampleMap.freq=(int)((double)e->song.sample[j]->centerRate*pow(2.0,((double)i-48.0)/12.0)); } } ImGui::EndCombo(); } - /*ImGui::TableNextColumn(); + + ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::InputInt("##SF",&sampleMap.freq,50,500)) { PARAMETER - if (sampleMap.freq<0) sampleMap.freq=0; - if (sampleMap.freq>262144) sampleMap.freq=262144; - }*/ + const char* nName="???"; + if ((sampleMap.freq+60)>0 && (sampleMap.freq+60)<180) { + nName=noteNames[sampleMap.freq+60]; + } + if (ImGui::BeginCombo("##SN",nName)) { + for (int j=0; j<180; j++) { + const char* nName2="???"; + nName2=noteNames[j]; + if (ImGui::Selectable(nName2,(sampleMap.freq+60)==j)) { + sampleMap.freq=j-60; + } + } + ImGui::EndCombo(); + } + ImGui::PopID(); } ImGui::EndTable(); diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index 33924b846..09369aa08 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -1109,11 +1109,23 @@ void FurnaceGUI::initSystemPresets() { } ); ENTRY( - "Commander X16", { + "Commander X16 (VERA only)", { + CH(DIV_SYSTEM_VERA, 1.0f, 0, "") + } + ); + ENTRY( + "Commander X16 (with OPM)", { CH(DIV_SYSTEM_VERA, 1.0f, 0, ""), CH(DIV_SYSTEM_YM2151, 1.0f, 0, "") } ); + ENTRY( + "Commander X16 (with Twin OPL3)", { + CH(DIV_SYSTEM_VERA, 1.0f, 0, ""), + CH(DIV_SYSTEM_OPL3, 1.0f, 0, ""), + CH(DIV_SYSTEM_OPL3, 1.0f, 0, "") + } + ); ENTRY( "TI-99/4A", { CH(DIV_SYSTEM_SMS, 1.0f, 0, diff --git a/src/log.cpp b/src/log.cpp index 5f24269d8..9cfabe901 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -18,13 +18,18 @@ */ #include "ta-log.h" +#include "fileutils.h" #include #include +#ifdef _WIN32 +#include +#endif + #ifdef IS_MOBILE int logLevel=LOGLEVEL_TRACE; #else -int logLevel=LOGLEVEL_INFO; +int logLevel=LOGLEVEL_TRACE; // until done #endif FILE* logFile; @@ -133,7 +138,16 @@ int writeLog(int level, const char* msg, fmt::printf_args args) { } void initLog() { - // initalize log buffer + // initialize coloring on Windows +#ifdef _WIN32 + HANDLE winout=GetStdHandle(STD_OUTPUT_HANDLE); + int termprop=0; + GetConsoleMode(winout,(LPDWORD)&termprop); + termprop|=ENABLE_VIRTUAL_TERMINAL_PROCESSING; + SetConsoleMode(winout,termprop); +#endif + + // initialize log buffer logPosition=0; for (int i=0; i=0; i--) { + if (i>0) { + snprintf(oldPath,4095,"%s.%d",path,i); + } else { + strncpy(oldPath,path,4095); + } + snprintf(newPath,4095,"%s.%d",path,i+1); + + if (i>=4) { + deleteFile(oldPath); + } else { + moveFiles(oldPath,newPath); + } + } + } // open log file - if ((logFile=fopen(path,"w+"))==NULL) { + if ((logFile=ps_fopen(path,"w+"))==NULL) { logFileAvail=false; logW("could not open log file! (%s)",strerror(errno)); return false; diff --git a/src/main.cpp b/src/main.cpp index 1a24f3b02..8ba66f7e7 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -28,7 +28,6 @@ #include "engine/engine.h" #ifdef _WIN32 -#define WIN32_LEAN_AND_MEAN #include #include #include diff --git a/src/ta-utils.h b/src/ta-utils.h index f0c896d00..0b81cc853 100644 --- a/src/ta-utils.h +++ b/src/ta-utils.h @@ -66,4 +66,6 @@ struct TAParam { func(f) {} }; +void reportError(String what); + #endif