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

* 'master' of https://github.com/tildearrow/furnace: (46 commits)
  PCE: fix two issues
  SegaPCM: fix samples bigger than 64KB
  SCC: implement VGM soft reset
  GUI: add option to clear orders
  GUI: implement "clear all subsongs"
  GUI: fix crash when deleting current subsong
  CI: only 1 core for MinGW
  Fix AY8910 envelope hangs
  OPL: fix fixed frequency drums
  pick nits: the sequel
  pick nits
  AY: fix possible hang
  hide .ftm format
  Add x2 icon variations as well
  Install more size variations on Linux
  OPLL: fix fixed drums freq
  GUI: make backupTimer atomic
  Have OPN* platforms set the correct YM2149 chip type.
  update to-do list
  ZX beeper: clarify effects (will be done later)
  ...

# Conflicts:
#	src/engine/platform/segapcm.cpp
This commit is contained in:
cam900 2022-05-22 18:24:17 +09:00
commit f7ba60bfa9
30 changed files with 1287 additions and 260 deletions

View file

@ -97,6 +97,10 @@ jobs:
if [ '${{ runner.os }}' == 'macOS' ]; then if [ '${{ runner.os }}' == 'macOS' ]; then
amount=3 amount=3
fi fi
# the Actions runner does not seem to be happy with two jobs at once on MinGW
if [ '${{ matrix.config.compiler }}' == 'mingw' ]; then
amount=1
fi
echo "Amount of cores we can build with: ${amount}" echo "Amount of cores we can build with: ${amount}"

View file

@ -565,6 +565,11 @@ if (NOT ANDROID)
install(DIRECTORY papers DESTINATION ${CMAKE_INSTALL_DOCDIR}) install(DIRECTORY papers DESTINATION ${CMAKE_INSTALL_DOCDIR})
install(FILES LICENSE DESTINATION ${CMAKE_INSTALL_DATADIR}/licenses/furnace) install(FILES LICENSE DESTINATION ${CMAKE_INSTALL_DATADIR}/licenses/furnace)
install(DIRECTORY demos DESTINATION ${CMAKE_INSTALL_DATADIR}/furnace) install(DIRECTORY demos DESTINATION ${CMAKE_INSTALL_DATADIR}/furnace)
foreach(num 16 32 64 128 256 512)
set(res ${num}x${num})
install(FILES res/icon.iconset/icon_${res}.png RENAME furnace.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/${res}/apps)
install(FILES res/icon.iconset/icon_${res}@2x.png RENAME furnace.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/${res}@2/apps)
endforeach()
install(FILES res/logo.png RENAME furnace.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/1024x1024/apps) install(FILES res/logo.png RENAME furnace.png DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/1024x1024/apps)
endif() endif()

View file

@ -814,6 +814,16 @@ bool DivEngine::removeSubSong(int index) {
return true; return true;
} }
void DivEngine::clearSubSongs() {
BUSY_BEGIN;
saveLock.lock();
song.clearSongData();
changeSong(0);
curOrder=0;
saveLock.unlock();
BUSY_END;
}
void DivEngine::changeSystem(int index, DivSystem which, bool preserveOrder) { void DivEngine::changeSystem(int index, DivSystem which, bool preserveOrder) {
int chanCount=chans; int chanCount=chans;
quitDispatch(); quitDispatch();
@ -1945,13 +1955,13 @@ int DivEngine::addSampleFromFile(const char* path) {
} }
extS+=i; extS+=i;
} }
if (extS==String(".dmc")) { // read as .dmc if (extS==".dmc") { // read as .dmc
size_t len=0; size_t len=0;
DivSample* sample=new DivSample; DivSample* sample=new DivSample;
int sampleCount=(int)song.sample.size(); int sampleCount=(int)song.sample.size();
sample->name=stripPath; sample->name=stripPath;
FILE* f=fopen(path,"rb"); FILE* f=ps_fopen(path,"rb");
if (f==NULL) { if (f==NULL) {
BUSY_END; BUSY_END;
lastError=fmt::sprintf("could not open file! (%s)",strerror(errno)); lastError=fmt::sprintf("could not open file! (%s)",strerror(errno));

View file

@ -399,8 +399,11 @@ class DivEngine {
void loadOPNI(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath); void loadOPNI(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
void loadY12(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath); void loadY12(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
void loadBNK(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath); void loadBNK(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
void loadGYB(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
void loadOPM(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath); void loadOPM(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
void loadFF(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath); void loadFF(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
void loadWOPL(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
void loadWOPN(SafeReader& reader, std::vector<DivInstrument*>& ret, String& stripPath);
int loadSampleROM(String path, ssize_t expectedSize, unsigned char*& ret); int loadSampleROM(String path, ssize_t expectedSize, unsigned char*& ret);
@ -820,6 +823,9 @@ class DivEngine {
// remove subsong // remove subsong
bool removeSubSong(int index); bool removeSubSong(int index);
// clear all subsong data
void clearSubSongs();
// change system // change system
void changeSystem(int index, DivSystem which, bool preserveOrder=true); void changeSystem(int index, DivSystem which, bool preserveOrder=true);

View file

@ -2682,16 +2682,19 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
if (song.ins.size()>256) { if (song.ins.size()>256) {
logE("maximum number of instruments is 256!"); logE("maximum number of instruments is 256!");
lastError="maximum number of instruments is 256"; lastError="maximum number of instruments is 256";
saveLock.unlock();
return NULL; return NULL;
} }
if (song.wave.size()>256) { if (song.wave.size()>256) {
logE("maximum number of wavetables is 256!"); logE("maximum number of wavetables is 256!");
lastError="maximum number of wavetables is 256"; lastError="maximum number of wavetables is 256";
saveLock.unlock();
return NULL; return NULL;
} }
if (song.sample.size()>256) { if (song.sample.size()>256) {
logE("maximum number of samples is 256!"); logE("maximum number of samples is 256!");
lastError="maximum number of samples is 256"; lastError="maximum number of samples is 256";
saveLock.unlock();
return NULL; return NULL;
} }

File diff suppressed because it is too large Load diff

View file

@ -660,6 +660,7 @@ void DivPlatformAY8910::setFlags(unsigned int flags) {
break; break;
} }
ay->device_start(); ay->device_start();
ay->device_reset();
stereo=(flags>>6)&1; stereo=(flags>>6)&1;
} }

View file

@ -47,6 +47,15 @@
rWrite((a),__VA_ARGS__); \ rWrite((a),__VA_ARGS__); \
} }
#define pageReadMask(p,pm,a,...) \
if (!skipRegisterWrites) { \
if ((curPage&(pm))!=((p)&(pm))) { \
curPage=(curPage&~(pm))|((p)&(pm)); \
rWrite(0xf,curPage,(pm)); \
} \
rRead((a),__VA_ARGS__); \
}
const char* regCheatSheetES5506[]={ const char* regCheatSheetES5506[]={
"CR", "00|00", "CR", "00|00",
@ -167,6 +176,8 @@ const char* DivPlatformES5506::getEffectName(unsigned char effect) {
return "3xxx: Set filter coefficient K1"; return "3xxx: Set filter coefficient K1";
} else if ((effect&0xf0)==0x40) { } else if ((effect&0xf0)==0x40) {
return "4xxx: Set filter coefficient K2"; return "4xxx: Set filter coefficient K2";
} else if ((effect&0xf0)==0x50) {
return "5xxx: Set transwave slice point";
} }
break; break;
} }
@ -268,6 +279,101 @@ void DivPlatformES5506::e_pin(bool state)
pageWriteMask(0x00|ch,0x5f,0x00,(chan[ch].pcm.reversed?0x0000:0x0040)|0x08,0x78); pageWriteMask(0x00|ch,0x5f,0x00,(chan[ch].pcm.reversed?0x0000:0x0040)|0x08,0x78);
chan[ch].isReverseLoop=false; chan[ch].isReverseLoop=false;
} }
if (chan[ch].transwaveIRQ) {
if ((chan[ch].cr&0x37)==0x34) { // IRQE = 1, BLE = 1, LPE = 0, LEI = 1
DivInstrument* ins=parent->getIns(chan[i].ins);
if (!ins->amiga.useNoteMap && ins->amiga.transWave.enable) {
const int next=chan[ch].pcm.next;
bool sampleVaild=false;
if (next>=0 && next<ins->amiga.transWaveMap.size()) {
DivInstrumentAmiga::TransWaveMap& transWaveInd=ins->amiga.transWaveMap[next];
int sample=transWaveInd.ind;
if (sample>=0 && sample<parent->song.sampleLen) {
sampleVaild=true;
chan[ch].pcm.index=sample;
chan[ch].transWave.ind=next;
DivSample* s=parent->getSample(sample);
// get frequency offset
double off=1.0;
double center=s->centerRate;
if (center<1) {
off=1.0;
} else {
off=(double)center/8363.0;
}
// get loop mode, transwave loop
double loopStart=(double)s->loopStart;
double loopEnd=(double)s->loopEnd;
DivSampleLoopMode loopMode=s->isLoopable()?s->loopMode:DIV_SAMPLE_LOOPMODE_ONESHOT;
if (transWaveInd.loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) {
loopMode=transWaveInd.loopMode;
} else if ((chan[ch].pcm.loopMode==DIV_SAMPLE_LOOPMODE_ONESHOT) || (!s->isLoopable())) { // default
loopMode=DIV_SAMPLE_LOOPMODE_PINGPONG;
}
// get loop position
loopStart=(double)transWaveInd.loopStart;
loopEnd=(double)transWaveInd.loopEnd;
if (ins->amiga.transWave.sliceEnable) { // sliced loop position?
chan[ch].transWave.updateSize(s->samples,loopStart,loopEnd);
chan[ch].transWave.slice=transWaveInd.slice;
chan[ch].transWave.slicePos(chan[ch].transWave.slice);
loopStart=transWaveInd.sliceStart;
loopEnd=transWaveInd.sliceEnd;
}
// get reversed
bool reversed=ins->amiga.reversed;
if (transWaveInd.reversed!=2) {
reversed=transWaveInd.reversed;
}
const unsigned int start=s->offES5506<<10;
const unsigned int length=s->samples-1;
const unsigned int end=start+(length<<11);
chan[ch].pcm.loopMode=loopMode;
chan[ch].pcm.nextFreqOffs=PITCH_OFFSET*off;
chan[ch].pcm.reversed=reversed;
chan[ch].pcm.bank=(s->offES5506>>22)&3;
chan[ch].pcm.start=start;
chan[ch].pcm.loopStart=(start+(unsigned int)(loopStart*2048.0))&0xfffff800;
chan[ch].pcm.loopEnd=(start+(unsigned int)((loopEnd-1.0)*2048.0))&0xffffff80;
chan[ch].pcm.end=end;
chan[ch].pcm.length=length;
pageWrite(0x20|ch,0x01,chan[ch].pcm.loopStart);
pageWrite(0x20|ch,0x02,chan[ch].pcm.loopEnd);
pageWrite(0x20|ch,0x03,(chan[ch].pcm.reversed)?chan[ch].pcm.loopEnd:chan[ch].pcm.loopStart);
unsigned int loopFlag=(chan[ch].pcm.bank<<14)|(chan[ch].pcm.reversed?0x0040:0x0000);
chan[ch].isReverseLoop=false;
switch (chan[ch].pcm.loopMode) {
case DIV_SAMPLE_LOOPMODE_ONESHOT: // One shot (no loop)
default:
break;
case DIV_SAMPLE_LOOPMODE_FORWARD: // Foward loop
loopFlag|=0x0008;
break;
case DIV_SAMPLE_LOOPMODE_BACKWARD: // Backward loop: IRQ enable
loopFlag|=0x0038;
chan[ch].isReverseLoop=true;
break;
case DIV_SAMPLE_LOOPMODE_PINGPONG: // Pingpong loop: Hardware support
loopFlag|=0x0018;
break;
}
// Set loop mode & Bank
pageWriteMask(0x00|ch,0x5f,0x00,loopFlag,0xfcfc);
}
}
if (sampleVaild) {
chan[ch].noteChanged.offs=1;
}
chan[ch].pcmChanged.changed=0;
}
}
chan[ch].transwaveIRQ=false;
}
if (chan[ch].isTransWave) {
pageReadMask(0x00|ch,0x5f,0x00,&chan[ch].cr);
chan[ch].transwaveIRQ=true;
chan[ch].isTransWave=false;
}
} }
} }
} }
@ -460,14 +566,56 @@ void DivPlatformES5506::tick(bool sysTick) {
} }
} }
// control macros // control macros
if (!chan[i].keyOn) { if (chan[i].active && chan[i].std.alg.had) {
if (chan[i].active && chan[i].std.alg.had) { if (chan[i].pcm.pause!=(bool)(chan[i].std.alg.val&1)) {
if (chan[i].pcm.pause!=(bool)(chan[i].std.alg.val&1)) { chan[i].pcm.pause=chan[i].std.alg.val&1;
chan[i].pcm.pause=chan[i].std.alg.val&1; if (!chan[i].keyOn) {
pageWriteMask(0x00|i,0x5f,0x00,chan[i].pcm.pause?0x0002:0x0000,0x0002); pageWriteMask(0x00|i,0x5f,0x00,chan[i].pcm.pause?0x0002:0x0000,0x0002);
} }
} }
} }
// transwave macros
if (chan[i].transWave.enable) {
if (chan[i].std.wave.had) {
if (chan[i].std.wave.val>=0 && chan[i].std.wave.val<ins->amiga.transWaveMap.size()) {
if (chan[i].pcm.next!=chan[i].std.wave.val) {
chan[i].pcm.next=chan[i].std.wave.val;
chan[i].pcmChanged.transwaveInd=1;
}
}
}
if (chan[i].std.fb.had) {
if (chan[i].transWave.sliceEnable!=(bool)(chan[i].std.fb.val&1)) {
chan[i].transWave.sliceEnable=chan[i].std.fb.val&1;
chan[i].pcmChanged.slice=1;
}
}
if (chan[i].std.fms.had) {
if (chan[i].transWave.slice!=(unsigned short)(chan[i].std.fms.val&0xfff)) {
chan[i].transWave.slice=chan[i].std.fms.val&0xfff;
chan[i].pcmChanged.slice=1;
}
}
} else if (chan[i].pcm.isNoteMap) {
// note map macros
if (chan[i].std.wave.had) {
if (chan[i].std.wave.val>=0 && chan[i].std.wave.val<120) {
if (chan[i].pcm.next!=chan[i].std.wave.val) {
chan[i].pcm.next=chan[i].std.wave.val;
chan[i].pcmChanged.index=1;
}
}
}
} else if (!chan[i].transWave.enable && !chan[i].pcm.isNoteMap) {
if (chan[i].std.wave.had) {
if (chan[i].std.wave.val>=0 && chan[i].std.wave.val<parent->song.sampleLen) {
if (chan[i].pcm.next!=chan[i].std.wave.val) {
chan[i].pcm.next=chan[i].std.wave.val;
chan[i].pcmChanged.index=1;
}
}
}
}
// update registers // update registers
if (chan[i].volChanged.changed) { if (chan[i].volChanged.changed) {
if (!isMuted[i]) { // calculate volume (16 bit) if (!isMuted[i]) { // calculate volume (16 bit)
@ -489,34 +637,193 @@ void DivPlatformES5506::tick(bool sysTick) {
} }
chan[i].volChanged.changed=0; chan[i].volChanged.changed=0;
} }
if (chan[i].pcmChanged) { if (chan[i].pcmChanged.changed) {
DivInstrument* ins=parent->getIns(chan[i].ins); if (!chan[i].isTransWave) {
if (!ins->amiga.useNoteMap) { if (chan[i].pcmChanged.transwaveInd && (!ins->amiga.useNoteMap && ins->amiga.transWave.enable)) {
double off=1.0; const int next=chan[i].pcm.next;
if (chan[i].pcm.next>=0 && chan[i].pcm.next<parent->song.sampleLen) { bool sampleVaild=false;
chan[i].pcm.index=chan[i].pcm.next; if (next>=0 && next<ins->amiga.transWaveMap.size()) {
DivSample* s=parent->getSample(chan[i].pcm.next); DivInstrumentAmiga::TransWaveMap& transWaveInd=ins->amiga.transWaveMap[next];
if (s->centerRate<1) { int sample=transWaveInd.ind;
off=1.0; if (sample>=0 && sample<parent->song.sampleLen) {
} else { if (chan[i].pcm.index!=sample) {
off=(double)s->centerRate/8363.0; pageWriteMask(0x00|i,0x5f,0x00,0x0034,0x00ff); // Set IRQ
chan[i].isTranswave=true;
} else {
chan[i].transWave.ind=next;
DivSample* s=parent->getSample(sample);
// get loop mode, transwave loop
double loopStart=(double)s->loopStart;
double loopEnd=(double)s->loopEnd;
DivSampleLoopMode loopMode=s->isLoopable()?s->loopMode:DIV_SAMPLE_LOOPMODE_ONESHOT;
if (transWaveInd.loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) {
loopMode=transWaveInd.loopMode;
} else if ((chan[i].pcm.loopMode==DIV_SAMPLE_LOOPMODE_ONESHOT) || (!s->isLoopable())) { // default
loopMode=DIV_SAMPLE_LOOPMODE_PINGPONG;
}
// get loop position
loopStart=(double)transWaveInd.loopStart;
loopEnd=(double)transWaveInd.loopEnd;
if (ins->amiga.transWave.sliceEnable) { // sliced loop position?
chan[i].transWave.updateSize(s->samples,loopStart,loopEnd);
chan[i].transWave.slice=transWaveInd.slice;
chan[i].transWave.slicePos(transWaveInd.slice);
loopStart=transWaveInd.sliceStart;
loopEnd=transWaveInd.sliceEnd;
}
// get reversed
bool reversed=ins->amiga.reversed;
if (transWaveInd.reversed!=2) {
reversed=transWaveInd.reversed;
}
chan[i].pcm.loopMode=loopMode;
chan[i].pcm.reversed=reversed;
if (sampleVaild) {
chan[i].pcmChanged.slice=1;
chan[i].pcmChanged.loopBank=1;
}
}
}
} }
const unsigned int start=s->offES5506<<10; chan[i].pcmChanged.transwaveInd=0;
const unsigned int length=s->samples-1; }
const unsigned int end=start+(length<<11); if ((!chan[i].pcmChanged.transwaveInd) && (!chan[i].isTransWave)) {
chan[i].pcm.loopMode=s->isLoopable()?s->loopMode:DIV_SAMPLE_LOOPMODE_ONESHOT; if (chan[i].pcmChanged.index) {
chan[i].pcm.freqOffs=PITCH_OFFSET*off; const int next=chan[i].pcm.next;
chan[i].pcm.reversed=ins->amiga.reversed; bool sampleVaild=false;
chan[i].pcm.bank=(s->offES5506>>22)&3; if (((ins->amiga.useNoteMap && !ins->amiga.transWave.enable) && (next>=0 && next<120)) ||
chan[i].pcm.start=start; ((!ins->amiga.useNoteMap && ins->amiga.transWave.enable) && (next>=0 && next<ins->amiga.transWaveMap.size())) ||
chan[i].pcm.end=end; ((!ins->amiga.useNoteMap && !ins->amiga.transWave.enable) && (next>=0 && next<parent->song.sampleLen))) {
chan[i].pcm.length=length; DivInstrumentAmiga::NoteMap& noteMapind=ins->amiga.noteMap[next];
chan[i].pcm.loopStart=(start+(s->loopStart<<11))&0xfffff800; DivInstrumentAmiga::TransWaveMap& transWaveInd=ins->amiga.transWaveMap[next];
chan[i].pcm.loopEnd=(start+((s->loopEnd-1)<<11))&0xffffff80; int sample=next;
chan[i].keyOn=true; if (ins->amiga.transWave.enable) {
sample=transWaveInd.ind;
} else if (ins->amiga.useNoteMap) {
sample=noteMapind.ind;
}
if (sample>=0 && sample<parent->song.sampleLen) {
sampleVaild=true;
chan[i].pcm.index=sample;
chan[i].pcm.isNoteMap=ins->amiga.useNoteMap && !ins->amiga.transWave.enable;
chan[i].transWave.enable=!ins->amiga.useNoteMap && ins->amiga.transWave.enable;
chan[i].transWave.ind=next;
DivSample* s=parent->getSample(sample);
// get frequency offset
double off=1.0;
double center=s->centerRate;
if (center<1) {
off=1.0;
} else {
off=(double)center/8363.0;
}
if (ins->amiga.useNoteMap) {
off*=(double)noteMapind.freq/((double)MAX(1,center)*pow(2.0,((double)next-48.0)/12.0));
chan[i].pcm.note=next;
}
// get loop mode, transwave loop
double loopStart=(double)s->loopStart;
double loopEnd=(double)s->loopEnd;
DivSampleLoopMode loopMode=s->isLoopable()?s->loopMode:DIV_SAMPLE_LOOPMODE_ONESHOT;
if (ins->amiga.transWave.enable) {
if (transWaveInd.loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) {
loopMode=transWaveInd.loopMode;
} else if ((chan[i].pcm.loopMode==DIV_SAMPLE_LOOPMODE_ONESHOT) || (!s->isLoopable())) { // default
loopMode=DIV_SAMPLE_LOOPMODE_PINGPONG;
}
// get loop position
loopStart=(double)transWaveInd.loopStart;
loopEnd=(double)transWaveInd.loopEnd;
if (ins->amiga.transWave.sliceEnable) { // sliced loop position?
chan[i].transWave.updateSize(s->samples,loopStart,loopEnd);
chan[i].transWave.slice=transWaveInd.slice;
chan[i].transWave.slicePos(transWaveInd.slice);
loopStart=transWaveInd.sliceStart;
loopEnd=transWaveInd.sliceEnd;
}
}
// get reversed
bool reversed=ins->amiga.reversed;
if (ins->amiga.transWave.enable&&transWaveInd.reversed!=2) {
reversed=transWaveInd.reversed;
} else if (ins->amiga.useNoteMap&&noteMapind.reversed!=2) {
reversed=noteMapind.reversed;
}
const unsigned int start=s->offES5506<<10;
const unsigned int length=s->samples-1;
const unsigned int end=start+(length<<11);
chan[i].pcm.loopMode=loopMode;
chan[i].pcm.nextFreqOffs=PITCH_OFFSET*off;
chan[i].pcm.reversed=reversed;
chan[i].pcm.bank=(s->offES5506>>22)&3;
chan[i].pcm.start=start;
chan[i].pcm.end=end;
chan[i].pcm.length=length;
chan[i].keyOn=true;
}
}
if (sampleVaild) {
chan[i].pcmChanged.slice=1;
chan[i].pcmChanged.loopBank=1;
chan[i].noteChanged.offs=1;
}
chan[i].pcmChanged.index=0;
}
if (chan[i].pcmChanged.slice) {
if (!chan[i].keyOn) {
if (chan[i].pcm.index>=0 && chan[i].pcm.index<parent->song.sampleLen) {
// get loop mode, transwave loop
DivSample* s=parent->getSample(chan[i].pcm.index);
double loopStart=(double)s->loopStart;
double loopEnd=(double)s->loopEnd;
if (ins->amiga.transWave.sliceEnable) { // sliced loop position?
chan[i].transWave.updateSize(s->samples,loopStart,loopEnd);
chan[i].transWave.slicePos(chan[i].transWave.slice);
loopStart=chan[i].transWave.sliceStart;
loopEnd=chan[i].transWave.sliceEnd;
}
const unsigned int start=s->offES5506<<10;
chan[i].pcm.loopStart=(start+(unsigned int)(loopStart*2048.0))&0xfffff800;
chan[i].pcm.loopEnd=(start+(unsigned int)((loopEnd-1.0)*2048.0))&0xffffff80;
chan[i].pcmChanged.position=1;
}
}
chan[i].pcmChanged.slice=0;
}
if (chan[i].pcmChanged.position) {
if (!chan[i].keyOn) {
pageWrite(0x20|i,0x01,(chan[i].pcm.loopMode==DIV_SAMPLE_LOOPMODE_ONESHOT)?chan[i].pcm.start:chan[i].pcm.loopStart);
pageWrite(0x20|i,0x02,(chan[i].pcm.loopMode==DIV_SAMPLE_LOOPMODE_ONESHOT)?chan[i].pcm.end:chan[i].pcm.loopEnd);
}
chan[i].pcmChanged.position=0;
}
if (chan[i].pcmChanged.loopBank) {
if (!chan[i].keyOn) {
unsigned int loopFlag=(chan[i].pcm.bank<<14)|(chan[i].pcm.reversed?0x0040:0x0000);
chan[i].isReverseLoop=false;
switch (chan[i].pcm.loopMode) {
case DIV_SAMPLE_LOOPMODE_ONESHOT: // One shot (no loop)
default:
break;
case DIV_SAMPLE_LOOPMODE_FORWARD: // Foward loop
loopFlag|=0x0008;
break;
case DIV_SAMPLE_LOOPMODE_BACKWARD: // Backward loop: IRQ enable
loopFlag|=0x0038;
chan[i].isReverseLoop=true;
break;
case DIV_SAMPLE_LOOPMODE_PINGPONG: // Pingpong loop: Hardware support
loopFlag|=0x0018;
break;
}
// Set loop mode & Bank
pageWriteMask(0x00|i,0x5f,0x00,loopFlag,0xfcfd);
}
chan[i].pcmChanged.loopBank=0;
}
chan[i].pcmChanged.dummy=0;
} }
} }
chan[i].pcmChanged=false;
} }
if (chan[i].filterChanged.changed) { if (chan[i].filterChanged.changed) {
if (!chan[i].keyOn) { if (!chan[i].keyOn) {
@ -564,12 +871,13 @@ void DivPlatformES5506::tick(bool sysTick) {
if (chan[i].noteChanged.offs) { if (chan[i].noteChanged.offs) {
if (chan[i].pcm.freqOffs!=chan[i].pcm.nextFreqOffs) { if (chan[i].pcm.freqOffs!=chan[i].pcm.nextFreqOffs) {
chan[i].pcm.freqOffs=chan[i].pcm.nextFreqOffs; chan[i].pcm.freqOffs=chan[i].pcm.nextFreqOffs;
const int nextFreq=NOTE_ES5506(i,chan[i].nextNote); const int nextFreq=NOTE_ES5506(i,chan[i].prevNote);
if (chan[i].nextFreq!=nextFreq) { if (chan[i].nextFreq!=nextFreq) {
chan[i].nextFreq=nextFreq; chan[i].nextFreq=nextFreq;
chan[i].noteChanged.freq=1; chan[i].noteChanged.freq=1;
} }
} }
chan[i].noteChanged.offs=0;
} }
if (chan[i].noteChanged.note) { // note value changed or frequency offset is changed if (chan[i].noteChanged.note) { // note value changed or frequency offset is changed
if (chan[i].prevNote!=chan[i].nextNote) { if (chan[i].prevNote!=chan[i].nextNote) {
@ -580,17 +888,19 @@ void DivPlatformES5506::tick(bool sysTick) {
chan[i].noteChanged.freq=1; chan[i].noteChanged.freq=1;
} }
} }
chan[i].noteChanged.note=0;
} }
if (chan[i].noteChanged.freq) { if (chan[i].noteChanged.freq) {
if (chan[i].baseFreq!=chan[i].nextFreq) { if (chan[i].baseFreq!=chan[i].nextFreq) {
chan[i].baseFreq=chan[i].nextFreq; chan[i].baseFreq=chan[i].nextFreq;
chan[i].freqChanged=true; chan[i].freqChanged=true;
} }
chan[i].noteChanged.freq=0;
} }
chan[i].noteChanged.changed=0; chan[i].noteChanged.dummy=0;
} }
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
chan[i].freq=CLAMP_VAL(parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,chipClock,chan[c].pcm.freqOffs),0,0x1ffff); chan[i].freq=CLAMP_VAL(parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,chipClock,chan[i].pcm.freqOffs),0,0x1ffff);
if (chan[i].keyOn) { if (chan[i].keyOn) {
if (chan[i].pcm.index>=0 && chan[i].pcm.index<parent->song.sampleLen) { if (chan[i].pcm.index>=0 && chan[i].pcm.index<parent->song.sampleLen) {
chan[i].k1Prev=0xffff; chan[i].k1Prev=0xffff;
@ -693,7 +1003,8 @@ int DivPlatformES5506::dispatch(DivCommand c) {
sampleVaild=true; sampleVaild=true;
chan[c.chan].pcm.index=sample; chan[c.chan].pcm.index=sample;
chan[c.chan].pcm.pause=(chan[c.chan].std.alg.will)?(chan[c.chan].std.alg.val&1):false; chan[c.chan].pcm.pause=(chan[c.chan].std.alg.will)?(chan[c.chan].std.alg.val&1):false;
chan[c.chan].transWave.enable=ins->amiga.transWave.enable; chan[c.chan].pcm.isNoteMap=ins->amiga.useNoteMap && !ins->amiga.transWave.enable;
chan[c.chan].transWave.enable=!ins->amiga.useNoteMap && ins->amiga.transWave.enable;
chan[c.chan].transWave.sliceEnable=ins->amiga.transWave.sliceEnable; chan[c.chan].transWave.sliceEnable=ins->amiga.transWave.sliceEnable;
chan[c.chan].transWave.ind=ins->amiga.transWave.ind; chan[c.chan].transWave.ind=ins->amiga.transWave.ind;
DivSample* s=parent->getSample(sample); DivSample* s=parent->getSample(sample);
@ -716,7 +1027,7 @@ int DivPlatformES5506::dispatch(DivCommand c) {
if (ins->amiga.transWave.enable) { if (ins->amiga.transWave.enable) {
if (transWaveInd.loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) { if (transWaveInd.loopMode!=DIV_SAMPLE_LOOPMODE_ONESHOT) {
loopMode=transWaveInd.loopMode; loopMode=transWaveInd.loopMode;
} else if (!s->isLoopable()) { // default } else if ((chan[i].pcm.loopMode==DIV_SAMPLE_LOOPMODE_ONESHOT) || (!s->isLoopable())) { // default
loopMode=DIV_SAMPLE_LOOPMODE_PINGPONG; loopMode=DIV_SAMPLE_LOOPMODE_PINGPONG;
} }
// get loop position // get loop position
@ -936,10 +1247,13 @@ int DivPlatformES5506::dispatch(DivCommand c) {
case DIV_CMD_SAMPLE_POS: { case DIV_CMD_SAMPLE_POS: {
if (chan[c.chan].useWave) break; if (chan[c.chan].useWave) break;
if (chan[c.chan].active) { if (chan[c.chan].active) {
const unsigned int pos=chan[c.chan].pcm.reversed?(chan[c.chan].pcm.length-c.value):c.value; const unsigned int start=chan[c.chan].transWave.enable?chan[c.chan].pcm.loopStart:chan[c.chan].pcm.start;
if ((chan[c.chan].pcm.reversed && pos>0) || ((!chan[c.chan].pcm.reversed) && pos<chan[c.chan].pcm.length)) { const unsigned int end=chan[c.chan].transWave.enable?chan[c.chan].pcm.loopEnd:chan[c.chan].pcm.length;
pageWrite(0x20|c.chan,0x03,chan[c.chan].pcm.start+(pos<<11)); const unsigned int pos=chan[c.chan].pcm.reversed?(end-c.value):c.value;
if ((chan[c.chan].pcm.reversed && pos>0) || ((!chan[c.chan].pcm.reversed) && pos<end)) {
pageWrite(0x20|c.chan,0x03,start+(pos<<11));
} }
break;
} }
break; break;
} }
@ -995,6 +1309,7 @@ void DivPlatformES5506::reset() {
isMasked=false; isMasked=false;
isReaded=false; isReaded=false;
irqTrigger=false; irqTrigger=false;
transwaveCh=0;
prevChanCycle=0; prevChanCycle=0;
chanMax=initChanMax; chanMax=initChanMax;

View file

@ -32,6 +32,7 @@
class DivPlatformES5506: public DivDispatch, public es550x_intf { class DivPlatformES5506: public DivDispatch, public es550x_intf {
struct Channel { struct Channel {
struct PCM { struct PCM {
bool isNoteMap;
int index, next; int index, next;
int note; int note;
double freqOffs; double freqOffs;
@ -45,6 +46,7 @@ class DivPlatformES5506: public DivDispatch, public es550x_intf {
unsigned int loopEnd; unsigned int loopEnd;
DivSampleLoopMode loopMode; DivSampleLoopMode loopMode;
PCM(): PCM():
isNoteMap(false),
index(-1), index(-1),
next(-1), next(-1),
note(0), note(0),
@ -62,7 +64,8 @@ class DivPlatformES5506: public DivDispatch, public es550x_intf {
} pcm; } pcm;
int freq, baseFreq, nextFreq, pitch, pitch2, note, nextNote, prevNote, ins, wave; int freq, baseFreq, nextFreq, pitch, pitch2, note, nextNote, prevNote, ins, wave;
unsigned int volMacroMax, panMacroMax; unsigned int volMacroMax, panMacroMax;
bool active, insChanged, freqChanged, pcmChanged, keyOn, keyOff, inPorta, useWave, isReverseLoop; bool active, insChanged, freqChanged, pcmChanged, keyOn, keyOff, inPorta, useWave, isReverseLoop, isTranswave, transwaveIRQ;
unsigned int cr;
struct NoteChanged { // Note changed flags struct NoteChanged { // Note changed flags
union { // pack flag bits in single byte union { // pack flag bits in single byte
@ -125,6 +128,20 @@ class DivPlatformES5506: public DivDispatch, public es550x_intf {
changed(0) {} changed(0) {}
} envChanged; } envChanged;
struct PcmChanged {
union {
struct {
unsigned char index: 1; // sample index
unsigned char slice: 1; // transwave slice
unsigned char position: 1; // sample position in memory
unsigned char loopBank: 1; // Loop mode and Bank
unsigned char transwaveInd: 1; // transwave index
unsigned char dummy: 4; // dummy for bit padding
};
unsigned char changed;
};
} pcmChanged;
signed int k1Offs, k2Offs; signed int k1Offs, k2Offs;
signed int k1Slide, k2Slide; signed int k1Slide, k2Slide;
signed int k1Prev, k2Prev; signed int k1Prev, k2Prev;
@ -170,6 +187,9 @@ class DivPlatformES5506: public DivDispatch, public es550x_intf {
inPorta(false), inPorta(false),
useWave(false), useWave(false),
isReverseLoop(false), isReverseLoop(false),
isTranswave(false),
transwaveIRQ(false),
cr(0),
k1Offs(0), k1Offs(0),
k2Offs(0), k2Offs(0),
k1Slide(0), k1Slide(0),
@ -225,6 +245,8 @@ class DivPlatformES5506: public DivDispatch, public es550x_intf {
unsigned int irqv; unsigned int irqv;
bool isMasked, isReaded; bool isMasked, isReaded;
bool irqTrigger; bool irqTrigger;
unsigned int curCR;
unsigned char transwaveCh;
unsigned char prevChanCycle; unsigned char prevChanCycle;
unsigned char initChanMax, chanMax; unsigned char initChanMax, chanMax;

View file

@ -357,7 +357,9 @@ void DivPlatformN163::tick(bool sysTick) {
chan[i].waveUpdated=false; chan[i].waveUpdated=false;
} }
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
chan[i].freq=parent->calcFreq((((chan[i].baseFreq*chan[i].waveLen)*(chanMax+1))/16),chan[i].pitch,false,0,chan[i].pitch2,chipClock,CHIP_FREQBASE); // TODO: what is this mess?
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,0,chan[i].pitch2,chipClock,CHIP_FREQBASE);
chan[i].freq=(((chan[i].freq*chan[i].waveLen)*(chanMax+1))/16);
if (chan[i].freq<0) chan[i].freq=0; if (chan[i].freq<0) chan[i].freq=0;
if (chan[i].freq>0x3ffff) chan[i].freq=0x3ffff; if (chan[i].freq>0x3ffff) chan[i].freq=0x3ffff;
if (chan[i].keyOn) { if (chan[i].keyOn) {

View file

@ -595,6 +595,7 @@ void DivPlatformOPL::tick(bool sysTick) {
for (int i=0; i<totalChans; i++) { for (int i=0; i<totalChans; i++) {
if (chan[i].freqChanged) { if (chan[i].freqChanged) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq),chan[i].pitch2,chipClock,CHIP_FREQBASE); chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq),chan[i].pitch2,chipClock,CHIP_FREQBASE);
if (chan[i].fixedFreq>0) chan[i].freq=chan[i].fixedFreq;
if (chan[i].freq>131071) chan[i].freq=131071; if (chan[i].freq>131071) chan[i].freq=131071;
int freqt=toFreq(chan[i].freq)+chan[i].pitch2; int freqt=toFreq(chan[i].freq)+chan[i].pitch2;
chan[i].freqH=freqt>>8; chan[i].freqH=freqt>>8;
@ -844,16 +845,18 @@ int DivPlatformOPL::dispatch(DivCommand c) {
if (c.value!=DIV_NOTE_NULL) { if (c.value!=DIV_NOTE_NULL) {
if (c.chan>=melodicChans && chan[c.chan].state.opllPreset==16 && chan[c.chan].state.fixedDrums) { // drums if (c.chan>=melodicChans && chan[c.chan].state.opllPreset==16 && chan[c.chan].state.fixedDrums) { // drums
if (c.chan==melodicChans) { if (c.chan==melodicChans) {
chan[c.chan].baseFreq=(chan[c.chan].state.kickFreq&1023)<<(chan[c.chan].state.kickFreq>>10); chan[c.chan].fixedFreq=(chan[c.chan].state.kickFreq&1023)<<(chan[c.chan].state.kickFreq>>10);
} else if (c.chan==melodicChans+1 || c.chan==melodicChans+4) { } else if (c.chan==melodicChans+1 || c.chan==melodicChans+4) {
chan[c.chan].baseFreq=(chan[c.chan].state.snareHatFreq&1023)<<(chan[c.chan].state.snareHatFreq>>10); chan[c.chan].fixedFreq=(chan[c.chan].state.snareHatFreq&1023)<<(chan[c.chan].state.snareHatFreq>>10);
} else if (c.chan==melodicChans+2 || c.chan==melodicChans+3) { } else if (c.chan==melodicChans+2 || c.chan==melodicChans+3) {
chan[c.chan].baseFreq=(chan[c.chan].state.tomTopFreq&1023)<<(chan[c.chan].state.tomTopFreq>>10); chan[c.chan].fixedFreq=(chan[c.chan].state.tomTopFreq&1023)<<(chan[c.chan].state.tomTopFreq>>10);
} else { } else {
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
chan[c.chan].fixedFreq=0;
} }
} else { } else {
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
chan[c.chan].fixedFreq=0;
} }
chan[c.chan].note=c.value; chan[c.chan].note=c.value;
chan[c.chan].freqChanged=true; chan[c.chan].freqChanged=true;

View file

@ -40,7 +40,7 @@ class DivPlatformOPL: public DivDispatch {
DivInstrumentFM state; DivInstrumentFM state;
DivMacroInt std; DivMacroInt std;
unsigned char freqH, freqL; unsigned char freqH, freqL;
int freq, baseFreq, pitch, pitch2, note, ins, sample; int freq, baseFreq, pitch, pitch2, note, ins, sample, fixedFreq;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnacePCM, inPorta, fourOp, hardReset; bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnacePCM, inPorta, fourOp, hardReset;
int vol, outVol; int vol, outVol;
unsigned char pan; unsigned char pan;
@ -58,6 +58,7 @@ class DivPlatformOPL: public DivDispatch {
note(0), note(0),
ins(-1), ins(-1),
sample(-1), sample(-1),
fixedFreq(0),
active(false), active(false),
insChanged(true), insChanged(true),
freqChanged(false), freqChanged(false),

View file

@ -301,6 +301,7 @@ void DivPlatformOPLL::tick(bool sysTick) {
for (int i=0; i<11; i++) { for (int i=0; i<11; i++) {
if (chan[i].freqChanged) { if (chan[i].freqChanged) {
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq),chan[i].pitch2,chipClock,CHIP_FREQBASE); chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq),chan[i].pitch2,chipClock,CHIP_FREQBASE);
if (chan[i].fixedFreq>0) chan[i].freq=chan[i].fixedFreq;
if (chan[i].freq>262143) chan[i].freq=262143; if (chan[i].freq>262143) chan[i].freq=262143;
int freqt=toFreq(chan[i].freq)+chan[i].pitch2; int freqt=toFreq(chan[i].freq)+chan[i].pitch2;
chan[i].freqL=freqt&0xff; chan[i].freqL=freqt&0xff;
@ -420,15 +421,16 @@ int DivPlatformOPLL::dispatch(DivCommand c) {
if (chan[c.chan].state.opllPreset==16 && chan[c.chan].state.fixedDrums) { if (chan[c.chan].state.opllPreset==16 && chan[c.chan].state.fixedDrums) {
switch (c.chan) { switch (c.chan) {
case 6: case 6:
chan[c.chan].baseFreq=(chan[c.chan].state.kickFreq&511)<<(chan[c.chan].state.kickFreq>>9); chan[c.chan].fixedFreq=(chan[c.chan].state.kickFreq&511)<<(chan[c.chan].state.kickFreq>>9);
break; break;
case 7: case 10: case 7: case 10:
chan[c.chan].baseFreq=(chan[c.chan].state.snareHatFreq&511)<<(chan[c.chan].state.snareHatFreq>>9); chan[c.chan].fixedFreq=(chan[c.chan].state.snareHatFreq&511)<<(chan[c.chan].state.snareHatFreq>>9);
break; break;
case 8: case 9: case 8: case 9:
chan[c.chan].baseFreq=(chan[c.chan].state.tomTopFreq&511)<<(chan[c.chan].state.tomTopFreq>>9); chan[c.chan].fixedFreq=(chan[c.chan].state.tomTopFreq&511)<<(chan[c.chan].state.tomTopFreq>>9);
break; break;
default: default:
chan[c.chan].fixedFreq=0;
chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value);
break; break;
} }

View file

@ -33,7 +33,7 @@ class DivPlatformOPLL: public DivDispatch {
DivInstrumentFM state; DivInstrumentFM state;
DivMacroInt std; DivMacroInt std;
unsigned char freqH, freqL; unsigned char freqH, freqL;
int freq, baseFreq, pitch, pitch2, note, ins; int freq, baseFreq, pitch, pitch2, note, ins, fixedFreq;
bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnaceDac, inPorta; bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnaceDac, inPorta;
int vol, outVol; int vol, outVol;
unsigned char pan; unsigned char pan;
@ -50,6 +50,7 @@ class DivPlatformOPLL: public DivDispatch {
pitch2(0), pitch2(0),
note(0), note(0),
ins(-1), ins(-1),
fixedFreq(0),
active(false), active(false),
insChanged(true), insChanged(true),
freqChanged(false), freqChanged(false),

View file

@ -155,7 +155,7 @@ void DivPlatformPCE::tick(bool sysTick) {
chan[i].std.next(); chan[i].std.next();
if (chan[i].std.vol.had) { if (chan[i].std.vol.had) {
chan[i].outVol=((chan[i].vol&31)*MIN(31,chan[i].std.vol.val))>>5; chan[i].outVol=((chan[i].vol&31)*MIN(31,chan[i].std.vol.val))>>5;
if (chan[i].furnaceDac) { if (chan[i].furnaceDac && chan[i].pcm) {
// ignore for now // ignore for now
} else { } else {
chWrite(i,0x04,0x80|chan[i].outVol); chWrite(i,0x04,0x80|chan[i].outVol);
@ -228,7 +228,7 @@ void DivPlatformPCE::tick(bool sysTick) {
if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) {
//DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_PCE); //DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_PCE);
chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER); chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER);
if (chan[i].furnaceDac) { if (chan[i].furnaceDac && chan[i].pcm) {
double off=1.0; double off=1.0;
if (chan[i].dacSample>=0 && chan[i].dacSample<parent->song.sampleLen) { if (chan[i].dacSample>=0 && chan[i].dacSample<parent->song.sampleLen) {
DivSample* s=parent->getSample(chan[i].dacSample); DivSample* s=parent->getSample(chan[i].dacSample);
@ -268,8 +268,9 @@ int DivPlatformPCE::dispatch(DivCommand c) {
chan[c.chan].pcm=false; chan[c.chan].pcm=false;
} }
if (chan[c.chan].pcm) { if (chan[c.chan].pcm) {
if (skipRegisterWrites) break;
if (ins->type==DIV_INS_AMIGA) { if (ins->type==DIV_INS_AMIGA) {
chan[c.chan].furnaceDac=true;
if (skipRegisterWrites) break;
chan[c.chan].dacSample=ins->amiga.getSample(c.value); chan[c.chan].dacSample=ins->amiga.getSample(c.value);
if (chan[c.chan].dacSample<0 || chan[c.chan].dacSample>=parent->song.sampleLen) { if (chan[c.chan].dacSample<0 || chan[c.chan].dacSample>=parent->song.sampleLen) {
chan[c.chan].dacSample=-1; chan[c.chan].dacSample=-1;
@ -291,8 +292,9 @@ int DivPlatformPCE::dispatch(DivCommand c) {
chan[c.chan].active=true; chan[c.chan].active=true;
chan[c.chan].macroInit(ins); chan[c.chan].macroInit(ins);
//chan[c.chan].keyOn=true; //chan[c.chan].keyOn=true;
chan[c.chan].furnaceDac=true;
} else { } else {
chan[c.chan].furnaceDac=false;
if (skipRegisterWrites) break;
if (c.value!=DIV_NOTE_NULL) { if (c.value!=DIV_NOTE_NULL) {
chan[c.chan].note=c.value; chan[c.chan].note=c.value;
} }
@ -311,7 +313,6 @@ int DivPlatformPCE::dispatch(DivCommand c) {
chWrite(c.chan,0x04,0xdf); chWrite(c.chan,0x04,0xdf);
addWrite(0xffff0001+(c.chan<<8),chan[c.chan].dacRate); addWrite(0xffff0001+(c.chan<<8),chan[c.chan].dacRate);
} }
chan[c.chan].furnaceDac=false;
} }
break; break;
} }

View file

@ -181,11 +181,13 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) {
chan[c.chan].macroInit(ins); chan[c.chan].macroInit(ins);
if (dumpWrites) { // Sega PCM writes if (dumpWrites) { // Sega PCM writes
DivSample* s=parent->getSample(chan[c.chan].pcm.sample); DivSample* s=parent->getSample(chan[c.chan].pcm.sample);
int actualLength=(int)(s->isLoopable()?s->loopEnd:s->length8);
if (actualLength>0xfeff) actualLength=0xfeff;
addWrite(0x10086+(c.chan<<3),3+((s->offSegaPCM>>16)<<3)); addWrite(0x10086+(c.chan<<3),3+((s->offSegaPCM>>16)<<3));
addWrite(0x10084+(c.chan<<3),(s->offSegaPCM)&0xff); addWrite(0x10084+(c.chan<<3),(s->offSegaPCM)&0xff);
addWrite(0x10085+(c.chan<<3),(s->offSegaPCM>>8)&0xff); addWrite(0x10085+(c.chan<<3),(s->offSegaPCM>>8)&0xff);
addWrite(0x10006+(c.chan<<3),MIN(255,((s->offSegaPCM&0xffff)+s->length8-1)>>8)); addWrite(0x10006+(c.chan<<3),MIN(255,((s->offSegaPCM&0xffff)+actualLength-1)>>8));
if (!s->isLoopable()) { if ((s->loopMode==DIV_SAMPLE_LOOPMODE_ONESHOT) || (s->loopStart<0 || s->loopStart>=actualLength || s->loopEnd<=s->loopStart || s->loopEnd>=actualLength)) {
addWrite(0x10086+(c.chan<<3),2+((s->offSegaPCM>>16)<<3)); addWrite(0x10086+(c.chan<<3),2+((s->offSegaPCM>>16)<<3));
} else { } else {
int loopPos=(s->offSegaPCM&0xffff)+s->loopStart+s->loopOffP; int loopPos=(s->offSegaPCM&0xffff)+s->loopStart+s->loopOffP;
@ -212,11 +214,13 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) {
chan[c.chan].furnacePCM=false; chan[c.chan].furnacePCM=false;
if (dumpWrites) { // Sega PCM writes if (dumpWrites) { // Sega PCM writes
DivSample* s=parent->getSample(chan[c.chan].pcm.sample); DivSample* s=parent->getSample(chan[c.chan].pcm.sample);
int actualLength=(int)(s->isLoopable()?s->loopEnd:s->length8);
if (actualLength>65536) actualLength=65536;
addWrite(0x10086+(c.chan<<3),3+((s->offSegaPCM>>16)<<3)); addWrite(0x10086+(c.chan<<3),3+((s->offSegaPCM>>16)<<3));
addWrite(0x10084+(c.chan<<3),(s->offSegaPCM)&0xff); addWrite(0x10084+(c.chan<<3),(s->offSegaPCM)&0xff);
addWrite(0x10085+(c.chan<<3),(s->offSegaPCM>>8)&0xff); addWrite(0x10085+(c.chan<<3),(s->offSegaPCM>>8)&0xff);
addWrite(0x10006+(c.chan<<3),MIN(255,((s->offSegaPCM&0xffff)+s->length8-1)>>8)); addWrite(0x10006+(c.chan<<3),MIN(255,((s->offSegaPCM&0xffff)+actualLength-1)>>8));
if (!s->isLoopable()) { if ((s->loopMode==DIV_SAMPLE_LOOPMODE_ONESHOT) || (s->loopStart<0 || s->loopStart>=actualLength || s->loopEnd<=s->loopStart || s->loopEnd>=actualLength)) {
addWrite(0x10086+(c.chan<<3),2+((s->offSegaPCM>>16)<<3)); addWrite(0x10086+(c.chan<<3),2+((s->offSegaPCM>>16)<<3));
} else { } else {
int loopPos=(s->offSegaPCM&0xffff)+s->loopStart+s->loopOffP; int loopPos=(s->offSegaPCM&0xffff)+s->loopStart+s->loopOffP;

View file

@ -1061,7 +1061,7 @@ void ay8910_device::sound_stream_update(short** outputs, int outLen)
for (int chan = 0; chan < NUM_CHANNELS; chan++) for (int chan = 0; chan < NUM_CHANNELS; chan++)
{ {
tone = &m_tone[chan]; tone = &m_tone[chan];
const int period = tone->period * (m_step_mul << 1); const int period = std::max<int>(1, tone->period) * (m_step_mul << 1);
tone->count += is_expanded_mode() ? 32 : ((m_feature & PSG_HAS_EXPANDED_MODE) ? 1 : 2); tone->count += is_expanded_mode() ? 32 : ((m_feature & PSG_HAS_EXPANDED_MODE) ? 1 : 2);
while (tone->count >= period) while (tone->count >= period)
{ {
@ -1115,7 +1115,7 @@ void ay8910_device::sound_stream_update(short** outputs, int outLen)
envelope = &m_envelope[chan]; envelope = &m_envelope[chan];
if (envelope->holding == 0) if (envelope->holding == 0)
{ {
const int period = envelope->period * m_env_step_mul; const int period = std::max<int>(1, envelope->period) * m_env_step_mul;
if ((++envelope->count) >= period) if ((++envelope->count) >= period)
{ {
envelope->count = 0; envelope->count = 0;

View file

@ -207,13 +207,13 @@ void DivPlatformSoundUnit::tick(bool sysTick) {
DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU); DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_SU);
DivSample* sample=parent->getSample(ins->amiga.getSample(chan[i].note)); DivSample* sample=parent->getSample(ins->amiga.getSample(chan[i].note));
if (sample!=NULL) { if (sample!=NULL) {
unsigned int sampleEnd=sample->offSU+sample->samples; unsigned int sampleEnd=sample->offSU+(s->isLoopable()?sample->loopEnd:sample->samples);
if (sampleEnd>=getSampleMemCapacity(0)) sampleEnd=getSampleMemCapacity(0)-1; if (sampleEnd>=getSampleMemCapacity(0)) sampleEnd=getSampleMemCapacity(0)-1;
chWrite(i,0x0a,sample->offSU&0xff); chWrite(i,0x0a,sample->offSU&0xff);
chWrite(i,0x0b,sample->offSU>>8); chWrite(i,0x0b,sample->offSU>>8);
chWrite(i,0x0c,sampleEnd&0xff); chWrite(i,0x0c,sampleEnd&0xff);
chWrite(i,0x0d,sampleEnd>>8); chWrite(i,0x0d,sampleEnd>>8);
if (sample->loopStart>=0 && sample->loopStart<(int)sample->samples) { if (s->isLoopable()) {
unsigned int sampleLoop=sample->offSU+sample->loopStart; unsigned int sampleLoop=sample->offSU+sample->loopStart;
if (sampleLoop>=getSampleMemCapacity(0)) sampleLoop=getSampleMemCapacity(0)-1; if (sampleLoop>=getSampleMemCapacity(0)) sampleLoop=getSampleMemCapacity(0)-1;
chWrite(i,0x0e,sampleLoop&0xff); chWrite(i,0x0e,sampleLoop&0xff);

View file

@ -1057,19 +1057,19 @@ void DivPlatformYM2203::setSkipRegisterWrites(bool value) {
} }
void DivPlatformYM2203::setFlags(unsigned int flags) { void DivPlatformYM2203::setFlags(unsigned int flags) {
unsigned char ayFlags=32; unsigned char ayFlags=16;
if (flags==3) { if (flags==3) {
chipClock=3000000.0; chipClock=3000000.0;
ayFlags=36; ayFlags=20;
} else if (flags==2) { } else if (flags==2) {
chipClock=4000000.0; chipClock=4000000.0;
ayFlags=35; ayFlags=19;
} else if (flags==1) { } else if (flags==1) {
chipClock=COLOR_PAL*4.0/5.0; chipClock=COLOR_PAL*4.0/5.0;
ayFlags=33; ayFlags=17;
} else { } else {
chipClock=COLOR_NTSC; chipClock=COLOR_NTSC;
ayFlags=32; ayFlags=16;
} }
ay->setFlags(ayFlags); ay->setFlags(ayFlags);
rate=fm->sample_rate(chipClock); rate=fm->sample_rate(chipClock);
@ -1090,7 +1090,7 @@ int DivPlatformYM2203::init(DivEngine* p, int channels, int sugRate, unsigned in
fm->set_fidelity(ymfm::OPN_FIDELITY_MIN); fm->set_fidelity(ymfm::OPN_FIDELITY_MIN);
// YM2149, 2MHz // YM2149, 2MHz
ay=new DivPlatformAY8910; ay=new DivPlatformAY8910;
ay->init(p,3,sugRate,35); ay->init(p,3,sugRate,19);
ay->toggleRegisterDump(true); ay->toggleRegisterDump(true);
setFlags(flags); setFlags(flags);

View file

@ -21,7 +21,7 @@
#include "../engine.h" #include "../engine.h"
#include <math.h> #include <math.h>
#include "ym2610shared.h" #include "ym2203shared.h"
#include "fmshared_OPN.h" #include "fmshared_OPN.h"
int DivPlatformYM2203Ext::dispatch(DivCommand c) { int DivPlatformYM2203Ext::dispatch(DivCommand c) {
@ -489,9 +489,6 @@ void DivPlatformYM2203Ext::forceIns() {
chan[i].freqChanged=true; chan[i].freqChanged=true;
} }
} }
for (int i=3; i<6; i++) {
chan[i].insChanged=true;
}
ay->forceIns(); ay->forceIns();
ay->flushWrites(); ay->flushWrites();

View file

@ -1459,7 +1459,7 @@ int DivPlatformYM2608::init(DivEngine* p, int channels, int sugRate, unsigned in
} }
// YM2149, 2MHz // YM2149, 2MHz
ay=new DivPlatformAY8910; ay=new DivPlatformAY8910;
ay->init(p,3,sugRate,35); ay->init(p,3,sugRate,19);
ay->toggleRegisterDump(true); ay->toggleRegisterDump(true);
reset(); reset();
return 16; return 16;

View file

@ -1454,7 +1454,7 @@ int DivPlatformYM2610::init(DivEngine* p, int channels, int sugRate, unsigned in
fm=new ymfm::ym2610(iface); fm=new ymfm::ym2610(iface);
// YM2149, 2MHz // YM2149, 2MHz
ay=new DivPlatformAY8910; ay=new DivPlatformAY8910;
ay->init(p,3,sugRate,35); ay->init(p,3,sugRate,19);
ay->toggleRegisterDump(true); ay->toggleRegisterDump(true);
reset(); reset();
return 14; return 14;

View file

@ -1432,7 +1432,7 @@ int DivPlatformYM2610B::init(DivEngine* p, int channels, int sugRate, unsigned i
fm=new ymfm::ym2610b(iface); fm=new ymfm::ym2610b(iface);
// YM2149, 2MHz // YM2149, 2MHz
ay=new DivPlatformAY8910; ay=new DivPlatformAY8910;
ay->init(p,3,sugRate,35); ay->init(p,3,sugRate,19);
ay->toggleRegisterDump(true); ay->toggleRegisterDump(true);
reset(); reset();
return 16; return 16;

View file

@ -29,20 +29,11 @@ const char** DivPlatformZXBeeper::getRegisterSheet() {
const char* DivPlatformZXBeeper::getEffectName(unsigned char effect) { const char* DivPlatformZXBeeper::getEffectName(unsigned char effect) {
switch (effect) { switch (effect) {
case 0x10:
return "10xx: Change waveform";
break;
case 0x11:
return "11xx: Toggle noise mode";
break;
case 0x12: case 0x12:
return "12xx: Setup LFO (0: disabled; 1: 1x depth; 2: 16x depth; 3: 256x depth)"; return "12xx: Set pulse width";
break;
case 0x13:
return "13xx: Set LFO speed";
break; break;
case 0x17: case 0x17:
return "17xx: Toggle PCM mode"; return "17xx: Trigger overlay drum";
break; break;
} }
return NULL; return NULL;

View file

@ -170,8 +170,8 @@ String SafeReader::readStringLine() {
unsigned char c; unsigned char c;
if (isEOF()) throw EndOfFileException(this, len); if (isEOF()) throw EndOfFileException(this, len);
while (!isEOF() && (c = readC()) != 0) { while (!isEOF() && (c=readC())!=0) {
if (c=='\r'||c=='\n') { if (c=='\r' || c=='\n') {
break; break;
} }
ret.push_back(c); ret.push_back(c);
@ -179,17 +179,17 @@ String SafeReader::readStringLine() {
return ret; return ret;
} }
String SafeReader::readStringToken(unsigned char delim) { String SafeReader::readStringToken(unsigned char delim, bool stripContiguous) {
String ret; String ret;
unsigned char c; unsigned char c;
if (isEOF()) throw EndOfFileException(this, len); if (isEOF()) throw EndOfFileException(this, len);
while (!isEOF() && (c=readC())!=0) { while (!isEOF() && (c=readC())!=0) {
if (c == '\r' || c == '\n') { if (c=='\r' || c=='\n') {
break; break;
} }
if (c == delim) { if (c==delim) {
if (ret.length() == 0) { if (ret.length()==0 && stripContiguous) {
continue; continue;
} }
break; break;
@ -200,5 +200,6 @@ String SafeReader::readStringToken(unsigned char delim) {
} }
String SafeReader::readStringToken() { String SafeReader::readStringToken() {
return readStringToken(' '); // This will strip LHS whitespace and only return contents after it.
return readStringToken(' ', true);
} }

View file

@ -67,7 +67,7 @@ class SafeReader {
String readString(); String readString();
String readString(size_t len); String readString(size_t len);
String readStringLine(); String readStringLine();
String readStringToken(unsigned char delim); String readStringToken(unsigned char delim, bool stripContiguous);
String readStringToken(); String readStringToken();
inline bool isEOF() { return curSeek >= len; }; inline bool isEOF() { return curSeek >= len; };

View file

@ -443,6 +443,13 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write
w->writeC(0x04); w->writeC(0x04);
w->writeC(0x00); w->writeC(0x00);
break; break;
case DIV_SYSTEM_SCC:
case DIV_SYSTEM_SCC_PLUS:
w->writeC(0xd2);
w->writeC(baseAddr2|3);
w->writeC(0);
w->writeC(0);
break;
default: default:
break; break;
} }
@ -1395,16 +1402,17 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) {
size_t memPos=0; size_t memPos=0;
for (int i=0; i<song.sampleLen; i++) { for (int i=0; i<song.sampleLen; i++) {
DivSample* sample=song.sample[i]; DivSample* sample=song.sample[i];
if ((memPos&0xff0000)!=((memPos+sample->length8)&0xff0000)) { unsigned int alignedSize=(sample->length8+0xff)&(~0xff);
if (alignedSize>65536) alignedSize=65536;
if ((memPos&0xff0000)!=((memPos+alignedSize)&0xff0000)) {
memPos=(memPos+0xffff)&0xff0000; memPos=(memPos+0xffff)&0xff0000;
} }
logV("- sample %d will be at %x with length %x",i,memPos,alignedSize);
if (memPos>=16777216) break; if (memPos>=16777216) break;
sample->offSegaPCM=memPos; sample->offSegaPCM=memPos;
unsigned int alignedSize=(sample->length8+0xff)&(~0xff);
unsigned int readPos=0; unsigned int readPos=0;
if (alignedSize>65536) alignedSize=65536;
for (unsigned int j=0; j<alignedSize; j++) { for (unsigned int j=0; j<alignedSize; j++) {
if ((sample->loopMode && readPos>=sample->loopEnd) || readPos>=sample->length8) { if (((sample->loopMode != DIV_SAMPLE_LOOPMODE_ONESHOT) && readPos>=sample->loopEnd) || readPos>=sample->length8) {
if (sample->isLoopable()) { if (sample->isLoopable()) {
readPos=sample->loopStart; readPos=sample->loopStart;
pcmMem[memPos++]=((unsigned char)sample->data8[readPos]+0x80); pcmMem[memPos++]=((unsigned char)sample->data8[readPos]+0x80);

View file

@ -1221,9 +1221,9 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
if (!dirExists(workingDirSong)) workingDirSong=getHomeDir(); if (!dirExists(workingDirSong)) workingDirSong=getHomeDir();
hasOpened=fileDialog->openLoad( hasOpened=fileDialog->openLoad(
"Open File", "Open File",
{"compatible files", "*.fur *.dmf *.mod *.ftm", {"compatible files", "*.fur *.dmf *.mod",
"all files", ".*"}, "all files", ".*"},
"compatible files{.fur,.dmf,.mod,.ftm},.*", "compatible files{.fur,.dmf,.mod},.*",
workingDirSong, workingDirSong,
dpiScale dpiScale
); );
@ -1261,9 +1261,25 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
if (!dirExists(workingDirIns)) workingDirIns=getHomeDir(); if (!dirExists(workingDirIns)) workingDirIns=getHomeDir();
hasOpened=fileDialog->openLoad( hasOpened=fileDialog->openLoad(
"Load Instrument", "Load Instrument",
{"compatible files", "*.fui *.dmp *.tfi *.vgi *.s3i *.sbi *.opli *.opni *.y12 *.bnk *.ff *.opm", // TODO supply loadable formats in a dynamic, scalable, "DRY" way.
{"all compatible files", "*.fui *.dmp *.tfi *.vgi *.s3i *.sbi *.opli *.opni *.y12 *.bnk *.ff *.gyb *.opm *.wopl *.wopn",
"Furnace instrument", "*.fui",
"DefleMask preset", "*.dmp",
"TFM Music Maker instrument", "*.tfi",
"VGM Music Maker instrument", "*.vgi",
"Scream Tracker 3 instrument", "*.s3i",
"SoundBlaster instrument", "*.sbi",
"Wohlstand OPL instrument", "*.opli",
"Wohlstand OPN instrument", "*.opni",
"Gens KMod patch dump", "*.y12",
"BNK file (AdLib)", "*.bnk",
"FF preset bank", "*.ff",
"2612edit GYB preset bank", "*.gyb",
"VOPM preset bank", "*.opm",
"Wohlstand WOPL bank", "*.wopl",
"Wohlstand WOPN bank", "*.wopn",
"all files", ".*"}, "all files", ".*"},
"compatible files{.fui,.dmp,.tfi,.vgi,.s3i,.sbi,.opli,.opni,.y12,.bnk,.ff,.opm},.*", "all compatible files{.fui,.dmp,.tfi,.vgi,.s3i,.sbi,.opli,.opni,.y12,.bnk,.ff,.gyb,.opm,.wopl,.wopn},.*",
workingDirIns, workingDirIns,
dpiScale, dpiScale,
[this](const char* path) { [this](const char* path) {
@ -3476,10 +3492,34 @@ bool FurnaceGUI::loop() {
} }
break; break;
case GUI_WARN_CLEAR: case GUI_WARN_CLEAR:
if (ImGui::Button("Song (orders and patterns)")) { if (ImGui::Button("All subsongs")) {
stop();
e->clearSubSongs();
curOrder=0;
oldOrder=0;
oldOrder1=0;
MARK_MODIFIED;
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button("Current subsong")) {
stop(); stop();
e->lockEngine([this]() { e->lockEngine([this]() {
e->song.clearSongData(); e->curSubSong->clearData();
});
e->setOrder(0);
curOrder=0;
oldOrder=0;
oldOrder1=0;
MARK_MODIFIED;
ImGui::CloseCurrentPopup();
}
ImGui::SameLine();
if (ImGui::Button("Orders")) {
stop();
e->lockEngine([this]() {
memset(e->curOrders->ord,0,DIV_MAX_CHANS*256);
e->curSubSong->ordersLen=1;
}); });
e->setOrder(0); e->setOrder(0);
curOrder=0; curOrder=0;
@ -3550,7 +3590,7 @@ bool FurnaceGUI::loop() {
// backup trigger // backup trigger
if (modified) { if (modified) {
if (backupTimer>0) { if (backupTimer>0) {
backupTimer-=ImGui::GetIO().DeltaTime; backupTimer=(backupTimer-ImGui::GetIO().DeltaTime);
if (backupTimer<=0) { if (backupTimer<=0) {
backupTask=std::async(std::launch::async,[this]() -> bool { backupTask=std::async(std::launch::async,[this]() -> bool {
if (backupPath==curFileName) { if (backupPath==curFileName) {

View file

@ -801,7 +801,7 @@ class FurnaceGUI {
double aboutScroll, aboutSin; double aboutScroll, aboutSin;
float aboutHue; float aboutHue;
double backupTimer; std::atomic<double> backupTimer;
std::future<bool> backupTask; std::future<bool> backupTask;
std::mutex backupLock; std::mutex backupLock;
String backupPath; String backupPath;

View file

@ -2790,6 +2790,11 @@ void FurnaceGUI::drawInsEdit() {
} }
ImGui::EndTable(); ImGui::EndTable();
} }
if (ImGui::BeginTabItem("Transwave Macros")) {
macroList.push_back(FurnaceGUIMacroDesc("Transwave control",&ins->std.fbMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,false,NULL,NULL,true,transwaveControlModes));
macroList.push_back(FurnaceGUIMacroDesc("Transwave slice",&ins->std.fmsMacro,0,4095,160,uiColors[GUI_COLOR_MACRO_OTHER]));
ImGui::EndTabItem();
}
} }
ImGui::EndDisabled(); ImGui::EndDisabled();
ImGui::EndTabItem(); ImGui::EndTabItem();
@ -3438,8 +3443,6 @@ void FurnaceGUI::drawInsEdit() {
macroList.push_back(FurnaceGUIMacroDesc("Envelope K2 ramp",&ins->std.ex7Macro,-128,127,160,uiColors[GUI_COLOR_MACRO_OTHER])); macroList.push_back(FurnaceGUIMacroDesc("Envelope K2 ramp",&ins->std.ex7Macro,-128,127,160,uiColors[GUI_COLOR_MACRO_OTHER]));
macroList.push_back(FurnaceGUIMacroDesc("Envelope mode",&ins->std.ex8Macro,0,2,64,uiColors[GUI_COLOR_MACRO_OTHER],false,false,NULL,NULL,true,es5506EnvelopeModes)); macroList.push_back(FurnaceGUIMacroDesc("Envelope mode",&ins->std.ex8Macro,0,2,64,uiColors[GUI_COLOR_MACRO_OTHER],false,false,NULL,NULL,true,es5506EnvelopeModes));
macroList.push_back(FurnaceGUIMacroDesc("Control",&ins->std.algMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,false,NULL,NULL,true,es5506ControlModes)); macroList.push_back(FurnaceGUIMacroDesc("Control",&ins->std.algMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,false,NULL,NULL,true,es5506ControlModes));
macroList.push_back(FurnaceGUIMacroDesc("Transwave control",&ins->std.fbMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,false,NULL,NULL,true,transwaveControlModes));
macroList.push_back(FurnaceGUIMacroDesc("Transwave slice",&ins->std.fmsMacro,0,4095,160,uiColors[GUI_COLOR_MACRO_OTHER]));
} }
if (ins->type==DIV_INS_SU) { if (ins->type==DIV_INS_SU) {
macroList.push_back(FurnaceGUIMacroDesc("Control",&ins->std.ex3Macro,0,4,64,uiColors[GUI_COLOR_MACRO_OTHER],false,false,NULL,NULL,true,suControlBits)); macroList.push_back(FurnaceGUIMacroDesc("Control",&ins->std.ex3Macro,0,4,64,uiColors[GUI_COLOR_MACRO_OTHER],false,false,NULL,NULL,true,suControlBits));