Merge branch 'master' of https://github.com/tildearrow/furnace into k053260

This commit is contained in:
cam900 2023-04-08 08:51:00 +09:00
commit 548aa5e6ac
56 changed files with 818 additions and 179 deletions

View File

@ -24,7 +24,7 @@ jobs:
#- { name: 'Windows MinGW x86_64', os: ubuntu-20.04, compiler: mingw, arch: x86_64 } #- { 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 x86_64', os: macos-latest, arch: x86_64 }
- { name: 'macOS ARM', os: macos-latest, arch: arm64 } - { 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 } #- { name: 'Linux ARM', os: ubuntu-18.04, arch: armhf }
fail-fast: false fail-fast: false

View File

@ -793,7 +793,7 @@ endif()
if(ANDROID AND NOT TERMUX) if(ANDROID AND NOT TERMUX)
add_library(furnace SHARED ${USED_SOURCES}) add_library(furnace SHARED ${USED_SOURCES})
elseif(WIN32) elseif(WIN32)
add_executable(furnace WIN32 ${USED_SOURCES}) add_executable(furnace ${USED_SOURCES})
else() else()
add_executable(furnace ${USED_SOURCES}) add_executable(furnace ${USED_SOURCES})
endif() endif()

16
TODO.md Normal file
View File

@ -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

Binary file not shown.

BIN
demos/multichip/collab.fur Normal file

Binary file not shown.

View File

@ -837,6 +837,8 @@ CODE
#include <stdint.h> // intptr_t #include <stdint.h> // intptr_t
#endif #endif
#include "../../src/fileutils.h"
// [Windows] On non-Visual Studio compilers, we default to IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS unless explicitly enabled // [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) #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 #define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS
@ -12457,14 +12459,47 @@ void ImGui::ClearIniSettings()
g.SettingsHandlers[handler_n].ClearAllFn(&g, &g.SettingsHandlers[handler_n]); 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; 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<file_data_size; j++) {
if (file_data[j]!='\r' && file_data[j]!='\n' && file_data[j]!=' ') {
viable=true;
break;
}
}
if (!viable) {
IM_FREE(file_data);
file_data=NULL;
file_data_size=0;
} else {
break;
}
}
} else {
file_data=(char*)ImFileLoadToMemory(ini_filename, "rb", &file_data_size);
}
if (!file_data) if (!file_data)
return; return false;
LoadIniSettingsFromMemory(file_data, (size_t)file_data_size); LoadIniSettingsFromMemory(file_data, (size_t)file_data_size);
IM_FREE(file_data); IM_FREE(file_data);
return true;
} }
// Zero-tolerance, no error reporting, cheap .ini parsing // Zero-tolerance, no error reporting, cheap .ini parsing
@ -12538,20 +12573,45 @@ void ImGui::LoadIniSettingsFromMemory(const char* ini_data, size_t ini_size)
g.SettingsHandlers[handler_n].ApplyAllFn(&g, &g.SettingsHandlers[handler_n]); g.SettingsHandlers[handler_n].ApplyAllFn(&g, &g.SettingsHandlers[handler_n]);
} }
void ImGui::SaveIniSettingsToDisk(const char* ini_filename) bool ImGui::SaveIniSettingsToDisk(const char* ini_filename, bool redundancy)
{ {
ImGuiContext& g = *GImGui; ImGuiContext& g = *GImGui;
g.SettingsDirtyTimer = 0.0f; g.SettingsDirtyTimer = 0.0f;
if (!ini_filename) if (!ini_filename)
return; return false;
if (redundancy) {
char oldPath[4096];
char newPath[4096];
if (fileExists(ini_filename)==1) {
for (int i=4; i>=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; size_t ini_data_size = 0;
const char* ini_data = SaveIniSettingsToMemory(&ini_data_size); const char* ini_data = SaveIniSettingsToMemory(&ini_data_size);
ImFileHandle f = ImFileOpen(ini_filename, "wt"); ImFileHandle f = ImFileOpen(ini_filename, "wt");
if (!f) if (!f)
return; return false;
ImFileWrite(ini_data, sizeof(char), ini_data_size, f); 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); 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 // Call registered handlers (e.g. SettingsHandlerWindow_WriteAll() + custom handlers) to write their stuff into a text buffer

View File

@ -950,9 +950,9 @@ namespace ImGui
// - The disk functions are automatically called if io.IniFilename != NULL (default is "imgui.ini"). // - 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. // - 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). // - 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 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. 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 // Debug Utilities

View File

@ -32,6 +32,15 @@ these fields are 0 in format versions prior to 100 (0.6pre1).
the format versions are: 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 - 143: Furnace 0.6pre4
- 142: Furnace dev142 - 142: Furnace dev142
- 141: Furnace Tournament Edition (for intro tune contest) - 141: Furnace Tournament Edition (for intro tune contest)

View File

@ -389,7 +389,7 @@ the sample map format:
``` ```
size | description size | description
-----|------------------------------------ -----|------------------------------------
2 | note to play 2 | note to play (>=152) or reserved
2 | sample to play 2 | sample to play
``` ```

View File

@ -23,21 +23,54 @@
#include "../fileutils.h" #include "../fileutils.h"
#include <fmt/printf.h> #include <fmt/printf.h>
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"); FILE* f=ps_fopen(path,"wb");
if (f==NULL) { if (f==NULL) {
logW("could not write config file! %s",strerror(errno)); logW("could not write config file! %s",strerror(errno));
reportError(fmt::sprintf("could not write config file! %s",strerror(errno)));
return false; return false;
} }
for (auto& i: conf) { for (auto& i: conf) {
String toWrite=fmt::sprintf("%s=%s\n",i.first,i.second); String toWrite=fmt::sprintf("%s=%s\n",i.first,i.second);
if (fwrite(toWrite.c_str(),1,toWrite.size(),f)!=toWrite.size()) { if (fwrite(toWrite.c_str(),1,toWrite.size(),f)!=toWrite.size()) {
logW("could not write config file! %s",strerror(errno)); 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); fclose(f);
deleteFile(path);
return false; return false;
} }
} }
fclose(f); fclose(f);
logD("config file written successfully.");
return true; return true;
} }
@ -63,6 +96,7 @@ void DivConfig::parseLine(const char* line) {
String value=""; String value="";
bool keyOrValue=false; bool keyOrValue=false;
for (const char* i=line; *i; i++) { for (const char* i=line; *i; i++) {
if (*i=='\r') continue;
if (*i=='\n') continue; if (*i=='\n') continue;
if (keyOrValue) { if (keyOrValue) {
value+=*i; 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]; char line[4096];
FILE* f=ps_fopen(path,"rb"); logD("opening config for read: %s",path);
if (f==NULL) {
if (createOnFail) { FILE* f=NULL;
logI("creating default config.");
return save(path); if (redundancy) {
} else { unsigned char* readBuf=new unsigned char[CHECK_BUF_SIZE];
return false; size_t readBufLen=0;
for (int i=0; i<REDUNDANCY_NUM_ATTEMPTS; i++) {
bool viable=false;
if (i>0) {
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<readBufLen; j++) {
if (readBuf[j]!='\r' && readBuf[j]!='\n' && readBuf[j]!=' ') {
viable=true;
break;
}
}
if (viable) break;
}
// there's something
if (viable) {
if (fseek(f,0,SEEK_SET)==-1) {
logV("fseek(): %s",strerror(errno));
viable=false;
} else {
break;
}
}
// close it (because there's nothing)
fclose(f);
f=NULL;
}
delete[] readBuf;
// we couldn't read at all
if (f==NULL) {
logD("config does not exist");
if (createOnFail) {
logI("creating default config.");
reportError(fmt::sprintf("Creating default config: %s",strerror(errno)));
return save(path,redundancy);
} else {
reportError(fmt::sprintf("COULD NOT LOAD CONFIG %s",strerror(errno)));
return false;
}
}
} else {
f=ps_fopen(path,"rb");
if (f==NULL) {
logD("config does not exist");
if (createOnFail) {
logI("creating default config.");
reportError(fmt::sprintf("Creating default config: %s",strerror(errno)));
return save(path);
} else {
reportError(fmt::sprintf("COULD NOT LOAD CONFIG %s",strerror(errno)));
return false;
}
} }
} }
logI("loading config."); logI("loading config.");
while (!feof(f)) { while (!feof(f)) {
if (fgets(line,4095,f)==NULL) { if (fgets(line,4095,f)==NULL) {
@ -97,6 +208,7 @@ bool DivConfig::loadFromFile(const char* path, bool createOnFail) {
} }
parseLine(line); parseLine(line);
} }
logD("end of file (%s)",strerror(errno));
fclose(f); fclose(f);
return true; return true;
} }
@ -175,6 +287,33 @@ String DivConfig::getString(String key, String fallback) const {
return fallback; return fallback;
} }
std::vector<int> DivConfig::getIntList(String key, std::initializer_list<int> fallback) const {
String next;
std::vector<int> 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 { bool DivConfig::has(String key) const {
try { try {
String test=conf.at(key); String test=conf.at(key);
@ -212,6 +351,17 @@ void DivConfig::set(String key, String value) {
conf[key]=value; conf[key]=value;
} }
void DivConfig::set(String key, const std::vector<int>& 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) { bool DivConfig::remove(String key) {
return conf.erase(key); return conf.erase(key);
} }

View File

@ -22,6 +22,8 @@
#include "../ta-utils.h" #include "../ta-utils.h"
#include <map> #include <map>
#include <vector>
#include <initializer_list>
class DivConfig { class DivConfig {
std::map<String,String> conf; std::map<String,String> conf;
@ -30,10 +32,10 @@ class DivConfig {
// config loading/saving // config loading/saving
bool loadFromMemory(const char* buf); bool loadFromMemory(const char* buf);
bool loadFromBase64(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 toString();
String toBase64(); String toBase64();
bool save(const char* path); bool save(const char* path, bool redundancy=false);
// get the map // get the map
const std::map<String,String>& configMap(); const std::map<String,String>& configMap();
@ -44,6 +46,7 @@ class DivConfig {
float getFloat(String key, float fallback) const; float getFloat(String key, float fallback) const;
double getDouble(String key, double fallback) const; double getDouble(String key, double fallback) const;
String getString(String key, String fallback) const; String getString(String key, String fallback) const;
std::vector<int> getIntList(String key, std::initializer_list<int> fallback) const;
// check for existence // check for existence
bool has(String key) const; bool has(String key) const;
@ -55,6 +58,7 @@ class DivConfig {
void set(String key, double value); void set(String key, double value);
void set(String key, const char* value); void set(String key, const char* value);
void set(String key, String value); void set(String key, String value);
void set(String key, const std::vector<int>& value);
// remove a config value // remove a config value
bool remove(String key); bool remove(String key);

View File

@ -110,12 +110,12 @@ void DivEngine::initConfDir() {
bool DivEngine::saveConf() { bool DivEngine::saveConf() {
configFile=configPath+String(CONFIG_FILE); configFile=configPath+String(CONFIG_FILE);
return conf.save(configFile.c_str()); return conf.save(configFile.c_str(),true);
} }
bool DivEngine::loadConf() { bool DivEngine::loadConf() {
configFile=configPath+String(CONFIG_FILE); 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) { bool DivEngine::getConfBool(String key, bool fallback) {

View File

@ -2441,6 +2441,16 @@ void DivEngine::stop() {
} }
} }
} }
// reset all chan oscs
for (int i=0; i<chans; i++) {
DivDispatchOscBuffer* buf=disCont[dispatchOfChan[i]].dispatch->getOscBuffer(dispatchChanOfChan[i]);
if (buf!=NULL) {
memset(buf->data,0,65536*sizeof(short));
buf->needle=0;
buf->readNeedle=0;
}
}
BUSY_END; BUSY_END;
} }

View File

@ -53,8 +53,8 @@
#define EXTERN_BUSY_BEGIN_SOFT e->softLocked=true; e->isBusy.lock(); #define EXTERN_BUSY_BEGIN_SOFT e->softLocked=true; e->isBusy.lock();
#define EXTERN_BUSY_END e->isBusy.unlock(); e->softLocked=false; #define EXTERN_BUSY_END e->isBusy.unlock(); e->softLocked=false;
#define DIV_VERSION "dev147" #define DIV_VERSION "dev152"
#define DIV_ENGINE_VERSION 147 #define DIV_ENGINE_VERSION 152
// for imports // for imports
#define DIV_VERSION_MOD 0xff01 #define DIV_VERSION_MOD 0xff01
#define DIV_VERSION_FC 0xff02 #define DIV_VERSION_FC 0xff02

View File

@ -696,6 +696,13 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
} }
ds.wave.push_back(wave); 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()); logV("%x",reader.tell());
@ -850,6 +857,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
sample->rate=22050; sample->rate=22050;
if (ds.version>=0x0b) { if (ds.version>=0x0b) {
sample->rate=fileToDivRate(reader.readC()); sample->rate=fileToDivRate(reader.readC());
sample->centerRate=sample->rate;
pitch=reader.readC(); pitch=reader.readC();
vol=reader.readC(); vol=reader.readC();
} }
@ -874,24 +882,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
// what the hell man... // what the hell man...
cutStart=reader.readI(); cutStart=reader.readI();
cutEnd=reader.readI(); cutEnd=reader.readI();
if (cutStart<0 || cutStart>length) { logV("cutStart: %d cutEnd: %d",cutStart,cutEnd);
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 (cutEnd<cutStart) {
logE("cutEnd %d is before cutStart %d. what's going on?",cutEnd,cutStart);
lastError="file is corrupt or unreadable at samples";
delete[] file;
return false;
}
} }
if (length>0) { if (length>0) {
if (ds.version>0x08) { if (ds.version>0x08) {
@ -903,19 +894,6 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
data=new short[length]; data=new short[length];
reader.read(data,length*2); 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 #ifdef TA_BIG_ENDIAN
// convert to big-endian // convert to big-endian
@ -924,27 +902,73 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
} }
#endif #endif
if (pitch!=5) { int scaledLen=(double)length/samplePitches[pitch];
if (scaledLen>0) {
// resample
logD("%d: scaling from %d...",i,pitch); logD("%d: scaling from %d...",i,pitch);
}
short* newData=new short[scaledLen];
// render data int k=0;
if (!sample->init((double)length/samplePitches[pitch])) { float mult=(float)(vol)/50.0f;
logE("%d: error while initializing sample!",i); for (double j=0; j<length; j+=samplePitches[pitch]) {
} if (k>=scaledLen) {
break;
unsigned int k=0; }
float mult=(float)(vol)/50.0f; if (sample->depth==DIV_SAMPLE_DEPTH_8BIT) {
for (double j=0; j<length; j+=samplePitches[pitch]) { float next=(float)(data[(unsigned int)j]-0x80)*mult;
if (k>=sample->samples) { newData[k++]=fmin(fmax(next,-128),127);
break; } 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; delete[] data;
sample->data8[k++]=fmin(fmax(next,-128),127); data=newData;
} else { }
float next=(float)data[(unsigned int)j]*mult;
sample->data16[k++]=fmin(fmax(next,-32768),32767); 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 (cutEnd<cutStart) {
logE("cutEnd %d is before cutStart %d. what's going on?",cutEnd,cutStart);
lastError="file is corrupt or unreadable at samples";
delete[] file;
return false;
}
if (cutStart!=0 || cutEnd!=scaledLen) {
// cut data
short* newData=new short[cutEnd-cutStart];
memcpy(newData,&data[cutStart],(cutEnd-cutStart)*sizeof(short));
delete[] data;
data=newData;
scaledLen=cutEnd-cutStart;
cutStart=0;
cutEnd=scaledLen;
}
}
// copy data
if (!sample->init(scaledLen)) {
logE("%d: error while initializing sample!",i);
} else {
for (int i=0; i<scaledLen; i++) {
if (sample->depth==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; i<song.insLen; i++) { for (int i=0; i<song.insLen; i++) {
DivInstrument* ins=song.ins[i]; DivInstrument* ins=song.ins[i];
insPtr.push_back(w->tell()); insPtr.push_back(w->tell());
logV("writing instrument %d...",i);
ins->putInsData2(w,false); ins->putInsData2(w,false);
} }

View File

@ -214,7 +214,6 @@ bool DivInstrumentSNES::operator==(const DivInstrumentSNES& other) {
#undef _C #undef _C
#define FEATURE_BEGIN(x) \ #define FEATURE_BEGIN(x) \
logV("- %s",x); \
w->write(x,2); \ w->write(x,2); \
size_t featStartSeek=w->tell(); \ size_t featStartSeek=w->tell(); \
w->writeS(0); w->writeS(0);
@ -2103,6 +2102,12 @@ void DivInstrument::readFeatureSM(SafeReader& reader, short version) {
amiga.noteMap[note].freq=reader.readS(); amiga.noteMap[note].freq=reader.readS();
amiga.noteMap[note].map=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; READ_FEAT_END;
@ -2970,6 +2975,12 @@ DivDataErrors DivInstrument::readInsDataOld(SafeReader &reader, short version) {
for (int note=0; note<120; note++) { for (int note=0; note<120; note++) {
amiga.noteMap[note].map=reader.readS(); amiga.noteMap[note].map=reader.readS();
} }
if (version<152) {
for (int note=0; note<120; note++) {
amiga.noteMap[note].freq=note;
}
}
} }
} }

View File

@ -415,7 +415,7 @@ struct DivInstrumentAmiga {
if (note>119) note=119; if (note>119) note=119;
return noteMap[note].freq; return noteMap[note].freq;
} }
return -1; return note;
} }
DivInstrumentAmiga(): DivInstrumentAmiga():
@ -424,8 +424,9 @@ struct DivInstrumentAmiga {
useSample(false), useSample(false),
useWave(false), useWave(false),
waveLen(31) { waveLen(31) {
for (SampleMap& elem: noteMap) { for (int i=0; i<120; i++) {
elem=SampleMap(); noteMap[i].map=-1;
noteMap[i].freq=i;
} }
} }
}; };

View File

@ -560,7 +560,10 @@ int DivPlatformAmiga::dispatch(DivCommand c) {
} }
} }
} else { } 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; chan[c.chan].useWave=false;
} }
if (c.value!=DIV_NOTE_NULL) { if (c.value!=DIV_NOTE_NULL) {

View File

@ -292,8 +292,6 @@ void DivPlatformAY8910::tick(bool sysTick) {
if (chan[i].std.phaseReset.val==1) { if (chan[i].std.phaseReset.val==1) {
if (chan[i].nextPSGMode.dac) { if (chan[i].nextPSGMode.dac) {
if (dumpWrites) addWrite(0xffff0002+(i<<8),0); 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 (chan[i].dac.sample<0 || chan[i].dac.sample>=parent->song.sampleLen) {
if (dumpWrites) { if (dumpWrites) {
rWrite(0x08+i,0); rWrite(0x08+i,0);
@ -405,7 +403,10 @@ int DivPlatformAY8910::dispatch(DivCommand c) {
if (chan[c.chan].nextPSGMode.dac) { if (chan[c.chan].nextPSGMode.dac) {
if (skipRegisterWrites) break; if (skipRegisterWrites) break;
if (!parent->song.disableSampleMacro && (ins->type==DIV_INS_AMIGA || ins->amiga.useSample)) { 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) { if (chan[c.chan].dac.sample<0 || chan[c.chan].dac.sample>=parent->song.sampleLen) {
chan[c.chan].dac.sample=-1; chan[c.chan].dac.sample=-1;
if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0); if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0);

View File

@ -280,8 +280,6 @@ void DivPlatformAY8930::tick(bool sysTick) {
if (chan[i].std.phaseReset.val==1) { if (chan[i].std.phaseReset.val==1) {
if (chan[i].nextPSGMode.dac) { if (chan[i].nextPSGMode.dac) {
if (dumpWrites) addWrite(0xffff0002+(i<<8),0); 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 (chan[i].dac.sample<0 || chan[i].dac.sample>=parent->song.sampleLen) {
if (dumpWrites) { if (dumpWrites) {
rWrite(0x08+i,0); rWrite(0x08+i,0);
@ -406,7 +404,10 @@ int DivPlatformAY8930::dispatch(DivCommand c) {
if (chan[c.chan].nextPSGMode.dac) { if (chan[c.chan].nextPSGMode.dac) {
if (skipRegisterWrites) break; if (skipRegisterWrites) break;
if (ins->type==DIV_INS_AMIGA || ins->amiga.useSample) { 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) { if (chan[c.chan].dac.sample<0 || chan[c.chan].dac.sample>=parent->song.sampleLen) {
chan[c.chan].dac.sample=-1; chan[c.chan].dac.sample=-1;
if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0); if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0);

View File

@ -410,7 +410,7 @@ void DivPlatformES5506::tick(bool sysTick) {
if (chan[i].pcmChanged.changed) { if (chan[i].pcmChanged.changed) {
if (chan[i].pcmChanged.index) { if (chan[i].pcmChanged.index) {
const int next=chan[i].pcm.next; const int next=chan[i].pcm.next;
bool sampleVaild=false; bool sampleValid=false;
if (((ins->amiga.useNoteMap) && (next>=0 && next<120)) || if (((ins->amiga.useNoteMap) && (next>=0 && next<120)) ||
((!ins->amiga.useNoteMap) && (next>=0 && next<parent->song.sampleLen))) { ((!ins->amiga.useNoteMap) && (next>=0 && next<parent->song.sampleLen))) {
DivInstrumentAmiga::SampleMap& noteMapind=ins->amiga.noteMap[next]; DivInstrumentAmiga::SampleMap& noteMapind=ins->amiga.noteMap[next];
@ -420,7 +420,7 @@ void DivPlatformES5506::tick(bool sysTick) {
} }
if (sample>=0 && sample<parent->song.sampleLen) { if (sample>=0 && sample<parent->song.sampleLen) {
const unsigned int offES5506=sampleOffES5506[sample]; const unsigned int offES5506=sampleOffES5506[sample];
sampleVaild=true; sampleValid=true;
chan[i].pcm.index=sample; chan[i].pcm.index=sample;
chan[i].pcm.isNoteMap=ins->amiga.useNoteMap; chan[i].pcm.isNoteMap=ins->amiga.useNoteMap;
DivSample* s=parent->getSample(sample); DivSample* s=parent->getSample(sample);
@ -459,7 +459,7 @@ void DivPlatformES5506::tick(bool sysTick) {
} }
} }
} }
if (sampleVaild) { if (sampleValid) {
if (!chan[i].keyOn) { if (!chan[i].keyOn) {
pageWrite(0x20|i,0x03,(chan[i].pcm.direction)?chan[i].pcm.end:chan[i].pcm.start); 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) { switch (c.cmd) {
case DIV_CMD_NOTE_ON: { case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_ES5506); 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)) || if (((ins->amiga.useNoteMap) && (c.value>=0 && c.value<120)) ||
((!ins->amiga.useNoteMap) && (ins->amiga.initSample>=0 && ins->amiga.initSample<parent->song.sampleLen))) { ((!ins->amiga.useNoteMap) && (ins->amiga.initSample>=0 && ins->amiga.initSample<parent->song.sampleLen))) {
int sample=ins->amiga.getSample(c.value); int sample=ins->amiga.getSample(c.value);
c.value=ins->amiga.getFreq(c.value);
if (sample>=0 && sample<parent->song.sampleLen) { if (sample>=0 && sample<parent->song.sampleLen) {
sampleVaild=true; sampleValid=true;
chan[c.chan].volMacroMax=ins->type==DIV_INS_AMIGA?64:0xfff; 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].panMacroMax=ins->type==DIV_INS_AMIGA?127:0xfff;
chan[c.chan].pcm.note=c.value; chan[c.chan].pcm.note=c.value;
@ -764,7 +765,7 @@ int DivPlatformES5506::dispatch(DivCommand c) {
chan[c.chan].envelope=ins->es5506.envelope; 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].pcm.index=chan[c.chan].pcm.next=-1;
chan[c.chan].filter=DivInstrumentES5506::Filter(); chan[c.chan].filter=DivInstrumentES5506::Filter();
chan[c.chan].envelope=DivInstrumentES5506::Envelope(); chan[c.chan].envelope=DivInstrumentES5506::Envelope();

View File

@ -200,7 +200,10 @@ int DivPlatformGA20::dispatch(DivCommand c) {
case DIV_CMD_NOTE_ON: { case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA); DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA);
chan[c.chan].macroVolMul=ins->type==DIV_INS_AMIGA?64:255; 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) { if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); chan[c.chan].baseFreq=NOTE_PERIODIC(c.value);
} }

View File

@ -681,7 +681,10 @@ int DivPlatformGenesis::dispatch(DivCommand c) {
if (c.chan>=5 && chan[c.chan].dacMode) { if (c.chan>=5 && chan[c.chan].dacMode) {
//if (skipRegisterWrites) break; //if (skipRegisterWrites) break;
if (ins->type==DIV_INS_AMIGA) { // Furnace mode 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) { if (chan[c.chan].dacSample<0 || chan[c.chan].dacSample>=parent->song.sampleLen) {
chan[c.chan].dacSample=-1; chan[c.chan].dacSample=-1;
if (dumpWrites) addWrite(0xffff0002,0); if (dumpWrites) addWrite(0xffff0002,0);

View File

@ -274,7 +274,10 @@ int DivPlatformK007232::dispatch(DivCommand c) {
case DIV_CMD_NOTE_ON: { case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA); DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA);
chan[c.chan].macroVolMul=ins->type==DIV_INS_AMIGA?64:15; 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) { if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); chan[c.chan].baseFreq=NOTE_PERIODIC(c.value);
} }

View File

@ -261,13 +261,14 @@ int DivPlatformLynx::dispatch(DivCommand c) {
chan[c.chan].macroVolMul=ins->type==DIV_INS_AMIGA?64:127; chan[c.chan].macroVolMul=ins->type==DIV_INS_AMIGA?64:127;
chan[c.chan].pcm=(ins->type==DIV_INS_AMIGA || ins->amiga.useSample); chan[c.chan].pcm=(ins->type==DIV_INS_AMIGA || ins->amiga.useSample);
if (c.value!=DIV_NOTE_NULL) { if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value);
if (chan[c.chan].pcm) { 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); 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].sampleAccum=0;
chan[c.chan].samplePos=0; chan[c.chan].samplePos=0;
} }
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value);
chan[c.chan].freqChanged=true; chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value; chan[c.chan].note=c.value;
chan[c.chan].actualNote=c.value; chan[c.chan].actualNote=c.value;

View File

@ -176,7 +176,10 @@ int DivPlatformMMC5::dispatch(DivCommand c) {
if (c.chan==2) { // PCM if (c.chan==2) { // PCM
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_STD); DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_STD);
if (ins->type==DIV_INS_AMIGA) { 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) { if (dacSample<0 || dacSample>=parent->song.sampleLen) {
dacSample=-1; dacSample=-1;
if (dumpWrites) addWrite(0xffff0002,0); if (dumpWrites) addWrite(0xffff0002,0);

View File

@ -361,7 +361,10 @@ int DivPlatformNES::dispatch(DivCommand c) {
if (c.chan==4) { // PCM if (c.chan==4) { // PCM
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_STD); DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_STD);
if (ins->type==DIV_INS_AMIGA) { 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) { if (dacSample<0 || dacSample>=parent->song.sampleLen) {
dacSample=-1; dacSample=-1;
if (dumpWrites && !dpcmMode) addWrite(0xffff0002,0); if (dumpWrites && !dpcmMode) addWrite(0xffff0002,0);

View File

@ -856,7 +856,10 @@ int DivPlatformOPL::dispatch(DivCommand c) {
chan[c.chan].outVol=chan[c.chan].vol; chan[c.chan].outVol=chan[c.chan].vol;
immWrite(18,chan[c.chan].outVol); 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].sample<parent->song.sampleLen) { if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[c.chan].sample); DivSample* s=parent->getSample(chan[c.chan].sample);
immWrite(8,0); immWrite(8,0);

View File

@ -282,7 +282,10 @@ int DivPlatformPCE::dispatch(DivCommand c) {
if (ins->type==DIV_INS_AMIGA || ins->amiga.useSample) { if (ins->type==DIV_INS_AMIGA || ins->amiga.useSample) {
chan[c.chan].furnaceDac=true; chan[c.chan].furnaceDac=true;
if (skipRegisterWrites) break; 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) { if (chan[c.chan].dacSample<0 || chan[c.chan].dacSample>=parent->song.sampleLen) {
chan[c.chan].dacSample=-1; chan[c.chan].dacSample=-1;
if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0); if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0);

View File

@ -271,7 +271,10 @@ int DivPlatformPCMDAC::dispatch(DivCommand c) {
} }
} }
} else { } 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; chan[0].useWave=false;
} }
if (c.value!=DIV_NOTE_NULL) { if (c.value!=DIV_NOTE_NULL) {

View File

@ -450,7 +450,10 @@ int DivPlatformQSound::dispatch(DivCommand c) {
case DIV_CMD_NOTE_ON: { case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA); DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA);
chan[c.chan].isNewQSound=(ins->type==DIV_INS_QSOUND); 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) { if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=QS_NOTE_FREQUENCY(c.value); chan[c.chan].baseFreq=QS_NOTE_FREQUENCY(c.value);
} }

View File

@ -182,7 +182,10 @@ int DivPlatformRF5C68::dispatch(DivCommand c) {
case DIV_CMD_NOTE_ON: { case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA); DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA);
chan[c.chan].macroVolMul=ins->type==DIV_INS_AMIGA?64:255; 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) { if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
} }

View File

@ -186,7 +186,10 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) {
if (ins->type==DIV_INS_AMIGA || ins->type==DIV_INS_SEGAPCM) { 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].macroVolMul=(ins->type==DIV_INS_AMIGA)?64:127;
chan[c.chan].isNewSegaPCM=(ins->type==DIV_INS_SEGAPCM); 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) { if (chan[c.chan].pcm.sample<0 || chan[c.chan].pcm.sample>=parent->song.sampleLen) {
chan[c.chan].pcm.sample=-1; chan[c.chan].pcm.sample=-1;
rWrite(0x86+(c.chan<<3),3); rWrite(0x86+(c.chan<<3),3);
@ -480,7 +483,11 @@ void DivPlatformSegaPCM::renderSamples(int sysID) {
if (memPos>=16777216) break; if (memPos>=16777216) break;
sampleOffSegaPCM[i]=memPos; sampleOffSegaPCM[i]=memPos;
for (unsigned int j=0; j<alignedSize; j++) { for (unsigned int j=0; j<alignedSize; j++) {
sampleMem[memPos++]=((unsigned char)sample->data8[j]+0x80); if (j>=sample->samples) {
sampleMem[memPos++]=0;
} else {
sampleMem[memPos++]=((unsigned char)sample->data8[j]+0x80);
}
sampleEndSegaPCM[i]=((memPos+0xff)>>8)-1; sampleEndSegaPCM[i]=((memPos+0xff)>>8)-1;
if (memPos>=16777216) break; if (memPos>=16777216) break;
} }

View File

@ -336,7 +336,10 @@ int DivPlatformSNES::dispatch(DivCommand c) {
} }
chan[c.chan].ws.init(ins,chan[c.chan].wtLen,15,chan[c.chan].insChanged); chan[c.chan].ws.init(ins,chan[c.chan].wtLen,15,chan[c.chan].insChanged);
} else { } 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; chan[c.chan].useWave=false;
} }
if (chan[c.chan].useWave || chan[c.chan].sample<0 || chan[c.chan].sample>=parent->song.sampleLen) { if (chan[c.chan].useWave || chan[c.chan].sample<0 || chan[c.chan].sample>=parent->song.sampleLen) {

View File

@ -261,7 +261,10 @@ int DivPlatformSwan::dispatch(DivCommand c) {
dacPos=0; dacPos=0;
dacPeriod=0; dacPeriod=0;
if (ins->type==DIV_INS_AMIGA || ins->amiga.useSample) { 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) { if (dacSample<0 || dacSample>=parent->song.sampleLen) {
dacSample=-1; dacSample=-1;
if (dumpWrites) postWrite(0xffff0002,0); if (dumpWrites) postWrite(0xffff0002,0);

View File

@ -235,7 +235,11 @@ int DivPlatformVERA::dispatch(DivCommand c) {
if (c.chan<16) { if (c.chan<16) {
rWriteLo(c.chan,2,chan[c.chan].vol); rWriteLo(c.chan,2,chan[c.chan].vol);
} else { } 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) { if (chan[16].pcm.sample<0 || chan[16].pcm.sample>=parent->song.sampleLen) {
chan[16].pcm.sample=-1; chan[16].pcm.sample=-1;
} }

View File

@ -179,8 +179,6 @@ void DivPlatformVRC6::tick(bool sysTick) {
if (chan[i].std.phaseReset.val && chan[i].active) { if (chan[i].std.phaseReset.val && chan[i].active) {
if ((i!=2) && (!chan[i].pcm)) { if ((i!=2) && (!chan[i].pcm)) {
if (dumpWrites) addWrite(0xffff0002+(i<<8),0); 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 (chan[i].dacSample<0 || chan[i].dacSample>=parent->song.sampleLen) {
if (dumpWrites) { if (dumpWrites) {
chWrite(i,2,0x80); chWrite(i,2,0x80);
@ -242,7 +240,10 @@ int DivPlatformVRC6::dispatch(DivCommand c) {
if (chan[c.chan].pcm) { if (chan[c.chan].pcm) {
if (skipRegisterWrites) break; if (skipRegisterWrites) break;
if (ins->type==DIV_INS_AMIGA || ins->amiga.useSample) { 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) { if (chan[c.chan].dacSample<0 || chan[c.chan].dacSample>=parent->song.sampleLen) {
chan[c.chan].dacSample=-1; chan[c.chan].dacSample=-1;
if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0); if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0);

View File

@ -540,7 +540,10 @@ int DivPlatformX1_010::dispatch(DivCommand c) {
if (chan[c.chan].furnacePCM) { if (chan[c.chan].furnacePCM) {
chan[c.chan].pcm=true; chan[c.chan].pcm=true;
chan[c.chan].macroInit(ins); 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].sample<parent->song.sampleLen) { if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[c.chan].sample); DivSample* s=parent->getSample(chan[c.chan].sample);
if (isBanked) { if (isBanked) {

View File

@ -896,7 +896,10 @@ int DivPlatformYM2608::dispatch(DivCommand c) {
chan[c.chan].outVol=chan[c.chan].vol; chan[c.chan].outVol=chan[c.chan].vol;
immWrite(0x10b,chan[c.chan].outVol); 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].sample<parent->song.sampleLen) { if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[c.chan].sample); DivSample* s=parent->getSample(chan[c.chan].sample);
immWrite(0x100,0x01); // reset immWrite(0x100,0x01); // reset

View File

@ -827,7 +827,10 @@ int DivPlatformYM2610::dispatch(DivCommand c) {
chan[c.chan].outVol=chan[c.chan].vol; chan[c.chan].outVol=chan[c.chan].vol;
immWrite(0x1b,chan[c.chan].outVol); 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].sample<parent->song.sampleLen) { if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[c.chan].sample); DivSample* s=parent->getSample(chan[c.chan].sample);
immWrite(0x12,(sampleOffB[chan[c.chan].sample]>>8)&0xff); immWrite(0x12,(sampleOffB[chan[c.chan].sample]>>8)&0xff);

View File

@ -894,7 +894,10 @@ int DivPlatformYM2610B::dispatch(DivCommand c) {
chan[c.chan].outVol=chan[c.chan].vol; chan[c.chan].outVol=chan[c.chan].vol;
immWrite(0x1b,chan[c.chan].outVol); 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].sample<parent->song.sampleLen) { if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[c.chan].sample); DivSample* s=parent->getSample(chan[c.chan].sample);
immWrite(0x12,(sampleOffB[chan[c.chan].sample]>>8)&0xff); immWrite(0x12,(sampleOffB[chan[c.chan].sample]>>8)&0xff);

View File

@ -212,7 +212,10 @@ int DivPlatformYMZ280B::dispatch(DivCommand c) {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA); DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA);
chan[c.chan].isNewYMZ=ins->type==DIV_INS_YMZ280B; chan[c.chan].isNewYMZ=ins->type==DIV_INS_YMZ280B;
chan[c.chan].macroVolMul=ins->type==DIV_INS_AMIGA?64:255; 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) { if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
} }

View File

@ -169,10 +169,11 @@ int DivPlatformZXBeeperQuadTone::dispatch(DivCommand c) {
} else { } else {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA); DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA);
if (c.value!=DIV_NOTE_NULL) { 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].baseFreq=NOTE_PERIODIC(c.value);
chan[c.chan].freqChanged=true; chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value; chan[c.chan].note=c.value;
curSample=ins->amiga.getSample(c.value);
// TODO support offset commands // TODO support offset commands
curSamplePos=0; curSamplePos=0;
curSamplePeriod=0; curSamplePeriod=0;

View File

@ -1412,6 +1412,15 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) {
ret=true; ret=true;
shallStop=false; shallStop=false;
shallStopSched=false; shallStopSched=false;
// reset all chan oscs
for (int i=0; i<chans; i++) {
DivDispatchOscBuffer* buf=disCont[dispatchOfChan[i]].dispatch->getOscBuffer(dispatchChanOfChan[i]);
if (buf!=NULL) {
memset(buf->data,0,65536*sizeof(short));
buf->needle=0;
buf->readNeedle=0;
}
}
return ret; return ret;
} }

View File

@ -212,7 +212,6 @@ void DivWaveSynth::setWidth(int val) {
void DivWaveSynth::changeWave1(int num) { void DivWaveSynth::changeWave1(int num) {
DivWavetable* w1=e->getWave(num); DivWavetable* w1=e->getWave(num);
logV("changeWave1 (%d)",width);
if (width<1) return; if (width<1) return;
for (int i=0; i<width; i++) { for (int i=0; i<width; i++) {
if (w1->max<1 || w1->len<1) { if (w1->max<1 || w1->len<1) {

View File

@ -20,6 +20,13 @@
#include "fileutils.h" #include "fileutils.h"
#ifdef _WIN32 #ifdef _WIN32
#include "utfutils.h" #include "utfutils.h"
#include <windows.h>
#include <shlobj.h>
#include <shlwapi.h>
#else
#include <unistd.h>
#include <errno.h>
#include <sys/stat.h>
#endif #endif
FILE* ps_fopen(const char* path, const char* mode) { 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); return fopen(path,mode);
#endif #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
}

View File

@ -22,5 +22,11 @@
#include <stdio.h> #include <stdio.h>
FILE* ps_fopen(const char* path, const char* mode); 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 #endif

View File

@ -50,9 +50,7 @@ void FurnaceGUI::doAction(int what) {
if (modified) { if (modified) {
showWarning("Unsaved changes! Save changes before opening backup?",GUI_WARN_OPEN_BACKUP); showWarning("Unsaved changes! Save changes before opening backup?",GUI_WARN_OPEN_BACKUP);
} else { } else {
if (load(backupPath)>0) { openFileDialog(GUI_FILE_OPEN_BACKUP);
showError("No backup available! (or unable to open it)");
}
} }
break; break;
case GUI_ACTION_SAVE: case GUI_ACTION_SAVE:

View File

@ -524,6 +524,11 @@ void FurnaceGUI::drawMobileControls() {
openFileDialog(GUI_FILE_EXPORT_CMDSTREAM); openFileDialog(GUI_FILE_EXPORT_CMDSTREAM);
} }
if (ImGui::Button("Restore Backup")) {
mobileMenuOpen=false;
doAction(GUI_ACTION_OPEN_BACKUP);
}
ImGui::Separator(); ImGui::Separator();
if (ImGui::BeginTabBar("MobileSong")) { if (ImGui::BeginTabBar("MobileSong")) {

View File

@ -50,13 +50,15 @@
#include <shlwapi.h> #include <shlwapi.h>
#include "../utfutils.h" #include "../utfutils.h"
#define LAYOUT_INI "\\layout.ini" #define LAYOUT_INI "\\layout.ini"
#define BACKUP_FUR "\\backup.fur" #define BACKUPS_DIR "\\backups"
#else #else
#include <sys/types.h>
#include <dirent.h>
#include <unistd.h> #include <unistd.h>
#include <pwd.h> #include <pwd.h>
#include <sys/stat.h> #include <sys/stat.h>
#define LAYOUT_INI "/layout.ini" #define LAYOUT_INI "/layout.ini"
#define BACKUP_FUR "/backup.fur" #define BACKUPS_DIR "/backups"
#endif #endif
#ifdef IS_MOBILE #ifdef IS_MOBILE
@ -528,18 +530,22 @@ void FurnaceGUI::setFileName(String name) {
if (index>=4095) break; if (index>=4095) break;
} }
ret[index]=0; ret[index]=0;
backupLock.lock();
if (GetFullPathNameW(ws.c_str(),4095,ret,NULL)==0) { if (GetFullPathNameW(ws.c_str(),4095,ret,NULL)==0) {
curFileName=name; curFileName=name;
} else { } else {
curFileName=utf16To8(ret); curFileName=utf16To8(ret);
} }
backupLock.unlock();
#else #else
char ret[4096]; char ret[4096];
backupLock.lock();
if (realpath(name.c_str(),ret)==NULL) { if (realpath(name.c_str(),ret)==NULL) {
curFileName=name; curFileName=name;
} else { } else {
curFileName=ret; curFileName=ret;
} }
backupLock.unlock();
#endif #endif
updateWindowTitle(); updateWindowTitle();
pushRecentFile(curFileName); pushRecentFile(curFileName);
@ -1076,10 +1082,6 @@ void FurnaceGUI::stop() {
e->stop(); e->stop();
curNibble=false; curNibble=false;
orderNibble=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) { void FurnaceGUI::previewNote(int refChan, int note, bool autoNote) {
@ -1458,15 +1460,8 @@ void FurnaceGUI::keyUp(SDL_Event& ev) {
// nothing for now // nothing for now
} }
bool dirExists(String what) { bool dirExists(String s) {
#ifdef _WIN32 return dirExists(s.c_str());
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
} }
void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
@ -1483,6 +1478,19 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
dpiScale dpiScale
); );
break; 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: case GUI_FILE_SAVE:
if (!dirExists(workingDirSong)) workingDirSong=getHomeDir(); if (!dirExists(workingDirSong)) workingDirSong=getHomeDir();
hasOpened=fileDialog->openSave( hasOpened=fileDialog->openSave(
@ -2009,7 +2017,9 @@ int FurnaceGUI::save(String path, int dmfVersion) {
#endif #endif
fclose(outFile); fclose(outFile);
w->finish(); w->finish();
backupLock.lock();
curFileName=path; curFileName=path;
backupLock.unlock();
modified=false; modified=false;
updateWindowTitle(); updateWindowTitle();
if (!e->getWarnings().empty()) { if (!e->getWarnings().empty()) {
@ -2074,7 +2084,9 @@ int FurnaceGUI::load(String path) {
return 1; return 1;
} }
} }
backupLock.lock();
curFileName=path; curFileName=path;
backupLock.unlock();
modified=false; modified=false;
curNibble=false; curNibble=false;
orderNibble=false; orderNibble=false;
@ -2100,7 +2112,7 @@ int FurnaceGUI::load(String path) {
void FurnaceGUI::pushRecentFile(String path) { void FurnaceGUI::pushRecentFile(String path) {
if (path.empty()) return; if (path.empty()) return;
if (path==backupPath) return; if (path.find(backupPath)==0) return;
for (int i=0; i<(int)recentFile.size(); i++) { for (int i=0; i<(int)recentFile.size(); i++) {
if (recentFile[i]==path) { if (recentFile[i]==path) {
recentFile.erase(recentFile.begin()+i); recentFile.erase(recentFile.begin()+i);
@ -2114,6 +2126,44 @@ void FurnaceGUI::pushRecentFile(String path) {
} }
} }
void FurnaceGUI::delFirstBackup(String name) {
std::vector<String> 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; i<totalDelete; i++) {
String toDelete=backupPath+String(DIR_SEPARATOR_STR)+listOfFiles[i];
deleteFile(toDelete.c_str());
}
}
int FurnaceGUI::loadStream(String path) { int FurnaceGUI::loadStream(String path) {
if (!path.empty()) { if (!path.empty()) {
logI("loading stream..."); logI("loading stream...");
@ -2786,7 +2836,9 @@ void FurnaceGUI::editOptions(bool topMenu) {
void FurnaceGUI::toggleMobileUI(bool enable, bool force) { void FurnaceGUI::toggleMobileUI(bool enable, bool force) {
if (mobileUI!=enable || force) { if (mobileUI!=enable || force) {
if (!mobileUI && enable) { if (!mobileUI && enable) {
ImGui::SaveIniSettingsToDisk(finalLayoutPath); if (!ImGui::SaveIniSettingsToDisk(finalLayoutPath,true)) {
reportError(fmt::sprintf("could NOT save layout! %s",strerror(errno)));
}
} }
mobileUI=enable; mobileUI=enable;
if (mobileUI) { if (mobileUI) {
@ -2796,7 +2848,10 @@ void FurnaceGUI::toggleMobileUI(bool enable, bool force) {
fileDialog->mobileUI=true; fileDialog->mobileUI=true;
} else { } else {
ImGui::GetIO().IniFilename=NULL; 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_InertialScrollEnable;
ImGui::GetIO().ConfigFlags&=~ImGuiConfigFlags_NoHoverColors; ImGui::GetIO().ConfigFlags&=~ImGuiConfigFlags_NoHoverColors;
fileDialog->mobileUI=false; 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; i<e->getTotalChannelCount(); i++) {
DivDispatchOscBuffer* buf=e->getOscBuffer(i);
if (buf!=NULL) {
buf->needle=0;
buf->readNeedle=0;
}
}
});
}
layoutTimeBegin=SDL_GetPerformanceCounter(); layoutTimeBegin=SDL_GetPerformanceCounter();
ImGui_ImplSDLRenderer_NewFrame(); ImGui_ImplSDLRenderer_NewFrame();
@ -3623,7 +3695,7 @@ bool FurnaceGUI::loop() {
} }
ImGui::Separator(); ImGui::Separator();
if (ImGui::MenuItem("save",BIND_FOR(GUI_ACTION_SAVE))) { 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); openFileDialog(GUI_FILE_SAVE);
} else { } else {
if (save(curFileName,e->song.isDMF?e->song.version:0)>0) { if (save(curFileName,e->song.isDMF?e->song.version:0)>0) {
@ -4318,6 +4390,8 @@ bool FurnaceGUI::loop() {
case GUI_FILE_TEST_SAVE: case GUI_FILE_TEST_SAVE:
workingDirTest=fileDialog->getPath()+DIR_SEPARATOR_STR; workingDirTest=fileDialog->getPath()+DIR_SEPARATOR_STR;
break; break;
case GUI_FILE_OPEN_BACKUP:
break;
} }
if (fileDialog->isError()) { if (fileDialog->isError()) {
#if defined(_WIN32) || defined(__APPLE__) #if defined(_WIN32) || defined(__APPLE__)
@ -4394,6 +4468,7 @@ bool FurnaceGUI::loop() {
String copyOfName=fileName; String copyOfName=fileName;
switch (curFileDialog) { switch (curFileDialog) {
case GUI_FILE_OPEN: case GUI_FILE_OPEN:
case GUI_FILE_OPEN_BACKUP:
if (load(copyOfName)>0) { if (load(copyOfName)>0) {
showError(fmt::sprintf("Error while loading file! (%s)",lastError)); showError(fmt::sprintf("Error while loading file! (%s)",lastError));
} }
@ -4422,9 +4497,7 @@ bool FurnaceGUI::loop() {
nextFile=""; nextFile="";
break; break;
case GUI_WARN_OPEN_BACKUP: case GUI_WARN_OPEN_BACKUP:
if (load(backupPath)>0) { openFileDialog(GUI_FILE_OPEN_BACKUP);
showError("No backup available! (or unable to open it)");
}
break; break;
default: default:
break; break;
@ -4891,7 +4964,7 @@ bool FurnaceGUI::loop() {
case GUI_WARN_QUIT: case GUI_WARN_QUIT:
if (ImGui::Button("Yes")) { if (ImGui::Button("Yes")) {
ImGui::CloseCurrentPopup(); ImGui::CloseCurrentPopup();
if (curFileName=="" || curFileName==backupPath || e->song.version>=0xff00) { if (curFileName=="" || curFileName.find(backupPath)==0 || e->song.version>=0xff00) {
openFileDialog(GUI_FILE_SAVE); openFileDialog(GUI_FILE_SAVE);
postWarnAction=GUI_WARN_QUIT; postWarnAction=GUI_WARN_QUIT;
} else { } else {
@ -4915,7 +4988,7 @@ bool FurnaceGUI::loop() {
case GUI_WARN_NEW: case GUI_WARN_NEW:
if (ImGui::Button("Yes")) { if (ImGui::Button("Yes")) {
ImGui::CloseCurrentPopup(); ImGui::CloseCurrentPopup();
if (curFileName=="" || curFileName==backupPath || e->song.version>=0xff00) { if (curFileName=="" || curFileName.find(backupPath)==0 || e->song.version>=0xff00) {
openFileDialog(GUI_FILE_SAVE); openFileDialog(GUI_FILE_SAVE);
postWarnAction=GUI_WARN_NEW; postWarnAction=GUI_WARN_NEW;
} else { } else {
@ -4939,7 +5012,7 @@ bool FurnaceGUI::loop() {
case GUI_WARN_OPEN: case GUI_WARN_OPEN:
if (ImGui::Button("Yes")) { if (ImGui::Button("Yes")) {
ImGui::CloseCurrentPopup(); ImGui::CloseCurrentPopup();
if (curFileName=="" || curFileName==backupPath || e->song.version>=0xff00) { if (curFileName=="" || curFileName.find(backupPath)==0 || e->song.version>=0xff00) {
openFileDialog(GUI_FILE_SAVE); openFileDialog(GUI_FILE_SAVE);
postWarnAction=GUI_WARN_OPEN; postWarnAction=GUI_WARN_OPEN;
} else { } else {
@ -4963,25 +5036,21 @@ bool FurnaceGUI::loop() {
case GUI_WARN_OPEN_BACKUP: case GUI_WARN_OPEN_BACKUP:
if (ImGui::Button("Yes")) { if (ImGui::Button("Yes")) {
ImGui::CloseCurrentPopup(); ImGui::CloseCurrentPopup();
if (curFileName=="" || curFileName==backupPath || e->song.version>=0xff00) { if (curFileName=="" || curFileName.find(backupPath)==0 || e->song.version>=0xff00) {
openFileDialog(GUI_FILE_SAVE); openFileDialog(GUI_FILE_SAVE);
postWarnAction=GUI_WARN_OPEN_BACKUP; postWarnAction=GUI_WARN_OPEN_BACKUP;
} else { } else {
if (save(curFileName,e->song.isDMF?e->song.version:0)>0) { if (save(curFileName,e->song.isDMF?e->song.version:0)>0) {
showError(fmt::sprintf("Error while saving file! (%s)",lastError)); showError(fmt::sprintf("Error while saving file! (%s)",lastError));
} else { } else {
if (load(backupPath)>0) { openFileDialog(GUI_FILE_OPEN_BACKUP);
showError("No backup available! (or unable to open it)");
}
} }
} }
} }
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::Button("No")) { if (ImGui::Button("No")) {
ImGui::CloseCurrentPopup(); ImGui::CloseCurrentPopup();
if (load(backupPath)>0) { openFileDialog(GUI_FILE_OPEN_BACKUP);
showError("No backup available! (or unable to open it)");
}
} }
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::Button("Cancel")) { if (ImGui::Button("Cancel")) {
@ -4991,7 +5060,7 @@ bool FurnaceGUI::loop() {
case GUI_WARN_OPEN_DROP: case GUI_WARN_OPEN_DROP:
if (ImGui::Button("Yes")) { if (ImGui::Button("Yes")) {
ImGui::CloseCurrentPopup(); ImGui::CloseCurrentPopup();
if (curFileName=="" || curFileName==backupPath || e->song.version>=0xff00) { if (curFileName=="" || curFileName.find(backupPath)==0 || e->song.version>=0xff00) {
openFileDialog(GUI_FILE_SAVE); openFileDialog(GUI_FILE_SAVE);
postWarnAction=GUI_WARN_OPEN_DROP; postWarnAction=GUI_WARN_OPEN_DROP;
} else { } else {
@ -5025,7 +5094,9 @@ bool FurnaceGUI::loop() {
ImGui::CloseCurrentPopup(); ImGui::CloseCurrentPopup();
if (!mobileUI) { if (!mobileUI) {
ImGui::LoadIniSettingsFromMemory(defaultLayout); ImGui::LoadIniSettingsFromMemory(defaultLayout);
ImGui::SaveIniSettingsToDisk(finalLayoutPath); if (!ImGui::SaveIniSettingsToDisk(finalLayoutPath,true)) {
reportError(fmt::sprintf("could NOT save layout! %s",strerror(errno)));
}
} }
} }
ImGui::SameLine(); ImGui::SameLine();
@ -5401,16 +5472,73 @@ bool FurnaceGUI::loop() {
backupTimer=(backupTimer-ImGui::GetIO().DeltaTime); backupTimer=(backupTimer-ImGui::GetIO().DeltaTime);
if (backupTimer<=0) { if (backupTimer<=0) {
backupTask=std::async(std::launch::async,[this]() -> bool { 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."); logD("backup file open. not saving backup.");
backupTimer=30.0;
backupLock.unlock();
return true; 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..."); logD("saving backup...");
SafeWriter* w=e->saveFur(true); SafeWriter* w=e->saveFur(true);
logV("writing file..."); logV("writing file...");
if (w!=NULL) { 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 (outFile!=NULL) {
if (fwrite(w->getFinalBuf(),1,w->size(),outFile)!=w->size()) { if (fwrite(w->getFinalBuf(),1,w->size(),outFile)!=w->size()) {
logW("did not write backup entirely: %s!",strerror(errno)); logW("did not write backup entirely: %s!",strerror(errno));
@ -5421,9 +5549,13 @@ bool FurnaceGUI::loop() {
logW("could not save backup: %s!",strerror(errno)); logW("could not save backup: %s!",strerror(errno));
w->finish(); w->finish();
} }
// delete previous backup if there are too many
delFirstBackup(backupBaseName);
} }
logD("backup saved."); logD("backup saved.");
backupTimer=30.0; backupTimer=30.0;
backupLock.unlock();
return true; return true;
}); });
} }
@ -5837,7 +5969,11 @@ bool FurnaceGUI::init() {
} }
strncpy(finalLayoutPath,(e->getConfigPath()+String(LAYOUT_INI)).c_str(),4095); 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(); prepareLayout();
ImGui::GetIO().ConfigFlags|=ImGuiConfigFlags_DockingEnable; ImGui::GetIO().ConfigFlags|=ImGuiConfigFlags_DockingEnable;
@ -5907,7 +6043,9 @@ bool FurnaceGUI::init() {
void FurnaceGUI::commitState() { void FurnaceGUI::commitState() {
if (!mobileUI) { 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); e->setConf("configVersion",(int)DIV_ENGINE_VERSION);

View File

@ -347,6 +347,7 @@ enum FurnaceGUIMobileScenes {
enum FurnaceGUIFileDialogs { enum FurnaceGUIFileDialogs {
GUI_FILE_OPEN, GUI_FILE_OPEN,
GUI_FILE_OPEN_BACKUP,
GUI_FILE_SAVE, GUI_FILE_SAVE,
GUI_FILE_SAVE_DMF, GUI_FILE_SAVE_DMF,
GUI_FILE_SAVE_DMF_LEGACY, GUI_FILE_SAVE_DMF_LEGACY,
@ -2080,6 +2081,7 @@ class FurnaceGUI {
int loadStream(String path); int loadStream(String path);
void pushRecentFile(String path); void pushRecentFile(String path);
void exportAudio(String path, DivAudioExportModes mode); void exportAudio(String path, DivAudioExportModes mode);
void delFirstBackup(String name);
bool parseSysEx(unsigned char* data, size_t len); bool parseSysEx(unsigned char* data, size_t len);

View File

@ -4406,11 +4406,10 @@ void FurnaceGUI::drawInsEdit() {
ImGui::BeginDisabled(ins->amiga.useWave); ImGui::BeginDisabled(ins->amiga.useWave);
P(ImGui::Checkbox("Use sample map",&ins->amiga.useNoteMap)); P(ImGui::Checkbox("Use sample map",&ins->amiga.useNoteMap));
if (ins->amiga.useNoteMap) { if (ins->amiga.useNoteMap) {
// TODO: frequency map? if (ImGui::BeginTable("NoteMap",3,ImGuiTableFlags_ScrollY|ImGuiTableFlags_Borders|ImGuiTableFlags_SizingStretchSame)) {
if (ImGui::BeginTable("NoteMap",2/*3*/,ImGuiTableFlags_ScrollY|ImGuiTableFlags_Borders|ImGuiTableFlags_SizingStretchSame)) {
ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch); ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch);
//ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch); ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch);
ImGui::TableSetupScrollFreeze(0,1); ImGui::TableSetupScrollFreeze(0,1);
@ -4418,8 +4417,8 @@ void FurnaceGUI::drawInsEdit() {
ImGui::TableNextColumn(); ImGui::TableNextColumn();
ImGui::TableNextColumn(); ImGui::TableNextColumn();
ImGui::Text("Sample"); ImGui::Text("Sample");
/*ImGui::TableNextColumn(); ImGui::TableNextColumn();
ImGui::Text("Frequency");*/ ImGui::Text("Note");
for (int i=0; i<120; i++) { for (int i=0; i<120; i++) {
DivInstrumentAmiga::SampleMap& sampleMap=ins->amiga.noteMap[i]; DivInstrumentAmiga::SampleMap& sampleMap=ins->amiga.noteMap[i];
ImGui::TableNextRow(); ImGui::TableNextRow();
@ -4443,17 +4442,28 @@ void FurnaceGUI::drawInsEdit() {
id=fmt::sprintf("%d: %s",j,e->song.sample[j]->name); id=fmt::sprintf("%d: %s",j,e->song.sample[j]->name);
if (ImGui::Selectable(id.c_str(),sampleMap.map==j)) { PARAMETER if (ImGui::Selectable(id.c_str(),sampleMap.map==j)) { PARAMETER
sampleMap.map=j; 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::EndCombo();
} }
/*ImGui::TableNextColumn();
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
if (ImGui::InputInt("##SF",&sampleMap.freq,50,500)) { PARAMETER const char* nName="???";
if (sampleMap.freq<0) sampleMap.freq=0; if ((sampleMap.freq+60)>0 && (sampleMap.freq+60)<180) {
if (sampleMap.freq>262144) sampleMap.freq=262144; 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::PopID();
} }
ImGui::EndTable(); ImGui::EndTable();

View File

@ -1109,11 +1109,23 @@ void FurnaceGUI::initSystemPresets() {
} }
); );
ENTRY( 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_VERA, 1.0f, 0, ""),
CH(DIV_SYSTEM_YM2151, 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( ENTRY(
"TI-99/4A", { "TI-99/4A", {
CH(DIV_SYSTEM_SMS, 1.0f, 0, CH(DIV_SYSTEM_SMS, 1.0f, 0,

View File

@ -18,13 +18,18 @@
*/ */
#include "ta-log.h" #include "ta-log.h"
#include "fileutils.h"
#include <thread> #include <thread>
#include <condition_variable> #include <condition_variable>
#ifdef _WIN32
#include <windows.h>
#endif
#ifdef IS_MOBILE #ifdef IS_MOBILE
int logLevel=LOGLEVEL_TRACE; int logLevel=LOGLEVEL_TRACE;
#else #else
int logLevel=LOGLEVEL_INFO; int logLevel=LOGLEVEL_TRACE; // until done
#endif #endif
FILE* logFile; FILE* logFile;
@ -133,7 +138,16 @@ int writeLog(int level, const char* msg, fmt::printf_args args) {
} }
void initLog() { 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; logPosition=0;
for (int i=0; i<TA_LOG_SIZE; i++) { for (int i=0; i<TA_LOG_SIZE; i++) {
logEntries[i].text.reserve(128); logEntries[i].text.reserve(128);
@ -169,9 +183,28 @@ bool startLogFile(const char* path) {
if (logFileAvail) return true; if (logFileAvail) return true;
// rotate log file if possible // rotate log file if possible
char oldPath[4096];
char newPath[4096];
if (fileExists(path)==1) {
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) {
deleteFile(oldPath);
} else {
moveFiles(oldPath,newPath);
}
}
}
// open log file // open log file
if ((logFile=fopen(path,"w+"))==NULL) { if ((logFile=ps_fopen(path,"w+"))==NULL) {
logFileAvail=false; logFileAvail=false;
logW("could not open log file! (%s)",strerror(errno)); logW("could not open log file! (%s)",strerror(errno));
return false; return false;

View File

@ -28,7 +28,6 @@
#include "engine/engine.h" #include "engine/engine.h"
#ifdef _WIN32 #ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h> #include <windows.h>
#include <combaseapi.h> #include <combaseapi.h>
#include <shellapi.h> #include <shellapi.h>

View File

@ -66,4 +66,6 @@ struct TAParam {
func(f) {} func(f) {}
}; };
void reportError(String what);
#endif #endif