Merge branch 'master' of https://github.com/tildearrow/furnace into es5506_alt
This commit is contained in:
		
						commit
						03e0c8d8ee
					
				| 
						 | 
					@ -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
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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) {
 | 
				
			||||||
| 
						 | 
					@ -876,6 +902,19 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
 | 
				
			||||||
            data=new short[length];
 | 
					            data=new short[length];
 | 
				
			||||||
            reader.read(data,length*2);
 | 
					            reader.read(data,length*2);
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          if (ds.version>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
 | 
				
			||||||
| 
						 | 
					@ -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");
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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) {}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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 {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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;
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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;
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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
									
								
							
							
						
						
									
										154
									
								
								src/gui/grooves.cpp
									
									
									
									
									
										Normal 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();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -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),
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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)),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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]));
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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();
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue