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: '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

View File

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

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
#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<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)
return;
return false;
LoadIniSettingsFromMemory(file_data, (size_t)file_data_size);
IM_FREE(file_data);
return true;
}
// 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]);
}
void ImGui::SaveIniSettingsToDisk(const char* ini_filename)
bool ImGui::SaveIniSettingsToDisk(const char* ini_filename, bool redundancy)
{
ImGuiContext& g = *GImGui;
g.SettingsDirtyTimer = 0.0f;
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;
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

View File

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

View File

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

View File

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

View File

@ -23,21 +23,54 @@
#include "../fileutils.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");
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; 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.");
while (!feof(f)) {
if (fgets(line,4095,f)==NULL) {
@ -97,6 +208,7 @@ bool DivConfig::loadFromFile(const char* path, bool createOnFail) {
}
parseLine(line);
}
logD("end of file (%s)",strerror(errno));
fclose(f);
return true;
}
@ -175,6 +287,33 @@ String DivConfig::getString(String key, String fallback) const {
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 {
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<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) {
return conf.erase(key);
}

View File

@ -22,6 +22,8 @@
#include "../ta-utils.h"
#include <map>
#include <vector>
#include <initializer_list>
class DivConfig {
std::map<String,String> 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<String,String>& 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<int> getIntList(String key, std::initializer_list<int> 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<int>& value);
// remove a config value
bool remove(String key);

View File

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

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;
}

View File

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

View File

@ -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 (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;
}
logV("cutStart: %d cutEnd: %d",cutStart,cutEnd);
}
if (length>0) {
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<length; j+=samplePitches[pitch]) {
if (k>=sample->samples) {
break;
short* newData=new short[scaledLen];
int k=0;
float mult=(float)(vol)/50.0f;
for (double j=0; j<length; j+=samplePitches[pitch]) {
if (k>=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 (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++) {
DivInstrument* ins=song.ins[i];
insPtr.push_back(w->tell());
logV("writing instrument %d...",i);
ins->putInsData2(w,false);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 && next<parent->song.sampleLen))) {
DivInstrumentAmiga::SampleMap& noteMapind=ins->amiga.noteMap[next];
@ -420,7 +420,7 @@ void DivPlatformES5506::tick(bool sysTick) {
}
if (sample>=0 && sample<parent->song.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.initSample<parent->song.sampleLen))) {
int sample=ins->amiga.getSample(c.value);
c.value=ins->amiga.getFreq(c.value);
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].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();

View File

@ -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);
}

View File

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

View File

@ -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);
}

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].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;

View File

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

View File

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

View File

@ -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].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[c.chan].sample);
immWrite(8,0);

View File

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

View File

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

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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; 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;
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);
} 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) {

View File

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

View File

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

View File

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

View File

@ -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].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[c.chan].sample);
if (isBanked) {

View File

@ -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].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[c.chan].sample);
immWrite(0x100,0x01); // reset

View File

@ -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].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[c.chan].sample);
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;
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) {
DivSample* s=parent->getSample(chan[c.chan].sample);
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);
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);
}

View File

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

View File

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

View File

@ -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; i<width; i++) {
if (w1->max<1 || w1->len<1) {

View File

@ -20,6 +20,13 @@
#include "fileutils.h"
#ifdef _WIN32
#include "utfutils.h"
#include <windows.h>
#include <shlobj.h>
#include <shlwapi.h>
#else
#include <unistd.h>
#include <errno.h>
#include <sys/stat.h>
#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
}

View File

@ -22,5 +22,11 @@
#include <stdio.h>
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

View File

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

View File

@ -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")) {

View File

@ -50,13 +50,15 @@
#include <shlwapi.h>
#include "../utfutils.h"
#define LAYOUT_INI "\\layout.ini"
#define BACKUP_FUR "\\backup.fur"
#define BACKUPS_DIR "\\backups"
#else
#include <sys/types.h>
#include <dirent.h>
#include <unistd.h>
#include <pwd.h>
#include <sys/stat.h>
#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<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) {
if (!path.empty()) {
logI("loading stream...");
@ -2786,7 +2836,9 @@ void FurnaceGUI::editOptions(bool topMenu) {
void FurnaceGUI::toggleMobileUI(bool enable, bool force) {
if (mobileUI!=enable || force) {
if (!mobileUI && enable) {
ImGui::SaveIniSettingsToDisk(finalLayoutPath);
if (!ImGui::SaveIniSettingsToDisk(finalLayoutPath,true)) {
reportError(fmt::sprintf("could NOT save layout! %s",strerror(errno)));
}
}
mobileUI=enable;
if (mobileUI) {
@ -2796,7 +2848,10 @@ void FurnaceGUI::toggleMobileUI(bool enable, bool force) {
fileDialog->mobileUI=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; i<e->getTotalChannelCount(); 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);

View File

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

View File

@ -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();

View File

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

View File

@ -18,13 +18,18 @@
*/
#include "ta-log.h"
#include "fileutils.h"
#include <thread>
#include <condition_variable>
#ifdef _WIN32
#include <windows.h>
#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<TA_LOG_SIZE; i++) {
logEntries[i].text.reserve(128);
@ -169,9 +183,28 @@ bool startLogFile(const char* path) {
if (logFileAvail) return true;
// 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
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;

View File

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

View File

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