diff --git a/papers/doc/3-pattern/effects.md b/papers/doc/3-pattern/effects.md index ce47e4849..d544c1ea6 100644 --- a/papers/doc/3-pattern/effects.md +++ b/papers/doc/3-pattern/effects.md @@ -10,6 +10,8 @@ however, effects are continuous, which means you only need to type it once and t - a note must be present for this effect to work. - `04xy`: vibrato. `x` is the speed, while `y` is the depth. - maximum vibrato depth is ±1 semitone. +- `07xy`: tremolo. `x` is the speed, while `y` is the depth. + - maximum tremolo depth is -60 volume steps. - `08xy`: set panning. `x` is the left channel and `y` is the right one. - not all systems support this effect. - `09xx`: set speed 1. @@ -22,6 +24,9 @@ however, effects are continuous, which means you only need to type it once and t - `0Dxx`: jump to next pattern. - `0Fxx`: set speed 2. +- `9xxx`: set sample position to `xxx`\*0x100. + - not all systems support this effect. + - `Cxxx`: change song Hz. - `xxx` may be from `000` to `3ff`. @@ -46,6 +51,14 @@ however, effects are continuous, which means you only need to type it once and t - `EFxx`: add or subtract global pitch. - this effect is rather weird. use with caution. - `80` is center. +- `F0xx`: change song Hz by BPM value. +- `F1xx`: single tick slide up. +- `F2xx`: single tick slide down. +- `F8xx`: single tick volume slide up. +- `F9xx`: single tick volume slide down. +- `FAxy`: fast volume slide (4x faster than `0Axy`). + - if `x` is 0 then this is a slide down. + - if `y` is 0 then this is a slide up. - `FFxx`: end of song/stop playback. additionally each system has its own effects. [click here for more details](../7-systems/README.md). diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index 130841d95..0ec3a295b 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -54,6 +54,7 @@ enum DivDispatchCmds { DIV_CMD_SAMPLE_MODE, DIV_CMD_SAMPLE_FREQ, DIV_CMD_SAMPLE_BANK, + DIV_CMD_SAMPLE_POS, DIV_CMD_FM_LFO, DIV_CMD_FM_LFO_WAVE, diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index a2fe24ed2..c16392772 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -55,6 +55,8 @@ const char* DivEngine::getEffectDesc(unsigned char effect, int chan) { return "03xx: Portamento"; case 0x04: return "04xy: Vibrato (x: speed; y: depth)"; + case 0x07: + return "07xy: Tremolo (x: speed; y: depth)"; case 0x08: return "08xy: Set panning (x: left; y: right)"; case 0x09: @@ -70,7 +72,7 @@ const char* DivEngine::getEffectDesc(unsigned char effect, int chan) { case 0x0f: return "0Fxx: Set speed 2"; case 0xc0: case 0xc1: case 0xc2: case 0xc3: - return "Cxxx: Set tick rate"; + return "Cxxx: Set tick rate (hz)"; case 0xe0: return "E0xx: Set arp speed"; case 0xe1: @@ -95,10 +97,25 @@ const char* DivEngine::getEffectDesc(unsigned char effect, int chan) { return "EExx: Send external command"; case 0xef: return "EFxx: Set global tuning (quirky!)"; + case 0xf0: + return "F0xx: Set tick rate (bpm)"; + case 0xf1: + return "F1xx: Single tick note slide up"; + case 0xf2: + return "F2xx: Single tick note slide down"; + case 0xf8: + return "F8xx: Single tick volume slide up"; + case 0xf9: + return "F9xx: Single tick volume slide down"; + case 0xfa: + return "FAxx: Fast volume slide (0y: down; x0: up)"; case 0xff: return "FFxx: Stop song"; default: - if (chan>=0 && chan=0 && changetEffectName(effect); if (ret!=NULL) return ret; } diff --git a/src/engine/engine.h b/src/engine/engine.h index b75fc2f07..c2b6c443e 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -242,6 +242,7 @@ class DivEngine { bool loadDMF(unsigned char* file, size_t len); bool loadFur(unsigned char* file, size_t len); + bool loadMod(unsigned char* file, size_t len); bool initAudioBackend(); bool deinitAudioBackend(); diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 524531f85..0d1780458 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -1223,6 +1223,329 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { return true; } + + +bool DivEngine::loadMod(unsigned char* file, size_t len) { + struct InvalidHeaderException {}; + bool success=false; + int chCount; + int ordCount; + std::vector patPtr; + char magic[4]={0,0,0,0}; + short defaultVols[31]; + int sampLens[31]; + // 0=arp, 1=pslide, 2=vib, 3=trem, 4=vslide + bool fxUsage[DIV_MAX_CHANS][5]; + SafeReader reader=SafeReader(file,len); + warnings=""; + try { + DivSong ds; + + // check mod magic bytes + if (!reader.seek(1080,SEEK_SET)) { + throw EndOfFileException(&reader,reader.tell()); + } + reader.read(magic,4); + if (memcmp(magic,"M.K.",4)==0 || memcmp(magic,"M!K!",4)==0) { + chCount=4; + } else if(memcmp(magic+1,"CHN",3)==0 && magic[0]>='1' && magic[0]<='9') { + chCount=magic[0]-'0'; + } else if((memcmp(magic+2,"CH",2)==0 || memcmp(magic+2,"CN",2)==0) + &&(magic[0]>='1' && magic[0]<='9' && magic[1]>='0' && magic[1]<='9')) { + chCount=((magic[0]-'0')*10)+(magic[1]-'0'); + } else { + throw InvalidHeaderException(); + } + // song name + reader.seek(0,SEEK_SET); + ds.name=reader.readString(20); + // samples + ds.sampleLen=31; + for (int i=0;i<31;i++) { + DivSample* sample=new DivSample; + sample->depth=8; + sample->name=reader.readString(22); + int slen=reader.readS_BE()*2; + sampLens[i]=slen; + if (slen==2) slen=0; + signed char fineTune=reader.readC()&0x0f; + if (fineTune>=8) fineTune-=16; + sample->rate=(int)(pow(2,fineTune/96.0)*COLOR_PAL/535); + sample->centerRate=sample->rate; + defaultVols[i]=reader.readC(); + int loopStart=reader.readS_BE()*2; + int loopLen=reader.readS_BE()*2; + int loopEnd=loopStart+loopLen; + // bunch of checks since ProTracker abuses those for one-shot samples + if (loopStart>loopEnd || loopEnd<4 || loopLen<4) { + loopStart=0; + loopLen=0; + } + if(loopLen>=2) { + if(loopEndloopStart=loopStart; + } + sample->init(slen); + ds.sample.push_back(sample); + } + // orders + ds.ordersLen=ordCount=reader.readC(); + reader.readC(); // restart position, unused + int patMax=0; + for (int i=0;i<128;i++) { + unsigned char pat=reader.readC(); + if (pat>patMax) patMax=pat; + for (int j=0;jdata[row]; + unsigned char data[4]; + reader.read(&data,4); + // instrument + short ins=(data[0]&0xf0)|(data[2]>>4); + if (ins>0) { + dstrow[2]=ins-1; + dstrow[3]=defaultVols[ins-1]; + } + // note + int period=data[1]+((data[0]&0x0f)*256); + if (period>0 && period<0x0fff) { + short note=(short)round(log2(3424.0/period)*12); + dstrow[0]=((note-1)%12)+1; + dstrow[1]=(note-1)/12+1; + } + // effects are done later + short fxtyp=data[2]&0x0f; + short fxval=data[3]; + dstrow[4]=fxtyp; + dstrow[5]=fxval; + switch(fxtyp) { + case 0: + if(fxval!=0) fxUsage[ch][0]=true; + break; + case 1: case 2: case 3: + fxUsage[ch][1]=true; + break; + case 4: + fxUsage[ch][2]=true; + break; + case 5: + fxUsage[ch][1]=true; + fxUsage[ch][4]=true; + break; + case 6: + fxUsage[ch][2]=true; + fxUsage[ch][4]=true; + break; + case 7: + fxUsage[ch][3]=true; + break; + case 10: + if(fxval!=0) fxUsage[ch][4]=true; + break; + } + } + } + } + // samples + size_t pos=reader.tell(); + for (int i=0;i<31;i++) { + reader.seek(pos,SEEK_SET); + reader.read(ds.sample[i]->data8,ds.sample[i]->samples); + pos+=sampLens[i]; + } + + // convert effects + for (int ch=0;ch<=chCount;ch++) { + unsigned char fxCols=1; + for (int pat=0;pat<=patMax;pat++) { + auto* data=ds.pat[ch].getPattern(pat,false)->data; + short lastPitchEffect=-1; + short lastEffectState[5]={-1,-1,-1,-1,-1}; + short setEffectState[5]={-1,-1,-1,-1,-1}; + for (int row=0;row<64;row++) { + const short fxUsageTyp[5]={0x00,0x01,0x04,0x07,0xFA}; + short effectState[5]={0,0,0,0,0}; + unsigned char curFxCol=0; + short fxTyp=data[row][4]; + short fxVal=data[row][5]; + auto writeFxCol=[data,row,&curFxCol](short typ, short val) { + data[row][4+curFxCol*2]=typ; + data[row][5+curFxCol*2]=val; + curFxCol++; + }; + writeFxCol(-1,-1); + curFxCol=0; + switch (fxTyp) { + case 0: // arp + effectState[0]=fxVal; + break; + case 5: // vol slide + porta + effectState[4]=fxVal; + fxTyp=3; + fxVal=0; + // fall through + case 1: // note slide up + case 2: // note slide down + case 3: // porta + if ((fxTyp==3)&&(fxVal==0)) { + if (setEffectState[1]<0) break; + fxVal=setEffectState[1]; + } + setEffectState[1]=fxVal; + effectState[1]=fxVal; + if((effectState[1]!=lastEffectState[1])|| + (fxTyp!=lastPitchEffect)|| + (effectState[1]!=0&&data[row][0]>0)) { + writeFxCol(fxTyp,fxVal); + } + lastPitchEffect=fxTyp; + lastEffectState[1]=fxVal; + break; + case 6: // vol slide + vibrato + effectState[4]=fxVal; + fxTyp=4; + fxVal=0; + // fall through + case 4: // vibrato + if (fxVal==0) { + if (setEffectState[2]<0) break; + fxVal=setEffectState[2]; + } + effectState[2]=fxVal; + setEffectState[2]=fxVal; + break; + case 7: // tremolo + if (fxVal==0) { + if (setEffectState[3]<0) break; + fxVal=setEffectState[3]; + } + effectState[3]=fxVal; + setEffectState[3]=fxVal; + break; + case 9: // set offset + writeFxCol(0x90,fxVal); + break; + case 10: // vol slide + effectState[4]=fxVal; + break; + case 11: // jump to pos + case 13: // break to row + writeFxCol(fxTyp,fxVal); + break; + case 12: // set vol + data[row][3]=fxVal; + break; + case 15: // set speed + // TODO somehow handle VBlank tunes + if (fxVal>=0x20) { + writeFxCol(0xf0,fxVal); + } else { + writeFxCol(0x09,fxVal); + writeFxCol(0x0f,fxVal); + } + break; + case 14: // extended + fxTyp=fxVal>>4; + fxVal&=0x0f; + switch (fxTyp) { + case 1: // single note slide up + case 2: // single note slide down + writeFxCol(fxTyp-1+0xf1,fxVal); + break; + case 9: // retrigger + writeFxCol(0x0c,fxVal); + break; + case 10: // single vol slide up + case 11: // single vol slide down + writeFxCol(fxTyp-10+0xf8,fxVal); + break; + case 12: // note cut + case 13: // note delay + writeFxCol(fxTyp-12+0xec,fxVal); + break; + } + break; + } + for (int i=0;i<5;i++) { + // pitch slide and volume slide needs to be kept active on new note + // even after target/max is reached + if (fxUsage[ch][i]&&((effectState[i]!=lastEffectState[i])||(effectState[i]!=0&&i==4&&data[row][3]>=0))) { + writeFxCol(fxUsageTyp[i],effectState[i]); + } + } + memcpy(lastEffectState,effectState,sizeof(effectState)); + if (curFxCol>fxCols) { + fxCols=curFxCol; + } + } + } + ds.pat[ch].effectRows=fxCols; + } + + ds.pal=false; + ds.hz=50; + ds.customTempo=false; + ds.systemLen=(chCount+3)/4; + for(int i=0;itype=DIV_INS_AMIGA; + ins->amiga.initSample=i; + ins->name=ds.sample[i]->name; + ds.ins.push_back(ins); + } + + if (active) quitDispatch(); + isBusy.lock(); + song.unload(); + song=ds; + recalcChans(); + renderSamples(); + isBusy.unlock(); + if (active) { + initDispatch(); + syncReset(); + } + success=true; + } catch (EndOfFileException e) { + logE("premature end of file!\n"); + lastError="incomplete file"; + } catch (InvalidHeaderException e) { + logE("invalid info header!\n"); + lastError="invalid info header!"; + } + return success; +} + bool DivEngine::load(unsigned char* f, size_t slen) { unsigned char* file; size_t len; @@ -1233,6 +1556,14 @@ bool DivEngine::load(unsigned char* f, size_t slen) { return false; } if (memcmp(f,DIV_DMF_MAGIC,16)!=0 && memcmp(f,DIV_FUR_MAGIC,16)!=0) { + // try loading as a .mod first before trying to decompress + logD("loading as .mod...\n"); + if (loadMod(f,slen)) { + delete[] f; + return true; + } + + lastError="not a .mod song"; logD("loading as zlib...\n"); // try zlib z_stream zl; diff --git a/src/engine/platform/amiga.cpp b/src/engine/platform/amiga.cpp index c5face681..31a7a04c6 100644 --- a/src/engine/platform/amiga.cpp +++ b/src/engine/platform/amiga.cpp @@ -184,7 +184,11 @@ int DivPlatformAmiga::dispatch(DivCommand c) { if (chan[c.chan].sample<0 || chan[c.chan].sample>=parent->song.sampleLen) { chan[c.chan].sample=-1; } - chan[c.chan].audPos=0; + if (chan[c.chan].setPos) { + chan[c.chan].setPos=false; + } else { + chan[c.chan].audPos=0; + } chan[c.chan].audSub=0; if (c.value!=DIV_NOTE_NULL) { chan[c.chan].freqChanged=true; @@ -276,6 +280,10 @@ int DivPlatformAmiga::dispatch(DivCommand c) { } chan[c.chan].inPorta=c.value; break; + case DIV_CMD_SAMPLE_POS: + chan[c.chan].audPos=c.value; + chan[c.chan].setPos=true; + break; case DIV_CMD_GET_VOLMAX: return 64; break; diff --git a/src/engine/platform/amiga.h b/src/engine/platform/amiga.h index 19fe7fb2f..93b61b6eb 100644 --- a/src/engine/platform/amiga.h +++ b/src/engine/platform/amiga.h @@ -36,7 +36,7 @@ class DivPlatformAmiga: public DivDispatch { unsigned char ins; int busClock; int note; - bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, useWave; + bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, useWave, setPos; signed char vol, outVol; DivMacroInt std; Channel(): @@ -60,6 +60,7 @@ class DivPlatformAmiga: public DivDispatch { keyOff(false), inPorta(false), useWave(false), + setPos(false), vol(64), outVol(64) {} }; diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 70af360f9..b92339d79 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -62,6 +62,7 @@ const char* cmdName[DIV_CMD_MAX]={ "SAMPLE_MODE", "SAMPLE_FREQ", "SAMPLE_BANK", + "SAMPLE_POS", "FM_LFO", "FM_LFO_WAVE", @@ -837,6 +838,9 @@ void DivEngine::processRow(int i, bool afterDelay) { chan[i].vibratoRate=effectVal>>4; dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(((chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15))); break; + case 0x07: // tremolo + // TODO + break; case 0x0a: // volume ramp if (effectVal!=0) { if ((effectVal&15)!=0) { @@ -857,15 +861,18 @@ void DivEngine::processRow(int i, bool afterDelay) { chan[i].retrigTick=0; } break; + case 0x90: case 0x91: case 0x92: case 0x93: + case 0x94: case 0x95: case 0x96: case 0x97: + case 0x98: case 0x99: case 0x9a: case 0x9b: + case 0x9c: case 0x9d: case 0x9e: case 0x9f: // set samp. pos + dispatchCmd(DivCommand(DIV_CMD_SAMPLE_POS,i,(((effect&0x0f)<<8)|effectVal)*256)); + break; case 0xc0: case 0xc1: case 0xc2: case 0xc3: // set Hz divider=((effect&0x3)<<8)|effectVal; if (divider<10) divider=10; cycles=((int)(got.rate)<0) { song.arpLen=effectVal; @@ -938,6 +945,52 @@ void DivEngine::processRow(int i, bool afterDelay) { case 0xef: // global pitch globalPitch+=(signed char)(effectVal-0x80); break; + case 0xf0: // set Hz by tempo + divider=(effectVal*2+2)/5; + if (divider<10) divider=10; + cycles=((int)(got.rate)<getPortaFloor(dispatchChanOfChan[i]):-60; + } + chan[i].portaSpeed=effectVal; + chan[i].portaStop=true; + chan[i].nowYouCanStop=false; + chan[i].stopOnOff=false; + chan[i].scheduledSlideReset=false; + chan[i].inPorta=false; + if (!song.arpNonPorta) dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,true,0)); + dispatchCmd(DivCommand(DIV_CMD_NOTE_PORTA,i,chan[i].portaSpeed,chan[i].portaNote)); + chan[i].portaNote=-1; + chan[i].portaSpeed=-1; + chan[i].inPorta=false; + if (!song.arpNonPorta) dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0)); + break; + case 0xf8: // single volume ramp up + chan[i].volume=MIN(chan[i].volume+effectVal*256,chan[i].volMax); + dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); + break; + case 0xf9: // single volume ramp down + chan[i].volume=MAX(chan[i].volume-effectVal*256,0); + dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); + break; + case 0xfa: // fast volume ramp + if (effectVal!=0) { + if ((effectVal&15)!=0) { + chan[i].volSpeed=-(effectVal&15)*256; + } else { + chan[i].volSpeed=(effectVal>>4)*256; + } + } else { + chan[i].volSpeed=0; + } + break; + case 0xff: // stop song freelance=false; playing=false; diff --git a/src/engine/safeReader.cpp b/src/engine/safeReader.cpp index 2a0d7c3ba..b2b955510 100644 --- a/src/engine/safeReader.cpp +++ b/src/engine/safeReader.cpp @@ -91,10 +91,10 @@ short SafeReader::readS() { } short SafeReader::readS_BE() { - if (curSeek+1>len) throw EndOfFileException(this,len); - unsigned short ret=*(unsigned short*)(&buf[curSeek]); + if (curSeek+2>len) throw EndOfFileException(this,len); + short ret=*(short*)(&buf[curSeek]); curSeek+=2; - return (short)((ret>>8)|((ret&0xff)<<8)); + return ((ret>>8)&0xff)|(ret<<8); } int SafeReader::readI() { diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 2d58615d4..f431d1839 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4647,9 +4647,9 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { if (!dirExists(workingDirSong)) workingDirSong=getHomeDir(); hasOpened=fileDialog->openLoad( "Open File", - {"compatible files", "*.fur *.dmf", + {"compatible files", "*.fur *.dmf *.mod", "all files", ".*"}, - "compatible files{.fur,.dmf},.*", + "compatible files{.fur,.dmf,.mod},.*", workingDirSong, dpiScale ); diff --git a/src/gui/pattern.cpp b/src/gui/pattern.cpp index f2ff7e100..aed8890ad 100644 --- a/src/gui/pattern.cpp +++ b/src/gui/pattern.cpp @@ -45,7 +45,7 @@ const FurnaceGUIColors fxColors[16]={ GUI_COLOR_PATTERN_EFFECT_SPEED, // 0F }; -const FurnaceGUIColors extFxColors[16]={ +const FurnaceGUIColors extFxColors[32]={ GUI_COLOR_PATTERN_EFFECT_MISC, // E0 GUI_COLOR_PATTERN_EFFECT_PITCH, // E1 GUI_COLOR_PATTERN_EFFECT_PITCH, // E2 @@ -62,6 +62,22 @@ const FurnaceGUIColors extFxColors[16]={ GUI_COLOR_PATTERN_EFFECT_TIME, // ED GUI_COLOR_PATTERN_EFFECT_SONG, // EE GUI_COLOR_PATTERN_EFFECT_SONG, // EF + GUI_COLOR_PATTERN_EFFECT_SPEED, // F0 + GUI_COLOR_PATTERN_EFFECT_PITCH, // F1 + GUI_COLOR_PATTERN_EFFECT_PITCH, // F2 + GUI_COLOR_PATTERN_EFFECT_INVALID, // F3 + GUI_COLOR_PATTERN_EFFECT_INVALID, // F4 + GUI_COLOR_PATTERN_EFFECT_INVALID, // F5 + GUI_COLOR_PATTERN_EFFECT_INVALID, // F6 + GUI_COLOR_PATTERN_EFFECT_INVALID, // F7 + GUI_COLOR_PATTERN_EFFECT_VOLUME, // F8 + GUI_COLOR_PATTERN_EFFECT_VOLUME, // F9 + GUI_COLOR_PATTERN_EFFECT_VOLUME, // FA + GUI_COLOR_PATTERN_EFFECT_INVALID, // FB + GUI_COLOR_PATTERN_EFFECT_INVALID, // FC + GUI_COLOR_PATTERN_EFFECT_INVALID, // FD + GUI_COLOR_PATTERN_EFFECT_INVALID, // FE + GUI_COLOR_PATTERN_EFFECT_SONG, // FF }; inline float randRange(float min, float max) { @@ -256,16 +272,18 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_SYS_SECONDARY]); } else if (pat->data[i][index]<0x48) { ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY]); + } else if (pat->data[i][index]<0x90) { + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_INVALID]); + } else if (pat->data[i][index]<0xa0) { + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_MISC]); } else if (pat->data[i][index]<0xc0) { ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_INVALID]); } else if (pat->data[i][index]<0xd0) { ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_SPEED]); } else if (pat->data[i][index]<0xe0) { ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_INVALID]); - } else if (pat->data[i][index]<0xf0) { - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[extFxColors[pat->data[i][index]-0xe0]]); } else { - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_INVALID]); + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[extFxColors[pat->data[i][index]-0xe0]]); } } ImGui::SameLine(0.0f,0.0f);