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

This commit is contained in:
cam900 2022-09-29 22:05:41 +09:00
commit 62f1ba73eb
33 changed files with 1426 additions and 499 deletions

View file

@ -22,78 +22,417 @@
#include "brrUtils.h"
#define NEXT_SAMPLE buf[j]-(buf[j]>>3)
#define DO_ONE_DEC(r) \
if (nextDec&8) nextDec|=0xfffffff0; \
\
if (r>=13) { /* invalid shift */ \
nextDec=(nextDec<0)?0xfffff800:0; \
} else { \
nextDec<<=r; /* range */ \
nextDec>>=1; \
} \
\
switch (filter) { /* filter */ \
case 0: \
break; \
case 1: \
nextDec+=last1+((-last1)>>4); \
break; \
case 2: \
nextDec+=last1*2+((-last1*3)>>5)-last2+(last2>>4); \
break; \
case 3: \
nextDec+=last1*2+((-last1*13)>>6)-last2+((last2*3)>>4); \
break; \
} \
\
if (nextDec>32767) nextDec=32767; \
if (nextDec<-32768) nextDec=-32768; \
nextDec&=0x7fff; \
if (nextDec&0x4000) nextDec|=0xffff8000; \
\
last2=last1; \
last1=nextDec; \
long brrEncode(short* buf, unsigned char* out, long len, long loopStart) {
if (len==0) return 0;
// encoding process:
// 1. read next group of 16 samples
// 2. is this the first block?
// - if yes, don't filter. output and then go to 1
// - if yes, don't filter. output and then go to 1
// 3. is this the loop block?
// - if yes, don't filter. output and then go to 1
// - if yes, don't filter. output and then go to 1
// 4. try encoding using 4 filters
// - perform linear prediction
// - calculate range
// - decode and apply correction to achieve low error
// 5. which one of these yields the least amount of error?
// 6. output the one which does
// 7. is this the last block?
// - if yes, mark end and finish
// - if yes, mark end and finish
// 8. go to 1
long total=0;
unsigned char next[9];
unsigned char next0[8];
unsigned char next1[8];
unsigned char next2[8];
unsigned char next3[8];
unsigned char filter=0;
unsigned char range=0;
unsigned char range0=0;
unsigned char range1=0;
unsigned char range2=0;
unsigned char range3=0;
unsigned char o=0;
int pred1[16];
int pred2[16];
int pred3[16];
short o1=0;
short o2=0;
short o0=0;
short o1f1=0;
short o1f2=0;
short o1f3=0;
//short o2f1=0;
short o2f2=0;
short o2f3=0;
int last1=0;
int last2=0;
int nextDec=0;
int maxError[4];
int avgError[4];
len&=~15;
loopStart&=~15;
for (long i=0; i<len; i+=16) {
// don't filter on the first or loop block
if (i && i!=loopStart) {
range0=0;
// encode with no filter
for (int j=0; j<16; j++) {
short s=NEXT_SAMPLE;
if (s<0) s=-s;
while (range0<12 && s>((8<<range0)-1)) range0++;
}
for (int j=0; j<16; j++) {
short s=NEXT_SAMPLE;
o0=s>>range0;
if (range0) if (s&(1<<(range1>>1))) o0++;
if (o0>7) o0=7;
if (o0<-8) o0=-8;
if (range0>=12) if (o0<-7) o0=-7;
o=o0&15;
if (j&1) {
next0[j>>1]|=o;
} else {
next0[j>>1]=o<<4;
}
}
// encode with filter
if (i && i!=loopStart) {
// 1: x = o0 - o1 * 15/16
// 2: x = o0 + o2 * 15/16 - o1 * 61/32
// 3: x = o0 + o2 * 13/16 - o1 * 115/64
range1=0;
range2=0;
range3=0;
//o2f1=o2;
o2f2=o2;
o2f3=o2;
o1f1=o1;
o1f2=o1;
o1f3=o1;
// first pass
for (int j=0; j<16; j++) {
int s=NEXT_SAMPLE;
pred1[j]=s-(((int)o1*15)>>4);
if (pred1[j]<-32768) pred1[j]=-32768;
if (pred1[j]>32767) pred1[j]=32767;
pred2[j]=s+(((int)o2*15)>>4)-(((int)o1*61)>>5);
if (pred2[j]<-32768) pred2[j]=-32768;
if (pred2[j]>32767) pred2[j]=32767;
pred3[j]=s+(((int)o2*13)>>4)-(((int)o1*115)>>6);
if (pred3[j]<-32768) pred3[j]=-32768;
if (pred3[j]>32767) pred3[j]=32767;
o2=o1;
o1=s;
}
// calculate range of values
for (int j=0; j<16; j++) {
short s=pred1[j];
if (s<0) s=-s;
while (range1<12 && s>((8<<range1)-1)) range1++;
s=pred2[j];
if (s<0) s=-s;
while (range2<12 && s>((8<<range2)-1)) range2++;
s=pred3[j];
if (s<0) s=-s;
while (range3<12 && s>((8<<range3)-1)) range3++;
}
// second pass
int prevLast1=last1;
int prevLast2=last2;
filter=1;
for (int j=0; j<16; j++) {
int s=NEXT_SAMPLE;
pred1[j]=s-(((int)o1f1*15)>>4);
if (pred1[j]<-32768) pred1[j]=-32768;
if (pred1[j]>32767) pred1[j]=32767;
o0=pred1[j]>>range1;
if (range1) if (pred1[j]&(1<<(range1>>1))) o0++;
if (o0>7) o0=7;
if (o0<-8) o0=-8;
o=o0&15;
if (j&1) {
next1[j>>1]|=o;
} else {
next1[j>>1]=o<<4;
}
nextDec=o;
DO_ONE_DEC(range1);
//o2f1=last2<<1;
o1f1=last1<<1;
}
last1=prevLast1;
last2=prevLast2;
filter=2;
for (int j=0; j<16; j++) {
int s=NEXT_SAMPLE;
pred2[j]=s+(((int)o2f2*15)>>4)-(((int)o1f2*61)>>5);
if (pred2[j]<-32768) pred2[j]=-32768;
if (pred2[j]>32767) pred2[j]=32767;
o0=pred2[j]>>range2;
if (range2) if (pred2[j]&(1<<(range2>>1))) o0++;
if (o0>7) o0=7;
if (o0<-8) o0=-8;
o=o0&15;
if (j&1) {
next2[j>>1]|=o;
} else {
next2[j>>1]=o<<4;
}
nextDec=o;
DO_ONE_DEC(range2);
o2f2=last2<<1;
o1f2=last1<<1;
}
last1=prevLast1;
last2=prevLast2;
filter=3;
for (int j=0; j<16; j++) {
int s=NEXT_SAMPLE;
pred3[j]=s+(((int)o2f3*13)>>4)-(((int)o1f3*115)>>6);
if (pred3[j]<-32768) pred3[j]=-32768;
if (pred3[j]>32767) pred3[j]=32767;
o0=pred3[j]>>range3;
if (range3) if (pred3[j]&(1<<(range3>>1))) o0++;
if (o0>7) o0=7;
if (o0<-8) o0=-8;
o=o0&15;
if (j&1) {
next3[j>>1]|=o;
} else {
next3[j>>1]=o<<4;
}
nextDec=o;
DO_ONE_DEC(range3);
o2f3=last2<<1;
o1f3=last1<<1;
}
last1=prevLast1;
last2=prevLast2;
// find best filter
int error=0;
maxError[0]=0;
maxError[1]=0;
maxError[2]=0;
maxError[3]=0;
avgError[0]=0;
avgError[1]=0;
avgError[2]=0;
avgError[3]=0;
// test filter 0
filter=0;
for (int j=0; j<16; j++) {
int s=NEXT_SAMPLE;
if (j&1) {
nextDec=next0[j>>1]&15;
} else {
nextDec=next0[j>>1]>>4;
}
DO_ONE_DEC(range0);
error=s-(nextDec<<1);
if (error<0) error=-error;
avgError[0]+=error;
if (error>maxError[0]) maxError[0]=error;
}
last1=prevLast1;
last2=prevLast2;
// test filter 1
filter=1;
for (int j=0; j<16; j++) {
int s=NEXT_SAMPLE;
if (j&1) {
nextDec=next1[j>>1]&15;
} else {
nextDec=next1[j>>1]>>4;
}
DO_ONE_DEC(range1);
error=s-(nextDec<<1);
if (error<0) error=-error;
avgError[1]+=error;
if (error>maxError[1]) maxError[1]=error;
}
last1=prevLast1;
last2=prevLast2;
// test filter 2
filter=2;
for (int j=0; j<16; j++) {
int s=NEXT_SAMPLE;
if (j&1) {
nextDec=next2[j>>1]&15;
} else {
nextDec=next2[j>>1]>>4;
}
DO_ONE_DEC(range2);
error=s-(nextDec<<1);
if (error<0) error=-error;
avgError[2]+=error;
if (error>maxError[2]) maxError[2]=error;
}
last1=prevLast1;
last2=prevLast2;
// test filter 3
filter=3;
for (int j=0; j<16; j++) {
int s=NEXT_SAMPLE;
if (j&1) {
nextDec=next3[j>>1]&15;
} else {
nextDec=next3[j>>1]>>4;
}
DO_ONE_DEC(range3);
error=s-(nextDec<<1);
if (error<0) error=-error;
avgError[3]+=error;
if (error>maxError[3]) maxError[3]=error;
}
last1=prevLast1;
last2=prevLast2;
// pick best filter
int candError=0x7fffffff;
for (int j=0; j<4; j++) {
if (avgError[j]<candError) {
candError=avgError[j];
filter=j;
}
}
//printf("block %ld: %8d %8d %8d %8d -> %d\n",i>>4,avgError[0],avgError[1],avgError[2],avgError[3],filter);
} else {
// don't filter on the first or loop block
filter=0;
}
range=0;
for (int j=0; j<16; j++) {
short s=buf[j]-(buf[j]>>13);
if (s<0) s=-s;
while (range<12 && s>((8<<range)-1)) range++;
}
next[0]=(range<<4)|(filter<<2)|((i+16>=len)?((loopStart>=0)?3:1):0);
switch (filter) {
case 0:
for (int j=0; j<16; j++) {
short s=buf[j]-(buf[j]>>13);
o0=s>>range;
if (o0>7) o0=7;
if (o0<-8) o0=-8;
if (range>=12) if (o0<-7) o0=-7;
o=o0&15;
if (j&1) {
next[1+(j>>1)]|=o;
} else {
next[1+(j>>1)]=o<<4;
}
for (int j=0; j<8; j++) {
nextDec=next0[j]>>4;
DO_ONE_DEC(range0);
nextDec=next0[j]&15;
DO_ONE_DEC(range0);
}
o2=last2<<1;
o1=last1<<1;
out[0]=(range0<<4)|(filter<<2)|((i+16>=len)?((loopStart>=0)?3:1):0);
out[1]=next0[0];
out[2]=next0[1];
out[3]=next0[2];
out[4]=next0[3];
out[5]=next0[4];
out[6]=next0[5];
out[7]=next0[6];
out[8]=next0[7];
break;
case 1:
for (int j=0; j<8; j++) {
nextDec=next1[j]>>4;
DO_ONE_DEC(range1);
nextDec=next1[j]&15;
DO_ONE_DEC(range1);
}
o2=last2<<1;
o1=last1<<1;
out[0]=(range1<<4)|(filter<<2)|((i+16>=len)?((loopStart>=0)?3:1):0);
out[1]=next1[0];
out[2]=next1[1];
out[3]=next1[2];
out[4]=next1[3];
out[5]=next1[4];
out[6]=next1[5];
out[7]=next1[6];
out[8]=next1[7];
break;
case 2:
for (int j=0; j<8; j++) {
nextDec=next2[j]>>4;
DO_ONE_DEC(range2);
nextDec=next2[j]&15;
DO_ONE_DEC(range2);
}
o2=last2<<1;
o1=last1<<1;
out[0]=(range2<<4)|(filter<<2)|((i+16>=len)?((loopStart>=0)?3:1):0);
out[1]=next2[0];
out[2]=next2[1];
out[3]=next2[2];
out[4]=next2[3];
out[5]=next2[4];
out[6]=next2[5];
out[7]=next2[6];
out[8]=next2[7];
break;
case 3:
for (int j=0; j<8; j++) {
nextDec=next3[j]>>4;
DO_ONE_DEC(range3);
nextDec=next3[j]&15;
DO_ONE_DEC(range3);
}
o2=last2<<1;
o1=last1<<1;
out[0]=(range3<<4)|(filter<<2)|((i+16>=len)?((loopStart>=0)?3:1):0);
out[1]=next3[0];
out[2]=next3[1];
out[3]=next3[2];
out[4]=next3[3];
out[5]=next3[4];
out[6]=next3[5];
out[7]=next3[6];
out[8]=next3[7];
break;
}
out[0]=next[0];
out[1]=next[1];
out[2]=next[2];
out[3]=next[3];
out[4]=next[4];
out[5]=next[5];
out[6]=next[6];
out[7]=next[7];
out[8]=next[8];
buf+=16;
out+=9;
total+=9;
@ -135,9 +474,6 @@ long brrEncode(short* buf, unsigned char* out, long len, long loopStart) {
*out=next<<1; \
out++;
// TODO:
// - what happens during overflow?
// - what happens when range values 12 to 15 are used?
long brrDecode(unsigned char* buf, short* out, long len) {
if (len==0) return 0;

View file

@ -17,101 +17,13 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "engine.h"
#include "config.h"
#include "../ta-log.h"
#include "../fileutils.h"
#include <fmt/printf.h>
#ifdef _WIN32
#include "winStuff.h"
#define CONFIG_FILE "\\furnace.cfg"
#else
#ifdef __HAIKU__
#include <support/SupportDefs.h>
#include <storage/FindDirectory.h>
#endif
#include <unistd.h>
#include <pwd.h>
#include <sys/stat.h>
#define CONFIG_FILE "/furnace.cfg"
#endif
#ifdef IS_MOBILE
#ifdef HAVE_SDL2
#include <SDL.h>
#else
#error "Furnace mobile requires SDL2!"
#endif
#endif
void DivEngine::initConfDir() {
#ifdef _WIN32
// maybe move this function in here instead?
configPath=getWinConfigPath();
#elif defined(IS_MOBILE)
configPath=SDL_GetPrefPath("tildearrow","furnace");
#else
#ifdef __HAIKU__
char userSettingsDir[PATH_MAX];
status_t findUserDir = find_directory(B_USER_SETTINGS_DIRECTORY,0,true,userSettingsDir,PATH_MAX);
if (findUserDir==B_OK) {
configPath=userSettingsDir;
} else {
logW("unable to find/create user settings directory (%s)!",strerror(findUserDir));
configPath=".";
return;
}
#else
// TODO this should check XDG_CONFIG_HOME first
char* home=getenv("HOME");
if (home==NULL) {
int uid=getuid();
struct passwd* entry=getpwuid(uid);
if (entry==NULL) {
logW("unable to determine home directory (%s)!",strerror(errno));
configPath=".";
return;
} else {
configPath=entry->pw_dir;
}
} else {
configPath=home;
}
#ifdef __APPLE__
configPath+="/Library/Application Support";
#else
// FIXME this doesn't honour XDG_CONFIG_HOME *at all*
configPath+="/.config";
#endif // __APPLE__
#endif // __HAIKU__
#ifdef __APPLE__
configPath+="/Furnace";
#else
configPath+="/furnace";
#endif // __APPLE__
struct stat st;
std::string pathSep="/";
configPath+=pathSep;
size_t sepPos=configPath.find(pathSep,1);
while (sepPos!=std::string::npos) {
std::string subpath=configPath.substr(0,sepPos++);
if (stat(subpath.c_str(),&st)!=0) {
logI("creating config path element %s ...",subpath.c_str());
if (mkdir(subpath.c_str(),0755)!=0) {
logW("could not create config path element %s! (%s)",subpath.c_str(),strerror(errno));
configPath=".";
return;
}
}
sepPos=configPath.find(pathSep,sepPos);
}
configPath.resize(configPath.length()-pathSep.length());
#endif // _WIN32
}
bool DivEngine::saveConf() {
configFile=configPath+String(CONFIG_FILE);
FILE* f=ps_fopen(configFile.c_str(),"wb");
bool DivConfig::save(const char* path) {
FILE* f=ps_fopen(path,"wb");
if (f==NULL) {
logW("could not write config file! %s",strerror(errno));
return false;
@ -128,43 +40,72 @@ bool DivEngine::saveConf() {
return true;
}
bool DivEngine::loadConf() {
String DivConfig::toString() {
String ret;
for (auto& i: conf) {
ret+=fmt::sprintf("%s=%s\n",i.first,i.second);
}
return ret;
}
void DivConfig::parseLine(const char* line) {
String key="";
String value="";
bool keyOrValue=false;
for (const char* i=line; *i; i++) {
if (*i=='\n') continue;
if (keyOrValue) {
value+=*i;
} else {
if (*i=='=') {
keyOrValue=true;
} else {
key+=*i;
}
}
}
if (keyOrValue) {
conf[key]=value;
}
}
bool DivConfig::loadFromFile(const char* path, bool createOnFail) {
char line[4096];
configFile=configPath+String(CONFIG_FILE);
FILE* f=ps_fopen(configFile.c_str(),"rb");
FILE* f=ps_fopen(path,"rb");
if (f==NULL) {
logI("creating default config.");
return saveConf();
if (createOnFail) {
logI("creating default config.");
return save(path);
} else {
return false;
}
}
logI("loading config.");
while (!feof(f)) {
String key="";
String value="";
bool keyOrValue=false;
if (fgets(line,4095,f)==NULL) {
break;
}
for (char* i=line; *i; i++) {
if (*i=='\n') continue;
if (keyOrValue) {
value+=*i;
} else {
if (*i=='=') {
keyOrValue=true;
} else {
key+=*i;
}
}
}
if (keyOrValue) {
conf[key]=value;
}
parseLine(line);
}
fclose(f);
return true;
}
bool DivEngine::getConfBool(String key, bool fallback) {
bool DivConfig::loadFromMemory(const char* buf) {
String line;
const char* readPos=buf;
while (*readPos) {
line+=*readPos;
readPos++;
if ((*readPos)=='\n' || (*readPos)==0) {
parseLine(line.c_str());
line="";
}
}
return true;
}
bool DivConfig::getConfBool(String key, bool fallback) {
try {
String val=conf.at(key);
if (val=="true") {
@ -177,7 +118,7 @@ bool DivEngine::getConfBool(String key, bool fallback) {
return fallback;
}
int DivEngine::getConfInt(String key, int fallback) {
int DivConfig::getConfInt(String key, int fallback) {
try {
String val=conf.at(key);
int ret=std::stoi(val);
@ -188,7 +129,7 @@ int DivEngine::getConfInt(String key, int fallback) {
return fallback;
}
float DivEngine::getConfFloat(String key, float fallback) {
float DivConfig::getConfFloat(String key, float fallback) {
try {
String val=conf.at(key);
float ret=std::stof(val);
@ -199,7 +140,7 @@ float DivEngine::getConfFloat(String key, float fallback) {
return fallback;
}
double DivEngine::getConfDouble(String key, double fallback) {
double DivConfig::getConfDouble(String key, double fallback) {
try {
String val=conf.at(key);
double ret=std::stod(val);
@ -210,7 +151,7 @@ double DivEngine::getConfDouble(String key, double fallback) {
return fallback;
}
String DivEngine::getConfString(String key, String fallback) {
String DivConfig::getConfString(String key, String fallback) {
try {
String val=conf.at(key);
return val;
@ -219,7 +160,7 @@ String DivEngine::getConfString(String key, String fallback) {
return fallback;
}
void DivEngine::setConf(String key, bool value) {
void DivConfig::setConf(String key, bool value) {
if (value) {
conf[key]="true";
} else {
@ -227,22 +168,22 @@ void DivEngine::setConf(String key, bool value) {
}
}
void DivEngine::setConf(String key, int value) {
void DivConfig::setConf(String key, int value) {
conf[key]=fmt::sprintf("%d",value);
}
void DivEngine::setConf(String key, float value) {
void DivConfig::setConf(String key, float value) {
conf[key]=fmt::sprintf("%f",value);
}
void DivEngine::setConf(String key, double value) {
void DivConfig::setConf(String key, double value) {
conf[key]=fmt::sprintf("%f",value);
}
void DivEngine::setConf(String key, const char* value) {
void DivConfig::setConf(String key, const char* value) {
conf[key]=String(value);
}
void DivEngine::setConf(String key, String value) {
void DivConfig::setConf(String key, String value) {
conf[key]=value;
}

52
src/engine/config.h Normal file
View file

@ -0,0 +1,52 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef _DIVCONFIG_H
#define _DIVCONFIG_H
#include "../ta-utils.h"
#include <map>
class DivConfig {
std::map<String,String> conf;
void parseLine(const char* line);
public:
// config loading/saving
bool loadFromMemory(const char* buf);
bool loadFromFile(const char* path, bool createOnFail=true);
String toString();
bool save(const char* path);
// get a config value
bool getConfBool(String key, bool fallback);
int getConfInt(String key, int fallback);
float getConfFloat(String key, float fallback);
double getConfDouble(String key, double fallback);
String getConfString(String key, String fallback);
// set a config value
void setConf(String key, bool value);
void setConf(String key, int value);
void setConf(String key, float value);
void setConf(String key, double value);
void setConf(String key, const char* value);
void setConf(String key, String value);
};
#endif

162
src/engine/configEngine.cpp Normal file
View file

@ -0,0 +1,162 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "engine.h"
#include "../ta-log.h"
#ifdef _WIN32
#include "winStuff.h"
#define CONFIG_FILE "\\furnace.cfg"
#else
#ifdef __HAIKU__
#include <support/SupportDefs.h>
#include <storage/FindDirectory.h>
#endif
#include <unistd.h>
#include <pwd.h>
#include <sys/stat.h>
#define CONFIG_FILE "/furnace.cfg"
#endif
#ifdef IS_MOBILE
#ifdef HAVE_SDL2
#include <SDL.h>
#else
#error "Furnace mobile requires SDL2!"
#endif
#endif
void DivEngine::initConfDir() {
#ifdef _WIN32
// maybe move this function in here instead?
configPath=getWinConfigPath();
#elif defined(IS_MOBILE)
configPath=SDL_GetPrefPath("tildearrow","furnace");
#else
#ifdef __HAIKU__
char userSettingsDir[PATH_MAX];
status_t findUserDir = find_directory(B_USER_SETTINGS_DIRECTORY,0,true,userSettingsDir,PATH_MAX);
if (findUserDir==B_OK) {
configPath=userSettingsDir;
} else {
logW("unable to find/create user settings directory (%s)!",strerror(findUserDir));
configPath=".";
return;
}
#else
// TODO this should check XDG_CONFIG_HOME first
char* home=getenv("HOME");
if (home==NULL) {
int uid=getuid();
struct passwd* entry=getpwuid(uid);
if (entry==NULL) {
logW("unable to determine home directory (%s)!",strerror(errno));
configPath=".";
return;
} else {
configPath=entry->pw_dir;
}
} else {
configPath=home;
}
#ifdef __APPLE__
configPath+="/Library/Application Support";
#else
// FIXME this doesn't honour XDG_CONFIG_HOME *at all*
configPath+="/.config";
#endif // __APPLE__
#endif // __HAIKU__
#ifdef __APPLE__
configPath+="/Furnace";
#else
configPath+="/furnace";
#endif // __APPLE__
struct stat st;
std::string pathSep="/";
configPath+=pathSep;
size_t sepPos=configPath.find(pathSep,1);
while (sepPos!=std::string::npos) {
std::string subpath=configPath.substr(0,sepPos++);
if (stat(subpath.c_str(),&st)!=0) {
logI("creating config path element %s ...",subpath.c_str());
if (mkdir(subpath.c_str(),0755)!=0) {
logW("could not create config path element %s! (%s)",subpath.c_str(),strerror(errno));
configPath=".";
return;
}
}
sepPos=configPath.find(pathSep,sepPos);
}
configPath.resize(configPath.length()-pathSep.length());
#endif // _WIN32
}
bool DivEngine::saveConf() {
configFile=configPath+String(CONFIG_FILE);
return conf.save(configFile.c_str());
}
bool DivEngine::loadConf() {
configFile=configPath+String(CONFIG_FILE);
return conf.loadFromFile(configFile.c_str());
}
bool DivEngine::getConfBool(String key, bool fallback) {
return conf.getConfBool(key,fallback);
}
int DivEngine::getConfInt(String key, int fallback) {
return conf.getConfInt(key,fallback);
}
float DivEngine::getConfFloat(String key, float fallback) {
return conf.getConfFloat(key,fallback);
}
double DivEngine::getConfDouble(String key, double fallback) {
return conf.getConfDouble(key,fallback);
}
String DivEngine::getConfString(String key, String fallback) {
return conf.getConfString(key,fallback);
}
void DivEngine::setConf(String key, bool value) {
conf.setConf(key,value);
}
void DivEngine::setConf(String key, int value) {
conf.setConf(key,value);
}
void DivEngine::setConf(String key, float value) {
conf.setConf(key,value);
}
void DivEngine::setConf(String key, double value) {
conf.setConf(key,value);
}
void DivEngine::setConf(String key, const char* value) {
conf.setConf(key,value);
}
void DivEngine::setConf(String key, String value) {
conf.setConf(key,value);
}

View file

@ -401,6 +401,7 @@ void writePackedCommandValues(SafeWriter* w, const DivCommand& c) {
SafeWriter* DivEngine::saveCommand(bool binary) {
stop();
repeatPattern=false;
shallStop=false;
setOrder(0);
BUSY_BEGIN_SOFT;
// determine loop point
@ -1121,7 +1122,7 @@ void DivEngine::initSongWithDesc(const int* description) {
song.system[index]=(DivSystem)description[i];
song.systemVol[index]=description[i+1];
song.systemPan[index]=description[i+2];
song.systemFlags[index]=description[i+3];
song.systemFlagsOld[index]=description[i+3];
index++;
chanCount+=getChannelCount(song.system[index]);
if (chanCount>=DIV_MAX_CHANS) break;
@ -1344,7 +1345,7 @@ void DivEngine::changeSystem(int index, DivSystem which, bool preserveOrder) {
}
song.system[index]=which;
song.systemFlags[index]=0;
song.systemFlagsOld[index]=0;
recalcChans();
saveLock.unlock();
BUSY_END;
@ -1370,7 +1371,7 @@ bool DivEngine::addSystem(DivSystem which) {
song.system[song.systemLen]=which;
song.systemVol[song.systemLen]=64;
song.systemPan[song.systemLen]=0;
song.systemFlags[song.systemLen++]=0;
song.systemFlagsOld[song.systemLen++]=0;
recalcChans();
saveLock.unlock();
BUSY_END;
@ -1414,7 +1415,7 @@ bool DivEngine::removeSystem(int index, bool preserveOrder) {
song.system[i]=song.system[i+1];
song.systemVol[i]=song.systemVol[i+1];
song.systemPan[i]=song.systemPan[i+1];
song.systemFlags[i]=song.systemFlags[i+1];
song.systemFlagsOld[i]=song.systemFlagsOld[i+1];
}
recalcChans();
saveLock.unlock();
@ -1540,9 +1541,9 @@ bool DivEngine::swapSystem(int src, int dest, bool preserveOrder) {
song.systemPan[dest]^=song.systemPan[src];
song.systemPan[src]^=song.systemPan[dest];
song.systemFlags[src]^=song.systemFlags[dest];
song.systemFlags[dest]^=song.systemFlags[src];
song.systemFlags[src]^=song.systemFlags[dest];
song.systemFlagsOld[src]^=song.systemFlagsOld[dest];
song.systemFlagsOld[dest]^=song.systemFlagsOld[src];
song.systemFlagsOld[src]^=song.systemFlagsOld[dest];
recalcChans();
saveLock.unlock();
@ -1873,6 +1874,7 @@ void DivEngine::play() {
sPreview.wave=-1;
sPreview.pos=0;
sPreview.dir=false;
shallStop=false;
if (stepPlay==0) {
freelance=false;
playSub(false);
@ -2031,6 +2033,7 @@ void DivEngine::reset() {
speed1=curSubSong->speed1;
speed2=curSubSong->speed2;
firstTick=false;
shallStop=false;
nextSpeed=speed1;
divider=60;
if (curSubSong->customTempo) {
@ -3469,9 +3472,9 @@ void DivEngine::setOrder(unsigned char order) {
void DivEngine::setSysFlags(int system, unsigned int flags, bool restart) {
BUSY_BEGIN_SOFT;
saveLock.lock();
song.systemFlags[system]=flags;
song.systemFlagsOld[system]=flags;
saveLock.unlock();
disCont[system].dispatch->setFlags(song.systemFlags[system]);
disCont[system].dispatch->setFlags(song.systemFlagsOld[system]);
disCont[system].setRates(got.rate);
if (restart && isPlaying()) {
playSub(false);
@ -3629,7 +3632,7 @@ void DivEngine::rescanAudioDevices() {
void DivEngine::initDispatch() {
BUSY_BEGIN;
for (int i=0; i<song.systemLen; i++) {
disCont[i].init(song.system[i],this,getChannelCount(song.system[i]),got.rate,song.systemFlags[i]);
disCont[i].init(song.system[i],this,getChannelCount(song.system[i]),got.rate,song.systemFlagsOld[i]);
disCont[i].setRates(got.rate);
disCont[i].setQuality(lowQuality);
}

View file

@ -19,6 +19,7 @@
#ifndef _ENGINE_H
#define _ENGINE_H
#include "config.h"
#include "instrument.h"
#include "song.h"
#include "dispatch.h"
@ -46,8 +47,8 @@
#define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock();
#define BUSY_END isBusy.unlock(); softLocked=false;
#define DIV_VERSION "dev117"
#define DIV_ENGINE_VERSION 117
#define DIV_VERSION "dev118"
#define DIV_ENGINE_VERSION 118
// for imports
#define DIV_VERSION_MOD 0xff01
#define DIV_VERSION_FC 0xff02
@ -327,6 +328,7 @@ class DivEngine {
bool lowQuality;
bool playing;
bool freelance;
bool shallStop;
bool speedAB;
bool endOfSong;
bool consoleMode;
@ -365,7 +367,7 @@ class DivEngine {
DivAudioEngines audioEngine;
DivAudioExportModes exportMode;
double exportFadeOut;
std::map<String,String> conf;
DivConfig conf;
std::deque<DivNoteEvent> pendingNotes;
// bitfield
unsigned char walked[8192];
@ -998,6 +1000,7 @@ class DivEngine {
lowQuality(false),
playing(false),
freelance(false),
shallStop(false),
speedAB(false),
endOfSong(false),
consoleMode(false),

View file

@ -1235,7 +1235,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
// system props
for (int i=0; i<32; i++) {
ds.systemFlags[i]=reader.readI();
ds.systemFlagsOld[i]=reader.readI();
}
// handle compound systems
@ -2373,7 +2373,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
ds.systemLen=(chCount+3)/4;
for(int i=0; i<ds.systemLen; i++) {
ds.system[i]=DIV_SYSTEM_AMIGA;
ds.systemFlags[i]=1|(80<<8)|(bypassLimits?4:0)|((ds.systemLen>1 || bypassLimits)?2:0); // PAL
ds.systemFlagsOld[i]=1|(80<<8)|(bypassLimits?4:0)|((ds.systemLen>1 || bypassLimits)?2:0); // PAL
}
for(int i=0; i<chCount; i++) {
ds.subsong[0]->chanShow[i]=true;
@ -2579,7 +2579,7 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) {
ds.system[0]=DIV_SYSTEM_AMIGA;
ds.systemVol[0]=64;
ds.systemPan[0]=0;
ds.systemFlags[0]=1|(80<<8); // PAL
ds.systemFlagsOld[0]=1|(80<<8); // PAL
ds.systemName="Amiga";
seqLen=reader.readI_BE();
@ -3219,11 +3219,11 @@ bool DivEngine::loadFTM(unsigned char* file, size_t len) {
}
if (expansions&16) {
ds.system[systemID]=DIV_SYSTEM_N163;
ds.systemFlags[systemID++]=n163Chans;
ds.systemFlagsOld[systemID++]=n163Chans;
}
if (expansions&32) {
ds.system[systemID]=DIV_SYSTEM_AY8910;
ds.systemFlags[systemID++]=38; // Sunsoft 5B
ds.systemFlagsOld[systemID++]=38; // Sunsoft 5B
}
ds.systemLen=systemID;
@ -3734,7 +3734,7 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
}
for (int i=0; i<32; i++) {
w->writeI(song.systemFlags[i]);
w->writeI(song.systemFlagsOld[i]);
}
// song name

View file

@ -559,13 +559,12 @@ void DivInstrument::putInsData(SafeWriter* w) {
w->writeC(es5506.envelope.k2Slow);
// SNES
// @tildearrow please update this
w->writeC(snes.useEnv);
w->writeC(0);
w->writeC(0);
w->writeC(snes.gainMode);
w->writeC(snes.gain);
w->writeC(snes.a);
w->writeC(snes.d);
w->writeC(snes.s);
w->writeC((snes.s&7)|(snes.sus?8:0));
w->writeC(snes.r);
// macro speed/delay
@ -1259,11 +1258,19 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) {
// SNES
if (version>=109) {
snes.useEnv=reader.readC();
reader.readC();
reader.readC();
if (version<118) {
// why why why
reader.readC();
reader.readC();
} else {
snes.gainMode=(DivInstrumentSNES::GainMode)reader.readC();
snes.gain=reader.readC();
}
snes.a=reader.readC();
snes.d=reader.readC();
snes.s=reader.readC();
snes.sus=snes.s&8;
snes.s&=7;
snes.r=reader.readC();
}

View file

@ -610,12 +610,13 @@ struct DivInstrumentSNES {
GAIN_MODE_INC_LINEAR=6,
GAIN_MODE_INC_INVLOG=7
};
bool useEnv;
bool useEnv, sus;
GainMode gainMode;
unsigned char gain;
unsigned char a, d, s, r;
DivInstrumentSNES():
useEnv(true),
sus(false),
gainMode(GAIN_MODE_DIRECT),
gain(127),
a(15),

View file

@ -218,6 +218,10 @@ void DivPlatformArcade::tick(bool sysTick) {
immWrite(0x18,chan[i].std.ex3.val);
}
if (chan[i].std.ex1.had || chan[i].std.ex2.had || chan[i].std.ex3.had) {
immWrite(0x01,0x00); // LFO On
}
if (chan[i].std.alg.had) {
chan[i].state.alg=chan[i].std.alg.val;
if (isMuted[i]) {

View file

@ -47,7 +47,7 @@ void DivPlatformPCMDAC::acquire(short* bufL, short* bufR, size_t start, size_t l
chan.audPos%=chan.audLen;
chan.audDir=false;
}
output=(chan.ws.output[chan.audPos]^0x80)<<8;
output=(chan.ws.output[chan.audPos]-0x80)<<8;
} else {
DivSample* s=parent->getSample(chan.sample);
if (s->samples>0) {
@ -195,7 +195,7 @@ int DivPlatformPCMDAC::dispatch(DivCommand c) {
DivInstrument* ins=parent->getIns(chan.ins,DIV_INS_AMIGA);
if (ins->amiga.useWave) {
chan.useWave=true;
chan.audLen=ins->amiga.waveLen;
chan.audLen=ins->amiga.waveLen+1;
if (chan.insChanged) {
if (chan.wave<0) {
chan.wave=0;

View file

@ -83,7 +83,7 @@ void DivPlatformSNES::acquire(short* bufL, short* bufR, size_t start, size_t len
bufL[h]=out[0];
bufR[h]=out[1];
for (int i=0; i<8; i++) {
int next=chOut[i*2]+chOut[i*2+1];
int next=(3*(chOut[i*2]+chOut[i*2+1]))>>2;
if (next<-32768) next=-32768;
if (next>32767) next=32767;
next=(next*254)/MAX(1,globalVolL+globalVolR);
@ -106,18 +106,9 @@ void DivPlatformSNES::tick(bool sysTick) {
}
if (chan[i].std.arp.had) {
if (!chan[i].inPorta) {
if (chan[i].std.arp.mode) {
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val);
} else {
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+chan[i].std.arp.val);
}
chan[i].baseFreq=NOTE_FREQUENCY(parent->calcArp(chan[i].note,chan[i].std.arp.val));
}
chan[i].freqChanged=true;
} else {
if (chan[i].std.arp.mode && chan[i].std.arp.finished) {
chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note);
chan[i].freqChanged=true;
}
}
if (chan[i].std.duty.had) {
noiseFreq=chan[i].std.duty.val;
@ -217,7 +208,9 @@ void DivPlatformSNES::tick(bool sysTick) {
chan[i].keyOn=false;
}
if (chan[i].keyOff) {
koff|=(1<<i);
if (!chan[i].state.sus) {
koff|=(1<<i);
}
chan[i].keyOff=false;
}
if (chan[i].freqChanged) {
@ -228,7 +221,7 @@ void DivPlatformSNES::tick(bool sysTick) {
}
}
if (writeControl) {
unsigned char control=noiseFreq&0x1f;
unsigned char control=(noiseFreq&0x1f)|(echoOn?0:0x20);
rWrite(0x6c,control);
writeControl=false;
}
@ -291,9 +284,9 @@ int DivPlatformSNES::dispatch(DivCommand c) {
if (chan[c.chan].insChanged) {
if (chan[c.chan].wave<0) {
chan[c.chan].wave=0;
chan[c.chan].ws.setWidth(chan[c.chan].wtLen);
chan[c.chan].ws.changeWave1(chan[c.chan].wave);
}
chan[c.chan].ws.setWidth(chan[c.chan].wtLen);
chan[c.chan].ws.changeWave1(chan[c.chan].wave);
}
chan[c.chan].ws.init(ins,chan[c.chan].wtLen,15,chan[c.chan].insChanged);
} else {
@ -306,47 +299,39 @@ int DivPlatformSNES::dispatch(DivCommand c) {
if (chan[c.chan].insChanged) {
chan[c.chan].state=ins->snes;
}
if (chan[c.chan].state.useEnv) {
chWrite(c.chan,5,chan[c.chan].state.a|(chan[c.chan].state.d<<4)|0x80);
chWrite(c.chan,6,chan[c.chan].state.r|(chan[c.chan].state.s<<5));
} else {
chWrite(c.chan,5,0);
switch (chan[c.chan].state.gainMode) {
case DivInstrumentSNES::GAIN_MODE_DIRECT:
chWrite(c.chan,7,chan[c.chan].state.gain&127);
break;
case DivInstrumentSNES::GAIN_MODE_DEC_LINEAR:
chWrite(c.chan,7,0x80|(chan[c.chan].state.gain&31));
break;
case DivInstrumentSNES::GAIN_MODE_INC_LINEAR:
chWrite(c.chan,7,0xc0|(chan[c.chan].state.gain&31));
break;
case DivInstrumentSNES::GAIN_MODE_DEC_LOG:
chWrite(c.chan,7,0xa0|(chan[c.chan].state.gain&31));
break;
case DivInstrumentSNES::GAIN_MODE_INC_INVLOG:
chWrite(c.chan,7,0xe0|(chan[c.chan].state.gain&31));
break;
}
}
chan[c.chan].active=true;
if (chan[c.chan].insChanged || chan[c.chan].state.sus) {
writeEnv(c.chan);
}
if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].baseFreq=round(NOTE_FREQUENCY(c.value));
chan[c.chan].freqChanged=true;
chan[c.chan].note=c.value;
}
chan[c.chan].active=true;
chan[c.chan].keyOn=true;
chan[c.chan].macroInit(ins);
chan[c.chan].insChanged=false;
break;
}
case DIV_CMD_NOTE_OFF:
chan[c.chan].sample=-1;
chan[c.chan].active=false;
chan[c.chan].keyOff=true;
chan[c.chan].macroInit(NULL);
chan[c.chan].keyOn=false;
if (chan[c.chan].state.sus) {
writeEnv(c.chan);
} else {
chan[c.chan].macroInit(NULL);
}
break;
case DIV_CMD_NOTE_OFF_ENV:
chan[c.chan].active=false;
chan[c.chan].keyOff=true;
chan[c.chan].keyOn=false;
if (chan[c.chan].state.sus) {
writeEnv(c.chan);
}
chan[c.chan].std.release();
break;
case DIV_CMD_ENV_RELEASE:
chan[c.chan].std.release();
break;
@ -359,7 +344,10 @@ int DivPlatformSNES::dispatch(DivCommand c) {
case DIV_CMD_VOLUME:
if (chan[c.chan].vol!=c.value) {
chan[c.chan].vol=c.value;
writeOutVol(c.chan);
if (!chan[c.chan].std.vol.has) {
chan[c.chan].outVol=c.value;
writeOutVol(c.chan);
}
}
break;
case DIV_CMD_GET_VOLUME:
@ -374,6 +362,11 @@ int DivPlatformSNES::dispatch(DivCommand c) {
chan[c.chan].pitch=c.value;
chan[c.chan].freqChanged=true;
break;
case DIV_CMD_WAVE:
if (!chan[c.chan].useWave) break;
chan[c.chan].wave=c.value;
chan[c.chan].ws.changeWave1(chan[c.chan].wave);
break;
case DIV_CMD_NOTE_PORTA: {
int destFreq=round(NOTE_FREQUENCY(c.value2));
bool return2=false;
@ -410,10 +403,117 @@ int DivPlatformSNES::dispatch(DivCommand c) {
chan[c.chan].inPorta=c.value;
break;
case DIV_CMD_SAMPLE_POS:
// may have to remove this
chan[c.chan].audPos=c.value;
chan[c.chan].setPos=true;
break;
// TODO SNES-specific commands
case DIV_CMD_STD_NOISE_MODE:
chan[c.chan].noise=c.value;
writeNoise=true;
break;
case DIV_CMD_SNES_PITCH_MOD:
chan[c.chan].pitchMod=c.value;
writePitchMod=true;
break;
case DIV_CMD_SNES_INVERT:
chan[c.chan].invertL=(c.value>>4);
chan[c.chan].invertR=c.chan&15;
writeOutVol(c.chan);
break;
case DIV_CMD_SNES_GAIN_MODE:
if (c.value) {
chan[c.chan].state.useEnv=false;
switch (c.value) {
case 1:
chan[c.chan].state.gainMode=DivInstrumentSNES::GAIN_MODE_DIRECT;
break;
case 2:
chan[c.chan].state.gainMode=DivInstrumentSNES::GAIN_MODE_DEC_LINEAR;
break;
case 3:
chan[c.chan].state.gainMode=DivInstrumentSNES::GAIN_MODE_DEC_LOG;
break;
case 4:
chan[c.chan].state.gainMode=DivInstrumentSNES::GAIN_MODE_INC_LINEAR;
break;
case 5:
chan[c.chan].state.gainMode=DivInstrumentSNES::GAIN_MODE_INC_INVLOG;
break;
}
} else {
chan[c.chan].state.useEnv=true;
}
writeEnv(c.chan);
break;
case DIV_CMD_SNES_GAIN:
if (chan[c.chan].state.gainMode==DivInstrumentSNES::GAIN_MODE_DIRECT) {
chan[c.chan].state.gain=c.value&0x7f;
} else {
chan[c.chan].state.gain=c.value&0x1f;
}
if (!chan[c.chan].state.useEnv) writeEnv(c.chan);
break;
case DIV_CMD_STD_NOISE_FREQ:
noiseFreq=c.value&0x1f;
writeControl=true;
break;
case DIV_CMD_FM_AR:
chan[c.chan].state.a=c.value&15;
if (chan[c.chan].state.useEnv) writeEnv(c.chan);
break;
case DIV_CMD_FM_DR:
chan[c.chan].state.d=c.value&7;
if (chan[c.chan].state.useEnv) writeEnv(c.chan);
break;
case DIV_CMD_FM_SL:
chan[c.chan].state.s=c.value&7;
if (chan[c.chan].state.useEnv) writeEnv(c.chan);
break;
case DIV_CMD_FM_RR:
chan[c.chan].state.r=c.value&0x1f;
if (chan[c.chan].state.useEnv) writeEnv(c.chan);
break;
case DIV_CMD_SNES_ECHO:
chan[c.chan].echo=c.value;
writeEcho=true;
break;
case DIV_CMD_SNES_ECHO_DELAY: {
echoDelay=c.value&15;
unsigned char esa=0xf8-(echoDelay<<3);
if (echoOn) {
rWrite(0x6d,esa);
rWrite(0x7d,echoDelay);
}
break;
}
case DIV_CMD_SNES_ECHO_ENABLE:
echoOn=c.value;
initEcho();
break;
case DIV_CMD_SNES_ECHO_FEEDBACK:
echoFeedback=c.value;
if (echoOn) {
rWrite(0x0d,echoFeedback);
}
break;
case DIV_CMD_SNES_ECHO_FIR:
echoFIR[c.value&7]=c.value2;
if (echoOn) {
rWrite(0x0f+((c.value&7)<<4),echoFIR[c.value&7]);
}
break;
case DIV_CMD_SNES_ECHO_VOL_LEFT:
echoVolL=c.value;
if (echoOn) {
rWrite(0x2c,echoVolL);
}
break;
case DIV_CMD_SNES_ECHO_VOL_RIGHT:
echoVolR=c.value;
if (echoOn) {
rWrite(0x3c,echoVolR);
}
break;
case DIV_CMD_GET_VOLMAX:
return 127;
break;
@ -450,6 +550,36 @@ void DivPlatformSNES::writeOutVol(int ch) {
chWrite(ch,1,outR);
}
void DivPlatformSNES::writeEnv(int ch) {
if (chan[ch].state.useEnv) {
chWrite(ch,5,chan[ch].state.a|(chan[ch].state.d<<4)|0x80);
if (chan[ch].state.sus && chan[ch].active) {
chWrite(ch,6,chan[ch].state.s<<5);
} else {
chWrite(ch,6,chan[ch].state.r|(chan[ch].state.s<<5));
}
} else {
chWrite(ch,5,0);
switch (chan[ch].state.gainMode) {
case DivInstrumentSNES::GAIN_MODE_DIRECT:
chWrite(ch,7,chan[ch].state.gain&127);
break;
case DivInstrumentSNES::GAIN_MODE_DEC_LINEAR:
chWrite(ch,7,0x80|(chan[ch].state.gain&31));
break;
case DivInstrumentSNES::GAIN_MODE_INC_LINEAR:
chWrite(ch,7,0xc0|(chan[ch].state.gain&31));
break;
case DivInstrumentSNES::GAIN_MODE_DEC_LOG:
chWrite(ch,7,0xa0|(chan[ch].state.gain&31));
break;
case DivInstrumentSNES::GAIN_MODE_INC_INVLOG:
chWrite(ch,7,0xe0|(chan[ch].state.gain&31));
break;
}
}
}
void DivPlatformSNES::muteChannel(int ch, bool mute) {
isMuted[ch]=mute;
writeOutVol(ch);
@ -469,6 +599,7 @@ void DivPlatformSNES::forceIns() {
writeNoise=true;
writePitchMod=true;
writeEcho=true;
initEcho();
}
void* DivPlatformSNES::getChanState(int ch) {
@ -497,7 +628,28 @@ int DivPlatformSNES::getRegisterPoolSize() {
return 128;
}
void DivPlatformSNES::initEcho() {
unsigned char esa=0xf8-(echoDelay<<3);
if (echoOn) {
rWrite(0x6d,esa);
rWrite(0x7d,echoDelay);
rWrite(0x0d,echoFeedback);
rWrite(0x2c,echoVolL);
rWrite(0x3c,echoVolR);
for (int i=0; i<8; i++) {
rWrite(0x0f+(i<<4),echoFIR[i]);
}
} else {
rWrite(0x6d,0);
rWrite(0x7d,0);
rWrite(0x2c,0);
rWrite(0x3c,0);
}
writeControl=true;
}
void DivPlatformSNES::reset() {
memcpy(sampleMem,copyOfSampleMem,65536);
dsp.init(sampleMem);
dsp.set_output(NULL,0);
memset(regPool,0,128);
@ -521,6 +673,22 @@ void DivPlatformSNES::reset() {
writeNoise=false;
writePitchMod=false;
writeEcho=false;
echoDelay=0;
echoFeedback=0;
echoFIR[0]=127;
echoFIR[1]=0;
echoFIR[2]=0;
echoFIR[3]=0;
echoFIR[4]=0;
echoFIR[5]=0;
echoFIR[6]=0;
echoFIR[7]=0;
echoVolL=127;
echoVolR=127;
echoOn=false;
initEcho();
}
bool DivPlatformSNES::isStereo() {
@ -574,7 +742,7 @@ size_t DivPlatformSNES::getSampleMemUsage(int index) {
}
void DivPlatformSNES::renderSamples() {
memset(sampleMem,0,getSampleMemCapacity());
memset(copyOfSampleMem,0,getSampleMemCapacity());
memset(sampleOff,0,256*sizeof(unsigned int));
// skip past sample table and wavetable buffer
@ -585,17 +753,18 @@ void DivPlatformSNES::renderSamples() {
int actualLength=MIN((int)(getSampleMemCapacity()-memPos)/9*9,length);
if (actualLength>0) {
sampleOff[i]=memPos;
memcpy(&sampleMem[memPos],s->dataBRR,actualLength);
memcpy(&copyOfSampleMem[memPos],s->dataBRR,actualLength);
memPos+=actualLength;
}
if (actualLength<length) {
// terminate the sample
sampleMem[memPos-9]=1;
copyOfSampleMem[memPos-9]=1;
logW("out of BRR memory for sample %d!",i);
break;
}
}
sampleMemLen=memPos;
memcpy(sampleMem,copyOfSampleMem,65536);
}
void DivPlatformSNES::setFlags(unsigned int flags) {
@ -607,13 +776,14 @@ int DivPlatformSNES::init(DivEngine* p, int channels, int sugRate, unsigned int
parent=p;
dumpWrites=false;
skipRegisterWrites=false;
for (int i=0; i<8; i++) {
oscBuf[i]=new DivDispatchOscBuffer;
isMuted[i]=false;
}
sampleMemLen=0;
chipClock=1024000;
rate=chipClock/32;
for (int i=0; i<8; i++) {
oscBuf[i]=new DivDispatchOscBuffer;
oscBuf[i]->rate=rate;
isMuted[i]=false;
}
setFlags(flags);
reset();
return 8;

View file

@ -77,11 +77,15 @@ class DivPlatformSNES: public DivDispatch {
bool isMuted[8];
int globalVolL, globalVolR;
unsigned char noiseFreq;
signed char echoVolL, echoVolR, echoFeedback;
signed char echoFIR[8];
unsigned char echoDelay;
size_t sampleTableBase;
bool writeControl;
bool writeNoise;
bool writePitchMod;
bool writeEcho;
bool echoOn;
struct QueuedWrite {
unsigned char addr;
@ -91,6 +95,7 @@ class DivPlatformSNES: public DivDispatch {
std::queue<QueuedWrite> writes;
signed char sampleMem[65536];
signed char copyOfSampleMem[65536];
size_t sampleMemLen;
unsigned int sampleOff[256];
unsigned char regPool[0x80];
@ -126,6 +131,8 @@ class DivPlatformSNES: public DivDispatch {
private:
void updateWave(int ch);
void writeOutVol(int ch);
void writeEnv(int ch);
void initEcho();
};
#endif

View file

@ -203,6 +203,7 @@ void DivPlatformYM2203::tick(bool sysTick) {
ay->tick(sysTick);
ay->flushWrites();
for (DivRegWrite& i: ay->getRegisterWrites()) {
if (i.addr>15) continue;
immWrite(i.addr&15,i.val);
}
ay->getRegisterWrites().clear();
@ -807,6 +808,7 @@ void DivPlatformYM2203::forceIns() {
ay->forceIns();
ay->flushWrites();
for (DivRegWrite& i: ay->getRegisterWrites()) {
if (i.addr>15) continue;
immWrite(i.addr&15,i.val);
}
ay->getRegisterWrites().clear();

View file

@ -448,6 +448,7 @@ void DivPlatformYM2203Ext::forceIns() {
ay->forceIns();
ay->flushWrites();
for (DivRegWrite& i: ay->getRegisterWrites()) {
if (i.addr>15) continue;
immWrite(i.addr&15,i.val);
}
ay->getRegisterWrites().clear();

View file

@ -654,6 +654,7 @@ void DivPlatformYM2608::tick(bool sysTick) {
ay->tick(sysTick);
ay->flushWrites();
for (DivRegWrite& i: ay->getRegisterWrites()) {
if (i.addr>15) continue;
immWrite(i.addr&15,i.val);
}
ay->getRegisterWrites().clear();
@ -1199,6 +1200,7 @@ void DivPlatformYM2608::forceIns() {
ay->forceIns();
ay->flushWrites();
for (DivRegWrite& i: ay->getRegisterWrites()) {
if (i.addr>15) continue;
immWrite(i.addr&15,i.val);
}
ay->getRegisterWrites().clear();

View file

@ -483,6 +483,7 @@ void DivPlatformYM2608Ext::forceIns() {
ay->forceIns();
ay->flushWrites();
for (DivRegWrite& i: ay->getRegisterWrites()) {
if (i.addr>15) continue;
immWrite(i.addr&15,i.val);
}
ay->getRegisterWrites().clear();

View file

@ -593,13 +593,14 @@ void DivPlatformYM2610::tick(bool sysTick) {
ay->tick(sysTick);
ay->flushWrites();
for (DivRegWrite& i: ay->getRegisterWrites()) {
if (i.addr>15) continue;
immWrite(i.addr&15,i.val);
}
ay->getRegisterWrites().clear();
}
int DivPlatformYM2610::dispatch(DivCommand c) {
if (c.chan>=psgChanOffs && c.chan<7) {
if (c.chan>=psgChanOffs && c.chan<adpcmAChanOffs) {
c.chan-=psgChanOffs;
return ay->dispatch(c);
}
@ -1169,6 +1170,7 @@ void DivPlatformYM2610::forceIns() {
ay->forceIns();
ay->flushWrites();
for (DivRegWrite& i: ay->getRegisterWrites()) {
if (i.addr>15) continue;
immWrite(i.addr&15,i.val);
}
ay->getRegisterWrites().clear();

View file

@ -656,6 +656,7 @@ void DivPlatformYM2610B::tick(bool sysTick) {
ay->tick(sysTick);
ay->flushWrites();
for (DivRegWrite& i: ay->getRegisterWrites()) {
if (i.addr>15) continue;
immWrite(i.addr&15,i.val);
}
ay->getRegisterWrites().clear();
@ -1232,6 +1233,7 @@ void DivPlatformYM2610B::forceIns() {
ay->forceIns();
ay->flushWrites();
for (DivRegWrite& i: ay->getRegisterWrites()) {
if (i.addr>15) continue;
immWrite(i.addr&15,i.val);
}
ay->getRegisterWrites().clear();

View file

@ -474,6 +474,7 @@ void DivPlatformYM2610BExt::forceIns() {
ay->forceIns();
ay->flushWrites();
for (DivRegWrite& i: ay->getRegisterWrites()) {
if (i.addr>15) continue;
immWrite(i.addr&15,i.val);
}
ay->getRegisterWrites().clear();

View file

@ -474,6 +474,7 @@ void DivPlatformYM2610Ext::forceIns() {
ay->forceIns();
ay->flushWrites();
for (DivRegWrite& i: ay->getRegisterWrites()) {
if (i.addr>15) continue;
immWrite(i.addr&15,i.val);
}
ay->getRegisterWrites().clear();

View file

@ -858,15 +858,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
break;
case 0xff: // stop song
freelance=false;
playing=false;
extValuePresent=false;
stepPlay=0;
remainingLoops=-1;
sPreview.sample=-1;
sPreview.wave=-1;
sPreview.pos=0;
sPreview.dir=false;
shallStop=true;
break;
}
}
@ -1299,6 +1291,21 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) {
firstTick=false;
if (shallStop) {
freelance=false;
playing=false;
extValuePresent=false;
stepPlay=0;
remainingLoops=-1;
sPreview.sample=-1;
sPreview.wave=-1;
sPreview.pos=0;
sPreview.dir=false;
ret=true;
shallStop=false;
return ret;
}
// system tick
for (int i=0; i<song.systemLen; i++) disCont[i].dispatch->tick(subticks==tickMult);

View file

@ -25,6 +25,7 @@
#define DIV_MAX_CHANS 128
#include "../ta-utils.h"
#include "config.h"
#include "orders.h"
#include "instrument.h"
#include "pattern.h"
@ -233,202 +234,10 @@ struct DivSong {
unsigned char systemLen;
signed char systemVol[32];
signed char systemPan[32];
// interpretation of these flags varies depending on system.
// - most systems:
// - bit 0: PAL
// - NES:
// - bit 0-1: system type
// - 0: NTSC
// - 1: PAL
// - 2: Dendy
// - SMS/SN76489:
// - bit 0-1, 8-15: clock rate
// - 0000: 3.58MHz (NTSC)
// - 0001: 3.55MHz (PAL)
// - 0002: 4MHz (Other)
// - 0003: 1.79MHz (half NTSC)
// - 0100: 3MHz
// - 0101: 2MHz
// - 0102: 447KHz (NTSC / 8)
// - bit 2-3, 6-7: chip type
// - 00: Sega VDP (16-bit noise)
// - 04: real SN76489 (15-bit noise)
// - 08: real SN76489 with Atari-like short noise buzz (15-bit noise)
// - 0c: Game Gear (16-bit noise, stereo)
// - 40: real SN76489A (17-bit noise)
// - 44: real SN76496 (17-bit noise)
// - 48: NCR 8496 (16-bit noise)
// - 4c: Tandy PSSJ-3 (16-bit noise)
// - 80: real SN94624 (15-bit noise)
// - 84: real SN76494 (17-bit noise)
// - bit 4: disable noise phase reset
// - YM2612/YM3438:
// - bit 0-30: clock rate
// - 0: Genesis NTSC (7.67MHz)
// - 1: Genesis PAL (7.61MHz)
// - 2: FM Towns (8MHz)
// - 3: AtGames Genesis (6.13MHz)
// - 4: Sega System 32 (8.06MHz)
// - bit 31: DAC distortion
// - 0: disable
// - 1: enable
// - YM2151:
// - bit 0-7: clock rate
// - 0: 3.58MHz (NTSC)
// - 1: 3.55MHz (PAL)
// - 2: 4MHz
// - YM2610(B):
// - bit 0-7: clock rate
// - 0: 8MHz (Neo Geo MVS)
// - 1: 8.06MHz (Neo Geo AES)
// - AY-3-8910/AY8930:
// - bit 0-3: clock rate
// - 0: 1.79MHz (MSX NTSC)
// - 1: 1.77MHz (ZX Spectrum, MSX PAL, etc.)
// - 2: 1.75MHz (ZX Spectrum)
// - 3: 2MHz (Atari ST)
// - 4: 1.5MHz (Vectrex)
// - 5: 1MHz (Amstrad CPC)
// - 6: 0.89MHz (Sunsoft 5B)
// - 7: 1.67MHz
// - 8: 0.83MHz (Sunsoft 5B on PAL)
// - 9: 1.10MHz (Gamate/VIC-20 PAL)
// - 10: 2.097152MHz (Game Boy)
// - 11: 3.58MHz (Darky)
// - 12: 3.6MHz (Darky)
// - 13: 1.25MHz
// - 14: 1.536MHz
// - bit 4-5: chip type (ignored on AY8930)
// - 0: AY-3-8910 or similar
// - 1: YM2149
// - 2: Sunsoft 5B
// - 3: AY-3-8914
// - bit 6: stereo (ignored on Sunsoft 5B)
// - 0: mono
// - 1: stereo ABC
// - bit 7: clock divider pin (YM2149, AY8930)
// - 0: high (disable divider)
// - 1: low (internally divided to half)
// - SAA1099:
// - bit 0-1: clock rate
// - 0: 8MHz (SAM Coupé)
// - 1: 7.15MHz (Game Blaster, NTSC)
// - 2: 7.09MHz (PAL)
// - Amiga:
// - bit 0: clock rate
// - 0: 7.15MHz (NTSC)
// - 1: 7.09MHz (PAL)
// - bit 1: model
// - 0: Amiga 500
// - 1: Amiga 1200
// - bit 8-14: stereo separation
// - 0 is 0% while 127 is 100%
// - PC Speaker:
// - bit 0-1: speaker type
// - 0: unfiltered
// - 1: cone
// - 2: piezo
// - 3: real (TODO)
// - QSound:
// - bit 12-20: echo feedback
// - Valid values are 0-255
// - bit 0-11: echo delay length
// - Valid values are 0-2725
// - 0 is max length, 2725 is min length
// - OPLL:
// - bit 0-3: clock rate
// - 0: NTSC (3.58MHz)
// - 1: PAL (3.55MHz)
// - 2: Other (4MHz)
// - 3: half NTSC (1.79MHz)
// - bit 4-7: patch set
// - 0: YM2413
// - 1: YMF281
// - 2: YM2423
// - 3: VRC7
// - 4: custom (TODO)
// - X1-010:
// - bit 0-3: clock rate
// - 0: 16MHz (Seta 1)
// - 1: 16.67MHz (Seta 2)
// - bit 4: stereo
// - 0: mono
// - 1: stereo
// - YM2203:
// - bit 0-4: clock rate
// - 0: 3.58MHz (NTSC)
// - 1: 3.55MHz (PAL)
// - 2: 4MHz
// - 3: 3MHz
// - 4: 3.9936MHz (PC-88, PC-98)
// - 5: 1.5MHz
// - bit 5-6: output rate
// - 0: FM: clock / 72, SSG: clock / 16
// - 1: FM: clock / 36, SSG: clock / 8
// - 2: FM: clock / 24, SSG: clock / 4
// - YM2608:
// - bit 0-4: clock rate
// - 0: 8MHz
// - 1: 7.987MHz (PC-88, PC-98)
// - bit 5-6: output rate
// - 0: FM: clock / 144, SSG: clock / 32
// - 1: FM: clock / 72, SSG: clock / 16
// - 2: FM: clock / 48, SSG: clock / 8
// - YM3526, YM3812, Y8950:
// - bit 0-7: clock rate
// - 0: 3.58MHz (NTSC)
// - 1: 3.55MHz (PAL)
// - 2: 4MHz
// - 3: 3MHz
// - 4: 3.9936MHz (PC-88, PC-98)
// - 5: 3.5MHz
// - YMF262:
// - bit 0-7: clock rate
// - 0: 14.32MHz (NTSC)
// - 1: 14.19MHz (PAL)
// - 2: 14MHz
// - 3: 16MHz
// - 4: 15MHz
// - YMF289B: (TODO)
// - bit 0-7: clock rate
// - 0: 33.8688MHz
// - 1: 28.64MHz (NTSC)
// - 2: 28.38MHz (PAL)
// - MSM6295:
// - bit 0-6: clock rate
// - 0: 1MHz
// - 1: 1.056MHz
// - 2: 4MHz
// - 3: 4.224MHz
// - 4: 3.58MHz (NTSC)
// - 5: 1.79MHz (Half NTSC)
// - 6: 1.023MHz
// - 7: 0.895MHz (Quarter NTSC)
// - 8: 2MHz
// - 9: 2.112MHz
// - 10: 0.875MHz
// - 11: 0.9375MHz
// - 12: 1.5MHz
// - 13: 3MHz
// - 14: 1.193MHz
// - bit 7: Output rate
// - 0: clock / 132
// - 1: clock / 165
// - SCC/+:
// - bit 0-6: clock rate
// - 0: 1.79MHz (MSX NTSC)
// - 1: 1.77MHz (PAL)
// - 2: 1.5MHz
// - 3: 2MHz
// - YMZ280B:
// - bit 0-7: clock rate
// - 0: 16.9344MHz
// - 1: 14.32MHz (NTSC)
// - 2: 14.19MHz (PAL)
// - 3: 16MHz
// - 4: 16.67MHz
// - 5: 14MHz
unsigned int systemFlags[32];
// this one will be removed soon...
unsigned int systemFlagsOld[32];
// ...and replaced with... this!
DivConfig systemFlags[32];
// song information
String name, author, systemName;
@ -624,7 +433,7 @@ struct DivSong {
system[i]=DIV_SYSTEM_NULL;
systemVol[i]=64;
systemPan[i]=0;
systemFlags[i]=0;
systemFlagsOld[i]=0;
}
subsong.push_back(new DivSubSong);
system[0]=DIV_SYSTEM_YM2612;

View file

@ -61,7 +61,7 @@ String DivEngine::getSongSystemLegacyName(DivSong& ds, bool isMultiSystemAccepta
return "help! what's going on!";
case 1:
if (ds.system[0]==DIV_SYSTEM_AY8910) {
switch (ds.systemFlags[0]&0x3f) {
switch (ds.systemFlagsOld[0]&0x3f) {
case 0: // AY-3-8910, 1.79MHz
case 1: // AY-3-8910, 1.77MHz
case 2: // AY-3-8910, 1.75MHz
@ -88,35 +88,35 @@ String DivEngine::getSongSystemLegacyName(DivSong& ds, bool isMultiSystemAccepta
return "Intellivision (PAL)";
default:
if ((ds.systemFlags[0]&0x30)==0x00) {
if ((ds.systemFlagsOld[0]&0x30)==0x00) {
return "AY-3-8910";
} else if ((ds.systemFlags[0]&0x30)==0x10) {
} else if ((ds.systemFlagsOld[0]&0x30)==0x10) {
return "Yamaha YM2149";
} else if ((ds.systemFlags[0]&0x30)==0x20) {
} else if ((ds.systemFlagsOld[0]&0x30)==0x20) {
return "Overclocked Sunsoft 5B";
} else if ((ds.systemFlags[0]&0x30)==0x30) {
} else if ((ds.systemFlagsOld[0]&0x30)==0x30) {
return "Intellivision";
}
}
} else if (ds.system[0]==DIV_SYSTEM_SMS) {
switch (ds.systemFlags[0]&0x0f) {
switch (ds.systemFlagsOld[0]&0x0f) {
case 0: case 1:
return "Sega Master System";
case 6:
return "BBC Micro";
}
} else if (ds.system[0]==DIV_SYSTEM_YM2612) {
switch (ds.systemFlags[0]&3) {
switch (ds.systemFlagsOld[0]&3) {
case 2:
return "FM Towns";
}
} else if (ds.system[0]==DIV_SYSTEM_YM2151) {
switch (ds.systemFlags[0]&3) {
switch (ds.systemFlagsOld[0]&3) {
case 2:
return "Sharp X68000";
}
} else if (ds.system[0]==DIV_SYSTEM_SAA1099) {
switch (ds.systemFlags[0]&3) {
switch (ds.systemFlagsOld[0]&3) {
case 0:
return "SAM Coupé";
}
@ -875,23 +875,11 @@ void DivEngine::registerSystems() {
{DIV_INS_SNES, DIV_INS_SNES, DIV_INS_SNES, DIV_INS_SNES, DIV_INS_SNES, DIV_INS_SNES, DIV_INS_SNES, DIV_INS_SNES},
{},
{
{0x10, {DIV_CMD_WAVE, "10xx: Set waveform"}},
{0x11, {DIV_CMD_STD_NOISE_MODE, "11xx: Toggle noise mode"}},
{0x12, {DIV_CMD_SNES_ECHO, "12xx: Toggle echo on this channel"}},
{0x13, {DIV_CMD_SNES_PITCH_MOD, "13xx: Toggle pitch modulation"}},
{0x14, {DIV_CMD_SNES_INVERT, "14xy: Toggle invert (x: left; y: right)"}},
{0x15, {DIV_CMD_SNES_GAIN_MODE, "15xx: Set gain mode"}},
{0x16, {DIV_CMD_SNES_GAIN, "16xx: Set gain"}},
{0x18, {DIV_CMD_SNES_ECHO_ENABLE, "18xx: Enable echo buffer"}},
{0x19, {DIV_CMD_SNES_ECHO_DELAY, "19xx: Set echo delay"}},
{0x19, {DIV_CMD_SNES_ECHO_DELAY, "19xx: Set echo delay (0 to F)"}},
{0x1a, {DIV_CMD_SNES_ECHO_VOL_LEFT, "1Axx: Set left echo volume"}},
{0x1b, {DIV_CMD_SNES_ECHO_VOL_RIGHT, "1Bxx: Set right echo volume"}},
{0x1c, {DIV_CMD_SNES_ECHO_FEEDBACK, "1Cxx: Set echo feedback"}},
{0x1d, {DIV_CMD_STD_NOISE_FREQ, "1Dxx: Set noise frequency"}},
{0x20, {DIV_CMD_FM_AR, "20xx: Set attack"}},
{0x21, {DIV_CMD_FM_DR, "21xx: Set decay"}},
{0x22, {DIV_CMD_FM_SL, "22xx: Set sustain"}},
{0x23, {DIV_CMD_FM_RR, "23xx: Set release"}},
{0x30, {DIV_CMD_SNES_ECHO_FIR, "30xx: Set echo filter coefficient 0",constVal<0>,effectVal}},
{0x31, {DIV_CMD_SNES_ECHO_FIR, "31xx: Set echo filter coefficient 1",constVal<1>,effectVal}},
{0x32, {DIV_CMD_SNES_ECHO_FIR, "32xx: Set echo filter coefficient 2",constVal<2>,effectVal}},
@ -900,6 +888,20 @@ void DivEngine::registerSystems() {
{0x35, {DIV_CMD_SNES_ECHO_FIR, "35xx: Set echo filter coefficient 5",constVal<5>,effectVal}},
{0x36, {DIV_CMD_SNES_ECHO_FIR, "36xx: Set echo filter coefficient 6",constVal<6>,effectVal}},
{0x37, {DIV_CMD_SNES_ECHO_FIR, "37xx: Set echo filter coefficient 7",constVal<7>,effectVal}},
},
{
{0x10, {DIV_CMD_WAVE, "10xx: Set waveform"}},
{0x11, {DIV_CMD_STD_NOISE_MODE, "11xx: Toggle noise mode"}},
{0x12, {DIV_CMD_SNES_ECHO, "12xx: Toggle echo on this channel"}},
{0x13, {DIV_CMD_SNES_PITCH_MOD, "13xx: Toggle pitch modulation"}},
{0x14, {DIV_CMD_SNES_INVERT, "14xy: Toggle invert (x: left; y: right)"}},
{0x15, {DIV_CMD_SNES_GAIN_MODE, "15xx: Set envelope mode (0: ADSR, 1: gain/direct, 2: dec, 3: exp, 4: inc, 5: bent)"}},
{0x16, {DIV_CMD_SNES_GAIN, "16xx: Set gain (00 to 7F if direct; 00 to 1F otherwise)"}},
{0x1d, {DIV_CMD_STD_NOISE_FREQ, "1Dxx: Set noise frequency (00 to 1F)"}},
{0x20, {DIV_CMD_FM_AR, "20xx: Set attack (0 to F)"}},
{0x21, {DIV_CMD_FM_DR, "21xx: Set decay (0 to 7)"}},
{0x22, {DIV_CMD_FM_SL, "22xx: Set sustain (0 to 7)"}},
{0x23, {DIV_CMD_FM_RR, "23xx: Set release (00 to 1F)"}},
}
);
@ -1193,7 +1195,7 @@ void DivEngine::registerSystems() {
);
sysDefs[DIV_SYSTEM_SFX_BEEPER]=new DivSysDef(
"ZX Spectrum Beeper", NULL, 0x9f, 0, 6, false, true, 0, false, 1U<<DIV_SAMPLE_DEPTH_1BIT,
"ZX Spectrum Beeper", NULL, 0x9f, 0, 6, false, true, 0, false, 1U<<DIV_SAMPLE_DEPTH_8BIT,
"the ZX Spectrum only had a basic beeper capable of...\n...a bunch of thin pulses and tons of other interesting stuff!\nFurnace provides a thin pulse system.",
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6"},
{"CH1", "CH2", "CH3", "CH4", "CH5", "CH6"},

View file

@ -979,7 +979,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
if (!hasSN) {
hasSN=disCont[i].dispatch->chipClock;
willExport[i]=true;
switch ((song.systemFlags[i]>>2)&3) {
switch ((song.systemFlagsOld[i]>>2)&3) {
case 1: // real SN
snNoiseConfig=3;
snNoiseSize=15;
@ -1094,7 +1094,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
ayConfig=0x03;
hasClockDivider=true;
} else {
switch ((song.systemFlags[i]>>4)&3) {
switch ((song.systemFlagsOld[i]>>4)&3) {
default:
case 0: // AY8910
ayConfig=0x00;
@ -1113,10 +1113,10 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
break;
}
}
if (hasClockDivider && ((song.systemFlags[i]>>7)&1)) {
if (hasClockDivider && ((song.systemFlagsOld[i]>>7)&1)) {
ayFlags|=0x10;
}
if (hasStereo && ((song.systemFlags[i]>>6)&1)) {
if (hasStereo && ((song.systemFlagsOld[i]>>6)&1)) {
ayFlags|=0x80;
}
willExport[i]=true;
@ -1358,7 +1358,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
// chips even though the only difference is the output resolution
// these system types are currently handled by reusing isSecond flag
// also this system is not dual-able
if ((song.systemFlags[i]>>4)==1) {
if ((song.systemFlagsOld[i]>>4)==1) {
if (!hasRFC1) {
hasRFC1=disCont[i].dispatch->chipClock;
isSecond[i]=true;