Merge branch 'master' of https://github.com/tildearrow/furnace into dialog-nitpicks

This commit is contained in:
YohananDiamond 2023-04-11 15:31:25 -03:00
commit 95e2d30f14
71 changed files with 853 additions and 228 deletions

View file

@ -11,7 +11,7 @@ defaults:
shell: bash shell: bash
env: env:
BUILD_TYPE: RelWithDebInfo BUILD_TYPE: Debug
jobs: jobs:
build: build:
@ -24,7 +24,7 @@ jobs:
#- { name: 'Windows MinGW x86_64', os: ubuntu-20.04, compiler: mingw, arch: x86_64 } #- { name: 'Windows MinGW x86_64', os: ubuntu-20.04, compiler: mingw, arch: x86_64 }
- { name: 'macOS x86_64', os: macos-latest, arch: x86_64 } - { name: 'macOS x86_64', os: macos-latest, arch: x86_64 }
- { name: 'macOS ARM', os: macos-latest, arch: arm64 } - { name: 'macOS ARM', os: macos-latest, arch: arm64 }
- { name: 'Linux x86_64', os: ubuntu-18.04, arch: x86_64 } - { name: 'Linux x86_64', os: ubuntu-20.04, arch: x86_64 }
#- { name: 'Linux ARM', os: ubuntu-18.04, arch: armhf } #- { name: 'Linux ARM', os: ubuntu-18.04, arch: armhf }
fail-fast: false fail-fast: false

View file

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
demos/multichip/collab.fur Normal file

Binary file not shown.

View file

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

View file

@ -950,9 +950,9 @@ namespace ImGui
// - The disk functions are automatically called if io.IniFilename != NULL (default is "imgui.ini"). // - The disk functions are automatically called if io.IniFilename != NULL (default is "imgui.ini").
// - Set io.IniFilename to NULL to load/save manually. Read io.WantSaveIniSettings description about handling .ini saving manually. // - Set io.IniFilename to NULL to load/save manually. Read io.WantSaveIniSettings description about handling .ini saving manually.
// - Important: default value "imgui.ini" is relative to current working dir! Most apps will want to lock this to an absolute path (e.g. same path as executables). // - Important: default value "imgui.ini" is relative to current working dir! Most apps will want to lock this to an absolute path (e.g. same path as executables).
IMGUI_API void LoadIniSettingsFromDisk(const char* ini_filename); // call after CreateContext() and before the first call to NewFrame(). NewFrame() automatically calls LoadIniSettingsFromDisk(io.IniFilename). IMGUI_API bool LoadIniSettingsFromDisk(const char* ini_filename, bool redundancy=false); // call after CreateContext() and before the first call to NewFrame(). NewFrame() automatically calls LoadIniSettingsFromDisk(io.IniFilename).
IMGUI_API void LoadIniSettingsFromMemory(const char* ini_data, size_t ini_size=0); // call after CreateContext() and before the first call to NewFrame() to provide .ini data from your own data source. IMGUI_API void LoadIniSettingsFromMemory(const char* ini_data, size_t ini_size=0); // call after CreateContext() and before the first call to NewFrame() to provide .ini data from your own data source.
IMGUI_API void SaveIniSettingsToDisk(const char* ini_filename); // this is automatically called (if io.IniFilename is not empty) a few seconds after any modification that should be reflected in the .ini file (and also by DestroyContext). IMGUI_API bool SaveIniSettingsToDisk(const char* ini_filename, bool redundancy=false); // this is automatically called (if io.IniFilename is not empty) a few seconds after any modification that should be reflected in the .ini file (and also by DestroyContext).
IMGUI_API const char* SaveIniSettingsToMemory(size_t* out_ini_size = NULL); // return a zero-terminated string with the .ini data which you can save by your own mean. call when io.WantSaveIniSettings is set, then save data by your own mean and clear io.WantSaveIniSettings. IMGUI_API const char* SaveIniSettingsToMemory(size_t* out_ini_size = NULL); // return a zero-terminated string with the .ini data which you can save by your own mean. call when io.WantSaveIniSettings is set, then save data by your own mean and clear io.WantSaveIniSettings.
// Debug Utilities // Debug Utilities

View file

@ -204,7 +204,9 @@ static void CopyNFDCharToWChar( const nfdchar_t *inStr, wchar_t **outStr )
#ifdef _DEBUG #ifdef _DEBUG
int inStrCharacterCount = static_cast<int>(NFDi_UTF8_Strlen(inStr)); int inStrCharacterCount = static_cast<int>(NFDi_UTF8_Strlen(inStr));
assert( ret == inStrCharacterCount ); if (ret!=inStrCharacterCount) {
logW("length does not match! %d != %d",ret,inStrCharacterCount);
}
#else #else
_NFD_UNUSED(ret); _NFD_UNUSED(ret);
#endif #endif

View file

@ -32,6 +32,15 @@ these fields are 0 in format versions prior to 100 (0.6pre1).
the format versions are: the format versions are:
- 152: Furnace dev152
- 151: Furnace dev151
- 150: Furnace dev150
- 149: Furnace dev149
- 148: Furnace dev148
- 147: Furnace dev147
- 146: Furnace Pro (joke version)
- 145: Furnace dev145
- 144: Furnace dev144
- 143: Furnace 0.6pre4 - 143: Furnace 0.6pre4
- 142: Furnace dev142 - 142: Furnace dev142
- 141: Furnace Tournament Edition (for intro tune contest) - 141: Furnace Tournament Edition (for intro tune contest)

View file

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

View file

@ -23,21 +23,54 @@
#include "../fileutils.h" #include "../fileutils.h"
#include <fmt/printf.h> #include <fmt/printf.h>
bool DivConfig::save(const char* path) { #define REDUNDANCY_NUM_ATTEMPTS 5
#define CHECK_BUF_SIZE 8192
bool DivConfig::save(const char* path, bool redundancy) {
if (redundancy) {
char oldPath[4096];
char newPath[4096];
if (fileExists(path)==1) {
logD("rotating config files...");
for (int i=4; i>=0; i--) {
if (i>0) {
snprintf(oldPath,4095,"%s.%d",path,i);
} else {
strncpy(oldPath,path,4095);
}
snprintf(newPath,4095,"%s.%d",path,i+1);
if (i>=4) {
logV("remove %s",oldPath);
deleteFile(oldPath);
} else {
logV("move %s to %s",oldPath,newPath);
moveFiles(oldPath,newPath);
}
}
}
}
logD("opening config for write: %s",path);
FILE* f=ps_fopen(path,"wb"); FILE* f=ps_fopen(path,"wb");
if (f==NULL) { if (f==NULL) {
logW("could not write config file! %s",strerror(errno)); logW("could not write config file! %s",strerror(errno));
reportError(fmt::sprintf("could not write config file! %s",strerror(errno)));
return false; return false;
} }
for (auto& i: conf) { for (auto& i: conf) {
String toWrite=fmt::sprintf("%s=%s\n",i.first,i.second); String toWrite=fmt::sprintf("%s=%s\n",i.first,i.second);
if (fwrite(toWrite.c_str(),1,toWrite.size(),f)!=toWrite.size()) { if (fwrite(toWrite.c_str(),1,toWrite.size(),f)!=toWrite.size()) {
logW("could not write config file! %s",strerror(errno)); logW("could not write config file! %s",strerror(errno));
reportError(fmt::sprintf("could not write config file! %s",strerror(errno)));
logV("removing config file");
fclose(f); fclose(f);
deleteFile(path);
return false; return false;
} }
} }
fclose(f); fclose(f);
logD("config file written successfully.");
return true; return true;
} }
@ -63,6 +96,7 @@ void DivConfig::parseLine(const char* line) {
String value=""; String value="";
bool keyOrValue=false; bool keyOrValue=false;
for (const char* i=line; *i; i++) { for (const char* i=line; *i; i++) {
if (*i=='\r') continue;
if (*i=='\n') continue; if (*i=='\n') continue;
if (keyOrValue) { if (keyOrValue) {
value+=*i; value+=*i;
@ -79,17 +113,94 @@ void DivConfig::parseLine(const char* line) {
} }
} }
bool DivConfig::loadFromFile(const char* path, bool createOnFail) { bool DivConfig::loadFromFile(const char* path, bool createOnFail, bool redundancy) {
char line[4096]; char line[4096];
FILE* f=ps_fopen(path,"rb"); logD("opening config for read: %s",path);
if (f==NULL) {
if (createOnFail) { FILE* f=NULL;
logI("creating default config.");
return save(path); if (redundancy) {
} else { unsigned char* readBuf=new unsigned char[CHECK_BUF_SIZE];
return false; size_t readBufLen=0;
for (int i=0; i<REDUNDANCY_NUM_ATTEMPTS; i++) {
bool viable=false;
if (i>0) {
snprintf(line,4095,"%s.%d",path,i);
} else {
strncpy(line,path,4095);
}
logV("trying: %s",line);
// try to open config
f=ps_fopen(line,"rb");
// check whether we could open it
if (f==NULL) {
logV("fopen(): %s",strerror(errno));
continue;
}
// check whether there's something
while (!feof(f)) {
readBufLen=fread(readBuf,1,CHECK_BUF_SIZE,f);
if (ferror(f)) {
logV("fread(): %s",strerror(errno));
break;
}
for (size_t j=0; j<readBufLen; j++) {
if (readBuf[j]!='\r' && readBuf[j]!='\n' && readBuf[j]!=' ') {
viable=true;
break;
}
}
if (viable) break;
}
// there's something
if (viable) {
if (fseek(f,0,SEEK_SET)==-1) {
logV("fseek(): %s",strerror(errno));
viable=false;
} else {
break;
}
}
// close it (because there's nothing)
fclose(f);
f=NULL;
}
delete[] readBuf;
// we couldn't read at all
if (f==NULL) {
logD("config does not exist");
if (createOnFail) {
logI("creating default config.");
reportError(fmt::sprintf("Creating default config: %s",strerror(errno)));
return save(path,redundancy);
} else {
reportError(fmt::sprintf("COULD NOT LOAD CONFIG %s",strerror(errno)));
return false;
}
}
} else {
f=ps_fopen(path,"rb");
if (f==NULL) {
logD("config does not exist");
if (createOnFail) {
logI("creating default config.");
reportError(fmt::sprintf("Creating default config: %s",strerror(errno)));
return save(path);
} else {
reportError(fmt::sprintf("COULD NOT LOAD CONFIG %s",strerror(errno)));
return false;
}
} }
} }
logI("loading config."); logI("loading config.");
while (!feof(f)) { while (!feof(f)) {
if (fgets(line,4095,f)==NULL) { if (fgets(line,4095,f)==NULL) {
@ -97,6 +208,7 @@ bool DivConfig::loadFromFile(const char* path, bool createOnFail) {
} }
parseLine(line); parseLine(line);
} }
logD("end of file (%s)",strerror(errno));
fclose(f); fclose(f);
return true; return true;
} }

View file

@ -32,10 +32,10 @@ class DivConfig {
// config loading/saving // config loading/saving
bool loadFromMemory(const char* buf); bool loadFromMemory(const char* buf);
bool loadFromBase64(const char* buf); bool loadFromBase64(const char* buf);
bool loadFromFile(const char* path, bool createOnFail=true); bool loadFromFile(const char* path, bool createOnFail=true, bool redundancy=false);
String toString(); String toString();
String toBase64(); String toBase64();
bool save(const char* path); bool save(const char* path, bool redundancy=false);
// get the map // get the map
const std::map<String,String>& configMap(); const std::map<String,String>& configMap();

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -560,7 +560,10 @@ int DivPlatformAmiga::dispatch(DivCommand c) {
} }
} }
} else { } else {
if (c.value!=DIV_NOTE_NULL) chan[c.chan].sample=ins->amiga.getSample(c.value); if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].sample=ins->amiga.getSample(c.value);
c.value=ins->amiga.getFreq(c.value);
}
chan[c.chan].useWave=false; chan[c.chan].useWave=false;
} }
if (c.value!=DIV_NOTE_NULL) { if (c.value!=DIV_NOTE_NULL) {

View file

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

View file

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

View file

@ -410,7 +410,7 @@ void DivPlatformES5506::tick(bool sysTick) {
if (chan[i].pcmChanged.changed) { if (chan[i].pcmChanged.changed) {
if (chan[i].pcmChanged.index) { if (chan[i].pcmChanged.index) {
const int next=chan[i].pcm.next; const int next=chan[i].pcm.next;
bool sampleVaild=false; bool sampleValid=false;
if (((ins->amiga.useNoteMap) && (next>=0 && next<120)) || if (((ins->amiga.useNoteMap) && (next>=0 && next<120)) ||
((!ins->amiga.useNoteMap) && (next>=0 && next<parent->song.sampleLen))) { ((!ins->amiga.useNoteMap) && (next>=0 && next<parent->song.sampleLen))) {
DivInstrumentAmiga::SampleMap& noteMapind=ins->amiga.noteMap[next]; DivInstrumentAmiga::SampleMap& noteMapind=ins->amiga.noteMap[next];
@ -420,7 +420,7 @@ void DivPlatformES5506::tick(bool sysTick) {
} }
if (sample>=0 && sample<parent->song.sampleLen) { if (sample>=0 && sample<parent->song.sampleLen) {
const unsigned int offES5506=sampleOffES5506[sample]; const unsigned int offES5506=sampleOffES5506[sample];
sampleVaild=true; sampleValid=true;
chan[i].pcm.index=sample; chan[i].pcm.index=sample;
chan[i].pcm.isNoteMap=ins->amiga.useNoteMap; chan[i].pcm.isNoteMap=ins->amiga.useNoteMap;
DivSample* s=parent->getSample(sample); DivSample* s=parent->getSample(sample);
@ -433,7 +433,6 @@ void DivPlatformES5506::tick(bool sysTick) {
off=(double)center/8363.0; off=(double)center/8363.0;
} }
if (ins->amiga.useNoteMap) { if (ins->amiga.useNoteMap) {
off*=(double)noteMapind.freq/((double)MAX(1,center)*pow(2.0,((double)next-48.0)/12.0));
chan[i].pcm.note=next; chan[i].pcm.note=next;
} }
// get loop mode // get loop mode
@ -459,7 +458,7 @@ void DivPlatformES5506::tick(bool sysTick) {
} }
} }
} }
if (sampleVaild) { if (sampleValid) {
if (!chan[i].keyOn) { if (!chan[i].keyOn) {
pageWrite(0x20|i,0x03,(chan[i].pcm.direction)?chan[i].pcm.end:chan[i].pcm.start); pageWrite(0x20|i,0x03,(chan[i].pcm.direction)?chan[i].pcm.end:chan[i].pcm.start);
} }
@ -618,10 +617,6 @@ void DivPlatformES5506::tick(bool sysTick) {
} else { } else {
off=(double)center/8363.0; off=(double)center/8363.0;
} }
if (ins->amiga.useNoteMap) {
DivInstrumentAmiga::SampleMap& noteMapind=ins->amiga.noteMap[chan[i].pcm.note];
off*=(double)noteMapind.freq/((double)MAX(1,center)*pow(2.0,((double)chan[i].pcm.note-48.0)/12.0));
}
chan[i].pcm.loopStart=(chan[i].pcm.start+(s->loopStart<<11))&0xfffff800; chan[i].pcm.loopStart=(chan[i].pcm.start+(s->loopStart<<11))&0xfffff800;
chan[i].pcm.loopEnd=(chan[i].pcm.start+((s->loopEnd-1)<<11))&0xffffff80; chan[i].pcm.loopEnd=(chan[i].pcm.start+((s->loopEnd-1)<<11))&0xffffff80;
chan[i].pcm.freqOffs=PITCH_OFFSET*off; chan[i].pcm.freqOffs=PITCH_OFFSET*off;
@ -750,12 +745,13 @@ int DivPlatformES5506::dispatch(DivCommand c) {
switch (c.cmd) { switch (c.cmd) {
case DIV_CMD_NOTE_ON: { case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_ES5506); DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_ES5506);
bool sampleVaild=false; bool sampleValid=false;
if (((ins->amiga.useNoteMap) && (c.value>=0 && c.value<120)) || if (((ins->amiga.useNoteMap) && (c.value>=0 && c.value<120)) ||
((!ins->amiga.useNoteMap) && (ins->amiga.initSample>=0 && ins->amiga.initSample<parent->song.sampleLen))) { ((!ins->amiga.useNoteMap) && (ins->amiga.initSample>=0 && ins->amiga.initSample<parent->song.sampleLen))) {
int sample=ins->amiga.getSample(c.value); int sample=ins->amiga.getSample(c.value);
c.value=ins->amiga.getFreq(c.value);
if (sample>=0 && sample<parent->song.sampleLen) { if (sample>=0 && sample<parent->song.sampleLen) {
sampleVaild=true; sampleValid=true;
chan[c.chan].volMacroMax=ins->type==DIV_INS_AMIGA?64:0xfff; chan[c.chan].volMacroMax=ins->type==DIV_INS_AMIGA?64:0xfff;
chan[c.chan].panMacroMax=ins->type==DIV_INS_AMIGA?127:0xfff; chan[c.chan].panMacroMax=ins->type==DIV_INS_AMIGA?127:0xfff;
chan[c.chan].pcm.note=c.value; chan[c.chan].pcm.note=c.value;
@ -764,7 +760,7 @@ int DivPlatformES5506::dispatch(DivCommand c) {
chan[c.chan].envelope=ins->es5506.envelope; chan[c.chan].envelope=ins->es5506.envelope;
} }
} }
if (!sampleVaild) { if (!sampleValid) {
chan[c.chan].pcm.index=chan[c.chan].pcm.next=-1; chan[c.chan].pcm.index=chan[c.chan].pcm.next=-1;
chan[c.chan].filter=DivInstrumentES5506::Filter(); chan[c.chan].filter=DivInstrumentES5506::Filter();
chan[c.chan].envelope=DivInstrumentES5506::Envelope(); chan[c.chan].envelope=DivInstrumentES5506::Envelope();

View file

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

View file

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

View file

@ -433,13 +433,8 @@ void DivPlatformGenesisExt::muteChannel(int ch, bool mute) {
DivInstrumentFM::Operator op=chan[2].state.op[ordch]; DivInstrumentFM::Operator op=chan[2].state.op[ordch];
if (isOpMuted[ch-2] || !op.enable) { if (isOpMuted[ch-2] || !op.enable) {
rWrite(baseAddr+0x40,127); rWrite(baseAddr+0x40,127);
immWrite(baseAddr+0x40,127);
} else if (KVS(2,ordch)) {
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-2].outVol&0x7f,127));
immWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-2].outVol&0x7f,127));
} else { } else {
rWrite(baseAddr+0x40,op.tl); rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-2].outVol&0x7f,127));
immWrite(baseAddr+0x40,op.tl);
} }
rWrite(chanOffs[2]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch-2].pan<<6))|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4)); rWrite(chanOffs[2]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch-2].pan<<6))|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4));

View file

@ -34,7 +34,7 @@ const char* regCheatSheetK007232[]={
"CHX_StartM", "X*6+3", "CHX_StartM", "X*6+3",
"CHX_StartH", "X*6+4", "CHX_StartH", "X*6+4",
"CHX_Keyon", "X*6+5", "CHX_Keyon", "X*6+5",
"SLEV", "C", // external IO "SLEV", "C", // external IO (Volume for Mono speaker)
"Loop", "D", "Loop", "D",
// off-chip // off-chip
"CHX_Volume", "X*2+10", "CHX_Volume", "X*2+10",
@ -157,8 +157,7 @@ void DivPlatformK007232::tick(bool sysTick) {
rWrite(0x10+i,(chan[i].lvol&0xf)|((chan[i].rvol&0xf)<<4)); rWrite(0x10+i,(chan[i].lvol&0xf)|((chan[i].rvol&0xf)<<4));
chan[i].prevPan=newPan; chan[i].prevPan=newPan;
} }
} } else {
else {
const unsigned char prevVolume=lastVolume; const unsigned char prevVolume=lastVolume;
lastVolume=(lastVolume&~(0xf<<(i<<2)))|((chan[i].resVol&0xf)<<(i<<2)); lastVolume=(lastVolume&~(0xf<<(i<<2)))|((chan[i].resVol&0xf)<<(i<<2));
if (prevVolume!=lastVolume) { if (prevVolume!=lastVolume) {
@ -274,7 +273,10 @@ int DivPlatformK007232::dispatch(DivCommand c) {
case DIV_CMD_NOTE_ON: { case DIV_CMD_NOTE_ON: {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA); DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA);
chan[c.chan].macroVolMul=ins->type==DIV_INS_AMIGA?64:15; chan[c.chan].macroVolMul=ins->type==DIV_INS_AMIGA?64:15;
if (c.value!=DIV_NOTE_NULL) chan[c.chan].sample=ins->amiga.getSample(c.value); if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].sample=ins->amiga.getSample(c.value);
c.value=ins->amiga.getFreq(c.value);
}
if (c.value!=DIV_NOTE_NULL) { if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); chan[c.chan].baseFreq=NOTE_PERIODIC(c.value);
} }
@ -477,6 +479,7 @@ void DivPlatformK007232::setFlags(const DivConfig& flags) {
rate=chipClock/4; rate=chipClock/4;
stereo=flags.getBool("stereo",false); stereo=flags.getBool("stereo",false);
for (int i=0; i<2; i++) { for (int i=0; i<2; i++) {
chan[i].volumeChanged=true;
oscBuf[i]->rate=rate; oscBuf[i]->rate=rate;
} }
} }

View file

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

View file

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

View file

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

View file

@ -856,7 +856,10 @@ int DivPlatformOPL::dispatch(DivCommand c) {
chan[c.chan].outVol=chan[c.chan].vol; chan[c.chan].outVol=chan[c.chan].vol;
immWrite(18,chan[c.chan].outVol); immWrite(18,chan[c.chan].outVol);
} }
if (c.value!=DIV_NOTE_NULL) chan[c.chan].sample=ins->amiga.getSample(c.value); if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].sample=ins->amiga.getSample(c.value);
c.value=ins->amiga.getFreq(c.value);
}
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) { if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[c.chan].sample); DivSample* s=parent->getSample(chan[c.chan].sample);
immWrite(8,0); immWrite(8,0);

View file

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

View file

@ -271,7 +271,10 @@ int DivPlatformPCMDAC::dispatch(DivCommand c) {
} }
} }
} else { } else {
if (c.value!=DIV_NOTE_NULL) chan[0].sample=ins->amiga.getSample(c.value); if (c.value!=DIV_NOTE_NULL) {
chan[0].sample=ins->amiga.getSample(c.value);
c.value=ins->amiga.getFreq(c.value);
}
chan[0].useWave=false; chan[0].useWave=false;
} }
if (c.value!=DIV_NOTE_NULL) { if (c.value!=DIV_NOTE_NULL) {

View file

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

View file

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

View file

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

View file

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

View file

@ -138,9 +138,7 @@ void DivPlatformSoundUnit::tick(bool sysTick) {
//DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU); //DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU);
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,chan[i].switchRoles,2,chan[i].pitch2,chipClock,chan[i].switchRoles?CHIP_DIVIDER:CHIP_FREQBASE); chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,chan[i].fixedArp?chan[i].baseNoteOverride:chan[i].arpOff,chan[i].fixedArp,chan[i].switchRoles,2,chan[i].pitch2,chipClock,chan[i].switchRoles?CHIP_DIVIDER:CHIP_FREQBASE);
if (chan[i].pcm) { if (chan[i].pcm) {
DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU); DivSample* sample=parent->getSample(chan[i].sample);
// TODO: sample map?
DivSample* sample=parent->getSample(ins->amiga.getSample(chan[i].note));
if (sample!=NULL) { if (sample!=NULL) {
double off=0.25; double off=0.25;
if (sample->centerRate<1) { if (sample->centerRate<1) {
@ -209,6 +207,12 @@ int DivPlatformSoundUnit::dispatch(DivCommand c) {
writeControlUpper(c.chan); writeControlUpper(c.chan);
} }
chan[c.chan].pcm=(ins->type==DIV_INS_AMIGA || ins->amiga.useSample); chan[c.chan].pcm=(ins->type==DIV_INS_AMIGA || ins->amiga.useSample);
if (chan[c.chan].pcm) {
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].sample=ins->amiga.getSample(c.value);
c.value=ins->amiga.getFreq(c.value);
}
}
if (c.value!=DIV_NOTE_NULL) { if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=NOTE_SU(c.chan,c.value); chan[c.chan].baseFreq=NOTE_SU(c.chan,c.value);
chan[c.chan].freqChanged=true; chan[c.chan].freqChanged=true;

View file

@ -26,7 +26,7 @@
class DivPlatformSoundUnit: public DivDispatch { class DivPlatformSoundUnit: public DivDispatch {
struct Channel: public SharedChannel<signed char> { struct Channel: public SharedChannel<signed char> {
int cutoff, baseCutoff, res, control, hasOffset; int cutoff, baseCutoff, res, control, hasOffset, sample;
signed char pan; signed char pan;
unsigned char duty; unsigned char duty;
bool noise, pcm, phaseReset, filterPhaseReset, switchRoles; bool noise, pcm, phaseReset, filterPhaseReset, switchRoles;
@ -43,6 +43,7 @@ class DivPlatformSoundUnit: public DivDispatch {
res(0), res(0),
control(0), control(0),
hasOffset(0), hasOffset(0),
sample(-1),
pan(0), pan(0),
duty(63), duty(63),
noise(false), noise(false),

View file

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

View file

@ -235,7 +235,11 @@ int DivPlatformVERA::dispatch(DivCommand c) {
if (c.chan<16) { if (c.chan<16) {
rWriteLo(c.chan,2,chan[c.chan].vol); rWriteLo(c.chan,2,chan[c.chan].vol);
} else { } else {
if (c.value!=DIV_NOTE_NULL) chan[16].pcm.sample=parent->getIns(chan[16].ins,DIV_INS_VERA)->amiga.getSample(c.value); if (c.value!=DIV_NOTE_NULL) {
DivInstrument* ins=parent->getIns(chan[16].ins,DIV_INS_VERA);
chan[16].pcm.sample=ins->amiga.getSample(c.value);
c.value=ins->amiga.getFreq(c.value);
}
if (chan[16].pcm.sample<0 || chan[16].pcm.sample>=parent->song.sampleLen) { if (chan[16].pcm.sample<0 || chan[16].pcm.sample>=parent->song.sampleLen) {
chan[16].pcm.sample=-1; chan[16].pcm.sample=-1;
} }

View file

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

View file

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

View file

@ -542,13 +542,8 @@ void DivPlatformYM2203Ext::muteChannel(int ch, bool mute) {
DivInstrumentFM::Operator op=chan[2].state.op[ordch]; DivInstrumentFM::Operator op=chan[2].state.op[ordch];
if (isOpMuted[ch-2] || !op.enable) { if (isOpMuted[ch-2] || !op.enable) {
rWrite(baseAddr+0x40,127); rWrite(baseAddr+0x40,127);
immWrite(baseAddr+0x40,127);
} else if (KVS(2,ordch)) {
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-2].outVol&0x7f,127));
immWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-2].outVol&0x7f,127));
} else { } else {
rWrite(baseAddr+0x40,op.tl); rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-2].outVol&0x7f,127));
immWrite(baseAddr+0x40,op.tl);
} }
} }

View file

@ -896,7 +896,10 @@ int DivPlatformYM2608::dispatch(DivCommand c) {
chan[c.chan].outVol=chan[c.chan].vol; chan[c.chan].outVol=chan[c.chan].vol;
immWrite(0x10b,chan[c.chan].outVol); immWrite(0x10b,chan[c.chan].outVol);
} }
if (c.value!=DIV_NOTE_NULL) chan[c.chan].sample=ins->amiga.getSample(c.value); if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].sample=ins->amiga.getSample(c.value);
c.value=ins->amiga.getFreq(c.value);
}
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) { if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[c.chan].sample); DivSample* s=parent->getSample(chan[c.chan].sample);
immWrite(0x100,0x01); // reset immWrite(0x100,0x01); // reset

View file

@ -564,13 +564,8 @@ void DivPlatformYM2608Ext::muteChannel(int ch, bool mute) {
DivInstrumentFM::Operator op=chan[2].state.op[ordch]; DivInstrumentFM::Operator op=chan[2].state.op[ordch];
if (isOpMuted[ch-2] || !op.enable) { if (isOpMuted[ch-2] || !op.enable) {
rWrite(baseAddr+0x40,127); rWrite(baseAddr+0x40,127);
immWrite(baseAddr+0x40,127);
} else if (KVS(2,ordch)) {
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-2].outVol&0x7f,127));
immWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-2].outVol&0x7f,127));
} else { } else {
rWrite(baseAddr+0x40,op.tl); rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-2].outVol&0x7f,127));
immWrite(baseAddr+0x40,op.tl);
} }
rWrite(chanOffs[2]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch-2].pan<<6))|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4)); rWrite(chanOffs[2]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch-2].pan<<6))|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4));

View file

@ -827,7 +827,10 @@ int DivPlatformYM2610::dispatch(DivCommand c) {
chan[c.chan].outVol=chan[c.chan].vol; chan[c.chan].outVol=chan[c.chan].vol;
immWrite(0x1b,chan[c.chan].outVol); immWrite(0x1b,chan[c.chan].outVol);
} }
if (c.value!=DIV_NOTE_NULL) chan[c.chan].sample=ins->amiga.getSample(c.value); if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].sample=ins->amiga.getSample(c.value);
c.value=ins->amiga.getFreq(c.value);
}
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) { if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[c.chan].sample); DivSample* s=parent->getSample(chan[c.chan].sample);
immWrite(0x12,(sampleOffB[chan[c.chan].sample]>>8)&0xff); immWrite(0x12,(sampleOffB[chan[c.chan].sample]>>8)&0xff);

View file

@ -894,7 +894,10 @@ int DivPlatformYM2610B::dispatch(DivCommand c) {
chan[c.chan].outVol=chan[c.chan].vol; chan[c.chan].outVol=chan[c.chan].vol;
immWrite(0x1b,chan[c.chan].outVol); immWrite(0x1b,chan[c.chan].outVol);
} }
if (c.value!=DIV_NOTE_NULL) chan[c.chan].sample=ins->amiga.getSample(c.value); if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].sample=ins->amiga.getSample(c.value);
c.value=ins->amiga.getFreq(c.value);
}
if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) { if (chan[c.chan].sample>=0 && chan[c.chan].sample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[c.chan].sample); DivSample* s=parent->getSample(chan[c.chan].sample);
immWrite(0x12,(sampleOffB[chan[c.chan].sample]>>8)&0xff); immWrite(0x12,(sampleOffB[chan[c.chan].sample]>>8)&0xff);

View file

@ -560,13 +560,8 @@ void DivPlatformYM2610BExt::muteChannel(int ch, bool mute) {
DivInstrumentFM::Operator op=chan[extChanOffs].state.op[ordch]; DivInstrumentFM::Operator op=chan[extChanOffs].state.op[ordch];
if (isOpMuted[ch-extChanOffs] || !op.enable) { if (isOpMuted[ch-extChanOffs] || !op.enable) {
rWrite(baseAddr+0x40,127); rWrite(baseAddr+0x40,127);
immWrite(baseAddr+0x40,127);
} else if (KVS(2,ordch)) {
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-extChanOffs].outVol&0x7f,127));
immWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-extChanOffs].outVol&0x7f,127));
} else { } else {
rWrite(baseAddr+0x40,op.tl); rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-extChanOffs].outVol&0x7f,127));
immWrite(baseAddr+0x40,op.tl);
} }
rWrite(chanOffs[extChanOffs]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch-extChanOffs].pan<<6))|(chan[extChanOffs].state.fms&7)|((chan[extChanOffs].state.ams&3)<<4)); rWrite(chanOffs[extChanOffs]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch-extChanOffs].pan<<6))|(chan[extChanOffs].state.fms&7)|((chan[extChanOffs].state.ams&3)<<4));

View file

@ -560,13 +560,8 @@ void DivPlatformYM2610Ext::muteChannel(int ch, bool mute) {
DivInstrumentFM::Operator op=chan[extChanOffs].state.op[ordch]; DivInstrumentFM::Operator op=chan[extChanOffs].state.op[ordch];
if (isOpMuted[ch-extChanOffs] || !op.enable) { if (isOpMuted[ch-extChanOffs] || !op.enable) {
rWrite(baseAddr+0x40,127); rWrite(baseAddr+0x40,127);
immWrite(baseAddr+0x40,127);
} else if (KVS(2,ordch)) {
rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-extChanOffs].outVol&0x7f,127));
immWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-extChanOffs].outVol&0x7f,127));
} else { } else {
rWrite(baseAddr+0x40,op.tl); rWrite(baseAddr+0x40,127-VOL_SCALE_LOG_BROKEN(127-op.tl,opChan[ch-extChanOffs].outVol&0x7f,127));
immWrite(baseAddr+0x40,op.tl);
} }
rWrite(chanOffs[extChanOffs]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch-extChanOffs].pan<<6))|(chan[extChanOffs].state.fms&7)|((chan[extChanOffs].state.ams&3)<<4)); rWrite(chanOffs[extChanOffs]+0xb4,(IS_EXTCH_MUTED?0:(opChan[ch-extChanOffs].pan<<6))|(chan[extChanOffs].state.fms&7)|((chan[extChanOffs].state.ams&3)<<4));

View file

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

View file

@ -32,11 +32,11 @@ const char** DivPlatformZXBeeperQuadTone::getRegisterSheet() {
void DivPlatformZXBeeperQuadTone::acquire(short** buf, size_t len) { void DivPlatformZXBeeperQuadTone::acquire(short** buf, size_t len) {
bool o=false; bool o=false;
for (size_t h=0; h<len; h++) { for (size_t h=0; h<len; h++) {
if (curSample>=0 && curSample<parent->song.sampleLen) { if (curSample>=0 && curSample<parent->song.sampleLen && !isMuted[4]) {
while (curSamplePeriod>=chan[4].freq) { while (curSamplePeriod>=chan[4].freq) {
DivSample* s=parent->getSample(curSample); DivSample* s=parent->getSample(curSample);
if (s->samples>0) { if (s->samples>0) {
o=(!isMuted[4]&&s->data8[curSamplePos++]>0); if (!isMuted[4]) o=(s->data8[curSamplePos++]>0);
if (curSamplePos>=s->samples) curSample=-1; if (curSamplePos>=s->samples) curSample=-1;
// (theoretical) 32KiB limit // (theoretical) 32KiB limit
if (curSamplePos>=32768*8) curSample=-1; if (curSamplePos>=32768*8) curSample=-1;
@ -59,6 +59,7 @@ void DivPlatformZXBeeperQuadTone::acquire(short** buf, size_t len) {
if ((outputClock&1)==0) { if ((outputClock&1)==0) {
chan[ch].sPosition+=(regPool[1+b]<<8)|regPool[0+b]; chan[ch].sPosition+=(regPool[1+b]<<8)|regPool[0+b];
chan[ch].out=regPool[3+b]+((((chan[ch].sPosition>>8)&0xff)<regPool[2+b])?1:0); chan[ch].out=regPool[3+b]+((((chan[ch].sPosition>>8)&0xff)<regPool[2+b])?1:0);
if (isMuted[ch]) chan[ch].out=0;
} }
if ((outputClock&3)==0) { if ((outputClock&3)==0) {
oscBuf[4]->data[oscBuf[4]->needle++]=0; oscBuf[4]->data[oscBuf[4]->needle++]=0;
@ -66,6 +67,22 @@ void DivPlatformZXBeeperQuadTone::acquire(short** buf, size_t len) {
o=chan[ch].out&0x10; o=chan[ch].out&0x10;
oscBuf[ch]->data[oscBuf[ch]->needle++]=o?32767:0; oscBuf[ch]->data[oscBuf[ch]->needle++]=o?32767:0;
chan[ch].out<<=1; chan[ch].out<<=1;
// if muted, ztill run sample
if (curSample>=0 && curSample<parent->song.sampleLen && isMuted[4]) {
while (curSamplePeriod>=chan[4].freq) {
DivSample* s=parent->getSample(curSample);
if (s->samples>0) {
if (curSamplePos>=s->samples) curSample=-1;
// (theoretical) 32KiB limit
if (curSamplePos>=32768*8) curSample=-1;
} else {
curSample=-1;
}
curSamplePeriod-=chan[4].freq;
}
curSamplePeriod+=40;
}
} }
outputClock=(outputClock+1)&7; outputClock=(outputClock+1)&7;
buf[0][h]=o?32767:0; buf[0][h]=o?32767:0;
@ -169,10 +186,11 @@ int DivPlatformZXBeeperQuadTone::dispatch(DivCommand c) {
} else { } else {
DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA); DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_AMIGA);
if (c.value!=DIV_NOTE_NULL) { if (c.value!=DIV_NOTE_NULL) {
curSample=ins->amiga.getSample(c.value);
c.value=ins->amiga.getFreq(c.value);
chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); chan[c.chan].baseFreq=NOTE_PERIODIC(c.value);
chan[c.chan].freqChanged=true; chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value; chan[c.chan].note=c.value;
curSample=ins->amiga.getSample(c.value);
// TODO support offset commands // TODO support offset commands
curSamplePos=0; curSamplePos=0;
curSamplePeriod=0; curSamplePeriod=0;
@ -282,7 +300,7 @@ int DivPlatformZXBeeperQuadTone::dispatch(DivCommand c) {
void DivPlatformZXBeeperQuadTone::writeOutVol(int ch) { void DivPlatformZXBeeperQuadTone::writeOutVol(int ch) {
if (ch>=4) return; if (ch>=4) return;
unsigned char val=(chan[ch].outVol>=1)?((chan[ch].outVol>=2)?31:7):0; unsigned char val=(chan[ch].outVol>=1)?((chan[ch].outVol>=2)?31:7):0;
rWrite(3+ch*4,(!isMuted[ch]&&chan[ch].active)?val:0); rWrite(3+ch*4,(chan[ch].active)?val:0);
} }
void DivPlatformZXBeeperQuadTone::muteChannel(int ch, bool mute) { void DivPlatformZXBeeperQuadTone::muteChannel(int ch, bool mute) {

View file

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

View file

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

View file

@ -20,6 +20,13 @@
#include "fileutils.h" #include "fileutils.h"
#ifdef _WIN32 #ifdef _WIN32
#include "utfutils.h" #include "utfutils.h"
#include <windows.h>
#include <shlobj.h>
#include <shlwapi.h>
#else
#include <unistd.h>
#include <errno.h>
#include <sys/stat.h>
#endif #endif
FILE* ps_fopen(const char* path, const char* mode) { FILE* ps_fopen(const char* path, const char* mode) {
@ -29,3 +36,67 @@ FILE* ps_fopen(const char* path, const char* mode) {
return fopen(path,mode); return fopen(path,mode);
#endif #endif
} }
// TODO: copy in case of failure
bool moveFiles(const char* src, const char* dest) {
#ifdef _WIN32
return MoveFileW(utf8To16(src).c_str(),utf8To16(dest).c_str());
#else
if (rename(src,dest)==-1) {
return false;
}
return true;
#endif
}
bool deleteFile(const char* path) {
#ifdef _WIN32
return DeleteFileW(utf8To16(path).c_str());
#else
return (unlink(path)==0);
#endif
}
int fileExists(const char* path) {
#ifdef _WIN32
if (PathFileExistsW(utf8To16(path).c_str())) return 1;
// which errors could PathFileExists possibly throw?
switch (GetLastError()) {
case ERROR_FILE_EXISTS:
return 1;
break;
case ERROR_FILE_NOT_FOUND:
case ERROR_PATH_NOT_FOUND:
case ERROR_INVALID_DRIVE:
case ERROR_DEV_NOT_EXIST:
case ERROR_NETNAME_DELETED:
case ERROR_BAD_NET_NAME:
return 0;
break;
}
return -1;
#else
if (access(path,F_OK)==0) return 1;
if (errno==ENOENT) return 0;
return -1;
#endif
}
bool dirExists(const char* what) {
#ifdef _WIN32
WString ws=utf8To16(what);
return (PathIsDirectoryW(ws.c_str())!=FALSE);
#else
struct stat st;
if (stat(what,&st)<0) return false;
return (st.st_mode&S_IFDIR);
#endif
}
bool makeDir(const char* path) {
#ifdef _WIN32
return (SHCreateDirectory(NULL,utf8To16(path).c_str())==ERROR_SUCCESS);
#else
return (mkdir(path,0755)==0);
#endif
}

View file

@ -22,5 +22,11 @@
#include <stdio.h> #include <stdio.h>
FILE* ps_fopen(const char* path, const char* mode); FILE* ps_fopen(const char* path, const char* mode);
bool moveFiles(const char* src, const char* dest);
bool deleteFile(const char* path);
// returns 1 if file exists, 0 if it doesn't and -1 on error.
int fileExists(const char* path);
bool dirExists(const char* what);
bool makeDir(const char* path);
#endif #endif

View file

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

View file

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

View file

@ -50,13 +50,15 @@
#include <shlwapi.h> #include <shlwapi.h>
#include "../utfutils.h" #include "../utfutils.h"
#define LAYOUT_INI "\\layout.ini" #define LAYOUT_INI "\\layout.ini"
#define BACKUP_FUR "\\backup.fur" #define BACKUPS_DIR "\\backups"
#else #else
#include <sys/types.h>
#include <dirent.h>
#include <unistd.h> #include <unistd.h>
#include <pwd.h> #include <pwd.h>
#include <sys/stat.h> #include <sys/stat.h>
#define LAYOUT_INI "/layout.ini" #define LAYOUT_INI "/layout.ini"
#define BACKUP_FUR "/backup.fur" #define BACKUPS_DIR "/backups"
#endif #endif
#ifdef IS_MOBILE #ifdef IS_MOBILE
@ -528,18 +530,22 @@ void FurnaceGUI::setFileName(String name) {
if (index>=4095) break; if (index>=4095) break;
} }
ret[index]=0; ret[index]=0;
backupLock.lock();
if (GetFullPathNameW(ws.c_str(),4095,ret,NULL)==0) { if (GetFullPathNameW(ws.c_str(),4095,ret,NULL)==0) {
curFileName=name; curFileName=name;
} else { } else {
curFileName=utf16To8(ret); curFileName=utf16To8(ret);
} }
backupLock.unlock();
#else #else
char ret[4096]; char ret[4096];
backupLock.lock();
if (realpath(name.c_str(),ret)==NULL) { if (realpath(name.c_str(),ret)==NULL) {
curFileName=name; curFileName=name;
} else { } else {
curFileName=ret; curFileName=ret;
} }
backupLock.unlock();
#endif #endif
updateWindowTitle(); updateWindowTitle();
pushRecentFile(curFileName); pushRecentFile(curFileName);
@ -1076,10 +1082,6 @@ void FurnaceGUI::stop() {
e->stop(); e->stop();
curNibble=false; curNibble=false;
orderNibble=false; orderNibble=false;
activeNotes.clear();
memset(chanOscVol,0,DIV_MAX_CHANS*sizeof(float));
memset(chanOscPitch,0,DIV_MAX_CHANS*sizeof(float));
memset(chanOscBright,0,DIV_MAX_CHANS*sizeof(float));
} }
void FurnaceGUI::previewNote(int refChan, int note, bool autoNote) { void FurnaceGUI::previewNote(int refChan, int note, bool autoNote) {
@ -1458,15 +1460,8 @@ void FurnaceGUI::keyUp(SDL_Event& ev) {
// nothing for now // nothing for now
} }
bool dirExists(String what) { bool dirExists(String s) {
#ifdef _WIN32 return dirExists(s.c_str());
WString ws=utf8To16(what.c_str());
return (PathIsDirectoryW(ws.c_str())!=FALSE);
#else
struct stat st;
if (stat(what.c_str(),&st)<0) return false;
return (st.st_mode&S_IFDIR);
#endif
} }
void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
@ -1483,6 +1478,19 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
dpiScale dpiScale
); );
break; break;
case GUI_FILE_OPEN_BACKUP:
if (!dirExists(backupPath)) {
showError("no backups made yet!");
break;
}
hasOpened=fileDialog->openLoad(
"Restore Backup",
{"Furnace song", "*.fur"},
"Furnace song{.fur}",
backupPath+String(DIR_SEPARATOR_STR),
dpiScale
);
break;
case GUI_FILE_SAVE: case GUI_FILE_SAVE:
if (!dirExists(workingDirSong)) workingDirSong=getHomeDir(); if (!dirExists(workingDirSong)) workingDirSong=getHomeDir();
hasOpened=fileDialog->openSave( hasOpened=fileDialog->openSave(
@ -2009,7 +2017,9 @@ int FurnaceGUI::save(String path, int dmfVersion) {
#endif #endif
fclose(outFile); fclose(outFile);
w->finish(); w->finish();
backupLock.lock();
curFileName=path; curFileName=path;
backupLock.unlock();
modified=false; modified=false;
updateWindowTitle(); updateWindowTitle();
if (!e->getWarnings().empty()) { if (!e->getWarnings().empty()) {
@ -2074,7 +2084,9 @@ int FurnaceGUI::load(String path) {
return 1; return 1;
} }
} }
backupLock.lock();
curFileName=path; curFileName=path;
backupLock.unlock();
modified=false; modified=false;
curNibble=false; curNibble=false;
orderNibble=false; orderNibble=false;
@ -2100,7 +2112,7 @@ int FurnaceGUI::load(String path) {
void FurnaceGUI::pushRecentFile(String path) { void FurnaceGUI::pushRecentFile(String path) {
if (path.empty()) return; if (path.empty()) return;
if (path==backupPath) return; if (path.find(backupPath)==0) return;
for (int i=0; i<(int)recentFile.size(); i++) { for (int i=0; i<(int)recentFile.size(); i++) {
if (recentFile[i]==path) { if (recentFile[i]==path) {
recentFile.erase(recentFile.begin()+i); recentFile.erase(recentFile.begin()+i);
@ -2114,6 +2126,44 @@ void FurnaceGUI::pushRecentFile(String path) {
} }
} }
void FurnaceGUI::delFirstBackup(String name) {
std::vector<String> listOfFiles;
#ifdef _WIN32
String findPath=backupPath+String(DIR_SEPARATOR_STR)+name+String("*.fur");
WIN32_FIND_DATAW next;
HANDLE backDir=FindFirstFileW(utf8To16(findPath.c_str()).c_str(),&next);
if (backDir!=INVALID_HANDLE_VALUE) {
do {
listOfFiles.push_back(utf16To8(next.cFileName));
} while (FindNextFileW(backDir,&next)!=0);
FindClose(backDir);
}
#else
DIR* backDir=opendir(backupPath.c_str());
if (backDir==NULL) {
logW("could not open backups dir!");
return;
}
while (true) {
struct dirent* next=readdir(backDir);
if (next==NULL) break;
if (strstr(next->d_name,name.c_str())!=next->d_name) continue;
listOfFiles.push_back(String(next->d_name));
}
closedir(backDir);
#endif
std::sort(listOfFiles.begin(),listOfFiles.end(),[](const String& a, const String& b) -> bool {
return strcmp(a.c_str(),b.c_str())<0;
});
int totalDelete=((int)listOfFiles.size())-5;
for (int i=0; i<totalDelete; i++) {
String toDelete=backupPath+String(DIR_SEPARATOR_STR)+listOfFiles[i];
deleteFile(toDelete.c_str());
}
}
int FurnaceGUI::loadStream(String path) { int FurnaceGUI::loadStream(String path) {
if (!path.empty()) { if (!path.empty()) {
logI("loading stream..."); logI("loading stream...");
@ -2786,7 +2836,9 @@ void FurnaceGUI::editOptions(bool topMenu) {
void FurnaceGUI::toggleMobileUI(bool enable, bool force) { void FurnaceGUI::toggleMobileUI(bool enable, bool force) {
if (mobileUI!=enable || force) { if (mobileUI!=enable || force) {
if (!mobileUI && enable) { if (!mobileUI && enable) {
ImGui::SaveIniSettingsToDisk(finalLayoutPath); if (!ImGui::SaveIniSettingsToDisk(finalLayoutPath,true)) {
reportError(fmt::sprintf("could NOT save layout! %s",strerror(errno)));
}
} }
mobileUI=enable; mobileUI=enable;
if (mobileUI) { if (mobileUI) {
@ -2796,7 +2848,10 @@ void FurnaceGUI::toggleMobileUI(bool enable, bool force) {
fileDialog->mobileUI=true; fileDialog->mobileUI=true;
} else { } else {
ImGui::GetIO().IniFilename=NULL; ImGui::GetIO().IniFilename=NULL;
ImGui::LoadIniSettingsFromDisk(finalLayoutPath); if (!ImGui::LoadIniSettingsFromDisk(finalLayoutPath,true)) {
reportError(fmt::sprintf("could NOT load layout! %s",strerror(errno)));
ImGui::LoadIniSettingsFromMemory(defaultLayout);
}
ImGui::GetIO().ConfigFlags&=~ImGuiConfigFlags_InertialScrollEnable; ImGui::GetIO().ConfigFlags&=~ImGuiConfigFlags_InertialScrollEnable;
ImGui::GetIO().ConfigFlags&=~ImGuiConfigFlags_NoHoverColors; ImGui::GetIO().ConfigFlags&=~ImGuiConfigFlags_NoHoverColors;
fileDialog->mobileUI=false; fileDialog->mobileUI=false;
@ -3568,6 +3623,23 @@ bool FurnaceGUI::loop() {
} }
} }
if (!e->isRunning()) {
activeNotes.clear();
memset(chanOscVol,0,DIV_MAX_CHANS*sizeof(float));
memset(chanOscPitch,0,DIV_MAX_CHANS*sizeof(float));
memset(chanOscBright,0,DIV_MAX_CHANS*sizeof(float));
e->synchronized([this]() {
for (int i=0; i<e->getTotalChannelCount(); i++) {
DivDispatchOscBuffer* buf=e->getOscBuffer(i);
if (buf!=NULL) {
buf->needle=0;
buf->readNeedle=0;
}
}
});
}
layoutTimeBegin=SDL_GetPerformanceCounter(); layoutTimeBegin=SDL_GetPerformanceCounter();
ImGui_ImplSDLRenderer_NewFrame(); ImGui_ImplSDLRenderer_NewFrame();
@ -3623,7 +3695,7 @@ bool FurnaceGUI::loop() {
} }
ImGui::Separator(); ImGui::Separator();
if (ImGui::MenuItem("save",BIND_FOR(GUI_ACTION_SAVE))) { if (ImGui::MenuItem("save",BIND_FOR(GUI_ACTION_SAVE))) {
if (curFileName=="" || curFileName==backupPath || e->song.version>=0xff00) { if (curFileName=="" || (curFileName.find(backupPath)==0) || e->song.version>=0xff00) {
openFileDialog(GUI_FILE_SAVE); openFileDialog(GUI_FILE_SAVE);
} else { } else {
if (save(curFileName,e->song.isDMF?e->song.version:0)>0) { if (save(curFileName,e->song.isDMF?e->song.version:0)>0) {
@ -4318,6 +4390,8 @@ bool FurnaceGUI::loop() {
case GUI_FILE_TEST_SAVE: case GUI_FILE_TEST_SAVE:
workingDirTest=fileDialog->getPath()+DIR_SEPARATOR_STR; workingDirTest=fileDialog->getPath()+DIR_SEPARATOR_STR;
break; break;
case GUI_FILE_OPEN_BACKUP:
break;
} }
if (fileDialog->isError()) { if (fileDialog->isError()) {
#if defined(_WIN32) || defined(__APPLE__) #if defined(_WIN32) || defined(__APPLE__)
@ -4394,6 +4468,7 @@ bool FurnaceGUI::loop() {
String copyOfName=fileName; String copyOfName=fileName;
switch (curFileDialog) { switch (curFileDialog) {
case GUI_FILE_OPEN: case GUI_FILE_OPEN:
case GUI_FILE_OPEN_BACKUP:
if (load(copyOfName)>0) { if (load(copyOfName)>0) {
showError(fmt::sprintf("Error while loading file! (%s)",lastError)); showError(fmt::sprintf("Error while loading file! (%s)",lastError));
} }
@ -4422,9 +4497,7 @@ bool FurnaceGUI::loop() {
nextFile=""; nextFile="";
break; break;
case GUI_WARN_OPEN_BACKUP: case GUI_WARN_OPEN_BACKUP:
if (load(backupPath)>0) { openFileDialog(GUI_FILE_OPEN_BACKUP);
showError("No backup available! (or unable to open it)");
}
break; break;
default: default:
break; break;
@ -4893,7 +4966,7 @@ bool FurnaceGUI::loop() {
case GUI_WARN_QUIT: case GUI_WARN_QUIT:
if (ImGui::Button("Yes")) { if (ImGui::Button("Yes")) {
ImGui::CloseCurrentPopup(); ImGui::CloseCurrentPopup();
if (curFileName=="" || curFileName==backupPath || e->song.version>=0xff00) { if (curFileName=="" || curFileName.find(backupPath)==0 || e->song.version>=0xff00) {
openFileDialog(GUI_FILE_SAVE); openFileDialog(GUI_FILE_SAVE);
postWarnAction=GUI_WARN_QUIT; postWarnAction=GUI_WARN_QUIT;
} else { } else {
@ -4917,7 +4990,7 @@ bool FurnaceGUI::loop() {
case GUI_WARN_NEW: case GUI_WARN_NEW:
if (ImGui::Button("Yes")) { if (ImGui::Button("Yes")) {
ImGui::CloseCurrentPopup(); ImGui::CloseCurrentPopup();
if (curFileName=="" || curFileName==backupPath || e->song.version>=0xff00) { if (curFileName=="" || curFileName.find(backupPath)==0 || e->song.version>=0xff00) {
openFileDialog(GUI_FILE_SAVE); openFileDialog(GUI_FILE_SAVE);
postWarnAction=GUI_WARN_NEW; postWarnAction=GUI_WARN_NEW;
} else { } else {
@ -4941,7 +5014,7 @@ bool FurnaceGUI::loop() {
case GUI_WARN_OPEN: case GUI_WARN_OPEN:
if (ImGui::Button("Yes")) { if (ImGui::Button("Yes")) {
ImGui::CloseCurrentPopup(); ImGui::CloseCurrentPopup();
if (curFileName=="" || curFileName==backupPath || e->song.version>=0xff00) { if (curFileName=="" || curFileName.find(backupPath)==0 || e->song.version>=0xff00) {
openFileDialog(GUI_FILE_SAVE); openFileDialog(GUI_FILE_SAVE);
postWarnAction=GUI_WARN_OPEN; postWarnAction=GUI_WARN_OPEN;
} else { } else {
@ -4965,25 +5038,21 @@ bool FurnaceGUI::loop() {
case GUI_WARN_OPEN_BACKUP: case GUI_WARN_OPEN_BACKUP:
if (ImGui::Button("Yes")) { if (ImGui::Button("Yes")) {
ImGui::CloseCurrentPopup(); ImGui::CloseCurrentPopup();
if (curFileName=="" || curFileName==backupPath || e->song.version>=0xff00) { if (curFileName=="" || curFileName.find(backupPath)==0 || e->song.version>=0xff00) {
openFileDialog(GUI_FILE_SAVE); openFileDialog(GUI_FILE_SAVE);
postWarnAction=GUI_WARN_OPEN_BACKUP; postWarnAction=GUI_WARN_OPEN_BACKUP;
} else { } else {
if (save(curFileName,e->song.isDMF?e->song.version:0)>0) { if (save(curFileName,e->song.isDMF?e->song.version:0)>0) {
showError(fmt::sprintf("Error while saving file! (%s)",lastError)); showError(fmt::sprintf("Error while saving file! (%s)",lastError));
} else { } else {
if (load(backupPath)>0) { openFileDialog(GUI_FILE_OPEN_BACKUP);
showError("No backup available! (or unable to open it)");
}
} }
} }
} }
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::Button("No")) { if (ImGui::Button("No")) {
ImGui::CloseCurrentPopup(); ImGui::CloseCurrentPopup();
if (load(backupPath)>0) { openFileDialog(GUI_FILE_OPEN_BACKUP);
showError("No backup available! (or unable to open it)");
}
} }
ImGui::SameLine(); ImGui::SameLine();
if (ImGui::Button("Cancel")) { if (ImGui::Button("Cancel")) {
@ -4993,7 +5062,7 @@ bool FurnaceGUI::loop() {
case GUI_WARN_OPEN_DROP: case GUI_WARN_OPEN_DROP:
if (ImGui::Button("Yes")) { if (ImGui::Button("Yes")) {
ImGui::CloseCurrentPopup(); ImGui::CloseCurrentPopup();
if (curFileName=="" || curFileName==backupPath || e->song.version>=0xff00) { if (curFileName=="" || curFileName.find(backupPath)==0 || e->song.version>=0xff00) {
openFileDialog(GUI_FILE_SAVE); openFileDialog(GUI_FILE_SAVE);
postWarnAction=GUI_WARN_OPEN_DROP; postWarnAction=GUI_WARN_OPEN_DROP;
} else { } else {
@ -5027,7 +5096,9 @@ bool FurnaceGUI::loop() {
ImGui::CloseCurrentPopup(); ImGui::CloseCurrentPopup();
if (!mobileUI) { if (!mobileUI) {
ImGui::LoadIniSettingsFromMemory(defaultLayout); ImGui::LoadIniSettingsFromMemory(defaultLayout);
ImGui::SaveIniSettingsToDisk(finalLayoutPath); if (!ImGui::SaveIniSettingsToDisk(finalLayoutPath,true)) {
reportError(fmt::sprintf("could NOT save layout! %s",strerror(errno)));
}
} }
} }
ImGui::SameLine(); ImGui::SameLine();
@ -5403,16 +5474,73 @@ bool FurnaceGUI::loop() {
backupTimer=(backupTimer-ImGui::GetIO().DeltaTime); backupTimer=(backupTimer-ImGui::GetIO().DeltaTime);
if (backupTimer<=0) { if (backupTimer<=0) {
backupTask=std::async(std::launch::async,[this]() -> bool { backupTask=std::async(std::launch::async,[this]() -> bool {
if (backupPath==curFileName) { backupLock.lock();
logV("backupPath: %s",backupPath);
logV("curFileName: %s",curFileName);
if (curFileName.find(backupPath)==0) {
logD("backup file open. not saving backup."); logD("backup file open. not saving backup.");
backupTimer=30.0;
backupLock.unlock();
return true; return true;
} }
if (!dirExists(backupPath.c_str())) {
if (!makeDir(backupPath.c_str())) {
logW("could not create backup directory!");
backupTimer=30.0;
backupLock.unlock();
return false;
}
}
logD("saving backup..."); logD("saving backup...");
SafeWriter* w=e->saveFur(true); SafeWriter* w=e->saveFur(true);
logV("writing file..."); logV("writing file...");
if (w!=NULL) { if (w!=NULL) {
FILE* outFile=ps_fopen(backupPath.c_str(),"wb"); size_t sepPos=curFileName.rfind(DIR_SEPARATOR);
String backupPreBaseName;
String backupBaseName;
String backupFileName;
if (sepPos==String::npos) {
backupPreBaseName=curFileName;
} else {
backupPreBaseName=curFileName.substr(sepPos+1);
}
size_t dotPos=backupPreBaseName.rfind('.');
if (dotPos!=String::npos) {
backupPreBaseName=backupPreBaseName.substr(0,dotPos);
}
for (char i: backupPreBaseName) {
if (backupBaseName.size()>=48) break;
if ((i>='0' && i<='9') || (i>='A' && i<='Z') || (i>='a' && i<='z') || i=='_' || i=='-' || i==' ') backupBaseName+=i;
}
if (backupBaseName.empty()) backupBaseName="untitled";
backupFileName=backupBaseName;
time_t curTime=time(NULL);
struct tm curTM;
#ifdef _WIN32
struct tm* tempTM=localtime(&curTime);
if (tempTM==NULL) {
backupFileName+="-unknownTime.fur";
} else {
curTM=*tempTM;
backupFileName+=fmt::sprintf("-%d%.2d%.2d-%.2d%.2d%.2d.fur",curTM.tm_year+1900,curTM.tm_mon+1,curTM.tm_mday,curTM.tm_hour,curTM.tm_min,curTM.tm_sec);
}
#else
if (localtime_r(&curTime,&curTM)==NULL) {
backupFileName+="-unknownTime.fur";
} else {
backupFileName+=fmt::sprintf("-%d%.2d%.2d-%.2d%.2d%.2d.fur",curTM.tm_year+1900,curTM.tm_mon+1,curTM.tm_mday,curTM.tm_hour,curTM.tm_min,curTM.tm_sec);
}
#endif
String finalPath=backupPath+String(DIR_SEPARATOR_STR)+backupFileName;
FILE* outFile=ps_fopen(finalPath.c_str(),"wb");
if (outFile!=NULL) { if (outFile!=NULL) {
if (fwrite(w->getFinalBuf(),1,w->size(),outFile)!=w->size()) { if (fwrite(w->getFinalBuf(),1,w->size(),outFile)!=w->size()) {
logW("did not write backup entirely: %s!",strerror(errno)); logW("did not write backup entirely: %s!",strerror(errno));
@ -5423,9 +5551,13 @@ bool FurnaceGUI::loop() {
logW("could not save backup: %s!",strerror(errno)); logW("could not save backup: %s!",strerror(errno));
w->finish(); w->finish();
} }
// delete previous backup if there are too many
delFirstBackup(backupBaseName);
} }
logD("backup saved."); logD("backup saved.");
backupTimer=30.0; backupTimer=30.0;
backupLock.unlock();
return true; return true;
}); });
} }
@ -5434,6 +5566,52 @@ bool FurnaceGUI::loop() {
curWindowThreadSafe=curWindow; curWindowThreadSafe=curWindow;
if (curWindow!=curWindowLast) {
int curWindowCat=0;
int lastWindowCat=0;
switch (curWindow) {
case GUI_WINDOW_WAVE_LIST:
case GUI_WINDOW_WAVE_EDIT:
curWindowCat=1;
break;
case GUI_WINDOW_SAMPLE_LIST:
case GUI_WINDOW_SAMPLE_EDIT:
curWindowCat=2;
break;
default:
curWindowCat=0;
break;
}
switch (curWindowLast) {
case GUI_WINDOW_WAVE_LIST:
case GUI_WINDOW_WAVE_EDIT:
lastWindowCat=1;
break;
case GUI_WINDOW_SAMPLE_LIST:
case GUI_WINDOW_SAMPLE_EDIT:
lastWindowCat=2;
break;
default:
lastWindowCat=0;
break;
}
if (curWindowCat!=lastWindowCat) {
switch (lastWindowCat) {
case 0:
e->autoNoteOffAll();
break;
case 1:
e->stopWavePreview();
break;
case 2:
e->stopSamplePreview();
break;
}
}
}
SDL_SetRenderDrawColor(sdlRend,uiColors[GUI_COLOR_BACKGROUND].x*255, SDL_SetRenderDrawColor(sdlRend,uiColors[GUI_COLOR_BACKGROUND].x*255,
uiColors[GUI_COLOR_BACKGROUND].y*255, uiColors[GUI_COLOR_BACKGROUND].y*255,
uiColors[GUI_COLOR_BACKGROUND].z*255, uiColors[GUI_COLOR_BACKGROUND].z*255,
@ -5839,7 +6017,11 @@ bool FurnaceGUI::init() {
} }
strncpy(finalLayoutPath,(e->getConfigPath()+String(LAYOUT_INI)).c_str(),4095); strncpy(finalLayoutPath,(e->getConfigPath()+String(LAYOUT_INI)).c_str(),4095);
backupPath=e->getConfigPath()+String(BACKUP_FUR); backupPath=e->getConfigPath();
if (backupPath.size()>0) {
if (backupPath[backupPath.size()-1]==DIR_SEPARATOR) backupPath.resize(backupPath.size()-1);
}
backupPath+=String(BACKUPS_DIR);
prepareLayout(); prepareLayout();
ImGui::GetIO().ConfigFlags|=ImGuiConfigFlags_DockingEnable; ImGui::GetIO().ConfigFlags|=ImGuiConfigFlags_DockingEnable;
@ -5909,7 +6091,9 @@ bool FurnaceGUI::init() {
void FurnaceGUI::commitState() { void FurnaceGUI::commitState() {
if (!mobileUI) { if (!mobileUI) {
ImGui::SaveIniSettingsToDisk(finalLayoutPath); if (!ImGui::SaveIniSettingsToDisk(finalLayoutPath,true)) {
reportError(fmt::sprintf("could NOT save layout! %s",strerror(errno)));
}
} }
e->setConf("configVersion",(int)DIV_ENGINE_VERSION); e->setConf("configVersion",(int)DIV_ENGINE_VERSION);

View file

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

View file

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

View file

@ -221,8 +221,9 @@ void FurnaceGUI::drawOsc() {
waveform[i]=ImLerp(inRect.Min,inRect.Max,ImVec2(x,0.5f-y)); waveform[i]=ImLerp(inRect.Min,inRect.Max,ImVec2(x,0.5f-y));
} }
if (settings.oscEscapesBoundary) { if (settings.oscEscapesBoundary) {
ImDrawList* dlf=ImGui::GetForegroundDrawList(); dl->PushClipRectFullScreen();
dlf->AddPolyline(waveform,512,color,ImDrawFlags_None,dpiScale); dl->AddPolyline(waveform,512,color,ImDrawFlags_None,dpiScale);
dl->PopClipRect();
} else { } else {
dl->AddPolyline(waveform,512,color,ImDrawFlags_None,dpiScale); dl->AddPolyline(waveform,512,color,ImDrawFlags_None,dpiScale);
} }

View file

@ -1109,11 +1109,23 @@ void FurnaceGUI::initSystemPresets() {
} }
); );
ENTRY( ENTRY(
"Commander X16", { "Commander X16 (VERA only)", {
CH(DIV_SYSTEM_VERA, 1.0f, 0, "")
}
);
ENTRY(
"Commander X16 (with OPM)", {
CH(DIV_SYSTEM_VERA, 1.0f, 0, ""), CH(DIV_SYSTEM_VERA, 1.0f, 0, ""),
CH(DIV_SYSTEM_YM2151, 1.0f, 0, "") CH(DIV_SYSTEM_YM2151, 1.0f, 0, "")
} }
); );
ENTRY(
"Commander X16 (with Twin OPL3)", {
CH(DIV_SYSTEM_VERA, 1.0f, 0, ""),
CH(DIV_SYSTEM_OPL3, 1.0f, 0, ""),
CH(DIV_SYSTEM_OPL3, 1.0f, 0, "")
}
);
ENTRY( ENTRY(
"TI-99/4A", { "TI-99/4A", {
CH(DIV_SYSTEM_SMS, 1.0f, 0, CH(DIV_SYSTEM_SMS, 1.0f, 0,

View file

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

View file

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

View file

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