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

This commit is contained in:
cam900 2023-02-06 08:56:14 +09:00
commit 03e0c8d8ee
30 changed files with 673 additions and 142 deletions

View file

@ -597,6 +597,7 @@ src/gui/editControls.cpp
src/gui/effectList.cpp src/gui/effectList.cpp
src/gui/findReplace.cpp src/gui/findReplace.cpp
src/gui/gradient.cpp src/gui/gradient.cpp
src/gui/grooves.cpp
src/gui/insEdit.cpp src/gui/insEdit.cpp
src/gui/log.cpp src/gui/log.cpp
src/gui/mixer.cpp src/gui/mixer.cpp

View file

@ -32,6 +32,7 @@ these fields are 0 in format versions prior to 100 (0.6pre1).
the format versions are: the format versions are:
- 139: Furnace dev139
- 138: Furnace dev138 - 138: Furnace dev138
- 137: Furnace dev137 - 137: Furnace dev137
- 136: Furnace dev136 - 136: Furnace dev136
@ -403,6 +404,14 @@ size | description
--- | **a couple more compat flags** (>=138) --- | **a couple more compat flags** (>=138)
1 | broken portamento during legato 1 | broken portamento during legato
7 | reserved 7 | reserved
--- | **speed pattern of first song** (>=139)
1 | length of speed pattern (fail if this is lower than 0 or higher than 16)
16 | speed pattern (this overrides speed 1 and speed 2 settings)
--- | **groove list** (>=139)
1 | number of entries
??? | groove entries. the format is:
| - 1 byte: length of groove
| - 16 bytes: groove pattern
``` ```
# patchbay # patchbay
@ -472,6 +481,9 @@ size | description
| - a list of channelCount C strings | - a list of channelCount C strings
S?? | channel short names S?? | channel short names
| - same as above | - same as above
--- | **speed pattern** (>=139)
1 | length of speed pattern (fail if this is lower than 0 or higher than 16)
16 | speed pattern (this overrides speed 1 and speed 2 settings)
``` ```
# chip flags # chip flags

View file

@ -24,6 +24,7 @@
#define DIV_MAX_CHIPS 32 #define DIV_MAX_CHIPS 32
#define DIV_MAX_CHANS 128 #define DIV_MAX_CHANS 128
#define DIV_MAX_PATTERNS 256 #define DIV_MAX_PATTERNS 256
#define DIV_MAX_CHIP_DEFS 256
// in-pattern // in-pattern
#define DIV_MAX_ROWS 256 #define DIV_MAX_ROWS 256

View file

@ -60,7 +60,7 @@ const char* DivEngine::getEffectDesc(unsigned char effect, int chan, bool notNul
case 0x08: case 0x08:
return "08xy: Set panning (x: left; y: right)"; return "08xy: Set panning (x: left; y: right)";
case 0x09: case 0x09:
return "09xx: Set speed 1"; return "09xx: Set groove pattern (speed 1 if no grooves exist)";
case 0x0a: case 0x0a:
return "0Axy: Volume slide (0y: down; x0: up)"; return "0Axy: Volume slide (0y: down; x0: up)";
case 0x0b: case 0x0b:
@ -70,7 +70,7 @@ const char* DivEngine::getEffectDesc(unsigned char effect, int chan, bool notNul
case 0x0d: case 0x0d:
return "0Dxx: Jump to next pattern"; return "0Dxx: Jump to next pattern";
case 0x0f: case 0x0f:
return "0Fxx: Set speed 2"; return "0Fxx: Set speed (speed 2 if no grooves exist)";
case 0x80: case 0x80:
return "80xx: Set panning (00: left; 80: center; FF: right)"; return "80xx: Set panning (00: left; 80: center; FF: right)";
case 0x81: case 0x81:
@ -1959,14 +1959,12 @@ String DivEngine::getPlaybackDebugInfo() {
"cmdsPerSecond: %d\n" "cmdsPerSecond: %d\n"
"globalPitch: %d\n" "globalPitch: %d\n"
"extValue: %d\n" "extValue: %d\n"
"speed1: %d\n"
"speed2: %d\n"
"tempoAccum: %d\n" "tempoAccum: %d\n"
"totalProcessed: %d\n" "totalProcessed: %d\n"
"bufferPos: %d\n", "bufferPos: %d\n",
curOrder,prevOrder,curRow,prevRow,ticks,subticks,totalLoops,lastLoopPos,nextSpeed,divider,cycles,clockDrift, curOrder,prevOrder,curRow,prevRow,ticks,subticks,totalLoops,lastLoopPos,nextSpeed,divider,cycles,clockDrift,
changeOrd,changePos,totalSeconds,totalTicks,totalTicksR,totalCmds,lastCmds,cmdsPerSecond,globalPitch, changeOrd,changePos,totalSeconds,totalTicks,totalTicksR,totalCmds,lastCmds,cmdsPerSecond,globalPitch,
(int)extValue,(int)speed1,(int)speed2,(int)tempoAccum,(int)totalProcessed,(int)bufferPos (int)extValue,(int)tempoAccum,(int)totalProcessed,(int)bufferPos
); );
} }
@ -2091,7 +2089,8 @@ void DivEngine::playSub(bool preserveDrift, int goalRow) {
lastLoopPos=-1; lastLoopPos=-1;
} }
endOfSong=false; endOfSong=false;
speedAB=false; // whaaaaa?
curSpeed=0;
playing=true; playing=true;
skipping=true; skipping=true;
memset(walked,0,8192); memset(walked,0,8192);
@ -2439,15 +2438,14 @@ void DivEngine::reset() {
} }
extValue=0; extValue=0;
extValuePresent=0; extValuePresent=0;
speed1=curSubSong->speed1; speeds=curSubSong->speeds;
speed2=curSubSong->speed2;
firstTick=false; firstTick=false;
shallStop=false; shallStop=false;
shallStopSched=false; shallStopSched=false;
pendingMetroTick=0; pendingMetroTick=0;
elapsedBars=0; elapsedBars=0;
elapsedBeats=0; elapsedBeats=0;
nextSpeed=speed1; nextSpeed=speeds.val[0];
divider=60; divider=60;
if (curSubSong->customTempo) { if (curSubSong->customTempo) {
divider=curSubSong->hz; divider=curSubSong->hz;
@ -2649,12 +2647,8 @@ size_t DivEngine::getCurrentSubSong() {
return curSubSongIndex; return curSubSongIndex;
} }
unsigned char DivEngine::getSpeed1() { const DivGroovePattern& DivEngine::getSpeeds() {
return speed1; return speeds;
}
unsigned char DivEngine::getSpeed2() {
return speed2;
} }
float DivEngine::getHz() { float DivEngine::getHz() {
@ -4236,7 +4230,7 @@ void DivEngine::quitDispatch() {
clockDrift=0; clockDrift=0;
chans=0; chans=0;
playing=false; playing=false;
speedAB=false; curSpeed=0;
endOfSong=false; endOfSong=false;
ticks=0; ticks=0;
tempoAccum=0; tempoAccum=0;

View file

@ -47,8 +47,8 @@
#define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock(); #define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock();
#define BUSY_END isBusy.unlock(); softLocked=false; #define BUSY_END isBusy.unlock(); softLocked=false;
#define DIV_VERSION "dev138" #define DIV_VERSION "dev139"
#define DIV_ENGINE_VERSION 138 #define DIV_ENGINE_VERSION 139
// for imports // for imports
#define DIV_VERSION_MOD 0xff01 #define DIV_VERSION_MOD 0xff01
#define DIV_VERSION_FC 0xff02 #define DIV_VERSION_FC 0xff02
@ -337,7 +337,6 @@ class DivEngine {
bool playing; bool playing;
bool freelance; bool freelance;
bool shallStop, shallStopSched; bool shallStop, shallStopSched;
bool speedAB;
bool endOfSong; bool endOfSong;
bool consoleMode; bool consoleMode;
bool extValuePresent; bool extValuePresent;
@ -359,7 +358,7 @@ class DivEngine {
bool midiOutClock; bool midiOutClock;
int midiOutMode; int midiOutMode;
int softLockCount; int softLockCount;
int subticks, ticks, curRow, curOrder, prevRow, prevOrder, remainingLoops, totalLoops, lastLoopPos, exportLoopCount, nextSpeed, elapsedBars, elapsedBeats; int subticks, ticks, curRow, curOrder, prevRow, prevOrder, remainingLoops, totalLoops, lastLoopPos, exportLoopCount, nextSpeed, elapsedBars, elapsedBeats, curSpeed;
size_t curSubSongIndex; size_t curSubSongIndex;
size_t bufferPos; size_t bufferPos;
double divider; double divider;
@ -368,7 +367,7 @@ class DivEngine {
int stepPlay; int stepPlay;
int changeOrd, changePos, totalSeconds, totalTicks, totalTicksR, totalCmds, lastCmds, cmdsPerSecond, globalPitch; int changeOrd, changePos, totalSeconds, totalTicks, totalTicksR, totalCmds, lastCmds, cmdsPerSecond, globalPitch;
unsigned char extValue, pendingMetroTick; unsigned char extValue, pendingMetroTick;
unsigned char speed1, speed2; DivGroovePattern speeds;
short tempoAccum; short tempoAccum;
DivStatusView view; DivStatusView view;
DivHaltPositions haltOn; DivHaltPositions haltOn;
@ -391,9 +390,9 @@ class DivEngine {
std::vector<String> midiOuts; std::vector<String> midiOuts;
std::vector<DivCommand> cmdStream; std::vector<DivCommand> cmdStream;
std::vector<DivInstrumentType> possibleInsTypes; std::vector<DivInstrumentType> possibleInsTypes;
static DivSysDef* sysDefs[256]; static DivSysDef* sysDefs[DIV_MAX_CHIP_DEFS];
static DivSystem sysFileMapFur[256]; static DivSystem sysFileMapFur[DIV_MAX_CHIP_DEFS];
static DivSystem sysFileMapDMF[256]; static DivSystem sysFileMapDMF[DIV_MAX_CHIP_DEFS];
struct SamplePreview { struct SamplePreview {
double rate; double rate;
@ -730,11 +729,8 @@ class DivEngine {
// get current subsong // get current subsong
size_t getCurrentSubSong(); size_t getCurrentSubSong();
// get speed 1 // get speeds
unsigned char getSpeed1(); const DivGroovePattern& getSpeeds();
// get speed 2
unsigned char getSpeed2();
// get Hz // get Hz
float getHz(); float getHz();
@ -1065,7 +1061,6 @@ class DivEngine {
freelance(false), freelance(false),
shallStop(false), shallStop(false),
shallStopSched(false), shallStopSched(false),
speedAB(false),
endOfSong(false), endOfSong(false),
consoleMode(false), consoleMode(false),
extValuePresent(false), extValuePresent(false),
@ -1099,6 +1094,7 @@ class DivEngine {
nextSpeed(3), nextSpeed(3),
elapsedBars(0), elapsedBars(0),
elapsedBeats(0), elapsedBeats(0),
curSpeed(0),
curSubSongIndex(0), curSubSongIndex(0),
bufferPos(0), bufferPos(0),
divider(60), divider(60),
@ -1116,8 +1112,6 @@ class DivEngine {
globalPitch(0), globalPitch(0),
extValue(0), extValue(0),
pendingMetroTick(0), pendingMetroTick(0),
speed1(3),
speed2(3),
tempoAccum(0), tempoAccum(0),
view(DIV_STATUS_NOTHING), view(DIV_STATUS_NOTHING),
haltOn(DIV_HALT_NONE), haltOn(DIV_HALT_NONE),
@ -1162,11 +1156,11 @@ class DivEngine {
memset(tremTable,0,128*sizeof(short)); memset(tremTable,0,128*sizeof(short));
memset(reversePitchTable,0,4096*sizeof(int)); memset(reversePitchTable,0,4096*sizeof(int));
memset(pitchTable,0,4096*sizeof(int)); memset(pitchTable,0,4096*sizeof(int));
memset(sysDefs,0,256*sizeof(void*)); memset(sysDefs,0,DIV_MAX_CHIP_DEFS*sizeof(void*));
memset(walked,0,8192); memset(walked,0,8192);
memset(oscBuf,0,DIV_MAX_OUTPUTS*(sizeof(float*))); memset(oscBuf,0,DIV_MAX_OUTPUTS*(sizeof(float*)));
for (int i=0; i<256; i++) { for (int i=0; i<DIV_MAX_CHIP_DEFS; i++) {
sysFileMapFur[i]=DIV_SYSTEM_NULL; sysFileMapFur[i]=DIV_SYSTEM_NULL;
sysFileMapDMF[i]=DIV_SYSTEM_NULL; sysFileMapDMF[i]=DIV_SYSTEM_NULL;
} }

View file

@ -83,7 +83,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
} }
ds.version=(unsigned char)reader.readC(); ds.version=(unsigned char)reader.readC();
logI("module version %d (0x%.2x)",ds.version,ds.version); logI("module version %d (0x%.2x)",ds.version,ds.version);
if (ds.version>0x1a) { if (ds.version>0x1b) {
logE("this version is not supported by Furnace yet!"); logE("this version is not supported by Furnace yet!");
lastError="this version is not supported by Furnace yet"; lastError="this version is not supported by Furnace yet";
delete[] file; delete[] file;
@ -219,14 +219,15 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
} }
ds.subsong[0]->timeBase=reader.readC(); ds.subsong[0]->timeBase=reader.readC();
ds.subsong[0]->speed1=reader.readC(); ds.subsong[0]->speeds.len=2;
ds.subsong[0]->speeds.val[0]=reader.readC();
if (ds.version>0x07) { if (ds.version>0x07) {
ds.subsong[0]->speed2=reader.readC(); ds.subsong[0]->speeds.val[1]=reader.readC();
ds.subsong[0]->pal=reader.readC(); ds.subsong[0]->pal=reader.readC();
ds.subsong[0]->hz=(ds.subsong[0]->pal)?60:50; ds.subsong[0]->hz=(ds.subsong[0]->pal)?60:50;
ds.subsong[0]->customTempo=reader.readC(); ds.subsong[0]->customTempo=reader.readC();
} else { } else {
ds.subsong[0]->speed2=ds.subsong[0]->speed1; ds.subsong[0]->speeds.len=1;
} }
if (ds.version>0x0a) { if (ds.version>0x0a) {
String hz=reader.readString(3); String hz=reader.readString(3);
@ -827,6 +828,8 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
for (int i=0; i<ds.sampleLen; i++) { for (int i=0; i<ds.sampleLen; i++) {
DivSample* sample=new DivSample; DivSample* sample=new DivSample;
int length=reader.readI(); int length=reader.readI();
int cutStart=0;
int cutEnd=length;
int pitch=5; int pitch=5;
int vol=50; int vol=50;
short* data; short* data;
@ -866,6 +869,29 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
sample->depth=DIV_SAMPLE_DEPTH_YMZ_ADPCM; sample->depth=DIV_SAMPLE_DEPTH_YMZ_ADPCM;
} }
} }
if (ds.version>=0x1a) {
// what the hell man...
cutStart=reader.readI();
cutEnd=reader.readI();
if (cutStart<0 || cutStart>length) {
logE("cutStart is out of range! (%d)",cutStart);
lastError="file is corrupt or unreadable at samples";
delete[] file;
return false;
}
if (cutEnd<0 || cutEnd>length) {
logE("cutEnd is out of range! (%d)",cutEnd);
lastError="file is corrupt or unreadable at samples";
delete[] file;
return false;
}
if (cutEnd<cutStart) {
logE("cutEnd %d is before cutStart %d. what's going on?",cutEnd,cutStart);
lastError="file is corrupt or unreadable at samples";
delete[] file;
return false;
}
}
if (length>0) { if (length>0) {
if (ds.version>0x08) { if (ds.version>0x08) {
if (ds.version<0x0b) { if (ds.version<0x0b) {
@ -877,6 +903,19 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
reader.read(data,length*2); reader.read(data,length*2);
} }
if (ds.version>0x1a) {
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
for (int pos=0; pos<length; pos++) { for (int pos=0; pos<length; pos++) {
@ -1742,8 +1781,9 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
reader.readI(); reader.readI();
subSong->timeBase=reader.readC(); subSong->timeBase=reader.readC();
subSong->speed1=reader.readC(); subSong->speeds.len=2;
subSong->speed2=reader.readC(); subSong->speeds.val[0]=reader.readC();
subSong->speeds.val[1]=reader.readC();
subSong->arpLen=reader.readC(); subSong->arpLen=reader.readC();
subSong->hz=reader.readF(); subSong->hz=reader.readF();
subSong->pal=(subSong->hz>=53); subSong->pal=(subSong->hz>=53);
@ -2231,6 +2271,25 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
} }
} }
if (ds.version>=139) {
subSong->speeds.len=reader.readC();
for (int i=0; i<16; i++) {
subSong->speeds.val[i]=reader.readC();
}
// grooves
unsigned char grooveCount=reader.readC();
for (int i=0; i<grooveCount; i++) {
DivGroovePattern gp;
gp.len=reader.readC();
for (int j=0; j<16; j++) {
gp.val[j]=reader.readC();
}
ds.grooves.push_back(gp);
}
}
// read system flags // read system flags
if (ds.version>=119) { if (ds.version>=119) {
logD("reading chip flags..."); logD("reading chip flags...");
@ -2289,8 +2348,9 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
subSong=ds.subsong[i+1]; subSong=ds.subsong[i+1];
subSong->timeBase=reader.readC(); subSong->timeBase=reader.readC();
subSong->speed1=reader.readC(); subSong->speeds.len=2;
subSong->speed2=reader.readC(); subSong->speeds.val[0]=reader.readC();
subSong->speeds.val[1]=reader.readC();
subSong->arpLen=reader.readC(); subSong->arpLen=reader.readC();
subSong->hz=reader.readF(); subSong->hz=reader.readF();
subSong->pal=(subSong->hz>=53); subSong->pal=(subSong->hz>=53);
@ -2338,6 +2398,13 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
for (int i=0; i<tchans; i++) { for (int i=0; i<tchans; i++) {
subSong->chanShortName[i]=reader.readString(); subSong->chanShortName[i]=reader.readString();
} }
if (ds.version>=139) {
subSong->speeds.len=reader.readC();
for (int i=0; i<16; i++) {
subSong->speeds.val[i]=reader.readC();
}
}
} }
} }
@ -2956,7 +3023,6 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) {
if (fxVal>0x20 && ds.name!="klisje paa klisje") { if (fxVal>0x20 && ds.name!="klisje paa klisje") {
writeFxCol(0xf0,fxVal); writeFxCol(0xf0,fxVal);
} else { } else {
writeFxCol(0x09,fxVal);
writeFxCol(0x0f,fxVal); writeFxCol(0x0f,fxVal);
} }
break; break;
@ -3435,8 +3501,8 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) {
ds.subsong[0]->pal=true; ds.subsong[0]->pal=true;
ds.subsong[0]->customTempo=true; ds.subsong[0]->customTempo=true;
ds.subsong[0]->pat[3].effectCols=3; ds.subsong[0]->pat[3].effectCols=3;
ds.subsong[0]->speed1=3; ds.subsong[0]->speeds.val[0]=3;
ds.subsong[0]->speed2=3; ds.subsong[0]->speeds.len=1;
int lastIns[4]; int lastIns[4];
int lastNote[4]; int lastNote[4];
@ -3453,10 +3519,8 @@ bool DivEngine::loadFC(unsigned char* file, size_t len) {
ds.subsong[0]->orders.ord[j][i]=i; ds.subsong[0]->orders.ord[j][i]=i;
DivPattern* p=ds.subsong[0]->pat[j].getPattern(i,true); DivPattern* p=ds.subsong[0]->pat[j].getPattern(i,true);
if (j==3 && seq[i].speed) { if (j==3 && seq[i].speed) {
p->data[0][6]=0x09; p->data[0][6]=0x0f;
p->data[0][7]=seq[i].speed; p->data[0][7]=seq[i].speed;
p->data[0][8]=0x0f;
p->data[0][9]=seq[i].speed;
} }
bool ignoreNext=false; bool ignoreNext=false;
@ -4343,8 +4407,9 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
w->writeI(0); w->writeI(0);
w->writeC(subSong->timeBase); w->writeC(subSong->timeBase);
w->writeC(subSong->speed1); // these are for compatibility
w->writeC(subSong->speed2); w->writeC(subSong->speeds.val[0]);
w->writeC((subSong->speeds.len>=2)?subSong->speeds.val[1]:subSong->speeds.val[0]);
w->writeC(subSong->arpLen); w->writeC(subSong->arpLen);
w->writeF(subSong->hz); w->writeF(subSong->hz);
w->writeS(subSong->patLen); w->writeS(subSong->patLen);
@ -4531,6 +4596,21 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
w->writeC(0); w->writeC(0);
} }
// speeds of first song
w->writeC(subSong->speeds.len);
for (int i=0; i<16; i++) {
w->writeC(subSong->speeds.val[i]);
}
// groove list
w->writeC((unsigned char)song.grooves.size());
for (const DivGroovePattern& i: song.grooves) {
w->writeC(i.len);
for (int j=0; j<16; j++) {
w->writeC(i.val[j]);
}
}
blockEndSeek=w->tell(); blockEndSeek=w->tell();
w->seek(blockStartSeek,SEEK_SET); w->seek(blockStartSeek,SEEK_SET);
w->writeI(blockEndSeek-blockStartSeek-4); w->writeI(blockEndSeek-blockStartSeek-4);
@ -4545,8 +4625,8 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
w->writeI(0); w->writeI(0);
w->writeC(subSong->timeBase); w->writeC(subSong->timeBase);
w->writeC(subSong->speed1); w->writeC(subSong->speeds.val[0]);
w->writeC(subSong->speed2); w->writeC((subSong->speeds.len>=2)?subSong->speeds.val[1]:subSong->speeds.val[0]);
w->writeC(subSong->arpLen); w->writeC(subSong->arpLen);
w->writeF(subSong->hz); w->writeF(subSong->hz);
w->writeS(subSong->patLen); w->writeS(subSong->patLen);
@ -4585,6 +4665,12 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
w->writeString(subSong->chanShortName[i],false); w->writeString(subSong->chanShortName[i],false);
} }
// speeds
w->writeC(subSong->speeds.len);
for (int i=0; i<16; i++) {
w->writeC(subSong->speeds.val[i]);
}
blockEndSeek=w->tell(); blockEndSeek=w->tell();
w->seek(blockStartSeek,SEEK_SET); w->seek(blockStartSeek,SEEK_SET);
w->writeI(blockEndSeek-blockStartSeek-4); w->writeI(blockEndSeek-blockStartSeek-4);
@ -4840,8 +4926,8 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) {
w->writeC(curSubSong->hilightB); w->writeC(curSubSong->hilightB);
w->writeC(curSubSong->timeBase); w->writeC(curSubSong->timeBase);
w->writeC(curSubSong->speed1); w->writeC(curSubSong->speeds.val[0]);
w->writeC(curSubSong->speed2); w->writeC((curSubSong->speeds.len>=2)?curSubSong->speeds.val[1]:curSubSong->speeds.val[0]);
w->writeC(curSubSong->pal); w->writeC(curSubSong->pal);
w->writeC(curSubSong->customTempo); w->writeC(curSubSong->customTempo);
char customHz[4]; char customHz[4];
@ -4865,6 +4951,14 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) {
addWarning("only the currently selected subsong will be saved"); addWarning("only the currently selected subsong will be saved");
} }
if (!song.grooves.empty()) {
addWarning("grooves will not be saved");
}
if (curSubSong->speeds.len>2) {
addWarning("only the first two speeds will be effective");
}
if (curSubSong->virtualTempoD!=curSubSong->virtualTempoN) { if (curSubSong->virtualTempoD!=curSubSong->virtualTempoN) {
addWarning(".dmf format does not support virtual tempo"); addWarning(".dmf format does not support virtual tempo");
} }

View file

@ -154,6 +154,8 @@ class DivPlatformOPN: public DivPlatformFMBase {
unsigned int ayDiv; unsigned int ayDiv;
unsigned char csmChan; unsigned char csmChan;
unsigned char lfoValue; unsigned char lfoValue;
unsigned short ssgVol;
unsigned short fmVol;
bool extSys, useCombo, fbAllOps; bool extSys, useCombo, fbAllOps;
DivConfig ayFlags; DivConfig ayFlags;
@ -172,6 +174,8 @@ class DivPlatformOPN: public DivPlatformFMBase {
ayDiv(a), ayDiv(a),
csmChan(cc), csmChan(cc),
lfoValue(0), lfoValue(0),
ssgVol(128),
fmVol(256),
extSys(isExtSys), extSys(isExtSys),
useCombo(false), useCombo(false),
fbAllOps(false) {} fbAllOps(false) {}

View file

@ -30,17 +30,26 @@
void DivYM2612Interface::ymfm_set_timer(uint32_t tnum, int32_t duration_in_clocks) { void DivYM2612Interface::ymfm_set_timer(uint32_t tnum, int32_t duration_in_clocks) {
if (tnum==1) { if (tnum==1) {
countB=duration_in_clocks; setB=duration_in_clocks;
} else if (tnum==0) { } else if (tnum==0) {
countA=duration_in_clocks; setA=duration_in_clocks;
} }
//logV("ymfm_set_timer(%d,%d)",tnum,duration_in_clocks);
} }
void DivYM2612Interface::clock() { void DivYM2612Interface::clock() {
if (countA>=0) { if (setA>=0) {
countA-=144; countA-=144;
if (countA<0) m_engine->engine_timer_expired(0); if (countA<0) {
m_engine->engine_timer_expired(0);
countA+=setA;
}
}
if (setB>=0) {
countB-=144;
if (countB<0) {
m_engine->engine_timer_expired(1);
countB+=setB;
}
} }
} }

View file

@ -25,6 +25,7 @@
class DivYM2612Interface: public ymfm::ymfm_interface { class DivYM2612Interface: public ymfm::ymfm_interface {
int setA, setB;
int countA, countB; int countA, countB;
public: public:
@ -32,8 +33,8 @@ class DivYM2612Interface: public ymfm::ymfm_interface {
void ymfm_set_timer(uint32_t tnum, int32_t duration_in_clocks); void ymfm_set_timer(uint32_t tnum, int32_t duration_in_clocks);
DivYM2612Interface(): DivYM2612Interface():
ymfm::ymfm_interface(), ymfm::ymfm_interface(),
countA(-1), countA(0),
countB(-1) {} countB(0) {}
}; };
class DivPlatformGenesis: public DivPlatformOPN { class DivPlatformGenesis: public DivPlatformOPN {

View file

@ -589,6 +589,7 @@ void DivPlatformGenesisExt::tick(bool sysTick) {
if (opChan[i].freq>0x3fff) opChan[i].freq=0x3fff; if (opChan[i].freq>0x3fff) opChan[i].freq=0x3fff;
immWrite(opChanOffsH[i],opChan[i].freq>>8); immWrite(opChanOffsH[i],opChan[i].freq>>8);
immWrite(opChanOffsL[i],opChan[i].freq&0xff); immWrite(opChanOffsL[i],opChan[i].freq&0xff);
opChan[i].freqChanged=false;
} }
writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i); writeMask|=(unsigned char)(opChan[i].mask && opChan[i].active)<<(4+i);
if (opChan[i].keyOn) { if (opChan[i].keyOn) {

View file

@ -431,6 +431,12 @@ bool fm_operator<RegisterType>::prepare()
// clock the key state // clock the key state
clock_keystate(uint32_t(m_keyon_live != 0)); clock_keystate(uint32_t(m_keyon_live != 0));
if (m_keyon_live & (1<<KEYON_CSM)) {
if (!(m_keyon_live & (1<<KEYON_NORMAL))) {
clock_keystate(0);
} else {
}
}
m_keyon_live &= ~(1 << KEYON_CSM); m_keyon_live &= ~(1 << KEYON_CSM);
// we're active until we're quiet after the release // we're active until we're quiet after the release

View file

@ -135,22 +135,27 @@ void DivPlatformTIA::tick(bool sysTick) {
int bf=chan[i].baseFreq; int bf=chan[i].baseFreq;
if (!parent->song.oldArpStrategy) { if (!parent->song.oldArpStrategy) {
if (!chan[i].fixedArp) { if (!chan[i].fixedArp) {
bf+=chan[i].arpOff; bf+=chan[i].arpOff<<8;
} }
} }
chan[i].freq=dealWithFreq(chan[i].shape,bf,chan[i].pitch+chan[i].pitch2); if (chan[i].fixedArp) {
if ((chan[i].shape==4 || chan[i].shape==5) && !(chan[i].baseFreq&0x80000000 && ((chan[i].baseFreq&0x7fffffff)<32))) { chan[i].freq=chan[i].baseNoteOverride&31;
if (bf<39*256) { } else {
rWrite(0x15+i,6); chan[i].freq=dealWithFreq(chan[i].shape,bf,chan[i].pitch+chan[i].pitch2);
chan[i].freq=dealWithFreq(6,bf,chan[i].pitch+chan[i].pitch2); if ((chan[i].shape==4 || chan[i].shape==5) && !(chan[i].baseFreq&0x80000000 && ((chan[i].baseFreq&0x7fffffff)<32))) {
} else if (bf<59*256) { if (bf<39*256) {
rWrite(0x15+i,12); rWrite(0x15+i,6);
chan[i].freq=dealWithFreq(12,bf,chan[i].pitch+chan[i].pitch2); chan[i].freq=dealWithFreq(6,bf,chan[i].pitch+chan[i].pitch2);
} else { } else if (bf<59*256) {
rWrite(0x15+i,chan[i].shape); rWrite(0x15+i,12);
chan[i].freq=dealWithFreq(12,bf,chan[i].pitch+chan[i].pitch2);
} else {
rWrite(0x15+i,chan[i].shape);
}
} }
if (chan[i].freq>31) chan[i].freq=31;
} }
if (chan[i].freq>31) chan[i].freq=31;
if (chan[i].keyOff) { if (chan[i].keyOff) {
rWrite(0x19+i,0); rWrite(0x19+i,0);
} }

View file

@ -210,11 +210,12 @@ void DivPlatformYM2203::acquire_combo(short** buf, size_t len) {
); );
os&=~3; os&=~3;
os=(os*fmVol)>>8;
// ymfm part // ymfm part
fm->generate(&fmout); fm->generate(&fmout);
os+=((fmout.data[1]+fmout.data[2]+fmout.data[3])>>1); os+=((fmout.data[1]+fmout.data[2]+fmout.data[3])*ssgVol)>>8;
if (os<-32768) os=-32768; if (os<-32768) os=-32768;
if (os>32767) os=32767; if (os>32767) os=32767;
@ -255,7 +256,7 @@ void DivPlatformYM2203::acquire_ymfm(short** buf, size_t len) {
fm->generate(&fmout); fm->generate(&fmout);
os=fmout.data[0]+((fmout.data[1]+fmout.data[2]+fmout.data[3])>>1); os=((fmout.data[0]*fmVol)>>8)+(((fmout.data[1]+fmout.data[2]+fmout.data[3])*ssgVol)>>8);
if (os<-32768) os=-32768; if (os<-32768) os=-32768;
if (os>32767) os=32767; if (os>32767) os=32767;
@ -1074,6 +1075,8 @@ void DivPlatformYM2203::setFlags(const DivConfig& flags) {
CHECK_CUSTOM_CLOCK; CHECK_CUSTOM_CLOCK;
noExtMacros=flags.getBool("noExtMacros",false); noExtMacros=flags.getBool("noExtMacros",false);
fbAllOps=flags.getBool("fbAllOps",false); fbAllOps=flags.getBool("fbAllOps",false);
ssgVol=flags.getInt("ssgVol",128);
fmVol=flags.getInt("fmVol",256);
rate=fm->sample_rate(chipClock); rate=fm->sample_rate(chipClock);
for (int i=0; i<6; i++) { for (int i=0; i<6; i++) {
oscBuf[i]->rate=rate; oscBuf[i]->rate=rate;

View file

@ -374,15 +374,17 @@ void DivPlatformYM2608::acquire_combo(short** buf, size_t len) {
os[0]>>=1; os[0]>>=1;
os[1]>>=1; os[1]>>=1;
os[0]=(os[0]*fmVol)>>8;
os[1]=(os[1]*fmVol)>>8;
// ymfm part // ymfm part
fm->generate(&fmout); fm->generate(&fmout);
os[0]+=fmout.data[0]+(fmout.data[2]>>1); os[0]+=((fmout.data[0]*fmVol)>>8)+((fmout.data[2]*ssgVol)>>8);
if (os[0]<-32768) os[0]=-32768; if (os[0]<-32768) os[0]=-32768;
if (os[0]>32767) os[0]=32767; if (os[0]>32767) os[0]=32767;
os[1]+=fmout.data[1]+(fmout.data[2]>>1); os[1]+=((fmout.data[1]*fmVol)>>8)+((fmout.data[2]*ssgVol)>>8);
if (os[1]<-32768) os[1]=-32768; if (os[1]<-32768) os[1]=-32768;
if (os[1]>32767) os[1]=32767; if (os[1]>32767) os[1]=32767;
@ -439,11 +441,11 @@ void DivPlatformYM2608::acquire_ymfm(short** buf, size_t len) {
fm->generate(&fmout); fm->generate(&fmout);
os[0]=fmout.data[0]+(fmout.data[2]>>1); os[0]=((fmout.data[0]*fmVol)>>8)+((fmout.data[2]*ssgVol)>>8);
if (os[0]<-32768) os[0]=-32768; if (os[0]<-32768) os[0]=-32768;
if (os[0]>32767) os[0]=32767; if (os[0]>32767) os[0]=32767;
os[1]=fmout.data[1]+(fmout.data[2]>>1); os[1]=((fmout.data[1]*fmVol)>>8)+((fmout.data[2]*ssgVol)>>8);
if (os[1]<-32768) os[1]=-32768; if (os[1]<-32768) os[1]=-32768;
if (os[1]>32767) os[1]=32767; if (os[1]>32767) os[1]=32767;
@ -1603,6 +1605,8 @@ void DivPlatformYM2608::setFlags(const DivConfig& flags) {
CHECK_CUSTOM_CLOCK; CHECK_CUSTOM_CLOCK;
noExtMacros=flags.getBool("noExtMacros",false); noExtMacros=flags.getBool("noExtMacros",false);
fbAllOps=flags.getBool("fbAllOps",false); fbAllOps=flags.getBool("fbAllOps",false);
ssgVol=flags.getInt("ssgVol",128);
fmVol=flags.getInt("fmVol",256);
rate=fm->sample_rate(chipClock); rate=fm->sample_rate(chipClock);
for (int i=0; i<16; i++) { for (int i=0; i<16; i++) {
oscBuf[i]->rate=rate; oscBuf[i]->rate=rate;

View file

@ -305,15 +305,17 @@ void DivPlatformYM2610::acquire_combo(short** buf, size_t len) {
os[0]>>=1; os[0]>>=1;
os[1]>>=1; os[1]>>=1;
os[0]=(os[0]*fmVol)>>8;
os[1]=(os[1]*fmVol)>>8;
// ymfm part // ymfm part
fm->generate(&fmout); fm->generate(&fmout);
os[0]+=fmout.data[0]+(fmout.data[2]>>1); os[0]+=((fmout.data[0]*fmVol)>>8)+((fmout.data[2]*ssgVol)>>8);
if (os[0]<-32768) os[0]=-32768; if (os[0]<-32768) os[0]=-32768;
if (os[0]>32767) os[0]=32767; if (os[0]>32767) os[0]=32767;
os[1]+=fmout.data[1]+(fmout.data[2]>>1); os[1]+=((fmout.data[1]*fmVol)>>8)+((fmout.data[2]*ssgVol)>>8);
if (os[1]<-32768) os[1]=-32768; if (os[1]<-32768) os[1]=-32768;
if (os[1]>32767) os[1]=32767; if (os[1]>32767) os[1]=32767;
@ -372,11 +374,11 @@ void DivPlatformYM2610::acquire_ymfm(short** buf, size_t len) {
fm->generate(&fmout); fm->generate(&fmout);
os[0]=fmout.data[0]+(fmout.data[2]>>1); os[0]+=((fmout.data[0]*fmVol)>>8)+((fmout.data[2]*ssgVol)>>8);
if (os[0]<-32768) os[0]=-32768; if (os[0]<-32768) os[0]=-32768;
if (os[0]>32767) os[0]=32767; if (os[0]>32767) os[0]=32767;
os[1]=fmout.data[1]+(fmout.data[2]>>1); os[1]+=((fmout.data[1]*fmVol)>>8)+((fmout.data[2]*ssgVol)>>8);
if (os[1]<-32768) os[1]=-32768; if (os[1]<-32768) os[1]=-32768;
if (os[1]>32767) os[1]=32767; if (os[1]>32767) os[1]=32767;

View file

@ -373,15 +373,17 @@ void DivPlatformYM2610B::acquire_combo(short** buf, size_t len) {
os[0]>>=1; os[0]>>=1;
os[1]>>=1; os[1]>>=1;
os[0]=(os[0]*fmVol)>>8;
os[1]=(os[1]*fmVol)>>8;
// ymfm part // ymfm part
fm->generate(&fmout); fm->generate(&fmout);
os[0]+=fmout.data[0]+(fmout.data[2]>>1); os[0]+=((fmout.data[0]*fmVol)>>8)+((fmout.data[2]*ssgVol)>>8);
if (os[0]<-32768) os[0]=-32768; if (os[0]<-32768) os[0]=-32768;
if (os[0]>32767) os[0]=32767; if (os[0]>32767) os[0]=32767;
os[1]+=fmout.data[1]+(fmout.data[2]>>1); os[1]+=((fmout.data[1]*fmVol)>>8)+((fmout.data[2]*ssgVol)>>8);
if (os[1]<-32768) os[1]=-32768; if (os[1]<-32768) os[1]=-32768;
if (os[1]>32767) os[1]=32767; if (os[1]>32767) os[1]=32767;
@ -438,11 +440,11 @@ void DivPlatformYM2610B::acquire_ymfm(short** buf, size_t len) {
fm->generate(&fmout); fm->generate(&fmout);
os[0]=fmout.data[0]+(fmout.data[2]>>1); os[0]+=((fmout.data[0]*fmVol)>>8)+((fmout.data[2]*ssgVol)>>8);
if (os[0]<-32768) os[0]=-32768; if (os[0]<-32768) os[0]=-32768;
if (os[0]>32767) os[0]=32767; if (os[0]>32767) os[0]=32767;
os[1]=fmout.data[1]+(fmout.data[2]>>1); os[1]+=((fmout.data[1]*fmVol)>>8)+((fmout.data[2]*ssgVol)>>8);
if (os[1]<-32768) os[1]=-32768; if (os[1]<-32768) os[1]=-32768;
if (os[1]>32767) os[1]=32767; if (os[1]>32767) os[1]=32767;

View file

@ -222,6 +222,8 @@ class DivPlatformYM2610Base: public DivPlatformOPN {
CHECK_CUSTOM_CLOCK; CHECK_CUSTOM_CLOCK;
noExtMacros=flags.getBool("noExtMacros",false); noExtMacros=flags.getBool("noExtMacros",false);
fbAllOps=flags.getBool("fbAllOps",false); fbAllOps=flags.getBool("fbAllOps",false);
ssgVol=flags.getInt("ssgVol",128);
fmVol=flags.getInt("fmVol",256);
rate=fm->sample_rate(chipClock); rate=fm->sample_rate(chipClock);
for (int i=0; i<16; i++) { for (int i=0; i<16; i++) {
oscBuf[i]->rate=rate; oscBuf[i]->rate=rate;

View file

@ -412,11 +412,22 @@ void DivEngine::processRow(int i, bool afterDelay) {
if (effectVal==-1) effectVal=0; if (effectVal==-1) effectVal=0;
switch (effect) { switch (effect) {
case 0x09: // speed 1 case 0x09: // select groove pattern/speed 1
if (effectVal>0) speed1=effectVal; if (song.grooves.empty()) {
if (effectVal>0) speeds.val[0]=effectVal;
} else {
if (effectVal<(short)song.grooves.size()) {
speeds=song.grooves[effectVal];
curSpeed=0;
}
}
break; break;
case 0x0f: // speed 2 case 0x0f: // speed 1/speed 2
if (effectVal>0) speed2=effectVal; if (speeds.len==2 && song.grooves.empty()) {
if (effectVal>0) speeds.val[1]=effectVal;
} else {
if (effectVal>0) speeds.val[0]=effectVal;
}
break; break;
case 0x0b: // change order case 0x0b: // change order
if (changeOrd==-1 || song.jumpTreatment==0) { if (changeOrd==-1 || song.jumpTreatment==0) {
@ -1083,6 +1094,9 @@ void DivEngine::nextRow() {
} }
if (song.brokenSpeedSel) { if (song.brokenSpeedSel) {
unsigned char speed2=(speeds.len>=2)?speeds.val[1]:speeds.val[0];
unsigned char speed1=speeds.val[0];
if ((curSubSong->patLen&1) && curOrder&1) { if ((curSubSong->patLen&1) && curOrder&1) {
ticks=((curRow&1)?speed2:speed1)*(curSubSong->timeBase+1); ticks=((curRow&1)?speed2:speed1)*(curSubSong->timeBase+1);
nextSpeed=(curRow&1)?speed1:speed2; nextSpeed=(curRow&1)?speed1:speed2;
@ -1091,14 +1105,10 @@ void DivEngine::nextRow() {
nextSpeed=(curRow&1)?speed2:speed1; nextSpeed=(curRow&1)?speed2:speed1;
} }
} else { } else {
if (speedAB) { ticks=speeds.val[curSpeed]*(curSubSong->timeBase+1);
ticks=speed2*(curSubSong->timeBase+1); curSpeed++;
nextSpeed=speed1; if (curSpeed>=speeds.len) curSpeed=0;
} else { nextSpeed=speeds.val[curSpeed];
ticks=speed1*(curSubSong->timeBase+1);
nextSpeed=speed2;
}
speedAB=!speedAB;
} }
// post row details // post row details

View file

@ -127,10 +127,20 @@ enum DivSystem {
DIV_SYSTEM_YM2608_CSM DIV_SYSTEM_YM2608_CSM
}; };
struct DivGroovePattern {
unsigned char val[16];
unsigned char len;
DivGroovePattern():
len(1) {
memset(val,6,16);
}
};
struct DivSubSong { struct DivSubSong {
String name, notes; String name, notes;
unsigned char hilightA, hilightB; unsigned char hilightA, hilightB;
unsigned char timeBase, speed1, speed2, arpLen; unsigned char timeBase, arpLen;
DivGroovePattern speeds;
short virtualTempoN, virtualTempoD; short virtualTempoN, virtualTempoD;
bool pal; bool pal;
bool customTempo; bool customTempo;
@ -153,8 +163,6 @@ struct DivSubSong {
hilightA(4), hilightA(4),
hilightB(16), hilightB(16),
timeBase(0), timeBase(0),
speed1(6),
speed2(6),
arpLen(1), arpLen(1),
virtualTempoN(150), virtualTempoN(150),
virtualTempoD(150), virtualTempoD(150),
@ -338,6 +346,7 @@ struct DivSong {
std::vector<DivSubSong*> subsong; std::vector<DivSubSong*> subsong;
std::vector<unsigned int> patchbay; std::vector<unsigned int> patchbay;
std::vector<DivGroovePattern> grooves;
DivInstrument nullIns, nullInsOPLL, nullInsOPL, nullInsOPLDrums, nullInsQSound; DivInstrument nullIns, nullInsOPLL, nullInsOPL, nullInsOPLDrums, nullInsQSound;
DivWavetable nullWave; DivWavetable nullWave;

View file

@ -23,9 +23,9 @@
#include "song.h" #include "song.h"
#include "../ta-log.h" #include "../ta-log.h"
DivSysDef* DivEngine::sysDefs[256]; DivSysDef* DivEngine::sysDefs[DIV_MAX_CHIP_DEFS];
DivSystem DivEngine::sysFileMapFur[256]; DivSystem DivEngine::sysFileMapFur[DIV_MAX_CHIP_DEFS];
DivSystem DivEngine::sysFileMapDMF[256]; DivSystem DivEngine::sysFileMapDMF[DIV_MAX_CHIP_DEFS];
DivSystem DivEngine::systemFromFileFur(unsigned char val) { DivSystem DivEngine::systemFromFileFur(unsigned char val) {
return sysFileMapFur[val]; return sysFileMapFur[val];
@ -1831,7 +1831,7 @@ void DivEngine::registerSystems() {
{DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD} {DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD}
); );
for (int i=0; i<256; i++) { for (int i=0; i<DIV_MAX_CHIP_DEFS; i++) {
if (sysDefs[i]==NULL) continue; if (sysDefs[i]==NULL) continue;
if (sysDefs[i]->id!=0) { if (sysDefs[i]->id!=0) {
sysFileMapFur[sysDefs[i]->id]=(DivSystem)i; sysFileMapFur[sysDefs[i]->id]=(DivSystem)i;

View file

@ -265,6 +265,9 @@ void FurnaceGUI::doAction(int what) {
case GUI_ACTION_WINDOW_FIND: case GUI_ACTION_WINDOW_FIND:
nextWindow=GUI_WINDOW_FIND; nextWindow=GUI_WINDOW_FIND;
break; break;
case GUI_ACTION_WINDOW_GROOVES:
nextWindow=GUI_WINDOW_GROOVES;
break;
case GUI_ACTION_COLLAPSE_WINDOW: case GUI_ACTION_COLLAPSE_WINDOW:
collapseWindow=true; collapseWindow=true;
@ -358,6 +361,9 @@ void FurnaceGUI::doAction(int what) {
case GUI_WINDOW_FIND: case GUI_WINDOW_FIND:
findOpen=false; findOpen=false;
break; break;
case GUI_WINDOW_GROOVES:
groovesOpen=false;
break;
default: default:
break; break;
} }

154
src/gui/grooves.cpp Normal file
View file

@ -0,0 +1,154 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2023 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 "gui.h"
#include "imgui.h"
#include "misc/cpp/imgui_stdlib.h"
#include "IconsFontAwesome4.h"
#include <fmt/printf.h>
#include "intConst.h"
void FurnaceGUI::drawGrooves() {
if (nextWindow==GUI_WINDOW_GROOVES) {
groovesOpen=true;
ImGui::SetNextWindowFocus();
nextWindow=GUI_WINDOW_NOTHING;
}
if (!groovesOpen) return;
ImGui::SetNextWindowSizeConstraints(ImVec2(64.0f*dpiScale,32.0f*dpiScale),ImVec2(canvasW,canvasH));
if (ImGui::Begin("Grooves",&groovesOpen,globalWinFlags)) {
int delGroove=-1;
ImGui::Text("use effect 09xx to select a groove pattern.");
if (!e->song.grooves.empty()) if (ImGui::BeginTable("GrooveList",3,ImGuiTableFlags_Borders)) {
ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed);
ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch);
ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthFixed);
ImGui::TableNextRow(ImGuiTableRowFlags_Headers);
ImGui::TableNextColumn();
ImGui::Text("#");
ImGui::TableNextColumn();
ImGui::Text("pattern");
ImGui::TableNextColumn();
ImGui::Text("remove");
int index=0;
for (DivGroovePattern& i: e->song.grooves) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::PushFont(patFont);
ImGui::Text("%.2X",index);
ImGui::PopFont();
ImGui::TableNextColumn();
String grooveStr;
if (curGroove==index) {
int intVersion[256];
unsigned char intVersionLen=i.len;
unsigned char ignoredLoop=0;
unsigned char ignoredRel=0;
memset(intVersion,0,sizeof(int));
for (int j=0; j<16; j++) {
intVersion[j]=i.val[j];
}
if (intVersionLen>16) intVersionLen=16;
grooveStr=fmt::sprintf("##_GRI%d",index);
bool wantedFocus=wantGrooveListFocus;
if (wantGrooveListFocus) {
wantGrooveListFocus=false;
ImGui::SetItemDefaultFocus();
ImGui::SetKeyboardFocusHere();
}
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
if (ImGui::InputText(grooveStr.c_str(),&grooveListString)) {
decodeMMLStr(grooveListString,intVersion,intVersionLen,ignoredLoop,1,255,ignoredRel);
if (intVersionLen<1) {
intVersionLen=1;
intVersion[0]=6;
}
if (intVersionLen>16) intVersionLen=16;
e->lockEngine([&i,intVersion,intVersionLen]() {
i.len=intVersionLen;
for (int j=0; j<16; j++) {
i.val[j]=intVersion[j];
}
});
MARK_MODIFIED;
}
if (!ImGui::IsItemActive() && !wantedFocus) {
curGroove=-1;
//encodeMMLStr(grooveListString,intVersion,intVersionLen,-1,-1,false);
}
} else {
String grooveStr;
for (int j=0; j<i.len; j++) {
if (j>0) {
grooveStr+=' ';
}
grooveStr+=fmt::sprintf("%d",(int)i.val[j]);
}
size_t groovePrevLen=grooveStr.size();
grooveStr+=fmt::sprintf("##_GR%d",index);
if (ImGui::Selectable(grooveStr.c_str(),false)) {
curGroove=index;
grooveListString=grooveStr.substr(0,groovePrevLen);
wantGrooveListFocus=true;
}
}
ImGui::TableNextColumn();
String grooveID=fmt::sprintf(ICON_FA_TIMES "##GRR%d",index);
if (ImGui::Button(grooveID.c_str())) {
delGroove=index;
}
index++;
}
ImGui::EndTable();
}
if (delGroove>=0) {
e->lockEngine([this,delGroove]() {
e->song.grooves.erase(e->song.grooves.begin()+delGroove);
});
MARK_MODIFIED;
}
if (ImGui::Button(ICON_FA_PLUS "##AddGroove")) {
e->lockEngine([this]() {
e->song.grooves.push_back(DivGroovePattern());
});
MARK_MODIFIED;
}
}
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) {
curWindow=GUI_WINDOW_GROOVES;
} else {
curGroove=-1;
}
ImGui::End();
}

View file

@ -988,15 +988,19 @@ void FurnaceGUI::prepareLayout() {
fclose(check); fclose(check);
} }
float FurnaceGUI::calcBPM(int s1, int s2, float hz, int vN, int vD) { float FurnaceGUI::calcBPM(const DivGroovePattern& speeds, float hz, int vN, int vD) {
float hl=e->curSubSong->hilightA; float hl=e->curSubSong->hilightA;
if (hl<=0.0f) hl=4.0f; if (hl<=0.0f) hl=4.0f;
float timeBase=e->curSubSong->timeBase+1; float timeBase=e->curSubSong->timeBase+1;
float speedSum=s1+s2; float speedSum=0;
for (int i=0; i<MIN(16,speeds.len); i++) {
speedSum+=speeds.val[i];
}
speedSum/=MAX(1,speeds.len);
if (timeBase<1.0f) timeBase=1.0f; if (timeBase<1.0f) timeBase=1.0f;
if (speedSum<1.0f) speedSum=1.0f; if (speedSum<1.0f) speedSum=1.0f;
if (vD<1) vD=1; if (vD<1) vD=1;
return (120.0f*hz/(timeBase*hl*speedSum))*(float)vN/(float)vD; return (60.0f*hz/(timeBase*hl*speedSum))*(float)vN/(float)vD;
} }
void FurnaceGUI::play(int row) { void FurnaceGUI::play(int row) {
@ -3740,6 +3744,7 @@ bool FurnaceGUI::loop() {
if (ImGui::MenuItem("orders",BIND_FOR(GUI_ACTION_WINDOW_ORDERS),ordersOpen)) ordersOpen=!ordersOpen; if (ImGui::MenuItem("orders",BIND_FOR(GUI_ACTION_WINDOW_ORDERS),ordersOpen)) ordersOpen=!ordersOpen;
if (ImGui::MenuItem("pattern",BIND_FOR(GUI_ACTION_WINDOW_PATTERN),patternOpen)) patternOpen=!patternOpen; if (ImGui::MenuItem("pattern",BIND_FOR(GUI_ACTION_WINDOW_PATTERN),patternOpen)) patternOpen=!patternOpen;
if (ImGui::MenuItem("mixer",BIND_FOR(GUI_ACTION_WINDOW_MIXER),mixerOpen)) mixerOpen=!mixerOpen; if (ImGui::MenuItem("mixer",BIND_FOR(GUI_ACTION_WINDOW_MIXER),mixerOpen)) mixerOpen=!mixerOpen;
if (ImGui::MenuItem("grooves",BIND_FOR(GUI_ACTION_WINDOW_GROOVES),groovesOpen)) groovesOpen=!groovesOpen;
if (ImGui::MenuItem("channels",BIND_FOR(GUI_ACTION_WINDOW_CHANNELS),channelsOpen)) channelsOpen=!channelsOpen; if (ImGui::MenuItem("channels",BIND_FOR(GUI_ACTION_WINDOW_CHANNELS),channelsOpen)) channelsOpen=!channelsOpen;
if (ImGui::MenuItem("pattern manager",BIND_FOR(GUI_ACTION_WINDOW_PAT_MANAGER),patManagerOpen)) patManagerOpen=!patManagerOpen; if (ImGui::MenuItem("pattern manager",BIND_FOR(GUI_ACTION_WINDOW_PAT_MANAGER),patManagerOpen)) patManagerOpen=!patManagerOpen;
if (ImGui::MenuItem("chip manager",BIND_FOR(GUI_ACTION_WINDOW_SYS_MANAGER),sysManagerOpen)) sysManagerOpen=!sysManagerOpen; if (ImGui::MenuItem("chip manager",BIND_FOR(GUI_ACTION_WINDOW_SYS_MANAGER),sysManagerOpen)) sysManagerOpen=!sysManagerOpen;
@ -3778,7 +3783,21 @@ bool FurnaceGUI::loop() {
if (e->isPlaying()) { if (e->isPlaying()) {
int totalTicks=e->getTotalTicks(); int totalTicks=e->getTotalTicks();
int totalSeconds=e->getTotalSeconds(); int totalSeconds=e->getTotalSeconds();
ImGui::Text("| Speed %d:%d @ %gHz (%g BPM) | Order %d/%d | Row %d/%d | %d:%.2d:%.2d.%.2d",e->getSpeed1(),e->getSpeed2(),e->getCurHz(),calcBPM(e->getSpeed1(),e->getSpeed2(),e->getCurHz(),e->curSubSong->virtualTempoN,e->curSubSong->virtualTempoD),e->getOrder(),e->curSubSong->ordersLen,e->getRow(),e->curSubSong->patLen,totalSeconds/3600,(totalSeconds/60)%60,totalSeconds%60,totalTicks/10000);
String info;
DivGroovePattern gp=e->getSpeeds();
if (gp.len==2) {
info=fmt::sprintf("| Speed %d:%d",gp.val[0],gp.val[1]);
} else if (gp.len==1) {
info=fmt::sprintf("| Speed %d",gp.val[0]);
} else {
info="| Groove";
}
info+=fmt::sprintf(" @ %gHz (%g BPM) | Order %d/%d | Row %d/%d | %d:%.2d:%.2d.%.2d",e->getCurHz(),calcBPM(e->getSpeeds(),e->getCurHz(),e->curSubSong->virtualTempoN,e->curSubSong->virtualTempoD),e->getOrder(),e->curSubSong->ordersLen,e->getRow(),e->curSubSong->patLen,totalSeconds/3600,(totalSeconds/60)%60,totalSeconds%60,totalTicks/10000);
ImGui::TextUnformatted(info.c_str());
} else { } else {
bool hasInfo=false; bool hasInfo=false;
String info; String info;
@ -3917,6 +3936,7 @@ bool FurnaceGUI::loop() {
drawPattern(); drawPattern();
drawEditControls(); drawEditControls();
drawSpeed(); drawSpeed();
drawGrooves();
drawSongInfo(); drawSongInfo();
drawOrders(); drawOrders();
drawSampleList(); drawSampleList();
@ -5248,6 +5268,7 @@ bool FurnaceGUI::init() {
sysManagerOpen=e->getConfBool("sysManagerOpen",false); sysManagerOpen=e->getConfBool("sysManagerOpen",false);
clockOpen=e->getConfBool("clockOpen",false); clockOpen=e->getConfBool("clockOpen",false);
speedOpen=e->getConfBool("speedOpen",true); speedOpen=e->getConfBool("speedOpen",true);
groovesOpen=e->getConfBool("groovesOpen",false);
regViewOpen=e->getConfBool("regViewOpen",false); regViewOpen=e->getConfBool("regViewOpen",false);
logOpen=e->getConfBool("logOpen",false); logOpen=e->getConfBool("logOpen",false);
effectListOpen=e->getConfBool("effectListOpen",false); effectListOpen=e->getConfBool("effectListOpen",false);
@ -5467,10 +5488,32 @@ bool FurnaceGUI::init() {
} }
#endif #endif
int numDrivers=SDL_GetNumRenderDrivers();
if (numDrivers<0) {
logW("could not list render drivers! %s",SDL_GetError());
} else {
SDL_RendererInfo ri;
for (int i=0; i<numDrivers; i++) {
int r=SDL_GetRenderDriverInfo(i,&ri);
if (r!=0) continue;
availRenderDrivers.push_back(String(ri.name));
}
}
if (!settings.renderDriver.empty()) {
SDL_SetHint(SDL_HINT_RENDER_DRIVER,settings.renderDriver.c_str());
}
sdlRend=SDL_CreateRenderer(sdlWin,-1,SDL_RENDERER_ACCELERATED|SDL_RENDERER_PRESENTVSYNC|SDL_RENDERER_TARGETTEXTURE); sdlRend=SDL_CreateRenderer(sdlWin,-1,SDL_RENDERER_ACCELERATED|SDL_RENDERER_PRESENTVSYNC|SDL_RENDERER_TARGETTEXTURE);
if (sdlRend==NULL) { if (sdlRend==NULL) {
lastError=fmt::sprintf("could not init renderer! %s",SDL_GetError()); lastError=fmt::sprintf("could not init renderer! %s",SDL_GetError());
if (!settings.renderDriver.empty()) {
settings.renderDriver="";
e->setConf("renderDriver","");
e->saveConf();
lastError=fmt::sprintf("\r\nthe render driver has been set to a safe value. please restart Furnace.");
}
return false; return false;
} }
@ -5623,6 +5666,7 @@ void FurnaceGUI::commitState() {
e->setConf("sysManagerOpen",sysManagerOpen); e->setConf("sysManagerOpen",sysManagerOpen);
e->setConf("clockOpen",clockOpen); e->setConf("clockOpen",clockOpen);
e->setConf("speedOpen",speedOpen); e->setConf("speedOpen",speedOpen);
e->setConf("groovesOpen",groovesOpen);
e->setConf("regViewOpen",regViewOpen); e->setConf("regViewOpen",regViewOpen);
e->setConf("logOpen",logOpen); e->setConf("logOpen",logOpen);
e->setConf("effectListOpen",effectListOpen); e->setConf("effectListOpen",effectListOpen);
@ -5835,6 +5879,7 @@ FurnaceGUI::FurnaceGUI():
dragDestinationY(0), dragDestinationY(0),
oldBeat(-1), oldBeat(-1),
oldBar(-1), oldBar(-1),
curGroove(-1),
soloTimeout(0.0f), soloTimeout(0.0f),
exportFadeOut(5.0), exportFadeOut(5.0),
editControlsOpen(true), editControlsOpen(true),
@ -5870,6 +5915,7 @@ FurnaceGUI::FurnaceGUI():
sysManagerOpen(false), sysManagerOpen(false),
clockOpen(false), clockOpen(false),
speedOpen(true), speedOpen(true),
groovesOpen(false),
clockShowReal(true), clockShowReal(true),
clockShowRow(true), clockShowRow(true),
clockShowBeat(true), clockShowBeat(true),
@ -5898,10 +5944,12 @@ FurnaceGUI::FurnaceGUI():
latchNibble(false), latchNibble(false),
nonLatchNibble(false), nonLatchNibble(false),
keepLoopAlive(false), keepLoopAlive(false),
keepGrooveAlive(false),
orderScrollLocked(false), orderScrollLocked(false),
orderScrollTolerance(false), orderScrollTolerance(false),
dragMobileMenu(false), dragMobileMenu(false),
dragMobileEditButton(false), dragMobileEditButton(false),
wantGrooveListFocus(false),
curWindow(GUI_WINDOW_NOTHING), curWindow(GUI_WINDOW_NOTHING),
nextWindow(GUI_WINDOW_NOTHING), nextWindow(GUI_WINDOW_NOTHING),
curWindowLast(GUI_WINDOW_NOTHING), curWindowLast(GUI_WINDOW_NOTHING),

View file

@ -113,6 +113,7 @@ enum FurnaceGUIColors {
GUI_COLOR_ORDER_ROW_INDEX, GUI_COLOR_ORDER_ROW_INDEX,
GUI_COLOR_ORDER_ACTIVE, GUI_COLOR_ORDER_ACTIVE,
GUI_COLOR_ORDER_SELECTED,
GUI_COLOR_ORDER_SIMILAR, GUI_COLOR_ORDER_SIMILAR,
GUI_COLOR_ORDER_INACTIVE, GUI_COLOR_ORDER_INACTIVE,
@ -321,6 +322,7 @@ enum FurnaceGUIWindows {
GUI_WINDOW_SUBSONGS, GUI_WINDOW_SUBSONGS,
GUI_WINDOW_FIND, GUI_WINDOW_FIND,
GUI_WINDOW_CLOCK, GUI_WINDOW_CLOCK,
GUI_WINDOW_GROOVES,
GUI_WINDOW_SPOILER GUI_WINDOW_SPOILER
}; };
@ -466,6 +468,7 @@ enum FurnaceGUIActions {
GUI_ACTION_WINDOW_SUBSONGS, GUI_ACTION_WINDOW_SUBSONGS,
GUI_ACTION_WINDOW_FIND, GUI_ACTION_WINDOW_FIND,
GUI_ACTION_WINDOW_CLOCK, GUI_ACTION_WINDOW_CLOCK,
GUI_ACTION_WINDOW_GROOVES,
GUI_ACTION_COLLAPSE_WINDOW, GUI_ACTION_COLLAPSE_WINDOW,
GUI_ACTION_CLOSE_WINDOW, GUI_ACTION_CLOSE_WINDOW,
@ -1097,13 +1100,13 @@ class FurnaceGUI {
String workingDirVGMExport, workingDirZSMExport, workingDirROMExport, workingDirFont, workingDirColors, workingDirKeybinds; String workingDirVGMExport, workingDirZSMExport, workingDirROMExport, workingDirFont, workingDirColors, workingDirKeybinds;
String workingDirLayout, workingDirROM, workingDirTest; String workingDirLayout, workingDirROM, workingDirTest;
String mmlString[32]; String mmlString[32];
String mmlStringW, mmlStringSNES; String mmlStringW, mmlStringSNES, grooveString, grooveListString;
std::vector<DivSystem> sysSearchResults; std::vector<DivSystem> sysSearchResults;
std::vector<FurnaceGUISysDef> newSongSearchResults; std::vector<FurnaceGUISysDef> newSongSearchResults;
std::deque<String> recentFile; std::deque<String> recentFile;
std::vector<DivInstrumentType> makeInsTypeList; std::vector<DivInstrumentType> makeInsTypeList;
std::vector<String> availRenderDrivers;
bool quit, warnQuit, willCommit, edit, modified, displayError, displayExporting, vgmExportLoop, zsmExportLoop, vgmExportPatternHints; bool quit, warnQuit, willCommit, edit, modified, displayError, displayExporting, vgmExportLoop, zsmExportLoop, vgmExportPatternHints;
bool vgmExportDirectStream, displayInsTypeList; bool vgmExportDirectStream, displayInsTypeList;
@ -1305,6 +1308,7 @@ class FurnaceGUI {
String midiInDevice; String midiInDevice;
String midiOutDevice; String midiOutDevice;
String c163Name; String c163Name;
String renderDriver;
String initialSysName; String initialSysName;
String noteOffLabel; String noteOffLabel;
String noteRelLabel; String noteRelLabel;
@ -1441,6 +1445,7 @@ class FurnaceGUI {
midiInDevice(""), midiInDevice(""),
midiOutDevice(""), midiOutDevice(""),
c163Name(""), c163Name(""),
renderDriver(""),
initialSysName("Sega Genesis/Mega Drive"), initialSysName("Sega Genesis/Mega Drive"),
noteOffLabel("OFF"), noteOffLabel("OFF"),
noteRelLabel("==="), noteRelLabel("==="),
@ -1456,6 +1461,7 @@ class FurnaceGUI {
int curIns, curWave, curSample, curOctave, curOrder, prevIns, oldRow, oldOrder, oldOrder1, editStep, exportLoops, soloChan,orderEditMode, orderCursor; int curIns, curWave, curSample, curOctave, curOrder, prevIns, oldRow, oldOrder, oldOrder1, editStep, exportLoops, soloChan,orderEditMode, orderCursor;
int loopOrder, loopRow, loopEnd, isClipping, extraChannelButtons, patNameTarget, newSongCategory, latchTarget; int loopOrder, loopRow, loopEnd, isClipping, extraChannelButtons, patNameTarget, newSongCategory, latchTarget;
int wheelX, wheelY, dragSourceX, dragSourceXFine, dragSourceY, dragDestinationX, dragDestinationXFine, dragDestinationY, oldBeat, oldBar; int wheelX, wheelY, dragSourceX, dragSourceXFine, dragSourceY, dragDestinationX, dragDestinationXFine, dragDestinationY, oldBeat, oldBar;
int curGroove;
float soloTimeout; float soloTimeout;
double exportFadeOut; double exportFadeOut;
@ -1465,6 +1471,7 @@ class FurnaceGUI {
bool mixerOpen, debugOpen, inspectorOpen, oscOpen, volMeterOpen, statsOpen, compatFlagsOpen; bool mixerOpen, debugOpen, inspectorOpen, oscOpen, volMeterOpen, statsOpen, compatFlagsOpen;
bool pianoOpen, notesOpen, channelsOpen, regViewOpen, logOpen, effectListOpen, chanOscOpen; bool pianoOpen, notesOpen, channelsOpen, regViewOpen, logOpen, effectListOpen, chanOscOpen;
bool subSongsOpen, findOpen, spoilerOpen, patManagerOpen, sysManagerOpen, clockOpen, speedOpen; bool subSongsOpen, findOpen, spoilerOpen, patManagerOpen, sysManagerOpen, clockOpen, speedOpen;
bool groovesOpen;
bool clockShowReal, clockShowRow, clockShowBeat, clockShowMetro, clockShowTime; bool clockShowReal, clockShowRow, clockShowBeat, clockShowMetro, clockShowTime;
float clockMetroTick[16]; float clockMetroTick[16];
@ -1472,7 +1479,7 @@ class FurnaceGUI {
SelectionPoint selStart, selEnd, cursor, cursorDrag, dragStart, dragEnd; SelectionPoint selStart, selEnd, cursor, cursorDrag, dragStart, dragEnd;
bool selecting, selectingFull, dragging, curNibble, orderNibble, followOrders, followPattern, changeAllOrders, mobileUI; bool selecting, selectingFull, dragging, curNibble, orderNibble, followOrders, followPattern, changeAllOrders, mobileUI;
bool collapseWindow, demandScrollX, fancyPattern, wantPatName, firstFrame, tempoView, waveHex, waveSigned, waveGenVisible, lockLayout, editOptsVisible, latchNibble, nonLatchNibble; bool collapseWindow, demandScrollX, fancyPattern, wantPatName, firstFrame, tempoView, waveHex, waveSigned, waveGenVisible, lockLayout, editOptsVisible, latchNibble, nonLatchNibble;
bool keepLoopAlive, orderScrollLocked, orderScrollTolerance, dragMobileMenu, dragMobileEditButton; bool keepLoopAlive, keepGrooveAlive, orderScrollLocked, orderScrollTolerance, dragMobileMenu, dragMobileEditButton, wantGrooveListFocus;
FurnaceGUIWindows curWindow, nextWindow, curWindowLast; FurnaceGUIWindows curWindow, nextWindow, curWindowLast;
std::atomic<FurnaceGUIWindows> curWindowThreadSafe; std::atomic<FurnaceGUIWindows> curWindowThreadSafe;
float peak[DIV_MAX_OUTPUTS]; float peak[DIV_MAX_OUTPUTS];
@ -1801,7 +1808,7 @@ class FurnaceGUI {
void pushAccentColors(const ImVec4& one, const ImVec4& two, const ImVec4& border, const ImVec4& borderShadow); void pushAccentColors(const ImVec4& one, const ImVec4& two, const ImVec4& border, const ImVec4& borderShadow);
void popAccentColors(); void popAccentColors();
float calcBPM(int s1, int s2, float hz, int vN, int vD); float calcBPM(const DivGroovePattern& speeds, float hz, int vN, int vD);
void patternRow(int i, bool isPlaying, float lineHeight, int chans, int ord, const DivPattern** patCache, bool inhibitSel); void patternRow(int i, bool isPlaying, float lineHeight, int chans, int ord, const DivPattern** patCache, bool inhibitSel);
@ -1821,6 +1828,7 @@ class FurnaceGUI {
void drawEditControls(); void drawEditControls();
void drawSongInfo(bool asChild=false); void drawSongInfo(bool asChild=false);
void drawSpeed(bool asChild=false); void drawSpeed(bool asChild=false);
void drawGrooves();
void drawOrders(); void drawOrders();
void drawPattern(); void drawPattern();
void drawInsList(bool asChild=false); void drawInsList(bool asChild=false);

View file

@ -520,6 +520,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={
D("WINDOW_SUBSONGS", "Subsongs", 0), D("WINDOW_SUBSONGS", "Subsongs", 0),
D("WINDOW_FIND", "Find/Replace", FURKMOD_CMD|SDLK_f), D("WINDOW_FIND", "Find/Replace", FURKMOD_CMD|SDLK_f),
D("WINDOW_CLOCK", "Clock", 0), D("WINDOW_CLOCK", "Clock", 0),
D("WINDOW_GROOVES", "Grooves", 0),
D("COLLAPSE_WINDOW", "Collapse/expand current window", 0), D("COLLAPSE_WINDOW", "Collapse/expand current window", 0),
D("CLOSE_WINDOW", "Close current window", FURKMOD_SHIFT|SDLK_ESCAPE), D("CLOSE_WINDOW", "Close current window", FURKMOD_SHIFT|SDLK_ESCAPE),
@ -741,6 +742,7 @@ const FurnaceGUIColorDef guiColors[GUI_COLOR_MAX]={
D(GUI_COLOR_ORDER_ROW_INDEX,"",ImVec4(0.5f,0.8f,1.0f,1.0f)), D(GUI_COLOR_ORDER_ROW_INDEX,"",ImVec4(0.5f,0.8f,1.0f,1.0f)),
D(GUI_COLOR_ORDER_ACTIVE,"",ImVec4(0.4f,0.7f,1.0f,0.25f)), D(GUI_COLOR_ORDER_ACTIVE,"",ImVec4(0.4f,0.7f,1.0f,0.25f)),
D(GUI_COLOR_ORDER_SELECTED,"",ImVec4(0.6f,0.8f,1.0f,0.75f)),
D(GUI_COLOR_ORDER_SIMILAR,"",ImVec4(0.5f,1.0f,1.0f,1.0f)), D(GUI_COLOR_ORDER_SIMILAR,"",ImVec4(0.5f,1.0f,1.0f,1.0f)),
D(GUI_COLOR_ORDER_INACTIVE,"",ImVec4(1.0f,1.0f,1.0f,1.0f)), D(GUI_COLOR_ORDER_INACTIVE,"",ImVec4(1.0f,1.0f,1.0f,1.0f)),

View file

@ -19,8 +19,10 @@
#include "gui.h" #include "gui.h"
#include <fmt/printf.h> #include <fmt/printf.h>
#include <imgui.h>
#include "IconsFontAwesome4.h" #include "IconsFontAwesome4.h"
#include "imgui_internal.h" #include "imgui_internal.h"
#include "../ta-log.h"
void FurnaceGUI::drawMobileOrderSel() { void FurnaceGUI::drawMobileOrderSel() {
if (!portrait) return; if (!portrait) return;
@ -129,6 +131,7 @@ void FurnaceGUI::drawOrders() {
} }
} }
ImGui::TableNextRow(0,lineHeight); ImGui::TableNextRow(0,lineHeight);
ImVec2 ra=ImGui::GetContentRegionAvail();
ImGui::TableNextColumn(); ImGui::TableNextColumn();
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_ORDER_ROW_INDEX]); ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_ORDER_ROW_INDEX]);
for (int i=0; i<e->getTotalChannelCount(); i++) { for (int i=0; i<e->getTotalChannelCount(); i++) {
@ -141,6 +144,14 @@ void FurnaceGUI::drawOrders() {
ImGui::TableNextRow(0,lineHeight); ImGui::TableNextRow(0,lineHeight);
if (oldOrder1==i) ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,ImGui::GetColorU32(uiColors[GUI_COLOR_ORDER_ACTIVE])); if (oldOrder1==i) ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,ImGui::GetColorU32(uiColors[GUI_COLOR_ORDER_ACTIVE]));
ImGui::TableNextColumn(); ImGui::TableNextColumn();
if ((!followPattern && curOrder==i) || (followPattern && oldOrder1==i)) {
// draw a border
ImDrawList* dl=ImGui::GetWindowDrawList();
ImVec2 rBegin=ImGui::GetCursorScreenPos();
rBegin.y-=ImGui::GetStyle().CellPadding.y;
ImVec2 rEnd=ImVec2(rBegin.x+ra.x,rBegin.y+lineHeight);
dl->AddRect(rBegin,rEnd,ImGui::GetColorU32(uiColors[GUI_COLOR_ORDER_SELECTED]),2.0f*dpiScale);
}
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_ORDER_ROW_INDEX]); ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_ORDER_ROW_INDEX]);
bool highlightLoop=(i>=loopOrder && i<=loopEnd); bool highlightLoop=(i>=loopOrder && i<=loopEnd);
if (highlightLoop) ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg,ImGui::GetColorU32(uiColors[GUI_COLOR_SONG_LOOP])); if (highlightLoop) ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg,ImGui::GetColorU32(uiColors[GUI_COLOR_SONG_LOOP]));

View file

@ -1207,6 +1207,18 @@ void FurnaceGUI::drawSettings() {
ImVec2 settingsViewSize=ImGui::GetContentRegionAvail(); ImVec2 settingsViewSize=ImGui::GetContentRegionAvail();
settingsViewSize.y-=ImGui::GetFrameHeight()+ImGui::GetStyle().WindowPadding.y; settingsViewSize.y-=ImGui::GetFrameHeight()+ImGui::GetStyle().WindowPadding.y;
if (ImGui::BeginChild("SettingsView",settingsViewSize)) { if (ImGui::BeginChild("SettingsView",settingsViewSize)) {
if (ImGui::BeginCombo("Render driver",settings.renderDriver.empty()?"Automatic":settings.renderDriver.c_str())) {
if (ImGui::Selectable("Automatic",settings.renderDriver.empty())) {
settings.renderDriver="";
}
for (String& i: availRenderDrivers) {
if (ImGui::Selectable(i.c_str(),i==settings.renderDriver)) {
settings.renderDriver=i;
}
}
ImGui::EndCombo();
}
bool dpiScaleAuto=(settings.dpiScale<0.5f); bool dpiScaleAuto=(settings.dpiScale<0.5f);
if (ImGui::Checkbox("Automatic UI scaling factor",&dpiScaleAuto)) { if (ImGui::Checkbox("Automatic UI scaling factor",&dpiScaleAuto)) {
if (dpiScaleAuto) { if (dpiScaleAuto) {
@ -1799,7 +1811,8 @@ void FurnaceGUI::drawSettings() {
} }
if (ImGui::TreeNode("Orders")) { if (ImGui::TreeNode("Orders")) {
UI_COLOR_CONFIG(GUI_COLOR_ORDER_ROW_INDEX,"Order number"); UI_COLOR_CONFIG(GUI_COLOR_ORDER_ROW_INDEX,"Order number");
UI_COLOR_CONFIG(GUI_COLOR_ORDER_ACTIVE,"Current order background"); UI_COLOR_CONFIG(GUI_COLOR_ORDER_ACTIVE,"Playing order background");
UI_COLOR_CONFIG(GUI_COLOR_ORDER_SELECTED,"Selected order");
UI_COLOR_CONFIG(GUI_COLOR_ORDER_SIMILAR,"Similar patterns"); UI_COLOR_CONFIG(GUI_COLOR_ORDER_SIMILAR,"Similar patterns");
UI_COLOR_CONFIG(GUI_COLOR_ORDER_INACTIVE,"Inactive patterns"); UI_COLOR_CONFIG(GUI_COLOR_ORDER_INACTIVE,"Inactive patterns");
ImGui::TreePop(); ImGui::TreePop();
@ -2447,6 +2460,7 @@ void FurnaceGUI::syncSettings() {
settings.midiInDevice=e->getConfString("midiInDevice",""); settings.midiInDevice=e->getConfString("midiInDevice","");
settings.midiOutDevice=e->getConfString("midiOutDevice",""); settings.midiOutDevice=e->getConfString("midiOutDevice","");
settings.c163Name=e->getConfString("c163Name",DIV_C163_DEFAULT_NAME); settings.c163Name=e->getConfString("c163Name",DIV_C163_DEFAULT_NAME);
settings.renderDriver=e->getConfString("renderDriver","");
settings.audioQuality=e->getConfInt("audioQuality",0); settings.audioQuality=e->getConfInt("audioQuality",0);
settings.audioBufSize=e->getConfInt("audioBufSize",1024); settings.audioBufSize=e->getConfInt("audioBufSize",1024);
settings.audioRate=e->getConfInt("audioRate",44100); settings.audioRate=e->getConfInt("audioRate",44100);
@ -2762,6 +2776,7 @@ void FurnaceGUI::commitSettings() {
e->setConf("midiInDevice",settings.midiInDevice); e->setConf("midiInDevice",settings.midiInDevice);
e->setConf("midiOutDevice",settings.midiOutDevice); e->setConf("midiOutDevice",settings.midiOutDevice);
e->setConf("c163Name",settings.c163Name); e->setConf("c163Name",settings.c163Name);
e->setConf("renderDriver",settings.renderDriver);
e->setConf("audioQuality",settings.audioQuality); e->setConf("audioQuality",settings.audioQuality);
e->setConf("audioBufSize",settings.audioBufSize); e->setConf("audioBufSize",settings.audioBufSize);
e->setConf("audioRate",settings.audioRate); e->setConf("audioRate",settings.audioRate);

View file

@ -31,19 +31,26 @@ void FurnaceGUI::drawSpeed(bool asChild) {
if (!speedOpen && !asChild) return; if (!speedOpen && !asChild) return;
bool began=asChild?ImGui::BeginChild("Speed"):ImGui::Begin("Speed",&speedOpen,globalWinFlags); bool began=asChild?ImGui::BeginChild("Speed"):ImGui::Begin("Speed",&speedOpen,globalWinFlags);
if (began) { if (began) {
if (ImGui::BeginTable("Props",3,ImGuiTableFlags_SizingStretchProp)) { if (ImGui::BeginTable("Props",2,ImGuiTableFlags_SizingStretchProp)) {
ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,0.0); ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,0.0);
ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.0); ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.0);
ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch,0.0);
ImGui::TableNextRow(); ImGui::TableNextRow();
ImGui::TableNextColumn(); ImGui::TableNextColumn();
if (ImGui::Selectable(tempoView?"Base Tempo##TempoOrHz":"Tick Rate##TempoOrHz")) { if (ImGui::SmallButton(tempoView?"Base Tempo##TempoOrHz":"Tick Rate##TempoOrHz")) {
tempoView=!tempoView; tempoView=!tempoView;
} }
if (ImGui::IsItemHovered()) {
if (tempoView) {
ImGui::SetTooltip("click to display tick rate");
} else {
ImGui::SetTooltip("click to display base tempo");
}
}
ImGui::TableNextColumn(); ImGui::TableNextColumn();
float avail=ImGui::GetContentRegionAvail().x; float avail=ImGui::GetContentRegionAvail().x;
ImGui::SetNextItemWidth(avail); float halfAvail=(avail-ImGui::GetStyle().ItemSpacing.x)*0.5;
ImGui::SetNextItemWidth(halfAvail);
float setHz=tempoView?e->curSubSong->hz*2.5:e->curSubSong->hz; float setHz=tempoView?e->curSubSong->hz*2.5:e->curSubSong->hz;
if (ImGui::InputFloat("##Rate",&setHz,1.0f,1.0f,"%g")) { MARK_MODIFIED if (ImGui::InputFloat("##Rate",&setHz,1.0f,1.0f,"%g")) { MARK_MODIFIED
if (tempoView) setHz/=2.5; if (tempoView) setHz/=2.5;
@ -52,40 +59,112 @@ void FurnaceGUI::drawSpeed(bool asChild) {
e->setSongRate(setHz,setHz<52); e->setSongRate(setHz,setHz<52);
} }
if (tempoView) { if (tempoView) {
ImGui::TableNextColumn(); ImGui::SameLine();
ImGui::Text("= %gHz",e->curSubSong->hz); ImGui::Text("= %gHz",e->curSubSong->hz);
} else { } else {
if (e->curSubSong->hz>=49.98 && e->curSubSong->hz<=50.02) { if (e->curSubSong->hz>=49.98 && e->curSubSong->hz<=50.02) {
ImGui::TableNextColumn(); ImGui::SameLine();
ImGui::Text("PAL"); ImGui::Text("PAL");
} }
if (e->curSubSong->hz>=59.9 && e->curSubSong->hz<=60.11) { if (e->curSubSong->hz>=59.9 && e->curSubSong->hz<=60.11) {
ImGui::TableNextColumn(); ImGui::SameLine();
ImGui::Text("NTSC"); ImGui::Text("NTSC");
} }
} }
ImGui::TableNextRow(); ImGui::TableNextRow();
ImGui::TableNextColumn(); ImGui::TableNextColumn();
ImGui::Text("Speed"); if (keepGrooveAlive || e->curSubSong->speeds.len>2) {
ImGui::TableNextColumn(); if (ImGui::SmallButton("Groove")) {
ImGui::SetNextItemWidth(avail); e->lockEngine([this]() {
if (ImGui::InputScalar("##Speed1",ImGuiDataType_U8,&e->curSubSong->speed1,&_ONE,&_THREE)) { MARK_MODIFIED e->curSubSong->speeds.len=1;
if (e->curSubSong->speed1<1) e->curSubSong->speed1=1; });
if (e->isPlaying()) play(); if (e->isPlaying()) play();
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("click for one speed");
}
} else if (e->curSubSong->speeds.len>1) {
if (ImGui::SmallButton("Speeds")) {
e->lockEngine([this]() {
e->curSubSong->speeds.len=4;
e->curSubSong->speeds.val[2]=e->curSubSong->speeds.val[0];
e->curSubSong->speeds.val[3]=e->curSubSong->speeds.val[1];
});
if (e->isPlaying()) play();
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("click for groove pattern");
}
} else {
if (ImGui::SmallButton("Speed")) {
e->lockEngine([this]() {
e->curSubSong->speeds.len=2;
e->curSubSong->speeds.val[1]=e->curSubSong->speeds.val[0];
});
if (e->isPlaying()) play();
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("click for two (alternating) speeds");
}
} }
ImGui::TableNextColumn(); ImGui::TableNextColumn();
ImGui::SetNextItemWidth(avail); if (keepGrooveAlive || e->curSubSong->speeds.len>2) {
if (ImGui::InputScalar("##Speed2",ImGuiDataType_U8,&e->curSubSong->speed2,&_ONE,&_THREE)) { MARK_MODIFIED int intVersion[256];
if (e->curSubSong->speed2<1) e->curSubSong->speed2=1; unsigned char intVersionLen=e->curSubSong->speeds.len;
if (e->isPlaying()) play(); unsigned char ignoredLoop=0;
unsigned char ignoredRel=0;
memset(intVersion,0,sizeof(int));
for (int i=0; i<16; i++) {
intVersion[i]=e->curSubSong->speeds.val[i];
}
if (intVersionLen>16) intVersionLen=16;
keepGrooveAlive=false;
ImGui::SetNextItemWidth(avail);
if (ImGui::InputText("##SpeedG",&grooveString)) {
decodeMMLStr(grooveString,intVersion,intVersionLen,ignoredLoop,1,255,ignoredRel);
if (intVersionLen<1) {
intVersionLen=1;
intVersion[0]=6;
}
if (intVersionLen>16) intVersionLen=16;
e->lockEngine([this,intVersion,intVersionLen]() {
e->curSubSong->speeds.len=intVersionLen;
for (int i=0; i<16; i++) {
e->curSubSong->speeds.val[i]=intVersion[i];
}
});
if (e->isPlaying()) play();
MARK_MODIFIED;
}
if (!ImGui::IsItemActive()) {
encodeMMLStr(grooveString,intVersion,intVersionLen,-1,-1,false);
} else {
keepGrooveAlive=true;
}
} else {
ImGui::SetNextItemWidth(halfAvail);
if (ImGui::InputScalar("##Speed1",ImGuiDataType_U8,&e->curSubSong->speeds.val[0],&_ONE,&_THREE)) { MARK_MODIFIED
if (e->curSubSong->speeds.val[0]<1) e->curSubSong->speeds.val[0]=1;
if (e->isPlaying()) play();
}
if (e->curSubSong->speeds.len>1) {
ImGui::SameLine();
ImGui::SetNextItemWidth(halfAvail);
if (ImGui::InputScalar("##Speed2",ImGuiDataType_U8,&e->curSubSong->speeds.val[1],&_ONE,&_THREE)) { MARK_MODIFIED
if (e->curSubSong->speeds.val[1]<1) e->curSubSong->speeds.val[1]=1;
if (e->isPlaying()) play();
}
}
} }
ImGui::TableNextRow(); ImGui::TableNextRow();
ImGui::TableNextColumn(); ImGui::TableNextColumn();
ImGui::Text("Virtual Tempo"); ImGui::Text("Virtual Tempo");
ImGui::TableNextColumn(); ImGui::TableNextColumn();
ImGui::SetNextItemWidth(avail); ImGui::SetNextItemWidth(halfAvail);
if (ImGui::InputScalar("##VTempoN",ImGuiDataType_S16,&e->curSubSong->virtualTempoN,&_ONE,&_THREE)) { MARK_MODIFIED if (ImGui::InputScalar("##VTempoN",ImGuiDataType_S16,&e->curSubSong->virtualTempoN,&_ONE,&_THREE)) { MARK_MODIFIED
if (e->curSubSong->virtualTempoN<1) e->curSubSong->virtualTempoN=1; if (e->curSubSong->virtualTempoN<1) e->curSubSong->virtualTempoN=1;
if (e->curSubSong->virtualTempoN>255) e->curSubSong->virtualTempoN=255; if (e->curSubSong->virtualTempoN>255) e->curSubSong->virtualTempoN=255;
@ -93,8 +172,8 @@ void FurnaceGUI::drawSpeed(bool asChild) {
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Numerator"); ImGui::SetTooltip("Numerator");
} }
ImGui::TableNextColumn(); ImGui::SameLine();
ImGui::SetNextItemWidth(avail); ImGui::SetNextItemWidth(halfAvail);
if (ImGui::InputScalar("##VTempoD",ImGuiDataType_S16,&e->curSubSong->virtualTempoD,&_ONE,&_THREE)) { MARK_MODIFIED if (ImGui::InputScalar("##VTempoD",ImGuiDataType_S16,&e->curSubSong->virtualTempoD,&_ONE,&_THREE)) { MARK_MODIFIED
if (e->curSubSong->virtualTempoD<1) e->curSubSong->virtualTempoD=1; if (e->curSubSong->virtualTempoD<1) e->curSubSong->virtualTempoD=1;
if (e->curSubSong->virtualTempoD>255) e->curSubSong->virtualTempoD=255; if (e->curSubSong->virtualTempoD>255) e->curSubSong->virtualTempoD=255;
@ -105,28 +184,28 @@ void FurnaceGUI::drawSpeed(bool asChild) {
ImGui::TableNextRow(); ImGui::TableNextRow();
ImGui::TableNextColumn(); ImGui::TableNextColumn();
ImGui::Text("TimeBase"); ImGui::Text("Divider");
ImGui::TableNextColumn(); ImGui::TableNextColumn();
ImGui::SetNextItemWidth(avail); ImGui::SetNextItemWidth(halfAvail);
unsigned char realTB=e->curSubSong->timeBase+1; unsigned char realTB=e->curSubSong->timeBase+1;
if (ImGui::InputScalar("##TimeBase",ImGuiDataType_U8,&realTB,&_ONE,&_THREE)) { MARK_MODIFIED if (ImGui::InputScalar("##TimeBase",ImGuiDataType_U8,&realTB,&_ONE,&_THREE)) { MARK_MODIFIED
if (realTB<1) realTB=1; if (realTB<1) realTB=1;
if (realTB>16) realTB=16; if (realTB>16) realTB=16;
e->curSubSong->timeBase=realTB-1; e->curSubSong->timeBase=realTB-1;
} }
ImGui::TableNextColumn(); ImGui::SameLine();
ImGui::Text("%.2f BPM",calcBPM(e->curSubSong->speed1,e->curSubSong->speed2,e->curSubSong->hz,e->curSubSong->virtualTempoN,e->curSubSong->virtualTempoD)); ImGui::Text("%.2f BPM",calcBPM(e->curSubSong->speeds,e->curSubSong->hz,e->curSubSong->virtualTempoN,e->curSubSong->virtualTempoD));
ImGui::TableNextRow(); ImGui::TableNextRow();
ImGui::TableNextColumn(); ImGui::TableNextColumn();
ImGui::Text("Highlight"); ImGui::Text("Highlight");
ImGui::TableNextColumn(); ImGui::TableNextColumn();
ImGui::SetNextItemWidth(avail); ImGui::SetNextItemWidth(halfAvail);
if (ImGui::InputScalar("##Highlight1",ImGuiDataType_U8,&e->curSubSong->hilightA,&_ONE,&_THREE)) { if (ImGui::InputScalar("##Highlight1",ImGuiDataType_U8,&e->curSubSong->hilightA,&_ONE,&_THREE)) {
MARK_MODIFIED; MARK_MODIFIED;
} }
ImGui::TableNextColumn(); ImGui::SameLine();
ImGui::SetNextItemWidth(avail); ImGui::SetNextItemWidth(halfAvail);
if (ImGui::InputScalar("##Highlight2",ImGuiDataType_U8,&e->curSubSong->hilightB,&_ONE,&_THREE)) { if (ImGui::InputScalar("##Highlight2",ImGuiDataType_U8,&e->curSubSong->hilightB,&_ONE,&_THREE)) {
MARK_MODIFIED; MARK_MODIFIED;
} }

View file

@ -106,6 +106,12 @@ void FurnaceGUI::drawSubSongs() {
if (ImGui::InputText("##SubSongName",&e->curSubSong->name,ImGuiInputTextFlags_UndoRedo)) { if (ImGui::InputText("##SubSongName",&e->curSubSong->name,ImGuiInputTextFlags_UndoRedo)) {
MARK_MODIFIED; MARK_MODIFIED;
} }
if (ImGui::GetContentRegionAvail().y>(10.0f*dpiScale)) {
if (ImGui::InputTextMultiline("##SubSongNotes",&e->curSubSong->notes,ImGui::GetContentRegionAvail(),ImGuiInputTextFlags_UndoRedo)) {
MARK_MODIFIED;
}
}
} }
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_SUBSONGS; if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_SUBSONGS;
ImGui::End(); ImGui::End();

View file

@ -472,6 +472,8 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo
int clockSel=flags.getInt("clockSel",0); int clockSel=flags.getInt("clockSel",0);
bool noExtMacros=flags.getBool("noExtMacros",false); bool noExtMacros=flags.getBool("noExtMacros",false);
bool fbAllOps=flags.getBool("fbAllOps",false); bool fbAllOps=flags.getBool("fbAllOps",false);
int ssgVol=flags.getInt("ssgVol",128);
int fmVol=flags.getInt("fmVol",256);
if (ImGui::RadioButton("8MHz (Neo Geo MVS)",clockSel==0)) { if (ImGui::RadioButton("8MHz (Neo Geo MVS)",clockSel==0)) {
clockSel=0; clockSel=0;
@ -491,11 +493,25 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo
} }
} }
if (CWSliderInt("SSG Volume",&ssgVol,0,256)) {
if (ssgVol<0) ssgVol=0;
if (ssgVol>256) ssgVol=256;
altered=true;
} rightClickable
if (CWSliderInt("FM/ADPCM Volume",&fmVol,0,256)) {
if (fmVol<0) fmVol=0;
if (fmVol>256) fmVol=256;
altered=true;
} rightClickable
if (altered) { if (altered) {
e->lockSave([&]() { e->lockSave([&]() {
flags.set("clockSel",clockSel); flags.set("clockSel",clockSel);
flags.set("noExtMacros",noExtMacros); flags.set("noExtMacros",noExtMacros);
flags.set("fbAllOps",fbAllOps); flags.set("fbAllOps",fbAllOps);
flags.set("ssgVol",ssgVol);
flags.set("fmVol",fmVol);
}); });
} }
break; break;
@ -870,6 +886,8 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo
int prescale=flags.getInt("prescale",0); int prescale=flags.getInt("prescale",0);
bool noExtMacros=flags.getBool("noExtMacros",false); bool noExtMacros=flags.getBool("noExtMacros",false);
bool fbAllOps=flags.getBool("fbAllOps",false); bool fbAllOps=flags.getBool("fbAllOps",false);
int ssgVol=flags.getInt("ssgVol",128);
int fmVol=flags.getInt("fmVol",256);
ImGui::Text("Clock rate:"); ImGui::Text("Clock rate:");
if (ImGui::RadioButton("3.58MHz (NTSC)",clockSel==0)) { if (ImGui::RadioButton("3.58MHz (NTSC)",clockSel==0)) {
@ -910,6 +928,18 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo
altered=true; altered=true;
} }
if (CWSliderInt("SSG Volume",&ssgVol,0,256)) {
if (ssgVol<0) ssgVol=0;
if (ssgVol>256) ssgVol=256;
altered=true;
} rightClickable
if (CWSliderInt("FM Volume",&fmVol,0,256)) {
if (fmVol<0) fmVol=0;
if (fmVol>256) fmVol=256;
altered=true;
} rightClickable
if (type==DIV_SYSTEM_YM2203_EXT || type==DIV_SYSTEM_YM2203_CSM) { if (type==DIV_SYSTEM_YM2203_EXT || type==DIV_SYSTEM_YM2203_CSM) {
if (ImGui::Checkbox("Disable ExtCh FM macros (compatibility)",&noExtMacros)) { if (ImGui::Checkbox("Disable ExtCh FM macros (compatibility)",&noExtMacros)) {
altered=true; altered=true;
@ -925,6 +955,8 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo
flags.set("prescale",prescale); flags.set("prescale",prescale);
flags.set("noExtMacros",noExtMacros); flags.set("noExtMacros",noExtMacros);
flags.set("fbAllOps",fbAllOps); flags.set("fbAllOps",fbAllOps);
flags.set("ssgVol",ssgVol);
flags.set("fmVol",fmVol);
}); });
} }
break; break;
@ -936,6 +968,8 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo
int prescale=flags.getInt("prescale",0); int prescale=flags.getInt("prescale",0);
bool noExtMacros=flags.getBool("noExtMacros",false); bool noExtMacros=flags.getBool("noExtMacros",false);
bool fbAllOps=flags.getBool("fbAllOps",false); bool fbAllOps=flags.getBool("fbAllOps",false);
int ssgVol=flags.getInt("ssgVol",128);
int fmVol=flags.getInt("fmVol",256);
ImGui::Text("Clock rate:"); ImGui::Text("Clock rate:");
if (ImGui::RadioButton("8MHz (Arcade)",clockSel==0)) { if (ImGui::RadioButton("8MHz (Arcade)",clockSel==0)) {
@ -960,6 +994,18 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo
altered=true; altered=true;
} }
if (CWSliderInt("SSG Volume",&ssgVol,0,256)) {
if (ssgVol<0) ssgVol=0;
if (ssgVol>256) ssgVol=256;
altered=true;
} rightClickable
if (CWSliderInt("FM/ADPCM Volume",&fmVol,0,256)) {
if (fmVol<0) fmVol=0;
if (fmVol>256) fmVol=256;
altered=true;
} rightClickable
if (type==DIV_SYSTEM_YM2608_EXT || type==DIV_SYSTEM_YM2608_CSM) { if (type==DIV_SYSTEM_YM2608_EXT || type==DIV_SYSTEM_YM2608_CSM) {
if (ImGui::Checkbox("Disable ExtCh FM macros (compatibility)",&noExtMacros)) { if (ImGui::Checkbox("Disable ExtCh FM macros (compatibility)",&noExtMacros)) {
altered=true; altered=true;
@ -975,6 +1021,8 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo
flags.set("prescale",prescale); flags.set("prescale",prescale);
flags.set("noExtMacros",noExtMacros); flags.set("noExtMacros",noExtMacros);
flags.set("fbAllOps",fbAllOps); flags.set("fbAllOps",fbAllOps);
flags.set("ssgVol",ssgVol);
flags.set("fmVol",fmVol);
}); });
} }
break; break;