Merge branch 'master' of https://github.com/tildearrow/furnace into k053260
This commit is contained in:
commit
548aa5e6ac
|
@ -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
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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.
Binary file not shown.
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
```
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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")) {
|
||||
|
|
214
src/gui/gui.cpp
214
src/gui/gui.cpp
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
|
|
39
src/log.cpp
39
src/log.cpp
|
@ -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;
|
||||
|
|
|
@ -28,7 +28,6 @@
|
|||
#include "engine/engine.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <combaseapi.h>
|
||||
#include <shellapi.h>
|
||||
|
|
|
@ -66,4 +66,6 @@ struct TAParam {
|
|||
func(f) {}
|
||||
};
|
||||
|
||||
void reportError(String what);
|
||||
|
||||
#endif
|
||||
|
|
Loading…
Reference in New Issue