From 1e2d5694b969a6b9f527917c0ebda774e4204232 Mon Sep 17 00:00:00 2001 From: cam900 Date: Fri, 25 Feb 2022 01:02:35 +0900 Subject: [PATCH 001/637] Prepare to support YM2610B and with Extended channel 3 mode Allow 8 bit volume for YM2610 ADPCM-B Remove sample mode macro in YM2610, it's always enabled and seperated channels. TODO: ADPCM-B is still not implemented, FM Channel 2 is silenced in extended channel 3 configuration --- CMakeLists.txt | 2 + papers/format.md | 1 + src/engine/dispatchContainer.cpp | 8 + src/engine/engine.cpp | 26 +- src/engine/fileOps.cpp | 8 +- src/engine/platform/ym2610Interface.cpp | 4 +- src/engine/platform/ym2610b.cpp | 965 ++++++++++++++++++++++++ src/engine/platform/ym2610b.h | 102 +++ src/engine/platform/ym2610bext.cpp | 337 +++++++++ src/engine/platform/ym2610bext.h | 51 ++ src/engine/platform/ym2610ext.cpp | 4 - src/engine/platform/ym2610shared.h | 16 +- src/engine/playback.cpp | 36 +- src/engine/song.h | 1 + src/engine/sysDef.cpp | 35 +- src/engine/vgmOps.cpp | 10 + src/gui/debug.cpp | 2 + src/gui/gui.cpp | 6 + 18 files changed, 1592 insertions(+), 22 deletions(-) create mode 100644 src/engine/platform/ym2610b.cpp create mode 100644 src/engine/platform/ym2610b.h create mode 100644 src/engine/platform/ym2610bext.cpp create mode 100644 src/engine/platform/ym2610bext.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 3b7a669aa..0704bfaef 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -311,6 +311,8 @@ src/engine/platform/c64.cpp src/engine/platform/arcade.cpp src/engine/platform/ym2610.cpp src/engine/platform/ym2610ext.cpp +src/engine/platform/ym2610b.cpp +src/engine/platform/ym2610bext.cpp src/engine/platform/ay.cpp src/engine/platform/ay8930.cpp src/engine/platform/tia.cpp diff --git a/papers/format.md b/papers/format.md index a61bb6266..899fc601a 100644 --- a/papers/format.md +++ b/papers/format.md @@ -156,6 +156,7 @@ size | description | - 0xa6: OPL3 4-op + drums (YMF262) - 14 channels | - 0xa7: OPLL drums (YM2413) - 11 channels | - 0xa8: Atari Lynx - 4 channels + | - 0xde: YM2610B extended - 19 channels | - 0xe0: QSound - 19 channels | - (compound!) means that the system is composed of two or more chips, | and has to be flattened. diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 801a3d84f..5ffffa865 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -29,6 +29,8 @@ #include "platform/arcade.h" #include "platform/ym2610.h" #include "platform/ym2610ext.h" +#include "platform/ym2610b.h" +#include "platform/ym2610bext.h" #include "platform/ay.h" #include "platform/ay8930.h" #include "platform/tia.h" @@ -181,6 +183,12 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_YM2610_FULL_EXT: dispatch=new DivPlatformYM2610Ext; break; + case DIV_SYSTEM_YM2610B: + dispatch=new DivPlatformYM2610B; + break; + case DIV_SYSTEM_YM2610B_EXT: + dispatch=new DivPlatformYM2610BExt; + break; case DIV_SYSTEM_AMIGA: dispatch=new DivPlatformAmiga; break; diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 9dee6ecfe..95e139180 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -506,6 +506,28 @@ void DivEngine::renderSamples() { memPos+=length+16; } qsoundMemLen=memPos+256; + + // step 5: allocate ADPCM-B samples + if (adpcmBMem==NULL) adpcmBMem=new unsigned char[16777216]; + + memPos=0; + for (int i=0; i=16777216) { + logW("out of ADPCM-B memory for sample %d!\n",i); + break; + } + if (memPos+s->adpcmBRendLength>=16777216) { + memcpy(adpcmBMem+memPos,s->adpcmBRendData,16777216-memPos); + logW("out of ADPCM-B memory for sample %d!\n",i); + } else { + memcpy(adpcmBMem+memPos,s->adpcmBRendData,s->adpcmBRendLength); + } + s->rendOff=memPos; + memPos+=s->adpcmBRendLength; + } + adpcmBMemLen=memPos+256; + */ } @@ -905,7 +927,9 @@ int DivEngine::getEffectiveSampleRate(int rate) { return 1789773/(1789773/rate); case DIV_SYSTEM_SEGAPCM: case DIV_SYSTEM_SEGAPCM_COMPAT: return (31250*MIN(255,(rate*255/31250)))/255; - case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_EXT: case DIV_SYSTEM_YM2610_FULL: case DIV_SYSTEM_YM2610_FULL_EXT: + case DIV_SYSTEM_QSOUND: + return (24038*MIN(65535,(rate*4096/24038)))/4096; + case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_EXT: case DIV_SYSTEM_YM2610_FULL: case DIV_SYSTEM_YM2610_FULL_EXT: case DIV_SYSTEM_YM2610B: case DIV_SYSTEM_YM2610B_EXT: return 18518; default: break; diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index dd3ff37e2..5177916fd 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -148,7 +148,9 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { } // Neo Geo detune - if (ds.system[0]==DIV_SYSTEM_YM2610 || ds.system[0]==DIV_SYSTEM_YM2610_EXT) { + if (ds.system[0]==DIV_SYSTEM_YM2610 || ds.system[0]==DIV_SYSTEM_YM2610_EXT + || ds.system[0]==DIV_SYSTEM_YM2610_FULL || ds.system[0]==DIV_SYSTEM_YM2610_FULL_EXT + || ds.system[0]==DIV_SYSTEM_YM2610B || ds.system[0]==DIV_SYSTEM_YM2610B_EXT) { ds.tuning=443.23; } @@ -264,7 +266,9 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { if (ds.system[0]==DIV_SYSTEM_C64_8580 || ds.system[0]==DIV_SYSTEM_C64_6581) { ins->type=DIV_INS_C64; } - if (ds.system[0]==DIV_SYSTEM_YM2610 || ds.system[0]==DIV_SYSTEM_YM2610_EXT) { + if (ds.system[0]==DIV_SYSTEM_YM2610 || ds.system[0]==DIV_SYSTEM_YM2610_EXT + || ds.system[0]==DIV_SYSTEM_YM2610_FULL || ds.system[0]==DIV_SYSTEM_YM2610_FULL_EXT + || ds.system[0]==DIV_SYSTEM_YM2610B || ds.system[0]==DIV_SYSTEM_YM2610B_EXT) { if (!ins->mode) { ins->type=DIV_INS_AY; } diff --git a/src/engine/platform/ym2610Interface.cpp b/src/engine/platform/ym2610Interface.cpp index 8d53e48ce..29a186ee1 100644 --- a/src/engine/platform/ym2610Interface.cpp +++ b/src/engine/platform/ym2610Interface.cpp @@ -23,8 +23,8 @@ uint8_t DivYM2610Interface::ymfm_external_read(ymfm::access_class type, uint32_t address) { //printf("wants to read from %x\n",address); - if (type!=ymfm::ACCESS_ADPCM_A) return 0; - return parent->adpcmMem[address&0xffffff]; + if (type!=ymfm::ACCESS_ADPCM_A) return /*s->dataB[address&0xffffff];*/0; + return parent->adpcmMem[address&0xffffff];/*s->dataA[address&0xffffff]*/ /*if (12*sampleBank+(address>>16)>=parent->song.sampleLen) return 0; return parent->song.sample[12*sampleBank+(address>>16)]->adpcmRendData[(address&0xffff)];*/ } diff --git a/src/engine/platform/ym2610b.cpp b/src/engine/platform/ym2610b.cpp new file mode 100644 index 000000000..bb3559e94 --- /dev/null +++ b/src/engine/platform/ym2610b.cpp @@ -0,0 +1,965 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "ym2610b.h" +#include "../engine.h" +#include +#include + +#include "ym2610shared.h" + +#include "fmshared_OPN.h" + +static unsigned char konOffs[6]={ + 0, 1, 2, 4, 5, 6 +}; + +#define CHIP_DIVIDER 32 + +const char* DivPlatformYM2610B::getEffectName(unsigned char effect) { + switch (effect) { + case 0x10: + return "10xy: Setup LFO (x: enable; y: speed)"; + break; + case 0x11: + return "11xx: Set feedback (0 to 7)"; + break; + case 0x12: + return "12xx: Set level of operator 1 (0 highest, 7F lowest)"; + break; + case 0x13: + return "13xx: Set level of operator 2 (0 highest, 7F lowest)"; + break; + case 0x14: + return "14xx: Set level of operator 3 (0 highest, 7F lowest)"; + break; + case 0x15: + return "15xx: Set level of operator 4 (0 highest, 7F lowest)"; + break; + case 0x16: + return "16xy: Set operator multiplier (x: operator from 1 to 4; y: multiplier)"; + break; + case 0x18: + return "18xx: Toggle extended channel 3 mode"; + break; + case 0x19: + return "19xx: Set attack of all operators (0 to 1F)"; + break; + case 0x1a: + return "1Axx: Set attack of operator 1 (0 to 1F)"; + break; + case 0x1b: + return "1Bxx: Set attack of operator 2 (0 to 1F)"; + break; + case 0x1c: + return "1Cxx: Set attack of operator 3 (0 to 1F)"; + break; + case 0x1d: + return "1Dxx: Set attack of operator 4 (0 to 1F)"; + break; + case 0x20: + return "20xx: Set SSG channel mode (bit 0: square; bit 1: noise; bit 2: envelope)"; + break; + case 0x21: + return "21xx: Set SSG noise frequency (0 to 1F)"; + break; + case 0x22: + return "22xy: Set SSG envelope mode (x: shape, y: enable for this channel)"; + break; + case 0x23: + return "23xx: Set SSG envelope period low byte"; + break; + case 0x24: + return "24xx: Set SSG envelope period high byte"; + break; + case 0x25: + return "25xx: SSG envelope slide up"; + break; + case 0x26: + return "26xx: SSG envelope slide down"; + break; + case 0x29: + return "29xy: Set SSG auto-envelope (x: numerator; y: denominator)"; + break; + } + return NULL; +} + +void DivPlatformYM2610B::acquire(short* bufL, short* bufR, size_t start, size_t len) { + static int os[2]; + + for (size_t h=start; hwrite(0x0+((w.addr>>8)<<1),w.addr); + fm->write(0x1+((w.addr>>8)<<1),w.val); + regPool[w.addr&0x1ff]=w.val; + writes.pop(); + delay=4; + } + } + + fm->generate(&fmout); + + os[0]=fmout.data[0]+(fmout.data[2]>>1); + if (os[0]<-32768) os[0]=-32768; + if (os[0]>32767) os[0]=32767; + + os[1]=fmout.data[1]+(fmout.data[2]>>1); + if (os[1]<-32768) os[1]=-32768; + if (os[1]>32767) os[1]=32767; + + bufL[h]=os[0]; + bufR[h]=os[1]; + } +} + +void DivPlatformYM2610B::tick() { + // PSG + for (int i=6; i<9; i++) { + chan[i].std.next(); + if (chan[i].std.hadVol) { + chan[i].outVol=MIN(15,chan[i].std.vol)-(15-(chan[i].vol&15)); + if (chan[i].outVol<0) chan[i].outVol=0; + if (isMuted[i]) { + rWrite(0x02+i,0); + } else { + rWrite(0x02+i,(chan[i].outVol&15)|((chan[i].psgMode&4)<<2)); + } + } + if (chan[i].std.hadArp) { + if (!chan[i].inPorta) { + if (chan[i].std.arpMode) { + chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp); + } else { + chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp); + } + } + chan[i].freqChanged=true; + } else { + if (chan[i].std.arpMode && chan[i].std.finishedArp) { + chan[i].baseFreq=NOTE_PERIODIC(chan[i].note); + chan[i].freqChanged=true; + } + } + if (chan[i].std.hadDuty) { + ayNoiseFreq=31-chan[i].std.duty; + rWrite(0x06,ayNoiseFreq); + } + if (chan[i].std.hadWave) { + chan[i].psgMode=(chan[i].std.wave+1)&7; + if (isMuted[i]) { + rWrite(0x02+i,0); + } else { + rWrite(0x02+i,(chan[i].outVol&15)|((chan[i].psgMode&4)<<2)); + } + } + if (chan[i].std.hadEx2) { + ayEnvMode=chan[i].std.ex2; + rWrite(0x0d,ayEnvMode); + } + if (chan[i].std.hadEx3) { + chan[i].autoEnvNum=chan[i].std.ex3; + chan[i].freqChanged=true; + if (!chan[i].std.willAlg) chan[i].autoEnvDen=1; + } + if (chan[i].std.hadAlg) { + chan[i].autoEnvDen=chan[i].std.alg; + chan[i].freqChanged=true; + if (!chan[i].std.willEx3) chan[i].autoEnvNum=1; + } + if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true); + if (chan[i].freq>4095) chan[i].freq=4095; + if (chan[i].keyOn) { + } + if (chan[i].keyOff) { + rWrite(0x02+i,0); + } + rWrite((i-6)<<1,chan[i].freq&0xff); + rWrite(1+((i-6)<<1),chan[i].freq>>8); + if (chan[i].keyOn) chan[i].keyOn=false; + if (chan[i].keyOff) chan[i].keyOff=false; + if (chan[i].freqChanged && chan[i].autoEnvNum>0 && chan[i].autoEnvDen>0) { + ayEnvPeriod=(chan[i].freq*chan[i].autoEnvDen/chan[i].autoEnvNum)>>4; + immWrite(0x0b,ayEnvPeriod); + immWrite(0x0c,ayEnvPeriod>>8); + } + chan[i].freqChanged=false; + } + } + + rWrite(0x07, + ~((chan[6].psgMode&1)| + ((chan[7].psgMode&1)<<1)| + ((chan[8].psgMode&1)<<2)| + ((chan[6].psgMode&2)<<2)| + ((chan[7].psgMode&2)<<3)| + ((chan[8].psgMode&2)<<4))); + + if (ayEnvSlide!=0) { + ayEnvSlideLow+=ayEnvSlide; + while (ayEnvSlideLow>7) { + ayEnvSlideLow-=8; + if (ayEnvPeriod<0xffff) { + ayEnvPeriod++; + immWrite(0x0b,ayEnvPeriod); + immWrite(0x0c,ayEnvPeriod>>8); + } + } + while (ayEnvSlideLow<-7) { + ayEnvSlideLow+=8; + if (ayEnvPeriod>0) { + ayEnvPeriod--; + immWrite(0x0b,ayEnvPeriod); + immWrite(0x0c,ayEnvPeriod>>8); + } + } + } + + // FM + for (int i=0; i<6; i++) { + if (i==1 && extMode) continue; + chan[i].std.next(); + + if (chan[i].std.hadVol) { + chan[i].outVol=(chan[i].vol*MIN(127,chan[i].std.vol))/127; + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs_b[i]|opOffs[j]; + DivInstrumentFM::Operator& op=chan[i].state.op[j]; + if (isOutput[chan[i].state.alg][j]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + } + + if (chan[i].std.hadArp) { + if (!chan[i].inPorta) { + if (chan[i].std.arpMode) { + chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp); + } else { + chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+(signed char)chan[i].std.arp); + } + } + chan[i].freqChanged=true; + } else { + if (chan[i].std.arpMode && chan[i].std.finishedArp) { + chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note); + chan[i].freqChanged=true; + } + } + + if (chan[i].std.hadAlg) { + chan[i].state.alg=chan[i].std.alg; + rWrite(chanOffs_b[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); + if (!parent->song.algMacroBehavior) for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs_b[i]|opOffs[j]; + DivInstrumentFM::Operator& op=chan[i].state.op[j]; + if (isMuted[i]) { + rWrite(baseAddr+ADDR_TL,127); + } else { + if (isOutput[chan[i].state.alg][j]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + } + } + if (chan[i].std.hadFb) { + chan[i].state.fb=chan[i].std.fb; + rWrite(chanOffs_b[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); + } + if (chan[i].std.hadFms) { + chan[i].state.fms=chan[i].std.fms; + rWrite(chanOffs_b[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + } + if (chan[i].std.hadAms) { + chan[i].state.ams=chan[i].std.ams; + rWrite(chanOffs_b[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + } + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs_b[i]|opOffs[j]; + DivInstrumentFM::Operator& op=chan[i].state.op[j]; + DivMacroInt::IntOp& m=chan[i].std.op[j]; + if (m.hadAm) { + op.am=m.am; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + if (m.hadAr) { + op.ar=m.ar; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + if (m.hadDr) { + op.dr=m.dr; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + if (m.hadMult) { + op.mult=m.mult; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + if (m.hadRr) { + op.rr=m.rr; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + if (m.hadSl) { + op.sl=m.sl; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + if (m.hadTl) { + op.tl=127-m.tl; + if (isOutput[chan[i].state.alg][j]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + if (m.hadRs) { + op.rs=m.rs; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + if (m.hadDt) { + op.dt=m.dt; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + if (m.hadD2r) { + op.d2r=m.d2r; + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + } + if (m.hadSsg) { + op.ssgEnv=m.ssg; + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + } + + if (chan[i].keyOn || chan[i].keyOff) { + immWrite(0x28,0x00|konOffs[i]); + chan[i].keyOff=false; + } + } + + for (int i=0; i<512; i++) { + if (pendingWrites[i]!=oldWrites[i]) { + immWrite(i,pendingWrites[i]&0xff); + oldWrites[i]=pendingWrites[i]; + } + } + + for (int i=0; i<6; i++) { + if (i==1 && extMode) continue; + if (chan[i].freqChanged) { + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq)); + if (chan[i].freq>262143) chan[i].freq=262143; + int freqt=toFreq(chan[i].freq); + immWrite(chanOffs_b[i]+ADDR_FREQH,freqt>>8); + immWrite(chanOffs_b[i]+ADDR_FREQ,freqt&0xff); + chan[i].freqChanged=false; + } + if (chan[i].keyOn) { + immWrite(0x28,0xf0|konOffs[i]); + chan[i].keyOn=false; + } + } +} + +int DivPlatformYM2610B::octave(int freq) { + if (freq>=622.0f*128) { + return 128; + } else if (freq>=622.0f*64) { + return 64; + } else if (freq>=622.0f*32) { + return 32; + } else if (freq>=622.0f*16) { + return 16; + } else if (freq>=622.0f*8) { + return 8; + } else if (freq>=622.0f*4) { + return 4; + } else if (freq>=622.0f*2) { + return 2; + } else { + return 1; + } + return 1; +} + +int DivPlatformYM2610B::toFreq(int freq) { + if (freq>=622.0f*128) { + return 0x3800|((freq>>7)&0x7ff); + } else if (freq>=622.0f*64) { + return 0x3000|((freq>>6)&0x7ff); + } else if (freq>=622.0f*32) { + return 0x2800|((freq>>5)&0x7ff); + } else if (freq>=622.0f*16) { + return 0x2000|((freq>>4)&0x7ff); + } else if (freq>=622.0f*8) { + return 0x1800|((freq>>3)&0x7ff); + } else if (freq>=622.0f*4) { + return 0x1000|((freq>>2)&0x7ff); + } else if (freq>=622.0f*2) { + return 0x800|((freq>>1)&0x7ff); + } else { + return freq&0x7ff; + } +} + +int DivPlatformYM2610B::dispatch(DivCommand c) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: { + if (c.chan>8) { // ADPCM + if (skipRegisterWrites) break; + if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) { + immWrite(0x100,0x80|(1<<(c.chan-9))); + immWrite(0x110+c.chan-9,0); + immWrite(0x118+c.chan-9,0); + immWrite(0x120+c.chan-9,0); + immWrite(0x128+c.chan-9,0); + break; + } + DivSample* s=parent->song.sample[12*sampleBank+c.value%12]; + immWrite(0x110+c.chan-9,(s->offA>>8)&0xff); + immWrite(0x118+c.chan-9,s->offA>>16); + int end=s->offA+s->lengthA-1; + immWrite(0x120+c.chan-9,(end>>8)&0xff); + immWrite(0x128+c.chan-9,end>>16); + immWrite(0x108+(c.chan-9),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].vol)); + immWrite(0x100,0x00|(1<<(c.chan-9))); + break; + } + DivInstrument* ins=parent->getIns(chan[c.chan].ins); + chan[c.chan].std.init(ins); + if (c.chan<6) { + if (!chan[c.chan].std.willVol) { + chan[c.chan].outVol=chan[c.chan].vol; + } + } + + if (c.chan>5) { // PSG + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + } + chan[c.chan].active=true; + chan[c.chan].keyOn=true; + if (isMuted[c.chan]) { + rWrite(0x02+c.chan,0); + } else { + rWrite(0x02+c.chan,(chan[c.chan].vol&15)|((chan[c.chan].psgMode&4)<<2)); + } + break; + } + + if (chan[c.chan].insChanged) { + chan[c.chan].state=ins->fm; + } + + for (int i=0; i<6; i++) { + unsigned short baseAddr=chanOffs_b[c.chan]|opOffs[i]; + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + if (isOutput[chan[c.chan].state.alg][i]) { + if (!chan[c.chan].active || chan[c.chan].insChanged) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); + } + } else { + if (chan[c.chan].insChanged) { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + if (chan[c.chan].insChanged) { + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + } + if (chan[c.chan].insChanged) { + rWrite(chanOffs_b[c.chan]+ADDR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3)); + rWrite(chanOffs_b[c.chan]+ADDR_LRAF,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|(chan[c.chan].state.fms&7)|((chan[c.chan].state.ams&3)<<4)); + } + chan[c.chan].insChanged=false; + + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + } + chan[c.chan].keyOn=true; + chan[c.chan].active=true; + break; + } + case DIV_CMD_NOTE_OFF: + if (c.chan>8) { + immWrite(0x100,0x80|(1<<(c.chan-9))); + break; + } + chan[c.chan].keyOff=true; + chan[c.chan].keyOn=false; + chan[c.chan].active=false; + chan[c.chan].std.init(NULL); + break; + case DIV_CMD_NOTE_OFF_ENV: + if (c.chan>8) { + immWrite(0x100,0x80|(1<<(c.chan-9))); + break; + } + if (c.chan>5) { + chan[c.chan].std.release(); + break; + } + chan[c.chan].keyOff=true; + chan[c.chan].keyOn=false; + chan[c.chan].active=false; + chan[c.chan].std.release(); + break; + case DIV_CMD_ENV_RELEASE: + chan[c.chan].std.release(); + break; + case DIV_CMD_VOLUME: { + chan[c.chan].vol=c.value; + if (!chan[c.chan].std.hasVol) { + chan[c.chan].outVol=c.value; + } + if (c.chan>8) { // ADPCM + immWrite(0x108+(c.chan-9),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].vol)); + break; + } + if (c.chan>5) { // PSG + if (isMuted[c.chan]) { + rWrite(0x02+c.chan,0); + } else { + if (chan[c.chan].active) rWrite(0x02+c.chan,(chan[c.chan].vol&15)|((chan[c.chan].psgMode&4)<<2)); + } + break; + } + for (int i=0; i<4; i++) { + unsigned short baseAddr=chanOffs_b[c.chan]|opOffs[i]; + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + if (isOutput[chan[c.chan].state.alg][i]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + break; + } + case DIV_CMD_GET_VOLUME: { + return chan[c.chan].vol; + break; + } + case DIV_CMD_INSTRUMENT: + if (chan[c.chan].ins!=c.value || c.value2==1) { + chan[c.chan].insChanged=true; + } + chan[c.chan].ins=c.value; + break; + case DIV_CMD_PANNING: { + switch (c.value) { + case 0x01: + chan[c.chan].pan=1; + break; + case 0x10: + chan[c.chan].pan=2; + break; + default: + chan[c.chan].pan=3; + break; + } + if (c.chan>8) { + immWrite(0x108+(c.chan-9),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].vol)); + break; + } + if (c.chan>5) break; + rWrite(chanOffs_b[c.chan]+ADDR_LRAF,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|(chan[c.chan].state.fms&7)|((chan[c.chan].state.ams&3)<<4)); + break; + } + case DIV_CMD_PITCH: { + chan[c.chan].pitch=c.value; + chan[c.chan].freqChanged=true; + break; + } + case DIV_CMD_NOTE_PORTA: { + if (c.chan>5) { // PSG + int destFreq=NOTE_PERIODIC(c.value2); + bool return2=false; + if (destFreq>chan[c.chan].baseFreq) { + chan[c.chan].baseFreq+=c.value; + if (chan[c.chan].baseFreq>=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } else { + chan[c.chan].baseFreq-=c.value; + if (chan[c.chan].baseFreq<=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } + chan[c.chan].freqChanged=true; + if (return2) { + chan[c.chan].inPorta=false; + return 2; + } + break; + } + + int destFreq=NOTE_FREQUENCY(c.value2); + int newFreq; + bool return2=false; + if (destFreq>chan[c.chan].baseFreq) { + newFreq=chan[c.chan].baseFreq+c.value*octave(chan[c.chan].baseFreq); + if (newFreq>=destFreq) { + newFreq=destFreq; + return2=true; + } + } else { + newFreq=chan[c.chan].baseFreq-c.value*octave(chan[c.chan].baseFreq); + if (newFreq<=destFreq) { + newFreq=destFreq; + return2=true; + } + } + if (!chan[c.chan].portaPause) { + if (octave(chan[c.chan].baseFreq)!=octave(newFreq)) { + chan[c.chan].portaPause=true; + break; + } + } + chan[c.chan].baseFreq=newFreq; + chan[c.chan].portaPause=false; + chan[c.chan].freqChanged=true; + if (return2) return 2; + break; + } + case DIV_CMD_SAMPLE_BANK: + sampleBank=c.value; + if (sampleBank>(parent->song.sample.size()/12)) { + sampleBank=parent->song.sample.size()/12; + } + iface.sampleBank=sampleBank; + break; + case DIV_CMD_LEGATO: { + if (c.chan>5) { // PSG + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); + } else { + chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); + } + chan[c.chan].freqChanged=true; + break; + } + case DIV_CMD_FM_LFO: { + rWrite(0x22,(c.value&7)|((c.value>>4)<<3)); + break; + } + case DIV_CMD_FM_FB: { + if (c.chan>5) break; + chan[c.chan].state.fb=c.value&7; + rWrite(chanOffs_b[c.chan]+ADDR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3)); + break; + } + case DIV_CMD_FM_MULT: { + if (c.chan>5) break; + unsigned short baseAddr=chanOffs_b[c.chan]|opOffs[orderedOps[c.value]]; + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.mult=c.value2&15; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + break; + } + case DIV_CMD_FM_TL: { + if (c.chan>5) break; + unsigned short baseAddr=chanOffs_b[c.chan]|opOffs[orderedOps[c.value]]; + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.tl=c.value2; + if (isOutput[chan[c.chan].state.alg][c.value]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + break; + } + case DIV_CMD_FM_AR: { + if (c.chan>5) break; + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.ar=c.value2&31; + unsigned short baseAddr=chanOffs_b[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + } else { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.ar=c.value2&31; + unsigned short baseAddr=chanOffs_b[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + break; + } + case DIV_CMD_STD_NOISE_MODE: + if (c.chan<6 || c.chan>8) break; + chan[c.chan].psgMode=(c.value+1)&7; + if (isMuted[c.chan]) { + rWrite(0x02+c.chan,0); + } else if (chan[c.chan].active) { + rWrite(0x02+c.chan,(chan[c.chan].outVol&15)|((chan[c.chan].psgMode&4)<<2)); + } + break; + case DIV_CMD_STD_NOISE_FREQ: + if (c.chan<6 || c.chan>8) break; + ayNoiseFreq=31-c.value; + rWrite(0x06,ayNoiseFreq); + break; + case DIV_CMD_AY_ENVELOPE_SET: + if (c.chan<6 || c.chan>8) break; + ayEnvMode=c.value>>4; + rWrite(0x0d,ayEnvMode); + if (c.value&15) { + chan[c.chan].psgMode|=4; + } else { + chan[c.chan].psgMode&=~4; + } + if (isMuted[c.chan]) { + rWrite(0x02+c.chan,0); + } else { + rWrite(0x02+c.chan,(chan[c.chan].vol&15)|((chan[c.chan].psgMode&4)<<2)); + } + break; + case DIV_CMD_AY_ENVELOPE_LOW: + if (c.chan<6 || c.chan>8) break; + ayEnvPeriod&=0xff00; + ayEnvPeriod|=c.value; + immWrite(0x0b,ayEnvPeriod); + immWrite(0x0c,ayEnvPeriod>>8); + break; + case DIV_CMD_AY_ENVELOPE_HIGH: + if (c.chan<6 || c.chan>8) break; + ayEnvPeriod&=0xff; + ayEnvPeriod|=c.value<<8; + immWrite(0x0b,ayEnvPeriod); + immWrite(0x0c,ayEnvPeriod>>8); + break; + case DIV_CMD_AY_ENVELOPE_SLIDE: + if (c.chan<6 || c.chan>8) break; + ayEnvSlide=c.value; + break; + case DIV_CMD_AY_AUTO_ENVELOPE: + if (c.chan<6 || c.chan>8) break; + chan[c.chan].autoEnvNum=c.value>>4; + chan[c.chan].autoEnvDen=c.value&15; + chan[c.chan].freqChanged=true; + break; + case DIV_ALWAYS_SET_VOLUME: + return 0; + break; + case DIV_CMD_GET_VOLMAX: + if (c.chan>14) return 255; + if (c.chan>8) return 31; + if (c.chan>5) return 15; + return 127; + break; + case DIV_CMD_PRE_PORTA: + if (c.chan>5) { + if (chan[c.chan].active && c.value2) { + if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + } + } + chan[c.chan].inPorta=c.value; + break; + case DIV_CMD_PRE_NOTE: + break; + default: + //printf("WARNING: unimplemented command %d\n",c.cmd); + break; + } + return 1; +} + +void DivPlatformYM2610B::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; + if (ch>8) { // ADPCM + immWrite(0x108+(ch-9),isMuted[ch]?0:((chan[ch].pan<<6)|chan[ch].vol)); + return; + } + if (ch>5) { // PSG + if (isMuted[ch]) { + rWrite(0x02+ch,0); + } else { + rWrite(0x02+ch,(chan[ch].outVol&15)|((chan[ch].psgMode&4)<<2)); + } + return; + } + // FM + rWrite(chanOffs_b[ch]+ADDR_LRAF,(isMuted[ch]?0:(chan[ch].pan<<6))|(chan[ch].state.fms&7)|((chan[ch].state.ams&3)<<4)); +} + +void DivPlatformYM2610B::forceIns() { + for (int i=0; i<6; i++) { + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs_b[i]|opOffs[j]; + DivInstrumentFM::Operator& op=chan[i].state.op[j]; + if (isOutput[chan[i].state.alg][j]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + rWrite(chanOffs_b[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); + rWrite(chanOffs_b[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + if (chan[i].active) { + chan[i].keyOn=true; + chan[i].freqChanged=true; + } + } + for (int i=6; i<16; i++) { + chan[i].insChanged=true; + } + immWrite(0x0b,ayEnvPeriod); + immWrite(0x0c,ayEnvPeriod>>8); + immWrite(0x0d,ayEnvMode); +} + +void* DivPlatformYM2610B::getChanState(int ch) { + return &chan[ch]; +} + +unsigned char* DivPlatformYM2610B::getRegisterPool() { + return regPool; +} + +int DivPlatformYM2610B::getRegisterPoolSize() { + return 512; +} + +void DivPlatformYM2610B::poke(unsigned int addr, unsigned short val) { + immWrite(addr,val); +} + +void DivPlatformYM2610B::poke(std::vector& wlist) { + for (DivRegWrite& i: wlist) immWrite(i.addr,i.val); +} + +void DivPlatformYM2610B::reset() { + while (!writes.empty()) writes.pop(); + memset(regPool,0,512); + if (dumpWrites) { + addWrite(0xffffffff,0); + } + fm->reset(); + for (int i=0; i<16; i++) { + chan[i]=DivPlatformYM2610B::Channel(); + } + for (int i=0; i<6; i++) { + chan[i].vol=0x7f; + chan[i].outVol=0x7f; + } + for (int i=6; i<9; i++) { + chan[i].vol=0x0f; + } + for (int i=9; i<15; i++) { + chan[i].vol=0x1f; + } + chan[15].vol=0xff; + + for (int i=0; i<512; i++) { + oldWrites[i]=-1; + pendingWrites[i]=-1; + } + + lastBusy=60; + dacMode=0; + dacPeriod=0; + dacPos=0; + dacRate=0; + dacSample=-1; + sampleBank=0; + ayEnvPeriod=0; + ayEnvMode=0; + ayEnvSlide=0; + ayEnvSlideLow=0; + ayNoiseFreq=0; + + delay=0; + + extMode=false; + + // AY noise + immWrite(0x06,ayNoiseFreq); + + // LFO + immWrite(0x22,0x08); + + // PCM volume + immWrite(0x101,0x3f); +} + +bool DivPlatformYM2610B::isStereo() { + return true; +} + +bool DivPlatformYM2610B::keyOffAffectsArp(int ch) { + return (ch>5); +} + +void DivPlatformYM2610B::notifyInsChange(int ins) { + for (int i=0; i<16; i++) { + if (chan[i].ins==ins) { + chan[i].insChanged=true; + } + } +} + +void DivPlatformYM2610B::notifyInsDeletion(void* ins) { + for (int i=6; i<9; i++) { + chan[i].std.notifyInsDeletion((DivInstrument*)ins); + } +} + +int DivPlatformYM2610B::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { + parent=p; + dumpWrites=false; + skipRegisterWrites=false; + for (int i=0; i<16; i++) { + isMuted[i]=false; + } + chipClock=8000000; + rate=chipClock/16; + iface.parent=parent; + iface.sampleBank=0; + fm=new ymfm::ym2610b(iface); + reset(); + return 16; +} + +void DivPlatformYM2610B::quit() { + delete fm; +} + +DivPlatformYM2610B::~DivPlatformYM2610B() { +} diff --git a/src/engine/platform/ym2610b.h b/src/engine/platform/ym2610b.h new file mode 100644 index 000000000..f7299efd0 --- /dev/null +++ b/src/engine/platform/ym2610b.h @@ -0,0 +1,102 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _YM2610B_H +#define _YM2610B_H +#include "../dispatch.h" +#include "../macroInt.h" +#include +#include "sound/ymfm/ymfm_opn.h" + +#include "ym2610.h" + +class DivPlatformYM2610B: public DivDispatch { + protected: + struct Channel { + DivInstrumentFM state; + unsigned char freqH, freqL; + int freq, baseFreq, pitch, note; + unsigned char ins, psgMode, autoEnvNum, autoEnvDen; + signed char konCycles; + bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta; + int vol, outVol; + unsigned char pan; + DivMacroInt std; + Channel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), note(0), ins(-1), psgMode(1), autoEnvNum(0), autoEnvDen(0), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), inPorta(false), vol(0), outVol(15), pan(3) {} + }; + Channel chan[16]; + bool isMuted[16]; + struct QueuedWrite { + unsigned short addr; + unsigned char val; + bool addrOrVal; + QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {} + }; + std::queue writes; + ymfm::ym2610b* fm; + ymfm::ym2610b::output_data fmout; + DivYM2610Interface iface; + unsigned char regPool[512]; + unsigned char lastBusy; + + bool dacMode; + int dacPeriod; + int dacRate; + int dacPos; + int dacSample; + int ayNoiseFreq; + unsigned char sampleBank; + + int delay; + + bool extMode; + + short oldWrites[512]; + short pendingWrites[512]; + unsigned char ayEnvMode; + unsigned short ayEnvPeriod; + short ayEnvSlideLow; + short ayEnvSlide; + + int octave(int freq); + int toFreq(int freq); + friend void putDispatchChan(void*,int,int); + + public: + void acquire(short* bufL, short* bufR, size_t start, size_t len); + int dispatch(DivCommand c); + void* getChanState(int chan); + unsigned char* getRegisterPool(); + int getRegisterPoolSize(); + void reset(); + void forceIns(); + void tick(); + void muteChannel(int ch, bool mute); + bool isStereo(); + bool keyOffAffectsArp(int ch); + void notifyInsChange(int ins); + void notifyInsDeletion(void* ins); + void poke(unsigned int addr, unsigned short val); + void poke(std::vector& wlist); + const char* getEffectName(unsigned char effect); + int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); + void quit(); + ~DivPlatformYM2610B(); +}; +#endif diff --git a/src/engine/platform/ym2610bext.cpp b/src/engine/platform/ym2610bext.cpp new file mode 100644 index 000000000..5c3649164 --- /dev/null +++ b/src/engine/platform/ym2610bext.cpp @@ -0,0 +1,337 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "ym2610bext.h" +#include "../engine.h" +#include + +#include "ym2610shared.h" + +int DivPlatformYM2610BExt::dispatch(DivCommand c) { + if (c.chan<2) { + return DivPlatformYM2610B::dispatch(c); + } + if (c.chan>5) { + c.chan-=3; + return DivPlatformYM2610B::dispatch(c); + } + int ch=c.chan-2; + int ordch=orderedOps[ch]; + switch (c.cmd) { + case DIV_CMD_NOTE_ON: { + DivInstrument* ins=parent->getIns(opChan[ch].ins); + + unsigned short baseAddr=chanOffs_b[2]|opOffs[ordch]; + DivInstrumentFM::Operator op=ins->fm.op[ordch]; + // TODO: how does this work?! + if (isOpMuted[ch]) { + rWrite(baseAddr+0x40,127); + } else { + if (opChan[ch].insChanged) { + rWrite(baseAddr+0x40,127-(((127-op.tl)*(opChan[ch].vol&0x7f))/127)); + } + } + if (opChan[ch].insChanged) { + rWrite(baseAddr+0x30,(op.mult&15)|(dtTable[op.dt&7]<<4)); + rWrite(baseAddr+0x50,(op.ar&31)|(op.rs<<6)); + rWrite(baseAddr+0x60,(op.dr&31)|(op.am<<7)); + rWrite(baseAddr+0x70,op.d2r&31); + rWrite(baseAddr+0x80,(op.rr&15)|(op.sl<<4)); + rWrite(baseAddr+0x90,op.ssgEnv&15); + } + if (opChan[ch].insChanged) { // TODO how does this work? + rWrite(chanOffs_b[2]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3)); + rWrite(chanOffs_b[2]+0xb4,(opChan[ch].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4)); + } + opChan[ch].insChanged=false; + + if (c.value!=DIV_NOTE_NULL) { + opChan[ch].baseFreq=NOTE_FREQUENCY(c.value); + opChan[ch].freqChanged=true; + } + opChan[ch].keyOn=true; + opChan[ch].active=true; + break; + } + case DIV_CMD_NOTE_OFF: + opChan[ch].keyOff=true; + opChan[ch].keyOn=false; + opChan[ch].active=false; + break; + case DIV_CMD_VOLUME: { + opChan[ch].vol=c.value; + DivInstrument* ins=parent->getIns(opChan[ch].ins); + unsigned short baseAddr=chanOffs_b[2]|opOffs[ordch]; + DivInstrumentFM::Operator op=ins->fm.op[ordch]; + if (isOpMuted[ch]) { + rWrite(baseAddr+0x40,127); + } else { + rWrite(baseAddr+0x40,127-(((127-op.tl)*(opChan[ch].vol&0x7f))/127)); + } + break; + } + case DIV_CMD_GET_VOLUME: { + return opChan[ch].vol; + break; + } + case DIV_CMD_INSTRUMENT: + if (opChan[ch].ins!=c.value || c.value2==1) { + opChan[ch].insChanged=true; + } + opChan[ch].ins=c.value; + break; + case DIV_CMD_PANNING: { + switch (c.value) { + case 0x01: + opChan[ch].pan=1; + break; + case 0x10: + opChan[ch].pan=2; + break; + default: + opChan[ch].pan=3; + break; + } + DivInstrument* ins=parent->getIns(opChan[ch].ins); + // TODO: ??? + rWrite(chanOffs_b[2]+0xb4,(opChan[ch].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4)); + break; + } + case DIV_CMD_PITCH: { + opChan[ch].pitch=c.value; + opChan[ch].freqChanged=true; + break; + } + case DIV_CMD_NOTE_PORTA: { + int destFreq=NOTE_FREQUENCY(c.value2); + int newFreq; + bool return2=false; + if (destFreq>opChan[ch].baseFreq) { + newFreq=opChan[ch].baseFreq+c.value*octave(opChan[ch].baseFreq); + if (newFreq>=destFreq) { + newFreq=destFreq; + return2=true; + } + } else { + newFreq=opChan[ch].baseFreq-c.value*octave(opChan[ch].baseFreq); + if (newFreq<=destFreq) { + newFreq=destFreq; + return2=true; + } + } + if (!opChan[ch].portaPause) { + if (octave(opChan[ch].baseFreq)!=octave(newFreq)) { + opChan[ch].portaPause=true; + break; + } + } + opChan[ch].baseFreq=newFreq; + opChan[ch].portaPause=false; + opChan[ch].freqChanged=true; + if (return2) return 2; + break; + } + case DIV_CMD_LEGATO: { + opChan[ch].baseFreq=NOTE_FREQUENCY(c.value); + opChan[ch].freqChanged=true; + break; + } + case DIV_CMD_FM_MULT: { // TODO + unsigned short baseAddr=chanOffs_b[2]|opOffs[orderedOps[c.value]]; + DivInstrument* ins=parent->getIns(opChan[ch].ins); + DivInstrumentFM::Operator op=ins->fm.op[orderedOps[c.value]]; + rWrite(baseAddr+0x30,(c.value2&15)|(dtTable[op.dt&7]<<4)); + break; + } + case DIV_CMD_FM_TL: { // TODO + unsigned short baseAddr=chanOffs_b[2]|opOffs[orderedOps[c.value]]; + DivInstrument* ins=parent->getIns(opChan[ch].ins); + if (isOutput[ins->fm.alg][c.value]) { + rWrite(baseAddr+0x40,127-(((127-c.value2)*(opChan[ch].vol&0x7f))/127)); + } else { + rWrite(baseAddr+0x40,c.value2); + } + break; + } + case DIV_CMD_FM_AR: { + DivInstrument* ins=parent->getIns(opChan[ch].ins); + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator op=ins->fm.op[i]; + unsigned short baseAddr=chanOffs_b[2]|opOffs[i]; + rWrite(baseAddr+0x50,(c.value2&31)|(op.rs<<6)); + } + } else { + DivInstrumentFM::Operator op=ins->fm.op[orderedOps[c.value]]; + unsigned short baseAddr=chanOffs_b[2]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+0x50,(c.value2&31)|(op.rs<<6)); + } + break; + } + case DIV_CMD_GET_VOLMAX: + return 127; + break; + case DIV_ALWAYS_SET_VOLUME: + return 0; + break; + case DIV_CMD_PRE_PORTA: + break; + default: + //printf("WARNING: unimplemented command %d\n",c.cmd); + break; + } + return 1; +} + +static int opChanOffsL[4]={ + 0xa9, 0xaa, 0xa8, 0xa2 +}; + +static int opChanOffsH[4]={ + 0xad, 0xae, 0xac, 0xa6 +}; + +void DivPlatformYM2610BExt::tick() { + if (extMode) { + bool writeSomething=false; + unsigned char writeMask=2; + for (int i=0; i<4; i++) { + writeMask|=opChan[i].active<<(4+i); + if (opChan[i].keyOn || opChan[i].keyOff) { + writeSomething=true; + writeMask&=~(1<<(4+i)); + opChan[i].keyOff=false; + } + } + if (writeSomething) { + immWrite(0x28,writeMask); + } + } + + DivPlatformYM2610B::tick(); + + bool writeNoteOn=false; + unsigned char writeMask=2; + if (extMode) for (int i=0; i<4; i++) { + if (opChan[i].freqChanged) { + opChan[i].freq=parent->calcFreq(opChan[i].baseFreq,opChan[i].pitch); + if (opChan[i].freq>262143) opChan[i].freq=262143; + int freqt=toFreq(opChan[i].freq); + opChan[i].freqH=freqt>>8; + opChan[i].freqL=freqt&0xff; + immWrite(opChanOffsH[i],opChan[i].freqH); + immWrite(opChanOffsL[i],opChan[i].freqL); + opChan[i].freqChanged=false; + } + writeMask|=opChan[i].active<<(4+i); + if (opChan[i].keyOn) { + writeNoteOn=true; + writeMask|=1<<(4+i); + opChan[i].keyOn=false; + } + } + if (writeNoteOn) { + immWrite(0x28,writeMask); + } +} + +void DivPlatformYM2610BExt::muteChannel(int ch, bool mute) { + if (ch<2) { + DivPlatformYM2610B::muteChannel(ch,mute); + return; + } + if (ch>5) { + DivPlatformYM2610B::muteChannel(ch-3,mute); + return; + } + isOpMuted[ch-2]=mute; + + int ordch=orderedOps[ch-2]; + DivInstrument* ins=parent->getIns(opChan[ch-2].ins); + unsigned short baseAddr=chanOffs_b[2]|opOffs[ordch]; + DivInstrumentFM::Operator op=ins->fm.op[ordch]; + if (isOpMuted[ch-2]) { + rWrite(baseAddr+0x40,127); + } else if (isOutput[ins->fm.alg][ordch]) { + rWrite(baseAddr+0x40,127-(((127-op.tl)*(opChan[ch-2].vol&0x7f))/127)); + } else { + rWrite(baseAddr+0x40,op.tl); + } +} + +void DivPlatformYM2610BExt::forceIns() { + DivPlatformYM2610B::forceIns(); + for (int i=0; i<4; i++) { + opChan[i].insChanged=true; + if (opChan[i].active) { + opChan[i].keyOn=true; + opChan[i].freqChanged=true; + } + } +} + + +void* DivPlatformYM2610BExt::getChanState(int ch) { + if (ch>=6) return &chan[ch-3]; + if (ch>=2) return &opChan[ch-2]; + return &chan[ch]; +} + +void DivPlatformYM2610BExt::reset() { + DivPlatformYM2610B::reset(); + + for (int i=0; i<4; i++) { + opChan[i]=DivPlatformYM2610BExt::OpChannel(); + opChan[i].vol=127; + } + + // channel 2 mode + immWrite(0x27,0x40); + extMode=true; +} + +bool DivPlatformYM2610BExt::keyOffAffectsArp(int ch) { + return (ch>8); +} + +void DivPlatformYM2610BExt::notifyInsChange(int ins) { + DivPlatformYM2610B::notifyInsChange(ins); + for (int i=0; i<4; i++) { + if (opChan[i].ins==ins) { + opChan[i].insChanged=true; + } + } +} + +int DivPlatformYM2610BExt::init(DivEngine* parent, int channels, int sugRate, unsigned int flags) { + DivPlatformYM2610B::init(parent,channels,sugRate,flags); + for (int i=0; i<4; i++) { + isOpMuted[i]=false; + } + + reset(); + return 19; +} + +void DivPlatformYM2610BExt::quit() { + DivPlatformYM2610B::quit(); +} + +DivPlatformYM2610BExt::~DivPlatformYM2610BExt() { +} \ No newline at end of file diff --git a/src/engine/platform/ym2610bext.h b/src/engine/platform/ym2610bext.h new file mode 100644 index 000000000..25ca59196 --- /dev/null +++ b/src/engine/platform/ym2610bext.h @@ -0,0 +1,51 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "../dispatch.h" + +#include "ym2610b.h" + +class DivPlatformYM2610BExt: public DivPlatformYM2610B { + struct OpChannel { + DivMacroInt std; + unsigned char freqH, freqL; + int freq, baseFreq, pitch; + unsigned char ins; + signed char konCycles; + bool active, insChanged, freqChanged, keyOn, keyOff, portaPause; + int vol; + unsigned char pan; + OpChannel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), portaPause(false), vol(0), pan(3) {} + }; + OpChannel opChan[4]; + bool isOpMuted[4]; + friend void putDispatchChan(void*,int,int); + public: + int dispatch(DivCommand c); + void* getChanState(int chan); + void reset(); + void forceIns(); + void tick(); + void muteChannel(int ch, bool mute); + bool keyOffAffectsArp(int ch); + void notifyInsChange(int ins); + int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); + void quit(); + ~DivPlatformYM2610BExt(); +}; diff --git a/src/engine/platform/ym2610ext.cpp b/src/engine/platform/ym2610ext.cpp index 000ab69da..aff4bbf33 100644 --- a/src/engine/platform/ym2610ext.cpp +++ b/src/engine/platform/ym2610ext.cpp @@ -147,10 +147,6 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) { if (return2) return 2; break; } - case DIV_CMD_SAMPLE_MODE: { - // ignored on extended channel 2 mode. - break; - } case DIV_CMD_LEGATO: { opChan[ch].baseFreq=NOTE_FREQUENCY(c.value); opChan[ch].freqChanged=true; diff --git a/src/engine/platform/ym2610shared.h b/src/engine/platform/ym2610shared.h index b548a0ec1..82dad07d8 100644 --- a/src/engine/platform/ym2610shared.h +++ b/src/engine/platform/ym2610shared.h @@ -17,9 +17,14 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include "sound/ymfm/ymfm_opn.h" + static unsigned short chanOffs[4]={ 0x01, 0x02, 0x101, 0x102 }; +static unsigned short chanOffs_b[6]={ + 0x00, 0x01, 0x02, 0x100, 0x101, 0x102 +}; static unsigned short opOffs[4]={ 0x00, 0x04, 0x08, 0x0c }; @@ -45,4 +50,13 @@ static int orderedOps[4]={ #define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;} #define immWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } -#define CHIP_FREQBASE 9440540 \ No newline at end of file +#define CHIP_FREQBASE 9440540 + +class DivYM2610BInterface: public ymfm::ymfm_interface { + public: + DivEngine* parent; + int sampleBank; + uint8_t ymfm_external_read(ymfm::access_class type, uint32_t address); + void ymfm_external_write(ymfm::access_class type, uint32_t address, uint8_t data); + DivYM2610BInterface(): parent(NULL), sampleBank(0) {} +}; diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 6c825cb71..099daadd6 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -262,6 +262,10 @@ bool DivEngine::perSystemPostEffect(int ch, unsigned char effect, unsigned char case DIV_SYSTEM_YM2151: case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_EXT: + case DIV_SYSTEM_YM2610_FULL: + case DIV_SYSTEM_YM2610_FULL_EXT: + case DIV_SYSTEM_YM2610B: + case DIV_SYSTEM_YM2610B_EXT: switch (effect) { case 0x10: // LFO or noise mode if (sysOfChan[ch]==DIV_SYSTEM_YM2151) { @@ -324,42 +328,58 @@ bool DivEngine::perSystemPostEffect(int ch, unsigned char effect, unsigned char dispatchCmd(DivCommand(DIV_CMD_FM_PM_DEPTH,ch,effectVal&127)); break; case 0x20: // Neo Geo PSG mode - if (sysOfChan[ch]==DIV_SYSTEM_YM2610 || sysOfChan[ch]==DIV_SYSTEM_YM2610_EXT) { + if (sysOfChan[ch]==DIV_SYSTEM_YM2610 || sysOfChan[ch]==DIV_SYSTEM_YM2610_EXT + || sysOfChan[ch]==DIV_SYSTEM_YM2610_FULL || sysOfChan[ch]==DIV_SYSTEM_YM2610_FULL_EXT + || sysOfChan[ch]==DIV_SYSTEM_YM2610B || sysOfChan[ch]==DIV_SYSTEM_YM2610B_EXT) { dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal)); } break; case 0x21: // Neo Geo PSG noise freq - if (sysOfChan[ch]==DIV_SYSTEM_YM2610 || sysOfChan[ch]==DIV_SYSTEM_YM2610_EXT) { + if (sysOfChan[ch]==DIV_SYSTEM_YM2610 || sysOfChan[ch]==DIV_SYSTEM_YM2610_EXT + || sysOfChan[ch]==DIV_SYSTEM_YM2610_FULL || sysOfChan[ch]==DIV_SYSTEM_YM2610_FULL_EXT + || sysOfChan[ch]==DIV_SYSTEM_YM2610B || sysOfChan[ch]==DIV_SYSTEM_YM2610B_EXT) { dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_FREQ,ch,effectVal)); } break; case 0x22: // UNOFFICIAL: Neo Geo PSG envelope enable - if (sysOfChan[ch]==DIV_SYSTEM_YM2610 || sysOfChan[ch]==DIV_SYSTEM_YM2610_EXT) { + if (sysOfChan[ch]==DIV_SYSTEM_YM2610 || sysOfChan[ch]==DIV_SYSTEM_YM2610_EXT + || sysOfChan[ch]==DIV_SYSTEM_YM2610_FULL || sysOfChan[ch]==DIV_SYSTEM_YM2610_FULL_EXT + || sysOfChan[ch]==DIV_SYSTEM_YM2610B || sysOfChan[ch]==DIV_SYSTEM_YM2610B_EXT) { dispatchCmd(DivCommand(DIV_CMD_AY_ENVELOPE_SET,ch,effectVal)); } break; case 0x23: // UNOFFICIAL: Neo Geo PSG envelope period low - if (sysOfChan[ch]==DIV_SYSTEM_YM2610 || sysOfChan[ch]==DIV_SYSTEM_YM2610_EXT) { + if (sysOfChan[ch]==DIV_SYSTEM_YM2610 || sysOfChan[ch]==DIV_SYSTEM_YM2610_EXT + || sysOfChan[ch]==DIV_SYSTEM_YM2610_FULL || sysOfChan[ch]==DIV_SYSTEM_YM2610_FULL_EXT + || sysOfChan[ch]==DIV_SYSTEM_YM2610B || sysOfChan[ch]==DIV_SYSTEM_YM2610B_EXT) { dispatchCmd(DivCommand(DIV_CMD_AY_ENVELOPE_LOW,ch,effectVal)); } break; case 0x24: // UNOFFICIAL: Neo Geo PSG envelope period high - if (sysOfChan[ch]==DIV_SYSTEM_YM2610 || sysOfChan[ch]==DIV_SYSTEM_YM2610_EXT) { + if (sysOfChan[ch]==DIV_SYSTEM_YM2610 || sysOfChan[ch]==DIV_SYSTEM_YM2610_EXT + || sysOfChan[ch]==DIV_SYSTEM_YM2610_FULL || sysOfChan[ch]==DIV_SYSTEM_YM2610_FULL_EXT + || sysOfChan[ch]==DIV_SYSTEM_YM2610B || sysOfChan[ch]==DIV_SYSTEM_YM2610B_EXT) { dispatchCmd(DivCommand(DIV_CMD_AY_ENVELOPE_HIGH,ch,effectVal)); } break; case 0x25: // UNOFFICIAL: Neo Geo PSG envelope slide up - if (sysOfChan[ch]==DIV_SYSTEM_YM2610 || sysOfChan[ch]==DIV_SYSTEM_YM2610_EXT) { + if (sysOfChan[ch]==DIV_SYSTEM_YM2610 || sysOfChan[ch]==DIV_SYSTEM_YM2610_EXT + || sysOfChan[ch]==DIV_SYSTEM_YM2610_FULL || sysOfChan[ch]==DIV_SYSTEM_YM2610_FULL_EXT + || sysOfChan[ch]==DIV_SYSTEM_YM2610B || sysOfChan[ch]==DIV_SYSTEM_YM2610B_EXT) { dispatchCmd(DivCommand(DIV_CMD_AY_ENVELOPE_SLIDE,ch,-effectVal)); } break; case 0x26: // UNOFFICIAL: Neo Geo PSG envelope slide down - if (sysOfChan[ch]==DIV_SYSTEM_YM2610 || sysOfChan[ch]==DIV_SYSTEM_YM2610_EXT) { + if (sysOfChan[ch]==DIV_SYSTEM_YM2610 || sysOfChan[ch]==DIV_SYSTEM_YM2610_EXT + || sysOfChan[ch]==DIV_SYSTEM_YM2610_FULL || sysOfChan[ch]==DIV_SYSTEM_YM2610_FULL_EXT + || sysOfChan[ch]==DIV_SYSTEM_YM2610B || sysOfChan[ch]==DIV_SYSTEM_YM2610B_EXT) { dispatchCmd(DivCommand(DIV_CMD_AY_ENVELOPE_SLIDE,ch,effectVal)); } break; case 0x29: // auto-envelope - if (sysOfChan[ch]==DIV_SYSTEM_YM2610 || sysOfChan[ch]==DIV_SYSTEM_YM2610_EXT) { + if (sysOfChan[ch]==DIV_SYSTEM_YM2610 || sysOfChan[ch]==DIV_SYSTEM_YM2610_EXT + || sysOfChan[ch]==DIV_SYSTEM_YM2610_FULL || sysOfChan[ch]==DIV_SYSTEM_YM2610_FULL_EXT + || sysOfChan[ch]==DIV_SYSTEM_YM2610B || sysOfChan[ch]==DIV_SYSTEM_YM2610B_EXT) { dispatchCmd(DivCommand(DIV_CMD_AY_AUTO_ENVELOPE,ch,effectVal)); } break; diff --git a/src/engine/song.h b/src/engine/song.h index f4620eef9..ba1413e5f 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -90,6 +90,7 @@ enum DivSystem { DIV_SYSTEM_OPLL_DRUMS, DIV_SYSTEM_LYNX, DIV_SYSTEM_QSOUND, + DIV_SYSTEM_YM2610B_EXT, DIV_SYSTEM_SEGAPCM_COMPAT }; diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 54589e37d..85fe54b8e 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -135,6 +135,8 @@ DivSystem DivEngine::systemFromFile(unsigned char val) { return DIV_SYSTEM_LYNX; case 0xa9: return DIV_SYSTEM_SEGAPCM_COMPAT; + case 0xde: + return DIV_SYSTEM_YM2610B_EXT; case 0xe0: return DIV_SYSTEM_QSOUND; } @@ -256,6 +258,8 @@ unsigned char DivEngine::systemToFile(DivSystem val) { return 0xa8; case DIV_SYSTEM_SEGAPCM_COMPAT: return 0xa9; + case DIV_SYSTEM_YM2610B_EXT: + return 0xde; case DIV_SYSTEM_QSOUND: return 0xe0; @@ -376,6 +380,7 @@ int DivEngine::getChannelCount(DivSystem sys) { return 4; case DIV_SYSTEM_SEGAPCM_COMPAT: return 5; + case DIV_SYSTEM_YM2610B_EXT: case DIV_SYSTEM_QSOUND: return 19; } @@ -629,6 +634,8 @@ const char* DivEngine::getSystemName(DivSystem sys) { return "Atari Lynx"; case DIV_SYSTEM_SEGAPCM_COMPAT: return "SegaPCM (compatible 5-channel mode)"; + case DIV_SYSTEM_YM2610B_EXT: + return "Taito Arcade Extended Channel 3"; case DIV_SYSTEM_QSOUND: return "Capcom QSound"; } @@ -752,6 +759,8 @@ const char* DivEngine::getSystemChips(DivSystem sys) { return "Mikey"; case DIV_SYSTEM_SEGAPCM_COMPAT: return "SegaPCM (compatible 5-channel mode)"; + case DIV_SYSTEM_YM2610B_EXT: + return "Yamaha YM2610B Extended Channel 3"; case DIV_SYSTEM_QSOUND: return "Capcom DL-1425"; } @@ -822,6 +831,8 @@ bool DivEngine::isFMSystem(DivSystem sys) { sys==DIV_SYSTEM_YM2610_EXT || sys==DIV_SYSTEM_YM2610_FULL || sys==DIV_SYSTEM_YM2610_FULL_EXT || + sys==DIV_SYSTEM_YM2610B || + sys==DIV_SYSTEM_YM2610B_EXT || sys==DIV_SYSTEM_YMU759 || sys==DIV_SYSTEM_YM2151 || sys==DIV_SYSTEM_YM2612); @@ -834,7 +845,7 @@ bool DivEngine::isSTDSystem(DivSystem sys) { sys!=DIV_SYSTEM_YM2151); } -const char* chanNames[37][24]={ +const char* chanNames[38][24]={ {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "PCM"}, // YMU759/SegaPCM {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Square 1", "Square 2", "Square 3", "Noise"}, // Genesis {"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6", "Square 1", "Square 2", "Square 3", "Noise"}, // Genesis (extended channel 3) @@ -872,9 +883,10 @@ const char* chanNames[37][24]={ {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "4OP 1", "4OP 2", "4OP 3", "4OP 4", "4OP 5", "4OP 6"}, // OPL3 4-op {"FM 1", "FM 2", "FM 3", "4OP 1", "4OP 2", "4OP 3", "4OP 4", "4OP 5", "4OP 6", "Kick", "Snare", "Tom", "Top", "HiHat"}, // OPL3 4-op + drums {"PCM 1", "PCM 2", "PCM 3", "PCM 4", "PCM 5", "PCM 6", "PCM 7", "PCM 8", "PCM 9", "PCM 10", "PCM 11", "PCM 12", "PCM 13", "PCM 14", "PCM 15", "PCM 16", "ADPCM 1", "ADPCM 2", "ADPCM 3"}, // QSound + {"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6", "ADPCM-B"}, // YM2610B (extended channel 3) }; -const char* chanShortNames[37][24]={ +const char* chanShortNames[38][24]={ {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "PCM"}, // YMU759 {"F1", "F2", "F3", "F4", "F5", "F6", "S1", "S2", "S3", "NO"}, // Genesis {"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6", "S1", "S2", "S3", "S4"}, // Genesis (extended channel 3) @@ -912,9 +924,10 @@ const char* chanShortNames[37][24]={ {"F1", "F2", "F3", "F4", "F5", "F6", "Q1", "Q2", "Q3", "Q4", "Q5", "Q6"}, // OPL3 4-op {"F1", "F2", "F3", "Q1", "Q2", "Q3", "Q4", "Q5", "Q6", "BD", "SD", "TM", "TP", "HH"}, // OPL3 4-op + drums {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "A1", "A2", "A3"}, // QSound + {"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"}, // YM2610B (extended channel 3) }; -const int chanTypes[37][24]={ +const int chanTypes[38][24]={ {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4}, // YMU759 {0, 0, 0, 0, 0, 0, 1, 1, 1, 2}, // Genesis {0, 0, 5, 5, 5, 5, 0, 0, 0, 1, 1, 1, 2}, // Genesis (extended channel 3) @@ -952,9 +965,10 @@ const int chanTypes[37][24]={ {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // OPL3 4-op {0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2}, // OPL3 4-op + drums {3, 3, 3, 3}, //Lynx + {0, 0, 5, 5, 5, 5, 0, 0, 0, 1, 1, 1, 4, 4, 4, 4, 4, 4, 4}, // YM2610B (extended channel 3) }; -const DivInstrumentType chanPrefType[43][24]={ +const DivInstrumentType chanPrefType[44][24]={ {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM}, // YMU759 {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD}, // Genesis {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD}, // Genesis (extended channel 3) @@ -998,6 +1012,7 @@ const DivInstrumentType chanPrefType[43][24]={ {DIV_INS_SWAN, DIV_INS_SWAN, DIV_INS_SWAN, DIV_INS_SWAN}, // Swan {DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ}, // Z {DIV_INS_MIKEY, DIV_INS_MIKEY, DIV_INS_MIKEY, DIV_INS_MIKEY}, // Lynx + {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, // YM2610B (extended channel 3) }; const char* DivEngine::getChannelName(int chan) { @@ -1115,6 +1130,9 @@ const char* DivEngine::getChannelName(int chan) { case DIV_SYSTEM_YM2610B: return chanNames[31][dispatchChanOfChan[chan]]; break; + case DIV_SYSTEM_YM2610B_EXT: + return chanNames[37][dispatchChanOfChan[chan]]; + break; case DIV_SYSTEM_OPLL_DRUMS: case DIV_SYSTEM_OPL_DRUMS: case DIV_SYSTEM_OPL2_DRUMS: @@ -1251,6 +1269,9 @@ const char* DivEngine::getChannelShortName(int chan) { case DIV_SYSTEM_YM2610B: return chanShortNames[31][dispatchChanOfChan[chan]]; break; + case DIV_SYSTEM_YM2610B_EXT: + return chanShortNames[37][dispatchChanOfChan[chan]]; + break; case DIV_SYSTEM_OPLL_DRUMS: case DIV_SYSTEM_OPL_DRUMS: case DIV_SYSTEM_OPL2_DRUMS: @@ -1385,6 +1406,9 @@ int DivEngine::getChannelType(int chan) { case DIV_SYSTEM_YM2610B: return chanTypes[31][dispatchChanOfChan[chan]]; break; + case DIV_SYSTEM_YM2610B_EXT: + return chanTypes[37][dispatchChanOfChan[chan]]; + break; case DIV_SYSTEM_OPLL_DRUMS: case DIV_SYSTEM_OPL_DRUMS: case DIV_SYSTEM_OPL2_DRUMS: @@ -1519,6 +1543,9 @@ DivInstrumentType DivEngine::getPreferInsType(int chan) { case DIV_SYSTEM_YM2610B: return chanPrefType[31][dispatchChanOfChan[chan]]; break; + case DIV_SYSTEM_YM2610B_EXT: + return chanPrefType[43][dispatchChanOfChan[chan]]; + break; case DIV_SYSTEM_OPLL_DRUMS: return chanPrefType[32][dispatchChanOfChan[chan]]; break; diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index 93a2bcf42..5c2a4d427 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -158,8 +158,10 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write break; case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_FULL: + case DIV_SYSTEM_YM2610B: case DIV_SYSTEM_YM2610_EXT: case DIV_SYSTEM_YM2610_FULL_EXT: + case DIV_SYSTEM_YM2610B_EXT: for (int i=0; i<2; i++) { // set SL and RR to highest w->writeC(isSecond?0xa8:0x58); w->writeC(0x81+i); @@ -386,8 +388,10 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write break; case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_FULL: + case DIV_SYSTEM_YM2610B: case DIV_SYSTEM_YM2610_EXT: case DIV_SYSTEM_YM2610_FULL_EXT: + case DIV_SYSTEM_YM2610B_EXT: switch (write.addr>>8) { case 0: // port 0 w->writeC(isSecond?0xa8:0x58); @@ -655,8 +659,10 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) { break; case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_FULL: + case DIV_SYSTEM_YM2610B: case DIV_SYSTEM_YM2610_EXT: case DIV_SYSTEM_YM2610_FULL_EXT: + case DIV_SYSTEM_YM2610B_EXT: if (!hasOPNB) { hasOPNB=disCont[i].dispatch->chipClock; willExport[i]=true; @@ -667,6 +673,10 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) { hasOPNB|=0x40000000; howManyChips++; } + break; + if (((song.system[i]==DIV_SYSTEM_YM2610B) || (song.system[i]==DIV_SYSTEM_YM2610B_EXT)) && (!(hasOPNB&0x80000000))) { // YM2610B flag + hasOPNB|=0x80000000; + } break; case DIV_SYSTEM_AY8910: case DIV_SYSTEM_AY8930: diff --git a/src/gui/debug.cpp b/src/gui/debug.cpp index 8b06f6dec..4a259145b 100644 --- a/src/gui/debug.cpp +++ b/src/gui/debug.cpp @@ -29,6 +29,8 @@ #include "../engine/platform/arcade.h" #include "../engine/platform/ym2610.h" #include "../engine/platform/ym2610ext.h" +#include "../engine/platform/ym2610b.h" +#include "../engine/platform/ym2610bext.h" #include "../engine/platform/ay.h" #include "../engine/platform/ay8930.h" #include "../engine/platform/tia.h" diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index ee8d5a954..f0a9bc7ec 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4485,6 +4485,8 @@ bool FurnaceGUI::loop() { sysAddOption(DIV_SYSTEM_YM2610_EXT); sysAddOption(DIV_SYSTEM_YM2610_FULL); sysAddOption(DIV_SYSTEM_YM2610_FULL_EXT); + sysAddOption(DIV_SYSTEM_YM2610B); + sysAddOption(DIV_SYSTEM_YM2610B_EXT); sysAddOption(DIV_SYSTEM_AY8910); sysAddOption(DIV_SYSTEM_AMIGA); sysAddOption(DIV_SYSTEM_TIA); @@ -4716,6 +4718,8 @@ bool FurnaceGUI::loop() { case DIV_SYSTEM_YM2610_EXT: case DIV_SYSTEM_YM2610_FULL: case DIV_SYSTEM_YM2610_FULL_EXT: + case DIV_SYSTEM_YM2610B: + case DIV_SYSTEM_YM2610B_EXT: case DIV_SYSTEM_YMU759: ImGui::Text("nothing to configure"); break; @@ -4749,6 +4753,8 @@ bool FurnaceGUI::loop() { sysChangeOption(i,DIV_SYSTEM_YM2610_EXT); sysChangeOption(i,DIV_SYSTEM_YM2610_FULL); sysChangeOption(i,DIV_SYSTEM_YM2610_FULL_EXT); + sysChangeOption(i,DIV_SYSTEM_YM2610B); + sysChangeOption(i,DIV_SYSTEM_YM2610B_EXT); sysChangeOption(i,DIV_SYSTEM_AY8910); sysChangeOption(i,DIV_SYSTEM_AMIGA); sysChangeOption(i,DIV_SYSTEM_TIA); From a132a28fcb19c05f24d6e0b346b587b9f889fd2b Mon Sep 17 00:00:00 2001 From: cam900 Date: Fri, 25 Feb 2022 17:37:43 +0900 Subject: [PATCH 002/637] Fix VGM saving --- src/engine/vgmOps.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index 7663914f8..fd5fa3ce4 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -673,10 +673,9 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) { hasOPNB|=0x40000000; howManyChips++; } - break; - if (((song.system[i]==DIV_SYSTEM_YM2610B) || (song.system[i]==DIV_SYSTEM_YM2610B_EXT)) && (!(hasOPNB&0x80000000))) { // YM2610B flag + if (((song.system[i]==DIV_SYSTEM_YM2610B) || (song.system[i]==DIV_SYSTEM_YM2610B_EXT)) && (!(hasOPNB&0x80000000))) { // YM2610B flag hasOPNB|=0x80000000; - } + } break; case DIV_SYSTEM_AY8910: case DIV_SYSTEM_AY8930: From e96cd77ba763c764fef45f8050ab6c297423a36e Mon Sep 17 00:00:00 2001 From: cam900 Date: Fri, 25 Feb 2022 17:39:33 +0900 Subject: [PATCH 003/637] Remove unnecessary interface --- src/engine/platform/ym2610shared.h | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/engine/platform/ym2610shared.h b/src/engine/platform/ym2610shared.h index 82dad07d8..2f3e563ec 100644 --- a/src/engine/platform/ym2610shared.h +++ b/src/engine/platform/ym2610shared.h @@ -17,8 +17,6 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "sound/ymfm/ymfm_opn.h" - static unsigned short chanOffs[4]={ 0x01, 0x02, 0x101, 0x102 }; @@ -51,12 +49,3 @@ static int orderedOps[4]={ #define immWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } #define CHIP_FREQBASE 9440540 - -class DivYM2610BInterface: public ymfm::ymfm_interface { - public: - DivEngine* parent; - int sampleBank; - uint8_t ymfm_external_read(ymfm::access_class type, uint32_t address); - void ymfm_external_write(ymfm::access_class type, uint32_t address, uint8_t data); - DivYM2610BInterface(): parent(NULL), sampleBank(0) {} -}; From ff743c92fda88e8955a79316dc9c2d34d2e0b6e4 Mon Sep 17 00:00:00 2001 From: cam900 Date: Sat, 26 Feb 2022 00:31:17 +0900 Subject: [PATCH 004/637] Fix build --- src/engine/platform/ym2610b.h | 4 ++++ src/engine/platform/ym2610shared.h | 3 --- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/engine/platform/ym2610b.h b/src/engine/platform/ym2610b.h index 8b18acd15..892f68437 100644 --- a/src/engine/platform/ym2610b.h +++ b/src/engine/platform/ym2610b.h @@ -26,6 +26,10 @@ #include "ym2610.h" +static unsigned short chanOffs_b[6]={ + 0x00, 0x01, 0x02, 0x100, 0x101, 0x102 +}; + class DivPlatformYM2610B: public DivDispatch { protected: struct Channel { diff --git a/src/engine/platform/ym2610shared.h b/src/engine/platform/ym2610shared.h index 2f3e563ec..7891a14fb 100644 --- a/src/engine/platform/ym2610shared.h +++ b/src/engine/platform/ym2610shared.h @@ -20,9 +20,6 @@ static unsigned short chanOffs[4]={ 0x01, 0x02, 0x101, 0x102 }; -static unsigned short chanOffs_b[6]={ - 0x00, 0x01, 0x02, 0x100, 0x101, 0x102 -}; static unsigned short opOffs[4]={ 0x00, 0x04, 0x08, 0x0c }; From b1a49dcdc5dcdc6808b775c63157afa28641d070 Mon Sep 17 00:00:00 2001 From: cam900 Date: Sat, 26 Feb 2022 00:50:49 +0900 Subject: [PATCH 005/637] Fix actually --- src/engine/platform/ym2610b.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/engine/platform/ym2610b.h b/src/engine/platform/ym2610b.h index 892f68437..f995d5975 100644 --- a/src/engine/platform/ym2610b.h +++ b/src/engine/platform/ym2610b.h @@ -26,12 +26,12 @@ #include "ym2610.h" -static unsigned short chanOffs_b[6]={ - 0x00, 0x01, 0x02, 0x100, 0x101, 0x102 -}; - class DivPlatformYM2610B: public DivDispatch { protected: + const unsigned short chanOffs_b[6]={ + 0x00, 0x01, 0x02, 0x100, 0x101, 0x102 + }; + struct Channel { DivInstrumentFM state; unsigned char freqH, freqL; From d64ddaadeefe2d1b79257c4cae61e571dcf4caf8 Mon Sep 17 00:00:00 2001 From: cam900 Date: Sat, 26 Feb 2022 01:12:37 +0900 Subject: [PATCH 006/637] Further fix build --- src/engine/platform/ym2610.h | 4 +++ src/engine/platform/ym2610b.cpp | 46 +++++++++++++++--------------- src/engine/platform/ym2610b.h | 2 +- src/engine/platform/ym2610bext.cpp | 20 ++++++------- src/engine/platform/ym2610shared.h | 3 -- 5 files changed, 38 insertions(+), 37 deletions(-) diff --git a/src/engine/platform/ym2610.h b/src/engine/platform/ym2610.h index 317870f69..65456c720 100644 --- a/src/engine/platform/ym2610.h +++ b/src/engine/platform/ym2610.h @@ -35,6 +35,10 @@ class DivYM2610Interface: public ymfm::ymfm_interface { class DivPlatformYM2610: public DivDispatch { protected: + const unsigned short chanOffs[4]={ + 0x01, 0x02, 0x101, 0x102 + }; + struct Channel { DivInstrumentFM state; unsigned char freqH, freqL; diff --git a/src/engine/platform/ym2610b.cpp b/src/engine/platform/ym2610b.cpp index 5e755eeb5..839811920 100644 --- a/src/engine/platform/ym2610b.cpp +++ b/src/engine/platform/ym2610b.cpp @@ -250,7 +250,7 @@ void DivPlatformYM2610B::tick() { if (chan[i].std.hadVol) { chan[i].outVol=(chan[i].vol*MIN(127,chan[i].std.vol))/127; for (int j=0; j<4; j++) { - unsigned short baseAddr=chanOffs_b[i]|opOffs[j]; + unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; if (isOutput[chan[i].state.alg][j]) { rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); @@ -278,9 +278,9 @@ void DivPlatformYM2610B::tick() { if (chan[i].std.hadAlg) { chan[i].state.alg=chan[i].std.alg; - rWrite(chanOffs_b[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); + rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); if (!parent->song.algMacroBehavior) for (int j=0; j<4; j++) { - unsigned short baseAddr=chanOffs_b[i]|opOffs[j]; + unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; if (isMuted[i]) { rWrite(baseAddr+ADDR_TL,127); @@ -295,18 +295,18 @@ void DivPlatformYM2610B::tick() { } if (chan[i].std.hadFb) { chan[i].state.fb=chan[i].std.fb; - rWrite(chanOffs_b[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); + rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); } if (chan[i].std.hadFms) { chan[i].state.fms=chan[i].std.fms; - rWrite(chanOffs_b[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); } if (chan[i].std.hadAms) { chan[i].state.ams=chan[i].std.ams; - rWrite(chanOffs_b[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); } for (int j=0; j<4; j++) { - unsigned short baseAddr=chanOffs_b[i]|opOffs[j]; + unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; DivMacroInt::IntOp& m=chan[i].std.op[j]; if (m.hadAm) { @@ -409,8 +409,8 @@ void DivPlatformYM2610B::tick() { chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq)); if (chan[i].freq>262143) chan[i].freq=262143; int freqt=toFreq(chan[i].freq); - immWrite(chanOffs_b[i]+ADDR_FREQH,freqt>>8); - immWrite(chanOffs_b[i]+ADDR_FREQ,freqt&0xff); + immWrite(chanOffs[i]+ADDR_FREQH,freqt>>8); + immWrite(chanOffs[i]+ADDR_FREQ,freqt&0xff); chan[i].freqChanged=false; } if (chan[i].keyOn) { @@ -566,7 +566,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { } for (int i=0; i<6; i++) { - unsigned short baseAddr=chanOffs_b[c.chan]|opOffs[i]; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; if (isOutput[chan[c.chan].state.alg][i]) { if (!chan[c.chan].active || chan[c.chan].insChanged) { @@ -587,8 +587,8 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { } } if (chan[c.chan].insChanged) { - rWrite(chanOffs_b[c.chan]+ADDR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3)); - rWrite(chanOffs_b[c.chan]+ADDR_LRAF,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|(chan[c.chan].state.fms&7)|((chan[c.chan].state.ams&3)<<4)); + rWrite(chanOffs[c.chan]+ADDR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3)); + rWrite(chanOffs[c.chan]+ADDR_LRAF,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|(chan[c.chan].state.fms&7)|((chan[c.chan].state.ams&3)<<4)); } chan[c.chan].insChanged=false; @@ -658,7 +658,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { break; } for (int i=0; i<4; i++) { - unsigned short baseAddr=chanOffs_b[c.chan]|opOffs[i]; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; if (isOutput[chan[c.chan].state.alg][i]) { rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); @@ -699,7 +699,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { break; } if (c.chan>5) break; - rWrite(chanOffs_b[c.chan]+ADDR_LRAF,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|(chan[c.chan].state.fms&7)|((chan[c.chan].state.ams&3)<<4)); + rWrite(chanOffs[c.chan]+ADDR_LRAF,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|(chan[c.chan].state.fms&7)|((chan[c.chan].state.ams&3)<<4)); break; } case DIV_CMD_PITCH: { @@ -783,12 +783,12 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { case DIV_CMD_FM_FB: { if (c.chan>5) break; chan[c.chan].state.fb=c.value&7; - rWrite(chanOffs_b[c.chan]+ADDR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3)); + rWrite(chanOffs[c.chan]+ADDR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3)); break; } case DIV_CMD_FM_MULT: { if (c.chan>5) break; - unsigned short baseAddr=chanOffs_b[c.chan]|opOffs[orderedOps[c.value]]; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; op.mult=c.value2&15; rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); @@ -796,7 +796,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { } case DIV_CMD_FM_TL: { if (c.chan>5) break; - unsigned short baseAddr=chanOffs_b[c.chan]|opOffs[orderedOps[c.value]]; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; op.tl=c.value2; if (isOutput[chan[c.chan].state.alg][c.value]) { @@ -812,13 +812,13 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { for (int i=0; i<4; i++) { DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; op.ar=c.value2&31; - unsigned short baseAddr=chanOffs_b[c.chan]|opOffs[i]; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); } } else { DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; op.ar=c.value2&31; - unsigned short baseAddr=chanOffs_b[c.chan]|opOffs[orderedOps[c.value]]; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); } break; @@ -920,13 +920,13 @@ void DivPlatformYM2610B::muteChannel(int ch, bool mute) { return; } // FM - rWrite(chanOffs_b[ch]+ADDR_LRAF,(isMuted[ch]?0:(chan[ch].pan<<6))|(chan[ch].state.fms&7)|((chan[ch].state.ams&3)<<4)); + rWrite(chanOffs[ch]+ADDR_LRAF,(isMuted[ch]?0:(chan[ch].pan<<6))|(chan[ch].state.fms&7)|((chan[ch].state.ams&3)<<4)); } void DivPlatformYM2610B::forceIns() { for (int i=0; i<6; i++) { for (int j=0; j<4; j++) { - unsigned short baseAddr=chanOffs_b[i]|opOffs[j]; + unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; if (isOutput[chan[i].state.alg][j]) { rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); @@ -940,8 +940,8 @@ void DivPlatformYM2610B::forceIns() { rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); } - rWrite(chanOffs_b[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); - rWrite(chanOffs_b[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); + rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); if (chan[i].active) { chan[i].keyOn=true; chan[i].freqChanged=true; diff --git a/src/engine/platform/ym2610b.h b/src/engine/platform/ym2610b.h index f995d5975..7ca766c22 100644 --- a/src/engine/platform/ym2610b.h +++ b/src/engine/platform/ym2610b.h @@ -28,7 +28,7 @@ class DivPlatformYM2610B: public DivDispatch { protected: - const unsigned short chanOffs_b[6]={ + const unsigned short chanOffs[6]={ 0x00, 0x01, 0x02, 0x100, 0x101, 0x102 }; diff --git a/src/engine/platform/ym2610bext.cpp b/src/engine/platform/ym2610bext.cpp index 5c3649164..f48fe19ab 100644 --- a/src/engine/platform/ym2610bext.cpp +++ b/src/engine/platform/ym2610bext.cpp @@ -37,7 +37,7 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(opChan[ch].ins); - unsigned short baseAddr=chanOffs_b[2]|opOffs[ordch]; + unsigned short baseAddr=chanOffs[2]|opOffs[ordch]; DivInstrumentFM::Operator op=ins->fm.op[ordch]; // TODO: how does this work?! if (isOpMuted[ch]) { @@ -56,8 +56,8 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) { rWrite(baseAddr+0x90,op.ssgEnv&15); } if (opChan[ch].insChanged) { // TODO how does this work? - rWrite(chanOffs_b[2]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3)); - rWrite(chanOffs_b[2]+0xb4,(opChan[ch].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4)); + rWrite(chanOffs[2]+0xb0,(ins->fm.alg&7)|(ins->fm.fb<<3)); + rWrite(chanOffs[2]+0xb4,(opChan[ch].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4)); } opChan[ch].insChanged=false; @@ -77,7 +77,7 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) { case DIV_CMD_VOLUME: { opChan[ch].vol=c.value; DivInstrument* ins=parent->getIns(opChan[ch].ins); - unsigned short baseAddr=chanOffs_b[2]|opOffs[ordch]; + unsigned short baseAddr=chanOffs[2]|opOffs[ordch]; DivInstrumentFM::Operator op=ins->fm.op[ordch]; if (isOpMuted[ch]) { rWrite(baseAddr+0x40,127); @@ -110,7 +110,7 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) { } DivInstrument* ins=parent->getIns(opChan[ch].ins); // TODO: ??? - rWrite(chanOffs_b[2]+0xb4,(opChan[ch].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4)); + rWrite(chanOffs[2]+0xb4,(opChan[ch].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4)); break; } case DIV_CMD_PITCH: { @@ -153,14 +153,14 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) { break; } case DIV_CMD_FM_MULT: { // TODO - unsigned short baseAddr=chanOffs_b[2]|opOffs[orderedOps[c.value]]; + unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]]; DivInstrument* ins=parent->getIns(opChan[ch].ins); DivInstrumentFM::Operator op=ins->fm.op[orderedOps[c.value]]; rWrite(baseAddr+0x30,(c.value2&15)|(dtTable[op.dt&7]<<4)); break; } case DIV_CMD_FM_TL: { // TODO - unsigned short baseAddr=chanOffs_b[2]|opOffs[orderedOps[c.value]]; + unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]]; DivInstrument* ins=parent->getIns(opChan[ch].ins); if (isOutput[ins->fm.alg][c.value]) { rWrite(baseAddr+0x40,127-(((127-c.value2)*(opChan[ch].vol&0x7f))/127)); @@ -174,12 +174,12 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) { if (c.value<0) { for (int i=0; i<4; i++) { DivInstrumentFM::Operator op=ins->fm.op[i]; - unsigned short baseAddr=chanOffs_b[2]|opOffs[i]; + unsigned short baseAddr=chanOffs[2]|opOffs[i]; rWrite(baseAddr+0x50,(c.value2&31)|(op.rs<<6)); } } else { DivInstrumentFM::Operator op=ins->fm.op[orderedOps[c.value]]; - unsigned short baseAddr=chanOffs_b[2]|opOffs[orderedOps[c.value]]; + unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]]; rWrite(baseAddr+0x50,(c.value2&31)|(op.rs<<6)); } break; @@ -264,7 +264,7 @@ void DivPlatformYM2610BExt::muteChannel(int ch, bool mute) { int ordch=orderedOps[ch-2]; DivInstrument* ins=parent->getIns(opChan[ch-2].ins); - unsigned short baseAddr=chanOffs_b[2]|opOffs[ordch]; + unsigned short baseAddr=chanOffs[2]|opOffs[ordch]; DivInstrumentFM::Operator op=ins->fm.op[ordch]; if (isOpMuted[ch-2]) { rWrite(baseAddr+0x40,127); diff --git a/src/engine/platform/ym2610shared.h b/src/engine/platform/ym2610shared.h index 7891a14fb..c41d5520a 100644 --- a/src/engine/platform/ym2610shared.h +++ b/src/engine/platform/ym2610shared.h @@ -17,9 +17,6 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -static unsigned short chanOffs[4]={ - 0x01, 0x02, 0x101, 0x102 -}; static unsigned short opOffs[4]={ 0x00, 0x04, 0x08, 0x0c }; From 34405de03c27cfc142a42df9a7590bb8914d0aca Mon Sep 17 00:00:00 2001 From: cam900 Date: Sun, 27 Feb 2022 06:40:13 +0900 Subject: [PATCH 007/637] Fix build actually --- src/engine/platform/ym2610b.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/ym2610b.cpp b/src/engine/platform/ym2610b.cpp index 839811920..770454a8d 100644 --- a/src/engine/platform/ym2610b.cpp +++ b/src/engine/platform/ym2610b.cpp @@ -565,7 +565,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { chan[c.chan].state=ins->fm; } - for (int i=0; i<6; i++) { + for (int i=0; i<4; i++) { unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; if (isOutput[chan[c.chan].state.alg][i]) { From e243a8558eca663a6f266522a8fd5a41b0c59c60 Mon Sep 17 00:00:00 2001 From: cam900 Date: Sun, 27 Feb 2022 06:47:31 +0900 Subject: [PATCH 008/637] Add YM2610B in documents --- papers/doc/7-systems/ym2610.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/papers/doc/7-systems/ym2610.md b/papers/doc/7-systems/ym2610.md index dbe8b6a54..f557ea23d 100644 --- a/papers/doc/7-systems/ym2610.md +++ b/papers/doc/7-systems/ym2610.md @@ -4,6 +4,8 @@ originally an arcade board, but SNK shortly adapted it to a rather expensive vid its soundchip is a 3-in-1: FM, YM2149 (AY-3-8910 clone) and ADPCM in a single package! +YM2610B variant is YM2610 with 2 extra FM channels, it's mainly used at some 90s Taito Arcade hardwares. + # effects - `10xy`: set LFO parameters. From 2312ab19d248573036e1e713f5ced46f7c5f337a Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 28 Feb 2022 03:04:04 +0900 Subject: [PATCH 009/637] Split YM2610B document --- papers/doc/7-systems/README.md | 1 + papers/doc/7-systems/ym2610.md | 2 -- papers/doc/7-systems/ym2610b.md | 57 +++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 papers/doc/7-systems/ym2610b.md diff --git a/papers/doc/7-systems/README.md b/papers/doc/7-systems/README.md index 90a17ee2d..78aeb8cea 100644 --- a/papers/doc/7-systems/README.md +++ b/papers/doc/7-systems/README.md @@ -10,6 +10,7 @@ this is a list of systems that Furnace supports, including each system's effects - [Commodore 64](c64.md) - [Arcade (YM2151/PCM)](arcade.md) - [Neo Geo/YM2610](ym2610.md) +- [Taito Arcade/YM2610B](ym2610b.md) - [AY-3-8910](ay8910.md) - [Amiga](amiga.md) - [Yamaha YM2612 standalone](ym2612.md) diff --git a/papers/doc/7-systems/ym2610.md b/papers/doc/7-systems/ym2610.md index f557ea23d..dbe8b6a54 100644 --- a/papers/doc/7-systems/ym2610.md +++ b/papers/doc/7-systems/ym2610.md @@ -4,8 +4,6 @@ originally an arcade board, but SNK shortly adapted it to a rather expensive vid its soundchip is a 3-in-1: FM, YM2149 (AY-3-8910 clone) and ADPCM in a single package! -YM2610B variant is YM2610 with 2 extra FM channels, it's mainly used at some 90s Taito Arcade hardwares. - # effects - `10xy`: set LFO parameters. diff --git a/papers/doc/7-systems/ym2610b.md b/papers/doc/7-systems/ym2610b.md new file mode 100644 index 000000000..0910d1d3d --- /dev/null +++ b/papers/doc/7-systems/ym2610b.md @@ -0,0 +1,57 @@ +# Taito Arcade/Yamaha YM2610 + +YM2610B is basically YM2610 with 2 extra FM channels used at some 90s Taito Arcade hardwares. + +# effects + +- `10xy`: set LFO parameters. + - `x` toggles the LFO. + - `y` sets its speed. +- `11xx`: set feedback of channel. +- `12xx`: set operator 1 level. +- `13xx`: set operator 2 level. +- `14xx`: set operator 3 level. +- `15xx`: set operator 4 level. +- `16xy`: set multiplier of operator. + - `x` is the operator (1-4). + - `y` is the mutliplier. +- `18xx`: toggle extended channel 2 mode. + - 0 disables it and 1 enables it. + - only in extended channel 2 system. +- `19xx`: set attack of all operators. +- `1Axx`: set attack of operator 1. +- `1Bxx`: set attack of operator 2. +- `1Cxx`: set attack of operator 3. +- `1Dxx`: set attack of operator 4. +- `20xx`: set SSG channel mode. `xx` may be one of the following: + - `00`: square + - `01`: noise + - `02`: square and noise + - `03`: nothing (apparently) + - `04`: envelope and square + - `05`: envelope and noise + - `06`: envelope and square and noise + - `07`: nothing +- `21xx`: set noise frequency. `xx` is a value between 00 and 1F. +- `22xy`: set envelope mode. + - `x` sets the envelope shape, which may be one of the following: + - `0: \___` decay + - `4: /___` attack once + - `8: \\\\` saw + - `9: \___` decay + - `A: \/\/` inverse obelisco + - `B: \¯¯¯` decay once + - `C: ////` inverse saw + - `D: /¯¯¯` attack + - `E: /\/\` obelisco + - `F: /___` attack once + - if `y` is 1 then the envelope will affect this channel. +- `23xx`: set envelope period low byte. +- `24xx`: set envelope period high byte. +- `25xx`: slide envelope period up. +- `26xx`: slide envelope period down. +- `29xy`: enable SSG auto-envelope mode. + - in this mode the envelope period is set to the channel's notes, multiplied by a fraction. + - `x` is the numerator. + - `y` is the denominator. + - if `x` or `y` are 0 this will disable auto-envelope mode. \ No newline at end of file From d92b662851bc39ff3d2fe51e71eb7c6c0993dc39 Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 28 Feb 2022 03:05:31 +0900 Subject: [PATCH 010/637] Typo --- papers/doc/7-systems/ym2610b.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/papers/doc/7-systems/ym2610b.md b/papers/doc/7-systems/ym2610b.md index 0910d1d3d..da4f542d4 100644 --- a/papers/doc/7-systems/ym2610b.md +++ b/papers/doc/7-systems/ym2610b.md @@ -1,4 +1,4 @@ -# Taito Arcade/Yamaha YM2610 +# Taito Arcade/Yamaha YM2610B YM2610B is basically YM2610 with 2 extra FM channels used at some 90s Taito Arcade hardwares. From ac1c65fd62310e39f0444e87f60d8a8b96d03e84 Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 28 Feb 2022 04:45:55 +0900 Subject: [PATCH 011/637] Add register sheet for YM2610* --- src/engine/platform/ym2610.cpp | 212 ++++++++++++++++++++++++ src/engine/platform/ym2610.h | 1 + src/engine/platform/ym2610b.cpp | 276 ++++++++++++++++++++++++++++++++ src/engine/platform/ym2610b.h | 1 + 4 files changed, 490 insertions(+) diff --git a/src/engine/platform/ym2610.cpp b/src/engine/platform/ym2610.cpp index e9e77d813..a6f29e4b8 100644 --- a/src/engine/platform/ym2610.cpp +++ b/src/engine/platform/ym2610.cpp @@ -32,6 +32,218 @@ static unsigned char konOffs[4]={ #define CHIP_DIVIDER 32 +const char* regCheatSheetYM2610[]={ + // SSG + "SSG_FreqL_A", "000", + "SSG_FreqH_A", "001", + "SSG_FreqL_B", "002", + "SSG_FreqH_B", "003", + "SSG_FreqL_C", "004", + "SSG_FreqH_C", "005", + "SSG_FreqNoise", "006", + "SSG_Enable", "007", + "SSG_Volume_A", "008", + "SSG_Volume_B", "009", + "SSG_Volume_C", "00A", + "SSG_FreqL_Env", "00B", + "SSG_FreqH_Env", "00C", + "SSG_Control_Env", "00D", + // ADPCM-B + "ADPCMB_Control", "010", + "ADPCMB_L_R", "011", + "ADPCMB_StartL", "012", + "ADPCMB_StartH", "013", + "ADPCMB_EndL", "014", + "ADPCMB_EndH", "015", + "ADPCMB_FreqL", "019", + "ADPCMB_FreqH", "01A", + "ADPCMB_Volume", "01B", + "ADPCM_Flag", "01C", + // FM (Common) + "FM_Test", "021", + "FM_LFOFreq", "022", + "ClockA1", "024", + "ClockA2", "025", + "ClockB", "026", + "FM_Control", "027", + "FM_NoteCtl", "028", + // FM (Channel 1-2) + "FM1_Op1_DT_MULT", "031", + "FM2_Op1_DT_MULT", "032", + "FM1_Op2_DT_MULT", "035", + "FM2_Op2_DT_MULT", "036", + "FM1_Op3_DT_MULT", "039", + "FM2_Op3_DT_MULT", "03A", + "FM1_Op4_DT_MULT", "03D", + "FM2_Op4_DT_MULT", "03E", + "FM1_Op1_TL", "041", + "FM2_Op1_TL", "042", + "FM1_Op2_TL", "045", + "FM2_Op2_TL", "046", + "FM1_Op3_TL", "049", + "FM2_Op3_TL", "04A", + "FM1_Op4_TL", "04D", + "FM2_Op4_TL", "04E", + "FM1_Op1_KS_AR", "051", + "FM2_Op1_KS_AR", "052", + "FM1_Op2_KS_AR", "055", + "FM2_Op2_KS_AR", "056", + "FM1_Op3_KS_AR", "059", + "FM2_Op3_KS_AR", "05A", + "FM1_Op4_KS_AR", "05D", + "FM2_Op4_KS_AR", "05E", + "FM1_Op1_AM_DR", "061", + "FM2_Op1_AM_DR", "062", + "FM1_Op2_AM_DR", "065", + "FM2_Op2_AM_DR", "066", + "FM1_Op3_AM_DR", "069", + "FM2_Op3_AM_DR", "06A", + "FM1_Op4_AM_DR", "06D", + "FM2_Op4_AM_DR", "06E", + "FM1_Op1_SR", "071", + "FM2_Op1_SR", "072", + "FM1_Op2_SR", "075", + "FM2_Op2_SR", "076", + "FM1_Op3_SR", "079", + "FM2_Op3_SR", "07A", + "FM1_Op4_SR", "07D", + "FM2_Op4_SR", "07E", + "FM1_Op1_SL_RR", "081", + "FM2_Op1_SL_RR", "082", + "FM1_Op2_SL_RR", "085", + "FM2_Op2_SL_RR", "086", + "FM1_Op3_SL_RR", "089", + "FM2_Op3_SL_RR", "08A", + "FM1_Op4_SL_RR", "08D", + "FM2_Op4_SL_RR", "08E", + "FM1_Op1_SSG_EG", "091", + "FM2_Op1_SSG_EG", "092", + "FM1_Op2_SSG_EG", "095", + "FM2_Op2_SSG_EG", "096", + "FM1_Op3_SSG_EG", "099", + "FM2_Op3_SSG_EG", "09A", + "FM1_Op4_SSG_EG", "09D", + "FM2_Op4_SSG_EG", "09E", + "FM1_FNum1", "0A1", + "FM2_(Op1)FNum1", "0A2", + "FM1_FNum2", "0A5", + "FM2_(Op1)FNum2", "0A6", + "FM2_Op2_FNum1", "0A8", + "FM2_Op3_FNum1", "0A9", + "FM2_Op4_FNum1", "0AA", + "FM2_Op2_FNum2", "0AC", + "FM2_Op3_FNum2", "0AD", + "FM2_Op4_FNum2", "0AE", + "FM1_FB_ALG", "0B1", + "FM2_FB_ALG", "0B2", + "FM1_Pan_LFO", "0B5", + "FM2_Pan_LFO", "0B6", + // ADPCM-A + "ADPCMA_Control", "100", + "ADPCMA_MVol", "101", + "ADPCMA_Test", "102", + "ADPCMA_Ch1_Vol", "108", + "ADPCMA_Ch2_Vol", "109", + "ADPCMA_Ch3_Vol", "10A", + "ADPCMA_Ch4_Vol", "10B", + "ADPCMA_Ch5_Vol", "10C", + "ADPCMA_Ch6_Vol", "10D", + "ADPCMA_Ch1_StL", "110", + "ADPCMA_Ch2_StL", "111", + "ADPCMA_Ch3_StL", "112", + "ADPCMA_Ch4_StL", "113", + "ADPCMA_Ch5_StL", "114", + "ADPCMA_Ch6_StL", "115", + "ADPCMA_Ch1_StH", "118", + "ADPCMA_Ch2_StH", "119", + "ADPCMA_Ch3_StH", "11A", + "ADPCMA_Ch4_StH", "11B", + "ADPCMA_Ch5_StH", "11C", + "ADPCMA_Ch6_StH", "11D", + "ADPCMA_Ch1_EdL", "120", + "ADPCMA_Ch2_EdL", "121", + "ADPCMA_Ch3_EdL", "122", + "ADPCMA_Ch4_EdL", "123", + "ADPCMA_Ch5_EdL", "124", + "ADPCMA_Ch6_EdL", "125", + "ADPCMA_Ch1_EdH", "128", + "ADPCMA_Ch2_EdH", "129", + "ADPCMA_Ch3_EdH", "12A", + "ADPCMA_Ch4_EdH", "12B", + "ADPCMA_Ch5_EdH", "12C", + "ADPCMA_Ch6_EdH", "12D", + // FM (Channel 3-4) + "FM3_Op1_DT_MULT", "131", + "FM4_Op1_DT_MULT", "132", + "FM3_Op2_DT_MULT", "135", + "FM4_Op2_DT_MULT", "136", + "FM3_Op3_DT_MULT", "139", + "FM4_Op3_DT_MULT", "13A", + "FM3_Op4_DT_MULT", "13D", + "FM4_Op4_DT_MULT", "13E", + "FM3_Op1_TL", "141", + "FM4_Op1_TL", "142", + "FM3_Op2_TL", "145", + "FM4_Op2_TL", "146", + "FM3_Op3_TL", "149", + "FM4_Op3_TL", "14A", + "FM3_Op4_TL", "14D", + "FM4_Op4_TL", "14E", + "FM3_Op1_KS_AR", "151", + "FM4_Op1_KS_AR", "152", + "FM3_Op2_KS_AR", "155", + "FM4_Op2_KS_AR", "156", + "FM3_Op3_KS_AR", "159", + "FM4_Op3_KS_AR", "15A", + "FM3_Op4_KS_AR", "15D", + "FM4_Op4_KS_AR", "15E", + "FM3_Op1_AM_DR", "161", + "FM4_Op1_AM_DR", "162", + "FM3_Op2_AM_DR", "165", + "FM4_Op2_AM_DR", "166", + "FM3_Op3_AM_DR", "169", + "FM4_Op3_AM_DR", "16A", + "FM3_Op4_AM_DR", "16D", + "FM4_Op4_AM_DR", "16E", + "FM3_Op1_SR", "171", + "FM4_Op1_SR", "172", + "FM3_Op2_SR", "175", + "FM4_Op2_SR", "176", + "FM3_Op3_SR", "179", + "FM4_Op3_SR", "17A", + "FM3_Op4_SR", "17D", + "FM4_Op4_SR", "17E", + "FM3_Op1_SL_RR", "181", + "FM4_Op1_SL_RR", "182", + "FM3_Op2_SL_RR", "185", + "FM4_Op2_SL_RR", "186", + "FM3_Op3_SL_RR", "189", + "FM4_Op3_SL_RR", "18A", + "FM3_Op4_SL_RR", "18D", + "FM4_Op4_SL_RR", "18E", + "FM3_Op1_SSG_EG", "191", + "FM4_Op1_SSG_EG", "192", + "FM3_Op2_SSG_EG", "195", + "FM4_Op2_SSG_EG", "196", + "FM3_Op3_SSG_EG", "199", + "FM4_Op3_SSG_EG", "19A", + "FM3_Op4_SSG_EG", "19D", + "FM4_Op4_SSG_EG", "19E", + "FM3_FNum1", "1A1", + "FM4_FNum1", "1A2", + "FM3_FNum2", "1A5", + "FM4_FNum2", "1A6", + "FM3_FB_ALG", "1B1", + "FM4_FB_ALG", "1B2", + "FM3_Pan_LFO", "1B5", + "FM4_Pan_LFO", "1B6", + NULL +}; + +const char** DivPlatformYM2610::getRegisterSheet() { + return regCheatSheetYM2610; +} + const char* DivPlatformYM2610::getEffectName(unsigned char effect) { switch (effect) { case 0x10: diff --git a/src/engine/platform/ym2610.h b/src/engine/platform/ym2610.h index 65456c720..a0ee8b8d4 100644 --- a/src/engine/platform/ym2610.h +++ b/src/engine/platform/ym2610.h @@ -127,6 +127,7 @@ class DivPlatformYM2610: public DivDispatch { void notifyInsDeletion(void* ins); void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); + const char** getRegisterSheet(); const char* getEffectName(unsigned char effect); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); diff --git a/src/engine/platform/ym2610b.cpp b/src/engine/platform/ym2610b.cpp index 770454a8d..edf9184d5 100644 --- a/src/engine/platform/ym2610b.cpp +++ b/src/engine/platform/ym2610b.cpp @@ -32,6 +32,282 @@ static unsigned char konOffs[6]={ #define CHIP_DIVIDER 32 +const char* regCheatSheetYM2610B[]={ + // SSG + "SSG_FreqL_A", "000", + "SSG_FreqH_A", "001", + "SSG_FreqL_B", "002", + "SSG_FreqH_B", "003", + "SSG_FreqL_C", "004", + "SSG_FreqH_C", "005", + "SSG_FreqNoise", "006", + "SSG_Enable", "007", + "SSG_Volume_A", "008", + "SSG_Volume_B", "009", + "SSG_Volume_C", "00A", + "SSG_FreqL_Env", "00B", + "SSG_FreqH_Env", "00C", + "SSG_Control_Env", "00D", + // ADPCM-B + "ADPCMB_Control", "010", + "ADPCMB_L_R", "011", + "ADPCMB_StartL", "012", + "ADPCMB_StartH", "013", + "ADPCMB_EndL", "014", + "ADPCMB_EndH", "015", + "ADPCMB_FreqL", "019", + "ADPCMB_FreqH", "01A", + "ADPCMB_Volume", "01B", + "ADPCM_Flag", "01C", + // FM (Common) + "FM_Test", "021", + "FM_LFOFreq", "022", + "ClockA1", "024", + "ClockA2", "025", + "ClockB", "026", + "FM_Control", "027", + "FM_NoteCtl", "028", + // FM (Channel 1-3) + "FM1_Op1_DT_MULT", "030", + "FM2_Op1_DT_MULT", "031", + "FM3_Op1_DT_MULT", "032", + "FM1_Op2_DT_MULT", "034", + "FM2_Op2_DT_MULT", "035", + "FM3_Op2_DT_MULT", "036", + "FM1_Op3_DT_MULT", "038", + "FM2_Op3_DT_MULT", "039", + "FM3_Op3_DT_MULT", "03A", + "FM1_Op4_DT_MULT", "03C", + "FM2_Op4_DT_MULT", "03D", + "FM3_Op4_DT_MULT", "03E", + "FM1_Op1_TL", "040", + "FM2_Op1_TL", "041", + "FM3_Op1_TL", "042", + "FM1_Op2_TL", "044", + "FM2_Op2_TL", "045", + "FM3_Op2_TL", "046", + "FM1_Op3_TL", "048", + "FM2_Op3_TL", "049", + "FM3_Op3_TL", "04A", + "FM1_Op4_TL", "04C", + "FM2_Op4_TL", "04D", + "FM3_Op4_TL", "04E", + "FM1_Op1_KS_AR", "050", + "FM2_Op1_KS_AR", "051", + "FM3_Op1_KS_AR", "052", + "FM1_Op2_KS_AR", "054", + "FM2_Op2_KS_AR", "055", + "FM3_Op2_KS_AR", "056", + "FM1_Op3_KS_AR", "058", + "FM2_Op3_KS_AR", "059", + "FM3_Op3_KS_AR", "05A", + "FM1_Op4_KS_AR", "05C", + "FM2_Op4_KS_AR", "05D", + "FM3_Op4_KS_AR", "05E", + "FM1_Op1_AM_DR", "060", + "FM2_Op1_AM_DR", "061", + "FM3_Op1_AM_DR", "062", + "FM1_Op2_AM_DR", "064", + "FM2_Op2_AM_DR", "065", + "FM3_Op2_AM_DR", "066", + "FM1_Op3_AM_DR", "068", + "FM2_Op3_AM_DR", "069", + "FM3_Op3_AM_DR", "06A", + "FM1_Op4_AM_DR", "06C", + "FM2_Op4_AM_DR", "06D", + "FM3_Op4_AM_DR", "06E", + "FM1_Op1_SR", "070", + "FM2_Op1_SR", "071", + "FM3_Op1_SR", "072", + "FM1_Op2_SR", "074", + "FM2_Op2_SR", "075", + "FM3_Op2_SR", "076", + "FM1_Op3_SR", "078", + "FM2_Op3_SR", "079", + "FM3_Op3_SR", "07A", + "FM1_Op4_SR", "07C", + "FM2_Op4_SR", "07D", + "FM3_Op4_SR", "07E", + "FM1_Op1_SL_RR", "080", + "FM2_Op1_SL_RR", "081", + "FM3_Op1_SL_RR", "082", + "FM1_Op2_SL_RR", "084", + "FM2_Op2_SL_RR", "085", + "FM3_Op2_SL_RR", "086", + "FM1_Op3_SL_RR", "088", + "FM2_Op3_SL_RR", "089", + "FM3_Op3_SL_RR", "08A", + "FM1_Op4_SL_RR", "08C", + "FM2_Op4_SL_RR", "08D", + "FM3_Op4_SL_RR", "08E", + "FM1_Op1_SSG_EG", "090", + "FM2_Op1_SSG_EG", "091", + "FM3_Op1_SSG_EG", "092", + "FM1_Op2_SSG_EG", "094", + "FM2_Op2_SSG_EG", "095", + "FM3_Op2_SSG_EG", "096", + "FM1_Op3_SSG_EG", "098", + "FM2_Op3_SSG_EG", "099", + "FM3_Op3_SSG_EG", "09A", + "FM1_Op4_SSG_EG", "09C", + "FM2_Op4_SSG_EG", "09D", + "FM3_Op4_SSG_EG", "09E", + "FM1_FNum1", "0A0", + "FM2_FNum1", "0A1", + "FM3_(Op1)FNum1", "0A2", + "FM1_FNum2", "0A4", + "FM2_FNum2", "0A5", + "FM3_(Op1)FNum2", "0A6", + "FM3_Op2_FNum1", "0A8", + "FM3_Op3_FNum1", "0A9", + "FM3_Op4_FNum1", "0AA", + "FM3_Op2_FNum2", "0AC", + "FM3_Op3_FNum2", "0AD", + "FM3_Op4_FNum2", "0AE", + "FM1_FB_ALG", "0B0", + "FM2_FB_ALG", "0B1", + "FM3_FB_ALG", "0B2", + "FM1_Pan_LFO", "0B4", + "FM2_Pan_LFO", "0B5", + "FM3_Pan_LFO", "0B6", + // ADPCM-A + "ADPCMA_Control", "100", + "ADPCMA_MVol", "101", + "ADPCMA_Test", "102", + "ADPCMA_Ch1_Vol", "108", + "ADPCMA_Ch2_Vol", "109", + "ADPCMA_Ch3_Vol", "10A", + "ADPCMA_Ch4_Vol", "10B", + "ADPCMA_Ch5_Vol", "10C", + "ADPCMA_Ch6_Vol", "10D", + "ADPCMA_Ch1_StL", "110", + "ADPCMA_Ch2_StL", "111", + "ADPCMA_Ch3_StL", "112", + "ADPCMA_Ch4_StL", "113", + "ADPCMA_Ch5_StL", "114", + "ADPCMA_Ch6_StL", "115", + "ADPCMA_Ch1_StH", "118", + "ADPCMA_Ch2_StH", "119", + "ADPCMA_Ch3_StH", "11A", + "ADPCMA_Ch4_StH", "11B", + "ADPCMA_Ch5_StH", "11C", + "ADPCMA_Ch6_StH", "11D", + "ADPCMA_Ch1_EdL", "120", + "ADPCMA_Ch2_EdL", "121", + "ADPCMA_Ch3_EdL", "122", + "ADPCMA_Ch4_EdL", "123", + "ADPCMA_Ch5_EdL", "124", + "ADPCMA_Ch6_EdL", "125", + "ADPCMA_Ch1_EdH", "128", + "ADPCMA_Ch2_EdH", "129", + "ADPCMA_Ch3_EdH", "12A", + "ADPCMA_Ch4_EdH", "12B", + "ADPCMA_Ch5_EdH", "12C", + "ADPCMA_Ch6_EdH", "12D", + // FM (Channel 4-6) + "FM4_Op1_DT_MULT", "130", + "FM5_Op1_DT_MULT", "131", + "FM6_Op1_DT_MULT", "132", + "FM4_Op2_DT_MULT", "134", + "FM5_Op2_DT_MULT", "135", + "FM6_Op2_DT_MULT", "136", + "FM4_Op3_DT_MULT", "138", + "FM5_Op3_DT_MULT", "139", + "FM6_Op3_DT_MULT", "13A", + "FM4_Op4_DT_MULT", "13C", + "FM5_Op4_DT_MULT", "13D", + "FM6_Op4_DT_MULT", "13E", + "FM4_Op1_TL", "140", + "FM5_Op1_TL", "141", + "FM6_Op1_TL", "142", + "FM4_Op2_TL", "144", + "FM5_Op2_TL", "145", + "FM6_Op2_TL", "146", + "FM4_Op3_TL", "148", + "FM5_Op3_TL", "149", + "FM6_Op3_TL", "14A", + "FM4_Op4_TL", "14C", + "FM5_Op4_TL", "14D", + "FM6_Op4_TL", "14E", + "FM4_Op1_KS_AR", "150", + "FM5_Op1_KS_AR", "151", + "FM6_Op1_KS_AR", "152", + "FM4_Op2_KS_AR", "154", + "FM5_Op2_KS_AR", "155", + "FM6_Op2_KS_AR", "156", + "FM4_Op3_KS_AR", "158", + "FM5_Op3_KS_AR", "159", + "FM6_Op3_KS_AR", "15A", + "FM4_Op4_KS_AR", "15C", + "FM5_Op4_KS_AR", "15D", + "FM6_Op4_KS_AR", "15E", + "FM4_Op1_AM_DR", "160", + "FM5_Op1_AM_DR", "161", + "FM6_Op1_AM_DR", "162", + "FM4_Op2_AM_DR", "164", + "FM5_Op2_AM_DR", "165", + "FM6_Op2_AM_DR", "166", + "FM4_Op3_AM_DR", "168", + "FM5_Op3_AM_DR", "169", + "FM6_Op3_AM_DR", "16A", + "FM4_Op4_AM_DR", "16C", + "FM5_Op4_AM_DR", "16D", + "FM6_Op4_AM_DR", "16E", + "FM4_Op1_SR", "170", + "FM5_Op1_SR", "171", + "FM6_Op1_SR", "172", + "FM4_Op2_SR", "174", + "FM5_Op2_SR", "175", + "FM6_Op2_SR", "176", + "FM4_Op3_SR", "178", + "FM5_Op3_SR", "179", + "FM6_Op3_SR", "17A", + "FM4_Op4_SR", "17C", + "FM5_Op4_SR", "17D", + "FM6_Op4_SR", "17E", + "FM4_Op1_SL_RR", "180", + "FM5_Op1_SL_RR", "181", + "FM6_Op1_SL_RR", "182", + "FM4_Op2_SL_RR", "184", + "FM5_Op2_SL_RR", "185", + "FM6_Op2_SL_RR", "186", + "FM4_Op3_SL_RR", "188", + "FM5_Op3_SL_RR", "189", + "FM6_Op3_SL_RR", "18A", + "FM4_Op4_SL_RR", "18C", + "FM5_Op4_SL_RR", "18D", + "FM6_Op4_SL_RR", "18E", + "FM4_Op1_SSG_EG", "190", + "FM5_Op1_SSG_EG", "191", + "FM6_Op1_SSG_EG", "192", + "FM4_Op2_SSG_EG", "194", + "FM5_Op2_SSG_EG", "195", + "FM6_Op2_SSG_EG", "196", + "FM4_Op3_SSG_EG", "198", + "FM5_Op3_SSG_EG", "199", + "FM6_Op3_SSG_EG", "19A", + "FM4_Op4_SSG_EG", "19C", + "FM5_Op4_SSG_EG", "19D", + "FM6_Op4_SSG_EG", "19E", + "FM4_FNum1", "1A0", + "FM5_FNum1", "1A1", + "FM6_FNum1", "1A2", + "FM4_FNum2", "1A4", + "FM5_FNum2", "1A5", + "FM6_FNum2", "1A6", + "FM4_FB_ALG", "1B0", + "FM5_FB_ALG", "1B1", + "FM6_FB_ALG", "1B2", + "FM4_Pan_LFO", "1B4", + "FM5_Pan_LFO", "1B5", + "FM6_Pan_LFO", "1B6", + NULL +}; + +const char** DivPlatformYM2610B::getRegisterSheet() { + return regCheatSheetYM2610B; +} + const char* DivPlatformYM2610B::getEffectName(unsigned char effect) { switch (effect) { case 0x10: diff --git a/src/engine/platform/ym2610b.h b/src/engine/platform/ym2610b.h index 7ca766c22..ca43e36b4 100644 --- a/src/engine/platform/ym2610b.h +++ b/src/engine/platform/ym2610b.h @@ -120,6 +120,7 @@ class DivPlatformYM2610B: public DivDispatch { void notifyInsDeletion(void* ins); void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); + const char** getRegisterSheet(); const char* getEffectName(unsigned char effect); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); From ddf341dfc095479b782ce12baa4f94e4322c21a8 Mon Sep 17 00:00:00 2001 From: cam900 Date: Tue, 1 Mar 2022 01:11:27 +0900 Subject: [PATCH 012/637] Add support YM2610* ADPCM-B Repeat flag, Add YM2610 ADPCM notes in GUI --- src/engine/platform/ym2610.cpp | 4 ++-- src/engine/platform/ym2610b.cpp | 4 ++-- src/gui/gui.cpp | 11 +++++++++-- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/src/engine/platform/ym2610.cpp b/src/engine/platform/ym2610.cpp index a6f29e4b8..dd2447da4 100644 --- a/src/engine/platform/ym2610.cpp +++ b/src/engine/platform/ym2610.cpp @@ -698,7 +698,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) { immWrite(0x14,(end>>8)&0xff); immWrite(0x15,end>>16); immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); - immWrite(0x10,0x80); // start + immWrite(0x10,(s->loopStart>=0)?0x90:0x80); // start/repeat if (c.value!=DIV_NOTE_NULL) { chan[c.chan].note=c.value; chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note); @@ -724,7 +724,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) { immWrite(0x14,(end>>8)&0xff); immWrite(0x15,end>>16); immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); - immWrite(0x10,0x80); // start + immWrite(0x10,(s->loopStart>=0)?0x90:0x80); // start/repeat chan[c.chan].baseFreq=(((unsigned int)s->rate)<<16)/(chipClock/144); chan[c.chan].freqChanged=true; } diff --git a/src/engine/platform/ym2610b.cpp b/src/engine/platform/ym2610b.cpp index edf9184d5..27bb2afbb 100644 --- a/src/engine/platform/ym2610b.cpp +++ b/src/engine/platform/ym2610b.cpp @@ -761,7 +761,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { immWrite(0x14,(end>>8)&0xff); immWrite(0x15,end>>16); immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); - immWrite(0x10,0x80); // start + immWrite(0x10,(s->loopStart>=0)?0x90:0x80); // start/repeat if (c.value!=DIV_NOTE_NULL) { chan[c.chan].note=c.value; chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note); @@ -787,7 +787,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { immWrite(0x14,(end>>8)&0xff); immWrite(0x15,end>>16); immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); - immWrite(0x10,0x80); // start + immWrite(0x10,(s->loopStart>=0)?0x90:0x80); // start/repeat chan[c.chan].baseFreq=(((unsigned int)s->rate)<<16)/(chipClock/144); chan[c.chan].freqChanged=true; } diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 265624684..034afb4ac 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1309,18 +1309,25 @@ void FurnaceGUI::drawSampleEdit() { ImGui::Text("notes:"); if (sample->loopStart>=0) { considerations=true; - ImGui::Text("- sample won't loop on Neo Geo ADPCM"); + ImGui::Text("- sample won't loop on Neo Geo ADPCM-A"); if (sample->loopStart&1) { ImGui::Text("- sample loop start will be aligned to the nearest even sample on Amiga"); } + if (sample->loopStart>0) { + ImGui::Text("- sample loop start will be ignored on Neo Geo ADPCM-B"); + } } if (sample->samples&1) { considerations=true; ImGui::Text("- sample length will be aligned to the nearest even sample on Amiga"); } + if (sample->samples&511) { + considerations=true; + ImGui::Text("- sample length will be aligned to 512 sample on Neo Geo ADPCM"); + } if (sample->samples>65535) { considerations=true; - ImGui::Text("- maximum sample length on Sega PCM is 65536 samples"); + ImGui::Text("- maximum sample length on Sega PCM and QSound is 65536 samples"); } if (sample->samples>2097151) { considerations=true; From 4e5b398994665f91d002557b673d8dafd6b8baa2 Mon Sep 17 00:00:00 2001 From: cam900 Date: Tue, 1 Mar 2022 02:11:02 +0900 Subject: [PATCH 013/637] Fix notes --- src/gui/gui.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 034afb4ac..ec566676b 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1323,7 +1323,7 @@ void FurnaceGUI::drawSampleEdit() { } if (sample->samples&511) { considerations=true; - ImGui::Text("- sample length will be aligned to 512 sample on Neo Geo ADPCM"); + ImGui::Text("- sample length will be aligned and padded to 512 sample units on Neo Geo ADPCM."); } if (sample->samples>65535) { considerations=true; From de8c79e3062ba05abf2a4f88f455969a256ca4f9 Mon Sep 17 00:00:00 2001 From: cam900 Date: Tue, 1 Mar 2022 02:50:26 +0900 Subject: [PATCH 014/637] Further informations --- papers/doc/7-systems/ym2610.md | 2 +- papers/doc/7-systems/ym2610b.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/papers/doc/7-systems/ym2610.md b/papers/doc/7-systems/ym2610.md index dbe8b6a54..7eb66eb33 100644 --- a/papers/doc/7-systems/ym2610.md +++ b/papers/doc/7-systems/ym2610.md @@ -2,7 +2,7 @@ originally an arcade board, but SNK shortly adapted it to a rather expensive video game console with the world's biggest cartridges because some people liked the system so much they wanted a home version of it. -its soundchip is a 3-in-1: FM, YM2149 (AY-3-8910 clone) and ADPCM in a single package! +its soundchip is a 3-in-1: FM, YM2149 (AY-3-8910 clone) and 2 different format ADPCM in a single package! # effects diff --git a/papers/doc/7-systems/ym2610b.md b/papers/doc/7-systems/ym2610b.md index da4f542d4..2b2e5feaf 100644 --- a/papers/doc/7-systems/ym2610b.md +++ b/papers/doc/7-systems/ym2610b.md @@ -1,6 +1,6 @@ # Taito Arcade/Yamaha YM2610B -YM2610B is basically YM2610 with 2 extra FM channels used at some 90s Taito Arcade hardwares. +YM2610B is basically YM2610 with 2 extra FM channels used at some 90s Taito Arcade hardwares, it's backward compatible with non-B chip. # effects From da55305c132c213c04bd8a30a4b1ec9f3af0169b Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 28 Feb 2022 16:33:01 -0500 Subject: [PATCH 015/637] prepare for OPL/YMU FM macros --- src/engine/instrument.h | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/engine/instrument.h b/src/engine/instrument.h index 366bd87d6..d5dbc8a63 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -161,31 +161,55 @@ struct DivInstrumentSTD { unsigned char dtMacro[256]; unsigned char d2rMacro[256]; unsigned char ssgMacro[256]; + unsigned char damMacro[256]; + unsigned char dvbMacro[256]; + unsigned char egtMacro[256]; + unsigned char kslMacro[256]; + unsigned char susMacro[256]; + unsigned char vibMacro[256]; + unsigned char wsMacro[256]; + unsigned char ksrMacro[256]; bool amMacroOpen, arMacroOpen, drMacroOpen, multMacroOpen; bool rrMacroOpen, slMacroOpen, tlMacroOpen, dt2MacroOpen; bool rsMacroOpen, dtMacroOpen, d2rMacroOpen, ssgMacroOpen; + bool damMacroOpen, dvbMacroOpen, egtMacroOpen, kslMacroOpen; + bool susMacroOpen, vibMacroOpen, wsMacroOpen, ksrMacroOpen; unsigned char amMacroLen, arMacroLen, drMacroLen, multMacroLen; unsigned char rrMacroLen, slMacroLen, tlMacroLen, dt2MacroLen; unsigned char rsMacroLen, dtMacroLen, d2rMacroLen, ssgMacroLen; + unsigned char damMacroLen, dvbMacroLen, egtMacroLen, kslMacroLen; + unsigned char susMacroLen, vibMacroLen, wsMacroLen, ksrMacroLen; signed char amMacroLoop, arMacroLoop, drMacroLoop, multMacroLoop; signed char rrMacroLoop, slMacroLoop, tlMacroLoop, dt2MacroLoop; signed char rsMacroLoop, dtMacroLoop, d2rMacroLoop, ssgMacroLoop; + signed char damMacroLoop, dvbMacroLoop, egtMacroLoop, kslMacroLoop; + signed char susMacroLoop, vibMacroLoop, wsMacroLoop, ksrMacroLoop; signed char amMacroRel, arMacroRel, drMacroRel, multMacroRel; signed char rrMacroRel, slMacroRel, tlMacroRel, dt2MacroRel; signed char rsMacroRel, dtMacroRel, d2rMacroRel, ssgMacroRel; + signed char damMacroRel, dvbMacroRel, egtMacroRel, kslMacroRel; + signed char susMacroRel, vibMacroRel, wsMacroRel, ksrMacroRel; OpMacro(): amMacroOpen(false), arMacroOpen(false), drMacroOpen(false), multMacroOpen(false), rrMacroOpen(false), slMacroOpen(false), tlMacroOpen(true), dt2MacroOpen(false), rsMacroOpen(false), dtMacroOpen(false), d2rMacroOpen(false), ssgMacroOpen(false), + damMacroOpen(false), dvbMacroOpen(false), egtMacroOpen(false), kslMacroOpen(false), + susMacroOpen(false), vibMacroOpen(false), wsMacroOpen(false), ksrMacroOpen(false), amMacroLen(0), arMacroLen(0), drMacroLen(0), multMacroLen(0), rrMacroLen(0), slMacroLen(0), tlMacroLen(0), dt2MacroLen(0), rsMacroLen(0), dtMacroLen(0), d2rMacroLen(0), ssgMacroLen(0), + damMacroLen(0), dvbMacroLen(0), egtMacroLen(0), kslMacroLen(0), + susMacroLen(0), vibMacroLen(0), wsMacroLen(0), ksrMacroLen(0), amMacroLoop(-1), arMacroLoop(-1), drMacroLoop(-1), multMacroLoop(-1), rrMacroLoop(-1), slMacroLoop(-1), tlMacroLoop(-1), dt2MacroLoop(-1), rsMacroLoop(-1), dtMacroLoop(-1), d2rMacroLoop(-1), ssgMacroLoop(-1), + damMacroLoop(-1), dvbMacroLoop(-1), egtMacroLoop(-1), kslMacroLoop(-1), + susMacroLoop(-1), vibMacroLoop(-1), wsMacroLoop(-1), ksrMacroLoop(-1), amMacroRel(-1), arMacroRel(-1), drMacroRel(-1), multMacroRel(-1), rrMacroRel(-1), slMacroRel(-1), tlMacroRel(-1), dt2MacroRel(-1), - rsMacroRel(-1), dtMacroRel(-1), d2rMacroRel(-1), ssgMacroRel(-1) { + rsMacroRel(-1), dtMacroRel(-1), d2rMacroRel(-1), ssgMacroRel(-1), + damMacroRel(-1), dvbMacroRel(-1), egtMacroRel(-1), kslMacroRel(-1), + susMacroRel(-1), vibMacroRel(-1), wsMacroRel(-1), ksrMacroRel(-1) { memset(amMacro,0,256); memset(arMacro,0,256); memset(drMacro,0,256); @@ -198,6 +222,14 @@ struct DivInstrumentSTD { memset(dtMacro,0,256); memset(d2rMacro,0,256); memset(ssgMacro,0,256); + memset(damMacro,0,256); + memset(dvbMacro,0,256); + memset(egtMacro,0,256); + memset(kslMacro,0,256); + memset(susMacro,0,256); + memset(vibMacro,0,256); + memset(wsMacro,0,256); + memset(ksrMacro,0,256); } } opMacros[4]; DivInstrumentSTD(): From bd9289cfdd5269f84a6aecdea441ea3e89f2e232 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 28 Feb 2022 18:16:05 -0500 Subject: [PATCH 016/637] store OP macros todo --- .../imgui_patched/imgui_impl_sdlrenderer.cpp | 2 + papers/format.md | 42 ++++ src/engine/engine.h | 4 +- src/engine/instrument.cpp | 124 +++++++++++ src/gui/gui.h | 2 +- src/gui/insEdit.cpp | 199 ++++++++++-------- 6 files changed, 287 insertions(+), 86 deletions(-) diff --git a/extern/imgui_patched/imgui_impl_sdlrenderer.cpp b/extern/imgui_patched/imgui_impl_sdlrenderer.cpp index ae034179c..89c87f62b 100644 --- a/extern/imgui_patched/imgui_impl_sdlrenderer.cpp +++ b/extern/imgui_patched/imgui_impl_sdlrenderer.cpp @@ -26,6 +26,7 @@ #include "imgui.h" #include "imgui_impl_sdlrenderer.h" +#include #if defined(_MSC_VER) && _MSC_VER <= 1500 // MSVC 2008 or earlier #include // intptr_t #else @@ -184,6 +185,7 @@ void ImGui_ImplSDLRenderer_RenderDrawData(ImDrawData* draw_data) // Bind texture, Draw SDL_Texture* tex = (SDL_Texture*)pcmd->GetTexID(); + SDL_SetTextureScaleMode(tex, SDL_ScaleModeBest); // ??? SDL_RenderGeometryRaw(bd->SDLRenderer, tex, xy, (int)sizeof(ImDrawVert), color, (int)sizeof(ImDrawVert), diff --git a/papers/format.md b/papers/format.md index 882efa7bd..1ca356b72 100644 --- a/papers/format.md +++ b/papers/format.md @@ -426,6 +426,48 @@ size | description 4 | DT macro release 4 | D2R macro release 4 | SSG-EG macro release + --- | **extended op macro headers** × 4 (>=61) + 4 | DAM macro length + 4 | DVB macro length + 4 | EGT macro length + 4 | KSL macro length + 4 | SUS macro length + 4 | VIB macro length + 4 | WS macro length + 4 | KSR macro length + 4 | DAM macro loop + 4 | DVB macro loop + 4 | EGT macro loop + 4 | KSL macro loop + 4 | SUS macro loop + 4 | VIB macro loop + 4 | WS macro loop + 4 | KSR macro loop + 4 | DAM macro release + 4 | DVB macro release + 4 | EGT macro release + 4 | KSL macro release + 4 | SUS macro release + 4 | VIB macro release + 4 | WS macro release + 4 | KSR macro release + 1 | DAM macro open + 1 | DVB macro open + 1 | EGT macro open + 1 | KSL macro open + 1 | SUS macro open + 1 | VIB macro open + 1 | WS macro open + 1 | KSR macro open + --- | **extended op macros** × 4 (>=61) + 1?? | DAM macro + 1?? | DVB macro + 1?? | EGT macro + 1?? | KSL macro + 1?? | SUS macro + 1?? | VIB macro + 1?? | WS macro + 1?? | KSR macro ``` # wavetable diff --git a/src/engine/engine.h b/src/engine/engine.h index f0ef1833a..2f060f14f 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -37,8 +37,8 @@ warnings+=(String("\n")+x); \ } -#define DIV_VERSION "dev60" -#define DIV_ENGINE_VERSION 60 +#define DIV_VERSION "dev61" +#define DIV_ENGINE_VERSION 61 enum DivStatusView { DIV_STATUS_NOTHING=0, diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index fa5564c93..d5933981d 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -302,6 +302,75 @@ void DivInstrument::putInsData(SafeWriter* w) { w->writeI(op.d2rMacroRel); w->writeI(op.ssgMacroRel); } + + // extended op macros + for (int i=0; i<4; i++) { + DivInstrumentSTD::OpMacro& op=std.opMacros[i]; + + w->writeI(op.damMacroLen); + w->writeI(op.dvbMacroLen); + w->writeI(op.egtMacroLen); + w->writeI(op.kslMacroLen); + w->writeI(op.susMacroLen); + w->writeI(op.vibMacroLen); + w->writeI(op.wsMacroLen); + w->writeI(op.ksrMacroLen); + + w->writeI(op.damMacroLoop); + w->writeI(op.dvbMacroLoop); + w->writeI(op.egtMacroLoop); + w->writeI(op.kslMacroLoop); + w->writeI(op.susMacroLoop); + w->writeI(op.vibMacroLoop); + w->writeI(op.wsMacroLoop); + w->writeI(op.ksrMacroLoop); + + w->writeI(op.damMacroRel); + w->writeI(op.dvbMacroRel); + w->writeI(op.egtMacroRel); + w->writeI(op.kslMacroRel); + w->writeI(op.susMacroRel); + w->writeI(op.vibMacroRel); + w->writeI(op.wsMacroRel); + w->writeI(op.ksrMacroRel); + + w->writeC(op.damMacroOpen); + w->writeC(op.dvbMacroOpen); + w->writeC(op.egtMacroOpen); + w->writeC(op.kslMacroOpen); + w->writeC(op.susMacroOpen); + w->writeC(op.vibMacroOpen); + w->writeC(op.wsMacroOpen); + w->writeC(op.ksrMacroOpen); + } + + for (int i=0; i<4; i++) { + DivInstrumentSTD::OpMacro& op=std.opMacros[i]; + for (int j=0; jwriteC(op.damMacro[j]); + } + for (int j=0; jwriteC(op.dvbMacro[j]); + } + for (int j=0; jwriteC(op.egtMacro[j]); + } + for (int j=0; jwriteC(op.kslMacro[j]); + } + for (int j=0; jwriteC(op.susMacro[j]); + } + for (int j=0; jwriteC(op.vibMacro[j]); + } + for (int j=0; jwriteC(op.wsMacro[j]); + } + for (int j=0; jwriteC(op.ksrMacro[j]); + } + } } DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) { @@ -570,6 +639,61 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) { } } + // extended op macros + if (version>=61) { + for (int i=0; i<4; i++) { + DivInstrumentSTD::OpMacro& op=std.opMacros[i]; + + op.damMacroLen=reader.readI(); + op.dvbMacroLen=reader.readI(); + op.egtMacroLen=reader.readI(); + op.kslMacroLen=reader.readI(); + op.susMacroLen=reader.readI(); + op.vibMacroLen=reader.readI(); + op.wsMacroLen=reader.readI(); + op.ksrMacroLen=reader.readI(); + + op.damMacroLoop=reader.readI(); + op.dvbMacroLoop=reader.readI(); + op.egtMacroLoop=reader.readI(); + op.kslMacroLoop=reader.readI(); + op.susMacroLoop=reader.readI(); + op.vibMacroLoop=reader.readI(); + op.wsMacroLoop=reader.readI(); + op.ksrMacroLoop=reader.readI(); + + op.damMacroRel=reader.readI(); + op.dvbMacroRel=reader.readI(); + op.egtMacroRel=reader.readI(); + op.kslMacroRel=reader.readI(); + op.susMacroRel=reader.readI(); + op.vibMacroRel=reader.readI(); + op.wsMacroRel=reader.readI(); + op.ksrMacroRel=reader.readI(); + + op.damMacroOpen=reader.readC(); + op.dvbMacroOpen=reader.readC(); + op.egtMacroOpen=reader.readC(); + op.kslMacroOpen=reader.readC(); + op.susMacroOpen=reader.readC(); + op.vibMacroOpen=reader.readC(); + op.wsMacroOpen=reader.readC(); + op.ksrMacroOpen=reader.readC(); + } + + for (int i=0; i<4; i++) { + DivInstrumentSTD::OpMacro& op=std.opMacros[i]; + reader.read(op.damMacro,op.damMacroLen); + reader.read(op.dvbMacro,op.dvbMacroLen); + reader.read(op.egtMacro,op.egtMacroLen); + reader.read(op.kslMacro,op.kslMacroLen); + reader.read(op.susMacro,op.susMacroLen); + reader.read(op.vibMacro,op.vibMacroLen); + reader.read(op.wsMacro,op.wsMacroLen); + reader.read(op.ksrMacro,op.ksrMacroLen); + } + } + return DIV_DATA_SUCCESS; } diff --git a/src/gui/gui.h b/src/gui/gui.h index 6eb591754..4cae110dc 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -626,7 +626,7 @@ class FurnaceGUI { int lastIns[DIV_MAX_CHANS]; void drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, const ImVec2& size); - void drawFMEnv(unsigned char tl, unsigned char ar, unsigned char dr, unsigned char d2r, unsigned char rr, unsigned char sl, const ImVec2& size); + void drawFMEnv(unsigned char tl, unsigned char ar, unsigned char dr, unsigned char d2r, unsigned char rr, unsigned char sl, float maxTl, float maxArDr, const ImVec2& size); void updateWindowTitle(); void prepareLayout(); diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 4790d61c1..a8558bb07 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -148,6 +148,10 @@ const char* mikeyFeedbackBits[11] = { "0", "1", "2", "3", "4", "5", "7", "10", "11", "int", NULL }; +const char* oneBit[2]={ + "on", NULL +}; + const int orderedOps[4]={ 0, 2, 1, 3 }; @@ -183,6 +187,13 @@ String macroLFOWaves(int id, float val) { return "???"; } +void addAALine(ImDrawList* dl, const ImVec2& p1, const ImVec2& p2, const ImU32 color, float thickness=1.0f) { + ImVec2 pt[2]; + pt[0]=p1; + pt[1]=p2; + dl->AddPolyline(pt,2,color,ImDrawFlags_None,thickness); +} + void FurnaceGUI::drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, const ImVec2& size) { ImDrawList* dl=ImGui::GetWindowDrawList(); ImGuiWindow* window=ImGui::GetCurrentWindow(); @@ -199,7 +210,6 @@ void FurnaceGUI::drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, cons ImGui::ItemSize(size,style.FramePadding.y); if (ImGui::ItemAdd(rect,ImGui::GetID("alg"))) { ImGui::RenderFrame(rect.Min,rect.Max,ImGui::GetColorU32(ImGuiCol_FrameBg),true,style.FrameRounding); - //ImReallyTiredOfThisGarbage(); const float circleRadius=6.0f*dpiScale+1.0f; switch (algType) { case FM_ALGS_4OP: @@ -211,11 +221,11 @@ void FurnaceGUI::drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, cons ImVec2 pos4=ImLerp(rect.Min,rect.Max,ImVec2(0.8,0.5)); dl->AddCircleFilled(pos1,4.0f*dpiScale+1.0f,color); dl->AddCircle(pos1,6.0f*dpiScale+1.0f,color); - dl->AddLine(pos1,pos2,colorL); + addAALine(dl,pos1,pos2,colorL); dl->AddCircleFilled(pos2,4.0f*dpiScale+1.0f,color); - dl->AddLine(pos2,pos3,colorL); + addAALine(dl,pos2,pos3,colorL); dl->AddCircleFilled(pos3,4.0f*dpiScale+1.0f,color); - dl->AddLine(pos3,pos4,colorL); + addAALine(dl,pos3,pos4,colorL); dl->AddCircleFilled(pos4,4.0f*dpiScale+1.0f,color); pos1.x-=ImGui::CalcTextSize("1").x*0.5; @@ -239,11 +249,11 @@ void FurnaceGUI::drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, cons ImVec2 pos4=ImLerp(rect.Min,rect.Max,ImVec2(0.75,0.5)); dl->AddCircleFilled(pos1,4.0f*dpiScale+1.0f,color); dl->AddCircle(pos1,6.0f*dpiScale+1.0f,color); - dl->AddLine(pos1,pos3,colorL); + addAALine(dl,pos1,pos3,colorL); dl->AddCircleFilled(pos2,4.0f*dpiScale+1.0f,color); - dl->AddLine(pos2,pos3,colorL); + addAALine(dl,pos2,pos3,colorL); dl->AddCircleFilled(pos3,4.0f*dpiScale+1.0f,color); - dl->AddLine(pos3,pos4,colorL); + addAALine(dl,pos3,pos4,colorL); dl->AddCircleFilled(pos4,4.0f*dpiScale+1.0f,color); pos2.x-=ImGui::CalcTextSize("2").x+circleRadius+3.0*dpiScale; @@ -267,11 +277,11 @@ void FurnaceGUI::drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, cons ImVec2 pos4=ImLerp(rect.Min,rect.Max,ImVec2(0.75,0.5)); dl->AddCircleFilled(pos1,4.0f*dpiScale+1.0f,color); dl->AddCircle(pos1,6.0f*dpiScale+1.0f,color); - dl->AddLine(pos1,pos4,colorL); + addAALine(dl,pos1,pos4,colorL); dl->AddCircleFilled(pos2,4.0f*dpiScale+1.0f,color); - dl->AddLine(pos2,pos3,colorL); + addAALine(dl,pos2,pos3,colorL); dl->AddCircleFilled(pos3,4.0f*dpiScale+1.0f,color); - dl->AddLine(pos3,pos4,colorL); + addAALine(dl,pos3,pos4,colorL); dl->AddCircleFilled(pos4,4.0f*dpiScale+1.0f,color); pos1.x-=ImGui::CalcTextSize("2").x+circleRadius+3.0*dpiScale; @@ -295,11 +305,11 @@ void FurnaceGUI::drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, cons ImVec2 pos4=ImLerp(rect.Min,rect.Max,ImVec2(0.75,0.5)); dl->AddCircleFilled(pos1,4.0f*dpiScale+1.0f,color); dl->AddCircle(pos1,6.0f*dpiScale+1.0f,color); - dl->AddLine(pos1,pos2,colorL); + addAALine(dl,pos1,pos2,colorL); dl->AddCircleFilled(pos2,4.0f*dpiScale+1.0f,color); - dl->AddLine(pos2,pos4,colorL); + addAALine(dl,pos2,pos4,colorL); dl->AddCircleFilled(pos3,4.0f*dpiScale+1.0f,color); - dl->AddLine(pos3,pos4,colorL); + addAALine(dl,pos3,pos4,colorL); dl->AddCircleFilled(pos4,4.0f*dpiScale+1.0f,color); pos1.x-=ImGui::CalcTextSize("2").x+circleRadius+3.0*dpiScale; @@ -324,13 +334,13 @@ void FurnaceGUI::drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, cons ImVec2 pos5=ImLerp(rect.Min,rect.Max,ImVec2(0.75,0.5)); dl->AddCircleFilled(pos1,4.0f*dpiScale+1.0f,color); dl->AddCircle(pos1,6.0f*dpiScale+1.0f,color); - dl->AddLine(pos1,pos2,colorL); + addAALine(dl,pos1,pos2,colorL); dl->AddCircleFilled(pos2,4.0f*dpiScale+1.0f,color); dl->AddCircleFilled(pos3,4.0f*dpiScale+1.0f,color); - dl->AddLine(pos3,pos4,colorL); + addAALine(dl,pos3,pos4,colorL); dl->AddCircleFilled(pos4,4.0f*dpiScale+1.0f,color); - dl->AddLine(pos2,pos5,colorL); - dl->AddLine(pos4,pos5,colorL); + addAALine(dl,pos2,pos5,colorL); + addAALine(dl,pos4,pos5,colorL); pos1.x-=ImGui::CalcTextSize("2").x+circleRadius+3.0*dpiScale; pos2.x-=ImGui::CalcTextSize("2").x+circleRadius+3.0*dpiScale; @@ -354,15 +364,15 @@ void FurnaceGUI::drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, cons ImVec2 pos5=ImLerp(rect.Min,rect.Max,ImVec2(0.75,0.5)); dl->AddCircleFilled(pos1,4.0f*dpiScale+1.0f,color); dl->AddCircle(pos1,6.0f*dpiScale+1.0f,color); - dl->AddLine(pos1,pos2,colorL); - dl->AddLine(pos1,pos3,colorL); - dl->AddLine(pos1,pos4,colorL); + addAALine(dl,pos1,pos2,colorL); + addAALine(dl,pos1,pos3,colorL); + addAALine(dl,pos1,pos4,colorL); dl->AddCircleFilled(pos2,4.0f*dpiScale+1.0f,color); dl->AddCircleFilled(pos3,4.0f*dpiScale+1.0f,color); dl->AddCircleFilled(pos4,4.0f*dpiScale+1.0f,color); - dl->AddLine(pos2,pos5,colorL); - dl->AddLine(pos3,pos5,colorL); - dl->AddLine(pos4,pos5,colorL); + addAALine(dl,pos2,pos5,colorL); + addAALine(dl,pos3,pos5,colorL); + addAALine(dl,pos4,pos5,colorL); pos1.x-=ImGui::CalcTextSize("2").x+circleRadius+3.0*dpiScale; pos2.x-=ImGui::CalcTextSize("2").x+circleRadius+3.0*dpiScale; @@ -386,13 +396,13 @@ void FurnaceGUI::drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, cons ImVec2 pos5=ImLerp(rect.Min,rect.Max,ImVec2(0.75,0.5)); dl->AddCircleFilled(pos1,4.0f*dpiScale+1.0f,color); dl->AddCircle(pos1,6.0f*dpiScale+1.0f,color); - dl->AddLine(pos1,pos2,colorL); + addAALine(dl,pos1,pos2,colorL); dl->AddCircleFilled(pos2,4.0f*dpiScale+1.0f,color); dl->AddCircleFilled(pos3,4.0f*dpiScale+1.0f,color); dl->AddCircleFilled(pos4,4.0f*dpiScale+1.0f,color); - dl->AddLine(pos2,pos5,colorL); - dl->AddLine(pos3,pos5,colorL); - dl->AddLine(pos4,pos5,colorL); + addAALine(dl,pos2,pos5,colorL); + addAALine(dl,pos3,pos5,colorL); + addAALine(dl,pos4,pos5,colorL); pos1.x-=ImGui::CalcTextSize("2").x+circleRadius+3.0*dpiScale; pos2.x-=ImGui::CalcTextSize("2").x+circleRadius+3.0*dpiScale; @@ -419,10 +429,10 @@ void FurnaceGUI::drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, cons dl->AddCircleFilled(pos2,4.0f*dpiScale+1.0f,color); dl->AddCircleFilled(pos3,4.0f*dpiScale+1.0f,color); dl->AddCircleFilled(pos4,4.0f*dpiScale+1.0f,color); - dl->AddLine(pos1,pos5,colorL); - dl->AddLine(pos2,pos5,colorL); - dl->AddLine(pos3,pos5,colorL); - dl->AddLine(pos4,pos5,colorL); + addAALine(dl,pos1,pos5,colorL); + addAALine(dl,pos2,pos5,colorL); + addAALine(dl,pos3,pos5,colorL); + addAALine(dl,pos4,pos5,colorL); pos1.x-=ImGui::CalcTextSize("2").x+circleRadius+3.0*dpiScale; pos2.x-=ImGui::CalcTextSize("2").x+circleRadius+3.0*dpiScale; @@ -447,7 +457,7 @@ void FurnaceGUI::drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, cons ImVec2 pos2=ImLerp(rect.Min,rect.Max,ImVec2(0.67,0.5)); dl->AddCircleFilled(pos1,4.0f*dpiScale+1.0f,color); dl->AddCircle(pos1,6.0f*dpiScale+1.0f,color); - dl->AddLine(pos1,pos2,colorL); + addAALine(dl,pos1,pos2,colorL); dl->AddCircleFilled(pos2,4.0f*dpiScale+1.0f,color); pos1.x-=ImGui::CalcTextSize("2").x+circleRadius+3.0*dpiScale; @@ -481,7 +491,7 @@ void FurnaceGUI::drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, cons } } -void FurnaceGUI::drawFMEnv(unsigned char tl, unsigned char ar, unsigned char dr, unsigned char d2r, unsigned char rr, unsigned char sl, const ImVec2& size) { +void FurnaceGUI::drawFMEnv(unsigned char tl, unsigned char ar, unsigned char dr, unsigned char d2r, unsigned char rr, unsigned char sl, float maxTl, float maxArDr, const ImVec2& size) { ImDrawList* dl=ImGui::GetWindowDrawList(); ImGuiWindow* window=ImGui::GetCurrentWindow(); @@ -499,8 +509,8 @@ void FurnaceGUI::drawFMEnv(unsigned char tl, unsigned char ar, unsigned char dr, ImGui::RenderFrame(rect.Min,rect.Max,ImGui::GetColorU32(ImGuiCol_FrameBg),true,style.FrameRounding); //calculate x positions - float arPos=float(31-ar)/31.0; //peak of AR, start of DR - float drPos=arPos+((sl/15.0)*(float(31-dr)/31.0)); //end of DR, start of D2R + float arPos=float(maxArDr-ar)/maxArDr; //peak of AR, start of DR + float drPos=arPos+((sl/15.0)*(float(maxArDr-dr)/maxArDr)); //end of DR, start of D2R float d2rPos=drPos+(((15.0-sl)/15.0)*(float(31.0-d2r)/31.0)); //End of D2R float rrPos=(float(15-rr)/15.0); //end of RR @@ -511,43 +521,42 @@ void FurnaceGUI::drawFMEnv(unsigned char tl, unsigned char ar, unsigned char dr, rrPos/=1.0; ImVec2 pos1=ImLerp(rect.Min,rect.Max,ImVec2(0.0,1.0)); //the bottom corner - ImVec2 pos2=ImLerp(rect.Min,rect.Max,ImVec2(arPos,(tl/127.0))); //peak of AR, start of DR - ImVec2 pos3=ImLerp(rect.Min,rect.Max,ImVec2(drPos,(float)((tl/127.0)+(sl/15.0)-((tl/127.0)*(sl/15.0))))); //end of DR, start of D2R + ImVec2 pos2=ImLerp(rect.Min,rect.Max,ImVec2(arPos,(tl/maxTl))); //peak of AR, start of DR + ImVec2 pos3=ImLerp(rect.Min,rect.Max,ImVec2(drPos,(float)((tl/maxTl)+(sl/15.0)-((tl/maxTl)*(sl/15.0))))); //end of DR, start of D2R ImVec2 pos4=ImLerp(rect.Min,rect.Max,ImVec2(d2rPos,1.0)); //end of D2R - ImVec2 posRStart=ImLerp(rect.Min,rect.Max,ImVec2(0.0,(tl/127.0))); //release start + ImVec2 posRStart=ImLerp(rect.Min,rect.Max,ImVec2(0.0,(tl/maxTl))); //release start ImVec2 posREnd=ImLerp(rect.Min,rect.Max,ImVec2(rrPos,1.0));//release end - ImVec2 posSLineHEnd=ImLerp(rect.Min,rect.Max,ImVec2(1.0,(float)((tl/127.0)+(sl/15.0)-((tl/127.0)*(sl/15.0))))); //sustain horizontal line end + ImVec2 posSLineHEnd=ImLerp(rect.Min,rect.Max,ImVec2(1.0,(float)((tl/maxTl)+(sl/15.0)-((tl/maxTl)*(sl/15.0))))); //sustain horizontal line end ImVec2 posSLineVEnd=ImLerp(rect.Min,rect.Max,ImVec2(drPos,1.0)); //sustain vertical line end - ImVec2 posDecayRate0Pt=ImLerp(rect.Min,rect.Max,ImVec2(1.0,(tl/127.0))); //Heght of the peak of AR, forever - ImVec2 posDecay2Rate0Pt=ImLerp(rect.Min,rect.Max,ImVec2(1.0,(float)((tl/127.0)+(sl/15.0)-((tl/127.0)*(sl/15.0))))); //Heght of the peak of SR, forever + ImVec2 posDecayRate0Pt=ImLerp(rect.Min,rect.Max,ImVec2(1.0,(tl/maxTl))); //Heght of the peak of AR, forever + ImVec2 posDecay2Rate0Pt=ImLerp(rect.Min,rect.Max,ImVec2(1.0,(float)((tl/maxTl)+(sl/15.0)-((tl/maxTl)*(sl/15.0))))); //Heght of the peak of SR, forever + //dl->Flags=ImDrawListFlags_AntiAliasedLines|ImDrawListFlags_AntiAliasedLinesUseTex; if (ar==0.0) { //if AR = 0, the envelope never starts dl->AddTriangleFilled(posRStart,posREnd,pos1,colorS); //draw release as shaded triangle behind everything - dl->AddLine(pos1,pos4,color); //draw line on ground - } - else if (dr==0.0 && sl!=0.0) { //if DR = 0 and SL is not 0, then the envelope stays at max volume forever + addAALine(dl,pos1,pos4,color); //draw line on ground + } else if (dr==0.0 && sl!=0.0) { //if DR = 0 and SL is not 0, then the envelope stays at max volume forever dl->AddTriangleFilled(posRStart,posREnd,pos1,colorS); //draw release as shaded triangle behind everything - //dl->AddLine(pos3,posSLineHEnd,colorS); //draw horiz line through sustain level - //dl->AddLine(pos3,posSLineVEnd,colorS); //draw vert. line through sustain level - dl->AddLine(pos1,pos2,color); //A - dl->AddLine(pos2,posDecayRate0Pt,color); //Line from A to end of graph - } - else if(d2r==0.0) { //if D2R = 0, the envelope stays at the sustain level forever + //addAALine(dl,pos3,posSLineHEnd,colorS); //draw horiz line through sustain level + //addAALine(dl,pos3,posSLineVEnd,colorS); //draw vert. line through sustain level + addAALine(dl,pos1,pos2,color); //A + addAALine(dl,pos2,posDecayRate0Pt,color); //Line from A to end of graph + } else if (d2r==0.0) { //if D2R = 0, the envelope stays at the sustain level forever dl->AddTriangleFilled(posRStart,posREnd,pos1,colorS); //draw release as shaded triangle behind everything - dl->AddLine(pos3,posSLineHEnd,colorS); //draw horiz line through sustain level - dl->AddLine(pos3,posSLineVEnd,colorS); //draw vert. line through sustain level - dl->AddLine(pos1,pos2,color); //A - dl->AddLine(pos2,pos3,color); //D - dl->AddLine(pos3,posDecay2Rate0Pt,color); //Line from D to end of graph - } - else { //draw graph normally + addAALine(dl,pos3,posSLineHEnd,colorS); //draw horiz line through sustain level + addAALine(dl,pos3,posSLineVEnd,colorS); //draw vert. line through sustain level + addAALine(dl,pos1,pos2,color); //A + addAALine(dl,pos2,pos3,color); //D + addAALine(dl,pos3,posDecay2Rate0Pt,color); //Line from D to end of graph + } else { //draw graph normally dl->AddTriangleFilled(posRStart,posREnd,pos1,colorS); //draw release as shaded triangle behind everything - dl->AddLine(pos3,posSLineHEnd,colorS); //draw horiz line through sustain level - dl->AddLine(pos3,posSLineVEnd,colorS); //draw vert. line through sustain level - dl->AddLine(pos1,pos2,color); //A - dl->AddLine(pos2,pos3,color); //D - dl->AddLine(pos3,pos4,color); //D2 + addAALine(dl,pos3,posSLineHEnd,colorS); //draw horiz line through sustain level + addAALine(dl,pos3,posSLineVEnd,colorS); //draw vert. line through sustain level + addAALine(dl,pos1,pos2,color); //A + addAALine(dl,pos2,pos3,color); //D + addAALine(dl,pos3,pos4,color); //D2 } + //dl->Flags^=ImDrawListFlags_AntiAliasedLines|ImDrawListFlags_AntiAliasedLinesUseTex; } } @@ -899,7 +908,7 @@ void FurnaceGUI::drawInsEdit() { } //52.0 controls vert scaling; default 96 - drawFMEnv(op.tl,op.ar,op.dr,op.d2r,op.rr,op.sl,ImVec2(ImGui::GetContentRegionAvail().x,52.0*dpiScale)); + drawFMEnv(op.tl&maxTl,op.ar&maxArDr,op.dr&maxArDr,(ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPLL)?((op.rr&15)*2):op.d2r&31,op.rr&15,op.sl&15,maxTl,maxArDr,ImVec2(ImGui::GetContentRegionAvail().x,52.0*dpiScale)); //P(ImGui::SliderScalar(FM_NAME(FM_AR),ImGuiDataType_U8,&op.ar,&_ZERO,&_THIRTY_ONE)); if (ImGui::BeginTable("opParams",2,ImGuiTableFlags_SizingStretchProp)) { ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch,0.0); \ @@ -1027,15 +1036,24 @@ void FurnaceGUI::drawInsEdit() { } if (ImGui::BeginTabItem("FM Macros")) { MACRO_BEGIN(0); - NORMAL_MACRO(ins->std.algMacro,ins->std.algMacroLen,ins->std.algMacroLoop,ins->std.algMacroRel,0,7,"alg",FM_NAME(FM_ALG),96,ins->std.algMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[0],0,7,NULL,false); - NORMAL_MACRO(ins->std.fbMacro,ins->std.fbMacroLen,ins->std.fbMacroLoop,ins->std.fbMacroRel,0,7,"fb",FM_NAME(FM_FB),96,ins->std.fbMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[1],0,7,NULL,false); - NORMAL_MACRO(ins->std.fmsMacro,ins->std.fmsMacroLen,ins->std.fmsMacroLoop,ins->std.fmsMacroRel,0,7,"fms",FM_NAME(FM_FMS),96,ins->std.fmsMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[2],0,7,NULL,false); - NORMAL_MACRO(ins->std.amsMacro,ins->std.amsMacroLen,ins->std.amsMacroLoop,ins->std.amsMacroRel,0,3,"ams",FM_NAME(FM_AMS),48,ins->std.amsMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[3],0,3,NULL,false); + if (ins->type==DIV_INS_OPLL) { + NORMAL_MACRO(ins->std.algMacro,ins->std.algMacroLen,ins->std.algMacroLoop,ins->std.algMacroRel,0,1,"alg",FM_NAME(FM_SUS),16,ins->std.algMacroOpen,true,oneBit,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[0],0,1,NULL,false); + NORMAL_MACRO(ins->std.fbMacro,ins->std.fbMacroLen,ins->std.fbMacroLoop,ins->std.fbMacroRel,0,7,"fb",FM_NAME(FM_FB),96,ins->std.fbMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[1],0,7,NULL,false); + NORMAL_MACRO(ins->std.fmsMacro,ins->std.fmsMacroLen,ins->std.fmsMacroLoop,ins->std.fmsMacroRel,0,1,"fms",FM_NAME(FM_DC),16,ins->std.fmsMacroOpen,true,oneBit,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[2],0,1,NULL,false); + NORMAL_MACRO(ins->std.amsMacro,ins->std.amsMacroLen,ins->std.amsMacroLoop,ins->std.amsMacroRel,0,1,"ams",FM_NAME(FM_DM),16,ins->std.amsMacroOpen,true,oneBit,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[3],0,1,NULL,false); + } else { + NORMAL_MACRO(ins->std.algMacro,ins->std.algMacroLen,ins->std.algMacroLoop,ins->std.algMacroRel,0,7,"alg",FM_NAME(FM_ALG),96,ins->std.algMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[0],0,7,NULL,false); + NORMAL_MACRO(ins->std.fbMacro,ins->std.fbMacroLen,ins->std.fbMacroLoop,ins->std.fbMacroRel,0,7,"fb",FM_NAME(FM_FB),96,ins->std.fbMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[1],0,7,NULL,false); + NORMAL_MACRO(ins->std.fmsMacro,ins->std.fmsMacroLen,ins->std.fmsMacroLoop,ins->std.fmsMacroRel,0,7,"fms",FM_NAME(FM_FMS),96,ins->std.fmsMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[2],0,7,NULL,false); + NORMAL_MACRO(ins->std.amsMacro,ins->std.amsMacroLen,ins->std.amsMacroLoop,ins->std.amsMacroRel,0,3,"ams",FM_NAME(FM_AMS),48,ins->std.amsMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[3],0,3,NULL,false); + } - NORMAL_MACRO(ins->std.ex1Macro,ins->std.ex1MacroLen,ins->std.ex1MacroLoop,ins->std.ex1MacroRel,0,127,"ex1","AM Depth",128,ins->std.ex1MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,127,NULL,false); - NORMAL_MACRO(ins->std.ex2Macro,ins->std.ex2MacroLen,ins->std.ex2MacroLoop,ins->std.ex2MacroRel,0,127,"ex2","PM Depth",128,ins->std.ex2MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[5],0,127,NULL,false); - NORMAL_MACRO(ins->std.ex3Macro,ins->std.ex3MacroLen,ins->std.ex3MacroLoop,ins->std.ex3MacroRel,0,255,"ex3","LFO Speed",128,ins->std.ex3MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[6],0,255,NULL,false); - NORMAL_MACRO(ins->std.waveMacro,ins->std.waveMacroLen,ins->std.waveMacroLoop,ins->std.waveMacroRel,0,3,"wave","LFO Shape",48,ins->std.waveMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_WAVE],mmlString[7],0,3,¯oLFOWaves,false); + if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { + NORMAL_MACRO(ins->std.ex1Macro,ins->std.ex1MacroLen,ins->std.ex1MacroLoop,ins->std.ex1MacroRel,0,127,"ex1","AM Depth",128,ins->std.ex1MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,127,NULL,false); + NORMAL_MACRO(ins->std.ex2Macro,ins->std.ex2MacroLen,ins->std.ex2MacroLoop,ins->std.ex2MacroRel,0,127,"ex2","PM Depth",128,ins->std.ex2MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[5],0,127,NULL,false); + NORMAL_MACRO(ins->std.ex3Macro,ins->std.ex3MacroLen,ins->std.ex3MacroLoop,ins->std.ex3MacroRel,0,255,"ex3","LFO Speed",128,ins->std.ex3MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[6],0,255,NULL,false); + NORMAL_MACRO(ins->std.waveMacro,ins->std.waveMacroLen,ins->std.waveMacroLoop,ins->std.waveMacroRel,0,3,"wave","LFO Shape",48,ins->std.waveMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_WAVE],mmlString[7],0,3,¯oLFOWaves,false); + } MACRO_END; ImGui::EndTabItem(); } @@ -1055,18 +1073,33 @@ void FurnaceGUI::drawInsEdit() { } int maxArDr=(ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ)?31:15; - OP_MACRO(ins->std.opMacros[ordi].tlMacro,ins->std.opMacros[ordi].tlMacroLen,ins->std.opMacros[ordi].tlMacroLoop,ins->std.opMacros[ordi].tlMacroRel,maxTl,ordi,"tl",FM_NAME(FM_TL),128,ins->std.opMacros[ordi].tlMacroOpen,false,NULL,mmlString[0]); - OP_MACRO(ins->std.opMacros[ordi].arMacro,ins->std.opMacros[ordi].arMacroLen,ins->std.opMacros[ordi].arMacroLoop,ins->std.opMacros[ordi].arMacroRel,maxArDr,ordi,"ar",FM_NAME(FM_AR),64,ins->std.opMacros[ordi].arMacroOpen,false,NULL,mmlString[1]); - OP_MACRO(ins->std.opMacros[ordi].drMacro,ins->std.opMacros[ordi].drMacroLen,ins->std.opMacros[ordi].drMacroLoop,ins->std.opMacros[ordi].drMacroRel,maxArDr,ordi,"dr",FM_NAME(FM_DR),64,ins->std.opMacros[ordi].drMacroOpen,false,NULL,mmlString[2]); - OP_MACRO(ins->std.opMacros[ordi].d2rMacro,ins->std.opMacros[ordi].d2rMacroLen,ins->std.opMacros[ordi].d2rMacroLoop,ins->std.opMacros[ordi].d2rMacroRel,31,ordi,"d2r",FM_NAME(FM_D2R),64,ins->std.opMacros[ordi].d2rMacroOpen,false,NULL,mmlString[3]); - OP_MACRO(ins->std.opMacros[ordi].rrMacro,ins->std.opMacros[ordi].rrMacroLen,ins->std.opMacros[ordi].rrMacroLoop,ins->std.opMacros[ordi].rrMacroRel,15,ordi,"rr",FM_NAME(FM_RR),64,ins->std.opMacros[ordi].rrMacroOpen,false,NULL,mmlString[4]); - OP_MACRO(ins->std.opMacros[ordi].slMacro,ins->std.opMacros[ordi].slMacroLen,ins->std.opMacros[ordi].slMacroLoop,ins->std.opMacros[ordi].slMacroRel,15,ordi,"sl",FM_NAME(FM_SL),64,ins->std.opMacros[ordi].slMacroOpen,false,NULL,mmlString[5]); - OP_MACRO(ins->std.opMacros[ordi].rsMacro,ins->std.opMacros[ordi].rsMacroLen,ins->std.opMacros[ordi].rsMacroLoop,ins->std.opMacros[ordi].rsMacroRel,3,ordi,"rs",FM_NAME(FM_RS),32,ins->std.opMacros[ordi].rsMacroOpen,false,NULL,mmlString[6]); - OP_MACRO(ins->std.opMacros[ordi].multMacro,ins->std.opMacros[ordi].multMacroLen,ins->std.opMacros[ordi].multMacroLoop,ins->std.opMacros[ordi].multMacroRel,15,ordi,"mult",FM_NAME(FM_MULT),64,ins->std.opMacros[ordi].multMacroOpen,false,NULL,mmlString[7]); - OP_MACRO(ins->std.opMacros[ordi].dtMacro,ins->std.opMacros[ordi].dtMacroLen,ins->std.opMacros[ordi].dtMacroLoop,ins->std.opMacros[ordi].dtMacroRel,7,ordi,"dt",FM_NAME(FM_DT),64,ins->std.opMacros[ordi].dtMacroOpen,false,NULL,mmlString[8]); - OP_MACRO(ins->std.opMacros[ordi].dt2Macro,ins->std.opMacros[ordi].dt2MacroLen,ins->std.opMacros[ordi].dt2MacroLoop,ins->std.opMacros[ordi].dt2MacroRel,3,ordi,"dt2",FM_NAME(FM_DT2),32,ins->std.opMacros[ordi].dt2MacroOpen,false,NULL,mmlString[9]); - OP_MACRO(ins->std.opMacros[ordi].amMacro,ins->std.opMacros[ordi].amMacroLen,ins->std.opMacros[ordi].amMacroLoop,ins->std.opMacros[ordi].amMacroRel,1,ordi,"am",FM_NAME(FM_AM),32,ins->std.opMacros[ordi].amMacroOpen,true,NULL,mmlString[10]); - OP_MACRO(ins->std.opMacros[ordi].ssgMacro,ins->std.opMacros[ordi].ssgMacroLen,ins->std.opMacros[ordi].ssgMacroLoop,ins->std.opMacros[ordi].ssgMacroRel,4,ordi,"ssg",FM_NAME(FM_SSG),64,ins->std.opMacros[ordi].ssgMacroOpen,true,ssgEnvBits,mmlString[11]); + if (ins->type==DIV_INS_OPLL) { + OP_MACRO(ins->std.opMacros[ordi].tlMacro,ins->std.opMacros[ordi].tlMacroLen,ins->std.opMacros[ordi].tlMacroLoop,ins->std.opMacros[ordi].tlMacroRel,maxTl,ordi,"tl",FM_NAME(FM_TL),128,ins->std.opMacros[ordi].tlMacroOpen,false,NULL,mmlString[0]); + OP_MACRO(ins->std.opMacros[ordi].arMacro,ins->std.opMacros[ordi].arMacroLen,ins->std.opMacros[ordi].arMacroLoop,ins->std.opMacros[ordi].arMacroRel,maxArDr,ordi,"ar",FM_NAME(FM_AR),64,ins->std.opMacros[ordi].arMacroOpen,false,NULL,mmlString[1]); + OP_MACRO(ins->std.opMacros[ordi].drMacro,ins->std.opMacros[ordi].drMacroLen,ins->std.opMacros[ordi].drMacroLoop,ins->std.opMacros[ordi].drMacroRel,maxArDr,ordi,"dr",FM_NAME(FM_DR),64,ins->std.opMacros[ordi].drMacroOpen,false,NULL,mmlString[2]); + OP_MACRO(ins->std.opMacros[ordi].slMacro,ins->std.opMacros[ordi].slMacroLen,ins->std.opMacros[ordi].slMacroLoop,ins->std.opMacros[ordi].slMacroRel,15,ordi,"sl",FM_NAME(FM_SL),64,ins->std.opMacros[ordi].slMacroOpen,false,NULL,mmlString[5]); + OP_MACRO(ins->std.opMacros[ordi].rrMacro,ins->std.opMacros[ordi].rrMacroLen,ins->std.opMacros[ordi].rrMacroLoop,ins->std.opMacros[ordi].rrMacroRel,15,ordi,"rr",FM_NAME(FM_RR),64,ins->std.opMacros[ordi].rrMacroOpen,false,NULL,mmlString[4]); + OP_MACRO(ins->std.opMacros[ordi].kslMacro,ins->std.opMacros[ordi].kslMacroLen,ins->std.opMacros[ordi].kslMacroLoop,ins->std.opMacros[ordi].kslMacroRel,3,ordi,"ksl",FM_NAME(FM_KSL),32,ins->std.opMacros[ordi].kslMacroOpen,false,NULL,mmlString[6]); + OP_MACRO(ins->std.opMacros[ordi].multMacro,ins->std.opMacros[ordi].multMacroLen,ins->std.opMacros[ordi].multMacroLoop,ins->std.opMacros[ordi].multMacroRel,15,ordi,"mult",FM_NAME(FM_MULT),64,ins->std.opMacros[ordi].multMacroOpen,false,NULL,mmlString[7]); + + OP_MACRO(ins->std.opMacros[ordi].dtMacro,ins->std.opMacros[ordi].dtMacroLen,ins->std.opMacros[ordi].dtMacroLoop,ins->std.opMacros[ordi].dtMacroRel,7,ordi,"dt",FM_NAME(FM_DT),64,ins->std.opMacros[ordi].dtMacroOpen,false,NULL,mmlString[8]); + OP_MACRO(ins->std.opMacros[ordi].dt2Macro,ins->std.opMacros[ordi].dt2MacroLen,ins->std.opMacros[ordi].dt2MacroLoop,ins->std.opMacros[ordi].dt2MacroRel,3,ordi,"dt2",FM_NAME(FM_DT2),32,ins->std.opMacros[ordi].dt2MacroOpen,false,NULL,mmlString[9]); + OP_MACRO(ins->std.opMacros[ordi].amMacro,ins->std.opMacros[ordi].amMacroLen,ins->std.opMacros[ordi].amMacroLoop,ins->std.opMacros[ordi].amMacroRel,1,ordi,"am",FM_NAME(FM_AM),32,ins->std.opMacros[ordi].amMacroOpen,true,NULL,mmlString[10]); + OP_MACRO(ins->std.opMacros[ordi].ssgMacro,ins->std.opMacros[ordi].ssgMacroLen,ins->std.opMacros[ordi].ssgMacroLoop,ins->std.opMacros[ordi].ssgMacroRel,4,ordi,"ssg",FM_NAME(FM_SSG),64,ins->std.opMacros[ordi].ssgMacroOpen,true,ssgEnvBits,mmlString[11]); + } else { + OP_MACRO(ins->std.opMacros[ordi].tlMacro,ins->std.opMacros[ordi].tlMacroLen,ins->std.opMacros[ordi].tlMacroLoop,ins->std.opMacros[ordi].tlMacroRel,maxTl,ordi,"tl",FM_NAME(FM_TL),128,ins->std.opMacros[ordi].tlMacroOpen,false,NULL,mmlString[0]); + OP_MACRO(ins->std.opMacros[ordi].arMacro,ins->std.opMacros[ordi].arMacroLen,ins->std.opMacros[ordi].arMacroLoop,ins->std.opMacros[ordi].arMacroRel,maxArDr,ordi,"ar",FM_NAME(FM_AR),64,ins->std.opMacros[ordi].arMacroOpen,false,NULL,mmlString[1]); + OP_MACRO(ins->std.opMacros[ordi].drMacro,ins->std.opMacros[ordi].drMacroLen,ins->std.opMacros[ordi].drMacroLoop,ins->std.opMacros[ordi].drMacroRel,maxArDr,ordi,"dr",FM_NAME(FM_DR),64,ins->std.opMacros[ordi].drMacroOpen,false,NULL,mmlString[2]); + OP_MACRO(ins->std.opMacros[ordi].d2rMacro,ins->std.opMacros[ordi].d2rMacroLen,ins->std.opMacros[ordi].d2rMacroLoop,ins->std.opMacros[ordi].d2rMacroRel,31,ordi,"d2r",FM_NAME(FM_D2R),64,ins->std.opMacros[ordi].d2rMacroOpen,false,NULL,mmlString[3]); + OP_MACRO(ins->std.opMacros[ordi].rrMacro,ins->std.opMacros[ordi].rrMacroLen,ins->std.opMacros[ordi].rrMacroLoop,ins->std.opMacros[ordi].rrMacroRel,15,ordi,"rr",FM_NAME(FM_RR),64,ins->std.opMacros[ordi].rrMacroOpen,false,NULL,mmlString[4]); + OP_MACRO(ins->std.opMacros[ordi].slMacro,ins->std.opMacros[ordi].slMacroLen,ins->std.opMacros[ordi].slMacroLoop,ins->std.opMacros[ordi].slMacroRel,15,ordi,"sl",FM_NAME(FM_SL),64,ins->std.opMacros[ordi].slMacroOpen,false,NULL,mmlString[5]); + OP_MACRO(ins->std.opMacros[ordi].rsMacro,ins->std.opMacros[ordi].rsMacroLen,ins->std.opMacros[ordi].rsMacroLoop,ins->std.opMacros[ordi].rsMacroRel,3,ordi,"rs",FM_NAME(FM_RS),32,ins->std.opMacros[ordi].rsMacroOpen,false,NULL,mmlString[6]); + OP_MACRO(ins->std.opMacros[ordi].multMacro,ins->std.opMacros[ordi].multMacroLen,ins->std.opMacros[ordi].multMacroLoop,ins->std.opMacros[ordi].multMacroRel,15,ordi,"mult",FM_NAME(FM_MULT),64,ins->std.opMacros[ordi].multMacroOpen,false,NULL,mmlString[7]); + OP_MACRO(ins->std.opMacros[ordi].dtMacro,ins->std.opMacros[ordi].dtMacroLen,ins->std.opMacros[ordi].dtMacroLoop,ins->std.opMacros[ordi].dtMacroRel,7,ordi,"dt",FM_NAME(FM_DT),64,ins->std.opMacros[ordi].dtMacroOpen,false,NULL,mmlString[8]); + OP_MACRO(ins->std.opMacros[ordi].dt2Macro,ins->std.opMacros[ordi].dt2MacroLen,ins->std.opMacros[ordi].dt2MacroLoop,ins->std.opMacros[ordi].dt2MacroRel,3,ordi,"dt2",FM_NAME(FM_DT2),32,ins->std.opMacros[ordi].dt2MacroOpen,false,NULL,mmlString[9]); + OP_MACRO(ins->std.opMacros[ordi].amMacro,ins->std.opMacros[ordi].amMacroLen,ins->std.opMacros[ordi].amMacroLoop,ins->std.opMacros[ordi].amMacroRel,1,ordi,"am",FM_NAME(FM_AM),32,ins->std.opMacros[ordi].amMacroOpen,true,NULL,mmlString[10]); + OP_MACRO(ins->std.opMacros[ordi].ssgMacro,ins->std.opMacros[ordi].ssgMacroLen,ins->std.opMacros[ordi].ssgMacroLoop,ins->std.opMacros[ordi].ssgMacroRel,4,ordi,"ssg",FM_NAME(FM_SSG),64,ins->std.opMacros[ordi].ssgMacroOpen,true,ssgEnvBits,mmlString[11]); + } MACRO_END; ImGui::PopID(); ImGui::EndTabItem(); From f8e7dd72143df2b12a48f65fc2c4aec93e7a0f3a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 28 Feb 2022 18:38:36 -0500 Subject: [PATCH 017/637] GUI: hopefully finish OPLL op macro interface --- src/gui/insEdit.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index a8558bb07..8a00002d0 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1082,10 +1082,10 @@ void FurnaceGUI::drawInsEdit() { OP_MACRO(ins->std.opMacros[ordi].kslMacro,ins->std.opMacros[ordi].kslMacroLen,ins->std.opMacros[ordi].kslMacroLoop,ins->std.opMacros[ordi].kslMacroRel,3,ordi,"ksl",FM_NAME(FM_KSL),32,ins->std.opMacros[ordi].kslMacroOpen,false,NULL,mmlString[6]); OP_MACRO(ins->std.opMacros[ordi].multMacro,ins->std.opMacros[ordi].multMacroLen,ins->std.opMacros[ordi].multMacroLoop,ins->std.opMacros[ordi].multMacroRel,15,ordi,"mult",FM_NAME(FM_MULT),64,ins->std.opMacros[ordi].multMacroOpen,false,NULL,mmlString[7]); - OP_MACRO(ins->std.opMacros[ordi].dtMacro,ins->std.opMacros[ordi].dtMacroLen,ins->std.opMacros[ordi].dtMacroLoop,ins->std.opMacros[ordi].dtMacroRel,7,ordi,"dt",FM_NAME(FM_DT),64,ins->std.opMacros[ordi].dtMacroOpen,false,NULL,mmlString[8]); - OP_MACRO(ins->std.opMacros[ordi].dt2Macro,ins->std.opMacros[ordi].dt2MacroLen,ins->std.opMacros[ordi].dt2MacroLoop,ins->std.opMacros[ordi].dt2MacroRel,3,ordi,"dt2",FM_NAME(FM_DT2),32,ins->std.opMacros[ordi].dt2MacroOpen,false,NULL,mmlString[9]); - OP_MACRO(ins->std.opMacros[ordi].amMacro,ins->std.opMacros[ordi].amMacroLen,ins->std.opMacros[ordi].amMacroLoop,ins->std.opMacros[ordi].amMacroRel,1,ordi,"am",FM_NAME(FM_AM),32,ins->std.opMacros[ordi].amMacroOpen,true,NULL,mmlString[10]); - OP_MACRO(ins->std.opMacros[ordi].ssgMacro,ins->std.opMacros[ordi].ssgMacroLen,ins->std.opMacros[ordi].ssgMacroLoop,ins->std.opMacros[ordi].ssgMacroRel,4,ordi,"ssg",FM_NAME(FM_SSG),64,ins->std.opMacros[ordi].ssgMacroOpen,true,ssgEnvBits,mmlString[11]); + OP_MACRO(ins->std.opMacros[ordi].amMacro,ins->std.opMacros[ordi].amMacroLen,ins->std.opMacros[ordi].amMacroLoop,ins->std.opMacros[ordi].amMacroRel,1,ordi,"am",FM_NAME(FM_AM),16,ins->std.opMacros[ordi].amMacroOpen,true,NULL,mmlString[8]); + OP_MACRO(ins->std.opMacros[ordi].vibMacro,ins->std.opMacros[ordi].vibMacroLen,ins->std.opMacros[ordi].vibMacroLoop,ins->std.opMacros[ordi].vibMacroRel,4,ordi,"vib",FM_NAME(FM_VIB),16,ins->std.opMacros[ordi].vibMacroOpen,true,oneBit,mmlString[9]); + OP_MACRO(ins->std.opMacros[ordi].ksrMacro,ins->std.opMacros[ordi].ksrMacroLen,ins->std.opMacros[ordi].ksrMacroLoop,ins->std.opMacros[ordi].ksrMacroRel,4,ordi,"ksr",FM_NAME(FM_KSR),16,ins->std.opMacros[ordi].ksrMacroOpen,true,oneBit,mmlString[10]); + OP_MACRO(ins->std.opMacros[ordi].egtMacro,ins->std.opMacros[ordi].egtMacroLen,ins->std.opMacros[ordi].egtMacroLoop,ins->std.opMacros[ordi].egtMacroRel,4,ordi,"egt",FM_NAME(FM_EGS),16,ins->std.opMacros[ordi].egtMacroOpen,true,oneBit,mmlString[11]); } else { OP_MACRO(ins->std.opMacros[ordi].tlMacro,ins->std.opMacros[ordi].tlMacroLen,ins->std.opMacros[ordi].tlMacroLoop,ins->std.opMacros[ordi].tlMacroRel,maxTl,ordi,"tl",FM_NAME(FM_TL),128,ins->std.opMacros[ordi].tlMacroOpen,false,NULL,mmlString[0]); OP_MACRO(ins->std.opMacros[ordi].arMacro,ins->std.opMacros[ordi].arMacroLen,ins->std.opMacros[ordi].arMacroLoop,ins->std.opMacros[ordi].arMacroRel,maxArDr,ordi,"ar",FM_NAME(FM_AR),64,ins->std.opMacros[ordi].arMacroOpen,false,NULL,mmlString[1]); From f8046facfcfb916e31a2b7b6a49e80267b426665 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 28 Feb 2022 22:42:52 -0500 Subject: [PATCH 018/637] GUI: make sliders right-clickable --- src/gui/gui.cpp | 12 ++++----- src/gui/gui.h | 2 ++ src/gui/insEdit.cpp | 58 ++++++++++++++++++++++---------------------- src/gui/settings.cpp | 2 +- 4 files changed, 38 insertions(+), 36 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index ee0c02370..b002afb9c 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1348,7 +1348,7 @@ void FurnaceGUI::drawMixer() { if (ImGui::SliderFloat("Master Volume",&e->song.masterVol,0,3,"%.2fx")) { if (e->song.masterVol<0) e->song.masterVol=0; if (e->song.masterVol>3) e->song.masterVol=3; - } + } rightClickable for (int i=0; isong.systemLen; i++) { snprintf(id,31,"MixS%d",i); bool doInvert=e->song.systemVol[i]&128; @@ -1362,9 +1362,9 @@ void FurnaceGUI::drawMixer() { ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x-(50.0f*dpiScale)); if (ImGui::SliderScalar("Volume",ImGuiDataType_S8,&vol,&_ZERO,&_ONE_HUNDRED_TWENTY_SEVEN)) { e->song.systemVol[i]=(e->song.systemVol[i]&128)|vol; - } + } rightClickable ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x-(50.0f*dpiScale)); - ImGui::SliderScalar("Panning",ImGuiDataType_S8,&e->song.systemPan[i],&_MINUS_ONE_HUNDRED_TWENTY_SEVEN,&_ONE_HUNDRED_TWENTY_SEVEN); + ImGui::SliderScalar("Panning",ImGuiDataType_S8,&e->song.systemPan[i],&_MINUS_ONE_HUNDRED_TWENTY_SEVEN,&_ONE_HUNDRED_TWENTY_SEVEN); rightClickable ImGui::PopID(); } @@ -4691,7 +4691,7 @@ bool FurnaceGUI::loop() { if (stereoSep>127) stereoSep=127; e->setSysFlags(i,(flags&1)|((stereoSep&127)<<8),restart); updateWindowTitle(); - } + } rightClickable /* TODO LATER: I want 0.5 out already if (ImGui::RadioButton("Amiga 500 (OCS)",(flags&2)==0)) { e->setSysFlags(i,flags&1); @@ -4714,7 +4714,7 @@ bool FurnaceGUI::loop() { if (echoBufSize>2725) echoBufSize=2725; e->setSysFlags(i,(flags & ~4095) | ((2725 - echoBufSize) & 4095),restart); updateWindowTitle(); - } + } rightClickable ImGui::Text("Echo feedback:"); int echoFeedback=(flags>>12)&255; if (ImGui::SliderInt("##EchoFeedback",&echoFeedback,0,255)) { @@ -4722,7 +4722,7 @@ bool FurnaceGUI::loop() { if (echoFeedback>255) echoFeedback=255; e->setSysFlags(i,(flags & ~(255 << 12)) | ((echoFeedback & 255) << 12),restart); updateWindowTitle(); - } + } rightClickable break; } case DIV_SYSTEM_GB: diff --git a/src/gui/gui.h b/src/gui/gui.h index 4cae110dc..291db6d8d 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -26,6 +26,8 @@ #include #include +#define rightClickable if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) ImGui::SetKeyboardFocusHere(-1); + enum FurnaceGUIColors { GUI_COLOR_BACKGROUND=0, GUI_COLOR_FRAME_BACKGROUND, diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 8a00002d0..4608f89b1 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -808,11 +808,11 @@ void FurnaceGUI::drawInsEdit() { case DIV_INS_FM: case DIV_INS_OPZ: ImGui::TableNextColumn(); - P(ImGui::SliderScalar(FM_NAME(FM_FB),ImGuiDataType_U8,&ins->fm.fb,&_ZERO,&_SEVEN)); - P(ImGui::SliderScalar(FM_NAME(FM_FMS),ImGuiDataType_U8,&ins->fm.fms,&_ZERO,&_SEVEN)); + P(ImGui::SliderScalar(FM_NAME(FM_FB),ImGuiDataType_U8,&ins->fm.fb,&_ZERO,&_SEVEN)); rightClickable + P(ImGui::SliderScalar(FM_NAME(FM_FMS),ImGuiDataType_U8,&ins->fm.fms,&_ZERO,&_SEVEN)); rightClickable ImGui::TableNextColumn(); - P(ImGui::SliderScalar(FM_NAME(FM_ALG),ImGuiDataType_U8,&ins->fm.alg,&_ZERO,&_SEVEN)); - P(ImGui::SliderScalar(FM_NAME(FM_AMS),ImGuiDataType_U8,&ins->fm.ams,&_ZERO,&_THREE)); + P(ImGui::SliderScalar(FM_NAME(FM_ALG),ImGuiDataType_U8,&ins->fm.alg,&_ZERO,&_SEVEN)); rightClickable + P(ImGui::SliderScalar(FM_NAME(FM_AMS),ImGuiDataType_U8,&ins->fm.ams,&_ZERO,&_THREE)); rightClickable ImGui::TableNextColumn(); drawAlgorithm(ins->fm.alg,FM_ALGS_4OP,ImVec2(ImGui::GetContentRegionAvail().x,48.0*dpiScale)); break; @@ -823,7 +823,7 @@ void FurnaceGUI::drawInsEdit() { bool sus=ins->fm.alg; ImGui::TableNextColumn(); ImGui::BeginDisabled(ins->fm.opllPreset!=0); - P(ImGui::SliderScalar(FM_NAME(FM_FB),ImGuiDataType_U8,&ins->fm.fb,&_ZERO,&_SEVEN)); + P(ImGui::SliderScalar(FM_NAME(FM_FB),ImGuiDataType_U8,&ins->fm.fb,&_ZERO,&_SEVEN)); rightClickable if (ImGui::Checkbox(FM_NAME(FM_DC),&dc)) { PARAMETER ins->fm.fms=dc; } @@ -863,7 +863,7 @@ void FurnaceGUI::drawInsEdit() { bool willDisplayOps=true; if (ins->type==DIV_INS_OPLL && ins->fm.opllPreset!=0) willDisplayOps=false; if (!willDisplayOps && ins->type==DIV_INS_OPLL) { - P(ImGui::SliderScalar("Volume##TL",ImGuiDataType_U8,&ins->fm.op[1].tl,&_FIFTEEN,&_ZERO)); + P(ImGui::SliderScalar("Volume##TL",ImGuiDataType_U8,&ins->fm.op[1].tl,&_FIFTEEN,&_ZERO)); rightClickable } if (willDisplayOps) if (ImGui::BeginTable("FMOperators",2,ImGuiTableFlags_SizingStretchSame)) { for (int i=0; itype==DIV_INS_OPL || ins->type==DIV_INS_OPLL)?((op.rr&15)*2):op.d2r&31,op.rr&15,op.sl&15,maxTl,maxArDr,ImVec2(ImGui::GetContentRegionAvail().x,52.0*dpiScale)); - //P(ImGui::SliderScalar(FM_NAME(FM_AR),ImGuiDataType_U8,&op.ar,&_ZERO,&_THIRTY_ONE)); + //P(ImGui::SliderScalar(FM_NAME(FM_AR),ImGuiDataType_U8,&op.ar,&_ZERO,&_THIRTY_ONE)); rightClickable if (ImGui::BeginTable("opParams",2,ImGuiTableFlags_SizingStretchProp)) { ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch,0.0); \ ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed,0.0); \ @@ -917,21 +917,21 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - P(ImGui::SliderScalar("##AR",ImGuiDataType_U8,&op.ar,&maxArDr,&_ZERO)); + P(ImGui::SliderScalar("##AR",ImGuiDataType_U8,&op.ar,&maxArDr,&_ZERO)); rightClickable ImGui::TableNextColumn(); ImGui::Text("%s",FM_NAME(FM_AR)); ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - P(ImGui::SliderScalar("##DR",ImGuiDataType_U8,&op.dr,&maxArDr,&_ZERO)); + P(ImGui::SliderScalar("##DR",ImGuiDataType_U8,&op.dr,&maxArDr,&_ZERO)); rightClickable ImGui::TableNextColumn(); ImGui::Text("%s",FM_NAME(FM_DR)); ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - P(ImGui::SliderScalar("##SL",ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); + P(ImGui::SliderScalar("##SL",ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); rightClickable ImGui::TableNextColumn(); ImGui::Text("%s",FM_NAME(FM_SL)); @@ -939,7 +939,7 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - P(ImGui::SliderScalar("##D2R",ImGuiDataType_U8,&op.d2r,&_THIRTY_ONE,&_ZERO)); + P(ImGui::SliderScalar("##D2R",ImGuiDataType_U8,&op.d2r,&_THIRTY_ONE,&_ZERO)); rightClickable ImGui::TableNextColumn(); ImGui::Text("%s",FM_NAME(FM_D2R)); } @@ -947,14 +947,14 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - P(ImGui::SliderScalar("##RR",ImGuiDataType_U8,&op.rr,&_FIFTEEN,&_ZERO)); + P(ImGui::SliderScalar("##RR",ImGuiDataType_U8,&op.rr,&_FIFTEEN,&_ZERO)); rightClickable ImGui::TableNextColumn(); ImGui::Text("%s",FM_NAME(FM_RR)); ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - P(ImGui::SliderScalar("##TL",ImGuiDataType_U8,&op.tl,&maxTl,&_ZERO)); + P(ImGui::SliderScalar("##TL",ImGuiDataType_U8,&op.tl,&maxTl,&_ZERO)); rightClickable ImGui::TableNextColumn(); ImGui::Text("%s",FM_NAME(FM_TL)); @@ -968,11 +968,11 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { - P(ImGui::SliderScalar("##RS",ImGuiDataType_U8,&op.rs,&_ZERO,&_THREE)); + P(ImGui::SliderScalar("##RS",ImGuiDataType_U8,&op.rs,&_ZERO,&_THREE)); rightClickable ImGui::TableNextColumn(); ImGui::Text("%s",FM_NAME(FM_RS)); } else { - P(ImGui::SliderScalar("##KSL",ImGuiDataType_U8,&op.ksl,&_ZERO,&_THREE)); + P(ImGui::SliderScalar("##KSL",ImGuiDataType_U8,&op.ksl,&_ZERO,&_THREE)); rightClickable ImGui::TableNextColumn(); ImGui::Text("%s",FM_NAME(FM_KSL)); } @@ -980,7 +980,7 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - P(ImGui::SliderScalar(FM_NAME(FM_MULT),ImGuiDataType_U8,&op.mult,&_ZERO,&_FIFTEEN)); + P(ImGui::SliderScalar(FM_NAME(FM_MULT),ImGuiDataType_U8,&op.mult,&_ZERO,&_FIFTEEN)); rightClickable ImGui::TableNextColumn(); ImGui::Text("%s",FM_NAME(FM_MULT)); @@ -991,14 +991,14 @@ void FurnaceGUI::drawInsEdit() { ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); if (ImGui::SliderInt("##DT",&detune,-3,3)) { PARAMETER op.dt=detune+3; - } + } rightClickable ImGui::TableNextColumn(); ImGui::Text("%s",FM_NAME(FM_DT)); ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - P(ImGui::SliderScalar("##DT2",ImGuiDataType_U8,&op.dt2,&_ZERO,&_THREE)); + P(ImGui::SliderScalar("##DT2",ImGuiDataType_U8,&op.dt2,&_ZERO,&_THREE)); rightClickable if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Only for Arcade system"); } @@ -1010,7 +1010,7 @@ void FurnaceGUI::drawInsEdit() { ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); if (ImGui::SliderScalar("##SSG",ImGuiDataType_U8,&ssgEnv,&_ZERO,&_SEVEN,ssgEnvTypes[ssgEnv])) { PARAMETER op.ssgEnv=(op.ssgEnv&8)|(ssgEnv&7); - } + } rightClickable ImGui::TableNextColumn(); ImGui::Text("%s",FM_NAME(FM_SSG)); } @@ -1107,9 +1107,9 @@ void FurnaceGUI::drawInsEdit() { } } if (ins->type==DIV_INS_GB) if (ImGui::BeginTabItem("Game Boy")) { - P(ImGui::SliderScalar("Volume",ImGuiDataType_U8,&ins->gb.envVol,&_ZERO,&_FIFTEEN)); - P(ImGui::SliderScalar("Envelope Length",ImGuiDataType_U8,&ins->gb.envLen,&_ZERO,&_SEVEN)); - P(ImGui::SliderScalar("Sound Length",ImGuiDataType_U8,&ins->gb.soundLen,&_ZERO,&_SIXTY_FOUR,ins->gb.soundLen>63?"Infinity":"%d")); + P(ImGui::SliderScalar("Volume",ImGuiDataType_U8,&ins->gb.envVol,&_ZERO,&_FIFTEEN)); rightClickable + P(ImGui::SliderScalar("Envelope Length",ImGuiDataType_U8,&ins->gb.envLen,&_ZERO,&_SEVEN)); rightClickable + P(ImGui::SliderScalar("Sound Length",ImGuiDataType_U8,&ins->gb.soundLen,&_ZERO,&_SIXTY_FOUR,ins->gb.soundLen>63?"Infinity":"%d")); rightClickable ImGui::Text("Envelope Direction:"); bool goesUp=ins->gb.envDir; @@ -1152,11 +1152,11 @@ void FurnaceGUI::drawInsEdit() { } ImGui::PopStyleColor(); - P(ImGui::SliderScalar("Attack",ImGuiDataType_U8,&ins->c64.a,&_ZERO,&_FIFTEEN)); - P(ImGui::SliderScalar("Decay",ImGuiDataType_U8,&ins->c64.d,&_ZERO,&_FIFTEEN)); - P(ImGui::SliderScalar("Sustain",ImGuiDataType_U8,&ins->c64.s,&_ZERO,&_FIFTEEN)); - P(ImGui::SliderScalar("Release",ImGuiDataType_U8,&ins->c64.r,&_ZERO,&_FIFTEEN)); - P(ImGui::SliderScalar("Duty",ImGuiDataType_U16,&ins->c64.duty,&_ZERO,&_FOUR_THOUSAND_NINETY_FIVE)); + P(ImGui::SliderScalar("Attack",ImGuiDataType_U8,&ins->c64.a,&_ZERO,&_FIFTEEN)); rightClickable + P(ImGui::SliderScalar("Decay",ImGuiDataType_U8,&ins->c64.d,&_ZERO,&_FIFTEEN)); rightClickable + P(ImGui::SliderScalar("Sustain",ImGuiDataType_U8,&ins->c64.s,&_ZERO,&_FIFTEEN)); rightClickable + P(ImGui::SliderScalar("Release",ImGuiDataType_U8,&ins->c64.r,&_ZERO,&_FIFTEEN)); rightClickable + P(ImGui::SliderScalar("Duty",ImGuiDataType_U16,&ins->c64.duty,&_ZERO,&_FOUR_THOUSAND_NINETY_FIVE)); rightClickable bool ringMod=ins->c64.ringMod; if (ImGui::Checkbox("Ring Modulation",&ringMod)) { PARAMETER @@ -1170,8 +1170,8 @@ void FurnaceGUI::drawInsEdit() { P(ImGui::Checkbox("Enable filter",&ins->c64.toFilter)); P(ImGui::Checkbox("Initialize filter",&ins->c64.initFilter)); - P(ImGui::SliderScalar("Cutoff",ImGuiDataType_U16,&ins->c64.cut,&_ZERO,&_TWO_THOUSAND_FORTY_SEVEN)); - P(ImGui::SliderScalar("Resonance",ImGuiDataType_U8,&ins->c64.res,&_ZERO,&_FIFTEEN)); + P(ImGui::SliderScalar("Cutoff",ImGuiDataType_U16,&ins->c64.cut,&_ZERO,&_TWO_THOUSAND_FORTY_SEVEN)); rightClickable + P(ImGui::SliderScalar("Resonance",ImGuiDataType_U8,&ins->c64.res,&_ZERO,&_FIFTEEN)); rightClickable ImGui::Text("Filter Mode"); ImGui::SameLine(); diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 52faa7b95..136e95122 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -292,7 +292,7 @@ void FurnaceGUI::drawSettings() { if (ImGui::SliderFloat("UI scaling factor",&settings.dpiScale,1.0f,3.0f,"%.2fx")) { if (settings.dpiScale<0.5f) settings.dpiScale=0.5f; if (settings.dpiScale>3.0f) settings.dpiScale=3.0f; - } + } rightClickable } ImGui::Text("Main font"); ImGui::SameLine(); From f85c6913cece809df21522a261437449a2e871fa Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 28 Feb 2022 22:49:03 -0500 Subject: [PATCH 019/637] update doc --- papers/doc/2-interface/components.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/papers/doc/2-interface/components.md b/papers/doc/2-interface/components.md index 8ff73e3fe..0243662b8 100644 --- a/papers/doc/2-interface/components.md +++ b/papers/doc/2-interface/components.md @@ -80,4 +80,4 @@ TODO: image sliders are used for controlling values in a quick manner by being dragged. -alternatively, Ctrl-clicking a slider (Command-click on macOS) will turn it into a number input field for a short period of time, allowing you to input fine values. +alternatively, right-clicking or Ctrl-clicking or a slider (Command-click on macOS) will turn it into a number input field for a short period of time, allowing you to input fine values. From e80de1487e12ea43c7db17876a6d98bd784ebb17 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 28 Feb 2022 23:31:49 -0500 Subject: [PATCH 020/637] i think I got it but there is a leak --- src/engine/platform/opll.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine/platform/opll.cpp b/src/engine/platform/opll.cpp index 0ca5a6c2b..d533d411f 100644 --- a/src/engine/platform/opll.cpp +++ b/src/engine/platform/opll.cpp @@ -76,7 +76,7 @@ const char* DivPlatformOPLL::getEffectName(unsigned char effect) { } const unsigned char cycleMapOPLL[18]={ - 1, 2, 0, 1, 2, 3, 4, 5, 3, 4, 5, 6, 7, 8, 6, 7, 8, 0 + 5, 6, 7, 8, 6, 7, 8, 0, 1, 2, 0, 1, 2, 3, 4, 5, 3, 4 }; void DivPlatformOPLL::acquire_nuked(short* bufL, short* bufR, size_t start, size_t len) { @@ -103,7 +103,7 @@ void DivPlatformOPLL::acquire_nuked(short* bufL, short* bufR, size_t start, size } } - unsigned char nextOut=cycleMapOPLL[(fm.cycles+17)%18]; + unsigned char nextOut=cycleMapOPLL[fm.cycles]; OPLL_Clock(&fm,o); if (!isMuted[nextOut]) { os+=(o[0]+o[1]); From 105aed5a503a02870b5a4bcf0e2fd987f4c11fff Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 1 Mar 2022 00:00:30 -0500 Subject: [PATCH 021/637] OPLL: 94% muting works (mostly) TODO: - FM macros - proper drums --- src/engine/platform/opll.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine/platform/opll.cpp b/src/engine/platform/opll.cpp index d533d411f..a67ff111f 100644 --- a/src/engine/platform/opll.cpp +++ b/src/engine/platform/opll.cpp @@ -76,7 +76,7 @@ const char* DivPlatformOPLL::getEffectName(unsigned char effect) { } const unsigned char cycleMapOPLL[18]={ - 5, 6, 7, 8, 6, 7, 8, 0, 1, 2, 0, 1, 2, 3, 4, 5, 3, 4 + 8, 7, 6, 7, 8, 7, 8, 6, 0, 1, 2, 7, 8, 9, 3, 4, 5, 9 }; void DivPlatformOPLL::acquire_nuked(short* bufL, short* bufR, size_t start, size_t len) { @@ -103,8 +103,8 @@ void DivPlatformOPLL::acquire_nuked(short* bufL, short* bufR, size_t start, size } } - unsigned char nextOut=cycleMapOPLL[fm.cycles]; OPLL_Clock(&fm,o); + unsigned char nextOut=cycleMapOPLL[fm.cycles]; if (!isMuted[nextOut]) { os+=(o[0]+o[1]); } From d9ae033f32b9df3763d8b02821cf1c12695481c3 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 1 Mar 2022 00:56:13 -0500 Subject: [PATCH 022/637] OPLL: 95% FM macros working TODO: - proper drums --- src/engine/macroInt.cpp | 52 ++++++++++++++ src/engine/macroInt.h | 38 +++++++++- src/engine/platform/opll.cpp | 135 ++++++++++++++++++----------------- src/gui/insEdit.cpp | 14 ++-- 4 files changed, 167 insertions(+), 72 deletions(-) diff --git a/src/engine/macroInt.cpp b/src/engine/macroInt.cpp index 8ae31411b..adcb33139 100644 --- a/src/engine/macroInt.cpp +++ b/src/engine/macroInt.cpp @@ -44,6 +44,7 @@ } \ } +// CPU hell void DivMacroInt::next() { if (ins==NULL) return; @@ -79,6 +80,16 @@ void DivMacroInt::next() { doMacro(o.finishedDt,o.hadDt,o.hasDt,o.dt,o.dtPos,m.dtMacro,m.dtMacroLen,m.dtMacroLoop,m.dtMacroRel); doMacro(o.finishedD2r,o.hadD2r,o.hasD2r,o.d2r,o.d2rPos,m.d2rMacro,m.d2rMacroLen,m.d2rMacroLoop,m.d2rMacroRel); doMacro(o.finishedSsg,o.hadSsg,o.hasSsg,o.ssg,o.ssgPos,m.ssgMacro,m.ssgMacroLen,m.ssgMacroLoop,m.ssgMacroRel); + + doMacro(o.finishedDam,o.hadDam,o.hasDam,o.dam,o.damPos,m.damMacro,m.damMacroLen,m.damMacroLoop,m.damMacroRel); + doMacro(o.finishedDvb,o.hadDvb,o.hasDvb,o.dvb,o.dvbPos,m.dvbMacro,m.dvbMacroLen,m.dvbMacroLoop,m.dvbMacroRel); + doMacro(o.finishedEgt,o.hadEgt,o.hasEgt,o.egt,o.egtPos,m.egtMacro,m.egtMacroLen,m.egtMacroLoop,m.egtMacroRel); + doMacro(o.finishedKsl,o.hadKsl,o.hasKsl,o.ksl,o.kslPos,m.kslMacro,m.kslMacroLen,m.kslMacroLoop,m.kslMacroRel); + + doMacro(o.finishedSus,o.hadSus,o.hasSus,o.sus,o.susPos,m.susMacro,m.susMacroLen,m.susMacroLoop,m.susMacroRel); + doMacro(o.finishedVib,o.hadVib,o.hasVib,o.vib,o.vibPos,m.vibMacro,m.vibMacroLen,m.vibMacroLoop,m.vibMacroRel); + doMacro(o.finishedWs,o.hadWs,o.hasWs,o.ws,o.wsPos,m.wsMacro,m.wsMacroLen,m.wsMacroLoop,m.wsMacroRel); + doMacro(o.finishedKsr,o.hadKsr,o.hasKsr,o.ksr,o.ksrPos,m.ksrMacro,m.ksrMacroLen,m.ksrMacroLoop,m.ksrMacroRel); } } @@ -280,6 +291,47 @@ void DivMacroInt::init(DivInstrument* which) { o.hasSsg=true; o.willSsg=true; } + + if (m.damMacroLen>0) { + o.hadDam=true; + o.hasDam=true; + o.willDam=true; + } + if (m.dvbMacroLen>0) { + o.hadDvb=true; + o.hasDvb=true; + o.willDvb=true; + } + if (m.egtMacroLen>0) { + o.hadEgt=true; + o.hasEgt=true; + o.willEgt=true; + } + if (m.kslMacroLen>0) { + o.hadKsl=true; + o.hasKsl=true; + o.willKsl=true; + } + if (m.susMacroLen>0) { + o.hadSus=true; + o.hasSus=true; + o.willSus=true; + } + if (m.vibMacroLen>0) { + o.hadVib=true; + o.hasVib=true; + o.willVib=true; + } + if (m.wsMacroLen>0) { + o.hadWs=true; + o.hasWs=true; + o.willWs=true; + } + if (m.ksrMacroLen>0) { + o.hadKsr=true; + o.hasKsr=true; + o.willKsr=true; + } } } diff --git a/src/engine/macroInt.h b/src/engine/macroInt.h index 60221d063..b107a4732 100644 --- a/src/engine/macroInt.h +++ b/src/engine/macroInt.h @@ -42,26 +42,38 @@ class DivMacroInt { int amPos, arPos, drPos, multPos; int rrPos, slPos, tlPos, dt2Pos; int rsPos, dtPos, d2rPos, ssgPos; + int damPos, dvbPos, egtPos, kslPos; + int susPos, vibPos, wsPos, ksrPos; int am, ar, dr, mult; int rr, sl, tl, dt2; int rs, dt, d2r, ssg; + int dam, dvb, egt, ksl; + int sus, vib, ws, ksr; bool hasAm, hasAr, hasDr, hasMult; bool hasRr, hasSl, hasTl, hasDt2; bool hasRs, hasDt, hasD2r, hasSsg; + bool hasDam, hasDvb, hasEgt, hasKsl; + bool hasSus, hasVib, hasWs, hasKsr; bool hadAm, hadAr, hadDr, hadMult; bool hadRr, hadSl, hadTl, hadDt2; bool hadRs, hadDt, hadD2r, hadSsg; + bool hadDam, hadDvb, hadEgt, hadKsl; + bool hadSus, hadVib, hadWs, hadKsr; bool finishedAm, finishedAr, finishedDr, finishedMult; bool finishedRr, finishedSl, finishedTl, finishedDt2; bool finishedRs, finishedDt, finishedD2r, finishedSsg; + bool finishedDam, finishedDvb, finishedEgt, finishedKsl; + bool finishedSus, finishedVib, finishedWs, finishedKsr; bool willAm, willAr, willDr, willMult; bool willRr, willSl, willTl, willDt2; bool willRs, willDt, willD2r, willSsg; + bool willDam, willDvb, willEgt, willKsl; + bool willSus, willVib, willWs, willKsr; IntOp(): amPos(0), arPos(0), @@ -75,6 +87,14 @@ class DivMacroInt { dtPos(0), d2rPos(0), ssgPos(0), + damPos(0), + dvbPos(0), + egtPos(0), + kslPos(0), + susPos(0), + vibPos(0), + wsPos(0), + ksrPos(0), am(0), ar(0), dr(0), @@ -87,18 +107,34 @@ class DivMacroInt { dt(0), d2r(0), ssg(0), + dam(0), + dvb(0), + egt(0), + ksl(0), + sus(0), + vib(0), + ws(0), + ksr(0), hasAm(false), hasAr(false), hasDr(false), hasMult(false), hasRr(false), hasSl(false), hasTl(false), hasDt2(false), hasRs(false), hasDt(false), hasD2r(false), hasSsg(false), + hasDam(false), hasDvb(false), hasEgt(false), hasKsl(false), + hasSus(false), hasVib(false), hasWs(false), hasKsr(false), hadAm(false), hadAr(false), hadDr(false), hadMult(false), hadRr(false), hadSl(false), hadTl(false), hadDt2(false), hadRs(false), hadDt(false), hadD2r(false), hadSsg(false), + hadDam(false), hadDvb(false), hadEgt(false), hadKsl(false), + hadSus(false), hadVib(false), hadWs(false), hadKsr(false), finishedAm(false), finishedAr(false), finishedDr(false), finishedMult(false), finishedRr(false), finishedSl(false), finishedTl(false), finishedDt2(false), finishedRs(false), finishedDt(false), finishedD2r(false), finishedSsg(false), + finishedDam(false), finishedDvb(false), finishedEgt(false), finishedKsl(false), + finishedSus(false), finishedVib(false), finishedWs(false), finishedKsr(false), willAm(false), willAr(false), willDr(false), willMult(false), willRr(false), willSl(false), willTl(false), willDt2(false), - willRs(false), willDt(false), willD2r(false), willSsg(false) {} + willRs(false), willDt(false), willD2r(false), willSsg(false), + willDam(false), willDvb(false), willEgt(false), willKsl(false), + willSus(false), willVib(false), willWs(false), willKsr(false) {} } op[4]; void release(); void next(); diff --git a/src/engine/platform/opll.cpp b/src/engine/platform/opll.cpp index a67ff111f..405e2327d 100644 --- a/src/engine/platform/opll.cpp +++ b/src/engine/platform/opll.cpp @@ -128,7 +128,7 @@ void DivPlatformOPLL::tick() { chan[i].std.next(); if (chan[i].std.hadVol) { - chan[i].outVol=(chan[i].vol*MIN(127,chan[i].std.vol))/127; + chan[i].outVol=(chan[i].vol*MIN(15,chan[i].std.vol))/15; rWrite(0x30+i,(15-(chan[i].outVol*(15-chan[i].state.op[1].tl))/15)|(chan[i].state.opllPreset<<4)); } @@ -147,77 +147,84 @@ void DivPlatformOPLL::tick() { chan[i].freqChanged=true; } } -/* - if (chan[i].std.hadAlg) { - chan[i].state.alg=chan[i].std.alg; - rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); - } - if (chan[i].std.hadFb) { - chan[i].state.fb=chan[i].std.fb; - } - if (chan[i].std.hadFms) { - chan[i].state.fms=chan[i].std.fms; - } - if (chan[i].std.hadAms) { - chan[i].state.ams=chan[i].std.ams; - } - for (int j=0; j<2; j++) { - unsigned short baseAddr=chanOffs[i]|opOffs[j]; - DivInstrumentFM::Operator& op=chan[i].state.op[j]; - DivMacroInt::IntOp& m=chan[i].std.op[j]; - if (m.hadAm) { - op.am=m.am; - rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + + if (chan[i].state.opllPreset==0) { + if (chan[i].std.hadAlg) { // SUS + chan[i].state.alg=chan[i].std.alg; + chan[i].freqChanged=true; } - if (m.hadAr) { - op.ar=m.ar; - rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + if (chan[i].std.hadFb) { + chan[i].state.fb=chan[i].std.fb; + rWrite(0x03,(chan[i].state.op[0].ksl<<6)|((chan[i].state.fms&1)<<4)|((chan[i].state.ams&1)<<3)|chan[i].state.fb); } - if (m.hadDr) { - op.dr=m.dr; - rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + if (chan[i].std.hadFms) { + chan[i].state.fms=chan[i].std.fms; + rWrite(0x03,(chan[i].state.op[0].ksl<<6)|((chan[i].state.fms&1)<<4)|((chan[i].state.ams&1)<<3)|chan[i].state.fb); } - if (m.hadMult) { - op.mult=m.mult; - rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + if (chan[i].std.hadAms) { + chan[i].state.ams=chan[i].std.ams; + rWrite(0x03,(chan[i].state.op[0].ksl<<6)|((chan[i].state.fms&1)<<4)|((chan[i].state.ams&1)<<3)|chan[i].state.fb); } - if (m.hadRr) { - op.rr=m.rr; - rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); - } - if (m.hadSl) { - op.sl=m.sl; - rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); - } - if (m.hadTl) { - op.tl=127-m.tl; - if (isMuted[i]) { - rWrite(baseAddr+ADDR_TL,127); - } else { - if (isOutput[chan[i].state.alg][j]) { - rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + + for (int j=0; j<2; j++) { + DivInstrumentFM::Operator& op=chan[i].state.op[j]; + DivMacroInt::IntOp& m=chan[i].std.op[j]; + + if (m.hadAm) { + op.am=m.am; + rWrite(0x00+j,(op.am<<7)|(op.vib<<6)|((op.ssgEnv&8)<<2)|(op.ksr<<4)|(op.mult)); + } + if (m.hadAr) { + op.ar=m.ar; + rWrite(0x04+j,(op.ar<<4)|(op.dr)); + } + if (m.hadDr) { + op.dr=m.dr; + rWrite(0x04+j,(op.ar<<4)|(op.dr)); + } + if (m.hadMult) { + op.mult=m.mult; + rWrite(0x00+j,(op.am<<7)|(op.vib<<6)|((op.ssgEnv&8)<<2)|(op.ksr<<4)|(op.mult)); + } + if (m.hadRr) { + op.rr=m.rr; + rWrite(0x06+j,(op.sl<<4)|(op.rr)); + } + if (m.hadSl) { + op.sl=m.sl; + rWrite(0x06+j,(op.sl<<4)|(op.rr)); + } + if (m.hadTl) { + op.tl=((j==1)?15:63)-m.tl; + if (j==1) { + rWrite(0x30+i,(15-(chan[i].outVol*(15-chan[i].state.op[1].tl))/15)|(chan[i].state.opllPreset<<4)); } else { - rWrite(baseAddr+ADDR_TL,op.tl); + rWrite(0x02,(chan[i].state.op[1].ksl<<6)|(op.tl&63)); } } + + if (m.hadEgt) { + op.ssgEnv=(m.egt&1)?8:0; + rWrite(0x00+j,(op.am<<7)|(op.vib<<6)|((op.ssgEnv&8)<<2)|(op.ksr<<4)|(op.mult)); + } + if (m.hadKsl) { + op.ksl=m.ksl; + if (j==1) { + rWrite(0x02,(op.ksl<<6)|(chan[i].state.op[0].tl&63)); + } else { + rWrite(0x03,(chan[i].state.op[0].ksl<<6)|((chan[i].state.fms&1)<<4)|((chan[i].state.ams&1)<<3)|chan[i].state.fb); + } + } + if (m.hadKsr) { + op.ksr=m.ksr; + rWrite(0x00+j,(op.am<<7)|(op.vib<<6)|((op.ssgEnv&8)<<2)|(op.ksr<<4)|(op.mult)); + } + if (m.hadVib) { + op.vib=m.vib; + rWrite(0x00+j,(op.am<<7)|(op.vib<<6)|((op.ssgEnv&8)<<2)|(op.ksr<<4)|(op.mult)); + } } - if (m.hadRs) { - op.rs=m.rs; - rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); - } - if (m.hadDt) { - op.dt=m.dt; - rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); - } - if (m.hadD2r) { - op.d2r=m.d2r; - rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); - } - if (m.hadSsg) { - op.ssgEnv=m.ssg; - rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); - } - }*/ + } if (chan[i].keyOn || chan[i].keyOff) { if (i>=6 && drums) { diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 4608f89b1..408c3c150 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1037,10 +1037,10 @@ void FurnaceGUI::drawInsEdit() { if (ImGui::BeginTabItem("FM Macros")) { MACRO_BEGIN(0); if (ins->type==DIV_INS_OPLL) { - NORMAL_MACRO(ins->std.algMacro,ins->std.algMacroLen,ins->std.algMacroLoop,ins->std.algMacroRel,0,1,"alg",FM_NAME(FM_SUS),16,ins->std.algMacroOpen,true,oneBit,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[0],0,1,NULL,false); + NORMAL_MACRO(ins->std.algMacro,ins->std.algMacroLen,ins->std.algMacroLoop,ins->std.algMacroRel,0,1,"alg",FM_NAME(FM_SUS),32,ins->std.algMacroOpen,true,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[0],0,1,NULL,false); NORMAL_MACRO(ins->std.fbMacro,ins->std.fbMacroLen,ins->std.fbMacroLoop,ins->std.fbMacroRel,0,7,"fb",FM_NAME(FM_FB),96,ins->std.fbMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[1],0,7,NULL,false); - NORMAL_MACRO(ins->std.fmsMacro,ins->std.fmsMacroLen,ins->std.fmsMacroLoop,ins->std.fmsMacroRel,0,1,"fms",FM_NAME(FM_DC),16,ins->std.fmsMacroOpen,true,oneBit,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[2],0,1,NULL,false); - NORMAL_MACRO(ins->std.amsMacro,ins->std.amsMacroLen,ins->std.amsMacroLoop,ins->std.amsMacroRel,0,1,"ams",FM_NAME(FM_DM),16,ins->std.amsMacroOpen,true,oneBit,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[3],0,1,NULL,false); + NORMAL_MACRO(ins->std.fmsMacro,ins->std.fmsMacroLen,ins->std.fmsMacroLoop,ins->std.fmsMacroRel,0,1,"fms",FM_NAME(FM_DC),32,ins->std.fmsMacroOpen,true,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[2],0,1,NULL,false); + NORMAL_MACRO(ins->std.amsMacro,ins->std.amsMacroLen,ins->std.amsMacroLoop,ins->std.amsMacroRel,0,1,"ams",FM_NAME(FM_DM),32,ins->std.amsMacroOpen,true,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[3],0,1,NULL,false); } else { NORMAL_MACRO(ins->std.algMacro,ins->std.algMacroLen,ins->std.algMacroLoop,ins->std.algMacroRel,0,7,"alg",FM_NAME(FM_ALG),96,ins->std.algMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[0],0,7,NULL,false); NORMAL_MACRO(ins->std.fbMacro,ins->std.fbMacroLen,ins->std.fbMacroLoop,ins->std.fbMacroRel,0,7,"fb",FM_NAME(FM_FB),96,ins->std.fbMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[1],0,7,NULL,false); @@ -1082,10 +1082,10 @@ void FurnaceGUI::drawInsEdit() { OP_MACRO(ins->std.opMacros[ordi].kslMacro,ins->std.opMacros[ordi].kslMacroLen,ins->std.opMacros[ordi].kslMacroLoop,ins->std.opMacros[ordi].kslMacroRel,3,ordi,"ksl",FM_NAME(FM_KSL),32,ins->std.opMacros[ordi].kslMacroOpen,false,NULL,mmlString[6]); OP_MACRO(ins->std.opMacros[ordi].multMacro,ins->std.opMacros[ordi].multMacroLen,ins->std.opMacros[ordi].multMacroLoop,ins->std.opMacros[ordi].multMacroRel,15,ordi,"mult",FM_NAME(FM_MULT),64,ins->std.opMacros[ordi].multMacroOpen,false,NULL,mmlString[7]); - OP_MACRO(ins->std.opMacros[ordi].amMacro,ins->std.opMacros[ordi].amMacroLen,ins->std.opMacros[ordi].amMacroLoop,ins->std.opMacros[ordi].amMacroRel,1,ordi,"am",FM_NAME(FM_AM),16,ins->std.opMacros[ordi].amMacroOpen,true,NULL,mmlString[8]); - OP_MACRO(ins->std.opMacros[ordi].vibMacro,ins->std.opMacros[ordi].vibMacroLen,ins->std.opMacros[ordi].vibMacroLoop,ins->std.opMacros[ordi].vibMacroRel,4,ordi,"vib",FM_NAME(FM_VIB),16,ins->std.opMacros[ordi].vibMacroOpen,true,oneBit,mmlString[9]); - OP_MACRO(ins->std.opMacros[ordi].ksrMacro,ins->std.opMacros[ordi].ksrMacroLen,ins->std.opMacros[ordi].ksrMacroLoop,ins->std.opMacros[ordi].ksrMacroRel,4,ordi,"ksr",FM_NAME(FM_KSR),16,ins->std.opMacros[ordi].ksrMacroOpen,true,oneBit,mmlString[10]); - OP_MACRO(ins->std.opMacros[ordi].egtMacro,ins->std.opMacros[ordi].egtMacroLen,ins->std.opMacros[ordi].egtMacroLoop,ins->std.opMacros[ordi].egtMacroRel,4,ordi,"egt",FM_NAME(FM_EGS),16,ins->std.opMacros[ordi].egtMacroOpen,true,oneBit,mmlString[11]); + OP_MACRO(ins->std.opMacros[ordi].amMacro,ins->std.opMacros[ordi].amMacroLen,ins->std.opMacros[ordi].amMacroLoop,ins->std.opMacros[ordi].amMacroRel,1,ordi,"am",FM_NAME(FM_AM),32,ins->std.opMacros[ordi].amMacroOpen,true,NULL,mmlString[8]); + OP_MACRO(ins->std.opMacros[ordi].vibMacro,ins->std.opMacros[ordi].vibMacroLen,ins->std.opMacros[ordi].vibMacroLoop,ins->std.opMacros[ordi].vibMacroRel,1,ordi,"vib",FM_NAME(FM_VIB),32,ins->std.opMacros[ordi].vibMacroOpen,true,NULL,mmlString[9]); + OP_MACRO(ins->std.opMacros[ordi].ksrMacro,ins->std.opMacros[ordi].ksrMacroLen,ins->std.opMacros[ordi].ksrMacroLoop,ins->std.opMacros[ordi].ksrMacroRel,1,ordi,"ksr",FM_NAME(FM_KSR),32,ins->std.opMacros[ordi].ksrMacroOpen,true,NULL,mmlString[10]); + OP_MACRO(ins->std.opMacros[ordi].egtMacro,ins->std.opMacros[ordi].egtMacroLen,ins->std.opMacros[ordi].egtMacroLoop,ins->std.opMacros[ordi].egtMacroRel,1,ordi,"egt",FM_NAME(FM_EGS),32,ins->std.opMacros[ordi].egtMacroOpen,true,NULL,mmlString[11]); } else { OP_MACRO(ins->std.opMacros[ordi].tlMacro,ins->std.opMacros[ordi].tlMacroLen,ins->std.opMacros[ordi].tlMacroLoop,ins->std.opMacros[ordi].tlMacroRel,maxTl,ordi,"tl",FM_NAME(FM_TL),128,ins->std.opMacros[ordi].tlMacroOpen,false,NULL,mmlString[0]); OP_MACRO(ins->std.opMacros[ordi].arMacro,ins->std.opMacros[ordi].arMacroLen,ins->std.opMacros[ordi].arMacroLoop,ins->std.opMacros[ordi].arMacroRel,maxArDr,ordi,"ar",FM_NAME(FM_AR),64,ins->std.opMacros[ordi].arMacroOpen,false,NULL,mmlString[1]); From 2605e8670ce426efa17ce6772a49b3a324c77081 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 1 Mar 2022 01:00:33 -0500 Subject: [PATCH 023/637] OPLL: small order change fix --- src/engine/platform/opll.cpp | 4 +++- src/engine/platform/opll.h | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/engine/platform/opll.cpp b/src/engine/platform/opll.cpp index 405e2327d..03db96c23 100644 --- a/src/engine/platform/opll.cpp +++ b/src/engine/platform/opll.cpp @@ -358,6 +358,7 @@ int DivPlatformOPLL::dispatch(DivCommand c) { rWrite(0x05,(car.ar<<4)|(car.dr)); rWrite(0x06,(mod.sl<<4)|(mod.rr)); rWrite(0x07,(car.sl<<4)|(car.rr)); + lastCustomMemory=c.chan; } if (chan[c.chan].state.opllPreset==16) { // compatible drums mode if (c.chan>=6) { @@ -564,7 +565,7 @@ int DivPlatformOPLL::dispatch(DivCommand c) { void DivPlatformOPLL::forceIns() { for (int i=0; i<9; i++) { // update custom preset - if (chan[i].state.opllPreset==0) { + if (chan[i].state.opllPreset==0 && i==lastCustomMemory) { DivInstrumentFM::Operator& mod=chan[i].state.op[0]; DivInstrumentFM::Operator& car=chan[i].state.op[1]; rWrite(0x00,(mod.am<<7)|(mod.vib<<6)|((mod.ssgEnv&8)<<2)|(mod.ksr<<4)|(mod.mult)); @@ -645,6 +646,7 @@ void DivPlatformOPLL::reset() { lastBusy=60; drumState=0; + lastCustomMemory=-1; drumVol[0]=0; drumVol[1]=0; diff --git a/src/engine/platform/opll.h b/src/engine/platform/opll.h index 87e970579..0bada9036 100644 --- a/src/engine/platform/opll.h +++ b/src/engine/platform/opll.h @@ -67,7 +67,7 @@ class DivPlatformOPLL: public DivDispatch { }; std::queue writes; opll_t fm; - int delay; + int delay, lastCustomMemory; unsigned char lastBusy; unsigned char drumState; unsigned char drumVol[5]; From ee5b24dcaf180890f90c331826a5f0fe3924b980 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 1 Mar 2022 02:38:10 -0500 Subject: [PATCH 024/637] describe FM param structure --- src/engine/instrument.h | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/engine/instrument.h b/src/engine/instrument.h index d5dbc8a63..aef84f202 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -50,11 +50,26 @@ enum DivInstrumentType { DIV_INS_MIKEY=23, }; +// FM operator structure: +// - OPN: +// - AM, AR, DR, MULT, RR, SL, TL, RS, DT, D2R, SSG-EG +// - OPM: +// - AM, AR, DR, MULT, RR, SL, TL, DT2, RS, DT, D2R +// - OPLL: +// - AM, AR, DR, MULT, RR, SL, TL, SSG-EG&8 = EG-S +// - KSL, VIB, KSR +// - OPL: +// - AM, AR, DR, MULT, RR, SL, TL, SSG-EG&8 = EG-S +// - KSL, VIB, WS (OPL2/3), KSR +// - OPZ: NOT FINAL! +// - AM, AR, DR, MULT (CRS), RR, SL, TL, DT2, RS, DT, D2R +// - KSL = LS, WS, DVB = MULT (FINE), DAM = REV, EGT = EGShift + struct DivInstrumentFM { unsigned char alg, fb, fms, ams, ops, opllPreset; struct Operator { unsigned char am, ar, dr, mult, rr, sl, tl, dt2, rs, dt, d2r, ssgEnv; - unsigned char dam, dvb, egt, ksl, sus, vib, ws, ksr; // YMU759/OPL + unsigned char dam, dvb, egt, ksl, sus, vib, ws, ksr; // YMU759/OPL/OPZ Operator(): am(0), ar(0), From 4ff056c64f7a4923186d53d2f3b092d2e723f081 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 1 Mar 2022 02:38:19 -0500 Subject: [PATCH 025/637] NES: how is this not initialized?! --- src/engine/platform/sound/nes/apu.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/engine/platform/sound/nes/apu.c b/src/engine/platform/sound/nes/apu.c index a278cbf74..70eddc8e7 100644 --- a/src/engine/platform/sound/nes/apu.c +++ b/src/engine/platform/sound/nes/apu.c @@ -226,4 +226,6 @@ void apu_turn_on(struct NESAPU* a, BYTE apu_type) { a->DMC.length = 1; a->DMC.address_start = 0xC000; a->apu.odd_cycle = 0; + // come non viene inizializzato? Vorrei qualche spiegazione... + a->r4011.frames = 0; } From 236ab5b3feb5fd59368efb93a9b152c7500fd5d8 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 1 Mar 2022 02:38:37 -0500 Subject: [PATCH 026/637] QSound: initialize memory to 0 for some reason this fixes some glitches? --- src/engine/engine.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 87fac32dc..ad0548399 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -501,6 +501,7 @@ void DivEngine::renderSamples() { // step 4: allocate qsound pcm samples if (qsoundMem==NULL) qsoundMem=new unsigned char[16777216]; + memset(qsoundMem,0,16777216); memPos=0; for (int i=0; i Date: Tue, 1 Mar 2022 04:33:02 -0500 Subject: [PATCH 027/637] prepare for OPLL proper drums mode --- src/gui/gui.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index b002afb9c..7c3aec199 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4496,6 +4496,7 @@ bool FurnaceGUI::loop() { sysAddOption(DIV_SYSTEM_AY8910); sysAddOption(DIV_SYSTEM_AMIGA); sysAddOption(DIV_SYSTEM_OPLL); + sysAddOption(DIV_SYSTEM_OPLL_DRUMS); sysAddOption(DIV_SYSTEM_VRC7); sysAddOption(DIV_SYSTEM_TIA); sysAddOption(DIV_SYSTEM_SAA1099); @@ -4766,6 +4767,7 @@ bool FurnaceGUI::loop() { sysChangeOption(i,DIV_SYSTEM_AY8910); sysChangeOption(i,DIV_SYSTEM_AMIGA); sysChangeOption(i,DIV_SYSTEM_OPLL); + sysChangeOption(i,DIV_SYSTEM_OPLL_DRUMS); sysChangeOption(i,DIV_SYSTEM_VRC7); sysChangeOption(i,DIV_SYSTEM_TIA); sysChangeOption(i,DIV_SYSTEM_SAA1099); From 6dfa208a367e85566ca7fd1dfc40fcfef560ea11 Mon Sep 17 00:00:00 2001 From: freq-mod <32672779+freq-mod@users.noreply.github.com> Date: Tue, 1 Mar 2022 12:07:38 +0100 Subject: [PATCH 028/637] remove "standalone" from YM2612 and YM2151 given the way it's done, it's not needed anymore i guess --- src/engine/sysDef.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 54589e37d..689af53cf 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -673,9 +673,9 @@ const char* DivEngine::getSystemChips(DivSystem sys) { case DIV_SYSTEM_AMIGA: return "MOS 8364 Paula"; case DIV_SYSTEM_YM2151: - return "Yamaha YM2151 standalone"; + return "Yamaha YM2151"; case DIV_SYSTEM_YM2612: - return "Yamaha YM2612 standalone"; + return "Yamaha YM2612"; case DIV_SYSTEM_TIA: return "Atari TIA"; case DIV_SYSTEM_VIC20: From 7dae9058d70f7d4da9516d405eaaa1d88bfcf473 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 1 Mar 2022 14:16:40 -0500 Subject: [PATCH 029/637] OPLL: 97% proper drums mode kinda works --- src/engine/platform/opll.cpp | 61 +++++++++++++++++++++++++++--------- 1 file changed, 46 insertions(+), 15 deletions(-) diff --git a/src/engine/platform/opll.cpp b/src/engine/platform/opll.cpp index 03db96c23..f8b542909 100644 --- a/src/engine/platform/opll.cpp +++ b/src/engine/platform/opll.cpp @@ -79,6 +79,10 @@ const unsigned char cycleMapOPLL[18]={ 8, 7, 6, 7, 8, 7, 8, 6, 0, 1, 2, 7, 8, 9, 3, 4, 5, 9 }; +const unsigned char drumSlot[11]={ + 0, 0, 0, 0, 0, 0, 6, 7, 7, 8, 8 +}; + void DivPlatformOPLL::acquire_nuked(short* bufL, short* bufR, size_t start, size_t len) { static int o[2]; static int os; @@ -124,12 +128,12 @@ void DivPlatformOPLL::acquire(short* bufL, short* bufR, size_t start, size_t len } void DivPlatformOPLL::tick() { - for (int i=0; i<9; i++) { + for (int i=0; i<11; i++) { chan[i].std.next(); if (chan[i].std.hadVol) { chan[i].outVol=(chan[i].vol*MIN(15,chan[i].std.vol))/15; - rWrite(0x30+i,(15-(chan[i].outVol*(15-chan[i].state.op[1].tl))/15)|(chan[i].state.opllPreset<<4)); + rWrite(0x30+i,((15-(chan[i].outVol*(15-chan[i].state.op[1].tl))/15)&15)|(chan[i].state.opllPreset<<4)); } if (chan[i].std.hadArp) { @@ -197,7 +201,7 @@ void DivPlatformOPLL::tick() { if (m.hadTl) { op.tl=((j==1)?15:63)-m.tl; if (j==1) { - rWrite(0x30+i,(15-(chan[i].outVol*(15-chan[i].state.op[1].tl))/15)|(chan[i].state.opllPreset<<4)); + rWrite(0x30+i,((15-(chan[i].outVol*(15-chan[i].state.op[1].tl))/15)&15)|(chan[i].state.opllPreset<<4)); } else { rWrite(0x02,(chan[i].state.op[1].ksl<<6)|(op.tl&63)); } @@ -227,7 +231,10 @@ void DivPlatformOPLL::tick() { } if (chan[i].keyOn || chan[i].keyOff) { - if (i>=6 && drums) { + if (i>=6 && properDrums) { + drumState&=~(0x10>>(i-6)); + immWrite(0x0e,0x20|drumState); + } else if (i>=6 && drums) { drumState&=~(0x10>>(chan[i].note%12)); immWrite(0x0e,0x20|drumState); } else { @@ -245,18 +252,25 @@ void DivPlatformOPLL::tick() { } } - for (int i=0; i<9; i++) { + for (int i=0; i<11; i++) { if (chan[i].freqChanged) { chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq)); if (chan[i].freq>262143) chan[i].freq=262143; int freqt=toFreq(chan[i].freq); chan[i].freqH=freqt>>8; chan[i].freqL=freqt&0xff; - if (i<6 || !drums) { + if (i>=6 && properDrums) { + immWrite(0x10+drumSlot[i],freqt&0xff); + immWrite(0x20+drumSlot[i],freqt>>8); + } else if (i<6 || !drums) { immWrite(0x10+i,freqt&0xff); } } - if (chan[i].keyOn && i>=6 && drums) { + if (chan[i].keyOn && i>=6 && properDrums) { + drumState|=(0x10>>(i-6)); + immWrite(0x0e,0x20|drumState); + chan[i].keyOn=false; + } else if (chan[i].keyOn && i>=6 && drums) { //printf("%d\n",chan[i].note%12); drumState|=(0x10>>(chan[i].note%12)); immWrite(0x0e,0x20|drumState); @@ -333,6 +347,7 @@ void DivPlatformOPLL::muteChannel(int ch, bool mute) { } int DivPlatformOPLL::dispatch(DivCommand c) { + if (c.chan>=9 && !properDrums) return 0; switch (c.cmd) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(chan[c.chan].ins); @@ -344,6 +359,18 @@ int DivPlatformOPLL::dispatch(DivCommand c) { if (!chan[c.chan].std.willVol) { chan[c.chan].outVol=chan[c.chan].vol; } + + if (c.chan>=6 && properDrums) { // drums mode + chan[c.chan].insChanged=false; + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); + chan[c.chan].note=c.value; + chan[c.chan].freqChanged=true; + } + chan[c.chan].keyOn=true; + chan[c.chan].active=true; + break; + } if (chan[c.chan].insChanged) { // update custom preset @@ -379,7 +406,7 @@ int DivPlatformOPLL::dispatch(DivCommand c) { drums=false; immWrite(0x0e,0); } - rWrite(0x30+c.chan,(15-(chan[c.chan].outVol*(15-chan[c.chan].state.op[1].tl))/15)|(chan[c.chan].state.opllPreset<<4)); + rWrite(0x30+c.chan,((15-(chan[c.chan].outVol*(15-chan[c.chan].state.op[1].tl))/15)&15)|(chan[c.chan].state.opllPreset<<4)); } } @@ -437,7 +464,7 @@ int DivPlatformOPLL::dispatch(DivCommand c) { chan[c.chan].outVol=c.value; } if (c.chan<6 || !drums) { - rWrite(0x30+c.chan,(15-(chan[c.chan].outVol*(15-chan[c.chan].state.op[1].tl))/15)|(chan[c.chan].state.opllPreset<<4)); + rWrite(0x30+c.chan,((15-(chan[c.chan].outVol*(15-chan[c.chan].state.op[1].tl))/15)&15)|(chan[c.chan].state.opllPreset<<4)); } break; } @@ -510,7 +537,7 @@ int DivPlatformOPLL::dispatch(DivCommand c) { } else { DivInstrumentFM::Operator& car=chan[c.chan].state.op[1]; car.mult=c.value2&15; - rWrite(0x30+c.chan,(15-(chan[c.chan].outVol*(15-chan[c.chan].state.op[1].tl))/15)|(chan[c.chan].state.opllPreset<<4)); + rWrite(0x01,(car.am<<7)|(car.vib<<6)|((car.ssgEnv&8)<<2)|(car.ksr<<4)|(car.mult)); } break; } @@ -523,7 +550,7 @@ int DivPlatformOPLL::dispatch(DivCommand c) { } else { DivInstrumentFM::Operator& car=chan[c.chan].state.op[1]; car.tl=c.value2&15; - rWrite(0x01,(car.am<<7)|(car.vib<<6)|((car.ssgEnv&8)<<2)|(car.ksr<<4)|(car.mult)); + rWrite(0x30+c.chan,((15-(chan[c.chan].outVol*(15-chan[c.chan].state.op[1].tl))/15)&15)|(chan[c.chan].state.opllPreset<<4)); } break; } @@ -577,7 +604,7 @@ void DivPlatformOPLL::forceIns() { rWrite(0x06,(mod.sl<<4)|(mod.rr)); rWrite(0x07,(car.sl<<4)|(car.rr)); } - rWrite(0x30+i,(15-(chan[i].outVol*(15-chan[i].state.op[1].tl))/15)|(chan[i].state.opllPreset<<4)); + rWrite(0x30+i,((15-(chan[i].outVol*(15-chan[i].state.op[1].tl))/15)&15)|(chan[i].state.opllPreset<<4)); if (chan[i].active) { chan[i].keyOn=true; chan[i].freqChanged=true; @@ -633,7 +660,7 @@ void DivPlatformOPLL::reset() { if (dumpWrites) { addWrite(0xffffffff,0); } - for (int i=0; i<9; i++) { + for (int i=0; i<11; i++) { chan[i]=DivPlatformOPLL::Channel(); chan[i].vol=15; chan[i].outVol=15; @@ -655,6 +682,10 @@ void DivPlatformOPLL::reset() { drumVol[4]=0; delay=0; + + if (properDrums) { + immWrite(0x0e,0x20); + } } bool DivPlatformOPLL::keyOffAffectsArp(int ch) { @@ -666,7 +697,7 @@ bool DivPlatformOPLL::keyOffAffectsPorta(int ch) { } void DivPlatformOPLL::notifyInsChange(int ins) { - for (int i=0; i<9; i++) { + for (int i=0; i<11; i++) { if (chan[i].ins==ins) { chan[i].insChanged=true; } @@ -709,7 +740,7 @@ int DivPlatformOPLL::init(DivEngine* p, int channels, int sugRate, unsigned int parent=p; dumpWrites=false; skipRegisterWrites=false; - for (int i=0; i<9; i++) { + for (int i=0; i<11; i++) { isMuted[i]=false; } setFlags(flags); From 7f3460bfcd55542c7c6cbad680cb2f4c32524de3 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 1 Mar 2022 14:20:29 -0500 Subject: [PATCH 030/637] i don't get it --- src/engine/platform/opll.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/opll.cpp b/src/engine/platform/opll.cpp index f8b542909..078cc393b 100644 --- a/src/engine/platform/opll.cpp +++ b/src/engine/platform/opll.cpp @@ -80,7 +80,7 @@ const unsigned char cycleMapOPLL[18]={ }; const unsigned char drumSlot[11]={ - 0, 0, 0, 0, 0, 0, 6, 7, 7, 8, 8 + 0, 0, 0, 0, 0, 0, 6, 7, 8, 8, 7 }; void DivPlatformOPLL::acquire_nuked(short* bufL, short* bufR, size_t start, size_t len) { From fa5e2bc439472ff948c708870e845e16edce2758 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 1 Mar 2022 17:19:52 -0500 Subject: [PATCH 031/637] TODO: revamp new song system picker --- src/engine/engine.cpp | 19 ++++++- src/engine/engine.h | 2 +- src/gui/gui.cpp | 126 +++++++++++++++++++++++++++++++++--------- src/gui/gui.h | 21 +++++++ 4 files changed, 139 insertions(+), 29 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index ad0548399..0c3621f81 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -19,6 +19,7 @@ #include "dataErrors.h" #include "song.h" +#include #define _USE_MATH_DEFINES #include "engine.h" #include "instrument.h" @@ -533,13 +534,25 @@ void DivEngine::renderSamples() { qsoundMemLen=memPos+256; } -void DivEngine::createNew() { - DivSystem sys=song.system[0]; +void DivEngine::createNew(int* description) { quitDispatch(); isBusy.lock(); song.unload(); song=DivSong(); - song.system[0]=sys; + if (description!=NULL) { + if (description[0]!=0) { + int index=0; + for (int i=0; description[i]; i+=4) { + song.system[index]=(DivSystem)description[i]; + song.systemVol[index]=description[i+1]; + song.systemPan[index]=description[i+2]; + song.systemFlags[index]=description[i+3]; + index++; + if (index>=32) break; + } + song.systemLen=index; + } + } recalcChans(); renderSamples(); isBusy.unlock(); diff --git a/src/engine/engine.h b/src/engine/engine.h index 2f060f14f..02eed74ed 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -262,7 +262,7 @@ class DivEngine { DivWavetable* getWave(int index); DivSample* getSample(int index); // start fresh - void createNew(); + void createNew(int* description); // load a file. bool load(unsigned char* f, size_t length); // save as .dmf. diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 7c3aec199..bec846c83 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1898,6 +1898,73 @@ void FurnaceGUI::drawDebug() { ImGui::End(); } +#define NEWSONG_CATEGORY(x) \ + if (ImGui::Selectable(x,false,ImGuiSelectableFlags_DontClosePopups)) { \ + printf("selected a category\n"); \ + } + +void FurnaceGUI::drawNewSong() { + bool accepted=false; + + ImGui::PushFont(bigFont); + ImGui::SetCursorPosX((ImGui::GetContentRegionAvail().x-ImGui::CalcTextSize("Choose a System!").x)*0.5); + ImGui::Text("Choose a System!"); + ImGui::PopFont(); + + if (ImGui::BeginTable("sysPicker",2,ImGuiTableFlags_Borders)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,0.0f); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.0f); + + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + ImGui::TableNextColumn(); + ImGui::Text("Categories"); + ImGui::TableNextColumn(); + ImGui::Text("Systems"); + + ImGui::TableNextRow(); + + // CATEGORIES + ImGui::TableNextColumn(); + NEWSONG_CATEGORY("All chips"); + NEWSONG_CATEGORY("FM"); + NEWSONG_CATEGORY("Square"); + NEWSONG_CATEGORY("Sample"); + NEWSONG_CATEGORY("Wavetable"); + NEWSONG_CATEGORY("Other/Special"); + NEWSONG_CATEGORY("Game consoles"); + NEWSONG_CATEGORY("Computers"); + NEWSONG_CATEGORY("Arcade systems"); + NEWSONG_CATEGORY("DefleMask-compatible"); + + // SYSTEMS + ImGui::TableNextColumn(); + if (ImGui::BeginTable("Systems",1,ImGuiTableFlags_BordersInnerV|ImGuiTableFlags_ScrollY)) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Selectable("System system",false,ImGuiSelectableFlags_DontClosePopups); + ImGui::EndTable(); + } + + ImGui::EndTable(); + } + + if (ImGui::Button("Go ahead") || accepted) { + e->createNew(nextDesc); + undoHist.clear(); + redoHist.clear(); + curFileName=""; + modified=false; + curNibble=false; + orderNibble=false; + orderCursor=-1; + selStart=SelectionPoint(); + selEnd=SelectionPoint(); + cursor=SelectionPoint(); + updateWindowTitle(); + ImGui::CloseCurrentPopup(); + } +} + void FurnaceGUI::drawStats() { if (nextWindow==GUI_WINDOW_STATS) { statsOpen=true; @@ -4391,22 +4458,11 @@ bool FurnaceGUI::loop() { ImGui::BeginMainMenuBar(); if (ImGui::BeginMenu("file")) { - if (ImGui::MenuItem("new")) { + if (ImGui::MenuItem("new...")) { if (modified) { showWarning("Unsaved changes! Are you sure?",GUI_WARN_NEW); } else { - e->createNew(); - undoHist.clear(); - redoHist.clear(); - curFileName=""; - modified=false; - curNibble=false; - orderNibble=false; - orderCursor=-1; - selStart=SelectionPoint(); - selEnd=SelectionPoint(); - cursor=SelectionPoint(); - updateWindowTitle(); + displayNew=true; } } if (ImGui::MenuItem("open...",BIND_FOR(GUI_ACTION_OPEN))) { @@ -5107,6 +5163,11 @@ bool FurnaceGUI::loop() { ImGui::OpenPopup("Rendering..."); } + if (displayNew) { + displayNew=false; + ImGui::OpenPopup("New Song"); + } + if (nextWindow==GUI_WINDOW_ABOUT) { aboutOpen=true; nextWindow=GUI_WINDOW_NOTHING; @@ -5126,6 +5187,13 @@ bool FurnaceGUI::loop() { ImGui::EndPopup(); } + ImGui::SetNextWindowSizeConstraints(ImVec2(400.0f*dpiScale,200.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); + if (ImGui::BeginPopupModal("New Song",NULL)) { + ImGui::SetWindowPos(ImVec2(((scrW*dpiScale)-ImGui::GetWindowSize().x)*0.5,((scrH*dpiScale)-ImGui::GetWindowSize().y)*0.5)); + drawNewSong(); + ImGui::EndPopup(); + } + if (ImGui::BeginPopupModal("Error",NULL,ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::Text("%s",errorString.c_str()); if (ImGui::Button("OK")) { @@ -5143,18 +5211,7 @@ bool FurnaceGUI::loop() { quit=true; break; case GUI_WARN_NEW: - e->createNew(); - undoHist.clear(); - redoHist.clear(); - curFileName=""; - modified=false; - curNibble=false; - orderNibble=false; - orderCursor=-1; - selStart=SelectionPoint(); - selEnd=SelectionPoint(); - cursor=SelectionPoint(); - updateWindowTitle(); + displayNew=true; break; case GUI_WARN_OPEN: openFileDialog(GUI_FILE_OPEN); @@ -5743,6 +5800,7 @@ FurnaceGUI::FurnaceGUI(): displayError(false), displayExporting(false), vgmExportLoop(true), + displayNew(false), curFileDialog(GUI_FILE_OPEN), warnAction(GUI_WARN_OPEN), scrW(1280), @@ -5804,6 +5862,7 @@ FurnaceGUI::FurnaceGUI(): wantPatName(false), curWindow(GUI_WINDOW_NOTHING), nextWindow(GUI_WINDOW_NOTHING), + nextDesc(NULL), wavePreviewOn(false), wavePreviewKey((SDL_Scancode)0), wavePreviewNote(0), @@ -5923,6 +5982,23 @@ FurnaceGUI::FurnaceGUI(): valueKeys[SDLK_KP_7]=7; valueKeys[SDLK_KP_8]=8; valueKeys[SDLK_KP_9]=9; + + /* +const char* sysCategories[]={ + "All chips", + "FM", + "Square", + "Sample", + "Wavetable", + "Other/Special", + "Game consoles", + "Computers", + "Arcade systems", + "DefleMask-compatible" +}; +*/ + + sysCategories.push_back(FurnaceGUISysCategory("All chips")); memset(willExport,1,32*sizeof(bool)); diff --git a/src/gui/gui.h b/src/gui/gui.h index 291db6d8d..c59705961 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -402,6 +402,22 @@ struct Particle { lifeSpeed(lS) {} }; +struct FurnaceGUISysDef { + const char* name; + const int* definition; + FurnaceGUISysDef(const char* n, const int* def): + name(n), definition(def) {} +}; + +struct FurnaceGUISysCategory { + const char* name; + std::vector systems; + FurnaceGUISysCategory(const char* n): + name(n) {} + FurnaceGUISysCategory(): + name(NULL) {} +}; + class FurnaceGUI { DivEngine* e; @@ -413,6 +429,7 @@ class FurnaceGUI { String mmlStringW; bool quit, warnQuit, willCommit, edit, modified, displayError, displayExporting, vgmExportLoop; + bool displayNew; bool willExport[32]; FurnaceGUIFileDialogs curFileDialog; @@ -533,6 +550,7 @@ class FurnaceGUI { float peak[2]; float patChanX[DIV_MAX_CHANS+1]; float patChanSlideY[DIV_MAX_CHANS+1]; + int* nextDesc; // bit 31: ctrl // bit 30: reserved for SDL scancode mask @@ -563,6 +581,8 @@ class FurnaceGUI { std::vector cmdStream; std::vector particles; + std::vector sysCategories; + bool wavePreviewOn; SDL_Scancode wavePreviewKey; int wavePreviewNote; @@ -657,6 +677,7 @@ class FurnaceGUI { void drawAbout(); void drawSettings(); void drawDebug(); + void drawNewSong(); void parseKeybinds(); void promptKey(int which); From 72178f6ce9d2833924d64c22b6e64397e179fbe8 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 1 Mar 2022 17:25:01 -0500 Subject: [PATCH 032/637] TODO: still to-do --- src/gui/gui.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index bec846c83..08e73e0d4 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -5998,7 +5998,16 @@ const char* sysCategories[]={ }; */ - sysCategories.push_back(FurnaceGUISysCategory("All chips")); + FurnaceGUISysCategory cat; + + cat=FurnaceGUISysCategory("All chips"); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha YM2612", NULL /*{ + DIV_SYSTEM_YM2612, 64, 0, 0, + 0 + }*/ + )); + sysCategories.push_back(cat); memset(willExport,1,32*sizeof(bool)); From 066769379b19e82afe3d9bf65dccf736afc2a3c1 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 1 Mar 2022 20:58:27 -0500 Subject: [PATCH 033/637] update format.md a bit --- papers/format.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/papers/format.md b/papers/format.md index 1ca356b72..c7702ae2b 100644 --- a/papers/format.md +++ b/papers/format.md @@ -29,6 +29,7 @@ furthermore, an `or reserved` indicates this field is always present, but is res the format versions are: +- 61: Furnace dev61 - 60: Furnace dev60 - 59: Furnace dev59 - 58: Furnace dev58 @@ -174,7 +175,7 @@ size | description | - signed char, 64=1.0, 127=~2.0 32 | sound chip panning | - signed char, -128=left, 127=right - 128 | sound chip parameters (TODO) + 128 | sound chip parameters STR | song name STR | song author 4f | A-4 tuning From 1e5d7a623a7601dc890651577b17b94afe1548ac Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 1 Mar 2022 20:59:38 -0500 Subject: [PATCH 034/637] GUI: report why does window creation fail --- src/gui/gui.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 08e73e0d4..50f45c1eb 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -5650,7 +5650,7 @@ bool FurnaceGUI::init() { sdlWin=SDL_CreateWindow("Furnace",SDL_WINDOWPOS_CENTERED,SDL_WINDOWPOS_CENTERED,scrW*dpiScale,scrH*dpiScale,SDL_WINDOW_RESIZABLE|SDL_WINDOW_ALLOW_HIGHDPI); if (sdlWin==NULL) { - logE("could not open window!\n"); + logE("could not open window! %s\n",SDL_GetError()); return false; } From ceb1cd0181050e52bf58203d2f728a633ab6742d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 1 Mar 2022 22:10:03 -0500 Subject: [PATCH 035/637] OPLL: more drum fixes! --- src/engine/platform/opll.cpp | 39 ++++++++++++++++++++++++++++++++++-- src/engine/platform/opll.h | 2 +- src/engine/playback.cpp | 11 ++++++++-- 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/src/engine/platform/opll.cpp b/src/engine/platform/opll.cpp index 078cc393b..489bea5dd 100644 --- a/src/engine/platform/opll.cpp +++ b/src/engine/platform/opll.cpp @@ -275,7 +275,7 @@ void DivPlatformOPLL::tick() { drumState|=(0x10>>(chan[i].note%12)); immWrite(0x0e,0x20|drumState); chan[i].keyOn=false; - } else if (chan[i].keyOn || chan[i].freqChanged) { + } else if ((chan[i].keyOn || chan[i].freqChanged) && i<9) { //immWrite(0x28,0xf0|konOffs[i]); immWrite(0x20+i,(chan[i].freqH)|(chan[i].active<<4)|(chan[i].state.alg?0x20:0)); chan[i].keyOn=false; @@ -347,9 +347,9 @@ void DivPlatformOPLL::muteChannel(int ch, bool mute) { } int DivPlatformOPLL::dispatch(DivCommand c) { - if (c.chan>=9 && !properDrums) return 0; switch (c.cmd) { case DIV_CMD_NOTE_ON: { + if (c.chan>=9 && !properDrums) return 0; DivInstrument* ins=parent->getIns(chan[c.chan].ins); if (chan[c.chan].insChanged) { chan[c.chan].state=ins->fm; @@ -445,24 +445,35 @@ int DivPlatformOPLL::dispatch(DivCommand c) { break; } case DIV_CMD_NOTE_OFF: + if (c.chan>=9 && !properDrums) return 0; chan[c.chan].keyOff=true; chan[c.chan].keyOn=false; chan[c.chan].active=false; break; case DIV_CMD_NOTE_OFF_ENV: + if (c.chan>=9 && !properDrums) return 0; chan[c.chan].keyOff=true; chan[c.chan].keyOn=false; chan[c.chan].active=false; chan[c.chan].std.release(); break; case DIV_CMD_ENV_RELEASE: + if (c.chan>=9 && !properDrums) return 0; chan[c.chan].std.release(); break; case DIV_CMD_VOLUME: { + if (c.chan>=9 && !properDrums) return 0; chan[c.chan].vol=c.value; if (!chan[c.chan].std.hasVol) { chan[c.chan].outVol=c.value; } + if (c.chan>=6 || properDrums) { + drumVol[c.chan-6]=15-chan[c.chan].outVol; + rWrite(0x36,drumVol[0]); + rWrite(0x37,drumVol[1]|(drumVol[4]<<4)); + rWrite(0x38,drumVol[3]|(drumVol[2]<<4)); + break; + } if (c.chan<6 || !drums) { rWrite(0x30+c.chan,((15-(chan[c.chan].outVol*(15-chan[c.chan].state.op[1].tl))/15)&15)|(chan[c.chan].state.opllPreset<<4)); } @@ -479,11 +490,13 @@ int DivPlatformOPLL::dispatch(DivCommand c) { chan[c.chan].ins=c.value; break; case DIV_CMD_PITCH: { + if (c.chan>=9 && !properDrums) return 0; chan[c.chan].pitch=c.value; chan[c.chan].freqChanged=true; break; } case DIV_CMD_NOTE_PORTA: { + if (c.chan>=9 && !properDrums) return 0; int destFreq=NOTE_FREQUENCY(c.value2); int newFreq; bool return2=false; @@ -516,12 +529,14 @@ int DivPlatformOPLL::dispatch(DivCommand c) { break; } case DIV_CMD_LEGATO: { + if (c.chan>=9 && !properDrums) return 0; chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); chan[c.chan].note=c.value; chan[c.chan].freqChanged=true; break; } case DIV_CMD_FM_FB: { + if (c.chan>=9 && !properDrums) return 0; DivInstrumentFM::Operator& mod=chan[c.chan].state.op[0]; //DivInstrumentFM::Operator& car=chan[c.chan].state.op[1]; chan[c.chan].state.fb=c.value&7; @@ -530,6 +545,7 @@ int DivPlatformOPLL::dispatch(DivCommand c) { } case DIV_CMD_FM_MULT: { + if (c.chan>=9 && !properDrums) return 0; if (c.value==0) { DivInstrumentFM::Operator& mod=chan[c.chan].state.op[0]; mod.mult=c.value2&15; @@ -542,6 +558,7 @@ int DivPlatformOPLL::dispatch(DivCommand c) { break; } case DIV_CMD_FM_TL: { + if (c.chan>=9 && !properDrums) return 0; if (c.value==0) { DivInstrumentFM::Operator& mod=chan[c.chan].state.op[0]; DivInstrumentFM::Operator& car=chan[c.chan].state.op[1]; @@ -555,6 +572,7 @@ int DivPlatformOPLL::dispatch(DivCommand c) { break; } case DIV_CMD_FM_AR: { + if (c.chan>=9 && !properDrums) return 0; DivInstrumentFM::Operator& mod=chan[c.chan].state.op[0]; DivInstrumentFM::Operator& car=chan[c.chan].state.op[1]; if (c.value<0) { @@ -571,6 +589,18 @@ int DivPlatformOPLL::dispatch(DivCommand c) { rWrite(0x05,(car.ar<<4)|(car.dr)); break; } + case DIV_CMD_FM_EXTCH: + if (!properDrumsSys) break; + if (properDrums==c.value) break; + if (c.value) { + properDrums=true; + immWrite(0x0e,0x20); + } else { + properDrums=false; + immWrite(0x0e,0x00); + drumState=0; + } + break; case DIV_ALWAYS_SET_VOLUME: return 0; break; @@ -578,6 +608,7 @@ int DivPlatformOPLL::dispatch(DivCommand c) { return 15; break; case DIV_CMD_PRE_PORTA: + if (c.chan>=9 && !properDrums) return 0; chan[c.chan].inPorta=c.value; break; case DIV_CMD_PRE_NOTE: @@ -622,6 +653,7 @@ void DivPlatformOPLL::forceIns() { immWrite(0x18,0xC0); immWrite(0x28,0x01); } + drumState=0; } void DivPlatformOPLL::toggleRegisterDump(bool enable) { @@ -634,6 +666,7 @@ void DivPlatformOPLL::setVRC7(bool vrc) { void DivPlatformOPLL::setProperDrums(bool pd) { properDrums=pd; + properDrumsSys=pd; } @@ -682,6 +715,8 @@ void DivPlatformOPLL::reset() { drumVol[4]=0; delay=0; + drums=false; + properDrums=properDrumsSys; if (properDrums) { immWrite(0x0e,0x20); diff --git a/src/engine/platform/opll.h b/src/engine/platform/opll.h index 0bada9036..382c23636 100644 --- a/src/engine/platform/opll.h +++ b/src/engine/platform/opll.h @@ -76,7 +76,7 @@ class DivPlatformOPLL: public DivDispatch { bool useYMFM; bool drums; - bool properDrums; + bool properDrums, properDrumsSys; bool vrc7; short oldWrites[256]; diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 656bc35c2..f99c5969d 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -232,6 +232,15 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe } return false; break; + case DIV_SYSTEM_OPLL_DRUMS: + switch (effect) { + case 0x18: // drum mode toggle + dispatchCmd(DivCommand(DIV_CMD_FM_EXTCH,ch,effectVal)); + break; + default: + return false; + } + break; case DIV_SYSTEM_QSOUND: switch (effect) { case 0x10: // echo feedback @@ -389,8 +398,6 @@ bool DivEngine::perSystemPostEffect(int ch, unsigned char effect, unsigned char dispatchCmd(DivCommand(DIV_CMD_FM_MULT,ch,(effectVal>>4)-1,effectVal&15)); } break; - case 0x18: // drum mode toggle - break; case 0x19: // AR global dispatchCmd(DivCommand(DIV_CMD_FM_AR,ch,-1,effectVal&31)); break; From 614c1a4da87c56a08dad1b08ffdb6b089918c052 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 1 Mar 2022 22:42:40 -0500 Subject: [PATCH 036/637] OPLL: 99% proper drums mode muting! --- src/engine/platform/opll.cpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/engine/platform/opll.cpp b/src/engine/platform/opll.cpp index 489bea5dd..b9949aa0d 100644 --- a/src/engine/platform/opll.cpp +++ b/src/engine/platform/opll.cpp @@ -109,7 +109,7 @@ void DivPlatformOPLL::acquire_nuked(short* bufL, short* bufR, size_t start, size OPLL_Clock(&fm,o); unsigned char nextOut=cycleMapOPLL[fm.cycles]; - if (!isMuted[nextOut]) { + if ((nextOut>=6 && properDrums) || !isMuted[nextOut]) { os+=(o[0]+o[1]); } } @@ -267,9 +267,11 @@ void DivPlatformOPLL::tick() { } } if (chan[i].keyOn && i>=6 && properDrums) { - drumState|=(0x10>>(i-6)); - immWrite(0x0e,0x20|drumState); - chan[i].keyOn=false; + if (!isMuted[i]) { + drumState|=(0x10>>(i-6)); + immWrite(0x0e,0x20|drumState); + } + chan[i].keyOn=false; } else if (chan[i].keyOn && i>=6 && drums) { //printf("%d\n",chan[i].note%12); drumState|=(0x10>>(chan[i].note%12)); @@ -636,9 +638,11 @@ void DivPlatformOPLL::forceIns() { rWrite(0x07,(car.sl<<4)|(car.rr)); } rWrite(0x30+i,((15-(chan[i].outVol*(15-chan[i].state.op[1].tl))/15)&15)|(chan[i].state.opllPreset<<4)); - if (chan[i].active) { - chan[i].keyOn=true; - chan[i].freqChanged=true; + if (!(i>=6 && properDrums)) { + if (chan[i].active) { + chan[i].keyOn=true; + chan[i].freqChanged=true; + } } } if (drums) { From b90fb02e635a4208f1207acb4be99892786fa59d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 1 Mar 2022 23:07:29 -0500 Subject: [PATCH 037/637] OPLL: 99.9% all features usable, except for custom patchsets (not sure how am i gonna implement it) --- extern/Nuked-OPLL/opll.c | 22 +++++++++++++++++++ extern/Nuked-OPLL/opll.h | 2 ++ src/engine/platform/opll.cpp | 34 ++++++++++++++++++++++-------- src/engine/platform/opll.h | 2 ++ src/engine/song.h | 12 +++++++++++ src/gui/gui.cpp | 41 ++++++++++++++++++++++++++++++++++++ 6 files changed, 104 insertions(+), 9 deletions(-) diff --git a/extern/Nuked-OPLL/opll.c b/extern/Nuked-OPLL/opll.c index fd2aa239f..ecff69fd3 100644 --- a/extern/Nuked-OPLL/opll.c +++ b/extern/Nuked-OPLL/opll.c @@ -290,6 +290,28 @@ static void OPLL_DoModeWrite(opll_t *chip) { } } +const opll_patch_t* OPLL_GetPatchROM(uint32_t chip_type) { + switch (chip_type) { + case opll_type_ds1001: + return patch_ds1001; + break; + case opll_type_ymf281: + case opll_type_ymf281b: + return patch_ymf281; + break; + case opll_type_ym2423: + return patch_ym2423; + break; + case opll_type_ym2413: + case opll_type_ym2413b: + case opll_type_ym2420: + default: + return patch_ym2413; + break; + } + return patch_ym2413; +} + void OPLL_Reset(opll_t *chip, uint32_t chip_type) { uint32_t i; memset(chip, 0, sizeof(opll_t)); diff --git a/extern/Nuked-OPLL/opll.h b/extern/Nuked-OPLL/opll.h index 706eb9f3b..85c721a78 100644 --- a/extern/Nuked-OPLL/opll.h +++ b/extern/Nuked-OPLL/opll.h @@ -193,6 +193,8 @@ typedef struct { } opll_t; +const opll_patch_t* OPLL_GetPatchROM(uint32_t chip_type); + void OPLL_Reset(opll_t *chip, uint32_t chip_type); void OPLL_Clock(opll_t *chip, int32_t *buffer); void OPLL_Write(opll_t *chip, uint32_t port, uint8_t data); diff --git a/src/engine/platform/opll.cpp b/src/engine/platform/opll.cpp index b9949aa0d..08d46cf9d 100644 --- a/src/engine/platform/opll.cpp +++ b/src/engine/platform/opll.cpp @@ -25,7 +25,7 @@ #define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;} #define immWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } -#define CHIP_FREQBASE 295017 +#define CHIP_FREQBASE 1180068 const char* DivPlatformOPLL::getEffectName(unsigned char effect) { switch (effect) { @@ -693,6 +693,20 @@ void DivPlatformOPLL::reset() { OPLL_Reset(&fm,opll_type_ds1001); } else { OPLL_Reset(&fm,opll_type_ym2413); + switch (patchSet) { + case 0: + fm.patchrom=OPLL_GetPatchROM(opll_type_ym2413); + break; + case 1: + fm.patchrom=OPLL_GetPatchROM(opll_type_ymf281); + break; + case 2: + fm.patchrom=OPLL_GetPatchROM(opll_type_ym2423); + break; + case 3: + fm.patchrom=OPLL_GetPatchROM(opll_type_ds1001); + break; + } } if (dumpWrites) { addWrite(0xffffffff,0); @@ -763,22 +777,24 @@ void DivPlatformOPLL::setYMFM(bool use) { } void DivPlatformOPLL::setFlags(unsigned int flags) { - if (flags==3) { - chipClock=COLOR_NTSC; - } else if (flags==2) { - chipClock=8000000.0; - } else if (flags==1) { - chipClock=COLOR_PAL*1.0/5.0; + if ((flags&15)==3) { + chipClock=COLOR_NTSC/2.0; + } else if ((flags&15)==2) { + chipClock=4000000.0; + } else if ((flags&15)==1) { + chipClock=COLOR_PAL*4.0/5.0; } else { - chipClock=COLOR_NTSC/4.0; + chipClock=COLOR_NTSC; } - rate=chipClock/9; + rate=chipClock/36; + patchSet=flags>>4; } int DivPlatformOPLL::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { parent=p; dumpWrites=false; skipRegisterWrites=false; + patchSet=0; for (int i=0; i<11; i++) { isMuted[i]=false; } diff --git a/src/engine/platform/opll.h b/src/engine/platform/opll.h index 382c23636..4a7a4c97f 100644 --- a/src/engine/platform/opll.h +++ b/src/engine/platform/opll.h @@ -78,6 +78,8 @@ class DivPlatformOPLL: public DivDispatch { bool drums; bool properDrums, properDrumsSys; bool vrc7; + + unsigned char patchSet; short oldWrites[256]; short pendingWrites[256]; diff --git a/src/engine/song.h b/src/engine/song.h index 85752288e..a992aa49f 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -220,6 +220,18 @@ struct DivSong { // - bit 0-11: echo delay length // - Valid values are 0-2725 // - 0 is max length, 2725 is min length + // - OPLL: + // - bit 0-3: clock rate + // - 0: NTSC (3.58MHz) + // - 1: PAL (3.55MHz) + // - 2: Other (4MHz) + // - 3: half NTSC (1.79MHz) + // - bit 4-7: patch set + // - 0: YM2413 + // - 1: YMF281 + // - 2: YM2423 + // - 3: VRC7 + // - 4: custom (TODO) unsigned int systemFlags[32]; // song information diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 50f45c1eb..50e883012 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4635,6 +4635,47 @@ bool FurnaceGUI::loop() { } break; } + case DIV_SYSTEM_OPLL: + case DIV_SYSTEM_OPLL_DRUMS: + case DIV_SYSTEM_VRC7: { + ImGui::Text("Clock rate:"); + if (ImGui::RadioButton("NTSC (3.58MHz)",(flags&15)==0)) { + e->setSysFlags(i,(flags&(~15))|0,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("PAL (3.55MHz)",(flags&15)==1)) { + e->setSysFlags(i,(flags&(~15))|1,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("BBC Micro (4MHz)",(flags&15)==2)) { + e->setSysFlags(i,(flags&(~15))|2,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("Half NTSC (1.79MHz)",(flags&15)==3)) { + e->setSysFlags(i,(flags&(~15))|3,restart); + updateWindowTitle(); + } + if (e->song.system[i]!=DIV_SYSTEM_VRC7) { + ImGui::Text("Patch set:"); + if (ImGui::RadioButton("Yamaha YM2413",((flags>>4)&15)==0)) { + e->setSysFlags(i,(flags&(~0xf0))|0,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("Yamaha YMF281",((flags>>4)&15)==1)) { + e->setSysFlags(i,(flags&(~0xf0))|0x10,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("Yamaha YM2423",((flags>>4)&15)==2)) { + e->setSysFlags(i,(flags&(~0xf0))|0x20,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("Konami VRC7",((flags>>4)&15)==3)) { + e->setSysFlags(i,(flags&(~0xf0))|0x30,restart); + updateWindowTitle(); + } + } + break; + } case DIV_SYSTEM_YM2151: if (ImGui::RadioButton("NTSC (3.58MHz)",flags==0)) { e->setSysFlags(i,0,restart); From cf1711c45b12b0351a117d08259d71f12eee3508 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 1 Mar 2022 23:27:53 -0500 Subject: [PATCH 038/637] GUI: attribution --- src/gui/gui.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index af193fc9a..787643514 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1513,6 +1513,7 @@ const char* aboutLine[]={ "", "-- program --", "tildearrow", + "cam900", "laoo", "superctr", "", From 56aa4b28fb00cd51ac80797f1cfb1d881bb9fb95 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 1 Mar 2022 23:28:05 -0500 Subject: [PATCH 039/637] tiny drums mode fix --- src/engine/platform/opll.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/engine/platform/opll.cpp b/src/engine/platform/opll.cpp index 08d46cf9d..651ff4b7d 100644 --- a/src/engine/platform/opll.cpp +++ b/src/engine/platform/opll.cpp @@ -405,8 +405,10 @@ int DivPlatformOPLL::dispatch(DivCommand c) { } } else { if (c.chan>=6) { - drums=false; - immWrite(0x0e,0); + if (drums) { + drums=false; + immWrite(0x0e,0); + } } rWrite(0x30+c.chan,((15-(chan[c.chan].outVol*(15-chan[c.chan].state.op[1].tl))/15)&15)|(chan[c.chan].state.opllPreset<<4)); } @@ -642,6 +644,7 @@ void DivPlatformOPLL::forceIns() { if (chan[i].active) { chan[i].keyOn=true; chan[i].freqChanged=true; + chan[i].insChanged=true; } } } From 14611fc744389196be39e82ab38a8674b86de671 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 1 Mar 2022 23:36:23 -0500 Subject: [PATCH 040/637] add 2 demo songs requested by CaptainMalware and Mahbod Karamoozian --- demos/Another_winter.fur | Bin 0 -> 26074 bytes demos/yky.fur | Bin 0 -> 17704 bytes src/gui/gui.cpp | 2 ++ 3 files changed, 2 insertions(+) create mode 100644 demos/Another_winter.fur create mode 100644 demos/yky.fur diff --git a/demos/Another_winter.fur b/demos/Another_winter.fur new file mode 100644 index 0000000000000000000000000000000000000000..b0ccceac151b639325d90edc1841e7e853c3c07c GIT binary patch literal 26074 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

kn?N;F4O|~GjD04wqPj@kwKLiPum^tODlrFS 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 a(+a&2!JK}|SSY-TF{61P{aGkK`TqdSGV*Ky literal 0 HcmV?d00001 diff --git a/demos/yky.fur b/demos/yky.fur new file mode 100644 index 0000000000000000000000000000000000000000..106ed1004b630610b60df810d9676b16f61288d1 GIT binary patch literal 17704 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 literal 0 HcmV?d00001 diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 787643514..98400b8f4 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1531,7 +1531,9 @@ const char* aboutLine[]={ "-- demo songs --", "0x5066", "breakthetargets", + "CaptainMalware", "kleeder", + "Mahbod Karamoozian", "nicco1690", "NikonTeen", "SuperJet Spade", From f10816ae80402967c1822f03f8f96337c31eb26d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 1 Mar 2022 23:46:04 -0500 Subject: [PATCH 041/637] GUI: add warning when layout reset is called issue #240 --- src/gui/gui.cpp | 7 +++++-- src/gui/gui.h | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 98400b8f4..46692dbd5 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4932,8 +4932,7 @@ bool FurnaceGUI::loop() { } if (ImGui::BeginMenu("settings")) { if (ImGui::MenuItem("reset layout")) { - ImGui::LoadIniSettingsFromMemory(defaultLayout); - ImGui::SaveIniSettingsToDisk(finalLayoutPath); + showWarning("Are you sure you want to reset the workspace layout?",GUI_WARN_RESET_LAYOUT); } if (ImGui::MenuItem("settings...",BIND_FOR(GUI_ACTION_WINDOW_SETTINGS))) { syncSettings(); @@ -5279,6 +5278,10 @@ bool FurnaceGUI::loop() { } nextFile=""; break; + case GUI_WARN_RESET_LAYOUT: + ImGui::LoadIniSettingsFromMemory(defaultLayout); + ImGui::SaveIniSettingsToDisk(finalLayoutPath); + break; case GUI_WARN_GENERIC: break; } diff --git a/src/gui/gui.h b/src/gui/gui.h index c59705961..0aebddfcc 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -157,6 +157,7 @@ enum FurnaceGUIWarnings { GUI_WARN_NEW, GUI_WARN_OPEN, GUI_WARN_OPEN_DROP, + GUI_WARN_RESET_LAYOUT, GUI_WARN_GENERIC }; From 36e1ead643336f176cd979fb472f60fea6e06d85 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 2 Mar 2022 00:02:52 -0500 Subject: [PATCH 042/637] GUI: lastDir per dialog type issue #227 --- src/gui/gui.cpp | 101 +++++++++++++++++++++++++++++++++++++----------- src/gui/gui.h | 1 + src/ta-utils.h | 2 + 3 files changed, 81 insertions(+), 23 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 46692dbd5..bcbebef2a 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -3992,56 +3992,70 @@ bool dirExists(String what) { } void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { - if (!dirExists(workingDir)) workingDir=getHomeDir(); ImGuiFileDialog::Instance()->DpiScale=dpiScale; switch (type) { case GUI_FILE_OPEN: - ImGuiFileDialog::Instance()->OpenModal("FileDialog","Open File","compatible files{.fur,.dmf},.*",workingDir); + if (!dirExists(workingDirSong)) workingDirSong=getHomeDir(); + ImGuiFileDialog::Instance()->OpenModal("FileDialog","Open File","compatible files{.fur,.dmf},.*",workingDirSong); break; case GUI_FILE_SAVE: - ImGuiFileDialog::Instance()->OpenModal("FileDialog","Save File","Furnace song{.fur},DefleMask 1.1 module{.dmf}",workingDir,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite); + if (!dirExists(workingDirSong)) workingDirSong=getHomeDir(); + ImGuiFileDialog::Instance()->OpenModal("FileDialog","Save File","Furnace song{.fur},DefleMask 1.1 module{.dmf}",workingDirSong,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite); break; case GUI_FILE_SAVE_DMF_LEGACY: - ImGuiFileDialog::Instance()->OpenModal("FileDialog","Save File","DefleMask 1.0/legacy module{.dmf}",workingDir,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite); + if (!dirExists(workingDirSong)) workingDirSong=getHomeDir(); + ImGuiFileDialog::Instance()->OpenModal("FileDialog","Save File","DefleMask 1.0/legacy module{.dmf}",workingDirSong,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite); break; case GUI_FILE_INS_OPEN: - ImGuiFileDialog::Instance()->OpenModal("FileDialog","Load Instrument","compatible files{.fui,.dmp,.tfi,.vgi},.*",workingDir); + if (!dirExists(workingDirIns)) workingDirIns=getHomeDir(); + ImGuiFileDialog::Instance()->OpenModal("FileDialog","Load Instrument","compatible files{.fui,.dmp,.tfi,.vgi},.*",workingDirIns); break; case GUI_FILE_INS_SAVE: - ImGuiFileDialog::Instance()->OpenModal("FileDialog","Save Instrument","Furnace instrument{.fui}",workingDir,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite); + if (!dirExists(workingDirIns)) workingDirIns=getHomeDir(); + ImGuiFileDialog::Instance()->OpenModal("FileDialog","Save Instrument","Furnace instrument{.fui}",workingDirIns,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite); break; case GUI_FILE_WAVE_OPEN: - ImGuiFileDialog::Instance()->OpenModal("FileDialog","Load Wavetable","compatible files{.fuw,.dmw},.*",workingDir); + if (!dirExists(workingDirWave)) workingDirWave=getHomeDir(); + ImGuiFileDialog::Instance()->OpenModal("FileDialog","Load Wavetable","compatible files{.fuw,.dmw},.*",workingDirWave); break; case GUI_FILE_WAVE_SAVE: - ImGuiFileDialog::Instance()->OpenModal("FileDialog","Save Wavetable","Furnace wavetable{.fuw}",workingDir,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite); + if (!dirExists(workingDirWave)) workingDirWave=getHomeDir(); + ImGuiFileDialog::Instance()->OpenModal("FileDialog","Save Wavetable","Furnace wavetable{.fuw}",workingDirWave,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite); break; case GUI_FILE_SAMPLE_OPEN: - ImGuiFileDialog::Instance()->OpenModal("FileDialog","Load Sample","Wave file{.wav},.*",workingDir); + if (!dirExists(workingDirSample)) workingDirSample=getHomeDir(); + ImGuiFileDialog::Instance()->OpenModal("FileDialog","Load Sample","Wave file{.wav},.*",workingDirSample); break; case GUI_FILE_SAMPLE_SAVE: - ImGuiFileDialog::Instance()->OpenModal("FileDialog","Save Sample","Wave file{.wav}",workingDir,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite); + if (!dirExists(workingDirSample)) workingDirSample=getHomeDir(); + ImGuiFileDialog::Instance()->OpenModal("FileDialog","Save Sample","Wave file{.wav}",workingDirSample,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite); break; case GUI_FILE_EXPORT_AUDIO_ONE: - ImGuiFileDialog::Instance()->OpenModal("FileDialog","Export Audio","Wave file{.wav}",workingDir,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite); + if (!dirExists(workingDirAudioExport)) workingDirAudioExport=getHomeDir(); + ImGuiFileDialog::Instance()->OpenModal("FileDialog","Export Audio","Wave file{.wav}",workingDirAudioExport,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite); break; case GUI_FILE_EXPORT_AUDIO_PER_SYS: - ImGuiFileDialog::Instance()->OpenModal("FileDialog","Export Audio","Wave file{.wav}",workingDir,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite); + if (!dirExists(workingDirAudioExport)) workingDirAudioExport=getHomeDir(); + ImGuiFileDialog::Instance()->OpenModal("FileDialog","Export Audio","Wave file{.wav}",workingDirAudioExport,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite); break; case GUI_FILE_EXPORT_AUDIO_PER_CHANNEL: - ImGuiFileDialog::Instance()->OpenModal("FileDialog","Export Audio","Wave file{.wav}",workingDir,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite); + if (!dirExists(workingDirAudioExport)) workingDirAudioExport=getHomeDir(); + ImGuiFileDialog::Instance()->OpenModal("FileDialog","Export Audio","Wave file{.wav}",workingDirAudioExport,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite); break; case GUI_FILE_EXPORT_VGM: - ImGuiFileDialog::Instance()->OpenModal("FileDialog","Export VGM",".vgm",workingDir,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite); + if (!dirExists(workingDirVGMExport)) workingDirVGMExport=getHomeDir(); + ImGuiFileDialog::Instance()->OpenModal("FileDialog","Export VGM",".vgm",workingDirVGMExport,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite); break; case GUI_FILE_EXPORT_ROM: showError("Coming soon!"); break; case GUI_FILE_LOAD_MAIN_FONT: - ImGuiFileDialog::Instance()->OpenModal("FileDialog","Select Font","compatible files{.ttf,.otf,.ttc}",workingDir); + if (!dirExists(workingDirFont)) workingDirFont=getHomeDir(); + ImGuiFileDialog::Instance()->OpenModal("FileDialog","Select Font","compatible files{.ttf,.otf,.ttc}",workingDirFont); break; case GUI_FILE_LOAD_PAT_FONT: - ImGuiFileDialog::Instance()->OpenModal("FileDialog","Select Font","compatible files{.ttf,.otf,.ttc}",workingDir); + if (!dirExists(workingDirFont)) workingDirFont=getHomeDir(); + ImGuiFileDialog::Instance()->OpenModal("FileDialog","Select Font","compatible files{.ttf,.otf,.ttc}",workingDirFont); break; } curFileDialog=type; @@ -5068,6 +5082,38 @@ bool FurnaceGUI::loop() { if (ImGuiFileDialog::Instance()->Display("FileDialog",ImGuiWindowFlags_NoCollapse|ImGuiWindowFlags_NoMove,ImVec2(600.0f*dpiScale,400.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale))) { //ImGui::GetIO().ConfigFlags&=~ImGuiConfigFlags_NavEnableKeyboard; + switch (curFileDialog) { + case GUI_FILE_OPEN: + case GUI_FILE_SAVE: + case GUI_FILE_SAVE_DMF_LEGACY: + workingDirSong=ImGuiFileDialog::Instance()->GetCurrentPath()+DIR_SEPARATOR_STR; + break; + case GUI_FILE_INS_OPEN: + case GUI_FILE_INS_SAVE: + workingDirIns=ImGuiFileDialog::Instance()->GetCurrentPath()+DIR_SEPARATOR_STR; + break; + case GUI_FILE_WAVE_OPEN: + case GUI_FILE_WAVE_SAVE: + workingDirWave=ImGuiFileDialog::Instance()->GetCurrentPath()+DIR_SEPARATOR_STR; + break; + case GUI_FILE_SAMPLE_OPEN: + case GUI_FILE_SAMPLE_SAVE: + workingDirSample=ImGuiFileDialog::Instance()->GetCurrentPath()+DIR_SEPARATOR_STR; + break; + case GUI_FILE_EXPORT_AUDIO_ONE: + case GUI_FILE_EXPORT_AUDIO_PER_SYS: + case GUI_FILE_EXPORT_AUDIO_PER_CHANNEL: + workingDirAudioExport=ImGuiFileDialog::Instance()->GetCurrentPath()+DIR_SEPARATOR_STR; + break; + case GUI_FILE_EXPORT_VGM: + case GUI_FILE_EXPORT_ROM: + workingDirVGMExport=ImGuiFileDialog::Instance()->GetCurrentPath()+DIR_SEPARATOR_STR; + break; + case GUI_FILE_LOAD_MAIN_FONT: + case GUI_FILE_LOAD_PAT_FONT: + workingDirFont=ImGuiFileDialog::Instance()->GetCurrentPath()+DIR_SEPARATOR_STR; + break; + } if (ImGuiFileDialog::Instance()->IsOk()) { fileName=ImGuiFileDialog::Instance()->GetFilePathName(); if (fileName!="") { @@ -5195,12 +5241,6 @@ bool FurnaceGUI::loop() { curFileDialog=GUI_FILE_OPEN; } } - workingDir=ImGuiFileDialog::Instance()->GetCurrentPath(); -#ifdef _WIN32 - workingDir+='\\'; -#else - workingDir+='/'; -#endif ImGuiFileDialog::Instance()->Close(); } @@ -5663,7 +5703,15 @@ bool FurnaceGUI::init() { float dpiScaleF; #endif - workingDir=e->getConfString("lastDir",getHomeDir()); + String homeDir=getHomeDir(); + workingDir=e->getConfString("lastDir",homeDir); + workingDirSong=e->getConfString("lastDirSong",workingDir); + workingDirIns=e->getConfString("lastDirIns",workingDir); + workingDirWave=e->getConfString("lastDirWave",workingDir); + workingDirSample=e->getConfString("lastDirSample",workingDir); + workingDirAudioExport=e->getConfString("lastDirAudioExport",workingDir); + workingDirVGMExport=e->getConfString("lastDirVGMExport",workingDir); + workingDirFont=e->getConfString("lastDirFont",workingDir); editControlsOpen=e->getConfBool("editControlsOpen",true); ordersOpen=e->getConfBool("ordersOpen",true); @@ -5817,6 +5865,13 @@ bool FurnaceGUI::finish() { SDL_DestroyWindow(sdlWin); e->setConf("lastDir",workingDir); + e->setConf("lastDirSong",workingDirSong); + e->setConf("lastDirIns",workingDirIns); + e->setConf("lastDirWave",workingDirWave); + e->setConf("lastDirSample",workingDirSample); + e->setConf("lastDirAudioExport",workingDirAudioExport); + e->setConf("lastDirVGMExport",workingDirVGMExport); + e->setConf("lastDirFont",workingDirFont); // commit last open windows e->setConf("editControlsOpen",editControlsOpen); diff --git a/src/gui/gui.h b/src/gui/gui.h index 0aebddfcc..8db639ab5 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -426,6 +426,7 @@ class FurnaceGUI { SDL_Renderer* sdlRend; String workingDir, fileName, clipboard, warnString, errorString, lastError, curFileName, nextFile; + String workingDirSong, workingDirIns, workingDirWave, workingDirSample, workingDirAudioExport, workingDirVGMExport, workingDirFont; String mmlString[12]; String mmlStringW; diff --git a/src/ta-utils.h b/src/ta-utils.h index db876f43f..98b184f31 100644 --- a/src/ta-utils.h +++ b/src/ta-utils.h @@ -30,8 +30,10 @@ typedef SSIZE_T ssize_t; #ifdef _WIN32 #define DIR_SEPARATOR '\\' +#define DIR_SEPARATOR_STR "\\" #else #define DIR_SEPARATOR '/' +#define DIR_SEPARATOR_STR "/" #endif typedef std::string String; From 1957c19f345452a7fee06c54cee343c93e5c6d41 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 2 Mar 2022 00:09:28 -0500 Subject: [PATCH 043/637] GUI: insert 100ms delay when minimized issue #220 --- src/gui/gui.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index bcbebef2a..e8b769a47 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include #define _USE_MATH_DEFINES #include "gui.h" #include "util.h" @@ -5350,6 +5351,10 @@ bool FurnaceGUI::loop() { commitSettings(); willCommit=false; } + + if (SDL_GetWindowFlags(sdlWin)&SDL_WINDOW_MINIMIZED) { + SDL_Delay(100); + } } return false; } From e133fb490641d65e606a53eaf5dfdd061e704e31 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 2 Mar 2022 00:38:32 -0500 Subject: [PATCH 044/637] GUI: make several things rate-independent --- src/gui/gui.cpp | 17 +++++++++-------- src/gui/gui.h | 2 +- src/gui/pattern.cpp | 8 +++++--- 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index e8b769a47..fd5d5a7d2 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -59,13 +59,13 @@ extern "C" { #define LAYOUT_INI "/layout.ini" #endif -bool Particle::update() { - pos.x+=speed.x; - pos.y+=speed.y; - speed.x*=friction; - speed.y*=friction; - speed.y+=gravity; - life-=lifeSpeed; +bool Particle::update(float frameTime) { + pos.x+=speed.x*frameTime; + pos.y+=speed.y*frameTime; + speed.x*=1.0-((1.0-friction)*frameTime); + speed.y*=1.0-((1.0-friction)*frameTime); + speed.y+=gravity*frameTime; + life-=lifeSpeed*frameTime; return (life>0); } @@ -1437,10 +1437,11 @@ void FurnaceGUI::drawVolMeter() { ImGuiStyle& style=ImGui::GetStyle(); ImGui::ItemSize(ImVec2(4.0f,4.0f),style.FramePadding.y); ImU32 lowColor=ImGui::GetColorU32(uiColors[GUI_COLOR_VOLMETER_LOW]); + float peakDecay=0.05f*60.0f*ImGui::GetIO().DeltaTime; if (ImGui::ItemAdd(rect,ImGui::GetID("volMeter"))) { ImGui::RenderFrame(rect.Min,rect.Max,ImGui::GetColorU32(ImGuiCol_FrameBg),true,style.FrameRounding); for (int i=0; i<2; i++) { - peak[i]*=0.95; + peak[i]*=1.0-peakDecay; if (peak[i]<0.0001) peak[i]=0.0; for (int j=0; joscSize; j++) { if (fabs(e->oscBuf[i][j])>peak[i]) { diff --git a/src/gui/gui.h b/src/gui/gui.h index 8db639ab5..495073a21 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -391,7 +391,7 @@ struct Particle { const char* type; ImVec2 pos, speed; float gravity, friction, life, lifeSpeed; - bool update(); + bool update(float frameTime); Particle(ImU32* color, const char* ty, float x, float y, float sX, float sY, float g, float fr, float l, float lS): colors(color), type(ty), diff --git a/src/gui/pattern.cpp b/src/gui/pattern.cpp index c6c78ba8d..7d9eb43ce 100644 --- a/src/gui/pattern.cpp +++ b/src/gui/pattern.cpp @@ -451,7 +451,7 @@ void FurnaceGUI::drawPattern() { chanHead.x*=0.25+keyHit[i]; chanHead.y*=0.25+keyHit[i]; chanHead.z*=0.25+keyHit[i]; chanHeadActive.x*=0.8; chanHeadActive.y*=0.8; chanHeadActive.z*=0.8; chanHeadHover.x*=0.4+keyHit[i]; chanHeadHover.y*=0.4+keyHit[i]; chanHeadHover.z*=0.4+keyHit[i]; - keyHit[i]-=0.02; + keyHit[i]-=0.02*60.0*ImGui::GetIO().DeltaTime; if (keyHit[i]<0) keyHit[i]=0; ImGui::PushStyleColor(ImGuiCol_Header,chanHead); ImGui::PushStyleColor(ImGuiCol_HeaderActive,chanHeadActive); @@ -717,6 +717,8 @@ void FurnaceGUI::drawPattern() { } } + float frameTime=ImGui::GetIO().DeltaTime*60.0f; + // note slides ImVec2 arrowPoints[7]; if (e->isPlaying()) for (int i=0; iAddPolyline(arrowPoints,7,ImGui::GetColorU32(col),ImDrawFlags_None,5.0f*dpiScale); } } - patChanSlideY[i]+=((ch->portaNote<=ch->note)?-8:8)*dpiScale; + patChanSlideY[i]+=((ch->portaNote<=ch->note)?-8:8)*dpiScale*frameTime; if (width>0) { if (patChanSlideY[i]<0) { patChanSlideY[i]=-fmod(-patChanSlideY[i],width*0.7); @@ -778,7 +780,7 @@ void FurnaceGUI::drawPattern() { ImDrawList* fdl=ImGui::GetForegroundDrawList(); for (size_t i=0; i255) part.life=255; fdl->AddText( iconFont, From 88433152568416f0a3a91b2cbbe34008b4cd2a99 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 2 Mar 2022 00:58:49 -0500 Subject: [PATCH 045/637] OPLL: 99.99% VGM export --- src/engine/sysDef.cpp | 3 +++ src/engine/vgmOps.cpp | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index a0d211c68..52a4cb89e 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -1601,6 +1601,9 @@ bool DivEngine::isVGMExportable(DivSystem which) { case DIV_SYSTEM_QSOUND: case DIV_SYSTEM_SEGAPCM: case DIV_SYSTEM_SEGAPCM_COMPAT: + case DIV_SYSTEM_OPLL: + case DIV_SYSTEM_OPLL_DRUMS: + case DIV_SYSTEM_VRC7: return true; default: return false; diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index 71f313e43..97f427372 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -214,6 +214,21 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write w->writeC(0); w->writeC(0xbf); break; + case DIV_SYSTEM_OPLL: + case DIV_SYSTEM_OPLL_DRUMS: + case DIV_SYSTEM_VRC7: + for (int i=0; i<9; i++) { + w->writeC(isSecond?0xa1:0x51); + w->writeC(0x20+i); + w->writeC(0); + w->writeC(isSecond?0xa1:0x51); + w->writeC(0x30+i); + w->writeC(0); + w->writeC(isSecond?0xa1:0x51); + w->writeC(0x10+i); + w->writeC(0); + } + break; case DIV_SYSTEM_AY8910: w->writeC(0xa0); w->writeC(isSecond?0x87:7); @@ -395,6 +410,13 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write break; } break; + case DIV_SYSTEM_OPLL: + case DIV_SYSTEM_OPLL_DRUMS: + case DIV_SYSTEM_VRC7: + w->writeC(isSecond?0xa1:0x51); + w->writeC(write.addr&0xff); + w->writeC(write.val); + break; case DIV_SYSTEM_AY8910: case DIV_SYSTEM_AY8930: w->writeC(0xa0); @@ -682,6 +704,19 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) { howManyChips++; } break; + case DIV_SYSTEM_OPLL: + case DIV_SYSTEM_OPLL_DRUMS: + case DIV_SYSTEM_VRC7: + if (!hasOPLL) { + hasOPLL=disCont[i].dispatch->chipClock; + willExport[i]=true; + } else if (!(hasOPLL&0x40000000)) { + isSecond[i]=true; + willExport[i]=true; + hasOPLL|=0x40000000; + howManyChips++; + } + break; case DIV_SYSTEM_LYNX: if (!hasLynx) { hasLynx=disCont[i].dispatch->chipClock; From d94e0ec3e79289364a531968c9e7105a18de6447 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 2 Mar 2022 02:22:51 -0500 Subject: [PATCH 046/637] GUI: prepare a better new song thingy --- src/engine/engine.cpp | 2 +- src/engine/engine.h | 2 +- src/gui/gui.cpp | 519 ++++++++++++++++++++++++++++++++++++++---- src/gui/gui.h | 12 +- 4 files changed, 489 insertions(+), 46 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 08ec2f590..a8b023d7d 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -534,7 +534,7 @@ void DivEngine::renderSamples() { qsoundMemLen=memPos+256; } -void DivEngine::createNew(int* description) { +void DivEngine::createNew(const int* description) { quitDispatch(); isBusy.lock(); song.unload(); diff --git a/src/engine/engine.h b/src/engine/engine.h index 02eed74ed..02870178a 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -262,7 +262,7 @@ class DivEngine { DivWavetable* getWave(int index); DivSample* getSample(int index); // start fresh - void createNew(int* description); + void createNew(const int* description); // load a file. bool load(unsigned char* f, size_t length); // save as .dmf. diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index fd5d5a7d2..9be913ae1 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1910,11 +1910,6 @@ void FurnaceGUI::drawDebug() { ImGui::End(); } -#define NEWSONG_CATEGORY(x) \ - if (ImGui::Selectable(x,false,ImGuiSelectableFlags_DontClosePopups)) { \ - printf("selected a category\n"); \ - } - void FurnaceGUI::drawNewSong() { bool accepted=false; @@ -1923,7 +1918,7 @@ void FurnaceGUI::drawNewSong() { ImGui::Text("Choose a System!"); ImGui::PopFont(); - if (ImGui::BeginTable("sysPicker",2,ImGuiTableFlags_Borders)) { + if (ImGui::BeginTable("sysPicker",2)) { ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,0.0f); ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.0f); @@ -1937,30 +1932,36 @@ void FurnaceGUI::drawNewSong() { // CATEGORIES ImGui::TableNextColumn(); - NEWSONG_CATEGORY("All chips"); - NEWSONG_CATEGORY("FM"); - NEWSONG_CATEGORY("Square"); - NEWSONG_CATEGORY("Sample"); - NEWSONG_CATEGORY("Wavetable"); - NEWSONG_CATEGORY("Other/Special"); - NEWSONG_CATEGORY("Game consoles"); - NEWSONG_CATEGORY("Computers"); - NEWSONG_CATEGORY("Arcade systems"); - NEWSONG_CATEGORY("DefleMask-compatible"); + int index=0; + for (FurnaceGUISysCategory& i: sysCategories) { + if (ImGui::Selectable(i.name,newSongCategory==index,ImGuiSelectableFlags_DontClosePopups)) { \ + newSongCategory=index; + } + index++; + } // SYSTEMS ImGui::TableNextColumn(); if (ImGui::BeginTable("Systems",1,ImGuiTableFlags_BordersInnerV|ImGuiTableFlags_ScrollY)) { - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Selectable("System system",false,ImGuiSelectableFlags_DontClosePopups); + for (FurnaceGUISysDef& i: sysCategories[newSongCategory].systems) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + if (ImGui::Selectable(i.name,false,ImGuiSelectableFlags_DontClosePopups)) { + nextDesc=i.definition.data(); + accepted=true; + } + } ImGui::EndTable(); } ImGui::EndTable(); } - if (ImGui::Button("Go ahead") || accepted) { + if (ImGui::Button("Cancel")) { + ImGui::CloseCurrentPopup(); + } + + if (accepted) { e->createNew(nextDesc); undoHist.clear(); redoHist.clear(); @@ -5286,7 +5287,7 @@ bool FurnaceGUI::loop() { } ImGui::SetNextWindowSizeConstraints(ImVec2(400.0f*dpiScale,200.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); - if (ImGui::BeginPopupModal("New Song",NULL)) { + if (ImGui::BeginPopupModal("New Song",NULL,ImGuiWindowFlags_NoMove)) { ImGui::SetWindowPos(ImVec2(((scrW*dpiScale)-ImGui::GetWindowSize().x)*0.5,((scrH*dpiScale)-ImGui::GetWindowSize().y)*0.5)); drawNewSong(); ImGui::EndPopup(); @@ -5949,6 +5950,7 @@ FurnaceGUI::FurnaceGUI(): isClipping(0), extraChannelButtons(0), patNameTarget(-1), + newSongCategory(0), editControlsOpen(true), ordersOpen(true), insListOpen(true), @@ -6103,30 +6105,469 @@ FurnaceGUI::FurnaceGUI(): valueKeys[SDLK_KP_7]=7; valueKeys[SDLK_KP_8]=8; valueKeys[SDLK_KP_9]=9; - - /* -const char* sysCategories[]={ - "All chips", - "FM", - "Square", - "Sample", - "Wavetable", - "Other/Special", - "Game consoles", - "Computers", - "Arcade systems", - "DefleMask-compatible" -}; -*/ FurnaceGUISysCategory cat; - cat=FurnaceGUISysCategory("All chips"); + cat=FurnaceGUISysCategory("FM"); cat.systems.push_back(FurnaceGUISysDef( - "Yamaha YM2612", NULL /*{ + "Yamaha YM2612", { DIV_SYSTEM_YM2612, 64, 0, 0, 0 - }*/ + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha YM2612 (extended channel 3)", { + DIV_SYSTEM_YM2612_EXT, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha YM2151", { + DIV_SYSTEM_YM2151, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha YM2610", { + DIV_SYSTEM_YM2610_FULL, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha YM2610 (extended channel 2)", { + DIV_SYSTEM_YM2610_FULL_EXT, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha YM2610B", { + DIV_SYSTEM_YM2610, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha YM2610B (extended channel 3)", { + DIV_SYSTEM_YM2610B_EXT, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha YM2413", { + DIV_SYSTEM_OPLL, 64, 0, 0, + 0 + } + )); + sysCategories.push_back(cat); + + cat=FurnaceGUISysCategory("Square"); + cat.systems.push_back(FurnaceGUISysDef( + "TI SN76489", { + DIV_SYSTEM_SMS, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "AY-3-8910", { + DIV_SYSTEM_AY8910, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Philips SAA1099", { + DIV_SYSTEM_SAA1099, 64, 0, 0, + 0 + } + )); + sysCategories.push_back(cat); + + cat=FurnaceGUISysCategory("Sample"); + cat.systems.push_back(FurnaceGUISysDef( + "Amiga", { + DIV_SYSTEM_AMIGA, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "SegaPCM", { + DIV_SYSTEM_SEGAPCM, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Capcom QSound", { + DIV_SYSTEM_QSOUND, 64, 0, 0, + 0 + } + )); + sysCategories.push_back(cat); + + cat=FurnaceGUISysCategory("Game consoles"); + cat.systems.push_back(FurnaceGUISysDef( + "Sega Genesis", { + DIV_SYSTEM_YM2612, 64, 0, 0, + DIV_SYSTEM_SMS, 24, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sega Genesis (extended channel 3)", { + DIV_SYSTEM_YM2612_EXT, 64, 0, 0, + DIV_SYSTEM_SMS, 24, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sega Master System", { + DIV_SYSTEM_SMS, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sega Master System (with FM expansion)", { + DIV_SYSTEM_SMS, 64, 0, 0, + DIV_SYSTEM_OPLL, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sega Master System (with FM expansion in drums mode)", { + DIV_SYSTEM_SMS, 64, 0, 0, + DIV_SYSTEM_OPLL_DRUMS, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Game Boy", { + DIV_SYSTEM_GB, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NEC PC Engine/TurboGrafx-16", { + DIV_SYSTEM_PCE, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NES", { + DIV_SYSTEM_NES, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NES with Konami VRC7", { + DIV_SYSTEM_NES, 64, 0, 0, + DIV_SYSTEM_VRC7, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NES with Sunsoft 5B", { + DIV_SYSTEM_NES, 64, 0, 0, + DIV_SYSTEM_AY8910, 64, 0, 38, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Mattel Intellivision", { + DIV_SYSTEM_AY8910, 64, 0, 6, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Vectrex", { + DIV_SYSTEM_AY8910, 64, 0, 4, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Neo Geo AES", { + DIV_SYSTEM_YM2610_FULL, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Neo Geo AES (extended channel 2)", { + DIV_SYSTEM_YM2610_FULL_EXT, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Atari 2600/7800", { + DIV_SYSTEM_TIA, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Atari Lynx", { + DIV_SYSTEM_LYNX, 64, 0, 0, + 0 + } + )); + sysCategories.push_back(cat); + + cat=FurnaceGUISysCategory("Computers"); + cat.systems.push_back(FurnaceGUISysDef( + "Commodore PET", { + DIV_SYSTEM_PET, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Commodore VIC-20", { + DIV_SYSTEM_VIC20, 64, 0, 1, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Commodore 64 (6581 SID)", { + DIV_SYSTEM_C64_6581, 64, 0, 1, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Commodore 64 (8580 SID)", { + DIV_SYSTEM_C64_8580, 64, 0, 1, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Amiga", { + DIV_SYSTEM_AMIGA, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "MSX", { + DIV_SYSTEM_AY8910, 64, 0, 16, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "ZX Spectrum (48K)", { + DIV_SYSTEM_AY8910, 64, 0, 2, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "ZX Spectrum (128K)", { + DIV_SYSTEM_AY8910, 64, 0, 1, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Amstrad CPC", { + DIV_SYSTEM_AY8910, 64, 0, 5, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "SAM Coupé", { + DIV_SYSTEM_SAA1099, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "BBC Micro", { + DIV_SYSTEM_SMS, 64, 0, 6, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "PC (barebones)", { + DIV_SYSTEM_PCSPKR, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "PC + Covox Sound Master", { + DIV_SYSTEM_AY8930, 64, 0, 3, + DIV_SYSTEM_PCSPKR, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "PC + Game Blaster", { + DIV_SYSTEM_SAA1099, 64, -127, 1, + DIV_SYSTEM_SAA1099, 64, 127, 1, + DIV_SYSTEM_PCSPKR, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "PC + AdLib/Sound Blaster", { + DIV_SYSTEM_OPL2, 64, 0, 0, + DIV_SYSTEM_PCSPKR, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "PC + AdLib/Sound Blaster (drums mode)", { + DIV_SYSTEM_OPL2_DRUMS, 64, 0, 0, + DIV_SYSTEM_PCSPKR, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "PC + Sound Blaster Pro 2", { + DIV_SYSTEM_OPL3, 64, 0, 0, + DIV_SYSTEM_PCSPKR, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "PC + Sound Blaster Pro 2 (drums mode)", { + DIV_SYSTEM_OPL3_DRUMS, 64, 0, 0, + DIV_SYSTEM_PCSPKR, 64, 0, 0, + 0 + } + )); + /* + cat.systems.push_back(FurnaceGUISysDef( + "Sharp X68000", { + DIV_SYSTEM_AY8910, 64, 0, 16, + 0 + } + ));*/ + sysCategories.push_back(cat); + + cat=FurnaceGUISysCategory("Arcade systems"); + cat.systems.push_back(FurnaceGUISysDef( + "Bally Midway MCR", { + DIV_SYSTEM_AY8910, 64, 0, 0, + DIV_SYSTEM_AY8910, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sega Kyugo", { + DIV_SYSTEM_AY8910, 64, 0, 4, + DIV_SYSTEM_AY8910, 64, 0, 4, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sega OutRun/X Board", { + DIV_SYSTEM_YM2151, 64, 0, 0, + DIV_SYSTEM_SEGAPCM, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Neo Geo MVS", { + DIV_SYSTEM_YM2610_FULL, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Neo Geo MVS (extended channel 2)", { + DIV_SYSTEM_YM2610_FULL_EXT, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Taito Arcade", { + DIV_SYSTEM_YM2610B, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Taito Arcade (extended channel 3)", { + DIV_SYSTEM_YM2610B_EXT, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Capcom CPS-2 (QSound)", { + DIV_SYSTEM_QSOUND, 64, 0, 0, + 0 + } + )); + sysCategories.push_back(cat); + + cat=FurnaceGUISysCategory("DefleMask-compatible"); + cat.systems.push_back(FurnaceGUISysDef( + "Sega Genesis", { + DIV_SYSTEM_YM2612, 64, 0, 0, + DIV_SYSTEM_SMS, 24, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sega Genesis (extended channel 3)", { + DIV_SYSTEM_YM2612_EXT, 64, 0, 0, + DIV_SYSTEM_SMS, 24, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sega Master System", { + DIV_SYSTEM_SMS, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sega Master System (with FM expansion)", { + DIV_SYSTEM_SMS, 64, 0, 0, + DIV_SYSTEM_OPLL, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Game Boy", { + DIV_SYSTEM_GB, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NEC PC Engine/TurboGrafx-16", { + DIV_SYSTEM_PCE, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NES", { + DIV_SYSTEM_NES, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NES with Konami VRC7", { + DIV_SYSTEM_NES, 64, 0, 0, + DIV_SYSTEM_VRC7, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Commodore 64 (6581 SID)", { + DIV_SYSTEM_C64_6581, 64, 0, 1, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Commodore 64 (8580 SID)", { + DIV_SYSTEM_C64_8580, 64, 0, 1, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Arcade (YM2151 and SegaPCM)", { + DIV_SYSTEM_YM2151, 64, 0, 0, + DIV_SYSTEM_SEGAPCM_COMPAT, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Neo Geo CD", { + DIV_SYSTEM_YM2610, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Neo Geo CD (extended channel 2)", { + DIV_SYSTEM_YM2610_EXT, 64, 0, 0, + 0 + } )); sysCategories.push_back(cat); diff --git a/src/gui/gui.h b/src/gui/gui.h index 495073a21..e41d71450 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -23,6 +23,7 @@ #include "imgui_impl_sdlrenderer.h" #include #include +#include #include #include @@ -405,9 +406,10 @@ struct Particle { struct FurnaceGUISysDef { const char* name; - const int* definition; - FurnaceGUISysDef(const char* n, const int* def): - name(n), definition(def) {} + std::vector definition; + FurnaceGUISysDef(const char* n, std::initializer_list def): + name(n), definition(def) { + } }; struct FurnaceGUISysCategory { @@ -540,7 +542,7 @@ class FurnaceGUI { char finalLayoutPath[4096]; int curIns, curWave, curSample, curOctave, oldRow, oldOrder, oldOrder1, editStep, exportLoops, soloChan, soloTimeout, orderEditMode, orderCursor; - int loopOrder, loopRow, loopEnd, isClipping, extraChannelButtons, patNameTarget; + int loopOrder, loopRow, loopEnd, isClipping, extraChannelButtons, patNameTarget, newSongCategory; bool editControlsOpen, ordersOpen, insListOpen, songInfoOpen, patternOpen, insEditOpen; bool waveListOpen, waveEditOpen, sampleListOpen, sampleEditOpen, aboutOpen, settingsOpen; bool mixerOpen, debugOpen, oscOpen, volMeterOpen, statsOpen, compatFlagsOpen; @@ -552,7 +554,7 @@ class FurnaceGUI { float peak[2]; float patChanX[DIV_MAX_CHANS+1]; float patChanSlideY[DIV_MAX_CHANS+1]; - int* nextDesc; + const int* nextDesc; // bit 31: ctrl // bit 30: reserved for SDL scancode mask From f95d38521e9cf293465183d7e11d5e700582172e Mon Sep 17 00:00:00 2001 From: cam900 Date: Wed, 2 Mar 2022 17:24:07 +0900 Subject: [PATCH 047/637] Fix chip correction --- src/gui/gui.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 9be913ae1..0aeeb129e 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -6141,7 +6141,7 @@ FurnaceGUI::FurnaceGUI(): )); cat.systems.push_back(FurnaceGUISysDef( "Yamaha YM2610B", { - DIV_SYSTEM_YM2610, 64, 0, 0, + DIV_SYSTEM_YM2610B, 64, 0, 0, 0 } )); From 0114523c96e9fc07fe796d8e90eb6835ad4f0595 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 2 Mar 2022 03:35:30 -0500 Subject: [PATCH 048/637] OPLL: fix volume setting --- src/engine/platform/opll.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/opll.cpp b/src/engine/platform/opll.cpp index 651ff4b7d..f49f48731 100644 --- a/src/engine/platform/opll.cpp +++ b/src/engine/platform/opll.cpp @@ -471,7 +471,7 @@ int DivPlatformOPLL::dispatch(DivCommand c) { if (!chan[c.chan].std.hasVol) { chan[c.chan].outVol=c.value; } - if (c.chan>=6 || properDrums) { + if (c.chan>=6 && properDrums) { drumVol[c.chan-6]=15-chan[c.chan].outVol; rWrite(0x36,drumVol[0]); rWrite(0x37,drumVol[1]|(drumVol[4]<<4)); From 251984922891053df733429d00e4006fe567e52b Mon Sep 17 00:00:00 2001 From: freq-mod <32672779+freq-mod@users.noreply.github.com> Date: Wed, 2 Mar 2022 10:14:18 +0100 Subject: [PATCH 049/637] OPLL systems --- src/engine/sysDef.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 52a4cb89e..3c988ceb8 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -463,6 +463,18 @@ const char* DivEngine::getSongSystemName() { return "Sega Genesis Extended Channel 3"; } + if (song.system[0]==DIV_SYSTEM_OPLL && song.system[1]==DIV_SYSTEM_SMS) { + return "NTSC-J Sega Master System"; + } + if (song.system[0]==DIV_SYSTEM_OPLL_DRUMS && song.system[1]==DIV_SYSTEM_SMS) { + return "NTSC-J Sega Master System + drums"; + } + if (song.system[0]==DIV_SYSTEM_OPLL && song.system[1]==DIV_SYSTEM_AY8910) { + return "MSX-MUSIC"; + } + if (song.system[0]==DIV_SYSTEM_OPLL_DRUMS && song.system[1]==DIV_SYSTEM_AY8910) { + return "MSX-MUSIC + drums"; + } if (song.system[0]==DIV_SYSTEM_C64_6581 && song.system[1]==DIV_SYSTEM_C64_6581) { return "Commodore 64 with dual 6581"; } From d4d1ade5134a5d49da216f8df034a5055dd70b62 Mon Sep 17 00:00:00 2001 From: cam900 Date: Thu, 3 Mar 2022 03:10:04 +0900 Subject: [PATCH 050/637] Add various system, Minor corrections YM2413 (drums mode) Standalone YM2413 with allows drum channel. Sound Expander for Commodore 64 OPL FM Sound expander cartridge for Commodore 64, it's placeholder until OPL is implemented. MSX-MUSIC: MSX's sound standard, appeared after MSX-AUDIO. it's basically OPLL FM sound expansion for MSX. SSI 2001: ISA Sound card with SID 6581. SID input clock is driven from ISA clock, so I modified flags value check routine. Sound Blaster w/Game Blaster Compatible Earliest Sound Blaster models has featured with Game Blaster compatiblity, It's has 2 SAA1099s like CMS/Game Blaster. It's removed at later models, but some hardware has just empty socket; you can restore this feature when you mount SAA1099 at empty socket. Sharp X1: Predecessor of X68000. it has built in AY PSG like competitors of the same period, but it has YM2151 FM sound addon in later models. FM sound is embedded in turbo Z, and that is succeeded by X68000. X68000 hasn't AY, instead OKI MSM6258. YM2151 in OutRun Board and X Board is 4MHz --- src/engine/platform/c64.cpp | 15 +++-- src/gui/gui.cpp | 113 ++++++++++++++++++++++++++++++++++-- 2 files changed, 119 insertions(+), 9 deletions(-) diff --git a/src/engine/platform/c64.cpp b/src/engine/platform/c64.cpp index 6257daead..889211332 100644 --- a/src/engine/platform/c64.cpp +++ b/src/engine/platform/c64.cpp @@ -509,10 +509,17 @@ void DivPlatformC64::setChipModel(bool is6581) { } void DivPlatformC64::setFlags(unsigned int flags) { - if (flags&1) { - rate=COLOR_PAL*2.0/9.0; - } else { - rate=COLOR_NTSC*2.0/7.0; + switch (flags&0xf) { + case 0x0: // NTSC C64 + rate=COLOR_NTSC*2.0/7.0; + break; + case 0x1: // PAL C64 + rate=COLOR_PAL*2.0/9.0; + break; + case 0x2: // SSI 2001 + default: + rate=14318180.0/16.0; + break; } chipClock=rate; } diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 0aeeb129e..5b4fc8134 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4714,7 +4714,7 @@ bool FurnaceGUI::loop() { e->setSysFlags(i,1,restart); updateWindowTitle(); } - if (ImGui::RadioButton("X68000 (4MHz)",flags==2)) { + if (ImGui::RadioButton("X1/X68000 (4MHz)",flags==2)) { e->setSysFlags(i,2,restart); updateWindowTitle(); } @@ -4733,6 +4733,21 @@ bool FurnaceGUI::loop() { updateWindowTitle(); } break; + case DIV_SYSTEM_C64_8580: + case DIV_SYSTEM_C64_6581: + if (ImGui::RadioButton("NTSC (1.02MHz)",flags==0)) { + e->setSysFlags(i,0,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("PAL (0.99MHz)",flags==1)) { + e->setSysFlags(i,1,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("SSI 2001 (0.89MHz)",flags==2)) { + e->setSysFlags(i,2,restart); + updateWindowTitle(); + } + break; case DIV_SYSTEM_AY8910: case DIV_SYSTEM_AY8930: { ImGui::Text("Clock rate:"); @@ -4748,7 +4763,7 @@ bool FurnaceGUI::loop() { e->setSysFlags(i,(flags&(~15))|2,restart); updateWindowTitle(); } - if (ImGui::RadioButton("2MHz (Atari ST)",(flags&15)==3)) { + if (ImGui::RadioButton("2MHz (Atari ST/Sharp X1)",(flags&15)==3)) { e->setSysFlags(i,(flags&(~15))|3,restart); updateWindowTitle(); } @@ -6157,6 +6172,12 @@ FurnaceGUI::FurnaceGUI(): 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha YM2413 (drums mode)", { + DIV_SYSTEM_OPLL_DRUMS, 64, 0, 0, + 0 + } + )); sysCategories.push_back(cat); cat=FurnaceGUISysCategory("Square"); @@ -6331,6 +6352,35 @@ FurnaceGUI::FurnaceGUI(): 0 } )); + /* + cat.systems.push_back(FurnaceGUISysDef( + "Commodore 64 (6581 SID + Sound Expander)", { + DIV_SYSTEM_OPL, 64, 0, 0, + DIV_SYSTEM_C64_6581, 64, 0, 1, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Commodore 64 (6581 SID + Sound Expander with drums mode)", { + DIV_SYSTEM_OPL_DRUMS, 64, 0, 0, + DIV_SYSTEM_C64_6581, 64, 0, 1, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Commodore 64 (8580 SID + Sound Expander)", { + DIV_SYSTEM_OPL, 64, 0, 0, + DIV_SYSTEM_C64_8580, 64, 0, 1, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Commodore 64 (8580 SID + Sound Expander with drums mode)", { + DIV_SYSTEM_OPL_DRUMS, 64, 0, 0, + DIV_SYSTEM_C64_8580, 64, 0, 1, + 0 + } + ));*/ cat.systems.push_back(FurnaceGUISysDef( "Amiga", { DIV_SYSTEM_AMIGA, 64, 0, 0, @@ -6343,6 +6393,20 @@ FurnaceGUI::FurnaceGUI(): 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "MSX + MSX-MUSIC", { + DIV_SYSTEM_OPLL, 64, 0, 0, + DIV_SYSTEM_AY8910, 64, 0, 16, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "MSX + MSX-MUSIC (drums mode)", { + DIV_SYSTEM_OPLL_DRUMS, 64, 0, 0, + DIV_SYSTEM_AY8910, 64, 0, 16, + 0 + } + )); cat.systems.push_back(FurnaceGUISysDef( "ZX Spectrum (48K)", { DIV_SYSTEM_AY8910, 64, 0, 2, @@ -6386,6 +6450,13 @@ FurnaceGUI::FurnaceGUI(): 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "PC + SSI 2001", { + DIV_SYSTEM_C64_6581, 64, 0, 2, + DIV_SYSTEM_PCSPKR, 64, 0, 0, + 0 + } + )); cat.systems.push_back(FurnaceGUISysDef( "PC + Game Blaster", { DIV_SYSTEM_SAA1099, 64, -127, 1, @@ -6408,6 +6479,24 @@ FurnaceGUI::FurnaceGUI(): 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "PC + Sound Blaster w/Game Blaster Compatible", { + DIV_SYSTEM_OPL2, 64, 0, 0, + DIV_SYSTEM_SAA1099, 64, -127, 1, + DIV_SYSTEM_SAA1099, 64, 127, 1, + DIV_SYSTEM_PCSPKR, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "PC + Sound Blaster w/Game Blaster Compatible (drums mode)", { + DIV_SYSTEM_OPL2_DRUMS, 64, 0, 0, + DIV_SYSTEM_SAA1099, 64, -127, 1, + DIV_SYSTEM_SAA1099, 64, 127, 1, + DIV_SYSTEM_PCSPKR, 64, 0, 0, + 0 + } + )); cat.systems.push_back(FurnaceGUISysDef( "PC + Sound Blaster Pro 2", { DIV_SYSTEM_OPL3, 64, 0, 0, @@ -6422,10 +6511,24 @@ FurnaceGUI::FurnaceGUI(): 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "Sharp X1", { + DIV_SYSTEM_AY8910, 64, 0, 3, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sharp X1 + FM Addon", { + DIV_SYSTEM_AY8910, 64, 0, 3, + DIV_SYSTEM_YM2151, 64, 0, 2, + 0 + } + )); /* cat.systems.push_back(FurnaceGUISysDef( "Sharp X68000", { - DIV_SYSTEM_AY8910, 64, 0, 16, + DIV_SYSTEM_YM2151, 64, 0, 2, + DIV_SYSTEM_MSM6258, 64, 0, 0, 0 } ));*/ @@ -6448,7 +6551,7 @@ FurnaceGUI::FurnaceGUI(): )); cat.systems.push_back(FurnaceGUISysDef( "Sega OutRun/X Board", { - DIV_SYSTEM_YM2151, 64, 0, 0, + DIV_SYSTEM_YM2151, 64, 0, 2, DIV_SYSTEM_SEGAPCM, 64, 0, 0, 0 } @@ -6552,7 +6655,7 @@ FurnaceGUI::FurnaceGUI(): )); cat.systems.push_back(FurnaceGUISysDef( "Arcade (YM2151 and SegaPCM)", { - DIV_SYSTEM_YM2151, 64, 0, 0, + DIV_SYSTEM_YM2151, 64, 0, 2, DIV_SYSTEM_SEGAPCM_COMPAT, 64, 0, 0, 0 } From 87561bf9cfd7d312aadce928e11535613d522384 Mon Sep 17 00:00:00 2001 From: cam900 Date: Thu, 3 Mar 2022 03:12:10 +0900 Subject: [PATCH 051/637] Fix spacing --- src/engine/platform/c64.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/engine/platform/c64.cpp b/src/engine/platform/c64.cpp index 889211332..49be60fb8 100644 --- a/src/engine/platform/c64.cpp +++ b/src/engine/platform/c64.cpp @@ -510,16 +510,16 @@ void DivPlatformC64::setChipModel(bool is6581) { void DivPlatformC64::setFlags(unsigned int flags) { switch (flags&0xf) { - case 0x0: // NTSC C64 + case 0x0: // NTSC C64 rate=COLOR_NTSC*2.0/7.0; - break; - case 0x1: // PAL C64 + break; + case 0x1: // PAL C64 rate=COLOR_PAL*2.0/9.0; - break; - case 0x2: // SSI 2001 - default: + break; + case 0x2: // SSI 2001 + default: rate=14318180.0/16.0; - break; + break; } chipClock=rate; } From 63456a88d838ba529b6c0eaf1d51cfb8b03f7d6d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 2 Mar 2022 16:10:18 -0500 Subject: [PATCH 052/637] get rid of SDL1 check see pull request #244 --- CMakeLists.txt | 43 ++++++++++++------------------------------- 1 file changed, 12 insertions(+), 31 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 761a4a299..af3e3a7d1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -150,39 +150,20 @@ endif() if (SYSTEM_SDL2) if (PKG_CONFIG_FOUND) - pkg_check_modules(SDL sdl>=${SYSTEM_SDL_MIN_VER}) - if (SDL_FOUND) - list(APPEND DEPENDENCIES_INCLUDE_DIRS ${SDL_INCLUDE_DIRS}) - list(APPEND DEPENDENCIES_COMPILE_OPTIONS ${SDL_CFLAGS_OTHER}) - list(APPEND DEPENDENCIES_LIBRARIES ${SDL_LIBRARIES}) - list(APPEND DEPENDENCIES_LIBRARY_DIRS ${SDL_LIBRARY_DIRS}) - list(APPEND DEPENDENCIES_LINK_OPTIONS ${SDL_LDFLAGS_OTHER}) - list(APPEND DEPENDENCIES_LEGACY_LDFLAGS ${SDL_LDFLAGS}) + pkg_check_modules(SDL2 sdl2>=${SYSTEM_SDL_MIN_VER}) + if (SDL2_FOUND) + list(APPEND DEPENDENCIES_INCLUDE_DIRS ${SDL2_INCLUDE_DIRS}) + list(APPEND DEPENDENCIES_COMPILE_OPTIONS ${SDL2_CFLAGS_OTHER}) + list(APPEND DEPENDENCIES_LIBRARIES ${SDL2_LIBRARIES}) + list(APPEND DEPENDENCIES_LIBRARY_DIRS ${SDL2_LIBRARY_DIRS}) + list(APPEND DEPENDENCIES_LINK_OPTIONS ${SDL2_LDFLAGS_OTHER}) + list(APPEND DEPENDENCIES_LEGACY_LDFLAGS ${SDL2_LDFLAGS}) endif() endif() - if (NOT SDL_FOUND) - find_package(SDL ${SYSTEM_SDL_MIN_VER}) - if (SDL_FOUND) - list(APPEND DEPENDENCIES_INCLUDE_DIRS ${SDL_INCLUDE_DIR}) - list(APPEND DEPENDENCIES_LIBRARIES ${SDL_LIBRARY}) - else() - if (PKG_CONFIG_FOUND) - pkg_check_modules(SDL2 sdl2>=${SYSTEM_SDL_MIN_VER}) - if (SDL2_FOUND) - list(APPEND DEPENDENCIES_INCLUDE_DIRS ${SDL2_INCLUDE_DIRS}) - list(APPEND DEPENDENCIES_COMPILE_OPTIONS ${SDL2_CFLAGS_OTHER}) - list(APPEND DEPENDENCIES_LIBRARIES ${SDL2_LIBRARIES}) - list(APPEND DEPENDENCIES_LIBRARY_DIRS ${SDL2_LIBRARY_DIRS}) - list(APPEND DEPENDENCIES_LINK_OPTIONS ${SDL2_LDFLAGS_OTHER}) - list(APPEND DEPENDENCIES_LEGACY_LDFLAGS ${SDL2_LDFLAGS}) - endif() - endif() - if (NOT SDL2_FOUND) - find_package(SDL2 ${SYSTEM_SDL_MIN_VER} REQUIRED) - list(APPEND DEPENDENCIES_INCLUDE_DIRS ${SDL2_INCLUDE_DIR}) - list(APPEND DEPENDENCIES_LIBRARIES ${SDL2_LIBRARY}) - endif() - endif() + if (NOT SDL2_FOUND) + find_package(SDL2 ${SYSTEM_SDL_MIN_VER} REQUIRED) + list(APPEND DEPENDENCIES_INCLUDE_DIRS ${SDL2_INCLUDE_DIR}) + list(APPEND DEPENDENCIES_LIBRARIES ${SDL2_LIBRARY}) endif() message(STATUS "Using system-installed SDL2") else() From abe7c12f22e37a8602f36d7a0f53ca670d3af4ec Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 2 Mar 2022 17:02:57 -0500 Subject: [PATCH 053/637] how am i gonna do this --- src/audio/taAudio.h | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/audio/taAudio.h b/src/audio/taAudio.h index 05348a7f0..10712bc03 100644 --- a/src/audio/taAudio.h +++ b/src/audio/taAudio.h @@ -128,11 +128,6 @@ class TAMidiOut { bool send(TAMidiMessage& what); }; -class TAMidi { - std::vector in; - std::vector out; -}; - class TAAudio { protected: TAAudioDesc desc; @@ -145,7 +140,8 @@ class TAAudio { void (*sampleRateChanged)(SampleRateChangeEvent); void (*bufferSizeChanged)(BufferSizeChangeEvent); public: - TAMidi* midi; + std::vector midiIn; + std::vector midiOut; void setSampleRateChangeCallback(void (*callback)(SampleRateChangeEvent)); void setBufferSizeChangeCallback(void (*callback)(BufferSizeChangeEvent)); From 5816f01c0739e2bf866b0d2b3ea6f0922dd11901 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 2 Mar 2022 18:25:17 -0500 Subject: [PATCH 054/637] Genesis: PSG clean-up since genesis is now split into YM2612 + SN, the "psg" inside DivPlatformGenesis becomes useless. --- src/engine/platform/genesis.cpp | 52 +++------------------------------ src/engine/platform/genesis.h | 3 -- 2 files changed, 4 insertions(+), 51 deletions(-) diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index 6f666306b..367bace65 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -133,17 +133,11 @@ void DivPlatformGenesis::acquire_nuked(short* bufL, short* bufR, size_t start, s //OPN2_Write(&fm,0,0); } - psgClocks+=psg.rate; - while (psgClocks>=rate) { - psgOut=(psg.acquireOne()*3)>>3; - psgClocks-=rate; - } - - os[0]=(os[0]<<5)+psgOut; + os[0]=(os[0]<<5); if (os[0]<-32768) os[0]=-32768; if (os[0]>32767) os[0]=32767; - os[1]=(os[1]<<5)+psgOut; + os[1]=(os[1]<<5); if (os[1]<-32768) os[1]=-32768; if (os[1]>32767) os[1]=32767; @@ -197,17 +191,9 @@ void DivPlatformGenesis::acquire_ymfm(short* bufL, short* bufR, size_t start, si os[1]=out_ymfm.data[1]; //OPN2_Write(&fm,0,0); - psgClocks+=psg.rate; - while (psgClocks>=rate) { - psgOut=(psg.acquireOne()*3)>>3; - psgClocks-=rate; - } - - os[0]=os[0]+psgOut; if (os[0]<-32768) os[0]=-32768; if (os[0]>32767) os[0]=32767; - os[1]=os[1]+psgOut; if (os[1]<-32768) os[1]=-32768; if (os[1]>32767) os[1]=32767; @@ -391,13 +377,6 @@ void DivPlatformGenesis::tick() { chan[i].keyOn=false; } } - - psg.tick(); - - for (DivRegWrite& i: psg.getRegisterWrites()) { - if (dumpWrites) addWrite(i.addr,i.val); - } - psg.getRegisterWrites().clear(); } int DivPlatformGenesis::octave(int freq) { @@ -442,10 +421,6 @@ int DivPlatformGenesis::toFreq(int freq) { } void DivPlatformGenesis::muteChannel(int ch, bool mute) { - if (ch>5) { - psg.muteChannel(ch-6,mute); - return; - } isMuted[ch]=mute; for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[ch]|opOffs[j]; @@ -464,10 +439,6 @@ void DivPlatformGenesis::muteChannel(int ch, bool mute) { } int DivPlatformGenesis::dispatch(DivCommand c) { - if (c.chan>5) { - c.chan-=6; - return psg.dispatch(c); - } switch (c.cmd) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(chan[c.chan].ins); @@ -786,16 +757,13 @@ void DivPlatformGenesis::forceIns() { rWrite(0x2b,0x80); } immWrite(0x22,lfoValue); - psg.forceIns(); } void DivPlatformGenesis::toggleRegisterDump(bool enable) { DivDispatch::toggleRegisterDump(enable); - psg.toggleRegisterDump(enable); } void* DivPlatformGenesis::getChanState(int ch) { - if (ch>5) return psg.getChanState(ch-6); return &chan[ch]; } @@ -844,12 +812,6 @@ void DivPlatformGenesis::reset() { immWrite(0x22,lfoValue); delay=0; - - // PSG - psg.reset(); - psg.getRegisterWrites().clear(); - psgClocks=0; - psgOut=0; } bool DivPlatformGenesis::isStereo() { @@ -865,17 +827,14 @@ bool DivPlatformGenesis::keyOffAffectsPorta(int ch) { } void DivPlatformGenesis::notifyInsChange(int ins) { - for (int i=0; i<10; i++) { - if (i>5) { - psg.notifyInsChange(ins); - } else if (chan[i].ins==ins) { + for (int i=0; i<6; i++) { + if (chan[i].ins==ins) { chan[i].insChanged=true; } } } void DivPlatformGenesis::notifyInsDeletion(void* ins) { - psg.notifyInsDeletion(ins); } void DivPlatformGenesis::poke(unsigned int addr, unsigned short val) { @@ -904,7 +863,6 @@ void DivPlatformGenesis::setFlags(unsigned int flags) { } else { chipClock=COLOR_NTSC*15.0/7.0; } - psg.setFlags(flags==1); ladder=flags&0x80000000; OPN2_SetChipType(ladder?ym3438_mode_ym2612:0); if (useYMFM) { @@ -929,7 +887,6 @@ int DivPlatformGenesis::init(DivEngine* p, int channels, int sugRate, unsigned i isMuted[i]=false; } fm_ymfm=NULL; - psg.init(p,4,sugRate,flags==1); setFlags(flags); reset(); @@ -938,7 +895,6 @@ int DivPlatformGenesis::init(DivEngine* p, int channels, int sugRate, unsigned i void DivPlatformGenesis::quit() { if (fm_ymfm!=NULL) delete fm_ymfm; - psg.quit(); } DivPlatformGenesis::~DivPlatformGenesis() { diff --git a/src/engine/platform/genesis.h b/src/engine/platform/genesis.h index 717c6c3dc..cd7b0396c 100644 --- a/src/engine/platform/genesis.h +++ b/src/engine/platform/genesis.h @@ -70,9 +70,6 @@ class DivPlatformGenesis: public DivDispatch { }; std::queue writes; ym3438_t fm; - DivPlatformSMS psg; - int psgClocks; - int psgOut; int delay; unsigned char lastBusy; From 1973992064accd3e1971f047f347ccde873f6086 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 2 Mar 2022 18:39:21 -0500 Subject: [PATCH 055/637] allocate system IDs for OKI chips --- papers/format.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/papers/format.md b/papers/format.md index ade5715de..073f92a8f 100644 --- a/papers/format.md +++ b/papers/format.md @@ -119,13 +119,13 @@ size | description | - 0x06: NES - 5 channels | - 0x07: C64 (8580) - 3 channels | - 0x08: Arcade (YM2151+SegaPCM) - 13 channels (compound!) - | - 0x09: Neo Geo (YM2610) - 13 channels + | - 0x09: Neo Geo CD (YM2610) - 13 channels | - bit 6 enables alternate mode: | - 0x42: Genesis extended - 13 channels | - 0x43: SMS (SN76489) + OPLL (YM2413) - 13 channels (compound!) | - 0x46: NES + VRC7 - 11 channels (compound!) | - 0x47: C64 (6581) - 3 channels - | - 0x49: Neo Geo extended - 16 channels + | - 0x49: Neo Geo CD extended - 16 channels | - bit 7 for non-DefleMask chips: | - 0x80: AY-3-8910 - 3 channels | - 0x81: Amiga - 4 channels @@ -164,10 +164,12 @@ size | description | - 0xa2: OPL drums (YM3526) - 11 channels | - 0xa3: OPL2 drums (YM3812) - 11 channels | - 0xa4: OPL3 drums (YMF262) - 20 channels - | - 0xa5: OPL3 4-op (YMF262) - 12 channels - | - 0xa6: OPL3 4-op + drums (YMF262) - 14 channels + | - 0xa5: Neo Geo (YM2610) - 14 channels + | - 0xa6: Neo Geo extended (YM2610) - 17 channels | - 0xa7: OPLL drums (YM2413) - 11 channels | - 0xa8: Atari Lynx - 4 channels + | - 0xa9: MSM6295 - 4 channels + | - 0xaa: MSM6258 - 1 channel | - 0xde: YM2610B extended - 19 channels | - 0xe0: QSound - 19 channels | - (compound!) means that the system is composed of two or more chips, From d5821b0157448e47958ad6620e26e21732c28441 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 2 Mar 2022 21:17:19 -0500 Subject: [PATCH 056/637] add a new demo song by TheRealHedgehogSonic --- demos/Coconut_Mall.fur | Bin 0 -> 5841 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 demos/Coconut_Mall.fur diff --git a/demos/Coconut_Mall.fur b/demos/Coconut_Mall.fur new file mode 100644 index 0000000000000000000000000000000000000000..471fcbec3ea7067d598ddfe6cfd0a19a3d6ed6f4 GIT binary patch literal 5841 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 o2}1lcd<=E{A~Hf(>UiD85)?l+n Date: Wed, 2 Mar 2022 21:17:55 -0500 Subject: [PATCH 057/637] attribution --- src/gui/gui.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 0aeeb129e..0c06de127 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1540,6 +1540,7 @@ const char* aboutLine[]={ "NikonTeen", "SuperJet Spade", "TheDuccinator", + "TheRealHedgehogSonic", "tildearrow", "Ultraprogramer", "", From c4f2090b48607c1773b748b0e0cacd8b51004262 Mon Sep 17 00:00:00 2001 From: cam900 Date: Thu, 3 Mar 2022 13:07:16 +0900 Subject: [PATCH 058/637] Deflemask compatibility --- src/gui/gui.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 5b4fc8134..dd2349c25 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -6655,7 +6655,7 @@ FurnaceGUI::FurnaceGUI(): )); cat.systems.push_back(FurnaceGUISysDef( "Arcade (YM2151 and SegaPCM)", { - DIV_SYSTEM_YM2151, 64, 0, 2, + DIV_SYSTEM_YM2151, 64, 0, 0, DIV_SYSTEM_SEGAPCM_COMPAT, 64, 0, 0, 0 } From e49ee1cd3df7c4f5f065a53ec253352313f203cb Mon Sep 17 00:00:00 2001 From: cam900 Date: Thu, 3 Mar 2022 14:14:54 +0900 Subject: [PATCH 059/637] 0xa9 is already allocated --- papers/format.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/papers/format.md b/papers/format.md index 073f92a8f..dc052d038 100644 --- a/papers/format.md +++ b/papers/format.md @@ -168,8 +168,9 @@ size | description | - 0xa6: Neo Geo extended (YM2610) - 17 channels | - 0xa7: OPLL drums (YM2413) - 11 channels | - 0xa8: Atari Lynx - 4 channels - | - 0xa9: MSM6295 - 4 channels - | - 0xaa: MSM6258 - 1 channel + | - 0xa9: SegaPCM (for Deflemask Compatibility) - 5 channels + | - 0xaa: MSM6295 - 4 channels + | - 0xab: MSM6258 - 1 channel | - 0xde: YM2610B extended - 19 channels | - 0xe0: QSound - 19 channels | - (compound!) means that the system is composed of two or more chips, From 5393b67c1d5e0bc3698e2f8e80b3ff7832f791da Mon Sep 17 00:00:00 2001 From: cam900 Date: Thu, 3 Mar 2022 16:03:40 +0900 Subject: [PATCH 060/637] Yamaha SFG-01 OPM FM sound expansion by Yamaha, for their CX series MSX computers: It's needs converter when connect it to standard MSX cartridge slot. Successor is SFG-05, It has YM2164 OPP instead YM2151 OPM. --- src/gui/gui.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index dd2349c25..e9e75980a 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -6393,6 +6393,13 @@ FurnaceGUI::FurnaceGUI(): 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "MSX + SFG-01", { + DIV_SYSTEM_YM2151, 64, 0, 0, + DIV_SYSTEM_AY8910, 64, 0, 16, + 0 + } + )); cat.systems.push_back(FurnaceGUISysDef( "MSX + MSX-MUSIC", { DIV_SYSTEM_OPLL, 64, 0, 0, From 52c03f1fd6bff12dc37df2811a1478ea4d9dec31 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 3 Mar 2022 04:36:52 -0500 Subject: [PATCH 061/637] OPLL: swap top and tom --- src/engine/platform/opll.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine/platform/opll.cpp b/src/engine/platform/opll.cpp index f49f48731..df4fca9ec 100644 --- a/src/engine/platform/opll.cpp +++ b/src/engine/platform/opll.cpp @@ -440,7 +440,7 @@ int DivPlatformOPLL::dispatch(DivCommand c) { } rWrite(0x36,drumVol[0]); rWrite(0x37,drumVol[1]|(drumVol[4]<<4)); - rWrite(0x38,drumVol[3]|(drumVol[2]<<4)); + rWrite(0x38,drumVol[2]|(drumVol[3]<<4)); } chan[c.chan].freqChanged=true; } @@ -475,7 +475,7 @@ int DivPlatformOPLL::dispatch(DivCommand c) { drumVol[c.chan-6]=15-chan[c.chan].outVol; rWrite(0x36,drumVol[0]); rWrite(0x37,drumVol[1]|(drumVol[4]<<4)); - rWrite(0x38,drumVol[3]|(drumVol[2]<<4)); + rWrite(0x38,drumVol[2]|(drumVol[3]<<4)); break; } if (c.chan<6 || !drums) { From 4e6ade7e0b10a500653cab02b9144d22928ed9bb Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 3 Mar 2022 04:37:22 -0500 Subject: [PATCH 062/637] more MIDI preparations this is difficult --- src/audio/rtmidi.h | 11 ++++++++++- src/audio/taAudio.h | 15 +++++++++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/audio/rtmidi.h b/src/audio/rtmidi.h index 5fbba78fe..0803c3fcd 100644 --- a/src/audio/rtmidi.h +++ b/src/audio/rtmidi.h @@ -17,4 +17,13 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "../../extern/rtmidi/RtMidi.h" \ No newline at end of file +#include "../../extern/rtmidi/RtMidi.h" +#include "taAudio.h" + +class TAMidiInRtMidi: public TAMidiIn { + +}; + +class TAMidiOutRtMidi: public TAMidiOut { + +}; \ No newline at end of file diff --git a/src/audio/taAudio.h b/src/audio/taAudio.h index 10712bc03..1f8c32305 100644 --- a/src/audio/taAudio.h +++ b/src/audio/taAudio.h @@ -20,6 +20,7 @@ #ifndef _TAAUDIO_H #define _TAAUDIO_H #include "../ta-utils.h" +#include #include struct SampleRateChangeEvent { @@ -116,14 +117,24 @@ struct TAMidiMessage { void submitSysEx(std::vector data); void done(); + + TAMidiMessage(): + type(0), + sysExData(NULL), + sysExLen(0) { + memset(&data,0,sizeof(data)); + } }; class TAMidiIn { + std::queue queue; public: + virtual bool gather(); bool next(TAMidiMessage& where); }; class TAMidiOut { + std::queue queue; public: bool send(TAMidiMessage& what); }; @@ -140,8 +151,8 @@ class TAAudio { void (*sampleRateChanged)(SampleRateChangeEvent); void (*bufferSizeChanged)(BufferSizeChangeEvent); public: - std::vector midiIn; - std::vector midiOut; + TAMidiIn* midiIn; + TAMidiOut* midiOut; void setSampleRateChangeCallback(void (*callback)(SampleRateChangeEvent)); void setBufferSizeChangeCallback(void (*callback)(BufferSizeChangeEvent)); From 64146bed5ef921da386677e55e844268690af886 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 3 Mar 2022 04:53:40 -0500 Subject: [PATCH 063/637] OPLL: i think i got it --- src/engine/platform/opll.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/engine/platform/opll.cpp b/src/engine/platform/opll.cpp index df4fca9ec..7ae61745d 100644 --- a/src/engine/platform/opll.cpp +++ b/src/engine/platform/opll.cpp @@ -440,7 +440,7 @@ int DivPlatformOPLL::dispatch(DivCommand c) { } rWrite(0x36,drumVol[0]); rWrite(0x37,drumVol[1]|(drumVol[4]<<4)); - rWrite(0x38,drumVol[2]|(drumVol[3]<<4)); + rWrite(0x38,drumVol[3]|(drumVol[2]<<4)); } chan[c.chan].freqChanged=true; } @@ -475,10 +475,9 @@ int DivPlatformOPLL::dispatch(DivCommand c) { drumVol[c.chan-6]=15-chan[c.chan].outVol; rWrite(0x36,drumVol[0]); rWrite(0x37,drumVol[1]|(drumVol[4]<<4)); - rWrite(0x38,drumVol[2]|(drumVol[3]<<4)); + rWrite(0x38,drumVol[3]|(drumVol[2]<<4)); break; - } - if (c.chan<6 || !drums) { + } else if (c.chan<6 || !drums) { rWrite(0x30+c.chan,((15-(chan[c.chan].outVol*(15-chan[c.chan].state.op[1].tl))/15)&15)|(chan[c.chan].state.opllPreset<<4)); } break; From e0cda2a77f4b6902be67ee97036c6396d0b22903 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 3 Mar 2022 12:44:29 -0500 Subject: [PATCH 064/637] GUI: add colors base setting pave the way for.light themes --- src/gui/gui.cpp | 6 +++++- src/gui/gui.h | 2 ++ src/gui/settings.cpp | 9 +++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 0c06de127..4e71e0d45 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -5440,7 +5440,11 @@ void FurnaceGUI::parseKeybinds() { void FurnaceGUI::applyUISettings() { ImGuiStyle sty; - ImGui::StyleColorsDark(&sty); + if (settings.guiColorsBase) { + ImGui::StyleColorsLight(&sty); + } else { + ImGui::StyleColorsDark(&sty); + } if (settings.dpiScale>=0.5f) dpiScale=settings.dpiScale; diff --git a/src/gui/gui.h b/src/gui/gui.h index e41d71450..dee44f59c 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -493,6 +493,7 @@ class FurnaceGUI { int statusDisplay; float dpiScale; int viewPrevPattern; + int guiColorsBase; unsigned int maxUndoSteps; String mainFontPath; String patFontPath; @@ -533,6 +534,7 @@ class FurnaceGUI { statusDisplay(0), dpiScale(0.0f), viewPrevPattern(1), + guiColorsBase(0), maxUndoSteps(100), mainFontPath(""), patFontPath(""), diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 136e95122..e40d47a13 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -425,6 +425,13 @@ void FurnaceGUI::drawSettings() { if (ImGui::TreeNode("Color scheme")) { if (ImGui::TreeNode("General")) { + ImGui::Text("Color scheme type:"); + if (ImGui::RadioButton("Dark##gcb0",settings.guiColorsBase==0)) { + settings.guiColorsBase=0; + } + if (ImGui::RadioButton("Light##gcb1",settings.guiColorsBase==1)) { + settings.guiColorsBase=1; + } UI_COLOR_CONFIG(GUI_COLOR_BACKGROUND,"Background"); UI_COLOR_CONFIG(GUI_COLOR_FRAME_BACKGROUND,"Window background"); UI_COLOR_CONFIG(GUI_COLOR_MODAL_BACKDROP,"Modal backdrop"); @@ -856,6 +863,7 @@ void FurnaceGUI::syncSettings() { settings.statusDisplay=e->getConfInt("statusDisplay",0); settings.dpiScale=e->getConfFloat("dpiScale",0.0f); settings.viewPrevPattern=e->getConfInt("viewPrevPattern",1); + settings.guiColorsBase=e->getConfInt("guiColorsBase",0); // keybinds LOAD_KEYBIND(GUI_ACTION_OPEN,FURKMOD_CMD|SDLK_o); @@ -1051,6 +1059,7 @@ void FurnaceGUI::commitSettings() { e->setConf("statusDisplay",settings.statusDisplay); e->setConf("dpiScale",settings.dpiScale); e->setConf("viewPrevPattern",settings.viewPrevPattern); + e->setConf("guiColorsBase",settings.guiColorsBase); PUT_UI_COLOR(GUI_COLOR_BACKGROUND); PUT_UI_COLOR(GUI_COLOR_FRAME_BACKGROUND); From 29474cd5c72c8aadac6c6102936a97842460a803 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 3 Mar 2022 14:50:23 -0500 Subject: [PATCH 065/637] GUI: possibly expand fixed arp macro range untested --- src/gui/insEdit.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 408c3c150..0f16cde04 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1311,7 +1311,7 @@ void FurnaceGUI::drawInsEdit() { if (volMax>0) { NORMAL_MACRO(ins->std.volMacro,ins->std.volMacroLen,ins->std.volMacroLoop,ins->std.volMacroRel,volMin,volMax,"vol",volumeLabel,160,ins->std.volMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_VOLUME],mmlString[0],volMin,volMax,NULL,false); } - NORMAL_MACRO(ins->std.arpMacro,ins->std.arpMacroLen,ins->std.arpMacroLoop,ins->std.arpMacroRel,arpMacroScroll,arpMacroScroll+24,"arp","Arpeggio",160,ins->std.arpMacroOpen,false,NULL,true,&arpMacroScroll,(arpMode?0:-80),0,0,&ins->std.arpMacroMode,uiColors[GUI_COLOR_MACRO_PITCH],mmlString[1],-92,94,(ins->std.arpMacroMode?(¯oHoverNote):NULL),true); + NORMAL_MACRO(ins->std.arpMacro,ins->std.arpMacroLen,ins->std.arpMacroLoop,ins->std.arpMacroRel,arpMacroScroll,arpMacroScroll+24,"arp","Arpeggio",160,ins->std.arpMacroOpen,false,NULL,true,&arpMacroScroll,(arpMode?-60:-80),0,0,&ins->std.arpMacroMode,uiColors[GUI_COLOR_MACRO_PITCH],mmlString[1],-92,94,(ins->std.arpMacroMode?(¯oHoverNote):NULL),true); if (dutyMax>0) { if (ins->type == DIV_INS_MIKEY) { NORMAL_MACRO(ins->std.dutyMacro,ins->std.dutyMacroLen,ins->std.dutyMacroLoop,ins->std.dutyMacroRel,0,dutyMax,"duty",dutyLabel,160,ins->std.dutyMacroOpen,true,mikeyFeedbackBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[2],0,dutyMax,NULL,false); From e0af849a4f552b04d6d08e13b70dbd362adcd380 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 3 Mar 2022 16:21:19 -0500 Subject: [PATCH 066/637] OPLL: TODO high byte? --- src/engine/platform/opll.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/engine/platform/opll.cpp b/src/engine/platform/opll.cpp index 7ae61745d..9d56d609e 100644 --- a/src/engine/platform/opll.cpp +++ b/src/engine/platform/opll.cpp @@ -264,6 +264,7 @@ void DivPlatformOPLL::tick() { immWrite(0x20+drumSlot[i],freqt>>8); } else if (i<6 || !drums) { immWrite(0x10+i,freqt&0xff); + // TODO high byte? } } if (chan[i].keyOn && i>=6 && properDrums) { From 99bee89ccab59851220afece53fcd17bc146ac73 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 3 Mar 2022 16:59:31 -0500 Subject: [PATCH 067/637] GUI: better adapt shades to light theme --- src/gui/gui.cpp | 23 ++++++++++++++++++----- src/gui/pattern.cpp | 12 +++++++++--- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 4e71e0d45..ef0e0d86f 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -5545,8 +5545,14 @@ void FurnaceGUI::applyUISettings() { primaryHover.w=primaryActive.w; primary.w=primaryActive.w; ImGui::ColorConvertRGBtoHSV(primaryActive.x,primaryActive.y,primaryActive.z,hue,sat,val); - ImGui::ColorConvertHSVtoRGB(hue,sat*0.9,val*0.5,primaryHover.x,primaryHover.y,primaryHover.z); - ImGui::ColorConvertHSVtoRGB(hue,sat*0.8,val*0.35,primary.x,primary.y,primary.z); + if (settings.guiColorsBase) { + primary=primaryActive; + ImGui::ColorConvertHSVtoRGB(hue,sat*0.9,val*0.9,primaryHover.x,primaryHover.y,primaryHover.z); + ImGui::ColorConvertHSVtoRGB(hue,sat,val*0.5,primaryActive.x,primaryActive.y,primaryActive.z); + } else { + ImGui::ColorConvertHSVtoRGB(hue,sat*0.9,val*0.5,primaryHover.x,primaryHover.y,primaryHover.z); + ImGui::ColorConvertHSVtoRGB(hue,sat*0.8,val*0.35,primary.x,primary.y,primary.z); + } ImVec4 secondaryActive=uiColors[GUI_COLOR_ACCENT_SECONDARY]; ImVec4 secondaryHover, secondary, secondarySemiActive; @@ -5554,9 +5560,16 @@ void FurnaceGUI::applyUISettings() { secondaryHover.w=secondaryActive.w; secondary.w=secondaryActive.w; ImGui::ColorConvertRGBtoHSV(secondaryActive.x,secondaryActive.y,secondaryActive.z,hue,sat,val); - ImGui::ColorConvertHSVtoRGB(hue,sat*0.9,val*0.75,secondarySemiActive.x,secondarySemiActive.y,secondarySemiActive.z); - ImGui::ColorConvertHSVtoRGB(hue,sat*0.9,val*0.5,secondaryHover.x,secondaryHover.y,secondaryHover.z); - ImGui::ColorConvertHSVtoRGB(hue,sat*0.9,val*0.25,secondary.x,secondary.y,secondary.z); + if (settings.guiColorsBase) { + secondary=secondaryActive; + ImGui::ColorConvertHSVtoRGB(hue,sat*0.9,val*0.7,secondarySemiActive.x,secondarySemiActive.y,secondarySemiActive.z); + ImGui::ColorConvertHSVtoRGB(hue,sat*0.9,val*0.9,secondaryHover.x,secondaryHover.y,secondaryHover.z); + ImGui::ColorConvertHSVtoRGB(hue,sat,val*0.5,secondaryActive.x,secondaryActive.y,secondaryActive.z); + } else { + ImGui::ColorConvertHSVtoRGB(hue,sat*0.9,val*0.75,secondarySemiActive.x,secondarySemiActive.y,secondarySemiActive.z); + ImGui::ColorConvertHSVtoRGB(hue,sat*0.9,val*0.5,secondaryHover.x,secondaryHover.y,secondaryHover.z); + ImGui::ColorConvertHSVtoRGB(hue,sat*0.9,val*0.25,secondary.x,secondary.y,secondary.z); + } sty.Colors[ImGuiCol_WindowBg]=uiColors[GUI_COLOR_FRAME_BACKGROUND]; diff --git a/src/gui/pattern.cpp b/src/gui/pattern.cpp index 7d9eb43ce..3d2f15cb8 100644 --- a/src/gui/pattern.cpp +++ b/src/gui/pattern.cpp @@ -448,9 +448,15 @@ void FurnaceGUI::drawPattern() { keyHit[i]=0.2; e->keyHit[i]=false; } - chanHead.x*=0.25+keyHit[i]; chanHead.y*=0.25+keyHit[i]; chanHead.z*=0.25+keyHit[i]; - chanHeadActive.x*=0.8; chanHeadActive.y*=0.8; chanHeadActive.z*=0.8; - chanHeadHover.x*=0.4+keyHit[i]; chanHeadHover.y*=0.4+keyHit[i]; chanHeadHover.z*=0.4+keyHit[i]; + if (settings.guiColorsBase) { + chanHead.x*=1.0-keyHit[i]; chanHead.y*=1.0-keyHit[i]; chanHead.z*=1.0-keyHit[i]; + chanHeadActive.x*=0.5; chanHeadActive.y*=0.5; chanHeadActive.z*=0.5; + chanHeadHover.x*=0.9-keyHit[i]; chanHeadHover.y*=0.9-keyHit[i]; chanHeadHover.z*=0.9-keyHit[i]; + } else { + chanHead.x*=0.25+keyHit[i]; chanHead.y*=0.25+keyHit[i]; chanHead.z*=0.25+keyHit[i]; + chanHeadActive.x*=0.8; chanHeadActive.y*=0.8; chanHeadActive.z*=0.8; + chanHeadHover.x*=0.4+keyHit[i]; chanHeadHover.y*=0.4+keyHit[i]; chanHeadHover.z*=0.4+keyHit[i]; + } keyHit[i]-=0.02*60.0*ImGui::GetIO().DeltaTime; if (keyHit[i]<0) keyHit[i]=0; ImGui::PushStyleColor(ImGuiCol_Header,chanHead); From aa446d5c439abf7282567a3cb31aab9f97f053df Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 3 Mar 2022 17:03:44 -0500 Subject: [PATCH 068/637] PCE: don't mute channel 2 when LFO is on apparently this is hardware behavior --- src/engine/platform/sound/pce_psg.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/engine/platform/sound/pce_psg.cpp b/src/engine/platform/sound/pce_psg.cpp index ed71e0fd2..f9c1ee099 100644 --- a/src/engine/platform/sound/pce_psg.cpp +++ b/src/engine/platform/sound/pce_psg.cpp @@ -157,12 +157,6 @@ void PCE_PSG::RecalcUOFunc(int chnum) //printf("UO Update: %d, %02x\n", chnum, ch->control); - // what is this? - if (lfoctrl&3 && chnum==1) { - ch->UpdateOutput = &PCE_PSG::UpdateOutput_Off; - return; - } - if((revision != REVISION_HUC6280 && !(ch->control & 0xC0)) || (revision == REVISION_HUC6280 && !(ch->control & 0x80))) ch->UpdateOutput = &PCE_PSG::UpdateOutput_Off; else if(ch->noisectrl & ch->control & 0x80) From 05bdfe74dbdf9219bfc0731a660669c1ffd9a5be Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 3 Mar 2022 17:23:55 -0500 Subject: [PATCH 069/637] GUI: fix some out-of-range sliders in OPLL --- src/gui/insEdit.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 0f16cde04..527ee6276 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -863,6 +863,7 @@ void FurnaceGUI::drawInsEdit() { bool willDisplayOps=true; if (ins->type==DIV_INS_OPLL && ins->fm.opllPreset!=0) willDisplayOps=false; if (!willDisplayOps && ins->type==DIV_INS_OPLL) { + ins->fm.op[1].tl&=15; P(ImGui::SliderScalar("Volume##TL",ImGuiDataType_U8,&ins->fm.op[1].tl,&_FIFTEEN,&_ZERO)); rightClickable } if (willDisplayOps) if (ImGui::BeginTable("FMOperators",2,ImGuiTableFlags_SizingStretchSame)) { @@ -917,6 +918,7 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + op.ar&=maxArDr; P(ImGui::SliderScalar("##AR",ImGuiDataType_U8,&op.ar,&maxArDr,&_ZERO)); rightClickable ImGui::TableNextColumn(); ImGui::Text("%s",FM_NAME(FM_AR)); @@ -924,6 +926,7 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + op.dr&=maxArDr; P(ImGui::SliderScalar("##DR",ImGuiDataType_U8,&op.dr,&maxArDr,&_ZERO)); rightClickable ImGui::TableNextColumn(); ImGui::Text("%s",FM_NAME(FM_DR)); @@ -954,6 +957,7 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + op.tl&=maxTl; P(ImGui::SliderScalar("##TL",ImGuiDataType_U8,&op.tl,&maxTl,&_ZERO)); rightClickable ImGui::TableNextColumn(); ImGui::Text("%s",FM_NAME(FM_TL)); From 862154b0a978b934cc9bde4076371bcb220ca5f3 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 3 Mar 2022 17:56:09 -0500 Subject: [PATCH 070/637] OPLL: fix drum mode slides --- src/engine/platform/opll.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/engine/platform/opll.cpp b/src/engine/platform/opll.cpp index 9d56d609e..cb89215ce 100644 --- a/src/engine/platform/opll.cpp +++ b/src/engine/platform/opll.cpp @@ -257,7 +257,6 @@ void DivPlatformOPLL::tick() { chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq)); if (chan[i].freq>262143) chan[i].freq=262143; int freqt=toFreq(chan[i].freq); - chan[i].freqH=freqt>>8; chan[i].freqL=freqt&0xff; if (i>=6 && properDrums) { immWrite(0x10+drumSlot[i],freqt&0xff); @@ -266,13 +265,14 @@ void DivPlatformOPLL::tick() { immWrite(0x10+i,freqt&0xff); // TODO high byte? } + chan[i].freqH=freqt>>8; } if (chan[i].keyOn && i>=6 && properDrums) { if (!isMuted[i]) { drumState|=(0x10>>(i-6)); immWrite(0x0e,0x20|drumState); } - chan[i].keyOn=false; + chan[i].keyOn=false; } else if (chan[i].keyOn && i>=6 && drums) { //printf("%d\n",chan[i].note%12); drumState|=(0x10>>(chan[i].note%12)); @@ -280,7 +280,9 @@ void DivPlatformOPLL::tick() { chan[i].keyOn=false; } else if ((chan[i].keyOn || chan[i].freqChanged) && i<9) { //immWrite(0x28,0xf0|konOffs[i]); - immWrite(0x20+i,(chan[i].freqH)|(chan[i].active<<4)|(chan[i].state.alg?0x20:0)); + if (!(i>=6 && properDrums)) { + immWrite(0x20+i,(chan[i].freqH)|(chan[i].active<<4)|(chan[i].state.alg?0x20:0)); + } chan[i].keyOn=false; } chan[i].freqChanged=false; From e342078f9ab64e69fa57090124b318355d3b74fa Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 3 Mar 2022 18:08:21 -0500 Subject: [PATCH 071/637] OPLL: fix mod/car KSL being swapped --- src/engine/platform/opll.cpp | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/engine/platform/opll.cpp b/src/engine/platform/opll.cpp index cb89215ce..a6b9063fa 100644 --- a/src/engine/platform/opll.cpp +++ b/src/engine/platform/opll.cpp @@ -159,15 +159,15 @@ void DivPlatformOPLL::tick() { } if (chan[i].std.hadFb) { chan[i].state.fb=chan[i].std.fb; - rWrite(0x03,(chan[i].state.op[0].ksl<<6)|((chan[i].state.fms&1)<<4)|((chan[i].state.ams&1)<<3)|chan[i].state.fb); + rWrite(0x03,(chan[i].state.op[1].ksl<<6)|((chan[i].state.fms&1)<<4)|((chan[i].state.ams&1)<<3)|chan[i].state.fb); } if (chan[i].std.hadFms) { chan[i].state.fms=chan[i].std.fms; - rWrite(0x03,(chan[i].state.op[0].ksl<<6)|((chan[i].state.fms&1)<<4)|((chan[i].state.ams&1)<<3)|chan[i].state.fb); + rWrite(0x03,(chan[i].state.op[1].ksl<<6)|((chan[i].state.fms&1)<<4)|((chan[i].state.ams&1)<<3)|chan[i].state.fb); } if (chan[i].std.hadAms) { chan[i].state.ams=chan[i].std.ams; - rWrite(0x03,(chan[i].state.op[0].ksl<<6)|((chan[i].state.fms&1)<<4)|((chan[i].state.ams&1)<<3)|chan[i].state.fb); + rWrite(0x03,(chan[i].state.op[1].ksl<<6)|((chan[i].state.fms&1)<<4)|((chan[i].state.ams&1)<<3)|chan[i].state.fb); } for (int j=0; j<2; j++) { @@ -203,7 +203,7 @@ void DivPlatformOPLL::tick() { if (j==1) { rWrite(0x30+i,((15-(chan[i].outVol*(15-chan[i].state.op[1].tl))/15)&15)|(chan[i].state.opllPreset<<4)); } else { - rWrite(0x02,(chan[i].state.op[1].ksl<<6)|(op.tl&63)); + rWrite(0x02,(chan[i].state.op[0].ksl<<6)|(op.tl&63)); } } @@ -214,9 +214,9 @@ void DivPlatformOPLL::tick() { if (m.hadKsl) { op.ksl=m.ksl; if (j==1) { - rWrite(0x02,(op.ksl<<6)|(chan[i].state.op[0].tl&63)); + rWrite(0x02,(chan[i].state.op[0].ksl<<6)|(chan[i].state.op[0].tl&63)); } else { - rWrite(0x03,(chan[i].state.op[0].ksl<<6)|((chan[i].state.fms&1)<<4)|((chan[i].state.ams&1)<<3)|chan[i].state.fb); + rWrite(0x03,(chan[i].state.op[1].ksl<<6)|((chan[i].state.fms&1)<<4)|((chan[i].state.ams&1)<<3)|chan[i].state.fb); } } if (m.hadKsr) { @@ -384,8 +384,8 @@ int DivPlatformOPLL::dispatch(DivCommand c) { DivInstrumentFM::Operator& car=chan[c.chan].state.op[1]; rWrite(0x00,(mod.am<<7)|(mod.vib<<6)|((mod.ssgEnv&8)<<2)|(mod.ksr<<4)|(mod.mult)); rWrite(0x01,(car.am<<7)|(car.vib<<6)|((car.ssgEnv&8)<<2)|(car.ksr<<4)|(car.mult)); - rWrite(0x02,(car.ksl<<6)|(mod.tl&63)); - rWrite(0x03,(mod.ksl<<6)|((chan[c.chan].state.fms&1)<<4)|((chan[c.chan].state.ams&1)<<3)|chan[c.chan].state.fb); + rWrite(0x02,(mod.ksl<<6)|(mod.tl&63)); + rWrite(0x03,(car.ksl<<6)|((chan[c.chan].state.fms&1)<<4)|((chan[c.chan].state.ams&1)<<3)|chan[c.chan].state.fb); rWrite(0x04,(mod.ar<<4)|(mod.dr)); rWrite(0x05,(car.ar<<4)|(car.dr)); rWrite(0x06,(mod.sl<<4)|(mod.rr)); @@ -543,10 +543,10 @@ int DivPlatformOPLL::dispatch(DivCommand c) { } case DIV_CMD_FM_FB: { if (c.chan>=9 && !properDrums) return 0; - DivInstrumentFM::Operator& mod=chan[c.chan].state.op[0]; - //DivInstrumentFM::Operator& car=chan[c.chan].state.op[1]; + //DivInstrumentFM::Operator& mod=chan[c.chan].state.op[0]; + DivInstrumentFM::Operator& car=chan[c.chan].state.op[1]; chan[c.chan].state.fb=c.value&7; - rWrite(0x03,(mod.ksl<<6)|((chan[c.chan].state.fms&1)<<4)|((chan[c.chan].state.ams&1)<<3)|chan[c.chan].state.fb); + rWrite(0x03,(car.ksl<<6)|((chan[c.chan].state.fms&1)<<4)|((chan[c.chan].state.ams&1)<<3)|chan[c.chan].state.fb); break; } @@ -567,9 +567,9 @@ int DivPlatformOPLL::dispatch(DivCommand c) { if (c.chan>=9 && !properDrums) return 0; if (c.value==0) { DivInstrumentFM::Operator& mod=chan[c.chan].state.op[0]; - DivInstrumentFM::Operator& car=chan[c.chan].state.op[1]; + //DivInstrumentFM::Operator& car=chan[c.chan].state.op[1]; mod.tl=c.value2&63; - rWrite(0x02,(car.ksl<<6)|(mod.tl&63)); + rWrite(0x02,(mod.ksl<<6)|(mod.tl&63)); } else { DivInstrumentFM::Operator& car=chan[c.chan].state.op[1]; car.tl=c.value2&15; @@ -634,8 +634,8 @@ void DivPlatformOPLL::forceIns() { DivInstrumentFM::Operator& car=chan[i].state.op[1]; rWrite(0x00,(mod.am<<7)|(mod.vib<<6)|((mod.ssgEnv&8)<<2)|(mod.ksr<<4)|(mod.mult)); rWrite(0x01,(car.am<<7)|(car.vib<<6)|((car.ssgEnv&8)<<2)|(car.ksr<<4)|(car.mult)); - rWrite(0x02,(car.ksl<<6)|(mod.tl&63)); - rWrite(0x03,(mod.ksl<<6)|((chan[i].state.fms&1)<<4)|((chan[i].state.ams&1)<<3)|chan[i].state.fb); + rWrite(0x02,(mod.ksl<<6)|(mod.tl&63)); + rWrite(0x03,(car.ksl<<6)|((chan[i].state.fms&1)<<4)|((chan[i].state.ams&1)<<3)|chan[i].state.fb); rWrite(0x04,(mod.ar<<4)|(mod.dr)); rWrite(0x05,(car.ar<<4)|(car.dr)); rWrite(0x06,(mod.sl<<4)|(mod.rr)); From 6132aa666e9418c2d81c9e1ea0c5a56b803e53d7 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 3 Mar 2022 18:10:41 -0500 Subject: [PATCH 072/637] GUI: hide duty/wave macros in OPLL/OPL --- src/gui/insEdit.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 527ee6276..3f5ac067d 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1284,6 +1284,9 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_TIA || ins->type==DIV_INS_PCE || ins->type==DIV_INS_AMIGA) { dutyMax=0; } + if (ins->type==DIV_INS_OPLL || ins->type==DIV_INS_OPL) { + dutyMax=0; + } bool dutyIsRel=(ins->type==DIV_INS_C64 && !ins->c64.dutyIsAbs); int waveMax=(ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930)?3:63; @@ -1295,7 +1298,7 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_TIA) waveMax=15; if (ins->type==DIV_INS_C64) waveMax=4; if (ins->type==DIV_INS_SAA1099) waveMax=2; - if (ins->type==DIV_INS_FM) waveMax=0; + if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPLL || ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPZ) waveMax=0; if (ins->type==DIV_INS_MIKEY) waveMax=0; const char** waveNames=ayShapeBits; @@ -1317,7 +1320,7 @@ void FurnaceGUI::drawInsEdit() { } NORMAL_MACRO(ins->std.arpMacro,ins->std.arpMacroLen,ins->std.arpMacroLoop,ins->std.arpMacroRel,arpMacroScroll,arpMacroScroll+24,"arp","Arpeggio",160,ins->std.arpMacroOpen,false,NULL,true,&arpMacroScroll,(arpMode?-60:-80),0,0,&ins->std.arpMacroMode,uiColors[GUI_COLOR_MACRO_PITCH],mmlString[1],-92,94,(ins->std.arpMacroMode?(¯oHoverNote):NULL),true); if (dutyMax>0) { - if (ins->type == DIV_INS_MIKEY) { + if (ins->type==DIV_INS_MIKEY) { NORMAL_MACRO(ins->std.dutyMacro,ins->std.dutyMacroLen,ins->std.dutyMacroLoop,ins->std.dutyMacroRel,0,dutyMax,"duty",dutyLabel,160,ins->std.dutyMacroOpen,true,mikeyFeedbackBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[2],0,dutyMax,NULL,false); } else { From 3a6f664cf036da12b69fb025bbf1c14b3e0e6f7a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 3 Mar 2022 18:19:51 -0500 Subject: [PATCH 073/637] OPLL: don't write to out of range registers fixes OPLLTest(1).zip --- src/engine/platform/opll.cpp | 37 ++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/src/engine/platform/opll.cpp b/src/engine/platform/opll.cpp index a6b9063fa..6864c8bf1 100644 --- a/src/engine/platform/opll.cpp +++ b/src/engine/platform/opll.cpp @@ -133,7 +133,9 @@ void DivPlatformOPLL::tick() { if (chan[i].std.hadVol) { chan[i].outVol=(chan[i].vol*MIN(15,chan[i].std.vol))/15; - rWrite(0x30+i,((15-(chan[i].outVol*(15-chan[i].state.op[1].tl))/15)&15)|(chan[i].state.opllPreset<<4)); + if (i<9) { + rWrite(0x30+i,((15-(chan[i].outVol*(15-chan[i].state.op[1].tl))/15)&15)|(chan[i].state.opllPreset<<4)); + } } if (chan[i].std.hadArp) { @@ -201,7 +203,9 @@ void DivPlatformOPLL::tick() { if (m.hadTl) { op.tl=((j==1)?15:63)-m.tl; if (j==1) { - rWrite(0x30+i,((15-(chan[i].outVol*(15-chan[i].state.op[1].tl))/15)&15)|(chan[i].state.opllPreset<<4)); + if (i<9) { + rWrite(0x30+i,((15-(chan[i].outVol*(15-chan[i].state.op[1].tl))/15)&15)|(chan[i].state.opllPreset<<4)); + } } else { rWrite(0x02,(chan[i].state.op[0].ksl<<6)|(op.tl&63)); } @@ -238,7 +242,9 @@ void DivPlatformOPLL::tick() { drumState&=~(0x10>>(chan[i].note%12)); immWrite(0x0e,0x20|drumState); } else { - immWrite(0x20+i,(chan[i].freqH)/*|(chan[i].state.alg?0x20:0)*/); + if (i<9) { + immWrite(0x20+i,(chan[i].freqH)/*|(chan[i].state.alg?0x20:0)*/); + } } //chan[i].keyOn=false; chan[i].keyOff=false; @@ -262,8 +268,9 @@ void DivPlatformOPLL::tick() { immWrite(0x10+drumSlot[i],freqt&0xff); immWrite(0x20+drumSlot[i],freqt>>8); } else if (i<6 || !drums) { - immWrite(0x10+i,freqt&0xff); - // TODO high byte? + if (i<9) { + immWrite(0x10+i,freqt&0xff); + } } chan[i].freqH=freqt>>8; } @@ -281,7 +288,9 @@ void DivPlatformOPLL::tick() { } else if ((chan[i].keyOn || chan[i].freqChanged) && i<9) { //immWrite(0x28,0xf0|konOffs[i]); if (!(i>=6 && properDrums)) { - immWrite(0x20+i,(chan[i].freqH)|(chan[i].active<<4)|(chan[i].state.alg?0x20:0)); + if (i<9) { + immWrite(0x20+i,(chan[i].freqH)|(chan[i].active<<4)|(chan[i].state.alg?0x20:0)); + } } chan[i].keyOn=false; } @@ -413,7 +422,9 @@ int DivPlatformOPLL::dispatch(DivCommand c) { immWrite(0x0e,0); } } - rWrite(0x30+c.chan,((15-(chan[c.chan].outVol*(15-chan[c.chan].state.op[1].tl))/15)&15)|(chan[c.chan].state.opllPreset<<4)); + if (c.chan<9) { + rWrite(0x30+c.chan,((15-(chan[c.chan].outVol*(15-chan[c.chan].state.op[1].tl))/15)&15)|(chan[c.chan].state.opllPreset<<4)); + } } } @@ -481,7 +492,9 @@ int DivPlatformOPLL::dispatch(DivCommand c) { rWrite(0x38,drumVol[3]|(drumVol[2]<<4)); break; } else if (c.chan<6 || !drums) { - rWrite(0x30+c.chan,((15-(chan[c.chan].outVol*(15-chan[c.chan].state.op[1].tl))/15)&15)|(chan[c.chan].state.opllPreset<<4)); + if (c.chan<9) { + rWrite(0x30+c.chan,((15-(chan[c.chan].outVol*(15-chan[c.chan].state.op[1].tl))/15)&15)|(chan[c.chan].state.opllPreset<<4)); + } } break; } @@ -573,7 +586,9 @@ int DivPlatformOPLL::dispatch(DivCommand c) { } else { DivInstrumentFM::Operator& car=chan[c.chan].state.op[1]; car.tl=c.value2&15; - rWrite(0x30+c.chan,((15-(chan[c.chan].outVol*(15-chan[c.chan].state.op[1].tl))/15)&15)|(chan[c.chan].state.opllPreset<<4)); + if (c.chan<9) { + rWrite(0x30+c.chan,((15-(chan[c.chan].outVol*(15-chan[c.chan].state.op[1].tl))/15)&15)|(chan[c.chan].state.opllPreset<<4)); + } } break; } @@ -641,7 +656,9 @@ void DivPlatformOPLL::forceIns() { rWrite(0x06,(mod.sl<<4)|(mod.rr)); rWrite(0x07,(car.sl<<4)|(car.rr)); } - rWrite(0x30+i,((15-(chan[i].outVol*(15-chan[i].state.op[1].tl))/15)&15)|(chan[i].state.opllPreset<<4)); + if (i<9) { + rWrite(0x30+i,((15-(chan[i].outVol*(15-chan[i].state.op[1].tl))/15)&15)|(chan[i].state.opllPreset<<4)); + } if (!(i>=6 && properDrums)) { if (chan[i].active) { chan[i].keyOn=true; From ee2e1a0cfbcbbf597dfa2e3db8f1dd5a0d4ca4a4 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 3 Mar 2022 18:23:09 -0500 Subject: [PATCH 074/637] OPLL: fix sus i thought defle had broken sus --- src/engine/platform/opll.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/opll.cpp b/src/engine/platform/opll.cpp index 6864c8bf1..c6a8bf4de 100644 --- a/src/engine/platform/opll.cpp +++ b/src/engine/platform/opll.cpp @@ -243,7 +243,7 @@ void DivPlatformOPLL::tick() { immWrite(0x0e,0x20|drumState); } else { if (i<9) { - immWrite(0x20+i,(chan[i].freqH)/*|(chan[i].state.alg?0x20:0)*/); + immWrite(0x20+i,(chan[i].freqH)|(chan[i].state.alg?0x20:0)); } } //chan[i].keyOn=false; From 742e813e981436e443d8be5e34c9c4310515f114 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 3 Mar 2022 23:05:07 -0500 Subject: [PATCH 075/637] E1xx and E2xx should not stop when Key OFF comes in indeed --- src/engine/playback.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index cf3ad1dbf..fac7728e8 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -728,7 +728,7 @@ void DivEngine::processRow(int i, bool afterDelay) { } chan[i].portaStop=true; if (chan[i].keyOn) chan[i].doNote=false; - chan[i].stopOnOff=true; + chan[i].stopOnOff=false; // what?! chan[i].scheduledSlideReset=false; dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,true,1)); lastSlide=0x1337; // i hate this so much @@ -778,7 +778,7 @@ void DivEngine::processRow(int i, bool afterDelay) { chan[i].portaSpeed=(effectVal>>4)*4; chan[i].portaStop=true; chan[i].nowYouCanStop=false; - chan[i].stopOnOff=true; + chan[i].stopOnOff=false; // what?! chan[i].scheduledSlideReset=false; if ((effectVal&15)!=0) { chan[i].inPorta=true; @@ -794,7 +794,7 @@ void DivEngine::processRow(int i, bool afterDelay) { chan[i].portaSpeed=(effectVal>>4)*4; chan[i].portaStop=true; chan[i].nowYouCanStop=false; - chan[i].stopOnOff=true; + chan[i].stopOnOff=false; // what?! chan[i].scheduledSlideReset=false; if ((effectVal&15)!=0) { chan[i].inPorta=true; From 8e5b3abab86b764ebb96a14e892023faa74de009 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 3 Mar 2022 23:14:38 -0500 Subject: [PATCH 076/637] add two more compatibility flags issue #167 and #249: - stop porta on note off - continuous vibrato --- papers/format.md | 5 ++++- src/engine/engine.h | 4 ++-- src/engine/fileOps.cpp | 16 ++++++++++++++-- src/engine/playback.cpp | 10 ++++++---- src/engine/song.h | 6 +++++- src/gui/gui.cpp | 8 ++++++++ 6 files changed, 39 insertions(+), 10 deletions(-) diff --git a/papers/format.md b/papers/format.md index dc052d038..7c113b53a 100644 --- a/papers/format.md +++ b/papers/format.md @@ -29,6 +29,7 @@ furthermore, an `or reserved` indicates this field is always present, but is res the format versions are: +- 62: Furnace dev62 - 61: Furnace dev61 - 60: Furnace dev60 - 59: Furnace dev59 @@ -197,7 +198,9 @@ size | description 1 | wack algorithm macro (>=47) or reserved 1 | broken shortcut slides (>=49) or reserved 1 | ignore duplicate slides (>=50) or reserved - 6 | reserved + 1 | stop portamento on note off (>=62) or reserved + 1 | continuous vibrato (>=62) or reserved + 4 | reserved 4?? | pointers to instruments 4?? | pointers to wavetables 4?? | pointers to samples diff --git a/src/engine/engine.h b/src/engine/engine.h index 02870178a..921d41569 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -37,8 +37,8 @@ warnings+=(String("\n")+x); \ } -#define DIV_VERSION "dev61" -#define DIV_ENGINE_VERSION 61 +#define DIV_VERSION "dev62" +#define DIV_ENGINE_VERSION 62 enum DivStatusView { DIV_STATUS_NOTHING=0, diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 859a99fe2..8b5cfad5d 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -796,6 +796,9 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { if (ds.version<50) { ds.ignoreDuplicateSlides=false; } + if (ds.version<62) { + ds.stopPortaOnNoteOff=true; + } ds.isDMF=false; reader.readS(); // reserved @@ -965,7 +968,14 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { } else { reader.readC(); } - for (int i=0; i<6; i++) reader.readC(); + if (ds.version>=62) { + ds.stopPortaOnNoteOff=reader.readC(); + ds.continuousVibrato=reader.readC(); + } else { + reader.readC(); + reader.readC(); + } + for (int i=0; i<4; i++) reader.readC(); } else { for (int i=0; i<20; i++) reader.readC(); } @@ -1404,7 +1414,9 @@ SafeWriter* DivEngine::saveFur() { w->writeC(song.algMacroBehavior); w->writeC(song.brokenShortcutSlides); w->writeC(song.ignoreDuplicateSlides); - for (int i=0; i<6; i++) { + w->writeC(song.stopPortaOnNoteOff); + w->writeC(song.continuousVibrato); + for (int i=0; i<4; i++) { w->writeC(0); } diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index fac7728e8..23cb54e1c 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -728,7 +728,7 @@ void DivEngine::processRow(int i, bool afterDelay) { } chan[i].portaStop=true; if (chan[i].keyOn) chan[i].doNote=false; - chan[i].stopOnOff=false; // what?! + chan[i].stopOnOff=song.stopPortaOnNoteOff; // what?! chan[i].scheduledSlideReset=false; dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,true,1)); lastSlide=0x1337; // i hate this so much @@ -778,7 +778,7 @@ void DivEngine::processRow(int i, bool afterDelay) { chan[i].portaSpeed=(effectVal>>4)*4; chan[i].portaStop=true; chan[i].nowYouCanStop=false; - chan[i].stopOnOff=false; // what?! + chan[i].stopOnOff=song.stopPortaOnNoteOff; // what?! chan[i].scheduledSlideReset=false; if ((effectVal&15)!=0) { chan[i].inPorta=true; @@ -794,7 +794,7 @@ void DivEngine::processRow(int i, bool afterDelay) { chan[i].portaSpeed=(effectVal>>4)*4; chan[i].portaStop=true; chan[i].nowYouCanStop=false; - chan[i].stopOnOff=false; // what?! + chan[i].stopOnOff=song.stopPortaOnNoteOff; // what?! chan[i].scheduledSlideReset=false; if ((effectVal&15)!=0) { chan[i].inPorta=true; @@ -854,7 +854,9 @@ void DivEngine::processRow(int i, bool afterDelay) { } if (chan[i].doNote) { - chan[i].vibratoPos=0; + if (!song.continuousVibrato) { + chan[i].vibratoPos=0; + } dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(((chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15))); if (chan[i].legato) { dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note)); diff --git a/src/engine/song.h b/src/engine/song.h index b200068e7..fff1065fe 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -276,6 +276,8 @@ struct DivSong { bool algMacroBehavior; bool brokenShortcutSlides; bool ignoreDuplicateSlides; + bool stopPortaOnNoteOff; + bool continuousVibrato; DivOrders orders; std::vector ins; @@ -338,7 +340,9 @@ struct DivSong { arpNonPorta(false), algMacroBehavior(false), brokenShortcutSlides(false), - ignoreDuplicateSlides(false) { + ignoreDuplicateSlides(false), + stopPortaOnNoteOff(false), + continuousVibrato(false) { for (int i=0; i<32; i++) { system[i]=DIV_SYSTEM_NULL; systemVol[i]=64; diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index ef0e0d86f..58e95003f 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2054,6 +2054,10 @@ void FurnaceGUI::drawCompatFlags() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("if this is on, only the first slide of a row in a channel will be considered."); } + ImGui::Checkbox("Continuous vibrato",&e->song.continuousVibrato); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("when enabled, vibrato will not be reset on a new note."); + } ImGui::Text("Loop modality:"); if (ImGui::RadioButton("Reset channels",e->song.loopModality==0)) { @@ -2091,6 +2095,10 @@ void FurnaceGUI::drawCompatFlags() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("behavior changed in 0.5.7"); } + ImGui::Checkbox("Stop portamento on note off",&e->song.stopPortaOnNoteOff); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("behavior changed in 0.6"); + } } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_COMPAT_FLAGS; ImGui::End(); From 875827719927cd7789e2b89b4ccdc6c1c1798952 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 4 Mar 2022 01:18:16 -0500 Subject: [PATCH 077/637] OPLL: implement fixed freq mode for drums issue #249 --- papers/format.md | 7 ++++ src/engine/engine.h | 4 +- src/engine/instrument.cpp | 16 ++++++++ src/engine/instrument.h | 8 +++- src/engine/platform/opll.cpp | 19 ++++++++- src/gui/insEdit.cpp | 75 +++++++++++++++++++++++++++++++++++- 6 files changed, 123 insertions(+), 6 deletions(-) diff --git a/papers/format.md b/papers/format.md index 7c113b53a..8e8c62e23 100644 --- a/papers/format.md +++ b/papers/format.md @@ -29,6 +29,7 @@ furthermore, an `or reserved` indicates this field is always present, but is res the format versions are: +- 63: Furnace dev63 - 62: Furnace dev62 - 61: Furnace dev61 - 60: Furnace dev60 @@ -476,6 +477,12 @@ size | description 1?? | VIB macro 1?? | WS macro 1?? | KSR macro + --- | **OPL drums mode data** (>=63) + 1 | fixed frequency mode + 1 | reserved + 2 | kick frequency + 2 | snare/hi-hat frequency + 2 | tom/top frequency ``` # wavetable diff --git a/src/engine/engine.h b/src/engine/engine.h index 921d41569..19d589dba 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -37,8 +37,8 @@ warnings+=(String("\n")+x); \ } -#define DIV_VERSION "dev62" -#define DIV_ENGINE_VERSION 62 +#define DIV_VERSION "dev63" +#define DIV_ENGINE_VERSION 63 enum DivStatusView { DIV_STATUS_NOTHING=0, diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index d5933981d..d65a27d0e 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -371,6 +371,13 @@ void DivInstrument::putInsData(SafeWriter* w) { w->writeC(op.ksrMacro[j]); } } + + // OPL drum data + w->writeC(fm.fixedDrums); + w->writeC(0); // reserved + w->writeS(fm.kickFreq); + w->writeS(fm.snareHatFreq); + w->writeS(fm.tomTopFreq); } DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) { @@ -694,6 +701,15 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) { } } + // OPL drum data + if (version>=63) { + fm.fixedDrums=reader.readC(); + reader.readC(); // reserved + fm.kickFreq=reader.readS(); + fm.snareHatFreq=reader.readS(); + fm.tomTopFreq=reader.readS(); + } + return DIV_DATA_SUCCESS; } diff --git a/src/engine/instrument.h b/src/engine/instrument.h index aef84f202..27ce2a4ce 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -67,6 +67,8 @@ enum DivInstrumentType { struct DivInstrumentFM { unsigned char alg, fb, fms, ams, ops, opllPreset; + bool fixedDrums; + unsigned short kickFreq, snareHatFreq, tomTopFreq; struct Operator { unsigned char am, ar, dr, mult, rr, sl, tl, dt2, rs, dt, d2r, ssgEnv; unsigned char dam, dvb, egt, ksl, sus, vib, ws, ksr; // YMU759/OPL/OPZ @@ -98,7 +100,11 @@ struct DivInstrumentFM { fms(0), ams(0), ops(4), - opllPreset(0) { + opllPreset(0), + fixedDrums(false), + kickFreq(0x520), + snareHatFreq(0x550), + tomTopFreq(0x1c0) { // default instrument fb=4; op[0].tl=42; diff --git a/src/engine/platform/opll.cpp b/src/engine/platform/opll.cpp index c6a8bf4de..2c2413c4d 100644 --- a/src/engine/platform/opll.cpp +++ b/src/engine/platform/opll.cpp @@ -377,7 +377,24 @@ int DivPlatformOPLL::dispatch(DivCommand c) { if (c.chan>=6 && properDrums) { // drums mode chan[c.chan].insChanged=false; if (c.value!=DIV_NOTE_NULL) { - chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); + if (chan[c.chan].state.opllPreset==16 && chan[c.chan].state.fixedDrums) { + switch (c.chan) { + case 6: + chan[c.chan].baseFreq=(chan[c.chan].state.kickFreq&511)<<(chan[c.chan].state.kickFreq>>9); + break; + case 7: case 10: + chan[c.chan].baseFreq=(chan[c.chan].state.snareHatFreq&511)<<(chan[c.chan].state.snareHatFreq>>9); + break; + case 8: case 9: + chan[c.chan].baseFreq=(chan[c.chan].state.tomTopFreq&511)<<(chan[c.chan].state.tomTopFreq>>9); + break; + default: + chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); + break; + } + } else { + chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); + } chan[c.chan].note=c.value; chan[c.chan].freqChanged=true; } diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 3f5ac067d..de381e6ba 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -81,7 +81,7 @@ const char* opllInsNames[17]={ "Synth Bass", "Acoustic Bass", "Electric Guitar", - "Drums (compatibility only!)" + "Drums" }; enum FMParams { @@ -857,7 +857,78 @@ void FurnaceGUI::drawInsEdit() { } if (ins->type==DIV_INS_OPLL && ins->fm.opllPreset==16) { - ImGui::Text("the Drums patch is only there for compatibility.\nit is highly encouraged you use the OPLL (drums) system instead!"); + P(ImGui::Checkbox("Fixed frequency mode",&ins->fm.fixedDrums)); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("when enabled, drums will be set to the specified frequencies, ignoring the note."); + } + if (ins->fm.fixedDrums) { + int block=0; + int fNum=0; + if (ImGui::BeginTable("fixedDrumSettings",3)) { + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + ImGui::TableNextColumn(); + ImGui::Text("Drum"); + ImGui::TableNextColumn(); + ImGui::Text("Block"); + ImGui::TableNextColumn(); + ImGui::Text("FreqNum"); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + block=(ins->fm.kickFreq>>9)&7; + fNum=ins->fm.kickFreq&511; + ImGui::Text("Kick"); + ImGui::TableNextColumn(); + if (ImGui::InputInt("##DBlock0",&block,1,1)) { + if (block<0) block=0; + if (block>7) block=7; + ins->fm.kickFreq=(block<<9)|fNum; + } + ImGui::TableNextColumn(); + if (ImGui::InputInt("##DFreq0",&fNum,1,1)) { + if (fNum<0) fNum=0; + if (fNum>511) fNum=511; + ins->fm.kickFreq=(block<<9)|fNum; + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + block=(ins->fm.snareHatFreq>>9)&7; + fNum=ins->fm.snareHatFreq&511; + ImGui::Text("Snare/Hi-hat"); + ImGui::TableNextColumn(); + if (ImGui::InputInt("##DBlock1",&block,1,1)) { + if (block<0) block=0; + if (block>7) block=7; + ins->fm.snareHatFreq=(block<<9)|fNum; + } + ImGui::TableNextColumn(); + if (ImGui::InputInt("##DFreq1",&fNum,1,1)) { + if (fNum<0) fNum=0; + if (fNum>511) fNum=511; + ins->fm.snareHatFreq=(block<<9)|fNum; + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + block=(ins->fm.tomTopFreq>>9)&7; + fNum=ins->fm.tomTopFreq&511; + ImGui::Text("Tom/Top"); + ImGui::TableNextColumn(); + if (ImGui::InputInt("##DBlock2",&block,1,1)) { + if (block<0) block=0; + if (block>7) block=7; + ins->fm.tomTopFreq=(block<<9)|fNum; + } + ImGui::TableNextColumn(); + if (ImGui::InputInt("##DFreq2",&fNum,1,1)) { + if (fNum<0) fNum=0; + if (fNum>511) fNum=511; + ins->fm.tomTopFreq=(block<<9)|fNum; + } + ImGui::EndTable(); + } + } } bool willDisplayOps=true; From 59d813db7cf87226f95589ce41d60326bdf78a7b Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 4 Mar 2022 02:20:13 -0500 Subject: [PATCH 078/637] PCE: add noise macro (finally!) --- src/engine/instrument.cpp | 7 +++++++ src/engine/platform/pce.cpp | 7 +++++++ src/gui/insEdit.cpp | 5 ++++- 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index d65a27d0e..bdf028905 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -710,6 +710,13 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) { fm.tomTopFreq=reader.readS(); } + // clear noise macro if PCE instrument and version<63 + if (version<63 && type==DIV_INS_PCE) { + std.dutyMacroLen=0; + std.dutyMacroLoop=-1; + std.dutyMacroRel=-1; + } + return DIV_DATA_SUCCESS; } diff --git a/src/engine/platform/pce.cpp b/src/engine/platform/pce.cpp index 7b45dfa7b..296eafc41 100644 --- a/src/engine/platform/pce.cpp +++ b/src/engine/platform/pce.cpp @@ -162,6 +162,13 @@ void DivPlatformPCE::tick() { chWrite(i,0x04,0x80|chan[i].outVol); } } + if (chan[i].std.hadDuty && i>=4) { + chan[i].noise=chan[i].std.duty; + chan[i].freqChanged=true; + int noiseSeek=chan[i].note; + if (noiseSeek<0) noiseSeek=0; + chWrite(i,0x07,chan[i].noise?(0x80|(parent->song.properNoiseLayout?(noiseSeek&31):noiseFreq[noiseSeek%12])):0); + } if (chan[i].std.hadArp) { if (!chan[i].inPorta) { if (chan[i].std.arpMode) { diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index de381e6ba..4afa67a5c 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1352,9 +1352,12 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_AY8930) { dutyMax=255; } - if (ins->type==DIV_INS_TIA || ins->type==DIV_INS_PCE || ins->type==DIV_INS_AMIGA) { + if (ins->type==DIV_INS_TIA || ins->type==DIV_INS_AMIGA) { dutyMax=0; } + if (ins->type==DIV_INS_PCE) { + dutyMax=1; + } if (ins->type==DIV_INS_OPLL || ins->type==DIV_INS_OPL) { dutyMax=0; } From 1f0fba4c131142809d315c8bd19dd1acac27cab6 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 4 Mar 2022 02:25:41 -0500 Subject: [PATCH 079/637] OPLL: getEffectName oopsie --- src/engine/platform/opll.cpp | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/src/engine/platform/opll.cpp b/src/engine/platform/opll.cpp index 2c2413c4d..5ed5b6e3c 100644 --- a/src/engine/platform/opll.cpp +++ b/src/engine/platform/opll.cpp @@ -41,20 +41,13 @@ const char* DivPlatformOPLL::getEffectName(unsigned char effect) { case 0x13: return "13xx: Set level of operator 2 (0 highest, 7F lowest)"; break; - case 0x14: - return "14xx: Set level of operator 3 (0 highest, 7F lowest)"; - break; - case 0x15: - return "15xx: Set level of operator 4 (0 highest, 7F lowest)"; - break; case 0x16: - return "16xy: Set operator multiplier (x: operator from 1 to 4; y: multiplier)"; - break; - case 0x17: - return "17xx: Enable channel 6 DAC"; + return "16xy: Set operator multiplier (x: operator from 1 to 2; y: multiplier)"; break; case 0x18: - return "18xx: Toggle extended channel 3 mode"; + if (properDrumsSys) { + return "18xx: Toggle drums mode (1: enabled; 0: disabled)"; + } break; case 0x19: return "19xx: Set attack of all operators (0 to 1F)"; @@ -65,12 +58,6 @@ const char* DivPlatformOPLL::getEffectName(unsigned char effect) { case 0x1b: return "1Bxx: Set attack of operator 2 (0 to 1F)"; break; - case 0x1c: - return "1Cxx: Set attack of operator 3 (0 to 1F)"; - break; - case 0x1d: - return "1Dxx: Set attack of operator 4 (0 to 1F)"; - break; } return NULL; } From 1407f14ee83bbb65957cc583e1c420fd44c2b5db Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 4 Mar 2022 02:26:47 -0500 Subject: [PATCH 080/637] AY8930: duty oopsie fixes #251 --- src/engine/platform/ay8930.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/ay8930.cpp b/src/engine/platform/ay8930.cpp index 3c56461bb..040800af6 100644 --- a/src/engine/platform/ay8930.cpp +++ b/src/engine/platform/ay8930.cpp @@ -372,7 +372,7 @@ int DivPlatformAY8930::dispatch(DivCommand c) { } } else { chan[c.chan].duty=c.value&15; - immWrite(0x16,chan[c.chan].duty); + immWrite(0x16+c.chan,chan[c.chan].duty); } break; case DIV_CMD_STD_NOISE_FREQ: From bc5ca47894df148bc99f5fe80078ca70d4f952b8 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 4 Mar 2022 02:40:42 -0500 Subject: [PATCH 081/637] GUI: add setting to not raise pattern editor issue #250 --- src/gui/gui.h | 2 ++ src/gui/pattern.cpp | 2 +- src/gui/settings.cpp | 7 +++++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/gui/gui.h b/src/gui/gui.h index dee44f59c..e58bd17b6 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -494,6 +494,7 @@ class FurnaceGUI { float dpiScale; int viewPrevPattern; int guiColorsBase; + int avoidRaisingPattern; unsigned int maxUndoSteps; String mainFontPath; String patFontPath; @@ -535,6 +536,7 @@ class FurnaceGUI { dpiScale(0.0f), viewPrevPattern(1), guiColorsBase(0), + avoidRaisingPattern(0), maxUndoSteps(100), mainFontPath(""), patFontPath(""), diff --git a/src/gui/pattern.cpp b/src/gui/pattern.cpp index 3d2f15cb8..2eabcd04b 100644 --- a/src/gui/pattern.cpp +++ b/src/gui/pattern.cpp @@ -357,7 +357,7 @@ void FurnaceGUI::drawPattern() { sel2.xFine^=sel1.xFine; } ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding,ImVec2(0.0f,0.0f)); - if (ImGui::Begin("Pattern",&patternOpen)) { + if (ImGui::Begin("Pattern",&patternOpen,settings.avoidRaisingPattern?ImGuiWindowFlags_NoBringToFrontOnFocus:0)) { //ImGui::SetWindowSize(ImVec2(scrW*dpiScale,scrH*dpiScale)); patWindowPos=ImGui::GetWindowPos(); patWindowSize=ImGui::GetWindowSize(); diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index e40d47a13..304db0468 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -162,6 +162,11 @@ void FurnaceGUI::drawSettings() { settings.allowEditDocking=allowEditDockingB; } + bool avoidRaisingPatternB=settings.avoidRaisingPattern; + if (ImGui::Checkbox("Don't raise pattern editor on click",&avoidRaisingPatternB)) { + settings.avoidRaisingPattern=avoidRaisingPatternB; + } + bool restartOnFlagChangeB=settings.restartOnFlagChange; if (ImGui::Checkbox("Restart song when changing system properties",&restartOnFlagChangeB)) { settings.restartOnFlagChange=restartOnFlagChangeB; @@ -864,6 +869,7 @@ void FurnaceGUI::syncSettings() { settings.dpiScale=e->getConfFloat("dpiScale",0.0f); settings.viewPrevPattern=e->getConfInt("viewPrevPattern",1); settings.guiColorsBase=e->getConfInt("guiColorsBase",0); + settings.avoidRaisingPattern=e->getConfInt("avoidRaisingPattern",0); // keybinds LOAD_KEYBIND(GUI_ACTION_OPEN,FURKMOD_CMD|SDLK_o); @@ -1060,6 +1066,7 @@ void FurnaceGUI::commitSettings() { e->setConf("dpiScale",settings.dpiScale); e->setConf("viewPrevPattern",settings.viewPrevPattern); e->setConf("guiColorsBase",settings.guiColorsBase); + e->setConf("avoidRaisingPattern",settings.avoidRaisingPattern); PUT_UI_COLOR(GUI_COLOR_BACKGROUND); PUT_UI_COLOR(GUI_COLOR_FRAME_BACKGROUND); From c74390f7546566e436c5fadc1d695afc5d447df7 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 4 Mar 2022 02:46:35 -0500 Subject: [PATCH 082/637] add distro package links --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index e0db0e05e..5f30660b2 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,22 @@ see the [Discussions](https://github.com/tildearrow/furnace/discussions) section check out the [documentation](papers/doc/README.md). it's mostly incomplete, but has details on effects. +# unofficial packages + +some people have provided packages for Unix/Unix-like distributions. here's a list. + +## Arch Linux + +[furnace-git is in the AUR.](https://aur.archlinux.org/packages/furnace-git) thank you Essem! + +## FreeBSD + +[a package in ports](https://www.freshports.org/audio/furnace/) is available courtesy of ehaupt. + +## Nix + +(TODO) + # developer info **NOTE: do not download the project's source as a .zip or .tar.gz as these do not include the project's submodules which are necessary to proceed with building.** From 98e2c5159269cdd7c2f222932d9439d85b84d2a4 Mon Sep 17 00:00:00 2001 From: Emanuel Haupt Date: Fri, 4 Mar 2022 10:09:16 +0100 Subject: [PATCH 083/637] Provide a repology badge (#252) Provide a repology badge for showing packaging status. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 5f30660b2..bed970b19 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ see the [Discussions](https://github.com/tildearrow/furnace/discussions) section check out the [documentation](papers/doc/README.md). it's mostly incomplete, but has details on effects. # unofficial packages +[![Packaging status](https://repology.org/badge/tiny-repos/furnace.svg)](https://repology.org/project/furnace/versions) some people have provided packages for Unix/Unix-like distributions. here's a list. From 9abf872ff3ef08633d3a6c324bc27b097d4d78bc Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Fri, 4 Mar 2022 18:13:49 +0700 Subject: [PATCH 084/637] Add VERA support for Commander X16 --- CMakeLists.txt | 1 + papers/doc/4-instrument/README.md | 1 + papers/doc/4-instrument/vera.md | 8 + src/engine/dispatchContainer.cpp | 4 + src/engine/engine.cpp | 2 + src/engine/instrument.h | 1 + src/engine/platform/vera.cpp | 404 ++++++++++++++++++++++++++++++ src/engine/platform/vera.h | 74 ++++++ src/engine/playback.cpp | 12 + src/engine/song.h | 3 +- src/engine/sysDef.cpp | 36 ++- src/gui/gui.cpp | 4 +- src/gui/insEdit.cpp | 28 ++- 13 files changed, 565 insertions(+), 13 deletions(-) create mode 100644 papers/doc/4-instrument/vera.md create mode 100644 src/engine/platform/vera.cpp create mode 100644 src/engine/platform/vera.h diff --git a/CMakeLists.txt b/CMakeLists.txt index af3e3a7d1..7211eb3d0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -303,6 +303,7 @@ src/engine/platform/saa.cpp src/engine/platform/amiga.cpp src/engine/platform/segapcm.cpp src/engine/platform/qsound.cpp +src/engine/platform/vera.cpp src/engine/platform/dummy.cpp src/engine/platform/lynx.cpp ) diff --git a/papers/doc/4-instrument/README.md b/papers/doc/4-instrument/README.md index b9d4b8f40..499a5ee14 100644 --- a/papers/doc/4-instrument/README.md +++ b/papers/doc/4-instrument/README.md @@ -23,6 +23,7 @@ depending on the instrument type, there are currently 10 different types of an i - [AY-3-8910](ay8910.md) - for use with AY-3-8910 PSG sound source and SSG portion in YM2610. - [Amiga/sample](amiga.md) for controlling Amiga and other sample based synthsizers like YM2612's Channel 6 PCM mode, NES channel 5, Sega PCM and PC Engine's sample playback mode. - [Atari Lynx](lynx.md) - for use with Atari Lynx handheld console. +- [VERA](vera.md) - for use with Commander X16 VERA. # macros diff --git a/papers/doc/4-instrument/vera.md b/papers/doc/4-instrument/vera.md new file mode 100644 index 000000000..b577ccd23 --- /dev/null +++ b/papers/doc/4-instrument/vera.md @@ -0,0 +1,8 @@ +# VERA instrument editor + +VERA instrument editor consists of only four macros: + +- [Volume] - volume sequence +- [Arpeggio] - pitch sequence +- [Duty cycle] - pulse duty cycle sequence +- [Waveform] - select the waveform used by instrument diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 8398ca3d0..e34eebbb8 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -39,6 +39,7 @@ #include "platform/amiga.h" #include "platform/segapcm.h" #include "platform/qsound.h" +#include "platform/vera.h" #include "platform/dummy.h" #include "platform/lynx.h" #include "../ta-log.h" @@ -226,6 +227,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_SEGAPCM_COMPAT: dispatch=new DivPlatformSegaPCM; break; + case DIV_SYSTEM_VERA: + dispatch = new DivPlatformVERA; + break; default: logW("this system is not supported yet! using dummy platform.\n"); dispatch=new DivPlatformDummy; diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index a8b023d7d..4f387b272 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -951,6 +951,8 @@ int DivEngine::getEffectiveSampleRate(int rate) { return (24038*MIN(65535,(rate*4096/24038)))/4096; case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_EXT: case DIV_SYSTEM_YM2610_FULL: case DIV_SYSTEM_YM2610_FULL_EXT: case DIV_SYSTEM_YM2610B: case DIV_SYSTEM_YM2610B_EXT: return 18518; + case DIV_SYSTEM_VERA: + return (48828*MIN(128,(rate*128/48828)))/128; default: break; } diff --git a/src/engine/instrument.h b/src/engine/instrument.h index 27ce2a4ce..5f11df65a 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -48,6 +48,7 @@ enum DivInstrumentType { DIV_INS_BEEPER=21, DIV_INS_SWAN=22, DIV_INS_MIKEY=23, + DIV_INS_VERA=24, }; // FM operator structure: diff --git a/src/engine/platform/vera.cpp b/src/engine/platform/vera.cpp new file mode 100644 index 000000000..47f4888d6 --- /dev/null +++ b/src/engine/platform/vera.cpp @@ -0,0 +1,404 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "vera.h" +#include "../engine.h" +#include +#include + +#define rWrite(c,a,d) {regPool[(c)*4+(a)]=(d);} +#define rWriteLo(c,a,d) rWrite(c,a,(regPool[(c)*4+(a)]&(~0x3f))|((d)&0x3f)) +#define rWriteHi(c,a,d) rWrite(c,a,(regPool[(c)*4+(a)]&(~0xc0))|(((d)<<6)&0xc0)) +#define rWriteFIFOVol(d) rWrite(16,0,(regPool[64]&(~0x3f))|((d)&0x3f)) + +const char* regCheatSheetVERA[]={ + "CHxFreq", "00+x*4", + "CHxVol", "02+x*4", + "CHxWave", "03+x*4", + + "AUDIO_CTRL", "40", + "AUDIO_RATE", "41", + + NULL +}; + +const char** DivPlatformVERA::getRegisterSheet() { + return regCheatSheetVERA; +} + +const char* DivPlatformVERA::getEffectName(unsigned char effect) { + switch (effect) { + case 0x20: + return "20xx: Change waveform"; + break; + case 0x22: + return "22xx: Set duty cycle (0 to 63)"; + break; + } + return NULL; +} + +void DivPlatformVERA::acquire(short* bufL, short* bufR, size_t start, size_t len) { + // taken from the official X16 emulator's source code, (c) 2020 Frank van den Hoef + const uint8_t volume_lut_psg[64]={0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 6, 6, 7, 7, 7, 8, 8, 9, 9, 10, 11, 11, 12, 13, 14, 14, 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 28, 29, 31, 33, 35, 37, 39, 42, 44, 47, 50, 52, 56, 59, 63}; + const uint8_t volume_lut_pcm[16]={0, 1, 2, 3, 4, 5, 6, 8, 11, 14, 18, 23, 30, 38, 49, 64}; + for (size_t pos=start; pos>6) { + case 0: val=(new_accum>>10)>(regPool[i*4+3]&(unsigned)0x3f)?0:63; break; + case 1: val=(new_accum>>11); break; + case 2: val=(new_accum&0x10000)?(~(new_accum>>10)&0x3f):((new_accum>>10)&0x3f); break; + case 3: val=chan[i].noiseval; break; + } + val=(val-0x20)*volume_lut_psg[regPool[i*4+2]&0x3f]; + lout+=(regPool[i*4+2]&0x40)?val:0; + rout+=(regPool[i*4+2]&0x80)?val:0; + } + // PCM + // simple one-channel sample player, actual hardware is essentially a DAC + // with buffering + if (chan[16].pcm.sample>=0) { + chan[16].accum+=regPool[65]; + if (chan[16].accum>=128) { + DivSample* s=parent->getSample(chan[16].pcm.sample); + if (s->samples>0) { + // TODO stereo samples once DivSample has a support for it + switch (s->depth) { + case 8: + chan[16].pcm.out_l=chan[16].pcm.pan&1?(s->data8[chan[16].pcm.pos]*256):0; + chan[16].pcm.out_r=chan[16].pcm.pan&2?(s->data8[chan[16].pcm.pos]*256):0; + regPool[64]|=0x20; // for register viewer purposes + break; + case 16: + chan[16].pcm.out_l=chan[16].pcm.pan&1?(s->data16[chan[16].pcm.pos]):0; + chan[16].pcm.out_r=chan[16].pcm.pan&2?(s->data16[chan[16].pcm.pos]):0; + regPool[64]&=~0x20; + break; + } + } else { + chan[16].pcm.sample=-1; + } + chan[16].accum&=0x7f; + chan[16].pcm.pos++; + if (chan[16].pcm.pos>=s->samples) { + if (s->loopStart>=0 && s->loopStart<=(int)s->samples) { + chan[16].pcm.pos=s->loopStart; + } else { + chan[16].pcm.sample=-1; + } + } + } + } + int pcmvol=volume_lut_pcm[regPool[64]&0x0f]; + lout+=chan[16].pcm.out_l*pcmvol/64; + rout+=chan[16].pcm.out_r*pcmvol/64; + + bufL[pos]=(short)(lout/2); + bufR[pos]=(short)(rout/2); + } +} + +void DivPlatformVERA::reset() { + for (int i=0; i<17; i++) { + chan[i]=Channel(); + } + memset(regPool,0,66); + for (int i=0; i<16; i++) { + chan[i].vol=63; + rWriteHi(i,2,3); // default pan + } + chan[16].vol=15; +} + +int DivPlatformVERA::calcNoteFreq(int ch, int note) { + if (ch<16) { + return parent->calcBaseFreq(chipClock,2097152,note,false); + } else { + double off=1.0; + if (chan[ch].pcm.sample>=0 && chan[ch].pcm.samplesong.sampleLen) { + DivSample* s=parent->getSample(chan[ch].pcm.sample); + if (s->centerRate<1) { + off=1.0; + } else { + off=s->centerRate/8363.0; + } + } + return (int)(off*parent->calcBaseFreq(chipClock,65536,note,false)); + } +} + +void DivPlatformVERA::tick() { + for (int i=0; i<17; i++) { + chan[i].std.next(); + if (chan[i].std.hadVol) { + if (i<16) { + chan[i].outVol=MAX(chan[i].vol+chan[i].std.vol-63,0); + rWriteLo(i,2,isMuted[i]?0:(chan[i].outVol&63)); + } else { + // NB this is currently assuming Amiga instrument type with a 0-64 + // (inclusive) volume range. This envelope is then scaled and added to + // the channel volume. Is this a better way to handle this instead of + // making another identical Amiga instrument type but with a 0-15 + // volume range? + chan[16].outVol=MAX(chan[16].vol+MIN(chan[16].std.vol/4,15)-15,0); + rWriteFIFOVol(isMuted[16]?0:(chan[16].outVol&15)); + } + } + if (chan[i].std.hadArp) { + if (!chan[i].inPorta) { + if (chan[i].std.arpMode) { + chan[i].baseFreq=calcNoteFreq(0,chan[i].std.arp); + } else { + chan[i].baseFreq=calcNoteFreq(0,chan[i].note+chan[i].std.arp); + } + } + chan[i].freqChanged=true; + } else { + if (chan[i].std.arpMode && chan[i].std.finishedArp) { + chan[i].baseFreq=calcNoteFreq(0,chan[i].note); + chan[i].freqChanged=true; + } + } + if (chan[i].std.hadDuty && i<16) { + rWriteLo(i,3,chan[i].std.duty); + } + if (chan[i].std.hadWave && i<16) { + rWriteHi(i,3,chan[i].std.wave); + } + if (chan[i].freqChanged) { + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,8); + if (i<16) { + if (chan[i].freq>65535) chan[i].freq=65535; + rWrite(i,0,chan[i].freq&0xff); + rWrite(i,1,(chan[i].freq>>8)&0xff); + } else { + if (chan[i].freq>128) chan[i].freq=128; + rWrite(16,1,chan[i].freq&0xff); + } + chan[i].freqChanged=false; + } + } + // PCM + chan[16].std.next(); + if (chan[16].std.hadVol) { + } + if (chan[16].std.hadArp) { + if (!chan[16].inPorta) { + if (chan[16].std.arpMode) { + chan[16].baseFreq=calcNoteFreq(16,chan[16].std.arp); + } else { + chan[16].baseFreq=calcNoteFreq(16,chan[16].note+chan[16].std.arp); + } + } + chan[16].freqChanged=true; + } else { + if (chan[16].std.arpMode && chan[16].std.finishedArp) { + chan[16].baseFreq=calcNoteFreq(16,chan[16].note); + chan[16].freqChanged=true; + } + } +} + +int DivPlatformVERA::dispatch(DivCommand c) { + int tmp; + switch (c.cmd) { + case DIV_CMD_NOTE_ON: + tmp = isMuted[c.chan]?0:chan[c.chan].vol; + if(c.chan<16) { + rWriteLo(c.chan,2,tmp) + } else { + chan[c.chan].pcm.sample=parent->getIns(chan[16].ins)->amiga.initSample; + if (chan[c.chan].pcm.sample<0 || chan[c.chan].pcm.sample>=parent->song.sampleLen) { + chan[c.chan].pcm.sample=-1; + } + chan[16].pcm.pos=0; + rWriteFIFOVol(tmp); + } + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=calcNoteFreq(c.chan,c.value); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + } + chan[c.chan].active=true; + chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + break; + case DIV_CMD_NOTE_OFF: + chan[c.chan].active=false; + if(c.chan<16) { + rWriteLo(c.chan,2,0) + } else { + chan[16].pcm.sample=-1; + rWriteFIFOVol(0); + rWrite(16,1,0); + } + chan[c.chan].std.init(NULL); + break; + case DIV_CMD_NOTE_OFF_ENV: + case DIV_CMD_ENV_RELEASE: + chan[c.chan].std.release(); + break; + case DIV_CMD_INSTRUMENT: + chan[c.chan].ins=(unsigned char)c.value; + break; + case DIV_CMD_VOLUME: + if (c.chan<16) { + tmp=c.value&0x3f; + chan[c.chan].vol=tmp; + rWriteLo(c.chan,2,(isMuted[c.chan]?0:tmp)); + } else { + tmp=c.value&0x0f; + chan[c.chan].vol=tmp; + rWriteFIFOVol(isMuted[c.chan]?0:tmp); + } + break; + case DIV_CMD_GET_VOLUME: + return chan[c.chan].vol; + break; + case DIV_CMD_PITCH: + chan[c.chan].pitch=c.value; + chan[c.chan].freqChanged=true; + break; + case DIV_CMD_NOTE_PORTA: { + int destFreq=calcNoteFreq(c.chan,c.value2); + bool return2=false; + if (destFreq>chan[c.chan].baseFreq) { + chan[c.chan].baseFreq+=c.value; + if (chan[c.chan].baseFreq>=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } else { + chan[c.chan].baseFreq-=c.value; + if (chan[c.chan].baseFreq<=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } + chan[c.chan].freqChanged=true; + if (return2) { + chan[c.chan].inPorta=false; + return 2; + } + break; + } + case DIV_CMD_LEGATO: + chan[c.chan].baseFreq=calcNoteFreq(c.chan,c.value+((chan[c.chan].std.willArp && !chan[c.chan].std.arpMode)?(chan[c.chan].std.arp):(0))); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + break; + case DIV_CMD_PRE_PORTA: + if (chan[c.chan].active && c.value2) { + if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + } + chan[c.chan].inPorta=c.value; + break; + case DIV_CMD_STD_NOISE_MODE: + if (c.chan<16) rWriteLo(c.chan,3,c.value); + break; + case DIV_CMD_WAVE: + if (c.chan<16) rWriteHi(c.chan,3,c.value); + break; + case DIV_CMD_PANNING: { + tmp=0; + tmp|=(c.value&0x10)?1:0; + tmp|=(c.value&0x01)?2:0; + if (c.chan<16) { + rWriteHi(c.chan,2,tmp); + } else { + chan[c.chan].pcm.pan = tmp&3; + } + break; + } + case DIV_CMD_GET_VOLMAX: + if(c.chan<16) { + return 63; + } else { + return 15; + } + break; + case DIV_ALWAYS_SET_VOLUME: + return 0; + break; + default: + break; + } + return 1; +} + +void* DivPlatformVERA::getChanState(int ch) { + return &chan[ch]; +} + +unsigned char* DivPlatformVERA::getRegisterPool() { + return regPool; +} + +int DivPlatformVERA::getRegisterPoolSize() { + return 66; +} + +void DivPlatformVERA::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; +} + +bool DivPlatformVERA::isStereo() { + return true; +} + +void DivPlatformVERA::notifyInsDeletion(void* ins) { + for (int i=0; i<2; i++) { + chan[i].std.notifyInsDeletion((DivInstrument*)ins); + } +} + +void DivPlatformVERA::poke(unsigned int addr, unsigned short val) { + regPool[addr] = (unsigned char)val; +} + +void DivPlatformVERA::poke(std::vector& wlist) { + for (auto &i: wlist) regPool[i.addr] = (unsigned char)i.val; +} + +int DivPlatformVERA::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { + for (int i=0; i<17; i++) { + isMuted[i]=false; + } + parent=p; + dumpWrites=false; + skipRegisterWrites=false; + chipClock=25000000; + rate=chipClock/512; + reset(); + return 17; +} + +DivPlatformVERA::~DivPlatformVERA() { +} diff --git a/src/engine/platform/vera.h b/src/engine/platform/vera.h new file mode 100644 index 000000000..c6a208b90 --- /dev/null +++ b/src/engine/platform/vera.h @@ -0,0 +1,74 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _VERA_H +#define _VERA_H +#include "../dispatch.h" +#include "../instrument.h" +#include "../macroInt.h" + +class DivPlatformVERA: public DivDispatch { + protected: + struct Channel { + int freq, baseFreq, pitch, note; + unsigned char ins; + bool active, freqChanged, inPorta; + int vol, outVol; + unsigned accum; + int noiseval; + DivMacroInt std; + + struct PCMChannel { + int sample; + int out_l, out_r; + unsigned pos; + unsigned len; + unsigned char freq, pan; + PCMChannel(): sample(-1), out_l(0), out_r(0), pos(0), len(0), freq(0), pan(3) {} + } pcm; + Channel(): freq(0), baseFreq(0), pitch(0), note(0), ins(-1), active(false), freqChanged(false), inPorta(false), vol(0), outVol(0), accum(0), noiseval(0) {} + }; + Channel chan[17]; + bool isMuted[17]; + unsigned char regPool[66]; + + friend void putDispatchChan(void*,int,int); + + public: + void acquire(short* bufL, short* bufR, size_t start, size_t len); + int dispatch(DivCommand c); + void* getChanState(int chan); + unsigned char* getRegisterPool(); + int getRegisterPoolSize(); + void reset(); + void tick(); + void muteChannel(int ch, bool mute); + void notifyInsDeletion(void* ins); + bool isStereo(); + void poke(unsigned int addr, unsigned short val); + void poke(std::vector& wlist); + const char** getRegisterSheet(); + const char* getEffectName(unsigned char effect); + int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); + ~DivPlatformVERA(); + + private: + int calcNoteFreq(int ch, int note); +}; +#endif diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 23cb54e1c..e1a7e3a4c 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -258,6 +258,18 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe break; } break; + case DIV_SYSTEM_VERA: + switch (effect) { + case 0x20: // select waveform + dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal)); + break; + case 0x22: // duty + dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal)); + break; + default: + return false; + } + break; default: return false; } diff --git a/src/engine/song.h b/src/engine/song.h index fff1065fe..133175c0a 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -90,8 +90,9 @@ enum DivSystem { DIV_SYSTEM_OPLL_DRUMS, DIV_SYSTEM_LYNX, DIV_SYSTEM_QSOUND, + DIV_SYSTEM_VERA, DIV_SYSTEM_YM2610B_EXT, - DIV_SYSTEM_SEGAPCM_COMPAT + DIV_SYSTEM_SEGAPCM_COMPAT, }; struct DivSong { diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 3c988ceb8..385c1609b 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -135,6 +135,8 @@ DivSystem DivEngine::systemFromFile(unsigned char val) { return DIV_SYSTEM_LYNX; case 0xa9: return DIV_SYSTEM_SEGAPCM_COMPAT; + case 0xaa: + return DIV_SYSTEM_VERA; case 0xde: return DIV_SYSTEM_YM2610B_EXT; case 0xe0: @@ -258,6 +260,8 @@ unsigned char DivEngine::systemToFile(DivSystem val) { return 0xa8; case DIV_SYSTEM_SEGAPCM_COMPAT: return 0xa9; + case DIV_SYSTEM_VERA: + return 0xaa; case DIV_SYSTEM_YM2610B_EXT: return 0xde; case DIV_SYSTEM_QSOUND: @@ -383,6 +387,8 @@ int DivEngine::getChannelCount(DivSystem sys) { case DIV_SYSTEM_YM2610B_EXT: case DIV_SYSTEM_QSOUND: return 19; + case DIV_SYSTEM_VERA: + return 17; } return 0; } @@ -522,6 +528,10 @@ const char* DivEngine::getSongSystemName() { if (song.system[0]==DIV_SYSTEM_AY8910 && song.system[1]==DIV_SYSTEM_AY8910) { return "Bally Midway MCR"; } + + if (song.system[0]==DIV_SYSTEM_YM2151 && song.system[1]==DIV_SYSTEM_VERA) { + return "Commander X16"; + } break; case 3: break; @@ -650,6 +660,8 @@ const char* DivEngine::getSystemName(DivSystem sys) { return "Taito Arcade Extended Channel 3"; case DIV_SYSTEM_QSOUND: return "Capcom QSound"; + case DIV_SYSTEM_VERA: + return "VERA"; } return "Unknown"; } @@ -775,6 +787,8 @@ const char* DivEngine::getSystemChips(DivSystem sys) { return "Yamaha YM2610B Extended Channel 3"; case DIV_SYSTEM_QSOUND: return "Capcom DL-1425"; + case DIV_SYSTEM_VERA: + return "VERA"; } return "Unknown"; } @@ -858,7 +872,7 @@ bool DivEngine::isSTDSystem(DivSystem sys) { } const char* chanNames[38][24]={ - {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "PCM"}, // YMU759/SegaPCM + {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "PCM"}, // YMU759/SegaPCM/VERA {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Square 1", "Square 2", "Square 3", "Noise"}, // Genesis {"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6", "Square 1", "Square 2", "Square 3", "Noise"}, // Genesis (extended channel 3) {"Square 1", "Square 2", "Square 3", "Noise"}, // SMS @@ -899,7 +913,7 @@ const char* chanNames[38][24]={ }; const char* chanShortNames[38][24]={ - {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "PCM"}, // YMU759 + {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "PCM"}, // YMU759/VERA {"F1", "F2", "F3", "F4", "F5", "F6", "S1", "S2", "S3", "NO"}, // Genesis {"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6", "S1", "S2", "S3", "S4"}, // Genesis (extended channel 3) {"S1", "S2", "S3", "NO"}, // SMS @@ -939,7 +953,7 @@ const char* chanShortNames[38][24]={ {"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"}, // YM2610B (extended channel 3) }; -const int chanTypes[38][24]={ +const int chanTypes[39][24]={ {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4}, // YMU759 {0, 0, 0, 0, 0, 0, 1, 1, 1, 2}, // Genesis {0, 0, 5, 5, 5, 5, 0, 0, 0, 1, 1, 1, 2}, // Genesis (extended channel 3) @@ -978,9 +992,10 @@ const int chanTypes[38][24]={ {0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2}, // OPL3 4-op + drums {3, 3, 3, 3}, //Lynx {0, 0, 5, 5, 5, 5, 0, 0, 0, 1, 1, 1, 4, 4, 4, 4, 4, 4, 4}, // YM2610B (extended channel 3) + {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4}, // VERA }; -const DivInstrumentType chanPrefType[44][24]={ +const DivInstrumentType chanPrefType[45][24]={ {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM}, // YMU759 {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD}, // Genesis {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD}, // Genesis (extended channel 3) @@ -1025,6 +1040,7 @@ const DivInstrumentType chanPrefType[44][24]={ {DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ}, // Z {DIV_INS_MIKEY, DIV_INS_MIKEY, DIV_INS_MIKEY, DIV_INS_MIKEY}, // Lynx {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, // YM2610B (extended channel 3) + {DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_AMIGA}, // VERA }; const char* DivEngine::getChannelName(int chan) { @@ -1162,6 +1178,9 @@ const char* DivEngine::getChannelName(int chan) { case DIV_SYSTEM_QSOUND: return chanNames[36][dispatchChanOfChan[chan]]; break; + case DIV_SYSTEM_VERA: + return chanNames[0][dispatchChanOfChan[chan]]; + break; } return "??"; } @@ -1301,6 +1320,9 @@ const char* DivEngine::getChannelShortName(int chan) { case DIV_SYSTEM_QSOUND: return chanShortNames[36][dispatchChanOfChan[chan]]; break; + case DIV_SYSTEM_VERA: + return chanShortNames[0][dispatchChanOfChan[chan]]; + break; } return "??"; } @@ -1438,6 +1460,9 @@ int DivEngine::getChannelType(int chan) { case DIV_SYSTEM_LYNX: return chanTypes[36][dispatchChanOfChan[chan]]; break; + case DIV_SYSTEM_VERA: + return chanTypes[38][dispatchChanOfChan[chan]]; + break; } return 1; } @@ -1588,6 +1613,9 @@ DivInstrumentType DivEngine::getPreferInsType(int chan) { case DIV_SYSTEM_LYNX: return chanPrefType[42][dispatchChanOfChan[chan]]; break; + case DIV_SYSTEM_VERA: + return chanPrefType[44][dispatchChanOfChan[chan]]; + break; } return DIV_INS_FM; } diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 58e95003f..f4fd48a28 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4597,6 +4597,7 @@ bool FurnaceGUI::loop() { sysAddOption(DIV_SYSTEM_AY8930); sysAddOption(DIV_SYSTEM_LYNX); sysAddOption(DIV_SYSTEM_QSOUND); + sysAddOption(DIV_SYSTEM_VERA); ImGui::EndMenu(); } if (ImGui::BeginMenu("configure system...")) { @@ -4715,7 +4716,7 @@ bool FurnaceGUI::loop() { break; } case DIV_SYSTEM_YM2151: - if (ImGui::RadioButton("NTSC (3.58MHz)",flags==0)) { + if (ImGui::RadioButton("NTSC/X16 (3.58MHz)",flags==0)) { e->setSysFlags(i,0,restart); updateWindowTitle(); } @@ -4913,6 +4914,7 @@ bool FurnaceGUI::loop() { sysChangeOption(i,DIV_SYSTEM_AY8930); sysChangeOption(i,DIV_SYSTEM_LYNX); sysChangeOption(i,DIV_SYSTEM_QSOUND); + sysChangeOption(i,DIV_SYSTEM_VERA); ImGui::EndMenu(); } } diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 4afa67a5c..1650fd151 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -27,7 +27,7 @@ #include #include "plot_nolerp.h" -const char* insTypes[24]={ +const char* insTypes[25]={ "Standard", "FM (4-operator)", "Game Boy", @@ -51,7 +51,8 @@ const char* insTypes[24]={ "POKEY", "PC Beeper", "WonderSwan", - "Atari Lynx" + "Atari Lynx", + "VERA" }; const char* ssgEnvTypes[8]={ @@ -783,9 +784,9 @@ void FurnaceGUI::drawInsEdit() { } else { DivInstrument* ins=e->song.ins[curIns]; ImGui::InputText("Name",&ins->name); - if (ins->type<0 || ins->type>23) ins->type=DIV_INS_FM; + if (ins->type<0 || ins->type>24) ins->type=DIV_INS_FM; int insType=ins->type; - if (ImGui::Combo("Type",&insType,insTypes,24,24)) { + if (ImGui::Combo("Type",&insType,insTypes,25,24)) { ins->type=(DivInstrumentType)insType; } @@ -1304,7 +1305,7 @@ void FurnaceGUI::drawInsEdit() { float loopIndicator[256]; const char* volumeLabel="Volume"; - int volMax=(ins->type==DIV_INS_PCE || ins->type==DIV_INS_AY8930)?31:15; + int volMax=15; int volMin=0; if (ins->type==DIV_INS_C64) { if (ins->c64.volIsCutoff) { @@ -1317,6 +1318,12 @@ void FurnaceGUI::drawInsEdit() { } } } + if ((ins->type==DIV_INS_PCE || ins->type==DIV_INS_AY8930)) { + volMax=31; + } + if (ins->type==DIV_INS_VERA) { + volMax=63; + } if (ins->type==DIV_INS_AMIGA) { volMax=64; } @@ -1330,7 +1337,7 @@ void FurnaceGUI::drawInsEdit() { bool arpMode=ins->std.arpMacroMode; const char* dutyLabel="Duty/Noise"; - int dutyMax=(ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930)?31:3; + int dutyMax=3; if (ins->type==DIV_INS_C64) { dutyLabel="Duty"; if (ins->c64.dutyIsAbs) { @@ -1342,6 +1349,9 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_FM) { dutyMax=32; } + if ((ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930)) { + dutyMax=31; + } if (ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_FM) { dutyLabel="Noise Freq"; } @@ -1361,9 +1371,13 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_OPLL || ins->type==DIV_INS_OPL) { dutyMax=0; } + if (ins->type==DIV_INS_VERA) { + dutyLabel="Duty"; + dutyMax=63; + } bool dutyIsRel=(ins->type==DIV_INS_C64 && !ins->c64.dutyIsAbs); - int waveMax=(ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930)?3:63; + int waveMax=(ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_VERA)?3:63; bool bitMode=false; if (ins->type==DIV_INS_C64 || ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_SAA1099) { bitMode=true; From 5ad54dad4df8b4c51c08c65ba5c07cb7e79ca934 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 4 Mar 2022 14:31:29 -0500 Subject: [PATCH 085/637] allocate VERA chip --- papers/format.md | 1 + 1 file changed, 1 insertion(+) diff --git a/papers/format.md b/papers/format.md index 8e8c62e23..4df49ed02 100644 --- a/papers/format.md +++ b/papers/format.md @@ -173,6 +173,7 @@ size | description | - 0xa9: SegaPCM (for Deflemask Compatibility) - 5 channels | - 0xaa: MSM6295 - 4 channels | - 0xab: MSM6258 - 1 channel + | - 0xac: Commander X16 (VERA) - 17 channels | - 0xde: YM2610B extended - 19 channels | - 0xe0: QSound - 19 channels | - (compound!) means that the system is composed of two or more chips, From 837b070aeeeea6aa1e89136be46c53c0113dec0a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 4 Mar 2022 14:52:35 -0500 Subject: [PATCH 086/637] add a pull request template --- .github/pull_request_template.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..6b5659d36 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1 @@ + From d209a45b92f93b3b6fb6b9701617e517d6ffd1c5 Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Sat, 5 Mar 2022 03:11:11 +0700 Subject: [PATCH 087/637] Change sound chip ID to 0xac --- src/engine/sysDef.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 385c1609b..ee771115e 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -135,7 +135,7 @@ DivSystem DivEngine::systemFromFile(unsigned char val) { return DIV_SYSTEM_LYNX; case 0xa9: return DIV_SYSTEM_SEGAPCM_COMPAT; - case 0xaa: + case 0xac: return DIV_SYSTEM_VERA; case 0xde: return DIV_SYSTEM_YM2610B_EXT; @@ -261,7 +261,7 @@ unsigned char DivEngine::systemToFile(DivSystem val) { case DIV_SYSTEM_SEGAPCM_COMPAT: return 0xa9; case DIV_SYSTEM_VERA: - return 0xaa; + return 0xac; case DIV_SYSTEM_YM2610B_EXT: return 0xde; case DIV_SYSTEM_QSOUND: From 84ba63db24bc640585a8229b85d2c5d7e2189028 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 4 Mar 2022 18:18:43 -0500 Subject: [PATCH 088/637] add PC speaker system haha --- CMakeLists.txt | 1 + src/engine/dispatchContainer.cpp | 4 + src/engine/platform/pcspkr.cpp | 267 +++++++++++++++++++++++++++++++ src/engine/platform/pcspkr.h | 84 ++++++++++ src/gui/gui.cpp | 2 + 5 files changed, 358 insertions(+) create mode 100644 src/engine/platform/pcspkr.cpp create mode 100644 src/engine/platform/pcspkr.h diff --git a/CMakeLists.txt b/CMakeLists.txt index af3e3a7d1..8bcb8ce98 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -301,6 +301,7 @@ src/engine/platform/ay8930.cpp src/engine/platform/tia.cpp src/engine/platform/saa.cpp src/engine/platform/amiga.cpp +src/engine/platform/pcspkr.cpp src/engine/platform/segapcm.cpp src/engine/platform/qsound.cpp src/engine/platform/dummy.cpp diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 8398ca3d0..0682bbd71 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -37,6 +37,7 @@ #include "platform/tia.h" #include "platform/saa.h" #include "platform/amiga.h" +#include "platform/pcspkr.h" #include "platform/segapcm.h" #include "platform/qsound.h" #include "platform/dummy.h" @@ -216,6 +217,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do ((DivPlatformSAA1099*)dispatch)->setCore((DivSAACores)saaCore); break; } + case DIV_SYSTEM_PCSPKR: + dispatch=new DivPlatformPCSpeaker; + break; case DIV_SYSTEM_LYNX: dispatch=new DivPlatformLynx; break; diff --git a/src/engine/platform/pcspkr.cpp b/src/engine/platform/pcspkr.cpp new file mode 100644 index 000000000..8a991387d --- /dev/null +++ b/src/engine/platform/pcspkr.cpp @@ -0,0 +1,267 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "pcspkr.h" +#include "../engine.h" +#include + +#define PCSPKR_DIVIDER 4 +#define CHIP_DIVIDER 1 + +const char* regCheatSheetPCSpeaker[]={ + "Period", "0", + NULL +}; + +const char** DivPlatformPCSpeaker::getRegisterSheet() { + return regCheatSheetPCSpeaker; +} + +const char* DivPlatformPCSpeaker::getEffectName(unsigned char effect) { + return NULL; +} + +void DivPlatformPCSpeaker::acquire(short* bufL, short* bufR, size_t start, size_t len) { + for (size_t i=start; i>1):((freq+1)>>1); + } + flip=!flip; + } + bufL[i]=(flip && !isMuted[0])?32767:0; + } else { + bufL[i]=0; + } + } +} + +void DivPlatformPCSpeaker::tick() { + for (int i=0; i<1; i++) { + chan[i].std.next(); + if (chan[i].std.hadVol) { + // ok, why are the volumes like that? + chan[i].outVol=chan[i].vol; + } + if (chan[i].std.hadArp) { + if (!chan[i].inPorta) { + if (chan[i].std.arpMode) { + chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp); + } else { + chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp); + } + } + chan[i].freqChanged=true; + } else { + if (chan[i].std.arpMode && chan[i].std.finishedArp) { + chan[i].baseFreq=NOTE_PERIODIC(chan[i].note); + chan[i].freqChanged=true; + } + } + if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true)-1; + if (chan[i].freq<0) chan[i].freq=0; + if (chan[i].freq>65535) chan[i].freq=65535; + if (chan[i].keyOn) { + on=true; + } + if (chan[i].keyOff) { + on=false; + } + freq=chan[i].freq; + if (chan[i].keyOn) chan[i].keyOn=false; + if (chan[i].keyOff) chan[i].keyOff=false; + chan[i].freqChanged=false; + } + } +} + +int DivPlatformPCSpeaker::dispatch(DivCommand c) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + } + chan[c.chan].active=true; + chan[c.chan].keyOn=true; + chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + break; + case DIV_CMD_NOTE_OFF: + chan[c.chan].active=false; + chan[c.chan].keyOff=true; + chan[c.chan].std.init(NULL); + break; + case DIV_CMD_NOTE_OFF_ENV: + case DIV_CMD_ENV_RELEASE: + chan[c.chan].std.release(); + break; + case DIV_CMD_INSTRUMENT: + if (chan[c.chan].ins!=c.value || c.value2==1) { + chan[c.chan].ins=c.value; + } + break; + case DIV_CMD_VOLUME: + if (chan[c.chan].vol!=c.value) { + chan[c.chan].vol=c.value; + if (!chan[c.chan].std.hasVol) { + chan[c.chan].outVol=c.value; + } + if (chan[c.chan].active) { + on=chan[c.chan].vol; + } + } + break; + case DIV_CMD_GET_VOLUME: + return chan[c.chan].vol; + break; + case DIV_CMD_PITCH: + chan[c.chan].pitch=c.value; + chan[c.chan].freqChanged=true; + break; + case DIV_CMD_NOTE_PORTA: { + int destFreq=NOTE_PERIODIC(c.value2); + bool return2=false; + if (destFreq>chan[c.chan].baseFreq) { + chan[c.chan].baseFreq+=c.value; + if (chan[c.chan].baseFreq>=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } else { + chan[c.chan].baseFreq-=c.value; + if (chan[c.chan].baseFreq<=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } + chan[c.chan].freqChanged=true; + if (return2) { + chan[c.chan].inPorta=false; + return 2; + } + break; + } + case DIV_CMD_LEGATO: + if (c.chan==3) break; + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.willArp && !chan[c.chan].std.arpMode)?(chan[c.chan].std.arp):(0))); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + break; + case DIV_CMD_PRE_PORTA: + if (chan[c.chan].active && c.value2) { + if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + } + chan[c.chan].inPorta=c.value; + break; + case DIV_CMD_GET_VOLMAX: + return 1; + break; + case DIV_ALWAYS_SET_VOLUME: + return 1; + break; + default: + break; + } + return 1; +} + +void DivPlatformPCSpeaker::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; +} + +void DivPlatformPCSpeaker::forceIns() { + for (int i=0; i<5; i++) { + chan[i].insChanged=true; + } +} + +void* DivPlatformPCSpeaker::getChanState(int ch) { + return &chan[ch]; +} + +unsigned char* DivPlatformPCSpeaker::getRegisterPool() { + return regPool; +} + +int DivPlatformPCSpeaker::getRegisterPoolSize() { + return 2; +} + +void DivPlatformPCSpeaker::reset() { + for (int i=0; i<1; i++) { + chan[i]=DivPlatformPCSpeaker::Channel(); + } + if (dumpWrites) { + addWrite(0xffffffff,0); + } + + on=false; + freq=0; + pos=0; + + memset(regPool,0,2); +} + +bool DivPlatformPCSpeaker::keyOffAffectsArp(int ch) { + return true; +} + +void DivPlatformPCSpeaker::setFlags(unsigned int flags) { + chipClock=COLOR_NTSC/3.0; + rate=chipClock/PCSPKR_DIVIDER; +} + +void DivPlatformPCSpeaker::notifyInsDeletion(void* ins) { + for (int i=0; i<1; i++) { + chan[i].std.notifyInsDeletion((DivInstrument*)ins); + } +} + +void DivPlatformPCSpeaker::poke(unsigned int addr, unsigned short val) { + // ??? +} + +void DivPlatformPCSpeaker::poke(std::vector& wlist) { + // ??? +} + +int DivPlatformPCSpeaker::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { + parent=p; + dumpWrites=false; + skipRegisterWrites=false; + for (int i=0; i<1; i++) { + isMuted[i]=false; + } + setFlags(flags); + + reset(); + return 5; +} + +void DivPlatformPCSpeaker::quit() { +} + +DivPlatformPCSpeaker::~DivPlatformPCSpeaker() { +} diff --git a/src/engine/platform/pcspkr.h b/src/engine/platform/pcspkr.h new file mode 100644 index 000000000..90e19b8ca --- /dev/null +++ b/src/engine/platform/pcspkr.h @@ -0,0 +1,84 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _PCSPKR_H +#define _PCSPKR_H + +#include "../dispatch.h" +#include "../macroInt.h" + +class DivPlatformPCSpeaker: public DivDispatch { + struct Channel { + int freq, baseFreq, pitch, note; + unsigned char ins, duty, sweep; + bool active, insChanged, freqChanged, sweepChanged, keyOn, keyOff, inPorta, furnaceDac; + signed char vol, outVol, wave; + DivMacroInt std; + Channel(): + freq(0), + baseFreq(0), + pitch(0), + note(0), + ins(-1), + duty(0), + sweep(8), + active(false), + insChanged(true), + freqChanged(false), + sweepChanged(false), + keyOn(false), + keyOff(false), + inPorta(false), + furnaceDac(false), + vol(15), + outVol(15), + wave(-1) {} + }; + Channel chan[1]; + bool isMuted[1]; + bool on, flip; + int pos; + unsigned short freq; + unsigned char regPool[2]; + + friend void putDispatchChan(void*,int,int); + + public: + void acquire(short* bufL, short* bufR, size_t start, size_t len); + int dispatch(DivCommand c); + void* getChanState(int chan); + unsigned char* getRegisterPool(); + int getRegisterPoolSize(); + void reset(); + void forceIns(); + void tick(); + void muteChannel(int ch, bool mute); + bool keyOffAffectsArp(int ch); + void setFlags(unsigned int flags); + void notifyInsDeletion(void* ins); + void poke(unsigned int addr, unsigned short val); + void poke(std::vector& wlist); + const char** getRegisterSheet(); + const char* getEffectName(unsigned char effect); + int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); + void quit(); + ~DivPlatformPCSpeaker(); +}; + +#endif diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 58e95003f..7ee75fc4d 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4589,6 +4589,7 @@ bool FurnaceGUI::loop() { sysAddOption(DIV_SYSTEM_YM2610B_EXT); sysAddOption(DIV_SYSTEM_AY8910); sysAddOption(DIV_SYSTEM_AMIGA); + sysAddOption(DIV_SYSTEM_PCSPKR); sysAddOption(DIV_SYSTEM_OPLL); sysAddOption(DIV_SYSTEM_OPLL_DRUMS); sysAddOption(DIV_SYSTEM_VRC7); @@ -4905,6 +4906,7 @@ bool FurnaceGUI::loop() { sysChangeOption(i,DIV_SYSTEM_YM2610B_EXT); sysChangeOption(i,DIV_SYSTEM_AY8910); sysChangeOption(i,DIV_SYSTEM_AMIGA); + sysChangeOption(i,DIV_SYSTEM_PCSPKR); sysChangeOption(i,DIV_SYSTEM_OPLL); sysChangeOption(i,DIV_SYSTEM_OPLL_DRUMS); sysChangeOption(i,DIV_SYSTEM_VRC7); From ea290a5015c286beb5bff75a693b6b447407258c Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 4 Mar 2022 18:37:04 -0500 Subject: [PATCH 089/637] screw ME --- src/engine/platform/pcspkr.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/pcspkr.cpp b/src/engine/platform/pcspkr.cpp index 8a991387d..144ec775f 100644 --- a/src/engine/platform/pcspkr.cpp +++ b/src/engine/platform/pcspkr.cpp @@ -192,7 +192,7 @@ void DivPlatformPCSpeaker::muteChannel(int ch, bool mute) { } void DivPlatformPCSpeaker::forceIns() { - for (int i=0; i<5; i++) { + for (int i=0; i<1; i++) { chan[i].insChanged=true; } } From b6717fd3143a5317a11fdaba31f6339ccda895a1 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 4 Mar 2022 23:11:34 -0500 Subject: [PATCH 090/637] uninitialized variable --- src/engine/platform/pcspkr.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/engine/platform/pcspkr.cpp b/src/engine/platform/pcspkr.cpp index 144ec775f..76d6612ab 100644 --- a/src/engine/platform/pcspkr.cpp +++ b/src/engine/platform/pcspkr.cpp @@ -220,6 +220,7 @@ void DivPlatformPCSpeaker::reset() { on=false; freq=0; pos=0; + flip=false; memset(regPool,0,2); } From 16dfc785d334bdc16da91b6b36d405ba979b9c65 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 5 Mar 2022 00:36:50 -0500 Subject: [PATCH 091/637] PC speaker: improvements --- src/engine/platform/pcspkr.cpp | 75 ++++++++++++++++++++++++++++++++-- src/engine/platform/pcspkr.h | 9 +++- src/engine/song.h | 6 +++ src/gui/gui.cpp | 22 ++++++++++ 4 files changed, 107 insertions(+), 5 deletions(-) diff --git a/src/engine/platform/pcspkr.cpp b/src/engine/platform/pcspkr.cpp index 76d6612ab..51353fedf 100644 --- a/src/engine/platform/pcspkr.cpp +++ b/src/engine/platform/pcspkr.cpp @@ -37,7 +37,10 @@ const char* DivPlatformPCSpeaker::getEffectName(unsigned char effect) { return NULL; } -void DivPlatformPCSpeaker::acquire(short* bufL, short* bufR, size_t start, size_t len) { +const float cut=0.05; +const float reso=0.06; + +void DivPlatformPCSpeaker::acquire_unfilt(short* bufL, short* bufR, size_t start, size_t len) { for (size_t i=start; i>1):((freq+1)>>1); + pos+=freq; } - flip=!flip; } - bufL[i]=(flip && !isMuted[0])?32767:0; + bufL[i]=(pos>(freq>>1) && !isMuted[0])?32767:0; } else { bufL[i]=0; } } } +void DivPlatformPCSpeaker::acquire_cone(short* bufL, short* bufR, size_t start, size_t len) { + for (size_t i=start; i((freq+16)>>1) && !isMuted[0])?1:0; + low+=0.04*band; + band+=0.04*(next-low-band); + float out=(low+band)*0.75; + if (out>1.0) out=1.0; + if (out<-1.0) out=-1.0; + bufL[i]=out*32767; + } else { + bufL[i]=0; + } + } +} + +void DivPlatformPCSpeaker::acquire_piezo(short* bufL, short* bufR, size_t start, size_t len) { + for (size_t i=start; i((freq+64)>>1) && !isMuted[0])?1:0; + low+=cut*band; + band+=cut*(next-low-(reso*band)); + float out=band*0.15-(next-low)*0.06; + if (out>1.0) out=1.0; + if (out<-1.0) out=-1.0; + bufL[i]=out*32767; + } else { + bufL[i]=0; + } + } +} + +void DivPlatformPCSpeaker::acquire(short* bufL, short* bufR, size_t start, size_t len) { + switch (speakerType) { + case 0: + acquire_unfilt(bufL,bufR,start,len); + break; + case 1: + acquire_cone(bufL,bufR,start,len); + break; + case 2: + acquire_piezo(bufL,bufR,start,len); + break; + } +} + void DivPlatformPCSpeaker::tick() { for (int i=0; i<1; i++) { chan[i].std.next(); @@ -221,6 +285,8 @@ void DivPlatformPCSpeaker::reset() { freq=0; pos=0; flip=false; + low=0; + band=0; memset(regPool,0,2); } @@ -232,6 +298,7 @@ bool DivPlatformPCSpeaker::keyOffAffectsArp(int ch) { void DivPlatformPCSpeaker::setFlags(unsigned int flags) { chipClock=COLOR_NTSC/3.0; rate=chipClock/PCSPKR_DIVIDER; + speakerType=flags&3; } void DivPlatformPCSpeaker::notifyInsDeletion(void* ins) { diff --git a/src/engine/platform/pcspkr.h b/src/engine/platform/pcspkr.h index 90e19b8ca..caf6b7f3a 100644 --- a/src/engine/platform/pcspkr.h +++ b/src/engine/platform/pcspkr.h @@ -53,12 +53,19 @@ class DivPlatformPCSpeaker: public DivDispatch { Channel chan[1]; bool isMuted[1]; bool on, flip; - int pos; + int pos, speakerType; + float low, band; + float low2, high2, band2; + float low3, band3; unsigned short freq; unsigned char regPool[2]; friend void putDispatchChan(void*,int,int); + void acquire_unfilt(short* bufL, short* bufR, size_t start, size_t len); + void acquire_cone(short* bufL, short* bufR, size_t start, size_t len); + void acquire_piezo(short* bufL, short* bufR, size_t start, size_t len); + public: void acquire(short* bufL, short* bufR, size_t start, size_t len); int dispatch(DivCommand c); diff --git a/src/engine/song.h b/src/engine/song.h index fff1065fe..d1e255d95 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -215,6 +215,12 @@ struct DivSong { // - 1: Amiga 1200 // - bit 8-14: stereo separation // - 0 is 0% while 127 is 100% + // - PC Speaker: + // - bit 0-1: speaker type + // - 0: unfiltered + // - 1: cone + // - 2: piezo + // - 3: real (TODO) // - QSound: // - bit 12-20: echo feedback // - Valid values are 0-255 diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 7ee75fc4d..191f0c21f 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4843,6 +4843,28 @@ bool FurnaceGUI::loop() { } break; } + case DIV_SYSTEM_PCSPKR: { + ImGui::Text("Speaker type:"); + if (ImGui::RadioButton("Unfiltered",(flags&3)==0)) { + e->setSysFlags(i,(flags&(~3))|0,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("Cone",(flags&3)==1)) { + e->setSysFlags(i,(flags&(~3))|1,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("Piezo",(flags&3)==2)) { + e->setSysFlags(i,(flags&(~3))|2,restart); + updateWindowTitle(); + } + /* + if (ImGui::RadioButton("Use system beeper",(flags&3)==3)) { + e->setSysFlags(i,(flags&(~3))|3,restart); + updateWindowTitle(); + } + */ + break; + } case DIV_SYSTEM_QSOUND: { ImGui::Text("Echo delay:"); int echoBufSize=2725 - (flags & 4095); From e07caddc92cc6ef4edf60366e32bb04504b701b8 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 5 Mar 2022 01:26:59 -0500 Subject: [PATCH 092/637] fix samples being cut at times --- src/engine/fileOps.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 8b5cfad5d..ec7cd1cb8 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -1112,6 +1112,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { logW("%d: sample depth is wrong! (%d)\n",i,sample->depth); sample->depth=16; } + sample->samples=(double)sample->samples/samplePitches[pitch]; sample->init(sample->samples); unsigned int k=0; From 23431323e2c0eaf52fbdaf8cff3afa37b2adff35 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 5 Mar 2022 02:13:15 -0500 Subject: [PATCH 093/637] GUI: major usability improvement coming click on orders in click-to-edit or select instrument=auto-focus pattern --- src/gui/gui.cpp | 55 ++++++++++++++++++++++++++++++++++++-------- src/gui/gui.h | 5 ++++ src/gui/orders.cpp | 26 +++++++++++++-------- src/gui/settings.cpp | 7 ++++++ 4 files changed, 75 insertions(+), 18 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 191f0c21f..8c8831b22 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -623,6 +623,10 @@ void FurnaceGUI::drawEditControls() { e->noteOff(activeNotes[i].chan); } activeNotes.clear(); + + if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { + nextWindow=GUI_WINDOW_PATTERN; + } } ImGui::Text("Edit Step"); @@ -630,6 +634,10 @@ void FurnaceGUI::drawEditControls() { if (ImGui::InputInt("##EditStep",&editStep,1,1)) { if (editStep>=e->song.patLen) editStep=e->song.patLen-1; if (editStep<0) editStep=0; + + if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { + nextWindow=GUI_WINDOW_PATTERN; + } } if (ImGui::Button(ICON_FA_PLAY "##Play")) { @@ -649,9 +657,9 @@ void FurnaceGUI::drawEditControls() { ImGui::Text("Follow"); ImGui::SameLine(); - ImGui::Checkbox("Orders",&followOrders); + unimportant(ImGui::Checkbox("Orders",&followOrders)); ImGui::SameLine(); - ImGui::Checkbox("Pattern",&followPattern); + unimportant(ImGui::Checkbox("Pattern",&followPattern)); bool repeatPattern=e->getRepeatPattern(); if (ImGui::Checkbox("Repeat pattern",&repeatPattern)) { @@ -713,6 +721,10 @@ void FurnaceGUI::drawEditControls() { e->noteOff(activeNotes[i].chan); } activeNotes.clear(); + + if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { + nextWindow=GUI_WINDOW_PATTERN; + } } ImGui::SameLine(); @@ -722,14 +734,18 @@ void FurnaceGUI::drawEditControls() { if (ImGui::InputInt("##EditStep",&editStep,1,1)) { if (editStep>=e->song.patLen) editStep=e->song.patLen-1; if (editStep<0) editStep=0; + + if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { + nextWindow=GUI_WINDOW_PATTERN; + } } ImGui::SameLine(); ImGui::Text("Follow"); ImGui::SameLine(); - ImGui::Checkbox("Orders",&followOrders); + unimportant(ImGui::Checkbox("Orders",&followOrders)); ImGui::SameLine(); - ImGui::Checkbox("Pattern",&followPattern); + unimportant(ImGui::Checkbox("Pattern",&followPattern)); } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS; ImGui::End(); @@ -776,6 +792,10 @@ void FurnaceGUI::drawEditControls() { e->noteOff(activeNotes[i].chan); } activeNotes.clear(); + + if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { + nextWindow=GUI_WINDOW_PATTERN; + } } ImGui::Text("Step"); @@ -783,16 +803,20 @@ void FurnaceGUI::drawEditControls() { if (ImGui::InputInt("##EditStep",&editStep,0,0)) { if (editStep>=e->song.patLen) editStep=e->song.patLen-1; if (editStep<0) editStep=0; + + if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { + nextWindow=GUI_WINDOW_PATTERN; + } } ImGui::Text("Foll."); ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(followOrders)?0.6f:0.2f,0.2f,1.0f)); - if (ImGui::SmallButton("Ord##FollowOrders")) { + if (ImGui::SmallButton("Ord##FollowOrders")) { handleUnimportant followOrders=!followOrders; } ImGui::PopStyleColor(); ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(followPattern)?0.6f:0.2f,0.2f,1.0f)); - if (ImGui::SmallButton("Pat##FollowPattern")) { + if (ImGui::SmallButton("Pat##FollowPattern")) { handleUnimportant followPattern=!followPattern; } ImGui::PopStyleColor(); @@ -860,6 +884,10 @@ void FurnaceGUI::drawEditControls() { e->noteOff(activeNotes[i].chan); } activeNotes.clear(); + + if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { + nextWindow=GUI_WINDOW_PATTERN; + } } ImGui::Text("Step"); @@ -869,11 +897,15 @@ void FurnaceGUI::drawEditControls() { if (ImGui::InputInt("##EditStep",&editStep,1,1)) { if (editStep>=e->song.patLen) editStep=e->song.patLen-1; if (editStep<0) editStep=0; + + if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { + nextWindow=GUI_WINDOW_PATTERN; + } } ImGui::NextColumn(); - ImGui::Checkbox("Follow orders",&followOrders); - ImGui::Checkbox("Follow pattern",&followPattern); + unimportant(ImGui::Checkbox("Follow orders",&followOrders)); + unimportant(ImGui::Checkbox("Follow pattern",&followPattern)); } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS; ImGui::End(); @@ -1169,10 +1201,15 @@ void FurnaceGUI::drawInsList() { if (ImGui::Selectable(name.c_str(),curIns==i)) { curIns=i; } + if (settings.insFocusesPattern && patternOpen && ImGui::IsItemActivated()) { + nextWindow=GUI_WINDOW_PATTERN; + curIns=i; + } ImGui::PopStyleColor(); if (ImGui::IsItemHovered()) { if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { insEditOpen=true; + nextWindow=GUI_WINDOW_INS_EDIT; } } } @@ -5094,6 +5131,7 @@ bool FurnaceGUI::loop() { ImGui::DockSpaceOverViewport(); + drawPattern(); drawEditControls(); drawSongInfo(); drawOrders(); @@ -5106,7 +5144,6 @@ bool FurnaceGUI::loop() { drawMixer(); drawOsc(); drawVolMeter(); - drawPattern(); drawSettings(); drawDebug(); drawStats(); diff --git a/src/gui/gui.h b/src/gui/gui.h index e58bd17b6..7d7f6b4a0 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -29,6 +29,9 @@ #define rightClickable if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) ImGui::SetKeyboardFocusHere(-1); +#define handleUnimportant if (settings.insFocusesPattern && patternOpen) {nextWindow=GUI_WINDOW_PATTERN;} +#define unimportant(x) if (x) {handleUnimportant} + enum FurnaceGUIColors { GUI_COLOR_BACKGROUND=0, GUI_COLOR_FRAME_BACKGROUND, @@ -495,6 +498,7 @@ class FurnaceGUI { int viewPrevPattern; int guiColorsBase; int avoidRaisingPattern; + int insFocusesPattern; unsigned int maxUndoSteps; String mainFontPath; String patFontPath; @@ -537,6 +541,7 @@ class FurnaceGUI { viewPrevPattern(1), guiColorsBase(0), avoidRaisingPattern(0), + insFocusesPattern(1), maxUndoSteps(100), mainFontPath(""), patFontPath(""), diff --git a/src/gui/orders.cpp b/src/gui/orders.cpp index 94191ec04..22bd7e0d1 100644 --- a/src/gui/orders.cpp +++ b/src/gui/orders.cpp @@ -75,6 +75,10 @@ void FurnaceGUI::drawOrders() { e->setOrder(i); curNibble=false; orderCursor=-1; + + if (orderEditMode==0) { + handleUnimportant; + } } ImGui::PopStyleColor(); for (int j=0; jgetTotalChannelCount(); j++) { @@ -111,6 +115,10 @@ void FurnaceGUI::drawOrders() { curNibble=false; } } + + if (orderEditMode==0) { + handleUnimportant; + } } if (!pat->name.empty() && ImGui::IsItemHovered()) { ImGui::SetTooltip("%s",pat->name.c_str()); @@ -148,21 +156,21 @@ void FurnaceGUI::drawOrders() { ImGui::EndTable(); } ImGui::NextColumn(); - if (ImGui::Button(ICON_FA_PLUS)) { + if (ImGui::Button(ICON_FA_PLUS)) { handleUnimportant // add order row (new) doAction(GUI_ACTION_ORDERS_ADD); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Add new order"); } - if (ImGui::Button(ICON_FA_MINUS)) { + if (ImGui::Button(ICON_FA_MINUS)) { handleUnimportant // remove this order row doAction(GUI_ACTION_ORDERS_REMOVE); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Remove order"); - } - if (ImGui::Button(ICON_FA_FILES_O)) { + } + if (ImGui::Button(ICON_FA_FILES_O)) { handleUnimportant // duplicate order row doAction(GUI_ACTION_ORDERS_DUPLICATE); } @@ -172,21 +180,21 @@ void FurnaceGUI::drawOrders() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Duplicate order (right-click to deep clone)"); } - if (ImGui::Button(ICON_FA_ANGLE_UP)) { + if (ImGui::Button(ICON_FA_ANGLE_UP)) { handleUnimportant // move order row up doAction(GUI_ACTION_ORDERS_MOVE_UP); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Move order up"); } - if (ImGui::Button(ICON_FA_ANGLE_DOWN)) { + if (ImGui::Button(ICON_FA_ANGLE_DOWN)) { handleUnimportant // move order row down doAction(GUI_ACTION_ORDERS_MOVE_DOWN); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Move order down"); } - if (ImGui::Button(ICON_FA_ANGLE_DOUBLE_DOWN)) { + if (ImGui::Button(ICON_FA_ANGLE_DOUBLE_DOWN)) { handleUnimportant // duplicate order row at end doAction(GUI_ACTION_ORDERS_DUPLICATE_END); } @@ -196,7 +204,7 @@ void FurnaceGUI::drawOrders() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Duplicate order at end of song (right-click to deep clone)"); } - if (ImGui::Button(changeAllOrders?ICON_FA_LINK"##ChangeAll":ICON_FA_CHAIN_BROKEN"##ChangeAll")) { + if (ImGui::Button(changeAllOrders?ICON_FA_LINK"##ChangeAll":ICON_FA_CHAIN_BROKEN"##ChangeAll")) { handleUnimportant // whether to change one or all orders in a row changeAllOrders=!changeAllOrders; } @@ -217,7 +225,7 @@ void FurnaceGUI::drawOrders() { } else { orderEditModeLabel=ICON_FA_MOUSE_POINTER "##OrderEditMode"; } - if (ImGui::Button(orderEditModeLabel)) { + if (ImGui::Button(orderEditModeLabel)) { handleUnimportant orderEditMode++; if (orderEditMode>3) orderEditMode=0; curNibble=false; diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 304db0468..c75d5ba88 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -167,6 +167,11 @@ void FurnaceGUI::drawSettings() { settings.avoidRaisingPattern=avoidRaisingPatternB; } + bool insFocusesPatternB=settings.insFocusesPattern; + if (ImGui::Checkbox("Focus pattern editor when selecting instrument",&insFocusesPatternB)) { + settings.insFocusesPattern=insFocusesPatternB; + } + bool restartOnFlagChangeB=settings.restartOnFlagChange; if (ImGui::Checkbox("Restart song when changing system properties",&restartOnFlagChangeB)) { settings.restartOnFlagChange=restartOnFlagChangeB; @@ -870,6 +875,7 @@ void FurnaceGUI::syncSettings() { settings.viewPrevPattern=e->getConfInt("viewPrevPattern",1); settings.guiColorsBase=e->getConfInt("guiColorsBase",0); settings.avoidRaisingPattern=e->getConfInt("avoidRaisingPattern",0); + settings.insFocusesPattern=e->getConfInt("insFocusesPattern",1); // keybinds LOAD_KEYBIND(GUI_ACTION_OPEN,FURKMOD_CMD|SDLK_o); @@ -1067,6 +1073,7 @@ void FurnaceGUI::commitSettings() { e->setConf("viewPrevPattern",settings.viewPrevPattern); e->setConf("guiColorsBase",settings.guiColorsBase); e->setConf("avoidRaisingPattern",settings.avoidRaisingPattern); + e->setConf("insFocusesPattern",settings.insFocusesPattern); PUT_UI_COLOR(GUI_COLOR_BACKGROUND); PUT_UI_COLOR(GUI_COLOR_FRAME_BACKGROUND); From 227006a1241635daa138f78bcafababc4ae9164e Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 5 Mar 2022 02:28:03 -0500 Subject: [PATCH 094/637] GUI: another usability improvement instrument list focused first if it is tabbed when starting program --- src/gui/gui.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 8c8831b22..ed9900e15 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -5135,12 +5135,12 @@ bool FurnaceGUI::loop() { drawEditControls(); drawSongInfo(); drawOrders(); - drawInsList(); - drawInsEdit(); - drawWaveList(); - drawWaveEdit(); drawSampleList(); drawSampleEdit(); + drawWaveList(); + drawWaveEdit(); + drawInsList(); + drawInsEdit(); drawMixer(); drawOsc(); drawVolMeter(); From bf74068c48e01aa4062b5dbbeb6056686ce07ac8 Mon Sep 17 00:00:00 2001 From: freq-mod <32672779+freq-mod@users.noreply.github.com> Date: Sat, 5 Mar 2022 09:37:56 +0100 Subject: [PATCH 095/637] Update README.md with newly added systems and way too frequently asked question about pcm sample playback --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index bed970b19..9c01efe9d 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,10 @@ this is a work-in-progress chiptune tracker compatible with DefleMask modules (. - Philips SAA1099 - Amiga - TIA (Atari 2600/7800) + - Yamaha YM2413 (including VRC7) + - Atari Lynx + - QSound + - PC Speaker - multiple sound chips in a single song! - clean-room design (guesswork and ABX tests only, no decompilation involved) - bug/quirk implementation for increased playback accuracy @@ -29,6 +33,7 @@ this is a work-in-progress chiptune tracker compatible with DefleMask modules (. - accurate emulation cores whether possible (Nuked, MAME, SameBoy, Mednafen PCE, puNES, reSID, Stella, SAASound and ymfm) - additional features on top: - FM macros! + - negative octaves - arbitrary pitch samples - sample loop points - SSG envelopes in Neo Geo @@ -186,6 +191,10 @@ also provided are two effects: - `3xxx`: set fine duty. - `4xxx`: set fine cutoff. `xxx` range is 000-7ff. +> how do I use PCM on a PCM-capable system? + +Two possibilities: the recommended way is via creating the "Amiga/Sample" type instrument and assigning sample to it, or via old, Deflemask-compatible method, using `17xx` effect + > my song sounds very odd at a certain point file a bug report. use the Issues page. From fa911a1f1119681d2cb556a2b2bb2eedb4899e6e Mon Sep 17 00:00:00 2001 From: freq-mod <32672779+freq-mod@users.noreply.github.com> Date: Sat, 5 Mar 2022 09:45:42 +0100 Subject: [PATCH 096/637] not yet --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index 9c01efe9d..e1d97726e 100644 --- a/README.md +++ b/README.md @@ -22,10 +22,6 @@ this is a work-in-progress chiptune tracker compatible with DefleMask modules (. - Philips SAA1099 - Amiga - TIA (Atari 2600/7800) - - Yamaha YM2413 (including VRC7) - - Atari Lynx - - QSound - - PC Speaker - multiple sound chips in a single song! - clean-room design (guesswork and ABX tests only, no decompilation involved) - bug/quirk implementation for increased playback accuracy From 9611a4fcc6d2e9c9ed289cbefa832a51ab1d3494 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 5 Mar 2022 04:24:40 -0500 Subject: [PATCH 097/637] OPLL: effect description oops --- src/engine/platform/opll.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/engine/platform/opll.cpp b/src/engine/platform/opll.cpp index 5ed5b6e3c..5159b3ea1 100644 --- a/src/engine/platform/opll.cpp +++ b/src/engine/platform/opll.cpp @@ -36,10 +36,10 @@ const char* DivPlatformOPLL::getEffectName(unsigned char effect) { return "11xx: Set feedback (0 to 7)"; break; case 0x12: - return "12xx: Set level of operator 1 (0 highest, 7F lowest)"; + return "12xx: Set level of operator 1 (0 highest, 3F lowest)"; break; case 0x13: - return "13xx: Set level of operator 2 (0 highest, 7F lowest)"; + return "13xx: Set level of operator 2 (0 highest, F lowest)"; break; case 0x16: return "16xy: Set operator multiplier (x: operator from 1 to 2; y: multiplier)"; @@ -50,13 +50,13 @@ const char* DivPlatformOPLL::getEffectName(unsigned char effect) { } break; case 0x19: - return "19xx: Set attack of all operators (0 to 1F)"; + return "19xx: Set attack of all operators (0 to F)"; break; case 0x1a: - return "1Axx: Set attack of operator 1 (0 to 1F)"; + return "1Axx: Set attack of operator 1 (0 to F)"; break; case 0x1b: - return "1Bxx: Set attack of operator 2 (0 to 1F)"; + return "1Bxx: Set attack of operator 2 (0 to F)"; break; } return NULL; From 7745ebb8ec4ef5eee8a2abaae0790ff01e2843d7 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 5 Mar 2022 04:49:11 -0500 Subject: [PATCH 098/637] clamp settings to sane values --- src/gui/settings.cpp | 48 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index c75d5ba88..3f0e532db 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -393,7 +393,7 @@ void FurnaceGUI::drawSettings() { } bool macroViewB=settings.macroView; - if (ImGui::Checkbox("Classic macro view (standard macros only)",¯oViewB)) { + if (ImGui::Checkbox("Classic macro view (standard macros only; deprecated!)",¯oViewB)) { settings.macroView=macroViewB; } @@ -834,6 +834,14 @@ void FurnaceGUI::drawSettings() { #define LOAD_KEYBIND(x,y) \ actionKeys[x]=e->getConfInt("keybind_" #x,y); +#define clampSetting(x,minV,maxV) \ + if (xmaxV) { \ + x=maxV; \ + } + void FurnaceGUI::syncSettings() { settings.mainFontSize=e->getConfInt("mainFontSize",18); settings.patFontSize=e->getConfInt("patFontSize",18); @@ -861,7 +869,6 @@ void FurnaceGUI::syncSettings() { settings.allowEditDocking=e->getConfInt("allowEditDocking",0); settings.chipNames=e->getConfInt("chipNames",0); settings.overflowHighlight=e->getConfInt("overflowHighlight",0); - if (settings.fmNames<0 || settings.fmNames>2) settings.fmNames=0; settings.partyTime=e->getConfInt("partyTime",0); settings.germanNotation=e->getConfInt("germanNotation",0); settings.stepOnDelete=e->getConfInt("stepOnDelete",0); @@ -877,6 +884,43 @@ void FurnaceGUI::syncSettings() { settings.avoidRaisingPattern=e->getConfInt("avoidRaisingPattern",0); settings.insFocusesPattern=e->getConfInt("insFocusesPattern",1); + clampSetting(settings.mainFontSize,2,96); + clampSetting(settings.patFontSize,2,96); + clampSetting(settings.iconSize,2,48); + clampSetting(settings.audioEngine,0,1); + clampSetting(settings.audioQuality,0,1); + clampSetting(settings.audioBufSize,32,4096); + clampSetting(settings.audioRate,8000,384000); + clampSetting(settings.arcadeCore,0,1); + clampSetting(settings.ym2612Core,0,1); + clampSetting(settings.saaCore,0,1); + clampSetting(settings.mainFont,0,6); + clampSetting(settings.patFont,0,6); + clampSetting(settings.patRowsBase,0,1); + clampSetting(settings.orderRowsBase,0,1); + clampSetting(settings.soloAction,0,2); + clampSetting(settings.pullDeleteBehavior,0,1); + clampSetting(settings.wrapHorizontal,0,2); + clampSetting(settings.wrapVertical,0,2); + clampSetting(settings.macroView,0,1); + clampSetting(settings.fmNames,0,2); + clampSetting(settings.allowEditDocking,0,1); + clampSetting(settings.chipNames,0,1); + clampSetting(settings.overflowHighlight,0,1); + clampSetting(settings.partyTime,0,1); + clampSetting(settings.germanNotation,0,1); + clampSetting(settings.stepOnDelete,0,1); + clampSetting(settings.scrollStep,0,1); + clampSetting(settings.sysSeparators,0,1); + clampSetting(settings.forceMono,0,1); + clampSetting(settings.controlLayout,0,3); + clampSetting(settings.statusDisplay,0,3); + clampSetting(settings.dpiScale,0.0f,4.0f); + clampSetting(settings.viewPrevPattern,0,1); + clampSetting(settings.guiColorsBase,0,1); + clampSetting(settings.avoidRaisingPattern,0,1); + clampSetting(settings.insFocusesPattern,0,1); + // keybinds LOAD_KEYBIND(GUI_ACTION_OPEN,FURKMOD_CMD|SDLK_o); LOAD_KEYBIND(GUI_ACTION_SAVE,FURKMOD_CMD|SDLK_s); From 6ce2a6743c12c832f97e7037540a7b1f4d03562c Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 5 Mar 2022 04:59:05 -0500 Subject: [PATCH 099/637] lynx: fix 3xxx effect not working --- src/engine/playback.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 23cb54e1c..f0f43dc1a 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -224,14 +224,6 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe return false; } break; - case DIV_SYSTEM_LYNX: - if (effect>=0x30 && effect<0x40) { - int value = ((int)(effect&0x0f)<<8)|effectVal; - dispatchCmd(DivCommand(DIV_CMD_LYNX_LFSR_LOAD,ch,value)); - break; - } - return false; - break; case DIV_SYSTEM_OPLL_DRUMS: switch (effect) { case 0x18: // drum mode toggle @@ -533,6 +525,14 @@ bool DivEngine::perSystemPostEffect(int ch, unsigned char effect, unsigned char return false; } break; + case DIV_SYSTEM_LYNX: + if (effect>=0x30 && effect<0x40) { + int value = ((int)(effect&0x0f)<<8)|effectVal; + dispatchCmd(DivCommand(DIV_CMD_LYNX_LFSR_LOAD,ch,value)); + break; + } + return false; + break; default: return false; } From e3a27cb37cc4b71f35b17c9382fe64a4d044881c Mon Sep 17 00:00:00 2001 From: freq-mod <32672779+freq-mod@users.noreply.github.com> Date: Sat, 5 Mar 2022 19:06:46 +0100 Subject: [PATCH 100/637] Change OPLL friendly param names to be less cringy --- src/gui/insEdit.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 4afa67a5c..a58934be1 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -59,7 +59,7 @@ const char* ssgEnvTypes[8]={ }; const char* fmParamNames[3][27]={ - {"Algorithm", "Feedback", "LFO > Freq", "LFO > Amp", "Attack", "Decay", "Decay 2", "Release", "Sustain", "Level", "EnvScale", "Multiplier", "Detune", "Detune 2", "SSG-EG", "AM", "AM Depth", "Vibrato Depth", "EnvAlternate", "EnvAlternate", "LevelScale/key", "Sustain", "Vibrato", "Waveform", "EnvScale/key", "OP2 HalfSine", "OP1 HalfSine"}, + {"Algorithm", "Feedback", "LFO > Freq", "LFO > Amp", "Attack", "Decay", "Decay 2", "Release", "Sustain", "Level", "EnvScale", "Multiplier", "Detune", "Detune 2", "SSG-EG", "AM", "AM Depth", "Vibrato Depth", "Sustained Voice", "Sustained Voice", "Level Scaling", "Sustain", "Vibrato", "Waveform", "Key Scale Rate", "OP2 Half Sine", "OP1 Half Sine"}, {"ALG", "FB", "FMS/PMS", "AMS", "AR", "DR", "SR", "RR", "SL", "TL", "KS", "MULT", "DT", "DT2", "SSG-EG", "AM", "AMD", "FMD", "EGT", "EGT", "KSL", "SUS", "VIB", "WS", "KSR", "DC", "DM"}, {"ALG", "FB", "FMS/PMS", "AMS", "AR", "DR", "D2R", "RR", "SL", "TL", "RS", "MULT", "DT", "DT2", "SSG-EG", "AM", "DAM", "DVB", "EGT", "EGS", "KSL", "SUS", "VIB", "WS", "KSR", "DC", "DM"} }; From c684107b11b529b00fe9e475a5bf6d7c9d17cbf9 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 5 Mar 2022 13:21:03 -0500 Subject: [PATCH 101/637] not a voice --- src/gui/insEdit.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index a58934be1..6ff30896f 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -59,7 +59,7 @@ const char* ssgEnvTypes[8]={ }; const char* fmParamNames[3][27]={ - {"Algorithm", "Feedback", "LFO > Freq", "LFO > Amp", "Attack", "Decay", "Decay 2", "Release", "Sustain", "Level", "EnvScale", "Multiplier", "Detune", "Detune 2", "SSG-EG", "AM", "AM Depth", "Vibrato Depth", "Sustained Voice", "Sustained Voice", "Level Scaling", "Sustain", "Vibrato", "Waveform", "Key Scale Rate", "OP2 Half Sine", "OP1 Half Sine"}, + {"Algorithm", "Feedback", "LFO > Freq", "LFO > Amp", "Attack", "Decay", "Decay 2", "Release", "Sustain", "Level", "EnvScale", "Multiplier", "Detune", "Detune 2", "SSG-EG", "AM", "AM Depth", "Vibrato Depth", "Sustained", "Sustained", "Level Scaling", "Sustain", "Vibrato", "Waveform", "Key Scale Rate", "OP2 Half Sine", "OP1 Half Sine"}, {"ALG", "FB", "FMS/PMS", "AMS", "AR", "DR", "SR", "RR", "SL", "TL", "KS", "MULT", "DT", "DT2", "SSG-EG", "AM", "AMD", "FMD", "EGT", "EGT", "KSL", "SUS", "VIB", "WS", "KSR", "DC", "DM"}, {"ALG", "FB", "FMS/PMS", "AMS", "AR", "DR", "D2R", "RR", "SL", "TL", "RS", "MULT", "DT", "DT2", "SSG-EG", "AM", "DAM", "DVB", "EGT", "EGS", "KSL", "SUS", "VIB", "WS", "KSR", "DC", "DM"} }; From 6728edbb71d406e0385b689d58f63daff48c2831 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 5 Mar 2022 15:00:19 -0500 Subject: [PATCH 102/637] UNTESTED! DO NOT USE - PC speaker passthrough --- src/engine/platform/pcspkr.cpp | 35 ++++++++++++++++++++++++++++++++++ src/engine/platform/pcspkr.h | 5 +++-- src/gui/gui.cpp | 5 +---- 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/src/engine/platform/pcspkr.cpp b/src/engine/platform/pcspkr.cpp index 51353fedf..eb201c1ec 100644 --- a/src/engine/platform/pcspkr.cpp +++ b/src/engine/platform/pcspkr.cpp @@ -21,6 +21,12 @@ #include "../engine.h" #include +#ifdef __linux__ +#include +#include +#include +#endif + #define PCSPKR_DIVIDER 4 #define CHIP_DIVIDER 1 @@ -106,6 +112,19 @@ void DivPlatformPCSpeaker::acquire_piezo(short* bufL, short* bufR, size_t start, } } +void DivPlatformPCSpeaker::acquire_real(short* bufL, short* bufR, size_t start, size_t len) { +#ifdef __linux__ + if (lastOn!=on || lastFreq!=freq) { + lastOn=on; + lastFreq=freq; + ioctl(STDOUT_FILENO,KIOCSOUND,on?freq:0); + } +#endif + for (size_t i=start; i #define _USE_MATH_DEFINES #include "gui.h" #include "util.h" @@ -4894,12 +4893,10 @@ bool FurnaceGUI::loop() { e->setSysFlags(i,(flags&(~3))|2,restart); updateWindowTitle(); } - /* - if (ImGui::RadioButton("Use system beeper",(flags&3)==3)) { + if (ImGui::RadioButton("Use system beeper (Linux only!)",(flags&3)==3)) { e->setSysFlags(i,(flags&(~3))|3,restart); updateWindowTitle(); } - */ break; } case DIV_SYSTEM_QSOUND: { From 8bda9df487f5fa8463f49c761a62056b85507848 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 5 Mar 2022 16:06:41 -0500 Subject: [PATCH 103/637] prepare for OPL systems --- .gitmodules | 3 +++ extern/Nuked-OPL3 | 1 + 2 files changed, 4 insertions(+) create mode 160000 extern/Nuked-OPL3 diff --git a/.gitmodules b/.gitmodules index 435f43c75..d63fd70b5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -19,3 +19,6 @@ [submodule "extern/adpcm"] path = extern/adpcm url = https://github.com/superctr/adpcm +[submodule "extern/Nuked-OPL3"] + path = extern/Nuked-OPL3 + url = https://github.com/nukeykt/Nuked-OPL3.git diff --git a/extern/Nuked-OPL3 b/extern/Nuked-OPL3 new file mode 160000 index 000000000..bb5c8d08a --- /dev/null +++ b/extern/Nuked-OPL3 @@ -0,0 +1 @@ +Subproject commit bb5c8d08a85779c42b75c79d7b84f365a1b93b66 From c34b8325c9e9dcc9caa0b41e8b70087fc7f414de Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 5 Mar 2022 18:18:08 -0500 Subject: [PATCH 104/637] more OPL preparation DOES NOT WORK YET - JUST A PLACEHOLDER --- CMakeLists.txt | 2 + src/engine/dispatchContainer.cpp | 4 + src/engine/platform/opl.cpp | 823 +++++++++++++++++++++++++++++++ src/engine/platform/opl.h | 120 +++++ 4 files changed, 949 insertions(+) create mode 100644 src/engine/platform/opl.cpp create mode 100644 src/engine/platform/opl.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 8bcb8ce98..caf08457f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -226,6 +226,7 @@ extern/adpcm/ymz_codec.c extern/Nuked-OPN2/ym3438.c extern/opm/opm.c extern/Nuked-OPLL/opll.c +extern/Nuked-OPL3/opl3.c src/engine/platform/sound/sn76496.cpp src/engine/platform/sound/ay8910.cpp src/engine/platform/sound/saa1099.cpp @@ -298,6 +299,7 @@ src/engine/platform/ym2610b.cpp src/engine/platform/ym2610bext.cpp src/engine/platform/ay.cpp src/engine/platform/ay8930.cpp +src/engine/platform/opl.cpp src/engine/platform/tia.cpp src/engine/platform/saa.cpp src/engine/platform/amiga.cpp diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 0682bbd71..52cc7177e 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -34,6 +34,7 @@ #include "platform/ym2610bext.h" #include "platform/ay.h" #include "platform/ay8930.h" +#include "platform/opl.h" #include "platform/tia.h" #include "platform/saa.h" #include "platform/amiga.h" @@ -210,6 +211,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do ((DivPlatformOPLL*)dispatch)->setVRC7(sys==DIV_SYSTEM_VRC7); ((DivPlatformOPLL*)dispatch)->setProperDrums(sys==DIV_SYSTEM_OPLL_DRUMS); break; + case DIV_SYSTEM_OPL3: + dispatch=new DivPlatformOPL; + break; case DIV_SYSTEM_SAA1099: { int saaCore=eng->getConfInt("saaCore",0); if (saaCore<0 || saaCore>2) saaCore=0; diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp new file mode 100644 index 000000000..bb195a471 --- /dev/null +++ b/src/engine/platform/opl.cpp @@ -0,0 +1,823 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "opl.h" +#include "../engine.h" +#include +#include + +#define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;} +#define immWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } + +#define CHIP_FREQBASE 4720272 + +// N = invalid +#define N 255 + +/* +const unsigned char slotsOPL2[4][20]={ + {0, 1, 2, 6, 7, 8, 12, 13, 14}, // OP1 + {3, 4, 5, 9, 10, 11, 15, 16, 17}, // OP2 + {N, N, N, N, N, N, N, N, N}, + {N, N, N, N, N, N, N, N, N} +}; + +const unsigned char slotsOPL2Drums[4][20]={ + {0, 1, 2, 6, 7, 8, 12, 16, 14, 17, 13}, // OP1 + {3, 4, 5, 9, 10, 11, 15, N, N, N, N}, // OP2 + {N, N, N, N, N, N, N, N, N, N, N}, + {N, N, N, N, N, N, N, N, N, N, N} +}; + +const unsigned char slotsOPL3[4][20]={ + {0, 6, 1, 7, 2, 8, 18, 24, 19, 25, 20, 26, 30, 31, 32, 12, 13, 14}, // OP1 + {3, 9, 4, 10, 5, 11, 21, 27, 22, 28, 23, 29, 33, 34, 35, 15, 16, 17}, // OP2 + {6, N, 7, N, 8, N, 24, N, 25, N, 26, N, N, N, N, N, N, N}, // OP3 + {9, N, 10, N, 11, N, 27, N, 28, N, 29, N, N, N, N, N, N, N} // OP4 +}; + +const unsigned char slotsOPL3Drums[4][20]={ + {0, 6, 1, 7, 2, 8, 18, 24, 19, 25, 20, 26, 30, 31, 32, 12, 16, 14, 17, 13}, // OP1 + {3, 9, 4, 10, 5, 11, 21, 27, 22, 28, 23, 29, 33, 34, 35, N, N, N, N, N}, // OP2 + {6, N, 7, N, 8, N, 24, N, 25, N, 26, N, N, N, N, N, N, N, N, N}, // OP3 + {9, N, 10, N, 11, N, 27, N, 28, N, 29, N, N, N, N, N, N, N, N, N} // OP4 +}; +*/ + +#undef N + +const char* DivPlatformOPL::getEffectName(unsigned char effect) { + switch (effect) { + case 0x10: + return "10xy: Setup LFO (x: enable; y: speed)"; + break; + case 0x11: + return "11xx: Set feedback (0 to 7)"; + break; + case 0x12: + return "12xx: Set level of operator 1 (0 highest, 3F lowest)"; + break; + case 0x13: + return "13xx: Set level of operator 2 (0 highest, 3F lowest)"; + break; + case 0x14: + return "14xx: Set level of operator 3 (0 highest, 3F lowest; 4-op only)"; + break; + case 0x15: + return "15xx: Set level of operator 4 (0 highest, 3F lowest; 4-op only)"; + break; + case 0x16: + return "16xy: Set operator multiplier (x: operator from 1 to 4; y: multiplier)"; + break; + case 0x17: + return "17xx: Enable channel 6 DAC"; + break; + case 0x18: + return "18xx: Toggle extended channel 3 mode"; + break; + case 0x19: + return "19xx: Set attack of all operators (0 to F)"; + break; + case 0x1a: + return "1Axx: Set attack of operator 1 (0 to F)"; + break; + case 0x1b: + return "1Bxx: Set attack of operator 2 (0 to F)"; + break; + case 0x1c: + return "1Cxx: Set attack of operator 3 (0 to F; 4-op only)"; + break; + case 0x1d: + return "1Dxx: Set attack of operator 4 (0 to F; 4-op only)"; + break; + case 0x20: + return "20xy: Set PSG noise mode (x: preset freq/ch3 freq; y: thin pulse/noise)"; + break; + } + return NULL; +} + +void DivPlatformOPL::acquire_nuked(short* bufL, short* bufR, size_t start, size_t len) { + static short o[2]; + static int os[2]; + + for (size_t h=start; h>8)<<1),w.val); + //printf("write: %x = %.2x\n",w.addr,w.val); + lastBusy=0; + regPool[w.addr&0x1ff]=w.val; + writes.pop(); + } else { + lastBusy++; + //printf("busycounter: %d\n",lastBusy); + OPL3_WriteReg(&fm,0x0+((w.addr>>8)<<1),w.addr); + w.addrOrVal=true; + } + } + + OPL3_Generate(&fm,o); os[0]+=o[0]; os[1]+=o[1]; + + if (os[0]<-32768) os[0]=-32768; + if (os[0]>32767) os[0]=32767; + + if (os[1]<-32768) os[1]=-32768; + if (os[1]>32767) os[1]=32767; + + bufL[h]=os[0]; + bufR[h]=os[1]; + } +} + +void DivPlatformOPL::acquire(short* bufL, short* bufR, size_t start, size_t len) { + //if (useYMFM) { + // acquire_ymfm(bufL,bufR,start,len); + //} else { + acquire_nuked(bufL,bufR,start,len); + //} +} + +void DivPlatformOPL::tick() { + /* + for (int i=0; i<20; i++) { + if (i==2 && extMode) continue; + chan[i].std.next(); + + if (chan[i].std.hadVol) { + chan[i].outVol=(chan[i].vol*MIN(127,chan[i].std.vol))/127; + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + DivInstrumentFM::Operator& op=chan[i].state.op[j]; + if (isMuted[i]) { + rWrite(baseAddr+ADDR_TL,127); + } else { + if (isOutput[chan[i].state.alg][j]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + } + } + + if (chan[i].std.hadArp) { + if (!chan[i].inPorta) { + if (chan[i].std.arpMode) { + chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp); + } else { + chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+(signed char)chan[i].std.arp); + } + } + chan[i].freqChanged=true; + } else { + if (chan[i].std.arpMode && chan[i].std.finishedArp) { + chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note); + chan[i].freqChanged=true; + } + } + + if (chan[i].std.hadAlg) { + chan[i].state.alg=chan[i].std.alg; + rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); + if (!parent->song.algMacroBehavior) for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + DivInstrumentFM::Operator& op=chan[i].state.op[j]; + if (isMuted[i]) { + rWrite(baseAddr+ADDR_TL,127); + } else { + if (isOutput[chan[i].state.alg][j]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + } + } + if (chan[i].std.hadFb) { + chan[i].state.fb=chan[i].std.fb; + rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); + } + if (chan[i].std.hadFms) { + chan[i].state.fms=chan[i].std.fms; + rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + } + if (chan[i].std.hadAms) { + chan[i].state.ams=chan[i].std.ams; + rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + } + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + DivInstrumentFM::Operator& op=chan[i].state.op[j]; + DivMacroInt::IntOp& m=chan[i].std.op[j]; + if (m.hadAm) { + op.am=m.am; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + if (m.hadAr) { + op.ar=m.ar; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + if (m.hadDr) { + op.dr=m.dr; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + if (m.hadMult) { + op.mult=m.mult; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + if (m.hadRr) { + op.rr=m.rr; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + if (m.hadSl) { + op.sl=m.sl; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + if (m.hadTl) { + op.tl=127-m.tl; + if (isMuted[i]) { + rWrite(baseAddr+ADDR_TL,127); + } else { + if (isOutput[chan[i].state.alg][j]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + } + if (m.hadRs) { + op.rs=m.rs; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + if (m.hadDt) { + op.dt=m.dt; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + if (m.hadD2r) { + op.d2r=m.d2r; + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + } + if (m.hadSsg) { + op.ssgEnv=m.ssg; + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + } + + if (chan[i].keyOn || chan[i].keyOff) { + immWrite(0x28,0x00|konOffs[i]); + chan[i].keyOff=false; + } + } + */ + + for (int i=0; i<512; i++) { + if (pendingWrites[i]!=oldWrites[i]) { + immWrite(i,pendingWrites[i]&0xff); + oldWrites[i]=pendingWrites[i]; + } + } + + /* + for (int i=0; i<20; i++) { + if (chan[i].freqChanged) { + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq)); + if (chan[i].freq>262143) chan[i].freq=262143; + int freqt=toFreq(chan[i].freq); + immWrite(chanOffs[i]+ADDR_FREQH,freqt>>8); + immWrite(chanOffs[i]+ADDR_FREQ,freqt&0xff); + if (chan[i].furnaceDac && dacMode) { + double off=1.0; + if (dacSample>=0 && dacSamplesong.sampleLen) { + DivSample* s=parent->getSample(dacSample); + if (s->centerRate<1) { + off=1.0; + } else { + off=8363.0/(double)s->centerRate; + } + } + dacRate=(1280000*1.25*off)/MAX(1,chan[i].baseFreq); + if (dacRate<1) dacRate=1; + if (dumpWrites) addWrite(0xffff0001,1280000/dacRate); + } + chan[i].freqChanged=false; + } + if (chan[i].keyOn) { + immWrite(0x28,0xf0|konOffs[i]); + chan[i].keyOn=false; + } + } + */ +} + +int DivPlatformOPL::octave(int freq) { + if (freq>=82432) { + return 128; + } else if (freq>=41216) { + return 64; + } else if (freq>=20608) { + return 32; + } else if (freq>=10304) { + return 16; + } else if (freq>=5152) { + return 8; + } else if (freq>=2576) { + return 4; + } else if (freq>=1288) { + return 2; + } else { + return 1; + } + return 1; +} + +int DivPlatformOPL::toFreq(int freq) { + if (freq>=82432) { + return 0x3800|((freq>>7)&0x7ff); + } else if (freq>=41216) { + return 0x3000|((freq>>6)&0x7ff); + } else if (freq>=20608) { + return 0x2800|((freq>>5)&0x7ff); + } else if (freq>=10304) { + return 0x2000|((freq>>4)&0x7ff); + } else if (freq>=5152) { + return 0x1800|((freq>>3)&0x7ff); + } else if (freq>=2576) { + return 0x1000|((freq>>2)&0x7ff); + } else if (freq>=1288) { + return 0x800|((freq>>1)&0x7ff); + } else { + return freq&0x7ff; + } +} + +void DivPlatformOPL::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; + /* + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[ch]|opOffs[j]; + DivInstrumentFM::Operator& op=chan[ch].state.op[j]; + if (isMuted[ch]) { + rWrite(baseAddr+ADDR_TL,127); + } else { + if (isOutput[chan[ch].state.alg][j]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[ch].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + } + rWrite(chanOffs[ch]+ADDR_LRAF,(isMuted[ch]?0:(chan[ch].pan<<6))|(chan[ch].state.fms&7)|((chan[ch].state.ams&3)<<4)); + */ +} + +int DivPlatformOPL::dispatch(DivCommand c) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: { + DivInstrument* ins=parent->getIns(chan[c.chan].ins); + + if (chan[c.chan].insChanged) { + chan[c.chan].state=ins->fm; + } + + chan[c.chan].std.init(ins); + if (!chan[c.chan].std.willVol) { + chan[c.chan].outVol=chan[c.chan].vol; + } + + /* + for (int i=0; i<4; i++) { + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + if (isMuted[c.chan]) { + rWrite(baseAddr+ADDR_TL,127); + } else { + if (isOutput[chan[c.chan].state.alg][i]) { + if (!chan[c.chan].active || chan[c.chan].insChanged) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); + } + } else { + if (chan[c.chan].insChanged) { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + } + if (chan[c.chan].insChanged) { + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + } + if (chan[c.chan].insChanged) { + rWrite(chanOffs[c.chan]+ADDR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3)); + rWrite(chanOffs[c.chan]+ADDR_LRAF,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|(chan[c.chan].state.fms&7)|((chan[c.chan].state.ams&3)<<4)); + } + */ + chan[c.chan].insChanged=false; + + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); + chan[c.chan].note=c.value; + chan[c.chan].freqChanged=true; + } + chan[c.chan].keyOn=true; + chan[c.chan].active=true; + break; + } + case DIV_CMD_NOTE_OFF: + if (c.chan==5) { + dacSample=-1; + if (dumpWrites) addWrite(0xffff0002,0); + } + chan[c.chan].keyOff=true; + chan[c.chan].keyOn=false; + chan[c.chan].active=false; + break; + case DIV_CMD_NOTE_OFF_ENV: + if (c.chan==5) { + dacSample=-1; + if (dumpWrites) addWrite(0xffff0002,0); + } + chan[c.chan].keyOff=true; + chan[c.chan].keyOn=false; + chan[c.chan].active=false; + chan[c.chan].std.release(); + break; + case DIV_CMD_ENV_RELEASE: + chan[c.chan].std.release(); + break; + case DIV_CMD_VOLUME: { + chan[c.chan].vol=c.value; + if (!chan[c.chan].std.hasVol) { + chan[c.chan].outVol=c.value; + } + /* + for (int i=0; i<4; i++) { + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + if (isMuted[c.chan]) { + rWrite(baseAddr+ADDR_TL,127); + } else { + if (isOutput[chan[c.chan].state.alg][i]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + } + */ + break; + } + case DIV_CMD_GET_VOLUME: { + return chan[c.chan].vol; + break; + } + case DIV_CMD_INSTRUMENT: + if (chan[c.chan].ins!=c.value || c.value2==1) { + chan[c.chan].insChanged=true; + } + chan[c.chan].ins=c.value; + break; + case DIV_CMD_PANNING: { + switch (c.value) { + case 0x01: + chan[c.chan].pan=1; + break; + case 0x10: + chan[c.chan].pan=2; + break; + default: + chan[c.chan].pan=3; + break; + } + //rWrite(chanOffs[c.chan]+ADDR_LRAF,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|(chan[c.chan].state.fms&7)|((chan[c.chan].state.ams&3)<<4)); + break; + } + case DIV_CMD_PITCH: { + chan[c.chan].pitch=c.value; + chan[c.chan].freqChanged=true; + break; + } + case DIV_CMD_NOTE_PORTA: { + int destFreq=NOTE_FREQUENCY(c.value2); + int newFreq; + bool return2=false; + if (destFreq>chan[c.chan].baseFreq) { + newFreq=chan[c.chan].baseFreq+c.value*octave(chan[c.chan].baseFreq); + if (newFreq>=destFreq) { + newFreq=destFreq; + return2=true; + } + } else { + newFreq=chan[c.chan].baseFreq-c.value*octave(chan[c.chan].baseFreq); + if (newFreq<=destFreq) { + newFreq=destFreq; + return2=true; + } + } + if (!chan[c.chan].portaPause) { + if (octave(chan[c.chan].baseFreq)!=octave(newFreq)) { + chan[c.chan].portaPause=true; + break; + } + } + chan[c.chan].baseFreq=newFreq; + chan[c.chan].portaPause=false; + chan[c.chan].freqChanged=true; + if (return2) { + chan[c.chan].inPorta=false; + return 2; + } + break; + } + case DIV_CMD_SAMPLE_MODE: { + dacMode=c.value; + rWrite(0x2b,c.value<<7); + break; + } + case DIV_CMD_SAMPLE_BANK: + sampleBank=c.value; + if (sampleBank>(parent->song.sample.size()/12)) { + sampleBank=parent->song.sample.size()/12; + } + break; + case DIV_CMD_LEGATO: { + chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); + chan[c.chan].note=c.value; + chan[c.chan].freqChanged=true; + break; + } + case DIV_CMD_FM_LFO: { + lfoValue=(c.value&7)|((c.value>>4)<<3); + rWrite(0x22,lfoValue); + break; + } + case DIV_CMD_FM_FB: { + chan[c.chan].state.fb=c.value&7; + //rWrite(chanOffs[c.chan]+ADDR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3)); + break; + } + case DIV_CMD_FM_MULT: { + /* + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.mult=c.value2&15; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + */ + break; + } + case DIV_CMD_FM_TL: { + /* + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.tl=c.value2; + if (isMuted[c.chan]) { + rWrite(baseAddr+ADDR_TL,127); + } else { + if (isOutput[chan[c.chan].state.alg][c.value]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + */ + break; + } + case DIV_CMD_FM_AR: { + /* + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.ar=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + } else { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.ar=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + } + */ + + break; + } + case DIV_ALWAYS_SET_VOLUME: + return 0; + break; + case DIV_CMD_GET_VOLMAX: + return 127; + break; + case DIV_CMD_PRE_PORTA: + chan[c.chan].inPorta=c.value; + break; + case DIV_CMD_PRE_NOTE: + break; + default: + //printf("WARNING: unimplemented command %d\n",c.cmd); + break; + } + return 1; +} + +void DivPlatformOPL::forceIns() { + /* + for (int i=0; i<20; i++) { + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + DivInstrumentFM::Operator& op=chan[i].state.op[j]; + if (isMuted[i]) { + rWrite(baseAddr+ADDR_TL,127); + } else { + if (isOutput[chan[i].state.alg][j]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); + rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + if (chan[i].active) { + chan[i].keyOn=true; + chan[i].freqChanged=true; + } + } + if (dacMode) { + rWrite(0x2b,0x80); + } + immWrite(0x22,lfoValue); + */ +} + +void DivPlatformOPL::toggleRegisterDump(bool enable) { + DivDispatch::toggleRegisterDump(enable); +} + +void* DivPlatformOPL::getChanState(int ch) { + return &chan[ch]; +} + +unsigned char* DivPlatformOPL::getRegisterPool() { + return regPool; +} + +int DivPlatformOPL::getRegisterPoolSize() { + return 512; +} + +void DivPlatformOPL::reset() { + while (!writes.empty()) writes.pop(); + memset(regPool,0,512); + /* + if (useYMFM) { + fm_ymfm->reset(); + } + */ + OPL3_Reset(&fm,rate); + if (dumpWrites) { + addWrite(0xffffffff,0); + } + for (int i=0; i<20; i++) { + chan[i]=DivPlatformOPL::Channel(); + chan[i].vol=0x3f; + chan[i].outVol=0x3f; + } + + for (int i=0; i<512; i++) { + oldWrites[i]=-1; + pendingWrites[i]=-1; + } + + lastBusy=60; + dacMode=0; + dacPeriod=0; + dacPos=0; + dacRate=0; + dacSample=-1; + sampleBank=0; + lfoValue=8; + + extMode=false; + + // LFO + immWrite(0x22,lfoValue); + + delay=0; +} + +bool DivPlatformOPL::isStereo() { + return true; +} + +bool DivPlatformOPL::keyOffAffectsArp(int ch) { + return (ch>5); +} + +bool DivPlatformOPL::keyOffAffectsPorta(int ch) { + return (ch>5); +} + +void DivPlatformOPL::notifyInsChange(int ins) { + for (int i=0; i<20; i++) { + if (chan[i].ins==ins) { + chan[i].insChanged=true; + } + } +} + +void DivPlatformOPL::notifyInsDeletion(void* ins) { +} + +void DivPlatformOPL::poke(unsigned int addr, unsigned short val) { + immWrite(addr,val); +} + +void DivPlatformOPL::poke(std::vector& wlist) { + for (DivRegWrite& i: wlist) immWrite(i.addr,i.val); +} + +int DivPlatformOPL::getPortaFloor(int ch) { + return (ch>5)?12:0; +} + +void DivPlatformOPL::setYMFM(bool use) { + useYMFM=use; +} + +void DivPlatformOPL::setFlags(unsigned int flags) { + /* + if (flags==3) { + chipClock=COLOR_NTSC*12.0/7.0; + } else if (flags==2) { + chipClock=8000000.0; + } else if (flags==1) { + chipClock=COLOR_PAL*12.0/7.0; + } else { + chipClock=COLOR_NTSC*15.0/7.0; + } + ladder=flags&0x80000000; + OPN2_SetChipType(ladder?ym3438_mode_ym2612:0); + if (useYMFM) { + if (fm_ymfm!=NULL) delete fm_ymfm; + if (ladder) { + fm_ymfm=new ymfm::ym2612(iface); + } else { + fm_ymfm=new ymfm::ym3438(iface); + } + rate=chipClock/144; + } else { + rate=chipClock/36; + }*/ + + chipClock=COLOR_NTSC*4.0; + rate=chipClock/32; +} + +int DivPlatformOPL::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { + parent=p; + dumpWrites=false; + ladder=false; + skipRegisterWrites=false; + for (int i=0; i<20; i++) { + isMuted[i]=false; + } + setFlags(flags); + + reset(); + return 10; +} + +void DivPlatformOPL::quit() { +} + +DivPlatformOPL::~DivPlatformOPL() { +} diff --git a/src/engine/platform/opl.h b/src/engine/platform/opl.h new file mode 100644 index 000000000..c42481927 --- /dev/null +++ b/src/engine/platform/opl.h @@ -0,0 +1,120 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _OPL_H +#define _OPL_H +#include "../dispatch.h" +#include "../macroInt.h" +#include +#include "../../../extern/Nuked-OPL3/opl3.h" + +class DivPlatformOPL: public DivDispatch { + protected: + struct Channel { + DivInstrumentFM state; + DivMacroInt std; + unsigned char freqH, freqL; + int freq, baseFreq, pitch, note; + unsigned char ins; + bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnaceDac, inPorta; + int vol, outVol; + unsigned char pan; + Channel(): + freqH(0), + freqL(0), + freq(0), + baseFreq(0), + pitch(0), + note(0), + ins(-1), + active(false), + insChanged(true), + freqChanged(false), + keyOn(false), + keyOff(false), + portaPause(false), + furnaceDac(false), + inPorta(false), + vol(0), + pan(3) {} + }; + Channel chan[20]; + bool isMuted[20]; + struct QueuedWrite { + unsigned short addr; + unsigned char val; + bool addrOrVal; + QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {} + }; + std::queue writes; + opl3_chip fm; + int delay; + unsigned char lastBusy; + + unsigned char regPool[512]; + + bool dacMode; + int dacPeriod; + int dacRate; + unsigned int dacPos; + int dacSample; + unsigned char sampleBank; + unsigned char lfoValue; + + bool extMode, useYMFM; + bool ladder; + + short oldWrites[512]; + short pendingWrites[512]; + + int octave(int freq); + int toFreq(int freq); + + friend void putDispatchChan(void*,int,int); + + void acquire_nuked(short* bufL, short* bufR, size_t start, size_t len); + //void acquire_ymfm(short* bufL, short* bufR, size_t start, size_t len); + + public: + void acquire(short* bufL, short* bufR, size_t start, size_t len); + int dispatch(DivCommand c); + void* getChanState(int chan); + unsigned char* getRegisterPool(); + int getRegisterPoolSize(); + void reset(); + void forceIns(); + void tick(); + void muteChannel(int ch, bool mute); + bool isStereo(); + void setYMFM(bool use); + bool keyOffAffectsArp(int ch); + bool keyOffAffectsPorta(int ch); + void toggleRegisterDump(bool enable); + void setFlags(unsigned int flags); + void notifyInsChange(int ins); + void notifyInsDeletion(void* ins); + int getPortaFloor(int ch); + void poke(unsigned int addr, unsigned short val); + void poke(std::vector& wlist); + const char* getEffectName(unsigned char effect); + int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); + void quit(); + ~DivPlatformOPL(); +}; +#endif From 7935f527372fe526f18fff84a04aa8f58abf7db1 Mon Sep 17 00:00:00 2001 From: nicco1690 <78063037+nicco1690@users.noreply.github.com> Date: Sat, 5 Mar 2022 23:27:48 -0500 Subject: [PATCH 105/637] Create Lynx MIKEY sound docs --- papers/doc/7-systems/lynx.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 papers/doc/7-systems/lynx.md diff --git a/papers/doc/7-systems/lynx.md b/papers/doc/7-systems/lynx.md new file mode 100644 index 000000000..c9c9bc26d --- /dev/null +++ b/papers/doc/7-systems/lynx.md @@ -0,0 +1,12 @@ +# Atari Lynx/MIKEY + +The Atari Lynx is a 16 bit handheld console developed by (obviously) Atari Corporation, and initially released in September of 1989, with the worldwide release being in 1990. + +The Atari Lynx's custom sound chip and CPU (MIKEY) is a 6502-based 8 bit CPU running at 16MHz, however this information is generally not useful in the context of Furnace. + +## Sound capabilities + + - The MIKEY has 4 channels of square wave-based sound, which can be modulated with different frequencies (×0, ×1, ×2, ×3, ×4, ×5, ×7, ×10, and ×11) to create wavetable-like results. + - The MIKEY also has hard stereo panning capabilities via the `08xx` effect command. + - The MIKEY has four 8-bit DACs (Digital to Analog Converter) — one for each voice — that essentially mean you can play samples on the MIKEY (at the cost of CPU time and memory). + - The MIKEY also has a variety of pitches to choose from, and they go from 32Hz to "above the range of human hearing", according to Atari. From 86a71cc6a25e288c097975dfe2e16b9cad07d963 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 6 Mar 2022 03:33:56 -0500 Subject: [PATCH 106/637] PC speaker: use evdev instead of KIOCSOUND still unsupported --- src/engine/platform/pcspkr.cpp | 40 ++++++++++++++++++++++++++++------ src/engine/platform/pcspkr.h | 4 +++- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/src/engine/platform/pcspkr.cpp b/src/engine/platform/pcspkr.cpp index eb201c1ec..54048f90e 100644 --- a/src/engine/platform/pcspkr.cpp +++ b/src/engine/platform/pcspkr.cpp @@ -19,10 +19,13 @@ #include "pcspkr.h" #include "../engine.h" +#include #include +#include #ifdef __linux__ #include +#include #include #include #endif @@ -112,14 +115,29 @@ void DivPlatformPCSpeaker::acquire_piezo(short* bufL, short* bufR, size_t start, } } -void DivPlatformPCSpeaker::acquire_real(short* bufL, short* bufR, size_t start, size_t len) { +void DivPlatformPCSpeaker::beepFreq(int freq) { #ifdef __linux__ + static struct input_event ie; + if (beepFD>=0) { + gettimeofday(&ie.time,NULL); + ie.type=EV_SND; + ie.code=SND_TONE; + ie.value=freq; + if (write(beepFD,&ie,sizeof(struct input_event))<0) { + perror("error while writing frequency!"); + } else { + //printf("writing freq: %d\n",freq); + } + } +#endif +} + +void DivPlatformPCSpeaker::acquire_real(short* bufL, short* bufR, size_t start, size_t len) { if (lastOn!=on || lastFreq!=freq) { lastOn=on; lastFreq=freq; - ioctl(STDOUT_FILENO,KIOCSOUND,on?freq:0); + beepFreq((on && !isMuted[0])?freq:0); } -#endif for (size_t i=start; i=0) close(beepFD); +#endif } DivPlatformPCSpeaker::~DivPlatformPCSpeaker() { diff --git a/src/engine/platform/pcspkr.h b/src/engine/platform/pcspkr.h index 6197098a4..82e0f461d 100644 --- a/src/engine/platform/pcspkr.h +++ b/src/engine/platform/pcspkr.h @@ -53,7 +53,7 @@ class DivPlatformPCSpeaker: public DivDispatch { Channel chan[1]; bool isMuted[1]; bool on, flip, lastOn; - int pos, speakerType; + int pos, speakerType, beepFD; float low, band; float low2, high2, band2; float low3, band3; @@ -62,6 +62,8 @@ class DivPlatformPCSpeaker: public DivDispatch { friend void putDispatchChan(void*,int,int); + void beepFreq(int freq); + void acquire_unfilt(short* bufL, short* bufR, size_t start, size_t len); void acquire_cone(short* bufL, short* bufR, size_t start, size_t len); void acquire_piezo(short* bufL, short* bufR, size_t start, size_t len); From e0eb0ad3f4e8e210f7e447f180374ad969558d82 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 6 Mar 2022 04:52:03 -0500 Subject: [PATCH 107/637] tilde you need to brush up on your header knowledg e. --- src/engine/platform/pcspkr.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine/platform/pcspkr.cpp b/src/engine/platform/pcspkr.cpp index 54048f90e..5f57f31ae 100644 --- a/src/engine/platform/pcspkr.cpp +++ b/src/engine/platform/pcspkr.cpp @@ -19,14 +19,14 @@ #include "pcspkr.h" #include "../engine.h" -#include #include -#include #ifdef __linux__ #include +#include #include #include +#include #include #endif From ab55a3f079dafa5b696fe7ca92d5c0b08affa066 Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Sat, 5 Mar 2022 16:02:01 +0700 Subject: [PATCH 108/637] Turn second chip checks into variables in vgmOps --- src/engine/vgmOps.cpp | 146 ++++++++++++++++++++++-------------------- 1 file changed, 75 insertions(+), 71 deletions(-) diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index 97f427372..bef7c5359 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -25,119 +25,123 @@ constexpr int MASTER_CLOCK_PREC=(sizeof(void*)==8)?8:0; void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write, int streamOff, double* loopTimer, double* loopFreq, int* loopSample, bool isSecond) { + unsigned char baseAddr1=isSecond?0xa0:0x50; + unsigned char baseAddr2=isSecond?0x80:0; + unsigned short baseAddr2S=isSecond?0x8000:0; + unsigned char smsAddr=isSecond?0x30:0x50; if (write.addr==0xffffffff) { // Furnace fake reset switch (sys) { case DIV_SYSTEM_YM2612: case DIV_SYSTEM_YM2612_EXT: for (int i=0; i<3; i++) { // set SL and RR to highest - w->writeC(isSecond?0xa2:0x52); + w->writeC(2|baseAddr1); w->writeC(0x80+i); w->writeC(0xff); - w->writeC(isSecond?0xa2:0x52); + w->writeC(2|baseAddr1); w->writeC(0x84+i); w->writeC(0xff); - w->writeC(isSecond?0xa2:0x52); + w->writeC(2|baseAddr1); w->writeC(0x88+i); w->writeC(0xff); - w->writeC(isSecond?0xa2:0x52); + w->writeC(2|baseAddr1); w->writeC(0x8c+i); w->writeC(0xff); - w->writeC(isSecond?0xa3:0x53); + w->writeC(3|baseAddr1); w->writeC(0x80+i); w->writeC(0xff); - w->writeC(isSecond?0xa3:0x53); + w->writeC(3|baseAddr1); w->writeC(0x84+i); w->writeC(0xff); - w->writeC(isSecond?0xa3:0x53); + w->writeC(3|baseAddr1); w->writeC(0x88+i); w->writeC(0xff); - w->writeC(isSecond?0xa3:0x53); + w->writeC(3|baseAddr1); w->writeC(0x8c+i); w->writeC(0xff); } for (int i=0; i<3; i++) { // note off - w->writeC(isSecond?0xa2:0x52); + w->writeC(2|baseAddr1); w->writeC(0x28); w->writeC(i); - w->writeC(isSecond?0xa2:0x52); + w->writeC(2|baseAddr1); w->writeC(0x28); w->writeC(4+i); } - w->writeC(isSecond?0xa2:0x52); // disable DAC + w->writeC(2|baseAddr1); // disable DAC w->writeC(0x2b); w->writeC(0); break; case DIV_SYSTEM_SMS: for (int i=0; i<4; i++) { - w->writeC(isSecond?0x30:0x50); + w->writeC(smsAddr); w->writeC(0x90|(i<<5)|15); } break; case DIV_SYSTEM_GB: // square 1 w->writeC(0xb3); - w->writeC(isSecond?0x82:2); + w->writeC(2|baseAddr2); w->writeC(0); w->writeC(0xb3); - w->writeC(isSecond?0x84:4); + w->writeC(4|baseAddr2); w->writeC(0x80); // square 2 w->writeC(0xb3); - w->writeC(isSecond?0x87:7); + w->writeC(7|baseAddr2); w->writeC(0); w->writeC(0xb3); - w->writeC(isSecond?0x89:9); + w->writeC(9|baseAddr2); w->writeC(0x80); // wave w->writeC(0xb3); - w->writeC(isSecond?0x8c:0x0c); + w->writeC(0x0c|baseAddr2); w->writeC(0); w->writeC(0xb3); - w->writeC(isSecond?0x8e:0x0e); + w->writeC(0x0e|baseAddr2); w->writeC(0x80); // noise w->writeC(0xb3); - w->writeC(isSecond?0x91:0x11); + w->writeC(0x11|baseAddr2); w->writeC(0); w->writeC(0xb3); - w->writeC(isSecond?0x93:0x13); + w->writeC(0x13|baseAddr2); w->writeC(0x80); break; case DIV_SYSTEM_PCE: for (int i=0; i<6; i++) { w->writeC(0xb9); - w->writeC(isSecond?0x80:0); + w->writeC(0|baseAddr2); w->writeC(i); w->writeC(0xb9); - w->writeC(isSecond?0x84:4); + w->writeC(4|baseAddr2); w->writeC(0); } break; case DIV_SYSTEM_NES: w->writeC(0xb4); - w->writeC(isSecond?0x95:0x15); + w->writeC(0x15|baseAddr2); w->writeC(0); break; case DIV_SYSTEM_YM2151: for (int i=0; i<8; i++) { - w->writeC(isSecond?0xa4:0x54); + w->writeC(4|baseAddr1); w->writeC(0xe0+i); w->writeC(0xff); - w->writeC(isSecond?0xa4:0x54); + w->writeC(4|baseAddr1); w->writeC(0xe8+i); w->writeC(0xff); - w->writeC(isSecond?0xa4:0x54); + w->writeC(4|baseAddr1); w->writeC(0xf0+i); w->writeC(0xff); - w->writeC(isSecond?0xa4:0x54); + w->writeC(4|baseAddr1); w->writeC(0xf8+i); w->writeC(0xff); - w->writeC(isSecond?0xa4:0x54); + w->writeC(4|baseAddr1); w->writeC(0x08); w->writeC(i); } @@ -146,7 +150,7 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write case DIV_SYSTEM_SEGAPCM_COMPAT: for (int i=0; i<16; i++) { w->writeC(0xc0); - w->writeS((isSecond?0x8086:0x86)+(i<<3)); + w->writeS((0x86|baseAddr2S)+(i<<3)); w->writeC(3); } break; @@ -157,60 +161,60 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write case DIV_SYSTEM_YM2610_FULL_EXT: case DIV_SYSTEM_YM2610B_EXT: for (int i=0; i<2; i++) { // set SL and RR to highest - w->writeC(isSecond?0xa8:0x58); + w->writeC(8|baseAddr1); w->writeC(0x81+i); w->writeC(0xff); - w->writeC(isSecond?0xa8:0x58); + w->writeC(8|baseAddr1); w->writeC(0x85+i); w->writeC(0xff); - w->writeC(isSecond?0xa8:0x58); + w->writeC(8|baseAddr1); w->writeC(0x89+i); w->writeC(0xff); - w->writeC(isSecond?0xa8:0x58); + w->writeC(8|baseAddr1); w->writeC(0x8d+i); w->writeC(0xff); - w->writeC(isSecond?0xa9:0x59); + w->writeC(9|baseAddr1); w->writeC(0x81+i); w->writeC(0xff); - w->writeC(isSecond?0xa9:0x59); + w->writeC(9|baseAddr1); w->writeC(0x85+i); w->writeC(0xff); - w->writeC(isSecond?0xa9:0x59); + w->writeC(9|baseAddr1); w->writeC(0x89+i); w->writeC(0xff); - w->writeC(isSecond?0xa9:0x59); + w->writeC(9|baseAddr1); w->writeC(0x8d+i); w->writeC(0xff); } for (int i=0; i<2; i++) { // note off - w->writeC(isSecond?0xa8:0x58); + w->writeC(8|baseAddr1); w->writeC(0x28); w->writeC(1+i); - w->writeC(isSecond?0xa8:0x58); + w->writeC(8|baseAddr1); w->writeC(0x28); w->writeC(5+i); } // reset AY - w->writeC(isSecond?0xa8:0x58); + w->writeC(8|baseAddr1); w->writeC(7); w->writeC(0x3f); - w->writeC(isSecond?0xa8:0x58); + w->writeC(8|baseAddr1); w->writeC(8); w->writeC(0); - w->writeC(isSecond?0xa8:0x58); + w->writeC(8|baseAddr1); w->writeC(9); w->writeC(0); - w->writeC(isSecond?0xa8:0x58); + w->writeC(8|baseAddr1); w->writeC(10); w->writeC(0); // reset sample - w->writeC(isSecond?0xa9:0x59); + w->writeC(9|baseAddr1); w->writeC(0); w->writeC(0xbf); break; @@ -218,56 +222,56 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write case DIV_SYSTEM_OPLL_DRUMS: case DIV_SYSTEM_VRC7: for (int i=0; i<9; i++) { - w->writeC(isSecond?0xa1:0x51); + w->writeC(1|baseAddr1); w->writeC(0x20+i); w->writeC(0); - w->writeC(isSecond?0xa1:0x51); + w->writeC(1|baseAddr1); w->writeC(0x30+i); w->writeC(0); - w->writeC(isSecond?0xa1:0x51); + w->writeC(1|baseAddr1); w->writeC(0x10+i); w->writeC(0); } break; case DIV_SYSTEM_AY8910: w->writeC(0xa0); - w->writeC(isSecond?0x87:7); + w->writeC(7|baseAddr2); w->writeC(0x3f); w->writeC(0xa0); - w->writeC(isSecond?0x88:8); + w->writeC(8|baseAddr2); w->writeC(0); w->writeC(0xa0); - w->writeC(isSecond?0x89:9); + w->writeC(9|baseAddr2); w->writeC(0); w->writeC(0xa0); - w->writeC(isSecond?0x8a:10); + w->writeC(10|baseAddr2); w->writeC(0); break; case DIV_SYSTEM_AY8930: w->writeC(0xa0); - w->writeC(isSecond?0x8d:0x0d); + w->writeC(0x0d|baseAddr2); w->writeC(0); w->writeC(0xa0); - w->writeC(isSecond?0x8d:0x0d); + w->writeC(0x0d|baseAddr2); w->writeC(0xa0); break; case DIV_SYSTEM_SAA1099: w->writeC(0xbd); - w->writeC(isSecond?0x9c:0x1c); + w->writeC(0x1c|baseAddr2); w->writeC(0x02); w->writeC(0xbd); - w->writeC(isSecond?0x94:0x14); + w->writeC(0x14|baseAddr2); w->writeC(0); w->writeC(0xbd); - w->writeC(isSecond?0x95:0x15); + w->writeC(0x15|baseAddr2); w->writeC(0); for (int i=0; i<6; i++) { w->writeC(0xbd); - w->writeC((isSecond?0x80:0)+i); + w->writeC((0|baseAddr2)+i); w->writeC(0); } break; @@ -346,49 +350,49 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write case DIV_SYSTEM_YM2612_EXT: switch (write.addr>>8) { case 0: // port 0 - w->writeC(isSecond?0xa2:0x52); + w->writeC(2|baseAddr1); w->writeC(write.addr&0xff); w->writeC(write.val); break; case 1: // port 1 - w->writeC(isSecond?0xa3:0x53); + w->writeC(3|baseAddr1); w->writeC(write.addr&0xff); w->writeC(write.val); break; case 2: // PSG - w->writeC(isSecond?0x30:0x50); + w->writeC(smsAddr); w->writeC(write.val); break; } break; case DIV_SYSTEM_SMS: - w->writeC(isSecond?0x30:0x50); + w->writeC(smsAddr); w->writeC(write.val); break; case DIV_SYSTEM_GB: w->writeC(0xb3); - w->writeC((isSecond?0x80:0)|((write.addr-16)&0xff)); + w->writeC(baseAddr2|((write.addr-16)&0xff)); w->writeC(write.val); break; case DIV_SYSTEM_PCE: w->writeC(0xb9); - w->writeC((isSecond?0x80:0)|(write.addr&0xff)); + w->writeC(baseAddr2|(write.addr&0xff)); w->writeC(write.val); break; case DIV_SYSTEM_NES: w->writeC(0xb4); - w->writeC((isSecond?0x80:0)|(write.addr&0xff)); + w->writeC(baseAddr2|(write.addr&0xff)); w->writeC(write.val); break; case DIV_SYSTEM_YM2151: - w->writeC(isSecond?0xa4:0x54); + w->writeC(4|baseAddr1); w->writeC(write.addr&0xff); w->writeC(write.val); break; case DIV_SYSTEM_SEGAPCM: case DIV_SYSTEM_SEGAPCM_COMPAT: w->writeC(0xc0); - w->writeS((isSecond?0x8000:0)|(write.addr&0xffff)); + w->writeS(baseAddr2S|(write.addr&0xffff)); w->writeC(write.val); break; case DIV_SYSTEM_YM2610: @@ -399,12 +403,12 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write case DIV_SYSTEM_YM2610B_EXT: switch (write.addr>>8) { case 0: // port 0 - w->writeC(isSecond?0xa8:0x58); + w->writeC(8|baseAddr1); w->writeC(write.addr&0xff); w->writeC(write.val); break; case 1: // port 1 - w->writeC(isSecond?0xa9:0x59); + w->writeC(9|baseAddr1); w->writeC(write.addr&0xff); w->writeC(write.val); break; @@ -413,19 +417,19 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write case DIV_SYSTEM_OPLL: case DIV_SYSTEM_OPLL_DRUMS: case DIV_SYSTEM_VRC7: - w->writeC(isSecond?0xa1:0x51); + w->writeC(1|baseAddr1); w->writeC(write.addr&0xff); w->writeC(write.val); break; case DIV_SYSTEM_AY8910: case DIV_SYSTEM_AY8930: w->writeC(0xa0); - w->writeC((isSecond?0x80:0)|(write.addr&0xff)); + w->writeC(baseAddr2|(write.addr&0xff)); w->writeC(write.val); break; case DIV_SYSTEM_SAA1099: w->writeC(0xbd); - w->writeC((isSecond?0x80:0)|(write.addr&0xff)); + w->writeC(baseAddr2|(write.addr&0xff)); w->writeC(write.val); break; case DIV_SYSTEM_LYNX: From df7ac3e0732644aabec12bc80ffff03014383543 Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Sun, 6 Mar 2022 23:13:47 +0700 Subject: [PATCH 109/637] Add WonderSwan support --- CMakeLists.txt | 5 +- README.md | 1 + papers/doc/4-instrument/README.md | 3 +- papers/doc/4-instrument/wonderswan.md | 8 + papers/doc/5-wave/README.md | 4 +- papers/doc/7-systems/README.md | 1 + papers/doc/7-systems/wonderswan.md | 20 + src/engine/dispatch.h | 3 + src/engine/dispatchContainer.cpp | 4 + src/engine/platform/sound/ws.cpp | 412 ++++++++++++++++++++ src/engine/platform/sound/ws.h | 82 ++++ src/engine/platform/ws.cpp | 522 ++++++++++++++++++++++++++ src/engine/platform/ws.h | 95 +++++ src/engine/playback.cpp | 21 ++ src/engine/sysDef.cpp | 1 + src/engine/vgmOps.cpp | 45 +++ src/gui/gui.cpp | 2 + src/gui/insEdit.cpp | 8 +- 18 files changed, 1231 insertions(+), 6 deletions(-) create mode 100644 papers/doc/4-instrument/wonderswan.md create mode 100644 papers/doc/7-systems/wonderswan.md create mode 100644 src/engine/platform/sound/ws.cpp create mode 100644 src/engine/platform/sound/ws.h create mode 100644 src/engine/platform/ws.cpp create mode 100644 src/engine/platform/ws.h diff --git a/CMakeLists.txt b/CMakeLists.txt index caf08457f..f31213030 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -265,6 +265,8 @@ src/engine/platform/sound/lynx/Mikey.cpp src/engine/platform/sound/qsound.c +src/engine/platform/sound/ws.cpp + src/engine/platform/ym2610Interface.cpp src/engine/blip_buf.c @@ -306,8 +308,9 @@ src/engine/platform/amiga.cpp src/engine/platform/pcspkr.cpp src/engine/platform/segapcm.cpp src/engine/platform/qsound.cpp -src/engine/platform/dummy.cpp src/engine/platform/lynx.cpp +src/engine/platform/ws.cpp +src/engine/platform/dummy.cpp ) if (WIN32) diff --git a/README.md b/README.md index e1d97726e..540052fd3 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ this is a work-in-progress chiptune tracker compatible with DefleMask modules (. - Philips SAA1099 - Amiga - TIA (Atari 2600/7800) + - WonderSwan - multiple sound chips in a single song! - clean-room design (guesswork and ABX tests only, no decompilation involved) - bug/quirk implementation for increased playback accuracy diff --git a/papers/doc/4-instrument/README.md b/papers/doc/4-instrument/README.md index b9d4b8f40..0b9ec0f4c 100644 --- a/papers/doc/4-instrument/README.md +++ b/papers/doc/4-instrument/README.md @@ -10,12 +10,13 @@ double-click to open the instrument editor. every instrument can be renamed and have its type changed. -depending on the instrument type, there are currently 10 different types of an instrument editor: +depending on the instrument type, there are currently 12 different types of an instrument editor: - [FM synthesis](fm.md) - for use with YM2612, YM2151 and FM block portion of YM2610. - [Standard](standard.md) - for use with NES and Sega Master System's PSG sound source and its derivatives. - [Game Boy](game-boy.md) - for use with Game Boy APU. - [PC Engine/TurboGrafx-16](pce.md) - for use with PC Engine's wavetable synthesizer. +- [WonderSwan](wonderswan.md) - for use with WonderSwan's wavetable synthesizer. - [AY8930](8930.md) - for use with Microchip AY8930 E-PSG sound source. - [Commodore 64](c64.md) - for use with Commodore 64 SID. - [SAA1099](saa.md) - for use with Philips SAA1099 PSG sound source. diff --git a/papers/doc/4-instrument/wonderswan.md b/papers/doc/4-instrument/wonderswan.md new file mode 100644 index 000000000..6ad0e8c97 --- /dev/null +++ b/papers/doc/4-instrument/wonderswan.md @@ -0,0 +1,8 @@ +# WonderSwan instrument editor + +WS instrument editor consists of only four macros, similar to PCE but with different volume and noise range: + +- [Volume] - volume sequence +- [Arpeggio] - pitch sequencr +- [Noise] - noise LFSR tap sequence +- [Waveform] - spicifies wavetables sequence diff --git a/papers/doc/5-wave/README.md b/papers/doc/5-wave/README.md index 91fe92656..005739257 100644 --- a/papers/doc/5-wave/README.md +++ b/papers/doc/5-wave/README.md @@ -1,5 +1,5 @@ # wavetable editor -Wavetable synthizers, in context of Furnace, are sound sources that operate on extremely short n-bit PCM streams. By extremely short, no more than 256 bytes. This amount of space is nowhere near enough to store an actual sampled sound, it allows certain amount of freedom to define a waveform shape. As of Furnace 0.5.5, wavetable editor affects PC Engine and channel 3 of Game Boy. +Wavetable synthizers, in context of Furnace, are sound sources that operate on extremely short n-bit PCM streams. By extremely short, no more than 256 bytes. This amount of space is nowhere near enough to store an actual sampled sound, it allows certain amount of freedom to define a waveform shape. As of Furnace 0.5.8, wavetable editor affects PC Engine, WonderSwan and channel 3 of Game Boy. -Furnace's wavetable editor is rather simple, you can draw the waveform using mouse or by pasting an MML bit stream in the input field. Maximum wave width (length) is 256 bytes, and maximum wave height (depth) is 256. NOTE: both Game Boy and PCE can handle max 32 byte waveforms as of now, width 16-level height for GB and 32-level height for PCE. If larger wave will be defined for these two systems, it will be squashed to fit in the constrains of the system. +Furnace's wavetable editor is rather simple, you can draw the waveform using mouse or by pasting an MML bit stream in the input field. Maximum wave width (length) is 256 bytes, and maximum wave height (depth) is 256. NOTE: Game Boy, PCE and WonderSwan can handle max 32 byte waveforms as of now, with 16-level height for GB and WS, and 32-level height for PCE. If larger wave will be defined for these two systems, it will be squashed to fit within the constraints of the system. diff --git a/papers/doc/7-systems/README.md b/papers/doc/7-systems/README.md index 78aeb8cea..a42e0d061 100644 --- a/papers/doc/7-systems/README.md +++ b/papers/doc/7-systems/README.md @@ -18,5 +18,6 @@ this is a list of systems that Furnace supports, including each system's effects - [Atari 2600](tia.md) - [Philips SAA1099](saa1099.md) - [Microchip AY8930](ay8930.md) +- [WonderSwan](wonderswan.md) Furnace also reads .dmf files with the [Yamaha YMU759](ymu759.md) system, but does not emulate the chip at all. diff --git a/papers/doc/7-systems/wonderswan.md b/papers/doc/7-systems/wonderswan.md new file mode 100644 index 000000000..c5c0bafdd --- /dev/null +++ b/papers/doc/7-systems/wonderswan.md @@ -0,0 +1,20 @@ +# WonderSwan + +A handheld console released only in Japan by Bandai. Designed by the same +people behind Game Boy and Virtual Boy, it has lots of similar elements from +those two systems in the sound department. + +It has 4 wavetable channels, one channel could play PCM, the other has hardware +sweep and the other could play noise. + +# effects + +- `10xx`: change wave. +- `11xx`: setup noise mode (channel 4 only). + - 0: disable. + - 1-8: enable and set tap preset. +- `12xx`: setup sweep period (channel 3 only). + - 0: disable. + - 1-32: enable and set period. +- `13xx`: setup sweep amount (channel 3 only). +- `17xx`: toggle PCM mode (channel 2 only). diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index 300b0033b..98a2843ea 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -103,6 +103,9 @@ enum DivDispatchCmds { DIV_CMD_QSOUND_ECHO_DELAY, DIV_CMD_QSOUND_ECHO_LEVEL, + DIV_CMD_WS_SWEEP_TIME, + DIV_CMD_WS_SWEEP_AMOUNT, + DIV_ALWAYS_SET_VOLUME, DIV_CMD_MAX diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 52cc7177e..0bc6a2729 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -41,6 +41,7 @@ #include "platform/pcspkr.h" #include "platform/segapcm.h" #include "platform/qsound.h" +#include "platform/ws.h" #include "platform/dummy.h" #include "platform/lynx.h" #include "../ta-log.h" @@ -234,6 +235,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_SEGAPCM_COMPAT: dispatch=new DivPlatformSegaPCM; break; + case DIV_SYSTEM_SWAN: + dispatch=new DivPlatformWS; + break; default: logW("this system is not supported yet! using dummy platform.\n"); dispatch=new DivPlatformDummy; diff --git a/src/engine/platform/sound/ws.cpp b/src/engine/platform/sound/ws.cpp new file mode 100644 index 000000000..e02d63ec6 --- /dev/null +++ b/src/engine/platform/sound/ws.cpp @@ -0,0 +1,412 @@ +/******************************************************************************/ +/* Mednafen - Multi-system Emulator */ +/******************************************************************************/ +/* sound.cpp - WonderSwan Sound Emulation +** Copyright (C) 2007-2017 Mednafen Team +** Copyright (C) 2016 Alex 'trap15' Marshall - http://daifukkat.su/ +** +** 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 "ws.h" +#include + +#define MK_SAMPLE_CACHE \ + { \ + int sample; \ + sample = (((wsRAM[(/*(SampleRAMPos << 6) + */(sample_pos[ch] >> 1) + (ch << 4)) ] >> ((sample_pos[ch] & 1) ? 4 : 0)) & 0x0F)); \ + sample_cache[ch][0] = sample * ((volume[ch] >> 4) & 0x0F); \ + sample_cache[ch][1] = sample * ((volume[ch] >> 0) & 0x0F); \ + } + +#define MK_SAMPLE_CACHE_NOISE \ + { \ + int sample; \ + sample = ((nreg & 1) ? 0xF : 0x0); \ + sample_cache[ch][0] = sample * ((volume[ch] >> 4) & 0x0F); \ + sample_cache[ch][1] = sample * ((volume[ch] >> 0) & 0x0F); \ + } + +#define MK_SAMPLE_CACHE_VOICE \ + { \ + int sample, half; \ + sample = volume[ch]; \ + half = sample >> 1; \ + sample_cache[ch][0] = (voice_volume & 4) ? sample : (voice_volume & 8) ? half : 0; \ + sample_cache[ch][1] = (voice_volume & 1) ? sample : (voice_volume & 2) ? half : 0; \ + } + + +#define SYNCSAMPLE(wt) /* \ + { \ + int32_t left = sample_cache[ch][0], right = sample_cache[ch][1]; \ + WaveSynth.offset_inline(wt, left - last_val[ch][0], sbuf[0]); \ + WaveSynth.offset_inline(wt, right - last_val[ch][1], sbuf[1]); \ + last_val[ch][0] = left; \ + last_val[ch][1] = right; \ + } */ + +#define SYNCSAMPLE_NOISE(wt) SYNCSAMPLE(wt) + +void WSwan::SoundUpdate(uint32_t v30mz_timestamp) +{ + int32_t run_time; + + //printf("%d\n", v30mz_timestamp); + //printf("%02x %02x\n", control, noise_control); + run_time = v30mz_timestamp - last_ts; + + for(int y = 0; y < 2; y++) + sbuf[y] = 0; + + for(unsigned int ch = 0; ch < 4; ch++) + { + // Channel is disabled? + if(!(control & (1 << ch))) + continue; + + if(ch == 1 && (control & 0x20)) // Direct D/A mode? + { + MK_SAMPLE_CACHE_VOICE; + SYNCSAMPLE(v30mz_timestamp); + } + else if(ch == 2 && (control & 0x40) && sweep_value) // Sweep + { + uint32_t tmp_pt = 2048 - period[ch]; + uint32_t meow_timestamp = v30mz_timestamp - run_time; + uint32_t tmp_run_time = run_time; + + while(tmp_run_time) + { + int32_t sub_run_time = tmp_run_time; + + if(sub_run_time > sweep_8192_divider) + sub_run_time = sweep_8192_divider; + + sweep_8192_divider -= sub_run_time; + if(sweep_8192_divider <= 0) + { + sweep_8192_divider += 8192; + sweep_counter--; + if(sweep_counter <= 0) + { + sweep_counter = sweep_step + 1; + period[ch] = (period[ch] + (int8_t)sweep_value) & 0x7FF; + } + } + + meow_timestamp += sub_run_time; + if(tmp_pt > 4) + { + period_counter[ch] -= sub_run_time; + while(period_counter[ch] <= 0) + { + sample_pos[ch] = (sample_pos[ch] + 1) & 0x1F; + + MK_SAMPLE_CACHE; + SYNCSAMPLE(meow_timestamp + period_counter[ch]); + period_counter[ch] += tmp_pt; + } + } + tmp_run_time -= sub_run_time; + } + } + else if(ch == 3 && (control & 0x80) && (noise_control & 0x10)) // Noise + { + uint32_t tmp_pt = 2048 - period[ch]; + + period_counter[ch] -= run_time; + while(period_counter[ch] <= 0) + { + static const uint8_t stab[8] = { 14, 10, 13, 4, 8, 6, 9, 11 }; + + nreg = ((nreg << 1) | ((1 ^ (nreg >> 7) ^ (nreg >> stab[noise_control & 0x7])) & 1)) & 0x7FFF; + + if(control & 0x80) + { + MK_SAMPLE_CACHE_NOISE; + SYNCSAMPLE_NOISE(v30mz_timestamp + period_counter[ch]); + } + else if(tmp_pt > 4) + { + sample_pos[ch] = (sample_pos[ch] + 1) & 0x1F; + MK_SAMPLE_CACHE; + SYNCSAMPLE(v30mz_timestamp + period_counter[ch]); + } + period_counter[ch] += tmp_pt; + } + } + else + { + uint32_t tmp_pt = 2048 - period[ch]; + + if(tmp_pt > 4) + { + period_counter[ch] -= run_time; + while(period_counter[ch] <= 0) + { + sample_pos[ch] = (sample_pos[ch] + 1) & 0x1F; + + MK_SAMPLE_CACHE; + SYNCSAMPLE(v30mz_timestamp + period_counter[ch]); // - period_counter[ch]); + period_counter[ch] += tmp_pt; + } + } + } + sbuf[0] += sample_cache[ch][0]; + sbuf[1] += sample_cache[ch][1]; + } + + if(HVoiceCtrl & 0x80) + { + int16_t sample = (uint8_t)HyperVoice; + + switch(HVoiceCtrl & 0xC) + { + case 0x0: sample = (uint16_t)sample << (8 - (HVoiceCtrl & 3)); break; + case 0x4: sample = (uint16_t)(sample | -0x100) << (8 - (HVoiceCtrl & 3)); break; + case 0x8: sample = (uint16_t)((int8_t)sample) << (8 - (HVoiceCtrl & 3)); break; + case 0xC: sample = (uint16_t)sample << 8; break; + } + // bring back to 11bit, keeping signedness + sample >>= 5; + + int32_t left, right; + left = (HVoiceChanCtrl & 0x40) ? sample : 0; + right = (HVoiceChanCtrl & 0x20) ? sample : 0; + + // WaveSynth.offset_inline(v30mz_timestamp, left - last_hv_val[0], sbuf[0]); + // WaveSynth.offset_inline(v30mz_timestamp, right - last_hv_val[1], sbuf[1]); + // last_hv_val[0] = left; + // last_hv_val[1] = right; + sbuf[0] += left; + sbuf[1] += right; + } + last_ts = v30mz_timestamp; +} + +void WSwan::SoundWrite(uint32_t A, uint8_t V) +{ + if(A >= 0x80 && A <= 0x87) + { + int ch = (A - 0x80) >> 1; + + if(A & 1) + period[ch] = (period[ch] & 0x00FF) | ((V & 0x07) << 8); + else + period[ch] = (period[ch] & 0x0700) | ((V & 0xFF) << 0); + + //printf("Period %d: 0x%04x --- %f\n", ch, period[ch], 3072000.0 / (2048 - period[ch])); + } + else if(A >= 0x88 && A <= 0x8B) + { + volume[A - 0x88] = V; + } + else if(A == 0x8C) + sweep_value = V; + else if(A == 0x8D) + { + sweep_step = V; + sweep_counter = sweep_step + 1; + sweep_8192_divider = 8192; + } + else if(A == 0x8E) + { + //printf("NOISECONTROL: %02x\n", V); + if(V & 0x8) + nreg = 0; + + noise_control = V & 0x17; + } + else if(A == 0x90) + { + for(int n = 0; n < 4; n++) + { + if(!(control & (1 << n)) && (V & (1 << n))) + { + period_counter[n] = 1; + sample_pos[n] = 0x1F; + } + } + control = V; + //printf("Sound Control: %02x\n", V); + } + else if(A == 0x91) + { + output_control = V & 0xF; + //printf("%02x, %02x\n", V, (V >> 1) & 3); + } + else if(A == 0x92) + nreg = (nreg & 0xFF00) | (V << 0); + else if(A == 0x93) + nreg = (nreg & 0x00FF) | ((V & 0x7F) << 8); + else if(A == 0x94) + { + voice_volume = V & 0xF; + //printf("%02x\n", V); + } + else switch(A) + { + case 0x6A: HVoiceCtrl = V; break; + case 0x6B: HVoiceChanCtrl = V & 0x6F; break; + case 0x8F: SampleRAMPos = V; break; + case 0x95: HyperVoice = V; break; // Pick a port, any port?! + //default: printf("%04x:%02x\n", A, V); break; + } +} + +uint8_t WSwan::SoundRead(uint32_t A) +{ + if(A >= 0x80 && A <= 0x87) + { + int ch = (A - 0x80) >> 1; + + if(A & 1) + return(period[ch] >> 8); + else + return(period[ch]); + } + else if(A >= 0x88 && A <= 0x8B) + return(volume[A - 0x88]); + else switch(A) + { + default: /*printf("SoundRead: %04x\n", A);*/ return(0); + case 0x6A: return(HVoiceCtrl); + case 0x6B: return(HVoiceChanCtrl); + case 0x8C: return(sweep_value); + case 0x8D: return(sweep_step); + case 0x8E: return(noise_control); + case 0x8F: return(SampleRAMPos); + case 0x90: return(control); + case 0x91: return(output_control | 0x80); + case 0x92: return((nreg >> 0) & 0xFF); + case 0x93: return((nreg >> 8) & 0xFF); + case 0x94: return(voice_volume); + } +} + +void WSwan::RAMWrite(uint32_t A, uint8_t V) +{ + wsRAM[A & 0x3F] = V; +} + +int32_t WSwan::SoundFlush(int16_t *SoundBuf, const int32_t MaxSoundFrames) +{ + int32_t FrameCount = 0; + + if(SoundBuf) + { + for(int y = 0; y < 2; y++) + { + // sbuf[y]->end_frame(v30mz_timestamp); + // FrameCount = sbuf[y]->read_samples(SoundBuf + y, MaxSoundFrames, true); + int32_t left = sbuf[0]; + int32_t right = sbuf[1]; + if (left >= 0x400) left = 0x3FF; + else if (left < -0x400) left = -0x400; + if (right >= 0x400) left = 0x3FF; + else if (right < -0x400) left = -0x400; + SoundBuf[0] = (int16_t)left << 5; + SoundBuf[1] = (int16_t)right << 5; + } + } + + last_ts = 0; + + return(FrameCount); +} + +// Call before wsRAM is updated +// void WSwan::SoundCheckRAMWrite(uint32_t A) +// { +// if((A >> 6) == SampleRAMPos) +// SoundUpdate(); +// } + +// static void RedoVolume(void) +// { +// WaveSynth.volume(2.5); +// } + +// void WSwan::SoundInit(void) +// { +// for(int i = 0; i < 2; i++) +// { +// sbuf[i] = new Blip_Buffer(); + +// sbuf[i]->set_sample_rate(0 ? 0 : 44100, 60); +// sbuf[i]->clock_rate((long)(3072000)); +// sbuf[i]->bass_freq(20); +// } + +// RedoVolume(); +// } + +// void WSwan::SoundKill(void) +// { +// for(int i = 0; i < 2; i++) +// { +// if(sbuf[i]) +// { +// delete sbuf[i]; +// sbuf[i] = NULL; +// } +// } + +// } + +// bool WSwan::SetSoundRate(uint32_t rate) +// { +// for(int i = 0; i < 2; i++) +// sbuf[i]->set_sample_rate(rate?rate:44100, 60); + +// return(true); +// } + +void WSwan::SoundReset(void) +{ + memset(period, 0, sizeof(period)); + memset(volume, 0, sizeof(volume)); + voice_volume = 0; + sweep_step = 0; + sweep_value = 0; + noise_control = 0; + control = 0; + output_control = 0; + + sweep_8192_divider = 8192; + sweep_counter = 1; + SampleRAMPos = 0; + + for(unsigned ch = 0; ch < 4; ch++) + period_counter[ch] = 1; + + memset(sample_pos, 0, sizeof(sample_pos)); + nreg = 0; + + memset(sample_cache, 0, sizeof(sample_cache)); + // memset(last_val, 0, sizeof(last_val)); + last_v_val = 0; + + HyperVoice = 0; + last_hv_val[0] = last_hv_val[1] = 0; + HVoiceCtrl = 0; + HVoiceChanCtrl = 0; + + for(int y = 0; y < 2; y++) + // sbuf[y]->clear(); + sbuf[y] = 0; + last_ts = 0; +} diff --git a/src/engine/platform/sound/ws.h b/src/engine/platform/sound/ws.h new file mode 100644 index 000000000..b1b0af740 --- /dev/null +++ b/src/engine/platform/sound/ws.h @@ -0,0 +1,82 @@ +/******************************************************************************/ +/* Mednafen - Multi-system Emulator */ +/******************************************************************************/ +/* sound.h - WonderSwan Sound Emulation +** Copyright (C) 2007-2016 Mednafen Team +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the GNU General Public License +** as published by the Free Software Foundation; either version 2 +** of the License, or (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software Foundation, Inc., +** 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef __WSWAN_SOUND_H +#define __WSWAN_SOUND_H + +#include + +class WSwan +{ +public: + int32_t SoundFlush(int16_t *SoundBuf, const int32_t MaxSoundFrames); + + // void SoundInit(void); + // void SoundKill(void); + // void SetSoundMultiplier(double multiplier); + // bool SetSoundRate(uint32_t rate); + + void SoundWrite(uint32_t, uint8_t); + uint8_t SoundRead(uint32_t); + void SoundReset(void); + // void SoundCheckRAMWrite(uint32_t A); + + void SoundUpdate(uint32_t); + void RAMWrite(uint32_t, uint8_t); + +private: + // Blip_Synth WaveSynth; + + // Blip_Buffer *sbuf[2] = { NULL }; + int32_t sbuf[2]; + + uint16_t period[4]; + uint8_t volume[4]; // left volume in upper 4 bits, right in lower 4 bits + uint8_t voice_volume; + + uint8_t sweep_step, sweep_value; + uint8_t noise_control; + uint8_t control; + uint8_t output_control; + + int32_t sweep_8192_divider; + uint8_t sweep_counter; + uint8_t SampleRAMPos; + + int32_t sample_cache[4][2]; + + int32_t last_v_val; + + uint8_t HyperVoice; + int32_t last_hv_val[2]; + uint8_t HVoiceCtrl, HVoiceChanCtrl; + + int32_t period_counter[4]; + // int32_t last_val[4][2]; // Last outputted value, l&r + uint8_t sample_pos[4]; + uint16_t nreg; + uint32_t last_ts; + + uint8_t wsRAM[64]; + int16_t sBuf[2]; +}; + +#endif diff --git a/src/engine/platform/ws.cpp b/src/engine/platform/ws.cpp new file mode 100644 index 000000000..df82376a4 --- /dev/null +++ b/src/engine/platform/ws.cpp @@ -0,0 +1,522 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "ws.h" +#include "../engine.h" +#include + +#define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);}} + +#define CHIP_DIVIDER 32 + +const char* regCheatSheetWS[]={ + "CH1_Pitch", "00", + "CH2_Pitch", "02", + "CH3_Pitch", "04", + "CH4_Pitch", "06", + "CH1_Vol", "08", + "CH2_Vol", "09", + "CH3_Vol", "0A", + "CH4_Vol", "0B", + "Sweep_Value", "0C", + "Sweep_Time", "0D", + "Noise", "0E", + "Wave_Base", "0F", + "Ctrl", "10", + "Output", "11", + "Random", "12", + "Voice_Ctrl", "14", + "Wave_Mem", "40", + NULL +}; + +const char** DivPlatformWS::getRegisterSheet() { + return regCheatSheetWS; +} + +const char* DivPlatformWS::getEffectName(unsigned char effect) { + switch (effect) { + case 0x10: + return "10xx: Change waveform"; + break; + case 0x11: + return "11xx: Setup noise mode (0: disabled; 1-8: enabled/tap)"; + break; + case 0x12: + return "12xx: Setup sweep period (0: disabled; 1-20: enabled/period)"; + break; + case 0x13: + return "13xx: Set sweep amount"; + break; + case 0x17: + return "17xx: Toggle PCM mode"; + break; + } + return NULL; +} + +void DivPlatformWS::acquire(short* bufL, short* bufR, size_t start, size_t len) { + for (size_t h=start; hrate) { + DivSample* s=parent->getSample(dacSample); + if (s->samples<=0) { + dacSample=-1; + continue; + } + rWrite(0x09,(unsigned char)s->data8[dacPos++]+0x80); + if (dacPos>=s->samples) { + if (s->loopStart>=0 && s->loopStart<=(int)s->samples) { + dacPos=s->loopStart; + } else { + dacSample=-1; + } + } + dacPeriod-=rate; + } + } + + // the rest + while (!writes.empty()) { + QueuedWrite w=writes.front(); + if (regPool[w.addr]!=w.val) { + if (w.addr<0x40) ws->SoundWrite(w.addr|0x80,w.val); + else ws->RAMWrite(w.addr&0x3f,w.val); + regPool[w.addr]=w.val; + } + writes.pop(); + } + int16_t samp[2]{0, 0}; + ws->SoundUpdate(16); + ws->SoundFlush(samp, 1); + bufL[h]=samp[0]; + bufR[h]=samp[1]; + } +} + +void DivPlatformWS::updateWave(int ch) { + DivWavetable* wt=parent->getWave(chan[ch].wave); + unsigned char addr=0x40+ch*16; + if (wt->max<1 || wt->len<1) { + for (int i=0; i<16; i++) { + rWrite(addr+i,0); + } + } else { + for (int i=0; i<16; i++) { + unsigned char nibble1=(wt->data[(i*2)*wt->len/32]*15)/wt->max; + unsigned char nibble2=(wt->data[(1+i*2)*wt->len/32]*15)/wt->max; + rWrite(addr+i,nibble1|(nibble2<<4)); + } + } +} + +void DivPlatformWS::calcAndWriteOutVol(int ch, int env) { + int vl=chan[ch].vol*((chan[ch].pan>>4)&0x0f)*env/225; + int vr=chan[ch].vol*(chan[ch].pan&0x0f)*env/225; + if (ch==1&&pcm) { + vl=(vl>0)?((vl>7)?3:2):0; + vr=(vr>0)?((vr>7)?3:2):0; + chan[1].outVol=vr|(vl<<2); + } else { + chan[ch].outVol=vr|(vl<<4); + } + writeOutVol(ch); +} + +void DivPlatformWS::writeOutVol(int ch) { + unsigned char val=isMuted[ch]?0:chan[ch].outVol; + if (ch==1&&pcm) { + rWrite(0x14,val) + } else { + rWrite(0x08+ch,val); + } +} + +void DivPlatformWS::tick() { + unsigned char sndCtrl=(pcm?0x20:0)|(sweep?0x40:0)|((noise>0)?0x80:0); + for (int i=0; i<4; i++) { + chan[i].std.next(); + if (chan[i].std.hadVol) { + int env=chan[i].std.vol; + if(parent->getIns(chan[i].ins)->type==DIV_INS_AMIGA) { + env=MIN(env/4,15); + } + calcAndWriteOutVol(i,env); + } + if (chan[i].std.hadArp) { + if (!chan[i].inPorta) { + if (chan[i].std.arpMode) { + chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp); + } else { + chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp); + } + } + chan[i].freqChanged=true; + } else { + if (chan[i].std.arpMode && chan[i].std.finishedArp) { + chan[i].baseFreq=NOTE_PERIODIC(chan[i].note); + chan[i].freqChanged=true; + } + } + if (chan[i].std.hadWave && !(i==1 && pcm)) { + if (chan[i].wave!=chan[i].std.wave) { + chan[i].wave=chan[i].std.wave; + updateWave(i); + } + } + if (chan[i].active) { + sndCtrl|=(1<calcFreq(chan[i].baseFreq,chan[i].pitch,true); + if (i==1 && furnaceDac) { + double off=1.0; + if (dacSample>=0 && dacSamplesong.sampleLen) { + DivSample* s=parent->getSample(dacSample); + if (s->centerRate<1) { + off=1.0; + } else { + off=8363.0/(double)s->centerRate; + } + } + dacRate=((double)chipClock/2)/MAX(1,off*chan[i].freq); + if (dumpWrites) addWrite(0xffff0001,dacRate); + } + if (chan[i].freq>2048) chan[i].freq=2048; + if (chan[i].freq<1) chan[i].freq=1; + int rVal=2048-chan[i].freq; + rWrite(i*2,rVal&0xff); + rWrite(i*2+1,rVal>>8); + if (chan[i].keyOn) { + if (!chan[i].std.hasVol) { + calcAndWriteOutVol(i,15); + } + if (chan[i].wave<0) { + chan[i].wave=0; + updateWave(i); + } + chan[i].keyOn=false; + } + if (chan[i].keyOff) { + chan[i].keyOff=false; + } + chan[i].freqChanged=false; + } + } + if (chan[3].std.hadDuty) { + noise=chan[3].std.duty; + if (noise>0) { + rWrite(0x0e,(noise-1)&0x07|0x18); + sndCtrl|=0x80; + } else { + sndCtrl&=~0x80; + } + } + rWrite(0x10,sndCtrl); +} + +int DivPlatformWS::dispatch(DivCommand c) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: { + DivInstrument* ins=parent->getIns(chan[c.chan].ins); + if (c.chan==1 && ins->type==DIV_INS_AMIGA) { + pcm=true; + } else if (furnaceDac) { + pcm=false; + } + if (c.chan==1 && pcm) { + if (skipRegisterWrites) break; + dacPos=0; + dacPeriod=0; + if (ins->type==DIV_INS_AMIGA) { + dacSample=ins->amiga.initSample; + if (dacSample<0 || dacSample>=parent->song.sampleLen) { + dacSample=-1; + if (dumpWrites) addWrite(0xffff0002,0); + break; + } else { + if (dumpWrites) { + addWrite(0xffff0000,dacSample); + } + } + if (c.value!=DIV_NOTE_NULL) { + chan[1].baseFreq=NOTE_PERIODIC(c.value); + chan[1].freqChanged=true; + chan[1].note=c.value; + } + chan[1].active=true; + chan[1].keyOn=true; + chan[1].std.init(ins); + furnaceDac=true; + } else { + if (c.value!=DIV_NOTE_NULL) { + chan[1].note=c.value; + } + dacSample=12*sampleBank+chan[1].note%12; + if (dacSample>=parent->song.sampleLen) { + dacSample=-1; + if (dumpWrites) addWrite(0xffff0002,0); + break; + } else { + if (dumpWrites) addWrite(0xffff0000,dacSample); + } + dacRate=parent->getSample(dacSample)->rate; + if (dumpWrites) { + addWrite(0xffff0001,dacRate); + } + chan[1].active=true; + chan[1].keyOn=true; + furnaceDac=false; + } + break; + } + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + } + chan[c.chan].active=true; + chan[c.chan].keyOn=true; + chan[c.chan].std.init(ins); + break; + } + case DIV_CMD_NOTE_OFF: + if (c.chan==1&&pcm) { + dacSample=-1; + if (dumpWrites) addWrite(0xffff0002,0); + pcm=false; + } + chan[c.chan].active=false; + chan[c.chan].keyOff=true; + chan[c.chan].std.init(NULL); + break; + case DIV_CMD_NOTE_OFF_ENV: + case DIV_CMD_ENV_RELEASE: + chan[c.chan].std.release(); + break; + case DIV_CMD_INSTRUMENT: + if (chan[c.chan].ins!=c.value || c.value2==1) { + chan[c.chan].ins=c.value; + } + break; + case DIV_CMD_VOLUME: + if (chan[c.chan].vol!=c.value) { + chan[c.chan].vol=c.value; + if (!chan[c.chan].std.hasVol) { + calcAndWriteOutVol(c.chan,15); + } + } + break; + case DIV_CMD_GET_VOLUME: + return chan[c.chan].vol; + break; + case DIV_CMD_PITCH: + chan[c.chan].pitch=c.value; + chan[c.chan].freqChanged=true; + break; + case DIV_CMD_WAVE: + chan[c.chan].wave=c.value; + updateWave(c.chan); + chan[c.chan].keyOn=true; + break; + case DIV_CMD_WS_SWEEP_TIME: + if (c.chan==2) { + if (c.value==0) { + sweep=false; + } else { + sweep=true; + rWrite(0x0d,(c.value-1)&0xff); + } + } + break; + case DIV_CMD_WS_SWEEP_AMOUNT: + if (c.chan==2) { + rWrite(0x0c,c.value&0xff); + } + break; + case DIV_CMD_NOTE_PORTA: { + int destFreq=NOTE_PERIODIC(c.value2); + bool return2=false; + if (destFreq>chan[c.chan].baseFreq) { + chan[c.chan].baseFreq+=c.value; + if (chan[c.chan].baseFreq>=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } else { + chan[c.chan].baseFreq-=c.value; + if (chan[c.chan].baseFreq<=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } + chan[c.chan].freqChanged=true; + if (return2) { + chan[c.chan].inPorta=false; + return 2; + } + break; + } + case DIV_CMD_STD_NOISE_MODE: + if (c.chan==3) { + noise=c.value&0xff; + if (noise>0) rWrite(0x0e,(noise-1)&0x07|0x18); + } + break; + case DIV_CMD_SAMPLE_MODE: + if (c.chan==1) pcm=c.value; + break; + case DIV_CMD_SAMPLE_BANK: + sampleBank=c.value; + if (sampleBank>(parent->song.sample.size()/12)) { + sampleBank=parent->song.sample.size()/12; + } + break; + case DIV_CMD_PANNING: { + chan[c.chan].pan=c.value; + if (!chan[c.chan].std.hasVol) { + calcAndWriteOutVol(c.chan,15); + } + break; + } + case DIV_CMD_LEGATO: + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.willArp && !chan[c.chan].std.arpMode)?(chan[c.chan].std.arp):(0))); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + break; + case DIV_CMD_PRE_PORTA: + if (chan[c.chan].active && c.value2) { + if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + } + chan[c.chan].inPorta=c.value; + break; + case DIV_CMD_GET_VOLMAX: + return 15; + break; + case DIV_ALWAYS_SET_VOLUME: + return 0; + break; + default: + break; + } + return 1; +} + +void DivPlatformWS::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; + writeOutVol(ch); +} + +void DivPlatformWS::forceIns() { + for (int i=0; i<4; i++) { + chan[i].insChanged=true; + chan[i].freqChanged=true; + updateWave(i); + writeOutVol(i); + } +} + +void* DivPlatformWS::getChanState(int ch) { + return &chan[ch]; +} + +unsigned char* DivPlatformWS::getRegisterPool() { + // get Random from emulator + regPool[0x12]=ws->SoundRead(0x92); + regPool[0x13]=ws->SoundRead(0x93); + return regPool; +} + +int DivPlatformWS::getRegisterPoolSize() { + return 128; +} + +void DivPlatformWS::reset() { + while (!writes.empty()) writes.pop(); + memset(regPool,0,128); + for (int i=0; i<4; i++) { + chan[i]=Channel(); + chan[i].vol=15; + chan[i].pan=0xff; + } + if (dumpWrites) { + addWrite(0xffffffff,0); + } + ws->SoundReset(); + pcm=false; + sweep=false; + furnaceDac=false; + noise=0; + dacPeriod=0; + dacRate=0; + dacPos=0; + dacSample=-1; + sampleBank=0; + rWrite(0x0f,0x00); // wave table at 0x0000 + rWrite(0x11,0x09); // enable speakers +} + +bool DivPlatformWS::isStereo() { + return true; +} + +void DivPlatformWS::notifyWaveChange(int wave) { + for (int i=0; i<4; i++) { + if (chan[i].wave==wave) { + updateWave(i); + } + } +} + +void DivPlatformWS::notifyInsDeletion(void* ins) { + for (int i=0; i<4; i++) { + chan[i].std.notifyInsDeletion((DivInstrument*)ins); + } +} + +void DivPlatformWS::poke(unsigned int addr, unsigned short val) { + rWrite(addr,val); +} + +void DivPlatformWS::poke(std::vector& wlist) { + for (DivRegWrite& i: wlist) rWrite(i.addr,i.val); +} + +int DivPlatformWS::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { + parent=p; + dumpWrites=false; + skipRegisterWrites=false; + chipClock=3072000; + rate=chipClock/16; // = 192000kHz, should be enough + for (int i=0; i<4; i++) { + isMuted[i]=false; + } + ws=new WSwan(); + reset(); + return 4; +} + +void DivPlatformWS::quit() { + delete ws; +} + +DivPlatformWS::~DivPlatformWS() { +} diff --git a/src/engine/platform/ws.h b/src/engine/platform/ws.h new file mode 100644 index 000000000..ea6466fbb --- /dev/null +++ b/src/engine/platform/ws.h @@ -0,0 +1,95 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _WS_H +#define _WS_H + +#include "../dispatch.h" +#include "../macroInt.h" +#include "sound/ws.h" +#include + +class DivPlatformWS: public DivDispatch { + struct Channel { + int freq, baseFreq, pitch, note; + unsigned char ins, pan; + bool active, insChanged, freqChanged, keyOn, keyOff, inPorta; + int vol, outVol, wave; + DivMacroInt std; + Channel(): + freq(0), + baseFreq(0), + pitch(0), + note(0), + ins(-1), + pan(255), + active(false), + insChanged(true), + freqChanged(false), + keyOn(false), + keyOff(false), + inPorta(false), + vol(15), + outVol(15), + wave(-1) {} + }; + Channel chan[4]; + bool isMuted[4]; + bool pcm, sweep, furnaceDac; + unsigned char sampleBank, noise; + int dacPeriod, dacRate; + unsigned int dacPos; + int dacSample; + + unsigned char regPool[0x80]; + struct QueuedWrite { + unsigned char addr; + unsigned char val; + QueuedWrite(unsigned char a, unsigned char v): addr(a), val(v) {} + }; + std::queue writes; + WSwan* ws; + void updateWave(int ch); + friend void putDispatchChan(void*,int,int); + public: + void acquire(short* bufL, short* bufR, size_t start, size_t len); + int dispatch(DivCommand c); + void* getChanState(int chan); + unsigned char* getRegisterPool(); + int getRegisterPoolSize(); + void reset(); + void forceIns(); + void tick(); + void muteChannel(int ch, bool mute); + void notifyWaveChange(int wave); + void notifyInsDeletion(void* ins); + bool isStereo(); + void poke(unsigned int addr, unsigned short val); + void poke(std::vector& wlist); + const char** getRegisterSheet(); + const char* getEffectName(unsigned char effect); + int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); + void quit(); + ~DivPlatformWS(); + private: + void calcAndWriteOutVol(int ch, int env); + void writeOutVol(int ch); +}; + +#endif diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index f0f43dc1a..fb3067dfe 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -250,6 +250,27 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe break; } break; + case DIV_SYSTEM_SWAN: + switch (effect) { + case 0x10: // select waveform + dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal)); + break; + case 0x11: // noise mode + dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal)); + break; + case 0x12: // sweep period + dispatchCmd(DivCommand(DIV_CMD_WS_SWEEP_TIME,ch,effectVal)); + break; + case 0x13: // sweep amount + dispatchCmd(DivCommand(DIV_CMD_WS_SWEEP_AMOUNT,ch,effectVal)); + break; + case 0x17: // PCM enable + dispatchCmd(DivCommand(DIV_CMD_SAMPLE_MODE,ch,(effectVal>0))); + break; + default: + return false; + } + break; default: return false; } diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 3c988ceb8..fe45a4e78 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -1616,6 +1616,7 @@ bool DivEngine::isVGMExportable(DivSystem which) { case DIV_SYSTEM_OPLL: case DIV_SYSTEM_OPLL_DRUMS: case DIV_SYSTEM_VRC7: + case DIV_SYSTEM_SWAN: return true; default: return false; diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index bef7c5359..caa811481 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -443,6 +443,18 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write w->writeC(write.val&0xff); w->writeC(write.addr&0xff); break; + case DIV_SYSTEM_SWAN: + if ((write.addr&0x7f)<0x40) { + w->writeC(0xbc); + w->writeC(baseAddr2|(write.addr&0x3f)); + w->writeC(write.val&0xff); + } else { + // (Wave) RAM write + w->writeC(0xc6); + w->writeS(baseAddr2S|(write.addr&0x3f)); + w->writeC(write.val&0xff); + } + break; default: logW("write not handled!\n"); break; @@ -746,6 +758,21 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) { addWarning("dual QSound is not supported by the VGM format"); } break; + case DIV_SYSTEM_SWAN: + if (!hasSwan) { + hasSwan=disCont[i].dispatch->chipClock; + willExport[i]=true; + // funny enough, VGM doesn't have support for WSC's sound DMA by design + // so DAC stream it goes + // since WS has the same PCM format as YM2612 DAC, I can just reuse this flag + writeDACSamples=true; + } else if (!(hasSwan&0x40000000)) { + isSecond[i]=true; + willExport[i]=true; + hasSwan|=0x40000000; + howManyChips++; + } + break; default: break; } @@ -1031,6 +1058,24 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) { streamID++; } break; + case DIV_SYSTEM_SWAN: + w->writeC(0x90); + w->writeC(streamID); + w->writeC(33); + w->writeC(0); // port + w->writeC(isSecond[i]?0x89:0x09); // DAC + + w->writeC(0x91); + w->writeC(streamID); + w->writeC(0); + w->writeC(1); + w->writeC(0); + + w->writeC(0x92); + w->writeC(streamID); + w->writeI(24000); // default + streamID++; + break; default: break; } diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index fa8900380..4dd7b05ef 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4634,6 +4634,7 @@ bool FurnaceGUI::loop() { sysAddOption(DIV_SYSTEM_AY8930); sysAddOption(DIV_SYSTEM_LYNX); sysAddOption(DIV_SYSTEM_QSOUND); + sysAddOption(DIV_SYSTEM_SWAN); ImGui::EndMenu(); } if (ImGui::BeginMenu("configure system...")) { @@ -4971,6 +4972,7 @@ bool FurnaceGUI::loop() { sysChangeOption(i,DIV_SYSTEM_AY8930); sysChangeOption(i,DIV_SYSTEM_LYNX); sysChangeOption(i,DIV_SYSTEM_QSOUND); + sysChangeOption(i,DIV_SYSTEM_SWAN); ImGui::EndMenu(); } } diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 6ff30896f..6c7f06cbe 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1358,6 +1358,10 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_PCE) { dutyMax=1; } + if (ins->type==DIV_INS_SWAN) { + dutyLabel="Noise"; + dutyMax=8; + } if (ins->type==DIV_INS_OPLL || ins->type==DIV_INS_OPL) { dutyMax=0; } @@ -1777,7 +1781,7 @@ void FurnaceGUI::drawWaveEdit() { DivWavetable* wave=e->song.wave[curWave]; ImGui::Text("Width"); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("use a width of 32 on Game Boy and PC Engine.\nany other widths will be scaled during playback."); + ImGui::SetTooltip("use a width of 32 on Game Boy, PC Engine and WonderSwan.\nany other widths will be scaled during playback."); } ImGui::SameLine(); ImGui::SetNextItemWidth(128.0f*dpiScale); @@ -1791,7 +1795,7 @@ void FurnaceGUI::drawWaveEdit() { ImGui::SameLine(); ImGui::Text("Height"); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("use a height of:\n- 15 for Game Boy\n- 31 for PC Engine\nany other heights will be scaled during playback."); + ImGui::SetTooltip("use a height of:\n- 15 for Game Boy and WonderSwan\n- 31 for PC Engine\nany other heights will be scaled during playback."); } ImGui::SameLine(); ImGui::SetNextItemWidth(128.0f*dpiScale); From 4a83c7c5a71f125b9c5cd5d450e5a2a822351f22 Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 7 Mar 2022 02:31:03 +0900 Subject: [PATCH 110/637] Add Seta/Allumer X1-010 Support its 16 channel wavetable/PCM chip, with (optional) stereo support. Its also has envelope, this feature has similar as AY PSG's one but its shape is also stored at RAM, and each nibble in envelope data is for each output: so i decided to added some feature for more stereo-ish envelope. Split: Envelope shape will be splitted to Left and Right half for each output. HInv, Vinv: Envelope shape will be Horizontally/Vertically mirrored the left one. Max sample length is sample bank size of Seta 2 arcade hardware (currently not emulated yet, nor it doesn't support on VGM). Chip id is temporary, it can be changed with to suggestions. --- .gitmodules | 3 + CMakeLists.txt | 6 +- extern/cam900_vgsound_emu | 1 + papers/doc/4-instrument/README.md | 3 +- papers/doc/4-instrument/x1_010.md | 11 + papers/doc/6-sample/README.md | 3 +- papers/doc/7-systems/README.md | 1 + papers/doc/7-systems/x1_010.md | 34 ++ papers/format.md | 1 + src/engine/dispatch.h | 7 + src/engine/dispatchContainer.cpp | 6 +- src/engine/engine.cpp | 34 +- src/engine/engine.h | 2 + src/engine/instrument.h | 1 + src/engine/platform/x1_010.cpp | 862 ++++++++++++++++++++++++++++++ src/engine/platform/x1_010.h | 138 +++++ src/engine/playback.cpp | 36 ++ src/engine/sample.cpp | 5 +- src/engine/sample.h | 2 +- src/engine/song.h | 10 +- src/engine/sysDef.cpp | 38 +- src/engine/vgmOps.cpp | 36 ++ src/gui/debug.cpp | 40 ++ src/gui/gui.cpp | 56 +- src/gui/gui.h | 1 + src/gui/insEdit.cpp | 26 +- src/gui/settings.cpp | 2 + 27 files changed, 1340 insertions(+), 25 deletions(-) create mode 160000 extern/cam900_vgsound_emu create mode 100644 papers/doc/4-instrument/x1_010.md create mode 100644 papers/doc/7-systems/x1_010.md create mode 100644 src/engine/platform/x1_010.cpp create mode 100644 src/engine/platform/x1_010.h diff --git a/.gitmodules b/.gitmodules index 435f43c75..56f11c097 100644 --- a/.gitmodules +++ b/.gitmodules @@ -19,3 +19,6 @@ [submodule "extern/adpcm"] path = extern/adpcm url = https://github.com/superctr/adpcm +[submodule "extern/cam900_vgsound_emu"] + path = extern/cam900_vgsound_emu + url = https://github.com/cam900/vgsound_emu diff --git a/CMakeLists.txt b/CMakeLists.txt index 8bcb8ce98..53aebac00 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -226,6 +226,9 @@ extern/adpcm/ymz_codec.c extern/Nuked-OPN2/ym3438.c extern/opm/opm.c extern/Nuked-OPLL/opll.c + +extern/cam900_vgsound_emu/x1_010/x1_010.cpp + src/engine/platform/sound/sn76496.cpp src/engine/platform/sound/ay8910.cpp src/engine/platform/sound/saa1099.cpp @@ -304,8 +307,9 @@ src/engine/platform/amiga.cpp src/engine/platform/pcspkr.cpp src/engine/platform/segapcm.cpp src/engine/platform/qsound.cpp -src/engine/platform/dummy.cpp +src/engine/platform/x1_010.cpp src/engine/platform/lynx.cpp +src/engine/platform/dummy.cpp ) if (WIN32) diff --git a/extern/cam900_vgsound_emu b/extern/cam900_vgsound_emu new file mode 160000 index 000000000..cf8816851 --- /dev/null +++ b/extern/cam900_vgsound_emu @@ -0,0 +1 @@ +Subproject commit cf88168512c1b32bbea7b7dcc1411c4da429a723 diff --git a/papers/doc/4-instrument/README.md b/papers/doc/4-instrument/README.md index b9d4b8f40..f6da9d26c 100644 --- a/papers/doc/4-instrument/README.md +++ b/papers/doc/4-instrument/README.md @@ -21,8 +21,9 @@ depending on the instrument type, there are currently 10 different types of an i - [SAA1099](saa.md) - for use with Philips SAA1099 PSG sound source. - [TIA](tia.md) - for use with Atari 2600 system. - [AY-3-8910](ay8910.md) - for use with AY-3-8910 PSG sound source and SSG portion in YM2610. -- [Amiga/sample](amiga.md) for controlling Amiga and other sample based synthsizers like YM2612's Channel 6 PCM mode, NES channel 5, Sega PCM and PC Engine's sample playback mode. +- [Amiga/sample](amiga.md) for controlling Amiga and other sample based synthsizers like YM2612's Channel 6 PCM mode, NES channel 5, Sega PCM, X1-010 and PC Engine's sample playback mode. - [Atari Lynx](lynx.md) - for use with Atari Lynx handheld console. +- [Seta/Allumer X1-010](x1_010.md) - for use with Wavetable portion in Seta/Allumer X1-010. # macros diff --git a/papers/doc/4-instrument/x1_010.md b/papers/doc/4-instrument/x1_010.md new file mode 100644 index 000000000..8f3691ee6 --- /dev/null +++ b/papers/doc/4-instrument/x1_010.md @@ -0,0 +1,11 @@ +# X1-010 instrument editor + +X1-010 instrument editor consists of 7 macros. + +- [Volume] - volume levels sequence +- [Arpeggio]- pitch sequence +- [Waveform] - spicifies wavetables sequence +- [Envelope Mode] - allows shaping an envelope +- [Envelope] - spicifies envelope shape sequence, it's also wavetable. +- [Auto envelope numerator] - sets the envelope to the channel's frequency multiplied by numerator +- [Auto envelope denominator] - ets the envelope to the channel's frequency multiplied by denominator diff --git a/papers/doc/6-sample/README.md b/papers/doc/6-sample/README.md index 78eb322c3..87ddf0f8f 100644 --- a/papers/doc/6-sample/README.md +++ b/papers/doc/6-sample/README.md @@ -12,7 +12,8 @@ As of Furnace 0.5.5, the following sound chips have sample support: - PC Engine/TurboGrafx 16/Huc6280 (same conditions as above) - Amiga/Paula (on all channels AND resamplable, but you need to make an instrument with the Amiga format and tie it to a sample first) - Arcade/SEGA PCM (same as above but you don't need to make an instrument for it and you have to use the `20xx` effect command to resample your samples) - - Neo Geo/Neo Geo EXT-Ch2 (on the last 5 channels only and can be resampled the same way as above) + - Neo Geo/Neo Geo EXT-Ch2 (on the last 7 channels only and can be resampled the same way as above) + - Seta/Allumer X1-010 (same as above, and both `1701` and `20xx` effect commands are affected on all 16 channels) Furnace also has a feature where you can make an Amiga formarted instrument on the YM2612 and Huc6280 to resample a sample you have in the module. diff --git a/papers/doc/7-systems/README.md b/papers/doc/7-systems/README.md index 78aeb8cea..1a0d28096 100644 --- a/papers/doc/7-systems/README.md +++ b/papers/doc/7-systems/README.md @@ -18,5 +18,6 @@ this is a list of systems that Furnace supports, including each system's effects - [Atari 2600](tia.md) - [Philips SAA1099](saa1099.md) - [Microchip AY8930](ay8930.md) +- [Seta/Allumer X1-010](x1_010.md) Furnace also reads .dmf files with the [Yamaha YMU759](ymu759.md) system, but does not emulate the chip at all. diff --git a/papers/doc/7-systems/x1_010.md b/papers/doc/7-systems/x1_010.md new file mode 100644 index 000000000..952e42316 --- /dev/null +++ b/papers/doc/7-systems/x1_010.md @@ -0,0 +1,34 @@ +# Seta/Allumer X1-010 + +One of sound chip originally designed by Seta, mainly used at their arcade hardwares at late-88 to early-2000s. +it has 2 output channel, but no known hardware using this feature for stereo sound. +later hardware paired this with external bankswitching logic, but its logic is not emulated now in current furnace revision. +Allumer one is just rebadged Seta's thing for use in their arcade hardwares. + +It has 16 channels, and all channels can be switchable to PCM sample or wavetable playback mode. +Wavetable needs to paired with envelope, this feature is similar as AY PSG but it's shape are stored at RAM: it means it is user-definable. + +In furnace, this chip is can be configurable for original arcade mono output or stereo output - its simulates early 'incorrect' emulation on some mono hardware but it is also based on the assumption that each channel is connected to each output. + +# effects + +- `10xx`: change wave. +- `11xx`: change envelope shape. (also wavetable) +- `17xx`: toggle PCM mode. +- `20xx`: set PCM frequency.* +- `22xx`: set envelope mode. + - bit 0 sets whether envelope will affect this channel. + - bit 1 sets whether envelope one-shot mode. when it sets, channel is halted after envelope cycle is ended. + - bit 2 sets whether envelope shape split mode. when it sets, envelope shape will splitted to left half and right half. + - bit 3 sets whether the right shape will mirror the left one. + - bit 4 sets whether the right output will mirror the left one. +- `23xx`: set envelope period. +- `25xx`: slide envelope period up. +- `26xx`: slide envelope period down. +- `29xy`: enable auto-envelope mode. + - in this mode the envelope period is set to the channel's notes, multiplied by a fraction. + - `x` is the numerator. + - `y` is the denominator. + - if `x` or `y` are 0 this will disable auto-envelope mode. + +* PCM frequency: 255 step, fomula: `step * (Chip clock / 8192)`; 1.95KHz to 498KHz if Chip clock is 16MHz. diff --git a/papers/format.md b/papers/format.md index 4df49ed02..ff068d830 100644 --- a/papers/format.md +++ b/papers/format.md @@ -174,6 +174,7 @@ size | description | - 0xaa: MSM6295 - 4 channels | - 0xab: MSM6258 - 1 channel | - 0xac: Commander X16 (VERA) - 17 channels + | - 0xb0: Seta/Allumer X1-010 - 16 channels | - 0xde: YM2610B extended - 19 channels | - 0xe0: QSound - 19 channels | - (compound!) means that the system is composed of two or more chips, diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index 300b0033b..7e1a56081 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -103,6 +103,13 @@ enum DivDispatchCmds { DIV_CMD_QSOUND_ECHO_DELAY, DIV_CMD_QSOUND_ECHO_LEVEL, + DIV_CMD_X1_010_ENVELOPE_SHAPE, + DIV_CMD_X1_010_ENVELOPE_ENABLE, + DIV_CMD_X1_010_ENVELOPE_MODE, + DIV_CMD_X1_010_ENVELOPE_PERIOD, + DIV_CMD_X1_010_ENVELOPE_SLIDE, + DIV_CMD_X1_010_AUTO_ENVELOPE, + DIV_ALWAYS_SET_VOLUME, DIV_CMD_MAX diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 0682bbd71..84b8315f3 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -40,8 +40,9 @@ #include "platform/pcspkr.h" #include "platform/segapcm.h" #include "platform/qsound.h" -#include "platform/dummy.h" +#include "platform/x1_010.h" #include "platform/lynx.h" +#include "platform/dummy.h" #include "../ta-log.h" #include "song.h" @@ -230,6 +231,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_SEGAPCM_COMPAT: dispatch=new DivPlatformSegaPCM; break; + case DIV_SYSTEM_X1_010: + dispatch=new DivPlatformX1_010; + break; default: logW("this system is not supported yet! using dummy platform.\n"); dispatch=new DivPlatformDummy; diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index a8b023d7d..e484baf31 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -532,6 +532,36 @@ void DivEngine::renderSamples() { memPos+=length+16; } qsoundMemLen=memPos+256; + + // step 4: allocate x1-010 pcm samples + if (x1_010Mem==NULL) x1_010Mem=new unsigned char[1048576]; + memset(x1_010Mem,0,1048576); + + memPos=0; + for (int i=0; ilength8+4095)&(~0xfff); + // fit sample bank size to 128KB for Seta 2 external bankswitching logic (not emulated yet!) + if (paddedLen>131072) { + paddedLen=131072; + } + if ((memPos&0xfe0000)!=((memPos+paddedLen)&0xfe0000)) { + memPos=(memPos+0x1ffff)&0xfe0000; + } + if (memPos>=1048576) { + logW("out of X1-010 memory for sample %d!\n",i); + break; + } + if (memPos+paddedLen>=1048576) { + memcpy(x1_010Mem+memPos,s->data8,1048576-memPos); + logW("out of X1-010 memory for sample %d!\n",i); + } else { + memcpy(x1_010Mem+memPos,s->data8,paddedLen); + } + s->offX1_010=memPos; + memPos+=paddedLen; + } + x1_010MemLen=memPos+256; } void DivEngine::createNew(const int* description) { @@ -950,7 +980,9 @@ int DivEngine::getEffectiveSampleRate(int rate) { case DIV_SYSTEM_QSOUND: return (24038*MIN(65535,(rate*4096/24038)))/4096; case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_EXT: case DIV_SYSTEM_YM2610_FULL: case DIV_SYSTEM_YM2610_FULL_EXT: case DIV_SYSTEM_YM2610B: case DIV_SYSTEM_YM2610B_EXT: - return 18518; + return 18518; // TODO: support ADPCM-B case + case DIV_SYSTEM_X1_010: + return (31250*MIN(255,(rate*16/31250)))/16; // TODO: support variable clock case default: break; } diff --git a/src/engine/engine.h b/src/engine/engine.h index 19d589dba..d16a987cb 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -632,6 +632,8 @@ class DivEngine { size_t qsoundAMemLen; unsigned char* dpcmMem; size_t dpcmMemLen; + unsigned char* x1_010Mem; + size_t x1_010MemLen; DivEngine(): output(NULL), diff --git a/src/engine/instrument.h b/src/engine/instrument.h index 27ce2a4ce..da7356f1e 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -48,6 +48,7 @@ enum DivInstrumentType { DIV_INS_BEEPER=21, DIV_INS_SWAN=22, DIV_INS_MIKEY=23, + DIV_INS_X1_010=24, }; // FM operator structure: diff --git a/src/engine/platform/x1_010.cpp b/src/engine/platform/x1_010.cpp new file mode 100644 index 000000000..6d072d730 --- /dev/null +++ b/src/engine/platform/x1_010.cpp @@ -0,0 +1,862 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "x1_010.h" +#include "../engine.h" +#include + +//#define rWrite(a,v) pendingWrites[a]=v; +#define rWrite(a,v) if (!skipRegisterWrites) { x1_010->ram_w(a,v); if (dumpWrites) { addWrite(a,v); } } + +#define chRead(c,a) x1_010->ram_r((c<<3)|(a&7)) +#define chWrite(c,a,v) rWrite((c<<3)|(a&7),v) +#define waveWrite(c,a,v) rWrite(0x1000|(chan[c].waveBank<<11)|(c<<7)|(a&0x7f),(v-128)&0xff) +#define envFill(c,a) rWrite(0x800|(c<<7)|(a&0x7f),(chan[c].lvol<<4)|chan[c].rvol) +#define envWrite(c,a,l,r) rWrite(0x800|(c<<7)|(a&0x7f),(((chan[c].lvol*(l))/15)<<4)|((chan[c].rvol*(r))/15)) + +#define refreshControl(c) chWrite(c,0,chan[c].active?(chan[c].pcm?1:((chan[c].env.flag.envEnable&&chan[c].env.flag.envOneshot)?7:3)):0); + +#define CHIP_FREQBASE 4194304 + +const char* regCheatSheetX1_010[]={ + // Channel registers + "Ch00_Control", "0000", + "Ch00_PCMVol_WavSel", "0001", + "Ch00_FreqL", "0002", + "Ch00_FreqH", "0003", + "Ch00_Start_EnvFrq", "0004", + "Ch00_End_EnvSel", "0005", + "Ch01_Control", "0008", + "Ch01_PCMVol_WavSel", "0009", + "Ch01_FreqL", "000A", + "Ch01_FreqH", "000B", + "Ch01_Start_EnvFrq", "000C", + "Ch01_End_EnvSel", "000D", + "Ch02_Control", "0010", + "Ch02_PCMVol_WavSel", "0011", + "Ch02_FreqL", "0012", + "Ch02_FreqH", "0013", + "Ch02_Start_EnvFrq", "0014", + "Ch02_End_EnvSel", "0015", + "Ch03_Control", "0018", + "Ch03_PCMVol_WavSel", "0019", + "Ch03_FreqL", "001A", + "Ch03_FreqH", "001B", + "Ch03_Start_EnvFrq", "001C", + "Ch03_End_EnvSel", "001D", + "Ch04_Control", "0020", + "Ch04_PCMVol_WavSel", "0021", + "Ch04_FreqL", "0022", + "Ch04_FreqH", "0023", + "Ch04_Start_EnvFrq", "0024", + "Ch04_End_EnvSel", "0025", + "Ch05_Control", "0028", + "Ch05_PCMVol_WavSel", "0029", + "Ch05_FreqL", "002A", + "Ch05_FreqH", "002B", + "Ch05_Start_EnvFrq", "002C", + "Ch05_End_EnvSel", "002D", + "Ch06_Control", "0030", + "Ch06_PCMVol_WavSel", "0031", + "Ch06_FreqL", "0032", + "Ch06_FreqH", "0033", + "Ch06_Start_EnvFrq", "0034", + "Ch06_End_EnvSel", "0035", + "Ch07_Control", "0038", + "Ch07_PCMVol_WavSel", "0039", + "Ch07_FreqL", "003A", + "Ch07_FreqH", "003B", + "Ch07_Start_EnvFrq", "003C", + "Ch07_End_EnvSel", "003D", + "Ch08_Control", "0040", + "Ch08_PCMVol_WavSel", "0041", + "Ch08_FreqL", "0042", + "Ch08_FreqH", "0043", + "Ch08_Start_EnvFrq", "0044", + "Ch08_End_EnvSel", "0045", + "Ch09_Control", "0048", + "Ch09_PCMVol_WavSel", "0049", + "Ch09_FreqL", "004A", + "Ch09_FreqH", "004B", + "Ch09_Start_EnvFrq", "004C", + "Ch09_End_EnvSel", "004D", + "Ch10_Control", "0050", + "Ch10_PCMVol_WavSel", "0051", + "Ch10_FreqL", "0052", + "Ch10_FreqH", "0053", + "Ch10_Start_EnvFrq", "0054", + "Ch10_End_EnvSel", "0055", + "Ch11_Control", "0058", + "Ch11_PCMVol_WavSel", "0059", + "Ch11_FreqL", "005A", + "Ch11_FreqH", "005B", + "Ch11_Start_EnvFrq", "005C", + "Ch11_End_EnvSel", "005D", + "Ch12_Control", "0060", + "Ch12_PCMVol_WavSel", "0061", + "Ch12_FreqL", "0062", + "Ch12_FreqH", "0063", + "Ch12_Start_EnvFrq", "0064", + "Ch12_End_EnvSel", "0065", + "Ch13_Control", "0068", + "Ch13_PCMVol_WavSel", "0069", + "Ch13_FreqL", "006A", + "Ch13_FreqH", "006B", + "Ch13_Start_EnvFrq", "006C", + "Ch13_End_EnvSel", "006D", + "Ch14_Control", "0070", + "Ch14_PCMVol_WavSel", "0071", + "Ch14_FreqL", "0072", + "Ch14_FreqH", "0073", + "Ch14_Start_EnvFrq", "0074", + "Ch14_End_EnvSel", "0075", + "Ch15_Control", "0078", + "Ch15_PCMVol_WavSel", "0079", + "Ch15_FreqL", "007A", + "Ch15_FreqH", "007B", + "Ch15_Start_EnvFrq", "007C", + "Ch15_End_EnvSel", "007D", + // Envelope data + "Env01Data", "0080", + "Env02Data", "0100", + "Env03Data", "0180", + "Env04Data", "0200", + "Env05Data", "0280", + "Env06Data", "0300", + "Env07Data", "0380", + "Env08Data", "0400", + "Env09Data", "0480", + "Env10Data", "0500", + "Env11Data", "0580", + "Env12Data", "0600", + "Env13Data", "0680", + "Env14Data", "0700", + "Env15Data", "0780", + "Env16Data", "0800", + "Env17Data", "0880", + "Env18Data", "0900", + "Env19Data", "0980", + "Env20Data", "0A00", + "Env21Data", "0A80", + "Env22Data", "0B00", + "Env23Data", "0B80", + "Env24Data", "0C00", + "Env25Data", "0C80", + "Env26Data", "0D00", + "Env27Data", "0D80", + "Env28Data", "0E00", + "Env29Data", "0E80", + "Env30Data", "0F00", + "Env31Data", "0F80", + // Wavetable data + "Wave00Data", "1000", + "Wave01Data", "1080", + "Wave02Data", "1100", + "Wave03Data", "1180", + "Wave04Data", "1200", + "Wave05Data", "1280", + "Wave06Data", "1300", + "Wave07Data", "1380", + "Wave08Data", "1400", + "Wave09Data", "1480", + "Wave10Data", "1500", + "Wave11Data", "1580", + "Wave12Data", "1600", + "Wave13Data", "1680", + "Wave14Data", "1700", + "Wave15Data", "1780", + "Wave16Data", "1800", + "Wave17Data", "1880", + "Wave18Data", "1900", + "Wave19Data", "1980", + "Wave20Data", "1A00", + "Wave21Data", "1A80", + "Wave22Data", "1B00", + "Wave23Data", "1B80", + "Wave24Data", "1C00", + "Wave25Data", "1C80", + "Wave26Data", "1D00", + "Wave27Data", "1D80", + "Wave28Data", "1E00", + "Wave29Data", "1E80", + "Wave30Data", "1F00", + "Wave31Data", "1F80", + NULL +}; + +const char** DivPlatformX1_010::getRegisterSheet() { + return regCheatSheetX1_010; +} + +const char* DivPlatformX1_010::getEffectName(unsigned char effect) { + switch (effect) { + case 0x10: + return "10xx: Change waveform"; + break; + case 0x11: + return "11xx: Change envelope shape"; + break; + case 0x17: + return "17xx: Toggle PCM mode"; + break; + case 0x20: + return "20xx: Set PCM frequency"; + break; + case 0x22: + return "22xx: Set envelope mode (bit 0: enable, bit 1: one-shot, bit 2: split shape to L/R, bit 3: H.invert right, bit 4: V.invert right)"; + break; + case 0x23: + return "23xx: Set envelope period"; + break; + case 0x25: + return "25xx: Envelope slide up"; + break; + case 0x26: + return "26xx: Envelope slide down"; + break; + case 0x29: + return "29xy: Set auto-envelope (x: numerator; y: denominator)"; + break; + } + return NULL; +} + +void DivPlatformX1_010::acquire(short* bufL, short* bufR, size_t start, size_t len) { + for (size_t h=start; htick(); + + signed short tempL=x1_010->output(0); + signed short tempR=x1_010->output(1); + + if (tempL<-32768) tempL=-32768; + if (tempL>32767) tempL=32767; + if (tempR<-32768) tempR=-32768; + if (tempR>32767) tempR=32767; + + //printf("tempL: %d tempR: %d\n",tempL,tempR); + bufL[h]=stereo?tempL:((tempL+tempR)>>1); + bufR[h]=stereo?tempR:bufL[h]; + } +} + +double DivPlatformX1_010::NoteX1_010(int ch, int note) { + if (chan[ch].pcm) { // PCM note + double off=1.0; + int sample = chan[ch].sample; + if (sample>=0 && samplesong.sampleLen) { + DivSample* s=parent->getSample(sample); + if (s->centerRate<1) { + off=1.0; + } else { + off=s->centerRate/8363.0; + } + } + return off*parent->calcBaseFreq(chipClock,8192,note,false); + } + // Wavetable note + return NOTE_FREQUENCY(note); +} + +void DivPlatformX1_010::updateWave(int ch) { + DivWavetable* wt=parent->getWave(chan[ch].wave); + if (chan[ch].active) { + chan[ch].waveBank ^= 1; + } + for (int i=0; i<128; i++) { + if (wt->max<1 || wt->len<1) { + waveWrite(ch,i,0); + } else { + waveWrite(ch,i,wt->data[i*wt->len/128]*255/wt->max); + } + } + if (!chan[ch].pcm) { + chWrite(ch,1,(chan[ch].waveBank<<4)|(ch&0xf)); + } +} + +void DivPlatformX1_010::updateEnvelope(int ch) { + if (!chan[ch].pcm) { + if (isMuted[ch]) { + for (int i=0; i<128; i++) { + rWrite(0x800|(ch<<7)|(i&0x7f),0); + } + } else { + if (!chan[ch].env.flag.envEnable) { + for (int i=0; i<128; i++) { + envFill(ch,i); + } + } else { + DivWavetable* wt=parent->getWave(chan[ch].env.shape); + for (int i=0; i<128; i++) { + if (wt->max<1 || wt->len<1) { + envFill(ch,i); + } else if (stereo&&(chan[ch].env.flag.envHinv||chan[ch].env.flag.envSplit||chan[ch].env.flag.envVinv)) { // Stereo config + int la = i, ra = i; + int lo, ro; + if (chan[ch].env.flag.envHinv) { ra = 127-i; } // horizontal invert right envelope + if (chan[ch].env.flag.envSplit) { // Split shape to left and right half + lo = wt->data[la*(wt->len/128/2)]*15/wt->max; + ro = wt->data[(ra+128)*(wt->len/128/2)]*15/wt->max; + } else { + lo = wt->data[la*wt->len/128]*15/wt->max; + ro = wt->data[ra*wt->len/128]*15/wt->max; + } + if (chan[ch].env.flag.envVinv) { ro = 15-ro; } // vertical invert right envelope + envWrite(ch,i,lo,ro); + } else { + int out = wt->data[i*wt->len/128]*15/wt->max; + envWrite(ch,i,out,out); + } + } + } + } + chWrite(ch,5,0x10|(ch&0xf)); + } else { + chWrite(ch,1,(chan[ch].lvol<<4)|chan[ch].rvol); + } +} + +void DivPlatformX1_010::tick() { + for (int i=0; i<16; i++) { + chan[i].std.next(); + if (chan[i].std.hadVol) { + signed char macroVol=((chan[i].vol&15)*MIN(chan[i].furnacePCM?64:15,chan[i].std.vol))/(chan[i].furnacePCM?64:15); + if ((!isMuted[i])&&(macroVol!=chan[i].outVol)) { + chan[i].outVol=macroVol; + chan[i].envChanged=true; + } + } + if ((!chan[i].pcm) || chan[i].furnacePCM) { + if (chan[i].std.hadArp) { + if (!chan[i].inPorta) { + if (chan[i].std.arpMode) { + chan[i].baseFreq=NoteX1_010(i,chan[i].std.arp); + } else { + chan[i].baseFreq=NoteX1_010(i,chan[i].note+chan[i].std.arp); + } + } + chan[i].freqChanged=true; + } else { + if (chan[i].std.arpMode && chan[i].std.finishedArp) { + chan[i].baseFreq=NoteX1_010(i,chan[i].note); + chan[i].freqChanged=true; + } + } + } + if (chan[i].std.hadWave && !chan[i].pcm) { + if (chan[i].wave!=chan[i].std.wave) { + chan[i].wave=chan[i].std.wave; + if (!chan[i].pcm) { + updateWave(i); + if (!chan[i].keyOff) chan[i].keyOn=true; + } + } + } + if (chan[i].std.hadEx1) { + bool nextEnable=(chan[i].std.ex1&1); + if (nextEnable!=(chan[i].env.flag.envEnable)) { + chan[i].env.flag.envEnable=nextEnable; + if (!chan[i].pcm) { + if (!isMuted[i]) { + chan[i].envChanged=true; + } + refreshControl(i); + } + } + bool nextOneshot=(chan[i].std.ex1&2); + if (nextOneshot!=(chan[i].env.flag.envOneshot)) { + chan[i].env.flag.envOneshot=nextOneshot; + if (!chan[i].pcm) { + refreshControl(i); + } + } + if (stereo) { + bool nextSplit=(chan[i].std.ex1&4); + if (nextSplit!=(chan[i].env.flag.envSplit)) { + chan[i].env.flag.envSplit=nextSplit; + if (!isMuted[i] && !chan[i].pcm) { + chan[i].envChanged=true; + } + } + bool nextHinv=(chan[i].std.ex1&8); + if (nextHinv!=(chan[i].env.flag.envHinv)) { + chan[i].env.flag.envHinv=nextHinv; + if (!isMuted[i] && !chan[i].pcm) { + chan[i].envChanged=true; + } + } + bool nextVinv=(chan[i].std.ex1&16); + if (nextVinv!=(chan[i].env.flag.envVinv)) { + chan[i].env.flag.envVinv=nextVinv; + if (!isMuted[i] && !chan[i].pcm) { + chan[i].envChanged=true; + } + } + } + } + if (chan[i].std.hadEx2) { + if (chan[i].env.shape!=chan[i].std.ex2) { + chan[i].env.shape=chan[i].std.ex2; + if (!chan[i].pcm) { + if (chan[i].env.flag.envEnable && (!isMuted[i])) { + chan[i].envChanged=true; + } + if (!chan[i].keyOff) chan[i].keyOn=true; + } + } + } + if (chan[i].std.hadEx3) { + chan[i].autoEnvNum=chan[i].std.ex3; + if (!chan[i].pcm) { + chan[i].freqChanged=true; + if (!chan[i].std.willAlg) chan[i].autoEnvDen=1; + } + } + if (chan[i].std.hadAlg) { + chan[i].autoEnvDen=chan[i].std.alg; + if (!chan[i].pcm) { + chan[i].freqChanged=true; + if (!chan[i].std.willEx3) chan[i].autoEnvNum=1; + } + } + if (chan[i].envChanged) { + if (!isMuted[i]) { + if (stereo) { + chan[i].lvol=((chan[i].outVol&0xf)*((chan[i].pan>>4)&0xf))/15; + chan[i].rvol=((chan[i].outVol&0xf)*((chan[i].pan>>0)&0xf))/15; + } else { + chan[i].lvol=chan[i].rvol=chan[i].outVol; + } + } + updateEnvelope(i); + chan[i].envChanged=false; + } + if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false); + if (chan[i].pcm) { + if (chan[i].freq<1) chan[i].freq=1; + if (chan[i].freq>255) chan[i].freq=255; + chWrite(i,2,chan[i].freq&0xff); + } else { + if (chan[i].freq>65535) chan[i].freq=65535; + chWrite(i,2,chan[i].freq&0xff); + chWrite(i,3,(chan[i].freq>>8)&0xff); + if (chan[i].freqChanged && chan[i].autoEnvNum>0 && chan[i].autoEnvDen>0) { + chan[i].env.period=(chan[i].freq*chan[i].autoEnvDen/chan[i].autoEnvNum)>>12; + chWrite(i,4,chan[i].env.period); + } + } + if (chan[i].keyOn || chan[i].keyOff || (chRead(i,0)&1)) { + refreshControl(i); + } + if (chan[i].keyOn) chan[i].keyOn=false; + if (chan[i].keyOff) chan[i].keyOff=false; + chan[i].freqChanged=false; + } + if (chan[i].env.slide!=0) { + chan[i].env.slidefrac+=chan[i].env.slide; + while (chan[i].env.slidefrac>0xf) { + chan[i].env.slidefrac-=0x10; + if (chan[i].env.period<0xff) { + chan[i].env.period++; + if (!chan[i].pcm) { + chWrite(i,4,chan[i].env.period); + } + } + } + while (chan[i].env.slidefrac<-0xf) { + chan[i].env.slidefrac+=0x10; + if (chan[i].env.period>0) { + chan[i].env.period--; + if (!chan[i].pcm) { + chWrite(i,4,chan[i].env.period); + } + } + } + } + } +} + +int DivPlatformX1_010::dispatch(DivCommand c) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: { + chWrite(c.chan,0,0); // reset previous note + DivInstrument* ins=parent->getIns(chan[c.chan].ins); + if ((ins->type==DIV_INS_AMIGA) || chan[c.chan].pcm) { + if (ins->type==DIV_INS_AMIGA) { + chan[c.chan].furnacePCM=true; + } else { + chan[c.chan].furnacePCM=false; + } + if (skipRegisterWrites) break; + if (chan[c.chan].furnacePCM) { + chan[c.chan].pcm=true; + chan[c.chan].std.init(ins); + chan[c.chan].sample=ins->amiga.initSample; + if (chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { + DivSample* s=parent->getSample(chan[c.chan].sample); + chWrite(c.chan,4,(s->offX1_010>>12)&0xff); + int end=(s->offX1_010+s->length8+0xfff)&~0xfff; // padded + chWrite(c.chan,5,(0x100-(end>>12))&0xff); + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].note=c.value; + chan[c.chan].baseFreq=NoteX1_010(c.chan,chan[c.chan].note); + chan[c.chan].freqChanged=true; + } + } else { + chan[c.chan].std.init(NULL); + chan[c.chan].outVol=chan[c.chan].vol; + if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) { + chWrite(c.chan,0,0); // reset + chWrite(c.chan,1,0); + chWrite(c.chan,2,0); + chWrite(c.chan,4,0); + chWrite(c.chan,5,0); + break; + } + } + } else { + chan[c.chan].std.init(NULL); + chan[c.chan].outVol=chan[c.chan].vol; + if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) { + chWrite(c.chan,0,0); // reset + chWrite(c.chan,1,0); + chWrite(c.chan,2,0); + chWrite(c.chan,4,0); + chWrite(c.chan,5,0); + break; + } + DivSample* s=parent->getSample(12*sampleBank+c.value%12); + chWrite(c.chan,4,(s->offX1_010>>12)&0xff); + int end=(s->offX1_010+s->length8+0xfff)&~0xfff; // padded + chWrite(c.chan,5,(0x100-(end>>12))&0xff); + chan[c.chan].baseFreq=(((unsigned int)s->rate)<<4)/(chipClock/512); + chan[c.chan].freqChanged=true; + } + } else if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].note=c.value; + chan[c.chan].baseFreq=NoteX1_010(c.chan,chan[c.chan].note); + chan[c.chan].freqChanged=true; + } + chan[c.chan].active=true; + chan[c.chan].keyOn=true; + chan[c.chan].envChanged=true; + chan[c.chan].std.init(ins); + refreshControl(c.chan); + break; + } + case DIV_CMD_NOTE_OFF: + chan[c.chan].pcm=false; + chan[c.chan].active=false; + chan[c.chan].keyOff=true; + chan[c.chan].std.init(NULL); + break; + case DIV_CMD_NOTE_OFF_ENV: + case DIV_CMD_ENV_RELEASE: + chan[c.chan].std.release(); + break; + case DIV_CMD_INSTRUMENT: + if (chan[c.chan].ins!=c.value || c.value2==1) { + chan[c.chan].ins=c.value; + } + break; + case DIV_CMD_VOLUME: + if (chan[c.chan].vol!=c.value) { + chan[c.chan].vol=c.value; + if (!chan[c.chan].std.hasVol) { + if (chan[c.chan].outVol!=c.value) { + chan[c.chan].outVol=c.value; + if (!isMuted[c.chan]) { + chan[c.chan].envChanged=true; + } + } + } + } + break; + case DIV_CMD_GET_VOLUME: + if (chan[c.chan].std.hasVol) { + return chan[c.chan].vol; + } + return chan[c.chan].outVol; + break; + case DIV_CMD_PITCH: + chan[c.chan].pitch=c.value; + chan[c.chan].freqChanged=true; + break; + case DIV_CMD_WAVE: + chan[c.chan].wave=c.value; + updateWave(c.chan); + chan[c.chan].keyOn=true; + break; + case DIV_CMD_X1_010_ENVELOPE_SHAPE: + if (chan[c.chan].env.shape!=c.value) { + chan[c.chan].env.shape=c.value; + if (!chan[c.chan].pcm) { + if (chan[c.chan].env.flag.envEnable&&(!isMuted[c.chan])) { + chan[c.chan].envChanged=true; + } + chan[c.chan].keyOn=true; + } + } + break; + case DIV_CMD_NOTE_PORTA: { + int destFreq=NoteX1_010(c.chan,c.value2); + bool return2=false; + if (destFreq>chan[c.chan].baseFreq) { + chan[c.chan].baseFreq+=c.value; + if (chan[c.chan].baseFreq>=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } else { + chan[c.chan].baseFreq-=c.value; + if (chan[c.chan].baseFreq<=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } + chan[c.chan].freqChanged=true; + if (return2) { + chan[c.chan].inPorta=false; + return 2; + } + break; + } + case DIV_CMD_SAMPLE_MODE: + if (chan[c.chan].pcm!=(c.value&1)) { + chan[c.chan].pcm=c.value&1; + chan[c.chan].freqChanged=true; + if (!isMuted[c.chan]) { + chan[c.chan].envChanged=true; + } + } + break; + case DIV_CMD_SAMPLE_BANK: + sampleBank=c.value; + if (sampleBank>(parent->song.sample.size()/12)) { + sampleBank=parent->song.sample.size()/12; + } + break; + case DIV_CMD_PANNING: { + if (stereo&&(chan[c.chan].pan!=c.value)) { + chan[c.chan].pan=c.value; + if (!isMuted[c.chan]) { + chan[c.chan].envChanged=true; + } + } + break; + } + case DIV_CMD_LEGATO: + chan[c.chan].note=c.value; + chan[c.chan].baseFreq=NoteX1_010(c.chan,chan[c.chan].note+((chan[c.chan].std.willArp && !chan[c.chan].std.arpMode)?(chan[c.chan].std.arp):(0))); + chan[c.chan].freqChanged=true; + break; + case DIV_CMD_PRE_PORTA: + if (chan[c.chan].active && c.value2) { + if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + } + chan[c.chan].inPorta=c.value; + break; + case DIV_CMD_SAMPLE_FREQ: + if (chan[c.chan].pcm) { + chan[c.chan].freq=c.value&0xff; + chWrite(c.chan,2,chan[c.chan].freq&0xff); + if (chRead(c.chan,0)&1) { + refreshControl(c.chan); + } + } + break; + case DIV_CMD_X1_010_ENVELOPE_MODE: { + bool nextEnable=c.value&1; + if (nextEnable!=(chan[c.chan].env.flag.envEnable)) { + chan[c.chan].env.flag.envEnable=nextEnable; + if (!chan[c.chan].pcm) { + if (!isMuted[c.chan]) { + chan[c.chan].envChanged=true; + } + refreshControl(c.chan); + } + } + bool nextOneshot=c.value&2; + if (nextOneshot!=(chan[c.chan].env.flag.envOneshot)) { + chan[c.chan].env.flag.envOneshot=nextOneshot; + if (!chan[c.chan].pcm) { + refreshControl(c.chan); + } + } + if (stereo) { + bool nextSplit=c.value&4; + if (nextSplit!=(chan[c.chan].env.flag.envSplit)) { + chan[c.chan].env.flag.envSplit=nextSplit; + if (!isMuted[c.chan] && !chan[c.chan].pcm) { + chan[c.chan].envChanged=true; + } + } + bool nextHinv=c.value&8; + if (nextHinv!=(chan[c.chan].env.flag.envHinv)) { + chan[c.chan].env.flag.envHinv=nextHinv; + if (!isMuted[c.chan] && !chan[c.chan].pcm) { + chan[c.chan].envChanged=true; + } + } + bool nextVinv=c.value&16; + if (nextVinv!=(chan[c.chan].env.flag.envVinv)) { + chan[c.chan].env.flag.envVinv=nextVinv; + if (!isMuted[c.chan] && !chan[c.chan].pcm) { + chan[c.chan].envChanged=true; + } + } + } + break; + } + case DIV_CMD_X1_010_ENVELOPE_PERIOD: + chan[c.chan].env.period=c.value; + if (!chan[c.chan].pcm) { + chWrite(c.chan,4,chan[c.chan].env.period); + } + break; + case DIV_CMD_X1_010_ENVELOPE_SLIDE: + chan[c.chan].env.slide=c.value; + break; + case DIV_CMD_X1_010_AUTO_ENVELOPE: + chan[c.chan].autoEnvNum=c.value>>4; + chan[c.chan].autoEnvDen=c.value&15; + chan[c.chan].freqChanged=true; + break; + case DIV_CMD_GET_VOLMAX: + return 15; + break; + case DIV_ALWAYS_SET_VOLUME: + return 1; + break; + default: + break; + } + return 1; +} + +void DivPlatformX1_010::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; + chan[ch].envChanged=true; +} + +void DivPlatformX1_010::forceIns() { + for (int i=0; i<16; i++) { + chan[i].insChanged=true; + chan[i].envChanged=true; + chan[i].freqChanged=true; + updateWave(i); + } +} + +void* DivPlatformX1_010::getChanState(int ch) { + return &chan[ch]; +} + +unsigned char* DivPlatformX1_010::getRegisterPool() { + for (int i=0; i<0x2000; i++) { + regPool[i]=x1_010->ram_r(i); + } + return regPool; +} + +int DivPlatformX1_010::getRegisterPoolSize() { + return 0x2000; +} + +void DivPlatformX1_010::reset() { + memset(regPool,0,0x2000); + for (int i=0; i<16; i++) { + chan[i]=DivPlatformX1_010::Channel(); + chan[i].reset(); + } + x1_010->reset(); + sampleBank=0; + // set per-channel initial panning + for (int i=0; i<16; i++) { + chWrite(i,0,0); + } +} + +bool DivPlatformX1_010::isStereo() { + return stereo; +} + +bool DivPlatformX1_010::keyOffAffectsArp(int ch) { + return true; +} + +void DivPlatformX1_010::notifyWaveChange(int wave) { + for (int i=0; i<16; i++) { + if (chan[i].wave==wave) { + updateWave(i); + } + } +} + +void DivPlatformX1_010::notifyInsDeletion(void* ins) { + for (int i=0; i<16; i++) { + chan[i].std.notifyInsDeletion((DivInstrument*)ins); + } +} + +void DivPlatformX1_010::setFlags(unsigned int flags) { + switch (flags&15) { + case 0: // 16MHz (earlier hardwares) + chipClock=16000000; + break; + case 1: // 16.67MHz (later hardwares) + chipClock=50000000.0/3.0; + break; + // Other clock is used? + } + rate=chipClock/512; + stereo=flags&16; +} + +void DivPlatformX1_010::poke(unsigned int addr, unsigned short val) { + rWrite(addr,val); +} + +void DivPlatformX1_010::poke(std::vector& wlist) { + for (DivRegWrite& i: wlist) rWrite(i.addr,i.val); +} + +int DivPlatformX1_010::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { + parent=p; + dumpWrites=false; + skipRegisterWrites=false; + stereo=false; + for (int i=0; i<16; i++) { + isMuted[i]=false; + } + setFlags(flags); + intf.parent=parent; + x1_010=new x1_010_core(intf); + x1_010->reset(); + reset(); + return 16; +} + +void DivPlatformX1_010::quit() { + delete x1_010; +} + +DivPlatformX1_010::~DivPlatformX1_010() { +} diff --git a/src/engine/platform/x1_010.h b/src/engine/platform/x1_010.h new file mode 100644 index 000000000..27bf4f4a6 --- /dev/null +++ b/src/engine/platform/x1_010.h @@ -0,0 +1,138 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _X1_010_H +#define _X1_010_H + +#include +#include "../dispatch.h" +#include "../engine.h" +#include "../macroInt.h" +#include "../../../extern/cam900_vgsound_emu/x1_010/x1_010.hpp" + +class DivX1_010Interface: public x1_010_intf { + public: + DivEngine* parent; + int sampleBank; + virtual u8 read_rom(uint32_t address) override { + if (parent->x1_010Mem==NULL) return 0; + return parent->x1_010Mem[address & 0xfffff]; + } + DivX1_010Interface(): parent(NULL), sampleBank(0) {} +}; + +class DivPlatformX1_010: public DivDispatch { + struct Channel { + struct Envelope { + struct EnvFlag { + unsigned char envEnable : 1; + unsigned char envOneshot : 1; + unsigned char envSplit : 1; + unsigned char envHinv : 1; + unsigned char envVinv : 1; + void reset() { + envEnable=0; + envOneshot=0; + envSplit=0; + envHinv=0; + envVinv=0; + } + EnvFlag(): + envEnable(0), + envOneshot(0), + envSplit(0), + envHinv(0), + envVinv(0) {} + }; + int shape, period, slide, slidefrac; + EnvFlag flag; + void reset() { + shape=-1; + period=0; + flag.reset(); + } + Envelope(): + shape(-1), + period(0), + slide(0), + slidefrac(0) {} + }; + int freq, baseFreq, pitch, note; + int wave, sample, ins; + unsigned char pan, autoEnvNum, autoEnvDen; + bool active, insChanged, envChanged, freqChanged, keyOn, keyOff, inPorta, furnacePCM, pcm; + int vol, outVol, lvol, rvol; + unsigned char waveBank; + Envelope env; + DivMacroInt std; + void reset() { + freq = baseFreq = pitch = note = 0; + wave = sample = ins = -1; + pan = 255; + autoEnvNum = autoEnvDen = 0; + active = false; + insChanged = envChanged = freqChanged = true; + keyOn = keyOff = inPorta = furnacePCM = pcm = false; + vol = outVol = lvol = rvol = 15; + waveBank = 0; + } + Channel(): + freq(0), baseFreq(0), pitch(0), note(0), + wave(-1), sample(-1), ins(-1), + pan(255), autoEnvNum(0), autoEnvDen(0), + active(false), insChanged(true), envChanged(true), freqChanged(false), keyOn(false), keyOff(false), inPorta(false), furnacePCM(false), pcm(false), + vol(15), outVol(15), lvol(15), rvol(15), + waveBank(0) {} + }; + Channel chan[16]; + bool isMuted[16]; + bool stereo=false; + unsigned char sampleBank; + DivX1_010Interface intf; + x1_010_core* x1_010; + unsigned char regPool[0x2000]; + double NoteX1_010(int ch, int note); + void updateWave(int ch); + void updateEnvelope(int ch); + friend void putDispatchChan(void*,int,int); + public: + void acquire(short* bufL, short* bufR, size_t start, size_t len); + int dispatch(DivCommand c); + void* getChanState(int chan); + unsigned char* getRegisterPool(); + int getRegisterPoolSize(); + void reset(); + void forceIns(); + void tick(); + void muteChannel(int ch, bool mute); + bool isStereo(); + bool keyOffAffectsArp(int ch); + void setFlags(unsigned int flags); + void notifyWaveChange(int wave); + void notifyInsDeletion(void* ins); + void poke(unsigned int addr, unsigned short val); + void poke(std::vector& wlist); + const char** getRegisterSheet(); + const char* getEffectName(unsigned char effect); + int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); + void quit(); + ~DivPlatformX1_010(); +}; + +#endif diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index f0f43dc1a..2fba35336 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -248,6 +248,18 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe return false; } break; + } + case DIV_SYSTEM_X1_010: + switch (effect) { + case 0x10: // select waveform + dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal)); + break; + case 0x11: // select envelope shape + dispatchCmd(DivCommand(DIV_CMD_X1_010_ENVELOPE_SHAPE,ch,effectVal)); + break; + case 0x17: // PCM enable + dispatchCmd(DivCommand(DIV_CMD_SAMPLE_MODE,ch,(effectVal>0))); + break; } break; default: @@ -533,6 +545,30 @@ bool DivEngine::perSystemPostEffect(int ch, unsigned char effect, unsigned char } return false; break; + case DIV_SYSTEM_X1_010: + switch (effect) { + case 0x20: // PCM frequency + dispatchCmd(DivCommand(DIV_CMD_SAMPLE_FREQ,ch,effectVal)); + break; + case 0x22: // envelope mode + dispatchCmd(DivCommand(DIV_CMD_X1_010_ENVELOPE_MODE,ch,effectVal)); + break; + case 0x23: // envelope period + dispatchCmd(DivCommand(DIV_CMD_X1_010_ENVELOPE_PERIOD,ch,effectVal)); + break; + case 0x25: // envelope slide up + dispatchCmd(DivCommand(DIV_CMD_X1_010_ENVELOPE_SLIDE,ch,effectVal)); + break; + case 0x26: // envelope slide down + dispatchCmd(DivCommand(DIV_CMD_X1_010_ENVELOPE_SLIDE,ch,-effectVal)); + break; + case 0x29: // auto-envelope + dispatchCmd(DivCommand(DIV_CMD_X1_010_AUTO_ENVELOPE,ch,effectVal)); + break; + default: + return false; + } + break; default: return false; } diff --git a/src/engine/sample.cpp b/src/engine/sample.cpp index ec6313fff..b802bf791 100644 --- a/src/engine/sample.cpp +++ b/src/engine/sample.cpp @@ -115,8 +115,9 @@ bool DivSample::initInternal(unsigned char d, int count) { case 8: // 8-bit if (data8!=NULL) delete[] data8; length8=count; - data8=new signed char[length8]; - memset(data8,0,length8); + // for padding X1-010 sample + data8=new signed char[(count+4095)&(~0xfff)]; + memset(data8,0,(count+4095)&(~0xfff)); break; case 9: // BRR if (dataBRR!=NULL) delete[] dataBRR; diff --git a/src/engine/sample.h b/src/engine/sample.h index 2915f2cef..8b6d16b19 100644 --- a/src/engine/sample.h +++ b/src/engine/sample.h @@ -49,7 +49,7 @@ struct DivSample { unsigned int length8, length16, length1, lengthDPCM, lengthQSoundA, lengthA, lengthB, lengthX68, lengthBRR, lengthVOX; unsigned int off8, off16, off1, offDPCM, offQSoundA, offA, offB, offX68, offBRR, offVOX; - unsigned int offSegaPCM, offQSound; + unsigned int offSegaPCM, offQSound, offX1_010; unsigned int samples; diff --git a/src/engine/song.h b/src/engine/song.h index d1e255d95..34d77386a 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -91,7 +91,8 @@ enum DivSystem { DIV_SYSTEM_LYNX, DIV_SYSTEM_QSOUND, DIV_SYSTEM_YM2610B_EXT, - DIV_SYSTEM_SEGAPCM_COMPAT + DIV_SYSTEM_SEGAPCM_COMPAT, + DIV_SYSTEM_X1_010 }; struct DivSong { @@ -239,6 +240,13 @@ struct DivSong { // - 2: YM2423 // - 3: VRC7 // - 4: custom (TODO) + // - X1-010: + // - bit 0-3: clock rate + // - 0: 16MHz (Seta 1) + // - 1: 16.67MHz (Seta 2) + // - bit 4: stereo + // - 0: mono + // - 1: stereo unsigned int systemFlags[32]; // song information diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 3c988ceb8..48de06c7f 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -135,6 +135,8 @@ DivSystem DivEngine::systemFromFile(unsigned char val) { return DIV_SYSTEM_LYNX; case 0xa9: return DIV_SYSTEM_SEGAPCM_COMPAT; + case 0xb0: + return DIV_SYSTEM_X1_010; case 0xde: return DIV_SYSTEM_YM2610B_EXT; case 0xe0: @@ -258,6 +260,8 @@ unsigned char DivEngine::systemToFile(DivSystem val) { return 0xa8; case DIV_SYSTEM_SEGAPCM_COMPAT: return 0xa9; + case DIV_SYSTEM_X1_010: + return 0xb0; case DIV_SYSTEM_YM2610B_EXT: return 0xde; case DIV_SYSTEM_QSOUND: @@ -335,7 +339,7 @@ int DivEngine::getChannelCount(DivSystem sys) { case DIV_SYSTEM_OPL3: return 18; case DIV_SYSTEM_MULTIPCM: - return 24; + return 28; case DIV_SYSTEM_PCSPKR: return 1; case DIV_SYSTEM_POKEY: @@ -351,6 +355,7 @@ int DivEngine::getChannelCount(DivSystem sys) { case DIV_SYSTEM_POKEMINI: return 1; case DIV_SYSTEM_SEGAPCM: + case DIV_SYSTEM_X1_010: return 16; case DIV_SYSTEM_VBOY: return 6; @@ -650,6 +655,8 @@ const char* DivEngine::getSystemName(DivSystem sys) { return "Taito Arcade Extended Channel 3"; case DIV_SYSTEM_QSOUND: return "Capcom QSound"; + case DIV_SYSTEM_X1_010: + return "Seta/Allumer X1-010"; } return "Unknown"; } @@ -775,6 +782,8 @@ const char* DivEngine::getSystemChips(DivSystem sys) { return "Yamaha YM2610B Extended Channel 3"; case DIV_SYSTEM_QSOUND: return "Capcom DL-1425"; + case DIV_SYSTEM_X1_010: + return "Seta/Allumer X1-010"; } return "Unknown"; } @@ -857,7 +866,7 @@ bool DivEngine::isSTDSystem(DivSystem sys) { sys!=DIV_SYSTEM_YM2151); } -const char* chanNames[38][24]={ +const char* chanNames[38][28]={ {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "PCM"}, // YMU759/SegaPCM {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Square 1", "Square 2", "Square 3", "Noise"}, // Genesis {"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6", "Square 1", "Square 2", "Square 3", "Noise"}, // Genesis (extended channel 3) @@ -886,7 +895,7 @@ const char* chanNames[38][24]={ {"FM 1", "FM 2", "FM 3", "PSG 1", "PSG 2", "PSG 3"}, // OPN {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Square 1", "Square 2", "Square 3", "Kick", "Snare", "Top", "HiHat", "Tom", "Rim", "ADPCM"}, // PC-98 {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8", "FM 9", "FM 10", "FM 11", "FM 12", "FM 13", "FM 14", "FM 15", "FM 16", "FM 17", "FM 18"}, // OPL3 - {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "Channel 17", "Channel 18", "Channel 19", "Channel 20", "Channel 21", "Channel 22", "Channel 23", "Channel 24"}, // MultiPCM + {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "Channel 17", "Channel 18", "Channel 19", "Channel 20", "Channel 21", "Channel 22", "Channel 23", "Channel 24", "Channel 25", "Channel 26", "Channel 27", "Channel 28"}, // MultiPCM {"Square"}, // PC Speaker/Pokémon Mini {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Noise"}, // Virtual Boy/SCC {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6", "ADPCM-B"}, // YM2610B @@ -898,7 +907,7 @@ const char* chanNames[38][24]={ {"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6", "ADPCM-B"}, // YM2610B (extended channel 3) }; -const char* chanShortNames[38][24]={ +const char* chanShortNames[38][28]={ {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "PCM"}, // YMU759 {"F1", "F2", "F3", "F4", "F5", "F6", "S1", "S2", "S3", "NO"}, // Genesis {"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6", "S1", "S2", "S3", "S4"}, // Genesis (extended channel 3) @@ -927,7 +936,7 @@ const char* chanShortNames[38][24]={ {"F1", "F2", "F3", "S1", "S2", "S3"}, // OPN {"F1", "F2", "F3", "F4", "F5", "F6", "S1", "S2", "S3", "BD", "SD", "TP", "HH", "TM", "RM", "P"}, // PC-98 {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18"}, // OPL3 - {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24"}, // MultiPCM + {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27", "28"}, // MultiPCM {"SQ"}, // PC Speaker/Pokémon Mini {"CH1", "CH2", "CH3", "CH4", "CH5", "NO"}, // Virtual Boy/SCC {"F1", "F2", "F3", "F4", "F5", "F6", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"}, // YM2610B @@ -939,7 +948,7 @@ const char* chanShortNames[38][24]={ {"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"}, // YM2610B (extended channel 3) }; -const int chanTypes[38][24]={ +const int chanTypes[39][28]={ {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4}, // YMU759 {0, 0, 0, 0, 0, 0, 1, 1, 1, 2}, // Genesis {0, 0, 5, 5, 5, 5, 0, 0, 0, 1, 1, 1, 2}, // Genesis (extended channel 3) @@ -968,7 +977,7 @@ const int chanTypes[38][24]={ {0, 0, 0, 1, 1, 1}, // OPN {0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 4}, // PC-98 {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // OPL3 - {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, // MultiPCM/QSound + {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, // MultiPCM/QSound {1}, // PC Speaker/Pokémon Mini {3, 3, 3, 3, 3, 2}, // Virtual Boy/SCC {0, 0, 0, 0, 0, 0, 1, 1, 1, 4, 4, 4, 4, 4, 4, 4}, // YM2610B @@ -978,9 +987,10 @@ const int chanTypes[38][24]={ {0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2}, // OPL3 4-op + drums {3, 3, 3, 3}, //Lynx {0, 0, 5, 5, 5, 5, 0, 0, 0, 1, 1, 1, 4, 4, 4, 4, 4, 4, 4}, // YM2610B (extended channel 3) + {3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3}, // X1-010 }; -const DivInstrumentType chanPrefType[44][24]={ +const DivInstrumentType chanPrefType[45][28]={ {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM}, // YMU759 {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD}, // Genesis {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD}, // Genesis (extended channel 3) @@ -1009,7 +1019,7 @@ const DivInstrumentType chanPrefType[44][24]={ {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY}, // OPN {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, // PC-98 {DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL, DIV_INS_OPL}, // OPL/OPL2/OPL3 - {DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, // MultiPCM/QSound + {DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, // MultiPCM/QSound {DIV_INS_STD}, // PC Speaker/Pokémon Mini {DIV_INS_VBOY, DIV_INS_VBOY, DIV_INS_VBOY, DIV_INS_VBOY, DIV_INS_VBOY, DIV_INS_VBOY}, // Virtual Boy {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, // YM2610B @@ -1025,6 +1035,7 @@ const DivInstrumentType chanPrefType[44][24]={ {DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ, DIV_INS_OPZ}, // Z {DIV_INS_MIKEY, DIV_INS_MIKEY, DIV_INS_MIKEY, DIV_INS_MIKEY}, // Lynx {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, // YM2610B (extended channel 3) + {DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010}, // X1-010 }; const char* DivEngine::getChannelName(int chan) { @@ -1129,6 +1140,7 @@ const char* DivEngine::getChannelName(int chan) { case DIV_SYSTEM_MULTIPCM: case DIV_SYSTEM_SEGAPCM: case DIV_SYSTEM_SEGAPCM_COMPAT: + case DIV_SYSTEM_X1_010: return chanNames[28][dispatchChanOfChan[chan]]; break; case DIV_SYSTEM_PCSPKR: @@ -1268,6 +1280,7 @@ const char* DivEngine::getChannelShortName(int chan) { case DIV_SYSTEM_MULTIPCM: case DIV_SYSTEM_SEGAPCM: case DIV_SYSTEM_SEGAPCM_COMPAT: + case DIV_SYSTEM_X1_010: return chanShortNames[28][dispatchChanOfChan[chan]]; break; case DIV_SYSTEM_PCSPKR: @@ -1438,6 +1451,9 @@ int DivEngine::getChannelType(int chan) { case DIV_SYSTEM_LYNX: return chanTypes[36][dispatchChanOfChan[chan]]; break; + case DIV_SYSTEM_X1_010: + return chanTypes[38][dispatchChanOfChan[chan]]; + break; } return 1; } @@ -1588,6 +1604,9 @@ DivInstrumentType DivEngine::getPreferInsType(int chan) { case DIV_SYSTEM_LYNX: return chanPrefType[42][dispatchChanOfChan[chan]]; break; + case DIV_SYSTEM_X1_010: + return chanPrefType[44][dispatchChanOfChan[chan]]; + break; } return DIV_INS_FM; } @@ -1616,6 +1635,7 @@ bool DivEngine::isVGMExportable(DivSystem which) { case DIV_SYSTEM_OPLL: case DIV_SYSTEM_OPLL_DRUMS: case DIV_SYSTEM_VRC7: + case DIV_SYSTEM_X1_010: return true; default: return false; diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index 97f427372..3c63f65c9 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -150,6 +150,13 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write w->writeC(3); } break; + case DIV_SYSTEM_X1_010: + for (int i=0; i<16; i++) { + w->writeC(0xc8); + w->writeS((isSecond?0x8000:0x0)+(i<<3)); + w->writeC(0); + } + break; case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_FULL: case DIV_SYSTEM_YM2610B: @@ -391,6 +398,11 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write w->writeS((isSecond?0x8000:0)|(write.addr&0xffff)); w->writeC(write.val); break; + case DIV_SYSTEM_X1_010: + w->writeC(0xc8); + w->writeS((isSecond?0x8000:0)|(write.addr&0x1fff)); + w->writeC(write.val); + break; case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_FULL: case DIV_SYSTEM_YM2610B: @@ -481,6 +493,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) { int hasOPM=0; int hasSegaPCM=0; int segaPCMOffset=0xf8000d; + int hasX1010=0; int hasRFC=0; int hasOPN=0; int hasOPNA=0; @@ -552,6 +565,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) { bool writePCESamples=false; bool writeADPCM=false; bool writeSegaPCM=false; + bool writeX1010=false; bool writeQSound=false; for (int i=0; ichipClock; + willExport[i]=true; + writeX1010=true; + } else if (!(hasX1010&0x40000000)) { + isSecond[i]=true; + willExport[i]=true; + hasX1010|=0x40000000; + howManyChips++; + } + break; case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_FULL: case DIV_SYSTEM_YM2610B: @@ -964,6 +990,16 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) { w->write(qsoundMem,blockSize); } + if (writeX1010 && x1_010MemLen>0) { + w->writeC(0x67); + w->writeC(0x66); + w->writeC(0x91); + w->writeI(x1_010MemLen+8); + w->writeI(x1_010MemLen); + w->writeI(0); + w->write(x1_010Mem,x1_010MemLen); + } + // initialize streams int streamID=0; for (int i=0; iinPorta?colorOn:colorOff,">> InPorta"); break; } + case DIV_SYSTEM_X1_010: { + DivPlatformX1_010::Channel* ch=(DivPlatformX1_010::Channel*)data; + ImGui::Text("> X1-010"); + ImGui::Text("* freq: %.4x",ch->freq); + ImGui::Text(" - base: %d",ch->baseFreq); + ImGui::Text(" - pitch: %d",ch->pitch); + ImGui::Text("- note: %d",ch->note); + ImGui::Text("- wave: %d",ch->wave); + ImGui::Text("- sample: %d",ch->sample); + ImGui::Text("- ins: %d",ch->ins); + ImGui::Text("- pan: %d",ch->pan); + ImGui::Text("* envelope:"); + ImGui::Text(" - shape: %d",ch->env.shape); + ImGui::Text(" - period: %.2x",ch->env.period); + ImGui::Text(" - slide: %.2x",ch->env.slide); + ImGui::Text(" - slidefrac: %.2x",ch->env.slidefrac); + ImGui::Text(" - autoEnvNum: %.2x",ch->autoEnvNum); + ImGui::Text(" - autoEnvDen: %.2x",ch->autoEnvDen); + ImGui::Text("- WaveBank: %d",ch->waveBank); + ImGui::Text("- vol: %.2x",ch->vol); + ImGui::Text("- outVol: %.2x",ch->outVol); + ImGui::Text("- Lvol: %.2x",ch->lvol); + ImGui::Text("- Rvol: %.2x",ch->rvol); + ImGui::TextColored(ch->active?colorOn:colorOff,">> Active"); + ImGui::TextColored(ch->insChanged?colorOn:colorOff,">> InsChanged"); + ImGui::TextColored(ch->envChanged?colorOn:colorOff,">> EnvChanged"); + ImGui::TextColored(ch->freqChanged?colorOn:colorOff,">> FreqChanged"); + ImGui::TextColored(ch->keyOn?colorOn:colorOff,">> KeyOn"); + ImGui::TextColored(ch->keyOff?colorOn:colorOff,">> KeyOff"); + ImGui::TextColored(ch->inPorta?colorOn:colorOff,">> InPorta"); + ImGui::TextColored(ch->furnacePCM?colorOn:colorOff,">> FurnacePCM"); + ImGui::TextColored(ch->pcm?colorOn:colorOff,">> PCM"); + ImGui::TextColored(ch->env.flag.envEnable?colorOn:colorOff,">> EnvEnable"); + ImGui::TextColored(ch->env.flag.envOneshot?colorOn:colorOff,">> EnvOneshot"); + ImGui::TextColored(ch->env.flag.envSplit?colorOn:colorOff,">> EnvSplit"); + ImGui::TextColored(ch->env.flag.envHinv?colorOn:colorOff,">> EnvHinv"); + ImGui::TextColored(ch->env.flag.envVinv?colorOn:colorOff,">> EnvVinv"); + break; + } default: ImGui::Text("Unknown system! Help!"); break; diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index ed9900e15..cb0c6dc4a 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1191,6 +1191,10 @@ void FurnaceGUI::drawInsList() { ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_MIKEY]); name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d\n",i,ins->name,i); break; + case DIV_INS_X1_010: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_X1_010]); + name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d\n",i,ins->name,i); + break; default: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_UNKNOWN]); name=fmt::sprintf(ICON_FA_QUESTION " %.2X: %s##_INS%d\n",i,ins->name,i); @@ -1347,7 +1351,7 @@ void FurnaceGUI::drawSampleEdit() { ImGui::Text("notes:"); if (sample->loopStart>=0) { considerations=true; - ImGui::Text("- sample won't loop on Neo Geo ADPCM-A"); + ImGui::Text("- sample won't loop on Neo Geo ADPCM-A and X1-010"); if (sample->loopStart&1) { ImGui::Text("- sample loop start will be aligned to the nearest even sample on Amiga"); } @@ -1363,10 +1367,18 @@ void FurnaceGUI::drawSampleEdit() { considerations=true; ImGui::Text("- sample length will be aligned and padded to 512 sample units on Neo Geo ADPCM."); } + if (sample->samples&4095) { + considerations=true; + ImGui::Text("- sample length will be aligned and padded to 4096 sample units on X1-010."); + } if (sample->samples>65535) { considerations=true; ImGui::Text("- maximum sample length on Sega PCM and QSound is 65536 samples"); } + if (sample->samples>131071) { + considerations=true; + ImGui::Text("- maximum sample length on X1-010 is 131072 samples"); + } if (sample->samples>2097151) { considerations=true; ImGui::Text("- maximum sample length on Neo Geo ADPCM is 2097152 samples"); @@ -2027,6 +2039,7 @@ void FurnaceGUI::drawStats() { String adpcmAUsage=fmt::sprintf("%d/16384KB",e->adpcmAMemLen/1024); String adpcmBUsage=fmt::sprintf("%d/16384KB",e->adpcmBMemLen/1024); String qsoundUsage=fmt::sprintf("%d/16384KB",e->qsoundMemLen/1024); + String x1_010Usage=fmt::sprintf("%d/1024KB",e->x1_010MemLen/1024); ImGui::Text("ADPCM-A"); ImGui::SameLine(); ImGui::ProgressBar(((float)e->adpcmAMemLen)/16777216.0f,ImVec2(-FLT_MIN,0),adpcmAUsage.c_str()); @@ -2036,6 +2049,9 @@ void FurnaceGUI::drawStats() { ImGui::Text("QSound"); ImGui::SameLine(); ImGui::ProgressBar(((float)e->qsoundMemLen)/16777216.0f,ImVec2(-FLT_MIN,0),qsoundUsage.c_str()); + ImGui::Text("X1-010"); + ImGui::SameLine(); + ImGui::ProgressBar(((float)e->x1_010MemLen)/1048576.0f,ImVec2(-FLT_MIN,0),x1_010Usage.c_str()); } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_STATS; ImGui::End(); @@ -4635,6 +4651,7 @@ bool FurnaceGUI::loop() { sysAddOption(DIV_SYSTEM_AY8930); sysAddOption(DIV_SYSTEM_LYNX); sysAddOption(DIV_SYSTEM_QSOUND); + sysAddOption(DIV_SYSTEM_X1_010); ImGui::EndMenu(); } if (ImGui::BeginMenu("configure system...")) { @@ -4921,6 +4938,23 @@ bool FurnaceGUI::loop() { } rightClickable break; } + case DIV_SYSTEM_X1_010: { + ImGui::Text("Clock rate:"); + if (ImGui::RadioButton("16MHz (Seta 1)",(flags&15)==0)) { + e->setSysFlags(i,(flags&(~16))|0,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("16.67MHz (Seta 2)",(flags&15)==1)) { + e->setSysFlags(i,(flags&(~16))|1,restart); + updateWindowTitle(); + } + bool x1_010Stereo=flags&16; + if (ImGui::Checkbox("Stereo",&x1_010Stereo)) { + e->setSysFlags(i,(flags&(~15))|(x1_010Stereo<<4),restart); + updateWindowTitle(); + } + break; + } case DIV_SYSTEM_GB: case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_EXT: @@ -4974,6 +5008,7 @@ bool FurnaceGUI::loop() { sysChangeOption(i,DIV_SYSTEM_AY8930); sysChangeOption(i,DIV_SYSTEM_LYNX); sysChangeOption(i,DIV_SYSTEM_QSOUND); + sysChangeOption(i,DIV_SYSTEM_X1_010); ImGui::EndMenu(); } } @@ -5557,6 +5592,7 @@ void FurnaceGUI::applyUISettings() { GET_UI_COLOR(GUI_COLOR_INSTR_BEEPER,ImVec4(0.0f,1.0f,0.0f,1.0f)); GET_UI_COLOR(GUI_COLOR_INSTR_SWAN,ImVec4(0.3f,0.5f,1.0f,1.0f)); GET_UI_COLOR(GUI_COLOR_INSTR_MIKEY,ImVec4(0.5f,1.0f,0.3f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_X1_010,ImVec4(0.3f,0.5f,1.0f,1.0f)); GET_UI_COLOR(GUI_COLOR_INSTR_UNKNOWN,ImVec4(0.3f,0.3f,0.3f,1.0f)); GET_UI_COLOR(GUI_COLOR_CHANNEL_FM,ImVec4(0.2f,0.8f,1.0f,1.0f)); GET_UI_COLOR(GUI_COLOR_CHANNEL_PULSE,ImVec4(0.4f,1.0f,0.2f,1.0f)); @@ -6286,6 +6322,12 @@ FurnaceGUI::FurnaceGUI(): 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "Seta/Allumer X1-010", { + DIV_SYSTEM_X1_010, 64, 0, 0, + 0 + } + )); sysCategories.push_back(cat); cat=FurnaceGUISysCategory("Game consoles"); @@ -6570,6 +6612,18 @@ FurnaceGUI::FurnaceGUI(): 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "Seta 1", { + DIV_SYSTEM_X1_010, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Seta 2", { + DIV_SYSTEM_X1_010, 64, 0, 1, + 0 + } + )); sysCategories.push_back(cat); cat=FurnaceGUISysCategory("DefleMask-compatible"); diff --git a/src/gui/gui.h b/src/gui/gui.h index 7d7f6b4a0..7f200e381 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -73,6 +73,7 @@ enum FurnaceGUIColors { GUI_COLOR_INSTR_BEEPER, GUI_COLOR_INSTR_SWAN, GUI_COLOR_INSTR_MIKEY, + GUI_COLOR_INSTR_X1_010, GUI_COLOR_INSTR_UNKNOWN, GUI_COLOR_CHANNEL_FM, GUI_COLOR_CHANNEL_PULSE, diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 4afa67a5c..f8306c200 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -27,7 +27,7 @@ #include #include "plot_nolerp.h" -const char* insTypes[24]={ +const char* insTypes[25]={ "Standard", "FM (4-operator)", "Game Boy", @@ -51,7 +51,8 @@ const char* insTypes[24]={ "POKEY", "PC Beeper", "WonderSwan", - "Atari Lynx" + "Atari Lynx", + "X1-010" }; const char* ssgEnvTypes[8]={ @@ -148,6 +149,10 @@ const char* mikeyFeedbackBits[11] = { "0", "1", "2", "3", "4", "5", "7", "10", "11", "int", NULL }; +const char* x1_010EnvBits[6]={ + "enable", "oneshot", "split L/R", "Hinv", "Vinv", NULL +}; + const char* oneBit[2]={ "on", NULL }; @@ -783,9 +788,9 @@ void FurnaceGUI::drawInsEdit() { } else { DivInstrument* ins=e->song.ins[curIns]; ImGui::InputText("Name",&ins->name); - if (ins->type<0 || ins->type>23) ins->type=DIV_INS_FM; + if (ins->type<0 || ins->type>24) ins->type=DIV_INS_FM; int insType=ins->type; - if (ImGui::Combo("Type",&insType,insTypes,24,24)) { + if (ImGui::Combo("Type",&insType,insTypes,25,25)) { ins->type=(DivInstrumentType)insType; } @@ -1380,11 +1385,18 @@ void FurnaceGUI::drawInsEdit() { int ex1Max=(ins->type==DIV_INS_AY8930)?8:0; int ex2Max=(ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930)?4:0; + bool ex2Bit=true; if (ins->type==DIV_INS_C64) { ex1Max=4; ex2Max=15; } + if (ins->type==DIV_INS_X1_010) { + dutyMax=0; + ex1Max=5; + ex2Max=63; + ex2Bit=false; + } if (ins->type==DIV_INS_SAA1099) ex1Max=8; if (settings.macroView==0) { // modern view @@ -1409,6 +1421,8 @@ void FurnaceGUI::drawInsEdit() { NORMAL_MACRO(ins->std.ex1Macro,ins->std.ex1MacroLen,ins->std.ex1MacroLoop,ins->std.ex1MacroRel,0,ex1Max,"ex1","Filter Mode",64,ins->std.ex1MacroOpen,true,filtModeBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false); } else if (ins->type==DIV_INS_SAA1099) { NORMAL_MACRO(ins->std.ex1Macro,ins->std.ex1MacroLen,ins->std.ex1MacroLoop,ins->std.ex1MacroRel,0,ex1Max,"ex1","Envelope",160,ins->std.ex1MacroOpen,true,saaEnvBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false); + } else if (ins->type==DIV_INS_X1_010) { + NORMAL_MACRO(ins->std.ex1Macro,ins->std.ex1MacroLen,ins->std.ex1MacroLoop,ins->std.ex1MacroRel,0,ex1Max,"ex1","Envelope Mode",160,ins->std.ex1MacroOpen,true,x1_010EnvBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false); } else { NORMAL_MACRO(ins->std.ex1Macro,ins->std.ex1MacroLen,ins->std.ex1MacroLoop,ins->std.ex1MacroRel,0,ex1Max,"ex1","Duty",160,ins->std.ex1MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false); } @@ -1417,13 +1431,13 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_C64) { NORMAL_MACRO(ins->std.ex2Macro,ins->std.ex2MacroLen,ins->std.ex2MacroLoop,ins->std.ex2MacroRel,0,ex2Max,"ex2","Resonance",64,ins->std.ex2MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[5],0,ex2Max,NULL,false); } else { - NORMAL_MACRO(ins->std.ex2Macro,ins->std.ex2MacroLen,ins->std.ex2MacroLoop,ins->std.ex2MacroRel,0,ex2Max,"ex2","Envelope",64,ins->std.ex2MacroOpen,true,ayEnvBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[5],0,ex2Max,NULL,false); + NORMAL_MACRO(ins->std.ex2Macro,ins->std.ex2MacroLen,ins->std.ex2MacroLoop,ins->std.ex2MacroRel,0,ex2Max,"ex2","Envelope",ex2Bit?64:160,ins->std.ex2MacroOpen,ex2Bit,ayEnvBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[5],0,ex2Max,NULL,false); } } if (ins->type==DIV_INS_C64) { NORMAL_MACRO(ins->std.ex3Macro,ins->std.ex3MacroLen,ins->std.ex3MacroLoop,ins->std.ex3MacroRel,0,2,"ex3","Special",32,ins->std.ex3MacroOpen,true,c64SpecialBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[6],0,2,NULL,false); } - if (ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930) { + if (ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_X1_010) { NORMAL_MACRO(ins->std.ex3Macro,ins->std.ex3MacroLen,ins->std.ex3MacroLoop,ins->std.ex3MacroRel,0,15,"ex3","AutoEnv Num",96,ins->std.ex3MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[6],0,15,NULL,false); NORMAL_MACRO(ins->std.algMacro,ins->std.algMacroLen,ins->std.algMacroLoop,ins->std.algMacroRel,0,15,"alg","AutoEnv Den",96,ins->std.algMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[7],0,15,NULL,false); } diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 3f0e532db..f34f67ac5 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -492,6 +492,7 @@ void FurnaceGUI::drawSettings() { UI_COLOR_CONFIG(GUI_COLOR_INSTR_BEEPER,"PC Beeper"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_SWAN,"WonderSwan"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_MIKEY,"Lynx"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_X1_010,"X1-010"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_UNKNOWN,"Other/Unknown"); ImGui::TreePop(); } @@ -1159,6 +1160,7 @@ void FurnaceGUI::commitSettings() { PUT_UI_COLOR(GUI_COLOR_INSTR_BEEPER); PUT_UI_COLOR(GUI_COLOR_INSTR_SWAN); PUT_UI_COLOR(GUI_COLOR_INSTR_MIKEY); + PUT_UI_COLOR(GUI_COLOR_INSTR_X1_010); PUT_UI_COLOR(GUI_COLOR_INSTR_UNKNOWN); PUT_UI_COLOR(GUI_COLOR_CHANNEL_FM); PUT_UI_COLOR(GUI_COLOR_CHANNEL_PULSE); From d0c32a56be05446915c1c24a3e635c7ebd67ddde Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 7 Mar 2022 03:06:01 +0900 Subject: [PATCH 111/637] Fix panning --- src/engine/platform/x1_010.cpp | 88 ++++++++++++++++------------------ src/engine/playback.cpp | 2 + 2 files changed, 42 insertions(+), 48 deletions(-) diff --git a/src/engine/platform/x1_010.cpp b/src/engine/platform/x1_010.cpp index 6d072d730..7119a67f2 100644 --- a/src/engine/platform/x1_010.cpp +++ b/src/engine/platform/x1_010.cpp @@ -306,7 +306,7 @@ void DivPlatformX1_010::updateEnvelope(int ch) { for (int i=0; i<128; i++) { if (wt->max<1 || wt->len<1) { envFill(ch,i); - } else if (stereo&&(chan[ch].env.flag.envHinv||chan[ch].env.flag.envSplit||chan[ch].env.flag.envVinv)) { // Stereo config + } else if (chan[ch].env.flag.envHinv||chan[ch].env.flag.envSplit||chan[ch].env.flag.envVinv) { // Stereo config int la = i, ra = i; int lo, ro; if (chan[ch].env.flag.envHinv) { ra = 127-i; } // horizontal invert right envelope @@ -386,29 +386,27 @@ void DivPlatformX1_010::tick() { refreshControl(i); } } - if (stereo) { - bool nextSplit=(chan[i].std.ex1&4); - if (nextSplit!=(chan[i].env.flag.envSplit)) { - chan[i].env.flag.envSplit=nextSplit; - if (!isMuted[i] && !chan[i].pcm) { - chan[i].envChanged=true; - } + bool nextSplit=(chan[i].std.ex1&4); + if (nextSplit!=(chan[i].env.flag.envSplit)) { + chan[i].env.flag.envSplit=nextSplit; + if (!isMuted[i] && !chan[i].pcm) { + chan[i].envChanged=true; } - bool nextHinv=(chan[i].std.ex1&8); - if (nextHinv!=(chan[i].env.flag.envHinv)) { - chan[i].env.flag.envHinv=nextHinv; - if (!isMuted[i] && !chan[i].pcm) { - chan[i].envChanged=true; - } + } + bool nextHinv=(chan[i].std.ex1&8); + if (nextHinv!=(chan[i].env.flag.envHinv)) { + chan[i].env.flag.envHinv=nextHinv; + if (!isMuted[i] && !chan[i].pcm) { + chan[i].envChanged=true; } - bool nextVinv=(chan[i].std.ex1&16); - if (nextVinv!=(chan[i].env.flag.envVinv)) { - chan[i].env.flag.envVinv=nextVinv; - if (!isMuted[i] && !chan[i].pcm) { - chan[i].envChanged=true; - } + } + bool nextVinv=(chan[i].std.ex1&16); + if (nextVinv!=(chan[i].env.flag.envVinv)) { + chan[i].env.flag.envVinv=nextVinv; + if (!isMuted[i] && !chan[i].pcm) { + chan[i].envChanged=true; } - } + } } if (chan[i].std.hadEx2) { if (chan[i].env.shape!=chan[i].std.ex2) { @@ -437,12 +435,8 @@ void DivPlatformX1_010::tick() { } if (chan[i].envChanged) { if (!isMuted[i]) { - if (stereo) { - chan[i].lvol=((chan[i].outVol&0xf)*((chan[i].pan>>4)&0xf))/15; - chan[i].rvol=((chan[i].outVol&0xf)*((chan[i].pan>>0)&0xf))/15; - } else { - chan[i].lvol=chan[i].rvol=chan[i].outVol; - } + chan[i].lvol=((chan[i].outVol&0xf)*((chan[i].pan>>4)&0xf))/15; + chan[i].rvol=((chan[i].outVol&0xf)*((chan[i].pan>>0)&0xf))/15; } updateEnvelope(i); chan[i].envChanged=false; @@ -654,7 +648,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { } break; case DIV_CMD_PANNING: { - if (stereo&&(chan[c.chan].pan!=c.value)) { + if (chan[c.chan].pan!=c.value) { chan[c.chan].pan=c.value; if (!isMuted[c.chan]) { chan[c.chan].envChanged=true; @@ -700,29 +694,27 @@ int DivPlatformX1_010::dispatch(DivCommand c) { refreshControl(c.chan); } } - if (stereo) { - bool nextSplit=c.value&4; - if (nextSplit!=(chan[c.chan].env.flag.envSplit)) { - chan[c.chan].env.flag.envSplit=nextSplit; - if (!isMuted[c.chan] && !chan[c.chan].pcm) { - chan[c.chan].envChanged=true; - } + bool nextSplit=c.value&4; + if (nextSplit!=(chan[c.chan].env.flag.envSplit)) { + chan[c.chan].env.flag.envSplit=nextSplit; + if (!isMuted[c.chan] && !chan[c.chan].pcm) { + chan[c.chan].envChanged=true; } - bool nextHinv=c.value&8; - if (nextHinv!=(chan[c.chan].env.flag.envHinv)) { - chan[c.chan].env.flag.envHinv=nextHinv; - if (!isMuted[c.chan] && !chan[c.chan].pcm) { - chan[c.chan].envChanged=true; - } + } + bool nextHinv=c.value&8; + if (nextHinv!=(chan[c.chan].env.flag.envHinv)) { + chan[c.chan].env.flag.envHinv=nextHinv; + if (!isMuted[c.chan] && !chan[c.chan].pcm) { + chan[c.chan].envChanged=true; } - bool nextVinv=c.value&16; - if (nextVinv!=(chan[c.chan].env.flag.envVinv)) { - chan[c.chan].env.flag.envVinv=nextVinv; - if (!isMuted[c.chan] && !chan[c.chan].pcm) { - chan[c.chan].envChanged=true; - } + } + bool nextVinv=c.value&16; + if (nextVinv!=(chan[c.chan].env.flag.envVinv)) { + chan[c.chan].env.flag.envVinv=nextVinv; + if (!isMuted[c.chan] && !chan[c.chan].pcm) { + chan[c.chan].envChanged=true; } - } + } break; } case DIV_CMD_X1_010_ENVELOPE_PERIOD: diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 2fba35336..9e4542211 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -260,6 +260,8 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe case 0x17: // PCM enable dispatchCmd(DivCommand(DIV_CMD_SAMPLE_MODE,ch,(effectVal>0))); break; + default: + return false; } break; default: From 666b061c8b0d3bc28be233c9ded6fb95181398c7 Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 7 Mar 2022 03:08:47 +0900 Subject: [PATCH 112/637] Fix year info --- papers/doc/7-systems/x1_010.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/papers/doc/7-systems/x1_010.md b/papers/doc/7-systems/x1_010.md index 952e42316..83587a2b8 100644 --- a/papers/doc/7-systems/x1_010.md +++ b/papers/doc/7-systems/x1_010.md @@ -1,6 +1,6 @@ # Seta/Allumer X1-010 -One of sound chip originally designed by Seta, mainly used at their arcade hardwares at late-88 to early-2000s. +One of sound chip originally designed by Seta, mainly used at their arcade hardwares at late-80s to early-2000s. it has 2 output channel, but no known hardware using this feature for stereo sound. later hardware paired this with external bankswitching logic, but its logic is not emulated now in current furnace revision. Allumer one is just rebadged Seta's thing for use in their arcade hardwares. From 8f31c4b49fd0ec2da40ec81900b36575c5387abf Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Mon, 7 Mar 2022 01:26:59 +0700 Subject: [PATCH 113/637] Fix playback and VGM export --- src/engine/platform/ws.cpp | 103 +++++++++++++++++++------------------ src/engine/safeWriter.cpp | 4 ++ src/engine/vgmOps.cpp | 6 +-- src/gui/gui.cpp | 6 +++ 4 files changed, 66 insertions(+), 53 deletions(-) diff --git a/src/engine/platform/ws.cpp b/src/engine/platform/ws.cpp index df82376a4..4e7c63309 100644 --- a/src/engine/platform/ws.cpp +++ b/src/engine/platform/ws.cpp @@ -187,7 +187,7 @@ void DivPlatformWS::tick() { } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true); - if (i==1 && furnaceDac) { + if (i==1 && pcm && furnaceDac) { double off=1.0; if (dacSample>=0 && dacSamplesong.sampleLen) { DivSample* s=parent->getSample(dacSample); @@ -237,56 +237,58 @@ int DivPlatformWS::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(chan[c.chan].ins); - if (c.chan==1 && ins->type==DIV_INS_AMIGA) { - pcm=true; - } else if (furnaceDac) { - pcm=false; - } - if (c.chan==1 && pcm) { - if (skipRegisterWrites) break; - dacPos=0; - dacPeriod=0; + if (c.chan==1) { if (ins->type==DIV_INS_AMIGA) { - dacSample=ins->amiga.initSample; - if (dacSample<0 || dacSample>=parent->song.sampleLen) { - dacSample=-1; - if (dumpWrites) addWrite(0xffff0002,0); - break; - } else { - if (dumpWrites) { - addWrite(0xffff0000,dacSample); - } - } - if (c.value!=DIV_NOTE_NULL) { - chan[1].baseFreq=NOTE_PERIODIC(c.value); - chan[1].freqChanged=true; - chan[1].note=c.value; - } - chan[1].active=true; - chan[1].keyOn=true; - chan[1].std.init(ins); - furnaceDac=true; - } else { - if (c.value!=DIV_NOTE_NULL) { - chan[1].note=c.value; - } - dacSample=12*sampleBank+chan[1].note%12; - if (dacSample>=parent->song.sampleLen) { - dacSample=-1; - if (dumpWrites) addWrite(0xffff0002,0); - break; - } else { - if (dumpWrites) addWrite(0xffff0000,dacSample); - } - dacRate=parent->getSample(dacSample)->rate; - if (dumpWrites) { - addWrite(0xffff0001,dacRate); - } - chan[1].active=true; - chan[1].keyOn=true; - furnaceDac=false; + pcm=true; + } else if (furnaceDac) { + pcm=false; + } + if (pcm) { + if (skipRegisterWrites) break; + dacPos=0; + dacPeriod=0; + if (ins->type==DIV_INS_AMIGA) { + dacSample=ins->amiga.initSample; + if (dacSample<0 || dacSample>=parent->song.sampleLen) { + dacSample=-1; + if (dumpWrites) addWrite(0xffff0002,0); + break; + } else { + if (dumpWrites) { + addWrite(0xffff0000,dacSample); + } + } + if (c.value!=DIV_NOTE_NULL) { + chan[1].baseFreq=NOTE_PERIODIC(c.value); + chan[1].freqChanged=true; + chan[1].note=c.value; + } + chan[1].active=true; + chan[1].keyOn=true; + chan[1].std.init(ins); + furnaceDac=true; + } else { + if (c.value!=DIV_NOTE_NULL) { + chan[1].note=c.value; + } + dacSample=12*sampleBank+chan[1].note%12; + if (dacSample>=parent->song.sampleLen) { + dacSample=-1; + if (dumpWrites) addWrite(0xffff0002,0); + break; + } else { + if (dumpWrites) addWrite(0xffff0000,dacSample); + } + dacRate=parent->getSample(dacSample)->rate; + if (dumpWrites) { + addWrite(0xffff0001,dacRate); + } + chan[1].active=true; + chan[1].keyOn=true; + furnaceDac=false; + } + break; } - break; } if (c.value!=DIV_NOTE_NULL) { chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); @@ -412,7 +414,7 @@ int DivPlatformWS::dispatch(DivCommand c) { return 15; break; case DIV_ALWAYS_SET_VOLUME: - return 0; + return 1; break; default: break; @@ -456,6 +458,7 @@ void DivPlatformWS::reset() { chan[i]=Channel(); chan[i].vol=15; chan[i].pan=0xff; + rWrite(0x08+i,0xff); } if (dumpWrites) { addWrite(0xffffffff,0); diff --git a/src/engine/safeWriter.cpp b/src/engine/safeWriter.cpp index 0e7a118a2..7b6f0b1e5 100644 --- a/src/engine/safeWriter.cpp +++ b/src/engine/safeWriter.cpp @@ -80,6 +80,10 @@ int SafeWriter::writeC(signed char val) { int SafeWriter::writeS(short val) { return write(&val,2); } +int SafeWriter::writeS_BE(short val) { + unsigned char bytes[2]{(val>>8)&0xff, val&0xff}; + return write(bytes,2); +} int SafeWriter::writeI(int val) { return write(&val,4); diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index caa811481..c3388399f 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -451,7 +451,7 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write } else { // (Wave) RAM write w->writeC(0xc6); - w->writeS(baseAddr2S|(write.addr&0x3f)); + w->writeS_BE(baseAddr2S|(write.addr&0x3f)); w->writeC(write.val&0xff); } break; @@ -1061,9 +1061,9 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) { case DIV_SYSTEM_SWAN: w->writeC(0x90); w->writeC(streamID); - w->writeC(33); + w->writeC(isSecond[i]?0xa1:0x21); w->writeC(0); // port - w->writeC(isSecond[i]?0x89:0x09); // DAC + w->writeC(0x09); // DAC w->writeC(0x91); w->writeC(streamID); diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 4dd7b05ef..6f7740f91 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -6390,6 +6390,12 @@ FurnaceGUI::FurnaceGUI(): 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "WonderSwan", { + DIV_SYSTEM_SWAN, 64, 0, 0, + 0 + } + )); sysCategories.push_back(cat); cat=FurnaceGUISysCategory("Computers"); From 789be838e34e2ce762f9f603f9a2ec058d08c0fe Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 7 Mar 2022 03:43:44 +0900 Subject: [PATCH 114/637] Submodule update --- extern/cam900_vgsound_emu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/cam900_vgsound_emu b/extern/cam900_vgsound_emu index cf8816851..cf4ba11ad 160000 --- a/extern/cam900_vgsound_emu +++ b/extern/cam900_vgsound_emu @@ -1 +1 @@ -Subproject commit cf88168512c1b32bbea7b7dcc1411c4da429a723 +Subproject commit cf4ba11ad786f13c374afb77616d509cb4c751bf From 8da59211961b85dc0af8b677d5c778adc7902ecb Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 7 Mar 2022 03:50:15 +0900 Subject: [PATCH 115/637] step 2 --- src/engine/platform/x1_010.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine/platform/x1_010.cpp b/src/engine/platform/x1_010.cpp index 7119a67f2..74bfdb13a 100644 --- a/src/engine/platform/x1_010.cpp +++ b/src/engine/platform/x1_010.cpp @@ -241,8 +241,8 @@ void DivPlatformX1_010::acquire(short* bufL, short* bufR, size_t start, size_t l for (size_t h=start; htick(); - signed short tempL=x1_010->output(0); - signed short tempR=x1_010->output(1); + signed int tempL=x1_010->output(0); + signed int tempR=x1_010->output(1); if (tempL<-32768) tempL=-32768; if (tempL>32767) tempL=32767; From 6c897722dbf439ed79acdc36812921317373df81 Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 7 Mar 2022 04:03:45 +0900 Subject: [PATCH 116/637] Compile fix Take 3 --- src/engine/playback.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 9e4542211..511e55e17 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -249,6 +249,7 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe } break; } + break; case DIV_SYSTEM_X1_010: switch (effect) { case 0x10: // select waveform From 2bfb84cd1eab23ff1acc473d639b929a5331de6f Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 6 Mar 2022 14:18:18 -0500 Subject: [PATCH 117/637] AY: add 1.10 and 2.10 rates --- src/engine/platform/ay.cpp | 6 ++++++ src/engine/platform/ay8930.cpp | 6 ++++++ src/engine/song.h | 2 ++ src/gui/gui.cpp | 8 ++++++++ 4 files changed, 22 insertions(+) diff --git a/src/engine/platform/ay.cpp b/src/engine/platform/ay.cpp index 2bcada1e8..0777fca8b 100644 --- a/src/engine/platform/ay.cpp +++ b/src/engine/platform/ay.cpp @@ -491,6 +491,12 @@ void DivPlatformAY8910::setFlags(unsigned int flags) { case 8: chipClock=COLOR_PAL*3.0/16.0; break; + case 9: + chipClock=COLOR_PAL/4.0; + break; + case 10: + chipClock=2097152; + break; default: chipClock=COLOR_NTSC/2.0; break; diff --git a/src/engine/platform/ay8930.cpp b/src/engine/platform/ay8930.cpp index 040800af6..d87045a64 100644 --- a/src/engine/platform/ay8930.cpp +++ b/src/engine/platform/ay8930.cpp @@ -552,6 +552,12 @@ void DivPlatformAY8930::setFlags(unsigned int flags) { case 8: chipClock=COLOR_PAL*3.0/16.0; break; + case 9: + chipClock=COLOR_PAL/4.0; + break; + case 10: + chipClock=2097152; + break; default: chipClock=COLOR_NTSC/2.0; break; diff --git a/src/engine/song.h b/src/engine/song.h index d1e255d95..e4bd428b0 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -194,6 +194,8 @@ struct DivSong { // - 6: 0.89MHz (Sunsoft 5B) // - 7: 1.67MHz // - 8: 0.83MHz (Sunsoft 5B on PAL) + // - 9: 1.10MHz (Gamate/VIC-20 PAL) + // - 10: 2.097152MHz (Game Boy) // - bit 4-5: chip type (ignored on AY8930) // - 0: AY-3-8910 or similar // - 1: YM2149 diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index fa8900380..fb3e6fc31 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4818,6 +4818,14 @@ bool FurnaceGUI::loop() { e->setSysFlags(i,(flags&(~15))|8,restart); updateWindowTitle(); } + if (ImGui::RadioButton("1.10MHz (Gamate/VIC-20 PAL)",(flags&15)==9)) { + e->setSysFlags(i,(flags&(~15))|9,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("2^21Hz (Game Boy)",(flags&15)==10)) { + e->setSysFlags(i,(flags&(~15))|10,restart); + updateWindowTitle(); + } if (e->song.system[i]==DIV_SYSTEM_AY8910) { ImGui::Text("Chip type:"); if (ImGui::RadioButton("AY-3-8910",(flags&0x30)==0)) { From 3b8388d90cd3f1308e3d508e7b8c318ef149ab4e Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 6 Mar 2022 14:39:20 -0500 Subject: [PATCH 118/637] YM2151/2610/2612/Game Boy: fix panning - UNTESTED --- src/engine/platform/arcade.cpp | 4 ++-- src/engine/platform/gb.cpp | 1 + src/engine/platform/genesis.cpp | 14 ++++---------- src/engine/platform/genesisext.cpp | 16 +++++----------- src/engine/platform/ym2610.cpp | 14 ++++---------- src/engine/platform/ym2610b.cpp | 14 ++++---------- src/engine/platform/ym2610bext.cpp | 16 +++++----------- src/engine/platform/ym2610ext.cpp | 16 +++++----------- 8 files changed, 30 insertions(+), 65 deletions(-) diff --git a/src/engine/platform/arcade.cpp b/src/engine/platform/arcade.cpp index 15a255178..1af99eb75 100644 --- a/src/engine/platform/arcade.cpp +++ b/src/engine/platform/arcade.cpp @@ -495,8 +495,8 @@ int DivPlatformArcade::dispatch(DivCommand c) { chan[c.chan].ins=c.value; break; case DIV_CMD_PANNING: { - chan[c.chan].chVolL=((c.value>>4)==1); - chan[c.chan].chVolR=((c.value&15)==1); + chan[c.chan].chVolL=((c.value>>4)>0); + chan[c.chan].chVolR=((c.value&15)>0); if (isMuted[c.chan]) { rWrite(chanOffs[c.chan]+ADDR_LR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3)); } else { diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp index f02ea1970..a2b53ef77 100644 --- a/src/engine/platform/gb.cpp +++ b/src/engine/platform/gb.cpp @@ -340,6 +340,7 @@ int DivPlatformGB::dispatch(DivCommand c) { case DIV_CMD_PANNING: { lastPan&=~(0x11<0)|(((c.value>>4)>0)<<4) lastPan|=c.value<0)|(((c.value>>4)>0)<<1); } rWrite(chanOffs[c.chan]+ADDR_LRAF,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|(chan[c.chan].state.fms&7)|((chan[c.chan].state.ams&3)<<4)); break; diff --git a/src/engine/platform/genesisext.cpp b/src/engine/platform/genesisext.cpp index 364505db2..02305a9aa 100644 --- a/src/engine/platform/genesisext.cpp +++ b/src/engine/platform/genesisext.cpp @@ -106,16 +106,10 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) { opChan[ch].ins=c.value; break; case DIV_CMD_PANNING: { - switch (c.value) { - case 0x01: - opChan[ch].pan=1; - break; - case 0x10: - opChan[ch].pan=2; - break; - default: - opChan[ch].pan=3; - break; + if (c.value==0) { + opChan[ch].pan=3; + } else { + opChan[ch].pan=((c.value&15)>0)|(((c.value>>4)>0)<<1); } // TODO: ??? rWrite(chanOffs[2]+0xb4,(opChan[ch].pan<<6)|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4)); @@ -378,4 +372,4 @@ void DivPlatformGenesisExt::quit() { } DivPlatformGenesisExt::~DivPlatformGenesisExt() { -} \ No newline at end of file +} diff --git a/src/engine/platform/ym2610.cpp b/src/engine/platform/ym2610.cpp index dd2447da4..1bd460fa1 100644 --- a/src/engine/platform/ym2610.cpp +++ b/src/engine/platform/ym2610.cpp @@ -892,16 +892,10 @@ int DivPlatformYM2610::dispatch(DivCommand c) { chan[c.chan].ins=c.value; break; case DIV_CMD_PANNING: { - switch (c.value) { - case 0x01: - chan[c.chan].pan=1; - break; - case 0x10: - chan[c.chan].pan=2; - break; - default: - chan[c.chan].pan=3; - break; + if (c.value==0) { + chan[c.chan].pan=3; + } else { + chan[c.chan].pan=((c.value&15)>0)|(((c.value>>4)>0)<<1); } if (c.chan>12) { immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); diff --git a/src/engine/platform/ym2610b.cpp b/src/engine/platform/ym2610b.cpp index 27bb2afbb..a6bcf9ac7 100644 --- a/src/engine/platform/ym2610b.cpp +++ b/src/engine/platform/ym2610b.cpp @@ -955,16 +955,10 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { chan[c.chan].ins=c.value; break; case DIV_CMD_PANNING: { - switch (c.value) { - case 0x01: - chan[c.chan].pan=1; - break; - case 0x10: - chan[c.chan].pan=2; - break; - default: - chan[c.chan].pan=3; - break; + if (c.value==0) { + chan[c.chan].pan=3; + } else { + chan[c.chan].pan=((c.value&15)>0)|(((c.value>>4)>0)<<1); } if (c.chan>14) { immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); diff --git a/src/engine/platform/ym2610bext.cpp b/src/engine/platform/ym2610bext.cpp index f48fe19ab..5b3c3872e 100644 --- a/src/engine/platform/ym2610bext.cpp +++ b/src/engine/platform/ym2610bext.cpp @@ -97,16 +97,10 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) { opChan[ch].ins=c.value; break; case DIV_CMD_PANNING: { - switch (c.value) { - case 0x01: - opChan[ch].pan=1; - break; - case 0x10: - opChan[ch].pan=2; - break; - default: - opChan[ch].pan=3; - break; + if (c.value==0) { + opChan[ch].pan=3; + } else { + opChan[ch].pan=((c.value&15)>0)|(((c.value>>4)>0)<<1); } DivInstrument* ins=parent->getIns(opChan[ch].ins); // TODO: ??? @@ -334,4 +328,4 @@ void DivPlatformYM2610BExt::quit() { } DivPlatformYM2610BExt::~DivPlatformYM2610BExt() { -} \ No newline at end of file +} diff --git a/src/engine/platform/ym2610ext.cpp b/src/engine/platform/ym2610ext.cpp index aff4bbf33..5e633eb2c 100644 --- a/src/engine/platform/ym2610ext.cpp +++ b/src/engine/platform/ym2610ext.cpp @@ -97,16 +97,10 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) { opChan[ch].ins=c.value; break; case DIV_CMD_PANNING: { - switch (c.value) { - case 0x01: - opChan[ch].pan=1; - break; - case 0x10: - opChan[ch].pan=2; - break; - default: - opChan[ch].pan=3; - break; + if (c.value==0) { + opChan[ch].pan=3; + } else { + opChan[ch].pan=((c.value&15)>0)|(((c.value>>4)>0)<<1); } DivInstrument* ins=parent->getIns(opChan[ch].ins); // TODO: ??? @@ -334,4 +328,4 @@ void DivPlatformYM2610Ext::quit() { } DivPlatformYM2610Ext::~DivPlatformYM2610Ext() { -} \ No newline at end of file +} From e10abe08589b3bb4663f759e88f44c00df33fcbf Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 6 Mar 2022 14:41:00 -0500 Subject: [PATCH 119/637] NO --- src/engine/platform/gb.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp index a2b53ef77..942032e32 100644 --- a/src/engine/platform/gb.cpp +++ b/src/engine/platform/gb.cpp @@ -340,7 +340,7 @@ int DivPlatformGB::dispatch(DivCommand c) { case DIV_CMD_PANNING: { lastPan&=~(0x11<0)|(((c.value>>4)>0)<<4) + c.value=((c.value&15)>0)|(((c.value>>4)>0)<<4); lastPan|=c.value< Date: Sun, 6 Mar 2022 17:42:51 -0500 Subject: [PATCH 120/637] Amiga: temporarily disable bus limit simulation --- src/engine/platform/amiga.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine/platform/amiga.cpp b/src/engine/platform/amiga.cpp index 3ee2fd39b..c5face681 100644 --- a/src/engine/platform/amiga.cpp +++ b/src/engine/platform/amiga.cpp @@ -84,7 +84,7 @@ void DivPlatformAmiga::acquire(short* bufL, short* bufR, size_t start, size_t le } else { chan[i].sample=-1; } - if (chan[i].freq<124) { + /*if (chan[i].freq<124) { if (++chan[i].busClock>=512) { unsigned int rAmount=(124-chan[i].freq)*2; if (chan[i].audPos>=rAmount) { @@ -92,7 +92,7 @@ void DivPlatformAmiga::acquire(short* bufL, short* bufR, size_t start, size_t le } chan[i].busClock=0; } - } + }*/ chan[i].audSub+=MAX(114,chan[i].freq); } } From 2f9d1e8c0f3fac7f7b8971e4f1d3a7882ddef2b7 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 6 Mar 2022 18:10:12 -0500 Subject: [PATCH 121/637] i'll finish this later --- src/engine/platform/opl.cpp | 26 ++++++++++++++++++++++++-- src/engine/platform/opl.h | 7 ++++++- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index bb195a471..ebf7dc927 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -30,7 +30,6 @@ // N = invalid #define N 255 -/* const unsigned char slotsOPL2[4][20]={ {0, 1, 2, 6, 7, 8, 12, 13, 14}, // OP1 {3, 4, 5, 9, 10, 11, 15, 16, 17}, // OP2 @@ -45,6 +44,10 @@ const unsigned char slotsOPL2Drums[4][20]={ {N, N, N, N, N, N, N, N, N, N, N} }; +const unsigned char chanMapOPL2[20]={ + 0, 1, 2, 3, 4, 5, 6, 7, 8, N, N, N, N, N, N, N, N, N, N, N +}; + const unsigned char slotsOPL3[4][20]={ {0, 6, 1, 7, 2, 8, 18, 24, 19, 25, 20, 26, 30, 31, 32, 12, 13, 14}, // OP1 {3, 9, 4, 10, 5, 11, 21, 27, 22, 28, 23, 29, 33, 34, 35, 15, 16, 17}, // OP2 @@ -58,7 +61,10 @@ const unsigned char slotsOPL3Drums[4][20]={ {6, N, 7, N, 8, N, 24, N, 25, N, 26, N, N, N, N, N, N, N, N, N}, // OP3 {9, N, 10, N, 11, N, 27, N, 28, N, 29, N, N, N, N, N, N, N, N, N} // OP4 }; -*/ + +const unsigned char chanMapOPL3[20]={ + 0, 3, 1, 4, 2, 5, 9, 12, 10, 13, 11, 14, 15, 16, 17, 6, 7, 8, N, N +}; #undef N @@ -773,6 +779,22 @@ void DivPlatformOPL::setYMFM(bool use) { useYMFM=use; } +void DivPlatformOPL::setOPLType(int type) { + switch (type) { + case 1: case 2: + slotsNonDrums=(const unsigned char**)slotsOPL2; + slotsDrums=(const unsigned char**)slotsOPL2Drums; + chanMap=chanMapOPL2; + break; + case 3: + slotsNonDrums=(const unsigned char**)slotsOPL3; + slotsDrums=(const unsigned char**)slotsOPL3Drums; + chanMap=chanMapOPL3; + break; + } + oplType=type; +} + void DivPlatformOPL::setFlags(unsigned int flags) { /* if (flags==3) { diff --git a/src/engine/platform/opl.h b/src/engine/platform/opl.h index c42481927..ab6226a45 100644 --- a/src/engine/platform/opl.h +++ b/src/engine/platform/opl.h @@ -64,7 +64,11 @@ class DivPlatformOPL: public DivDispatch { }; std::queue writes; opl3_chip fm; - int delay; + const unsigned char** slotsNonDrums; + const unsigned char** slotsDrums; + const unsigned char** slots; + const unsigned char* chanMap; + int delay, oplType; unsigned char lastBusy; unsigned char regPool[512]; @@ -103,6 +107,7 @@ class DivPlatformOPL: public DivDispatch { void muteChannel(int ch, bool mute); bool isStereo(); void setYMFM(bool use); + void setOPLType(int type); bool keyOffAffectsArp(int ch); bool keyOffAffectsPorta(int ch); void toggleRegisterDump(bool enable); From 72c1116a89a249a9a5541fc57ecb3993523d7d5b Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 6 Mar 2022 22:11:01 -0500 Subject: [PATCH 122/637] PC speaker: correct frequency in real mode damn it --- src/engine/platform/pcspkr.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/engine/platform/pcspkr.cpp b/src/engine/platform/pcspkr.cpp index 5f57f31ae..f485bca8a 100644 --- a/src/engine/platform/pcspkr.cpp +++ b/src/engine/platform/pcspkr.cpp @@ -122,7 +122,11 @@ void DivPlatformPCSpeaker::beepFreq(int freq) { gettimeofday(&ie.time,NULL); ie.type=EV_SND; ie.code=SND_TONE; - ie.value=freq; + if (freq>0) { + ie.value=chipClock/freq; + } else { + ie.value=0; + } if (write(beepFD,&ie,sizeof(struct input_event))<0) { perror("error while writing frequency!"); } else { From 458f8c5881bec3a6eca3a12ac352ed6989afa9b0 Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 7 Mar 2022 12:21:51 +0900 Subject: [PATCH 123/637] Fix instrument allocation --- src/engine/instrument.h | 4 +++- src/gui/insEdit.cpp | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/engine/instrument.h b/src/engine/instrument.h index da7356f1e..abee4223a 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -48,7 +48,9 @@ enum DivInstrumentType { DIV_INS_BEEPER=21, DIV_INS_SWAN=22, DIV_INS_MIKEY=23, - DIV_INS_X1_010=24, + DIV_INS_VERA=24, + DIV_INS_X1_010=25, + DIV_INS_MAX, }; // FM operator structure: diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 29085c561..35059804d 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -788,9 +788,9 @@ void FurnaceGUI::drawInsEdit() { } else { DivInstrument* ins=e->song.ins[curIns]; ImGui::InputText("Name",&ins->name); - if (ins->type<0 || ins->type>24) ins->type=DIV_INS_FM; + if (ins->type<0 || ins->type>=DIV_INS_MAX) ins->type=DIV_INS_FM; int insType=ins->type; - if (ImGui::Combo("Type",&insType,insTypes,25,25)) { + if (ImGui::Combo("Type",&insType,insTypes,DIV_INS_MAX,DIV_INS_MAX)) { ins->type=(DivInstrumentType)insType; } From 9333b5bd515320795cae7ba496e1a51d9b98d22d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 6 Mar 2022 22:36:13 -0500 Subject: [PATCH 124/637] prepare for X1-010 --- papers/format.md | 1 + 1 file changed, 1 insertion(+) diff --git a/papers/format.md b/papers/format.md index 4df49ed02..ff068d830 100644 --- a/papers/format.md +++ b/papers/format.md @@ -174,6 +174,7 @@ size | description | - 0xaa: MSM6295 - 4 channels | - 0xab: MSM6258 - 1 channel | - 0xac: Commander X16 (VERA) - 17 channels + | - 0xb0: Seta/Allumer X1-010 - 16 channels | - 0xde: YM2610B extended - 19 channels | - 0xe0: QSound - 19 channels | - (compound!) means that the system is composed of two or more chips, From 177c409e19c902c99567e76b07d16677ca0f5b37 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 6 Mar 2022 22:36:32 -0500 Subject: [PATCH 125/637] add more notes when working with new systems --- src/engine/dispatch.h | 6 ++++++ src/engine/instrument.h | 3 +++ src/engine/playback.cpp | 1 + 3 files changed, 10 insertions(+) diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index 300b0033b..e697cccbe 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -29,6 +29,12 @@ #define addWrite(a,v) regWrites.push_back(DivRegWrite(a,v)); +// HOW TO ADD A NEW COMMAND: +// add it to this enum. then see playback.cpp. +// there is a const char* cmdName[] array, which contains the command +// names as strings for the commands (and other debug stuff). +// +// if you miss it, the program will crash or misbehave at some point. enum DivDispatchCmds { DIV_CMD_NOTE_ON=0, DIV_CMD_NOTE_OFF, diff --git a/src/engine/instrument.h b/src/engine/instrument.h index 27ce2a4ce..a0d37523e 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -23,6 +23,9 @@ #include "dataErrors.h" #include "../ta-utils.h" +// NOTICE! +// before adding new instrument types to this struct, please ask me first. +// absolutely zero support granted to conflicting formats. enum DivInstrumentType { DIV_INS_STD=0, DIV_INS_FM=1, diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index f0f43dc1a..7114a7c2a 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -42,6 +42,7 @@ const char* notes[12]={ "C-", "C#", "D-", "D#", "E-", "F-", "F#", "G-", "G#", "A-", "A#", "B-" }; +// update this when adding new commands. const char* cmdName[DIV_CMD_MAX]={ "NOTE_ON", "NOTE_OFF", From 36647ac81d88d21a48d866ca087c6b96692c50a2 Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 7 Mar 2022 13:03:39 +0900 Subject: [PATCH 126/637] Update submodule --- .gitmodules | 1 + extern/cam900_vgsound_emu | 2 +- src/engine/platform/x1_010.h | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.gitmodules b/.gitmodules index 075769005..8c4c6b0dd 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,6 +22,7 @@ [submodule "extern/cam900_vgsound_emu"] path = extern/cam900_vgsound_emu url = https://github.com/cam900/vgsound_emu + branch = main [submodule "extern/Nuked-OPL3"] path = extern/Nuked-OPL3 url = https://github.com/nukeykt/Nuked-OPL3.git diff --git a/extern/cam900_vgsound_emu b/extern/cam900_vgsound_emu index cf4ba11ad..3f8b5c5c6 160000 --- a/extern/cam900_vgsound_emu +++ b/extern/cam900_vgsound_emu @@ -1 +1 @@ -Subproject commit cf4ba11ad786f13c374afb77616d509cb4c751bf +Subproject commit 3f8b5c5c6b996588afec7e4ce7469abccf16ecbe diff --git a/src/engine/platform/x1_010.h b/src/engine/platform/x1_010.h index 27bf4f4a6..fd00fd1c9 100644 --- a/src/engine/platform/x1_010.h +++ b/src/engine/platform/x1_010.h @@ -26,11 +26,11 @@ #include "../macroInt.h" #include "../../../extern/cam900_vgsound_emu/x1_010/x1_010.hpp" -class DivX1_010Interface: public x1_010_intf { +class DivX1_010Interface: public vgsound_emu_mem_intf { public: DivEngine* parent; int sampleBank; - virtual u8 read_rom(uint32_t address) override { + virtual u8 read_byte(u32 address) override { if (parent->x1_010Mem==NULL) return 0; return parent->x1_010Mem[address & 0xfffff]; } From 55934bc044f17b03cea4d97c036461b1174e32d5 Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 7 Mar 2022 13:09:25 +0900 Subject: [PATCH 127/637] Fix crash --- src/gui/insEdit.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 35059804d..a66a7f585 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -27,7 +27,7 @@ #include #include "plot_nolerp.h" -const char* insTypes[25]={ +const char* insTypes[DIV_INS_MAX]={ "Standard", "FM (4-operator)", "Game Boy", @@ -52,6 +52,7 @@ const char* insTypes[25]={ "PC Beeper", "WonderSwan", "Atari Lynx", + "VERA", "X1-010" }; From bc26fbaa3d1ac7d066f4fc7ab12a38fe300febef Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 7 Mar 2022 13:34:13 +0900 Subject: [PATCH 128/637] Add cmdName for X1-010 commands --- src/engine/playback.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index acc4e7607..665409d67 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -117,6 +117,13 @@ const char* cmdName[DIV_CMD_MAX]={ "QSOUND_ECHO_DELAY", "QSOUND_ECHO_LEVEL", + "X1_010_ENVELOPE_SHAPE", + "X1_010_ENVELOPE_ENABLE", + "X1_010_ENVELOPE_MODE", + "X1_010_ENVELOPE_PERIOD", + "X1_010_ENVELOPE_SLIDE", + "X1_010_AUTO_ENVELOPE", + "ALWAYS_SET_VOLUME" }; From 93d160da5e6e5288cdfa749ffef945211985a341 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 7 Mar 2022 00:24:50 -0500 Subject: [PATCH 129/637] OPLL: but it doesn't have LFOOOOOO --- src/engine/platform/opll.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/engine/platform/opll.cpp b/src/engine/platform/opll.cpp index 5159b3ea1..fd1d61d62 100644 --- a/src/engine/platform/opll.cpp +++ b/src/engine/platform/opll.cpp @@ -29,9 +29,6 @@ const char* DivPlatformOPLL::getEffectName(unsigned char effect) { switch (effect) { - case 0x10: - return "10xy: Setup LFO (x: enable; y: speed)"; - break; case 0x11: return "11xx: Set feedback (0 to 7)"; break; From 8f957baa3e07cbe593debfd9f532fbd96f3b027d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 7 Mar 2022 01:48:48 -0500 Subject: [PATCH 130/637] dispatch: add function to notify playback stopped for the PC Speaker real driver --- src/engine/dispatch.h | 5 +++++ src/engine/engine.cpp | 3 +++ src/engine/platform/abstract.cpp | 4 ++++ src/engine/platform/pcspkr.cpp | 6 ++++++ src/engine/platform/pcspkr.h | 1 + 5 files changed, 19 insertions(+) diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index e697cccbe..3c39b03b4 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -316,6 +316,11 @@ class DivDispatch { */ virtual void notifyInsDeletion(void* ins); + /** + * notify that playback stopped. + */ + virtual void notifyPlaybackStop(); + /** * force-retrigger instruments. */ diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index a8b023d7d..75da62e0d 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -828,6 +828,9 @@ void DivEngine::stop() { sPreview.sample=-1; sPreview.wave=-1; sPreview.pos=0; + for (int i=0; inotifyPlaybackStop(); + } isBusy.unlock(); } diff --git a/src/engine/platform/abstract.cpp b/src/engine/platform/abstract.cpp index f61bc5be0..7b6115aef 100644 --- a/src/engine/platform/abstract.cpp +++ b/src/engine/platform/abstract.cpp @@ -97,6 +97,10 @@ void DivDispatch::notifyInsDeletion(void* ins) { } +void DivDispatch::notifyPlaybackStop() { + +} + void DivDispatch::forceIns() { } diff --git a/src/engine/platform/pcspkr.cpp b/src/engine/platform/pcspkr.cpp index f485bca8a..d8c34681e 100644 --- a/src/engine/platform/pcspkr.cpp +++ b/src/engine/platform/pcspkr.cpp @@ -344,6 +344,8 @@ void DivPlatformPCSpeaker::reset() { } #endif beepFreq(0); + } else { + beepFreq(0); } memset(regPool,0,2); @@ -365,6 +367,10 @@ void DivPlatformPCSpeaker::notifyInsDeletion(void* ins) { } } +void DivPlatformPCSpeaker::notifyPlaybackStop() { + beepFreq(0); +} + void DivPlatformPCSpeaker::poke(unsigned int addr, unsigned short val) { // ??? } diff --git a/src/engine/platform/pcspkr.h b/src/engine/platform/pcspkr.h index 82e0f461d..17dedf247 100644 --- a/src/engine/platform/pcspkr.h +++ b/src/engine/platform/pcspkr.h @@ -82,6 +82,7 @@ class DivPlatformPCSpeaker: public DivDispatch { bool keyOffAffectsArp(int ch); void setFlags(unsigned int flags); void notifyInsDeletion(void* ins); + void notifyPlaybackStop(); void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); const char** getRegisterSheet(); From 165a8a4361e8dad6f0387ecf3139ae78b3eb348a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 7 Mar 2022 01:54:28 -0500 Subject: [PATCH 131/637] PC speaker: register view one register :p --- src/engine/platform/pcspkr.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/engine/platform/pcspkr.cpp b/src/engine/platform/pcspkr.cpp index d8c34681e..1ff8ae0c8 100644 --- a/src/engine/platform/pcspkr.cpp +++ b/src/engine/platform/pcspkr.cpp @@ -310,6 +310,13 @@ void* DivPlatformPCSpeaker::getChanState(int ch) { } unsigned char* DivPlatformPCSpeaker::getRegisterPool() { + if (on) { + regPool[0]=freq; + regPool[1]=freq>>8; + } else { + regPool[0]=0; + regPool[1]=0; + } return regPool; } From 119d815a165528c451d2995344d1b23ebf5855a5 Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Mon, 7 Mar 2022 15:44:15 +0700 Subject: [PATCH 132/637] No need to de-duplicate writes here --- src/engine/platform/ws.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/engine/platform/ws.cpp b/src/engine/platform/ws.cpp index 4e7c63309..1a842a227 100644 --- a/src/engine/platform/ws.cpp +++ b/src/engine/platform/ws.cpp @@ -97,11 +97,8 @@ void DivPlatformWS::acquire(short* bufL, short* bufR, size_t start, size_t len) // the rest while (!writes.empty()) { QueuedWrite w=writes.front(); - if (regPool[w.addr]!=w.val) { - if (w.addr<0x40) ws->SoundWrite(w.addr|0x80,w.val); - else ws->RAMWrite(w.addr&0x3f,w.val); - regPool[w.addr]=w.val; - } + if (w.addr<0x40) ws->SoundWrite(w.addr|0x80,w.val); + else ws->RAMWrite(w.addr&0x3f,w.val); writes.pop(); } int16_t samp[2]{0, 0}; From 2453426d031ac04b4b3560b1b07b4542a009e9e0 Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Sat, 5 Mar 2022 16:02:01 +0700 Subject: [PATCH 133/637] Turn second chip checks into variables in vgmOps --- src/engine/vgmOps.cpp | 146 ++++++++++++++++++++++-------------------- 1 file changed, 75 insertions(+), 71 deletions(-) diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index 97f427372..bef7c5359 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -25,119 +25,123 @@ constexpr int MASTER_CLOCK_PREC=(sizeof(void*)==8)?8:0; void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write, int streamOff, double* loopTimer, double* loopFreq, int* loopSample, bool isSecond) { + unsigned char baseAddr1=isSecond?0xa0:0x50; + unsigned char baseAddr2=isSecond?0x80:0; + unsigned short baseAddr2S=isSecond?0x8000:0; + unsigned char smsAddr=isSecond?0x30:0x50; if (write.addr==0xffffffff) { // Furnace fake reset switch (sys) { case DIV_SYSTEM_YM2612: case DIV_SYSTEM_YM2612_EXT: for (int i=0; i<3; i++) { // set SL and RR to highest - w->writeC(isSecond?0xa2:0x52); + w->writeC(2|baseAddr1); w->writeC(0x80+i); w->writeC(0xff); - w->writeC(isSecond?0xa2:0x52); + w->writeC(2|baseAddr1); w->writeC(0x84+i); w->writeC(0xff); - w->writeC(isSecond?0xa2:0x52); + w->writeC(2|baseAddr1); w->writeC(0x88+i); w->writeC(0xff); - w->writeC(isSecond?0xa2:0x52); + w->writeC(2|baseAddr1); w->writeC(0x8c+i); w->writeC(0xff); - w->writeC(isSecond?0xa3:0x53); + w->writeC(3|baseAddr1); w->writeC(0x80+i); w->writeC(0xff); - w->writeC(isSecond?0xa3:0x53); + w->writeC(3|baseAddr1); w->writeC(0x84+i); w->writeC(0xff); - w->writeC(isSecond?0xa3:0x53); + w->writeC(3|baseAddr1); w->writeC(0x88+i); w->writeC(0xff); - w->writeC(isSecond?0xa3:0x53); + w->writeC(3|baseAddr1); w->writeC(0x8c+i); w->writeC(0xff); } for (int i=0; i<3; i++) { // note off - w->writeC(isSecond?0xa2:0x52); + w->writeC(2|baseAddr1); w->writeC(0x28); w->writeC(i); - w->writeC(isSecond?0xa2:0x52); + w->writeC(2|baseAddr1); w->writeC(0x28); w->writeC(4+i); } - w->writeC(isSecond?0xa2:0x52); // disable DAC + w->writeC(2|baseAddr1); // disable DAC w->writeC(0x2b); w->writeC(0); break; case DIV_SYSTEM_SMS: for (int i=0; i<4; i++) { - w->writeC(isSecond?0x30:0x50); + w->writeC(smsAddr); w->writeC(0x90|(i<<5)|15); } break; case DIV_SYSTEM_GB: // square 1 w->writeC(0xb3); - w->writeC(isSecond?0x82:2); + w->writeC(2|baseAddr2); w->writeC(0); w->writeC(0xb3); - w->writeC(isSecond?0x84:4); + w->writeC(4|baseAddr2); w->writeC(0x80); // square 2 w->writeC(0xb3); - w->writeC(isSecond?0x87:7); + w->writeC(7|baseAddr2); w->writeC(0); w->writeC(0xb3); - w->writeC(isSecond?0x89:9); + w->writeC(9|baseAddr2); w->writeC(0x80); // wave w->writeC(0xb3); - w->writeC(isSecond?0x8c:0x0c); + w->writeC(0x0c|baseAddr2); w->writeC(0); w->writeC(0xb3); - w->writeC(isSecond?0x8e:0x0e); + w->writeC(0x0e|baseAddr2); w->writeC(0x80); // noise w->writeC(0xb3); - w->writeC(isSecond?0x91:0x11); + w->writeC(0x11|baseAddr2); w->writeC(0); w->writeC(0xb3); - w->writeC(isSecond?0x93:0x13); + w->writeC(0x13|baseAddr2); w->writeC(0x80); break; case DIV_SYSTEM_PCE: for (int i=0; i<6; i++) { w->writeC(0xb9); - w->writeC(isSecond?0x80:0); + w->writeC(0|baseAddr2); w->writeC(i); w->writeC(0xb9); - w->writeC(isSecond?0x84:4); + w->writeC(4|baseAddr2); w->writeC(0); } break; case DIV_SYSTEM_NES: w->writeC(0xb4); - w->writeC(isSecond?0x95:0x15); + w->writeC(0x15|baseAddr2); w->writeC(0); break; case DIV_SYSTEM_YM2151: for (int i=0; i<8; i++) { - w->writeC(isSecond?0xa4:0x54); + w->writeC(4|baseAddr1); w->writeC(0xe0+i); w->writeC(0xff); - w->writeC(isSecond?0xa4:0x54); + w->writeC(4|baseAddr1); w->writeC(0xe8+i); w->writeC(0xff); - w->writeC(isSecond?0xa4:0x54); + w->writeC(4|baseAddr1); w->writeC(0xf0+i); w->writeC(0xff); - w->writeC(isSecond?0xa4:0x54); + w->writeC(4|baseAddr1); w->writeC(0xf8+i); w->writeC(0xff); - w->writeC(isSecond?0xa4:0x54); + w->writeC(4|baseAddr1); w->writeC(0x08); w->writeC(i); } @@ -146,7 +150,7 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write case DIV_SYSTEM_SEGAPCM_COMPAT: for (int i=0; i<16; i++) { w->writeC(0xc0); - w->writeS((isSecond?0x8086:0x86)+(i<<3)); + w->writeS((0x86|baseAddr2S)+(i<<3)); w->writeC(3); } break; @@ -157,60 +161,60 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write case DIV_SYSTEM_YM2610_FULL_EXT: case DIV_SYSTEM_YM2610B_EXT: for (int i=0; i<2; i++) { // set SL and RR to highest - w->writeC(isSecond?0xa8:0x58); + w->writeC(8|baseAddr1); w->writeC(0x81+i); w->writeC(0xff); - w->writeC(isSecond?0xa8:0x58); + w->writeC(8|baseAddr1); w->writeC(0x85+i); w->writeC(0xff); - w->writeC(isSecond?0xa8:0x58); + w->writeC(8|baseAddr1); w->writeC(0x89+i); w->writeC(0xff); - w->writeC(isSecond?0xa8:0x58); + w->writeC(8|baseAddr1); w->writeC(0x8d+i); w->writeC(0xff); - w->writeC(isSecond?0xa9:0x59); + w->writeC(9|baseAddr1); w->writeC(0x81+i); w->writeC(0xff); - w->writeC(isSecond?0xa9:0x59); + w->writeC(9|baseAddr1); w->writeC(0x85+i); w->writeC(0xff); - w->writeC(isSecond?0xa9:0x59); + w->writeC(9|baseAddr1); w->writeC(0x89+i); w->writeC(0xff); - w->writeC(isSecond?0xa9:0x59); + w->writeC(9|baseAddr1); w->writeC(0x8d+i); w->writeC(0xff); } for (int i=0; i<2; i++) { // note off - w->writeC(isSecond?0xa8:0x58); + w->writeC(8|baseAddr1); w->writeC(0x28); w->writeC(1+i); - w->writeC(isSecond?0xa8:0x58); + w->writeC(8|baseAddr1); w->writeC(0x28); w->writeC(5+i); } // reset AY - w->writeC(isSecond?0xa8:0x58); + w->writeC(8|baseAddr1); w->writeC(7); w->writeC(0x3f); - w->writeC(isSecond?0xa8:0x58); + w->writeC(8|baseAddr1); w->writeC(8); w->writeC(0); - w->writeC(isSecond?0xa8:0x58); + w->writeC(8|baseAddr1); w->writeC(9); w->writeC(0); - w->writeC(isSecond?0xa8:0x58); + w->writeC(8|baseAddr1); w->writeC(10); w->writeC(0); // reset sample - w->writeC(isSecond?0xa9:0x59); + w->writeC(9|baseAddr1); w->writeC(0); w->writeC(0xbf); break; @@ -218,56 +222,56 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write case DIV_SYSTEM_OPLL_DRUMS: case DIV_SYSTEM_VRC7: for (int i=0; i<9; i++) { - w->writeC(isSecond?0xa1:0x51); + w->writeC(1|baseAddr1); w->writeC(0x20+i); w->writeC(0); - w->writeC(isSecond?0xa1:0x51); + w->writeC(1|baseAddr1); w->writeC(0x30+i); w->writeC(0); - w->writeC(isSecond?0xa1:0x51); + w->writeC(1|baseAddr1); w->writeC(0x10+i); w->writeC(0); } break; case DIV_SYSTEM_AY8910: w->writeC(0xa0); - w->writeC(isSecond?0x87:7); + w->writeC(7|baseAddr2); w->writeC(0x3f); w->writeC(0xa0); - w->writeC(isSecond?0x88:8); + w->writeC(8|baseAddr2); w->writeC(0); w->writeC(0xa0); - w->writeC(isSecond?0x89:9); + w->writeC(9|baseAddr2); w->writeC(0); w->writeC(0xa0); - w->writeC(isSecond?0x8a:10); + w->writeC(10|baseAddr2); w->writeC(0); break; case DIV_SYSTEM_AY8930: w->writeC(0xa0); - w->writeC(isSecond?0x8d:0x0d); + w->writeC(0x0d|baseAddr2); w->writeC(0); w->writeC(0xa0); - w->writeC(isSecond?0x8d:0x0d); + w->writeC(0x0d|baseAddr2); w->writeC(0xa0); break; case DIV_SYSTEM_SAA1099: w->writeC(0xbd); - w->writeC(isSecond?0x9c:0x1c); + w->writeC(0x1c|baseAddr2); w->writeC(0x02); w->writeC(0xbd); - w->writeC(isSecond?0x94:0x14); + w->writeC(0x14|baseAddr2); w->writeC(0); w->writeC(0xbd); - w->writeC(isSecond?0x95:0x15); + w->writeC(0x15|baseAddr2); w->writeC(0); for (int i=0; i<6; i++) { w->writeC(0xbd); - w->writeC((isSecond?0x80:0)+i); + w->writeC((0|baseAddr2)+i); w->writeC(0); } break; @@ -346,49 +350,49 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write case DIV_SYSTEM_YM2612_EXT: switch (write.addr>>8) { case 0: // port 0 - w->writeC(isSecond?0xa2:0x52); + w->writeC(2|baseAddr1); w->writeC(write.addr&0xff); w->writeC(write.val); break; case 1: // port 1 - w->writeC(isSecond?0xa3:0x53); + w->writeC(3|baseAddr1); w->writeC(write.addr&0xff); w->writeC(write.val); break; case 2: // PSG - w->writeC(isSecond?0x30:0x50); + w->writeC(smsAddr); w->writeC(write.val); break; } break; case DIV_SYSTEM_SMS: - w->writeC(isSecond?0x30:0x50); + w->writeC(smsAddr); w->writeC(write.val); break; case DIV_SYSTEM_GB: w->writeC(0xb3); - w->writeC((isSecond?0x80:0)|((write.addr-16)&0xff)); + w->writeC(baseAddr2|((write.addr-16)&0xff)); w->writeC(write.val); break; case DIV_SYSTEM_PCE: w->writeC(0xb9); - w->writeC((isSecond?0x80:0)|(write.addr&0xff)); + w->writeC(baseAddr2|(write.addr&0xff)); w->writeC(write.val); break; case DIV_SYSTEM_NES: w->writeC(0xb4); - w->writeC((isSecond?0x80:0)|(write.addr&0xff)); + w->writeC(baseAddr2|(write.addr&0xff)); w->writeC(write.val); break; case DIV_SYSTEM_YM2151: - w->writeC(isSecond?0xa4:0x54); + w->writeC(4|baseAddr1); w->writeC(write.addr&0xff); w->writeC(write.val); break; case DIV_SYSTEM_SEGAPCM: case DIV_SYSTEM_SEGAPCM_COMPAT: w->writeC(0xc0); - w->writeS((isSecond?0x8000:0)|(write.addr&0xffff)); + w->writeS(baseAddr2S|(write.addr&0xffff)); w->writeC(write.val); break; case DIV_SYSTEM_YM2610: @@ -399,12 +403,12 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write case DIV_SYSTEM_YM2610B_EXT: switch (write.addr>>8) { case 0: // port 0 - w->writeC(isSecond?0xa8:0x58); + w->writeC(8|baseAddr1); w->writeC(write.addr&0xff); w->writeC(write.val); break; case 1: // port 1 - w->writeC(isSecond?0xa9:0x59); + w->writeC(9|baseAddr1); w->writeC(write.addr&0xff); w->writeC(write.val); break; @@ -413,19 +417,19 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write case DIV_SYSTEM_OPLL: case DIV_SYSTEM_OPLL_DRUMS: case DIV_SYSTEM_VRC7: - w->writeC(isSecond?0xa1:0x51); + w->writeC(1|baseAddr1); w->writeC(write.addr&0xff); w->writeC(write.val); break; case DIV_SYSTEM_AY8910: case DIV_SYSTEM_AY8930: w->writeC(0xa0); - w->writeC((isSecond?0x80:0)|(write.addr&0xff)); + w->writeC(baseAddr2|(write.addr&0xff)); w->writeC(write.val); break; case DIV_SYSTEM_SAA1099: w->writeC(0xbd); - w->writeC((isSecond?0x80:0)|(write.addr&0xff)); + w->writeC(baseAddr2|(write.addr&0xff)); w->writeC(write.val); break; case DIV_SYSTEM_LYNX: From 840a6fa306d654851ad9530a13b495b53e14d1e2 Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Sun, 6 Mar 2022 23:13:47 +0700 Subject: [PATCH 134/637] Add WonderSwan support --- CMakeLists.txt | 5 +- README.md | 1 + papers/doc/4-instrument/README.md | 3 +- papers/doc/4-instrument/wonderswan.md | 8 + papers/doc/5-wave/README.md | 4 +- papers/doc/7-systems/README.md | 1 + papers/doc/7-systems/wonderswan.md | 20 + src/engine/dispatch.h | 3 + src/engine/dispatchContainer.cpp | 4 + src/engine/platform/sound/ws.cpp | 412 ++++++++++++++++++++ src/engine/platform/sound/ws.h | 82 ++++ src/engine/platform/ws.cpp | 522 ++++++++++++++++++++++++++ src/engine/platform/ws.h | 95 +++++ src/engine/playback.cpp | 21 ++ src/engine/sysDef.cpp | 1 + src/engine/vgmOps.cpp | 45 +++ src/gui/gui.cpp | 2 + src/gui/insEdit.cpp | 8 +- 18 files changed, 1231 insertions(+), 6 deletions(-) create mode 100644 papers/doc/4-instrument/wonderswan.md create mode 100644 papers/doc/7-systems/wonderswan.md create mode 100644 src/engine/platform/sound/ws.cpp create mode 100644 src/engine/platform/sound/ws.h create mode 100644 src/engine/platform/ws.cpp create mode 100644 src/engine/platform/ws.h diff --git a/CMakeLists.txt b/CMakeLists.txt index caf08457f..f31213030 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -265,6 +265,8 @@ src/engine/platform/sound/lynx/Mikey.cpp src/engine/platform/sound/qsound.c +src/engine/platform/sound/ws.cpp + src/engine/platform/ym2610Interface.cpp src/engine/blip_buf.c @@ -306,8 +308,9 @@ src/engine/platform/amiga.cpp src/engine/platform/pcspkr.cpp src/engine/platform/segapcm.cpp src/engine/platform/qsound.cpp -src/engine/platform/dummy.cpp src/engine/platform/lynx.cpp +src/engine/platform/ws.cpp +src/engine/platform/dummy.cpp ) if (WIN32) diff --git a/README.md b/README.md index e1d97726e..540052fd3 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,7 @@ this is a work-in-progress chiptune tracker compatible with DefleMask modules (. - Philips SAA1099 - Amiga - TIA (Atari 2600/7800) + - WonderSwan - multiple sound chips in a single song! - clean-room design (guesswork and ABX tests only, no decompilation involved) - bug/quirk implementation for increased playback accuracy diff --git a/papers/doc/4-instrument/README.md b/papers/doc/4-instrument/README.md index b9d4b8f40..0b9ec0f4c 100644 --- a/papers/doc/4-instrument/README.md +++ b/papers/doc/4-instrument/README.md @@ -10,12 +10,13 @@ double-click to open the instrument editor. every instrument can be renamed and have its type changed. -depending on the instrument type, there are currently 10 different types of an instrument editor: +depending on the instrument type, there are currently 12 different types of an instrument editor: - [FM synthesis](fm.md) - for use with YM2612, YM2151 and FM block portion of YM2610. - [Standard](standard.md) - for use with NES and Sega Master System's PSG sound source and its derivatives. - [Game Boy](game-boy.md) - for use with Game Boy APU. - [PC Engine/TurboGrafx-16](pce.md) - for use with PC Engine's wavetable synthesizer. +- [WonderSwan](wonderswan.md) - for use with WonderSwan's wavetable synthesizer. - [AY8930](8930.md) - for use with Microchip AY8930 E-PSG sound source. - [Commodore 64](c64.md) - for use with Commodore 64 SID. - [SAA1099](saa.md) - for use with Philips SAA1099 PSG sound source. diff --git a/papers/doc/4-instrument/wonderswan.md b/papers/doc/4-instrument/wonderswan.md new file mode 100644 index 000000000..6ad0e8c97 --- /dev/null +++ b/papers/doc/4-instrument/wonderswan.md @@ -0,0 +1,8 @@ +# WonderSwan instrument editor + +WS instrument editor consists of only four macros, similar to PCE but with different volume and noise range: + +- [Volume] - volume sequence +- [Arpeggio] - pitch sequencr +- [Noise] - noise LFSR tap sequence +- [Waveform] - spicifies wavetables sequence diff --git a/papers/doc/5-wave/README.md b/papers/doc/5-wave/README.md index 91fe92656..005739257 100644 --- a/papers/doc/5-wave/README.md +++ b/papers/doc/5-wave/README.md @@ -1,5 +1,5 @@ # wavetable editor -Wavetable synthizers, in context of Furnace, are sound sources that operate on extremely short n-bit PCM streams. By extremely short, no more than 256 bytes. This amount of space is nowhere near enough to store an actual sampled sound, it allows certain amount of freedom to define a waveform shape. As of Furnace 0.5.5, wavetable editor affects PC Engine and channel 3 of Game Boy. +Wavetable synthizers, in context of Furnace, are sound sources that operate on extremely short n-bit PCM streams. By extremely short, no more than 256 bytes. This amount of space is nowhere near enough to store an actual sampled sound, it allows certain amount of freedom to define a waveform shape. As of Furnace 0.5.8, wavetable editor affects PC Engine, WonderSwan and channel 3 of Game Boy. -Furnace's wavetable editor is rather simple, you can draw the waveform using mouse or by pasting an MML bit stream in the input field. Maximum wave width (length) is 256 bytes, and maximum wave height (depth) is 256. NOTE: both Game Boy and PCE can handle max 32 byte waveforms as of now, width 16-level height for GB and 32-level height for PCE. If larger wave will be defined for these two systems, it will be squashed to fit in the constrains of the system. +Furnace's wavetable editor is rather simple, you can draw the waveform using mouse or by pasting an MML bit stream in the input field. Maximum wave width (length) is 256 bytes, and maximum wave height (depth) is 256. NOTE: Game Boy, PCE and WonderSwan can handle max 32 byte waveforms as of now, with 16-level height for GB and WS, and 32-level height for PCE. If larger wave will be defined for these two systems, it will be squashed to fit within the constraints of the system. diff --git a/papers/doc/7-systems/README.md b/papers/doc/7-systems/README.md index 78aeb8cea..a42e0d061 100644 --- a/papers/doc/7-systems/README.md +++ b/papers/doc/7-systems/README.md @@ -18,5 +18,6 @@ this is a list of systems that Furnace supports, including each system's effects - [Atari 2600](tia.md) - [Philips SAA1099](saa1099.md) - [Microchip AY8930](ay8930.md) +- [WonderSwan](wonderswan.md) Furnace also reads .dmf files with the [Yamaha YMU759](ymu759.md) system, but does not emulate the chip at all. diff --git a/papers/doc/7-systems/wonderswan.md b/papers/doc/7-systems/wonderswan.md new file mode 100644 index 000000000..c5c0bafdd --- /dev/null +++ b/papers/doc/7-systems/wonderswan.md @@ -0,0 +1,20 @@ +# WonderSwan + +A handheld console released only in Japan by Bandai. Designed by the same +people behind Game Boy and Virtual Boy, it has lots of similar elements from +those two systems in the sound department. + +It has 4 wavetable channels, one channel could play PCM, the other has hardware +sweep and the other could play noise. + +# effects + +- `10xx`: change wave. +- `11xx`: setup noise mode (channel 4 only). + - 0: disable. + - 1-8: enable and set tap preset. +- `12xx`: setup sweep period (channel 3 only). + - 0: disable. + - 1-32: enable and set period. +- `13xx`: setup sweep amount (channel 3 only). +- `17xx`: toggle PCM mode (channel 2 only). diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index 3c39b03b4..fe7c3ea34 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -109,6 +109,9 @@ enum DivDispatchCmds { DIV_CMD_QSOUND_ECHO_DELAY, DIV_CMD_QSOUND_ECHO_LEVEL, + DIV_CMD_WS_SWEEP_TIME, + DIV_CMD_WS_SWEEP_AMOUNT, + DIV_ALWAYS_SET_VOLUME, DIV_CMD_MAX diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 52cc7177e..0bc6a2729 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -41,6 +41,7 @@ #include "platform/pcspkr.h" #include "platform/segapcm.h" #include "platform/qsound.h" +#include "platform/ws.h" #include "platform/dummy.h" #include "platform/lynx.h" #include "../ta-log.h" @@ -234,6 +235,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_SEGAPCM_COMPAT: dispatch=new DivPlatformSegaPCM; break; + case DIV_SYSTEM_SWAN: + dispatch=new DivPlatformWS; + break; default: logW("this system is not supported yet! using dummy platform.\n"); dispatch=new DivPlatformDummy; diff --git a/src/engine/platform/sound/ws.cpp b/src/engine/platform/sound/ws.cpp new file mode 100644 index 000000000..e02d63ec6 --- /dev/null +++ b/src/engine/platform/sound/ws.cpp @@ -0,0 +1,412 @@ +/******************************************************************************/ +/* Mednafen - Multi-system Emulator */ +/******************************************************************************/ +/* sound.cpp - WonderSwan Sound Emulation +** Copyright (C) 2007-2017 Mednafen Team +** Copyright (C) 2016 Alex 'trap15' Marshall - http://daifukkat.su/ +** +** 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 "ws.h" +#include + +#define MK_SAMPLE_CACHE \ + { \ + int sample; \ + sample = (((wsRAM[(/*(SampleRAMPos << 6) + */(sample_pos[ch] >> 1) + (ch << 4)) ] >> ((sample_pos[ch] & 1) ? 4 : 0)) & 0x0F)); \ + sample_cache[ch][0] = sample * ((volume[ch] >> 4) & 0x0F); \ + sample_cache[ch][1] = sample * ((volume[ch] >> 0) & 0x0F); \ + } + +#define MK_SAMPLE_CACHE_NOISE \ + { \ + int sample; \ + sample = ((nreg & 1) ? 0xF : 0x0); \ + sample_cache[ch][0] = sample * ((volume[ch] >> 4) & 0x0F); \ + sample_cache[ch][1] = sample * ((volume[ch] >> 0) & 0x0F); \ + } + +#define MK_SAMPLE_CACHE_VOICE \ + { \ + int sample, half; \ + sample = volume[ch]; \ + half = sample >> 1; \ + sample_cache[ch][0] = (voice_volume & 4) ? sample : (voice_volume & 8) ? half : 0; \ + sample_cache[ch][1] = (voice_volume & 1) ? sample : (voice_volume & 2) ? half : 0; \ + } + + +#define SYNCSAMPLE(wt) /* \ + { \ + int32_t left = sample_cache[ch][0], right = sample_cache[ch][1]; \ + WaveSynth.offset_inline(wt, left - last_val[ch][0], sbuf[0]); \ + WaveSynth.offset_inline(wt, right - last_val[ch][1], sbuf[1]); \ + last_val[ch][0] = left; \ + last_val[ch][1] = right; \ + } */ + +#define SYNCSAMPLE_NOISE(wt) SYNCSAMPLE(wt) + +void WSwan::SoundUpdate(uint32_t v30mz_timestamp) +{ + int32_t run_time; + + //printf("%d\n", v30mz_timestamp); + //printf("%02x %02x\n", control, noise_control); + run_time = v30mz_timestamp - last_ts; + + for(int y = 0; y < 2; y++) + sbuf[y] = 0; + + for(unsigned int ch = 0; ch < 4; ch++) + { + // Channel is disabled? + if(!(control & (1 << ch))) + continue; + + if(ch == 1 && (control & 0x20)) // Direct D/A mode? + { + MK_SAMPLE_CACHE_VOICE; + SYNCSAMPLE(v30mz_timestamp); + } + else if(ch == 2 && (control & 0x40) && sweep_value) // Sweep + { + uint32_t tmp_pt = 2048 - period[ch]; + uint32_t meow_timestamp = v30mz_timestamp - run_time; + uint32_t tmp_run_time = run_time; + + while(tmp_run_time) + { + int32_t sub_run_time = tmp_run_time; + + if(sub_run_time > sweep_8192_divider) + sub_run_time = sweep_8192_divider; + + sweep_8192_divider -= sub_run_time; + if(sweep_8192_divider <= 0) + { + sweep_8192_divider += 8192; + sweep_counter--; + if(sweep_counter <= 0) + { + sweep_counter = sweep_step + 1; + period[ch] = (period[ch] + (int8_t)sweep_value) & 0x7FF; + } + } + + meow_timestamp += sub_run_time; + if(tmp_pt > 4) + { + period_counter[ch] -= sub_run_time; + while(period_counter[ch] <= 0) + { + sample_pos[ch] = (sample_pos[ch] + 1) & 0x1F; + + MK_SAMPLE_CACHE; + SYNCSAMPLE(meow_timestamp + period_counter[ch]); + period_counter[ch] += tmp_pt; + } + } + tmp_run_time -= sub_run_time; + } + } + else if(ch == 3 && (control & 0x80) && (noise_control & 0x10)) // Noise + { + uint32_t tmp_pt = 2048 - period[ch]; + + period_counter[ch] -= run_time; + while(period_counter[ch] <= 0) + { + static const uint8_t stab[8] = { 14, 10, 13, 4, 8, 6, 9, 11 }; + + nreg = ((nreg << 1) | ((1 ^ (nreg >> 7) ^ (nreg >> stab[noise_control & 0x7])) & 1)) & 0x7FFF; + + if(control & 0x80) + { + MK_SAMPLE_CACHE_NOISE; + SYNCSAMPLE_NOISE(v30mz_timestamp + period_counter[ch]); + } + else if(tmp_pt > 4) + { + sample_pos[ch] = (sample_pos[ch] + 1) & 0x1F; + MK_SAMPLE_CACHE; + SYNCSAMPLE(v30mz_timestamp + period_counter[ch]); + } + period_counter[ch] += tmp_pt; + } + } + else + { + uint32_t tmp_pt = 2048 - period[ch]; + + if(tmp_pt > 4) + { + period_counter[ch] -= run_time; + while(period_counter[ch] <= 0) + { + sample_pos[ch] = (sample_pos[ch] + 1) & 0x1F; + + MK_SAMPLE_CACHE; + SYNCSAMPLE(v30mz_timestamp + period_counter[ch]); // - period_counter[ch]); + period_counter[ch] += tmp_pt; + } + } + } + sbuf[0] += sample_cache[ch][0]; + sbuf[1] += sample_cache[ch][1]; + } + + if(HVoiceCtrl & 0x80) + { + int16_t sample = (uint8_t)HyperVoice; + + switch(HVoiceCtrl & 0xC) + { + case 0x0: sample = (uint16_t)sample << (8 - (HVoiceCtrl & 3)); break; + case 0x4: sample = (uint16_t)(sample | -0x100) << (8 - (HVoiceCtrl & 3)); break; + case 0x8: sample = (uint16_t)((int8_t)sample) << (8 - (HVoiceCtrl & 3)); break; + case 0xC: sample = (uint16_t)sample << 8; break; + } + // bring back to 11bit, keeping signedness + sample >>= 5; + + int32_t left, right; + left = (HVoiceChanCtrl & 0x40) ? sample : 0; + right = (HVoiceChanCtrl & 0x20) ? sample : 0; + + // WaveSynth.offset_inline(v30mz_timestamp, left - last_hv_val[0], sbuf[0]); + // WaveSynth.offset_inline(v30mz_timestamp, right - last_hv_val[1], sbuf[1]); + // last_hv_val[0] = left; + // last_hv_val[1] = right; + sbuf[0] += left; + sbuf[1] += right; + } + last_ts = v30mz_timestamp; +} + +void WSwan::SoundWrite(uint32_t A, uint8_t V) +{ + if(A >= 0x80 && A <= 0x87) + { + int ch = (A - 0x80) >> 1; + + if(A & 1) + period[ch] = (period[ch] & 0x00FF) | ((V & 0x07) << 8); + else + period[ch] = (period[ch] & 0x0700) | ((V & 0xFF) << 0); + + //printf("Period %d: 0x%04x --- %f\n", ch, period[ch], 3072000.0 / (2048 - period[ch])); + } + else if(A >= 0x88 && A <= 0x8B) + { + volume[A - 0x88] = V; + } + else if(A == 0x8C) + sweep_value = V; + else if(A == 0x8D) + { + sweep_step = V; + sweep_counter = sweep_step + 1; + sweep_8192_divider = 8192; + } + else if(A == 0x8E) + { + //printf("NOISECONTROL: %02x\n", V); + if(V & 0x8) + nreg = 0; + + noise_control = V & 0x17; + } + else if(A == 0x90) + { + for(int n = 0; n < 4; n++) + { + if(!(control & (1 << n)) && (V & (1 << n))) + { + period_counter[n] = 1; + sample_pos[n] = 0x1F; + } + } + control = V; + //printf("Sound Control: %02x\n", V); + } + else if(A == 0x91) + { + output_control = V & 0xF; + //printf("%02x, %02x\n", V, (V >> 1) & 3); + } + else if(A == 0x92) + nreg = (nreg & 0xFF00) | (V << 0); + else if(A == 0x93) + nreg = (nreg & 0x00FF) | ((V & 0x7F) << 8); + else if(A == 0x94) + { + voice_volume = V & 0xF; + //printf("%02x\n", V); + } + else switch(A) + { + case 0x6A: HVoiceCtrl = V; break; + case 0x6B: HVoiceChanCtrl = V & 0x6F; break; + case 0x8F: SampleRAMPos = V; break; + case 0x95: HyperVoice = V; break; // Pick a port, any port?! + //default: printf("%04x:%02x\n", A, V); break; + } +} + +uint8_t WSwan::SoundRead(uint32_t A) +{ + if(A >= 0x80 && A <= 0x87) + { + int ch = (A - 0x80) >> 1; + + if(A & 1) + return(period[ch] >> 8); + else + return(period[ch]); + } + else if(A >= 0x88 && A <= 0x8B) + return(volume[A - 0x88]); + else switch(A) + { + default: /*printf("SoundRead: %04x\n", A);*/ return(0); + case 0x6A: return(HVoiceCtrl); + case 0x6B: return(HVoiceChanCtrl); + case 0x8C: return(sweep_value); + case 0x8D: return(sweep_step); + case 0x8E: return(noise_control); + case 0x8F: return(SampleRAMPos); + case 0x90: return(control); + case 0x91: return(output_control | 0x80); + case 0x92: return((nreg >> 0) & 0xFF); + case 0x93: return((nreg >> 8) & 0xFF); + case 0x94: return(voice_volume); + } +} + +void WSwan::RAMWrite(uint32_t A, uint8_t V) +{ + wsRAM[A & 0x3F] = V; +} + +int32_t WSwan::SoundFlush(int16_t *SoundBuf, const int32_t MaxSoundFrames) +{ + int32_t FrameCount = 0; + + if(SoundBuf) + { + for(int y = 0; y < 2; y++) + { + // sbuf[y]->end_frame(v30mz_timestamp); + // FrameCount = sbuf[y]->read_samples(SoundBuf + y, MaxSoundFrames, true); + int32_t left = sbuf[0]; + int32_t right = sbuf[1]; + if (left >= 0x400) left = 0x3FF; + else if (left < -0x400) left = -0x400; + if (right >= 0x400) left = 0x3FF; + else if (right < -0x400) left = -0x400; + SoundBuf[0] = (int16_t)left << 5; + SoundBuf[1] = (int16_t)right << 5; + } + } + + last_ts = 0; + + return(FrameCount); +} + +// Call before wsRAM is updated +// void WSwan::SoundCheckRAMWrite(uint32_t A) +// { +// if((A >> 6) == SampleRAMPos) +// SoundUpdate(); +// } + +// static void RedoVolume(void) +// { +// WaveSynth.volume(2.5); +// } + +// void WSwan::SoundInit(void) +// { +// for(int i = 0; i < 2; i++) +// { +// sbuf[i] = new Blip_Buffer(); + +// sbuf[i]->set_sample_rate(0 ? 0 : 44100, 60); +// sbuf[i]->clock_rate((long)(3072000)); +// sbuf[i]->bass_freq(20); +// } + +// RedoVolume(); +// } + +// void WSwan::SoundKill(void) +// { +// for(int i = 0; i < 2; i++) +// { +// if(sbuf[i]) +// { +// delete sbuf[i]; +// sbuf[i] = NULL; +// } +// } + +// } + +// bool WSwan::SetSoundRate(uint32_t rate) +// { +// for(int i = 0; i < 2; i++) +// sbuf[i]->set_sample_rate(rate?rate:44100, 60); + +// return(true); +// } + +void WSwan::SoundReset(void) +{ + memset(period, 0, sizeof(period)); + memset(volume, 0, sizeof(volume)); + voice_volume = 0; + sweep_step = 0; + sweep_value = 0; + noise_control = 0; + control = 0; + output_control = 0; + + sweep_8192_divider = 8192; + sweep_counter = 1; + SampleRAMPos = 0; + + for(unsigned ch = 0; ch < 4; ch++) + period_counter[ch] = 1; + + memset(sample_pos, 0, sizeof(sample_pos)); + nreg = 0; + + memset(sample_cache, 0, sizeof(sample_cache)); + // memset(last_val, 0, sizeof(last_val)); + last_v_val = 0; + + HyperVoice = 0; + last_hv_val[0] = last_hv_val[1] = 0; + HVoiceCtrl = 0; + HVoiceChanCtrl = 0; + + for(int y = 0; y < 2; y++) + // sbuf[y]->clear(); + sbuf[y] = 0; + last_ts = 0; +} diff --git a/src/engine/platform/sound/ws.h b/src/engine/platform/sound/ws.h new file mode 100644 index 000000000..b1b0af740 --- /dev/null +++ b/src/engine/platform/sound/ws.h @@ -0,0 +1,82 @@ +/******************************************************************************/ +/* Mednafen - Multi-system Emulator */ +/******************************************************************************/ +/* sound.h - WonderSwan Sound Emulation +** Copyright (C) 2007-2016 Mednafen Team +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the GNU General Public License +** as published by the Free Software Foundation; either version 2 +** of the License, or (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software Foundation, Inc., +** 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef __WSWAN_SOUND_H +#define __WSWAN_SOUND_H + +#include + +class WSwan +{ +public: + int32_t SoundFlush(int16_t *SoundBuf, const int32_t MaxSoundFrames); + + // void SoundInit(void); + // void SoundKill(void); + // void SetSoundMultiplier(double multiplier); + // bool SetSoundRate(uint32_t rate); + + void SoundWrite(uint32_t, uint8_t); + uint8_t SoundRead(uint32_t); + void SoundReset(void); + // void SoundCheckRAMWrite(uint32_t A); + + void SoundUpdate(uint32_t); + void RAMWrite(uint32_t, uint8_t); + +private: + // Blip_Synth WaveSynth; + + // Blip_Buffer *sbuf[2] = { NULL }; + int32_t sbuf[2]; + + uint16_t period[4]; + uint8_t volume[4]; // left volume in upper 4 bits, right in lower 4 bits + uint8_t voice_volume; + + uint8_t sweep_step, sweep_value; + uint8_t noise_control; + uint8_t control; + uint8_t output_control; + + int32_t sweep_8192_divider; + uint8_t sweep_counter; + uint8_t SampleRAMPos; + + int32_t sample_cache[4][2]; + + int32_t last_v_val; + + uint8_t HyperVoice; + int32_t last_hv_val[2]; + uint8_t HVoiceCtrl, HVoiceChanCtrl; + + int32_t period_counter[4]; + // int32_t last_val[4][2]; // Last outputted value, l&r + uint8_t sample_pos[4]; + uint16_t nreg; + uint32_t last_ts; + + uint8_t wsRAM[64]; + int16_t sBuf[2]; +}; + +#endif diff --git a/src/engine/platform/ws.cpp b/src/engine/platform/ws.cpp new file mode 100644 index 000000000..df82376a4 --- /dev/null +++ b/src/engine/platform/ws.cpp @@ -0,0 +1,522 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "ws.h" +#include "../engine.h" +#include + +#define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);}} + +#define CHIP_DIVIDER 32 + +const char* regCheatSheetWS[]={ + "CH1_Pitch", "00", + "CH2_Pitch", "02", + "CH3_Pitch", "04", + "CH4_Pitch", "06", + "CH1_Vol", "08", + "CH2_Vol", "09", + "CH3_Vol", "0A", + "CH4_Vol", "0B", + "Sweep_Value", "0C", + "Sweep_Time", "0D", + "Noise", "0E", + "Wave_Base", "0F", + "Ctrl", "10", + "Output", "11", + "Random", "12", + "Voice_Ctrl", "14", + "Wave_Mem", "40", + NULL +}; + +const char** DivPlatformWS::getRegisterSheet() { + return regCheatSheetWS; +} + +const char* DivPlatformWS::getEffectName(unsigned char effect) { + switch (effect) { + case 0x10: + return "10xx: Change waveform"; + break; + case 0x11: + return "11xx: Setup noise mode (0: disabled; 1-8: enabled/tap)"; + break; + case 0x12: + return "12xx: Setup sweep period (0: disabled; 1-20: enabled/period)"; + break; + case 0x13: + return "13xx: Set sweep amount"; + break; + case 0x17: + return "17xx: Toggle PCM mode"; + break; + } + return NULL; +} + +void DivPlatformWS::acquire(short* bufL, short* bufR, size_t start, size_t len) { + for (size_t h=start; hrate) { + DivSample* s=parent->getSample(dacSample); + if (s->samples<=0) { + dacSample=-1; + continue; + } + rWrite(0x09,(unsigned char)s->data8[dacPos++]+0x80); + if (dacPos>=s->samples) { + if (s->loopStart>=0 && s->loopStart<=(int)s->samples) { + dacPos=s->loopStart; + } else { + dacSample=-1; + } + } + dacPeriod-=rate; + } + } + + // the rest + while (!writes.empty()) { + QueuedWrite w=writes.front(); + if (regPool[w.addr]!=w.val) { + if (w.addr<0x40) ws->SoundWrite(w.addr|0x80,w.val); + else ws->RAMWrite(w.addr&0x3f,w.val); + regPool[w.addr]=w.val; + } + writes.pop(); + } + int16_t samp[2]{0, 0}; + ws->SoundUpdate(16); + ws->SoundFlush(samp, 1); + bufL[h]=samp[0]; + bufR[h]=samp[1]; + } +} + +void DivPlatformWS::updateWave(int ch) { + DivWavetable* wt=parent->getWave(chan[ch].wave); + unsigned char addr=0x40+ch*16; + if (wt->max<1 || wt->len<1) { + for (int i=0; i<16; i++) { + rWrite(addr+i,0); + } + } else { + for (int i=0; i<16; i++) { + unsigned char nibble1=(wt->data[(i*2)*wt->len/32]*15)/wt->max; + unsigned char nibble2=(wt->data[(1+i*2)*wt->len/32]*15)/wt->max; + rWrite(addr+i,nibble1|(nibble2<<4)); + } + } +} + +void DivPlatformWS::calcAndWriteOutVol(int ch, int env) { + int vl=chan[ch].vol*((chan[ch].pan>>4)&0x0f)*env/225; + int vr=chan[ch].vol*(chan[ch].pan&0x0f)*env/225; + if (ch==1&&pcm) { + vl=(vl>0)?((vl>7)?3:2):0; + vr=(vr>0)?((vr>7)?3:2):0; + chan[1].outVol=vr|(vl<<2); + } else { + chan[ch].outVol=vr|(vl<<4); + } + writeOutVol(ch); +} + +void DivPlatformWS::writeOutVol(int ch) { + unsigned char val=isMuted[ch]?0:chan[ch].outVol; + if (ch==1&&pcm) { + rWrite(0x14,val) + } else { + rWrite(0x08+ch,val); + } +} + +void DivPlatformWS::tick() { + unsigned char sndCtrl=(pcm?0x20:0)|(sweep?0x40:0)|((noise>0)?0x80:0); + for (int i=0; i<4; i++) { + chan[i].std.next(); + if (chan[i].std.hadVol) { + int env=chan[i].std.vol; + if(parent->getIns(chan[i].ins)->type==DIV_INS_AMIGA) { + env=MIN(env/4,15); + } + calcAndWriteOutVol(i,env); + } + if (chan[i].std.hadArp) { + if (!chan[i].inPorta) { + if (chan[i].std.arpMode) { + chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp); + } else { + chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp); + } + } + chan[i].freqChanged=true; + } else { + if (chan[i].std.arpMode && chan[i].std.finishedArp) { + chan[i].baseFreq=NOTE_PERIODIC(chan[i].note); + chan[i].freqChanged=true; + } + } + if (chan[i].std.hadWave && !(i==1 && pcm)) { + if (chan[i].wave!=chan[i].std.wave) { + chan[i].wave=chan[i].std.wave; + updateWave(i); + } + } + if (chan[i].active) { + sndCtrl|=(1<calcFreq(chan[i].baseFreq,chan[i].pitch,true); + if (i==1 && furnaceDac) { + double off=1.0; + if (dacSample>=0 && dacSamplesong.sampleLen) { + DivSample* s=parent->getSample(dacSample); + if (s->centerRate<1) { + off=1.0; + } else { + off=8363.0/(double)s->centerRate; + } + } + dacRate=((double)chipClock/2)/MAX(1,off*chan[i].freq); + if (dumpWrites) addWrite(0xffff0001,dacRate); + } + if (chan[i].freq>2048) chan[i].freq=2048; + if (chan[i].freq<1) chan[i].freq=1; + int rVal=2048-chan[i].freq; + rWrite(i*2,rVal&0xff); + rWrite(i*2+1,rVal>>8); + if (chan[i].keyOn) { + if (!chan[i].std.hasVol) { + calcAndWriteOutVol(i,15); + } + if (chan[i].wave<0) { + chan[i].wave=0; + updateWave(i); + } + chan[i].keyOn=false; + } + if (chan[i].keyOff) { + chan[i].keyOff=false; + } + chan[i].freqChanged=false; + } + } + if (chan[3].std.hadDuty) { + noise=chan[3].std.duty; + if (noise>0) { + rWrite(0x0e,(noise-1)&0x07|0x18); + sndCtrl|=0x80; + } else { + sndCtrl&=~0x80; + } + } + rWrite(0x10,sndCtrl); +} + +int DivPlatformWS::dispatch(DivCommand c) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: { + DivInstrument* ins=parent->getIns(chan[c.chan].ins); + if (c.chan==1 && ins->type==DIV_INS_AMIGA) { + pcm=true; + } else if (furnaceDac) { + pcm=false; + } + if (c.chan==1 && pcm) { + if (skipRegisterWrites) break; + dacPos=0; + dacPeriod=0; + if (ins->type==DIV_INS_AMIGA) { + dacSample=ins->amiga.initSample; + if (dacSample<0 || dacSample>=parent->song.sampleLen) { + dacSample=-1; + if (dumpWrites) addWrite(0xffff0002,0); + break; + } else { + if (dumpWrites) { + addWrite(0xffff0000,dacSample); + } + } + if (c.value!=DIV_NOTE_NULL) { + chan[1].baseFreq=NOTE_PERIODIC(c.value); + chan[1].freqChanged=true; + chan[1].note=c.value; + } + chan[1].active=true; + chan[1].keyOn=true; + chan[1].std.init(ins); + furnaceDac=true; + } else { + if (c.value!=DIV_NOTE_NULL) { + chan[1].note=c.value; + } + dacSample=12*sampleBank+chan[1].note%12; + if (dacSample>=parent->song.sampleLen) { + dacSample=-1; + if (dumpWrites) addWrite(0xffff0002,0); + break; + } else { + if (dumpWrites) addWrite(0xffff0000,dacSample); + } + dacRate=parent->getSample(dacSample)->rate; + if (dumpWrites) { + addWrite(0xffff0001,dacRate); + } + chan[1].active=true; + chan[1].keyOn=true; + furnaceDac=false; + } + break; + } + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + } + chan[c.chan].active=true; + chan[c.chan].keyOn=true; + chan[c.chan].std.init(ins); + break; + } + case DIV_CMD_NOTE_OFF: + if (c.chan==1&&pcm) { + dacSample=-1; + if (dumpWrites) addWrite(0xffff0002,0); + pcm=false; + } + chan[c.chan].active=false; + chan[c.chan].keyOff=true; + chan[c.chan].std.init(NULL); + break; + case DIV_CMD_NOTE_OFF_ENV: + case DIV_CMD_ENV_RELEASE: + chan[c.chan].std.release(); + break; + case DIV_CMD_INSTRUMENT: + if (chan[c.chan].ins!=c.value || c.value2==1) { + chan[c.chan].ins=c.value; + } + break; + case DIV_CMD_VOLUME: + if (chan[c.chan].vol!=c.value) { + chan[c.chan].vol=c.value; + if (!chan[c.chan].std.hasVol) { + calcAndWriteOutVol(c.chan,15); + } + } + break; + case DIV_CMD_GET_VOLUME: + return chan[c.chan].vol; + break; + case DIV_CMD_PITCH: + chan[c.chan].pitch=c.value; + chan[c.chan].freqChanged=true; + break; + case DIV_CMD_WAVE: + chan[c.chan].wave=c.value; + updateWave(c.chan); + chan[c.chan].keyOn=true; + break; + case DIV_CMD_WS_SWEEP_TIME: + if (c.chan==2) { + if (c.value==0) { + sweep=false; + } else { + sweep=true; + rWrite(0x0d,(c.value-1)&0xff); + } + } + break; + case DIV_CMD_WS_SWEEP_AMOUNT: + if (c.chan==2) { + rWrite(0x0c,c.value&0xff); + } + break; + case DIV_CMD_NOTE_PORTA: { + int destFreq=NOTE_PERIODIC(c.value2); + bool return2=false; + if (destFreq>chan[c.chan].baseFreq) { + chan[c.chan].baseFreq+=c.value; + if (chan[c.chan].baseFreq>=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } else { + chan[c.chan].baseFreq-=c.value; + if (chan[c.chan].baseFreq<=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } + chan[c.chan].freqChanged=true; + if (return2) { + chan[c.chan].inPorta=false; + return 2; + } + break; + } + case DIV_CMD_STD_NOISE_MODE: + if (c.chan==3) { + noise=c.value&0xff; + if (noise>0) rWrite(0x0e,(noise-1)&0x07|0x18); + } + break; + case DIV_CMD_SAMPLE_MODE: + if (c.chan==1) pcm=c.value; + break; + case DIV_CMD_SAMPLE_BANK: + sampleBank=c.value; + if (sampleBank>(parent->song.sample.size()/12)) { + sampleBank=parent->song.sample.size()/12; + } + break; + case DIV_CMD_PANNING: { + chan[c.chan].pan=c.value; + if (!chan[c.chan].std.hasVol) { + calcAndWriteOutVol(c.chan,15); + } + break; + } + case DIV_CMD_LEGATO: + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.willArp && !chan[c.chan].std.arpMode)?(chan[c.chan].std.arp):(0))); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + break; + case DIV_CMD_PRE_PORTA: + if (chan[c.chan].active && c.value2) { + if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + } + chan[c.chan].inPorta=c.value; + break; + case DIV_CMD_GET_VOLMAX: + return 15; + break; + case DIV_ALWAYS_SET_VOLUME: + return 0; + break; + default: + break; + } + return 1; +} + +void DivPlatformWS::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; + writeOutVol(ch); +} + +void DivPlatformWS::forceIns() { + for (int i=0; i<4; i++) { + chan[i].insChanged=true; + chan[i].freqChanged=true; + updateWave(i); + writeOutVol(i); + } +} + +void* DivPlatformWS::getChanState(int ch) { + return &chan[ch]; +} + +unsigned char* DivPlatformWS::getRegisterPool() { + // get Random from emulator + regPool[0x12]=ws->SoundRead(0x92); + regPool[0x13]=ws->SoundRead(0x93); + return regPool; +} + +int DivPlatformWS::getRegisterPoolSize() { + return 128; +} + +void DivPlatformWS::reset() { + while (!writes.empty()) writes.pop(); + memset(regPool,0,128); + for (int i=0; i<4; i++) { + chan[i]=Channel(); + chan[i].vol=15; + chan[i].pan=0xff; + } + if (dumpWrites) { + addWrite(0xffffffff,0); + } + ws->SoundReset(); + pcm=false; + sweep=false; + furnaceDac=false; + noise=0; + dacPeriod=0; + dacRate=0; + dacPos=0; + dacSample=-1; + sampleBank=0; + rWrite(0x0f,0x00); // wave table at 0x0000 + rWrite(0x11,0x09); // enable speakers +} + +bool DivPlatformWS::isStereo() { + return true; +} + +void DivPlatformWS::notifyWaveChange(int wave) { + for (int i=0; i<4; i++) { + if (chan[i].wave==wave) { + updateWave(i); + } + } +} + +void DivPlatformWS::notifyInsDeletion(void* ins) { + for (int i=0; i<4; i++) { + chan[i].std.notifyInsDeletion((DivInstrument*)ins); + } +} + +void DivPlatformWS::poke(unsigned int addr, unsigned short val) { + rWrite(addr,val); +} + +void DivPlatformWS::poke(std::vector& wlist) { + for (DivRegWrite& i: wlist) rWrite(i.addr,i.val); +} + +int DivPlatformWS::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { + parent=p; + dumpWrites=false; + skipRegisterWrites=false; + chipClock=3072000; + rate=chipClock/16; // = 192000kHz, should be enough + for (int i=0; i<4; i++) { + isMuted[i]=false; + } + ws=new WSwan(); + reset(); + return 4; +} + +void DivPlatformWS::quit() { + delete ws; +} + +DivPlatformWS::~DivPlatformWS() { +} diff --git a/src/engine/platform/ws.h b/src/engine/platform/ws.h new file mode 100644 index 000000000..ea6466fbb --- /dev/null +++ b/src/engine/platform/ws.h @@ -0,0 +1,95 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _WS_H +#define _WS_H + +#include "../dispatch.h" +#include "../macroInt.h" +#include "sound/ws.h" +#include + +class DivPlatformWS: public DivDispatch { + struct Channel { + int freq, baseFreq, pitch, note; + unsigned char ins, pan; + bool active, insChanged, freqChanged, keyOn, keyOff, inPorta; + int vol, outVol, wave; + DivMacroInt std; + Channel(): + freq(0), + baseFreq(0), + pitch(0), + note(0), + ins(-1), + pan(255), + active(false), + insChanged(true), + freqChanged(false), + keyOn(false), + keyOff(false), + inPorta(false), + vol(15), + outVol(15), + wave(-1) {} + }; + Channel chan[4]; + bool isMuted[4]; + bool pcm, sweep, furnaceDac; + unsigned char sampleBank, noise; + int dacPeriod, dacRate; + unsigned int dacPos; + int dacSample; + + unsigned char regPool[0x80]; + struct QueuedWrite { + unsigned char addr; + unsigned char val; + QueuedWrite(unsigned char a, unsigned char v): addr(a), val(v) {} + }; + std::queue writes; + WSwan* ws; + void updateWave(int ch); + friend void putDispatchChan(void*,int,int); + public: + void acquire(short* bufL, short* bufR, size_t start, size_t len); + int dispatch(DivCommand c); + void* getChanState(int chan); + unsigned char* getRegisterPool(); + int getRegisterPoolSize(); + void reset(); + void forceIns(); + void tick(); + void muteChannel(int ch, bool mute); + void notifyWaveChange(int wave); + void notifyInsDeletion(void* ins); + bool isStereo(); + void poke(unsigned int addr, unsigned short val); + void poke(std::vector& wlist); + const char** getRegisterSheet(); + const char* getEffectName(unsigned char effect); + int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); + void quit(); + ~DivPlatformWS(); + private: + void calcAndWriteOutVol(int ch, int env); + void writeOutVol(int ch); +}; + +#endif diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 7114a7c2a..6895fcc5d 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -251,6 +251,27 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe break; } break; + case DIV_SYSTEM_SWAN: + switch (effect) { + case 0x10: // select waveform + dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal)); + break; + case 0x11: // noise mode + dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal)); + break; + case 0x12: // sweep period + dispatchCmd(DivCommand(DIV_CMD_WS_SWEEP_TIME,ch,effectVal)); + break; + case 0x13: // sweep amount + dispatchCmd(DivCommand(DIV_CMD_WS_SWEEP_AMOUNT,ch,effectVal)); + break; + case 0x17: // PCM enable + dispatchCmd(DivCommand(DIV_CMD_SAMPLE_MODE,ch,(effectVal>0))); + break; + default: + return false; + } + break; default: return false; } diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 3c988ceb8..fe45a4e78 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -1616,6 +1616,7 @@ bool DivEngine::isVGMExportable(DivSystem which) { case DIV_SYSTEM_OPLL: case DIV_SYSTEM_OPLL_DRUMS: case DIV_SYSTEM_VRC7: + case DIV_SYSTEM_SWAN: return true; default: return false; diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index bef7c5359..caa811481 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -443,6 +443,18 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write w->writeC(write.val&0xff); w->writeC(write.addr&0xff); break; + case DIV_SYSTEM_SWAN: + if ((write.addr&0x7f)<0x40) { + w->writeC(0xbc); + w->writeC(baseAddr2|(write.addr&0x3f)); + w->writeC(write.val&0xff); + } else { + // (Wave) RAM write + w->writeC(0xc6); + w->writeS(baseAddr2S|(write.addr&0x3f)); + w->writeC(write.val&0xff); + } + break; default: logW("write not handled!\n"); break; @@ -746,6 +758,21 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) { addWarning("dual QSound is not supported by the VGM format"); } break; + case DIV_SYSTEM_SWAN: + if (!hasSwan) { + hasSwan=disCont[i].dispatch->chipClock; + willExport[i]=true; + // funny enough, VGM doesn't have support for WSC's sound DMA by design + // so DAC stream it goes + // since WS has the same PCM format as YM2612 DAC, I can just reuse this flag + writeDACSamples=true; + } else if (!(hasSwan&0x40000000)) { + isSecond[i]=true; + willExport[i]=true; + hasSwan|=0x40000000; + howManyChips++; + } + break; default: break; } @@ -1031,6 +1058,24 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) { streamID++; } break; + case DIV_SYSTEM_SWAN: + w->writeC(0x90); + w->writeC(streamID); + w->writeC(33); + w->writeC(0); // port + w->writeC(isSecond[i]?0x89:0x09); // DAC + + w->writeC(0x91); + w->writeC(streamID); + w->writeC(0); + w->writeC(1); + w->writeC(0); + + w->writeC(0x92); + w->writeC(streamID); + w->writeI(24000); // default + streamID++; + break; default: break; } diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index fb3e6fc31..04cf8ce49 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4634,6 +4634,7 @@ bool FurnaceGUI::loop() { sysAddOption(DIV_SYSTEM_AY8930); sysAddOption(DIV_SYSTEM_LYNX); sysAddOption(DIV_SYSTEM_QSOUND); + sysAddOption(DIV_SYSTEM_SWAN); ImGui::EndMenu(); } if (ImGui::BeginMenu("configure system...")) { @@ -4979,6 +4980,7 @@ bool FurnaceGUI::loop() { sysChangeOption(i,DIV_SYSTEM_AY8930); sysChangeOption(i,DIV_SYSTEM_LYNX); sysChangeOption(i,DIV_SYSTEM_QSOUND); + sysChangeOption(i,DIV_SYSTEM_SWAN); ImGui::EndMenu(); } } diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 6ff30896f..6c7f06cbe 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1358,6 +1358,10 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_PCE) { dutyMax=1; } + if (ins->type==DIV_INS_SWAN) { + dutyLabel="Noise"; + dutyMax=8; + } if (ins->type==DIV_INS_OPLL || ins->type==DIV_INS_OPL) { dutyMax=0; } @@ -1777,7 +1781,7 @@ void FurnaceGUI::drawWaveEdit() { DivWavetable* wave=e->song.wave[curWave]; ImGui::Text("Width"); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("use a width of 32 on Game Boy and PC Engine.\nany other widths will be scaled during playback."); + ImGui::SetTooltip("use a width of 32 on Game Boy, PC Engine and WonderSwan.\nany other widths will be scaled during playback."); } ImGui::SameLine(); ImGui::SetNextItemWidth(128.0f*dpiScale); @@ -1791,7 +1795,7 @@ void FurnaceGUI::drawWaveEdit() { ImGui::SameLine(); ImGui::Text("Height"); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("use a height of:\n- 15 for Game Boy\n- 31 for PC Engine\nany other heights will be scaled during playback."); + ImGui::SetTooltip("use a height of:\n- 15 for Game Boy and WonderSwan\n- 31 for PC Engine\nany other heights will be scaled during playback."); } ImGui::SameLine(); ImGui::SetNextItemWidth(128.0f*dpiScale); From 25088d6032bacc0fed7053531f2cc7daaf217132 Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Mon, 7 Mar 2022 01:26:59 +0700 Subject: [PATCH 135/637] Fix playback and VGM export --- src/engine/platform/ws.cpp | 103 +++++++++++++++++++------------------ src/engine/safeWriter.cpp | 4 ++ src/engine/vgmOps.cpp | 6 +-- src/gui/gui.cpp | 6 +++ 4 files changed, 66 insertions(+), 53 deletions(-) diff --git a/src/engine/platform/ws.cpp b/src/engine/platform/ws.cpp index df82376a4..4e7c63309 100644 --- a/src/engine/platform/ws.cpp +++ b/src/engine/platform/ws.cpp @@ -187,7 +187,7 @@ void DivPlatformWS::tick() { } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true); - if (i==1 && furnaceDac) { + if (i==1 && pcm && furnaceDac) { double off=1.0; if (dacSample>=0 && dacSamplesong.sampleLen) { DivSample* s=parent->getSample(dacSample); @@ -237,56 +237,58 @@ int DivPlatformWS::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(chan[c.chan].ins); - if (c.chan==1 && ins->type==DIV_INS_AMIGA) { - pcm=true; - } else if (furnaceDac) { - pcm=false; - } - if (c.chan==1 && pcm) { - if (skipRegisterWrites) break; - dacPos=0; - dacPeriod=0; + if (c.chan==1) { if (ins->type==DIV_INS_AMIGA) { - dacSample=ins->amiga.initSample; - if (dacSample<0 || dacSample>=parent->song.sampleLen) { - dacSample=-1; - if (dumpWrites) addWrite(0xffff0002,0); - break; - } else { - if (dumpWrites) { - addWrite(0xffff0000,dacSample); - } - } - if (c.value!=DIV_NOTE_NULL) { - chan[1].baseFreq=NOTE_PERIODIC(c.value); - chan[1].freqChanged=true; - chan[1].note=c.value; - } - chan[1].active=true; - chan[1].keyOn=true; - chan[1].std.init(ins); - furnaceDac=true; - } else { - if (c.value!=DIV_NOTE_NULL) { - chan[1].note=c.value; - } - dacSample=12*sampleBank+chan[1].note%12; - if (dacSample>=parent->song.sampleLen) { - dacSample=-1; - if (dumpWrites) addWrite(0xffff0002,0); - break; - } else { - if (dumpWrites) addWrite(0xffff0000,dacSample); - } - dacRate=parent->getSample(dacSample)->rate; - if (dumpWrites) { - addWrite(0xffff0001,dacRate); - } - chan[1].active=true; - chan[1].keyOn=true; - furnaceDac=false; + pcm=true; + } else if (furnaceDac) { + pcm=false; + } + if (pcm) { + if (skipRegisterWrites) break; + dacPos=0; + dacPeriod=0; + if (ins->type==DIV_INS_AMIGA) { + dacSample=ins->amiga.initSample; + if (dacSample<0 || dacSample>=parent->song.sampleLen) { + dacSample=-1; + if (dumpWrites) addWrite(0xffff0002,0); + break; + } else { + if (dumpWrites) { + addWrite(0xffff0000,dacSample); + } + } + if (c.value!=DIV_NOTE_NULL) { + chan[1].baseFreq=NOTE_PERIODIC(c.value); + chan[1].freqChanged=true; + chan[1].note=c.value; + } + chan[1].active=true; + chan[1].keyOn=true; + chan[1].std.init(ins); + furnaceDac=true; + } else { + if (c.value!=DIV_NOTE_NULL) { + chan[1].note=c.value; + } + dacSample=12*sampleBank+chan[1].note%12; + if (dacSample>=parent->song.sampleLen) { + dacSample=-1; + if (dumpWrites) addWrite(0xffff0002,0); + break; + } else { + if (dumpWrites) addWrite(0xffff0000,dacSample); + } + dacRate=parent->getSample(dacSample)->rate; + if (dumpWrites) { + addWrite(0xffff0001,dacRate); + } + chan[1].active=true; + chan[1].keyOn=true; + furnaceDac=false; + } + break; } - break; } if (c.value!=DIV_NOTE_NULL) { chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); @@ -412,7 +414,7 @@ int DivPlatformWS::dispatch(DivCommand c) { return 15; break; case DIV_ALWAYS_SET_VOLUME: - return 0; + return 1; break; default: break; @@ -456,6 +458,7 @@ void DivPlatformWS::reset() { chan[i]=Channel(); chan[i].vol=15; chan[i].pan=0xff; + rWrite(0x08+i,0xff); } if (dumpWrites) { addWrite(0xffffffff,0); diff --git a/src/engine/safeWriter.cpp b/src/engine/safeWriter.cpp index 0e7a118a2..7b6f0b1e5 100644 --- a/src/engine/safeWriter.cpp +++ b/src/engine/safeWriter.cpp @@ -80,6 +80,10 @@ int SafeWriter::writeC(signed char val) { int SafeWriter::writeS(short val) { return write(&val,2); } +int SafeWriter::writeS_BE(short val) { + unsigned char bytes[2]{(val>>8)&0xff, val&0xff}; + return write(bytes,2); +} int SafeWriter::writeI(int val) { return write(&val,4); diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index caa811481..c3388399f 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -451,7 +451,7 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write } else { // (Wave) RAM write w->writeC(0xc6); - w->writeS(baseAddr2S|(write.addr&0x3f)); + w->writeS_BE(baseAddr2S|(write.addr&0x3f)); w->writeC(write.val&0xff); } break; @@ -1061,9 +1061,9 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) { case DIV_SYSTEM_SWAN: w->writeC(0x90); w->writeC(streamID); - w->writeC(33); + w->writeC(isSecond[i]?0xa1:0x21); w->writeC(0); // port - w->writeC(isSecond[i]?0x89:0x09); // DAC + w->writeC(0x09); // DAC w->writeC(0x91); w->writeC(streamID); diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 04cf8ce49..98bbddb99 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -6398,6 +6398,12 @@ FurnaceGUI::FurnaceGUI(): 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "WonderSwan", { + DIV_SYSTEM_SWAN, 64, 0, 0, + 0 + } + )); sysCategories.push_back(cat); cat=FurnaceGUISysCategory("Computers"); From 56be067af63c319c5ff5e0475e5cf558a80626f0 Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Mon, 7 Mar 2022 15:44:15 +0700 Subject: [PATCH 136/637] No need to de-duplicate writes here --- src/engine/platform/ws.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/engine/platform/ws.cpp b/src/engine/platform/ws.cpp index 4e7c63309..1a842a227 100644 --- a/src/engine/platform/ws.cpp +++ b/src/engine/platform/ws.cpp @@ -97,11 +97,8 @@ void DivPlatformWS::acquire(short* bufL, short* bufR, size_t start, size_t len) // the rest while (!writes.empty()) { QueuedWrite w=writes.front(); - if (regPool[w.addr]!=w.val) { - if (w.addr<0x40) ws->SoundWrite(w.addr|0x80,w.val); - else ws->RAMWrite(w.addr&0x3f,w.val); - regPool[w.addr]=w.val; - } + if (w.addr<0x40) ws->SoundWrite(w.addr|0x80,w.val); + else ws->RAMWrite(w.addr&0x3f,w.val); writes.pop(); } int16_t samp[2]{0, 0}; From 252dc16492da387afc72c622c99a50660de6abf9 Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Mon, 7 Mar 2022 16:45:34 +0700 Subject: [PATCH 137/637] Add X16 to the New menu --- src/gui/gui.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index f4fd48a28..79335dfdf 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -6457,6 +6457,13 @@ FurnaceGUI::FurnaceGUI(): 0 } ));*/ + cat.systems.push_back(FurnaceGUISysDef( + "Commander X16", { + DIV_SYSTEM_YM2151, 64, 0, 0, + DIV_SYSTEM_VERA, 64, 0, 0, + 0 + } + )); sysCategories.push_back(cat); cat=FurnaceGUISysCategory("Arcade systems"); From 7f3519b9708957b8e556b0d2314ac88b73e678fd Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Mon, 7 Mar 2022 16:46:12 +0700 Subject: [PATCH 138/637] Implement VERA noise generation instead of rand() --- src/engine/platform/vera.cpp | 11 ++++++++--- src/engine/platform/vera.h | 5 ++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/engine/platform/vera.cpp b/src/engine/platform/vera.cpp index 47f4888d6..ce45341be 100644 --- a/src/engine/platform/vera.cpp +++ b/src/engine/platform/vera.cpp @@ -62,14 +62,17 @@ void DivPlatformVERA::acquire(short* bufL, short* bufR, size_t start, size_t len int32_t lout=0; int32_t rout=0; // PSG + // TODO this is a currently speculated noise generation + // as the hardware and sources for it are not out in the public + // and the official emulator just uses rand() + noiseState=(noiseState<<1)|(((noiseState>>1)^(noiseState>>2)^(noiseState>>4)^(noiseState>>15))&1); + noiseOut=((noiseOut<<1)|(noiseState&1))&63; for (int i=0; i<16; i++) { unsigned freq=regPool[i*4+0] | (regPool[i*4+1] << 8); unsigned old_accum=chan[i].accum; unsigned new_accum=old_accum+freq; int val=0x20; - // TODO actually emulate the LFSR, it's currently unknown publicly - // and the official emulator just uses this: - if ((old_accum^new_accum)&0x10000) chan[i].noiseval=rand()&63; + if ((old_accum^new_accum)&0x10000) chan[i].noiseval=noiseOut; new_accum&=0x1ffff; chan[i].accum=new_accum; switch (regPool[i*4+3]>>6) { @@ -136,6 +139,8 @@ void DivPlatformVERA::reset() { rWriteHi(i,2,3); // default pan } chan[16].vol=15; + noiseState=1; + noiseOut=0; } int DivPlatformVERA::calcNoteFreq(int ch, int note) { diff --git a/src/engine/platform/vera.h b/src/engine/platform/vera.h index c6a208b90..64a8ee7c1 100644 --- a/src/engine/platform/vera.h +++ b/src/engine/platform/vera.h @@ -46,8 +46,10 @@ class DivPlatformVERA: public DivDispatch { }; Channel chan[17]; bool isMuted[17]; + unsigned noiseState, noiseOut; unsigned char regPool[66]; + int calcNoteFreq(int ch, int note); friend void putDispatchChan(void*,int,int); public: @@ -67,8 +69,5 @@ class DivPlatformVERA: public DivDispatch { const char* getEffectName(unsigned char effect); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); ~DivPlatformVERA(); - - private: - int calcNoteFreq(int ch, int note); }; #endif From b270513639dc06acef4b9d0de3c62871476cf8f2 Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 7 Mar 2022 19:41:26 +0900 Subject: [PATCH 139/637] Frequency range limit --- papers/doc/7-systems/x1_010.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/papers/doc/7-systems/x1_010.md b/papers/doc/7-systems/x1_010.md index 83587a2b8..7d5778713 100644 --- a/papers/doc/7-systems/x1_010.md +++ b/papers/doc/7-systems/x1_010.md @@ -15,7 +15,7 @@ In furnace, this chip is can be configurable for original arcade mono output or - `10xx`: change wave. - `11xx`: change envelope shape. (also wavetable) - `17xx`: toggle PCM mode. -- `20xx`: set PCM frequency.* +- `20xx`: set PCM frequency. (1 to FF)* - `22xx`: set envelope mode. - bit 0 sets whether envelope will affect this channel. - bit 1 sets whether envelope one-shot mode. when it sets, channel is halted after envelope cycle is ended. From 8a924da586e9d60d85d61946251a22411269bd4c Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Mon, 7 Mar 2022 18:55:25 +0700 Subject: [PATCH 140/637] Fix narrowing conversion error --- src/engine/safeWriter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/safeWriter.cpp b/src/engine/safeWriter.cpp index 7b6f0b1e5..f29800a4a 100644 --- a/src/engine/safeWriter.cpp +++ b/src/engine/safeWriter.cpp @@ -81,7 +81,7 @@ int SafeWriter::writeS(short val) { return write(&val,2); } int SafeWriter::writeS_BE(short val) { - unsigned char bytes[2]{(val>>8)&0xff, val&0xff}; + unsigned char bytes[2]{(unsigned char)((val>>8)&0xff), (unsigned char)(val&0xff)}; return write(bytes,2); } From b8ea64b8014a16792049ddfb98fc937582a0f649 Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Mon, 7 Mar 2022 19:04:20 +0700 Subject: [PATCH 141/637] Rename WS to Swan --- CMakeLists.txt | 4 +- src/engine/dispatchContainer.cpp | 4 +- .../platform/sound/{ws.cpp => swan.cpp} | 2 +- src/engine/platform/sound/{ws.h => swan.h} | 0 src/engine/platform/{ws.cpp => swan.cpp} | 46 +++++++++---------- src/engine/platform/{ws.h => swan.h} | 10 ++-- 6 files changed, 33 insertions(+), 33 deletions(-) rename src/engine/platform/sound/{ws.cpp => swan.cpp} (99%) rename src/engine/platform/sound/{ws.h => swan.h} (100%) rename src/engine/platform/{ws.cpp => swan.cpp} (91%) rename src/engine/platform/{ws.h => swan.h} (95%) diff --git a/CMakeLists.txt b/CMakeLists.txt index f31213030..9edfc2528 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -265,7 +265,7 @@ src/engine/platform/sound/lynx/Mikey.cpp src/engine/platform/sound/qsound.c -src/engine/platform/sound/ws.cpp +src/engine/platform/sound/swan.cpp src/engine/platform/ym2610Interface.cpp @@ -309,7 +309,7 @@ src/engine/platform/pcspkr.cpp src/engine/platform/segapcm.cpp src/engine/platform/qsound.cpp src/engine/platform/lynx.cpp -src/engine/platform/ws.cpp +src/engine/platform/swan.cpp src/engine/platform/dummy.cpp ) diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 0bc6a2729..8a597cbea 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -41,7 +41,7 @@ #include "platform/pcspkr.h" #include "platform/segapcm.h" #include "platform/qsound.h" -#include "platform/ws.h" +#include "platform/swan.h" #include "platform/dummy.h" #include "platform/lynx.h" #include "../ta-log.h" @@ -236,7 +236,7 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do dispatch=new DivPlatformSegaPCM; break; case DIV_SYSTEM_SWAN: - dispatch=new DivPlatformWS; + dispatch=new DivPlatformSwan; break; default: logW("this system is not supported yet! using dummy platform.\n"); diff --git a/src/engine/platform/sound/ws.cpp b/src/engine/platform/sound/swan.cpp similarity index 99% rename from src/engine/platform/sound/ws.cpp rename to src/engine/platform/sound/swan.cpp index e02d63ec6..60052f617 100644 --- a/src/engine/platform/sound/ws.cpp +++ b/src/engine/platform/sound/swan.cpp @@ -20,7 +20,7 @@ ** 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -#include "ws.h" +#include "swan.h" #include #define MK_SAMPLE_CACHE \ diff --git a/src/engine/platform/sound/ws.h b/src/engine/platform/sound/swan.h similarity index 100% rename from src/engine/platform/sound/ws.h rename to src/engine/platform/sound/swan.h diff --git a/src/engine/platform/ws.cpp b/src/engine/platform/swan.cpp similarity index 91% rename from src/engine/platform/ws.cpp rename to src/engine/platform/swan.cpp index 1a842a227..c86f2f70a 100644 --- a/src/engine/platform/ws.cpp +++ b/src/engine/platform/swan.cpp @@ -17,7 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "ws.h" +#include "swan.h" #include "../engine.h" #include @@ -46,11 +46,11 @@ const char* regCheatSheetWS[]={ NULL }; -const char** DivPlatformWS::getRegisterSheet() { +const char** DivPlatformSwan::getRegisterSheet() { return regCheatSheetWS; } -const char* DivPlatformWS::getEffectName(unsigned char effect) { +const char* DivPlatformSwan::getEffectName(unsigned char effect) { switch (effect) { case 0x10: return "10xx: Change waveform"; @@ -71,7 +71,7 @@ const char* DivPlatformWS::getEffectName(unsigned char effect) { return NULL; } -void DivPlatformWS::acquire(short* bufL, short* bufR, size_t start, size_t len) { +void DivPlatformSwan::acquire(short* bufL, short* bufR, size_t start, size_t len) { for (size_t h=start; hgetWave(chan[ch].wave); unsigned char addr=0x40+ch*16; if (wt->max<1 || wt->len<1) { @@ -125,7 +125,7 @@ void DivPlatformWS::updateWave(int ch) { } } -void DivPlatformWS::calcAndWriteOutVol(int ch, int env) { +void DivPlatformSwan::calcAndWriteOutVol(int ch, int env) { int vl=chan[ch].vol*((chan[ch].pan>>4)&0x0f)*env/225; int vr=chan[ch].vol*(chan[ch].pan&0x0f)*env/225; if (ch==1&&pcm) { @@ -138,7 +138,7 @@ void DivPlatformWS::calcAndWriteOutVol(int ch, int env) { writeOutVol(ch); } -void DivPlatformWS::writeOutVol(int ch) { +void DivPlatformSwan::writeOutVol(int ch) { unsigned char val=isMuted[ch]?0:chan[ch].outVol; if (ch==1&&pcm) { rWrite(0x14,val) @@ -147,7 +147,7 @@ void DivPlatformWS::writeOutVol(int ch) { } } -void DivPlatformWS::tick() { +void DivPlatformSwan::tick() { unsigned char sndCtrl=(pcm?0x20:0)|(sweep?0x40:0)|((noise>0)?0x80:0); for (int i=0; i<4; i++) { chan[i].std.next(); @@ -230,7 +230,7 @@ void DivPlatformWS::tick() { rWrite(0x10,sndCtrl); } -int DivPlatformWS::dispatch(DivCommand c) { +int DivPlatformSwan::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(chan[c.chan].ins); @@ -419,12 +419,12 @@ int DivPlatformWS::dispatch(DivCommand c) { return 1; } -void DivPlatformWS::muteChannel(int ch, bool mute) { +void DivPlatformSwan::muteChannel(int ch, bool mute) { isMuted[ch]=mute; writeOutVol(ch); } -void DivPlatformWS::forceIns() { +void DivPlatformSwan::forceIns() { for (int i=0; i<4; i++) { chan[i].insChanged=true; chan[i].freqChanged=true; @@ -433,22 +433,22 @@ void DivPlatformWS::forceIns() { } } -void* DivPlatformWS::getChanState(int ch) { +void* DivPlatformSwan::getChanState(int ch) { return &chan[ch]; } -unsigned char* DivPlatformWS::getRegisterPool() { +unsigned char* DivPlatformSwan::getRegisterPool() { // get Random from emulator regPool[0x12]=ws->SoundRead(0x92); regPool[0x13]=ws->SoundRead(0x93); return regPool; } -int DivPlatformWS::getRegisterPoolSize() { +int DivPlatformSwan::getRegisterPoolSize() { return 128; } -void DivPlatformWS::reset() { +void DivPlatformSwan::reset() { while (!writes.empty()) writes.pop(); memset(regPool,0,128); for (int i=0; i<4; i++) { @@ -474,11 +474,11 @@ void DivPlatformWS::reset() { rWrite(0x11,0x09); // enable speakers } -bool DivPlatformWS::isStereo() { +bool DivPlatformSwan::isStereo() { return true; } -void DivPlatformWS::notifyWaveChange(int wave) { +void DivPlatformSwan::notifyWaveChange(int wave) { for (int i=0; i<4; i++) { if (chan[i].wave==wave) { updateWave(i); @@ -486,21 +486,21 @@ void DivPlatformWS::notifyWaveChange(int wave) { } } -void DivPlatformWS::notifyInsDeletion(void* ins) { +void DivPlatformSwan::notifyInsDeletion(void* ins) { for (int i=0; i<4; i++) { chan[i].std.notifyInsDeletion((DivInstrument*)ins); } } -void DivPlatformWS::poke(unsigned int addr, unsigned short val) { +void DivPlatformSwan::poke(unsigned int addr, unsigned short val) { rWrite(addr,val); } -void DivPlatformWS::poke(std::vector& wlist) { +void DivPlatformSwan::poke(std::vector& wlist) { for (DivRegWrite& i: wlist) rWrite(i.addr,i.val); } -int DivPlatformWS::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { +int DivPlatformSwan::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { parent=p; dumpWrites=false; skipRegisterWrites=false; @@ -514,9 +514,9 @@ int DivPlatformWS::init(DivEngine* p, int channels, int sugRate, unsigned int fl return 4; } -void DivPlatformWS::quit() { +void DivPlatformSwan::quit() { delete ws; } -DivPlatformWS::~DivPlatformWS() { +DivPlatformSwan::~DivPlatformSwan() { } diff --git a/src/engine/platform/ws.h b/src/engine/platform/swan.h similarity index 95% rename from src/engine/platform/ws.h rename to src/engine/platform/swan.h index ea6466fbb..47470f57b 100644 --- a/src/engine/platform/ws.h +++ b/src/engine/platform/swan.h @@ -17,15 +17,15 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#ifndef _WS_H -#define _WS_H +#ifndef _SWAN_H +#define _SWAN_H #include "../dispatch.h" #include "../macroInt.h" -#include "sound/ws.h" +#include "sound/swan.h" #include -class DivPlatformWS: public DivDispatch { +class DivPlatformSwan: public DivDispatch { struct Channel { int freq, baseFreq, pitch, note; unsigned char ins, pan; @@ -86,7 +86,7 @@ class DivPlatformWS: public DivDispatch { const char* getEffectName(unsigned char effect); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); - ~DivPlatformWS(); + ~DivPlatformSwan(); private: void calcAndWriteOutVol(int ch, int env); void writeOutVol(int ch); From bbaf31d0c21c2ecb50ecc90f26a6f11a5fc6167a Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Mon, 7 Mar 2022 19:08:18 +0700 Subject: [PATCH 142/637] Make register view work again --- src/engine/platform/swan.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/engine/platform/swan.cpp b/src/engine/platform/swan.cpp index c86f2f70a..962fbbd1e 100644 --- a/src/engine/platform/swan.cpp +++ b/src/engine/platform/swan.cpp @@ -97,6 +97,7 @@ void DivPlatformSwan::acquire(short* bufL, short* bufR, size_t start, size_t len // the rest while (!writes.empty()) { QueuedWrite w=writes.front(); + regPool[w.addr]=w.val; if (w.addr<0x40) ws->SoundWrite(w.addr|0x80,w.val); else ws->RAMWrite(w.addr&0x3f,w.val); writes.pop(); From 65149a466f5ec69a9fe5a20b409c844ec956318a Mon Sep 17 00:00:00 2001 From: cam900 Date: Tue, 8 Mar 2022 00:15:21 +0900 Subject: [PATCH 143/637] Fix accidently auto-generated spaces --- src/engine/engine.cpp | 4 +-- src/engine/fileOps.cpp | 8 +++--- src/engine/platform/x1_010.cpp | 44 ++++++++++++++++----------------- src/engine/platform/x1_010.h | 6 ++--- src/engine/platform/ym2610b.cpp | 2 +- src/engine/playback.cpp | 2 +- src/engine/sample.cpp | 2 +- src/engine/vgmOps.cpp | 2 +- src/gui/gui.cpp | 2 +- src/gui/insEdit.cpp | 12 ++++----- 10 files changed, 42 insertions(+), 42 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index e484baf31..87594f339 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -541,7 +541,7 @@ void DivEngine::renderSamples() { for (int i=0; ilength8+4095)&(~0xfff); - // fit sample bank size to 128KB for Seta 2 external bankswitching logic (not emulated yet!) + // fit sample bank size to 128KB for Seta 2 external bankswitching logic (not emulated yet!) if (paddedLen>131072) { paddedLen=131072; } @@ -977,7 +977,7 @@ int DivEngine::getEffectiveSampleRate(int rate) { return 1789773/(1789773/rate); case DIV_SYSTEM_SEGAPCM: case DIV_SYSTEM_SEGAPCM_COMPAT: return (31250*MIN(255,(rate*255/31250)))/255; - case DIV_SYSTEM_QSOUND: + case DIV_SYSTEM_QSOUND: return (24038*MIN(65535,(rate*4096/24038)))/4096; case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_EXT: case DIV_SYSTEM_YM2610_FULL: case DIV_SYSTEM_YM2610_FULL_EXT: case DIV_SYSTEM_YM2610B: case DIV_SYSTEM_YM2610B_EXT: return 18518; // TODO: support ADPCM-B case diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index ec7cd1cb8..3a79cebed 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -149,8 +149,8 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { // Neo Geo detune if (ds.system[0]==DIV_SYSTEM_YM2610 || ds.system[0]==DIV_SYSTEM_YM2610_EXT - || ds.system[0]==DIV_SYSTEM_YM2610_FULL || ds.system[0]==DIV_SYSTEM_YM2610_FULL_EXT - || ds.system[0]==DIV_SYSTEM_YM2610B || ds.system[0]==DIV_SYSTEM_YM2610B_EXT) { + || ds.system[0]==DIV_SYSTEM_YM2610_FULL || ds.system[0]==DIV_SYSTEM_YM2610_FULL_EXT + || ds.system[0]==DIV_SYSTEM_YM2610B || ds.system[0]==DIV_SYSTEM_YM2610B_EXT) { ds.tuning=443.23; } @@ -259,8 +259,8 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { ins->type=DIV_INS_C64; } if (ds.system[0]==DIV_SYSTEM_YM2610 || ds.system[0]==DIV_SYSTEM_YM2610_EXT - || ds.system[0]==DIV_SYSTEM_YM2610_FULL || ds.system[0]==DIV_SYSTEM_YM2610_FULL_EXT - || ds.system[0]==DIV_SYSTEM_YM2610B || ds.system[0]==DIV_SYSTEM_YM2610B_EXT) { + || ds.system[0]==DIV_SYSTEM_YM2610_FULL || ds.system[0]==DIV_SYSTEM_YM2610_FULL_EXT + || ds.system[0]==DIV_SYSTEM_YM2610B || ds.system[0]==DIV_SYSTEM_YM2610B_EXT) { if (!ins->mode) { ins->type=DIV_INS_AY; } diff --git a/src/engine/platform/x1_010.cpp b/src/engine/platform/x1_010.cpp index 74bfdb13a..1705e8272 100644 --- a/src/engine/platform/x1_010.cpp +++ b/src/engine/platform/x1_010.cpp @@ -342,7 +342,7 @@ void DivPlatformX1_010::tick() { chan[i].envChanged=true; } } - if ((!chan[i].pcm) || chan[i].furnacePCM) { + if ((!chan[i].pcm) || chan[i].furnacePCM) { if (chan[i].std.hadArp) { if (!chan[i].inPorta) { if (chan[i].std.arpMode) { @@ -358,7 +358,7 @@ void DivPlatformX1_010::tick() { chan[i].freqChanged=true; } } - } + } if (chan[i].std.hadWave && !chan[i].pcm) { if (chan[i].wave!=chan[i].std.wave) { chan[i].wave=chan[i].std.wave; @@ -369,7 +369,7 @@ void DivPlatformX1_010::tick() { } } if (chan[i].std.hadEx1) { - bool nextEnable=(chan[i].std.ex1&1); + bool nextEnable=(chan[i].std.ex1&1); if (nextEnable!=(chan[i].env.flag.envEnable)) { chan[i].env.flag.envEnable=nextEnable; if (!chan[i].pcm) { @@ -379,28 +379,28 @@ void DivPlatformX1_010::tick() { refreshControl(i); } } - bool nextOneshot=(chan[i].std.ex1&2); + bool nextOneshot=(chan[i].std.ex1&2); if (nextOneshot!=(chan[i].env.flag.envOneshot)) { chan[i].env.flag.envOneshot=nextOneshot; if (!chan[i].pcm) { refreshControl(i); } } - bool nextSplit=(chan[i].std.ex1&4); + bool nextSplit=(chan[i].std.ex1&4); if (nextSplit!=(chan[i].env.flag.envSplit)) { chan[i].env.flag.envSplit=nextSplit; if (!isMuted[i] && !chan[i].pcm) { chan[i].envChanged=true; } } - bool nextHinv=(chan[i].std.ex1&8); + bool nextHinv=(chan[i].std.ex1&8); if (nextHinv!=(chan[i].env.flag.envHinv)) { chan[i].env.flag.envHinv=nextHinv; if (!isMuted[i] && !chan[i].pcm) { chan[i].envChanged=true; } } - bool nextVinv=(chan[i].std.ex1&16); + bool nextVinv=(chan[i].std.ex1&16); if (nextVinv!=(chan[i].env.flag.envVinv)) { chan[i].env.flag.envVinv=nextVinv; if (!isMuted[i] && !chan[i].pcm) { @@ -435,7 +435,7 @@ void DivPlatformX1_010::tick() { } if (chan[i].envChanged) { if (!isMuted[i]) { - chan[i].lvol=((chan[i].outVol&0xf)*((chan[i].pan>>4)&0xf))/15; + chan[i].lvol=((chan[i].outVol&0xf)*((chan[i].pan>>4)&0xf))/15; chan[i].rvol=((chan[i].outVol&0xf)*((chan[i].pan>>0)&0xf))/15; } updateEnvelope(i); @@ -500,10 +500,10 @@ int DivPlatformX1_010::dispatch(DivCommand c) { } if (skipRegisterWrites) break; if (chan[c.chan].furnacePCM) { - chan[c.chan].pcm=true; + chan[c.chan].pcm=true; chan[c.chan].std.init(ins); - chan[c.chan].sample=ins->amiga.initSample; - if (chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { + chan[c.chan].sample=ins->amiga.initSample; + if (chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { DivSample* s=parent->getSample(chan[c.chan].sample); chWrite(c.chan,4,(s->offX1_010>>12)&0xff); int end=(s->offX1_010+s->length8+0xfff)&~0xfff; // padded @@ -513,7 +513,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { chan[c.chan].baseFreq=NoteX1_010(c.chan,chan[c.chan].note); chan[c.chan].freqChanged=true; } - } else { + } else { chan[c.chan].std.init(NULL); chan[c.chan].outVol=chan[c.chan].vol; if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) { @@ -524,7 +524,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { chWrite(c.chan,5,0); break; } - } + } } else { chan[c.chan].std.init(NULL); chan[c.chan].outVol=chan[c.chan].vol; @@ -552,7 +552,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { chan[c.chan].keyOn=true; chan[c.chan].envChanged=true; chan[c.chan].std.init(ins); - refreshControl(c.chan); + refreshControl(c.chan); break; } case DIV_CMD_NOTE_OFF: @@ -598,7 +598,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { updateWave(c.chan); chan[c.chan].keyOn=true; break; - case DIV_CMD_X1_010_ENVELOPE_SHAPE: + case DIV_CMD_X1_010_ENVELOPE_SHAPE: if (chan[c.chan].env.shape!=c.value) { chan[c.chan].env.shape=c.value; if (!chan[c.chan].pcm) { @@ -677,7 +677,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { } break; case DIV_CMD_X1_010_ENVELOPE_MODE: { - bool nextEnable=c.value&1; + bool nextEnable=c.value&1; if (nextEnable!=(chan[c.chan].env.flag.envEnable)) { chan[c.chan].env.flag.envEnable=nextEnable; if (!chan[c.chan].pcm) { @@ -687,28 +687,28 @@ int DivPlatformX1_010::dispatch(DivCommand c) { refreshControl(c.chan); } } - bool nextOneshot=c.value&2; + bool nextOneshot=c.value&2; if (nextOneshot!=(chan[c.chan].env.flag.envOneshot)) { chan[c.chan].env.flag.envOneshot=nextOneshot; if (!chan[c.chan].pcm) { refreshControl(c.chan); } } - bool nextSplit=c.value&4; + bool nextSplit=c.value&4; if (nextSplit!=(chan[c.chan].env.flag.envSplit)) { chan[c.chan].env.flag.envSplit=nextSplit; if (!isMuted[c.chan] && !chan[c.chan].pcm) { chan[c.chan].envChanged=true; } } - bool nextHinv=c.value&8; + bool nextHinv=c.value&8; if (nextHinv!=(chan[c.chan].env.flag.envHinv)) { chan[c.chan].env.flag.envHinv=nextHinv; if (!isMuted[c.chan] && !chan[c.chan].pcm) { chan[c.chan].envChanged=true; } } - bool nextVinv=c.value&16; + bool nextVinv=c.value&16; if (nextVinv!=(chan[c.chan].env.flag.envVinv)) { chan[c.chan].env.flag.envVinv=nextVinv; if (!isMuted[c.chan] && !chan[c.chan].pcm) { @@ -716,7 +716,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { } } break; - } + } case DIV_CMD_X1_010_ENVELOPE_PERIOD: chan[c.chan].env.period=c.value; if (!chan[c.chan].pcm) { @@ -816,7 +816,7 @@ void DivPlatformX1_010::setFlags(unsigned int flags) { case 1: // 16.67MHz (later hardwares) chipClock=50000000.0/3.0; break; - // Other clock is used? + // Other clock is used? } rate=chipClock/512; stereo=flags&16; diff --git a/src/engine/platform/x1_010.h b/src/engine/platform/x1_010.h index fd00fd1c9..8e03109ba 100644 --- a/src/engine/platform/x1_010.h +++ b/src/engine/platform/x1_010.h @@ -31,9 +31,9 @@ class DivX1_010Interface: public vgsound_emu_mem_intf { DivEngine* parent; int sampleBank; virtual u8 read_byte(u32 address) override { - if (parent->x1_010Mem==NULL) return 0; - return parent->x1_010Mem[address & 0xfffff]; - } + if (parent->x1_010Mem==NULL) return 0; + return parent->x1_010Mem[address & 0xfffff]; + } DivX1_010Interface(): parent(NULL), sampleBank(0) {} }; diff --git a/src/engine/platform/ym2610b.cpp b/src/engine/platform/ym2610b.cpp index a6bcf9ac7..e7a6e45c2 100644 --- a/src/engine/platform/ym2610b.cpp +++ b/src/engine/platform/ym2610b.cpp @@ -752,7 +752,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { chan[c.chan].std.init(ins); if (!chan[c.chan].std.willVol) { chan[c.chan].outVol=chan[c.chan].vol; - immWrite(0x1b,chan[c.chan].outVol); + immWrite(0x1b,chan[c.chan].outVol); } DivSample* s=parent->getSample(ins->amiga.initSample); immWrite(0x12,(s->offB>>8)&0xff); diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 665409d67..b1a820952 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -256,7 +256,7 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe return false; } break; - } + } break; case DIV_SYSTEM_X1_010: switch (effect) { diff --git a/src/engine/sample.cpp b/src/engine/sample.cpp index b802bf791..571e35118 100644 --- a/src/engine/sample.cpp +++ b/src/engine/sample.cpp @@ -115,7 +115,7 @@ bool DivSample::initInternal(unsigned char d, int count) { case 8: // 8-bit if (data8!=NULL) delete[] data8; length8=count; - // for padding X1-010 sample + // for padding X1-010 sample data8=new signed char[(count+4095)&(~0xfff)]; memset(data8,0,(count+4095)&(~0xfff)); break; diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index 3c63f65c9..74654f6ea 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -679,7 +679,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) { } if (((song.system[i]==DIV_SYSTEM_YM2610B) || (song.system[i]==DIV_SYSTEM_YM2610B_EXT)) && (!(hasOPNB&0x80000000))) { // YM2610B flag hasOPNB|=0x80000000; - } + } break; case DIV_SYSTEM_AY8910: case DIV_SYSTEM_AY8930: diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 743f91083..50a2db78f 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4959,7 +4959,7 @@ bool FurnaceGUI::loop() { updateWindowTitle(); } break; - } + } case DIV_SYSTEM_GB: case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_EXT: diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index a66a7f585..7ce11531e 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1392,12 +1392,12 @@ void FurnaceGUI::drawInsEdit() { ex1Max=4; ex2Max=15; } - if (ins->type==DIV_INS_X1_010) { - dutyMax=0; - ex1Max=5; - ex2Max=63; - ex2Bit=false; - } + if (ins->type==DIV_INS_X1_010) { + dutyMax=0; + ex1Max=5; + ex2Max=63; + ex2Bit=false; + } if (ins->type==DIV_INS_SAA1099) ex1Max=8; if (settings.macroView==0) { // modern view From 26470d594e27cfb8f67026b83a45d45871b8284c Mon Sep 17 00:00:00 2001 From: cam900 Date: Tue, 8 Mar 2022 00:43:16 +0900 Subject: [PATCH 144/637] Actually PCM frequency limit --- src/engine/platform/x1_010.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/x1_010.cpp b/src/engine/platform/x1_010.cpp index 1705e8272..8fce448e8 100644 --- a/src/engine/platform/x1_010.cpp +++ b/src/engine/platform/x1_010.cpp @@ -669,7 +669,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { break; case DIV_CMD_SAMPLE_FREQ: if (chan[c.chan].pcm) { - chan[c.chan].freq=c.value&0xff; + chan[c.chan].freq=MAX(1,c.value&0xff); chWrite(c.chan,2,chan[c.chan].freq&0xff); if (chRead(c.chan,0)&1) { refreshControl(c.chan); From 3f4966096ab24348c84acfd6e903abcdd27fb1d4 Mon Sep 17 00:00:00 2001 From: cam900 Date: Tue, 8 Mar 2022 00:44:37 +0900 Subject: [PATCH 145/637] Fix info --- src/engine/platform/x1_010.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/x1_010.cpp b/src/engine/platform/x1_010.cpp index 8fce448e8..331b38420 100644 --- a/src/engine/platform/x1_010.cpp +++ b/src/engine/platform/x1_010.cpp @@ -216,7 +216,7 @@ const char* DivPlatformX1_010::getEffectName(unsigned char effect) { return "17xx: Toggle PCM mode"; break; case 0x20: - return "20xx: Set PCM frequency"; + return "20xx: Set PCM frequency (1 to FF)"; break; case 0x22: return "22xx: Set envelope mode (bit 0: enable, bit 1: one-shot, bit 2: split shape to L/R, bit 3: H.invert right, bit 4: V.invert right)"; From 7704dc0d79d1916bf59b8facfac8e0b39743556a Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Mon, 7 Mar 2022 23:49:52 +0700 Subject: [PATCH 146/637] Fix volume calculation sometimes not working --- src/engine/platform/swan.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/engine/platform/swan.cpp b/src/engine/platform/swan.cpp index 962fbbd1e..e7cefc464 100644 --- a/src/engine/platform/swan.cpp +++ b/src/engine/platform/swan.cpp @@ -152,7 +152,7 @@ void DivPlatformSwan::tick() { unsigned char sndCtrl=(pcm?0x20:0)|(sweep?0x40:0)|((noise>0)?0x80:0); for (int i=0; i<4; i++) { chan[i].std.next(); - if (chan[i].std.hadVol) { + if (chan[i].std.willVol) { int env=chan[i].std.vol; if(parent->getIns(chan[i].ins)->type==DIV_INS_AMIGA) { env=MIN(env/4,15); @@ -204,7 +204,7 @@ void DivPlatformSwan::tick() { rWrite(i*2,rVal&0xff); rWrite(i*2+1,rVal>>8); if (chan[i].keyOn) { - if (!chan[i].std.hasVol) { + if (!chan[i].std.willVol) { calcAndWriteOutVol(i,15); } if (chan[i].wave<0) { @@ -320,7 +320,7 @@ int DivPlatformSwan::dispatch(DivCommand c) { case DIV_CMD_VOLUME: if (chan[c.chan].vol!=c.value) { chan[c.chan].vol=c.value; - if (!chan[c.chan].std.hasVol) { + if (!chan[c.chan].std.willVol) { calcAndWriteOutVol(c.chan,15); } } @@ -392,7 +392,7 @@ int DivPlatformSwan::dispatch(DivCommand c) { break; case DIV_CMD_PANNING: { chan[c.chan].pan=c.value; - if (!chan[c.chan].std.hasVol) { + if (!chan[c.chan].std.willVol) { calcAndWriteOutVol(c.chan,15); } break; From 27758434af1a80afe7935ab0150de30e13938830 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 7 Mar 2022 17:07:00 -0500 Subject: [PATCH 147/637] update format.md to parse op count --- papers/format.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/papers/format.md b/papers/format.md index ff068d830..7bc30abe0 100644 --- a/papers/format.md +++ b/papers/format.md @@ -248,7 +248,9 @@ size | description 1 | feedback 1 | fms 1 | ams - 1 | operator count (always 4) + 1 | operator count + | - this is either 2 or 4, and is ignored on non-OPL systems. + | - always read 4 ops regardless of this value. 1 | OPLL preset (>=60) or reserved | - 0: custom | - 1-15: pre-defined patches From 03d2f87804c009d78b7c1c435b436dc41f27db33 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 7 Mar 2022 17:07:29 -0500 Subject: [PATCH 148/637] OPL: some work - still does not work --- src/engine/dispatchContainer.cpp | 21 +++++ src/engine/platform/opl.cpp | 150 ++++++++++++++++++------------- src/engine/platform/opl.h | 15 ++-- 3 files changed, 114 insertions(+), 72 deletions(-) diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 52cc7177e..0b4c9767f 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -211,8 +211,29 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do ((DivPlatformOPLL*)dispatch)->setVRC7(sys==DIV_SYSTEM_VRC7); ((DivPlatformOPLL*)dispatch)->setProperDrums(sys==DIV_SYSTEM_OPLL_DRUMS); break; + case DIV_SYSTEM_OPL: + dispatch=new DivPlatformOPL; + ((DivPlatformOPL*)dispatch)->setOPLType(1,false); + break; + case DIV_SYSTEM_OPL_DRUMS: + dispatch=new DivPlatformOPL; + ((DivPlatformOPL*)dispatch)->setOPLType(1,true); + break; + case DIV_SYSTEM_OPL2: + dispatch=new DivPlatformOPL; + ((DivPlatformOPL*)dispatch)->setOPLType(2,false); + break; + case DIV_SYSTEM_OPL2_DRUMS: + dispatch=new DivPlatformOPL; + ((DivPlatformOPL*)dispatch)->setOPLType(2,true); + break; case DIV_SYSTEM_OPL3: dispatch=new DivPlatformOPL; + ((DivPlatformOPL*)dispatch)->setOPLType(3,false); + break; + case DIV_SYSTEM_OPL3_DRUMS: + dispatch=new DivPlatformOPL; + ((DivPlatformOPL*)dispatch)->setOPLType(3,true); break; case DIV_SYSTEM_SAA1099: { int saaCore=eng->getConfInt("saaCore",0); diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index ebf7dc927..50cdda389 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -44,7 +44,7 @@ const unsigned char slotsOPL2Drums[4][20]={ {N, N, N, N, N, N, N, N, N, N, N} }; -const unsigned char chanMapOPL2[20]={ +const unsigned short chanMapOPL2[20]={ 0, 1, 2, 3, 4, 5, 6, 7, 8, N, N, N, N, N, N, N, N, N, N, N }; @@ -62,12 +62,51 @@ const unsigned char slotsOPL3Drums[4][20]={ {9, N, 10, N, 11, N, 27, N, 28, N, 29, N, N, N, N, N, N, N, N, N} // OP4 }; -const unsigned char chanMapOPL3[20]={ - 0, 3, 1, 4, 2, 5, 9, 12, 10, 13, 11, 14, 15, 16, 17, 6, 7, 8, N, N +const unsigned short chanMapOPL3[20]={ + 0, 3, 1, 4, 2, 5, 0x100, 0x103, 0x101, 0x104, 0x102, 0x105, 0x106, 0x107, 0x108, 6, 7, 8, N, N +}; + +const unsigned int slotMap[36]={ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, + + 0x100, 0x101, 0x102, 0x103, 0x104, 0x105, + 0x108, 0x109, 0x10a, 0x10b, 0x10c, 0x10d, + 0x110, 0x111, 0x112, 0x113, 0x114, 0x115, +}; + +const bool isOutputL[2][4][4]={ + { // 2-op + {false, true, false, false}, // 1 > 2 + { true, true, false, false}, // 1 + 2 + {false, true, false, false}, // ditto, 0 + { true, true, false, false}, // ditto, 1 + }, + { // 4-op + {false, false, false, true}, // 1 > 2 > 3 > 4 + { true, false, false, true}, // 1 + (2 > 3 > 4) + {false, true, false, true}, // (1 > 2) + (3 > 4) + { true, false, true, true} // 1 + (2 > 3) + 4 + } }; #undef N +const int orderedOpsL[4]={ + 0,2,1,3 +}; + +#define ADDR_AM_VIB_SUS_KSR_MULT 0x20 +#define ADDR_KSL_TL 0x40 +#define ADDR_AR_DR 0x60 +#define ADDR_SL_RR 0x80 +#define ADDR_WS 0xe0 + +#define ADDR_FREQ 0xa0 +#define ADDR_FREQH 0xb0 +#define ADDR_LR_FB_ALG 0xc0 + const char* DivPlatformOPL::getEffectName(unsigned char effect) { switch (effect) { case 0x10: @@ -409,38 +448,43 @@ int DivPlatformOPL::dispatch(DivCommand c) { if (!chan[c.chan].std.willVol) { chan[c.chan].outVol=chan[c.chan].vol; } - - /* - for (int i=0; i<4; i++) { - unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; - DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; - if (isMuted[c.chan]) { - rWrite(baseAddr+ADDR_TL,127); - } else { - if (isOutput[chan[c.chan].state.alg][i]) { - if (!chan[c.chan].active || chan[c.chan].insChanged) { - rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); - } + if (chan[c.chan].insChanged) { + int ops=(slots[3][c.chan]!=255 && ins->fm.ops==4)?4:2; + for (int i=0; i1) { + rWrite(baseAddr+ADDR_WS,op.ws&((oplType==3)?7:3)); + } } - if (chan[c.chan].insChanged) { - rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); - rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); - rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); - rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); - rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); - rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + + if (isMuted[c.chan]) { + rWrite(chanMap[c.chan]+ADDR_LR_FB_ALG,(chan[c.chan].state.alg&1)|(chan[c.chan].state.fb<<1)); + if (ops==4) { + rWrite(chanMap[c.chan+1]+ADDR_LR_FB_ALG,((chan[c.chan].state.alg>>1)&1)|(chan[c.chan].state.fb<<1)); + } + } else { + rWrite(chanMap[c.chan]+ADDR_LR_FB_ALG,(chan[c.chan].state.alg&1)|(chan[c.chan].state.fb<<1)|((chan[c.chan].pan&3)<<4)); + if (ops==4) { + rWrite(chanMap[c.chan+1]+ADDR_LR_FB_ALG,((chan[c.chan].state.alg>>1)&1)|(chan[c.chan].state.fb<<1)|((chan[c.chan].pan&3)<<4)); + } } } - if (chan[c.chan].insChanged) { - rWrite(chanOffs[c.chan]+ADDR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3)); - rWrite(chanOffs[c.chan]+ADDR_LRAF,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|(chan[c.chan].state.fms&7)|((chan[c.chan].state.ams&3)<<4)); - } - */ + chan[c.chan].insChanged=false; if (c.value!=DIV_NOTE_NULL) { @@ -453,19 +497,11 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_NOTE_OFF: - if (c.chan==5) { - dacSample=-1; - if (dumpWrites) addWrite(0xffff0002,0); - } chan[c.chan].keyOff=true; chan[c.chan].keyOn=false; chan[c.chan].active=false; break; case DIV_CMD_NOTE_OFF_ENV: - if (c.chan==5) { - dacSample=-1; - if (dumpWrites) addWrite(0xffff0002,0); - } chan[c.chan].keyOff=true; chan[c.chan].keyOn=false; chan[c.chan].active=false; @@ -558,17 +594,6 @@ int DivPlatformOPL::dispatch(DivCommand c) { } break; } - case DIV_CMD_SAMPLE_MODE: { - dacMode=c.value; - rWrite(0x2b,c.value<<7); - break; - } - case DIV_CMD_SAMPLE_BANK: - sampleBank=c.value; - if (sampleBank>(parent->song.sample.size()/12)) { - sampleBank=parent->song.sample.size()/12; - } - break; case DIV_CMD_LEGATO: { chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); chan[c.chan].note=c.value; @@ -724,18 +749,12 @@ void DivPlatformOPL::reset() { } lastBusy=60; - dacMode=0; - dacPeriod=0; - dacPos=0; - dacRate=0; - dacSample=-1; - sampleBank=0; lfoValue=8; + properDrums=properDrumsSys; - extMode=false; - - // LFO - immWrite(0x22,lfoValue); + if (oplType==3) { // enable OPL3 features + immWrite(0x105,1); + } delay=0; } @@ -779,20 +798,23 @@ void DivPlatformOPL::setYMFM(bool use) { useYMFM=use; } -void DivPlatformOPL::setOPLType(int type) { +void DivPlatformOPL::setOPLType(int type, bool drums) { switch (type) { case 1: case 2: slotsNonDrums=(const unsigned char**)slotsOPL2; slotsDrums=(const unsigned char**)slotsOPL2Drums; + slots=drums?slotsDrums:slotsNonDrums; chanMap=chanMapOPL2; break; case 3: slotsNonDrums=(const unsigned char**)slotsOPL3; slotsDrums=(const unsigned char**)slotsOPL3Drums; + slots=drums?slotsDrums:slotsNonDrums; chanMap=chanMapOPL3; break; } oplType=type; + properDrumsSys=drums; } void DivPlatformOPL::setFlags(unsigned int flags) { @@ -820,14 +842,18 @@ void DivPlatformOPL::setFlags(unsigned int flags) { rate=chipClock/36; }*/ - chipClock=COLOR_NTSC*4.0; - rate=chipClock/32; + if (oplType==3) { + chipClock=COLOR_NTSC*4.0; + rate=chipClock/36; + } else { + chipClock=COLOR_NTSC; + rate=chipClock/9; + } } int DivPlatformOPL::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { parent=p; dumpWrites=false; - ladder=false; skipRegisterWrites=false; for (int i=0; i<20; i++) { isMuted[i]=false; diff --git a/src/engine/platform/opl.h b/src/engine/platform/opl.h index ab6226a45..32913a8d6 100644 --- a/src/engine/platform/opl.h +++ b/src/engine/platform/opl.h @@ -67,22 +67,17 @@ class DivPlatformOPL: public DivDispatch { const unsigned char** slotsNonDrums; const unsigned char** slotsDrums; const unsigned char** slots; - const unsigned char* chanMap; + const unsigned short* chanMap; int delay, oplType; unsigned char lastBusy; unsigned char regPool[512]; - bool dacMode; - int dacPeriod; - int dacRate; - unsigned int dacPos; - int dacSample; - unsigned char sampleBank; + bool properDrums, properDrumsSys; + unsigned char lfoValue; - bool extMode, useYMFM; - bool ladder; + bool useYMFM; short oldWrites[512]; short pendingWrites[512]; @@ -107,7 +102,7 @@ class DivPlatformOPL: public DivDispatch { void muteChannel(int ch, bool mute); bool isStereo(); void setYMFM(bool use); - void setOPLType(int type); + void setOPLType(int type, bool drums); bool keyOffAffectsArp(int ch); bool keyOffAffectsPorta(int ch); void toggleRegisterDump(bool enable); From ec007b44430825ec4f89d196988af00018012297 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 7 Mar 2022 18:19:25 -0500 Subject: [PATCH 149/637] OPL: more work - still not there yet --- src/engine/platform/opl.cpp | 95 +++++++++++++++++++------------------ src/gui/gui.cpp | 12 +++++ src/gui/insEdit.cpp | 17 ++++++- 3 files changed, 77 insertions(+), 47 deletions(-) diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index 50cdda389..fbcc643bb 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -30,14 +30,14 @@ // N = invalid #define N 255 -const unsigned char slotsOPL2[4][20]={ +const unsigned char slotsOPL2i[4][20]={ {0, 1, 2, 6, 7, 8, 12, 13, 14}, // OP1 {3, 4, 5, 9, 10, 11, 15, 16, 17}, // OP2 {N, N, N, N, N, N, N, N, N}, {N, N, N, N, N, N, N, N, N} }; -const unsigned char slotsOPL2Drums[4][20]={ +const unsigned char slotsOPL2Drumsi[4][20]={ {0, 1, 2, 6, 7, 8, 12, 16, 14, 17, 13}, // OP1 {3, 4, 5, 9, 10, 11, 15, N, N, N, N}, // OP2 {N, N, N, N, N, N, N, N, N, N, N}, @@ -48,14 +48,28 @@ const unsigned short chanMapOPL2[20]={ 0, 1, 2, 3, 4, 5, 6, 7, 8, N, N, N, N, N, N, N, N, N, N, N }; -const unsigned char slotsOPL3[4][20]={ +const unsigned char* slotsOPL2[4]={ + slotsOPL2i[0], + slotsOPL2i[1], + slotsOPL2i[2], + slotsOPL2i[3] +}; + +const unsigned char* slotsOPL2Drums[4]={ + slotsOPL2Drumsi[0], + slotsOPL2Drumsi[1], + slotsOPL2Drumsi[2], + slotsOPL2Drumsi[3] +}; + +const unsigned char slotsOPL3i[4][20]={ {0, 6, 1, 7, 2, 8, 18, 24, 19, 25, 20, 26, 30, 31, 32, 12, 13, 14}, // OP1 {3, 9, 4, 10, 5, 11, 21, 27, 22, 28, 23, 29, 33, 34, 35, 15, 16, 17}, // OP2 {6, N, 7, N, 8, N, 24, N, 25, N, 26, N, N, N, N, N, N, N}, // OP3 {9, N, 10, N, 11, N, 27, N, 28, N, 29, N, N, N, N, N, N, N} // OP4 }; -const unsigned char slotsOPL3Drums[4][20]={ +const unsigned char slotsOPL3Drumsi[4][20]={ {0, 6, 1, 7, 2, 8, 18, 24, 19, 25, 20, 26, 30, 31, 32, 12, 16, 14, 17, 13}, // OP1 {3, 9, 4, 10, 5, 11, 21, 27, 22, 28, 23, 29, 33, 34, 35, N, N, N, N, N}, // OP2 {6, N, 7, N, 8, N, 24, N, 25, N, 26, N, N, N, N, N, N, N, N, N}, // OP3 @@ -66,6 +80,20 @@ const unsigned short chanMapOPL3[20]={ 0, 3, 1, 4, 2, 5, 0x100, 0x103, 0x101, 0x104, 0x102, 0x105, 0x106, 0x107, 0x108, 6, 7, 8, N, N }; +const unsigned char* slotsOPL3[4]={ + slotsOPL3i[0], + slotsOPL3i[1], + slotsOPL3i[2], + slotsOPL3i[3] +}; + +const unsigned char* slotsOPL3Drums[4]={ + slotsOPL3Drumsi[0], + slotsOPL3Drumsi[1], + slotsOPL3Drumsi[2], + slotsOPL3Drumsi[3] +}; + const unsigned int slotMap[36]={ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, @@ -167,18 +195,8 @@ void DivPlatformOPL::acquire_nuked(short* bufL, short* bufR, size_t start, size_ if (!writes.empty() && --delay<0) { delay=12; QueuedWrite& w=writes.front(); - if (w.addrOrVal) { - OPL3_WriteReg(&fm,0x1+((w.addr>>8)<<1),w.val); - //printf("write: %x = %.2x\n",w.addr,w.val); - lastBusy=0; - regPool[w.addr&0x1ff]=w.val; - writes.pop(); - } else { - lastBusy++; - //printf("busycounter: %d\n",lastBusy); - OPL3_WriteReg(&fm,0x0+((w.addr>>8)<<1),w.addr); - w.addrOrVal=true; - } + OPL3_WriteReg(&fm,w.addr,w.val); + writes.pop(); } OPL3_Generate(&fm,o); os[0]+=o[0]; os[1]+=o[1]; @@ -203,11 +221,10 @@ void DivPlatformOPL::acquire(short* bufL, short* bufR, size_t start, size_t len) } void DivPlatformOPL::tick() { - /* for (int i=0; i<20; i++) { - if (i==2 && extMode) continue; chan[i].std.next(); + /* if (chan[i].std.hadVol) { chan[i].outVol=(chan[i].vol*MIN(127,chan[i].std.vol))/127; for (int j=0; j<4; j++) { @@ -327,13 +344,13 @@ void DivPlatformOPL::tick() { rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); } } + */ if (chan[i].keyOn || chan[i].keyOff) { - immWrite(0x28,0x00|konOffs[i]); + immWrite(chanMap[i]+ADDR_FREQH,0x00|(chan[i].freqH&31)); chan[i].keyOff=false; } } - */ for (int i=0; i<512; i++) { if (pendingWrites[i]!=oldWrites[i]) { @@ -342,36 +359,23 @@ void DivPlatformOPL::tick() { } } - /* for (int i=0; i<20; i++) { if (chan[i].freqChanged) { chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq)); if (chan[i].freq>262143) chan[i].freq=262143; int freqt=toFreq(chan[i].freq); - immWrite(chanOffs[i]+ADDR_FREQH,freqt>>8); - immWrite(chanOffs[i]+ADDR_FREQ,freqt&0xff); - if (chan[i].furnaceDac && dacMode) { - double off=1.0; - if (dacSample>=0 && dacSamplesong.sampleLen) { - DivSample* s=parent->getSample(dacSample); - if (s->centerRate<1) { - off=1.0; - } else { - off=8363.0/(double)s->centerRate; - } - } - dacRate=(1280000*1.25*off)/MAX(1,chan[i].baseFreq); - if (dacRate<1) dacRate=1; - if (dumpWrites) addWrite(0xffff0001,1280000/dacRate); - } - chan[i].freqChanged=false; + chan[i].freqH=freqt>>8; + chan[i].freqL=freqt&0xff; + immWrite(chanMap[i]+ADDR_FREQ,chan[i].freqL); } if (chan[i].keyOn) { - immWrite(0x28,0xf0|konOffs[i]); + immWrite(chanMap[i]+ADDR_FREQH,chan[i].freqH|(0x20)); chan[i].keyOn=false; + } else if (chan[i].freqChanged) { + immWrite(chanMap[i]+ADDR_FREQH,chan[i].freqH|(chan[i].active<<5)); } + chan[i].freqChanged=false; } - */ } int DivPlatformOPL::octave(int freq) { @@ -395,6 +399,7 @@ int DivPlatformOPL::octave(int freq) { return 1; } +// TODO int DivPlatformOPL::toFreq(int freq) { if (freq>=82432) { return 0x3800|((freq>>7)&0x7ff); @@ -659,7 +664,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { return 0; break; case DIV_CMD_GET_VOLMAX: - return 127; + return 63; break; case DIV_CMD_PRE_PORTA: chan[c.chan].inPorta=c.value; @@ -801,14 +806,14 @@ void DivPlatformOPL::setYMFM(bool use) { void DivPlatformOPL::setOPLType(int type, bool drums) { switch (type) { case 1: case 2: - slotsNonDrums=(const unsigned char**)slotsOPL2; - slotsDrums=(const unsigned char**)slotsOPL2Drums; + slotsNonDrums=slotsOPL2; + slotsDrums=slotsOPL2Drums; slots=drums?slotsDrums:slotsNonDrums; chanMap=chanMapOPL2; break; case 3: - slotsNonDrums=(const unsigned char**)slotsOPL3; - slotsDrums=(const unsigned char**)slotsOPL3Drums; + slotsNonDrums=slotsOPL3; + slotsDrums=slotsOPL3Drums; slots=drums?slotsDrums:slotsNonDrums; chanMap=chanMapOPL3; break; diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index fb3e6fc31..06f102de1 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4629,6 +4629,12 @@ bool FurnaceGUI::loop() { sysAddOption(DIV_SYSTEM_OPLL); sysAddOption(DIV_SYSTEM_OPLL_DRUMS); sysAddOption(DIV_SYSTEM_VRC7); + sysAddOption(DIV_SYSTEM_OPL); + sysAddOption(DIV_SYSTEM_OPL_DRUMS); + sysAddOption(DIV_SYSTEM_OPL2); + sysAddOption(DIV_SYSTEM_OPL2_DRUMS); + sysAddOption(DIV_SYSTEM_OPL3); + sysAddOption(DIV_SYSTEM_OPL3_DRUMS); sysAddOption(DIV_SYSTEM_TIA); sysAddOption(DIV_SYSTEM_SAA1099); sysAddOption(DIV_SYSTEM_AY8930); @@ -4974,6 +4980,12 @@ bool FurnaceGUI::loop() { sysChangeOption(i,DIV_SYSTEM_OPLL); sysChangeOption(i,DIV_SYSTEM_OPLL_DRUMS); sysChangeOption(i,DIV_SYSTEM_VRC7); + sysChangeOption(i,DIV_SYSTEM_OPL); + sysChangeOption(i,DIV_SYSTEM_OPL_DRUMS); + sysChangeOption(i,DIV_SYSTEM_OPL2); + sysChangeOption(i,DIV_SYSTEM_OPL2_DRUMS); + sysChangeOption(i,DIV_SYSTEM_OPL3); + sysChangeOption(i,DIV_SYSTEM_OPL3_DRUMS); sysChangeOption(i,DIV_SYSTEM_TIA); sysChangeOption(i,DIV_SYSTEM_SAA1099); sysChangeOption(i,DIV_SYSTEM_AY8930); diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 6ff30896f..b05f315ef 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -796,7 +796,8 @@ void FurnaceGUI::drawInsEdit() { int asInt[256]; float loopIndicator[256]; int opCount=4; - if (ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPLL) opCount=2; + if (ins->type==DIV_INS_OPLL) opCount=2; + if (ins->type==DIV_INS_OPL) opCount=(ins->fm.ops==4)?4:2; if (ImGui::BeginTabItem("FM")) { if (ImGui::BeginTable("fmDetails",3,ImGuiTableFlags_SizingStretchSame)) { @@ -816,7 +817,19 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); drawAlgorithm(ins->fm.alg,FM_ALGS_4OP,ImVec2(ImGui::GetContentRegionAvail().x,48.0*dpiScale)); break; - case DIV_INS_OPL: + case DIV_INS_OPL: { + bool fourOp=(ins->fm.ops==4); + ImGui::TableNextColumn(); + P(ImGui::SliderScalar(FM_NAME(FM_FB),ImGuiDataType_U8,&ins->fm.fb,&_ZERO,&_SEVEN)); rightClickable + if (ImGui::Checkbox("4-op",&fourOp)) { PARAMETER + ins->fm.ops=fourOp?4:2; + } + ImGui::TableNextColumn(); + P(ImGui::SliderScalar(FM_NAME(FM_ALG),ImGuiDataType_U8,&ins->fm.alg,&_ZERO,&_SEVEN)); rightClickable + ImGui::TableNextColumn(); + drawAlgorithm(ins->fm.alg&1,FM_ALGS_2OP_OPL,ImVec2(ImGui::GetContentRegionAvail().x,48.0*dpiScale)); + break; + } case DIV_INS_OPLL: { bool dc=ins->fm.fms; bool dm=ins->fm.ams; From cdd45bb18c3e5d0842bdd6c87f8469f3a45bd1ef Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 7 Mar 2022 22:28:20 -0500 Subject: [PATCH 150/637] allow detune 4 --- src/engine/platform/arcade.cpp | 2 +- src/engine/platform/genesisshared.h | 4 ++-- src/engine/platform/ym2610shared.h | 2 +- src/gui/insEdit.cpp | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/engine/platform/arcade.cpp b/src/engine/platform/arcade.cpp index 1af99eb75..0f13be178 100644 --- a/src/engine/platform/arcade.cpp +++ b/src/engine/platform/arcade.cpp @@ -42,7 +42,7 @@ static bool isOutput[8][4]={ {true ,true ,true ,true}, }; static unsigned char dtTable[8]={ - 7,6,5,0,1,2,3,0 + 7,6,5,0,1,2,3,4 }; static int orderedOps[4]={ diff --git a/src/engine/platform/genesisshared.h b/src/engine/platform/genesisshared.h index 22e4cd826..d2402a60d 100644 --- a/src/engine/platform/genesisshared.h +++ b/src/engine/platform/genesisshared.h @@ -35,7 +35,7 @@ static bool isOutput[8][4]={ {true ,true ,true ,true}, }; static unsigned char dtTable[8]={ - 7,6,5,0,1,2,3,0 + 7,6,5,0,1,2,3,4 }; static int orderedOps[4]={ @@ -45,4 +45,4 @@ static int orderedOps[4]={ #define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;} #define immWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } -#include "fmshared_OPN.h" \ No newline at end of file +#include "fmshared_OPN.h" diff --git a/src/engine/platform/ym2610shared.h b/src/engine/platform/ym2610shared.h index c41d5520a..8d8847c2e 100644 --- a/src/engine/platform/ym2610shared.h +++ b/src/engine/platform/ym2610shared.h @@ -32,7 +32,7 @@ static bool isOutput[8][4]={ {true ,true ,true ,true}, }; static unsigned char dtTable[8]={ - 7,6,5,0,1,2,3,0 + 7,6,5,0,1,2,3,4 }; static int orderedOps[4]={ diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index b05f315ef..dba0b1bae 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1077,7 +1077,7 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::SliderInt("##DT",&detune,-3,3)) { PARAMETER + if (ImGui::SliderInt("##DT",&detune,-3,4)) { PARAMETER op.dt=detune+3; } rightClickable ImGui::TableNextColumn(); From 36db137e8f6d630d73fbf3d108ff5bc0649f5e2a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 7 Mar 2022 22:28:33 -0500 Subject: [PATCH 151/637] OPL: absolute mess up now it kinda works --- src/engine/platform/opl.cpp | 50 +++++++++++++++++++------------------ 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index fbcc643bb..ec8f03b34 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -196,6 +196,7 @@ void DivPlatformOPL::acquire_nuked(short* bufL, short* bufR, size_t start, size_ delay=12; QueuedWrite& w=writes.front(); OPL3_WriteReg(&fm,w.addr,w.val); + regPool[w.addr&511]=w.val; writes.pop(); } @@ -378,20 +379,22 @@ void DivPlatformOPL::tick() { } } +#define OPLL_C_NUM 686 + int DivPlatformOPL::octave(int freq) { - if (freq>=82432) { + if (freq>=OPLL_C_NUM*64) { return 128; - } else if (freq>=41216) { + } else if (freq>=OPLL_C_NUM*32) { return 64; - } else if (freq>=20608) { + } else if (freq>=OPLL_C_NUM*16) { return 32; - } else if (freq>=10304) { + } else if (freq>=OPLL_C_NUM*8) { return 16; - } else if (freq>=5152) { + } else if (freq>=OPLL_C_NUM*4) { return 8; - } else if (freq>=2576) { + } else if (freq>=OPLL_C_NUM*2) { return 4; - } else if (freq>=1288) { + } else if (freq>=OPLL_C_NUM) { return 2; } else { return 1; @@ -399,24 +402,23 @@ int DivPlatformOPL::octave(int freq) { return 1; } -// TODO int DivPlatformOPL::toFreq(int freq) { - if (freq>=82432) { - return 0x3800|((freq>>7)&0x7ff); - } else if (freq>=41216) { - return 0x3000|((freq>>6)&0x7ff); - } else if (freq>=20608) { - return 0x2800|((freq>>5)&0x7ff); - } else if (freq>=10304) { - return 0x2000|((freq>>4)&0x7ff); - } else if (freq>=5152) { - return 0x1800|((freq>>3)&0x7ff); - } else if (freq>=2576) { - return 0x1000|((freq>>2)&0x7ff); - } else if (freq>=1288) { - return 0x800|((freq>>1)&0x7ff); + if (freq>=OPLL_C_NUM*64) { + return 0x1c00|((freq>>7)&0x3ff); + } else if (freq>=OPLL_C_NUM*32) { + return 0x1800|((freq>>6)&0x3ff); + } else if (freq>=OPLL_C_NUM*16) { + return 0x1400|((freq>>5)&0x3ff); + } else if (freq>=OPLL_C_NUM*8) { + return 0x1000|((freq>>4)&0x3ff); + } else if (freq>=OPLL_C_NUM*4) { + return 0xc00|((freq>>3)&0x3ff); + } else if (freq>=OPLL_C_NUM*2) { + return 0x800|((freq>>2)&0x3ff); + } else if (freq>=OPLL_C_NUM) { + return 0x400|((freq>>1)&0x3ff); } else { - return freq&0x7ff; + return freq&0x3ff; } } @@ -456,7 +458,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { if (chan[c.chan].insChanged) { int ops=(slots[3][c.chan]!=255 && ins->fm.ops==4)?4:2; for (int i=0; i Date: Mon, 7 Mar 2022 22:52:32 -0500 Subject: [PATCH 152/637] OPL: it's coming together --- src/engine/instrument.cpp | 2 +- src/engine/platform/opl.cpp | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index bdf028905..b22fdced2 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -39,7 +39,7 @@ void DivInstrument::putInsData(SafeWriter* w) { w->writeC(fm.fb); w->writeC(fm.fms); w->writeC(fm.ams); - w->writeC(4); // operator count; always 4 + w->writeC(fm.ops); w->writeC(fm.opllPreset); w->writeC(0); // reserved w->writeC(0); diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index ec8f03b34..2c005ee90 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -25,7 +25,7 @@ #define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;} #define immWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } -#define CHIP_FREQBASE 4720272 +#define CHIP_FREQBASE 9440540 // N = invalid #define N 255 @@ -193,7 +193,7 @@ void DivPlatformOPL::acquire_nuked(short* bufL, short* bufR, size_t start, size_ for (size_t h=start; hcalcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq)); - if (chan[i].freq>262143) chan[i].freq=262143; + if (chan[i].freq>131071) chan[i].freq=131071; int freqt=toFreq(chan[i].freq); chan[i].freqH=freqt>>8; chan[i].freqL=freqt&0xff; @@ -851,10 +851,10 @@ void DivPlatformOPL::setFlags(unsigned int flags) { if (oplType==3) { chipClock=COLOR_NTSC*4.0; - rate=chipClock/36; + rate=chipClock/288; } else { chipClock=COLOR_NTSC; - rate=chipClock/9; + rate=chipClock/72; } } From 09655f7d57e3233908b36228b73d6613f233ae8e Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 7 Mar 2022 23:09:42 -0500 Subject: [PATCH 153/637] WonderSwan: fix build --- src/engine/platform/swan.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine/platform/swan.cpp b/src/engine/platform/swan.cpp index e7cefc464..8fdca1d40 100644 --- a/src/engine/platform/swan.cpp +++ b/src/engine/platform/swan.cpp @@ -222,7 +222,7 @@ void DivPlatformSwan::tick() { if (chan[3].std.hadDuty) { noise=chan[3].std.duty; if (noise>0) { - rWrite(0x0e,(noise-1)&0x07|0x18); + rWrite(0x0e,((noise-1)&0x07)|0x18); sndCtrl|=0x80; } else { sndCtrl&=~0x80; @@ -378,7 +378,7 @@ int DivPlatformSwan::dispatch(DivCommand c) { case DIV_CMD_STD_NOISE_MODE: if (c.chan==3) { noise=c.value&0xff; - if (noise>0) rWrite(0x0e,(noise-1)&0x07|0x18); + if (noise>0) rWrite(0x0e,((noise-1)&0x07)|0x18); } break; case DIV_CMD_SAMPLE_MODE: From 2d922d5e0981e5fbce8a052f076ca8bf0f17ec40 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 7 Mar 2022 23:11:14 -0500 Subject: [PATCH 154/637] GUI: attribution --- README.md | 1 - src/gui/gui.cpp | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 540052fd3..e1d97726e 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,6 @@ this is a work-in-progress chiptune tracker compatible with DefleMask modules (. - Philips SAA1099 - Amiga - TIA (Atari 2600/7800) - - WonderSwan - multiple sound chips in a single song! - clean-room design (guesswork and ABX tests only, no decompilation involved) - bug/quirk implementation for increased playback accuracy diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 7245f2f30..9cdc1cefe 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1551,6 +1551,7 @@ const char* aboutLine[]={ "", "-- program --", "tildearrow", + "akumanatt", "cam900", "laoo", "superctr", From 1bc8bc87461dbcfa71c192c193061da9a97c2586 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 7 Mar 2022 23:23:18 -0500 Subject: [PATCH 155/637] OPL: proper frequency on OPL1/2 --- src/engine/platform/opl.cpp | 10 +++++++--- src/engine/platform/opl.h | 1 + 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index 2c005ee90..d84265530 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -25,7 +25,7 @@ #define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;} #define immWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } -#define CHIP_FREQBASE 9440540 +#define CHIP_FREQBASE chipFreqBase // N = invalid #define N 255 @@ -456,9 +456,11 @@ int DivPlatformOPL::dispatch(DivCommand c) { chan[c.chan].outVol=chan[c.chan].vol; } if (chan[c.chan].insChanged) { - int ops=(slots[3][c.chan]!=255 && ins->fm.ops==4)?4:2; + int ops=(slots[3][c.chan]!=255 && ins->fm.ops==4 && oplType==3)?4:2; for (int i=0; i Date: Mon, 7 Mar 2022 23:38:36 -0500 Subject: [PATCH 156/637] WonderSwan: make macOS happy --- src/engine/platform/sound/swan.cpp | 3 --- src/engine/platform/sound/swan.h | 1 - 2 files changed, 4 deletions(-) diff --git a/src/engine/platform/sound/swan.cpp b/src/engine/platform/sound/swan.cpp index 60052f617..685fefe94 100644 --- a/src/engine/platform/sound/swan.cpp +++ b/src/engine/platform/sound/swan.cpp @@ -85,7 +85,6 @@ void WSwan::SoundUpdate(uint32_t v30mz_timestamp) else if(ch == 2 && (control & 0x40) && sweep_value) // Sweep { uint32_t tmp_pt = 2048 - period[ch]; - uint32_t meow_timestamp = v30mz_timestamp - run_time; uint32_t tmp_run_time = run_time; while(tmp_run_time) @@ -107,7 +106,6 @@ void WSwan::SoundUpdate(uint32_t v30mz_timestamp) } } - meow_timestamp += sub_run_time; if(tmp_pt > 4) { period_counter[ch] -= sub_run_time; @@ -116,7 +114,6 @@ void WSwan::SoundUpdate(uint32_t v30mz_timestamp) sample_pos[ch] = (sample_pos[ch] + 1) & 0x1F; MK_SAMPLE_CACHE; - SYNCSAMPLE(meow_timestamp + period_counter[ch]); period_counter[ch] += tmp_pt; } } diff --git a/src/engine/platform/sound/swan.h b/src/engine/platform/sound/swan.h index b1b0af740..a1d01fa54 100644 --- a/src/engine/platform/sound/swan.h +++ b/src/engine/platform/sound/swan.h @@ -76,7 +76,6 @@ private: uint32_t last_ts; uint8_t wsRAM[64]; - int16_t sBuf[2]; }; #endif From 270225f6d16e88a414d82cf4507e5d27f090443f Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 7 Mar 2022 23:47:40 -0500 Subject: [PATCH 157/637] WonderSwan doesn't have config flags --- src/gui/gui.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 9cdc1cefe..25d662d05 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4935,6 +4935,7 @@ bool FurnaceGUI::loop() { break; } case DIV_SYSTEM_GB: + case DIV_SYSTEM_SWAN: case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_EXT: case DIV_SYSTEM_YM2610_FULL: From a86a7f766be4325d73913d279951e66079859b28 Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Tue, 8 Mar 2022 15:06:11 +0700 Subject: [PATCH 158/637] VERA doesn't have config flags --- src/gui/gui.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index f13ad55df..4a64bb3b5 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4928,6 +4928,7 @@ bool FurnaceGUI::loop() { break; } case DIV_SYSTEM_GB: + case DIV_SYSTEM_VERA: case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_EXT: case DIV_SYSTEM_YM2610_FULL: From e05052d9d7fbe0fdca88b3eedd7d2cf835473b27 Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Tue, 8 Mar 2022 15:44:14 +0700 Subject: [PATCH 159/637] Properly case PCM channel --- src/engine/platform/vera.cpp | 39 +++++++++++++++--------------------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/src/engine/platform/vera.cpp b/src/engine/platform/vera.cpp index ce45341be..4d0355731 100644 --- a/src/engine/platform/vera.cpp +++ b/src/engine/platform/vera.cpp @@ -161,21 +161,11 @@ int DivPlatformVERA::calcNoteFreq(int ch, int note) { } void DivPlatformVERA::tick() { - for (int i=0; i<17; i++) { + for (int i=0; i<16; i++) { chan[i].std.next(); if (chan[i].std.hadVol) { - if (i<16) { - chan[i].outVol=MAX(chan[i].vol+chan[i].std.vol-63,0); - rWriteLo(i,2,isMuted[i]?0:(chan[i].outVol&63)); - } else { - // NB this is currently assuming Amiga instrument type with a 0-64 - // (inclusive) volume range. This envelope is then scaled and added to - // the channel volume. Is this a better way to handle this instead of - // making another identical Amiga instrument type but with a 0-15 - // volume range? - chan[16].outVol=MAX(chan[16].vol+MIN(chan[16].std.vol/4,15)-15,0); - rWriteFIFOVol(isMuted[16]?0:(chan[16].outVol&15)); - } + chan[i].outVol=MAX(chan[i].vol+chan[i].std.vol-63,0); + rWriteLo(i,2,isMuted[i]?0:(chan[i].outVol&63)); } if (chan[i].std.hadArp) { if (!chan[i].inPorta) { @@ -192,28 +182,25 @@ void DivPlatformVERA::tick() { chan[i].freqChanged=true; } } - if (chan[i].std.hadDuty && i<16) { + if (chan[i].std.hadDuty) { rWriteLo(i,3,chan[i].std.duty); } - if (chan[i].std.hadWave && i<16) { + if (chan[i].std.hadWave) { rWriteHi(i,3,chan[i].std.wave); } if (chan[i].freqChanged) { chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,8); - if (i<16) { - if (chan[i].freq>65535) chan[i].freq=65535; - rWrite(i,0,chan[i].freq&0xff); - rWrite(i,1,(chan[i].freq>>8)&0xff); - } else { - if (chan[i].freq>128) chan[i].freq=128; - rWrite(16,1,chan[i].freq&0xff); - } + if (chan[i].freq>65535) chan[i].freq=65535; + rWrite(i,0,chan[i].freq&0xff); + rWrite(i,1,(chan[i].freq>>8)&0xff); chan[i].freqChanged=false; } } // PCM chan[16].std.next(); if (chan[16].std.hadVol) { + chan[16].outVol=MAX(chan[16].vol+MIN(chan[16].std.vol/4,15)-15,0); + rWriteFIFOVol(isMuted[16]?0:(chan[16].outVol&15)); } if (chan[16].std.hadArp) { if (!chan[16].inPorta) { @@ -230,6 +217,12 @@ void DivPlatformVERA::tick() { chan[16].freqChanged=true; } } + if (chan[16].freqChanged) { + chan[16].freq=parent->calcFreq(chan[16].baseFreq,chan[16].pitch,false,8); + if (chan[16].freq>128) chan[16].freq=128; + rWrite(16,1,chan[16].freq&0xff); + chan[16].freqChanged=false; + } } int DivPlatformVERA::dispatch(DivCommand c) { From 7711069a66e9b76dbea43c395012da2caae499ef Mon Sep 17 00:00:00 2001 From: Natt Akuma <77432377+akumanatt@users.noreply.github.com> Date: Tue, 8 Mar 2022 16:51:37 +0700 Subject: [PATCH 160/637] WonderSwan: match DM's volume command behavior (#266) --- src/engine/platform/swan.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/engine/platform/swan.cpp b/src/engine/platform/swan.cpp index 8fdca1d40..219871804 100644 --- a/src/engine/platform/swan.cpp +++ b/src/engine/platform/swan.cpp @@ -152,7 +152,7 @@ void DivPlatformSwan::tick() { unsigned char sndCtrl=(pcm?0x20:0)|(sweep?0x40:0)|((noise>0)?0x80:0); for (int i=0; i<4; i++) { chan[i].std.next(); - if (chan[i].std.willVol) { + if (chan[i].std.hadVol) { int env=chan[i].std.vol; if(parent->getIns(chan[i].ins)->type==DIV_INS_AMIGA) { env=MIN(env/4,15); @@ -320,7 +320,7 @@ int DivPlatformSwan::dispatch(DivCommand c) { case DIV_CMD_VOLUME: if (chan[c.chan].vol!=c.value) { chan[c.chan].vol=c.value; - if (!chan[c.chan].std.willVol) { + if (!chan[c.chan].std.hadVol) { calcAndWriteOutVol(c.chan,15); } } @@ -392,9 +392,7 @@ int DivPlatformSwan::dispatch(DivCommand c) { break; case DIV_CMD_PANNING: { chan[c.chan].pan=c.value; - if (!chan[c.chan].std.willVol) { - calcAndWriteOutVol(c.chan,15); - } + calcAndWriteOutVol(c.chan,chan[c.chan].std.willVol?chan[c.chan].std.vol:15); break; } case DIV_CMD_LEGATO: From 8b1e557b5c9983f2ca1166c526781d9cf93059b6 Mon Sep 17 00:00:00 2001 From: cam900 Date: Tue, 8 Mar 2022 21:34:12 +0900 Subject: [PATCH 161/637] Sync with master --- src/engine/vgmOps.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index dc825f6a7..069d5a2ef 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -157,7 +157,7 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write case DIV_SYSTEM_X1_010: for (int i=0; i<16; i++) { w->writeC(0xc8); - w->writeS((isSecond?0x8000:0x0)+(i<<3)); + w->writeS(baseAddr2S+(i<<3)); w->writeC(0); } break; @@ -404,7 +404,7 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write break; case DIV_SYSTEM_X1_010: w->writeC(0xc8); - w->writeS((isSecond?0x8000:0)|(write.addr&0x1fff)); + w->writeS(baseAddr2S|(write.addr&0x1fff)); w->writeC(write.val); break; case DIV_SYSTEM_YM2610: From 6c432bc42e6ec1f2280c5931c1568b155fd2bd56 Mon Sep 17 00:00:00 2001 From: cam900 Date: Wed, 9 Mar 2022 00:50:10 +0900 Subject: [PATCH 162/637] Allow Left waveform can be invertable, Improvement documents --- papers/doc/4-instrument/README.md | 2 +- papers/doc/5-wave/README.md | 2 +- papers/doc/7-systems/x1_010.md | 16 +++- src/engine/platform/x1_010.cpp | 120 +++++++++++++++++++----------- src/engine/platform/x1_010.h | 18 +++-- src/gui/debug.cpp | 6 +- src/gui/insEdit.cpp | 6 +- 7 files changed, 110 insertions(+), 60 deletions(-) diff --git a/papers/doc/4-instrument/README.md b/papers/doc/4-instrument/README.md index 2b907634d..9a31cf65f 100644 --- a/papers/doc/4-instrument/README.md +++ b/papers/doc/4-instrument/README.md @@ -10,7 +10,7 @@ double-click to open the instrument editor. every instrument can be renamed and have its type changed. -depending on the instrument type, there are currently 12 different types of an instrument editor: +depending on the instrument type, there are currently 13 different types of an instrument editor: - [FM synthesis](fm.md) - for use with YM2612, YM2151 and FM block portion of YM2610. - [Standard](standard.md) - for use with NES and Sega Master System's PSG sound source and its derivatives. diff --git a/papers/doc/5-wave/README.md b/papers/doc/5-wave/README.md index 005739257..5e234bf65 100644 --- a/papers/doc/5-wave/README.md +++ b/papers/doc/5-wave/README.md @@ -2,4 +2,4 @@ Wavetable synthizers, in context of Furnace, are sound sources that operate on extremely short n-bit PCM streams. By extremely short, no more than 256 bytes. This amount of space is nowhere near enough to store an actual sampled sound, it allows certain amount of freedom to define a waveform shape. As of Furnace 0.5.8, wavetable editor affects PC Engine, WonderSwan and channel 3 of Game Boy. -Furnace's wavetable editor is rather simple, you can draw the waveform using mouse or by pasting an MML bit stream in the input field. Maximum wave width (length) is 256 bytes, and maximum wave height (depth) is 256. NOTE: Game Boy, PCE and WonderSwan can handle max 32 byte waveforms as of now, with 16-level height for GB and WS, and 32-level height for PCE. If larger wave will be defined for these two systems, it will be squashed to fit within the constraints of the system. +Furnace's wavetable editor is rather simple, you can draw the waveform using mouse or by pasting an MML bit stream in the input field. Maximum wave width (length) is 256 bytes, and maximum wave height (depth) is 256. NOTE: Game Boy, PCE and WonderSwan can handle max 32 byte waveforms, X1-010 can handle max 128 byte waveforms as of now, with 16-level height for GB, X1-010 Envelope and WS, and 32-level height for PCE. If larger wave will be defined for these systems, it will be squashed to fit within the constraints of the system. diff --git a/papers/doc/7-systems/x1_010.md b/papers/doc/7-systems/x1_010.md index 7d5778713..e2e44f183 100644 --- a/papers/doc/7-systems/x1_010.md +++ b/papers/doc/7-systems/x1_010.md @@ -10,6 +10,18 @@ Wavetable needs to paired with envelope, this feature is similar as AY PSG but i In furnace, this chip is can be configurable for original arcade mono output or stereo output - its simulates early 'incorrect' emulation on some mono hardware but it is also based on the assumption that each channel is connected to each output. +# waveform type + +This chip supports 2 type waveforms, needs to paired external 8KB RAM for use these feature: + +One is signed 8 bit mono waveform, its operated like other wavetable based sound systems. +These are stored at bottom half of RAM at common case. + +Another one ("Envelope") is 4 bit stereo waveform, its multiplied with above and calculates final output, Each nibble is used for each output channels. +These are stored at upper half of RAM at common case. + +Both waveforms are 128 byte fixed size, its freely allocated at each half of RAM except channel register area: each half can be stored total 32/31 waveforms at once. + # effects - `10xx`: change wave. @@ -20,8 +32,8 @@ In furnace, this chip is can be configurable for original arcade mono output or - bit 0 sets whether envelope will affect this channel. - bit 1 sets whether envelope one-shot mode. when it sets, channel is halted after envelope cycle is ended. - bit 2 sets whether envelope shape split mode. when it sets, envelope shape will splitted to left half and right half. - - bit 3 sets whether the right shape will mirror the left one. - - bit 4 sets whether the right output will mirror the left one. + - bit 3/5 sets whether the right/left shape will mirror the original one. + - bit 4/6 sets whether the right/left output will mirror the original one. - `23xx`: set envelope period. - `25xx`: slide envelope period up. - `26xx`: slide envelope period down. diff --git a/src/engine/platform/x1_010.cpp b/src/engine/platform/x1_010.cpp index 331b38420..e6b07d01b 100644 --- a/src/engine/platform/x1_010.cpp +++ b/src/engine/platform/x1_010.cpp @@ -219,7 +219,7 @@ const char* DivPlatformX1_010::getEffectName(unsigned char effect) { return "20xx: Set PCM frequency (1 to FF)"; break; case 0x22: - return "22xx: Set envelope mode (bit 0: enable, bit 1: one-shot, bit 2: split shape to L/R, bit 3: H.invert right, bit 4: V.invert right)"; + return "22xx: Set envelope mode (bit 0: enable, bit 1: one-shot, bit 2: split shape to L/R, bit 3/5: H.invert right/left, bit 4/6: V.invert right/left)"; break; case 0x23: return "23xx: Set envelope period"; @@ -258,8 +258,8 @@ void DivPlatformX1_010::acquire(short* bufL, short* bufR, size_t start, size_t l double DivPlatformX1_010::NoteX1_010(int ch, int note) { if (chan[ch].pcm) { // PCM note double off=1.0; - int sample = chan[ch].sample; - if (sample>=0 && samplesong.sampleLen) { + int sample=chan[ch].sample; + if (sample>=0&&samplesong.sampleLen) { DivSample* s=parent->getSample(sample); if (s->centerRate<1) { off=1.0; @@ -279,7 +279,7 @@ void DivPlatformX1_010::updateWave(int ch) { chan[ch].waveBank ^= 1; } for (int i=0; i<128; i++) { - if (wt->max<1 || wt->len<1) { + if (wt->max<1||wt->len<1) { waveWrite(ch,i,0); } else { waveWrite(ch,i,wt->data[i*wt->len/128]*255/wt->max); @@ -304,23 +304,25 @@ void DivPlatformX1_010::updateEnvelope(int ch) { } else { DivWavetable* wt=parent->getWave(chan[ch].env.shape); for (int i=0; i<128; i++) { - if (wt->max<1 || wt->len<1) { + if (wt->max<1||wt->len<1) { envFill(ch,i); - } else if (chan[ch].env.flag.envHinv||chan[ch].env.flag.envSplit||chan[ch].env.flag.envVinv) { // Stereo config - int la = i, ra = i; - int lo, ro; - if (chan[ch].env.flag.envHinv) { ra = 127-i; } // horizontal invert right envelope + } else if (chan[ch].env.flag.envSplit||chan[ch].env.flag.envHinvR||chan[ch].env.flag.envVinvR||chan[ch].env.flag.envHinvL||chan[ch].env.flag.envVinvL) { // Stereo config + int la=i,ra=i; + int lo,ro; + if (chan[ch].env.flag.envHinvR) { ra=127-i; } // horizontal invert right envelope + if (chan[ch].env.flag.envHinvL) { la=127-i; } // horizontal invert left envelope if (chan[ch].env.flag.envSplit) { // Split shape to left and right half - lo = wt->data[la*(wt->len/128/2)]*15/wt->max; - ro = wt->data[(ra+128)*(wt->len/128/2)]*15/wt->max; + lo=wt->data[la*(wt->len/128/2)]*15/wt->max; + ro=wt->data[(ra+128)*(wt->len/128/2)]*15/wt->max; } else { - lo = wt->data[la*wt->len/128]*15/wt->max; - ro = wt->data[ra*wt->len/128]*15/wt->max; + lo=wt->data[la*wt->len/128]*15/wt->max; + ro=wt->data[ra*wt->len/128]*15/wt->max; } - if (chan[ch].env.flag.envVinv) { ro = 15-ro; } // vertical invert right envelope + if (chan[ch].env.flag.envVinvR) { ro=15-ro; } // vertical invert right envelope + if (chan[ch].env.flag.envVinvL) { lo=15-lo; } // vertical invert left envelope envWrite(ch,i,lo,ro); } else { - int out = wt->data[i*wt->len/128]*15/wt->max; + int out=wt->data[i*wt->len/128]*15/wt->max; envWrite(ch,i,out,out); } } @@ -342,7 +344,7 @@ void DivPlatformX1_010::tick() { chan[i].envChanged=true; } } - if ((!chan[i].pcm) || chan[i].furnacePCM) { + if ((!chan[i].pcm)||chan[i].furnacePCM) { if (chan[i].std.hadArp) { if (!chan[i].inPorta) { if (chan[i].std.arpMode) { @@ -353,13 +355,13 @@ void DivPlatformX1_010::tick() { } chan[i].freqChanged=true; } else { - if (chan[i].std.arpMode && chan[i].std.finishedArp) { + if (chan[i].std.arpMode&&chan[i].std.finishedArp) { chan[i].baseFreq=NoteX1_010(i,chan[i].note); chan[i].freqChanged=true; } } } - if (chan[i].std.hadWave && !chan[i].pcm) { + if (chan[i].std.hadWave&&!chan[i].pcm) { if (chan[i].wave!=chan[i].std.wave) { chan[i].wave=chan[i].std.wave; if (!chan[i].pcm) { @@ -389,21 +391,35 @@ void DivPlatformX1_010::tick() { bool nextSplit=(chan[i].std.ex1&4); if (nextSplit!=(chan[i].env.flag.envSplit)) { chan[i].env.flag.envSplit=nextSplit; - if (!isMuted[i] && !chan[i].pcm) { + if (!isMuted[i]&&!chan[i].pcm) { chan[i].envChanged=true; } } - bool nextHinv=(chan[i].std.ex1&8); - if (nextHinv!=(chan[i].env.flag.envHinv)) { - chan[i].env.flag.envHinv=nextHinv; - if (!isMuted[i] && !chan[i].pcm) { + bool nextHinvR=(chan[i].std.ex1&8); + if (nextHinvR!=(chan[i].env.flag.envHinvR)) { + chan[i].env.flag.envHinvR=nextHinvR; + if (!isMuted[i]&&!chan[i].pcm) { chan[i].envChanged=true; } } - bool nextVinv=(chan[i].std.ex1&16); - if (nextVinv!=(chan[i].env.flag.envVinv)) { - chan[i].env.flag.envVinv=nextVinv; - if (!isMuted[i] && !chan[i].pcm) { + bool nextVinvR=(chan[i].std.ex1&16); + if (nextVinvR!=(chan[i].env.flag.envVinvR)) { + chan[i].env.flag.envVinvR=nextVinvR; + if (!isMuted[i]&&!chan[i].pcm) { + chan[i].envChanged=true; + } + } + bool nextHinvL=(chan[i].std.ex1&32); + if (nextHinvL!=(chan[i].env.flag.envHinvL)) { + chan[i].env.flag.envHinvL=nextHinvL; + if (!isMuted[i]&&!chan[i].pcm) { + chan[i].envChanged=true; + } + } + bool nextVinvL=(chan[i].std.ex1&64); + if (nextVinvL!=(chan[i].env.flag.envVinvL)) { + chan[i].env.flag.envVinvL=nextVinvL; + if (!isMuted[i]&&!chan[i].pcm) { chan[i].envChanged=true; } } @@ -412,7 +428,7 @@ void DivPlatformX1_010::tick() { if (chan[i].env.shape!=chan[i].std.ex2) { chan[i].env.shape=chan[i].std.ex2; if (!chan[i].pcm) { - if (chan[i].env.flag.envEnable && (!isMuted[i])) { + if (chan[i].env.flag.envEnable&&(!isMuted[i])) { chan[i].envChanged=true; } if (!chan[i].keyOff) chan[i].keyOn=true; @@ -441,7 +457,7 @@ void DivPlatformX1_010::tick() { updateEnvelope(i); chan[i].envChanged=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].pitch,false); if (chan[i].pcm) { if (chan[i].freq<1) chan[i].freq=1; @@ -451,12 +467,12 @@ void DivPlatformX1_010::tick() { if (chan[i].freq>65535) chan[i].freq=65535; chWrite(i,2,chan[i].freq&0xff); chWrite(i,3,(chan[i].freq>>8)&0xff); - if (chan[i].freqChanged && chan[i].autoEnvNum>0 && chan[i].autoEnvDen>0) { + if (chan[i].freqChanged&&chan[i].autoEnvNum>0&&chan[i].autoEnvDen>0) { chan[i].env.period=(chan[i].freq*chan[i].autoEnvDen/chan[i].autoEnvNum)>>12; chWrite(i,4,chan[i].env.period); } } - if (chan[i].keyOn || chan[i].keyOff || (chRead(i,0)&1)) { + if (chan[i].keyOn||chan[i].keyOff||(chRead(i,0)&1)) { refreshControl(i); } if (chan[i].keyOn) chan[i].keyOn=false; @@ -492,7 +508,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { case DIV_CMD_NOTE_ON: { chWrite(c.chan,0,0); // reset previous note DivInstrument* ins=parent->getIns(chan[c.chan].ins); - if ((ins->type==DIV_INS_AMIGA) || chan[c.chan].pcm) { + if ((ins->type==DIV_INS_AMIGA)||chan[c.chan].pcm) { if (ins->type==DIV_INS_AMIGA) { chan[c.chan].furnacePCM=true; } else { @@ -503,7 +519,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { chan[c.chan].pcm=true; chan[c.chan].std.init(ins); chan[c.chan].sample=ins->amiga.initSample; - if (chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { + if (chan[c.chan].sample>=0&&chan[c.chan].samplesong.sampleLen) { DivSample* s=parent->getSample(chan[c.chan].sample); chWrite(c.chan,4,(s->offX1_010>>12)&0xff); int end=(s->offX1_010+s->length8+0xfff)&~0xfff; // padded @@ -566,7 +582,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { chan[c.chan].std.release(); break; case DIV_CMD_INSTRUMENT: - if (chan[c.chan].ins!=c.value || c.value2==1) { + if (chan[c.chan].ins!=c.value||c.value2==1) { chan[c.chan].ins=c.value; } break; @@ -658,11 +674,11 @@ int DivPlatformX1_010::dispatch(DivCommand c) { } case DIV_CMD_LEGATO: chan[c.chan].note=c.value; - chan[c.chan].baseFreq=NoteX1_010(c.chan,chan[c.chan].note+((chan[c.chan].std.willArp && !chan[c.chan].std.arpMode)?(chan[c.chan].std.arp):(0))); + chan[c.chan].baseFreq=NoteX1_010(c.chan,chan[c.chan].note+((chan[c.chan].std.willArp&&!chan[c.chan].std.arpMode)?(chan[c.chan].std.arp):(0))); chan[c.chan].freqChanged=true; break; case DIV_CMD_PRE_PORTA: - if (chan[c.chan].active && c.value2) { + if (chan[c.chan].active&&c.value2) { if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); } chan[c.chan].inPorta=c.value; @@ -697,21 +713,35 @@ int DivPlatformX1_010::dispatch(DivCommand c) { bool nextSplit=c.value&4; if (nextSplit!=(chan[c.chan].env.flag.envSplit)) { chan[c.chan].env.flag.envSplit=nextSplit; - if (!isMuted[c.chan] && !chan[c.chan].pcm) { + if (!isMuted[c.chan]&&!chan[c.chan].pcm) { chan[c.chan].envChanged=true; } } - bool nextHinv=c.value&8; - if (nextHinv!=(chan[c.chan].env.flag.envHinv)) { - chan[c.chan].env.flag.envHinv=nextHinv; - if (!isMuted[c.chan] && !chan[c.chan].pcm) { + bool nextHinvR=c.value&8; + if (nextHinvR!=(chan[c.chan].env.flag.envHinvR)) { + chan[c.chan].env.flag.envHinvR=nextHinvR; + if (!isMuted[c.chan]&&!chan[c.chan].pcm) { chan[c.chan].envChanged=true; } } - bool nextVinv=c.value&16; - if (nextVinv!=(chan[c.chan].env.flag.envVinv)) { - chan[c.chan].env.flag.envVinv=nextVinv; - if (!isMuted[c.chan] && !chan[c.chan].pcm) { + bool nextVinvR=c.value&16; + if (nextVinvR!=(chan[c.chan].env.flag.envVinvR)) { + chan[c.chan].env.flag.envVinvR=nextVinvR; + if (!isMuted[c.chan]&&!chan[c.chan].pcm) { + chan[c.chan].envChanged=true; + } + } + bool nextHinvL=c.value&32; + if (nextHinvL!=(chan[c.chan].env.flag.envHinvL)) { + chan[c.chan].env.flag.envHinvL=nextHinvL; + if (!isMuted[c.chan]&&!chan[c.chan].pcm) { + chan[c.chan].envChanged=true; + } + } + bool nextVinvL=c.value&64; + if (nextVinvL!=(chan[c.chan].env.flag.envVinvL)) { + chan[c.chan].env.flag.envVinvL=nextVinvL; + if (!isMuted[c.chan]&&!chan[c.chan].pcm) { chan[c.chan].envChanged=true; } } diff --git a/src/engine/platform/x1_010.h b/src/engine/platform/x1_010.h index 8e03109ba..cb9ad7b42 100644 --- a/src/engine/platform/x1_010.h +++ b/src/engine/platform/x1_010.h @@ -44,21 +44,27 @@ class DivPlatformX1_010: public DivDispatch { unsigned char envEnable : 1; unsigned char envOneshot : 1; unsigned char envSplit : 1; - unsigned char envHinv : 1; - unsigned char envVinv : 1; + unsigned char envHinvR : 1; + unsigned char envVinvR : 1; + unsigned char envHinvL : 1; + unsigned char envVinvL : 1; void reset() { envEnable=0; envOneshot=0; envSplit=0; - envHinv=0; - envVinv=0; + envHinvR=0; + envVinvR=0; + envHinvL=0; + envVinvL=0; } EnvFlag(): envEnable(0), envOneshot(0), envSplit(0), - envHinv(0), - envVinv(0) {} + envHinvR(0), + envVinvR(0), + envHinvL(0), + envVinvL(0) {} }; int shape, period, slide, slidefrac; EnvFlag flag; diff --git a/src/gui/debug.cpp b/src/gui/debug.cpp index fc2d51ea1..ddbfb5b57 100644 --- a/src/gui/debug.cpp +++ b/src/gui/debug.cpp @@ -268,8 +268,10 @@ void putDispatchChan(void* data, int chanNum, int type) { ImGui::TextColored(ch->env.flag.envEnable?colorOn:colorOff,">> EnvEnable"); ImGui::TextColored(ch->env.flag.envOneshot?colorOn:colorOff,">> EnvOneshot"); ImGui::TextColored(ch->env.flag.envSplit?colorOn:colorOff,">> EnvSplit"); - ImGui::TextColored(ch->env.flag.envHinv?colorOn:colorOff,">> EnvHinv"); - ImGui::TextColored(ch->env.flag.envVinv?colorOn:colorOff,">> EnvVinv"); + ImGui::TextColored(ch->env.flag.envHinvR?colorOn:colorOff,">> EnvHinvR"); + ImGui::TextColored(ch->env.flag.envVinvR?colorOn:colorOff,">> EnvVinvR"); + ImGui::TextColored(ch->env.flag.envHinvL?colorOn:colorOff,">> EnvHinvL"); + ImGui::TextColored(ch->env.flag.envVinvL?colorOn:colorOff,">> EnvVinvL"); break; } default: diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 0b7ce0ab2..260713e99 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -150,8 +150,8 @@ const char* mikeyFeedbackBits[11] = { "0", "1", "2", "3", "4", "5", "7", "10", "11", "int", NULL }; -const char* x1_010EnvBits[6]={ - "enable", "oneshot", "split L/R", "Hinv", "Vinv", NULL +const char* x1_010EnvBits[8]={ + "enable", "oneshot", "split L/R", "HinvR", "VinvR", "HinvL", "VinvL", NULL }; const char* oneBit[2]={ @@ -1411,7 +1411,7 @@ void FurnaceGUI::drawInsEdit() { } if (ins->type==DIV_INS_X1_010) { dutyMax=0; - ex1Max=5; + ex1Max=7; ex2Max=63; ex2Bit=false; } From 66eb40e55e75b0f603cfcb240a381f3d25fc7279 Mon Sep 17 00:00:00 2001 From: cam900 Date: Wed, 9 Mar 2022 01:00:09 +0900 Subject: [PATCH 163/637] Extract X1-010 core from submodule --- .gitmodules | 4 - CMakeLists.txt | 4 +- extern/cam900_vgsound_emu | 1 - src/engine/platform/sound/x1_010/x1_010.cpp | 224 ++++++++++++++++++++ src/engine/platform/sound/x1_010/x1_010.hpp | 127 +++++++++++ src/engine/platform/x1_010.h | 4 +- 6 files changed, 355 insertions(+), 9 deletions(-) delete mode 160000 extern/cam900_vgsound_emu create mode 100644 src/engine/platform/sound/x1_010/x1_010.cpp create mode 100644 src/engine/platform/sound/x1_010/x1_010.hpp diff --git a/.gitmodules b/.gitmodules index 8c4c6b0dd..d63fd70b5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -19,10 +19,6 @@ [submodule "extern/adpcm"] path = extern/adpcm url = https://github.com/superctr/adpcm -[submodule "extern/cam900_vgsound_emu"] - path = extern/cam900_vgsound_emu - url = https://github.com/cam900/vgsound_emu - branch = main [submodule "extern/Nuked-OPL3"] path = extern/Nuked-OPL3 url = https://github.com/nukeykt/Nuked-OPL3.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 23c8dffcf..364875867 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -228,8 +228,6 @@ extern/opm/opm.c extern/Nuked-OPLL/opll.c extern/Nuked-OPL3/opl3.c -extern/cam900_vgsound_emu/x1_010/x1_010.cpp - src/engine/platform/sound/sn76496.cpp src/engine/platform/sound/ay8910.cpp src/engine/platform/sound/saa1099.cpp @@ -268,6 +266,8 @@ src/engine/platform/sound/lynx/Mikey.cpp src/engine/platform/sound/qsound.c +src/engine/platform/sound/x1_010/x1_010.cpp + src/engine/platform/sound/swan.cpp src/engine/platform/ym2610Interface.cpp diff --git a/extern/cam900_vgsound_emu b/extern/cam900_vgsound_emu deleted file mode 160000 index 3f8b5c5c6..000000000 --- a/extern/cam900_vgsound_emu +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 3f8b5c5c6b996588afec7e4ce7469abccf16ecbe diff --git a/src/engine/platform/sound/x1_010/x1_010.cpp b/src/engine/platform/sound/x1_010/x1_010.cpp new file mode 100644 index 000000000..ea1e52ace --- /dev/null +++ b/src/engine/platform/sound/x1_010/x1_010.cpp @@ -0,0 +1,224 @@ +/* + License: BSD-3-Clause + see https://github.com/cam900/vgsound_emu/LICENSE for more details + + Copyright holders: cam900 + Seta/Allumer X1-010 Emulation core + + the chip has 16 voices, all voices can be switchable to Wavetable or PCM sample playback mode. + It has also 2 output channels, but no known hardware using this feature for stereo sound. + + Wavetable needs to paired with envelope, it's always enabled and similar as AY PSG's one + but its shape is stored at RAM. + + PCM volume is stored by each register. + + Both volume is 4bit per output. + + Everything except PCM sample is stored at paired 8 bit RAM. + + RAM layout (common case: Address bit 12 is swapped when RAM is shared with CPU) + + ----------------------------- + 0000...007f Voice Registers + + 0000...0007 Voice 0 Register + + Address Bits Description + 7654 3210 + 0 x--- ---- Frequency divider* + ---- -x-- Envelope one-shot mode + ---- --x- Sound format + ---- --0- PCM + ---- --1- Wavetable + ---- ---x Keyon/off + PCM case: + 1 xxxx xxxx Volume (Each nibble is for each output) + + 2 xxxx xxxx Frequency* + + 4 xxxx xxxx Start address / 4096 + + 5 xxxx xxxx 0x100 - (End address / 4096) + Wavetable case: + 1 ---x xxxx Wavetable data select + + 2 xxxx xxxx Frequency LSB* + 3 xxxx xxxx "" MSB + + 4 xxxx xxxx Envelope period (.10 fixed point, Low 8 bit) + + 5 ---x xxxx Envelope shape select (!= 0 : Reserved for Voice registers) + + 0008...000f Voice 1 Register + ... + 0078...007f Voice 15 Register + ----------------------------- + 0080...0fff Envelope shape data (Same as volume; Each nibble is for each output) + + 0080...00ff Envelope shape data 1 + 0100...017f Envelope shape data 2 + ... + 0f80...0fff Envelope shape data 31 + ----------------------------- + 1000...1fff Wavetable data + + 1000...107f Wavetable data 0 + 1080...10ff Wavetable data 1 + ... + 1f80...1fff Wavetable data 31 + ----------------------------- + + * Frequency is 4.4 fixed point for PCM, + 6.10 for Wavetable. + Frequency divider is higher precision or just right shift? + needs verification. +*/ + +#include "x1_010.hpp" + +void x1_010_core::tick() +{ + // reset output + m_out[0] = m_out[1] = 0; + for (int i = 0; i < 16; i++) + { + voice_t &v = m_voice[i]; + v.tick(); + m_out[0] += v.data * v.vol_out[0]; + m_out[1] += v.data * v.vol_out[1]; + } +} + +void x1_010_core::voice_t::tick() +{ + data = vol_out[0] = vol_out[1] = 0; + if (flag.keyon) + { + if (flag.wavetable) // Wavetable + { + // envelope, each nibble is for each output + u8 vol = m_host.m_envelope[(bitfield(end_envshape, 0, 5) << 7) | bitfield(env_acc, 10, 7)]; + vol_out[0] = bitfield(vol, 4, 4); + vol_out[1] = bitfield(vol, 0, 4); + env_acc += start_envfreq; + if (flag.env_oneshot && bitfield(env_acc, 17)) + flag.keyon = false; + else + env_acc = bitfield(env_acc, 0, 17); + // get wavetable data + data = m_host.m_wave[(bitfield(vol_wave, 0, 5) << 7) | bitfield(acc, 10, 7)]; + acc = bitfield(acc + (freq >> flag.div), 0, 17); + } + else // PCM sample + { + // volume register, each nibble is for each output + vol_out[0] = bitfield(vol_wave, 4, 4); + vol_out[1] = bitfield(vol_wave, 0, 4); + // get PCM sample + data = m_host.m_intf.read_byte(bitfield(acc, 4, 20)); + acc += bitfield(freq, 0, 8) >> flag.div; + if ((acc >> 16) > (0xff ^ end_envshape)) + flag.keyon = false; + } + } +} + +u8 x1_010_core::ram_r(u16 offset) +{ + if (offset & 0x1000) // wavetable data + return m_wave[offset & 0xfff]; + else if (offset & 0xf80) // envelope shape data + return m_envelope[offset & 0xfff]; + else // channel register + return m_voice[bitfield(offset, 3, 4)].reg_r(offset & 0x7); +} + +void x1_010_core::ram_w(u16 offset, u8 data) +{ + if (offset & 0x1000) // wavetable data + m_wave[offset & 0xfff] = data; + else if (offset & 0xf80) // envelope shape data + m_envelope[offset & 0xfff] = data; + else // channel register + m_voice[bitfield(offset, 3, 4)].reg_w(offset & 0x7, data); +} + +u8 x1_010_core::voice_t::reg_r(u8 offset) +{ + switch (offset & 0x7) + { + case 0x00: return (flag.div << 7) + | (flag.env_oneshot << 2) + | (flag.wavetable << 1) + | (flag.keyon << 0); + case 0x01: return vol_wave; + case 0x02: return bitfield(freq, 0, 8); + case 0x03: return bitfield(freq, 8, 8); + case 0x04: return start_envfreq; + case 0x05: return end_envshape; + default: break; + } + return 0; +} + +void x1_010_core::voice_t::reg_w(u8 offset, u8 data) +{ + switch (offset & 0x7) + { + case 0x00: + { + const bool prev_keyon = flag.keyon; + flag.div = bitfield(data, 7); + flag.env_oneshot = bitfield(data, 2); + flag.wavetable = bitfield(data, 1); + flag.keyon = bitfield(data, 0); + if (!prev_keyon && flag.keyon) // Key on + { + acc = flag.wavetable ? 0 : (u32(start_envfreq) << 16); + env_acc = 0; + } + break; + } + case 0x01: + vol_wave = data; + break; + case 0x02: + freq = (freq & 0xff00) | data; + break; + case 0x03: + freq = (freq & 0x00ff) | (u16(data) << 8); + break; + case 0x04: + start_envfreq = data; + break; + case 0x05: + end_envshape = data; + break; + default: + break; + } +} + +void x1_010_core::voice_t::reset() +{ + flag.reset(); + vol_wave = 0; + freq = 0; + start_envfreq = 0; + end_envshape = 0; + acc = 0; + env_acc = 0; + data = 0; + vol_out[0] = vol_out[1] = 0; +} + +void x1_010_core::reset() +{ + for (int i = 0; i < 16; i++) + m_voice[i].reset(); + + std::fill_n(&m_envelope[0], 0x1000, 0); + std::fill_n(&m_wave[0], 0x1000, 0); + m_out[0] = m_out[1] = 0; +} diff --git a/src/engine/platform/sound/x1_010/x1_010.hpp b/src/engine/platform/sound/x1_010/x1_010.hpp new file mode 100644 index 000000000..d5c429fda --- /dev/null +++ b/src/engine/platform/sound/x1_010/x1_010.hpp @@ -0,0 +1,127 @@ +/* + License: BSD-3-Clause + see https://github.com/cam900/vgsound_emu/LICENSE for more details + + Copyright holders: cam900 + Seta/Allumer X1-010 Emulation core + + See x1_010.cpp for more info. +*/ + +#include +#include + +#ifndef _VGSOUND_EMU_X1_010_HPP +#define _VGSOUND_EMU_X1_010_HPP + +#pragma once + +typedef unsigned char u8; +typedef unsigned short u16; +typedef unsigned int u32; +typedef signed char s8; +typedef signed int s32; + +template T bitfield(T in, u8 pos, u8 len = 1) +{ + return (in >> pos) & (len ? (T(1 << len) - 1) : 1); +} + +class x1_010_mem_intf +{ +public: + virtual u8 read_byte(u32 address) { return 0; } +}; + +class x1_010_core +{ + friend class x1_010_mem_intf; +public: + // constructor + x1_010_core(x1_010_mem_intf &intf) + : m_voice{*this,*this,*this,*this, + *this,*this,*this,*this, + *this,*this,*this,*this, + *this,*this,*this,*this} + , m_intf(intf) + { + m_envelope = std::make_unique(0x1000); + m_wave = std::make_unique(0x1000); + + std::fill_n(&m_envelope[0], 0x1000, 0); + std::fill_n(&m_wave[0], 0x1000, 0); + } + + // register accessor + u8 ram_r(u16 offset); + void ram_w(u16 offset, u8 data); + + // getters + s32 output(u8 channel) { return m_out[channel & 1]; } + + // internal state + void reset(); + void tick(); + +private: + // 16 voices in chip + struct voice_t + { + // constructor + voice_t(x1_010_core &host) : m_host(host) {} + + // internal state + void reset(); + void tick(); + + // register accessor + u8 reg_r(u8 offset); + void reg_w(u8 offset, u8 data); + + // registers + x1_010_core &m_host; + struct flag_t + { + u8 div : 1; + u8 env_oneshot : 1; + u8 wavetable : 1; + u8 keyon : 1; + void reset() + { + div = 0; + env_oneshot = 0; + wavetable = 0; + keyon = 0; + } + flag_t() + : div(0) + , env_oneshot(0) + , wavetable(0) + , keyon(0) + { } + }; + flag_t flag; + u8 vol_wave = 0; + u16 freq = 0; + u8 start_envfreq = 0; + u8 end_envshape = 0; + + // internal registers + u32 acc = 0; + u32 env_acc = 0; + s8 data = 0; + u8 vol_out[2] = {0}; + }; + voice_t m_voice[16]; + + // RAM + std::unique_ptr m_envelope = nullptr; + std::unique_ptr m_wave = nullptr; + + // output data + s32 m_out[2] = {0}; + + x1_010_mem_intf &m_intf; +}; + +#endif diff --git a/src/engine/platform/x1_010.h b/src/engine/platform/x1_010.h index cb9ad7b42..73cbaf6b1 100644 --- a/src/engine/platform/x1_010.h +++ b/src/engine/platform/x1_010.h @@ -24,9 +24,9 @@ #include "../dispatch.h" #include "../engine.h" #include "../macroInt.h" -#include "../../../extern/cam900_vgsound_emu/x1_010/x1_010.hpp" +#include "sound/x1_010/x1_010.hpp" -class DivX1_010Interface: public vgsound_emu_mem_intf { +class DivX1_010Interface: public x1_010_mem_intf { public: DivEngine* parent; int sampleBank; From 75b635229ce50c08edd8dcede663acc2919c208c Mon Sep 17 00:00:00 2001 From: cam900 Date: Wed, 9 Mar 2022 01:01:40 +0900 Subject: [PATCH 164/637] Unnecessary changes --- CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 364875867..eafb87623 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -227,7 +227,6 @@ extern/Nuked-OPN2/ym3438.c extern/opm/opm.c extern/Nuked-OPLL/opll.c extern/Nuked-OPL3/opl3.c - src/engine/platform/sound/sn76496.cpp src/engine/platform/sound/ay8910.cpp src/engine/platform/sound/saa1099.cpp From ba68ad6ed52e5b79e2b226665f890f15a1a2f242 Mon Sep 17 00:00:00 2001 From: cam900 Date: Wed, 9 Mar 2022 01:06:47 +0900 Subject: [PATCH 165/637] More info in waveform size --- papers/doc/7-systems/x1_010.md | 1 + 1 file changed, 1 insertion(+) diff --git a/papers/doc/7-systems/x1_010.md b/papers/doc/7-systems/x1_010.md index e2e44f183..76517b642 100644 --- a/papers/doc/7-systems/x1_010.md +++ b/papers/doc/7-systems/x1_010.md @@ -21,6 +21,7 @@ Another one ("Envelope") is 4 bit stereo waveform, its multiplied with above and These are stored at upper half of RAM at common case. Both waveforms are 128 byte fixed size, its freely allocated at each half of RAM except channel register area: each half can be stored total 32/31 waveforms at once. +in furnace, You can set envelope shape split mode. when it sets, its waveform will be splitted to left half and right half for each outputs. each max sizes are 128 bytes, total 256 bytes. # effects From a32781bb1a1d02d4bf45775247f78d58f5d1d327 Mon Sep 17 00:00:00 2001 From: cam900 Date: Wed, 9 Mar 2022 01:17:16 +0900 Subject: [PATCH 166/637] grammar --- papers/doc/7-systems/x1_010.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/papers/doc/7-systems/x1_010.md b/papers/doc/7-systems/x1_010.md index 76517b642..759d519c9 100644 --- a/papers/doc/7-systems/x1_010.md +++ b/papers/doc/7-systems/x1_010.md @@ -1,27 +1,27 @@ # Seta/Allumer X1-010 One of sound chip originally designed by Seta, mainly used at their arcade hardwares at late-80s to early-2000s. -it has 2 output channel, but no known hardware using this feature for stereo sound. -later hardware paired this with external bankswitching logic, but its logic is not emulated now in current furnace revision. +It has 2 output channels, but no known hardware using this feature for stereo sound. +Later hardware paired this with external bankswitching logic, but its logic is not emulated now in current furnace revision. Allumer one is just rebadged Seta's thing for use in their arcade hardwares. It has 16 channels, and all channels can be switchable to PCM sample or wavetable playback mode. -Wavetable needs to paired with envelope, this feature is similar as AY PSG but it's shape are stored at RAM: it means it is user-definable. +Wavetable needs to paired with envelope, this feature is similar as AY PSG, but its shape are stored at RAM: it means it is user-definable. -In furnace, this chip is can be configurable for original arcade mono output or stereo output - its simulates early 'incorrect' emulation on some mono hardware but it is also based on the assumption that each channel is connected to each output. +In furnace, this chip is can be configurable for original arcade mono output or stereo output - it simulates early 'incorrect' emulation on some mono hardware, but it is also based on the assumption that each channel is connected to each output. # waveform type -This chip supports 2 type waveforms, needs to paired external 8KB RAM for use these feature: +This chip supports 2 type waveforms, needs to paired external 8 KB RAM for use these features: -One is signed 8 bit mono waveform, its operated like other wavetable based sound systems. -These are stored at bottom half of RAM at common case. +One is signed 8 bit mono waveform, it's operated like other wavetable based sound systems. +These are stored at the bottom half of RAM at common case. -Another one ("Envelope") is 4 bit stereo waveform, its multiplied with above and calculates final output, Each nibble is used for each output channels. -These are stored at upper half of RAM at common case. +Another one ("Envelope") is 4 bit stereo waveform, it's multiplied with above and calculates final output, Each nibble is used for each output channels. +These are stored at the upper half of RAM at common case. -Both waveforms are 128 byte fixed size, its freely allocated at each half of RAM except channel register area: each half can be stored total 32/31 waveforms at once. -in furnace, You can set envelope shape split mode. when it sets, its waveform will be splitted to left half and right half for each outputs. each max sizes are 128 bytes, total 256 bytes. +Both waveforms are 128 byte fixed size, it's freely allocated at each half of RAM except channel register area: each half can be stored total 32/31 waveforms at once. +In furnace, You can set envelope shape split mode. When it sets, its waveform will be split to left half and right half for each outputs. each max size are 128 bytes, total 256 bytes. # effects From eb3a73c38b552d010c9e621341159a1beb42310a Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Wed, 9 Mar 2022 00:58:21 +0700 Subject: [PATCH 167/637] Mute on pan registers instead --- src/engine/platform/vera.cpp | 57 +++++++++++++++++++++--------------- src/engine/platform/vera.h | 8 ++--- 2 files changed, 38 insertions(+), 27 deletions(-) diff --git a/src/engine/platform/vera.cpp b/src/engine/platform/vera.cpp index 4d0355731..49255be80 100644 --- a/src/engine/platform/vera.cpp +++ b/src/engine/platform/vera.cpp @@ -93,19 +93,27 @@ void DivPlatformVERA::acquire(short* bufL, short* bufR, size_t start, size_t len if (chan[16].accum>=128) { DivSample* s=parent->getSample(chan[16].pcm.sample); if (s->samples>0) { - // TODO stereo samples once DivSample has a support for it - switch (s->depth) { - case 8: - chan[16].pcm.out_l=chan[16].pcm.pan&1?(s->data8[chan[16].pcm.pos]*256):0; - chan[16].pcm.out_r=chan[16].pcm.pan&2?(s->data8[chan[16].pcm.pos]*256):0; - regPool[64]|=0x20; // for register viewer purposes - break; - case 16: - chan[16].pcm.out_l=chan[16].pcm.pan&1?(s->data16[chan[16].pcm.pos]):0; - chan[16].pcm.out_r=chan[16].pcm.pan&2?(s->data16[chan[16].pcm.pos]):0; - regPool[64]&=~0x20; - break; + int tmp_l=0; + int tmp_r=0; + if (!isMuted[16]) { + // TODO stereo samples once DivSample has a support for it + switch (s->depth) { + case 8: + tmp_l=s->data8[chan[16].pcm.pos]*256; + tmp_r=tmp_l; + regPool[64]|=0x20; // for register viewer purposes + break; + case 16: + tmp_l=s->data16[chan[16].pcm.pos]; + tmp_r=tmp_l; + regPool[64]&=~0x20; + break; + } + if (!(chan[16].pan&1)) tmp_l=0; + if (!(chan[16].pan&2)) tmp_r=0; } + chan[16].pcm.out_l=tmp_l; + chan[16].pcm.out_r=tmp_r; } else { chan[16].pcm.sample=-1; } @@ -136,9 +144,11 @@ void DivPlatformVERA::reset() { memset(regPool,0,66); for (int i=0; i<16; i++) { chan[i].vol=63; - rWriteHi(i,2,3); // default pan + chan[i].pan=3; + rWriteHi(i,2,isMuted[i]?0:3); } chan[16].vol=15; + chan[16].pan=3; noiseState=1; noiseOut=0; } @@ -165,7 +175,7 @@ void DivPlatformVERA::tick() { chan[i].std.next(); if (chan[i].std.hadVol) { chan[i].outVol=MAX(chan[i].vol+chan[i].std.vol-63,0); - rWriteLo(i,2,isMuted[i]?0:(chan[i].outVol&63)); + rWriteLo(i,2,chan[i].outVol); } if (chan[i].std.hadArp) { if (!chan[i].inPorta) { @@ -200,7 +210,7 @@ void DivPlatformVERA::tick() { chan[16].std.next(); if (chan[16].std.hadVol) { chan[16].outVol=MAX(chan[16].vol+MIN(chan[16].std.vol/4,15)-15,0); - rWriteFIFOVol(isMuted[16]?0:(chan[16].outVol&15)); + rWriteFIFOVol(chan[16].outVol&15); } if (chan[16].std.hadArp) { if (!chan[16].inPorta) { @@ -229,16 +239,15 @@ int DivPlatformVERA::dispatch(DivCommand c) { int tmp; switch (c.cmd) { case DIV_CMD_NOTE_ON: - tmp = isMuted[c.chan]?0:chan[c.chan].vol; if(c.chan<16) { - rWriteLo(c.chan,2,tmp) + rWriteLo(c.chan,2,chan[c.chan].vol) } else { chan[c.chan].pcm.sample=parent->getIns(chan[16].ins)->amiga.initSample; if (chan[c.chan].pcm.sample<0 || chan[c.chan].pcm.sample>=parent->song.sampleLen) { chan[c.chan].pcm.sample=-1; } chan[16].pcm.pos=0; - rWriteFIFOVol(tmp); + rWriteFIFOVol(chan[c.chan].vol); } if (c.value!=DIV_NOTE_NULL) { chan[c.chan].baseFreq=calcNoteFreq(c.chan,c.value); @@ -270,11 +279,11 @@ int DivPlatformVERA::dispatch(DivCommand c) { if (c.chan<16) { tmp=c.value&0x3f; chan[c.chan].vol=tmp; - rWriteLo(c.chan,2,(isMuted[c.chan]?0:tmp)); + rWriteLo(c.chan,2,tmp); } else { tmp=c.value&0x0f; chan[c.chan].vol=tmp; - rWriteFIFOVol(isMuted[c.chan]?0:tmp); + rWriteFIFOVol(tmp); } break; case DIV_CMD_GET_VOLUME: @@ -328,10 +337,9 @@ int DivPlatformVERA::dispatch(DivCommand c) { tmp=0; tmp|=(c.value&0x10)?1:0; tmp|=(c.value&0x01)?2:0; + chan[c.chan].pan=tmp&3; if (c.chan<16) { - rWriteHi(c.chan,2,tmp); - } else { - chan[c.chan].pcm.pan = tmp&3; + rWriteHi(c.chan,2,isMuted[c.chan]?0:chan[c.chan].pan); } break; } @@ -365,6 +373,9 @@ int DivPlatformVERA::getRegisterPoolSize() { void DivPlatformVERA::muteChannel(int ch, bool mute) { isMuted[ch]=mute; + if (ch<16) { + rWriteHi(ch,2,mute?0:chan[ch].pan); + } } bool DivPlatformVERA::isStereo() { diff --git a/src/engine/platform/vera.h b/src/engine/platform/vera.h index 64a8ee7c1..e5cb2ad9e 100644 --- a/src/engine/platform/vera.h +++ b/src/engine/platform/vera.h @@ -27,7 +27,7 @@ class DivPlatformVERA: public DivDispatch { protected: struct Channel { int freq, baseFreq, pitch, note; - unsigned char ins; + unsigned char ins, pan; bool active, freqChanged, inPorta; int vol, outVol; unsigned accum; @@ -39,10 +39,10 @@ class DivPlatformVERA: public DivDispatch { int out_l, out_r; unsigned pos; unsigned len; - unsigned char freq, pan; - PCMChannel(): sample(-1), out_l(0), out_r(0), pos(0), len(0), freq(0), pan(3) {} + unsigned char freq; + PCMChannel(): sample(-1), out_l(0), out_r(0), pos(0), len(0), freq(0) {} } pcm; - Channel(): freq(0), baseFreq(0), pitch(0), note(0), ins(-1), active(false), freqChanged(false), inPorta(false), vol(0), outVol(0), accum(0), noiseval(0) {} + Channel(): freq(0), baseFreq(0), pitch(0), note(0), ins(-1), pan(0), active(false), freqChanged(false), inPorta(false), vol(0), outVol(0), accum(0), noiseval(0) {} }; Channel chan[17]; bool isMuted[17]; From ad19a69f279cf9ed5ef53a5d98473d4b4a42f254 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 8 Mar 2022 13:12:20 -0500 Subject: [PATCH 168/637] GUI: improvements to OPL ins edit --- src/gui/insEdit.cpp | 46 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index d51db3edf..4bb9ce543 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -84,6 +84,10 @@ const char* opllInsNames[17]={ "Drums" }; +const char* oplWaveforms[8]={ + "Sine", "Half Sine", "Absolute Sine", "Quarter Sine", "Squished Sine", "Squished AbsSine", "Square", "Derived Square" +}; + enum FMParams { FM_ALG=0, FM_FB=1, @@ -819,13 +823,19 @@ void FurnaceGUI::drawInsEdit() { break; case DIV_INS_OPL: { bool fourOp=(ins->fm.ops==4); + bool drums=ins->fm.opllPreset==16; + int algMax=fourOp?3:1; ImGui::TableNextColumn(); + ins->fm.alg&=algMax; P(ImGui::SliderScalar(FM_NAME(FM_FB),ImGuiDataType_U8,&ins->fm.fb,&_ZERO,&_SEVEN)); rightClickable if (ImGui::Checkbox("4-op",&fourOp)) { PARAMETER ins->fm.ops=fourOp?4:2; } ImGui::TableNextColumn(); - P(ImGui::SliderScalar(FM_NAME(FM_ALG),ImGuiDataType_U8,&ins->fm.alg,&_ZERO,&_SEVEN)); rightClickable + P(ImGui::SliderScalar(FM_NAME(FM_ALG),ImGuiDataType_U8,&ins->fm.alg,&_ZERO,&algMax)); rightClickable + if (ImGui::Checkbox("Drums",&drums)) { PARAMETER + ins->fm.opllPreset=drums?16:0; + } ImGui::TableNextColumn(); drawAlgorithm(ins->fm.alg&1,FM_ALGS_2OP_OPL,ImVec2(ImGui::GetContentRegionAvail().x,48.0*dpiScale)); break; @@ -977,18 +987,30 @@ void FurnaceGUI::drawInsEdit() { maxTl=63; } } + if (ins->type==DIV_INS_OPL) { + maxTl=63; + } int maxArDr=(ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ)?31:15; bool ssgOn=op.ssgEnv&8; bool ksrOn=op.ksr; bool vibOn=op.vib; + bool susOn=op.sus; // don't you make fun of this one unsigned char ssgEnv=op.ssgEnv&7; - if (ImGui::Checkbox((ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPLL)?FM_NAME(FM_EGS):"SSG On",&ssgOn)) { PARAMETER - op.ssgEnv=(op.ssgEnv&7)|(ssgOn<<3); + if (ins->type!=DIV_INS_OPL && ins->type!=DIV_INS_OPZ) { + if (ImGui::Checkbox((ins->type==DIV_INS_OPLL)?FM_NAME(FM_EGS):"SSG On",&ssgOn)) { PARAMETER + op.ssgEnv=(op.ssgEnv&7)|(ssgOn<<3); + } + if (ins->type==DIV_INS_FM) { + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Only for Genesis and Neo Geo systems"); + } + } } - if (ins->type==DIV_INS_FM) { - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Only for Genesis and Neo Geo systems"); + + if (ins->type==DIV_INS_OPL) { + if (ImGui::Checkbox(FM_NAME(FM_SUS),&susOn)) { PARAMETER + op.sus=susOn; } } @@ -1102,6 +1124,18 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); ImGui::Text("%s",FM_NAME(FM_SSG)); } + + if (ins->type==DIV_INS_OPL) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + P(ImGui::SliderScalar("##WS",ImGuiDataType_U8,&op.ws,&_ZERO,&_SEVEN,oplWaveforms[op.ws&7])); rightClickable + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("OPL2/3 only (last 4 waveforms are OPL3 only)"); + } + ImGui::TableNextColumn(); + ImGui::Text("%s",FM_NAME(FM_WS)); + } ImGui::EndTable(); } From 42a3391574dd7365692df7c5d381d46512c43735 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 8 Mar 2022 13:46:48 -0500 Subject: [PATCH 169/637] GUI: add OPZ waveforms --- src/gui/insEdit.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 4bb9ce543..274fed360 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -88,6 +88,10 @@ const char* oplWaveforms[8]={ "Sine", "Half Sine", "Absolute Sine", "Quarter Sine", "Squished Sine", "Squished AbsSine", "Square", "Derived Square" }; +const char* opzWaveforms[8]={ + "Sine", "Triangle", "Cut Sine", "Cut Triangle", "Squished Sine", "Squished Triangle" "Squished AbsSine", "Squished AbsTriangle" +}; + enum FMParams { FM_ALG=0, FM_FB=1, @@ -1125,12 +1129,12 @@ void FurnaceGUI::drawInsEdit() { ImGui::Text("%s",FM_NAME(FM_SSG)); } - if (ins->type==DIV_INS_OPL) { + if (ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPZ) { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - P(ImGui::SliderScalar("##WS",ImGuiDataType_U8,&op.ws,&_ZERO,&_SEVEN,oplWaveforms[op.ws&7])); rightClickable - if (ImGui::IsItemHovered()) { + P(ImGui::SliderScalar("##WS",ImGuiDataType_U8,&op.ws,&_ZERO,&_SEVEN,(ins->type==DIV_INS_OPZ)?opzWaveforms[op.ws&7]:oplWaveforms[op.ws&7])); rightClickable + if (ins->type==DIV_INS_OPL && ImGui::IsItemHovered()) { ImGui::SetTooltip("OPL2/3 only (last 4 waveforms are OPL3 only)"); } ImGui::TableNextColumn(); From 698b8e960fa21e0b3e9b743ba507efa53b391ae7 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 8 Mar 2022 14:20:21 -0500 Subject: [PATCH 170/637] I must be blind --- src/gui/insEdit.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 274fed360..aaab3f644 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -89,7 +89,7 @@ const char* oplWaveforms[8]={ }; const char* opzWaveforms[8]={ - "Sine", "Triangle", "Cut Sine", "Cut Triangle", "Squished Sine", "Squished Triangle" "Squished AbsSine", "Squished AbsTriangle" + "Sine", "Triangle", "Cut Sine", "Cut Triangle", "Squished Sine", "Squished Triangle", "Squished AbsSine", "Squished AbsTriangle" }; enum FMParams { From 9e080956ec692aa7685feb7b0d06ea7a26666e21 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 8 Mar 2022 18:26:41 -0500 Subject: [PATCH 171/637] prepare to add a right click menu to pattern DO NOT USE IF YOU WANT TO SOLO CHANNELS - I'LL FIX IT when I come back --- src/gui/pattern.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/gui/pattern.cpp b/src/gui/pattern.cpp index 2eabcd04b..20f8975f5 100644 --- a/src/gui/pattern.cpp +++ b/src/gui/pattern.cpp @@ -807,6 +807,24 @@ void FurnaceGUI::drawPattern() { ImGui::PopFont(); } ImGui::PopStyleVar(); + if (patternOpen) if (ImGui::BeginPopupContextItem("furnace quit unexpectedly.")) { + char id[4096]; + ImGui::Selectable("cut"); + ImGui::Selectable("copy"); + ImGui::Selectable("paste"); + if (ImGui::BeginMenu("change instrument...")) { + if (e->song.ins.empty()) { + ImGui::Text("no instruments available"); + } + for (size_t i=0; isong.ins.size(); i++) { + snprintf(id,4095,"%.2lX: %s",i,e->song.ins[i]->name.c_str()); + if (ImGui::Selectable(id)) { // TODO + } + } + ImGui::EndMenu(); + } + ImGui::EndPopup(); + } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_PATTERN; ImGui::End(); } From 0826f22d8ba839c6ca625241ae71227570dd6c05 Mon Sep 17 00:00:00 2001 From: nicco1690 <78063037+nicco1690@users.noreply.github.com> Date: Tue, 8 Mar 2022 22:42:43 -0500 Subject: [PATCH 172/637] Update docs to add more info Thanks to @freq-mod for providing the effect commands section and some more general information which has been copied into this version. --- papers/doc/7-systems/lynx.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/papers/doc/7-systems/lynx.md b/papers/doc/7-systems/lynx.md index c9c9bc26d..2e2f3e3c5 100644 --- a/papers/doc/7-systems/lynx.md +++ b/papers/doc/7-systems/lynx.md @@ -2,11 +2,19 @@ The Atari Lynx is a 16 bit handheld console developed by (obviously) Atari Corporation, and initially released in September of 1989, with the worldwide release being in 1990. +The Lynx, while being an incredible handheld for the time (and a lot more powerful than a Game Boy), unfortunately meant nothing in the end due to the Lynx being a market failiure, and ending up as one of the things that contributed to the downfall of Atari. + +Although the Lynx is still getting (rather impressive) homebrew developed for it, it does not mean that the Lynx is a popular system at all. + The Atari Lynx's custom sound chip and CPU (MIKEY) is a 6502-based 8 bit CPU running at 16MHz, however this information is generally not useful in the context of Furnace. ## Sound capabilities - The MIKEY has 4 channels of square wave-based sound, which can be modulated with different frequencies (×0, ×1, ×2, ×3, ×4, ×5, ×7, ×10, and ×11) to create wavetable-like results. + - Likewise, when a lot of the modulators are activated, this can provide a "pseudo-white noise"-like effect, whoch can be useful for drums and sound effects. - The MIKEY also has hard stereo panning capabilities via the `08xx` effect command. - The MIKEY has four 8-bit DACs (Digital to Analog Converter) — one for each voice — that essentially mean you can play samples on the MIKEY (at the cost of CPU time and memory). - The MIKEY also has a variety of pitches to choose from, and they go from 32Hz to "above the range of human hearing", according to Atari. + +## Effect commands + - `3xxx`: Load LFSR (0 to FFF). For it to work, duty macro in instrument editor must be set to some value, without it LFSR will not be fed with any bits. From f372088aad049c4268a69d4b8dab7394bafbf6c0 Mon Sep 17 00:00:00 2001 From: nicco1690 <78063037+nicco1690@users.noreply.github.com> Date: Tue, 8 Mar 2022 23:15:52 -0500 Subject: [PATCH 173/637] Create OPL3 docs (read Effect Commands section) --- papers/doc/7-systems/opl3.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 papers/doc/7-systems/opl3.md diff --git a/papers/doc/7-systems/opl3.md b/papers/doc/7-systems/opl3.md new file mode 100644 index 000000000..79fa9df05 --- /dev/null +++ b/papers/doc/7-systems/opl3.md @@ -0,0 +1,22 @@ +# Yamaha OPL3/YMF262 + +The Yamaha OPL3/YMF262 was an FM sound chip developed by Yamaha (obviously) and released in 1994. + +The OPL3 saw most of its use in PC sound cards (such as the SoundBlaster 16 and the Pro AudioSpectrum). + +Unfortunately, developers who wanted to port their OPL/OPL2 music to the OPL3 were very lazy in doing so, so most of them ended up disreguarding the additions to the OPL3 entirely, and would use entirely the same MIDI driver and patches. + +## Sound capabilities + + - 18 channels 2-op of FM synthesis + - 8 unique waveforms which can be used on the carrier or the modulator + - A "split operators" mode that makes it so that the first and second operators are their own "voices" (each take the base pitch of the note to play and add the frequency multiplier's pitch to it) + - Hard panning (left, center, and right only) + - A rhythm mode where the last 3 voices are replaced with 5 drum channels + - A 4-op mode where 12 FM channels are combined to make 6 4-op FM channels + - A "tremolo" mode where AM (amplitude modulation) is applied, but unfortunately without any strength or speed controls. + - A built in vibrato mode which enables vibrato without a music driver doing it for it + - ADSR support on the carrier and/or modulator + +## Effect commands + - PLEASE DO NOT MERGE UNTIL I HAVE COMMITED THE EFFECT COMMANDS TO THIS PULL REQUEST. From 1a3eed7830e5f32eb534562d3737ac884cefb31c Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 9 Mar 2022 02:30:36 -0500 Subject: [PATCH 174/637] fix MinGW build --- src/gui/pattern.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/pattern.cpp b/src/gui/pattern.cpp index 20f8975f5..8b124bfc0 100644 --- a/src/gui/pattern.cpp +++ b/src/gui/pattern.cpp @@ -817,7 +817,7 @@ void FurnaceGUI::drawPattern() { ImGui::Text("no instruments available"); } for (size_t i=0; isong.ins.size(); i++) { - snprintf(id,4095,"%.2lX: %s",i,e->song.ins[i]->name.c_str()); + snprintf(id,4095,"%.2X: %s",(int)i,e->song.ins[i]->name.c_str()); if (ImGui::Selectable(id)) { // TODO } } From 372f2a20a2d46dfd76c7c1c4b8b44105c35cd760 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 9 Mar 2022 03:40:24 -0500 Subject: [PATCH 175/637] GUI: much better --- src/gui/pattern.cpp | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/src/gui/pattern.cpp b/src/gui/pattern.cpp index 8b124bfc0..9bf6eee59 100644 --- a/src/gui/pattern.cpp +++ b/src/gui/pattern.cpp @@ -332,6 +332,7 @@ void FurnaceGUI::drawPattern() { } if (!patternOpen) return; + bool inhibitMenu=false; float scrollX=0; if (e->isPlaying() && followPattern) cursor.y=oldRow; @@ -411,6 +412,7 @@ void FurnaceGUI::drawPattern() { } if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { fancyPattern=!fancyPattern; + inhibitMenu=true; e->enableCommandStream(fancyPattern); e->getCommandStream(cmdStream); cmdStream.clear(); @@ -481,6 +483,7 @@ void FurnaceGUI::drawPattern() { if (muted) ImGui::PopStyleColor(); ImGui::PopStyleColor(3); if (settings.soloAction!=2) if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { + inhibitMenu=true; e->toggleSolo(i); } if (extraChannelButtons==2) { @@ -807,23 +810,26 @@ void FurnaceGUI::drawPattern() { ImGui::PopFont(); } ImGui::PopStyleVar(); - if (patternOpen) if (ImGui::BeginPopupContextItem("furnace quit unexpectedly.")) { - char id[4096]; - ImGui::Selectable("cut"); - ImGui::Selectable("copy"); - ImGui::Selectable("paste"); - if (ImGui::BeginMenu("change instrument...")) { - if (e->song.ins.empty()) { - ImGui::Text("no instruments available"); - } - for (size_t i=0; isong.ins.size(); i++) { - snprintf(id,4095,"%.2X: %s",(int)i,e->song.ins[i]->name.c_str()); - if (ImGui::Selectable(id)) { // TODO + if (patternOpen) { + if (!inhibitMenu && ImGui::IsItemClicked(ImGuiMouseButton_Right)) ImGui::OpenPopup("patternActionMenu"); + if (ImGui::BeginPopup("patternActionMenu",ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings)) { + char id[4096]; + ImGui::Selectable("cut"); + ImGui::Selectable("copy"); + ImGui::Selectable("paste"); + if (ImGui::BeginMenu("change instrument...")) { + if (e->song.ins.empty()) { + ImGui::Text("no instruments available"); } + for (size_t i=0; isong.ins.size(); i++) { + snprintf(id,4095,"%.2X: %s",(int)i,e->song.ins[i]->name.c_str()); + if (ImGui::Selectable(id)) { // TODO + } + } + ImGui::EndMenu(); } - ImGui::EndMenu(); + ImGui::EndPopup(); } - ImGui::EndPopup(); } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_PATTERN; ImGui::End(); From 6bb9843fb96b446f403e75564c85f13e91379f91 Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Wed, 9 Mar 2022 18:06:07 +0700 Subject: [PATCH 176/637] Fix wrong noise sampling operation This really shouldn't have an effect on anything though... --- src/engine/platform/vera.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/vera.cpp b/src/engine/platform/vera.cpp index 49255be80..3376b0a39 100644 --- a/src/engine/platform/vera.cpp +++ b/src/engine/platform/vera.cpp @@ -65,8 +65,8 @@ void DivPlatformVERA::acquire(short* bufL, short* bufR, size_t start, size_t len // TODO this is a currently speculated noise generation // as the hardware and sources for it are not out in the public // and the official emulator just uses rand() - noiseState=(noiseState<<1)|(((noiseState>>1)^(noiseState>>2)^(noiseState>>4)^(noiseState>>15))&1); noiseOut=((noiseOut<<1)|(noiseState&1))&63; + noiseState=(noiseState<<1)|(((noiseState>>1)^(noiseState>>2)^(noiseState>>4)^(noiseState>>15))&1); for (int i=0; i<16; i++) { unsigned freq=regPool[i*4+0] | (regPool[i*4+1] << 8); unsigned old_accum=chan[i].accum; From 2fb6ea021b7cca10018c878be672106ccde72385 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 9 Mar 2022 15:43:30 -0500 Subject: [PATCH 177/637] GUI: this menu is packed with features that DON'T WORK YET --- src/gui/gui.cpp | 5 +++++ src/gui/gui.h | 2 ++ src/gui/pattern.cpp | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 25d662d05..7317385bc 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -6135,6 +6135,11 @@ FurnaceGUI::FurnaceGUI(): bindSetPending(false), nextScroll(-1.0f), nextAddScroll(0.0f), + transposeAmount(0), + randomizeMin(0), + randomizeMax(255), + scaleMin(0.0f), + scaleMax(100.0f), oldOrdersLen(0) { // octave 1 diff --git a/src/gui/gui.h b/src/gui/gui.h index 7d7f6b4a0..e64fe4a04 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -650,6 +650,8 @@ class FurnaceGUI { ImVec2 threeChars, twoChars; SelectionPoint sel1, sel2; int dummyRows, demandX; + int transposeAmount, randomizeMin, randomizeMax; + float scaleMin, scaleMax; int oldOrdersLen; DivOrders oldOrders; diff --git a/src/gui/pattern.cpp b/src/gui/pattern.cpp index 9bf6eee59..2b3f2d6a4 100644 --- a/src/gui/pattern.cpp +++ b/src/gui/pattern.cpp @@ -817,6 +817,24 @@ void FurnaceGUI::drawPattern() { ImGui::Selectable("cut"); ImGui::Selectable("copy"); ImGui::Selectable("paste"); + if (ImGui::BeginMenu("paste special...")) { + ImGui::Selectable("paste mix"); + ImGui::Selectable("paste mix (background)"); + ImGui::Selectable("paste flood"); + ImGui::Selectable("paste overflow"); + ImGui::EndMenu(); + } + ImGui::Selectable("delete"); + ImGui::Separator(); + + ImGui::InputInt("##TransposeAmount",&transposeAmount,1,1); + ImGui::SameLine(); + ImGui::Button("Transpose"); + + ImGui::Separator(); + ImGui::Selectable("interpolate"); + ImGui::Selectable("fade in"); + ImGui::Selectable("fade out"); if (ImGui::BeginMenu("change instrument...")) { if (e->song.ins.empty()) { ImGui::Text("no instruments available"); @@ -828,6 +846,26 @@ void FurnaceGUI::drawPattern() { } ImGui::EndMenu(); } + if (ImGui::BeginMenu("scale...")) { + ImGui::InputFloat("Bottom",&scaleMin,1,1); + ImGui::InputFloat("Top",&scaleMax,1,1); + ImGui::Button("Scale"); + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("randomize...")) { + ImGui::InputInt("Minimum",&randomizeMin,1,1); + ImGui::InputInt("Maximum",&randomizeMax,1,1); + ImGui::Button("Randomize"); + ImGui::EndMenu(); + } + ImGui::Selectable("invert values"); + + ImGui::SameLine(); + + ImGui::Selectable("flip selection"); + ImGui::Selectable("collapse"); + ImGui::Selectable("expand"); + ImGui::EndPopup(); } } From b80b33ac8e46a4b345f047e262ff9a8fd85ff574 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 9 Mar 2022 16:42:15 -0500 Subject: [PATCH 178/637] GUI: demand scroll X in more situations --- src/gui/gui.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 7317385bc..a2cd182ce 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2479,6 +2479,7 @@ void FurnaceGUI::moveCursorPrevChannel(bool overflow) { selStart=cursor; selEnd=cursor; + demandScrollX=true; } void FurnaceGUI::moveCursorNextChannel(bool overflow) { @@ -2501,6 +2502,7 @@ void FurnaceGUI::moveCursorNextChannel(bool overflow) { selStart=cursor; selEnd=cursor; + demandScrollX=true; } void FurnaceGUI::moveCursorTop(bool select) { @@ -2510,6 +2512,7 @@ void FurnaceGUI::moveCursorTop(bool select) { DETERMINE_FIRST; cursor.xCoarse=firstChannel; cursor.xFine=0; + demandScrollX=true; } else { cursor.y=0; } @@ -2527,6 +2530,7 @@ void FurnaceGUI::moveCursorBottom(bool select) { DETERMINE_LAST; cursor.xCoarse=lastChannel-1; cursor.xFine=2+e->song.pat[cursor.xCoarse].effectRows*2; + demandScrollX=true; } else { cursor.y=e->song.patLen-1; } From 93b3e1621396c4238c09d72cdaaf70fcb0ff5c2d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 9 Mar 2022 18:03:15 -0500 Subject: [PATCH 179/637] i need to go TODO: - EDIT LATCH - EDIT MASK --- src/gui/gui.cpp | 106 +++++++++++++++++++++++++++++++++++++++----- src/gui/gui.h | 27 ++++++++++- src/gui/pattern.cpp | 54 +--------------------- 3 files changed, 122 insertions(+), 65 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index a2cd182ce..a1fad1539 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2861,7 +2861,7 @@ void FurnaceGUI::doCopy(bool cut) { } } -void FurnaceGUI::doPaste() { +void FurnaceGUI::doPaste(PasteMode mode) { finishSelection(); prepareUndo(GUI_UNDO_PATTERN_PASTE); char* clipText=SDL_GetClipboardText(); @@ -4394,6 +4394,99 @@ void FurnaceGUI::processDrags(int dragX, int dragY) { #define BIND_FOR(x) getKeyName(actionKeys[x],true).c_str() +void FurnaceGUI::editOptions(bool topMenu) { + char id[4096]; + if (ImGui::MenuItem("cut",BIND_FOR(GUI_ACTION_PAT_CUT))) doCopy(true); + if (ImGui::MenuItem("copy",BIND_FOR(GUI_ACTION_PAT_COPY))) doCopy(false); + if (ImGui::MenuItem("paste",BIND_FOR(GUI_ACTION_PAT_PASTE))) doPaste(); + if (ImGui::BeginMenu("paste special...")) { + ImGui::MenuItem("paste mix",BIND_FOR(GUI_ACTION_PAT_PASTE_MIX)); + ImGui::MenuItem("paste mix (background)",BIND_FOR(GUI_ACTION_PAT_PASTE_MIX_BG)); + ImGui::MenuItem("paste flood",BIND_FOR(GUI_ACTION_PAT_PASTE_FLOOD)); + ImGui::MenuItem("paste overflow",BIND_FOR(GUI_ACTION_PAT_PASTE_OVERFLOW)); + ImGui::EndMenu(); + } + if (ImGui::MenuItem("delete",BIND_FOR(GUI_ACTION_PAT_DELETE))) doDelete(); + if (topMenu) { + if (ImGui::MenuItem("select all",BIND_FOR(GUI_ACTION_PAT_SELECT_ALL))) doSelectAll(); + } + ImGui::Separator(); + + if (ImGui::MenuItem("set latch",BIND_FOR(GUI_ACTION_PAT_LATCH))) { + // TODO + } + ImGui::Separator(); + + if (ImGui::MenuItem("note up",BIND_FOR(GUI_ACTION_PAT_NOTE_UP))) doTranspose(1); + if (ImGui::MenuItem("note down",BIND_FOR(GUI_ACTION_PAT_NOTE_DOWN))) doTranspose(-1); + if (ImGui::MenuItem("octave up",BIND_FOR(GUI_ACTION_PAT_OCTAVE_UP))) doTranspose(12); + if (ImGui::MenuItem("octave down",BIND_FOR(GUI_ACTION_PAT_OCTAVE_DOWN))) doTranspose(-12); + if (ImGui::InputInt("##TransposeAmount",&transposeAmount,1,1)) { + if (transposeAmount<-96) transposeAmount=-96; + if (transposeAmount>96) transposeAmount=96; + } + ImGui::SameLine(); + if (ImGui::Button("Transpose")) { + doTranspose(transposeAmount); + ImGui::CloseCurrentPopup(); + } + + ImGui::Separator(); + ImGui::MenuItem("interpolate",BIND_FOR(GUI_ACTION_PAT_INTERPOLATE)); + ImGui::MenuItem("fade in",BIND_FOR(GUI_ACTION_PAT_FADE_IN)); + ImGui::MenuItem("fade out",BIND_FOR(GUI_ACTION_PAT_FADE_OUT)); + if (ImGui::BeginMenu("change instrument...")) { + if (e->song.ins.empty()) { + ImGui::Text("no instruments available"); + } + for (size_t i=0; isong.ins.size(); i++) { + snprintf(id,4095,"%.2X: %s",(int)i,e->song.ins[i]->name.c_str()); + if (ImGui::MenuItem(id)) { // TODO + } + } + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("scale...")) { + if (ImGui::InputFloat("Bottom",&scaleMin,1,1,"%.1f%%")) { + if (scaleMin<0.0f) scaleMin=0.0f; + if (scaleMin>100.0f) scaleMin=100.0f; + } + if (ImGui::InputFloat("Top",&scaleMax,1,1,"%.1f%%")) { + if (scaleMax<0.0f) scaleMax=0.0f; + if (scaleMax>100.0f) scaleMax=100.0f; + } + if (ImGui::Button("Scale")) { + ImGui::CloseCurrentPopup(); + } + ImGui::EndMenu(); + } + if (ImGui::BeginMenu("randomize...")) { + ImGui::InputInt("Minimum",&randomizeMin,1,1); + ImGui::InputInt("Maximum",&randomizeMax,1,1); + if (ImGui::Button("Randomize")) { + ImGui::CloseCurrentPopup(); + } + ImGui::EndMenu(); + } + ImGui::MenuItem("invert values",BIND_FOR(GUI_ACTION_PAT_INVERT_VALUES)); + + ImGui::Separator(); + + ImGui::MenuItem("flip selection",BIND_FOR(GUI_ACTION_PAT_FLIP_SELECTION)); + ImGui::MenuItem("collapse",BIND_FOR(GUI_ACTION_PAT_COLLAPSE_ROWS)); + ImGui::MenuItem("expand",BIND_FOR(GUI_ACTION_PAT_EXPAND_ROWS)); + + if (topMenu) { + ImGui::Separator(); + ImGui::MenuItem("collapse pattern",BIND_FOR(GUI_ACTION_PAT_COLLAPSE_PAT)); + ImGui::MenuItem("expand pattern",BIND_FOR(GUI_ACTION_PAT_EXPAND_PAT)); + + ImGui::Separator(); + ImGui::MenuItem("collapse song",BIND_FOR(GUI_ACTION_PAT_COLLAPSE_SONG)); + ImGui::MenuItem("expand song",BIND_FOR(GUI_ACTION_PAT_EXPAND_SONG)); + } +} + bool FurnaceGUI::loop() { while (!quit) { SDL_Event ev; @@ -5028,16 +5121,7 @@ bool FurnaceGUI::loop() { if (ImGui::MenuItem("undo",BIND_FOR(GUI_ACTION_UNDO))) doUndo(); if (ImGui::MenuItem("redo",BIND_FOR(GUI_ACTION_REDO))) doRedo(); ImGui::Separator(); - if (ImGui::MenuItem("cut",BIND_FOR(GUI_ACTION_PAT_CUT))) doCopy(true); - if (ImGui::MenuItem("copy",BIND_FOR(GUI_ACTION_PAT_COPY))) doCopy(false); - if (ImGui::MenuItem("paste",BIND_FOR(GUI_ACTION_PAT_PASTE))) doPaste(); - if (ImGui::MenuItem("delete",BIND_FOR(GUI_ACTION_PAT_DELETE))) doDelete(); - if (ImGui::MenuItem("select all",BIND_FOR(GUI_ACTION_PAT_SELECT_ALL))) doSelectAll(); - ImGui::Separator(); - if (ImGui::MenuItem("note up",BIND_FOR(GUI_ACTION_PAT_NOTE_UP))) doTranspose(1); - if (ImGui::MenuItem("note down",BIND_FOR(GUI_ACTION_PAT_NOTE_DOWN))) doTranspose(-1); - if (ImGui::MenuItem("octave up",BIND_FOR(GUI_ACTION_PAT_OCTAVE_UP))) doTranspose(12); - if (ImGui::MenuItem("octave down",BIND_FOR(GUI_ACTION_PAT_OCTAVE_DOWN))) doTranspose(-12); + editOptions(true); /*ImGui::Separator(); ImGui::MenuItem("clear...");*/ ImGui::EndMenu(); diff --git a/src/gui/gui.h b/src/gui/gui.h index e64fe4a04..1ce74f3ea 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -233,6 +233,10 @@ enum FurnaceGUIActions { GUI_ACTION_PAT_CUT, GUI_ACTION_PAT_COPY, GUI_ACTION_PAT_PASTE, + GUI_ACTION_PAT_PASTE_MIX, + GUI_ACTION_PAT_PASTE_MIX_BG, + GUI_ACTION_PAT_PASTE_FLOOD, + GUI_ACTION_PAT_PASTE_OVERFLOW, GUI_ACTION_PAT_CURSOR_UP, GUI_ACTION_PAT_CURSOR_DOWN, GUI_ACTION_PAT_CURSOR_LEFT, @@ -268,6 +272,18 @@ enum FurnaceGUIActions { GUI_ACTION_PAT_COLLAPSE, GUI_ACTION_PAT_INCREASE_COLUMNS, GUI_ACTION_PAT_DECREASE_COLUMNS, + GUI_ACTION_PAT_INTERPOLATE, + GUI_ACTION_PAT_FADE_IN, + GUI_ACTION_PAT_FADE_OUT, + GUI_ACTION_PAT_INVERT_VALUES, + GUI_ACTION_PAT_FLIP_SELECTION, + GUI_ACTION_PAT_COLLAPSE_ROWS, + GUI_ACTION_PAT_EXPAND_ROWS, + GUI_ACTION_PAT_COLLAPSE_PAT, + GUI_ACTION_PAT_EXPAND_PAT, + GUI_ACTION_PAT_COLLAPSE_SONG, + GUI_ACTION_PAT_EXPAND_SONG, + GUI_ACTION_PAT_LATCH, GUI_ACTION_PAT_MAX, GUI_ACTION_INS_LIST_MIN, @@ -334,6 +350,14 @@ enum FurnaceGUIActions { GUI_ACTION_MAX }; +enum PasteMode { + GUI_PASTE_MODE_NORMAL=0, + GUI_PASTE_MODE_MIX_FG, + GUI_PASTE_MODE_MIX_BG, + GUI_PASTE_MODE_FLOOD, + GUI_PASTE_MODE_OVERFLOW +}; + #define FURKMOD_CTRL (1<<31) #define FURKMOD_SHIFT (1<<29) #define FURKMOD_META (1<<28) @@ -720,9 +744,10 @@ class FurnaceGUI { void doInsert(); void doTranspose(int amount); void doCopy(bool cut); - void doPaste(); + void doPaste(PasteMode mode=GUI_PASTE_MODE_NORMAL); void doUndo(); void doRedo(); + void editOptions(bool topMenu); void play(int row=0); void stop(); diff --git a/src/gui/pattern.cpp b/src/gui/pattern.cpp index 2b3f2d6a4..b2ff56cec 100644 --- a/src/gui/pattern.cpp +++ b/src/gui/pattern.cpp @@ -813,59 +813,7 @@ void FurnaceGUI::drawPattern() { if (patternOpen) { if (!inhibitMenu && ImGui::IsItemClicked(ImGuiMouseButton_Right)) ImGui::OpenPopup("patternActionMenu"); if (ImGui::BeginPopup("patternActionMenu",ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings)) { - char id[4096]; - ImGui::Selectable("cut"); - ImGui::Selectable("copy"); - ImGui::Selectable("paste"); - if (ImGui::BeginMenu("paste special...")) { - ImGui::Selectable("paste mix"); - ImGui::Selectable("paste mix (background)"); - ImGui::Selectable("paste flood"); - ImGui::Selectable("paste overflow"); - ImGui::EndMenu(); - } - ImGui::Selectable("delete"); - ImGui::Separator(); - - ImGui::InputInt("##TransposeAmount",&transposeAmount,1,1); - ImGui::SameLine(); - ImGui::Button("Transpose"); - - ImGui::Separator(); - ImGui::Selectable("interpolate"); - ImGui::Selectable("fade in"); - ImGui::Selectable("fade out"); - if (ImGui::BeginMenu("change instrument...")) { - if (e->song.ins.empty()) { - ImGui::Text("no instruments available"); - } - for (size_t i=0; isong.ins.size(); i++) { - snprintf(id,4095,"%.2X: %s",(int)i,e->song.ins[i]->name.c_str()); - if (ImGui::Selectable(id)) { // TODO - } - } - ImGui::EndMenu(); - } - if (ImGui::BeginMenu("scale...")) { - ImGui::InputFloat("Bottom",&scaleMin,1,1); - ImGui::InputFloat("Top",&scaleMax,1,1); - ImGui::Button("Scale"); - ImGui::EndMenu(); - } - if (ImGui::BeginMenu("randomize...")) { - ImGui::InputInt("Minimum",&randomizeMin,1,1); - ImGui::InputInt("Maximum",&randomizeMax,1,1); - ImGui::Button("Randomize"); - ImGui::EndMenu(); - } - ImGui::Selectable("invert values"); - - ImGui::SameLine(); - - ImGui::Selectable("flip selection"); - ImGui::Selectable("collapse"); - ImGui::Selectable("expand"); - + editOptions(false); ImGui::EndPopup(); } } From d0b76ed5a6bd6bfc319bb512a347bbd07ebcfc13 Mon Sep 17 00:00:00 2001 From: Waldemar Pawlaszek Date: Thu, 10 Mar 2022 17:36:27 +0100 Subject: [PATCH 180/637] Updated Lynx register sheet --- src/engine/platform/lynx.cpp | 39 +++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/src/engine/platform/lynx.cpp b/src/engine/platform/lynx.cpp index 2783ba5d5..872cd99c5 100644 --- a/src/engine/platform/lynx.cpp +++ b/src/engine/platform/lynx.cpp @@ -82,7 +82,44 @@ static int32_t clamp(int32_t v, int32_t lo, int32_t hi) } const char* regCheatSheetLynx[]={ - "DATA", "0", + "AUDIO0_VOLCNTRL", "20", + "AUDIO0_FEEDBACK", "21", + "AUDIO0_OUTPUT", "22", + "AUDIO0_SHIFT", "23", + "AUDIO0_BACKUP", "24", + "AUDIO0_CONTROL", "25", + "AUDIO0_COUNTER", "26", + "AUDIO0_OTHER", "27", + "AUDIO1_VOLCNTRL", "28", + "AUDIO1_FEEDBACK", "29", + "AUDIO1_OUTPUT", "2a", + "AUDIO1_SHIFT", "2b", + "AUDIO1_BACKUP", "2c", + "AUDIO1_CONTROL", "2d", + "AUDIO1_COUNTER", "2e", + "AUDIO1_OTHER", "2f", + "AUDIO2_VOLCNTRL", "30", + "AUDIO2_FEEDBACK", "31", + "AUDIO2_OUTPUT", "32", + "AUDIO2_SHIFT", "33", + "AUDIO2_BACKUP", "34", + "AUDIO2_CONTROL", "35", + "AUDIO2_COUNTER", "36", + "AUDIO2_OTHER", "37", + "AUDIO3_VOLCNTRL", "38", + "AUDIO3_FEEDBACK", "39", + "AUDIO3_OUTPUT", "3a", + "AUDIO3_SHIFT", "3b", + "AUDIO3_BACKUP", "3c", + "AUDIO3_CONTROL", "3d", + "AUDIO3_COUNTER", "3e", + "AUDIO3_OTHER", "3f", + "ATTENREG0", "40", + "ATTENREG1", "41", + "ATTENREG2", "42", + "ATTENREG3", "43", + "MPAN", "44", + "MSTEREO", "50", NULL }; From 8d447542e108feebe52af00a715020c9ff77dd52 Mon Sep 17 00:00:00 2001 From: cam900 Date: Fri, 11 Mar 2022 02:42:27 +0900 Subject: [PATCH 181/637] Use lamda --- src/engine/platform/sound/x1_010/x1_010.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine/platform/sound/x1_010/x1_010.cpp b/src/engine/platform/sound/x1_010/x1_010.cpp index ea1e52ace..150928e66 100644 --- a/src/engine/platform/sound/x1_010/x1_010.cpp +++ b/src/engine/platform/sound/x1_010/x1_010.cpp @@ -215,8 +215,8 @@ void x1_010_core::voice_t::reset() void x1_010_core::reset() { - for (int i = 0; i < 16; i++) - m_voice[i].reset(); + for (auto & elem : m_voice) + elem.reset(); std::fill_n(&m_envelope[0], 0x1000, 0); std::fill_n(&m_wave[0], 0x1000, 0); From f3e4810dda918f0d0cb265fc3d8a0271596d69ba Mon Sep 17 00:00:00 2001 From: cam900 Date: Fri, 11 Mar 2022 03:47:36 +0900 Subject: [PATCH 182/637] Make some pitch command work in ADPCM-B, still partially and 01xx command is broken Fix sample check routine Remove dac* variables, No way to enable DAC mode in YM2610* --- src/engine/platform/ym2610.cpp | 73 +++++++++++++++++++------------- src/engine/platform/ym2610.h | 8 ++-- src/engine/platform/ym2610b.cpp | 75 +++++++++++++++++++-------------- src/engine/platform/ym2610b.h | 8 ++-- 4 files changed, 93 insertions(+), 71 deletions(-) diff --git a/src/engine/platform/ym2610.cpp b/src/engine/platform/ym2610.cpp index 1bd460fa1..5b2b19cc8 100644 --- a/src/engine/platform/ym2610.cpp +++ b/src/engine/platform/ym2610.cpp @@ -313,11 +313,22 @@ const char* DivPlatformYM2610::getEffectName(unsigned char effect) { return NULL; } +double DivPlatformYM2610::NOTE_OPNB(int ch, int note) { + if (ch>6) { // ADPCM + return NOTE_ADPCMB(note); + } else if (ch>3) { // PSG + return NOTE_PERIODIC(note); + } + // FM + return NOTE_FREQUENCY(note); +} + double DivPlatformYM2610::NOTE_ADPCMB(int note) { - DivInstrument* ins=parent->getIns(chan[13].ins); - if (ins->type!=DIV_INS_AMIGA) return 0; - double off=(double)(parent->getSample(ins->amiga.initSample)->centerRate)/8363.0; - return off*parent->calcBaseFreq((double)chipClock/144,65535,note,false); + if (chan[13].sample>=0&&chan[13].samplesong.sampleLen) { + double off=(double)(parent->getSample(chan[13].sample)->centerRate)/8363.0; + return off*parent->calcBaseFreq((double)chipClock/144,65535,note,false); + } + return 0; } void DivPlatformYM2610::acquire(short* bufL, short* bufR, size_t start, size_t len) { @@ -691,22 +702,33 @@ int DivPlatformYM2610::dispatch(DivCommand c) { chan[c.chan].outVol=chan[c.chan].vol; immWrite(0x1b,chan[c.chan].outVol); } - DivSample* s=parent->getSample(ins->amiga.initSample); - immWrite(0x12,(s->offB>>8)&0xff); - immWrite(0x13,s->offB>>16); - int end=s->offB+s->lengthB-1; - immWrite(0x14,(end>>8)&0xff); - immWrite(0x15,end>>16); - immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); - immWrite(0x10,(s->loopStart>=0)?0x90:0x80); // start/repeat - if (c.value!=DIV_NOTE_NULL) { - chan[c.chan].note=c.value; - chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note); - chan[c.chan].freqChanged=true; + chan[c.chan].sample=ins->amiga.initSample; + if (chan[c.chan].sample>=0&&chan[c.chan].samplesong.sampleLen) { + DivSample* s=parent->getSample(chan[c.chan].sample); + immWrite(0x12,(s->offB>>8)&0xff); + immWrite(0x13,s->offB>>16); + int end=s->offB+s->lengthB-1; + immWrite(0x14,(end>>8)&0xff); + immWrite(0x15,end>>16); + immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); + immWrite(0x10,(s->loopStart>=0)?0x90:0x80); // start/repeat + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].note=c.value; + chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note); + chan[c.chan].freqChanged=true; + } + chan[c.chan].active=true; + chan[c.chan].keyOn=true; + } else { + immWrite(0x10,0x01); // reset + immWrite(0x12,0); + immWrite(0x13,0); + immWrite(0x14,0); + immWrite(0x15,0); + break; } - chan[c.chan].active=true; - chan[c.chan].keyOn=true; } else { + chan[c.chan].sample=-1; chan[c.chan].std.init(NULL); chan[c.chan].outVol=chan[c.chan].vol; if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) { @@ -915,8 +937,8 @@ int DivPlatformYM2610::dispatch(DivCommand c) { break; } case DIV_CMD_NOTE_PORTA: { - if (c.chan>3) { // PSG - int destFreq=NOTE_PERIODIC(c.value2); + if (c.chan>3) { // PSG, ADPCM-B + int destFreq=NOTE_OPNB(c.chan,c.value2); bool return2=false; if (destFreq>chan[c.chan].baseFreq) { chan[c.chan].baseFreq+=c.value; @@ -975,11 +997,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) { iface.sampleBank=sampleBank; break; case DIV_CMD_LEGATO: { - if (c.chan>3) { // PSG - chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); - } else { - chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); - } + chan[c.chan].baseFreq=NOTE_OPNB(c.chan,c.value); chan[c.chan].freqChanged=true; break; } @@ -1210,11 +1228,6 @@ void DivPlatformYM2610::reset() { } lastBusy=60; - dacMode=0; - dacPeriod=0; - dacPos=0; - dacRate=0; - dacSample=-1; sampleBank=0; ayEnvPeriod=0; ayEnvMode=0; diff --git a/src/engine/platform/ym2610.h b/src/engine/platform/ym2610.h index a0ee8b8d4..4e9b81f98 100644 --- a/src/engine/platform/ym2610.h +++ b/src/engine/platform/ym2610.h @@ -47,6 +47,7 @@ class DivPlatformYM2610: public DivDispatch { signed char konCycles; bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM; int vol, outVol; + int sample; unsigned char pan; DivMacroInt std; Channel(): @@ -70,6 +71,7 @@ class DivPlatformYM2610: public DivDispatch { furnacePCM(false), vol(0), outVol(15), + sample(-1), pan(3) {} }; Channel chan[14]; @@ -87,11 +89,6 @@ class DivPlatformYM2610: public DivDispatch { unsigned char regPool[512]; unsigned char lastBusy; - bool dacMode; - int dacPeriod; - int dacRate; - int dacPos; - int dacSample; int ayNoiseFreq; unsigned char sampleBank; @@ -108,6 +105,7 @@ class DivPlatformYM2610: public DivDispatch { int octave(int freq); int toFreq(int freq); + double NOTE_OPNB(int ch, int note); double NOTE_ADPCMB(int note); friend void putDispatchChan(void*,int,int); diff --git a/src/engine/platform/ym2610b.cpp b/src/engine/platform/ym2610b.cpp index a6bcf9ac7..02d120331 100644 --- a/src/engine/platform/ym2610b.cpp +++ b/src/engine/platform/ym2610b.cpp @@ -377,11 +377,22 @@ const char* DivPlatformYM2610B::getEffectName(unsigned char effect) { return NULL; } +double DivPlatformYM2610B::NOTE_OPNB(int ch, int note) { + if (ch>8) { // ADPCM-B + return NOTE_ADPCMB(note); + } else if (ch>5) { // PSG + return NOTE_PERIODIC(note); + } + // FM + return NOTE_FREQUENCY(note); +} + double DivPlatformYM2610B::NOTE_ADPCMB(int note) { - DivInstrument* ins=parent->getIns(chan[15].ins); - if (ins->type!=DIV_INS_AMIGA) return 0; - double off=(double)(parent->getSample(ins->amiga.initSample)->centerRate)/8363.0; - return off*parent->calcBaseFreq((double)chipClock/144,65535,note,false); + if (chan[15].sample>=0&&chan[15].samplesong.sampleLen) { + double off=(double)(parent->getSample(chan[15].sample)->centerRate)/8363.0; + return off*parent->calcBaseFreq((double)chipClock/144,65535,note,false); + } + return 0; } void DivPlatformYM2610B::acquire(short* bufL, short* bufR, size_t start, size_t len) { @@ -752,24 +763,35 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { chan[c.chan].std.init(ins); if (!chan[c.chan].std.willVol) { chan[c.chan].outVol=chan[c.chan].vol; - immWrite(0x1b,chan[c.chan].outVol); + immWrite(0x1b,chan[c.chan].outVol); } - DivSample* s=parent->getSample(ins->amiga.initSample); - immWrite(0x12,(s->offB>>8)&0xff); - immWrite(0x13,s->offB>>16); - int end=s->offB+s->lengthB-1; - immWrite(0x14,(end>>8)&0xff); - immWrite(0x15,end>>16); - immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); - immWrite(0x10,(s->loopStart>=0)?0x90:0x80); // start/repeat - if (c.value!=DIV_NOTE_NULL) { - chan[c.chan].note=c.value; - chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note); - chan[c.chan].freqChanged=true; + chan[c.chan].sample=ins->amiga.initSample; + if (chan[c.chan].sample>=0&&chan[c.chan].samplesong.sampleLen) { + DivSample* s=parent->getSample(chan[c.chan].sample); + immWrite(0x12,(s->offB>>8)&0xff); + immWrite(0x13,s->offB>>16); + int end=s->offB+s->lengthB-1; + immWrite(0x14,(end>>8)&0xff); + immWrite(0x15,end>>16); + immWrite(0x11,isMuted[c.chan]?0:(chan[c.chan].pan<<6)); + immWrite(0x10,(s->loopStart>=0)?0x90:0x80); // start/repeat + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].note=c.value; + chan[c.chan].baseFreq=NOTE_ADPCMB(chan[c.chan].note); + chan[c.chan].freqChanged=true; + } + chan[c.chan].active=true; + chan[c.chan].keyOn=true; + } else { + immWrite(0x10,0x01); // reset + immWrite(0x12,0); + immWrite(0x13,0); + immWrite(0x14,0); + immWrite(0x15,0); + break; } - chan[c.chan].active=true; - chan[c.chan].keyOn=true; } else { + chan[c.chan].sample=-1; chan[c.chan].std.init(NULL); chan[c.chan].outVol=chan[c.chan].vol; if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) { @@ -978,8 +1000,8 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { break; } case DIV_CMD_NOTE_PORTA: { - if (c.chan>5) { // PSG - int destFreq=NOTE_PERIODIC(c.value2); + if (c.chan>5) { // PSG, ADPCM-B + int destFreq=NOTE_OPNB(c.chan,c.value2); bool return2=false; if (destFreq>chan[c.chan].baseFreq) { chan[c.chan].baseFreq+=c.value; @@ -1038,11 +1060,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { iface.sampleBank=sampleBank; break; case DIV_CMD_LEGATO: { - if (c.chan>5) { // PSG - chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); - } else { - chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); - } + chan[c.chan].baseFreq=NOTE_OPNB(c.chan,c.value); chan[c.chan].freqChanged=true; break; } @@ -1273,11 +1291,6 @@ void DivPlatformYM2610B::reset() { } lastBusy=60; - dacMode=0; - dacPeriod=0; - dacPos=0; - dacRate=0; - dacSample=-1; sampleBank=0; ayEnvPeriod=0; ayEnvMode=0; diff --git a/src/engine/platform/ym2610b.h b/src/engine/platform/ym2610b.h index ca43e36b4..99c641a6e 100644 --- a/src/engine/platform/ym2610b.h +++ b/src/engine/platform/ym2610b.h @@ -40,6 +40,7 @@ class DivPlatformYM2610B: public DivDispatch { signed char konCycles; bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM; int vol, outVol; + int sample; unsigned char pan; DivMacroInt std; Channel(): @@ -63,6 +64,7 @@ class DivPlatformYM2610B: public DivDispatch { furnacePCM(false), vol(0), outVol(15), + sample(-1), pan(3) {} }; Channel chan[16]; @@ -80,11 +82,6 @@ class DivPlatformYM2610B: public DivDispatch { unsigned char regPool[512]; unsigned char lastBusy; - bool dacMode; - int dacPeriod; - int dacRate; - int dacPos; - int dacSample; int ayNoiseFreq; unsigned char sampleBank; @@ -101,6 +98,7 @@ class DivPlatformYM2610B: public DivDispatch { int octave(int freq); int toFreq(int freq); + double NOTE_OPNB(int ch, int note); double NOTE_ADPCMB(int note); friend void putDispatchChan(void*,int,int); From 28192b77bd3ee851ee6dcf7a7cdca9d9c06254f5 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 10 Mar 2022 13:52:04 -0500 Subject: [PATCH 183/637] fix big endian functions --- src/engine/safeReader.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/engine/safeReader.cpp b/src/engine/safeReader.cpp index df3936655..2a0d7c3ba 100644 --- a/src/engine/safeReader.cpp +++ b/src/engine/safeReader.cpp @@ -92,9 +92,9 @@ short SafeReader::readS() { short SafeReader::readS_BE() { if (curSeek+1>len) throw EndOfFileException(this,len); - short ret=*(short*)(&buf[curSeek]); + unsigned short ret=*(unsigned short*)(&buf[curSeek]); curSeek+=2; - return (ret>>8)|((ret&0xff)<<8); + return (short)((ret>>8)|((ret&0xff)<<8)); } int SafeReader::readI() { @@ -112,9 +112,9 @@ int SafeReader::readI() { int SafeReader::readI_BE() { if (curSeek+4>len) throw EndOfFileException(this,len); - int ret=*(int*)(&buf[curSeek]); + unsigned int ret=*(unsigned int*)(&buf[curSeek]); curSeek+=4; - return (ret>>24)|((ret&0xff0000)>>8)|((ret&0xff00)<<8)|((ret&0xff)<<24); + return (int)((ret>>24)|((ret&0xff0000)>>8)|((ret&0xff00)<<8)|((ret&0xff)<<24)); } int64_t SafeReader::readL() { From 406faaeeea8d3a12ec4c9d4d78284ac20d34a340 Mon Sep 17 00:00:00 2001 From: cam900 Date: Fri, 11 Mar 2022 04:07:11 +0900 Subject: [PATCH 184/637] Gamate Handheld game console by Taiwanese Bit Corporation that supports stereo headphone, with had a AY-3-8910 based sound. --- src/gui/gui.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index b3e21250a..b6a93a40f 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -6532,6 +6532,12 @@ FurnaceGUI::FurnaceGUI(): 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "Gamate", { + DIV_SYSTEM_AY8910, 64, 0, 73, + 0 + } + )); sysCategories.push_back(cat); cat=FurnaceGUISysCategory("Computers"); From 36b336c7f49a257403a750ae1ecb625aa2884fde Mon Sep 17 00:00:00 2001 From: cam900 Date: Fri, 11 Mar 2022 04:11:23 +0900 Subject: [PATCH 185/637] A && B --- src/engine/platform/ym2610.cpp | 4 ++-- src/engine/platform/ym2610b.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/engine/platform/ym2610.cpp b/src/engine/platform/ym2610.cpp index 5b2b19cc8..8d7626632 100644 --- a/src/engine/platform/ym2610.cpp +++ b/src/engine/platform/ym2610.cpp @@ -324,7 +324,7 @@ double DivPlatformYM2610::NOTE_OPNB(int ch, int note) { } double DivPlatformYM2610::NOTE_ADPCMB(int note) { - if (chan[13].sample>=0&&chan[13].samplesong.sampleLen) { + if (chan[13].sample>=0 && chan[13].samplesong.sampleLen) { double off=(double)(parent->getSample(chan[13].sample)->centerRate)/8363.0; return off*parent->calcBaseFreq((double)chipClock/144,65535,note,false); } @@ -703,7 +703,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) { immWrite(0x1b,chan[c.chan].outVol); } chan[c.chan].sample=ins->amiga.initSample; - if (chan[c.chan].sample>=0&&chan[c.chan].samplesong.sampleLen) { + if (chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { DivSample* s=parent->getSample(chan[c.chan].sample); immWrite(0x12,(s->offB>>8)&0xff); immWrite(0x13,s->offB>>16); diff --git a/src/engine/platform/ym2610b.cpp b/src/engine/platform/ym2610b.cpp index 02d120331..1d954ebe6 100644 --- a/src/engine/platform/ym2610b.cpp +++ b/src/engine/platform/ym2610b.cpp @@ -388,7 +388,7 @@ double DivPlatformYM2610B::NOTE_OPNB(int ch, int note) { } double DivPlatformYM2610B::NOTE_ADPCMB(int note) { - if (chan[15].sample>=0&&chan[15].samplesong.sampleLen) { + if (chan[15].sample>=0 && chan[15].samplesong.sampleLen) { double off=(double)(parent->getSample(chan[15].sample)->centerRate)/8363.0; return off*parent->calcBaseFreq((double)chipClock/144,65535,note,false); } @@ -766,7 +766,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { immWrite(0x1b,chan[c.chan].outVol); } chan[c.chan].sample=ins->amiga.initSample; - if (chan[c.chan].sample>=0&&chan[c.chan].samplesong.sampleLen) { + if (chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { DivSample* s=parent->getSample(chan[c.chan].sample); immWrite(0x12,(s->offB>>8)&0xff); immWrite(0x13,s->offB>>16); From b42ceae1cbaec8fd70bf9a3300eefd516bf80c3f Mon Sep 17 00:00:00 2001 From: cam900 Date: Fri, 11 Mar 2022 04:15:04 +0900 Subject: [PATCH 186/637] Code style --- src/engine/platform/x1_010.cpp | 56 +++++++++++++++++----------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/src/engine/platform/x1_010.cpp b/src/engine/platform/x1_010.cpp index e6b07d01b..42853855a 100644 --- a/src/engine/platform/x1_010.cpp +++ b/src/engine/platform/x1_010.cpp @@ -30,7 +30,7 @@ #define envFill(c,a) rWrite(0x800|(c<<7)|(a&0x7f),(chan[c].lvol<<4)|chan[c].rvol) #define envWrite(c,a,l,r) rWrite(0x800|(c<<7)|(a&0x7f),(((chan[c].lvol*(l))/15)<<4)|((chan[c].rvol*(r))/15)) -#define refreshControl(c) chWrite(c,0,chan[c].active?(chan[c].pcm?1:((chan[c].env.flag.envEnable&&chan[c].env.flag.envOneshot)?7:3)):0); +#define refreshControl(c) chWrite(c,0,chan[c].active?(chan[c].pcm?1:((chan[c].env.flag.envEnable && chan[c].env.flag.envOneshot)?7:3)):0); #define CHIP_FREQBASE 4194304 @@ -259,7 +259,7 @@ double DivPlatformX1_010::NoteX1_010(int ch, int note) { if (chan[ch].pcm) { // PCM note double off=1.0; int sample=chan[ch].sample; - if (sample>=0&&samplesong.sampleLen) { + if (sample>=0 && samplesong.sampleLen) { DivSample* s=parent->getSample(sample); if (s->centerRate<1) { off=1.0; @@ -279,7 +279,7 @@ void DivPlatformX1_010::updateWave(int ch) { chan[ch].waveBank ^= 1; } for (int i=0; i<128; i++) { - if (wt->max<1||wt->len<1) { + if (wt->max<1 || wt->len<1) { waveWrite(ch,i,0); } else { waveWrite(ch,i,wt->data[i*wt->len/128]*255/wt->max); @@ -304,9 +304,9 @@ void DivPlatformX1_010::updateEnvelope(int ch) { } else { DivWavetable* wt=parent->getWave(chan[ch].env.shape); for (int i=0; i<128; i++) { - if (wt->max<1||wt->len<1) { + if (wt->max<1 || wt->len<1) { envFill(ch,i); - } else if (chan[ch].env.flag.envSplit||chan[ch].env.flag.envHinvR||chan[ch].env.flag.envVinvR||chan[ch].env.flag.envHinvL||chan[ch].env.flag.envVinvL) { // Stereo config + } else if (chan[ch].env.flag.envSplit || chan[ch].env.flag.envHinvR || chan[ch].env.flag.envVinvR || chan[ch].env.flag.envHinvL || chan[ch].env.flag.envVinvL) { // Stereo config int la=i,ra=i; int lo,ro; if (chan[ch].env.flag.envHinvR) { ra=127-i; } // horizontal invert right envelope @@ -339,12 +339,12 @@ void DivPlatformX1_010::tick() { chan[i].std.next(); if (chan[i].std.hadVol) { signed char macroVol=((chan[i].vol&15)*MIN(chan[i].furnacePCM?64:15,chan[i].std.vol))/(chan[i].furnacePCM?64:15); - if ((!isMuted[i])&&(macroVol!=chan[i].outVol)) { + if ((!isMuted[i]) && (macroVol!=chan[i].outVol)) { chan[i].outVol=macroVol; chan[i].envChanged=true; } } - if ((!chan[i].pcm)||chan[i].furnacePCM) { + if ((!chan[i].pcm) || chan[i].furnacePCM) { if (chan[i].std.hadArp) { if (!chan[i].inPorta) { if (chan[i].std.arpMode) { @@ -355,13 +355,13 @@ void DivPlatformX1_010::tick() { } chan[i].freqChanged=true; } else { - if (chan[i].std.arpMode&&chan[i].std.finishedArp) { + if (chan[i].std.arpMode && chan[i].std.finishedArp) { chan[i].baseFreq=NoteX1_010(i,chan[i].note); chan[i].freqChanged=true; } } } - if (chan[i].std.hadWave&&!chan[i].pcm) { + if (chan[i].std.hadWave && !chan[i].pcm) { if (chan[i].wave!=chan[i].std.wave) { chan[i].wave=chan[i].std.wave; if (!chan[i].pcm) { @@ -391,35 +391,35 @@ void DivPlatformX1_010::tick() { bool nextSplit=(chan[i].std.ex1&4); if (nextSplit!=(chan[i].env.flag.envSplit)) { chan[i].env.flag.envSplit=nextSplit; - if (!isMuted[i]&&!chan[i].pcm) { + if (!isMuted[i] && !chan[i].pcm) { chan[i].envChanged=true; } } bool nextHinvR=(chan[i].std.ex1&8); if (nextHinvR!=(chan[i].env.flag.envHinvR)) { chan[i].env.flag.envHinvR=nextHinvR; - if (!isMuted[i]&&!chan[i].pcm) { + if (!isMuted[i] && !chan[i].pcm) { chan[i].envChanged=true; } } bool nextVinvR=(chan[i].std.ex1&16); if (nextVinvR!=(chan[i].env.flag.envVinvR)) { chan[i].env.flag.envVinvR=nextVinvR; - if (!isMuted[i]&&!chan[i].pcm) { + if (!isMuted[i] && !chan[i].pcm) { chan[i].envChanged=true; } } bool nextHinvL=(chan[i].std.ex1&32); if (nextHinvL!=(chan[i].env.flag.envHinvL)) { chan[i].env.flag.envHinvL=nextHinvL; - if (!isMuted[i]&&!chan[i].pcm) { + if (!isMuted[i] && !chan[i].pcm) { chan[i].envChanged=true; } } bool nextVinvL=(chan[i].std.ex1&64); if (nextVinvL!=(chan[i].env.flag.envVinvL)) { chan[i].env.flag.envVinvL=nextVinvL; - if (!isMuted[i]&&!chan[i].pcm) { + if (!isMuted[i] && !chan[i].pcm) { chan[i].envChanged=true; } } @@ -428,7 +428,7 @@ void DivPlatformX1_010::tick() { if (chan[i].env.shape!=chan[i].std.ex2) { chan[i].env.shape=chan[i].std.ex2; if (!chan[i].pcm) { - if (chan[i].env.flag.envEnable&&(!isMuted[i])) { + if (chan[i].env.flag.envEnable && (!isMuted[i])) { chan[i].envChanged=true; } if (!chan[i].keyOff) chan[i].keyOn=true; @@ -457,7 +457,7 @@ void DivPlatformX1_010::tick() { updateEnvelope(i); chan[i].envChanged=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].pitch,false); if (chan[i].pcm) { if (chan[i].freq<1) chan[i].freq=1; @@ -467,12 +467,12 @@ void DivPlatformX1_010::tick() { if (chan[i].freq>65535) chan[i].freq=65535; chWrite(i,2,chan[i].freq&0xff); chWrite(i,3,(chan[i].freq>>8)&0xff); - if (chan[i].freqChanged&&chan[i].autoEnvNum>0&&chan[i].autoEnvDen>0) { + if (chan[i].freqChanged && chan[i].autoEnvNum>0 && chan[i].autoEnvDen>0) { chan[i].env.period=(chan[i].freq*chan[i].autoEnvDen/chan[i].autoEnvNum)>>12; chWrite(i,4,chan[i].env.period); } } - if (chan[i].keyOn||chan[i].keyOff||(chRead(i,0)&1)) { + if (chan[i].keyOn || chan[i].keyOff || (chRead(i,0)&1)) { refreshControl(i); } if (chan[i].keyOn) chan[i].keyOn=false; @@ -508,7 +508,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { case DIV_CMD_NOTE_ON: { chWrite(c.chan,0,0); // reset previous note DivInstrument* ins=parent->getIns(chan[c.chan].ins); - if ((ins->type==DIV_INS_AMIGA)||chan[c.chan].pcm) { + if ((ins->type==DIV_INS_AMIGA) || chan[c.chan].pcm) { if (ins->type==DIV_INS_AMIGA) { chan[c.chan].furnacePCM=true; } else { @@ -519,7 +519,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { chan[c.chan].pcm=true; chan[c.chan].std.init(ins); chan[c.chan].sample=ins->amiga.initSample; - if (chan[c.chan].sample>=0&&chan[c.chan].samplesong.sampleLen) { + if (chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { DivSample* s=parent->getSample(chan[c.chan].sample); chWrite(c.chan,4,(s->offX1_010>>12)&0xff); int end=(s->offX1_010+s->length8+0xfff)&~0xfff; // padded @@ -582,7 +582,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { chan[c.chan].std.release(); break; case DIV_CMD_INSTRUMENT: - if (chan[c.chan].ins!=c.value||c.value2==1) { + if (chan[c.chan].ins!=c.value || c.value2==1) { chan[c.chan].ins=c.value; } break; @@ -618,7 +618,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { if (chan[c.chan].env.shape!=c.value) { chan[c.chan].env.shape=c.value; if (!chan[c.chan].pcm) { - if (chan[c.chan].env.flag.envEnable&&(!isMuted[c.chan])) { + if (chan[c.chan].env.flag.envEnable && (!isMuted[c.chan])) { chan[c.chan].envChanged=true; } chan[c.chan].keyOn=true; @@ -678,7 +678,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { chan[c.chan].freqChanged=true; break; case DIV_CMD_PRE_PORTA: - if (chan[c.chan].active&&c.value2) { + if (chan[c.chan].active && c.value2) { if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); } chan[c.chan].inPorta=c.value; @@ -713,35 +713,35 @@ int DivPlatformX1_010::dispatch(DivCommand c) { bool nextSplit=c.value&4; if (nextSplit!=(chan[c.chan].env.flag.envSplit)) { chan[c.chan].env.flag.envSplit=nextSplit; - if (!isMuted[c.chan]&&!chan[c.chan].pcm) { + if (!isMuted[c.chan] && !chan[c.chan].pcm) { chan[c.chan].envChanged=true; } } bool nextHinvR=c.value&8; if (nextHinvR!=(chan[c.chan].env.flag.envHinvR)) { chan[c.chan].env.flag.envHinvR=nextHinvR; - if (!isMuted[c.chan]&&!chan[c.chan].pcm) { + if (!isMuted[c.chan] && !chan[c.chan].pcm) { chan[c.chan].envChanged=true; } } bool nextVinvR=c.value&16; if (nextVinvR!=(chan[c.chan].env.flag.envVinvR)) { chan[c.chan].env.flag.envVinvR=nextVinvR; - if (!isMuted[c.chan]&&!chan[c.chan].pcm) { + if (!isMuted[c.chan] && !chan[c.chan].pcm) { chan[c.chan].envChanged=true; } } bool nextHinvL=c.value&32; if (nextHinvL!=(chan[c.chan].env.flag.envHinvL)) { chan[c.chan].env.flag.envHinvL=nextHinvL; - if (!isMuted[c.chan]&&!chan[c.chan].pcm) { + if (!isMuted[c.chan] && !chan[c.chan].pcm) { chan[c.chan].envChanged=true; } } bool nextVinvL=c.value&64; if (nextVinvL!=(chan[c.chan].env.flag.envVinvL)) { chan[c.chan].env.flag.envVinvL=nextVinvL; - if (!isMuted[c.chan]&&!chan[c.chan].pcm) { + if (!isMuted[c.chan] && !chan[c.chan].pcm) { chan[c.chan].envChanged=true; } } From 587fecd11d41777bc5b7fe119e903eeb77787a00 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 10 Mar 2022 14:40:45 -0500 Subject: [PATCH 187/637] temporarily strip out emulation code --- src/engine/platform/vera.cpp | 80 ------------------------------------ 1 file changed, 80 deletions(-) diff --git a/src/engine/platform/vera.cpp b/src/engine/platform/vera.cpp index 3376b0a39..e58266dd6 100644 --- a/src/engine/platform/vera.cpp +++ b/src/engine/platform/vera.cpp @@ -55,86 +55,6 @@ const char* DivPlatformVERA::getEffectName(unsigned char effect) { } void DivPlatformVERA::acquire(short* bufL, short* bufR, size_t start, size_t len) { - // taken from the official X16 emulator's source code, (c) 2020 Frank van den Hoef - const uint8_t volume_lut_psg[64]={0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 6, 6, 7, 7, 7, 8, 8, 9, 9, 10, 11, 11, 12, 13, 14, 14, 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 28, 29, 31, 33, 35, 37, 39, 42, 44, 47, 50, 52, 56, 59, 63}; - const uint8_t volume_lut_pcm[16]={0, 1, 2, 3, 4, 5, 6, 8, 11, 14, 18, 23, 30, 38, 49, 64}; - for (size_t pos=start; pos>1)^(noiseState>>2)^(noiseState>>4)^(noiseState>>15))&1); - for (int i=0; i<16; i++) { - unsigned freq=regPool[i*4+0] | (regPool[i*4+1] << 8); - unsigned old_accum=chan[i].accum; - unsigned new_accum=old_accum+freq; - int val=0x20; - if ((old_accum^new_accum)&0x10000) chan[i].noiseval=noiseOut; - new_accum&=0x1ffff; - chan[i].accum=new_accum; - switch (regPool[i*4+3]>>6) { - case 0: val=(new_accum>>10)>(regPool[i*4+3]&(unsigned)0x3f)?0:63; break; - case 1: val=(new_accum>>11); break; - case 2: val=(new_accum&0x10000)?(~(new_accum>>10)&0x3f):((new_accum>>10)&0x3f); break; - case 3: val=chan[i].noiseval; break; - } - val=(val-0x20)*volume_lut_psg[regPool[i*4+2]&0x3f]; - lout+=(regPool[i*4+2]&0x40)?val:0; - rout+=(regPool[i*4+2]&0x80)?val:0; - } - // PCM - // simple one-channel sample player, actual hardware is essentially a DAC - // with buffering - if (chan[16].pcm.sample>=0) { - chan[16].accum+=regPool[65]; - if (chan[16].accum>=128) { - DivSample* s=parent->getSample(chan[16].pcm.sample); - if (s->samples>0) { - int tmp_l=0; - int tmp_r=0; - if (!isMuted[16]) { - // TODO stereo samples once DivSample has a support for it - switch (s->depth) { - case 8: - tmp_l=s->data8[chan[16].pcm.pos]*256; - tmp_r=tmp_l; - regPool[64]|=0x20; // for register viewer purposes - break; - case 16: - tmp_l=s->data16[chan[16].pcm.pos]; - tmp_r=tmp_l; - regPool[64]&=~0x20; - break; - } - if (!(chan[16].pan&1)) tmp_l=0; - if (!(chan[16].pan&2)) tmp_r=0; - } - chan[16].pcm.out_l=tmp_l; - chan[16].pcm.out_r=tmp_r; - } else { - chan[16].pcm.sample=-1; - } - chan[16].accum&=0x7f; - chan[16].pcm.pos++; - if (chan[16].pcm.pos>=s->samples) { - if (s->loopStart>=0 && s->loopStart<=(int)s->samples) { - chan[16].pcm.pos=s->loopStart; - } else { - chan[16].pcm.sample=-1; - } - } - } - } - int pcmvol=volume_lut_pcm[regPool[64]&0x0f]; - lout+=chan[16].pcm.out_l*pcmvol/64; - rout+=chan[16].pcm.out_r*pcmvol/64; - - bufL[pos]=(short)(lout/2); - bufR[pos]=(short)(rout/2); - } } void DivPlatformVERA::reset() { From 9bd15bd513fab980706383f00cab338c649388d6 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 10 Mar 2022 15:51:27 -0500 Subject: [PATCH 188/637] VERA: bring up actual emulation core --- CMakeLists.txt | 2 + src/engine/platform/sound/vera_pcm.c | 130 +++++++++++++++++++++++++++ src/engine/platform/sound/vera_pcm.h | 31 +++++++ src/engine/platform/sound/vera_psg.c | 98 ++++++++++++++++++++ src/engine/platform/sound/vera_psg.h | 27 ++++++ src/engine/platform/vera.cpp | 11 ++- src/engine/platform/vera.h | 7 +- 7 files changed, 303 insertions(+), 3 deletions(-) create mode 100644 src/engine/platform/sound/vera_pcm.c create mode 100644 src/engine/platform/sound/vera_pcm.h create mode 100644 src/engine/platform/sound/vera_psg.c create mode 100644 src/engine/platform/sound/vera_psg.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 487d8617c..e3dbdbbaa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -234,6 +234,8 @@ src/engine/platform/sound/gb/apu.c src/engine/platform/sound/gb/timing.c src/engine/platform/sound/pce_psg.cpp src/engine/platform/sound/nes/apu.c +src/engine/platform/sound/vera_psg.c +src/engine/platform/sound/vera_pcm.c src/engine/platform/sound/c64/sid.cc src/engine/platform/sound/c64/voice.cc diff --git a/src/engine/platform/sound/vera_pcm.c b/src/engine/platform/sound/vera_pcm.c new file mode 100644 index 000000000..2a95ff04e --- /dev/null +++ b/src/engine/platform/sound/vera_pcm.c @@ -0,0 +1,130 @@ +// Commander X16 Emulator +// Copyright (c) 2020 Frank van den Hoef +// All rights reserved. License: 2-clause BSD + +#include "vera_pcm.h" +#include + +static uint8_t volume_lut[16] = {0, 1, 2, 3, 4, 5, 6, 8, 11, 14, 18, 23, 30, 38, 49, 64}; + +static void +fifo_reset(struct VERA_PCM* pcm) +{ + pcm->fifo_wridx = 0; + pcm->fifo_rdidx = 0; + pcm->fifo_cnt = 0; +} + +void +pcm_reset(struct VERA_PCM* pcm) +{ + fifo_reset(pcm); + pcm->ctrl = 0; + pcm->rate = 0; + pcm->cur_l = 0; + pcm->cur_r = 0; + pcm->phase = 0; +} + +void +pcm_write_ctrl(struct VERA_PCM* pcm, uint8_t val) +{ + if (val & 0x80) { + fifo_reset(pcm); + } + + pcm->ctrl = val & 0x3F; +} + +uint8_t +pcm_read_ctrl(struct VERA_PCM* pcm) +{ + uint8_t result = pcm->ctrl; + if (pcm->fifo_cnt == sizeof(pcm->fifo)) { + result |= 0x80; + } + return result; +} + +void +pcm_write_rate(struct VERA_PCM* pcm, uint8_t val) +{ + pcm->rate = val; +} + +uint8_t +pcm_read_rate(struct VERA_PCM* pcm) +{ + return pcm->rate; +} + +void +pcm_write_fifo(struct VERA_PCM* pcm, uint8_t val) +{ + if (pcm->fifo_cnt < sizeof(pcm->fifo)) { + pcm->fifo[pcm->fifo_wridx++] = val; + if (pcm->fifo_wridx == sizeof(pcm->fifo)) { + pcm->fifo_wridx = 0; + } + pcm->fifo_cnt++; + } +} + +static uint8_t +read_fifo(struct VERA_PCM* pcm) +{ + if (pcm->fifo_cnt == 0) { + return 0; + } + uint8_t result = pcm->fifo[pcm->fifo_rdidx++]; + if (pcm->fifo_rdidx == sizeof(pcm->fifo)) { + pcm->fifo_rdidx = 0; + } + pcm->fifo_cnt--; + return result; +} + +bool +pcm_is_fifo_almost_empty(struct VERA_PCM* pcm) +{ + return pcm->fifo_cnt < 1024; +} + +void +pcm_render(struct VERA_PCM* pcm, int16_t* buf_l, int16_t* buf_r, unsigned num_samples) +{ + while (num_samples--) { + uint8_t old_phase = pcm->phase; + pcm->phase += pcm->rate; + if ((old_phase & 0x80) != (pcm->phase & 0x80)) { + switch ((pcm->ctrl >> 4) & 3) { + case 0: { // mono 8-bit + pcm->cur_l = (int16_t)read_fifo(pcm) << 8; + pcm->cur_r = pcm->cur_l; + break; + } + case 1: { // stereo 8-bit + pcm->cur_l = read_fifo(pcm) << 8; + pcm->cur_r = read_fifo(pcm) << 8; + break; + } + case 2: { // mono 16-bit + pcm->cur_l = read_fifo(pcm); + pcm->cur_l |= read_fifo(pcm) << 8; + pcm->cur_r = pcm->cur_l; + break; + } + case 3: { // stereo 16-bit + pcm->cur_l = read_fifo(pcm); + pcm->cur_l |= read_fifo(pcm) << 8; + pcm->cur_r = read_fifo(pcm); + pcm->cur_r |= read_fifo(pcm) << 8; + break; + } + } + } + + *(buf_l++) = ((int)pcm->cur_l * (int)volume_lut[pcm->ctrl & 0xF]) >> 6; + *(buf_r++) = ((int)pcm->cur_r * (int)volume_lut[pcm->ctrl & 0xF]) >> 6; + } +} diff --git a/src/engine/platform/sound/vera_pcm.h b/src/engine/platform/sound/vera_pcm.h new file mode 100644 index 000000000..d9b600d0b --- /dev/null +++ b/src/engine/platform/sound/vera_pcm.h @@ -0,0 +1,31 @@ +// Commander X16 Emulator +// Copyright (c) 2020 Frank van den Hoef +// All rights reserved. License: 2-clause BSD + +#pragma once + +#include +#include + +struct VERA_PCM { + uint8_t fifo[4096 - 1]; // Actual hardware FIFO is 4kB, but you can only use 4095 bytes. + unsigned fifo_wridx; + unsigned fifo_rdidx; + unsigned fifo_cnt; + + uint8_t ctrl; + uint8_t rate; + + int16_t cur_l, cur_r; + uint8_t phase; +}; + + +void pcm_reset(struct VERA_PCM* pcm); +void pcm_write_ctrl(struct VERA_PCM* pcm, uint8_t val); +uint8_t pcm_read_ctrl(struct VERA_PCM* pcm); +void pcm_write_rate(struct VERA_PCM* pcm, uint8_t val); +uint8_t pcm_read_rate(struct VERA_PCM* pcm); +void pcm_write_fifo(struct VERA_PCM* pcm, uint8_t val); +void pcm_render(struct VERA_PCM* pcm, int16_t* buf_l, int16_t* buf_r, unsigned num_samples); +bool pcm_is_fifo_almost_empty(struct VERA_PCM* pcm); diff --git a/src/engine/platform/sound/vera_psg.c b/src/engine/platform/sound/vera_psg.c new file mode 100644 index 000000000..92dac698c --- /dev/null +++ b/src/engine/platform/sound/vera_psg.c @@ -0,0 +1,98 @@ +// Commander X16 Emulator +// Copyright (c) 2020 Frank van den Hoef +// All rights reserved. License: 2-clause BSD + +#include "vera_psg.h" + +#include +#include + +enum waveform { + WF_PULSE = 0, + WF_SAWTOOTH, + WF_TRIANGLE, + WF_NOISE, +}; + +static uint8_t volume_lut[64] = {0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 6, 6, 7, 7, 7, 8, 8, 9, 9, 10, 11, 11, 12, 13, 14, 14, 15, 16, 17, 18, 19, 21, 22, 23, 25, 26, 28, 29, 31, 33, 35, 37, 39, 42, 44, 47, 50, 52, 56, 59, 63}; + +void +psg_reset(struct VERA_PSG* psg) +{ + memset(psg->channels, 0, sizeof(psg->channels)); +} + +void +psg_writereg(struct VERA_PSG* psg, uint8_t reg, uint8_t val) +{ + reg &= 0x3f; + + int ch = reg / 4; + int idx = reg & 3; + + switch (idx) { + case 0: psg->channels[ch].freq = (psg->channels[ch].freq & 0xFF00) | val; break; + case 1: psg->channels[ch].freq = (psg->channels[ch].freq & 0x00FF) | (val << 8); break; + case 2: { + psg->channels[ch].right = (val & 0x80) != 0; + psg->channels[ch].left = (val & 0x40) != 0; + psg->channels[ch].volume = volume_lut[val & 0x3F]; + break; + } + case 3: { + psg->channels[ch].pw = val & 0x3F; + psg->channels[ch].waveform = val >> 6; + break; + } + } +} + +static inline void +render(struct VERA_PSG* psg, int16_t *left, int16_t *right) +{ + int l = 0; + int r = 0; + + for (int i = 0; i < 16; i++) { + struct VERAChannel *ch = &psg->channels[i]; + + unsigned new_phase = (ch->phase + ch->freq) & 0x1FFFF; + if ((ch->phase & 0x10000) != (new_phase & 0x10000)) { + ch->noiseval = rand() & 63; + } + ch->phase = new_phase; + + uint8_t v = 0; + switch (ch->waveform) { + case WF_PULSE: v = (ch->phase >> 10) > ch->pw ? 0 : 63; break; + case WF_SAWTOOTH: v = ch->phase >> 11; break; + case WF_TRIANGLE: v = (ch->phase & 0x10000) ? (~(ch->phase >> 10) & 0x3F) : ((ch->phase >> 10) & 0x3F); break; + case WF_NOISE: v = ch->noiseval; break; + } + int8_t sv = (v ^ 0x20); + if (sv & 0x20) { + sv |= 0xC0; + } + + int val = (int)sv * (int)ch->volume; + + if (ch->left) { + l += val; + } + if (ch->right) { + r += val; + } + } + + *left = l; + *right = r; +} + +void +psg_render(struct VERA_PSG* psg, int16_t *buf, unsigned num_samples) +{ + while (num_samples--) { + render(psg, &buf[0], &buf[1]); + buf += 2; + } +} diff --git a/src/engine/platform/sound/vera_psg.h b/src/engine/platform/sound/vera_psg.h new file mode 100644 index 000000000..9f94bb578 --- /dev/null +++ b/src/engine/platform/sound/vera_psg.h @@ -0,0 +1,27 @@ +// Commander X16 Emulator +// Copyright (c) 2020 Frank van den Hoef +// All rights reserved. License: 2-clause BSD + +#pragma once + +#include +#include + +struct VERAChannel { + uint16_t freq; + uint8_t volume; + bool left, right; + uint8_t pw; + uint8_t waveform; + + unsigned phase; + uint8_t noiseval; +}; + +struct VERA_PSG { + struct VERAChannel channels[16]; +}; + +void psg_reset(struct VERA_PSG* psg); +void psg_writereg(struct VERA_PSG* psg, uint8_t reg, uint8_t val); +void psg_render(struct VERA_PSG* psg, int16_t *buf, unsigned num_samples); diff --git a/src/engine/platform/vera.cpp b/src/engine/platform/vera.cpp index e58266dd6..99c6517ea 100644 --- a/src/engine/platform/vera.cpp +++ b/src/engine/platform/vera.cpp @@ -22,6 +22,9 @@ #include #include +#include "sound/vera_psg.h" +#include "sound/vera_pcm.h" + #define rWrite(c,a,d) {regPool[(c)*4+(a)]=(d);} #define rWriteLo(c,a,d) rWrite(c,a,(regPool[(c)*4+(a)]&(~0x3f))|((d)&0x3f)) #define rWriteHi(c,a,d) rWrite(c,a,(regPool[(c)*4+(a)]&(~0xc0))|(((d)<<6)&0xc0)) @@ -69,8 +72,6 @@ void DivPlatformVERA::reset() { } chan[16].vol=15; chan[16].pan=3; - noiseState=1; - noiseOut=0; } int DivPlatformVERA::calcNoteFreq(int ch, int note) { @@ -321,6 +322,8 @@ int DivPlatformVERA::init(DivEngine* p, int channels, int sugRate, unsigned int isMuted[i]=false; } parent=p; + psg=new struct VERA_PSG; + pcm=new struct VERA_PCM; dumpWrites=false; skipRegisterWrites=false; chipClock=25000000; @@ -329,5 +332,9 @@ int DivPlatformVERA::init(DivEngine* p, int channels, int sugRate, unsigned int return 17; } +void DivPlatformVERA::quit() { + delete psg; + delete pcm; +} DivPlatformVERA::~DivPlatformVERA() { } diff --git a/src/engine/platform/vera.h b/src/engine/platform/vera.h index e5cb2ad9e..e1369301b 100644 --- a/src/engine/platform/vera.h +++ b/src/engine/platform/vera.h @@ -23,6 +23,9 @@ #include "../instrument.h" #include "../macroInt.h" +struct VERA_PSG; +struct VERA_PCM; + class DivPlatformVERA: public DivDispatch { protected: struct Channel { @@ -46,8 +49,9 @@ class DivPlatformVERA: public DivDispatch { }; Channel chan[17]; bool isMuted[17]; - unsigned noiseState, noiseOut; unsigned char regPool[66]; + struct VERA_PSG* psg; + struct VERA_PCM* pcm; int calcNoteFreq(int ch, int note); friend void putDispatchChan(void*,int,int); @@ -68,6 +72,7 @@ class DivPlatformVERA: public DivDispatch { const char** getRegisterSheet(); const char* getEffectName(unsigned char effect); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); + void quit(); ~DivPlatformVERA(); }; #endif From 2f02e24a2f9185e001dd5531ab44edf5e1182f8a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 10 Mar 2022 16:52:59 -0500 Subject: [PATCH 189/637] VERA: get rid of rand() and adapt code --- src/engine/platform/sound/vera_psg.c | 16 ++++++++++++---- src/engine/platform/sound/vera_psg.h | 3 ++- src/engine/platform/vera.cpp | 9 ++++++--- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/engine/platform/sound/vera_psg.c b/src/engine/platform/sound/vera_psg.c index 92dac698c..f61bd26a2 100644 --- a/src/engine/platform/sound/vera_psg.c +++ b/src/engine/platform/sound/vera_psg.c @@ -20,6 +20,8 @@ void psg_reset(struct VERA_PSG* psg) { memset(psg->channels, 0, sizeof(psg->channels)); + psg->noiseState=1; + psg->noiseOut=0; } void @@ -52,13 +54,18 @@ render(struct VERA_PSG* psg, int16_t *left, int16_t *right) { int l = 0; int r = 0; + // TODO this is a currently speculated noise generation + // as the hardware and sources for it are not out in the public + // and the official emulator just uses rand() + psg->noiseOut=((psg->noiseOut<<1)|(psg->noiseState&1))&63; + psg->noiseState=(psg->noiseState<<1)|(((psg->noiseState>>1)^(psg->noiseState>>2)^(psg->noiseState>>4)^(psg->noiseState>>15))&1); for (int i = 0; i < 16; i++) { struct VERAChannel *ch = &psg->channels[i]; unsigned new_phase = (ch->phase + ch->freq) & 0x1FFFF; if ((ch->phase & 0x10000) != (new_phase & 0x10000)) { - ch->noiseval = rand() & 63; + ch->noiseval = psg->noiseOut; } ch->phase = new_phase; @@ -89,10 +96,11 @@ render(struct VERA_PSG* psg, int16_t *left, int16_t *right) } void -psg_render(struct VERA_PSG* psg, int16_t *buf, unsigned num_samples) +psg_render(struct VERA_PSG* psg, int16_t *bufL, int16_t *bufR, unsigned num_samples) { while (num_samples--) { - render(psg, &buf[0], &buf[1]); - buf += 2; + render(psg, bufL, bufR); + bufL++; + bufR++; } } diff --git a/src/engine/platform/sound/vera_psg.h b/src/engine/platform/sound/vera_psg.h index 9f94bb578..7a6a7f01d 100644 --- a/src/engine/platform/sound/vera_psg.h +++ b/src/engine/platform/sound/vera_psg.h @@ -19,9 +19,10 @@ struct VERAChannel { }; struct VERA_PSG { + unsigned int noiseState, noiseOut; struct VERAChannel channels[16]; }; void psg_reset(struct VERA_PSG* psg); void psg_writereg(struct VERA_PSG* psg, uint8_t reg, uint8_t val); -void psg_render(struct VERA_PSG* psg, int16_t *buf, unsigned num_samples); +void psg_render(struct VERA_PSG* psg, int16_t *bufL, int16_t *bufR, unsigned num_samples); diff --git a/src/engine/platform/vera.cpp b/src/engine/platform/vera.cpp index 99c6517ea..f6fc22f70 100644 --- a/src/engine/platform/vera.cpp +++ b/src/engine/platform/vera.cpp @@ -22,10 +22,12 @@ #include #include -#include "sound/vera_psg.h" -#include "sound/vera_pcm.h" +extern "C" { + #include "sound/vera_psg.h" + #include "sound/vera_pcm.h" +} -#define rWrite(c,a,d) {regPool[(c)*4+(a)]=(d);} +#define rWrite(c,a,d) {regPool[(c)*4+(a)]=(d); psg_writereg(psg,((c)*4+(a)),(d));} #define rWriteLo(c,a,d) rWrite(c,a,(regPool[(c)*4+(a)]&(~0x3f))|((d)&0x3f)) #define rWriteHi(c,a,d) rWrite(c,a,(regPool[(c)*4+(a)]&(~0xc0))|(((d)<<6)&0xc0)) #define rWriteFIFOVol(d) rWrite(16,0,(regPool[64]&(~0x3f))|((d)&0x3f)) @@ -58,6 +60,7 @@ const char* DivPlatformVERA::getEffectName(unsigned char effect) { } void DivPlatformVERA::acquire(short* bufL, short* bufR, size_t start, size_t len) { + psg_render(psg,bufL+start,bufR+start,len); } void DivPlatformVERA::reset() { From 6d9befaf278a8d7aa3e02222b0e6a9009ae56a27 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 10 Mar 2022 17:30:34 -0500 Subject: [PATCH 190/637] yay --- src/engine/platform/sound/vera_pcm.c | 7 +++++-- src/engine/platform/vera.cpp | 2 ++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/engine/platform/sound/vera_pcm.c b/src/engine/platform/sound/vera_pcm.c index 2a95ff04e..857ba46e2 100644 --- a/src/engine/platform/sound/vera_pcm.c +++ b/src/engine/platform/sound/vera_pcm.c @@ -124,7 +124,10 @@ pcm_render(struct VERA_PCM* pcm, int16_t* buf_l, int16_t* buf_r, unsigned num_sa } } - *(buf_l++) = ((int)pcm->cur_l * (int)volume_lut[pcm->ctrl & 0xF]) >> 6; - *(buf_r++) = ((int)pcm->cur_r * (int)volume_lut[pcm->ctrl & 0xF]) >> 6; + *(buf_l) += ((int)pcm->cur_l * (int)volume_lut[pcm->ctrl & 0xF]) >> 6; + *(buf_r) += ((int)pcm->cur_r * (int)volume_lut[pcm->ctrl & 0xF]) >> 6; + + buf_l++; + buf_r++; } } diff --git a/src/engine/platform/vera.cpp b/src/engine/platform/vera.cpp index f6fc22f70..3546ac1fc 100644 --- a/src/engine/platform/vera.cpp +++ b/src/engine/platform/vera.cpp @@ -59,8 +59,10 @@ const char* DivPlatformVERA::getEffectName(unsigned char effect) { return NULL; } +// TODO: wire up PCM. void DivPlatformVERA::acquire(short* bufL, short* bufR, size_t start, size_t len) { psg_render(psg,bufL+start,bufR+start,len); + pcm_render(pcm,bufL+start,bufR+start,len); } void DivPlatformVERA::reset() { From a9f80b841c8e569174789e8c48c599ec002a842a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 10 Mar 2022 17:46:40 -0500 Subject: [PATCH 191/637] VERA: add ins color and reset --- src/engine/platform/sound/vera_pcm.c | 2 ++ src/engine/platform/vera.cpp | 2 ++ src/gui/gui.cpp | 5 +++++ src/gui/gui.h | 1 + src/gui/settings.cpp | 2 ++ 5 files changed, 12 insertions(+) diff --git a/src/engine/platform/sound/vera_pcm.c b/src/engine/platform/sound/vera_pcm.c index 857ba46e2..740f5c821 100644 --- a/src/engine/platform/sound/vera_pcm.c +++ b/src/engine/platform/sound/vera_pcm.c @@ -4,6 +4,7 @@ #include "vera_pcm.h" #include +#include static uint8_t volume_lut[16] = {0, 1, 2, 3, 4, 5, 6, 8, 11, 14, 18, 23, 30, 38, 49, 64}; @@ -13,6 +14,7 @@ fifo_reset(struct VERA_PCM* pcm) pcm->fifo_wridx = 0; pcm->fifo_rdidx = 0; pcm->fifo_cnt = 0; + memset(pcm->fifo,0,sizeof(pcm->fifo)); } void diff --git a/src/engine/platform/vera.cpp b/src/engine/platform/vera.cpp index 3546ac1fc..e63ac8bec 100644 --- a/src/engine/platform/vera.cpp +++ b/src/engine/platform/vera.cpp @@ -69,6 +69,8 @@ void DivPlatformVERA::reset() { for (int i=0; i<17; i++) { chan[i]=Channel(); } + psg_reset(psg); + pcm_reset(pcm); memset(regPool,0,66); for (int i=0; i<16; i++) { chan[i].vol=63; diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 35597b65b..41d085022 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1190,6 +1190,10 @@ void FurnaceGUI::drawInsList() { ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_MIKEY]); name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d\n",i,ins->name,i); break; + case DIV_INS_VERA: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_VERA]); + name=fmt::sprintf(ICON_FA_KEYBOARD_O " %.2X: %s##_INS%d\n",i,ins->name,i); + break; default: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_UNKNOWN]); name=fmt::sprintf(ICON_FA_QUESTION " %.2X: %s##_INS%d\n",i,ins->name,i); @@ -5669,6 +5673,7 @@ void FurnaceGUI::applyUISettings() { GET_UI_COLOR(GUI_COLOR_INSTR_BEEPER,ImVec4(0.0f,1.0f,0.0f,1.0f)); GET_UI_COLOR(GUI_COLOR_INSTR_SWAN,ImVec4(0.3f,0.5f,1.0f,1.0f)); GET_UI_COLOR(GUI_COLOR_INSTR_MIKEY,ImVec4(0.5f,1.0f,0.3f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_VERA,ImVec4(0.4f,0.6f,1.0f,1.0f)) GET_UI_COLOR(GUI_COLOR_INSTR_UNKNOWN,ImVec4(0.3f,0.3f,0.3f,1.0f)); GET_UI_COLOR(GUI_COLOR_CHANNEL_FM,ImVec4(0.2f,0.8f,1.0f,1.0f)); GET_UI_COLOR(GUI_COLOR_CHANNEL_PULSE,ImVec4(0.4f,1.0f,0.2f,1.0f)); diff --git a/src/gui/gui.h b/src/gui/gui.h index 1ce74f3ea..07048cbde 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -73,6 +73,7 @@ enum FurnaceGUIColors { GUI_COLOR_INSTR_BEEPER, GUI_COLOR_INSTR_SWAN, GUI_COLOR_INSTR_MIKEY, + GUI_COLOR_INSTR_VERA, GUI_COLOR_INSTR_UNKNOWN, GUI_COLOR_CHANNEL_FM, GUI_COLOR_CHANNEL_PULSE, diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 3f0e532db..cd3608987 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -492,6 +492,7 @@ void FurnaceGUI::drawSettings() { UI_COLOR_CONFIG(GUI_COLOR_INSTR_BEEPER,"PC Beeper"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_SWAN,"WonderSwan"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_MIKEY,"Lynx"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_VERA,"VERA"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_UNKNOWN,"Other/Unknown"); ImGui::TreePop(); } @@ -1159,6 +1160,7 @@ void FurnaceGUI::commitSettings() { PUT_UI_COLOR(GUI_COLOR_INSTR_BEEPER); PUT_UI_COLOR(GUI_COLOR_INSTR_SWAN); PUT_UI_COLOR(GUI_COLOR_INSTR_MIKEY); + PUT_UI_COLOR(GUI_COLOR_INSTR_VERA); PUT_UI_COLOR(GUI_COLOR_INSTR_UNKNOWN); PUT_UI_COLOR(GUI_COLOR_CHANNEL_FM); PUT_UI_COLOR(GUI_COLOR_CHANNEL_PULSE); From eb48a3d1082adc8857bfecaa339b49f5ac5e463f Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 10 Mar 2022 21:35:34 -0500 Subject: [PATCH 192/637] Revert "Create OPL3 docs (read Effect Commands section)" --- papers/doc/7-systems/opl3.md | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 papers/doc/7-systems/opl3.md diff --git a/papers/doc/7-systems/opl3.md b/papers/doc/7-systems/opl3.md deleted file mode 100644 index 79fa9df05..000000000 --- a/papers/doc/7-systems/opl3.md +++ /dev/null @@ -1,22 +0,0 @@ -# Yamaha OPL3/YMF262 - -The Yamaha OPL3/YMF262 was an FM sound chip developed by Yamaha (obviously) and released in 1994. - -The OPL3 saw most of its use in PC sound cards (such as the SoundBlaster 16 and the Pro AudioSpectrum). - -Unfortunately, developers who wanted to port their OPL/OPL2 music to the OPL3 were very lazy in doing so, so most of them ended up disreguarding the additions to the OPL3 entirely, and would use entirely the same MIDI driver and patches. - -## Sound capabilities - - - 18 channels 2-op of FM synthesis - - 8 unique waveforms which can be used on the carrier or the modulator - - A "split operators" mode that makes it so that the first and second operators are their own "voices" (each take the base pitch of the note to play and add the frequency multiplier's pitch to it) - - Hard panning (left, center, and right only) - - A rhythm mode where the last 3 voices are replaced with 5 drum channels - - A 4-op mode where 12 FM channels are combined to make 6 4-op FM channels - - A "tremolo" mode where AM (amplitude modulation) is applied, but unfortunately without any strength or speed controls. - - A built in vibrato mode which enables vibrato without a music driver doing it for it - - ADSR support on the carrier and/or modulator - -## Effect commands - - PLEASE DO NOT MERGE UNTIL I HAVE COMMITED THE EFFECT COMMANDS TO THIS PULL REQUEST. From 0700ba7e658baf126f347b6b9ba389d64e41935a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 10 Mar 2022 22:10:52 -0500 Subject: [PATCH 193/637] GUI: start with the pattern view focused --- src/gui/gui.cpp | 8 ++++++++ src/gui/gui.h | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index d9ee10cfe..bb7168edf 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -5319,6 +5319,11 @@ bool FurnaceGUI::loop() { drawChannels(); drawRegView(); + if (firstFrame) { + firstFrame=false; + if (patternOpen) nextWindow=GUI_WINDOW_PATTERN; + } + if (ImGuiFileDialog::Instance()->Display("FileDialog",ImGuiWindowFlags_NoCollapse|ImGuiWindowFlags_NoMove,ImVec2(600.0f*dpiScale,400.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale))) { //ImGui::GetIO().ConfigFlags&=~ImGuiConfigFlags_NavEnableKeyboard; switch (curFileDialog) { @@ -6112,6 +6117,8 @@ bool FurnaceGUI::init() { oldPat[i]=new DivPattern; } + firstFrame=true; + #ifdef __APPLE__ SDL_RaiseWindow(sdlWin); #endif @@ -6238,6 +6245,7 @@ FurnaceGUI::FurnaceGUI(): demandScrollX(false), fancyPattern(false), wantPatName(false), + firstFrame(true), curWindow(GUI_WINDOW_NOTHING), nextWindow(GUI_WINDOW_NOTHING), nextDesc(NULL), diff --git a/src/gui/gui.h b/src/gui/gui.h index c2f7d58d3..129361c95 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -584,7 +584,7 @@ class FurnaceGUI { bool pianoOpen, notesOpen, channelsOpen, regViewOpen; SelectionPoint selStart, selEnd, cursor; bool selecting, curNibble, orderNibble, followOrders, followPattern, changeAllOrders; - bool collapseWindow, demandScrollX, fancyPattern, wantPatName; + bool collapseWindow, demandScrollX, fancyPattern, wantPatName, firstFrame; FurnaceGUIWindows curWindow, nextWindow; float peak[2]; float patChanX[DIV_MAX_CHANS+1]; From 8e61a0d314b03ad59cff5b86cdbef5299edb37a7 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 10 Mar 2022 23:49:44 -0500 Subject: [PATCH 194/637] better channel names --- src/engine/sysDef.cpp | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 883c9a617..70e27dae8 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -880,7 +880,7 @@ bool DivEngine::isSTDSystem(DivSystem sys) { sys!=DIV_SYSTEM_YM2151); } -const char* chanNames[38][32]={ +const char* chanNames[40][32]={ {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "PCM"}, // YMU759/SegaPCM {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Square 1", "Square 2", "Square 3", "Noise"}, // Genesis {"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6", "Square 1", "Square 2", "Square 3", "Noise"}, // Genesis (extended channel 3) @@ -893,7 +893,7 @@ const char* chanNames[38][32]={ {"FM 1", "FM 2", "FM 3", "FM 4", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6", "ADPCM-B"}, // YM2610 {"FM 1", "FM 2 OP1", "FM 2 OP2", "FM 2 OP3", "FM 2 OP4", "FM 3", "FM 4", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6", "ADPCM-B"}, // YM2610 (extended channel 2) {"PSG 1", "PSG 2", "PSG 3"}, // AY-3-8910 - {"Channel 1", "Channel 2", "Channel 3", "Channel 4"}, // Amiga/POKEY/Swan/Lynx + {"Channel 1", "Channel 2", "Channel 3", "Channel 4"}, // Amiga/POKEY/Lynx {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8"}, // YM2151/YM2414 {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6"}, // YM2612 {"Channel 1", "Channel 2"}, // TIA @@ -919,6 +919,8 @@ const char* chanNames[38][32]={ {"FM 1", "FM 2", "FM 3", "4OP 1", "4OP 2", "4OP 3", "4OP 4", "4OP 5", "4OP 6", "Kick", "Snare", "Tom", "Top", "HiHat"}, // OPL3 4-op + drums {"PCM 1", "PCM 2", "PCM 3", "PCM 4", "PCM 5", "PCM 6", "PCM 7", "PCM 8", "PCM 9", "PCM 10", "PCM 11", "PCM 12", "PCM 13", "PCM 14", "PCM 15", "PCM 16", "ADPCM 1", "ADPCM 2", "ADPCM 3"}, // QSound {"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6", "ADPCM-B"}, // YM2610B (extended channel 3) + {"Wave", "Wave/PCM", "Wave", "Wave/Noise"}, // Swan + {"PSG 1", "PSG 2", "PSG 3", "PSG 4", "PSG 5", "PSG 6", "PSG 7", "PSG 8", "PSG 9", "PSG 10", "PSG 11", "PSG 12", "PSG 13", "PSG 14", "PSG 15", "PSG 16", "PCM"}, // VERA }; const char* chanShortNames[38][32]={ @@ -962,7 +964,7 @@ const char* chanShortNames[38][32]={ {"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"}, // YM2610B (extended channel 3) }; -const int chanTypes[40][32]={ +const int chanTypes[41][32]={ {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4}, // YMU759 {0, 0, 0, 0, 0, 0, 1, 1, 1, 2}, // Genesis {0, 0, 5, 5, 5, 5, 0, 0, 0, 1, 1, 1, 2}, // Genesis (extended channel 3) @@ -999,10 +1001,11 @@ const int chanTypes[40][32]={ {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2}, // OPL3 drums {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // OPL3 4-op {0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2}, // OPL3 4-op + drums - {3, 3, 3, 3}, //Lynx + {3, 3, 3, 3}, // Lynx {0, 0, 5, 5, 5, 5, 0, 0, 0, 1, 1, 1, 4, 4, 4, 4, 4, 4, 4}, // YM2610B (extended channel 3) {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4}, // VERA {3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3}, // X1-010 + {3, 4, 3, 2}, // Swan }; const DivInstrumentType chanPrefType[46][28]={ @@ -1105,10 +1108,12 @@ const char* DivEngine::getChannelName(int chan) { break; case DIV_SYSTEM_AMIGA: case DIV_SYSTEM_POKEY: - case DIV_SYSTEM_SWAN: case DIV_SYSTEM_LYNX: return chanNames[12][dispatchChanOfChan[chan]]; break; + case DIV_SYSTEM_SWAN: + return chanNames[38][dispatchChanOfChan[chan]]; + break; case DIV_SYSTEM_YM2151: return chanNames[13][dispatchChanOfChan[chan]]; break; @@ -1191,7 +1196,7 @@ const char* DivEngine::getChannelName(int chan) { return chanNames[36][dispatchChanOfChan[chan]]; break; case DIV_SYSTEM_VERA: - return chanNames[0][dispatchChanOfChan[chan]]; + return chanNames[39][dispatchChanOfChan[chan]]; break; } return "??"; @@ -1389,7 +1394,6 @@ int DivEngine::getChannelType(int chan) { break; case DIV_SYSTEM_AMIGA: case DIV_SYSTEM_POKEY: - case DIV_SYSTEM_SWAN: return chanTypes[12][dispatchChanOfChan[chan]]; break; case DIV_SYSTEM_YM2151: @@ -1479,6 +1483,9 @@ int DivEngine::getChannelType(int chan) { case DIV_SYSTEM_X1_010: return chanTypes[39][dispatchChanOfChan[chan]]; break; + case DIV_SYSTEM_SWAN: + return chanTypes[40][dispatchChanOfChan[chan]]; + break; } return 1; } From 74a23b3ec5373a4b2cfa7f70b914dbad28086e00 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 11 Mar 2022 01:31:21 -0500 Subject: [PATCH 195/637] GUI: begin work on some of the new actions --- src/gui/gui.cpp | 158 ++++++++++++++++++++++++++++++++++++++++++++---- src/gui/gui.h | 19 +++++- 2 files changed, 164 insertions(+), 13 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index bb7168edf..e79029e37 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2583,6 +2583,16 @@ void FurnaceGUI::prepareUndo(ActionType action) { case GUI_UNDO_PATTERN_PUSH: case GUI_UNDO_PATTERN_CUT: case GUI_UNDO_PATTERN_PASTE: + case GUI_UNDO_PATTERN_CHANGE_INS: + case GUI_UNDO_PATTERN_INTERPOLATE: + case GUI_UNDO_PATTERN_FADE_IN: + case GUI_UNDO_PATTERN_FADE_OUT: + case GUI_UNDO_PATTERN_SCALE: + case GUI_UNDO_PATTERN_RANDOMIZE: + case GUI_UNDO_PATTERN_INVERT_VAL: + case GUI_UNDO_PATTERN_FLIP: + case GUI_UNDO_PATTERN_COLLAPSE: + case GUI_UNDO_PATTERN_EXPAND: for (int i=0; igetTotalChannelCount(); i++) { e->song.pat[i].getPattern(e->song.orders.ord[i][order],false)->copyOn(oldPat[i]); } @@ -2624,6 +2634,16 @@ void FurnaceGUI::makeUndo(ActionType action) { case GUI_UNDO_PATTERN_PUSH: case GUI_UNDO_PATTERN_CUT: case GUI_UNDO_PATTERN_PASTE: + case GUI_UNDO_PATTERN_CHANGE_INS: + case GUI_UNDO_PATTERN_INTERPOLATE: + case GUI_UNDO_PATTERN_FADE_IN: + case GUI_UNDO_PATTERN_FADE_OUT: + case GUI_UNDO_PATTERN_SCALE: + case GUI_UNDO_PATTERN_RANDOMIZE: + case GUI_UNDO_PATTERN_INVERT_VAL: + case GUI_UNDO_PATTERN_FLIP: + case GUI_UNDO_PATTERN_COLLAPSE: + case GUI_UNDO_PATTERN_EXPAND: for (int i=0; igetTotalChannelCount(); i++) { DivPattern* p=e->song.pat[i].getPattern(e->song.orders.ord[i][order],false); for (int j=0; jsong.patLen; j++) { @@ -2813,6 +2833,7 @@ void FurnaceGUI::doTranspose(int amount) { origNote+=12; origOctave--; } + // FIX!!!!! TODO if (origOctave>7) { origNote=12; origOctave=7; @@ -2995,6 +3016,67 @@ void FurnaceGUI::doPaste(PasteMode mode) { makeUndo(GUI_UNDO_PATTERN_PASTE); } +void FurnaceGUI::doChangeIns(int ins) { + finishSelection(); + prepareUndo(GUI_UNDO_PATTERN_CHANGE_INS); + + int iCoarse=selStart.xCoarse; + int ord=e->getOrder(); + for (; iCoarse<=selEnd.xCoarse; iCoarse++) { + if (!e->song.chanShow[iCoarse]) continue; + DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); + for (int j=selStart.y; j<=selEnd.y; j++) { + if (pat->data[j][2]!=-1 || !(pat->data[j][0]==0 && pat->data[j][1]==0)) { + pat->data[j][2]=ins; + } + } + } + + makeUndo(GUI_UNDO_PATTERN_CHANGE_INS); +} + +void FurnaceGUI::doInterpolate() { + finishSelection(); + prepareUndo(GUI_UNDO_PATTERN_INTERPOLATE); + + makeUndo(GUI_UNDO_PATTERN_INTERPOLATE); +} + +void FurnaceGUI::doFade(bool fadeIn) { + finishSelection(); + prepareUndo(fadeIn?GUI_UNDO_PATTERN_FADE_IN:GUI_UNDO_PATTERN_FADE_OUT); + + makeUndo(fadeIn?GUI_UNDO_PATTERN_FADE_IN:GUI_UNDO_PATTERN_FADE_OUT); +} + +void FurnaceGUI::doInvertValues() { + finishSelection(); + prepareUndo(GUI_UNDO_PATTERN_INVERT_VAL); + + makeUndo(GUI_UNDO_PATTERN_INVERT_VAL); +} + +void FurnaceGUI::doFlip() { + finishSelection(); + prepareUndo(GUI_UNDO_PATTERN_FLIP); + + makeUndo(GUI_UNDO_PATTERN_FLIP); +} + +void FurnaceGUI::doCollapse(int divider) { + finishSelection(); + prepareUndo(GUI_UNDO_PATTERN_COLLAPSE); + + makeUndo(GUI_UNDO_PATTERN_COLLAPSE); +} + +void FurnaceGUI::doExpand(int multiplier) { + finishSelection(); + prepareUndo(GUI_UNDO_PATTERN_EXPAND); + + makeUndo(GUI_UNDO_PATTERN_EXPAND); +} + void FurnaceGUI::doUndo() { if (undoHist.empty()) return; UndoStep& us=undoHist.back(); @@ -3014,6 +3096,16 @@ void FurnaceGUI::doUndo() { case GUI_UNDO_PATTERN_PUSH: case GUI_UNDO_PATTERN_CUT: case GUI_UNDO_PATTERN_PASTE: + case GUI_UNDO_PATTERN_CHANGE_INS: + case GUI_UNDO_PATTERN_INTERPOLATE: + case GUI_UNDO_PATTERN_FADE_IN: + case GUI_UNDO_PATTERN_FADE_OUT: + case GUI_UNDO_PATTERN_SCALE: + case GUI_UNDO_PATTERN_RANDOMIZE: + case GUI_UNDO_PATTERN_INVERT_VAL: + case GUI_UNDO_PATTERN_FLIP: + case GUI_UNDO_PATTERN_COLLAPSE: + case GUI_UNDO_PATTERN_EXPAND: for (UndoPatternData& i: us.pat) { DivPattern* p=e->song.pat[i.chan].getPattern(i.pat,true); p->data[i.row][i.col]=i.oldVal; @@ -3051,6 +3143,16 @@ void FurnaceGUI::doRedo() { case GUI_UNDO_PATTERN_PUSH: case GUI_UNDO_PATTERN_CUT: case GUI_UNDO_PATTERN_PASTE: + case GUI_UNDO_PATTERN_CHANGE_INS: + case GUI_UNDO_PATTERN_INTERPOLATE: + case GUI_UNDO_PATTERN_FADE_IN: + case GUI_UNDO_PATTERN_FADE_OUT: + case GUI_UNDO_PATTERN_SCALE: + case GUI_UNDO_PATTERN_RANDOMIZE: + case GUI_UNDO_PATTERN_INVERT_VAL: + case GUI_UNDO_PATTERN_FLIP: + case GUI_UNDO_PATTERN_COLLAPSE: + case GUI_UNDO_PATTERN_EXPAND: for (UndoPatternData& i: us.pat) { DivPattern* p=e->song.pat[i.chan].getPattern(i.pat,true); p->data[i.row][i.col]=i.newVal; @@ -3527,6 +3629,37 @@ void FurnaceGUI::doAction(int what) { e->song.pat[cursor.xCoarse].effectRows--; if (e->song.pat[cursor.xCoarse].effectRows<1) e->song.pat[cursor.xCoarse].effectRows=1; break; + case GUI_ACTION_PAT_INTERPOLATE: + doInterpolate(); + break; + case GUI_ACTION_PAT_FADE_IN: + doFade(true); + break; + case GUI_ACTION_PAT_FADE_OUT: + doFade(false); + break; + case GUI_ACTION_PAT_INVERT_VALUES: + doInvertValues(); + break; + case GUI_ACTION_PAT_FLIP_SELECTION: + doFlip(); + break; + case GUI_ACTION_PAT_COLLAPSE_ROWS: + doCollapse(2); + break; + case GUI_ACTION_PAT_EXPAND_ROWS: + doExpand(2); + break; + case GUI_ACTION_PAT_COLLAPSE_PAT: // TODO + break; + case GUI_ACTION_PAT_EXPAND_PAT: // TODO + break; + case GUI_ACTION_PAT_COLLAPSE_SONG: // TODO + break; + case GUI_ACTION_PAT_EXPAND_SONG: // TODO + break; + case GUI_ACTION_PAT_LATCH: // TODO + break; case GUI_ACTION_INS_LIST_ADD: curIns=e->addInstrument(cursor.xCoarse); @@ -4420,10 +4553,10 @@ void FurnaceGUI::editOptions(bool topMenu) { if (ImGui::MenuItem("copy",BIND_FOR(GUI_ACTION_PAT_COPY))) doCopy(false); if (ImGui::MenuItem("paste",BIND_FOR(GUI_ACTION_PAT_PASTE))) doPaste(); if (ImGui::BeginMenu("paste special...")) { - ImGui::MenuItem("paste mix",BIND_FOR(GUI_ACTION_PAT_PASTE_MIX)); - ImGui::MenuItem("paste mix (background)",BIND_FOR(GUI_ACTION_PAT_PASTE_MIX_BG)); - ImGui::MenuItem("paste flood",BIND_FOR(GUI_ACTION_PAT_PASTE_FLOOD)); - ImGui::MenuItem("paste overflow",BIND_FOR(GUI_ACTION_PAT_PASTE_OVERFLOW)); + if (ImGui::MenuItem("paste mix",BIND_FOR(GUI_ACTION_PAT_PASTE_MIX))) doPaste(GUI_PASTE_MODE_MIX_FG); + if (ImGui::MenuItem("paste mix (background)",BIND_FOR(GUI_ACTION_PAT_PASTE_MIX_BG))) doPaste(GUI_PASTE_MODE_MIX_BG); + if (ImGui::MenuItem("paste flood",BIND_FOR(GUI_ACTION_PAT_PASTE_FLOOD))) doPaste(GUI_PASTE_MODE_FLOOD); + if (ImGui::MenuItem("paste overflow",BIND_FOR(GUI_ACTION_PAT_PASTE_OVERFLOW))) doPaste(GUI_PASTE_MODE_OVERFLOW); ImGui::EndMenu(); } if (ImGui::MenuItem("delete",BIND_FOR(GUI_ACTION_PAT_DELETE))) doDelete(); @@ -4452,16 +4585,17 @@ void FurnaceGUI::editOptions(bool topMenu) { } ImGui::Separator(); - ImGui::MenuItem("interpolate",BIND_FOR(GUI_ACTION_PAT_INTERPOLATE)); - ImGui::MenuItem("fade in",BIND_FOR(GUI_ACTION_PAT_FADE_IN)); - ImGui::MenuItem("fade out",BIND_FOR(GUI_ACTION_PAT_FADE_OUT)); + if (ImGui::MenuItem("interpolate",BIND_FOR(GUI_ACTION_PAT_INTERPOLATE))) doInterpolate(); + if (ImGui::MenuItem("fade in",BIND_FOR(GUI_ACTION_PAT_FADE_IN))) doFade(true); + if (ImGui::MenuItem("fade out",BIND_FOR(GUI_ACTION_PAT_FADE_OUT))) doFade(false); if (ImGui::BeginMenu("change instrument...")) { if (e->song.ins.empty()) { ImGui::Text("no instruments available"); } for (size_t i=0; isong.ins.size(); i++) { snprintf(id,4095,"%.2X: %s",(int)i,e->song.ins[i]->name.c_str()); - if (ImGui::MenuItem(id)) { // TODO + if (ImGui::MenuItem(id)) { + doChangeIns(i); } } ImGui::EndMenu(); @@ -4488,13 +4622,13 @@ void FurnaceGUI::editOptions(bool topMenu) { } ImGui::EndMenu(); } - ImGui::MenuItem("invert values",BIND_FOR(GUI_ACTION_PAT_INVERT_VALUES)); + if (ImGui::MenuItem("invert values",BIND_FOR(GUI_ACTION_PAT_INVERT_VALUES))) doInvertValues(); ImGui::Separator(); - ImGui::MenuItem("flip selection",BIND_FOR(GUI_ACTION_PAT_FLIP_SELECTION)); - ImGui::MenuItem("collapse",BIND_FOR(GUI_ACTION_PAT_COLLAPSE_ROWS)); - ImGui::MenuItem("expand",BIND_FOR(GUI_ACTION_PAT_EXPAND_ROWS)); + if (ImGui::MenuItem("flip selection",BIND_FOR(GUI_ACTION_PAT_FLIP_SELECTION))) doFlip(); + if (ImGui::MenuItem("collapse",BIND_FOR(GUI_ACTION_PAT_COLLAPSE_ROWS))) doCollapse(2); + if (ImGui::MenuItem("expand",BIND_FOR(GUI_ACTION_PAT_EXPAND_ROWS))) doExpand(2); if (topMenu) { ImGui::Separator(); diff --git a/src/gui/gui.h b/src/gui/gui.h index 129361c95..17e240c8c 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -380,7 +380,17 @@ enum ActionType { GUI_UNDO_PATTERN_PULL, GUI_UNDO_PATTERN_PUSH, GUI_UNDO_PATTERN_CUT, - GUI_UNDO_PATTERN_PASTE + GUI_UNDO_PATTERN_PASTE, + GUI_UNDO_PATTERN_CHANGE_INS, + GUI_UNDO_PATTERN_INTERPOLATE, + GUI_UNDO_PATTERN_FADE_IN, + GUI_UNDO_PATTERN_FADE_OUT, + GUI_UNDO_PATTERN_SCALE, + GUI_UNDO_PATTERN_RANDOMIZE, + GUI_UNDO_PATTERN_INVERT_VAL, + GUI_UNDO_PATTERN_FLIP, + GUI_UNDO_PATTERN_COLLAPSE, + GUI_UNDO_PATTERN_EXPAND }; struct UndoPatternData { @@ -747,6 +757,13 @@ class FurnaceGUI { void doTranspose(int amount); void doCopy(bool cut); void doPaste(PasteMode mode=GUI_PASTE_MODE_NORMAL); + void doChangeIns(int ins); + void doInterpolate(); + void doFade(bool fadeIn); + void doInvertValues(); + void doFlip(); + void doCollapse(int divider); + void doExpand(int multiplier); void doUndo(); void doRedo(); void editOptions(bool topMenu); From 3ac1dce3fe453b02050ea6aa80f0217e0659f08c Mon Sep 17 00:00:00 2001 From: cam900 Date: Sat, 12 Mar 2022 03:30:54 +0900 Subject: [PATCH 196/637] Add AY-3-8914 support as configurable in AY-3-8910 Previous PR (https://github.com/tildearrow/furnace/pull/278) is closed due this, but archived for info. It's AY with 4 level envelope volume per channel and different register format. --- papers/doc/7-systems/ay8910.md | 2 ++ src/engine/platform/ay.cpp | 65 ++++++++++++++++++++++++++++++---- src/engine/platform/ay.h | 6 +++- src/engine/sysDef.cpp | 13 ++++--- src/gui/gui.cpp | 6 +++- 5 files changed, 79 insertions(+), 13 deletions(-) diff --git a/papers/doc/7-systems/ay8910.md b/papers/doc/7-systems/ay8910.md index 3dd1049f6..4d13a153a 100644 --- a/papers/doc/7-systems/ay8910.md +++ b/papers/doc/7-systems/ay8910.md @@ -4,6 +4,8 @@ this chip was used in several home computers (ZX Spectrum, MSX, Amstrad CPC, Ata the chip's powerful sound comes from the envelope... +AY-3-8914 variant was used in Intellivision, it's basically original AY with 4 level envelope volume per channel and different register format. + # effects - `20xx`: set channel mode. `xx` may be one of the following: diff --git a/src/engine/platform/ay.cpp b/src/engine/platform/ay.cpp index 0777fca8b..526b3fbd2 100644 --- a/src/engine/platform/ay.cpp +++ b/src/engine/platform/ay.cpp @@ -24,7 +24,7 @@ #include #define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;} -#define immWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } +#define immWrite(a,v) if (!skipRegisterWrites) {writes.emplace(regRemap(a),v); if (dumpWrites) {addWrite(regRemap(a),v);} } #define CHIP_DIVIDER 8 @@ -48,8 +48,28 @@ const char* regCheatSheetAY[]={ NULL }; +const char* regCheatSheetAY8914[]={ + "FreqL_A", "0", + "FreqL_B", "1", + "FreqL_C", "2", + "FreqL_Env", "3", + "FreqH_A", "4", + "FreqH_B", "5", + "FreqH_C", "6", + "FreqH_Env", "7", + "Enable", "8", + "FreqNoise", "9", + "Control_Env", "A", + "Volume_A", "B", + "Volume_B", "C", + "Volume_C", "D", + "PortA", "E", + "PortB", "F", + NULL +}; + const char** DivPlatformAY8910::getRegisterSheet() { - return regCheatSheetAY; + return intellivision?regCheatSheetAY8914:regCheatSheetAY; } const char* DivPlatformAY8910::getEffectName(unsigned char effect) { @@ -92,8 +112,13 @@ void DivPlatformAY8910::acquire(short* bufL, short* bufR, size_t start, size_t l } while (!writes.empty()) { QueuedWrite w=writes.front(); - ay->address_w(w.addr); - ay->data_w(w.val); + if (intellivision) { + ay8914_device* ay8914=(ay8914_device*)ay; + ay8914->write(w.addr,w.val); + } else { + ay->address_w(w.addr); + ay->data_w(w.val); + } regPool[w.addr&0x0f]=w.val; writes.pop(); } @@ -125,6 +150,8 @@ void DivPlatformAY8910::tick() { if (chan[i].outVol<0) chan[i].outVol=0; if (isMuted[i]) { rWrite(0x08+i,0); + } else if (intellivision && (chan[i].psgMode&4)) { + rWrite(0x08+i,(chan[i].outVol&0xc)<<2); } else { rWrite(0x08+i,(chan[i].outVol&15)|((chan[i].psgMode&4)<<2)); } @@ -151,6 +178,8 @@ void DivPlatformAY8910::tick() { chan[i].psgMode=(chan[i].std.wave+1)&7; if (isMuted[i]) { rWrite(0x08+i,0); + } else if (intellivision && (chan[i].psgMode&4)) { + rWrite(0x08+i,(chan[i].outVol&0xc)<<2); } else { rWrite(0x08+i,(chan[i].outVol&15)|((chan[i].psgMode&4)<<2)); } @@ -242,6 +271,8 @@ int DivPlatformAY8910::dispatch(DivCommand c) { chan[c.chan].std.init(ins); if (isMuted[c.chan]) { rWrite(0x08+c.chan,0); + } else if (intellivision && (chan[c.chan].psgMode&4)) { + rWrite(0x08+c.chan,(chan[c.chan].vol&0xc)<<2); } else { rWrite(0x08+c.chan,(chan[c.chan].vol&15)|((chan[c.chan].psgMode&4)<<2)); } @@ -264,7 +295,13 @@ int DivPlatformAY8910::dispatch(DivCommand c) { if (isMuted[c.chan]) { rWrite(0x08+c.chan,0); } else { - if (chan[c.chan].active) rWrite(0x08+c.chan,(chan[c.chan].vol&15)|((chan[c.chan].psgMode&4)<<2)); + if (chan[c.chan].active) { + if (intellivision && (chan[c.chan].psgMode&4)) { + rWrite(0x08+c.chan,(chan[c.chan].vol&0xc)<<2); + } else { + rWrite(0x08+c.chan,(chan[c.chan].vol&15)|((chan[c.chan].psgMode&4)<<2)); + } + } } break; } @@ -317,7 +354,11 @@ int DivPlatformAY8910::dispatch(DivCommand c) { if (isMuted[c.chan]) { rWrite(0x08+c.chan,0); } else if (chan[c.chan].active) { - rWrite(0x08+c.chan,(chan[c.chan].outVol&15)|((chan[c.chan].psgMode&4)<<2)); + if (intellivision && (chan[c.chan].psgMode&4)) { + rWrite(0x08+c.chan,(chan[c.chan].outVol&0xc)<<2); + } else { + rWrite(0x08+c.chan,(chan[c.chan].outVol&15)|((chan[c.chan].psgMode&4)<<2)); + } } } break; @@ -334,6 +375,8 @@ int DivPlatformAY8910::dispatch(DivCommand c) { } if (isMuted[c.chan]) { rWrite(0x08+c.chan,0); + } else if (intellivision && (chan[c.chan].psgMode&4)) { + rWrite(0x08+c.chan,(chan[c.chan].vol&0xc)<<2); } else { rWrite(0x08+c.chan,(chan[c.chan].vol&15)|((chan[c.chan].psgMode&4)<<2)); } @@ -383,6 +426,8 @@ void DivPlatformAY8910::muteChannel(int ch, bool mute) { isMuted[ch]=mute; if (isMuted[ch]) { rWrite(0x08+ch,0); + } else if (intellivision && (chan[ch].psgMode&4)) { + rWrite(0x08+ch,(chan[ch].vol&0xc)<<2); } else { rWrite(0x08+ch,(chan[ch].outVol&15)|((chan[ch].psgMode&4)<<2)); } @@ -508,14 +553,22 @@ void DivPlatformAY8910::setFlags(unsigned int flags) { case 1: ay=new ym2149_device(rate); sunsoft=false; + intellivision=false; break; case 2: ay=new sunsoft_5b_sound_device(rate); sunsoft=true; + intellivision=false; + break; + case 3: + ay=new ay8914_device(rate); + sunsoft=false; + intellivision=true; break; default: ay=new ay8910_device(rate); sunsoft=false; + intellivision=false; break; } ay->device_start(); diff --git a/src/engine/platform/ay.h b/src/engine/platform/ay.h index c22929900..41c3c404e 100644 --- a/src/engine/platform/ay.h +++ b/src/engine/platform/ay.h @@ -26,6 +26,10 @@ class DivPlatformAY8910: public DivDispatch { protected: + const unsigned char AY8914RegRemap[16]={ + 0,4,1,5,2,6,9,8,11,12,13,3,7,10,14,15 + }; + inline unsigned char regRemap(unsigned char reg) { return intellivision?AY8914RegRemap[reg&0x0f]:reg&0x0f; } struct Channel { unsigned char freqH, freqL; int freq, baseFreq, note, pitch; @@ -60,7 +64,7 @@ class DivPlatformAY8910: public DivDispatch { int delay; bool extMode; - bool stereo, sunsoft; + bool stereo, sunsoft, intellivision; short oldWrites[16]; short pendingWrites[16]; diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 70e27dae8..5e57f6e36 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -419,10 +419,6 @@ const char* DivEngine::getSongSystemName() { return "Vectrex"; case 5: // AY-3-8910, 1MHz return "Amstrad CPC"; - case 6: // AY-3-8910, 0.somethingMhz - return "Intellivision"; - case 8: // AY-3-8910, 0.somethingMhz - return "Intellivision (PAL)"; case 0x10: // YM2149, 1.79MHz return "MSX"; @@ -432,7 +428,12 @@ const char* DivEngine::getSongSystemName() { return "Sunsoft 5B standalone"; case 0x28: // 5B PAL return "Sunsoft 5B standalone (PAL)"; - + + case 0x30: // AY-3-8914, 1.79MHz + return "Intellivision"; + case 0x33: // AY-3-8914, 2MHz + return "Intellivision (PAL)"; + default: if ((song.systemFlags[0]&0x30)==0x00) { return "AY-3-8910"; @@ -440,6 +441,8 @@ const char* DivEngine::getSongSystemName() { return "Yamaha YM2149"; } else if ((song.systemFlags[0]&0x30)==0x20) { return "Overclocked Sunsoft 5B"; + } else if ((song.systemFlags[0]&0x30)==0x30) { + return "Intellivision"; } } } else if (song.system[0]==DIV_SYSTEM_SMS) { diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index e79029e37..850fbf742 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -5116,6 +5116,10 @@ bool FurnaceGUI::loop() { e->setSysFlags(i,(flags&(~0x30))|32,restart); updateWindowTitle(); } + if (ImGui::RadioButton("AY-3-8914",(flags&0x30)==48)) { + e->setSysFlags(i,(flags&(~0x30))|48,restart); + updateWindowTitle(); + } } bool stereo=flags&0x40; ImGui::BeginDisabled((flags&0x30)==32); @@ -6684,7 +6688,7 @@ FurnaceGUI::FurnaceGUI(): )); cat.systems.push_back(FurnaceGUISysDef( "Mattel Intellivision", { - DIV_SYSTEM_AY8910, 64, 0, 6, + DIV_SYSTEM_AY8910, 64, 0, 48, 0 } )); From 5fadcf4891d9fcadc06d66dc4000aa648bece34c Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 11 Mar 2022 16:53:46 -0500 Subject: [PATCH 197/637] GUI: fix transpose octave range --- src/gui/gui.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index e79029e37..1cc6165c0 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2833,10 +2833,13 @@ void FurnaceGUI::doTranspose(int amount) { origNote+=12; origOctave--; } - // FIX!!!!! TODO - if (origOctave>7) { - origNote=12; - origOctave=7; + if (origOctave==9 && origNote>11) { + origNote=11; + origOctave=9; + } + if (origOctave>9) { + origNote=11; + origOctave=9; } if (origOctave<-5) { origNote=1; From ab3884e5aa3357d4478f07d4dcd325a59e73d75a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 11 Mar 2022 16:58:43 -0500 Subject: [PATCH 198/637] clamp wave data issue #267 --- src/engine/platform/gb.cpp | 8 ++++++-- src/engine/platform/pce.cpp | 5 ++++- src/engine/platform/swan.cpp | 8 ++++++-- src/engine/platform/x1_010.cpp | 5 ++++- 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp index 942032e32..ea6ad7719 100644 --- a/src/engine/platform/gb.cpp +++ b/src/engine/platform/gb.cpp @@ -97,8 +97,12 @@ void DivPlatformGB::updateWave() { if (wt->max<1 || wt->len<1) { rWrite(0x30+i,0); } else { - unsigned char nibble1=15-((wt->data[(i*2)*wt->len/32]*15)/wt->max); - unsigned char nibble2=15-((wt->data[(1+i*2)*wt->len/32]*15)/wt->max); + int nibble1=15-((wt->data[(i*2)*wt->len/32]*15)/wt->max); + int nibble2=15-((wt->data[(1+i*2)*wt->len/32]*15)/wt->max); + if (nibble1<0) nibble1=0; + if (nibble1>15) nibble1=15; + if (nibble2<0) nibble2=0; + if (nibble2>15) nibble2=15; rWrite(0x30+i,(nibble1<<4)|nibble2); } } diff --git a/src/engine/platform/pce.cpp b/src/engine/platform/pce.cpp index 296eafc41..1af1741c5 100644 --- a/src/engine/platform/pce.cpp +++ b/src/engine/platform/pce.cpp @@ -138,7 +138,10 @@ void DivPlatformPCE::updateWave(int ch) { if (wt->max<1 || wt->len<1) { chWrite(ch,0x06,0); } else { - chWrite(ch,0x06,wt->data[i*wt->len/32]*31/wt->max); + int data=wt->data[i*wt->len/32]*31/wt->max; + if (data<0) data=0; + if (data>31) data=31; + chWrite(ch,0x06,data); } } if (chan[ch].active) { diff --git a/src/engine/platform/swan.cpp b/src/engine/platform/swan.cpp index 219871804..de6236478 100644 --- a/src/engine/platform/swan.cpp +++ b/src/engine/platform/swan.cpp @@ -119,8 +119,12 @@ void DivPlatformSwan::updateWave(int ch) { } } else { for (int i=0; i<16; i++) { - unsigned char nibble1=(wt->data[(i*2)*wt->len/32]*15)/wt->max; - unsigned char nibble2=(wt->data[(1+i*2)*wt->len/32]*15)/wt->max; + int nibble1=(wt->data[(i*2)*wt->len/32]*15)/wt->max; + int nibble2=(wt->data[(1+i*2)*wt->len/32]*15)/wt->max; + if (nibble1<0) nibble1=0; + if (nibble1>15) nibble1=15; + if (nibble2<0) nibble2=0; + if (nibble2>15) nibble2=15; rWrite(addr+i,nibble1|(nibble2<<4)); } } diff --git a/src/engine/platform/x1_010.cpp b/src/engine/platform/x1_010.cpp index 42853855a..1782deb9d 100644 --- a/src/engine/platform/x1_010.cpp +++ b/src/engine/platform/x1_010.cpp @@ -282,7 +282,10 @@ void DivPlatformX1_010::updateWave(int ch) { if (wt->max<1 || wt->len<1) { waveWrite(ch,i,0); } else { - waveWrite(ch,i,wt->data[i*wt->len/128]*255/wt->max); + int data=wt->data[i*wt->len/128]*255/wt->max; + if (data<0) data=0; + if (data>255) data=255; + waveWrite(ch,i,data); } } if (!chan[ch].pcm) { From cb3c4e23021de6179e9e7005c9b57f7c5ed82702 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 11 Mar 2022 17:00:57 -0500 Subject: [PATCH 199/637] GUI: clamp waves to max value --- src/gui/insEdit.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index c8eec6763..e653b097f 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1885,6 +1885,7 @@ void FurnaceGUI::drawWaveEdit() { modified=true; } for (int i=0; ilen; i++) { + if (wave->data[i]>wave->max) wave->data[i]=wave->max; wavePreview[i]=wave->data[i]; } if (wave->len>0) wavePreview[wave->len]=wave->data[wave->len-1]; From 07d15643c262524be780ba346fcb45cb7b2fbeb7 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 11 Mar 2022 17:56:10 -0500 Subject: [PATCH 200/637] GUI: implement paste flood --- src/gui/gui.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 1cc6165c0..657aa9e4a 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -3014,6 +3014,10 @@ void FurnaceGUI::doPaste(PasteMode mode) { break; } j++; + + if (mode==GUI_PASTE_MODE_FLOOD && i==data.size()-1) { + i=1; + } } makeUndo(GUI_UNDO_PATTERN_PASTE); From 155e602e61c57fa4e25f64bdcaecb78097740faf Mon Sep 17 00:00:00 2001 From: cam900 Date: Sat, 12 Mar 2022 10:22:21 +0900 Subject: [PATCH 201/637] Fix X1-010 VGM logging Register/RAM offset is Big endian --- src/engine/vgmOps.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index 069d5a2ef..2518b9faa 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -157,7 +157,7 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write case DIV_SYSTEM_X1_010: for (int i=0; i<16; i++) { w->writeC(0xc8); - w->writeS(baseAddr2S+(i<<3)); + w->writeS_BE(baseAddr2S+(i<<3)); w->writeC(0); } break; @@ -404,7 +404,7 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write break; case DIV_SYSTEM_X1_010: w->writeC(0xc8); - w->writeS(baseAddr2S|(write.addr&0x1fff)); + w->writeS_BE(baseAddr2S|(write.addr&0x1fff)); w->writeC(write.val); break; case DIV_SYSTEM_YM2610: From 2643d6b0ee3d4c69f5d9d9a4d82e4b971c89cabd Mon Sep 17 00:00:00 2001 From: cam900 Date: Sat, 12 Mar 2022 11:32:19 +0900 Subject: [PATCH 202/637] Clamp X1-010 Envelope wave --- src/engine/platform/x1_010.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/engine/platform/x1_010.cpp b/src/engine/platform/x1_010.cpp index 1782deb9d..8d1338e14 100644 --- a/src/engine/platform/x1_010.cpp +++ b/src/engine/platform/x1_010.cpp @@ -323,9 +323,15 @@ void DivPlatformX1_010::updateEnvelope(int ch) { } if (chan[ch].env.flag.envVinvR) { ro=15-ro; } // vertical invert right envelope if (chan[ch].env.flag.envVinvL) { lo=15-lo; } // vertical invert left envelope + if (lo<0) lo=0; + if (lo>15) lo=15; + if (ro<0) ro=0; + if (ro>15) ro=15; envWrite(ch,i,lo,ro); } else { int out=wt->data[i*wt->len/128]*15/wt->max; + if (out<0) out=0; + if (out>15) out=15; envWrite(ch,i,out,out); } } From 2a0aa19b2b1b9e329ee2bf732f5faae5aabb70d4 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 11 Mar 2022 22:11:33 -0500 Subject: [PATCH 203/637] fix broken DAC mode adds new compat flag --- papers/format.md | 4 +++- src/engine/engine.h | 4 ++-- src/engine/fileOps.cpp | 14 ++++++++++++-- src/engine/platform/genesis.cpp | 12 ++++++++++++ src/engine/song.h | 4 +++- src/gui/gui.cpp | 4 ++++ 6 files changed, 36 insertions(+), 6 deletions(-) diff --git a/papers/format.md b/papers/format.md index 7bc30abe0..882c5d8c9 100644 --- a/papers/format.md +++ b/papers/format.md @@ -29,6 +29,7 @@ furthermore, an `or reserved` indicates this field is always present, but is res the format versions are: +- 64: Furnace dev64 - 63: Furnace dev63 - 62: Furnace dev62 - 61: Furnace dev61 @@ -203,7 +204,8 @@ size | description 1 | ignore duplicate slides (>=50) or reserved 1 | stop portamento on note off (>=62) or reserved 1 | continuous vibrato (>=62) or reserved - 4 | reserved + 1 | broken DAC mode (>=64) or reserved + 3 | reserved 4?? | pointers to instruments 4?? | pointers to wavetables 4?? | pointers to samples diff --git a/src/engine/engine.h b/src/engine/engine.h index d16a987cb..29d5a7795 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -37,8 +37,8 @@ warnings+=(String("\n")+x); \ } -#define DIV_VERSION "dev63" -#define DIV_ENGINE_VERSION 63 +#define DIV_VERSION "dev64" +#define DIV_ENGINE_VERSION 64 enum DivStatusView { DIV_STATUS_NOTHING=0, diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 3a79cebed..9a927fdd2 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -140,6 +140,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { ds.algMacroBehavior=false; ds.brokenShortcutSlides=false; ds.ignoreDuplicateSlides=true; + ds.brokenDACMode=true; // 1.1 compat flags if (ds.version>24) { @@ -799,6 +800,9 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { if (ds.version<62) { ds.stopPortaOnNoteOff=true; } + if (ds.version<64) { + ds.brokenDACMode=false; + } ds.isDMF=false; reader.readS(); // reserved @@ -975,7 +979,12 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { reader.readC(); reader.readC(); } - for (int i=0; i<4; i++) reader.readC(); + if (ds.version>=64) { + ds.brokenDACMode=reader.readC(); + } else { + reader.readC(); + } + for (int i=0; i<3; i++) reader.readC(); } else { for (int i=0; i<20; i++) reader.readC(); } @@ -1417,7 +1426,8 @@ SafeWriter* DivEngine::saveFur() { w->writeC(song.ignoreDuplicateSlides); w->writeC(song.stopPortaOnNoteOff); w->writeC(song.continuousVibrato); - for (int i=0; i<4; i++) { + w->writeC(song.brokenDACMode); + for (int i=0; i<3; i++) { w->writeC(0); } diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index 16c44ae5c..aefde827f 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -99,6 +99,9 @@ void DivPlatformGenesis::acquire_nuked(short* bufL, short* bufR, size_t start, s dacPos=s->loopStart; } else { dacSample=-1; + if (parent->song.brokenDACMode) { + rWrite(0x2b,0); + } } } dacPeriod+=MAX(40,dacRate); @@ -163,6 +166,9 @@ void DivPlatformGenesis::acquire_ymfm(short* bufL, short* bufR, size_t start, si dacPos=s->loopStart; } else { dacSample=-1; + if (parent->song.brokenDACMode) { + rWrite(0x2b,0); + } } } dacPeriod+=MAX(40,dacRate); @@ -460,6 +466,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) { if (dumpWrites) addWrite(0xffff0002,0); break; } else { + rWrite(0x2b,1<<7); if (dumpWrites) addWrite(0xffff0000,dacSample); } dacPos=0; @@ -477,6 +484,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) { if (dumpWrites) addWrite(0xffff0002,0); break; } else { + rWrite(0x2b,1<<7); if (dumpWrites) addWrite(0xffff0000,dacSample); } dacPos=0; @@ -541,6 +549,10 @@ int DivPlatformGenesis::dispatch(DivCommand c) { if (c.chan==5) { dacSample=-1; if (dumpWrites) addWrite(0xffff0002,0); + if (parent->song.brokenDACMode) { + rWrite(0x2b,0); + break; + } } chan[c.chan].keyOff=true; chan[c.chan].keyOn=false; diff --git a/src/engine/song.h b/src/engine/song.h index 2eb4fe5d2..44b027320 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -295,6 +295,7 @@ struct DivSong { bool ignoreDuplicateSlides; bool stopPortaOnNoteOff; bool continuousVibrato; + bool brokenDACMode; DivOrders orders; std::vector ins; @@ -359,7 +360,8 @@ struct DivSong { brokenShortcutSlides(false), ignoreDuplicateSlides(false), stopPortaOnNoteOff(false), - continuousVibrato(false) { + continuousVibrato(false), + brokenDACMode(false) { for (int i=0; i<32; i++) { system[i]=DIV_SYSTEM_NULL; systemVol[i]=64; diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 657aa9e4a..a1f176fa1 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2115,6 +2115,10 @@ void FurnaceGUI::drawCompatFlags() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("when enabled, vibrato will not be reset on a new note."); } + ImGui::Checkbox("Broken DAC mode",&e->song.brokenDACMode); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("when enabled, the DAC in YM2612 will be disabled if there isn't any sample playing."); + } ImGui::Text("Loop modality:"); if (ImGui::RadioButton("Reset channels",e->song.loopModality==0)) { From 2103f249fb747a89fe0ac1cabf4e53e9a8cfb087 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 11 Mar 2022 22:33:22 -0500 Subject: [PATCH 204/637] C64: fix note/env release cutting note --- src/engine/playback.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index f96474a70..7bee3c411 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -1064,8 +1064,11 @@ void DivEngine::nextRow() { for (int i=0; idata[curRow][0]==0 && pat->data[curRow][1]==0)) { - if (pat->data[curRow][0]!=100) { - if (!chan[i].legato) dispatchCmd(DivCommand(DIV_CMD_PRE_NOTE,i,ticks)); + if (pat->data[curRow][0]!=100 && pat->data[curRow][0]!=101 && pat->data[curRow][0]!=102) { + if (!chan[i].legato) { + dispatchCmd(DivCommand(DIV_CMD_PRE_NOTE,i,ticks)); + //chan[i].cut=ticks; + } } } } From 716298c49cac4463cf1115e13d4404322404c2d2 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 11 Mar 2022 22:41:04 -0500 Subject: [PATCH 205/637] Genesis: now fix off not working on channel 6 --- src/engine/platform/genesis.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index aefde827f..b3096e72a 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -551,7 +551,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) { if (dumpWrites) addWrite(0xffff0002,0); if (parent->song.brokenDACMode) { rWrite(0x2b,0); - break; + if (dacMode) break; } } chan[c.chan].keyOff=true; From cf07e1861ef32c3bf93b96e6c4634b50e673ee30 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 11 Mar 2022 23:01:18 -0500 Subject: [PATCH 206/637] add "auto-insert one tick gap" option --- papers/format.md | 4 +++- src/engine/engine.h | 4 ++-- src/engine/fileOps.cpp | 14 ++++++++++++-- src/engine/playback.cpp | 19 ++++++++++++++++++- src/engine/song.h | 4 +++- src/gui/gui.cpp | 6 +++++- 6 files changed, 43 insertions(+), 8 deletions(-) diff --git a/papers/format.md b/papers/format.md index 882c5d8c9..b04deac74 100644 --- a/papers/format.md +++ b/papers/format.md @@ -29,6 +29,7 @@ furthermore, an `or reserved` indicates this field is always present, but is res the format versions are: +- 65: Furnace dev65 - 64: Furnace dev64 - 63: Furnace dev63 - 62: Furnace dev62 @@ -205,7 +206,8 @@ size | description 1 | stop portamento on note off (>=62) or reserved 1 | continuous vibrato (>=62) or reserved 1 | broken DAC mode (>=64) or reserved - 3 | reserved + 1 | one tick cut (>=65) or reserved + 2 | reserved 4?? | pointers to instruments 4?? | pointers to wavetables 4?? | pointers to samples diff --git a/src/engine/engine.h b/src/engine/engine.h index 29d5a7795..fc70c8730 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -37,8 +37,8 @@ warnings+=(String("\n")+x); \ } -#define DIV_VERSION "dev64" -#define DIV_ENGINE_VERSION 64 +#define DIV_VERSION "dev65" +#define DIV_ENGINE_VERSION 65 enum DivStatusView { DIV_STATUS_NOTHING=0, diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 9a927fdd2..3c1a9d2a1 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -141,6 +141,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { ds.brokenShortcutSlides=false; ds.ignoreDuplicateSlides=true; ds.brokenDACMode=true; + ds.oneTickCut=false; // 1.1 compat flags if (ds.version>24) { @@ -803,6 +804,9 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { if (ds.version<64) { ds.brokenDACMode=false; } + if (ds.version<65) { + ds.oneTickCut=false; + } ds.isDMF=false; reader.readS(); // reserved @@ -984,7 +988,12 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { } else { reader.readC(); } - for (int i=0; i<3; i++) reader.readC(); + if (ds.version>=65) { + ds.oneTickCut=reader.readC(); + } else { + reader.readC(); + } + for (int i=0; i<2; i++) reader.readC(); } else { for (int i=0; i<20; i++) reader.readC(); } @@ -1427,7 +1436,8 @@ SafeWriter* DivEngine::saveFur() { w->writeC(song.stopPortaOnNoteOff); w->writeC(song.continuousVibrato); w->writeC(song.brokenDACMode); - for (int i=0; i<3; i++) { + w->writeC(song.oneTickCut); + for (int i=0; i<2; i++) { w->writeC(0); } diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 7bee3c411..4626635b0 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -1067,7 +1067,24 @@ void DivEngine::nextRow() { if (pat->data[curRow][0]!=100 && pat->data[curRow][0]!=101 && pat->data[curRow][0]!=102) { if (!chan[i].legato) { dispatchCmd(DivCommand(DIV_CMD_PRE_NOTE,i,ticks)); - //chan[i].cut=ticks; + + if (song.oneTickCut) { + bool doPrepareCut=true; + + for (int j=0; jdata[curRow][4+(j<<1)]==0x03) { + doPrepareCut=false; + break; + } + if (pat->data[curRow][4+(j<<1)]==0xea) { + if (pat->data[curRow][5+(j<<1)]>0) { + doPrepareCut=false; + break; + } + } + } + if (doPrepareCut) chan[i].cut=ticks; + } } } } diff --git a/src/engine/song.h b/src/engine/song.h index 44b027320..5747aa511 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -296,6 +296,7 @@ struct DivSong { bool stopPortaOnNoteOff; bool continuousVibrato; bool brokenDACMode; + bool oneTickCut; DivOrders orders; std::vector ins; @@ -361,7 +362,8 @@ struct DivSong { ignoreDuplicateSlides(false), stopPortaOnNoteOff(false), continuousVibrato(false), - brokenDACMode(false) { + brokenDACMode(false), + oneTickCut(false) { for (int i=0; i<32; i++) { system[i]=DIV_SYSTEM_NULL; systemVol[i]=64; diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index a1f176fa1..88c945bdd 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2069,7 +2069,7 @@ void FurnaceGUI::drawCompatFlags() { } if (!compatFlagsOpen) return; if (ImGui::Begin("Compatibility Flags",&compatFlagsOpen)) { - ImGui::TextWrapped("these flags are stored in the song when saving in .fur format, and are automatically enabled when saving in .dmf format."); + ImGui::TextWrapped("these flags are designed to provide better DefleMask/older Furnace compatibility."); ImGui::Checkbox("Limit slide range",&e->song.limitSlides); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("when enabled, slides are limited to a compatible range.\nmay cause problems with slides in negative octaves."); @@ -2119,6 +2119,10 @@ void FurnaceGUI::drawCompatFlags() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("when enabled, the DAC in YM2612 will be disabled if there isn't any sample playing."); } + ImGui::Checkbox("Auto-insert one tick gap between notes",&e->song.oneTickCut); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("when enabled, a one-tick note cut will be inserted between non-legato/non-portamento notes.\nthis simulates the behavior of some Amiga/SNES music engines."); + } ImGui::Text("Loop modality:"); if (ImGui::RadioButton("Reset channels",e->song.loopModality==0)) { From fe9b379ca93e52a1d60904d9cd7371bc7755c3d0 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 11 Mar 2022 23:47:16 -0500 Subject: [PATCH 207/637] GUI: implement paste mix --- src/gui/gui.cpp | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 88c945bdd..6fd31c409 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2985,9 +2985,15 @@ void FurnaceGUI::doPaste(PasteMode mode) { note[2]=line[charPos++]; note[3]=0; - if (!decodeNote(note,pat->data[j][0],pat->data[j][1])) { - invalidData=true; - break; + if ((mode==GUI_PASTE_MODE_MIX_BG || mode==GUI_PASTE_MODE_MIX_FG) && strcmp(note,"...")==0) { + // do nothing. + } else { + if (mode!=GUI_PASTE_MODE_MIX_BG || (pat->data[j][0]==0 && pat->data[j][1]==0)) { + if (!decodeNote(note,pat->data[j][0],pat->data[j][1])) { + invalidData=true; + break; + } + } } } else { if (charPos>=line.size()) { @@ -3003,14 +3009,18 @@ void FurnaceGUI::doPaste(PasteMode mode) { note[2]=0; if (strcmp(note,"..")==0) { - pat->data[j][iFine+1]=-1; + if (!(mode==GUI_PASTE_MODE_MIX_BG || mode==GUI_PASTE_MODE_MIX_FG)) { + pat->data[j][iFine+1]=-1; + } } else { unsigned int val=0; if (sscanf(note,"%2X",&val)!=1) { invalidData=true; break; } - if (iFine<(3+e->song.pat[iCoarse].effectRows*2)) pat->data[j][iFine+1]=val; + if (mode!=GUI_PASTE_MODE_MIX_BG || pat->data[j][iFine+1]==-1) { + if (iFine<(3+e->song.pat[iCoarse].effectRows*2)) pat->data[j][iFine+1]=val; + } } } iFine++; From f52d919240f0915112469926ae187d7fa4577542 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 11 Mar 2022 23:50:59 -0500 Subject: [PATCH 208/637] GUI: implement paste overflow --- src/gui/gui.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 6fd31c409..d8e61f3b7 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -3032,6 +3032,10 @@ void FurnaceGUI::doPaste(PasteMode mode) { break; } j++; + if (mode==GUI_PASTE_MODE_OVERFLOW && j>=e->song.patLen && ordsong.ordersLen-1) { + j=0; + ord++; + } if (mode==GUI_PASTE_MODE_FLOOD && i==data.size()-1) { i=1; From 3e890a391b975a02601062a6001a850563bcf8ce Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 12 Mar 2022 02:13:15 -0500 Subject: [PATCH 209/637] GUI: operation mask this means transpose also works on non-note columns! --- src/gui/gui.cpp | 190 +++++++++++++++++++++++++++++++++++++++++++++--- src/gui/gui.h | 7 +- 2 files changed, 187 insertions(+), 10 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index d8e61f3b7..2d3342fe8 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2718,6 +2718,19 @@ void FurnaceGUI::doSelectAll() { } } +#define maskOut(x) \ + if (x==0) { \ + if (!opMaskNote) continue; \ + } else if (x==1) { \ + if (!opMaskIns) continue; \ + } else if (x==2) { \ + if (!opMaskVol) continue; \ + } else if (((x)&1)==0) { \ + if (!opMaskEffectVal) continue; \ + } else if (((x)&1)==1) { \ + if (!opMaskEffect) continue; \ + } + void FurnaceGUI::doDelete() { finishSelection(); prepareUndo(GUI_UNDO_PATTERN_DELETE); @@ -2730,6 +2743,7 @@ void FurnaceGUI::doDelete() { if (!e->song.chanShow[iCoarse]) continue; DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsedata[j][iFine]=0; @@ -2763,6 +2777,7 @@ void FurnaceGUI::doPullDelete() { if (!e->song.chanShow[iCoarse]) continue; DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsesong.patLen; j++) { if (jsong.patLen-1) { if (iFine==0) { @@ -2795,6 +2810,7 @@ void FurnaceGUI::doInsert() { if (!e->song.chanShow[iCoarse]) continue; DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsesong.patLen-1; j>=selStart.y; j--) { if (j==selStart.y) { if (iFine==0) { @@ -2827,6 +2843,7 @@ void FurnaceGUI::doTranspose(int amount) { if (!e->song.chanShow[iCoarse]) continue; DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsedata[j][0]; @@ -2856,6 +2873,16 @@ void FurnaceGUI::doTranspose(int amount) { pat->data[j][0]=origNote; pat->data[j][1]=(unsigned char)origOctave; } + } else { + int top=255; + if (iFine==1) { + if (e->song.ins.empty()) continue; + top=e->song.ins.size()-1; + } else if (iFine==2) { // volume + top=e->getMaxVolumeChan(iCoarse); + } + if (pat->data[j][iFine+1]==-1) continue; + pat->data[j][iFine+1]=MIN(top,MAX(0,pat->data[j][iFine+1]+amount)); } } } @@ -2985,6 +3012,11 @@ void FurnaceGUI::doPaste(PasteMode mode) { note[2]=line[charPos++]; note[3]=0; + if (iFine==0 && !opMaskNote) { + iFine++; + continue; + } + if ((mode==GUI_PASTE_MODE_MIX_BG || mode==GUI_PASTE_MODE_MIX_FG) && strcmp(note,"...")==0) { // do nothing. } else { @@ -3008,6 +3040,28 @@ void FurnaceGUI::doPaste(PasteMode mode) { note[1]=line[charPos++]; note[2]=0; + if (iFine==1) { + if (!opMaskIns) { + iFine++; + continue; + } + } else if (iFine==2) { + if (!opMaskVol) { + iFine++; + continue; + } + } else if ((iFine&1)==0) { + if (!opMaskEffectVal) { + iFine++; + continue; + } + } else if ((iFine&1)==1) { + if (!opMaskEffect) { + iFine++; + continue; + } + } + if (strcmp(note,"..")==0) { if (!(mode==GUI_PASTE_MODE_MIX_BG || mode==GUI_PASTE_MODE_MIX_FG)) { pat->data[j][iFine+1]=-1; @@ -3071,6 +3125,7 @@ void FurnaceGUI::doInterpolate() { makeUndo(GUI_UNDO_PATTERN_INTERPOLATE); } +// huh?! void FurnaceGUI::doFade(bool fadeIn) { finishSelection(); prepareUndo(fadeIn?GUI_UNDO_PATTERN_FADE_IN:GUI_UNDO_PATTERN_FADE_OUT); @@ -3082,9 +3137,73 @@ void FurnaceGUI::doInvertValues() { finishSelection(); prepareUndo(GUI_UNDO_PATTERN_INVERT_VAL); + int iCoarse=selStart.xCoarse; + int iFine=selStart.xFine; + int ord=e->getOrder(); + for (; iCoarse<=selEnd.xCoarse; iCoarse++) { + if (!e->song.chanShow[iCoarse]) continue; + DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); + for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsesong.ins.empty()) continue; + top=e->song.ins.size()-1; + } else if (iFine==2) { // volume + top=e->getMaxVolumeChan(iCoarse); + } + for (int j=selStart.y; j<=selEnd.y; j++) { + if (pat->data[j][iFine+1]==-1) continue; + pat->data[j][iFine+1]=top-pat->data[j][iFine+1]; + } + } + } + iFine=0; + } + makeUndo(GUI_UNDO_PATTERN_INVERT_VAL); } +void FurnaceGUI::doScale(float top) { + finishSelection(); + prepareUndo(GUI_UNDO_PATTERN_SCALE); + + int iCoarse=selStart.xCoarse; + int iFine=selStart.xFine; + int ord=e->getOrder(); + for (; iCoarse<=selEnd.xCoarse; iCoarse++) { + if (!e->song.chanShow[iCoarse]) continue; + DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); + for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsesong.ins.empty()) continue; + absoluteTop=e->song.ins.size()-1; + } else if (iFine==2) { // volume + absoluteTop=e->getMaxVolumeChan(iCoarse); + } + for (int j=selStart.y; j<=selEnd.y; j++) { + if (pat->data[j][iFine+1]==-1) continue; + pat->data[j][iFine+1]=MIN(absoluteTop,(double)pat->data[j][iFine+1]*(top/100.0f)); + } + } + } + iFine=0; + } + + makeUndo(GUI_UNDO_PATTERN_SCALE); +} + +void FurnaceGUI::doRandomize(int bottom, int top) { + finishSelection(); + prepareUndo(GUI_UNDO_PATTERN_RANDOMIZE); + + makeUndo(GUI_UNDO_PATTERN_RANDOMIZE); +} + void FurnaceGUI::doFlip() { finishSelection(); prepareUndo(GUI_UNDO_PATTERN_FLIP); @@ -4594,6 +4713,44 @@ void FurnaceGUI::editOptions(bool topMenu) { } ImGui::Separator(); + ImGui::Text("operation mask"); + + ImGui::PushFont(patFont); + if (ImGui::BeginTable("opMaskTable",5,ImGuiTableFlags_Borders|ImGuiTableFlags_SizingFixedFit|ImGuiTableFlags_NoHostExtendX)) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_ACTIVE]); + if (ImGui::Selectable(opMaskNote?"C-4##opMaskNote":"---##opMaskNote",opMaskNote,ImGuiSelectableFlags_DontClosePopups)) { + opMaskNote=!opMaskNote; + } + ImGui::PopStyleColor(); + ImGui::TableNextColumn(); + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_INS]); + if (ImGui::Selectable(opMaskIns?"01##opMaskIns":"--##opMaskIns",opMaskIns,ImGuiSelectableFlags_DontClosePopups)) { + opMaskIns=!opMaskIns; + } + ImGui::PopStyleColor(); + ImGui::TableNextColumn(); + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_VOLUME_MAX]); + if (ImGui::Selectable(opMaskVol?"7F##opMaskVol":"--##opMaskVol",opMaskVol,ImGuiSelectableFlags_DontClosePopups)) { + opMaskVol=!opMaskVol; + } + ImGui::PopStyleColor(); + ImGui::TableNextColumn(); + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_PITCH]); + if (ImGui::Selectable(opMaskEffect?"04##opMaskEffect":"--##opMaskEffect",opMaskEffect,ImGuiSelectableFlags_DontClosePopups)) { + opMaskEffect=!opMaskEffect; + } + ImGui::TableNextColumn(); + if (ImGui::Selectable(opMaskEffectVal?"72##opMaskEffectVal":"--##opMaskEffectVal",opMaskEffectVal,ImGuiSelectableFlags_DontClosePopups)) { + opMaskEffectVal=!opMaskEffectVal; + } + ImGui::PopStyleColor(); + ImGui::EndTable(); + } + ImGui::PopFont(); + + ImGui::Text("input latch"); if (ImGui::MenuItem("set latch",BIND_FOR(GUI_ACTION_PAT_LATCH))) { // TODO } @@ -4630,23 +4787,29 @@ void FurnaceGUI::editOptions(bool topMenu) { ImGui::EndMenu(); } if (ImGui::BeginMenu("scale...")) { - if (ImGui::InputFloat("Bottom",&scaleMin,1,1,"%.1f%%")) { - if (scaleMin<0.0f) scaleMin=0.0f; - if (scaleMin>100.0f) scaleMin=100.0f; - } - if (ImGui::InputFloat("Top",&scaleMax,1,1,"%.1f%%")) { + if (ImGui::InputFloat("##ScaleMax",&scaleMax,1,1,"%.1f%%")) { if (scaleMax<0.0f) scaleMax=0.0f; - if (scaleMax>100.0f) scaleMax=100.0f; + if (scaleMax>25600.0f) scaleMax=25600.0f; } if (ImGui::Button("Scale")) { + doScale(scaleMax); ImGui::CloseCurrentPopup(); } ImGui::EndMenu(); } if (ImGui::BeginMenu("randomize...")) { - ImGui::InputInt("Minimum",&randomizeMin,1,1); - ImGui::InputInt("Maximum",&randomizeMax,1,1); + if (ImGui::InputInt("Minimum",&randomizeMin,1,1)) { + if (randomizeMin<0) randomizeMin=0; + if (randomizeMin>255) randomizeMin=255; + if (randomizeMin>randomizeMax) randomizeMin=randomizeMax; + } + if (ImGui::InputInt("Maximum",&randomizeMax,1,1)) { + if (randomizeMax<0) randomizeMax=0; + if (randomizeMax255) randomizeMax=255; + } if (ImGui::Button("Randomize")) { + doRandomize(randomizeMin,randomizeMax); ImGui::CloseCurrentPopup(); } ImGui::EndMenu(); @@ -6412,6 +6575,16 @@ FurnaceGUI::FurnaceGUI(): curWindow(GUI_WINDOW_NOTHING), nextWindow(GUI_WINDOW_NOTHING), nextDesc(NULL), + opMaskNote(true), + opMaskIns(true), + opMaskVol(true), + opMaskEffect(true), + opMaskEffectVal(true), + latchNote(-1), + latchIns(-1), + latchVol(-1), + latchEffect(-1), + latchEffectVal(-1), wavePreviewOn(false), wavePreviewKey((SDL_Scancode)0), wavePreviewNote(0), @@ -6456,7 +6629,6 @@ FurnaceGUI::FurnaceGUI(): transposeAmount(0), randomizeMin(0), randomizeMax(255), - scaleMin(0.0f), scaleMax(100.0f), oldOrdersLen(0) { diff --git a/src/gui/gui.h b/src/gui/gui.h index 17e240c8c..483066210 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -601,6 +601,9 @@ class FurnaceGUI { float patChanSlideY[DIV_MAX_CHANS+1]; const int* nextDesc; + bool opMaskNote, opMaskIns, opMaskVol, opMaskEffect, opMaskEffectVal; + short latchNote, latchIns, latchVol, latchEffect, latchEffectVal; + // bit 31: ctrl // bit 30: reserved for SDL scancode mask // bit 29: shift @@ -687,7 +690,7 @@ class FurnaceGUI { SelectionPoint sel1, sel2; int dummyRows, demandX; int transposeAmount, randomizeMin, randomizeMax; - float scaleMin, scaleMax; + float scaleMax; int oldOrdersLen; DivOrders oldOrders; @@ -761,6 +764,8 @@ class FurnaceGUI { void doInterpolate(); void doFade(bool fadeIn); void doInvertValues(); + void doScale(float top); + void doRandomize(int bottom, int top); void doFlip(); void doCollapse(int divider); void doExpand(int multiplier); From a6eec9f7c4296598050060fffa44fbe09c7630a5 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 12 Mar 2022 02:24:23 -0500 Subject: [PATCH 210/637] GUI: implement randomize --- src/gui/gui.cpp | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 2d3342fe8..e48034947 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -3201,6 +3201,34 @@ void FurnaceGUI::doRandomize(int bottom, int top) { finishSelection(); prepareUndo(GUI_UNDO_PATTERN_RANDOMIZE); + int iCoarse=selStart.xCoarse; + int iFine=selStart.xFine; + int ord=e->getOrder(); + for (; iCoarse<=selEnd.xCoarse; iCoarse++) { + if (!e->song.chanShow[iCoarse]) continue; + DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); + for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsesong.ins.empty()) continue; + absoluteTop=e->song.ins.size()-1; + } else if (iFine==2) { // volume + absoluteTop=e->getMaxVolumeChan(iCoarse); + } + for (int j=selStart.y; j<=selEnd.y; j++) { + if (top-bottom<=0) { + pat->data[j][iFine+1]=MIN(absoluteTop,bottom); + } else { + pat->data[j][iFine+1]=MIN(absoluteTop,bottom+(rand()%(top-bottom))); + } + } + } + } + iFine=0; + } + makeUndo(GUI_UNDO_PATTERN_RANDOMIZE); } From 1f058ac653721f5e83166e1b0f0ee2ff5453887d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 12 Mar 2022 02:32:02 -0500 Subject: [PATCH 211/637] GUI: add move cursor by edit step on insert option --- src/gui/gui.cpp | 3 +++ src/gui/gui.h | 2 ++ src/gui/settings.cpp | 9 +++++++++ 3 files changed, 14 insertions(+) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index e48034947..a6fedaa4f 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -3769,6 +3769,9 @@ void FurnaceGUI::doAction(int what) { break; case GUI_ACTION_PAT_INSERT: doInsert(); + if (settings.stepOnInsert) { + moveCursor(0,editStep,false); + } break; case GUI_ACTION_PAT_MUTE_CURSOR: if (cursor.xCoarse<0 || cursor.xCoarse>=e->getTotalChannelCount()) break; diff --git a/src/gui/gui.h b/src/gui/gui.h index 483066210..65b3c34e8 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -535,6 +535,7 @@ class FurnaceGUI { int guiColorsBase; int avoidRaisingPattern; int insFocusesPattern; + int stepOnInsert; unsigned int maxUndoSteps; String mainFontPath; String patFontPath; @@ -578,6 +579,7 @@ class FurnaceGUI { guiColorsBase(0), avoidRaisingPattern(0), insFocusesPattern(1), + stepOnInsert(0), maxUndoSteps(100), mainFontPath(""), patFontPath(""), diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index f3ef2f0ea..2d018399b 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -157,6 +157,11 @@ void FurnaceGUI::drawSettings() { settings.stepOnDelete=stepOnDeleteB; } + bool stepOnInsertB=settings.stepOnInsert; + if (ImGui::Checkbox("Move cursor by edit step on insert (push)",&stepOnInsertB)) { + settings.stepOnInsert=stepOnInsertB; + } + bool allowEditDockingB=settings.allowEditDocking; if (ImGui::Checkbox("Allow docking editors",&allowEditDockingB)) { settings.allowEditDocking=allowEditDockingB; @@ -206,6 +211,7 @@ void FurnaceGUI::drawSettings() { if (ImGui::RadioButton("Move by Edit Step##cmk1",settings.scrollStep==1)) { settings.scrollStep=1; } + ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Audio")) { @@ -885,6 +891,7 @@ void FurnaceGUI::syncSettings() { settings.guiColorsBase=e->getConfInt("guiColorsBase",0); settings.avoidRaisingPattern=e->getConfInt("avoidRaisingPattern",0); settings.insFocusesPattern=e->getConfInt("insFocusesPattern",1); + settings.stepOnInsert=e->getConfInt("stepOnInsert",0); clampSetting(settings.mainFontSize,2,96); clampSetting(settings.patFontSize,2,96); @@ -922,6 +929,7 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.guiColorsBase,0,1); clampSetting(settings.avoidRaisingPattern,0,1); clampSetting(settings.insFocusesPattern,0,1); + clampSetting(settings.stepOnInsert,0,1); // keybinds LOAD_KEYBIND(GUI_ACTION_OPEN,FURKMOD_CMD|SDLK_o); @@ -1120,6 +1128,7 @@ void FurnaceGUI::commitSettings() { e->setConf("guiColorsBase",settings.guiColorsBase); e->setConf("avoidRaisingPattern",settings.avoidRaisingPattern); e->setConf("insFocusesPattern",settings.insFocusesPattern); + e->setConf("stepOnInsert",settings.stepOnInsert); PUT_UI_COLOR(GUI_COLOR_BACKGROUND); PUT_UI_COLOR(GUI_COLOR_FRAME_BACKGROUND); From e82b1e6a67adaa91bcdbd65324cd0b70e94f0599 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 12 Mar 2022 03:04:16 -0500 Subject: [PATCH 212/637] GUI: don't allow right-click menu movement --- src/gui/pattern.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/pattern.cpp b/src/gui/pattern.cpp index b2ff56cec..f2ff7e100 100644 --- a/src/gui/pattern.cpp +++ b/src/gui/pattern.cpp @@ -812,7 +812,7 @@ void FurnaceGUI::drawPattern() { ImGui::PopStyleVar(); if (patternOpen) { if (!inhibitMenu && ImGui::IsItemClicked(ImGuiMouseButton_Right)) ImGui::OpenPopup("patternActionMenu"); - if (ImGui::BeginPopup("patternActionMenu",ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings)) { + if (ImGui::BeginPopup("patternActionMenu",ImGuiWindowFlags_NoMove|ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings)) { editOptions(false); ImGui::EndPopup(); } From de604bdf01a21397d38f6b793c29b4404c16f9c5 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 12 Mar 2022 03:04:34 -0500 Subject: [PATCH 213/637] GUI: add gradient/fade edit option --- src/gui/gui.cpp | 96 ++++++++++++++++++++++++++++++++++++++----------- src/gui/gui.h | 11 +++--- 2 files changed, 81 insertions(+), 26 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index a6fedaa4f..812428bfd 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2593,8 +2593,7 @@ void FurnaceGUI::prepareUndo(ActionType action) { case GUI_UNDO_PATTERN_PASTE: case GUI_UNDO_PATTERN_CHANGE_INS: case GUI_UNDO_PATTERN_INTERPOLATE: - case GUI_UNDO_PATTERN_FADE_IN: - case GUI_UNDO_PATTERN_FADE_OUT: + case GUI_UNDO_PATTERN_FADE: case GUI_UNDO_PATTERN_SCALE: case GUI_UNDO_PATTERN_RANDOMIZE: case GUI_UNDO_PATTERN_INVERT_VAL: @@ -2644,8 +2643,7 @@ void FurnaceGUI::makeUndo(ActionType action) { case GUI_UNDO_PATTERN_PASTE: case GUI_UNDO_PATTERN_CHANGE_INS: case GUI_UNDO_PATTERN_INTERPOLATE: - case GUI_UNDO_PATTERN_FADE_IN: - case GUI_UNDO_PATTERN_FADE_OUT: + case GUI_UNDO_PATTERN_FADE: case GUI_UNDO_PATTERN_SCALE: case GUI_UNDO_PATTERN_RANDOMIZE: case GUI_UNDO_PATTERN_INVERT_VAL: @@ -3125,12 +3123,43 @@ void FurnaceGUI::doInterpolate() { makeUndo(GUI_UNDO_PATTERN_INTERPOLATE); } -// huh?! -void FurnaceGUI::doFade(bool fadeIn) { +void FurnaceGUI::doFade(int p0, int p1, bool mode) { finishSelection(); - prepareUndo(fadeIn?GUI_UNDO_PATTERN_FADE_IN:GUI_UNDO_PATTERN_FADE_OUT); + prepareUndo(GUI_UNDO_PATTERN_FADE); - makeUndo(fadeIn?GUI_UNDO_PATTERN_FADE_IN:GUI_UNDO_PATTERN_FADE_OUT); + int iCoarse=selStart.xCoarse; + int iFine=selStart.xFine; + int ord=e->getOrder(); + for (; iCoarse<=selEnd.xCoarse; iCoarse++) { + if (!e->song.chanShow[iCoarse]) continue; + DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); + for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsesong.ins.empty()) continue; + absoluteTop=e->song.ins.size()-1; + } else if (iFine==2) { // volume + absoluteTop=e->getMaxVolumeChan(iCoarse); + } + if (selEnd.y-selStart.y<1) continue; + for (int j=selStart.y; j<=selEnd.y; j++) { + double fraction=double(j-selStart.y)/double(selEnd.y-selStart.y); + int value=p0+double(p1-p0)*fraction; + if (mode) { // nibble + value&=15; + pat->data[j][iFine+1]=MIN(absoluteTop,value|(value<<4)); + } else { // byte + pat->data[j][iFine+1]=MIN(absoluteTop,value); + } + } + } + } + iFine=0; + } + + makeUndo(GUI_UNDO_PATTERN_FADE); } void FurnaceGUI::doInvertValues() { @@ -3274,8 +3303,7 @@ void FurnaceGUI::doUndo() { case GUI_UNDO_PATTERN_PASTE: case GUI_UNDO_PATTERN_CHANGE_INS: case GUI_UNDO_PATTERN_INTERPOLATE: - case GUI_UNDO_PATTERN_FADE_IN: - case GUI_UNDO_PATTERN_FADE_OUT: + case GUI_UNDO_PATTERN_FADE: case GUI_UNDO_PATTERN_SCALE: case GUI_UNDO_PATTERN_RANDOMIZE: case GUI_UNDO_PATTERN_INVERT_VAL: @@ -3321,8 +3349,7 @@ void FurnaceGUI::doRedo() { case GUI_UNDO_PATTERN_PASTE: case GUI_UNDO_PATTERN_CHANGE_INS: case GUI_UNDO_PATTERN_INTERPOLATE: - case GUI_UNDO_PATTERN_FADE_IN: - case GUI_UNDO_PATTERN_FADE_OUT: + case GUI_UNDO_PATTERN_FADE: case GUI_UNDO_PATTERN_SCALE: case GUI_UNDO_PATTERN_RANDOMIZE: case GUI_UNDO_PATTERN_INVERT_VAL: @@ -3811,12 +3838,6 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_PAT_INTERPOLATE: doInterpolate(); break; - case GUI_ACTION_PAT_FADE_IN: - doFade(true); - break; - case GUI_ACTION_PAT_FADE_OUT: - doFade(false); - break; case GUI_ACTION_PAT_INVERT_VALUES: doInvertValues(); break; @@ -4745,6 +4766,7 @@ void FurnaceGUI::editOptions(bool topMenu) { ImGui::Separator(); ImGui::Text("operation mask"); + ImGui::SameLine(); ImGui::PushFont(patFont); if (ImGui::BeginTable("opMaskTable",5,ImGuiTableFlags_Borders|ImGuiTableFlags_SizingFixedFit|ImGuiTableFlags_NoHostExtendX)) { @@ -4803,8 +4825,6 @@ void FurnaceGUI::editOptions(bool topMenu) { ImGui::Separator(); if (ImGui::MenuItem("interpolate",BIND_FOR(GUI_ACTION_PAT_INTERPOLATE))) doInterpolate(); - if (ImGui::MenuItem("fade in",BIND_FOR(GUI_ACTION_PAT_FADE_IN))) doFade(true); - if (ImGui::MenuItem("fade out",BIND_FOR(GUI_ACTION_PAT_FADE_OUT))) doFade(false); if (ImGui::BeginMenu("change instrument...")) { if (e->song.ins.empty()) { ImGui::Text("no instruments available"); @@ -4817,6 +4837,38 @@ void FurnaceGUI::editOptions(bool topMenu) { } ImGui::EndMenu(); } + if (ImGui::BeginMenu("gradient/fade...")) { + if (ImGui::InputInt("Start",&fadeMin,1,1)) { + if (fadeMin<0) fadeMin=0; + if (fadeMode) { + if (fadeMin>15) fadeMin=15; + } else { + if (fadeMin>255) fadeMin=255; + } + } + if (ImGui::InputInt("End",&fadeMax,1,1)) { + if (fadeMax<0) fadeMax=0; + if (fadeMode) { + if (fadeMax>15) fadeMax=15; + } else { + if (fadeMax>255) fadeMax=255; + } + } + if (ImGui::Checkbox("Nibble mode",&fadeMode)) { + if (fadeMode) { + if (fadeMin>15) fadeMin=15; + if (fadeMax>15) fadeMax=15; + } else { + if (fadeMin>255) fadeMin=255; + if (fadeMax>255) fadeMax=255; + } + } + if (ImGui::Button("Go ahead")) { + doFade(fadeMin,fadeMax,fadeMode); + ImGui::CloseCurrentPopup(); + } + ImGui::EndMenu(); + } if (ImGui::BeginMenu("scale...")) { if (ImGui::InputFloat("##ScaleMax",&scaleMax,1,1,"%.1f%%")) { if (scaleMax<0.0f) scaleMax=0.0f; @@ -4839,6 +4891,7 @@ void FurnaceGUI::editOptions(bool topMenu) { if (randomizeMax255) randomizeMax=255; } + // TODO: add an option to set effect to specific value? if (ImGui::Button("Randomize")) { doRandomize(randomizeMin,randomizeMax); ImGui::CloseCurrentPopup(); @@ -6660,7 +6713,10 @@ FurnaceGUI::FurnaceGUI(): transposeAmount(0), randomizeMin(0), randomizeMax(255), + fadeMin(0), + fadeMax(255), scaleMax(100.0f), + fadeMode(false), oldOrdersLen(0) { // octave 1 diff --git a/src/gui/gui.h b/src/gui/gui.h index 65b3c34e8..16312423a 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -275,8 +275,7 @@ enum FurnaceGUIActions { GUI_ACTION_PAT_INCREASE_COLUMNS, GUI_ACTION_PAT_DECREASE_COLUMNS, GUI_ACTION_PAT_INTERPOLATE, - GUI_ACTION_PAT_FADE_IN, - GUI_ACTION_PAT_FADE_OUT, + GUI_ACTION_PAT_FADE, GUI_ACTION_PAT_INVERT_VALUES, GUI_ACTION_PAT_FLIP_SELECTION, GUI_ACTION_PAT_COLLAPSE_ROWS, @@ -383,8 +382,7 @@ enum ActionType { GUI_UNDO_PATTERN_PASTE, GUI_UNDO_PATTERN_CHANGE_INS, GUI_UNDO_PATTERN_INTERPOLATE, - GUI_UNDO_PATTERN_FADE_IN, - GUI_UNDO_PATTERN_FADE_OUT, + GUI_UNDO_PATTERN_FADE, GUI_UNDO_PATTERN_SCALE, GUI_UNDO_PATTERN_RANDOMIZE, GUI_UNDO_PATTERN_INVERT_VAL, @@ -691,8 +689,9 @@ class FurnaceGUI { ImVec2 threeChars, twoChars; SelectionPoint sel1, sel2; int dummyRows, demandX; - int transposeAmount, randomizeMin, randomizeMax; + int transposeAmount, randomizeMin, randomizeMax, fadeMin, fadeMax; float scaleMax; + bool fadeMode; int oldOrdersLen; DivOrders oldOrders; @@ -764,7 +763,7 @@ class FurnaceGUI { void doPaste(PasteMode mode=GUI_PASTE_MODE_NORMAL); void doChangeIns(int ins); void doInterpolate(); - void doFade(bool fadeIn); + void doFade(int p0, int p1, bool mode); void doInvertValues(); void doScale(float top); void doRandomize(int bottom, int top); From 7971b7323b4d37d831db929f3b195b26ec75aaa3 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 12 Mar 2022 03:40:56 -0500 Subject: [PATCH 214/637] GUI: add nibble mode to randomize --- src/gui/gui.cpp | 41 +++++++++++++++++++++++++++++++++++------ src/gui/gui.h | 4 ++-- 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 812428bfd..4da41cb35 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -3226,7 +3226,7 @@ void FurnaceGUI::doScale(float top) { makeUndo(GUI_UNDO_PATTERN_SCALE); } -void FurnaceGUI::doRandomize(int bottom, int top) { +void FurnaceGUI::doRandomize(int bottom, int top, bool mode) { finishSelection(); prepareUndo(GUI_UNDO_PATTERN_RANDOMIZE); @@ -3247,10 +3247,21 @@ void FurnaceGUI::doRandomize(int bottom, int top) { absoluteTop=e->getMaxVolumeChan(iCoarse); } for (int j=selStart.y; j<=selEnd.y; j++) { + int value=0; + int value2=0; if (top-bottom<=0) { - pat->data[j][iFine+1]=MIN(absoluteTop,bottom); + value=MIN(absoluteTop,bottom); + value2=MIN(absoluteTop,bottom); } else { - pat->data[j][iFine+1]=MIN(absoluteTop,bottom+(rand()%(top-bottom))); + value=MIN(absoluteTop,bottom+(rand()%(top-bottom+1))); + value2=MIN(absoluteTop,bottom+(rand()%(top-bottom+1))); + } + if (mode) { + value&=15; + value2&=15; + pat->data[j][iFine+1]=value|(value2<<4); + } else { + pat->data[j][iFine+1]=value; } } } @@ -4883,17 +4894,34 @@ void FurnaceGUI::editOptions(bool topMenu) { if (ImGui::BeginMenu("randomize...")) { if (ImGui::InputInt("Minimum",&randomizeMin,1,1)) { if (randomizeMin<0) randomizeMin=0; - if (randomizeMin>255) randomizeMin=255; + if (randomMode) { + if (randomizeMin>15) randomizeMin=15; + } else { + if (randomizeMin>255) randomizeMin=255; + } if (randomizeMin>randomizeMax) randomizeMin=randomizeMax; } if (ImGui::InputInt("Maximum",&randomizeMax,1,1)) { if (randomizeMax<0) randomizeMax=0; if (randomizeMax255) randomizeMax=255; + if (randomMode) { + if (randomizeMax>15) randomizeMax=15; + } else { + if (randomizeMax>255) randomizeMax=255; + } + } + if (ImGui::Checkbox("Nibble mode",&randomMode)) { + if (randomMode) { + if (randomizeMin>15) randomizeMin=15; + if (randomizeMax>15) randomizeMax=15; + } else { + if (randomizeMin>255) randomizeMin=255; + if (randomizeMax>255) randomizeMax=255; + } } // TODO: add an option to set effect to specific value? if (ImGui::Button("Randomize")) { - doRandomize(randomizeMin,randomizeMax); + doRandomize(randomizeMin,randomizeMax,randomMode); ImGui::CloseCurrentPopup(); } ImGui::EndMenu(); @@ -6717,6 +6745,7 @@ FurnaceGUI::FurnaceGUI(): fadeMax(255), scaleMax(100.0f), fadeMode(false), + randomMode(false), oldOrdersLen(0) { // octave 1 diff --git a/src/gui/gui.h b/src/gui/gui.h index 16312423a..e1e414f83 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -691,7 +691,7 @@ class FurnaceGUI { int dummyRows, demandX; int transposeAmount, randomizeMin, randomizeMax, fadeMin, fadeMax; float scaleMax; - bool fadeMode; + bool fadeMode, randomMode; int oldOrdersLen; DivOrders oldOrders; @@ -766,7 +766,7 @@ class FurnaceGUI { void doFade(int p0, int p1, bool mode); void doInvertValues(); void doScale(float top); - void doRandomize(int bottom, int top); + void doRandomize(int bottom, int top, bool mode); void doFlip(); void doCollapse(int divider); void doExpand(int multiplier); From e775703c445cd8d0b4bb9abe6434ec2ba0a58aff Mon Sep 17 00:00:00 2001 From: Waldemar Pawlaszek Date: Sat, 12 Mar 2022 12:16:01 +0100 Subject: [PATCH 215/637] Lynx panning swap --- src/engine/platform/lynx.cpp | 2 +- src/engine/platform/sound/lynx/Mikey.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/engine/platform/lynx.cpp b/src/engine/platform/lynx.cpp index 872cd99c5..dc0df8a65 100644 --- a/src/engine/platform/lynx.cpp +++ b/src/engine/platform/lynx.cpp @@ -235,7 +235,7 @@ int DivPlatformLynx::dispatch(DivCommand c) { } break; case DIV_CMD_PANNING: - chan[c.chan].pan=((c.value&0x0f)<<4)|((c.value&0xf0)>>4); + chan[c.chan].pan=c.value; WRITE_ATTEN(c.chan,chan[c.chan].pan); break; case DIV_CMD_GET_VOLUME: diff --git a/src/engine/platform/sound/lynx/Mikey.cpp b/src/engine/platform/sound/lynx/Mikey.cpp index ad112baf1..5d12bbb86 100644 --- a/src/engine/platform/sound/lynx/Mikey.cpp +++ b/src/engine/platform/sound/lynx/Mikey.cpp @@ -435,8 +435,8 @@ public: case ATTENREG2: case ATTENREG3: mRegisterPool[8*4+idx] = value; - mAttenuationLeft[idx] = ( value & 0x0f ) << 2; - mAttenuationRight[idx] = ( value & 0xf0 ) >> 2; + mAttenuationRight[idx] = ( value & 0x0f ) << 2; + mAttenuationLeft[idx] = ( value & 0xf0 ) >> 2; break; case MPAN: mPan = value; From c778251f262b361dfaafc15e880495633f41e279 Mon Sep 17 00:00:00 2001 From: cam900 Date: Sat, 12 Mar 2022 23:39:38 +0900 Subject: [PATCH 216/637] Oops! It's already exists --- src/engine/vgmOps.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index 2518b9faa..6133fff62 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -509,7 +509,6 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) { int hasOPM=0; int hasSegaPCM=0; int segaPCMOffset=0xf8000d; - int hasX1010=0; int hasRFC=0; int hasOPN=0; int hasOPNA=0; @@ -666,14 +665,14 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) { } break; case DIV_SYSTEM_X1_010: - if (!hasX1010) { - hasX1010=disCont[i].dispatch->chipClock; + if (!hasX1) { + hasX1=disCont[i].dispatch->chipClock; willExport[i]=true; writeX1010=true; - } else if (!(hasX1010&0x40000000)) { + } else if (!(hasX1&0x40000000)) { isSecond[i]=true; willExport[i]=true; - hasX1010|=0x40000000; + hasX1|=0x40000000; howManyChips++; } break; From ab8bace7f4e04496ad0693c05b42e30cdfb9a3be Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 12 Mar 2022 12:53:53 -0500 Subject: [PATCH 217/637] change default SAA1099 core to SAASound --- src/engine/dispatchContainer.cpp | 2 +- src/gui/gui.h | 2 +- src/gui/settings.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index da37565b9..ca2d99ce2 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -239,7 +239,7 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do ((DivPlatformOPL*)dispatch)->setOPLType(3,true); break; case DIV_SYSTEM_SAA1099: { - int saaCore=eng->getConfInt("saaCore",0); + int saaCore=eng->getConfInt("saaCore",1); if (saaCore<0 || saaCore>2) saaCore=0; dispatch=new DivPlatformSAA1099; ((DivPlatformSAA1099*)dispatch)->setCore((DivSAACores)saaCore); diff --git a/src/gui/gui.h b/src/gui/gui.h index e1e414f83..0aec32566 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -547,7 +547,7 @@ class FurnaceGUI { audioQuality(0), arcadeCore(0), ym2612Core(0), - saaCore(0), + saaCore(1), mainFont(0), patFont(0), audioRate(44100), diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 2d018399b..c8f6763b4 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -861,7 +861,7 @@ void FurnaceGUI::syncSettings() { settings.audioRate=e->getConfInt("audioRate",44100); settings.arcadeCore=e->getConfInt("arcadeCore",0); settings.ym2612Core=e->getConfInt("ym2612Core",0); - settings.saaCore=e->getConfInt("saaCore",0); + settings.saaCore=e->getConfInt("saaCore",1); settings.mainFont=e->getConfInt("mainFont",0); settings.patFont=e->getConfInt("patFont",0); settings.mainFontPath=e->getConfString("mainFontPath",""); From adafb49be71207128dc8fa62455a02504a22cbd8 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 12 Mar 2022 15:12:39 -0500 Subject: [PATCH 218/637] GUI: prepare for interpolate --- src/gui/gui.cpp | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 4da41cb35..59115f4a7 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -3120,6 +3120,27 @@ void FurnaceGUI::doInterpolate() { finishSelection(); prepareUndo(GUI_UNDO_PATTERN_INTERPOLATE); + std::vector> points; + int iCoarse=selStart.xCoarse; + int iFine=selStart.xFine; + int ord=e->getOrder(); + for (; iCoarse<=selEnd.xCoarse; iCoarse++) { + if (!e->song.chanShow[iCoarse]) continue; + DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); + for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsedata[j][iFine+1]!=-1) { + points.emplace(points.end(),j,pat->data[j][iFine+1]); + } + } + } + } + iFine=0; + } + makeUndo(GUI_UNDO_PATTERN_INTERPOLATE); } From a0c658f1d3bafd996f21bee072893790644ce0fe Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 12 Mar 2022 21:06:47 -0500 Subject: [PATCH 219/637] GUI: implement interpolate values --- src/gui/gui.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 59115f4a7..d979b4d50 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -3136,6 +3136,15 @@ void FurnaceGUI::doInterpolate() { points.emplace(points.end(),j,pat->data[j][iFine+1]); } } + + if (points.size()>1) for (size_t j=0; j& curPoint=points[j]; + std::pair& nextPoint=points[j+1]; + double distance=nextPoint.first-curPoint.first; + for (int k=0; k<(nextPoint.first-curPoint.first); k++) { + pat->data[k+curPoint.first][iFine+1]=curPoint.second+((nextPoint.second-curPoint.second)*(double)k/distance); + } + } } } iFine=0; From bd705d837db220a1b23f765e3662f612cea0ac19 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 12 Mar 2022 21:13:42 -0500 Subject: [PATCH 220/637] interpolate now works on notes --- src/gui/gui.cpp | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index d979b4d50..2220299f8 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -3145,6 +3145,30 @@ void FurnaceGUI::doInterpolate() { pat->data[k+curPoint.first][iFine+1]=curPoint.second+((nextPoint.second-curPoint.second)*(double)k/distance); } } + } else { + for (int j=selStart.y; j<=selEnd.y; j++) { + if (pat->data[j][0]!=0 && pat->data[j][1]!=0) { + if (pat->data[j][0]!=100 && pat->data[j][0]!=101 && pat->data[j][0]!=102) { + points.emplace(points.end(),j,pat->data[j][0]+pat->data[j][1]*12); + } + } + } + + if (points.size()>1) for (size_t j=0; j& curPoint=points[j]; + std::pair& nextPoint=points[j+1]; + double distance=nextPoint.first-curPoint.first; + for (int k=0; k<(nextPoint.first-curPoint.first); k++) { + int val=curPoint.second+((nextPoint.second-curPoint.second)*(double)k/distance); + pat->data[k+curPoint.first][0]=val%12; + pat->data[k+curPoint.first][1]=val/12; + if (pat->data[k+curPoint.first][0]==0) { + pat->data[k+curPoint.first][0]=12; + pat->data[k+curPoint.first][1]--; + } + pat->data[k+curPoint.first][1]&=255; + } + } } } iFine=0; From cd42a8b9f38f62816e5418aecdebc63c7faefae0 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 13 Mar 2022 02:36:52 -0500 Subject: [PATCH 221/637] GUI: implement flip --- src/gui/gui.cpp | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 2220299f8..3b77476d6 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -3330,6 +3330,31 @@ void FurnaceGUI::doFlip() { finishSelection(); prepareUndo(GUI_UNDO_PATTERN_FLIP); + DivPattern patBuffer; + int iCoarse=selStart.xCoarse; + int iFine=selStart.xFine; + int ord=e->getOrder(); + for (; iCoarse<=selEnd.xCoarse; iCoarse++) { + if (!e->song.chanShow[iCoarse]) continue; + DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); + for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsedata[j][0]; + } + patBuffer.data[j][iFine+1]=pat->data[j][iFine+1]; + } + for (int j=selStart.y; j<=selEnd.y; j++) { + if (iFine==0) { + pat->data[j][0]=patBuffer.data[selEnd.y-j+selStart.y][0]; + } + pat->data[j][iFine+1]=patBuffer.data[selEnd.y-j+selStart.y][iFine+1]; + } + } + iFine=0; + } + makeUndo(GUI_UNDO_PATTERN_FLIP); } From 6167feaf18b6e431560beeb341884c8d8c6c4de7 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 13 Mar 2022 03:13:11 -0500 Subject: [PATCH 222/637] GUI: implement shrink and expand! yay ONE MORE THING!!!!! then O P L --- src/gui/gui.cpp | 86 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 3b77476d6..4b46d3868 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -3362,13 +3362,99 @@ void FurnaceGUI::doCollapse(int divider) { finishSelection(); prepareUndo(GUI_UNDO_PATTERN_COLLAPSE); + DivPattern patBuffer; + int iCoarse=selStart.xCoarse; + int iFine=selStart.xFine; + int ord=e->getOrder(); + for (; iCoarse<=selEnd.xCoarse; iCoarse++) { + if (!e->song.chanShow[iCoarse]) continue; + DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); + for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsedata[j][0]; + } + patBuffer.data[j][iFine+1]=pat->data[j][iFine+1]; + } + for (int j=0; j<=selEnd.y-selStart.y; j++) { + if (j*divider>=selEnd.y-selStart.y) { + if (iFine==0) { + pat->data[j+selStart.y][0]=0; + pat->data[j+selStart.y][1]=0; + } else { + pat->data[j+selStart.y][iFine+1]=-1; + } + } else { + if (iFine==0) { + pat->data[j+selStart.y][0]=patBuffer.data[j*divider+selStart.y][0]; + } + pat->data[j+selStart.y][iFine+1]=patBuffer.data[j*divider+selStart.y][iFine+1]; + + if (iFine==0) { + for (int k=1; k=selEnd.y-selStart.y) break; + if (!(pat->data[j+selStart.y][0]==0 && pat->data[j+selStart.y][1]==0)) break; + pat->data[j+selStart.y][0]=patBuffer.data[j*divider+selStart.y+k][0]; + pat->data[j+selStart.y][1]=patBuffer.data[j*divider+selStart.y+k][1]; + } + } else { + for (int k=1; k=selEnd.y-selStart.y) break; + if (pat->data[j+selStart.y][iFine+1]!=-1) break; + pat->data[j+selStart.y][iFine+1]=patBuffer.data[j*divider+selStart.y+k][iFine+1]; + } + } + } + } + } + iFine=0; + } + makeUndo(GUI_UNDO_PATTERN_COLLAPSE); } void FurnaceGUI::doExpand(int multiplier) { + if (multiplier<1) return; + finishSelection(); prepareUndo(GUI_UNDO_PATTERN_EXPAND); + DivPattern patBuffer; + int iCoarse=selStart.xCoarse; + int iFine=selStart.xFine; + int ord=e->getOrder(); + for (; iCoarse<=selEnd.xCoarse; iCoarse++) { + if (!e->song.chanShow[iCoarse]) continue; + DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); + for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsedata[j][0]; + } + patBuffer.data[j][iFine+1]=pat->data[j][iFine+1]; + } + for (int j=0; j<=(selEnd.y-selStart.y)*multiplier; j++) { + if ((j+selStart.y)>=e->song.patLen) break; + if ((j%multiplier)!=0) { + if (iFine==0) { + pat->data[j+selStart.y][0]=0; + pat->data[j+selStart.y][1]=0; + } else { + pat->data[j+selStart.y][iFine+1]=-1; + } + continue; + } + if (iFine==0) { + pat->data[j+selStart.y][0]=patBuffer.data[j/multiplier+selStart.y][0]; + } + pat->data[j+selStart.y][iFine+1]=patBuffer.data[j/multiplier+selStart.y][iFine+1]; + } + } + iFine=0; + } + makeUndo(GUI_UNDO_PATTERN_EXPAND); } From a41736cc89b6559e8434a298a9c8e455934d3090 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 13 Mar 2022 04:51:05 -0500 Subject: [PATCH 223/637] GUI: partially implement note input latch the UI for it is missing --- src/gui/gui.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 4b46d3868..8c7981865 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4332,6 +4332,7 @@ void FurnaceGUI::keyDown(SDL_Event& ev) { int num=12*curOctave+key; if (edit) { + // TODO: separate when adding MIDI input. DivPattern* pat=e->song.pat[cursor.xCoarse].getPattern(e->song.orders.ord[cursor.xCoarse][e->getOrder()],true); prepareUndo(GUI_UNDO_PATTERN_EDIT); @@ -4353,7 +4354,17 @@ void FurnaceGUI::keyDown(SDL_Event& ev) { pat->data[cursor.y][1]--; } pat->data[cursor.y][1]=(unsigned char)pat->data[cursor.y][1]; - pat->data[cursor.y][2]=curIns; + if (latchIns==-2) { + pat->data[cursor.y][2]=curIns; + } else if (latchIns!=-1 && !e->song.ins.empty()) { + pat->data[cursor.y][2]=MIN(((int)e->song.ins.size())-1,latchIns); + } + if (latchVol!=-1) { + int maxVol=e->getMaxVolumeChan(cursor.xCoarse); + pat->data[cursor.y][3]=MIN(maxVol,latchVol); + } + if (latchEffect!=-1) pat->data[cursor.y][4]=latchEffect; + if (latchEffectVal!=-1) pat->data[cursor.y][5]=latchEffectVal; previewNote(cursor.xCoarse,num); } makeUndo(GUI_UNDO_PATTERN_EDIT); @@ -6858,7 +6869,7 @@ FurnaceGUI::FurnaceGUI(): opMaskEffect(true), opMaskEffectVal(true), latchNote(-1), - latchIns(-1), + latchIns(-2), latchVol(-1), latchEffect(-1), latchEffectVal(-1), From 57631ac056252f114fc65aeec89affb6149fbfc7 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 13 Mar 2022 16:56:22 -0500 Subject: [PATCH 224/637] update release scripts --- scripts/release-win32.sh | 3 ++- scripts/release-win64.sh | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/scripts/release-win32.sh b/scripts/release-win32.sh index d2c086d2e..215575a2b 100755 --- a/scripts/release-win32.sh +++ b/scripts/release-win32.sh @@ -1,6 +1,6 @@ #!/bin/bash # make Windows release -# this script shall be run from Linux with MinGW installed! +# this script shall be run from Arch Linux with MinGW installed! if [ ! -e /tmp/furnace ]; then ln -s "$PWD" /tmp/furnace || exit 1 @@ -14,6 +14,7 @@ fi cd win32build +# TODO: potential Arch-ism? i686-w64-mingw32-cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS="-O2" -DCMAKE_CXX_FLAGS="-O2 -Wall -Wextra -Wno-unused-parameter -Werror" -DBUILD_SHARED_LIBS=OFF .. || exit 1 make -j8 || exit 1 i686-w64-mingw32-strip -s furnace.exe || exit 1 diff --git a/scripts/release-win64.sh b/scripts/release-win64.sh index 69bc602aa..f0dfc4d2c 100755 --- a/scripts/release-win64.sh +++ b/scripts/release-win64.sh @@ -1,6 +1,6 @@ #!/bin/bash # make Windows release -# this script shall be run from Linux with MinGW installed! +# this script shall be run from Arch Linux with MinGW installed! if [ ! -e /tmp/furnace ]; then ln -s "$PWD" /tmp/furnace || exit 1 @@ -14,6 +14,7 @@ fi cd winbuild +# TODO: potential Arch-ism? x86_64-w64-mingw32-cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS="-O2" -DCMAKE_CXX_FLAGS="-O2 -Wall -Wextra -Wno-unused-parameter -Werror" .. || exit 1 make -j8 || exit 1 x86_64-w64-mingw32-strip -s furnace.exe || exit 1 From 6bca34725420dfa4a81c4db8800d7ccfc6207bb4 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 13 Mar 2022 17:29:43 -0500 Subject: [PATCH 225/637] maybe BUG --- extern/igfd/ImGuiFileDialog.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/extern/igfd/ImGuiFileDialog.cpp b/extern/igfd/ImGuiFileDialog.cpp index 5be457930..3980bcd3e 100644 --- a/extern/igfd/ImGuiFileDialog.cpp +++ b/extern/igfd/ImGuiFileDialog.cpp @@ -3877,6 +3877,7 @@ namespace IGFD static ImGuiSelectableFlags selectableFlags = ImGuiSelectableFlags_AllowDoubleClick | ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_SpanAvailWidth; + // TODO BUG?! va_list args; va_start(args, vFmt); vsnprintf(fdi.puVariadicBuffer, MAX_FILE_DIALOG_NAME_BUFFER, vFmt, args); @@ -4074,6 +4075,7 @@ namespace IGFD if (ImGui::TableNextColumn()) // file name { + // TODO BUG?!?!?! needToBreakTheloop = prSelectableItem(i, infos, selected, _str.c_str()); if (needToBreakTheloop==2) escape=true; } From 3be56d50ab4aa288e39e0f09a90a7d1641b6a7cc Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 13 Mar 2022 17:30:43 -0500 Subject: [PATCH 226/637] GUI: prepare for two things - unified ins/wave/sample view - macro line drawing --- src/gui/gui.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/gui/gui.h b/src/gui/gui.h index 0aec32566..dc985e8d0 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -534,6 +534,9 @@ class FurnaceGUI { int avoidRaisingPattern; int insFocusesPattern; int stepOnInsert; + // TODO flags + int unifiedDataView; + // end unsigned int maxUndoSteps; String mainFontPath; String patFontPath; @@ -663,6 +666,7 @@ class FurnaceGUI { bool macroDragInitialValueSet; bool macroDragInitialValue; bool macroDragChar; + bool macroDragLineMode; // TODO bool macroDragActive; ImVec2 macroLoopDragStart; From 5e77b474673a1ddcf691566167fd7e1b469fb71a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 13 Mar 2022 18:32:35 -0500 Subject: [PATCH 227/637] system file picker anyone? DO NOT COMPILE! --- .gitmodules | 3 ++ CMakeLists.txt | 1 + extern/pfd | 1 + src/gui/fileDialog.cpp | 109 +++++++++++++++++++++++++++++++++++++++++ src/gui/fileDialog.h | 31 ++++++++++++ src/gui/gui.cpp | 27 +++++----- src/gui/gui.h | 7 +++ src/gui/settings.cpp | 14 ++++++ 8 files changed, 180 insertions(+), 13 deletions(-) create mode 160000 extern/pfd create mode 100644 src/gui/fileDialog.cpp create mode 100644 src/gui/fileDialog.h diff --git a/.gitmodules b/.gitmodules index d63fd70b5..d08aa505b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,3 +22,6 @@ [submodule "extern/Nuked-OPL3"] path = extern/Nuked-OPL3 url = https://github.com/nukeykt/Nuked-OPL3.git +[submodule "extern/pfd"] + path = extern/pfd + url = https://github.com/samhocevar/portable-file-dialogs.git diff --git a/CMakeLists.txt b/CMakeLists.txt index de4eefbcc..99f1089b8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -347,6 +347,7 @@ src/gui/font_unifont.cpp src/gui/font_icon.cpp src/gui/fonts.cpp src/gui/debug.cpp +src/gui/fileDialog.cpp src/gui/intConst.cpp src/gui/guiConst.cpp diff --git a/extern/pfd b/extern/pfd new file mode 160000 index 000000000..dea8520de --- /dev/null +++ b/extern/pfd @@ -0,0 +1 @@ +Subproject commit dea8520de18af09eefdbc18aaf7c24409d18491b diff --git a/src/gui/fileDialog.cpp b/src/gui/fileDialog.cpp new file mode 100644 index 000000000..54b38739d --- /dev/null +++ b/src/gui/fileDialog.cpp @@ -0,0 +1,109 @@ +#include "fileDialog.h" +#include "ImGuiFileDialog.h" +#include "../../extern/pfd/portable-file-dialogs.h" + +bool FurnaceGUIFileDialog::openLoad(String header, std::vector filter, String path, double dpiScale) { + if (opened) return false; + saving=false; + curPath=path; + if (sysDialog) { + dialogO=new pfd::open_file(header,path,filter); + } else { + String parsedFilter; + if (filter.size()&1) return false; + + for (size_t i=0; iDpiScale=dpiScale; + ImGuiFileDialog::Instance()->OpenModal("FileDialog",header,parsedFilter.c_str(),path); + } + return true; +} + +bool FurnaceGUIFileDialog::openSave(String header, std::vector filter, String path, double dpiScale) { + curPath=path; + if (sysDialog) { + // TODO + } else { + String parsedFilter; + ImGuiFileDialog::Instance()->DpiScale=dpiScale; + ImGuiFileDialog::Instance()->OpenModal("FileDialog",header,parsedFilter.c_str(),path,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite); + } + return true; +} + +bool FurnaceGUIFileDialog::accepted() { + if (sysDialog) { + return (fileName!=""); + } else { + return ImGuiFileDialog::Instance()->IsOk(); + } +} + +void FurnaceGUIFileDialog::close() { + if (sysDialog) { + if (saving) { + if (dialogS!=NULL) { + delete dialogS; + dialogS=NULL; + } + } else { + if (dialogO!=NULL) { + delete dialogO; + dialogO=NULL; + printf("deleting\n"); + } + } + } else { + ImGuiFileDialog::Instance()->Close(); + } +} + +bool FurnaceGUIFileDialog::render(const ImVec2& min, const ImVec2& max) { + if (sysDialog) { + if (saving) { + if (dialogS!=NULL) { + if (dialogS->ready(1)) { + fileName=dialogS->result(); + printf("returning %s\n",fileName.c_str()); + return true; + } + } + } else { + if (dialogO!=NULL) { + if (dialogO->ready(1)) { + if (dialogO->result().empty()) { + fileName=""; + printf("returning nothing\n"); + } else { + fileName=dialogO->result()[0]; + printf("returning %s\n",fileName.c_str()); + } + return true; + } + } + } + return false; + } else { + return ImGuiFileDialog::Instance()->Display("FileDialog",ImGuiWindowFlags_NoCollapse|ImGuiWindowFlags_NoMove,min,max); + } +} + +String FurnaceGUIFileDialog::getPath() { + if (sysDialog) { + return curPath; + } else { + return ImGuiFileDialog::Instance()->GetCurrentPath(); + } +} + +String FurnaceGUIFileDialog::getFileName() { + if (sysDialog) { + return fileName; + } else { + return ImGuiFileDialog::Instance()->GetFilePathName(); + } +} \ No newline at end of file diff --git a/src/gui/fileDialog.h b/src/gui/fileDialog.h new file mode 100644 index 000000000..057bef1fa --- /dev/null +++ b/src/gui/fileDialog.h @@ -0,0 +1,31 @@ +#include "../ta-utils.h" +#include "imgui.h" + +namespace pfd { + class open_file; + class save_file; +} + +class FurnaceGUIFileDialog { + bool sysDialog; + bool opened; + bool saving; + String curPath; + String fileName; + pfd::open_file* dialogO; + pfd::save_file* dialogS; + public: + bool openLoad(String header, std::vector filter, String path, double dpiScale); + bool openSave(String header, std::vector filter, String path, double dpiScale); + bool accepted(); + void close(); + bool render(const ImVec2& min, const ImVec2& max); + String getPath(); + String getFileName(); + FurnaceGUIFileDialog(bool system): + sysDialog(system), + opened(false), + saving(false), + dialogO(NULL), + dialogS(NULL) {} +}; \ No newline at end of file diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index eff4cd2e7..fe53b9113 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4585,11 +4585,11 @@ bool dirExists(String what) { } void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { - ImGuiFileDialog::Instance()->DpiScale=dpiScale; switch (type) { case GUI_FILE_OPEN: if (!dirExists(workingDirSong)) workingDirSong=getHomeDir(); - ImGuiFileDialog::Instance()->OpenModal("FileDialog","Open File","compatible files{.fur,.dmf},.*",workingDirSong); + fileDialog->openLoad("Open File",{"compatible files", ".fur,.dmf", "all files", ".*"},workingDirSong,dpiScale); + //ImGuiFileDialog::Instance()->OpenModal("FileDialog","Open File","compatible files{.fur,.dmf},.*",workingDirSong); break; case GUI_FILE_SAVE: if (!dirExists(workingDirSong)) workingDirSong=getHomeDir(); @@ -5942,42 +5942,42 @@ bool FurnaceGUI::loop() { if (patternOpen) nextWindow=GUI_WINDOW_PATTERN; } - if (ImGuiFileDialog::Instance()->Display("FileDialog",ImGuiWindowFlags_NoCollapse|ImGuiWindowFlags_NoMove,ImVec2(600.0f*dpiScale,400.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale))) { + if (fileDialog->render(ImVec2(600.0f*dpiScale,400.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale))) { //ImGui::GetIO().ConfigFlags&=~ImGuiConfigFlags_NavEnableKeyboard; switch (curFileDialog) { case GUI_FILE_OPEN: case GUI_FILE_SAVE: case GUI_FILE_SAVE_DMF_LEGACY: - workingDirSong=ImGuiFileDialog::Instance()->GetCurrentPath()+DIR_SEPARATOR_STR; + workingDirSong=fileDialog->getPath()+DIR_SEPARATOR_STR; break; case GUI_FILE_INS_OPEN: case GUI_FILE_INS_SAVE: - workingDirIns=ImGuiFileDialog::Instance()->GetCurrentPath()+DIR_SEPARATOR_STR; + workingDirIns=fileDialog->getPath()+DIR_SEPARATOR_STR; break; case GUI_FILE_WAVE_OPEN: case GUI_FILE_WAVE_SAVE: - workingDirWave=ImGuiFileDialog::Instance()->GetCurrentPath()+DIR_SEPARATOR_STR; + workingDirWave=fileDialog->getPath()+DIR_SEPARATOR_STR; break; case GUI_FILE_SAMPLE_OPEN: case GUI_FILE_SAMPLE_SAVE: - workingDirSample=ImGuiFileDialog::Instance()->GetCurrentPath()+DIR_SEPARATOR_STR; + workingDirSample=fileDialog->getPath()+DIR_SEPARATOR_STR; break; case GUI_FILE_EXPORT_AUDIO_ONE: case GUI_FILE_EXPORT_AUDIO_PER_SYS: case GUI_FILE_EXPORT_AUDIO_PER_CHANNEL: - workingDirAudioExport=ImGuiFileDialog::Instance()->GetCurrentPath()+DIR_SEPARATOR_STR; + workingDirAudioExport=fileDialog->getPath()+DIR_SEPARATOR_STR; break; case GUI_FILE_EXPORT_VGM: case GUI_FILE_EXPORT_ROM: - workingDirVGMExport=ImGuiFileDialog::Instance()->GetCurrentPath()+DIR_SEPARATOR_STR; + workingDirVGMExport=fileDialog->getPath()+DIR_SEPARATOR_STR; break; case GUI_FILE_LOAD_MAIN_FONT: case GUI_FILE_LOAD_PAT_FONT: - workingDirFont=ImGuiFileDialog::Instance()->GetCurrentPath()+DIR_SEPARATOR_STR; + workingDirFont=fileDialog->getPath()+DIR_SEPARATOR_STR; break; } - if (ImGuiFileDialog::Instance()->IsOk()) { - fileName=ImGuiFileDialog::Instance()->GetFilePathName(); + if (fileDialog->accepted()) { + fileName=fileDialog->getFileName(); if (fileName!="") { if (curFileDialog==GUI_FILE_SAVE) { if (ImGuiFileDialog::Instance()->GetCurrentFilter()=="Furnace song") { @@ -6103,7 +6103,7 @@ bool FurnaceGUI::loop() { curFileDialog=GUI_FILE_OPEN; } } - ImGuiFileDialog::Instance()->Close(); + fileDialog->close(); } if (warnQuit) { @@ -6805,6 +6805,7 @@ FurnaceGUI::FurnaceGUI(): displayNew(false), curFileDialog(GUI_FILE_OPEN), warnAction(GUI_WARN_OPEN), + fileDialog(NULL), scrW(1280), scrH(800), dpiScale(1), diff --git a/src/gui/gui.h b/src/gui/gui.h index dc985e8d0..9bf68e529 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -27,6 +27,8 @@ #include #include +#include "fileDialog.h" + #define rightClickable if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) ImGui::SetKeyboardFocusHere(-1); #define handleUnimportant if (settings.insFocusesPattern && patternOpen) {nextWindow=GUI_WINDOW_PATTERN;} @@ -476,6 +478,8 @@ class FurnaceGUI { FurnaceGUIFileDialogs curFileDialog; FurnaceGUIWarnings warnAction; + FurnaceGUIFileDialog* fileDialog; + int scrW, scrH; double dpiScale; @@ -536,6 +540,7 @@ class FurnaceGUI { int stepOnInsert; // TODO flags int unifiedDataView; + int sysFileDialog; // end unsigned int maxUndoSteps; String mainFontPath; @@ -581,6 +586,8 @@ class FurnaceGUI { avoidRaisingPattern(0), insFocusesPattern(1), stepOnInsert(0), + unifiedDataView(0), + sysFileDialog(0), maxUndoSteps(100), mainFontPath(""), patFontPath(""), diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index c8f6763b4..eb09e86fa 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -182,6 +182,11 @@ void FurnaceGUI::drawSettings() { settings.restartOnFlagChange=restartOnFlagChangeB; } + bool sysFileDialogB=settings.sysFileDialog; + if (ImGui::Checkbox("Use system file picker",&sysFileDialogB)) { + settings.sysFileDialog=sysFileDialogB; + } + ImGui::Text("Wrap pattern cursor horizontally:"); if (ImGui::RadioButton("No##wrapH0",settings.wrapHorizontal==0)) { settings.wrapHorizontal=0; @@ -892,6 +897,8 @@ void FurnaceGUI::syncSettings() { settings.avoidRaisingPattern=e->getConfInt("avoidRaisingPattern",0); settings.insFocusesPattern=e->getConfInt("insFocusesPattern",1); settings.stepOnInsert=e->getConfInt("stepOnInsert",0); + settings.unifiedDataView=e->getConfInt("unifiedDataView",0); + settings.sysFileDialog=e->getConfInt("sysFileDialog",0); clampSetting(settings.mainFontSize,2,96); clampSetting(settings.patFontSize,2,96); @@ -930,6 +937,8 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.avoidRaisingPattern,0,1); clampSetting(settings.insFocusesPattern,0,1); clampSetting(settings.stepOnInsert,0,1); + clampSetting(settings.unifiedDataView,0,1); + clampSetting(settings.sysFileDialog,0,1); // keybinds LOAD_KEYBIND(GUI_ACTION_OPEN,FURKMOD_CMD|SDLK_o); @@ -1082,6 +1091,9 @@ void FurnaceGUI::syncSettings() { decodeKeyMap(noteKeys,e->getConfString("noteKeys",DEFAULT_NOTE_KEYS)); parseKeybinds(); + + if (fileDialog!=NULL) delete fileDialog; + fileDialog=new FurnaceGUIFileDialog(settings.sysFileDialog); } #define PUT_UI_COLOR(source) e->setConf(#source,(int)ImGui::GetColorU32(uiColors[source])); @@ -1129,6 +1141,8 @@ void FurnaceGUI::commitSettings() { e->setConf("avoidRaisingPattern",settings.avoidRaisingPattern); e->setConf("insFocusesPattern",settings.insFocusesPattern); e->setConf("stepOnInsert",settings.stepOnInsert); + e->setConf("unifiedDataView",settings.unifiedDataView); + e->setConf("sysFileDialog",settings.sysFileDialog); PUT_UI_COLOR(GUI_COLOR_BACKGROUND); PUT_UI_COLOR(GUI_COLOR_FRAME_BACKGROUND); From bfc44320230057b3d638941244721c6551be48d7 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 13 Mar 2022 21:06:08 -0500 Subject: [PATCH 228/637] nooooooooooooooooooooooooooooooooo --- src/gui/fileDialog.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/gui/fileDialog.h b/src/gui/fileDialog.h index 057bef1fa..05ee87b40 100644 --- a/src/gui/fileDialog.h +++ b/src/gui/fileDialog.h @@ -1,5 +1,6 @@ #include "../ta-utils.h" #include "imgui.h" +#include namespace pfd { class open_file; @@ -28,4 +29,4 @@ class FurnaceGUIFileDialog { saving(false), dialogO(NULL), dialogS(NULL) {} -}; \ No newline at end of file +}; From d9a93e0cec4faab1b0b68ac6f5b264de29cd57bb Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 13 Mar 2022 21:19:52 -0500 Subject: [PATCH 229/637] ... --- src/gui/fileDialog.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/gui/fileDialog.cpp b/src/gui/fileDialog.cpp index 54b38739d..60435b016 100644 --- a/src/gui/fileDialog.cpp +++ b/src/gui/fileDialog.cpp @@ -1,5 +1,10 @@ #include "fileDialog.h" #include "ImGuiFileDialog.h" + +#ifdef __GNUC__ +#pragma GCC diagnostic ignored "-Wcast-function-type" +#endif + #include "../../extern/pfd/portable-file-dialogs.h" bool FurnaceGUIFileDialog::openLoad(String header, std::vector filter, String path, double dpiScale) { @@ -106,4 +111,4 @@ String FurnaceGUIFileDialog::getFileName() { } else { return ImGuiFileDialog::Instance()->GetFilePathName(); } -} \ No newline at end of file +} From 2ba01857016441a9fa7f6cc29c1b5165be697756 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 13 Mar 2022 21:46:52 -0500 Subject: [PATCH 230/637] well that didn't last long --- .gitmodules | 3 --- extern/pfd | 1 - 2 files changed, 4 deletions(-) delete mode 160000 extern/pfd diff --git a/.gitmodules b/.gitmodules index d08aa505b..d63fd70b5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,6 +22,3 @@ [submodule "extern/Nuked-OPL3"] path = extern/Nuked-OPL3 url = https://github.com/nukeykt/Nuked-OPL3.git -[submodule "extern/pfd"] - path = extern/pfd - url = https://github.com/samhocevar/portable-file-dialogs.git diff --git a/extern/pfd b/extern/pfd deleted file mode 160000 index dea8520de..000000000 --- a/extern/pfd +++ /dev/null @@ -1 +0,0 @@ -Subproject commit dea8520de18af09eefdbc18aaf7c24409d18491b From 0874d58fb80423892ae1493478e8cbcecc1f73db Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 13 Mar 2022 22:02:50 -0500 Subject: [PATCH 231/637] damn it --- CMakeLists.txt | 2 +- extern/pfd-fixed/.gitignore | 4 + extern/pfd-fixed/.lgtm.yml | 5 + extern/pfd-fixed/CMakeLists.txt | 6 + extern/pfd-fixed/COPYING | 14 + extern/pfd-fixed/README.md | 64 + extern/pfd-fixed/doc/message.md | 97 ++ extern/pfd-fixed/doc/notify.md | 40 + extern/pfd-fixed/doc/open_file.md | 90 ++ extern/pfd-fixed/doc/pfd.md | 120 ++ extern/pfd-fixed/doc/save_file.md | 73 + extern/pfd-fixed/doc/select_folder.md | 55 + extern/pfd-fixed/examples/.gitignore | 11 + extern/pfd-fixed/examples/example.cpp | 110 ++ extern/pfd-fixed/examples/example.vcxproj | 96 ++ extern/pfd-fixed/examples/kill.cpp | 42 + extern/pfd-fixed/examples/kill.vcxproj | 96 ++ extern/pfd-fixed/portable-file-dialogs.h | 1731 +++++++++++++++++++++ scripts/release-win32.sh | 2 +- scripts/release-win64.sh | 2 +- src/gui/fileDialog.cpp | 6 +- src/gui/gui.cpp | 3 + src/gui/settings.cpp | 3 - 23 files changed, 2661 insertions(+), 11 deletions(-) create mode 100644 extern/pfd-fixed/.gitignore create mode 100644 extern/pfd-fixed/.lgtm.yml create mode 100644 extern/pfd-fixed/CMakeLists.txt create mode 100644 extern/pfd-fixed/COPYING create mode 100644 extern/pfd-fixed/README.md create mode 100644 extern/pfd-fixed/doc/message.md create mode 100644 extern/pfd-fixed/doc/notify.md create mode 100644 extern/pfd-fixed/doc/open_file.md create mode 100644 extern/pfd-fixed/doc/pfd.md create mode 100644 extern/pfd-fixed/doc/save_file.md create mode 100644 extern/pfd-fixed/doc/select_folder.md create mode 100644 extern/pfd-fixed/examples/.gitignore create mode 100644 extern/pfd-fixed/examples/example.cpp create mode 100644 extern/pfd-fixed/examples/example.vcxproj create mode 100644 extern/pfd-fixed/examples/kill.cpp create mode 100644 extern/pfd-fixed/examples/kill.vcxproj create mode 100644 extern/pfd-fixed/portable-file-dialogs.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 99f1089b8..2dd372d2b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -398,7 +398,7 @@ if (APPLE) endif() if (NOT MSVC) - set(WARNING_FLAGS -Wall -Wextra -Wno-unused-parameter) + set(WARNING_FLAGS -Wall -Wextra -Wno-unused-parameter -Wno-cast-function-type) if (WARNINGS_ARE_ERRORS) list(APPEND WARNING_FLAGS -Werror) endif() diff --git a/extern/pfd-fixed/.gitignore b/extern/pfd-fixed/.gitignore new file mode 100644 index 000000000..ec121a999 --- /dev/null +++ b/extern/pfd-fixed/.gitignore @@ -0,0 +1,4 @@ +CMakeCache.txt +CMakeFiles +Makefile +cmake_install.cmake diff --git a/extern/pfd-fixed/.lgtm.yml b/extern/pfd-fixed/.lgtm.yml new file mode 100644 index 000000000..b4c4985b9 --- /dev/null +++ b/extern/pfd-fixed/.lgtm.yml @@ -0,0 +1,5 @@ +extraction: + cpp: + index: + build_command: + - make -C examples diff --git a/extern/pfd-fixed/CMakeLists.txt b/extern/pfd-fixed/CMakeLists.txt new file mode 100644 index 000000000..3be61ae5a --- /dev/null +++ b/extern/pfd-fixed/CMakeLists.txt @@ -0,0 +1,6 @@ +cmake_minimum_required(VERSION 3.1.0) + +project(portable_file_dialogs VERSION 1.00 LANGUAGES CXX) + +add_library(${PROJECT_NAME} INTERFACE) +target_include_directories(${PROJECT_NAME} INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) \ No newline at end of file diff --git a/extern/pfd-fixed/COPYING b/extern/pfd-fixed/COPYING new file mode 100644 index 000000000..8b014d64a --- /dev/null +++ b/extern/pfd-fixed/COPYING @@ -0,0 +1,14 @@ + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + Version 2, December 2004 + + Copyright (C) 2004 Sam Hocevar + + Everyone is permitted to copy and distribute verbatim or modified + copies of this license document, and changing it is allowed as long + as the name is changed. + + DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHAT THE FUCK YOU WANT TO. + diff --git a/extern/pfd-fixed/README.md b/extern/pfd-fixed/README.md new file mode 100644 index 000000000..ceb36a405 --- /dev/null +++ b/extern/pfd-fixed/README.md @@ -0,0 +1,64 @@ +# Portable File Dialogs + +A free C++11 file dialog library. + +- works on Windows, Mac OS X, Linux +- **single-header**, no extra library dependencies +- **synchronous *or* asynchronous** (does not block the rest of your program!) +- **cancelable** (kill asynchronous dialogues without user interaction) +- **secure** (immune to shell-quote vulnerabilities) + +[![Codacy Badge](https://app.codacy.com/project/badge/Grade/a25d3fd6959a4333871f630ac70b6e09)](https://www.codacy.com/manual/samhocevar/portable-file-dialogs?utm_source=github.com&utm_medium=referral&utm_content=samhocevar/portable-file-dialogs&utm_campaign=Badge_Grade) + +## Status + +The library is now pretty robust. It is not as feature-complete as +[Tiny File Dialogs](https://sourceforge.net/projects/tinyfiledialogs/), +but has asynchonous dialogs, more maintainable code, and fewer potential +security issues. + +The currently available backends are: + +- Win32 API (all known versions of Windows) +- Mac OS X (using AppleScript) +- GNOME desktop (using [Zenity](https://en.wikipedia.org/wiki/Zenity) or its clones Matedialog and Qarma) +- KDE desktop (using [KDialog](https://github.com/KDE/kdialog)) + +Experimental support for Emscripten is on its way. + +## Documentation + +- [`pfd`](doc/pfd.md) general documentation +- [`pfd::message`](doc/message.md) message box +- [`pfd::notify`](doc/notify.md) notification +- [`pfd::open_file`](doc/open_file.md) file open +- [`pfd::save_file`](doc/save_file.md) file save +- [`pfd::select_folder`](doc/select_folder.md) folder selection + +## History + +- 0.1.0 (July 16, 2020): first public release + +## Screenshots (Windows 10) + +![warning-win32](https://user-images.githubusercontent.com/245089/47136607-76919a00-d2b4-11e8-8f42-e2d62c4f9570.png) +![notify-win32](https://user-images.githubusercontent.com/245089/47142453-2ff76c00-d2c3-11e8-871a-1a110ac91eb2.png) +![open-win32](https://user-images.githubusercontent.com/245089/47155865-0f8cd900-d2e6-11e8-8041-1e20b6f77dee.png) + +## Screenshots (Mac OS X, dark theme) + +![warning-osxdark](https://user-images.githubusercontent.com/245089/56053001-22dba700-5d53-11e9-8233-ca7a2c58188d.png) +![notify-osxdark](https://user-images.githubusercontent.com/245089/56053188-bc0abd80-5d53-11e9-8298-68aa96315c6c.png) +![open-osxdark](https://user-images.githubusercontent.com/245089/56053378-39363280-5d54-11e9-9583-9f1c978fa0db.png) + +## Screenshots (Linux, GNOME desktop) + +![warning-gnome](https://user-images.githubusercontent.com/245089/47136608-772a3080-d2b4-11e8-9e1d-60a7e743e908.png) +![notify-gnome](https://user-images.githubusercontent.com/245089/47142455-30900280-d2c3-11e8-8b76-ea16c7e502d4.png) +![open-gnome](https://user-images.githubusercontent.com/245089/47155867-0f8cd900-d2e6-11e8-93af-275636491ec4.png) + +## Screenshots (Linux, KDE Plasma desktop) + +![warning-kde](https://user-images.githubusercontent.com/245089/47149255-4dcccd00-d2d3-11e8-84c9-f85612784680.png) +![notify-kde](https://user-images.githubusercontent.com/245089/47149206-27a72d00-d2d3-11e8-8f1b-96e462f08c2b.png) +![open-kde](https://user-images.githubusercontent.com/245089/47155866-0f8cd900-d2e6-11e8-8006-f14b948afc55.png) diff --git a/extern/pfd-fixed/doc/message.md b/extern/pfd-fixed/doc/message.md new file mode 100644 index 000000000..e54c44771 --- /dev/null +++ b/extern/pfd-fixed/doc/message.md @@ -0,0 +1,97 @@ +## Message Box API + +Displaying a message box is done using the `pfd::message` class. It can be provided a title, a +message text, a `choice` representing which buttons need to be rendered, and an `icon` for the +message: + +```cpp +pfd::message::message(std::string const &title, + std::string const &text, + pfd::choice choice = pfd::choice::ok_cancel, + pfd::icon icon = pfd::icon::info); + +enum class pfd::choice { ok, ok_cancel, yes_no, yes_no_cancel }; + +enum class pfd::icon { info, warning, error, question }; +``` + +The pressed button is queried using `pfd::message::result()`. If the dialog box is closed by any +other means, the `pfd::button::cancel` is assumed: + +```cpp +pfd::button pfd::message::result(); + +enum class pfd::button { ok, cancel, yes, no }; +``` + +It is possible to ask the dialog box whether the user took action using the `pfd::message::ready()` +method, with an optional `timeout` argument. If the user did not press a button within `timeout` +milliseconds, the function will return `false`: + +```cpp +bool pfd::message::ready(int timeout = pfd::default_wait_timeout); +``` + +## Example 1: simple notification + +The `pfd::message` destructor waits for user action, so this operation will block until the user +closes the message box: + +```cpp +pfd::message("Problem", "An error occurred while doing things", + pfd::choice::ok, pfd::icon::error); +``` + +## Example 2: retrieving the pressed button + +Using `pfd::message::result()` will also wait for user action before returning. This operation will block and return the user choice: + +```cpp +// Ask for user opinion +auto button = pfd::message("Action requested", "Do you want to proceed with things?", + pfd::choice::yes_no, pfd::icon::question).result(); +// Do something with button… +``` + +## Example 3: asynchronous message box + +Using `pfd::message::ready()` allows the application to perform other tasks while waiting for +user input: + +```cpp +// Message box with nice message +auto box = pfd::message("Unsaved Files", "Do you want to save the current " + "document before closing the application?", + pfd::choice::yes_no_cancel, + pfd::icon::warning); + +// Do something while waiting for user input +while (!box.ready(1000)) + std::cout << "Waited 1 second for user input...\n"; + +// Act depending on the selected button +switch (box.result()) +{ + case pfd::button::yes: std::cout << "User agreed.\n"; break; + case pfd::button::no: std::cout << "User disagreed.\n"; break; + case pfd::button::cancel: std::cout << "User freaked out.\n"; break; +} +``` + +## Screenshots + +#### Windows 10 + +![warning-win32](https://user-images.githubusercontent.com/245089/47136607-76919a00-d2b4-11e8-8f42-e2d62c4f9570.png) + +#### Mac OS X + +![warning-osx-dark](https://user-images.githubusercontent.com/245089/56053001-22dba700-5d53-11e9-8233-ca7a2c58188d.png) ![warning-osx-light](https://user-images.githubusercontent.com/245089/56053055-49014700-5d53-11e9-8306-e9a03a25e044.png) + +#### Linux (GNOME desktop) + +![warning-gnome](https://user-images.githubusercontent.com/245089/47140824-8662ab80-d2bf-11e8-9c87-2742dd5b58af.png) + +#### Linux (KDE desktop) + +![warning-kde](https://user-images.githubusercontent.com/245089/47149255-4dcccd00-d2d3-11e8-84c9-f85612784680.png) diff --git a/extern/pfd-fixed/doc/notify.md b/extern/pfd-fixed/doc/notify.md new file mode 100644 index 000000000..b140e2619 --- /dev/null +++ b/extern/pfd-fixed/doc/notify.md @@ -0,0 +1,40 @@ +## Notification API + +Displaying a desktop notification is done using the `pfd::notify` class. It can be provided a +title, a message text, and an `icon` for the notification style: + +```cpp +pfd::notify::notify(std::string const &title, + std::string const &text, + pfd::icon icon = pfd::icon::info); + +enum class pfd::icon { info, warning, error }; +``` + +## Example + +Displaying a notification is straightforward. Emoji are supported: + +```cpp +pfd::notify("System event", "Something might be on fire 🔥", + pfd::icon::warning); +``` + +The `pfd::notify` object needs not be kept around, letting the object clean up itself is enough. + +## Screenshots + +Windows 10: +![notify-win32](https://user-images.githubusercontent.com/245089/47142453-2ff76c00-d2c3-11e8-871a-1a110ac91eb2.png) + +Mac OS X (dark theme): +![image](https://user-images.githubusercontent.com/245089/56053188-bc0abd80-5d53-11e9-8298-68aa96315c6c.png) + +Mac OS X (light theme): +![image](https://user-images.githubusercontent.com/245089/56053137-92ea2d00-5d53-11e9-8cf2-049486c45713.png) + +Linux (GNOME desktop): +![notify-gnome](https://user-images.githubusercontent.com/245089/47142455-30900280-d2c3-11e8-8b76-ea16c7e502d4.png) + +Linux (KDE desktop): +![notify-kde](https://user-images.githubusercontent.com/245089/47149206-27a72d00-d2d3-11e8-8f1b-96e462f08c2b.png) diff --git a/extern/pfd-fixed/doc/open_file.md b/extern/pfd-fixed/doc/open_file.md new file mode 100644 index 000000000..db3158ef2 --- /dev/null +++ b/extern/pfd-fixed/doc/open_file.md @@ -0,0 +1,90 @@ +## File Open API + +The `pfd::open_file` class handles file opening dialogs. It can be provided a title, a starting +directory and/or pre-selected file, an optional filter for recognised file types, and an optional +flag to allow multiple selection: + +```cpp +pfd::open_file::open_file(std::string const &title, + std::string const &initial_path, + std::vector filters = { "All Files", "*" }, + pfd::opt option = pfd::opt::none); +``` + +The `option` parameter can be `pfd::opt::multiselect` to allow selecting multiple files. + +The selected files are queried using `pfd::open_file::result()`. If the user canceled the +operation, the returned list is empty: + +```cpp +std::vector pfd::open_file::result(); +``` + +It is possible to ask the file open dialog whether the user took action using the +`pfd::message::ready()` method, with an optional `timeout` argument. If the user did not validate +the dialog within `timeout` milliseconds, the function will return `false`: + +```cpp +bool pfd::open_file::ready(int timeout = pfd::default_wait_timeout); +``` + +## Example 1: simple file selection + +Using `pfd::open_file::result()` will wait for user action before returning. This operation will +block and return the user choice: + +```cpp +auto selection = pfd::open_file("Select a file").result(); +if (!selection.empty()) + std::cout << "User selected file " << selection[0] << "\n"; +``` + +## Example 2: filters + +The filter list enumerates filter names and corresponded space-separated wildcard lists. It +defaults to `{ "All Files", "*" }`, but here is how to use other options: + +```cpp +auto selection = pfd::open_file("Select a file", ".", + { "Image Files", "*.png *.jpg *.jpeg *.bmp", + "Audio Files", "*.wav *.mp3", + "All Files", "*" }, + pfd::opt::multiselect).result(); +// Do something with selection +for (auto const &filename : dialog.result()) + std::cout << "Selected file: " << filename << "\n"; +``` + +## Example 3: asynchronous file open + +Using `pfd::open_file::ready()` allows the application to perform other tasks while waiting for +user input: + +```cpp +// File open dialog +auto dialog = pfd::open_file("Select file to open"); + +// Do something while waiting for user input +while (!dialog.ready(1000)) + std::cout << "Waited 1 second for user input...\n"; + +// Act depending on the user choice +std::cout << "Number of selected files: " << dialog.result().size() << "\n"; +``` + +## Screenshots + +Windows 10: +![open-win32](https://user-images.githubusercontent.com/245089/47155865-0f8cd900-d2e6-11e8-8041-1e20b6f77dee.png) + +Mac OS X (dark theme): +![image](https://user-images.githubusercontent.com/245089/56053378-39363280-5d54-11e9-9583-9f1c978fa0db.png) + +Mac OS X (light theme): +![image](https://user-images.githubusercontent.com/245089/56053413-4fdc8980-5d54-11e9-85e3-e9e5d0e10772.png) + +Linux (GNOME desktop): +![open-gnome](https://user-images.githubusercontent.com/245089/47155867-0f8cd900-d2e6-11e8-93af-275636491ec4.png) + +Linux (KDE desktop): +![open-kde](https://user-images.githubusercontent.com/245089/47155866-0f8cd900-d2e6-11e8-8006-f14b948afc55.png) diff --git a/extern/pfd-fixed/doc/pfd.md b/extern/pfd-fixed/doc/pfd.md new file mode 100644 index 000000000..f62799efc --- /dev/null +++ b/extern/pfd-fixed/doc/pfd.md @@ -0,0 +1,120 @@ +## Portable File Dialogs documentation + +The library can be used either as a [header-only library](https://en.wikipedia.org/wiki/Header-only), +or as a [single file library](https://github.com/nothings/single_file_libs). + +### Use as header-only library + +Just include the main header file wherever needed: + +```cpp +#include "portable-file-dialogs.h" + +/* ... */ + + pfd::message::message("Hello", "This is a test"); + +/* ... */ +``` + +### Use as a single-file library + +Defining the `PFD_SKIP_IMPLEMENTATION` macro before including `portable-file-dialogs.h` will +skip all the implementation code and reduce compilation times. You still need to include the +header without the macro at least once, typically in a `pfd-impl.cpp` file. + +```cpp +// In pfd-impl.cpp +#include "portable-file-dialogs.h" +``` + +```cpp +// In all other files +#define PFD_SKIP_IMPLEMENTATION 1 +#include "portable-file-dialogs.h" +``` + +### General concepts + +Dialogs inherit from `pfd::dialog` and are created by calling their class constructor. Their +destructor will block until the window is closed by user interaction. So for instance this +will block until the end of the line: + +```cpp +pfd::message::message("Hi", "there"); +``` + +Whereas this will only block until the end of the scope, allowing the program to perform +additional operations while the dialog is open: + +```cpp +{ + auto m = pfd::message::message("Hi", "there"); + + // ... perform asynchronous operations here +} +``` + +It is possible to call `bool pfd::dialog::ready(timeout)` on the dialog in order to query its +status and perform asynchronous operations as long as the user has not interacted: + +```cpp +{ + auto m = pfd::message::message("Hi", "there"); + + while (!m.ready()) + { + // ... perform asynchronous operations here + } +} +``` + +If necessary, a dialog can be forcibly closed using `bool pfd::dialog::kill()`. Note that this +may be confusing to the user and should only be used in very specific situations. It is also not +possible to close a Windows message box that provides no _Cancel_ button. + +```cpp +{ + auto m = pfd::message::message("Hi", "there"); + + while (!m.ready()) + { + // ... perform asynchronous operations here + + if (too_much_time_has_passed()) + m.kill(); + } +} +``` + +Finally, the user response can be retrieved using `pfd::dialog::result()`. The return value of +this function depends on which dialog is being used. See their respective documentation for more +information: + + * [`pfd::message`](message.md) (message box) + * [`pfd::notify`](notify.md) (notification) + * [`pfd::open_file`](open_file.md) (file open) + * [`pfd::save_file`](save_file.md) (file save) + * [`pfd::select_folder`](select_folder.md) (folder selection) + +### Settings + +The library can be queried and configured through the `pfd::settings` class. + +```cpp +bool pfd::settings::available(); +void pfd::settings::verbose(bool value); +void pfd::settings::rescan(); +``` + +The return value of `pfd::settings::available()` indicates whether a suitable dialog backend (such +as Zenity or KDialog on Linux) has been found. If not, the library will not work and all dialog +invocations will be no-ops. The program will not crash but you should account for this situation +and add a fallback mechanism or exit gracefully. + +Calling `pfd::settings::rescan()` will force a rescan of available backends. This may change the +result of `pfd::settings::available()` if a backend was installed on the system in the meantime. +This is probably only useful for debugging purposes. + +Calling `pfd::settings::verbose(true)` may help debug the library. It will output debug information +to `std::cout` about some operations being performed. diff --git a/extern/pfd-fixed/doc/save_file.md b/extern/pfd-fixed/doc/save_file.md new file mode 100644 index 000000000..5c10badb6 --- /dev/null +++ b/extern/pfd-fixed/doc/save_file.md @@ -0,0 +1,73 @@ +## File Open API + +The `pfd::save_file` class handles file saving dialogs. It can be provided a title, a starting +directory and/or pre-selected file, an optional filter for recognised file types, and an optional +flag to allow multiple selection: + +```cpp +pfd::save_file::save_file(std::string const &title, + std::string const &initial_path, + std::vector filters = { "All Files", "*" }, + pfd::opt option = pfd::opt::none); +``` + +The `option` parameter can be `pfd::opt::force_overwrite` to disable a potential warning when +saving to an existing file. + +The selected file is queried using `pfd::save_file::result()`. If the user canceled the +operation, the returned file name is empty: + +```cpp +std::string pfd::save_file::result(); +``` + +It is possible to ask the file save dialog whether the user took action using the +`pfd::message::ready()` method, with an optional `timeout` argument. If the user did not validate +the dialog within `timeout` milliseconds, the function will return `false`: + +```cpp +bool pfd::save_file::ready(int timeout = pfd::default_wait_timeout); +``` + +## Example 1: simple file selection + +Using `pfd::save_file::result()` will wait for user action before returning. This operation will +block and return the user choice: + +```cpp +auto destination = pfd::save_file("Select a file").result(); +if (!destination.empty()) + std::cout << "User selected file " << destination << "\n"; +``` + +## Example 2: filters + +The filter list enumerates filter names and corresponded space-separated wildcard lists. It +defaults to `{ "All Files", "*" }`, but here is how to use other options: + +```cpp +auto destination = pfd::save_file("Select a file", ".", + { "Image Files", "*.png *.jpg *.jpeg *.bmp", + "Audio Files", "*.wav *.mp3", + "All Files", "*" }, + pfd::opt::force_overwrite).result(); +// Do something with destination +std::cout << "Selected file: " << destination << "\n"; +``` + +## Example 3: asynchronous file save + +Using `pfd::save_file::ready()` allows the application to perform other tasks while waiting for +user input: + +```cpp +// File save dialog +auto dialog = pfd::save_file("Select file to save"); + +// Do something while waiting for user input +while (!dialog.ready(1000)) + std::cout << "Waited 1 second for user input...\n"; + +// Act depending on the user choice +std::cout << "User selected file: " << dialog.result() << "\n"; +``` diff --git a/extern/pfd-fixed/doc/select_folder.md b/extern/pfd-fixed/doc/select_folder.md new file mode 100644 index 000000000..28f5f63fa --- /dev/null +++ b/extern/pfd-fixed/doc/select_folder.md @@ -0,0 +1,55 @@ +## Folder Selection API + +The `pfd::select_folder` class handles folder opening dialogs. It can be provided a title, and an +optional starting directory: + +```cpp +pfd::select_folder::select_folder(std::string const &title, + std::string const &default_path = "", + pfd::opt option = pfd::opt::none); +``` + +The `option` parameter can be `pfd::opt::force_path` to force the operating system to use the +provided path. Some systems default to the most recently used path, if applicable. + +The selected folder is queried using `pfd::select_folder::result()`. If the user canceled the +operation, the returned string is empty: + +```cpp +std::string pfd::select_folder::result(); +``` + +It is possible to ask the folder selection dialog whether the user took action using the +`pfd::message::ready()` method, with an optional `timeout` argument. If the user did not validate +the dialog within `timeout` milliseconds, the function will return `false`: + +```cpp +bool pfd::select_folder::ready(int timeout = pfd::default_wait_timeout); +``` + +## Example 1: simple folder selection + +Using `pfd::select_folder::result()` will wait for user action before returning. This operation +will block and return the user choice: + +```cpp +auto selection = pfd::select_folder("Select a folder").result(); +if (!selection.empty()) + std::cout << "User selected folder " << selection << "\n"; +``` + +## Example 2: asynchronous folder open + +Using `pfd::select_folder::ready()` allows the application to perform other tasks while waiting for user input: + +```cpp +// Folder selection dialog +auto dialog = pfd::select_folder("Select folder to open"); + +// Do something while waiting for user input +while (!dialog.ready(1000)) + std::cout << "Waited 1 second for user input...\n"; + +// Act depending on the user choice +std::cout << "Selected folder: " << dialog.result() << "\n"; +``` diff --git a/extern/pfd-fixed/examples/.gitignore b/extern/pfd-fixed/examples/.gitignore new file mode 100644 index 000000000..bda6acf70 --- /dev/null +++ b/extern/pfd-fixed/examples/.gitignore @@ -0,0 +1,11 @@ +example +example.exe +kill +kill.exe + +Debug +Release +*.vcxproj.user + +.idea +cmake-build-* diff --git a/extern/pfd-fixed/examples/example.cpp b/extern/pfd-fixed/examples/example.cpp new file mode 100644 index 000000000..a216ae4b3 --- /dev/null +++ b/extern/pfd-fixed/examples/example.cpp @@ -0,0 +1,110 @@ +// +// Portable File Dialogs +// +// Copyright © 2018—2020 Sam Hocevar +// +// This program is free software. It comes without any warranty, to +// the extent permitted by applicable law. You can redistribute it +// and/or modify it under the terms of the Do What the Fuck You Want +// to Public License, Version 2, as published by the WTFPL Task Force. +// See http://www.wtfpl.net/ for more details. +// + +#include "portable-file-dialogs.h" + +#include + +#if _WIN32 +#define DEFAULT_PATH "C:\\" +#else +#define DEFAULT_PATH "/tmp" +#endif + +int main() +{ + // Check that a backend is available + if (!pfd::settings::available()) + { + std::cout << "Portable File Dialogs are not available on this platform.\n"; + return 1; + } + + // Set verbosity to true + pfd::settings::verbose(true); + + // Notification + pfd::notify("Important Notification", + "This is ' a message, pay \" attention \\ to it!", + pfd::icon::info); + + // Message box with nice message + auto m = pfd::message("Personal Message", + "You are an amazing person, don’t let anyone make you think otherwise.", + pfd::choice::yes_no_cancel, + pfd::icon::warning); + + // Optional: do something while waiting for user action + for (int i = 0; i < 10 && !m.ready(1000); ++i) + std::cout << "Waited 1 second for user input...\n"; + + // Do something according to the selected button + switch (m.result()) + { + case pfd::button::yes: std::cout << "User agreed.\n"; break; + case pfd::button::no: std::cout << "User disagreed.\n"; break; + case pfd::button::cancel: std::cout << "User freaked out.\n"; break; + default: break; // Should not happen + } + + // Directory selection + auto dir = pfd::select_folder("Select any directory", DEFAULT_PATH).result(); + std::cout << "Selected dir: " << dir << "\n"; + + // File open + auto f = pfd::open_file("Choose files to read", DEFAULT_PATH, + { "Text Files (.txt .text)", "*.txt *.text", + "All Files", "*" }, + pfd::opt::multiselect); + std::cout << "Selected files:"; + for (auto const &name : f.result()) + std::cout << " " + name; + std::cout << "\n"; +} + +// Unused function that just tests the whole API +void api() +{ + // pfd::settings + pfd::settings::verbose(true); + pfd::settings::rescan(); + + // pfd::notify + pfd::notify("", ""); + pfd::notify("", "", pfd::icon::info); + pfd::notify("", "", pfd::icon::warning); + pfd::notify("", "", pfd::icon::error); + pfd::notify("", "", pfd::icon::question); + + pfd::notify a("", ""); + (void)a.ready(); + (void)a.ready(42); + + // pfd::message + pfd::message("", ""); + pfd::message("", "", pfd::choice::ok); + pfd::message("", "", pfd::choice::ok_cancel); + pfd::message("", "", pfd::choice::yes_no); + pfd::message("", "", pfd::choice::yes_no_cancel); + pfd::message("", "", pfd::choice::retry_cancel); + pfd::message("", "", pfd::choice::abort_retry_ignore); + pfd::message("", "", pfd::choice::ok, pfd::icon::info); + pfd::message("", "", pfd::choice::ok, pfd::icon::warning); + pfd::message("", "", pfd::choice::ok, pfd::icon::error); + pfd::message("", "", pfd::choice::ok, pfd::icon::question); + + pfd::message b("", ""); + (void)b.ready(); + (void)b.ready(42); + (void)b.result(); +} + diff --git a/extern/pfd-fixed/examples/example.vcxproj b/extern/pfd-fixed/examples/example.vcxproj new file mode 100644 index 000000000..d7e914928 --- /dev/null +++ b/extern/pfd-fixed/examples/example.vcxproj @@ -0,0 +1,96 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + + + + + + + 15.0 + {10F4364D-27C4-4C74-8079-7C42971E81E7} + Win32Proj + example + 10.0 + + + + Application + v142 + Unicode + + + true + + + false + true + + + + + + + + + + + + true + + + false + + + + .. + NotUsing + Level3 + true + true + + + Console + true + + + + + Disabled + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + + + MaxSpeed + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + true + + + true + true + + + + + + \ No newline at end of file diff --git a/extern/pfd-fixed/examples/kill.cpp b/extern/pfd-fixed/examples/kill.cpp new file mode 100644 index 000000000..787edbeb3 --- /dev/null +++ b/extern/pfd-fixed/examples/kill.cpp @@ -0,0 +1,42 @@ +// +// Portable File Dialogs +// +// Copyright © 2018—2020 Sam Hocevar +// +// This program is free software. It comes without any warranty, to +// the extent permitted by applicable law. You can redistribute it +// and/or modify it under the terms of the Do What the Fuck You Want +// to Public License, Version 2, as published by the WTFPL Task Force. +// See http://www.wtfpl.net/ for more details. +// + +#include "portable-file-dialogs.h" + +#include + +int main() +{ + // Set verbosity to true + pfd::settings::verbose(true); + + // Message box with nice message + auto m = pfd::message("Upgrade software?", + "Press OK to upgrade this software.\n" + "\n" + "By default, the software will update itself\n" + "automatically in 10 seconds.", + pfd::choice::ok_cancel, + pfd::icon::warning); + + // Wait for an answer for up to 10 seconds + for (int i = 0; i < 10 && !m.ready(1000); ++i) + ; + + // Upgrade software if user clicked OK, or if user didn’t interact + bool upgrade = m.ready() ? m.result() == pfd::button::ok : m.kill(); + if (upgrade) + std::cout << "Upgrading software!\n"; + else + std::cout << "Not upgrading software.\n"; +} + diff --git a/extern/pfd-fixed/examples/kill.vcxproj b/extern/pfd-fixed/examples/kill.vcxproj new file mode 100644 index 000000000..b1ee3c9c4 --- /dev/null +++ b/extern/pfd-fixed/examples/kill.vcxproj @@ -0,0 +1,96 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + + + + + + + 15.0 + {B94D26B1-7EF7-43A2-A973-9A96A08E2E17} + Win32Proj + kill + 10.0 + + + + Application + v142 + Unicode + + + true + + + false + true + + + + + + + + + + + + true + + + false + + + + .. + NotUsing + Level3 + true + true + + + Console + true + + + + + Disabled + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + + + MaxSpeed + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + true + + + true + true + + + + + + diff --git a/extern/pfd-fixed/portable-file-dialogs.h b/extern/pfd-fixed/portable-file-dialogs.h new file mode 100644 index 000000000..41e588acf --- /dev/null +++ b/extern/pfd-fixed/portable-file-dialogs.h @@ -0,0 +1,1731 @@ +// +// Portable File Dialogs +// +// Copyright © 2018—2020 Sam Hocevar +// +// This library is free software. It comes without any warranty, to +// the extent permitted by applicable law. You can redistribute it +// and/or modify it under the terms of the Do What the Fuck You Want +// to Public License, Version 2, as published by the WTFPL Task Force. +// See http://www.wtfpl.net/ for more details. +// + +#pragma once + +#if _WIN32 +#ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN 1 +#endif +#include +#include +#include +#include // IFileDialog +#include +#include +#include // std::async + +#elif __EMSCRIPTEN__ +#include + +#else +#ifndef _POSIX_C_SOURCE +# define _POSIX_C_SOURCE 2 // for popen() +#endif +#ifdef __APPLE__ +# ifndef _DARWIN_C_SOURCE +# define _DARWIN_C_SOURCE +# endif +#endif +#include // popen() +#include // std::getenv() +#include // fcntl() +#include // read(), pipe(), dup2() +#include // ::kill, std::signal +#include // waitpid() +#endif + +#include // std::string +#include // std::shared_ptr +#include // std::ostream +#include // std::map +#include // std::set +#include // std::regex +#include // std::mutex, std::this_thread +#include // std::chrono + +// Versions of mingw64 g++ up to 9.3.0 do not have a complete IFileDialog +#ifndef PFD_HAS_IFILEDIALOG +# define PFD_HAS_IFILEDIALOG 1 +# if (defined __MINGW64__ || defined __MINGW32__) && defined __GXX_ABI_VERSION +# if __GXX_ABI_VERSION <= 1013 +# undef PFD_HAS_IFILEDIALOG +# define PFD_HAS_IFILEDIALOG 0 +# endif +# endif +#endif + +namespace pfd +{ + +enum class button +{ + cancel = -1, + ok, + yes, + no, + abort, + retry, + ignore, +}; + +enum class choice +{ + ok = 0, + ok_cancel, + yes_no, + yes_no_cancel, + retry_cancel, + abort_retry_ignore, +}; + +enum class icon +{ + info = 0, + warning, + error, + question, +}; + +// Additional option flags for various dialog constructors +enum class opt : uint8_t +{ + none = 0, + // For file open, allow multiselect. + multiselect = 0x1, + // For file save, force overwrite and disable the confirmation dialog. + force_overwrite = 0x2, + // For folder select, force path to be the provided argument instead + // of the last opened directory, which is the Microsoft-recommended, + // user-friendly behaviour. + force_path = 0x4, +}; + +inline opt operator |(opt a, opt b) { return opt(uint8_t(a) | uint8_t(b)); } +inline bool operator &(opt a, opt b) { return bool(uint8_t(a) & uint8_t(b)); } + +// The settings class, only exposing to the user a way to set verbose mode +// and to force a rescan of installed desktop helpers (zenity, kdialog…). +class settings +{ +public: + static bool available(); + + static void verbose(bool value); + static void rescan(); + +protected: + explicit settings(bool resync = false); + + bool check_program(std::string const &program); + + inline bool is_osascript() const; + inline bool is_zenity() const; + inline bool is_kdialog() const; + + enum class flag + { + is_scanned = 0, + is_verbose, + + has_zenity, + has_matedialog, + has_qarma, + has_kdialog, + is_vista, + + max_flag, + }; + + // Static array of flags for internal state + bool const &flags(flag in_flag) const; + + // Non-const getter for the static array of flags + bool &flags(flag in_flag); +}; + +// Internal classes, not to be used by client applications +namespace internal +{ + +// Process wait timeout, in milliseconds +static int const default_wait_timeout = 20; + +class executor +{ + friend class dialog; + +public: + // High level function to get the result of a command + std::string result(int *exit_code = nullptr); + + // High level function to abort + bool kill(); + +#if _WIN32 + void start_func(std::function const &fun); + static BOOL CALLBACK enum_windows_callback(HWND hwnd, LPARAM lParam); +#elif __EMSCRIPTEN__ + void start(int exit_code); +#else + void start_process(std::vector const &command); +#endif + + ~executor(); + +protected: + bool ready(int timeout = default_wait_timeout); + void stop(); + +private: + bool m_running = false; + std::string m_stdout; + int m_exit_code = -1; +#if _WIN32 + std::future m_future; + std::set m_windows; + std::condition_variable m_cond; + std::mutex m_mutex; + DWORD m_tid; +#elif __EMSCRIPTEN__ || __NX__ + // FIXME: do something +#else + pid_t m_pid = 0; + int m_fd = -1; +#endif +}; + +class platform +{ +protected: +#if _WIN32 + // Helper class around LoadLibraryA() and GetProcAddress() with some safety + class dll + { + public: + dll(std::string const &name); + ~dll(); + + template class proc + { + public: + proc(dll const &lib, std::string const &sym) + : m_proc(reinterpret_cast(::GetProcAddress(lib.handle, sym.c_str()))) + {} + + operator bool() const { return m_proc != nullptr; } + operator T *() const { return m_proc; } + + private: + T *m_proc; + }; + + private: + HMODULE handle; + }; + + // Helper class around CoInitialize() and CoUnInitialize() + class ole32_dll : public dll + { + public: + ole32_dll(); + ~ole32_dll(); + bool is_initialized(); + + private: + HRESULT m_state; + }; + + // Helper class around CreateActCtx() and ActivateActCtx() + class new_style_context + { + public: + new_style_context(); + ~new_style_context(); + + private: + HANDLE create(); + ULONG_PTR m_cookie = 0; + }; +#endif +}; + +class dialog : protected settings, protected platform +{ +public: + bool ready(int timeout = default_wait_timeout) const; + bool kill() const; + +protected: + explicit dialog(); + + std::vector desktop_helper() const; + static std::string buttons_to_name(choice _choice); + static std::string get_icon_name(icon _icon); + + std::string powershell_quote(std::string const &str) const; + std::string osascript_quote(std::string const &str) const; + std::string shell_quote(std::string const &str) const; + + // Keep handle to executing command + std::shared_ptr m_async; +}; + +class file_dialog : public dialog +{ +protected: + enum type + { + open, + save, + folder, + }; + + file_dialog(type in_type, + std::string const &title, + std::string const &default_path = "", + std::vector const &filters = {}, + opt options = opt::none); + +protected: + std::string string_result(); + std::vector vector_result(); + +#if _WIN32 + static int CALLBACK bffcallback(HWND hwnd, UINT uMsg, LPARAM, LPARAM pData); +#if PFD_HAS_IFILEDIALOG + std::string select_folder_vista(IFileDialog *ifd, bool force_path); +#endif + + std::wstring m_wtitle; + std::wstring m_wdefault_path; + + std::vector m_vector_result; +#endif +}; + +} // namespace internal + +// +// The notify widget +// + +class notify : public internal::dialog +{ +public: + notify(std::string const &title, + std::string const &message, + icon _icon = icon::info); +}; + +// +// The message widget +// + +class message : public internal::dialog +{ +public: + message(std::string const &title, + std::string const &text, + choice _choice = choice::ok_cancel, + icon _icon = icon::info); + + button result(); + +private: + // Some extra logic to map the exit code to button number + std::map m_mappings; +}; + +// +// The open_file, save_file, and open_folder widgets +// + +class open_file : public internal::file_dialog +{ +public: + open_file(std::string const &title, + std::string const &default_path = "", + std::vector const &filters = { "All Files", "*" }, + opt options = opt::none); + +#if defined(__has_cpp_attribute) +#if __has_cpp_attribute(deprecated) + // Backwards compatibility + [[deprecated("Use pfd::opt::multiselect instead of allow_multiselect")]] +#endif +#endif + open_file(std::string const &title, + std::string const &default_path, + std::vector const &filters, + bool allow_multiselect); + + std::vector result(); +}; + +class save_file : public internal::file_dialog +{ +public: + save_file(std::string const &title, + std::string const &default_path = "", + std::vector const &filters = { "All Files", "*" }, + opt options = opt::none); + +#if defined(__has_cpp_attribute) +#if __has_cpp_attribute(deprecated) + // Backwards compatibility + [[deprecated("Use pfd::opt::force_overwrite instead of confirm_overwrite")]] +#endif +#endif + save_file(std::string const &title, + std::string const &default_path, + std::vector const &filters, + bool confirm_overwrite); + + std::string result(); +}; + +class select_folder : public internal::file_dialog +{ +public: + select_folder(std::string const &title, + std::string const &default_path = "", + opt options = opt::none); + + std::string result(); +}; + +// +// Below this are all the method implementations. You may choose to define the +// macro PFD_SKIP_IMPLEMENTATION everywhere before including this header except +// in one place. This may reduce compilation times. +// + +#if !defined PFD_SKIP_IMPLEMENTATION + +// internal free functions implementations + +namespace internal +{ + +#if _WIN32 +static inline std::wstring str2wstr(std::string const &str) +{ + int len = MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int)str.size(), nullptr, 0); + std::wstring ret(len, '\0'); + MultiByteToWideChar(CP_UTF8, 0, str.c_str(), (int)str.size(), (LPWSTR)ret.data(), (int)ret.size()); + return ret; +} + +static inline std::string wstr2str(std::wstring const &str) +{ + int len = WideCharToMultiByte(CP_UTF8, 0, str.c_str(), (int)str.size(), nullptr, 0, nullptr, nullptr); + std::string ret(len, '\0'); + WideCharToMultiByte(CP_UTF8, 0, str.c_str(), (int)str.size(), (LPSTR)ret.data(), (int)ret.size(), nullptr, nullptr); + return ret; +} + +static inline bool is_vista() +{ + OSVERSIONINFOEXW osvi; + memset(&osvi, 0, sizeof(osvi)); + DWORDLONG const mask = VerSetConditionMask( + VerSetConditionMask( + VerSetConditionMask( + 0, VER_MAJORVERSION, VER_GREATER_EQUAL), + VER_MINORVERSION, VER_GREATER_EQUAL), + VER_SERVICEPACKMAJOR, VER_GREATER_EQUAL); + osvi.dwOSVersionInfoSize = sizeof(osvi); + osvi.dwMajorVersion = HIBYTE(_WIN32_WINNT_VISTA); + osvi.dwMinorVersion = LOBYTE(_WIN32_WINNT_VISTA); + osvi.wServicePackMajor = 0; + + return VerifyVersionInfoW(&osvi, VER_MAJORVERSION | VER_MINORVERSION | VER_SERVICEPACKMAJOR, mask) != FALSE; +} +#endif + +// This is necessary until C++20 which will have std::string::ends_with() etc. + +static inline bool ends_with(std::string const &str, std::string const &suffix) +{ + return suffix.size() <= str.size() && + str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0; +} + +static inline bool starts_with(std::string const &str, std::string const &prefix) +{ + return prefix.size() <= str.size() && + str.compare(0, prefix.size(), prefix) == 0; +} + +} // namespace internal + +// settings implementation + +inline settings::settings(bool resync) +{ + flags(flag::is_scanned) &= !resync; + + if (flags(flag::is_scanned)) + return; + +#if _WIN32 + flags(flag::is_vista) = internal::is_vista(); +#elif !__APPLE__ + flags(flag::has_zenity) = check_program("zenity"); + flags(flag::has_matedialog) = check_program("matedialog"); + flags(flag::has_qarma) = check_program("qarma"); + flags(flag::has_kdialog) = check_program("kdialog"); + + // If multiple helpers are available, try to default to the best one + if (flags(flag::has_zenity) && flags(flag::has_kdialog)) + { + auto desktop_name = std::getenv("XDG_SESSION_DESKTOP"); + if (desktop_name && desktop_name == std::string("gnome")) + flags(flag::has_kdialog) = false; + else if (desktop_name && desktop_name == std::string("KDE")) + flags(flag::has_zenity) = false; + } +#endif + + flags(flag::is_scanned) = true; +} + +inline bool settings::available() +{ +#if _WIN32 + return true; +#elif __APPLE__ + return true; +#else + settings tmp; + return tmp.flags(flag::has_zenity) || + tmp.flags(flag::has_matedialog) || + tmp.flags(flag::has_qarma) || + tmp.flags(flag::has_kdialog); +#endif +} + +inline void settings::verbose(bool value) +{ + settings().flags(flag::is_verbose) = value; +} + +inline void settings::rescan() +{ + settings(/* resync = */ true); +} + +// Check whether a program is present using “which”. +inline bool settings::check_program(std::string const &program) +{ +#if _WIN32 + (void)program; + return false; +#elif __EMSCRIPTEN__ + (void)program; + return false; +#else + int exit_code = -1; + internal::executor async; + async.start_process({"/bin/sh", "-c", "which " + program}); + async.result(&exit_code); + return exit_code == 0; +#endif +} + +inline bool settings::is_osascript() const +{ +#if __APPLE__ + return true; +#else + return false; +#endif +} + +inline bool settings::is_zenity() const +{ + return flags(flag::has_zenity) || + flags(flag::has_matedialog) || + flags(flag::has_qarma); +} + +inline bool settings::is_kdialog() const +{ + return flags(flag::has_kdialog); +} + +inline bool const &settings::flags(flag in_flag) const +{ + static bool flags[size_t(flag::max_flag)]; + return flags[size_t(in_flag)]; +} + +inline bool &settings::flags(flag in_flag) +{ + return const_cast(static_cast(this)->flags(in_flag)); +} + +// executor implementation + +inline std::string internal::executor::result(int *exit_code /* = nullptr */) +{ + stop(); + if (exit_code) + *exit_code = m_exit_code; + return m_stdout; +} + +inline bool internal::executor::kill() +{ +#if _WIN32 + if (m_future.valid()) + { + // Close all windows that weren’t open when we started the future + auto previous_windows = m_windows; + EnumWindows(&enum_windows_callback, (LPARAM)this); + for (auto hwnd : m_windows) + if (previous_windows.find(hwnd) == previous_windows.end()) + SendMessage(hwnd, WM_CLOSE, 0, 0); + } +#elif __EMSCRIPTEN__ || __NX__ + // FIXME: do something + (void)timeout; + return false; // cannot kill +#else + ::kill(m_pid, SIGKILL); +#endif + stop(); + return true; +} + +#if _WIN32 +inline BOOL CALLBACK internal::executor::enum_windows_callback(HWND hwnd, LPARAM lParam) +{ + auto that = (executor *)lParam; + + DWORD pid; + auto tid = GetWindowThreadProcessId(hwnd, &pid); + if (tid == that->m_tid) + that->m_windows.insert(hwnd); + return TRUE; +} +#endif + +#if _WIN32 +inline void internal::executor::start_func(std::function const &fun) +{ + stop(); + + auto trampoline = [fun, this]() + { + // Save our thread id so that the caller can cancel us + m_tid = GetCurrentThreadId(); + EnumWindows(&enum_windows_callback, (LPARAM)this); + m_cond.notify_all(); + return fun(&m_exit_code); + }; + + std::unique_lock lock(m_mutex); + m_future = std::async(std::launch::async, trampoline); + m_cond.wait(lock); + m_running = true; +} + +#elif __EMSCRIPTEN__ +inline void internal::executor::start(int exit_code) +{ + m_exit_code = exit_code; +} + +#else +inline void internal::executor::start_process(std::vector const &command) +{ + stop(); + m_stdout.clear(); + m_exit_code = -1; + + int in[2], out[2]; + if (pipe(in) != 0 || pipe(out) != 0) + return; + + m_pid = fork(); + if (m_pid < 0) + return; + + close(in[m_pid ? 0 : 1]); + close(out[m_pid ? 1 : 0]); + + if (m_pid == 0) + { + dup2(in[0], STDIN_FILENO); + dup2(out[1], STDOUT_FILENO); + + // Ignore stderr so that it doesn’t pollute the console (e.g. GTK+ errors from zenity) + int fd = open("/dev/null", O_WRONLY); + dup2(fd, STDERR_FILENO); + close(fd); + + std::vector args; + std::transform(command.cbegin(), command.cend(), std::back_inserter(args), + [](std::string const &s) { return const_cast(s.c_str()); }); + args.push_back(nullptr); // null-terminate argv[] + + execvp(args[0], args.data()); + exit(1); + } + + close(in[1]); + m_fd = out[0]; + auto flags = fcntl(m_fd, F_GETFL); + fcntl(m_fd, F_SETFL, flags | O_NONBLOCK); + + m_running = true; +} +#endif + +inline internal::executor::~executor() +{ + stop(); +} + +inline bool internal::executor::ready(int timeout /* = default_wait_timeout */) +{ + if (!m_running) + return true; + +#if _WIN32 + if (m_future.valid()) + { + auto status = m_future.wait_for(std::chrono::milliseconds(timeout)); + if (status != std::future_status::ready) + { + // On Windows, we need to run the message pump. If the async + // thread uses a Windows API dialog, it may be attached to the + // main thread and waiting for messages that only we can dispatch. + MSG msg; + while (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + return false; + } + + m_stdout = m_future.get(); + } +#elif __EMSCRIPTEN__ || __NX__ + // FIXME: do something + (void)timeout; +#else + char buf[BUFSIZ]; + ssize_t received = read(m_fd, buf, BUFSIZ); // Flawfinder: ignore + if (received > 0) + { + m_stdout += std::string(buf, received); + return false; + } + + // Reap child process if it is dead. It is possible that the system has already reaped it + // (this happens when the calling application handles or ignores SIG_CHLD) and results in + // waitpid() failing with ECHILD. Otherwise we assume the child is running and we sleep for + // a little while. + int status; + pid_t child = waitpid(m_pid, &status, WNOHANG); + if (child != m_pid && (child >= 0 || errno != ECHILD)) + { + // FIXME: this happens almost always at first iteration + std::this_thread::sleep_for(std::chrono::milliseconds(timeout)); + return false; + } + + close(m_fd); + m_exit_code = WEXITSTATUS(status); +#endif + + m_running = false; + return true; +} + +inline void internal::executor::stop() +{ + // Loop until the user closes the dialog + while (!ready()) + ; +} + +// dll implementation + +#if _WIN32 +inline internal::platform::dll::dll(std::string const &name) + : handle(::LoadLibraryA(name.c_str())) +{} + +inline internal::platform::dll::~dll() +{ + if (handle) + ::FreeLibrary(handle); +} +#endif // _WIN32 + +// ole32_dll implementation + +#if _WIN32 +inline internal::platform::ole32_dll::ole32_dll() + : dll("ole32.dll") +{ + // Use COINIT_MULTITHREADED because COINIT_APARTMENTTHREADED causes crashes. + // See https://github.com/samhocevar/portable-file-dialogs/issues/51 + auto coinit = proc(*this, "CoInitializeEx"); + m_state = coinit(nullptr, COINIT_MULTITHREADED); +} + +inline internal::platform::ole32_dll::~ole32_dll() +{ + if (is_initialized()) + proc(*this, "CoUninitialize")(); +} + +inline bool internal::platform::ole32_dll::is_initialized() +{ + return m_state == S_OK || m_state == S_FALSE; +} +#endif + +// new_style_context implementation + +#if _WIN32 +inline internal::platform::new_style_context::new_style_context() +{ + // Only create one activation context for the whole app lifetime. + static HANDLE hctx = create(); + + if (hctx != INVALID_HANDLE_VALUE) + ActivateActCtx(hctx, &m_cookie); +} + +inline internal::platform::new_style_context::~new_style_context() +{ + DeactivateActCtx(0, m_cookie); +} + +inline HANDLE internal::platform::new_style_context::create() +{ + // This “hack” seems to be necessary for this code to work on windows XP. + // Without it, dialogs do not show and close immediately. GetError() + // returns 0 so I don’t know what causes this. I was not able to reproduce + // this behavior on Windows 7 and 10 but just in case, let it be here for + // those versions too. + // This hack is not required if other dialogs are used (they load comdlg32 + // automatically), only if message boxes are used. + dll comdlg32("comdlg32.dll"); + + // Using approach as shown here: https://stackoverflow.com/a/10444161 + UINT len = ::GetSystemDirectoryA(nullptr, 0); + std::string sys_dir(len, '\0'); + ::GetSystemDirectoryA(&sys_dir[0], len); + + ACTCTXA act_ctx = + { + // Do not set flag ACTCTX_FLAG_SET_PROCESS_DEFAULT, since it causes a + // crash with error “default context is already set”. + sizeof(act_ctx), + ACTCTX_FLAG_RESOURCE_NAME_VALID | ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID, + "shell32.dll", 0, 0, sys_dir.c_str(), (LPCSTR)124, NULL, NULL + }; + + return ::CreateActCtxA(&act_ctx); +} +#endif // _WIN32 + +// dialog implementation + +inline bool internal::dialog::ready(int timeout /* = default_wait_timeout */) const +{ + return m_async->ready(timeout); +} + +inline bool internal::dialog::kill() const +{ + return m_async->kill(); +} + +inline internal::dialog::dialog() + : m_async(std::make_shared()) +{ +} + +inline std::vector internal::dialog::desktop_helper() const +{ +#if __APPLE__ + return { "osascript" }; +#else + return { flags(flag::has_zenity) ? "zenity" + : flags(flag::has_matedialog) ? "matedialog" + : flags(flag::has_qarma) ? "qarma" + : flags(flag::has_kdialog) ? "kdialog" + : "echo" }; +#endif +} + +inline std::string internal::dialog::buttons_to_name(choice _choice) +{ + switch (_choice) + { + case choice::ok_cancel: return "okcancel"; + case choice::yes_no: return "yesno"; + case choice::yes_no_cancel: return "yesnocancel"; + case choice::retry_cancel: return "retrycancel"; + case choice::abort_retry_ignore: return "abortretryignore"; + /* case choice::ok: */ default: return "ok"; + } +} + +inline std::string internal::dialog::get_icon_name(icon _icon) +{ + switch (_icon) + { + case icon::warning: return "warning"; + case icon::error: return "error"; + case icon::question: return "question"; + // Zenity wants "information" but WinForms wants "info" + /* case icon::info: */ default: +#if _WIN32 + return "info"; +#else + return "information"; +#endif + } +} + +// THis is only used for debugging purposes +inline std::ostream& operator <<(std::ostream &s, std::vector const &v) +{ + int not_first = 0; + for (auto &e : v) + s << (not_first++ ? " " : "") << e; + return s; +} + +// Properly quote a string for Powershell: replace ' or " with '' or "" +// FIXME: we should probably get rid of newlines! +// FIXME: the \" sequence seems unsafe, too! +// XXX: this is no longer used but I would like to keep it around just in case +inline std::string internal::dialog::powershell_quote(std::string const &str) const +{ + return "'" + std::regex_replace(str, std::regex("['\"]"), "$&$&") + "'"; +} + +// Properly quote a string for osascript: replace \ or " with \\ or \" +// XXX: this also used to replace ' with \' when popen was used, but it would be +// smarter to do shell_quote(osascript_quote(...)) if this is needed again. +inline std::string internal::dialog::osascript_quote(std::string const &str) const +{ + return "\"" + std::regex_replace(str, std::regex("[\\\\\"]"), "\\$&") + "\""; +} + +// Properly quote a string for the shell: just replace ' with '\'' +// XXX: this is no longer used but I would like to keep it around just in case +inline std::string internal::dialog::shell_quote(std::string const &str) const +{ + return "'" + std::regex_replace(str, std::regex("'"), "'\\''") + "'"; +} + +// file_dialog implementation + +inline internal::file_dialog::file_dialog(type in_type, + std::string const &title, + std::string const &default_path /* = "" */, + std::vector const &filters /* = {} */, + opt options /* = opt::none */) +{ +#if _WIN32 + std::string filter_list; + std::regex whitespace(" *"); + for (size_t i = 0; i + 1 < filters.size(); i += 2) + { + filter_list += filters[i] + '\0'; + filter_list += std::regex_replace(filters[i + 1], whitespace, ";") + '\0'; + } + filter_list += '\0'; + + m_async->start_func([this, in_type, title, default_path, filter_list, + options](int *exit_code) -> std::string + { + (void)exit_code; + m_wtitle = internal::str2wstr(title); + m_wdefault_path = internal::str2wstr(default_path); + auto wfilter_list = internal::str2wstr(filter_list); + + // Initialise COM. This is required for the new folder selection window, + // (see https://github.com/samhocevar/portable-file-dialogs/pull/21) + // and to avoid random crashes with GetOpenFileNameW() (see + // https://github.com/samhocevar/portable-file-dialogs/issues/51) + ole32_dll ole32; + + // Folder selection uses a different method + if (in_type == type::folder) + { +#if PFD_HAS_IFILEDIALOG + if (flags(flag::is_vista)) + { + // On Vista and higher we should be able to use IFileDialog for folder selection + IFileDialog *ifd; + HRESULT hr = dll::proc(ole32, "CoCreateInstance") + (CLSID_FileOpenDialog, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&ifd)); + + // In case CoCreateInstance fails (which it should not), try legacy approach + if (SUCCEEDED(hr)) + return select_folder_vista(ifd, options & opt::force_path); + } +#endif + + BROWSEINFOW bi; + memset(&bi, 0, sizeof(bi)); + + bi.lpfn = &bffcallback; + bi.lParam = (LPARAM)this; + + if (flags(flag::is_vista)) + { + if (ole32.is_initialized()) + bi.ulFlags |= BIF_NEWDIALOGSTYLE; + bi.ulFlags |= BIF_EDITBOX; + bi.ulFlags |= BIF_STATUSTEXT; + } + + auto *list = SHBrowseForFolderW(&bi); + std::string ret; + if (list) + { + auto buffer = new wchar_t[MAX_PATH]; + SHGetPathFromIDListW(list, buffer); + dll::proc(ole32, "CoTaskMemFree")(list); + ret = internal::wstr2str(buffer); + delete[] buffer; + } + return ret; + } + + OPENFILENAMEW ofn; + memset(&ofn, 0, sizeof(ofn)); + ofn.lStructSize = sizeof(OPENFILENAMEW); + ofn.hwndOwner = GetActiveWindow(); + + ofn.lpstrFilter = wfilter_list.c_str(); + + auto woutput = std::wstring(MAX_PATH * 256, L'\0'); + ofn.lpstrFile = (LPWSTR)woutput.data(); + ofn.nMaxFile = (DWORD)woutput.size(); + if (!m_wdefault_path.empty()) + { + // If a directory was provided, use it as the initial directory. If + // a valid path was provided, use it as the initial file. Otherwise, + // let the Windows API decide. + auto path_attr = GetFileAttributesW(m_wdefault_path.c_str()); + if (path_attr != INVALID_FILE_ATTRIBUTES && (path_attr & FILE_ATTRIBUTE_DIRECTORY)) + ofn.lpstrInitialDir = m_wdefault_path.c_str(); + else if (m_wdefault_path.size() <= woutput.size()) + //second argument is size of buffer, not length of string + StringCchCopyW(ofn.lpstrFile, MAX_PATH*256+1, m_wdefault_path.c_str()); + else + { + ofn.lpstrFileTitle = (LPWSTR)m_wdefault_path.data(); + ofn.nMaxFileTitle = (DWORD)m_wdefault_path.size(); + } + } + ofn.lpstrTitle = m_wtitle.c_str(); + ofn.Flags = OFN_NOCHANGEDIR | OFN_EXPLORER; + + dll comdlg32("comdlg32.dll"); + + // Apply new visual style (required for windows XP) + new_style_context ctx; + + if (in_type == type::save) + { + if (!(options & opt::force_overwrite)) + ofn.Flags |= OFN_OVERWRITEPROMPT; + + dll::proc get_save_file_name(comdlg32, "GetSaveFileNameW"); + if (get_save_file_name(&ofn) == 0) + return ""; + return internal::wstr2str(woutput.c_str()); + } + else + { + if (options & opt::multiselect) + ofn.Flags |= OFN_ALLOWMULTISELECT; + ofn.Flags |= OFN_PATHMUSTEXIST; + + dll::proc get_open_file_name(comdlg32, "GetOpenFileNameW"); + if (get_open_file_name(&ofn) == 0) + return ""; + } + + std::string prefix; + for (wchar_t const *p = woutput.c_str(); *p; ) + { + auto filename = internal::wstr2str(p); + p += wcslen(p); + // In multiselect mode, we advance p one wchar further and + // check for another filename. If there is one and the + // prefix is empty, it means we just read the prefix. + if ((options & opt::multiselect) && *++p && prefix.empty()) + { + prefix = filename + "/"; + continue; + } + + m_vector_result.push_back(prefix + filename); + } + + return ""; + }); +#else + auto command = desktop_helper(); + + if (is_osascript()) + { + std::string script = "set ret to choose"; + switch (in_type) + { + case type::save: + script += " file name"; + break; + case type::open: default: + script += " file"; + if (options & opt::multiselect) + script += " with multiple selections allowed"; + break; + case type::folder: + script += " folder"; + break; + } + + if (default_path.size()) + script += " default location " + osascript_quote(default_path); + script += " with prompt " + osascript_quote(title); + + if (in_type == type::open) + { + // Concatenate all user-provided filter patterns + std::string patterns; + for (size_t i = 0; i < filters.size() / 2; ++i) + patterns += " " + filters[2 * i + 1]; + + // Split the pattern list to check whether "*" is in there; if it + // is, we have to disable filters because there is no mechanism in + // OS X for the user to override the filter. + std::regex sep("\\s+"); + std::string filter_list; + bool has_filter = true; + std::sregex_token_iterator iter(patterns.begin(), patterns.end(), sep, -1); + std::sregex_token_iterator end; + for ( ; iter != end; ++iter) + { + auto pat = iter->str(); + if (pat == "*" || pat == "*.*") + has_filter = false; + else if (internal::starts_with(pat, "*.")) + filter_list += (filter_list.size() == 0 ? "" : ",") + + osascript_quote(pat.substr(2, pat.size() - 2)); + } + if (has_filter && filter_list.size() > 0) + script += " of type {" + filter_list + "}"; + } + + if (in_type == type::open && (options & opt::multiselect)) + { + script += "\nset s to \"\""; + script += "\nrepeat with i in ret"; + script += "\n set s to s & (POSIX path of i) & \"\\n\""; + script += "\nend repeat"; + script += "\ncopy s to stdout"; + } + else + { + script += "\nPOSIX path of ret"; + } + + command.push_back("-e"); + command.push_back(script); + } + else if (is_zenity()) + { + command.push_back("--file-selection"); + command.push_back("--filename=" + default_path); + command.push_back("--title"); + command.push_back(title); + command.push_back("--separator=\n"); + + for (size_t i = 0; i < filters.size() / 2; ++i) + { + command.push_back("--file-filter"); + command.push_back(filters[2 * i] + "|" + filters[2 * i + 1]); + } + + if (in_type == type::save) + command.push_back("--save"); + if (in_type == type::folder) + command.push_back("--directory"); + if (!(options & opt::force_overwrite)) + command.push_back("--confirm-overwrite"); + if (options & opt::multiselect) + command.push_back("--multiple"); + } + else if (is_kdialog()) + { + switch (in_type) + { + case type::save: command.push_back("--getsavefilename"); break; + case type::open: command.push_back("--getopenfilename"); break; + case type::folder: command.push_back("--getexistingdirectory"); break; + } + if (options & opt::multiselect) + { + command.push_back("--multiple"); + command.push_back("--separate-output"); + } + + command.push_back(default_path); + + std::string filter; + for (size_t i = 0; i < filters.size() / 2; ++i) + filter += (i == 0 ? "" : " | ") + filters[2 * i] + "(" + filters[2 * i + 1] + ")"; + command.push_back(filter); + + command.push_back("--title"); + command.push_back(title); + } + + if (flags(flag::is_verbose)) + std::cerr << "pfd: " << command << std::endl; + + m_async->start_process(command); +#endif +} + +inline std::string internal::file_dialog::string_result() +{ +#if _WIN32 + return m_async->result(); +#else + auto ret = m_async->result(); + // Strip potential trailing newline (zenity). Also strip trailing slash + // added by osascript for consistency with other backends. + while (!ret.empty() && (ret.back() == '\n' || ret.back() == '/')) + ret.pop_back(); + return ret; +#endif +} + +inline std::vector internal::file_dialog::vector_result() +{ +#if _WIN32 + m_async->result(); + return m_vector_result; +#else + std::vector ret; + auto result = m_async->result(); + for (;;) + { + // Split result along newline characters + auto i = result.find('\n'); + if (i == 0 || i == std::string::npos) + break; + ret.push_back(result.substr(0, i)); + result = result.substr(i + 1, result.size()); + } + return ret; +#endif +} + +#if _WIN32 +// Use a static function to pass as BFFCALLBACK for legacy folder select +inline int CALLBACK internal::file_dialog::bffcallback(HWND hwnd, UINT uMsg, + LPARAM, LPARAM pData) +{ + auto inst = (file_dialog *)pData; + switch (uMsg) + { + case BFFM_INITIALIZED: + SendMessage(hwnd, BFFM_SETSELECTIONW, TRUE, (LPARAM)inst->m_wdefault_path.c_str()); + break; + } + return 0; +} + +#if PFD_HAS_IFILEDIALOG +inline std::string internal::file_dialog::select_folder_vista(IFileDialog *ifd, bool force_path) +{ + std::string result; + + IShellItem *folder; + + // Load library at runtime so app doesn't link it at load time (which will fail on windows XP) + dll shell32("shell32.dll"); + dll::proc + create_item(shell32, "SHCreateItemFromParsingName"); + + if (!create_item) + return ""; + + auto hr = create_item(m_wdefault_path.c_str(), + nullptr, + IID_PPV_ARGS(&folder)); + + // Set default folder if found. This only sets the default folder. If + // Windows has any info about the most recently selected folder, it + // will display it instead. Generally, calling SetFolder() to set the + // current directory “is not a good or expected user experience and + // should therefore be avoided”: + // https://docs.microsoft.com/windows/win32/api/shobjidl_core/nf-shobjidl_core-ifiledialog-setfolder + if (SUCCEEDED(hr)) + { + if (force_path) + ifd->SetFolder(folder); + else + ifd->SetDefaultFolder(folder); + folder->Release(); + } + + // Set the dialog title and option to select folders + ifd->SetOptions(FOS_PICKFOLDERS); + ifd->SetTitle(m_wtitle.c_str()); + + hr = ifd->Show(GetActiveWindow()); + if (SUCCEEDED(hr)) + { + IShellItem* item; + hr = ifd->GetResult(&item); + if (SUCCEEDED(hr)) + { + wchar_t* wselected = nullptr; + item->GetDisplayName(SIGDN_FILESYSPATH, &wselected); + item->Release(); + + if (wselected) + { + result = internal::wstr2str(std::wstring(wselected)); + dll::proc(ole32_dll(), "CoTaskMemFree")(wselected); + } + } + } + + ifd->Release(); + + return result; +} +#endif +#endif + +// notify implementation + +inline notify::notify(std::string const &title, + std::string const &message, + icon _icon /* = icon::info */) +{ + if (_icon == icon::question) // Not supported by notifications + _icon = icon::info; + +#if _WIN32 + // Use a static shared pointer for notify_icon so that we can delete + // it whenever we need to display a new one, and we can also wait + // until the program has finished running. + struct notify_icon_data : public NOTIFYICONDATAW + { + ~notify_icon_data() { Shell_NotifyIconW(NIM_DELETE, this); } + }; + + static std::shared_ptr nid; + + // Release the previous notification icon, if any, and allocate a new + // one. Note that std::make_shared() does value initialization, so there + // is no need to memset the structure. + nid = nullptr; + nid = std::make_shared(); + + // For XP support + nid->cbSize = NOTIFYICONDATAW_V2_SIZE; + nid->hWnd = nullptr; + nid->uID = 0; + + // Flag Description: + // - NIF_ICON The hIcon member is valid. + // - NIF_MESSAGE The uCallbackMessage member is valid. + // - NIF_TIP The szTip member is valid. + // - NIF_STATE The dwState and dwStateMask members are valid. + // - NIF_INFO Use a balloon ToolTip instead of a standard ToolTip. The szInfo, uTimeout, szInfoTitle, and dwInfoFlags members are valid. + // - NIF_GUID Reserved. + nid->uFlags = NIF_MESSAGE | NIF_ICON | NIF_INFO; + + // Flag Description + // - NIIF_ERROR An error icon. + // - NIIF_INFO An information icon. + // - NIIF_NONE No icon. + // - NIIF_WARNING A warning icon. + // - NIIF_ICON_MASK Version 6.0. Reserved. + // - NIIF_NOSOUND Version 6.0. Do not play the associated sound. Applies only to balloon ToolTips + switch (_icon) + { + case icon::warning: nid->dwInfoFlags = NIIF_WARNING; break; + case icon::error: nid->dwInfoFlags = NIIF_ERROR; break; + /* case icon::info: */ default: nid->dwInfoFlags = NIIF_INFO; break; + } + + ENUMRESNAMEPROC icon_enum_callback = [](HMODULE, LPCTSTR, LPTSTR lpName, LONG_PTR lParam) -> BOOL + { + ((NOTIFYICONDATAW *)lParam)->hIcon = ::LoadIcon(GetModuleHandle(nullptr), lpName); + return false; + }; + + nid->hIcon = ::LoadIcon(nullptr, IDI_APPLICATION); + ::EnumResourceNames(nullptr, RT_GROUP_ICON, icon_enum_callback, (LONG_PTR)nid.get()); + + nid->uTimeout = 5000; + + StringCchCopyW(nid->szInfoTitle, ARRAYSIZE(nid->szInfoTitle), internal::str2wstr(title).c_str()); + StringCchCopyW(nid->szInfo, ARRAYSIZE(nid->szInfo), internal::str2wstr(message).c_str()); + + // Display the new icon + Shell_NotifyIconW(NIM_ADD, nid.get()); +#else + auto command = desktop_helper(); + + if (is_osascript()) + { + command.push_back("-e"); + command.push_back("display notification " + osascript_quote(message) + + " with title " + osascript_quote(title)); + } + else if (is_zenity()) + { + command.push_back("--notification"); + command.push_back("--window-icon"); + command.push_back(get_icon_name(_icon)); + command.push_back("--text"); + command.push_back(title + "\n" + message); + } + else if (is_kdialog()) + { + command.push_back("--icon"); + command.push_back(get_icon_name(_icon)); + command.push_back("--title"); + command.push_back(title); + command.push_back("--passivepopup"); + command.push_back(message); + command.push_back("5"); + } + + if (flags(flag::is_verbose)) + std::cerr << "pfd: " << command << std::endl; + + m_async->start_process(command); +#endif +} + +// message implementation + +inline message::message(std::string const &title, + std::string const &text, + choice _choice /* = choice::ok_cancel */, + icon _icon /* = icon::info */) +{ +#if _WIN32 + // Use MB_SYSTEMMODAL rather than MB_TOPMOST to ensure the message window is brought + // to front. See https://github.com/samhocevar/portable-file-dialogs/issues/52 + UINT style = MB_SYSTEMMODAL; + switch (_icon) + { + case icon::warning: style |= MB_ICONWARNING; break; + case icon::error: style |= MB_ICONERROR; break; + case icon::question: style |= MB_ICONQUESTION; break; + /* case icon::info: */ default: style |= MB_ICONINFORMATION; break; + } + + switch (_choice) + { + case choice::ok_cancel: style |= MB_OKCANCEL; break; + case choice::yes_no: style |= MB_YESNO; break; + case choice::yes_no_cancel: style |= MB_YESNOCANCEL; break; + case choice::retry_cancel: style |= MB_RETRYCANCEL; break; + case choice::abort_retry_ignore: style |= MB_ABORTRETRYIGNORE; break; + /* case choice::ok: */ default: style |= MB_OK; break; + } + + m_mappings[IDCANCEL] = button::cancel; + m_mappings[IDOK] = button::ok; + m_mappings[IDYES] = button::yes; + m_mappings[IDNO] = button::no; + m_mappings[IDABORT] = button::abort; + m_mappings[IDRETRY] = button::retry; + m_mappings[IDIGNORE] = button::ignore; + + m_async->start_func([text, title, style](int* exit_code) -> std::string + { + auto wtext = internal::str2wstr(text); + auto wtitle = internal::str2wstr(title); + // Apply new visual style (required for all Windows versions) + new_style_context ctx; + *exit_code = MessageBoxW(GetActiveWindow(), wtext.c_str(), wtitle.c_str(), style); + return ""; + }); + +#elif __EMSCRIPTEN__ + std::string full_message; + switch (_icon) + { + case icon::warning: full_message = "⚠️"; break; + case icon::error: full_message = "⛔"; break; + case icon::question: full_message = "❓"; break; + /* case icon::info: */ default: full_message = "ℹ"; break; + } + + full_message += ' ' + title + "\n\n" + text; + + // This does not really start an async task; it just passes the + // EM_ASM_INT return value to a fake start() function. + m_async->start(EM_ASM_INT( + { + if ($1) + return window.confirm(UTF8ToString($0)) ? 0 : -1; + alert(UTF8ToString($0)); + return 0; + }, full_message.c_str(), _choice == choice::ok_cancel)); +#else + auto command = desktop_helper(); + + if (is_osascript()) + { + std::string script = "display dialog " + osascript_quote(text) + + " with title " + osascript_quote(title); + switch (_choice) + { + case choice::ok_cancel: + script += "buttons {\"OK\", \"Cancel\"}" + " default button \"OK\"" + " cancel button \"Cancel\""; + m_mappings[256] = button::cancel; + break; + case choice::yes_no: + script += "buttons {\"Yes\", \"No\"}" + " default button \"Yes\"" + " cancel button \"No\""; + m_mappings[256] = button::no; + break; + case choice::yes_no_cancel: + script += "buttons {\"Yes\", \"No\", \"Cancel\"}" + " default button \"Yes\"" + " cancel button \"Cancel\""; + m_mappings[256] = button::cancel; + break; + case choice::retry_cancel: + script += "buttons {\"Retry\", \"Cancel\"}" + " default button \"Retry\"" + " cancel button \"Cancel\""; + m_mappings[256] = button::cancel; + break; + case choice::abort_retry_ignore: + script += "buttons {\"Abort\", \"Retry\", \"Ignore\"}" + " default button \"Retry\"" + " cancel button \"Retry\""; + m_mappings[256] = button::cancel; + break; + case choice::ok: default: + script += "buttons {\"OK\"}" + " default button \"OK\"" + " cancel button \"OK\""; + m_mappings[256] = button::ok; + break; + } + script += " with icon "; + switch (_icon) + { + #define PFD_OSX_ICON(n) "alias ((path to library folder from system domain) as text " \ + "& \"CoreServices:CoreTypes.bundle:Contents:Resources:" n ".icns\")" + case icon::info: default: script += PFD_OSX_ICON("ToolBarInfo"); break; + case icon::warning: script += "caution"; break; + case icon::error: script += "stop"; break; + case icon::question: script += PFD_OSX_ICON("GenericQuestionMarkIcon"); break; + #undef PFD_OSX_ICON + } + + command.push_back("-e"); + command.push_back(script); + } + else if (is_zenity()) + { + switch (_choice) + { + case choice::ok_cancel: + command.insert(command.end(), { "--question", "--cancel-label=Cancel", "--ok-label=OK" }); break; + case choice::yes_no: + // Do not use standard --question because it causes “No” to return -1, + // which is inconsistent with the “Yes/No/Cancel” mode below. + command.insert(command.end(), { "--question", "--switch", "--extra-button=No", "--extra-button=Yes" }); break; + case choice::yes_no_cancel: + command.insert(command.end(), { "--question", "--switch", "--extra-button=Cancel", "--extra-button=No", "--extra-button=Yes" }); break; + case choice::retry_cancel: + command.insert(command.end(), { "--question", "--switch", "--extra-button=Cancel", "--extra-button=Retry" }); break; + case choice::abort_retry_ignore: + command.insert(command.end(), { "--question", "--switch", "--extra-button=Ignore", "--extra-button=Abort", "--extra-button=Retry" }); break; + case choice::ok: + default: + switch (_icon) + { + case icon::error: command.push_back("--error"); break; + case icon::warning: command.push_back("--warning"); break; + default: command.push_back("--info"); break; + } + } + + command.insert(command.end(), { "--title", title, + "--width=300", "--height=0", // sensible defaults + "--text", text, + "--icon-name=dialog-" + get_icon_name(_icon) }); + } + else if (is_kdialog()) + { + if (_choice == choice::ok) + { + switch (_icon) + { + case icon::error: command.push_back("--error"); break; + case icon::warning: command.push_back("--sorry"); break; + default: command.push_back("--msgbox"); break; + } + } + else + { + std::string flag = "--"; + if (_icon == icon::warning || _icon == icon::error) + flag += "warning"; + flag += "yesno"; + if (_choice == choice::yes_no_cancel) + flag += "cancel"; + command.push_back(flag); + if (_choice == choice::yes_no || _choice == choice::yes_no_cancel) + { + m_mappings[0] = button::yes; + m_mappings[256] = button::no; + } + } + + command.push_back(text); + command.push_back("--title"); + command.push_back(title); + + // Must be after the above part + if (_choice == choice::ok_cancel) + command.insert(command.end(), { "--yes-label", "OK", "--no-label", "Cancel" }); + } + + if (flags(flag::is_verbose)) + std::cerr << "pfd: " << command << std::endl; + + m_async->start_process(command); +#endif +} + +inline button message::result() +{ + int exit_code; + auto ret = m_async->result(&exit_code); + // osascript will say "button returned:Cancel\n" + // and others will just say "Cancel\n" + if (exit_code < 0 || // this means cancel + internal::ends_with(ret, "Cancel\n")) + return button::cancel; + if (internal::ends_with(ret, "OK\n")) + return button::ok; + if (internal::ends_with(ret, "Yes\n")) + return button::yes; + if (internal::ends_with(ret, "No\n")) + return button::no; + if (internal::ends_with(ret, "Abort\n")) + return button::abort; + if (internal::ends_with(ret, "Retry\n")) + return button::retry; + if (internal::ends_with(ret, "Ignore\n")) + return button::ignore; + if (m_mappings.count(exit_code) != 0) + return m_mappings[exit_code]; + return exit_code == 0 ? button::ok : button::cancel; +} + +// open_file implementation + +inline open_file::open_file(std::string const &title, + std::string const &default_path /* = "" */, + std::vector const &filters /* = { "All Files", "*" } */, + opt options /* = opt::none */) + : file_dialog(type::open, title, default_path, filters, options) +{ +} + +inline open_file::open_file(std::string const &title, + std::string const &default_path, + std::vector const &filters, + bool allow_multiselect) + : open_file(title, default_path, filters, + (allow_multiselect ? opt::multiselect : opt::none)) +{ +} + +inline std::vector open_file::result() +{ + return vector_result(); +} + +// save_file implementation + +inline save_file::save_file(std::string const &title, + std::string const &default_path /* = "" */, + std::vector const &filters /* = { "All Files", "*" } */, + opt options /* = opt::none */) + : file_dialog(type::save, title, default_path, filters, options) +{ +} + +inline save_file::save_file(std::string const &title, + std::string const &default_path, + std::vector const &filters, + bool confirm_overwrite) + : save_file(title, default_path, filters, + (confirm_overwrite ? opt::none : opt::force_overwrite)) +{ +} + +inline std::string save_file::result() +{ + return string_result(); +} + +// select_folder implementation + +inline select_folder::select_folder(std::string const &title, + std::string const &default_path /* = "" */, + opt options /* = opt::none */) + : file_dialog(type::folder, title, default_path, {}, options) +{ +} + +inline std::string select_folder::result() +{ + return string_result(); +} + +#endif // PFD_SKIP_IMPLEMENTATION + +} // namespace pfd + diff --git a/scripts/release-win32.sh b/scripts/release-win32.sh index 215575a2b..ddb9f806c 100755 --- a/scripts/release-win32.sh +++ b/scripts/release-win32.sh @@ -15,7 +15,7 @@ fi cd win32build # TODO: potential Arch-ism? -i686-w64-mingw32-cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS="-O2" -DCMAKE_CXX_FLAGS="-O2 -Wall -Wextra -Wno-unused-parameter -Werror" -DBUILD_SHARED_LIBS=OFF .. || exit 1 +i686-w64-mingw32-cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS="-O2" -DCMAKE_CXX_FLAGS="-O2 -Wall -Wextra -Wno-unused-parameter -Wno-cast-function-type -Werror" -DBUILD_SHARED_LIBS=OFF .. || exit 1 make -j8 || exit 1 i686-w64-mingw32-strip -s furnace.exe || exit 1 diff --git a/scripts/release-win64.sh b/scripts/release-win64.sh index f0dfc4d2c..c02892e86 100755 --- a/scripts/release-win64.sh +++ b/scripts/release-win64.sh @@ -15,7 +15,7 @@ fi cd winbuild # TODO: potential Arch-ism? -x86_64-w64-mingw32-cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS="-O2" -DCMAKE_CXX_FLAGS="-O2 -Wall -Wextra -Wno-unused-parameter -Werror" .. || exit 1 +x86_64-w64-mingw32-cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS="-O2" -DCMAKE_CXX_FLAGS="-O2 -Wall -Wextra -Wno-unused-parameter -Wno-cast-function-type -Werror" .. || exit 1 make -j8 || exit 1 x86_64-w64-mingw32-strip -s furnace.exe || exit 1 diff --git a/src/gui/fileDialog.cpp b/src/gui/fileDialog.cpp index 60435b016..f94c3fd5d 100644 --- a/src/gui/fileDialog.cpp +++ b/src/gui/fileDialog.cpp @@ -1,11 +1,7 @@ #include "fileDialog.h" #include "ImGuiFileDialog.h" -#ifdef __GNUC__ -#pragma GCC diagnostic ignored "-Wcast-function-type" -#endif - -#include "../../extern/pfd/portable-file-dialogs.h" +#include "../../extern/pfd-fixed/portable-file-dialogs.h" bool FurnaceGUIFileDialog::openLoad(String header, std::vector filter, String path, double dpiScale) { if (opened) return false; diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index fe53b9113..6d15ea089 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -6581,6 +6581,9 @@ void FurnaceGUI::applyUISettings() { if ((bigFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(font_plexSans_compressed_data,font_plexSans_compressed_size,40*dpiScale))==NULL) { logE("could not load big UI font!\n"); } + + if (fileDialog!=NULL) delete fileDialog; + fileDialog=new FurnaceGUIFileDialog(settings.sysFileDialog); } bool FurnaceGUI::init() { diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index eb09e86fa..be0e979a8 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -1091,9 +1091,6 @@ void FurnaceGUI::syncSettings() { decodeKeyMap(noteKeys,e->getConfString("noteKeys",DEFAULT_NOTE_KEYS)); parseKeybinds(); - - if (fileDialog!=NULL) delete fileDialog; - fileDialog=new FurnaceGUIFileDialog(settings.sysFileDialog); } #define PUT_UI_COLOR(source) e->setConf(#source,(int)ImGui::GetColorU32(uiColors[source])); From 25eb3e4aae8e635bceba9314aa5cc06ccfe62cb3 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 13 Mar 2022 22:21:01 -0500 Subject: [PATCH 232/637] furnace II - the final fix --- CMakeLists.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2dd372d2b..4feccdf8d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -398,7 +398,10 @@ if (APPLE) endif() if (NOT MSVC) - set(WARNING_FLAGS -Wall -Wextra -Wno-unused-parameter -Wno-cast-function-type) + set(WARNING_FLAGS -Wall -Wextra -Wno-unused-parameter) + if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + list(APPEND WARNING_FLAGS -Wno-cast-function-type) + endif() if (WARNINGS_ARE_ERRORS) list(APPEND WARNING_FLAGS -Werror) endif() From f56f4c80d1f461d81bf98cc9c7d2279be839ffc0 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 13 Mar 2022 23:10:43 -0500 Subject: [PATCH 233/637] GUI: move to system file dialog default setting will be selected by a poll --- src/gui/fileDialog.cpp | 32 ++++---- src/gui/fileDialog.h | 4 +- src/gui/gui.cpp | 162 ++++++++++++++++++++++++++++++++++------- 3 files changed, 151 insertions(+), 47 deletions(-) diff --git a/src/gui/fileDialog.cpp b/src/gui/fileDialog.cpp index f94c3fd5d..79a87f3ed 100644 --- a/src/gui/fileDialog.cpp +++ b/src/gui/fileDialog.cpp @@ -1,38 +1,34 @@ #include "fileDialog.h" #include "ImGuiFileDialog.h" +#include "../ta-log.h" #include "../../extern/pfd-fixed/portable-file-dialogs.h" -bool FurnaceGUIFileDialog::openLoad(String header, std::vector filter, String path, double dpiScale) { +bool FurnaceGUIFileDialog::openLoad(String header, std::vector filter, const char* noSysFilter, String path, double dpiScale) { if (opened) return false; saving=false; curPath=path; if (sysDialog) { dialogO=new pfd::open_file(header,path,filter); } else { - String parsedFilter; - if (filter.size()&1) return false; - - for (size_t i=0; iDpiScale=dpiScale; - ImGuiFileDialog::Instance()->OpenModal("FileDialog",header,parsedFilter.c_str(),path); + ImGuiFileDialog::Instance()->OpenModal("FileDialog",header,noSysFilter,path); } + opened=true; return true; } -bool FurnaceGUIFileDialog::openSave(String header, std::vector filter, String path, double dpiScale) { +bool FurnaceGUIFileDialog::openSave(String header, std::vector filter, const char* noSysFilter, String path, double dpiScale) { + if (opened) return false; + saving=true; curPath=path; if (sysDialog) { - // TODO + dialogS=new pfd::save_file(header,path,filter); } else { - String parsedFilter; ImGuiFileDialog::Instance()->DpiScale=dpiScale; - ImGuiFileDialog::Instance()->OpenModal("FileDialog",header,parsedFilter.c_str(),path,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite); + ImGuiFileDialog::Instance()->OpenModal("FileDialog",header,noSysFilter,path,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite); } + opened=true; return true; } @@ -55,12 +51,12 @@ void FurnaceGUIFileDialog::close() { if (dialogO!=NULL) { delete dialogO; dialogO=NULL; - printf("deleting\n"); } } } else { ImGuiFileDialog::Instance()->Close(); } + opened=false; } bool FurnaceGUIFileDialog::render(const ImVec2& min, const ImVec2& max) { @@ -69,7 +65,7 @@ bool FurnaceGUIFileDialog::render(const ImVec2& min, const ImVec2& max) { if (dialogS!=NULL) { if (dialogS->ready(1)) { fileName=dialogS->result(); - printf("returning %s\n",fileName.c_str()); + logD("returning %s\n",fileName.c_str()); return true; } } @@ -78,10 +74,10 @@ bool FurnaceGUIFileDialog::render(const ImVec2& min, const ImVec2& max) { if (dialogO->ready(1)) { if (dialogO->result().empty()) { fileName=""; - printf("returning nothing\n"); + logD("returning nothing\n"); } else { fileName=dialogO->result()[0]; - printf("returning %s\n",fileName.c_str()); + logD("returning %s\n",fileName.c_str()); } return true; } diff --git a/src/gui/fileDialog.h b/src/gui/fileDialog.h index 05ee87b40..b7f21abf0 100644 --- a/src/gui/fileDialog.h +++ b/src/gui/fileDialog.h @@ -16,8 +16,8 @@ class FurnaceGUIFileDialog { pfd::open_file* dialogO; pfd::save_file* dialogS; public: - bool openLoad(String header, std::vector filter, String path, double dpiScale); - bool openSave(String header, std::vector filter, String path, double dpiScale); + bool openLoad(String header, std::vector filter, const char* noSysFilter, String path, double dpiScale); + bool openSave(String header, std::vector filter, const char* noSysFilter, String path, double dpiScale); bool accepted(); void close(); bool render(const ImVec2& min, const ImVec2& max); diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 6d15ea089..c16b7d72e 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4585,73 +4585,168 @@ bool dirExists(String what) { } void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { + bool hasOpened=false; switch (type) { case GUI_FILE_OPEN: if (!dirExists(workingDirSong)) workingDirSong=getHomeDir(); - fileDialog->openLoad("Open File",{"compatible files", ".fur,.dmf", "all files", ".*"},workingDirSong,dpiScale); - //ImGuiFileDialog::Instance()->OpenModal("FileDialog","Open File","compatible files{.fur,.dmf},.*",workingDirSong); + hasOpened=fileDialog->openLoad( + "Open File", + {"compatible files", "*.fur *.dmf", + "all files", ".*"}, + "compatible files{.fur,.dmf},.*", + workingDirSong, + dpiScale + ); break; case GUI_FILE_SAVE: if (!dirExists(workingDirSong)) workingDirSong=getHomeDir(); - ImGuiFileDialog::Instance()->OpenModal("FileDialog","Save File","Furnace song{.fur},DefleMask 1.1 module{.dmf}",workingDirSong,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite); + hasOpened=fileDialog->openSave( + "Save File", + {"Furnace song", "*.fur", + "DefleMask 1.1 module", "*.dmf"}, + "Furnace song{.fur},DefleMask 1.1 module{.dmf}", + workingDirSong, + dpiScale + ); break; case GUI_FILE_SAVE_DMF_LEGACY: if (!dirExists(workingDirSong)) workingDirSong=getHomeDir(); - ImGuiFileDialog::Instance()->OpenModal("FileDialog","Save File","DefleMask 1.0/legacy module{.dmf}",workingDirSong,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite); + hasOpened=fileDialog->openSave( + "Save File", + {"DefleMask 1.0/legacy module", "*.dmf"}, + "DefleMask 1.0/legacy module{.dmf}", + workingDirSong, + dpiScale + ); break; case GUI_FILE_INS_OPEN: if (!dirExists(workingDirIns)) workingDirIns=getHomeDir(); - ImGuiFileDialog::Instance()->OpenModal("FileDialog","Load Instrument","compatible files{.fui,.dmp,.tfi,.vgi},.*",workingDirIns); + hasOpened=fileDialog->openLoad( + "Load Instrument", + {"compatible files", "*.fui *.dmp *.tfi *.vgi", + "all files", ".*"}, + "compatible files{.fui,.dmp,.tfi,.vgi},.*", + workingDirIns, + dpiScale + ); break; case GUI_FILE_INS_SAVE: if (!dirExists(workingDirIns)) workingDirIns=getHomeDir(); - ImGuiFileDialog::Instance()->OpenModal("FileDialog","Save Instrument","Furnace instrument{.fui}",workingDirIns,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite); + hasOpened=fileDialog->openSave( + "Save Instrument", + {"Furnace instrument", "*.fui"}, + "Furnace instrument{.fui}", + workingDirIns, + dpiScale + ); break; case GUI_FILE_WAVE_OPEN: if (!dirExists(workingDirWave)) workingDirWave=getHomeDir(); - ImGuiFileDialog::Instance()->OpenModal("FileDialog","Load Wavetable","compatible files{.fuw,.dmw},.*",workingDirWave); + hasOpened=fileDialog->openLoad( + "Load Wavetable", + {"compatible files", "*.fuw *.dmw", + "all files", ".*"}, + "compatible files{.fuw,.dmw},.*", + workingDirWave, + dpiScale + ); break; case GUI_FILE_WAVE_SAVE: if (!dirExists(workingDirWave)) workingDirWave=getHomeDir(); - ImGuiFileDialog::Instance()->OpenModal("FileDialog","Save Wavetable","Furnace wavetable{.fuw}",workingDirWave,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite); + hasOpened=fileDialog->openSave( + "Save Wavetable", + {"Furnace wavetable", ".fuw"}, + "Furnace wavetable{.fuw}", + workingDirWave, + dpiScale + ); break; case GUI_FILE_SAMPLE_OPEN: if (!dirExists(workingDirSample)) workingDirSample=getHomeDir(); - ImGuiFileDialog::Instance()->OpenModal("FileDialog","Load Sample","Wave file{.wav},.*",workingDirSample); + hasOpened=fileDialog->openLoad( + "Load Sample", + {"Wave file", "*.wav", + "all files", ".*"}, + "Wave file{.wav},.*", + workingDirSample, + dpiScale + ); break; case GUI_FILE_SAMPLE_SAVE: if (!dirExists(workingDirSample)) workingDirSample=getHomeDir(); - ImGuiFileDialog::Instance()->OpenModal("FileDialog","Save Sample","Wave file{.wav}",workingDirSample,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite); + hasOpened=fileDialog->openSave( + "Save Sample", + {"Wave file", "*.wav"}, + "Wave file{.wav}", + workingDirSample, + dpiScale + ); break; case GUI_FILE_EXPORT_AUDIO_ONE: if (!dirExists(workingDirAudioExport)) workingDirAudioExport=getHomeDir(); - ImGuiFileDialog::Instance()->OpenModal("FileDialog","Export Audio","Wave file{.wav}",workingDirAudioExport,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite); + hasOpened=fileDialog->openSave( + "Export Audio", + {"Wave file", "*.wav"}, + "Wave file{.wav}", + workingDirAudioExport, + dpiScale + ); break; case GUI_FILE_EXPORT_AUDIO_PER_SYS: if (!dirExists(workingDirAudioExport)) workingDirAudioExport=getHomeDir(); - ImGuiFileDialog::Instance()->OpenModal("FileDialog","Export Audio","Wave file{.wav}",workingDirAudioExport,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite); + hasOpened=fileDialog->openSave( + "Export Audio", + {"Wave file", "*.wav"}, + "Wave file{.wav}", + workingDirAudioExport, + dpiScale + ); break; case GUI_FILE_EXPORT_AUDIO_PER_CHANNEL: if (!dirExists(workingDirAudioExport)) workingDirAudioExport=getHomeDir(); - ImGuiFileDialog::Instance()->OpenModal("FileDialog","Export Audio","Wave file{.wav}",workingDirAudioExport,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite); + hasOpened=fileDialog->openSave( + "Export Audio", + {"Wave file", "*.wav"}, + "Wave file{.wav}", + workingDirAudioExport, + dpiScale + ); break; case GUI_FILE_EXPORT_VGM: if (!dirExists(workingDirVGMExport)) workingDirVGMExport=getHomeDir(); - ImGuiFileDialog::Instance()->OpenModal("FileDialog","Export VGM",".vgm",workingDirVGMExport,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite); + hasOpened=fileDialog->openSave( + "Export VGM", + {"VGM file", "*.vgm"}, + "VGM file{.vgm}", + workingDirVGMExport, + dpiScale + ); break; case GUI_FILE_EXPORT_ROM: showError("Coming soon!"); break; case GUI_FILE_LOAD_MAIN_FONT: if (!dirExists(workingDirFont)) workingDirFont=getHomeDir(); - ImGuiFileDialog::Instance()->OpenModal("FileDialog","Select Font","compatible files{.ttf,.otf,.ttc}",workingDirFont); + hasOpened=fileDialog->openLoad( + "Select Font", + {"compatible files", "*.ttf *.otf *.ttc"}, + "compatible files{.ttf,.otf,.ttc}", + workingDirFont, + dpiScale + ); break; case GUI_FILE_LOAD_PAT_FONT: if (!dirExists(workingDirFont)) workingDirFont=getHomeDir(); - ImGuiFileDialog::Instance()->OpenModal("FileDialog","Select Font","compatible files{.ttf,.otf,.ttc}",workingDirFont); + hasOpened=fileDialog->openLoad( + "Select Font", + {"compatible files", "*.ttf *.otf *.ttc"}, + "compatible files{.ttf,.otf,.ttc}", + workingDirFont, + dpiScale + ); break; } - curFileDialog=type; + if (hasOpened) curFileDialog=type; //ImGui::GetIO().ConfigFlags|=ImGuiConfigFlags_NavEnableKeyboard; } @@ -4776,7 +4871,7 @@ int FurnaceGUI::load(String path) { } if (len<1) { if (len==0) { - printf("that file is empty!\n"); + logE("that file is empty!\n"); lastError="file is empty"; } else { perror("tell error"); @@ -4932,6 +5027,15 @@ void FurnaceGUI::processDrags(int dragX, int dragY) { fileName+=x; \ } +#define checkExtensionDual(x,y,fallback) \ + String lowerCase=fileName; \ + for (char& i: lowerCase) { \ + if (i>='A' && i<='Z') i+='a'-'A'; \ + } \ + if (lowerCase.size()<4 || (lowerCase.rfind(x)!=lowerCase.size()-4 && lowerCase.rfind(y)!=lowerCase.size()-4)) { \ + fileName+=fallback; \ + } + #define BIND_FOR(x) getKeyName(actionKeys[x],true).c_str() void FurnaceGUI::editOptions(bool topMenu) { @@ -5980,11 +6084,9 @@ bool FurnaceGUI::loop() { fileName=fileDialog->getFileName(); if (fileName!="") { if (curFileDialog==GUI_FILE_SAVE) { - if (ImGuiFileDialog::Instance()->GetCurrentFilter()=="Furnace song") { - checkExtension(".fur"); - } else { - checkExtension(".dmf"); - } + // we can't tell whether the user chose .dmf or .fur in the system file picker + const char* fallbackExt=(settings.sysFileDialog || ImGuiFileDialog::Instance()->GetCurrentFilter()=="Furnace song")?".fur":".dmf"; + checkExtensionDual(".fur",".dmf",fallbackExt); } if (curFileDialog==GUI_FILE_SAVE_DMF_LEGACY) { checkExtension(".dmf"); @@ -6011,9 +6113,13 @@ bool FurnaceGUI::loop() { showError(fmt::sprintf("Error while loading file! (%s)",lastError)); } break; - case GUI_FILE_SAVE: - printf("saving: %s\n",copyOfName.c_str()); - if (ImGuiFileDialog::Instance()->GetCurrentFilter()=="Furnace song") { + case GUI_FILE_SAVE: { + logD("saving: %s\n",copyOfName.c_str()); + String lowerCase=fileName; + for (char& i: lowerCase) { + if (i>='A' && i<='Z') i+='a'-'A'; + } + if ((lowerCase.size()<4 || lowerCase.rfind(".dmf")!=lowerCase.size()-4)) { if (save(copyOfName,0)>0) { showError(fmt::sprintf("Error while saving file! (%s)",lastError)); } @@ -6023,8 +6129,9 @@ bool FurnaceGUI::loop() { } } break; + } case GUI_FILE_SAVE_DMF_LEGACY: - printf("saving: %s\n",copyOfName.c_str()); + logD("saving: %s\n",copyOfName.c_str()); if (save(copyOfName,24)>0) { showError(fmt::sprintf("Error while saving file! (%s)",lastError)); } @@ -6713,6 +6820,7 @@ bool FurnaceGUI::init() { ImGui::GetIO().IniFilename=finalLayoutPath; ImGui::LoadIniSettingsFromDisk(finalLayoutPath); + // TODO: allow changing these colors. ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByTypeDir,"",ImVec4(0.0f,1.0f,1.0f,1.0f),ICON_FA_FOLDER_O); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByTypeFile,"",ImVec4(0.7f,0.7f,0.7f,1.0f),ICON_FA_FILE_O); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".fur",ImVec4(0.5f,1.0f,0.5f,1.0f),ICON_FA_FILE); From e1eee0540e8b860f5296f1d06b7fa0eb6ff8aeb7 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 13 Mar 2022 23:10:52 -0500 Subject: [PATCH 234/637] update readme to add CI badge --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index e1d97726e..a1b648540 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,8 @@ some people have provided packages for Unix/Unix-like distributions. here's a li # developer info +[![Build furnace](https://github.com/tildearrow/furnace/actions/workflows/build.yml/badge.svg)](https://github.com/tildearrow/furnace/actions/workflows/build.yml) + **NOTE: do not download the project's source as a .zip or .tar.gz as these do not include the project's submodules which are necessary to proceed with building.** ## dependencies From 4ba6058b03a57f370f6d34a6ce44483336259cda Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 13 Mar 2022 23:24:57 -0500 Subject: [PATCH 235/637] GUI: system file dialog on by default in order to make a test build --- src/gui/gui.h | 2 +- src/gui/settings.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/gui.h b/src/gui/gui.h index 9bf68e529..9e5585475 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -587,7 +587,7 @@ class FurnaceGUI { insFocusesPattern(1), stepOnInsert(0), unifiedDataView(0), - sysFileDialog(0), + sysFileDialog(1), maxUndoSteps(100), mainFontPath(""), patFontPath(""), diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index be0e979a8..98724bc59 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -898,7 +898,7 @@ void FurnaceGUI::syncSettings() { settings.insFocusesPattern=e->getConfInt("insFocusesPattern",1); settings.stepOnInsert=e->getConfInt("stepOnInsert",0); settings.unifiedDataView=e->getConfInt("unifiedDataView",0); - settings.sysFileDialog=e->getConfInt("sysFileDialog",0); + settings.sysFileDialog=e->getConfInt("sysFileDialog",1); clampSetting(settings.mainFontSize,2,96); clampSetting(settings.patFontSize,2,96); From 71702ee59555c9d9e5149d09674a46a99c28c7d8 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 13 Mar 2022 23:37:12 -0500 Subject: [PATCH 236/637] update release scripts --- scripts/release-win32.sh | 4 ++++ scripts/release-win64.sh | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/scripts/release-win32.sh b/scripts/release-win32.sh index ddb9f806c..2257a595e 100755 --- a/scripts/release-win32.sh +++ b/scripts/release-win32.sh @@ -31,3 +31,7 @@ cp -r ../../papers papers || exit 1 cp -r ../../demos demos || exit 1 zip -r furnace.zip LICENSE.txt furnace.exe README.txt papers demos + +furName=$(git describe --tags | sed "s/v0/0/") + +mv furnace.zip furnace-"$furName"-win32.zip diff --git a/scripts/release-win64.sh b/scripts/release-win64.sh index c02892e86..db580e093 100755 --- a/scripts/release-win64.sh +++ b/scripts/release-win64.sh @@ -31,3 +31,7 @@ cp -r ../../papers papers || exit 1 cp -r ../../demos demos || exit 1 zip -r furnace.zip LICENSE.txt furnace.exe README.txt papers demos + +furName=$(git describe --tags | sed "s/v0/0/") + +mv furnace.zip furnace-"$furName"-win64.zip From 394a440f3d8085b366270ef205f708d4ba6ebe8d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 13 Mar 2022 23:37:25 -0500 Subject: [PATCH 237/637] prepare for unified ins/wave/sample list --- src/gui/settings.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 98724bc59..a10795fb5 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -408,6 +408,11 @@ void FurnaceGUI::drawSettings() { settings.macroView=macroViewB; } + bool unifiedDataViewB=settings.unifiedDataView; + if (ImGui::Checkbox("Unified instrument/wavetable/sample list",&unifiedDataViewB)) { + settings.unifiedDataView=unifiedDataViewB; + } + bool chipNamesB=settings.chipNames; if (ImGui::Checkbox("Use chip names instead of system names",&chipNamesB)) { settings.chipNames=chipNamesB; From 5b2ec3ee8791825a434c7e2cf47e5ce56bee1373 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 14 Mar 2022 00:26:06 -0500 Subject: [PATCH 238/637] NES: fix slide up fixes #208 --- src/engine/platform/nes.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/engine/platform/nes.cpp b/src/engine/platform/nes.cpp index 897d8e842..06fbc725c 100644 --- a/src/engine/platform/nes.cpp +++ b/src/engine/platform/nes.cpp @@ -201,6 +201,7 @@ void DivPlatformNES::tick() { } else { chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true)-1; if (chan[i].freq>2047) chan[i].freq=2047; + if (chan[i].freq<0) chan[i].freq=0; } if (chan[i].keyOn) { //rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(ins->gb.soundLen&63))); From a96fd5727e75feb54e6e44b454f1f08c17cd498a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 14 Mar 2022 00:26:27 -0500 Subject: [PATCH 239/637] GUI: begin working on unified ins/wave/sample list --- src/gui/gui.cpp | 88 +++++++++++++++++++++++++++++++++++--------- src/gui/gui.h | 3 ++ src/gui/guiConst.cpp | 29 +++++++++++++++ src/gui/guiConst.h | 1 + src/gui/insEdit.cpp | 50 +------------------------ 5 files changed, 104 insertions(+), 67 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index c16b7d72e..3143f6843 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -32,6 +32,7 @@ #include "ImGuiFileDialog.h" #include "IconsFontAwesome4.h" #include "misc/cpp/imgui_stdlib.h" +#include "plot_nolerp.h" #include "guiConst.h" #include "intConst.h" #include @@ -1090,6 +1091,13 @@ void FurnaceGUI::drawInsList() { } ImGui::Separator(); if (ImGui::BeginTable("InsListScroll",1,ImGuiTableFlags_ScrollY)) { + if (settings.unifiedDataView) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text(ICON_FA_TASKS " Instruments"); + ImGui::Indent(); + } + for (int i=0; i<(int)e->song.ins.size(); i++) { DivInstrument* ins=e->song.ins[i]; String name; @@ -1214,12 +1222,32 @@ void FurnaceGUI::drawInsList() { } ImGui::PopStyleColor(); if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("%s",(ins->type>DIV_INS_MAX)?"Unknown":insTypes[ins->type]); if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { insEditOpen=true; nextWindow=GUI_WINDOW_INS_EDIT; } } } + + if (settings.unifiedDataView) { + ImGui::Unindent(); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text(ICON_FA_AREA_CHART " Wavetables"); + ImGui::Indent(); + actualWaveList(); + ImGui::Unindent(); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text(ICON_FA_VOLUME_UP " Samples"); + ImGui::Indent(); + actualSampleList(); + ImGui::Unindent(); + } + ImGui::EndTable(); } } @@ -1231,6 +1259,47 @@ const char* sampleNote[12]={ "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" }; + +void FurnaceGUI::actualWaveList() { + float wavePreview[256]; + for (int i=0; i<(int)e->song.wave.size(); i++) { + DivWavetable* wave=e->song.wave[i]; + for (int i=0; ilen; i++) { + wavePreview[i]=wave->data[i]; + } + if (wave->len>0) wavePreview[wave->len]=wave->data[wave->len-1]; + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + if (ImGui::Selectable(fmt::sprintf("%d##_WAVE%d\n",i,i).c_str(),curWave==i)) { + curWave=i; + } + if (ImGui::IsItemHovered()) { + if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { + waveEditOpen=true; + } + } + ImGui::SameLine(); + PlotNoLerp(fmt::sprintf("##_WAVEP%d",i).c_str(),wavePreview,wave->len+1,0,NULL,0,wave->max); + } +} + +void FurnaceGUI::actualSampleList() { + for (int i=0; i<(int)e->song.sample.size(); i++) { + DivSample* sample=e->song.sample[i]; + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + if (ImGui::Selectable(fmt::sprintf("%d: %s##_SAM%d",i,sample->name,i).c_str(),curSample==i)) { + curSample=i; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Bank %d: %s",i/12,sampleNote[i%12]); + if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { + sampleEditOpen=true; + } + } + } +} + void FurnaceGUI::drawSampleList() { if (nextWindow==GUI_WINDOW_SAMPLE_LIST) { sampleListOpen=true; @@ -1272,24 +1341,7 @@ void FurnaceGUI::drawSampleList() { } ImGui::Separator(); if (ImGui::BeginTable("SampleListScroll",1,ImGuiTableFlags_ScrollY)) { - for (int i=0; i<(int)e->song.sample.size(); i++) { - DivSample* sample=e->song.sample[i]; - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - if ((i%12)==0) { - if (i>0) ImGui::Unindent(); - ImGui::Text("Bank %d",i/12); - ImGui::Indent(); - } - if (ImGui::Selectable(fmt::sprintf("%s: %s##_SAM%d",sampleNote[i%12],sample->name,i).c_str(),curSample==i)) { - curSample=i; - } - if (ImGui::IsItemHovered()) { - if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { - sampleEditOpen=true; - } - } - } + actualSampleList(); ImGui::EndTable(); } ImGui::Unindent(); diff --git a/src/gui/gui.h b/src/gui/gui.h index 9e5585475..22e8036e0 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -721,6 +721,9 @@ class FurnaceGUI { void patternRow(int i, bool isPlaying, float lineHeight, int chans, int ord); + void actualWaveList(); + void actualSampleList(); + void drawEditControls(); void drawSongInfo(); void drawOrders(); diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index c872b05bd..83220d252 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -19,6 +19,7 @@ // guiConst: constants used in the GUI like arrays, strings and other stuff #include "guiConst.h" +#include "../engine/instrument.h" const int opOrder[4]={ 0, 2, 1, 3 @@ -64,3 +65,31 @@ const char* pitchLabel[11]={ "1/6", "1/5", "1/4", "1/3", "1/2", "1x", "2x", "3x", "4x", "5x", "6x" }; +const char* insTypes[DIV_INS_MAX]={ + "Standard", + "FM (4-operator)", + "Game Boy", + "C64", + "Amiga/Sample", + "PC Engine", + "AY-3-8910/SSG", + "AY8930", + "TIA", + "SAA1099", + "VIC", + "PET", + "VRC6", + "FM (OPLL)", + "FM (OPL)", + "FDS", + "Virtual Boy", + "Namco 163", + "Konami SCC", + "FM (OPZ)", + "POKEY", + "PC Beeper", + "WonderSwan", + "Atari Lynx", + "VERA", + "X1-010" +}; \ No newline at end of file diff --git a/src/gui/guiConst.h b/src/gui/guiConst.h index 3d252b536..9f4159159 100644 --- a/src/gui/guiConst.h +++ b/src/gui/guiConst.h @@ -23,3 +23,4 @@ extern const int opOrder[4]; extern const char* noteNames[180]; extern const char* noteNamesG[180]; extern const char* pitchLabel[11]; +extern const char* insTypes[]; \ No newline at end of file diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index e653b097f..828eced3a 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -27,35 +27,6 @@ #include #include "plot_nolerp.h" -const char* insTypes[DIV_INS_MAX]={ - "Standard", - "FM (4-operator)", - "Game Boy", - "C64", - "Amiga/Sample", - "PC Engine", - "AY-3-8910/SSG", - "AY8930", - "TIA", - "SAA1099", - "VIC", - "PET", - "VRC6", - "FM (OPLL)", - "FM (OPL)", - "FDS", - "Virtual Boy", - "Namco 163", - "Konami SCC", - "FM (OPZ)", - "POKEY", - "PC Beeper", - "WonderSwan", - "Atari Lynx", - "VERA", - "X1-010" -}; - const char* ssgEnvTypes[8]={ "Down Down Down", "Down.", "Down Up Down Up", "Down UP", "Up Up Up", "Up.", "Up Down Up Down", "Up DOWN" }; @@ -1787,7 +1758,6 @@ void FurnaceGUI::drawWaveList() { nextWindow=GUI_WINDOW_NOTHING; } if (!waveListOpen) return; - float wavePreview[256]; if (ImGui::Begin("Wavetables",&waveListOpen)) { if (ImGui::Button(ICON_FA_PLUS "##WaveAdd")) { doAction(GUI_ACTION_WAVE_LIST_ADD); @@ -1818,25 +1788,7 @@ void FurnaceGUI::drawWaveList() { } ImGui::Separator(); if (ImGui::BeginTable("WaveListScroll",1,ImGuiTableFlags_ScrollY)) { - for (int i=0; i<(int)e->song.wave.size(); i++) { - DivWavetable* wave=e->song.wave[i]; - for (int i=0; ilen; i++) { - wavePreview[i]=wave->data[i]; - } - if (wave->len>0) wavePreview[wave->len]=wave->data[wave->len-1]; - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - if (ImGui::Selectable(fmt::sprintf("%d##_WAVE%d\n",i,i).c_str(),curWave==i)) { - curWave=i; - } - if (ImGui::IsItemHovered()) { - if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { - waveEditOpen=true; - } - } - ImGui::SameLine(); - PlotNoLerp(fmt::sprintf("##_WAVEP%d",i).c_str(),wavePreview,wave->len+1,0,NULL,0,wave->max); - } + actualWaveList(); ImGui::EndTable(); } } From e009fc64f2a58cbea6c8ee8093bd61998ee3ddd9 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 14 Mar 2022 01:23:31 -0500 Subject: [PATCH 240/637] allow instrument change during slides --- papers/format.md | 4 +++- src/engine/engine.h | 7 ++++--- src/engine/fileOps.cpp | 14 ++++++++++++-- src/engine/playback.cpp | 6 ++++++ src/engine/song.h | 4 +++- src/gui/gui.cpp | 4 ++++ 6 files changed, 32 insertions(+), 7 deletions(-) diff --git a/papers/format.md b/papers/format.md index b04deac74..d5b9c67e6 100644 --- a/papers/format.md +++ b/papers/format.md @@ -29,6 +29,7 @@ furthermore, an `or reserved` indicates this field is always present, but is res the format versions are: +- 66: Furnace dev66 - 65: Furnace dev65 - 64: Furnace dev64 - 63: Furnace dev63 @@ -207,7 +208,8 @@ size | description 1 | continuous vibrato (>=62) or reserved 1 | broken DAC mode (>=64) or reserved 1 | one tick cut (>=65) or reserved - 2 | reserved + 1 | instrument change allowed during porta (>=66) or reserved + 1 | reserved 4?? | pointers to instruments 4?? | pointers to wavetables 4?? | pointers to samples diff --git a/src/engine/engine.h b/src/engine/engine.h index fc70c8730..b75fc2f07 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -37,8 +37,8 @@ warnings+=(String("\n")+x); \ } -#define DIV_VERSION "dev65" -#define DIV_ENGINE_VERSION 65 +#define DIV_VERSION "dev66" +#define DIV_ENGINE_VERSION 66 enum DivStatusView { DIV_STATUS_NOTHING=0, @@ -69,7 +69,7 @@ enum DivHaltPositions { struct DivChannelState { std::vector delayed; - int note, oldNote, pitch, portaSpeed, portaNote; + int note, oldNote, lastIns, pitch, portaSpeed, portaNote; int volume, volSpeed, cut, rowDelay, volMax; int delayOrder, delayRow, retrigSpeed, retrigTick; int vibratoDepth, vibratoRate, vibratoPos, vibratoDir, vibratoFine; @@ -80,6 +80,7 @@ struct DivChannelState { DivChannelState(): note(-1), oldNote(-1), + lastIns(-1), pitch(0), portaSpeed(-1), portaNote(-1), diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 3c1a9d2a1..524531f85 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -142,6 +142,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { ds.ignoreDuplicateSlides=true; ds.brokenDACMode=true; ds.oneTickCut=false; + ds.newInsTriggersInPorta=true; // 1.1 compat flags if (ds.version>24) { @@ -807,6 +808,9 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { if (ds.version<65) { ds.oneTickCut=false; } + if (ds.version<66) { + ds.newInsTriggersInPorta=false; + } ds.isDMF=false; reader.readS(); // reserved @@ -993,7 +997,12 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { } else { reader.readC(); } - for (int i=0; i<2; i++) reader.readC(); + if (ds.version>=66) { + ds.newInsTriggersInPorta=reader.readC(); + } else { + reader.readC(); + } + for (int i=0; i<1; i++) reader.readC(); } else { for (int i=0; i<20; i++) reader.readC(); } @@ -1437,7 +1446,8 @@ SafeWriter* DivEngine::saveFur() { w->writeC(song.continuousVibrato); w->writeC(song.brokenDACMode); w->writeC(song.oneTickCut); - for (int i=0; i<2; i++) { + w->writeC(song.newInsTriggersInPorta); + for (int i=0; i<1; i++) { w->writeC(0); } diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 4626635b0..8a543fee2 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -654,6 +654,12 @@ void DivEngine::processRow(int i, bool afterDelay) { // instrument if (pat->data[whatRow][2]!=-1) { dispatchCmd(DivCommand(DIV_CMD_INSTRUMENT,i,pat->data[whatRow][2])); + if (chan[i].lastIns!=pat->data[whatRow][2]) { + chan[i].lastIns=pat->data[whatRow][2]; + if (chan[i].inPorta && song.newInsTriggersInPorta) { + dispatchCmd(DivCommand(DIV_CMD_NOTE_ON,i,DIV_NOTE_NULL)); + } + } } // note if (pat->data[whatRow][0]==100) { // note off diff --git a/src/engine/song.h b/src/engine/song.h index 5747aa511..7fc6ec7fd 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -297,6 +297,7 @@ struct DivSong { bool continuousVibrato; bool brokenDACMode; bool oneTickCut; + bool newInsTriggersInPorta; DivOrders orders; std::vector ins; @@ -363,7 +364,8 @@ struct DivSong { stopPortaOnNoteOff(false), continuousVibrato(false), brokenDACMode(false), - oneTickCut(false) { + oneTickCut(false), + newInsTriggersInPorta(true) { for (int i=0; i<32; i++) { system[i]=DIV_SYSTEM_NULL; systemVol[i]=64; diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 3143f6843..24b7d46be 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2216,6 +2216,10 @@ void FurnaceGUI::drawCompatFlags() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("behavior changed in 0.6"); } + ImGui::Checkbox("Allow instrument change during slides",&e->song.newInsTriggersInPorta); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("behavior changed in 0.6"); + } } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_COMPAT_FLAGS; ImGui::End(); From 67d516fcee95ffd21843bca1d5c821f8afa9b4e7 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 14 Mar 2022 01:38:11 -0500 Subject: [PATCH 241/637] Genesis: prioritize DAC writes --- src/engine/platform/genesis.cpp | 10 +++++----- src/engine/platform/genesis.h | 4 ++-- src/engine/platform/genesisshared.h | 3 ++- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index b3096e72a..75c54af68 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -92,7 +92,7 @@ void DivPlatformGenesis::acquire_nuked(short* bufL, short* bufR, size_t start, s DivSample* s=parent->getSample(dacSample); if (s->samples>0) { if (!isMuted[5]) { - immWrite(0x2a,(unsigned char)s->data8[dacPos]+0x80); + urgentWrite(0x2a,(unsigned char)s->data8[dacPos]+0x80); } if (++dacPos>=s->samples) { if (s->loopStart>=0 && s->loopStart<=(int)s->samples) { @@ -121,7 +121,7 @@ void DivPlatformGenesis::acquire_nuked(short* bufL, short* bufR, size_t start, s //printf("write: %x = %.2x\n",w.addr,w.val); lastBusy=0; regPool[w.addr&0x1ff]=w.val; - writes.pop(); + writes.pop_front(); } else { lastBusy++; if (fm.write_busy==0) { @@ -159,7 +159,7 @@ void DivPlatformGenesis::acquire_ymfm(short* bufL, short* bufR, size_t start, si DivSample* s=parent->getSample(dacSample); if (s->samples>0) { if (!isMuted[5]) { - immWrite(0x2a,(unsigned char)s->data8[dacPos]+0x80); + urgentWrite(0x2a,(unsigned char)s->data8[dacPos]+0x80); } if (++dacPos>=s->samples) { if (s->loopStart>=0 && s->loopStart<=(int)s->samples) { @@ -184,7 +184,7 @@ void DivPlatformGenesis::acquire_ymfm(short* bufL, short* bufR, size_t start, si fm_ymfm->write(0x0+((w.addr>>8)<<1),w.addr); fm_ymfm->write(0x1+((w.addr>>8)<<1),w.val); regPool[w.addr&0x1ff]=w.val; - writes.pop(); + writes.pop_front(); lastBusy=1; } @@ -782,7 +782,7 @@ int DivPlatformGenesis::getRegisterPoolSize() { } void DivPlatformGenesis::reset() { - while (!writes.empty()) writes.pop(); + while (!writes.empty()) writes.pop_front(); memset(regPool,0,512); if (useYMFM) { fm_ymfm->reset(); diff --git a/src/engine/platform/genesis.h b/src/engine/platform/genesis.h index cd7b0396c..095744dfe 100644 --- a/src/engine/platform/genesis.h +++ b/src/engine/platform/genesis.h @@ -20,7 +20,7 @@ #ifndef _GENESIS_H #define _GENESIS_H #include "../dispatch.h" -#include +#include #include "../../../extern/Nuked-OPN2/ym3438.h" #include "sound/ymfm/ymfm_opn.h" @@ -68,7 +68,7 @@ class DivPlatformGenesis: public DivDispatch { bool addrOrVal; QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {} }; - std::queue writes; + std::deque writes; ym3438_t fm; int delay; unsigned char lastBusy; diff --git a/src/engine/platform/genesisshared.h b/src/engine/platform/genesisshared.h index d2402a60d..01d45f955 100644 --- a/src/engine/platform/genesisshared.h +++ b/src/engine/platform/genesisshared.h @@ -43,6 +43,7 @@ static int orderedOps[4]={ }; #define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;} -#define immWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } +#define immWrite(a,v) if (!skipRegisterWrites) {writes.push_back(QueuedWrite(a,v)); if (dumpWrites) {addWrite(a,v);} } +#define urgentWrite(a,v) if (!skipRegisterWrites) {if (writes.front().addrOrVal) {writes.push_back(QueuedWrite(a,v));} else {writes.push_front(QueuedWrite(a,v));}; if (dumpWrites) {addWrite(a,v);} } #include "fmshared_OPN.h" From df5c1ae8596a05a7e500020dcd9015de3647e5aa Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 14 Mar 2022 01:54:31 -0500 Subject: [PATCH 242/637] OPL: finally fix that order issue (kind of) --- src/engine/platform/opl.cpp | 57 ++++++++++++++++++++----------------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index d84265530..ffe4435dc 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -456,7 +456,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { chan[c.chan].outVol=chan[c.chan].vol; } if (chan[c.chan].insChanged) { - int ops=(slots[3][c.chan]!=255 && ins->fm.ops==4 && oplType==3)?4:2; + int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2; for (int i=0; i1) { + rWrite(baseAddr+ADDR_WS,op.ws&((oplType==3)?7:3)); + } } - rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); - rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); - if (chan[i].active) { - chan[i].keyOn=true; - chan[i].freqChanged=true; + + if (isMuted[i]) { + rWrite(chanMap[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&1)|(chan[i].state.fb<<1)); + if (ops==4) { + rWrite(chanMap[i+1]+ADDR_LR_FB_ALG,((chan[i].state.alg>>1)&1)|(chan[i].state.fb<<1)); + } + } else { + rWrite(chanMap[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&1)|(chan[i].state.fb<<1)|((chan[i].pan&3)<<4)); + if (ops==4) { + rWrite(chanMap[i+1]+ADDR_LR_FB_ALG,((chan[i].state.alg>>1)&1)|(chan[i].state.fb<<1)|((chan[i].pan&3)<<4)); + } } } - if (dacMode) { - rWrite(0x2b,0x80); - } - immWrite(0x22,lfoValue); - */ } void DivPlatformOPL::toggleRegisterDump(bool enable) { From 32581bb228171f8e65aea834b2196dc4169a115a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 14 Mar 2022 01:57:46 -0500 Subject: [PATCH 243/637] OPL: volume --- src/engine/platform/opl.cpp | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index ffe4435dc..d453dab2b 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -524,21 +524,23 @@ int DivPlatformOPL::dispatch(DivCommand c) { if (!chan[c.chan].std.hasVol) { chan[c.chan].outVol=c.value; } - /* - for (int i=0; i<4; i++) { - unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; - DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2; + for (int i=0; i Date: Mon, 14 Mar 2022 02:30:25 -0500 Subject: [PATCH 244/637] OPL: kind of fix 4-op mode --- src/engine/platform/opl.cpp | 57 ++++++++++++++++++++++++++++++------- src/engine/platform/opl.h | 5 ++-- 2 files changed, 49 insertions(+), 13 deletions(-) diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index d453dab2b..fe446ad23 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -349,10 +349,21 @@ void DivPlatformOPL::tick() { if (chan[i].keyOn || chan[i].keyOff) { immWrite(chanMap[i]+ADDR_FREQH,0x00|(chan[i].freqH&31)); + if (chan[i].state.ops==4 && i<6) { + immWrite(chanMap[i+1]+ADDR_FREQH,0x00|(chan[i].freqH&31)); + } chan[i].keyOff=false; } } + if (update4OpMask) { + update4OpMask=false; + if (oplType==3) { + unsigned char opMask=chan[0].fourOp|(chan[2].fourOp<<1)|(chan[4].fourOp<<2)|(chan[6].fourOp<<3)|(chan[8].fourOp<<4)|(chan[10].fourOp<<5); + rWrite(0x104,opMask); + } + } + for (int i=0; i<512; i++) { if (pendingWrites[i]!=oldWrites[i]) { immWrite(i,pendingWrites[i]&0xff); @@ -368,12 +379,21 @@ void DivPlatformOPL::tick() { chan[i].freqH=freqt>>8; chan[i].freqL=freqt&0xff; immWrite(chanMap[i]+ADDR_FREQ,chan[i].freqL); + if (chan[i].state.ops==4 && i<6) { + immWrite(chanMap[i+1]+ADDR_FREQ,chan[i].freqL); + } } if (chan[i].keyOn) { immWrite(chanMap[i]+ADDR_FREQH,chan[i].freqH|(0x20)); + if (chan[i].state.ops==4 && i<6) { + immWrite(chanMap[i+1]+ADDR_FREQH,chan[i].freqH|(0x20)); + } chan[i].keyOn=false; } else if (chan[i].freqChanged) { immWrite(chanMap[i]+ADDR_FREQH,chan[i].freqH|(chan[i].active<<5)); + if (chan[i].state.ops==4 && i<6) { + immWrite(chanMap[i+1]+ADDR_FREQH,chan[i].freqH|(chan[i].active<<5)); + } } chan[i].freqChanged=false; } @@ -457,6 +477,8 @@ int DivPlatformOPL::dispatch(DivCommand c) { } if (chan[c.chan].insChanged) { int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2; + chan[c.chan].fourOp=(ops==4); + update4OpMask=true; for (int i=0; i0)<<1)|((c.value>>4)>0); + } + int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2; + if (isMuted[c.chan]) { + rWrite(chanMap[c.chan]+ADDR_LR_FB_ALG,(chan[c.chan].state.alg&1)|(chan[c.chan].state.fb<<1)); + if (ops==4) { + rWrite(chanMap[c.chan+1]+ADDR_LR_FB_ALG,((chan[c.chan].state.alg>>1)&1)|(chan[c.chan].state.fb<<1)); + } + } else { + rWrite(chanMap[c.chan]+ADDR_LR_FB_ALG,(chan[c.chan].state.alg&1)|(chan[c.chan].state.fb<<1)|((chan[c.chan].pan&3)<<4)); + if (ops==4) { + rWrite(chanMap[c.chan+1]+ADDR_LR_FB_ALG,((chan[c.chan].state.alg>>1)&1)|(chan[c.chan].state.fb<<1)|((chan[c.chan].pan&3)<<4)); + } } //rWrite(chanOffs[c.chan]+ADDR_LRAF,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|(chan[c.chan].state.fms&7)|((chan[c.chan].state.ams&3)<<4)); break; @@ -687,6 +715,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { void DivPlatformOPL::forceIns() { for (int i=0; i<18; i++) { int ops=(slots[3][i]!=255 && chan[i].state.ops==4 && oplType==3)?4:2; + chan[i].fourOp=(ops==4); for (int j=0; j Date: Mon, 14 Mar 2022 02:39:10 -0500 Subject: [PATCH 245/637] fix 4-op mode for real --- src/engine/platform/opl.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index fe446ad23..7fe8bca92 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -360,7 +360,8 @@ void DivPlatformOPL::tick() { update4OpMask=false; if (oplType==3) { unsigned char opMask=chan[0].fourOp|(chan[2].fourOp<<1)|(chan[4].fourOp<<2)|(chan[6].fourOp<<3)|(chan[8].fourOp<<4)|(chan[10].fourOp<<5); - rWrite(0x104,opMask); + immWrite(0x104,opMask); + printf("updating opMask to %.2x\n",opMask); } } From 714d189b57abc2deb78cbb98c97031f1d23c92e2 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 14 Mar 2022 02:47:04 -0500 Subject: [PATCH 246/637] OPL: more work and channel muting --- src/engine/platform/opl.cpp | 55 ++++++++++++++++++++++++++----------- src/engine/platform/opl.h | 2 +- 2 files changed, 40 insertions(+), 17 deletions(-) diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index 7fe8bca92..84e8dd260 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -222,7 +222,7 @@ void DivPlatformOPL::acquire(short* bufL, short* bufR, size_t start, size_t len) } void DivPlatformOPL::tick() { - for (int i=0; i<20; i++) { + for (int i=0; icalcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq)); if (chan[i].freq>131071) chan[i].freq=131071; @@ -445,25 +445,42 @@ int DivPlatformOPL::toFreq(int freq) { void DivPlatformOPL::muteChannel(int ch, bool mute) { isMuted[ch]=mute; - /* - for (int j=0; j<4; j++) { - unsigned short baseAddr=chanOffs[ch]|opOffs[j]; - DivInstrumentFM::Operator& op=chan[ch].state.op[j]; + int ops=(slots[3][ch]!=255 && chan[ch].state.ops==4 && oplType==3)?4:2; + chan[ch].fourOp=(ops==4); + update4OpMask=true; + for (int i=0; i>1)&1)|(chan[ch].state.fb<<1)); + } + } else { + rWrite(chanMap[ch]+ADDR_LR_FB_ALG,(chan[ch].state.alg&1)|(chan[ch].state.fb<<1)|((chan[ch].pan&3)<<4)); + if (ops==4) { + rWrite(chanMap[ch+1]+ADDR_LR_FB_ALG,((chan[ch].state.alg>>1)&1)|(chan[ch].state.fb<<1)|((chan[ch].pan&3)<<4)); + } + } } int DivPlatformOPL::dispatch(DivCommand c) { + // TODO: drums mode! + if (c.chan>=melodicChans) return 0; switch (c.cmd) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(chan[c.chan].ins); @@ -714,7 +731,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { } void DivPlatformOPL::forceIns() { - for (int i=0; i<18; i++) { + for (int i=0; i Date: Mon, 14 Mar 2022 19:22:12 +0900 Subject: [PATCH 247/637] Add Bubble System Support Bubble System 2 channel Wavetable sound generator logic is configuration with K005289, 4 bit PROM and DAC. K005289 controls pitch and 5 bit address generator per channel, Waveform select and Volume control is tied to AY-3-8910 ports. (each port for per channels) these configuration is better known as K005289, the part of logic. furnace emulates this configurations as single system, waveform format is 15 level and 32 width. --- CMakeLists.txt | 3 + papers/doc/4-instrument/README.md | 1 + papers/doc/4-instrument/pce.md | 2 +- papers/doc/4-instrument/scc.md | 7 + papers/doc/5-wave/README.md | 2 +- papers/doc/7-systems/README.md | 1 + papers/doc/7-systems/bubblesystem.md | 13 + src/engine/dispatchContainer.cpp | 4 + src/engine/platform/k005289.cpp | 334 ++++++++++++++++++ src/engine/platform/k005289.h | 84 +++++ src/engine/platform/sound/k005289/k005289.cpp | 45 +++ src/engine/platform/sound/k005289/k005289.hpp | 67 ++++ src/engine/platform/sound/x1_010/x1_010.hpp | 21 +- src/engine/playback.cpp | 9 + src/engine/song.h | 3 +- src/engine/sysDef.cpp | 20 +- src/gui/gui.cpp | 12 + src/gui/insEdit.cpp | 2 +- 18 files changed, 616 insertions(+), 14 deletions(-) create mode 100644 papers/doc/4-instrument/scc.md create mode 100644 papers/doc/7-systems/bubblesystem.md create mode 100644 src/engine/platform/k005289.cpp create mode 100644 src/engine/platform/k005289.h create mode 100644 src/engine/platform/sound/k005289/k005289.cpp create mode 100644 src/engine/platform/sound/k005289/k005289.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index de4eefbcc..9b9a23769 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -271,6 +271,8 @@ src/engine/platform/sound/x1_010/x1_010.cpp src/engine/platform/sound/swan.cpp +src/engine/platform/sound/k005289/k005289.cpp + src/engine/platform/ym2610Interface.cpp src/engine/blip_buf.c @@ -316,6 +318,7 @@ src/engine/platform/x1_010.cpp src/engine/platform/lynx.cpp src/engine/platform/swan.cpp src/engine/platform/vera.cpp +src/engine/platform/k005289.cpp src/engine/platform/dummy.cpp ) diff --git a/papers/doc/4-instrument/README.md b/papers/doc/4-instrument/README.md index 5592fb4e4..6c5c3f5f9 100644 --- a/papers/doc/4-instrument/README.md +++ b/papers/doc/4-instrument/README.md @@ -26,6 +26,7 @@ depending on the instrument type, there are currently 13 different types of an i - [Atari Lynx](lynx.md) - for use with Atari Lynx handheld console. - [VERA](vera.md) - for use with Commander X16 VERA. - [Seta/Allumer X1-010](x1_010.md) - for use with Wavetable portion in Seta/Allumer X1-010. +- [Konami SCC/Bubble System](scc.md) - for use with Konami SCC, Wavetable portion in Bubble System's sound hardware. # macros diff --git a/papers/doc/4-instrument/pce.md b/papers/doc/4-instrument/pce.md index 7bff37c70..50ac335bf 100644 --- a/papers/doc/4-instrument/pce.md +++ b/papers/doc/4-instrument/pce.md @@ -3,5 +3,5 @@ PCE instrument editor consists of only three macros, almost like TIA: - [Volume] - volume sequence -- [Arpeggio] - pitch sequencr +- [Arpeggio] - pitch sequence - [Waveform] - spicifies wavetables sequence diff --git a/papers/doc/4-instrument/scc.md b/papers/doc/4-instrument/scc.md new file mode 100644 index 000000000..9cce33168 --- /dev/null +++ b/papers/doc/4-instrument/scc.md @@ -0,0 +1,7 @@ +# Konami SCC instrument editor + +SCC instrument editor consists of only three macros: + +- [Volume] - volume sequence +- [Arpeggio] - pitch sequence +- [Waveform] - spicifies wavetables sequence diff --git a/papers/doc/5-wave/README.md b/papers/doc/5-wave/README.md index 5e234bf65..324691ba0 100644 --- a/papers/doc/5-wave/README.md +++ b/papers/doc/5-wave/README.md @@ -2,4 +2,4 @@ Wavetable synthizers, in context of Furnace, are sound sources that operate on extremely short n-bit PCM streams. By extremely short, no more than 256 bytes. This amount of space is nowhere near enough to store an actual sampled sound, it allows certain amount of freedom to define a waveform shape. As of Furnace 0.5.8, wavetable editor affects PC Engine, WonderSwan and channel 3 of Game Boy. -Furnace's wavetable editor is rather simple, you can draw the waveform using mouse or by pasting an MML bit stream in the input field. Maximum wave width (length) is 256 bytes, and maximum wave height (depth) is 256. NOTE: Game Boy, PCE and WonderSwan can handle max 32 byte waveforms, X1-010 can handle max 128 byte waveforms as of now, with 16-level height for GB, X1-010 Envelope and WS, and 32-level height for PCE. If larger wave will be defined for these systems, it will be squashed to fit within the constraints of the system. +Furnace's wavetable editor is rather simple, you can draw the waveform using mouse or by pasting an MML bit stream in the input field. Maximum wave width (length) is 256 bytes, and maximum wave height (depth) is 256. NOTE: Game Boy, PCE, WonderSwan, Bubble System can handle max 32 byte waveforms, X1-010 can handle max 128 byte waveforms as of now, with 16-level height for GB, X1-010 Envelope, WS and Bubble System, and 32-level height for PCE. If larger wave will be defined for these systems, it will be squashed to fit within the constraints of the system. diff --git a/papers/doc/7-systems/README.md b/papers/doc/7-systems/README.md index a6648a329..405d0563c 100644 --- a/papers/doc/7-systems/README.md +++ b/papers/doc/7-systems/README.md @@ -20,5 +20,6 @@ this is a list of systems that Furnace supports, including each system's effects - [Microchip AY8930](ay8930.md) - [Seta/Allumer X1-010](x1_010.md) - [WonderSwan](wonderswan.md) +- [Bubble System/K005289](bubblesystem.md) Furnace also reads .dmf files with the [Yamaha YMU759](ymu759.md) system, but does not emulate the chip at all. diff --git a/papers/doc/7-systems/bubblesystem.md b/papers/doc/7-systems/bubblesystem.md new file mode 100644 index 000000000..1dc18cab1 --- /dev/null +++ b/papers/doc/7-systems/bubblesystem.md @@ -0,0 +1,13 @@ +# Bubble System/K005289 + +a Konami's 2 channel wavetable sound generator logic used at their arcade hardware Bubble System. + +It's configured with K005289, 4 bit PROM and DAC. + +Also known as K005289, but that's just part of the logic used for pitch and wavetable ROM address. Waveform select and Volume control are tied with AY-3-8910 port. + +furnace emulates this configurations as single system, waveform format is 15 level and 32 width. + +# effects + +- `10xx`: change wave. diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index ca2d99ce2..3dcf17a8f 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -45,6 +45,7 @@ #include "platform/x1_010.h" #include "platform/swan.h" #include "platform/lynx.h" +#include "platform/k005289.h" #include "platform/dummy.h" #include "../ta-log.h" #include "song.h" @@ -267,6 +268,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_VERA: dispatch=new DivPlatformVERA; break; + case DIV_SYSTEM_K005289: + dispatch=new DivPlatformK005289; + break; default: logW("this system is not supported yet! using dummy platform.\n"); dispatch=new DivPlatformDummy; diff --git a/src/engine/platform/k005289.cpp b/src/engine/platform/k005289.cpp new file mode 100644 index 000000000..b50a76af2 --- /dev/null +++ b/src/engine/platform/k005289.cpp @@ -0,0 +1,334 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "k005289.h" +#include "../engine.h" +#include + +#define CHIP_DIVIDER 32 + +#define rWrite(a,v) {if(!skipRegisterWrites) {regPool[a]=v; if(dumpWrites) addWrite(a,v); }} + +const char* regCheatSheetK005289[]={ + // K005289 + "Freq_A", "0", + "Freq_B", "1", + // PROM, DAC control from External logic (Connected to AY PSG ports on Bubble System) + "WaveVol_A", "2", + "WaveVol_B", "3", + NULL +}; + +const char** DivPlatformK005289::getRegisterSheet() { + return regCheatSheetK005289; +} + +const char* DivPlatformK005289::getEffectName(unsigned char effect) { + switch (effect) { + case 0x10: + return "10xx: Change waveform"; + break; + } + return NULL; +} + +void DivPlatformK005289::acquire(short* bufL, short* bufR, size_t start, size_t len) { + for (size_t h=start; htick(); + + // Wavetable part + for (int i=0; i<2; i++) { + out+=chan[i].waveROM[k005289->addr(i)]*(regPool[2+i]&0xf); + } + + out<<=6; // scale output to 16 bit + + if (out<-32768) out=-32768; + if (out>32767) out=32767; + + //printf("out: %d\n",out); + bufL[h]=bufR[h]=out; + } +} + +void DivPlatformK005289::updateWave(int ch) { + DivWavetable* wt=parent->getWave(chan[ch].wave); + for (int i=0; i<32; i++) { + if (wt->max>0 && wt->len>0) { + int data=wt->data[i*wt->len/32]*15/wt->max; // 4 bit PROM at bubble system + if (data<0) data=0; + if (data>15) data=15; + chan[ch].waveROM[i]=data-8; // convert to signed + } + } + if (chan[ch].active) { + rWrite(2+ch,(chan[ch].wave<<5)|(isMuted[ch]?0:chan[ch].outVol)); + } +} + +void DivPlatformK005289::tick() { + for (int i=0; i<2; i++) { + chan[i].std.next(); + if (chan[i].std.hadVol) { + chan[i].outVol=((chan[i].vol&15)*MIN(15,chan[i].std.vol))/15; + if (!isMuted[i]) { + rWrite(2+i,(chan[i].wave<<5)|chan[i].outVol); + } + } + if (chan[i].std.hadArp) { + if (!chan[i].inPorta) { + if (chan[i].std.arpMode) { + chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp); + } else { + chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp); + } + } + chan[i].freqChanged=true; + } else { + if (chan[i].std.arpMode && chan[i].std.finishedArp) { + chan[i].baseFreq=NOTE_PERIODIC(chan[i].note); + chan[i].freqChanged=true; + } + } + if (chan[i].std.hadWave) { + if (chan[i].wave!=chan[i].std.wave) { + chan[i].wave=chan[i].std.wave; + updateWave(i); + if (!chan[i].keyOff) chan[i].keyOn=true; + } + } + if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { + //DivInstrument* ins=parent->getIns(chan[i].ins); + chan[i].freq=0x1000-parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true); + if (chan[i].freq<0) chan[i].freq=0; + if (chan[i].freq>4095) chan[i].freq=4095; + k005289->load(i,chan[i].freq); + rWrite(i,chan[i].freq); + k005289->update(i); + if (chan[i].keyOn) { + if (chan[i].wave<0) { + chan[i].wave=0; + updateWave(i); + } + } + if (chan[i].keyOff && (!isMuted[i])) { + rWrite(2+i,(chan[i].wave<<5)|0); + } + if (chan[i].keyOn) chan[i].keyOn=false; + if (chan[i].keyOff) chan[i].keyOff=false; + chan[i].freqChanged=false; + } + } +} + +int DivPlatformK005289::dispatch(DivCommand c) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: { + DivInstrument* ins=parent->getIns(chan[c.chan].ins); + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + } + chan[c.chan].active=true; + chan[c.chan].keyOn=true; + if (!isMuted[c.chan]) rWrite(2+c.chan,(chan[c.chan].wave<<5)|chan[c.chan].vol); + chan[c.chan].std.init(ins); + break; + } + case DIV_CMD_NOTE_OFF: + chan[c.chan].active=false; + chan[c.chan].keyOff=true; + chan[c.chan].std.init(NULL); + break; + case DIV_CMD_NOTE_OFF_ENV: + case DIV_CMD_ENV_RELEASE: + chan[c.chan].std.release(); + break; + case DIV_CMD_INSTRUMENT: + if (chan[c.chan].ins!=c.value || c.value2==1) { + chan[c.chan].ins=c.value; + } + break; + case DIV_CMD_VOLUME: + if (chan[c.chan].vol!=c.value) { + chan[c.chan].vol=c.value; + if (!chan[c.chan].std.hasVol) { + chan[c.chan].outVol=c.value; + if (chan[c.chan].active && !isMuted[c.chan]) rWrite(2+c.chan,(chan[c.chan].wave<<5)|chan[c.chan].outVol); + } + } + break; + case DIV_CMD_GET_VOLUME: + if (chan[c.chan].std.hasVol) { + return chan[c.chan].vol; + } + return chan[c.chan].outVol; + break; + case DIV_CMD_PITCH: + chan[c.chan].pitch=c.value; + chan[c.chan].freqChanged=true; + break; + case DIV_CMD_WAVE: + chan[c.chan].wave=c.value; + updateWave(c.chan); + chan[c.chan].keyOn=true; + break; + case DIV_CMD_NOTE_PORTA: { + int destFreq=NOTE_PERIODIC(c.value2); + bool return2=false; + if (destFreq>chan[c.chan].baseFreq) { + chan[c.chan].baseFreq+=c.value; + if (chan[c.chan].baseFreq>=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } else { + chan[c.chan].baseFreq-=c.value; + if (chan[c.chan].baseFreq<=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } + chan[c.chan].freqChanged=true; + if (return2) { + chan[c.chan].inPorta=false; + return 2; + } + break; + } + case DIV_CMD_LEGATO: + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.willArp && !chan[c.chan].std.arpMode)?(chan[c.chan].std.arp):(0))); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + break; + case DIV_CMD_PRE_PORTA: + if (chan[c.chan].active && c.value2) { + if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + } + chan[c.chan].inPorta=c.value; + break; + case DIV_CMD_GET_VOLMAX: + return 15; + break; + case DIV_ALWAYS_SET_VOLUME: + return 1; + break; + default: + break; + } + return 1; +} + +void DivPlatformK005289::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; + rWrite(2+ch,(chan[ch].wave<<5)|((chan[ch].active && isMuted[ch])?0:chan[ch].outVol)); +} + +void DivPlatformK005289::forceIns() { + for (int i=0; i<2; i++) { + chan[i].insChanged=true; + chan[i].freqChanged=true; + updateWave(i); + } +} + +void* DivPlatformK005289::getChanState(int ch) { + return &chan[ch]; +} + +unsigned char* DivPlatformK005289::getRegisterPool() { + return (unsigned char*)regPool; +} + +int DivPlatformK005289::getRegisterPoolSize() { + return 4; +} + +int DivPlatformK005289::getRegisterPoolDepth() { + return 16; +} + +void DivPlatformK005289::reset() { + memset(regPool,0,4*2); + for (int i=0; i<2; i++) { + chan[i]=DivPlatformK005289::Channel(); + } + if (dumpWrites) { + addWrite(0xffffffff,0); + } + k005289->reset(); +} + +bool DivPlatformK005289::isStereo() { + return false; +} + +bool DivPlatformK005289::keyOffAffectsArp(int ch) { + return true; +} + +void DivPlatformK005289::notifyWaveChange(int wave) { + for (int i=0; i<2; i++) { + if (chan[i].wave==wave) { + updateWave(i); + } + } +} + +void DivPlatformK005289::notifyInsDeletion(void* ins) { + for (int i=0; i<2; i++) { + chan[i].std.notifyInsDeletion((DivInstrument*)ins); + } +} + +void DivPlatformK005289::setFlags(unsigned int flags) { + chipClock=COLOR_NTSC; + rate=chipClock; +} + +void DivPlatformK005289::poke(unsigned int addr, unsigned short val) { + rWrite(addr,val); +} + +void DivPlatformK005289::poke(std::vector& wlist) { + for (DivRegWrite& i: wlist) rWrite(i.addr,i.val); +} + +int DivPlatformK005289::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { + parent=p; + dumpWrites=false; + skipRegisterWrites=false; + for (int i=0; i<2; i++) { + isMuted[i]=false; + } + setFlags(flags); + k005289=new k005289_core(); + reset(); + return 2; +} + +void DivPlatformK005289::quit() { + delete k005289; +} + +DivPlatformK005289::~DivPlatformK005289() { +} diff --git a/src/engine/platform/k005289.h b/src/engine/platform/k005289.h new file mode 100644 index 000000000..d8818fe41 --- /dev/null +++ b/src/engine/platform/k005289.h @@ -0,0 +1,84 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _K005289_H +#define _K005289_H + +#include "../dispatch.h" +#include +#include "../macroInt.h" +#include "sound/k005289/k005289.hpp" + +class DivPlatformK005289: public DivDispatch { + struct Channel { + int freq, baseFreq, pitch, note; + unsigned char ins; + bool active, insChanged, freqChanged, keyOn, keyOff, inPorta; + signed char vol, outVol, wave; + signed char waveROM[32] = {0}; // 4 bit PROM per channel on bubble system + DivMacroInt std; + Channel(): + freq(0), + baseFreq(0), + pitch(0), + note(0), + ins(-1), + active(false), + insChanged(true), + freqChanged(false), + keyOn(false), + keyOff(false), + inPorta(false), + vol(15), + outVol(15), + wave(-1) {} + }; + Channel chan[2]; + bool isMuted[2]; + + k005289_core* k005289; + unsigned short regPool[4]; + void updateWave(int ch); + friend void putDispatchChan(void*,int,int); + public: + void acquire(short* bufL, short* bufR, size_t start, size_t len); + int dispatch(DivCommand c); + void* getChanState(int chan); + unsigned char* getRegisterPool(); + int getRegisterPoolSize(); + int getRegisterPoolDepth(); + void reset(); + void forceIns(); + void tick(); + void muteChannel(int ch, bool mute); + bool isStereo(); + bool keyOffAffectsArp(int ch); + void setFlags(unsigned int flags); + void notifyWaveChange(int wave); + void notifyInsDeletion(void* ins); + void poke(unsigned int addr, unsigned short val); + void poke(std::vector& wlist); + const char** getRegisterSheet(); + const char* getEffectName(unsigned char effect); + int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); + void quit(); + ~DivPlatformK005289(); +}; + +#endif diff --git a/src/engine/platform/sound/k005289/k005289.cpp b/src/engine/platform/sound/k005289/k005289.cpp new file mode 100644 index 000000000..4763b63f0 --- /dev/null +++ b/src/engine/platform/sound/k005289/k005289.cpp @@ -0,0 +1,45 @@ +/* + License: BSD-3-Clause + see https://github.com/cam900/vgsound_emu/LICENSE for more details + + Copyright holder(s): cam900 + Konami K005289 emulation core + + This chip is used at infamous Konami Bubble System, for part of Wavetable sound generator. + But seriously, It is just to 2 internal 12 bit timer and address generators, rather than sound generator. + + Everything except for internal counter and address are done by external logic, the chip is only has external address, frequency registers and its update pins. + + Frequency calculation: Input clock / (4096 - Pitch input) +*/ + +#include "k005289.hpp" + +void k005289_core::tick() +{ + for (auto & elem : m_voice) + elem.tick(); +} + +void k005289_core::reset() +{ + for (auto & elem : m_voice) + elem.reset(); +} + +void k005289_core::voice_t::tick() +{ + if (bitfield(++counter, 0, 12) == 0) + { + addr = bitfield(addr + 1, 0, 5); + counter = freq; + } +} + +void k005289_core::voice_t::reset() +{ + addr = 0; + pitch = 0; + freq = 0; + counter = 0; +} diff --git a/src/engine/platform/sound/k005289/k005289.hpp b/src/engine/platform/sound/k005289/k005289.hpp new file mode 100644 index 000000000..fe5d50ac9 --- /dev/null +++ b/src/engine/platform/sound/k005289/k005289.hpp @@ -0,0 +1,67 @@ +/* + License: BSD-3-Clause + see https://github.com/cam900/vgsound_emu/LICENSE for more details + + Copyright holder(s): cam900 + Konami K005289 emulation core + + See k005289.cpp for more info. +*/ + +#include +#include + +#ifndef _VGSOUND_EMU_K005289_HPP +#define _VGSOUND_EMU_K005289_HPP + +#pragma once + +namespace k005289 +{ + typedef unsigned char u8; + typedef unsigned short u16; + typedef signed short s16; + + // get bitfield, bitfield(input, position, len) + template T bitfield(T in, u8 pos, u8 len = 1) + { + return (in >> pos) & (len ? (T(1 << len) - 1) : 1); + } +} + +using namespace k005289; +class k005289_core +{ +public: + // accessors, getters, setters + u8 addr(int voice) { return m_voice[voice & 1].addr; } // 1QA...E/2QA...E pin + void load(int voice, u16 addr) { m_voice[voice & 1].load(addr); } // LD1/2 pin, A0...11 pin + void update(int voice) { m_voice[voice & 1].update(); } // TG1/2 pin + + // internal state + void reset(); + void tick(); + +private: + // k005289 voice structs + struct voice_t + { + // internal state + void reset(); + void tick(); + + // accessors, getters, setters + void load(u16 addr) { pitch = addr; } // Load pitch data (address pin) + void update() { freq = pitch; } // Replace current frequency to lastest loaded pitch + + // registers + u8 addr = 0; // external address pin + u16 pitch = 0; // pitch + u16 freq = 0; // current frequency + s16 counter = 0; // frequency counter + }; + + voice_t m_voice[2]; +}; + +#endif diff --git a/src/engine/platform/sound/x1_010/x1_010.hpp b/src/engine/platform/sound/x1_010/x1_010.hpp index d5c429fda..e8f5e3e40 100644 --- a/src/engine/platform/sound/x1_010/x1_010.hpp +++ b/src/engine/platform/sound/x1_010/x1_010.hpp @@ -16,23 +16,28 @@ #pragma once -typedef unsigned char u8; -typedef unsigned short u16; -typedef unsigned int u32; -typedef signed char s8; -typedef signed int s32; - -template T bitfield(T in, u8 pos, u8 len = 1) +namespace x1_010 { - return (in >> pos) & (len ? (T(1 << len) - 1) : 1); + typedef unsigned char u8; + typedef unsigned short u16; + typedef unsigned int u32; + typedef signed char s8; + typedef signed int s32; + + template T bitfield(T in, u8 pos, u8 len = 1) + { + return (in >> pos) & (len ? (T(1 << len) - 1) : 1); + } } +using namespace x1_010; class x1_010_mem_intf { public: virtual u8 read_byte(u32 address) { return 0; } }; +using namespace x1_010; class x1_010_core { friend class x1_010_mem_intf; diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 4626635b0..a30603fc0 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -309,6 +309,15 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe return false; } break; + case DIV_SYSTEM_K005289: + switch (effect) { + case 0x10: // select waveform + dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal)); + break; + default: + return false; + } + break; default: return false; } diff --git a/src/engine/song.h b/src/engine/song.h index 5747aa511..c0db8d018 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -93,7 +93,8 @@ enum DivSystem { DIV_SYSTEM_VERA, DIV_SYSTEM_YM2610B_EXT, DIV_SYSTEM_SEGAPCM_COMPAT, - DIV_SYSTEM_X1_010 + DIV_SYSTEM_X1_010, + DIV_SYSTEM_K005289 }; struct DivSong { diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 70e27dae8..bb743f960 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -226,7 +226,7 @@ unsigned char DivEngine::systemToFile(DivSystem val) { return 0x96; case DIV_SYSTEM_SAA1099: return 0x97; - case DIV_SYSTEM_OPZ: + case DIV_SYSTEM_OPZ: return 0x98; case DIV_SYSTEM_POKEMINI: return 0x99; @@ -394,6 +394,8 @@ int DivEngine::getChannelCount(DivSystem sys) { return 19; case DIV_SYSTEM_VERA: return 17; + case DIV_SYSTEM_K005289: + return 2; } return 0; } @@ -539,6 +541,9 @@ const char* DivEngine::getSongSystemName() { } break; case 3: + if (song.system[0]==DIV_SYSTEM_AY8910 && song.system[1]==DIV_SYSTEM_AY8910 && song.system[2]==DIV_SYSTEM_K005289) { + return "Konami Bubble System"; + } break; } return "multi-system"; @@ -669,6 +674,8 @@ const char* DivEngine::getSystemName(DivSystem sys) { return "VERA"; case DIV_SYSTEM_X1_010: return "Seta/Allumer X1-010"; + case DIV_SYSTEM_K005289: + return "Konami Bubble System Sound"; } return "Unknown"; } @@ -798,6 +805,8 @@ const char* DivEngine::getSystemChips(DivSystem sys) { return "VERA"; case DIV_SYSTEM_X1_010: return "Seta/Allumer X1-010"; + case DIV_SYSTEM_K005289: + return "Konami K005289"; } return "Unknown"; } @@ -1008,7 +1017,7 @@ const int chanTypes[41][32]={ {3, 4, 3, 2}, // Swan }; -const DivInstrumentType chanPrefType[46][28]={ +const DivInstrumentType chanPrefType[47][28]={ {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM}, // YMU759 {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD}, // Genesis {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD, DIV_INS_STD}, // Genesis (extended channel 3) @@ -1055,6 +1064,7 @@ const DivInstrumentType chanPrefType[46][28]={ {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, // YM2610B (extended channel 3) {DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_AMIGA}, // VERA {DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010}, // X1-010 + {DIV_INS_SCC, DIV_INS_SCC}, // K005289 }; const char* DivEngine::getChannelName(int chan) { @@ -1083,6 +1093,7 @@ const char* DivEngine::getChannelName(int chan) { break; case DIV_SYSTEM_PCE: case DIV_SYSTEM_SFX_BEEPER: + case DIV_SYSTEM_K005289: return chanNames[5][dispatchChanOfChan[chan]]; break; case DIV_SYSTEM_NES: @@ -1228,6 +1239,7 @@ const char* DivEngine::getChannelShortName(int chan) { break; case DIV_SYSTEM_PCE: case DIV_SYSTEM_SFX_BEEPER: + case DIV_SYSTEM_K005289: return chanShortNames[5][dispatchChanOfChan[chan]]; break; case DIV_SYSTEM_NES: @@ -1369,6 +1381,7 @@ int DivEngine::getChannelType(int chan) { break; case DIV_SYSTEM_PCE: case DIV_SYSTEM_SFX_BEEPER: + case DIV_SYSTEM_K005289: return chanTypes[5][dispatchChanOfChan[chan]]; break; case DIV_SYSTEM_NES: @@ -1642,6 +1655,9 @@ DivInstrumentType DivEngine::getPreferInsType(int chan) { case DIV_SYSTEM_X1_010: return chanPrefType[45][dispatchChanOfChan[chan]]; break; + case DIV_SYSTEM_K005289: + return chanPrefType[46][dispatchChanOfChan[chan]]; + break; } return DIV_INS_FM; } diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 2220299f8..9c9368f98 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -5253,6 +5253,7 @@ bool FurnaceGUI::loop() { sysAddOption(DIV_SYSTEM_X1_010); sysAddOption(DIV_SYSTEM_SWAN); sysAddOption(DIV_SYSTEM_VERA); + sysAddOption(DIV_SYSTEM_K005289); ImGui::EndMenu(); } if (ImGui::BeginMenu("configure system...")) { @@ -5580,6 +5581,7 @@ bool FurnaceGUI::loop() { case DIV_SYSTEM_GB: case DIV_SYSTEM_SWAN: case DIV_SYSTEM_VERA: + case DIV_SYSTEM_K005289: case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_EXT: case DIV_SYSTEM_YM2610_FULL: @@ -5641,6 +5643,7 @@ bool FurnaceGUI::loop() { sysChangeOption(i,DIV_SYSTEM_X1_010); sysChangeOption(i,DIV_SYSTEM_SWAN); sysChangeOption(i,DIV_SYSTEM_VERA); + sysChangeOption(i,DIV_SYSTEM_K005289); ImGui::EndMenu(); } } @@ -7388,6 +7391,15 @@ FurnaceGUI::FurnaceGUI(): 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "Konami Bubble System", { + DIV_SYSTEM_AY8910, 64, 0, 0, + DIV_SYSTEM_AY8910, 64, 0, 0, + DIV_SYSTEM_K005289, 64, 0, 0, + // VLM5030 exists but not used for music at all + 0 + } + )); sysCategories.push_back(cat); cat=FurnaceGUISysCategory("DefleMask-compatible"); diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index e653b097f..2c178058e 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1418,7 +1418,7 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_AY8930) { dutyMax=255; } - if (ins->type==DIV_INS_TIA || ins->type==DIV_INS_AMIGA) { + if (ins->type==DIV_INS_TIA || ins->type==DIV_INS_AMIGA || ins->type==DIV_INS_SCC) { dutyMax=0; } if (ins->type==DIV_INS_PCE) { From f24a776a7acb99355bbe4b15adbbf9b73e5b93a9 Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 14 Mar 2022 19:40:27 +0900 Subject: [PATCH 248/637] More user friendly instrument name --- papers/doc/4-instrument/README.md | 2 +- papers/doc/4-instrument/scc.md | 4 ++-- papers/doc/5-wave/README.md | 2 +- src/gui/guiConst.cpp | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/papers/doc/4-instrument/README.md b/papers/doc/4-instrument/README.md index 6c5c3f5f9..577e18ebc 100644 --- a/papers/doc/4-instrument/README.md +++ b/papers/doc/4-instrument/README.md @@ -26,7 +26,7 @@ depending on the instrument type, there are currently 13 different types of an i - [Atari Lynx](lynx.md) - for use with Atari Lynx handheld console. - [VERA](vera.md) - for use with Commander X16 VERA. - [Seta/Allumer X1-010](x1_010.md) - for use with Wavetable portion in Seta/Allumer X1-010. -- [Konami SCC/Bubble System](scc.md) - for use with Konami SCC, Wavetable portion in Bubble System's sound hardware. +- [Konami SCC/Bubble System](scc.md) - for use with Konami SCC and Wavetable portion in Bubble System's sound hardware. # macros diff --git a/papers/doc/4-instrument/scc.md b/papers/doc/4-instrument/scc.md index 9cce33168..86e8c11bd 100644 --- a/papers/doc/4-instrument/scc.md +++ b/papers/doc/4-instrument/scc.md @@ -1,6 +1,6 @@ -# Konami SCC instrument editor +# Konami SCC/Bubble System instrument editor -SCC instrument editor consists of only three macros: +SCC/Bubble System instrument editor consists of only three macros: - [Volume] - volume sequence - [Arpeggio] - pitch sequence diff --git a/papers/doc/5-wave/README.md b/papers/doc/5-wave/README.md index 324691ba0..f6dd9b94b 100644 --- a/papers/doc/5-wave/README.md +++ b/papers/doc/5-wave/README.md @@ -2,4 +2,4 @@ Wavetable synthizers, in context of Furnace, are sound sources that operate on extremely short n-bit PCM streams. By extremely short, no more than 256 bytes. This amount of space is nowhere near enough to store an actual sampled sound, it allows certain amount of freedom to define a waveform shape. As of Furnace 0.5.8, wavetable editor affects PC Engine, WonderSwan and channel 3 of Game Boy. -Furnace's wavetable editor is rather simple, you can draw the waveform using mouse or by pasting an MML bit stream in the input field. Maximum wave width (length) is 256 bytes, and maximum wave height (depth) is 256. NOTE: Game Boy, PCE, WonderSwan, Bubble System can handle max 32 byte waveforms, X1-010 can handle max 128 byte waveforms as of now, with 16-level height for GB, X1-010 Envelope, WS and Bubble System, and 32-level height for PCE. If larger wave will be defined for these systems, it will be squashed to fit within the constraints of the system. +Furnace's wavetable editor is rather simple, you can draw the waveform using mouse or by pasting an MML bit stream in the input field. Maximum wave width (length) is 256 bytes, and maximum wave height (depth) is 256. NOTE: Game Boy, PCE, WonderSwan and Bubble System can handle max 32 byte waveforms, X1-010 can handle max 128 byte waveforms as of now, with 16-level height for GB, X1-010 Envelope, WS and Bubble System, and 32-level height for PCE. If larger wave will be defined for these systems, it will be squashed to fit within the constraints of the system. diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index 83220d252..7b4cd52a2 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -84,7 +84,7 @@ const char* insTypes[DIV_INS_MAX]={ "FDS", "Virtual Boy", "Namco 163", - "Konami SCC", + "Konami SCC/Bubble System", "FM (OPZ)", "POKEY", "PC Beeper", @@ -92,4 +92,4 @@ const char* insTypes[DIV_INS_MAX]={ "Atari Lynx", "VERA", "X1-010" -}; \ No newline at end of file +}; From 87f225074c13c40eb565764ad059f4791f4761a1 Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Mon, 14 Mar 2022 21:50:52 +0700 Subject: [PATCH 249/637] .mod import --- src/engine/dispatch.h | 1 + src/engine/engine.cpp | 17 +- src/engine/engine.h | 1 + src/engine/fileOps.cpp | 329 ++++++++++++++++++++++++++++++++++ src/engine/platform/amiga.cpp | 10 +- src/engine/platform/amiga.h | 3 +- src/engine/playback.cpp | 49 +++++ src/engine/safeReader.cpp | 4 +- src/gui/gui.cpp | 2 +- src/gui/pattern.cpp | 26 ++- 10 files changed, 432 insertions(+), 10 deletions(-) diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index fe7c3ea34..8fdb112fa 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 75da62e0d..5bf1bd554 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -56,6 +56,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: @@ -96,10 +98,23 @@ const char* DivEngine::getEffectDesc(unsigned char effect, int chan) { return "EExx: Send external command"; case 0xef: return "EFxx: Set global tuning (quirky!)"; + case 0xf1: + return "F1xx: Single note slide up"; + case 0xf2: + return "F2xx: Single note slide down"; + case 0xf8: + return "F8xx: Single volume slide up"; + case 0xf9: + return "F9xx: Single 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 19d589dba..670f61255 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -241,6 +241,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 ec7cd1cb8..0a07ceb1a 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -1196,6 +1196,327 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { return true; } +bool DivEngine::loadMod(unsigned char* file, size_t len) { + 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 std::exception("invalid info header!"); + } + // 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->samples=slen; + sample->data8=new signed char[slen]; + ds.sample.push_back(sample); + } + // orders + ds.ordersLen=ordCount=reader.readC(); + int restartPos=reader.readC(); + 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(0xc0,(fxVal*2+2)/5); + } 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 (std::exception e) { + logE("%s\n",e.what()); + lastError=e.what(); + } + return success; +} + bool DivEngine::load(unsigned char* f, size_t slen) { unsigned char* file; size_t len; @@ -1206,6 +1527,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 6895fcc5d..e3313a9de 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -761,6 +761,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) { @@ -781,6 +784,12 @@ 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; @@ -862,6 +871,46 @@ void DivEngine::processRow(int i, bool afterDelay) { case 0xef: // global pitch globalPitch+=(signed char)(effectVal-0x80); break; + case 0xf1: // single pitch ramp up + case 0xf2: // single pitch ramp down + if (effect==0xf1) { + chan[i].portaNote=song.limitSlides?0x60:255; + } else { + chan[i].portaNote=song.limitSlides?disCont[dispatchOfChan[i]].dispatch->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 df3936655..dcc85fd18 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); + if (curSeek+2>len) throw EndOfFileException(this,len); short ret=*(short*)(&buf[curSeek]); curSeek+=2; - return (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 25d662d05..81aad9c19 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4045,7 +4045,7 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { switch (type) { case GUI_FILE_OPEN: if (!dirExists(workingDirSong)) workingDirSong=getHomeDir(); - ImGuiFileDialog::Instance()->OpenModal("FileDialog","Open File","compatible files{.fur,.dmf},.*",workingDirSong); + ImGuiFileDialog::Instance()->OpenModal("FileDialog","Open File","compatible files{.fur,.dmf,.mod},.*",workingDirSong); break; case GUI_FILE_SAVE: if (!dirExists(workingDirSong)) workingDirSong=getHomeDir(); diff --git a/src/gui/pattern.cpp b/src/gui/pattern.cpp index 9bf6eee59..7841667fc 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_INVALID, // 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); From 157e27eff53b6bc103b42fee26725f6bfbbb9224 Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Mon, 14 Mar 2022 22:48:33 +0700 Subject: [PATCH 250/637] Make it work on GCC (hopefully) --- src/engine/fileOps.cpp | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 73013f123..5c4154cb8 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -1223,7 +1223,10 @@ 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; @@ -1251,7 +1254,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { &&(magic[0]>='1' && magic[0]<='9' && magic[1]>='0' && magic[1]<='9')) { chCount=((magic[0]-'0')*10)+(magic[1]-'0'); } else { - throw std::exception("invalid info header!"); + throw InvalidHeaderException(); } // song name reader.seek(0,SEEK_SET); @@ -1288,7 +1291,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { } // orders ds.ordersLen=ordCount=reader.readC(); - int restartPos=reader.readC(); + reader.readC(); // restart position, unused int patMax=0; for (int i=0;i<128;i++) { unsigned char pat=reader.readC(); @@ -1537,9 +1540,9 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { } catch (EndOfFileException e) { logE("premature end of file!\n"); lastError="incomplete file"; - } catch (std::exception e) { - logE("%s\n",e.what()); - lastError=e.what(); + } catch (InvalidHeaderException e) { + logE("invalid info header!\n"); + lastError="invalid info header!"; } return success; } From 6b5bbae1c1f3486e7d6bd186aed2df3727477c68 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 14 Mar 2022 12:03:10 -0500 Subject: [PATCH 251/637] allocate bubble system ID --- papers/format.md | 1 + 1 file changed, 1 insertion(+) diff --git a/papers/format.md b/papers/format.md index d5b9c67e6..503099b99 100644 --- a/papers/format.md +++ b/papers/format.md @@ -177,6 +177,7 @@ size | description | - 0xaa: MSM6295 - 4 channels | - 0xab: MSM6258 - 1 channel | - 0xac: Commander X16 (VERA) - 17 channels + | - 0xad: Bubble System - 2 channels | - 0xb0: Seta/Allumer X1-010 - 16 channels | - 0xde: YM2610B extended - 19 channels | - 0xe0: QSound - 19 channels From 54cf22fdcde31f7a73d39fba245931797d2de0b2 Mon Sep 17 00:00:00 2001 From: cam900 Date: Tue, 15 Mar 2022 03:02:31 +0900 Subject: [PATCH 252/637] Allocate System ID --- src/engine/sysDef.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index c23343d7c..d4aaa353f 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -137,6 +137,8 @@ DivSystem DivEngine::systemFromFile(unsigned char val) { return DIV_SYSTEM_SEGAPCM_COMPAT; case 0xac: return DIV_SYSTEM_VERA; + case 0xad: + return DIV_SYSTEM_K005289; case 0xb0: return DIV_SYSTEM_X1_010; case 0xde: @@ -264,6 +266,8 @@ unsigned char DivEngine::systemToFile(DivSystem val) { return 0xa9; case DIV_SYSTEM_VERA: return 0xac; + case DIV_SYSTEM_K005289: + return 0xad; case DIV_SYSTEM_X1_010: return 0xb0; case DIV_SYSTEM_YM2610B_EXT: From 749b0f39d0ab3364f822238ac6bee834dd5d25d6 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 14 Mar 2022 14:54:45 -0500 Subject: [PATCH 253/637] GUI: better order display - UNTESTED --- src/gui/orders.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/gui/orders.cpp b/src/gui/orders.cpp index 22bd7e0d1..fc4972e9d 100644 --- a/src/gui/orders.cpp +++ b/src/gui/orders.cpp @@ -39,7 +39,10 @@ void FurnaceGUI::drawOrders() { for (int i=0; igetTotalChannelCount(); i++) { if (e->song.chanShow[i]) displayChans++; } - if (ImGui::BeginTable("OrdersTable",1+displayChans,ImGuiTableFlags_SizingStretchSame|ImGuiTableFlags_ScrollX|ImGuiTableFlags_ScrollY)) { + ImGui::PushFont(patFont); + bool tooSmall=(displayChans>((ImGui::GetWindowSize().x-24.0f*dpiScale)/ImGui::CalcTextSize("AAA").x)); + ImGui::PopFont(); + if (ImGui::BeginTable("OrdersTable",1+displayChans,(tooSmall?ImGuiTableFlags_SizingFixedFit:ImGuiTableFlags_SizingStretchSame)|ImGuiTableFlags_ScrollX|ImGuiTableFlags_ScrollY)) { ImGui::PushFont(patFont); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,prevSpacing); ImGui::TableSetupScrollFreeze(1,1); @@ -246,4 +249,4 @@ void FurnaceGUI::drawOrders() { if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_ORDERS; oldOrder1=e->getOrder(); ImGui::End(); -} \ No newline at end of file +} From 96434989464341f57d85cca47524ef8ed627a08d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 14 Mar 2022 15:53:43 -0500 Subject: [PATCH 254/637] GUI: fix orders up --- src/gui/orders.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/orders.cpp b/src/gui/orders.cpp index fc4972e9d..43976fdc0 100644 --- a/src/gui/orders.cpp +++ b/src/gui/orders.cpp @@ -40,7 +40,7 @@ void FurnaceGUI::drawOrders() { if (e->song.chanShow[i]) displayChans++; } ImGui::PushFont(patFont); - bool tooSmall=(displayChans>((ImGui::GetWindowSize().x-24.0f*dpiScale)/ImGui::CalcTextSize("AAA").x)); + bool tooSmall=((displayChans+1)>((ImGui::GetContentRegionAvail().x)/(ImGui::CalcTextSize("AA").x+2.0*ImGui::GetStyle().ItemInnerSpacing.x))); ImGui::PopFont(); if (ImGui::BeginTable("OrdersTable",1+displayChans,(tooSmall?ImGuiTableFlags_SizingFixedFit:ImGuiTableFlags_SizingStretchSame)|ImGuiTableFlags_ScrollX|ImGuiTableFlags_ScrollY)) { ImGui::PushFont(patFont); From 54da047b5a4879d7a13990d902b396b0124e6769 Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Tue, 15 Mar 2022 03:59:42 +0700 Subject: [PATCH 255/637] Apply requested changes - Use sample->init() instead - Implement set Hz by tempo effect and move from C4xx to F0xx - Add "SAMPLE_POS" to cmdName --- src/engine/engine.cpp | 12 +++++++----- src/engine/fileOps.cpp | 5 ++--- src/engine/playback.cpp | 10 +++++++--- src/gui/pattern.cpp | 2 +- 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 655b7f5ed..c16392772 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -72,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: @@ -97,14 +97,16 @@ 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 note slide up"; + return "F1xx: Single tick note slide up"; case 0xf2: - return "F2xx: Single note slide down"; + return "F2xx: Single tick note slide down"; case 0xf8: - return "F8xx: Single volume slide up"; + return "F8xx: Single tick volume slide up"; case 0xf9: - return "F9xx: Single volume slide down"; + return "F9xx: Single tick volume slide down"; case 0xfa: return "FAxx: Fast volume slide (0y: down; x0: up)"; case 0xff: diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 5c4154cb8..0d1780458 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -1285,8 +1285,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { if(loopEndloopStart=loopStart; } - sample->samples=slen; - sample->data8=new signed char[slen]; + sample->init(slen); ds.sample.push_back(sample); } // orders @@ -1456,7 +1455,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { case 15: // set speed // TODO somehow handle VBlank tunes if (fxVal>=0x20) { - writeFxCol(0xc0,(fxVal*2+2)/5); + writeFxCol(0xf0,fxVal); } else { writeFxCol(0x09,fxVal); writeFxCol(0x0f,fxVal); diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index d94c18085..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", @@ -872,9 +873,6 @@ void DivEngine::processRow(int i, bool afterDelay) { cycles=((int)(got.rate)<0) { song.arpLen=effectVal; @@ -947,6 +945,12 @@ 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)< Date: Tue, 15 Mar 2022 03:59:52 +0700 Subject: [PATCH 256/637] Document new effects --- papers/doc/3-pattern/effects.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) 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). From d2458a8ae883e4d9bfcf788d9a179e95d356bafa Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 14 Mar 2022 16:29:13 -0500 Subject: [PATCH 257/637] MOD import: use 436Hz tuning --- src/engine/fileOps.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 0d1780458..c45a797e8 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -1240,6 +1240,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { warnings=""; try { DivSong ds; + ds.tuning=436.0; // check mod magic bytes if (!reader.seek(1080,SEEK_SET)) { @@ -1270,7 +1271,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { 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->rate=(int)(pow(2.0,(double)fineTune/96.0)*8363.0); sample->centerRate=sample->rate; defaultVols[i]=reader.readC(); int loopStart=reader.readS_BE()*2; From 977d23bc3a1a51789794d05c1758d892ee2efead Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 14 Mar 2022 16:33:13 -0500 Subject: [PATCH 258/637] MOD import: don't use linear pitch improves vibrato --- src/engine/fileOps.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index c45a797e8..bd66cf686 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -1241,6 +1241,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { try { DivSong ds; ds.tuning=436.0; + ds.linearPitch=false; // check mod magic bytes if (!reader.seek(1080,SEEK_SET)) { From d7b967c41e9e3d30b4a2769699e66a474e072ddb Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 14 Mar 2022 16:36:15 -0500 Subject: [PATCH 259/637] Revert "MOD import: don't use linear pitch" This reverts commit 977d23bc3a1a51789794d05c1758d892ee2efead. --- src/engine/fileOps.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index bd66cf686..c45a797e8 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -1241,7 +1241,6 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { try { DivSong ds; ds.tuning=436.0; - ds.linearPitch=false; // check mod magic bytes if (!reader.seek(1080,SEEK_SET)) { From 8012676993fae040233a6b9f41790318fc7c5592 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 14 Mar 2022 17:16:43 -0500 Subject: [PATCH 260/637] MOD import: crash fix --- src/engine/engine.h | 3 +++ src/engine/fileOps.cpp | 12 +++++++----- src/gui/gui.cpp | 4 ++-- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/engine/engine.h b/src/engine/engine.h index c2b6c443e..b4d5f7b6b 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -40,6 +40,9 @@ #define DIV_VERSION "dev66" #define DIV_ENGINE_VERSION 66 +// for imports +#define DIV_VERSION_MOD 0xff01 + enum DivStatusView { DIV_STATUS_NOTHING=0, DIV_STATUS_PATTERN, diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index c45a797e8..da4ca4e82 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -1241,6 +1241,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { try { DivSong ds; ds.tuning=436.0; + ds.version=DIV_VERSION_MOD; // check mod magic bytes if (!reader.seek(1080,SEEK_SET)) { @@ -1255,6 +1256,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { &&(magic[0]>='1' && magic[0]<='9' && magic[1]>='0' && magic[1]<='9')) { chCount=((magic[0]-'0')*10)+(magic[1]-'0'); } else { + // TODO: Soundtracker MOD? throw InvalidHeaderException(); } // song name @@ -1266,7 +1268,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { DivSample* sample=new DivSample; sample->depth=8; sample->name=reader.readString(22); - int slen=reader.readS_BE()*2; + int slen=((unsigned short)reader.readS_BE())*2; sampLens[i]=slen; if (slen==2) slen=0; signed char fineTune=reader.readC()&0x0f; @@ -1504,20 +1506,20 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { ds.hz=50; ds.customTempo=false; ds.systemLen=(chCount+3)/4; - for(int i=0;itype=DIV_INS_AMIGA; ins->amiga.initSample=i; diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index f431d1839..af3fe98eb 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -3682,7 +3682,7 @@ void FurnaceGUI::doAction(int what) { } break; case GUI_ACTION_SAVE: - if (curFileName=="") { + if (curFileName=="" || e->song.version>=0xff00) { openFileDialog(GUI_FILE_SAVE); } else { if (save(curFileName,e->song.isDMF?e->song.version:0)>0) { @@ -5438,7 +5438,7 @@ bool FurnaceGUI::loop() { } ImGui::Separator(); if (ImGui::MenuItem("save",BIND_FOR(GUI_ACTION_SAVE))) { - if (curFileName=="") { + if (curFileName=="" || e->song.version>=0xff00) { openFileDialog(GUI_FILE_SAVE); } else { if (save(curFileName,e->song.isDMF?e->song.version:0)>0) { From bfae208a242dc3f276d2930286fd2c48cc233276 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 14 Mar 2022 17:40:22 -0500 Subject: [PATCH 261/637] GUI: change default layout --- src/gui/gui.cpp | 224 +++++++++++++++++++++++++++---------------- src/gui/gui.h | 2 +- src/gui/settings.cpp | 2 +- 3 files changed, 145 insertions(+), 83 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index af3fe98eb..344480b47 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -436,75 +436,64 @@ Size=1280,731\n\ Collapsed=0\n\ \n\ [Window][Debug##Default]\n\ -Pos=60,60\n\ +Pos=54,0\n\ Size=400,400\n\ Collapsed=0\n\ \n\ [Window][Play/Edit Controls]\n\ -Pos=390,24\n\ -Size=243,177\n\ +Pos=181,208\n\ +Size=45,409\n\ Collapsed=0\n\ -DockId=0x00000009,0\n\ \n\ [Window][Song Information]\n\ -Pos=951,24\n\ -Size=329,240\n\ +Pos=978,24\n\ +Size=302,217\n\ Collapsed=0\n\ DockId=0x00000004,0\n\ \n\ [Window][Orders]\n\ Pos=0,24\n\ -Size=388,240\n\ +Size=345,217\n\ Collapsed=0\n\ -DockId=0x00000005,0\n\ +DockId=0x00000007,0\n\ \n\ [Window][Instruments]\n\ -Pos=635,24\n\ -Size=314,240\n\ +Pos=653,24\n\ +Size=323,217\n\ Collapsed=0\n\ -DockId=0x00000008,1\n\ +DockId=0x00000006,2\n\ \n\ [Window][Wavetables]\n\ -Pos=635,24\n\ -Size=314,240\n\ +Pos=653,24\n\ +Size=323,217\n\ Collapsed=0\n\ -DockId=0x00000008,2\n\ +DockId=0x00000006,1\n\ \n\ [Window][Samples]\n\ -Pos=635,24\n\ -Size=314,240\n\ +Pos=653,24\n\ +Size=323,217\n\ Collapsed=0\n\ -DockId=0x00000008,0\n\ +DockId=0x00000006,0\n\ \n\ [Window][Pattern]\n\ -Pos=0,266\n\ -Size=1246,489\n\ +Pos=0,243\n\ +Size=1246,512\n\ Collapsed=0\n\ DockId=0x0000000B,0\n\ \n\ -[Window][Open File##FileDialog]\n\ -Pos=213,99\n\ -Size=853,557\n\ -Collapsed=0\n\ -\n\ [Window][Instrument Editor]\n\ -Pos=324,130\n\ -Size=951,623\n\ +Pos=372,102\n\ +Size=682,604\n\ Collapsed=0\n\ \n\ [Window][Warning]\n\ -Pos=516,339\n\ +Pos=481,338\n\ Size=346,71\n\ Collapsed=0\n\ \n\ -[Window][Load Sample##FileDialog]\n\ -Pos=340,177\n\ -Size=600,400\n\ -Collapsed=0\n\ -\n\ [Window][Sample Editor]\n\ -Pos=238,298\n\ -Size=551,286\n\ +Pos=531,176\n\ +Size=613,416\n\ Collapsed=0\n\ \n\ [Window][About Furnace]\n\ @@ -516,29 +505,61 @@ Pos=340,177\n\ Size=600,400\n\ Collapsed=0\n\ \n\ -[Window][Load Wavetable##FileDialog]\n\ -Pos=340,177\n\ -Size=600,400\n\ -Collapsed=0\n\ -\n\ [Window][Wavetable Editor]\n\ -Pos=228,81\n\ -Size=580,368\n\ -Collapsed=0\n\ -\n\ -[Window][Save Wavetable##FileDialog]\n\ -Pos=340,177\n\ -Size=600,400\n\ +Pos=253,295\n\ +Size=748,378\n\ Collapsed=0\n\ \n\ [Window][Settings]\n\ -Pos=495,97\n\ -Size=552,559\n\ +Pos=655,224\n\ +Size=601,508\n\ Collapsed=0\n\ \n\ [Window][Error]\n\ -Pos=488,342\n\ -Size=304,71\n\ +Pos=491,342\n\ +Size=514,71\n\ +Collapsed=0\n\ +\n\ +[Window][Mixer]\n\ +Pos=63,55\n\ +Size=450,215\n\ +Collapsed=0\n\ +\n\ +[Window][Oscilloscope]\n\ +Pos=347,94\n\ +Size=304,105\n\ +Collapsed=0\n\ +DockId=0x0000000E,0\n\ +\n\ +[Window][Volume Meter]\n\ +Pos=1248,243\n\ +Size=32,512\n\ +Collapsed=0\n\ +DockId=0x0000000C,0\n\ +\n\ +[Window][Debug]\n\ +Pos=113,148\n\ +Size=945,473\n\ +Collapsed=0\n\ +\n\ +[Window][Load Sample##FileDialog]\n\ +Pos=40,0\n\ +Size=1200,755\n\ +Collapsed=0\n\ +\n\ +[Window][Open File##FileDialog]\n\ +Pos=250,143\n\ +Size=779,469\n\ +Collapsed=0\n\ +\n\ +[Window][Export Audio##FileDialog]\n\ +Pos=339,177\n\ +Size=601,400\n\ +Collapsed=0\n\ +\n\ +[Window][Rendering...]\n\ +Pos=585,342\n\ +Size=114,71\n\ Collapsed=0\n\ \n\ [Window][Export VGM##FileDialog]\n\ @@ -546,43 +567,84 @@ Pos=340,177\n\ Size=600,400\n\ Collapsed=0\n\ \n\ -[Window][Mixer]\n\ -Pos=60,60\n\ -Size=450,215\n\ +[Window][Warning##Save FileFileDialogOverWriteDialog]\n\ +Pos=390,351\n\ +Size=500,71\n\ Collapsed=0\n\ \n\ -[Window][Oscilloscope]\n\ -Pos=390,203\n\ -Size=243,61\n\ +[Window][Statistics]\n\ +Pos=596,307\n\ +Size=512,219\n\ +Collapsed=0\n\ +\n\ +[Window][Warning##Export VGMFileDialogOverWriteDialog]\n\ +Pos=390,351\n\ +Size=500,71\n\ +Collapsed=0\n\ +\n\ +[Window][Compatibility Flags]\n\ +Pos=682,287\n\ +Size=347,262\n\ +Collapsed=0\n\ +\n\ +[Window][Song Comments]\n\ +Pos=60,60\n\ +Size=395,171\n\ +Collapsed=0\n\ +\n\ +[Window][Warning##Export AudioFileDialogOverWriteDialog]\n\ +Pos=381,351\n\ +Size=500,71\n\ +Collapsed=0\n\ +\n\ +[Window][Select Font##FileDialog]\n\ +Pos=340,177\n\ +Size=600,400\n\ +Collapsed=0\n\ +\n\ +[Window][Channels]\n\ +Pos=60,60\n\ +Size=368,449\n\ +Collapsed=0\n\ +\n\ +[Window][Register View]\n\ +Pos=847,180\n\ +Size=417,393\n\ +Collapsed=0\n\ +\n\ +[Window][New Song]\n\ +Pos=267,110\n\ +Size=746,534\n\ +Collapsed=0\n\ +\n\ +[Window][Edit Controls]\n\ +Pos=347,24\n\ +Size=304,68\n\ +Collapsed=0\n\ +DockId=0x0000000D,0\n\ +\n\ +[Window][Play Controls]\n\ +Pos=347,201\n\ +Size=304,40\n\ Collapsed=0\n\ DockId=0x0000000A,0\n\ \n\ -[Window][Volume Meter]\n\ -Pos=1248,266\n\ -Size=32,489\n\ -Collapsed=0\n\ -DockId=0x0000000C,0\n\ -\n\ -[Window][Debug]\n\ -Pos=38,96\n\ -Size=1243,574\n\ -Collapsed=0\n\ -\n\ [Docking][Data]\n\ -DockSpace ID=0x8B93E3BD Window=0xA787BDB4 Pos=0,24 Size=1280,731 Split=Y Selected=0x6C01C512\n\ - DockNode ID=0x00000001 Parent=0x8B93E3BD SizeRef=1280,240 Split=X Selected=0xF3094A52\n\ - DockNode ID=0x00000003 Parent=0x00000001 SizeRef=949,231 Split=X Selected=0x65CC51DC\n\ - DockNode ID=0x00000005 Parent=0x00000003 SizeRef=388,231 Selected=0xE283F8D8\n\ - DockNode ID=0x00000006 Parent=0x00000003 SizeRef=559,231 Split=X Selected=0x756E3877\n\ - DockNode ID=0x00000007 Parent=0x00000006 SizeRef=243,231 Split=Y Selected=0xD2BA8AA2\n\ - DockNode ID=0x00000009 Parent=0x00000007 SizeRef=220,177 Selected=0xD2BA8AA2\n\ - DockNode ID=0x0000000A Parent=0x00000007 SizeRef=220,61 HiddenTabBar=1 Selected=0x608FDEB4\n\ - DockNode ID=0x00000008 Parent=0x00000006 SizeRef=314,231 Selected=0xD62F6EEB\n\ - DockNode ID=0x00000004 Parent=0x00000001 SizeRef=329,231 Selected=0xF3094A52\n\ - DockNode ID=0x00000002 Parent=0x8B93E3BD SizeRef=1280,489 Split=X Selected=0x6C01C512\n\ - DockNode ID=0x0000000B Parent=0x00000002 SizeRef=1246,503 CentralNode=1 Selected=0x6C01C512\n\ - DockNode ID=0x0000000C Parent=0x00000002 SizeRef=32,503 HiddenTabBar=1 Selected=0xD67E3EB0\n\ -"; +DockSpace ID=0x8B93E3BD Window=0xA787BDB4 Pos=0,24 Size=1280,731 Split=Y Selected=0x6C01C512\n\ + DockNode ID=0x00000001 Parent=0x8B93E3BD SizeRef=1280,217 Split=X Selected=0xF3094A52\n\ + DockNode ID=0x00000003 Parent=0x00000001 SizeRef=976,231 Split=X Selected=0x65CC51DC\n\ + DockNode ID=0x00000007 Parent=0x00000003 SizeRef=345,231 HiddenTabBar=1 Selected=0x8F5BFC9A\n\ + DockNode ID=0x00000008 Parent=0x00000003 SizeRef=629,231 Split=X Selected=0xD2AD486B\n\ + DockNode ID=0x00000005 Parent=0x00000008 SizeRef=304,406 Split=Y Selected=0x6D682373\n\ + DockNode ID=0x00000009 Parent=0x00000005 SizeRef=292,175 Split=Y Selected=0x6D682373\n\ + DockNode ID=0x0000000D Parent=0x00000009 SizeRef=292,68 HiddenTabBar=1 Selected=0xE57B1A9D\n\ + DockNode ID=0x0000000E Parent=0x00000009 SizeRef=292,105 HiddenTabBar=1 Selected=0x6D682373\n\ + DockNode ID=0x0000000A Parent=0x00000005 SizeRef=292,40 HiddenTabBar=1 Selected=0x0DE44CFF\n\ + DockNode ID=0x00000006 Parent=0x00000008 SizeRef=323,406 Selected=0xD2AD486B\n\ + DockNode ID=0x00000004 Parent=0x00000001 SizeRef=302,231 Selected=0x60B9D088\n\ + DockNode ID=0x00000002 Parent=0x8B93E3BD SizeRef=1280,512 Split=X Selected=0x6C01C512\n\ + DockNode ID=0x0000000B Parent=0x00000002 SizeRef=1246,503 CentralNode=1 HiddenTabBar=1 Selected=0xB9ADD0D5\n\ + DockNode ID=0x0000000C Parent=0x00000002 SizeRef=32,503 HiddenTabBar=1 Selected=0x644DA2C1\n\n"; void FurnaceGUI::prepareLayout() { FILE* check; diff --git a/src/gui/gui.h b/src/gui/gui.h index 22e8036e0..b0812b387 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -577,7 +577,7 @@ class FurnaceGUI { scrollStep(0), sysSeparators(1), forceMono(0), - controlLayout(0), + controlLayout(3), restartOnFlagChange(1), statusDisplay(0), dpiScale(0.0f), diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index a10795fb5..aa342ae9a 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -893,7 +893,7 @@ void FurnaceGUI::syncSettings() { settings.scrollStep=e->getConfInt("scrollStep",0); settings.sysSeparators=e->getConfInt("sysSeparators",1); settings.forceMono=e->getConfInt("forceMono",0); - settings.controlLayout=e->getConfInt("controlLayout",0); + settings.controlLayout=e->getConfInt("controlLayout",3); settings.restartOnFlagChange=e->getConfInt("restartOnFlagChange",1); settings.statusDisplay=e->getConfInt("statusDisplay",0); settings.dpiScale=e->getConfFloat("dpiScale",0.0f); From 8355aa01759ac423c4c513e68b56af8485a36452 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 15 Mar 2022 00:09:45 -0500 Subject: [PATCH 262/637] GUI: OPL 4-op algorithm images --- src/gui/insEdit.cpp | 125 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 124 insertions(+), 1 deletion(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 862de6e81..fa03091dd 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -470,6 +470,129 @@ void FurnaceGUI::drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, cons } } break; + case FM_ALGS_4OP_OPL: + switch (alg) { + case 0: { // 1 > 2 > 3 > 4 + ImVec2 pos1=ImLerp(rect.Min,rect.Max,ImVec2(0.2,0.5)); + ImVec2 pos2=ImLerp(rect.Min,rect.Max,ImVec2(0.4,0.5)); + ImVec2 pos3=ImLerp(rect.Min,rect.Max,ImVec2(0.6,0.5)); + ImVec2 pos4=ImLerp(rect.Min,rect.Max,ImVec2(0.8,0.5)); + dl->AddCircleFilled(pos1,4.0f*dpiScale+1.0f,color); + dl->AddCircle(pos1,6.0f*dpiScale+1.0f,color); + addAALine(dl,pos1,pos2,colorL); + dl->AddCircleFilled(pos2,4.0f*dpiScale+1.0f,color); + addAALine(dl,pos2,pos3,colorL); + dl->AddCircleFilled(pos3,4.0f*dpiScale+1.0f,color); + addAALine(dl,pos3,pos4,colorL); + dl->AddCircleFilled(pos4,4.0f*dpiScale+1.0f,color); + + pos1.x-=ImGui::CalcTextSize("1").x*0.5; + pos2.x-=ImGui::CalcTextSize("2").x*0.5; + pos3.x-=ImGui::CalcTextSize("3").x*0.5; + pos4.x-=ImGui::CalcTextSize("4").x*0.5; + pos1.y-=ImGui::CalcTextSize("1").y+circleRadius; + pos2.y-=ImGui::CalcTextSize("2").y+circleRadius; + pos3.y-=ImGui::CalcTextSize("3").y+circleRadius; + pos4.y-=ImGui::CalcTextSize("4").y+circleRadius; + dl->AddText(pos1,color,"1"); + dl->AddText(pos2,color,"2"); + dl->AddText(pos3,color,"3"); + dl->AddText(pos4,color,"4"); + break; + } + case 1: { // 1 + (2 > 3 > 4) + ImVec2 pos1=ImLerp(rect.Min,rect.Max,ImVec2(0.4,0.3)); + ImVec2 pos2=ImLerp(rect.Min,rect.Max,ImVec2(0.2,0.7)); + ImVec2 pos3=ImLerp(rect.Min,rect.Max,ImVec2(0.4,0.7)); + ImVec2 pos4=ImLerp(rect.Min,rect.Max,ImVec2(0.6,0.7)); + ImVec2 pos5=ImLerp(rect.Min,rect.Max,ImVec2(0.8,0.7)); + dl->AddCircleFilled(pos1,4.0f*dpiScale+1.0f,color); + dl->AddCircle(pos1,6.0f*dpiScale+1.0f,color); + addAALine(dl,pos1,pos5,colorL); + dl->AddCircleFilled(pos2,4.0f*dpiScale+1.0f,color); + addAALine(dl,pos2,pos3,colorL); + dl->AddCircleFilled(pos3,4.0f*dpiScale+1.0f,color); + addAALine(dl,pos3,pos4,colorL); + dl->AddCircleFilled(pos4,4.0f*dpiScale+1.0f,color); + + addAALine(dl,pos4,pos5,colorL); + + pos1.x-=ImGui::CalcTextSize("2").x+circleRadius+3.0*dpiScale; + pos2.x-=ImGui::CalcTextSize("2").x+circleRadius+3.0*dpiScale; + pos3.x-=ImGui::CalcTextSize("3").x+circleRadius+3.0*dpiScale; + pos4.x-=ImGui::CalcTextSize("4").x*0.5; + pos1.y-=ImGui::CalcTextSize("1").y*0.5; + pos2.y-=ImGui::CalcTextSize("2").y*0.5; + pos3.y-=ImGui::CalcTextSize("3").y*0.5; + pos4.y-=ImGui::CalcTextSize("4").y+circleRadius; + dl->AddText(pos1,color,"1"); + dl->AddText(pos2,color,"2"); + dl->AddText(pos3,color,"3"); + dl->AddText(pos4,color,"4"); + break; + } + case 2: { // (1>2) + (3>4) + ImVec2 pos1=ImLerp(rect.Min,rect.Max,ImVec2(0.25,0.3)); + ImVec2 pos2=ImLerp(rect.Min,rect.Max,ImVec2(0.5,0.3)); + ImVec2 pos3=ImLerp(rect.Min,rect.Max,ImVec2(0.25,0.7)); + ImVec2 pos4=ImLerp(rect.Min,rect.Max,ImVec2(0.5,0.7)); + ImVec2 pos5=ImLerp(rect.Min,rect.Max,ImVec2(0.75,0.5)); + dl->AddCircleFilled(pos1,4.0f*dpiScale+1.0f,color); + dl->AddCircle(pos1,6.0f*dpiScale+1.0f,color); + addAALine(dl,pos1,pos2,colorL); + dl->AddCircleFilled(pos2,4.0f*dpiScale+1.0f,color); + dl->AddCircleFilled(pos3,4.0f*dpiScale+1.0f,color); + addAALine(dl,pos3,pos4,colorL); + dl->AddCircleFilled(pos4,4.0f*dpiScale+1.0f,color); + addAALine(dl,pos2,pos5,colorL); + addAALine(dl,pos4,pos5,colorL); + + pos1.x-=ImGui::CalcTextSize("2").x+circleRadius+3.0*dpiScale; + pos2.x-=ImGui::CalcTextSize("2").x+circleRadius+3.0*dpiScale; + pos3.x-=ImGui::CalcTextSize("3").x+circleRadius+3.0*dpiScale; + pos4.x-=ImGui::CalcTextSize("4").x+circleRadius+3.0*dpiScale; + pos1.y-=ImGui::CalcTextSize("1").y*0.5; + pos2.y-=ImGui::CalcTextSize("2").y*0.5; + pos3.y-=ImGui::CalcTextSize("3").y*0.5; + pos4.y-=ImGui::CalcTextSize("4").y*0.5; + dl->AddText(pos1,color,"1"); + dl->AddText(pos2,color,"2"); + dl->AddText(pos3,color,"3"); + dl->AddText(pos4,color,"4"); + break; + } + case 3: { // 1 + (2 > 3) + 4 + ImVec2 pos1=ImLerp(rect.Min,rect.Max,ImVec2(0.5,0.25)); + ImVec2 pos2=ImLerp(rect.Min,rect.Max,ImVec2(0.25,0.5)); + ImVec2 pos3=ImLerp(rect.Min,rect.Max,ImVec2(0.5,0.5)); + ImVec2 pos4=ImLerp(rect.Min,rect.Max,ImVec2(0.5,0.75)); + ImVec2 pos5=ImLerp(rect.Min,rect.Max,ImVec2(0.75,0.5)); + dl->AddCircleFilled(pos1,4.0f*dpiScale+1.0f,color); + dl->AddCircle(pos1,6.0f*dpiScale+1.0f,color); + addAALine(dl,pos2,pos3,colorL); + dl->AddCircleFilled(pos2,4.0f*dpiScale+1.0f,color); + dl->AddCircleFilled(pos3,4.0f*dpiScale+1.0f,color); + dl->AddCircleFilled(pos4,4.0f*dpiScale+1.0f,color); + addAALine(dl,pos1,pos5,colorL); + addAALine(dl,pos3,pos5,colorL); + addAALine(dl,pos4,pos5,colorL); + + pos1.x-=ImGui::CalcTextSize("2").x+circleRadius+3.0*dpiScale; + pos2.x-=ImGui::CalcTextSize("2").x+circleRadius+3.0*dpiScale; + pos3.x-=ImGui::CalcTextSize("3").x+circleRadius+3.0*dpiScale; + pos4.x-=ImGui::CalcTextSize("4").x+circleRadius+3.0*dpiScale; + pos1.y-=ImGui::CalcTextSize("1").y*0.5; + pos2.y-=ImGui::CalcTextSize("2").y*0.5; + pos3.y-=ImGui::CalcTextSize("3").y*0.5; + pos4.y-=ImGui::CalcTextSize("4").y*0.5; + dl->AddText(pos1,color,"1"); + dl->AddText(pos2,color,"2"); + dl->AddText(pos3,color,"3"); + dl->AddText(pos4,color,"4"); + break; + } + } + break; default: break; } @@ -818,7 +941,7 @@ void FurnaceGUI::drawInsEdit() { ins->fm.opllPreset=drums?16:0; } ImGui::TableNextColumn(); - drawAlgorithm(ins->fm.alg&1,FM_ALGS_2OP_OPL,ImVec2(ImGui::GetContentRegionAvail().x,48.0*dpiScale)); + drawAlgorithm(ins->fm.alg&algMax,fourOp?FM_ALGS_4OP_OPL:FM_ALGS_2OP_OPL,ImVec2(ImGui::GetContentRegionAvail().x,48.0*dpiScale)); break; } case DIV_INS_OPLL: { From a65df5cdabf6b24e88eb1e4c6a810f884ff14783 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 15 Mar 2022 00:33:57 -0500 Subject: [PATCH 263/637] OPL: pretend to be YMU and enable SOME emulation? --- src/engine/dispatchContainer.cpp | 4 ++++ src/engine/fileOps.cpp | 9 ++++++++- src/engine/platform/opl.cpp | 16 ++++++++++++++-- src/engine/platform/opl.h | 2 +- 4 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 3dcf17a8f..62daaafe0 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -150,6 +150,10 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do bbInLen=32768; switch (sys) { + case DIV_SYSTEM_YMU759: + dispatch=new DivPlatformOPL; + ((DivPlatformOPL*)dispatch)->setOPLType(759,false); + break; case DIV_SYSTEM_YM2612: dispatch=new DivPlatformGenesis; ((DivPlatformGenesis*)dispatch)->setYMFM(eng->getConfInt("ym2612Core",0)); diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index da4ca4e82..4400567eb 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -223,7 +223,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { } ds.customTempo=true; ds.timeBase=0; - addWarning("Yamaha YMU759 emulation is not currently possible!"); + addWarning("Yamaha YMU759 emulation is incomplete! please migrate your song to the OPL3 system."); } logI("reading pattern matrix (%d)...\n",ds.ordersLen); @@ -277,6 +277,9 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { if ((ds.system[0]==DIV_SYSTEM_SMS_OPLL || ds.system[0]==DIV_SYSTEM_NES_VRC7) && ins->type==DIV_INS_FM) { ins->type=DIV_INS_OPLL; } + if (ds.system[0]==DIV_SYSTEM_YMU759) { + ins->type=DIV_INS_OPL; + } if (ins->mode) { // FM ins->fm.alg=reader.readC(); @@ -573,6 +576,10 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { pat->data[k][1]-=2; } } + if (ds.system[0]==DIV_SYSTEM_YMU759 && pat->data[k][0]!=0) { + // apparently YMU759 is stored 2 octaves lower + pat->data[k][1]+=2; + } if (pat->data[k][0]==0 && pat->data[k][1]!=0) { logD("what? %d:%d:%d note %d octave %d\n",i,j,k,pat->data[k][0],pat->data[k][1]); pat->data[k][0]=12; diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index 84e8dd260..443ef81d6 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -560,6 +560,11 @@ int DivPlatformOPL::dispatch(DivCommand c) { chan[c.chan].std.release(); break; case DIV_CMD_VOLUME: { + if (pretendYMU) { + c.value=pow(((double)c.value/127.0),0.5)*63.0; + if (c.value<0) c.value=0; + if (c.value>63) c.value=63; + } chan[c.chan].vol=c.value; if (!chan[c.chan].std.hasVol) { chan[c.chan].outVol=c.value; @@ -716,6 +721,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { return 0; break; case DIV_CMD_GET_VOLMAX: + if (pretendYMU) return 127; return 63; break; case DIV_CMD_PRE_PORTA: @@ -868,6 +874,7 @@ void DivPlatformOPL::setYMFM(bool use) { } void DivPlatformOPL::setOPLType(int type, bool drums) { + pretendYMU=false; switch (type) { case 1: case 2: slotsNonDrums=slotsOPL2; @@ -879,7 +886,7 @@ void DivPlatformOPL::setOPLType(int type, bool drums) { melodicChans=drums?6:9; totalChans=drums?11:9; break; - case 3: + case 3: case 759: slotsNonDrums=slotsOPL3; slotsDrums=slotsOPL3Drums; slots=drums?slotsDrums:slotsNonDrums; @@ -888,9 +895,14 @@ void DivPlatformOPL::setOPLType(int type, bool drums) { chans=18; melodicChans=drums?15:18; totalChans=drums?20:18; + if (type==759) pretendYMU=true; break; } - oplType=type; + if (type==759) { + oplType=3; + } else { + oplType=type; + } properDrumsSys=drums; } diff --git a/src/engine/platform/opl.h b/src/engine/platform/opl.h index b8bd11d69..8f0994bb5 100644 --- a/src/engine/platform/opl.h +++ b/src/engine/platform/opl.h @@ -79,7 +79,7 @@ class DivPlatformOPL: public DivDispatch { unsigned char lfoValue; - bool useYMFM, update4OpMask; + bool useYMFM, update4OpMask, pretendYMU; short oldWrites[512]; short pendingWrites[512]; From 1918f59f571cb65fadd8e8fc9b7bace7c3a5f76c Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 15 Mar 2022 02:52:22 -0500 Subject: [PATCH 264/637] don't error out all the time --- src/engine/fileOps.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 4400567eb..5b4846433 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -1550,7 +1550,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { logE("premature end of file!\n"); lastError="incomplete file"; } catch (InvalidHeaderException e) { - logE("invalid info header!\n"); + //logE("invalid info header!\n"); lastError="invalid info header!"; } return success; From f4c3f5a17fe19f746a3ef132c120aaa9d7f6186e Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 15 Mar 2022 03:25:38 -0500 Subject: [PATCH 265/637] MOD import: fix klisje --- src/engine/fileOps.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 5b4846433..d02a22c74 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -1464,7 +1464,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { break; case 15: // set speed // TODO somehow handle VBlank tunes - if (fxVal>=0x20) { + if (fxVal>0x20) { writeFxCol(0xf0,fxVal); } else { writeFxCol(0x09,fxVal); From 219c5a052231904b64a9acf212807085f0a02b80 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 15 Mar 2022 04:23:27 -0500 Subject: [PATCH 266/637] MOD import: add some stereo separation --- src/engine/fileOps.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index d02a22c74..7c93b4237 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -1434,6 +1434,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { fxVal=0; // fall through case 4: // vibrato + // TODO: handle 0 value? if (fxVal==0) { if (setEffectState[2]<0) break; fxVal=setEffectState[2]; @@ -1463,7 +1464,8 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { data[row][3]=fxVal; break; case 15: // set speed - // TODO somehow handle VBlank tunes + // TODO: somehow handle VBlank tunes + // TODO: klisje is still broken, perhaps because there wasn't tempo set back then? if (fxVal>0x20) { writeFxCol(0xf0,fxVal); } else { @@ -1515,7 +1517,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { ds.systemLen=(chCount+3)/4; for(int i=0; i Date: Tue, 15 Mar 2022 15:28:45 -0500 Subject: [PATCH 267/637] OPL: some fixes --- src/engine/platform/opl.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index 443ef81d6..259799378 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -481,6 +481,10 @@ void DivPlatformOPL::muteChannel(int ch, bool mute) { int DivPlatformOPL::dispatch(DivCommand c) { // TODO: drums mode! if (c.chan>=melodicChans) return 0; + // ineffective in 4-op mode + if (oplType==3 && c.chan<14 && (c.chan&1) && c.cmd!=DIV_CMD_GET_VOLMAX && c.cmd!=DIV_ALWAYS_SET_VOLUME) { + if (chan[c.chan-1].fourOp) return 0; + } switch (c.cmd) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(chan[c.chan].ins); @@ -589,6 +593,9 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_GET_VOLUME: { + if (pretendYMU) { + return pow(((double)chan[c.chan].vol/63.0),2.0)*127.0; + } return chan[c.chan].vol; break; } @@ -599,6 +606,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { chan[c.chan].ins=c.value; break; case DIV_CMD_PANNING: { + if (oplType!=3) break; if (c.value==0) { chan[c.chan].pan=3; } else { @@ -616,7 +624,6 @@ int DivPlatformOPL::dispatch(DivCommand c) { rWrite(chanMap[c.chan+1]+ADDR_LR_FB_ALG,((chan[c.chan].state.alg>>1)&1)|(chan[c.chan].state.fb<<1)|((chan[c.chan].pan&3)<<4)); } } - //rWrite(chanOffs[c.chan]+ADDR_LRAF,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|(chan[c.chan].state.fms&7)|((chan[c.chan].state.ams&3)<<4)); break; } case DIV_CMD_PITCH: { @@ -739,6 +746,8 @@ int DivPlatformOPL::dispatch(DivCommand c) { void DivPlatformOPL::forceIns() { for (int i=0; i5); + return false; } bool DivPlatformOPL::keyOffAffectsPorta(int ch) { - return (ch>5); + return false; } void DivPlatformOPL::notifyInsChange(int ins) { From 83e7d966b9efd14772d7b2b5a7d43020d3ce7c5b Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 15 Mar 2022 18:01:24 -0500 Subject: [PATCH 268/637] OPL: more 4-op mode fixes --- src/engine/platform/opl.cpp | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index 259799378..1e77b1e6e 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -349,9 +349,6 @@ void DivPlatformOPL::tick() { if (chan[i].keyOn || chan[i].keyOff) { immWrite(chanMap[i]+ADDR_FREQH,0x00|(chan[i].freqH&31)); - if (chan[i].state.ops==4 && i<6) { - immWrite(chanMap[i+1]+ADDR_FREQH,0x00|(chan[i].freqH&31)); - } chan[i].keyOff=false; } } @@ -361,7 +358,7 @@ void DivPlatformOPL::tick() { if (oplType==3) { unsigned char opMask=chan[0].fourOp|(chan[2].fourOp<<1)|(chan[4].fourOp<<2)|(chan[6].fourOp<<3)|(chan[8].fourOp<<4)|(chan[10].fourOp<<5); immWrite(0x104,opMask); - //printf("updating opMask to %.2x\n",opMask); + printf("updating opMask to %.2x\n",opMask); } } @@ -380,21 +377,12 @@ void DivPlatformOPL::tick() { chan[i].freqH=freqt>>8; chan[i].freqL=freqt&0xff; immWrite(chanMap[i]+ADDR_FREQ,chan[i].freqL); - if (chan[i].state.ops==4 && i<6) { - immWrite(chanMap[i+1]+ADDR_FREQ,chan[i].freqL); - } } if (chan[i].keyOn) { immWrite(chanMap[i]+ADDR_FREQH,chan[i].freqH|(0x20)); - if (chan[i].state.ops==4 && i<6) { - immWrite(chanMap[i+1]+ADDR_FREQH,chan[i].freqH|(0x20)); - } chan[i].keyOn=false; } else if (chan[i].freqChanged) { immWrite(chanMap[i]+ADDR_FREQH,chan[i].freqH|(chan[i].active<<5)); - if (chan[i].state.ops==4 && i<6) { - immWrite(chanMap[i+1]+ADDR_FREQH,chan[i].freqH|(chan[i].active<<5)); - } } chan[i].freqChanged=false; } @@ -526,13 +514,17 @@ int DivPlatformOPL::dispatch(DivCommand c) { } if (isMuted[c.chan]) { + oldWrites[chanMap[c.chan]+ADDR_LR_FB_ALG]=-1; rWrite(chanMap[c.chan]+ADDR_LR_FB_ALG,(chan[c.chan].state.alg&1)|(chan[c.chan].state.fb<<1)); if (ops==4) { + oldWrites[chanMap[c.chan+1]+ADDR_LR_FB_ALG]=-1; rWrite(chanMap[c.chan+1]+ADDR_LR_FB_ALG,((chan[c.chan].state.alg>>1)&1)|(chan[c.chan].state.fb<<1)); } } else { + oldWrites[chanMap[c.chan]+ADDR_LR_FB_ALG]=-1; rWrite(chanMap[c.chan]+ADDR_LR_FB_ALG,(chan[c.chan].state.alg&1)|(chan[c.chan].state.fb<<1)|((chan[c.chan].pan&3)<<4)); if (ops==4) { + oldWrites[chanMap[c.chan+1]+ADDR_LR_FB_ALG]=-1; rWrite(chanMap[c.chan+1]+ADDR_LR_FB_ALG,((chan[c.chan].state.alg>>1)&1)|(chan[c.chan].state.fb<<1)|((chan[c.chan].pan&3)<<4)); } } From ff2b3e77a007044065a1a7839379ada7e6521f9c Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 15 Mar 2022 18:02:29 -0500 Subject: [PATCH 269/637] MOD import: don't complain about end of file I gotta figure out how to fix this --- src/engine/fileOps.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 7c93b4237..a77722c5a 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -1252,15 +1252,19 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { // check mod magic bytes if (!reader.seek(1080,SEEK_SET)) { + logD("couldn't seek to 1080\n"); throw EndOfFileException(&reader,reader.tell()); } reader.read(magic,4); if (memcmp(magic,"M.K.",4)==0 || memcmp(magic,"M!K!",4)==0) { + logD("detected a ProTracker module\n"); chCount=4; } else if(memcmp(magic+1,"CHN",3)==0 && magic[0]>='1' && magic[0]<='9') { + logD("detected a FastTracker module\n"); 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')) { + logD("detected a FastTracker module\n"); chCount=((magic[0]-'0')*10)+(magic[1]-'0'); } else { // TODO: Soundtracker MOD? @@ -1549,7 +1553,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { } success=true; } catch (EndOfFileException e) { - logE("premature end of file!\n"); + //logE("premature end of file!\n"); lastError="incomplete file"; } catch (InvalidHeaderException e) { //logE("invalid info header!\n"); From 10cea9956b3dcd3e1af265ed8e49b130b0840da8 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 15 Mar 2022 18:30:29 -0500 Subject: [PATCH 270/637] X1-010: fix crash --- src/engine/platform/x1_010.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/engine/platform/x1_010.cpp b/src/engine/platform/x1_010.cpp index 8d1338e14..822f2c40b 100644 --- a/src/engine/platform/x1_010.cpp +++ b/src/engine/platform/x1_010.cpp @@ -855,7 +855,10 @@ void DivPlatformX1_010::setFlags(unsigned int flags) { case 1: // 16.67MHz (later hardwares) chipClock=50000000.0/3.0; break; - // Other clock is used? + // Other clock is used + default: + chipClock=16000000; + break; } rate=chipClock/512; stereo=flags&16; From 0a307fc4a6eb3acf3330bff50105822f8441c7d5 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 15 Mar 2022 18:32:15 -0500 Subject: [PATCH 271/637] MOD import: more improvements - prepare for old Soundtracker MOD import - add "bypass limits" flag - dope.mod plays correctly now - automatic channel names --- src/engine/fileOps.cpp | 38 ++++++++++++++++++++++++++--------- src/engine/platform/amiga.cpp | 8 +++++++- src/engine/platform/amiga.h | 2 ++ src/gui/gui.cpp | 16 +++++++++------ 4 files changed, 47 insertions(+), 17 deletions(-) diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index a77722c5a..28b7ff8fe 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -1250,32 +1250,44 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { ds.tuning=436.0; ds.version=DIV_VERSION_MOD; + int insCount=31; + bool bypassLimits=false; + // check mod magic bytes if (!reader.seek(1080,SEEK_SET)) { logD("couldn't seek to 1080\n"); throw EndOfFileException(&reader,reader.tell()); } reader.read(magic,4); - if (memcmp(magic,"M.K.",4)==0 || memcmp(magic,"M!K!",4)==0) { + if (memcmp(magic,"M.K.",4)==0 || memcmp(magic,"M!K!",4)==0 || memcmp(magic,"M&K!",4)==0) { logD("detected a ProTracker module\n"); chCount=4; - } else if(memcmp(magic+1,"CHN",3)==0 && magic[0]>='1' && magic[0]<='9') { + } else if (memcmp(magic,"CD81",4)==0 || memcmp(magic,"OKTA",4)==0 || memcmp(magic,"OCTA",4)==0) { + logD("detected an Oktalyzer/Octalyzer/OctaMED module\n"); + chCount=8; + } else if (memcmp(magic+1,"CHN",3)==0 && magic[0]>='1' && magic[0]<='9') { logD("detected a FastTracker module\n"); 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')) { - logD("detected a FastTracker module\n"); + } else if (memcmp(magic,"FLT",3)==0 && magic[3]>='1' && magic[3]<='9') { + logD("detected a Fairlight module\n"); + chCount=magic[3]-'0'; + } else if (memcmp(magic,"TDZ",3)==0 && magic[3]>='1' && magic[3]<='9') { + logD("detected a TakeTracker module\n"); + chCount=magic[3]-'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')) { + logD("detected a Fast/TakeTracker module\n"); chCount=((magic[0]-'0')*10)+(magic[1]-'0'); } else { // TODO: Soundtracker MOD? + insCount=15; 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++) { + for (int i=0;idepth=8; sample->name=reader.readString(22); @@ -1302,6 +1314,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { sample->init(slen); ds.sample.push_back(sample); } + ds.sampleLen=ds.sample.size(); // orders ds.ordersLen=ordCount=reader.readC(); reader.readC(); // restart position, unused @@ -1343,6 +1356,9 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { short note=(short)round(log2(3424.0/period)*12); dstrow[0]=((note-1)%12)+1; dstrow[1]=(note-1)/12+1; + if (period<114) { + bypassLimits=true; + } } // effects are done later short fxtyp=data[2]&0x0f; @@ -1521,24 +1537,26 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { 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); } + ds.insLen=ds.ins.size(); if (active) quitDispatch(); isBusy.lock(); diff --git a/src/engine/platform/amiga.cpp b/src/engine/platform/amiga.cpp index 31a7a04c6..49b5b59ab 100644 --- a/src/engine/platform/amiga.cpp +++ b/src/engine/platform/amiga.cpp @@ -93,7 +93,11 @@ void DivPlatformAmiga::acquire(short* bufL, short* bufR, size_t start, size_t le chan[i].busClock=0; } }*/ - chan[i].audSub+=MAX(114,chan[i].freq); + if (bypassLimits) { + chan[i].audSub+=MAX(AMIGA_DIVIDER,chan[i].freq); + } else { + chan[i].audSub+=MAX(114,chan[i].freq); + } } } if (!isMuted[i]) { @@ -355,6 +359,8 @@ void DivPlatformAmiga::setFlags(unsigned int flags) { rate=chipClock/AMIGA_DIVIDER; sep1=((flags>>8)&127)+127; sep2=127-((flags>>8)&127); + amigaModel=flags&2; + bypassLimits=flags&4; } int DivPlatformAmiga::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { diff --git a/src/engine/platform/amiga.h b/src/engine/platform/amiga.h index 93b61b6eb..02dcb3de9 100644 --- a/src/engine/platform/amiga.h +++ b/src/engine/platform/amiga.h @@ -66,6 +66,8 @@ class DivPlatformAmiga: public DivDispatch { }; Channel chan[4]; bool isMuted[4]; + bool bypassLimits; + bool amigaModel; int sep1, sep2; diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 344480b47..809c61fe7 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -5853,19 +5853,23 @@ bool FurnaceGUI::loop() { if (ImGui::SliderInt("##StereoSep",&stereoSep,0,127)) { if (stereoSep<0) stereoSep=0; if (stereoSep>127) stereoSep=127; - e->setSysFlags(i,(flags&1)|((stereoSep&127)<<8),restart); + e->setSysFlags(i,(flags&(~0x7f00))|((stereoSep&127)<<8),restart); updateWindowTitle(); } rightClickable - /* TODO LATER: I want 0.5 out already if (ImGui::RadioButton("Amiga 500 (OCS)",(flags&2)==0)) { - e->setSysFlags(i,flags&1); + e->setSysFlags(i,flags&(~2),restart); } if (ImGui::RadioButton("Amiga 1200 (AGA)",(flags&2)==2)) { - e->setSysFlags(i,(flags&1)|2); - }*/ + e->setSysFlags(i,(flags&(~2))|2,restart); + } sysPal=flags&1; if (ImGui::Checkbox("PAL",&sysPal)) { - e->setSysFlags(i,(flags&2)|sysPal,restart); + e->setSysFlags(i,(flags&(~1))|sysPal,restart); + updateWindowTitle(); + } + bool bypassLimits=flags&4; + if (ImGui::Checkbox("Bypass frequency limits",&bypassLimits)) { + e->setSysFlags(i,(flags&(~4))|(bypassLimits<<2),restart); updateWindowTitle(); } break; From fa32cadd361838092a98644f3e54524b660b05c5 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 15 Mar 2022 18:34:41 -0500 Subject: [PATCH 272/637] fix conflicting system flags when changing systems --- src/engine/engine.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index c16392772..18841d4e3 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -612,6 +612,7 @@ void DivEngine::changeSystem(int index, DivSystem which) { quitDispatch(); isBusy.lock(); song.system[index]=which; + song.systemFlags[index]=0; recalcChans(); isBusy.unlock(); initDispatch(); @@ -633,7 +634,10 @@ bool DivEngine::addSystem(DivSystem which) { } quitDispatch(); isBusy.lock(); - song.system[song.systemLen++]=which; + song.system[song.systemLen]=which; + song.systemVol[song.systemLen]=64; + song.systemPan[song.systemLen]=0; + song.systemFlags[song.systemLen++]=0; recalcChans(); isBusy.unlock(); initDispatch(); From d8be1ddc4bc02e9840341aa8250fe7c66f73452b Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 15 Mar 2022 18:36:24 -0500 Subject: [PATCH 273/637] make addSystem error more clear --- src/engine/engine.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 18841d4e3..0bbf4cb48 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -624,7 +624,7 @@ void DivEngine::changeSystem(int index, DivSystem which) { bool DivEngine::addSystem(DivSystem which) { if (song.systemLen>32) { - lastError="cannot add more than 32"; + lastError="max number of systems is 32"; return false; } // this was DIV_MAX_CHANS but I am setting it to 63 for now due to an ImGui limitation From 9b3e0d45bd4c7084bb9f14193e23250bd6edae26 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 15 Mar 2022 18:36:34 -0500 Subject: [PATCH 274/637] prepare for decimal Hz? --- src/engine/song.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/engine/song.h b/src/engine/song.h index 130c9a916..3e74fa1e6 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -271,6 +271,7 @@ struct DivSong { unsigned char timeBase, speed1, speed2, arpLen; bool pal; bool customTempo; + // TODO: change Hz to float? int hz, patLen, ordersLen, insLen, waveLen, sampleLen; float masterVol; float tuning; From 724bd5b8dcd4c5bd5fb1a9cea116b0cf842ae933 Mon Sep 17 00:00:00 2001 From: cam900 Date: Wed, 16 Mar 2022 11:35:22 +0900 Subject: [PATCH 275/637] Fix X1-010 muting --- src/engine/platform/x1_010.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/engine/platform/x1_010.cpp b/src/engine/platform/x1_010.cpp index 822f2c40b..47c8a10af 100644 --- a/src/engine/platform/x1_010.cpp +++ b/src/engine/platform/x1_010.cpp @@ -339,7 +339,11 @@ void DivPlatformX1_010::updateEnvelope(int ch) { } chWrite(ch,5,0x10|(ch&0xf)); } else { - chWrite(ch,1,(chan[ch].lvol<<4)|chan[ch].rvol); + if (isMuted[ch]) { + chWrite(ch,1,0); + } else { + chWrite(ch,1,(chan[ch].lvol<<4)|chan[ch].rvol); + } } } @@ -459,10 +463,8 @@ void DivPlatformX1_010::tick() { } } if (chan[i].envChanged) { - if (!isMuted[i]) { - chan[i].lvol=((chan[i].outVol&0xf)*((chan[i].pan>>4)&0xf))/15; - chan[i].rvol=((chan[i].outVol&0xf)*((chan[i].pan>>0)&0xf))/15; - } + chan[i].lvol=isMuted[i]?0:((chan[i].outVol&0xf)*((chan[i].pan>>4)&0xf))/15; + chan[i].rvol=isMuted[i]?0:((chan[i].outVol&0xf)*((chan[i].pan>>0)&0xf))/15; updateEnvelope(i); chan[i].envChanged=false; } @@ -661,9 +663,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { if (chan[c.chan].pcm!=(c.value&1)) { chan[c.chan].pcm=c.value&1; chan[c.chan].freqChanged=true; - if (!isMuted[c.chan]) { - chan[c.chan].envChanged=true; - } + chan[c.chan].envChanged=true; } break; case DIV_CMD_SAMPLE_BANK: From 53ad1c93e6f58c339259d8e8b592ecb5344a321b Mon Sep 17 00:00:00 2001 From: cam900 Date: Wed, 16 Mar 2022 11:40:10 +0900 Subject: [PATCH 276/637] Reduce line --- src/engine/platform/x1_010.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/engine/platform/x1_010.cpp b/src/engine/platform/x1_010.cpp index 47c8a10af..cec8659b4 100644 --- a/src/engine/platform/x1_010.cpp +++ b/src/engine/platform/x1_010.cpp @@ -339,11 +339,7 @@ void DivPlatformX1_010::updateEnvelope(int ch) { } chWrite(ch,5,0x10|(ch&0xf)); } else { - if (isMuted[ch]) { - chWrite(ch,1,0); - } else { - chWrite(ch,1,(chan[ch].lvol<<4)|chan[ch].rvol); - } + chWrite(ch,1,isMuted[ch]?0:((chan[ch].lvol<<4)|chan[ch].rvol)); } } From c269346eda1c0da0ae4741c8d14e9397e62c3ba8 Mon Sep 17 00:00:00 2001 From: cam900 Date: Wed, 16 Mar 2022 11:41:24 +0900 Subject: [PATCH 277/637] safety check --- src/engine/platform/x1_010.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine/platform/x1_010.cpp b/src/engine/platform/x1_010.cpp index cec8659b4..d70a31df1 100644 --- a/src/engine/platform/x1_010.cpp +++ b/src/engine/platform/x1_010.cpp @@ -459,8 +459,8 @@ void DivPlatformX1_010::tick() { } } if (chan[i].envChanged) { - chan[i].lvol=isMuted[i]?0:((chan[i].outVol&0xf)*((chan[i].pan>>4)&0xf))/15; - chan[i].rvol=isMuted[i]?0:((chan[i].outVol&0xf)*((chan[i].pan>>0)&0xf))/15; + chan[i].lvol=isMuted[i]?0:(((chan[i].outVol&0xf)*((chan[i].pan>>4)&0xf))/15); + chan[i].rvol=isMuted[i]?0:(((chan[i].outVol&0xf)*((chan[i].pan>>0)&0xf))/15); updateEnvelope(i); chan[i].envChanged=false; } From 4afd3b3ff1f9cc8f6037af1140d8a2ded3728805 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 15 Mar 2022 21:45:31 -0500 Subject: [PATCH 278/637] MOD import: coding style and uninit fix --- src/engine/fileOps.cpp | 74 +++++++++++++++++++++++++----------------- 1 file changed, 44 insertions(+), 30 deletions(-) diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 28b7ff8fe..65ef0113d 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -1235,16 +1235,21 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { bool DivEngine::loadMod(unsigned char* file, size_t len) { struct InvalidHeaderException {}; bool success=false; - int chCount; - int ordCount; + int chCount=0; + int ordCount=0; 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]; + bool fxUsage[DIV_MAX_CHANS][5]; SafeReader reader=SafeReader(file,len); warnings=""; + + memset(defaultVols,0,31*sizeof(short)); + memset(sampLens,0,31*sizeof(int)); + memset(fxUsage,0,DIV_MAX_CHANS*5*sizeof(bool)); + try { DivSong ds; ds.tuning=436.0; @@ -1274,8 +1279,8 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { } else if (memcmp(magic,"TDZ",3)==0 && magic[3]>='1' && magic[3]<='9') { logD("detected a TakeTracker module\n"); chCount=magic[3]-'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')) { + } 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')) { logD("detected a Fast/TakeTracker module\n"); chCount=((magic[0]-'0')*10)+(magic[1]-'0'); } else { @@ -1283,11 +1288,13 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { insCount=15; throw InvalidHeaderException(); } + // song name reader.seek(0,SEEK_SET); ds.name=reader.readString(20); + // samples - for (int i=0;idepth=8; sample->name=reader.readString(22); @@ -1308,40 +1315,44 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { loopLen=0; } if(loopLen>=2) { - if(loopEndloopStart=loopStart; } sample->init(slen); ds.sample.push_back(sample); } ds.sampleLen=ds.sample.size(); + // orders ds.ordersLen=ordCount=reader.readC(); reader.readC(); // restart position, unused int patMax=0; - for (int i=0;i<128;i++) { + for (int i=0; i<128; i++) { unsigned char pat=reader.readC(); if (pat>patMax) patMax=pat; - for (int j=0;jdata[row]; + for (int row=0; row<64; row++) { + for (int ch=0; chdata[row]; unsigned char data[4]; reader.read(&data,4); // instrument @@ -1351,7 +1362,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { dstrow[3]=defaultVols[ins-1]; } // note - int period=data[1]+((data[0]&0x0f)*256); + int period=data[1]+((data[0]&0x0f)<<8); if (period>0 && period<0x0fff) { short note=(short)round(log2(3424.0/period)*12); dstrow[0]=((note-1)%12)+1; @@ -1365,9 +1376,9 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { short fxval=data[3]; dstrow[4]=fxtyp; dstrow[5]=fxval; - switch(fxtyp) { + switch (fxtyp) { case 0: - if(fxval!=0) fxUsage[ch][0]=true; + if (fxval!=0) fxUsage[ch][0]=true; break; case 1: case 2: case 3: fxUsage[ch][1]=true; @@ -1387,25 +1398,26 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { fxUsage[ch][3]=true; break; case 10: - if(fxval!=0) fxUsage[ch][4]=true; + if (fxval!=0) fxUsage[ch][4]=true; break; } } } } + // samples size_t pos=reader.tell(); - for (int i=0;i<31;i++) { + 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++) { + 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; + for (int pat=0; pat<=patMax; pat++) { + auto* data=ds.pat[ch].getPattern(pat,true)->data; short lastPitchEffect=-1; short lastEffectState[5]={-1,-1,-1,-1,-1}; short setEffectState[5]={-1,-1,-1,-1,-1}; @@ -1434,15 +1446,15 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { case 1: // note slide up case 2: // note slide down case 3: // porta - if ((fxTyp==3)&&(fxVal==0)) { + 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)) { + if ((effectState[1]!=lastEffectState[1]) || + (fxTyp!=lastPitchEffect) || + (effectState[1]!=0 && data[row][0]>0)) { writeFxCol(fxTyp,fxVal); } lastPitchEffect=fxTyp; @@ -1515,10 +1527,10 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { } break; } - for (int i=0;i<5;i++) { + 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))) { + if (fxUsage[ch][i] && (effectState[i]!=lastEffectState[i] || (effectState[i]!=0 && i==4 && data[row][3]>=0))) { writeFxCol(fxUsageTyp[i],effectState[i]); } } @@ -1548,6 +1560,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { ds.pat[i].effectRows=1; ds.chanShow[i]=false; } + // instrument creation for(int i=0; i Date: Tue, 15 Mar 2022 21:45:45 -0500 Subject: [PATCH 279/637] changes to contributing guidelines --- CONTRIBUTING.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1f57b2025..ad608e4cb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -33,6 +33,23 @@ the coding style is described here: - indent switch cases - preprocessor directives not intended - if macro comprises more than one line, indent +- prefer built-in types: + - `bool` + - `signed char` or `unsigned char` are 8-bit + - when the type is `char`, **always** specify whether it is signed or not. + - unspecified `char` is signed on x86 and unsigned on ARM, so yeah. + - the only situation in where unspecified `char` is allowed is for C strings (`const char*`). + - `short` or `unsigned short` are 16-bit + - `int` or `unsigned int` are 32-bit + - `float` is 32-bit + - `double` is 64-bit + - `long long int` or `unsigned long long int` are 64-bit + - avoid using 64-bit numbers as I still build for 32-bit systems. + - two `long`s are required to make Windows happy. + - `size_t` are 32-bit or 64-bit, depending on architecture. +- in float/double operations, always use decimal and `f` if single-precision. + - e.g. `1.0f` or `1.0` instead of `1`. +- don't use `auto` unless needed. some files (particularly the ones in `src/engine/platform/sound` and `extern/`) don't follow this style. From f4c26dbea7e96b482dc621acade59b0672af238a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 15 Mar 2022 22:05:55 -0500 Subject: [PATCH 280/637] document some of the structures --- src/engine/instrument.h | 17 +++++++++++++++++ src/engine/macroInt.h | 19 +++++++++++++++++++ src/engine/pattern.h | 24 ++++++++++++++++++++++++ src/engine/sample.h | 33 +++++++++++++++++++++++++++++++++ src/engine/song.h | 4 ++++ src/engine/wavetable.h | 17 +++++++++++++++++ 6 files changed, 114 insertions(+) diff --git a/src/engine/instrument.h b/src/engine/instrument.h index f1885225b..e4613c8b9 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -388,8 +388,25 @@ struct DivInstrument { DivInstrumentC64 c64; DivInstrumentAmiga amiga; + /** + * save the instrument to a SafeWriter. + * @param w the SafeWriter in question. + */ void putInsData(SafeWriter* w); + + /** + * read instrument data in .fui format. + * @param reader the reader. + * @param version the format version. + * @return a DivDataErrors. + */ DivDataErrors readInsData(SafeReader& reader, short version); + + /** + * save this instrument to a file. + * @param path file path. + * @return whether it was successful. + */ bool save(const char* path); DivInstrument(): name(""), diff --git a/src/engine/macroInt.h b/src/engine/macroInt.h index b107a4732..b2b3c6837 100644 --- a/src/engine/macroInt.h +++ b/src/engine/macroInt.h @@ -136,10 +136,29 @@ class DivMacroInt { willDam(false), willDvb(false), willEgt(false), willKsl(false), willSus(false), willVib(false), willWs(false), willKsr(false) {} } op[4]; + + /** + * trigger macro release. + */ void release(); + + /** + * trigger next macro tick. + */ void next(); + + /** + * initialize the macro interpreter. + * @param which an instrument, or NULL. + */ void init(DivInstrument* which); + + /** + * notify this macro interpreter that an instrument has been deleted. + * @param which the instrument in question. + */ void notifyInsDeletion(DivInstrument* which); + DivMacroInt(): ins(NULL), volPos(0), diff --git a/src/engine/pattern.h b/src/engine/pattern.h index 749431b28..1af744b65 100644 --- a/src/engine/pattern.h +++ b/src/engine/pattern.h @@ -22,7 +22,19 @@ struct DivPattern { String name; short data[256][32]; + + /** + * copy this pattern to another. + * @param dest the destination pattern. + */ void copyOn(DivPattern* dest); + + /** + * don't use yet! + * @param len the pattern length + * @param fxRows number of effect ...columns + * @return a SafeReader. + */ SafeReader* compile(int len=256, int fxRows=1); DivPattern(); }; @@ -36,8 +48,20 @@ struct DivChannelData { // 2: instrument // 3: volume // 4-5+: effect/effect value + // do NOT access directly unless you know what you're doing! DivPattern* data[128]; + + /** + * get a pattern from this channel, or the empty pattern if not initialized. + * @param index the pattern ID. + * @param create whether to initialize a new pattern if not init'ed. always use true if you're going to modify it! + * @return a DivPattern. + */ DivPattern* getPattern(int index, bool create); + + /** + * destroy all patterns on this DivChannelData. + */ void wipePatterns(); DivChannelData(); }; diff --git a/src/engine/sample.h b/src/engine/sample.h index 8b6d16b19..3598d67a7 100644 --- a/src/engine/sample.h +++ b/src/engine/sample.h @@ -53,11 +53,44 @@ struct DivSample { unsigned int samples; + /** + * save this sample to a file. + * @param path a path. + * @return whether saving succeeded or not. + */ bool save(const char* path); + + /** + * @warning DO NOT USE - internal function + * initialize sample data. + * @param d sample type. + * @param count number of samples. + * @return whether it was successful. + */ bool initInternal(unsigned char d, int count); + + /** + * initialize sample data. make sure you have set `depth` before doing so. + * @param count number of samples. + * @return whether it was successful. + */ bool init(unsigned int count); + + /** + * initialize the rest of sample formats for this sample. + */ void render(); + + /** + * get the sample data for the current depth. + * @return the sample data, or NULL if not created. + */ void* getCurBuf(); + + /** + * get the sample data length for the current depth. + * @return the sample data length. + */ unsigned int getCurBufLen(); DivSample(): name(""), diff --git a/src/engine/song.h b/src/engine/song.h index 3e74fa1e6..5cedb1846 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -314,6 +314,10 @@ struct DivSong { DivWavetable nullWave; DivSample nullSample; + /** + * unloads the song, freeing all memory associated with it. + * use before destroying the object. + */ void unload(); DivSong(): diff --git a/src/engine/wavetable.h b/src/engine/wavetable.h index ff98bf3f2..616b048cf 100644 --- a/src/engine/wavetable.h +++ b/src/engine/wavetable.h @@ -26,8 +26,25 @@ struct DivWavetable { int len, min, max; int data[256]; + /** + * save the wavetable to a SafeWriter. + * @param w the SafeWriter in question. + */ void putWaveData(SafeWriter* w); + + /** + * read wavetable data in .fuw format. + * @param reader the reader. + * @param version the format version. + * @return a DivDataErrors. + */ DivDataErrors readWaveData(SafeReader& reader, short version); + + /** + * save this wavetable to a file. + * @param path file path. + * @return whether it was successful. + */ bool save(const char* path); DivWavetable(): len(32), From 02760ddcbce45b828a4fc559db83021ee0a3e06d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 15 Mar 2022 22:16:47 -0500 Subject: [PATCH 281/637] MOD import: fix brainless 3 duration 0:00 --- src/engine/fileOps.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 65ef0113d..dd1e8ad1a 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -1489,12 +1489,14 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { 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 13: // break to row (BCD) + writeFxCol(fxTyp,((fxVal>>4)*10)+(fxVal&15)); + break; case 15: // set speed // TODO: somehow handle VBlank tunes // TODO: klisje is still broken, perhaps because there wasn't tempo set back then? From 02e9edbad93bd02d233bb8601b6de5b5382da4b9 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 15 Mar 2022 23:30:15 -0500 Subject: [PATCH 282/637] unlock the power of DECIMAL HZ! --- src/engine/engine.cpp | 15 ++++++++------- src/engine/engine.h | 13 ++++++++----- src/engine/fileOps.cpp | 4 ++-- src/engine/playback.cpp | 4 ++-- src/engine/song.h | 6 +++--- src/gui/gui.cpp | 28 ++++++++++++++++------------ src/gui/gui.h | 2 ++ 7 files changed, 41 insertions(+), 31 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 0bbf4cb48..cd7eb3ea2 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -1095,18 +1095,18 @@ unsigned char DivEngine::getSpeed2() { return speed2; } -int DivEngine::getHz() { +float DivEngine::getHz() { if (song.customTempo) { return song.hz; } else if (song.pal) { - return 60; + return 60.0; } else { - return 50; + return 50.0; } - return 60; + return 60.0; } -int DivEngine::getCurHz() { +float DivEngine::getCurHz() { return divider; } @@ -2216,11 +2216,12 @@ void DivEngine::setSysFlags(int system, unsigned int flags, bool restart) { isBusy.unlock(); } -void DivEngine::setSongRate(int hz, bool pal) { +void DivEngine::setSongRate(float hz, bool pal) { isBusy.lock(); song.pal=!pal; song.hz=hz; - song.customTempo=(song.hz!=50 && song.hz!=60); + // what? + song.customTempo=true; divider=60; if (song.customTempo) { divider=song.hz; diff --git a/src/engine/engine.h b/src/engine/engine.h index b4d5f7b6b..9e31e5636 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -182,8 +182,11 @@ class DivEngine { bool halted; bool forceMono; bool cmdStreamEnabled; - int ticks, curRow, curOrder, remainingLoops, nextSpeed, divider; - int cycles, clockDrift, stepPlay; + int ticks, curRow, curOrder, remainingLoops, nextSpeed; + double divider; + int cycles; + double clockDrift; + int stepPlay; int changeOrd, changePos, totalSeconds, totalTicks, totalTicksR, totalCmds, lastCmds, cmdsPerSecond, globalPitch; unsigned char extValue; unsigned char speed1, speed2; @@ -431,10 +434,10 @@ class DivEngine { unsigned char getSpeed2(); // get Hz - int getHz(); + float getHz(); // get current Hz - int getCurHz(); + float getCurHz(); // get time int getTotalTicks(); // 1/1000000th of a second @@ -526,7 +529,7 @@ class DivEngine { void setSysFlags(int system, unsigned int flags, bool restart); // set Hz - void setSongRate(int hz, bool pal); + void setSongRate(float hz, bool pal); // set remaining loops. -1 means loop forever. void setLoops(int loops); diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index dd1e8ad1a..a47be934f 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -841,7 +841,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { ds.arpLen=reader.readC(); ds.hz=reader.readF(); ds.pal=(ds.hz>=53); - if (ds.hz!=50 && ds.hz!=60) ds.customTempo=true; + ds.customTempo=true; ds.patLen=reader.readS(); ds.ordersLen=reader.readS(); @@ -2064,7 +2064,7 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { w->writeC(song.customTempo); char customHz[4]; memset(customHz,0,4); - snprintf(customHz,4,"%d",song.hz); + snprintf(customHz,4,"%d",(int)song.hz); w->write(customHz,3); w->writeI(song.patLen); w->writeC(song.ordersLen); diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index b92339d79..65480a586 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -946,7 +946,7 @@ void DivEngine::processRow(int i, bool afterDelay) { globalPitch+=(signed char)(effectVal-0x80); break; case 0xf0: // set Hz by tempo - divider=(effectVal*2+2)/5; + divider=(double)effectVal*2.0/5.0; if (divider<10) divider=10; cycles=((int)(got.rate)<=divider) { clockDrift-=divider; cycles++; diff --git a/src/engine/song.h b/src/engine/song.h index 5cedb1846..6799909d4 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -271,8 +271,8 @@ struct DivSong { unsigned char timeBase, speed1, speed2, arpLen; bool pal; bool customTempo; - // TODO: change Hz to float? - int hz, patLen, ordersLen, insLen, waveLen, sampleLen; + float hz; + int patLen, ordersLen, insLen, waveLen, sampleLen; float masterVol; float tuning; @@ -345,7 +345,7 @@ struct DivSong { arpLen(1), pal(true), customTempo(false), - hz(60), + hz(60.0), patLen(64), ordersLen(1), insLen(0), diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 809c61fe7..352c7330a 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -666,6 +666,16 @@ void FurnaceGUI::prepareLayout() { fclose(check); } +float FurnaceGUI::calcBPM(int s1, int s2, float hz) { + float hl=e->song.hilightA; + if (hl<=0.0f) hl=4.0f; + float timeBase=e->song.timeBase+1; + float speedSum=s1+s2; + if (timeBase<1.0f) timeBase=1.0f; + if (speedSum<1.0f) speedSum=1.0f; + return 120.0f*hz/(timeBase*hl*speedSum); +} + void FurnaceGUI::drawEditControls() { if (nextWindow==GUI_WINDOW_EDIT_CONTROLS) { editControlsOpen=true; @@ -1020,13 +1030,7 @@ void FurnaceGUI::drawSongInfo() { e->song.timeBase=realTB-1; } ImGui::TableNextColumn(); - float hl=e->song.hilightA; - if (hl<=0.0f) hl=4.0f; - float timeBase=e->song.timeBase+1; - float speedSum=e->song.speed1+e->song.speed2; - if (timeBase<1.0f) timeBase=1.0f; - if (speedSum<1.0f) speedSum=1.0f; - ImGui::Text("%.2f BPM",120.0f*(float)e->song.hz/(timeBase*hl*speedSum)); + ImGui::Text("%.2f BPM",calcBPM(e->song.speed1,e->song.speed2,e->song.hz)); ImGui::TableNextRow(); ImGui::TableNextColumn(); @@ -1083,17 +1087,17 @@ void FurnaceGUI::drawSongInfo() { ImGui::Text("Tick Rate"); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(avail); - int setHz=e->song.hz; - if (ImGui::InputInt("##Rate",&setHz)) { + float setHz=e->song.hz; + if (ImGui::InputFloat("##Rate",&setHz,1.0f,1.0f,"%g")) { if (setHz<10) setHz=10; if (setHz>999) setHz=999; e->setSongRate(setHz,setHz<52); } - if (e->song.hz==50) { + if (e->song.hz>=49.98 && e->song.hz<=50.02) { ImGui::TableNextColumn(); ImGui::Text("PAL"); } - if (e->song.hz==60) { + if (e->song.hz>=59.9 && e->song.hz<=60.11) { ImGui::TableNextColumn(); ImGui::Text("NTSC"); } @@ -6078,7 +6082,7 @@ bool FurnaceGUI::loop() { if (e->isPlaying()) { int totalTicks=e->getTotalTicks(); int totalSeconds=e->getTotalSeconds(); - ImGui::Text("| Speed %d:%d @ %dHz | Order %d/%d | Row %d/%d | %d:%.2d:%.2d.%.2d",e->getSpeed1(),e->getSpeed2(),e->getCurHz(),e->getOrder(),e->song.ordersLen,e->getRow(),e->song.patLen,totalSeconds/3600,(totalSeconds/60)%60,totalSeconds%60,totalTicks/10000); + 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->getOrder(),e->song.ordersLen,e->getRow(),e->song.patLen,totalSeconds/3600,(totalSeconds/60)%60,totalSeconds%60,totalTicks/10000); } else { bool hasInfo=false; String info; diff --git a/src/gui/gui.h b/src/gui/gui.h index b0812b387..722554403 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -719,6 +719,8 @@ class FurnaceGUI { void updateWindowTitle(); void prepareLayout(); + float calcBPM(int s1, int s2, float hz); + void patternRow(int i, bool isPlaying, float lineHeight, int chans, int ord); void actualWaveList(); From 790c4345d9f9cbb0255c9491e43ded8139cb2220 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 15 Mar 2022 23:43:24 -0500 Subject: [PATCH 283/637] GUI: add ability to view Hz as base tempo --- src/gui/gui.cpp | 8 ++++++-- src/gui/gui.h | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 352c7330a..669db1452 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1084,11 +1084,14 @@ void FurnaceGUI::drawSongInfo() { ImGui::TableNextRow(); ImGui::TableNextColumn(); - ImGui::Text("Tick Rate"); + if (ImGui::Selectable(tempoView?"Base Tempo##TempoOrHz":"Tick Rate##TempoOrHz")) { + tempoView=!tempoView; + } ImGui::TableNextColumn(); ImGui::SetNextItemWidth(avail); - float setHz=e->song.hz; + float setHz=tempoView?e->song.hz*2.5:e->song.hz; if (ImGui::InputFloat("##Rate",&setHz,1.0f,1.0f,"%g")) { + if (tempoView) setHz/=2.5; if (setHz<10) setHz=10; if (setHz>999) setHz=999; e->setSongRate(setHz,setHz<52); @@ -7105,6 +7108,7 @@ FurnaceGUI::FurnaceGUI(): fancyPattern(false), wantPatName(false), firstFrame(true), + tempoView(false), curWindow(GUI_WINDOW_NOTHING), nextWindow(GUI_WINDOW_NOTHING), nextDesc(NULL), diff --git a/src/gui/gui.h b/src/gui/gui.h index 722554403..2c0a3a630 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -604,7 +604,7 @@ class FurnaceGUI { bool pianoOpen, notesOpen, channelsOpen, regViewOpen; SelectionPoint selStart, selEnd, cursor; bool selecting, curNibble, orderNibble, followOrders, followPattern, changeAllOrders; - bool collapseWindow, demandScrollX, fancyPattern, wantPatName, firstFrame; + bool collapseWindow, demandScrollX, fancyPattern, wantPatName, firstFrame, tempoView; FurnaceGUIWindows curWindow, nextWindow; float peak[2]; float patChanX[DIV_MAX_CHANS+1]; From 492533e5c0089fc5597d93ded232cff232fa8817 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 16 Mar 2022 01:27:34 -0500 Subject: [PATCH 284/637] GUI: add option to view Hz as base tempo --- src/gui/gui.cpp | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 669db1452..21872cbc0 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1096,13 +1096,18 @@ void FurnaceGUI::drawSongInfo() { if (setHz>999) setHz=999; e->setSongRate(setHz,setHz<52); } - if (e->song.hz>=49.98 && e->song.hz<=50.02) { + if (tempoView) { ImGui::TableNextColumn(); - ImGui::Text("PAL"); - } - if (e->song.hz>=59.9 && e->song.hz<=60.11) { - ImGui::TableNextColumn(); - ImGui::Text("NTSC"); + ImGui::Text("= %gHz",e->song.hz); + } else { + if (e->song.hz>=49.98 && e->song.hz<=50.02) { + ImGui::TableNextColumn(); + ImGui::Text("PAL"); + } + if (e->song.hz>=59.9 && e->song.hz<=60.11) { + ImGui::TableNextColumn(); + ImGui::Text("NTSC"); + } } ImGui::TableNextRow(); @@ -6861,6 +6866,8 @@ bool FurnaceGUI::init() { channelsOpen=e->getConfBool("channelsOpen",false); regViewOpen=e->getConfBool("regViewOpen",false); + tempoView=e->getConfBool("tempoView",true); + syncSettings(); if (settings.dpiScale>=0.5f) { @@ -7029,6 +7036,8 @@ bool FurnaceGUI::finish() { e->setConf("lastWindowWidth",scrW); e->setConf("lastWindowHeight",scrH); + e->setConf("tempoView",tempoView); + for (int i=0; i Date: Wed, 16 Mar 2022 01:53:46 -0500 Subject: [PATCH 285/637] OPL: implement effects --- src/engine/platform/opl.cpp | 111 ++++++++++++++++++++++++------------ src/engine/platform/opl.h | 8 ++- src/engine/playback.cpp | 55 ++++++++++++++++++ 3 files changed, 137 insertions(+), 37 deletions(-) diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index 1e77b1e6e..032e03916 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -138,7 +138,7 @@ const int orderedOpsL[4]={ const char* DivPlatformOPL::getEffectName(unsigned char effect) { switch (effect) { case 0x10: - return "10xy: Setup LFO (x: enable; y: speed)"; + return "10xx: Set global AM depth (0: 1dB, 1: 4.8dB)"; break; case 0x11: return "11xx: Set feedback (0 to 7)"; @@ -159,10 +159,12 @@ const char* DivPlatformOPL::getEffectName(unsigned char effect) { return "16xy: Set operator multiplier (x: operator from 1 to 4; y: multiplier)"; break; case 0x17: - return "17xx: Enable channel 6 DAC"; + return "17xx: Set global vibrato depth (0: normal, 1: double)"; break; case 0x18: - return "18xx: Toggle extended channel 3 mode"; + if (properDrumsSys) { + return "18xx: Toggle drums mode (1: enabled; 0: disabled)"; + } break; case 0x19: return "19xx: Set attack of all operators (0 to F)"; @@ -242,6 +244,7 @@ void DivPlatformOPL::tick() { } } } + */ if (chan[i].std.hadArp) { if (!chan[i].inPorta) { @@ -259,6 +262,7 @@ void DivPlatformOPL::tick() { } } + /* if (chan[i].std.hadAlg) { chan[i].state.alg=chan[i].std.alg; rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); @@ -358,7 +362,7 @@ void DivPlatformOPL::tick() { if (oplType==3) { unsigned char opMask=chan[0].fourOp|(chan[2].fourOp<<1)|(chan[4].fourOp<<2)|(chan[6].fourOp<<3)|(chan[8].fourOp<<4)|(chan[10].fourOp<<5); immWrite(0x104,opMask); - printf("updating opMask to %.2x\n",opMask); + //printf("updating opMask to %.2x\n",opMask); } } @@ -662,58 +666,85 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_FM_LFO: { - lfoValue=(c.value&7)|((c.value>>4)<<3); - rWrite(0x22,lfoValue); + if (c.value&2) { + dvb=c.value&1; + } else { + dam=c.value&1; + } + immWrite(0xbd,(dam<<7)|(dvb<<6)|(properDrums<<5)|drumState); break; } case DIV_CMD_FM_FB: { chan[c.chan].state.fb=c.value&7; - //rWrite(chanOffs[c.chan]+ADDR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3)); + int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2; + if (isMuted[c.chan]) { + rWrite(chanMap[c.chan]+ADDR_LR_FB_ALG,(chan[c.chan].state.alg&1)|(chan[c.chan].state.fb<<1)); + if (ops==4) { + rWrite(chanMap[c.chan+1]+ADDR_LR_FB_ALG,((chan[c.chan].state.alg>>1)&1)|(chan[c.chan].state.fb<<1)); + } + } else { + rWrite(chanMap[c.chan]+ADDR_LR_FB_ALG,(chan[c.chan].state.alg&1)|(chan[c.chan].state.fb<<1)|((chan[c.chan].pan&3)<<4)); + if (ops==4) { + rWrite(chanMap[c.chan+1]+ADDR_LR_FB_ALG,((chan[c.chan].state.alg>>1)&1)|(chan[c.chan].state.fb<<1)|((chan[c.chan].pan&3)<<4)); + } + } break; } case DIV_CMD_FM_MULT: { - /* - unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; - DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2; + if (c.value>=ops) break; + DivInstrumentFM::Operator& op=chan[c.chan].state.op[(ops==4)?orderedOpsL[c.value]:c.value]; op.mult=c.value2&15; - rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); - */ + unsigned char slot=slots[c.value][c.chan]; + if (slot==255) break; + unsigned short baseAddr=slotMap[slot]; + rWrite(baseAddr+ADDR_AM_VIB_SUS_KSR_MULT,(op.am<<7)|(op.vib<<6)|(op.sus<<5)|(op.ksr<<4)|op.mult); break; } case DIV_CMD_FM_TL: { - /* - unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; - DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; - op.tl=c.value2; + int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2; + if (c.value>=ops) break; + DivInstrumentFM::Operator& op=chan[c.chan].state.op[(ops==4)?orderedOpsL[c.value]:c.value]; + op.tl=c.value2&63; + unsigned char slot=slots[c.value][c.chan]; + if (slot==255) break; + unsigned short baseAddr=slotMap[slot]; if (isMuted[c.chan]) { - rWrite(baseAddr+ADDR_TL,127); + rWrite(baseAddr+ADDR_KSL_TL,63|(op.ksl<<6)); } else { - if (isOutput[chan[c.chan].state.alg][c.value]) { - rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); + if (isOutputL[ops==4][chan[c.chan].state.alg][c.value]) { + rWrite(baseAddr+ADDR_KSL_TL,(63-(((63-op.tl)*(chan[c.chan].outVol&0x3f))/63))|(op.ksl<<6)); } else { - rWrite(baseAddr+ADDR_TL,op.tl); + rWrite(baseAddr+ADDR_KSL_TL,op.tl|(op.ksl<<6)); } } - */ break; } case DIV_CMD_FM_AR: { - /* - if (c.value<0) { - for (int i=0; i<4; i++) { - DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; - op.ar=c.value2&31; - unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; - rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2; + if (c.value<0) { + for (int i=0; i=ops) break; + DivInstrumentFM::Operator& op=chan[c.chan].state.op[(ops==4)?orderedOpsL[c.value]:c.value]; + op.ar=c.value2&15; + unsigned char slot=slots[c.value][c.chan]; + if (slot==255) break; + unsigned short baseAddr=slotMap[slot]; + rWrite(baseAddr+ADDR_AR_DR,(op.ar<<4)|op.dr); + } + break; + } + case DIV_CMD_FM_EXTCH: { + properDrums=c.value; + immWrite(0xbd,(dam<<7)|(dvb<<6)|(properDrums<<5)|drumState); break; } case DIV_ALWAYS_SET_VOLUME: @@ -777,6 +808,7 @@ void DivPlatformOPL::forceIns() { } } } + immWrite(0xbd,(dam<<7)|(dvb<<6)|(properDrums<<5)|drumState); update4OpMask=true; } @@ -822,6 +854,13 @@ void DivPlatformOPL::reset() { lastBusy=60; lfoValue=8; properDrums=properDrumsSys; + drumState=0; + + drumVol[0]=0; + drumVol[1]=0; + drumVol[2]=0; + drumVol[3]=0; + drumVol[4]=0; if (oplType==1) { // disable waveforms immWrite(0x01,0x20); @@ -832,6 +871,8 @@ void DivPlatformOPL::reset() { } update4OpMask=true; + dam=false; + dvb=false; delay=0; } diff --git a/src/engine/platform/opl.h b/src/engine/platform/opl.h index 8f0994bb5..44d80fda2 100644 --- a/src/engine/platform/opl.h +++ b/src/engine/platform/opl.h @@ -53,7 +53,9 @@ class DivPlatformOPL: public DivDispatch { inPorta(false), fourOp(false), vol(0), - pan(3) {} + pan(3) { + state.ops=2; + } }; Channel chan[20]; bool isMuted[20]; @@ -72,10 +74,12 @@ class DivPlatformOPL: public DivDispatch { double chipFreqBase; int delay, oplType, chans, melodicChans, totalChans; unsigned char lastBusy; + unsigned char drumState; + unsigned char drumVol[5]; unsigned char regPool[512]; - bool properDrums, properDrumsSys; + bool properDrums, properDrumsSys, dam, dvb; unsigned char lfoValue; diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 65480a586..68ebbef58 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -237,6 +237,9 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe } break; case DIV_SYSTEM_OPLL_DRUMS: + case DIV_SYSTEM_OPL_DRUMS: + case DIV_SYSTEM_OPL2_DRUMS: + case DIV_SYSTEM_OPL3_DRUMS: switch (effect) { case 0x18: // drum mode toggle dispatchCmd(DivCommand(DIV_CMD_FM_EXTCH,ch,effectVal)); @@ -474,6 +477,58 @@ bool DivEngine::perSystemPostEffect(int ch, unsigned char effect, unsigned char return false; } break; + case DIV_SYSTEM_OPL: + case DIV_SYSTEM_OPL2: + case DIV_SYSTEM_OPL3: + case DIV_SYSTEM_OPL_DRUMS: + case DIV_SYSTEM_OPL2_DRUMS: + case DIV_SYSTEM_OPL3_DRUMS: + switch (effect) { + case 0x10: // DAM + dispatchCmd(DivCommand(DIV_CMD_FM_LFO,ch,effectVal&1)); + break; + case 0x11: // FB + dispatchCmd(DivCommand(DIV_CMD_FM_FB,ch,effectVal&7)); + break; + case 0x12: // TL op1 + dispatchCmd(DivCommand(DIV_CMD_FM_TL,ch,0,effectVal&0x3f)); + break; + case 0x13: // TL op2 + dispatchCmd(DivCommand(DIV_CMD_FM_TL,ch,1,effectVal&0x3f)); + break; + case 0x14: // TL op3 + dispatchCmd(DivCommand(DIV_CMD_FM_TL,ch,2,effectVal&0x3f)); + break; + case 0x15: // TL op4 + dispatchCmd(DivCommand(DIV_CMD_FM_TL,ch,3,effectVal&0x3f)); + break; + case 0x16: // MULT + if ((effectVal>>4)>0 && (effectVal>>4)<5) { + dispatchCmd(DivCommand(DIV_CMD_FM_MULT,ch,(effectVal>>4)-1,effectVal&15)); + } + break; + case 0x17: // DVB + dispatchCmd(DivCommand(DIV_CMD_FM_LFO,ch,2+(effectVal&1))); + break; + case 0x19: // AR global + dispatchCmd(DivCommand(DIV_CMD_FM_AR,ch,-1,effectVal&15)); + break; + case 0x1a: // AR op1 + dispatchCmd(DivCommand(DIV_CMD_FM_AR,ch,0,effectVal&15)); + break; + case 0x1b: // AR op2 + dispatchCmd(DivCommand(DIV_CMD_FM_AR,ch,1,effectVal&15)); + break; + case 0x1c: // AR op3 + dispatchCmd(DivCommand(DIV_CMD_FM_AR,ch,2,effectVal&15)); + break; + case 0x1d: // AR op4 + dispatchCmd(DivCommand(DIV_CMD_FM_AR,ch,3,effectVal&15)); + break; + default: + return false; + } + break; case DIV_SYSTEM_C64_6581: case DIV_SYSTEM_C64_8580: switch (effect) { case 0x10: // select waveform From 9322e9467c7ace387fd94518a908668b541987c1 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 16 Mar 2022 02:01:44 -0500 Subject: [PATCH 286/637] GUI: prepare macro editor for OPL op macros --- src/engine/platform/opl.cpp | 16 +--------------- src/gui/gui.h | 2 +- src/gui/insEdit.cpp | 25 ++++++++++++++++++++++--- 3 files changed, 24 insertions(+), 19 deletions(-) diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index 032e03916..084b22c33 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -227,24 +227,10 @@ void DivPlatformOPL::tick() { for (int i=0; istd.algMacro,ins->std.algMacroLen,ins->std.algMacroLoop,ins->std.algMacroRel,0,7,"alg",FM_NAME(FM_ALG),96,ins->std.algMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[0],0,7,NULL,false); NORMAL_MACRO(ins->std.fbMacro,ins->std.fbMacroLen,ins->std.fbMacroLoop,ins->std.fbMacroRel,0,7,"fb",FM_NAME(FM_FB),96,ins->std.fbMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[1],0,7,NULL,false); - NORMAL_MACRO(ins->std.fmsMacro,ins->std.fmsMacroLen,ins->std.fmsMacroLoop,ins->std.fmsMacroRel,0,7,"fms",FM_NAME(FM_FMS),96,ins->std.fmsMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[2],0,7,NULL,false); - NORMAL_MACRO(ins->std.amsMacro,ins->std.amsMacroLen,ins->std.amsMacroLoop,ins->std.amsMacroRel,0,3,"ams",FM_NAME(FM_AMS),48,ins->std.amsMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[3],0,3,NULL,false); + if (ins->type!=DIV_INS_OPL) { + NORMAL_MACRO(ins->std.fmsMacro,ins->std.fmsMacroLen,ins->std.fmsMacroLoop,ins->std.fmsMacroRel,0,7,"fms",FM_NAME(FM_FMS),96,ins->std.fmsMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[2],0,7,NULL,false); + NORMAL_MACRO(ins->std.amsMacro,ins->std.amsMacroLen,ins->std.amsMacroLoop,ins->std.amsMacroRel,0,3,"ams",FM_NAME(FM_AMS),48,ins->std.amsMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[3],0,3,NULL,false); + } } if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { @@ -1297,9 +1299,26 @@ void FurnaceGUI::drawInsEdit() { maxTl=63; } } + if (ins->type==DIV_INS_OPL) { + maxTl=63; + } int maxArDr=(ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ)?31:15; - if (ins->type==DIV_INS_OPLL) { + if (ins->type==DIV_INS_OPL) { + OP_MACRO(ins->std.opMacros[ordi].tlMacro,ins->std.opMacros[ordi].tlMacroLen,ins->std.opMacros[ordi].tlMacroLoop,ins->std.opMacros[ordi].tlMacroRel,maxTl,ordi,"tl",FM_NAME(FM_TL),128,ins->std.opMacros[ordi].tlMacroOpen,false,NULL,mmlString[0]); + OP_MACRO(ins->std.opMacros[ordi].arMacro,ins->std.opMacros[ordi].arMacroLen,ins->std.opMacros[ordi].arMacroLoop,ins->std.opMacros[ordi].arMacroRel,maxArDr,ordi,"ar",FM_NAME(FM_AR),64,ins->std.opMacros[ordi].arMacroOpen,false,NULL,mmlString[1]); + OP_MACRO(ins->std.opMacros[ordi].drMacro,ins->std.opMacros[ordi].drMacroLen,ins->std.opMacros[ordi].drMacroLoop,ins->std.opMacros[ordi].drMacroRel,maxArDr,ordi,"dr",FM_NAME(FM_DR),64,ins->std.opMacros[ordi].drMacroOpen,false,NULL,mmlString[2]); + OP_MACRO(ins->std.opMacros[ordi].slMacro,ins->std.opMacros[ordi].slMacroLen,ins->std.opMacros[ordi].slMacroLoop,ins->std.opMacros[ordi].slMacroRel,15,ordi,"sl",FM_NAME(FM_SL),64,ins->std.opMacros[ordi].slMacroOpen,false,NULL,mmlString[5]); + OP_MACRO(ins->std.opMacros[ordi].rrMacro,ins->std.opMacros[ordi].rrMacroLen,ins->std.opMacros[ordi].rrMacroLoop,ins->std.opMacros[ordi].rrMacroRel,15,ordi,"rr",FM_NAME(FM_RR),64,ins->std.opMacros[ordi].rrMacroOpen,false,NULL,mmlString[4]); + OP_MACRO(ins->std.opMacros[ordi].kslMacro,ins->std.opMacros[ordi].kslMacroLen,ins->std.opMacros[ordi].kslMacroLoop,ins->std.opMacros[ordi].kslMacroRel,3,ordi,"ksl",FM_NAME(FM_KSL),32,ins->std.opMacros[ordi].kslMacroOpen,false,NULL,mmlString[6]); + OP_MACRO(ins->std.opMacros[ordi].multMacro,ins->std.opMacros[ordi].multMacroLen,ins->std.opMacros[ordi].multMacroLoop,ins->std.opMacros[ordi].multMacroRel,15,ordi,"mult",FM_NAME(FM_MULT),64,ins->std.opMacros[ordi].multMacroOpen,false,NULL,mmlString[7]); + OP_MACRO(ins->std.opMacros[ordi].wsMacro,ins->std.opMacros[ordi].wsMacroLen,ins->std.opMacros[ordi].wsMacroLoop,ins->std.opMacros[ordi].wsMacroRel,7,ordi,"ws",FM_NAME(FM_WS),64,ins->std.opMacros[ordi].wsMacroOpen,false,NULL,mmlString[8]); + + OP_MACRO(ins->std.opMacros[ordi].amMacro,ins->std.opMacros[ordi].amMacroLen,ins->std.opMacros[ordi].amMacroLoop,ins->std.opMacros[ordi].amMacroRel,1,ordi,"am",FM_NAME(FM_AM),32,ins->std.opMacros[ordi].amMacroOpen,true,NULL,mmlString[9]); + OP_MACRO(ins->std.opMacros[ordi].vibMacro,ins->std.opMacros[ordi].vibMacroLen,ins->std.opMacros[ordi].vibMacroLoop,ins->std.opMacros[ordi].vibMacroRel,1,ordi,"vib",FM_NAME(FM_VIB),32,ins->std.opMacros[ordi].vibMacroOpen,true,NULL,mmlString[10]); + OP_MACRO(ins->std.opMacros[ordi].ksrMacro,ins->std.opMacros[ordi].ksrMacroLen,ins->std.opMacros[ordi].ksrMacroLoop,ins->std.opMacros[ordi].ksrMacroRel,1,ordi,"ksr",FM_NAME(FM_KSR),32,ins->std.opMacros[ordi].ksrMacroOpen,true,NULL,mmlString[11]); + OP_MACRO(ins->std.opMacros[ordi].susMacro,ins->std.opMacros[ordi].susMacroLen,ins->std.opMacros[ordi].susMacroLoop,ins->std.opMacros[ordi].susMacroRel,1,ordi,"sus",FM_NAME(FM_SUS),32,ins->std.opMacros[ordi].susMacroOpen,true,NULL,mmlString[12]); + } else if (ins->type==DIV_INS_OPLL) { OP_MACRO(ins->std.opMacros[ordi].tlMacro,ins->std.opMacros[ordi].tlMacroLen,ins->std.opMacros[ordi].tlMacroLoop,ins->std.opMacros[ordi].tlMacroRel,maxTl,ordi,"tl",FM_NAME(FM_TL),128,ins->std.opMacros[ordi].tlMacroOpen,false,NULL,mmlString[0]); OP_MACRO(ins->std.opMacros[ordi].arMacro,ins->std.opMacros[ordi].arMacroLen,ins->std.opMacros[ordi].arMacroLoop,ins->std.opMacros[ordi].arMacroRel,maxArDr,ordi,"ar",FM_NAME(FM_AR),64,ins->std.opMacros[ordi].arMacroOpen,false,NULL,mmlString[1]); OP_MACRO(ins->std.opMacros[ordi].drMacro,ins->std.opMacros[ordi].drMacroLen,ins->std.opMacros[ordi].drMacroLoop,ins->std.opMacros[ordi].drMacroRel,maxArDr,ordi,"dr",FM_NAME(FM_DR),64,ins->std.opMacros[ordi].drMacroOpen,false,NULL,mmlString[2]); From cbd5414eeee1797120514e36f2fe9f53869fb2da Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 16 Mar 2022 02:35:33 -0500 Subject: [PATCH 287/637] increase playback tempo precision thanks akumanatt --- src/engine/playback.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 68ebbef58..965f28538 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -923,9 +923,9 @@ void DivEngine::processRow(int i, bool afterDelay) { 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; + divider=(double)(((effect&0x3)<<8)|effectVal); if (divider<10) divider=10; - cycles=((int)(got.rate)<=divider) { clockDrift-=divider; cycles++; From 966d133bf4e10af6422d9ccb6809e7f43a2bcdad Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 16 Mar 2022 03:44:22 -0500 Subject: [PATCH 288/637] OPL: FM macros --- src/engine/platform/opl.cpp | 145 ++++++++++++++++++++---------------- src/gui/insEdit.cpp | 2 +- 2 files changed, 83 insertions(+), 64 deletions(-) diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index 084b22c33..3c46b1f2a 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -225,11 +225,27 @@ void DivPlatformOPL::acquire(short* bufL, short* bufR, size_t start, size_t len) void DivPlatformOPL::tick() { for (int i=0; isong.algMacroBehavior) for (int j=0; j<4; j++) { - unsigned short baseAddr=chanOffs[i]|opOffs[j]; - DivInstrumentFM::Operator& op=chan[i].state.op[j]; - if (isMuted[i]) { - rWrite(baseAddr+ADDR_TL,127); - } else { - if (isOutput[chan[i].state.alg][j]) { - rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); - } else { - rWrite(baseAddr+ADDR_TL,op.tl); - } - } - } + } if (chan[i].std.hadFb) { chan[i].state.fb=chan[i].std.fb; - rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); } - if (chan[i].std.hadFms) { - chan[i].state.fms=chan[i].std.fms; - rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + + if (chan[i].std.hadAlg || chan[i].std.hadFb) { + if (isMuted[i]) { + rWrite(chanMap[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&1)|(chan[i].state.fb<<1)); + if (ops==4) { + rWrite(chanMap[i+1]+ADDR_LR_FB_ALG,((chan[i].state.alg>>1)&1)|(chan[i].state.fb<<1)); + } + } else { + rWrite(chanMap[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&1)|(chan[i].state.fb<<1)|((chan[i].pan&3)<<4)); + if (ops==4) { + rWrite(chanMap[i+1]+ADDR_LR_FB_ALG,((chan[i].state.alg>>1)&1)|(chan[i].state.fb<<1)|((chan[i].pan&3)<<4)); + } + } } - if (chan[i].std.hadAms) { - chan[i].state.ams=chan[i].std.ams; - rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); - } - for (int j=0; j<4; j++) { - unsigned short baseAddr=chanOffs[i]|opOffs[j]; - DivInstrumentFM::Operator& op=chan[i].state.op[j]; - DivMacroInt::IntOp& m=chan[i].std.op[j]; + + for (int j=0; j1) { + if (m.hadWs) { + op.ws=m.ws; + rWrite(baseAddr+ADDR_WS,op.ws&((oplType==3)?7:3)); + } + } + if (m.hadTl) { - op.tl=127-m.tl; + op.tl=63-m.tl; + } + if (m.hadKsl) { + op.ksl=m.ksl; + } + if (m.hadTl || m.hadKsl) { if (isMuted[i]) { - rWrite(baseAddr+ADDR_TL,127); + rWrite(baseAddr+ADDR_KSL_TL,63|(op.ksl<<6)); } else { - if (isOutput[chan[i].state.alg][j]) { - rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + if (isOutputL[ops==4][chan[i].state.alg][j]) { + rWrite(baseAddr+ADDR_KSL_TL,(63-(((63-op.tl)*(chan[i].outVol&0x3f))/63))|(op.ksl<<6)); } else { - rWrite(baseAddr+ADDR_TL,op.tl); + rWrite(baseAddr+ADDR_KSL_TL,op.tl|(op.ksl<<6)); } } } - if (m.hadRs) { - op.rs=m.rs; - rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); - } - if (m.hadDt) { - op.dt=m.dt; - rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); - } - if (m.hadD2r) { - op.d2r=m.d2r; - rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); - } - if (m.hadSsg) { - op.ssgEnv=m.ssg; - rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); - } } - */ if (chan[i].keyOn || chan[i].keyOff) { immWrite(chanMap[i]+ADDR_FREQH,0x00|(chan[i].freqH&31)); diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index c63ee6a29..b8506a5ef 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1490,7 +1490,7 @@ void FurnaceGUI::drawInsEdit() { if ((ins->type==DIV_INS_PCE || ins->type==DIV_INS_AY8930)) { volMax=31; } - if (ins->type==DIV_INS_VERA) { + if (ins->type==DIV_INS_OPL || ins->type==DIV_INS_VERA) { volMax=63; } if (ins->type==DIV_INS_AMIGA) { From 966539bdf5a6964c2d9f924c1ad32aafe24a6fe2 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 16 Mar 2022 13:11:02 -0500 Subject: [PATCH 289/637] Genesis: fix sample crash possibly --- src/engine/platform/genesisshared.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/genesisshared.h b/src/engine/platform/genesisshared.h index 01d45f955..6410ddb27 100644 --- a/src/engine/platform/genesisshared.h +++ b/src/engine/platform/genesisshared.h @@ -44,6 +44,6 @@ static int orderedOps[4]={ #define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;} #define immWrite(a,v) if (!skipRegisterWrites) {writes.push_back(QueuedWrite(a,v)); if (dumpWrites) {addWrite(a,v);} } -#define urgentWrite(a,v) if (!skipRegisterWrites) {if (writes.front().addrOrVal) {writes.push_back(QueuedWrite(a,v));} else {writes.push_front(QueuedWrite(a,v));}; if (dumpWrites) {addWrite(a,v);} } +#define urgentWrite(a,v) if (!skipRegisterWrites) {if (writes.empty() || writes.front().addrOrVal) {writes.push_back(QueuedWrite(a,v));} else {writes.push_front(QueuedWrite(a,v));}; if (dumpWrites) {addWrite(a,v);} } #include "fmshared_OPN.h" From fb68d2e5cb774ff5364ccf8c10b4b2ab331267e8 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 16 Mar 2022 13:31:42 -0500 Subject: [PATCH 290/637] GUI: add option to toggle visualizer in settings --- src/gui/gui.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 21872cbc0..bfa2d523f 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -6043,6 +6043,9 @@ bool FurnaceGUI::loop() { ImGui::EndMenu(); } if (ImGui::BeginMenu("settings")) { + if (ImGui::MenuItem("visualizer",NULL,fancyPattern)) { + fancyPattern=!fancyPattern; + } if (ImGui::MenuItem("reset layout")) { showWarning("Are you sure you want to reset the workspace layout?",GUI_WARN_RESET_LAYOUT); } From 73cf7cf161751360bcd72d6ddc28649344c6a9b9 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 16 Mar 2022 13:45:22 -0500 Subject: [PATCH 291/637] GUI: enforce minimum size for macro edit left col UNTESTED --- src/gui/insEdit.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index b8506a5ef..e7fe917ac 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -853,6 +853,7 @@ if (ImGui::BeginTable("MacroSpace",2)) { \ ImGui::TableNextRow(); \ ImGui::TableNextColumn(); \ float lenAvail=ImGui::GetContentRegionAvail().x; \ + ImGui::Dummy(ImVec2(120.0f*dpiScale,dpiScale)); \ ImGui::TableNextColumn(); \ float availableWidth=ImGui::GetContentRegionAvail().x-reservedSpace; \ int totalFit=MIN(127,availableWidth/(16*dpiScale)); \ From 3163730fe8e1436938597249e9f2aada7c4ca152 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 16 Mar 2022 17:01:44 -0500 Subject: [PATCH 292/637] prepare for drum kits/sample map --- papers/format.md | 7 ++++++ src/engine/engine.h | 4 +-- src/engine/instrument.cpp | 16 ++++++++++++ src/engine/instrument.h | 9 ++++++- src/gui/insEdit.cpp | 53 +++++++++++++++++++++++++++++++++++++++ 5 files changed, 86 insertions(+), 3 deletions(-) diff --git a/papers/format.md b/papers/format.md index 503099b99..9afa4b47e 100644 --- a/papers/format.md +++ b/papers/format.md @@ -494,6 +494,13 @@ size | description 2 | kick frequency 2 | snare/hi-hat frequency 2 | tom/top frequency + --- | **Sample instrument extra data** (>=67) + 1 | use note map + | - only read the following two data structures if this is true! + 4?? | note frequency × 120 + | - 480 bytes + 2?? | note sample × 120 + | - 240 bytes ``` # wavetable diff --git a/src/engine/engine.h b/src/engine/engine.h index 9e31e5636..a3ac2c249 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -37,8 +37,8 @@ warnings+=(String("\n")+x); \ } -#define DIV_VERSION "dev66" -#define DIV_ENGINE_VERSION 66 +#define DIV_VERSION "dev67" +#define DIV_ENGINE_VERSION 67 // for imports #define DIV_VERSION_MOD 0xff01 diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index b22fdced2..785c9b19d 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -378,6 +378,13 @@ void DivInstrument::putInsData(SafeWriter* w) { w->writeS(fm.kickFreq); w->writeS(fm.snareHatFreq); w->writeS(fm.tomTopFreq); + + // sample map + w->writeC(amiga.useNoteMap); + if (amiga.useNoteMap) { + w->write(amiga.noteFreq,120*sizeof(unsigned int)); + w->write(amiga.noteMap,120*sizeof(short)); + } } DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) { @@ -717,6 +724,15 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) { std.dutyMacroRel=-1; } + // sample map + if (version>=67) { + amiga.useNoteMap=reader.readC(); + if (amiga.useNoteMap) { + reader.read(amiga.noteFreq,120*sizeof(unsigned int)); + reader.read(amiga.noteMap,120*sizeof(short)); + } + } + return DIV_DATA_SUCCESS; } diff --git a/src/engine/instrument.h b/src/engine/instrument.h index e4613c8b9..97d64da00 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -373,9 +373,16 @@ struct DivInstrumentC64 { struct DivInstrumentAmiga { short initSample; + bool useNoteMap; + int noteFreq[120]; + short noteMap[120]; DivInstrumentAmiga(): - initSample(0) {} + initSample(0), + useNoteMap(false) { + memset(noteMap,-1,120*sizeof(short)); + memset(noteFreq,0,120*sizeof(int)); + } }; struct DivInstrument { diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index e7fe917ac..6e0635a99 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1467,6 +1467,59 @@ void FurnaceGUI::drawInsEdit() { } ImGui::EndCombo(); } + P(ImGui::Checkbox("Use sample map (does not work yet!)",&ins->amiga.useNoteMap)); + if (ins->amiga.useNoteMap) { + if (ImGui::BeginTable("NoteMap",3,ImGuiTableFlags_ScrollY|ImGuiTableFlags_Borders|ImGuiTableFlags_SizingStretchSame)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch); + + ImGui::TableSetupScrollFreeze(0,1); + + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + ImGui::TableNextColumn(); + ImGui::TableNextColumn(); + ImGui::Text("Sample"); + ImGui::TableNextColumn(); + ImGui::Text("Frequency"); + for (int i=0; i<120; i++) { + ImGui::TableNextRow(); + ImGui::PushID(fmt::sprintf("NM_%d",i).c_str()); + ImGui::TableNextColumn(); + ImGui::Text("%s",noteNames[60+i]); + ImGui::TableNextColumn(); + if (ins->amiga.noteMap[i]<0 || ins->amiga.noteMap[i]>=e->song.sampleLen) { + sName="-- empty --"; + ins->amiga.noteMap[i]=-1; + } else { + sName=e->song.sample[ins->amiga.noteMap[i]]->name; + } + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::BeginCombo("##SM",sName.c_str())) { + String id; + if (ImGui::Selectable("-- empty --",ins->amiga.noteMap[i]==-1)) { PARAMETER + ins->amiga.noteMap[i]=-1; + } + for (int j=0; jsong.sampleLen; j++) { + id=fmt::sprintf("%d: %s",j,e->song.sample[j]->name); + if (ImGui::Selectable(id.c_str(),ins->amiga.noteMap[i]==j)) { PARAMETER + ins->amiga.noteMap[i]=j; + if (ins->amiga.noteFreq[i]<=0) ins->amiga.noteFreq[i]=(int)((double)e->song.sample[j]->centerRate*pow(2.0,((double)i-60.0)/12.0)); + } + } + ImGui::EndCombo(); + } + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputInt("##SF",&ins->amiga.noteFreq[i],50,500)) { PARAMETER + if (ins->amiga.noteFreq[i]<0) ins->amiga.noteFreq[i]=0; + if (ins->amiga.noteFreq[i]>262144) ins->amiga.noteFreq[i]=262144; + } + ImGui::PopID(); + } + ImGui::EndTable(); + } + } ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Macros")) { From a3efaf0bd39056c8274af00fcf7db1c2e04e0a09 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 16 Mar 2022 17:02:57 -0500 Subject: [PATCH 293/637] fix --- src/gui/insEdit.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 6e0635a99..b19904b0d 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1504,7 +1504,7 @@ void FurnaceGUI::drawInsEdit() { id=fmt::sprintf("%d: %s",j,e->song.sample[j]->name); if (ImGui::Selectable(id.c_str(),ins->amiga.noteMap[i]==j)) { PARAMETER ins->amiga.noteMap[i]=j; - if (ins->amiga.noteFreq[i]<=0) ins->amiga.noteFreq[i]=(int)((double)e->song.sample[j]->centerRate*pow(2.0,((double)i-60.0)/12.0)); + if (ins->amiga.noteFreq[i]<=0) ins->amiga.noteFreq[i]=(int)((double)e->song.sample[j]->centerRate*pow(2.0,((double)i-48.0)/12.0)); } } ImGui::EndCombo(); From d09aa778d9cc0f4044b28850625ed8e0d66f57ce Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 16 Mar 2022 18:40:11 -0500 Subject: [PATCH 294/637] prepare to rewrite sample editor - DO NOT USE! --- CMakeLists.txt | 1 + src/gui/gui.cpp | 97 ----------------------------- src/gui/guiConst.cpp | 20 ++++++ src/gui/guiConst.h | 3 +- src/gui/sampleEdit.cpp | 137 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 160 insertions(+), 98 deletions(-) create mode 100644 src/gui/sampleEdit.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index c45e87f5f..914a1fe0f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -358,6 +358,7 @@ src/gui/guiConst.cpp src/gui/insEdit.cpp src/gui/orders.cpp src/gui/pattern.cpp +src/gui/sampleEdit.cpp src/gui/settings.cpp src/gui/util.cpp src/gui/gui.cpp diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index bfa2d523f..d48a20c9d 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1424,103 +1424,6 @@ void FurnaceGUI::drawSampleList() { ImGui::End(); } -void FurnaceGUI::drawSampleEdit() { - if (nextWindow==GUI_WINDOW_SAMPLE_EDIT) { - sampleEditOpen=true; - ImGui::SetNextWindowFocus(); - nextWindow=GUI_WINDOW_NOTHING; - } - if (!sampleEditOpen) return; - if (ImGui::Begin("Sample Editor",&sampleEditOpen,settings.allowEditDocking?0:ImGuiWindowFlags_NoDocking)) { - if (curSample<0 || curSample>=(int)e->song.sample.size()) { - ImGui::Text("no sample selected"); - } else { - DivSample* sample=e->song.sample[curSample]; - ImGui::InputText("Name",&sample->name); - ImGui::Text("Length: %d",sample->samples); - ImGui::Text("Type: %d-bit",sample->depth); - if (ImGui::InputInt("Rate (Hz)",&sample->rate,10,200)) { - if (sample->rate<100) sample->rate=100; - if (sample->rate>96000) sample->rate=96000; - } - if (ImGui::InputInt("Pitch of C-4 (Hz)",&sample->centerRate,10,200)) { - if (sample->centerRate<100) sample->centerRate=100; - if (sample->centerRate>65535) sample->centerRate=65535; - } - ImGui::Text("effective rate: %dHz",e->getEffectiveSampleRate(sample->rate)); - bool doLoop=(sample->loopStart>=0); - if (ImGui::Checkbox("Loop",&doLoop)) { - if (doLoop) { - sample->loopStart=0; - } else { - sample->loopStart=-1; - } - } - if (doLoop) { - ImGui::SameLine(); - if (ImGui::InputInt("##LoopPosition",&sample->loopStart,1,10)) { - if (sample->loopStart<0 || sample->loopStart>=(int)sample->samples) { - sample->loopStart=0; - } - } - } - if (ImGui::Button("Apply")) { - e->renderSamplesP(); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_VOLUME_UP "##PreviewSample")) { - e->previewSample(curSample); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_VOLUME_OFF "##StopSample")) { - e->stopSamplePreview(); - } - ImGui::Separator(); - bool considerations=false; - ImGui::Text("notes:"); - if (sample->loopStart>=0) { - considerations=true; - ImGui::Text("- sample won't loop on Neo Geo ADPCM-A and X1-010"); - if (sample->loopStart&1) { - ImGui::Text("- sample loop start will be aligned to the nearest even sample on Amiga"); - } - if (sample->loopStart>0) { - ImGui::Text("- sample loop start will be ignored on Neo Geo ADPCM-B"); - } - } - if (sample->samples&1) { - considerations=true; - ImGui::Text("- sample length will be aligned to the nearest even sample on Amiga"); - } - if (sample->samples&511) { - considerations=true; - ImGui::Text("- sample length will be aligned and padded to 512 sample units on Neo Geo ADPCM."); - } - if (sample->samples&4095) { - considerations=true; - ImGui::Text("- sample length will be aligned and padded to 4096 sample units on X1-010."); - } - if (sample->samples>65535) { - considerations=true; - ImGui::Text("- maximum sample length on Sega PCM and QSound is 65536 samples"); - } - if (sample->samples>131071) { - considerations=true; - ImGui::Text("- maximum sample length on X1-010 is 131072 samples"); - } - if (sample->samples>2097151) { - considerations=true; - ImGui::Text("- maximum sample length on Neo Geo ADPCM is 2097152 samples"); - } - if (!considerations) { - ImGui::Text("- none"); - } - } - } - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_SAMPLE_EDIT; - ImGui::End(); -} - void FurnaceGUI::drawMixer() { if (nextWindow==GUI_WINDOW_MIXER) { mixerOpen=true; diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index 7b4cd52a2..043498091 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -93,3 +93,23 @@ const char* insTypes[DIV_INS_MAX]={ "VERA", "X1-010" }; + +const char* sampleDepths[17]={ + "1-bit PCM", + "1-bit DPCM", + NULL, + NULL, + "QSound ADPCM", + "ADPCM-A", + "ADPCM-B", + "X68000 ADPCM", + "8-bit PCM", + "BRR", + "VOX", + NULL, + NULL, + NULL, + NULL, + NULL, + "16-bit PCM" +}; \ No newline at end of file diff --git a/src/gui/guiConst.h b/src/gui/guiConst.h index 9f4159159..0bf621180 100644 --- a/src/gui/guiConst.h +++ b/src/gui/guiConst.h @@ -23,4 +23,5 @@ extern const int opOrder[4]; extern const char* noteNames[180]; extern const char* noteNamesG[180]; extern const char* pitchLabel[11]; -extern const char* insTypes[]; \ No newline at end of file +extern const char* insTypes[]; +extern const char* sampleDepths[17]; \ No newline at end of file diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp new file mode 100644 index 000000000..4edc1dfac --- /dev/null +++ b/src/gui/sampleEdit.cpp @@ -0,0 +1,137 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +#include "gui.h" +#include "IconsFontAwesome4.h" +#include "misc/cpp/imgui_stdlib.h" +#include "guiConst.h" +#include + +void FurnaceGUI::drawSampleEdit() { + if (nextWindow==GUI_WINDOW_SAMPLE_EDIT) { + sampleEditOpen=true; + ImGui::SetNextWindowFocus(); + nextWindow=GUI_WINDOW_NOTHING; + } + if (!sampleEditOpen) return; + if (ImGui::Begin("Sample Editor",&sampleEditOpen,settings.allowEditDocking?0:ImGuiWindowFlags_NoDocking)) { + if (curSample<0 || curSample>=(int)e->song.sample.size()) { + ImGui::Text("no sample selected"); + } else { + DivSample* sample=e->song.sample[curSample]; + String sampleType="Invalid"; + if (sample->depth<17) { + if (sampleDepths[sample->depth]!=NULL) { + sampleType=sampleDepths[sample->depth]; + } + } + ImGui::InputText("Name",&sample->name); + if (ImGui::BeginCombo("Type",sampleType.c_str())) { + for (int i=0; i<17; i++) { + if (sampleDepths[i]==NULL) continue; + if (ImGui::Selectable(sampleDepths[i])) { + sample->depth=i; + e->renderSamplesP(); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("no undo for sample type change operations!"); + } + } + ImGui::EndCombo(); + } + if (ImGui::InputInt("Rate (Hz)",&sample->rate,10,200)) { + if (sample->rate<100) sample->rate=100; + if (sample->rate>96000) sample->rate=96000; + } + if (ImGui::InputInt("Pitch of C-4 (Hz)",&sample->centerRate,10,200)) { + if (sample->centerRate<100) sample->centerRate=100; + if (sample->centerRate>65535) sample->centerRate=65535; + } + ImGui::Text("effective rate: %dHz",e->getEffectiveSampleRate(sample->rate)); + bool doLoop=(sample->loopStart>=0); + if (ImGui::Checkbox("Loop",&doLoop)) { + if (doLoop) { + sample->loopStart=0; + } else { + sample->loopStart=-1; + } + } + if (doLoop) { + ImGui::SameLine(); + if (ImGui::InputInt("##LoopPosition",&sample->loopStart,1,10)) { + if (sample->loopStart<0 || sample->loopStart>=(int)sample->samples) { + sample->loopStart=0; + } + } + } + if (ImGui::Button("Apply")) { + e->renderSamplesP(); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_VOLUME_UP "##PreviewSample")) { + e->previewSample(curSample); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_VOLUME_OFF "##StopSample")) { + e->stopSamplePreview(); + } + ImGui::Separator(); + bool considerations=false; + ImGui::Text("notes:"); + if (sample->loopStart>=0) { + considerations=true; + ImGui::Text("- sample won't loop on Neo Geo ADPCM-A and X1-010"); + if (sample->loopStart&1) { + ImGui::Text("- sample loop start will be aligned to the nearest even sample on Amiga"); + } + if (sample->loopStart>0) { + ImGui::Text("- sample loop start will be ignored on Neo Geo ADPCM-B"); + } + } + if (sample->samples&1) { + considerations=true; + ImGui::Text("- sample length will be aligned to the nearest even sample on Amiga"); + } + if (sample->samples&511) { + considerations=true; + ImGui::Text("- sample length will be aligned and padded to 512 sample units on Neo Geo ADPCM."); + } + if (sample->samples&4095) { + considerations=true; + ImGui::Text("- sample length will be aligned and padded to 4096 sample units on X1-010."); + } + if (sample->samples>65535) { + considerations=true; + ImGui::Text("- maximum sample length on Sega PCM and QSound is 65536 samples"); + } + if (sample->samples>131071) { + considerations=true; + ImGui::Text("- maximum sample length on X1-010 is 131072 samples"); + } + if (sample->samples>2097151) { + considerations=true; + ImGui::Text("- maximum sample length on Neo Geo ADPCM is 2097152 samples"); + } + if (!considerations) { + ImGui::Text("- none"); + } + } + } + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_SAMPLE_EDIT; + ImGui::End(); +} \ No newline at end of file From 234c5e9295f95697ec3bb012f998bff17a782bca Mon Sep 17 00:00:00 2001 From: cam900 Date: Thu, 17 Mar 2022 10:11:48 +0900 Subject: [PATCH 295/637] Reduce naming confusion in Bubble System Wavetable Sound It means Custom wavetable sound generator logic on Bubble System. It's wavetable select and volume is controlled by single AY-3-8910 IO for both channels, Another AY-3-8910 IO is used for reading sound status. Schematics: http://pdf.textfiles.com/manuals/ARCADE/K-R/Nemesis%20[Schematics]%20[English].pdf (Nemesis, derivative of Bubble system) --- CMakeLists.txt | 2 +- papers/doc/4-instrument/README.md | 2 +- papers/doc/4-instrument/scc.md | 4 +- papers/doc/7-systems/README.md | 2 +- papers/doc/7-systems/bubblesystem.md | 6 +- papers/format.md | 2 +- src/engine/dispatchContainer.cpp | 6 +- .../platform/{k005289.cpp => bubsyswsg.cpp} | 56 +++++++++---------- .../platform/{k005289.h => bubsyswsg.h} | 4 +- src/engine/playback.cpp | 2 +- src/engine/song.h | 2 +- src/engine/sysDef.cpp | 26 ++++----- src/gui/gui.cpp | 8 +-- src/gui/guiConst.cpp | 2 +- 14 files changed, 63 insertions(+), 61 deletions(-) rename src/engine/platform/{k005289.cpp => bubsyswsg.cpp} (84%) rename src/engine/platform/{k005289.h => bubsyswsg.h} (97%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 914a1fe0f..dd63fec2d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -318,7 +318,7 @@ src/engine/platform/x1_010.cpp src/engine/platform/lynx.cpp src/engine/platform/swan.cpp src/engine/platform/vera.cpp -src/engine/platform/k005289.cpp +src/engine/platform/bubsyswsg.cpp src/engine/platform/dummy.cpp ) diff --git a/papers/doc/4-instrument/README.md b/papers/doc/4-instrument/README.md index 577e18ebc..ab03e8364 100644 --- a/papers/doc/4-instrument/README.md +++ b/papers/doc/4-instrument/README.md @@ -26,7 +26,7 @@ depending on the instrument type, there are currently 13 different types of an i - [Atari Lynx](lynx.md) - for use with Atari Lynx handheld console. - [VERA](vera.md) - for use with Commander X16 VERA. - [Seta/Allumer X1-010](x1_010.md) - for use with Wavetable portion in Seta/Allumer X1-010. -- [Konami SCC/Bubble System](scc.md) - for use with Konami SCC and Wavetable portion in Bubble System's sound hardware. +- [Konami SCC/Bubble System WSG](scc.md) - for use with Konami SCC and Wavetable portion in Bubble System's sound hardware. # macros diff --git a/papers/doc/4-instrument/scc.md b/papers/doc/4-instrument/scc.md index 86e8c11bd..2650e0a30 100644 --- a/papers/doc/4-instrument/scc.md +++ b/papers/doc/4-instrument/scc.md @@ -1,6 +1,6 @@ -# Konami SCC/Bubble System instrument editor +# Konami SCC/Bubble System WSG instrument editor -SCC/Bubble System instrument editor consists of only three macros: +SCC/Bubble System WSG instrument editor consists of only three macros: - [Volume] - volume sequence - [Arpeggio] - pitch sequence diff --git a/papers/doc/7-systems/README.md b/papers/doc/7-systems/README.md index 405d0563c..5413b0b50 100644 --- a/papers/doc/7-systems/README.md +++ b/papers/doc/7-systems/README.md @@ -20,6 +20,6 @@ this is a list of systems that Furnace supports, including each system's effects - [Microchip AY8930](ay8930.md) - [Seta/Allumer X1-010](x1_010.md) - [WonderSwan](wonderswan.md) -- [Bubble System/K005289](bubblesystem.md) +- [Bubble System WSG](bubblesystem.md) Furnace also reads .dmf files with the [Yamaha YMU759](ymu759.md) system, but does not emulate the chip at all. diff --git a/papers/doc/7-systems/bubblesystem.md b/papers/doc/7-systems/bubblesystem.md index 1dc18cab1..835fd622f 100644 --- a/papers/doc/7-systems/bubblesystem.md +++ b/papers/doc/7-systems/bubblesystem.md @@ -1,10 +1,12 @@ -# Bubble System/K005289 +# Bubble System WSG a Konami's 2 channel wavetable sound generator logic used at their arcade hardware Bubble System. It's configured with K005289, 4 bit PROM and DAC. -Also known as K005289, but that's just part of the logic used for pitch and wavetable ROM address. Waveform select and Volume control are tied with AY-3-8910 port. +Also known as K005289, but that's just part of the logic used for pitch and wavetable ROM address. +Waveform select and Volume control are tied with single AY-3-8910 IO for both channels. +Another AY-3-8910 IO is used for reading sound hardware status. furnace emulates this configurations as single system, waveform format is 15 level and 32 width. diff --git a/papers/format.md b/papers/format.md index 9afa4b47e..c7b550853 100644 --- a/papers/format.md +++ b/papers/format.md @@ -177,7 +177,7 @@ size | description | - 0xaa: MSM6295 - 4 channels | - 0xab: MSM6258 - 1 channel | - 0xac: Commander X16 (VERA) - 17 channels - | - 0xad: Bubble System - 2 channels + | - 0xad: Bubble System WSG - 2 channels | - 0xb0: Seta/Allumer X1-010 - 16 channels | - 0xde: YM2610B extended - 19 channels | - 0xe0: QSound - 19 channels diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 62daaafe0..f80ed13bd 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -45,7 +45,7 @@ #include "platform/x1_010.h" #include "platform/swan.h" #include "platform/lynx.h" -#include "platform/k005289.h" +#include "platform/bubsyswsg.h" #include "platform/dummy.h" #include "../ta-log.h" #include "song.h" @@ -272,8 +272,8 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_VERA: dispatch=new DivPlatformVERA; break; - case DIV_SYSTEM_K005289: - dispatch=new DivPlatformK005289; + case DIV_SYSTEM_BUBSYS_WSG: + dispatch=new DivPlatformBubSysWSG; break; default: logW("this system is not supported yet! using dummy platform.\n"); diff --git a/src/engine/platform/k005289.cpp b/src/engine/platform/bubsyswsg.cpp similarity index 84% rename from src/engine/platform/k005289.cpp rename to src/engine/platform/bubsyswsg.cpp index b50a76af2..450f45305 100644 --- a/src/engine/platform/k005289.cpp +++ b/src/engine/platform/bubsyswsg.cpp @@ -17,7 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "k005289.h" +#include "bubsyswsg.h" #include "../engine.h" #include @@ -25,8 +25,8 @@ #define rWrite(a,v) {if(!skipRegisterWrites) {regPool[a]=v; if(dumpWrites) addWrite(a,v); }} -const char* regCheatSheetK005289[]={ - // K005289 +const char* regCheatSheetBubSysWSG[]={ + // K005289 timer "Freq_A", "0", "Freq_B", "1", // PROM, DAC control from External logic (Connected to AY PSG ports on Bubble System) @@ -35,11 +35,11 @@ const char* regCheatSheetK005289[]={ NULL }; -const char** DivPlatformK005289::getRegisterSheet() { - return regCheatSheetK005289; +const char** DivPlatformBubSysWSG::getRegisterSheet() { + return regCheatSheetBubSysWSG; } -const char* DivPlatformK005289::getEffectName(unsigned char effect) { +const char* DivPlatformBubSysWSG::getEffectName(unsigned char effect) { switch (effect) { case 0x10: return "10xx: Change waveform"; @@ -48,7 +48,7 @@ const char* DivPlatformK005289::getEffectName(unsigned char effect) { return NULL; } -void DivPlatformK005289::acquire(short* bufL, short* bufR, size_t start, size_t len) { +void DivPlatformBubSysWSG::acquire(short* bufL, short* bufR, size_t start, size_t len) { for (size_t h=start; hgetWave(chan[ch].wave); for (int i=0; i<32; i++) { if (wt->max>0 && wt->len>0) { @@ -84,7 +84,7 @@ void DivPlatformK005289::updateWave(int ch) { } } -void DivPlatformK005289::tick() { +void DivPlatformBubSysWSG::tick() { for (int i=0; i<2; i++) { chan[i].std.next(); if (chan[i].std.hadVol) { @@ -139,7 +139,7 @@ void DivPlatformK005289::tick() { } } -int DivPlatformK005289::dispatch(DivCommand c) { +int DivPlatformBubSysWSG::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(chan[c.chan].ins); @@ -238,12 +238,12 @@ int DivPlatformK005289::dispatch(DivCommand c) { return 1; } -void DivPlatformK005289::muteChannel(int ch, bool mute) { +void DivPlatformBubSysWSG::muteChannel(int ch, bool mute) { isMuted[ch]=mute; rWrite(2+ch,(chan[ch].wave<<5)|((chan[ch].active && isMuted[ch])?0:chan[ch].outVol)); } -void DivPlatformK005289::forceIns() { +void DivPlatformBubSysWSG::forceIns() { for (int i=0; i<2; i++) { chan[i].insChanged=true; chan[i].freqChanged=true; @@ -251,26 +251,26 @@ void DivPlatformK005289::forceIns() { } } -void* DivPlatformK005289::getChanState(int ch) { +void* DivPlatformBubSysWSG::getChanState(int ch) { return &chan[ch]; } -unsigned char* DivPlatformK005289::getRegisterPool() { +unsigned char* DivPlatformBubSysWSG::getRegisterPool() { return (unsigned char*)regPool; } -int DivPlatformK005289::getRegisterPoolSize() { +int DivPlatformBubSysWSG::getRegisterPoolSize() { return 4; } -int DivPlatformK005289::getRegisterPoolDepth() { +int DivPlatformBubSysWSG::getRegisterPoolDepth() { return 16; } -void DivPlatformK005289::reset() { +void DivPlatformBubSysWSG::reset() { memset(regPool,0,4*2); for (int i=0; i<2; i++) { - chan[i]=DivPlatformK005289::Channel(); + chan[i]=DivPlatformBubSysWSG::Channel(); } if (dumpWrites) { addWrite(0xffffffff,0); @@ -278,15 +278,15 @@ void DivPlatformK005289::reset() { k005289->reset(); } -bool DivPlatformK005289::isStereo() { +bool DivPlatformBubSysWSG::isStereo() { return false; } -bool DivPlatformK005289::keyOffAffectsArp(int ch) { +bool DivPlatformBubSysWSG::keyOffAffectsArp(int ch) { return true; } -void DivPlatformK005289::notifyWaveChange(int wave) { +void DivPlatformBubSysWSG::notifyWaveChange(int wave) { for (int i=0; i<2; i++) { if (chan[i].wave==wave) { updateWave(i); @@ -294,26 +294,26 @@ void DivPlatformK005289::notifyWaveChange(int wave) { } } -void DivPlatformK005289::notifyInsDeletion(void* ins) { +void DivPlatformBubSysWSG::notifyInsDeletion(void* ins) { for (int i=0; i<2; i++) { chan[i].std.notifyInsDeletion((DivInstrument*)ins); } } -void DivPlatformK005289::setFlags(unsigned int flags) { +void DivPlatformBubSysWSG::setFlags(unsigned int flags) { chipClock=COLOR_NTSC; rate=chipClock; } -void DivPlatformK005289::poke(unsigned int addr, unsigned short val) { +void DivPlatformBubSysWSG::poke(unsigned int addr, unsigned short val) { rWrite(addr,val); } -void DivPlatformK005289::poke(std::vector& wlist) { +void DivPlatformBubSysWSG::poke(std::vector& wlist) { for (DivRegWrite& i: wlist) rWrite(i.addr,i.val); } -int DivPlatformK005289::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { +int DivPlatformBubSysWSG::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { parent=p; dumpWrites=false; skipRegisterWrites=false; @@ -326,9 +326,9 @@ int DivPlatformK005289::init(DivEngine* p, int channels, int sugRate, unsigned i return 2; } -void DivPlatformK005289::quit() { +void DivPlatformBubSysWSG::quit() { delete k005289; } -DivPlatformK005289::~DivPlatformK005289() { +DivPlatformBubSysWSG::~DivPlatformBubSysWSG() { } diff --git a/src/engine/platform/k005289.h b/src/engine/platform/bubsyswsg.h similarity index 97% rename from src/engine/platform/k005289.h rename to src/engine/platform/bubsyswsg.h index d8818fe41..5fda0fd95 100644 --- a/src/engine/platform/k005289.h +++ b/src/engine/platform/bubsyswsg.h @@ -25,7 +25,7 @@ #include "../macroInt.h" #include "sound/k005289/k005289.hpp" -class DivPlatformK005289: public DivDispatch { +class DivPlatformBubSysWSG: public DivDispatch { struct Channel { int freq, baseFreq, pitch, note; unsigned char ins; @@ -78,7 +78,7 @@ class DivPlatformK005289: public DivDispatch { const char* getEffectName(unsigned char effect); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); - ~DivPlatformK005289(); + ~DivPlatformBubSysWSG(); }; #endif diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 965f28538..e2fad1604 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -313,7 +313,7 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe return false; } break; - case DIV_SYSTEM_K005289: + case DIV_SYSTEM_BUBSYS_WSG: switch (effect) { case 0x10: // select waveform dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal)); diff --git a/src/engine/song.h b/src/engine/song.h index 6799909d4..c271033e6 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -94,7 +94,7 @@ enum DivSystem { DIV_SYSTEM_YM2610B_EXT, DIV_SYSTEM_SEGAPCM_COMPAT, DIV_SYSTEM_X1_010, - DIV_SYSTEM_K005289 + DIV_SYSTEM_BUBSYS_WSG }; struct DivSong { diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index d4aaa353f..95e1a7b05 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -138,7 +138,7 @@ DivSystem DivEngine::systemFromFile(unsigned char val) { case 0xac: return DIV_SYSTEM_VERA; case 0xad: - return DIV_SYSTEM_K005289; + return DIV_SYSTEM_BUBSYS_WSG; case 0xb0: return DIV_SYSTEM_X1_010; case 0xde: @@ -266,7 +266,7 @@ unsigned char DivEngine::systemToFile(DivSystem val) { return 0xa9; case DIV_SYSTEM_VERA: return 0xac; - case DIV_SYSTEM_K005289: + case DIV_SYSTEM_BUBSYS_WSG: return 0xad; case DIV_SYSTEM_X1_010: return 0xb0; @@ -398,7 +398,7 @@ int DivEngine::getChannelCount(DivSystem sys) { return 19; case DIV_SYSTEM_VERA: return 17; - case DIV_SYSTEM_K005289: + case DIV_SYSTEM_BUBSYS_WSG: return 2; } return 0; @@ -548,7 +548,7 @@ const char* DivEngine::getSongSystemName() { } break; case 3: - if (song.system[0]==DIV_SYSTEM_AY8910 && song.system[1]==DIV_SYSTEM_AY8910 && song.system[2]==DIV_SYSTEM_K005289) { + if (song.system[0]==DIV_SYSTEM_AY8910 && song.system[1]==DIV_SYSTEM_AY8910 && song.system[2]==DIV_SYSTEM_BUBSYS_WSG) { return "Konami Bubble System"; } break; @@ -681,8 +681,8 @@ const char* DivEngine::getSystemName(DivSystem sys) { return "VERA"; case DIV_SYSTEM_X1_010: return "Seta/Allumer X1-010"; - case DIV_SYSTEM_K005289: - return "Konami Bubble System Sound"; + case DIV_SYSTEM_BUBSYS_WSG: + return "Konami Bubble System WSG"; } return "Unknown"; } @@ -812,8 +812,8 @@ const char* DivEngine::getSystemChips(DivSystem sys) { return "VERA"; case DIV_SYSTEM_X1_010: return "Seta/Allumer X1-010"; - case DIV_SYSTEM_K005289: - return "Konami K005289"; + case DIV_SYSTEM_BUBSYS_WSG: + return "Konami Bubble System WSG"; } return "Unknown"; } @@ -1071,7 +1071,7 @@ const DivInstrumentType chanPrefType[47][28]={ {DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_FM, DIV_INS_AY, DIV_INS_AY, DIV_INS_AY, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA, DIV_INS_AMIGA}, // YM2610B (extended channel 3) {DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_VERA, DIV_INS_AMIGA}, // VERA {DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010, DIV_INS_X1_010}, // X1-010 - {DIV_INS_SCC, DIV_INS_SCC}, // K005289 + {DIV_INS_SCC, DIV_INS_SCC}, // Bubble System WSG }; const char* DivEngine::getChannelName(int chan) { @@ -1100,7 +1100,7 @@ const char* DivEngine::getChannelName(int chan) { break; case DIV_SYSTEM_PCE: case DIV_SYSTEM_SFX_BEEPER: - case DIV_SYSTEM_K005289: + case DIV_SYSTEM_BUBSYS_WSG: return chanNames[5][dispatchChanOfChan[chan]]; break; case DIV_SYSTEM_NES: @@ -1246,7 +1246,7 @@ const char* DivEngine::getChannelShortName(int chan) { break; case DIV_SYSTEM_PCE: case DIV_SYSTEM_SFX_BEEPER: - case DIV_SYSTEM_K005289: + case DIV_SYSTEM_BUBSYS_WSG: return chanShortNames[5][dispatchChanOfChan[chan]]; break; case DIV_SYSTEM_NES: @@ -1388,7 +1388,7 @@ int DivEngine::getChannelType(int chan) { break; case DIV_SYSTEM_PCE: case DIV_SYSTEM_SFX_BEEPER: - case DIV_SYSTEM_K005289: + case DIV_SYSTEM_BUBSYS_WSG: return chanTypes[5][dispatchChanOfChan[chan]]; break; case DIV_SYSTEM_NES: @@ -1662,7 +1662,7 @@ DivInstrumentType DivEngine::getPreferInsType(int chan) { case DIV_SYSTEM_X1_010: return chanPrefType[45][dispatchChanOfChan[chan]]; break; - case DIV_SYSTEM_K005289: + case DIV_SYSTEM_BUBSYS_WSG: return chanPrefType[46][dispatchChanOfChan[chan]]; break; } diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index d48a20c9d..694f57b99 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -5512,7 +5512,7 @@ bool FurnaceGUI::loop() { sysAddOption(DIV_SYSTEM_X1_010); sysAddOption(DIV_SYSTEM_SWAN); sysAddOption(DIV_SYSTEM_VERA); - sysAddOption(DIV_SYSTEM_K005289); + sysAddOption(DIV_SYSTEM_BUBSYS_WSG); ImGui::EndMenu(); } if (ImGui::BeginMenu("configure system...")) { @@ -5848,7 +5848,7 @@ bool FurnaceGUI::loop() { case DIV_SYSTEM_GB: case DIV_SYSTEM_SWAN: case DIV_SYSTEM_VERA: - case DIV_SYSTEM_K005289: + case DIV_SYSTEM_BUBSYS_WSG: case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_EXT: case DIV_SYSTEM_YM2610_FULL: @@ -5910,7 +5910,7 @@ bool FurnaceGUI::loop() { sysChangeOption(i,DIV_SYSTEM_X1_010); sysChangeOption(i,DIV_SYSTEM_SWAN); sysChangeOption(i,DIV_SYSTEM_VERA); - sysChangeOption(i,DIV_SYSTEM_K005289); + sysChangeOption(i,DIV_SYSTEM_BUBSYS_WSG); ImGui::EndMenu(); } } @@ -7678,7 +7678,7 @@ FurnaceGUI::FurnaceGUI(): "Konami Bubble System", { DIV_SYSTEM_AY8910, 64, 0, 0, DIV_SYSTEM_AY8910, 64, 0, 0, - DIV_SYSTEM_K005289, 64, 0, 0, + DIV_SYSTEM_BUBSYS_WSG, 64, 0, 0, // VLM5030 exists but not used for music at all 0 } diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index 043498091..ef7610496 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -84,7 +84,7 @@ const char* insTypes[DIV_INS_MAX]={ "FDS", "Virtual Boy", "Namco 163", - "Konami SCC/Bubble System", + "Konami SCC/Bubble System WSG", "FM (OPZ)", "POKEY", "PC Beeper", From e7f6290012f34678c1d1134a003a076980b50fbf Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 17 Mar 2022 01:40:03 -0500 Subject: [PATCH 296/637] OPL: possibly implement drums mode we need a good UI for it --- src/engine/platform/opl.cpp | 72 +++++++++++++++++++++++++++++-------- 1 file changed, 57 insertions(+), 15 deletions(-) diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index 3c46b1f2a..06bba5b59 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -48,6 +48,10 @@ const unsigned short chanMapOPL2[20]={ 0, 1, 2, 3, 4, 5, 6, 7, 8, N, N, N, N, N, N, N, N, N, N, N }; +const unsigned short chanMapOPL2Drums[20]={ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 8, 7, N, N, N, N, N, N, N, N, N +}; + const unsigned char* slotsOPL2[4]={ slotsOPL2i[0], slotsOPL2i[1], @@ -80,6 +84,10 @@ const unsigned short chanMapOPL3[20]={ 0, 3, 1, 4, 2, 5, 0x100, 0x103, 0x101, 0x104, 0x102, 0x105, 0x106, 0x107, 0x108, 6, 7, 8, N, N }; +const unsigned short chanMapOPL3Drums[20]={ + 0, 3, 1, 4, 2, 5, 0x100, 0x103, 0x101, 0x104, 0x102, 0x105, 0x106, 0x107, 0x108, 6, 7, 8, 8, 7 +}; + const unsigned char* slotsOPL3[4]={ slotsOPL3i[0], slotsOPL3i[1], @@ -224,7 +232,7 @@ void DivPlatformOPL::acquire(short* bufL, short* bufR, size_t start, size_t len) } void DivPlatformOPL::tick() { - for (int i=0; icalcFreq(chan[i].baseFreq,chan[i].pitch,false,octave(chan[i].baseFreq)); if (chan[i].freq>131071) chan[i].freq=131071; @@ -387,14 +413,29 @@ void DivPlatformOPL::tick() { chan[i].freqL=freqt&0xff; immWrite(chanMap[i]+ADDR_FREQ,chan[i].freqL); } - if (chan[i].keyOn) { - immWrite(chanMap[i]+ADDR_FREQH,chan[i].freqH|(0x20)); - chan[i].keyOn=false; - } else if (chan[i].freqChanged) { - immWrite(chanMap[i]+ADDR_FREQH,chan[i].freqH|(chan[i].active<<5)); + if (i=melodicChans) return 0; + if (c.chan>=totalChans) return 0; // ineffective in 4-op mode if (oplType==3 && c.chan<14 && (c.chan&1) && c.cmd!=DIV_CMD_GET_VOLMAX && c.cmd!=DIV_ALWAYS_SET_VOLUME) { if (chan[c.chan-1].fourOp) return 0; @@ -879,6 +919,8 @@ void DivPlatformOPL::reset() { dam=false; dvb=false; delay=0; + + immWrite(0xbd,(dam<<7)|(dvb<<6)|(properDrums<<5)|drumState); } bool DivPlatformOPL::isStereo() { @@ -927,7 +969,7 @@ void DivPlatformOPL::setOPLType(int type, bool drums) { slotsNonDrums=slotsOPL2; slotsDrums=slotsOPL2Drums; slots=drums?slotsDrums:slotsNonDrums; - chanMap=chanMapOPL2; + chanMap=drums?chanMapOPL2Drums:chanMapOPL2; chipFreqBase=9440540*0.25; chans=9; melodicChans=drums?6:9; @@ -937,7 +979,7 @@ void DivPlatformOPL::setOPLType(int type, bool drums) { slotsNonDrums=slotsOPL3; slotsDrums=slotsOPL3Drums; slots=drums?slotsDrums:slotsNonDrums; - chanMap=chanMapOPL3; + chanMap=drums?chanMapOPL3Drums:chanMapOPL3; chipFreqBase=9440540; chans=18; melodicChans=drums?15:18; From 5579ef2bc1f6c1ee2eb22082a0137bfc717d4da0 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 17 Mar 2022 01:50:42 -0500 Subject: [PATCH 297/637] OPL: even more drums mode work --- src/engine/platform/opl.cpp | 38 ++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index 06bba5b59..2549deed6 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -788,8 +788,20 @@ int DivPlatformOPL::dispatch(DivCommand c) { break; } case DIV_CMD_FM_EXTCH: { + if (!properDrumsSys) break; properDrums=c.value; immWrite(0xbd,(dam<<7)|(dvb<<6)|(properDrums<<5)|drumState); + slots=properDrums?slotsDrums:slotsNonDrums; + if (oplType==3) { + chanMap=properDrums?chanMapOPL3Drums:chanMapOPL3; + melodicChans=properDrums?15:18; + totalChans=properDrums?20:18; + } else { + chanMap=properDrums?chanMapOPL2Drums:chanMapOPL2; + melodicChans=properDrums?6:9; + totalChans=properDrums?11:9; + } + printf("CHANGING. DRUMS. MODE.\n"); break; } case DIV_ALWAYS_SET_VOLUME: @@ -812,7 +824,16 @@ int DivPlatformOPL::dispatch(DivCommand c) { } void DivPlatformOPL::forceIns() { - for (int i=0; i Date: Thu, 17 Mar 2022 02:07:46 -0500 Subject: [PATCH 298/637] GUI: work on the drum UI --- src/engine/platform/opl.cpp | 3 +- src/gui/insEdit.cpp | 104 +++++++++++++++++------------------- 2 files changed, 50 insertions(+), 57 deletions(-) diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index 2549deed6..53163114f 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -75,7 +75,7 @@ const unsigned char slotsOPL3i[4][20]={ const unsigned char slotsOPL3Drumsi[4][20]={ {0, 6, 1, 7, 2, 8, 18, 24, 19, 25, 20, 26, 30, 31, 32, 12, 16, 14, 17, 13}, // OP1 - {3, 9, 4, 10, 5, 11, 21, 27, 22, 28, 23, 29, 33, 34, 35, N, N, N, N, N}, // OP2 + {3, 9, 4, 10, 5, 11, 21, 27, 22, 28, 23, 29, 33, 34, 35, 15, N, N, N, N}, // OP2 {6, N, 7, N, 8, N, 24, N, 25, N, 26, N, N, N, N, N, N, N, N, N}, // OP3 {9, N, 10, N, 11, N, 27, N, 28, N, 29, N, N, N, N, N, N, N, N, N} // OP4 }; @@ -801,7 +801,6 @@ int DivPlatformOPL::dispatch(DivCommand c) { melodicChans=properDrums?6:9; totalChans=properDrums?11:9; } - printf("CHANGING. DRUMS. MODE.\n"); break; } case DIV_ALWAYS_SET_VOLUME: diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index b19904b0d..def41aad3 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -878,6 +878,39 @@ if (ImGui::BeginTable("MacroSpace",2)) { \ ImGui::EndTable(); \ } +#define DRUM_FREQ(name,db,df,prop) \ + ImGui::TableNextRow(); \ + ImGui::TableNextColumn(); \ + if (ins->type==DIV_INS_OPLL) { \ + block=(prop>>9)&7; \ + fNum=prop&511; \ + } else { \ + block=(prop>>10)&7; \ + fNum=prop&1023; \ + } \ + ImGui::Text(name); \ + ImGui::TableNextColumn(); \ + if (ImGui::InputInt(db,&block,1,1)) { \ + if (block<0) block=0; \ + if (block>7) block=7; \ + if (ins->type==DIV_INS_OPLL) { \ + prop=(block<<9)|fNum; \ + } else { \ + prop=(block<<10)|fNum; \ + } \ + } \ + ImGui::TableNextColumn(); \ + if (ImGui::InputInt(df,&fNum,1,1)) { \ + if (fNum<0) fNum=0; \ + if (ins->type==DIV_INS_OPLL) { \ + if (fNum>511) fNum=511; \ + prop=(block<<9)|fNum; \ + } else { \ + if (fNum>1023) fNum=1023; \ + prop=(block<<10)|fNum; \ + } \ + } + void FurnaceGUI::drawInsEdit() { if (nextWindow==GUI_WINDOW_INS_EDIT) { insEditOpen=true; @@ -933,9 +966,11 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); ins->fm.alg&=algMax; P(ImGui::SliderScalar(FM_NAME(FM_FB),ImGuiDataType_U8,&ins->fm.fb,&_ZERO,&_SEVEN)); rightClickable + ImGui::BeginDisabled(ins->fm.opllPreset==16); if (ImGui::Checkbox("4-op",&fourOp)) { PARAMETER ins->fm.ops=fourOp?4:2; } + ImGui::EndDisabled(); ImGui::TableNextColumn(); P(ImGui::SliderScalar(FM_NAME(FM_ALG),ImGuiDataType_U8,&ins->fm.alg,&_ZERO,&algMax)); rightClickable if (ImGui::Checkbox("Drums",&drums)) { PARAMETER @@ -984,7 +1019,8 @@ void FurnaceGUI::drawInsEdit() { ImGui::EndTable(); } - if (ins->type==DIV_INS_OPLL && ins->fm.opllPreset==16) { + if ((ins->type==DIV_INS_OPLL || ins->type==DIV_INS_OPL) && ins->fm.opllPreset==16) { + ins->fm.ops=2; P(ImGui::Checkbox("Fixed frequency mode",&ins->fm.fixedDrums)); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("when enabled, drums will be set to the specified frequencies, ignoring the note."); @@ -1001,59 +1037,9 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); ImGui::Text("FreqNum"); - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - block=(ins->fm.kickFreq>>9)&7; - fNum=ins->fm.kickFreq&511; - ImGui::Text("Kick"); - ImGui::TableNextColumn(); - if (ImGui::InputInt("##DBlock0",&block,1,1)) { - if (block<0) block=0; - if (block>7) block=7; - ins->fm.kickFreq=(block<<9)|fNum; - } - ImGui::TableNextColumn(); - if (ImGui::InputInt("##DFreq0",&fNum,1,1)) { - if (fNum<0) fNum=0; - if (fNum>511) fNum=511; - ins->fm.kickFreq=(block<<9)|fNum; - } - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - block=(ins->fm.snareHatFreq>>9)&7; - fNum=ins->fm.snareHatFreq&511; - ImGui::Text("Snare/Hi-hat"); - ImGui::TableNextColumn(); - if (ImGui::InputInt("##DBlock1",&block,1,1)) { - if (block<0) block=0; - if (block>7) block=7; - ins->fm.snareHatFreq=(block<<9)|fNum; - } - ImGui::TableNextColumn(); - if (ImGui::InputInt("##DFreq1",&fNum,1,1)) { - if (fNum<0) fNum=0; - if (fNum>511) fNum=511; - ins->fm.snareHatFreq=(block<<9)|fNum; - } - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - block=(ins->fm.tomTopFreq>>9)&7; - fNum=ins->fm.tomTopFreq&511; - ImGui::Text("Tom/Top"); - ImGui::TableNextColumn(); - if (ImGui::InputInt("##DBlock2",&block,1,1)) { - if (block<0) block=0; - if (block>7) block=7; - ins->fm.tomTopFreq=(block<<9)|fNum; - } - ImGui::TableNextColumn(); - if (ImGui::InputInt("##DFreq2",&fNum,1,1)) { - if (fNum<0) fNum=0; - if (fNum>511) fNum=511; - ins->fm.tomTopFreq=(block<<9)|fNum; - } + DRUM_FREQ("Kick","##DBlock0","##DFreq0",ins->fm.kickFreq); + DRUM_FREQ("Snare/Hi-hat","##DBlock1","##DFreq1",ins->fm.snareHatFreq); + DRUM_FREQ("Tom/Top","##DBlock2","##DFreq2",ins->fm.tomTopFreq); ImGui::EndTable(); } } @@ -1073,7 +1059,15 @@ void FurnaceGUI::drawInsEdit() { ImGui::Separator(); ImGui::PushID(fmt::sprintf("op%d",i).c_str()); ImGui::Dummy(ImVec2(dpiScale,dpiScale)); - ImGui::Text("OP%d",i+1); + if (ins->type==DIV_INS_OPL && ins->fm.opllPreset==16) { + if (i==1) { + ImGui::Text("Envelope 2 (kick only)"); + } else { + ImGui::Text("Envelope"); + } + } else { + ImGui::Text("OP%d",i+1); + } ImGui::SameLine(); From e6bc0c15e0abe06b5c119316ab7692f0db85f3ce Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 17 Mar 2022 02:13:45 -0500 Subject: [PATCH 299/637] default to 2 ops to make OPL happy next up: work on a default patch that is both OPL and other OP friendly --- src/engine/instrument.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/instrument.h b/src/engine/instrument.h index 97d64da00..ea52e6d8a 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -105,7 +105,7 @@ struct DivInstrumentFM { fb(0), fms(0), ams(0), - ops(4), + ops(2), opllPreset(0), fixedDrums(false), kickFreq(0x520), From 5d6bbce916a5378f45d37c9b69862b672a38cd71 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 17 Mar 2022 02:33:41 -0500 Subject: [PATCH 300/637] change default patch this makes the patch better on OPL --- src/engine/instrument.h | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/engine/instrument.h b/src/engine/instrument.h index ea52e6d8a..7141899ff 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -115,11 +115,12 @@ struct DivInstrumentFM { fb=4; op[0].tl=42; op[0].ar=31; - op[0].dr=8; + op[0].dr=2; op[0].sl=15; op[0].rr=3; op[0].mult=5; op[0].dt=5; + op[0].rs=3; op[2].tl=18; op[2].ar=31; @@ -129,13 +130,14 @@ struct DivInstrumentFM { op[2].mult=1; op[2].dt=0; - op[1].tl=48; + op[1].tl=64; op[1].ar=31; - op[1].dr=4; + op[1].dr=2; op[1].sl=11; - op[1].rr=1; + op[1].rr=8; op[1].mult=1; op[1].dt=5; + op[1].rs=2; op[3].tl=2; op[3].ar=31; From d6dfe2636a19b409c2f33cc2637b7f9be65a52ac Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 17 Mar 2022 02:40:10 -0500 Subject: [PATCH 301/637] OPL: implement fixed frequency mode --- src/engine/platform/opl.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index 53163114f..42ce11587 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -582,7 +582,19 @@ int DivPlatformOPL::dispatch(DivCommand c) { chan[c.chan].insChanged=false; if (c.value!=DIV_NOTE_NULL) { - chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); + if (c.chan>=melodicChans && chan[c.chan].state.opllPreset==16 && chan[c.chan].state.fixedDrums) { // drums + if (c.chan==melodicChans) { + chan[c.chan].baseFreq=(chan[c.chan].state.kickFreq&1023)<<(chan[c.chan].state.kickFreq>>10); + } 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); + } 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); + } else { + chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); + } + } else { + chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); + } chan[c.chan].note=c.value; chan[c.chan].freqChanged=true; } From 3c1b1b69fc2cb5f9095a16d6d6bcf4c6a8ef05bd Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 17 Mar 2022 03:43:02 -0500 Subject: [PATCH 302/637] GUI: define sample edit texture --- src/gui/gui.cpp | 4 ++ src/gui/gui.h | 4 ++ src/gui/sampleEdit.cpp | 133 ++++++++++++++++++++++++++++++----------- 3 files changed, 107 insertions(+), 34 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index d48a20c9d..1e06fa8ba 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -6952,6 +6952,10 @@ bool FurnaceGUI::finish() { FurnaceGUI::FurnaceGUI(): e(NULL), + sampleTex(NULL), + sampleTexW(0), + sampleTexH(0), + updateSampleTex(true), quit(false), warnQuit(false), willCommit(false), diff --git a/src/gui/gui.h b/src/gui/gui.h index a0eb52fed..677f2b54e 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -466,6 +466,10 @@ class FurnaceGUI { SDL_Window* sdlWin; SDL_Renderer* sdlRend; + SDL_Texture* sampleTex; + int sampleTexW, sampleTexH; + bool updateSampleTex; + String workingDir, fileName, clipboard, warnString, errorString, lastError, curFileName, nextFile; String workingDirSong, workingDirIns, workingDirWave, workingDirSample, workingDirAudioExport, workingDirVGMExport, workingDirFont; String mmlString[13]; diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index 4edc1dfac..6592e2151 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -17,9 +17,13 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "gui.h" +#include "../ta-log.h" #include "IconsFontAwesome4.h" #include "misc/cpp/imgui_stdlib.h" #include "guiConst.h" +#include +#include +#include #include void FurnaceGUI::drawSampleEdit() { @@ -40,45 +44,60 @@ void FurnaceGUI::drawSampleEdit() { sampleType=sampleDepths[sample->depth]; } } - ImGui::InputText("Name",&sample->name); - if (ImGui::BeginCombo("Type",sampleType.c_str())) { - for (int i=0; i<17; i++) { - if (sampleDepths[i]==NULL) continue; - if (ImGui::Selectable(sampleDepths[i])) { - sample->depth=i; - e->renderSamplesP(); - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("no undo for sample type change operations!"); + ImGui::Text("Name"); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImGui::InputText("##SampleName",&sample->name); + + if (ImGui::BeginTable("SampleProps",4,ImGuiTableFlags_SizingStretchSame)) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + if (ImGui::BeginCombo("Type",sampleType.c_str())) { + for (int i=0; i<17; i++) { + if (sampleDepths[i]==NULL) continue; + if (ImGui::Selectable(sampleDepths[i])) { + sample->depth=i; + e->renderSamplesP(); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("no undo for sample type change operations!"); + } } + ImGui::EndCombo(); } - ImGui::EndCombo(); - } - if (ImGui::InputInt("Rate (Hz)",&sample->rate,10,200)) { - if (sample->rate<100) sample->rate=100; - if (sample->rate>96000) sample->rate=96000; - } - if (ImGui::InputInt("Pitch of C-4 (Hz)",&sample->centerRate,10,200)) { - if (sample->centerRate<100) sample->centerRate=100; - if (sample->centerRate>65535) sample->centerRate=65535; - } - ImGui::Text("effective rate: %dHz",e->getEffectiveSampleRate(sample->rate)); - bool doLoop=(sample->loopStart>=0); - if (ImGui::Checkbox("Loop",&doLoop)) { - if (doLoop) { - sample->loopStart=0; - } else { - sample->loopStart=-1; + + ImGui::TableNextColumn(); + if (ImGui::InputInt("Rate (Hz)",&sample->rate,10,200)) { + if (sample->rate<100) sample->rate=100; + if (sample->rate>96000) sample->rate=96000; } - } - if (doLoop) { - ImGui::SameLine(); - if (ImGui::InputInt("##LoopPosition",&sample->loopStart,1,10)) { - if (sample->loopStart<0 || sample->loopStart>=(int)sample->samples) { + + ImGui::TableNextColumn(); + if (ImGui::InputInt("Pitch of C-4 (Hz)",&sample->centerRate,10,200)) { + if (sample->centerRate<100) sample->centerRate=100; + if (sample->centerRate>65535) sample->centerRate=65535; + } + + ImGui::TableNextColumn(); + bool doLoop=(sample->loopStart>=0); + if (ImGui::Checkbox("Loop",&doLoop)) { + if (doLoop) { sample->loopStart=0; + } else { + sample->loopStart=-1; } } + if (doLoop) { + ImGui::SameLine(); + if (ImGui::InputInt("##LoopPosition",&sample->loopStart,1,10)) { + if (sample->loopStart<0 || sample->loopStart>=(int)sample->samples) { + sample->loopStart=0; + } + } + } + ImGui::EndTable(); } + + /* if (ImGui::Button("Apply")) { e->renderSamplesP(); } @@ -89,8 +108,54 @@ void FurnaceGUI::drawSampleEdit() { ImGui::SameLine(); if (ImGui::Button(ICON_FA_VOLUME_OFF "##StopSample")) { e->stopSamplePreview(); - } + }*/ ImGui::Separator(); + + ImVec2 avail=ImGui::GetContentRegionAvail(); + //int availX=avail.x; + int availY=avail.y; + + if (sampleTex==NULL || sampleTexW!=avail.x || sampleTexH!=avail.y) { + if (sampleTex!=NULL) { + SDL_DestroyTexture(sampleTex); + sampleTex=NULL; + } + if (avail.x>=1 && avail.y>=1) { + logD("recreating sample texture.\n"); + sampleTex=SDL_CreateTexture(sdlRend,SDL_PIXELFORMAT_ARGB8888,SDL_TEXTUREACCESS_STREAMING,avail.x,avail.y); + sampleTexW=avail.x; + sampleTexH=avail.y; + if (sampleTex==NULL) { + logE("error while creating sample texture! %s\n",SDL_GetError()); + } else { + updateSampleTex=true; + } + } + } + + if (sampleTex!=NULL) { + if (updateSampleTex) { + unsigned char* data=NULL; + int pitch=0; + logD("updating sample texture.\n"); + if (SDL_LockTexture(sampleTex,NULL,(void**)&data,&pitch)!=0) { + logE("error while locking sample texture! %s\n",SDL_GetError()); + } else { + for (int i=0; iloopStart>=0) { @@ -129,7 +194,7 @@ void FurnaceGUI::drawSampleEdit() { } if (!considerations) { ImGui::Text("- none"); - } + }*/ } } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_SAMPLE_EDIT; From 90a4fefc76d54483fc286509ec5ad4b2f494e0d0 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 17 Mar 2022 14:32:01 -0500 Subject: [PATCH 303/637] Revert "change default patch" This reverts commit 5d6bbce916a5378f45d37c9b69862b672a38cd71. --- src/engine/instrument.h | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/engine/instrument.h b/src/engine/instrument.h index 7141899ff..ea52e6d8a 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -115,12 +115,11 @@ struct DivInstrumentFM { fb=4; op[0].tl=42; op[0].ar=31; - op[0].dr=2; + op[0].dr=8; op[0].sl=15; op[0].rr=3; op[0].mult=5; op[0].dt=5; - op[0].rs=3; op[2].tl=18; op[2].ar=31; @@ -130,14 +129,13 @@ struct DivInstrumentFM { op[2].mult=1; op[2].dt=0; - op[1].tl=64; + op[1].tl=48; op[1].ar=31; - op[1].dr=2; + op[1].dr=4; op[1].sl=11; - op[1].rr=8; + op[1].rr=1; op[1].mult=1; op[1].dt=5; - op[1].rs=2; op[3].tl=2; op[3].ar=31; From c264678fdce7f81e5f3d29ff936ad9c8c886aa14 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 17 Mar 2022 14:35:38 -0500 Subject: [PATCH 304/637] dev68 --- papers/format.md | 2 ++ src/engine/engine.h | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/papers/format.md b/papers/format.md index 9afa4b47e..28da13a06 100644 --- a/papers/format.md +++ b/papers/format.md @@ -29,6 +29,8 @@ furthermore, an `or reserved` indicates this field is always present, but is res the format versions are: +- 68: Furnace dev68 +- 67: Furnace dev67 - 66: Furnace dev66 - 65: Furnace dev65 - 64: Furnace dev64 diff --git a/src/engine/engine.h b/src/engine/engine.h index a3ac2c249..80e2514da 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -37,8 +37,8 @@ warnings+=(String("\n")+x); \ } -#define DIV_VERSION "dev67" -#define DIV_ENGINE_VERSION 67 +#define DIV_VERSION "dev68" +#define DIV_ENGINE_VERSION 68 // for imports #define DIV_VERSION_MOD 0xff01 From 9dea0930527c31037338fac90b538cc1c7eb5805 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 17 Mar 2022 16:17:33 -0500 Subject: [PATCH 305/637] GUI: reduce scroll boundaries --- src/gui/gui.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 1e06fa8ba..11fedfed1 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -5277,10 +5277,10 @@ bool FurnaceGUI::loop() { #endif if (selecting) { // detect whether we have to scroll - if (motionYpatWindowPos.y+patWindowSize.y) { + if (motionY>patWindowPos.y+patWindowSize.y-2.0f*dpiScale) { addScroll(1); } } From d63f3d311b48548f218a370bbb71e862a1f72d91 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 17 Mar 2022 16:37:49 -0500 Subject: [PATCH 306/637] fix arpeggio not resetting note to base on 0000 --- src/engine/engine.h | 10 ++++++---- src/engine/fileOps.cpp | 14 ++++++++++---- src/engine/playback.cpp | 7 +++++++ src/engine/song.h | 4 +++- src/gui/gui.cpp | 4 ++++ 5 files changed, 30 insertions(+), 9 deletions(-) diff --git a/src/engine/engine.h b/src/engine/engine.h index 80e2514da..28e2a8702 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -37,8 +37,8 @@ warnings+=(String("\n")+x); \ } -#define DIV_VERSION "dev68" -#define DIV_ENGINE_VERSION 68 +#define DIV_VERSION "dev69" +#define DIV_ENGINE_VERSION 69 // for imports #define DIV_VERSION_MOD 0xff01 @@ -78,7 +78,8 @@ struct DivChannelState { int vibratoDepth, vibratoRate, vibratoPos, vibratoDir, vibratoFine; int tremoloDepth, tremoloRate, tremoloPos; unsigned char arp, arpStage, arpTicks; - bool doNote, legato, portaStop, keyOn, keyOff, nowYouCanStop, stopOnOff, arpYield, delayLocked, inPorta, scheduledSlideReset, shorthandPorta, noteOnInhibit; + bool doNote, legato, portaStop, keyOn, keyOff, nowYouCanStop, stopOnOff; + bool arpYield, delayLocked, inPorta, scheduledSlideReset, shorthandPorta, noteOnInhibit, resetArp; DivChannelState(): note(-1), @@ -119,7 +120,8 @@ struct DivChannelState { inPorta(false), scheduledSlideReset(false), shorthandPorta(false), - noteOnInhibit(false) {} + noteOnInhibit(false), + resetArp(false) {} }; struct DivNoteEvent { diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index a47be934f..f6038f45d 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -143,6 +143,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { ds.brokenDACMode=true; ds.oneTickCut=false; ds.newInsTriggersInPorta=true; + ds.arp0Reset=true; // 1.1 compat flags if (ds.version>24) { @@ -818,6 +819,9 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { if (ds.version<66) { ds.newInsTriggersInPorta=false; } + if (ds.version<69) { + ds.arp0Reset=false; + } ds.isDMF=false; reader.readS(); // reserved @@ -1009,7 +1013,11 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { } else { reader.readC(); } - for (int i=0; i<1; i++) reader.readC(); + if (ds.version>=69) { + ds.arp0Reset=reader.readC(); + } else { + reader.readC(); + } } else { for (int i=0; i<20; i++) reader.readC(); } @@ -1828,9 +1836,7 @@ SafeWriter* DivEngine::saveFur() { w->writeC(song.brokenDACMode); w->writeC(song.oneTickCut); w->writeC(song.newInsTriggersInPorta); - for (int i=0; i<1; i++) { - w->writeC(0); - } + w->writeC(song.arp0Reset); ptrSeek=w->tell(); // instrument pointers (we'll seek here later) diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 965f28538..f1fb7fc44 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -909,6 +909,9 @@ void DivEngine::processRow(int i, bool afterDelay) { break; case 0x00: // arpeggio chan[i].arp=effectVal; + if (chan[i].arp==0 && song.arp0Reset) { + chan[i].resetArp=true; + } break; case 0x0c: // retrigger if (effectVal!=0) { @@ -1335,6 +1338,10 @@ bool DivEngine::nextTick(bool noAccum) { dispatchCmd(DivCommand(DIV_CMD_NOTE_OFF,i)); } } + if (chan[i].resetArp) { + dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note)); + chan[i].resetArp=false; + } if (chan[i].arp!=0 && !chan[i].arpYield && chan[i].portaSpeed<1) { if (--chan[i].arpTicks<1) { chan[i].arpTicks=song.arpLen; diff --git a/src/engine/song.h b/src/engine/song.h index 6799909d4..7495b01c0 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -300,6 +300,7 @@ struct DivSong { bool brokenDACMode; bool oneTickCut; bool newInsTriggersInPorta; + bool arp0Reset; DivOrders orders; std::vector ins; @@ -371,7 +372,8 @@ struct DivSong { continuousVibrato(false), brokenDACMode(false), oneTickCut(false), - newInsTriggersInPorta(true) { + newInsTriggersInPorta(true), + arp0Reset(true) { for (int i=0; i<32; i++) { system[i]=DIV_SYSTEM_NULL; systemVol[i]=64; diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 11fedfed1..e006f01fd 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2197,6 +2197,10 @@ void FurnaceGUI::drawCompatFlags() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("behavior changed in 0.6"); } + ImGui::Checkbox("Reset note to base on arpeggio stop",&e->song.arp0Reset); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("behavior changed in 0.6"); + } } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_COMPAT_FLAGS; ImGui::End(); From 8ee7efc8a019e202b4a13e333d619335b386d7f1 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 17 Mar 2022 16:39:01 -0500 Subject: [PATCH 307/637] update format --- papers/format.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/papers/format.md b/papers/format.md index 28da13a06..11be0b283 100644 --- a/papers/format.md +++ b/papers/format.md @@ -29,6 +29,7 @@ furthermore, an `or reserved` indicates this field is always present, but is res the format versions are: +- 69: Furnace dev69 - 68: Furnace dev68 - 67: Furnace dev67 - 66: Furnace dev66 @@ -212,7 +213,7 @@ size | description 1 | broken DAC mode (>=64) or reserved 1 | one tick cut (>=65) or reserved 1 | instrument change allowed during porta (>=66) or reserved - 1 | reserved + 1 | reset note base on arpeggio effect stop (0000) (>=69) or reserved 4?? | pointers to instruments 4?? | pointers to wavetables 4?? | pointers to samples From b31ab408db6d4b18aae9e235bbf9210e1b78f7d1 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 17 Mar 2022 18:08:59 -0500 Subject: [PATCH 308/637] GUI: new sample editor work --- src/gui/gui.cpp | 5 +++- src/gui/gui.h | 5 ++++ src/gui/sampleEdit.cpp | 57 ++++++++++++++++++++++++++++++++++++------ 3 files changed, 58 insertions(+), 9 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index e006f01fd..8f801533d 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1364,6 +1364,7 @@ void FurnaceGUI::actualSampleList() { ImGui::TableNextColumn(); if (ImGui::Selectable(fmt::sprintf("%d: %s##_SAM%d",i,sample->name,i).c_str(),curSample==i)) { curSample=i; + updateSampleTex=true; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Bank %d: %s",i/12,sampleNote[i%12]); @@ -7094,7 +7095,9 @@ FurnaceGUI::FurnaceGUI(): scaleMax(100.0f), fadeMode(false), randomMode(false), - oldOrdersLen(0) { + oldOrdersLen(0), + sampleZoom(1.0), + samplePos(0) { // octave 1 /* diff --git a/src/gui/gui.h b/src/gui/gui.h index 677f2b54e..c1ce40e2f 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -714,6 +714,11 @@ class FurnaceGUI { std::deque undoHist; std::deque redoHist; + // sample editor specific + double sampleZoom; + int samplePos; + + // visualizer float keyHit[DIV_MAX_CHANS]; int lastIns[DIV_MAX_CHANS]; diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index 6592e2151..e6d156e7b 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -45,6 +45,7 @@ void FurnaceGUI::drawSampleEdit() { } } ImGui::Text("Name"); + ImGui::SameLine(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); ImGui::InputText("##SampleName",&sample->name); @@ -97,6 +98,18 @@ void FurnaceGUI::drawSampleEdit() { ImGui::EndTable(); } + if (ImGui::InputDouble("Zoom",&sampleZoom,0.1,2.0)) { + if (sampleZoom<0.01) sampleZoom=0.01; + + updateSampleTex=true; + } + if (ImGui::InputInt("Pos",&samplePos,1,10)) { + if (samplePos>=(int)sample->samples) samplePos=sample->samples-1; + if (samplePos<0) samplePos=0; + + updateSampleTex=true; + } + /* if (ImGui::Button("Apply")) { e->renderSamplesP(); @@ -112,7 +125,7 @@ void FurnaceGUI::drawSampleEdit() { ImGui::Separator(); ImVec2 avail=ImGui::GetContentRegionAvail(); - //int availX=avail.x; + int availX=avail.x; int availY=avail.y; if (sampleTex==NULL || sampleTexW!=avail.x || sampleTexH!=avail.y) { @@ -122,7 +135,7 @@ void FurnaceGUI::drawSampleEdit() { } if (avail.x>=1 && avail.y>=1) { logD("recreating sample texture.\n"); - sampleTex=SDL_CreateTexture(sdlRend,SDL_PIXELFORMAT_ARGB8888,SDL_TEXTUREACCESS_STREAMING,avail.x,avail.y); + sampleTex=SDL_CreateTexture(sdlRend,SDL_PIXELFORMAT_ABGR8888,SDL_TEXTUREACCESS_STREAMING,avail.x,avail.y); sampleTexW=avail.x; sampleTexH=avail.y; if (sampleTex==NULL) { @@ -135,17 +148,45 @@ void FurnaceGUI::drawSampleEdit() { if (sampleTex!=NULL) { if (updateSampleTex) { - unsigned char* data=NULL; + unsigned int* data=NULL; int pitch=0; logD("updating sample texture.\n"); if (SDL_LockTexture(sampleTex,NULL,(void**)&data,&pitch)!=0) { logE("error while locking sample texture! %s\n",SDL_GetError()); } else { - for (int i=0; i=sample->samples) break; + int y1, y2; + int totalAdvance=0; + y1=((unsigned short)sample->data16[xCoarse]^0x8000)*availY/65536; + xFine+=xAdvanceFine; + if (xFine>=16777216) { + xFine-=16777216; + totalAdvance++; + } + totalAdvance+=xAdvanceCoarse; + if (xCoarse>=sample->samples) break; + do { + y2=((unsigned short)sample->data16[xCoarse]^0x8000)*availY/65536; + if (y1>y2) { + y2^=y1; + y1^=y2; + y2^=y1; + } + for (int j=y1; j<=y2; j++) { + data[i+availX*j]=lineColor; + } + if (totalAdvance>0) xCoarse++; + } while ((totalAdvance--)>0); } SDL_UnlockTexture(sampleTex); } From 07624f6012a39f43c421616e6753a8bd2a827cb4 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 17 Mar 2022 18:15:44 -0500 Subject: [PATCH 309/637] fix DPCM to 16-bit --- src/engine/sample.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/sample.cpp b/src/engine/sample.cpp index 571e35118..04bdb91eb 100644 --- a/src/engine/sample.cpp +++ b/src/engine/sample.cpp @@ -162,7 +162,7 @@ void DivSample::render() { case 1: { // DPCM int accum=0; for (unsigned int i=0; i>3]>>(i&7))&1)?1:-1; + accum+=((dataDPCM[i>>3]>>(i&7))&1)?1:-1; if (accum>63) accum=63; if (accum<-64) accum=-64; data16[i]=accum*512; From 5f0c1e9077eaea7b2c1ca5aa788290c76fe0df82 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 17 Mar 2022 18:28:56 -0500 Subject: [PATCH 310/637] GUI: more sample editor work --- src/gui/sampleEdit.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index e6d156e7b..513a72fa3 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -58,6 +58,7 @@ void FurnaceGUI::drawSampleEdit() { if (ImGui::Selectable(sampleDepths[i])) { sample->depth=i; e->renderSamplesP(); + updateSampleTex=true; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("no undo for sample type change operations!"); @@ -86,6 +87,7 @@ void FurnaceGUI::drawSampleEdit() { } else { sample->loopStart=-1; } + updateSampleTex=true; } if (doLoop) { ImGui::SameLine(); @@ -93,6 +95,7 @@ void FurnaceGUI::drawSampleEdit() { if (sample->loopStart<0 || sample->loopStart>=(int)sample->samples) { sample->loopStart=0; } + updateSampleTex=true; } } ImGui::EndTable(); @@ -100,13 +103,11 @@ void FurnaceGUI::drawSampleEdit() { if (ImGui::InputDouble("Zoom",&sampleZoom,0.1,2.0)) { if (sampleZoom<0.01) sampleZoom=0.01; - updateSampleTex=true; } if (ImGui::InputInt("Pos",&samplePos,1,10)) { if (samplePos>=(int)sample->samples) samplePos=sample->samples-1; if (samplePos<0) samplePos=0; - updateSampleTex=true; } From 1af6eb21f65518bf35eb1970a44f0ca8ec246099 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 17 Mar 2022 18:30:28 -0500 Subject: [PATCH 311/637] freaking clang extension --- src/gui/sampleEdit.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index 513a72fa3..cb1d3a99f 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -17,14 +17,11 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "gui.h" +#include #include "../ta-log.h" #include "IconsFontAwesome4.h" #include "misc/cpp/imgui_stdlib.h" #include "guiConst.h" -#include -#include -#include -#include void FurnaceGUI::drawSampleEdit() { if (nextWindow==GUI_WINDOW_SAMPLE_EDIT) { @@ -241,4 +238,4 @@ void FurnaceGUI::drawSampleEdit() { } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_SAMPLE_EDIT; ImGui::End(); -} \ No newline at end of file +} From bb955e22eea7163fbb4ee08fa1043a1631415e5f Mon Sep 17 00:00:00 2001 From: Alex <99325922+ActualNK358@users.noreply.github.com> Date: Thu, 17 Mar 2022 21:24:00 -0500 Subject: [PATCH 312/637] Yet another demo song --- demos/silverlining.fur | Bin 0 -> 24216 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 demos/silverlining.fur diff --git a/demos/silverlining.fur b/demos/silverlining.fur new file mode 100644 index 0000000000000000000000000000000000000000..06567d7313d66cd2f4236e40215dad829cc3082f GIT binary patch literal 24216 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 Date: Thu, 17 Mar 2022 22:03:14 -0500 Subject: [PATCH 313/637] GUI: update credits --- src/gui/gui.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 8f801533d..5f8171f40 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1615,6 +1615,7 @@ const char* aboutLine[]={ "", "-- demo songs --", "0x5066", + "ActualNK358", "breakthetargets", "CaptainMalware", "kleeder", @@ -1679,10 +1680,6 @@ const char* aboutLine[]={ "", "it also comes with ABSOLUTELY NO WARRANTY.", "", - "look out for Furnace 0.6 coming somewhere", - "before the equinox with more systems", - "and plenty of other things...", - "", "thanks to all contributors/bug reporters!" }; From c59c176de247b668d7eb3f1aecbf6b964a4eb69e Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 17 Mar 2022 23:28:01 -0500 Subject: [PATCH 314/637] W O R K A R O U N D adding text fixes the issue --- src/gui/gui.cpp | 56 ++++++++++++++++++++++-------------------- src/gui/gui.h | 21 ++++++++++++++++ src/gui/sampleEdit.cpp | 4 +++ 3 files changed, 54 insertions(+), 27 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index b3a454595..3844ea383 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1178,111 +1178,111 @@ void FurnaceGUI::drawInsList() { switch (ins->type) { case DIV_INS_FM: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_FM]); - name=fmt::sprintf(ICON_FA_AREA_CHART " %.2X: %s##_INS%d\n",i,ins->name,i); + name=fmt::sprintf(ICON_FA_AREA_CHART " %.2X: %s##_INS%d",i,ins->name,i); break; case DIV_INS_STD: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_STD]); - name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d\n",i,ins->name,i); + name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); break; case DIV_INS_GB: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_GB]); - name=fmt::sprintf(ICON_FA_GAMEPAD " %.2X: %s##_INS%d\n",i,ins->name,i); + name=fmt::sprintf(ICON_FA_GAMEPAD " %.2X: %s##_INS%d",i,ins->name,i); break; case DIV_INS_C64: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_C64]); - name=fmt::sprintf(ICON_FA_KEYBOARD_O " %.2X: %s##_INS%d\n",i,ins->name,i); + name=fmt::sprintf(ICON_FA_KEYBOARD_O " %.2X: %s##_INS%d",i,ins->name,i); break; case DIV_INS_AMIGA: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_AMIGA]); - name=fmt::sprintf(ICON_FA_VOLUME_UP " %.2X: %s##_INS%d\n",i,ins->name,i); + name=fmt::sprintf(ICON_FA_VOLUME_UP " %.2X: %s##_INS%d",i,ins->name,i); break; case DIV_INS_PCE: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_PCE]); - name=fmt::sprintf(ICON_FA_ID_BADGE " %.2X: %s##_INS%d\n",i,ins->name,i); + name=fmt::sprintf(ICON_FA_ID_BADGE " %.2X: %s##_INS%d",i,ins->name,i); break; case DIV_INS_AY: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_AY]); - name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d\n",i,ins->name,i); + name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); break; case DIV_INS_AY8930: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_AY8930]); - name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d\n",i,ins->name,i); + name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); break; case DIV_INS_TIA: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_TIA]); - name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d\n",i,ins->name,i); + name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); break; case DIV_INS_SAA1099: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_SAA1099]); - name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d\n",i,ins->name,i); + name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); break; case DIV_INS_VIC: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_VIC]); - name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d\n",i,ins->name,i); + name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); break; case DIV_INS_PET: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_PET]); - name=fmt::sprintf(ICON_FA_SQUARE " %.2X: %s##_INS%d\n",i,ins->name,i); + name=fmt::sprintf(ICON_FA_SQUARE " %.2X: %s##_INS%d",i,ins->name,i); break; case DIV_INS_VRC6: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_VRC6]); - name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d\n",i,ins->name,i); + name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); break; case DIV_INS_OPLL: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_OPLL]); - name=fmt::sprintf(ICON_FA_AREA_CHART " %.2X: %s##_INS%d\n",i,ins->name,i); + name=fmt::sprintf(ICON_FA_AREA_CHART " %.2X: %s##_INS%d",i,ins->name,i); break; case DIV_INS_OPL: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_OPL]); - name=fmt::sprintf(ICON_FA_AREA_CHART " %.2X: %s##_INS%d\n",i,ins->name,i); + name=fmt::sprintf(ICON_FA_AREA_CHART " %.2X: %s##_INS%d",i,ins->name,i); break; case DIV_INS_FDS: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_FDS]); - name=fmt::sprintf(ICON_FA_FLOPPY_O " %.2X: %s##_INS%d\n",i,ins->name,i); + name=fmt::sprintf(ICON_FA_FLOPPY_O " %.2X: %s##_INS%d",i,ins->name,i); break; case DIV_INS_VBOY: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_VBOY]); - name=fmt::sprintf(ICON_FA_BINOCULARS " %.2X: %s##_INS%d\n",i,ins->name,i); + name=fmt::sprintf(ICON_FA_BINOCULARS " %.2X: %s##_INS%d",i,ins->name,i); break; case DIV_INS_N163: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_N163]); - name=fmt::sprintf(ICON_FA_CALCULATOR " %.2X: %s##_INS%d\n",i,ins->name,i); + name=fmt::sprintf(ICON_FA_CALCULATOR " %.2X: %s##_INS%d",i,ins->name,i); break; case DIV_INS_SCC: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_SCC]); - name=fmt::sprintf(ICON_FA_CALCULATOR " %.2X: %s##_INS%d\n",i,ins->name,i); + name=fmt::sprintf(ICON_FA_CALCULATOR " %.2X: %s##_INS%d",i,ins->name,i); break; case DIV_INS_OPZ: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_OPZ]); - name=fmt::sprintf(ICON_FA_AREA_CHART " %.2X: %s##_INS%d\n",i,ins->name,i); + name=fmt::sprintf(ICON_FA_AREA_CHART " %.2X: %s##_INS%d",i,ins->name,i); break; case DIV_INS_POKEY: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_POKEY]); - name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d\n",i,ins->name,i); + name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); break; case DIV_INS_BEEPER: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_BEEPER]); - name=fmt::sprintf(ICON_FA_SQUARE " %.2X: %s##_INS%d\n",i,ins->name,i); + name=fmt::sprintf(ICON_FA_SQUARE " %.2X: %s##_INS%d",i,ins->name,i); break; case DIV_INS_SWAN: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_SWAN]); - name=fmt::sprintf(ICON_FA_GAMEPAD " %.2X: %s##_INS%d\n",i,ins->name,i); + name=fmt::sprintf(ICON_FA_GAMEPAD " %.2X: %s##_INS%d",i,ins->name,i); break; case DIV_INS_MIKEY: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_MIKEY]); - name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d\n",i,ins->name,i); + name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); break; case DIV_INS_VERA: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_VERA]); - name=fmt::sprintf(ICON_FA_KEYBOARD_O " %.2X: %s##_INS%d\n",i,ins->name,i); + name=fmt::sprintf(ICON_FA_KEYBOARD_O " %.2X: %s##_INS%d",i,ins->name,i); break; case DIV_INS_X1_010: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_X1_010]); - name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d\n",i,ins->name,i); + name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); break; default: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_UNKNOWN]); - name=fmt::sprintf(ICON_FA_QUESTION " %.2X: %s##_INS%d\n",i,ins->name,i); + name=fmt::sprintf(ICON_FA_QUESTION " %.2X: %s##_INS%d",i,ins->name,i); break; } ImGui::TableNextRow(); @@ -6086,6 +6086,8 @@ bool FurnaceGUI::loop() { drawChannels(); drawRegView(); + //ImGui::ShowMetricsWindow(); + if (firstFrame) { firstFrame=false; if (patternOpen) nextWindow=GUI_WINDOW_PATTERN; diff --git a/src/gui/gui.h b/src/gui/gui.h index c1ce40e2f..73d34a1af 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -44,13 +44,31 @@ enum FurnaceGUIColors { GUI_COLOR_ACCENT_SECONDARY, GUI_COLOR_EDITING, GUI_COLOR_SONG_LOOP, + + GUI_COLOR_FILE_DIR, + GUI_COLOR_FILE_SONG_NATIVE, + GUI_COLOR_FILE_SONG_IMPORT, + GUI_COLOR_FILE_INSTR, + GUI_COLOR_FILE_AUDIO, + GUI_COLOR_FILE_WAVE, + GUI_COLOR_FILE_VGM, + GUI_COLOR_FILE_FONT, + GUI_COLOR_FILE_OTHER, + GUI_COLOR_VOLMETER_LOW, GUI_COLOR_VOLMETER_HIGH, GUI_COLOR_VOLMETER_PEAK, + + GUI_COLOR_ORDER_ROW_INDEX, + GUI_COLOR_ORDER_ACTIVE, + GUI_COLOR_ORDER_SIMILAR, + GUI_COLOR_ORDER_INACTIVE, + GUI_COLOR_MACRO_VOLUME, GUI_COLOR_MACRO_PITCH, GUI_COLOR_MACRO_OTHER, GUI_COLOR_MACRO_WAVE, + GUI_COLOR_INSTR_FM, GUI_COLOR_INSTR_STD, GUI_COLOR_INSTR_GB, @@ -78,6 +96,7 @@ enum FurnaceGUIColors { GUI_COLOR_INSTR_VERA, GUI_COLOR_INSTR_X1_010, GUI_COLOR_INSTR_UNKNOWN, + GUI_COLOR_CHANNEL_FM, GUI_COLOR_CHANNEL_PULSE, GUI_COLOR_CHANNEL_NOISE, @@ -85,6 +104,7 @@ enum FurnaceGUIColors { GUI_COLOR_CHANNEL_PCM, GUI_COLOR_CHANNEL_OP, GUI_COLOR_CHANNEL_MUTED, + GUI_COLOR_PATTERN_CURSOR, GUI_COLOR_PATTERN_CURSOR_HOVER, GUI_COLOR_PATTERN_CURSOR_ACTIVE, @@ -110,6 +130,7 @@ enum FurnaceGUIColors { GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY, GUI_COLOR_PATTERN_EFFECT_SYS_SECONDARY, GUI_COLOR_PATTERN_EFFECT_MISC, + GUI_COLOR_EE_VALUE, GUI_COLOR_PLAYBACK_STAT, GUI_COLOR_MAX diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index cb1d3a99f..d2003d41d 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "gui.h" +#include #include #include "../ta-log.h" #include "IconsFontAwesome4.h" @@ -123,6 +124,7 @@ void FurnaceGUI::drawSampleEdit() { ImGui::Separator(); ImVec2 avail=ImGui::GetContentRegionAvail(); + avail.y-=ImGui::GetFontSize()+ImGui::GetStyle().ItemSpacing.y; int availX=avail.x; int availY=avail.y; @@ -192,6 +194,8 @@ void FurnaceGUI::drawSampleEdit() { } ImGui::Image(sampleTex,avail); + + ImGui::Text("A workaround! Pretty cool huh?"); } /* From 689ed3bf654912da3cdeb321987b5fde21f07840 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 19 Mar 2022 03:42:44 -0500 Subject: [PATCH 315/637] sample editor workkkkkkk --- src/engine/engine.cpp | 7 ++ src/engine/engine.h | 8 ++- src/engine/sample.cpp | 29 ++++++++ src/engine/sample.h | 8 +++ src/gui/gui.cpp | 10 ++- src/gui/gui.h | 6 ++ src/gui/guiConst.cpp | 9 +++ src/gui/guiConst.h | 3 +- src/gui/sampleEdit.cpp | 148 +++++++++++++++++++++++++++++++++++++++-- 9 files changed, 221 insertions(+), 7 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index cd7eb3ea2..3352a8737 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -19,6 +19,7 @@ #include "dataErrors.h" #include "song.h" +#include #define _USE_MATH_DEFINES #include "engine.h" #include "instrument.h" @@ -2275,6 +2276,12 @@ bool DivEngine::switchMaster() { return true; } +void DivEngine::synchronized(const std::function& what) { + isBusy.lock(); + what(); + isBusy.unlock(); +} + TAAudioDesc& DivEngine::getAudioDescWant() { return want; } diff --git a/src/engine/engine.h b/src/engine/engine.h index 28e2a8702..ba6a770e5 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -25,6 +25,7 @@ #include "safeWriter.h" #include "../audio/taAudio.h" #include "blip_buf.h" +#include #include #include #include @@ -244,7 +245,6 @@ class DivEngine { bool perSystemEffect(int ch, unsigned char effect, unsigned char effectVal); bool perSystemPostEffect(int ch, unsigned char effect, unsigned char effectVal); void recalcChans(); - void renderSamples(); void reset(); void playSub(bool preserveDrift, int goalRow=0); @@ -587,6 +587,9 @@ class DivEngine { // get register cheatsheet const char** getRegisterSheet(int sys); + // UNSAFE render samples - only execute when locked + void renderSamples(); + // public render samples void renderSamplesP(); @@ -614,6 +617,9 @@ class DivEngine { // switch master bool switchMaster(); + // perform secure/sync operation + void synchronized(const std::function& what); + // get audio desc want TAAudioDesc& getAudioDescWant(); diff --git a/src/engine/sample.cpp b/src/engine/sample.cpp index 04bdb91eb..53fa42fe2 100644 --- a/src/engine/sample.cpp +++ b/src/engine/sample.cpp @@ -149,6 +149,35 @@ bool DivSample::init(unsigned int count) { return true; } +bool DivSample::resize(unsigned int count) { + if (depth==8) { + if (data8!=NULL) { + signed char* oldData8=data8; + data8=NULL; + initInternal(8,count); + memcpy(data8,oldData8,MIN(count,samples)); + delete[] oldData8; + } else { + initInternal(8,count); + } + samples=count; + return true; + } else if (depth==16) { + if (data16!=NULL) { + short* oldData16=data16; + data16=NULL; + initInternal(16,count); + memcpy(data16,oldData16,sizeof(short)*MIN(count,samples)); + delete[] oldData16; + } else { + initInternal(16,count); + } + samples=count; + return true; + } + return false; +} + void DivSample::render() { // step 1: convert to 16-bit if needed if (depth!=16) { diff --git a/src/engine/sample.h b/src/engine/sample.h index 3598d67a7..026ca290e 100644 --- a/src/engine/sample.h +++ b/src/engine/sample.h @@ -76,6 +76,14 @@ struct DivSample { */ bool init(unsigned int count); + /** + * resize sample data. make sure the sample has been initialized before doing so. + * @warning do not attempt to resize a sample outside of a synchronized block! + * @param count number of samples. + * @return whether it was successful. + */ + bool resize(unsigned int count); + /** * initialize the rest of sample formats for this sample. */ diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 3844ea383..78deccd3e 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -7096,7 +7096,15 @@ FurnaceGUI::FurnaceGUI(): randomMode(false), oldOrdersLen(0), sampleZoom(1.0), - samplePos(0) { + samplePos(0), + resizeSize(1024), + resampleTarget(32000), + resampleStrat(5), + amplifyVol(100.0), + sampleSelStart(-1), + sampleSelEnd(-1), + sampleDragActive(false), + sampleDragMode(false) { // octave 1 /* diff --git a/src/gui/gui.h b/src/gui/gui.h index 73d34a1af..568ea8ed0 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -738,6 +738,12 @@ class FurnaceGUI { // sample editor specific double sampleZoom; int samplePos; + int resizeSize; + double resampleTarget; + int resampleStrat; + float amplifyVol; + int sampleSelStart, sampleSelEnd; + bool sampleDragActive, sampleDragMode; // visualizer float keyHit[DIV_MAX_CHANS]; diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index ef7610496..11795e665 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -112,4 +112,13 @@ const char* sampleDepths[17]={ NULL, NULL, "16-bit PCM" +}; + +const char* resampleStrats[]={ + "none", + "linear", + "cubic spline", + "blep synthesis", + "sinc", + "best possible" }; \ No newline at end of file diff --git a/src/gui/guiConst.h b/src/gui/guiConst.h index 0bf621180..ee49c1daa 100644 --- a/src/gui/guiConst.h +++ b/src/gui/guiConst.h @@ -24,4 +24,5 @@ extern const char* noteNames[180]; extern const char* noteNamesG[180]; extern const char* pitchLabel[11]; extern const char* insTypes[]; -extern const char* sampleDepths[17]; \ No newline at end of file +extern const char* sampleDepths[17]; +extern const char* resampleStrats[]; \ No newline at end of file diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index d2003d41d..07afe23ff 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -50,7 +50,10 @@ void FurnaceGUI::drawSampleEdit() { if (ImGui::BeginTable("SampleProps",4,ImGuiTableFlags_SizingStretchSame)) { ImGui::TableNextRow(); ImGui::TableNextColumn(); - if (ImGui::BeginCombo("Type",sampleType.c_str())) { + ImGui::Text("Type"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::BeginCombo("##SampleType",sampleType.c_str())) { for (int i=0; i<17; i++) { if (sampleDepths[i]==NULL) continue; if (ImGui::Selectable(sampleDepths[i])) { @@ -66,13 +69,19 @@ void FurnaceGUI::drawSampleEdit() { } ImGui::TableNextColumn(); - if (ImGui::InputInt("Rate (Hz)",&sample->rate,10,200)) { + ImGui::Text("Rate (Hz)"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputInt("##SampleRate",&sample->rate,10,200)) { if (sample->rate<100) sample->rate=100; if (sample->rate>96000) sample->rate=96000; } ImGui::TableNextColumn(); - if (ImGui::InputInt("Pitch of C-4 (Hz)",&sample->centerRate,10,200)) { + ImGui::Text("C-4 (Hz)"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputInt("##SampleCenter",&sample->centerRate,10,200)) { if (sample->centerRate<100) sample->centerRate=100; if (sample->centerRate>65535) sample->centerRate=65535; } @@ -89,6 +98,7 @@ void FurnaceGUI::drawSampleEdit() { } if (doLoop) { ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); if (ImGui::InputInt("##LoopPosition",&sample->loopStart,1,10)) { if (sample->loopStart<0 || sample->loopStart>=(int)sample->samples) { sample->loopStart=0; @@ -123,6 +133,133 @@ void FurnaceGUI::drawSampleEdit() { }*/ ImGui::Separator(); + ImGui::Button(ICON_FA_I_CURSOR "##SSelect"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Edit mode: Select"); + } + ImGui::SameLine(); + ImGui::Button(ICON_FA_PENCIL "##SDraw"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Edit mode: Draw"); + } + ImGui::SameLine(); + ImGui::Dummy(ImVec2(4.0*dpiScale,dpiScale)); + ImGui::SameLine(); + ImGui::Button(ICON_FA_ARROWS_H "##SResize"); + if (ImGui::IsItemClicked()) { + resizeSize=sample->samples; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Resize"); + } + if (ImGui::BeginPopupContextItem("SResizeOpt",ImGuiPopupFlags_MouseButtonLeft)) { + if (ImGui::InputInt("Samples",&resizeSize,1,64)) { + if (resizeSize<0) resizeSize=0; + if (resizeSize>16777215) resizeSize=16777215; + } + if (ImGui::Button("Resize")) { + e->synchronized([this,sample]() { + sample->resize(resizeSize); + e->renderSamples(); + }); + updateSampleTex=true; + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } else { + resizeSize=sample->samples; + } + ImGui::SameLine(); + ImGui::Button(ICON_FA_EXPAND "##SResample"); + if (ImGui::IsItemClicked()) { + resampleTarget=sample->rate; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Resample"); + } + if (ImGui::BeginPopupContextItem("SResampleOpt",ImGuiPopupFlags_MouseButtonLeft)) { + ImGui::Text("Rate"); + if (ImGui::InputDouble("##SRRate",&resampleTarget,1.0,50.0,"%g")) { + if (resampleTarget<0) resampleTarget=0; + if (resampleTarget>96000) resampleTarget=96000; + } + ImGui::SameLine(); + if (ImGui::Button("0.5x")) { + resampleTarget*=0.5; + } + ImGui::SameLine(); + if (ImGui::Button("==")) { + resampleTarget=sample->rate; + } + ImGui::SameLine(); + if (ImGui::Button("2.0x")) { + resampleTarget*=2.0; + } + double factor=resampleTarget/(double)sample->rate; + if (ImGui::InputDouble("Factor",&factor,0.125,0.5,"%g")) { + resampleTarget=(double)sample->rate*factor; + if (resampleTarget<0) resampleTarget=0; + if (resampleTarget>96000) resampleTarget=96000; + } + ImGui::Combo("Filter",&resampleStrat,resampleStrats,6); + ImGui::Button("Resample"); + ImGui::EndPopup(); + } else { + resampleTarget=sample->rate; + } + ImGui::SameLine(); + ImGui::Dummy(ImVec2(4.0*dpiScale,dpiScale)); + ImGui::SameLine(); + ImGui::Button(ICON_FA_VOLUME_UP "##SAmplify"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Amplify"); + } + if (ImGui::BeginPopupContextItem("SAmplifyOpt",ImGuiPopupFlags_MouseButtonLeft)) { + ImGui::Text("Volume"); + if (ImGui::InputFloat("##SRVolume",&lifyVol,10.0,50.0,"%g%%")) { + if (amplifyVol<0) amplifyVol=0; + if (amplifyVol>10000) amplifyVol=10000; + } + ImGui::SameLine(); + ImGui::Text("(%.1fdB)",20.0*log10(amplifyVol/100.0f)); + ImGui::Button("Apply"); + ImGui::EndPopup(); + } + ImGui::SameLine(); + ImGui::Button(ICON_FA_ARROWS_V "##SNormalize"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Normalize"); + } + ImGui::SameLine(); + ImGui::Button(ICON_FA_ERASER "##SSilence"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Apply silence"); + } + ImGui::SameLine(); + ImGui::Dummy(ImVec2(4.0*dpiScale,dpiScale)); + ImGui::SameLine(); + ImGui::Button(ICON_FA_BACKWARD "##SReverse"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Reverse"); + } + ImGui::SameLine(); + ImGui::Button(ICON_FA_SORT_AMOUNT_ASC "##SInvert"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Invert"); + } + ImGui::SameLine(); + ImGui::Button(ICON_FA_LEVEL_DOWN "##SSign"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Signed/unsigned exchange"); + } + ImGui::SameLine(); + ImGui::Button(ICON_FA_INDUSTRY "##SFilter"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Apply filter"); + } + + ImGui::Separator(); + ImVec2 avail=ImGui::GetContentRegionAvail(); avail.y-=ImGui::GetFontSize()+ImGui::GetStyle().ItemSpacing.y; int availX=avail.x; @@ -193,7 +330,10 @@ void FurnaceGUI::drawSampleEdit() { updateSampleTex=false; } - ImGui::Image(sampleTex,avail); + ImGui::ImageButton(sampleTex,avail,ImVec2(0,0),ImVec2(1,1),0); + if (ImGui::IsItemClicked()) { + printf("drawing\n"); + } ImGui::Text("A workaround! Pretty cool huh?"); } From e0325806390762fe04acdc7dbcf38f7fc1ad33ca Mon Sep 17 00:00:00 2001 From: James Alan Nguyen Date: Sat, 19 Mar 2022 17:13:00 +1100 Subject: [PATCH 316/637] Started SBI import work (Issue #79 SBI task). Also ensure catch by reference not value. --- src/engine/engine.cpp | 139 +++++++++++++++++++++++++++++++++++++---- src/engine/fileOps.cpp | 8 +-- src/gui/gui.cpp | 5 +- 3 files changed, 135 insertions(+), 17 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 3352a8737..169dcb3cf 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -1222,7 +1222,8 @@ enum DivInsFormats { DIV_INSFORMAT_TFI, DIV_INSFORMAT_VGI, DIV_INSFORMAT_FTI, - DIV_INSFORMAT_BTI + DIV_INSFORMAT_BTI, + DIV_INSFORMAT_SBI, }; bool DivEngine::addInstrumentFromFile(const char* path) { @@ -1290,7 +1291,7 @@ bool DivEngine::addInstrumentFromFile(const char* path) { if (memcmp("-Furnace instr.-",magic,16)==0) { isFurnaceInstr=true; } - } catch (EndOfFileException e) { + } catch (EndOfFileException& e) { reader.seek(0,SEEK_SET); } @@ -1313,7 +1314,7 @@ bool DivEngine::addInstrumentFromFile(const char* path) { delete[] buf; return false; } - } catch (EndOfFileException e) { + } catch (EndOfFileException& e) { lastError="premature end of file"; logE("premature end of file!\n"); delete ins; @@ -1342,6 +1343,8 @@ bool DivEngine::addInstrumentFromFile(const char* path) { format=DIV_INSFORMAT_FTI; } else if (extS==String(".bti")) { format=DIV_INSFORMAT_BTI; + } else if (extS==String(".sbi")) { + format = DIV_INSFORMAT_SBI; } } switch (format) { @@ -1352,7 +1355,7 @@ bool DivEngine::addInstrumentFromFile(const char* path) { try { reader.seek(0,SEEK_SET); version=reader.readC(); - } catch (EndOfFileException e) { + } catch (EndOfFileException& e) { lastError="premature end of file"; logE("premature end of file!\n"); delete ins; @@ -1405,7 +1408,7 @@ bool DivEngine::addInstrumentFromFile(const char* path) { return false; break; } - } catch (EndOfFileException e) { + } catch (EndOfFileException& e) { lastError="premature end of file"; logE("premature end of file!\n"); delete ins; @@ -1586,7 +1589,7 @@ bool DivEngine::addInstrumentFromFile(const char* path) { ins->gb.soundLen=reader.readC(); } } - } catch (EndOfFileException e) { + } catch (EndOfFileException& e) { lastError="premature end of file"; logE("premature end of file!\n"); delete ins; @@ -1619,7 +1622,7 @@ bool DivEngine::addInstrumentFromFile(const char* path) { op.sl=reader.readC(); op.ssgEnv=reader.readC(); } - } catch (EndOfFileException e) { + } catch (EndOfFileException& e) { lastError="premature end of file"; logE("premature end of file!\n"); delete ins; @@ -1658,7 +1661,7 @@ bool DivEngine::addInstrumentFromFile(const char* path) { op.sl=reader.readC(); op.ssgEnv=reader.readC(); } - } catch (EndOfFileException e) { + } catch (EndOfFileException& e) { lastError="premature end of file"; logE("premature end of file!\n"); delete ins; @@ -1670,6 +1673,120 @@ bool DivEngine::addInstrumentFromFile(const char* path) { break; case DIV_INSFORMAT_BTI: break; + case DIV_INSFORMAT_SBI: + try { + reader.seek(0, SEEK_SET); + ins->type = DIV_INS_OPL; + + int sbi_header = reader.readI(); + // TODO check header == "SBI\x1A" (0x1A494253) + + // 60 bytes = 4op SBI + // 52 bytes = 2op SBI (assumed) + bool is4op = (reader.size() >= 60); + + // 32-byte null terminated instrument name + ins->name = reader.readString(32); + + // 2op SBI + uint8_t sbi_Mcharacteristics = reader.readC(); + uint8_t sbi_Ccharacteristics = reader.readC(); + uint8_t sbi_Mscaling_output = reader.readC(); + uint8_t sbi_Cscaling_output = reader.readC(); + uint8_t sbi_Meg_AD = reader.readC(); + uint8_t sbi_Ceg_AD = reader.readC(); + uint8_t sbi_Meg_SR = reader.readC(); + uint8_t sbi_Ceg_SR = reader.readC(); + uint8_t sbi_Mwave = reader.readC(); + uint8_t sbi_Cwave = reader.readC(); + uint8_t sbi_FeedConnect = reader.readC(); + + // 4op SBI + uint8_t sbi_M4characteristics; + uint8_t sbi_C4characteristics; + uint8_t sbi_M4scaling_output; + uint8_t sbi_C4scaling_output; + uint8_t sbi_M4eg_AD; + uint8_t sbi_C4eg_AD; + uint8_t sbi_M4eg_SR; + uint8_t sbi_C4eg_SR; + uint8_t sbi_M4wave; + uint8_t sbi_C4wave; + uint8_t sbi_4opConnect; + for (int j = 0; j < 6 && reader.tell() < reader.size(); ++j) { + reader.readC(); + } + + DivInstrumentFM::Operator& opM = ins->fm.op[0]; + DivInstrumentFM::Operator& opC = ins->fm.op[1]; + + opM.mult = sbi_Mcharacteristics & 0xF; + opM.tl = sbi_Mscaling_output & 0x3F; + //opM.rs = reader.readC(); + opM.ar = ((sbi_Meg_AD >> 4) & 0xF); + opM.dr = (sbi_Meg_AD & 0xF); + opM.rr = (sbi_Meg_SR & 0xF); + opM.sl = ((sbi_Meg_SR >> 4) & 0xF); + //opM.ssgEnv = reader.readC(); + + ins->fm.alg = (sbi_FeedConnect & 0x1); + ins->fm.fb = (sbi_FeedConnect >> 1) & 0x7; + + opC.mult = sbi_Ccharacteristics & 0xF; + opC.tl = sbi_Cscaling_output & 0x3F; + //opC.rs = ???; + opC.ar = ((sbi_Ceg_AD >> 4) & 0xF); + opC.dr = (sbi_Ceg_AD & 0xF); + opC.rr = (sbi_Ceg_SR & 0xF); + opC.sl = ((sbi_Ceg_SR >> 4) & 0xF); + //opC.ssgEnv = ???; + + if (is4op) { + sbi_M4characteristics = reader.readC(); + sbi_C4characteristics = reader.readC(); + sbi_M4scaling_output = reader.readC(); + sbi_C4scaling_output = reader.readC(); + sbi_M4eg_AD = reader.readC(); + sbi_M4eg_SR = reader.readC(); + sbi_M4wave = reader.readC(); + sbi_C4wave = reader.readC(); + sbi_4opConnect = reader.readC(); + for (int j = 0; j < 6 && reader.tell() < reader.size(); ++j) { + reader.readC(); + } + + + ins->fm.alg |= (sbi_4opConnect & 0x1); + DivInstrumentFM::Operator& opM4 = ins->fm.op[2]; + DivInstrumentFM::Operator& opC4 = ins->fm.op[3]; + + opM4.mult = sbi_M4characteristics & 0xF; + opM4.tl = sbi_M4scaling_output & 0x3F; + //opM4.rs = reader.readC(); + opM4.ar = ((sbi_M4eg_AD >> 4) & 0xF); + opM4.dr = (sbi_M4eg_AD & 0xF); + opM4.rr = (sbi_M4eg_SR & 0xF); + opM4.sl = ((sbi_M4eg_SR >> 4) & 0xF); + //opM4.ssgEnv = reader.readC(); + + opC4.mult = sbi_C4characteristics & 0xF; + opC4.tl = sbi_C4scaling_output & 0x3F; + //opC4.rs = ???; + opC4.ar = ((sbi_C4eg_AD >> 4) & 0xF); + opC4.dr = (sbi_C4eg_AD & 0xF); + opC4.rr = (sbi_C4eg_SR & 0xF); + opC4.sl = ((sbi_C4eg_SR >> 4) & 0xF); + //opC4.ssgEnv = ???; + } + } + catch (EndOfFileException& e) { + lastError = "premature end of file"; + logE("premature end of file!\n"); + delete ins; + delete[] buf; + return false; + } + break; } if (reader.tell()len=len; } - } catch (EndOfFileException e) { + } catch (EndOfFileException& e) { // read as binary len=reader.size(); logI("reading binary for being too small...\n"); @@ -1824,7 +1941,7 @@ bool DivEngine::addWaveFromFile(const char* path) { wave->len=len; } } - } catch (EndOfFileException e) { + } catch (EndOfFileException& e) { delete wave; delete[] buf; return false; diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index f6038f45d..23570c473 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -742,7 +742,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { initDispatch(); syncReset(); } - } catch (EndOfFileException e) { + } catch (EndOfFileException& e) { logE("premature end of file!\n"); lastError="incomplete file"; delete[] file; @@ -1228,7 +1228,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { initDispatch(); syncReset(); } - } catch (EndOfFileException e) { + } catch (EndOfFileException& e) { logE("premature end of file!\n"); lastError="incomplete file"; delete[] file; @@ -1593,10 +1593,10 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { syncReset(); } success=true; - } catch (EndOfFileException e) { + } catch (EndOfFileException& e) { //logE("premature end of file!\n"); lastError="incomplete file"; - } catch (InvalidHeaderException e) { + } catch (InvalidHeaderException& e) { //logE("invalid info header!\n"); lastError="invalid info header!"; } diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 78deccd3e..03a679a65 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4658,9 +4658,9 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { if (!dirExists(workingDirIns)) workingDirIns=getHomeDir(); hasOpened=fileDialog->openLoad( "Load Instrument", - {"compatible files", "*.fui *.dmp *.tfi *.vgi", + {"compatible files", "*.fui *.dmp *.tfi *.vgi, *.sbi", "all files", ".*"}, - "compatible files{.fui,.dmp,.tfi,.vgi},.*", + "compatible files{.fui,.dmp,.tfi,.vgi.sbi},.*", workingDirIns, dpiScale ); @@ -6886,6 +6886,7 @@ bool FurnaceGUI::init() { ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".tfi",ImVec4(1.0f,0.5f,0.5f,1.0f),ICON_FA_FILE); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".vgi",ImVec4(1.0f,0.5f,0.5f,1.0f),ICON_FA_FILE); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".sbi",ImVec4(1.0f,0.5f,0.5f,1.0f),ICON_FA_FILE); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".fti",ImVec4(1.0f,0.5f,0.5f,1.0f),ICON_FA_FILE); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".bti",ImVec4(1.0f,0.5f,0.5f,1.0f),ICON_FA_FILE); From b1a0aa86ef9d4c4088a4c615607114cd7409af9a Mon Sep 17 00:00:00 2001 From: James Alan Nguyen Date: Sun, 20 Mar 2022 00:52:43 +1100 Subject: [PATCH 317/637] Instrument File Import support for 2op SBI, 4op SBI, and Freq Monster 801 SBI format (4op portion only). --- src/engine/engine.cpp | 144 +++++++++++++++++++++++++++++------------- 1 file changed, 100 insertions(+), 44 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 169dcb3cf..2345f0d68 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -1347,6 +1347,8 @@ bool DivEngine::addInstrumentFromFile(const char* path) { format = DIV_INSFORMAT_SBI; } } + + // TDOO these really should be refactored to separate functions/cpp files per instrument file type. switch (format) { case DIV_INSFORMAT_DMP: { // this is a ridiculous mess @@ -1679,12 +1681,11 @@ bool DivEngine::addInstrumentFromFile(const char* path) { ins->type = DIV_INS_OPL; int sbi_header = reader.readI(); - // TODO check header == "SBI\x1A" (0x1A494253) + // SBI header determines format + bool is_2op = (sbi_header == 0x1A494253); // SBI\x1A + bool is_4op = (sbi_header == 0x1A504F34); // 4OP\x1A + bool is_6op = (sbi_header == 0x1A504F36); // 6OP\x1A - Freq Monster 801-specific - // 60 bytes = 4op SBI - // 52 bytes = 2op SBI (assumed) - bool is4op = (reader.size() >= 60); - // 32-byte null terminated instrument name ins->name = reader.readString(32); @@ -1713,73 +1714,128 @@ bool DivEngine::addInstrumentFromFile(const char* path) { uint8_t sbi_M4wave; uint8_t sbi_C4wave; uint8_t sbi_4opConnect; - for (int j = 0; j < 6 && reader.tell() < reader.size(); ++j) { - reader.readC(); + + if (is_2op) { + DivInstrumentFM::Operator& opM = ins->fm.op[0]; + DivInstrumentFM::Operator& opC = ins->fm.op[1]; + ins->fm.ops = 2; + opM.mult = sbi_Mcharacteristics & 0xF; + opM.ksr = ((sbi_Mcharacteristics >> 4) & 0x1); + opM.sus = ((sbi_Mcharacteristics >> 5) & 0x1); + opM.vib = ((sbi_Mcharacteristics >> 6) & 0x1); + opM.am = ((sbi_Mcharacteristics >> 7) & 0x1); + opM.tl = sbi_Mscaling_output & 0x3F; + opM.ksl = ((sbi_Mscaling_output >> 6) & 0x3); + opM.ar = ((sbi_Meg_AD >> 4) & 0xF); + opM.dr = (sbi_Meg_AD & 0xF); + opM.rr = (sbi_Meg_SR & 0xF); + opM.sl = ((sbi_Meg_SR >> 4) & 0xF); + opM.ws = sbi_Mwave; + + ins->fm.alg = (sbi_FeedConnect & 0x1); + ins->fm.fb = ((sbi_FeedConnect >> 1) & 0x7); + + opC.mult = sbi_Ccharacteristics & 0xF; + opC.ksr = ((sbi_Ccharacteristics >> 4) & 0x1); + opC.sus = ((sbi_Ccharacteristics >> 5) & 0x1); + opC.vib = ((sbi_Ccharacteristics >> 6) & 0x1); + opC.am = ((sbi_Ccharacteristics >> 7) & 0x1); + opC.tl = sbi_Cscaling_output & 0x3F; + opC.ksl = ((sbi_Cscaling_output >> 6) & 0x3); + opC.ar = ((sbi_Ceg_AD >> 4) & 0xF); + opC.dr = (sbi_Ceg_AD & 0xF); + opC.rr = (sbi_Ceg_SR & 0xF); + opC.sl = ((sbi_Ceg_SR >> 4) & 0xF); + opC.ws = sbi_Cwave; + + // Ignore rest of file - rest is 'reserved padding'. + if (is_2op) { + reader.seek(0, SEEK_END); + } } - - DivInstrumentFM::Operator& opM = ins->fm.op[0]; - DivInstrumentFM::Operator& opC = ins->fm.op[1]; - opM.mult = sbi_Mcharacteristics & 0xF; - opM.tl = sbi_Mscaling_output & 0x3F; - //opM.rs = reader.readC(); - opM.ar = ((sbi_Meg_AD >> 4) & 0xF); - opM.dr = (sbi_Meg_AD & 0xF); - opM.rr = (sbi_Meg_SR & 0xF); - opM.sl = ((sbi_Meg_SR >> 4) & 0xF); - //opM.ssgEnv = reader.readC(); + if (is_4op || is_6op) { + // Operator placement is different so need to place in correct registers. + // Note: 6op is an unofficial extension of 4op SBIs by Darron Broad (Freq Monster 801). + // We'll only use the 4op portion here for pure OPL3. + DivInstrumentFM::Operator& opM = ins->fm.op[0]; + DivInstrumentFM::Operator& opC = ins->fm.op[2]; + DivInstrumentFM::Operator& opM4 = ins->fm.op[1]; + DivInstrumentFM::Operator& opC4 = ins->fm.op[3]; + ins->fm.ops = 4; - ins->fm.alg = (sbi_FeedConnect & 0x1); - ins->fm.fb = (sbi_FeedConnect >> 1) & 0x7; - - opC.mult = sbi_Ccharacteristics & 0xF; - opC.tl = sbi_Cscaling_output & 0x3F; - //opC.rs = ???; - opC.ar = ((sbi_Ceg_AD >> 4) & 0xF); - opC.dr = (sbi_Ceg_AD & 0xF); - opC.rr = (sbi_Ceg_SR & 0xF); - opC.sl = ((sbi_Ceg_SR >> 4) & 0xF); - //opC.ssgEnv = ???; - - if (is4op) { sbi_M4characteristics = reader.readC(); sbi_C4characteristics = reader.readC(); sbi_M4scaling_output = reader.readC(); sbi_C4scaling_output = reader.readC(); sbi_M4eg_AD = reader.readC(); + sbi_C4eg_AD = reader.readC(); sbi_M4eg_SR = reader.readC(); + sbi_C4eg_SR = reader.readC(); sbi_M4wave = reader.readC(); sbi_C4wave = reader.readC(); sbi_4opConnect = reader.readC(); - for (int j = 0; j < 6 && reader.tell() < reader.size(); ++j) { - reader.readC(); - } + + ins->fm.alg = (sbi_FeedConnect & 0x1) | ((sbi_4opConnect & 0x1) << 1); + opM.mult = sbi_Mcharacteristics & 0xF; + opM.ksr = ((sbi_Mcharacteristics >> 4) & 0x1); + opM.sus = ((sbi_Mcharacteristics >> 5) & 0x1); + opM.vib = ((sbi_Mcharacteristics >> 6) & 0x1); + opM.am = ((sbi_Mcharacteristics >> 7) & 0x1); + opM.tl = sbi_Mscaling_output & 0x3F; + opM.ksl = ((sbi_Mscaling_output >> 6) & 0x3); + opM.ar = ((sbi_Meg_AD >> 4) & 0xF); + opM.dr = (sbi_Meg_AD & 0xF); + opM.rr = (sbi_Meg_SR & 0xF); + opM.sl = ((sbi_Meg_SR >> 4) & 0xF); + opM.ws = sbi_Mwave; - ins->fm.alg |= (sbi_4opConnect & 0x1); - DivInstrumentFM::Operator& opM4 = ins->fm.op[2]; - DivInstrumentFM::Operator& opC4 = ins->fm.op[3]; - + opC.mult = sbi_Ccharacteristics & 0xF; + opC.ksr = ((sbi_Ccharacteristics >> 4) & 0x1); + opC.sus = ((sbi_Ccharacteristics >> 5) & 0x1); + opC.vib = ((sbi_Ccharacteristics >> 6) & 0x1); + opC.am = ((sbi_Ccharacteristics >> 7) & 0x1); + opC.tl = sbi_Cscaling_output & 0x3F; + opC.ksl = ((sbi_Cscaling_output >> 6) & 0x3); + opC.ar = ((sbi_Ceg_AD >> 4) & 0xF); + opC.dr = (sbi_Ceg_AD & 0xF); + opC.rr = (sbi_Ceg_SR & 0xF); + opC.sl = ((sbi_Ceg_SR >> 4) & 0xF); + opC.ws = sbi_Cwave; + opM4.mult = sbi_M4characteristics & 0xF; + opM4.ksr = ((sbi_M4characteristics >> 4) & 0x1); + opM4.sus = ((sbi_M4characteristics >> 5) & 0x1); + opM4.vib = ((sbi_M4characteristics >> 6) & 0x1); + opM4.am = ((sbi_M4characteristics >> 7) & 0x1); opM4.tl = sbi_M4scaling_output & 0x3F; - //opM4.rs = reader.readC(); + opM4.ksl = ((sbi_M4scaling_output >> 6) & 0x3); opM4.ar = ((sbi_M4eg_AD >> 4) & 0xF); opM4.dr = (sbi_M4eg_AD & 0xF); opM4.rr = (sbi_M4eg_SR & 0xF); opM4.sl = ((sbi_M4eg_SR >> 4) & 0xF); - //opM4.ssgEnv = reader.readC(); + opM4.ws = sbi_M4wave; opC4.mult = sbi_C4characteristics & 0xF; + opC4.ksr = ((sbi_C4characteristics >> 4) & 0x1); + opC4.sus = ((sbi_C4characteristics >> 5) & 0x1); + opC4.vib = ((sbi_C4characteristics >> 6) & 0x1); + opC4.am = ((sbi_C4characteristics >> 7) & 0x1); opC4.tl = sbi_C4scaling_output & 0x3F; - //opC4.rs = ???; + opC4.ksl = ((sbi_C4scaling_output >> 6) & 0x3); opC4.ar = ((sbi_C4eg_AD >> 4) & 0xF); opC4.dr = (sbi_C4eg_AD & 0xF); opC4.rr = (sbi_C4eg_SR & 0xF); opC4.sl = ((sbi_C4eg_SR >> 4) & 0xF); - //opC4.ssgEnv = ???; + opC4.ws = sbi_C4wave; + + // Ignore rest of file once we've read in all we need. + // Note: Freq Monster 801 adds a ton of other additional fields irrelevant to chip registers. + reader.seek(0, SEEK_END); } - } - catch (EndOfFileException& e) { + + } catch (EndOfFileException& e) { lastError = "premature end of file"; logE("premature end of file!\n"); delete ins; From 6df3d1e0df4db33e24d07b1bc148db4d93385e07 Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Sat, 19 Mar 2022 20:59:17 +0700 Subject: [PATCH 318/637] VERA: Implement PCM playback (again) --- src/engine/platform/sound/vera_psg.c | 7 +- src/engine/platform/vera.cpp | 113 +++++++++++++++++++++++---- src/engine/platform/vera.h | 6 +- 3 files changed, 101 insertions(+), 25 deletions(-) diff --git a/src/engine/platform/sound/vera_psg.c b/src/engine/platform/sound/vera_psg.c index f61bd26a2..afdf69cec 100644 --- a/src/engine/platform/sound/vera_psg.c +++ b/src/engine/platform/sound/vera_psg.c @@ -54,11 +54,8 @@ render(struct VERA_PSG* psg, int16_t *left, int16_t *right) { int l = 0; int r = 0; - // TODO this is a currently speculated noise generation - // as the hardware and sources for it are not out in the public - // and the official emulator just uses rand() - psg->noiseOut=((psg->noiseOut<<1)|(psg->noiseState&1))&63; - psg->noiseState=(psg->noiseState<<1)|(((psg->noiseState>>1)^(psg->noiseState>>2)^(psg->noiseState>>4)^(psg->noiseState>>15))&1); + psg->noiseOut=((psg->noiseOut<<1)|(psg->noiseState&1))&63; + psg->noiseState=(psg->noiseState<<1)|(((psg->noiseState>>1)^(psg->noiseState>>2)^(psg->noiseState>>4)^(psg->noiseState>>15))&1); for (int i = 0; i < 16; i++) { struct VERAChannel *ch = &psg->channels[i]; diff --git a/src/engine/platform/vera.cpp b/src/engine/platform/vera.cpp index e63ac8bec..a4d76aa08 100644 --- a/src/engine/platform/vera.cpp +++ b/src/engine/platform/vera.cpp @@ -30,7 +30,10 @@ extern "C" { #define rWrite(c,a,d) {regPool[(c)*4+(a)]=(d); psg_writereg(psg,((c)*4+(a)),(d));} #define rWriteLo(c,a,d) rWrite(c,a,(regPool[(c)*4+(a)]&(~0x3f))|((d)&0x3f)) #define rWriteHi(c,a,d) rWrite(c,a,(regPool[(c)*4+(a)]&(~0xc0))|(((d)<<6)&0xc0)) -#define rWriteFIFOVol(d) rWrite(16,0,(regPool[64]&(~0x3f))|((d)&0x3f)) +#define rWritePCMCtrl(d) {regPool[64]=(d); pcm_write_ctrl(pcm,d);} +#define rWritePCMRate(d) {regPool[65]=(d); pcm_write_rate(pcm,d);} +#define rWritePCMData(d) {regPool[66]=(d); pcm_write_fifo(pcm,d);} +#define rWritePCMVol(d) rWritePCMCtrl((regPool[64]&(~0x3f))|((d)&0x3f)) const char* regCheatSheetVERA[]={ "CHxFreq", "00+x*4", @@ -39,6 +42,7 @@ const char* regCheatSheetVERA[]={ "AUDIO_CTRL", "40", "AUDIO_RATE", "41", + "AUDIO_DATA", "42", NULL }; @@ -59,10 +63,63 @@ const char* DivPlatformVERA::getEffectName(unsigned char effect) { return NULL; } -// TODO: wire up PCM. void DivPlatformVERA::acquire(short* bufL, short* bufR, size_t start, size_t len) { - psg_render(psg,bufL+start,bufR+start,len); - pcm_render(pcm,bufL+start,bufR+start,len); + // both PSG part and PCM part output a full 16-bit range, putting bufL/R + // argument right into both could cause an overflow + short buf[4][128]; + size_t pos=start; + DivSample* s=parent->getSample(chan[16].pcm.sample); + while (len>0) { + if (s->samples>0) { + while (pcm_is_fifo_almost_empty(pcm)) { + short tmp_l=0; + short tmp_r=0; + if (!isMuted[16]) { + // TODO stereo samples once DivSample has a support for it + if (chan[16].pcm.depth16) { + tmp_l=s->data16[chan[16].pcm.pos]; + tmp_r=tmp_l; + } else { + tmp_l=s->data8[chan[16].pcm.pos]; + tmp_r=tmp_l; + } + if (!(chan[16].pan&1)) tmp_l=0; + if (!(chan[16].pan&2)) tmp_r=0; + } + if (chan[16].pcm.depth16) { + rWritePCMData(tmp_l&0xff); + rWritePCMData((tmp_l>>8)&0xff); + rWritePCMData(tmp_r&0xff); + rWritePCMData((tmp_r>>8)&0xff); + } else { + rWritePCMData(tmp_l&0xff); + rWritePCMData(tmp_r&0xff); + } + chan[16].pcm.pos++; + if (chan[16].pcm.pos>=s->samples) { + if (s->loopStart>=0 && s->loopStart<=(int)s->samples) { + chan[16].pcm.pos=s->loopStart; + } else { + chan[16].pcm.sample=-1; + break; + } + } + } + } else { + // just let the buffer run out + chan[16].pcm.sample=-1; + } + int curLen=MIN(len,128); + memset(buf,0,sizeof(buf)); + psg_render(psg,buf[0],buf[1],curLen); + pcm_render(pcm,buf[2],buf[3],curLen); + for (int i=0; icalcFreq(chan[16].baseFreq,chan[16].pitch,false,8); if (chan[16].freq>128) chan[16].freq=128; - rWrite(16,1,chan[16].freq&0xff); + rWritePCMRate(chan[16].freq&0xff); chan[16].freqChanged=false; } } @@ -170,12 +227,21 @@ int DivPlatformVERA::dispatch(DivCommand c) { if(c.chan<16) { rWriteLo(c.chan,2,chan[c.chan].vol) } else { - chan[c.chan].pcm.sample=parent->getIns(chan[16].ins)->amiga.initSample; - if (chan[c.chan].pcm.sample<0 || chan[c.chan].pcm.sample>=parent->song.sampleLen) { - chan[c.chan].pcm.sample=-1; + chan[16].pcm.sample=parent->getIns(chan[16].ins)->amiga.initSample; + if (chan[16].pcm.sample<0 || chan[16].pcm.sample>=parent->song.sampleLen) { + chan[16].pcm.sample=-1; } chan[16].pcm.pos=0; - rWriteFIFOVol(chan[c.chan].vol); + DivSample* s=parent->getSample(chan[16].pcm.sample); + unsigned char ctrl=0x90|chan[16].vol; // always stereo + if (s->depth==16) { + chan[16].pcm.depth16=true; + ctrl|=0x20; + } else { + chan[16].pcm.depth16=false; + if (s->depth!=8) chan[16].pcm.sample=-1; + } + rWritePCMCtrl(ctrl); } if (c.value!=DIV_NOTE_NULL) { chan[c.chan].baseFreq=calcNoteFreq(c.chan,c.value); @@ -191,8 +257,8 @@ int DivPlatformVERA::dispatch(DivCommand c) { rWriteLo(c.chan,2,0) } else { chan[16].pcm.sample=-1; - rWriteFIFOVol(0); - rWrite(16,1,0); + rWritePCMCtrl(0x80); + rWritePCMRate(0); } chan[c.chan].std.init(NULL); break; @@ -211,7 +277,7 @@ int DivPlatformVERA::dispatch(DivCommand c) { } else { tmp=c.value&0x0f; chan[c.chan].vol=tmp; - rWriteFIFOVol(tmp); + rWritePCMVol(tmp); } break; case DIV_CMD_GET_VOLUME: @@ -296,7 +362,7 @@ unsigned char* DivPlatformVERA::getRegisterPool() { } int DivPlatformVERA::getRegisterPoolSize() { - return 66; + return 67; } void DivPlatformVERA::muteChannel(int ch, bool mute) { @@ -317,11 +383,24 @@ void DivPlatformVERA::notifyInsDeletion(void* ins) { } void DivPlatformVERA::poke(unsigned int addr, unsigned short val) { - regPool[addr] = (unsigned char)val; + switch (addr) { + case 64: + rWritePCMCtrl((unsigned char)val); + break; + case 65: + rWritePCMRate((unsigned char)val); + break; + case 66: + rWritePCMData((unsigned char)val); + break; + default: + rWrite(0,addr,(unsigned char)val); + break; + } } void DivPlatformVERA::poke(std::vector& wlist) { - for (auto &i: wlist) regPool[i.addr] = (unsigned char)i.val; + for (auto &i: wlist) poke(i.addr,i.val); } int DivPlatformVERA::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { diff --git a/src/engine/platform/vera.h b/src/engine/platform/vera.h index e1369301b..a3773cccb 100644 --- a/src/engine/platform/vera.h +++ b/src/engine/platform/vera.h @@ -39,17 +39,17 @@ class DivPlatformVERA: public DivDispatch { struct PCMChannel { int sample; - int out_l, out_r; unsigned pos; unsigned len; unsigned char freq; - PCMChannel(): sample(-1), out_l(0), out_r(0), pos(0), len(0), freq(0) {} + bool depth16; + PCMChannel(): sample(-1), pos(0), len(0), freq(0), depth16(false) {} } pcm; Channel(): freq(0), baseFreq(0), pitch(0), note(0), ins(-1), pan(0), active(false), freqChanged(false), inPorta(false), vol(0), outVol(0), accum(0), noiseval(0) {} }; Channel chan[17]; bool isMuted[17]; - unsigned char regPool[66]; + unsigned char regPool[67]; struct VERA_PSG* psg; struct VERA_PCM* pcm; From efdedd104466fae856a801a13849cb583259645a Mon Sep 17 00:00:00 2001 From: James Alan Nguyen Date: Sun, 20 Mar 2022 01:02:34 +1100 Subject: [PATCH 319/637] Fix typo --- src/gui/gui.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 03a679a65..033ae2f03 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4660,7 +4660,7 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { "Load Instrument", {"compatible files", "*.fui *.dmp *.tfi *.vgi, *.sbi", "all files", ".*"}, - "compatible files{.fui,.dmp,.tfi,.vgi.sbi},.*", + "compatible files{.fui,.dmp,.tfi,.vgi,.sbi},.*", workingDirIns, dpiScale ); From 53968891955ff66108c35d32f2c680204d0dd70a Mon Sep 17 00:00:00 2001 From: James Alan Nguyen Date: Sun, 20 Mar 2022 01:04:38 +1100 Subject: [PATCH 320/637] Missed feedback for 4op block --- src/engine/engine.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 2345f0d68..86fa0ec36 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -1777,6 +1777,7 @@ bool DivEngine::addInstrumentFromFile(const char* path) { sbi_4opConnect = reader.readC(); ins->fm.alg = (sbi_FeedConnect & 0x1) | ((sbi_4opConnect & 0x1) << 1); + ins->fm.fb = ((sbi_FeedConnect >> 1) & 0x7); opM.mult = sbi_Mcharacteristics & 0xF; opM.ksr = ((sbi_Mcharacteristics >> 4) & 0x1); From 629049dea0ce5af426849fb1b09298e4222fd193 Mon Sep 17 00:00:00 2001 From: James Alan Nguyen Date: Sun, 20 Mar 2022 05:50:22 +1100 Subject: [PATCH 321/637] Redundant if-statement --- src/engine/engine.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 86fa0ec36..835636bdd 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -1749,9 +1749,7 @@ bool DivEngine::addInstrumentFromFile(const char* path) { opC.ws = sbi_Cwave; // Ignore rest of file - rest is 'reserved padding'. - if (is_2op) { - reader.seek(0, SEEK_END); - } + reader.seek(0, SEEK_END); } if (is_4op || is_6op) { From 67ca88b4789abed26296c5a9e4e24ecf83a50c6f Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 19 Mar 2022 16:14:11 -0500 Subject: [PATCH 322/637] GUI: prepare for sample select/draw --- src/gui/gui.cpp | 6 +++++- src/gui/gui.h | 5 ++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 78deccd3e..dde870bb5 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -7104,7 +7104,11 @@ FurnaceGUI::FurnaceGUI(): sampleSelStart(-1), sampleSelEnd(-1), sampleDragActive(false), - sampleDragMode(false) { + sampleDragMode(false), + sampleDrag16(false), + sampleDragTarget(NULL), + sampleDragStart(0,0), + sampleDragAreaSize(0,0) { // octave 1 /* diff --git a/src/gui/gui.h b/src/gui/gui.h index 568ea8ed0..0edbea956 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -743,7 +743,10 @@ class FurnaceGUI { int resampleStrat; float amplifyVol; int sampleSelStart, sampleSelEnd; - bool sampleDragActive, sampleDragMode; + bool sampleDragActive, sampleDragMode, sampleDrag16; + void* sampleDragTarget; + ImVec2 sampleDragStart; + ImVec2 sampleDragAreaSize; // visualizer float keyHit[DIV_MAX_CHANS]; From 607a347b7a92c3771c0e6d24ee88034cc6ce53fb Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 19 Mar 2022 21:59:19 -0500 Subject: [PATCH 323/637] X1-010: don't pan in mono mode --- src/engine/platform/x1_010.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/engine/platform/x1_010.cpp b/src/engine/platform/x1_010.cpp index d70a31df1..6834986f6 100644 --- a/src/engine/platform/x1_010.cpp +++ b/src/engine/platform/x1_010.cpp @@ -669,6 +669,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { } break; case DIV_CMD_PANNING: { + if (!stereo) break; if (chan[c.chan].pan!=c.value) { chan[c.chan].pan=c.value; if (!isMuted[c.chan]) { From 4593e33064595bd7eea89c8353fc624ca3eaa4f0 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 19 Mar 2022 22:02:21 -0500 Subject: [PATCH 324/637] GUI: fix X1-010 flags --- src/gui/gui.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index dde870bb5..600793598 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -5833,16 +5833,16 @@ bool FurnaceGUI::loop() { case DIV_SYSTEM_X1_010: { ImGui::Text("Clock rate:"); if (ImGui::RadioButton("16MHz (Seta 1)",(flags&15)==0)) { - e->setSysFlags(i,(flags&(~16))|0,restart); + e->setSysFlags(i,(flags&(~15))|0,restart); updateWindowTitle(); } if (ImGui::RadioButton("16.67MHz (Seta 2)",(flags&15)==1)) { - e->setSysFlags(i,(flags&(~16))|1,restart); + e->setSysFlags(i,(flags&(~15))|1,restart); updateWindowTitle(); } bool x1_010Stereo=flags&16; if (ImGui::Checkbox("Stereo",&x1_010Stereo)) { - e->setSysFlags(i,(flags&(~15))|(x1_010Stereo<<4),restart); + e->setSysFlags(i,(flags&(~16))|(x1_010Stereo<<4),restart); updateWindowTitle(); } break; From 90a18611cd5bfa0441e3eec92561f89a1dd9e753 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 19 Mar 2022 22:03:12 -0500 Subject: [PATCH 325/637] sys flags: don't play when song is stopped --- src/engine/engine.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 3352a8737..bf18cb5a4 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -2211,7 +2211,7 @@ void DivEngine::setSysFlags(int system, unsigned int flags, bool restart) { song.systemFlags[system]=flags; disCont[system].dispatch->setFlags(song.systemFlags[system]); disCont[system].setRates(got.rate); - if (restart) { + if (restart && isPlaying()) { playSub(false); } isBusy.unlock(); From cbe74b26adc38a633188f07967c18a1ef639bf78 Mon Sep 17 00:00:00 2001 From: James Alan Nguyen Date: Sun, 20 Mar 2022 17:12:03 +1100 Subject: [PATCH 326/637] More #79 - Add S3I Adlib instrument support. Also fix SafeReader `SEEK_CUR` handling (wasn't used at all). --- src/engine/engine.cpp | 82 +++++++++++++++++++++++++++++++++++++++ src/engine/safeReader.cpp | 4 +- src/gui/gui.cpp | 5 ++- 3 files changed, 87 insertions(+), 4 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 835636bdd..1f52ad166 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -1223,6 +1223,7 @@ enum DivInsFormats { DIV_INSFORMAT_VGI, DIV_INSFORMAT_FTI, DIV_INSFORMAT_BTI, + DIV_INSFORMAT_S3I, DIV_INSFORMAT_SBI, }; @@ -1343,6 +1344,8 @@ bool DivEngine::addInstrumentFromFile(const char* path) { format=DIV_INSFORMAT_FTI; } else if (extS==String(".bti")) { format=DIV_INSFORMAT_BTI; + } else if (extS==String(".s3i")) { + format = DIV_INSFORMAT_S3I; } else if (extS==String(".sbi")) { format = DIV_INSFORMAT_SBI; } @@ -1675,6 +1678,85 @@ bool DivEngine::addInstrumentFromFile(const char* path) { break; case DIV_INSFORMAT_BTI: break; + case DIV_INSFORMAT_S3I: + try { + reader.seek(0, SEEK_SET); + + uint8_t s3i_type = reader.readC(); + + if (s3i_type >= 2) { + ins->type = DIV_INS_OPL; + // skip internal filename - we'll use the long name description + reader.seek(12, SEEK_CUR); + + // skip reserved bytes + reader.seek(3, SEEK_CUR); + + // 12-byte opl value + uint8_t s3i_Mcharacteristics = reader.readC(); + uint8_t s3i_Ccharacteristics = reader.readC(); + uint8_t s3i_Mscaling_output = reader.readC(); + uint8_t s3i_Cscaling_output = reader.readC(); + uint8_t s3i_Meg_AD = reader.readC(); + uint8_t s3i_Ceg_AD = reader.readC(); + uint8_t s3i_Meg_SR = reader.readC(); + uint8_t s3i_Ceg_SR = reader.readC(); + uint8_t s3i_Mwave = reader.readC(); + uint8_t s3i_Cwave = reader.readC(); + uint8_t s3i_FeedConnect = reader.readC(); + + DivInstrumentFM::Operator& opM = ins->fm.op[0]; + DivInstrumentFM::Operator& opC = ins->fm.op[1]; + ins->fm.ops = 2; + opM.mult = s3i_Mcharacteristics & 0xF; + opM.ksr = ((s3i_Mcharacteristics >> 4) & 0x1); + opM.sus = ((s3i_Mcharacteristics >> 5) & 0x1); + opM.vib = ((s3i_Mcharacteristics >> 6) & 0x1); + opM.am = ((s3i_Mcharacteristics >> 7) & 0x1); + opM.tl = s3i_Mscaling_output & 0x3F; + opM.ksl = ((s3i_Mscaling_output >> 6) & 0x3); + opM.ar = ((s3i_Meg_AD >> 4) & 0xF); + opM.dr = (s3i_Meg_AD & 0xF); + opM.rr = (s3i_Meg_SR & 0xF); + opM.sl = ((s3i_Meg_SR >> 4) & 0xF); + opM.ws = s3i_Mwave; + + ins->fm.alg = (s3i_FeedConnect & 0x1); + ins->fm.fb = ((s3i_FeedConnect >> 1) & 0x7); + + opC.mult = s3i_Ccharacteristics & 0xF; + opC.ksr = ((s3i_Ccharacteristics >> 4) & 0x1); + opC.sus = ((s3i_Ccharacteristics >> 5) & 0x1); + opC.vib = ((s3i_Ccharacteristics >> 6) & 0x1); + opC.am = ((s3i_Ccharacteristics >> 7) & 0x1); + opC.tl = s3i_Cscaling_output & 0x3F; + opC.ksl = ((s3i_Cscaling_output >> 6) & 0x3); + opC.ar = ((s3i_Ceg_AD >> 4) & 0xF); + opC.dr = (s3i_Ceg_AD & 0xF); + opC.rr = (s3i_Ceg_SR & 0xF); + opC.sl = ((s3i_Ceg_SR >> 4) & 0xF); + opC.ws = s3i_Cwave; + + // Skip more stuff we don't need + reader.seek(21, SEEK_CUR); + } else { + logE("S3I PCM samples currently not supported."); + } + ins->name = reader.readString(28); + int s3i_signature = reader.readI(); + + if (s3i_signature != 0x49524353) { + logW("S3I signature invalid."); + }; + } + catch (EndOfFileException& e) { + lastError = "premature end of file"; + logE("premature end of file!\n"); + delete ins; + delete[] buf; + return false; + } + break; case DIV_INSFORMAT_SBI: try { reader.seek(0, SEEK_SET); diff --git a/src/engine/safeReader.cpp b/src/engine/safeReader.cpp index b2b955510..9b096ef79 100644 --- a/src/engine/safeReader.cpp +++ b/src/engine/safeReader.cpp @@ -30,14 +30,14 @@ bool SafeReader::seek(ssize_t where, int whence) { curSeek=where; break; case SEEK_CUR: { - ssize_t finalSeek=len+where; + ssize_t finalSeek=curSeek+where; if (finalSeek<0) return false; if (finalSeek>(ssize_t)len) return false; curSeek=finalSeek; break; } case SEEK_END: { - ssize_t finalSeek=len-where; + ssize_t finalSeek=curSeek-where; if (finalSeek<0) return false; if (finalSeek>(ssize_t)len) return false; curSeek=finalSeek; diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 033ae2f03..ae176447e 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4658,9 +4658,9 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { if (!dirExists(workingDirIns)) workingDirIns=getHomeDir(); hasOpened=fileDialog->openLoad( "Load Instrument", - {"compatible files", "*.fui *.dmp *.tfi *.vgi, *.sbi", + {"compatible files", "*.fui *.dmp *.tfi *.vgi *.s3i *.sbi", "all files", ".*"}, - "compatible files{.fui,.dmp,.tfi,.vgi,.sbi},.*", + "compatible files{.fui,.dmp,.tfi,.vgi,.s3i,.sbi},.*", workingDirIns, dpiScale ); @@ -6886,6 +6886,7 @@ bool FurnaceGUI::init() { ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".tfi",ImVec4(1.0f,0.5f,0.5f,1.0f),ICON_FA_FILE); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".vgi",ImVec4(1.0f,0.5f,0.5f,1.0f),ICON_FA_FILE); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".s3i",ImVec4(1.0f,0.5f,0.5f,1.0f),ICON_FA_FILE); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".sbi",ImVec4(1.0f,0.5f,0.5f,1.0f),ICON_FA_FILE); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".fti",ImVec4(1.0f,0.5f,0.5f,1.0f),ICON_FA_FILE); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".bti",ImVec4(1.0f,0.5f,0.5f,1.0f),ICON_FA_FILE); From 5e005262a5832fc573364963350452b032b86a52 Mon Sep 17 00:00:00 2001 From: James Alan Nguyen Date: Sun, 20 Mar 2022 17:18:54 +1100 Subject: [PATCH 327/637] `SEEK_END` is correct - revert accidental change there. --- src/engine/safeReader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/safeReader.cpp b/src/engine/safeReader.cpp index 9b096ef79..9add1b5aa 100644 --- a/src/engine/safeReader.cpp +++ b/src/engine/safeReader.cpp @@ -37,7 +37,7 @@ bool SafeReader::seek(ssize_t where, int whence) { break; } case SEEK_END: { - ssize_t finalSeek=curSeek-where; + ssize_t finalSeek=len-where; if (finalSeek<0) return false; if (finalSeek>(ssize_t)len) return false; curSeek=finalSeek; From 03cb910e93322ef662182e9fc19baee413e15558 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 20 Mar 2022 03:14:00 -0500 Subject: [PATCH 328/637] GUI: sample resampling --- CMakeLists.txt | 1 + src/engine/filter.cpp | 88 +++++++++++ src/engine/filter.h | 43 +++++ src/engine/sample.cpp | 345 ++++++++++++++++++++++++++++++++++++++++- src/engine/sample.h | 27 ++++ src/gui/sampleEdit.cpp | 17 +- 6 files changed, 517 insertions(+), 4 deletions(-) create mode 100644 src/engine/filter.cpp create mode 100644 src/engine/filter.h diff --git a/CMakeLists.txt b/CMakeLists.txt index dd63fec2d..6116dba8c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -282,6 +282,7 @@ src/engine/config.cpp src/engine/dispatchContainer.cpp src/engine/engine.cpp src/engine/fileOps.cpp +src/engine/filter.cpp src/engine/instrument.cpp src/engine/macroInt.cpp src/engine/pattern.cpp diff --git a/src/engine/filter.cpp b/src/engine/filter.cpp new file mode 100644 index 000000000..729c8caa1 --- /dev/null +++ b/src/engine/filter.cpp @@ -0,0 +1,88 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#define _USE_MATH_DEFINES +#include +#include "filter.h" +#include "../ta-log.h" + +float* DivFilterTables::cubicTable=NULL; +float* DivFilterTables::sincTable=NULL; +float* DivFilterTables::sincIntegralTable=NULL; + +// portions from Schism Tracker (scripts/lutgen.c) +// licensed under same license as this program. +float* DivFilterTables::getCubicTable() { + if (cubicTable==NULL) { + logD("initializing cubic spline table.\n"); + cubicTable=new float[4096]; + + for (int i=0; i<1024; i++) { + float x=(float)i/1024.0; + cubicTable[(i<<2)]=-0.5*pow(x,3)+1.0*pow(x,2)-0.5*x; + cubicTable[1+(i<<2)]=1.5*pow(x,3)-2.5*pow(x,2)+1.0; + cubicTable[2+(i<<2)]=-1.5*pow(x,3)+2.0*pow(x,2)+0.5*x; + cubicTable[3+(i<<2)]=0.5*pow(x,3)-0.5*pow(x,2); + } + } + return cubicTable; +} + +float* DivFilterTables:: getSincTable() { + if (sincTable==NULL) { + logD("initializing sinc table.\n"); + sincTable=new float[65536]; + + sincTable[0]=1.0f; + for (int i=1; i<65536; i++) { + int mapped=((i&8191)<<3)|(i>>13); + double x=(double)i*M_PI/8192.0; + sincTable[mapped]=sin(x)/x; + } + + for (int i=0; i<65536; i++) { + int mapped=((i&8191)<<3)|(i>>13); + sincTable[mapped]*=pow(cos(M_PI*(double)i/131072.0),2.0); + } + } + return sincTable; +} + +float* DivFilterTables::getSincIntegralTable() { + if (sincIntegralTable==NULL) { + logD("initializing sinc integral table.\n"); + sincIntegralTable=new float[65536]; + + sincIntegralTable[0]=-0.5f; + for (int i=1; i<65536; i++) { + int mapped=((i&8191)<<3)|(i>>13); + int mappedPrev=(((i-1)&8191)<<3)|((i-1)>>13); + double x=(double)i*M_PI/8192.0; + double sinc=sin(x)/x; + sincIntegralTable[mapped]=sincIntegralTable[mappedPrev]+(sinc/8192.0); + } + + for (int i=0; i<65536; i++) { + int mapped=((i&8191)<<3)|(i>>13); + sincIntegralTable[mapped]*=pow(cos(M_PI*(double)i/131072.0),2.0); + } + } + return sincIntegralTable; +} \ No newline at end of file diff --git a/src/engine/filter.h b/src/engine/filter.h new file mode 100644 index 000000000..974a448e3 --- /dev/null +++ b/src/engine/filter.h @@ -0,0 +1,43 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +class DivFilterTables { + public: + static float* cubicTable; + static float* sincTable; + static float* sincIntegralTable; + + /** + * get a 1024x4 cubic spline table. + * @return the table. + */ + static float* getCubicTable(); + + /** + * get a 8192x8 one-side sine-windowed sinc table. + * @return the table. + */ + static float* getSincTable(); + + /** + * get a 8192x8 one-side sine-windowed sinc integral table. + * @return the table. + */ + static float* getSincIntegralTable(); +}; \ No newline at end of file diff --git a/src/engine/sample.cpp b/src/engine/sample.cpp index 53fa42fe2..8b0c78e56 100644 --- a/src/engine/sample.cpp +++ b/src/engine/sample.cpp @@ -19,9 +19,10 @@ #include "sample.h" #include "../ta-log.h" +#include #include #include -#include +#include "filter.h" extern "C" { #include "../../extern/adpcm/bs_codec.h" @@ -178,6 +179,348 @@ bool DivSample::resize(unsigned int count) { return false; } +#define RESAMPLE_BEGIN \ + if (samples<1) return true; \ + int finalCount=(double)samples*(r/(double)rate); \ + signed char* oldData8=data8; \ + short* oldData16=data16; \ + if (depth==16) { \ + if (data16!=NULL) { \ + data16=NULL; \ + initInternal(16,finalCount); \ + } \ + } else if (depth==8) { \ + if (data8!=NULL) { \ + data8=NULL; \ + initInternal(8,finalCount); \ + } \ + } else { \ + return false; \ + } + +#define RESAMPLE_END \ + samples=finalCount; \ + if (depth==16) { \ + delete[] oldData16; \ + } else if (depth==8) { \ + delete[] oldData8; \ + } + +bool DivSample::resampleNone(double r) { + RESAMPLE_BEGIN; + + if (depth==16) { + for (int i=0; i=samples) { + data16[i]=0; + } else { + data16[i]=oldData16[pos]; + } + } + } else if (depth==8) { + for (int i=0; i=samples) { + data8[i]=0; + } else { + data8[i]=oldData8[pos]; + } + } + } + + rate=r; + + RESAMPLE_END; + return true; +} + +bool DivSample::resampleLinear(double r) { + RESAMPLE_BEGIN; + + double posFrac=0; + unsigned int posInt=0; + double factor=(double)rate/r; + + if (depth==16) { + for (int i=0; i=samples)?0:oldData16[posInt]; + short s2=(posInt+1>=samples)?((loopStart>=0 && loopStart<(int)samples)?oldData16[loopStart]:0):oldData16[posInt+1]; + + data16[i]=s1+(float)(s2-s1)*posFrac; + + posFrac+=factor; + while (posFrac>=1.0) { + posFrac-=1.0; + posInt++; + } + } + } else if (depth==8) { + for (int i=0; i=samples)?0:oldData8[posInt]; + short s2=(posInt+1>=samples)?((loopStart>=0 && loopStart<(int)samples)?oldData8[loopStart]:0):oldData8[posInt+1]; + + data8[i]=s1+(float)(s2-s1)*posFrac; + + posFrac+=factor; + while (posFrac>=1.0) { + posFrac-=1.0; + posInt++; + } + } + } + + rate=r; + + RESAMPLE_END; + return true; +} + +bool DivSample::resampleCubic(double r) { + RESAMPLE_BEGIN; + + double posFrac=0; + unsigned int posInt=0; + double factor=(double)rate/r; + float* cubicTable=DivFilterTables::getCubicTable(); + + if (depth==16) { + for (int i=0; i=samples)?0:oldData16[posInt]; + float s2=(posInt+1>=samples)?((loopStart>=0 && loopStart<(int)samples)?oldData16[loopStart]:0):oldData16[posInt+1]; + float s3=(posInt+2>=samples)?((loopStart>=0 && loopStart<(int)samples)?oldData16[loopStart]:0):oldData16[posInt+2]; + + float result=s0*t[0]+s1*t[1]+s2*t[2]+s3*t[3]; + if (result<-32768) result=-32768; + if (result>32767) result=32767; + data16[i]=result; + + posFrac+=factor; + while (posFrac>=1.0) { + posFrac-=1.0; + posInt++; + } + } + } else if (depth==8) { + for (int i=0; i=samples)?0:oldData8[posInt]; + float s2=(posInt+1>=samples)?((loopStart>=0 && loopStart<(int)samples)?oldData8[loopStart]:0):oldData8[posInt+1]; + float s3=(posInt+2>=samples)?((loopStart>=0 && loopStart<(int)samples)?oldData8[loopStart]:0):oldData8[posInt+2]; + + float result=s0*t[0]+s1*t[1]+s2*t[2]+s3*t[3]; + if (result<-128) result=-128; + if (result>127) result=127; + data8[i]=result; + + posFrac+=factor; + while (posFrac>=1.0) { + posFrac-=1.0; + posInt++; + } + } + } + + rate=r; + + RESAMPLE_END; + return true; +} + +bool DivSample::resampleBlep(double r) { + RESAMPLE_BEGIN; + + double posFrac=0; + unsigned int posInt=0; + double factor=r/(double)rate; + float* sincITable=DivFilterTables::getSincIntegralTable(); + float s[16]; + + memset(s,0,16*sizeof(float)); + + if (depth==16) { + memset(data16,0,finalCount*sizeof(short)); + for (int i=0; i32767) result=32767; + data16[i]=result; + } + + posFrac+=1.0; + while (posFrac>=1.0) { + unsigned int n=((unsigned int)(posFrac*8192.0))&8191; + posFrac-=factor; + posInt++; + + float* t1=&sincITable[(8191-n)<<3]; + float* t2=&sincITable[n<<3]; + float delta=oldData16[posInt]-oldData16[posInt-1]; + + for (int j=0; j<8; j++) { + if (i-j>0) { + float result=data16[i-j]+t1[j]*-delta; + if (result<-32768) result=-32768; + if (result>32767) result=32767; + data16[i-j]=result; + } + if (i+j+132767) result=32767; + data16[i+j+1]=result; + } + } + } + } + } else if (depth==8) { + memset(data8,0,finalCount*sizeof(short)); + for (int i=0; i127) result=127; + data8[i]=result; + } + + posFrac+=1.0; + while (posFrac>=1.0) { + unsigned int n=((unsigned int)(posFrac*8192.0))&8191; + posFrac-=factor; + posInt++; + + float* t1=&sincITable[(8191-n)<<3]; + float* t2=&sincITable[n<<3]; + float delta=oldData8[posInt]-oldData8[posInt-1]; + + for (int j=0; j<8; j++) { + if (i-j>0) { + float result=data8[i-j]+t1[j]*-delta; + if (result<-128) result=-128; + if (result>127) result=127; + data8[i-j]=result; + } + if (i+j+1127) result=127; + data8[i+j+1]=result; + } + } + } + } + } + + rate=r; + + RESAMPLE_END; + return true; +} + +bool DivSample::resampleSinc(double r) { + RESAMPLE_BEGIN; + + double posFrac=0; + unsigned int posInt=0; + double factor=(double)rate/r; + float* sincTable=DivFilterTables::getSincTable(); + float s[16]; + + memset(s,0,16*sizeof(float)); + + if (depth==16) { + for (int i=0; i32767) result=32767; + if (i>=8) { + data16[i-8]=result; + } + + posFrac+=factor; + while (posFrac>=1.0) { + posFrac-=1.0; + posInt++; + + for (int j=0; j<15; j++) s[j]=s[j+1]; + s[15]=(posInt>=samples)?0:oldData16[posInt]; + } + } + } else if (depth==8) { + for (int i=0; i32767) result=32767; + if (i>=8) { + data8[i-8]=result; + } + + posFrac+=factor; + while (posFrac>=1.0) { + posFrac-=1.0; + posInt++; + + for (int j=0; j<15; j++) s[j]=s[j+1]; + s[15]=(posInt>=samples)?0:oldData8[posInt]; + } + } + } + + rate=r; + + RESAMPLE_END; + return true; +} + +bool DivSample::resample(double r, int filter) { + if (depth!=8 && depth!=16) return false; + switch (filter) { + case DIV_RESAMPLE_NONE: + return resampleNone(r); + break; + case DIV_RESAMPLE_LINEAR: + return resampleLinear(r); + break; + case DIV_RESAMPLE_CUBIC: + return resampleCubic(r); + break; + case DIV_RESAMPLE_BLEP: + return resampleBlep(r); + break; + case DIV_RESAMPLE_SINC: + return resampleSinc(r); + break; + case DIV_RESAMPLE_BEST: + if (r>rate) { + return resampleSinc(r); + } else { + return resampleBlep(r); + } + break; + } + return false; +} + void DivSample::render() { // step 1: convert to 16-bit if needed if (depth!=16) { diff --git a/src/engine/sample.h b/src/engine/sample.h index 026ca290e..4737a00d8 100644 --- a/src/engine/sample.h +++ b/src/engine/sample.h @@ -19,6 +19,15 @@ #include "../ta-utils.h" +enum DivResampleFilters { + DIV_RESAMPLE_NONE=0, + DIV_RESAMPLE_LINEAR, + DIV_RESAMPLE_CUBIC, + DIV_RESAMPLE_BLEP, + DIV_RESAMPLE_SINC, + DIV_RESAMPLE_BEST +}; + struct DivSample { String name; int rate, centerRate, loopStart, loopOffP; @@ -53,6 +62,15 @@ struct DivSample { unsigned int samples; + /** + * @warning DO NOT USE - internal functions + */ + bool resampleNone(double rate); + bool resampleLinear(double rate); + bool resampleCubic(double rate); + bool resampleBlep(double rate); + bool resampleSinc(double rate); + /** * save this sample to a file. * @param path a path. @@ -84,6 +102,15 @@ struct DivSample { */ bool resize(unsigned int count); + /** + * change the sample rate. + * @warning do not attempt to resample outside of a synchronized block! + * @param rate number of samples. + * @param filter the interpolation filter. + * @return whether it was successful. + */ + bool resample(double rate, int filter); + /** * initialize the rest of sample formats for this sample. */ diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index 07afe23ff..5f38706b1 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -159,7 +159,9 @@ void FurnaceGUI::drawSampleEdit() { } if (ImGui::Button("Resize")) { e->synchronized([this,sample]() { - sample->resize(resizeSize); + if (!sample->resize(resizeSize)) { + showError("couldn't resize! make sure your sample is 8 or 16-bit."); + } e->renderSamples(); }); updateSampleTex=true; @@ -202,7 +204,16 @@ void FurnaceGUI::drawSampleEdit() { if (resampleTarget>96000) resampleTarget=96000; } ImGui::Combo("Filter",&resampleStrat,resampleStrats,6); - ImGui::Button("Resample"); + if (ImGui::Button("Resample")) { + e->synchronized([this,sample]() { + if (!sample->resample(resampleTarget,resampleStrat)) { + showError("couldn't resample! make sure your sample is 8 or 16-bit."); + } + e->renderSamples(); + }); + updateSampleTex=true; + ImGui::CloseCurrentPopup(); + } ImGui::EndPopup(); } else { resampleTarget=sample->rate; @@ -332,7 +343,7 @@ void FurnaceGUI::drawSampleEdit() { ImGui::ImageButton(sampleTex,avail,ImVec2(0,0),ImVec2(1,1),0); if (ImGui::IsItemClicked()) { - printf("drawing\n"); + logD("drawing\n"); } ImGui::Text("A workaround! Pretty cool huh?"); From 299dbf14e461578d0a386cc230aa756e26f052b2 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 20 Mar 2022 04:28:57 -0500 Subject: [PATCH 329/637] GUI: sample editor status bar --- src/gui/sampleEdit.cpp | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index 5f38706b1..a59a043e7 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -22,6 +22,7 @@ #include "../ta-log.h" #include "IconsFontAwesome4.h" #include "misc/cpp/imgui_stdlib.h" +#include #include "guiConst.h" void FurnaceGUI::drawSampleEdit() { @@ -133,12 +134,16 @@ void FurnaceGUI::drawSampleEdit() { }*/ ImGui::Separator(); - ImGui::Button(ICON_FA_I_CURSOR "##SSelect"); + if (ImGui::Button(ICON_FA_I_CURSOR "##SSelect")) { + sampleDragMode=false; + } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Edit mode: Select"); } ImGui::SameLine(); - ImGui::Button(ICON_FA_PENCIL "##SDraw"); + if (ImGui::Button(ICON_FA_PENCIL "##SDraw")) { + sampleDragMode=true; + } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Edit mode: Draw"); } @@ -345,8 +350,32 @@ void FurnaceGUI::drawSampleEdit() { if (ImGui::IsItemClicked()) { logD("drawing\n"); } + String statusBar=sampleDragMode?"Draw":"Select"; - ImGui::Text("A workaround! Pretty cool huh?"); + if (!sampleDragMode) { + if (sampleSelStart>=0 && sampleSelEnd>=0) { + statusBar+=fmt::sprintf(" (%d-%d)",sampleSelStart,sampleSelEnd); + } + } + + if (ImGui::IsItemHovered()) { + int posX=-1; + int posY=0; + ImVec2 pos=ImGui::GetMousePos(); + pos.x-=ImGui::GetItemRectMin().x; + pos.y-=ImGui::GetItemRectMin().y; + + if (sampleZoom>0) { + posX=samplePos+pos.x*sampleZoom; + if (posX>(int)sample->samples) posX=-1; + } + posY=(0.5-pos.y/ImGui::GetItemRectSize().y)*((sample->depth==8)?255:32767); + if (posX>=0) { + statusBar+=fmt::sprintf(" | (%d, %d)",posX,posY); + } + } + + ImGui::Text("%s",statusBar.c_str()); } /* From 9a94b7124d8ce9d3695e63de110aed5c54dbca52 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 20 Mar 2022 04:42:23 -0500 Subject: [PATCH 330/637] GUI: creditssss --- src/gui/gui.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 9680173e8..57b152ac8 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1599,6 +1599,7 @@ const char* aboutLine[]={ "tildearrow", "akumanatt", "cam900", + "djtuBIG-MaliceX", "laoo", "superctr", "", From b7fd410c1f95e61af81e0902a56953a6d1ab913b Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 20 Mar 2022 05:19:04 -0500 Subject: [PATCH 331/637] GUI: temporarily hide BRR sample type --- src/gui/guiConst.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index 11795e665..3e12314c9 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -104,7 +104,7 @@ const char* sampleDepths[17]={ "ADPCM-B", "X68000 ADPCM", "8-bit PCM", - "BRR", + NULL, // "BRR", "VOX", NULL, NULL, @@ -121,4 +121,4 @@ const char* resampleStrats[]={ "blep synthesis", "sinc", "best possible" -}; \ No newline at end of file +}; From bdc29455cc28e456a0a859f5542e25486ab49b89 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 20 Mar 2022 05:19:23 -0500 Subject: [PATCH 332/637] GUI: one more change to the status bar --- src/gui/sampleEdit.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index a59a043e7..f63080cf3 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -354,7 +354,14 @@ void FurnaceGUI::drawSampleEdit() { if (!sampleDragMode) { if (sampleSelStart>=0 && sampleSelEnd>=0) { - statusBar+=fmt::sprintf(" (%d-%d)",sampleSelStart,sampleSelEnd); + int start=sampleSelStart; + int end=sampleSelEnd; + if (start>end) { + start^=end; + end^=start; + start^=end; + } + statusBar+=fmt::sprintf(" (%d-%d)",start,end); } } From 1941ca36164e5ece57eca79851e23ba1c961a791 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 20 Mar 2022 13:36:48 -0500 Subject: [PATCH 333/637] fix crash on sample preview at low rate --- src/engine/engine.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index e3cf9f339..3a6812adf 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -1029,6 +1029,7 @@ void DivEngine::previewSample(int sample, int note) { rate=(song.tuning*pow(2.0,(double)(note+3)/12.0)*((double)song.sample[sample]->centerRate/8363.0)); if (rate<=0) rate=song.sample[sample]->rate; } + if (rate<100) rate=100; blip_set_rates(samp_bb,rate,got.rate); samp_prevSample=0; sPreview.pos=0; @@ -1057,7 +1058,9 @@ void DivEngine::previewWave(int wave, int note) { return; } blip_clear(samp_bb); - blip_set_rates(samp_bb,song.wave[wave]->len*((song.tuning*0.0625)*pow(2.0,(double)(note+3)/12.0)),got.rate); + double rate=song.wave[wave]->len*((song.tuning*0.0625)*pow(2.0,(double)(note+3)/12.0)); + if (rate<100) rate=100; + blip_set_rates(samp_bb,rate,got.rate); samp_prevSample=0; sPreview.pos=0; sPreview.sample=-1; From b04e1f2870c9ecc0ec034c773f260e8079c56f18 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 20 Mar 2022 18:25:48 -0500 Subject: [PATCH 334/637] GUI: implement sample draw --- src/engine/playback.cpp | 2 +- src/gui/gui.cpp | 45 +++++++++++++++++++++++++++++++++++++++-- src/gui/gui.h | 1 + src/gui/sampleEdit.cpp | 30 +++++++++++++++++++++++---- 4 files changed, 71 insertions(+), 7 deletions(-) diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 16f883e9e..cfa27f5d6 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -1560,7 +1560,7 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi return; } - logD("attempts: %d\n",attempts); + //logD("attempts: %d\n",attempts); if (attempts>=100) { logE("hang detected! stopping! at %d seconds %d micro\n",totalSeconds,totalTicks); freelance=false; diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 57b152ac8..d45f937a1 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -5038,6 +5038,34 @@ void FurnaceGUI::processDrags(int dragX, int dragY) { modified=true; } } + if (sampleDragActive) { + int x=samplePos+(int)(double(dragX-sampleDragStart.x)*sampleZoom); + int x1=samplePos+(int)(double(dragX-sampleDragStart.x+1)*sampleZoom); + if (x<0) x=0; + if (x>=(int)sampleDragLen) x=sampleDragLen-1; + if (x1<0) x1=0; + if (x1>=(int)sampleDragLen) x1=sampleDragLen-1; + double y=0.5-double(dragY-sampleDragStart.y)/sampleDragAreaSize.y; + if (sampleDragMode) { // draw + if (sampleDrag16) { + int val=y*65536; + if (val<-32768) val=-32768; + if (val>32767) val=32767; + for (int i=x; i<=x1; i++) ((short*)sampleDragTarget)[i]=val; + } else { + int val=y*256; + if (val<-128) val=-128; + if (val>127) val=127; + for (int i=x; i<=x1; i++) ((signed char*)sampleDragTarget)[i]=val; + } + updateSampleTex=true; + } else { // select + if (sampleSelStart<0) { + sampleSelStart=x; + } + sampleSelEnd=x; + } + } } #define sysAddOption(x) \ @@ -5287,7 +5315,7 @@ bool FurnaceGUI::loop() { addScroll(1); } } - if (macroDragActive || macroLoopDragActive || waveDragActive) { + if (macroDragActive || macroLoopDragActive || waveDragActive || sampleDragActive) { int distance=fabs(motionXrel); if (distance<1) distance=1; float start=motionX-motionXrel; @@ -5304,7 +5332,7 @@ bool FurnaceGUI::loop() { break; } case SDL_MOUSEBUTTONUP: - if (macroDragActive || macroLoopDragActive || waveDragActive) modified=true; + if (macroDragActive || macroLoopDragActive || waveDragActive || (sampleDragActive && sampleDragMode)) modified=true; macroDragActive=false; macroDragBitMode=false; macroDragInitialValue=false; @@ -5313,6 +5341,19 @@ bool FurnaceGUI::loop() { macroDragLastY=-1; macroLoopDragActive=false; waveDragActive=false; + if (sampleDragActive) { + logD("stopping sample drag\n"); + if (sampleDragMode) { + e->renderSamplesP(); + } else { + if (sampleSelStart>sampleSelEnd) { + sampleSelStart^=sampleSelEnd; + sampleSelEnd^=sampleSelStart; + sampleSelStart^=sampleSelEnd; + } + } + } + sampleDragActive=false; if (selecting) { cursor=selEnd; finishSelection(); diff --git a/src/gui/gui.h b/src/gui/gui.h index 0edbea956..5ffb01814 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -747,6 +747,7 @@ class FurnaceGUI { void* sampleDragTarget; ImVec2 sampleDragStart; ImVec2 sampleDragAreaSize; + unsigned int sampleDragLen; // visualizer float keyHit[DIV_MAX_CHANS]; diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index f63080cf3..f90f3d9c6 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -320,7 +320,11 @@ void FurnaceGUI::drawSampleEdit() { if (xCoarse>=sample->samples) break; int y1, y2; int totalAdvance=0; - y1=((unsigned short)sample->data16[xCoarse]^0x8000)*availY/65536; + if (sample->depth==8) { + y1=((unsigned char)sample->data8[xCoarse]^0x80)*availY/256; + } else { + y1=((unsigned short)sample->data16[xCoarse]^0x8000)*availY/65536; + } xFine+=xAdvanceFine; if (xFine>=16777216) { xFine-=16777216; @@ -329,14 +333,22 @@ void FurnaceGUI::drawSampleEdit() { totalAdvance+=xAdvanceCoarse; if (xCoarse>=sample->samples) break; do { - y2=((unsigned short)sample->data16[xCoarse]^0x8000)*availY/65536; + if (sample->depth==8) { + y2=((unsigned char)sample->data8[xCoarse]^0x80)*availY/256; + } else { + y2=((unsigned short)sample->data16[xCoarse]^0x8000)*availY/65536; + } if (y1>y2) { y2^=y1; y1^=y2; y2^=y1; } + if (y1<0) y1=0; + if (y1>=availY) y1=availY-1; + if (y2<0) y2=0; + if (y2>=availY) y2=availY-1; for (int j=y1; j<=y2; j++) { - data[i+availX*j]=lineColor; + data[i+availX*(availY-j-1)]=lineColor; } if (totalAdvance>0) xCoarse++; } while ((totalAdvance--)>0); @@ -348,7 +360,17 @@ void FurnaceGUI::drawSampleEdit() { ImGui::ImageButton(sampleTex,avail,ImVec2(0,0),ImVec2(1,1),0); if (ImGui::IsItemClicked()) { - logD("drawing\n"); + if (sample->samples>0 && (sample->depth==16 || sample->depth==8)) { + sampleDragStart=ImGui::GetItemRectMin(); + sampleDragAreaSize=ImGui::GetItemRectSize(); + sampleDrag16=(sample->depth==16); + sampleDragTarget=(sample->depth==16)?((void*)sample->data16):((void*)sample->data8); + sampleDragLen=sample->samples; + sampleDragActive=true; + sampleSelStart=-1; + sampleSelEnd=-1; + processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); + } } String statusBar=sampleDragMode?"Draw":"Select"; From ef3bf8f92445b8ef8cd52d23a43f1be347ba7f2d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 20 Mar 2022 18:31:25 -0500 Subject: [PATCH 335/637] GUI: display sample selection - NOT FINISHED --- src/gui/sampleEdit.cpp | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index f90f3d9c6..7594d1fe4 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -373,9 +373,10 @@ void FurnaceGUI::drawSampleEdit() { } } String statusBar=sampleDragMode?"Draw":"Select"; + bool drawSelection=false; if (!sampleDragMode) { - if (sampleSelStart>=0 && sampleSelEnd>=0) { + if (sampleSelStart>=0 && sampleSelEnd>=0 && sampleSelStart!=sampleSelEnd) { int start=sampleSelStart; int end=sampleSelEnd; if (start>end) { @@ -384,6 +385,7 @@ void FurnaceGUI::drawSampleEdit() { start^=end; } statusBar+=fmt::sprintf(" (%d-%d)",start,end); + drawSelection=true; } } @@ -403,6 +405,24 @@ void FurnaceGUI::drawSampleEdit() { statusBar+=fmt::sprintf(" | (%d, %d)",posX,posY); } } + + if (drawSelection) { + int start=sampleSelStart; + int end=sampleSelEnd; + if (start>end) { + start^=end; + end^=start; + start^=end; + } + ImDrawList* dl=ImGui::GetWindowDrawList(); + ImVec2 p1=ImGui::GetItemRectMin(); + p1.x+=start/sampleZoom-samplePos; + + ImVec2 p2=ImVec2(ImGui::GetItemRectMin().x+end/sampleZoom-samplePos,ImGui::GetItemRectMax().y); + + // TODO: color + dl->AddRectFilled(p1,p2,0xc0c0c0c0); + } ImGui::Text("%s",statusBar.c_str()); } From 21b15d686bc137399230e1659df782200e58bfe8 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 20 Mar 2022 22:16:20 -0500 Subject: [PATCH 336/637] GUI: prevent typing out-of-range notes cope --- src/gui/gui.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index d45f937a1..b25253d10 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -4367,6 +4367,9 @@ void FurnaceGUI::keyDown(SDL_Event& ev) { int key=noteKeys.at(ev.key.keysym.scancode); int num=12*curOctave+key; + if (num<-60) num=-60; // C-(-5) + if (num>119) num=119; // B-9 + if (edit) { // TODO: separate when adding MIDI input. DivPattern* pat=e->song.pat[cursor.xCoarse].getPattern(e->song.orders.ord[cursor.xCoarse][e->getOrder()],true); From 20799402c8dc5ba94b672f832428422d55b28da5 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 20 Mar 2022 22:22:10 -0500 Subject: [PATCH 337/637] GUI: select all is vertical now --- src/gui/gui.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index b25253d10..4e8699dbc 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2742,7 +2742,7 @@ void FurnaceGUI::doSelectAll() { } float aspect=float(selEndX-selStartX+1)/float(selEnd.y-selStart.y+1); - if (aspect<1.0f && !(selStart.y==0 && selEnd.y==e->song.patLen-1)) { // up-down + if (aspect<=1.0f && !(selStart.y==0 && selEnd.y==e->song.patLen-1)) { // up-down selStart.y=0; selEnd.y=e->song.patLen-1; } else { // left-right From b83b46aa2c007229b456891b32f93ea37b48d72d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 21 Mar 2022 00:41:18 -0500 Subject: [PATCH 338/637] GUI: more sample editor work scrollbar and some layout changes --- src/gui/gui.cpp | 21 ++++++-- src/gui/gui.h | 3 +- src/gui/sampleEdit.cpp | 115 +++++++++++++++++++++++++++++++---------- 3 files changed, 108 insertions(+), 31 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 4e8699dbc..65f1996b1 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1364,6 +1364,7 @@ void FurnaceGUI::actualSampleList() { ImGui::TableNextColumn(); if (ImGui::Selectable(fmt::sprintf("%d: %s##_SAM%d",i,sample->name,i).c_str(),curSample==i)) { curSample=i; + samplePos=0; updateSampleTex=true; } if (ImGui::IsItemHovered()) { @@ -2052,6 +2053,8 @@ void FurnaceGUI::drawNewSong() { curNibble=false; orderNibble=false; orderCursor=-1; + samplePos=0; + updateSampleTex=true; selStart=SelectionPoint(); selEnd=SelectionPoint(); cursor=SelectionPoint(); @@ -4946,6 +4949,8 @@ int FurnaceGUI::load(String path) { curNibble=false; orderNibble=false; orderCursor=-1; + samplePos=0; + updateSampleTex=true; selStart=SelectionPoint(); selEnd=SelectionPoint(); cursor=SelectionPoint(); @@ -5042,10 +5047,14 @@ void FurnaceGUI::processDrags(int dragX, int dragY) { } } if (sampleDragActive) { - int x=samplePos+(int)(double(dragX-sampleDragStart.x)*sampleZoom); - int x1=samplePos+(int)(double(dragX-sampleDragStart.x+1)*sampleZoom); + int x=samplePos+round(double(dragX-sampleDragStart.x)*sampleZoom); + int x1=samplePos+round(double(dragX-sampleDragStart.x+1)*sampleZoom); if (x<0) x=0; - if (x>=(int)sampleDragLen) x=sampleDragLen-1; + if (sampleDragMode) { + if (x>=(int)sampleDragLen) x=sampleDragLen-1; + } else { + if (x>(int)sampleDragLen) x=sampleDragLen; + } if (x1<0) x1=0; if (x1>=(int)sampleDragLen) x1=sampleDragLen-1; double y=0.5-double(dragY-sampleDragStart.y)/sampleDragAreaSize.y; @@ -6660,6 +6669,10 @@ void FurnaceGUI::applyUISettings() { sty.Colors[ImGuiCol_PlotHistogram]=uiColors[GUI_COLOR_MACRO_OTHER]; sty.Colors[ImGuiCol_PlotHistogramHovered]=uiColors[GUI_COLOR_MACRO_OTHER]; + /*sty.WindowRounding=8.0f; + sty.FrameRounding=6.0f; + sty.PopupRounding=8.0f;*/ + sty.ScaleAllSizes(dpiScale); ImGui::GetStyle()=sty; @@ -7143,6 +7156,7 @@ FurnaceGUI::FurnaceGUI(): randomMode(false), oldOrdersLen(0), sampleZoom(1.0), + prevSampleZoom(1.0), samplePos(0), resizeSize(1024), resampleTarget(32000), @@ -7153,6 +7167,7 @@ FurnaceGUI::FurnaceGUI(): sampleDragActive(false), sampleDragMode(false), sampleDrag16(false), + sampleZoomAuto(true), sampleDragTarget(NULL), sampleDragStart(0,0), sampleDragAreaSize(0,0) { diff --git a/src/gui/gui.h b/src/gui/gui.h index 5ffb01814..0ab87ef8a 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -737,13 +737,14 @@ class FurnaceGUI { // sample editor specific double sampleZoom; + double prevSampleZoom; int samplePos; int resizeSize; double resampleTarget; int resampleStrat; float amplifyVol; int sampleSelStart, sampleSelEnd; - bool sampleDragActive, sampleDragMode, sampleDrag16; + bool sampleDragActive, sampleDragMode, sampleDrag16, sampleZoomAuto; void* sampleDragTarget; ImVec2 sampleDragStart; ImVec2 sampleDragAreaSize; diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index 7594d1fe4..429273f6a 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -18,6 +18,7 @@ */ #include "gui.h" #include +#include #include #include "../ta-log.h" #include "IconsFontAwesome4.h" @@ -110,28 +111,12 @@ void FurnaceGUI::drawSampleEdit() { ImGui::EndTable(); } - if (ImGui::InputDouble("Zoom",&sampleZoom,0.1,2.0)) { - if (sampleZoom<0.01) sampleZoom=0.01; - updateSampleTex=true; - } - if (ImGui::InputInt("Pos",&samplePos,1,10)) { - if (samplePos>=(int)sample->samples) samplePos=sample->samples-1; - if (samplePos<0) samplePos=0; - updateSampleTex=true; - } - /* if (ImGui::Button("Apply")) { e->renderSamplesP(); } ImGui::SameLine(); - if (ImGui::Button(ICON_FA_VOLUME_UP "##PreviewSample")) { - e->previewSample(curSample); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_VOLUME_OFF "##StopSample")) { - e->stopSamplePreview(); - }*/ + */ ImGui::Separator(); if (ImGui::Button(ICON_FA_I_CURSOR "##SSelect")) { @@ -274,13 +259,71 @@ void FurnaceGUI::drawSampleEdit() { ImGui::SetTooltip("Apply filter"); } + ImGui::SameLine(); + ImGui::Dummy(ImVec2(4.0*dpiScale,dpiScale)); + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_VOLUME_UP "##PreviewSample")) { + e->previewSample(curSample); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Preview sample"); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_VOLUME_OFF "##StopSample")) { + e->stopSamplePreview(); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Stop sample preview"); + } + + ImGui::SameLine(); + double zoomPercent=100.0/sampleZoom; + ImGui::Text("Zoom"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(150.0f*dpiScale); + if (ImGui::InputDouble("##SZoom",&zoomPercent,5.0,20.0,"%g%%")) { + if (zoomPercent>10000.0) zoomPercent=10000.0; + if (zoomPercent<1.0) zoomPercent=1.0; + sampleZoom=100.0/zoomPercent; + if (sampleZoom<0.01) sampleZoom=0.01; + sampleZoomAuto=false; + updateSampleTex=true; + } + ImGui::SameLine(); + if (sampleZoomAuto) { + if (ImGui::Button("100%")) { + sampleZoom=1.0; + sampleZoomAuto=false; + updateSampleTex=true; + } + } else { + if (ImGui::Button("Auto")) { + sampleZoomAuto=true; + updateSampleTex=true; + } + } + ImGui::Separator(); ImVec2 avail=ImGui::GetContentRegionAvail(); - avail.y-=ImGui::GetFontSize()+ImGui::GetStyle().ItemSpacing.y; + avail.y-=ImGui::GetFontSize()+ImGui::GetStyle().ItemSpacing.y+ImGui::GetStyle().ScrollbarSize; int availX=avail.x; int availY=avail.y; + + if (sampleZoomAuto) { + samplePos=0; + if (sample->samples<1 || avail.x<=0) { + sampleZoom=1.0; + } else { + sampleZoom=(double)sample->samples/avail.x; + } + if (sampleZoom!=prevSampleZoom) { + prevSampleZoom=sampleZoom; + updateSampleTex=true; + } + } + if (sampleTex==NULL || sampleTexW!=avail.x || sampleTexH!=avail.y) { if (sampleTex!=NULL) { SDL_DestroyTexture(sampleTex); @@ -359,10 +402,15 @@ void FurnaceGUI::drawSampleEdit() { } ImGui::ImageButton(sampleTex,avail,ImVec2(0,0),ImVec2(1,1),0); + + ImVec2 rectMin=ImGui::GetItemRectMin(); + ImVec2 rectMax=ImGui::GetItemRectMax(); + ImVec2 rectSize=ImGui::GetItemRectSize(); + if (ImGui::IsItemClicked()) { if (sample->samples>0 && (sample->depth==16 || sample->depth==8)) { - sampleDragStart=ImGui::GetItemRectMin(); - sampleDragAreaSize=ImGui::GetItemRectSize(); + sampleDragStart=rectMin; + sampleDragAreaSize=rectSize; sampleDrag16=(sample->depth==16); sampleDragTarget=(sample->depth==16)?((void*)sample->data16):((void*)sample->data8); sampleDragLen=sample->samples; @@ -393,14 +441,14 @@ void FurnaceGUI::drawSampleEdit() { int posX=-1; int posY=0; ImVec2 pos=ImGui::GetMousePos(); - pos.x-=ImGui::GetItemRectMin().x; - pos.y-=ImGui::GetItemRectMin().y; + pos.x-=rectMin.x; + pos.y-=rectMin.y; if (sampleZoom>0) { posX=samplePos+pos.x*sampleZoom; if (posX>(int)sample->samples) posX=-1; } - posY=(0.5-pos.y/ImGui::GetItemRectSize().y)*((sample->depth==8)?255:32767); + posY=(0.5-pos.y/rectSize.y)*((sample->depth==8)?255:32767); if (posX>=0) { statusBar+=fmt::sprintf(" | (%d, %d)",posX,posY); } @@ -415,15 +463,28 @@ void FurnaceGUI::drawSampleEdit() { start^=end; } ImDrawList* dl=ImGui::GetWindowDrawList(); - ImVec2 p1=ImGui::GetItemRectMin(); + ImVec2 p1=rectMin; p1.x+=start/sampleZoom-samplePos; - ImVec2 p2=ImVec2(ImGui::GetItemRectMin().x+end/sampleZoom-samplePos,ImGui::GetItemRectMax().y); + ImVec2 p2=ImVec2(rectMin.x+end/sampleZoom-samplePos,rectMax.y); + ImVec4 selColor=uiColors[GUI_COLOR_ACCENT_SECONDARY]; + selColor.w*=0.25; - // TODO: color - dl->AddRectFilled(p1,p2,0xc0c0c0c0); + dl->AddRectFilled(p1,p2,ImGui::GetColorU32(selColor)); + } + + ImS64 scrollV=samplePos; + ImS64 availV=round(rectSize.x*sampleZoom); + ImS64 contentsV=MAX(sample->samples,MAX(availV,1)); + + if (ImGui::ScrollbarEx(ImRect(ImVec2(rectMin.x,rectMax.y),ImVec2(rectMax.x,rectMax.y+ImGui::GetStyle().ScrollbarSize)),ImGui::GetID("sampleScroll"),ImGuiAxis_X,&scrollV,availV,contentsV,0)) { + if (!sampleZoomAuto && samplePos!=scrollV) { + samplePos=scrollV; + updateSampleTex=true; + } } + ImGui::SetCursorPosY(ImGui::GetCursorPosY()+ImGui::GetStyle().ScrollbarSize); ImGui::Text("%s",statusBar.c_str()); } From a68dbed7608f9f703dabf1ca2b70de60f6e6affb Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 21 Mar 2022 00:52:38 -0500 Subject: [PATCH 339/637] GUI: add options for rounded UI elements --- src/gui/gui.cpp | 18 ++++++++++++++---- src/gui/gui.h | 8 +++++++- src/gui/settings.cpp | 26 ++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 5 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 65f1996b1..bdee67fb1 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1976,6 +1976,12 @@ void FurnaceGUI::drawDebug() { } ImGui::TreePop(); } + if (ImGui::TreeNode("User Interface")) { + if (ImGui::Button("Inspect")) { + inspectorOpen=!inspectorOpen; + } + ImGui::TreePop(); + } if (ImGui::TreeNode("Settings")) { if (ImGui::Button("Sync")) syncSettings(); ImGui::SameLine(); @@ -6140,7 +6146,7 @@ bool FurnaceGUI::loop() { drawChannels(); drawRegView(); - //ImGui::ShowMetricsWindow(); + if (inspectorOpen) ImGui::ShowMetricsWindow(&inspectorOpen); if (firstFrame) { firstFrame=false; @@ -6669,9 +6675,12 @@ void FurnaceGUI::applyUISettings() { sty.Colors[ImGuiCol_PlotHistogram]=uiColors[GUI_COLOR_MACRO_OTHER]; sty.Colors[ImGuiCol_PlotHistogramHovered]=uiColors[GUI_COLOR_MACRO_OTHER]; - /*sty.WindowRounding=8.0f; - sty.FrameRounding=6.0f; - sty.PopupRounding=8.0f;*/ + if (settings.roundedWindows) sty.WindowRounding=8.0f; + if (settings.roundedButtons) { + sty.FrameRounding=6.0f; + sty.GrabRounding=6.0f; + } + if (settings.roundedMenus) sty.PopupRounding=8.0f; sty.ScaleAllSizes(dpiScale); @@ -7072,6 +7081,7 @@ FurnaceGUI::FurnaceGUI(): settingsOpen(false), mixerOpen(false), debugOpen(false), + inspectorOpen(false), oscOpen(true), volMeterOpen(true), statsOpen(false), diff --git a/src/gui/gui.h b/src/gui/gui.h index 0ab87ef8a..91a4e440c 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -567,6 +567,9 @@ class FurnaceGUI { int unifiedDataView; int sysFileDialog; // end + int roundedWindows; + int roundedButtons; + int roundedMenus; unsigned int maxUndoSteps; String mainFontPath; String patFontPath; @@ -613,6 +616,9 @@ class FurnaceGUI { stepOnInsert(0), unifiedDataView(0), sysFileDialog(1), + roundedWindows(1), + roundedButtons(1), + roundedMenus(0), maxUndoSteps(100), mainFontPath(""), patFontPath(""), @@ -625,7 +631,7 @@ class FurnaceGUI { int loopOrder, loopRow, loopEnd, isClipping, extraChannelButtons, patNameTarget, newSongCategory; bool editControlsOpen, ordersOpen, insListOpen, songInfoOpen, patternOpen, insEditOpen; bool waveListOpen, waveEditOpen, sampleListOpen, sampleEditOpen, aboutOpen, settingsOpen; - bool mixerOpen, debugOpen, oscOpen, volMeterOpen, statsOpen, compatFlagsOpen; + bool mixerOpen, debugOpen, inspectorOpen, oscOpen, volMeterOpen, statsOpen, compatFlagsOpen; bool pianoOpen, notesOpen, channelsOpen, regViewOpen; SelectionPoint selStart, selEnd, cursor; bool selecting, curNibble, orderNibble, followOrders, followPattern, changeAllOrders; diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index aa342ae9a..be1b6b169 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -449,6 +449,23 @@ void FurnaceGUI::drawSettings() { ImGui::Separator(); + bool roundedWindowsB=settings.roundedWindows; + if (ImGui::Checkbox("Rounded window corners",&roundedWindowsB)) { + settings.roundedWindows=roundedWindowsB; + } + + bool roundedButtonsB=settings.roundedButtons; + if (ImGui::Checkbox("Rounded buttons",&roundedButtonsB)) { + settings.roundedButtons=roundedButtonsB; + } + + bool roundedMenusB=settings.roundedMenus; + if (ImGui::Checkbox("Rounded menu corners",&roundedMenusB)) { + settings.roundedMenus=roundedMenusB; + } + + ImGui::Separator(); + if (ImGui::TreeNode("Color scheme")) { if (ImGui::TreeNode("General")) { ImGui::Text("Color scheme type:"); @@ -904,6 +921,9 @@ void FurnaceGUI::syncSettings() { settings.stepOnInsert=e->getConfInt("stepOnInsert",0); settings.unifiedDataView=e->getConfInt("unifiedDataView",0); settings.sysFileDialog=e->getConfInt("sysFileDialog",1); + settings.roundedWindows=e->getConfInt("roundedWindows",1); + settings.roundedButtons=e->getConfInt("roundedButtons",1); + settings.roundedMenus=e->getConfInt("roundedMenus",0); clampSetting(settings.mainFontSize,2,96); clampSetting(settings.patFontSize,2,96); @@ -944,6 +964,9 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.stepOnInsert,0,1); clampSetting(settings.unifiedDataView,0,1); clampSetting(settings.sysFileDialog,0,1); + clampSetting(settings.roundedWindows,0,1); + clampSetting(settings.roundedButtons,0,1); + clampSetting(settings.roundedMenus,0,1); // keybinds LOAD_KEYBIND(GUI_ACTION_OPEN,FURKMOD_CMD|SDLK_o); @@ -1145,6 +1168,9 @@ void FurnaceGUI::commitSettings() { e->setConf("stepOnInsert",settings.stepOnInsert); e->setConf("unifiedDataView",settings.unifiedDataView); e->setConf("sysFileDialog",settings.sysFileDialog); + e->setConf("roundedWindows",settings.roundedWindows); + e->setConf("roundedButtons",settings.roundedButtons); + e->setConf("roundedMenus",settings.roundedMenus); PUT_UI_COLOR(GUI_COLOR_BACKGROUND); PUT_UI_COLOR(GUI_COLOR_FRAME_BACKGROUND); From 2df7658fd0ee00d5d67aef6ea7a8dd1f2bff5b7e Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 21 Mar 2022 02:43:52 -0500 Subject: [PATCH 340/637] GUI: implement sample sel operations plenty of them --- src/engine/sample.cpp | 79 ++++++++++++++ src/engine/sample.h | 18 ++++ src/gui/sampleEdit.cpp | 232 ++++++++++++++++++++++++++++++++++++++--- 3 files changed, 313 insertions(+), 16 deletions(-) diff --git a/src/engine/sample.cpp b/src/engine/sample.cpp index 8b0c78e56..be9c5029c 100644 --- a/src/engine/sample.cpp +++ b/src/engine/sample.cpp @@ -179,6 +179,85 @@ bool DivSample::resize(unsigned int count) { return false; } +bool DivSample::strip(unsigned int begin, unsigned int end) { + if (begin>samples) begin=samples; + if (end>samples) end=samples; + int count=samples-(end-begin); + if (count<=0) return resize(0); + if (depth==8) { + if (data8!=NULL) { + signed char* oldData8=data8; + data8=NULL; + initInternal(8,count); + if (begin>0) { + memcpy(data8,oldData8,begin); + } + if (samples-end>0) { + memcpy(data8+begin,oldData8+end,samples-end); + } + delete[] oldData8; + } else { + // do nothing + return true; + } + samples=count; + return true; + } else if (depth==16) { + if (data16!=NULL) { + short* oldData16=data16; + data16=NULL; + initInternal(16,count); + if (begin>0) { + memcpy(data16,oldData16,sizeof(short)*begin); + } + if (samples-end>0) { + memcpy(&(data16[begin]),&(oldData16[end]),sizeof(short)*(samples-end)); + } + delete[] oldData16; + } else { + // do nothing + return true; + } + samples=count; + return true; + } + return false; +} + +bool DivSample::trim(unsigned int begin, unsigned int end) { + int count=end-begin; + if (count==0) return true; + if (begin==0 && end==samples) return true; + if (depth==8) { + if (data8!=NULL) { + signed char* oldData8=data8; + data8=NULL; + initInternal(8,count); + memcpy(data8,oldData8+begin,count); + delete[] oldData8; + } else { + // do nothing + return true; + } + samples=count; + return true; + } else if (depth==16) { + if (data16!=NULL) { + short* oldData16=data16; + data16=NULL; + initInternal(16,count); + memcpy(data16,&(oldData16[begin]),sizeof(short)*count); + delete[] oldData16; + } else { + // do nothing + return true; + } + samples=count; + return true; + } + return false; +} + #define RESAMPLE_BEGIN \ if (samples<1) return true; \ int finalCount=(double)samples*(r/(double)rate); \ diff --git a/src/engine/sample.h b/src/engine/sample.h index 4737a00d8..7e4d76bec 100644 --- a/src/engine/sample.h +++ b/src/engine/sample.h @@ -102,6 +102,24 @@ struct DivSample { */ bool resize(unsigned int count); + /** + * remove part of the sample data. + * @warning do not attempt to strip a sample outside of a synchronized block! + * @param start the beginning. + * @param end the end. + * @return whether it was successful. + */ + bool strip(unsigned int begin, unsigned int end); + + /** + * clip the sample data to specified boundaries. + * @warning do not attempt to trim a sample outside of a synchronized block! + * @param start the beginning. + * @param end the end. + * @return whether it was successful. + */ + bool trim(unsigned int begin, unsigned int end); + /** * change the sample rate. * @warning do not attempt to resample outside of a synchronized block! diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index 429273f6a..190621895 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -26,6 +26,19 @@ #include #include "guiConst.h" +#define SAMPLE_OP_BEGIN \ + unsigned int start=0; \ + unsigned int end=sample->samples; \ + if (sampleSelStart!=-1 && sampleSelEnd!=-1 && sampleSelStart!=sampleSelEnd) { \ + start=sampleSelStart; \ + end=sampleSelEnd; \ + if (start>end) { \ + start^=end; \ + end^=start; \ + start^=end; \ + } \ + } \ + void FurnaceGUI::drawSampleEdit() { if (nextWindow==GUI_WINDOW_SAMPLE_EDIT) { sampleEditOpen=true; @@ -223,33 +236,214 @@ void FurnaceGUI::drawSampleEdit() { } ImGui::SameLine(); ImGui::Text("(%.1fdB)",20.0*log10(amplifyVol/100.0f)); - ImGui::Button("Apply"); + if (ImGui::Button("Apply")) { + e->synchronized([this,sample]() { + SAMPLE_OP_BEGIN; + float vol=amplifyVol/100.0f; + + if (sample->depth==16) { + for (unsigned int i=start; idata16[i]*vol; + if (val<-32768) val=-32768; + if (val>32767) val=32767; + sample->data16[i]=val; + } + } else if (sample->depth==8) { + for (unsigned int i=start; idata8[i]*vol; + if (val<-128) val=-128; + if (val>127) val=127; + sample->data8[i]=val; + } + } + + updateSampleTex=true; + + e->renderSamples(); + }); + ImGui::CloseCurrentPopup(); + } ImGui::EndPopup(); } ImGui::SameLine(); - ImGui::Button(ICON_FA_ARROWS_V "##SNormalize"); + if (ImGui::Button(ICON_FA_ARROWS_V "##SNormalize")) { + e->synchronized([this,sample]() { + SAMPLE_OP_BEGIN; + float maxVal=0.0f; + + if (sample->depth==16) { + for (unsigned int i=start; idata16[i]/32767.0f); + if (val>maxVal) maxVal=val; + } + if (maxVal>1.0f) maxVal=1.0f; + if (maxVal>0.0f) { + float vol=1.0f/maxVal; + for (unsigned int i=start; idata16[i]*vol; + if (val<-32768) val=-32768; + if (val>32767) val=32767; + sample->data16[i]=val; + } + } + } else if (sample->depth==8) { + for (unsigned int i=start; idata8[i]/127.0f); + if (val>maxVal) maxVal=val; + } + if (maxVal>1.0f) maxVal=1.0f; + if (maxVal>0.0f) { + float vol=1.0f/maxVal; + for (unsigned int i=start; idata8[i]*vol; + if (val<-128) val=-128; + if (val>127) val=127; + sample->data8[i]=val; + } + } + } + + updateSampleTex=true; + + e->renderSamples(); + }); + } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Normalize"); } ImGui::SameLine(); - ImGui::Button(ICON_FA_ERASER "##SSilence"); + if (ImGui::Button(ICON_FA_ERASER "##SSilence")) { + e->synchronized([this,sample]() { + SAMPLE_OP_BEGIN; + + if (sample->depth==16) { + for (unsigned int i=start; idata16[i]=0; + } + } else if (sample->depth==8) { + for (unsigned int i=start; idata8[i]=0; + } + } + + updateSampleTex=true; + + e->renderSamples(); + }); + } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Apply silence"); } ImGui::SameLine(); + if (ImGui::Button(ICON_FA_TIMES "##SDelete")) { + e->synchronized([this,sample]() { + SAMPLE_OP_BEGIN; + + sample->strip(start,end); + updateSampleTex=true; + + e->renderSamples(); + }); + sampleSelStart=-1; + sampleSelEnd=-1; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Delete"); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_CROP "##STrim")) { + e->synchronized([this,sample]() { + SAMPLE_OP_BEGIN; + + sample->trim(start,end); + updateSampleTex=true; + + e->renderSamples(); + }); + sampleSelStart=-1; + sampleSelEnd=-1; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Trim"); + } + ImGui::SameLine(); ImGui::Dummy(ImVec2(4.0*dpiScale,dpiScale)); ImGui::SameLine(); - ImGui::Button(ICON_FA_BACKWARD "##SReverse"); + if (ImGui::Button(ICON_FA_BACKWARD "##SReverse")) { + e->synchronized([this,sample]() { + SAMPLE_OP_BEGIN; + + if (sample->depth==16) { + for (unsigned int i=start; idata16[i]^=sample->data16[ri]; + sample->data16[ri]^=sample->data16[i]; + sample->data16[i]^=sample->data16[ri]; + } + } else if (sample->depth==8) { + for (unsigned int i=start; idata8[i]^=sample->data8[ri]; + sample->data8[ri]^=sample->data8[i]; + sample->data8[i]^=sample->data8[ri]; + } + } + + updateSampleTex=true; + + e->renderSamples(); + }); + } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Reverse"); } ImGui::SameLine(); - ImGui::Button(ICON_FA_SORT_AMOUNT_ASC "##SInvert"); + if (ImGui::Button(ICON_FA_SORT_AMOUNT_ASC "##SInvert")) { + e->synchronized([this,sample]() { + SAMPLE_OP_BEGIN; + + if (sample->depth==16) { + for (unsigned int i=start; idata16[i]=-sample->data16[i]; + if (sample->data16[i]==-32768) sample->data16[i]=32767; + } + } else if (sample->depth==8) { + for (unsigned int i=start; idata8[i]=-sample->data8[i]; + if (sample->data16[i]==-128) sample->data16[i]=127; + } + } + + updateSampleTex=true; + + e->renderSamples(); + }); + } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Invert"); } ImGui::SameLine(); - ImGui::Button(ICON_FA_LEVEL_DOWN "##SSign"); + if (ImGui::Button(ICON_FA_LEVEL_DOWN "##SSign")) { + e->synchronized([this,sample]() { + SAMPLE_OP_BEGIN; + + if (sample->depth==16) { + for (unsigned int i=start; idata16[i]^=0x8000; + } + } else if (sample->depth==8) { + for (unsigned int i=start; idata8[i]^=0x80; + } + } + + updateSampleTex=true; + + e->renderSamples(); + }); + } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Signed/unsigned exchange"); } @@ -408,16 +602,22 @@ void FurnaceGUI::drawSampleEdit() { ImVec2 rectSize=ImGui::GetItemRectSize(); if (ImGui::IsItemClicked()) { - if (sample->samples>0 && (sample->depth==16 || sample->depth==8)) { - sampleDragStart=rectMin; - sampleDragAreaSize=rectSize; - sampleDrag16=(sample->depth==16); - sampleDragTarget=(sample->depth==16)?((void*)sample->data16):((void*)sample->data8); - sampleDragLen=sample->samples; - sampleDragActive=true; - sampleSelStart=-1; - sampleSelEnd=-1; - processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); + if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { + sampleDragActive=false; + sampleSelStart=0; + sampleSelEnd=sample->samples; + } else { + if (sample->samples>0 && (sample->depth==16 || sample->depth==8)) { + sampleDragStart=rectMin; + sampleDragAreaSize=rectSize; + sampleDrag16=(sample->depth==16); + sampleDragTarget=(sample->depth==16)?((void*)sample->data16):((void*)sample->data8); + sampleDragLen=sample->samples; + sampleDragActive=true; + sampleSelStart=-1; + sampleSelEnd=-1; + processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); + } } } String statusBar=sampleDragMode?"Draw":"Select"; From b45d2be312d2dd1d6ed2d25caa0c14fcdf5bada5 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 21 Mar 2022 03:45:20 -0500 Subject: [PATCH 341/637] GUI: sample editor almost complete only keybinds are missing --- src/gui/gui.cpp | 10 ++- src/gui/gui.h | 2 + src/gui/sampleEdit.cpp | 159 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 170 insertions(+), 1 deletion(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index bdee67fb1..b1dfc9876 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -7180,7 +7180,15 @@ FurnaceGUI::FurnaceGUI(): sampleZoomAuto(true), sampleDragTarget(NULL), sampleDragStart(0,0), - sampleDragAreaSize(0,0) { + sampleDragAreaSize(0,0), + sampleDragLen(0), + sampleFilterL(1.0f), + sampleFilterB(0.0f), + sampleFilterH(0.0f), + sampleFilterRes(0.25f), + sampleFilterCutStart(16000.0f), + sampleFilterCutEnd(100.0f), + sampleFilterPower(1) { // octave 1 /* diff --git a/src/gui/gui.h b/src/gui/gui.h index 91a4e440c..e51bc254b 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -755,6 +755,8 @@ class FurnaceGUI { ImVec2 sampleDragStart; ImVec2 sampleDragAreaSize; unsigned int sampleDragLen; + float sampleFilterL, sampleFilterB, sampleFilterH, sampleFilterRes, sampleFilterCutStart, sampleFilterCutEnd; + unsigned char sampleFilterPower; // visualizer float keyHit[DIV_MAX_CHANS]; diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index 190621895..1af7ddf63 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -312,6 +312,64 @@ void FurnaceGUI::drawSampleEdit() { ImGui::SetTooltip("Normalize"); } ImGui::SameLine(); + if (ImGui::Button(ICON_FA_ARROW_UP "##SFadeIn")) { + e->synchronized([this,sample]() { + SAMPLE_OP_BEGIN; + + if (sample->depth==16) { + for (unsigned int i=start; idata16[i]*float(i-start)/float(end-start); + if (val<-32768) val=-32768; + if (val>32767) val=32767; + sample->data16[i]=val; + } + } else if (sample->depth==8) { + for (unsigned int i=start; idata8[i]*float(i-start)/float(end-start); + if (val<-128) val=-128; + if (val>127) val=127; + sample->data8[i]=val; + } + } + + updateSampleTex=true; + + e->renderSamples(); + }); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Fade in"); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_ARROW_DOWN "##SFadeOut")) { + e->synchronized([this,sample]() { + SAMPLE_OP_BEGIN; + + if (sample->depth==16) { + for (unsigned int i=start; idata16[i]*float(end-i)/float(end-start); + if (val<-32768) val=-32768; + if (val>32767) val=32767; + sample->data16[i]=val; + } + } else if (sample->depth==8) { + for (unsigned int i=start; idata8[i]*float(end-i)/float(end-start); + if (val<-128) val=-128; + if (val>127) val=127; + sample->data8[i]=val; + } + } + + updateSampleTex=true; + + e->renderSamples(); + }); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Fade out"); + } + ImGui::SameLine(); if (ImGui::Button(ICON_FA_ERASER "##SSilence")) { e->synchronized([this,sample]() { SAMPLE_OP_BEGIN; @@ -452,7 +510,108 @@ void FurnaceGUI::drawSampleEdit() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Apply filter"); } + if (ImGui::BeginPopupContextItem("SFilterOpt",ImGuiPopupFlags_MouseButtonLeft)) { + float lowP=sampleFilterL*100.0f; + float bandP=sampleFilterB*100.0f; + float highP=sampleFilterH*100.0f; + float resP=sampleFilterRes*100.0f; + ImGui::Text("Cutoff:"); + if (ImGui::SliderFloat("From",&sampleFilterCutStart,0.0f,sample->rate*0.5,"%.0fHz")) { + if (sampleFilterCutStart<0.0) sampleFilterCutStart=0.0; + if (sampleFilterCutStart>sample->rate*0.5) sampleFilterCutStart=sample->rate*0.5; + } + if (ImGui::SliderFloat("To",&sampleFilterCutEnd,0.0f,sample->rate*0.5,"%.0fHz")) { + if (sampleFilterCutEnd<0.0) sampleFilterCutEnd=0.0; + if (sampleFilterCutEnd>sample->rate*0.5) sampleFilterCutEnd=sample->rate*0.5; + } + ImGui::Separator(); + if (ImGui::SliderFloat("Resonance",&resP,0.0f,99.0f,"%.1f%%")) { + sampleFilterRes=resP/100.0f; + if (sampleFilterRes<0.0f) sampleFilterRes=0.0f; + if (sampleFilterRes>0.99f) sampleFilterRes=0.99f; + } + ImGui::Text("Power"); + ImGui::SameLine(); + if (ImGui::RadioButton("1x",sampleFilterPower==1)) { + sampleFilterPower=1; + } + ImGui::SameLine(); + if (ImGui::RadioButton("2x",sampleFilterPower==2)) { + sampleFilterPower=2; + } + ImGui::SameLine(); + if (ImGui::RadioButton("3x",sampleFilterPower==3)) { + sampleFilterPower=3; + } + ImGui::Separator(); + if (ImGui::SliderFloat("Low-pass",&lowP,0.0f,100.0f,"%.1f%%")) { + sampleFilterL=lowP/100.0f; + if (sampleFilterL<0.0f) sampleFilterL=0.0f; + if (sampleFilterL>1.0f) sampleFilterL=1.0f; + } + if (ImGui::SliderFloat("Band-pass",&bandP,0.0f,100.0f,"%.1f%%")) { + sampleFilterB=bandP/100.0f; + if (sampleFilterB<0.0f) sampleFilterB=0.0f; + if (sampleFilterB>1.0f) sampleFilterB=1.0f; + } + if (ImGui::SliderFloat("High-pass",&highP,0.0f,100.0f,"%.1f%%")) { + sampleFilterH=highP/100.0f; + if (sampleFilterH<0.0f) sampleFilterH=0.0f; + if (sampleFilterH>1.0f) sampleFilterH=1.0f; + } + if (ImGui::Button("Apply")) { + e->synchronized([this,sample]() { + SAMPLE_OP_BEGIN; + float res=1.0-pow(sampleFilterRes,0.5f); + float low=0; + float band=0; + float high=0; + + double power=(sampleFilterCutStart>sampleFilterCutEnd)?0.5:2.0; + + if (sample->depth==16) { + for (unsigned int i=start; irate))*M_PI); + + for (int j=0; jdata16[i])-low-(res*band); + band=cut*high+band; + } + + float val=low*sampleFilterL+band*sampleFilterB+high*sampleFilterH; + if (val<-32768) val=-32768; + if (val>32767) val=32767; + sample->data16[i]=val; + } + } else if (sample->depth==8) { + for (unsigned int i=start; irate))*M_PI); + + for (int j=0; jdata8[i])-low-(res*band); + band=cut*high+band; + } + + float val=low*sampleFilterL+band*sampleFilterB+high*sampleFilterH; + if (val<-128) val=-128; + if (val>127) val=127; + sample->data8[i]=val; + } + } + + updateSampleTex=true; + + e->renderSamples(); + }); + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } ImGui::SameLine(); ImGui::Dummy(ImVec2(4.0*dpiScale,dpiScale)); ImGui::SameLine(); From 47c5c34e1c15f7e742e109138c1c696871f458aa Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Mon, 21 Mar 2022 21:02:51 +0700 Subject: [PATCH 342/637] Add PET support --- CMakeLists.txt | 1 + src/engine/dispatchContainer.cpp | 4 + src/engine/platform/pet.cpp | 311 +++++++++++++++++++++++++++++++ src/engine/platform/pet.h | 84 +++++++++ src/engine/playback.cpp | 1 + src/gui/gui.cpp | 3 + src/gui/insEdit.cpp | 9 +- 7 files changed, 410 insertions(+), 3 deletions(-) create mode 100644 src/engine/platform/pet.cpp create mode 100644 src/engine/platform/pet.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 6116dba8c..b3d1fc379 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -320,6 +320,7 @@ src/engine/platform/lynx.cpp src/engine/platform/swan.cpp src/engine/platform/vera.cpp src/engine/platform/bubsyswsg.cpp +src/engine/platform/pet.cpp src/engine/platform/dummy.cpp ) diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index f80ed13bd..be429c356 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -46,6 +46,7 @@ #include "platform/swan.h" #include "platform/lynx.h" #include "platform/bubsyswsg.h" +#include "platform/pet.h" #include "platform/dummy.h" #include "../ta-log.h" #include "song.h" @@ -275,6 +276,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_BUBSYS_WSG: dispatch=new DivPlatformBubSysWSG; break; + case DIV_SYSTEM_PET: + dispatch=new DivPlatformPET; + break; default: logW("this system is not supported yet! using dummy platform.\n"); dispatch=new DivPlatformDummy; diff --git a/src/engine/platform/pet.cpp b/src/engine/platform/pet.cpp new file mode 100644 index 000000000..bbc0562c7 --- /dev/null +++ b/src/engine/platform/pet.cpp @@ -0,0 +1,311 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "pet.h" +#include "../engine.h" +#include + +#define rWrite(a,v) {regPool[(a)]=(v)&0xff; if((a)==10) {chan.sreg=(v); chan.cnt=2;}} + +#define CHIP_DIVIDER 16 +#define SAMP_DIVIDER 4 + +const char* regCheatSheet6522[]={ + "T2L", "08", + "SR", "0A", + "ACR", "0B", + NULL +}; + +const char** DivPlatformPET::getRegisterSheet() { + return regCheatSheet6522; +} + +const char* DivPlatformPET::getEffectName(unsigned char effect) { + switch (effect) { + case 0x10: + return "10xx: Change waveform"; + break; + } + return NULL; +} + +void DivPlatformPET::acquire(short* bufL, short* bufR, size_t start, size_t len) { + // high-level emulation of 6522 shift register for now + int t2=regPool[8]*2+4; + if (((regPool[11]>>2)&7)==4) { + for (size_t h=start; h0) { + int adv=MIN(cycs,chan.cnt); + chan.cnt-=adv; + cycs-=adv; + if (chan.cnt==0) { + chan.out=(chan.sreg&1)*32767; + chan.sreg=(chan.sreg>>1)|((chan.sreg&1)<<7); + chan.cnt=t2; + } + } + bufL[h]=chan.out; + bufR[h]=chan.out; + } + } else { + chan.out=0; + for (size_t h=start; hgetWave(chan.wave); + if (wt->max<1 || wt->len<1) { + rWrite(10,0); + } else { + unsigned char sr=0; + for (int i=0; i<8; i++) { + sr=(sr<<1)|((wt->data[i*wt->len/8]*2)/(wt->max+1)); + } + rWrite(10,sr); + } +} + +void DivPlatformPET::writeOutVol() { + if (chan.active && !isMuted && chan.outVol>0) { + if (regPool[11]!=16) { + rWrite(11,16); + rWrite(10,regPool[10]); + } + } else { + rWrite(11,0); + } +} + +void DivPlatformPET::tick() { + chan.std.next(); + if (chan.std.hadVol) { + chan.outVol=chan.std.vol&chan.vol; + writeOutVol(); + } + if (chan.std.hadArp) { + if (!chan.inPorta) { + if (chan.std.arpMode) { + chan.baseFreq=NOTE_PERIODIC(chan.std.arp); + } else { + chan.baseFreq=NOTE_PERIODIC(chan.note+chan.std.arp); + } + } + chan.freqChanged=true; + } else { + if (chan.std.arpMode && chan.std.finishedArp) { + chan.baseFreq=NOTE_PERIODIC(chan.note); + chan.freqChanged=true; + } + } + if (chan.std.hadWave) { + if (chan.wave!=chan.std.wave) { + chan.wave=chan.std.wave; + updateWave(); + } + } + if (chan.freqChanged || chan.keyOn || chan.keyOff) { + chan.freq=parent->calcFreq(chan.baseFreq,chan.pitch,true); + if (chan.freq>257) chan.freq=257; + if (chan.freq<2) chan.freq=2; + rWrite(8,chan.freq-2); + if (chan.keyOn) { + if (!chan.std.willVol) { + chan.outVol=chan.vol; + writeOutVol(); + } + if (chan.wave<0) { + chan.wave=0; + updateWave(); + } + chan.keyOn=false; + } + if (chan.keyOff) { + rWrite(11,0); + chan.keyOff=false; + } + chan.freqChanged=false; + } +} + +int DivPlatformPET::dispatch(DivCommand c) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: { + DivInstrument* ins=parent->getIns(chan.ins); + if (c.value!=DIV_NOTE_NULL) { + chan.baseFreq=NOTE_PERIODIC(c.value); + chan.freqChanged=true; + chan.note=c.value; + } + chan.active=true; + chan.keyOn=true; + chan.std.init(ins); + break; + } + case DIV_CMD_NOTE_OFF: + chan.active=false; + chan.keyOff=true; + chan.std.init(NULL); + break; + case DIV_CMD_NOTE_OFF_ENV: + case DIV_CMD_ENV_RELEASE: + chan.std.release(); + break; + case DIV_CMD_INSTRUMENT: + if (chan.ins!=c.value || c.value2==1) { + chan.ins=c.value; + } + break; + case DIV_CMD_VOLUME: + if (chan.vol!=c.value) { + chan.vol=c.value; + if (!chan.std.hadVol) { + chan.outVol=chan.vol; + writeOutVol(); + } + } + break; + case DIV_CMD_GET_VOLUME: + return chan.vol; + break; + case DIV_CMD_PITCH: + chan.pitch=c.value; + chan.freqChanged=true; + break; + case DIV_CMD_WAVE: + chan.wave=c.value; + updateWave(); + chan.keyOn=true; + break; + case DIV_CMD_NOTE_PORTA: { + int destFreq=NOTE_PERIODIC(c.value2); + bool return2=false; + if (destFreq>chan.baseFreq) { + chan.baseFreq+=c.value; + if (chan.baseFreq>=destFreq) { + chan.baseFreq=destFreq; + return2=true; + } + } else { + chan.baseFreq-=c.value; + if (chan.baseFreq<=destFreq) { + chan.baseFreq=destFreq; + return2=true; + } + } + chan.freqChanged=true; + if (return2) { + chan.inPorta=false; + return 2; + } + break; + } + case DIV_CMD_LEGATO: + chan.baseFreq=NOTE_PERIODIC(c.value+((chan.std.willArp && !chan.std.arpMode)?(chan.std.arp):(0))); + chan.freqChanged=true; + chan.note=c.value; + break; + case DIV_CMD_PRE_PORTA: + if (chan.active && c.value2) { + if (parent->song.resetMacroOnPorta) chan.std.init(parent->getIns(chan.ins)); + } + chan.inPorta=c.value; + break; + case DIV_CMD_GET_VOLMAX: + return 1; + break; + case DIV_ALWAYS_SET_VOLUME: + return 1; + break; + default: + break; + } + return 1; +} + +void DivPlatformPET::muteChannel(int ch, bool mute) { + isMuted=mute; + writeOutVol(); +} + +void DivPlatformPET::forceIns() { + chan.insChanged=true; + chan.freqChanged=true; + updateWave(); + writeOutVol(); +} + +void* DivPlatformPET::getChanState(int ch) { + return &chan; +} + +unsigned char* DivPlatformPET::getRegisterPool() { + return regPool; +} + +int DivPlatformPET::getRegisterPoolSize() { + return 16; +} + +void DivPlatformPET::reset() { + memset(regPool,0,16); + chan=Channel(); + chan.vol=1; +} + +bool DivPlatformPET::isStereo() { + return false; +} + +void DivPlatformPET::notifyWaveChange(int wave) { + if (chan.wave==wave) { + updateWave(); + } +} + +void DivPlatformPET::notifyInsDeletion(void* ins) { + chan.std.notifyInsDeletion((DivInstrument*)ins); +} + +void DivPlatformPET::poke(unsigned int addr, unsigned short val) { + rWrite(addr,val); +} + +void DivPlatformPET::poke(std::vector& wlist) { + for (DivRegWrite& i: wlist) rWrite(i.addr,i.val); +} + +int DivPlatformPET::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { + parent=p; + dumpWrites=false; + skipRegisterWrites=false; + chipClock=1000000; + rate=chipClock/SAMP_DIVIDER; // = 250000kHz + isMuted=false; + reset(); + return 1; +} + +DivPlatformPET::~DivPlatformPET() { +} diff --git a/src/engine/platform/pet.h b/src/engine/platform/pet.h new file mode 100644 index 000000000..bc0a815bb --- /dev/null +++ b/src/engine/platform/pet.h @@ -0,0 +1,84 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _PET_H +#define _PET_H + +#include "../dispatch.h" +#include "../macroInt.h" + +class DivPlatformPET: public DivDispatch { + struct Channel { + int freq, baseFreq, pitch, note; + unsigned char ins; + bool active, insChanged, freqChanged, keyOn, keyOff, inPorta; + int vol, outVol, wave; + unsigned char sreg; + int cnt; + short out; + DivMacroInt std; + Channel(): + freq(0), + baseFreq(0), + pitch(0), + note(0), + ins(-1), + active(false), + insChanged(true), + freqChanged(false), + keyOn(false), + keyOff(false), + inPorta(false), + vol(1), + outVol(1), + wave(-1), + sreg(0), + cnt(0), + out(0) {} + }; + Channel chan; + bool isMuted; + + unsigned char regPool[16]; + void updateWave(); + friend void putDispatchChan(void*,int,int); + public: + void acquire(short* bufL, short* bufR, size_t start, size_t len); + int dispatch(DivCommand c); + void* getChanState(int chan); + unsigned char* getRegisterPool(); + int getRegisterPoolSize(); + void reset(); + void forceIns(); + void tick(); + void muteChannel(int ch, bool mute); + void notifyWaveChange(int wave); + void notifyInsDeletion(void* ins); + bool isStereo(); + void poke(unsigned int addr, unsigned short val); + void poke(std::vector& wlist); + const char** getRegisterSheet(); + const char* getEffectName(unsigned char effect); + int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); + ~DivPlatformPET(); + private: + void writeOutVol(); +}; + +#endif diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index cfa27f5d6..cf3e1309f 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -314,6 +314,7 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe } break; case DIV_SYSTEM_BUBSYS_WSG: + case DIV_SYSTEM_PET: switch (effect) { case 0x10: // select waveform dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal)); diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index b1dfc9876..3bb8c568e 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -5575,6 +5575,7 @@ bool FurnaceGUI::loop() { sysAddOption(DIV_SYSTEM_SWAN); sysAddOption(DIV_SYSTEM_VERA); sysAddOption(DIV_SYSTEM_BUBSYS_WSG); + sysAddOption(DIV_SYSTEM_PET); ImGui::EndMenu(); } if (ImGui::BeginMenu("configure system...")) { @@ -5918,6 +5919,7 @@ bool FurnaceGUI::loop() { case DIV_SYSTEM_YM2610B: case DIV_SYSTEM_YM2610B_EXT: case DIV_SYSTEM_YMU759: + case DIV_SYSTEM_PET: ImGui::Text("nothing to configure"); break; default: @@ -5973,6 +5975,7 @@ bool FurnaceGUI::loop() { sysChangeOption(i,DIV_SYSTEM_SWAN); sysChangeOption(i,DIV_SYSTEM_VERA); sysChangeOption(i,DIV_SYSTEM_BUBSYS_WSG); + sysChangeOption(i,DIV_SYSTEM_PET); ImGui::EndMenu(); } } diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index def41aad3..5d1e23cac 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1550,6 +1550,9 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_GB) { volMax=0; } + if (ins->type==DIV_INS_PET) { + volMax=1; + } bool arpMode=ins->std.arpMacroMode; @@ -1579,7 +1582,7 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_AY8930) { dutyMax=255; } - if (ins->type==DIV_INS_TIA || ins->type==DIV_INS_AMIGA || ins->type==DIV_INS_SCC) { + if (ins->type==DIV_INS_TIA || ins->type==DIV_INS_AMIGA || ins->type==DIV_INS_SCC || ins->type==DIV_INS_PET) { dutyMax=0; } if (ins->type==DIV_INS_PCE) { @@ -2002,7 +2005,7 @@ void FurnaceGUI::drawWaveEdit() { DivWavetable* wave=e->song.wave[curWave]; ImGui::Text("Width"); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("use a width of 32 on Game Boy, PC Engine and WonderSwan.\nany other widths will be scaled during playback."); + ImGui::SetTooltip("use a width of:\n- 8 for PET\n- 32 on Game Boy, PC Engine and WonderSwan.\nany other widths will be scaled during playback."); } ImGui::SameLine(); ImGui::SetNextItemWidth(128.0f*dpiScale); @@ -2016,7 +2019,7 @@ void FurnaceGUI::drawWaveEdit() { ImGui::SameLine(); ImGui::Text("Height"); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("use a height of:\n- 15 for Game Boy and WonderSwan\n- 31 for PC Engine\nany other heights will be scaled during playback."); + ImGui::SetTooltip("use a height of:\n- 1 for PET\n- 15 for Game Boy and WonderSwan\n- 31 for PC Engine\nany other heights will be scaled during playback."); } ImGui::SameLine(); ImGui::SetNextItemWidth(128.0f*dpiScale); From c32ea592853c68e419c3c305c571c8eb34530abc Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Tue, 22 Mar 2022 01:37:22 +0700 Subject: [PATCH 343/637] PET: Use wave bits macro instead --- src/engine/platform/pet.cpp | 32 +++----------------------------- src/engine/platform/pet.h | 4 +--- src/gui/insEdit.cpp | 7 ++++++- 3 files changed, 10 insertions(+), 33 deletions(-) diff --git a/src/engine/platform/pet.cpp b/src/engine/platform/pet.cpp index bbc0562c7..b89f95102 100644 --- a/src/engine/platform/pet.cpp +++ b/src/engine/platform/pet.cpp @@ -74,24 +74,11 @@ void DivPlatformPET::acquire(short* bufL, short* bufR, size_t start, size_t len) } } -void DivPlatformPET::updateWave() { - DivWavetable* wt=parent->getWave(chan.wave); - if (wt->max<1 || wt->len<1) { - rWrite(10,0); - } else { - unsigned char sr=0; - for (int i=0; i<8; i++) { - sr=(sr<<1)|((wt->data[i*wt->len/8]*2)/(wt->max+1)); - } - rWrite(10,sr); - } -} - void DivPlatformPET::writeOutVol() { if (chan.active && !isMuted && chan.outVol>0) { if (regPool[11]!=16) { rWrite(11,16); - rWrite(10,regPool[10]); + rWrite(10,chan.wave); } } else { rWrite(11,0); @@ -122,7 +109,7 @@ void DivPlatformPET::tick() { if (chan.std.hadWave) { if (chan.wave!=chan.std.wave) { chan.wave=chan.std.wave; - updateWave(); + rWrite(10,chan.wave); } } if (chan.freqChanged || chan.keyOn || chan.keyOff) { @@ -135,10 +122,6 @@ void DivPlatformPET::tick() { chan.outVol=chan.vol; writeOutVol(); } - if (chan.wave<0) { - chan.wave=0; - updateWave(); - } chan.keyOn=false; } if (chan.keyOff) { @@ -195,8 +178,7 @@ int DivPlatformPET::dispatch(DivCommand c) { break; case DIV_CMD_WAVE: chan.wave=c.value; - updateWave(); - chan.keyOn=true; + rWrite(10,chan.wave); break; case DIV_CMD_NOTE_PORTA: { int destFreq=NOTE_PERIODIC(c.value2); @@ -252,7 +234,6 @@ void DivPlatformPET::muteChannel(int ch, bool mute) { void DivPlatformPET::forceIns() { chan.insChanged=true; chan.freqChanged=true; - updateWave(); writeOutVol(); } @@ -271,19 +252,12 @@ int DivPlatformPET::getRegisterPoolSize() { void DivPlatformPET::reset() { memset(regPool,0,16); chan=Channel(); - chan.vol=1; } bool DivPlatformPET::isStereo() { return false; } -void DivPlatformPET::notifyWaveChange(int wave) { - if (chan.wave==wave) { - updateWave(); - } -} - void DivPlatformPET::notifyInsDeletion(void* ins) { chan.std.notifyInsDeletion((DivInstrument*)ins); } diff --git a/src/engine/platform/pet.h b/src/engine/platform/pet.h index bc0a815bb..3b6af48d9 100644 --- a/src/engine/platform/pet.h +++ b/src/engine/platform/pet.h @@ -47,7 +47,7 @@ class DivPlatformPET: public DivDispatch { inPorta(false), vol(1), outVol(1), - wave(-1), + wave(0b00001111), sreg(0), cnt(0), out(0) {} @@ -56,7 +56,6 @@ class DivPlatformPET: public DivDispatch { bool isMuted; unsigned char regPool[16]; - void updateWave(); friend void putDispatchChan(void*,int,int); public: void acquire(short* bufL, short* bufR, size_t start, size_t len); @@ -68,7 +67,6 @@ class DivPlatformPET: public DivDispatch { void forceIns(); void tick(); void muteChannel(int ch, bool mute); - void notifyWaveChange(int wave); void notifyInsDeletion(void* ins); bool isStereo(); void poke(unsigned int addr, unsigned short val); diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 5d1e23cac..1143e4fe0 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1612,8 +1612,13 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_SAA1099) waveMax=2; if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPLL || ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPZ) waveMax=0; if (ins->type==DIV_INS_MIKEY) waveMax=0; + if (ins->type==DIV_INS_PET) { + waveMax=8; + bitMode=true; + } - const char** waveNames=ayShapeBits; + const char** waveNames=NULL; + if (ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_SAA1099) waveNames=ayShapeBits; if (ins->type==DIV_INS_C64) waveNames=c64ShapeBits; int ex1Max=(ins->type==DIV_INS_AY8930)?8:0; From 8c961f0aae9953d8696b86946aae1639566bc576 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 21 Mar 2022 14:11:28 -0500 Subject: [PATCH 344/637] add backup functionality - UNTESTED --- src/engine/engine.h | 3 ++- src/engine/fileOps.cpp | 8 ++++--- src/gui/gui.cpp | 47 ++++++++++++++++++++++++++++++++++++++++++ src/gui/gui.h | 8 +++++++ 4 files changed, 62 insertions(+), 4 deletions(-) diff --git a/src/engine/engine.h b/src/engine/engine.h index ba6a770e5..958508a3a 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -278,7 +278,8 @@ class DivEngine { // save as .dmf. SafeWriter* saveDMF(unsigned char version); // save as .fur. - SafeWriter* saveFur(); + // if notPrimary is true then the song will not be altered + SafeWriter* saveFur(bool notPrimary=false); // build a ROM file (TODO). // specify system to build ROM for. SafeWriter* buildROM(int sys); diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 23570c473..afe5df630 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -1728,7 +1728,7 @@ bool DivEngine::load(unsigned char* f, size_t slen) { return false; } -SafeWriter* DivEngine::saveFur() { +SafeWriter* DivEngine::saveFur(bool notPrimary) { int insPtr[256]; int wavePtr[256]; int samplePtr[256]; @@ -1736,8 +1736,10 @@ SafeWriter* DivEngine::saveFur() { size_t ptrSeek; warnings=""; - song.isDMF=false; - song.version=DIV_ENGINE_VERSION; + if (!notPrimary) { + song.isDMF=false; + song.version=DIV_ENGINE_VERSION; + } SafeWriter* w=new SafeWriter; w->init(); diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index b1dfc9876..eb62f5d1d 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -52,11 +52,13 @@ extern "C" { #include #include "../utfutils.h" #define LAYOUT_INI "\\layout.ini" +#define BACKUP_FUR "\\backup.fur" #else #include #include #include #define LAYOUT_INI "/layout.ini" +#define BACKUP_FUR "/backup.fur" #endif bool Particle::update(float frameTime) { @@ -4802,11 +4804,13 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { int FurnaceGUI::save(String path, int dmfVersion) { SafeWriter* w; + backupLock.lock(); if (dmfVersion) { w=e->saveDMF(dmfVersion); } else { w=e->saveFur(); } + backupLock.unlock(); if (w==NULL) { lastError=e->getLastError(); return 3; @@ -6411,6 +6415,41 @@ bool FurnaceGUI::loop() { ImGui::EndPopup(); } + // backup trigger + if (modified) { + if (backupTimer>0) { + backupTimer-=ImGui::GetIO().DeltaTime; + if (backupTimer<=0) { + backupTask=std::async(std::launch::async,[this]() -> bool { + if (backupPath==curFileName) { + logD("backup file open. not saving backup.\n"); + return true; + } + logD("saving backup...\n"); + backupLock.lock(); + SafeWriter* w=e->saveFur(true); + backupLock.unlock(); + + if (w!=NULL) { + FILE* outFile=ps_fopen(backupPath.c_str(),"wb"); + if (outFile!=NULL) { + if (fwrite(w->getFinalBuf(),1,w->size(),outFile)!=w->size()) { + logW("did not write backup entirely: %s!\n",strerror(errno)); + fclose(outFile); + w->finish(); + } + } else { + logW("could not save backup: %s!\n",strerror(errno)); + w->finish(); + } + } + backupTimer=30.0; + return true; + }); + } + } + } + SDL_SetRenderDrawColor(sdlRend,uiColors[GUI_COLOR_BACKGROUND].x*255, uiColors[GUI_COLOR_BACKGROUND].y*255, uiColors[GUI_COLOR_BACKGROUND].z*255, @@ -6930,6 +6969,7 @@ bool FurnaceGUI::init() { } strncpy(finalLayoutPath,(e->getConfigPath()+String(LAYOUT_INI)).c_str(),4095); + backupPath=e->getConfigPath()+String(BACKUP_FUR); prepareLayout(); ImGui::GetIO().ConfigFlags|=ImGuiConfigFlags_DockingEnable; @@ -6969,6 +7009,7 @@ bool FurnaceGUI::init() { #ifdef __APPLE__ SDL_RaiseWindow(sdlWin); #endif + return true; } @@ -7020,6 +7061,11 @@ bool FurnaceGUI::finish() { for (int i=0; i #include #include +#include +#include #include #include "fileDialog.h" @@ -512,6 +514,11 @@ class FurnaceGUI { double aboutScroll, aboutSin; float aboutHue; + double backupTimer; + std::future backupTask; + std::mutex backupLock; + String backupPath; + ImFont* mainFont; ImFont* iconFont; ImFont* patFont; @@ -875,6 +882,7 @@ class FurnaceGUI { void updateScroll(int amount); void addScroll(int amount); void setFileName(String name); + void runBackupThread(); bool loop(); bool finish(); bool init(); From 854d0fea488efd2ef9bd69c4de8e1b8aa634bf03 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 21 Mar 2022 14:32:33 -0500 Subject: [PATCH 345/637] GUI: add option to restore backup --- src/gui/gui.cpp | 20 +++++++++++++++++++- src/gui/gui.h | 2 ++ src/gui/settings.cpp | 3 +++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index eb62f5d1d..42c12ed66 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -3672,6 +3672,15 @@ void FurnaceGUI::doAction(int what) { openFileDialog(GUI_FILE_OPEN); } break; + case GUI_ACTION_OPEN_BACKUP: + if (modified) { + showWarning("Unsaved changes! Are you sure?",GUI_WARN_OPEN_BACKUP); + } else { + if (load(backupPath)>0) { + showError("No backup available! (or unable to open it)"); + } + } + break; case GUI_ACTION_SAVE: if (curFileName=="" || e->song.version>=0xff00) { openFileDialog(GUI_FILE_SAVE); @@ -5993,6 +6002,10 @@ bool FurnaceGUI::loop() { ImGui::EndMenu(); } ImGui::Separator(); + if (ImGui::MenuItem("restore backup",BIND_FOR(GUI_ACTION_OPEN_BACKUP))) { + doAction(GUI_ACTION_OPEN_BACKUP); + } + ImGui::Separator(); if (ImGui::MenuItem("exit")) { if (modified) { showWarning("Unsaved changes! Are you sure you want to quit?",GUI_WARN_QUIT); @@ -6392,6 +6405,11 @@ bool FurnaceGUI::loop() { case GUI_WARN_OPEN: openFileDialog(GUI_FILE_OPEN); break; + case GUI_WARN_OPEN_BACKUP: + if (load(backupPath)>0) { + showError("No backup available! (or unable to open it)"); + } + break; case GUI_WARN_OPEN_DROP: if (load(nextFile)>0) { showError(fmt::sprintf("Error while loading file! (%s)",lastError)); @@ -7093,7 +7111,7 @@ FurnaceGUI::FurnaceGUI(): aboutScroll(0), aboutSin(0), aboutHue(0.0f), - backupTimer(0.1), + backupTimer(15.0), curIns(0), curWave(0), curSample(0), diff --git a/src/gui/gui.h b/src/gui/gui.h index 28e28f374..ad73fba96 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -187,6 +187,7 @@ enum FurnaceGUIWarnings { GUI_WARN_QUIT, GUI_WARN_NEW, GUI_WARN_OPEN, + GUI_WARN_OPEN_BACKUP, GUI_WARN_OPEN_DROP, GUI_WARN_RESET_LAYOUT, GUI_WARN_GENERIC @@ -201,6 +202,7 @@ enum FurnaceGUIFMAlgs { enum FurnaceGUIActions { GUI_ACTION_GLOBAL_MIN=0, GUI_ACTION_OPEN, + GUI_ACTION_OPEN_BACKUP, GUI_ACTION_SAVE, GUI_ACTION_SAVE_AS, GUI_ACTION_UNDO, diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index be1b6b169..ed17a1e3b 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -579,6 +579,7 @@ void FurnaceGUI::drawSettings() { KEYBIND_CONFIG_BEGIN("keysGlobal"); UI_KEYBIND_CONFIG(GUI_ACTION_OPEN,"Open file"); + UI_KEYBIND_CONFIG(GUI_ACTION_OPEN_BACKUP,"Restore backup"); UI_KEYBIND_CONFIG(GUI_ACTION_SAVE,"Save file"); UI_KEYBIND_CONFIG(GUI_ACTION_SAVE_AS,"Save as"); UI_KEYBIND_CONFIG(GUI_ACTION_UNDO,"Undo"); @@ -970,6 +971,7 @@ void FurnaceGUI::syncSettings() { // keybinds LOAD_KEYBIND(GUI_ACTION_OPEN,FURKMOD_CMD|SDLK_o); + LOAD_KEYBIND(GUI_ACTION_OPEN_BACKUP,0); LOAD_KEYBIND(GUI_ACTION_SAVE,FURKMOD_CMD|SDLK_s); LOAD_KEYBIND(GUI_ACTION_SAVE_AS,FURKMOD_CMD|FURKMOD_SHIFT|SDLK_s); LOAD_KEYBIND(GUI_ACTION_UNDO,FURKMOD_CMD|SDLK_z); @@ -1251,6 +1253,7 @@ void FurnaceGUI::commitSettings() { PUT_UI_COLOR(GUI_COLOR_PLAYBACK_STAT); SAVE_KEYBIND(GUI_ACTION_OPEN); + SAVE_KEYBIND(GUI_ACTION_OPEN_BACKUP); SAVE_KEYBIND(GUI_ACTION_SAVE); SAVE_KEYBIND(GUI_ACTION_SAVE_AS); SAVE_KEYBIND(GUI_ACTION_UNDO); From 6dc231917c42f6f845a2b5321fcf1fb7787b82b1 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 21 Mar 2022 14:35:04 -0500 Subject: [PATCH 346/637] add Nix package link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a1b648540..15f0dcd2f 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ some people have provided packages for Unix/Unix-like distributions. here's a li ## Nix -(TODO) +[package](https://search.nixos.org/packages?channel=unstable&show=furnace&from=0&size=50&sort=relevance&type=packages&query=furnace) thanks to OPNA2608. # developer info From 0a114b1168bfb20ffcc429eb0a7621feb7077669 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 21 Mar 2022 14:47:10 -0500 Subject: [PATCH 347/637] GUI: don't allow direct saving to the backup file --- src/gui/gui.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 42c12ed66..adf7c1a20 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -3682,7 +3682,7 @@ void FurnaceGUI::doAction(int what) { } break; case GUI_ACTION_SAVE: - if (curFileName=="" || e->song.version>=0xff00) { + if (curFileName=="" || curFileName==backupPath || e->song.version>=0xff00) { openFileDialog(GUI_FILE_SAVE); } else { if (save(curFileName,e->song.isDMF?e->song.version:0)>0) { @@ -5490,7 +5490,7 @@ bool FurnaceGUI::loop() { } ImGui::Separator(); if (ImGui::MenuItem("save",BIND_FOR(GUI_ACTION_SAVE))) { - if (curFileName=="" || e->song.version>=0xff00) { + if (curFileName=="" || curFileName==backupPath || e->song.version>=0xff00) { openFileDialog(GUI_FILE_SAVE); } else { if (save(curFileName,e->song.isDMF?e->song.version:0)>0) { From 1003d9fcb8fe425d6fb9c54794a9e638758b0b89 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 21 Mar 2022 16:17:51 -0500 Subject: [PATCH 348/637] GUI: more "modified" status situations fixes #236 --- src/engine/engine.cpp | 69 ++++++++++++++++++++++- src/engine/engine.h | 8 ++- src/engine/fileOps.cpp | 12 +++- src/gui/gui.cpp | 70 ++++++++++++----------- src/gui/gui.h | 2 + src/gui/insEdit.cpp | 16 +++--- src/gui/orders.cpp | 28 ++++++---- src/gui/sampleEdit.cpp | 122 ++++++++++++++++++++++++----------------- 8 files changed, 223 insertions(+), 104 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 3a6812adf..2b3283917 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -584,6 +584,7 @@ void DivEngine::renderSamples() { void DivEngine::createNew(const int* description) { quitDispatch(); isBusy.lock(); + saveLock.lock(); song.unload(); song=DivSong(); if (description!=NULL) { @@ -602,6 +603,7 @@ void DivEngine::createNew(const int* description) { } recalcChans(); renderSamples(); + saveLock.unlock(); isBusy.unlock(); initDispatch(); isBusy.lock(); @@ -1214,8 +1216,10 @@ int DivEngine::addInstrument(int refChan) { int insCount=(int)song.ins.size(); ins->name=fmt::sprintf("Instrument %d",insCount); ins->type=getPreferInsType(refChan); + saveLock.lock(); song.ins.push_back(ins); song.insLen=insCount+1; + saveLock.unlock(); isBusy.unlock(); return insCount; } @@ -1230,6 +1234,9 @@ enum DivInsFormats { DIV_INSFORMAT_SBI, }; +// TODO: re-organize this function to: +// - support replacing instruments +// - support instrument formats which contain multiple instruments bool DivEngine::addInstrumentFromFile(const char* path) { warnings=""; @@ -1354,7 +1361,7 @@ bool DivEngine::addInstrumentFromFile(const char* path) { } } - // TDOO these really should be refactored to separate functions/cpp files per instrument file type. + // TDOO these really should be re-organized to separate functions per instrument file type. switch (format) { case DIV_INSFORMAT_DMP: { // this is a ridiculous mess @@ -1937,15 +1944,18 @@ bool DivEngine::addInstrumentFromFile(const char* path) { } isBusy.lock(); + saveLock.lock(); int insCount=(int)song.ins.size(); song.ins.push_back(ins); song.insLen=insCount+1; + saveLock.unlock(); isBusy.unlock(); return true; } void DivEngine::delInstrument(int index) { isBusy.lock(); + saveLock.lock(); if (index>=0 && index<(int)song.ins.size()) { for (int i=0; inotifyInsDeletion(song.ins[index]); @@ -1964,15 +1974,18 @@ void DivEngine::delInstrument(int index) { } } } + saveLock.unlock(); isBusy.unlock(); } int DivEngine::addWave() { isBusy.lock(); + saveLock.lock(); DivWavetable* wave=new DivWavetable; int waveCount=(int)song.wave.size(); song.wave.push_back(wave); song.waveLen=waveCount+1; + saveLock.unlock(); isBusy.unlock(); return waveCount; } @@ -2088,30 +2101,36 @@ bool DivEngine::addWaveFromFile(const char* path) { } isBusy.lock(); + saveLock.lock(); int waveCount=(int)song.wave.size(); song.wave.push_back(wave); song.waveLen=waveCount+1; + saveLock.unlock(); isBusy.unlock(); return true; } void DivEngine::delWave(int index) { isBusy.lock(); + saveLock.lock(); if (index>=0 && index<(int)song.wave.size()) { delete song.wave[index]; song.wave.erase(song.wave.begin()+index); song.waveLen=song.wave.size(); } + saveLock.unlock(); isBusy.unlock(); } int DivEngine::addSample() { isBusy.lock(); + saveLock.lock(); DivSample* sample=new DivSample; int sampleCount=(int)song.sample.size(); sample->name=fmt::sprintf("Sample %d",sampleCount); song.sample.push_back(sample); song.sampleLen=sampleCount+1; + saveLock.unlock(); renderSamples(); isBusy.unlock(); return sampleCount; @@ -2189,8 +2208,10 @@ bool DivEngine::addSampleFromFile(const char* path) { if (sample->centerRate<4000) sample->centerRate=4000; if (sample->centerRate>64000) sample->centerRate=64000; sf_close(f); + saveLock.lock(); song.sample.push_back(sample); song.sampleLen=sampleCount+1; + saveLock.unlock(); renderSamples(); isBusy.unlock(); return sampleCount; @@ -2198,12 +2219,14 @@ bool DivEngine::addSampleFromFile(const char* path) { void DivEngine::delSample(int index) { isBusy.lock(); + saveLock.lock(); if (index>=0 && index<(int)song.sample.size()) { delete song.sample[index]; song.sample.erase(song.sample.begin()+index); song.sampleLen=song.sample.size(); renderSamples(); } + saveLock.unlock(); isBusy.unlock(); } @@ -2232,11 +2255,14 @@ void DivEngine::addOrder(bool duplicate, bool where) { } } if (where) { // at the end + saveLock.lock(); for (int i=0; icurOrder; j--) { song.orders.ord[i][j]=song.orders.ord[i][j-1]; @@ -2244,6 +2270,7 @@ void DivEngine::addOrder(bool duplicate, bool where) { song.orders.ord[i][curOrder+1]=order[i]; } song.ordersLen++; + saveLock.unlock(); curOrder++; if (playing && !freelance) { playSub(false); @@ -2280,11 +2307,14 @@ void DivEngine::deepCloneOrder(bool where) { } } if (where) { // at the end + saveLock.lock(); for (int i=0; icurOrder; j--) { song.orders.ord[i][j]=song.orders.ord[i][j-1]; @@ -2292,6 +2322,7 @@ void DivEngine::deepCloneOrder(bool where) { song.orders.ord[i][curOrder+1]=order[i]; } song.ordersLen++; + saveLock.unlock(); curOrder++; if (playing && !freelance) { playSub(false); @@ -2303,12 +2334,14 @@ void DivEngine::deepCloneOrder(bool where) { void DivEngine::deleteOrder() { if (song.ordersLen<=1) return; isBusy.lock(); + saveLock.lock(); for (int i=0; i=song.ordersLen) curOrder=song.ordersLen-1; if (playing && !freelance) { playSub(false); @@ -2322,11 +2355,13 @@ void DivEngine::moveOrderUp() { isBusy.unlock(); return; } + saveLock.lock(); for (int i=0; i=(int)song.ins.size()) return false; isBusy.lock(); DivInstrument* prev=song.ins[which]; + saveLock.lock(); song.ins[which]=song.ins[which-1]; song.ins[which-1]=prev; exchangeIns(which,which-1); + saveLock.unlock(); isBusy.unlock(); return true; } @@ -2382,8 +2421,10 @@ bool DivEngine::moveWaveUp(int which) { if (which<1 || which>=(int)song.wave.size()) return false; isBusy.lock(); DivWavetable* prev=song.wave[which]; + saveLock.lock(); song.wave[which]=song.wave[which-1]; song.wave[which-1]=prev; + saveLock.unlock(); isBusy.unlock(); return true; } @@ -2392,8 +2433,10 @@ bool DivEngine::moveSampleUp(int which) { if (which<1 || which>=(int)song.sample.size()) return false; isBusy.lock(); DivSample* prev=song.sample[which]; + saveLock.lock(); song.sample[which]=song.sample[which-1]; song.sample[which-1]=prev; + saveLock.unlock(); isBusy.unlock(); return true; } @@ -2402,9 +2445,11 @@ bool DivEngine::moveInsDown(int which) { if (which<0 || which>=((int)song.ins.size())-1) return false; isBusy.lock(); DivInstrument* prev=song.ins[which]; + saveLock.lock(); song.ins[which]=song.ins[which+1]; song.ins[which+1]=prev; exchangeIns(which,which+1); + saveLock.unlock(); isBusy.unlock(); return true; } @@ -2413,8 +2458,10 @@ bool DivEngine::moveWaveDown(int which) { if (which<0 || which>=((int)song.wave.size())-1) return false; isBusy.lock(); DivWavetable* prev=song.wave[which]; + saveLock.lock(); song.wave[which]=song.wave[which+1]; song.wave[which+1]=prev; + saveLock.unlock(); isBusy.unlock(); return true; } @@ -2423,8 +2470,10 @@ bool DivEngine::moveSampleDown(int which) { if (which<0 || which>=((int)song.sample.size())-1) return false; isBusy.lock(); DivSample* prev=song.sample[which]; + saveLock.lock(); song.sample[which]=song.sample[which+1]; song.sample[which+1]=prev; + saveLock.unlock(); isBusy.unlock(); return true; } @@ -2465,7 +2514,9 @@ void DivEngine::setOrder(unsigned char order) { void DivEngine::setSysFlags(int system, unsigned int flags, bool restart) { isBusy.lock(); + saveLock.lock(); song.systemFlags[system]=flags; + saveLock.unlock(); disCont[system].dispatch->setFlags(song.systemFlags[system]); disCont[system].setRates(got.rate); if (restart && isPlaying()) { @@ -2476,6 +2527,7 @@ void DivEngine::setSysFlags(int system, unsigned int flags, bool restart) { void DivEngine::setSongRate(float hz, bool pal) { isBusy.lock(); + saveLock.lock(); song.pal=!pal; song.hz=hz; // what? @@ -2490,6 +2542,7 @@ void DivEngine::setSongRate(float hz, bool pal) { divider=50; } } + saveLock.unlock(); isBusy.unlock(); } @@ -2539,6 +2592,20 @@ void DivEngine::synchronized(const std::function& what) { isBusy.unlock(); } +void DivEngine::lockSave(const std::function& what) { + saveLock.lock(); + what(); + saveLock.unlock(); +} + +void DivEngine::lockEngine(const std::function& what) { + isBusy.lock(); + saveLock.lock(); + what(); + saveLock.unlock(); + isBusy.unlock(); +} + TAAudioDesc& DivEngine::getAudioDescWant() { return want; } diff --git a/src/engine/engine.h b/src/engine/engine.h index 958508a3a..9f7464e37 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -201,7 +201,7 @@ class DivEngine { std::map conf; std::queue pendingNotes; bool isMuted[DIV_MAX_CHANS]; - std::mutex isBusy; + std::mutex isBusy, saveLock; String configPath; String configFile; String lastError; @@ -621,6 +621,12 @@ class DivEngine { // perform secure/sync operation void synchronized(const std::function& what); + // perform secure/sync song operation + void lockSave(const std::function& what); + + // perform secure/sync song operation (and lock audio too) + void lockEngine(const std::function& what); + // get audio desc want TAAudioDesc& getAudioDescWant(); diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index afe5df630..460f7d8d8 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -733,10 +733,12 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { if (active) quitDispatch(); isBusy.lock(); + saveLock.lock(); song.unload(); song=ds; recalcChans(); renderSamples(); + saveLock.unlock(); isBusy.unlock(); if (active) { initDispatch(); @@ -1219,10 +1221,12 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { if (active) quitDispatch(); isBusy.lock(); + saveLock.lock(); song.unload(); song=ds; recalcChans(); renderSamples(); + saveLock.unlock(); isBusy.unlock(); if (active) { initDispatch(); @@ -1583,10 +1587,12 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { if (active) quitDispatch(); isBusy.lock(); + saveLock.lock(); song.unload(); song=ds; recalcChans(); renderSamples(); + saveLock.unlock(); isBusy.unlock(); if (active) { initDispatch(); @@ -1729,6 +1735,7 @@ bool DivEngine::load(unsigned char* f, size_t slen) { } SafeWriter* DivEngine::saveFur(bool notPrimary) { + saveLock.lock(); int insPtr[256]; int wavePtr[256]; int samplePtr[256]; @@ -1969,6 +1976,7 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { w->writeI(i); } + saveLock.unlock(); return w; } @@ -2028,6 +2036,7 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { lastError="this system is not possible on .dmf"; return NULL; } + saveLock.lock(); warnings=""; song.version=version; song.isDMF=true; @@ -2259,7 +2268,8 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { w->writeC(16); w->write(i->data16,i->length16); } - + + saveLock.unlock(); return w; } diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index adf7c1a20..17d14f6ef 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1004,13 +1004,17 @@ void FurnaceGUI::drawSongInfo() { ImGui::TableNextColumn(); float avail=ImGui::GetContentRegionAvail().x; ImGui::SetNextItemWidth(avail); - if (ImGui::InputText("##Name",&e->song.name)) updateWindowTitle(); + if (ImGui::InputText("##Name",&e->song.name)) { MARK_MODIFIED + updateWindowTitle(); + } ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::Text("Author"); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(avail); - ImGui::InputText("##Author",&e->song.author); + if (ImGui::InputText("##Author",&e->song.author)) { + MARK_MODIFIED; + } ImGui::EndTable(); } @@ -1026,7 +1030,7 @@ void FurnaceGUI::drawSongInfo() { float avail=ImGui::GetContentRegionAvail().x; ImGui::SetNextItemWidth(avail); unsigned char realTB=e->song.timeBase+1; - if (ImGui::InputScalar("##TimeBase",ImGuiDataType_U8,&realTB,&_ONE,&_THREE)) { + if (ImGui::InputScalar("##TimeBase",ImGuiDataType_U8,&realTB,&_ONE,&_THREE)) { MARK_MODIFIED if (realTB<1) realTB=1; if (realTB>16) realTB=16; e->song.timeBase=realTB-1; @@ -1039,13 +1043,13 @@ void FurnaceGUI::drawSongInfo() { ImGui::Text("Speed"); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(avail); - if (ImGui::InputScalar("##Speed1",ImGuiDataType_U8,&e->song.speed1,&_ONE,&_THREE)) { + if (ImGui::InputScalar("##Speed1",ImGuiDataType_U8,&e->song.speed1,&_ONE,&_THREE)) { MARK_MODIFIED if (e->song.speed1<1) e->song.speed1=1; if (e->isPlaying()) play(); } ImGui::TableNextColumn(); ImGui::SetNextItemWidth(avail); - if (ImGui::InputScalar("##Speed2",ImGuiDataType_U8,&e->song.speed2,&_ONE,&_THREE)) { + if (ImGui::InputScalar("##Speed2",ImGuiDataType_U8,&e->song.speed2,&_ONE,&_THREE)) { MARK_MODIFIED if (e->song.speed2<1) e->song.speed2=1; if (e->isPlaying()) play(); } @@ -1055,10 +1059,14 @@ void FurnaceGUI::drawSongInfo() { ImGui::Text("Highlight"); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(avail); - ImGui::InputScalar("##Highlight1",ImGuiDataType_U8,&e->song.hilightA,&_ONE,&_THREE); + if (ImGui::InputScalar("##Highlight1",ImGuiDataType_U8,&e->song.hilightA,&_ONE,&_THREE)) { + MARK_MODIFIED; + } ImGui::TableNextColumn(); ImGui::SetNextItemWidth(avail); - ImGui::InputScalar("##Highlight2",ImGuiDataType_U8,&e->song.hilightB,&_ONE,&_THREE); + if (ImGui::InputScalar("##Highlight2",ImGuiDataType_U8,&e->song.hilightB,&_ONE,&_THREE)) { + MARK_MODIFIED; + } ImGui::TableNextRow(); ImGui::TableNextColumn(); @@ -1066,7 +1074,7 @@ void FurnaceGUI::drawSongInfo() { ImGui::TableNextColumn(); ImGui::SetNextItemWidth(avail); int patLen=e->song.patLen; - if (ImGui::InputInt("##PatLength",&patLen,1,3)) { + if (ImGui::InputInt("##PatLength",&patLen,1,3)) { MARK_MODIFIED if (patLen<1) patLen=1; if (patLen>256) patLen=256; e->song.patLen=patLen; @@ -1078,7 +1086,7 @@ void FurnaceGUI::drawSongInfo() { ImGui::TableNextColumn(); ImGui::SetNextItemWidth(avail); int ordLen=e->song.ordersLen; - if (ImGui::InputInt("##OrdLength",&ordLen,1,3)) { + if (ImGui::InputInt("##OrdLength",&ordLen,1,3)) { MARK_MODIFIED if (ordLen<1) ordLen=1; if (ordLen>127) ordLen=127; e->song.ordersLen=ordLen; @@ -1092,7 +1100,7 @@ void FurnaceGUI::drawSongInfo() { ImGui::TableNextColumn(); ImGui::SetNextItemWidth(avail); float setHz=tempoView?e->song.hz*2.5:e->song.hz; - if (ImGui::InputFloat("##Rate",&setHz,1.0f,1.0f,"%g")) { + if (ImGui::InputFloat("##Rate",&setHz,1.0f,1.0f,"%g")) { MARK_MODIFIED if (tempoView) setHz/=2.5; if (setHz<10) setHz=10; if (setHz>999) setHz=999; @@ -1118,7 +1126,7 @@ void FurnaceGUI::drawSongInfo() { ImGui::TableNextColumn(); float tune=e->song.tuning; ImGui::SetNextItemWidth(avail); - if (ImGui::InputFloat("##Tuning",&tune,1.0f,3.0f,"%g")) { + if (ImGui::InputFloat("##Tuning",&tune,1.0f,3.0f,"%g")) { MARK_MODIFIED if (tune<220.0f) tune=220.0f; if (tune>880.0f) tune=880.0f; e->song.tuning=tune; @@ -2713,7 +2721,7 @@ void FurnaceGUI::makeUndo(ActionType action) { break; } if (doPush) { - modified=true; + MARK_MODIFIED; undoHist.push_back(s); redoHist.clear(); if (undoHist.size()>settings.maxUndoSteps) undoHist.pop_front(); @@ -3509,7 +3517,7 @@ void FurnaceGUI::doUndo() { if (undoHist.empty()) return; UndoStep& us=undoHist.back(); redoHist.push_back(us); - modified=true; + MARK_MODIFIED; switch (us.type) { case GUI_UNDO_CHANGE_ORDER: @@ -3555,7 +3563,7 @@ void FurnaceGUI::doRedo() { if (redoHist.empty()) return; UndoStep& us=redoHist.back(); undoHist.push_back(us); - modified=true; + MARK_MODIFIED; switch (us.type) { case GUI_UNDO_CHANGE_ORDER: @@ -4095,14 +4103,14 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_INS_LIST_ADD: curIns=e->addInstrument(cursor.xCoarse); - modified=true; + MARK_MODIFIED; break; case GUI_ACTION_INS_LIST_DUPLICATE: if (curIns>=0 && curIns<(int)e->song.ins.size()) { int prevIns=curIns; curIns=e->addInstrument(cursor.xCoarse); (*e->song.ins[curIns])=(*e->song.ins[prevIns]); - modified=true; + MARK_MODIFIED; } break; case GUI_ACTION_INS_LIST_OPEN: @@ -4120,7 +4128,7 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_INS_LIST_DELETE: if (curIns>=0 && curIns<(int)e->song.ins.size()) { e->delInstrument(curIns); - modified=true; + MARK_MODIFIED; if (curIns>=(int)e->song.ins.size()) { curIns--; } @@ -4138,14 +4146,14 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_WAVE_LIST_ADD: curWave=e->addWave(); - modified=true; + MARK_MODIFIED; break; case GUI_ACTION_WAVE_LIST_DUPLICATE: if (curWave>=0 && curWave<(int)e->song.wave.size()) { int prevWave=curWave; curWave=e->addWave(); (*e->song.wave[curWave])=(*e->song.wave[prevWave]); - modified=true; + MARK_MODIFIED; } break; case GUI_ACTION_WAVE_LIST_OPEN: @@ -4163,7 +4171,7 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_WAVE_LIST_DELETE: if (curWave>=0 && curWave<(int)e->song.wave.size()) { e->delWave(curWave); - modified=true; + MARK_MODIFIED; if (curWave>=(int)e->song.wave.size()) { curWave--; } @@ -4181,7 +4189,7 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_SAMPLE_LIST_ADD: curSample=e->addSample(); - modified=true; + MARK_MODIFIED; break; case GUI_ACTION_SAMPLE_LIST_OPEN: openFileDialog(GUI_FILE_SAMPLE_OPEN); @@ -4197,7 +4205,7 @@ void FurnaceGUI::doAction(int what) { break; case GUI_ACTION_SAMPLE_LIST_DELETE: e->delSample(curSample); - modified=true; + MARK_MODIFIED; if (curSample>=(int)e->song.sample.size()) { curSample--; } @@ -4498,7 +4506,9 @@ void FurnaceGUI::keyDown(SDL_Event& ev) { int num=valueKeys.at(ev.key.keysym.sym); if (orderCursor>=0 && orderCursorgetTotalChannelCount()) { int curOrder=e->getOrder(); - e->song.orders.ord[orderCursor][curOrder]=((e->song.orders.ord[orderCursor][curOrder]<<4)|num)&0x7f; + e->lockSave([this,curOrder,num]() { + e->song.orders.ord[orderCursor][curOrder]=((e->song.orders.ord[orderCursor][curOrder]<<4)|num)&0x7f; + }); if (orderEditMode==2 || orderEditMode==3) { curNibble=!curNibble; if (!curNibble) { @@ -4813,13 +4823,11 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { int FurnaceGUI::save(String path, int dmfVersion) { SafeWriter* w; - backupLock.lock(); if (dmfVersion) { w=e->saveDMF(dmfVersion); } else { w=e->saveFur(); } - backupLock.unlock(); if (w==NULL) { lastError=e->getLastError(); return 3; @@ -5062,7 +5070,7 @@ void FurnaceGUI::processDrags(int dragX, int dragY) { if (ynotifyWaveChange(curWave); - modified=true; + MARK_MODIFIED; } } if (sampleDragActive) { @@ -5363,7 +5371,9 @@ bool FurnaceGUI::loop() { break; } case SDL_MOUSEBUTTONUP: - if (macroDragActive || macroLoopDragActive || waveDragActive || (sampleDragActive && sampleDragMode)) modified=true; + if (macroDragActive || macroLoopDragActive || waveDragActive || (sampleDragActive && sampleDragMode)) { + MARK_MODIFIED; + } macroDragActive=false; macroDragBitMode=false; macroDragInitialValue=false; @@ -6272,7 +6282,7 @@ bool FurnaceGUI::loop() { break; case GUI_FILE_SAMPLE_OPEN: e->addSampleFromFile(copyOfName.c_str()); - modified=true; + MARK_MODIFIED; break; case GUI_FILE_SAMPLE_SAVE: if (curSample>=0 && curSample<(int)e->song.sample.size()) { @@ -6299,7 +6309,7 @@ bool FurnaceGUI::loop() { break; case GUI_FILE_WAVE_OPEN: e->addWaveFromFile(copyOfName.c_str()); - modified=true; + MARK_MODIFIED; break; case GUI_FILE_EXPORT_VGM: { SafeWriter* w=e->saveVGM(willExport,vgmExportLoop); @@ -6444,9 +6454,7 @@ bool FurnaceGUI::loop() { return true; } logD("saving backup...\n"); - backupLock.lock(); SafeWriter* w=e->saveFur(true); - backupLock.unlock(); if (w!=NULL) { FILE* outFile=ps_fopen(backupPath.c_str(),"wb"); diff --git a/src/gui/gui.h b/src/gui/gui.h index ad73fba96..75ebab6e2 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -36,6 +36,8 @@ #define handleUnimportant if (settings.insFocusesPattern && patternOpen) {nextWindow=GUI_WINDOW_PATTERN;} #define unimportant(x) if (x) {handleUnimportant} +#define MARK_MODIFIED modified=true; + enum FurnaceGUIColors { GUI_COLOR_BACKGROUND=0, GUI_COLOR_FRAME_BACKGROUND, diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index def41aad3..0257c6073 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -669,11 +669,11 @@ void FurnaceGUI::drawFMEnv(unsigned char tl, unsigned char ar, unsigned char dr, } #define P(x) if (x) { \ - modified=true; \ + MARK_MODIFIED; \ e->notifyInsChange(curIns); \ } -#define PARAMETER modified=true; e->notifyInsChange(curIns); +#define PARAMETER MARK_MODIFIED; e->notifyInsChange(curIns); #define NORMAL_MACRO(macro,macroLen,macroLoop,macroRel,macroMin,macroHeight,macroName,displayName,displayHeight,displayLoop,bitfield,bfVal,drawSlider,sliderVal,sliderLow,macroDispMin,bitOff,macroMode,macroColor,mmlStr,macroAMin,macroAMax,hoverFunc,blockMode) \ ImGui::TableNextRow(); \ @@ -685,7 +685,7 @@ void FurnaceGUI::drawFMEnv(unsigned char tl, unsigned char ar, unsigned char dr, } \ if (displayLoop) { \ ImGui::SetNextItemWidth(lenAvail); \ - if (ImGui::InputScalar("##IMacroLen_" macroName,ImGuiDataType_U8,¯oLen,&_ONE,&_THREE)) { \ + if (ImGui::InputScalar("##IMacroLen_" macroName,ImGuiDataType_U8,¯oLen,&_ONE,&_THREE)) { MARK_MODIFIED \ if (macroLen>127) macroLen=127; \ } \ if (macroMode!=NULL) { \ @@ -774,7 +774,7 @@ void FurnaceGUI::drawFMEnv(unsigned char tl, unsigned char ar, unsigned char dr, } \ if (displayLoop) { \ ImGui::SetNextItemWidth(lenAvail); \ - if (ImGui::InputScalar("##IOPMacroLen_" #op macroName,ImGuiDataType_U8,¯oLen,&_ONE,&_THREE)) { \ + if (ImGui::InputScalar("##IOPMacroLen_" #op macroName,ImGuiDataType_U8,¯oLen,&_ONE,&_THREE)) { MARK_MODIFIED \ if (macroLen>127) macroLen=127; \ } \ } \ @@ -924,7 +924,9 @@ void FurnaceGUI::drawInsEdit() { ImGui::Text("no instrument selected"); } else { DivInstrument* ins=e->song.ins[curIns]; - ImGui::InputText("Name",&ins->name); + if (ImGui::InputText("Name",&ins->name)) { + MARK_MODIFIED; + } if (ins->type<0 || ins->type>=DIV_INS_MAX) ins->type=DIV_INS_FM; int insType=ins->type; if (ImGui::Combo("Type",&insType,insTypes,DIV_INS_MAX,DIV_INS_MAX)) { @@ -2011,7 +2013,7 @@ void FurnaceGUI::drawWaveEdit() { if (wave->len<1) wave->len=1; e->notifyWaveChange(curWave); if (wavePreviewOn) e->previewWave(curWave,wavePreviewNote); - modified=true; + MARK_MODIFIED; } ImGui::SameLine(); ImGui::Text("Height"); @@ -2024,7 +2026,7 @@ void FurnaceGUI::drawWaveEdit() { if (wave->max>255) wave->max=255; if (wave->max<1) wave->max=1; e->notifyWaveChange(curWave); - modified=true; + MARK_MODIFIED; } for (int i=0; ilen; i++) { if (wave->data[i]>wave->max) wave->data[i]=wave->max; diff --git a/src/gui/orders.cpp b/src/gui/orders.cpp index 43976fdc0..37faba8d4 100644 --- a/src/gui/orders.cpp +++ b/src/gui/orders.cpp @@ -97,13 +97,15 @@ void FurnaceGUI::drawOrders() { if (curOrder==i) { if (orderEditMode==0) { prepareUndo(GUI_UNDO_CHANGE_ORDER); - if (changeAllOrders) { - for (int k=0; kgetTotalChannelCount(); k++) { - if (e->song.orders.ord[k][i]<0x7f) e->song.orders.ord[k][i]++; + e->lockSave([this,i,j]() { + if (changeAllOrders) { + for (int k=0; kgetTotalChannelCount(); k++) { + if (e->song.orders.ord[k][i]<0x7f) e->song.orders.ord[k][i]++; + } + } else { + if (e->song.orders.ord[j][i]<0x7f) e->song.orders.ord[j][i]++; } - } else { - if (e->song.orders.ord[j][i]<0x7f) e->song.orders.ord[j][i]++; - } + }); e->walkSong(loopOrder,loopRow,loopEnd); makeUndo(GUI_UNDO_CHANGE_ORDER); } else { @@ -130,13 +132,15 @@ void FurnaceGUI::drawOrders() { if (curOrder==i) { if (orderEditMode==0) { prepareUndo(GUI_UNDO_CHANGE_ORDER); - if (changeAllOrders) { - for (int k=0; kgetTotalChannelCount(); k++) { - if (e->song.orders.ord[k][i]>0) e->song.orders.ord[k][i]--; + e->lockSave([this,i,j]() { + if (changeAllOrders) { + for (int k=0; kgetTotalChannelCount(); k++) { + if (e->song.orders.ord[k][i]>0) e->song.orders.ord[k][i]--; + } + } else { + if (e->song.orders.ord[j][i]>0) e->song.orders.ord[j][i]--; } - } else { - if (e->song.orders.ord[j][i]>0) e->song.orders.ord[j][i]--; - } + }); e->walkSong(loopOrder,loopRow,loopEnd); makeUndo(GUI_UNDO_CHANGE_ORDER); } else { diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index 1af7ddf63..55d2e4859 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -60,7 +60,9 @@ void FurnaceGUI::drawSampleEdit() { ImGui::Text("Name"); ImGui::SameLine(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - ImGui::InputText("##SampleName",&sample->name); + if (ImGui::InputText("##SampleName",&sample->name)) { + MARK_MODIFIED; + } if (ImGui::BeginTable("SampleProps",4,ImGuiTableFlags_SizingStretchSame)) { ImGui::TableNextRow(); @@ -75,6 +77,7 @@ void FurnaceGUI::drawSampleEdit() { sample->depth=i; e->renderSamplesP(); updateSampleTex=true; + MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("no undo for sample type change operations!"); @@ -87,7 +90,7 @@ void FurnaceGUI::drawSampleEdit() { ImGui::Text("Rate (Hz)"); ImGui::SameLine(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::InputInt("##SampleRate",&sample->rate,10,200)) { + if (ImGui::InputInt("##SampleRate",&sample->rate,10,200)) { MARK_MODIFIED if (sample->rate<100) sample->rate=100; if (sample->rate>96000) sample->rate=96000; } @@ -96,14 +99,14 @@ void FurnaceGUI::drawSampleEdit() { ImGui::Text("C-4 (Hz)"); ImGui::SameLine(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::InputInt("##SampleCenter",&sample->centerRate,10,200)) { + if (ImGui::InputInt("##SampleCenter",&sample->centerRate,10,200)) { MARK_MODIFIED if (sample->centerRate<100) sample->centerRate=100; if (sample->centerRate>65535) sample->centerRate=65535; } ImGui::TableNextColumn(); bool doLoop=(sample->loopStart>=0); - if (ImGui::Checkbox("Loop",&doLoop)) { + if (ImGui::Checkbox("Loop",&doLoop)) { MARK_MODIFIED if (doLoop) { sample->loopStart=0; } else { @@ -114,7 +117,7 @@ void FurnaceGUI::drawSampleEdit() { if (doLoop) { ImGui::SameLine(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::InputInt("##LoopPosition",&sample->loopStart,1,10)) { + if (ImGui::InputInt("##LoopPosition",&sample->loopStart,1,10)) { MARK_MODIFIED if (sample->loopStart<0 || sample->loopStart>=(int)sample->samples) { sample->loopStart=0; } @@ -161,13 +164,16 @@ void FurnaceGUI::drawSampleEdit() { if (resizeSize>16777215) resizeSize=16777215; } if (ImGui::Button("Resize")) { - e->synchronized([this,sample]() { + e->lockEngine([this,sample]() { if (!sample->resize(resizeSize)) { showError("couldn't resize! make sure your sample is 8 or 16-bit."); } e->renderSamples(); }); updateSampleTex=true; + sampleSelStart=-1; + sampleSelEnd=-1; + MARK_MODIFIED; ImGui::CloseCurrentPopup(); } ImGui::EndPopup(); @@ -208,13 +214,16 @@ void FurnaceGUI::drawSampleEdit() { } ImGui::Combo("Filter",&resampleStrat,resampleStrats,6); if (ImGui::Button("Resample")) { - e->synchronized([this,sample]() { + e->lockEngine([this,sample]() { if (!sample->resample(resampleTarget,resampleStrat)) { showError("couldn't resample! make sure your sample is 8 or 16-bit."); } e->renderSamples(); }); updateSampleTex=true; + sampleSelStart=-1; + sampleSelEnd=-1; + MARK_MODIFIED; ImGui::CloseCurrentPopup(); } ImGui::EndPopup(); @@ -237,7 +246,7 @@ void FurnaceGUI::drawSampleEdit() { ImGui::SameLine(); ImGui::Text("(%.1fdB)",20.0*log10(amplifyVol/100.0f)); if (ImGui::Button("Apply")) { - e->synchronized([this,sample]() { + e->lockEngine([this,sample]() { SAMPLE_OP_BEGIN; float vol=amplifyVol/100.0f; @@ -261,59 +270,61 @@ void FurnaceGUI::drawSampleEdit() { e->renderSamples(); }); + MARK_MODIFIED; ImGui::CloseCurrentPopup(); } ImGui::EndPopup(); } ImGui::SameLine(); if (ImGui::Button(ICON_FA_ARROWS_V "##SNormalize")) { - e->synchronized([this,sample]() { - SAMPLE_OP_BEGIN; - float maxVal=0.0f; + e->lockEngine([this,sample]() { + SAMPLE_OP_BEGIN; + float maxVal=0.0f; - if (sample->depth==16) { - for (unsigned int i=start; idata16[i]/32767.0f); - if (val>maxVal) maxVal=val; - } - if (maxVal>1.0f) maxVal=1.0f; - if (maxVal>0.0f) { - float vol=1.0f/maxVal; + if (sample->depth==16) { for (unsigned int i=start; idata16[i]*vol; - if (val<-32768) val=-32768; - if (val>32767) val=32767; - sample->data16[i]=val; + float val=fabs((float)sample->data16[i]/32767.0f); + if (val>maxVal) maxVal=val; + } + if (maxVal>1.0f) maxVal=1.0f; + if (maxVal>0.0f) { + float vol=1.0f/maxVal; + for (unsigned int i=start; idata16[i]*vol; + if (val<-32768) val=-32768; + if (val>32767) val=32767; + sample->data16[i]=val; + } + } + } else if (sample->depth==8) { + for (unsigned int i=start; idata8[i]/127.0f); + if (val>maxVal) maxVal=val; + } + if (maxVal>1.0f) maxVal=1.0f; + if (maxVal>0.0f) { + float vol=1.0f/maxVal; + for (unsigned int i=start; idata8[i]*vol; + if (val<-128) val=-128; + if (val>127) val=127; + sample->data8[i]=val; + } } } - } else if (sample->depth==8) { - for (unsigned int i=start; idata8[i]/127.0f); - if (val>maxVal) maxVal=val; - } - if (maxVal>1.0f) maxVal=1.0f; - if (maxVal>0.0f) { - float vol=1.0f/maxVal; - for (unsigned int i=start; idata8[i]*vol; - if (val<-128) val=-128; - if (val>127) val=127; - sample->data8[i]=val; - } - } - } - updateSampleTex=true; + updateSampleTex=true; - e->renderSamples(); - }); + e->renderSamples(); + }); + MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Normalize"); } ImGui::SameLine(); if (ImGui::Button(ICON_FA_ARROW_UP "##SFadeIn")) { - e->synchronized([this,sample]() { + e->lockEngine([this,sample]() { SAMPLE_OP_BEGIN; if (sample->depth==16) { @@ -336,13 +347,14 @@ void FurnaceGUI::drawSampleEdit() { e->renderSamples(); }); + MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Fade in"); } ImGui::SameLine(); if (ImGui::Button(ICON_FA_ARROW_DOWN "##SFadeOut")) { - e->synchronized([this,sample]() { + e->lockEngine([this,sample]() { SAMPLE_OP_BEGIN; if (sample->depth==16) { @@ -365,13 +377,14 @@ void FurnaceGUI::drawSampleEdit() { e->renderSamples(); }); + MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Fade out"); } ImGui::SameLine(); if (ImGui::Button(ICON_FA_ERASER "##SSilence")) { - e->synchronized([this,sample]() { + e->lockEngine([this,sample]() { SAMPLE_OP_BEGIN; if (sample->depth==16) { @@ -388,13 +401,14 @@ void FurnaceGUI::drawSampleEdit() { e->renderSamples(); }); + MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Apply silence"); } ImGui::SameLine(); if (ImGui::Button(ICON_FA_TIMES "##SDelete")) { - e->synchronized([this,sample]() { + e->lockEngine([this,sample]() { SAMPLE_OP_BEGIN; sample->strip(start,end); @@ -404,13 +418,14 @@ void FurnaceGUI::drawSampleEdit() { }); sampleSelStart=-1; sampleSelEnd=-1; + MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Delete"); } ImGui::SameLine(); if (ImGui::Button(ICON_FA_CROP "##STrim")) { - e->synchronized([this,sample]() { + e->lockEngine([this,sample]() { SAMPLE_OP_BEGIN; sample->trim(start,end); @@ -420,6 +435,7 @@ void FurnaceGUI::drawSampleEdit() { }); sampleSelStart=-1; sampleSelEnd=-1; + MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Trim"); @@ -428,7 +444,7 @@ void FurnaceGUI::drawSampleEdit() { ImGui::Dummy(ImVec2(4.0*dpiScale,dpiScale)); ImGui::SameLine(); if (ImGui::Button(ICON_FA_BACKWARD "##SReverse")) { - e->synchronized([this,sample]() { + e->lockEngine([this,sample]() { SAMPLE_OP_BEGIN; if (sample->depth==16) { @@ -453,13 +469,14 @@ void FurnaceGUI::drawSampleEdit() { e->renderSamples(); }); + MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Reverse"); } ImGui::SameLine(); if (ImGui::Button(ICON_FA_SORT_AMOUNT_ASC "##SInvert")) { - e->synchronized([this,sample]() { + e->lockEngine([this,sample]() { SAMPLE_OP_BEGIN; if (sample->depth==16) { @@ -478,13 +495,14 @@ void FurnaceGUI::drawSampleEdit() { e->renderSamples(); }); + MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Invert"); } ImGui::SameLine(); if (ImGui::Button(ICON_FA_LEVEL_DOWN "##SSign")) { - e->synchronized([this,sample]() { + e->lockEngine([this,sample]() { SAMPLE_OP_BEGIN; if (sample->depth==16) { @@ -501,6 +519,7 @@ void FurnaceGUI::drawSampleEdit() { e->renderSamples(); }); + MARK_MODIFIED; } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Signed/unsigned exchange"); @@ -561,7 +580,7 @@ void FurnaceGUI::drawSampleEdit() { } if (ImGui::Button("Apply")) { - e->synchronized([this,sample]() { + e->lockEngine([this,sample]() { SAMPLE_OP_BEGIN; float res=1.0-pow(sampleFilterRes,0.5f); float low=0; @@ -608,6 +627,7 @@ void FurnaceGUI::drawSampleEdit() { e->renderSamples(); }); + MARK_MODIFIED; ImGui::CloseCurrentPopup(); } ImGui::EndPopup(); From cbbca2d6c85c02976425d414fd57a2476a229a8d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 21 Mar 2022 16:22:43 -0500 Subject: [PATCH 349/637] GUI: no mention of PET in wave edit --- src/gui/insEdit.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index b3ab2f0d2..ab206e16b 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -2012,7 +2012,7 @@ void FurnaceGUI::drawWaveEdit() { DivWavetable* wave=e->song.wave[curWave]; ImGui::Text("Width"); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("use a width of:\n- 8 for PET\n- 32 on Game Boy, PC Engine and WonderSwan.\nany other widths will be scaled during playback."); + ImGui::SetTooltip("use a width of:\n- any on Amiga/X1-010\n- 32 on Game Boy, PC Engine and WonderSwan\nany other widths will be scaled during playback."); } ImGui::SameLine(); ImGui::SetNextItemWidth(128.0f*dpiScale); @@ -2026,7 +2026,7 @@ void FurnaceGUI::drawWaveEdit() { ImGui::SameLine(); ImGui::Text("Height"); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("use a height of:\n- 1 for PET\n- 15 for Game Boy and WonderSwan\n- 31 for PC Engine\nany other heights will be scaled during playback."); + ImGui::SetTooltip("use a height of:\n- 15 for Game Boy and WonderSwan\n- 31 for PC Engine\nany other heights will be scaled during playback."); } ImGui::SameLine(); ImGui::SetNextItemWidth(128.0f*dpiScale); From e62f9bffd344c2b6128257bdfa375744e30d4e61 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 21 Mar 2022 16:34:19 -0500 Subject: [PATCH 350/637] GUI: re-organize code ins/wave/sample lists now in dataList.cpp wave editor now under its own file --- CMakeLists.txt | 2 + src/gui/dataList.cpp | 348 +++++++++++++++++++++++++++++++++++++++++++ src/gui/gui.cpp | 298 ------------------------------------ src/gui/insEdit.cpp | 130 +--------------- src/gui/waveEdit.cpp | 82 ++++++++++ 5 files changed, 433 insertions(+), 427 deletions(-) create mode 100644 src/gui/dataList.cpp create mode 100644 src/gui/waveEdit.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b3d1fc379..8f51dc9e0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -357,12 +357,14 @@ src/gui/fileDialog.cpp src/gui/intConst.cpp src/gui/guiConst.cpp +src/gui/dataList.cpp src/gui/insEdit.cpp src/gui/orders.cpp src/gui/pattern.cpp src/gui/sampleEdit.cpp src/gui/settings.cpp src/gui/util.cpp +src/gui/waveEdit.cpp src/gui/gui.cpp ) diff --git a/src/gui/dataList.cpp b/src/gui/dataList.cpp new file mode 100644 index 000000000..f32ffda77 --- /dev/null +++ b/src/gui/dataList.cpp @@ -0,0 +1,348 @@ +#include "gui.h" +#include "IconsFontAwesome4.h" +#include "misc/cpp/imgui_stdlib.h" +#include "plot_nolerp.h" +#include "guiConst.h" +#include + +const char* sampleNote[12]={ + "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" +}; + +void FurnaceGUI::drawInsList() { + if (nextWindow==GUI_WINDOW_INS_LIST) { + insListOpen=true; + ImGui::SetNextWindowFocus(); + nextWindow=GUI_WINDOW_NOTHING; + } + if (!insListOpen) return; + if (ImGui::Begin("Instruments",&insListOpen)) { + if (ImGui::Button(ICON_FA_PLUS "##InsAdd")) { + doAction(GUI_ACTION_INS_LIST_ADD); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_FILES_O "##InsClone")) { + doAction(GUI_ACTION_INS_LIST_DUPLICATE); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_FOLDER_OPEN "##InsLoad")) { + doAction(GUI_ACTION_INS_LIST_OPEN); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_FLOPPY_O "##InsSave")) { + doAction(GUI_ACTION_INS_LIST_SAVE); + } + ImGui::SameLine(); + if (ImGui::ArrowButton("InsUp",ImGuiDir_Up)) { + doAction(GUI_ACTION_INS_LIST_MOVE_UP); + } + ImGui::SameLine(); + if (ImGui::ArrowButton("InsDown",ImGuiDir_Down)) { + doAction(GUI_ACTION_INS_LIST_MOVE_DOWN); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_TIMES "##InsDelete")) { + doAction(GUI_ACTION_INS_LIST_DELETE); + } + ImGui::Separator(); + if (ImGui::BeginTable("InsListScroll",1,ImGuiTableFlags_ScrollY)) { + if (settings.unifiedDataView) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text(ICON_FA_TASKS " Instruments"); + ImGui::Indent(); + } + + for (int i=0; i<(int)e->song.ins.size(); i++) { + DivInstrument* ins=e->song.ins[i]; + String name; + switch (ins->type) { + case DIV_INS_FM: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_FM]); + name=fmt::sprintf(ICON_FA_AREA_CHART " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_STD: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_STD]); + name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_GB: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_GB]); + name=fmt::sprintf(ICON_FA_GAMEPAD " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_C64: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_C64]); + name=fmt::sprintf(ICON_FA_KEYBOARD_O " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_AMIGA: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_AMIGA]); + name=fmt::sprintf(ICON_FA_VOLUME_UP " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_PCE: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_PCE]); + name=fmt::sprintf(ICON_FA_ID_BADGE " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_AY: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_AY]); + name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_AY8930: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_AY8930]); + name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_TIA: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_TIA]); + name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_SAA1099: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_SAA1099]); + name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_VIC: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_VIC]); + name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_PET: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_PET]); + name=fmt::sprintf(ICON_FA_SQUARE " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_VRC6: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_VRC6]); + name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_OPLL: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_OPLL]); + name=fmt::sprintf(ICON_FA_AREA_CHART " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_OPL: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_OPL]); + name=fmt::sprintf(ICON_FA_AREA_CHART " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_FDS: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_FDS]); + name=fmt::sprintf(ICON_FA_FLOPPY_O " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_VBOY: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_VBOY]); + name=fmt::sprintf(ICON_FA_BINOCULARS " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_N163: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_N163]); + name=fmt::sprintf(ICON_FA_CALCULATOR " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_SCC: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_SCC]); + name=fmt::sprintf(ICON_FA_CALCULATOR " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_OPZ: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_OPZ]); + name=fmt::sprintf(ICON_FA_AREA_CHART " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_POKEY: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_POKEY]); + name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_BEEPER: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_BEEPER]); + name=fmt::sprintf(ICON_FA_SQUARE " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_SWAN: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_SWAN]); + name=fmt::sprintf(ICON_FA_GAMEPAD " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_MIKEY: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_MIKEY]); + name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_VERA: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_VERA]); + name=fmt::sprintf(ICON_FA_KEYBOARD_O " %.2X: %s##_INS%d",i,ins->name,i); + break; + case DIV_INS_X1_010: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_X1_010]); + name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); + break; + default: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_UNKNOWN]); + name=fmt::sprintf(ICON_FA_QUESTION " %.2X: %s##_INS%d",i,ins->name,i); + break; + } + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + if (ImGui::Selectable(name.c_str(),curIns==i)) { + curIns=i; + } + if (settings.insFocusesPattern && patternOpen && ImGui::IsItemActivated()) { + nextWindow=GUI_WINDOW_PATTERN; + curIns=i; + } + ImGui::PopStyleColor(); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("%s",(ins->type>DIV_INS_MAX)?"Unknown":insTypes[ins->type]); + if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { + insEditOpen=true; + nextWindow=GUI_WINDOW_INS_EDIT; + } + } + } + + if (settings.unifiedDataView) { + ImGui::Unindent(); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text(ICON_FA_AREA_CHART " Wavetables"); + ImGui::Indent(); + actualWaveList(); + ImGui::Unindent(); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text(ICON_FA_VOLUME_UP " Samples"); + ImGui::Indent(); + actualSampleList(); + ImGui::Unindent(); + } + + ImGui::EndTable(); + } + } + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_INS_LIST; + ImGui::End(); +} + +void FurnaceGUI::drawWaveList() { + if (nextWindow==GUI_WINDOW_WAVE_LIST) { + waveListOpen=true; + ImGui::SetNextWindowFocus(); + nextWindow=GUI_WINDOW_NOTHING; + } + if (!waveListOpen) return; + if (ImGui::Begin("Wavetables",&waveListOpen)) { + if (ImGui::Button(ICON_FA_PLUS "##WaveAdd")) { + doAction(GUI_ACTION_WAVE_LIST_ADD); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_FILES_O "##WaveClone")) { + doAction(GUI_ACTION_WAVE_LIST_DUPLICATE); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_FOLDER_OPEN "##WaveLoad")) { + doAction(GUI_ACTION_WAVE_LIST_OPEN); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_FLOPPY_O "##WaveSave")) { + doAction(GUI_ACTION_WAVE_LIST_SAVE); + } + ImGui::SameLine(); + if (ImGui::ArrowButton("WaveUp",ImGuiDir_Up)) { + doAction(GUI_ACTION_WAVE_LIST_UP); + } + ImGui::SameLine(); + if (ImGui::ArrowButton("WaveDown",ImGuiDir_Down)) { + doAction(GUI_ACTION_WAVE_LIST_DOWN); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_TIMES "##WaveDelete")) { + doAction(GUI_ACTION_WAVE_LIST_DELETE); + } + ImGui::Separator(); + if (ImGui::BeginTable("WaveListScroll",1,ImGuiTableFlags_ScrollY)) { + actualWaveList(); + ImGui::EndTable(); + } + } + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_WAVE_LIST; + ImGui::End(); +} + +void FurnaceGUI::drawSampleList() { + if (nextWindow==GUI_WINDOW_SAMPLE_LIST) { + sampleListOpen=true; + ImGui::SetNextWindowFocus(); + nextWindow=GUI_WINDOW_NOTHING; + } + if (!sampleListOpen) return; + if (ImGui::Begin("Samples",&sampleListOpen)) { + if (ImGui::Button(ICON_FA_PLUS "##SampleAdd")) { + doAction(GUI_ACTION_SAMPLE_LIST_ADD); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_FOLDER_OPEN "##SampleLoad")) { + doAction(GUI_ACTION_SAMPLE_LIST_OPEN); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_FLOPPY_O "##SampleSave")) { + doAction(GUI_ACTION_SAMPLE_LIST_SAVE); + } + ImGui::SameLine(); + if (ImGui::ArrowButton("SampleUp",ImGuiDir_Up)) { + doAction(GUI_ACTION_SAMPLE_LIST_MOVE_UP); + } + ImGui::SameLine(); + if (ImGui::ArrowButton("SampleDown",ImGuiDir_Down)) { + doAction(GUI_ACTION_SAMPLE_LIST_MOVE_DOWN); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_TIMES "##SampleDelete")) { + doAction(GUI_ACTION_SAMPLE_LIST_DELETE); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_VOLUME_UP "##PreviewSampleL")) { + doAction(GUI_ACTION_SAMPLE_LIST_PREVIEW); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_VOLUME_OFF "##StopSampleL")) { + doAction(GUI_ACTION_SAMPLE_LIST_STOP_PREVIEW); + } + ImGui::Separator(); + if (ImGui::BeginTable("SampleListScroll",1,ImGuiTableFlags_ScrollY)) { + actualSampleList(); + ImGui::EndTable(); + } + ImGui::Unindent(); + } + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_SAMPLE_LIST; + ImGui::End(); +} + +void FurnaceGUI::actualWaveList() { + float wavePreview[256]; + for (int i=0; i<(int)e->song.wave.size(); i++) { + DivWavetable* wave=e->song.wave[i]; + for (int i=0; ilen; i++) { + wavePreview[i]=wave->data[i]; + } + if (wave->len>0) wavePreview[wave->len]=wave->data[wave->len-1]; + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + if (ImGui::Selectable(fmt::sprintf("%d##_WAVE%d\n",i,i).c_str(),curWave==i)) { + curWave=i; + } + if (ImGui::IsItemHovered()) { + if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { + waveEditOpen=true; + } + } + ImGui::SameLine(); + PlotNoLerp(fmt::sprintf("##_WAVEP%d",i).c_str(),wavePreview,wave->len+1,0,NULL,0,wave->max); + } +} + +void FurnaceGUI::actualSampleList() { + for (int i=0; i<(int)e->song.sample.size(); i++) { + DivSample* sample=e->song.sample[i]; + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + if (ImGui::Selectable(fmt::sprintf("%d: %s##_SAM%d",i,sample->name,i).c_str(),curSample==i)) { + curSample=i; + samplePos=0; + updateSampleTex=true; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Bank %d: %s",i/12,sampleNote[i%12]); + if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { + sampleEditOpen=true; + } + } + } +} \ No newline at end of file diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 980ca7c80..f3ae5a7f3 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1138,304 +1138,6 @@ void FurnaceGUI::drawSongInfo() { ImGui::End(); } -void FurnaceGUI::drawInsList() { - if (nextWindow==GUI_WINDOW_INS_LIST) { - insListOpen=true; - ImGui::SetNextWindowFocus(); - nextWindow=GUI_WINDOW_NOTHING; - } - if (!insListOpen) return; - if (ImGui::Begin("Instruments",&insListOpen)) { - if (ImGui::Button(ICON_FA_PLUS "##InsAdd")) { - doAction(GUI_ACTION_INS_LIST_ADD); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_FILES_O "##InsClone")) { - doAction(GUI_ACTION_INS_LIST_DUPLICATE); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_FOLDER_OPEN "##InsLoad")) { - doAction(GUI_ACTION_INS_LIST_OPEN); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_FLOPPY_O "##InsSave")) { - doAction(GUI_ACTION_INS_LIST_SAVE); - } - ImGui::SameLine(); - if (ImGui::ArrowButton("InsUp",ImGuiDir_Up)) { - doAction(GUI_ACTION_INS_LIST_MOVE_UP); - } - ImGui::SameLine(); - if (ImGui::ArrowButton("InsDown",ImGuiDir_Down)) { - doAction(GUI_ACTION_INS_LIST_MOVE_DOWN); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_TIMES "##InsDelete")) { - doAction(GUI_ACTION_INS_LIST_DELETE); - } - ImGui::Separator(); - if (ImGui::BeginTable("InsListScroll",1,ImGuiTableFlags_ScrollY)) { - if (settings.unifiedDataView) { - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text(ICON_FA_TASKS " Instruments"); - ImGui::Indent(); - } - - for (int i=0; i<(int)e->song.ins.size(); i++) { - DivInstrument* ins=e->song.ins[i]; - String name; - switch (ins->type) { - case DIV_INS_FM: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_FM]); - name=fmt::sprintf(ICON_FA_AREA_CHART " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_STD: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_STD]); - name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_GB: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_GB]); - name=fmt::sprintf(ICON_FA_GAMEPAD " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_C64: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_C64]); - name=fmt::sprintf(ICON_FA_KEYBOARD_O " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_AMIGA: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_AMIGA]); - name=fmt::sprintf(ICON_FA_VOLUME_UP " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_PCE: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_PCE]); - name=fmt::sprintf(ICON_FA_ID_BADGE " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_AY: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_AY]); - name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_AY8930: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_AY8930]); - name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_TIA: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_TIA]); - name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_SAA1099: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_SAA1099]); - name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_VIC: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_VIC]); - name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_PET: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_PET]); - name=fmt::sprintf(ICON_FA_SQUARE " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_VRC6: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_VRC6]); - name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_OPLL: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_OPLL]); - name=fmt::sprintf(ICON_FA_AREA_CHART " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_OPL: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_OPL]); - name=fmt::sprintf(ICON_FA_AREA_CHART " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_FDS: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_FDS]); - name=fmt::sprintf(ICON_FA_FLOPPY_O " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_VBOY: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_VBOY]); - name=fmt::sprintf(ICON_FA_BINOCULARS " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_N163: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_N163]); - name=fmt::sprintf(ICON_FA_CALCULATOR " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_SCC: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_SCC]); - name=fmt::sprintf(ICON_FA_CALCULATOR " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_OPZ: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_OPZ]); - name=fmt::sprintf(ICON_FA_AREA_CHART " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_POKEY: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_POKEY]); - name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_BEEPER: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_BEEPER]); - name=fmt::sprintf(ICON_FA_SQUARE " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_SWAN: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_SWAN]); - name=fmt::sprintf(ICON_FA_GAMEPAD " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_MIKEY: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_MIKEY]); - name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_VERA: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_VERA]); - name=fmt::sprintf(ICON_FA_KEYBOARD_O " %.2X: %s##_INS%d",i,ins->name,i); - break; - case DIV_INS_X1_010: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_X1_010]); - name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); - break; - default: - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_UNKNOWN]); - name=fmt::sprintf(ICON_FA_QUESTION " %.2X: %s##_INS%d",i,ins->name,i); - break; - } - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - if (ImGui::Selectable(name.c_str(),curIns==i)) { - curIns=i; - } - if (settings.insFocusesPattern && patternOpen && ImGui::IsItemActivated()) { - nextWindow=GUI_WINDOW_PATTERN; - curIns=i; - } - ImGui::PopStyleColor(); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("%s",(ins->type>DIV_INS_MAX)?"Unknown":insTypes[ins->type]); - if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { - insEditOpen=true; - nextWindow=GUI_WINDOW_INS_EDIT; - } - } - } - - if (settings.unifiedDataView) { - ImGui::Unindent(); - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text(ICON_FA_AREA_CHART " Wavetables"); - ImGui::Indent(); - actualWaveList(); - ImGui::Unindent(); - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text(ICON_FA_VOLUME_UP " Samples"); - ImGui::Indent(); - actualSampleList(); - ImGui::Unindent(); - } - - ImGui::EndTable(); - } - } - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_INS_LIST; - ImGui::End(); -} - -const char* sampleNote[12]={ - "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" -}; - - -void FurnaceGUI::actualWaveList() { - float wavePreview[256]; - for (int i=0; i<(int)e->song.wave.size(); i++) { - DivWavetable* wave=e->song.wave[i]; - for (int i=0; ilen; i++) { - wavePreview[i]=wave->data[i]; - } - if (wave->len>0) wavePreview[wave->len]=wave->data[wave->len-1]; - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - if (ImGui::Selectable(fmt::sprintf("%d##_WAVE%d\n",i,i).c_str(),curWave==i)) { - curWave=i; - } - if (ImGui::IsItemHovered()) { - if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { - waveEditOpen=true; - } - } - ImGui::SameLine(); - PlotNoLerp(fmt::sprintf("##_WAVEP%d",i).c_str(),wavePreview,wave->len+1,0,NULL,0,wave->max); - } -} - -void FurnaceGUI::actualSampleList() { - for (int i=0; i<(int)e->song.sample.size(); i++) { - DivSample* sample=e->song.sample[i]; - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - if (ImGui::Selectable(fmt::sprintf("%d: %s##_SAM%d",i,sample->name,i).c_str(),curSample==i)) { - curSample=i; - samplePos=0; - updateSampleTex=true; - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Bank %d: %s",i/12,sampleNote[i%12]); - if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { - sampleEditOpen=true; - } - } - } -} - -void FurnaceGUI::drawSampleList() { - if (nextWindow==GUI_WINDOW_SAMPLE_LIST) { - sampleListOpen=true; - ImGui::SetNextWindowFocus(); - nextWindow=GUI_WINDOW_NOTHING; - } - if (!sampleListOpen) return; - if (ImGui::Begin("Samples",&sampleListOpen)) { - if (ImGui::Button(ICON_FA_PLUS "##SampleAdd")) { - doAction(GUI_ACTION_SAMPLE_LIST_ADD); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_FOLDER_OPEN "##SampleLoad")) { - doAction(GUI_ACTION_SAMPLE_LIST_OPEN); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_FLOPPY_O "##SampleSave")) { - doAction(GUI_ACTION_SAMPLE_LIST_SAVE); - } - ImGui::SameLine(); - if (ImGui::ArrowButton("SampleUp",ImGuiDir_Up)) { - doAction(GUI_ACTION_SAMPLE_LIST_MOVE_UP); - } - ImGui::SameLine(); - if (ImGui::ArrowButton("SampleDown",ImGuiDir_Down)) { - doAction(GUI_ACTION_SAMPLE_LIST_MOVE_DOWN); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_TIMES "##SampleDelete")) { - doAction(GUI_ACTION_SAMPLE_LIST_DELETE); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_VOLUME_UP "##PreviewSampleL")) { - doAction(GUI_ACTION_SAMPLE_LIST_PREVIEW); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_VOLUME_OFF "##StopSampleL")) { - doAction(GUI_ACTION_SAMPLE_LIST_STOP_PREVIEW); - } - ImGui::Separator(); - if (ImGui::BeginTable("SampleListScroll",1,ImGuiTableFlags_ScrollY)) { - actualSampleList(); - ImGui::EndTable(); - } - ImGui::Unindent(); - } - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_SAMPLE_LIST; - ImGui::End(); -} - void FurnaceGUI::drawMixer() { if (nextWindow==GUI_WINDOW_MIXER) { mixerOpen=true; diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index ab206e16b..a1ba5f572 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1946,132 +1946,4 @@ void FurnaceGUI::drawInsEdit() { } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_INS_EDIT; ImGui::End(); -} - -#undef P -#undef PARAMETER - -void FurnaceGUI::drawWaveList() { - if (nextWindow==GUI_WINDOW_WAVE_LIST) { - waveListOpen=true; - ImGui::SetNextWindowFocus(); - nextWindow=GUI_WINDOW_NOTHING; - } - if (!waveListOpen) return; - if (ImGui::Begin("Wavetables",&waveListOpen)) { - if (ImGui::Button(ICON_FA_PLUS "##WaveAdd")) { - doAction(GUI_ACTION_WAVE_LIST_ADD); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_FILES_O "##WaveClone")) { - doAction(GUI_ACTION_WAVE_LIST_DUPLICATE); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_FOLDER_OPEN "##WaveLoad")) { - doAction(GUI_ACTION_WAVE_LIST_OPEN); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_FLOPPY_O "##WaveSave")) { - doAction(GUI_ACTION_WAVE_LIST_SAVE); - } - ImGui::SameLine(); - if (ImGui::ArrowButton("WaveUp",ImGuiDir_Up)) { - doAction(GUI_ACTION_WAVE_LIST_UP); - } - ImGui::SameLine(); - if (ImGui::ArrowButton("WaveDown",ImGuiDir_Down)) { - doAction(GUI_ACTION_WAVE_LIST_DOWN); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_TIMES "##WaveDelete")) { - doAction(GUI_ACTION_WAVE_LIST_DELETE); - } - ImGui::Separator(); - if (ImGui::BeginTable("WaveListScroll",1,ImGuiTableFlags_ScrollY)) { - actualWaveList(); - ImGui::EndTable(); - } - } - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_WAVE_LIST; - ImGui::End(); -} - -void FurnaceGUI::drawWaveEdit() { - if (nextWindow==GUI_WINDOW_WAVE_EDIT) { - waveEditOpen=true; - ImGui::SetNextWindowFocus(); - nextWindow=GUI_WINDOW_NOTHING; - } - if (!waveEditOpen) return; - float wavePreview[256]; - ImGui::SetNextWindowSizeConstraints(ImVec2(450.0f*dpiScale,300.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); - if (ImGui::Begin("Wavetable Editor",&waveEditOpen,settings.allowEditDocking?0:ImGuiWindowFlags_NoDocking)) { - if (curWave<0 || curWave>=(int)e->song.wave.size()) { - ImGui::Text("no wavetable selected"); - } else { - DivWavetable* wave=e->song.wave[curWave]; - ImGui::Text("Width"); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("use a width of:\n- any on Amiga/X1-010\n- 32 on Game Boy, PC Engine and WonderSwan\nany other widths will be scaled during playback."); - } - ImGui::SameLine(); - ImGui::SetNextItemWidth(128.0f*dpiScale); - if (ImGui::InputInt("##_WTW",&wave->len,1,2)) { - if (wave->len>256) wave->len=256; - if (wave->len<1) wave->len=1; - e->notifyWaveChange(curWave); - if (wavePreviewOn) e->previewWave(curWave,wavePreviewNote); - MARK_MODIFIED; - } - ImGui::SameLine(); - ImGui::Text("Height"); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("use a height of:\n- 15 for Game Boy and WonderSwan\n- 31 for PC Engine\nany other heights will be scaled during playback."); - } - ImGui::SameLine(); - ImGui::SetNextItemWidth(128.0f*dpiScale); - if (ImGui::InputInt("##_WTH",&wave->max,1,2)) { - if (wave->max>255) wave->max=255; - if (wave->max<1) wave->max=1; - e->notifyWaveChange(curWave); - MARK_MODIFIED; - } - for (int i=0; ilen; i++) { - if (wave->data[i]>wave->max) wave->data[i]=wave->max; - wavePreview[i]=wave->data[i]; - } - if (wave->len>0) wavePreview[wave->len]=wave->data[wave->len-1]; - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); //wavetable text input size found here - if (ImGui::InputText("##MMLWave",&mmlStringW)) { - decodeMMLStrW(mmlStringW,wave->data,wave->len,wave->max); - } - if (!ImGui::IsItemActive()) { - encodeMMLStr(mmlStringW,wave->data,wave->len,-1,-1); - } - - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,ImVec2(0.0f,0.0f)); - - ImVec2 contentRegion=ImGui::GetContentRegionAvail(); //wavetable graph size determined here - if (ImGui::GetContentRegionAvail().y > (ImGui::GetContentRegionAvail().x / 2.0f)) { - contentRegion=ImVec2(ImGui::GetContentRegionAvail().x,ImGui::GetContentRegionAvail().x / 2.0f); - } - PlotNoLerp("##Waveform",wavePreview,wave->len+1,0,NULL,0,wave->max,contentRegion); - if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { - waveDragStart=ImGui::GetItemRectMin(); - waveDragAreaSize=contentRegion; - waveDragMin=0; - waveDragMax=wave->max; - waveDragLen=wave->len; - waveDragActive=true; - waveDragTarget=wave->data; - processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); - e->notifyWaveChange(curWave); - modified=true; - } - ImGui::PopStyleVar(); - } - } - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_WAVE_EDIT; - ImGui::End(); -} - +} \ No newline at end of file diff --git a/src/gui/waveEdit.cpp b/src/gui/waveEdit.cpp new file mode 100644 index 000000000..09ec95b1c --- /dev/null +++ b/src/gui/waveEdit.cpp @@ -0,0 +1,82 @@ +#include "gui.h" +#include "plot_nolerp.h" +#include "misc/cpp/imgui_stdlib.h" + +void FurnaceGUI::drawWaveEdit() { + if (nextWindow==GUI_WINDOW_WAVE_EDIT) { + waveEditOpen=true; + ImGui::SetNextWindowFocus(); + nextWindow=GUI_WINDOW_NOTHING; + } + if (!waveEditOpen) return; + float wavePreview[256]; + ImGui::SetNextWindowSizeConstraints(ImVec2(450.0f*dpiScale,300.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); + if (ImGui::Begin("Wavetable Editor",&waveEditOpen,settings.allowEditDocking?0:ImGuiWindowFlags_NoDocking)) { + if (curWave<0 || curWave>=(int)e->song.wave.size()) { + ImGui::Text("no wavetable selected"); + } else { + DivWavetable* wave=e->song.wave[curWave]; + ImGui::Text("Width"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("use a width of:\n- any on Amiga/X1-010\n- 32 on Game Boy, PC Engine and WonderSwan\nany other widths will be scaled during playback."); + } + ImGui::SameLine(); + ImGui::SetNextItemWidth(128.0f*dpiScale); + if (ImGui::InputInt("##_WTW",&wave->len,1,2)) { + if (wave->len>256) wave->len=256; + if (wave->len<1) wave->len=1; + e->notifyWaveChange(curWave); + if (wavePreviewOn) e->previewWave(curWave,wavePreviewNote); + MARK_MODIFIED; + } + ImGui::SameLine(); + ImGui::Text("Height"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("use a height of:\n- 15 for Game Boy and WonderSwan\n- 31 for PC Engine\nany other heights will be scaled during playback."); + } + ImGui::SameLine(); + ImGui::SetNextItemWidth(128.0f*dpiScale); + if (ImGui::InputInt("##_WTH",&wave->max,1,2)) { + if (wave->max>255) wave->max=255; + if (wave->max<1) wave->max=1; + e->notifyWaveChange(curWave); + MARK_MODIFIED; + } + for (int i=0; ilen; i++) { + if (wave->data[i]>wave->max) wave->data[i]=wave->max; + wavePreview[i]=wave->data[i]; + } + if (wave->len>0) wavePreview[wave->len]=wave->data[wave->len-1]; + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); // wavetable text input size found here + if (ImGui::InputText("##MMLWave",&mmlStringW)) { + decodeMMLStrW(mmlStringW,wave->data,wave->len,wave->max); + } + if (!ImGui::IsItemActive()) { + encodeMMLStr(mmlStringW,wave->data,wave->len,-1,-1); + } + + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,ImVec2(0.0f,0.0f)); + + ImVec2 contentRegion=ImGui::GetContentRegionAvail(); // wavetable graph size determined here + if (ImGui::GetContentRegionAvail().y > (ImGui::GetContentRegionAvail().x / 2.0f)) { + contentRegion=ImVec2(ImGui::GetContentRegionAvail().x,ImGui::GetContentRegionAvail().x / 2.0f); + } + PlotNoLerp("##Waveform",wavePreview,wave->len+1,0,NULL,0,wave->max,contentRegion); + if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { + waveDragStart=ImGui::GetItemRectMin(); + waveDragAreaSize=contentRegion; + waveDragMin=0; + waveDragMax=wave->max; + waveDragLen=wave->len; + waveDragActive=true; + waveDragTarget=wave->data; + processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); + e->notifyWaveChange(curWave); + modified=true; + } + ImGui::PopStyleVar(); + } + } + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_WAVE_EDIT; + ImGui::End(); +} \ No newline at end of file From 7e5c27c5b7eb835db861e52e2503989056cba708 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 21 Mar 2022 17:34:43 -0500 Subject: [PATCH 351/637] GUI: massive code split gui.cpp now less than 3000 lines --- CMakeLists.txt | 19 + src/gui/about.cpp | 211 ++ src/gui/actionUtil.h | 21 + src/gui/channels.cpp | 53 + src/gui/compatFlags.cpp | 136 ++ src/gui/cursor.cpp | 285 +++ src/gui/dataList.cpp | 19 + src/gui/debugWindow.cpp | 250 ++ src/gui/doAction.cpp | 687 ++++++ src/gui/editControls.cpp | 330 +++ src/gui/editing.cpp | 1015 ++++++++ src/gui/gui.cpp | 4802 +------------------------------------- src/gui/gui.h | 7 + src/gui/guiConst.cpp | 46 +- src/gui/guiConst.h | 3 +- src/gui/mixer.cpp | 59 + src/gui/newSong.cpp | 90 + src/gui/osc.cpp | 47 + src/gui/piano.cpp | 46 + src/gui/presets.cpp | 639 +++++ src/gui/regView.cpp | 71 + src/gui/settings.cpp | 397 ++++ src/gui/songInfo.cpp | 173 ++ src/gui/songNotes.cpp | 37 + src/gui/stats.cpp | 50 + src/gui/sysConf.cpp | 371 +++ src/gui/volMeter.cpp | 111 + src/gui/waveEdit.cpp | 19 + 28 files changed, 5201 insertions(+), 4793 deletions(-) create mode 100644 src/gui/about.cpp create mode 100644 src/gui/actionUtil.h create mode 100644 src/gui/channels.cpp create mode 100644 src/gui/compatFlags.cpp create mode 100644 src/gui/cursor.cpp create mode 100644 src/gui/debugWindow.cpp create mode 100644 src/gui/doAction.cpp create mode 100644 src/gui/editControls.cpp create mode 100644 src/gui/editing.cpp create mode 100644 src/gui/mixer.cpp create mode 100644 src/gui/newSong.cpp create mode 100644 src/gui/osc.cpp create mode 100644 src/gui/piano.cpp create mode 100644 src/gui/presets.cpp create mode 100644 src/gui/regView.cpp create mode 100644 src/gui/songInfo.cpp create mode 100644 src/gui/songNotes.cpp create mode 100644 src/gui/stats.cpp create mode 100644 src/gui/sysConf.cpp create mode 100644 src/gui/volMeter.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 8f51dc9e0..f09c88a64 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -357,14 +357,33 @@ src/gui/fileDialog.cpp src/gui/intConst.cpp src/gui/guiConst.cpp +src/gui/about.cpp +src/gui/channels.cpp +src/gui/compatFlags.cpp +src/gui/cursor.cpp src/gui/dataList.cpp +src/gui/debugWindow.cpp +src/gui/doAction.cpp +src/gui/editing.cpp +src/gui/editControls.cpp src/gui/insEdit.cpp +src/gui/mixer.cpp +src/gui/newSong.cpp src/gui/orders.cpp +src/gui/osc.cpp src/gui/pattern.cpp +src/gui/piano.cpp +src/gui/presets.cpp +src/gui/regView.cpp src/gui/sampleEdit.cpp src/gui/settings.cpp +src/gui/songInfo.cpp +src/gui/songNotes.cpp +src/gui/stats.cpp +src/gui/sysConf.cpp src/gui/util.cpp src/gui/waveEdit.cpp +src/gui/volMeter.cpp src/gui/gui.cpp ) diff --git a/src/gui/about.cpp b/src/gui/about.cpp new file mode 100644 index 000000000..cd5888c61 --- /dev/null +++ b/src/gui/about.cpp @@ -0,0 +1,211 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "gui.h" + +const char* aboutLine[]={ + "tildearrow", + "is proud to present", + "", + ("Furnace " DIV_VERSION), + "", + "the free software chiptune tracker,", + "compatible with DefleMask modules.", + "", + "zero disassembly.", + "just clean-room design,", + "time and dedication.", + "", + "> CREDITS <", + "", + "-- program --", + "tildearrow", + "akumanatt", + "cam900", + "djtuBIG-MaliceX", + "laoo", + "superctr", + "", + "-- graphics/UI design --", + "tildearrow", + "BlastBrothers", + "", + "-- documentation --", + "tildearrow", + "freq-mod", + "nicco1690", + "DeMOSic", + "cam900", + "", + "-- demo songs --", + "0x5066", + "ActualNK358", + "breakthetargets", + "CaptainMalware", + "kleeder", + "Mahbod Karamoozian", + "nicco1690", + "NikonTeen", + "SuperJet Spade", + "TheDuccinator", + "TheRealHedgehogSonic", + "tildearrow", + "Ultraprogramer", + "", + "-- additional feedback/fixes --", + "fd", + "OPNA2608", + "plane", + "TheEssem", + "", + "powered by:", + "Dear ImGui by Omar Cornut", + "SDL2 by Sam Lantinga", + "zlib by Jean-loup Gailly", + "and Mark Adler", + "libsndfile by Erik de Castro Lopo", + "Nuked-OPM & Nuked-OPN2 by Nuke.YKT", + "ymfm by Aaron Giles", + "MAME SN76496 by Nicola Salmoria", + "MAME AY-3-8910 by Couriersud", + "with AY8930 fixes by Eulous", + "MAME SAA1099 by Juergen Buchmueller and Manuel Abadia", + "SAASound", + "SameBoy by Lior Halphon", + "Mednafen PCE", + "puNES by FHorse", + "reSID by Dag Lem", + "Stella by Stella Team", + "QSound emulator by Ian Karlsson and Valley Bell", + "", + "greetings to:", + "Delek", + "fd", + "ILLUMIDARO", + "all members of Deflers of Noice!", + "", + "copyright © 2021-2022 tildearrow", + "(and contributors).", + "licensed under GPLv2+! see", + "LICENSE for more information.", + "", + "help Furnace grow:", + "https://github.com/tildearrow/furnace", + "", + "contact tildearrow at:", + "https://tildearrow.org/?p=contact", + "", + "disclaimer:", + "despite the fact this program works", + "with the .dmf file format, it is NOT", + "affiliated with Delek or DefleMask in", + "any way, nor it is a replacement for", + "the original program.", + "", + "it also comes with ABSOLUTELY NO WARRANTY.", + "", + "thanks to all contributors/bug reporters!" +}; + +const size_t aboutCount=sizeof(aboutLine)/sizeof(aboutLine[0]); + +void FurnaceGUI::drawAbout() { + // do stuff + if (ImGui::Begin("About Furnace",NULL,ImGuiWindowFlags_Modal|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoDocking|ImGuiWindowFlags_NoTitleBar)) { + ImGui::SetWindowPos(ImVec2(0,0)); + ImGui::SetWindowSize(ImVec2(scrW*dpiScale,scrH*dpiScale)); + ImGui::PushFont(bigFont); + ImDrawList* dl=ImGui::GetWindowDrawList(); + float r=0; + float g=0; + float b=0; + float peakMix=settings.partyTime?((peak[0]+peak[1])*0.5):0.3; + ImGui::ColorConvertHSVtoRGB(aboutHue,1.0,0.25+MIN(0.75f,peakMix*0.75f),r,g,b); + dl->AddRectFilled(ImVec2(0,0),ImVec2(scrW*dpiScale,scrH*dpiScale),0xff000000); + bool skip=false; + bool skip2=false; + for (int i=(-80-sin(double(aboutSin)*2*M_PI/120.0)*80.0)*2; iAddRectFilled(ImVec2(i*dpiScale,j*dpiScale),ImVec2((i+160)*dpiScale,(j+160)*dpiScale),ImGui::GetColorU32(ImVec4(r*0.25,g*0.25,b*0.25,1.0))); + } + } + + skip=false; + skip2=false; + for (int i=(-80-cos(double(aboutSin)*2*M_PI/120.0)*80.0)*2; iAddRectFilled(ImVec2(i*dpiScale,j*dpiScale),ImVec2((i+160)*dpiScale,(j+160)*dpiScale),ImGui::GetColorU32(ImVec4(r*0.5,g*0.5,b*0.5,1.0))); + } + } + + skip=false; + skip2=false; + for (int i=(-160+fmod(aboutSin*2,160))*2; iAddRectFilled(ImVec2(i*dpiScale,j*dpiScale),ImVec2((i+160)*dpiScale,(j+160)*dpiScale),ImGui::GetColorU32(ImVec4(r*0.75,g*0.75,b*0.75,1.0))); + } + } + + for (size_t i=0; iscrH*dpiScale) continue; + dl->AddText(bigFont,bigFont->FontSize, + ImVec2(posX+dpiScale,posY+dpiScale), + 0xff000000,aboutLine[i]); + dl->AddText(bigFont,bigFont->FontSize, + ImVec2(posX+dpiScale,posY-dpiScale), + 0xff000000,aboutLine[i]); + dl->AddText(bigFont,bigFont->FontSize, + ImVec2(posX-dpiScale,posY+dpiScale), + 0xff000000,aboutLine[i]); + dl->AddText(bigFont,bigFont->FontSize, + ImVec2(posX-dpiScale,posY-dpiScale), + 0xff000000,aboutLine[i]); + dl->AddText(bigFont,bigFont->FontSize, + ImVec2(posX,posY), + 0xffffffff,aboutLine[i]); + } + ImGui::PopFont(); + + float timeScale=60.0f*ImGui::GetIO().DeltaTime; + + aboutHue+=(0.001+peakMix*0.004)*timeScale; + aboutScroll+=(2+(peakMix>0.78)*3)*timeScale; + aboutSin+=(1+(peakMix>0.75)*2)*timeScale; + + while (aboutHue>1) aboutHue--; + while (aboutSin>=2400) aboutSin-=2400; + if (aboutScroll>(42*aboutCount+scrH)) aboutScroll=-20; + } + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_ABOUT; + ImGui::End(); +} \ No newline at end of file diff --git a/src/gui/actionUtil.h b/src/gui/actionUtil.h new file mode 100644 index 000000000..38ed9a5cd --- /dev/null +++ b/src/gui/actionUtil.h @@ -0,0 +1,21 @@ +#define DETERMINE_FIRST \ + int firstChannel=0; \ + for (int i=0; igetTotalChannelCount(); i++) { \ + if (e->song.chanShow[i]) { \ + firstChannel=i; \ + break; \ + } \ + } \ + +#define DETERMINE_LAST \ + int lastChannel=0; \ + for (int i=e->getTotalChannelCount()-1; i>=0; i--) { \ + if (e->song.chanShow[i]) { \ + lastChannel=i+1; \ + break; \ + } \ + } + +#define DETERMINE_FIRST_LAST \ + DETERMINE_FIRST \ + DETERMINE_LAST \ No newline at end of file diff --git a/src/gui/channels.cpp b/src/gui/channels.cpp new file mode 100644 index 000000000..5b2a7e0c6 --- /dev/null +++ b/src/gui/channels.cpp @@ -0,0 +1,53 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "gui.h" +#include "misc/cpp/imgui_stdlib.h" + +void FurnaceGUI::drawChannels() { + if (nextWindow==GUI_WINDOW_CHANNELS) { + channelsOpen=true; + ImGui::SetNextWindowFocus(); + nextWindow=GUI_WINDOW_NOTHING; + } + if (!channelsOpen) return; + if (ImGui::Begin("Channels",&channelsOpen)) { + if (ImGui::BeginTable("ChannelList",3)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,0.0); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.0); + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthFixed,48.0f*dpiScale); + for (int i=0; igetTotalChannelCount(); i++) { + ImGui::PushID(i); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Checkbox("##Visible",&e->song.chanShow[i]); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImGui::InputTextWithHint("##ChanName",e->getChannelName(i),&e->song.chanName[i]); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + ImGui::InputTextWithHint("##ChanShortName",e->getChannelShortName(i),&e->song.chanShortName[i]); + ImGui::PopID(); + } + ImGui::EndTable(); + } + } + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_CHANNELS; + ImGui::End(); +} \ No newline at end of file diff --git a/src/gui/compatFlags.cpp b/src/gui/compatFlags.cpp new file mode 100644 index 000000000..46424fc52 --- /dev/null +++ b/src/gui/compatFlags.cpp @@ -0,0 +1,136 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "gui.h" + +void FurnaceGUI::drawCompatFlags() { + if (nextWindow==GUI_WINDOW_COMPAT_FLAGS) { + compatFlagsOpen=true; + ImGui::SetNextWindowFocus(); + nextWindow=GUI_WINDOW_NOTHING; + } + if (!compatFlagsOpen) return; + if (ImGui::Begin("Compatibility Flags",&compatFlagsOpen)) { + ImGui::TextWrapped("these flags are designed to provide better DefleMask/older Furnace compatibility."); + ImGui::Checkbox("Limit slide range",&e->song.limitSlides); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("when enabled, slides are limited to a compatible range.\nmay cause problems with slides in negative octaves."); + } + ImGui::Checkbox("Linear pitch control",&e->song.linearPitch); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("linear pitch:\n- slides work in frequency/period space\n- E5xx and 04xx effects work in tonality space\nnon-linear pitch:\n- slides work in frequency/period space\n- E5xx and 04xx effects work on frequency/period space"); + } + ImGui::Checkbox("Proper noise layout on NES and PC Engine",&e->song.properNoiseLayout); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("use a proper noise channel note mapping (0-15) instead of a rather unusual compatible one.\nunlocks all noise frequencies on PC Engine."); + } + ImGui::Checkbox("Game Boy instrument duty is wave volume",&e->song.waveDutyIsVol); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("if enabled, an instrument with duty macro in the wave channel will be mapped to wavetable volume."); + } + + ImGui::Checkbox("Restart macro on portamento",&e->song.resetMacroOnPorta); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("when enabled, a portamento effect will reset the channel's macro if used in combination with a note."); + } + ImGui::Checkbox("Legacy volume slides",&e->song.legacyVolumeSlides); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("simulate glitchy volume slide behavior by silently overflowing the volume when the slide goes below 0."); + } + ImGui::Checkbox("Compatible arpeggio",&e->song.compatibleArpeggio); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("delay arpeggio by one tick on every new note."); + } + ImGui::Checkbox("Reset slides after note off",&e->song.noteOffResetsSlides); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("when enabled, note off will reset the channel's slide effect."); + } + ImGui::Checkbox("Reset portamento after reaching target",&e->song.targetResetsSlides); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("when enabled, the slide effect is disabled after it reaches its target."); + } + ImGui::Checkbox("Ignore duplicate slide effects",&e->song.ignoreDuplicateSlides); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("if this is on, only the first slide of a row in a channel will be considered."); + } + ImGui::Checkbox("Continuous vibrato",&e->song.continuousVibrato); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("when enabled, vibrato will not be reset on a new note."); + } + ImGui::Checkbox("Broken DAC mode",&e->song.brokenDACMode); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("when enabled, the DAC in YM2612 will be disabled if there isn't any sample playing."); + } + ImGui::Checkbox("Auto-insert one tick gap between notes",&e->song.oneTickCut); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("when enabled, a one-tick note cut will be inserted between non-legato/non-portamento notes.\nthis simulates the behavior of some Amiga/SNES music engines."); + } + + ImGui::Text("Loop modality:"); + if (ImGui::RadioButton("Reset channels",e->song.loopModality==0)) { + e->song.loopModality=0; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("select to reset channels on loop. may trigger a voltage click on every loop!"); + } + if (ImGui::RadioButton("Soft reset channels",e->song.loopModality==1)) { + e->song.loopModality=1; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("select to turn channels off on loop."); + } + if (ImGui::RadioButton("Do nothing",e->song.loopModality==2)) { + e->song.loopModality=2; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("select to not reset channels on loop."); + } + + ImGui::Separator(); + + ImGui::TextWrapped("the following flags are for compatibility with older Furnace versions."); + + ImGui::Checkbox("Arpeggio inhibits non-porta slides",&e->song.arpNonPorta); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("behavior changed in 0.5.5"); + } + ImGui::Checkbox("Wack FM algorithm macro",&e->song.algMacroBehavior); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("behavior changed in 0.5.5"); + } + ImGui::Checkbox("Broken shortcut slides (E1xy/E2xy)",&e->song.brokenShortcutSlides); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("behavior changed in 0.5.7"); + } + ImGui::Checkbox("Stop portamento on note off",&e->song.stopPortaOnNoteOff); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("behavior changed in 0.6"); + } + ImGui::Checkbox("Allow instrument change during slides",&e->song.newInsTriggersInPorta); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("behavior changed in 0.6"); + } + ImGui::Checkbox("Reset note to base on arpeggio stop",&e->song.arp0Reset); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("behavior changed in 0.6"); + } + } + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_COMPAT_FLAGS; + ImGui::End(); +} \ No newline at end of file diff --git a/src/gui/cursor.cpp b/src/gui/cursor.cpp new file mode 100644 index 000000000..c783856a7 --- /dev/null +++ b/src/gui/cursor.cpp @@ -0,0 +1,285 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "gui.h" + +#include "actionUtil.h" + +void FurnaceGUI::startSelection(int xCoarse, int xFine, int y) { + if (xCoarse!=selStart.xCoarse || xFine!=selStart.xFine || y!=selStart.y) { + curNibble=false; + } + cursor.xCoarse=xCoarse; + cursor.xFine=xFine; + cursor.y=y; + selStart.xCoarse=xCoarse; + selStart.xFine=xFine; + selStart.y=y; + selEnd.xCoarse=xCoarse; + selEnd.xFine=xFine; + selEnd.y=y; + selecting=true; +} + +void FurnaceGUI::updateSelection(int xCoarse, int xFine, int y) { + if (!selecting) return; + selEnd.xCoarse=xCoarse; + selEnd.xFine=xFine; + selEnd.y=y; +} + +void FurnaceGUI::finishSelection() { + // swap points if needed + if (selEnd.ygetTotalChannelCount(); + + if (selStart.xCoarse<0) selStart.xCoarse=0; + if (selStart.xCoarse>=chanCount) selStart.xCoarse=chanCount-1; + if (selStart.y<0) selStart.y=0; + if (selStart.y>=e->song.patLen) selStart.y=e->song.patLen-1; + if (selEnd.xCoarse<0) selEnd.xCoarse=0; + if (selEnd.xCoarse>=chanCount) selEnd.xCoarse=chanCount-1; + if (selEnd.y<0) selEnd.y=0; + if (selEnd.y>=e->song.patLen) selEnd.y=e->song.patLen-1; + if (cursor.xCoarse<0) cursor.xCoarse=0; + if (cursor.xCoarse>=chanCount) cursor.xCoarse=chanCount-1; + if (cursor.y<0) cursor.y=0; + if (cursor.y>=e->song.patLen) cursor.y=e->song.patLen-1; + + if (e->song.chanCollapse[selEnd.xCoarse]) { + selStart.xFine=0; + } + if (e->song.chanCollapse[selEnd.xCoarse]) { + selEnd.xFine=2+e->song.pat[cursor.xCoarse].effectRows*2; + } +} + +void FurnaceGUI::moveCursor(int x, int y, bool select) { + if (!select) { + finishSelection(); + } + + DETERMINE_FIRST_LAST; + + curNibble=false; + if (x!=0) { + demandScrollX=true; + if (x>0) { + for (int i=0; i=(e->song.chanCollapse[cursor.xCoarse]?1:(3+e->song.pat[cursor.xCoarse].effectRows*2))) { + cursor.xFine=0; + if (++cursor.xCoarse>=lastChannel) { + if (settings.wrapHorizontal!=0 && !select) { + cursor.xCoarse=firstChannel; + if (settings.wrapHorizontal==2) y++; + } else { + cursor.xCoarse=lastChannel-1; + cursor.xFine=e->song.chanCollapse[cursor.xCoarse]?0:(2+e->song.pat[cursor.xCoarse].effectRows*2); + } + } else { + while (!e->song.chanShow[cursor.xCoarse]) { + cursor.xCoarse++; + if (cursor.xCoarse>=e->getTotalChannelCount()) break; + } + } + } + } + } else { + for (int i=0; i<-x; i++) { + if (--cursor.xFine<0) { + if (--cursor.xCoarsesong.pat[cursor.xCoarse].effectRows*2; + if (settings.wrapHorizontal==2) y--; + } else { + cursor.xCoarse=firstChannel; + cursor.xFine=0; + } + } else { + while (!e->song.chanShow[cursor.xCoarse]) { + cursor.xCoarse--; + if (cursor.xCoarse<0) break; + } + if (e->song.chanCollapse[cursor.xCoarse]) { + cursor.xFine=0; + } else { + cursor.xFine=2+e->song.pat[cursor.xCoarse].effectRows*2; + } + } + } + } + } + } + if (y!=0) { + if (y>0) { + for (int i=0; i=e->song.patLen) { + if (settings.wrapVertical!=0 && !select) { + cursor.y=0; + if (settings.wrapVertical==2) { + if (!e->isPlaying() && e->getOrder()<(e->song.ordersLen-1)) { + e->setOrder(e->getOrder()+1); + } else { + cursor.y=e->song.patLen-1; + } + } + } else { + cursor.y=e->song.patLen-1; + } + } + } + } else { + for (int i=0; i<-y; i++) { + cursor.y--; + if (cursor.y<0) { + if (settings.wrapVertical!=0 && !select) { + cursor.y=e->song.patLen-1; + if (settings.wrapVertical==2) { + if (!e->isPlaying() && e->getOrder()>0) { + e->setOrder(e->getOrder()-1); + } else { + cursor.y=0; + } + } + } else { + cursor.y=0; + } + } + } + } + } + if (!select) { + selStart=cursor; + } + selEnd=cursor; + updateScroll(cursor.y); +} + +void FurnaceGUI::moveCursorPrevChannel(bool overflow) { + finishSelection(); + curNibble=false; + + DETERMINE_FIRST_LAST; + + do { + cursor.xCoarse--; + if (cursor.xCoarse<0) break; + } while (!e->song.chanShow[cursor.xCoarse]); + if (cursor.xCoarse=e->getTotalChannelCount()) break; + } while (!e->song.chanShow[cursor.xCoarse]); + if (cursor.xCoarse>=lastChannel) { + if (overflow) { + cursor.xCoarse=firstChannel; + } else { + cursor.xCoarse=lastChannel-1; + } + } + + selStart=cursor; + selEnd=cursor; + demandScrollX=true; +} + +void FurnaceGUI::moveCursorTop(bool select) { + finishSelection(); + curNibble=false; + if (cursor.y==0) { + DETERMINE_FIRST; + cursor.xCoarse=firstChannel; + cursor.xFine=0; + demandScrollX=true; + } else { + cursor.y=0; + } + selStart=cursor; + if (!select) { + selEnd=cursor; + } + updateScroll(cursor.y); +} + +void FurnaceGUI::moveCursorBottom(bool select) { + finishSelection(); + curNibble=false; + if (cursor.y==e->song.patLen-1) { + DETERMINE_LAST; + cursor.xCoarse=lastChannel-1; + cursor.xFine=2+e->song.pat[cursor.xCoarse].effectRows*2; + demandScrollX=true; + } else { + cursor.y=e->song.patLen-1; + } + if (!select) { + selStart=cursor; + } + selEnd=cursor; + updateScroll(cursor.y); +} + +void FurnaceGUI::editAdvance() { + finishSelection(); + cursor.y+=editStep; + if (cursor.y>=e->song.patLen) cursor.y=e->song.patLen-1; + selStart=cursor; + selEnd=cursor; + updateScroll(cursor.y); +} \ No newline at end of file diff --git a/src/gui/dataList.cpp b/src/gui/dataList.cpp index f32ffda77..d6c88a78e 100644 --- a/src/gui/dataList.cpp +++ b/src/gui/dataList.cpp @@ -1,3 +1,22 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + #include "gui.h" #include "IconsFontAwesome4.h" #include "misc/cpp/imgui_stdlib.h" diff --git a/src/gui/debugWindow.cpp b/src/gui/debugWindow.cpp new file mode 100644 index 000000000..31830681f --- /dev/null +++ b/src/gui/debugWindow.cpp @@ -0,0 +1,250 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "gui.h" +#include "debug.h" +#include "IconsFontAwesome4.h" +#include + +void FurnaceGUI::drawDebug() { + static int bpOrder; + static int bpRow; + static int bpTick; + static bool bpOn; + if (nextWindow==GUI_WINDOW_DEBUG) { + debugOpen=true; + ImGui::SetNextWindowFocus(); + nextWindow=GUI_WINDOW_NOTHING; + } + if (!debugOpen) return; + ImGui::SetNextWindowSizeConstraints(ImVec2(400.0f*dpiScale,200.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); + if (ImGui::Begin("Debug",&debugOpen,ImGuiWindowFlags_NoDocking)) { + ImGui::Text("NOTE: use with caution."); + if (ImGui::TreeNode("Debug Controls")) { + if (e->isHalted()) { + if (ImGui::Button("Resume")) e->resume(); + } else { + if (ImGui::Button("Pause")) e->halt(); + } + ImGui::SameLine(); + if (ImGui::Button("Frame Advance")) e->haltWhen(DIV_HALT_TICK); + ImGui::SameLine(); + if (ImGui::Button("Row Advance")) e->haltWhen(DIV_HALT_ROW); + ImGui::SameLine(); + if (ImGui::Button("Pattern Advance")) e->haltWhen(DIV_HALT_PATTERN); + + if (ImGui::Button("Panic")) e->syncReset(); + ImGui::SameLine(); + if (ImGui::Button("Abort")) { + abort(); + } + ImGui::TreePop(); + } + if (ImGui::TreeNode("Breakpoint")) { + ImGui::InputInt("Order",&bpOrder); + ImGui::InputInt("Row",&bpRow); + ImGui::InputInt("Tick",&bpTick); + ImGui::Checkbox("Enable",&bpOn); + ImGui::TreePop(); + } + if (ImGui::TreeNode("Dispatch Status")) { + ImGui::Text("for best results set latency to minimum or use the Frame Advance button."); + ImGui::Columns(e->getTotalChannelCount()); + for (int i=0; igetTotalChannelCount(); i++) { + void* ch=e->getDispatchChanState(i); + ImGui::TextColored(uiColors[GUI_COLOR_ACCENT_PRIMARY],"Ch. %d: %d, %d",i,e->dispatchOfChan[i],e->dispatchChanOfChan[i]); + if (ch==NULL) { + ImGui::Text("NULL"); + } else { + putDispatchChan(ch,e->dispatchChanOfChan[i],e->sysOfChan[i]); + } + ImGui::NextColumn(); + } + ImGui::Columns(); + ImGui::TreePop(); + } + if (ImGui::TreeNode("Playback Status")) { + ImGui::Text("for best results set latency to minimum or use the Frame Advance button."); + ImGui::Columns(e->getTotalChannelCount()); + for (int i=0; igetTotalChannelCount(); i++) { + DivChannelState* ch=e->getChanState(i); + ImGui::TextColored(uiColors[GUI_COLOR_ACCENT_PRIMARY],"Channel %d:",i); + if (ch==NULL) { + ImGui::Text("NULL"); + } else { + ImGui::Text("* General:"); + ImGui::Text("- note = %d",ch->note); + ImGui::Text("- oldNote = %d",ch->oldNote); + ImGui::Text("- pitch = %d",ch->pitch); + ImGui::Text("- portaSpeed = %d",ch->portaSpeed); + ImGui::Text("- portaNote = %d",ch->portaNote); + ImGui::Text("- volume = %.4x",ch->volume); + ImGui::Text("- volSpeed = %d",ch->volSpeed); + ImGui::Text("- cut = %d",ch->cut); + ImGui::Text("- rowDelay = %d",ch->rowDelay); + ImGui::Text("- volMax = %.4x",ch->volMax); + ImGui::Text("- delayOrder = %d",ch->delayOrder); + ImGui::Text("- delayRow = %d",ch->delayRow); + ImGui::Text("- retrigSpeed = %d",ch->retrigSpeed); + ImGui::Text("- retrigTick = %d",ch->retrigTick); + ImGui::PushStyleColor(ImGuiCol_Text,(ch->vibratoDepth>0)?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_TEXT]); + ImGui::Text("* Vibrato:"); + ImGui::Text("- depth = %d",ch->vibratoDepth); + ImGui::Text("- rate = %d",ch->vibratoRate); + ImGui::Text("- pos = %d",ch->vibratoPos); + ImGui::Text("- dir = %d",ch->vibratoDir); + ImGui::Text("- fine = %d",ch->vibratoFine); + ImGui::PopStyleColor(); + ImGui::PushStyleColor(ImGuiCol_Text,(ch->tremoloDepth>0)?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_TEXT]); + ImGui::Text("* Tremolo:"); + ImGui::Text("- depth = %d",ch->tremoloDepth); + ImGui::Text("- rate = %d",ch->tremoloRate); + ImGui::Text("- pos = %d",ch->tremoloPos); + ImGui::PopStyleColor(); + ImGui::PushStyleColor(ImGuiCol_Text,(ch->arp>0)?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_TEXT]); + ImGui::Text("* Arpeggio:"); + ImGui::Text("- arp = %.2X",ch->arp); + ImGui::Text("- stage = %d",ch->arpStage); + ImGui::Text("- ticks = %d",ch->arpTicks); + ImGui::PopStyleColor(); + ImGui::Text("* Miscellaneous:"); + ImGui::TextColored(ch->doNote?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_HEADER],">> Do Note"); + ImGui::TextColored(ch->legato?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_HEADER],">> Legato"); + ImGui::TextColored(ch->portaStop?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_HEADER],">> PortaStop"); + ImGui::TextColored(ch->keyOn?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_HEADER],">> Key On"); + ImGui::TextColored(ch->keyOff?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_HEADER],">> Key Off"); + ImGui::TextColored(ch->nowYouCanStop?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_HEADER],">> NowYouCanStop"); + ImGui::TextColored(ch->stopOnOff?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_HEADER],">> Stop on Off"); + ImGui::TextColored(ch->arpYield?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_HEADER],">> Arp Yield"); + ImGui::TextColored(ch->delayLocked?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_HEADER],">> DelayLocked"); + ImGui::TextColored(ch->inPorta?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_HEADER],">> InPorta"); + ImGui::TextColored(ch->scheduledSlideReset?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_HEADER],">> SchedSlide"); + } + ImGui::NextColumn(); + } + ImGui::Columns(); + ImGui::TreePop(); + } + if (ImGui::TreeNode("Playground")) { + if (pgSys<0 || pgSys>=e->song.systemLen) pgSys=0; + if (ImGui::BeginCombo("System",fmt::sprintf("%d. %s",pgSys+1,e->getSystemName(e->song.system[pgSys])).c_str())) { + for (int i=0; isong.systemLen; i++) { + if (ImGui::Selectable(fmt::sprintf("%d. %s",i+1,e->getSystemName(e->song.system[i])).c_str())) { + pgSys=i; + break; + } + } + ImGui::EndCombo(); + } + ImGui::Text("Program"); + if (pgProgram.empty()) { + ImGui::Text("-nothing here-"); + } else { + char id[32]; + for (size_t index=0; indexpoke(pgSys,pgProgram); + } + ImGui::SameLine(); + if (ImGui::Button("Clear")) { + pgProgram.clear(); + } + + ImGui::Text("Address"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(100.0f*dpiScale); + ImGui::InputInt("##PAddress",&pgAddr,0,0,ImGuiInputTextFlags_CharsHexadecimal); + ImGui::SameLine(); + ImGui::Text("Value"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(100.0f*dpiScale); + ImGui::InputInt("##PValue",&pgVal,0,0,ImGuiInputTextFlags_CharsHexadecimal); + ImGui::SameLine(); + if (ImGui::Button("Write")) { + e->poke(pgSys,pgAddr,pgVal); + } + ImGui::SameLine(); + if (ImGui::Button("Add")) { + pgProgram.push_back(DivRegWrite(pgAddr,pgVal)); + } + if (ImGui::TreeNode("Register Cheatsheet")) { + const char** sheet=e->getRegisterSheet(pgSys); + if (sheet==NULL) { + ImGui::Text("no cheatsheet available for this system."); + } else { + if (ImGui::BeginTable("RegisterSheet",2,ImGuiTableFlags_SizingFixedSame)) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Name"); + ImGui::TableNextColumn(); + ImGui::Text("Address"); + for (int i=0; sheet[i]!=NULL; i+=2) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("%s",sheet[i]); + ImGui::TableNextColumn(); + ImGui::Text("$%s",sheet[i+1]); + } + ImGui::EndTable(); + } + } + ImGui::TreePop(); + } + ImGui::TreePop(); + } + if (ImGui::TreeNode("User Interface")) { + if (ImGui::Button("Inspect")) { + inspectorOpen=!inspectorOpen; + } + ImGui::TreePop(); + } + if (ImGui::TreeNode("Settings")) { + if (ImGui::Button("Sync")) syncSettings(); + ImGui::SameLine(); + if (ImGui::Button("Commit")) commitSettings(); + ImGui::SameLine(); + if (ImGui::Button("Force Load")) e->loadConf(); + ImGui::SameLine(); + if (ImGui::Button("Force Save")) e->saveConf(); + ImGui::TreePop(); + } + ImGui::Text("Song format version %d",e->song.version); + ImGui::Text("Furnace version " DIV_VERSION " (%d)",DIV_ENGINE_VERSION); + } + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_DEBUG; + ImGui::End(); +} \ No newline at end of file diff --git a/src/gui/doAction.cpp b/src/gui/doAction.cpp new file mode 100644 index 000000000..903f6edf0 --- /dev/null +++ b/src/gui/doAction.cpp @@ -0,0 +1,687 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "gui.h" +#include + +#include "actionUtil.h" + +void FurnaceGUI::doAction(int what) { + switch (what) { + case GUI_ACTION_OPEN: + if (modified) { + showWarning("Unsaved changes! Are you sure?",GUI_WARN_OPEN); + } else { + openFileDialog(GUI_FILE_OPEN); + } + break; + case GUI_ACTION_OPEN_BACKUP: + if (modified) { + showWarning("Unsaved changes! Are you sure?",GUI_WARN_OPEN_BACKUP); + } else { + if (load(backupPath)>0) { + showError("No backup available! (or unable to open it)"); + } + } + break; + case GUI_ACTION_SAVE: + if (curFileName=="" || curFileName==backupPath || e->song.version>=0xff00) { + openFileDialog(GUI_FILE_SAVE); + } else { + if (save(curFileName,e->song.isDMF?e->song.version:0)>0) { + showError(fmt::sprintf("Error while saving file! (%s)",lastError)); + } + } + break; + case GUI_ACTION_SAVE_AS: + openFileDialog(GUI_FILE_SAVE); + break; + case GUI_ACTION_UNDO: + doUndo(); + break; + case GUI_ACTION_REDO: + doRedo(); + break; + case GUI_ACTION_PLAY_TOGGLE: + if (e->isPlaying() && !e->isStepping()) { + stop(); + } else { + play(); + } + break; + case GUI_ACTION_PLAY: + play(); + break; + case GUI_ACTION_STOP: + stop(); + break; + case GUI_ACTION_PLAY_REPEAT: + play(); + e->setRepeatPattern(true); + break; + case GUI_ACTION_PLAY_CURSOR: + if (e->isPlaying() && !e->isStepping()) { + stop(); + } else { + play(cursor.y); + } + break; + case GUI_ACTION_STEP_ONE: + e->stepOne(cursor.y); + break; + case GUI_ACTION_OCTAVE_UP: + if (++curOctave>7) { + curOctave=7; + } else { + for (size_t i=0; inoteOff(activeNotes[i].chan); + } + activeNotes.clear(); + } + break; + case GUI_ACTION_OCTAVE_DOWN: + if (--curOctave<-5) { + curOctave=-5; + } else { + for (size_t i=0; inoteOff(activeNotes[i].chan); + } + activeNotes.clear(); + } + break; + case GUI_ACTION_INS_UP: + if (--curIns<-1) curIns=-1; + break; + case GUI_ACTION_INS_DOWN: + if (++curIns>=(int)e->song.ins.size()) curIns=((int)e->song.ins.size())-1; + break; + case GUI_ACTION_STEP_UP: + if (++editStep>64) editStep=64; + break; + case GUI_ACTION_STEP_DOWN: + if (--editStep<0) editStep=0; + break; + case GUI_ACTION_TOGGLE_EDIT: + edit=!edit; + break; + case GUI_ACTION_METRONOME: + e->setMetronome(!e->getMetronome()); + break; + case GUI_ACTION_REPEAT_PATTERN: + e->setRepeatPattern(!e->getRepeatPattern()); + break; + case GUI_ACTION_FOLLOW_ORDERS: + followOrders=!followOrders; + break; + case GUI_ACTION_FOLLOW_PATTERN: + followPattern=!followPattern; + break; + case GUI_ACTION_PANIC: + e->syncReset(); + break; + + case GUI_ACTION_WINDOW_EDIT_CONTROLS: + nextWindow=GUI_WINDOW_EDIT_CONTROLS; + break; + case GUI_ACTION_WINDOW_ORDERS: + nextWindow=GUI_WINDOW_ORDERS; + break; + case GUI_ACTION_WINDOW_INS_LIST: + nextWindow=GUI_WINDOW_INS_LIST; + break; + case GUI_ACTION_WINDOW_INS_EDIT: + nextWindow=GUI_WINDOW_INS_EDIT; + break; + case GUI_ACTION_WINDOW_SONG_INFO: + nextWindow=GUI_WINDOW_SONG_INFO; + break; + case GUI_ACTION_WINDOW_PATTERN: + nextWindow=GUI_WINDOW_PATTERN; + break; + case GUI_ACTION_WINDOW_WAVE_LIST: + nextWindow=GUI_WINDOW_WAVE_LIST; + break; + case GUI_ACTION_WINDOW_WAVE_EDIT: + nextWindow=GUI_WINDOW_WAVE_EDIT; + break; + case GUI_ACTION_WINDOW_SAMPLE_LIST: + nextWindow=GUI_WINDOW_SAMPLE_LIST; + break; + case GUI_ACTION_WINDOW_SAMPLE_EDIT: + nextWindow=GUI_WINDOW_SAMPLE_EDIT; + break; + case GUI_ACTION_WINDOW_ABOUT: + nextWindow=GUI_WINDOW_ABOUT; + break; + case GUI_ACTION_WINDOW_SETTINGS: + nextWindow=GUI_WINDOW_SETTINGS; + break; + case GUI_ACTION_WINDOW_MIXER: + nextWindow=GUI_WINDOW_MIXER; + break; + case GUI_ACTION_WINDOW_DEBUG: + nextWindow=GUI_WINDOW_DEBUG; + break; + case GUI_ACTION_WINDOW_OSCILLOSCOPE: + nextWindow=GUI_WINDOW_OSCILLOSCOPE; + break; + case GUI_ACTION_WINDOW_VOL_METER: + nextWindow=GUI_WINDOW_VOL_METER; + break; + case GUI_ACTION_WINDOW_STATS: + nextWindow=GUI_WINDOW_STATS; + break; + case GUI_ACTION_WINDOW_COMPAT_FLAGS: + nextWindow=GUI_WINDOW_COMPAT_FLAGS; + break; + case GUI_ACTION_WINDOW_PIANO: + nextWindow=GUI_WINDOW_PIANO; + break; + case GUI_ACTION_WINDOW_NOTES: + nextWindow=GUI_WINDOW_NOTES; + break; + case GUI_ACTION_WINDOW_CHANNELS: + nextWindow=GUI_WINDOW_CHANNELS; + break; + case GUI_ACTION_WINDOW_REGISTER_VIEW: + nextWindow=GUI_WINDOW_REGISTER_VIEW; + break; + + case GUI_ACTION_COLLAPSE_WINDOW: + collapseWindow=true; + break; + case GUI_ACTION_CLOSE_WINDOW: + switch (curWindow) { + case GUI_WINDOW_EDIT_CONTROLS: + editControlsOpen=false; + break; + case GUI_WINDOW_SONG_INFO: + songInfoOpen=false; + break; + case GUI_WINDOW_ORDERS: + ordersOpen=false; + break; + case GUI_WINDOW_INS_LIST: + insListOpen=false; + break; + case GUI_WINDOW_PATTERN: + patternOpen=false; + break; + case GUI_WINDOW_INS_EDIT: + insEditOpen=false; + break; + case GUI_WINDOW_WAVE_LIST: + waveListOpen=false; + break; + case GUI_WINDOW_WAVE_EDIT: + waveEditOpen=false; + break; + case GUI_WINDOW_SAMPLE_LIST: + sampleListOpen=false; + break; + case GUI_WINDOW_SAMPLE_EDIT: + sampleEditOpen=false; + break; + case GUI_WINDOW_MIXER: + mixerOpen=false; + break; + case GUI_WINDOW_ABOUT: + aboutOpen=false; + break; + case GUI_WINDOW_SETTINGS: + settingsOpen=false; + break; + case GUI_WINDOW_DEBUG: + debugOpen=false; + break; + case GUI_WINDOW_OSCILLOSCOPE: + oscOpen=false; + break; + case GUI_WINDOW_VOL_METER: + volMeterOpen=false; + break; + case GUI_WINDOW_STATS: + statsOpen=false; + break; + case GUI_WINDOW_COMPAT_FLAGS: + compatFlagsOpen=false; + break; + case GUI_WINDOW_PIANO: + pianoOpen=false; + break; + case GUI_WINDOW_NOTES: + notesOpen=false; + break; + case GUI_WINDOW_CHANNELS: + channelsOpen=false; + break; + case GUI_WINDOW_REGISTER_VIEW: + regViewOpen=false; + break; + default: + break; + } + curWindow=GUI_WINDOW_NOTHING; + break; + + case GUI_ACTION_PAT_NOTE_UP: + doTranspose(1); + break; + case GUI_ACTION_PAT_NOTE_DOWN: + doTranspose(-1); + break; + case GUI_ACTION_PAT_OCTAVE_UP: + doTranspose(12); + break; + case GUI_ACTION_PAT_OCTAVE_DOWN: + doTranspose(-12); + break; + case GUI_ACTION_PAT_SELECT_ALL: + doSelectAll(); + break; + case GUI_ACTION_PAT_CUT: + doCopy(true); + break; + case GUI_ACTION_PAT_COPY: + doCopy(false); + break; + case GUI_ACTION_PAT_PASTE: + doPaste(); + break; + case GUI_ACTION_PAT_CURSOR_UP: + moveCursor(0,-MAX(1,settings.scrollStep?editStep:1),false); + break; + case GUI_ACTION_PAT_CURSOR_DOWN: + moveCursor(0,MAX(1,settings.scrollStep?editStep:1),false); + break; + case GUI_ACTION_PAT_CURSOR_LEFT: + moveCursor(-1,0,false); + break; + case GUI_ACTION_PAT_CURSOR_RIGHT: + moveCursor(1,0,false); + break; + case GUI_ACTION_PAT_CURSOR_UP_ONE: + moveCursor(0,-1,false); + break; + case GUI_ACTION_PAT_CURSOR_DOWN_ONE: + moveCursor(0,1,false); + break; + case GUI_ACTION_PAT_CURSOR_LEFT_CHANNEL: + moveCursorPrevChannel(false); + break; + case GUI_ACTION_PAT_CURSOR_RIGHT_CHANNEL: + moveCursorNextChannel(false); + break; + case GUI_ACTION_PAT_CURSOR_NEXT_CHANNEL: + moveCursorNextChannel(true); + break; + case GUI_ACTION_PAT_CURSOR_PREVIOUS_CHANNEL: + moveCursorPrevChannel(true); + break; + case GUI_ACTION_PAT_CURSOR_BEGIN: + moveCursorTop(false); + break; + case GUI_ACTION_PAT_CURSOR_END: + moveCursorBottom(false); + break; + case GUI_ACTION_PAT_CURSOR_UP_COARSE: + moveCursor(0,-16,false); + break; + case GUI_ACTION_PAT_CURSOR_DOWN_COARSE: + moveCursor(0,16,false); + break; + case GUI_ACTION_PAT_SELECTION_UP: + moveCursor(0,-MAX(1,settings.scrollStep?editStep:1),true); + break; + case GUI_ACTION_PAT_SELECTION_DOWN: + moveCursor(0,MAX(1,settings.scrollStep?editStep:1),true); + break; + case GUI_ACTION_PAT_SELECTION_LEFT: + moveCursor(-1,0,true); + break; + case GUI_ACTION_PAT_SELECTION_RIGHT: + moveCursor(1,0,true); + break; + case GUI_ACTION_PAT_SELECTION_UP_ONE: + moveCursor(0,-1,true); + break; + case GUI_ACTION_PAT_SELECTION_DOWN_ONE: + moveCursor(0,1,true); + break; + case GUI_ACTION_PAT_SELECTION_BEGIN: + moveCursorTop(true); + break; + case GUI_ACTION_PAT_SELECTION_END: + moveCursorBottom(true); + break; + case GUI_ACTION_PAT_SELECTION_UP_COARSE: + moveCursor(0,-16,true); + break; + case GUI_ACTION_PAT_SELECTION_DOWN_COARSE: + moveCursor(0,16,true); + break; + case GUI_ACTION_PAT_DELETE: + doDelete(); + if (settings.stepOnDelete) { + moveCursor(0,editStep,false); + } + break; + case GUI_ACTION_PAT_PULL_DELETE: + doPullDelete(); + break; + case GUI_ACTION_PAT_INSERT: + doInsert(); + if (settings.stepOnInsert) { + moveCursor(0,editStep,false); + } + break; + case GUI_ACTION_PAT_MUTE_CURSOR: + if (cursor.xCoarse<0 || cursor.xCoarse>=e->getTotalChannelCount()) break; + e->toggleMute(cursor.xCoarse); + break; + case GUI_ACTION_PAT_SOLO_CURSOR: + if (cursor.xCoarse<0 || cursor.xCoarse>=e->getTotalChannelCount()) break; + e->toggleSolo(cursor.xCoarse); + break; + case GUI_ACTION_PAT_UNMUTE_ALL: + e->unmuteAll(); + break; + case GUI_ACTION_PAT_NEXT_ORDER: + if (e->getOrder()song.ordersLen-1) { + e->setOrder(e->getOrder()+1); + } + break; + case GUI_ACTION_PAT_PREV_ORDER: + if (e->getOrder()>0) { + e->setOrder(e->getOrder()-1); + } + break; + case GUI_ACTION_PAT_COLLAPSE: + if (cursor.xCoarse<0 || cursor.xCoarse>=e->getTotalChannelCount()) break; + e->song.chanCollapse[cursor.xCoarse]=!e->song.chanCollapse[cursor.xCoarse]; + break; + case GUI_ACTION_PAT_INCREASE_COLUMNS: + if (cursor.xCoarse<0 || cursor.xCoarse>=e->getTotalChannelCount()) break; + e->song.pat[cursor.xCoarse].effectRows++; + if (e->song.pat[cursor.xCoarse].effectRows>8) e->song.pat[cursor.xCoarse].effectRows=8; + break; + case GUI_ACTION_PAT_DECREASE_COLUMNS: + if (cursor.xCoarse<0 || cursor.xCoarse>=e->getTotalChannelCount()) break; + e->song.pat[cursor.xCoarse].effectRows--; + if (e->song.pat[cursor.xCoarse].effectRows<1) e->song.pat[cursor.xCoarse].effectRows=1; + break; + case GUI_ACTION_PAT_INTERPOLATE: + doInterpolate(); + break; + case GUI_ACTION_PAT_INVERT_VALUES: + doInvertValues(); + break; + case GUI_ACTION_PAT_FLIP_SELECTION: + doFlip(); + break; + case GUI_ACTION_PAT_COLLAPSE_ROWS: + doCollapse(2); + break; + case GUI_ACTION_PAT_EXPAND_ROWS: + doExpand(2); + break; + case GUI_ACTION_PAT_COLLAPSE_PAT: // TODO + break; + case GUI_ACTION_PAT_EXPAND_PAT: // TODO + break; + case GUI_ACTION_PAT_COLLAPSE_SONG: // TODO + break; + case GUI_ACTION_PAT_EXPAND_SONG: // TODO + break; + case GUI_ACTION_PAT_LATCH: // TODO + break; + + case GUI_ACTION_INS_LIST_ADD: + curIns=e->addInstrument(cursor.xCoarse); + MARK_MODIFIED; + break; + case GUI_ACTION_INS_LIST_DUPLICATE: + if (curIns>=0 && curIns<(int)e->song.ins.size()) { + int prevIns=curIns; + curIns=e->addInstrument(cursor.xCoarse); + (*e->song.ins[curIns])=(*e->song.ins[prevIns]); + MARK_MODIFIED; + } + break; + case GUI_ACTION_INS_LIST_OPEN: + openFileDialog(GUI_FILE_INS_OPEN); + break; + case GUI_ACTION_INS_LIST_SAVE: + if (curIns>=0 && curIns<(int)e->song.ins.size()) openFileDialog(GUI_FILE_INS_SAVE); + break; + case GUI_ACTION_INS_LIST_MOVE_UP: + if (e->moveInsUp(curIns)) curIns--; + break; + case GUI_ACTION_INS_LIST_MOVE_DOWN: + if (e->moveInsDown(curIns)) curIns++; + break; + case GUI_ACTION_INS_LIST_DELETE: + if (curIns>=0 && curIns<(int)e->song.ins.size()) { + e->delInstrument(curIns); + MARK_MODIFIED; + if (curIns>=(int)e->song.ins.size()) { + curIns--; + } + } + break; + case GUI_ACTION_INS_LIST_EDIT: + insEditOpen=true; + break; + case GUI_ACTION_INS_LIST_UP: + if (--curIns<0) curIns=0; + break; + case GUI_ACTION_INS_LIST_DOWN: + if (++curIns>=(int)e->song.ins.size()) curIns=((int)e->song.ins.size())-1; + break; + + case GUI_ACTION_WAVE_LIST_ADD: + curWave=e->addWave(); + MARK_MODIFIED; + break; + case GUI_ACTION_WAVE_LIST_DUPLICATE: + if (curWave>=0 && curWave<(int)e->song.wave.size()) { + int prevWave=curWave; + curWave=e->addWave(); + (*e->song.wave[curWave])=(*e->song.wave[prevWave]); + MARK_MODIFIED; + } + break; + case GUI_ACTION_WAVE_LIST_OPEN: + openFileDialog(GUI_FILE_WAVE_OPEN); + break; + case GUI_ACTION_WAVE_LIST_SAVE: + if (curWave>=0 && curWave<(int)e->song.wave.size()) openFileDialog(GUI_FILE_WAVE_SAVE); + break; + case GUI_ACTION_WAVE_LIST_MOVE_UP: + if (e->moveWaveUp(curWave)) curWave--; + break; + case GUI_ACTION_WAVE_LIST_MOVE_DOWN: + if (e->moveWaveDown(curWave)) curWave++; + break; + case GUI_ACTION_WAVE_LIST_DELETE: + if (curWave>=0 && curWave<(int)e->song.wave.size()) { + e->delWave(curWave); + MARK_MODIFIED; + if (curWave>=(int)e->song.wave.size()) { + curWave--; + } + } + break; + case GUI_ACTION_WAVE_LIST_EDIT: + waveEditOpen=true; + break; + case GUI_ACTION_WAVE_LIST_UP: + if (--curWave<0) curWave=0; + break; + case GUI_ACTION_WAVE_LIST_DOWN: + if (++curWave>=(int)e->song.wave.size()) curWave=((int)e->song.wave.size())-1; + break; + + case GUI_ACTION_SAMPLE_LIST_ADD: + curSample=e->addSample(); + MARK_MODIFIED; + break; + case GUI_ACTION_SAMPLE_LIST_OPEN: + openFileDialog(GUI_FILE_SAMPLE_OPEN); + break; + case GUI_ACTION_SAMPLE_LIST_SAVE: + if (curSample>=0 && curSample<(int)e->song.sample.size()) openFileDialog(GUI_FILE_SAMPLE_SAVE); + break; + case GUI_ACTION_SAMPLE_LIST_MOVE_UP: + if (e->moveSampleUp(curSample)) curSample--; + break; + case GUI_ACTION_SAMPLE_LIST_MOVE_DOWN: + if (e->moveSampleDown(curSample)) curSample++; + break; + case GUI_ACTION_SAMPLE_LIST_DELETE: + e->delSample(curSample); + MARK_MODIFIED; + if (curSample>=(int)e->song.sample.size()) { + curSample--; + } + break; + case GUI_ACTION_SAMPLE_LIST_EDIT: + sampleEditOpen=true; + break; + case GUI_ACTION_SAMPLE_LIST_UP: + if (--curSample<0) curSample=0; + break; + case GUI_ACTION_SAMPLE_LIST_DOWN: + if (++curSample>=(int)e->song.sample.size()) curSample=((int)e->song.sample.size())-1; + break; + case GUI_ACTION_SAMPLE_LIST_PREVIEW: + e->previewSample(curSample); + break; + case GUI_ACTION_SAMPLE_LIST_STOP_PREVIEW: + e->stopSamplePreview(); + break; + + case GUI_ACTION_ORDERS_UP: + if (e->getOrder()>0) { + e->setOrder(e->getOrder()-1); + } + break; + case GUI_ACTION_ORDERS_DOWN: + if (e->getOrder()song.ordersLen-1) { + e->setOrder(e->getOrder()+1); + } + break; + case GUI_ACTION_ORDERS_LEFT: { + DETERMINE_FIRST; + + do { + orderCursor--; + if (orderCursorsong.chanShow[orderCursor]); + break; + } + case GUI_ACTION_ORDERS_RIGHT: { + DETERMINE_LAST; + + do { + orderCursor++; + if (orderCursor>=lastChannel) { + orderCursor=lastChannel-1; + break; + } + } while (!e->song.chanShow[orderCursor]); + break; + } + case GUI_ACTION_ORDERS_INCREASE: { + if (orderCursor<0 || orderCursor>=e->getTotalChannelCount()) break; + int curOrder=e->getOrder(); + if (e->song.orders.ord[orderCursor][curOrder]<0x7f) { + e->song.orders.ord[orderCursor][curOrder]++; + } + break; + } + case GUI_ACTION_ORDERS_DECREASE: { + if (orderCursor<0 || orderCursor>=e->getTotalChannelCount()) break; + int curOrder=e->getOrder(); + if (e->song.orders.ord[orderCursor][curOrder]>0) { + e->song.orders.ord[orderCursor][curOrder]--; + } + break; + } + case GUI_ACTION_ORDERS_EDIT_MODE: + orderEditMode++; + if (orderEditMode>3) orderEditMode=0; + break; + case GUI_ACTION_ORDERS_LINK: + changeAllOrders=!changeAllOrders; + break; + case GUI_ACTION_ORDERS_ADD: + prepareUndo(GUI_UNDO_CHANGE_ORDER); + e->addOrder(false,false); + makeUndo(GUI_UNDO_CHANGE_ORDER); + break; + case GUI_ACTION_ORDERS_DUPLICATE: + prepareUndo(GUI_UNDO_CHANGE_ORDER); + e->addOrder(true,false); + makeUndo(GUI_UNDO_CHANGE_ORDER); + break; + case GUI_ACTION_ORDERS_DEEP_CLONE: + prepareUndo(GUI_UNDO_CHANGE_ORDER); + e->deepCloneOrder(false); + makeUndo(GUI_UNDO_CHANGE_ORDER); + if (!e->getWarnings().empty()) { + showWarning(e->getWarnings(),GUI_WARN_GENERIC); + } + break; + case GUI_ACTION_ORDERS_DUPLICATE_END: + prepareUndo(GUI_UNDO_CHANGE_ORDER); + e->addOrder(true,true); + makeUndo(GUI_UNDO_CHANGE_ORDER); + break; + case GUI_ACTION_ORDERS_DEEP_CLONE_END: + prepareUndo(GUI_UNDO_CHANGE_ORDER); + e->deepCloneOrder(true); + makeUndo(GUI_UNDO_CHANGE_ORDER); + if (!e->getWarnings().empty()) { + showWarning(e->getWarnings(),GUI_WARN_GENERIC); + } + break; + case GUI_ACTION_ORDERS_REMOVE: + prepareUndo(GUI_UNDO_CHANGE_ORDER); + e->deleteOrder(); + makeUndo(GUI_UNDO_CHANGE_ORDER); + break; + case GUI_ACTION_ORDERS_MOVE_UP: + prepareUndo(GUI_UNDO_CHANGE_ORDER); + e->moveOrderUp(); + makeUndo(GUI_UNDO_CHANGE_ORDER); + break; + case GUI_ACTION_ORDERS_MOVE_DOWN: + prepareUndo(GUI_UNDO_CHANGE_ORDER); + e->moveOrderDown(); + makeUndo(GUI_UNDO_CHANGE_ORDER); + break; + case GUI_ACTION_ORDERS_REPLAY: + e->setOrder(e->getOrder()); + break; + } +} \ No newline at end of file diff --git a/src/gui/editControls.cpp b/src/gui/editControls.cpp new file mode 100644 index 000000000..68f0fee2d --- /dev/null +++ b/src/gui/editControls.cpp @@ -0,0 +1,330 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "gui.h" +#include "IconsFontAwesome4.h" + +void FurnaceGUI::drawEditControls() { + if (nextWindow==GUI_WINDOW_EDIT_CONTROLS) { + editControlsOpen=true; + ImGui::SetNextWindowFocus(); + nextWindow=GUI_WINDOW_NOTHING; + } + if (!editControlsOpen) return; + switch (settings.controlLayout) { + case 0: // classic + if (ImGui::Begin("Play/Edit Controls",&editControlsOpen)) { + ImGui::Text("Octave"); + ImGui::SameLine(); + if (ImGui::InputInt("##Octave",&curOctave,1,1)) { + if (curOctave>7) curOctave=7; + if (curOctave<-5) curOctave=-5; + for (size_t i=0; inoteOff(activeNotes[i].chan); + } + activeNotes.clear(); + + if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { + nextWindow=GUI_WINDOW_PATTERN; + } + } + + ImGui::Text("Edit Step"); + ImGui::SameLine(); + if (ImGui::InputInt("##EditStep",&editStep,1,1)) { + if (editStep>=e->song.patLen) editStep=e->song.patLen-1; + if (editStep<0) editStep=0; + + if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { + nextWindow=GUI_WINDOW_PATTERN; + } + } + + if (ImGui::Button(ICON_FA_PLAY "##Play")) { + play(); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_STOP "##Stop")) { + stop(); + } + ImGui::SameLine(); + ImGui::Checkbox("Edit",&edit); + ImGui::SameLine(); + bool metro=e->getMetronome(); + if (ImGui::Checkbox("Metronome",&metro)) { + e->setMetronome(metro); + } + + ImGui::Text("Follow"); + ImGui::SameLine(); + unimportant(ImGui::Checkbox("Orders",&followOrders)); + ImGui::SameLine(); + unimportant(ImGui::Checkbox("Pattern",&followPattern)); + + bool repeatPattern=e->getRepeatPattern(); + if (ImGui::Checkbox("Repeat pattern",&repeatPattern)) { + e->setRepeatPattern(repeatPattern); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_ARROW_DOWN "##StepOne")) { + e->stepOne(cursor.y); + } + } + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS; + ImGui::End(); + break; + case 1: // compact + if (ImGui::Begin("Play/Edit Controls",&editControlsOpen,ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoScrollWithMouse)) { + if (ImGui::Button(ICON_FA_STOP "##Stop")) { + stop(); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_PLAY "##Play")) { + play(); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_ARROW_DOWN "##StepOne")) { + e->stepOne(cursor.y); + } + + ImGui::SameLine(); + bool repeatPattern=e->getRepeatPattern(); + ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(repeatPattern)?0.6f:0.2f,0.2f,1.0f)); + if (ImGui::Button(ICON_FA_REPEAT "##RepeatPattern")) { + e->setRepeatPattern(!repeatPattern); + } + ImGui::PopStyleColor(); + + ImGui::SameLine(); + ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(edit)?0.6f:0.2f,0.2f,1.0f)); + if (ImGui::Button(ICON_FA_CIRCLE "##Edit")) { + edit=!edit; + } + ImGui::PopStyleColor(); + + ImGui::SameLine(); + bool metro=e->getMetronome(); + ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(metro)?0.6f:0.2f,0.2f,1.0f)); + if (ImGui::Button(ICON_FA_BELL_O "##Metronome")) { + e->setMetronome(!metro); + } + ImGui::PopStyleColor(); + + ImGui::SameLine(); + ImGui::Text("Octave"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(96.0f*dpiScale); + if (ImGui::InputInt("##Octave",&curOctave,1,1)) { + if (curOctave>7) curOctave=7; + if (curOctave<-5) curOctave=-5; + for (size_t i=0; inoteOff(activeNotes[i].chan); + } + activeNotes.clear(); + + if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { + nextWindow=GUI_WINDOW_PATTERN; + } + } + + ImGui::SameLine(); + ImGui::Text("Edit Step"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(96.0f*dpiScale); + if (ImGui::InputInt("##EditStep",&editStep,1,1)) { + if (editStep>=e->song.patLen) editStep=e->song.patLen-1; + if (editStep<0) editStep=0; + + if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { + nextWindow=GUI_WINDOW_PATTERN; + } + } + + ImGui::SameLine(); + ImGui::Text("Follow"); + ImGui::SameLine(); + unimportant(ImGui::Checkbox("Orders",&followOrders)); + ImGui::SameLine(); + unimportant(ImGui::Checkbox("Pattern",&followPattern)); + } + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS; + ImGui::End(); + break; + case 2: // compact vertical + if (ImGui::Begin("Play/Edit Controls",&editControlsOpen,ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoScrollWithMouse)) { + if (ImGui::Button(ICON_FA_PLAY "##Play")) { + play(); + } + if (ImGui::Button(ICON_FA_STOP "##Stop")) { + stop(); + } + if (ImGui::Button(ICON_FA_ARROW_DOWN "##StepOne")) { + e->stepOne(cursor.y); + } + + bool repeatPattern=e->getRepeatPattern(); + ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(repeatPattern)?0.6f:0.2f,0.2f,1.0f)); + if (ImGui::Button(ICON_FA_REPEAT "##RepeatPattern")) { + e->setRepeatPattern(!repeatPattern); + } + ImGui::PopStyleColor(); + + ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(edit)?0.6f:0.2f,0.2f,1.0f)); + if (ImGui::Button(ICON_FA_CIRCLE "##Edit")) { + edit=!edit; + } + ImGui::PopStyleColor(); + + bool metro=e->getMetronome(); + ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(metro)?0.6f:0.2f,0.2f,1.0f)); + if (ImGui::Button(ICON_FA_BELL_O "##Metronome")) { + e->setMetronome(!metro); + } + ImGui::PopStyleColor(); + + ImGui::Text("Oct."); + float avail=ImGui::GetContentRegionAvail().x; + ImGui::SetNextItemWidth(avail); + if (ImGui::InputInt("##Octave",&curOctave,0,0)) { + if (curOctave>7) curOctave=7; + if (curOctave<-5) curOctave=-5; + for (size_t i=0; inoteOff(activeNotes[i].chan); + } + activeNotes.clear(); + + if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { + nextWindow=GUI_WINDOW_PATTERN; + } + } + + ImGui::Text("Step"); + ImGui::SetNextItemWidth(avail); + if (ImGui::InputInt("##EditStep",&editStep,0,0)) { + if (editStep>=e->song.patLen) editStep=e->song.patLen-1; + if (editStep<0) editStep=0; + + if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { + nextWindow=GUI_WINDOW_PATTERN; + } + } + + ImGui::Text("Foll."); + ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(followOrders)?0.6f:0.2f,0.2f,1.0f)); + if (ImGui::SmallButton("Ord##FollowOrders")) { handleUnimportant + followOrders=!followOrders; + } + ImGui::PopStyleColor(); + ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(followPattern)?0.6f:0.2f,0.2f,1.0f)); + if (ImGui::SmallButton("Pat##FollowPattern")) { handleUnimportant + followPattern=!followPattern; + } + ImGui::PopStyleColor(); + } + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS; + ImGui::End(); + break; + case 3: // split + if (ImGui::Begin("Play Controls",&editControlsOpen,ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoScrollWithMouse)) { + if (e->isPlaying()) { + if (ImGui::Button(ICON_FA_STOP "##Stop")) { + stop(); + } + } else { + if (ImGui::Button(ICON_FA_PLAY "##Play")) { + play(); + } + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_PLAY_CIRCLE "##PlayAgain")) { + play(); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_ARROW_DOWN "##StepOne")) { + e->stepOne(cursor.y); + } + + ImGui::SameLine(); + ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(edit)?0.6f:0.2f,0.2f,1.0f)); + if (ImGui::Button(ICON_FA_CIRCLE "##Edit")) { + edit=!edit; + } + ImGui::PopStyleColor(); + + bool metro=e->getMetronome(); + ImGui::SameLine(); + ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(metro)?0.6f:0.2f,0.2f,1.0f)); + if (ImGui::Button(ICON_FA_BELL_O "##Metronome")) { + e->setMetronome(!metro); + } + ImGui::PopStyleColor(); + + ImGui::SameLine(); + bool repeatPattern=e->getRepeatPattern(); + ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(repeatPattern)?0.6f:0.2f,0.2f,1.0f)); + if (ImGui::Button(ICON_FA_REPEAT "##RepeatPattern")) { + e->setRepeatPattern(!repeatPattern); + } + ImGui::PopStyleColor(); + } + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS; + ImGui::End(); + + if (ImGui::Begin("Edit Controls",&editControlsOpen)) { + ImGui::Columns(2); + ImGui::Text("Octave"); + ImGui::SameLine(); + float cursor=ImGui::GetCursorPosX(); + float avail=ImGui::GetContentRegionAvail().x; + ImGui::SetNextItemWidth(avail); + if (ImGui::InputInt("##Octave",&curOctave,1,1)) { + if (curOctave>7) curOctave=7; + if (curOctave<-5) curOctave=-5; + for (size_t i=0; inoteOff(activeNotes[i].chan); + } + activeNotes.clear(); + + if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { + nextWindow=GUI_WINDOW_PATTERN; + } + } + + ImGui::Text("Step"); + ImGui::SameLine(); + ImGui::SetCursorPosX(cursor); + ImGui::SetNextItemWidth(avail); + if (ImGui::InputInt("##EditStep",&editStep,1,1)) { + if (editStep>=e->song.patLen) editStep=e->song.patLen-1; + if (editStep<0) editStep=0; + + if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { + nextWindow=GUI_WINDOW_PATTERN; + } + } + ImGui::NextColumn(); + + unimportant(ImGui::Checkbox("Follow orders",&followOrders)); + unimportant(ImGui::Checkbox("Follow pattern",&followPattern)); + } + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS; + ImGui::End(); + break; + } +} \ No newline at end of file diff --git a/src/gui/editing.cpp b/src/gui/editing.cpp new file mode 100644 index 000000000..59c1fc342 --- /dev/null +++ b/src/gui/editing.cpp @@ -0,0 +1,1015 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "gui.h" +#include "../ta-log.h" +#include "guiConst.h" +#include + +#include "actionUtil.h" + +const char* noteNameNormal(short note, short octave) { + if (note==100) { // note cut + return "OFF"; + } else if (note==101) { // note off and envelope release + return "==="; + } else if (note==102) { // envelope release only + return "REL"; + } else if (octave==0 && note==0) { + return "..."; + } + int seek=(note+(signed char)octave*12)+60; + if (seek<0 || seek>=180) { + return "???"; + } + return noteNames[seek]; +} + +void FurnaceGUI::prepareUndo(ActionType action) { + int order=e->getOrder(); + switch (action) { + case GUI_UNDO_CHANGE_ORDER: + oldOrders=e->song.orders; + oldOrdersLen=e->song.ordersLen; + break; + case GUI_UNDO_PATTERN_EDIT: + case GUI_UNDO_PATTERN_DELETE: + case GUI_UNDO_PATTERN_PULL: + case GUI_UNDO_PATTERN_PUSH: + case GUI_UNDO_PATTERN_CUT: + case GUI_UNDO_PATTERN_PASTE: + case GUI_UNDO_PATTERN_CHANGE_INS: + case GUI_UNDO_PATTERN_INTERPOLATE: + case GUI_UNDO_PATTERN_FADE: + case GUI_UNDO_PATTERN_SCALE: + case GUI_UNDO_PATTERN_RANDOMIZE: + case GUI_UNDO_PATTERN_INVERT_VAL: + case GUI_UNDO_PATTERN_FLIP: + case GUI_UNDO_PATTERN_COLLAPSE: + case GUI_UNDO_PATTERN_EXPAND: + for (int i=0; igetTotalChannelCount(); i++) { + e->song.pat[i].getPattern(e->song.orders.ord[i][order],false)->copyOn(oldPat[i]); + } + break; + } +} + +void FurnaceGUI::makeUndo(ActionType action) { + bool doPush=false; + UndoStep s; + s.type=action; + s.cursor=cursor; + s.selStart=selStart; + s.selEnd=selEnd; + int order=e->getOrder(); + s.order=order; + s.nibble=curNibble; + switch (action) { + case GUI_UNDO_CHANGE_ORDER: + for (int i=0; isong.orders.ord[i][j]) { + s.ord.push_back(UndoOrderData(i,j,oldOrders.ord[i][j],e->song.orders.ord[i][j])); + } + } + } + s.oldOrdersLen=oldOrdersLen; + s.newOrdersLen=e->song.ordersLen; + if (oldOrdersLen!=e->song.ordersLen) { + doPush=true; + } + if (!s.ord.empty()) { + doPush=true; + } + break; + case GUI_UNDO_PATTERN_EDIT: + case GUI_UNDO_PATTERN_DELETE: + case GUI_UNDO_PATTERN_PULL: + case GUI_UNDO_PATTERN_PUSH: + case GUI_UNDO_PATTERN_CUT: + case GUI_UNDO_PATTERN_PASTE: + case GUI_UNDO_PATTERN_CHANGE_INS: + case GUI_UNDO_PATTERN_INTERPOLATE: + case GUI_UNDO_PATTERN_FADE: + case GUI_UNDO_PATTERN_SCALE: + case GUI_UNDO_PATTERN_RANDOMIZE: + case GUI_UNDO_PATTERN_INVERT_VAL: + case GUI_UNDO_PATTERN_FLIP: + case GUI_UNDO_PATTERN_COLLAPSE: + case GUI_UNDO_PATTERN_EXPAND: + for (int i=0; igetTotalChannelCount(); i++) { + DivPattern* p=e->song.pat[i].getPattern(e->song.orders.ord[i][order],false); + for (int j=0; jsong.patLen; j++) { + for (int k=0; k<32; k++) { + if (p->data[j][k]!=oldPat[i]->data[j][k]) { + s.pat.push_back(UndoPatternData(i,e->song.orders.ord[i][order],j,k,oldPat[i]->data[j][k],p->data[j][k])); + } + } + } + } + if (!s.pat.empty()) { + doPush=true; + } + break; + } + if (doPush) { + MARK_MODIFIED; + undoHist.push_back(s); + redoHist.clear(); + if (undoHist.size()>settings.maxUndoSteps) undoHist.pop_front(); + } +} + +void FurnaceGUI::doSelectAll() { + finishSelection(); + curNibble=false; + if (selStart.xFine==0 && selEnd.xFine==2+e->song.pat[selEnd.xCoarse].effectRows*2) { + if (selStart.y==0 && selEnd.y==e->song.patLen-1) { // select entire pattern + selStart.xCoarse=0; + selStart.xFine=0; + selEnd.xCoarse=e->getTotalChannelCount()-1; + selEnd.xFine=2+e->song.pat[selEnd.xCoarse].effectRows*2; + } else { // select entire column + selStart.y=0; + selEnd.y=e->song.patLen-1; + } + } else { + int selStartX=0; + int selEndX=0; + // find row position + for (SelectionPoint i; i.xCoarse!=selStart.xCoarse || i.xFine!=selStart.xFine; selStartX++) { + i.xFine++; + if (i.xFine>=3+e->song.pat[i.xCoarse].effectRows*2) { + i.xFine=0; + i.xCoarse++; + } + } + for (SelectionPoint i; i.xCoarse!=selEnd.xCoarse || i.xFine!=selEnd.xFine; selEndX++) { + i.xFine++; + if (i.xFine>=3+e->song.pat[i.xCoarse].effectRows*2) { + i.xFine=0; + i.xCoarse++; + } + } + + float aspect=float(selEndX-selStartX+1)/float(selEnd.y-selStart.y+1); + if (aspect<=1.0f && !(selStart.y==0 && selEnd.y==e->song.patLen-1)) { // up-down + selStart.y=0; + selEnd.y=e->song.patLen-1; + } else { // left-right + selStart.xFine=0; + selEnd.xFine=2+e->song.pat[selEnd.xCoarse].effectRows*2; + } + } +} + +#define maskOut(x) \ + if (x==0) { \ + if (!opMaskNote) continue; \ + } else if (x==1) { \ + if (!opMaskIns) continue; \ + } else if (x==2) { \ + if (!opMaskVol) continue; \ + } else if (((x)&1)==0) { \ + if (!opMaskEffectVal) continue; \ + } else if (((x)&1)==1) { \ + if (!opMaskEffect) continue; \ + } + +void FurnaceGUI::doDelete() { + finishSelection(); + prepareUndo(GUI_UNDO_PATTERN_DELETE); + curNibble=false; + + int iCoarse=selStart.xCoarse; + int iFine=selStart.xFine; + int ord=e->getOrder(); + for (; iCoarse<=selEnd.xCoarse; iCoarse++) { + if (!e->song.chanShow[iCoarse]) continue; + DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); + for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsedata[j][iFine]=0; + if (selStart.y==selEnd.y) pat->data[j][2]=-1; + } + pat->data[j][iFine+1]=(iFine<1)?0:-1; + } + } + iFine=0; + } + + makeUndo(GUI_UNDO_PATTERN_DELETE); +} + +void FurnaceGUI::doPullDelete() { + finishSelection(); + prepareUndo(GUI_UNDO_PATTERN_PULL); + curNibble=false; + + if (settings.pullDeleteBehavior) { + if (--selStart.y<0) selStart.y=0; + if (--selEnd.y<0) selEnd.y=0; + if (--cursor.y<0) cursor.y=0; + updateScroll(cursor.y); + } + + int iCoarse=selStart.xCoarse; + int iFine=selStart.xFine; + int ord=e->getOrder(); + for (; iCoarse<=selEnd.xCoarse; iCoarse++) { + if (!e->song.chanShow[iCoarse]) continue; + DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); + for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsesong.patLen; j++) { + if (jsong.patLen-1) { + if (iFine==0) { + pat->data[j][iFine]=pat->data[j+1][iFine]; + } + pat->data[j][iFine+1]=pat->data[j+1][iFine+1]; + } else { + if (iFine==0) { + pat->data[j][iFine]=0; + } + pat->data[j][iFine+1]=(iFine<1)?0:-1; + } + } + } + iFine=0; + } + + makeUndo(GUI_UNDO_PATTERN_PULL); +} + +void FurnaceGUI::doInsert() { + finishSelection(); + prepareUndo(GUI_UNDO_PATTERN_PUSH); + curNibble=false; + + int iCoarse=selStart.xCoarse; + int iFine=selStart.xFine; + int ord=e->getOrder(); + for (; iCoarse<=selEnd.xCoarse; iCoarse++) { + if (!e->song.chanShow[iCoarse]) continue; + DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); + for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsesong.patLen-1; j>=selStart.y; j--) { + if (j==selStart.y) { + if (iFine==0) { + pat->data[j][iFine]=0; + } + pat->data[j][iFine+1]=(iFine<1)?0:-1; + } else { + if (iFine==0) { + pat->data[j][iFine]=pat->data[j-1][iFine]; + } + pat->data[j][iFine+1]=pat->data[j-1][iFine+1]; + } + } + } + iFine=0; + } + + makeUndo(GUI_UNDO_PATTERN_PUSH); +} + +void FurnaceGUI::doTranspose(int amount) { + finishSelection(); + prepareUndo(GUI_UNDO_PATTERN_DELETE); + curNibble=false; + + int iCoarse=selStart.xCoarse; + int iFine=selStart.xFine; + int ord=e->getOrder(); + for (; iCoarse<=selEnd.xCoarse; iCoarse++) { + if (!e->song.chanShow[iCoarse]) continue; + DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); + for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsedata[j][0]; + int origOctave=(signed char)pat->data[j][1]; + if (origNote!=0 && origNote!=100 && origNote!=101 && origNote!=102) { + origNote+=amount; + while (origNote>12) { + origNote-=12; + origOctave++; + } + while (origNote<1) { + origNote+=12; + origOctave--; + } + if (origOctave==9 && origNote>11) { + origNote=11; + origOctave=9; + } + if (origOctave>9) { + origNote=11; + origOctave=9; + } + if (origOctave<-5) { + origNote=1; + origOctave=-5; + } + pat->data[j][0]=origNote; + pat->data[j][1]=(unsigned char)origOctave; + } + } else { + int top=255; + if (iFine==1) { + if (e->song.ins.empty()) continue; + top=e->song.ins.size()-1; + } else if (iFine==2) { // volume + top=e->getMaxVolumeChan(iCoarse); + } + if (pat->data[j][iFine+1]==-1) continue; + pat->data[j][iFine+1]=MIN(top,MAX(0,pat->data[j][iFine+1]+amount)); + } + } + } + iFine=0; + } + + makeUndo(GUI_UNDO_PATTERN_DELETE); +} + +void FurnaceGUI::doCopy(bool cut) { + finishSelection(); + if (cut) { + curNibble=false; + prepareUndo(GUI_UNDO_PATTERN_CUT); + } + clipboard=fmt::sprintf("org.tildearrow.furnace - Pattern Data (%d)\n%d",DIV_ENGINE_VERSION,selStart.xFine); + + for (int j=selStart.y; j<=selEnd.y; j++) { + int iCoarse=selStart.xCoarse; + int iFine=selStart.xFine; + if (iFine>3 && !(iFine&1)) { + iFine--; + } + int ord=e->getOrder(); + clipboard+='\n'; + for (; iCoarse<=selEnd.xCoarse; iCoarse++) { + if (!e->song.chanShow[iCoarse]) continue; + DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); + for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsedata[j][0],pat->data[j][1]); + if (cut) { + pat->data[j][0]=0; + pat->data[j][1]=0; + } + } else { + if (pat->data[j][iFine+1]==-1) { + clipboard+=".."; + } else { + clipboard+=fmt::sprintf("%.2X",pat->data[j][iFine+1]); + } + if (cut) { + pat->data[j][iFine+1]=-1; + } + } + } + clipboard+='|'; + iFine=0; + } + } + SDL_SetClipboardText(clipboard.c_str()); + + if (cut) { + makeUndo(GUI_UNDO_PATTERN_CUT); + } +} + +void FurnaceGUI::doPaste(PasteMode mode) { + finishSelection(); + prepareUndo(GUI_UNDO_PATTERN_PASTE); + char* clipText=SDL_GetClipboardText(); + if (clipText!=NULL) { + if (clipText[0]) { + clipboard=clipText; + } + SDL_free(clipText); + } + std::vector data; + String tempS; + for (char i: clipboard) { + if (i=='\r') continue; + if (i=='\n') { + data.push_back(tempS); + tempS=""; + continue; + } + tempS+=i; + } + data.push_back(tempS); + + int startOff=-1; + bool invalidData=false; + if (data.size()<2) return; + if (data[0]!=fmt::sprintf("org.tildearrow.furnace - Pattern Data (%d)",DIV_ENGINE_VERSION)) return; + if (sscanf(data[1].c_str(),"%d",&startOff)!=1) return; + if (startOff<0) return; + + DETERMINE_LAST; + + int j=cursor.y; + char note[4]; + int ord=e->getOrder(); + for (size_t i=2; isong.patLen; i++) { + size_t charPos=0; + int iCoarse=cursor.xCoarse; + int iFine=(startOff>2 && cursor.xFine>2)?(((cursor.xFine-1)&(~1))|1):startOff; + + String& line=data[i]; + + while (charPossong.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); + if (line[charPos]=='|') { + iCoarse++; + if (iCoarsesong.chanShow[iCoarse]) { + iCoarse++; + if (iCoarse>=lastChannel) break; + } + iFine=0; + charPos++; + continue; + } + if (iFine==0) { + if (charPos>=line.size()) { + invalidData=true; + break; + } + note[0]=line[charPos++]; + if (charPos>=line.size()) { + invalidData=true; + break; + } + note[1]=line[charPos++]; + if (charPos>=line.size()) { + invalidData=true; + break; + } + note[2]=line[charPos++]; + note[3]=0; + + if (iFine==0 && !opMaskNote) { + iFine++; + continue; + } + + if ((mode==GUI_PASTE_MODE_MIX_BG || mode==GUI_PASTE_MODE_MIX_FG) && strcmp(note,"...")==0) { + // do nothing. + } else { + if (mode!=GUI_PASTE_MODE_MIX_BG || (pat->data[j][0]==0 && pat->data[j][1]==0)) { + if (!decodeNote(note,pat->data[j][0],pat->data[j][1])) { + invalidData=true; + break; + } + } + } + } else { + if (charPos>=line.size()) { + invalidData=true; + break; + } + note[0]=line[charPos++]; + if (charPos>=line.size()) { + invalidData=true; + break; + } + note[1]=line[charPos++]; + note[2]=0; + + if (iFine==1) { + if (!opMaskIns) { + iFine++; + continue; + } + } else if (iFine==2) { + if (!opMaskVol) { + iFine++; + continue; + } + } else if ((iFine&1)==0) { + if (!opMaskEffectVal) { + iFine++; + continue; + } + } else if ((iFine&1)==1) { + if (!opMaskEffect) { + iFine++; + continue; + } + } + + if (strcmp(note,"..")==0) { + if (!(mode==GUI_PASTE_MODE_MIX_BG || mode==GUI_PASTE_MODE_MIX_FG)) { + pat->data[j][iFine+1]=-1; + } + } else { + unsigned int val=0; + if (sscanf(note,"%2X",&val)!=1) { + invalidData=true; + break; + } + if (mode!=GUI_PASTE_MODE_MIX_BG || pat->data[j][iFine+1]==-1) { + if (iFine<(3+e->song.pat[iCoarse].effectRows*2)) pat->data[j][iFine+1]=val; + } + } + } + iFine++; + } + + if (invalidData) { + logW("invalid clipboard data! failed at line %d char %d\n",i,charPos); + logW("%s\n",line.c_str()); + break; + } + j++; + if (mode==GUI_PASTE_MODE_OVERFLOW && j>=e->song.patLen && ordsong.ordersLen-1) { + j=0; + ord++; + } + + if (mode==GUI_PASTE_MODE_FLOOD && i==data.size()-1) { + i=1; + } + } + + makeUndo(GUI_UNDO_PATTERN_PASTE); +} + +void FurnaceGUI::doChangeIns(int ins) { + finishSelection(); + prepareUndo(GUI_UNDO_PATTERN_CHANGE_INS); + + int iCoarse=selStart.xCoarse; + int ord=e->getOrder(); + for (; iCoarse<=selEnd.xCoarse; iCoarse++) { + if (!e->song.chanShow[iCoarse]) continue; + DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); + for (int j=selStart.y; j<=selEnd.y; j++) { + if (pat->data[j][2]!=-1 || !(pat->data[j][0]==0 && pat->data[j][1]==0)) { + pat->data[j][2]=ins; + } + } + } + + makeUndo(GUI_UNDO_PATTERN_CHANGE_INS); +} + +void FurnaceGUI::doInterpolate() { + finishSelection(); + prepareUndo(GUI_UNDO_PATTERN_INTERPOLATE); + + std::vector> points; + int iCoarse=selStart.xCoarse; + int iFine=selStart.xFine; + int ord=e->getOrder(); + for (; iCoarse<=selEnd.xCoarse; iCoarse++) { + if (!e->song.chanShow[iCoarse]) continue; + DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); + for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsedata[j][iFine+1]!=-1) { + points.emplace(points.end(),j,pat->data[j][iFine+1]); + } + } + + if (points.size()>1) for (size_t j=0; j& curPoint=points[j]; + std::pair& nextPoint=points[j+1]; + double distance=nextPoint.first-curPoint.first; + for (int k=0; k<(nextPoint.first-curPoint.first); k++) { + pat->data[k+curPoint.first][iFine+1]=curPoint.second+((nextPoint.second-curPoint.second)*(double)k/distance); + } + } + } else { + for (int j=selStart.y; j<=selEnd.y; j++) { + if (pat->data[j][0]!=0 && pat->data[j][1]!=0) { + if (pat->data[j][0]!=100 && pat->data[j][0]!=101 && pat->data[j][0]!=102) { + points.emplace(points.end(),j,pat->data[j][0]+pat->data[j][1]*12); + } + } + } + + if (points.size()>1) for (size_t j=0; j& curPoint=points[j]; + std::pair& nextPoint=points[j+1]; + double distance=nextPoint.first-curPoint.first; + for (int k=0; k<(nextPoint.first-curPoint.first); k++) { + int val=curPoint.second+((nextPoint.second-curPoint.second)*(double)k/distance); + pat->data[k+curPoint.first][0]=val%12; + pat->data[k+curPoint.first][1]=val/12; + if (pat->data[k+curPoint.first][0]==0) { + pat->data[k+curPoint.first][0]=12; + pat->data[k+curPoint.first][1]--; + } + pat->data[k+curPoint.first][1]&=255; + } + } + } + } + iFine=0; + } + + makeUndo(GUI_UNDO_PATTERN_INTERPOLATE); +} + +void FurnaceGUI::doFade(int p0, int p1, bool mode) { + finishSelection(); + prepareUndo(GUI_UNDO_PATTERN_FADE); + + int iCoarse=selStart.xCoarse; + int iFine=selStart.xFine; + int ord=e->getOrder(); + for (; iCoarse<=selEnd.xCoarse; iCoarse++) { + if (!e->song.chanShow[iCoarse]) continue; + DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); + for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsesong.ins.empty()) continue; + absoluteTop=e->song.ins.size()-1; + } else if (iFine==2) { // volume + absoluteTop=e->getMaxVolumeChan(iCoarse); + } + if (selEnd.y-selStart.y<1) continue; + for (int j=selStart.y; j<=selEnd.y; j++) { + double fraction=double(j-selStart.y)/double(selEnd.y-selStart.y); + int value=p0+double(p1-p0)*fraction; + if (mode) { // nibble + value&=15; + pat->data[j][iFine+1]=MIN(absoluteTop,value|(value<<4)); + } else { // byte + pat->data[j][iFine+1]=MIN(absoluteTop,value); + } + } + } + } + iFine=0; + } + + makeUndo(GUI_UNDO_PATTERN_FADE); +} + +void FurnaceGUI::doInvertValues() { + finishSelection(); + prepareUndo(GUI_UNDO_PATTERN_INVERT_VAL); + + int iCoarse=selStart.xCoarse; + int iFine=selStart.xFine; + int ord=e->getOrder(); + for (; iCoarse<=selEnd.xCoarse; iCoarse++) { + if (!e->song.chanShow[iCoarse]) continue; + DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); + for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsesong.ins.empty()) continue; + top=e->song.ins.size()-1; + } else if (iFine==2) { // volume + top=e->getMaxVolumeChan(iCoarse); + } + for (int j=selStart.y; j<=selEnd.y; j++) { + if (pat->data[j][iFine+1]==-1) continue; + pat->data[j][iFine+1]=top-pat->data[j][iFine+1]; + } + } + } + iFine=0; + } + + makeUndo(GUI_UNDO_PATTERN_INVERT_VAL); +} + +void FurnaceGUI::doScale(float top) { + finishSelection(); + prepareUndo(GUI_UNDO_PATTERN_SCALE); + + int iCoarse=selStart.xCoarse; + int iFine=selStart.xFine; + int ord=e->getOrder(); + for (; iCoarse<=selEnd.xCoarse; iCoarse++) { + if (!e->song.chanShow[iCoarse]) continue; + DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); + for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsesong.ins.empty()) continue; + absoluteTop=e->song.ins.size()-1; + } else if (iFine==2) { // volume + absoluteTop=e->getMaxVolumeChan(iCoarse); + } + for (int j=selStart.y; j<=selEnd.y; j++) { + if (pat->data[j][iFine+1]==-1) continue; + pat->data[j][iFine+1]=MIN(absoluteTop,(double)pat->data[j][iFine+1]*(top/100.0f)); + } + } + } + iFine=0; + } + + makeUndo(GUI_UNDO_PATTERN_SCALE); +} + +void FurnaceGUI::doRandomize(int bottom, int top, bool mode) { + finishSelection(); + prepareUndo(GUI_UNDO_PATTERN_RANDOMIZE); + + int iCoarse=selStart.xCoarse; + int iFine=selStart.xFine; + int ord=e->getOrder(); + for (; iCoarse<=selEnd.xCoarse; iCoarse++) { + if (!e->song.chanShow[iCoarse]) continue; + DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); + for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsesong.ins.empty()) continue; + absoluteTop=e->song.ins.size()-1; + } else if (iFine==2) { // volume + absoluteTop=e->getMaxVolumeChan(iCoarse); + } + for (int j=selStart.y; j<=selEnd.y; j++) { + int value=0; + int value2=0; + if (top-bottom<=0) { + value=MIN(absoluteTop,bottom); + value2=MIN(absoluteTop,bottom); + } else { + value=MIN(absoluteTop,bottom+(rand()%(top-bottom+1))); + value2=MIN(absoluteTop,bottom+(rand()%(top-bottom+1))); + } + if (mode) { + value&=15; + value2&=15; + pat->data[j][iFine+1]=value|(value2<<4); + } else { + pat->data[j][iFine+1]=value; + } + } + } + } + iFine=0; + } + + makeUndo(GUI_UNDO_PATTERN_RANDOMIZE); +} + +void FurnaceGUI::doFlip() { + finishSelection(); + prepareUndo(GUI_UNDO_PATTERN_FLIP); + + DivPattern patBuffer; + int iCoarse=selStart.xCoarse; + int iFine=selStart.xFine; + int ord=e->getOrder(); + for (; iCoarse<=selEnd.xCoarse; iCoarse++) { + if (!e->song.chanShow[iCoarse]) continue; + DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); + for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsedata[j][0]; + } + patBuffer.data[j][iFine+1]=pat->data[j][iFine+1]; + } + for (int j=selStart.y; j<=selEnd.y; j++) { + if (iFine==0) { + pat->data[j][0]=patBuffer.data[selEnd.y-j+selStart.y][0]; + } + pat->data[j][iFine+1]=patBuffer.data[selEnd.y-j+selStart.y][iFine+1]; + } + } + iFine=0; + } + + makeUndo(GUI_UNDO_PATTERN_FLIP); +} + +void FurnaceGUI::doCollapse(int divider) { + finishSelection(); + prepareUndo(GUI_UNDO_PATTERN_COLLAPSE); + + DivPattern patBuffer; + int iCoarse=selStart.xCoarse; + int iFine=selStart.xFine; + int ord=e->getOrder(); + for (; iCoarse<=selEnd.xCoarse; iCoarse++) { + if (!e->song.chanShow[iCoarse]) continue; + DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); + for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsedata[j][0]; + } + patBuffer.data[j][iFine+1]=pat->data[j][iFine+1]; + } + for (int j=0; j<=selEnd.y-selStart.y; j++) { + if (j*divider>=selEnd.y-selStart.y) { + if (iFine==0) { + pat->data[j+selStart.y][0]=0; + pat->data[j+selStart.y][1]=0; + } else { + pat->data[j+selStart.y][iFine+1]=-1; + } + } else { + if (iFine==0) { + pat->data[j+selStart.y][0]=patBuffer.data[j*divider+selStart.y][0]; + } + pat->data[j+selStart.y][iFine+1]=patBuffer.data[j*divider+selStart.y][iFine+1]; + + if (iFine==0) { + for (int k=1; k=selEnd.y-selStart.y) break; + if (!(pat->data[j+selStart.y][0]==0 && pat->data[j+selStart.y][1]==0)) break; + pat->data[j+selStart.y][0]=patBuffer.data[j*divider+selStart.y+k][0]; + pat->data[j+selStart.y][1]=patBuffer.data[j*divider+selStart.y+k][1]; + } + } else { + for (int k=1; k=selEnd.y-selStart.y) break; + if (pat->data[j+selStart.y][iFine+1]!=-1) break; + pat->data[j+selStart.y][iFine+1]=patBuffer.data[j*divider+selStart.y+k][iFine+1]; + } + } + } + } + } + iFine=0; + } + + makeUndo(GUI_UNDO_PATTERN_COLLAPSE); +} + +void FurnaceGUI::doExpand(int multiplier) { + if (multiplier<1) return; + + finishSelection(); + prepareUndo(GUI_UNDO_PATTERN_EXPAND); + + DivPattern patBuffer; + int iCoarse=selStart.xCoarse; + int iFine=selStart.xFine; + int ord=e->getOrder(); + for (; iCoarse<=selEnd.xCoarse; iCoarse++) { + if (!e->song.chanShow[iCoarse]) continue; + DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); + for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsedata[j][0]; + } + patBuffer.data[j][iFine+1]=pat->data[j][iFine+1]; + } + for (int j=0; j<=(selEnd.y-selStart.y)*multiplier; j++) { + if ((j+selStart.y)>=e->song.patLen) break; + if ((j%multiplier)!=0) { + if (iFine==0) { + pat->data[j+selStart.y][0]=0; + pat->data[j+selStart.y][1]=0; + } else { + pat->data[j+selStart.y][iFine+1]=-1; + } + continue; + } + if (iFine==0) { + pat->data[j+selStart.y][0]=patBuffer.data[j/multiplier+selStart.y][0]; + } + pat->data[j+selStart.y][iFine+1]=patBuffer.data[j/multiplier+selStart.y][iFine+1]; + } + } + iFine=0; + } + + makeUndo(GUI_UNDO_PATTERN_EXPAND); +} + +void FurnaceGUI::doUndo() { + if (undoHist.empty()) return; + UndoStep& us=undoHist.back(); + redoHist.push_back(us); + MARK_MODIFIED; + + switch (us.type) { + case GUI_UNDO_CHANGE_ORDER: + e->song.ordersLen=us.oldOrdersLen; + for (UndoOrderData& i: us.ord) { + e->song.orders.ord[i.chan][i.ord]=i.oldVal; + } + break; + case GUI_UNDO_PATTERN_EDIT: + case GUI_UNDO_PATTERN_DELETE: + case GUI_UNDO_PATTERN_PULL: + case GUI_UNDO_PATTERN_PUSH: + case GUI_UNDO_PATTERN_CUT: + case GUI_UNDO_PATTERN_PASTE: + case GUI_UNDO_PATTERN_CHANGE_INS: + case GUI_UNDO_PATTERN_INTERPOLATE: + case GUI_UNDO_PATTERN_FADE: + case GUI_UNDO_PATTERN_SCALE: + case GUI_UNDO_PATTERN_RANDOMIZE: + case GUI_UNDO_PATTERN_INVERT_VAL: + case GUI_UNDO_PATTERN_FLIP: + case GUI_UNDO_PATTERN_COLLAPSE: + case GUI_UNDO_PATTERN_EXPAND: + for (UndoPatternData& i: us.pat) { + DivPattern* p=e->song.pat[i.chan].getPattern(i.pat,true); + p->data[i.row][i.col]=i.oldVal; + } + if (!e->isPlaying()) { + cursor=us.cursor; + selStart=us.selStart; + selEnd=us.selEnd; + curNibble=us.nibble; + updateScroll(cursor.y); + e->setOrder(us.order); + } + break; + } + + undoHist.pop_back(); +} + +void FurnaceGUI::doRedo() { + if (redoHist.empty()) return; + UndoStep& us=redoHist.back(); + undoHist.push_back(us); + MARK_MODIFIED; + + switch (us.type) { + case GUI_UNDO_CHANGE_ORDER: + e->song.ordersLen=us.newOrdersLen; + for (UndoOrderData& i: us.ord) { + e->song.orders.ord[i.chan][i.ord]=i.newVal; + } + break; + case GUI_UNDO_PATTERN_EDIT: + case GUI_UNDO_PATTERN_DELETE: + case GUI_UNDO_PATTERN_PULL: + case GUI_UNDO_PATTERN_PUSH: + case GUI_UNDO_PATTERN_CUT: + case GUI_UNDO_PATTERN_PASTE: + case GUI_UNDO_PATTERN_CHANGE_INS: + case GUI_UNDO_PATTERN_INTERPOLATE: + case GUI_UNDO_PATTERN_FADE: + case GUI_UNDO_PATTERN_SCALE: + case GUI_UNDO_PATTERN_RANDOMIZE: + case GUI_UNDO_PATTERN_INVERT_VAL: + case GUI_UNDO_PATTERN_FLIP: + case GUI_UNDO_PATTERN_COLLAPSE: + case GUI_UNDO_PATTERN_EXPAND: + for (UndoPatternData& i: us.pat) { + DivPattern* p=e->song.pat[i.chan].getPattern(i.pat,true); + p->data[i.row][i.col]=i.newVal; + } + if (!e->isPlaying()) { + cursor=us.cursor; + selStart=us.selStart; + selEnd=us.selEnd; + curNibble=us.nibble; + updateScroll(cursor.y); + e->setOrder(us.order); + } + + break; + } + + redoHist.pop_back(); +} \ No newline at end of file diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index f3ae5a7f3..2ba2131cc 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -20,15 +20,12 @@ #define _USE_MATH_DEFINES #include "gui.h" #include "util.h" -#include "debug.h" -#include "fonts.h" #include "icon.h" #include "../ta-log.h" #include "../fileutils.h" #include "imgui.h" #include "imgui_impl_sdl.h" #include "imgui_impl_sdlrenderer.h" -#include "imgui_internal.h" #include "ImGuiFileDialog.h" #include "IconsFontAwesome4.h" #include "misc/cpp/imgui_stdlib.h" @@ -61,6 +58,8 @@ extern "C" { #define BACKUP_FUR "/backup.fur" #endif +#include "actionUtil.h" + bool Particle::update(float frameTime) { pos.x+=speed.x*frameTime; pos.y+=speed.y*frameTime; @@ -75,23 +74,6 @@ void FurnaceGUI::bindEngine(DivEngine* eng) { e=eng; } -const char* noteNameNormal(short note, short octave) { - if (note==100) { // note cut - return "OFF"; - } else if (note==101) { // note off and envelope release - return "==="; - } else if (note==102) { // envelope release only - return "REL"; - } else if (octave==0 && note==0) { - return "..."; - } - int seek=(note+(signed char)octave*12)+60; - if (seek<0 || seek>=180) { - return "???"; - } - return noteNames[seek]; -} - const char* FurnaceGUI::noteName(short note, short octave) { if (note==100) { return "OFF"; @@ -678,2636 +660,6 @@ float FurnaceGUI::calcBPM(int s1, int s2, float hz) { return 120.0f*hz/(timeBase*hl*speedSum); } -void FurnaceGUI::drawEditControls() { - if (nextWindow==GUI_WINDOW_EDIT_CONTROLS) { - editControlsOpen=true; - ImGui::SetNextWindowFocus(); - nextWindow=GUI_WINDOW_NOTHING; - } - if (!editControlsOpen) return; - switch (settings.controlLayout) { - case 0: // classic - if (ImGui::Begin("Play/Edit Controls",&editControlsOpen)) { - ImGui::Text("Octave"); - ImGui::SameLine(); - if (ImGui::InputInt("##Octave",&curOctave,1,1)) { - if (curOctave>7) curOctave=7; - if (curOctave<-5) curOctave=-5; - for (size_t i=0; inoteOff(activeNotes[i].chan); - } - activeNotes.clear(); - - if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { - nextWindow=GUI_WINDOW_PATTERN; - } - } - - ImGui::Text("Edit Step"); - ImGui::SameLine(); - if (ImGui::InputInt("##EditStep",&editStep,1,1)) { - if (editStep>=e->song.patLen) editStep=e->song.patLen-1; - if (editStep<0) editStep=0; - - if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { - nextWindow=GUI_WINDOW_PATTERN; - } - } - - if (ImGui::Button(ICON_FA_PLAY "##Play")) { - play(); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_STOP "##Stop")) { - stop(); - } - ImGui::SameLine(); - ImGui::Checkbox("Edit",&edit); - ImGui::SameLine(); - bool metro=e->getMetronome(); - if (ImGui::Checkbox("Metronome",&metro)) { - e->setMetronome(metro); - } - - ImGui::Text("Follow"); - ImGui::SameLine(); - unimportant(ImGui::Checkbox("Orders",&followOrders)); - ImGui::SameLine(); - unimportant(ImGui::Checkbox("Pattern",&followPattern)); - - bool repeatPattern=e->getRepeatPattern(); - if (ImGui::Checkbox("Repeat pattern",&repeatPattern)) { - e->setRepeatPattern(repeatPattern); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_ARROW_DOWN "##StepOne")) { - e->stepOne(cursor.y); - } - } - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS; - ImGui::End(); - break; - case 1: // compact - if (ImGui::Begin("Play/Edit Controls",&editControlsOpen,ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoScrollWithMouse)) { - if (ImGui::Button(ICON_FA_STOP "##Stop")) { - stop(); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_PLAY "##Play")) { - play(); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_ARROW_DOWN "##StepOne")) { - e->stepOne(cursor.y); - } - - ImGui::SameLine(); - bool repeatPattern=e->getRepeatPattern(); - ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(repeatPattern)?0.6f:0.2f,0.2f,1.0f)); - if (ImGui::Button(ICON_FA_REPEAT "##RepeatPattern")) { - e->setRepeatPattern(!repeatPattern); - } - ImGui::PopStyleColor(); - - ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(edit)?0.6f:0.2f,0.2f,1.0f)); - if (ImGui::Button(ICON_FA_CIRCLE "##Edit")) { - edit=!edit; - } - ImGui::PopStyleColor(); - - ImGui::SameLine(); - bool metro=e->getMetronome(); - ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(metro)?0.6f:0.2f,0.2f,1.0f)); - if (ImGui::Button(ICON_FA_BELL_O "##Metronome")) { - e->setMetronome(!metro); - } - ImGui::PopStyleColor(); - - ImGui::SameLine(); - ImGui::Text("Octave"); - ImGui::SameLine(); - ImGui::SetNextItemWidth(96.0f*dpiScale); - if (ImGui::InputInt("##Octave",&curOctave,1,1)) { - if (curOctave>7) curOctave=7; - if (curOctave<-5) curOctave=-5; - for (size_t i=0; inoteOff(activeNotes[i].chan); - } - activeNotes.clear(); - - if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { - nextWindow=GUI_WINDOW_PATTERN; - } - } - - ImGui::SameLine(); - ImGui::Text("Edit Step"); - ImGui::SameLine(); - ImGui::SetNextItemWidth(96.0f*dpiScale); - if (ImGui::InputInt("##EditStep",&editStep,1,1)) { - if (editStep>=e->song.patLen) editStep=e->song.patLen-1; - if (editStep<0) editStep=0; - - if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { - nextWindow=GUI_WINDOW_PATTERN; - } - } - - ImGui::SameLine(); - ImGui::Text("Follow"); - ImGui::SameLine(); - unimportant(ImGui::Checkbox("Orders",&followOrders)); - ImGui::SameLine(); - unimportant(ImGui::Checkbox("Pattern",&followPattern)); - } - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS; - ImGui::End(); - break; - case 2: // compact vertical - if (ImGui::Begin("Play/Edit Controls",&editControlsOpen,ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoScrollWithMouse)) { - if (ImGui::Button(ICON_FA_PLAY "##Play")) { - play(); - } - if (ImGui::Button(ICON_FA_STOP "##Stop")) { - stop(); - } - if (ImGui::Button(ICON_FA_ARROW_DOWN "##StepOne")) { - e->stepOne(cursor.y); - } - - bool repeatPattern=e->getRepeatPattern(); - ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(repeatPattern)?0.6f:0.2f,0.2f,1.0f)); - if (ImGui::Button(ICON_FA_REPEAT "##RepeatPattern")) { - e->setRepeatPattern(!repeatPattern); - } - ImGui::PopStyleColor(); - - ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(edit)?0.6f:0.2f,0.2f,1.0f)); - if (ImGui::Button(ICON_FA_CIRCLE "##Edit")) { - edit=!edit; - } - ImGui::PopStyleColor(); - - bool metro=e->getMetronome(); - ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(metro)?0.6f:0.2f,0.2f,1.0f)); - if (ImGui::Button(ICON_FA_BELL_O "##Metronome")) { - e->setMetronome(!metro); - } - ImGui::PopStyleColor(); - - ImGui::Text("Oct."); - float avail=ImGui::GetContentRegionAvail().x; - ImGui::SetNextItemWidth(avail); - if (ImGui::InputInt("##Octave",&curOctave,0,0)) { - if (curOctave>7) curOctave=7; - if (curOctave<-5) curOctave=-5; - for (size_t i=0; inoteOff(activeNotes[i].chan); - } - activeNotes.clear(); - - if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { - nextWindow=GUI_WINDOW_PATTERN; - } - } - - ImGui::Text("Step"); - ImGui::SetNextItemWidth(avail); - if (ImGui::InputInt("##EditStep",&editStep,0,0)) { - if (editStep>=e->song.patLen) editStep=e->song.patLen-1; - if (editStep<0) editStep=0; - - if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { - nextWindow=GUI_WINDOW_PATTERN; - } - } - - ImGui::Text("Foll."); - ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(followOrders)?0.6f:0.2f,0.2f,1.0f)); - if (ImGui::SmallButton("Ord##FollowOrders")) { handleUnimportant - followOrders=!followOrders; - } - ImGui::PopStyleColor(); - ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(followPattern)?0.6f:0.2f,0.2f,1.0f)); - if (ImGui::SmallButton("Pat##FollowPattern")) { handleUnimportant - followPattern=!followPattern; - } - ImGui::PopStyleColor(); - } - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS; - ImGui::End(); - break; - case 3: // split - if (ImGui::Begin("Play Controls",&editControlsOpen,ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoScrollWithMouse)) { - if (e->isPlaying()) { - if (ImGui::Button(ICON_FA_STOP "##Stop")) { - stop(); - } - } else { - if (ImGui::Button(ICON_FA_PLAY "##Play")) { - play(); - } - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_PLAY_CIRCLE "##PlayAgain")) { - play(); - } - ImGui::SameLine(); - if (ImGui::Button(ICON_FA_ARROW_DOWN "##StepOne")) { - e->stepOne(cursor.y); - } - - ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(edit)?0.6f:0.2f,0.2f,1.0f)); - if (ImGui::Button(ICON_FA_CIRCLE "##Edit")) { - edit=!edit; - } - ImGui::PopStyleColor(); - - bool metro=e->getMetronome(); - ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(metro)?0.6f:0.2f,0.2f,1.0f)); - if (ImGui::Button(ICON_FA_BELL_O "##Metronome")) { - e->setMetronome(!metro); - } - ImGui::PopStyleColor(); - - ImGui::SameLine(); - bool repeatPattern=e->getRepeatPattern(); - ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(repeatPattern)?0.6f:0.2f,0.2f,1.0f)); - if (ImGui::Button(ICON_FA_REPEAT "##RepeatPattern")) { - e->setRepeatPattern(!repeatPattern); - } - ImGui::PopStyleColor(); - } - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS; - ImGui::End(); - - if (ImGui::Begin("Edit Controls",&editControlsOpen)) { - ImGui::Columns(2); - ImGui::Text("Octave"); - ImGui::SameLine(); - float cursor=ImGui::GetCursorPosX(); - float avail=ImGui::GetContentRegionAvail().x; - ImGui::SetNextItemWidth(avail); - if (ImGui::InputInt("##Octave",&curOctave,1,1)) { - if (curOctave>7) curOctave=7; - if (curOctave<-5) curOctave=-5; - for (size_t i=0; inoteOff(activeNotes[i].chan); - } - activeNotes.clear(); - - if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { - nextWindow=GUI_WINDOW_PATTERN; - } - } - - ImGui::Text("Step"); - ImGui::SameLine(); - ImGui::SetCursorPosX(cursor); - ImGui::SetNextItemWidth(avail); - if (ImGui::InputInt("##EditStep",&editStep,1,1)) { - if (editStep>=e->song.patLen) editStep=e->song.patLen-1; - if (editStep<0) editStep=0; - - if (settings.insFocusesPattern && !ImGui::IsItemActive() && patternOpen) { - nextWindow=GUI_WINDOW_PATTERN; - } - } - ImGui::NextColumn(); - - unimportant(ImGui::Checkbox("Follow orders",&followOrders)); - unimportant(ImGui::Checkbox("Follow pattern",&followPattern)); - } - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS; - ImGui::End(); - break; - } -} - -void FurnaceGUI::drawSongInfo() { - if (nextWindow==GUI_WINDOW_SONG_INFO) { - songInfoOpen=true; - ImGui::SetNextWindowFocus(); - nextWindow=GUI_WINDOW_NOTHING; - } - if (!songInfoOpen) return; - if (ImGui::Begin("Song Information",&songInfoOpen)) { - if (ImGui::BeginTable("NameAuthor",2,ImGuiTableFlags_SizingStretchProp)) { - ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,0.0); - ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.0); - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("Name"); - ImGui::TableNextColumn(); - float avail=ImGui::GetContentRegionAvail().x; - ImGui::SetNextItemWidth(avail); - if (ImGui::InputText("##Name",&e->song.name)) { MARK_MODIFIED - updateWindowTitle(); - } - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("Author"); - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(avail); - if (ImGui::InputText("##Author",&e->song.author)) { - MARK_MODIFIED; - } - ImGui::EndTable(); - } - - if (ImGui::BeginTable("OtherProps",3,ImGuiTableFlags_SizingStretchProp)) { - ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,0.0); - ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.0); - ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch,0.0); - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("TimeBase"); - ImGui::TableNextColumn(); - float avail=ImGui::GetContentRegionAvail().x; - ImGui::SetNextItemWidth(avail); - unsigned char realTB=e->song.timeBase+1; - if (ImGui::InputScalar("##TimeBase",ImGuiDataType_U8,&realTB,&_ONE,&_THREE)) { MARK_MODIFIED - if (realTB<1) realTB=1; - if (realTB>16) realTB=16; - e->song.timeBase=realTB-1; - } - ImGui::TableNextColumn(); - ImGui::Text("%.2f BPM",calcBPM(e->song.speed1,e->song.speed2,e->song.hz)); - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("Speed"); - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(avail); - if (ImGui::InputScalar("##Speed1",ImGuiDataType_U8,&e->song.speed1,&_ONE,&_THREE)) { MARK_MODIFIED - if (e->song.speed1<1) e->song.speed1=1; - if (e->isPlaying()) play(); - } - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(avail); - if (ImGui::InputScalar("##Speed2",ImGuiDataType_U8,&e->song.speed2,&_ONE,&_THREE)) { MARK_MODIFIED - if (e->song.speed2<1) e->song.speed2=1; - if (e->isPlaying()) play(); - } - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("Highlight"); - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(avail); - if (ImGui::InputScalar("##Highlight1",ImGuiDataType_U8,&e->song.hilightA,&_ONE,&_THREE)) { - MARK_MODIFIED; - } - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(avail); - if (ImGui::InputScalar("##Highlight2",ImGuiDataType_U8,&e->song.hilightB,&_ONE,&_THREE)) { - MARK_MODIFIED; - } - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("Pattern Length"); - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(avail); - int patLen=e->song.patLen; - if (ImGui::InputInt("##PatLength",&patLen,1,3)) { MARK_MODIFIED - if (patLen<1) patLen=1; - if (patLen>256) patLen=256; - e->song.patLen=patLen; - } - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("Song Length"); - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(avail); - int ordLen=e->song.ordersLen; - if (ImGui::InputInt("##OrdLength",&ordLen,1,3)) { MARK_MODIFIED - if (ordLen<1) ordLen=1; - if (ordLen>127) ordLen=127; - e->song.ordersLen=ordLen; - } - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - if (ImGui::Selectable(tempoView?"Base Tempo##TempoOrHz":"Tick Rate##TempoOrHz")) { - tempoView=!tempoView; - } - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(avail); - float setHz=tempoView?e->song.hz*2.5:e->song.hz; - if (ImGui::InputFloat("##Rate",&setHz,1.0f,1.0f,"%g")) { MARK_MODIFIED - if (tempoView) setHz/=2.5; - if (setHz<10) setHz=10; - if (setHz>999) setHz=999; - e->setSongRate(setHz,setHz<52); - } - if (tempoView) { - ImGui::TableNextColumn(); - ImGui::Text("= %gHz",e->song.hz); - } else { - if (e->song.hz>=49.98 && e->song.hz<=50.02) { - ImGui::TableNextColumn(); - ImGui::Text("PAL"); - } - if (e->song.hz>=59.9 && e->song.hz<=60.11) { - ImGui::TableNextColumn(); - ImGui::Text("NTSC"); - } - } - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("Tuning (A-4)"); - ImGui::TableNextColumn(); - float tune=e->song.tuning; - ImGui::SetNextItemWidth(avail); - if (ImGui::InputFloat("##Tuning",&tune,1.0f,3.0f,"%g")) { MARK_MODIFIED - if (tune<220.0f) tune=220.0f; - if (tune>880.0f) tune=880.0f; - e->song.tuning=tune; - } - ImGui::EndTable(); - } - } - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_SONG_INFO; - ImGui::End(); -} - -void FurnaceGUI::drawMixer() { - if (nextWindow==GUI_WINDOW_MIXER) { - mixerOpen=true; - ImGui::SetNextWindowFocus(); - nextWindow=GUI_WINDOW_NOTHING; - } - if (!mixerOpen) return; - ImGui::SetNextWindowSizeConstraints(ImVec2(400.0f*dpiScale,200.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); - if (ImGui::Begin("Mixer",&mixerOpen,settings.allowEditDocking?0:ImGuiWindowFlags_NoDocking)) { - char id[32]; - if (ImGui::SliderFloat("Master Volume",&e->song.masterVol,0,3,"%.2fx")) { - if (e->song.masterVol<0) e->song.masterVol=0; - if (e->song.masterVol>3) e->song.masterVol=3; - } rightClickable - for (int i=0; isong.systemLen; i++) { - snprintf(id,31,"MixS%d",i); - bool doInvert=e->song.systemVol[i]&128; - signed char vol=e->song.systemVol[i]&127; - ImGui::PushID(id); - ImGui::Text("%d. %s",i+1,getSystemName(e->song.system[i])); - ImGui::SameLine(ImGui::GetWindowWidth()-(82.0f*dpiScale)); - if (ImGui::Checkbox("Invert",&doInvert)) { - e->song.systemVol[i]^=128; - } - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x-(50.0f*dpiScale)); - if (ImGui::SliderScalar("Volume",ImGuiDataType_S8,&vol,&_ZERO,&_ONE_HUNDRED_TWENTY_SEVEN)) { - e->song.systemVol[i]=(e->song.systemVol[i]&128)|vol; - } rightClickable - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x-(50.0f*dpiScale)); - ImGui::SliderScalar("Panning",ImGuiDataType_S8,&e->song.systemPan[i],&_MINUS_ONE_HUNDRED_TWENTY_SEVEN,&_ONE_HUNDRED_TWENTY_SEVEN); rightClickable - - ImGui::PopID(); - } - } - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_MIXER; - ImGui::End(); -} - -void FurnaceGUI::drawOsc() { - if (nextWindow==GUI_WINDOW_OSCILLOSCOPE) { - oscOpen=true; - ImGui::SetNextWindowFocus(); - nextWindow=GUI_WINDOW_NOTHING; - } - if (!oscOpen) return; - ImGui::SetNextWindowSizeConstraints(ImVec2(64.0f*dpiScale,32.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding,ImVec2(0,0)); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,ImVec2(0,0)); - ImGui::PushStyleVar(ImGuiStyleVar_ItemInnerSpacing,ImVec2(0,0)); - if (ImGui::Begin("Oscilloscope",&oscOpen)) { - float values[512]; - for (int i=0; i<512; i++) { - int pos=i*e->oscSize/512; - values[i]=(e->oscBuf[0][pos]+e->oscBuf[1][pos])*0.5f; - } - //ImGui::SetCursorPos(ImVec2(0,0)); - ImGui::BeginDisabled(); - ImGui::PlotLines("##SingleOsc",values,512,0,NULL,-1.0f,1.0f,ImGui::GetContentRegionAvail()); - ImGui::EndDisabled(); - } - ImGui::PopStyleVar(3); - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_OSCILLOSCOPE; - ImGui::End(); -} - -void FurnaceGUI::drawVolMeter() { - if (nextWindow==GUI_WINDOW_VOL_METER) { - volMeterOpen=true; - ImGui::SetNextWindowFocus(); - nextWindow=GUI_WINDOW_NOTHING; - } - if (!volMeterOpen) return; - if (--isClipping<0) isClipping=0; - ImGui::SetNextWindowSizeConstraints(ImVec2(6.0f*dpiScale,6.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding,ImVec2(0,0)); - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,ImVec2(0,0)); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,ImVec2(0,0)); - ImGui::PushStyleVar(ImGuiStyleVar_ItemInnerSpacing,ImVec2(0,0)); - if (ImGui::Begin("Volume Meter",&volMeterOpen)) { - ImDrawList* dl=ImGui::GetWindowDrawList(); - bool aspectRatio=(ImGui::GetWindowSize().x/ImGui::GetWindowSize().y)>1.0; - - ImVec2 minArea=ImVec2( - ImGui::GetWindowPos().x+ImGui::GetCursorPos().x, - ImGui::GetWindowPos().y+ImGui::GetCursorPos().y - ); - ImVec2 maxArea=ImVec2( - ImGui::GetWindowPos().x+ImGui::GetCursorPos().x+ImGui::GetContentRegionAvail().x, - ImGui::GetWindowPos().y+ImGui::GetCursorPos().y+ImGui::GetContentRegionAvail().y - ); - ImRect rect=ImRect(minArea,maxArea); - ImGuiStyle& style=ImGui::GetStyle(); - ImGui::ItemSize(ImVec2(4.0f,4.0f),style.FramePadding.y); - ImU32 lowColor=ImGui::GetColorU32(uiColors[GUI_COLOR_VOLMETER_LOW]); - float peakDecay=0.05f*60.0f*ImGui::GetIO().DeltaTime; - if (ImGui::ItemAdd(rect,ImGui::GetID("volMeter"))) { - ImGui::RenderFrame(rect.Min,rect.Max,ImGui::GetColorU32(ImGuiCol_FrameBg),true,style.FrameRounding); - for (int i=0; i<2; i++) { - peak[i]*=1.0-peakDecay; - if (peak[i]<0.0001) peak[i]=0.0; - for (int j=0; joscSize; j++) { - if (fabs(e->oscBuf[i][j])>peak[i]) { - peak[i]=fabs(e->oscBuf[i][j]); - } - } - float logPeak=(20*log10(peak[i])/36.0); - if (logPeak==NAN) logPeak=0.0; - if (logPeak<-1.0) logPeak=-1.0; - if (logPeak>0.0) { - isClipping=8; - logPeak=0.0; - } - logPeak+=1.0; - ImU32 highColor=ImGui::GetColorU32( - ImLerp(uiColors[GUI_COLOR_VOLMETER_LOW],uiColors[GUI_COLOR_VOLMETER_HIGH],logPeak) - ); - ImRect s; - if (aspectRatio) { - s=ImRect( - ImLerp(rect.Min,rect.Max,ImVec2(0,float(i)*0.5)), - ImLerp(rect.Min,rect.Max,ImVec2(logPeak,float(i+1)*0.5)) - ); - if (i==0) s.Max.y-=dpiScale; - if (isClipping) { - dl->AddRectFilled(s.Min,s.Max,ImGui::GetColorU32(uiColors[GUI_COLOR_VOLMETER_PEAK])); - } else { - dl->AddRectFilledMultiColor(s.Min,s.Max,lowColor,highColor,highColor,lowColor); - } - } else { - s=ImRect( - ImLerp(rect.Min,rect.Max,ImVec2(float(i)*0.5,1.0-logPeak)), - ImLerp(rect.Min,rect.Max,ImVec2(float(i+1)*0.5,1.0)) - ); - if (i==0) s.Max.x-=dpiScale; - if (isClipping) { - dl->AddRectFilled(s.Min,s.Max,ImGui::GetColorU32(uiColors[GUI_COLOR_VOLMETER_PEAK])); - } else { - dl->AddRectFilledMultiColor(s.Min,s.Max,highColor,highColor,lowColor,lowColor); - } - } - } - if (ImGui::IsItemHovered()) { - if (aspectRatio) { - ImGui::SetTooltip("%.1fdB",36*((ImGui::GetMousePos().x-ImGui::GetItemRectMin().x)/(rect.Max.x-rect.Min.x)-1.0)); - } else { - ImGui::SetTooltip("%.1fdB",-(36+36*((ImGui::GetMousePos().y-ImGui::GetItemRectMin().y)/(rect.Max.y-rect.Min.y)-1.0))); - } - } - } - } - ImGui::PopStyleVar(4); - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_VOL_METER; - ImGui::End(); -} - -const char* aboutLine[]={ - "tildearrow", - "is proud to present", - "", - ("Furnace " DIV_VERSION), - "", - "the free software chiptune tracker,", - "compatible with DefleMask modules.", - "", - "zero disassembly.", - "just clean-room design,", - "time and dedication.", - "", - "> CREDITS <", - "", - "-- program --", - "tildearrow", - "akumanatt", - "cam900", - "djtuBIG-MaliceX", - "laoo", - "superctr", - "", - "-- graphics/UI design --", - "tildearrow", - "BlastBrothers", - "", - "-- documentation --", - "tildearrow", - "freq-mod", - "nicco1690", - "DeMOSic", - "cam900", - "", - "-- demo songs --", - "0x5066", - "ActualNK358", - "breakthetargets", - "CaptainMalware", - "kleeder", - "Mahbod Karamoozian", - "nicco1690", - "NikonTeen", - "SuperJet Spade", - "TheDuccinator", - "TheRealHedgehogSonic", - "tildearrow", - "Ultraprogramer", - "", - "-- additional feedback/fixes --", - "fd", - "OPNA2608", - "plane", - "TheEssem", - "", - "powered by:", - "Dear ImGui by Omar Cornut", - "SDL2 by Sam Lantinga", - "zlib by Jean-loup Gailly", - "and Mark Adler", - "libsndfile by Erik de Castro Lopo", - "Nuked-OPM & Nuked-OPN2 by Nuke.YKT", - "ymfm by Aaron Giles", - "MAME SN76496 by Nicola Salmoria", - "MAME AY-3-8910 by Couriersud", - "with AY8930 fixes by Eulous", - "MAME SAA1099 by Juergen Buchmueller and Manuel Abadia", - "SAASound", - "SameBoy by Lior Halphon", - "Mednafen PCE", - "puNES by FHorse", - "reSID by Dag Lem", - "Stella by Stella Team", - "QSound emulator by Ian Karlsson and Valley Bell", - "", - "greetings to:", - "Delek", - "fd", - "ILLUMIDARO", - "all members of Deflers of Noice!", - "", - "copyright © 2021-2022 tildearrow", - "(and contributors).", - "licensed under GPLv2+! see", - "LICENSE for more information.", - "", - "help Furnace grow:", - "https://github.com/tildearrow/furnace", - "", - "contact tildearrow at:", - "https://tildearrow.org/?p=contact", - "", - "disclaimer:", - "despite the fact this program works", - "with the .dmf file format, it is NOT", - "affiliated with Delek or DefleMask in", - "any way, nor it is a replacement for", - "the original program.", - "", - "it also comes with ABSOLUTELY NO WARRANTY.", - "", - "thanks to all contributors/bug reporters!" -}; - -const size_t aboutCount = sizeof(aboutLine)/sizeof(aboutLine[0]); - -void FurnaceGUI::drawAbout() { - // do stuff - if (ImGui::Begin("About Furnace",NULL,ImGuiWindowFlags_Modal|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoDocking|ImGuiWindowFlags_NoTitleBar)) { - ImGui::SetWindowPos(ImVec2(0,0)); - ImGui::SetWindowSize(ImVec2(scrW*dpiScale,scrH*dpiScale)); - ImGui::PushFont(bigFont); - ImDrawList* dl=ImGui::GetWindowDrawList(); - float r=0; - float g=0; - float b=0; - float peakMix=settings.partyTime?((peak[0]+peak[1])*0.5):0.3; - ImGui::ColorConvertHSVtoRGB(aboutHue,1.0,0.25+MIN(0.75f,peakMix*0.75f),r,g,b); - dl->AddRectFilled(ImVec2(0,0),ImVec2(scrW*dpiScale,scrH*dpiScale),0xff000000); - bool skip=false; - bool skip2=false; - for (int i=(-80-sin(double(aboutSin)*2*M_PI/120.0)*80.0)*2; iAddRectFilled(ImVec2(i*dpiScale,j*dpiScale),ImVec2((i+160)*dpiScale,(j+160)*dpiScale),ImGui::GetColorU32(ImVec4(r*0.25,g*0.25,b*0.25,1.0))); - } - } - - skip=false; - skip2=false; - for (int i=(-80-cos(double(aboutSin)*2*M_PI/120.0)*80.0)*2; iAddRectFilled(ImVec2(i*dpiScale,j*dpiScale),ImVec2((i+160)*dpiScale,(j+160)*dpiScale),ImGui::GetColorU32(ImVec4(r*0.5,g*0.5,b*0.5,1.0))); - } - } - - skip=false; - skip2=false; - for (int i=(-160+fmod(aboutSin*2,160))*2; iAddRectFilled(ImVec2(i*dpiScale,j*dpiScale),ImVec2((i+160)*dpiScale,(j+160)*dpiScale),ImGui::GetColorU32(ImVec4(r*0.75,g*0.75,b*0.75,1.0))); - } - } - - for (size_t i=0; iscrH*dpiScale) continue; - dl->AddText(bigFont,bigFont->FontSize, - ImVec2(posX+dpiScale,posY+dpiScale), - 0xff000000,aboutLine[i]); - dl->AddText(bigFont,bigFont->FontSize, - ImVec2(posX+dpiScale,posY-dpiScale), - 0xff000000,aboutLine[i]); - dl->AddText(bigFont,bigFont->FontSize, - ImVec2(posX-dpiScale,posY+dpiScale), - 0xff000000,aboutLine[i]); - dl->AddText(bigFont,bigFont->FontSize, - ImVec2(posX-dpiScale,posY-dpiScale), - 0xff000000,aboutLine[i]); - dl->AddText(bigFont,bigFont->FontSize, - ImVec2(posX,posY), - 0xffffffff,aboutLine[i]); - } - ImGui::PopFont(); - - float timeScale=60.0f*ImGui::GetIO().DeltaTime; - - aboutHue+=(0.001+peakMix*0.004)*timeScale; - aboutScroll+=(2+(peakMix>0.78)*3)*timeScale; - aboutSin+=(1+(peakMix>0.75)*2)*timeScale; - - while (aboutHue>1) aboutHue--; - while (aboutSin>=2400) aboutSin-=2400; - if (aboutScroll>(42*aboutCount+scrH)) aboutScroll=-20; - } - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_ABOUT; - ImGui::End(); -} - -void FurnaceGUI::drawDebug() { - static int bpOrder; - static int bpRow; - static int bpTick; - static bool bpOn; - if (nextWindow==GUI_WINDOW_DEBUG) { - debugOpen=true; - ImGui::SetNextWindowFocus(); - nextWindow=GUI_WINDOW_NOTHING; - } - if (!debugOpen) return; - ImGui::SetNextWindowSizeConstraints(ImVec2(400.0f*dpiScale,200.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); - if (ImGui::Begin("Debug",&debugOpen,ImGuiWindowFlags_NoDocking)) { - ImGui::Text("NOTE: use with caution."); - if (ImGui::TreeNode("Debug Controls")) { - if (e->isHalted()) { - if (ImGui::Button("Resume")) e->resume(); - } else { - if (ImGui::Button("Pause")) e->halt(); - } - ImGui::SameLine(); - if (ImGui::Button("Frame Advance")) e->haltWhen(DIV_HALT_TICK); - ImGui::SameLine(); - if (ImGui::Button("Row Advance")) e->haltWhen(DIV_HALT_ROW); - ImGui::SameLine(); - if (ImGui::Button("Pattern Advance")) e->haltWhen(DIV_HALT_PATTERN); - - if (ImGui::Button("Panic")) e->syncReset(); - ImGui::SameLine(); - if (ImGui::Button("Abort")) { - abort(); - } - ImGui::TreePop(); - } - if (ImGui::TreeNode("Breakpoint")) { - ImGui::InputInt("Order",&bpOrder); - ImGui::InputInt("Row",&bpRow); - ImGui::InputInt("Tick",&bpTick); - ImGui::Checkbox("Enable",&bpOn); - ImGui::TreePop(); - } - if (ImGui::TreeNode("Dispatch Status")) { - ImGui::Text("for best results set latency to minimum or use the Frame Advance button."); - ImGui::Columns(e->getTotalChannelCount()); - for (int i=0; igetTotalChannelCount(); i++) { - void* ch=e->getDispatchChanState(i); - ImGui::TextColored(uiColors[GUI_COLOR_ACCENT_PRIMARY],"Ch. %d: %d, %d",i,e->dispatchOfChan[i],e->dispatchChanOfChan[i]); - if (ch==NULL) { - ImGui::Text("NULL"); - } else { - putDispatchChan(ch,e->dispatchChanOfChan[i],e->sysOfChan[i]); - } - ImGui::NextColumn(); - } - ImGui::Columns(); - ImGui::TreePop(); - } - if (ImGui::TreeNode("Playback Status")) { - ImGui::Text("for best results set latency to minimum or use the Frame Advance button."); - ImGui::Columns(e->getTotalChannelCount()); - for (int i=0; igetTotalChannelCount(); i++) { - DivChannelState* ch=e->getChanState(i); - ImGui::TextColored(uiColors[GUI_COLOR_ACCENT_PRIMARY],"Channel %d:",i); - if (ch==NULL) { - ImGui::Text("NULL"); - } else { - ImGui::Text("* General:"); - ImGui::Text("- note = %d",ch->note); - ImGui::Text("- oldNote = %d",ch->oldNote); - ImGui::Text("- pitch = %d",ch->pitch); - ImGui::Text("- portaSpeed = %d",ch->portaSpeed); - ImGui::Text("- portaNote = %d",ch->portaNote); - ImGui::Text("- volume = %.4x",ch->volume); - ImGui::Text("- volSpeed = %d",ch->volSpeed); - ImGui::Text("- cut = %d",ch->cut); - ImGui::Text("- rowDelay = %d",ch->rowDelay); - ImGui::Text("- volMax = %.4x",ch->volMax); - ImGui::Text("- delayOrder = %d",ch->delayOrder); - ImGui::Text("- delayRow = %d",ch->delayRow); - ImGui::Text("- retrigSpeed = %d",ch->retrigSpeed); - ImGui::Text("- retrigTick = %d",ch->retrigTick); - ImGui::PushStyleColor(ImGuiCol_Text,(ch->vibratoDepth>0)?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_TEXT]); - ImGui::Text("* Vibrato:"); - ImGui::Text("- depth = %d",ch->vibratoDepth); - ImGui::Text("- rate = %d",ch->vibratoRate); - ImGui::Text("- pos = %d",ch->vibratoPos); - ImGui::Text("- dir = %d",ch->vibratoDir); - ImGui::Text("- fine = %d",ch->vibratoFine); - ImGui::PopStyleColor(); - ImGui::PushStyleColor(ImGuiCol_Text,(ch->tremoloDepth>0)?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_TEXT]); - ImGui::Text("* Tremolo:"); - ImGui::Text("- depth = %d",ch->tremoloDepth); - ImGui::Text("- rate = %d",ch->tremoloRate); - ImGui::Text("- pos = %d",ch->tremoloPos); - ImGui::PopStyleColor(); - ImGui::PushStyleColor(ImGuiCol_Text,(ch->arp>0)?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_TEXT]); - ImGui::Text("* Arpeggio:"); - ImGui::Text("- arp = %.2X",ch->arp); - ImGui::Text("- stage = %d",ch->arpStage); - ImGui::Text("- ticks = %d",ch->arpTicks); - ImGui::PopStyleColor(); - ImGui::Text("* Miscellaneous:"); - ImGui::TextColored(ch->doNote?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_HEADER],">> Do Note"); - ImGui::TextColored(ch->legato?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_HEADER],">> Legato"); - ImGui::TextColored(ch->portaStop?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_HEADER],">> PortaStop"); - ImGui::TextColored(ch->keyOn?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_HEADER],">> Key On"); - ImGui::TextColored(ch->keyOff?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_HEADER],">> Key Off"); - ImGui::TextColored(ch->nowYouCanStop?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_HEADER],">> NowYouCanStop"); - ImGui::TextColored(ch->stopOnOff?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_HEADER],">> Stop on Off"); - ImGui::TextColored(ch->arpYield?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_HEADER],">> Arp Yield"); - ImGui::TextColored(ch->delayLocked?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_HEADER],">> DelayLocked"); - ImGui::TextColored(ch->inPorta?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_HEADER],">> InPorta"); - ImGui::TextColored(ch->scheduledSlideReset?uiColors[GUI_COLOR_MACRO_VOLUME]:uiColors[GUI_COLOR_HEADER],">> SchedSlide"); - } - ImGui::NextColumn(); - } - ImGui::Columns(); - ImGui::TreePop(); - } - if (ImGui::TreeNode("Playground")) { - if (pgSys<0 || pgSys>=e->song.systemLen) pgSys=0; - if (ImGui::BeginCombo("System",fmt::sprintf("%d. %s",pgSys+1,e->getSystemName(e->song.system[pgSys])).c_str())) { - for (int i=0; isong.systemLen; i++) { - if (ImGui::Selectable(fmt::sprintf("%d. %s",i+1,e->getSystemName(e->song.system[i])).c_str())) { - pgSys=i; - break; - } - } - ImGui::EndCombo(); - } - ImGui::Text("Program"); - if (pgProgram.empty()) { - ImGui::Text("-nothing here-"); - } else { - char id[32]; - for (size_t index=0; indexpoke(pgSys,pgProgram); - } - ImGui::SameLine(); - if (ImGui::Button("Clear")) { - pgProgram.clear(); - } - - ImGui::Text("Address"); - ImGui::SameLine(); - ImGui::SetNextItemWidth(100.0f*dpiScale); - ImGui::InputInt("##PAddress",&pgAddr,0,0,ImGuiInputTextFlags_CharsHexadecimal); - ImGui::SameLine(); - ImGui::Text("Value"); - ImGui::SameLine(); - ImGui::SetNextItemWidth(100.0f*dpiScale); - ImGui::InputInt("##PValue",&pgVal,0,0,ImGuiInputTextFlags_CharsHexadecimal); - ImGui::SameLine(); - if (ImGui::Button("Write")) { - e->poke(pgSys,pgAddr,pgVal); - } - ImGui::SameLine(); - if (ImGui::Button("Add")) { - pgProgram.push_back(DivRegWrite(pgAddr,pgVal)); - } - if (ImGui::TreeNode("Register Cheatsheet")) { - const char** sheet=e->getRegisterSheet(pgSys); - if (sheet==NULL) { - ImGui::Text("no cheatsheet available for this system."); - } else { - if (ImGui::BeginTable("RegisterSheet",2,ImGuiTableFlags_SizingFixedSame)) { - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("Name"); - ImGui::TableNextColumn(); - ImGui::Text("Address"); - for (int i=0; sheet[i]!=NULL; i+=2) { - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Text("%s",sheet[i]); - ImGui::TableNextColumn(); - ImGui::Text("$%s",sheet[i+1]); - } - ImGui::EndTable(); - } - } - ImGui::TreePop(); - } - ImGui::TreePop(); - } - if (ImGui::TreeNode("User Interface")) { - if (ImGui::Button("Inspect")) { - inspectorOpen=!inspectorOpen; - } - ImGui::TreePop(); - } - if (ImGui::TreeNode("Settings")) { - if (ImGui::Button("Sync")) syncSettings(); - ImGui::SameLine(); - if (ImGui::Button("Commit")) commitSettings(); - ImGui::SameLine(); - if (ImGui::Button("Force Load")) e->loadConf(); - ImGui::SameLine(); - if (ImGui::Button("Force Save")) e->saveConf(); - ImGui::TreePop(); - } - ImGui::Text("Song format version %d",e->song.version); - ImGui::Text("Furnace version " DIV_VERSION " (%d)",DIV_ENGINE_VERSION); - } - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_DEBUG; - ImGui::End(); -} - -void FurnaceGUI::drawNewSong() { - bool accepted=false; - - ImGui::PushFont(bigFont); - ImGui::SetCursorPosX((ImGui::GetContentRegionAvail().x-ImGui::CalcTextSize("Choose a System!").x)*0.5); - ImGui::Text("Choose a System!"); - ImGui::PopFont(); - - if (ImGui::BeginTable("sysPicker",2)) { - ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,0.0f); - ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.0f); - - ImGui::TableNextRow(ImGuiTableRowFlags_Headers); - ImGui::TableNextColumn(); - ImGui::Text("Categories"); - ImGui::TableNextColumn(); - ImGui::Text("Systems"); - - ImGui::TableNextRow(); - - // CATEGORIES - ImGui::TableNextColumn(); - int index=0; - for (FurnaceGUISysCategory& i: sysCategories) { - if (ImGui::Selectable(i.name,newSongCategory==index,ImGuiSelectableFlags_DontClosePopups)) { \ - newSongCategory=index; - } - index++; - } - - // SYSTEMS - ImGui::TableNextColumn(); - if (ImGui::BeginTable("Systems",1,ImGuiTableFlags_BordersInnerV|ImGuiTableFlags_ScrollY)) { - for (FurnaceGUISysDef& i: sysCategories[newSongCategory].systems) { - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - if (ImGui::Selectable(i.name,false,ImGuiSelectableFlags_DontClosePopups)) { - nextDesc=i.definition.data(); - accepted=true; - } - } - ImGui::EndTable(); - } - - ImGui::EndTable(); - } - - if (ImGui::Button("Cancel")) { - ImGui::CloseCurrentPopup(); - } - - if (accepted) { - e->createNew(nextDesc); - undoHist.clear(); - redoHist.clear(); - curFileName=""; - modified=false; - curNibble=false; - orderNibble=false; - orderCursor=-1; - samplePos=0; - updateSampleTex=true; - selStart=SelectionPoint(); - selEnd=SelectionPoint(); - cursor=SelectionPoint(); - updateWindowTitle(); - ImGui::CloseCurrentPopup(); - } -} - -void FurnaceGUI::drawStats() { - if (nextWindow==GUI_WINDOW_STATS) { - statsOpen=true; - ImGui::SetNextWindowFocus(); - nextWindow=GUI_WINDOW_NOTHING; - } - if (!statsOpen) return; - if (ImGui::Begin("Statistics",&statsOpen)) { - String adpcmAUsage=fmt::sprintf("%d/16384KB",e->adpcmAMemLen/1024); - String adpcmBUsage=fmt::sprintf("%d/16384KB",e->adpcmBMemLen/1024); - String qsoundUsage=fmt::sprintf("%d/16384KB",e->qsoundMemLen/1024); - String x1_010Usage=fmt::sprintf("%d/1024KB",e->x1_010MemLen/1024); - ImGui::Text("ADPCM-A"); - ImGui::SameLine(); - ImGui::ProgressBar(((float)e->adpcmAMemLen)/16777216.0f,ImVec2(-FLT_MIN,0),adpcmAUsage.c_str()); - ImGui::Text("ADPCM-B"); - ImGui::SameLine(); - ImGui::ProgressBar(((float)e->adpcmBMemLen)/16777216.0f,ImVec2(-FLT_MIN,0),adpcmBUsage.c_str()); - ImGui::Text("QSound"); - ImGui::SameLine(); - ImGui::ProgressBar(((float)e->qsoundMemLen)/16777216.0f,ImVec2(-FLT_MIN,0),qsoundUsage.c_str()); - ImGui::Text("X1-010"); - ImGui::SameLine(); - ImGui::ProgressBar(((float)e->x1_010MemLen)/1048576.0f,ImVec2(-FLT_MIN,0),x1_010Usage.c_str()); - } - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_STATS; - ImGui::End(); -} - -void FurnaceGUI::drawCompatFlags() { - if (nextWindow==GUI_WINDOW_COMPAT_FLAGS) { - compatFlagsOpen=true; - ImGui::SetNextWindowFocus(); - nextWindow=GUI_WINDOW_NOTHING; - } - if (!compatFlagsOpen) return; - if (ImGui::Begin("Compatibility Flags",&compatFlagsOpen)) { - ImGui::TextWrapped("these flags are designed to provide better DefleMask/older Furnace compatibility."); - ImGui::Checkbox("Limit slide range",&e->song.limitSlides); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("when enabled, slides are limited to a compatible range.\nmay cause problems with slides in negative octaves."); - } - ImGui::Checkbox("Linear pitch control",&e->song.linearPitch); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("linear pitch:\n- slides work in frequency/period space\n- E5xx and 04xx effects work in tonality space\nnon-linear pitch:\n- slides work in frequency/period space\n- E5xx and 04xx effects work on frequency/period space"); - } - ImGui::Checkbox("Proper noise layout on NES and PC Engine",&e->song.properNoiseLayout); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("use a proper noise channel note mapping (0-15) instead of a rather unusual compatible one.\nunlocks all noise frequencies on PC Engine."); - } - ImGui::Checkbox("Game Boy instrument duty is wave volume",&e->song.waveDutyIsVol); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("if enabled, an instrument with duty macro in the wave channel will be mapped to wavetable volume."); - } - - ImGui::Checkbox("Restart macro on portamento",&e->song.resetMacroOnPorta); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("when enabled, a portamento effect will reset the channel's macro if used in combination with a note."); - } - ImGui::Checkbox("Legacy volume slides",&e->song.legacyVolumeSlides); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("simulate glitchy volume slide behavior by silently overflowing the volume when the slide goes below 0."); - } - ImGui::Checkbox("Compatible arpeggio",&e->song.compatibleArpeggio); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("delay arpeggio by one tick on every new note."); - } - ImGui::Checkbox("Reset slides after note off",&e->song.noteOffResetsSlides); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("when enabled, note off will reset the channel's slide effect."); - } - ImGui::Checkbox("Reset portamento after reaching target",&e->song.targetResetsSlides); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("when enabled, the slide effect is disabled after it reaches its target."); - } - ImGui::Checkbox("Ignore duplicate slide effects",&e->song.ignoreDuplicateSlides); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("if this is on, only the first slide of a row in a channel will be considered."); - } - ImGui::Checkbox("Continuous vibrato",&e->song.continuousVibrato); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("when enabled, vibrato will not be reset on a new note."); - } - ImGui::Checkbox("Broken DAC mode",&e->song.brokenDACMode); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("when enabled, the DAC in YM2612 will be disabled if there isn't any sample playing."); - } - ImGui::Checkbox("Auto-insert one tick gap between notes",&e->song.oneTickCut); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("when enabled, a one-tick note cut will be inserted between non-legato/non-portamento notes.\nthis simulates the behavior of some Amiga/SNES music engines."); - } - - ImGui::Text("Loop modality:"); - if (ImGui::RadioButton("Reset channels",e->song.loopModality==0)) { - e->song.loopModality=0; - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("select to reset channels on loop. may trigger a voltage click on every loop!"); - } - if (ImGui::RadioButton("Soft reset channels",e->song.loopModality==1)) { - e->song.loopModality=1; - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("select to turn channels off on loop."); - } - if (ImGui::RadioButton("Do nothing",e->song.loopModality==2)) { - e->song.loopModality=2; - } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("select to not reset channels on loop."); - } - - ImGui::Separator(); - - ImGui::TextWrapped("the following flags are for compatibility with older Furnace versions."); - - ImGui::Checkbox("Arpeggio inhibits non-porta slides",&e->song.arpNonPorta); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("behavior changed in 0.5.5"); - } - ImGui::Checkbox("Wack FM algorithm macro",&e->song.algMacroBehavior); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("behavior changed in 0.5.5"); - } - ImGui::Checkbox("Broken shortcut slides (E1xy/E2xy)",&e->song.brokenShortcutSlides); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("behavior changed in 0.5.7"); - } - ImGui::Checkbox("Stop portamento on note off",&e->song.stopPortaOnNoteOff); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("behavior changed in 0.6"); - } - ImGui::Checkbox("Allow instrument change during slides",&e->song.newInsTriggersInPorta); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("behavior changed in 0.6"); - } - ImGui::Checkbox("Reset note to base on arpeggio stop",&e->song.arp0Reset); - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("behavior changed in 0.6"); - } - } - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_COMPAT_FLAGS; - ImGui::End(); -} - -void FurnaceGUI::drawPiano() { - if (nextWindow==GUI_WINDOW_PIANO) { - pianoOpen=true; - ImGui::SetNextWindowFocus(); - nextWindow=GUI_WINDOW_NOTHING; - } - if (!pianoOpen) return; - if (ImGui::Begin("Piano",&pianoOpen)) { - for (int i=0; igetTotalChannelCount(); i++) { - DivChannelState* cs=e->getChanState(i); - if (cs->keyOn) { - const char* noteName=NULL; - if (cs->note<-60 || cs->note>120) { - noteName="???"; - } else { - noteName=noteNames[cs->note+60]; - } - ImGui::Text("%d: %s",i,noteName); - } - } - } - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_PIANO; - ImGui::End(); -} - -// NOTE: please don't ask me to enable text wrap. -// Dear ImGui doesn't have that feature. D: -void FurnaceGUI::drawNotes() { - if (nextWindow==GUI_WINDOW_NOTES) { - notesOpen=true; - ImGui::SetNextWindowFocus(); - nextWindow=GUI_WINDOW_NOTHING; - } - if (!notesOpen) return; - if (ImGui::Begin("Song Comments",¬esOpen)) { - ImGui::InputTextMultiline("##SongNotes",&e->song.notes,ImGui::GetContentRegionAvail()); - } - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_NOTES; - ImGui::End(); -} - -void FurnaceGUI::drawChannels() { - if (nextWindow==GUI_WINDOW_CHANNELS) { - channelsOpen=true; - ImGui::SetNextWindowFocus(); - nextWindow=GUI_WINDOW_NOTHING; - } - if (!channelsOpen) return; - if (ImGui::Begin("Channels",&channelsOpen)) { - if (ImGui::BeginTable("ChannelList",3)) { - ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,0.0); - ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.0); - ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthFixed,48.0f*dpiScale); - for (int i=0; igetTotalChannelCount(); i++) { - ImGui::PushID(i); - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Checkbox("##Visible",&e->song.chanShow[i]); - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - ImGui::InputTextWithHint("##ChanName",e->getChannelName(i),&e->song.chanName[i]); - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - ImGui::InputTextWithHint("##ChanShortName",e->getChannelShortName(i),&e->song.chanShortName[i]); - ImGui::PopID(); - } - ImGui::EndTable(); - } - } - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_CHANNELS; - ImGui::End(); -} - -void FurnaceGUI::drawRegView() { - if (nextWindow==GUI_WINDOW_REGISTER_VIEW) { - channelsOpen=true; - ImGui::SetNextWindowFocus(); - nextWindow=GUI_WINDOW_NOTHING; - } - if (!regViewOpen) return; - if (ImGui::Begin("Register View",®ViewOpen)) { - for (int i=0; isong.systemLen; i++) { - ImGui::Text("%d. %s",i+1,getSystemName(e->song.system[i])); - int size=0; - int depth=8; - unsigned char* regPool=e->getRegisterPool(i,size,depth); - unsigned short* regPoolW=(unsigned short*)regPool; - if (regPool==NULL) { - ImGui::Text("- no register pool available"); - } else { - ImGui::PushFont(patFont); - if (ImGui::BeginTable("Memory",17)) { - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - for (int i=0; i<16; i++) { - ImGui::TableNextColumn(); - ImGui::TextColored(uiColors[GUI_COLOR_PATTERN_ROW_INDEX]," %X",i); - } - for (int i=0; i<=((size-1)>>4); i++) { - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::TextColored(uiColors[GUI_COLOR_PATTERN_ROW_INDEX],"%.2X",i*16); - for (int j=0; j<16; j++) { - ImGui::TableNextColumn(); - if (i*16+j>=size) continue; - if (depth == 8) { - ImGui::Text("%.2x",regPool[i*16+j]); - } else if (depth == 16) { - ImGui::Text("%.4x",regPoolW[i*16+j]); - } else { - ImGui::Text("??"); - } - } - } - ImGui::EndTable(); - } - ImGui::PopFont(); - } - } - } - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_REGISTER_VIEW; - ImGui::End(); -} - -void FurnaceGUI::startSelection(int xCoarse, int xFine, int y) { - if (xCoarse!=selStart.xCoarse || xFine!=selStart.xFine || y!=selStart.y) { - curNibble=false; - } - cursor.xCoarse=xCoarse; - cursor.xFine=xFine; - cursor.y=y; - selStart.xCoarse=xCoarse; - selStart.xFine=xFine; - selStart.y=y; - selEnd.xCoarse=xCoarse; - selEnd.xFine=xFine; - selEnd.y=y; - selecting=true; -} - -void FurnaceGUI::updateSelection(int xCoarse, int xFine, int y) { - if (!selecting) return; - selEnd.xCoarse=xCoarse; - selEnd.xFine=xFine; - selEnd.y=y; -} - -void FurnaceGUI::finishSelection() { - // swap points if needed - if (selEnd.ygetTotalChannelCount(); - - if (selStart.xCoarse<0) selStart.xCoarse=0; - if (selStart.xCoarse>=chanCount) selStart.xCoarse=chanCount-1; - if (selStart.y<0) selStart.y=0; - if (selStart.y>=e->song.patLen) selStart.y=e->song.patLen-1; - if (selEnd.xCoarse<0) selEnd.xCoarse=0; - if (selEnd.xCoarse>=chanCount) selEnd.xCoarse=chanCount-1; - if (selEnd.y<0) selEnd.y=0; - if (selEnd.y>=e->song.patLen) selEnd.y=e->song.patLen-1; - if (cursor.xCoarse<0) cursor.xCoarse=0; - if (cursor.xCoarse>=chanCount) cursor.xCoarse=chanCount-1; - if (cursor.y<0) cursor.y=0; - if (cursor.y>=e->song.patLen) cursor.y=e->song.patLen-1; - - if (e->song.chanCollapse[selEnd.xCoarse]) { - selStart.xFine=0; - } - if (e->song.chanCollapse[selEnd.xCoarse]) { - selEnd.xFine=2+e->song.pat[cursor.xCoarse].effectRows*2; - } -} - -#define DETERMINE_FIRST \ - int firstChannel=0; \ - for (int i=0; igetTotalChannelCount(); i++) { \ - if (e->song.chanShow[i]) { \ - firstChannel=i; \ - break; \ - } \ - } \ - -#define DETERMINE_LAST \ - int lastChannel=0; \ - for (int i=e->getTotalChannelCount()-1; i>=0; i--) { \ - if (e->song.chanShow[i]) { \ - lastChannel=i+1; \ - break; \ - } \ - } - -#define DETERMINE_FIRST_LAST \ - DETERMINE_FIRST \ - DETERMINE_LAST - -void FurnaceGUI::moveCursor(int x, int y, bool select) { - if (!select) { - finishSelection(); - } - - DETERMINE_FIRST_LAST; - - curNibble=false; - if (x!=0) { - demandScrollX=true; - if (x>0) { - for (int i=0; i=(e->song.chanCollapse[cursor.xCoarse]?1:(3+e->song.pat[cursor.xCoarse].effectRows*2))) { - cursor.xFine=0; - if (++cursor.xCoarse>=lastChannel) { - if (settings.wrapHorizontal!=0 && !select) { - cursor.xCoarse=firstChannel; - if (settings.wrapHorizontal==2) y++; - } else { - cursor.xCoarse=lastChannel-1; - cursor.xFine=e->song.chanCollapse[cursor.xCoarse]?0:(2+e->song.pat[cursor.xCoarse].effectRows*2); - } - } else { - while (!e->song.chanShow[cursor.xCoarse]) { - cursor.xCoarse++; - if (cursor.xCoarse>=e->getTotalChannelCount()) break; - } - } - } - } - } else { - for (int i=0; i<-x; i++) { - if (--cursor.xFine<0) { - if (--cursor.xCoarsesong.pat[cursor.xCoarse].effectRows*2; - if (settings.wrapHorizontal==2) y--; - } else { - cursor.xCoarse=firstChannel; - cursor.xFine=0; - } - } else { - while (!e->song.chanShow[cursor.xCoarse]) { - cursor.xCoarse--; - if (cursor.xCoarse<0) break; - } - if (e->song.chanCollapse[cursor.xCoarse]) { - cursor.xFine=0; - } else { - cursor.xFine=2+e->song.pat[cursor.xCoarse].effectRows*2; - } - } - } - } - } - } - if (y!=0) { - if (y>0) { - for (int i=0; i=e->song.patLen) { - if (settings.wrapVertical!=0 && !select) { - cursor.y=0; - if (settings.wrapVertical==2) { - if (!e->isPlaying() && e->getOrder()<(e->song.ordersLen-1)) { - e->setOrder(e->getOrder()+1); - } else { - cursor.y=e->song.patLen-1; - } - } - } else { - cursor.y=e->song.patLen-1; - } - } - } - } else { - for (int i=0; i<-y; i++) { - cursor.y--; - if (cursor.y<0) { - if (settings.wrapVertical!=0 && !select) { - cursor.y=e->song.patLen-1; - if (settings.wrapVertical==2) { - if (!e->isPlaying() && e->getOrder()>0) { - e->setOrder(e->getOrder()-1); - } else { - cursor.y=0; - } - } - } else { - cursor.y=0; - } - } - } - } - } - if (!select) { - selStart=cursor; - } - selEnd=cursor; - updateScroll(cursor.y); -} - -void FurnaceGUI::moveCursorPrevChannel(bool overflow) { - finishSelection(); - curNibble=false; - - DETERMINE_FIRST_LAST; - - do { - cursor.xCoarse--; - if (cursor.xCoarse<0) break; - } while (!e->song.chanShow[cursor.xCoarse]); - if (cursor.xCoarse=e->getTotalChannelCount()) break; - } while (!e->song.chanShow[cursor.xCoarse]); - if (cursor.xCoarse>=lastChannel) { - if (overflow) { - cursor.xCoarse=firstChannel; - } else { - cursor.xCoarse=lastChannel-1; - } - } - - selStart=cursor; - selEnd=cursor; - demandScrollX=true; -} - -void FurnaceGUI::moveCursorTop(bool select) { - finishSelection(); - curNibble=false; - if (cursor.y==0) { - DETERMINE_FIRST; - cursor.xCoarse=firstChannel; - cursor.xFine=0; - demandScrollX=true; - } else { - cursor.y=0; - } - selStart=cursor; - if (!select) { - selEnd=cursor; - } - updateScroll(cursor.y); -} - -void FurnaceGUI::moveCursorBottom(bool select) { - finishSelection(); - curNibble=false; - if (cursor.y==e->song.patLen-1) { - DETERMINE_LAST; - cursor.xCoarse=lastChannel-1; - cursor.xFine=2+e->song.pat[cursor.xCoarse].effectRows*2; - demandScrollX=true; - } else { - cursor.y=e->song.patLen-1; - } - if (!select) { - selStart=cursor; - } - selEnd=cursor; - updateScroll(cursor.y); -} - -void FurnaceGUI::editAdvance() { - finishSelection(); - cursor.y+=editStep; - if (cursor.y>=e->song.patLen) cursor.y=e->song.patLen-1; - selStart=cursor; - selEnd=cursor; - updateScroll(cursor.y); -} - -void FurnaceGUI::prepareUndo(ActionType action) { - int order=e->getOrder(); - switch (action) { - case GUI_UNDO_CHANGE_ORDER: - oldOrders=e->song.orders; - oldOrdersLen=e->song.ordersLen; - break; - case GUI_UNDO_PATTERN_EDIT: - case GUI_UNDO_PATTERN_DELETE: - case GUI_UNDO_PATTERN_PULL: - case GUI_UNDO_PATTERN_PUSH: - case GUI_UNDO_PATTERN_CUT: - case GUI_UNDO_PATTERN_PASTE: - case GUI_UNDO_PATTERN_CHANGE_INS: - case GUI_UNDO_PATTERN_INTERPOLATE: - case GUI_UNDO_PATTERN_FADE: - case GUI_UNDO_PATTERN_SCALE: - case GUI_UNDO_PATTERN_RANDOMIZE: - case GUI_UNDO_PATTERN_INVERT_VAL: - case GUI_UNDO_PATTERN_FLIP: - case GUI_UNDO_PATTERN_COLLAPSE: - case GUI_UNDO_PATTERN_EXPAND: - for (int i=0; igetTotalChannelCount(); i++) { - e->song.pat[i].getPattern(e->song.orders.ord[i][order],false)->copyOn(oldPat[i]); - } - break; - } -} - -void FurnaceGUI::makeUndo(ActionType action) { - bool doPush=false; - UndoStep s; - s.type=action; - s.cursor=cursor; - s.selStart=selStart; - s.selEnd=selEnd; - int order=e->getOrder(); - s.order=order; - s.nibble=curNibble; - switch (action) { - case GUI_UNDO_CHANGE_ORDER: - for (int i=0; isong.orders.ord[i][j]) { - s.ord.push_back(UndoOrderData(i,j,oldOrders.ord[i][j],e->song.orders.ord[i][j])); - } - } - } - s.oldOrdersLen=oldOrdersLen; - s.newOrdersLen=e->song.ordersLen; - if (oldOrdersLen!=e->song.ordersLen) { - doPush=true; - } - if (!s.ord.empty()) { - doPush=true; - } - break; - case GUI_UNDO_PATTERN_EDIT: - case GUI_UNDO_PATTERN_DELETE: - case GUI_UNDO_PATTERN_PULL: - case GUI_UNDO_PATTERN_PUSH: - case GUI_UNDO_PATTERN_CUT: - case GUI_UNDO_PATTERN_PASTE: - case GUI_UNDO_PATTERN_CHANGE_INS: - case GUI_UNDO_PATTERN_INTERPOLATE: - case GUI_UNDO_PATTERN_FADE: - case GUI_UNDO_PATTERN_SCALE: - case GUI_UNDO_PATTERN_RANDOMIZE: - case GUI_UNDO_PATTERN_INVERT_VAL: - case GUI_UNDO_PATTERN_FLIP: - case GUI_UNDO_PATTERN_COLLAPSE: - case GUI_UNDO_PATTERN_EXPAND: - for (int i=0; igetTotalChannelCount(); i++) { - DivPattern* p=e->song.pat[i].getPattern(e->song.orders.ord[i][order],false); - for (int j=0; jsong.patLen; j++) { - for (int k=0; k<32; k++) { - if (p->data[j][k]!=oldPat[i]->data[j][k]) { - s.pat.push_back(UndoPatternData(i,e->song.orders.ord[i][order],j,k,oldPat[i]->data[j][k],p->data[j][k])); - } - } - } - } - if (!s.pat.empty()) { - doPush=true; - } - break; - } - if (doPush) { - MARK_MODIFIED; - undoHist.push_back(s); - redoHist.clear(); - if (undoHist.size()>settings.maxUndoSteps) undoHist.pop_front(); - } -} - -void FurnaceGUI::doSelectAll() { - finishSelection(); - curNibble=false; - if (selStart.xFine==0 && selEnd.xFine==2+e->song.pat[selEnd.xCoarse].effectRows*2) { - if (selStart.y==0 && selEnd.y==e->song.patLen-1) { // select entire pattern - selStart.xCoarse=0; - selStart.xFine=0; - selEnd.xCoarse=e->getTotalChannelCount()-1; - selEnd.xFine=2+e->song.pat[selEnd.xCoarse].effectRows*2; - } else { // select entire column - selStart.y=0; - selEnd.y=e->song.patLen-1; - } - } else { - int selStartX=0; - int selEndX=0; - // find row position - for (SelectionPoint i; i.xCoarse!=selStart.xCoarse || i.xFine!=selStart.xFine; selStartX++) { - i.xFine++; - if (i.xFine>=3+e->song.pat[i.xCoarse].effectRows*2) { - i.xFine=0; - i.xCoarse++; - } - } - for (SelectionPoint i; i.xCoarse!=selEnd.xCoarse || i.xFine!=selEnd.xFine; selEndX++) { - i.xFine++; - if (i.xFine>=3+e->song.pat[i.xCoarse].effectRows*2) { - i.xFine=0; - i.xCoarse++; - } - } - - float aspect=float(selEndX-selStartX+1)/float(selEnd.y-selStart.y+1); - if (aspect<=1.0f && !(selStart.y==0 && selEnd.y==e->song.patLen-1)) { // up-down - selStart.y=0; - selEnd.y=e->song.patLen-1; - } else { // left-right - selStart.xFine=0; - selEnd.xFine=2+e->song.pat[selEnd.xCoarse].effectRows*2; - } - } -} - -#define maskOut(x) \ - if (x==0) { \ - if (!opMaskNote) continue; \ - } else if (x==1) { \ - if (!opMaskIns) continue; \ - } else if (x==2) { \ - if (!opMaskVol) continue; \ - } else if (((x)&1)==0) { \ - if (!opMaskEffectVal) continue; \ - } else if (((x)&1)==1) { \ - if (!opMaskEffect) continue; \ - } - -void FurnaceGUI::doDelete() { - finishSelection(); - prepareUndo(GUI_UNDO_PATTERN_DELETE); - curNibble=false; - - int iCoarse=selStart.xCoarse; - int iFine=selStart.xFine; - int ord=e->getOrder(); - for (; iCoarse<=selEnd.xCoarse; iCoarse++) { - if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); - for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsedata[j][iFine]=0; - if (selStart.y==selEnd.y) pat->data[j][2]=-1; - } - pat->data[j][iFine+1]=(iFine<1)?0:-1; - } - } - iFine=0; - } - - makeUndo(GUI_UNDO_PATTERN_DELETE); -} - -void FurnaceGUI::doPullDelete() { - finishSelection(); - prepareUndo(GUI_UNDO_PATTERN_PULL); - curNibble=false; - - if (settings.pullDeleteBehavior) { - if (--selStart.y<0) selStart.y=0; - if (--selEnd.y<0) selEnd.y=0; - if (--cursor.y<0) cursor.y=0; - updateScroll(cursor.y); - } - - int iCoarse=selStart.xCoarse; - int iFine=selStart.xFine; - int ord=e->getOrder(); - for (; iCoarse<=selEnd.xCoarse; iCoarse++) { - if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); - for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsesong.patLen; j++) { - if (jsong.patLen-1) { - if (iFine==0) { - pat->data[j][iFine]=pat->data[j+1][iFine]; - } - pat->data[j][iFine+1]=pat->data[j+1][iFine+1]; - } else { - if (iFine==0) { - pat->data[j][iFine]=0; - } - pat->data[j][iFine+1]=(iFine<1)?0:-1; - } - } - } - iFine=0; - } - - makeUndo(GUI_UNDO_PATTERN_PULL); -} - -void FurnaceGUI::doInsert() { - finishSelection(); - prepareUndo(GUI_UNDO_PATTERN_PUSH); - curNibble=false; - - int iCoarse=selStart.xCoarse; - int iFine=selStart.xFine; - int ord=e->getOrder(); - for (; iCoarse<=selEnd.xCoarse; iCoarse++) { - if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); - for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsesong.patLen-1; j>=selStart.y; j--) { - if (j==selStart.y) { - if (iFine==0) { - pat->data[j][iFine]=0; - } - pat->data[j][iFine+1]=(iFine<1)?0:-1; - } else { - if (iFine==0) { - pat->data[j][iFine]=pat->data[j-1][iFine]; - } - pat->data[j][iFine+1]=pat->data[j-1][iFine+1]; - } - } - } - iFine=0; - } - - makeUndo(GUI_UNDO_PATTERN_PUSH); -} - -void FurnaceGUI::doTranspose(int amount) { - finishSelection(); - prepareUndo(GUI_UNDO_PATTERN_DELETE); - curNibble=false; - - int iCoarse=selStart.xCoarse; - int iFine=selStart.xFine; - int ord=e->getOrder(); - for (; iCoarse<=selEnd.xCoarse; iCoarse++) { - if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); - for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsedata[j][0]; - int origOctave=(signed char)pat->data[j][1]; - if (origNote!=0 && origNote!=100 && origNote!=101 && origNote!=102) { - origNote+=amount; - while (origNote>12) { - origNote-=12; - origOctave++; - } - while (origNote<1) { - origNote+=12; - origOctave--; - } - if (origOctave==9 && origNote>11) { - origNote=11; - origOctave=9; - } - if (origOctave>9) { - origNote=11; - origOctave=9; - } - if (origOctave<-5) { - origNote=1; - origOctave=-5; - } - pat->data[j][0]=origNote; - pat->data[j][1]=(unsigned char)origOctave; - } - } else { - int top=255; - if (iFine==1) { - if (e->song.ins.empty()) continue; - top=e->song.ins.size()-1; - } else if (iFine==2) { // volume - top=e->getMaxVolumeChan(iCoarse); - } - if (pat->data[j][iFine+1]==-1) continue; - pat->data[j][iFine+1]=MIN(top,MAX(0,pat->data[j][iFine+1]+amount)); - } - } - } - iFine=0; - } - - makeUndo(GUI_UNDO_PATTERN_DELETE); -} - -void FurnaceGUI::doCopy(bool cut) { - finishSelection(); - if (cut) { - curNibble=false; - prepareUndo(GUI_UNDO_PATTERN_CUT); - } - clipboard=fmt::sprintf("org.tildearrow.furnace - Pattern Data (%d)\n%d",DIV_ENGINE_VERSION,selStart.xFine); - - for (int j=selStart.y; j<=selEnd.y; j++) { - int iCoarse=selStart.xCoarse; - int iFine=selStart.xFine; - if (iFine>3 && !(iFine&1)) { - iFine--; - } - int ord=e->getOrder(); - clipboard+='\n'; - for (; iCoarse<=selEnd.xCoarse; iCoarse++) { - if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); - for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsedata[j][0],pat->data[j][1]); - if (cut) { - pat->data[j][0]=0; - pat->data[j][1]=0; - } - } else { - if (pat->data[j][iFine+1]==-1) { - clipboard+=".."; - } else { - clipboard+=fmt::sprintf("%.2X",pat->data[j][iFine+1]); - } - if (cut) { - pat->data[j][iFine+1]=-1; - } - } - } - clipboard+='|'; - iFine=0; - } - } - SDL_SetClipboardText(clipboard.c_str()); - - if (cut) { - makeUndo(GUI_UNDO_PATTERN_CUT); - } -} - -void FurnaceGUI::doPaste(PasteMode mode) { - finishSelection(); - prepareUndo(GUI_UNDO_PATTERN_PASTE); - char* clipText=SDL_GetClipboardText(); - if (clipText!=NULL) { - if (clipText[0]) { - clipboard=clipText; - } - SDL_free(clipText); - } - std::vector data; - String tempS; - for (char i: clipboard) { - if (i=='\r') continue; - if (i=='\n') { - data.push_back(tempS); - tempS=""; - continue; - } - tempS+=i; - } - data.push_back(tempS); - - int startOff=-1; - bool invalidData=false; - if (data.size()<2) return; - if (data[0]!=fmt::sprintf("org.tildearrow.furnace - Pattern Data (%d)",DIV_ENGINE_VERSION)) return; - if (sscanf(data[1].c_str(),"%d",&startOff)!=1) return; - if (startOff<0) return; - - DETERMINE_LAST; - - int j=cursor.y; - char note[4]; - int ord=e->getOrder(); - for (size_t i=2; isong.patLen; i++) { - size_t charPos=0; - int iCoarse=cursor.xCoarse; - int iFine=(startOff>2 && cursor.xFine>2)?(((cursor.xFine-1)&(~1))|1):startOff; - - String& line=data[i]; - - while (charPossong.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); - if (line[charPos]=='|') { - iCoarse++; - if (iCoarsesong.chanShow[iCoarse]) { - iCoarse++; - if (iCoarse>=lastChannel) break; - } - iFine=0; - charPos++; - continue; - } - if (iFine==0) { - if (charPos>=line.size()) { - invalidData=true; - break; - } - note[0]=line[charPos++]; - if (charPos>=line.size()) { - invalidData=true; - break; - } - note[1]=line[charPos++]; - if (charPos>=line.size()) { - invalidData=true; - break; - } - note[2]=line[charPos++]; - note[3]=0; - - if (iFine==0 && !opMaskNote) { - iFine++; - continue; - } - - if ((mode==GUI_PASTE_MODE_MIX_BG || mode==GUI_PASTE_MODE_MIX_FG) && strcmp(note,"...")==0) { - // do nothing. - } else { - if (mode!=GUI_PASTE_MODE_MIX_BG || (pat->data[j][0]==0 && pat->data[j][1]==0)) { - if (!decodeNote(note,pat->data[j][0],pat->data[j][1])) { - invalidData=true; - break; - } - } - } - } else { - if (charPos>=line.size()) { - invalidData=true; - break; - } - note[0]=line[charPos++]; - if (charPos>=line.size()) { - invalidData=true; - break; - } - note[1]=line[charPos++]; - note[2]=0; - - if (iFine==1) { - if (!opMaskIns) { - iFine++; - continue; - } - } else if (iFine==2) { - if (!opMaskVol) { - iFine++; - continue; - } - } else if ((iFine&1)==0) { - if (!opMaskEffectVal) { - iFine++; - continue; - } - } else if ((iFine&1)==1) { - if (!opMaskEffect) { - iFine++; - continue; - } - } - - if (strcmp(note,"..")==0) { - if (!(mode==GUI_PASTE_MODE_MIX_BG || mode==GUI_PASTE_MODE_MIX_FG)) { - pat->data[j][iFine+1]=-1; - } - } else { - unsigned int val=0; - if (sscanf(note,"%2X",&val)!=1) { - invalidData=true; - break; - } - if (mode!=GUI_PASTE_MODE_MIX_BG || pat->data[j][iFine+1]==-1) { - if (iFine<(3+e->song.pat[iCoarse].effectRows*2)) pat->data[j][iFine+1]=val; - } - } - } - iFine++; - } - - if (invalidData) { - logW("invalid clipboard data! failed at line %d char %d\n",i,charPos); - logW("%s\n",line.c_str()); - break; - } - j++; - if (mode==GUI_PASTE_MODE_OVERFLOW && j>=e->song.patLen && ordsong.ordersLen-1) { - j=0; - ord++; - } - - if (mode==GUI_PASTE_MODE_FLOOD && i==data.size()-1) { - i=1; - } - } - - makeUndo(GUI_UNDO_PATTERN_PASTE); -} - -void FurnaceGUI::doChangeIns(int ins) { - finishSelection(); - prepareUndo(GUI_UNDO_PATTERN_CHANGE_INS); - - int iCoarse=selStart.xCoarse; - int ord=e->getOrder(); - for (; iCoarse<=selEnd.xCoarse; iCoarse++) { - if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); - for (int j=selStart.y; j<=selEnd.y; j++) { - if (pat->data[j][2]!=-1 || !(pat->data[j][0]==0 && pat->data[j][1]==0)) { - pat->data[j][2]=ins; - } - } - } - - makeUndo(GUI_UNDO_PATTERN_CHANGE_INS); -} - -void FurnaceGUI::doInterpolate() { - finishSelection(); - prepareUndo(GUI_UNDO_PATTERN_INTERPOLATE); - - std::vector> points; - int iCoarse=selStart.xCoarse; - int iFine=selStart.xFine; - int ord=e->getOrder(); - for (; iCoarse<=selEnd.xCoarse; iCoarse++) { - if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); - for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsedata[j][iFine+1]!=-1) { - points.emplace(points.end(),j,pat->data[j][iFine+1]); - } - } - - if (points.size()>1) for (size_t j=0; j& curPoint=points[j]; - std::pair& nextPoint=points[j+1]; - double distance=nextPoint.first-curPoint.first; - for (int k=0; k<(nextPoint.first-curPoint.first); k++) { - pat->data[k+curPoint.first][iFine+1]=curPoint.second+((nextPoint.second-curPoint.second)*(double)k/distance); - } - } - } else { - for (int j=selStart.y; j<=selEnd.y; j++) { - if (pat->data[j][0]!=0 && pat->data[j][1]!=0) { - if (pat->data[j][0]!=100 && pat->data[j][0]!=101 && pat->data[j][0]!=102) { - points.emplace(points.end(),j,pat->data[j][0]+pat->data[j][1]*12); - } - } - } - - if (points.size()>1) for (size_t j=0; j& curPoint=points[j]; - std::pair& nextPoint=points[j+1]; - double distance=nextPoint.first-curPoint.first; - for (int k=0; k<(nextPoint.first-curPoint.first); k++) { - int val=curPoint.second+((nextPoint.second-curPoint.second)*(double)k/distance); - pat->data[k+curPoint.first][0]=val%12; - pat->data[k+curPoint.first][1]=val/12; - if (pat->data[k+curPoint.first][0]==0) { - pat->data[k+curPoint.first][0]=12; - pat->data[k+curPoint.first][1]--; - } - pat->data[k+curPoint.first][1]&=255; - } - } - } - } - iFine=0; - } - - makeUndo(GUI_UNDO_PATTERN_INTERPOLATE); -} - -void FurnaceGUI::doFade(int p0, int p1, bool mode) { - finishSelection(); - prepareUndo(GUI_UNDO_PATTERN_FADE); - - int iCoarse=selStart.xCoarse; - int iFine=selStart.xFine; - int ord=e->getOrder(); - for (; iCoarse<=selEnd.xCoarse; iCoarse++) { - if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); - for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsesong.ins.empty()) continue; - absoluteTop=e->song.ins.size()-1; - } else if (iFine==2) { // volume - absoluteTop=e->getMaxVolumeChan(iCoarse); - } - if (selEnd.y-selStart.y<1) continue; - for (int j=selStart.y; j<=selEnd.y; j++) { - double fraction=double(j-selStart.y)/double(selEnd.y-selStart.y); - int value=p0+double(p1-p0)*fraction; - if (mode) { // nibble - value&=15; - pat->data[j][iFine+1]=MIN(absoluteTop,value|(value<<4)); - } else { // byte - pat->data[j][iFine+1]=MIN(absoluteTop,value); - } - } - } - } - iFine=0; - } - - makeUndo(GUI_UNDO_PATTERN_FADE); -} - -void FurnaceGUI::doInvertValues() { - finishSelection(); - prepareUndo(GUI_UNDO_PATTERN_INVERT_VAL); - - int iCoarse=selStart.xCoarse; - int iFine=selStart.xFine; - int ord=e->getOrder(); - for (; iCoarse<=selEnd.xCoarse; iCoarse++) { - if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); - for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsesong.ins.empty()) continue; - top=e->song.ins.size()-1; - } else if (iFine==2) { // volume - top=e->getMaxVolumeChan(iCoarse); - } - for (int j=selStart.y; j<=selEnd.y; j++) { - if (pat->data[j][iFine+1]==-1) continue; - pat->data[j][iFine+1]=top-pat->data[j][iFine+1]; - } - } - } - iFine=0; - } - - makeUndo(GUI_UNDO_PATTERN_INVERT_VAL); -} - -void FurnaceGUI::doScale(float top) { - finishSelection(); - prepareUndo(GUI_UNDO_PATTERN_SCALE); - - int iCoarse=selStart.xCoarse; - int iFine=selStart.xFine; - int ord=e->getOrder(); - for (; iCoarse<=selEnd.xCoarse; iCoarse++) { - if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); - for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsesong.ins.empty()) continue; - absoluteTop=e->song.ins.size()-1; - } else if (iFine==2) { // volume - absoluteTop=e->getMaxVolumeChan(iCoarse); - } - for (int j=selStart.y; j<=selEnd.y; j++) { - if (pat->data[j][iFine+1]==-1) continue; - pat->data[j][iFine+1]=MIN(absoluteTop,(double)pat->data[j][iFine+1]*(top/100.0f)); - } - } - } - iFine=0; - } - - makeUndo(GUI_UNDO_PATTERN_SCALE); -} - -void FurnaceGUI::doRandomize(int bottom, int top, bool mode) { - finishSelection(); - prepareUndo(GUI_UNDO_PATTERN_RANDOMIZE); - - int iCoarse=selStart.xCoarse; - int iFine=selStart.xFine; - int ord=e->getOrder(); - for (; iCoarse<=selEnd.xCoarse; iCoarse++) { - if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); - for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsesong.ins.empty()) continue; - absoluteTop=e->song.ins.size()-1; - } else if (iFine==2) { // volume - absoluteTop=e->getMaxVolumeChan(iCoarse); - } - for (int j=selStart.y; j<=selEnd.y; j++) { - int value=0; - int value2=0; - if (top-bottom<=0) { - value=MIN(absoluteTop,bottom); - value2=MIN(absoluteTop,bottom); - } else { - value=MIN(absoluteTop,bottom+(rand()%(top-bottom+1))); - value2=MIN(absoluteTop,bottom+(rand()%(top-bottom+1))); - } - if (mode) { - value&=15; - value2&=15; - pat->data[j][iFine+1]=value|(value2<<4); - } else { - pat->data[j][iFine+1]=value; - } - } - } - } - iFine=0; - } - - makeUndo(GUI_UNDO_PATTERN_RANDOMIZE); -} - -void FurnaceGUI::doFlip() { - finishSelection(); - prepareUndo(GUI_UNDO_PATTERN_FLIP); - - DivPattern patBuffer; - int iCoarse=selStart.xCoarse; - int iFine=selStart.xFine; - int ord=e->getOrder(); - for (; iCoarse<=selEnd.xCoarse; iCoarse++) { - if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); - for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsedata[j][0]; - } - patBuffer.data[j][iFine+1]=pat->data[j][iFine+1]; - } - for (int j=selStart.y; j<=selEnd.y; j++) { - if (iFine==0) { - pat->data[j][0]=patBuffer.data[selEnd.y-j+selStart.y][0]; - } - pat->data[j][iFine+1]=patBuffer.data[selEnd.y-j+selStart.y][iFine+1]; - } - } - iFine=0; - } - - makeUndo(GUI_UNDO_PATTERN_FLIP); -} - -void FurnaceGUI::doCollapse(int divider) { - finishSelection(); - prepareUndo(GUI_UNDO_PATTERN_COLLAPSE); - - DivPattern patBuffer; - int iCoarse=selStart.xCoarse; - int iFine=selStart.xFine; - int ord=e->getOrder(); - for (; iCoarse<=selEnd.xCoarse; iCoarse++) { - if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); - for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsedata[j][0]; - } - patBuffer.data[j][iFine+1]=pat->data[j][iFine+1]; - } - for (int j=0; j<=selEnd.y-selStart.y; j++) { - if (j*divider>=selEnd.y-selStart.y) { - if (iFine==0) { - pat->data[j+selStart.y][0]=0; - pat->data[j+selStart.y][1]=0; - } else { - pat->data[j+selStart.y][iFine+1]=-1; - } - } else { - if (iFine==0) { - pat->data[j+selStart.y][0]=patBuffer.data[j*divider+selStart.y][0]; - } - pat->data[j+selStart.y][iFine+1]=patBuffer.data[j*divider+selStart.y][iFine+1]; - - if (iFine==0) { - for (int k=1; k=selEnd.y-selStart.y) break; - if (!(pat->data[j+selStart.y][0]==0 && pat->data[j+selStart.y][1]==0)) break; - pat->data[j+selStart.y][0]=patBuffer.data[j*divider+selStart.y+k][0]; - pat->data[j+selStart.y][1]=patBuffer.data[j*divider+selStart.y+k][1]; - } - } else { - for (int k=1; k=selEnd.y-selStart.y) break; - if (pat->data[j+selStart.y][iFine+1]!=-1) break; - pat->data[j+selStart.y][iFine+1]=patBuffer.data[j*divider+selStart.y+k][iFine+1]; - } - } - } - } - } - iFine=0; - } - - makeUndo(GUI_UNDO_PATTERN_COLLAPSE); -} - -void FurnaceGUI::doExpand(int multiplier) { - if (multiplier<1) return; - - finishSelection(); - prepareUndo(GUI_UNDO_PATTERN_EXPAND); - - DivPattern patBuffer; - int iCoarse=selStart.xCoarse; - int iFine=selStart.xFine; - int ord=e->getOrder(); - for (; iCoarse<=selEnd.xCoarse; iCoarse++) { - if (!e->song.chanShow[iCoarse]) continue; - DivPattern* pat=e->song.pat[iCoarse].getPattern(e->song.orders.ord[iCoarse][ord],true); - for (; iFine<3+e->song.pat[iCoarse].effectRows*2 && (iCoarsedata[j][0]; - } - patBuffer.data[j][iFine+1]=pat->data[j][iFine+1]; - } - for (int j=0; j<=(selEnd.y-selStart.y)*multiplier; j++) { - if ((j+selStart.y)>=e->song.patLen) break; - if ((j%multiplier)!=0) { - if (iFine==0) { - pat->data[j+selStart.y][0]=0; - pat->data[j+selStart.y][1]=0; - } else { - pat->data[j+selStart.y][iFine+1]=-1; - } - continue; - } - if (iFine==0) { - pat->data[j+selStart.y][0]=patBuffer.data[j/multiplier+selStart.y][0]; - } - pat->data[j+selStart.y][iFine+1]=patBuffer.data[j/multiplier+selStart.y][iFine+1]; - } - } - iFine=0; - } - - makeUndo(GUI_UNDO_PATTERN_EXPAND); -} - -void FurnaceGUI::doUndo() { - if (undoHist.empty()) return; - UndoStep& us=undoHist.back(); - redoHist.push_back(us); - MARK_MODIFIED; - - switch (us.type) { - case GUI_UNDO_CHANGE_ORDER: - e->song.ordersLen=us.oldOrdersLen; - for (UndoOrderData& i: us.ord) { - e->song.orders.ord[i.chan][i.ord]=i.oldVal; - } - break; - case GUI_UNDO_PATTERN_EDIT: - case GUI_UNDO_PATTERN_DELETE: - case GUI_UNDO_PATTERN_PULL: - case GUI_UNDO_PATTERN_PUSH: - case GUI_UNDO_PATTERN_CUT: - case GUI_UNDO_PATTERN_PASTE: - case GUI_UNDO_PATTERN_CHANGE_INS: - case GUI_UNDO_PATTERN_INTERPOLATE: - case GUI_UNDO_PATTERN_FADE: - case GUI_UNDO_PATTERN_SCALE: - case GUI_UNDO_PATTERN_RANDOMIZE: - case GUI_UNDO_PATTERN_INVERT_VAL: - case GUI_UNDO_PATTERN_FLIP: - case GUI_UNDO_PATTERN_COLLAPSE: - case GUI_UNDO_PATTERN_EXPAND: - for (UndoPatternData& i: us.pat) { - DivPattern* p=e->song.pat[i.chan].getPattern(i.pat,true); - p->data[i.row][i.col]=i.oldVal; - } - if (!e->isPlaying()) { - cursor=us.cursor; - selStart=us.selStart; - selEnd=us.selEnd; - curNibble=us.nibble; - updateScroll(cursor.y); - e->setOrder(us.order); - } - break; - } - - undoHist.pop_back(); -} - -void FurnaceGUI::doRedo() { - if (redoHist.empty()) return; - UndoStep& us=redoHist.back(); - undoHist.push_back(us); - MARK_MODIFIED; - - switch (us.type) { - case GUI_UNDO_CHANGE_ORDER: - e->song.ordersLen=us.newOrdersLen; - for (UndoOrderData& i: us.ord) { - e->song.orders.ord[i.chan][i.ord]=i.newVal; - } - break; - case GUI_UNDO_PATTERN_EDIT: - case GUI_UNDO_PATTERN_DELETE: - case GUI_UNDO_PATTERN_PULL: - case GUI_UNDO_PATTERN_PUSH: - case GUI_UNDO_PATTERN_CUT: - case GUI_UNDO_PATTERN_PASTE: - case GUI_UNDO_PATTERN_CHANGE_INS: - case GUI_UNDO_PATTERN_INTERPOLATE: - case GUI_UNDO_PATTERN_FADE: - case GUI_UNDO_PATTERN_SCALE: - case GUI_UNDO_PATTERN_RANDOMIZE: - case GUI_UNDO_PATTERN_INVERT_VAL: - case GUI_UNDO_PATTERN_FLIP: - case GUI_UNDO_PATTERN_COLLAPSE: - case GUI_UNDO_PATTERN_EXPAND: - for (UndoPatternData& i: us.pat) { - DivPattern* p=e->song.pat[i.chan].getPattern(i.pat,true); - p->data[i.row][i.col]=i.newVal; - } - if (!e->isPlaying()) { - cursor=us.cursor; - selStart=us.selStart; - selEnd=us.selEnd; - curNibble=us.nibble; - updateScroll(cursor.y); - e->setOrder(us.order); - } - - break; - } - - redoHist.pop_back(); -} - void FurnaceGUI::play(int row) { e->walkSong(loopOrder,loopRow,loopEnd); memset(lastIns,-1,sizeof(int)*DIV_MAX_CHANS); @@ -3373,670 +725,6 @@ void FurnaceGUI::stopPreviewNote(SDL_Scancode scancode) { } } -void FurnaceGUI::doAction(int what) { - switch (what) { - case GUI_ACTION_OPEN: - if (modified) { - showWarning("Unsaved changes! Are you sure?",GUI_WARN_OPEN); - } else { - openFileDialog(GUI_FILE_OPEN); - } - break; - case GUI_ACTION_OPEN_BACKUP: - if (modified) { - showWarning("Unsaved changes! Are you sure?",GUI_WARN_OPEN_BACKUP); - } else { - if (load(backupPath)>0) { - showError("No backup available! (or unable to open it)"); - } - } - break; - case GUI_ACTION_SAVE: - if (curFileName=="" || curFileName==backupPath || e->song.version>=0xff00) { - openFileDialog(GUI_FILE_SAVE); - } else { - if (save(curFileName,e->song.isDMF?e->song.version:0)>0) { - showError(fmt::sprintf("Error while saving file! (%s)",lastError)); - } - } - break; - case GUI_ACTION_SAVE_AS: - openFileDialog(GUI_FILE_SAVE); - break; - case GUI_ACTION_UNDO: - doUndo(); - break; - case GUI_ACTION_REDO: - doRedo(); - break; - case GUI_ACTION_PLAY_TOGGLE: - if (e->isPlaying() && !e->isStepping()) { - stop(); - } else { - play(); - } - break; - case GUI_ACTION_PLAY: - play(); - break; - case GUI_ACTION_STOP: - stop(); - break; - case GUI_ACTION_PLAY_REPEAT: - play(); - e->setRepeatPattern(true); - break; - case GUI_ACTION_PLAY_CURSOR: - if (e->isPlaying() && !e->isStepping()) { - stop(); - } else { - play(cursor.y); - } - break; - case GUI_ACTION_STEP_ONE: - e->stepOne(cursor.y); - break; - case GUI_ACTION_OCTAVE_UP: - if (++curOctave>7) { - curOctave=7; - } else { - for (size_t i=0; inoteOff(activeNotes[i].chan); - } - activeNotes.clear(); - } - break; - case GUI_ACTION_OCTAVE_DOWN: - if (--curOctave<-5) { - curOctave=-5; - } else { - for (size_t i=0; inoteOff(activeNotes[i].chan); - } - activeNotes.clear(); - } - break; - case GUI_ACTION_INS_UP: - if (--curIns<-1) curIns=-1; - break; - case GUI_ACTION_INS_DOWN: - if (++curIns>=(int)e->song.ins.size()) curIns=((int)e->song.ins.size())-1; - break; - case GUI_ACTION_STEP_UP: - if (++editStep>64) editStep=64; - break; - case GUI_ACTION_STEP_DOWN: - if (--editStep<0) editStep=0; - break; - case GUI_ACTION_TOGGLE_EDIT: - edit=!edit; - break; - case GUI_ACTION_METRONOME: - e->setMetronome(!e->getMetronome()); - break; - case GUI_ACTION_REPEAT_PATTERN: - e->setRepeatPattern(!e->getRepeatPattern()); - break; - case GUI_ACTION_FOLLOW_ORDERS: - followOrders=!followOrders; - break; - case GUI_ACTION_FOLLOW_PATTERN: - followPattern=!followPattern; - break; - case GUI_ACTION_PANIC: - e->syncReset(); - break; - - case GUI_ACTION_WINDOW_EDIT_CONTROLS: - nextWindow=GUI_WINDOW_EDIT_CONTROLS; - break; - case GUI_ACTION_WINDOW_ORDERS: - nextWindow=GUI_WINDOW_ORDERS; - break; - case GUI_ACTION_WINDOW_INS_LIST: - nextWindow=GUI_WINDOW_INS_LIST; - break; - case GUI_ACTION_WINDOW_INS_EDIT: - nextWindow=GUI_WINDOW_INS_EDIT; - break; - case GUI_ACTION_WINDOW_SONG_INFO: - nextWindow=GUI_WINDOW_SONG_INFO; - break; - case GUI_ACTION_WINDOW_PATTERN: - nextWindow=GUI_WINDOW_PATTERN; - break; - case GUI_ACTION_WINDOW_WAVE_LIST: - nextWindow=GUI_WINDOW_WAVE_LIST; - break; - case GUI_ACTION_WINDOW_WAVE_EDIT: - nextWindow=GUI_WINDOW_WAVE_EDIT; - break; - case GUI_ACTION_WINDOW_SAMPLE_LIST: - nextWindow=GUI_WINDOW_SAMPLE_LIST; - break; - case GUI_ACTION_WINDOW_SAMPLE_EDIT: - nextWindow=GUI_WINDOW_SAMPLE_EDIT; - break; - case GUI_ACTION_WINDOW_ABOUT: - nextWindow=GUI_WINDOW_ABOUT; - break; - case GUI_ACTION_WINDOW_SETTINGS: - nextWindow=GUI_WINDOW_SETTINGS; - break; - case GUI_ACTION_WINDOW_MIXER: - nextWindow=GUI_WINDOW_MIXER; - break; - case GUI_ACTION_WINDOW_DEBUG: - nextWindow=GUI_WINDOW_DEBUG; - break; - case GUI_ACTION_WINDOW_OSCILLOSCOPE: - nextWindow=GUI_WINDOW_OSCILLOSCOPE; - break; - case GUI_ACTION_WINDOW_VOL_METER: - nextWindow=GUI_WINDOW_VOL_METER; - break; - case GUI_ACTION_WINDOW_STATS: - nextWindow=GUI_WINDOW_STATS; - break; - case GUI_ACTION_WINDOW_COMPAT_FLAGS: - nextWindow=GUI_WINDOW_COMPAT_FLAGS; - break; - case GUI_ACTION_WINDOW_PIANO: - nextWindow=GUI_WINDOW_PIANO; - break; - case GUI_ACTION_WINDOW_NOTES: - nextWindow=GUI_WINDOW_NOTES; - break; - case GUI_ACTION_WINDOW_CHANNELS: - nextWindow=GUI_WINDOW_CHANNELS; - break; - case GUI_ACTION_WINDOW_REGISTER_VIEW: - nextWindow=GUI_WINDOW_REGISTER_VIEW; - break; - - case GUI_ACTION_COLLAPSE_WINDOW: - collapseWindow=true; - break; - case GUI_ACTION_CLOSE_WINDOW: - switch (curWindow) { - case GUI_WINDOW_EDIT_CONTROLS: - editControlsOpen=false; - break; - case GUI_WINDOW_SONG_INFO: - songInfoOpen=false; - break; - case GUI_WINDOW_ORDERS: - ordersOpen=false; - break; - case GUI_WINDOW_INS_LIST: - insListOpen=false; - break; - case GUI_WINDOW_PATTERN: - patternOpen=false; - break; - case GUI_WINDOW_INS_EDIT: - insEditOpen=false; - break; - case GUI_WINDOW_WAVE_LIST: - waveListOpen=false; - break; - case GUI_WINDOW_WAVE_EDIT: - waveEditOpen=false; - break; - case GUI_WINDOW_SAMPLE_LIST: - sampleListOpen=false; - break; - case GUI_WINDOW_SAMPLE_EDIT: - sampleEditOpen=false; - break; - case GUI_WINDOW_MIXER: - mixerOpen=false; - break; - case GUI_WINDOW_ABOUT: - aboutOpen=false; - break; - case GUI_WINDOW_SETTINGS: - settingsOpen=false; - break; - case GUI_WINDOW_DEBUG: - debugOpen=false; - break; - case GUI_WINDOW_OSCILLOSCOPE: - oscOpen=false; - break; - case GUI_WINDOW_VOL_METER: - volMeterOpen=false; - break; - case GUI_WINDOW_STATS: - statsOpen=false; - break; - case GUI_WINDOW_COMPAT_FLAGS: - compatFlagsOpen=false; - break; - case GUI_WINDOW_PIANO: - pianoOpen=false; - break; - case GUI_WINDOW_NOTES: - notesOpen=false; - break; - case GUI_WINDOW_CHANNELS: - channelsOpen=false; - break; - case GUI_WINDOW_REGISTER_VIEW: - regViewOpen=false; - break; - default: - break; - } - curWindow=GUI_WINDOW_NOTHING; - break; - - case GUI_ACTION_PAT_NOTE_UP: - doTranspose(1); - break; - case GUI_ACTION_PAT_NOTE_DOWN: - doTranspose(-1); - break; - case GUI_ACTION_PAT_OCTAVE_UP: - doTranspose(12); - break; - case GUI_ACTION_PAT_OCTAVE_DOWN: - doTranspose(-12); - break; - case GUI_ACTION_PAT_SELECT_ALL: - doSelectAll(); - break; - case GUI_ACTION_PAT_CUT: - doCopy(true); - break; - case GUI_ACTION_PAT_COPY: - doCopy(false); - break; - case GUI_ACTION_PAT_PASTE: - doPaste(); - break; - case GUI_ACTION_PAT_CURSOR_UP: - moveCursor(0,-MAX(1,settings.scrollStep?editStep:1),false); - break; - case GUI_ACTION_PAT_CURSOR_DOWN: - moveCursor(0,MAX(1,settings.scrollStep?editStep:1),false); - break; - case GUI_ACTION_PAT_CURSOR_LEFT: - moveCursor(-1,0,false); - break; - case GUI_ACTION_PAT_CURSOR_RIGHT: - moveCursor(1,0,false); - break; - case GUI_ACTION_PAT_CURSOR_UP_ONE: - moveCursor(0,-1,false); - break; - case GUI_ACTION_PAT_CURSOR_DOWN_ONE: - moveCursor(0,1,false); - break; - case GUI_ACTION_PAT_CURSOR_LEFT_CHANNEL: - moveCursorPrevChannel(false); - break; - case GUI_ACTION_PAT_CURSOR_RIGHT_CHANNEL: - moveCursorNextChannel(false); - break; - case GUI_ACTION_PAT_CURSOR_NEXT_CHANNEL: - moveCursorNextChannel(true); - break; - case GUI_ACTION_PAT_CURSOR_PREVIOUS_CHANNEL: - moveCursorPrevChannel(true); - break; - case GUI_ACTION_PAT_CURSOR_BEGIN: - moveCursorTop(false); - break; - case GUI_ACTION_PAT_CURSOR_END: - moveCursorBottom(false); - break; - case GUI_ACTION_PAT_CURSOR_UP_COARSE: - moveCursor(0,-16,false); - break; - case GUI_ACTION_PAT_CURSOR_DOWN_COARSE: - moveCursor(0,16,false); - break; - case GUI_ACTION_PAT_SELECTION_UP: - moveCursor(0,-MAX(1,settings.scrollStep?editStep:1),true); - break; - case GUI_ACTION_PAT_SELECTION_DOWN: - moveCursor(0,MAX(1,settings.scrollStep?editStep:1),true); - break; - case GUI_ACTION_PAT_SELECTION_LEFT: - moveCursor(-1,0,true); - break; - case GUI_ACTION_PAT_SELECTION_RIGHT: - moveCursor(1,0,true); - break; - case GUI_ACTION_PAT_SELECTION_UP_ONE: - moveCursor(0,-1,true); - break; - case GUI_ACTION_PAT_SELECTION_DOWN_ONE: - moveCursor(0,1,true); - break; - case GUI_ACTION_PAT_SELECTION_BEGIN: - moveCursorTop(true); - break; - case GUI_ACTION_PAT_SELECTION_END: - moveCursorBottom(true); - break; - case GUI_ACTION_PAT_SELECTION_UP_COARSE: - moveCursor(0,-16,true); - break; - case GUI_ACTION_PAT_SELECTION_DOWN_COARSE: - moveCursor(0,16,true); - break; - case GUI_ACTION_PAT_DELETE: - doDelete(); - if (settings.stepOnDelete) { - moveCursor(0,editStep,false); - } - break; - case GUI_ACTION_PAT_PULL_DELETE: - doPullDelete(); - break; - case GUI_ACTION_PAT_INSERT: - doInsert(); - if (settings.stepOnInsert) { - moveCursor(0,editStep,false); - } - break; - case GUI_ACTION_PAT_MUTE_CURSOR: - if (cursor.xCoarse<0 || cursor.xCoarse>=e->getTotalChannelCount()) break; - e->toggleMute(cursor.xCoarse); - break; - case GUI_ACTION_PAT_SOLO_CURSOR: - if (cursor.xCoarse<0 || cursor.xCoarse>=e->getTotalChannelCount()) break; - e->toggleSolo(cursor.xCoarse); - break; - case GUI_ACTION_PAT_UNMUTE_ALL: - e->unmuteAll(); - break; - case GUI_ACTION_PAT_NEXT_ORDER: - if (e->getOrder()song.ordersLen-1) { - e->setOrder(e->getOrder()+1); - } - break; - case GUI_ACTION_PAT_PREV_ORDER: - if (e->getOrder()>0) { - e->setOrder(e->getOrder()-1); - } - break; - case GUI_ACTION_PAT_COLLAPSE: - if (cursor.xCoarse<0 || cursor.xCoarse>=e->getTotalChannelCount()) break; - e->song.chanCollapse[cursor.xCoarse]=!e->song.chanCollapse[cursor.xCoarse]; - break; - case GUI_ACTION_PAT_INCREASE_COLUMNS: - if (cursor.xCoarse<0 || cursor.xCoarse>=e->getTotalChannelCount()) break; - e->song.pat[cursor.xCoarse].effectRows++; - if (e->song.pat[cursor.xCoarse].effectRows>8) e->song.pat[cursor.xCoarse].effectRows=8; - break; - case GUI_ACTION_PAT_DECREASE_COLUMNS: - if (cursor.xCoarse<0 || cursor.xCoarse>=e->getTotalChannelCount()) break; - e->song.pat[cursor.xCoarse].effectRows--; - if (e->song.pat[cursor.xCoarse].effectRows<1) e->song.pat[cursor.xCoarse].effectRows=1; - break; - case GUI_ACTION_PAT_INTERPOLATE: - doInterpolate(); - break; - case GUI_ACTION_PAT_INVERT_VALUES: - doInvertValues(); - break; - case GUI_ACTION_PAT_FLIP_SELECTION: - doFlip(); - break; - case GUI_ACTION_PAT_COLLAPSE_ROWS: - doCollapse(2); - break; - case GUI_ACTION_PAT_EXPAND_ROWS: - doExpand(2); - break; - case GUI_ACTION_PAT_COLLAPSE_PAT: // TODO - break; - case GUI_ACTION_PAT_EXPAND_PAT: // TODO - break; - case GUI_ACTION_PAT_COLLAPSE_SONG: // TODO - break; - case GUI_ACTION_PAT_EXPAND_SONG: // TODO - break; - case GUI_ACTION_PAT_LATCH: // TODO - break; - - case GUI_ACTION_INS_LIST_ADD: - curIns=e->addInstrument(cursor.xCoarse); - MARK_MODIFIED; - break; - case GUI_ACTION_INS_LIST_DUPLICATE: - if (curIns>=0 && curIns<(int)e->song.ins.size()) { - int prevIns=curIns; - curIns=e->addInstrument(cursor.xCoarse); - (*e->song.ins[curIns])=(*e->song.ins[prevIns]); - MARK_MODIFIED; - } - break; - case GUI_ACTION_INS_LIST_OPEN: - openFileDialog(GUI_FILE_INS_OPEN); - break; - case GUI_ACTION_INS_LIST_SAVE: - if (curIns>=0 && curIns<(int)e->song.ins.size()) openFileDialog(GUI_FILE_INS_SAVE); - break; - case GUI_ACTION_INS_LIST_MOVE_UP: - if (e->moveInsUp(curIns)) curIns--; - break; - case GUI_ACTION_INS_LIST_MOVE_DOWN: - if (e->moveInsDown(curIns)) curIns++; - break; - case GUI_ACTION_INS_LIST_DELETE: - if (curIns>=0 && curIns<(int)e->song.ins.size()) { - e->delInstrument(curIns); - MARK_MODIFIED; - if (curIns>=(int)e->song.ins.size()) { - curIns--; - } - } - break; - case GUI_ACTION_INS_LIST_EDIT: - insEditOpen=true; - break; - case GUI_ACTION_INS_LIST_UP: - if (--curIns<0) curIns=0; - break; - case GUI_ACTION_INS_LIST_DOWN: - if (++curIns>=(int)e->song.ins.size()) curIns=((int)e->song.ins.size())-1; - break; - - case GUI_ACTION_WAVE_LIST_ADD: - curWave=e->addWave(); - MARK_MODIFIED; - break; - case GUI_ACTION_WAVE_LIST_DUPLICATE: - if (curWave>=0 && curWave<(int)e->song.wave.size()) { - int prevWave=curWave; - curWave=e->addWave(); - (*e->song.wave[curWave])=(*e->song.wave[prevWave]); - MARK_MODIFIED; - } - break; - case GUI_ACTION_WAVE_LIST_OPEN: - openFileDialog(GUI_FILE_WAVE_OPEN); - break; - case GUI_ACTION_WAVE_LIST_SAVE: - if (curWave>=0 && curWave<(int)e->song.wave.size()) openFileDialog(GUI_FILE_WAVE_SAVE); - break; - case GUI_ACTION_WAVE_LIST_MOVE_UP: - if (e->moveWaveUp(curWave)) curWave--; - break; - case GUI_ACTION_WAVE_LIST_MOVE_DOWN: - if (e->moveWaveDown(curWave)) curWave++; - break; - case GUI_ACTION_WAVE_LIST_DELETE: - if (curWave>=0 && curWave<(int)e->song.wave.size()) { - e->delWave(curWave); - MARK_MODIFIED; - if (curWave>=(int)e->song.wave.size()) { - curWave--; - } - } - break; - case GUI_ACTION_WAVE_LIST_EDIT: - waveEditOpen=true; - break; - case GUI_ACTION_WAVE_LIST_UP: - if (--curWave<0) curWave=0; - break; - case GUI_ACTION_WAVE_LIST_DOWN: - if (++curWave>=(int)e->song.wave.size()) curWave=((int)e->song.wave.size())-1; - break; - - case GUI_ACTION_SAMPLE_LIST_ADD: - curSample=e->addSample(); - MARK_MODIFIED; - break; - case GUI_ACTION_SAMPLE_LIST_OPEN: - openFileDialog(GUI_FILE_SAMPLE_OPEN); - break; - case GUI_ACTION_SAMPLE_LIST_SAVE: - if (curSample>=0 && curSample<(int)e->song.sample.size()) openFileDialog(GUI_FILE_SAMPLE_SAVE); - break; - case GUI_ACTION_SAMPLE_LIST_MOVE_UP: - if (e->moveSampleUp(curSample)) curSample--; - break; - case GUI_ACTION_SAMPLE_LIST_MOVE_DOWN: - if (e->moveSampleDown(curSample)) curSample++; - break; - case GUI_ACTION_SAMPLE_LIST_DELETE: - e->delSample(curSample); - MARK_MODIFIED; - if (curSample>=(int)e->song.sample.size()) { - curSample--; - } - break; - case GUI_ACTION_SAMPLE_LIST_EDIT: - sampleEditOpen=true; - break; - case GUI_ACTION_SAMPLE_LIST_UP: - if (--curSample<0) curSample=0; - break; - case GUI_ACTION_SAMPLE_LIST_DOWN: - if (++curSample>=(int)e->song.sample.size()) curSample=((int)e->song.sample.size())-1; - break; - case GUI_ACTION_SAMPLE_LIST_PREVIEW: - e->previewSample(curSample); - break; - case GUI_ACTION_SAMPLE_LIST_STOP_PREVIEW: - e->stopSamplePreview(); - break; - - case GUI_ACTION_ORDERS_UP: - if (e->getOrder()>0) { - e->setOrder(e->getOrder()-1); - } - break; - case GUI_ACTION_ORDERS_DOWN: - if (e->getOrder()song.ordersLen-1) { - e->setOrder(e->getOrder()+1); - } - break; - case GUI_ACTION_ORDERS_LEFT: { - DETERMINE_FIRST; - - do { - orderCursor--; - if (orderCursorsong.chanShow[orderCursor]); - break; - } - case GUI_ACTION_ORDERS_RIGHT: { - DETERMINE_LAST; - - do { - orderCursor++; - if (orderCursor>=lastChannel) { - orderCursor=lastChannel-1; - break; - } - } while (!e->song.chanShow[orderCursor]); - break; - } - case GUI_ACTION_ORDERS_INCREASE: { - if (orderCursor<0 || orderCursor>=e->getTotalChannelCount()) break; - int curOrder=e->getOrder(); - if (e->song.orders.ord[orderCursor][curOrder]<0x7f) { - e->song.orders.ord[orderCursor][curOrder]++; - } - break; - } - case GUI_ACTION_ORDERS_DECREASE: { - if (orderCursor<0 || orderCursor>=e->getTotalChannelCount()) break; - int curOrder=e->getOrder(); - if (e->song.orders.ord[orderCursor][curOrder]>0) { - e->song.orders.ord[orderCursor][curOrder]--; - } - break; - } - case GUI_ACTION_ORDERS_EDIT_MODE: - orderEditMode++; - if (orderEditMode>3) orderEditMode=0; - break; - case GUI_ACTION_ORDERS_LINK: - changeAllOrders=!changeAllOrders; - break; - case GUI_ACTION_ORDERS_ADD: - prepareUndo(GUI_UNDO_CHANGE_ORDER); - e->addOrder(false,false); - makeUndo(GUI_UNDO_CHANGE_ORDER); - break; - case GUI_ACTION_ORDERS_DUPLICATE: - prepareUndo(GUI_UNDO_CHANGE_ORDER); - e->addOrder(true,false); - makeUndo(GUI_UNDO_CHANGE_ORDER); - break; - case GUI_ACTION_ORDERS_DEEP_CLONE: - prepareUndo(GUI_UNDO_CHANGE_ORDER); - e->deepCloneOrder(false); - makeUndo(GUI_UNDO_CHANGE_ORDER); - if (!e->getWarnings().empty()) { - showWarning(e->getWarnings(),GUI_WARN_GENERIC); - } - break; - case GUI_ACTION_ORDERS_DUPLICATE_END: - prepareUndo(GUI_UNDO_CHANGE_ORDER); - e->addOrder(true,true); - makeUndo(GUI_UNDO_CHANGE_ORDER); - break; - case GUI_ACTION_ORDERS_DEEP_CLONE_END: - prepareUndo(GUI_UNDO_CHANGE_ORDER); - e->deepCloneOrder(true); - makeUndo(GUI_UNDO_CHANGE_ORDER); - if (!e->getWarnings().empty()) { - showWarning(e->getWarnings(),GUI_WARN_GENERIC); - } - break; - case GUI_ACTION_ORDERS_REMOVE: - prepareUndo(GUI_UNDO_CHANGE_ORDER); - e->deleteOrder(); - makeUndo(GUI_UNDO_CHANGE_ORDER); - break; - case GUI_ACTION_ORDERS_MOVE_UP: - prepareUndo(GUI_UNDO_CHANGE_ORDER); - e->moveOrderUp(); - makeUndo(GUI_UNDO_CHANGE_ORDER); - break; - case GUI_ACTION_ORDERS_MOVE_DOWN: - prepareUndo(GUI_UNDO_CHANGE_ORDER); - e->moveOrderDown(); - makeUndo(GUI_UNDO_CHANGE_ORDER); - break; - case GUI_ACTION_ORDERS_REPLAY: - e->setOrder(e->getOrder()); - break; - } -} - void FurnaceGUI::keyDown(SDL_Event& ev) { if (ImGuiFileDialog::Instance()->IsOpened()) return; if (aboutOpen) return; @@ -5262,398 +1950,15 @@ bool FurnaceGUI::loop() { } ImGui::Separator(); if (ImGui::BeginMenu("add system...")) { - sysAddOption(DIV_SYSTEM_YM2612); - sysAddOption(DIV_SYSTEM_YM2612_EXT); - sysAddOption(DIV_SYSTEM_SMS); - sysAddOption(DIV_SYSTEM_GB); - sysAddOption(DIV_SYSTEM_PCE); - sysAddOption(DIV_SYSTEM_NES); - sysAddOption(DIV_SYSTEM_C64_8580); - sysAddOption(DIV_SYSTEM_C64_6581); - sysAddOption(DIV_SYSTEM_YM2151); - sysAddOption(DIV_SYSTEM_SEGAPCM); - sysAddOption(DIV_SYSTEM_SEGAPCM_COMPAT); - sysAddOption(DIV_SYSTEM_YM2610); - sysAddOption(DIV_SYSTEM_YM2610_EXT); - sysAddOption(DIV_SYSTEM_YM2610_FULL); - sysAddOption(DIV_SYSTEM_YM2610_FULL_EXT); - sysAddOption(DIV_SYSTEM_YM2610B); - sysAddOption(DIV_SYSTEM_YM2610B_EXT); - sysAddOption(DIV_SYSTEM_AY8910); - sysAddOption(DIV_SYSTEM_AMIGA); - sysAddOption(DIV_SYSTEM_PCSPKR); - sysAddOption(DIV_SYSTEM_OPLL); - sysAddOption(DIV_SYSTEM_OPLL_DRUMS); - sysAddOption(DIV_SYSTEM_VRC7); - sysAddOption(DIV_SYSTEM_OPL); - sysAddOption(DIV_SYSTEM_OPL_DRUMS); - sysAddOption(DIV_SYSTEM_OPL2); - sysAddOption(DIV_SYSTEM_OPL2_DRUMS); - sysAddOption(DIV_SYSTEM_OPL3); - sysAddOption(DIV_SYSTEM_OPL3_DRUMS); - sysAddOption(DIV_SYSTEM_TIA); - sysAddOption(DIV_SYSTEM_SAA1099); - sysAddOption(DIV_SYSTEM_AY8930); - sysAddOption(DIV_SYSTEM_LYNX); - sysAddOption(DIV_SYSTEM_QSOUND); - sysAddOption(DIV_SYSTEM_X1_010); - sysAddOption(DIV_SYSTEM_SWAN); - sysAddOption(DIV_SYSTEM_VERA); - sysAddOption(DIV_SYSTEM_BUBSYS_WSG); - sysAddOption(DIV_SYSTEM_PET); + for (int j=0; availableSystems[j]; j++) { + sysAddOption((DivSystem)availableSystems[j]); + } ImGui::EndMenu(); } if (ImGui::BeginMenu("configure system...")) { for (int i=0; isong.systemLen; i++) { if (ImGui::TreeNode(fmt::sprintf("%d. %s##_SYSP%d",i+1,getSystemName(e->song.system[i]),i).c_str())) { - unsigned int flags=e->song.systemFlags[i]; - bool restart=settings.restartOnFlagChange; - bool sysPal=flags&1; - switch (e->song.system[i]) { - case DIV_SYSTEM_YM2612: - case DIV_SYSTEM_YM2612_EXT: { - if (ImGui::RadioButton("NTSC (7.67MHz)",(flags&3)==0)) { - e->setSysFlags(i,(flags&0x80000000)|0,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("PAL (7.61MHz)",(flags&3)==1)) { - e->setSysFlags(i,(flags&0x80000000)|1,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("FM Towns (8MHz)",(flags&3)==2)) { - e->setSysFlags(i,(flags&0x80000000)|2,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("AtGames Genesis (6.13MHz)",(flags&3)==3)) { - e->setSysFlags(i,(flags&0x80000000)|3,restart); - updateWindowTitle(); - } - bool ladder=flags&0x80000000; - if (ImGui::Checkbox("Enable DAC distortion",&ladder)) { - e->setSysFlags(i,(flags&(~0x80000000))|(ladder?0x80000000:0),restart); - updateWindowTitle(); - } - break; - } - case DIV_SYSTEM_SMS: { - ImGui::Text("Clock rate:"); - if (ImGui::RadioButton("NTSC (3.58MHz)",(flags&3)==0)) { - e->setSysFlags(i,(flags&(~3))|0,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("PAL (3.55MHz)",(flags&3)==1)) { - e->setSysFlags(i,(flags&(~3))|1,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("BBC Micro (4MHz)",(flags&3)==2)) { - e->setSysFlags(i,(flags&(~3))|2,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("Half NTSC (1.79MHz)",(flags&3)==3)) { - e->setSysFlags(i,(flags&(~3))|3,restart); - updateWindowTitle(); - } - ImGui::Text("Chip type:"); - if (ImGui::RadioButton("Sega VDP/Master System",((flags>>2)&3)==0)) { - e->setSysFlags(i,(flags&(~12))|0,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("TI SN76489",((flags>>2)&3)==1)) { - e->setSysFlags(i,(flags&(~12))|4,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("TI SN76489 with Atari-like short noise",((flags>>2)&3)==2)) { - e->setSysFlags(i,(flags&(~12))|8,restart); - updateWindowTitle(); - } - /*if (ImGui::RadioButton("Game Gear",(flags>>2)==3)) { - e->setSysFlags(i,(flags&3)|12); - }*/ - - bool noPhaseReset=flags&16; - if (ImGui::Checkbox("Disable noise period change phase reset",&noPhaseReset)) { - e->setSysFlags(i,(flags&(~16))|(noPhaseReset<<4),restart); - updateWindowTitle(); - } - break; - } - case DIV_SYSTEM_OPLL: - case DIV_SYSTEM_OPLL_DRUMS: - case DIV_SYSTEM_VRC7: { - ImGui::Text("Clock rate:"); - if (ImGui::RadioButton("NTSC (3.58MHz)",(flags&15)==0)) { - e->setSysFlags(i,(flags&(~15))|0,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("PAL (3.55MHz)",(flags&15)==1)) { - e->setSysFlags(i,(flags&(~15))|1,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("BBC Micro (4MHz)",(flags&15)==2)) { - e->setSysFlags(i,(flags&(~15))|2,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("Half NTSC (1.79MHz)",(flags&15)==3)) { - e->setSysFlags(i,(flags&(~15))|3,restart); - updateWindowTitle(); - } - if (e->song.system[i]!=DIV_SYSTEM_VRC7) { - ImGui::Text("Patch set:"); - if (ImGui::RadioButton("Yamaha YM2413",((flags>>4)&15)==0)) { - e->setSysFlags(i,(flags&(~0xf0))|0,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("Yamaha YMF281",((flags>>4)&15)==1)) { - e->setSysFlags(i,(flags&(~0xf0))|0x10,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("Yamaha YM2423",((flags>>4)&15)==2)) { - e->setSysFlags(i,(flags&(~0xf0))|0x20,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("Konami VRC7",((flags>>4)&15)==3)) { - e->setSysFlags(i,(flags&(~0xf0))|0x30,restart); - updateWindowTitle(); - } - } - break; - } - case DIV_SYSTEM_YM2151: - if (ImGui::RadioButton("NTSC/X16 (3.58MHz)",flags==0)) { - e->setSysFlags(i,0,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("PAL (3.55MHz)",flags==1)) { - e->setSysFlags(i,1,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("X1/X68000 (4MHz)",flags==2)) { - e->setSysFlags(i,2,restart); - updateWindowTitle(); - } - break; - case DIV_SYSTEM_NES: - if (ImGui::RadioButton("NTSC (1.79MHz)",flags==0)) { - e->setSysFlags(i,0,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("PAL (1.67MHz)",flags==1)) { - e->setSysFlags(i,1,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("Dendy (1.77MHz)",flags==2)) { - e->setSysFlags(i,2,restart); - updateWindowTitle(); - } - break; - case DIV_SYSTEM_C64_8580: - case DIV_SYSTEM_C64_6581: - if (ImGui::RadioButton("NTSC (1.02MHz)",flags==0)) { - e->setSysFlags(i,0,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("PAL (0.99MHz)",flags==1)) { - e->setSysFlags(i,1,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("SSI 2001 (0.89MHz)",flags==2)) { - e->setSysFlags(i,2,restart); - updateWindowTitle(); - } - break; - case DIV_SYSTEM_AY8910: - case DIV_SYSTEM_AY8930: { - ImGui::Text("Clock rate:"); - if (ImGui::RadioButton("1.79MHz (ZX Spectrum NTSC/MSX)",(flags&15)==0)) { - e->setSysFlags(i,(flags&(~15))|0,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("1.77MHz (ZX Spectrum)",(flags&15)==1)) { - e->setSysFlags(i,(flags&(~15))|1,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("1.75MHz (ZX Spectrum)",(flags&15)==2)) { - e->setSysFlags(i,(flags&(~15))|2,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("2MHz (Atari ST/Sharp X1)",(flags&15)==3)) { - e->setSysFlags(i,(flags&(~15))|3,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("1.5MHz (Vectrex)",(flags&15)==4)) { - e->setSysFlags(i,(flags&(~15))|4,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("1MHz (Amstrad CPC)",(flags&15)==5)) { - e->setSysFlags(i,(flags&(~15))|5,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("0.89MHz (Sunsoft 5B)",(flags&15)==6)) { - e->setSysFlags(i,(flags&(~15))|6,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("1.67MHz (?)",(flags&15)==7)) { - e->setSysFlags(i,(flags&(~15))|7,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("0.83MHz (Sunsoft 5B on PAL)",(flags&15)==8)) { - e->setSysFlags(i,(flags&(~15))|8,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("1.10MHz (Gamate/VIC-20 PAL)",(flags&15)==9)) { - e->setSysFlags(i,(flags&(~15))|9,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("2^21Hz (Game Boy)",(flags&15)==10)) { - e->setSysFlags(i,(flags&(~15))|10,restart); - updateWindowTitle(); - } - if (e->song.system[i]==DIV_SYSTEM_AY8910) { - ImGui::Text("Chip type:"); - if (ImGui::RadioButton("AY-3-8910",(flags&0x30)==0)) { - e->setSysFlags(i,(flags&(~0x30))|0,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("YM2149(F)",(flags&0x30)==16)) { - e->setSysFlags(i,(flags&(~0x30))|16,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("Sunsoft 5B",(flags&0x30)==32)) { - e->setSysFlags(i,(flags&(~0x30))|32,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("AY-3-8914",(flags&0x30)==48)) { - e->setSysFlags(i,(flags&(~0x30))|48,restart); - updateWindowTitle(); - } - } - bool stereo=flags&0x40; - ImGui::BeginDisabled((flags&0x30)==32); - if (ImGui::Checkbox("Stereo##_AY_STEREO",&stereo)) { - e->setSysFlags(i,(flags&(~0x40))|(stereo?0x40:0),restart); - updateWindowTitle(); - } - ImGui::EndDisabled(); - break; - } - case DIV_SYSTEM_SAA1099: - if (ImGui::RadioButton("SAM Coupé (8MHz)",flags==0)) { - e->setSysFlags(i,0,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("NTSC (7.15MHz)",flags==1)) { - e->setSysFlags(i,1,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("PAL (7.09MHz)",flags==2)) { - e->setSysFlags(i,2,restart); - updateWindowTitle(); - } - break; - case DIV_SYSTEM_AMIGA: { - ImGui::Text("Stereo separation:"); - int stereoSep=(flags>>8)&127; - if (ImGui::SliderInt("##StereoSep",&stereoSep,0,127)) { - if (stereoSep<0) stereoSep=0; - if (stereoSep>127) stereoSep=127; - e->setSysFlags(i,(flags&(~0x7f00))|((stereoSep&127)<<8),restart); - updateWindowTitle(); - } rightClickable - if (ImGui::RadioButton("Amiga 500 (OCS)",(flags&2)==0)) { - e->setSysFlags(i,flags&(~2),restart); - } - if (ImGui::RadioButton("Amiga 1200 (AGA)",(flags&2)==2)) { - e->setSysFlags(i,(flags&(~2))|2,restart); - } - sysPal=flags&1; - if (ImGui::Checkbox("PAL",&sysPal)) { - e->setSysFlags(i,(flags&(~1))|sysPal,restart); - updateWindowTitle(); - } - bool bypassLimits=flags&4; - if (ImGui::Checkbox("Bypass frequency limits",&bypassLimits)) { - e->setSysFlags(i,(flags&(~4))|(bypassLimits<<2),restart); - updateWindowTitle(); - } - break; - } - case DIV_SYSTEM_PCSPKR: { - ImGui::Text("Speaker type:"); - if (ImGui::RadioButton("Unfiltered",(flags&3)==0)) { - e->setSysFlags(i,(flags&(~3))|0,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("Cone",(flags&3)==1)) { - e->setSysFlags(i,(flags&(~3))|1,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("Piezo",(flags&3)==2)) { - e->setSysFlags(i,(flags&(~3))|2,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("Use system beeper (Linux only!)",(flags&3)==3)) { - e->setSysFlags(i,(flags&(~3))|3,restart); - updateWindowTitle(); - } - break; - } - case DIV_SYSTEM_QSOUND: { - ImGui::Text("Echo delay:"); - int echoBufSize=2725 - (flags & 4095); - if (ImGui::SliderInt("##EchoBufSize",&echoBufSize,0,2725)) { - if (echoBufSize<0) echoBufSize=0; - if (echoBufSize>2725) echoBufSize=2725; - e->setSysFlags(i,(flags & ~4095) | ((2725 - echoBufSize) & 4095),restart); - updateWindowTitle(); - } rightClickable - ImGui::Text("Echo feedback:"); - int echoFeedback=(flags>>12)&255; - if (ImGui::SliderInt("##EchoFeedback",&echoFeedback,0,255)) { - if (echoFeedback<0) echoFeedback=0; - if (echoFeedback>255) echoFeedback=255; - e->setSysFlags(i,(flags & ~(255 << 12)) | ((echoFeedback & 255) << 12),restart); - updateWindowTitle(); - } rightClickable - break; - } - case DIV_SYSTEM_X1_010: { - ImGui::Text("Clock rate:"); - if (ImGui::RadioButton("16MHz (Seta 1)",(flags&15)==0)) { - e->setSysFlags(i,(flags&(~15))|0,restart); - updateWindowTitle(); - } - if (ImGui::RadioButton("16.67MHz (Seta 2)",(flags&15)==1)) { - e->setSysFlags(i,(flags&(~15))|1,restart); - updateWindowTitle(); - } - bool x1_010Stereo=flags&16; - if (ImGui::Checkbox("Stereo",&x1_010Stereo)) { - e->setSysFlags(i,(flags&(~16))|(x1_010Stereo<<4),restart); - updateWindowTitle(); - } - break; - } - case DIV_SYSTEM_GB: - case DIV_SYSTEM_SWAN: - case DIV_SYSTEM_VERA: - case DIV_SYSTEM_BUBSYS_WSG: - case DIV_SYSTEM_YM2610: - case DIV_SYSTEM_YM2610_EXT: - case DIV_SYSTEM_YM2610_FULL: - case DIV_SYSTEM_YM2610_FULL_EXT: - case DIV_SYSTEM_YM2610B: - case DIV_SYSTEM_YM2610B_EXT: - case DIV_SYSTEM_YMU759: - case DIV_SYSTEM_PET: - ImGui::Text("nothing to configure"); - break; - default: - if (ImGui::Checkbox("PAL",&sysPal)) { - e->setSysFlags(i,sysPal,restart); - updateWindowTitle(); - } - break; - } + drawSysConf(i); ImGui::TreePop(); } } @@ -5662,45 +1967,9 @@ bool FurnaceGUI::loop() { if (ImGui::BeginMenu("change system...")) { for (int i=0; isong.systemLen; i++) { if (ImGui::BeginMenu(fmt::sprintf("%d. %s##_SYSC%d",i+1,getSystemName(e->song.system[i]),i).c_str())) { - sysChangeOption(i,DIV_SYSTEM_YM2612); - sysChangeOption(i,DIV_SYSTEM_YM2612_EXT); - sysChangeOption(i,DIV_SYSTEM_SMS); - sysChangeOption(i,DIV_SYSTEM_GB); - sysChangeOption(i,DIV_SYSTEM_PCE); - sysChangeOption(i,DIV_SYSTEM_NES); - sysChangeOption(i,DIV_SYSTEM_C64_8580); - sysChangeOption(i,DIV_SYSTEM_C64_6581); - sysChangeOption(i,DIV_SYSTEM_YM2151); - sysChangeOption(i,DIV_SYSTEM_SEGAPCM); - sysChangeOption(i,DIV_SYSTEM_SEGAPCM_COMPAT); - sysChangeOption(i,DIV_SYSTEM_YM2610); - sysChangeOption(i,DIV_SYSTEM_YM2610_EXT); - sysChangeOption(i,DIV_SYSTEM_YM2610_FULL); - sysChangeOption(i,DIV_SYSTEM_YM2610_FULL_EXT); - sysChangeOption(i,DIV_SYSTEM_YM2610B); - sysChangeOption(i,DIV_SYSTEM_YM2610B_EXT); - sysChangeOption(i,DIV_SYSTEM_AY8910); - sysChangeOption(i,DIV_SYSTEM_AMIGA); - sysChangeOption(i,DIV_SYSTEM_PCSPKR); - sysChangeOption(i,DIV_SYSTEM_OPLL); - sysChangeOption(i,DIV_SYSTEM_OPLL_DRUMS); - sysChangeOption(i,DIV_SYSTEM_VRC7); - sysChangeOption(i,DIV_SYSTEM_OPL); - sysChangeOption(i,DIV_SYSTEM_OPL_DRUMS); - sysChangeOption(i,DIV_SYSTEM_OPL2); - sysChangeOption(i,DIV_SYSTEM_OPL2_DRUMS); - sysChangeOption(i,DIV_SYSTEM_OPL3); - sysChangeOption(i,DIV_SYSTEM_OPL3_DRUMS); - sysChangeOption(i,DIV_SYSTEM_TIA); - sysChangeOption(i,DIV_SYSTEM_SAA1099); - sysChangeOption(i,DIV_SYSTEM_AY8930); - sysChangeOption(i,DIV_SYSTEM_LYNX); - sysChangeOption(i,DIV_SYSTEM_QSOUND); - sysChangeOption(i,DIV_SYSTEM_X1_010); - sysChangeOption(i,DIV_SYSTEM_SWAN); - sysChangeOption(i,DIV_SYSTEM_VERA); - sysChangeOption(i,DIV_SYSTEM_BUBSYS_WSG); - sysChangeOption(i,DIV_SYSTEM_PET); + for (int j=0; availableSystems[j]; j++) { + sysChangeOption(i,(DivSystem)availableSystems[j]); + } ImGui::EndMenu(); } } @@ -6204,379 +2473,6 @@ bool FurnaceGUI::loop() { return false; } -void FurnaceGUI::parseKeybinds() { - actionMapGlobal.clear(); - actionMapPat.clear(); - actionMapInsList.clear(); - actionMapWaveList.clear(); - actionMapSampleList.clear(); - actionMapOrders.clear(); - - for (int i=GUI_ACTION_GLOBAL_MIN+1; igetConfInt(#target,ImGui::GetColorU32(def))); - -#ifdef _WIN32 -#define SYSTEM_FONT_PATH_1 "C:\\Windows\\Fonts\\segoeui.ttf" -#define SYSTEM_FONT_PATH_2 "C:\\Windows\\Fonts\\tahoma.ttf" -// TODO! -#define SYSTEM_FONT_PATH_3 "C:\\Windows\\Fonts\\tahoma.ttf" -// TODO! -#define SYSTEM_PAT_FONT_PATH_1 "C:\\Windows\\Fonts\\consola.ttf" -#define SYSTEM_PAT_FONT_PATH_2 "C:\\Windows\\Fonts\\cour.ttf" -// GOOD LUCK WITH THIS ONE - UNTESTED -#define SYSTEM_PAT_FONT_PATH_3 "C:\\Windows\\Fonts\\vgasys.fon" -#elif defined(__APPLE__) -#define SYSTEM_FONT_PATH_1 "/System/Library/Fonts/SFAANS.ttf" -#define SYSTEM_FONT_PATH_2 "/System/Library/Fonts/Helvetica.ttc" -#define SYSTEM_FONT_PATH_3 "/System/Library/Fonts/Helvetica.dfont" -#define SYSTEM_PAT_FONT_PATH_1 "/System/Library/Fonts/SFNSMono.ttf" -#define SYSTEM_PAT_FONT_PATH_2 "/System/Library/Fonts/Courier New.ttf" -#define SYSTEM_PAT_FONT_PATH_3 "/System/Library/Fonts/Courier New.ttf" -#else -#define SYSTEM_FONT_PATH_1 "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf" -#define SYSTEM_FONT_PATH_2 "/usr/share/fonts/TTF/DejaVuSans.ttf" -#define SYSTEM_FONT_PATH_3 "/usr/share/fonts/ubuntu/Ubuntu-R.ttf" -#define SYSTEM_PAT_FONT_PATH_1 "/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf" -#define SYSTEM_PAT_FONT_PATH_2 "/usr/share/fonts/TTF/DejaVuSansMono.ttf" -#define SYSTEM_PAT_FONT_PATH_3 "/usr/share/fonts/ubuntu/UbuntuMono-R.ttf" -#endif - -void FurnaceGUI::applyUISettings() { - ImGuiStyle sty; - if (settings.guiColorsBase) { - ImGui::StyleColorsLight(&sty); - } else { - ImGui::StyleColorsDark(&sty); - } - - if (settings.dpiScale>=0.5f) dpiScale=settings.dpiScale; - - GET_UI_COLOR(GUI_COLOR_BACKGROUND,ImVec4(0.1f,0.1f,0.1f,1.0f)); - GET_UI_COLOR(GUI_COLOR_FRAME_BACKGROUND,ImVec4(0.0f,0.0f,0.0f,0.85f)); - GET_UI_COLOR(GUI_COLOR_MODAL_BACKDROP,ImVec4(0.0f,0.0f,0.0f,0.55f)); - GET_UI_COLOR(GUI_COLOR_HEADER,ImVec4(0.2f,0.2f,0.2f,1.0f)); - GET_UI_COLOR(GUI_COLOR_TEXT,ImVec4(1.0f,1.0f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_ACCENT_PRIMARY,ImVec4(0.06f,0.53f,0.98f,1.0f)); - GET_UI_COLOR(GUI_COLOR_ACCENT_SECONDARY,ImVec4(0.26f,0.59f,0.98f,1.0f)); - GET_UI_COLOR(GUI_COLOR_EDITING,ImVec4(0.2f,0.1f,0.1f,1.0f)); - GET_UI_COLOR(GUI_COLOR_SONG_LOOP,ImVec4(0.3f,0.5f,0.8f,0.4f)); - GET_UI_COLOR(GUI_COLOR_VOLMETER_LOW,ImVec4(0.2f,0.6f,0.2f,1.0f)); - GET_UI_COLOR(GUI_COLOR_VOLMETER_HIGH,ImVec4(1.0f,0.9f,0.2f,1.0f)); - GET_UI_COLOR(GUI_COLOR_VOLMETER_PEAK,ImVec4(1.0f,0.1f,0.1f,1.0f)); - GET_UI_COLOR(GUI_COLOR_MACRO_VOLUME,ImVec4(0.2f,1.0f,0.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_MACRO_PITCH,ImVec4(1.0f,0.8f,0.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_MACRO_OTHER,ImVec4(0.0f,0.9f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_MACRO_WAVE,ImVec4(1.0f,0.4f,0.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_FM,ImVec4(0.6f,0.9f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_STD,ImVec4(0.6f,1.0f,0.5f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_GB,ImVec4(1.0f,1.0f,0.5f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_C64,ImVec4(0.85f,0.8f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_AMIGA,ImVec4(1.0f,0.5f,0.5f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_PCE,ImVec4(1.0f,0.8f,0.5f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_AY,ImVec4(1.0f,0.5f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_AY8930,ImVec4(0.7f,0.5f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_TIA,ImVec4(1.0f,0.6f,0.4f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_SAA1099,ImVec4(0.3f,0.3f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_VIC,ImVec4(0.2f,1.0f,0.6f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_PET,ImVec4(1.0f,1.0f,0.8f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_VRC6,ImVec4(1.0f,0.9f,0.5f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_OPLL,ImVec4(0.6f,0.7f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_OPL,ImVec4(0.3f,1.0f,0.9f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_FDS,ImVec4(0.8f,0.5f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_VBOY,ImVec4(1.0f,0.1f,0.1f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_N163,ImVec4(1.0f,0.4f,0.1f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_SCC,ImVec4(0.7f,1.0f,0.3f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_OPZ,ImVec4(0.2f,0.8f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_POKEY,ImVec4(0.5f,1.0f,0.3f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_BEEPER,ImVec4(0.0f,1.0f,0.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_SWAN,ImVec4(0.3f,0.5f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_MIKEY,ImVec4(0.5f,1.0f,0.3f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_VERA,ImVec4(0.4f,0.6f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_X1_010,ImVec4(0.3f,0.5f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_UNKNOWN,ImVec4(0.3f,0.3f,0.3f,1.0f)); - GET_UI_COLOR(GUI_COLOR_CHANNEL_FM,ImVec4(0.2f,0.8f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_CHANNEL_PULSE,ImVec4(0.4f,1.0f,0.2f,1.0f)); - GET_UI_COLOR(GUI_COLOR_CHANNEL_NOISE,ImVec4(0.8f,0.8f,0.8f,1.0f)); - GET_UI_COLOR(GUI_COLOR_CHANNEL_PCM,ImVec4(1.0f,0.9f,0.2f,1.0f)); - GET_UI_COLOR(GUI_COLOR_CHANNEL_WAVE,ImVec4(1.0f,0.5f,0.2f,1.0f)); - GET_UI_COLOR(GUI_COLOR_CHANNEL_OP,ImVec4(0.2f,0.4f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_CHANNEL_MUTED,ImVec4(0.5f,0.5f,0.5f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_CURSOR,ImVec4(0.1f,0.3f,0.5f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_CURSOR_HOVER,ImVec4(0.2f,0.4f,0.6f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_CURSOR_ACTIVE,ImVec4(0.2f,0.5f,0.7f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_SELECTION,ImVec4(0.15f,0.15f,0.2f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_SELECTION_HOVER,ImVec4(0.2f,0.2f,0.3f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_SELECTION_ACTIVE,ImVec4(0.4f,0.4f,0.5f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_HI_1,ImVec4(0.6f,0.6f,0.6f,0.2f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_HI_2,ImVec4(0.5f,0.8f,1.0f,0.2f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_ROW_INDEX,ImVec4(0.5f,0.8f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_ACTIVE,ImVec4(1.0f,1.0f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_INACTIVE,ImVec4(0.5f,0.5f,0.5f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_INS,ImVec4(0.4f,0.7f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_VOLUME_MIN,ImVec4(0.0f,0.5f,0.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_VOLUME_HALF,ImVec4(0.0f,0.75f,0.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_VOLUME_MAX,ImVec4(0.0f,1.0f,0.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_INVALID,ImVec4(1.0f,0.0f,0.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_PITCH,ImVec4(1.0f,1.0f,0.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_VOLUME,ImVec4(0.0f,1.0f,0.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_PANNING,ImVec4(0.0f,1.0f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_SONG,ImVec4(1.0f,0.0f,0.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_TIME,ImVec4(0.5f,0.0f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_SPEED,ImVec4(1.0f,0.0f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,ImVec4(0.5f,1.0f,0.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_SYS_SECONDARY,ImVec4(0.0f,1.0f,0.5f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_MISC,ImVec4(0.3f,0.3f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_EE_VALUE,ImVec4(0.0f,1.0f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PLAYBACK_STAT,ImVec4(0.6f,0.6f,0.6f,1.0f)); - - for (int i=0; i<64; i++) { - ImVec4 col1=uiColors[GUI_COLOR_PATTERN_VOLUME_MIN]; - ImVec4 col2=uiColors[GUI_COLOR_PATTERN_VOLUME_HALF]; - ImVec4 col3=uiColors[GUI_COLOR_PATTERN_VOLUME_MAX]; - volColors[i]=ImVec4(col1.x+((col2.x-col1.x)*float(i)/64.0f), - col1.y+((col2.y-col1.y)*float(i)/64.0f), - col1.z+((col2.z-col1.z)*float(i)/64.0f), - 1.0f); - volColors[i+64]=ImVec4(col2.x+((col3.x-col2.x)*float(i)/64.0f), - col2.y+((col3.y-col2.y)*float(i)/64.0f), - col2.z+((col3.z-col2.z)*float(i)/64.0f), - 1.0f); - } - - float hue, sat, val; - - ImVec4 primaryActive=uiColors[GUI_COLOR_ACCENT_PRIMARY]; - ImVec4 primaryHover, primary; - primaryHover.w=primaryActive.w; - primary.w=primaryActive.w; - ImGui::ColorConvertRGBtoHSV(primaryActive.x,primaryActive.y,primaryActive.z,hue,sat,val); - if (settings.guiColorsBase) { - primary=primaryActive; - ImGui::ColorConvertHSVtoRGB(hue,sat*0.9,val*0.9,primaryHover.x,primaryHover.y,primaryHover.z); - ImGui::ColorConvertHSVtoRGB(hue,sat,val*0.5,primaryActive.x,primaryActive.y,primaryActive.z); - } else { - ImGui::ColorConvertHSVtoRGB(hue,sat*0.9,val*0.5,primaryHover.x,primaryHover.y,primaryHover.z); - ImGui::ColorConvertHSVtoRGB(hue,sat*0.8,val*0.35,primary.x,primary.y,primary.z); - } - - ImVec4 secondaryActive=uiColors[GUI_COLOR_ACCENT_SECONDARY]; - ImVec4 secondaryHover, secondary, secondarySemiActive; - secondarySemiActive.w=secondaryActive.w; - secondaryHover.w=secondaryActive.w; - secondary.w=secondaryActive.w; - ImGui::ColorConvertRGBtoHSV(secondaryActive.x,secondaryActive.y,secondaryActive.z,hue,sat,val); - if (settings.guiColorsBase) { - secondary=secondaryActive; - ImGui::ColorConvertHSVtoRGB(hue,sat*0.9,val*0.7,secondarySemiActive.x,secondarySemiActive.y,secondarySemiActive.z); - ImGui::ColorConvertHSVtoRGB(hue,sat*0.9,val*0.9,secondaryHover.x,secondaryHover.y,secondaryHover.z); - ImGui::ColorConvertHSVtoRGB(hue,sat,val*0.5,secondaryActive.x,secondaryActive.y,secondaryActive.z); - } else { - ImGui::ColorConvertHSVtoRGB(hue,sat*0.9,val*0.75,secondarySemiActive.x,secondarySemiActive.y,secondarySemiActive.z); - ImGui::ColorConvertHSVtoRGB(hue,sat*0.9,val*0.5,secondaryHover.x,secondaryHover.y,secondaryHover.z); - ImGui::ColorConvertHSVtoRGB(hue,sat*0.9,val*0.25,secondary.x,secondary.y,secondary.z); - } - - - sty.Colors[ImGuiCol_WindowBg]=uiColors[GUI_COLOR_FRAME_BACKGROUND]; - sty.Colors[ImGuiCol_ModalWindowDimBg]=uiColors[GUI_COLOR_MODAL_BACKDROP]; - sty.Colors[ImGuiCol_Text]=uiColors[GUI_COLOR_TEXT]; - - sty.Colors[ImGuiCol_Button]=primary; - sty.Colors[ImGuiCol_ButtonHovered]=primaryHover; - sty.Colors[ImGuiCol_ButtonActive]=primaryActive; - sty.Colors[ImGuiCol_Tab]=primary; - sty.Colors[ImGuiCol_TabHovered]=secondaryHover; - sty.Colors[ImGuiCol_TabActive]=secondarySemiActive; - sty.Colors[ImGuiCol_TabUnfocused]=primary; - sty.Colors[ImGuiCol_TabUnfocusedActive]=primaryHover; - sty.Colors[ImGuiCol_Header]=secondary; - sty.Colors[ImGuiCol_HeaderHovered]=secondaryHover; - sty.Colors[ImGuiCol_HeaderActive]=secondaryActive; - sty.Colors[ImGuiCol_ResizeGrip]=secondary; - sty.Colors[ImGuiCol_ResizeGripHovered]=secondaryHover; - sty.Colors[ImGuiCol_ResizeGripActive]=secondaryActive; - sty.Colors[ImGuiCol_FrameBg]=secondary; - sty.Colors[ImGuiCol_FrameBgHovered]=secondaryHover; - sty.Colors[ImGuiCol_FrameBgActive]=secondaryActive; - sty.Colors[ImGuiCol_SliderGrab]=primaryActive; - sty.Colors[ImGuiCol_SliderGrabActive]=primaryActive; - sty.Colors[ImGuiCol_TitleBgActive]=primary; - sty.Colors[ImGuiCol_CheckMark]=primaryActive; - sty.Colors[ImGuiCol_TextSelectedBg]=secondaryHover; - sty.Colors[ImGuiCol_PlotHistogram]=uiColors[GUI_COLOR_MACRO_OTHER]; - sty.Colors[ImGuiCol_PlotHistogramHovered]=uiColors[GUI_COLOR_MACRO_OTHER]; - - if (settings.roundedWindows) sty.WindowRounding=8.0f; - if (settings.roundedButtons) { - sty.FrameRounding=6.0f; - sty.GrabRounding=6.0f; - } - if (settings.roundedMenus) sty.PopupRounding=8.0f; - - sty.ScaleAllSizes(dpiScale); - - ImGui::GetStyle()=sty; - - for (int i=0; i<256; i++) { - ImVec4& base=uiColors[GUI_COLOR_PATTERN_EFFECT_PITCH]; - pitchGrad[i]=ImGui::GetColorU32(ImVec4(base.x,base.y,base.z,((float)i/255.0f)*base.w)); - } - for (int i=0; i<256; i++) { - ImVec4& base=uiColors[GUI_COLOR_PATTERN_ACTIVE]; - noteGrad[i]=ImGui::GetColorU32(ImVec4(base.x,base.y,base.z,((float)i/255.0f)*base.w)); - } - for (int i=0; i<256; i++) { - ImVec4& base=uiColors[GUI_COLOR_PATTERN_EFFECT_PANNING]; - panGrad[i]=ImGui::GetColorU32(ImVec4(base.x,base.y,base.z,((float)i/255.0f)*base.w)); - } - for (int i=0; i<256; i++) { - ImVec4& base=uiColors[GUI_COLOR_PATTERN_INS]; - insGrad[i]=ImGui::GetColorU32(ImVec4(base.x,base.y,base.z,((float)i/255.0f)*base.w)); - } - for (int i=0; i<256; i++) { - ImVec4& base=volColors[i/2]; - volGrad[i]=ImGui::GetColorU32(ImVec4(base.x,base.y,base.z,((float)i/255.0f)*base.w)); - } - for (int i=0; i<256; i++) { - ImVec4& base=uiColors[GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY]; - sysCmd1Grad[i]=ImGui::GetColorU32(ImVec4(base.x,base.y,base.z,((float)i/255.0f)*base.w)); - } - for (int i=0; i<256; i++) { - ImVec4& base=uiColors[GUI_COLOR_PATTERN_EFFECT_SYS_SECONDARY]; - sysCmd2Grad[i]=ImGui::GetColorU32(ImVec4(base.x,base.y,base.z,((float)i/255.0f)*base.w)); - } - - // set to 800 for now due to problems with unifont - static const ImWchar loadEverything[]={0x20,0x800,0}; - - if (settings.mainFont<0 || settings.mainFont>6) settings.mainFont=0; - if (settings.patFont<0 || settings.patFont>6) settings.patFont=0; - - if (settings.mainFont==6 && settings.mainFontPath.empty()) { - logW("UI font path is empty! reverting to default font\n"); - settings.mainFont=0; - } - if (settings.patFont==6 && settings.patFontPath.empty()) { - logW("pattern font path is empty! reverting to default font\n"); - settings.patFont=0; - } - - if (settings.mainFont==6) { // custom font - if ((mainFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(settings.mainFontPath.c_str(),e->getConfInt("mainFontSize",18)*dpiScale,NULL,loadEverything))==NULL) { - logW("could not load UI font! reverting to default font\n"); - settings.mainFont=0; - if ((mainFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFont[settings.mainFont],builtinFontLen[settings.mainFont],e->getConfInt("mainFontSize",18)*dpiScale,NULL,loadEverything))==NULL) { - logE("could not load UI font! falling back to Proggy Clean.\n"); - mainFont=ImGui::GetIO().Fonts->AddFontDefault(); - } - } - } else if (settings.mainFont==5) { // system font - if ((mainFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_FONT_PATH_1,e->getConfInt("mainFontSize",18)*dpiScale,NULL,loadEverything))==NULL) { - if ((mainFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_FONT_PATH_2,e->getConfInt("mainFontSize",18)*dpiScale,NULL,loadEverything))==NULL) { - if ((mainFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_FONT_PATH_3,e->getConfInt("mainFontSize",18)*dpiScale,NULL,loadEverything))==NULL) { - logW("could not load UI font! reverting to default font\n"); - settings.mainFont=0; - if ((mainFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFont[settings.mainFont],builtinFontLen[settings.mainFont],e->getConfInt("mainFontSize",18)*dpiScale,NULL,loadEverything))==NULL) { - logE("could not load UI font! falling back to Proggy Clean.\n"); - mainFont=ImGui::GetIO().Fonts->AddFontDefault(); - } - } - } - } - } else { - if ((mainFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFont[settings.mainFont],builtinFontLen[settings.mainFont],e->getConfInt("mainFontSize",18)*dpiScale,NULL,loadEverything))==NULL) { - logE("could not load UI font! falling back to Proggy Clean.\n"); - mainFont=ImGui::GetIO().Fonts->AddFontDefault(); - } - } - - ImFontConfig fc; - fc.MergeMode=true; - fc.GlyphMinAdvanceX=e->getConfInt("iconSize",16)*dpiScale; - static const ImWchar fontRange[]={ICON_MIN_FA,ICON_MAX_FA,0}; - if ((iconFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(iconFont_compressed_data,iconFont_compressed_size,e->getConfInt("iconSize",16)*dpiScale,&fc,fontRange))==NULL) { - logE("could not load icon font!\n"); - } - if (settings.mainFontSize==settings.patFontSize && settings.patFont<5 && builtinFontM[settings.patFont]==builtinFont[settings.mainFont]) { - logD("using main font for pat font.\n"); - patFont=mainFont; - } else { - if (settings.patFont==6) { // custom font - if ((patFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(settings.patFontPath.c_str(),e->getConfInt("patFontSize",18)*dpiScale,NULL,loadEverything))==NULL) { - logW("could not load pattern font! reverting to default font\n"); - settings.patFont=0; - if ((patFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFontM[settings.patFont],builtinFontMLen[settings.patFont],e->getConfInt("patFontSize",18)*dpiScale,NULL,loadEverything))==NULL) { - logE("could not load pattern font! falling back to Proggy Clean.\n"); - patFont=ImGui::GetIO().Fonts->AddFontDefault(); - } - } - } else if (settings.patFont==5) { // system font - if ((patFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_PAT_FONT_PATH_1,e->getConfInt("patFontSize",18)*dpiScale,NULL,loadEverything))==NULL) { - if ((patFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_PAT_FONT_PATH_2,e->getConfInt("patFontSize",18)*dpiScale,NULL,loadEverything))==NULL) { - if ((patFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_PAT_FONT_PATH_3,e->getConfInt("patFontSize",18)*dpiScale,NULL,loadEverything))==NULL) { - logW("could not load pattern font! reverting to default font\n"); - settings.patFont=0; - if ((patFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFontM[settings.patFont],builtinFontMLen[settings.patFont],e->getConfInt("patFontSize",18)*dpiScale,NULL,loadEverything))==NULL) { - logE("could not load pattern font! falling back to Proggy Clean.\n"); - patFont=ImGui::GetIO().Fonts->AddFontDefault(); - } - } - } - } - } else { - if ((patFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFontM[settings.patFont],builtinFontMLen[settings.patFont],e->getConfInt("patFontSize",18)*dpiScale,NULL,loadEverything))==NULL) { - logE("could not load pattern font!\n"); - patFont=ImGui::GetIO().Fonts->AddFontDefault(); - } - } - } - if ((bigFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(font_plexSans_compressed_data,font_plexSans_compressed_size,40*dpiScale))==NULL) { - logE("could not load big UI font!\n"); - } - - if (fileDialog!=NULL) delete fileDialog; - fileDialog=new FurnaceGUIFileDialog(settings.sysFileDialog); -} - bool FurnaceGUI::init() { #ifndef __APPLE__ float dpiScaleF; @@ -6707,28 +2603,6 @@ bool FurnaceGUI::init() { ImGui::GetIO().IniFilename=finalLayoutPath; ImGui::LoadIniSettingsFromDisk(finalLayoutPath); - // TODO: allow changing these colors. - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByTypeDir,"",ImVec4(0.0f,1.0f,1.0f,1.0f),ICON_FA_FOLDER_O); - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByTypeFile,"",ImVec4(0.7f,0.7f,0.7f,1.0f),ICON_FA_FILE_O); - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".fur",ImVec4(0.5f,1.0f,0.5f,1.0f),ICON_FA_FILE); - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".fui",ImVec4(1.0f,0.5f,0.5f,1.0f),ICON_FA_FILE); - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".fuw",ImVec4(1.0f,0.75f,0.5f,1.0f),ICON_FA_FILE); - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".dmf",ImVec4(0.5f,1.0f,0.5f,1.0f),ICON_FA_FILE); - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".dmp",ImVec4(1.0f,0.5f,0.5f,1.0f),ICON_FA_FILE); - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".dmw",ImVec4(1.0f,0.75f,0.5f,1.0f),ICON_FA_FILE); - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".wav",ImVec4(1.0f,1.0f,0.5f,1.0f),ICON_FA_FILE_AUDIO_O); - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".vgm",ImVec4(1.0f,1.0f,0.5f,1.0f),ICON_FA_FILE_AUDIO_O); - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".ttf",ImVec4(0.3f,1.0f,0.6f,1.0f),ICON_FA_FONT); - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".otf",ImVec4(0.3f,1.0f,0.6f,1.0f),ICON_FA_FONT); - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".ttc",ImVec4(0.3f,1.0f,0.6f,1.0f),ICON_FA_FONT); - - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".tfi",ImVec4(1.0f,0.5f,0.5f,1.0f),ICON_FA_FILE); - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".vgi",ImVec4(1.0f,0.5f,0.5f,1.0f),ICON_FA_FILE); - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".s3i",ImVec4(1.0f,0.5f,0.5f,1.0f),ICON_FA_FILE); - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".sbi",ImVec4(1.0f,0.5f,0.5f,1.0f),ICON_FA_FILE); - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".fti",ImVec4(1.0f,0.5f,0.5f,1.0f),ICON_FA_FILE); - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".bti",ImVec4(1.0f,0.5f,0.5f,1.0f),ICON_FA_FILE); - updateWindowTitle(); for (int i=0; isong.masterVol,0,3,"%.2fx")) { + if (e->song.masterVol<0) e->song.masterVol=0; + if (e->song.masterVol>3) e->song.masterVol=3; + } rightClickable + for (int i=0; isong.systemLen; i++) { + snprintf(id,31,"MixS%d",i); + bool doInvert=e->song.systemVol[i]&128; + signed char vol=e->song.systemVol[i]&127; + ImGui::PushID(id); + ImGui::Text("%d. %s",i+1,getSystemName(e->song.system[i])); + ImGui::SameLine(ImGui::GetWindowWidth()-(82.0f*dpiScale)); + if (ImGui::Checkbox("Invert",&doInvert)) { + e->song.systemVol[i]^=128; + } + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x-(50.0f*dpiScale)); + if (ImGui::SliderScalar("Volume",ImGuiDataType_S8,&vol,&_ZERO,&_ONE_HUNDRED_TWENTY_SEVEN)) { + e->song.systemVol[i]=(e->song.systemVol[i]&128)|vol; + } rightClickable + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x-(50.0f*dpiScale)); + ImGui::SliderScalar("Panning",ImGuiDataType_S8,&e->song.systemPan[i],&_MINUS_ONE_HUNDRED_TWENTY_SEVEN,&_ONE_HUNDRED_TWENTY_SEVEN); rightClickable + + ImGui::PopID(); + } + } + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_MIXER; + ImGui::End(); +} \ No newline at end of file diff --git a/src/gui/newSong.cpp b/src/gui/newSong.cpp new file mode 100644 index 000000000..d6188b3e5 --- /dev/null +++ b/src/gui/newSong.cpp @@ -0,0 +1,90 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "gui.h" + +void FurnaceGUI::drawNewSong() { + bool accepted=false; + + ImGui::PushFont(bigFont); + ImGui::SetCursorPosX((ImGui::GetContentRegionAvail().x-ImGui::CalcTextSize("Choose a System!").x)*0.5); + ImGui::Text("Choose a System!"); + ImGui::PopFont(); + + if (ImGui::BeginTable("sysPicker",2)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,0.0f); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.0f); + + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + ImGui::TableNextColumn(); + ImGui::Text("Categories"); + ImGui::TableNextColumn(); + ImGui::Text("Systems"); + + ImGui::TableNextRow(); + + // CATEGORIES + ImGui::TableNextColumn(); + int index=0; + for (FurnaceGUISysCategory& i: sysCategories) { + if (ImGui::Selectable(i.name,newSongCategory==index,ImGuiSelectableFlags_DontClosePopups)) { \ + newSongCategory=index; + } + index++; + } + + // SYSTEMS + ImGui::TableNextColumn(); + if (ImGui::BeginTable("Systems",1,ImGuiTableFlags_BordersInnerV|ImGuiTableFlags_ScrollY)) { + for (FurnaceGUISysDef& i: sysCategories[newSongCategory].systems) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + if (ImGui::Selectable(i.name,false,ImGuiSelectableFlags_DontClosePopups)) { + nextDesc=i.definition.data(); + accepted=true; + } + } + ImGui::EndTable(); + } + + ImGui::EndTable(); + } + + if (ImGui::Button("Cancel")) { + ImGui::CloseCurrentPopup(); + } + + if (accepted) { + e->createNew(nextDesc); + undoHist.clear(); + redoHist.clear(); + curFileName=""; + modified=false; + curNibble=false; + orderNibble=false; + orderCursor=-1; + samplePos=0; + updateSampleTex=true; + selStart=SelectionPoint(); + selEnd=SelectionPoint(); + cursor=SelectionPoint(); + updateWindowTitle(); + ImGui::CloseCurrentPopup(); + } +} \ No newline at end of file diff --git a/src/gui/osc.cpp b/src/gui/osc.cpp new file mode 100644 index 000000000..0bdcdf8a0 --- /dev/null +++ b/src/gui/osc.cpp @@ -0,0 +1,47 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "gui.h" + +void FurnaceGUI::drawOsc() { + if (nextWindow==GUI_WINDOW_OSCILLOSCOPE) { + oscOpen=true; + ImGui::SetNextWindowFocus(); + nextWindow=GUI_WINDOW_NOTHING; + } + if (!oscOpen) return; + ImGui::SetNextWindowSizeConstraints(ImVec2(64.0f*dpiScale,32.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding,ImVec2(0,0)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,ImVec2(0,0)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemInnerSpacing,ImVec2(0,0)); + if (ImGui::Begin("Oscilloscope",&oscOpen)) { + float values[512]; + for (int i=0; i<512; i++) { + int pos=i*e->oscSize/512; + values[i]=(e->oscBuf[0][pos]+e->oscBuf[1][pos])*0.5f; + } + //ImGui::SetCursorPos(ImVec2(0,0)); + ImGui::BeginDisabled(); + ImGui::PlotLines("##SingleOsc",values,512,0,NULL,-1.0f,1.0f,ImGui::GetContentRegionAvail()); + ImGui::EndDisabled(); + } + ImGui::PopStyleVar(3); + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_OSCILLOSCOPE; + ImGui::End(); +} \ No newline at end of file diff --git a/src/gui/piano.cpp b/src/gui/piano.cpp new file mode 100644 index 000000000..d3a3d35ef --- /dev/null +++ b/src/gui/piano.cpp @@ -0,0 +1,46 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "gui.h" +#include "guiConst.h" + +void FurnaceGUI::drawPiano() { + if (nextWindow==GUI_WINDOW_PIANO) { + pianoOpen=true; + ImGui::SetNextWindowFocus(); + nextWindow=GUI_WINDOW_NOTHING; + } + if (!pianoOpen) return; + if (ImGui::Begin("Piano",&pianoOpen)) { + for (int i=0; igetTotalChannelCount(); i++) { + DivChannelState* cs=e->getChanState(i); + if (cs->keyOn) { + const char* noteName=NULL; + if (cs->note<-60 || cs->note>120) { + noteName="???"; + } else { + noteName=noteNames[cs->note+60]; + } + ImGui::Text("%d: %s",i,noteName); + } + } + } + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_PIANO; + ImGui::End(); +} \ No newline at end of file diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp new file mode 100644 index 000000000..0ab7c2ba6 --- /dev/null +++ b/src/gui/presets.cpp @@ -0,0 +1,639 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "gui.h" + +// add system configurations here. +// every entry is written in the following format: +// cat.systems.push_back(FurnaceGUISysDef( +// "System Name", { +// DIV_SYSTEM_???, Volume, Panning, Flags, +// DIV_SYSTEM_???, Volume, Panning, Flags, +// ... +// 0 +// } +// )); + +void FurnaceGUI::initSystemPresets() { + FurnaceGUISysCategory cat; + + cat=FurnaceGUISysCategory("FM"); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha YM2612", { + DIV_SYSTEM_YM2612, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha YM2612 (extended channel 3)", { + DIV_SYSTEM_YM2612_EXT, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha YM2151", { + DIV_SYSTEM_YM2151, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha YM2610", { + DIV_SYSTEM_YM2610_FULL, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha YM2610 (extended channel 2)", { + DIV_SYSTEM_YM2610_FULL_EXT, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha YM2610B", { + DIV_SYSTEM_YM2610B, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha YM2610B (extended channel 3)", { + DIV_SYSTEM_YM2610B_EXT, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha YM2413", { + DIV_SYSTEM_OPLL, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha YM2413 (drums mode)", { + DIV_SYSTEM_OPLL_DRUMS, 64, 0, 0, + 0 + } + )); + sysCategories.push_back(cat); + + cat=FurnaceGUISysCategory("Square"); + cat.systems.push_back(FurnaceGUISysDef( + "TI SN76489", { + DIV_SYSTEM_SMS, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "AY-3-8910", { + DIV_SYSTEM_AY8910, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Philips SAA1099", { + DIV_SYSTEM_SAA1099, 64, 0, 0, + 0 + } + )); + sysCategories.push_back(cat); + + cat=FurnaceGUISysCategory("Sample"); + cat.systems.push_back(FurnaceGUISysDef( + "Amiga", { + DIV_SYSTEM_AMIGA, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "SegaPCM", { + DIV_SYSTEM_SEGAPCM, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Capcom QSound", { + DIV_SYSTEM_QSOUND, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Seta/Allumer X1-010", { + DIV_SYSTEM_X1_010, 64, 0, 0, + 0 + } + )); + sysCategories.push_back(cat); + + cat=FurnaceGUISysCategory("Game consoles"); + cat.systems.push_back(FurnaceGUISysDef( + "Sega Genesis", { + DIV_SYSTEM_YM2612, 64, 0, 0, + DIV_SYSTEM_SMS, 24, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sega Genesis (extended channel 3)", { + DIV_SYSTEM_YM2612_EXT, 64, 0, 0, + DIV_SYSTEM_SMS, 24, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sega Master System", { + DIV_SYSTEM_SMS, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sega Master System (with FM expansion)", { + DIV_SYSTEM_SMS, 64, 0, 0, + DIV_SYSTEM_OPLL, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sega Master System (with FM expansion in drums mode)", { + DIV_SYSTEM_SMS, 64, 0, 0, + DIV_SYSTEM_OPLL_DRUMS, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Game Boy", { + DIV_SYSTEM_GB, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NEC PC Engine/TurboGrafx-16", { + DIV_SYSTEM_PCE, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NES", { + DIV_SYSTEM_NES, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NES with Konami VRC7", { + DIV_SYSTEM_NES, 64, 0, 0, + DIV_SYSTEM_VRC7, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NES with Sunsoft 5B", { + DIV_SYSTEM_NES, 64, 0, 0, + DIV_SYSTEM_AY8910, 64, 0, 38, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Mattel Intellivision", { + DIV_SYSTEM_AY8910, 64, 0, 48, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Vectrex", { + DIV_SYSTEM_AY8910, 64, 0, 4, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Neo Geo AES", { + DIV_SYSTEM_YM2610_FULL, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Neo Geo AES (extended channel 2)", { + DIV_SYSTEM_YM2610_FULL_EXT, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Atari 2600/7800", { + DIV_SYSTEM_TIA, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Atari Lynx", { + DIV_SYSTEM_LYNX, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "WonderSwan", { + DIV_SYSTEM_SWAN, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Gamate", { + DIV_SYSTEM_AY8910, 64, 0, 73, + 0 + } + )); + sysCategories.push_back(cat); + + cat=FurnaceGUISysCategory("Computers"); + cat.systems.push_back(FurnaceGUISysDef( + "Commodore PET", { + DIV_SYSTEM_PET, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Commodore VIC-20", { + DIV_SYSTEM_VIC20, 64, 0, 1, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Commodore 64 (6581 SID)", { + DIV_SYSTEM_C64_6581, 64, 0, 1, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Commodore 64 (8580 SID)", { + DIV_SYSTEM_C64_8580, 64, 0, 1, + 0 + } + )); + /* + cat.systems.push_back(FurnaceGUISysDef( + "Commodore 64 (6581 SID + Sound Expander)", { + DIV_SYSTEM_OPL, 64, 0, 0, + DIV_SYSTEM_C64_6581, 64, 0, 1, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Commodore 64 (6581 SID + Sound Expander with drums mode)", { + DIV_SYSTEM_OPL_DRUMS, 64, 0, 0, + DIV_SYSTEM_C64_6581, 64, 0, 1, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Commodore 64 (8580 SID + Sound Expander)", { + DIV_SYSTEM_OPL, 64, 0, 0, + DIV_SYSTEM_C64_8580, 64, 0, 1, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Commodore 64 (8580 SID + Sound Expander with drums mode)", { + DIV_SYSTEM_OPL_DRUMS, 64, 0, 0, + DIV_SYSTEM_C64_8580, 64, 0, 1, + 0 + } + ));*/ + cat.systems.push_back(FurnaceGUISysDef( + "Amiga", { + DIV_SYSTEM_AMIGA, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "MSX", { + DIV_SYSTEM_AY8910, 64, 0, 16, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "MSX + SFG-01", { + DIV_SYSTEM_YM2151, 64, 0, 0, + DIV_SYSTEM_AY8910, 64, 0, 16, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "MSX + MSX-MUSIC", { + DIV_SYSTEM_OPLL, 64, 0, 0, + DIV_SYSTEM_AY8910, 64, 0, 16, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "MSX + MSX-MUSIC (drums mode)", { + DIV_SYSTEM_OPLL_DRUMS, 64, 0, 0, + DIV_SYSTEM_AY8910, 64, 0, 16, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "ZX Spectrum (48K)", { + DIV_SYSTEM_AY8910, 64, 0, 2, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "ZX Spectrum (128K)", { + DIV_SYSTEM_AY8910, 64, 0, 1, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Amstrad CPC", { + DIV_SYSTEM_AY8910, 64, 0, 5, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "SAM Coupé", { + DIV_SYSTEM_SAA1099, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "BBC Micro", { + DIV_SYSTEM_SMS, 64, 0, 6, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "PC (barebones)", { + DIV_SYSTEM_PCSPKR, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "PC + Covox Sound Master", { + DIV_SYSTEM_AY8930, 64, 0, 3, + DIV_SYSTEM_PCSPKR, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "PC + SSI 2001", { + DIV_SYSTEM_C64_6581, 64, 0, 2, + DIV_SYSTEM_PCSPKR, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "PC + Game Blaster", { + DIV_SYSTEM_SAA1099, 64, -127, 1, + DIV_SYSTEM_SAA1099, 64, 127, 1, + DIV_SYSTEM_PCSPKR, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "PC + AdLib/Sound Blaster", { + DIV_SYSTEM_OPL2, 64, 0, 0, + DIV_SYSTEM_PCSPKR, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "PC + AdLib/Sound Blaster (drums mode)", { + DIV_SYSTEM_OPL2_DRUMS, 64, 0, 0, + DIV_SYSTEM_PCSPKR, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "PC + Sound Blaster w/Game Blaster Compatible", { + DIV_SYSTEM_OPL2, 64, 0, 0, + DIV_SYSTEM_SAA1099, 64, -127, 1, + DIV_SYSTEM_SAA1099, 64, 127, 1, + DIV_SYSTEM_PCSPKR, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "PC + Sound Blaster w/Game Blaster Compatible (drums mode)", { + DIV_SYSTEM_OPL2_DRUMS, 64, 0, 0, + DIV_SYSTEM_SAA1099, 64, -127, 1, + DIV_SYSTEM_SAA1099, 64, 127, 1, + DIV_SYSTEM_PCSPKR, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "PC + Sound Blaster Pro 2", { + DIV_SYSTEM_OPL3, 64, 0, 0, + DIV_SYSTEM_PCSPKR, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "PC + Sound Blaster Pro 2 (drums mode)", { + DIV_SYSTEM_OPL3_DRUMS, 64, 0, 0, + DIV_SYSTEM_PCSPKR, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sharp X1", { + DIV_SYSTEM_AY8910, 64, 0, 3, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sharp X1 + FM Addon", { + DIV_SYSTEM_AY8910, 64, 0, 3, + DIV_SYSTEM_YM2151, 64, 0, 2, + 0 + } + )); + /* + cat.systems.push_back(FurnaceGUISysDef( + "Sharp X68000", { + DIV_SYSTEM_YM2151, 64, 0, 2, + DIV_SYSTEM_MSM6258, 64, 0, 0, + 0 + } + ));*/ + cat.systems.push_back(FurnaceGUISysDef( + "Commander X16", { + DIV_SYSTEM_YM2151, 64, 0, 0, + DIV_SYSTEM_VERA, 64, 0, 0, + 0 + } + )); + sysCategories.push_back(cat); + + cat=FurnaceGUISysCategory("Arcade systems"); + cat.systems.push_back(FurnaceGUISysDef( + "Bally Midway MCR", { + DIV_SYSTEM_AY8910, 64, 0, 0, + DIV_SYSTEM_AY8910, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sega Kyugo", { + DIV_SYSTEM_AY8910, 64, 0, 4, + DIV_SYSTEM_AY8910, 64, 0, 4, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sega OutRun/X Board", { + DIV_SYSTEM_YM2151, 64, 0, 2, + DIV_SYSTEM_SEGAPCM, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Neo Geo MVS", { + DIV_SYSTEM_YM2610_FULL, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Neo Geo MVS (extended channel 2)", { + DIV_SYSTEM_YM2610_FULL_EXT, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Taito Arcade", { + DIV_SYSTEM_YM2610B, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Taito Arcade (extended channel 3)", { + DIV_SYSTEM_YM2610B_EXT, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Capcom CPS-2 (QSound)", { + DIV_SYSTEM_QSOUND, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Seta 1", { + DIV_SYSTEM_X1_010, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Seta 2", { + DIV_SYSTEM_X1_010, 64, 0, 1, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Konami Bubble System", { + DIV_SYSTEM_AY8910, 64, 0, 0, + DIV_SYSTEM_AY8910, 64, 0, 0, + DIV_SYSTEM_BUBSYS_WSG, 64, 0, 0, + // VLM5030 exists but not used for music at all + 0 + } + )); + sysCategories.push_back(cat); + + cat=FurnaceGUISysCategory("DefleMask-compatible"); + cat.systems.push_back(FurnaceGUISysDef( + "Sega Genesis", { + DIV_SYSTEM_YM2612, 64, 0, 0, + DIV_SYSTEM_SMS, 24, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sega Genesis (extended channel 3)", { + DIV_SYSTEM_YM2612_EXT, 64, 0, 0, + DIV_SYSTEM_SMS, 24, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sega Master System", { + DIV_SYSTEM_SMS, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Sega Master System (with FM expansion)", { + DIV_SYSTEM_SMS, 64, 0, 0, + DIV_SYSTEM_OPLL, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Game Boy", { + DIV_SYSTEM_GB, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NEC PC Engine/TurboGrafx-16", { + DIV_SYSTEM_PCE, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NES", { + DIV_SYSTEM_NES, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NES with Konami VRC7", { + DIV_SYSTEM_NES, 64, 0, 0, + DIV_SYSTEM_VRC7, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Commodore 64 (6581 SID)", { + DIV_SYSTEM_C64_6581, 64, 0, 1, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Commodore 64 (8580 SID)", { + DIV_SYSTEM_C64_8580, 64, 0, 1, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Arcade (YM2151 and SegaPCM)", { + DIV_SYSTEM_YM2151, 64, 0, 0, + DIV_SYSTEM_SEGAPCM_COMPAT, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Neo Geo CD", { + DIV_SYSTEM_YM2610, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Neo Geo CD (extended channel 2)", { + DIV_SYSTEM_YM2610_EXT, 64, 0, 0, + 0 + } + )); + sysCategories.push_back(cat); +} diff --git a/src/gui/regView.cpp b/src/gui/regView.cpp new file mode 100644 index 000000000..9755e4a83 --- /dev/null +++ b/src/gui/regView.cpp @@ -0,0 +1,71 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "gui.h" + +void FurnaceGUI::drawRegView() { + if (nextWindow==GUI_WINDOW_REGISTER_VIEW) { + channelsOpen=true; + ImGui::SetNextWindowFocus(); + nextWindow=GUI_WINDOW_NOTHING; + } + if (!regViewOpen) return; + if (ImGui::Begin("Register View",®ViewOpen)) { + for (int i=0; isong.systemLen; i++) { + ImGui::Text("%d. %s",i+1,getSystemName(e->song.system[i])); + int size=0; + int depth=8; + unsigned char* regPool=e->getRegisterPool(i,size,depth); + unsigned short* regPoolW=(unsigned short*)regPool; + if (regPool==NULL) { + ImGui::Text("- no register pool available"); + } else { + ImGui::PushFont(patFont); + if (ImGui::BeginTable("Memory",17)) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + for (int i=0; i<16; i++) { + ImGui::TableNextColumn(); + ImGui::TextColored(uiColors[GUI_COLOR_PATTERN_ROW_INDEX]," %X",i); + } + for (int i=0; i<=((size-1)>>4); i++) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::TextColored(uiColors[GUI_COLOR_PATTERN_ROW_INDEX],"%.2X",i*16); + for (int j=0; j<16; j++) { + ImGui::TableNextColumn(); + if (i*16+j>=size) continue; + if (depth == 8) { + ImGui::Text("%.2x",regPool[i*16+j]); + } else if (depth == 16) { + ImGui::Text("%.4x",regPoolW[i*16+j]); + } else { + ImGui::Text("??"); + } + } + } + ImGui::EndTable(); + } + ImGui::PopFont(); + } + } + } + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_REGISTER_VIEW; + ImGui::End(); +} \ No newline at end of file diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index ed17a1e3b..a3fd347d6 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -18,8 +18,10 @@ */ #include "gui.h" +#include "fonts.h" #include "../ta-log.h" #include "util.h" +#include "ImGuiFileDialog.h" #include "IconsFontAwesome4.h" #include "misc/cpp/imgui_stdlib.h" #include @@ -1425,3 +1427,398 @@ void FurnaceGUI::commitSettings() { } } } + +void FurnaceGUI::parseKeybinds() { + actionMapGlobal.clear(); + actionMapPat.clear(); + actionMapInsList.clear(); + actionMapWaveList.clear(); + actionMapSampleList.clear(); + actionMapOrders.clear(); + + for (int i=GUI_ACTION_GLOBAL_MIN+1; igetConfInt(#target,ImGui::GetColorU32(def))); + +#ifdef _WIN32 +#define SYSTEM_FONT_PATH_1 "C:\\Windows\\Fonts\\segoeui.ttf" +#define SYSTEM_FONT_PATH_2 "C:\\Windows\\Fonts\\tahoma.ttf" +// TODO! +#define SYSTEM_FONT_PATH_3 "C:\\Windows\\Fonts\\tahoma.ttf" +// TODO! +#define SYSTEM_PAT_FONT_PATH_1 "C:\\Windows\\Fonts\\consola.ttf" +#define SYSTEM_PAT_FONT_PATH_2 "C:\\Windows\\Fonts\\cour.ttf" +// GOOD LUCK WITH THIS ONE - UNTESTED +#define SYSTEM_PAT_FONT_PATH_3 "C:\\Windows\\Fonts\\vgasys.fon" +#elif defined(__APPLE__) +#define SYSTEM_FONT_PATH_1 "/System/Library/Fonts/SFAANS.ttf" +#define SYSTEM_FONT_PATH_2 "/System/Library/Fonts/Helvetica.ttc" +#define SYSTEM_FONT_PATH_3 "/System/Library/Fonts/Helvetica.dfont" +#define SYSTEM_PAT_FONT_PATH_1 "/System/Library/Fonts/SFNSMono.ttf" +#define SYSTEM_PAT_FONT_PATH_2 "/System/Library/Fonts/Courier New.ttf" +#define SYSTEM_PAT_FONT_PATH_3 "/System/Library/Fonts/Courier New.ttf" +#else +#define SYSTEM_FONT_PATH_1 "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf" +#define SYSTEM_FONT_PATH_2 "/usr/share/fonts/TTF/DejaVuSans.ttf" +#define SYSTEM_FONT_PATH_3 "/usr/share/fonts/ubuntu/Ubuntu-R.ttf" +#define SYSTEM_PAT_FONT_PATH_1 "/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf" +#define SYSTEM_PAT_FONT_PATH_2 "/usr/share/fonts/TTF/DejaVuSansMono.ttf" +#define SYSTEM_PAT_FONT_PATH_3 "/usr/share/fonts/ubuntu/UbuntuMono-R.ttf" +#endif + +void FurnaceGUI::applyUISettings() { + ImGuiStyle sty; + if (settings.guiColorsBase) { + ImGui::StyleColorsLight(&sty); + } else { + ImGui::StyleColorsDark(&sty); + } + + if (settings.dpiScale>=0.5f) dpiScale=settings.dpiScale; + + GET_UI_COLOR(GUI_COLOR_BACKGROUND,ImVec4(0.1f,0.1f,0.1f,1.0f)); + GET_UI_COLOR(GUI_COLOR_FRAME_BACKGROUND,ImVec4(0.0f,0.0f,0.0f,0.85f)); + GET_UI_COLOR(GUI_COLOR_MODAL_BACKDROP,ImVec4(0.0f,0.0f,0.0f,0.55f)); + GET_UI_COLOR(GUI_COLOR_HEADER,ImVec4(0.2f,0.2f,0.2f,1.0f)); + GET_UI_COLOR(GUI_COLOR_TEXT,ImVec4(1.0f,1.0f,1.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_ACCENT_PRIMARY,ImVec4(0.06f,0.53f,0.98f,1.0f)); + GET_UI_COLOR(GUI_COLOR_ACCENT_SECONDARY,ImVec4(0.26f,0.59f,0.98f,1.0f)); + GET_UI_COLOR(GUI_COLOR_EDITING,ImVec4(0.2f,0.1f,0.1f,1.0f)); + GET_UI_COLOR(GUI_COLOR_SONG_LOOP,ImVec4(0.3f,0.5f,0.8f,0.4f)); + GET_UI_COLOR(GUI_COLOR_VOLMETER_LOW,ImVec4(0.2f,0.6f,0.2f,1.0f)); + GET_UI_COLOR(GUI_COLOR_VOLMETER_HIGH,ImVec4(1.0f,0.9f,0.2f,1.0f)); + GET_UI_COLOR(GUI_COLOR_VOLMETER_PEAK,ImVec4(1.0f,0.1f,0.1f,1.0f)); + GET_UI_COLOR(GUI_COLOR_MACRO_VOLUME,ImVec4(0.2f,1.0f,0.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_MACRO_PITCH,ImVec4(1.0f,0.8f,0.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_MACRO_OTHER,ImVec4(0.0f,0.9f,1.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_MACRO_WAVE,ImVec4(1.0f,0.4f,0.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_FM,ImVec4(0.6f,0.9f,1.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_STD,ImVec4(0.6f,1.0f,0.5f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_GB,ImVec4(1.0f,1.0f,0.5f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_C64,ImVec4(0.85f,0.8f,1.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_AMIGA,ImVec4(1.0f,0.5f,0.5f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_PCE,ImVec4(1.0f,0.8f,0.5f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_AY,ImVec4(1.0f,0.5f,1.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_AY8930,ImVec4(0.7f,0.5f,1.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_TIA,ImVec4(1.0f,0.6f,0.4f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_SAA1099,ImVec4(0.3f,0.3f,1.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_VIC,ImVec4(0.2f,1.0f,0.6f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_PET,ImVec4(1.0f,1.0f,0.8f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_VRC6,ImVec4(1.0f,0.9f,0.5f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_OPLL,ImVec4(0.6f,0.7f,1.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_OPL,ImVec4(0.3f,1.0f,0.9f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_FDS,ImVec4(0.8f,0.5f,1.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_VBOY,ImVec4(1.0f,0.1f,0.1f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_N163,ImVec4(1.0f,0.4f,0.1f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_SCC,ImVec4(0.7f,1.0f,0.3f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_OPZ,ImVec4(0.2f,0.8f,1.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_POKEY,ImVec4(0.5f,1.0f,0.3f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_BEEPER,ImVec4(0.0f,1.0f,0.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_SWAN,ImVec4(0.3f,0.5f,1.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_MIKEY,ImVec4(0.5f,1.0f,0.3f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_VERA,ImVec4(0.4f,0.6f,1.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_X1_010,ImVec4(0.3f,0.5f,1.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_UNKNOWN,ImVec4(0.3f,0.3f,0.3f,1.0f)); + GET_UI_COLOR(GUI_COLOR_CHANNEL_FM,ImVec4(0.2f,0.8f,1.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_CHANNEL_PULSE,ImVec4(0.4f,1.0f,0.2f,1.0f)); + GET_UI_COLOR(GUI_COLOR_CHANNEL_NOISE,ImVec4(0.8f,0.8f,0.8f,1.0f)); + GET_UI_COLOR(GUI_COLOR_CHANNEL_PCM,ImVec4(1.0f,0.9f,0.2f,1.0f)); + GET_UI_COLOR(GUI_COLOR_CHANNEL_WAVE,ImVec4(1.0f,0.5f,0.2f,1.0f)); + GET_UI_COLOR(GUI_COLOR_CHANNEL_OP,ImVec4(0.2f,0.4f,1.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_CHANNEL_MUTED,ImVec4(0.5f,0.5f,0.5f,1.0f)); + GET_UI_COLOR(GUI_COLOR_PATTERN_CURSOR,ImVec4(0.1f,0.3f,0.5f,1.0f)); + GET_UI_COLOR(GUI_COLOR_PATTERN_CURSOR_HOVER,ImVec4(0.2f,0.4f,0.6f,1.0f)); + GET_UI_COLOR(GUI_COLOR_PATTERN_CURSOR_ACTIVE,ImVec4(0.2f,0.5f,0.7f,1.0f)); + GET_UI_COLOR(GUI_COLOR_PATTERN_SELECTION,ImVec4(0.15f,0.15f,0.2f,1.0f)); + GET_UI_COLOR(GUI_COLOR_PATTERN_SELECTION_HOVER,ImVec4(0.2f,0.2f,0.3f,1.0f)); + GET_UI_COLOR(GUI_COLOR_PATTERN_SELECTION_ACTIVE,ImVec4(0.4f,0.4f,0.5f,1.0f)); + GET_UI_COLOR(GUI_COLOR_PATTERN_HI_1,ImVec4(0.6f,0.6f,0.6f,0.2f)); + GET_UI_COLOR(GUI_COLOR_PATTERN_HI_2,ImVec4(0.5f,0.8f,1.0f,0.2f)); + GET_UI_COLOR(GUI_COLOR_PATTERN_ROW_INDEX,ImVec4(0.5f,0.8f,1.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_PATTERN_ACTIVE,ImVec4(1.0f,1.0f,1.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_PATTERN_INACTIVE,ImVec4(0.5f,0.5f,0.5f,1.0f)); + GET_UI_COLOR(GUI_COLOR_PATTERN_INS,ImVec4(0.4f,0.7f,1.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_PATTERN_VOLUME_MIN,ImVec4(0.0f,0.5f,0.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_PATTERN_VOLUME_HALF,ImVec4(0.0f,0.75f,0.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_PATTERN_VOLUME_MAX,ImVec4(0.0f,1.0f,0.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_INVALID,ImVec4(1.0f,0.0f,0.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_PITCH,ImVec4(1.0f,1.0f,0.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_VOLUME,ImVec4(0.0f,1.0f,0.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_PANNING,ImVec4(0.0f,1.0f,1.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_SONG,ImVec4(1.0f,0.0f,0.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_TIME,ImVec4(0.5f,0.0f,1.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_SPEED,ImVec4(1.0f,0.0f,1.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,ImVec4(0.5f,1.0f,0.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_SYS_SECONDARY,ImVec4(0.0f,1.0f,0.5f,1.0f)); + GET_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_MISC,ImVec4(0.3f,0.3f,1.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_EE_VALUE,ImVec4(0.0f,1.0f,1.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_PLAYBACK_STAT,ImVec4(0.6f,0.6f,0.6f,1.0f)); + + for (int i=0; i<64; i++) { + ImVec4 col1=uiColors[GUI_COLOR_PATTERN_VOLUME_MIN]; + ImVec4 col2=uiColors[GUI_COLOR_PATTERN_VOLUME_HALF]; + ImVec4 col3=uiColors[GUI_COLOR_PATTERN_VOLUME_MAX]; + volColors[i]=ImVec4(col1.x+((col2.x-col1.x)*float(i)/64.0f), + col1.y+((col2.y-col1.y)*float(i)/64.0f), + col1.z+((col2.z-col1.z)*float(i)/64.0f), + 1.0f); + volColors[i+64]=ImVec4(col2.x+((col3.x-col2.x)*float(i)/64.0f), + col2.y+((col3.y-col2.y)*float(i)/64.0f), + col2.z+((col3.z-col2.z)*float(i)/64.0f), + 1.0f); + } + + float hue, sat, val; + + ImVec4 primaryActive=uiColors[GUI_COLOR_ACCENT_PRIMARY]; + ImVec4 primaryHover, primary; + primaryHover.w=primaryActive.w; + primary.w=primaryActive.w; + ImGui::ColorConvertRGBtoHSV(primaryActive.x,primaryActive.y,primaryActive.z,hue,sat,val); + if (settings.guiColorsBase) { + primary=primaryActive; + ImGui::ColorConvertHSVtoRGB(hue,sat*0.9,val*0.9,primaryHover.x,primaryHover.y,primaryHover.z); + ImGui::ColorConvertHSVtoRGB(hue,sat,val*0.5,primaryActive.x,primaryActive.y,primaryActive.z); + } else { + ImGui::ColorConvertHSVtoRGB(hue,sat*0.9,val*0.5,primaryHover.x,primaryHover.y,primaryHover.z); + ImGui::ColorConvertHSVtoRGB(hue,sat*0.8,val*0.35,primary.x,primary.y,primary.z); + } + + ImVec4 secondaryActive=uiColors[GUI_COLOR_ACCENT_SECONDARY]; + ImVec4 secondaryHover, secondary, secondarySemiActive; + secondarySemiActive.w=secondaryActive.w; + secondaryHover.w=secondaryActive.w; + secondary.w=secondaryActive.w; + ImGui::ColorConvertRGBtoHSV(secondaryActive.x,secondaryActive.y,secondaryActive.z,hue,sat,val); + if (settings.guiColorsBase) { + secondary=secondaryActive; + ImGui::ColorConvertHSVtoRGB(hue,sat*0.9,val*0.7,secondarySemiActive.x,secondarySemiActive.y,secondarySemiActive.z); + ImGui::ColorConvertHSVtoRGB(hue,sat*0.9,val*0.9,secondaryHover.x,secondaryHover.y,secondaryHover.z); + ImGui::ColorConvertHSVtoRGB(hue,sat,val*0.5,secondaryActive.x,secondaryActive.y,secondaryActive.z); + } else { + ImGui::ColorConvertHSVtoRGB(hue,sat*0.9,val*0.75,secondarySemiActive.x,secondarySemiActive.y,secondarySemiActive.z); + ImGui::ColorConvertHSVtoRGB(hue,sat*0.9,val*0.5,secondaryHover.x,secondaryHover.y,secondaryHover.z); + ImGui::ColorConvertHSVtoRGB(hue,sat*0.9,val*0.25,secondary.x,secondary.y,secondary.z); + } + + + sty.Colors[ImGuiCol_WindowBg]=uiColors[GUI_COLOR_FRAME_BACKGROUND]; + sty.Colors[ImGuiCol_ModalWindowDimBg]=uiColors[GUI_COLOR_MODAL_BACKDROP]; + sty.Colors[ImGuiCol_Text]=uiColors[GUI_COLOR_TEXT]; + + sty.Colors[ImGuiCol_Button]=primary; + sty.Colors[ImGuiCol_ButtonHovered]=primaryHover; + sty.Colors[ImGuiCol_ButtonActive]=primaryActive; + sty.Colors[ImGuiCol_Tab]=primary; + sty.Colors[ImGuiCol_TabHovered]=secondaryHover; + sty.Colors[ImGuiCol_TabActive]=secondarySemiActive; + sty.Colors[ImGuiCol_TabUnfocused]=primary; + sty.Colors[ImGuiCol_TabUnfocusedActive]=primaryHover; + sty.Colors[ImGuiCol_Header]=secondary; + sty.Colors[ImGuiCol_HeaderHovered]=secondaryHover; + sty.Colors[ImGuiCol_HeaderActive]=secondaryActive; + sty.Colors[ImGuiCol_ResizeGrip]=secondary; + sty.Colors[ImGuiCol_ResizeGripHovered]=secondaryHover; + sty.Colors[ImGuiCol_ResizeGripActive]=secondaryActive; + sty.Colors[ImGuiCol_FrameBg]=secondary; + sty.Colors[ImGuiCol_FrameBgHovered]=secondaryHover; + sty.Colors[ImGuiCol_FrameBgActive]=secondaryActive; + sty.Colors[ImGuiCol_SliderGrab]=primaryActive; + sty.Colors[ImGuiCol_SliderGrabActive]=primaryActive; + sty.Colors[ImGuiCol_TitleBgActive]=primary; + sty.Colors[ImGuiCol_CheckMark]=primaryActive; + sty.Colors[ImGuiCol_TextSelectedBg]=secondaryHover; + sty.Colors[ImGuiCol_PlotHistogram]=uiColors[GUI_COLOR_MACRO_OTHER]; + sty.Colors[ImGuiCol_PlotHistogramHovered]=uiColors[GUI_COLOR_MACRO_OTHER]; + + if (settings.roundedWindows) sty.WindowRounding=8.0f; + if (settings.roundedButtons) { + sty.FrameRounding=6.0f; + sty.GrabRounding=6.0f; + } + if (settings.roundedMenus) sty.PopupRounding=8.0f; + + sty.ScaleAllSizes(dpiScale); + + ImGui::GetStyle()=sty; + + for (int i=0; i<256; i++) { + ImVec4& base=uiColors[GUI_COLOR_PATTERN_EFFECT_PITCH]; + pitchGrad[i]=ImGui::GetColorU32(ImVec4(base.x,base.y,base.z,((float)i/255.0f)*base.w)); + } + for (int i=0; i<256; i++) { + ImVec4& base=uiColors[GUI_COLOR_PATTERN_ACTIVE]; + noteGrad[i]=ImGui::GetColorU32(ImVec4(base.x,base.y,base.z,((float)i/255.0f)*base.w)); + } + for (int i=0; i<256; i++) { + ImVec4& base=uiColors[GUI_COLOR_PATTERN_EFFECT_PANNING]; + panGrad[i]=ImGui::GetColorU32(ImVec4(base.x,base.y,base.z,((float)i/255.0f)*base.w)); + } + for (int i=0; i<256; i++) { + ImVec4& base=uiColors[GUI_COLOR_PATTERN_INS]; + insGrad[i]=ImGui::GetColorU32(ImVec4(base.x,base.y,base.z,((float)i/255.0f)*base.w)); + } + for (int i=0; i<256; i++) { + ImVec4& base=volColors[i/2]; + volGrad[i]=ImGui::GetColorU32(ImVec4(base.x,base.y,base.z,((float)i/255.0f)*base.w)); + } + for (int i=0; i<256; i++) { + ImVec4& base=uiColors[GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY]; + sysCmd1Grad[i]=ImGui::GetColorU32(ImVec4(base.x,base.y,base.z,((float)i/255.0f)*base.w)); + } + for (int i=0; i<256; i++) { + ImVec4& base=uiColors[GUI_COLOR_PATTERN_EFFECT_SYS_SECONDARY]; + sysCmd2Grad[i]=ImGui::GetColorU32(ImVec4(base.x,base.y,base.z,((float)i/255.0f)*base.w)); + } + + // set to 800 for now due to problems with unifont + static const ImWchar loadEverything[]={0x20,0x800,0}; + + if (settings.mainFont<0 || settings.mainFont>6) settings.mainFont=0; + if (settings.patFont<0 || settings.patFont>6) settings.patFont=0; + + if (settings.mainFont==6 && settings.mainFontPath.empty()) { + logW("UI font path is empty! reverting to default font\n"); + settings.mainFont=0; + } + if (settings.patFont==6 && settings.patFontPath.empty()) { + logW("pattern font path is empty! reverting to default font\n"); + settings.patFont=0; + } + + if (settings.mainFont==6) { // custom font + if ((mainFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(settings.mainFontPath.c_str(),e->getConfInt("mainFontSize",18)*dpiScale,NULL,loadEverything))==NULL) { + logW("could not load UI font! reverting to default font\n"); + settings.mainFont=0; + if ((mainFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFont[settings.mainFont],builtinFontLen[settings.mainFont],e->getConfInt("mainFontSize",18)*dpiScale,NULL,loadEverything))==NULL) { + logE("could not load UI font! falling back to Proggy Clean.\n"); + mainFont=ImGui::GetIO().Fonts->AddFontDefault(); + } + } + } else if (settings.mainFont==5) { // system font + if ((mainFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_FONT_PATH_1,e->getConfInt("mainFontSize",18)*dpiScale,NULL,loadEverything))==NULL) { + if ((mainFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_FONT_PATH_2,e->getConfInt("mainFontSize",18)*dpiScale,NULL,loadEverything))==NULL) { + if ((mainFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_FONT_PATH_3,e->getConfInt("mainFontSize",18)*dpiScale,NULL,loadEverything))==NULL) { + logW("could not load UI font! reverting to default font\n"); + settings.mainFont=0; + if ((mainFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFont[settings.mainFont],builtinFontLen[settings.mainFont],e->getConfInt("mainFontSize",18)*dpiScale,NULL,loadEverything))==NULL) { + logE("could not load UI font! falling back to Proggy Clean.\n"); + mainFont=ImGui::GetIO().Fonts->AddFontDefault(); + } + } + } + } + } else { + if ((mainFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFont[settings.mainFont],builtinFontLen[settings.mainFont],e->getConfInt("mainFontSize",18)*dpiScale,NULL,loadEverything))==NULL) { + logE("could not load UI font! falling back to Proggy Clean.\n"); + mainFont=ImGui::GetIO().Fonts->AddFontDefault(); + } + } + + ImFontConfig fc; + fc.MergeMode=true; + fc.GlyphMinAdvanceX=e->getConfInt("iconSize",16)*dpiScale; + static const ImWchar fontRange[]={ICON_MIN_FA,ICON_MAX_FA,0}; + if ((iconFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(iconFont_compressed_data,iconFont_compressed_size,e->getConfInt("iconSize",16)*dpiScale,&fc,fontRange))==NULL) { + logE("could not load icon font!\n"); + } + if (settings.mainFontSize==settings.patFontSize && settings.patFont<5 && builtinFontM[settings.patFont]==builtinFont[settings.mainFont]) { + logD("using main font for pat font.\n"); + patFont=mainFont; + } else { + if (settings.patFont==6) { // custom font + if ((patFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(settings.patFontPath.c_str(),e->getConfInt("patFontSize",18)*dpiScale,NULL,loadEverything))==NULL) { + logW("could not load pattern font! reverting to default font\n"); + settings.patFont=0; + if ((patFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFontM[settings.patFont],builtinFontMLen[settings.patFont],e->getConfInt("patFontSize",18)*dpiScale,NULL,loadEverything))==NULL) { + logE("could not load pattern font! falling back to Proggy Clean.\n"); + patFont=ImGui::GetIO().Fonts->AddFontDefault(); + } + } + } else if (settings.patFont==5) { // system font + if ((patFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_PAT_FONT_PATH_1,e->getConfInt("patFontSize",18)*dpiScale,NULL,loadEverything))==NULL) { + if ((patFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_PAT_FONT_PATH_2,e->getConfInt("patFontSize",18)*dpiScale,NULL,loadEverything))==NULL) { + if ((patFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_PAT_FONT_PATH_3,e->getConfInt("patFontSize",18)*dpiScale,NULL,loadEverything))==NULL) { + logW("could not load pattern font! reverting to default font\n"); + settings.patFont=0; + if ((patFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFontM[settings.patFont],builtinFontMLen[settings.patFont],e->getConfInt("patFontSize",18)*dpiScale,NULL,loadEverything))==NULL) { + logE("could not load pattern font! falling back to Proggy Clean.\n"); + patFont=ImGui::GetIO().Fonts->AddFontDefault(); + } + } + } + } + } else { + if ((patFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFontM[settings.patFont],builtinFontMLen[settings.patFont],e->getConfInt("patFontSize",18)*dpiScale,NULL,loadEverything))==NULL) { + logE("could not load pattern font!\n"); + patFont=ImGui::GetIO().Fonts->AddFontDefault(); + } + } + } + if ((bigFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(font_plexSans_compressed_data,font_plexSans_compressed_size,40*dpiScale))==NULL) { + logE("could not load big UI font!\n"); + } + + // TODO: allow changing these colors. + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByTypeDir,"",ImVec4(0.0f,1.0f,1.0f,1.0f),ICON_FA_FOLDER_O); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByTypeFile,"",ImVec4(0.7f,0.7f,0.7f,1.0f),ICON_FA_FILE_O); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".fur",ImVec4(0.5f,1.0f,0.5f,1.0f),ICON_FA_FILE); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".fui",ImVec4(1.0f,0.5f,0.5f,1.0f),ICON_FA_FILE); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".fuw",ImVec4(1.0f,0.75f,0.5f,1.0f),ICON_FA_FILE); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".dmf",ImVec4(0.5f,1.0f,0.5f,1.0f),ICON_FA_FILE); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".dmp",ImVec4(1.0f,0.5f,0.5f,1.0f),ICON_FA_FILE); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".dmw",ImVec4(1.0f,0.75f,0.5f,1.0f),ICON_FA_FILE); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".wav",ImVec4(1.0f,1.0f,0.5f,1.0f),ICON_FA_FILE_AUDIO_O); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".vgm",ImVec4(1.0f,1.0f,0.5f,1.0f),ICON_FA_FILE_AUDIO_O); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".ttf",ImVec4(0.3f,1.0f,0.6f,1.0f),ICON_FA_FONT); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".otf",ImVec4(0.3f,1.0f,0.6f,1.0f),ICON_FA_FONT); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".ttc",ImVec4(0.3f,1.0f,0.6f,1.0f),ICON_FA_FONT); + + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".tfi",ImVec4(1.0f,0.5f,0.5f,1.0f),ICON_FA_FILE); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".vgi",ImVec4(1.0f,0.5f,0.5f,1.0f),ICON_FA_FILE); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".s3i",ImVec4(1.0f,0.5f,0.5f,1.0f),ICON_FA_FILE); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".sbi",ImVec4(1.0f,0.5f,0.5f,1.0f),ICON_FA_FILE); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".fti",ImVec4(1.0f,0.5f,0.5f,1.0f),ICON_FA_FILE); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".bti",ImVec4(1.0f,0.5f,0.5f,1.0f),ICON_FA_FILE); + + if (fileDialog!=NULL) delete fileDialog; + fileDialog=new FurnaceGUIFileDialog(settings.sysFileDialog); +} \ No newline at end of file diff --git a/src/gui/songInfo.cpp b/src/gui/songInfo.cpp new file mode 100644 index 000000000..e9caf5af9 --- /dev/null +++ b/src/gui/songInfo.cpp @@ -0,0 +1,173 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "gui.h" +#include "misc/cpp/imgui_stdlib.h" +#include "intConst.h" + +void FurnaceGUI::drawSongInfo() { + if (nextWindow==GUI_WINDOW_SONG_INFO) { + songInfoOpen=true; + ImGui::SetNextWindowFocus(); + nextWindow=GUI_WINDOW_NOTHING; + } + if (!songInfoOpen) return; + if (ImGui::Begin("Song Information",&songInfoOpen)) { + if (ImGui::BeginTable("NameAuthor",2,ImGuiTableFlags_SizingStretchProp)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,0.0); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.0); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Name"); + ImGui::TableNextColumn(); + float avail=ImGui::GetContentRegionAvail().x; + ImGui::SetNextItemWidth(avail); + if (ImGui::InputText("##Name",&e->song.name)) { MARK_MODIFIED + updateWindowTitle(); + } + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Author"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(avail); + if (ImGui::InputText("##Author",&e->song.author)) { + MARK_MODIFIED; + } + ImGui::EndTable(); + } + + if (ImGui::BeginTable("OtherProps",3,ImGuiTableFlags_SizingStretchProp)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,0.0); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.0); + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch,0.0); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("TimeBase"); + ImGui::TableNextColumn(); + float avail=ImGui::GetContentRegionAvail().x; + ImGui::SetNextItemWidth(avail); + unsigned char realTB=e->song.timeBase+1; + if (ImGui::InputScalar("##TimeBase",ImGuiDataType_U8,&realTB,&_ONE,&_THREE)) { MARK_MODIFIED + if (realTB<1) realTB=1; + if (realTB>16) realTB=16; + e->song.timeBase=realTB-1; + } + ImGui::TableNextColumn(); + ImGui::Text("%.2f BPM",calcBPM(e->song.speed1,e->song.speed2,e->song.hz)); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Speed"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(avail); + if (ImGui::InputScalar("##Speed1",ImGuiDataType_U8,&e->song.speed1,&_ONE,&_THREE)) { MARK_MODIFIED + if (e->song.speed1<1) e->song.speed1=1; + if (e->isPlaying()) play(); + } + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(avail); + if (ImGui::InputScalar("##Speed2",ImGuiDataType_U8,&e->song.speed2,&_ONE,&_THREE)) { MARK_MODIFIED + if (e->song.speed2<1) e->song.speed2=1; + if (e->isPlaying()) play(); + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Highlight"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(avail); + if (ImGui::InputScalar("##Highlight1",ImGuiDataType_U8,&e->song.hilightA,&_ONE,&_THREE)) { + MARK_MODIFIED; + } + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(avail); + if (ImGui::InputScalar("##Highlight2",ImGuiDataType_U8,&e->song.hilightB,&_ONE,&_THREE)) { + MARK_MODIFIED; + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Pattern Length"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(avail); + int patLen=e->song.patLen; + if (ImGui::InputInt("##PatLength",&patLen,1,3)) { MARK_MODIFIED + if (patLen<1) patLen=1; + if (patLen>256) patLen=256; + e->song.patLen=patLen; + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Song Length"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(avail); + int ordLen=e->song.ordersLen; + if (ImGui::InputInt("##OrdLength",&ordLen,1,3)) { MARK_MODIFIED + if (ordLen<1) ordLen=1; + if (ordLen>127) ordLen=127; + e->song.ordersLen=ordLen; + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + if (ImGui::Selectable(tempoView?"Base Tempo##TempoOrHz":"Tick Rate##TempoOrHz")) { + tempoView=!tempoView; + } + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(avail); + float setHz=tempoView?e->song.hz*2.5:e->song.hz; + if (ImGui::InputFloat("##Rate",&setHz,1.0f,1.0f,"%g")) { MARK_MODIFIED + if (tempoView) setHz/=2.5; + if (setHz<10) setHz=10; + if (setHz>999) setHz=999; + e->setSongRate(setHz,setHz<52); + } + if (tempoView) { + ImGui::TableNextColumn(); + ImGui::Text("= %gHz",e->song.hz); + } else { + if (e->song.hz>=49.98 && e->song.hz<=50.02) { + ImGui::TableNextColumn(); + ImGui::Text("PAL"); + } + if (e->song.hz>=59.9 && e->song.hz<=60.11) { + ImGui::TableNextColumn(); + ImGui::Text("NTSC"); + } + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Tuning (A-4)"); + ImGui::TableNextColumn(); + float tune=e->song.tuning; + ImGui::SetNextItemWidth(avail); + if (ImGui::InputFloat("##Tuning",&tune,1.0f,3.0f,"%g")) { MARK_MODIFIED + if (tune<220.0f) tune=220.0f; + if (tune>880.0f) tune=880.0f; + e->song.tuning=tune; + } + ImGui::EndTable(); + } + } + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_SONG_INFO; + ImGui::End(); +} \ No newline at end of file diff --git a/src/gui/songNotes.cpp b/src/gui/songNotes.cpp new file mode 100644 index 000000000..7c7cbdaa9 --- /dev/null +++ b/src/gui/songNotes.cpp @@ -0,0 +1,37 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "gui.h" +#include "misc/cpp/imgui_stdlib.h" + +// NOTE: please don't ask me to enable text wrap. +// Dear ImGui doesn't have that feature. D: +void FurnaceGUI::drawNotes() { + if (nextWindow==GUI_WINDOW_NOTES) { + notesOpen=true; + ImGui::SetNextWindowFocus(); + nextWindow=GUI_WINDOW_NOTHING; + } + if (!notesOpen) return; + if (ImGui::Begin("Song Comments",¬esOpen)) { + ImGui::InputTextMultiline("##SongNotes",&e->song.notes,ImGui::GetContentRegionAvail()); + } + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_NOTES; + ImGui::End(); +} \ No newline at end of file diff --git a/src/gui/stats.cpp b/src/gui/stats.cpp new file mode 100644 index 000000000..274be2eda --- /dev/null +++ b/src/gui/stats.cpp @@ -0,0 +1,50 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "gui.h" +#include + +void FurnaceGUI::drawStats() { + if (nextWindow==GUI_WINDOW_STATS) { + statsOpen=true; + ImGui::SetNextWindowFocus(); + nextWindow=GUI_WINDOW_NOTHING; + } + if (!statsOpen) return; + if (ImGui::Begin("Statistics",&statsOpen)) { + String adpcmAUsage=fmt::sprintf("%d/16384KB",e->adpcmAMemLen/1024); + String adpcmBUsage=fmt::sprintf("%d/16384KB",e->adpcmBMemLen/1024); + String qsoundUsage=fmt::sprintf("%d/16384KB",e->qsoundMemLen/1024); + String x1_010Usage=fmt::sprintf("%d/1024KB",e->x1_010MemLen/1024); + ImGui::Text("ADPCM-A"); + ImGui::SameLine(); + ImGui::ProgressBar(((float)e->adpcmAMemLen)/16777216.0f,ImVec2(-FLT_MIN,0),adpcmAUsage.c_str()); + ImGui::Text("ADPCM-B"); + ImGui::SameLine(); + ImGui::ProgressBar(((float)e->adpcmBMemLen)/16777216.0f,ImVec2(-FLT_MIN,0),adpcmBUsage.c_str()); + ImGui::Text("QSound"); + ImGui::SameLine(); + ImGui::ProgressBar(((float)e->qsoundMemLen)/16777216.0f,ImVec2(-FLT_MIN,0),qsoundUsage.c_str()); + ImGui::Text("X1-010"); + ImGui::SameLine(); + ImGui::ProgressBar(((float)e->x1_010MemLen)/1048576.0f,ImVec2(-FLT_MIN,0),x1_010Usage.c_str()); + } + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_STATS; + ImGui::End(); +} \ No newline at end of file diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp new file mode 100644 index 000000000..b61245f2c --- /dev/null +++ b/src/gui/sysConf.cpp @@ -0,0 +1,371 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "gui.h" + +void FurnaceGUI::drawSysConf(int i) { + unsigned int flags=e->song.systemFlags[i]; + bool restart=settings.restartOnFlagChange; + bool sysPal=flags&1; + switch (e->song.system[i]) { + case DIV_SYSTEM_YM2612: + case DIV_SYSTEM_YM2612_EXT: { + if (ImGui::RadioButton("NTSC (7.67MHz)",(flags&3)==0)) { + e->setSysFlags(i,(flags&0x80000000)|0,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("PAL (7.61MHz)",(flags&3)==1)) { + e->setSysFlags(i,(flags&0x80000000)|1,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("FM Towns (8MHz)",(flags&3)==2)) { + e->setSysFlags(i,(flags&0x80000000)|2,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("AtGames Genesis (6.13MHz)",(flags&3)==3)) { + e->setSysFlags(i,(flags&0x80000000)|3,restart); + updateWindowTitle(); + } + bool ladder=flags&0x80000000; + if (ImGui::Checkbox("Enable DAC distortion",&ladder)) { + e->setSysFlags(i,(flags&(~0x80000000))|(ladder?0x80000000:0),restart); + updateWindowTitle(); + } + break; + } + case DIV_SYSTEM_SMS: { + ImGui::Text("Clock rate:"); + if (ImGui::RadioButton("NTSC (3.58MHz)",(flags&3)==0)) { + e->setSysFlags(i,(flags&(~3))|0,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("PAL (3.55MHz)",(flags&3)==1)) { + e->setSysFlags(i,(flags&(~3))|1,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("BBC Micro (4MHz)",(flags&3)==2)) { + e->setSysFlags(i,(flags&(~3))|2,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("Half NTSC (1.79MHz)",(flags&3)==3)) { + e->setSysFlags(i,(flags&(~3))|3,restart); + updateWindowTitle(); + } + ImGui::Text("Chip type:"); + if (ImGui::RadioButton("Sega VDP/Master System",((flags>>2)&3)==0)) { + e->setSysFlags(i,(flags&(~12))|0,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("TI SN76489",((flags>>2)&3)==1)) { + e->setSysFlags(i,(flags&(~12))|4,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("TI SN76489 with Atari-like short noise",((flags>>2)&3)==2)) { + e->setSysFlags(i,(flags&(~12))|8,restart); + updateWindowTitle(); + } + /*if (ImGui::RadioButton("Game Gear",(flags>>2)==3)) { + e->setSysFlags(i,(flags&3)|12); + }*/ + + bool noPhaseReset=flags&16; + if (ImGui::Checkbox("Disable noise period change phase reset",&noPhaseReset)) { + e->setSysFlags(i,(flags&(~16))|(noPhaseReset<<4),restart); + updateWindowTitle(); + } + break; + } + case DIV_SYSTEM_OPLL: + case DIV_SYSTEM_OPLL_DRUMS: + case DIV_SYSTEM_VRC7: { + ImGui::Text("Clock rate:"); + if (ImGui::RadioButton("NTSC (3.58MHz)",(flags&15)==0)) { + e->setSysFlags(i,(flags&(~15))|0,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("PAL (3.55MHz)",(flags&15)==1)) { + e->setSysFlags(i,(flags&(~15))|1,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("BBC Micro (4MHz)",(flags&15)==2)) { + e->setSysFlags(i,(flags&(~15))|2,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("Half NTSC (1.79MHz)",(flags&15)==3)) { + e->setSysFlags(i,(flags&(~15))|3,restart); + updateWindowTitle(); + } + if (e->song.system[i]!=DIV_SYSTEM_VRC7) { + ImGui::Text("Patch set:"); + if (ImGui::RadioButton("Yamaha YM2413",((flags>>4)&15)==0)) { + e->setSysFlags(i,(flags&(~0xf0))|0,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("Yamaha YMF281",((flags>>4)&15)==1)) { + e->setSysFlags(i,(flags&(~0xf0))|0x10,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("Yamaha YM2423",((flags>>4)&15)==2)) { + e->setSysFlags(i,(flags&(~0xf0))|0x20,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("Konami VRC7",((flags>>4)&15)==3)) { + e->setSysFlags(i,(flags&(~0xf0))|0x30,restart); + updateWindowTitle(); + } + } + break; + } + case DIV_SYSTEM_YM2151: + if (ImGui::RadioButton("NTSC/X16 (3.58MHz)",flags==0)) { + e->setSysFlags(i,0,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("PAL (3.55MHz)",flags==1)) { + e->setSysFlags(i,1,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("X1/X68000 (4MHz)",flags==2)) { + e->setSysFlags(i,2,restart); + updateWindowTitle(); + } + break; + case DIV_SYSTEM_NES: + if (ImGui::RadioButton("NTSC (1.79MHz)",flags==0)) { + e->setSysFlags(i,0,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("PAL (1.67MHz)",flags==1)) { + e->setSysFlags(i,1,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("Dendy (1.77MHz)",flags==2)) { + e->setSysFlags(i,2,restart); + updateWindowTitle(); + } + break; + case DIV_SYSTEM_C64_8580: + case DIV_SYSTEM_C64_6581: + if (ImGui::RadioButton("NTSC (1.02MHz)",flags==0)) { + e->setSysFlags(i,0,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("PAL (0.99MHz)",flags==1)) { + e->setSysFlags(i,1,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("SSI 2001 (0.89MHz)",flags==2)) { + e->setSysFlags(i,2,restart); + updateWindowTitle(); + } + break; + case DIV_SYSTEM_AY8910: + case DIV_SYSTEM_AY8930: { + ImGui::Text("Clock rate:"); + if (ImGui::RadioButton("1.79MHz (ZX Spectrum NTSC/MSX)",(flags&15)==0)) { + e->setSysFlags(i,(flags&(~15))|0,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("1.77MHz (ZX Spectrum)",(flags&15)==1)) { + e->setSysFlags(i,(flags&(~15))|1,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("1.75MHz (ZX Spectrum)",(flags&15)==2)) { + e->setSysFlags(i,(flags&(~15))|2,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("2MHz (Atari ST/Sharp X1)",(flags&15)==3)) { + e->setSysFlags(i,(flags&(~15))|3,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("1.5MHz (Vectrex)",(flags&15)==4)) { + e->setSysFlags(i,(flags&(~15))|4,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("1MHz (Amstrad CPC)",(flags&15)==5)) { + e->setSysFlags(i,(flags&(~15))|5,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("0.89MHz (Sunsoft 5B)",(flags&15)==6)) { + e->setSysFlags(i,(flags&(~15))|6,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("1.67MHz (?)",(flags&15)==7)) { + e->setSysFlags(i,(flags&(~15))|7,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("0.83MHz (Sunsoft 5B on PAL)",(flags&15)==8)) { + e->setSysFlags(i,(flags&(~15))|8,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("1.10MHz (Gamate/VIC-20 PAL)",(flags&15)==9)) { + e->setSysFlags(i,(flags&(~15))|9,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("2^21Hz (Game Boy)",(flags&15)==10)) { + e->setSysFlags(i,(flags&(~15))|10,restart); + updateWindowTitle(); + } + if (e->song.system[i]==DIV_SYSTEM_AY8910) { + ImGui::Text("Chip type:"); + if (ImGui::RadioButton("AY-3-8910",(flags&0x30)==0)) { + e->setSysFlags(i,(flags&(~0x30))|0,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("YM2149(F)",(flags&0x30)==16)) { + e->setSysFlags(i,(flags&(~0x30))|16,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("Sunsoft 5B",(flags&0x30)==32)) { + e->setSysFlags(i,(flags&(~0x30))|32,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("AY-3-8914",(flags&0x30)==48)) { + e->setSysFlags(i,(flags&(~0x30))|48,restart); + updateWindowTitle(); + } + } + bool stereo=flags&0x40; + ImGui::BeginDisabled((flags&0x30)==32); + if (ImGui::Checkbox("Stereo##_AY_STEREO",&stereo)) { + e->setSysFlags(i,(flags&(~0x40))|(stereo?0x40:0),restart); + updateWindowTitle(); + } + ImGui::EndDisabled(); + break; + } + case DIV_SYSTEM_SAA1099: + if (ImGui::RadioButton("SAM Coupé (8MHz)",flags==0)) { + e->setSysFlags(i,0,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("NTSC (7.15MHz)",flags==1)) { + e->setSysFlags(i,1,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("PAL (7.09MHz)",flags==2)) { + e->setSysFlags(i,2,restart); + updateWindowTitle(); + } + break; + case DIV_SYSTEM_AMIGA: { + ImGui::Text("Stereo separation:"); + int stereoSep=(flags>>8)&127; + if (ImGui::SliderInt("##StereoSep",&stereoSep,0,127)) { + if (stereoSep<0) stereoSep=0; + if (stereoSep>127) stereoSep=127; + e->setSysFlags(i,(flags&(~0x7f00))|((stereoSep&127)<<8),restart); + updateWindowTitle(); + } rightClickable + if (ImGui::RadioButton("Amiga 500 (OCS)",(flags&2)==0)) { + e->setSysFlags(i,flags&(~2),restart); + } + if (ImGui::RadioButton("Amiga 1200 (AGA)",(flags&2)==2)) { + e->setSysFlags(i,(flags&(~2))|2,restart); + } + sysPal=flags&1; + if (ImGui::Checkbox("PAL",&sysPal)) { + e->setSysFlags(i,(flags&(~1))|sysPal,restart); + updateWindowTitle(); + } + bool bypassLimits=flags&4; + if (ImGui::Checkbox("Bypass frequency limits",&bypassLimits)) { + e->setSysFlags(i,(flags&(~4))|(bypassLimits<<2),restart); + updateWindowTitle(); + } + break; + } + case DIV_SYSTEM_PCSPKR: { + ImGui::Text("Speaker type:"); + if (ImGui::RadioButton("Unfiltered",(flags&3)==0)) { + e->setSysFlags(i,(flags&(~3))|0,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("Cone",(flags&3)==1)) { + e->setSysFlags(i,(flags&(~3))|1,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("Piezo",(flags&3)==2)) { + e->setSysFlags(i,(flags&(~3))|2,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("Use system beeper (Linux only!)",(flags&3)==3)) { + e->setSysFlags(i,(flags&(~3))|3,restart); + updateWindowTitle(); + } + break; + } + case DIV_SYSTEM_QSOUND: { + ImGui::Text("Echo delay:"); + int echoBufSize=2725 - (flags & 4095); + if (ImGui::SliderInt("##EchoBufSize",&echoBufSize,0,2725)) { + if (echoBufSize<0) echoBufSize=0; + if (echoBufSize>2725) echoBufSize=2725; + e->setSysFlags(i,(flags & ~4095) | ((2725 - echoBufSize) & 4095),restart); + updateWindowTitle(); + } rightClickable + ImGui::Text("Echo feedback:"); + int echoFeedback=(flags>>12)&255; + if (ImGui::SliderInt("##EchoFeedback",&echoFeedback,0,255)) { + if (echoFeedback<0) echoFeedback=0; + if (echoFeedback>255) echoFeedback=255; + e->setSysFlags(i,(flags & ~(255 << 12)) | ((echoFeedback & 255) << 12),restart); + updateWindowTitle(); + } rightClickable + break; + } + case DIV_SYSTEM_X1_010: { + ImGui::Text("Clock rate:"); + if (ImGui::RadioButton("16MHz (Seta 1)",(flags&15)==0)) { + e->setSysFlags(i,(flags&(~15))|0,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("16.67MHz (Seta 2)",(flags&15)==1)) { + e->setSysFlags(i,(flags&(~15))|1,restart); + updateWindowTitle(); + } + bool x1_010Stereo=flags&16; + if (ImGui::Checkbox("Stereo",&x1_010Stereo)) { + e->setSysFlags(i,(flags&(~16))|(x1_010Stereo<<4),restart); + updateWindowTitle(); + } + break; + } + case DIV_SYSTEM_GB: + case DIV_SYSTEM_SWAN: + case DIV_SYSTEM_VERA: + case DIV_SYSTEM_BUBSYS_WSG: + case DIV_SYSTEM_YM2610: + case DIV_SYSTEM_YM2610_EXT: + case DIV_SYSTEM_YM2610_FULL: + case DIV_SYSTEM_YM2610_FULL_EXT: + case DIV_SYSTEM_YM2610B: + case DIV_SYSTEM_YM2610B_EXT: + case DIV_SYSTEM_YMU759: + case DIV_SYSTEM_PET: + ImGui::Text("nothing to configure"); + break; + default: + if (ImGui::Checkbox("PAL",&sysPal)) { + e->setSysFlags(i,sysPal,restart); + updateWindowTitle(); + } + break; + } +} \ No newline at end of file diff --git a/src/gui/volMeter.cpp b/src/gui/volMeter.cpp new file mode 100644 index 000000000..29f62c969 --- /dev/null +++ b/src/gui/volMeter.cpp @@ -0,0 +1,111 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "gui.h" +#include "imgui_internal.h" + +void FurnaceGUI::drawVolMeter() { + if (nextWindow==GUI_WINDOW_VOL_METER) { + volMeterOpen=true; + ImGui::SetNextWindowFocus(); + nextWindow=GUI_WINDOW_NOTHING; + } + if (!volMeterOpen) return; + if (--isClipping<0) isClipping=0; + ImGui::SetNextWindowSizeConstraints(ImVec2(6.0f*dpiScale,6.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding,ImVec2(0,0)); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,ImVec2(0,0)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,ImVec2(0,0)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemInnerSpacing,ImVec2(0,0)); + if (ImGui::Begin("Volume Meter",&volMeterOpen)) { + ImDrawList* dl=ImGui::GetWindowDrawList(); + bool aspectRatio=(ImGui::GetWindowSize().x/ImGui::GetWindowSize().y)>1.0; + + ImVec2 minArea=ImVec2( + ImGui::GetWindowPos().x+ImGui::GetCursorPos().x, + ImGui::GetWindowPos().y+ImGui::GetCursorPos().y + ); + ImVec2 maxArea=ImVec2( + ImGui::GetWindowPos().x+ImGui::GetCursorPos().x+ImGui::GetContentRegionAvail().x, + ImGui::GetWindowPos().y+ImGui::GetCursorPos().y+ImGui::GetContentRegionAvail().y + ); + ImRect rect=ImRect(minArea,maxArea); + ImGuiStyle& style=ImGui::GetStyle(); + ImGui::ItemSize(ImVec2(4.0f,4.0f),style.FramePadding.y); + ImU32 lowColor=ImGui::GetColorU32(uiColors[GUI_COLOR_VOLMETER_LOW]); + float peakDecay=0.05f*60.0f*ImGui::GetIO().DeltaTime; + if (ImGui::ItemAdd(rect,ImGui::GetID("volMeter"))) { + ImGui::RenderFrame(rect.Min,rect.Max,ImGui::GetColorU32(ImGuiCol_FrameBg),true,style.FrameRounding); + for (int i=0; i<2; i++) { + peak[i]*=1.0-peakDecay; + if (peak[i]<0.0001) peak[i]=0.0; + for (int j=0; joscSize; j++) { + if (fabs(e->oscBuf[i][j])>peak[i]) { + peak[i]=fabs(e->oscBuf[i][j]); + } + } + float logPeak=(20*log10(peak[i])/36.0); + if (logPeak==NAN) logPeak=0.0; + if (logPeak<-1.0) logPeak=-1.0; + if (logPeak>0.0) { + isClipping=8; + logPeak=0.0; + } + logPeak+=1.0; + ImU32 highColor=ImGui::GetColorU32( + ImLerp(uiColors[GUI_COLOR_VOLMETER_LOW],uiColors[GUI_COLOR_VOLMETER_HIGH],logPeak) + ); + ImRect s; + if (aspectRatio) { + s=ImRect( + ImLerp(rect.Min,rect.Max,ImVec2(0,float(i)*0.5)), + ImLerp(rect.Min,rect.Max,ImVec2(logPeak,float(i+1)*0.5)) + ); + if (i==0) s.Max.y-=dpiScale; + if (isClipping) { + dl->AddRectFilled(s.Min,s.Max,ImGui::GetColorU32(uiColors[GUI_COLOR_VOLMETER_PEAK])); + } else { + dl->AddRectFilledMultiColor(s.Min,s.Max,lowColor,highColor,highColor,lowColor); + } + } else { + s=ImRect( + ImLerp(rect.Min,rect.Max,ImVec2(float(i)*0.5,1.0-logPeak)), + ImLerp(rect.Min,rect.Max,ImVec2(float(i+1)*0.5,1.0)) + ); + if (i==0) s.Max.x-=dpiScale; + if (isClipping) { + dl->AddRectFilled(s.Min,s.Max,ImGui::GetColorU32(uiColors[GUI_COLOR_VOLMETER_PEAK])); + } else { + dl->AddRectFilledMultiColor(s.Min,s.Max,highColor,highColor,lowColor,lowColor); + } + } + } + if (ImGui::IsItemHovered()) { + if (aspectRatio) { + ImGui::SetTooltip("%.1fdB",36*((ImGui::GetMousePos().x-ImGui::GetItemRectMin().x)/(rect.Max.x-rect.Min.x)-1.0)); + } else { + ImGui::SetTooltip("%.1fdB",-(36+36*((ImGui::GetMousePos().y-ImGui::GetItemRectMin().y)/(rect.Max.y-rect.Min.y)-1.0))); + } + } + } + } + ImGui::PopStyleVar(4); + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_VOL_METER; + ImGui::End(); +} \ No newline at end of file diff --git a/src/gui/waveEdit.cpp b/src/gui/waveEdit.cpp index 09ec95b1c..3fba43f70 100644 --- a/src/gui/waveEdit.cpp +++ b/src/gui/waveEdit.cpp @@ -1,3 +1,22 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + #include "gui.h" #include "plot_nolerp.h" #include "misc/cpp/imgui_stdlib.h" From 80927b80b12014205ff80fb47d14f1f07d106ec0 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 21 Mar 2022 17:38:11 -0500 Subject: [PATCH 352/637] fix build --- src/gui/about.cpp | 4 +++- src/gui/actionUtil.h | 5 +++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/gui/about.cpp b/src/gui/about.cpp index cd5888c61..4d1322a5a 100644 --- a/src/gui/about.cpp +++ b/src/gui/about.cpp @@ -17,7 +17,9 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#define _USE_MATH_DEFINES #include "gui.h" +#include const char* aboutLine[]={ "tildearrow", @@ -208,4 +210,4 @@ void FurnaceGUI::drawAbout() { } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_ABOUT; ImGui::End(); -} \ No newline at end of file +} diff --git a/src/gui/actionUtil.h b/src/gui/actionUtil.h index 38ed9a5cd..4e9196afc 100644 --- a/src/gui/actionUtil.h +++ b/src/gui/actionUtil.h @@ -5,7 +5,7 @@ firstChannel=i; \ break; \ } \ - } \ + } #define DETERMINE_LAST \ int lastChannel=0; \ @@ -18,4 +18,5 @@ #define DETERMINE_FIRST_LAST \ DETERMINE_FIRST \ - DETERMINE_LAST \ No newline at end of file + DETERMINE_LAST + From 6050fc72082ce3ad5e3f619895baf48336a380ab Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 21 Mar 2022 17:56:48 -0500 Subject: [PATCH 353/637] one more race condition fix --- src/engine/engine.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 2b3283917..fb11f1df6 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -614,9 +614,11 @@ void DivEngine::createNew(const int* description) { void DivEngine::changeSystem(int index, DivSystem which) { quitDispatch(); isBusy.lock(); + saveLock.lock(); song.system[index]=which; song.systemFlags[index]=0; recalcChans(); + saveLock.unlock(); isBusy.unlock(); initDispatch(); isBusy.lock(); @@ -637,11 +639,13 @@ bool DivEngine::addSystem(DivSystem which) { } quitDispatch(); isBusy.lock(); + saveLock.lock(); song.system[song.systemLen]=which; song.systemVol[song.systemLen]=64; song.systemPan[song.systemLen]=0; song.systemFlags[song.systemLen++]=0; recalcChans(); + saveLock.unlock(); isBusy.unlock(); initDispatch(); isBusy.lock(); @@ -662,12 +666,14 @@ bool DivEngine::removeSystem(int index) { } quitDispatch(); isBusy.lock(); + saveLock.lock(); song.system[index]=DIV_SYSTEM_NULL; song.systemLen--; for (int i=index; i Date: Mon, 21 Mar 2022 17:59:48 -0500 Subject: [PATCH 354/637] update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 15f0dcd2f..b5e28cf6a 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ![screenshot](papers/screenshot1.png) -this is a work-in-progress chiptune tracker compatible with DefleMask modules (.dmf). +a multi-system chiptune tracker. [downloads](#downloads) | [discussion](#discussion) | [help](#help) | [developer info](#developer-info) @@ -23,6 +23,7 @@ this is a work-in-progress chiptune tracker compatible with DefleMask modules (. - Amiga - TIA (Atari 2600/7800) - multiple sound chips in a single song! +- DefleMask compatibility - loads .dmf modules, .dmp instruments and .dmw wavetables - clean-room design (guesswork and ABX tests only, no decompilation involved) - bug/quirk implementation for increased playback accuracy - VGM and audio file export From 322981b0215c60e2c1bfc7aad52e55811beb4af5 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 21 Mar 2022 21:26:36 -0500 Subject: [PATCH 355/637] maybe fix possible crash regarding smp loop points --- src/engine/platform/amiga.cpp | 2 +- src/engine/platform/genesis.cpp | 4 ++-- src/engine/platform/nes.cpp | 2 +- src/engine/platform/pce.cpp | 2 +- src/engine/platform/segapcm.cpp | 2 +- src/engine/platform/swan.cpp | 2 +- src/engine/platform/vera.cpp | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/engine/platform/amiga.cpp b/src/engine/platform/amiga.cpp index 49b5b59ab..d160d1125 100644 --- a/src/engine/platform/amiga.cpp +++ b/src/engine/platform/amiga.cpp @@ -75,7 +75,7 @@ void DivPlatformAmiga::acquire(short* bufL, short* bufR, size_t start, size_t le if (s->samples>0) { chan[i].audDat=s->data8[chan[i].audPos++]; if (chan[i].audPos>=s->samples || chan[i].audPos>=131071) { - if (s->loopStart>=0 && s->loopStart<=(int)s->samples) { + if (s->loopStart>=0 && s->loopStart<(int)s->samples) { chan[i].audPos=s->loopStart; } else { chan[i].sample=-1; diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index 75c54af68..46e243332 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -95,7 +95,7 @@ void DivPlatformGenesis::acquire_nuked(short* bufL, short* bufR, size_t start, s urgentWrite(0x2a,(unsigned char)s->data8[dacPos]+0x80); } if (++dacPos>=s->samples) { - if (s->loopStart>=0 && s->loopStart<=(int)s->samples) { + if (s->loopStart>=0 && s->loopStart<(int)s->samples) { dacPos=s->loopStart; } else { dacSample=-1; @@ -162,7 +162,7 @@ void DivPlatformGenesis::acquire_ymfm(short* bufL, short* bufR, size_t start, si urgentWrite(0x2a,(unsigned char)s->data8[dacPos]+0x80); } if (++dacPos>=s->samples) { - if (s->loopStart>=0 && s->loopStart<=(int)s->samples) { + if (s->loopStart>=0 && s->loopStart<(int)s->samples) { dacPos=s->loopStart; } else { dacSample=-1; diff --git a/src/engine/platform/nes.cpp b/src/engine/platform/nes.cpp index 06fbc725c..d04edf4de 100644 --- a/src/engine/platform/nes.cpp +++ b/src/engine/platform/nes.cpp @@ -81,7 +81,7 @@ void DivPlatformNES::acquire(short* bufL, short* bufR, size_t start, size_t len) rWrite(0x4011,((unsigned char)s->data8[dacPos]+0x80)>>1); } if (++dacPos>=s->samples) { - if (s->loopStart>=0 && s->loopStart<=(int)s->samples) { + if (s->loopStart>=0 && s->loopStart<(int)s->samples) { dacPos=s->loopStart; } else { dacSample=-1; diff --git a/src/engine/platform/pce.cpp b/src/engine/platform/pce.cpp index 1af1741c5..bf7d0d8db 100644 --- a/src/engine/platform/pce.cpp +++ b/src/engine/platform/pce.cpp @@ -91,7 +91,7 @@ void DivPlatformPCE::acquire(short* bufL, short* bufR, size_t start, size_t len) chWrite(i,0x06,(((unsigned char)s->data8[chan[i].dacPos]+0x80)>>3)); chan[i].dacPos++; if (chan[i].dacPos>=s->samples) { - if (s->loopStart>=0 && s->loopStart<=(int)s->samples) { + if (s->loopStart>=0 && s->loopStart<(int)s->samples) { chan[i].dacPos=s->loopStart; } else { chan[i].dacSample=-1; diff --git a/src/engine/platform/segapcm.cpp b/src/engine/platform/segapcm.cpp index 7872e0b69..c0366a18a 100644 --- a/src/engine/platform/segapcm.cpp +++ b/src/engine/platform/segapcm.cpp @@ -54,7 +54,7 @@ void DivPlatformSegaPCM::acquire(short* bufL, short* bufR, size_t start, size_t } chan[i].pcm.pos+=chan[i].pcm.freq; if (chan[i].pcm.pos>=(s->samples<<8)) { - if (s->loopStart>=0 && s->loopStart<=(int)s->samples) { + if (s->loopStart>=0 && s->loopStart<(int)s->samples) { chan[i].pcm.pos=s->loopStart<<8; } else { chan[i].pcm.sample=-1; diff --git a/src/engine/platform/swan.cpp b/src/engine/platform/swan.cpp index de6236478..0f32e9a08 100644 --- a/src/engine/platform/swan.cpp +++ b/src/engine/platform/swan.cpp @@ -84,7 +84,7 @@ void DivPlatformSwan::acquire(short* bufL, short* bufR, size_t start, size_t len } rWrite(0x09,(unsigned char)s->data8[dacPos++]+0x80); if (dacPos>=s->samples) { - if (s->loopStart>=0 && s->loopStart<=(int)s->samples) { + if (s->loopStart>=0 && s->loopStart<(int)s->samples) { dacPos=s->loopStart; } else { dacSample=-1; diff --git a/src/engine/platform/vera.cpp b/src/engine/platform/vera.cpp index a4d76aa08..522e4be6f 100644 --- a/src/engine/platform/vera.cpp +++ b/src/engine/platform/vera.cpp @@ -97,7 +97,7 @@ void DivPlatformVERA::acquire(short* bufL, short* bufR, size_t start, size_t len } chan[16].pcm.pos++; if (chan[16].pcm.pos>=s->samples) { - if (s->loopStart>=0 && s->loopStart<=(int)s->samples) { + if (s->loopStart>=0 && s->loopStart<(int)s->samples) { chan[16].pcm.pos=s->loopStart; } else { chan[16].pcm.sample=-1; From 333051f82ec54bfbb45ceb3e9c253fedd8171ab0 Mon Sep 17 00:00:00 2001 From: nicco1690 <78063037+nicco1690@users.noreply.github.com> Date: Mon, 21 Mar 2022 22:34:50 -0400 Subject: [PATCH 356/637] Potential README.MD formatting overhaul I didn't like the way that the original README.MD was formatted, so I decided to change some of it to make it more consistent and be more clear. --- README.md | 72 +++++++++++++++++++++++++------------------------------ 1 file changed, 33 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index b5e28cf6a..b921a47c7 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,11 @@ ![screenshot](papers/screenshot1.png) -a multi-system chiptune tracker. +Furnace, a multi-system chiptune tracker. -[downloads](#downloads) | [discussion](#discussion) | [help](#help) | [developer info](#developer-info) +Jump to a section: [downloads](#downloads) | [discussion](#discussion) | [help](#help) | [developer info](#developer-info) | [unofficial packages](#unofficial-packages) | [faq](#faq) +*** ## features - supports the following systems: @@ -38,40 +39,26 @@ a multi-system chiptune tracker. - ability to change tempo mid-song with `Cxxx` effect (`xxx` between `000` and `3ff`) - open-source under GPLv2 or later. -## downloads +*** +# quick references -check out the [Releases](https://github.com/tildearrow/furnace/releases) page. available for Windows, macOS and Linux (AppImage). - -## discussion - -see the [Discussions](https://github.com/tildearrow/furnace/discussions) section, or (preferably) the [official Discord server](https://discord.gg/EfrwT2wq7z). - -## help - -check out the [documentation](papers/doc/README.md). it's mostly incomplete, but has details on effects. - -# unofficial packages + - **downloads**: check out the [Releases](https://github.com/tildearrow/furnace/releases) page. available for Windows, macOS and Linux (AppImage). + - **discussion**: see the [Discussions](https://github.com/tildearrow/furnace/discussions) section, or (preferably) the [official Discord server](https://discord.gg/EfrwT2wq7z). + - **help**: check out the [documentation](papers/doc/README.md). it's mostly incomplete, but has details on effects. +## unofficial packages [![Packaging status](https://repology.org/badge/tiny-repos/furnace.svg)](https://repology.org/project/furnace/versions) some people have provided packages for Unix/Unix-like distributions. here's a list. + - **Arch Linux**: [furnace-git is in the AUR.](https://aur.archlinux.org/packages/furnace-git) thank you Essem! + - **FreeBSD**: [a package in ports](https://www.freshports.org/audio/furnace/) is available courtesy of ehaupt. + - **Nix**: [package](https://search.nixos.org/packages?channel=unstable&show=furnace&from=0&size=50&sort=relevance&type=packages&query=furnace) thanks to OPNA2608. -## Arch Linux - -[furnace-git is in the AUR.](https://aur.archlinux.org/packages/furnace-git) thank you Essem! - -## FreeBSD - -[a package in ports](https://www.freshports.org/audio/furnace/) is available courtesy of ehaupt. - -## Nix - -[package](https://search.nixos.org/packages?channel=unstable&show=furnace&from=0&size=50&sort=relevance&type=packages&query=furnace) thanks to OPNA2608. - +*** # developer info [![Build furnace](https://github.com/tildearrow/furnace/actions/workflows/build.yml/badge.svg)](https://github.com/tildearrow/furnace/actions/workflows/build.yml) -**NOTE: do not download the project's source as a .zip or .tar.gz as these do not include the project's submodules which are necessary to proceed with building.** +**NOTE: do not download the project's source as a .zip or .tar.gz as these do not include the project's submodules which are necessary to proceed with building. please instead use Git as shown below.** ## dependencies @@ -118,6 +105,7 @@ cd build cmake .. make ``` +Alternatively, build scripts are provided in the `scripts/` folder in the root of the repository. ### CMake options @@ -156,6 +144,9 @@ this will play a compatible file. this will play a compatible file and enable the commands view. +**note that these commands only actually work in Linux environments. on other command lines, such as Windows' Command Prompt, or MacOS Terminal, it may not work correctly.** + +*** # notes > how do I use Neo Geo SSG envelopes? @@ -182,6 +173,9 @@ the following effects are provided: a lower envelope period will make the envelope run faster. +*** +# faq + > how do I use C64 absolute filter/duty? on Instrument Editor in the C64 tab there are two options to toggle these. @@ -189,29 +183,29 @@ also provided are two effects: - `3xxx`: set fine duty. - `4xxx`: set fine cutoff. `xxx` range is 000-7ff. +additionally, you can change the cutoff and/or duty as a macro inside an instrument by clicking the `absolute cutoff macro` and/or `absolute duty macro` checkbox at the bottom of the instrument. (for the filter, you also need to click the checkbox that says `volume macro is cutoff macro`.) -> how do I use PCM on a PCM-capable system? +> Q: how do I use PCM on a PCM-capable system? -Two possibilities: the recommended way is via creating the "Amiga/Sample" type instrument and assigning sample to it, or via old, Deflemask-compatible method, using `17xx` effect +A: Two possibilities: the recommended way is via creating the "Amiga/Sample" type instrument and assigning sample to it, or via old, Deflemask-compatible method, using `17xx` effect -> my song sounds very odd at a certain point +> Q: my song sounds very odd at a certain point -file a bug report. use the Issues page. +A: file a bug report. use the Issues page. it's probably another playback inaccuracy. -it's probably another playback inaccuracy. +> Q: my song sounds correct, but it doesn't in DefleMask -> my song sounds correct, but it doesn't in DefleMask +A: file a bug report **here**. it still is a playback inaccuracy. -file a bug report **here**. it still is a playback inaccuracy. +> Q: my C64 song sounds terrible after saving as .dmf! -> my C64 song sounds terrible after saving as .dmf! +A: that's a limitation of the DefleMask format. save in Furnace song format instead (.fur). -that's a limitation of the DefleMask format. save in Furnace song format instead (.fur). +> Q: how do I solo channels? -> how do I solo channels? - -right click on the channel name. +A: right click on the channel name. +*** # footnotes copyright (C) 2021-2022 tildearrow and contributors. From cec5def34733a39f9b43f52deab5bf82ab7a4b10 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 21 Mar 2022 21:47:27 -0500 Subject: [PATCH 357/637] what? unacceptable. --- src/engine/fileOps.cpp | 7 +++++++ src/engine/sample.cpp | 1 + src/gui/gui.cpp | 2 +- 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 460f7d8d8..b50eb74ae 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -1030,6 +1030,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { reader.read(samplePtr,ds.sampleLen*4); for (int i=0; ireadInsData(reader,ds.version)!=DIV_DATA_SUCCESS) { @@ -1084,6 +1086,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { // read wavetables for (int i=0; ireadWaveData(reader,ds.version)!=DIV_DATA_SUCCESS) { @@ -1111,6 +1114,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { } reader.readI(); DivSample* sample=new DivSample; + logD("reading sample %d at %x...\n",i,samplePtr[i]); sample->name=reader.readString(); sample->samples=reader.readI(); @@ -1184,6 +1188,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { for (int i: patPtr) { reader.seek(i,SEEK_SET); reader.read(magic,4); + logD("reading pattern in %x...\n",i); if (strcmp(magic,"PATR")!=0) { logE("%x: invalid pattern header!\n",i); lastError="invalid pattern header!"; @@ -1196,6 +1201,8 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { int index=reader.readS(); reader.readI(); + logD("- %d, %d\n",chan,index); + DivPattern* pat=ds.pat[chan].getPattern(index,true); for (int j=0; jdata[j][0]=reader.readS(); diff --git a/src/engine/sample.cpp b/src/engine/sample.cpp index be9c5029c..69e61e547 100644 --- a/src/engine/sample.cpp +++ b/src/engine/sample.cpp @@ -279,6 +279,7 @@ bool DivSample::trim(unsigned int begin, unsigned int end) { #define RESAMPLE_END \ samples=finalCount; \ + if (loopStart>=0) loopStart=(double)loopStart*(r/(double)rate); \ if (depth==16) { \ delete[] oldData16; \ } else if (depth==8) { \ diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 2ba2131cc..7a1464254 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2435,9 +2435,9 @@ bool FurnaceGUI::loop() { if (outFile!=NULL) { if (fwrite(w->getFinalBuf(),1,w->size(),outFile)!=w->size()) { logW("did not write backup entirely: %s!\n",strerror(errno)); - fclose(outFile); w->finish(); } + fclose(outFile); } else { logW("could not save backup: %s!\n",strerror(errno)); w->finish(); From bd84dc1c17bf66a161012672818c082a5579f471 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 21 Mar 2022 21:48:36 -0500 Subject: [PATCH 358/637] GUI: make clang happy --- src/gui/gui.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 7a1464254..4af736eeb 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1745,7 +1745,7 @@ bool FurnaceGUI::loop() { } } if (macroDragActive || macroLoopDragActive || waveDragActive || sampleDragActive) { - int distance=fabs(motionXrel); + int distance=fabs((double)motionXrel); if (distance<1) distance=1; float start=motionX-motionXrel; float end=motionX; From 1996913214f18a46e45cf1c23d16233157b88923 Mon Sep 17 00:00:00 2001 From: nicco1690 <78063037+nicco1690@users.noreply.github.com> Date: Mon, 21 Mar 2022 22:58:07 -0400 Subject: [PATCH 359/637] Change README.MD to be better Didn't let me fit "change README.MD according to Tildearrow's suggestions" :P --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index b921a47c7..b03478e1f 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,14 @@ ![screenshot](papers/screenshot1.png) -Furnace, a multi-system chiptune tracker. +Furnace tracker, a multi-system chiptune tracker. -Jump to a section: [downloads](#downloads) | [discussion](#discussion) | [help](#help) | [developer info](#developer-info) | [unofficial packages](#unofficial-packages) | [faq](#faq) +[downloads](#downloads) | [discussion](#discussion) | [help](#help) | [developer info](#developer-info) | [unofficial packages](#unofficial-packages) | [faq](#faq) *** +## downloads +check out the [Releases](https://github.com/tildearrow/furnace/releases) page. available for Windows, macOS and Linux (AppImage). + ## features - supports the following systems: @@ -41,8 +44,6 @@ Jump to a section: [downloads](#downloads) | [discussion](#discussion) | [help]( *** # quick references - - - **downloads**: check out the [Releases](https://github.com/tildearrow/furnace/releases) page. available for Windows, macOS and Linux (AppImage). - **discussion**: see the [Discussions](https://github.com/tildearrow/furnace/discussions) section, or (preferably) the [official Discord server](https://discord.gg/EfrwT2wq7z). - **help**: check out the [documentation](papers/doc/README.md). it's mostly incomplete, but has details on effects. ## unofficial packages From e256efa6416cc99f5ea124fb0db78c44fd895e16 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 21 Mar 2022 23:36:17 -0500 Subject: [PATCH 360/637] GUI: add option to load Japanese chars issue #52 --- src/gui/fonts.h | 5 +++ src/gui/gui.cpp | 5 +++ src/gui/gui.h | 3 ++ src/gui/settings.cpp | 77 ++++++++++++++++++++++++++++++++++---------- 4 files changed, 73 insertions(+), 17 deletions(-) diff --git a/src/gui/fonts.h b/src/gui/fonts.h index 8f808a061..a0a42d251 100644 --- a/src/gui/fonts.h +++ b/src/gui/fonts.h @@ -46,3 +46,8 @@ extern const unsigned int builtinFontLen[]; extern const unsigned int* builtinFontM[]; extern const unsigned int builtinFontMLen[]; #endif + +// "(Glimmer of hope: the atlas system will be rewritten in the future to make scaling more flexible.)"" +// not just that. somebody rewrite it already so I can load these glyphs at run-time and support +// all languages at once, instead of having to load Unifont's 65k+ characters and blow the GPU up in the +// process! \ No newline at end of file diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 4af736eeb..ddb7433d4 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2699,6 +2699,11 @@ FurnaceGUI::FurnaceGUI(): aboutSin(0), aboutHue(0.0f), backupTimer(15.0), + mainFont(NULL), + iconFont(NULL), + patFont(NULL), + bigFont(NULL), + fontRange(NULL), curIns(0), curWave(0), curSample(0), diff --git a/src/gui/gui.h b/src/gui/gui.h index bfecdbb13..7433750ea 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -530,6 +530,7 @@ class FurnaceGUI { ImFont* iconFont; ImFont* patFont; ImFont* bigFont; + ImWchar* fontRange; ImVec4 uiColors[GUI_COLOR_MAX]; ImVec4 volColors[128]; ImU32 pitchGrad[256]; @@ -584,6 +585,7 @@ class FurnaceGUI { int roundedWindows; int roundedButtons; int roundedMenus; + int loadJapanese; unsigned int maxUndoSteps; String mainFontPath; String patFontPath; @@ -633,6 +635,7 @@ class FurnaceGUI { roundedWindows(1), roundedButtons(1), roundedMenus(0), + loadJapanese(0), maxUndoSteps(100), mainFontPath(""), patFontPath(""), diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index a3fd347d6..5348bfa06 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -346,6 +346,19 @@ void FurnaceGUI::drawSettings() { if (settings.patFontSize>96) settings.patFontSize=96; } + bool loadJapaneseB=settings.loadJapanese; + if (ImGui::Checkbox("Display Japanese characters",&loadJapaneseB)) { + settings.loadJapanese=loadJapaneseB; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip( + "Only toggle this option if you have enough graphics memory.\n" + "This is a temporary solution until dynamic font atlas is implemented in Dear ImGui.\n\n" + "このオプションは、十分なグラフィックメモリがある場合にのみ切り替えてください。\n" + "これは、Dear ImGuiにダイナミックフォントアトラスが実装されるまでの一時的な解決策です。" + ); + } + ImGui::Separator(); ImGui::Text("Orders row number format:"); @@ -927,6 +940,7 @@ void FurnaceGUI::syncSettings() { settings.roundedWindows=e->getConfInt("roundedWindows",1); settings.roundedButtons=e->getConfInt("roundedButtons",1); settings.roundedMenus=e->getConfInt("roundedMenus",0); + settings.loadJapanese=e->getConfInt("loadJapanese",0); clampSetting(settings.mainFontSize,2,96); clampSetting(settings.patFontSize,2,96); @@ -970,6 +984,7 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.roundedWindows,0,1); clampSetting(settings.roundedButtons,0,1); clampSetting(settings.roundedMenus,0,1); + clampSetting(settings.loadJapanese,0,1); // keybinds LOAD_KEYBIND(GUI_ACTION_OPEN,FURKMOD_CMD|SDLK_o); @@ -1175,6 +1190,7 @@ void FurnaceGUI::commitSettings() { e->setConf("roundedWindows",settings.roundedWindows); e->setConf("roundedButtons",settings.roundedButtons); e->setConf("roundedMenus",settings.roundedMenus); + e->setConf("loadJapanese",settings.loadJapanese); PUT_UI_COLOR(GUI_COLOR_BACKGROUND); PUT_UI_COLOR(GUI_COLOR_FRAME_BACKGROUND); @@ -1710,7 +1726,24 @@ void FurnaceGUI::applyUISettings() { } // set to 800 for now due to problems with unifont - static const ImWchar loadEverything[]={0x20,0x800,0}; + static const ImWchar upTo800[]={0x20,0x7e,0xa0,0x800,0}; + ImFontGlyphRangesBuilder range; + ImVector outRange; + + range.AddRanges(upTo800); + if (settings.loadJapanese) { + range.AddRanges(ImGui::GetIO().Fonts->GetGlyphRangesJapanese()); + } + // I'm terribly sorry + range.UsedChars[0x80>>5]=0; + + range.BuildRanges(&outRange); + if (fontRange!=NULL) delete[] fontRange; + fontRange=new ImWchar[outRange.size()]; + int index=0; + for (ImWchar& i: outRange) { + fontRange[index++]=i; + } if (settings.mainFont<0 || settings.mainFont>6) settings.mainFont=0; if (settings.patFont<0 || settings.patFont>6) settings.patFont=0; @@ -1724,22 +1757,25 @@ void FurnaceGUI::applyUISettings() { settings.patFont=0; } + ImFontConfig fc1; + fc1.MergeMode=true; + if (settings.mainFont==6) { // custom font - if ((mainFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(settings.mainFontPath.c_str(),e->getConfInt("mainFontSize",18)*dpiScale,NULL,loadEverything))==NULL) { + if ((mainFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(settings.mainFontPath.c_str(),e->getConfInt("mainFontSize",18)*dpiScale,NULL,fontRange))==NULL) { logW("could not load UI font! reverting to default font\n"); settings.mainFont=0; - if ((mainFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFont[settings.mainFont],builtinFontLen[settings.mainFont],e->getConfInt("mainFontSize",18)*dpiScale,NULL,loadEverything))==NULL) { + if ((mainFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFont[settings.mainFont],builtinFontLen[settings.mainFont],e->getConfInt("mainFontSize",18)*dpiScale,NULL,fontRange))==NULL) { logE("could not load UI font! falling back to Proggy Clean.\n"); mainFont=ImGui::GetIO().Fonts->AddFontDefault(); } } } else if (settings.mainFont==5) { // system font - if ((mainFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_FONT_PATH_1,e->getConfInt("mainFontSize",18)*dpiScale,NULL,loadEverything))==NULL) { - if ((mainFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_FONT_PATH_2,e->getConfInt("mainFontSize",18)*dpiScale,NULL,loadEverything))==NULL) { - if ((mainFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_FONT_PATH_3,e->getConfInt("mainFontSize",18)*dpiScale,NULL,loadEverything))==NULL) { + if ((mainFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_FONT_PATH_1,e->getConfInt("mainFontSize",18)*dpiScale,NULL,fontRange))==NULL) { + if ((mainFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_FONT_PATH_2,e->getConfInt("mainFontSize",18)*dpiScale,NULL,fontRange))==NULL) { + if ((mainFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_FONT_PATH_3,e->getConfInt("mainFontSize",18)*dpiScale,NULL,fontRange))==NULL) { logW("could not load UI font! reverting to default font\n"); settings.mainFont=0; - if ((mainFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFont[settings.mainFont],builtinFontLen[settings.mainFont],e->getConfInt("mainFontSize",18)*dpiScale,NULL,loadEverything))==NULL) { + if ((mainFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFont[settings.mainFont],builtinFontLen[settings.mainFont],e->getConfInt("mainFontSize",18)*dpiScale,NULL,fontRange))==NULL) { logE("could not load UI font! falling back to Proggy Clean.\n"); mainFont=ImGui::GetIO().Fonts->AddFontDefault(); } @@ -1747,17 +1783,21 @@ void FurnaceGUI::applyUISettings() { } } } else { - if ((mainFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFont[settings.mainFont],builtinFontLen[settings.mainFont],e->getConfInt("mainFontSize",18)*dpiScale,NULL,loadEverything))==NULL) { + if ((mainFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFont[settings.mainFont],builtinFontLen[settings.mainFont],e->getConfInt("mainFontSize",18)*dpiScale,NULL,fontRange))==NULL) { logE("could not load UI font! falling back to Proggy Clean.\n"); mainFont=ImGui::GetIO().Fonts->AddFontDefault(); } } + // two fallback fonts + mainFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(font_liberationSans_compressed_data,font_liberationSans_compressed_size,e->getConfInt("mainFontSize",18)*dpiScale,&fc1,fontRange); + mainFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(font_unifont_compressed_data,font_unifont_compressed_size,e->getConfInt("mainFontSize",18)*dpiScale,&fc1,fontRange); + ImFontConfig fc; fc.MergeMode=true; fc.GlyphMinAdvanceX=e->getConfInt("iconSize",16)*dpiScale; - static const ImWchar fontRange[]={ICON_MIN_FA,ICON_MAX_FA,0}; - if ((iconFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(iconFont_compressed_data,iconFont_compressed_size,e->getConfInt("iconSize",16)*dpiScale,&fc,fontRange))==NULL) { + static const ImWchar fontRangeIcon[]={ICON_MIN_FA,ICON_MAX_FA,0}; + if ((iconFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(iconFont_compressed_data,iconFont_compressed_size,e->getConfInt("iconSize",16)*dpiScale,&fc,fontRangeIcon))==NULL) { logE("could not load icon font!\n"); } if (settings.mainFontSize==settings.patFontSize && settings.patFont<5 && builtinFontM[settings.patFont]==builtinFont[settings.mainFont]) { @@ -1765,21 +1805,21 @@ void FurnaceGUI::applyUISettings() { patFont=mainFont; } else { if (settings.patFont==6) { // custom font - if ((patFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(settings.patFontPath.c_str(),e->getConfInt("patFontSize",18)*dpiScale,NULL,loadEverything))==NULL) { + if ((patFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(settings.patFontPath.c_str(),e->getConfInt("patFontSize",18)*dpiScale,NULL,upTo800))==NULL) { logW("could not load pattern font! reverting to default font\n"); settings.patFont=0; - if ((patFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFontM[settings.patFont],builtinFontMLen[settings.patFont],e->getConfInt("patFontSize",18)*dpiScale,NULL,loadEverything))==NULL) { + if ((patFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFontM[settings.patFont],builtinFontMLen[settings.patFont],e->getConfInt("patFontSize",18)*dpiScale,NULL,upTo800))==NULL) { logE("could not load pattern font! falling back to Proggy Clean.\n"); patFont=ImGui::GetIO().Fonts->AddFontDefault(); } } } else if (settings.patFont==5) { // system font - if ((patFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_PAT_FONT_PATH_1,e->getConfInt("patFontSize",18)*dpiScale,NULL,loadEverything))==NULL) { - if ((patFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_PAT_FONT_PATH_2,e->getConfInt("patFontSize",18)*dpiScale,NULL,loadEverything))==NULL) { - if ((patFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_PAT_FONT_PATH_3,e->getConfInt("patFontSize",18)*dpiScale,NULL,loadEverything))==NULL) { + if ((patFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_PAT_FONT_PATH_1,e->getConfInt("patFontSize",18)*dpiScale,NULL,upTo800))==NULL) { + if ((patFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_PAT_FONT_PATH_2,e->getConfInt("patFontSize",18)*dpiScale,NULL,upTo800))==NULL) { + if ((patFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_PAT_FONT_PATH_3,e->getConfInt("patFontSize",18)*dpiScale,NULL,upTo800))==NULL) { logW("could not load pattern font! reverting to default font\n"); settings.patFont=0; - if ((patFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFontM[settings.patFont],builtinFontMLen[settings.patFont],e->getConfInt("patFontSize",18)*dpiScale,NULL,loadEverything))==NULL) { + if ((patFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFontM[settings.patFont],builtinFontMLen[settings.patFont],e->getConfInt("patFontSize",18)*dpiScale,NULL,upTo800))==NULL) { logE("could not load pattern font! falling back to Proggy Clean.\n"); patFont=ImGui::GetIO().Fonts->AddFontDefault(); } @@ -1787,7 +1827,7 @@ void FurnaceGUI::applyUISettings() { } } } else { - if ((patFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFontM[settings.patFont],builtinFontMLen[settings.patFont],e->getConfInt("patFontSize",18)*dpiScale,NULL,loadEverything))==NULL) { + if ((patFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFontM[settings.patFont],builtinFontMLen[settings.patFont],e->getConfInt("patFontSize",18)*dpiScale,NULL,upTo800))==NULL) { logE("could not load pattern font!\n"); patFont=ImGui::GetIO().Fonts->AddFontDefault(); } @@ -1797,6 +1837,9 @@ void FurnaceGUI::applyUISettings() { logE("could not load big UI font!\n"); } + mainFont->FallbackChar='?'; + mainFont->DotChar='.'; + // TODO: allow changing these colors. ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByTypeDir,"",ImVec4(0.0f,1.0f,1.0f,1.0f),ICON_FA_FOLDER_O); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByTypeFile,"",ImVec4(0.7f,0.7f,0.7f,1.0f),ICON_FA_FILE_O); From 13eb0f96f1e34058cc31886dbdc75ab484874601 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 21 Mar 2022 23:48:18 -0500 Subject: [PATCH 361/637] double facepalm --- src/engine/sample.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/sample.cpp b/src/engine/sample.cpp index 69e61e547..55982e592 100644 --- a/src/engine/sample.cpp +++ b/src/engine/sample.cpp @@ -460,7 +460,7 @@ bool DivSample::resampleBlep(double r) { } } } else if (depth==8) { - memset(data8,0,finalCount*sizeof(short)); + memset(data8,0,finalCount); for (int i=0; i Date: Mon, 21 Mar 2022 23:54:01 -0500 Subject: [PATCH 362/637] scale loop when resampling --- src/engine/sample.cpp | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/engine/sample.cpp b/src/engine/sample.cpp index 55982e592..66e54d857 100644 --- a/src/engine/sample.cpp +++ b/src/engine/sample.cpp @@ -278,8 +278,9 @@ bool DivSample::trim(unsigned int begin, unsigned int end) { } #define RESAMPLE_END \ - samples=finalCount; \ if (loopStart>=0) loopStart=(double)loopStart*(r/(double)rate); \ + rate=r; \ + samples=finalCount; \ if (depth==16) { \ delete[] oldData16; \ } else if (depth==8) { \ @@ -309,8 +310,6 @@ bool DivSample::resampleNone(double r) { } } - rate=r; - RESAMPLE_END; return true; } @@ -350,8 +349,6 @@ bool DivSample::resampleLinear(double r) { } } - rate=r; - RESAMPLE_END; return true; } @@ -406,8 +403,6 @@ bool DivSample::resampleCubic(double r) { } } - rate=r; - RESAMPLE_END; return true; } @@ -497,8 +492,6 @@ bool DivSample::resampleBlep(double r) { } } - rate=r; - RESAMPLE_END; return true; } @@ -566,8 +559,6 @@ bool DivSample::resampleSinc(double r) { } } - rate=r; - RESAMPLE_END; return true; } From a1969d30f6c0a0476192acaceb0c878534999d29 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 22 Mar 2022 00:06:57 -0500 Subject: [PATCH 363/637] rename x1_010 to x1-010 --- papers/doc/7-systems/README.md | 2 +- papers/doc/7-systems/{x1_010.md => x1-010.md} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename papers/doc/7-systems/{x1_010.md => x1-010.md} (100%) diff --git a/papers/doc/7-systems/README.md b/papers/doc/7-systems/README.md index 5413b0b50..757a41f5c 100644 --- a/papers/doc/7-systems/README.md +++ b/papers/doc/7-systems/README.md @@ -18,7 +18,7 @@ this is a list of systems that Furnace supports, including each system's effects - [Atari 2600](tia.md) - [Philips SAA1099](saa1099.md) - [Microchip AY8930](ay8930.md) -- [Seta/Allumer X1-010](x1_010.md) +- [Seta/Allumer X1-010](x1-010.md) - [WonderSwan](wonderswan.md) - [Bubble System WSG](bubblesystem.md) diff --git a/papers/doc/7-systems/x1_010.md b/papers/doc/7-systems/x1-010.md similarity index 100% rename from papers/doc/7-systems/x1_010.md rename to papers/doc/7-systems/x1-010.md From 848da5bcc52c1ea2c8ac79e0155528f443961f8e Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 22 Mar 2022 00:13:28 -0500 Subject: [PATCH 364/637] VERA: 0 to 3F --- src/engine/platform/vera.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/vera.cpp b/src/engine/platform/vera.cpp index 522e4be6f..8965719f5 100644 --- a/src/engine/platform/vera.cpp +++ b/src/engine/platform/vera.cpp @@ -57,7 +57,7 @@ const char* DivPlatformVERA::getEffectName(unsigned char effect) { return "20xx: Change waveform"; break; case 0x22: - return "22xx: Set duty cycle (0 to 63)"; + return "22xx: Set duty cycle (0 to 3F)"; break; } return NULL; From 2589709d1afbfa26a2270bd4bf7aa9a92f9da7f7 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 22 Mar 2022 00:16:19 -0500 Subject: [PATCH 365/637] add doc for VERA chip --- papers/doc/7-systems/vera.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 papers/doc/7-systems/vera.md diff --git a/papers/doc/7-systems/vera.md b/papers/doc/7-systems/vera.md new file mode 100644 index 000000000..7d1b09c43 --- /dev/null +++ b/papers/doc/7-systems/vera.md @@ -0,0 +1,15 @@ +# VERA + +this is a video and sound generator chip used in the Commander X16, a modern 8-bit computer created by The 8-Bit Guy. +it has 16 channels of pulse/triangle/saw/noise and one stereo PCM channel. + +currently Furnace does not support the PCM channel's stereo mode, though. + +# effects + +- `20xx`: set waveform. the following values are accepted: + - 0: pulse + - 1: saw + - 2: triangle + - 3: noise +- `22xx`: set duty cycle. `xx` may go from 0 to 3F. From 8ae0796f0069fa405e1b778dc33d6f199a0a7db7 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 22 Mar 2022 00:17:00 -0500 Subject: [PATCH 366/637] now add it to the list --- papers/doc/7-systems/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/papers/doc/7-systems/README.md b/papers/doc/7-systems/README.md index 757a41f5c..c9ce873b6 100644 --- a/papers/doc/7-systems/README.md +++ b/papers/doc/7-systems/README.md @@ -18,6 +18,7 @@ this is a list of systems that Furnace supports, including each system's effects - [Atari 2600](tia.md) - [Philips SAA1099](saa1099.md) - [Microchip AY8930](ay8930.md) +- [VERA](vera.md) - [Seta/Allumer X1-010](x1-010.md) - [WonderSwan](wonderswan.md) - [Bubble System WSG](bubblesystem.md) From ae3165dbb4bc9754414e1b0c17cde70e4d9b240e Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 22 Mar 2022 00:58:07 -0500 Subject: [PATCH 367/637] system doc work --- papers/doc/7-systems/README.md | 7 ++++- papers/doc/7-systems/arcade.md | 2 +- papers/doc/7-systems/bubblesystem.md | 2 +- papers/doc/7-systems/lynx.md | 6 ++-- papers/doc/7-systems/opl.md | 47 ++++++++++++++++++++++++++++ papers/doc/7-systems/opll.md | 47 ++++++++++++++++++---------- papers/doc/7-systems/pcspkr.md | 7 +++++ papers/doc/7-systems/pet.md | 11 +++++++ papers/doc/7-systems/segapcm.md | 12 +++++++ papers/doc/7-systems/x1-010.md | 6 ++-- papers/doc/7-systems/ym2151.md | 4 ++- papers/doc/7-systems/ym2610b.md | 5 +-- papers/doc/7-systems/ymu759.md | 3 +- 13 files changed, 130 insertions(+), 29 deletions(-) create mode 100644 papers/doc/7-systems/opl.md create mode 100644 papers/doc/7-systems/pcspkr.md create mode 100644 papers/doc/7-systems/pet.md create mode 100644 papers/doc/7-systems/segapcm.md diff --git a/papers/doc/7-systems/README.md b/papers/doc/7-systems/README.md index c9ce873b6..319314401 100644 --- a/papers/doc/7-systems/README.md +++ b/papers/doc/7-systems/README.md @@ -4,6 +4,7 @@ this is a list of systems that Furnace supports, including each system's effects - [Sega Genesis/Mega Drive](genesis.md) - [Sega Master System](sms.md) +- [Yamaha OPLL](opll.md) - [Game Boy](game-boy.md) - [PC Engine/TurboGrafx-16](pce.md) - [NES](nes.md) @@ -15,6 +16,7 @@ this is a list of systems that Furnace supports, including each system's effects - [Amiga](amiga.md) - [Yamaha YM2612 standalone](ym2612.md) - [Yamaha YM2151 standalone](ym2151.md) +- [SegaPCM](segapcm.md) - [Atari 2600](tia.md) - [Philips SAA1099](saa1099.md) - [Microchip AY8930](ay8930.md) @@ -22,5 +24,8 @@ this is a list of systems that Furnace supports, including each system's effects - [Seta/Allumer X1-010](x1-010.md) - [WonderSwan](wonderswan.md) - [Bubble System WSG](bubblesystem.md) +- [Yamaha OPL](opl.md) +- [PC Speaker](pcspkr.md) +- [Commodore PET](pet.md) -Furnace also reads .dmf files with the [Yamaha YMU759](ymu759.md) system, but does not emulate the chip at all. +Furnace also reads .dmf files with the [Yamaha YMU759](ymu759.md) system, but... diff --git a/papers/doc/7-systems/arcade.md b/papers/doc/7-systems/arcade.md index fd17f91c1..dc9496e6b 100644 --- a/papers/doc/7-systems/arcade.md +++ b/papers/doc/7-systems/arcade.md @@ -1,7 +1,7 @@ # Arcade (Yamaha YM2151/PCM) this chip combination was used in the Sega OutRun, X and Y arcade boards, and perhaps a few others. -the actual PCM chip had 16 channels, but the number has been cut to 5 in DefleMask for seemingly arbitrary reasons. a system with all 16 channels may be coming soon. +the actual PCM chip had 16 channels, but the number has been cut to 5 in DefleMask for seemingly arbitrary reasons. # effects diff --git a/papers/doc/7-systems/bubblesystem.md b/papers/doc/7-systems/bubblesystem.md index 835fd622f..0c363163e 100644 --- a/papers/doc/7-systems/bubblesystem.md +++ b/papers/doc/7-systems/bubblesystem.md @@ -8,7 +8,7 @@ Also known as K005289, but that's just part of the logic used for pitch and wave Waveform select and Volume control are tied with single AY-3-8910 IO for both channels. Another AY-3-8910 IO is used for reading sound hardware status. -furnace emulates this configurations as single system, waveform format is 15 level and 32 width. +Furnace emulates this configurations as single system, waveform format is 15 level and 32 width. # effects diff --git a/papers/doc/7-systems/lynx.md b/papers/doc/7-systems/lynx.md index 2e2f3e3c5..a92b25806 100644 --- a/papers/doc/7-systems/lynx.md +++ b/papers/doc/7-systems/lynx.md @@ -2,7 +2,7 @@ The Atari Lynx is a 16 bit handheld console developed by (obviously) Atari Corporation, and initially released in September of 1989, with the worldwide release being in 1990. -The Lynx, while being an incredible handheld for the time (and a lot more powerful than a Game Boy), unfortunately meant nothing in the end due to the Lynx being a market failiure, and ending up as one of the things that contributed to the downfall of Atari. +The Lynx, while being an incredible handheld for the time (and a lot more powerful than a Game Boy), unfortunately meant nothing in the end due to the Lynx being a market failure, and ending up as one of the things that contributed to the downfall of Atari. Although the Lynx is still getting (rather impressive) homebrew developed for it, it does not mean that the Lynx is a popular system at all. @@ -17,4 +17,6 @@ The Atari Lynx's custom sound chip and CPU (MIKEY) is a 6502-based 8 bit CPU run - The MIKEY also has a variety of pitches to choose from, and they go from 32Hz to "above the range of human hearing", according to Atari. ## Effect commands - - `3xxx`: Load LFSR (0 to FFF). For it to work, duty macro in instrument editor must be set to some value, without it LFSR will not be fed with any bits. + - `3xxx`: Load LFSR (0 to FFF). + - this is a bitmask. + - for it to work, duty macro in instrument editor must be set to some value, without it LFSR will not be fed with any bits. diff --git a/papers/doc/7-systems/opl.md b/papers/doc/7-systems/opl.md new file mode 100644 index 000000000..1459eee4c --- /dev/null +++ b/papers/doc/7-systems/opl.md @@ -0,0 +1,47 @@ +# Yamaha OPL + +a series of FM sound chips which were very popular in DOS land. it was so popular that even Yamaha made a logo for it! + +essentially a downgraded version of Yamaha's other FM chips, with only 2 operators per channel. +however, it also had a drums mode, and later chips in the series added more waveforms (than just the typical sine) and even a 4-operator mode. + +the original OPL was present as an expansion for the Commodore 64 and MSX computers (erm, a variant of it). it only had 9 channels and drums mode. + +its successor, the OPL2, added 3 more waveforms and was one of the more popular chips because it was present on the AdLib card for PC. +later Creative would borrow the chip to make the Sound Blaster, and totally destroyed AdLib's dominance. + +the OPL3 added 9 more channels, 4 more waveforms, 4-operator mode (pairing up to 12 channels to make up to six 4-operator channels), quadraphonic output (sadly Furnace only supports stereo) and some other things. +it was overkill. + +afterwards everyone moved to Windows and software mixing... + +# effects + +- 10xx: set AM depth. the following values are accepted: + - 0: 1dB (shallow) + - 1: 4.8dB (deep) + - this effect applies to all channels. +- `11xx`: set feedback of channel. +- `12xx`: set operator 1 level. +- `13xx`: set operator 2 level. +- `14xx`: set operator 3 level. + - only in 4-op mode (OPL3). +- `15xx`: set operator 4 level. + - only in 4-op mode (OPL3). +- `16xy`: set multiplier of operator. + - `x` is the operator (1-4; last 2 operators only in 4-op mode). + - `y` is the mutliplier. +- 17xx: set vibrato depth. the following values are accepted: + - 0: normal + - 1: double + - this effect applies to all channels. +- `18xx`: toggle drums mode. + - 0 disables it and 1 enables it. + - only in drums system. +- `19xx`: set attack of all operators. +- `1Axx`: set attack of operator 1. +- `1Bxx`: set attack of operator 2. +- `1Cxx`: set attack of operator 3. + - only in 4-op mode (OPL3). +- `1Dxx`: set attack of operator 4. + - only in 4-op mode (OPL3). diff --git a/papers/doc/7-systems/opll.md b/papers/doc/7-systems/opll.md index d760d413e..880750b35 100644 --- a/papers/doc/7-systems/opll.md +++ b/papers/doc/7-systems/opll.md @@ -1,21 +1,34 @@ # Yamaha YM2413/OPLL -The YM2413, otherwise known as OPLL, is a cost-reduced FM synthesis sound chip manufactured by Yamaha Corporation and based on the Yamaha YM3812 sound chip (OPL2). -As of Furnace version 0.5.7pre4, the OPLL sound chip is not yet emulated. It is, however, emulated in Deflemask as of version 1.1.0. Support for loading .DMFs which contain the YM2413 was added in Furnace version 0.5.7pre4. +the YM2413, otherwise known as OPLL, is a cost-reduced FM synthesis sound chip manufactured by Yamaha Corporation and based on the Yamaha YM3812 sound chip (OPL2). thought OPL was downgraded enough? :p -## Technical specifications -The YM2413 is equipped with the following features: - - 9 channels of 2 operator FM synthesis - - A drum/percussion mode, replacing the last 3 voices with 3 rhythm channels - - 1 user-definable patch (this patch can be changed throughout the course of the song) - - 15 pre-defined patches which can all be used at the same time - - Support for ADSR on both the modulator and the carrier - - Sine and half-sine based FM synthesis - - 9 octave note control - - 4096 different frequencies for channels - - 16 unique volume levels (NOTE: Volume 0 is NOT silent.) - - Modulator and carrier key scaling - - Built-in hardware vibrato support +# technical specifications -## Effect commands -TODO: Add effect commands here when YM2413 emulation is added. +the YM2413 is equipped with the following features: + +- 9 channels of 2 operator FM synthesis +- A drum/percussion mode, replacing the last 3 voices with 3 rhythm channels +- 1 user-definable patch (this patch can be changed throughout the course of the song) +- 15 pre-defined patches which can all be used at the same time +- Support for ADSR on both the modulator and the carrier +- Sine and half-sine based FM synthesis +- 9 octave note control +- 4096 different frequencies for channels +- 16 unique volume levels (NOTE: Volume 0 is NOT silent.) +- Modulator and carrier key scaling +- Built-in hardware vibrato support + +# effects + +- `11xx`: set feedback of channel. +- `12xx`: set operator 1 level. +- `13xx`: set operator 2 level. +- `16xy`: set multiplier of operator. + - `x` is the operator (1 or 2). + - `y` is the mutliplier. +- `18xx`: toggle drums mode. + - 0 disables it and 1 enables it. + - only in drums system. +- `19xx`: set attack of all operators. +- `1Axx`: set attack of operator 1. +- `1Bxx`: set attack of operator 2. diff --git a/papers/doc/7-systems/pcspkr.md b/papers/doc/7-systems/pcspkr.md new file mode 100644 index 000000000..afaa4f82c --- /dev/null +++ b/papers/doc/7-systems/pcspkr.md @@ -0,0 +1,7 @@ +# PC Speaker + +40 years of one square beep - and still going! + +# effects + +ha! effects... diff --git a/papers/doc/7-systems/pet.md b/papers/doc/7-systems/pet.md new file mode 100644 index 000000000..f2c2c1c61 --- /dev/null +++ b/papers/doc/7-systems/pet.md @@ -0,0 +1,11 @@ +# Commodore PET + +a computer from 1977 which was leader on US schools back then. subsequently the Apple II took its throne. + +maybe no better than a computer terminal, but somebody discovered a way to update the screen at turbo rate - and eventually its sound "chip" (it was nothing more than an 8-bit shift register) was abused as well. + +some of these didn't even have sound... + +# effects + +- 10xx: set waveform. `xx` is a bitmask. diff --git a/papers/doc/7-systems/segapcm.md b/papers/doc/7-systems/segapcm.md new file mode 100644 index 000000000..1b3ed63fb --- /dev/null +++ b/papers/doc/7-systems/segapcm.md @@ -0,0 +1,12 @@ +# SegaPCM + +16 channels of PCM? no way! + +yep, that's right! 16 channels of PCM! + +a chip used in the Sega OutRun/X/Y arcade boards. eventually the MultiPCM surpassed it with 24 channels, and later they joined the software mixing gang. + +# effects +- `20xx`: set PCM frequency. + - `xx` is a 256th fraction of 31250Hz. + - this effect exists for mostly DefleMask compatibility - it is otherwise recommended to use Sample type instruments. diff --git a/papers/doc/7-systems/x1-010.md b/papers/doc/7-systems/x1-010.md index 759d519c9..1fde55e0f 100644 --- a/papers/doc/7-systems/x1-010.md +++ b/papers/doc/7-systems/x1-010.md @@ -2,7 +2,7 @@ One of sound chip originally designed by Seta, mainly used at their arcade hardwares at late-80s to early-2000s. It has 2 output channels, but no known hardware using this feature for stereo sound. -Later hardware paired this with external bankswitching logic, but its logic is not emulated now in current furnace revision. +Later hardware paired this with external bankswitching logic, but this isn't emulated yet. Allumer one is just rebadged Seta's thing for use in their arcade hardwares. It has 16 channels, and all channels can be switchable to PCM sample or wavetable playback mode. @@ -26,9 +26,9 @@ In furnace, You can set envelope shape split mode. When it sets, its waveform wi # effects - `10xx`: change wave. -- `11xx`: change envelope shape. (also wavetable) +- `11xx`: change envelope shape (also wavetable). - `17xx`: toggle PCM mode. -- `20xx`: set PCM frequency. (1 to FF)* +- `20xx`: set PCM frequency (1 to FF). - `22xx`: set envelope mode. - bit 0 sets whether envelope will affect this channel. - bit 1 sets whether envelope one-shot mode. when it sets, channel is halted after envelope cycle is ended. diff --git a/papers/doc/7-systems/ym2151.md b/papers/doc/7-systems/ym2151.md index ff5a789df..bf8b0d6b1 100644 --- a/papers/doc/7-systems/ym2151.md +++ b/papers/doc/7-systems/ym2151.md @@ -1,6 +1,8 @@ # Yamaha YM2151 -the sound chip powering the Arcade system, available for standalone use if you want to make X68000 music or pair it with a 16-channel Sega PCM when it comes. +the sound chip powering several arcade boards and the Sharp X1/X68000. + +it also was present on several pinball machines and synthesizers of the era, and later surpassed by the YM2414 (OPZ) present in the world-famous TX81Z. # effects diff --git a/papers/doc/7-systems/ym2610b.md b/papers/doc/7-systems/ym2610b.md index 2b2e5feaf..e251122cd 100644 --- a/papers/doc/7-systems/ym2610b.md +++ b/papers/doc/7-systems/ym2610b.md @@ -1,6 +1,7 @@ # Taito Arcade/Yamaha YM2610B -YM2610B is basically YM2610 with 2 extra FM channels used at some 90s Taito Arcade hardwares, it's backward compatible with non-B chip. +YM2610B is basically YM2610 with 2 extra FM channels used at some 90s Taito arcade hardware. +it is backward compatible with the original chip. # effects @@ -54,4 +55,4 @@ YM2610B is basically YM2610 with 2 extra FM channels used at some 90s Taito Arca - in this mode the envelope period is set to the channel's notes, multiplied by a fraction. - `x` is the numerator. - `y` is the denominator. - - if `x` or `y` are 0 this will disable auto-envelope mode. \ No newline at end of file + - if `x` or `y` are 0 this will disable auto-envelope mode. diff --git a/papers/doc/7-systems/ymu759.md b/papers/doc/7-systems/ymu759.md index 1ad80939e..339848321 100644 --- a/papers/doc/7-systems/ymu759.md +++ b/papers/doc/7-systems/ymu759.md @@ -4,7 +4,8 @@ the Yamaha YMU759 is a sound chip designed for feature phones during the early 2 it is also known as MA-2. sadly Yamaha didn't care about these chips too much, and the register specs were completely unavailable, which means the YMU759 is totally unsupported and unemulated besides Yamaha's official emulator for it built into MidRadio. -hence Furnace does not emulate the chip (and doesn't even let you add it to a song). + +Furnace 0.6 loads DefleMask modules written for this system; however, it doesn't support any of its effects and is simulated using the OPL core. # effects From 3b63ca945527d4bd8e70fcf7d01fce91c9b2ea46 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 22 Mar 2022 01:06:13 -0500 Subject: [PATCH 368/637] update ay8930.md --- papers/doc/7-systems/ay8930.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/papers/doc/7-systems/ay8930.md b/papers/doc/7-systems/ay8930.md index 7344b8f0b..6d9db10a9 100644 --- a/papers/doc/7-systems/ay8930.md +++ b/papers/doc/7-systems/ay8930.md @@ -5,7 +5,7 @@ a backwards-compatible successor to the AY-3-8910, with increased volume resolut sadly, this soundchip has only ever observed minimal success, and has remained rather obscure since. it is known for being used in the Covox Sound Master, which didn't sell well either. -while emulation of this chip is mostly complete, the additional noise setup registers are not emulated (yet). whether it ever has been emulated at some point in a now-abandoned tracker with similar goal as Furnace is unknown. +while emulation of this chip is mostly complete, hardware comparison hasn't been performed yet due to its scarcity. it also was emulated in a now-abandoned tracker with similar goal as Furnace, which sparked interest on the chip. # effects From bfadb3b5cf11d64716b5d4f5fbc93648f0ea077b Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 22 Mar 2022 01:06:26 -0500 Subject: [PATCH 369/637] OPL: oopsie. 20xx effect doesn't exist --- src/engine/platform/opl.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index 42ce11587..b0894fe70 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -189,9 +189,6 @@ const char* DivPlatformOPL::getEffectName(unsigned char effect) { case 0x1d: return "1Dxx: Set attack of operator 4 (0 to F; 4-op only)"; break; - case 0x20: - return "20xy: Set PSG noise mode (x: preset freq/ch3 freq; y: thin pulse/noise)"; - break; } return NULL; } From 6c10c269a100012443219164423d8e19e1575f5d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 22 Mar 2022 01:18:22 -0500 Subject: [PATCH 370/637] VERA: Furnace does support stereo for panning --- papers/doc/7-systems/vera.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/papers/doc/7-systems/vera.md b/papers/doc/7-systems/vera.md index 7d1b09c43..be610a809 100644 --- a/papers/doc/7-systems/vera.md +++ b/papers/doc/7-systems/vera.md @@ -3,7 +3,7 @@ this is a video and sound generator chip used in the Commander X16, a modern 8-bit computer created by The 8-Bit Guy. it has 16 channels of pulse/triangle/saw/noise and one stereo PCM channel. -currently Furnace does not support the PCM channel's stereo mode, though. +currently Furnace does not support the PCM channel's stereo mode, though (except for panning). # effects From 6dbc46d50e323367d734d3f0e2850607c72dbac3 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 22 Mar 2022 01:21:13 -0500 Subject: [PATCH 371/637] GUI: prevent editing non-8/16-bit samples --- src/gui/sampleEdit.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index 55d2e4859..6b9694124 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -135,6 +135,8 @@ void FurnaceGUI::drawSampleEdit() { */ ImGui::Separator(); + ImGui::BeginDisabled(sample->depth!=8 && sample->depth!=16); + if (ImGui::Button(ICON_FA_I_CURSOR "##SSelect")) { sampleDragMode=false; } @@ -862,6 +864,12 @@ void FurnaceGUI::drawSampleEdit() { updateSampleTex=true; } } + + if (sample->depth!=8 && sample->depth!=16) { + statusBar="Non-8/16-bit samples cannot be edited without prior conversion."; + } + + ImGui::EndDisabled(); ImGui::SetCursorPosY(ImGui::GetCursorPosY()+ImGui::GetStyle().ScrollbarSize); ImGui::Text("%s",statusBar.c_str()); From 73d2f97274b490ea237fa0ab726f2810c2e40e39 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 22 Mar 2022 01:34:53 -0500 Subject: [PATCH 372/637] GUI: fix visualizer option in menu --- src/gui/gui.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index ddb7433d4..ff0d4967e 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2011,6 +2011,9 @@ bool FurnaceGUI::loop() { if (ImGui::BeginMenu("settings")) { if (ImGui::MenuItem("visualizer",NULL,fancyPattern)) { fancyPattern=!fancyPattern; + e->enableCommandStream(fancyPattern); + e->getCommandStream(cmdStream); + cmdStream.clear(); } if (ImGui::MenuItem("reset layout")) { showWarning("Are you sure you want to reset the workspace layout?",GUI_WARN_RESET_LAYOUT); From f4f91ca49ebb46f4e51f6792a722559369ad088f Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 22 Mar 2022 01:57:06 -0500 Subject: [PATCH 373/637] GUI: ability to customize toggle button color --- src/gui/editControls.cpp | 30 +++++++++++++++++++----------- src/gui/gui.cpp | 4 +++- src/gui/gui.h | 6 ++++++ src/gui/insEdit.cpp | 16 ++++++++-------- src/gui/sampleEdit.cpp | 4 ++++ src/gui/settings.cpp | 6 ++++++ 6 files changed, 46 insertions(+), 20 deletions(-) diff --git a/src/gui/editControls.cpp b/src/gui/editControls.cpp index 68f0fee2d..5e8d72574 100644 --- a/src/gui/editControls.cpp +++ b/src/gui/editControls.cpp @@ -56,9 +56,11 @@ void FurnaceGUI::drawEditControls() { } } + ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(e->isPlaying())); if (ImGui::Button(ICON_FA_PLAY "##Play")) { play(); } + ImGui::PopStyleColor(); ImGui::SameLine(); if (ImGui::Button(ICON_FA_STOP "##Stop")) { stop(); @@ -95,9 +97,11 @@ void FurnaceGUI::drawEditControls() { stop(); } ImGui::SameLine(); + ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(e->isPlaying())); if (ImGui::Button(ICON_FA_PLAY "##Play")) { play(); } + ImGui::PopStyleColor(); ImGui::SameLine(); if (ImGui::Button(ICON_FA_ARROW_DOWN "##StepOne")) { e->stepOne(cursor.y); @@ -105,14 +109,14 @@ void FurnaceGUI::drawEditControls() { ImGui::SameLine(); bool repeatPattern=e->getRepeatPattern(); - ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(repeatPattern)?0.6f:0.2f,0.2f,1.0f)); + ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(repeatPattern)); if (ImGui::Button(ICON_FA_REPEAT "##RepeatPattern")) { e->setRepeatPattern(!repeatPattern); } ImGui::PopStyleColor(); ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(edit)?0.6f:0.2f,0.2f,1.0f)); + ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(edit)); if (ImGui::Button(ICON_FA_CIRCLE "##Edit")) { edit=!edit; } @@ -120,7 +124,7 @@ void FurnaceGUI::drawEditControls() { ImGui::SameLine(); bool metro=e->getMetronome(); - ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(metro)?0.6f:0.2f,0.2f,1.0f)); + ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(metro)); if (ImGui::Button(ICON_FA_BELL_O "##Metronome")) { e->setMetronome(!metro); } @@ -168,9 +172,11 @@ void FurnaceGUI::drawEditControls() { break; case 2: // compact vertical if (ImGui::Begin("Play/Edit Controls",&editControlsOpen,ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoScrollWithMouse)) { + ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(e->isPlaying())); if (ImGui::Button(ICON_FA_PLAY "##Play")) { play(); } + ImGui::PopStyleColor(); if (ImGui::Button(ICON_FA_STOP "##Stop")) { stop(); } @@ -179,20 +185,20 @@ void FurnaceGUI::drawEditControls() { } bool repeatPattern=e->getRepeatPattern(); - ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(repeatPattern)?0.6f:0.2f,0.2f,1.0f)); + ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(repeatPattern)); if (ImGui::Button(ICON_FA_REPEAT "##RepeatPattern")) { e->setRepeatPattern(!repeatPattern); } ImGui::PopStyleColor(); - ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(edit)?0.6f:0.2f,0.2f,1.0f)); + ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(edit)); if (ImGui::Button(ICON_FA_CIRCLE "##Edit")) { edit=!edit; } ImGui::PopStyleColor(); bool metro=e->getMetronome(); - ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(metro)?0.6f:0.2f,0.2f,1.0f)); + ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(metro)); if (ImGui::Button(ICON_FA_BELL_O "##Metronome")) { e->setMetronome(!metro); } @@ -226,12 +232,12 @@ void FurnaceGUI::drawEditControls() { } ImGui::Text("Foll."); - ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(followOrders)?0.6f:0.2f,0.2f,1.0f)); + ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(followOrders)); if (ImGui::SmallButton("Ord##FollowOrders")) { handleUnimportant followOrders=!followOrders; } ImGui::PopStyleColor(); - ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(followPattern)?0.6f:0.2f,0.2f,1.0f)); + ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(followPattern)); if (ImGui::SmallButton("Pat##FollowPattern")) { handleUnimportant followPattern=!followPattern; } @@ -243,9 +249,11 @@ void FurnaceGUI::drawEditControls() { case 3: // split if (ImGui::Begin("Play Controls",&editControlsOpen,ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoScrollWithMouse)) { if (e->isPlaying()) { + ImGui::PushStyleColor(ImGuiCol_Button,uiColors[GUI_COLOR_TOGGLE_ON]); if (ImGui::Button(ICON_FA_STOP "##Stop")) { stop(); } + ImGui::PopStyleColor(); } else { if (ImGui::Button(ICON_FA_PLAY "##Play")) { play(); @@ -261,7 +269,7 @@ void FurnaceGUI::drawEditControls() { } ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(edit)?0.6f:0.2f,0.2f,1.0f)); + ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(edit)); if (ImGui::Button(ICON_FA_CIRCLE "##Edit")) { edit=!edit; } @@ -269,7 +277,7 @@ void FurnaceGUI::drawEditControls() { bool metro=e->getMetronome(); ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(metro)?0.6f:0.2f,0.2f,1.0f)); + ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(metro)); if (ImGui::Button(ICON_FA_BELL_O "##Metronome")) { e->setMetronome(!metro); } @@ -277,7 +285,7 @@ void FurnaceGUI::drawEditControls() { ImGui::SameLine(); bool repeatPattern=e->getRepeatPattern(); - ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(repeatPattern)?0.6f:0.2f,0.2f,1.0f)); + ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(repeatPattern)); if (ImGui::Button(ICON_FA_REPEAT "##RepeatPattern")) { e->setRepeatPattern(!repeatPattern); } diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index ff0d4967e..3db50d06e 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2848,7 +2848,9 @@ FurnaceGUI::FurnaceGUI(): sampleFilterRes(0.25f), sampleFilterCutStart(16000.0f), sampleFilterCutEnd(100.0f), - sampleFilterPower(1) { + sampleFilterPower(1), + sampleClipboard(NULL), + sampleClipboardLen(0) { // value keys valueKeys[SDLK_0]=0; valueKeys[SDLK_1]=1; diff --git a/src/gui/gui.h b/src/gui/gui.h index 7433750ea..58539ab10 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -41,6 +41,8 @@ #define MARK_MODIFIED modified=true; +#define TOGGLE_COLOR(x) ((x)?uiColors[GUI_COLOR_TOGGLE_ON]:uiColors[GUI_COLOR_TOGGLE_OFF]) + enum FurnaceGUIColors { GUI_COLOR_BACKGROUND=0, GUI_COLOR_FRAME_BACKGROUND, @@ -49,6 +51,8 @@ enum FurnaceGUIColors { GUI_COLOR_TEXT, GUI_COLOR_ACCENT_PRIMARY, GUI_COLOR_ACCENT_SECONDARY, + GUI_COLOR_TOGGLE_OFF, + GUI_COLOR_TOGGLE_ON, GUI_COLOR_EDITING, GUI_COLOR_SONG_LOOP, @@ -774,6 +778,8 @@ class FurnaceGUI { unsigned int sampleDragLen; float sampleFilterL, sampleFilterB, sampleFilterH, sampleFilterRes, sampleFilterCutStart, sampleFilterCutEnd; unsigned char sampleFilterPower; + void* sampleClipboard; + size_t sampleClipboardLen; // visualizer float keyHit[DIV_MAX_CHANS]; diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index a1ba5f572..93b85a197 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1370,25 +1370,25 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_C64) if (ImGui::BeginTabItem("C64")) { ImGui::Text("Waveform"); ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(ins->c64.triOn)?0.6f:0.2f,0.2f,1.0f)); + ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(ins->c64.triOn)); if (ImGui::Button("tri")) { PARAMETER ins->c64.triOn=!ins->c64.triOn; } ImGui::PopStyleColor(); ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(ins->c64.sawOn)?0.6f:0.2f,0.2f,1.0f)); + ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(ins->c64.sawOn)); if (ImGui::Button("saw")) { PARAMETER ins->c64.sawOn=!ins->c64.sawOn; } ImGui::PopStyleColor(); ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(ins->c64.pulseOn)?0.6f:0.2f,0.2f,1.0f)); + ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(ins->c64.pulseOn)); if (ImGui::Button("pulse")) { PARAMETER ins->c64.pulseOn=!ins->c64.pulseOn; } ImGui::PopStyleColor(); ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(ins->c64.noiseOn)?0.6f:0.2f,0.2f,1.0f)); + ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(ins->c64.noiseOn)); if (ImGui::Button("noise")) { PARAMETER ins->c64.noiseOn=!ins->c64.noiseOn; } @@ -1417,25 +1417,25 @@ void FurnaceGUI::drawInsEdit() { ImGui::Text("Filter Mode"); ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(ins->c64.lp)?0.6f:0.2f,0.2f,1.0f)); + ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(ins->c64.lp)); if (ImGui::Button("low")) { PARAMETER ins->c64.lp=!ins->c64.lp; } ImGui::PopStyleColor(); ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(ins->c64.bp)?0.6f:0.2f,0.2f,1.0f)); + ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(ins->c64.bp)); if (ImGui::Button("band")) { PARAMETER ins->c64.bp=!ins->c64.bp; } ImGui::PopStyleColor(); ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(ins->c64.hp)?0.6f:0.2f,0.2f,1.0f)); + ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(ins->c64.hp)); if (ImGui::Button("high")) { PARAMETER ins->c64.hp=!ins->c64.hp; } ImGui::PopStyleColor(); ImGui::SameLine(); - ImGui::PushStyleColor(ImGuiCol_Button,ImVec4(0.2f,(ins->c64.ch3off)?0.6f:0.2f,0.2f,1.0f)); + ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(ins->c64.ch3off)); if (ImGui::Button("ch3off")) { PARAMETER ins->c64.ch3off=!ins->c64.ch3off; } diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index 6b9694124..b33c1e802 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -137,16 +137,20 @@ void FurnaceGUI::drawSampleEdit() { ImGui::BeginDisabled(sample->depth!=8 && sample->depth!=16); + ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(!sampleDragMode)); if (ImGui::Button(ICON_FA_I_CURSOR "##SSelect")) { sampleDragMode=false; } + ImGui::PopStyleColor(); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Edit mode: Select"); } ImGui::SameLine(); + ImGui::PushStyleColor(ImGuiCol_Button,TOGGLE_COLOR(sampleDragMode)); if (ImGui::Button(ICON_FA_PENCIL "##SDraw")) { sampleDragMode=true; } + ImGui::PopStyleColor(); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Edit mode: Draw"); } diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 5348bfa06..b89ed593f 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -497,6 +497,8 @@ void FurnaceGUI::drawSettings() { UI_COLOR_CONFIG(GUI_COLOR_TEXT,"Text"); UI_COLOR_CONFIG(GUI_COLOR_ACCENT_PRIMARY,"Primary"); UI_COLOR_CONFIG(GUI_COLOR_ACCENT_SECONDARY,"Secondary"); + UI_COLOR_CONFIG(GUI_COLOR_TOGGLE_ON,"Toggle on"); + UI_COLOR_CONFIG(GUI_COLOR_TOGGLE_OFF,"Toggle off"); UI_COLOR_CONFIG(GUI_COLOR_EDITING,"Editing"); UI_COLOR_CONFIG(GUI_COLOR_SONG_LOOP,"Song loop"); UI_COLOR_CONFIG(GUI_COLOR_PLAYBACK_STAT,"Playback status"); @@ -1199,6 +1201,8 @@ void FurnaceGUI::commitSettings() { PUT_UI_COLOR(GUI_COLOR_TEXT); PUT_UI_COLOR(GUI_COLOR_ACCENT_PRIMARY); PUT_UI_COLOR(GUI_COLOR_ACCENT_SECONDARY); + PUT_UI_COLOR(GUI_COLOR_TOGGLE_ON); + PUT_UI_COLOR(GUI_COLOR_TOGGLE_OFF); PUT_UI_COLOR(GUI_COLOR_EDITING); PUT_UI_COLOR(GUI_COLOR_SONG_LOOP); PUT_UI_COLOR(GUI_COLOR_VOLMETER_LOW); @@ -1537,6 +1541,8 @@ void FurnaceGUI::applyUISettings() { GET_UI_COLOR(GUI_COLOR_TEXT,ImVec4(1.0f,1.0f,1.0f,1.0f)); GET_UI_COLOR(GUI_COLOR_ACCENT_PRIMARY,ImVec4(0.06f,0.53f,0.98f,1.0f)); GET_UI_COLOR(GUI_COLOR_ACCENT_SECONDARY,ImVec4(0.26f,0.59f,0.98f,1.0f)); + GET_UI_COLOR(GUI_COLOR_TOGGLE_ON,ImVec4(0.2f,0.6f,0.2f,1.0f)); + GET_UI_COLOR(GUI_COLOR_TOGGLE_OFF,ImVec4(0.2f,0.2f,0.2f,1.0f)); GET_UI_COLOR(GUI_COLOR_EDITING,ImVec4(0.2f,0.1f,0.1f,1.0f)); GET_UI_COLOR(GUI_COLOR_SONG_LOOP,ImVec4(0.3f,0.5f,0.8f,0.4f)); GET_UI_COLOR(GUI_COLOR_VOLMETER_LOW,ImVec4(0.2f,0.6f,0.2f,1.0f)); From feb138cefc5d08f21f5768fbc582c364e166c5cd Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Tue, 22 Mar 2022 14:48:48 +0700 Subject: [PATCH 374/637] Add VIC-20 support --- CMakeLists.txt | 3 + src/engine/dispatchContainer.cpp | 4 + src/engine/platform/sound/vic20sound.c | 253 +++++++++++++++++++ src/engine/platform/sound/vic20sound.h | 69 ++++++ src/engine/platform/vic20.cpp | 329 +++++++++++++++++++++++++ src/engine/platform/vic20.h | 86 +++++++ src/engine/playback.cpp | 1 + src/gui/guiConst.cpp | 1 + src/gui/insEdit.cpp | 6 +- 9 files changed, 749 insertions(+), 3 deletions(-) create mode 100644 src/engine/platform/sound/vic20sound.c create mode 100644 src/engine/platform/sound/vic20sound.h create mode 100644 src/engine/platform/vic20.cpp create mode 100644 src/engine/platform/vic20.h diff --git a/CMakeLists.txt b/CMakeLists.txt index f09c88a64..b95900c8c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -273,6 +273,8 @@ src/engine/platform/sound/swan.cpp src/engine/platform/sound/k005289/k005289.cpp +src/engine/platform/sound/vic20sound.c + src/engine/platform/ym2610Interface.cpp src/engine/blip_buf.c @@ -321,6 +323,7 @@ src/engine/platform/swan.cpp src/engine/platform/vera.cpp src/engine/platform/bubsyswsg.cpp src/engine/platform/pet.cpp +src/engine/platform/vic20.cpp src/engine/platform/dummy.cpp ) diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index be429c356..0f553772b 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -47,6 +47,7 @@ #include "platform/lynx.h" #include "platform/bubsyswsg.h" #include "platform/pet.h" +#include "platform/vic20.h" #include "platform/dummy.h" #include "../ta-log.h" #include "song.h" @@ -279,6 +280,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_PET: dispatch=new DivPlatformPET; break; + case DIV_SYSTEM_VIC20: + dispatch=new DivPlatformVIC20; + break; default: logW("this system is not supported yet! using dummy platform.\n"); dispatch=new DivPlatformDummy; diff --git a/src/engine/platform/sound/vic20sound.c b/src/engine/platform/sound/vic20sound.c new file mode 100644 index 000000000..8a42bc707 --- /dev/null +++ b/src/engine/platform/sound/vic20sound.c @@ -0,0 +1,253 @@ +/* + * vic20sound.c - Implementation of VIC20 sound code. + * + * Written by + * Rami Rasanen + * Ville-Matias Heikkila + * + * This file is part of VICE, the Versatile Commodore Emulator. + * See README for copyright notice. + * + * 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., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA. + * + */ + +#include +#include +#include + +#include "vic20sound.h" + +/* ---------------------------------------------------------------------*/ + +static float voltagefunction[] = { + 0.00f, 148.28f, 296.55f, 735.97f, 914.88f, 1126.89f, 1321.86f, 1503.07f, 1603.50f, + 1758.00f, 1913.98f, 2070.94f, 2220.36f, 2342.91f, 2488.07f, 3188.98f, 3285.76f, 3382.53f, + 3479.31f, 3576.08f, 3672.86f, 3769.63f, 3866.41f, 3963.18f, 4059.96f, 4248.10f, 4436.24f, + 4624.38f, 4812.53f, 5000.67f, 5188.81f, 5192.91f, 5197.00f, 5338.52f, 5480.04f, 5621.56f, + 5763.07f, 5904.59f, 6046.11f, 6187.62f, 6329.14f, 6609.31f, 6889.47f, 7169.64f, 7449.80f, + 7729.97f, 7809.36f, 7888.75f, 7968.13f, 8047.52f, 8126.91f, 8206.30f, 8285.69f, 8365.07f, + 8444.46f, 8523.85f, 8603.24f, 8905.93f, 9208.63f, 9511.32f, 9814.02f, 9832.86f, 9851.70f, + 9870.54f, 9889.38f, 9908.22f, 9927.07f, 9945.91f, 9964.75f, 9983.59f, 10002.43f, 10021.27f, + 10040.12f, 10787.23f, 11534.34f, 12281.45f, 12284.98f, 12288.50f, 12292.03f, 12295.56f, 12299.09f, + 12302.62f, 12306.15f, 12309.68f, 12313.21f, 12316.74f, 12320.26f, 12323.79f, 12327.32f, 13113.05f, + 13898.78f, 13910.58f, 13922.39f, 13934.19f, 13945.99f, 13957.80f, 13969.60f, 13981.40f, 13993.21f, + 14005.01f, 14016.81f, 14028.62f, 14040.42f, 14052.22f, 14064.03f, 16926.31f, 16987.04f, 17047.77f, + 17108.50f, 17169.23f, 17229.96f, 17290.69f, 17351.42f, 17412.15f, 17472.88f, 17533.61f, 17594.34f, + 17655.07f, 17715.80f, 17776.53f, 17837.26f, 18041.51f, 18245.77f, 18450.02f, 18654.28f, 18858.53f, + 19062.78f, 19267.04f, 19471.29f, 19675.55f, 19879.80f, 20084.05f, 20288.31f, 20417.74f, 20547.17f, + 20676.61f, 20774.26f, 20871.91f, 20969.55f, 21067.20f, 21164.85f, 21262.50f, 21360.15f, 21457.80f, + 21555.45f, 21653.09f, 21750.74f, 21848.39f, 21946.04f, 22043.69f, 22141.34f, 22212.33f, 22283.33f, + 22354.33f, 22425.33f, 22496.32f, 22567.32f, 22638.32f, 22709.32f, 22780.31f, 22851.31f, 22922.31f, + 22993.31f, 23064.30f, 23135.30f, 23206.30f, 23255.45f, 23304.60f, 23353.75f, 23402.91f, 23452.06f, + 23501.21f, 23550.36f, 23599.51f, 23648.67f, 23768.81f, 23888.96f, 24009.11f, 24129.26f, 24249.41f, + 24369.56f, 24451.92f, 24534.28f, 24616.63f, 24698.99f, 24781.35f, 24863.70f, 24946.06f, 25028.42f, + 25110.77f, 25193.13f, 25275.49f, 25357.84f, 25440.20f, 25522.56f, 25604.92f, 25658.87f, 25712.83f, + 25766.79f, 25820.75f, 25874.71f, 25928.66f, 25982.62f, 26036.58f, 26090.54f, 26144.49f, 26198.45f, + 26252.41f, 26306.37f, 26360.33f, 26414.28f, 26501.23f, 26588.17f, 26675.12f, 26762.06f, 26849.01f, + 26935.95f, 27022.90f, 27109.84f, 27196.78f, 27283.73f, 27370.67f, 27457.62f, 27544.56f, 27631.51f, + 27718.45f, 27726.89f, 27735.33f, 27743.78f, 27752.22f, 27760.66f, 27769.10f, 27777.54f, 27785.98f, + 27794.43f, 27802.87f, 27811.31f, 27819.75f, 27828.19f, 27836.63f, 27845.08f, 27853.52f, 27861.96f, + 27870.40f, 27878.84f, 27887.28f, 27895.73f, 27904.17f, 27912.61f, 27921.05f, 27929.49f, 27937.93f, + 27946.38f, 27954.82f, 27963.26f, 27971.70f, 27980.14f, 27988.58f, 27997.03f, 28005.47f, 28013.91f, + 28022.35f, 28030.79f, 28039.23f, 28047.68f, 28056.12f, 28064.56f, 28073.00f, 28081.44f, 28089.88f, + 28098.33f, 28106.77f, 28115.21f, 28123.65f, 28132.09f, 28140.53f, 28148.98f, 28157.42f, 28165.86f, + 28174.30f, 28182.74f, 28191.18f, 28199.63f, 28208.07f, 28216.51f, 28224.95f, 28233.39f, 28241.83f, + 28250.28f, 28258.72f, 28267.16f, 28275.60f, 28284.04f, 28292.48f, 28300.93f, 28309.37f, 28317.81f, + 28326.25f, 28334.69f, 28343.13f, 28351.58f, 28360.02f, 28368.46f, 28376.90f, 28385.34f, 28393.78f, + 28402.23f, 28410.67f, 28419.11f, 28427.55f, 28435.99f, 28444.43f, 28452.88f, 28461.32f, 28469.76f, + 28478.20f, 28486.64f, 28495.08f, 28503.53f, 28511.97f, 28520.41f, 28528.85f, 28537.29f, 28545.73f, + 28554.18f, 28562.62f, 28571.06f, 28579.50f, 28587.94f, 28596.38f, 28604.83f, 28613.27f, 28621.71f, + 28630.15f, 28638.59f, 28647.03f, 28655.48f, 28663.92f, 28672.36f, 28680.80f, 28689.24f, 28697.68f, + 28706.13f, 28714.57f, 28723.01f, 28731.45f, 28739.89f, 28748.33f, 28756.78f, 28765.22f, 28773.66f, + 28782.10f, 28790.54f, 28798.98f, 28807.43f, 28815.87f, 28824.31f, 28832.75f, 28841.19f, 28849.63f, + 28858.08f, 28866.52f, 28874.96f, 28883.40f, 28891.84f, 28900.28f, 28908.73f, 28917.17f, 28925.61f, + 28934.05f, 28942.49f, 28950.93f, 28959.38f, 28967.82f, 28976.26f, 28984.70f, 28993.14f, 29001.58f, + 29010.03f, 29018.47f, 29026.91f, 29035.35f, 29043.79f, 29052.23f, 29060.68f, 29069.12f, 29077.56f, + 29086.00f, 29094.44f, 29102.88f, 29111.33f, 29119.77f, 29128.21f, 29136.65f, 29145.09f, 29153.53f, + 29161.98f, 29170.42f, 29178.86f, 29187.30f, 29195.74f, 29204.18f, 29212.63f, 29221.07f, 29229.51f, + 29237.95f, 29246.39f, 29254.83f, 29263.28f, 29271.72f, 29280.16f, 29288.60f, 29297.04f, 29305.48f, + 29313.93f, 29322.37f, 29330.81f, 29339.25f, 29347.69f, 29356.13f, 29364.58f, 29373.02f, 29381.46f, + 29389.90f, 29398.34f, 29406.78f, 29415.23f, 29423.67f, 29432.11f, 29440.55f, 29448.99f, 29457.43f, + 29465.88f, 29474.32f, 29482.76f, 29491.20f +}; + +static void vic_sound_clock(sound_vic20_t *snd, uint32_t cycles); + +int vic_sound_machine_calculate_samples(sound_vic20_t *snd, int16_t *pbuf, int nr, int soc, int scc, uint32_t delta_t) +{ + int s = 0; + int i; + float o; + int16_t vicbuf; + int samples_to_do; + + while (s < nr && delta_t >= snd->cycles_per_sample - snd->leftover_cycles) { + samples_to_do = (int)(snd->cycles_per_sample - snd->leftover_cycles); + snd->leftover_cycles += samples_to_do - snd->cycles_per_sample; + vic_sound_clock(snd, samples_to_do); + + o = snd->lowpassbuf - snd->highpassbuf; + snd->highpassbuf += snd->highpassbeta * (snd->lowpassbuf - snd->highpassbuf); + snd->lowpassbuf += snd->lowpassbeta * (voltagefunction[(((snd->accum * 7) / snd->accum_cycles) + 1) * snd->volume] - snd->lowpassbuf); + + if (o < -32768) { + vicbuf = -32768; + } else if (o > 32767) { + vicbuf = 32767; + } else { + vicbuf = (int16_t)o; + } + + for (i = 0; i < soc; i++) { + pbuf[(s * soc) + i] = vicbuf; + } + s++; + snd->accum = 0; + snd->accum_cycles = 0; + delta_t -= samples_to_do; + } + if (delta_t > 0) { + snd->leftover_cycles += delta_t; + vic_sound_clock(snd, delta_t); + delta_t = 0; + } + return s; +} + +static uint16_t noise_LFSR = 0x0000; +static uint8_t noise_LFSR0_old = 0; + +static void vic_sound_clock(sound_vic20_t *snd, uint32_t cycles) +{ + uint32_t i; + int j, enabled; + + if (cycles <= 0) { + return; + } + + for (j = 0; j < 4; j++) { + int chspeed = "\4\3\2\1"[j]; + + if (snd->ch[j].ctr > cycles) { + snd->accum += snd->ch[j].out * cycles; + snd->ch[j].ctr -= cycles; + } else { + for (i = cycles; i; i--) { + snd->ch[j].ctr--; + if (snd->ch[j].ctr <= 0) { + int a = (~snd->ch[j].reg) & 127; + int edge_trigger; + a = a ? a : 128; + snd->ch[j].ctr += a << chspeed; + enabled = (snd->ch[j].reg & 128) >> 7; + edge_trigger = (noise_LFSR & 1) & !noise_LFSR0_old; + + if((j != 3) || ((j == 3) && edge_trigger)) { + uint8_t shift = snd->ch[j].shift; + shift = ((shift << 1) | (((((shift & 128) >> 7)) ^ 1) & enabled)); + snd->ch[j].shift = shift; + } + if(j == 3) { + int bit3 = (noise_LFSR >> 3) & 1; + int bit12 = (noise_LFSR >> 12) & 1; + int bit14 = (noise_LFSR >> 14) & 1; + int bit15 = (noise_LFSR >> 15) & 1; + int gate1 = bit3 ^ bit12; + int gate2 = bit14 ^ bit15; + int gate3 = (gate1 ^ gate2) ^ 1; + int gate4 = (gate3 & enabled) ^ 1; + noise_LFSR0_old = noise_LFSR & 1; + noise_LFSR = (noise_LFSR << 1) | gate4; + } + snd->ch[j].out = snd->ch[j].shift & (j == 3 ? enabled : 1); + } + snd->accum += snd->ch[j].out; /* FIXME: doesn't take DC offset into account */ + } + } + } + + snd->accum_cycles += cycles; +} + +void vic_sound_machine_store(sound_vic20_t *snd, uint16_t addr, uint8_t value) +{ + switch (addr) { + case 0xA: + snd->ch[0].reg = value; + break; + case 0xB: + snd->ch[1].reg = value; + break; + case 0xC: + snd->ch[2].reg = value; + break; + case 0xD: + snd->ch[3].reg = value; + break; + case 0xE: + snd->volume = value & 0x0f; + break; + } +} + +int vic_sound_machine_init(sound_vic20_t *snd, int speed, int cycles_per_sec) +{ + uint32_t i; + float dt; + + memset(snd, 0, sizeof(snd)); + + snd->cycles_per_sample = (float)cycles_per_sec / speed; + snd->leftover_cycles = 0.0f; + + snd->lowpassbuf = 0.0f; + snd->highpassbuf = 0.0f; + + snd->speed = speed; + + dt = 1.f / speed; + + /* + Audio output stage + + 5V + -----+ + audio| 1k | + +---+---R---+--------(K) +----- + out | | | | |audio + -----+ C .01 C .1 | 1 uF | + | uF | uF +-----C-----+ 1K + | | + GND GND R 470 | amp + | +----- + + GND + + */ + + /* Low-pass: R = 1 kOhm, C = 100 nF; RC = 1e3*1e-7 = 1e-4 (1591 Hz) */ + snd->lowpassbeta = dt / ( dt + 1e-4f ); + /* High-pass: R = 1 kOhm, C = 1 uF; RC = 1e3*1e-6 = 1e-3 ( 159 Hz) */ + snd->highpassbeta = dt / ( dt + 1e-3f ); + + for (i = 0; i < 16; i++) { + vic_sound_machine_store(snd, (uint16_t)i, 0); + } + + return 1; +} diff --git a/src/engine/platform/sound/vic20sound.h b/src/engine/platform/sound/vic20sound.h new file mode 100644 index 000000000..334833304 --- /dev/null +++ b/src/engine/platform/sound/vic20sound.h @@ -0,0 +1,69 @@ +/* + * vic20sound.h - implementation of VIC20 sound code + * + * Written by + * Teemu Rantanen + * + * This file is part of VICE, the Versatile Commodore Emulator. + * See README for copyright notice. + * + * 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., 59 Temple Place, Suite 330, Boston, MA + * 02111-1307 USA. + * + */ + +#ifndef VICE_VIC20SOUND_H +#define VICE_VIC20SOUND_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +struct sound_vic20_s { + unsigned char div; + struct { + unsigned char out; + unsigned char reg; + unsigned char shift; + signed short ctr; + } ch[4]; + unsigned short noisectr; + unsigned char volume; + int cyclecount; + + int accum; + int accum_cycles; + + float cycles_per_sample; + float leftover_cycles; + int speed; + + float highpassbuf; + float highpassbeta; + float lowpassbuf; + float lowpassbeta; +}; +typedef struct sound_vic20_s sound_vic20_t; + +int vic_sound_machine_init(sound_vic20_t *snd, int speed, int cycles_per_sec); +void vic_sound_machine_store(sound_vic20_t *snd, uint16_t addr, uint8_t value); +int vic_sound_machine_calculate_samples(sound_vic20_t *snd, int16_t *pbuf, int nr, int soc, int scc, uint32_t delta_t); + +#ifdef __cplusplus +}; +#endif +#endif diff --git a/src/engine/platform/vic20.cpp b/src/engine/platform/vic20.cpp new file mode 100644 index 000000000..558bc2123 --- /dev/null +++ b/src/engine/platform/vic20.cpp @@ -0,0 +1,329 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "vic20.h" +#include "../engine.h" +#include + +#define rWrite(a,v) {regPool[(a)]=(v)&0xff; vic_sound_machine_store(vic,a,(v)&0xff);} + +#define CHIP_DIVIDER 32 +#define SAMP_DIVIDER 4 + +const char* regCheatSheetVIC[]={ + "CH1_Pitch", "0A", + "CH2_Pitch", "0B", + "CH3_Pitch", "0C", + "Noise_Pitch", "0D", + "Volume", "0E", + NULL +}; + +const char** DivPlatformVIC20::getRegisterSheet() { + return regCheatSheetVIC; +} + +const char* DivPlatformVIC20::getEffectName(unsigned char effect) { + switch (effect) { + case 0x10: + return "10xx: Change waveform"; + break; + } + return NULL; +} + +void DivPlatformVIC20::acquire(short* bufL, short* bufR, size_t start, size_t len) { + const unsigned char loadFreq[3] = {0x7e, 0x7d, 0x7b}; + const unsigned char wavePatterns[16] = { + 0b0, 0b10, 0b100, 0b110, 0b1000, 0b1010, 0b1011, 0b1110, + 0b10010, 0b10100, 0b10110, 0b11000, 0b11010, 0b100100, 0b101010, 0b101100 + }; + for (size_t h=start; h=0) { + if (chan[i].waveWriteCycle>=16*7) { + // empty shift register first + rWrite(10+i,126); + } else if (chan[i].waveWriteCycle>=16) { + unsigned bit=8-(chan[i].waveWriteCycle/16); + rWrite(10+i,loadFreq[i]|((wavePatterns[chan[i].wave]<calcFreq(chan[i].baseFreq,chan[i].pitch,true); + if (i<3) chan[i].freq>>=(2-i); + else chan[i].freq>>=1; + if (chan[i].freq<1) chan[i].freq=1; + if (chan[i].freq>127) chan[i].freq=0; + if (isMuted[i]) chan[i].keyOn=false; + if (chan[i].keyOn) { + if (i<3) { + // 128*16 cycles for lowest channel to finish counting at lowest freq + // 2*16 cycles for lowest channel to empty first 2 bits + // 7*16 cycles to write 7 bits + chan[i].waveWriteCycle=137*16-1; + hasWaveWrite=true; + } else { + rWrite(10+i,255-chan[i].freq); + } + chan[i].keyOn=false; + } else if (chan[i].freqChanged && chan[i].active && !isMuted[i]) { + rWrite(10+i,255-chan[i].freq); + } + if (chan[i].keyOff) { + rWrite(10+i,0); + chan[i].keyOff=false; + } + chan[i].freqChanged=false; + } + } +} + +int DivPlatformVIC20::dispatch(DivCommand c) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: { + DivInstrument* ins=parent->getIns(chan[c.chan].ins); + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + } + chan[c.chan].active=true; + chan[c.chan].keyOn=true; + chan[c.chan].std.init(ins); + break; + } + case DIV_CMD_NOTE_OFF: + chan[c.chan].active=false; + chan[c.chan].keyOff=true; + chan[c.chan].std.init(NULL); + break; + case DIV_CMD_NOTE_OFF_ENV: + case DIV_CMD_ENV_RELEASE: + chan[c.chan].std.release(); + break; + case DIV_CMD_INSTRUMENT: + if (chan[c.chan].ins!=c.value || c.value2==1) { + chan[c.chan].ins=c.value; + } + break; + case DIV_CMD_VOLUME: + if (chan[c.chan].vol!=c.value) { + chan[c.chan].vol=c.value; + if (!chan[c.chan].std.hadVol) { + calcAndWriteOutVol(c.chan,15); + } + } + break; + case DIV_CMD_GET_VOLUME: + return chan[c.chan].vol; + break; + case DIV_CMD_PITCH: + chan[c.chan].pitch=c.value; + chan[c.chan].freqChanged=true; + break; + case DIV_CMD_WAVE: + chan[c.chan].wave=c.value&0x0f; + chan[c.chan].keyOn=true; + break; + case DIV_CMD_NOTE_PORTA: { + int destFreq=NOTE_PERIODIC(c.value2); + bool return2=false; + if (destFreq>chan[c.chan].baseFreq) { + chan[c.chan].baseFreq+=c.value; + if (chan[c.chan].baseFreq>=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } else { + chan[c.chan].baseFreq-=c.value; + if (chan[c.chan].baseFreq<=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } + chan[c.chan].freqChanged=true; + if (return2) { + chan[c.chan].inPorta=false; + return 2; + } + break; + } + case DIV_CMD_LEGATO: + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.willArp && !chan[c.chan].std.arpMode)?(chan[c.chan].std.arp):(0))); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + break; + case DIV_CMD_PRE_PORTA: + if (chan[c.chan].active && c.value2) { + if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + } + chan[c.chan].inPorta=c.value; + break; + case DIV_CMD_GET_VOLMAX: + return 15; + break; + case DIV_ALWAYS_SET_VOLUME: + return 1; + break; + default: + break; + } + return 1; +} + +void DivPlatformVIC20::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; + if (mute) { + chan[ch].keyOff=true; + } else if (chan[ch].active) { + chan[ch].keyOn=true; + } +} + +void DivPlatformVIC20::forceIns() { + for (int i=0; i<4; i++) { + chan[i].insChanged=true; + chan[i].freqChanged=true; + writeOutVol(i); + } +} + +void* DivPlatformVIC20::getChanState(int ch) { + return &chan[ch]; +} + +unsigned char* DivPlatformVIC20::getRegisterPool() { + return regPool; +} + +int DivPlatformVIC20::getRegisterPoolSize() { + return 16; +} + +void DivPlatformVIC20::reset() { + memset(regPool,0,16); + for (int i=0; i<4; i++) { + chan[i]=Channel(); + } + vic_sound_machine_init(vic,rate,chipClock); + hasWaveWrite=false; + rWrite(14,15); +} + +bool DivPlatformVIC20::isStereo() { + return false; +} + +void DivPlatformVIC20::notifyInsDeletion(void* ins) { + for (int i=0; i<4; i++) { + chan[i].std.notifyInsDeletion((DivInstrument*)ins); + } +} + +void DivPlatformVIC20::setFlags(unsigned int flags) { + if (flags&1) { + chipClock=COLOR_PAL/4.0; + } else { + chipClock=COLOR_NTSC*2.0/7.0; + } + rate=chipClock/4; +} + +void DivPlatformVIC20::poke(unsigned int addr, unsigned short val) { + rWrite(addr,val); +} + +void DivPlatformVIC20::poke(std::vector& wlist) { + for (DivRegWrite& i: wlist) rWrite(i.addr,i.val); +} + +int DivPlatformVIC20::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { + parent=p; + dumpWrites=false; + skipRegisterWrites=false; + for (int i=0; i<4; i++) { + isMuted[i]=false; + } + setFlags(flags); + vic=new sound_vic20_t(); + reset(); + return 4; +} + +void DivPlatformVIC20::quit() { + delete vic; +} + +DivPlatformVIC20::~DivPlatformVIC20() { +} diff --git a/src/engine/platform/vic20.h b/src/engine/platform/vic20.h new file mode 100644 index 000000000..1c4d384b7 --- /dev/null +++ b/src/engine/platform/vic20.h @@ -0,0 +1,86 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _VIC20_H +#define _VIC20_H + +#include "../dispatch.h" +#include "../macroInt.h" +#include "sound/vic20sound.h" +#include + +class DivPlatformVIC20: public DivDispatch { + struct Channel { + int freq, baseFreq, pitch, note; + unsigned char ins, pan; + bool active, insChanged, freqChanged, keyOn, keyOff, inPorta; + int vol, outVol, wave, waveWriteCycle; + DivMacroInt std; + Channel(): + freq(0), + baseFreq(0), + pitch(0), + note(0), + ins(-1), + pan(255), + active(false), + insChanged(true), + freqChanged(false), + keyOn(false), + keyOff(false), + inPorta(false), + vol(15), + outVol(15), + wave(0), + waveWriteCycle(-1) {} + }; + Channel chan[4]; + bool isMuted[4]; + bool hasWaveWrite; + + unsigned char regPool[16]; + sound_vic20_t* vic; + void updateWave(int ch); + friend void putDispatchChan(void*,int,int); + public: + void acquire(short* bufL, short* bufR, size_t start, size_t len); + int dispatch(DivCommand c); + void* getChanState(int chan); + unsigned char* getRegisterPool(); + int getRegisterPoolSize(); + void reset(); + void forceIns(); + void tick(); + void muteChannel(int ch, bool mute); + void setFlags(unsigned int flags); + void notifyInsDeletion(void* ins); + bool isStereo(); + void poke(unsigned int addr, unsigned short val); + void poke(std::vector& wlist); + const char** getRegisterSheet(); + const char* getEffectName(unsigned char effect); + int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); + void quit(); + ~DivPlatformVIC20(); + private: + void calcAndWriteOutVol(int ch, int env); + void writeOutVol(int ch); +}; + +#endif diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index cf3e1309f..404901a53 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -315,6 +315,7 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe break; case DIV_SYSTEM_BUBSYS_WSG: case DIV_SYSTEM_PET: + case DIV_SYSTEM_VIC20: switch (effect) { case 0x10: // select waveform dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal)); diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index 695ab163e..a25e831e5 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -164,5 +164,6 @@ const int availableSystems[]={ DIV_SYSTEM_VERA, DIV_SYSTEM_BUBSYS_WSG, DIV_SYSTEM_PET, + DIV_SYSTEM_VIC20, 0 // don't remove this last one! }; \ No newline at end of file diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index a1ba5f572..38642007b 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1584,7 +1584,7 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_AY8930) { dutyMax=255; } - if (ins->type==DIV_INS_TIA || ins->type==DIV_INS_AMIGA || ins->type==DIV_INS_SCC || ins->type==DIV_INS_PET) { + if (ins->type==DIV_INS_TIA || ins->type==DIV_INS_AMIGA || ins->type==DIV_INS_SCC || ins->type==DIV_INS_PET || ins->type==DIV_INS_VIC) { dutyMax=0; } if (ins->type==DIV_INS_PCE) { @@ -1609,7 +1609,7 @@ void FurnaceGUI::drawInsEdit() { bitMode=true; } if (ins->type==DIV_INS_STD) waveMax=0; - if (ins->type==DIV_INS_TIA) waveMax=15; + if (ins->type==DIV_INS_TIA || ins->type==DIV_INS_VIC) waveMax=15; if (ins->type==DIV_INS_C64) waveMax=4; if (ins->type==DIV_INS_SAA1099) waveMax=2; if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPLL || ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPZ) waveMax=0; @@ -1946,4 +1946,4 @@ void FurnaceGUI::drawInsEdit() { } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_INS_EDIT; ImGui::End(); -} \ No newline at end of file +} From 705ba4273bbcd2148460e4a021dd2dd6e2b57ebb Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 22 Mar 2022 04:54:01 -0500 Subject: [PATCH 375/637] GUI: sample editor keybinds works --- src/engine/sample.cpp | 36 ++++ src/engine/sample.h | 9 + src/gui/actionUtil.h | 19 +++ src/gui/doAction.cpp | 366 +++++++++++++++++++++++++++++++++++++++++ src/gui/gui.cpp | 16 +- src/gui/gui.h | 33 +++- src/gui/sampleEdit.cpp | 232 +++----------------------- src/gui/sampleUtil.h | 31 ++++ src/gui/settings.cpp | 95 +++++++++++ 9 files changed, 629 insertions(+), 208 deletions(-) create mode 100644 src/gui/sampleUtil.h diff --git a/src/engine/sample.cpp b/src/engine/sample.cpp index 66e54d857..33532d43d 100644 --- a/src/engine/sample.cpp +++ b/src/engine/sample.cpp @@ -258,6 +258,42 @@ bool DivSample::trim(unsigned int begin, unsigned int end) { return false; } +// TODO: for clipboard +bool DivSample::insert(unsigned int pos, unsigned int length) { + unsigned int count=samples+length; + if (depth==8) { + if (data8!=NULL) { + signed char* oldData8=data8; + data8=NULL; + initInternal(8,count); + if (pos>0) { + memcpy(data8,oldData8,pos); + } + if (count-pos-length>0) { + memcpy(data8+pos+length,oldData8+pos,count-pos-length); + } + delete[] oldData8; + } else { + initInternal(8,count); + } + samples=count; + return true; + } else if (depth==16) { + if (data16!=NULL) { + short* oldData16=data16; + data16=NULL; + initInternal(16,count); + memcpy(data16,oldData16,sizeof(short)*count); + delete[] oldData16; + } else { + initInternal(16,count); + } + samples=count; + return true; + } + return false; +} + #define RESAMPLE_BEGIN \ if (samples<1) return true; \ int finalCount=(double)samples*(r/(double)rate); \ diff --git a/src/engine/sample.h b/src/engine/sample.h index 7e4d76bec..c697a9951 100644 --- a/src/engine/sample.h +++ b/src/engine/sample.h @@ -120,6 +120,15 @@ struct DivSample { */ bool trim(unsigned int begin, unsigned int end); + /** + * insert silence at specified position. + * @warning do not attempt to do this outside of a synchronized block! + * @param pos the beginning. + * @param length how many samples to insert. + * @return whether it was successful. + */ + bool insert(unsigned int pos, unsigned int length); + /** * change the sample rate. * @warning do not attempt to resample outside of a synchronized block! diff --git a/src/gui/actionUtil.h b/src/gui/actionUtil.h index 4e9196afc..752a54423 100644 --- a/src/gui/actionUtil.h +++ b/src/gui/actionUtil.h @@ -1,3 +1,22 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + #define DETERMINE_FIRST \ int firstChannel=0; \ for (int i=0; igetTotalChannelCount(); i++) { \ diff --git a/src/gui/doAction.cpp b/src/gui/doAction.cpp index 903f6edf0..51dad892e 100644 --- a/src/gui/doAction.cpp +++ b/src/gui/doAction.cpp @@ -19,8 +19,10 @@ #include "gui.h" #include +#include #include "actionUtil.h" +#include "sampleUtil.h" void FurnaceGUI::doAction(int what) { switch (what) { @@ -577,6 +579,370 @@ void FurnaceGUI::doAction(int what) { e->stopSamplePreview(); break; + case GUI_ACTION_SAMPLE_SELECT: + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + sampleDragMode=false; + break; + case GUI_ACTION_SAMPLE_DRAW: + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + sampleDragMode=true; + break; + case GUI_ACTION_SAMPLE_CUT: { + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + DivSample* sample=e->song.sample[curSample]; + SAMPLE_OP_BEGIN; + + if (end-start<1) break; + + if (sampleClipboard!=NULL) { + delete[] sampleClipboard; + } + sampleClipboard=new short[end-start]; + sampleClipboardLen=end-start; + memcpy(sampleClipboard,&(sample->data16[start]),end-start); + + e->lockEngine([this,sample,start,end]() { + sample->strip(start,end); + updateSampleTex=true; + + e->renderSamples(); + }); + sampleSelStart=-1; + sampleSelEnd=-1; + MARK_MODIFIED; + + break; + } + case GUI_ACTION_SAMPLE_COPY: { + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + DivSample* sample=e->song.sample[curSample]; + SAMPLE_OP_BEGIN; + + if (end-start<1) break; + + if (sampleClipboard!=NULL) { + delete[] sampleClipboard; + } + sampleClipboard=new short[end-start]; + sampleClipboardLen=end-start; + memcpy(sampleClipboard,&(sample->data16[start]),end-start); + break; + } + case GUI_ACTION_SAMPLE_PASTE: // TODO!!! + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + break; + case GUI_ACTION_SAMPLE_PASTE_REPLACE: + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + break; + case GUI_ACTION_SAMPLE_PASTE_MIX: + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + break; + case GUI_ACTION_SAMPLE_SELECT_ALL: { + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + DivSample* sample=e->song.sample[curSample]; + sampleDragActive=false; + sampleSelStart=0; + sampleSelEnd=sample->samples; + break; + } + case GUI_ACTION_SAMPLE_RESIZE: + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + openSampleResizeOpt=true; + break; + case GUI_ACTION_SAMPLE_RESAMPLE: + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + openSampleResampleOpt=true; + break; + case GUI_ACTION_SAMPLE_AMPLIFY: + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + openSampleAmplifyOpt=true; + break; + case GUI_ACTION_SAMPLE_NORMALIZE: { + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + DivSample* sample=e->song.sample[curSample]; + e->lockEngine([this,sample]() { + SAMPLE_OP_BEGIN; + float maxVal=0.0f; + + if (sample->depth==16) { + for (unsigned int i=start; idata16[i]/32767.0f); + if (val>maxVal) maxVal=val; + } + if (maxVal>1.0f) maxVal=1.0f; + if (maxVal>0.0f) { + float vol=1.0f/maxVal; + for (unsigned int i=start; idata16[i]*vol; + if (val<-32768) val=-32768; + if (val>32767) val=32767; + sample->data16[i]=val; + } + } + } else if (sample->depth==8) { + for (unsigned int i=start; idata8[i]/127.0f); + if (val>maxVal) maxVal=val; + } + if (maxVal>1.0f) maxVal=1.0f; + if (maxVal>0.0f) { + float vol=1.0f/maxVal; + for (unsigned int i=start; idata8[i]*vol; + if (val<-128) val=-128; + if (val>127) val=127; + sample->data8[i]=val; + } + } + } + + updateSampleTex=true; + + e->renderSamples(); + }); + MARK_MODIFIED; + break; + } + case GUI_ACTION_SAMPLE_FADE_IN: { + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + DivSample* sample=e->song.sample[curSample]; + e->lockEngine([this,sample]() { + SAMPLE_OP_BEGIN; + + if (sample->depth==16) { + for (unsigned int i=start; idata16[i]*float(i-start)/float(end-start); + if (val<-32768) val=-32768; + if (val>32767) val=32767; + sample->data16[i]=val; + } + } else if (sample->depth==8) { + for (unsigned int i=start; idata8[i]*float(i-start)/float(end-start); + if (val<-128) val=-128; + if (val>127) val=127; + sample->data8[i]=val; + } + } + + updateSampleTex=true; + + e->renderSamples(); + }); + MARK_MODIFIED; + break; + } + case GUI_ACTION_SAMPLE_FADE_OUT: { + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + DivSample* sample=e->song.sample[curSample]; + e->lockEngine([this,sample]() { + SAMPLE_OP_BEGIN; + + if (sample->depth==16) { + for (unsigned int i=start; idata16[i]*float(end-i)/float(end-start); + if (val<-32768) val=-32768; + if (val>32767) val=32767; + sample->data16[i]=val; + } + } else if (sample->depth==8) { + for (unsigned int i=start; idata8[i]*float(end-i)/float(end-start); + if (val<-128) val=-128; + if (val>127) val=127; + sample->data8[i]=val; + } + } + + updateSampleTex=true; + + e->renderSamples(); + }); + MARK_MODIFIED; + break; + } + case GUI_ACTION_SAMPLE_SILENCE: { + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + DivSample* sample=e->song.sample[curSample]; + e->lockEngine([this,sample]() { + SAMPLE_OP_BEGIN; + + if (sample->depth==16) { + for (unsigned int i=start; idata16[i]=0; + } + } else if (sample->depth==8) { + for (unsigned int i=start; idata8[i]=0; + } + } + + updateSampleTex=true; + + e->renderSamples(); + }); + MARK_MODIFIED; + break; + } + case GUI_ACTION_SAMPLE_DELETE: { + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + DivSample* sample=e->song.sample[curSample]; + e->lockEngine([this,sample]() { + SAMPLE_OP_BEGIN; + + sample->strip(start,end); + updateSampleTex=true; + + e->renderSamples(); + }); + sampleSelStart=-1; + sampleSelEnd=-1; + MARK_MODIFIED; + break; + } + case GUI_ACTION_SAMPLE_TRIM: { + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + DivSample* sample=e->song.sample[curSample]; + e->lockEngine([this,sample]() { + SAMPLE_OP_BEGIN; + + sample->trim(start,end); + updateSampleTex=true; + + e->renderSamples(); + }); + sampleSelStart=-1; + sampleSelEnd=-1; + MARK_MODIFIED; + break; + } + case GUI_ACTION_SAMPLE_REVERSE: { + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + DivSample* sample=e->song.sample[curSample]; + e->lockEngine([this,sample]() { + SAMPLE_OP_BEGIN; + + if (sample->depth==16) { + for (unsigned int i=start; idata16[i]^=sample->data16[ri]; + sample->data16[ri]^=sample->data16[i]; + sample->data16[i]^=sample->data16[ri]; + } + } else if (sample->depth==8) { + for (unsigned int i=start; idata8[i]^=sample->data8[ri]; + sample->data8[ri]^=sample->data8[i]; + sample->data8[i]^=sample->data8[ri]; + } + } + + updateSampleTex=true; + + e->renderSamples(); + }); + MARK_MODIFIED; + break; + } + case GUI_ACTION_SAMPLE_INVERT: { + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + DivSample* sample=e->song.sample[curSample]; + e->lockEngine([this,sample]() { + SAMPLE_OP_BEGIN; + + if (sample->depth==16) { + for (unsigned int i=start; idata16[i]=-sample->data16[i]; + if (sample->data16[i]==-32768) sample->data16[i]=32767; + } + } else if (sample->depth==8) { + for (unsigned int i=start; idata8[i]=-sample->data8[i]; + if (sample->data16[i]==-128) sample->data16[i]=127; + } + } + + updateSampleTex=true; + + e->renderSamples(); + }); + MARK_MODIFIED; + break; + } + case GUI_ACTION_SAMPLE_SIGN: { + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + DivSample* sample=e->song.sample[curSample]; + e->lockEngine([this,sample]() { + SAMPLE_OP_BEGIN; + + if (sample->depth==16) { + for (unsigned int i=start; idata16[i]^=0x8000; + } + } else if (sample->depth==8) { + for (unsigned int i=start; idata8[i]^=0x80; + } + } + + updateSampleTex=true; + + e->renderSamples(); + }); + MARK_MODIFIED; + break; + } + case GUI_ACTION_SAMPLE_FILTER: + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + openSampleFilterOpt=true; + break; + case GUI_ACTION_SAMPLE_PREVIEW: + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + e->previewSample(curSample); + break; + case GUI_ACTION_SAMPLE_STOP_PREVIEW: + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + e->stopSamplePreview(); + break; + case GUI_ACTION_SAMPLE_ZOOM_IN: { + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + double zoomPercent=100.0/sampleZoom; + zoomPercent+=10.0; + if (zoomPercent>10000.0) zoomPercent=10000.0; + if (zoomPercent<1.0) zoomPercent=1.0; + sampleZoom=100.0/zoomPercent; + if (sampleZoom<0.01) sampleZoom=0.01; + sampleZoomAuto=false; + updateSampleTex=true; + break; + } + case GUI_ACTION_SAMPLE_ZOOM_OUT: { + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + double zoomPercent=100.0/sampleZoom; + zoomPercent-=10.0; + if (zoomPercent>10000.0) zoomPercent=10000.0; + if (zoomPercent<1.0) zoomPercent=1.0; + sampleZoom=100.0/zoomPercent; + if (sampleZoom<0.01) sampleZoom=0.01; + sampleZoomAuto=false; + updateSampleTex=true; + break; + } + case GUI_ACTION_SAMPLE_ZOOM_AUTO: + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + if (sampleZoomAuto) { + sampleZoom=1.0; + sampleZoomAuto=false; + updateSampleTex=true; + } else { + sampleZoomAuto=true; + updateSampleTex=true; + } + break; + case GUI_ACTION_ORDERS_UP: if (e->getOrder()>0) { e->setOrder(e->getOrder()-1); diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 3db50d06e..f32e6eba2 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -918,6 +918,16 @@ void FurnaceGUI::keyDown(SDL_Event& ev) { } } break; + case GUI_WINDOW_SAMPLE_EDIT: + try { + int action=actionMapSample.at(mapped); + if (action>0) { + doAction(action); + return; + } + } catch (std::out_of_range& e) { + } + break; case GUI_WINDOW_INS_LIST: try { int action=actionMapInsList.at(mapped); @@ -2850,7 +2860,11 @@ FurnaceGUI::FurnaceGUI(): sampleFilterCutEnd(100.0f), sampleFilterPower(1), sampleClipboard(NULL), - sampleClipboardLen(0) { + sampleClipboardLen(0), + openSampleResizeOpt(false), + openSampleResampleOpt(false), + openSampleAmplifyOpt(false), + openSampleFilterOpt(false) { // value keys valueKeys[SDLK_0]=0; valueKeys[SDLK_1]=1; diff --git a/src/gui/gui.h b/src/gui/gui.h index 58539ab10..aa661b3a4 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -364,6 +364,35 @@ enum FurnaceGUIActions { GUI_ACTION_SAMPLE_LIST_STOP_PREVIEW, GUI_ACTION_SAMPLE_LIST_MAX, + GUI_ACTION_SAMPLE_MIN, + GUI_ACTION_SAMPLE_SELECT, + GUI_ACTION_SAMPLE_DRAW, + GUI_ACTION_SAMPLE_CUT, + GUI_ACTION_SAMPLE_COPY, + GUI_ACTION_SAMPLE_PASTE, + GUI_ACTION_SAMPLE_PASTE_REPLACE, + GUI_ACTION_SAMPLE_PASTE_MIX, + GUI_ACTION_SAMPLE_SELECT_ALL, + GUI_ACTION_SAMPLE_RESIZE, + GUI_ACTION_SAMPLE_RESAMPLE, + GUI_ACTION_SAMPLE_AMPLIFY, + GUI_ACTION_SAMPLE_NORMALIZE, + GUI_ACTION_SAMPLE_FADE_IN, + GUI_ACTION_SAMPLE_FADE_OUT, + GUI_ACTION_SAMPLE_SILENCE, + GUI_ACTION_SAMPLE_DELETE, + GUI_ACTION_SAMPLE_TRIM, + GUI_ACTION_SAMPLE_REVERSE, + GUI_ACTION_SAMPLE_INVERT, + GUI_ACTION_SAMPLE_SIGN, + GUI_ACTION_SAMPLE_FILTER, + GUI_ACTION_SAMPLE_PREVIEW, + GUI_ACTION_SAMPLE_STOP_PREVIEW, + GUI_ACTION_SAMPLE_ZOOM_IN, + GUI_ACTION_SAMPLE_ZOOM_OUT, + GUI_ACTION_SAMPLE_ZOOM_AUTO, + GUI_ACTION_SAMPLE_MAX, + GUI_ACTION_ORDERS_MIN, GUI_ACTION_ORDERS_UP, GUI_ACTION_ORDERS_DOWN, @@ -677,6 +706,7 @@ class FurnaceGUI { std::map actionMapGlobal; std::map actionMapPat; std::map actionMapOrders; + std::map actionMapSample; std::map actionMapInsList; std::map actionMapWaveList; std::map actionMapSampleList; @@ -778,8 +808,9 @@ class FurnaceGUI { unsigned int sampleDragLen; float sampleFilterL, sampleFilterB, sampleFilterH, sampleFilterRes, sampleFilterCutStart, sampleFilterCutEnd; unsigned char sampleFilterPower; - void* sampleClipboard; + short* sampleClipboard; size_t sampleClipboardLen; + bool openSampleResizeOpt, openSampleResampleOpt, openSampleAmplifyOpt, openSampleFilterOpt; // visualizer float keyHit[DIV_MAX_CHANS]; diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index b33c1e802..28e0f3d59 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -25,19 +25,7 @@ #include "misc/cpp/imgui_stdlib.h" #include #include "guiConst.h" - -#define SAMPLE_OP_BEGIN \ - unsigned int start=0; \ - unsigned int end=sample->samples; \ - if (sampleSelStart!=-1 && sampleSelEnd!=-1 && sampleSelStart!=sampleSelEnd) { \ - start=sampleSelStart; \ - end=sampleSelEnd; \ - if (start>end) { \ - start^=end; \ - end^=start; \ - start^=end; \ - } \ - } \ +#include "sampleUtil.h" void FurnaceGUI::drawSampleEdit() { if (nextWindow==GUI_WINDOW_SAMPLE_EDIT) { @@ -164,6 +152,10 @@ void FurnaceGUI::drawSampleEdit() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Resize"); } + if (openSampleResizeOpt) { + openSampleResizeOpt=false; + ImGui::OpenPopup("SResizeOpt"); + } if (ImGui::BeginPopupContextItem("SResizeOpt",ImGuiPopupFlags_MouseButtonLeft)) { if (ImGui::InputInt("Samples",&resizeSize,1,64)) { if (resizeSize<0) resizeSize=0; @@ -194,6 +186,10 @@ void FurnaceGUI::drawSampleEdit() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Resample"); } + if (openSampleResampleOpt) { + openSampleResampleOpt=false; + ImGui::OpenPopup("SResampleOpt"); + } if (ImGui::BeginPopupContextItem("SResampleOpt",ImGuiPopupFlags_MouseButtonLeft)) { ImGui::Text("Rate"); if (ImGui::InputDouble("##SRRate",&resampleTarget,1.0,50.0,"%g")) { @@ -243,6 +239,10 @@ void FurnaceGUI::drawSampleEdit() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Amplify"); } + if (openSampleAmplifyOpt) { + openSampleAmplifyOpt=false; + ImGui::OpenPopup("SAmplifyOpt"); + } if (ImGui::BeginPopupContextItem("SAmplifyOpt",ImGuiPopupFlags_MouseButtonLeft)) { ImGui::Text("Volume"); if (ImGui::InputFloat("##SRVolume",&lifyVol,10.0,50.0,"%g%%")) { @@ -283,165 +283,42 @@ void FurnaceGUI::drawSampleEdit() { } ImGui::SameLine(); if (ImGui::Button(ICON_FA_ARROWS_V "##SNormalize")) { - e->lockEngine([this,sample]() { - SAMPLE_OP_BEGIN; - float maxVal=0.0f; - - if (sample->depth==16) { - for (unsigned int i=start; idata16[i]/32767.0f); - if (val>maxVal) maxVal=val; - } - if (maxVal>1.0f) maxVal=1.0f; - if (maxVal>0.0f) { - float vol=1.0f/maxVal; - for (unsigned int i=start; idata16[i]*vol; - if (val<-32768) val=-32768; - if (val>32767) val=32767; - sample->data16[i]=val; - } - } - } else if (sample->depth==8) { - for (unsigned int i=start; idata8[i]/127.0f); - if (val>maxVal) maxVal=val; - } - if (maxVal>1.0f) maxVal=1.0f; - if (maxVal>0.0f) { - float vol=1.0f/maxVal; - for (unsigned int i=start; idata8[i]*vol; - if (val<-128) val=-128; - if (val>127) val=127; - sample->data8[i]=val; - } - } - } - - updateSampleTex=true; - - e->renderSamples(); - }); - MARK_MODIFIED; + doAction(GUI_ACTION_SAMPLE_NORMALIZE); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Normalize"); } ImGui::SameLine(); if (ImGui::Button(ICON_FA_ARROW_UP "##SFadeIn")) { - e->lockEngine([this,sample]() { - SAMPLE_OP_BEGIN; - - if (sample->depth==16) { - for (unsigned int i=start; idata16[i]*float(i-start)/float(end-start); - if (val<-32768) val=-32768; - if (val>32767) val=32767; - sample->data16[i]=val; - } - } else if (sample->depth==8) { - for (unsigned int i=start; idata8[i]*float(i-start)/float(end-start); - if (val<-128) val=-128; - if (val>127) val=127; - sample->data8[i]=val; - } - } - - updateSampleTex=true; - - e->renderSamples(); - }); - MARK_MODIFIED; + doAction(GUI_ACTION_SAMPLE_FADE_IN); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Fade in"); } ImGui::SameLine(); if (ImGui::Button(ICON_FA_ARROW_DOWN "##SFadeOut")) { - e->lockEngine([this,sample]() { - SAMPLE_OP_BEGIN; - - if (sample->depth==16) { - for (unsigned int i=start; idata16[i]*float(end-i)/float(end-start); - if (val<-32768) val=-32768; - if (val>32767) val=32767; - sample->data16[i]=val; - } - } else if (sample->depth==8) { - for (unsigned int i=start; idata8[i]*float(end-i)/float(end-start); - if (val<-128) val=-128; - if (val>127) val=127; - sample->data8[i]=val; - } - } - - updateSampleTex=true; - - e->renderSamples(); - }); - MARK_MODIFIED; + doAction(GUI_ACTION_SAMPLE_FADE_OUT); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Fade out"); } ImGui::SameLine(); if (ImGui::Button(ICON_FA_ERASER "##SSilence")) { - e->lockEngine([this,sample]() { - SAMPLE_OP_BEGIN; - - if (sample->depth==16) { - for (unsigned int i=start; idata16[i]=0; - } - } else if (sample->depth==8) { - for (unsigned int i=start; idata8[i]=0; - } - } - - updateSampleTex=true; - - e->renderSamples(); - }); - MARK_MODIFIED; + doAction(GUI_ACTION_SAMPLE_SILENCE); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Apply silence"); } ImGui::SameLine(); if (ImGui::Button(ICON_FA_TIMES "##SDelete")) { - e->lockEngine([this,sample]() { - SAMPLE_OP_BEGIN; - - sample->strip(start,end); - updateSampleTex=true; - - e->renderSamples(); - }); - sampleSelStart=-1; - sampleSelEnd=-1; - MARK_MODIFIED; + doAction(GUI_ACTION_SAMPLE_DELETE); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Delete"); } ImGui::SameLine(); if (ImGui::Button(ICON_FA_CROP "##STrim")) { - e->lockEngine([this,sample]() { - SAMPLE_OP_BEGIN; - - sample->trim(start,end); - updateSampleTex=true; - - e->renderSamples(); - }); - sampleSelStart=-1; - sampleSelEnd=-1; - MARK_MODIFIED; + doAction(GUI_ACTION_SAMPLE_TRIM); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Trim"); @@ -450,82 +327,21 @@ void FurnaceGUI::drawSampleEdit() { ImGui::Dummy(ImVec2(4.0*dpiScale,dpiScale)); ImGui::SameLine(); if (ImGui::Button(ICON_FA_BACKWARD "##SReverse")) { - e->lockEngine([this,sample]() { - SAMPLE_OP_BEGIN; - - if (sample->depth==16) { - for (unsigned int i=start; idata16[i]^=sample->data16[ri]; - sample->data16[ri]^=sample->data16[i]; - sample->data16[i]^=sample->data16[ri]; - } - } else if (sample->depth==8) { - for (unsigned int i=start; idata8[i]^=sample->data8[ri]; - sample->data8[ri]^=sample->data8[i]; - sample->data8[i]^=sample->data8[ri]; - } - } - - updateSampleTex=true; - - e->renderSamples(); - }); - MARK_MODIFIED; + doAction(GUI_ACTION_SAMPLE_REVERSE); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Reverse"); } ImGui::SameLine(); if (ImGui::Button(ICON_FA_SORT_AMOUNT_ASC "##SInvert")) { - e->lockEngine([this,sample]() { - SAMPLE_OP_BEGIN; - - if (sample->depth==16) { - for (unsigned int i=start; idata16[i]=-sample->data16[i]; - if (sample->data16[i]==-32768) sample->data16[i]=32767; - } - } else if (sample->depth==8) { - for (unsigned int i=start; idata8[i]=-sample->data8[i]; - if (sample->data16[i]==-128) sample->data16[i]=127; - } - } - - updateSampleTex=true; - - e->renderSamples(); - }); - MARK_MODIFIED; + doAction(GUI_ACTION_SAMPLE_INVERT); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Invert"); } ImGui::SameLine(); if (ImGui::Button(ICON_FA_LEVEL_DOWN "##SSign")) { - e->lockEngine([this,sample]() { - SAMPLE_OP_BEGIN; - - if (sample->depth==16) { - for (unsigned int i=start; idata16[i]^=0x8000; - } - } else if (sample->depth==8) { - for (unsigned int i=start; idata8[i]^=0x80; - } - } - - updateSampleTex=true; - - e->renderSamples(); - }); - MARK_MODIFIED; + doAction(GUI_ACTION_SAMPLE_SIGN); } if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Signed/unsigned exchange"); @@ -535,6 +351,10 @@ void FurnaceGUI::drawSampleEdit() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Apply filter"); } + if (openSampleFilterOpt) { + openSampleFilterOpt=false; + ImGui::OpenPopup("SFilterOpt"); + } if (ImGui::BeginPopupContextItem("SFilterOpt",ImGuiPopupFlags_MouseButtonLeft)) { float lowP=sampleFilterL*100.0f; float bandP=sampleFilterB*100.0f; diff --git a/src/gui/sampleUtil.h b/src/gui/sampleUtil.h new file mode 100644 index 000000000..981a56046 --- /dev/null +++ b/src/gui/sampleUtil.h @@ -0,0 +1,31 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#define SAMPLE_OP_BEGIN \ + unsigned int start=0; \ + unsigned int end=sample->samples; \ + if (sampleSelStart!=-1 && sampleSelEnd!=-1 && sampleSelStart!=sampleSelEnd) { \ + start=sampleSelStart; \ + end=sampleSelEnd; \ + if (start>end) { \ + start^=end; \ + end^=start; \ + start^=end; \ + } \ + } diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index b89ed593f..9a1a9eee2 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -24,6 +24,7 @@ #include "ImGuiFileDialog.h" #include "IconsFontAwesome4.h" #include "misc/cpp/imgui_stdlib.h" +#include #include #include #include @@ -865,6 +866,39 @@ void FurnaceGUI::drawSettings() { KEYBIND_CONFIG_END; ImGui::TreePop(); } + if (ImGui::TreeNode("Sample editor")) { + KEYBIND_CONFIG_BEGIN("keysSampleEdit"); + + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_SELECT,"Edit mode: Select"); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_DRAW,"Edit mode: Draw"); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_CUT,"Cut"); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_COPY,"Copy"); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_PASTE,"Paste"); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_PASTE_REPLACE,"Paste replace"); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_PASTE_MIX,"Paste mix"); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_SELECT_ALL,"Select all"); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_RESIZE,"Resize"); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_RESAMPLE,"Resample"); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_AMPLIFY,"Amplify"); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_NORMALIZE,"Normalize"); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_FADE_IN,"Fade in"); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_FADE_OUT,"Fade out"); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_SILENCE,"Apply silence"); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_DELETE,"Delete"); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_TRIM,"Trim"); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_REVERSE,"Reverse"); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_INVERT,"Invert"); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_SIGN,"Signed/unsigned exchange"); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_FILTER,"Apply filter"); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_PREVIEW,"Preview sample"); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_STOP_PREVIEW,"Stop sample preview"); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_ZOOM_IN,"Zoom in"); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_ZOOM_OUT,"Zoom out"); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_ZOOM_AUTO,"Toggle auto-zoom"); + + KEYBIND_CONFIG_END; + ImGui::TreePop(); + } ImGui::EndTabItem(); } ImGui::EndTabBar(); @@ -1119,6 +1153,33 @@ void FurnaceGUI::syncSettings() { LOAD_KEYBIND(GUI_ACTION_SAMPLE_LIST_PREVIEW,0); LOAD_KEYBIND(GUI_ACTION_SAMPLE_LIST_STOP_PREVIEW,0); + LOAD_KEYBIND(GUI_ACTION_SAMPLE_SELECT,FURKMOD_SHIFT|SDLK_i); + LOAD_KEYBIND(GUI_ACTION_SAMPLE_DRAW,FURKMOD_SHIFT|SDLK_d); + LOAD_KEYBIND(GUI_ACTION_SAMPLE_CUT,FURKMOD_CMD|SDLK_x); + LOAD_KEYBIND(GUI_ACTION_SAMPLE_COPY,FURKMOD_CMD|SDLK_c); + LOAD_KEYBIND(GUI_ACTION_SAMPLE_PASTE,FURKMOD_CMD|SDLK_v); + LOAD_KEYBIND(GUI_ACTION_SAMPLE_PASTE_REPLACE,FURKMOD_CMD|FURKMOD_SHIFT|SDLK_x); + LOAD_KEYBIND(GUI_ACTION_SAMPLE_PASTE_MIX,FURKMOD_CMD|FURKMOD_ALT|SDLK_x); + LOAD_KEYBIND(GUI_ACTION_SAMPLE_SELECT_ALL,FURKMOD_CMD|SDLK_a); + LOAD_KEYBIND(GUI_ACTION_SAMPLE_RESIZE,FURKMOD_CMD|SDLK_r); + LOAD_KEYBIND(GUI_ACTION_SAMPLE_RESAMPLE,FURKMOD_CMD|SDLK_e); + LOAD_KEYBIND(GUI_ACTION_SAMPLE_AMPLIFY,FURKMOD_CMD|SDLK_b); + LOAD_KEYBIND(GUI_ACTION_SAMPLE_NORMALIZE,FURKMOD_CMD|SDLK_n); + LOAD_KEYBIND(GUI_ACTION_SAMPLE_FADE_IN,FURKMOD_CMD|SDLK_i); + LOAD_KEYBIND(GUI_ACTION_SAMPLE_FADE_OUT,FURKMOD_CMD|SDLK_o); + LOAD_KEYBIND(GUI_ACTION_SAMPLE_SILENCE,FURKMOD_SHIFT|SDLK_DELETE); + LOAD_KEYBIND(GUI_ACTION_SAMPLE_DELETE,SDLK_DELETE); + LOAD_KEYBIND(GUI_ACTION_SAMPLE_TRIM,FURKMOD_CMD|SDLK_DELETE); + LOAD_KEYBIND(GUI_ACTION_SAMPLE_REVERSE,FURKMOD_CMD|SDLK_t); + LOAD_KEYBIND(GUI_ACTION_SAMPLE_INVERT,FURKMOD_CMD|FURKMOD_SHIFT|SDLK_t); + LOAD_KEYBIND(GUI_ACTION_SAMPLE_SIGN,FURKMOD_CMD|SDLK_u); + LOAD_KEYBIND(GUI_ACTION_SAMPLE_FILTER,FURKMOD_CMD|SDLK_f); + LOAD_KEYBIND(GUI_ACTION_SAMPLE_PREVIEW,0); + LOAD_KEYBIND(GUI_ACTION_SAMPLE_STOP_PREVIEW,0); + LOAD_KEYBIND(GUI_ACTION_SAMPLE_ZOOM_IN,FURKMOD_CMD|SDLK_EQUALS); + LOAD_KEYBIND(GUI_ACTION_SAMPLE_ZOOM_OUT,FURKMOD_CMD|SDLK_MINUS); + LOAD_KEYBIND(GUI_ACTION_SAMPLE_ZOOM_AUTO,FURKMOD_CMD|SDLK_0); + LOAD_KEYBIND(GUI_ACTION_ORDERS_UP,SDLK_UP); LOAD_KEYBIND(GUI_ACTION_ORDERS_DOWN,SDLK_DOWN); LOAD_KEYBIND(GUI_ACTION_ORDERS_LEFT,SDLK_LEFT); @@ -1404,6 +1465,33 @@ void FurnaceGUI::commitSettings() { SAVE_KEYBIND(GUI_ACTION_SAMPLE_LIST_PREVIEW); SAVE_KEYBIND(GUI_ACTION_SAMPLE_LIST_STOP_PREVIEW); + SAVE_KEYBIND(GUI_ACTION_SAMPLE_SELECT); + SAVE_KEYBIND(GUI_ACTION_SAMPLE_DRAW); + SAVE_KEYBIND(GUI_ACTION_SAMPLE_CUT); + SAVE_KEYBIND(GUI_ACTION_SAMPLE_COPY); + SAVE_KEYBIND(GUI_ACTION_SAMPLE_PASTE); + SAVE_KEYBIND(GUI_ACTION_SAMPLE_PASTE_REPLACE); + SAVE_KEYBIND(GUI_ACTION_SAMPLE_PASTE_MIX); + SAVE_KEYBIND(GUI_ACTION_SAMPLE_SELECT_ALL); + SAVE_KEYBIND(GUI_ACTION_SAMPLE_RESIZE); + SAVE_KEYBIND(GUI_ACTION_SAMPLE_RESAMPLE); + SAVE_KEYBIND(GUI_ACTION_SAMPLE_AMPLIFY); + SAVE_KEYBIND(GUI_ACTION_SAMPLE_NORMALIZE); + SAVE_KEYBIND(GUI_ACTION_SAMPLE_FADE_IN); + SAVE_KEYBIND(GUI_ACTION_SAMPLE_FADE_OUT); + SAVE_KEYBIND(GUI_ACTION_SAMPLE_SILENCE); + SAVE_KEYBIND(GUI_ACTION_SAMPLE_DELETE); + SAVE_KEYBIND(GUI_ACTION_SAMPLE_TRIM); + SAVE_KEYBIND(GUI_ACTION_SAMPLE_REVERSE); + SAVE_KEYBIND(GUI_ACTION_SAMPLE_INVERT); + SAVE_KEYBIND(GUI_ACTION_SAMPLE_SIGN); + SAVE_KEYBIND(GUI_ACTION_SAMPLE_FILTER); + SAVE_KEYBIND(GUI_ACTION_SAMPLE_PREVIEW); + SAVE_KEYBIND(GUI_ACTION_SAMPLE_STOP_PREVIEW); + SAVE_KEYBIND(GUI_ACTION_SAMPLE_ZOOM_IN); + SAVE_KEYBIND(GUI_ACTION_SAMPLE_ZOOM_OUT); + SAVE_KEYBIND(GUI_ACTION_SAMPLE_ZOOM_AUTO); + SAVE_KEYBIND(GUI_ACTION_ORDERS_UP); SAVE_KEYBIND(GUI_ACTION_ORDERS_DOWN); SAVE_KEYBIND(GUI_ACTION_ORDERS_LEFT); @@ -1454,6 +1542,7 @@ void FurnaceGUI::parseKeybinds() { actionMapInsList.clear(); actionMapWaveList.clear(); actionMapSampleList.clear(); + actionMapSample.clear(); actionMapOrders.clear(); for (int i=GUI_ACTION_GLOBAL_MIN+1; i Date: Wed, 23 Mar 2022 01:48:45 +0900 Subject: [PATCH 376/637] Add Namco 163 Support --- CMakeLists.txt | 3 + papers/doc/4-instrument/README.md | 1 + papers/doc/4-instrument/n163.md | 14 + papers/doc/5-wave/README.md | 2 +- papers/doc/7-systems/README.md | 1 + papers/doc/7-systems/n163.md | 25 + src/engine/dispatch.h | 13 + src/engine/dispatchContainer.cpp | 4 + src/engine/platform/n163.cpp | 642 ++++++++++++++++++++++++ src/engine/platform/n163.h | 107 ++++ src/engine/platform/sound/n163/n163.cpp | 137 +++++ src/engine/platform/sound/n163/n163.hpp | 78 +++ src/engine/playback.cpp | 57 +++ src/gui/debug.cpp | 31 ++ src/gui/gui.cpp | 90 +++- src/gui/insEdit.cpp | 26 +- 16 files changed, 1225 insertions(+), 6 deletions(-) create mode 100644 papers/doc/4-instrument/n163.md create mode 100644 papers/doc/7-systems/n163.md create mode 100644 src/engine/platform/n163.cpp create mode 100644 src/engine/platform/n163.h create mode 100644 src/engine/platform/sound/n163/n163.cpp create mode 100644 src/engine/platform/sound/n163/n163.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 6116dba8c..62a1452f5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -273,6 +273,8 @@ src/engine/platform/sound/swan.cpp src/engine/platform/sound/k005289/k005289.cpp +src/engine/platform/sound/n163/n163.cpp + src/engine/platform/ym2610Interface.cpp src/engine/blip_buf.c @@ -320,6 +322,7 @@ src/engine/platform/lynx.cpp src/engine/platform/swan.cpp src/engine/platform/vera.cpp src/engine/platform/bubsyswsg.cpp +src/engine/platform/n163.cpp src/engine/platform/dummy.cpp ) diff --git a/papers/doc/4-instrument/README.md b/papers/doc/4-instrument/README.md index ab03e8364..2cf4d25ae 100644 --- a/papers/doc/4-instrument/README.md +++ b/papers/doc/4-instrument/README.md @@ -27,6 +27,7 @@ depending on the instrument type, there are currently 13 different types of an i - [VERA](vera.md) - for use with Commander X16 VERA. - [Seta/Allumer X1-010](x1_010.md) - for use with Wavetable portion in Seta/Allumer X1-010. - [Konami SCC/Bubble System WSG](scc.md) - for use with Konami SCC and Wavetable portion in Bubble System's sound hardware. +- [Namco 163](n163.md) - for use with Namco 163. # macros diff --git a/papers/doc/4-instrument/n163.md b/papers/doc/4-instrument/n163.md new file mode 100644 index 000000000..ccf2c46e5 --- /dev/null +++ b/papers/doc/4-instrument/n163.md @@ -0,0 +1,14 @@ +# Namco 163 instrument editor + +Namco 163 instrument editor consists of 10 macros. + +- [Volume] - volume levels sequence +- [Arpeggio]- pitch sequence +- [Waveform pos.] - sets the waveform source address in RAM for playback (single nibble unit) +- [Waveform] - sets waveform source for playback immediately or update later +- [Waveform len.] - sets the waveform source length for playback (4 nibble unit) +- [Waveform update] - sets the waveform update trigger behavior for playback +- [Waveform to load] - sets waveform source for load to RAM immediately or later +- [Wave pos. to load] - sets address of waveform for load to RAM (single nibble unit) +- [Wave len. to load] - sets length of waveform for load to RAM (4 nibble unit) +- [Waveform load] - sets the waveform load trigger behavior diff --git a/papers/doc/5-wave/README.md b/papers/doc/5-wave/README.md index f6dd9b94b..0668c0bf2 100644 --- a/papers/doc/5-wave/README.md +++ b/papers/doc/5-wave/README.md @@ -2,4 +2,4 @@ Wavetable synthizers, in context of Furnace, are sound sources that operate on extremely short n-bit PCM streams. By extremely short, no more than 256 bytes. This amount of space is nowhere near enough to store an actual sampled sound, it allows certain amount of freedom to define a waveform shape. As of Furnace 0.5.8, wavetable editor affects PC Engine, WonderSwan and channel 3 of Game Boy. -Furnace's wavetable editor is rather simple, you can draw the waveform using mouse or by pasting an MML bit stream in the input field. Maximum wave width (length) is 256 bytes, and maximum wave height (depth) is 256. NOTE: Game Boy, PCE, WonderSwan and Bubble System can handle max 32 byte waveforms, X1-010 can handle max 128 byte waveforms as of now, with 16-level height for GB, X1-010 Envelope, WS and Bubble System, and 32-level height for PCE. If larger wave will be defined for these systems, it will be squashed to fit within the constraints of the system. +Furnace's wavetable editor is rather simple, you can draw the waveform using mouse or by pasting an MML bit stream in the input field. Maximum wave width (length) is 256 bytes, and maximum wave height (depth) is 256. NOTE: Game Boy, PCE, WonderSwan and Bubble System can handle max 32 byte waveforms, X1-010 can handle max 128 byte waveforms as of now, with 16-level height for GB, X1-010 Envelope, WS, Bubble System and N163, and 32-level height for PCE. If larger wave will be defined for these systems, it will be squashed to fit within the constraints of the system. diff --git a/papers/doc/7-systems/README.md b/papers/doc/7-systems/README.md index 5413b0b50..27e87924b 100644 --- a/papers/doc/7-systems/README.md +++ b/papers/doc/7-systems/README.md @@ -21,5 +21,6 @@ this is a list of systems that Furnace supports, including each system's effects - [Seta/Allumer X1-010](x1_010.md) - [WonderSwan](wonderswan.md) - [Bubble System WSG](bubblesystem.md) +- [Namco 163](n163.md) Furnace also reads .dmf files with the [Yamaha YMU759](ymu759.md) system, but does not emulate the chip at all. diff --git a/papers/doc/7-systems/n163.md b/papers/doc/7-systems/n163.md new file mode 100644 index 000000000..80a2d28de --- /dev/null +++ b/papers/doc/7-systems/n163.md @@ -0,0 +1,25 @@ +# Namco 163 + +This is Namco's one of NES mapper, with up to 8 wavetable channels. It has also 128 byte of internal RAM, both channel register and wavetables are stored here. Wavetables are variable size and freely allocable anywhere in RAM, it means it can be uses part of or merged 2 or more pre-loaded waveforms in RAM. But waveform RAM area becomes smaller as much as activating more channels; Channel register consumes 8 byte for each channels. You must avoid conflict with channel register area and waveform for avoid channel playback broken. + +It has can be outputs only single channel at clock; so it's sound quality is more crunchy as much as activating more channels. + +Furnace supports both load waveform into RAM and waveform playback simultaneously, and channel limit is dynamically changeable with effect commands. +You must load waveform to RAM first for playback or do something, its load behavior is changeable to auto-update when every waveform changes or manual update. +Both waveform playback and load command is works independently per each channel columns, (Global) commands are don't care about the channel columns for work commands and its load behavior is independent with per-channel column load commands. + +# effects + +- `10xx`: set waveform for playback. +- `11xx`: set waveform position in RAM for playback. (single nibble unit) +- `12xx`: set waveform length in RAM for playback. (04 to FC, 4 nibble unit) +- `130x`: set playback waveform update behavior. (0: off, bit 0: update now, bit 1: update when every waveform is changed) +- `14xx`: set waveform for load to RAM. +- `15xx`: set waveform position for load to RAM. (single nibble unit) +- `16xx`: set waveform length for load to RAM. (04 to FC, 4 nibble unit) +- `170x`: set waveform load behavior. (0: off, bit 0: load now, bit 1: load when every waveform is changed) +- `180x`: set channel limit (0 to 7, x + 1) +- `20xx`: (Global) set waveform for load to RAM. +- `21xx`: (Global) set waveform position for load to RAM. (single nibble unit) +- `22xx`: (Global) set waveform length for load to RAM. (04 to FC, 4 nibble unit) +- `230x`: (Global) set waveform load behavior. (0: off, bit 0: load now, bit 1: load when every waveform is changed) diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index 0ec3a295b..272045b37 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -120,6 +120,19 @@ enum DivDispatchCmds { DIV_CMD_WS_SWEEP_TIME, DIV_CMD_WS_SWEEP_AMOUNT, + DIV_CMD_N163_WAVE_POSITION, + DIV_CMD_N163_WAVE_LENGTH, + DIV_CMD_N163_WAVE_MODE, + DIV_CMD_N163_WAVE_LOAD, + DIV_CMD_N163_WAVE_LOADPOS, + DIV_CMD_N163_WAVE_LOADLEN, + DIV_CMD_N163_WAVE_LOADMODE, + DIV_CMD_N163_CHANNEL_LIMIT, + DIV_CMD_N163_GLOBAL_WAVE_LOAD, + DIV_CMD_N163_GLOBAL_WAVE_LOADPOS, + DIV_CMD_N163_GLOBAL_WAVE_LOADLEN, + DIV_CMD_N163_GLOBAL_WAVE_LOADMODE, + DIV_ALWAYS_SET_VOLUME, DIV_CMD_MAX diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index f80ed13bd..6978aa284 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -46,6 +46,7 @@ #include "platform/swan.h" #include "platform/lynx.h" #include "platform/bubsyswsg.h" +#include "platform/n163.h" #include "platform/dummy.h" #include "../ta-log.h" #include "song.h" @@ -275,6 +276,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_BUBSYS_WSG: dispatch=new DivPlatformBubSysWSG; break; + case DIV_SYSTEM_N163: + dispatch=new DivPlatformN163; + break; default: logW("this system is not supported yet! using dummy platform.\n"); dispatch=new DivPlatformDummy; diff --git a/src/engine/platform/n163.cpp b/src/engine/platform/n163.cpp new file mode 100644 index 000000000..9d22bd27a --- /dev/null +++ b/src/engine/platform/n163.cpp @@ -0,0 +1,642 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "n163.h" +#include "../engine.h" +#include + +#define rRead(a,v) n163->addr_w(a); n163->data_r(v); +#define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } +#define rWriteMask(a,v,m) if (!skipRegisterWrites) {writes.emplace(a,v,m); if (dumpWrites) {addWrite(a,v);} } +#define chWrite(c,a,v) \ + if (c<=chanMax) { \ + rWrite(0x78-(c<<3)+(a&7),v) \ + } + +#define chWriteMask(c,a,v,m) \ + if (c<=chanMax) { \ + rWriteMask(0x78-(c<<3)+(a&7),v,m) \ + } + +#define CHIP_FREQBASE (15*65536) + +const char* regCheatSheetN163[]={ + "FreqL7", "40", + "AccL7", "41", + "FreqM7", "42", + "AccM7", "43", + "WavLen_FreqH7", "44", + "AccH7", "45", + "WavPos7", "46", + "Vol7", "47", + "FreqL6", "48", + "AccL6", "49", + "FreqM6", "4A", + "AccM6", "4B", + "WavLen_FreqH6", "4C", + "AccH6", "4D", + "WavPos6", "4E", + "Vol6", "4F", + "FreqL5", "50", + "AccL5", "51", + "FreqM5", "52", + "AccM5", "53", + "WavLen_FreqH5", "54", + "AccH5", "55", + "WavPos5", "56", + "Vol5", "57", + "FreqL4", "58", + "AccL4", "59", + "FreqM4", "5A", + "AccM4", "5B", + "WavLen_FreqH4", "5C", + "AccH4", "5D", + "WavPos4", "5E", + "Vol4", "5F", + "FreqL3", "60", + "AccL3", "61", + "FreqM3", "62", + "AccM3", "63", + "WavLen_FreqH3", "64", + "AccH3", "65", + "WavPos3", "66", + "Vol3", "67", + "FreqL2", "68", + "AccL2", "69", + "FreqM2", "6A", + "AccM2", "6B", + "WavLen_FreqH2", "6C", + "AccH2", "6D", + "WavPos2", "6E", + "Vol2", "6F", + "FreqL1", "70", + "AccL1", "71", + "FreqM1", "72", + "AccM1", "73", + "WavLen_FreqH1", "74", + "AccH1", "75", + "WavPos1", "76", + "Vol1", "77", + "FreqL0", "78", + "AccL0", "79", + "FreqM0", "7A", + "AccM0", "7B", + "WavLen_FreqH0", "7C", + "AccH0", "7D", + "WavPos0", "7E", + "ChanMax_Vol0", "7F", + NULL +}; + +const char** DivPlatformN163::getRegisterSheet() { + return regCheatSheetN163; +} + +const char* DivPlatformN163::getEffectName(unsigned char effect) { + switch (effect) { + case 0x10: + return "10xx: Select waveform"; + break; + case 0x11: + return "11xx: Set waveform position in RAM (single nibble unit)"; + break; + case 0x12: + return "12xx: Set waveform length in RAM (04 to FC, 4 nibble unit)"; + break; + case 0x13: + return "130x: Change waveform update mode (0: off, bit 0: update now, bit 1: update when every waveform changes)"; + break; + case 0x14: + return "14xx: Select waveform for load to RAM"; + break; + case 0x15: + return "15xx: Set waveform position for load to RAM (single nibble unit)"; + break; + case 0x16: + return "16xx: Set waveform length for load to RAM (04 to FC, 4 nibble unit)"; + break; + case 0x17: + return "170x: Change waveform load mode (0: off, bit 0: load now, bit 1: load when every waveform changes)"; + break; + case 0x18: + return "180x: Change channel limits (0 to 7, x + 1)"; + break; + case 0x20: + return "20xx: (Global) Select waveform for load to RAM"; + break; + case 0x21: + return "21xx: (Global) Set waveform position for load to RAM (single nibble unit)"; + break; + case 0x22: + return "22xx: (Global) Set waveform length for load to RAM (04 to FC, 4 nibble unit)"; + break; + case 0x23: + return "230x: (Global) Change waveform load mode (0: off, bit 0: load now, bit 1: load when every waveform changes)"; + break; + } + return NULL; +} + +void DivPlatformN163::acquire(short* bufL, short* bufR, size_t start, size_t len) { + for (size_t i=start; itick(); + int out=(n163->out()<<6)*(chanMax+1); // scale to 16 bit + if (out>32767) out=32767; + if (out<-32768) out=-32768; + bufL[i]=bufR[i]=out; + + // command queue + while (!writes.empty()) { + QueuedWrite w=writes.front(); + n163->addr_w(w.addr); + n163->data_w((n163->data_r()&~w.mask)|(w.val&w.mask)); + writes.pop(); + } + } +} + +void DivPlatformN163::updateWave(int wave, int pos, int len) { + len=MAX(0,MIN(((0x78-(chanMax<<3))<<1)-pos,len)); // avoid conflict with channel register area + DivWavetable* wt=parent->getWave(wave); + for (int i=0; imax<1 || wt->len<1) { + rWriteMask(addr>>1,0,mask); + } else { + int data=wt->data[i*wt->len/len]*15/wt->max; + if (data<0) data=0; + if (data>15) data=15; + rWriteMask(addr>>1,(addr&1)?(data<<4):(data&0xf),mask); + } + } +} + +void DivPlatformN163::updateWaveCh(int ch) { + if (ch<=chanMax) { + updateWave(chan[ch].wave,chan[ch].wavePos,chan[ch].waveLen); + if (chan[ch].active) { + chan[ch].volumeChanged=true; + } + } +} + +void DivPlatformN163::tick() { + for (int i=0; i<=chanMax; i++) { + chan[i].std.next(); + if (chan[i].std.hadVol) { + chan[i].outVol=(MIN(15,chan[i].std.vol)*(chan[i].vol&15))/15; + if (chan[i].outVol<0) chan[i].outVol=0; + if (chan[i].outVol>15) chan[i].outVol=15; + if (chan[i].resVol!=chan[i].outVol) { + chan[i].resVol=chan[i].outVol; + if (!isMuted[i]) { + chan[i].volumeChanged=true; + } + } + } + if (chan[i].std.hadArp) { + if (!chan[i].inPorta) { + if (chan[i].std.arpMode) { + chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp); + } else { + chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+(signed char)chan[i].std.arp); + } + } + chan[i].freqChanged=true; + } else { + if (chan[i].std.arpMode && chan[i].std.finishedArp) { + chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note); + chan[i].freqChanged=true; + } + } + if (chan[i].std.hadDuty) { + if (chan[i].wavePos!=chan[i].std.duty) { + chan[i].wavePos=chan[i].std.duty; + if (chan[i].waveMode&0x2) { + chan[i].waveUpdated=true; + } + chan[i].waveChanged=true; + } + } + if (chan[i].std.hadWave) { + if (chan[i].wave!=chan[i].std.wave) { + chan[i].wave=chan[i].std.wave; + if (chan[i].waveMode&0x2) { + chan[i].waveUpdated=true; + } + } + } + if (chan[i].std.hadEx1) { + if (chan[i].waveLen!=(chan[i].std.ex1&0xfc)) { + chan[i].waveLen=chan[i].std.ex1&0xfc; + if (chan[i].waveMode&0x2) { + chan[i].waveUpdated=true; + } + chan[i].freqChanged=true; + } + } + if (chan[i].std.hadEx2) { + if ((chan[i].waveMode&0x1)!=(chan[i].std.ex2&0x1)) { // update waveform now + chan[i].waveMode=(chan[i].waveMode&~0x1)|(chan[i].std.ex2&0x1); + if (chan[i].waveMode&0x1) { // rising edge + chan[i].waveUpdated=true; + chan[i].waveChanged=true; + } + } + if ((chan[i].waveMode&0x2)!=(chan[i].std.ex2&0x2)) { // update when every waveform changed + chan[i].waveMode=(chan[i].waveMode&~0x2)|(chan[i].std.ex2&0x2); + if (chan[i].waveMode&0x2) { + chan[i].waveUpdated=true; + chan[i].waveChanged=true; + } + } + } + if (chan[i].std.hadEx3) { + if (chan[i].loadWave!=chan[i].std.ex3) { + chan[i].loadWave=chan[i].std.ex3; + if (chan[i].loadMode&0x2) { + updateWave(chan[i].loadWave,chan[i].loadPos,chan[i].loadLen&0xfc); + } + } + } + if (chan[i].std.hadAlg) { + if (chan[i].loadPos!=chan[i].std.alg) { + chan[i].loadPos=chan[i].std.alg; + } + } + if (chan[i].std.hadFb) { + if (chan[i].loadLen!=(chan[i].std.fb&0xfc)) { + chan[i].loadLen=chan[i].std.fb&0xfc; + } + } + if (chan[i].std.hadFms) { + if ((chan[i].loadMode&0x1)!=(chan[i].std.fms&0x1)) { // load now + chan[i].loadMode=(chan[i].loadMode&~0x1)|(chan[i].std.fms&0x1); + if (chan[i].loadMode&0x1) { // rising edge + updateWave(chan[i].loadWave,chan[i].loadPos,chan[i].loadLen&0xfc); + } + } + if ((chan[i].loadMode&0x2)!=(chan[i].std.fms&0x2)) { // load when every waveform changes + chan[i].loadMode=(chan[i].loadMode&~0x2)|(chan[i].std.fms&0x2); + } + } + if (chan[i].volumeChanged) { + if ((!chan[i].active) || isMuted[i]) { + chWriteMask(i,0x7,0,0xf); + } else { + chWriteMask(i,0x7,chan[i].resVol&0xf,0xf); + } + chan[i].volumeChanged=false; + } + if (chan[i].waveChanged) { + chWrite(i,0x6,chan[i].wavePos); + chan[i].freqChanged=true; + chan[i].waveChanged=false; + } + if (chan[i].waveUpdated) { + updateWaveCh(i); + if (!chan[i].keyOff) chan[i].keyOn=true; + chan[i].waveUpdated=false; + } + if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { + chan[i].freq=parent->calcFreq(chan[i].baseFreq*(chan[i].waveLen/16)*(chanMax+1),chan[i].pitch,false,0); + if (chan[i].freq<0) chan[i].freq=0; + if (chan[i].freq>0x3ffff) chan[i].freq=0x3ffff; + if (chan[i].keyOn) { + if (chan[i].wave<0) { + chan[i].wave=0; + if (chan[i].waveMode&0x2) { + updateWaveCh(i); + } + } + } + if (chan[i].keyOff && !isMuted[i]) { + chWriteMask(i,0x07,0,0xf); + } + chWrite(i,0x0,chan[i].freq&0xff); + chWrite(i,0x2,chan[i].freq>>8); + chWrite(i,0x4,((256-chan[i].waveLen)&0xfc)|((chan[i].freq>>16)&3)); + if (chan[i].keyOn) chan[i].keyOn=false; + if (chan[i].keyOff) chan[i].keyOff=false; + chan[i].freqChanged=false; + } + } +} + +int DivPlatformN163::dispatch(DivCommand c) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: { + DivInstrument* ins=parent->getIns(chan[c.chan].ins); + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + } + chan[c.chan].active=true; + chan[c.chan].keyOn=true; + chan[c.chan].resVol=chan[c.chan].vol; + if (!isMuted[c.chan]) { + chan[c.chan].volumeChanged=true; + } + chan[c.chan].std.init(ins); + break; + } + case DIV_CMD_NOTE_OFF: + chan[c.chan].active=false; + chan[c.chan].keyOff=true; + chan[c.chan].keyOn=false; + //chan[c.chan].std.init(NULL); + break; + case DIV_CMD_NOTE_OFF_ENV: + chan[c.chan].active=false; + chan[c.chan].keyOff=true; + chan[c.chan].keyOn=false; + chan[c.chan].std.release(); + break; + case DIV_CMD_ENV_RELEASE: + chan[c.chan].std.release(); + break; + case DIV_CMD_INSTRUMENT: + if (chan[c.chan].ins!=c.value || c.value2==1) { + chan[c.chan].insChanged=true; + chan[c.chan].ins=c.value; + } + break; + case DIV_CMD_VOLUME: + if (chan[c.chan].vol!=c.value) { + chan[c.chan].vol=c.value; + if (!chan[c.chan].std.hasVol) { + chan[c.chan].outVol=c.value; + chan[c.chan].resVol=chan[c.chan].outVol; + if (!isMuted[c.chan]) { + chan[c.chan].volumeChanged=true; + } + } + } + break; + case DIV_CMD_GET_VOLUME: + return chan[c.chan].vol; + break; + case DIV_CMD_PITCH: + chan[c.chan].pitch=c.value; + chan[c.chan].freqChanged=true; + break; + case DIV_CMD_NOTE_PORTA: { + int destFreq=NOTE_FREQUENCY(c.value2); + bool return2=false; + if (destFreq>chan[c.chan].baseFreq) { + chan[c.chan].baseFreq+=c.value; + if (chan[c.chan].baseFreq>=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } else { + chan[c.chan].baseFreq-=c.value; + if (chan[c.chan].baseFreq<=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } + chan[c.chan].freqChanged=true; + if (return2) { + chan[c.chan].inPorta=false; + return 2; + } + break; + } + case DIV_CMD_WAVE: + chan[c.chan].wave=c.value; + if (chan[c.chan].waveMode&0x2) { + chan[c.chan].waveUpdated=true; + } + chan[c.chan].keyOn=true; + break; + case DIV_CMD_N163_WAVE_POSITION: + chan[c.chan].wavePos=c.value; + if (chan[c.chan].waveMode&0x2) { + chan[c.chan].waveUpdated=true; + } + chan[c.chan].waveChanged=true; + break; + case DIV_CMD_N163_WAVE_LENGTH: + chan[c.chan].waveLen=c.value&0xfc; + if (chan[c.chan].waveMode&0x2) { + chan[c.chan].waveUpdated=true; + } + chan[c.chan].freqChanged=true; + break; + case DIV_CMD_N163_WAVE_MODE: + chan[c.chan].waveMode=c.value&0x3; + if (chan[c.chan].waveMode&0x3) { // update now + chan[c.chan].waveUpdated=true; + chan[c.chan].waveChanged=true; + } + break; + case DIV_CMD_N163_WAVE_LOAD: + chan[c.chan].loadWave=c.value; + if (chan[c.chan].loadMode&0x2) { // load when every waveform changes + updateWave(chan[c.chan].loadWave,chan[c.chan].loadPos,chan[c.chan].loadLen); + } + break; + case DIV_CMD_N163_WAVE_LOADPOS: + chan[c.chan].loadPos=c.value; + break; + case DIV_CMD_N163_WAVE_LOADLEN: + chan[c.chan].loadLen=c.value&0xfc; + break; + case DIV_CMD_N163_WAVE_LOADMODE: + chan[c.chan].loadMode=c.value&0x3; + if (chan[c.chan].loadMode&0x1) { // load now + updateWave(chan[c.chan].loadWave,chan[c.chan].loadPos,chan[c.chan].loadLen); + } + break; + case DIV_CMD_N163_GLOBAL_WAVE_LOAD: + loadWave=c.value; + if (loadMode&0x2) { // load when every waveform changes + updateWave(loadWave,loadPos,loadLen); + } + break; + case DIV_CMD_N163_GLOBAL_WAVE_LOADPOS: + loadPos=c.value; + break; + case DIV_CMD_N163_GLOBAL_WAVE_LOADLEN: + loadLen=c.value&0xfc; + break; + case DIV_CMD_N163_GLOBAL_WAVE_LOADMODE: + loadMode=c.value&0x3; + if (loadMode&0x3) { // load now + updateWave(loadWave,loadPos,loadLen); + } + break; + case DIV_CMD_N163_CHANNEL_LIMIT: + if (chanMax!=(c.value&0x7)) { + chanMax=c.value&0x7; + rWriteMask(0x7f,chanMax<<4,0x70); + forceIns(); + } + break; + case DIV_CMD_LEGATO: + chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value+((chan[c.chan].std.willArp && !chan[c.chan].std.arpMode)?(chan[c.chan].std.arp):(0))); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + break; + case DIV_CMD_PRE_PORTA: + if (chan[c.chan].active && c.value2) { + if (parent->song.resetMacroOnPorta) { + chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + chan[c.chan].keyOn=true; + } + } + chan[c.chan].inPorta=c.value; + break; + case DIV_CMD_GET_VOLMAX: + return 15; + break; + case DIV_ALWAYS_SET_VOLUME: + return 1; + break; + default: + break; + } + return 1; +} + +void DivPlatformN163::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; + chan[ch].volumeChanged=true; +} + +void DivPlatformN163::forceIns() { + for (int i=0; i<=chanMax; i++) { + chan[i].insChanged=true; + if (chan[i].active) { + chan[i].keyOn=true; + chan[i].freqChanged=true; + chan[i].volumeChanged=true; + chan[i].waveChanged=true; + if (chan[i].waveMode&0x2) { + chan[i].waveUpdated=true; + } + } + } +} + +void DivPlatformN163::notifyWaveChange(int wave) { + for (int i=0; i<8; i++) { + if (chan[i].wave==wave) { + if (chan[i].waveMode&0x2) { + chan[i].waveUpdated=true; + } + } + } +} + +void DivPlatformN163::notifyInsChange(int ins) { + for (int i=0; i<8; i++) { + if (chan[i].ins==ins) { + chan[i].insChanged=true; + } + } +} + +void DivPlatformN163::notifyInsDeletion(void* ins) { + for (int i=0; i<8; i++) { + chan[i].std.notifyInsDeletion((DivInstrument*)ins); + } +} + +void* DivPlatformN163::getChanState(int ch) { + return &chan[ch]; +} + +unsigned char* DivPlatformN163::getRegisterPool() { + for (int i=0; i<128; i++) { + regPool[i]=n163->reg(i); + } + return regPool; +} + +int DivPlatformN163::getRegisterPoolSize() { + return 128; +} + +void DivPlatformN163::reset() { + while (!writes.empty()) writes.pop(); + for (int i=0; i<8; i++) { + chan[i]=DivPlatformN163::Channel(); + } + + n163->reset(); + memset(regPool,0,128); + + n163->set_disable(false); + rWrite(0x7f,chanMax<<4); + loadWave=-1; + loadPos=0; + loadLen=0; + loadMode=0; +} + +void DivPlatformN163::poke(unsigned int addr, unsigned short val) { + rWrite(addr,val); +} + +void DivPlatformN163::poke(std::vector& wlist) { + for (DivRegWrite& i: wlist) rWrite(i.addr,i.val); +} + +void DivPlatformN163::setFlags(unsigned int flags) { + switch (flags&0xf) { + case 0x0: // NTSC + rate=COLOR_NTSC/2.0; + break; + case 0x1: // PAL + rate=COLOR_PAL*3.0/8.0; + break; + case 0x2: // Dendy + rate=COLOR_PAL*2.0/5.0; + break; + } + chanMax=(flags>>4)&7; + chipClock=rate; + rate/=15; +} + +int DivPlatformN163::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { + parent=p; + dumpWrites=false; + skipRegisterWrites=false; + for (int i=0; i<8; i++) { + isMuted[i]=false; + } + n163=new n163_core(); + setFlags(flags); + + reset(); + + return 8; +} + +void DivPlatformN163::quit() { + delete n163; +} + +DivPlatformN163::~DivPlatformN163() { +} diff --git a/src/engine/platform/n163.h b/src/engine/platform/n163.h new file mode 100644 index 000000000..1aa520482 --- /dev/null +++ b/src/engine/platform/n163.h @@ -0,0 +1,107 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _N163_H +#define _N163_H + +#include "../dispatch.h" +#include +#include "../macroInt.h" +#include "sound/n163/n163.hpp" + +class DivPlatformN163: public DivDispatch { + struct Channel { + int freq, baseFreq, pitch, note; + short ins, wave, wavePos, waveLen; + unsigned char waveMode; + short loadWave, loadPos, loadLen; + unsigned char loadMode; + bool active, insChanged, freqChanged, volumeChanged, waveChanged, waveUpdated, keyOn, keyOff, inPorta; + signed char vol, outVol, resVol; + DivMacroInt std; + Channel(): + freq(0), + baseFreq(0), + pitch(0), + note(0), + ins(-1), + wave(-1), + wavePos(0), + waveLen(0), + waveMode(0), + loadWave(-1), + loadPos(0), + loadLen(0), + loadMode(0), + active(false), + insChanged(true), + freqChanged(false), + volumeChanged(false), + waveChanged(false), + waveUpdated(false), + keyOn(false), + keyOff(false), + inPorta(false), + vol(15), + outVol(15), + resVol(15) {} + }; + Channel chan[8]; + bool isMuted[8]; + struct QueuedWrite { + unsigned char addr; + unsigned char val; + unsigned char mask; + QueuedWrite(unsigned char a, unsigned char v, unsigned char m=~0): addr(a), val(v), mask(m) {} + }; + std::queue writes; + unsigned char chanMax; + short loadWave, loadPos, loadLen; + unsigned char loadMode; + + n163_core *n163; + unsigned char regPool[128]; + void updateWave(int wave, int pos, int len); + void updateWaveCh(int ch); + friend void putDispatchChan(void*,int,int); + + public: + void acquire(short* bufL, short* bufR, size_t start, size_t len); + int dispatch(DivCommand c); + void* getChanState(int chan); + unsigned char* getRegisterPool(); + int getRegisterPoolSize(); + void reset(); + void forceIns(); + void tick(); + void muteChannel(int ch, bool mute); + void setFlags(unsigned int flags); + void notifyWaveChange(int wave); + void notifyInsChange(int ins); + void notifyInsDeletion(void* ins); + void poke(unsigned int addr, unsigned short val); + void poke(std::vector& wlist); + const char** getRegisterSheet(); + const char* getEffectName(unsigned char effect); + int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); + void quit(); + ~DivPlatformN163(); +}; + +#endif diff --git a/src/engine/platform/sound/n163/n163.cpp b/src/engine/platform/sound/n163/n163.cpp new file mode 100644 index 000000000..ee05bca4f --- /dev/null +++ b/src/engine/platform/sound/n163/n163.cpp @@ -0,0 +1,137 @@ +/* + License: BSD-3-Clause + see https://github.com/cam900/vgsound_emu/LICENSE for more details + + Copyright holder(s): cam900 + Namco 163 Sound emulation core + + This chip is one of NES mapper with sound expansion, This one is by Namco. + + It has 1 to 8 wavetable channels, All channel registers and waveforms are stored to internal RAM. + 4 bit Waveforms are freely allocatable, and its length is variables; its can be stores many short waveforms or few long waveforms in RAM. + + But waveforms are needs to squash, reallocate to avoid conflict with channel register area, each channel register size is 8 bytes per channels. + + Sound output is time division multiplexed, it's can be captured only single channels output at once. in reason, More activated channels are less sound quality. + + Sound register layout + + Address Bit Description + 7654 3210 + + 78-7f Channel 0 + 78 xxxx xxxx Channel 0 Pitch input bit 0-7 + 79 xxxx xxxx Channel 0 Accumulator bit 0-7* + 7a xxxx xxxx Channel 0 Pitch input bit 8-15 + 7b xxxx xxxx Channel 0 Accumulator bit 8-15* + 7c xxxx xx-- Channel 0 Waveform length, 256 - (x * 4) + ---- --xx Channel 0 Pitch input bit 16-17 + 7d xxxx xxxx Channel 0 Accumulator bit 16-23* + 7e xxxx xxxx Channel 0 Waveform base offset + xxxx xxx- RAM byte (0 to 127) + ---- ---x RAM nibble + ---- ---0 Low nibble + ---- ---1 High nibble + 7f ---- xxxx Channel 0 Volume + + 7f Number of active channels + 7f -xxx ---- Number of active channels + -000 ---- Channel 0 activated + -001 ---- Channel 1 activated + -010 ---- Channel 2 activated + ... + -110 ---- Channel 6 activated + -111 ---- Channel 7 activated + + 70-77 Channel 1 (Optional if activated) + 68-6f Channel 2 (Optional if activated) + ... + 48-4f Channel 6 (Optional if activated) + 40-47 Channel 7 (Optional if activated) + + Rest of RAM area are for 4 bit Waveform and/or scratchpad. + Each waveform byte has 2 nibbles packed, fetches LSB first, MSB next. + ---- xxxx 4 bit waveform, LSB + xxxx ---- Same as above, MSB + + Waveform address: Waveform base offset + Bit 16 to 23 of Accumulator, 1 LSB of result is nibble select, 7 MSB of result is Byte address in RAM. + + Frequency formula: + Frequency: Pitch input * ((Input clock * 15 * Number of activated voices) / 65536) +*/ + +#include "n163.hpp" + +void n163_core::tick() +{ + m_out = 0; + // 0xe000-0xe7ff Disable sound bits (bit 6, bit 0 to 5 are CPU ROM Bank 0x8000-0x9fff select.) + if (m_disable) + return; + + // tick per each clock + const u32 freq = m_ram[m_voice_cycle + 0] | (u32(m_ram[m_voice_cycle + 2]) << 8) | (bitfield(m_ram[m_voice_cycle + 4], 0, 2) << 16); // 18 bit frequency + u32 accum = m_ram[m_voice_cycle + 1] | (u32(m_ram[m_voice_cycle + 3]) << 8) | ( u32(m_ram[m_voice_cycle + 5]) << 16); // 24 bit accumulator + const u16 length = 256 - (m_ram[m_voice_cycle + 4] & 0xfc); + const u8 addr = m_ram[m_voice_cycle + 6] + bitfield(accum, 16, 8); + const s16 wave = (bitfield(m_ram[bitfield(addr, 1, 7)], bitfield(addr, 0) << 2, 4) - 8); + const s16 volume = bitfield(m_ram[m_voice_cycle + 7], 0, 4); + + // accumulate address + accum = bitfield(accum + freq, 0, 24); + if (bitfield(accum, 16, 8) >= length) + accum = bitfield(accum, 0, 18); + + // writeback to register + m_ram[m_voice_cycle + 1] = bitfield(accum, 0, 8); + m_ram[m_voice_cycle + 3] = bitfield(accum, 8, 8); + m_ram[m_voice_cycle + 5] = bitfield(accum, 16, 8); + + // update voice cycle + m_voice_cycle -= 0x8; + if (m_voice_cycle < (0x78 - (bitfield(m_ram[0x7f], 4, 3) << 3))) + m_voice_cycle = 0x78; + + // output 4 bit waveform and volume, multiplexed + m_out = wave * volume; +} + +void n163_core::reset() +{ + // reset this chip + m_disable = false; + std::fill(std::begin(m_ram), std::end(m_ram), 0); + m_voice_cycle = 0x78; + m_addr_latch.reset(); + m_out = 0; +} + +// accessor +void n163_core::addr_w(u8 data) +{ + // 0xf800-0xffff Sound address, increment + m_addr_latch.addr = bitfield(data, 0, 7); + m_addr_latch.incr = bitfield(data, 7); +} + +void n163_core::data_w(u8 data, bool cpu_access) +{ + // 0x4800-0x4fff Sound data write + m_ram[m_addr_latch.addr] = data; + + // address latch increment + if (cpu_access && m_addr_latch.incr) + m_addr_latch.addr = bitfield(m_addr_latch.addr + 1, 0, 7); +} + +u8 n163_core::data_r(bool cpu_access) +{ + // 0x4800-0x4fff Sound data read + const u8 ret = m_ram[m_addr_latch.addr]; + + // address latch increment + if (cpu_access && m_addr_latch.incr) + m_addr_latch.addr = bitfield(m_addr_latch.addr + 1, 0, 7); + + return ret; +} diff --git a/src/engine/platform/sound/n163/n163.hpp b/src/engine/platform/sound/n163/n163.hpp new file mode 100644 index 000000000..b97de9ae0 --- /dev/null +++ b/src/engine/platform/sound/n163/n163.hpp @@ -0,0 +1,78 @@ +/* + License: BSD-3-Clause + see https://github.com/cam900/vgsound_emu/LICENSE for more details + + Copyright holder(s): cam900 + Namco 163 Sound emulation core +*/ + +#include +#include + +#ifndef _VGSOUND_EMU_N163_HPP +#define _VGSOUND_EMU_N163_HPP + +#pragma once + +namespace n163 +{ + typedef unsigned char u8; + typedef unsigned short u16; + typedef unsigned int u32; + typedef signed short s16; + + // get bitfield, bitfield(input, position, len) + template T bitfield(T in, u8 pos, u8 len = 1) + { + return (in >> pos) & (len ? (T(1 << len) - 1) : 1); + } +}; + +using namespace n163; +class n163_core +{ +public: + // accessors, getters, setters + void addr_w(u8 data); + void data_w(u8 data, bool cpu_access = false); + u8 data_r(bool cpu_access = false); + + void set_disable(bool disable) { m_disable = disable; } + + // internal state + void reset(); + void tick(); + + // sound output pin + s16 out() { return m_out; } + + // register pool + u8 reg(u8 addr) { return m_ram[addr & 0x7f]; } + +private: + // Address latch + struct addr_latch_t + { + addr_latch_t() + : addr(0) + , incr(0) + { }; + + void reset() + { + addr = 0; + incr = 0; + } + + u8 addr : 7; + u8 incr : 1; + }; + + bool m_disable = false; + u8 m_ram[0x80] = {0}; // internal 128 byte RAM + u8 m_voice_cycle = 0x78; // Voice cycle for processing + addr_latch_t m_addr_latch; // address latch + s16 m_out = 0; // output +}; + +#endif diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 16f883e9e..e68188a89 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -128,6 +128,18 @@ const char* cmdName[DIV_CMD_MAX]={ "WS_SWEEP_TIME", "WS_SWEEP_AMOUNT", + "N163_WAVE_POSITION", + "N163_WAVE_LENGTH", + "N163_WAVE_MODE", + "N163_WAVE_LOAD", + "N163_WAVE_LOADPOS", + "N163_WAVE_LOADLEN", + "N163_CHANNEL_LIMIT", + "N163_GLOBAL_WAVE_LOAD", + "N163_GLOBAL_WAVE_LOADPOS", + "N163_GLOBAL_WAVE_LOADLEN", + "N163_GLOBAL_WAVE_LOADMODE", + "ALWAYS_SET_VOLUME" }; @@ -248,6 +260,51 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe return false; } break; + case DIV_SYSTEM_N163: + switch (effect) { + case 0x10: // select instrument waveform + dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal)); + break; + case 0x11: // select instrument waveform position in RAM + dispatchCmd(DivCommand(DIV_CMD_N163_WAVE_POSITION,ch,effectVal)); + break; + case 0x12: // select instrument waveform length in RAM + dispatchCmd(DivCommand(DIV_CMD_N163_WAVE_LENGTH,ch,effectVal)); + break; + case 0x13: // change instrument waveform update mode + dispatchCmd(DivCommand(DIV_CMD_N163_WAVE_MODE,ch,effectVal)); + break; + case 0x14: // select waveform for load to RAM + dispatchCmd(DivCommand(DIV_CMD_N163_WAVE_LOAD,ch,effectVal)); + break; + case 0x15: // select waveform position for load to RAM + dispatchCmd(DivCommand(DIV_CMD_N163_WAVE_LOADPOS,ch,effectVal)); + break; + case 0x16: // select waveform length for load to RAM + dispatchCmd(DivCommand(DIV_CMD_N163_WAVE_LOADLEN,ch,effectVal)); + break; + case 0x17: // change waveform load mode + dispatchCmd(DivCommand(DIV_CMD_N163_WAVE_LOADMODE,ch,effectVal)); + break; + case 0x18: // change channel limits + dispatchCmd(DivCommand(DIV_CMD_N163_CHANNEL_LIMIT,ch,effectVal)); + break; + case 0x20: // (global) select waveform for load to RAM + dispatchCmd(DivCommand(DIV_CMD_N163_GLOBAL_WAVE_LOAD,ch,effectVal)); + break; + case 0x21: // (global) select waveform position for load to RAM + dispatchCmd(DivCommand(DIV_CMD_N163_GLOBAL_WAVE_LOADPOS,ch,effectVal)); + break; + case 0x22: // (global) select waveform length for load to RAM + dispatchCmd(DivCommand(DIV_CMD_N163_GLOBAL_WAVE_LOADLEN,ch,effectVal)); + break; + case 0x23: // (global) change waveform load mode + dispatchCmd(DivCommand(DIV_CMD_N163_GLOBAL_WAVE_LOADMODE,ch,effectVal)); + break; + default: + return false; + } + break; case DIV_SYSTEM_QSOUND: switch (effect) { case 0x10: // echo feedback diff --git a/src/gui/debug.cpp b/src/gui/debug.cpp index ddbfb5b57..f5adb754b 100644 --- a/src/gui/debug.cpp +++ b/src/gui/debug.cpp @@ -37,6 +37,7 @@ #include "../engine/platform/saa.h" #include "../engine/platform/amiga.h" #include "../engine/platform/x1_010.h" +#include "../engine/platform/n163.h" #include "../engine/platform/dummy.h" #define GENESIS_DEBUG \ @@ -274,6 +275,36 @@ void putDispatchChan(void* data, int chanNum, int type) { ImGui::TextColored(ch->env.flag.envVinvL?colorOn:colorOff,">> EnvVinvL"); break; } + case DIV_SYSTEM_N163: { + DivPlatformN163::Channel* ch=(DivPlatformN163::Channel*)data; + ImGui::Text("> N163"); + ImGui::Text("* freq: %.4x",ch->freq); + ImGui::Text(" - base: %d",ch->baseFreq); + ImGui::Text(" - pitch: %d",ch->pitch); + ImGui::Text("- note: %d",ch->note); + ImGui::Text("- wave: %d",ch->wave); + ImGui::Text("- wavepos: %d",ch->wavePos); + ImGui::Text("- wavelen: %d",ch->waveLen); + ImGui::Text("- wavemode: %d",ch->waveMode); + ImGui::Text("- loadwave: %d",ch->loadWave); + ImGui::Text("- loadpos: %d",ch->loadPos); + ImGui::Text("- loadlen: %d",ch->loadLen); + ImGui::Text("- loadmode: %d",ch->loadMode); + ImGui::Text("- ins: %d",ch->ins); + ImGui::Text("- vol: %.2x",ch->vol); + ImGui::Text("- outVol: %.2x",ch->outVol); + ImGui::Text("- resVol: %.2x",ch->resVol); + ImGui::TextColored(ch->active?colorOn:colorOff,">> Active"); + ImGui::TextColored(ch->insChanged?colorOn:colorOff,">> InsChanged"); + ImGui::TextColored(ch->freqChanged?colorOn:colorOff,">> FreqChanged"); + ImGui::TextColored(ch->volumeChanged?colorOn:colorOff,">> VolumeChanged"); + ImGui::TextColored(ch->waveChanged?colorOn:colorOff,">> WaveChanged"); + ImGui::TextColored(ch->waveUpdated?colorOn:colorOff,">> WaveUpdated"); + ImGui::TextColored(ch->keyOn?colorOn:colorOff,">> KeyOn"); + ImGui::TextColored(ch->keyOff?colorOn:colorOff,">> KeyOff"); + ImGui::TextColored(ch->inPorta?colorOn:colorOff,">> InPorta"); + break; + } default: ImGui::Text("Unknown system! Help!"); break; diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 57b152ac8..ddf9a55a8 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -5516,6 +5516,7 @@ bool FurnaceGUI::loop() { sysAddOption(DIV_SYSTEM_SWAN); sysAddOption(DIV_SYSTEM_VERA); sysAddOption(DIV_SYSTEM_BUBSYS_WSG); + sysAddOption(DIV_SYSTEM_N163); ImGui::EndMenu(); } if (ImGui::BeginMenu("configure system...")) { @@ -5848,6 +5849,30 @@ bool FurnaceGUI::loop() { } break; } + case DIV_SYSTEM_N163: { + ImGui::Text("Clock rate:"); + if (ImGui::RadioButton("NTSC (1.79MHz)",(flags&15)==0)) { + e->setSysFlags(i,(flags&(~15))|0,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("PAL (1.67MHz)",(flags&15)==1)) { + e->setSysFlags(i,(flags&(~15))|1,restart); + updateWindowTitle(); + } + if (ImGui::RadioButton("Dendy (1.77MHz)",(flags&15)==2)) { + e->setSysFlags(i,(flags&(~15))|2,restart); + updateWindowTitle(); + } + ImGui::Text("Initial channel limit:"); + int initialChannelLimit=((flags>>4)&7)+1; + if (ImGui::SliderInt("##InitialChannelLimit",&initialChannelLimit,1,8)) { + if (initialChannelLimit<1) initialChannelLimit=1; + if (initialChannelLimit>8) initialChannelLimit=8; + e->setSysFlags(i,(flags & ~(7 << 4)) | (((initialChannelLimit-1) & 7) << 4),restart); + updateWindowTitle(); + } rightClickable + break; + } case DIV_SYSTEM_GB: case DIV_SYSTEM_SWAN: case DIV_SYSTEM_VERA: @@ -5914,6 +5939,7 @@ bool FurnaceGUI::loop() { sysChangeOption(i,DIV_SYSTEM_SWAN); sysChangeOption(i,DIV_SYSTEM_VERA); sysChangeOption(i,DIV_SYSTEM_BUBSYS_WSG); + sysChangeOption(i,DIV_SYSTEM_N163); ImGui::EndMenu(); } } @@ -7247,6 +7273,42 @@ FurnaceGUI::FurnaceGUI(): 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha YM3526", { + DIV_SYSTEM_OPL, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha YM3526 (drums mode)", { + DIV_SYSTEM_OPL_DRUMS, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha YM3812", { + DIV_SYSTEM_OPL2, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha YM3812 (drums mode)", { + DIV_SYSTEM_OPL2_DRUMS, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha YMF262", { + DIV_SYSTEM_OPL3, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha YMF262 (drums mode)", { + DIV_SYSTEM_OPL3_DRUMS, 64, 0, 0, + 0 + } + )); sysCategories.push_back(cat); cat=FurnaceGUISysCategory("Square"); @@ -7364,6 +7426,13 @@ FurnaceGUI::FurnaceGUI(): 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "NES with Namco 163", { + DIV_SYSTEM_NES, 64, 0, 0, + DIV_SYSTEM_N163, 64, 0, 112, + 0 + } + )); cat.systems.push_back(FurnaceGUISysDef( "Mattel Intellivision", { DIV_SYSTEM_AY8910, 64, 0, 48, @@ -7408,7 +7477,7 @@ FurnaceGUI::FurnaceGUI(): )); cat.systems.push_back(FurnaceGUISysDef( "Gamate", { - DIV_SYSTEM_AY8910, 64, 0, 73, + DIV_SYSTEM_AY8910, 64, 0, 73, // Swap channel 2 and 3 for play correctly to real gamate with stereo headphone 0 } )); @@ -7439,7 +7508,6 @@ FurnaceGUI::FurnaceGUI(): 0 } )); - /* cat.systems.push_back(FurnaceGUISysDef( "Commodore 64 (6581 SID + Sound Expander)", { DIV_SYSTEM_OPL, 64, 0, 0, @@ -7467,7 +7535,7 @@ FurnaceGUI::FurnaceGUI(): DIV_SYSTEM_C64_8580, 64, 0, 1, 0 } - ));*/ + )); cat.systems.push_back(FurnaceGUISysDef( "Amiga", { DIV_SYSTEM_AMIGA, 64, 0, 0, @@ -7591,6 +7659,22 @@ FurnaceGUI::FurnaceGUI(): 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "PC + Sound Blaster Pro", { + DIV_SYSTEM_OPL2, 64, -127, 0, + DIV_SYSTEM_OPL2, 64, 127, 0, + DIV_SYSTEM_PCSPKR, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "PC + Sound Blaster Pro (drums mode)", { + DIV_SYSTEM_OPL2_DRUMS, 64, -127, 0, + DIV_SYSTEM_OPL2_DRUMS, 64, 127, 0, + DIV_SYSTEM_PCSPKR, 64, 0, 0, + 0 + } + )); cat.systems.push_back(FurnaceGUISysDef( "PC + Sound Blaster Pro 2", { DIV_SYSTEM_OPL3, 64, 0, 0, diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index def41aad3..0afac9cc5 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -133,6 +133,10 @@ const char* x1_010EnvBits[8]={ "enable", "oneshot", "split L/R", "HinvR", "VinvR", "HinvL", "VinvL", NULL }; +const char* n163UpdateBits[8]={ + "now", "every waveform changed", NULL +}; + const char* oneBit[2]={ "on", NULL }; @@ -1596,6 +1600,10 @@ void FurnaceGUI::drawInsEdit() { dutyLabel="Duty"; dutyMax=63; } + if (ins->type==DIV_INS_N163) { + dutyLabel="Waveform pos."; + dutyMax=255; + } bool dutyIsRel=(ins->type==DIV_INS_C64 && !ins->c64.dutyIsAbs); int waveMax=(ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_VERA)?3:63; @@ -1627,6 +1635,10 @@ void FurnaceGUI::drawInsEdit() { ex2Max=63; ex2Bit=false; } + if (ins->type==DIV_INS_N163) { + ex1Max=252; + ex2Max=2; + } if (ins->type==DIV_INS_SAA1099) ex1Max=8; if (settings.macroView==0) { // modern view @@ -1653,6 +1665,8 @@ void FurnaceGUI::drawInsEdit() { NORMAL_MACRO(ins->std.ex1Macro,ins->std.ex1MacroLen,ins->std.ex1MacroLoop,ins->std.ex1MacroRel,0,ex1Max,"ex1","Envelope",160,ins->std.ex1MacroOpen,true,saaEnvBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false); } else if (ins->type==DIV_INS_X1_010) { NORMAL_MACRO(ins->std.ex1Macro,ins->std.ex1MacroLen,ins->std.ex1MacroLoop,ins->std.ex1MacroRel,0,ex1Max,"ex1","Envelope Mode",160,ins->std.ex1MacroOpen,true,x1_010EnvBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false); + } else if (ins->type==DIV_INS_N163) { + NORMAL_MACRO(ins->std.ex1Macro,ins->std.ex1MacroLen,ins->std.ex1MacroLoop,ins->std.ex1MacroRel,0,ex1Max,"ex1","Waveform len.",160,ins->std.ex1MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false); } else { NORMAL_MACRO(ins->std.ex1Macro,ins->std.ex1MacroLen,ins->std.ex1MacroLoop,ins->std.ex1MacroRel,0,ex1Max,"ex1","Duty",160,ins->std.ex1MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false); } @@ -1660,6 +1674,8 @@ void FurnaceGUI::drawInsEdit() { if (ex2Max>0) { if (ins->type==DIV_INS_C64) { NORMAL_MACRO(ins->std.ex2Macro,ins->std.ex2MacroLen,ins->std.ex2MacroLoop,ins->std.ex2MacroRel,0,ex2Max,"ex2","Resonance",64,ins->std.ex2MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[5],0,ex2Max,NULL,false); + } else if (ins->type==DIV_INS_N163) { + NORMAL_MACRO(ins->std.ex2Macro,ins->std.ex2MacroLen,ins->std.ex2MacroLoop,ins->std.ex2MacroRel,0,ex2Max,"ex2","Waveform update",64,ins->std.ex2MacroOpen,true,n163UpdateBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[5],0,ex2Max,NULL,false); } else { NORMAL_MACRO(ins->std.ex2Macro,ins->std.ex2MacroLen,ins->std.ex2MacroLoop,ins->std.ex2MacroRel,0,ex2Max,"ex2","Envelope",ex2Bit?64:160,ins->std.ex2MacroOpen,ex2Bit,ayEnvBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[5],0,ex2Max,NULL,false); } @@ -1676,6 +1692,12 @@ void FurnaceGUI::drawInsEdit() { NORMAL_MACRO(ins->std.fbMacro,ins->std.fbMacroLen,ins->std.fbMacroLoop,ins->std.fbMacroRel,0,8,"fb","Noise AND Mask",96,ins->std.fbMacroOpen,true,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[8],0,8,NULL,false); NORMAL_MACRO(ins->std.fmsMacro,ins->std.fmsMacroLen,ins->std.fmsMacroLoop,ins->std.fmsMacroRel,0,8,"fms","Noise OR Mask",96,ins->std.fmsMacroOpen,true,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[9],0,8,NULL,false); } + if (ins->type==DIV_INS_N163) { + NORMAL_MACRO(ins->std.ex3Macro,ins->std.ex3MacroLen,ins->std.ex3MacroLoop,ins->std.ex3MacroRel,0,255,"ex3","Waveform to Load",160,ins->std.ex3MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[6],0,255,NULL,false); + NORMAL_MACRO(ins->std.algMacro,ins->std.algMacroLen,ins->std.algMacroLoop,ins->std.algMacroRel,0,255,"alg","Wave pos. to Load",160,ins->std.algMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[7],0,255,NULL,false); + NORMAL_MACRO(ins->std.fbMacro,ins->std.fbMacroLen,ins->std.fbMacroLoop,ins->std.fbMacroRel,0,252,"fb","Wave len. to Load",160,ins->std.fbMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[8],0,252,NULL,false); + NORMAL_MACRO(ins->std.fmsMacro,ins->std.fmsMacroLen,ins->std.fmsMacroLoop,ins->std.fmsMacroRel,0,2,"fms","Waveform load",64,ins->std.fmsMacroOpen,true,n163UpdateBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[9],0,2,NULL,false); + } MACRO_END; } else { // classic view @@ -2002,7 +2024,7 @@ void FurnaceGUI::drawWaveEdit() { DivWavetable* wave=e->song.wave[curWave]; ImGui::Text("Width"); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("use a width of 32 on Game Boy, PC Engine and WonderSwan.\nany other widths will be scaled during playback."); + ImGui::SetTooltip("use a width of:\n- 32 on Game Boy, PC Engine, WonderSwan and Bubble System WSG\n- 128 on X1-010\nany other widths will be scaled during playback."); } ImGui::SameLine(); ImGui::SetNextItemWidth(128.0f*dpiScale); @@ -2016,7 +2038,7 @@ void FurnaceGUI::drawWaveEdit() { ImGui::SameLine(); ImGui::Text("Height"); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("use a height of:\n- 15 for Game Boy and WonderSwan\n- 31 for PC Engine\nany other heights will be scaled during playback."); + ImGui::SetTooltip("use a height of:\n- 15 for Game Boy, WonderSwan, X1-010 envelope shape, Bubble System WSG and N163\n- 31 for PC Engine\n- 255 for X1-010 waveform\nany other heights will be scaled during playback."); } ImGui::SameLine(); ImGui::SetNextItemWidth(128.0f*dpiScale); From abb5f0314394b033c9759006295d7c5e58fa56b9 Mon Sep 17 00:00:00 2001 From: cam900 Date: Wed, 23 Mar 2022 03:16:20 +0900 Subject: [PATCH 377/637] System docs --- papers/doc/7-systems/n163.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/papers/doc/7-systems/n163.md b/papers/doc/7-systems/n163.md index 80a2d28de..68774801c 100644 --- a/papers/doc/7-systems/n163.md +++ b/papers/doc/7-systems/n163.md @@ -1,6 +1,6 @@ # Namco 163 -This is Namco's one of NES mapper, with up to 8 wavetable channels. It has also 128 byte of internal RAM, both channel register and wavetables are stored here. Wavetables are variable size and freely allocable anywhere in RAM, it means it can be uses part of or merged 2 or more pre-loaded waveforms in RAM. But waveform RAM area becomes smaller as much as activating more channels; Channel register consumes 8 byte for each channels. You must avoid conflict with channel register area and waveform for avoid channel playback broken. +This is Namco's one of NES mapper, with up to 8 wavetable channels. It has also 128 byte of internal RAM, both channel register and wavetables are stored here. Wavetables are variable size and freely allocable anywhere in RAM, it means it can be uses part of or continuously pre-loaded waveform and/or its sequences in RAM. But waveform RAM area becomes smaller as much as activating more channels; Channel register consumes 8 byte for each channels. You must avoid conflict with channel register area and waveform for avoid channel playback broken. It has can be outputs only single channel at clock; so it's sound quality is more crunchy as much as activating more channels. From 4ba65d39067f01fa6024cc98173cde98984e6621 Mon Sep 17 00:00:00 2001 From: cam900 Date: Wed, 23 Mar 2022 03:17:06 +0900 Subject: [PATCH 378/637] Fix spacing --- src/engine/platform/sound/n163/n163.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine/platform/sound/n163/n163.cpp b/src/engine/platform/sound/n163/n163.cpp index ee05bca4f..192189d93 100644 --- a/src/engine/platform/sound/n163/n163.cpp +++ b/src/engine/platform/sound/n163/n163.cpp @@ -30,8 +30,8 @@ 7e xxxx xxxx Channel 0 Waveform base offset xxxx xxx- RAM byte (0 to 127) ---- ---x RAM nibble - ---- ---0 Low nibble - ---- ---1 High nibble + ---- ---0 Low nibble + ---- ---1 High nibble 7f ---- xxxx Channel 0 Volume 7f Number of active channels From a199c102ce7626611222feca841a5e22981febdf Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 22 Mar 2022 15:37:36 -0500 Subject: [PATCH 379/637] GUI: hopefully better workaround to macOS #4 --- src/gui/gui.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index f32e6eba2..11d8875b2 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2165,6 +2165,9 @@ bool FurnaceGUI::loop() { if (firstFrame) { firstFrame=false; if (patternOpen) nextWindow=GUI_WINDOW_PATTERN; +#ifdef __APPLE__ + SDL_RaiseWindow(sdlWin); +#endif } if (fileDialog->render(ImVec2(600.0f*dpiScale,400.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale))) { @@ -2624,10 +2627,6 @@ bool FurnaceGUI::init() { firstFrame=true; -#ifdef __APPLE__ - SDL_RaiseWindow(sdlWin); -#endif - return true; } From 7ebc63a5144547498c69201cca8b55a432b06865 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 22 Mar 2022 17:01:06 -0500 Subject: [PATCH 380/637] GUI: implement sample paste --- src/engine/sample.cpp | 8 +++- src/gui/doAction.cpp | 88 +++++++++++++++++++++++++++++++++++++++--- src/gui/gui.cpp | 2 + src/gui/gui.h | 5 ++- src/gui/sampleEdit.cpp | 30 ++++++++++++++ src/gui/settings.cpp | 7 +++- 6 files changed, 129 insertions(+), 11 deletions(-) diff --git a/src/engine/sample.cpp b/src/engine/sample.cpp index 33532d43d..19768fcf7 100644 --- a/src/engine/sample.cpp +++ b/src/engine/sample.cpp @@ -258,7 +258,6 @@ bool DivSample::trim(unsigned int begin, unsigned int end) { return false; } -// TODO: for clipboard bool DivSample::insert(unsigned int pos, unsigned int length) { unsigned int count=samples+length; if (depth==8) { @@ -283,7 +282,12 @@ bool DivSample::insert(unsigned int pos, unsigned int length) { short* oldData16=data16; data16=NULL; initInternal(16,count); - memcpy(data16,oldData16,sizeof(short)*count); + if (pos>0) { + memcpy(data16,oldData16,sizeof(short)*pos); + } + if (count-pos-length>0) { + memcpy(&(data16[pos+length]),&(oldData16[pos]),sizeof(short)*(count-pos-length)); + } delete[] oldData16; } else { initInternal(16,count); diff --git a/src/gui/doAction.cpp b/src/gui/doAction.cpp index 51dad892e..c89c957c4 100644 --- a/src/gui/doAction.cpp +++ b/src/gui/doAction.cpp @@ -599,7 +599,7 @@ void FurnaceGUI::doAction(int what) { } sampleClipboard=new short[end-start]; sampleClipboardLen=end-start; - memcpy(sampleClipboard,&(sample->data16[start]),end-start); + memcpy(sampleClipboard,&(sample->data16[start]),sizeof(short)*(end-start)); e->lockEngine([this,sample,start,end]() { sample->strip(start,end); @@ -625,18 +625,92 @@ void FurnaceGUI::doAction(int what) { } sampleClipboard=new short[end-start]; sampleClipboardLen=end-start; - memcpy(sampleClipboard,&(sample->data16[start]),end-start); + memcpy(sampleClipboard,&(sample->data16[start]),sizeof(short)*(end-start)); break; } - case GUI_ACTION_SAMPLE_PASTE: // TODO!!! + case GUI_ACTION_SAMPLE_PASTE: { if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + if (sampleClipboard==NULL || sampleClipboardLen<1) break; + DivSample* sample=e->song.sample[curSample]; + int pos=(sampleSelStart==-1 || sampleSelStart==sampleSelEnd)?sample->samples:sampleSelStart; + + e->lockEngine([this,sample,pos]() { + if (!sample->insert(pos,sampleClipboardLen)) { + showError("couldn't paste! make sure your sample is 8 or 16-bit."); + } else { + if (sample->depth==8) { + for (size_t i=0; idata8[pos+i]=sampleClipboard[i]>>8; + } + } else { + memcpy(&(sample->data16[pos]),sampleClipboard,sizeof(short)*sampleClipboardLen); + } + } + e->renderSamples(); + }); + sampleSelStart=pos; + sampleSelEnd=pos+sampleClipboardLen; + MARK_MODIFIED; break; - case GUI_ACTION_SAMPLE_PASTE_REPLACE: + } + case GUI_ACTION_SAMPLE_PASTE_REPLACE: { if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + if (sampleClipboard==NULL || sampleClipboardLen<1) break; + DivSample* sample=e->song.sample[curSample]; + int pos=(sampleSelStart==-1 || sampleSelStart==sampleSelEnd)?0:sampleSelStart; + + e->lockEngine([this,sample,pos]() { + if (sample->depth==8) { + for (size_t i=0; i=sample->samples) break; + sample->data8[pos+i]=sampleClipboard[i]>>8; + } + } else { + for (size_t i=0; i=sample->samples) break; + sample->data16[pos+i]=sampleClipboard[i]; + } + } + e->renderSamples(); + }); + sampleSelStart=pos; + sampleSelEnd=pos+sampleClipboardLen; + if (sampleSelEnd>(int)sample->samples) sampleSelEnd=sample->samples; + MARK_MODIFIED; break; - case GUI_ACTION_SAMPLE_PASTE_MIX: + } + case GUI_ACTION_SAMPLE_PASTE_MIX: { if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + if (sampleClipboard==NULL || sampleClipboardLen<1) break; + DivSample* sample=e->song.sample[curSample]; + int pos=(sampleSelStart==-1 || sampleSelStart==sampleSelEnd)?0:sampleSelStart; + + e->lockEngine([this,sample,pos]() { + if (sample->depth==8) { + for (size_t i=0; i=sample->samples) break; + int val=sample->data8[pos+i]+(sampleClipboard[i]>>8); + if (val>127) val=127; + if (val<-128) val=-128; + sample->data8[pos+i]=val; + } + } else { + for (size_t i=0; i=sample->samples) break; + int val=sample->data16[pos+i]+sampleClipboard[i]; + if (val>32767) val=32767; + if (val<-32768) val=-32768; + sample->data16[pos+i]=val; + } + } + e->renderSamples(); + }); + sampleSelStart=pos; + sampleSelEnd=pos+sampleClipboardLen; + if (sampleSelEnd>(int)sample->samples) sampleSelEnd=sample->samples; + MARK_MODIFIED; break; + } case GUI_ACTION_SAMPLE_SELECT_ALL: { if (curSample<0 || curSample>=(int)e->song.sample.size()) break; DivSample* sample=e->song.sample[curSample]; @@ -761,6 +835,10 @@ void FurnaceGUI::doAction(int what) { MARK_MODIFIED; break; } + case GUI_ACTION_SAMPLE_INSERT: + if (curSample<0 || curSample>=(int)e->song.sample.size()) break; + openSampleSilenceOpt=true; + break; case GUI_ACTION_SAMPLE_SILENCE: { if (curSample<0 || curSample>=(int)e->song.sample.size()) break; DivSample* sample=e->song.sample[curSample]; diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 11d8875b2..fedf53d15 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2838,6 +2838,7 @@ FurnaceGUI::FurnaceGUI(): prevSampleZoom(1.0), samplePos(0), resizeSize(1024), + silenceSize(1024), resampleTarget(32000), resampleStrat(5), amplifyVol(100.0), @@ -2863,6 +2864,7 @@ FurnaceGUI::FurnaceGUI(): openSampleResizeOpt(false), openSampleResampleOpt(false), openSampleAmplifyOpt(false), + openSampleSilenceOpt(false), openSampleFilterOpt(false) { // value keys valueKeys[SDLK_0]=0; diff --git a/src/gui/gui.h b/src/gui/gui.h index aa661b3a4..f92efd959 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -380,6 +380,7 @@ enum FurnaceGUIActions { GUI_ACTION_SAMPLE_FADE_IN, GUI_ACTION_SAMPLE_FADE_OUT, GUI_ACTION_SAMPLE_SILENCE, + GUI_ACTION_SAMPLE_INSERT, GUI_ACTION_SAMPLE_DELETE, GUI_ACTION_SAMPLE_TRIM, GUI_ACTION_SAMPLE_REVERSE, @@ -796,7 +797,7 @@ class FurnaceGUI { double sampleZoom; double prevSampleZoom; int samplePos; - int resizeSize; + int resizeSize, silenceSize; double resampleTarget; int resampleStrat; float amplifyVol; @@ -810,7 +811,7 @@ class FurnaceGUI { unsigned char sampleFilterPower; short* sampleClipboard; size_t sampleClipboardLen; - bool openSampleResizeOpt, openSampleResampleOpt, openSampleAmplifyOpt, openSampleFilterOpt; + bool openSampleResizeOpt, openSampleResampleOpt, openSampleAmplifyOpt, openSampleSilenceOpt, openSampleFilterOpt; // visualizer float keyHit[DIV_MAX_CHANS]; diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index 28e0f3d59..81bab4489 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -303,6 +303,36 @@ void FurnaceGUI::drawSampleEdit() { ImGui::SetTooltip("Fade out"); } ImGui::SameLine(); + ImGui::Button(ICON_FA_ADJUST "##SInsertSilence"); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Insert silence"); + } + if (openSampleSilenceOpt) { + openSampleSilenceOpt=false; + ImGui::OpenPopup("SSilenceOpt"); + } + if (ImGui::BeginPopupContextItem("SSilenceOpt",ImGuiPopupFlags_MouseButtonLeft)) { + if (ImGui::InputInt("Samples",&silenceSize,1,64)) { + if (silenceSize<0) silenceSize=0; + if (silenceSize>16777215) silenceSize=16777215; + } + if (ImGui::Button("Resize")) { + int pos=(sampleSelStart==-1 || sampleSelStart==sampleSelEnd)?sample->samples:sampleSelStart; + e->lockEngine([this,sample,pos]() { + if (!sample->insert(pos,silenceSize)) { + showError("couldn't insert! make sure your sample is 8 or 16-bit."); + } + e->renderSamples(); + }); + updateSampleTex=true; + sampleSelStart=pos; + sampleSelEnd=pos+silenceSize; + MARK_MODIFIED; + ImGui::CloseCurrentPopup(); + } + ImGui::EndPopup(); + } + ImGui::SameLine(); if (ImGui::Button(ICON_FA_ERASER "##SSilence")) { doAction(GUI_ACTION_SAMPLE_SILENCE); } diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 9a1a9eee2..9f1a8f5b1 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -883,6 +883,7 @@ void FurnaceGUI::drawSettings() { UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_NORMALIZE,"Normalize"); UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_FADE_IN,"Fade in"); UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_FADE_OUT,"Fade out"); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_INSERT,"Insert silence"); UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_SILENCE,"Apply silence"); UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_DELETE,"Delete"); UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_TRIM,"Trim"); @@ -1158,8 +1159,8 @@ void FurnaceGUI::syncSettings() { LOAD_KEYBIND(GUI_ACTION_SAMPLE_CUT,FURKMOD_CMD|SDLK_x); LOAD_KEYBIND(GUI_ACTION_SAMPLE_COPY,FURKMOD_CMD|SDLK_c); LOAD_KEYBIND(GUI_ACTION_SAMPLE_PASTE,FURKMOD_CMD|SDLK_v); - LOAD_KEYBIND(GUI_ACTION_SAMPLE_PASTE_REPLACE,FURKMOD_CMD|FURKMOD_SHIFT|SDLK_x); - LOAD_KEYBIND(GUI_ACTION_SAMPLE_PASTE_MIX,FURKMOD_CMD|FURKMOD_ALT|SDLK_x); + LOAD_KEYBIND(GUI_ACTION_SAMPLE_PASTE_REPLACE,FURKMOD_CMD|FURKMOD_SHIFT|SDLK_v); + LOAD_KEYBIND(GUI_ACTION_SAMPLE_PASTE_MIX,FURKMOD_CMD|FURKMOD_ALT|SDLK_v); LOAD_KEYBIND(GUI_ACTION_SAMPLE_SELECT_ALL,FURKMOD_CMD|SDLK_a); LOAD_KEYBIND(GUI_ACTION_SAMPLE_RESIZE,FURKMOD_CMD|SDLK_r); LOAD_KEYBIND(GUI_ACTION_SAMPLE_RESAMPLE,FURKMOD_CMD|SDLK_e); @@ -1167,6 +1168,7 @@ void FurnaceGUI::syncSettings() { LOAD_KEYBIND(GUI_ACTION_SAMPLE_NORMALIZE,FURKMOD_CMD|SDLK_n); LOAD_KEYBIND(GUI_ACTION_SAMPLE_FADE_IN,FURKMOD_CMD|SDLK_i); LOAD_KEYBIND(GUI_ACTION_SAMPLE_FADE_OUT,FURKMOD_CMD|SDLK_o); + LOAD_KEYBIND(GUI_ACTION_SAMPLE_INSERT,SDLK_INSERT); LOAD_KEYBIND(GUI_ACTION_SAMPLE_SILENCE,FURKMOD_SHIFT|SDLK_DELETE); LOAD_KEYBIND(GUI_ACTION_SAMPLE_DELETE,SDLK_DELETE); LOAD_KEYBIND(GUI_ACTION_SAMPLE_TRIM,FURKMOD_CMD|SDLK_DELETE); @@ -1479,6 +1481,7 @@ void FurnaceGUI::commitSettings() { SAVE_KEYBIND(GUI_ACTION_SAMPLE_NORMALIZE); SAVE_KEYBIND(GUI_ACTION_SAMPLE_FADE_IN); SAVE_KEYBIND(GUI_ACTION_SAMPLE_FADE_OUT); + SAVE_KEYBIND(GUI_ACTION_SAMPLE_INSERT); SAVE_KEYBIND(GUI_ACTION_SAMPLE_SILENCE); SAVE_KEYBIND(GUI_ACTION_SAMPLE_DELETE); SAVE_KEYBIND(GUI_ACTION_SAMPLE_TRIM); From 97926c4e5c21f723259557bf878ec5000daf3b52 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 22 Mar 2022 17:04:36 -0500 Subject: [PATCH 381/637] GUI: update sample texture --- src/gui/doAction.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/gui/doAction.cpp b/src/gui/doAction.cpp index c89c957c4..66a05fb3a 100644 --- a/src/gui/doAction.cpp +++ b/src/gui/doAction.cpp @@ -650,6 +650,7 @@ void FurnaceGUI::doAction(int what) { }); sampleSelStart=pos; sampleSelEnd=pos+sampleClipboardLen; + updateSampleTex=true; MARK_MODIFIED; break; } @@ -676,6 +677,7 @@ void FurnaceGUI::doAction(int what) { sampleSelStart=pos; sampleSelEnd=pos+sampleClipboardLen; if (sampleSelEnd>(int)sample->samples) sampleSelEnd=sample->samples; + updateSampleTex=true; MARK_MODIFIED; break; } @@ -708,6 +710,7 @@ void FurnaceGUI::doAction(int what) { sampleSelStart=pos; sampleSelEnd=pos+sampleClipboardLen; if (sampleSelEnd>(int)sample->samples) sampleSelEnd=sample->samples; + updateSampleTex=true; MARK_MODIFIED; break; } From 8e0119b2d30852012a9e551d3f6bc05022927bc7 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 22 Mar 2022 17:33:54 -0500 Subject: [PATCH 382/637] GUI: more sample editor fixes --- src/gui/sampleEdit.cpp | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index 81bab4489..f8515db54 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -581,9 +581,15 @@ void FurnaceGUI::drawSampleEdit() { } else { ImU32 bgColor=ImGui::GetColorU32(ImGuiCol_FrameBg); ImU32 lineColor=ImGui::GetColorU32(ImGuiCol_PlotLines); + ImU32 centerLineColor=ImAlphaBlendColors(bgColor,ImGui::GetColorU32(ImGuiCol_PlotLines,0.25)); for (int i=0; i0) { + for (int i=availX*(availY>>1); i>1)); i++) { + data[i]=centerLineColor; + } + } unsigned int xCoarse=samplePos; unsigned int xFine=0; unsigned int xAdvanceCoarse=sampleZoom; @@ -659,7 +665,7 @@ void FurnaceGUI::drawSampleEdit() { bool drawSelection=false; if (!sampleDragMode) { - if (sampleSelStart>=0 && sampleSelEnd>=0 && sampleSelStart!=sampleSelEnd) { + if (sampleSelStart>=0 && sampleSelEnd>=0) { int start=sampleSelStart; int end=sampleSelEnd; if (start>end) { @@ -699,13 +705,25 @@ void FurnaceGUI::drawSampleEdit() { } ImDrawList* dl=ImGui::GetWindowDrawList(); ImVec2 p1=rectMin; - p1.x+=start/sampleZoom-samplePos; + p1.x+=(start-samplePos)/sampleZoom; - ImVec2 p2=ImVec2(rectMin.x+end/sampleZoom-samplePos,rectMax.y); + ImVec2 p2=ImVec2(rectMin.x+(end-samplePos)/sampleZoom,rectMax.y); + ImVec4 boundColor=uiColors[GUI_COLOR_ACCENT_PRIMARY]; ImVec4 selColor=uiColors[GUI_COLOR_ACCENT_SECONDARY]; + boundColor.w*=0.5; selColor.w*=0.25; + if (p1.xrectMax.x) p1.x=rectMax.x; + + if (p2.xrectMax.x) p2.x=rectMax.x; + dl->AddRectFilled(p1,p2,ImGui::GetColorU32(selColor)); + dl->AddLine(ImVec2(p1.x,p1.y),ImVec2(p1.x,p2.y),ImGui::GetColorU32(boundColor)); + if (start!=end) { + dl->AddLine(ImVec2(p2.x,p1.y),ImVec2(p2.x,p2.y),ImGui::GetColorU32(boundColor)); + } } ImS64 scrollV=samplePos; From 519dd7f2dd66b26c9cd5fb074059d1ae6028e6cb Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 22 Mar 2022 18:05:32 -0500 Subject: [PATCH 383/637] GUI: aaand more sample editor work now it has a right click menu --- src/gui/gui.cpp | 2 -- src/gui/gui.h | 2 ++ src/gui/sampleEdit.cpp | 39 +++++++++++++++++++++++++++++++++++++-- 3 files changed, 39 insertions(+), 4 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index fedf53d15..9f869e57d 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1539,8 +1539,6 @@ void FurnaceGUI::processDrags(int dragX, int dragY) { fileName+=fallback; \ } -#define BIND_FOR(x) getKeyName(actionKeys[x],true).c_str() - void FurnaceGUI::editOptions(bool topMenu) { char id[4096]; if (ImGui::MenuItem("cut",BIND_FOR(GUI_ACTION_PAT_CUT))) doCopy(true); diff --git a/src/gui/gui.h b/src/gui/gui.h index f92efd959..a4feed9b5 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -43,6 +43,8 @@ #define TOGGLE_COLOR(x) ((x)?uiColors[GUI_COLOR_TOGGLE_ON]:uiColors[GUI_COLOR_TOGGLE_OFF]) +#define BIND_FOR(x) getKeyName(actionKeys[x],true).c_str() + enum FurnaceGUIColors { GUI_COLOR_BACKGROUND=0, GUI_COLOR_FRAME_BACKGROUND, diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index f8515db54..3bdfa6c9f 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -26,6 +26,7 @@ #include #include "guiConst.h" #include "sampleUtil.h" +#include "util.h" void FurnaceGUI::drawSampleEdit() { if (nextWindow==GUI_WINDOW_SAMPLE_EDIT) { @@ -580,10 +581,17 @@ void FurnaceGUI::drawSampleEdit() { logE("error while locking sample texture! %s\n",SDL_GetError()); } else { ImU32 bgColor=ImGui::GetColorU32(ImGuiCol_FrameBg); + ImU32 bgColorLoop=ImAlphaBlendColors(bgColor,ImGui::GetColorU32(ImGuiCol_FrameBgHovered,0.5)); ImU32 lineColor=ImGui::GetColorU32(ImGuiCol_PlotLines); ImU32 centerLineColor=ImAlphaBlendColors(bgColor,ImGui::GetColorU32(ImGuiCol_PlotLines,0.25)); - for (int i=0; iloopStart>=0 && sample->loopStart<(int)sample->samples && j-samplePos>sample->loopStart) { + data[i*availX+j]=bgColorLoop; + } else { + data[i*availX+j]=bgColor; + } + } } if (availY>0) { for (int i=availX*(availY>>1); i>1)); i++) { @@ -661,6 +669,33 @@ void FurnaceGUI::drawSampleEdit() { } } } + + if (!sampleDragMode && ImGui::IsItemClicked(ImGuiMouseButton_Right)) { + ImGui::OpenPopup("SRightClick"); + } + + if (ImGui::BeginPopup("SRightClick",ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_AlwaysAutoResize)) { + if (ImGui::MenuItem("cut",BIND_FOR(GUI_ACTION_SAMPLE_CUT))) { + doAction(GUI_ACTION_SAMPLE_CUT); + } + if (ImGui::MenuItem("copy",BIND_FOR(GUI_ACTION_SAMPLE_COPY))) { + doAction(GUI_ACTION_SAMPLE_COPY); + } + if (ImGui::MenuItem("paste",BIND_FOR(GUI_ACTION_SAMPLE_PASTE))) { + doAction(GUI_ACTION_SAMPLE_PASTE); + } + if (ImGui::MenuItem("paste (replace)",BIND_FOR(GUI_ACTION_SAMPLE_PASTE_REPLACE))) { + doAction(GUI_ACTION_SAMPLE_PASTE_REPLACE); + } + if (ImGui::MenuItem("paste (mix)",BIND_FOR(GUI_ACTION_SAMPLE_PASTE_MIX))) { + doAction(GUI_ACTION_SAMPLE_PASTE_MIX); + } + if (ImGui::MenuItem("select all",BIND_FOR(GUI_ACTION_SAMPLE_SELECT_ALL))) { + doAction(GUI_ACTION_SAMPLE_SELECT_ALL); + } + ImGui::EndPopup(); + } + String statusBar=sampleDragMode?"Draw":"Select"; bool drawSelection=false; From 2c05d56ab22f5476fdd9573c79bc91110e3f046f Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 22 Mar 2022 18:36:29 -0500 Subject: [PATCH 384/637] prepare for sample undo/redo --- src/engine/sample.cpp | 45 +++++++++++++++++++++++++++++++++++++ src/engine/sample.h | 52 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+) diff --git a/src/engine/sample.cpp b/src/engine/sample.cpp index 19768fcf7..d5e1844c8 100644 --- a/src/engine/sample.cpp +++ b/src/engine/sample.cpp @@ -32,6 +32,10 @@ extern "C" { #include "../../extern/adpcm/ymz_codec.h" } +DivSampleHistory::~DivSampleHistory() { + if (data!=NULL) delete[] data; +} + bool DivSample::save(const char* path) { SNDFILE* f; SF_INFO si; @@ -786,7 +790,48 @@ unsigned int DivSample::getCurBufLen() { return 0; } +DivSampleHistory* DivSample::prepareUndo(bool data) { + DivSampleHistory* h; + if (data) { + unsigned char* duplicate; + if (getCurBuf()==NULL) { + duplicate=NULL; + } else { + duplicate=new unsigned char[getCurBufLen()]; + memcpy(duplicate,getCurBuf(),getCurBufLen()); + } + h=new DivSampleHistory(duplicate,getCurBufLen(),samples,depth,rate,centerRate,loopStart); + } else { + h=new DivSampleHistory(depth,rate,centerRate,loopStart); + } + while (!redoHist.empty()) { + DivSampleHistory* h=redoHist.front(); + delete h; + redoHist.pop_front(); + } + undoHist.push_back(h); + return h; +} + +int DivSample::undo() { + return 0; +} + +int DivSample::redo() { + return 0; +} + DivSample::~DivSample() { + while (!undoHist.empty()) { + DivSampleHistory* h=undoHist.front(); + delete h; + undoHist.pop_front(); + } + while (!redoHist.empty()) { + DivSampleHistory* h=redoHist.front(); + delete h; + redoHist.pop_front(); + } if (data8) delete[] data8; if (data16) delete[] data16; if (data1) delete[] data1; diff --git a/src/engine/sample.h b/src/engine/sample.h index c697a9951..f07c7ef91 100644 --- a/src/engine/sample.h +++ b/src/engine/sample.h @@ -18,6 +18,7 @@ */ #include "../ta-utils.h" +#include enum DivResampleFilters { DIV_RESAMPLE_NONE=0, @@ -28,6 +29,33 @@ enum DivResampleFilters { DIV_RESAMPLE_BEST }; +struct DivSampleHistory { + unsigned char* data; + unsigned int length, samples; + unsigned char depth; + int rate, centerRate, loopStart; + bool hasSample; + DivSampleHistory(void* d, unsigned int l, unsigned int s, unsigned char de, int r, int cr, int ls): + data((unsigned char*)d), + length(l), + samples(s), + depth(de), + rate(r), + centerRate(cr), + loopStart(ls), + hasSample(true) {} + DivSampleHistory(unsigned char de, int r, int cr, int ls): + data(NULL), + length(0), + samples(0), + depth(de), + rate(r), + centerRate(cr), + loopStart(ls), + hasSample(false) {} + ~DivSampleHistory(); +}; + struct DivSample { String name; int rate, centerRate, loopStart, loopOffP; @@ -62,6 +90,9 @@ struct DivSample { unsigned int samples; + std::deque undoHist; + std::deque redoHist; + /** * @warning DO NOT USE - internal functions */ @@ -154,6 +185,27 @@ struct DivSample { * @return the sample data length. */ unsigned int getCurBufLen(); + + /** + * prepare an undo step for this sample. + * @param data whether to include sample data. + * @return the undo step. + */ + DivSampleHistory* prepareUndo(bool data); + + /** + * undo. you may need to call DivEngine::renderSamples afterwards. + * @warning do not attempt to undo outside of a synchronized block! + * @return 0 on failure; 1 on success and 2 on success (data changed). + */ + int undo(); + + /** + * redo. you may need to call DivEngine::renderSamples afterwards. + * @warning do not attempt to redo outside of a synchronized block! + * @return 0 on failure; 1 on success and 2 on success (data changed). + */ + int redo(); DivSample(): name(""), rate(32000), From f45273c89ccafc908d40510e69743e6a40c5c3d2 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 23 Mar 2022 00:04:47 -0500 Subject: [PATCH 385/637] OPL: better 4-op channel naming --- src/engine/sysDef.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 95e1a7b05..bcfbf1c63 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -924,15 +924,15 @@ const char* chanNames[40][32]={ {"Pulse 1", "Pulse 2", "PCM"}, // MMC5 {"FM 1", "FM 2", "FM 3", "PSG 1", "PSG 2", "PSG 3"}, // OPN {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Square 1", "Square 2", "Square 3", "Kick", "Snare", "Top", "HiHat", "Tom", "Rim", "ADPCM"}, // PC-98 - {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8", "FM 9", "FM 10", "FM 11", "FM 12", "FM 13", "FM 14", "FM 15", "FM 16", "FM 17", "FM 18"}, // OPL3 + {"4OP 1", "FM 2", "4OP 3", "FM 4", "4OP 5", "FM 6", "4OP 7", "FM 8", "4OP 9", "FM 10", "4OP 11", "FM 12", "FM 13", "FM 14", "FM 15", "FM 16", "FM 17", "FM 18"}, // OPL3 {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "Channel 17", "Channel 18", "Channel 19", "Channel 20", "Channel 21", "Channel 22", "Channel 23", "Channel 24", "Channel 25", "Channel 26", "Channel 27", "Channel 28"}, // MultiPCM {"Square"}, // PC Speaker/Pokémon Mini {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Noise"}, // Virtual Boy/SCC {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6", "ADPCM-B"}, // YM2610B {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Kick", "Snare", "Tom", "Top", "HiHat"}, // OPLL/OPL/OPL2 drums - {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8", "FM 9", "FM 10", "FM 11", "FM 12", "FM 13", "FM 14", "FM 15", "Kick", "Snare", "Tom", "Top", "HiHat"}, // OPL3 drums - {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "4OP 1", "4OP 2", "4OP 3", "4OP 4", "4OP 5", "4OP 6"}, // OPL3 4-op - {"FM 1", "FM 2", "FM 3", "4OP 1", "4OP 2", "4OP 3", "4OP 4", "4OP 5", "4OP 6", "Kick", "Snare", "Tom", "Top", "HiHat"}, // OPL3 4-op + drums + {"4OP 1", "FM 2", "4OP 3", "FM 4", "4OP 5", "FM 6", "4OP 7", "FM 8", "4OP 9", "FM 10", "4OP 11", "FM 12", "FM 13", "FM 14", "FM 15", "Kick", "Snare", "Tom", "Top", "HiHat"}, // OPL3 drums + {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "4OP 1", "4OP 2", "4OP 3", "4OP 4", "4OP 5", "4OP 6"}, // OPL3 4-op (UNUSED) + {"FM 1", "FM 2", "FM 3", "4OP 1", "4OP 2", "4OP 3", "4OP 4", "4OP 5", "4OP 6", "Kick", "Snare", "Tom", "Top", "HiHat"}, // OPL3 4-op + drums (UNUSED) {"PCM 1", "PCM 2", "PCM 3", "PCM 4", "PCM 5", "PCM 6", "PCM 7", "PCM 8", "PCM 9", "PCM 10", "PCM 11", "PCM 12", "PCM 13", "PCM 14", "PCM 15", "PCM 16", "ADPCM 1", "ADPCM 2", "ADPCM 3"}, // QSound {"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6", "ADPCM-B"}, // YM2610B (extended channel 3) {"Wave", "Wave/PCM", "Wave", "Wave/Noise"}, // Swan @@ -974,8 +974,8 @@ const char* chanShortNames[38][32]={ {"F1", "F2", "F3", "F4", "F5", "F6", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"}, // YM2610B {"F1", "F2", "F3", "F4", "F5", "F6", "BD", "SD", "TM", "TP", "HH"}, // OPLL/OPL/OPL2 drums {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "BD", "SD", "TM", "TP", "HH"}, // OPL3 drums - {"F1", "F2", "F3", "F4", "F5", "F6", "Q1", "Q2", "Q3", "Q4", "Q5", "Q6"}, // OPL3 4-op - {"F1", "F2", "F3", "Q1", "Q2", "Q3", "Q4", "Q5", "Q6", "BD", "SD", "TM", "TP", "HH"}, // OPL3 4-op + drums + {"F1", "F2", "F3", "F4", "F5", "F6", "Q1", "Q2", "Q3", "Q4", "Q5", "Q6"}, // OPL3 4-op (UNUSED) + {"F1", "F2", "F3", "Q1", "Q2", "Q3", "Q4", "Q5", "Q6", "BD", "SD", "TM", "TP", "HH"}, // OPL3 4-op + drums (UNUSED) {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "A1", "A2", "A3"}, // QSound {"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"}, // YM2610B (extended channel 3) }; @@ -1008,15 +1008,15 @@ const int chanTypes[41][32]={ {1, 1, 4}, // MMC5 {0, 0, 0, 1, 1, 1}, // OPN {0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 4}, // PC-98 - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // OPL3 + {5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 0, 0, 0, 0, 0, 0}, // OPL3 {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, // MultiPCM/QSound {1}, // PC Speaker/Pokémon Mini {3, 3, 3, 3, 3, 2}, // Virtual Boy/SCC {0, 0, 0, 0, 0, 0, 1, 1, 1, 4, 4, 4, 4, 4, 4, 4}, // YM2610B {0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2}, // OPLL/OPL/OPL2 drums - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2}, // OPL3 drums - {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // OPL3 4-op - {0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2}, // OPL3 4-op + drums + {5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 0, 0, 0, 2, 2, 2, 2, 2}, // OPL3 drums + {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // OPL3 4-op (UNUSED) + {0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2}, // OPL3 4-op + drums (UNUSED) {3, 3, 3, 3}, // Lynx {0, 0, 5, 5, 5, 5, 0, 0, 0, 1, 1, 1, 4, 4, 4, 4, 4, 4, 4}, // YM2610B (extended channel 3) {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4}, // VERA From afc701b0b9c1cbe43fdeaf6efab124b09fae0f36 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 23 Mar 2022 00:42:59 -0500 Subject: [PATCH 386/637] GUI: sample edit undo/redo! it seems to work but if you find bugs/crashes tell me --- src/engine/sample.cpp | 68 ++++++++++++++++++++++++++++++++++-------- src/engine/sample.h | 3 +- src/gui/doAction.cpp | 26 ++++++++++++++-- src/gui/gui.h | 3 ++ src/gui/sampleEdit.cpp | 34 +++++++++++++++++++-- 5 files changed, 116 insertions(+), 18 deletions(-) diff --git a/src/engine/sample.cpp b/src/engine/sample.cpp index d5e1844c8..5fab8b2b7 100644 --- a/src/engine/sample.cpp +++ b/src/engine/sample.cpp @@ -790,7 +790,7 @@ unsigned int DivSample::getCurBufLen() { return 0; } -DivSampleHistory* DivSample::prepareUndo(bool data) { +DivSampleHistory* DivSample::prepareUndo(bool data, bool doNotPush) { DivSampleHistory* h; if (data) { unsigned char* duplicate; @@ -804,33 +804,77 @@ DivSampleHistory* DivSample::prepareUndo(bool data) { } else { h=new DivSampleHistory(depth,rate,centerRate,loopStart); } - while (!redoHist.empty()) { - DivSampleHistory* h=redoHist.front(); - delete h; - redoHist.pop_front(); + if (!doNotPush) { + while (!redoHist.empty()) { + DivSampleHistory* h=redoHist.back(); + delete h; + redoHist.pop_back(); + } + if (undoHist.size()>100) undoHist.pop_front(); + undoHist.push_back(h); } - undoHist.push_back(h); return h; } +#define applyHistory \ + depth=h->depth; \ + if (h->hasSample) { \ + initInternal(h->depth,h->samples); \ + samples=h->samples; \ +\ + if (h->length!=getCurBufLen()) logW("undo buffer length not equal to current buffer length! %d != %d\n",h->length,getCurBufLen()); \ +\ + void* buf=getCurBuf(); \ +\ + if (buf!=NULL && h->data!=NULL) { \ + memcpy(buf,h->data,h->length); \ + } \ + } \ + rate=h->rate; \ + centerRate=h->centerRate; \ + loopStart=h->loopStart; + + int DivSample::undo() { - return 0; + if (undoHist.empty()) return 0; + DivSampleHistory* h=undoHist.back(); + DivSampleHistory* redo=prepareUndo(h->hasSample,true); + + int ret=h->hasSample?2:1; + + applyHistory; + + redoHist.push_back(redo); + delete h; + undoHist.pop_back(); + return ret; } int DivSample::redo() { - return 0; + if (redoHist.empty()) return 0; + DivSampleHistory* h=redoHist.back(); + DivSampleHistory* undo=prepareUndo(h->hasSample,true); + + int ret=h->hasSample?2:1; + + applyHistory; + + undoHist.push_back(undo); + delete h; + redoHist.pop_back(); + return ret; } DivSample::~DivSample() { while (!undoHist.empty()) { - DivSampleHistory* h=undoHist.front(); + DivSampleHistory* h=undoHist.back(); delete h; - undoHist.pop_front(); + undoHist.pop_back(); } while (!redoHist.empty()) { - DivSampleHistory* h=redoHist.front(); + DivSampleHistory* h=redoHist.back(); delete h; - redoHist.pop_front(); + redoHist.pop_back(); } if (data8) delete[] data8; if (data16) delete[] data16; diff --git a/src/engine/sample.h b/src/engine/sample.h index f07c7ef91..1a9e53d32 100644 --- a/src/engine/sample.h +++ b/src/engine/sample.h @@ -189,9 +189,10 @@ struct DivSample { /** * prepare an undo step for this sample. * @param data whether to include sample data. + * @param doNotPush if this is true, don't push the DivSampleHistory to the undo history. * @return the undo step. */ - DivSampleHistory* prepareUndo(bool data); + DivSampleHistory* prepareUndo(bool data, bool doNotPush=false); /** * undo. you may need to call DivEngine::renderSamples afterwards. diff --git a/src/gui/doAction.cpp b/src/gui/doAction.cpp index 66a05fb3a..3866624ac 100644 --- a/src/gui/doAction.cpp +++ b/src/gui/doAction.cpp @@ -55,10 +55,18 @@ void FurnaceGUI::doAction(int what) { openFileDialog(GUI_FILE_SAVE); break; case GUI_ACTION_UNDO: - doUndo(); + if (curWindow==GUI_WINDOW_SAMPLE_EDIT) { + doUndoSample(); + } else { + doUndo(); + } break; case GUI_ACTION_REDO: - doRedo(); + if (curWindow==GUI_WINDOW_SAMPLE_EDIT) { + doRedoSample(); + } else { + doRedo(); + } break; case GUI_ACTION_PLAY_TOGGLE: if (e->isPlaying() && !e->isStepping()) { @@ -594,6 +602,8 @@ void FurnaceGUI::doAction(int what) { if (end-start<1) break; + sample->prepareUndo(true); + if (sampleClipboard!=NULL) { delete[] sampleClipboard; } @@ -632,6 +642,7 @@ void FurnaceGUI::doAction(int what) { if (curSample<0 || curSample>=(int)e->song.sample.size()) break; if (sampleClipboard==NULL || sampleClipboardLen<1) break; DivSample* sample=e->song.sample[curSample]; + sample->prepareUndo(true); int pos=(sampleSelStart==-1 || sampleSelStart==sampleSelEnd)?sample->samples:sampleSelStart; e->lockEngine([this,sample,pos]() { @@ -658,6 +669,7 @@ void FurnaceGUI::doAction(int what) { if (curSample<0 || curSample>=(int)e->song.sample.size()) break; if (sampleClipboard==NULL || sampleClipboardLen<1) break; DivSample* sample=e->song.sample[curSample]; + sample->prepareUndo(true); int pos=(sampleSelStart==-1 || sampleSelStart==sampleSelEnd)?0:sampleSelStart; e->lockEngine([this,sample,pos]() { @@ -685,6 +697,7 @@ void FurnaceGUI::doAction(int what) { if (curSample<0 || curSample>=(int)e->song.sample.size()) break; if (sampleClipboard==NULL || sampleClipboardLen<1) break; DivSample* sample=e->song.sample[curSample]; + sample->prepareUndo(true); int pos=(sampleSelStart==-1 || sampleSelStart==sampleSelEnd)?0:sampleSelStart; e->lockEngine([this,sample,pos]() { @@ -737,6 +750,7 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_SAMPLE_NORMALIZE: { if (curSample<0 || curSample>=(int)e->song.sample.size()) break; DivSample* sample=e->song.sample[curSample]; + sample->prepareUndo(true); e->lockEngine([this,sample]() { SAMPLE_OP_BEGIN; float maxVal=0.0f; @@ -783,6 +797,7 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_SAMPLE_FADE_IN: { if (curSample<0 || curSample>=(int)e->song.sample.size()) break; DivSample* sample=e->song.sample[curSample]; + sample->prepareUndo(true); e->lockEngine([this,sample]() { SAMPLE_OP_BEGIN; @@ -812,6 +827,7 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_SAMPLE_FADE_OUT: { if (curSample<0 || curSample>=(int)e->song.sample.size()) break; DivSample* sample=e->song.sample[curSample]; + sample->prepareUndo(true); e->lockEngine([this,sample]() { SAMPLE_OP_BEGIN; @@ -845,6 +861,7 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_SAMPLE_SILENCE: { if (curSample<0 || curSample>=(int)e->song.sample.size()) break; DivSample* sample=e->song.sample[curSample]; + sample->prepareUndo(true); e->lockEngine([this,sample]() { SAMPLE_OP_BEGIN; @@ -868,6 +885,7 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_SAMPLE_DELETE: { if (curSample<0 || curSample>=(int)e->song.sample.size()) break; DivSample* sample=e->song.sample[curSample]; + sample->prepareUndo(true); e->lockEngine([this,sample]() { SAMPLE_OP_BEGIN; @@ -884,6 +902,7 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_SAMPLE_TRIM: { if (curSample<0 || curSample>=(int)e->song.sample.size()) break; DivSample* sample=e->song.sample[curSample]; + sample->prepareUndo(true); e->lockEngine([this,sample]() { SAMPLE_OP_BEGIN; @@ -900,6 +919,7 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_SAMPLE_REVERSE: { if (curSample<0 || curSample>=(int)e->song.sample.size()) break; DivSample* sample=e->song.sample[curSample]; + sample->prepareUndo(true); e->lockEngine([this,sample]() { SAMPLE_OP_BEGIN; @@ -931,6 +951,7 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_SAMPLE_INVERT: { if (curSample<0 || curSample>=(int)e->song.sample.size()) break; DivSample* sample=e->song.sample[curSample]; + sample->prepareUndo(true); e->lockEngine([this,sample]() { SAMPLE_OP_BEGIN; @@ -956,6 +977,7 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_SAMPLE_SIGN: { if (curSample<0 || curSample>=(int)e->song.sample.size()) break; DivSample* sample=e->song.sample[curSample]; + sample->prepareUndo(true); e->lockEngine([this,sample]() { SAMPLE_OP_BEGIN; diff --git a/src/gui/gui.h b/src/gui/gui.h index a4feed9b5..315ce7421 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -897,6 +897,9 @@ class FurnaceGUI { void doRedo(); void editOptions(bool topMenu); + void doUndoSample(); + void doRedoSample(); + void play(int row=0); void stop(); diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index 3bdfa6c9f..0998856cc 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -63,14 +63,12 @@ void FurnaceGUI::drawSampleEdit() { for (int i=0; i<17; i++) { if (sampleDepths[i]==NULL) continue; if (ImGui::Selectable(sampleDepths[i])) { + sample->prepareUndo(true); sample->depth=i; e->renderSamplesP(); updateSampleTex=true; MARK_MODIFIED; } - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("no undo for sample type change operations!"); - } } ImGui::EndCombo(); } @@ -163,6 +161,7 @@ void FurnaceGUI::drawSampleEdit() { if (resizeSize>16777215) resizeSize=16777215; } if (ImGui::Button("Resize")) { + sample->prepareUndo(true); e->lockEngine([this,sample]() { if (!sample->resize(resizeSize)) { showError("couldn't resize! make sure your sample is 8 or 16-bit."); @@ -217,6 +216,7 @@ void FurnaceGUI::drawSampleEdit() { } ImGui::Combo("Filter",&resampleStrat,resampleStrats,6); if (ImGui::Button("Resample")) { + sample->prepareUndo(true); e->lockEngine([this,sample]() { if (!sample->resample(resampleTarget,resampleStrat)) { showError("couldn't resample! make sure your sample is 8 or 16-bit."); @@ -253,6 +253,7 @@ void FurnaceGUI::drawSampleEdit() { ImGui::SameLine(); ImGui::Text("(%.1fdB)",20.0*log10(amplifyVol/100.0f)); if (ImGui::Button("Apply")) { + sample->prepareUndo(true); e->lockEngine([this,sample]() { SAMPLE_OP_BEGIN; float vol=amplifyVol/100.0f; @@ -319,6 +320,7 @@ void FurnaceGUI::drawSampleEdit() { } if (ImGui::Button("Resize")) { int pos=(sampleSelStart==-1 || sampleSelStart==sampleSelEnd)?sample->samples:sampleSelStart; + sample->prepareUndo(true); e->lockEngine([this,sample,pos]() { if (!sample->insert(pos,silenceSize)) { showError("couldn't insert! make sure your sample is 8 or 16-bit."); @@ -437,6 +439,7 @@ void FurnaceGUI::drawSampleEdit() { } if (ImGui::Button("Apply")) { + sample->prepareUndo(true); e->lockEngine([this,sample]() { SAMPLE_OP_BEGIN; float res=1.0-pow(sampleFilterRes,0.5f); @@ -665,6 +668,7 @@ void FurnaceGUI::drawSampleEdit() { sampleDragActive=true; sampleSelStart=-1; sampleSelEnd=-1; + if (sampleDragMode) sample->prepareUndo(true); processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); } } @@ -827,3 +831,27 @@ void FurnaceGUI::drawSampleEdit() { if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_SAMPLE_EDIT; ImGui::End(); } + +void FurnaceGUI::doUndoSample() { + if (!sampleEditOpen) return; + if (curSample<0 || curSample>=(int)e->song.sample.size()) return; + DivSample* sample=e->song.sample[curSample]; + e->lockEngine([this,sample]() { + if (sample->undo()==2) { + e->renderSamples(); + updateSampleTex=true; + } + }); +} + +void FurnaceGUI::doRedoSample() { + if (!sampleEditOpen) return; + if (curSample<0 || curSample>=(int)e->song.sample.size()) return; + DivSample* sample=e->song.sample[curSample]; + e->lockEngine([this,sample]() { + if (sample->redo()==2) { + e->renderSamples(); + updateSampleTex=true; + } + }); +} \ No newline at end of file From ac79e7d6af862b511623f8640d08f51eaa80d1c6 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 23 Mar 2022 01:35:57 -0500 Subject: [PATCH 387/637] add broken speed alternation flag - dev70 --- papers/format.md | 4 ++++ src/engine/engine.h | 4 ++-- src/engine/fileOps.cpp | 15 +++++++++++++++ src/engine/playback.cpp | 22 ++++++++++++++++------ src/engine/song.h | 4 +++- src/gui/compatFlags.cpp | 4 ++++ 6 files changed, 44 insertions(+), 9 deletions(-) diff --git a/papers/format.md b/papers/format.md index 51dc8dd93..af2c4a5c3 100644 --- a/papers/format.md +++ b/papers/format.md @@ -29,6 +29,7 @@ furthermore, an `or reserved` indicates this field is always present, but is res the format versions are: +- 70: Furnace dev70 - 69: Furnace dev69 - 68: Furnace dev68 - 67: Furnace dev67 @@ -235,6 +236,9 @@ size | description STR | song comment 4f | master volume, 1.0f=100% (>=59) | this is 2.0f for modules before 59 + --- | **extended compatibility flags** (>=70) + 1 | broken speed selection + 31 | reserved ``` # instrument diff --git a/src/engine/engine.h b/src/engine/engine.h index 9f7464e37..911a33c7c 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -38,8 +38,8 @@ warnings+=(String("\n")+x); \ } -#define DIV_VERSION "dev69" -#define DIV_ENGINE_VERSION 69 +#define DIV_VERSION "dev70" +#define DIV_ENGINE_VERSION 70 // for imports #define DIV_VERSION_MOD 0xff01 diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index b50eb74ae..f267e2c23 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -144,6 +144,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { ds.oneTickCut=false; ds.newInsTriggersInPorta=true; ds.arp0Reset=true; + ds.brokenSpeedSel=true; // 1.1 compat flags if (ds.version>24) { @@ -1067,6 +1068,14 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { ds.masterVol=2.0f; } + if (ds.version>=70) { + // extended compat flags + ds.brokenSpeedSel=reader.readC(); + for (int i=0; i<31; i++) { + reader.readC(); + } + } + // read instruments for (int i=0; iwriteF(song.masterVol); + // extended compat flags + w->writeC(song.brokenSpeedSel); + for (int i=0; i<31; i++) { + w->writeC(0); + } + /// INSTRUMENT for (int i=0; i ins; @@ -373,7 +374,8 @@ struct DivSong { brokenDACMode(false), oneTickCut(false), newInsTriggersInPorta(true), - arp0Reset(true) { + arp0Reset(true), + brokenSpeedSel(false) { for (int i=0; i<32; i++) { system[i]=DIV_SYSTEM_NULL; systemVol[i]=64; diff --git a/src/gui/compatFlags.cpp b/src/gui/compatFlags.cpp index 46424fc52..b49c251f8 100644 --- a/src/gui/compatFlags.cpp +++ b/src/gui/compatFlags.cpp @@ -81,6 +81,10 @@ void FurnaceGUI::drawCompatFlags() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("when enabled, a one-tick note cut will be inserted between non-legato/non-portamento notes.\nthis simulates the behavior of some Amiga/SNES music engines."); } + ImGui::Checkbox("Broken speed alternation",&e->song.brokenSpeedSel); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("determines next speed based on whether the row is odd/even instead of alternating between speeds."); + } ImGui::Text("Loop modality:"); if (ImGui::RadioButton("Reset channels",e->song.loopModality==0)) { From df8f40486dd58c9123d2d211a527c40fe750f473 Mon Sep 17 00:00:00 2001 From: cam900 Date: Thu, 24 Mar 2022 03:53:07 +0900 Subject: [PATCH 388/637] Fix frequency, Loading waveform, Add instrument tab for waveform initialize now for saving DivInstrumentN163 struct is... needs to compatibility breaks? --- papers/doc/4-instrument/n163.md | 11 +++++++++- src/engine/instrument.cpp | 3 +++ src/engine/instrument.h | 12 +++++++++++ src/engine/platform/n163.cpp | 36 +++++++++++++++++++++++---------- src/gui/insEdit.cpp | 29 ++++++++++++++++++++++++++ src/gui/intConst.cpp | 1 + src/gui/intConst.h | 1 + 7 files changed, 81 insertions(+), 12 deletions(-) diff --git a/papers/doc/4-instrument/n163.md b/papers/doc/4-instrument/n163.md index ccf2c46e5..0bd914453 100644 --- a/papers/doc/4-instrument/n163.md +++ b/papers/doc/4-instrument/n163.md @@ -1,7 +1,16 @@ # Namco 163 instrument editor -Namco 163 instrument editor consists of 10 macros. +Namco 163 instrument editor consists of two tabs: one controlling various parameters for waveform initialize and macro tab containing 10 macros. +## N163 +- [Initial Waveform] - Determines the initial waveform for playing. +- [Initial Waveform position in RAM] - Determines the initial waveform position will be load to RAM. +- [Initial Waveform length in RAM] - Determines the initial waveform length will be load to RAM. +- [Load waveform before playback] - Determines the load initial waveform into RAM before playback. +- [Update waveforms into RAM when every waveform changes] - Determines the update every different waveform changes in playback. + + +## Macros - [Volume] - volume levels sequence - [Arpeggio]- pitch sequence - [Waveform pos.] - sets the waveform source address in RAM for playback (single nibble unit) diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index 785c9b19d..41ccea399 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -385,6 +385,8 @@ void DivInstrument::putInsData(SafeWriter* w) { w->write(amiga.noteFreq,120*sizeof(unsigned int)); w->write(amiga.noteMap,120*sizeof(short)); } + + // N163 } DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) { @@ -733,6 +735,7 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) { } } + // N163 return DIV_DATA_SUCCESS; } diff --git a/src/engine/instrument.h b/src/engine/instrument.h index ea52e6d8a..a69800a1f 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -385,6 +385,17 @@ struct DivInstrumentAmiga { } }; +struct DivInstrumentN163 { + int wave, wavePos, waveLen; + unsigned char waveMode; + + DivInstrumentN163(): + wave(-1), + wavePos(0), + waveLen(0), + waveMode(0) {} +}; + struct DivInstrument { String name; bool mode; @@ -394,6 +405,7 @@ struct DivInstrument { DivInstrumentGB gb; DivInstrumentC64 c64; DivInstrumentAmiga amiga; + DivInstrumentN163 n163; /** * save the instrument to a SafeWriter. diff --git a/src/engine/platform/n163.cpp b/src/engine/platform/n163.cpp index 9d22bd27a..42b55e907 100644 --- a/src/engine/platform/n163.cpp +++ b/src/engine/platform/n163.cpp @@ -172,10 +172,13 @@ void DivPlatformN163::acquire(short* bufL, short* bufR, size_t start, size_t len } void DivPlatformN163::updateWave(int wave, int pos, int len) { - len=MAX(0,MIN(((0x78-(chanMax<<3))<<1)-pos,len)); // avoid conflict with channel register area + len&=0xfc; // 4 nibble boundary DivWavetable* wt=parent->getWave(wave); for (int i=0; i=((0x78-(chanMax<<3))<<1)) { // avoid conflict with channel register area + break; + } unsigned char mask=(addr&1)?0xf0:0x0f; if (wt->max<1 || wt->len<1) { rWriteMask(addr>>1,0,mask); @@ -253,16 +256,16 @@ void DivPlatformN163::tick() { } } if (chan[i].std.hadEx2) { - if ((chan[i].waveMode&0x1)!=(chan[i].std.ex2&0x1)) { // update waveform now - chan[i].waveMode=(chan[i].waveMode&~0x1)|(chan[i].std.ex2&0x1); - if (chan[i].waveMode&0x1) { // rising edge + if ((chan[i].waveMode&0x2)!=(chan[i].std.ex2&0x2)) { // update when every waveform changed + chan[i].waveMode=(chan[i].waveMode&~0x2)|(chan[i].std.ex2&0x2); + if (chan[i].waveMode&0x2) { chan[i].waveUpdated=true; chan[i].waveChanged=true; } } - if ((chan[i].waveMode&0x2)!=(chan[i].std.ex2&0x2)) { // update when every waveform changed - chan[i].waveMode=(chan[i].waveMode&~0x2)|(chan[i].std.ex2&0x2); - if (chan[i].waveMode&0x2) { + if ((chan[i].waveMode&0x1)!=(chan[i].std.ex2&0x1)) { // update waveform now + chan[i].waveMode=(chan[i].waveMode&~0x1)|(chan[i].std.ex2&0x1); + if (chan[i].waveMode&0x1) { // rising edge chan[i].waveUpdated=true; chan[i].waveChanged=true; } @@ -287,15 +290,15 @@ void DivPlatformN163::tick() { } } if (chan[i].std.hadFms) { + if ((chan[i].loadMode&0x2)!=(chan[i].std.fms&0x2)) { // load when every waveform changes + chan[i].loadMode=(chan[i].loadMode&~0x2)|(chan[i].std.fms&0x2); + } if ((chan[i].loadMode&0x1)!=(chan[i].std.fms&0x1)) { // load now chan[i].loadMode=(chan[i].loadMode&~0x1)|(chan[i].std.fms&0x1); if (chan[i].loadMode&0x1) { // rising edge updateWave(chan[i].loadWave,chan[i].loadPos,chan[i].loadLen&0xfc); } } - if ((chan[i].loadMode&0x2)!=(chan[i].std.fms&0x2)) { // load when every waveform changes - chan[i].loadMode=(chan[i].loadMode&~0x2)|(chan[i].std.fms&0x2); - } } if (chan[i].volumeChanged) { if ((!chan[i].active) || isMuted[i]) { @@ -316,7 +319,7 @@ void DivPlatformN163::tick() { chan[i].waveUpdated=false; } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { - chan[i].freq=parent->calcFreq(chan[i].baseFreq*(chan[i].waveLen/16)*(chanMax+1),chan[i].pitch,false,0); + chan[i].freq=parent->calcFreq((((chan[i].baseFreq*chan[i].waveLen)*(chanMax+1))/16),chan[i].pitch,false,0); if (chan[i].freq<0) chan[i].freq=0; if (chan[i].freq>0x3ffff) chan[i].freq=0x3ffff; if (chan[i].keyOn) { @@ -344,6 +347,17 @@ int DivPlatformN163::dispatch(DivCommand c) { switch (c.cmd) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(chan[c.chan].ins); + if (chan[c.chan].insChanged) { + chan[c.chan].wave=ins->n163.wave; + chan[c.chan].wavePos=ins->n163.wavePos; + chan[c.chan].waveLen=ins->n163.waveLen; + chan[c.chan].waveMode=ins->n163.waveMode; + chan[c.chan].waveChanged=true; + if (chan[c.chan].waveMode&0x3) { + chan[c.chan].waveUpdated=true; + } + chan[c.chan].insChanged=false; + } if (c.value!=DIV_NOTE_NULL) { chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); chan[c.chan].freqChanged=true; diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 24533c8d8..b26ea5345 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1522,6 +1522,35 @@ void FurnaceGUI::drawInsEdit() { } ImGui::EndTabItem(); } + if (ins->type==DIV_INS_N163) if (ImGui::BeginTabItem("N163")) { + ImGui::Text("Initial waveform"); + if (ImGui::InputInt("##WAVE",&ins->n163.wave,1,10)) { PARAMETER + if (ins->n163.wave<0) ins->n163.wave=0; + if (ins->n163.wave>=e->song.waveLen) ins->n163.wave=e->song.waveLen-1; + } + ImGui::Text("Initial waveform position in RAM"); + if (ImGui::InputInt("##WAVEPOS",&ins->n163.wavePos,1,16)) { PARAMETER + if (ins->n163.wavePos<0) ins->n163.wavePos=0; + if (ins->n163.wavePos>255) ins->n163.wavePos=255; + } + ImGui::Text("Initial waveform length in RAM"); + if (ImGui::InputInt("##WAVELEN",&ins->n163.waveLen,4,16)) { PARAMETER + if (ins->n163.waveLen<0) ins->n163.waveLen=0; + if (ins->n163.waveLen>252) ins->n163.waveLen=252; + ins->n163.waveLen&=0xfc; + } + + bool preLoad=ins->n163.waveMode&0x1; + if (ImGui::Checkbox("Load waveform before playback",&preLoad)) { PARAMETER + ins->n163.waveMode=(ins->n163.waveMode&~0x1)|(preLoad?0x1:0); + } + bool waveMode=ins->n163.waveMode&0x2; + if (ImGui::Checkbox("Update waveforms into RAM when every waveform changes",&waveMode)) { PARAMETER + ins->n163.waveMode=(ins->n163.waveMode&~0x2)|(waveMode?0x2:0); + } + + ImGui::EndTabItem(); + } if (ImGui::BeginTabItem("Macros")) { float asFloat[256]; int asInt[256]; diff --git a/src/gui/intConst.cpp b/src/gui/intConst.cpp index dae78ff10..9c7f53b94 100644 --- a/src/gui/intConst.cpp +++ b/src/gui/intConst.cpp @@ -29,6 +29,7 @@ const int _THIRTY_ONE=31; const int _SIXTY_FOUR=64; const int _ONE_HUNDRED=100; const int _ONE_HUNDRED_TWENTY_SEVEN=127; +const int _TWO_HUNDRED_FIFTY_FIVE=255; const int _TWO_THOUSAND_FORTY_SEVEN=2047; const int _FOUR_THOUSAND_NINETY_FIVE=4095; const int _MINUS_ONE_HUNDRED_TWENTY_SEVEN=-127; diff --git a/src/gui/intConst.h b/src/gui/intConst.h index 2618ec652..c6b13b9af 100644 --- a/src/gui/intConst.h +++ b/src/gui/intConst.h @@ -31,6 +31,7 @@ extern const int _THIRTY_ONE; extern const int _SIXTY_FOUR; extern const int _ONE_HUNDRED; extern const int _ONE_HUNDRED_TWENTY_SEVEN; +extern const int _TWO_HUNDRED_FIFTY_FIVE; extern const int _TWO_THOUSAND_FORTY_SEVEN; extern const int _FOUR_THOUSAND_NINETY_FIVE; extern const int _MINUS_ONE_HUNDRED_TWENTY_SEVEN; From 5a08e0d230786dc8442a624c1e2c552a611b16b1 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 23 Mar 2022 15:50:18 -0500 Subject: [PATCH 389/637] OPLL: add patch macro --- src/engine/platform/opll.cpp | 7 +++++++ src/gui/insEdit.cpp | 11 ++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/engine/platform/opll.cpp b/src/engine/platform/opll.cpp index fd1d61d62..4f21ba212 100644 --- a/src/engine/platform/opll.cpp +++ b/src/engine/platform/opll.cpp @@ -138,6 +138,13 @@ void DivPlatformOPLL::tick() { } } + if (chan[i].std.hadWave && chan[i].state.opllPreset!=16) { + chan[i].state.opllPreset=chan[i].std.wave; + if (i<9) { + rWrite(0x30+i,((15-(chan[i].outVol*(15-chan[i].state.op[1].tl))/15)&15)|(chan[i].state.opllPreset<<4)); + } + } + if (chan[i].state.opllPreset==0) { if (chan[i].std.hadAlg) { // SUS chan[i].state.alg=chan[i].std.alg; diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 21e7534bc..d77a4632e 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1603,22 +1603,27 @@ void FurnaceGUI::drawInsEdit() { } bool dutyIsRel=(ins->type==DIV_INS_C64 && !ins->c64.dutyIsAbs); + const char* waveLabel="Waveform"; int waveMax=(ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_VERA)?3:63; bool bitMode=false; if (ins->type==DIV_INS_C64 || ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_SAA1099) { bitMode=true; } if (ins->type==DIV_INS_STD) waveMax=0; - if (ins->type==DIV_INS_TIA || ins->type==DIV_INS_VIC) waveMax=15; + if (ins->type==DIV_INS_TIA || ins->type==DIV_INS_VIC || ins->type==DIV_INS_OPLL) waveMax=15; if (ins->type==DIV_INS_C64) waveMax=4; if (ins->type==DIV_INS_SAA1099) waveMax=2; - if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPLL || ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPZ) waveMax=0; + if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPZ) waveMax=0; if (ins->type==DIV_INS_MIKEY) waveMax=0; if (ins->type==DIV_INS_PET) { waveMax=8; bitMode=true; } + if (ins->type==DIV_INS_OPLL) { + waveLabel="Patch"; + } + const char** waveNames=NULL; if (ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_SAA1099) waveNames=ayShapeBits; if (ins->type==DIV_INS_C64) waveNames=c64ShapeBits; @@ -1654,7 +1659,7 @@ void FurnaceGUI::drawInsEdit() { } } if (waveMax>0) { - NORMAL_MACRO(ins->std.waveMacro,ins->std.waveMacroLen,ins->std.waveMacroLoop,ins->std.waveMacroRel,0,waveMax,"wave","Waveform",bitMode?64:160,ins->std.waveMacroOpen,bitMode,waveNames,false,NULL,0,0,((ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930)?1:0),NULL,uiColors[GUI_COLOR_MACRO_WAVE],mmlString[3],0,waveMax,NULL,false); + NORMAL_MACRO(ins->std.waveMacro,ins->std.waveMacroLen,ins->std.waveMacroLoop,ins->std.waveMacroRel,0,waveMax,"wave",waveLabel,bitMode?64:160,ins->std.waveMacroOpen,bitMode,waveNames,false,NULL,0,0,((ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930)?1:0),NULL,uiColors[GUI_COLOR_MACRO_WAVE],mmlString[3],0,waveMax,NULL,false); } if (ex1Max>0) { if (ins->type==DIV_INS_C64) { From 6492eeff569542aac183bb21a673ebdbca5dbbc8 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 23 Mar 2022 16:39:08 -0500 Subject: [PATCH 390/637] GUI: proper sample errors --- src/engine/engine.cpp | 10 ++++++---- src/engine/engine.h | 2 +- src/gui/gui.cpp | 7 +++++-- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index fb11f1df6..a634bd3c2 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -2142,18 +2142,20 @@ int DivEngine::addSample() { return sampleCount; } -bool DivEngine::addSampleFromFile(const char* path) { +int DivEngine::addSampleFromFile(const char* path) { isBusy.lock(); SF_INFO si; SNDFILE* f=sf_open(path,SFM_READ,&si); if (f==NULL) { isBusy.unlock(); - return false; + lastError=fmt::sprintf("could not open file! (%s)",sf_error_number(sf_error(NULL))); + return -1; } - if (si.frames>1000000) { + if (si.frames>16777215) { + lastError="this sample is too big! max sample size is 16777215."; sf_close(f); isBusy.unlock(); - return false; + return -1; } short* buf=new short[si.channels*si.frames]; if (sf_readf_short(f,buf,si.frames)!=si.frames) { diff --git a/src/engine/engine.h b/src/engine/engine.h index 911a33c7c..af4793324 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -489,7 +489,7 @@ class DivEngine { int addSample(); // add sample from file - bool addSampleFromFile(const char* path); + int addSampleFromFile(const char* path); // delete sample void delSample(int index); diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 9f869e57d..7cbf10970 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2269,8 +2269,11 @@ bool FurnaceGUI::loop() { } break; case GUI_FILE_SAMPLE_OPEN: - e->addSampleFromFile(copyOfName.c_str()); - MARK_MODIFIED; + if (e->addSampleFromFile(copyOfName.c_str())==-1) { + showError(e->getLastError()); + } else { + MARK_MODIFIED; + } break; case GUI_FILE_SAMPLE_SAVE: if (curSample>=0 && curSample<(int)e->song.sample.size()) { From 11d9ce3f87c97582536a7ea25d74d9c2ddfe5d1d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 23 Mar 2022 17:00:40 -0500 Subject: [PATCH 391/637] what is going on --- src/engine/engine.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index a634bd3c2..bb027ab63 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -2148,7 +2148,12 @@ int DivEngine::addSampleFromFile(const char* path) { SNDFILE* f=sf_open(path,SFM_READ,&si); if (f==NULL) { isBusy.unlock(); - lastError=fmt::sprintf("could not open file! (%s)",sf_error_number(sf_error(NULL))); + int err=sf_error(NULL); + if (err==SF_ERR_SYSTEM) { + lastError=fmt::sprintf("could not open file! (%s %s)",sf_error_number(err),strerror(errno)); + } else { + lastError=fmt::sprintf("could not open file! (%s)",sf_error_number(err)); + } return -1; } if (si.frames>16777215) { From 711b60d454009263e79fe50a7fdf2adae2053753 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 23 Mar 2022 21:38:28 -0500 Subject: [PATCH 392/637] improved mutex locking - less xruns when seeking especially in JACK and macOS --- src/engine/engine.cpp | 252 ++++++++++++++++++++-------------------- src/engine/engine.h | 5 + src/engine/fileOps.cpp | 12 +- src/engine/playback.cpp | 8 +- src/engine/vgmOps.cpp | 4 +- 5 files changed, 147 insertions(+), 134 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index bb027ab63..e0c149752 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -437,25 +437,25 @@ bool DivEngine::haltAudioFile() { } void DivEngine::notifyInsChange(int ins) { - isBusy.lock(); + BUSY_BEGIN; for (int i=0; inotifyInsChange(ins); } - isBusy.unlock(); + BUSY_END; } void DivEngine::notifyWaveChange(int wave) { - isBusy.lock(); + BUSY_BEGIN; for (int i=0; inotifyWaveChange(wave); } - isBusy.unlock(); + BUSY_END; } void DivEngine::renderSamplesP() { - isBusy.lock(); + BUSY_BEGIN; renderSamples(); - isBusy.unlock(); + BUSY_END; } void DivEngine::renderSamples() { @@ -583,7 +583,7 @@ void DivEngine::renderSamples() { void DivEngine::createNew(const int* description) { quitDispatch(); - isBusy.lock(); + BUSY_BEGIN; saveLock.lock(); song.unload(); song=DivSong(); @@ -604,27 +604,27 @@ void DivEngine::createNew(const int* description) { recalcChans(); renderSamples(); saveLock.unlock(); - isBusy.unlock(); + BUSY_END; initDispatch(); - isBusy.lock(); + BUSY_BEGIN; reset(); - isBusy.unlock(); + BUSY_END; } void DivEngine::changeSystem(int index, DivSystem which) { quitDispatch(); - isBusy.lock(); + BUSY_BEGIN; saveLock.lock(); song.system[index]=which; song.systemFlags[index]=0; recalcChans(); saveLock.unlock(); - isBusy.unlock(); + BUSY_END; initDispatch(); - isBusy.lock(); + BUSY_BEGIN; renderSamples(); reset(); - isBusy.unlock(); + BUSY_END; } bool DivEngine::addSystem(DivSystem which) { @@ -638,7 +638,7 @@ bool DivEngine::addSystem(DivSystem which) { return false; } quitDispatch(); - isBusy.lock(); + BUSY_BEGIN; saveLock.lock(); song.system[song.systemLen]=which; song.systemVol[song.systemLen]=64; @@ -646,12 +646,12 @@ bool DivEngine::addSystem(DivSystem which) { song.systemFlags[song.systemLen++]=0; recalcChans(); saveLock.unlock(); - isBusy.unlock(); + BUSY_END; initDispatch(); - isBusy.lock(); + BUSY_BEGIN; renderSamples(); reset(); - isBusy.unlock(); + BUSY_END; return true; } @@ -665,7 +665,7 @@ bool DivEngine::removeSystem(int index) { return false; } quitDispatch(); - isBusy.lock(); + BUSY_BEGIN; saveLock.lock(); song.system[index]=DIV_SYSTEM_NULL; song.systemLen--; @@ -674,27 +674,27 @@ bool DivEngine::removeSystem(int index) { } recalcChans(); saveLock.unlock(); - isBusy.unlock(); + BUSY_END; initDispatch(); - isBusy.lock(); + BUSY_BEGIN; renderSamples(); reset(); - isBusy.unlock(); + BUSY_END; return true; } void DivEngine::poke(int sys, unsigned int addr, unsigned short val) { if (sys<0 || sys>=song.systemLen) return; - isBusy.lock(); + BUSY_BEGIN; disCont[sys].dispatch->poke(addr,val); - isBusy.unlock(); + BUSY_END; } void DivEngine::poke(int sys, std::vector& wlist) { if (sys<0 || sys>=song.systemLen) return; - isBusy.lock(); + BUSY_BEGIN; disCont[sys].dispatch->poke(wlist); - isBusy.unlock(); + BUSY_END; } String DivEngine::getLastError() { @@ -753,13 +753,13 @@ void DivEngine::enableCommandStream(bool enable) { } void DivEngine::getCommandStream(std::vector& where) { - isBusy.lock(); + BUSY_BEGIN; where.clear(); for (DivCommand& i: cmdStream) { where.push_back(i); } cmdStream.clear(); - isBusy.unlock(); + BUSY_END; } void DivEngine::playSub(bool preserveDrift, int goalRow) { @@ -834,7 +834,7 @@ int DivEngine::calcFreq(int base, int pitch, bool period, int octave) { } void DivEngine::play() { - isBusy.lock(); + BUSY_BEGIN_SOFT; sPreview.sample=-1; sPreview.wave=-1; sPreview.pos=0; @@ -847,11 +847,11 @@ void DivEngine::play() { for (int i=0; inotifyPlaybackStop(); } - isBusy.unlock(); + BUSY_END; } void DivEngine::halt() { - isBusy.lock(); + BUSY_BEGIN; halted=true; - isBusy.unlock(); + BUSY_END; } void DivEngine::resume() { - isBusy.lock(); + BUSY_BEGIN; halted=false; haltOn=DIV_HALT_NONE; - isBusy.unlock(); + BUSY_END; } void DivEngine::haltWhen(DivHaltPositions when) { - isBusy.lock(); + BUSY_BEGIN; halted=false; haltOn=when; - isBusy.unlock(); + BUSY_END; } bool DivEngine::isHalted() { @@ -966,9 +968,9 @@ void DivEngine::reset() { } void DivEngine::syncReset() { - isBusy.lock(); + BUSY_BEGIN; reset(); - isBusy.unlock(); + BUSY_END; } const int sampleRates[6]={ @@ -1024,11 +1026,11 @@ int DivEngine::getEffectiveSampleRate(int rate) { } void DivEngine::previewSample(int sample, int note) { - isBusy.lock(); + BUSY_BEGIN; if (sample<0 || sample>=(int)song.sample.size()) { sPreview.sample=-1; sPreview.pos=0; - isBusy.unlock(); + BUSY_END; return; } blip_clear(samp_bb); @@ -1043,26 +1045,26 @@ void DivEngine::previewSample(int sample, int note) { sPreview.pos=0; sPreview.sample=sample; sPreview.wave=-1; - isBusy.unlock(); + BUSY_END; } void DivEngine::stopSamplePreview() { - isBusy.lock(); + BUSY_BEGIN; sPreview.sample=-1; sPreview.pos=0; - isBusy.unlock(); + BUSY_END; } void DivEngine::previewWave(int wave, int note) { - isBusy.lock(); + BUSY_BEGIN; if (wave<0 || wave>=(int)song.wave.size()) { sPreview.wave=-1; sPreview.pos=0; - isBusy.unlock(); + BUSY_END; return; } if (song.wave[wave]->len<=0) { - isBusy.unlock(); + BUSY_END; return; } blip_clear(samp_bb); @@ -1073,14 +1075,14 @@ void DivEngine::previewWave(int wave, int note) { sPreview.pos=0; sPreview.sample=-1; sPreview.wave=wave; - isBusy.unlock(); + BUSY_END; } void DivEngine::stopWavePreview() { - isBusy.lock(); + BUSY_BEGIN; sPreview.wave=-1; sPreview.pos=0; - isBusy.unlock(); + BUSY_END; } String DivEngine::getConfigPath() { @@ -1135,9 +1137,9 @@ bool DivEngine::getRepeatPattern() { } void DivEngine::setRepeatPattern(bool value) { - isBusy.lock(); + BUSY_BEGIN; repeatPattern=value; - isBusy.unlock(); + BUSY_END; } bool DivEngine::hasExtValue() { @@ -1177,7 +1179,7 @@ void DivEngine::toggleSolo(int chan) { } } } - isBusy.lock(); + BUSY_BEGIN; if (!solo) { for (int i=0; imuteChannel(dispatchChanOfChan[chan],isMuted[chan]); } - isBusy.unlock(); + BUSY_END; } void DivEngine::unmuteAll() { - isBusy.lock(); + BUSY_BEGIN; for (int i=0; imuteChannel(dispatchChanOfChan[i],isMuted[i]); } } - isBusy.unlock(); + BUSY_END; } int DivEngine::addInstrument(int refChan) { - isBusy.lock(); + BUSY_BEGIN; DivInstrument* ins=new DivInstrument; int insCount=(int)song.ins.size(); ins->name=fmt::sprintf("Instrument %d",insCount); @@ -1226,7 +1228,7 @@ int DivEngine::addInstrument(int refChan) { song.ins.push_back(ins); song.insLen=insCount+1; saveLock.unlock(); - isBusy.unlock(); + BUSY_END; return insCount; } @@ -1949,18 +1951,18 @@ bool DivEngine::addInstrumentFromFile(const char* path) { } } - isBusy.lock(); + BUSY_BEGIN; saveLock.lock(); int insCount=(int)song.ins.size(); song.ins.push_back(ins); song.insLen=insCount+1; saveLock.unlock(); - isBusy.unlock(); + BUSY_END; return true; } void DivEngine::delInstrument(int index) { - isBusy.lock(); + BUSY_BEGIN; saveLock.lock(); if (index>=0 && index<(int)song.ins.size()) { for (int i=0; i=0 && index<(int)song.wave.size()) { delete song.wave[index]; @@ -2125,11 +2127,11 @@ void DivEngine::delWave(int index) { song.waveLen=song.wave.size(); } saveLock.unlock(); - isBusy.unlock(); + BUSY_END; } int DivEngine::addSample() { - isBusy.lock(); + BUSY_BEGIN; saveLock.lock(); DivSample* sample=new DivSample; int sampleCount=(int)song.sample.size(); @@ -2138,16 +2140,16 @@ int DivEngine::addSample() { song.sampleLen=sampleCount+1; saveLock.unlock(); renderSamples(); - isBusy.unlock(); + BUSY_END; return sampleCount; } int DivEngine::addSampleFromFile(const char* path) { - isBusy.lock(); + BUSY_BEGIN; SF_INFO si; SNDFILE* f=sf_open(path,SFM_READ,&si); if (f==NULL) { - isBusy.unlock(); + BUSY_END; int err=sf_error(NULL); if (err==SF_ERR_SYSTEM) { lastError=fmt::sprintf("could not open file! (%s %s)",sf_error_number(err),strerror(errno)); @@ -2159,7 +2161,7 @@ int DivEngine::addSampleFromFile(const char* path) { if (si.frames>16777215) { lastError="this sample is too big! max sample size is 16777215."; sf_close(f); - isBusy.unlock(); + BUSY_END; return -1; } short* buf=new short[si.channels*si.frames]; @@ -2226,12 +2228,12 @@ int DivEngine::addSampleFromFile(const char* path) { song.sampleLen=sampleCount+1; saveLock.unlock(); renderSamples(); - isBusy.unlock(); + BUSY_END; return sampleCount; } void DivEngine::delSample(int index) { - isBusy.lock(); + BUSY_BEGIN; saveLock.lock(); if (index>=0 && index<(int)song.sample.size()) { delete song.sample[index]; @@ -2240,13 +2242,13 @@ void DivEngine::delSample(int index) { renderSamples(); } saveLock.unlock(); - isBusy.unlock(); + BUSY_END; } void DivEngine::addOrder(bool duplicate, bool where) { unsigned char order[DIV_MAX_CHANS]; if (song.ordersLen>=0x7e) return; - isBusy.lock(); + BUSY_BEGIN_SOFT; if (duplicate) { for (int i=0; i=0x7e) return; warnings=""; - isBusy.lock(); + BUSY_BEGIN_SOFT; for (int i=0; i=song.ordersLen-1) { - isBusy.unlock(); + BUSY_END; return; } saveLock.lock(); @@ -2399,7 +2401,7 @@ void DivEngine::moveOrderDown() { if (playing && !freelance) { playSub(false); } - isBusy.unlock(); + BUSY_END; } void DivEngine::exchangeIns(int one, int two) { @@ -2419,114 +2421,114 @@ void DivEngine::exchangeIns(int one, int two) { bool DivEngine::moveInsUp(int which) { if (which<1 || which>=(int)song.ins.size()) return false; - isBusy.lock(); + BUSY_BEGIN; DivInstrument* prev=song.ins[which]; saveLock.lock(); song.ins[which]=song.ins[which-1]; song.ins[which-1]=prev; exchangeIns(which,which-1); saveLock.unlock(); - isBusy.unlock(); + BUSY_END; return true; } bool DivEngine::moveWaveUp(int which) { if (which<1 || which>=(int)song.wave.size()) return false; - isBusy.lock(); + BUSY_BEGIN; DivWavetable* prev=song.wave[which]; saveLock.lock(); song.wave[which]=song.wave[which-1]; song.wave[which-1]=prev; saveLock.unlock(); - isBusy.unlock(); + BUSY_END; return true; } bool DivEngine::moveSampleUp(int which) { if (which<1 || which>=(int)song.sample.size()) return false; - isBusy.lock(); + BUSY_BEGIN; DivSample* prev=song.sample[which]; saveLock.lock(); song.sample[which]=song.sample[which-1]; song.sample[which-1]=prev; saveLock.unlock(); - isBusy.unlock(); + BUSY_END; return true; } bool DivEngine::moveInsDown(int which) { if (which<0 || which>=((int)song.ins.size())-1) return false; - isBusy.lock(); + BUSY_BEGIN; DivInstrument* prev=song.ins[which]; saveLock.lock(); song.ins[which]=song.ins[which+1]; song.ins[which+1]=prev; exchangeIns(which,which+1); saveLock.unlock(); - isBusy.unlock(); + BUSY_END; return true; } bool DivEngine::moveWaveDown(int which) { if (which<0 || which>=((int)song.wave.size())-1) return false; - isBusy.lock(); + BUSY_BEGIN; DivWavetable* prev=song.wave[which]; saveLock.lock(); song.wave[which]=song.wave[which+1]; song.wave[which+1]=prev; saveLock.unlock(); - isBusy.unlock(); + BUSY_END; return true; } bool DivEngine::moveSampleDown(int which) { if (which<0 || which>=((int)song.sample.size())-1) return false; - isBusy.lock(); + BUSY_BEGIN; DivSample* prev=song.sample[which]; saveLock.lock(); song.sample[which]=song.sample[which+1]; song.sample[which+1]=prev; saveLock.unlock(); - isBusy.unlock(); + BUSY_END; return true; } void DivEngine::noteOn(int chan, int ins, int note, int vol) { if (chan<0 || chan>=chans) return; - isBusy.lock(); + BUSY_BEGIN; pendingNotes.push(DivNoteEvent(chan,ins,note,vol,true)); if (!playing) { reset(); freelance=true; playing=true; } - isBusy.unlock(); + BUSY_END; } void DivEngine::noteOff(int chan) { if (chan<0 || chan>=chans) return; - isBusy.lock(); + BUSY_BEGIN; pendingNotes.push(DivNoteEvent(chan,-1,-1,-1,false)); if (!playing) { reset(); freelance=true; playing=true; } - isBusy.unlock(); + BUSY_END; } void DivEngine::setOrder(unsigned char order) { - isBusy.lock(); + BUSY_BEGIN_SOFT; curOrder=order; if (order>=song.ordersLen) curOrder=0; if (playing && !freelance) { playSub(false); } - isBusy.unlock(); + BUSY_END; } void DivEngine::setSysFlags(int system, unsigned int flags, bool restart) { - isBusy.lock(); + BUSY_BEGIN_SOFT; saveLock.lock(); song.systemFlags[system]=flags; saveLock.unlock(); @@ -2535,11 +2537,11 @@ void DivEngine::setSysFlags(int system, unsigned int flags, bool restart) { if (restart && isPlaying()) { playSub(false); } - isBusy.unlock(); + BUSY_END; } void DivEngine::setSongRate(float hz, bool pal) { - isBusy.lock(); + BUSY_BEGIN; saveLock.lock(); song.pal=!pal; song.hz=hz; @@ -2556,7 +2558,7 @@ void DivEngine::setSongRate(float hz, bool pal) { } } saveLock.unlock(); - isBusy.unlock(); + BUSY_END; } void DivEngine::setAudio(DivAudioEngines which) { @@ -2600,9 +2602,9 @@ bool DivEngine::switchMaster() { } void DivEngine::synchronized(const std::function& what) { - isBusy.lock(); + BUSY_BEGIN; what(); - isBusy.unlock(); + BUSY_END; } void DivEngine::lockSave(const std::function& what) { @@ -2612,11 +2614,11 @@ void DivEngine::lockSave(const std::function& what) { } void DivEngine::lockEngine(const std::function& what) { - isBusy.lock(); + BUSY_BEGIN; saveLock.lock(); what(); saveLock.unlock(); - isBusy.unlock(); + BUSY_END; } TAAudioDesc& DivEngine::getAudioDescWant() { @@ -2639,18 +2641,18 @@ void DivEngine::rescanAudioDevices() { } void DivEngine::initDispatch() { - isBusy.lock(); + BUSY_BEGIN; for (int i=0; i=0 && sPreview.sample<(int)song.sample.size()) || (sPreview.wave>=0 && sPreview.wave<(int)song.wave.size()))) { diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index 6133fff62..c2356dbcd 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -477,7 +477,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) { stop(); repeatPattern=false; setOrder(0); - isBusy.lock(); + BUSY_BEGIN_SOFT; double origRate=got.rate; got.rate=44100; // determine loop point @@ -1305,6 +1305,6 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) { logI("%d register writes total.\n",writeCount); - isBusy.unlock(); + BUSY_END; return w; } From 47d7722f6e6fc5248db0698c174845602367b0ec Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 23 Mar 2022 22:05:09 -0500 Subject: [PATCH 393/637] add a new log level (trace) --- src/engine/engine.h | 3 +++ src/engine/playback.cpp | 1 + src/log.cpp | 18 ++++++++++++++++++ src/main.cpp | 6 ++++-- src/ta-log.h | 2 ++ 5 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/engine/engine.h b/src/engine/engine.h index 7c8c90976..1aa2b0c53 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -190,6 +190,7 @@ class DivEngine { bool forceMono; bool cmdStreamEnabled; bool softLocked; + int softLockCount; int ticks, curRow, curOrder, remainingLoops, nextSpeed; double divider; int cycles; @@ -681,6 +682,8 @@ class DivEngine { halted(false), forceMono(false), cmdStreamEnabled(false), + softLocked(0), + softLockCount(0), ticks(0), curRow(0), curOrder(0), diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 5443faa51..1d7f24175 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -1410,6 +1410,7 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi if (softLocked) { if (!isBusy.try_lock()) { + logV("audio is soft-locked (%d)\n",softLockCount++); return; } } else { diff --git a/src/log.cpp b/src/log.cpp index c8dd030e7..1f77925b4 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -17,10 +17,28 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +// TODO: improve these routines to allow logging to memory for eventual log window! + #include "ta-log.h" int logLevel=LOGLEVEL_INFO; +int logV(const char* format, ...) { + va_list va; + int ret; + if (logLevel Date: Wed, 23 Mar 2022 22:16:25 -0500 Subject: [PATCH 394/637] YM2151: implement song tuning --- src/engine/platform/arcade.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/engine/platform/arcade.cpp b/src/engine/platform/arcade.cpp index 0f13be178..1eb950183 100644 --- a/src/engine/platform/arcade.cpp +++ b/src/engine/platform/arcade.cpp @@ -52,6 +52,8 @@ static int orderedOps[4]={ #define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;} #define immWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } +#define NOTE_LINEAR(x) (((x)<<6)+baseFreqOff+log2(parent->song.tuning/440.0)*12.0*64.0) + const char* regCheatSheetOPM[]={ "Test", "00", "NoteCtl", "08", @@ -234,15 +236,15 @@ void DivPlatformArcade::tick() { if (chan[i].std.hadArp) { if (!chan[i].inPorta) { if (chan[i].std.arpMode) { - chan[i].baseFreq=(chan[i].std.arp<<6)+baseFreqOff; + chan[i].baseFreq=NOTE_LINEAR(chan[i].std.arp); } else { - chan[i].baseFreq=((chan[i].note+(signed char)chan[i].std.arp)<<6)+baseFreqOff; + chan[i].baseFreq=NOTE_LINEAR(chan[i].note+(signed char)chan[i].std.arp); } } chan[i].freqChanged=true; } else { if (chan[i].std.arpMode && chan[i].std.finishedArp) { - chan[i].baseFreq=(chan[i].note<<6)+baseFreqOff; + chan[i].baseFreq=NOTE_LINEAR(chan[i].note); chan[i].freqChanged=true; } } @@ -446,7 +448,7 @@ int DivPlatformArcade::dispatch(DivCommand c) { chan[c.chan].insChanged=false; if (c.value!=DIV_NOTE_NULL) { - chan[c.chan].baseFreq=(c.value<<6)+baseFreqOff; + chan[c.chan].baseFreq=NOTE_LINEAR(c.value); chan[c.chan].note=c.value; chan[c.chan].freqChanged=true; } @@ -510,7 +512,7 @@ int DivPlatformArcade::dispatch(DivCommand c) { break; } case DIV_CMD_NOTE_PORTA: { - int destFreq=(c.value2<<6)+baseFreqOff; + int destFreq=NOTE_LINEAR(c.value2); int newFreq; bool return2=false; if (destFreq>chan[c.chan].baseFreq) { @@ -535,7 +537,7 @@ int DivPlatformArcade::dispatch(DivCommand c) { break; } case DIV_CMD_LEGATO: { - chan[c.chan].baseFreq=(c.value<<6)+baseFreqOff; + chan[c.chan].baseFreq=NOTE_LINEAR(c.value); chan[c.chan].freqChanged=true; break; } From 3ed38aca5e7597c55f1b20e46df336b5ba8d6667 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 23 Mar 2022 22:32:36 -0500 Subject: [PATCH 395/637] GUI: allow customization of file picker colors --- src/gui/settings.cpp | 77 +++++++++++++++++++++++++++++++++----------- 1 file changed, 58 insertions(+), 19 deletions(-) diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 9f1a8f5b1..19e91e893 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -505,6 +505,18 @@ void FurnaceGUI::drawSettings() { UI_COLOR_CONFIG(GUI_COLOR_PLAYBACK_STAT,"Playback status"); ImGui::TreePop(); } + if (ImGui::TreeNode("File Picker (built-in)")) { + UI_COLOR_CONFIG(GUI_COLOR_FILE_DIR,"Directory"); + UI_COLOR_CONFIG(GUI_COLOR_FILE_SONG_NATIVE,"Song (native)"); + UI_COLOR_CONFIG(GUI_COLOR_FILE_SONG_IMPORT,"Song (import)"); + UI_COLOR_CONFIG(GUI_COLOR_FILE_INSTR,"Instrument"); + UI_COLOR_CONFIG(GUI_COLOR_FILE_AUDIO,"Audio"); + UI_COLOR_CONFIG(GUI_COLOR_FILE_WAVE,"Wavetable"); + UI_COLOR_CONFIG(GUI_COLOR_FILE_VGM,"VGM"); + UI_COLOR_CONFIG(GUI_COLOR_FILE_FONT,"Font"); + UI_COLOR_CONFIG(GUI_COLOR_FILE_OTHER,"Other"); + ImGui::TreePop(); + } if (ImGui::TreeNode("Volume Meter")) { UI_COLOR_CONFIG(GUI_COLOR_VOLMETER_LOW,"Low"); UI_COLOR_CONFIG(GUI_COLOR_VOLMETER_HIGH,"High"); @@ -1268,6 +1280,15 @@ void FurnaceGUI::commitSettings() { PUT_UI_COLOR(GUI_COLOR_TOGGLE_OFF); PUT_UI_COLOR(GUI_COLOR_EDITING); PUT_UI_COLOR(GUI_COLOR_SONG_LOOP); + PUT_UI_COLOR(GUI_COLOR_FILE_DIR); + PUT_UI_COLOR(GUI_COLOR_FILE_SONG_NATIVE); + PUT_UI_COLOR(GUI_COLOR_FILE_SONG_IMPORT); + PUT_UI_COLOR(GUI_COLOR_FILE_INSTR); + PUT_UI_COLOR(GUI_COLOR_FILE_AUDIO); + PUT_UI_COLOR(GUI_COLOR_FILE_WAVE); + PUT_UI_COLOR(GUI_COLOR_FILE_VGM); + PUT_UI_COLOR(GUI_COLOR_FILE_FONT); + PUT_UI_COLOR(GUI_COLOR_FILE_OTHER); PUT_UI_COLOR(GUI_COLOR_VOLMETER_LOW); PUT_UI_COLOR(GUI_COLOR_VOLMETER_HIGH); PUT_UI_COLOR(GUI_COLOR_VOLMETER_PEAK); @@ -1643,13 +1664,26 @@ void FurnaceGUI::applyUISettings() { GET_UI_COLOR(GUI_COLOR_TOGGLE_OFF,ImVec4(0.2f,0.2f,0.2f,1.0f)); GET_UI_COLOR(GUI_COLOR_EDITING,ImVec4(0.2f,0.1f,0.1f,1.0f)); GET_UI_COLOR(GUI_COLOR_SONG_LOOP,ImVec4(0.3f,0.5f,0.8f,0.4f)); + + GET_UI_COLOR(GUI_COLOR_FILE_DIR,ImVec4(0.0f,1.0f,1.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_FILE_SONG_NATIVE,ImVec4(0.5f,1.0f,0.5f,1.0f)); + GET_UI_COLOR(GUI_COLOR_FILE_SONG_IMPORT,ImVec4(0.5f,1.0f,0.8f,1.0f)); + GET_UI_COLOR(GUI_COLOR_FILE_INSTR,ImVec4(1.0f,0.5f,0.5f,1.0f)); + GET_UI_COLOR(GUI_COLOR_FILE_AUDIO,ImVec4(1.0f,1.0f,0.5f,1.0f)); + GET_UI_COLOR(GUI_COLOR_FILE_WAVE,ImVec4(1.0f,0.75f,0.5f,1.0f)); + GET_UI_COLOR(GUI_COLOR_FILE_VGM,ImVec4(1.0f,1.0f,0.5f,1.0f)); + GET_UI_COLOR(GUI_COLOR_FILE_FONT,ImVec4(0.3f,1.0f,0.6f,1.0f)); + GET_UI_COLOR(GUI_COLOR_FILE_OTHER,ImVec4(0.7f,0.7f,0.7f,1.0f)); + GET_UI_COLOR(GUI_COLOR_VOLMETER_LOW,ImVec4(0.2f,0.6f,0.2f,1.0f)); GET_UI_COLOR(GUI_COLOR_VOLMETER_HIGH,ImVec4(1.0f,0.9f,0.2f,1.0f)); GET_UI_COLOR(GUI_COLOR_VOLMETER_PEAK,ImVec4(1.0f,0.1f,0.1f,1.0f)); + GET_UI_COLOR(GUI_COLOR_MACRO_VOLUME,ImVec4(0.2f,1.0f,0.0f,1.0f)); GET_UI_COLOR(GUI_COLOR_MACRO_PITCH,ImVec4(1.0f,0.8f,0.0f,1.0f)); GET_UI_COLOR(GUI_COLOR_MACRO_OTHER,ImVec4(0.0f,0.9f,1.0f,1.0f)); GET_UI_COLOR(GUI_COLOR_MACRO_WAVE,ImVec4(1.0f,0.4f,0.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_FM,ImVec4(0.6f,0.9f,1.0f,1.0f)); GET_UI_COLOR(GUI_COLOR_INSTR_STD,ImVec4(0.6f,1.0f,0.5f,1.0f)); GET_UI_COLOR(GUI_COLOR_INSTR_GB,ImVec4(1.0f,1.0f,0.5f,1.0f)); @@ -1677,6 +1711,7 @@ void FurnaceGUI::applyUISettings() { GET_UI_COLOR(GUI_COLOR_INSTR_VERA,ImVec4(0.4f,0.6f,1.0f,1.0f)); GET_UI_COLOR(GUI_COLOR_INSTR_X1_010,ImVec4(0.3f,0.5f,1.0f,1.0f)); GET_UI_COLOR(GUI_COLOR_INSTR_UNKNOWN,ImVec4(0.3f,0.3f,0.3f,1.0f)); + GET_UI_COLOR(GUI_COLOR_CHANNEL_FM,ImVec4(0.2f,0.8f,1.0f,1.0f)); GET_UI_COLOR(GUI_COLOR_CHANNEL_PULSE,ImVec4(0.4f,1.0f,0.2f,1.0f)); GET_UI_COLOR(GUI_COLOR_CHANNEL_NOISE,ImVec4(0.8f,0.8f,0.8f,1.0f)); @@ -1684,6 +1719,7 @@ void FurnaceGUI::applyUISettings() { GET_UI_COLOR(GUI_COLOR_CHANNEL_WAVE,ImVec4(1.0f,0.5f,0.2f,1.0f)); GET_UI_COLOR(GUI_COLOR_CHANNEL_OP,ImVec4(0.2f,0.4f,1.0f,1.0f)); GET_UI_COLOR(GUI_COLOR_CHANNEL_MUTED,ImVec4(0.5f,0.5f,0.5f,1.0f)); + GET_UI_COLOR(GUI_COLOR_PATTERN_CURSOR,ImVec4(0.1f,0.3f,0.5f,1.0f)); GET_UI_COLOR(GUI_COLOR_PATTERN_CURSOR_HOVER,ImVec4(0.2f,0.4f,0.6f,1.0f)); GET_UI_COLOR(GUI_COLOR_PATTERN_CURSOR_ACTIVE,ImVec4(0.2f,0.5f,0.7f,1.0f)); @@ -1709,6 +1745,7 @@ void FurnaceGUI::applyUISettings() { GET_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,ImVec4(0.5f,1.0f,0.0f,1.0f)); GET_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_SYS_SECONDARY,ImVec4(0.0f,1.0f,0.5f,1.0f)); GET_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_MISC,ImVec4(0.3f,0.3f,1.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_EE_VALUE,ImVec4(0.0f,1.0f,1.0f,1.0f)); GET_UI_COLOR(GUI_COLOR_PLAYBACK_STAT,ImVec4(0.6f,0.6f,0.6f,1.0f)); @@ -1945,26 +1982,28 @@ void FurnaceGUI::applyUISettings() { mainFont->DotChar='.'; // TODO: allow changing these colors. - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByTypeDir,"",ImVec4(0.0f,1.0f,1.0f,1.0f),ICON_FA_FOLDER_O); - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByTypeFile,"",ImVec4(0.7f,0.7f,0.7f,1.0f),ICON_FA_FILE_O); - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".fur",ImVec4(0.5f,1.0f,0.5f,1.0f),ICON_FA_FILE); - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".fui",ImVec4(1.0f,0.5f,0.5f,1.0f),ICON_FA_FILE); - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".fuw",ImVec4(1.0f,0.75f,0.5f,1.0f),ICON_FA_FILE); - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".dmf",ImVec4(0.5f,1.0f,0.5f,1.0f),ICON_FA_FILE); - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".dmp",ImVec4(1.0f,0.5f,0.5f,1.0f),ICON_FA_FILE); - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".dmw",ImVec4(1.0f,0.75f,0.5f,1.0f),ICON_FA_FILE); - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".wav",ImVec4(1.0f,1.0f,0.5f,1.0f),ICON_FA_FILE_AUDIO_O); - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".vgm",ImVec4(1.0f,1.0f,0.5f,1.0f),ICON_FA_FILE_AUDIO_O); - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".ttf",ImVec4(0.3f,1.0f,0.6f,1.0f),ICON_FA_FONT); - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".otf",ImVec4(0.3f,1.0f,0.6f,1.0f),ICON_FA_FONT); - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".ttc",ImVec4(0.3f,1.0f,0.6f,1.0f),ICON_FA_FONT); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByTypeDir,"",uiColors[GUI_COLOR_FILE_DIR],ICON_FA_FOLDER_O); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByTypeFile,"",uiColors[GUI_COLOR_FILE_OTHER],ICON_FA_FILE_O); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".fur",uiColors[GUI_COLOR_FILE_SONG_NATIVE],ICON_FA_FILE); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".fui",uiColors[GUI_COLOR_FILE_INSTR],ICON_FA_FILE); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".fuw",uiColors[GUI_COLOR_FILE_WAVE],ICON_FA_FILE); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".dmf",uiColors[GUI_COLOR_FILE_SONG_NATIVE],ICON_FA_FILE); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".dmp",uiColors[GUI_COLOR_FILE_INSTR],ICON_FA_FILE); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".dmw",uiColors[GUI_COLOR_FILE_WAVE],ICON_FA_FILE); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".wav",uiColors[GUI_COLOR_FILE_AUDIO],ICON_FA_FILE_AUDIO_O); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".vgm",uiColors[GUI_COLOR_FILE_VGM],ICON_FA_FILE_AUDIO_O); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".ttf",uiColors[GUI_COLOR_FILE_FONT],ICON_FA_FONT); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".otf",uiColors[GUI_COLOR_FILE_FONT],ICON_FA_FONT); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".ttc",uiColors[GUI_COLOR_FILE_FONT],ICON_FA_FONT); - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".tfi",ImVec4(1.0f,0.5f,0.5f,1.0f),ICON_FA_FILE); - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".vgi",ImVec4(1.0f,0.5f,0.5f,1.0f),ICON_FA_FILE); - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".s3i",ImVec4(1.0f,0.5f,0.5f,1.0f),ICON_FA_FILE); - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".sbi",ImVec4(1.0f,0.5f,0.5f,1.0f),ICON_FA_FILE); - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".fti",ImVec4(1.0f,0.5f,0.5f,1.0f),ICON_FA_FILE); - ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".bti",ImVec4(1.0f,0.5f,0.5f,1.0f),ICON_FA_FILE); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".mod",uiColors[GUI_COLOR_FILE_SONG_IMPORT],ICON_FA_FILE); + + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".tfi",uiColors[GUI_COLOR_FILE_INSTR],ICON_FA_FILE); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".vgi",uiColors[GUI_COLOR_FILE_INSTR],ICON_FA_FILE); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".s3i",uiColors[GUI_COLOR_FILE_INSTR],ICON_FA_FILE); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".sbi",uiColors[GUI_COLOR_FILE_INSTR],ICON_FA_FILE); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".fti",uiColors[GUI_COLOR_FILE_INSTR],ICON_FA_FILE); + ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".bti",uiColors[GUI_COLOR_FILE_INSTR],ICON_FA_FILE); if (fileDialog!=NULL) delete fileDialog; fileDialog=new FurnaceGUIFileDialog(settings.sysFileDialog); From ece34990e5f72671108a728d143b7b32193c3cdb Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 23 Mar 2022 23:19:16 -0500 Subject: [PATCH 396/637] several pitch fixes and stuff pull request #303 --- src/engine/dispatch.h | 3 ++- src/engine/engine.cpp | 8 ++++++++ src/engine/engine.h | 2 +- src/engine/platform/amiga.cpp | 23 +++++++++++++++++------ src/engine/platform/segapcm.cpp | 16 ++++++++-------- 5 files changed, 36 insertions(+), 16 deletions(-) diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index 0ec3a295b..c6486f26b 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -384,7 +384,8 @@ class DivDispatch { virtual ~DivDispatch(); }; -#define NOTE_PERIODIC(x) parent->calcBaseFreq(chipClock,CHIP_DIVIDER,x,true) +#define NOTE_PERIODIC(x) round(parent->calcBaseFreq(chipClock,CHIP_DIVIDER,x,true)) +#define NOTE_PERIODIC_NOROUND(x) parent->calcBaseFreq(chipClock,CHIP_DIVIDER,x,true) #define NOTE_FREQUENCY(x) parent->calcBaseFreq(chipClock,CHIP_FREQBASE,x,false) #define COLOR_NTSC (315000000.0/88.0) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index e0c149752..48f88bfa1 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -815,11 +815,19 @@ void DivEngine::playSub(bool preserveDrift, int goalRow) { cmdStream.clear(); } +/* int DivEngine::calcBaseFreq(double clock, double divider, int note, bool period) { double base=(period?(song.tuning*0.0625):song.tuning)*pow(2.0,(float)(note+3)/12.0); return period? round((clock/base)/divider): base*(divider/clock); +}*/ + +double DivEngine::calcBaseFreq(double clock, double divider, int note, bool period) { + double base=(period?(song.tuning*0.0625):song.tuning)*pow(2.0,(float)(note+3)/12.0); + return period? + (clock/base)/divider: + base*(divider/clock); } int DivEngine::calcFreq(int base, int pitch, bool period, int octave) { diff --git a/src/engine/engine.h b/src/engine/engine.h index 1aa2b0c53..1eca56123 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -326,7 +326,7 @@ class DivEngine { void setConf(String key, String value); // calculate base frequency/period - int calcBaseFreq(double clock, double divider, int note, bool period); + double calcBaseFreq(double clock, double divider, int note, bool period); // calculate frequency/period int calcFreq(int base, int pitch, bool period=false, int octave=0); diff --git a/src/engine/platform/amiga.cpp b/src/engine/platform/amiga.cpp index d160d1125..13cb9604c 100644 --- a/src/engine/platform/amiga.cpp +++ b/src/engine/platform/amiga.cpp @@ -131,15 +131,15 @@ void DivPlatformAmiga::tick() { if (chan[i].std.hadArp) { if (!chan[i].inPorta) { if (chan[i].std.arpMode) { - chan[i].baseFreq=off*NOTE_PERIODIC(chan[i].std.arp); + chan[i].baseFreq=round(off*NOTE_PERIODIC_NOROUND(chan[i].std.arp)); } else { - chan[i].baseFreq=off*NOTE_PERIODIC(chan[i].note+chan[i].std.arp); + chan[i].baseFreq=round(off*NOTE_PERIODIC_NOROUND(chan[i].note+chan[i].std.arp)); } } chan[i].freqChanged=true; } else { if (chan[i].std.arpMode && chan[i].std.finishedArp) { - chan[i].baseFreq=off*NOTE_PERIODIC(chan[i].note); + chan[i].baseFreq=round(off*NOTE_PERIODIC_NOROUND(chan[i].note)); chan[i].freqChanged=true; } } @@ -183,7 +183,7 @@ int DivPlatformAmiga::dispatch(DivCommand c) { } } if (c.value!=DIV_NOTE_NULL) { - chan[c.chan].baseFreq=off*NOTE_PERIODIC(c.value); + chan[c.chan].baseFreq=round(off*NOTE_PERIODIC_NOROUND(c.value)); } if (chan[c.chan].sample<0 || chan[c.chan].sample>=parent->song.sampleLen) { chan[c.chan].sample=-1; @@ -241,7 +241,18 @@ int DivPlatformAmiga::dispatch(DivCommand c) { chan[c.chan].keyOn=true; break; case DIV_CMD_NOTE_PORTA: { - int destFreq=NOTE_PERIODIC(c.value2); + DivInstrument* ins=parent->getIns(chan[c.chan].ins); + chan[c.chan].sample=ins->amiga.initSample; + double off=1.0; + if (chan[c.chan].sample>=0 && chan[c.chan].samplesong.sampleLen) { + DivSample* s=parent->getSample(chan[c.chan].sample); + if (s->centerRate<1) { + off=1.0; + } else { + off=8363.0/(double)s->centerRate; + } + } + int destFreq=round(off*NOTE_PERIODIC_NOROUND(c.value2)); bool return2=false; if (destFreq>chan[c.chan].baseFreq) { chan[c.chan].baseFreq+=c.value; @@ -273,7 +284,7 @@ int DivPlatformAmiga::dispatch(DivCommand c) { off=8363.0/(double)s->centerRate; } } - chan[c.chan].baseFreq=off*NOTE_PERIODIC(c.value+((chan[c.chan].std.willArp && !chan[c.chan].std.arpMode)?(chan[c.chan].std.arp-12):(0))); + chan[c.chan].baseFreq=round(off*NOTE_PERIODIC_NOROUND(c.value+((chan[c.chan].std.willArp && !chan[c.chan].std.arpMode)?(chan[c.chan].std.arp-12):(0)))); chan[c.chan].freqChanged=true; chan[c.chan].note=c.value; break; diff --git a/src/engine/platform/segapcm.cpp b/src/engine/platform/segapcm.cpp index c0366a18a..361cef63c 100644 --- a/src/engine/platform/segapcm.cpp +++ b/src/engine/platform/segapcm.cpp @@ -87,15 +87,15 @@ void DivPlatformSegaPCM::tick() { if (chan[i].std.hadArp) { if (!chan[i].inPorta) { if (chan[i].std.arpMode) { - chan[i].baseFreq=(chan[i].std.arp<<6)+baseFreqOff; + chan[i].baseFreq=(chan[i].std.arp<<6); } else { - chan[i].baseFreq=((chan[i].note+(signed char)chan[i].std.arp)<<6)+baseFreqOff; + chan[i].baseFreq=((chan[i].note+(signed char)chan[i].std.arp)<<6); } } chan[i].freqChanged=true; } else { if (chan[i].std.arpMode && chan[i].std.finishedArp) { - chan[i].baseFreq=(chan[i].note<<6)+baseFreqOff; + chan[i].baseFreq=(chan[i].note<<6); chan[i].freqChanged=true; } } @@ -113,7 +113,7 @@ void DivPlatformSegaPCM::tick() { DivSample* s=parent->getSample(chan[i].pcm.sample); off=(double)s->centerRate/8363.0; } - chan[i].pcm.freq=MIN(255,((off*parent->song.tuning*pow(2.0,double(chan[i].freq+256)/(64.0*12.0)))*255)/31250); + chan[i].pcm.freq=MIN(255,(15625+(off*parent->song.tuning*pow(2.0,double(chan[i].freq+256)/(64.0*12.0)))*255)/31250); if (dumpWrites && i>=8) { addWrite(0x10007+((i-8)<<3),chan[i].pcm.freq); } @@ -250,17 +250,17 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) { break; } case DIV_CMD_NOTE_PORTA: { - int destFreq=(c.value2<<6)+baseFreqOff; + int destFreq=(c.value2<<6); int newFreq; bool return2=false; if (destFreq>chan[c.chan].baseFreq) { - newFreq=chan[c.chan].baseFreq+c.value; + newFreq=chan[c.chan].baseFreq+c.value*4; if (newFreq>=destFreq) { newFreq=destFreq; return2=true; } } else { - newFreq=chan[c.chan].baseFreq-c.value; + newFreq=chan[c.chan].baseFreq-c.value*4; if (newFreq<=destFreq) { newFreq=destFreq; return2=true; @@ -275,7 +275,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) { break; } case DIV_CMD_LEGATO: { - chan[c.chan].baseFreq=(c.value<<6)+baseFreqOff; + chan[c.chan].baseFreq=(c.value<<6); chan[c.chan].freqChanged=true; break; } From 6f18be3ede8976f5eb71187e9b9dd0251bb55aed Mon Sep 17 00:00:00 2001 From: cam900 Date: Thu, 24 Mar 2022 13:46:35 +0900 Subject: [PATCH 397/637] Add presets: Standalone OPLs Sound Blaster Pro 1.0 Earliest Sound Blaster Pro has 2 OPL2s, it supports stereo like successor but with hardpanned OPL2: one for Left output and one for Right output. PC-FXGA PC-FX, SuperGrafx on steroids, Released at December 1994 in NEC. PC-FXGA is PC add-on card version of PC-FX for IBM PC and PC-98. It's released at 1995 with 3D acceleration - it lacks at original PC-FX console. SAAYM Modern CMS/Game Blaster Compatible PC ISA sound card with YM2151 addon. Commander X16 will be use its configuration but SAA in X16 is finally replaced to VERA. Seta 1 + FM Addon X1-010 and YM3438, Both driven by Z80. Ultra Toukon Densetsu used this configuration. --- src/gui/presets.cpp | 78 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 76 insertions(+), 2 deletions(-) diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index 0ab7c2ba6..1a3bb1278 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -88,6 +88,42 @@ void FurnaceGUI::initSystemPresets() { 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha YM3526", { + DIV_SYSTEM_OPL, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha YM3526 (drums mode)", { + DIV_SYSTEM_OPL_DRUMS, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha YM3812", { + DIV_SYSTEM_OPL2, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha YM3812 (drums mode)", { + DIV_SYSTEM_OPL2_DRUMS, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha YMF262", { + DIV_SYSTEM_OPL3, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Yamaha YMF262 (drums mode)", { + DIV_SYSTEM_OPL3_DRUMS, 64, 0, 0, + 0 + } + )); sysCategories.push_back(cat); cat=FurnaceGUISysCategory("Square"); @@ -280,7 +316,6 @@ void FurnaceGUI::initSystemPresets() { 0 } )); - /* cat.systems.push_back(FurnaceGUISysDef( "Commodore 64 (6581 SID + Sound Expander)", { DIV_SYSTEM_OPL, 64, 0, 0, @@ -308,7 +343,7 @@ void FurnaceGUI::initSystemPresets() { DIV_SYSTEM_C64_8580, 64, 0, 1, 0 } - ));*/ + )); cat.systems.push_back(FurnaceGUISysDef( "Amiga", { DIV_SYSTEM_AMIGA, 64, 0, 0, @@ -432,6 +467,22 @@ void FurnaceGUI::initSystemPresets() { 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "PC + Sound Blaster Pro", { + DIV_SYSTEM_OPL2, 64, -127, 0, + DIV_SYSTEM_OPL2, 64, 127, 0, + DIV_SYSTEM_PCSPKR, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "PC + Sound Blaster Pro (drums mode)", { + DIV_SYSTEM_OPL2_DRUMS, 64, -127, 0, + DIV_SYSTEM_OPL2_DRUMS, 64, 127, 0, + DIV_SYSTEM_PCSPKR, 64, 0, 0, + 0 + } + )); cat.systems.push_back(FurnaceGUISysDef( "PC + Sound Blaster Pro 2", { DIV_SYSTEM_OPL3, 64, 0, 0, @@ -446,6 +497,22 @@ void FurnaceGUI::initSystemPresets() { 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "PC + PC-FXGA", { + DIV_SYSTEM_PCE, 64, 0, 0, // HuC6230 (WSG from HuC6280 but with built in 2 OKI ADPCM playback engines) + DIV_SYSTEM_PCSPKR, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "PC + SAAYM", { + DIV_SYSTEM_YM2151, 64, 0, 0, // 3.58MHz or 4MHz selectable via jumper + DIV_SYSTEM_SAA1099, 64, -127, 1, // 7.16MHz or 8MHz selectable via jumper + DIV_SYSTEM_SAA1099, 64, 127, 1, // "" + DIV_SYSTEM_PCSPKR, 64, 0, 0, + 0 + } + )); cat.systems.push_back(FurnaceGUISysDef( "Sharp X1", { DIV_SYSTEM_AY8910, 64, 0, 3, @@ -534,6 +601,13 @@ void FurnaceGUI::initSystemPresets() { 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "Seta 1 + FM addon", { + DIV_SYSTEM_YM2612, 64, 0, 2, // Discrete YM3438 + DIV_SYSTEM_X1_010, 64, 0, 0, + 0 + } + )); cat.systems.push_back(FurnaceGUISysDef( "Seta 2", { DIV_SYSTEM_X1_010, 64, 0, 1, From 168577e4b99b008e72785d647536a2aeb0cfe372 Mon Sep 17 00:00:00 2001 From: cam900 Date: Thu, 24 Mar 2022 13:49:41 +0900 Subject: [PATCH 398/637] Revert preset --- src/gui/presets.cpp | 71 ++------------------------------------------- 1 file changed, 2 insertions(+), 69 deletions(-) diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index ba7478553..16d69cf78 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -88,42 +88,6 @@ void FurnaceGUI::initSystemPresets() { 0 } )); - cat.systems.push_back(FurnaceGUISysDef( - "Yamaha YM3526", { - DIV_SYSTEM_OPL, 64, 0, 0, - 0 - } - )); - cat.systems.push_back(FurnaceGUISysDef( - "Yamaha YM3526 (drums mode)", { - DIV_SYSTEM_OPL_DRUMS, 64, 0, 0, - 0 - } - )); - cat.systems.push_back(FurnaceGUISysDef( - "Yamaha YM3812", { - DIV_SYSTEM_OPL2, 64, 0, 0, - 0 - } - )); - cat.systems.push_back(FurnaceGUISysDef( - "Yamaha YM3812 (drums mode)", { - DIV_SYSTEM_OPL2_DRUMS, 64, 0, 0, - 0 - } - )); - cat.systems.push_back(FurnaceGUISysDef( - "Yamaha YMF262", { - DIV_SYSTEM_OPL3, 64, 0, 0, - 0 - } - )); - cat.systems.push_back(FurnaceGUISysDef( - "Yamaha YMF262 (drums mode)", { - DIV_SYSTEM_OPL3_DRUMS, 64, 0, 0, - 0 - } - )); sysCategories.push_back(cat); cat=FurnaceGUISysCategory("Square"); @@ -323,6 +287,7 @@ void FurnaceGUI::initSystemPresets() { 0 } )); + /* cat.systems.push_back(FurnaceGUISysDef( "Commodore 64 (6581 SID + Sound Expander)", { DIV_SYSTEM_OPL, 64, 0, 0, @@ -350,7 +315,7 @@ void FurnaceGUI::initSystemPresets() { DIV_SYSTEM_C64_8580, 64, 0, 1, 0 } - )); + ));*/ cat.systems.push_back(FurnaceGUISysDef( "Amiga", { DIV_SYSTEM_AMIGA, 64, 0, 0, @@ -474,22 +439,6 @@ void FurnaceGUI::initSystemPresets() { 0 } )); - cat.systems.push_back(FurnaceGUISysDef( - "PC + Sound Blaster Pro", { - DIV_SYSTEM_OPL2, 64, -127, 0, - DIV_SYSTEM_OPL2, 64, 127, 0, - DIV_SYSTEM_PCSPKR, 64, 0, 0, - 0 - } - )); - cat.systems.push_back(FurnaceGUISysDef( - "PC + Sound Blaster Pro (drums mode)", { - DIV_SYSTEM_OPL2_DRUMS, 64, -127, 0, - DIV_SYSTEM_OPL2_DRUMS, 64, 127, 0, - DIV_SYSTEM_PCSPKR, 64, 0, 0, - 0 - } - )); cat.systems.push_back(FurnaceGUISysDef( "PC + Sound Blaster Pro 2", { DIV_SYSTEM_OPL3, 64, 0, 0, @@ -504,15 +453,6 @@ void FurnaceGUI::initSystemPresets() { 0 } )); - cat.systems.push_back(FurnaceGUISysDef( - "PC + SAAYM", { - DIV_SYSTEM_YM2151, 64, 0, 0, // 3.58MHz or 4MHz selectable via jumper - DIV_SYSTEM_SAA1099, 64, -127, 1, // 7.16MHz or 8MHz selectable via jumper - DIV_SYSTEM_SAA1099, 64, 127, 1, // "" - DIV_SYSTEM_PCSPKR, 64, 0, 0, - 0 - } - )); cat.systems.push_back(FurnaceGUISysDef( "Sharp X1", { DIV_SYSTEM_AY8910, 64, 0, 3, @@ -601,13 +541,6 @@ void FurnaceGUI::initSystemPresets() { 0 } )); - cat.systems.push_back(FurnaceGUISysDef( - "Seta 1 + FM addon", { - DIV_SYSTEM_YM2612, 64, 0, 2, // Discrete YM3438 - DIV_SYSTEM_X1_010, 64, 0, 0, - 0 - } - )); cat.systems.push_back(FurnaceGUISysDef( "Seta 2", { DIV_SYSTEM_X1_010, 64, 0, 1, From bd36a4ffdcb85e6b8283de2ac8c749d0cea4f75c Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 23 Mar 2022 23:56:59 -0500 Subject: [PATCH 399/637] dev71 - more compatibility flags for .mod --- papers/format.md | 6 ++++- src/engine/engine.cpp | 3 ++- src/engine/engine.h | 8 +++--- src/engine/fileOps.cpp | 27 ++++++++++++++++++-- src/engine/playback.cpp | 56 ++++++++++++++++++++++++----------------- src/engine/song.h | 8 +++++- src/gui/compatFlags.cpp | 12 +++++++++ 7 files changed, 89 insertions(+), 31 deletions(-) diff --git a/papers/format.md b/papers/format.md index af2c4a5c3..fc954ba05 100644 --- a/papers/format.md +++ b/papers/format.md @@ -29,6 +29,7 @@ furthermore, an `or reserved` indicates this field is always present, but is res the format versions are: +- 71: Furnace dev71 - 70: Furnace dev70 - 69: Furnace dev69 - 68: Furnace dev68 @@ -238,7 +239,10 @@ size | description | this is 2.0f for modules before 59 --- | **extended compatibility flags** (>=70) 1 | broken speed selection - 31 | reserved + 1 | no slides on first tick (>=71) or reserved + 1 | next row reset arp pos (>=71) or reserved + 1 | ignore jump at end (>=71) or reserved + 28 | reserved ``` # instrument diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 48f88bfa1..2d7b84d95 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -144,7 +144,7 @@ void DivEngine::walkSong(int& loopOrder, int& loopRow, int& loopEnd) { effectVal=pat[k]->data[j][5+(l<<1)]; if (effectVal<0) effectVal=0; if (pat[k]->data[j][4+(l<<1)]==0x0d) { - if (nextOrder==-1 && i24) { @@ -825,6 +828,11 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { if (ds.version<69) { ds.arp0Reset=false; } + if (ds.version<71) { + ds.noSlidesOnFirstTick=false; + ds.rowResetsArpPos=false; + ds.ignoreJumpAtEnd=true; + } ds.isDMF=false; reader.readS(); // reserved @@ -1071,7 +1079,16 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { if (ds.version>=70) { // extended compat flags ds.brokenSpeedSel=reader.readC(); - for (int i=0; i<31; i++) { + if (ds.version>=71) { + song.noSlidesOnFirstTick=reader.readC(); + song.rowResetsArpPos=reader.readC(); + song.ignoreJumpAtEnd=reader.readC(); + } else { + reader.readC(); + reader.readC(); + reader.readC(); + } + for (int i=0; i<28; i++) { reader.readC(); } } @@ -1282,6 +1299,9 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { DivSong ds; ds.tuning=436.0; ds.version=DIV_VERSION_MOD; + ds.noSlidesOnFirstTick=true; + ds.rowResetsArpPos=true; + ds.ignoreJumpAtEnd=false; int insCount=31; bool bypassLimits=false; @@ -1916,7 +1936,10 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { // extended compat flags w->writeC(song.brokenSpeedSel); - for (int i=0; i<31; i++) { + w->writeC(song.noSlidesOnFirstTick); + w->writeC(song.rowResetsArpPos); + w->writeC(song.ignoreJumpAtEnd); + for (int i=0; i<28; i++) { w->writeC(0); } diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 1d7f24175..22e95b02d 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -821,7 +821,7 @@ void DivEngine::processRow(int i, bool afterDelay) { } break; case 0x0d: // next order - if (changeOrd<0 && curOrder<(song.ordersLen-1)) { + if (changeOrd<0 && (curOrder<(song.ordersLen-1) || !song.ignoreJumpAtEnd)) { changeOrd=-2; changePos=effectVal; } @@ -1229,6 +1229,7 @@ void DivEngine::nextRow() { } if (haltOn==DIV_HALT_ROW) halted=true; + firstTick=true; } bool DivEngine::nextTick(bool noAccum) { @@ -1281,23 +1282,25 @@ bool DivEngine::nextTick(bool noAccum) { keyHit[i]=true; } } - if (chan[i].volSpeed!=0) { - chan[i].volume=(chan[i].volume&0xff)|(dispatchCmd(DivCommand(DIV_CMD_GET_VOLUME,i))<<8); - chan[i].volume+=chan[i].volSpeed; - if (chan[i].volume>chan[i].volMax) { - chan[i].volume=chan[i].volMax; - chan[i].volSpeed=0; - dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); - } else if (chan[i].volume<0) { - chan[i].volSpeed=0; - if (song.legacyVolumeSlides) { - chan[i].volume=chan[i].volMax+1; + if (!song.noSlidesOnFirstTick || !firstTick) { + if (chan[i].volSpeed!=0) { + chan[i].volume=(chan[i].volume&0xff)|(dispatchCmd(DivCommand(DIV_CMD_GET_VOLUME,i))<<8); + chan[i].volume+=chan[i].volSpeed; + if (chan[i].volume>chan[i].volMax) { + chan[i].volume=chan[i].volMax; + chan[i].volSpeed=0; + dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); + } else if (chan[i].volume<0) { + chan[i].volSpeed=0; + if (song.legacyVolumeSlides) { + chan[i].volume=chan[i].volMax+1; + } else { + chan[i].volume=0; + } + dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); } else { - chan[i].volume=0; + dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); } - dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); - } else { - dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); } } if (chan[i].vibratoDepth>0) { @@ -1315,13 +1318,15 @@ bool DivEngine::nextTick(bool noAccum) { break; } } - if ((chan[i].keyOn || chan[i].keyOff) && chan[i].portaSpeed>0) { - if (dispatchCmd(DivCommand(DIV_CMD_NOTE_PORTA,i,chan[i].portaSpeed,chan[i].portaNote))==2 && chan[i].portaStop && song.targetResetsSlides) { - chan[i].portaSpeed=0; - chan[i].oldNote=chan[i].note; - chan[i].note=chan[i].portaNote; - chan[i].inPorta=false; - dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note)); + if (!song.noSlidesOnFirstTick || !firstTick) { + if ((chan[i].keyOn || chan[i].keyOff) && chan[i].portaSpeed>0) { + if (dispatchCmd(DivCommand(DIV_CMD_NOTE_PORTA,i,chan[i].portaSpeed,chan[i].portaNote))==2 && chan[i].portaStop && song.targetResetsSlides) { + chan[i].portaSpeed=0; + chan[i].oldNote=chan[i].note; + chan[i].note=chan[i].portaNote; + chan[i].inPorta=false; + dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note)); + } } } if (chan[i].cut>0) { @@ -1354,6 +1359,9 @@ bool DivEngine::nextTick(bool noAccum) { dispatchCmd(DivCommand(DIV_CMD_LEGATO,i,chan[i].note)); chan[i].resetArp=false; } + if (song.rowResetsArpPos && firstTick) { + chan[i].arpStage=-1; + } if (chan[i].arp!=0 && !chan[i].arpYield && chan[i].portaSpeed<1) { if (--chan[i].arpTicks<1) { chan[i].arpTicks=song.arpLen; @@ -1377,6 +1385,8 @@ bool DivEngine::nextTick(bool noAccum) { } } + firstTick=false; + // system tick for (int i=0; itick(); diff --git a/src/engine/song.h b/src/engine/song.h index c6d52840b..19a89cfa4 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -302,6 +302,9 @@ struct DivSong { bool newInsTriggersInPorta; bool arp0Reset; bool brokenSpeedSel; + bool noSlidesOnFirstTick; + bool rowResetsArpPos; + bool ignoreJumpAtEnd; DivOrders orders; std::vector ins; @@ -375,7 +378,10 @@ struct DivSong { oneTickCut(false), newInsTriggersInPorta(true), arp0Reset(true), - brokenSpeedSel(false) { + brokenSpeedSel(false), + noSlidesOnFirstTick(false), + rowResetsArpPos(false), + ignoreJumpAtEnd(false) { for (int i=0; i<32; i++) { system[i]=DIV_SYSTEM_NULL; systemVol[i]=64; diff --git a/src/gui/compatFlags.cpp b/src/gui/compatFlags.cpp index b49c251f8..ca0538ea6 100644 --- a/src/gui/compatFlags.cpp +++ b/src/gui/compatFlags.cpp @@ -85,6 +85,18 @@ void FurnaceGUI::drawCompatFlags() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("determines next speed based on whether the row is odd/even instead of alternating between speeds."); } + ImGui::Checkbox("Don't slide on the first tick of a row",&e->song.noSlidesOnFirstTick); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("simulates ProTracker's behavior of not applying volume/pitch slides on the first tick of a row."); + } + ImGui::Checkbox("Reset arpeggio position on row change",&e->song.rowResetsArpPos); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("simulates ProTracker's behavior of arpeggio being bound to the current tick of a row."); + } + ImGui::Checkbox("Ignore 0Dxx on the last order",&e->song.ignoreJumpAtEnd); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("if this is on, a jump to next row effect will not take place when it is on the last order of a song."); + } ImGui::Text("Loop modality:"); if (ImGui::RadioButton("Reset channels",e->song.loopModality==0)) { From 7f39ec723abf65c43bfb9b73d581ebe44cb4201e Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 24 Mar 2022 01:27:53 -0500 Subject: [PATCH 400/637] SMS: overdrive 2 fixes nice --- src/engine/platform/sms.cpp | 17 ++++++++++++----- src/engine/playback.cpp | 12 ++++++------ 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/engine/platform/sms.cpp b/src/engine/platform/sms.cpp index 114d67b05..08cf568d8 100644 --- a/src/engine/platform/sms.cpp +++ b/src/engine/platform/sms.cpp @@ -57,7 +57,10 @@ void DivPlatformSMS::tick() { for (int i=0; i<4; i++) { chan[i].std.next(); if (chan[i].std.hadVol) { - chan[i].outVol=((chan[i].vol&15)*MIN(15,chan[i].std.vol))>>4; + chan[i].outVol=MIN(15,chan[i].std.vol)-(15-(chan[i].vol&15)); + if (chan[i].outVol<0) chan[i].outVol=0; + // old formula + // ((chan[i].vol&15)*MIN(15,chan[i].std.vol))>>4; rWrite(0x90|(i<<5)|(isMuted[i]?15:(15-(chan[i].outVol&15)))); } if (chan[i].std.hadArp) { @@ -66,8 +69,11 @@ void DivPlatformSMS::tick() { chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp); chan[i].actualNote=chan[i].std.arp; } else { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp); - chan[i].actualNote=chan[i].note+chan[i].std.arp; + // TODO: check whether this weird octave boundary thing applies to other systems as well + int areYouSerious=chan[i].note+chan[i].std.arp; + while (areYouSerious>0x60) areYouSerious-=12; + chan[i].baseFreq=NOTE_PERIODIC(areYouSerious); + chan[i].actualNote=areYouSerious; } chan[i].freqChanged=true; } @@ -93,10 +99,11 @@ void DivPlatformSMS::tick() { if (chan[i].actualNote>0x5d) chan[i].freq=0x01; rWrite(0x80|i<<5|(chan[i].freq&15)); rWrite(chan[i].freq>>4); - if (i==2 && snNoiseMode&2) { + // what? + /*if (i==2 && snNoiseMode&2) { chan[3].baseFreq=chan[2].baseFreq; chan[3].actualNote=chan[2].actualNote; - } + }*/ chan[i].freqChanged=false; } } diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 22e95b02d..cada57fe0 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -742,10 +742,10 @@ void DivEngine::processRow(int i, bool afterDelay) { if (disCont[dispatchOfChan[i]].dispatch->keyOffAffectsPorta(dispatchChanOfChan[i])) { chan[i].portaNote=-1; chan[i].portaSpeed=-1; - if (i==2 && sysOfChan[i]==DIV_SYSTEM_SMS) { + /*if (i==2 && sysOfChan[i]==DIV_SYSTEM_SMS) { chan[i+1].portaNote=-1; chan[i+1].portaSpeed=-1; - } + }*/ } chan[i].scheduledSlideReset=true; } @@ -763,10 +763,10 @@ void DivEngine::processRow(int i, bool afterDelay) { if (disCont[dispatchOfChan[i]].dispatch->keyOffAffectsPorta(dispatchChanOfChan[i])) { chan[i].portaNote=-1; chan[i].portaSpeed=-1; - if (i==2 && sysOfChan[i]==DIV_SYSTEM_SMS) { + /*if (i==2 && sysOfChan[i]==DIV_SYSTEM_SMS) { chan[i+1].portaNote=-1; chan[i+1].portaSpeed=-1; - } + }*/ } chan[i].scheduledSlideReset=true; } @@ -1344,10 +1344,10 @@ bool DivEngine::nextTick(bool noAccum) { if (disCont[dispatchOfChan[i]].dispatch->keyOffAffectsPorta(dispatchChanOfChan[i])) { chan[i].portaNote=-1; chan[i].portaSpeed=-1; - if (i==2 && sysOfChan[i]==DIV_SYSTEM_SMS) { + /*if (i==2 && sysOfChan[i]==DIV_SYSTEM_SMS) { chan[i+1].portaNote=-1; chan[i+1].portaSpeed=-1; - } + }*/ } dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0)); chan[i].scheduledSlideReset=true; From ea49c760c58b826c5d0a5b70bc08810a12b28d08 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 24 Mar 2022 04:53:09 -0500 Subject: [PATCH 401/637] OPLL: part 1 of fixing drum volumes --- src/engine/platform/opll.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/engine/platform/opll.cpp b/src/engine/platform/opll.cpp index 4f21ba212..fc19daf60 100644 --- a/src/engine/platform/opll.cpp +++ b/src/engine/platform/opll.cpp @@ -651,6 +651,7 @@ int DivPlatformOPLL::dispatch(DivCommand c) { void DivPlatformOPLL::forceIns() { for (int i=0; i<9; i++) { + if (i>=6 && properDrums) continue; // update custom preset if (chan[i].state.opllPreset==0 && i==lastCustomMemory) { DivInstrumentFM::Operator& mod=chan[i].state.op[0]; @@ -675,7 +676,7 @@ void DivPlatformOPLL::forceIns() { } } } - if (drums) { + if (drums) { // WHAT?! FIX THIS! immWrite(0x16,0x20); immWrite(0x26,0x05); immWrite(0x16,0x20); @@ -687,6 +688,12 @@ void DivPlatformOPLL::forceIns() { immWrite(0x18,0xC0); immWrite(0x28,0x01); } + // UNTESTED + if (properDrums) { + rWrite(0x36,drumVol[0]); + rWrite(0x37,drumVol[1]|(drumVol[4]<<4)); + rWrite(0x38,drumVol[3]|(drumVol[2]<<4)); + } drumState=0; } From d2a78295cebac49f1b84018e94c8312612c5f7d8 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 25 Mar 2022 01:42:04 -0500 Subject: [PATCH 402/637] OPLL: wooooow how did this break --- src/engine/instrument.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index 785c9b19d..b87dd1334 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -724,6 +724,13 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) { std.dutyMacroRel=-1; } + // clear wave macro if OPLL instrument and version<70 + if (version<70 && type==DIV_INS_OPLL) { + std.waveMacroLen=0; + std.waveMacroLoop=-1; + std.waveMacroRel=-1; + } + // sample map if (version>=67) { amiga.useNoteMap=reader.readC(); From 6e35640537598b93ba96a46e43bc585835a62d81 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 25 Mar 2022 02:10:44 -0500 Subject: [PATCH 403/637] GUI: less annoying PET waveform view --- src/gui/insEdit.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index d77a4632e..baecd544e 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1659,7 +1659,7 @@ void FurnaceGUI::drawInsEdit() { } } if (waveMax>0) { - NORMAL_MACRO(ins->std.waveMacro,ins->std.waveMacroLen,ins->std.waveMacroLoop,ins->std.waveMacroRel,0,waveMax,"wave",waveLabel,bitMode?64:160,ins->std.waveMacroOpen,bitMode,waveNames,false,NULL,0,0,((ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930)?1:0),NULL,uiColors[GUI_COLOR_MACRO_WAVE],mmlString[3],0,waveMax,NULL,false); + NORMAL_MACRO(ins->std.waveMacro,ins->std.waveMacroLen,ins->std.waveMacroLoop,ins->std.waveMacroRel,0,waveMax,"wave",waveLabel,(bitMode && ins->type!=DIV_INS_PET)?64:160,ins->std.waveMacroOpen,bitMode,waveNames,false,NULL,0,0,((ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930)?1:0),NULL,uiColors[GUI_COLOR_MACRO_WAVE],mmlString[3],0,waveMax,NULL,false); } if (ex1Max>0) { if (ins->type==DIV_INS_C64) { From 03da02711a62cea9f04a28f7e6d377a876730712 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 25 Mar 2022 02:10:57 -0500 Subject: [PATCH 404/637] OPLL: it's tested now. works last thing to do is to restore drum pitches --- src/engine/platform/opll.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/opll.cpp b/src/engine/platform/opll.cpp index fc19daf60..55a1fa80c 100644 --- a/src/engine/platform/opll.cpp +++ b/src/engine/platform/opll.cpp @@ -688,7 +688,7 @@ void DivPlatformOPLL::forceIns() { immWrite(0x18,0xC0); immWrite(0x28,0x01); } - // UNTESTED + // restore drum volumes if (properDrums) { rWrite(0x36,drumVol[0]); rWrite(0x37,drumVol[1]|(drumVol[4]<<4)); From ed857b20c4738692566ef55be2ecdfb9c0bcab11 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 25 Mar 2022 02:52:41 -0500 Subject: [PATCH 405/637] potentially breaking change: better freq formula now using a 4096-entry-long table for calculating final period/frequency see issue #303 --- src/engine/engine.cpp | 12 ++++++++++++ src/engine/engine.h | 2 ++ src/engine/platform/vic20.cpp | 13 ++++++++----- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 2d7b84d95..1000395b4 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -832,9 +832,17 @@ double DivEngine::calcBaseFreq(double clock, double divider, int note, bool peri int DivEngine::calcFreq(int base, int pitch, bool period, int octave) { if (song.linearPitch) { + pitch+=2048; + if (pitch<0) pitch=0; + if (pitch>4095) pitch=4095; + return period? + (base*reversePitchTable[pitch])>>10: + (base*pitchTable[pitch])>>10; + /* return period? base*pow(2,-(double)pitch/(12.0*128.0))/(98.0+globalPitch*6.0)*98.0: (base*pow(2,(double)pitch/(12.0*128.0))*(98+globalPitch*6))/98; + */ } return period? base-pitch: @@ -2850,6 +2858,10 @@ bool DivEngine::init() { for (int i=0; i<64; i++) { vibTable[i]=127*sin(((double)i/64.0)*(2*M_PI)); } + for (int i=0; i<4096; i++) { + reversePitchTable[i]=round(1024.0*pow(2.0,(2048.0-(double)i)/(12.0*128.0))); + pitchTable[i]=round(1024.0*pow(2.0,((double)i-2048.0)/(12.0*128.0))); + } for (int i=0; i #define rWrite(a,v) {regPool[(a)]=(v)&0xff; vic_sound_machine_store(vic,a,(v)&0xff);} - -#define CHIP_DIVIDER 32 #define SAMP_DIVIDER 4 +const int chipDividers[4]={ + 128, 64, 32, 64 +}; + const char* regCheatSheetVIC[]={ "CH1_Pitch", "0A", "CH2_Pitch", "0B", @@ -93,6 +95,7 @@ void DivPlatformVIC20::writeOutVol(int ch) { void DivPlatformVIC20::tick() { for (int i=0; i<4; i++) { + int CHIP_DIVIDER=chipDividers[i]; chan[i].std.next(); if (chan[i].std.hadVol) { int env=chan[i].std.vol; @@ -121,10 +124,9 @@ void DivPlatformVIC20::tick() { } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true); - if (i<3) chan[i].freq>>=(2-i); - else chan[i].freq>>=1; + printf("%d freq: %d\n",i,chan[i].freq); if (chan[i].freq<1) chan[i].freq=1; - if (chan[i].freq>127) chan[i].freq=0; + if (chan[i].freq>127) chan[i].freq=127; if (isMuted[i]) chan[i].keyOn=false; if (chan[i].keyOn) { if (i<3) { @@ -150,6 +152,7 @@ void DivPlatformVIC20::tick() { } int DivPlatformVIC20::dispatch(DivCommand c) { + int CHIP_DIVIDER=chipDividers[c.chan]; switch (c.cmd) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(chan[c.chan].ins); From 0687a6f21777bfaca2a0bc80c56312c57c10a5f8 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 25 Mar 2022 03:18:44 -0500 Subject: [PATCH 406/637] this stupid effect --- src/engine/engine.cpp | 4 ++-- src/engine/playback.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 1000395b4..ed21aed07 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -836,8 +836,8 @@ int DivEngine::calcFreq(int base, int pitch, bool period, int octave) { if (pitch<0) pitch=0; if (pitch>4095) pitch=4095; return period? - (base*reversePitchTable[pitch])>>10: - (base*pitchTable[pitch])>>10; + ((base*(reversePitchTable[pitch]))>>10): + (((base*(pitchTable[pitch]))>>10)*(16+globalPitch))/16; /* return period? base*pow(2,-(double)pitch/(12.0*128.0))/(98.0+globalPitch*6.0)*98.0: diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index cada57fe0..3a64af7a9 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -983,7 +983,7 @@ void DivEngine::processRow(int i, bool afterDelay) { if (chan[i].pitch<-128) chan[i].pitch=-128; if (chan[i].pitch>127) chan[i].pitch=127; } - chan[i].pitch+=globalPitch; + //chan[i].pitch+=globalPitch; dispatchCmd(DivCommand(DIV_CMD_PITCH,i,chan[i].pitch+(((chan[i].vibratoDepth*vibTable[chan[i].vibratoPos]*chan[i].vibratoFine)>>4)/15))); break; case 0xea: // legato mode From 5f7078db4206d0f6455154b86d7056f8d028eac3 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 25 Mar 2022 03:41:43 -0500 Subject: [PATCH 407/637] bang bang bang --- src/engine/engine.cpp | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index ed21aed07..e9de013af 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -832,17 +832,15 @@ double DivEngine::calcBaseFreq(double clock, double divider, int note, bool peri int DivEngine::calcFreq(int base, int pitch, bool period, int octave) { if (song.linearPitch) { + // global pitch multiplier + int whatTheFuck=(1024+(globalPitch<<6)-(globalPitch<0?globalPitch-6:0)); + if (whatTheFuck<1) whatTheFuck=1; // avoids division by zero but please kill me pitch+=2048; if (pitch<0) pitch=0; if (pitch>4095) pitch=4095; return period? - ((base*(reversePitchTable[pitch]))>>10): - (((base*(pitchTable[pitch]))>>10)*(16+globalPitch))/16; - /* - return period? - base*pow(2,-(double)pitch/(12.0*128.0))/(98.0+globalPitch*6.0)*98.0: - (base*pow(2,(double)pitch/(12.0*128.0))*(98+globalPitch*6))/98; - */ + ((base*(reversePitchTable[pitch]))/whatTheFuck): + (((base*(pitchTable[pitch]))>>10)*whatTheFuck)/1024; } return period? base-pitch: From ae17f7e616ae8101bdf6d142ad84a74d67355220 Mon Sep 17 00:00:00 2001 From: cam900 Date: Sat, 26 Mar 2022 00:47:39 +0900 Subject: [PATCH 408/637] NES with Family Noraebang An Karaoke unit connectable for NES cartridge slot. And, sure. It is unlicensed. --- src/gui/presets.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index 1a3bb1278..1c8a01d56 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -241,6 +241,20 @@ void FurnaceGUI::initSystemPresets() { 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "NES with Family Noraebang", { + DIV_SYSTEM_NES, 64, 0, 0, + DIV_SYSTEM_OPLL, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "NES with Family Noraebang (drums mode)", { + DIV_SYSTEM_NES, 64, 0, 0, + DIV_SYSTEM_OPLL_DRUMS, 64, 0, 0, + 0 + } + )); cat.systems.push_back(FurnaceGUISysDef( "Mattel Intellivision", { DIV_SYSTEM_AY8910, 64, 0, 48, From dc62c8610c5a54f382aac089fbbbbd6e653a45f4 Mon Sep 17 00:00:00 2001 From: cam900 Date: Sat, 26 Mar 2022 02:24:58 +0900 Subject: [PATCH 409/637] SAA1099 in CMS/Game Blaster and Compatible isn't hard panned per chip Info: http://hackipedia.org/browse.cgi/Computer/Platform/PC%2c%20IBM%20compatible/Sound%20and%20Music/Creative%20Labs/Game%20Blaster/C%e2%88%95MS%20Programming%20Information%20by%20Creative%20Labs%20%281990%29.pdf , VGMs recorded from CMS/Game Blaster --- src/gui/presets.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index 1c8a01d56..d1513bebc 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -443,8 +443,8 @@ void FurnaceGUI::initSystemPresets() { )); cat.systems.push_back(FurnaceGUISysDef( "PC + Game Blaster", { - DIV_SYSTEM_SAA1099, 64, -127, 1, - DIV_SYSTEM_SAA1099, 64, 127, 1, + DIV_SYSTEM_SAA1099, 64, 0, 1, + DIV_SYSTEM_SAA1099, 64, 0, 1, DIV_SYSTEM_PCSPKR, 64, 0, 0, 0 } @@ -466,8 +466,8 @@ void FurnaceGUI::initSystemPresets() { cat.systems.push_back(FurnaceGUISysDef( "PC + Sound Blaster w/Game Blaster Compatible", { DIV_SYSTEM_OPL2, 64, 0, 0, - DIV_SYSTEM_SAA1099, 64, -127, 1, - DIV_SYSTEM_SAA1099, 64, 127, 1, + DIV_SYSTEM_SAA1099, 64, 0, 1, + DIV_SYSTEM_SAA1099, 64, 0, 1, DIV_SYSTEM_PCSPKR, 64, 0, 0, 0 } @@ -475,8 +475,8 @@ void FurnaceGUI::initSystemPresets() { cat.systems.push_back(FurnaceGUISysDef( "PC + Sound Blaster w/Game Blaster Compatible (drums mode)", { DIV_SYSTEM_OPL2_DRUMS, 64, 0, 0, - DIV_SYSTEM_SAA1099, 64, -127, 1, - DIV_SYSTEM_SAA1099, 64, 127, 1, + DIV_SYSTEM_SAA1099, 64, 0, 1, + DIV_SYSTEM_SAA1099, 64, 0, 1, DIV_SYSTEM_PCSPKR, 64, 0, 0, 0 } @@ -521,8 +521,8 @@ void FurnaceGUI::initSystemPresets() { cat.systems.push_back(FurnaceGUISysDef( "PC + SAAYM", { DIV_SYSTEM_YM2151, 64, 0, 0, // 3.58MHz or 4MHz selectable via jumper - DIV_SYSTEM_SAA1099, 64, -127, 1, // 7.16MHz or 8MHz selectable via jumper - DIV_SYSTEM_SAA1099, 64, 127, 1, // "" + DIV_SYSTEM_SAA1099, 64, 0, 1, // 7.16MHz or 8MHz selectable via jumper + DIV_SYSTEM_SAA1099, 64, 0, 1, // "" DIV_SYSTEM_PCSPKR, 64, 0, 0, 0 } From 26791df58e7e0a37e256a84527e9c5861110c831 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 25 Mar 2022 18:16:47 -0500 Subject: [PATCH 410/637] GUI: redesign FM editor layout, part 1 thanks Raijin for the concept --- src/gui/about.cpp | 1 + src/gui/gui.h | 2 + src/gui/insEdit.cpp | 518 +++++++++++++++++++++++++++++-------------- src/gui/settings.cpp | 17 ++ 4 files changed, 367 insertions(+), 171 deletions(-) diff --git a/src/gui/about.cpp b/src/gui/about.cpp index 4d1322a5a..386b01b0c 100644 --- a/src/gui/about.cpp +++ b/src/gui/about.cpp @@ -47,6 +47,7 @@ const char* aboutLine[]={ "-- graphics/UI design --", "tildearrow", "BlastBrothers", + "Raijin", "", "-- documentation --", "tildearrow", diff --git a/src/gui/gui.h b/src/gui/gui.h index 315ce7421..1c6466631 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -622,6 +622,7 @@ class FurnaceGUI { int roundedButtons; int roundedMenus; int loadJapanese; + int fmLayout; unsigned int maxUndoSteps; String mainFontPath; String patFontPath; @@ -672,6 +673,7 @@ class FurnaceGUI { roundedButtons(1), roundedMenus(0), loadJapanese(0), + fmLayout(0), maxUndoSteps(100), mainFontPath(""), patFontPath(""), diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index baecd544e..718eaf013 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1053,207 +1053,383 @@ void FurnaceGUI::drawInsEdit() { ins->fm.op[1].tl&=15; P(ImGui::SliderScalar("Volume##TL",ImGuiDataType_U8,&ins->fm.op[1].tl,&_FIFTEEN,&_ZERO)); rightClickable } - if (willDisplayOps) if (ImGui::BeginTable("FMOperators",2,ImGuiTableFlags_SizingStretchSame)) { - for (int i=0; ifm.op[(opCount==4)?opOrder[i]:i]; - if ((i+1)&1) ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Separator(); - ImGui::PushID(fmt::sprintf("op%d",i).c_str()); - ImGui::Dummy(ImVec2(dpiScale,dpiScale)); - if (ins->type==DIV_INS_OPL && ins->fm.opllPreset==16) { - if (i==1) { - ImGui::Text("Envelope 2 (kick only)"); - } else { - ImGui::Text("Envelope"); - } - } else { - ImGui::Text("OP%d",i+1); - } - - ImGui::SameLine(); - - bool amOn=op.am; - if (ImGui::Checkbox(FM_NAME(FM_AM),&amOn)) { PARAMETER - op.am=amOn; - } - - ImGui::SameLine(); - - int maxTl=127; - if (ins->type==DIV_INS_OPLL) { - if (i==1) { - maxTl=15; - } else { - maxTl=63; - } - } - if (ins->type==DIV_INS_OPL) { - maxTl=63; - } - int maxArDr=(ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ)?31:15; - - bool ssgOn=op.ssgEnv&8; - bool ksrOn=op.ksr; - bool vibOn=op.vib; - bool susOn=op.sus; // don't you make fun of this one - unsigned char ssgEnv=op.ssgEnv&7; - if (ins->type!=DIV_INS_OPL && ins->type!=DIV_INS_OPZ) { - if (ImGui::Checkbox((ins->type==DIV_INS_OPLL)?FM_NAME(FM_EGS):"SSG On",&ssgOn)) { PARAMETER - op.ssgEnv=(op.ssgEnv&7)|(ssgOn<<3); - } - if (ins->type==DIV_INS_FM) { - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Only for Genesis and Neo Geo systems"); - } - } - } - - if (ins->type==DIV_INS_OPL) { - if (ImGui::Checkbox(FM_NAME(FM_SUS),&susOn)) { PARAMETER - op.sus=susOn; - } - } - - //52.0 controls vert scaling; default 96 - drawFMEnv(op.tl&maxTl,op.ar&maxArDr,op.dr&maxArDr,(ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPLL)?((op.rr&15)*2):op.d2r&31,op.rr&15,op.sl&15,maxTl,maxArDr,ImVec2(ImGui::GetContentRegionAvail().x,52.0*dpiScale)); - //P(ImGui::SliderScalar(FM_NAME(FM_AR),ImGuiDataType_U8,&op.ar,&_ZERO,&_THIRTY_ONE)); rightClickable - if (ImGui::BeginTable("opParams",2,ImGuiTableFlags_SizingStretchProp)) { - ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch,0.0); \ - ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed,0.0); \ - - ImGui::TableNextRow(); + if (willDisplayOps) { + if (settings.fmLayout==0) { + if (ImGui::BeginTable("FMOperators",14,ImGuiTableFlags_SizingStretchProp|ImGuiTableFlags_Borders)) { + // header + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - op.ar&=maxArDr; - P(ImGui::SliderScalar("##AR",ImGuiDataType_U8,&op.ar,&maxArDr,&_ZERO)); rightClickable - ImGui::TableNextColumn(); - ImGui::Text("%s",FM_NAME(FM_AR)); - ImGui::TableNextRow(); ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - op.dr&=maxArDr; - P(ImGui::SliderScalar("##DR",ImGuiDataType_U8,&op.dr,&maxArDr,&_ZERO)); rightClickable + ImGui::TextUnformatted(FM_NAME(FM_AR)); ImGui::TableNextColumn(); - ImGui::Text("%s",FM_NAME(FM_DR)); - - ImGui::TableNextRow(); + ImGui::TextUnformatted(FM_NAME(FM_DR)); ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - P(ImGui::SliderScalar("##SL",ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); rightClickable - ImGui::TableNextColumn(); - ImGui::Text("%s",FM_NAME(FM_SL)); - + ImGui::TextUnformatted(FM_NAME(FM_SL)); if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { - ImGui::TableNextRow(); ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - P(ImGui::SliderScalar("##D2R",ImGuiDataType_U8,&op.d2r,&_THIRTY_ONE,&_ZERO)); rightClickable - ImGui::TableNextColumn(); - ImGui::Text("%s",FM_NAME(FM_D2R)); + ImGui::TextUnformatted(FM_NAME(FM_D2R)); } - - ImGui::TableNextRow(); ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - P(ImGui::SliderScalar("##RR",ImGuiDataType_U8,&op.rr,&_FIFTEEN,&_ZERO)); rightClickable + ImGui::TextUnformatted(FM_NAME(FM_RR)); ImGui::TableNextColumn(); - ImGui::Text("%s",FM_NAME(FM_RR)); - - ImGui::TableNextRow(); + ImGui::TextUnformatted(FM_NAME(FM_TL)); ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - op.tl&=maxTl; - P(ImGui::SliderScalar("##TL",ImGuiDataType_U8,&op.tl,&maxTl,&_ZERO)); rightClickable - ImGui::TableNextColumn(); - ImGui::Text("%s",FM_NAME(FM_TL)); - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::Separator(); - ImGui::TableNextColumn(); - ImGui::Separator(); - - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { - P(ImGui::SliderScalar("##RS",ImGuiDataType_U8,&op.rs,&_ZERO,&_THREE)); rightClickable - ImGui::TableNextColumn(); - ImGui::Text("%s",FM_NAME(FM_RS)); + ImGui::TextUnformatted(FM_NAME(FM_RS)); } else { - P(ImGui::SliderScalar("##KSL",ImGuiDataType_U8,&op.ksl,&_ZERO,&_THREE)); rightClickable - ImGui::TableNextColumn(); - ImGui::Text("%s",FM_NAME(FM_KSL)); + ImGui::TextUnformatted(FM_NAME(FM_KSR)); } - - ImGui::TableNextRow(); ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - P(ImGui::SliderScalar(FM_NAME(FM_MULT),ImGuiDataType_U8,&op.mult,&_ZERO,&_FIFTEEN)); rightClickable + ImGui::TextUnformatted(FM_NAME(FM_MULT)); ImGui::TableNextColumn(); - ImGui::Text("%s",FM_NAME(FM_MULT)); - - if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { - int detune=(op.dt&7)-3; - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::SliderInt("##DT",&detune,-3,4)) { PARAMETER - op.dt=detune+3; - } rightClickable - ImGui::TableNextColumn(); - ImGui::Text("%s",FM_NAME(FM_DT)); + ImGui::TextUnformatted(FM_NAME(FM_DT)); + ImGui::TableNextColumn(); + ImGui::TextUnformatted(FM_NAME(FM_DT2)); + ImGui::TableNextColumn(); + ImGui::TextUnformatted(FM_NAME(FM_SSG)); + ImGui::TableNextColumn(); + ImGui::TextUnformatted("Envelope"); + ImGui::TableNextColumn(); + ImGui::TextUnformatted(FM_NAME(FM_AM)); + float sliderHeight=32.0f*dpiScale; + + for (int i=0; ifm.op[(opCount==4)?opOrder[i]:i]; ImGui::TableNextRow(); ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - P(ImGui::SliderScalar("##DT2",ImGuiDataType_U8,&op.dt2,&_ZERO,&_THREE)); rightClickable - if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Only for Arcade system"); + + if (i==0) sliderHeight=(ImGui::GetContentRegionAvail().y/opCount)-ImGui::GetStyle().ItemSpacing.y; + + ImGui::PushID(fmt::sprintf("op%d",i).c_str()); + if (ins->type==DIV_INS_OPL && ins->fm.opllPreset==16) { + if (i==1) { + ImGui::Text("Kick"); + } else { + ImGui::Text("Env"); + } + } else { + ImGui::Text("OP%d",i+1); } - ImGui::TableNextColumn(); - ImGui::Text("%s",FM_NAME(FM_DT2)); - ImGui::TableNextRow(); + int maxTl=127; + if (ins->type==DIV_INS_OPLL) { + if (i==1) { + maxTl=15; + } else { + maxTl=63; + } + } + if (ins->type==DIV_INS_OPL) { + maxTl=63; + } + int maxArDr=(ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ)?31:15; + bool ssgOn=op.ssgEnv&8; + /*bool ksrOn=op.ksr; + bool vibOn=op.vib; + bool susOn=op.sus;*/ + unsigned char ssgEnv=op.ssgEnv&7; + ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::SliderScalar("##SSG",ImGuiDataType_U8,&ssgEnv,&_ZERO,&_SEVEN,ssgEnvTypes[ssgEnv])) { PARAMETER - op.ssgEnv=(op.ssgEnv&8)|(ssgEnv&7); - } rightClickable + op.ar&=maxArDr; + P(ImGui::VSliderScalar("##AR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.ar,&_ZERO,&maxArDr)); + ImGui::TableNextColumn(); - ImGui::Text("%s",FM_NAME(FM_SSG)); + op.dr&=maxArDr; + P(ImGui::VSliderScalar("##DR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.dr,&_ZERO,&maxArDr)); + + ImGui::TableNextColumn(); + op.sl&=15; + P(ImGui::VSliderScalar("##SL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); + + if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { + ImGui::TableNextColumn(); + op.d2r&=31; + P(ImGui::VSliderScalar("##D2R",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.d2r,&_ZERO,&_THIRTY_ONE)); + } + + ImGui::TableNextColumn(); + op.rr&=15; + P(ImGui::VSliderScalar("##RR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.rr,&_ZERO,&_FIFTEEN)); + + ImGui::TableNextColumn(); + op.tl&=maxTl; + P(ImGui::VSliderScalar("##TL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.tl,&maxTl,&_ZERO)); + + ImGui::TableNextColumn(); + if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { + P(ImGui::VSliderScalar("##RS",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.rs,&_ZERO,&_THREE)); + } else { + P(ImGui::VSliderScalar("##KSL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.ksl,&_ZERO,&_THREE)); + } + + ImGui::TableNextColumn(); + P(ImGui::VSliderScalar("##MULT",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.mult,&_ZERO,&_FIFTEEN)); + + if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { + int detune=(op.dt&7)-3; + ImGui::TableNextColumn(); + if (ImGui::VSliderInt("##DT",ImVec2(20.0f*dpiScale,sliderHeight),&detune,-3,4)) { PARAMETER + op.dt=detune+3; + } + + ImGui::TableNextColumn(); + P(ImGui::VSliderScalar("##DT2",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.dt2,&_ZERO,&_THREE)); rightClickable + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Only on YM2151 (OPM)"); + } + + ImGui::TableNextColumn(); + ImGui::Text("Preview here"); + + if (ins->type!=DIV_INS_OPL && ins->type!=DIV_INS_OPZ) { + if (ImGui::Checkbox((ins->type==DIV_INS_OPLL)?FM_NAME(FM_EGS):"SSG On",&ssgOn)) { PARAMETER + op.ssgEnv=(op.ssgEnv&7)|(ssgOn<<3); + } + if (ins->type==DIV_INS_FM) { + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Only for OPN family chips"); + } + } + } + + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::SliderScalar("##SSG",ImGuiDataType_U8,&ssgEnv,&_ZERO,&_SEVEN,ssgEnvTypes[ssgEnv])) { PARAMETER + op.ssgEnv=(op.ssgEnv&8)|(ssgEnv&7); + } + } + + ImGui::TableNextColumn(); + drawFMEnv(op.tl&maxTl,op.ar&maxArDr,op.dr&maxArDr,(ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPLL)?((op.rr&15)*2):op.d2r&31,op.rr&15,op.sl&15,maxTl,maxArDr,ImVec2(ImGui::GetContentRegionAvail().x,sliderHeight)); + + ImGui::TableNextColumn(); + bool amOn=op.am; + if (ImGui::Checkbox("##AM",&amOn)) { PARAMETER + op.am=amOn; + } + + ImGui::PopID(); } - if (ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPZ) { - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - P(ImGui::SliderScalar("##WS",ImGuiDataType_U8,&op.ws,&_ZERO,&_SEVEN,(ins->type==DIV_INS_OPZ)?opzWaveforms[op.ws&7]:oplWaveforms[op.ws&7])); rightClickable - if (ins->type==DIV_INS_OPL && ImGui::IsItemHovered()) { - ImGui::SetTooltip("OPL2/3 only (last 4 waveforms are OPL3 only)"); - } - ImGui::TableNextColumn(); - ImGui::Text("%s",FM_NAME(FM_WS)); - } - ImGui::EndTable(); } - - if (ins->type==DIV_INS_OPLL || ins->type==DIV_INS_OPL) { - if (ImGui::Checkbox(FM_NAME(FM_VIB),&vibOn)) { PARAMETER - op.vib=vibOn; - } - ImGui::SameLine(); - if (ImGui::Checkbox(FM_NAME(FM_KSR),&ksrOn)) { PARAMETER - op.ksr=ksrOn; - } + } else { + int columns=2; + switch (settings.fmLayout) { + case 1: // 2x2 + columns=2; + break; + case 2: // 1x4 + columns=1; + break; + case 3: // 4x1 + columns=opCount; + break; } + if (ImGui::BeginTable("FMOperators",columns,ImGuiTableFlags_SizingStretchSame)) { + for (int i=0; ifm.op[(opCount==4)?opOrder[i]:i]; + if ((settings.fmLayout!=3 && ((i+1)&1)) || i==0 || settings.fmLayout==2) ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Separator(); + ImGui::PushID(fmt::sprintf("op%d",i).c_str()); + ImGui::Dummy(ImVec2(dpiScale,dpiScale)); + if (ins->type==DIV_INS_OPL && ins->fm.opllPreset==16) { + if (i==1) { + ImGui::Text("Envelope 2 (kick only)"); + } else { + ImGui::Text("Envelope"); + } + } else { + ImGui::Text("OP%d",i+1); + } - ImGui::PopID(); + ImGui::SameLine(); + + bool amOn=op.am; + if (ImGui::Checkbox(FM_NAME(FM_AM),&amOn)) { PARAMETER + op.am=amOn; + } + + ImGui::SameLine(); + + int maxTl=127; + if (ins->type==DIV_INS_OPLL) { + if (i==1) { + maxTl=15; + } else { + maxTl=63; + } + } + if (ins->type==DIV_INS_OPL) { + maxTl=63; + } + int maxArDr=(ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ)?31:15; + + bool ssgOn=op.ssgEnv&8; + bool ksrOn=op.ksr; + bool vibOn=op.vib; + bool susOn=op.sus; // don't you make fun of this one + unsigned char ssgEnv=op.ssgEnv&7; + if (ins->type!=DIV_INS_OPL && ins->type!=DIV_INS_OPZ) { + if (ImGui::Checkbox((ins->type==DIV_INS_OPLL)?FM_NAME(FM_EGS):"SSG On",&ssgOn)) { PARAMETER + op.ssgEnv=(op.ssgEnv&7)|(ssgOn<<3); + } + if (ins->type==DIV_INS_FM) { + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Only for OPN family chips"); + } + } + } + + if (ins->type==DIV_INS_OPL) { + if (ImGui::Checkbox(FM_NAME(FM_SUS),&susOn)) { PARAMETER + op.sus=susOn; + } + } + + //52.0 controls vert scaling; default 96 + drawFMEnv(op.tl&maxTl,op.ar&maxArDr,op.dr&maxArDr,(ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPLL)?((op.rr&15)*2):op.d2r&31,op.rr&15,op.sl&15,maxTl,maxArDr,ImVec2(ImGui::GetContentRegionAvail().x,52.0*dpiScale)); + //P(ImGui::SliderScalar(FM_NAME(FM_AR),ImGuiDataType_U8,&op.ar,&_ZERO,&_THIRTY_ONE)); rightClickable + if (ImGui::BeginTable("opParams",2,ImGuiTableFlags_SizingStretchProp)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch,0.0); \ + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed,0.0); \ + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + op.ar&=maxArDr; + P(ImGui::SliderScalar("##AR",ImGuiDataType_U8,&op.ar,&maxArDr,&_ZERO)); rightClickable + ImGui::TableNextColumn(); + ImGui::Text("%s",FM_NAME(FM_AR)); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + op.dr&=maxArDr; + P(ImGui::SliderScalar("##DR",ImGuiDataType_U8,&op.dr,&maxArDr,&_ZERO)); rightClickable + ImGui::TableNextColumn(); + ImGui::Text("%s",FM_NAME(FM_DR)); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + P(ImGui::SliderScalar("##SL",ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); rightClickable + ImGui::TableNextColumn(); + ImGui::Text("%s",FM_NAME(FM_SL)); + + if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + P(ImGui::SliderScalar("##D2R",ImGuiDataType_U8,&op.d2r,&_THIRTY_ONE,&_ZERO)); rightClickable + ImGui::TableNextColumn(); + ImGui::Text("%s",FM_NAME(FM_D2R)); + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + P(ImGui::SliderScalar("##RR",ImGuiDataType_U8,&op.rr,&_FIFTEEN,&_ZERO)); rightClickable + ImGui::TableNextColumn(); + ImGui::Text("%s",FM_NAME(FM_RR)); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + op.tl&=maxTl; + P(ImGui::SliderScalar("##TL",ImGuiDataType_U8,&op.tl,&maxTl,&_ZERO)); rightClickable + ImGui::TableNextColumn(); + ImGui::Text("%s",FM_NAME(FM_TL)); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Separator(); + ImGui::TableNextColumn(); + ImGui::Separator(); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { + P(ImGui::SliderScalar("##RS",ImGuiDataType_U8,&op.rs,&_ZERO,&_THREE)); rightClickable + ImGui::TableNextColumn(); + ImGui::Text("%s",FM_NAME(FM_RS)); + } else { + P(ImGui::SliderScalar("##KSL",ImGuiDataType_U8,&op.ksl,&_ZERO,&_THREE)); rightClickable + ImGui::TableNextColumn(); + ImGui::Text("%s",FM_NAME(FM_KSL)); + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + P(ImGui::SliderScalar(FM_NAME(FM_MULT),ImGuiDataType_U8,&op.mult,&_ZERO,&_FIFTEEN)); rightClickable + ImGui::TableNextColumn(); + ImGui::Text("%s",FM_NAME(FM_MULT)); + + if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { + int detune=(op.dt&7)-3; + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::SliderInt("##DT",&detune,-3,4)) { PARAMETER + op.dt=detune+3; + } rightClickable + ImGui::TableNextColumn(); + ImGui::Text("%s",FM_NAME(FM_DT)); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + P(ImGui::SliderScalar("##DT2",ImGuiDataType_U8,&op.dt2,&_ZERO,&_THREE)); rightClickable + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Only on YM2151 (OPM)"); + } + ImGui::TableNextColumn(); + ImGui::Text("%s",FM_NAME(FM_DT2)); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::SliderScalar("##SSG",ImGuiDataType_U8,&ssgEnv,&_ZERO,&_SEVEN,ssgEnvTypes[ssgEnv])) { PARAMETER + op.ssgEnv=(op.ssgEnv&8)|(ssgEnv&7); + } rightClickable + ImGui::TableNextColumn(); + ImGui::Text("%s",FM_NAME(FM_SSG)); + } + + if (ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPZ) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + P(ImGui::SliderScalar("##WS",ImGuiDataType_U8,&op.ws,&_ZERO,&_SEVEN,(ins->type==DIV_INS_OPZ)?opzWaveforms[op.ws&7]:oplWaveforms[op.ws&7])); rightClickable + if (ins->type==DIV_INS_OPL && ImGui::IsItemHovered()) { + ImGui::SetTooltip("OPL2/3 only (last 4 waveforms are OPL3 only)"); + } + ImGui::TableNextColumn(); + ImGui::Text("%s",FM_NAME(FM_WS)); + } + + ImGui::EndTable(); + } + + if (ins->type==DIV_INS_OPLL || ins->type==DIV_INS_OPL) { + if (ImGui::Checkbox(FM_NAME(FM_VIB),&vibOn)) { PARAMETER + op.vib=vibOn; + } + ImGui::SameLine(); + if (ImGui::Checkbox(FM_NAME(FM_KSR),&ksrOn)) { PARAMETER + op.ksr=ksrOn; + } + } + + ImGui::PopID(); + } + ImGui::EndTable(); + } } - ImGui::EndTable(); } ImGui::EndTabItem(); } diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 19e91e893..02ca297b7 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -419,6 +419,20 @@ void FurnaceGUI::drawSettings() { settings.controlLayout=3; } + ImGui::Text("FM parameter editor layout:"); + if (ImGui::RadioButton("Modern##fml0",settings.fmLayout==0)) { + settings.fmLayout=0; + } + if (ImGui::RadioButton("Compact (2x2, classic)##fml1",settings.fmLayout==1)) { + settings.fmLayout=1; + } + if (ImGui::RadioButton("Compact (1x4)##fml2",settings.fmLayout==2)) { + settings.fmLayout=2; + } + if (ImGui::RadioButton("Compact (4x1)##fml3",settings.fmLayout==3)) { + settings.fmLayout=3; + } + bool macroViewB=settings.macroView; if (ImGui::Checkbox("Classic macro view (standard macros only; deprecated!)",¯oViewB)) { settings.macroView=macroViewB; @@ -990,6 +1004,7 @@ void FurnaceGUI::syncSettings() { settings.roundedButtons=e->getConfInt("roundedButtons",1); settings.roundedMenus=e->getConfInt("roundedMenus",0); settings.loadJapanese=e->getConfInt("loadJapanese",0); + settings.fmLayout=e->getConfInt("fmLayout",0); clampSetting(settings.mainFontSize,2,96); clampSetting(settings.patFontSize,2,96); @@ -1034,6 +1049,7 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.roundedButtons,0,1); clampSetting(settings.roundedMenus,0,1); clampSetting(settings.loadJapanese,0,1); + clampSetting(settings.fmLayout,0,3); // keybinds LOAD_KEYBIND(GUI_ACTION_OPEN,FURKMOD_CMD|SDLK_o); @@ -1268,6 +1284,7 @@ void FurnaceGUI::commitSettings() { e->setConf("roundedButtons",settings.roundedButtons); e->setConf("roundedMenus",settings.roundedMenus); e->setConf("loadJapanese",settings.loadJapanese); + e->setConf("fmLayout",settings.fmLayout); PUT_UI_COLOR(GUI_COLOR_BACKGROUND); PUT_UI_COLOR(GUI_COLOR_FRAME_BACKGROUND); From 248942879564e53a44711c0ed46425f8217a2b90 Mon Sep 17 00:00:00 2001 From: AugiteSoul Date: Sat, 26 Mar 2022 19:37:07 +0100 Subject: [PATCH 411/637] Cleaned up grammar, mostly Might need some changes after this considering some lines were rather confusing - I don't actually know much about how this chip works exactly --- papers/doc/7-systems/x1-010.md | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/papers/doc/7-systems/x1-010.md b/papers/doc/7-systems/x1-010.md index 1fde55e0f..411afb3e7 100644 --- a/papers/doc/7-systems/x1-010.md +++ b/papers/doc/7-systems/x1-010.md @@ -1,27 +1,27 @@ # Seta/Allumer X1-010 -One of sound chip originally designed by Seta, mainly used at their arcade hardwares at late-80s to early-2000s. -It has 2 output channels, but no known hardware using this feature for stereo sound. +A sound chip designed by Seta, mainly used in their own arcade hardware from the late 80s to the early 2000s. +It has 2 output channels, but there is no known hardware taking advantage of stereo sound capabilities. Later hardware paired this with external bankswitching logic, but this isn't emulated yet. -Allumer one is just rebadged Seta's thing for use in their arcade hardwares. +Allumer rebadged it for their own arcade hardware. -It has 16 channels, and all channels can be switchable to PCM sample or wavetable playback mode. -Wavetable needs to paired with envelope, this feature is similar as AY PSG, but its shape are stored at RAM: it means it is user-definable. +It has 16 channels, which can all be switched between PCM sample or wavetable playback mode. +Wavetable playback needs to paired with envelope, similar to AY PSG, but shapes are stored in RAM and as such are user-definable. -In furnace, this chip is can be configurable for original arcade mono output or stereo output - it simulates early 'incorrect' emulation on some mono hardware, but it is also based on the assumption that each channel is connected to each output. +In furnace, this chip can be configured for original arcade mono output or stereo output - it simulates early 'incorrect' emulation on some mono hardware, but it is also based on the assumption that each channel is connected to each output. -# waveform type +# waveform types -This chip supports 2 type waveforms, needs to paired external 8 KB RAM for use these features: +This chip supports 2 types of waveforms, needs to be paired to external 8 KB RAM to access these features: -One is signed 8 bit mono waveform, it's operated like other wavetable based sound systems. -These are stored at the bottom half of RAM at common case. +One is a signed 8 bit mono waveform, operated like other wavetable based sound systems. +These are stored at the lower half of RAM at common case. -Another one ("Envelope") is 4 bit stereo waveform, it's multiplied with above and calculates final output, Each nibble is used for each output channels. +The other one ("Envelope") is a 4 bit stereo waveform, multiplied with the above and calculates final output, each nibble is used for each output channel. These are stored at the upper half of RAM at common case. -Both waveforms are 128 byte fixed size, it's freely allocated at each half of RAM except channel register area: each half can be stored total 32/31 waveforms at once. -In furnace, You can set envelope shape split mode. When it sets, its waveform will be split to left half and right half for each outputs. each max size are 128 bytes, total 256 bytes. +Both waveforms are 128 bytes (fixed size), freely allocated at each half of RAM except the channel register area: each half can store total 32/31 waveforms at once. +In furnace, you can enable the envelope shape split mode. When it is set, its waveform will be split to the left and right halves for each output. Each max size is 128 bytes, total 256 bytes. # effects @@ -31,8 +31,8 @@ In furnace, You can set envelope shape split mode. When it sets, its waveform wi - `20xx`: set PCM frequency (1 to FF). - `22xx`: set envelope mode. - bit 0 sets whether envelope will affect this channel. - - bit 1 sets whether envelope one-shot mode. when it sets, channel is halted after envelope cycle is ended. - - bit 2 sets whether envelope shape split mode. when it sets, envelope shape will splitted to left half and right half. + - bit 1 toggles the envelope one-shot mode. when it is set, channel is halted after envelope cycle is finished. + - bit 2 toggles the envelope shape split mode. when it is set, envelope shape will be split to left half and right half. - bit 3/5 sets whether the right/left shape will mirror the original one. - bit 4/6 sets whether the right/left output will mirror the original one. - `23xx`: set envelope period. From d869c21f522d45c55a53faf45fb0274638eb3e09 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 26 Mar 2022 13:47:13 -0500 Subject: [PATCH 412/637] oops I forgot to commit! --- src/engine/platform/vic20.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/engine/platform/vic20.cpp b/src/engine/platform/vic20.cpp index 7c2890848..b640f780c 100644 --- a/src/engine/platform/vic20.cpp +++ b/src/engine/platform/vic20.cpp @@ -22,11 +22,9 @@ #include #define rWrite(a,v) {regPool[(a)]=(v)&0xff; vic_sound_machine_store(vic,a,(v)&0xff);} -#define SAMP_DIVIDER 4 -const int chipDividers[4]={ - 128, 64, 32, 64 -}; +#define CHIP_DIVIDER 32 +#define SAMP_DIVIDER 4 const char* regCheatSheetVIC[]={ "CH1_Pitch", "0A", @@ -95,7 +93,6 @@ void DivPlatformVIC20::writeOutVol(int ch) { void DivPlatformVIC20::tick() { for (int i=0; i<4; i++) { - int CHIP_DIVIDER=chipDividers[i]; chan[i].std.next(); if (chan[i].std.hadVol) { int env=chan[i].std.vol; @@ -124,9 +121,13 @@ void DivPlatformVIC20::tick() { } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true); - printf("%d freq: %d\n",i,chan[i].freq); + if (i<3) { + chan[i].freq>>=(2-i); + } else { + chan[i].freq>>=1; + } if (chan[i].freq<1) chan[i].freq=1; - if (chan[i].freq>127) chan[i].freq=127; + if (chan[i].freq>127) chan[i].freq=0; if (isMuted[i]) chan[i].keyOn=false; if (chan[i].keyOn) { if (i<3) { @@ -152,7 +153,6 @@ void DivPlatformVIC20::tick() { } int DivPlatformVIC20::dispatch(DivCommand c) { - int CHIP_DIVIDER=chipDividers[c.chan]; switch (c.cmd) { case DIV_CMD_NOTE_ON: { DivInstrument* ins=parent->getIns(chan[c.chan].ins); From 1a4290f1c3ef53abd0e3bea218d609ffe824bb91 Mon Sep 17 00:00:00 2001 From: freq-mod <32672779+freq-mod@users.noreply.github.com> Date: Sat, 26 Mar 2022 19:55:20 +0100 Subject: [PATCH 413/637] somewhat improved system descriptions --- papers/doc/7-systems/ay8910.md | 6 +++--- papers/doc/7-systems/nes.md | 4 ++-- papers/doc/7-systems/opl.md | 9 ++++----- papers/doc/7-systems/opll.md | 2 +- papers/doc/7-systems/pce.md | 6 ++++-- papers/doc/7-systems/pcspkr.md | 2 +- papers/doc/7-systems/sms.md | 2 +- papers/doc/7-systems/wonderswan.md | 7 ++----- papers/doc/7-systems/ym2151.md | 2 +- papers/doc/7-systems/ym2610.md | 4 ++-- papers/doc/7-systems/ym2612.md | 2 +- 11 files changed, 22 insertions(+), 24 deletions(-) diff --git a/papers/doc/7-systems/ay8910.md b/papers/doc/7-systems/ay8910.md index 4d13a153a..b01d9657e 100644 --- a/papers/doc/7-systems/ay8910.md +++ b/papers/doc/7-systems/ay8910.md @@ -1,8 +1,8 @@ # General Instrument AY-3-8910 -this chip was used in several home computers (ZX Spectrum, MSX, Amstrad CPC, Atari ST, etc.), video game consoles (Intellivision and Vectrex), arcade boards and even slot machines! +this chip was used in many home computers (ZX Spectrum, MSX, Amstrad CPC, Atari ST, etc.), video game consoles (Intellivision and Vectrex), arcade boards and even slot machines! -the chip's powerful sound comes from the envelope... +It is a 3-channel PSG sound source. The chip's powerful sound comes from the envelope... AY-3-8914 variant was used in Intellivision, it's basically original AY with 4 level envelope volume per channel and different register format. @@ -39,4 +39,4 @@ AY-3-8914 variant was used in Intellivision, it's basically original AY with 4 l - in this mode the envelope period is set to the channel's notes, multiplied by a fraction. - `x` is the numerator. - `y` is the denominator. - - if `x` or `y` are 0 this will disable auto-envelope mode. \ No newline at end of file + - if `x` or `y` are 0 this will disable auto-envelope mode. diff --git a/papers/doc/7-systems/nes.md b/papers/doc/7-systems/nes.md index dc6eb20af..3de0e3076 100644 --- a/papers/doc/7-systems/nes.md +++ b/papers/doc/7-systems/nes.md @@ -2,7 +2,7 @@ the console from Nintendo that plays Super Mario Bros. and helped revive the agonizing video game market in the US during mid-80s. -also known as Famicom. +also known as Famicom. It is a five-channel PSG: first two channels play pulse wave with three different duty cycles, third is a fixed-volume triangle channel, fourth is a noise channel (can work in both pseudo-random and periodic modes) and fifth is a (D)PCM sample channel # effects @@ -15,4 +15,4 @@ also known as Famicom. - `14xy`: setup sweep down. - `x` is the time. - `y` is the shift. - - set to 0 to disable it. \ No newline at end of file + - set to 0 to disable it. diff --git a/papers/doc/7-systems/opl.md b/papers/doc/7-systems/opl.md index 1459eee4c..b019e3f98 100644 --- a/papers/doc/7-systems/opl.md +++ b/papers/doc/7-systems/opl.md @@ -5,15 +5,14 @@ a series of FM sound chips which were very popular in DOS land. it was so popula essentially a downgraded version of Yamaha's other FM chips, with only 2 operators per channel. however, it also had a drums mode, and later chips in the series added more waveforms (than just the typical sine) and even a 4-operator mode. -the original OPL was present as an expansion for the Commodore 64 and MSX computers (erm, a variant of it). it only had 9 channels and drums mode. +the original OPL (Yamaha YM3526) was present as an expansion for the Commodore 64 and MSX computers (erm, a variant of it). it only had 9 two-operator channels and drums mode. -its successor, the OPL2, added 3 more waveforms and was one of the more popular chips because it was present on the AdLib card for PC. +its successor, the OPL2 (Yamaha YM3812), added 3 more waveforms and was one of the more popular chips because it was present on the AdLib card for PC. later Creative would borrow the chip to make the Sound Blaster, and totally destroyed AdLib's dominance. -the OPL3 added 9 more channels, 4 more waveforms, 4-operator mode (pairing up to 12 channels to make up to six 4-operator channels), quadraphonic output (sadly Furnace only supports stereo) and some other things. -it was overkill. +the OPL3 (Yamaha YMF262) added 9 more channels, 4 more waveforms, rudimentary 4-operator mode (pairing up to 12 channels to make up to six 4-operator channels), quadraphonic output (sadly Furnace only supports stereo) and some other things. -afterwards everyone moved to Windows and software mixing... +afterwards everyone moved to Windows and software mixed PCM streaming... # effects diff --git a/papers/doc/7-systems/opll.md b/papers/doc/7-systems/opll.md index 880750b35..06f542f16 100644 --- a/papers/doc/7-systems/opll.md +++ b/papers/doc/7-systems/opll.md @@ -1,6 +1,6 @@ # Yamaha YM2413/OPLL -the YM2413, otherwise known as OPLL, is a cost-reduced FM synthesis sound chip manufactured by Yamaha Corporation and based on the Yamaha YM3812 sound chip (OPL2). thought OPL was downgraded enough? :p +the YM2413, otherwise known as OPLL, is a cost-reduced FM synthesis sound chip, based on the Yamaha YM3812 (OPL2). thought OPL was downgraded enough? :p # technical specifications diff --git a/papers/doc/7-systems/pce.md b/papers/doc/7-systems/pce.md index 5034e83fd..c061a9b7a 100644 --- a/papers/doc/7-systems/pce.md +++ b/papers/doc/7-systems/pce.md @@ -1,9 +1,11 @@ # PC Engine/TurboGrafx-16 -a console from NEC that attempted to enter the fierce battle between Nintendo and Sega, but because its capabilities are a mix of third and fourth generation, it failed to last long. +a console from NEC that, depending on a region: + attempted to enter the fierce battle between Nintendo and Sega, but because its capabilities are a mix of third and fourth generation, it failed to last long. (US and Europe) + was Nintendo's most fearsome rival, completely defeating Sega Mega Drive and defending itself against Super Famicom (Japan) it has 6 wavetable channels and the last two ones also double as noise channels. -furthermore, it has some PCM! +furthermore, it has some PCM and LFO! # effects diff --git a/papers/doc/7-systems/pcspkr.md b/papers/doc/7-systems/pcspkr.md index afaa4f82c..6c2e1a94c 100644 --- a/papers/doc/7-systems/pcspkr.md +++ b/papers/doc/7-systems/pcspkr.md @@ -1,6 +1,6 @@ # PC Speaker -40 years of one square beep - and still going! +40 years of one square beep - and still going! Single channel, no volume control... # effects diff --git a/papers/doc/7-systems/sms.md b/papers/doc/7-systems/sms.md index 61650ecf9..53560a976 100644 --- a/papers/doc/7-systems/sms.md +++ b/papers/doc/7-systems/sms.md @@ -2,7 +2,7 @@ the predecessor to Genesis. -surely had better graphics than NES, but its sound is subject of several jokes. +surely had better graphics than NES, but its sound (fairly weak, 4ch PSG with A-3 is a lowest tone) is subject of several jokes. this console is powered by a derivative of the Texas Instruments SN76489. diff --git a/papers/doc/7-systems/wonderswan.md b/papers/doc/7-systems/wonderswan.md index c5c0bafdd..44657decb 100644 --- a/papers/doc/7-systems/wonderswan.md +++ b/papers/doc/7-systems/wonderswan.md @@ -1,11 +1,8 @@ # WonderSwan -A handheld console released only in Japan by Bandai. Designed by the same -people behind Game Boy and Virtual Boy, it has lots of similar elements from -those two systems in the sound department. +A handheld console released only in Japan by Bandai. Designed by the same people behind Game Boy and Virtual Boy, it has lots of similar elements from those two systems in the sound department. -It has 4 wavetable channels, one channel could play PCM, the other has hardware -sweep and the other could play noise. +It has 4 wavetable channels, channel #2 could play PCM, channel #3 has hardware sweep and channel #4 could play noise. # effects diff --git a/papers/doc/7-systems/ym2151.md b/papers/doc/7-systems/ym2151.md index bf8b0d6b1..69eba0fb3 100644 --- a/papers/doc/7-systems/ym2151.md +++ b/papers/doc/7-systems/ym2151.md @@ -1,6 +1,6 @@ # Yamaha YM2151 -the sound chip powering several arcade boards and the Sharp X1/X68000. +the sound chip powering several arcade boards and the Sharp X1/X68000. Eight 4-op FM channels, with overpowered LFO and almost unused noise generator. it also was present on several pinball machines and synthesizers of the era, and later surpassed by the YM2414 (OPZ) present in the world-famous TX81Z. diff --git a/papers/doc/7-systems/ym2610.md b/papers/doc/7-systems/ym2610.md index 7eb66eb33..4515fde23 100644 --- a/papers/doc/7-systems/ym2610.md +++ b/papers/doc/7-systems/ym2610.md @@ -2,7 +2,7 @@ originally an arcade board, but SNK shortly adapted it to a rather expensive video game console with the world's biggest cartridges because some people liked the system so much they wanted a home version of it. -its soundchip is a 3-in-1: FM, YM2149 (AY-3-8910 clone) and 2 different format ADPCM in a single package! +its soundchip is a 4-in-1: 4ch 4-op FM, YM2149 (AY-3-8910 clone) and 2 different format ADPCM in a single package! # effects @@ -56,4 +56,4 @@ its soundchip is a 3-in-1: FM, YM2149 (AY-3-8910 clone) and 2 different format A - in this mode the envelope period is set to the channel's notes, multiplied by a fraction. - `x` is the numerator. - `y` is the denominator. - - if `x` or `y` are 0 this will disable auto-envelope mode. \ No newline at end of file + - if `x` or `y` are 0 this will disable auto-envelope mode. diff --git a/papers/doc/7-systems/ym2612.md b/papers/doc/7-systems/ym2612.md index bfe87cf53..b7dd8a16e 100644 --- a/papers/doc/7-systems/ym2612.md +++ b/papers/doc/7-systems/ym2612.md @@ -1,6 +1,6 @@ # Yamaha YM2612 -one of two chips that powered the Sega Genesis. +one of two chips that powered the Sega Genesis. It is a six-channel, four-operator FM synthesizer. Channel #6 can be turned into 8-bit PCM player. # effects From 4caa9376bce558d94a6ae4076a2f85302e0ad521 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 26 Mar 2022 17:30:26 -0500 Subject: [PATCH 414/637] GUI: what --- src/gui/insEdit.cpp | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 718eaf013..629fe4c49 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1055,7 +1055,23 @@ void FurnaceGUI::drawInsEdit() { } if (willDisplayOps) { if (settings.fmLayout==0) { - if (ImGui::BeginTable("FMOperators",14,ImGuiTableFlags_SizingStretchProp|ImGuiTableFlags_Borders)) { + if (ImGui::BeginTable("FMOperators",14,ImGuiTableFlags_SizingStretchProp|ImGuiTableFlags_BordersH|ImGuiTableFlags_BordersOuterV)) { + // configure columns + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c4",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c5",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c6",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c7",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c8",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c9",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c10",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c11",ImGuiTableColumnFlags_WidthStretch,0.4f); + ImGui::TableSetupColumn("c12",ImGuiTableColumnFlags_WidthStretch,0.6f); + ImGui::TableSetupColumn("c13",ImGuiTableColumnFlags_WidthFixed); + // header ImGui::TableNextRow(ImGuiTableRowFlags_Headers); ImGui::TableNextColumn(); @@ -1181,10 +1197,8 @@ void FurnaceGUI::drawInsEdit() { } ImGui::TableNextColumn(); - ImGui::Text("Preview here"); - if (ins->type!=DIV_INS_OPL && ins->type!=DIV_INS_OPZ) { - if (ImGui::Checkbox((ins->type==DIV_INS_OPLL)?FM_NAME(FM_EGS):"SSG On",&ssgOn)) { PARAMETER + if (ImGui::Checkbox("##SSGOn",&ssgOn)) { PARAMETER op.ssgEnv=(op.ssgEnv&7)|(ssgOn<<3); } if (ins->type==DIV_INS_FM) { @@ -1192,11 +1206,12 @@ void FurnaceGUI::drawInsEdit() { ImGui::SetTooltip("Only for OPN family chips"); } } - } - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::SliderScalar("##SSG",ImGuiDataType_U8,&ssgEnv,&_ZERO,&_SEVEN,ssgEnvTypes[ssgEnv])) { PARAMETER - op.ssgEnv=(op.ssgEnv&8)|(ssgEnv&7); + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::SliderScalar("##SSG",ImGuiDataType_U8,&ssgEnv,&_ZERO,&_SEVEN,ssgEnvTypes[ssgEnv])) { PARAMETER + op.ssgEnv=(op.ssgEnv&8)|(ssgEnv&7); + } } } From 7237e8fb394dadc3db02d692123379ce618369f0 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 26 Mar 2022 18:30:08 -0500 Subject: [PATCH 415/637] GUI: add a space i am lazy --- src/gui/insEdit.cpp | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 629fe4c49..930bcccf0 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1055,22 +1055,23 @@ void FurnaceGUI::drawInsEdit() { } if (willDisplayOps) { if (settings.fmLayout==0) { - if (ImGui::BeginTable("FMOperators",14,ImGuiTableFlags_SizingStretchProp|ImGuiTableFlags_BordersH|ImGuiTableFlags_BordersOuterV)) { + if (ImGui::BeginTable("FMOperators",15,ImGuiTableFlags_SizingStretchProp|ImGuiTableFlags_BordersH|ImGuiTableFlags_BordersOuterV)) { // configure columns - ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn("c4",ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn("c5",ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn("c6",ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn("c7",ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn("c8",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); // op name + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed); // ar + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthFixed); // dr + ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthFixed); // sl + ImGui::TableSetupColumn("c4",ImGuiTableColumnFlags_WidthFixed); // d2r + ImGui::TableSetupColumn("c5",ImGuiTableColumnFlags_WidthFixed); // rr + ImGui::TableSetupColumn("c6",ImGuiTableColumnFlags_WidthFixed); // -separator- + ImGui::TableSetupColumn("c7",ImGuiTableColumnFlags_WidthFixed); // tl + ImGui::TableSetupColumn("c8",ImGuiTableColumnFlags_WidthFixed); // ... ImGui::TableSetupColumn("c9",ImGuiTableColumnFlags_WidthFixed); ImGui::TableSetupColumn("c10",ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn("c11",ImGuiTableColumnFlags_WidthStretch,0.4f); - ImGui::TableSetupColumn("c12",ImGuiTableColumnFlags_WidthStretch,0.6f); - ImGui::TableSetupColumn("c13",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c11",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c12",ImGuiTableColumnFlags_WidthStretch,0.4f); + ImGui::TableSetupColumn("c13",ImGuiTableColumnFlags_WidthStretch,0.6f); + ImGui::TableSetupColumn("c14",ImGuiTableColumnFlags_WidthFixed); // header ImGui::TableNextRow(ImGuiTableRowFlags_Headers); @@ -1089,6 +1090,7 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); ImGui::TextUnformatted(FM_NAME(FM_RR)); ImGui::TableNextColumn(); + ImGui::TableNextColumn(); ImGui::TextUnformatted(FM_NAME(FM_TL)); ImGui::TableNextColumn(); if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { @@ -1169,6 +1171,9 @@ void FurnaceGUI::drawInsEdit() { op.rr&=15; P(ImGui::VSliderScalar("##RR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.rr,&_ZERO,&_FIFTEEN)); + ImGui::TableNextColumn(); + ImGui::Dummy(ImVec2(4.0f*dpiScale,2.0f*dpiScale)); + ImGui::TableNextColumn(); op.tl&=maxTl; P(ImGui::VSliderScalar("##TL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.tl,&maxTl,&_ZERO)); From 1022d64bd0ecb8ae884c5922f9334d2370d1654f Mon Sep 17 00:00:00 2001 From: nicco1690 <78063037+nicco1690@users.noreply.github.com> Date: Sat, 26 Mar 2022 21:32:29 -0400 Subject: [PATCH 416/637] Create VIC-20 documentation funny low-pass sound chip waves go brr --- papers/doc/7-systems/vic20.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 papers/doc/7-systems/vic20.md diff --git a/papers/doc/7-systems/vic20.md b/papers/doc/7-systems/vic20.md new file mode 100644 index 000000000..b221a3cbd --- /dev/null +++ b/papers/doc/7-systems/vic20.md @@ -0,0 +1,11 @@ +# Commodore VIC-20 + +The Commodore VIC-20 was Commodore's major attempt at making a personal home computer, and is the percursor to the Commodore 64. The VIC-20 was also known as the VC-20 in Germany, and the VIC-1001 in Japan. + +It has 4 PSG voices that has a limited but wide tuning range, and like the SN76489, the last voice is dedicated to playing pseudo-white noise. Every voice on the VIC-20 has a high-pass and low-pass filter applied to it, which is likely how Rob Yannes got the inspiration to put custom filter modes on the C64's SID. + +The 3 pulse wave channels also have different octaves that they can play notes on (not currently emulated in Furnace version dev71). The first channel is the bass channel, and it can play notes from octaves 2 to octaves 4. The next is the 'mid/chord' channel, and it plays notes from octaves 3 to 5. And rather obviously, the 3rd pulse channel is typically the lead channel, can play notes from octaves 4 to 6. + +## effect commands + + - `10xx` Switch waveform (Only the values 00 though 0F are unique. Everything else is a copy. For example, `1006` is the same as `10f6`.) From 8c6c3f170732521b9d2de8fac98df21f359c7ce9 Mon Sep 17 00:00:00 2001 From: nicco1690 <78063037+nicco1690@users.noreply.github.com> Date: Sat, 26 Mar 2022 21:34:32 -0400 Subject: [PATCH 417/637] Add the VIC-20 to the systems list in README.md --- papers/doc/7-systems/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/papers/doc/7-systems/README.md b/papers/doc/7-systems/README.md index 319314401..5e4e101b0 100644 --- a/papers/doc/7-systems/README.md +++ b/papers/doc/7-systems/README.md @@ -27,5 +27,6 @@ this is a list of systems that Furnace supports, including each system's effects - [Yamaha OPL](opl.md) - [PC Speaker](pcspkr.md) - [Commodore PET](pet.md) +- [Commodore VIC-20](vic20.md) Furnace also reads .dmf files with the [Yamaha YMU759](ymu759.md) system, but... From 73536c069104461823120ce933e2780acd5bbda7 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 26 Mar 2022 20:55:43 -0500 Subject: [PATCH 418/637] AY: add effects to write to I/O ports --- papers/doc/7-systems/ay8910.md | 4 +++ src/engine/dispatch.h | 2 ++ src/engine/platform/ay.cpp | 57 +++++++++++++++++++++++++++++----- src/engine/platform/ay.h | 4 +++ src/engine/platform/ay8930.cpp | 57 +++++++++++++++++++++++++++++----- src/engine/platform/ay8930.h | 4 +++ src/engine/playback.cpp | 14 +++++++++ 7 files changed, 128 insertions(+), 14 deletions(-) diff --git a/papers/doc/7-systems/ay8910.md b/papers/doc/7-systems/ay8910.md index b01d9657e..5df69eb65 100644 --- a/papers/doc/7-systems/ay8910.md +++ b/papers/doc/7-systems/ay8910.md @@ -40,3 +40,7 @@ AY-3-8914 variant was used in Intellivision, it's basically original AY with 4 l - `x` is the numerator. - `y` is the denominator. - if `x` or `y` are 0 this will disable auto-envelope mode. +- `2Exx`: write to I/O port A. + - this changes the port's mode to "write". make sure you have connected something to it. +- `2Fxx`: write to I/O port B. + - this changes the port's mode to "write". make sure you have connected something to it. \ No newline at end of file diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index c6486f26b..a3cd1e810 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -101,6 +101,8 @@ enum DivDispatchCmds { DIV_CMD_AY_NOISE_MASK_AND, DIV_CMD_AY_NOISE_MASK_OR, DIV_CMD_AY_AUTO_ENVELOPE, + DIV_CMD_AY_IO_WRITE, + DIV_CMD_AY_AUTO_PWM, DIV_CMD_SAA_ENVELOPE, diff --git a/src/engine/platform/ay.cpp b/src/engine/platform/ay.cpp index 526b3fbd2..3e53e8fe3 100644 --- a/src/engine/platform/ay.cpp +++ b/src/engine/platform/ay.cpp @@ -19,6 +19,7 @@ #include "ay.h" #include "../engine.h" +#include "../../ta-log.h" #include "sound/ay8910.h" #include #include @@ -98,6 +99,12 @@ const char* DivPlatformAY8910::getEffectName(unsigned char effect) { case 0x29: return "29xy: Set auto-envelope (x: numerator; y: denominator)"; break; + case 0x2e: + return "2Exx: Write to I/O port A"; + break; + case 0x2f: + return "2Fxx: Write to I/O port B"; + break; } return NULL; } @@ -141,6 +148,30 @@ void DivPlatformAY8910::acquire(short* bufL, short* bufR, size_t start, size_t l } } +void DivPlatformAY8910::updateOutSel(bool immediate) { + if (immediate) { + immWrite(0x07, + ~((chan[0].psgMode&1)| + ((chan[1].psgMode&1)<<1)| + ((chan[2].psgMode&1)<<2)| + ((chan[0].psgMode&2)<<2)| + ((chan[1].psgMode&2)<<3)| + ((chan[2].psgMode&2)<<4)| + ((!ioPortA)<<6)| + ((!ioPortB)<<7))); + } else { + rWrite(0x07, + ~((chan[0].psgMode&1)| + ((chan[1].psgMode&1)<<1)| + ((chan[2].psgMode&1)<<2)| + ((chan[0].psgMode&2)<<2)| + ((chan[1].psgMode&2)<<3)| + ((chan[2].psgMode&2)<<4)| + ((!ioPortA)<<6)| + ((!ioPortB)<<7))); + } +} + void DivPlatformAY8910::tick() { // PSG for (int i=0; i<3; i++) { @@ -221,13 +252,7 @@ void DivPlatformAY8910::tick() { } } - rWrite(0x07, - ~((chan[0].psgMode&1)| - ((chan[1].psgMode&1)<<1)| - ((chan[2].psgMode&1)<<2)| - ((chan[0].psgMode&2)<<2)| - ((chan[1].psgMode&2)<<3)| - ((chan[2].psgMode&2)<<4))); + updateOutSel(); if (ayEnvSlide!=0) { ayEnvSlideLow+=ayEnvSlide; @@ -401,6 +426,19 @@ int DivPlatformAY8910::dispatch(DivCommand c) { chan[c.chan].autoEnvDen=c.value&15; chan[c.chan].freqChanged=true; break; + case DIV_CMD_AY_IO_WRITE: + if (c.value) { // port B + ioPortB=true; + portBVal=c.value2; + logI("AY I/O port B write: %x\n",portBVal); + } else { // port A + ioPortA=true; + portAVal=c.value2; + logI("AY I/O port A write: %x\n",portAVal); + } + updateOutSel(true); + immWrite(14+(c.value?1:0),(c.value?portBVal:portAVal)); + break; case DIV_ALWAYS_SET_VOLUME: return 0; break; @@ -486,6 +524,11 @@ void DivPlatformAY8910::reset() { delay=0; extMode=false; + + ioPortA=false; + ioPortB=false; + portAVal=0; + portBVal=0; } bool DivPlatformAY8910::isStereo() { diff --git a/src/engine/platform/ay.h b/src/engine/platform/ay.h index 41c3c404e..498c75e6f 100644 --- a/src/engine/platform/ay.h +++ b/src/engine/platform/ay.h @@ -65,6 +65,8 @@ class DivPlatformAY8910: public DivDispatch { bool extMode; bool stereo, sunsoft, intellivision; + bool ioPortA, ioPortB; + unsigned char portAVal, portBVal; short oldWrites[16]; short pendingWrites[16]; @@ -75,6 +77,8 @@ class DivPlatformAY8910: public DivDispatch { short* ayBuf[3]; size_t ayBufLen; + void updateOutSel(bool immediate=false); + friend void putDispatchChan(void*,int,int); public: diff --git a/src/engine/platform/ay8930.cpp b/src/engine/platform/ay8930.cpp index d87045a64..3e114e61b 100644 --- a/src/engine/platform/ay8930.cpp +++ b/src/engine/platform/ay8930.cpp @@ -19,6 +19,7 @@ #include "ay8930.h" #include "../engine.h" +#include "../../ta-log.h" #include "sound/ay8910.h" #include #include @@ -99,6 +100,12 @@ const char* DivPlatformAY8930::getEffectName(unsigned char effect) { case 0x29: return "29xy: Set auto-envelope (x: numerator; y: denominator)"; break; + case 0x2e: + return "2Exx: Write to I/O port A"; + break; + case 0x2f: + return "2Fxx: Write to I/O port B"; + break; } return NULL; } @@ -141,6 +148,30 @@ void DivPlatformAY8930::acquire(short* bufL, short* bufR, size_t start, size_t l } } +void DivPlatformAY8930::updateOutSel(bool immediate) { + if (immediate) { + immWrite(0x07, + ~((chan[0].psgMode&1)| + ((chan[1].psgMode&1)<<1)| + ((chan[2].psgMode&1)<<2)| + ((chan[0].psgMode&2)<<2)| + ((chan[1].psgMode&2)<<3)| + ((chan[2].psgMode&2)<<4)| + ((!ioPortA)<<6)| + ((!ioPortB)<<7))); + } else { + rWrite(0x07, + ~((chan[0].psgMode&1)| + ((chan[1].psgMode&1)<<1)| + ((chan[2].psgMode&1)<<2)| + ((chan[0].psgMode&2)<<2)| + ((chan[1].psgMode&2)<<3)| + ((chan[2].psgMode&2)<<4)| + ((!ioPortA)<<6)| + ((!ioPortB)<<7))); + } +} + const unsigned char regPeriodL[3]={ 0x0b, 0x10, 0x12 }; @@ -262,13 +293,7 @@ void DivPlatformAY8930::tick() { } } - rWrite(0x07, - ~((chan[0].psgMode&1)| - ((chan[1].psgMode&1)<<1)| - ((chan[2].psgMode&1)<<2)| - ((chan[0].psgMode&2)<<2)| - ((chan[1].psgMode&2)<<3)| - ((chan[2].psgMode&2)<<4))); + updateOutSel(); for (int i=0; i<32; i++) { if (pendingWrites[i]!=oldWrites[i]) { @@ -420,6 +445,19 @@ int DivPlatformAY8930::dispatch(DivCommand c) { chan[c.chan].autoEnvDen=c.value&15; chan[c.chan].freqChanged=true; break; + case DIV_CMD_AY_IO_WRITE: + if (c.value) { // port B + ioPortB=true; + portBVal=c.value2; + logI("AY I/O port B write: %x\n",portBVal); + } else { // port A + ioPortA=true; + portAVal=c.value2; + logI("AY I/O port A write: %x\n",portAVal); + } + updateOutSel(true); + immWrite(14+(c.value?1:0),(c.value?portBVal:portAVal)); + break; case DIV_ALWAYS_SET_VOLUME: return 0; break; @@ -499,6 +537,11 @@ void DivPlatformAY8930::reset() { extMode=false; bank=false; + ioPortA=false; + ioPortB=false; + portAVal=0; + portBVal=0; + immWrite(0x0d,0xa0); immWrite(0x19,2); // and mask immWrite(0x1a,0x00); // or mask diff --git a/src/engine/platform/ay8930.h b/src/engine/platform/ay8930.h index 2568fd7a9..6bd51bdcd 100644 --- a/src/engine/platform/ay8930.h +++ b/src/engine/platform/ay8930.h @@ -54,6 +54,8 @@ class DivPlatformAY8930: public DivDispatch { int delay; bool extMode, stereo; + bool ioPortA, ioPortB; + unsigned char portAVal, portBVal; short oldWrites[32]; short pendingWrites[32]; @@ -64,6 +66,8 @@ class DivPlatformAY8930: public DivDispatch { short* ayBuf[3]; size_t ayBufLen; + void updateOutSel(bool immediate=false); + friend void putDispatchChan(void*,int,int); public: diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 3a64af7a9..aea9e1f8b 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -109,6 +109,8 @@ const char* cmdName[DIV_CMD_MAX]={ "AY_NOISE_MASK_AND", "AY_NOISE_MASK_OR", "AY_AUTO_ENVELOPE", + "AY_IO_WRITE", + "AY_AUTO_PWM", "SAA_ENVELOPE", @@ -613,6 +615,12 @@ bool DivEngine::perSystemPostEffect(int ch, unsigned char effect, unsigned char case 0x29: // auto-envelope dispatchCmd(DivCommand(DIV_CMD_AY_AUTO_ENVELOPE,ch,effectVal)); break; + case 0x2e: // I/O port A + dispatchCmd(DivCommand(DIV_CMD_AY_IO_WRITE,ch,0,effectVal)); + break; + case 0x2f: // I/O port B + dispatchCmd(DivCommand(DIV_CMD_AY_IO_WRITE,ch,1,effectVal)); + break; default: return false; } @@ -1031,6 +1039,12 @@ void DivEngine::processRow(int i, bool afterDelay) { chan[i].inPorta=false; if (!song.arpNonPorta) dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0)); break; + case 0xf3: // fine volume ramp up + chan[i].volSpeed=effectVal; + break; + case 0xf4: // fine volume ramp down + chan[i].volSpeed=-effectVal; + 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)); From 3ea9c0360f39a2f411b25d6cbc0b114ec7aed7d9 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 26 Mar 2022 20:58:33 -0500 Subject: [PATCH 419/637] implement F3xx and F4xx for fine vol slides --- papers/doc/3-pattern/effects.md | 2 ++ src/engine/engine.cpp | 4 ++++ src/gui/pattern.cpp | 4 ++-- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/papers/doc/3-pattern/effects.md b/papers/doc/3-pattern/effects.md index d544c1ea6..7a0588eb3 100644 --- a/papers/doc/3-pattern/effects.md +++ b/papers/doc/3-pattern/effects.md @@ -54,6 +54,8 @@ however, effects are continuous, which means you only need to type it once and t - `F0xx`: change song Hz by BPM value. - `F1xx`: single tick slide up. - `F2xx`: single tick slide down. +- `F3xx`: fine volume slide up (64x slower than `0Axy`). +- `F4xx`: fine volume slide down (64x slower than `0Axy`). - `F8xx`: single tick volume slide up. - `F9xx`: single tick volume slide down. - `FAxy`: fast volume slide (4x faster than `0Axy`). diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index e9de013af..eefefdec8 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -104,6 +104,10 @@ const char* DivEngine::getEffectDesc(unsigned char effect, int chan) { return "F1xx: Single tick note slide up"; case 0xf2: return "F2xx: Single tick note slide down"; + case 0xf3: + return "F3xx: Fine volume slide up"; + case 0xf4: + return "F4xx: Fine volume slide down"; case 0xf8: return "F8xx: Single tick volume slide up"; case 0xf9: diff --git a/src/gui/pattern.cpp b/src/gui/pattern.cpp index aed8890ad..79612827c 100644 --- a/src/gui/pattern.cpp +++ b/src/gui/pattern.cpp @@ -65,8 +65,8 @@ const FurnaceGUIColors extFxColors[32]={ 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_VOLUME, // F3 + GUI_COLOR_PATTERN_EFFECT_VOLUME, // F4 GUI_COLOR_PATTERN_EFFECT_INVALID, // F5 GUI_COLOR_PATTERN_EFFECT_INVALID, // F6 GUI_COLOR_PATTERN_EFFECT_INVALID, // F7 From 00876a461a4a8d0188f5fff9f8e6ba1c41438062 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 26 Mar 2022 21:16:15 -0500 Subject: [PATCH 420/637] update effect list --- papers/doc/3-pattern/effects.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/papers/doc/3-pattern/effects.md b/papers/doc/3-pattern/effects.md index 7a0588eb3..773d30373 100644 --- a/papers/doc/3-pattern/effects.md +++ b/papers/doc/3-pattern/effects.md @@ -47,7 +47,7 @@ however, effects are continuous, which means you only need to type it once and t - `ECxx`: note off after `xx` ticks. - `EDxx`: delay note by `xx` ticks. - `EExx`: send external command. - - currently not used, but this eventually will allow you to do special things after I add VGM export. + - this effect is currently incomplete. - `EFxx`: add or subtract global pitch. - this effect is rather weird. use with caution. - `80` is center. From 5c11150b877df97f4a0b28607351ff8132977adc Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 26 Mar 2022 21:43:15 -0500 Subject: [PATCH 421/637] T O D O --- src/engine/platform/gb.cpp | 1 + src/engine/playback.cpp | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp index ea6ad7719..d25cc2118 100644 --- a/src/engine/platform/gb.cpp +++ b/src/engine/platform/gb.cpp @@ -289,6 +289,7 @@ int DivPlatformGB::dispatch(DivCommand c) { chan[c.chan].ins=c.value; if (c.chan!=2) { chan[c.chan].vol=parent->getIns(chan[c.chan].ins)->gb.envVol; + // TODO: also change envelope values } } break; diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index aea9e1f8b..8042e2cde 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -905,8 +905,16 @@ void DivEngine::processRow(int i, bool afterDelay) { break; case 0x07: // tremolo // TODO + // this effect is really weird. i thought it would alter the tremolo depth but turns out it's completely different + // this is how it works: + // - 07xy enables tremolo + // - when enabled, a "low" boundary is calculated based on the current volume + // - then a volume slide down starts to the low boundary, and then when this is reached a volume slide up begins + // - this process repeats until 0700 or 0Axy are found + // - note that a volume value does not stop tremolo - instead it glitches this whole thing up break; case 0x0a: // volume ramp + // TODO: non-0x-or-x0 value should be treated as 00 if (effectVal!=0) { if ((effectVal&15)!=0) { chan[i].volSpeed=-(effectVal&15)*64; From 9b6e582f8d97350367bda600dd67793d2599bf21 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 26 Mar 2022 22:15:15 -0500 Subject: [PATCH 422/637] dev72 - two more compat flags --- papers/format.md | 5 ++++- src/engine/engine.h | 4 ++-- src/engine/fileOps.cpp | 25 ++++++++++++++++++++----- src/engine/platform/gb.cpp | 7 +++++-- src/engine/playback.cpp | 2 +- src/engine/song.h | 6 +++++- src/gui/compatFlags.cpp | 8 ++++++++ 7 files changed, 45 insertions(+), 12 deletions(-) diff --git a/papers/format.md b/papers/format.md index fc954ba05..d61e94d43 100644 --- a/papers/format.md +++ b/papers/format.md @@ -29,6 +29,7 @@ furthermore, an `or reserved` indicates this field is always present, but is res the format versions are: +- 72: Furnace dev72 - 71: Furnace dev71 - 70: Furnace dev70 - 69: Furnace dev69 @@ -242,7 +243,9 @@ size | description 1 | no slides on first tick (>=71) or reserved 1 | next row reset arp pos (>=71) or reserved 1 | ignore jump at end (>=71) or reserved - 28 | reserved + 1 | buggy portamento after slide (>=72) or reserved + 1 | new ins affects envelope (Game Boy) (>=72) or reserved + 26 | reserved ``` # instrument diff --git a/src/engine/engine.h b/src/engine/engine.h index 52d400ca0..24c3e1352 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -42,8 +42,8 @@ #define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock(); #define BUSY_END isBusy.unlock(); softLocked=false; -#define DIV_VERSION "dev71" -#define DIV_ENGINE_VERSION 71 +#define DIV_VERSION "dev72" +#define DIV_ENGINE_VERSION 72 // for imports #define DIV_VERSION_MOD 0xff01 diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 4697d338a..eca105a39 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -148,6 +148,8 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { ds.noSlidesOnFirstTick=false; ds.rowResetsArpPos=false; ds.ignoreJumpAtEnd=true; + ds.buggyPortaAfterSlide=true; + ds.gbInsAffectsEnvelope=true; // 1.1 compat flags if (ds.version>24) { @@ -833,6 +835,10 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { ds.rowResetsArpPos=false; ds.ignoreJumpAtEnd=true; } + if (ds.version<72) { + ds.buggyPortaAfterSlide=true; + ds.gbInsAffectsEnvelope=false; + } ds.isDMF=false; reader.readS(); // reserved @@ -1080,15 +1086,22 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { // extended compat flags ds.brokenSpeedSel=reader.readC(); if (ds.version>=71) { - song.noSlidesOnFirstTick=reader.readC(); - song.rowResetsArpPos=reader.readC(); - song.ignoreJumpAtEnd=reader.readC(); + ds.noSlidesOnFirstTick=reader.readC(); + ds.rowResetsArpPos=reader.readC(); + ds.ignoreJumpAtEnd=reader.readC(); } else { reader.readC(); reader.readC(); reader.readC(); } - for (int i=0; i<28; i++) { + if (ds.version>=72) { + ds.buggyPortaAfterSlide=reader.readC(); + ds.gbInsAffectsEnvelope=reader.readC(); + } else { + reader.readC(); + reader.readC(); + } + for (int i=0; i<26; i++) { reader.readC(); } } @@ -1939,7 +1952,9 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { w->writeC(song.noSlidesOnFirstTick); w->writeC(song.rowResetsArpPos); w->writeC(song.ignoreJumpAtEnd); - for (int i=0; i<28; i++) { + w->writeC(song.buggyPortaAfterSlide); + w->writeC(song.gbInsAffectsEnvelope); + for (int i=0; i<26; i++) { w->writeC(0); } diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp index d25cc2118..d1d580c84 100644 --- a/src/engine/platform/gb.cpp +++ b/src/engine/platform/gb.cpp @@ -288,8 +288,11 @@ int DivPlatformGB::dispatch(DivCommand c) { if (chan[c.chan].ins!=c.value || c.value2==1) { chan[c.chan].ins=c.value; if (c.chan!=2) { - chan[c.chan].vol=parent->getIns(chan[c.chan].ins)->gb.envVol; - // TODO: also change envelope values + DivInstrument* ins=parent->getIns(chan[c.chan].ins); + chan[c.chan].vol=ins->gb.envVol; + if (parent->song.gbInsAffectsEnvelope) { + rWrite(16+c.chan*5+2,((chan[c.chan].vol<<4))|(ins->gb.envLen&7)|((ins->gb.envDir&1)<<3)); + } } } break; diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 8042e2cde..71fd58ddd 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -882,7 +882,7 @@ void DivEngine::processRow(int i, bool afterDelay) { chan[i].inPorta=false; dispatchCmd(DivCommand(DIV_CMD_PRE_PORTA,i,false,0)); } else { - if (chan[i].note==chan[i].oldNote && !chan[i].inPorta) { + if (chan[i].note==chan[i].oldNote && !chan[i].inPorta && song.buggyPortaAfterSlide) { chan[i].portaNote=chan[i].note; chan[i].portaSpeed=-1; } else { diff --git a/src/engine/song.h b/src/engine/song.h index 19a89cfa4..9142636e3 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -305,6 +305,8 @@ struct DivSong { bool noSlidesOnFirstTick; bool rowResetsArpPos; bool ignoreJumpAtEnd; + bool buggyPortaAfterSlide; + bool gbInsAffectsEnvelope; DivOrders orders; std::vector ins; @@ -381,7 +383,9 @@ struct DivSong { brokenSpeedSel(false), noSlidesOnFirstTick(false), rowResetsArpPos(false), - ignoreJumpAtEnd(false) { + ignoreJumpAtEnd(false), + buggyPortaAfterSlide(false), + gbInsAffectsEnvelope(true) { for (int i=0; i<32; i++) { system[i]=DIV_SYSTEM_NULL; systemVol[i]=64; diff --git a/src/gui/compatFlags.cpp b/src/gui/compatFlags.cpp index ca0538ea6..91aa34c7e 100644 --- a/src/gui/compatFlags.cpp +++ b/src/gui/compatFlags.cpp @@ -97,6 +97,14 @@ void FurnaceGUI::drawCompatFlags() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("if this is on, a jump to next row effect will not take place when it is on the last order of a song."); } + ImGui::Checkbox("Buggy portamento after pitch slide",&e->song.buggyPortaAfterSlide); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("simulates a bug in where portamento does not work after sliding."); + } + ImGui::Checkbox("Apply Game Boy envelope on note-less instrument change",&e->song.gbInsAffectsEnvelope); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("if this is on, an instrument change will also affect the envelope."); + } ImGui::Text("Loop modality:"); if (ImGui::RadioButton("Reset channels",e->song.loopModality==0)) { From b514ee30da4a9a39dd82e3f162d5ae0f5d02a071 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 26 Mar 2022 22:34:11 -0500 Subject: [PATCH 423/637] MOD import: non-linear pitch --- src/engine/engine.cpp | 1 + src/engine/fileOps.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index eefefdec8..4973b44f6 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -962,6 +962,7 @@ void DivEngine::reset() { chan[i]=DivChannelState(); if (idispatch(DivCommand(DIV_CMD_GET_VOLMAX,dispatchChanOfChan[i]))<<8)|0xff; chan[i].volume=chan[i].volMax; + if (!song.linearPitch) chan[i].vibratoFine=4; } extValue=0; extValuePresent=0; diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index eca105a39..d9806db57 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -1312,6 +1312,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { DivSong ds; ds.tuning=436.0; ds.version=DIV_VERSION_MOD; + ds.linearPitch=false; ds.noSlidesOnFirstTick=true; ds.rowResetsArpPos=true; ds.ignoreJumpAtEnd=false; From 154ef3f9a3aaaf9fbee3e90f713882c88fc4e543 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 26 Mar 2022 23:39:20 -0500 Subject: [PATCH 424/637] Amiga: filter emulation --- papers/doc/7-systems/amiga.md | 2 +- src/engine/dispatch.h | 2 ++ src/engine/fileOps.cpp | 5 +++- src/engine/platform/amiga.cpp | 43 ++++++++++++++++++++++++++++++----- src/engine/platform/amiga.h | 6 +++++ src/engine/playback.cpp | 11 +++++++++ 6 files changed, 61 insertions(+), 8 deletions(-) diff --git a/papers/doc/7-systems/amiga.md b/papers/doc/7-systems/amiga.md index fba62c430..713300c29 100644 --- a/papers/doc/7-systems/amiga.md +++ b/papers/doc/7-systems/amiga.md @@ -6,4 +6,4 @@ in this very computer music trackers were born... # effects -none. as of this moment the Amiga doesn't need any effects in particular, but some may be added in a future. +- `10xx`: toggle low-pass filter. `0` turns it off and `1` turns it on. \ No newline at end of file diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index a3cd1e810..b535df52d 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -106,6 +106,8 @@ enum DivDispatchCmds { DIV_CMD_SAA_ENVELOPE, + DIV_CMD_AMIGA_FILTER, + DIV_CMD_LYNX_LFSR_LOAD, DIV_CMD_QSOUND_ECHO_FEEDBACK, diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index d9806db57..0e82b6b79 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -1573,6 +1573,9 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { fxTyp=fxVal>>4; fxVal&=0x0f; switch (fxTyp) { + case 0: + writeFxCol(0x10,!fxVal); + break; case 1: // single note slide up case 2: // single note slide down writeFxCol(fxTyp-1+0xf1,fxVal); @@ -1613,7 +1616,7 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { ds.systemLen=(chCount+3)/4; for(int i=0; i1 || bypassLimits)?2:0); // PAL } for(int i=0; i=0 && chan[i].samplesong.sampleLen) { chan[i].audSub-=AMIGA_DIVIDER; @@ -102,14 +112,20 @@ void DivPlatformAmiga::acquire(short* bufL, short* bufR, size_t start, size_t le } if (!isMuted[i]) { if (i==0 || i==3) { - bufL[h]+=((chan[i].audDat*chan[i].outVol)*sep1)>>7; - bufR[h]+=((chan[i].audDat*chan[i].outVol)*sep2)>>7; + outL+=((chan[i].audDat*chan[i].outVol)*sep1)>>7; + outR+=((chan[i].audDat*chan[i].outVol)*sep2)>>7; } else { - bufL[h]+=((chan[i].audDat*chan[i].outVol)*sep2)>>7; - bufR[h]+=((chan[i].audDat*chan[i].outVol)*sep1)>>7; + outL+=((chan[i].audDat*chan[i].outVol)*sep2)>>7; + outR+=((chan[i].audDat*chan[i].outVol)*sep1)>>7; } } } + filter[0][0]+=(filtConst*(outL-filter[0][0]))>>12; + filter[0][1]+=(filtConst*(filter[0][0]-filter[0][1]))>>12; + filter[1][0]+=(filtConst*(outR-filter[1][0]))>>12; + filter[1][1]+=(filtConst*(filter[1][0]-filter[1][1]))>>12; + bufL[h]=filter[0][1]; + bufR[h]=filter[1][1]; } } @@ -299,6 +315,10 @@ int DivPlatformAmiga::dispatch(DivCommand c) { chan[c.chan].audPos=c.value; chan[c.chan].setPos=true; break; + case DIV_CMD_AMIGA_FILTER: + filterOn=c.value; + filtConst=filterOn?filtConstOn:filtConstOff; + break; case DIV_CMD_GET_VOLMAX: return 64; break; @@ -332,7 +352,11 @@ void* DivPlatformAmiga::getChanState(int ch) { void DivPlatformAmiga::reset() { for (int i=0; i<4; i++) { chan[i]=DivPlatformAmiga::Channel(); + filter[0][i]=0; + filter[1][i]=0; } + filterOn=false; + filtConst=filterOn?filtConstOn:filtConstOff; } bool DivPlatformAmiga::isStereo() { @@ -372,6 +396,13 @@ void DivPlatformAmiga::setFlags(unsigned int flags) { sep2=127-((flags>>8)&127); amigaModel=flags&2; bypassLimits=flags&4; + if (amigaModel) { + filtConstOff=4000; + filtConstOn=sin(M_PI*8000.0/(double)rate)*4096.0; + } else { + filtConstOff=sin(M_PI*16000.0/(double)rate)*4096.0; + filtConstOn=sin(M_PI*5500.0/(double)rate)*4096.0; + } } int DivPlatformAmiga::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { diff --git a/src/engine/platform/amiga.h b/src/engine/platform/amiga.h index 02dcb3de9..d73918e96 100644 --- a/src/engine/platform/amiga.h +++ b/src/engine/platform/amiga.h @@ -68,6 +68,11 @@ class DivPlatformAmiga: public DivDispatch { bool isMuted[4]; bool bypassLimits; bool amigaModel; + bool filterOn; + + int filter[2][4]; + int filtConst; + int filtConstOff, filtConstOn; int sep1, sep2; @@ -88,6 +93,7 @@ class DivPlatformAmiga: public DivDispatch { void notifyWaveChange(int wave); void notifyInsDeletion(void* ins); const char** getRegisterSheet(); + const char* getEffectName(unsigned char effect); int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); void quit(); }; diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 71fd58ddd..7d089e609 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -113,6 +113,8 @@ const char* cmdName[DIV_CMD_MAX]={ "AY_AUTO_PWM", "SAA_ENVELOPE", + + "AMIGA_FILTER", "LYNX_LFSR_LOAD", @@ -649,6 +651,15 @@ bool DivEngine::perSystemPostEffect(int ch, unsigned char effect, unsigned char return false; } break; + case DIV_SYSTEM_AMIGA: + switch (effect) { + case 0x10: // toggle filter + dispatchCmd(DivCommand(DIV_CMD_AMIGA_FILTER,ch,effectVal)); + break; + default: + return false; + } + break; case DIV_SYSTEM_SEGAPCM: case DIV_SYSTEM_SEGAPCM_COMPAT: switch (effect) { From 08dd693fa0f8a125637cd8bd523e55ecd400eee7 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 27 Mar 2022 00:02:17 -0500 Subject: [PATCH 425/637] Amiga: add AM/PM effects --- papers/doc/7-systems/amiga.md | 6 +++++- src/engine/dispatch.h | 2 ++ src/engine/platform/amiga.cpp | 21 +++++++++++++++++++++ src/engine/platform/amiga.h | 4 +++- src/engine/playback.cpp | 8 ++++++++ 5 files changed, 39 insertions(+), 2 deletions(-) diff --git a/papers/doc/7-systems/amiga.md b/papers/doc/7-systems/amiga.md index 713300c29..97c1151f6 100644 --- a/papers/doc/7-systems/amiga.md +++ b/papers/doc/7-systems/amiga.md @@ -6,4 +6,8 @@ in this very computer music trackers were born... # effects -- `10xx`: toggle low-pass filter. `0` turns it off and `1` turns it on. \ No newline at end of file +- `10xx`: toggle low-pass filter. `0` turns it off and `1` turns it on. +- `11xx`: toggle amplitude modulation with the next channel. + - does not work on the last channel. +- `12xx`: toggle period (frequency) modulation with the next channel. + - does not work on the last channel. \ No newline at end of file diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index b535df52d..15f1cf0d5 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -107,6 +107,8 @@ enum DivDispatchCmds { DIV_CMD_SAA_ENVELOPE, DIV_CMD_AMIGA_FILTER, + DIV_CMD_AMIGA_AM, + DIV_CMD_AMIGA_PM, DIV_CMD_LYNX_LFSR_LOAD, diff --git a/src/engine/platform/amiga.cpp b/src/engine/platform/amiga.cpp index a58de52f5..b0f25f728 100644 --- a/src/engine/platform/amiga.cpp +++ b/src/engine/platform/amiga.cpp @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#define _USE_MATH_DEFINES #include "amiga.h" #include "../engine.h" #include @@ -68,6 +69,12 @@ const char* DivPlatformAmiga::getEffectName(unsigned char effect) { case 0x10: return "10xx: Toggle filter (0 disables; 1 enables)"; break; + case 0x11: + return "11xx: Toggle AM with next channel"; + break; + case 0x12: + return "12xx: Toggle period modulation with next channel"; + break; } return NULL; } @@ -84,6 +91,14 @@ void DivPlatformAmiga::acquire(short* bufL, short* bufR, size_t start, size_t le DivSample* s=parent->getSample(chan[i].sample); if (s->samples>0) { chan[i].audDat=s->data8[chan[i].audPos++]; + if (i<3 && chan[i].useV) { + chan[i+1].outVol=(unsigned char)chan[i].audDat^0x80; + if (chan[i+1].outVol>64) chan[i+1].outVol=64; + } + if (i<3 && chan[i].useP) { + chan[i+1].freq=(unsigned char)chan[i].audDat^0x80; + if (chan[i+1].freq>AMIGA_DIVIDER) chan[i+1].freq=AMIGA_DIVIDER; + } if (chan[i].audPos>=s->samples || chan[i].audPos>=131071) { if (s->loopStart>=0 && s->loopStart<(int)s->samples) { chan[i].audPos=s->loopStart; @@ -319,6 +334,12 @@ int DivPlatformAmiga::dispatch(DivCommand c) { filterOn=c.value; filtConst=filterOn?filtConstOn:filtConstOff; break; + case DIV_CMD_AMIGA_AM: + chan[c.chan].useV=c.value; + break; + case DIV_CMD_AMIGA_PM: + chan[c.chan].useP=c.value; + break; case DIV_CMD_GET_VOLMAX: return 64; break; diff --git a/src/engine/platform/amiga.h b/src/engine/platform/amiga.h index d73918e96..b5f13701d 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, setPos; + bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, useWave, setPos, useV, useP; signed char vol, outVol; DivMacroInt std; Channel(): @@ -61,6 +61,8 @@ class DivPlatformAmiga: public DivDispatch { inPorta(false), useWave(false), setPos(false), + useV(false), + useP(false), vol(64), outVol(64) {} }; diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 7d089e609..4ab6a517c 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -115,6 +115,8 @@ const char* cmdName[DIV_CMD_MAX]={ "SAA_ENVELOPE", "AMIGA_FILTER", + "AMIGA_AM", + "AMIGA_PM", "LYNX_LFSR_LOAD", @@ -656,6 +658,12 @@ bool DivEngine::perSystemPostEffect(int ch, unsigned char effect, unsigned char case 0x10: // toggle filter dispatchCmd(DivCommand(DIV_CMD_AMIGA_FILTER,ch,effectVal)); break; + case 0x11: // toggle AM + dispatchCmd(DivCommand(DIV_CMD_AMIGA_AM,ch,effectVal)); + break; + case 0x12: // toggle PM + dispatchCmd(DivCommand(DIV_CMD_AMIGA_PM,ch,effectVal)); + break; default: return false; } From a58c6da19d97ff1c0ce45cb21c1f0193c2c83496 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 27 Mar 2022 00:29:37 -0500 Subject: [PATCH 426/637] Amiga: oops --- src/engine/platform/amiga.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/amiga.cpp b/src/engine/platform/amiga.cpp index b0f25f728..e1bc83947 100644 --- a/src/engine/platform/amiga.cpp +++ b/src/engine/platform/amiga.cpp @@ -97,7 +97,7 @@ void DivPlatformAmiga::acquire(short* bufL, short* bufR, size_t start, size_t le } if (i<3 && chan[i].useP) { chan[i+1].freq=(unsigned char)chan[i].audDat^0x80; - if (chan[i+1].freq>AMIGA_DIVIDER) chan[i+1].freq=AMIGA_DIVIDER; + if (chan[i+1].freq=s->samples || chan[i].audPos>=131071) { if (s->loopStart>=0 && s->loopStart<(int)s->samples) { From 1c98748a88176697d9a9a9d93958898e8aae60b3 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 27 Mar 2022 01:38:24 -0500 Subject: [PATCH 427/637] GUI: redesign FM editor layout, part 2 --- src/gui/insEdit.cpp | 64 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 50 insertions(+), 14 deletions(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 930bcccf0..2e0645d5d 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -911,6 +911,12 @@ if (ImGui::BeginTable("MacroSpace",2)) { \ } \ } +#define CENTER_TEXT(text) \ + ImGui::SetCursorPosX(ImGui::GetCursorPosX()+0.5*(ImGui::GetContentRegionAvail().x-ImGui::CalcTextSize(text).x)); + +#define CENTER_VSLIDER \ + ImGui::SetCursorPosX(ImGui::GetCursorPosX()+0.5f*ImGui::GetContentRegionAvail().x-20.0f); + void FurnaceGUI::drawInsEdit() { if (nextWindow==GUI_WINDOW_INS_EDIT) { insEditOpen=true; @@ -1055,60 +1061,76 @@ void FurnaceGUI::drawInsEdit() { } if (willDisplayOps) { if (settings.fmLayout==0) { - if (ImGui::BeginTable("FMOperators",15,ImGuiTableFlags_SizingStretchProp|ImGuiTableFlags_BordersH|ImGuiTableFlags_BordersOuterV)) { + if (ImGui::BeginTable("FMOperators",16,ImGuiTableFlags_SizingStretchProp|ImGuiTableFlags_BordersH|ImGuiTableFlags_BordersOuterV)) { // configure columns ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); // op name - ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed); // ar - ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthFixed); // dr - ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthFixed); // sl - ImGui::TableSetupColumn("c4",ImGuiTableColumnFlags_WidthFixed); // d2r - ImGui::TableSetupColumn("c5",ImGuiTableColumnFlags_WidthFixed); // rr + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.05f); // ar + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch,0.05f); // dr + ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthStretch,0.05f); // sl + ImGui::TableSetupColumn("c4",ImGuiTableColumnFlags_WidthStretch,0.05f); // d2r + ImGui::TableSetupColumn("c5",ImGuiTableColumnFlags_WidthStretch,0.05f); // rr ImGui::TableSetupColumn("c6",ImGuiTableColumnFlags_WidthFixed); // -separator- - ImGui::TableSetupColumn("c7",ImGuiTableColumnFlags_WidthFixed); // tl - ImGui::TableSetupColumn("c8",ImGuiTableColumnFlags_WidthFixed); // ... - ImGui::TableSetupColumn("c9",ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn("c10",ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn("c11",ImGuiTableColumnFlags_WidthFixed); - ImGui::TableSetupColumn("c12",ImGuiTableColumnFlags_WidthStretch,0.4f); - ImGui::TableSetupColumn("c13",ImGuiTableColumnFlags_WidthStretch,0.6f); - ImGui::TableSetupColumn("c14",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c7",ImGuiTableColumnFlags_WidthStretch,0.05f); // tl + ImGui::TableSetupColumn("c8",ImGuiTableColumnFlags_WidthStretch,0.05f); // rs + ImGui::TableSetupColumn("c9",ImGuiTableColumnFlags_WidthStretch,0.05f); // mult + ImGui::TableSetupColumn("c10",ImGuiTableColumnFlags_WidthStretch,0.05f); // dt + ImGui::TableSetupColumn("c11",ImGuiTableColumnFlags_WidthStretch,0.05f); // dt2 + ImGui::TableSetupColumn("c12",ImGuiTableColumnFlags_WidthFixed); // -separator- + ImGui::TableSetupColumn("c13",ImGuiTableColumnFlags_WidthStretch,0.2f); // ssg + ImGui::TableSetupColumn("c14",ImGuiTableColumnFlags_WidthStretch,0.3f); // env + ImGui::TableSetupColumn("c15",ImGuiTableColumnFlags_WidthFixed); // am // header ImGui::TableNextRow(ImGuiTableRowFlags_Headers); ImGui::TableNextColumn(); ImGui::TableNextColumn(); + CENTER_TEXT(FM_NAME(FM_AR)); ImGui::TextUnformatted(FM_NAME(FM_AR)); ImGui::TableNextColumn(); + CENTER_TEXT(FM_NAME(FM_DR)); ImGui::TextUnformatted(FM_NAME(FM_DR)); ImGui::TableNextColumn(); + CENTER_TEXT(FM_NAME(FM_SL)); ImGui::TextUnformatted(FM_NAME(FM_SL)); if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { ImGui::TableNextColumn(); + CENTER_TEXT(FM_NAME(FM_D2R)); ImGui::TextUnformatted(FM_NAME(FM_D2R)); } ImGui::TableNextColumn(); + CENTER_TEXT(FM_NAME(FM_RR)); ImGui::TextUnformatted(FM_NAME(FM_RR)); ImGui::TableNextColumn(); ImGui::TableNextColumn(); + CENTER_TEXT(FM_NAME(FM_TL)); ImGui::TextUnformatted(FM_NAME(FM_TL)); ImGui::TableNextColumn(); if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { + CENTER_TEXT(FM_NAME(FM_RS)); ImGui::TextUnformatted(FM_NAME(FM_RS)); } else { + CENTER_TEXT(FM_NAME(FM_KSR)); ImGui::TextUnformatted(FM_NAME(FM_KSR)); } ImGui::TableNextColumn(); + CENTER_TEXT(FM_NAME(FM_MULT)); ImGui::TextUnformatted(FM_NAME(FM_MULT)); ImGui::TableNextColumn(); + CENTER_TEXT(FM_NAME(FM_DT)); ImGui::TextUnformatted(FM_NAME(FM_DT)); ImGui::TableNextColumn(); + CENTER_TEXT(FM_NAME(FM_DT2)); ImGui::TextUnformatted(FM_NAME(FM_DT2)); ImGui::TableNextColumn(); + ImGui::TableNextColumn(); + CENTER_TEXT(FM_NAME(FM_SSG)); ImGui::TextUnformatted(FM_NAME(FM_SSG)); ImGui::TableNextColumn(); + CENTER_TEXT("Envelope"); ImGui::TextUnformatted("Envelope"); ImGui::TableNextColumn(); + CENTER_TEXT(FM_NAME(FM_AM)); ImGui::TextUnformatted(FM_NAME(FM_AM)); float sliderHeight=32.0f*dpiScale; @@ -1151,24 +1173,29 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); op.ar&=maxArDr; + CENTER_VSLIDER; P(ImGui::VSliderScalar("##AR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.ar,&_ZERO,&maxArDr)); ImGui::TableNextColumn(); op.dr&=maxArDr; + CENTER_VSLIDER; P(ImGui::VSliderScalar("##DR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.dr,&_ZERO,&maxArDr)); ImGui::TableNextColumn(); op.sl&=15; + CENTER_VSLIDER; P(ImGui::VSliderScalar("##SL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { ImGui::TableNextColumn(); op.d2r&=31; + CENTER_VSLIDER; P(ImGui::VSliderScalar("##D2R",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.d2r,&_ZERO,&_THIRTY_ONE)); } ImGui::TableNextColumn(); op.rr&=15; + CENTER_VSLIDER; P(ImGui::VSliderScalar("##RR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.rr,&_ZERO,&_FIFTEEN)); ImGui::TableNextColumn(); @@ -1176,9 +1203,11 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); op.tl&=maxTl; + CENTER_VSLIDER; P(ImGui::VSliderScalar("##TL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.tl,&maxTl,&_ZERO)); ImGui::TableNextColumn(); + CENTER_VSLIDER; if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { P(ImGui::VSliderScalar("##RS",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.rs,&_ZERO,&_THREE)); } else { @@ -1186,21 +1215,27 @@ void FurnaceGUI::drawInsEdit() { } ImGui::TableNextColumn(); + CENTER_VSLIDER; P(ImGui::VSliderScalar("##MULT",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.mult,&_ZERO,&_FIFTEEN)); if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { int detune=(op.dt&7)-3; ImGui::TableNextColumn(); + CENTER_VSLIDER; if (ImGui::VSliderInt("##DT",ImVec2(20.0f*dpiScale,sliderHeight),&detune,-3,4)) { PARAMETER op.dt=detune+3; } ImGui::TableNextColumn(); + CENTER_VSLIDER; P(ImGui::VSliderScalar("##DT2",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.dt2,&_ZERO,&_THREE)); rightClickable if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Only on YM2151 (OPM)"); } + ImGui::TableNextColumn(); + ImGui::Dummy(ImVec2(4.0f*dpiScale,2.0f*dpiScale)); + ImGui::TableNextColumn(); if (ins->type!=DIV_INS_OPL && ins->type!=DIV_INS_OPZ) { if (ImGui::Checkbox("##SSGOn",&ssgOn)) { PARAMETER @@ -1225,6 +1260,7 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); bool amOn=op.am; + ImGui::SetCursorPosY(ImGui::GetCursorPosY()+0.5*(sliderHeight-ImGui::GetFrameHeight())); if (ImGui::Checkbox("##AM",&amOn)) { PARAMETER op.am=amOn; } From 5dac609d92f0c317d9088a00c79b428c5f0eafae Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 27 Mar 2022 01:47:27 -0500 Subject: [PATCH 428/637] Genesis: better DAC write algorithm only write DAC if there aren't too many queued writes --- src/engine/platform/genesis.cpp | 8 ++++++-- src/engine/platform/genesisshared.h | 2 +- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index 46e243332..10a8762c0 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -92,7 +92,9 @@ void DivPlatformGenesis::acquire_nuked(short* bufL, short* bufR, size_t start, s DivSample* s=parent->getSample(dacSample); if (s->samples>0) { if (!isMuted[5]) { - urgentWrite(0x2a,(unsigned char)s->data8[dacPos]+0x80); + if (writes.size()<16) { + urgentWrite(0x2a,(unsigned char)s->data8[dacPos]+0x80); + } } if (++dacPos>=s->samples) { if (s->loopStart>=0 && s->loopStart<(int)s->samples) { @@ -159,7 +161,9 @@ void DivPlatformGenesis::acquire_ymfm(short* bufL, short* bufR, size_t start, si DivSample* s=parent->getSample(dacSample); if (s->samples>0) { if (!isMuted[5]) { - urgentWrite(0x2a,(unsigned char)s->data8[dacPos]+0x80); + if (writes.size()<16) { + urgentWrite(0x2a,(unsigned char)s->data8[dacPos]+0x80); + } } if (++dacPos>=s->samples) { if (s->loopStart>=0 && s->loopStart<(int)s->samples) { diff --git a/src/engine/platform/genesisshared.h b/src/engine/platform/genesisshared.h index 6410ddb27..3d5320f6b 100644 --- a/src/engine/platform/genesisshared.h +++ b/src/engine/platform/genesisshared.h @@ -44,6 +44,6 @@ static int orderedOps[4]={ #define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;} #define immWrite(a,v) if (!skipRegisterWrites) {writes.push_back(QueuedWrite(a,v)); if (dumpWrites) {addWrite(a,v);} } -#define urgentWrite(a,v) if (!skipRegisterWrites) {if (writes.empty() || writes.front().addrOrVal) {writes.push_back(QueuedWrite(a,v));} else {writes.push_front(QueuedWrite(a,v));}; if (dumpWrites) {addWrite(a,v);} } +#define urgentWrite(a,v) if (!skipRegisterWrites) {if (writes.empty() || writes.size()>16 || writes.front().addrOrVal) {writes.push_back(QueuedWrite(a,v));} else {writes.push_front(QueuedWrite(a,v));}; if (dumpWrites) {addWrite(a,v);} } #include "fmshared_OPN.h" From 062e85af504ae2e2a6017e15d8512a2604bb1137 Mon Sep 17 00:00:00 2001 From: freq-mod <32672779+freq-mod@users.noreply.github.com> Date: Sun, 27 Mar 2022 09:29:53 +0200 Subject: [PATCH 429/637] fix a critical vic20 documentation error --- papers/doc/7-systems/vic20.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/papers/doc/7-systems/vic20.md b/papers/doc/7-systems/vic20.md index b221a3cbd..b4dbbffa2 100644 --- a/papers/doc/7-systems/vic20.md +++ b/papers/doc/7-systems/vic20.md @@ -4,7 +4,7 @@ The Commodore VIC-20 was Commodore's major attempt at making a personal home com It has 4 PSG voices that has a limited but wide tuning range, and like the SN76489, the last voice is dedicated to playing pseudo-white noise. Every voice on the VIC-20 has a high-pass and low-pass filter applied to it, which is likely how Rob Yannes got the inspiration to put custom filter modes on the C64's SID. -The 3 pulse wave channels also have different octaves that they can play notes on (not currently emulated in Furnace version dev71). The first channel is the bass channel, and it can play notes from octaves 2 to octaves 4. The next is the 'mid/chord' channel, and it plays notes from octaves 3 to 5. And rather obviously, the 3rd pulse channel is typically the lead channel, can play notes from octaves 4 to 6. +The 3 pulse wave channels also have different octaves that they can play notes on. The first channel is the bass channel, and it can play notes from octaves 2 to octaves 4. The next is the 'mid/chord' channel, and it plays notes from octaves 3 to 5. And rather obviously, the 3rd pulse channel is typically the lead channel, can play notes from octaves 4 to 6. ## effect commands From 688190db91f4b5579f05b0a3e21012066429b0f1 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 27 Mar 2022 02:31:03 -0500 Subject: [PATCH 430/637] improve VIC-20 doc --- papers/doc/7-systems/vic20.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/papers/doc/7-systems/vic20.md b/papers/doc/7-systems/vic20.md index b221a3cbd..0c8186318 100644 --- a/papers/doc/7-systems/vic20.md +++ b/papers/doc/7-systems/vic20.md @@ -2,10 +2,10 @@ The Commodore VIC-20 was Commodore's major attempt at making a personal home computer, and is the percursor to the Commodore 64. The VIC-20 was also known as the VC-20 in Germany, and the VIC-1001 in Japan. -It has 4 PSG voices that has a limited but wide tuning range, and like the SN76489, the last voice is dedicated to playing pseudo-white noise. Every voice on the VIC-20 has a high-pass and low-pass filter applied to it, which is likely how Rob Yannes got the inspiration to put custom filter modes on the C64's SID. +It has 4 PSG voices that has a limited but wide tuning range, and like the SN76489, the last voice is dedicated to playing pseudo-white noise. -The 3 pulse wave channels also have different octaves that they can play notes on (not currently emulated in Furnace version dev71). The first channel is the bass channel, and it can play notes from octaves 2 to octaves 4. The next is the 'mid/chord' channel, and it plays notes from octaves 3 to 5. And rather obviously, the 3rd pulse channel is typically the lead channel, can play notes from octaves 4 to 6. +The 3 pulse wave channels also have different octaves that they can play notes on. The first channel is the bass channel, and it can play notes from octave 1. The next is the 'mid/chord' channel, and it plays notes from octave 2. And rather obviously, the 3rd pulse channel is typically the lead channel, can play notes from octave 3. ## effect commands - - `10xx` Switch waveform (Only the values 00 though 0F are unique. Everything else is a copy. For example, `1006` is the same as `10f6`.) + - `10xx` Switch waveform (`xx` from `00` to `0F`) \ No newline at end of file From f7d7b00e938e1981ffc8e87804aeeec357b41b01 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 27 Mar 2022 03:29:45 -0500 Subject: [PATCH 431/637] GUI: redesign FM editor layout, part 3 --- src/gui/gui.h | 2 + src/gui/insEdit.cpp | 173 ++++++++++++++++++++++++++++++++++++++------ 2 files changed, 151 insertions(+), 24 deletions(-) diff --git a/src/gui/gui.h b/src/gui/gui.h index 1c6466631..841b2d3df 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -821,6 +821,8 @@ class FurnaceGUI { float keyHit[DIV_MAX_CHANS]; int lastIns[DIV_MAX_CHANS]; + void drawSSGEnv(unsigned char type, const ImVec2& size); + void drawWaveform(unsigned char type, bool opz, const ImVec2& size); void drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, const ImVec2& size); void drawFMEnv(unsigned char tl, unsigned char ar, unsigned char dr, unsigned char d2r, unsigned char rr, unsigned char sl, float maxTl, float maxArDr, const ImVec2& size); void drawSysConf(int i); diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 2e0645d5d..6fa99feb8 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -37,6 +37,12 @@ const char* fmParamNames[3][27]={ {"ALG", "FB", "FMS/PMS", "AMS", "AR", "DR", "D2R", "RR", "SL", "TL", "RS", "MULT", "DT", "DT2", "SSG-EG", "AM", "DAM", "DVB", "EGT", "EGS", "KSL", "SUS", "VIB", "WS", "KSR", "DC", "DM"} }; +const char* fmParamShortNames[3][27]={ + {"ALG", "FB", "FMS", "AMS", "A", "D", "D2", "R", "S", "TL", "RS", "ML", "DT", "DT2", "SSG", "AM", "DAM", "DVB", "SUS", "SUS", "KSL", "SUS", "VIB", "WS", "KSR", "DC", "DM"}, + {"ALG", "FB", "FMS", "AMS", "A", "D", "SR", "R", "S", "TL", "KS", "ML", "DT", "DT2", "SSG", "AM", "AMD", "FMD", "EGT", "EGT", "KSL", "SUS", "VIB", "WS", "KSR", "DC", "DM"}, + {"ALG", "FB", "FMS", "AMS", "A", "D", "D2", "R", "S", "TL", "RS", "ML", "DT", "DT2", "SSG", "AM", "DAM", "DVB", "EGT", "EGS", "KSL", "SUS", "VIB", "WS", "KSR", "DC", "DM"} +}; + const char* opllInsNames[17]={ "User", "Violin", @@ -96,6 +102,7 @@ enum FMParams { }; #define FM_NAME(x) fmParamNames[settings.fmNames][x] +#define FM_SHORT_NAME(x) fmParamShortNames[settings.fmNames][x] const char* c64ShapeBits[5]={ "triangle", "saw", "pulse", "noise", NULL @@ -179,6 +186,121 @@ void addAALine(ImDrawList* dl, const ImVec2& p1, const ImVec2& p2, const ImU32 c dl->AddPolyline(pt,2,color,ImDrawFlags_None,thickness); } +// TODO: don't get drunk tonight +void FurnaceGUI::drawSSGEnv(unsigned char type, const ImVec2& size) { + ImDrawList* dl=ImGui::GetWindowDrawList(); + ImGuiWindow* window=ImGui::GetCurrentWindow(); + + ImVec2 minArea=window->DC.CursorPos; + ImVec2 maxArea=ImVec2( + minArea.x+size.x, + minArea.y+size.y + ); + ImRect rect=ImRect(minArea,maxArea); + ImGuiStyle& style=ImGui::GetStyle(); + ImU32 color=ImGui::GetColorU32(uiColors[GUI_COLOR_TEXT]); + ImGui::ItemSize(size,style.FramePadding.y); + if (ImGui::ItemAdd(rect,ImGui::GetID("ssgEnvDisplay"))) { + ImGui::RenderFrame(rect.Min,rect.Max,ImGui::GetColorU32(ImGuiCol_FrameBg),true,style.FrameRounding); + switch (type) { + case 0: + for (int i=0; i<4; i++) { + ImVec2 pos1=ImLerp(rect.Min,rect.Max,ImVec2((float)i/4.0f,0.2)); + ImVec2 pos2=ImLerp(rect.Min,rect.Max,ImVec2((float)(i+1)/4.0f,0.8)); + addAALine(dl,pos1,pos2,color); + pos1.x=pos2.x; + if (i<3) addAALine(dl,pos1,pos2,color); + } + break; + case 1: { + ImVec2 pos1=ImLerp(rect.Min,rect.Max,ImVec2(0.0,0.2)); + ImVec2 pos2=ImLerp(rect.Min,rect.Max,ImVec2(0.25,0.8)); + addAALine(dl,pos1,pos2,color); + + pos1=ImLerp(rect.Min,rect.Max,ImVec2(1.0,0.8)); + addAALine(dl,pos1,pos2,color); + break; + } + case 2: { + ImVec2 pos1=ImLerp(rect.Min,rect.Max,ImVec2(0.0,0.2)); + ImVec2 pos2=ImLerp(rect.Min,rect.Max,ImVec2(0.25,0.8)); + addAALine(dl,pos1,pos2,color); + + pos1=ImLerp(rect.Min,rect.Max,ImVec2(0.5,0.2)); + addAALine(dl,pos1,pos2,color); + + pos2=ImLerp(rect.Min,rect.Max,ImVec2(0.75,0.8)); + addAALine(dl,pos1,pos2,color); + + pos1=ImLerp(rect.Min,rect.Max,ImVec2(1.0,0.2)); + addAALine(dl,pos1,pos2,color); + break; + } + case 3: { + ImVec2 pos1=ImLerp(rect.Min,rect.Max,ImVec2(0.0,0.2)); + ImVec2 pos2=ImLerp(rect.Min,rect.Max,ImVec2(0.25,0.8)); + addAALine(dl,pos1,pos2,color); + + pos1.x=pos2.x; + addAALine(dl,pos1,pos2,color); + + pos2=ImLerp(rect.Min,rect.Max,ImVec2(1.0,0.2)); + addAALine(dl,pos1,pos2,color); + break; + } + case 4: + for (int i=0; i<4; i++) { + ImVec2 pos1=ImLerp(rect.Min,rect.Max,ImVec2((float)i/4.0f,0.8)); + ImVec2 pos2=ImLerp(rect.Min,rect.Max,ImVec2((float)(i+1)/4.0f,0.2)); + addAALine(dl,pos1,pos2,color); + pos1.x=pos2.x; + if (i<3) addAALine(dl,pos1,pos2,color); + } + break; + case 5: { + ImVec2 pos1=ImLerp(rect.Min,rect.Max,ImVec2(0.0,0.8)); + ImVec2 pos2=ImLerp(rect.Min,rect.Max,ImVec2(0.25,0.2)); + addAALine(dl,pos1,pos2,color); + + pos1=ImLerp(rect.Min,rect.Max,ImVec2(1.0,0.2)); + addAALine(dl,pos1,pos2,color); + break; + } + case 6: { + ImVec2 pos1=ImLerp(rect.Min,rect.Max,ImVec2(0.0,0.8)); + ImVec2 pos2=ImLerp(rect.Min,rect.Max,ImVec2(0.25,0.2)); + addAALine(dl,pos1,pos2,color); + + pos1=ImLerp(rect.Min,rect.Max,ImVec2(0.5,0.8)); + addAALine(dl,pos1,pos2,color); + + pos2=ImLerp(rect.Min,rect.Max,ImVec2(0.75,0.2)); + addAALine(dl,pos1,pos2,color); + + pos1=ImLerp(rect.Min,rect.Max,ImVec2(1.0,0.8)); + addAALine(dl,pos1,pos2,color); + break; + } + case 7: { + ImVec2 pos1=ImLerp(rect.Min,rect.Max,ImVec2(0.0,0.8)); + ImVec2 pos2=ImLerp(rect.Min,rect.Max,ImVec2(0.25,0.2)); + addAALine(dl,pos1,pos2,color); + + pos1.x=pos2.x; + addAALine(dl,pos1,pos2,color); + + pos2=ImLerp(rect.Min,rect.Max,ImVec2(1.0,0.8)); + addAALine(dl,pos1,pos2,color); + break; + } + } + } +} + +void FurnaceGUI::drawWaveform(unsigned char type, bool opz, const ImVec2& size) { + +} + void FurnaceGUI::drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, const ImVec2& size) { ImDrawList* dl=ImGui::GetWindowDrawList(); ImGuiWindow* window=ImGui::GetCurrentWindow(); @@ -1085,43 +1207,43 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); ImGui::TableNextColumn(); - CENTER_TEXT(FM_NAME(FM_AR)); - ImGui::TextUnformatted(FM_NAME(FM_AR)); + CENTER_TEXT(FM_SHORT_NAME(FM_AR)); + ImGui::TextUnformatted(FM_SHORT_NAME(FM_AR)); ImGui::TableNextColumn(); - CENTER_TEXT(FM_NAME(FM_DR)); - ImGui::TextUnformatted(FM_NAME(FM_DR)); + CENTER_TEXT(FM_SHORT_NAME(FM_DR)); + ImGui::TextUnformatted(FM_SHORT_NAME(FM_DR)); ImGui::TableNextColumn(); - CENTER_TEXT(FM_NAME(FM_SL)); - ImGui::TextUnformatted(FM_NAME(FM_SL)); + CENTER_TEXT(FM_SHORT_NAME(FM_SL)); + ImGui::TextUnformatted(FM_SHORT_NAME(FM_SL)); if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { ImGui::TableNextColumn(); - CENTER_TEXT(FM_NAME(FM_D2R)); - ImGui::TextUnformatted(FM_NAME(FM_D2R)); + CENTER_TEXT(FM_SHORT_NAME(FM_D2R)); + ImGui::TextUnformatted(FM_SHORT_NAME(FM_D2R)); } ImGui::TableNextColumn(); - CENTER_TEXT(FM_NAME(FM_RR)); - ImGui::TextUnformatted(FM_NAME(FM_RR)); + CENTER_TEXT(FM_SHORT_NAME(FM_RR)); + ImGui::TextUnformatted(FM_SHORT_NAME(FM_RR)); ImGui::TableNextColumn(); ImGui::TableNextColumn(); - CENTER_TEXT(FM_NAME(FM_TL)); - ImGui::TextUnformatted(FM_NAME(FM_TL)); + CENTER_TEXT(FM_SHORT_NAME(FM_TL)); + ImGui::TextUnformatted(FM_SHORT_NAME(FM_TL)); ImGui::TableNextColumn(); if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { - CENTER_TEXT(FM_NAME(FM_RS)); - ImGui::TextUnformatted(FM_NAME(FM_RS)); + CENTER_TEXT(FM_SHORT_NAME(FM_RS)); + ImGui::TextUnformatted(FM_SHORT_NAME(FM_RS)); } else { - CENTER_TEXT(FM_NAME(FM_KSR)); - ImGui::TextUnformatted(FM_NAME(FM_KSR)); + CENTER_TEXT(FM_SHORT_NAME(FM_KSR)); + ImGui::TextUnformatted(FM_SHORT_NAME(FM_KSR)); } ImGui::TableNextColumn(); - CENTER_TEXT(FM_NAME(FM_MULT)); - ImGui::TextUnformatted(FM_NAME(FM_MULT)); + CENTER_TEXT(FM_SHORT_NAME(FM_MULT)); + ImGui::TextUnformatted(FM_SHORT_NAME(FM_MULT)); ImGui::TableNextColumn(); - CENTER_TEXT(FM_NAME(FM_DT)); - ImGui::TextUnformatted(FM_NAME(FM_DT)); + CENTER_TEXT(FM_SHORT_NAME(FM_DT)); + ImGui::TextUnformatted(FM_SHORT_NAME(FM_DT)); ImGui::TableNextColumn(); - CENTER_TEXT(FM_NAME(FM_DT2)); - ImGui::TextUnformatted(FM_NAME(FM_DT2)); + CENTER_TEXT(FM_SHORT_NAME(FM_DT2)); + ImGui::TextUnformatted(FM_SHORT_NAME(FM_DT2)); ImGui::TableNextColumn(); ImGui::TableNextColumn(); CENTER_TEXT(FM_NAME(FM_SSG)); @@ -1130,8 +1252,8 @@ void FurnaceGUI::drawInsEdit() { CENTER_TEXT("Envelope"); ImGui::TextUnformatted("Envelope"); ImGui::TableNextColumn(); - CENTER_TEXT(FM_NAME(FM_AM)); - ImGui::TextUnformatted(FM_NAME(FM_AM)); + CENTER_TEXT(FM_SHORT_NAME(FM_AM)); + ImGui::TextUnformatted(FM_SHORT_NAME(FM_AM)); float sliderHeight=32.0f*dpiScale; @@ -1238,6 +1360,9 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); if (ins->type!=DIV_INS_OPL && ins->type!=DIV_INS_OPZ) { + ImGui::BeginDisabled(!ssgOn); + drawSSGEnv(op.ssgEnv&7,ImVec2(ImGui::GetContentRegionAvail().x,sliderHeight-ImGui::GetFrameHeightWithSpacing())); + ImGui::EndDisabled(); if (ImGui::Checkbox("##SSGOn",&ssgOn)) { PARAMETER op.ssgEnv=(op.ssgEnv&7)|(ssgOn<<3); } From 09b5dd556e7f3dbf88f1165d13c2e48f0f94a308 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 27 Mar 2022 03:38:04 -0500 Subject: [PATCH 432/637] GUI: add setting to change position of SL slider --- src/gui/gui.h | 2 ++ src/gui/insEdit.cpp | 53 +++++++++++++++++++++++++++++++++----------- src/gui/settings.cpp | 11 +++++++++ 3 files changed, 53 insertions(+), 13 deletions(-) diff --git a/src/gui/gui.h b/src/gui/gui.h index 841b2d3df..3b6056f4f 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -623,6 +623,7 @@ class FurnaceGUI { int roundedMenus; int loadJapanese; int fmLayout; + int susPosition; unsigned int maxUndoSteps; String mainFontPath; String patFontPath; @@ -674,6 +675,7 @@ class FurnaceGUI { roundedMenus(0), loadJapanese(0), fmLayout(0), + susPosition(0), maxUndoSteps(100), mainFontPath(""), patFontPath(""), diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 6fa99feb8..837815da0 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1212,9 +1212,11 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); CENTER_TEXT(FM_SHORT_NAME(FM_DR)); ImGui::TextUnformatted(FM_SHORT_NAME(FM_DR)); - ImGui::TableNextColumn(); - CENTER_TEXT(FM_SHORT_NAME(FM_SL)); - ImGui::TextUnformatted(FM_SHORT_NAME(FM_SL)); + if (settings.susPosition==0) { + ImGui::TableNextColumn(); + CENTER_TEXT(FM_SHORT_NAME(FM_SL)); + ImGui::TextUnformatted(FM_SHORT_NAME(FM_SL)); + } if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { ImGui::TableNextColumn(); CENTER_TEXT(FM_SHORT_NAME(FM_D2R)); @@ -1223,6 +1225,11 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); CENTER_TEXT(FM_SHORT_NAME(FM_RR)); ImGui::TextUnformatted(FM_SHORT_NAME(FM_RR)); + if (settings.susPosition==1) { + ImGui::TableNextColumn(); + CENTER_TEXT(FM_SHORT_NAME(FM_SL)); + ImGui::TextUnformatted(FM_SHORT_NAME(FM_SL)); + } ImGui::TableNextColumn(); ImGui::TableNextColumn(); CENTER_TEXT(FM_SHORT_NAME(FM_TL)); @@ -1303,10 +1310,12 @@ void FurnaceGUI::drawInsEdit() { CENTER_VSLIDER; P(ImGui::VSliderScalar("##DR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.dr,&_ZERO,&maxArDr)); - ImGui::TableNextColumn(); - op.sl&=15; - CENTER_VSLIDER; - P(ImGui::VSliderScalar("##SL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); + if (settings.susPosition==0) { + ImGui::TableNextColumn(); + op.sl&=15; + CENTER_VSLIDER; + P(ImGui::VSliderScalar("##SL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); + } if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { ImGui::TableNextColumn(); @@ -1320,6 +1329,13 @@ void FurnaceGUI::drawInsEdit() { CENTER_VSLIDER; P(ImGui::VSliderScalar("##RR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.rr,&_ZERO,&_FIFTEEN)); + if (settings.susPosition==1) { + ImGui::TableNextColumn(); + op.sl&=15; + CENTER_VSLIDER; + P(ImGui::VSliderScalar("##SL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); + } + ImGui::TableNextColumn(); ImGui::Dummy(ImVec2(4.0f*dpiScale,2.0f*dpiScale)); @@ -1493,12 +1509,14 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); ImGui::Text("%s",FM_NAME(FM_DR)); - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - P(ImGui::SliderScalar("##SL",ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); rightClickable - ImGui::TableNextColumn(); - ImGui::Text("%s",FM_NAME(FM_SL)); + if (settings.susPosition==0) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + P(ImGui::SliderScalar("##SL",ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); rightClickable + ImGui::TableNextColumn(); + ImGui::Text("%s",FM_NAME(FM_SL)); + } if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { ImGui::TableNextRow(); @@ -1516,6 +1534,15 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); ImGui::Text("%s",FM_NAME(FM_RR)); + if (settings.susPosition==1) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + P(ImGui::SliderScalar("##SL",ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); rightClickable + ImGui::TableNextColumn(); + ImGui::Text("%s",FM_NAME(FM_SL)); + } + ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 02ca297b7..9484762b8 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -433,6 +433,14 @@ void FurnaceGUI::drawSettings() { settings.fmLayout=3; } + ImGui::Text("Position of Sustain in FM editor:"); + if (ImGui::RadioButton("Between Decay and Sustain Rate##susp0",settings.susPosition==0)) { + settings.susPosition=0; + } + if (ImGui::RadioButton("After Release Rate##susp1",settings.susPosition==1)) { + settings.susPosition=1; + } + bool macroViewB=settings.macroView; if (ImGui::Checkbox("Classic macro view (standard macros only; deprecated!)",¯oViewB)) { settings.macroView=macroViewB; @@ -1005,6 +1013,7 @@ void FurnaceGUI::syncSettings() { settings.roundedMenus=e->getConfInt("roundedMenus",0); settings.loadJapanese=e->getConfInt("loadJapanese",0); settings.fmLayout=e->getConfInt("fmLayout",0); + settings.susPosition=e->getConfInt("susPosition",0); clampSetting(settings.mainFontSize,2,96); clampSetting(settings.patFontSize,2,96); @@ -1050,6 +1059,7 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.roundedMenus,0,1); clampSetting(settings.loadJapanese,0,1); clampSetting(settings.fmLayout,0,3); + clampSetting(settings.susPosition,0,1); // keybinds LOAD_KEYBIND(GUI_ACTION_OPEN,FURKMOD_CMD|SDLK_o); @@ -1285,6 +1295,7 @@ void FurnaceGUI::commitSettings() { e->setConf("roundedMenus",settings.roundedMenus); e->setConf("loadJapanese",settings.loadJapanese); e->setConf("fmLayout",settings.fmLayout); + e->setConf("susPosition",settings.susPosition); PUT_UI_COLOR(GUI_COLOR_BACKGROUND); PUT_UI_COLOR(GUI_COLOR_FRAME_BACKGROUND); From ef104ce0b0539ed165fbb165755e05e7eece435b Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 28 Mar 2022 01:06:56 +0900 Subject: [PATCH 433/637] Add VRC6 support 2 Pulse channels: - 8 level pulse duty cycle, DAC mode(just ignores duty cycle)/pulse wave mode, 4 bit volume. - Furnace support PCM playback in pulse channels with duty cycle ignore mode. Sawtooth: - nothing but 6 bit volume (8 bit accumulator in technically) and 12 bit frequency (periodic). VRC6 instrument: - 6 bit Volume macro for finer sawtooth volume handling, also 3 bit Duty cycle macro for pulse channels. Duty, PCM mode command and Duty macro affects for pulse channel only. --- CMakeLists.txt | 3 + papers/doc/4-instrument/README.md | 1 + papers/doc/4-instrument/vrc6.md | 7 + papers/doc/7-systems/README.md | 1 + papers/doc/7-systems/vrc6.md | 16 + src/engine/dispatchContainer.cpp | 4 + src/engine/platform/sound/vrcvi/vrcvi.cpp | 307 +++++++++++++ src/engine/platform/sound/vrcvi/vrcvi.hpp | 238 +++++++++++ src/engine/platform/vrc6.cpp | 496 ++++++++++++++++++++++ src/engine/platform/vrc6.h | 100 +++++ src/engine/platform/x1_010.h | 1 - src/engine/playback.cpp | 12 + src/gui/debug.cpp | 28 ++ src/gui/guiConst.cpp | 1 + src/gui/insEdit.cpp | 8 +- src/gui/presets.cpp | 7 + src/gui/sysConf.cpp | 1 + 17 files changed, 1228 insertions(+), 3 deletions(-) create mode 100644 papers/doc/4-instrument/vrc6.md create mode 100644 papers/doc/7-systems/vrc6.md create mode 100644 src/engine/platform/sound/vrcvi/vrcvi.cpp create mode 100644 src/engine/platform/sound/vrcvi/vrcvi.hpp create mode 100644 src/engine/platform/vrc6.cpp create mode 100644 src/engine/platform/vrc6.h diff --git a/CMakeLists.txt b/CMakeLists.txt index b95900c8c..8b64ae985 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -275,6 +275,8 @@ src/engine/platform/sound/k005289/k005289.cpp src/engine/platform/sound/vic20sound.c +src/engine/platform/sound/vrcvi/vrcvi.cpp + src/engine/platform/ym2610Interface.cpp src/engine/blip_buf.c @@ -324,6 +326,7 @@ src/engine/platform/vera.cpp src/engine/platform/bubsyswsg.cpp src/engine/platform/pet.cpp src/engine/platform/vic20.cpp +src/engine/platform/vrc6.cpp src/engine/platform/dummy.cpp ) diff --git a/papers/doc/4-instrument/README.md b/papers/doc/4-instrument/README.md index ab03e8364..4e72043ea 100644 --- a/papers/doc/4-instrument/README.md +++ b/papers/doc/4-instrument/README.md @@ -27,6 +27,7 @@ depending on the instrument type, there are currently 13 different types of an i - [VERA](vera.md) - for use with Commander X16 VERA. - [Seta/Allumer X1-010](x1_010.md) - for use with Wavetable portion in Seta/Allumer X1-010. - [Konami SCC/Bubble System WSG](scc.md) - for use with Konami SCC and Wavetable portion in Bubble System's sound hardware. +- [Konami VRC6](vrc6.md) - for use with VRC6's PSG sound source. # macros diff --git a/papers/doc/4-instrument/vrc6.md b/papers/doc/4-instrument/vrc6.md new file mode 100644 index 000000000..b7612c2f7 --- /dev/null +++ b/papers/doc/4-instrument/vrc6.md @@ -0,0 +1,7 @@ +# VRC6 instrument editor + +VRC6 instrument editor consists of only three macros: + +- [Volume] - volume sequence +- [Arpeggio] - pitch sequencr +- [Duty cycle] - spicifies duty cycle for pulse wave channels diff --git a/papers/doc/7-systems/README.md b/papers/doc/7-systems/README.md index 5e4e101b0..a76c412b6 100644 --- a/papers/doc/7-systems/README.md +++ b/papers/doc/7-systems/README.md @@ -28,5 +28,6 @@ this is a list of systems that Furnace supports, including each system's effects - [PC Speaker](pcspkr.md) - [Commodore PET](pet.md) - [Commodore VIC-20](vic20.md) +- [Konami VRC6](vrc6.md) Furnace also reads .dmf files with the [Yamaha YMU759](ymu759.md) system, but... diff --git a/papers/doc/7-systems/vrc6.md b/papers/doc/7-systems/vrc6.md new file mode 100644 index 000000000..c60ae26f6 --- /dev/null +++ b/papers/doc/7-systems/vrc6.md @@ -0,0 +1,16 @@ +# Konami VRC6 + +Its one of NES mapper with sound expansion, and one of two VRCs with this feature by Konami. + +The chip has 2 pulse wave channel and single sawtooth channel. +volume register is 4 bit for pulse wave and 6 bit for sawtooth, but sawtooth output is corrupted when volume register value is too high. because this register is 8 bit accumulator in technically, its output is wraparoundable. + +pulse wave duty cycle is 8 level, it can be ignored and it has potential for DAC at this case: volume register in this mode is DAC output and it can be PCM playback through this mode. +Furnace supports this routine for PCM playback, but it's consume a lot of CPU resource in real hardware. + +# effects + +- `12xx`: set duty cycle. (0 to 7) +- `17xx`: toggle PCM mode. + +* All effects are affects at pulse channels only. \ No newline at end of file diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 0f553772b..dd13b38e3 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -48,6 +48,7 @@ #include "platform/bubsyswsg.h" #include "platform/pet.h" #include "platform/vic20.h" +#include "platform/vrc6.h" #include "platform/dummy.h" #include "../ta-log.h" #include "song.h" @@ -283,6 +284,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_VIC20: dispatch=new DivPlatformVIC20; break; + case DIV_SYSTEM_VRC6: + dispatch=new DivPlatformVRC6; + break; default: logW("this system is not supported yet! using dummy platform.\n"); dispatch=new DivPlatformDummy; diff --git a/src/engine/platform/sound/vrcvi/vrcvi.cpp b/src/engine/platform/sound/vrcvi/vrcvi.cpp new file mode 100644 index 000000000..4754620e8 --- /dev/null +++ b/src/engine/platform/sound/vrcvi/vrcvi.cpp @@ -0,0 +1,307 @@ +/* + License: BSD-3-Clause + see https://github.com/cam900/vgsound_emu/LICENSE for more details + + Copyright holder(s): cam900 + Konami VRC VI sound emulation core + + It's one of NES mapper with built-in sound chip, and also one of 2 Konami VRCs with this feature. (rest one has OPLL derivatives.) + + It's also DACless like other sound chip and mapper-with-sound manufactured by konami, + the Chips 6 bit digital sound output is needs converted to analog sound output when you it want to make some sounds, or send to sound mixer. + + Its are used for Akumajou Densetsu (Japan release of Castlevania III), Madara, Esper Dream 2. + + The chip is installed in 351951 PCB and 351949A PCB. + + 351951 PCB is used exclusivly for Akumajou Densetsu, Small board has VRC VI, PRG and CHR ROM. + - It's configuration also calls VRC6a, iNES mapper 024. + + 351949A PCB is for Last 2 titles with VRC VI, Bigger board has VRC VI, PRG and CHR ROM, and Battery Backed 8K x 8 bit SRAM. + - Additionally, It's PRG A0 and A1 bit to VRC VI input is swapped, compare to above. + - It's configuration also calls VRC6b, iNES mapper 026. + + The chip itself has 053328, 053329, 053330 Revision, but Its difference between revision is unknown. + + Like other mappers for NES, It has internal timer - Its timer can be sync with scanline like other Konami mapper in this era. + + Register layout (Sound and Timer only; 351951 PCB case, 351949A swaps xxx1 and xxx2): + + Address Bits Description + 7654 3210 + + 9000-9002 Pulse 1 + + 9000 x--- ---- Pulse 1 Duty ignore + -xxx ---- Pulse 1 Duty cycle + ---- xxxx Pulse 1 Volume + 9001 xxxx xxxx Pulse 1 Pitch bit 0-7 + 9002 x--- ---- Pulse 1 Disable + ---- xxxx Pulse 1 Pitch bit 8-11 + + 9003 Sound control + + 9003 ---- -x-- 4 bit Frequency mode + ---- -0x- 8 bit Frequency mode + ---- ---x Halt + + a000-a002 Pulse 2 + + a000 x--- ---- Pulse 2 Duty ignore + -xxx ---- Pulse 2 Duty cycle + ---- xxxx Pulse 2 Volume + a001 xxxx xxxx Pulse 2 Pitch bit 0-7 + a002 x--- ---- Pulse 2 Disable + ---- xxxx Pulse 2 Pitch bit 8-11 + + b000-b002 Sawtooth + + b000 --xx xxxx Sawtooth Accumulate Rate + b001 xxxx xxxx Sawtooth Pitch bit 0-7 + b002 x--- ---- Sawtooth Disable + ---- xxxx Sawtooth Pitch bit 8-11 + + f000-f002 IRQ Timer + + f000 xxxx xxxx IRQ Timer latch + f001 ---- -0-- Sync with scanline + ---- --x- Enable timer + ---- ---x Enable timer after IRQ Acknowledge + f002 ---- ---- IRQ Acknowledge + + Frequency calculations: + + if 4 bit Frequency Mode then + Frequency: Input clock / (bit 8 to 11 of Pitch + 1) + end else if 8 bit Frequency Mode then + Frequency: Input clock / (bit 4 to 11 of Pitch + 1) + end else then + Frequency: Input clock / (Pitch + 1) + end +*/ + +#include "vrcvi.hpp" + +void vrcvi_core::tick() +{ + m_out = 0; + if (!m_control.m_halt) // Halt flag + { + // tick per each clock + for (auto & elem : m_pulse) + { + if (elem.tick()) + m_out += elem.m_control.m_volume; // add 4 bit pulse output + } + if (m_sawtooth.tick()) + m_out += bitfield(m_sawtooth.m_accum, 3, 5); // add 5 bit sawtooth output + } + if (m_timer.tick()) + m_timer.counter_tick(); +} + +void vrcvi_core::reset() +{ + for (auto & elem : m_pulse) + elem.reset(); + + m_sawtooth.reset(); + m_timer.reset(); + m_control.reset(); + m_out = 0; +} + +bool vrcvi_core::alu_t::tick() +{ + if (!m_divider.m_disable) + { + const u16 temp = m_counter; + // post decrement + if (bitfield(m_host.m_control.m_shift, 1)) + { + m_counter = (m_counter & 0x0ff) | (bitfield(bitfield(m_counter, 8, 4) - 1, 0, 4) << 8); + m_counter = (m_counter & 0xf00) | (bitfield(bitfield(m_counter, 0, 8) - 1, 0, 8) << 0); + } + else if (bitfield(m_host.m_control.m_shift, 0)) + { + m_counter = (m_counter & 0x00f) | (bitfield(bitfield(m_counter, 4, 8) - 1, 0, 8) << 4); + m_counter = (m_counter & 0xff0) | (bitfield(bitfield(m_counter, 0, 4) - 1, 0, 4) << 0); + } + else + m_counter = bitfield(bitfield(m_counter, 0, 12) - 1, 0, 12); + + // carry handling + bool carry = bitfield(m_host.m_control.m_shift, 1) ? (bitfield(temp, 8, 4) == 0) : + (bitfield(m_host.m_control.m_shift, 0) ? (bitfield(temp, 4, 8) == 0) : + (bitfield(temp, 0, 12) == 0)); + if (carry) + m_counter = m_divider.m_divider; + + return carry; + } + return false; +} + +bool vrcvi_core::pulse_t::tick() +{ + if (m_divider.m_disable) + return false; + + if (vrcvi_core::alu_t::tick()) + m_cycle = bitfield(m_cycle + 1, 0, 4); + + return m_control.m_mode ? true : ((m_cycle > m_control.m_duty) ? true : false); +} + +bool vrcvi_core::sawtooth_t::tick() +{ + if (m_divider.m_disable) + return false; + + if (vrcvi_core::alu_t::tick()) + { + if (bitfield(m_cycle++, 0)) // Even step only + m_accum += m_rate; + if (m_cycle >= 14) // Reset accumulator at every 14 cycles + { + m_accum = 0; + m_cycle = 0; + } + } + return (m_accum == 0) ? false : true; +} + +void vrcvi_core::alu_t::reset() +{ + m_divider.reset(); + m_counter = 0; + m_cycle = 0; +} + +void vrcvi_core::pulse_t::reset() +{ + vrcvi_core::alu_t::reset(); + m_control.reset(); +} + +void vrcvi_core::sawtooth_t::reset() +{ + vrcvi_core::alu_t::reset(); + m_rate = 0; + m_accum = 0; +} + +bool vrcvi_core::timer_t::tick() +{ + if (m_timer_control.m_enable) + { + if (!m_timer_control.m_sync) // scanline sync mode + { + m_prescaler -= 3; + if (m_prescaler <= 0) + { + m_prescaler += 341; + return true; + } + } + } + return (m_timer_control.m_enable && m_timer_control.m_sync) ? true : false; +} + +void vrcvi_core::timer_t::counter_tick() +{ + if (bitfield(++m_counter, 0, 8) == 0) + { + m_counter = m_counter_latch; + irq_set(); + } +} + +void vrcvi_core::timer_t::reset() +{ + m_timer_control.reset(); + m_prescaler = 341; + m_counter = m_counter_latch = 0; + irq_clear(); +} + +// Accessors + +void vrcvi_core::alu_t::divider_t::write(bool msb, u8 data) +{ + if (msb) + { + m_divider = (m_divider & ~0xf00) | (bitfield(data, 0, 4) << 8); + m_disable = bitfield(data, 7); + } + else + m_divider = (m_divider & ~0x0ff) | data; +} + + +void vrcvi_core::pulse_w(u8 voice, u8 address, u8 data) +{ + pulse_t &v = m_pulse[voice]; + switch (address) + { + case 0x00: // Control - 0x9000 (Pulse 1), 0xa000 (Pulse 2) + v.m_control.m_mode = bitfield(data, 7); + v.m_control.m_duty = bitfield(data, 4, 3); + v.m_control.m_volume = bitfield(data, 0, 4); + break; + case 0x01: // Pitch LSB - 0x9001/0x9002 (Pulse 1), 0xa001/0xa002 (Pulse 2) + v.m_divider.write(false, data); + break; + case 0x02: // Pitch MSB, Enable/Disable - 0x9002/0x9001 (Pulse 1), 0xa002/0xa001 (Pulse 2) + v.m_divider.write(true, data); + break; + } +} + +void vrcvi_core::saw_w(u8 address, u8 data) +{ + switch (address) + { + case 0x00: // Sawtooth Accumulate - 0xb000 + m_sawtooth.m_rate = bitfield(data, 0, 6); + break; + case 0x01: // Pitch LSB - 0xb001/0xb002 (Sawtooth) + m_sawtooth.m_divider.write(false, data); + break; + case 0x02: // Pitch MSB, Enable/Disable - 0xb002/0xb001 (Sawtooth) + m_sawtooth.m_divider.write(true, data); + break; + } +} + +void vrcvi_core::timer_w(u8 address, u8 data) +{ + switch (address) + { + case 0x00: // Timer latch - 0xf000 + m_timer.m_counter_latch = data; + break; + case 0x01: // Timer control - 0xf001/0xf002 + m_timer.m_timer_control.m_sync = bitfield(data, 2); + m_timer.m_timer_control.m_enable = bitfield(data, 1); + m_timer.m_timer_control.m_enable_ack = bitfield(data, 0); + if (m_timer.m_timer_control.m_enable) + { + m_timer.m_counter = m_timer.m_counter_latch; + m_timer.m_prescaler = 341; + } + m_timer.irq_clear(); + break; + case 0x02: // IRQ Acknowledge - 0xf002/0xf001 + m_timer.irq_clear(); + m_timer.m_timer_control.m_enable = m_timer.m_timer_control.m_enable_ack; + break; + } +} + +void vrcvi_core::control_w(u8 data) +{ + // Global control - 0x9003 + m_control.m_halt = bitfield(data, 0); + m_control.m_shift = bitfield(data, 1, 2); +} diff --git a/src/engine/platform/sound/vrcvi/vrcvi.hpp b/src/engine/platform/sound/vrcvi/vrcvi.hpp new file mode 100644 index 000000000..028778b08 --- /dev/null +++ b/src/engine/platform/sound/vrcvi/vrcvi.hpp @@ -0,0 +1,238 @@ +/* + License: BSD-3-Clause + see https://github.com/cam900/vgsound_emu/LICENSE for more details + + Copyright holder(s): cam900 + Konami VRC VI sound emulation core + + See vrcvi.cpp to more infos. +*/ + +#include +#include + +#ifndef _VGSOUND_EMU_VRCVI_HPP +#define _VGSOUND_EMU_VRCVI_HPP + +#pragma once + +namespace vrcvi +{ + typedef unsigned char u8; + typedef unsigned short u16; + typedef unsigned int u32; + typedef signed char s8; + typedef signed short s16; + + // get bitfield, bitfield(input, position, len) + template T bitfield(T in, u8 pos, u8 len = 1) + { + return (in >> pos) & (len ? (T(1 << len) - 1) : 1); + } +}; + +class vrcvi_intf +{ +public: + virtual void irq_w(bool irq) { } +}; + +using namespace vrcvi; +class vrcvi_core +{ +public: + friend class vrcvi_intf; + // constructor + vrcvi_core(vrcvi_intf &intf) + : m_pulse{*this,*this} + , m_sawtooth(*this) + , m_timer(*this) + , m_intf(intf) + { + } + // accessors, getters, setters + void pulse_w(u8 voice, u8 address, u8 data); + void saw_w(u8 address, u8 data); + void timer_w(u8 address, u8 data); + void control_w(u8 data); + + // internal state + void reset(); + void tick(); + + // 6 bit output + s8 out() { return m_out; } +private: + // Common ALU for sound channels + struct alu_t + { + alu_t(vrcvi_core &host) + : m_host(host) + { }; + + + virtual void reset(); + virtual bool tick(); + + struct divider_t + { + divider_t() + : m_divider(0) + , m_disable(0) + { }; + + void reset() + { + m_divider = 0; + m_disable = 0; + } + + void write(bool msb, u8 data); + + u16 m_divider : 12; // divider (pitch) + u16 m_disable : 1; // channel disable flag + }; + + vrcvi_core &m_host; + divider_t m_divider; + u16 m_counter = 0; // clock counter + u8 m_cycle = 0; // clock cycle + }; + + // 2 Pulse channels + struct pulse_t : alu_t + { + pulse_t(vrcvi_core &host) + : alu_t(host) + { }; + + virtual void reset() override; + virtual bool tick() override; + + // Control bits + struct pulse_control_t + { + pulse_control_t() + : m_mode(0) + , m_duty(0) + , m_volume(0) + { }; + + void reset() + { + m_mode = 0; + m_duty = 0; + m_volume = 0; + } + + u8 m_mode : 1; // duty toggle flag + u8 m_duty : 3; // 3 bit duty cycle + u8 m_volume : 4; // 4 bit volume + }; + + pulse_control_t m_control; + }; + + // 1 Sawtooth channel + struct sawtooth_t : alu_t + { + sawtooth_t(vrcvi_core &host) + : alu_t(host) + { }; + + virtual void reset() override; + virtual bool tick() override; + + u8 m_rate = 0; // sawtooth accumulate rate + u8 m_accum = 0; // sawtooth accumulator, high 5 bit is accumulated to output + }; + + // Internal timer + struct timer_t + { + timer_t(vrcvi_core &host) + : m_host(host) + { }; + + void reset(); + bool tick(); + void counter_tick(); + + // IRQ update + void update() { m_host.m_intf.irq_w(m_timer_control.m_irq_trigger); } + void irq_set() + { + if (!m_timer_control.m_irq_trigger) + { + m_timer_control.m_irq_trigger = 1; + update(); + } + } + void irq_clear() + { + if (m_timer_control.m_irq_trigger) + { + m_timer_control.m_irq_trigger = 0; + update(); + } + } + + // Control bits + struct timer_control_t + { + timer_control_t() + : m_irq_trigger(0) + , m_enable_ack(0) + , m_enable(0) + , m_sync(0) + { }; + + void reset() + { + m_irq_trigger = 0; + m_enable_ack = 0; + m_enable = 0; + m_sync = 0; + } + + u8 m_irq_trigger : 1; + u8 m_enable_ack : 1; + u8 m_enable : 1; + u8 m_sync : 1; + }; + + vrcvi_core &m_host; // host core + timer_control_t m_timer_control; // timer control bits + s16 m_prescaler = 341; // prescaler + u8 m_counter = 0; // clock counter + u8 m_counter_latch = 0; // clock counter latch + }; + + struct global_control_t + { + global_control_t() + : m_halt(0) + , m_shift(0) + { }; + + void reset() + { + m_halt = 0; + m_shift = 0; + } + + u8 m_halt : 1; // halt sound + u8 m_shift : 2; // 4/8 bit right shift + }; + + pulse_t m_pulse[2]; // 2 pulse channels + sawtooth_t m_sawtooth; // sawtooth channel + timer_t m_timer; // internal timer + global_control_t m_control; // control + + vrcvi_intf &m_intf; + + s8 m_out = 0; // 6 bit output +}; + +#endif diff --git a/src/engine/platform/vrc6.cpp b/src/engine/platform/vrc6.cpp new file mode 100644 index 000000000..83fb9c629 --- /dev/null +++ b/src/engine/platform/vrc6.cpp @@ -0,0 +1,496 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "vrc6.h" +#include "../engine.h" +#include +#include + +#define CHIP_DIVIDER 1 // 16 for pulse, 14 for sawtooth + +#define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } +#define chWrite(c,a,v) rWrite(0x9000+(c<<12)+(a&3),v) + +const char* regCheatSheetVRC6[]={ + "S0DutyVol", "9000", + "S0PeriodL", "9001", + "S0PeriodH", "9002", + "GlobalCtl", "9003", + "S1DutyVol", "A000", + "S1PeriodL", "A001", + "S1PeriodH", "A002", + "SawVolume", "B000", + "SawPeriodL", "B001", + "SawPeriodH", "B002", + "TimerLatch", "F000", + "TimerCtl", "F001", + "IRQAck", "F002", + NULL +}; + +const char** DivPlatformVRC6::getRegisterSheet() { + return regCheatSheetVRC6; +} + +const char* DivPlatformVRC6::getEffectName(unsigned char effect) { + switch (effect) { + case 0x12: + return "12xx: Set duty cycle (pulse: 0 to 7)"; + break; + case 0x17: + return "17xx: Toggle PCM mode (pulse channel)"; + break; + } + return NULL; +} + +void DivPlatformVRC6::acquire(short* bufL, short* bufR, size_t start, size_t len) { + for (size_t i=start; irate) { + DivSample* s=parent->getSample(chan[i].dacSample); + if (s->samples<=0) { + chan[i].dacSample=-1; + chWrite(i,0,0); + continue; + } + unsigned char dacData=(((unsigned char)s->data8[chan[i].dacPos]^0x80)>>4); + chan[i].dacOut=MAX(0,MIN(15,(dacData*chan[i].outVol)>>4)); + if (!isMuted[i]) { + chWrite(i,0,0x80|chan[i].dacOut); + } + chan[i].dacPos++; + if (chan[i].dacPos>=s->samples) { + if (s->loopStart>=0 && s->loopStart<(int)s->samples) { + chan[i].dacPos=s->loopStart; + } else { + chan[i].dacSample=-1; + chWrite(i,0,0); + } + } + chan[i].dacPeriod-=rate; + } + } + } + + // VRC6 part + vrc6.tick(); + int sample=vrc6.out()<<9; // scale to 16 bit + if (sample>32767) sample=32767; + if (sample<-32768) sample=-32768; + bufL[i]=bufR[i]=sample; + + // Command part + while (!writes.empty()) { + QueuedWrite w=writes.front(); + switch (w.addr&0xf000) { + case 0x9000: // Pulse 1 + if (w.addr<=0x9003) { + if (w.addr==0x9003) { + vrc6.control_w(w.val); + } else if (w.addr<=0x9002) { + vrc6.pulse_w(0,w.addr&3,w.val); + } + regPool[w.addr-0x9000]=w.val; + } + break; + case 0xa000: // Pulse 2 + if (w.addr<=0xa002) { + vrc6.pulse_w(1,w.addr&3,w.val); + regPool[(w.addr-0xa000)+4]=w.val; + } + break; + case 0xb000: // Sawtooth + if (w.addr<=0xb002) { + vrc6.saw_w(w.addr&3,w.val); + regPool[(w.addr-0xb000)+7]=w.val; + } + break; + case 0xf000: // IRQ/Timer + if (w.addr<=0xf002) { + vrc6.timer_w(w.addr&3,w.val); + regPool[(w.addr-0xf000)+10]=w.val; + } + break; + } + writes.pop(); + } + } +} + +void DivPlatformVRC6::tick() { + for (int i=0; i<3; i++) { + chan[i].std.next(); + if (chan[i].std.hadVol) { + if (i==2) { // sawtooth + chan[i].outVol=((chan[i].vol&63)*MIN(63,chan[i].std.vol))/63; + if (chan[i].outVol<0) chan[i].outVol=0; + if (chan[i].outVol>63) chan[i].outVol=63; + if (!isMuted[i]) { + chWrite(i,0,chan[i].outVol); + } + } else { // pulse + chan[i].outVol=((chan[i].vol&15)*MIN(63,chan[i].std.vol))/63; + if (chan[i].outVol<0) chan[i].outVol=0; + if (chan[i].outVol>15) chan[i].outVol=15; + if ((!isMuted[i]) && (!chan[i].pcm)) { + chWrite(i,0,(chan[i].outVol&0xf)|((chan[i].duty&7)<<4)); + } + } + } + if (chan[i].std.hadArp) { + if (!chan[i].inPorta) { + if (chan[i].std.arpMode) { + chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp); + } else { + chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp); + } + } + chan[i].freqChanged=true; + } else { + if (chan[i].std.arpMode && chan[i].std.finishedArp) { + chan[i].baseFreq=NOTE_PERIODIC(chan[i].note); + chan[i].freqChanged=true; + } + } + if (chan[i].std.hadDuty) { + chan[i].duty=chan[i].std.duty; + if ((!isMuted[i]) && (i!=2) && (!chan[i].pcm)) { // pulse + chWrite(i,0,(chan[i].outVol&0xf)|((chan[i].duty&7)<<4)); + } + } + if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { + if (i==2) { // sawtooth + chan[i].freq=parent->calcFreq(chan[i].baseFreq/14,chan[i].pitch,true)-1; + } else { // pulse + chan[i].freq=parent->calcFreq(chan[i].baseFreq/16,chan[i].pitch,true)-1; + if (chan[i].furnaceDac) { + double off=1.0; + if (chan[i].dacSample>=0 && chan[i].dacSamplesong.sampleLen) { + DivSample* s=parent->getSample(chan[i].dacSample); + if (s->centerRate<1) { + off=1.0; + } else { + off=8363.0/(double)s->centerRate; + } + } + chan[i].dacRate=((double)chipClock)/MAX(1,off*chan[i].freq); + if (dumpWrites) addWrite(0xffff0001+(i<<8),chan[i].dacRate); + } + } + if (chan[i].freq>4095) chan[i].freq=4095; + if (chan[i].freq<0) chan[i].freq=0; + if (chan[i].keyOn) { + //rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(ins->gb.soundLen&63))); + //rWrite(16+i*5+2,((chan[i].vol<<4))|(ins->gb.envLen&7)|((ins->gb.envDir&1)<<3)); + } + if (chan[i].keyOff) { + chWrite(i,2,0x80); + } else { + chWrite(i,1,chan[i].freq&0xff); + chWrite(i,2,(chan[i].freq>>8)&0xf); + } + if (chan[i].keyOn) chan[i].keyOn=false; + if (chan[i].keyOff) chan[i].keyOff=false; + chan[i].freqChanged=false; + } + } +} + +int DivPlatformVRC6::dispatch(DivCommand c) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: + if (c.chan!=2) { // pulse wave + DivInstrument* ins=parent->getIns(chan[c.chan].ins); + if (ins->type==DIV_INS_AMIGA) { + chan[c.chan].pcm=true; + } else if (chan[c.chan].furnaceDac) { + chan[c.chan].pcm=false; + } + if (chan[c.chan].pcm) { + if (skipRegisterWrites) break; + if (ins->type==DIV_INS_AMIGA) { + chan[c.chan].dacSample=ins->amiga.initSample; + if (chan[c.chan].dacSample<0 || chan[c.chan].dacSample>=parent->song.sampleLen) { + chan[c.chan].dacSample=-1; + if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0); + break; + } else { + if (dumpWrites) { + chWrite(c.chan,2,0); + chWrite(c.chan,0,isMuted[c.chan]?0:0x80); + addWrite(0xffff0000+(c.chan<<8),chan[c.chan].dacSample); + } + } + chan[c.chan].dacPos=0; + chan[c.chan].dacPeriod=0; + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + } + chan[c.chan].active=true; + chan[c.chan].std.init(ins); + //chan[c.chan].keyOn=true; + chan[c.chan].furnaceDac=true; + } else { + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].note=c.value; + } + chan[c.chan].dacSample=12*sampleBank+chan[c.chan].note%12; + if (chan[c.chan].dacSample>=parent->song.sampleLen) { + chan[c.chan].dacSample=-1; + if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0); + break; + } else { + if (dumpWrites) addWrite(0xffff0000+(c.chan<<8),chan[c.chan].dacSample); + } + chan[c.chan].dacPos=0; + chan[c.chan].dacPeriod=0; + chan[c.chan].dacRate=parent->getSample(chan[c.chan].dacSample)->rate; + if (dumpWrites) { + chWrite(c.chan,2,0); + chWrite(c.chan,0,isMuted[c.chan]?0:0x80); + addWrite(0xffff0001+(c.chan<<8),chan[c.chan].dacRate); + } + chan[c.chan].furnaceDac=false; + } + break; + } + } + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + } + chan[c.chan].active=true; + chan[c.chan].keyOn=true; + chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + if (!isMuted[c.chan]) { + if (c.chan==2) { // sawtooth + chWrite(c.chan,0,chan[c.chan].vol); + } else if (!chan[c.chan].pcm) { + chWrite(c.chan,0,(chan[c.chan].vol&0xf)|((chan[c.chan].duty&7)<<4)); + } + } + break; + case DIV_CMD_NOTE_OFF: + chan[c.chan].dacSample=-1; + if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0); + chan[c.chan].pcm=false; + chan[c.chan].active=false; + chan[c.chan].keyOff=true; + chan[c.chan].std.init(NULL); + break; + case DIV_CMD_NOTE_OFF_ENV: + case DIV_CMD_ENV_RELEASE: + chan[c.chan].std.release(); + break; + case DIV_CMD_INSTRUMENT: + if (chan[c.chan].ins!=c.value || c.value2==1) { + chan[c.chan].ins=c.value; + } + break; + case DIV_CMD_VOLUME: + if (chan[c.chan].vol!=c.value) { + chan[c.chan].vol=c.value; + if (!chan[c.chan].std.hasVol) { + chan[c.chan].outVol=c.value; + } + if (!isMuted[c.chan]) { + if (chan[c.chan].active) { + if (c.chan==2) { // sawtooth + chWrite(c.chan,0,chan[c.chan].vol); + } else if (!chan[c.chan].pcm) { + chWrite(c.chan,0,(chan[c.chan].vol&0xf)|((chan[c.chan].duty&7)<<4)); + } + } + } + } + break; + case DIV_CMD_GET_VOLUME: + return chan[c.chan].vol; + break; + case DIV_CMD_PITCH: + chan[c.chan].pitch=c.value; + chan[c.chan].freqChanged=true; + break; + case DIV_CMD_NOTE_PORTA: { + int destFreq=NOTE_PERIODIC(c.value2); + bool return2=false; + if (destFreq>chan[c.chan].baseFreq) { + chan[c.chan].baseFreq+=c.value; + if (chan[c.chan].baseFreq>=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } else { + chan[c.chan].baseFreq-=c.value; + if (chan[c.chan].baseFreq<=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } + chan[c.chan].freqChanged=true; + if (return2) { + chan[c.chan].inPorta=false; + return 2; + } + break; + } + case DIV_CMD_STD_NOISE_MODE: + if ((c.chan!=2) && (!chan[c.chan].pcm)) { // pulse + chan[c.chan].duty=c.value; + } + break; + case DIV_CMD_SAMPLE_MODE: + if (c.chan!=2) { // pulse + chan[c.chan].pcm=c.value; + } + break; + case DIV_CMD_SAMPLE_BANK: + sampleBank=c.value; + if (sampleBank>(parent->song.sample.size()/12)) { + sampleBank=parent->song.sample.size()/12; + } + break; + case DIV_CMD_LEGATO: + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.willArp && !chan[c.chan].std.arpMode)?(chan[c.chan].std.arp):(0))); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + break; + case DIV_CMD_PRE_PORTA: + if (chan[c.chan].active && c.value2) { + if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + } + chan[c.chan].inPorta=c.value; + break; + case DIV_CMD_GET_VOLMAX: + if (c.chan==2) return 63; // sawtooth has 6 bit volume + return 15; // pulse has 4 bit volume + break; + case DIV_ALWAYS_SET_VOLUME: + return 1; + break; + default: + break; + } + return 1; +} + +void DivPlatformVRC6::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; + if (isMuted[ch]) { + chWrite(ch,0,0); + } else if (chan[ch].active) { + if (ch==2) { // sawtooth + chWrite(ch,0,chan[ch].outVol); + } else { + chWrite(ch,0,chan[ch].pcm?chan[ch].dacOut:((chan[ch].outVol&0xf)|((chan[ch].duty&7)<<4))); + } + } +} + +void DivPlatformVRC6::forceIns() { + for (int i=0; i<3; i++) { + chan[i].insChanged=true; + chan[i].freqChanged=true; + } +} + +void* DivPlatformVRC6::getChanState(int ch) { + return &chan[ch]; +} + +unsigned char* DivPlatformVRC6::getRegisterPool() { + return regPool; +} + +int DivPlatformVRC6::getRegisterPoolSize() { + return 13; +} + +void DivPlatformVRC6::reset() { + for (int i=0; i<3; i++) { + chan[i]=DivPlatformVRC6::Channel(); + } + if (dumpWrites) { + addWrite(0xffffffff,0); + } + + sampleBank=0; + + vrc6.reset(); + // Initialize control register + rWrite(0x9003,0); + // Clear internal IRQ + rWrite(0xf000,0); + rWrite(0xf001,0); + rWrite(0xf002,0); +} + +bool DivPlatformVRC6::keyOffAffectsArp(int ch) { + return true; +} + +void DivPlatformVRC6::setFlags(unsigned int flags) { + if (flags==2) { // Dendy + rate=COLOR_PAL*2.0/5.0; + } else if (flags==1) { // PAL + rate=COLOR_PAL*3.0/8.0; + } else { // NTSC + rate=COLOR_NTSC/2.0; + } + chipClock=rate; +} + +void DivPlatformVRC6::notifyInsDeletion(void* ins) { + for (int i=0; i<3; i++) { + chan[i].std.notifyInsDeletion((DivInstrument*)ins); + } +} + +void DivPlatformVRC6::poke(unsigned int addr, unsigned short val) { + rWrite(addr,val); +} + +void DivPlatformVRC6::poke(std::vector& wlist) { + for (DivRegWrite& i: wlist) rWrite(i.addr,i.val); +} + +int DivPlatformVRC6::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { + parent=p; + dumpWrites=false; + skipRegisterWrites=false; + for (int i=0; i<3; i++) { + isMuted[i]=false; + } + setFlags(flags); + reset(); + return 3; +} + +void DivPlatformVRC6::quit() { +} + +DivPlatformVRC6::~DivPlatformVRC6() { +} diff --git a/src/engine/platform/vrc6.h b/src/engine/platform/vrc6.h new file mode 100644 index 000000000..05cd89415 --- /dev/null +++ b/src/engine/platform/vrc6.h @@ -0,0 +1,100 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _VRC6_H +#define _VRC6_H + +#include +#include "../dispatch.h" +#include "../macroInt.h" +#include "sound/vrcvi/vrcvi.hpp" + + +class DivPlatformVRC6: public DivDispatch { + struct Channel { + int freq, baseFreq, pitch, note; + int dacPeriod, dacRate, dacOut; + unsigned int dacPos; + int dacSample; + unsigned char ins, duty; + bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, pcm, furnaceDac; + signed char vol, outVol; + DivMacroInt std; + Channel(): + freq(0), + baseFreq(0), + pitch(0), + note(0), + dacPeriod(0), + dacRate(0), + dacOut(0), + dacPos(0), + dacSample(-1), + ins(-1), + duty(0), + active(false), + insChanged(true), + freqChanged(false), + keyOn(false), + keyOff(false), + inPorta(false), + pcm(false), + furnaceDac(false), + vol(15), + outVol(15) {} + }; + Channel chan[3]; + bool isMuted[3]; + struct QueuedWrite { + unsigned short addr; + unsigned char val; + QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v) {} + }; + std::queue writes; + unsigned char sampleBank; + vrcvi_intf intf; + vrcvi_core vrc6; + unsigned char regPool[13]; + + friend void putDispatchChan(void*,int,int); + + public: + void acquire(short* bufL, short* bufR, size_t start, size_t len); + int dispatch(DivCommand c); + void* getChanState(int chan); + unsigned char* getRegisterPool(); + int getRegisterPoolSize(); + void reset(); + void forceIns(); + void tick(); + void muteChannel(int ch, bool mute); + bool keyOffAffectsArp(int ch); + void setFlags(unsigned int flags); + void notifyInsDeletion(void* ins); + void poke(unsigned int addr, unsigned short val); + void poke(std::vector& wlist); + const char** getRegisterSheet(); + const char* getEffectName(unsigned char effect); + int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); + void quit(); + DivPlatformVRC6() : vrc6(intf) {}; + ~DivPlatformVRC6(); +}; + +#endif diff --git a/src/engine/platform/x1_010.h b/src/engine/platform/x1_010.h index 73cbaf6b1..389fa7bf7 100644 --- a/src/engine/platform/x1_010.h +++ b/src/engine/platform/x1_010.h @@ -20,7 +20,6 @@ #ifndef _X1_010_H #define _X1_010_H -#include #include "../dispatch.h" #include "../engine.h" #include "../macroInt.h" diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 4ab6a517c..d43f1f461 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -330,6 +330,18 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe return false; } break; + case DIV_SYSTEM_VRC6: + switch (effect) { + case 0x12: // duty or noise mode + dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal)); + break; + case 0x17: // PCM enable + dispatchCmd(DivCommand(DIV_CMD_SAMPLE_MODE,ch,(effectVal>0))); + break; + default: + return false; + } + break; default: return false; } diff --git a/src/gui/debug.cpp b/src/gui/debug.cpp index ddbfb5b57..03ecede27 100644 --- a/src/gui/debug.cpp +++ b/src/gui/debug.cpp @@ -37,6 +37,7 @@ #include "../engine/platform/saa.h" #include "../engine/platform/amiga.h" #include "../engine/platform/x1_010.h" +#include "../engine/platform/vrc6.h" #include "../engine/platform/dummy.h" #define GENESIS_DEBUG \ @@ -274,6 +275,33 @@ void putDispatchChan(void* data, int chanNum, int type) { ImGui::TextColored(ch->env.flag.envVinvL?colorOn:colorOff,">> EnvVinvL"); break; } + case DIV_SYSTEM_VRC6: { + DivPlatformVRC6::Channel* ch=(DivPlatformVRC6::Channel*)data; + ImGui::Text("> VRC6"); + ImGui::Text("* freq: %d",ch->freq); + ImGui::Text(" - base: %d",ch->baseFreq); + ImGui::Text(" - pitch: %d",ch->pitch); + ImGui::Text("- note: %d",ch->note); + ImGui::Text("* DAC:"); + ImGui::Text(" - period: %d",ch->dacPeriod); + ImGui::Text(" - rate: %d",ch->dacRate); + ImGui::Text(" - out: %d",ch->dacOut); + ImGui::Text(" - pos: %d",ch->dacPos); + ImGui::Text(" - sample: %d",ch->dacSample); + ImGui::Text("- ins: %d",ch->ins); + ImGui::Text("- duty: %d",ch->duty); + ImGui::Text("- vol: %.2x",ch->vol); + ImGui::Text("- outVol: %.2x",ch->outVol); + ImGui::TextColored(ch->active?colorOn:colorOff,">> Active"); + ImGui::TextColored(ch->insChanged?colorOn:colorOff,">> InsChanged"); + ImGui::TextColored(ch->freqChanged?colorOn:colorOff,">> FreqChanged"); + ImGui::TextColored(ch->keyOn?colorOn:colorOff,">> KeyOn"); + ImGui::TextColored(ch->keyOff?colorOn:colorOff,">> KeyOff"); + ImGui::TextColored(ch->inPorta?colorOn:colorOff,">> InPorta"); + ImGui::TextColored(ch->pcm?colorOn:colorOff,">> DAC"); + ImGui::TextColored(ch->furnaceDac?colorOn:colorOff,">> FurnaceDAC"); + break; + } default: ImGui::Text("Unknown system! Help!"); break; diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index a25e831e5..120ec7746 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -165,5 +165,6 @@ const int availableSystems[]={ DIV_SYSTEM_BUBSYS_WSG, DIV_SYSTEM_PET, DIV_SYSTEM_VIC20, + DIV_SYSTEM_VRC6, 0 // don't remove this last one! }; \ No newline at end of file diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 837815da0..ef0212777 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1924,7 +1924,7 @@ void FurnaceGUI::drawInsEdit() { if ((ins->type==DIV_INS_PCE || ins->type==DIV_INS_AY8930)) { volMax=31; } - if (ins->type==DIV_INS_OPL || ins->type==DIV_INS_VERA) { + if (ins->type==DIV_INS_OPL || ins->type==DIV_INS_VERA || ins->type==DIV_INS_VRC6) { volMax=63; } if (ins->type==DIV_INS_AMIGA) { @@ -1985,6 +1985,10 @@ void FurnaceGUI::drawInsEdit() { dutyLabel="Duty"; dutyMax=63; } + if (ins->type==DIV_INS_VRC6) { + dutyLabel="Duty"; + dutyMax=7; + } bool dutyIsRel=(ins->type==DIV_INS_C64 && !ins->c64.dutyIsAbs); const char* waveLabel="Waveform"; @@ -1993,7 +1997,7 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_C64 || ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_SAA1099) { bitMode=true; } - if (ins->type==DIV_INS_STD) waveMax=0; + if (ins->type==DIV_INS_STD || ins->type==DIV_INS_VRC6) waveMax=0; if (ins->type==DIV_INS_TIA || ins->type==DIV_INS_VIC || ins->type==DIV_INS_OPLL) waveMax=15; if (ins->type==DIV_INS_C64) waveMax=4; if (ins->type==DIV_INS_SAA1099) waveMax=2; diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index 0ab7c2ba6..835b46fa0 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -191,6 +191,13 @@ void FurnaceGUI::initSystemPresets() { 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "NES with Konami VRC6", { + DIV_SYSTEM_NES, 64, 0, 0, + DIV_SYSTEM_VRC6, 64, 0, 0, + 0 + } + )); cat.systems.push_back(FurnaceGUISysDef( "NES with Konami VRC7", { DIV_SYSTEM_NES, 64, 0, 0, diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index b61245f2c..f7bc2e1e2 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -147,6 +147,7 @@ void FurnaceGUI::drawSysConf(int i) { } break; case DIV_SYSTEM_NES: + case DIV_SYSTEM_VRC6: if (ImGui::RadioButton("NTSC (1.79MHz)",flags==0)) { e->setSysFlags(i,0,restart); updateWindowTitle(); From c430d24d2f609ae7cdfeeefaa264f938b2584474 Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 28 Mar 2022 01:12:44 +0900 Subject: [PATCH 434/637] VRC6 has internal timer --- papers/doc/7-systems/vrc6.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/papers/doc/7-systems/vrc6.md b/papers/doc/7-systems/vrc6.md index c60ae26f6..7dfc2585f 100644 --- a/papers/doc/7-systems/vrc6.md +++ b/papers/doc/7-systems/vrc6.md @@ -6,7 +6,7 @@ The chip has 2 pulse wave channel and single sawtooth channel. volume register is 4 bit for pulse wave and 6 bit for sawtooth, but sawtooth output is corrupted when volume register value is too high. because this register is 8 bit accumulator in technically, its output is wraparoundable. pulse wave duty cycle is 8 level, it can be ignored and it has potential for DAC at this case: volume register in this mode is DAC output and it can be PCM playback through this mode. -Furnace supports this routine for PCM playback, but it's consume a lot of CPU resource in real hardware. +Furnace supports this routine for PCM playback, but it's consume a lot of CPU resource in real hardware. (even if conjunction with VRC6 integrated IRQ timer) # effects From 5c5c9199c714ae549fa37054dc1e18e38141a2ee Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 28 Mar 2022 01:22:24 +0900 Subject: [PATCH 435/637] Channel enable bit is inverted --- src/engine/platform/sound/vrcvi/vrcvi.cpp | 14 +++++++------- src/engine/platform/sound/vrcvi/vrcvi.hpp | 6 +++--- src/engine/platform/vrc6.cpp | 8 ++++---- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/engine/platform/sound/vrcvi/vrcvi.cpp b/src/engine/platform/sound/vrcvi/vrcvi.cpp index 4754620e8..21c3c5eae 100644 --- a/src/engine/platform/sound/vrcvi/vrcvi.cpp +++ b/src/engine/platform/sound/vrcvi/vrcvi.cpp @@ -36,7 +36,7 @@ -xxx ---- Pulse 1 Duty cycle ---- xxxx Pulse 1 Volume 9001 xxxx xxxx Pulse 1 Pitch bit 0-7 - 9002 x--- ---- Pulse 1 Disable + 9002 x--- ---- Pulse 1 Enable ---- xxxx Pulse 1 Pitch bit 8-11 9003 Sound control @@ -51,14 +51,14 @@ -xxx ---- Pulse 2 Duty cycle ---- xxxx Pulse 2 Volume a001 xxxx xxxx Pulse 2 Pitch bit 0-7 - a002 x--- ---- Pulse 2 Disable + a002 x--- ---- Pulse 2 Enable ---- xxxx Pulse 2 Pitch bit 8-11 b000-b002 Sawtooth b000 --xx xxxx Sawtooth Accumulate Rate b001 xxxx xxxx Sawtooth Pitch bit 0-7 - b002 x--- ---- Sawtooth Disable + b002 x--- ---- Sawtooth Enable ---- xxxx Sawtooth Pitch bit 8-11 f000-f002 IRQ Timer @@ -113,7 +113,7 @@ void vrcvi_core::reset() bool vrcvi_core::alu_t::tick() { - if (!m_divider.m_disable) + if (m_divider.m_enable) { const u16 temp = m_counter; // post decrement @@ -144,7 +144,7 @@ bool vrcvi_core::alu_t::tick() bool vrcvi_core::pulse_t::tick() { - if (m_divider.m_disable) + if (!m_divider.m_enable) return false; if (vrcvi_core::alu_t::tick()) @@ -155,7 +155,7 @@ bool vrcvi_core::pulse_t::tick() bool vrcvi_core::sawtooth_t::tick() { - if (m_divider.m_disable) + if (!m_divider.m_enable) return false; if (vrcvi_core::alu_t::tick()) @@ -232,7 +232,7 @@ void vrcvi_core::alu_t::divider_t::write(bool msb, u8 data) if (msb) { m_divider = (m_divider & ~0xf00) | (bitfield(data, 0, 4) << 8); - m_disable = bitfield(data, 7); + m_enable = bitfield(data, 7); } else m_divider = (m_divider & ~0x0ff) | data; diff --git a/src/engine/platform/sound/vrcvi/vrcvi.hpp b/src/engine/platform/sound/vrcvi/vrcvi.hpp index 028778b08..1d721a872 100644 --- a/src/engine/platform/sound/vrcvi/vrcvi.hpp +++ b/src/engine/platform/sound/vrcvi/vrcvi.hpp @@ -78,19 +78,19 @@ private: { divider_t() : m_divider(0) - , m_disable(0) + , m_enable(0) { }; void reset() { m_divider = 0; - m_disable = 0; + m_enable = 0; } void write(bool msb, u8 data); u16 m_divider : 12; // divider (pitch) - u16 m_disable : 1; // channel disable flag + u16 m_enable : 1; // channel enable flag }; vrcvi_core &m_host; diff --git a/src/engine/platform/vrc6.cpp b/src/engine/platform/vrc6.cpp index 83fb9c629..630d59722 100644 --- a/src/engine/platform/vrc6.cpp +++ b/src/engine/platform/vrc6.cpp @@ -204,10 +204,10 @@ void DivPlatformVRC6::tick() { //rWrite(16+i*5+2,((chan[i].vol<<4))|(ins->gb.envLen&7)|((ins->gb.envDir&1)<<3)); } if (chan[i].keyOff) { - chWrite(i,2,0x80); + chWrite(i,2,0); } else { chWrite(i,1,chan[i].freq&0xff); - chWrite(i,2,(chan[i].freq>>8)&0xf); + chWrite(i,2,0x80|((chan[i].freq>>8)&0xf)); } if (chan[i].keyOn) chan[i].keyOn=false; if (chan[i].keyOff) chan[i].keyOff=false; @@ -236,7 +236,7 @@ int DivPlatformVRC6::dispatch(DivCommand c) { break; } else { if (dumpWrites) { - chWrite(c.chan,2,0); + chWrite(c.chan,2,0x80); chWrite(c.chan,0,isMuted[c.chan]?0:0x80); addWrite(0xffff0000+(c.chan<<8),chan[c.chan].dacSample); } @@ -268,7 +268,7 @@ int DivPlatformVRC6::dispatch(DivCommand c) { chan[c.chan].dacPeriod=0; chan[c.chan].dacRate=parent->getSample(chan[c.chan].dacSample)->rate; if (dumpWrites) { - chWrite(c.chan,2,0); + chWrite(c.chan,2,0x80); chWrite(c.chan,0,isMuted[c.chan]?0:0x80); addWrite(0xffff0001+(c.chan<<8),chan[c.chan].dacRate); } From 44b4c5c5aa4ba39ff05125591daacb63b355cb39 Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 28 Mar 2022 01:23:52 +0900 Subject: [PATCH 436/637] Spacing --- src/engine/platform/sound/vrcvi/vrcvi.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/sound/vrcvi/vrcvi.hpp b/src/engine/platform/sound/vrcvi/vrcvi.hpp index 1d721a872..40b4245e3 100644 --- a/src/engine/platform/sound/vrcvi/vrcvi.hpp +++ b/src/engine/platform/sound/vrcvi/vrcvi.hpp @@ -90,7 +90,7 @@ private: void write(bool msb, u8 data); u16 m_divider : 12; // divider (pitch) - u16 m_enable : 1; // channel enable flag + u16 m_enable : 1; // channel enable flag }; vrcvi_core &m_host; From 5c922a090e369e33ea208260a9a76050a88a869b Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 28 Mar 2022 01:43:43 +0900 Subject: [PATCH 437/637] Fix enable bit correction --- src/engine/platform/sound/vrcvi/vrcvi.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/engine/platform/sound/vrcvi/vrcvi.cpp b/src/engine/platform/sound/vrcvi/vrcvi.cpp index 21c3c5eae..a8a289da9 100644 --- a/src/engine/platform/sound/vrcvi/vrcvi.cpp +++ b/src/engine/platform/sound/vrcvi/vrcvi.cpp @@ -145,7 +145,10 @@ bool vrcvi_core::alu_t::tick() bool vrcvi_core::pulse_t::tick() { if (!m_divider.m_enable) + { + m_cycle = 0; return false; + } if (vrcvi_core::alu_t::tick()) m_cycle = bitfield(m_cycle + 1, 0, 4); @@ -156,7 +159,10 @@ bool vrcvi_core::pulse_t::tick() bool vrcvi_core::sawtooth_t::tick() { if (!m_divider.m_enable) + { + m_accum = 0; return false; + } if (vrcvi_core::alu_t::tick()) { From a7647a1d577dfe746ce0cd423f81244e5a08afd5 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 27 Mar 2022 13:38:28 -0500 Subject: [PATCH 438/637] nice troll --- src/engine/platform/genesisshared.h | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/engine/platform/genesisshared.h b/src/engine/platform/genesisshared.h index 3d5320f6b..d58d339e6 100644 --- a/src/engine/platform/genesisshared.h +++ b/src/engine/platform/genesisshared.h @@ -44,6 +44,17 @@ static int orderedOps[4]={ #define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;} #define immWrite(a,v) if (!skipRegisterWrites) {writes.push_back(QueuedWrite(a,v)); if (dumpWrites) {addWrite(a,v);} } -#define urgentWrite(a,v) if (!skipRegisterWrites) {if (writes.empty() || writes.size()>16 || writes.front().addrOrVal) {writes.push_back(QueuedWrite(a,v));} else {writes.push_front(QueuedWrite(a,v));}; if (dumpWrites) {addWrite(a,v);} } +#define urgentWrite(a,v) if (!skipRegisterWrites) { \ + if (writes.empty()) { \ + writes.push_back(QueuedWrite(a,v)); \ + } else if (writes.size()>16 || writes.front().addrOrVal) { \ + writes.push_back(QueuedWrite(a,v)); \ + } else { \ + writes.push_front(QueuedWrite(a,v)); \ + } \ + if (dumpWrites) { \ + addWrite(a,v); \ + } \ +} #include "fmshared_OPN.h" From ef88fc57d86b26731951684788acb4d68cad3427 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 27 Mar 2022 16:35:55 -0500 Subject: [PATCH 439/637] GUI: remove invalid comment --- src/gui/insEdit.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 837815da0..8febd9f41 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -186,7 +186,6 @@ void addAALine(ImDrawList* dl, const ImVec2& p1, const ImVec2& p2, const ImU32 c dl->AddPolyline(pt,2,color,ImDrawFlags_None,thickness); } -// TODO: don't get drunk tonight void FurnaceGUI::drawSSGEnv(unsigned char type, const ImVec2& size) { ImDrawList* dl=ImGui::GetWindowDrawList(); ImGuiWindow* window=ImGui::GetCurrentWindow(); From e5ce7c63f867a158c00893bccf5f09f6feb8f1f7 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 27 Mar 2022 18:30:46 -0500 Subject: [PATCH 440/637] GUI: redesign FM editor layout, part 4 now with OPL and OPLL --- src/gui/insEdit.cpp | 200 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 169 insertions(+), 31 deletions(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 8febd9f41..68defe8ca 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -297,7 +297,83 @@ void FurnaceGUI::drawSSGEnv(unsigned char type, const ImVec2& size) { } void FurnaceGUI::drawWaveform(unsigned char type, bool opz, const ImVec2& size) { + ImDrawList* dl=ImGui::GetWindowDrawList(); + ImGuiWindow* window=ImGui::GetCurrentWindow(); + ImVec2 waveform[65]; + const size_t waveformLen=64; + + ImVec2 minArea=window->DC.CursorPos; + ImVec2 maxArea=ImVec2( + minArea.x+size.x, + minArea.y+size.y + ); + ImRect rect=ImRect(minArea,maxArea); + ImGuiStyle& style=ImGui::GetStyle(); + ImU32 color=ImGui::GetColorU32(uiColors[GUI_COLOR_TEXT]); + ImGui::ItemSize(size,style.FramePadding.y); + if (ImGui::ItemAdd(rect,ImGui::GetID("wsDisplay"))) { + ImGui::RenderFrame(rect.Min,rect.Max,ImGui::GetColorU32(ImGuiCol_FrameBg),true,style.FrameRounding); + switch (type) { + case 0: + for (size_t i=0; i<=waveformLen; i++) { + float x=(float)i/(float)waveformLen; + float y=sin(x*2.0*M_PI); + waveform[i]=ImLerp(rect.Min,rect.Max,ImVec2(x,0.5-y*0.4)); + } + break; + case 1: + for (size_t i=0; i<=waveformLen; i++) { + float x=(float)i/(float)waveformLen; + float y=MAX(0.0,sin(x*2.0*M_PI)); + waveform[i]=ImLerp(rect.Min,rect.Max,ImVec2(x,0.5-y*0.4)); + } + break; + case 2: + for (size_t i=0; i<=waveformLen; i++) { + float x=(float)i/(float)waveformLen; + float y=fabs(sin(x*2.0*M_PI)); + waveform[i]=ImLerp(rect.Min,rect.Max,ImVec2(x,0.5-y*0.4)); + } + break; + case 3: + for (size_t i=0; i<=waveformLen; i++) { + float x=(float)i/(float)waveformLen; + float y=fabs((tan(x*2.0*M_PI)>=0.0)?sin(x*2.0*M_PI):0.0); + waveform[i]=ImLerp(rect.Min,rect.Max,ImVec2(x,0.5-y*0.4)); + } + break; + case 4: + for (size_t i=0; i<=waveformLen; i++) { + float x=(float)i/(float)waveformLen; + float y=(x>=0.5)?0.0:sin(x*4.0*M_PI); + waveform[i]=ImLerp(rect.Min,rect.Max,ImVec2(x,0.5-y*0.4)); + } + break; + case 5: + for (size_t i=0; i<=waveformLen; i++) { + float x=(float)i/(float)waveformLen; + float y=(x>=0.5)?0.0:fabs(sin(x*4.0*M_PI)); + waveform[i]=ImLerp(rect.Min,rect.Max,ImVec2(x,0.5-y*0.4)); + } + break; + case 6: + for (size_t i=0; i<=waveformLen; i++) { + float x=(float)i/(float)waveformLen; + float y=(x>=0.5)?-1.0:1.0; + waveform[i]=ImLerp(rect.Min,rect.Max,ImVec2(x,0.5-y*0.4)); + } + break; + case 7: + for (size_t i=0; i<=waveformLen; i++) { + float x=(float)i/(float)waveformLen; + float y=pow(2.0*(x-0.5),3.0); + waveform[i]=ImLerp(rect.Min,rect.Max,ImVec2(x,0.5-y*0.4)); + } + break; + } + dl->AddPolyline(waveform,waveformLen+1,color,ImDrawFlags_None,dpiScale); + } } void FurnaceGUI::drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, const ImVec2& size) { @@ -1036,7 +1112,7 @@ if (ImGui::BeginTable("MacroSpace",2)) { \ ImGui::SetCursorPosX(ImGui::GetCursorPosX()+0.5*(ImGui::GetContentRegionAvail().x-ImGui::CalcTextSize(text).x)); #define CENTER_VSLIDER \ - ImGui::SetCursorPosX(ImGui::GetCursorPosX()+0.5f*ImGui::GetContentRegionAvail().x-20.0f); + ImGui::SetCursorPosX(ImGui::GetCursorPosX()+0.5f*ImGui::GetContentRegionAvail().x-10.0f*dpiScale); void FurnaceGUI::drawInsEdit() { if (nextWindow==GUI_WINDOW_INS_EDIT) { @@ -1182,25 +1258,36 @@ void FurnaceGUI::drawInsEdit() { } if (willDisplayOps) { if (settings.fmLayout==0) { - if (ImGui::BeginTable("FMOperators",16,ImGuiTableFlags_SizingStretchProp|ImGuiTableFlags_BordersH|ImGuiTableFlags_BordersOuterV)) { + int numCols=16; + if (ins->type==DIV_INS_OPL) numCols=13; + if (ins->type==DIV_INS_OPLL) numCols=12; + if (ImGui::BeginTable("FMOperators",numCols,ImGuiTableFlags_SizingStretchProp|ImGuiTableFlags_BordersH|ImGuiTableFlags_BordersOuterV)) { // configure columns ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); // op name ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.05f); // ar ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch,0.05f); // dr ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthStretch,0.05f); // sl - ImGui::TableSetupColumn("c4",ImGuiTableColumnFlags_WidthStretch,0.05f); // d2r + if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { + ImGui::TableSetupColumn("c4",ImGuiTableColumnFlags_WidthStretch,0.05f); // d2r + } ImGui::TableSetupColumn("c5",ImGuiTableColumnFlags_WidthStretch,0.05f); // rr ImGui::TableSetupColumn("c6",ImGuiTableColumnFlags_WidthFixed); // -separator- ImGui::TableSetupColumn("c7",ImGuiTableColumnFlags_WidthStretch,0.05f); // tl - ImGui::TableSetupColumn("c8",ImGuiTableColumnFlags_WidthStretch,0.05f); // rs + ImGui::TableSetupColumn("c8",ImGuiTableColumnFlags_WidthStretch,0.05f); // rs/ksl ImGui::TableSetupColumn("c9",ImGuiTableColumnFlags_WidthStretch,0.05f); // mult - ImGui::TableSetupColumn("c10",ImGuiTableColumnFlags_WidthStretch,0.05f); // dt - ImGui::TableSetupColumn("c11",ImGuiTableColumnFlags_WidthStretch,0.05f); // dt2 - ImGui::TableSetupColumn("c12",ImGuiTableColumnFlags_WidthFixed); // -separator- - ImGui::TableSetupColumn("c13",ImGuiTableColumnFlags_WidthStretch,0.2f); // ssg - ImGui::TableSetupColumn("c14",ImGuiTableColumnFlags_WidthStretch,0.3f); // env + + if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { + ImGui::TableSetupColumn("c10",ImGuiTableColumnFlags_WidthStretch,0.05f); // dt + ImGui::TableSetupColumn("c11",ImGuiTableColumnFlags_WidthStretch,0.05f); // dt2 + } ImGui::TableSetupColumn("c15",ImGuiTableColumnFlags_WidthFixed); // am + ImGui::TableSetupColumn("c12",ImGuiTableColumnFlags_WidthFixed); // -separator- + if (ins->type!=DIV_INS_OPLL) { + ImGui::TableSetupColumn("c13",ImGuiTableColumnFlags_WidthStretch,0.2f); // ssg/waveform + } + ImGui::TableSetupColumn("c14",ImGuiTableColumnFlags_WidthStretch,0.3f); // env + // header ImGui::TableNextRow(ImGuiTableRowFlags_Headers); ImGui::TableNextColumn(); @@ -1238,28 +1325,41 @@ void FurnaceGUI::drawInsEdit() { CENTER_TEXT(FM_SHORT_NAME(FM_RS)); ImGui::TextUnformatted(FM_SHORT_NAME(FM_RS)); } else { - CENTER_TEXT(FM_SHORT_NAME(FM_KSR)); - ImGui::TextUnformatted(FM_SHORT_NAME(FM_KSR)); + CENTER_TEXT(FM_SHORT_NAME(FM_KSL)); + ImGui::TextUnformatted(FM_SHORT_NAME(FM_KSL)); } ImGui::TableNextColumn(); CENTER_TEXT(FM_SHORT_NAME(FM_MULT)); ImGui::TextUnformatted(FM_SHORT_NAME(FM_MULT)); ImGui::TableNextColumn(); - CENTER_TEXT(FM_SHORT_NAME(FM_DT)); - ImGui::TextUnformatted(FM_SHORT_NAME(FM_DT)); + if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { + CENTER_TEXT(FM_SHORT_NAME(FM_DT)); + ImGui::TextUnformatted(FM_SHORT_NAME(FM_DT)); + ImGui::TableNextColumn(); + CENTER_TEXT(FM_SHORT_NAME(FM_DT2)); + ImGui::TextUnformatted(FM_SHORT_NAME(FM_DT2)); + ImGui::TableNextColumn(); + } + if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { + CENTER_TEXT(FM_SHORT_NAME(FM_AM)); + ImGui::TextUnformatted(FM_SHORT_NAME(FM_AM)); + } else { + CENTER_TEXT("Other"); + ImGui::TextUnformatted("Other"); + } ImGui::TableNextColumn(); - CENTER_TEXT(FM_SHORT_NAME(FM_DT2)); - ImGui::TextUnformatted(FM_SHORT_NAME(FM_DT2)); - ImGui::TableNextColumn(); - ImGui::TableNextColumn(); - CENTER_TEXT(FM_NAME(FM_SSG)); - ImGui::TextUnformatted(FM_NAME(FM_SSG)); + if (ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPZ) { + ImGui::TableNextColumn(); + CENTER_TEXT(FM_NAME(FM_WS)); + ImGui::TextUnformatted(FM_NAME(FM_WS)); + } else if (ins->type!=DIV_INS_OPLL) { + ImGui::TableNextColumn(); + CENTER_TEXT(FM_NAME(FM_SSG)); + ImGui::TextUnformatted(FM_NAME(FM_SSG)); + } ImGui::TableNextColumn(); CENTER_TEXT("Envelope"); ImGui::TextUnformatted("Envelope"); - ImGui::TableNextColumn(); - CENTER_TEXT(FM_SHORT_NAME(FM_AM)); - ImGui::TextUnformatted(FM_SHORT_NAME(FM_AM)); float sliderHeight=32.0f*dpiScale; @@ -1294,9 +1394,9 @@ void FurnaceGUI::drawInsEdit() { } int maxArDr=(ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ)?31:15; bool ssgOn=op.ssgEnv&8; - /*bool ksrOn=op.ksr; + bool ksrOn=op.ksr; bool vibOn=op.vib; - bool susOn=op.sus;*/ + bool susOn=op.sus; unsigned char ssgEnv=op.ssgEnv&7; ImGui::TableNextColumn(); @@ -1370,6 +1470,13 @@ void FurnaceGUI::drawInsEdit() { ImGui::SetTooltip("Only on YM2151 (OPM)"); } + ImGui::TableNextColumn(); + bool amOn=op.am; + ImGui::SetCursorPosY(ImGui::GetCursorPosY()+0.5*(sliderHeight-ImGui::GetFrameHeight())); + if (ImGui::Checkbox("##AM",&amOn)) { PARAMETER + op.am=amOn; + } + ImGui::TableNextColumn(); ImGui::Dummy(ImVec2(4.0f*dpiScale,2.0f*dpiScale)); @@ -1393,18 +1500,49 @@ void FurnaceGUI::drawInsEdit() { op.ssgEnv=(op.ssgEnv&8)|(ssgEnv&7); } } + } else { + ImGui::TableNextColumn(); + bool amOn=op.am; + ImGui::SetCursorPosY(ImGui::GetCursorPosY()+0.5*(sliderHeight-ImGui::GetFrameHeight()*4.0-ImGui::GetStyle().ItemSpacing.y*3.0)); + if (ImGui::Checkbox(FM_NAME(FM_AM),&amOn)) { PARAMETER + op.am=amOn; + } + if (ImGui::Checkbox(FM_NAME(FM_VIB),&vibOn)) { PARAMETER + op.vib=vibOn; + } + if (ImGui::Checkbox(FM_NAME(FM_KSR),&ksrOn)) { PARAMETER + op.ksr=ksrOn; + } + if (ins->type==DIV_INS_OPL) { + if (ImGui::Checkbox(FM_NAME(FM_SUS),&susOn)) { PARAMETER + op.sus=susOn; + } + } else if (ins->type==DIV_INS_OPLL) { + if (ImGui::Checkbox(FM_NAME(FM_EGS),&ssgOn)) { PARAMETER + op.ssgEnv=(op.ssgEnv&7)|(ssgOn<<3); + } + } + } + + if (ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPZ) { + ImGui::TableNextColumn(); + ImGui::Dummy(ImVec2(4.0f*dpiScale,2.0f*dpiScale)); + ImGui::TableNextColumn(); + + drawWaveform(op.ws&7,ins->type==DIV_INS_OPZ,ImVec2(ImGui::GetContentRegionAvail().x,sliderHeight-ImGui::GetFrameHeightWithSpacing())); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + P(ImGui::SliderScalar("##WS",ImGuiDataType_U8,&op.ws,&_ZERO,&_SEVEN,(ins->type==DIV_INS_OPZ)?opzWaveforms[op.ws&7]:oplWaveforms[op.ws&7])); rightClickable + if (ins->type==DIV_INS_OPL && ImGui::IsItemHovered()) { + ImGui::SetTooltip("OPL2/3 only (last 4 waveforms are OPL3 only)"); + } + } else if (ins->type==DIV_INS_OPLL) { + ImGui::TableNextColumn(); + ImGui::Dummy(ImVec2(4.0f*dpiScale,2.0f*dpiScale)); } ImGui::TableNextColumn(); drawFMEnv(op.tl&maxTl,op.ar&maxArDr,op.dr&maxArDr,(ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPLL)?((op.rr&15)*2):op.d2r&31,op.rr&15,op.sl&15,maxTl,maxArDr,ImVec2(ImGui::GetContentRegionAvail().x,sliderHeight)); - ImGui::TableNextColumn(); - bool amOn=op.am; - ImGui::SetCursorPosY(ImGui::GetCursorPosY()+0.5*(sliderHeight-ImGui::GetFrameHeight())); - if (ImGui::Checkbox("##AM",&amOn)) { PARAMETER - op.am=amOn; - } - ImGui::PopID(); } From 075f758e4dd65d7886d8e5f363cd7a05dfd49231 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 27 Mar 2022 22:04:01 -0500 Subject: [PATCH 441/637] Namco 163 refinements --- papers/format.md | 9 +++++++++ src/engine/engine.h | 4 ++-- src/engine/instrument.cpp | 12 ++++++++++++ src/engine/instrument.h | 4 ++-- src/engine/platform/n163.cpp | 2 +- src/gui/insEdit.cpp | 11 ++++------- 6 files changed, 30 insertions(+), 12 deletions(-) diff --git a/papers/format.md b/papers/format.md index d61e94d43..dd4bcce2d 100644 --- a/papers/format.md +++ b/papers/format.md @@ -29,6 +29,7 @@ furthermore, an `or reserved` indicates this field is always present, but is res the format versions are: +- 73: Furnace dev73 - 72: Furnace dev72 - 71: Furnace dev71 - 70: Furnace dev70 @@ -515,6 +516,14 @@ size | description | - 480 bytes 2?? | note sample × 120 | - 240 bytes + --- | **Namco 163 data** (>=73) + 4 | initial waveform + 1 | wave position + 1 | wave length + 1 | wave mode: + | - bit 1: update on change + | - bit 0: load on playback + 1 | reserved ``` # wavetable diff --git a/src/engine/engine.h b/src/engine/engine.h index 24c3e1352..0751941ee 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -42,8 +42,8 @@ #define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock(); #define BUSY_END isBusy.unlock(); softLocked=false; -#define DIV_VERSION "dev72" -#define DIV_ENGINE_VERSION 72 +#define DIV_VERSION "dev73" +#define DIV_ENGINE_VERSION 73 // for imports #define DIV_VERSION_MOD 0xff01 diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index 6556bfea9..f3cf9f591 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -387,6 +387,11 @@ void DivInstrument::putInsData(SafeWriter* w) { } // N163 + w->writeI(n163.wave); + w->writeC(n163.wavePos); + w->writeC(n163.waveLen); + w->writeC(n163.waveMode); + w->writeC(0); // reserved } DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) { @@ -743,6 +748,13 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) { } // N163 + if (version>=73) { + n163.wave=reader.readI(); + n163.wavePos=(unsigned char)reader.readC(); + n163.waveLen=(unsigned char)reader.readC(); + n163.waveMode=(unsigned char)reader.readC(); + reader.readC(); // reserved + } return DIV_DATA_SUCCESS; } diff --git a/src/engine/instrument.h b/src/engine/instrument.h index a69800a1f..9d51670c1 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -392,8 +392,8 @@ struct DivInstrumentN163 { DivInstrumentN163(): wave(-1), wavePos(0), - waveLen(0), - waveMode(0) {} + waveLen(32), + waveMode(3) {} }; struct DivInstrument { diff --git a/src/engine/platform/n163.cpp b/src/engine/platform/n163.cpp index 42b55e907..14c2d12db 100644 --- a/src/engine/platform/n163.cpp +++ b/src/engine/platform/n163.cpp @@ -34,7 +34,7 @@ rWriteMask(0x78-(c<<3)+(a&7),v,m) \ } -#define CHIP_FREQBASE (15*65536) +#define CHIP_FREQBASE (15*32768) const char* regCheatSheetN163[]={ "FreqL7", "40", diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index da5b0e519..4d707f81f 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -2043,19 +2043,16 @@ void FurnaceGUI::drawInsEdit() { } ImGui::EndTabItem(); } - if (ins->type==DIV_INS_N163) if (ImGui::BeginTabItem("N163")) { - ImGui::Text("Initial waveform"); - if (ImGui::InputInt("##WAVE",&ins->n163.wave,1,10)) { PARAMETER + if (ins->type==DIV_INS_N163) if (ImGui::BeginTabItem("Namco 163")) { + if (ImGui::InputInt("Waveform##WAVE",&ins->n163.wave,1,10)) { PARAMETER if (ins->n163.wave<0) ins->n163.wave=0; if (ins->n163.wave>=e->song.waveLen) ins->n163.wave=e->song.waveLen-1; } - ImGui::Text("Initial waveform position in RAM"); - if (ImGui::InputInt("##WAVEPOS",&ins->n163.wavePos,1,16)) { PARAMETER + if (ImGui::InputInt("Offset##WAVEPOS",&ins->n163.wavePos,1,16)) { PARAMETER if (ins->n163.wavePos<0) ins->n163.wavePos=0; if (ins->n163.wavePos>255) ins->n163.wavePos=255; } - ImGui::Text("Initial waveform length in RAM"); - if (ImGui::InputInt("##WAVELEN",&ins->n163.waveLen,4,16)) { PARAMETER + if (ImGui::InputInt("Length##WAVELEN",&ins->n163.waveLen,4,16)) { PARAMETER if (ins->n163.waveLen<0) ins->n163.waveLen=0; if (ins->n163.waveLen>252) ins->n163.waveLen=252; ins->n163.waveLen&=0xfc; From 25b07fb4f193963650bea3d0b2fa1e78185a7d5e Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 27 Mar 2022 22:18:50 -0500 Subject: [PATCH 442/637] typo fixes --- papers/doc/4-instrument/vrc6.md | 4 ++-- papers/doc/7-systems/vrc6.md | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/papers/doc/4-instrument/vrc6.md b/papers/doc/4-instrument/vrc6.md index b7612c2f7..a4ea64a18 100644 --- a/papers/doc/4-instrument/vrc6.md +++ b/papers/doc/4-instrument/vrc6.md @@ -3,5 +3,5 @@ VRC6 instrument editor consists of only three macros: - [Volume] - volume sequence -- [Arpeggio] - pitch sequencr -- [Duty cycle] - spicifies duty cycle for pulse wave channels +- [Arpeggio] - pitch sequence +- [Duty cycle] - specifies duty cycle for pulse wave channels diff --git a/papers/doc/7-systems/vrc6.md b/papers/doc/7-systems/vrc6.md index 7dfc2585f..63bda1ab7 100644 --- a/papers/doc/7-systems/vrc6.md +++ b/papers/doc/7-systems/vrc6.md @@ -1,16 +1,16 @@ # Konami VRC6 -Its one of NES mapper with sound expansion, and one of two VRCs with this feature by Konami. +the most popular expansion chip to the NES' sound system. -The chip has 2 pulse wave channel and single sawtooth channel. -volume register is 4 bit for pulse wave and 6 bit for sawtooth, but sawtooth output is corrupted when volume register value is too high. because this register is 8 bit accumulator in technically, its output is wraparoundable. +the chip has 2 pulse wave channels and one sawtooth channel. +volume register is 4 bit for pulse wave and 6 bit for sawtooth, but sawtooth output is corrupted when volume register value is too high. because this register is actually an 8 bit accumulator, its output may wrap around. -pulse wave duty cycle is 8 level, it can be ignored and it has potential for DAC at this case: volume register in this mode is DAC output and it can be PCM playback through this mode. -Furnace supports this routine for PCM playback, but it's consume a lot of CPU resource in real hardware. (even if conjunction with VRC6 integrated IRQ timer) +pulse wave duty cycle is 8-level. it can be ignored and it has potential for DAC at this case: volume register in this mode is DAC output and it can be PCM playback through this mode. +Furnace supports this routine for PCM playback, but it consumes a lot of CPU time in real hardware (even if conjunction with VRC6's integrated IRQ timer). # effects -- `12xx`: set duty cycle. (0 to 7) -- `17xx`: toggle PCM mode. +these effects only are effective in the pulse channels. -* All effects are affects at pulse channels only. \ No newline at end of file +- `12xx`: set duty cycle (0 to 7). +- `17xx`: toggle PCM mode. \ No newline at end of file From 13a88730503bca61f9923067f6b5b0deee6c8c21 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 27 Mar 2022 22:30:29 -0500 Subject: [PATCH 443/637] VRC6: period tuning fixes now it is identical to the NES channels --- src/engine/platform/vrc6.cpp | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/engine/platform/vrc6.cpp b/src/engine/platform/vrc6.cpp index 630d59722..27cb2ffaf 100644 --- a/src/engine/platform/vrc6.cpp +++ b/src/engine/platform/vrc6.cpp @@ -22,8 +22,6 @@ #include #include -#define CHIP_DIVIDER 1 // 16 for pulse, 14 for sawtooth - #define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } #define chWrite(c,a,v) rWrite(0x9000+(c<<12)+(a&3),v) @@ -139,6 +137,8 @@ void DivPlatformVRC6::acquire(short* bufL, short* bufR, size_t start, size_t len void DivPlatformVRC6::tick() { for (int i=0; i<3; i++) { + // 16 for pulse; 14 for saw + int CHIP_DIVIDER=(i==2)?14:16; chan[i].std.next(); if (chan[i].std.hadVol) { if (i==2) { // sawtooth @@ -180,9 +180,9 @@ void DivPlatformVRC6::tick() { } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { if (i==2) { // sawtooth - chan[i].freq=parent->calcFreq(chan[i].baseFreq/14,chan[i].pitch,true)-1; + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true)-1; } else { // pulse - chan[i].freq=parent->calcFreq(chan[i].baseFreq/16,chan[i].pitch,true)-1; + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true)-1; if (chan[i].furnaceDac) { double off=1.0; if (chan[i].dacSample>=0 && chan[i].dacSamplesong.sampleLen) { @@ -199,10 +199,6 @@ void DivPlatformVRC6::tick() { } if (chan[i].freq>4095) chan[i].freq=4095; if (chan[i].freq<0) chan[i].freq=0; - if (chan[i].keyOn) { - //rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(ins->gb.soundLen&63))); - //rWrite(16+i*5+2,((chan[i].vol<<4))|(ins->gb.envLen&7)|((ins->gb.envDir&1)<<3)); - } if (chan[i].keyOff) { chWrite(i,2,0); } else { @@ -217,6 +213,7 @@ void DivPlatformVRC6::tick() { } int DivPlatformVRC6::dispatch(DivCommand c) { + int CHIP_DIVIDER=(c.chan==2)?14:16; switch (c.cmd) { case DIV_CMD_NOTE_ON: if (c.chan!=2) { // pulse wave From 7bd39603a99da43bd180d9f50fba9d0c55c21371 Mon Sep 17 00:00:00 2001 From: cam900 Date: Mon, 28 Mar 2022 12:31:42 +0900 Subject: [PATCH 444/637] Further fix --- src/engine/platform/sound/vrcvi/vrcvi.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/engine/platform/sound/vrcvi/vrcvi.cpp b/src/engine/platform/sound/vrcvi/vrcvi.cpp index a8a289da9..19152ed74 100644 --- a/src/engine/platform/sound/vrcvi/vrcvi.cpp +++ b/src/engine/platform/sound/vrcvi/vrcvi.cpp @@ -145,10 +145,7 @@ bool vrcvi_core::alu_t::tick() bool vrcvi_core::pulse_t::tick() { if (!m_divider.m_enable) - { - m_cycle = 0; return false; - } if (vrcvi_core::alu_t::tick()) m_cycle = bitfield(m_cycle + 1, 0, 4); @@ -159,10 +156,7 @@ bool vrcvi_core::pulse_t::tick() bool vrcvi_core::sawtooth_t::tick() { if (!m_divider.m_enable) - { - m_accum = 0; return false; - } if (vrcvi_core::alu_t::tick()) { @@ -260,6 +254,8 @@ void vrcvi_core::pulse_w(u8 voice, u8 address, u8 data) break; case 0x02: // Pitch MSB, Enable/Disable - 0x9002/0x9001 (Pulse 1), 0xa002/0xa001 (Pulse 2) v.m_divider.write(true, data); + if (!v.m_divider.m_enable) // Reset duty cycle + v.m_cycle = 0; break; } } @@ -276,6 +272,8 @@ void vrcvi_core::saw_w(u8 address, u8 data) break; case 0x02: // Pitch MSB, Enable/Disable - 0xb002/0xb001 (Sawtooth) m_sawtooth.m_divider.write(true, data); + if (!m_sawtooth.m_divider.m_enable) // Reset accumulator + m_sawtooth.m_accum = 0; break; } } From 5360cd73f434cbcb7505cd792459ae1f59433c77 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 28 Mar 2022 03:46:50 -0500 Subject: [PATCH 445/637] earliest MIDI input! (no note input tho) --- CMakeLists.txt | 2 + TODO.md | 15 ++ amongus/RtMidiConfigUninstall.cmake.in | 21 +++ src/audio/abstract.cpp | 58 +++++++ src/audio/jack.cpp | 1 + src/audio/midi.cpp | 61 ++++++++ src/audio/rtmidi.cpp | 200 +++++++++++++++++++++++++ src/audio/rtmidi.h | 28 +++- src/audio/sdl.cpp | 1 + src/audio/taAudio.h | 50 ++++--- src/engine/engine.cpp | 38 +++++ src/engine/engine.h | 8 + src/engine/playback.cpp | 25 ++++ src/gui/gui.h | 6 +- src/gui/settings.cpp | 38 ++++- 15 files changed, 525 insertions(+), 27 deletions(-) create mode 100644 TODO.md create mode 100644 amongus/RtMidiConfigUninstall.cmake.in create mode 100644 src/audio/midi.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 80120b85c..3b9b5729a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -177,6 +177,7 @@ endif() set(AUDIO_SOURCES src/audio/abstract.cpp +src/audio/midi.cpp src/audio/sdl.cpp ) @@ -199,6 +200,7 @@ endif() if (USE_RTMIDI) list(APPEND AUDIO_SOURCES src/audio/rtmidi.cpp) message(STATUS "Building with RtMidi") + list(APPEND DEPENDENCIES_DEFINES HAVE_RTMIDI) else() message(STATUS "Building without RtMidi") endif() diff --git a/TODO.md b/TODO.md new file mode 100644 index 000000000..256b81d1e --- /dev/null +++ b/TODO.md @@ -0,0 +1,15 @@ +# to-do for 0.6 + +- **auto-backup. please.** +- non-monospaced fonts in pattern editor +- panning macro +- pitch macro +- sample editor + - keybinds + - don't allow editing samples that aren't 8/16-bit + - maybe an hex editor +- piano/input pad +- MIDI input +- all the systems +- Also confused as to why after playing a row with Ctrl-Enter, you are locked out of moving the cursor outside of +/- 1 position and THEN typing something places it under the actual cursor, and not the ghost cursor. it seems like just a way to get "please can we play rows" people to be quiet because it's otherwise effectively useless +- edit latch! and maybe separate edit masks diff --git a/amongus/RtMidiConfigUninstall.cmake.in b/amongus/RtMidiConfigUninstall.cmake.in new file mode 100644 index 000000000..db894b3fc --- /dev/null +++ b/amongus/RtMidiConfigUninstall.cmake.in @@ -0,0 +1,21 @@ +if(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") + message(FATAL_ERROR "Cannot find install manifest: \"@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt\"") +endif(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") + +file(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files) +string(REGEX REPLACE "\n" ";" files "${files}") +foreach(file ${files}) + message(STATUS "Uninstalling \"$ENV{DESTDIR}${file}\"") + if(EXISTS "$ENV{DESTDIR}${file}") + exec_program( + "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" + OUTPUT_VARIABLE rm_out + RETURN_VALUE rm_retval + ) + if(NOT "${rm_retval}" STREQUAL 0) + message(FATAL_ERROR "Problem when removing \"$ENV{DESTDIR}${file}\"") + endif(NOT "${rm_retval}" STREQUAL 0) + else(EXISTS "$ENV{DESTDIR}${file}") + message(STATUS "File \"$ENV{DESTDIR}${file}\" does not exist.") + endif(EXISTS "$ENV{DESTDIR}${file}") +endforeach(file) diff --git a/src/audio/abstract.cpp b/src/audio/abstract.cpp index 7f9404828..d4973180a 100644 --- a/src/audio/abstract.cpp +++ b/src/audio/abstract.cpp @@ -55,4 +55,62 @@ bool TAAudio::init(TAAudioDesc& request, TAAudioDesc& response) { } TAAudio::~TAAudio() { +} + +bool TAMidiIn::gather() { + return false; +} + +bool TAMidiIn::isDeviceOpen() { + return false; +} + +bool TAMidiOut::isDeviceOpen() { + return false; +} + +bool TAMidiIn::openDevice(String name) { + return false; +} + +bool TAMidiOut::openDevice(String name) { + return false; +} + +bool TAMidiIn::closeDevice() { + return false; +} + +bool TAMidiOut::closeDevice() { + return false; +} + +std::vector TAMidiIn::listDevices() { + return std::vector(); +} + +std::vector TAMidiOut::listDevices() { + return std::vector(); +} + +bool TAMidiIn::init() { + return false; +} + +bool TAMidiOut::init() { + return false; +} + +bool TAMidiIn::quit() { + return true; +} + +bool TAMidiOut::quit() { + return true; +} + +TAMidiIn::~TAMidiIn() { +} + +TAMidiOut::~TAMidiOut() { } \ No newline at end of file diff --git a/src/audio/jack.cpp b/src/audio/jack.cpp index 080dad2c5..33a464d51 100644 --- a/src/audio/jack.cpp +++ b/src/audio/jack.cpp @@ -52,6 +52,7 @@ void TAAudioJACK::onBufferSize(jack_nframes_t bufsize) { void TAAudioJACK::onProcess(jack_nframes_t nframes) { if (audioProcCallback!=NULL) { + if (midiIn!=NULL) midiIn->gather(); audioProcCallback(audioProcCallbackUser,inBufs,outBufs,desc.inChans,desc.outChans,desc.bufsize); } for (int i=0; iinit()) { + delete midiIn; + midiIn=NULL; + return false; + } + + if (!midiOut->init()) { + midiIn->quit(); + delete midiOut; + delete midiIn; + midiOut=NULL; + midiIn=NULL; + return false; + } + return true; +#endif +} + +void TAAudio::quitMidi() { + if (midiIn!=NULL) { + midiIn->quit(); + delete midiIn; + midiIn=NULL; + } + if (midiOut!=NULL) { + midiOut->quit(); + delete midiOut; + midiOut=NULL; + } +} \ No newline at end of file diff --git a/src/audio/rtmidi.cpp b/src/audio/rtmidi.cpp index e8f0c60d6..a9b2d4b11 100644 --- a/src/audio/rtmidi.cpp +++ b/src/audio/rtmidi.cpp @@ -1 +1,201 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + #include "rtmidi.h" +#include "../ta-log.h" + +// --- IN --- + +bool TAMidiInRtMidi::gather() { + std::vector msg; + if (port==NULL) return false; + while (true) { + TAMidiMessage m; + double t=port->getMessage(&msg); + if (msg.empty()) break; + + // parse message + m.time=t; + m.type=msg[0]; + if (m.type!=TA_MIDI_SYSEX && msg.size()>1) { + memcpy(m.data,msg.data()+1,MIN(msg.size()-1,7)); + } + queue.push(m); + } + return true; +} + +std::vector TAMidiInRtMidi::listDevices() { + std::vector ret; + logD("listing devices.\n"); + if (port==NULL) return ret; + + try { + unsigned int count=port->getPortCount(); + logD("got port count.\n"); + for (unsigned int i=0; igetPortName(i); + if (name!="") ret.push_back(name); + } + } catch (RtMidiError& e) { + logW("could not get MIDI inputs! %s\n",e.what()); + } + return ret; +} + +bool TAMidiInRtMidi::isDeviceOpen() { + return isOpen; +} + +bool TAMidiInRtMidi::openDevice(String name) { + if (port==NULL) return false; + if (isOpen) return false; + try { + bool portOpen=false; + unsigned int count=port->getPortCount(); + for (unsigned int i=0; igetPortName(i)==name) { + port->openPort(i); + portOpen=true; + break; + } + } + isOpen=portOpen; + return portOpen; + } catch (RtMidiError& e) { + logW("could not open MIDI in device! %s\n",e.what()); + return false; + } + return true; +} + +bool TAMidiInRtMidi::closeDevice() { + if (port==NULL) return false; + if (!isOpen) return false; + try { + port->closePort(); + } catch (RtMidiError& e) { + logW("could not close MIDI in device! %s\n",e.what()); + isOpen=false; // still + return false; + } + isOpen=false; + return true; +} + +bool TAMidiInRtMidi::init() { + if (port!=NULL) return true; + try { + port=new RtMidiIn; + } catch (RtMidiError& e) { + logW("could not initialize RtMidi in! %s\n",e.what()); + return false; + } + return true; +} + +bool TAMidiInRtMidi::quit() { + if (port!=NULL) { + delete port; + port=NULL; + } + return true; +} + +// --- OUT --- + +bool TAMidiOutRtMidi::send(TAMidiMessage& what) { + // TODO + return false; +} + +bool TAMidiOutRtMidi::isDeviceOpen() { + return isOpen; +} + +bool TAMidiOutRtMidi::openDevice(String name) { + if (port==NULL) return false; + if (isOpen) return false; + try { + bool portOpen=false; + unsigned int count=port->getPortCount(); + for (unsigned int i=0; igetPortName(i)==name) { + port->openPort(i); + portOpen=true; + break; + } + } + isOpen=portOpen; + return portOpen; + } catch (RtMidiError& e) { + logW("could not open MIDI out device! %s\n",e.what()); + return false; + } + return true; +} + +bool TAMidiOutRtMidi::closeDevice() { + if (port==NULL) return false; + if (!isOpen) return false; + try { + port->closePort(); + } catch (RtMidiError& e) { + logW("could not close MIDI out device! %s\n",e.what()); + isOpen=false; // still + return false; + } + isOpen=false; + return true; +} + +std::vector TAMidiOutRtMidi::listDevices() { + std::vector ret; + if (port==NULL) return ret; + + try { + unsigned int count=port->getPortCount(); + for (unsigned int i=0; igetPortName(i); + if (name!="") ret.push_back(name); + } + } catch (RtMidiError& e) { + logW("could not get MIDI outputs! %s\n",e.what()); + } + return ret; +} + +bool TAMidiOutRtMidi::init() { + if (port!=NULL) return true; + try { + port=new RtMidiOut; + } catch (RtMidiError& e) { + logW("could not initialize RtMidi out! %s\n",e.what()); + return false; + } + return true; +} + +bool TAMidiOutRtMidi::quit() { + if (port!=NULL) { + delete port; + port=NULL; + } + return true; +} \ No newline at end of file diff --git a/src/audio/rtmidi.h b/src/audio/rtmidi.h index 0803c3fcd..31d7119b1 100644 --- a/src/audio/rtmidi.h +++ b/src/audio/rtmidi.h @@ -21,9 +21,33 @@ #include "taAudio.h" class TAMidiInRtMidi: public TAMidiIn { - + RtMidiIn* port; + bool isOpen; + public: + bool gather(); + bool isDeviceOpen(); + bool openDevice(String name); + bool closeDevice(); + std::vector listDevices(); + bool quit(); + bool init(); + TAMidiInRtMidi(): + port(NULL), + isOpen(false) {} }; class TAMidiOutRtMidi: public TAMidiOut { - + RtMidiOut* port; + bool isOpen; + public: + bool send(TAMidiMessage& what); + bool isDeviceOpen(); + bool openDevice(String name); + bool closeDevice(); + std::vector listDevices(); + bool quit(); + bool init(); + TAMidiOutRtMidi(): + port(NULL), + isOpen(false) {} }; \ No newline at end of file diff --git a/src/audio/sdl.cpp b/src/audio/sdl.cpp index 9f330fd59..7fbbceb55 100644 --- a/src/audio/sdl.cpp +++ b/src/audio/sdl.cpp @@ -30,6 +30,7 @@ void taSDLProcess(void* inst, unsigned char* buf, int nframes) { void TAAudioSDL::onProcess(unsigned char* buf, int nframes) { if (audioProcCallback!=NULL) { + if (midiIn!=NULL) midiIn->gather(); audioProcCallback(audioProcCallbackUser,inBufs,outBufs,desc.inChans,desc.outChans,desc.bufsize); } float* fbuf=(float*)buf; diff --git a/src/audio/taAudio.h b/src/audio/taAudio.h index 1f8c32305..f532e2a7e 100644 --- a/src/audio/taAudio.h +++ b/src/audio/taAudio.h @@ -90,28 +90,9 @@ enum TAMidiMessageTypes { }; struct TAMidiMessage { + double time; unsigned char type; - union { - struct { - unsigned char note, vol; - } note; - struct { - unsigned char which, val; - } control; - unsigned char patch; - unsigned char pressure; - struct { - unsigned char low, high; - } pitch; - struct { - unsigned int vendor; - } sysEx; - unsigned char timeCode; - struct { - unsigned char low, high; - } position; - unsigned char song; - } data; + unsigned char data[7]; unsigned char* sysExData; size_t sysExLen; @@ -119,6 +100,7 @@ struct TAMidiMessage { void done(); TAMidiMessage(): + time(0.0), type(0), sysExData(NULL), sysExLen(0) { @@ -127,16 +109,34 @@ struct TAMidiMessage { }; class TAMidiIn { - std::queue queue; public: + std::queue queue; virtual bool gather(); bool next(TAMidiMessage& where); + virtual bool isDeviceOpen(); + virtual bool openDevice(String name); + virtual bool closeDevice(); + virtual std::vector listDevices(); + virtual bool init(); + virtual bool quit(); + TAMidiIn() { + } + virtual ~TAMidiIn(); }; class TAMidiOut { std::queue queue; public: bool send(TAMidiMessage& what); + virtual bool isDeviceOpen(); + virtual bool openDevice(String name); + virtual bool closeDevice(); + virtual std::vector listDevices(); + virtual bool init(); + virtual bool quit(); + TAMidiOut() { + } + virtual ~TAMidiOut(); }; class TAAudio { @@ -162,6 +162,8 @@ class TAAudio { virtual bool quit(); virtual bool setRun(bool run); virtual std::vector listAudioDevices(); + bool initMidi(bool jack); + void quitMidi(); virtual bool init(TAAudioDesc& request, TAAudioDesc& response); TAAudio(): @@ -172,7 +174,9 @@ class TAAudio { outBufs(NULL), audioProcCallback(NULL), sampleRateChanged(NULL), - bufferSizeChanged(NULL) {} + bufferSizeChanged(NULL), + midiIn(NULL), + midiOut(NULL) {} virtual ~TAAudio(); }; diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 4973b44f6..431146f1a 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -2653,10 +2653,24 @@ std::vector& DivEngine::getAudioDevices() { return audioDevs; } +std::vector& DivEngine::getMidiIns() { + return midiIns; +} + +std::vector& DivEngine::getMidiOuts() { + return midiOuts; +} + void DivEngine::rescanAudioDevices() { audioDevs.clear(); if (output!=NULL) { audioDevs=output->listAudioDevices(); + if (output->midiIn!=NULL) { + midiIns=output->midiIn->listDevices(); + } + if (output->midiOut!=NULL) { + midiOuts=output->midiOut->listDevices(); + } } } @@ -2786,11 +2800,35 @@ bool DivEngine::initAudioBackend() { return false; } + if (output->initMidi(false)) { + midiIns=output->midiIn->listDevices(); + midiOuts=output->midiOut->listDevices(); + } else { + logW("error while initializing MIDI!\n"); + } + if (output->midiIn) { + String inName=getConfString("midiInDevice",""); + if (!inName.empty()) { + // try opening device + logI("opening MIDI input.\n"); + if (!output->midiIn->openDevice(inName)) { + logW("could not open MIDI input device!\n"); + } + } + } + return true; } bool DivEngine::deinitAudioBackend() { if (output!=NULL) { + if (output->midiIn) { + if (output->midiIn->isDeviceOpen()) { + logI("closing MIDI input.\n"); + output->midiIn->closeDevice(); + } + } + output->quitMidi(); output->quit(); delete output; output=NULL; diff --git a/src/engine/engine.h b/src/engine/engine.h index 0751941ee..8e20dd418 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -214,6 +214,8 @@ class DivEngine { String lastError; String warnings; std::vector audioDevs; + std::vector midiIns; + std::vector midiOuts; std::vector cmdStream; struct SamplePreview { @@ -570,6 +572,12 @@ class DivEngine { // get available audio devices std::vector& getAudioDevices(); + // get available MIDI inputs + std::vector& getMidiIns(); + + // get available MIDI inputs + std::vector& getMidiOuts(); + // rescan audio devices void rescanAudioDevices(); diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 3d990bb2d..515ae8e6d 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -1537,7 +1537,32 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi isBusy.lock(); } got.bufsize=size; + + // process MIDI events (TODO: everything) + if (output->midiIn) while (!output->midiIn->queue.empty()) { + TAMidiMessage& msg=output->midiIn->queue.front(); + int chan=msg.type&15; + switch (msg.type&0xf0) { + case TA_MIDI_NOTE_OFF: { + if (chan<0 || chan>=chans) break; + pendingNotes.push(DivNoteEvent(msg.type&15,-1,-1,-1,false)); + break; + } + case TA_MIDI_NOTE_ON: { + if (chan<0 || chan>=chans) break; + pendingNotes.push(DivNoteEvent(msg.type&15,-1,(int)msg.data[0]-12,msg.data[1],true)); + break; + } + case TA_MIDI_PROGRAM: { + // TODO: change instrument event thingy + break; + } + } + logD("%.2x\n",msg.type); + output->midiIn->queue.pop(); + } + // process audio if (out!=NULL && ((sPreview.sample>=0 && sPreview.sample<(int)song.sample.size()) || (sPreview.wave>=0 && sPreview.wave<(int)song.wave.size()))) { unsigned int samp_bbOff=0; unsigned int prevAvail=blip_samples_avail(samp_bb); diff --git a/src/gui/gui.h b/src/gui/gui.h index 3b6056f4f..1b06d1526 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -628,6 +628,8 @@ class FurnaceGUI { String mainFontPath; String patFontPath; String audioDevice; + String midiInDevice; + String midiOutDevice; Settings(): mainFontSize(18), @@ -679,7 +681,9 @@ class FurnaceGUI { maxUndoSteps(100), mainFontPath(""), patFontPath(""), - audioDevice("") {} + audioDevice(""), + midiInDevice(""), + midiOutDevice("") {} } settings; char finalLayoutPath[4096]; diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 9484762b8..c2281d633 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -222,7 +222,7 @@ void FurnaceGUI::drawSettings() { ImGui::EndTabItem(); } - if (ImGui::BeginTabItem("Audio")) { + if (ImGui::BeginTabItem("Audio/MIDI")) { ImGui::Text("Backend"); ImGui::SameLine(); ImGui::Combo("##Backend",&settings.audioEngine,audioBackends,2); @@ -286,6 +286,38 @@ void FurnaceGUI::drawSettings() { ImGui::Text("want: %d samples @ %.0fHz\n",audioWant.bufsize,audioWant.rate); ImGui::Text("got: %d samples @ %.0fHz\n",audioGot.bufsize,audioGot.rate); + ImGui::Separator(); + + ImGui::Text("MIDI input"); + ImGui::SameLine(); + String midiInName=settings.midiInDevice.empty()?"":settings.midiInDevice; + if (ImGui::BeginCombo("##MidiInDevice",midiInName.c_str())) { + if (ImGui::Selectable("",settings.midiInDevice.empty())) { + settings.midiInDevice=""; + } + for (String& i: e->getMidiIns()) { + if (ImGui::Selectable(i.c_str(),i==settings.midiInDevice)) { + settings.midiInDevice=i; + } + } + ImGui::EndCombo(); + } + + ImGui::Text("MIDI output"); + ImGui::SameLine(); + String midiOutName=settings.midiOutDevice.empty()?"":settings.midiOutDevice; + if (ImGui::BeginCombo("##MidiOutDevice",midiOutName.c_str())) { + if (ImGui::Selectable("",settings.midiOutDevice.empty())) { + settings.midiOutDevice=""; + } + for (String& i: e->getMidiIns()) { + if (ImGui::Selectable(i.c_str(),i==settings.midiOutDevice)) { + settings.midiOutDevice=i; + } + } + ImGui::EndCombo(); + } + ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Emulation")) { @@ -970,6 +1002,8 @@ void FurnaceGUI::syncSettings() { settings.iconSize=e->getConfInt("iconSize",16); settings.audioEngine=(e->getConfString("audioEngine","SDL")=="SDL")?1:0; settings.audioDevice=e->getConfString("audioDevice",""); + settings.midiInDevice=e->getConfString("midiInDevice",""); + settings.midiOutDevice=e->getConfString("midiOutDevice",""); settings.audioQuality=e->getConfInt("audioQuality",0); settings.audioBufSize=e->getConfInt("audioBufSize",1024); settings.audioRate=e->getConfInt("audioRate",44100); @@ -1252,6 +1286,8 @@ void FurnaceGUI::commitSettings() { e->setConf("iconSize",settings.iconSize); e->setConf("audioEngine",String(audioBackends[settings.audioEngine])); e->setConf("audioDevice",settings.audioDevice); + e->setConf("midiInDevice",settings.midiInDevice); + e->setConf("midiOutDevice",settings.midiOutDevice); e->setConf("audioQuality",settings.audioQuality); e->setConf("audioBufSize",settings.audioBufSize); e->setConf("audioRate",settings.audioRate); From 0784fec5f54024c1f6e523ae451a78cb3aa87610 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 28 Mar 2022 05:02:32 -0500 Subject: [PATCH 446/637] what?! --- TODO.md | 15 --------------- amongus/RtMidiConfigUninstall.cmake.in | 21 --------------------- 2 files changed, 36 deletions(-) delete mode 100644 TODO.md delete mode 100644 amongus/RtMidiConfigUninstall.cmake.in diff --git a/TODO.md b/TODO.md deleted file mode 100644 index 256b81d1e..000000000 --- a/TODO.md +++ /dev/null @@ -1,15 +0,0 @@ -# to-do for 0.6 - -- **auto-backup. please.** -- non-monospaced fonts in pattern editor -- panning macro -- pitch macro -- sample editor - - keybinds - - don't allow editing samples that aren't 8/16-bit - - maybe an hex editor -- piano/input pad -- MIDI input -- all the systems -- Also confused as to why after playing a row with Ctrl-Enter, you are locked out of moving the cursor outside of +/- 1 position and THEN typing something places it under the actual cursor, and not the ghost cursor. it seems like just a way to get "please can we play rows" people to be quiet because it's otherwise effectively useless -- edit latch! and maybe separate edit masks diff --git a/amongus/RtMidiConfigUninstall.cmake.in b/amongus/RtMidiConfigUninstall.cmake.in deleted file mode 100644 index db894b3fc..000000000 --- a/amongus/RtMidiConfigUninstall.cmake.in +++ /dev/null @@ -1,21 +0,0 @@ -if(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") - message(FATAL_ERROR "Cannot find install manifest: \"@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt\"") -endif(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt") - -file(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files) -string(REGEX REPLACE "\n" ";" files "${files}") -foreach(file ${files}) - message(STATUS "Uninstalling \"$ENV{DESTDIR}${file}\"") - if(EXISTS "$ENV{DESTDIR}${file}") - exec_program( - "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" - OUTPUT_VARIABLE rm_out - RETURN_VALUE rm_retval - ) - if(NOT "${rm_retval}" STREQUAL 0) - message(FATAL_ERROR "Problem when removing \"$ENV{DESTDIR}${file}\"") - endif(NOT "${rm_retval}" STREQUAL 0) - else(EXISTS "$ENV{DESTDIR}${file}") - message(STATUS "File \"$ENV{DESTDIR}${file}\" does not exist.") - endif(EXISTS "$ENV{DESTDIR}${file}") -endforeach(file) From 1ca63bdc093c89fcea93fcd51e84c8827e53d37d Mon Sep 17 00:00:00 2001 From: cam900 Date: Tue, 29 Mar 2022 00:31:17 +0900 Subject: [PATCH 447/637] Fix N163 --- src/engine/platform/n163.cpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/engine/platform/n163.cpp b/src/engine/platform/n163.cpp index 14c2d12db..6c0418c8a 100644 --- a/src/engine/platform/n163.cpp +++ b/src/engine/platform/n163.cpp @@ -194,7 +194,7 @@ void DivPlatformN163::updateWave(int wave, int pos, int len) { void DivPlatformN163::updateWaveCh(int ch) { if (ch<=chanMax) { updateWave(chan[ch].wave,chan[ch].wavePos,chan[ch].waveLen); - if (chan[ch].active) { + if (chan[ch].active && !isMuted[ch]) { chan[ch].volumeChanged=true; } } @@ -301,21 +301,25 @@ void DivPlatformN163::tick() { } } if (chan[i].volumeChanged) { - if ((!chan[i].active) || isMuted[i]) { - chWriteMask(i,0x7,0,0xf); - } else { + if (chan[i].active && !isMuted[i]) { chWriteMask(i,0x7,chan[i].resVol&0xf,0xf); + } else { + chWriteMask(i,0x7,0,0xf); } chan[i].volumeChanged=false; } if (chan[i].waveChanged) { chWrite(i,0x6,chan[i].wavePos); - chan[i].freqChanged=true; + if (chan[i].active) { + chan[i].freqChanged=true; + } chan[i].waveChanged=false; } if (chan[i].waveUpdated) { updateWaveCh(i); - if (!chan[i].keyOff) chan[i].keyOn=true; + if (chan[i].active) { + if (!chan[i].keyOff) chan[i].keyOn=true; + } chan[i].waveUpdated=false; } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { @@ -331,7 +335,7 @@ void DivPlatformN163::tick() { } } if (chan[i].keyOff && !isMuted[i]) { - chWriteMask(i,0x07,0,0xf); + chWriteMask(i,0x7,0,0xf); } chWrite(i,0x0,chan[i].freq&0xff); chWrite(i,0x2,chan[i].freq>>8); From 1019faf34cb9e234d0533a3b1b72631f39d3dc2b Mon Sep 17 00:00:00 2001 From: cam900 Date: Tue, 29 Mar 2022 04:56:35 +0900 Subject: [PATCH 448/637] Fix initial channel, Minor optimization for N163 --- src/engine/platform/n163.cpp | 24 ++++++++++++------------ src/engine/platform/n163.h | 3 ++- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/engine/platform/n163.cpp b/src/engine/platform/n163.cpp index 6c0418c8a..97abe1024 100644 --- a/src/engine/platform/n163.cpp +++ b/src/engine/platform/n163.cpp @@ -21,7 +21,7 @@ #include "../engine.h" #include -#define rRead(a,v) n163->addr_w(a); n163->data_r(v); +#define rRead(a,v) n163.addr_w(a); n163.data_r(v); #define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } #define rWriteMask(a,v,m) if (!skipRegisterWrites) {writes.emplace(a,v,m); if (dumpWrites) {addWrite(a,v);} } #define chWrite(c,a,v) \ @@ -155,8 +155,8 @@ const char* DivPlatformN163::getEffectName(unsigned char effect) { void DivPlatformN163::acquire(short* bufL, short* bufR, size_t start, size_t len) { for (size_t i=start; itick(); - int out=(n163->out()<<6)*(chanMax+1); // scale to 16 bit + n163.tick(); + int out=(n163.out()<<6)*(chanMax+1); // scale to 16 bit if (out>32767) out=32767; if (out<-32768) out=-32768; bufL[i]=bufR[i]=out; @@ -164,8 +164,8 @@ void DivPlatformN163::acquire(short* bufL, short* bufR, size_t start, size_t len // command queue while (!writes.empty()) { QueuedWrite w=writes.front(); - n163->addr_w(w.addr); - n163->data_w((n163->data_r()&~w.mask)|(w.val&w.mask)); + n163.addr_w(w.addr); + n163.data_w((n163.data_r()&~w.mask)|(w.val&w.mask)); writes.pop(); } } @@ -586,7 +586,7 @@ void* DivPlatformN163::getChanState(int ch) { unsigned char* DivPlatformN163::getRegisterPool() { for (int i=0; i<128; i++) { - regPool[i]=n163->reg(i); + regPool[i]=n163.reg(i); } return regPool; } @@ -601,15 +601,16 @@ void DivPlatformN163::reset() { chan[i]=DivPlatformN163::Channel(); } - n163->reset(); + n163.reset(); memset(regPool,0,128); - n163->set_disable(false); - rWrite(0x7f,chanMax<<4); + n163.set_disable(false); + chanMax=initChanMax; loadWave=-1; loadPos=0; loadLen=0; loadMode=0; + rWrite(0x7f,initChanMax<<4); } void DivPlatformN163::poke(unsigned int addr, unsigned short val) { @@ -632,9 +633,10 @@ void DivPlatformN163::setFlags(unsigned int flags) { rate=COLOR_PAL*2.0/5.0; break; } - chanMax=(flags>>4)&7; + initChanMax=chanMax=(flags>>4)&7; chipClock=rate; rate/=15; + rWrite(0x7f,initChanMax<<4); } int DivPlatformN163::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { @@ -644,7 +646,6 @@ int DivPlatformN163::init(DivEngine* p, int channels, int sugRate, unsigned int for (int i=0; i<8; i++) { isMuted[i]=false; } - n163=new n163_core(); setFlags(flags); reset(); @@ -653,7 +654,6 @@ int DivPlatformN163::init(DivEngine* p, int channels, int sugRate, unsigned int } void DivPlatformN163::quit() { - delete n163; } DivPlatformN163::~DivPlatformN163() { diff --git a/src/engine/platform/n163.h b/src/engine/platform/n163.h index 1aa520482..4bc5cc637 100644 --- a/src/engine/platform/n163.h +++ b/src/engine/platform/n163.h @@ -71,11 +71,12 @@ class DivPlatformN163: public DivDispatch { QueuedWrite(unsigned char a, unsigned char v, unsigned char m=~0): addr(a), val(v), mask(m) {} }; std::queue writes; + unsigned char initChanMax; unsigned char chanMax; short loadWave, loadPos, loadLen; unsigned char loadMode; - n163_core *n163; + n163_core n163; unsigned char regPool[128]; void updateWave(int wave, int pos, int len); void updateWaveCh(int ch); From 08910d37b273b431c333afcf83cc216708dbcdc8 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 28 Mar 2022 15:24:09 -0500 Subject: [PATCH 449/637] oh yes more MIDI work --- src/engine/engine.cpp | 4 ++++ src/engine/engine.h | 6 ++++++ src/engine/playback.cpp | 32 +++++++++++++++++--------------- src/gui/gui.cpp | 7 ++++++- 4 files changed, 33 insertions(+), 16 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 431146f1a..2493d620d 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -2621,6 +2621,10 @@ bool DivEngine::switchMaster() { return true; } +void DivEngine::setMidiCallback(std::function what) { + midiCallback=what; +} + void DivEngine::synchronized(const std::function& what) { BUSY_BEGIN; what(); diff --git a/src/engine/engine.h b/src/engine/engine.h index 8e20dd418..1fe93f345 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -244,6 +244,8 @@ class DivEngine { size_t totalProcessed; + std::function midiCallback=[](const TAMidiMessage&) -> bool {return false;}; + DivSystem systemFromFile(unsigned char val); unsigned char systemToFile(DivSystem val); int dispatchCmd(DivCommand c); @@ -635,6 +637,10 @@ class DivEngine { // switch master bool switchMaster(); + // set MIDI input callback + // if the specified function returns true, note feedback will be inhibited. + void setMidiCallback(std::function what); + // perform secure/sync operation void synchronized(const std::function& what); diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 515ae8e6d..259209e4a 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -1541,21 +1541,23 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi // process MIDI events (TODO: everything) if (output->midiIn) while (!output->midiIn->queue.empty()) { TAMidiMessage& msg=output->midiIn->queue.front(); - int chan=msg.type&15; - switch (msg.type&0xf0) { - case TA_MIDI_NOTE_OFF: { - if (chan<0 || chan>=chans) break; - pendingNotes.push(DivNoteEvent(msg.type&15,-1,-1,-1,false)); - break; - } - case TA_MIDI_NOTE_ON: { - if (chan<0 || chan>=chans) break; - pendingNotes.push(DivNoteEvent(msg.type&15,-1,(int)msg.data[0]-12,msg.data[1],true)); - break; - } - case TA_MIDI_PROGRAM: { - // TODO: change instrument event thingy - break; + if (!midiCallback(msg)) { + int chan=msg.type&15; + switch (msg.type&0xf0) { + case TA_MIDI_NOTE_OFF: { + if (chan<0 || chan>=chans) break; + pendingNotes.push(DivNoteEvent(msg.type&15,-1,-1,-1,false)); + break; + } + case TA_MIDI_NOTE_ON: { + if (chan<0 || chan>=chans) break; + pendingNotes.push(DivNoteEvent(msg.type&15,-1,(int)msg.data[0]-12,msg.data[1],true)); + break; + } + case TA_MIDI_PROGRAM: { + // TODO: change instrument event thingy + break; + } } } logD("%.2x\n",msg.type); diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 7cbf10970..870e7de1b 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2628,6 +2628,11 @@ bool FurnaceGUI::init() { firstFrame=true; + // TODO + e->setMidiCallback([this](const TAMidiMessage& msg) -> bool { + return true; + }); + return true; } @@ -2907,4 +2912,4 @@ FurnaceGUI::FurnaceGUI(): memset(patChanX,0,sizeof(float)*(DIV_MAX_CHANS+1)); memset(patChanSlideY,0,sizeof(float)*(DIV_MAX_CHANS+1)); memset(lastIns,-1,sizeof(int)*DIV_MAX_CHANS); -} \ No newline at end of file +} From 81319e34bcc670647b0adb8b348642c4248c307a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 28 Mar 2022 15:31:03 -0500 Subject: [PATCH 450/637] error: There was a problem with the editor 'vi'. --- src/gui/gui.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 870e7de1b..911bef9f2 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2630,6 +2630,7 @@ bool FurnaceGUI::init() { // TODO e->setMidiCallback([this](const TAMidiMessage& msg) -> bool { + logD("I hate macOS: %p\n",this); return true; }); From 4825fe7adb1b92511e0781e1a62877c1f4c22808 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 28 Mar 2022 18:19:47 -0500 Subject: [PATCH 451/637] more work --- src/engine/engine.cpp | 2 +- src/engine/engine.h | 7 ++++--- src/engine/playback.cpp | 19 +++++++++++++++++-- src/gui/gui.cpp | 28 ++++++++++++++++++++++++---- src/gui/gui.h | 11 +++++++++++ 5 files changed, 57 insertions(+), 10 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 2493d620d..f6252cbe7 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -2621,7 +2621,7 @@ bool DivEngine::switchMaster() { return true; } -void DivEngine::setMidiCallback(std::function what) { +void DivEngine::setMidiCallback(std::function what) { midiCallback=what; } diff --git a/src/engine/engine.h b/src/engine/engine.h index 1fe93f345..52a01291b 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -244,7 +244,8 @@ class DivEngine { size_t totalProcessed; - std::function midiCallback=[](const TAMidiMessage&) -> bool {return false;}; + // MIDI stuff + std::function midiCallback=[](const TAMidiMessage&) -> int {return -1;}; DivSystem systemFromFile(unsigned char val); unsigned char systemToFile(DivSystem val); @@ -638,8 +639,8 @@ class DivEngine { bool switchMaster(); // set MIDI input callback - // if the specified function returns true, note feedback will be inhibited. - void setMidiCallback(std::function what); + // if the specified function returns -2, note feedback will be inhibited. + void setMidiCallback(std::function what); // perform secure/sync operation void synchronized(const std::function& what); diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 259209e4a..2f9d6f5c1 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -1541,17 +1541,32 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi // process MIDI events (TODO: everything) if (output->midiIn) while (!output->midiIn->queue.empty()) { TAMidiMessage& msg=output->midiIn->queue.front(); - if (!midiCallback(msg)) { + int ins=-1; + if ((ins=midiCallback(msg))!=-2) { int chan=msg.type&15; switch (msg.type&0xf0) { case TA_MIDI_NOTE_OFF: { if (chan<0 || chan>=chans) break; pendingNotes.push(DivNoteEvent(msg.type&15,-1,-1,-1,false)); + if (!playing) { + reset(); + freelance=true; + playing=true; + } break; } case TA_MIDI_NOTE_ON: { if (chan<0 || chan>=chans) break; - pendingNotes.push(DivNoteEvent(msg.type&15,-1,(int)msg.data[0]-12,msg.data[1],true)); + if (msg.data[1]==0) { + pendingNotes.push(DivNoteEvent(msg.type&15,-1,-1,-1,false)); + } else { + pendingNotes.push(DivNoteEvent(msg.type&15,ins,(int)msg.data[0]-12,msg.data[1],true)); + } + if (!playing) { + reset(); + freelance=true; + playing=true; + } break; } case TA_MIDI_PROGRAM: { diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 911bef9f2..89bd1a019 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1874,6 +1874,24 @@ bool FurnaceGUI::loop() { } } + while (true) { + midiLock.lock(); + if (midiQueue.empty()) { + midiLock.unlock(); + break; + } + TAMidiMessage msg=midiQueue.front(); + midiLock.unlock(); + + // parse message here + logD("message is %.2x\n",msg.type); + if (msg.type==0xb0) doAction(GUI_ACTION_PLAY_TOGGLE); + + midiLock.lock(); + midiQueue.pop(); + midiLock.unlock(); + } + ImGui_ImplSDLRenderer_NewFrame(); ImGui_ImplSDL2_NewFrame(sdlWin); ImGui::NewFrame(); @@ -2628,10 +2646,12 @@ bool FurnaceGUI::init() { firstFrame=true; - // TODO - e->setMidiCallback([this](const TAMidiMessage& msg) -> bool { - logD("I hate macOS: %p\n",this); - return true; + // TODO: MIDI mapping time! + e->setMidiCallback([this](const TAMidiMessage& msg) -> int { + midiLock.lock(); + midiQueue.push(msg); + midiLock.unlock(); + return curIns; }); return true; diff --git a/src/gui/gui.h b/src/gui/gui.h index 1b06d1526..933455def 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -492,6 +492,14 @@ struct UndoStep { std::vector pat; }; +struct MIDIBind { + int type, channel, data1, data2; +}; + +struct MIDIMap { + std::vector binds; +}; + struct Particle { ImU32* colors; const char* type; @@ -562,6 +570,9 @@ class FurnaceGUI { std::mutex backupLock; String backupPath; + std::mutex midiLock; + std::queue midiQueue; + ImFont* mainFont; ImFont* iconFont; ImFont* patFont; From 964039c8db84f97f20f37f8c1129b0ffea223887 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 29 Mar 2022 00:25:28 -0500 Subject: [PATCH 452/637] fix file export crash closes #323 --- src/engine/playback.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 2f9d6f5c1..ce2e485b6 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -1539,7 +1539,7 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi got.bufsize=size; // process MIDI events (TODO: everything) - if (output->midiIn) while (!output->midiIn->queue.empty()) { + if (output) if (output->midiIn) while (!output->midiIn->queue.empty()) { TAMidiMessage& msg=output->midiIn->queue.front(); int ins=-1; if ((ins=midiCallback(msg))!=-2) { From 92c1add0754007372775a1027cc0e4fa54d2f05a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 29 Mar 2022 00:37:45 -0500 Subject: [PATCH 453/637] GUI: prevent one possible bug when moving cursor --- src/gui/cursor.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/cursor.cpp b/src/gui/cursor.cpp index c783856a7..6a77ff0bd 100644 --- a/src/gui/cursor.cpp +++ b/src/gui/cursor.cpp @@ -263,6 +263,7 @@ void FurnaceGUI::moveCursorBottom(bool select) { if (cursor.y==e->song.patLen-1) { DETERMINE_LAST; cursor.xCoarse=lastChannel-1; + if (cursor.xCoarse<0) cursor.xCoarse=0; cursor.xFine=2+e->song.pat[cursor.xCoarse].effectRows*2; demandScrollX=true; } else { From f92eb9352d79c7c60e18ff05a814213746c90d26 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 29 Mar 2022 01:36:45 -0500 Subject: [PATCH 454/637] GUI: fix visual glitch when changing song length --- src/gui/songInfo.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/gui/songInfo.cpp b/src/gui/songInfo.cpp index e9caf5af9..b5923801e 100644 --- a/src/gui/songInfo.cpp +++ b/src/gui/songInfo.cpp @@ -124,6 +124,9 @@ void FurnaceGUI::drawSongInfo() { if (ordLen<1) ordLen=1; if (ordLen>127) ordLen=127; e->song.ordersLen=ordLen; + if (e->getOrder()>=ordLen) { + e->setOrder(ordLen-1); + } } ImGui::TableNextRow(); From 5cea8ed3e35f1facb78019a44066925acc2ac5ea Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 29 Mar 2022 02:28:40 -0500 Subject: [PATCH 455/637] QSound: change volume macro formula --- src/engine/platform/qsound.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/qsound.cpp b/src/engine/platform/qsound.cpp index 0e0637d8a..12bf1c58b 100644 --- a/src/engine/platform/qsound.cpp +++ b/src/engine/platform/qsound.cpp @@ -278,7 +278,7 @@ void DivPlatformQSound::tick() { for (int i=0; i<16; i++) { chan[i].std.next(); if (chan[i].std.hadVol) { - chan[i].outVol=((chan[i].vol&0xff)*MIN(255,chan[i].std.vol<<2))>>8; + chan[i].outVol=((chan[i].vol&0xff)*chan[i].std.vol)>>6; // Check if enabled and write volume if (chan[i].active) { rWrite(q1_reg_map[Q1V_VOL][i], chan[i].outVol << 4); From c26123e78226c07138dd94f68be1eeef7c90c85d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 29 Mar 2022 14:11:57 -0500 Subject: [PATCH 456/637] VIC-20: wait what? --- src/engine/platform/sound/vic20sound.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/sound/vic20sound.c b/src/engine/platform/sound/vic20sound.c index 8a42bc707..160151793 100644 --- a/src/engine/platform/sound/vic20sound.c +++ b/src/engine/platform/sound/vic20sound.c @@ -210,7 +210,7 @@ int vic_sound_machine_init(sound_vic20_t *snd, int speed, int cycles_per_sec) uint32_t i; float dt; - memset(snd, 0, sizeof(snd)); + memset(snd, 0, sizeof(sound_vic20_t)); snd->cycles_per_sample = (float)cycles_per_sec / speed; snd->leftover_cycles = 0.0f; From 77798f6ed7c90722ece1fc5d209efd594717ec5d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 29 Mar 2022 16:09:15 -0500 Subject: [PATCH 457/637] more MIDI work... still not there --- CMakeLists.txt | 1 + src/gui/gui.h | 65 +++++++- src/gui/guiConst.cpp | 211 +++++++++++++++++++++++++- src/gui/guiConst.h | 3 +- src/gui/midiMap.cpp | 209 ++++++++++++++++++++++++++ src/gui/settings.cpp | 344 +++++++++++++++++++++---------------------- 6 files changed, 657 insertions(+), 176 deletions(-) create mode 100644 src/gui/midiMap.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 3b9b5729a..4d4366020 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -379,6 +379,7 @@ src/gui/editing.cpp src/gui/editControls.cpp src/gui/insEdit.cpp src/gui/mixer.cpp +src/gui/midiMap.cpp src/gui/newSong.cpp src/gui/orders.cpp src/gui/osc.cpp diff --git a/src/gui/gui.h b/src/gui/gui.h index 933455def..22b185a41 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -492,12 +492,74 @@ struct UndoStep { std::vector pat; }; +// -1 = any struct MIDIBind { int type, channel, data1, data2; + int action; + MIDIBind(): + type(-1), + channel(-1), + data1(-1), + data2(-1), + action(0) {} }; struct MIDIMap { + // access method: map[type][channel][data1][data2]; + // channel 16 = any + // data1 128 = any + // data2 128 = any + int**** map; std::vector binds; + + bool noteInput, volInput, rawVolume, polyInput, directChannel, programChange, midiClock, midiTimeCode; + // 0: disabled + // + // 1: C- C# D- D# E- F- F# G- G# A- A# B- + // o1 1 3 6 8 A + // 0 2 4 5 7 9 B + // C- C# D- D# E- F- F# G- G# A- A# B- + // o2 D F + // C E + // + // 2: C- C# D- D# E- F- F# G- G# A- A# B- + // o1 1 3 6 8 A + // 0 2 4 5 7 9 B + // C- C# D- D# E- F- F# G- G# A- A# B- + // o2 D F 2 4 6 + // C E 0 1 3 5 7 + // + // 3: C- C# D- D# E- F- F# G- G# A- A# B- + // o1 A B C D E + // 0 1 2 3 4 5 6 + // C- C# D- D# E- F- F# G- G# A- A# B- + // o2 F + // 7 8 9 + // + // 4: use dual CC for value input (nibble) + // 5: use 14-bit CC for value input (MSB/LSB) + int valueInputStyle; + int valueInputControlMSB; // on 4 + int valueInputControlLSB; // on 4 + float volExp; + + void compile(); + void deinit(); + int at(TAMidiMessage& where); + bool read(String path); + bool write(String path); + MIDIMap(): + map(NULL), + noteInput(true), + volInput(false), + rawVolume(false), + polyInput(false), + directChannel(false), + programChange(true), + midiClock(false), + midiTimeCode(false), + valueInputStyle(1), + volExp(1.0f) {} }; struct Particle { @@ -572,6 +634,7 @@ class FurnaceGUI { std::mutex midiLock; std::queue midiQueue; + MIDIMap midiMap; ImFont* mainFont; ImFont* iconFont; @@ -965,4 +1028,4 @@ class FurnaceGUI { FurnaceGUI(); }; -#endif \ No newline at end of file +#endif diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index 4aa0f29ea..efb52331d 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -19,6 +19,7 @@ // guiConst: constants used in the GUI like arrays, strings and other stuff #include "guiConst.h" +#include "gui.h" #include "../engine/song.h" const int opOrder[4]={ @@ -123,6 +124,213 @@ const char* resampleStrats[]={ "best possible" }; +const char* guiActions[GUI_ACTION_MAX][2]={ + {"GLOBAL_MIN", "---Global"}, + {"OPEN", "Open file"}, + {"OPEN_BACKUP", "Restore backup"}, + {"SAVE", "Save file"}, + {"SAVE_AS", "Save as"}, + {"UNDO", "Undo"}, + {"REDO", "Redo"}, + {"PLAY_TOGGLE", "Play/Stop (toggle)"}, + {"PLAY", "Play"}, + {"STOP", "Stop"}, + {"PLAY_REPEAT", "Play (repeat pattern)"}, + {"PLAY_CURSOR", "Play from cursor"}, + {"STEP_ONE", "Step row"}, + {"OCTAVE_UP", "Octave up"}, + {"OCTAVE_DOWN", "Octave down"}, + {"INS_UP", "Previous instrument"}, + {"INS_DOWN", "Next instrument"}, + {"STEP_UP", "Increase edit step"}, + {"STEP_DOWN", "Decrease edit step"}, + {"TOGGLE_EDIT", "Toggle edit mode"}, + {"METRONOME", "Metronome"}, + {"REPEAT_PATTERN", "Toggle repeat pattern"}, + {"FOLLOW_ORDERS", "Follow orders"}, + {"FOLLOW_PATTERN", "Follow pattern"}, + {"PANIC", "Panic"}, + + {"WINDOW_EDIT_CONTROLS", "Edit Controls"}, + {"WINDOW_ORDERS", "Orders"}, + {"WINDOW_INS_LIST", "Instrument List"}, + {"WINDOW_INS_EDIT", "Instrument Editor"}, + {"WINDOW_SONG_INFO", "Song Information"}, + {"WINDOW_PATTERN", "Pattern"}, + {"WINDOW_WAVE_LIST", "Wavetable List"}, + {"WINDOW_WAVE_EDIT", "Wavetable Editor"}, + {"WINDOW_SAMPLE_LIST", "Sample List"}, + {"WINDOW_SAMPLE_EDIT", "Sample Editor"}, + {"WINDOW_ABOUT", "About"}, + {"WINDOW_SETTINGS", "Settings"}, + {"WINDOW_MIXER", "Mixer"}, + {"WINDOW_DEBUG", "Debug Menu"}, + {"WINDOW_OSCILLOSCOPE", "Oscilloscope"}, + {"WINDOW_VOL_METER", "Volume Meter"}, + {"WINDOW_STATS", "Statistics"}, + {"WINDOW_COMPAT_FLAGS", "Compatibility Flags"}, + {"WINDOW_PIANO", "Piano"}, + {"WINDOW_NOTES", "Song Comments"}, + {"WINDOW_CHANNELS", "Channels"}, + {"WINDOW_REGISTER_VIEW", "Register View"}, + + {"COLLAPSE_WINDOW", "Collapse/expand current window"}, + {"CLOSE_WINDOW", "Close current window"}, + {"GLOBAL_MAX", ""}, + + {"PAT_MIN", "---Pattern"}, + {"PAT_NOTE_UP", "Transpose (+1)"}, + {"PAT_NOTE_DOWN", "Transpose (-1)"}, + {"PAT_OCTAVE_UP", "Transpose (+1 octave)"}, + {"PAT_OCTAVE_DOWN", "Transpose (-1 octave)"}, + {"PAT_SELECT_ALL", "Select all"}, + {"PAT_CUT", "Cut"}, + {"PAT_COPY", "Copy"}, + {"PAT_PASTE", "Paste"}, + {"PAT_PASTE_MIX", "Paste Mix (foreground)"}, + {"PAT_PASTE_MIX_BG", "Paste Mix (background)"}, + {"PAT_PASTE_FLOOD", "Paste Flood"}, + {"PAT_PASTE_OVERFLOW", "Paste Overflow"}, + {"PAT_CURSOR_UP", "Move cursor up"}, + {"PAT_CURSOR_DOWN", "Move cursor down"}, + {"PAT_CURSOR_LEFT", "Move cursor left"}, + {"PAT_CURSOR_RIGHT", "Move cursor right"}, + {"PAT_CURSOR_UP_ONE", "Move cursor up by one (override Edit Step)"}, + {"PAT_CURSOR_DOWN_ONE", "Move cursor down by one (override Edit Step)"}, + {"PAT_CURSOR_LEFT_CHANNEL", "Move cursor to previous channel"}, + {"PAT_CURSOR_RIGHT_CHANNEL", "Move cursor to next channel"}, + {"PAT_CURSOR_NEXT_CHANNEL", "Move cursor to previous channel (overflow)"}, + {"PAT_CURSOR_PREVIOUS_CHANNEL", "Move cursor to next channel (overflow)"}, + {"PAT_CURSOR_BEGIN", "Move cursor to beginning of pattern"}, + {"PAT_CURSOR_END", "Move cursor to end of pattern"}, + {"PAT_CURSOR_UP_COARSE", "Move cursor up (coarse)"}, + {"PAT_CURSOR_DOWN_COARSE", "Move cursor down (coarse)"}, + {"PAT_SELECTION_UP", "Expand selection upwards"}, + {"PAT_SELECTION_DOWN", "Expand selection downwards"}, + {"PAT_SELECTION_LEFT", "Expand selection to the left"}, + {"PAT_SELECTION_RIGHT", "Expand selection to the right"}, + {"PAT_SELECTION_UP_ONE", "Expand selection upwards by one (override Edit Step)"}, + {"PAT_SELECTION_DOWN_ONE", "Expand selection downwards by one (override Edit Step)"}, + {"PAT_SELECTION_BEGIN", "Expand selection to beginning of pattern"}, + {"PAT_SELECTION_END", "Expand selection to end of pattern"}, + {"PAT_SELECTION_UP_COARSE", "Expand selection upwards (coarse)"}, + {"PAT_SELECTION_DOWN_COARSE", "Expand selection downwards (coarse)"}, + {"PAT_DELETE", "Delete"}, + {"PAT_PULL_DELETE", "Pull delete"}, + {"PAT_INSERT", "Insert"}, + {"PAT_MUTE_CURSOR", "Mute channel at cursor"}, + {"PAT_SOLO_CURSOR", "Solo channel at cursor"}, + {"PAT_UNMUTE_ALL", "Unmute all channels"}, + {"PAT_NEXT_ORDER", "Go to next order"}, + {"PAT_PREV_ORDER", "Go to previous order"}, + {"PAT_COLLAPSE", "Collapse channel at cursor"}, + {"PAT_INCREASE_COLUMNS", "Increase effect columns"}, + {"PAT_DECREASE_COLUMNS", "Decrease effect columns"}, + {"PAT_INTERPOLATE", "Interpolate"}, + {"PAT_FADE", "Fade"}, + {"PAT_INVERT_VALUES", "Invert values"}, + {"PAT_FLIP_SELECTION", "Flip selection"}, + {"PAT_COLLAPSE_ROWS", "Collapse rows"}, + {"PAT_EXPAND_ROWS", "Expand rows"}, + {"PAT_COLLAPSE_PAT", "Collapse pattern"}, + {"PAT_EXPAND_PAT", "Expand pattern"}, + {"PAT_COLLAPSE_SONG", "Collapse song"}, + {"PAT_EXPAND_SONG", "Expand song"}, + {"PAT_LATCH", "Set note input latch"}, + {"PAT_MAX", ""}, + + {"INS_LIST_MIN", "---Instrument list"}, + {"INS_LIST_ADD", "Add"}, + {"INS_LIST_DUPLICATE", "Duplicate"}, + {"INS_LIST_OPEN", "Open"}, + {"INS_LIST_SAVE", "Save"}, + {"INS_LIST_MOVE_UP", "Move up"}, + {"INS_LIST_MOVE_DOWN", "Move down"}, + {"INS_LIST_DELETE", "Delete"}, + {"INS_LIST_EDIT", "Edit"}, + {"INS_LIST_UP", "Cursor up"}, + {"INS_LIST_DOWN", "Cursor down"}, + {"INS_LIST_MAX", ""}, + + {"WAVE_LIST_MIN", "---Wavetable list"}, + {"WAVE_LIST_ADD", "Add"}, + {"WAVE_LIST_DUPLICATE", "Duplicate"}, + {"WAVE_LIST_OPEN", "Open"}, + {"WAVE_LIST_SAVE", "Save"}, + {"WAVE_LIST_MOVE_UP", "Move up"}, + {"WAVE_LIST_MOVE_DOWN", "Move down"}, + {"WAVE_LIST_DELETE", "Delete"}, + {"WAVE_LIST_EDIT", "Edit"}, + {"WAVE_LIST_UP", "Cursor up"}, + {"WAVE_LIST_DOWN", "Cursor down"}, + {"WAVE_LIST_MAX", ""}, + + {"SAMPLE_LIST_MIN", "---Sample list"}, + {"SAMPLE_LIST_ADD", "Add"}, + {"SAMPLE_LIST_DUPLICATE", "Duplicate"}, + {"SAMPLE_LIST_OPEN", "Open"}, + {"SAMPLE_LIST_SAVE", "Save"}, + {"SAMPLE_LIST_MOVE_UP", "Move up"}, + {"SAMPLE_LIST_MOVE_DOWN", "Move down"}, + {"SAMPLE_LIST_DELETE", "Delete"}, + {"SAMPLE_LIST_EDIT", "Edit"}, + {"SAMPLE_LIST_UP", "Cursor up"}, + {"SAMPLE_LIST_DOWN", "Cursor down"}, + {"SAMPLE_LIST_PREVIEW", "Preview"}, + {"SAMPLE_LIST_STOP_PREVIEW", "Stop preview"}, + {"SAMPLE_LIST_MAX", ""}, + + {"SAMPLE_MIN", "---Sample editor"}, + {"SAMPLE_SELECT", "Edit mode: Select"}, + {"SAMPLE_DRAW", "Edit mode: Draw"}, + {"SAMPLE_CUT", "Cut"}, + {"SAMPLE_COPY", "Copy"}, + {"SAMPLE_PASTE", "Paste"}, + {"SAMPLE_PASTE_REPLACE", "Paste replace"}, + {"SAMPLE_PASTE_MIX", "Paste mix"}, + {"SAMPLE_SELECT_ALL", "Select all"}, + {"SAMPLE_RESIZE", "Resize"}, + {"SAMPLE_RESAMPLE", "Resample"}, + {"SAMPLE_AMPLIFY", "Amplify"}, + {"SAMPLE_NORMALIZE", "Normalize"}, + {"SAMPLE_FADE_IN", "Fade in"}, + {"SAMPLE_FADE_OUT", "Fade out"}, + {"SAMPLE_SILENCE", "Apply silence"}, + {"SAMPLE_INSERT", "Insert silence"}, + {"SAMPLE_DELETE", "Delete"}, + {"SAMPLE_TRIM", "Trim"}, + {"SAMPLE_REVERSE", "Reverse"}, + {"SAMPLE_INVERT", "Invert"}, + {"SAMPLE_SIGN", "Signed/unsigned exchange"}, + {"SAMPLE_FILTER", "Apply filter"}, + {"SAMPLE_PREVIEW", "Preview sample"}, + {"SAMPLE_STOP_PREVIEW", "Stop sample preview"}, + {"SAMPLE_ZOOM_IN", "Zoom in"}, + {"SAMPLE_ZOOM_OUT", "Zoom out"}, + {"SAMPLE_ZOOM_AUTO", "Toggle auto-zoom"}, + {"SAMPLE_MAX", ""}, + + {"ORDERS_MIN", "---Orders"}, + {"ORDERS_UP", "Previous order"}, + {"ORDERS_DOWN", "Next order"}, + {"ORDERS_LEFT", "Cursor left"}, + {"ORDERS_RIGHT", "Cursor right"}, + {"ORDERS_INCREASE", "Increase value"}, + {"ORDERS_DECREASE", "Decrease value"}, + {"ORDERS_EDIT_MODE", "Switch edit mode"}, + {"ORDERS_LINK", "Toggle alter entire row"}, + {"ORDERS_ADD", "Add"}, + {"ORDERS_DUPLICATE", "Duplicate"}, + {"ORDERS_DEEP_CLONE", "Deep clone"}, + {"ORDERS_DUPLICATE_END", "Duplicate to end of song"}, + {"ORDERS_DEEP_CLONE_END", "Deep clone to end of song"}, + {"ORDERS_REMOVE", "Remove"}, + {"ORDERS_MOVE_UP", "Move up"}, + {"ORDERS_MOVE_DOWN", "Move down"}, + {"ORDERS_REPLAY", "Replay"}, + {"ORDERS_MAX", ""}, +}; + // define systems. const int availableSystems[]={ DIV_SYSTEM_YM2612, @@ -168,4 +376,5 @@ const int availableSystems[]={ DIV_SYSTEM_VIC20, DIV_SYSTEM_VRC6, 0 // don't remove this last one! -}; \ No newline at end of file +}; + diff --git a/src/gui/guiConst.h b/src/gui/guiConst.h index 25b4bc4c1..6811845e3 100644 --- a/src/gui/guiConst.h +++ b/src/gui/guiConst.h @@ -26,4 +26,5 @@ extern const char* pitchLabel[11]; extern const char* insTypes[]; extern const char* sampleDepths[17]; extern const char* resampleStrats[]; -extern const int availableSystems[]; \ No newline at end of file +extern const int availableSystems[]; +extern const char* guiActions[][2]; diff --git a/src/gui/midiMap.cpp b/src/gui/midiMap.cpp new file mode 100644 index 000000000..63337e45e --- /dev/null +++ b/src/gui/midiMap.cpp @@ -0,0 +1,209 @@ +#include "gui.h" +#include "guiConst.h" +#include "../ta-log.h" + +int MIDIMap::at(TAMidiMessage& where) { + if (map==NULL) return 0; + int type=(where.type>>4)-8; + int chan=where.type&15; + int data1=where.data[0]; + int data2=where.data[1]; + if (type<0) return 0; + if (map[type]==NULL) return 0; + + int** chanMap=map[type][chan]; + if (chanMap==NULL) { + chanMap=map[type][16]; + if (chanMap==NULL) return 0; + } + + int* dataMap=chanMap[data1]; + if (dataMap==NULL) { + dataMap=chanMap[128]; + if (dataMap==NULL) return 0; + } + + int ret=dataMap[data2]; + if (ret==0) { + ret=dataMap[128]; + if (ret==0) { // maybe this is not the correct mapping + dataMap=chanMap[128]; + if (dataMap==NULL) { + chanMap=map[type][16]; + if (chanMap==NULL) return 0; + + dataMap=chanMap[data1]; + if (dataMap==NULL) { + dataMap=chanMap[128]; + if (dataMap==NULL) return 0; + } + + ret=dataMap[data2]; + if (ret==0) { + ret=dataMap[128]; + } + } else { + ret=dataMap[data2]; + if (ret==0) { + ret=dataMap[128]; + } + } + } + } + + return ret; +} + +bool MIDIMap::read(String path) { + char line[4096]; + int curLine=1; + FILE* f=fopen(path.c_str(),"rb"); + if (f==NULL) { + logE("error while loading MIDI mapping! %s\n",strerror(errno)); + return false; + } + + binds.clear(); + while (fgets(line,4096,f)) { + char* nlPos=strrchr(line,'\n'); + if (nlPos!=NULL) *nlPos=0; + if (strstr(line,"option")==line) { + char optionName[256]; + char optionValue[256]; + String optionNameS, optionValueS; + int result=sscanf(line,"option %255s %255s",optionName,optionValue); + if (result!=2) { + logW("MIDI map garbage data at line %d: %s\n",curLine,line); + break; + } + + optionNameS=optionName; + optionValueS=optionValue; + + try { + if (optionNameS=="noteInput") { + noteInput=std::stoi(optionValueS); + } else { + logW("MIDI map unknown option %s at line %d: %s\n",optionName,curLine,line); + } + } catch (std::out_of_range& e) { + logW("MIDI map invalid value %s for option %s at line %d: %s\n",optionValue,optionName,curLine,line); + } catch (std::invalid_argument& e) { + logW("MIDI map invalid value %s for option %s at line %d: %s\n",optionValue,optionName,curLine,line); + } + + curLine++; + continue; + } + + char bindAction[256]; + MIDIBind bind; + int result=sscanf(line,"%d %d %d %d %255s",&bind.type,&bind.channel,&bind.data1,&bind.data2,bindAction); + if (result!=5 || result==EOF) { + logW("MIDI map garbage data at line %d: %s\n",curLine,line); + break; + } + + bool foundAction=false; + for (int i=0; i15) continue; + if (i.channel<0 || i.channel>16) continue; + if (i.data1<0 || i.data1>128) continue; + if (i.data2<0 || i.data2>128) continue; + + if (map[i.type-8]==NULL) { + map[i.type-8]=new int**[17]; + memset(map[i.type-8],0,sizeof(int**)*17); + } + if (map[i.type-8][i.channel]==NULL) { + map[i.type-8][i.channel]=new int*[129]; + memset(map[i.type-8][i.channel],0,sizeof(int*)*129); + } + if (map[i.type-8][i.channel][i.data1]==NULL) { + map[i.type-8][i.channel][i.data1]=new int[129]; + memset(map[i.type-8][i.channel][i.data1],0,sizeof(int)*129); + } + + map[i.type-8][i.channel][i.data1][i.data2]=i.action; + logD("MIDI mapping %d %d %d %d to %d\n",i.type-8,i.channel,i.data1,i.data2,i.action); + } +} diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index c2281d633..03c17670c 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -21,13 +21,11 @@ #include "fonts.h" #include "../ta-log.h" #include "util.h" +#include "guiConst.h" #include "ImGuiFileDialog.h" #include "IconsFontAwesome4.h" #include "misc/cpp/imgui_stdlib.h" -#include -#include #include -#include #ifdef __APPLE__ #define FURKMOD_CMD FURKMOD_META @@ -102,10 +100,10 @@ const char* saaCores[]={ ImGui::EndTable(); \ } -#define UI_KEYBIND_CONFIG(what,label) \ +#define UI_KEYBIND_CONFIG(what) \ ImGui::TableNextRow(); \ ImGui::TableNextColumn(); \ - ImGui::Text(label); \ + ImGui::TextUnformatted(guiActions[what][1]); \ ImGui::TableNextColumn(); \ if (ImGui::Button(fmt::sprintf("%s##KC_" #what,(bindSetPending && bindSetTarget==what)?"Press key...":getKeyName(actionKeys[what])).c_str())) { \ promptKey(what); \ @@ -662,30 +660,30 @@ void FurnaceGUI::drawSettings() { if (ImGui::TreeNode("Global hotkeys")) { KEYBIND_CONFIG_BEGIN("keysGlobal"); - UI_KEYBIND_CONFIG(GUI_ACTION_OPEN,"Open file"); - UI_KEYBIND_CONFIG(GUI_ACTION_OPEN_BACKUP,"Restore backup"); - UI_KEYBIND_CONFIG(GUI_ACTION_SAVE,"Save file"); - UI_KEYBIND_CONFIG(GUI_ACTION_SAVE_AS,"Save as"); - UI_KEYBIND_CONFIG(GUI_ACTION_UNDO,"Undo"); - UI_KEYBIND_CONFIG(GUI_ACTION_REDO,"Redo"); - UI_KEYBIND_CONFIG(GUI_ACTION_PLAY_TOGGLE,"Play/Stop (toggle)"); - UI_KEYBIND_CONFIG(GUI_ACTION_PLAY,"Play"); - UI_KEYBIND_CONFIG(GUI_ACTION_STOP,"Stop"); - UI_KEYBIND_CONFIG(GUI_ACTION_PLAY_REPEAT,"Play (repeat pattern)"); - UI_KEYBIND_CONFIG(GUI_ACTION_PLAY_CURSOR,"Play from cursor"); - UI_KEYBIND_CONFIG(GUI_ACTION_STEP_ONE,"Step row"); - UI_KEYBIND_CONFIG(GUI_ACTION_OCTAVE_UP,"Octave up"); - UI_KEYBIND_CONFIG(GUI_ACTION_OCTAVE_DOWN,"Octave down"); - UI_KEYBIND_CONFIG(GUI_ACTION_INS_UP,"Previous instrument"); - UI_KEYBIND_CONFIG(GUI_ACTION_INS_DOWN,"Next instrument"); - UI_KEYBIND_CONFIG(GUI_ACTION_STEP_UP,"Increase edit step"); - UI_KEYBIND_CONFIG(GUI_ACTION_STEP_DOWN,"Decrease edit step"); - UI_KEYBIND_CONFIG(GUI_ACTION_TOGGLE_EDIT,"Toggle edit mode"); - UI_KEYBIND_CONFIG(GUI_ACTION_METRONOME,"Metronome"); - UI_KEYBIND_CONFIG(GUI_ACTION_REPEAT_PATTERN,"Toggle repeat pattern"); - UI_KEYBIND_CONFIG(GUI_ACTION_FOLLOW_ORDERS,"Follow orders"); - UI_KEYBIND_CONFIG(GUI_ACTION_FOLLOW_PATTERN,"Follow pattern"); - UI_KEYBIND_CONFIG(GUI_ACTION_PANIC,"Panic"); + UI_KEYBIND_CONFIG(GUI_ACTION_OPEN); + UI_KEYBIND_CONFIG(GUI_ACTION_OPEN_BACKUP); + UI_KEYBIND_CONFIG(GUI_ACTION_SAVE); + UI_KEYBIND_CONFIG(GUI_ACTION_SAVE_AS); + UI_KEYBIND_CONFIG(GUI_ACTION_UNDO); + UI_KEYBIND_CONFIG(GUI_ACTION_REDO); + UI_KEYBIND_CONFIG(GUI_ACTION_PLAY_TOGGLE); + UI_KEYBIND_CONFIG(GUI_ACTION_PLAY); + UI_KEYBIND_CONFIG(GUI_ACTION_STOP); + UI_KEYBIND_CONFIG(GUI_ACTION_PLAY_REPEAT); + UI_KEYBIND_CONFIG(GUI_ACTION_PLAY_CURSOR); + UI_KEYBIND_CONFIG(GUI_ACTION_STEP_ONE); + UI_KEYBIND_CONFIG(GUI_ACTION_OCTAVE_UP); + UI_KEYBIND_CONFIG(GUI_ACTION_OCTAVE_DOWN); + UI_KEYBIND_CONFIG(GUI_ACTION_INS_UP); + UI_KEYBIND_CONFIG(GUI_ACTION_INS_DOWN); + UI_KEYBIND_CONFIG(GUI_ACTION_STEP_UP); + UI_KEYBIND_CONFIG(GUI_ACTION_STEP_DOWN); + UI_KEYBIND_CONFIG(GUI_ACTION_TOGGLE_EDIT); + UI_KEYBIND_CONFIG(GUI_ACTION_METRONOME); + UI_KEYBIND_CONFIG(GUI_ACTION_REPEAT_PATTERN); + UI_KEYBIND_CONFIG(GUI_ACTION_FOLLOW_ORDERS); + UI_KEYBIND_CONFIG(GUI_ACTION_FOLLOW_PATTERN); + UI_KEYBIND_CONFIG(GUI_ACTION_PANIC); KEYBIND_CONFIG_END; ImGui::TreePop(); @@ -693,31 +691,31 @@ void FurnaceGUI::drawSettings() { if (ImGui::TreeNode("Window activation")) { KEYBIND_CONFIG_BEGIN("keysWindow"); - UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_EDIT_CONTROLS,"Edit Controls"); - UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_ORDERS,"Orders"); - UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_INS_LIST,"Instrument List"); - UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_INS_EDIT,"Instrument Editor"); - UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_SONG_INFO,"Song Information"); - UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_PATTERN,"Pattern"); - UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_WAVE_LIST,"Wavetable List"); - UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_WAVE_EDIT,"Wavetable Editor"); - UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_SAMPLE_LIST,"Sample List"); - UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_SAMPLE_EDIT,"Sample Editor"); - UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_ABOUT,"About"); - UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_SETTINGS,"Settings"); - UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_MIXER,"Mixer"); - UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_DEBUG,"Debug Menu"); - UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_OSCILLOSCOPE,"Oscilloscope"); - UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_VOL_METER,"Volume Meter"); - UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_STATS,"Statistics"); - UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_COMPAT_FLAGS,"Compatibility Flags"); - UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_PIANO,"Piano"); - UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_NOTES,"Song Comments"); - UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_CHANNELS,"Channels"); - UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_REGISTER_VIEW,"Register View"); + UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_EDIT_CONTROLS); + UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_ORDERS); + UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_INS_LIST); + UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_INS_EDIT); + UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_SONG_INFO); + UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_PATTERN); + UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_WAVE_LIST); + UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_WAVE_EDIT); + UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_SAMPLE_LIST); + UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_SAMPLE_EDIT); + UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_ABOUT); + UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_SETTINGS); + UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_MIXER); + UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_DEBUG); + UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_OSCILLOSCOPE); + UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_VOL_METER); + UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_STATS); + UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_COMPAT_FLAGS); + UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_PIANO); + UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_NOTES); + UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_CHANNELS); + UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_REGISTER_VIEW); - UI_KEYBIND_CONFIG(GUI_ACTION_COLLAPSE_WINDOW,"Collapse/expand current window"); - UI_KEYBIND_CONFIG(GUI_ACTION_CLOSE_WINDOW,"Close current window"); + UI_KEYBIND_CONFIG(GUI_ACTION_COLLAPSE_WINDOW); + UI_KEYBIND_CONFIG(GUI_ACTION_CLOSE_WINDOW); KEYBIND_CONFIG_END; ImGui::TreePop(); @@ -808,49 +806,49 @@ void FurnaceGUI::drawSettings() { if (ImGui::TreeNode("Pattern")) { KEYBIND_CONFIG_BEGIN("keysPattern"); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_NOTE_UP,"Transpose (semitone up)"); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_NOTE_DOWN,"Transpose (semitone down"); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_OCTAVE_UP,"Transpose (octave up)"); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_OCTAVE_DOWN,"Transpose (octave down)"); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_SELECT_ALL,"Select all"); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_CUT,"Cut"); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_COPY,"Copy"); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_PASTE,"Paste"); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_CURSOR_UP,"Move cursor up"); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_CURSOR_DOWN,"Move cursor down"); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_CURSOR_LEFT,"Move cursor left"); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_CURSOR_RIGHT,"Move cursor right"); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_CURSOR_UP_ONE,"Move cursor up by one (override Edit Step)"); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_CURSOR_DOWN_ONE,"Move cursor down by one (override Edit Step)"); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_CURSOR_LEFT_CHANNEL,"Move cursor to previous channel"); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_CURSOR_RIGHT_CHANNEL,"Move cursor to next channel"); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_CURSOR_PREVIOUS_CHANNEL,"Move cursor to previous channel (overflow)"); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_CURSOR_NEXT_CHANNEL,"Move cursor to next channel (overflow)"); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_CURSOR_BEGIN,"Move cursor to beginning of pattern"); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_CURSOR_END,"Move cursor to end of pattern"); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_CURSOR_UP_COARSE,"Move cursor up (coarse)"); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_CURSOR_DOWN_COARSE,"Move cursor down (coarse)"); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_SELECTION_UP,"Expand selection upwards"); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_SELECTION_DOWN,"Expand selection downwards"); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_SELECTION_LEFT,"Expand selection to the left"); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_SELECTION_RIGHT,"Expand selection to the right"); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_SELECTION_UP_ONE,"Expand selection upwards by one (override Edit Step)"); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_SELECTION_DOWN_ONE,"Expand selection downwards by one (override Edit Step)"); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_SELECTION_BEGIN,"Expand selection to beginning of pattern"); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_SELECTION_END,"Expand selection to end of pattern"); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_SELECTION_UP_COARSE,"Expand selection upwards (coarse)"); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_SELECTION_DOWN_COARSE,"Expand selection downwards (coarse)"); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_DELETE,"Delete"); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_PULL_DELETE,"Pull delete"); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_INSERT,"Insert"); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_MUTE_CURSOR,"Mute channel at cursor"); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_SOLO_CURSOR,"Solo channel at cursor"); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_UNMUTE_ALL,"Unmute all channels"); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_NEXT_ORDER,"Go to next order"); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_PREV_ORDER,"Go to previous order"); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_COLLAPSE,"Collapse channel at cursor"); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_INCREASE_COLUMNS,"Increase effect columns"); - UI_KEYBIND_CONFIG(GUI_ACTION_PAT_DECREASE_COLUMNS,"Decrease effect columns"); + UI_KEYBIND_CONFIG(GUI_ACTION_PAT_NOTE_UP); + UI_KEYBIND_CONFIG(GUI_ACTION_PAT_NOTE_DOWN); + UI_KEYBIND_CONFIG(GUI_ACTION_PAT_OCTAVE_UP); + UI_KEYBIND_CONFIG(GUI_ACTION_PAT_OCTAVE_DOWN); + UI_KEYBIND_CONFIG(GUI_ACTION_PAT_SELECT_ALL); + UI_KEYBIND_CONFIG(GUI_ACTION_PAT_CUT); + UI_KEYBIND_CONFIG(GUI_ACTION_PAT_COPY); + UI_KEYBIND_CONFIG(GUI_ACTION_PAT_PASTE); + UI_KEYBIND_CONFIG(GUI_ACTION_PAT_CURSOR_UP); + UI_KEYBIND_CONFIG(GUI_ACTION_PAT_CURSOR_DOWN); + UI_KEYBIND_CONFIG(GUI_ACTION_PAT_CURSOR_LEFT); + UI_KEYBIND_CONFIG(GUI_ACTION_PAT_CURSOR_RIGHT); + UI_KEYBIND_CONFIG(GUI_ACTION_PAT_CURSOR_UP_ONE); + UI_KEYBIND_CONFIG(GUI_ACTION_PAT_CURSOR_DOWN_ONE); + UI_KEYBIND_CONFIG(GUI_ACTION_PAT_CURSOR_LEFT_CHANNEL); + UI_KEYBIND_CONFIG(GUI_ACTION_PAT_CURSOR_RIGHT_CHANNEL); + UI_KEYBIND_CONFIG(GUI_ACTION_PAT_CURSOR_PREVIOUS_CHANNEL); + UI_KEYBIND_CONFIG(GUI_ACTION_PAT_CURSOR_NEXT_CHANNEL); + UI_KEYBIND_CONFIG(GUI_ACTION_PAT_CURSOR_BEGIN); + UI_KEYBIND_CONFIG(GUI_ACTION_PAT_CURSOR_END); + UI_KEYBIND_CONFIG(GUI_ACTION_PAT_CURSOR_UP_COARSE); + UI_KEYBIND_CONFIG(GUI_ACTION_PAT_CURSOR_DOWN_COARSE); + UI_KEYBIND_CONFIG(GUI_ACTION_PAT_SELECTION_UP); + UI_KEYBIND_CONFIG(GUI_ACTION_PAT_SELECTION_DOWN); + UI_KEYBIND_CONFIG(GUI_ACTION_PAT_SELECTION_LEFT); + UI_KEYBIND_CONFIG(GUI_ACTION_PAT_SELECTION_RIGHT); + UI_KEYBIND_CONFIG(GUI_ACTION_PAT_SELECTION_UP_ONE); + UI_KEYBIND_CONFIG(GUI_ACTION_PAT_SELECTION_DOWN_ONE); + UI_KEYBIND_CONFIG(GUI_ACTION_PAT_SELECTION_BEGIN); + UI_KEYBIND_CONFIG(GUI_ACTION_PAT_SELECTION_END); + UI_KEYBIND_CONFIG(GUI_ACTION_PAT_SELECTION_UP_COARSE); + UI_KEYBIND_CONFIG(GUI_ACTION_PAT_SELECTION_DOWN_COARSE); + UI_KEYBIND_CONFIG(GUI_ACTION_PAT_DELETE); + UI_KEYBIND_CONFIG(GUI_ACTION_PAT_PULL_DELETE); + UI_KEYBIND_CONFIG(GUI_ACTION_PAT_INSERT); + UI_KEYBIND_CONFIG(GUI_ACTION_PAT_MUTE_CURSOR); + UI_KEYBIND_CONFIG(GUI_ACTION_PAT_SOLO_CURSOR); + UI_KEYBIND_CONFIG(GUI_ACTION_PAT_UNMUTE_ALL); + UI_KEYBIND_CONFIG(GUI_ACTION_PAT_NEXT_ORDER); + UI_KEYBIND_CONFIG(GUI_ACTION_PAT_PREV_ORDER); + UI_KEYBIND_CONFIG(GUI_ACTION_PAT_COLLAPSE); + UI_KEYBIND_CONFIG(GUI_ACTION_PAT_INCREASE_COLUMNS); + UI_KEYBIND_CONFIG(GUI_ACTION_PAT_DECREASE_COLUMNS); KEYBIND_CONFIG_END; ImGui::TreePop(); @@ -858,16 +856,16 @@ void FurnaceGUI::drawSettings() { if (ImGui::TreeNode("Instrument list")) { KEYBIND_CONFIG_BEGIN("keysInsList"); - UI_KEYBIND_CONFIG(GUI_ACTION_INS_LIST_ADD,"Add"); - UI_KEYBIND_CONFIG(GUI_ACTION_INS_LIST_DUPLICATE,"Duplicate"); - UI_KEYBIND_CONFIG(GUI_ACTION_INS_LIST_OPEN,"Open"); - UI_KEYBIND_CONFIG(GUI_ACTION_INS_LIST_SAVE,"Save"); - UI_KEYBIND_CONFIG(GUI_ACTION_INS_LIST_MOVE_UP,"Move up"); - UI_KEYBIND_CONFIG(GUI_ACTION_INS_LIST_MOVE_DOWN,"Move down"); - UI_KEYBIND_CONFIG(GUI_ACTION_INS_LIST_DELETE,"Delete"); - UI_KEYBIND_CONFIG(GUI_ACTION_INS_LIST_EDIT,"Edit"); - UI_KEYBIND_CONFIG(GUI_ACTION_INS_LIST_UP,"Cursor up"); - UI_KEYBIND_CONFIG(GUI_ACTION_INS_LIST_DOWN,"Cursor down"); + UI_KEYBIND_CONFIG(GUI_ACTION_INS_LIST_ADD); + UI_KEYBIND_CONFIG(GUI_ACTION_INS_LIST_DUPLICATE); + UI_KEYBIND_CONFIG(GUI_ACTION_INS_LIST_OPEN); + UI_KEYBIND_CONFIG(GUI_ACTION_INS_LIST_SAVE); + UI_KEYBIND_CONFIG(GUI_ACTION_INS_LIST_MOVE_UP); + UI_KEYBIND_CONFIG(GUI_ACTION_INS_LIST_MOVE_DOWN); + UI_KEYBIND_CONFIG(GUI_ACTION_INS_LIST_DELETE); + UI_KEYBIND_CONFIG(GUI_ACTION_INS_LIST_EDIT); + UI_KEYBIND_CONFIG(GUI_ACTION_INS_LIST_UP); + UI_KEYBIND_CONFIG(GUI_ACTION_INS_LIST_DOWN); KEYBIND_CONFIG_END; ImGui::TreePop(); @@ -875,16 +873,16 @@ void FurnaceGUI::drawSettings() { if (ImGui::TreeNode("Wavetable list")) { KEYBIND_CONFIG_BEGIN("keysWaveList"); - UI_KEYBIND_CONFIG(GUI_ACTION_WAVE_LIST_ADD,"Add"); - UI_KEYBIND_CONFIG(GUI_ACTION_WAVE_LIST_DUPLICATE,"Duplicate"); - UI_KEYBIND_CONFIG(GUI_ACTION_WAVE_LIST_OPEN,"Open"); - UI_KEYBIND_CONFIG(GUI_ACTION_WAVE_LIST_SAVE,"Save"); - UI_KEYBIND_CONFIG(GUI_ACTION_WAVE_LIST_MOVE_UP,"Move up"); - UI_KEYBIND_CONFIG(GUI_ACTION_WAVE_LIST_MOVE_DOWN,"Move down"); - UI_KEYBIND_CONFIG(GUI_ACTION_WAVE_LIST_DELETE,"Delete"); - UI_KEYBIND_CONFIG(GUI_ACTION_WAVE_LIST_EDIT,"Edit"); - UI_KEYBIND_CONFIG(GUI_ACTION_WAVE_LIST_UP,"Cursor up"); - UI_KEYBIND_CONFIG(GUI_ACTION_WAVE_LIST_DOWN,"Cursor down"); + UI_KEYBIND_CONFIG(GUI_ACTION_WAVE_LIST_ADD); + UI_KEYBIND_CONFIG(GUI_ACTION_WAVE_LIST_DUPLICATE); + UI_KEYBIND_CONFIG(GUI_ACTION_WAVE_LIST_OPEN); + UI_KEYBIND_CONFIG(GUI_ACTION_WAVE_LIST_SAVE); + UI_KEYBIND_CONFIG(GUI_ACTION_WAVE_LIST_MOVE_UP); + UI_KEYBIND_CONFIG(GUI_ACTION_WAVE_LIST_MOVE_DOWN); + UI_KEYBIND_CONFIG(GUI_ACTION_WAVE_LIST_DELETE); + UI_KEYBIND_CONFIG(GUI_ACTION_WAVE_LIST_EDIT); + UI_KEYBIND_CONFIG(GUI_ACTION_WAVE_LIST_UP); + UI_KEYBIND_CONFIG(GUI_ACTION_WAVE_LIST_DOWN); KEYBIND_CONFIG_END; ImGui::TreePop(); @@ -892,18 +890,18 @@ void FurnaceGUI::drawSettings() { if (ImGui::TreeNode("Sample list")) { KEYBIND_CONFIG_BEGIN("keysSampleList"); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_LIST_ADD,"Add"); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_LIST_DUPLICATE,"Duplicate"); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_LIST_OPEN,"Open"); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_LIST_SAVE,"Save"); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_LIST_MOVE_UP,"Move up"); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_LIST_MOVE_DOWN,"Move down"); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_LIST_DELETE,"Delete"); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_LIST_EDIT,"Edit"); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_LIST_UP,"Cursor up"); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_LIST_DOWN,"Cursor down"); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_LIST_PREVIEW,"Preview"); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_LIST_STOP_PREVIEW,"Stop preview"); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_LIST_ADD); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_LIST_DUPLICATE); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_LIST_OPEN); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_LIST_SAVE); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_LIST_MOVE_UP); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_LIST_MOVE_DOWN); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_LIST_DELETE); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_LIST_EDIT); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_LIST_UP); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_LIST_DOWN); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_LIST_PREVIEW); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_LIST_STOP_PREVIEW); KEYBIND_CONFIG_END; ImGui::TreePop(); @@ -911,23 +909,23 @@ void FurnaceGUI::drawSettings() { if (ImGui::TreeNode("Orders")) { KEYBIND_CONFIG_BEGIN("keysOrders"); - UI_KEYBIND_CONFIG(GUI_ACTION_ORDERS_UP,"Previous order"); - UI_KEYBIND_CONFIG(GUI_ACTION_ORDERS_DOWN,"Next order"); - UI_KEYBIND_CONFIG(GUI_ACTION_ORDERS_LEFT,"Cursor left"); - UI_KEYBIND_CONFIG(GUI_ACTION_ORDERS_RIGHT,"Cursor right"); - UI_KEYBIND_CONFIG(GUI_ACTION_ORDERS_INCREASE,"Increase value"); - UI_KEYBIND_CONFIG(GUI_ACTION_ORDERS_DECREASE,"Decrease value"); - UI_KEYBIND_CONFIG(GUI_ACTION_ORDERS_EDIT_MODE,"Switch edit mode"); - UI_KEYBIND_CONFIG(GUI_ACTION_ORDERS_LINK,"Toggle alter entire row"); - UI_KEYBIND_CONFIG(GUI_ACTION_ORDERS_ADD,"Add"); - UI_KEYBIND_CONFIG(GUI_ACTION_ORDERS_DUPLICATE,"Duplicate"); - UI_KEYBIND_CONFIG(GUI_ACTION_ORDERS_DEEP_CLONE,"Deep clone"); - UI_KEYBIND_CONFIG(GUI_ACTION_ORDERS_DUPLICATE_END,"Duplicate to end of song"); - UI_KEYBIND_CONFIG(GUI_ACTION_ORDERS_DEEP_CLONE_END,"Deep clone to end of song"); - UI_KEYBIND_CONFIG(GUI_ACTION_ORDERS_REMOVE,"Remove"); - UI_KEYBIND_CONFIG(GUI_ACTION_ORDERS_MOVE_UP,"Move up"); - UI_KEYBIND_CONFIG(GUI_ACTION_ORDERS_MOVE_DOWN,"Move down"); - UI_KEYBIND_CONFIG(GUI_ACTION_ORDERS_REPLAY,"Replay"); + UI_KEYBIND_CONFIG(GUI_ACTION_ORDERS_UP); + UI_KEYBIND_CONFIG(GUI_ACTION_ORDERS_DOWN); + UI_KEYBIND_CONFIG(GUI_ACTION_ORDERS_LEFT); + UI_KEYBIND_CONFIG(GUI_ACTION_ORDERS_RIGHT); + UI_KEYBIND_CONFIG(GUI_ACTION_ORDERS_INCREASE); + UI_KEYBIND_CONFIG(GUI_ACTION_ORDERS_DECREASE); + UI_KEYBIND_CONFIG(GUI_ACTION_ORDERS_EDIT_MODE); + UI_KEYBIND_CONFIG(GUI_ACTION_ORDERS_LINK); + UI_KEYBIND_CONFIG(GUI_ACTION_ORDERS_ADD); + UI_KEYBIND_CONFIG(GUI_ACTION_ORDERS_DUPLICATE); + UI_KEYBIND_CONFIG(GUI_ACTION_ORDERS_DEEP_CLONE); + UI_KEYBIND_CONFIG(GUI_ACTION_ORDERS_DUPLICATE_END); + UI_KEYBIND_CONFIG(GUI_ACTION_ORDERS_DEEP_CLONE_END); + UI_KEYBIND_CONFIG(GUI_ACTION_ORDERS_REMOVE); + UI_KEYBIND_CONFIG(GUI_ACTION_ORDERS_MOVE_UP); + UI_KEYBIND_CONFIG(GUI_ACTION_ORDERS_MOVE_DOWN); + UI_KEYBIND_CONFIG(GUI_ACTION_ORDERS_REPLAY); KEYBIND_CONFIG_END; ImGui::TreePop(); @@ -935,33 +933,33 @@ void FurnaceGUI::drawSettings() { if (ImGui::TreeNode("Sample editor")) { KEYBIND_CONFIG_BEGIN("keysSampleEdit"); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_SELECT,"Edit mode: Select"); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_DRAW,"Edit mode: Draw"); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_CUT,"Cut"); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_COPY,"Copy"); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_PASTE,"Paste"); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_PASTE_REPLACE,"Paste replace"); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_PASTE_MIX,"Paste mix"); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_SELECT_ALL,"Select all"); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_RESIZE,"Resize"); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_RESAMPLE,"Resample"); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_AMPLIFY,"Amplify"); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_NORMALIZE,"Normalize"); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_FADE_IN,"Fade in"); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_FADE_OUT,"Fade out"); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_INSERT,"Insert silence"); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_SILENCE,"Apply silence"); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_DELETE,"Delete"); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_TRIM,"Trim"); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_REVERSE,"Reverse"); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_INVERT,"Invert"); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_SIGN,"Signed/unsigned exchange"); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_FILTER,"Apply filter"); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_PREVIEW,"Preview sample"); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_STOP_PREVIEW,"Stop sample preview"); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_ZOOM_IN,"Zoom in"); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_ZOOM_OUT,"Zoom out"); - UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_ZOOM_AUTO,"Toggle auto-zoom"); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_SELECT); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_DRAW); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_CUT); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_COPY); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_PASTE); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_PASTE_REPLACE); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_PASTE_MIX); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_SELECT_ALL); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_RESIZE); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_RESAMPLE); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_AMPLIFY); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_NORMALIZE); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_FADE_IN); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_FADE_OUT); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_INSERT); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_SILENCE); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_DELETE); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_TRIM); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_REVERSE); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_INVERT); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_SIGN); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_FILTER); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_PREVIEW); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_STOP_PREVIEW); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_ZOOM_IN); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_ZOOM_OUT); + UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_ZOOM_AUTO); KEYBIND_CONFIG_END; ImGui::TreePop(); @@ -2071,4 +2069,4 @@ void FurnaceGUI::applyUISettings() { if (fileDialog!=NULL) delete fileDialog; fileDialog=new FurnaceGUIFileDialog(settings.sysFileDialog); -} \ No newline at end of file +} From 2921f88895a5d09a7bc6491482a6b50df5bd7c7f Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 29 Mar 2022 16:18:38 -0500 Subject: [PATCH 458/637] try using 0 --- src/gui/fileDialog.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/fileDialog.cpp b/src/gui/fileDialog.cpp index 79a87f3ed..95a6ac7b7 100644 --- a/src/gui/fileDialog.cpp +++ b/src/gui/fileDialog.cpp @@ -63,7 +63,7 @@ bool FurnaceGUIFileDialog::render(const ImVec2& min, const ImVec2& max) { if (sysDialog) { if (saving) { if (dialogS!=NULL) { - if (dialogS->ready(1)) { + if (dialogS->ready(0)) { fileName=dialogS->result(); logD("returning %s\n",fileName.c_str()); return true; @@ -71,7 +71,7 @@ bool FurnaceGUIFileDialog::render(const ImVec2& min, const ImVec2& max) { } } else { if (dialogO!=NULL) { - if (dialogO->ready(1)) { + if (dialogO->ready(0)) { if (dialogO->result().empty()) { fileName=""; logD("returning nothing\n"); From c7a37959f0bb17ae769dcd9aa18bd3ac9ebf5b72 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 29 Mar 2022 17:38:30 -0500 Subject: [PATCH 459/637] GUI: early MIDI action UI - COMPLETELY UNTESTED it will crash (or not) --- src/gui/gui.cpp | 4 +- src/gui/gui.h | 10 +- src/gui/midiMap.cpp | 31 +++++-- src/gui/settings.cpp | 213 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 246 insertions(+), 12 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 89bd1a019..256cefce7 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1885,7 +1885,8 @@ bool FurnaceGUI::loop() { // parse message here logD("message is %.2x\n",msg.type); - if (msg.type==0xb0) doAction(GUI_ACTION_PLAY_TOGGLE); + int action=midiMap.at(msg); + if (action!=0) doAction(action); midiLock.lock(); midiQueue.pop(); @@ -2651,6 +2652,7 @@ bool FurnaceGUI::init() { midiLock.lock(); midiQueue.push(msg); midiLock.unlock(); + if (midiMap.at(msg)) return -2; return curIns; }); diff --git a/src/gui/gui.h b/src/gui/gui.h index 22b185a41..05fe6208c 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -497,10 +497,10 @@ struct MIDIBind { int type, channel, data1, data2; int action; MIDIBind(): - type(-1), - channel(-1), - data1(-1), - data2(-1), + type(0), + channel(16), + data1(128), + data2(128), action(0) {} }; @@ -545,7 +545,7 @@ struct MIDIMap { void compile(); void deinit(); - int at(TAMidiMessage& where); + int at(const TAMidiMessage& where); bool read(String path); bool write(String path); MIDIMap(): diff --git a/src/gui/midiMap.cpp b/src/gui/midiMap.cpp index 63337e45e..8d31892be 100644 --- a/src/gui/midiMap.cpp +++ b/src/gui/midiMap.cpp @@ -2,7 +2,7 @@ #include "guiConst.h" #include "../ta-log.h" -int MIDIMap::at(TAMidiMessage& where) { +int MIDIMap::at(const TAMidiMessage& where) { if (map==NULL) return 0; int type=(where.type>>4)-8; int chan=where.type&15; @@ -54,6 +54,14 @@ int MIDIMap::at(TAMidiMessage& where) { return ret; } +#define UNDERSTAND_OPTION(x) if (optionNameS==#x) { \ + x=std::stoi(optionValueS); \ +} + +#define UNDERSTAND_FLOAT_OPTION(x) if (optionNameS==#x) { \ + x=std::stof(optionValueS); \ +} + bool MIDIMap::read(String path) { char line[4096]; int curLine=1; @@ -81,9 +89,18 @@ bool MIDIMap::read(String path) { optionValueS=optionValue; try { - if (optionNameS=="noteInput") { - noteInput=std::stoi(optionValueS); - } else { + UNDERSTAND_OPTION(noteInput) else + UNDERSTAND_OPTION(volInput) else + UNDERSTAND_OPTION(rawVolume) else + UNDERSTAND_OPTION(polyInput) else + UNDERSTAND_OPTION(directChannel) else + UNDERSTAND_OPTION(programChange) else + UNDERSTAND_OPTION(midiClock) else + UNDERSTAND_OPTION(midiTimeCode) else + UNDERSTAND_OPTION(valueInputStyle) else + UNDERSTAND_OPTION(valueInputControlMSB) else + UNDERSTAND_OPTION(valueInputControlLSB) else + UNDERSTAND_FLOAT_OPTION(volExp) else { logW("MIDI map unknown option %s at line %d: %s\n",optionName,curLine,line); } } catch (std::out_of_range& e) { @@ -116,6 +133,8 @@ bool MIDIMap::read(String path) { logW("MIDI map unknown action %s at line %d: %s\n",bindAction,curLine,line); break; } + + binds.push_back(bind); curLine++; } @@ -123,8 +142,8 @@ bool MIDIMap::read(String path) { return true; } -#define WRITE_OPTION(x) fprintf(f,"option " #x "%d\n",x); -#define WRITE_FLOAT_OPTION(x) fprintf(f,"option " #x "%f\n",x); +#define WRITE_OPTION(x) fprintf(f,"option " #x " %d\n",x); +#define WRITE_FLOAT_OPTION(x) fprintf(f,"option " #x " %f\n",x); bool MIDIMap::write(String path) { FILE* f=fopen(path.c_str(),"wb"); diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 03c17670c..be9aaf034 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -26,6 +26,7 @@ #include "IconsFontAwesome4.h" #include "misc/cpp/imgui_stdlib.h" #include +#include #ifdef __APPLE__ #define FURKMOD_CMD FURKMOD_META @@ -80,6 +81,38 @@ const char* saaCores[]={ "SAASound" }; +const char* valueInputStyles[]={ + "Disabled/custom", + "Two octaves (0 is C-4, F is D#5)", + "Raw (note number is value)", + "Two octaves alternate (lower keys are 0-9, upper keys are A-F)", + "Use dual control change (one for each nibble)", + "Use 14-bit control change" +}; + +const char* messageTypes[]={ + "--select--", + "???", + "???", + "???", + "???", + "???", + "???", + "???", + "Note Off", + "Note On", + "Aftertouch", + "Control", + "Program", + "ChanPressure", + "Pitch Bend", + "SysEx" +}; + +const char* messageChannels[]={ + "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "Any" +}; + #define SAMPLE_RATE_SELECTABLE(x) \ if (ImGui::Selectable(#x,settings.audioRate==x)) { \ settings.audioRate=x; \ @@ -110,6 +143,18 @@ const char* saaCores[]={ } \ if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) actionKeys[what]=0; +String stripName(String what) { + String ret; + for (char& i: what) { + if ((i>='A' && i<='Z') || (i>='a' && i<='z') || (i>='0' && i<='9')) { + ret+=i; + } else { + ret+='-'; + } + } + return ret; +} + void FurnaceGUI::promptKey(int which) { bindSetTarget=which; bindSetActive=true; @@ -316,6 +361,168 @@ void FurnaceGUI::drawSettings() { ImGui::EndCombo(); } + if (ImGui::TreeNode("MIDI input settings")) { + ImGui::Checkbox("Note input",&midiMap.noteInput); + ImGui::Checkbox("Velocity input",&midiMap.volInput); + ImGui::Checkbox("Use raw velocity value (don't map from linear to log)",&midiMap.rawVolume); + ImGui::Checkbox("Polyphonic/chord input",&midiMap.polyInput); + ImGui::Checkbox("Map MIDI channels to direct channels",&midiMap.directChannel); + ImGui::Checkbox("Program change is instrument selection",&midiMap.programChange); + ImGui::Checkbox("Listen to MIDI clock",&midiMap.midiClock); + ImGui::Checkbox("Listen to MIDI time code",&midiMap.midiTimeCode); + ImGui::Combo("Value input style",&midiMap.valueInputStyle,valueInputStyles,6); + if (midiMap.valueInputStyle>3) { + if (ImGui::InputInt((midiMap.valueInputStyle==4)?"CC of upper nibble##valueCC1":"MSB CC##valueCC1",&midiMap.valueInputControlMSB,1,16)) { + if (midiMap.valueInputControlMSB<0) midiMap.valueInputControlMSB=0; + if (midiMap.valueInputControlMSB>127) midiMap.valueInputControlMSB=127; + } + if (ImGui::InputInt((midiMap.valueInputStyle==4)?"CC of lower nibble##valueCC2":"LSB CC##valueCC2",&midiMap.valueInputControlLSB,1,16)) { + if (midiMap.valueInputControlLSB<0) midiMap.valueInputControlLSB=0; + if (midiMap.valueInputControlLSB>127) midiMap.valueInputControlLSB=127; + } + } + if (ImGui::SliderFloat("Volume curve",&midiMap.volExp,0.01,8.0,"%.2f")) { + if (midiMap.volExp<0.01) midiMap.volExp=0.01; + if (midiMap.volExp>8.0) midiMap.volExp=8.0; + } rightClickable + float curve[128]; + for (int i=0; i<128; i++) { + curve[i]=(int)(pow((double)i/127.0,midiMap.volExp)*127.0); + } + ImGui::PlotLines("##VolCurveDisplay",curve,128,0,"Volume curve",0.0,127.0,ImVec2(200.0f*dpiScale,200.0f*dpiScale)); + + ImGui::Text("Actions:"); + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_PLUS "##AddAction")) { + midiMap.binds.push_back(MIDIBind()); + } + + if (ImGui::BeginTable("MIDIActions",7)) { + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + ImGui::TableNextColumn(); + ImGui::Text("Type"); + ImGui::TableNextColumn(); + ImGui::Text("Channel"); + ImGui::TableNextColumn(); + ImGui::Text("Note/Control"); + ImGui::TableNextColumn(); + ImGui::Text("Velocity/Value"); + ImGui::TableNextColumn(); + ImGui::Text("Action"); + ImGui::TableNextColumn(); + ImGui::Text("Detect"); + ImGui::TableNextColumn(); + ImGui::Text("Remove"); + + for (size_t i=0; igetConfString("noteKeys",DEFAULT_NOTE_KEYS)); parseKeybinds(); + + midiMap.read(e->getConfigPath()+DIR_SEPARATOR_STR+"midiIn_"+stripName(settings.midiInDevice)+".cfg"); + midiMap.compile(); } #define PUT_UI_COLOR(source) e->setConf(#source,(int)ImGui::GetColorU32(uiColors[source])); @@ -1598,6 +1808,9 @@ void FurnaceGUI::commitSettings() { e->setConf("noteKeys",encodeKeyMap(noteKeys)); + midiMap.compile(); + midiMap.write(e->getConfigPath()+DIR_SEPARATOR_STR+"midiIn_"+stripName(settings.midiInDevice)+".cfg"); + e->saveConf(); if (!e->switchMaster()) { From 6eba483cbae11b27a680ea7665bf11d25be275d4 Mon Sep 17 00:00:00 2001 From: cam900 Date: Wed, 30 Mar 2022 08:28:38 +0900 Subject: [PATCH 460/637] Minor N163 macro fix --- src/engine/platform/n163.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/engine/platform/n163.cpp b/src/engine/platform/n163.cpp index 97abe1024..f48583a42 100644 --- a/src/engine/platform/n163.cpp +++ b/src/engine/platform/n163.cpp @@ -403,9 +403,11 @@ int DivPlatformN163::dispatch(DivCommand c) { if (!chan[c.chan].std.hasVol) { chan[c.chan].outVol=c.value; chan[c.chan].resVol=chan[c.chan].outVol; - if (!isMuted[c.chan]) { - chan[c.chan].volumeChanged=true; - } + } else { + chan[c.chan].resVol=chan[c.chan].vol; + } + if (!isMuted[c.chan]) { + chan[c.chan].volumeChanged=true; } } break; From a43034f559dad7bb516b0b2f7a50574821b2c048 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 29 Mar 2022 23:58:50 -0500 Subject: [PATCH 461/637] OPN: fix portaPause not resetting on new note --- src/engine/platform/genesis.cpp | 1 + src/engine/platform/genesisext.cpp | 1 + src/engine/platform/ym2610.cpp | 1 + src/engine/platform/ym2610b.cpp | 1 + src/engine/platform/ym2610bext.cpp | 1 + src/engine/platform/ym2610ext.cpp | 1 + 6 files changed, 6 insertions(+) diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index 10a8762c0..0aff2b43a 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -542,6 +542,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) { if (c.value!=DIV_NOTE_NULL) { chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); + chan[c.chan].portaPause=false; chan[c.chan].note=c.value; chan[c.chan].freqChanged=true; } diff --git a/src/engine/platform/genesisext.cpp b/src/engine/platform/genesisext.cpp index 02305a9aa..b53cd3894 100644 --- a/src/engine/platform/genesisext.cpp +++ b/src/engine/platform/genesisext.cpp @@ -73,6 +73,7 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) { if (c.value!=DIV_NOTE_NULL) { opChan[ch].baseFreq=NOTE_FREQUENCY(c.value); + opChan[ch].portaPause=false; opChan[ch].freqChanged=true; } opChan[ch].keyOn=true; diff --git a/src/engine/platform/ym2610.cpp b/src/engine/platform/ym2610.cpp index 8d7626632..eb5fa86a7 100644 --- a/src/engine/platform/ym2610.cpp +++ b/src/engine/platform/ym2610.cpp @@ -829,6 +829,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) { if (c.value!=DIV_NOTE_NULL) { chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); + chan[c.chan].portaPause=false; chan[c.chan].freqChanged=true; chan[c.chan].note=c.value; } diff --git a/src/engine/platform/ym2610b.cpp b/src/engine/platform/ym2610b.cpp index 1d954ebe6..745efa224 100644 --- a/src/engine/platform/ym2610b.cpp +++ b/src/engine/platform/ym2610b.cpp @@ -892,6 +892,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { if (c.value!=DIV_NOTE_NULL) { chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); + chan[c.chan].portaPause=false; chan[c.chan].freqChanged=true; chan[c.chan].note=c.value; } diff --git a/src/engine/platform/ym2610bext.cpp b/src/engine/platform/ym2610bext.cpp index 5b3c3872e..e85215730 100644 --- a/src/engine/platform/ym2610bext.cpp +++ b/src/engine/platform/ym2610bext.cpp @@ -63,6 +63,7 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) { if (c.value!=DIV_NOTE_NULL) { opChan[ch].baseFreq=NOTE_FREQUENCY(c.value); + opChan[ch].portaPause=false; opChan[ch].freqChanged=true; } opChan[ch].keyOn=true; diff --git a/src/engine/platform/ym2610ext.cpp b/src/engine/platform/ym2610ext.cpp index 5e633eb2c..63d584d04 100644 --- a/src/engine/platform/ym2610ext.cpp +++ b/src/engine/platform/ym2610ext.cpp @@ -63,6 +63,7 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) { if (c.value!=DIV_NOTE_NULL) { opChan[ch].baseFreq=NOTE_FREQUENCY(c.value); + opChan[ch].portaPause=false; opChan[ch].freqChanged=true; } opChan[ch].keyOn=true; From 7e34c7746eed5df8147683f058bd998834b91f03 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 30 Mar 2022 00:08:04 -0500 Subject: [PATCH 462/637] change the porta logic a bit --- src/engine/playback.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index ce2e485b6..0e1922b34 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -815,13 +815,12 @@ void DivEngine::processRow(int i, bool afterDelay) { if (chan[i].delayLocked) return; // instrument + bool insChanged=false; if (pat->data[whatRow][2]!=-1) { dispatchCmd(DivCommand(DIV_CMD_INSTRUMENT,i,pat->data[whatRow][2])); if (chan[i].lastIns!=pat->data[whatRow][2]) { chan[i].lastIns=pat->data[whatRow][2]; - if (chan[i].inPorta && song.newInsTriggersInPorta) { - dispatchCmd(DivCommand(DIV_CMD_NOTE_ON,i,DIV_NOTE_NULL)); - } + insChanged=true; } } // note @@ -894,6 +893,7 @@ void DivEngine::processRow(int i, bool afterDelay) { chan[i].retrigSpeed=0; short lastSlide=-1; + bool calledPorta=false; // effects for (int j=0; j Date: Wed, 30 Mar 2022 00:09:53 -0500 Subject: [PATCH 463/637] OPN: fix LFO effect not working in ext ch mode --- src/engine/platform/genesisext.cpp | 5 +++++ src/engine/platform/ym2610bext.cpp | 4 ++++ src/engine/platform/ym2610ext.cpp | 4 ++++ 3 files changed, 13 insertions(+) diff --git a/src/engine/platform/genesisext.cpp b/src/engine/platform/genesisext.cpp index b53cd3894..2fb1ab390 100644 --- a/src/engine/platform/genesisext.cpp +++ b/src/engine/platform/genesisext.cpp @@ -159,6 +159,11 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) { opChan[ch].freqChanged=true; break; } + case DIV_CMD_FM_LFO: { + lfoValue=(c.value&7)|((c.value>>4)<<3); + rWrite(0x22,lfoValue); + break; + } case DIV_CMD_FM_MULT: { // TODO unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]]; DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]]; diff --git a/src/engine/platform/ym2610bext.cpp b/src/engine/platform/ym2610bext.cpp index e85215730..597710ced 100644 --- a/src/engine/platform/ym2610bext.cpp +++ b/src/engine/platform/ym2610bext.cpp @@ -147,6 +147,10 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) { opChan[ch].freqChanged=true; break; } + case DIV_CMD_FM_LFO: { + rWrite(0x22,(c.value&7)|((c.value>>4)<<3)); + break; + } case DIV_CMD_FM_MULT: { // TODO unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]]; DivInstrument* ins=parent->getIns(opChan[ch].ins); diff --git a/src/engine/platform/ym2610ext.cpp b/src/engine/platform/ym2610ext.cpp index 63d584d04..8ef9c16ea 100644 --- a/src/engine/platform/ym2610ext.cpp +++ b/src/engine/platform/ym2610ext.cpp @@ -147,6 +147,10 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) { opChan[ch].freqChanged=true; break; } + case DIV_CMD_FM_LFO: { + rWrite(0x22,(c.value&7)|((c.value>>4)<<3)); + break; + } case DIV_CMD_FM_MULT: { // TODO unsigned short baseAddr=chanOffs[1]|opOffs[orderedOps[c.value]]; DivInstrument* ins=parent->getIns(opChan[ch].ins); From 80d67f9b23604280a06c05d795e3226d096c2e53 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 30 Mar 2022 00:15:42 -0500 Subject: [PATCH 464/637] BubSys: fix muting --- src/engine/platform/bubsyswsg.cpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/engine/platform/bubsyswsg.cpp b/src/engine/platform/bubsyswsg.cpp index 450f45305..406ac69fb 100644 --- a/src/engine/platform/bubsyswsg.cpp +++ b/src/engine/platform/bubsyswsg.cpp @@ -56,6 +56,7 @@ void DivPlatformBubSysWSG::acquire(short* bufL, short* bufR, size_t start, size_ // Wavetable part for (int i=0; i<2; i++) { + if (isMuted[i]) continue; out+=chan[i].waveROM[k005289->addr(i)]*(regPool[2+i]&0xf); } @@ -80,7 +81,7 @@ void DivPlatformBubSysWSG::updateWave(int ch) { } } if (chan[ch].active) { - rWrite(2+ch,(chan[ch].wave<<5)|(isMuted[ch]?0:chan[ch].outVol)); + rWrite(2+ch,(chan[ch].wave<<5)|chan[ch].outVol); } } @@ -89,9 +90,7 @@ void DivPlatformBubSysWSG::tick() { chan[i].std.next(); if (chan[i].std.hadVol) { chan[i].outVol=((chan[i].vol&15)*MIN(15,chan[i].std.vol))/15; - if (!isMuted[i]) { - rWrite(2+i,(chan[i].wave<<5)|chan[i].outVol); - } + rWrite(2+i,(chan[i].wave<<5)|chan[i].outVol); } if (chan[i].std.hadArp) { if (!chan[i].inPorta) { @@ -129,7 +128,7 @@ void DivPlatformBubSysWSG::tick() { updateWave(i); } } - if (chan[i].keyOff && (!isMuted[i])) { + if (chan[i].keyOff) { rWrite(2+i,(chan[i].wave<<5)|0); } if (chan[i].keyOn) chan[i].keyOn=false; @@ -150,7 +149,7 @@ int DivPlatformBubSysWSG::dispatch(DivCommand c) { } chan[c.chan].active=true; chan[c.chan].keyOn=true; - if (!isMuted[c.chan]) rWrite(2+c.chan,(chan[c.chan].wave<<5)|chan[c.chan].vol); + rWrite(2+c.chan,(chan[c.chan].wave<<5)|chan[c.chan].vol); chan[c.chan].std.init(ins); break; } @@ -173,7 +172,7 @@ int DivPlatformBubSysWSG::dispatch(DivCommand c) { chan[c.chan].vol=c.value; if (!chan[c.chan].std.hasVol) { chan[c.chan].outVol=c.value; - if (chan[c.chan].active && !isMuted[c.chan]) rWrite(2+c.chan,(chan[c.chan].wave<<5)|chan[c.chan].outVol); + if (chan[c.chan].active) rWrite(2+c.chan,(chan[c.chan].wave<<5)|chan[c.chan].outVol); } } break; @@ -240,7 +239,7 @@ int DivPlatformBubSysWSG::dispatch(DivCommand c) { void DivPlatformBubSysWSG::muteChannel(int ch, bool mute) { isMuted[ch]=mute; - rWrite(2+ch,(chan[ch].wave<<5)|((chan[ch].active && isMuted[ch])?0:chan[ch].outVol)); + //rWrite(2+ch,(chan[ch].wave<<5)|((chan[ch].active && isMuted[ch])?0:chan[ch].outVol)); } void DivPlatformBubSysWSG::forceIns() { From 941aab0def0f8201e2ab276a627dfa923608d34b Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 30 Mar 2022 00:28:49 -0500 Subject: [PATCH 465/637] GUI: add ability to input waveform data in hex closes #327 --- src/gui/gui.cpp | 36 ++++++++++++++++++++++++++++++------ src/gui/gui.h | 6 +++--- src/gui/waveEdit.cpp | 13 +++++++++++-- 3 files changed, 44 insertions(+), 11 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 256cefce7..77aa687ea 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -202,22 +202,30 @@ void FurnaceGUI::encodeMMLStr(String& target, unsigned char* macro, unsigned cha } } -void FurnaceGUI::encodeMMLStr(String& target, int* macro, int macroLen, int macroLoop, int macroRel) { +void FurnaceGUI::encodeMMLStr(String& target, int* macro, int macroLen, int macroLoop, int macroRel, bool hex) { target=""; char buf[32]; for (int i=0; igetConfBool("regViewOpen",false); tempoView=e->getConfBool("tempoView",true); + waveHex=e->getConfBool("waveHex",false); syncSettings(); @@ -2703,6 +2726,7 @@ bool FurnaceGUI::finish() { e->setConf("lastWindowHeight",scrH); e->setConf("tempoView",tempoView); + e->setConf("waveHex",waveHex); for (int i=0; i& map); void decodeKeyMap(std::map& map, String source); diff --git a/src/gui/waveEdit.cpp b/src/gui/waveEdit.cpp index 60938d0a0..e1359da93 100644 --- a/src/gui/waveEdit.cpp +++ b/src/gui/waveEdit.cpp @@ -20,6 +20,7 @@ #include "gui.h" #include "plot_nolerp.h" #include "misc/cpp/imgui_stdlib.h" +#include void FurnaceGUI::drawWaveEdit() { if (nextWindow==GUI_WINDOW_WAVE_EDIT) { @@ -61,6 +62,14 @@ void FurnaceGUI::drawWaveEdit() { e->notifyWaveChange(curWave); MARK_MODIFIED; } + ImGui::SameLine(); + if (ImGui::RadioButton("Dec",!waveHex)) { + waveHex=false; + } + ImGui::SameLine(); + if (ImGui::RadioButton("Hex",waveHex)) { + waveHex=true; + } for (int i=0; ilen; i++) { if (wave->data[i]>wave->max) wave->data[i]=wave->max; wavePreview[i]=wave->data[i]; @@ -68,10 +77,10 @@ void FurnaceGUI::drawWaveEdit() { if (wave->len>0) wavePreview[wave->len]=wave->data[wave->len-1]; ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); // wavetable text input size found here if (ImGui::InputText("##MMLWave",&mmlStringW)) { - decodeMMLStrW(mmlStringW,wave->data,wave->len,wave->max); + decodeMMLStrW(mmlStringW,wave->data,wave->len,wave->max,waveHex); } if (!ImGui::IsItemActive()) { - encodeMMLStr(mmlStringW,wave->data,wave->len,-1,-1); + encodeMMLStr(mmlStringW,wave->data,wave->len,-1,-1,waveHex); } ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,ImVec2(0.0f,0.0f)); From 12ade13580df60fa9832f038a02ab5499eaeb1d5 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 30 Mar 2022 00:33:29 -0500 Subject: [PATCH 466/637] SegaPCM: fix pitched samples on VGM export fixes #291 --- src/engine/platform/segapcm.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine/platform/segapcm.cpp b/src/engine/platform/segapcm.cpp index 361cef63c..69d8bd6e7 100644 --- a/src/engine/platform/segapcm.cpp +++ b/src/engine/platform/segapcm.cpp @@ -114,8 +114,8 @@ void DivPlatformSegaPCM::tick() { off=(double)s->centerRate/8363.0; } chan[i].pcm.freq=MIN(255,(15625+(off*parent->song.tuning*pow(2.0,double(chan[i].freq+256)/(64.0*12.0)))*255)/31250); - if (dumpWrites && i>=8) { - addWrite(0x10007+((i-8)<<3),chan[i].pcm.freq); + if (dumpWrites) { + addWrite(0x10007+(i<<3),chan[i].pcm.freq); } } chan[i].freqChanged=false; From f1ee04393b3502dff8d8e50d585f0652bec3a0b9 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 30 Mar 2022 01:01:45 -0500 Subject: [PATCH 467/637] GUI: fix log error if MIDI map file doesn't exist --- src/gui/midiMap.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/gui/midiMap.cpp b/src/gui/midiMap.cpp index 8d31892be..a72324413 100644 --- a/src/gui/midiMap.cpp +++ b/src/gui/midiMap.cpp @@ -67,7 +67,9 @@ bool MIDIMap::read(String path) { int curLine=1; FILE* f=fopen(path.c_str(),"rb"); if (f==NULL) { - logE("error while loading MIDI mapping! %s\n",strerror(errno)); + if (errno!=ENOENT) { + logE("error while loading MIDI mapping! %s\n",strerror(errno)); + } return false; } From cc152bc3d039cfdb1b09bb24cf51613e997f4950 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 30 Mar 2022 03:36:16 -0500 Subject: [PATCH 468/637] OPL: partially enable VGM export it works except for fake reset --- src/engine/sysDef.cpp | 6 ++++ src/engine/vgmOps.cpp | 73 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index bcfbf1c63..dfcbb1fc0 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -1695,6 +1695,12 @@ bool DivEngine::isVGMExportable(DivSystem which) { case DIV_SYSTEM_VRC7: case DIV_SYSTEM_X1_010: case DIV_SYSTEM_SWAN: + case DIV_SYSTEM_OPL: + case DIV_SYSTEM_OPL_DRUMS: + case DIV_SYSTEM_OPL2: + case DIV_SYSTEM_OPL2_DRUMS: + case DIV_SYSTEM_OPL3: + case DIV_SYSTEM_OPL3_DRUMS: return true; default: return false; diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index c2356dbcd..4365b55aa 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -317,6 +317,16 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write w->writeC(0xd6+i); } break; + // TODO: it's 3:35am + case DIV_SYSTEM_OPL: + case DIV_SYSTEM_OPL_DRUMS: + break; + case DIV_SYSTEM_OPL2: + case DIV_SYSTEM_OPL2_DRUMS: + break; + case DIV_SYSTEM_OPL3: + case DIV_SYSTEM_OPL3_DRUMS: + break; default: break; } @@ -467,6 +477,33 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write w->writeC(write.val&0xff); } break; + case DIV_SYSTEM_OPL: + case DIV_SYSTEM_OPL_DRUMS: + w->writeC(0x0b|baseAddr1); + w->writeC(write.addr&0xff); + w->writeC(write.val); + break; + case DIV_SYSTEM_OPL2: + case DIV_SYSTEM_OPL2_DRUMS: + w->writeC(0x0a|baseAddr1); + w->writeC(write.addr&0xff); + w->writeC(write.val); + break; + case DIV_SYSTEM_OPL3: + case DIV_SYSTEM_OPL3_DRUMS: + switch (write.addr>>8) { + case 0: // port 0 + w->writeC(0x0e|baseAddr1); + w->writeC(write.addr&0xff); + w->writeC(write.val); + break; + case 1: // port 1 + w->writeC(0x0f|baseAddr1); + w->writeC(write.addr&0xff); + w->writeC(write.val); + break; + } + break; default: logW("write not handled!\n"); break; @@ -798,6 +835,42 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) { howManyChips++; } break; + case DIV_SYSTEM_OPL: + case DIV_SYSTEM_OPL_DRUMS: + if (!hasOPL) { + hasOPL=disCont[i].dispatch->chipClock; + willExport[i]=true; + } else if (!(hasOPL&0x40000000)) { + isSecond[i]=true; + willExport[i]=true; + hasOPL|=0x40000000; + howManyChips++; + } + break; + case DIV_SYSTEM_OPL2: + case DIV_SYSTEM_OPL2_DRUMS: + if (!hasOPL2) { + hasOPL2=disCont[i].dispatch->chipClock; + willExport[i]=true; + } else if (!(hasOPL2&0x40000000)) { + isSecond[i]=true; + willExport[i]=true; + hasOPL2|=0x40000000; + howManyChips++; + } + break; + case DIV_SYSTEM_OPL3: + case DIV_SYSTEM_OPL3_DRUMS: + if (!hasOPL3) { + hasOPL3=disCont[i].dispatch->chipClock; + willExport[i]=true; + } else if (!(hasOPL3&0x40000000)) { + isSecond[i]=true; + willExport[i]=true; + hasOPL3|=0x40000000; + howManyChips++; + } + break; default: break; } From cccf90d417e3e708f49670163e613f98102c49cc Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 30 Mar 2022 04:27:11 -0500 Subject: [PATCH 469/637] OPL: implement VGM export fake reset --- src/engine/vgmOps.cpp | 82 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index 4365b55aa..c5fa63f3d 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -320,12 +320,94 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write // TODO: it's 3:35am case DIV_SYSTEM_OPL: case DIV_SYSTEM_OPL_DRUMS: + // disable envelope + for (int i=0; i<6; i++) { + w->writeC(0x0b|baseAddr1); + w->writeC(0x80+i); + w->writeC(0x0f); + w->writeC(0x0b|baseAddr1); + w->writeC(0x88+i); + w->writeC(0x0f); + w->writeC(0x0b|baseAddr1); + w->writeC(0x90+i); + w->writeC(0x0f); + } + // key off + freq reset + for (int i=0; i<9; i++) { + w->writeC(0x0b|baseAddr1); + w->writeC(0xa0+i); + w->writeC(0); + w->writeC(0x0b|baseAddr1); + w->writeC(0xb0+i); + w->writeC(0); + } break; case DIV_SYSTEM_OPL2: case DIV_SYSTEM_OPL2_DRUMS: + // disable envelope + for (int i=0; i<6; i++) { + w->writeC(0x0a|baseAddr1); + w->writeC(0x80+i); + w->writeC(0x0f); + w->writeC(0x0a|baseAddr1); + w->writeC(0x88+i); + w->writeC(0x0f); + w->writeC(0x0a|baseAddr1); + w->writeC(0x90+i); + w->writeC(0x0f); + } + // key off + freq reset + for (int i=0; i<9; i++) { + w->writeC(0x0a|baseAddr1); + w->writeC(0xa0+i); + w->writeC(0); + w->writeC(0x0a|baseAddr1); + w->writeC(0xb0+i); + w->writeC(0); + } break; case DIV_SYSTEM_OPL3: case DIV_SYSTEM_OPL3_DRUMS: + // disable envelope + for (int i=0; i<6; i++) { + w->writeC(0x0e|baseAddr1); + w->writeC(0x80+i); + w->writeC(0x0f); + w->writeC(0x0e|baseAddr1); + w->writeC(0x88+i); + w->writeC(0x0f); + w->writeC(0x0e|baseAddr1); + w->writeC(0x90+i); + w->writeC(0x0f); + w->writeC(0x0f|baseAddr1); + w->writeC(0x80+i); + w->writeC(0x0f); + w->writeC(0x0f|baseAddr1); + w->writeC(0x88+i); + w->writeC(0x0f); + w->writeC(0x0f|baseAddr1); + w->writeC(0x90+i); + w->writeC(0x0f); + } + // key off + freq reset + for (int i=0; i<9; i++) { + w->writeC(0x0e|baseAddr1); + w->writeC(0xa0+i); + w->writeC(0); + w->writeC(0x0e|baseAddr1); + w->writeC(0xb0+i); + w->writeC(0); + w->writeC(0x0f|baseAddr1); + w->writeC(0xa0+i); + w->writeC(0); + w->writeC(0x0f|baseAddr1); + w->writeC(0xb0+i); + w->writeC(0); + } + // reset 4-op + w->writeC(0x0f|baseAddr1); + w->writeC(0x04); + w->writeC(0x00); break; default: break; From cb107ebbacf1558b183ab4f94f033113499cfe1d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 30 Mar 2022 04:49:15 -0500 Subject: [PATCH 470/637] GUI: possibly fix out-of-range notes in preview now they should release --- src/gui/gui.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 77aa687ea..f3a6eced3 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -730,6 +730,8 @@ void FurnaceGUI::stopPreviewNote(SDL_Scancode scancode) { try { int key=noteKeys.at(scancode); int num=12*curOctave+key; + if (num<-60) num=-60; // C-(-5) + if (num>119) num=119; // B-9 if (key==100) return; if (key==101) return; From 501ecc063d5e19031c57759d923760427b18bd88 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 30 Mar 2022 15:44:27 -0500 Subject: [PATCH 471/637] GUI: earliest MIDI note input --- src/gui/gui.cpp | 94 +++++++++++++++++++++++++++++-------------------- src/gui/gui.h | 1 + 2 files changed, 56 insertions(+), 39 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index f3a6eced3..a577608c6 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -749,6 +749,46 @@ void FurnaceGUI::stopPreviewNote(SDL_Scancode scancode) { } } +void FurnaceGUI::noteInput(int num, int key) { + DivPattern* pat=e->song.pat[cursor.xCoarse].getPattern(e->song.orders.ord[cursor.xCoarse][e->getOrder()],true); + + prepareUndo(GUI_UNDO_PATTERN_EDIT); + + if (key==100) { // note off + pat->data[cursor.y][0]=100; + pat->data[cursor.y][1]=0; + } else if (key==101) { // note off + env release + pat->data[cursor.y][0]=101; + pat->data[cursor.y][1]=0; + } else if (key==102) { // env release only + pat->data[cursor.y][0]=102; + pat->data[cursor.y][1]=0; + } else { + pat->data[cursor.y][0]=num%12; + pat->data[cursor.y][1]=num/12; + if (pat->data[cursor.y][0]==0) { + pat->data[cursor.y][0]=12; + pat->data[cursor.y][1]--; + } + pat->data[cursor.y][1]=(unsigned char)pat->data[cursor.y][1]; + if (latchIns==-2) { + pat->data[cursor.y][2]=curIns; + } else if (latchIns!=-1 && !e->song.ins.empty()) { + pat->data[cursor.y][2]=MIN(((int)e->song.ins.size())-1,latchIns); + } + if (latchVol!=-1) { + int maxVol=e->getMaxVolumeChan(cursor.xCoarse); + pat->data[cursor.y][3]=MIN(maxVol,latchVol); + } + if (latchEffect!=-1) pat->data[cursor.y][4]=latchEffect; + if (latchEffectVal!=-1) pat->data[cursor.y][5]=latchEffectVal; + previewNote(cursor.xCoarse,num); + } + makeUndo(GUI_UNDO_PATTERN_EDIT); + editAdvance(); + curNibble=false; +} + void FurnaceGUI::keyDown(SDL_Event& ev) { if (ImGuiFileDialog::Instance()->IsOpened()) return; if (aboutOpen) return; @@ -813,44 +853,7 @@ void FurnaceGUI::keyDown(SDL_Event& ev) { if (num>119) num=119; // B-9 if (edit) { - // TODO: separate when adding MIDI input. - DivPattern* pat=e->song.pat[cursor.xCoarse].getPattern(e->song.orders.ord[cursor.xCoarse][e->getOrder()],true); - - prepareUndo(GUI_UNDO_PATTERN_EDIT); - - if (key==100) { // note off - pat->data[cursor.y][0]=100; - pat->data[cursor.y][1]=0; - } else if (key==101) { // note off + env release - pat->data[cursor.y][0]=101; - pat->data[cursor.y][1]=0; - } else if (key==102) { // env release only - pat->data[cursor.y][0]=102; - pat->data[cursor.y][1]=0; - } else { - pat->data[cursor.y][0]=num%12; - pat->data[cursor.y][1]=num/12; - if (pat->data[cursor.y][0]==0) { - pat->data[cursor.y][0]=12; - pat->data[cursor.y][1]--; - } - pat->data[cursor.y][1]=(unsigned char)pat->data[cursor.y][1]; - if (latchIns==-2) { - pat->data[cursor.y][2]=curIns; - } else if (latchIns!=-1 && !e->song.ins.empty()) { - pat->data[cursor.y][2]=MIN(((int)e->song.ins.size())-1,latchIns); - } - if (latchVol!=-1) { - int maxVol=e->getMaxVolumeChan(cursor.xCoarse); - pat->data[cursor.y][3]=MIN(maxVol,latchVol); - } - if (latchEffect!=-1) pat->data[cursor.y][4]=latchEffect; - if (latchEffectVal!=-1) pat->data[cursor.y][5]=latchEffectVal; - previewNote(cursor.xCoarse,num); - } - makeUndo(GUI_UNDO_PATTERN_EDIT); - editAdvance(); - curNibble=false; + noteInput(num,key); } else { if (key!=100 && key!=101 && key!=102) { previewNote(cursor.xCoarse,num); @@ -1910,7 +1913,20 @@ bool FurnaceGUI::loop() { // parse message here logD("message is %.2x\n",msg.type); int action=midiMap.at(msg); - if (action!=0) doAction(action); + if (action!=0) { + doAction(action); + } else switch (msg.type&0xf0) { + case TA_MIDI_NOTE_ON: + noteInput(msg.data[0],0); + // TODO volume input + break; + case TA_MIDI_PROGRAM: + if (midiMap.programChange) { + curIns=msg.data[0]; + if (curIns>(int)e->song.ins.size()) curIns=e->song.ins.size()-1; + } + break; + } midiLock.lock(); midiQueue.pop(); diff --git a/src/gui/gui.h b/src/gui/gui.h index a1978a240..bb0afefb7 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -980,6 +980,7 @@ class FurnaceGUI { void doUndo(); void doRedo(); void editOptions(bool topMenu); + void noteInput(int num, int key); void doUndoSample(); void doRedoSample(); From a8ee4b124511a5d9b7c9700a2d5ff3fe8791bd2a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 30 Mar 2022 18:18:39 -0500 Subject: [PATCH 472/637] GUI: kinda working MIDI note input --- src/gui/gui.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index a577608c6..cbf7f2928 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -782,7 +782,6 @@ void FurnaceGUI::noteInput(int num, int key) { } if (latchEffect!=-1) pat->data[cursor.y][4]=latchEffect; if (latchEffectVal!=-1) pat->data[cursor.y][5]=latchEffectVal; - previewNote(cursor.xCoarse,num); } makeUndo(GUI_UNDO_PATTERN_EDIT); editAdvance(); @@ -854,10 +853,9 @@ void FurnaceGUI::keyDown(SDL_Event& ev) { if (edit) { noteInput(num,key); - } else { - if (key!=100 && key!=101 && key!=102) { - previewNote(cursor.xCoarse,num); - } + } + if (key!=100 && key!=101 && key!=102) { + previewNote(cursor.xCoarse,num); } } catch (std::out_of_range& e) { } @@ -1917,8 +1915,10 @@ bool FurnaceGUI::loop() { doAction(action); } else switch (msg.type&0xf0) { case TA_MIDI_NOTE_ON: - noteInput(msg.data[0],0); - // TODO volume input + if (edit && msg.data[1]!=0) { + noteInput(msg.data[0]-12,0); + // TODO volume input + } break; case TA_MIDI_PROGRAM: if (midiMap.programChange) { From 6f3b9f2e5d671f4a6b6277e22df8c6846992ad7c Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 30 Mar 2022 22:55:55 -0500 Subject: [PATCH 473/637] VRC6: changes around volume macro --- src/engine/platform/vrc6.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/vrc6.cpp b/src/engine/platform/vrc6.cpp index 27cb2ffaf..2162fe567 100644 --- a/src/engine/platform/vrc6.cpp +++ b/src/engine/platform/vrc6.cpp @@ -149,7 +149,7 @@ void DivPlatformVRC6::tick() { chWrite(i,0,chan[i].outVol); } } else { // pulse - chan[i].outVol=((chan[i].vol&15)*MIN(63,chan[i].std.vol))/63; + chan[i].outVol=((chan[i].vol&15)*MIN(15,chan[i].std.vol))/15; if (chan[i].outVol<0) chan[i].outVol=0; if (chan[i].outVol>15) chan[i].outVol=15; if ((!isMuted[i]) && (!chan[i].pcm)) { From 45ce940d66fd164d691c119f02d404f734a3462c Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 31 Mar 2022 01:51:57 -0500 Subject: [PATCH 474/637] better MIDI and note input handling closes #147 --- src/engine/engine.cpp | 44 +++++++++++++++++++++++++++++++++++++++++ src/engine/engine.h | 13 +++++++++++- src/engine/instrument.h | 2 +- src/engine/playback.cpp | 11 +++-------- src/gui/cursor.cpp | 8 ++++++++ src/gui/gui.cpp | 28 ++++++++++++++++++++------ src/gui/gui.h | 4 ++-- 7 files changed, 92 insertions(+), 18 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index f6252cbe7..9ebe26ff6 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -2537,6 +2537,45 @@ void DivEngine::noteOff(int chan) { BUSY_END; } +void DivEngine::autoNoteOn(int ch, int ins, int note, int vol) { + //if (ch<0 || ch>=chans) return; + if (midiBaseChan<0) midiBaseChan=0; + if (midiBaseChan>=chans) midiBaseChan=chans-1; + int finalChan=midiBaseChan; + + if (!playing) { + reset(); + freelance=true; + playing=true; + } + + do { + if ((ins==-1 || getPreferInsType(finalChan)==getIns(ins)->type) && chan[finalChan].midiNote==-1) { + chan[finalChan].midiNote=note; + pendingNotes.push(DivNoteEvent(finalChan,ins,note,vol,true)); + break; + } + if (++finalChan>=chans) { + finalChan=0; + } + } while (finalChan!=midiBaseChan); +} + +void DivEngine::autoNoteOff(int ch, int note, int vol) { + if (!playing) { + reset(); + freelance=true; + playing=true; + } + //if (ch<0 || ch>=chans) return; + for (int i=0; i=chans) chan=0; + midiBaseChan=chan; +} + void DivEngine::setMidiCallback(std::function what) { midiCallback=what; } diff --git a/src/engine/engine.h b/src/engine/engine.h index 52a01291b..7ae5fa7f0 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -86,6 +86,8 @@ struct DivChannelState { bool doNote, legato, portaStop, keyOn, keyOff, nowYouCanStop, stopOnOff; bool arpYield, delayLocked, inPorta, scheduledSlideReset, shorthandPorta, noteOnInhibit, resetArp; + int midiNote; + DivChannelState(): note(-1), oldNote(-1), @@ -126,7 +128,8 @@ struct DivChannelState { scheduledSlideReset(false), shorthandPorta(false), noteOnInhibit(false), - resetArp(false) {} + resetArp(false), + midiNote(-1) {} }; struct DivNoteEvent { @@ -231,6 +234,7 @@ class DivEngine { short vibTable[64]; int reversePitchTable[4096]; int pitchTable[4096]; + int midiBaseChan; blip_buffer_t* samp_bb; size_t samp_bbInLen; @@ -539,6 +543,9 @@ class DivEngine { // stop note void noteOff(int chan); + void autoNoteOn(int chan, int ins, int note, int vol=-1); + void autoNoteOff(int chan, int note, int vol=-1); + // go to order void setOrder(unsigned char order); @@ -638,6 +645,9 @@ class DivEngine { // switch master bool switchMaster(); + // set MIDI base channel + void setMidiBaseChan(int chan); + // set MIDI input callback // if the specified function returns -2, note feedback will be inhibited. void setMidiCallback(std::function what); @@ -726,6 +736,7 @@ class DivEngine { view(DIV_STATUS_NOTHING), haltOn(DIV_HALT_NONE), audioEngine(DIV_AUDIO_NULL), + midiBaseChan(0), samp_bbInLen(0), samp_temp(0), samp_prevSample(0), diff --git a/src/engine/instrument.h b/src/engine/instrument.h index 9d51670c1..570f8a63f 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -430,7 +430,7 @@ struct DivInstrument { DivInstrument(): name(""), mode(false), - type(DIV_INS_STD) { + type(DIV_INS_FM) { } }; #endif diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 0e1922b34..a1acff8cd 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -1552,7 +1552,7 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi switch (msg.type&0xf0) { case TA_MIDI_NOTE_OFF: { if (chan<0 || chan>=chans) break; - pendingNotes.push(DivNoteEvent(msg.type&15,-1,-1,-1,false)); + autoNoteOff(msg.type&15,msg.data[0]-12,msg.data[1]); if (!playing) { reset(); freelance=true; @@ -1563,14 +1563,9 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi case TA_MIDI_NOTE_ON: { if (chan<0 || chan>=chans) break; if (msg.data[1]==0) { - pendingNotes.push(DivNoteEvent(msg.type&15,-1,-1,-1,false)); + autoNoteOff(msg.type&15,msg.data[0]-12,msg.data[1]); } else { - pendingNotes.push(DivNoteEvent(msg.type&15,ins,(int)msg.data[0]-12,msg.data[1],true)); - } - if (!playing) { - reset(); - freelance=true; - playing=true; + autoNoteOn(msg.type&15,ins,msg.data[0]-12,msg.data[1]); } break; } diff --git a/src/gui/cursor.cpp b/src/gui/cursor.cpp index 6a77ff0bd..421b6d486 100644 --- a/src/gui/cursor.cpp +++ b/src/gui/cursor.cpp @@ -35,6 +35,7 @@ void FurnaceGUI::startSelection(int xCoarse, int xFine, int y) { selEnd.xFine=xFine; selEnd.y=y; selecting=true; + e->setMidiBaseChan(cursor.xCoarse); } void FurnaceGUI::updateSelection(int xCoarse, int xFine, int y) { @@ -88,6 +89,8 @@ void FurnaceGUI::finishSelection() { if (e->song.chanCollapse[selEnd.xCoarse]) { selEnd.xFine=2+e->song.pat[cursor.xCoarse].effectRows*2; } + + e->setMidiBaseChan(cursor.xCoarse); } void FurnaceGUI::moveCursor(int x, int y, bool select) { @@ -191,6 +194,7 @@ void FurnaceGUI::moveCursor(int x, int y, bool select) { } selEnd=cursor; updateScroll(cursor.y); + e->setMidiBaseChan(cursor.xCoarse); } void FurnaceGUI::moveCursorPrevChannel(bool overflow) { @@ -210,6 +214,7 @@ void FurnaceGUI::moveCursorPrevChannel(bool overflow) { cursor.xCoarse=firstChannel; } } + e->setMidiBaseChan(cursor.xCoarse); selStart=cursor; selEnd=cursor; @@ -233,6 +238,7 @@ void FurnaceGUI::moveCursorNextChannel(bool overflow) { cursor.xCoarse=lastChannel-1; } } + e->setMidiBaseChan(cursor.xCoarse); selStart=cursor; selEnd=cursor; @@ -254,6 +260,7 @@ void FurnaceGUI::moveCursorTop(bool select) { if (!select) { selEnd=cursor; } + e->setMidiBaseChan(cursor.xCoarse); updateScroll(cursor.y); } @@ -273,6 +280,7 @@ void FurnaceGUI::moveCursorBottom(bool select) { selStart=cursor; } selEnd=cursor; + e->setMidiBaseChan(cursor.xCoarse); updateScroll(cursor.y); } diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index cbf7f2928..04c9d1461 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -703,7 +703,15 @@ void FurnaceGUI::stop() { activeNotes.clear(); } -void FurnaceGUI::previewNote(int refChan, int note) { +void FurnaceGUI::previewNote(int refChan, int note, bool autoNote) { + if (autoNote) { + e->setMidiBaseChan(refChan); + e->synchronized([this,note]() { + e->autoNoteOn(-1,curIns,note); + }); + return; + } + bool chanBusy[DIV_MAX_CHANS]; memset(chanBusy,0,DIV_MAX_CHANS*sizeof(bool)); for (ActiveNote& i: activeNotes) { @@ -725,8 +733,8 @@ void FurnaceGUI::previewNote(int refChan, int note) { //printf("FAILED TO FIND CHANNEL!\n"); } -void FurnaceGUI::stopPreviewNote(SDL_Scancode scancode) { - if (activeNotes.empty()) return; +void FurnaceGUI::stopPreviewNote(SDL_Scancode scancode, bool autoNote) { + if (activeNotes.empty() && !autoNote) return; try { int key=noteKeys.at(scancode); int num=12*curOctave+key; @@ -737,6 +745,13 @@ void FurnaceGUI::stopPreviewNote(SDL_Scancode scancode) { if (key==101) return; if (key==102) return; + if (autoNote) { + e->synchronized([this,num]() { + e->autoNoteOff(-1,num); + }); + return; + } + for (size_t i=0; inoteOff(activeNotes[i].chan); @@ -1008,7 +1023,7 @@ void FurnaceGUI::keyDown(SDL_Event& ev) { int key=noteKeys.at(ev.key.keysym.scancode); int num=12*curOctave+key; if (key!=100 && key!=101 && key!=102) { - previewNote(cursor.xCoarse,num); + previewNote(cursor.xCoarse,num,true); } } catch (std::out_of_range& e) { } @@ -1052,7 +1067,7 @@ void FurnaceGUI::keyDown(SDL_Event& ev) { } void FurnaceGUI::keyUp(SDL_Event& ev) { - stopPreviewNote(ev.key.keysym.scancode); + stopPreviewNote(ev.key.keysym.scancode,curWindow!=GUI_WINDOW_PATTERN); if (wavePreviewOn) { if (ev.key.keysym.scancode==wavePreviewKey) { wavePreviewOn=false; @@ -1923,7 +1938,7 @@ bool FurnaceGUI::loop() { case TA_MIDI_PROGRAM: if (midiMap.programChange) { curIns=msg.data[0]; - if (curIns>(int)e->song.ins.size()) curIns=e->song.ins.size()-1; + if (curIns>=(int)e->song.ins.size()) curIns=e->song.ins.size()-1; } break; } @@ -2693,6 +2708,7 @@ bool FurnaceGUI::init() { midiLock.lock(); midiQueue.push(msg); midiLock.unlock(); + e->setMidiBaseChan(cursor.xCoarse); if (midiMap.at(msg)) return -2; return curIns; }); diff --git a/src/gui/gui.h b/src/gui/gui.h index bb0afefb7..8624080ad 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -988,8 +988,8 @@ class FurnaceGUI { void play(int row=0); void stop(); - void previewNote(int refChan, int note); - void stopPreviewNote(SDL_Scancode scancode); + void previewNote(int refChan, int note, bool autoNote=false); + void stopPreviewNote(SDL_Scancode scancode, bool autoNote=false); void keyDown(SDL_Event& ev); void keyUp(SDL_Event& ev); From f689409f02ea98ad4620377bc34cc9415efc3273 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 31 Mar 2022 03:33:05 -0500 Subject: [PATCH 475/637] kind of MIDI output --- src/audio/abstract.cpp | 4 ++++ src/audio/rtmidi.cpp | 40 ++++++++++++++++++++++++++++++++++++--- src/audio/rtmidi.h | 2 +- src/audio/taAudio.h | 12 +++++++++++- src/engine/engine.cpp | 28 +++++++++++++++++++++++++-- src/engine/engine.h | 7 +++++-- src/engine/playback.cpp | 42 +++++++++++++++++++++++++++++++++++++++++ 7 files changed, 126 insertions(+), 9 deletions(-) diff --git a/src/audio/abstract.cpp b/src/audio/abstract.cpp index d4973180a..45960fea9 100644 --- a/src/audio/abstract.cpp +++ b/src/audio/abstract.cpp @@ -61,6 +61,10 @@ bool TAMidiIn::gather() { return false; } +bool TAMidiOut::send(const TAMidiMessage& what) { + return false; +} + bool TAMidiIn::isDeviceOpen() { return false; } diff --git a/src/audio/rtmidi.cpp b/src/audio/rtmidi.cpp index a9b2d4b11..fcd992beb 100644 --- a/src/audio/rtmidi.cpp +++ b/src/audio/rtmidi.cpp @@ -19,6 +19,7 @@ #include "rtmidi.h" #include "../ta-log.h" +#include "taAudio.h" // --- IN --- @@ -77,6 +78,7 @@ bool TAMidiInRtMidi::openDevice(String name) { } } isOpen=portOpen; + if (!portOpen) logW("could not find MIDI in device...\n"); return portOpen; } catch (RtMidiError& e) { logW("could not open MIDI in device! %s\n",e.what()); @@ -120,9 +122,40 @@ bool TAMidiInRtMidi::quit() { // --- OUT --- -bool TAMidiOutRtMidi::send(TAMidiMessage& what) { - // TODO - return false; +bool TAMidiOutRtMidi::send(const TAMidiMessage& what) { + if (!isOpen) return false; + if (what.type<0x80) return false; + size_t len=0; + switch (what.type&0xf0) { + case TA_MIDI_NOTE_OFF: + case TA_MIDI_NOTE_ON: + case TA_MIDI_AFTERTOUCH: + case TA_MIDI_CONTROL: + case TA_MIDI_PITCH_BEND: + len=3; + break; + case TA_MIDI_PROGRAM: + case TA_MIDI_CHANNEL_AFTERTOUCH: + len=2; + break; + } + if (len==0) switch (what.type) { + case TA_MIDI_SYSEX: // currently not supported :< + return false; + break; + case TA_MIDI_MTC_FRAME: + case TA_MIDI_SONG_SELECT: + len=2; + break; + case TA_MIDI_POSITION: + len=3; + break; + default: + len=1; + break; + } + port->sendMessage((const unsigned char*)&what.type,len); + return true; } bool TAMidiOutRtMidi::isDeviceOpen() { @@ -143,6 +176,7 @@ bool TAMidiOutRtMidi::openDevice(String name) { } } isOpen=portOpen; + if (!portOpen) logW("could not find MIDI out device...\n"); return portOpen; } catch (RtMidiError& e) { logW("could not open MIDI out device! %s\n",e.what()); diff --git a/src/audio/rtmidi.h b/src/audio/rtmidi.h index 31d7119b1..34ddf73e6 100644 --- a/src/audio/rtmidi.h +++ b/src/audio/rtmidi.h @@ -40,7 +40,7 @@ class TAMidiOutRtMidi: public TAMidiOut { RtMidiOut* port; bool isOpen; public: - bool send(TAMidiMessage& what); + bool send(const TAMidiMessage& what); bool isDeviceOpen(); bool openDevice(String name); bool closeDevice(); diff --git a/src/audio/taAudio.h b/src/audio/taAudio.h index f532e2a7e..c1ef519dd 100644 --- a/src/audio/taAudio.h +++ b/src/audio/taAudio.h @@ -99,6 +99,16 @@ struct TAMidiMessage { void submitSysEx(std::vector data); void done(); + TAMidiMessage(unsigned char t, unsigned char d0, unsigned char d1): + time(0.0), + type(t), + sysExData(NULL), + sysExLen(0) { + memset(&data,0,sizeof(data)); + data[0]=d0; + data[1]=d1; + } + TAMidiMessage(): time(0.0), type(0), @@ -127,7 +137,7 @@ class TAMidiIn { class TAMidiOut { std::queue queue; public: - bool send(TAMidiMessage& what); + virtual bool send(const TAMidiMessage& what); virtual bool isDeviceOpen(); virtual bool openDevice(String name); virtual bool closeDevice(); diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 9ebe26ff6..fe38e8077 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -790,13 +790,20 @@ void DivEngine::playSub(bool preserveDrift, int goalRow) { } speedAB=false; playing=true; + skipping=true; for (int i=0; isetSkipRegisterWrites(true); while (playing && curOrdersetSkipRegisterWrites(false); @@ -816,6 +823,7 @@ void DivEngine::playSub(bool preserveDrift, int goalRow) { if (!preserveDrift) { ticks=1; } + skipping=false; cmdStream.clear(); } @@ -2864,6 +2872,16 @@ bool DivEngine::initAudioBackend() { } } } + if (output->midiOut) { + String outName=getConfString("midiOutDevice",""); + if (!outName.empty()) { + // try opening device + logI("opening MIDI output.\n"); + if (!output->midiOut->openDevice(outName)) { + logW("could not open MIDI output device!\n"); + } + } + } return true; } @@ -2876,6 +2894,12 @@ bool DivEngine::deinitAudioBackend() { output->midiIn->closeDevice(); } } + if (output->midiOut) { + if (output->midiOut->isDeviceOpen()) { + logI("closing MIDI output.\n"); + output->midiOut->closeDevice(); + } + } output->quitMidi(); output->quit(); delete output; diff --git a/src/engine/engine.h b/src/engine/engine.h index 7ae5fa7f0..2f334edeb 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -86,7 +86,7 @@ struct DivChannelState { bool doNote, legato, portaStop, keyOn, keyOff, nowYouCanStop, stopOnOff; bool arpYield, delayLocked, inPorta, scheduledSlideReset, shorthandPorta, noteOnInhibit, resetArp; - int midiNote; + int midiNote, curMidiNote; DivChannelState(): note(-1), @@ -129,7 +129,8 @@ struct DivChannelState { shorthandPorta(false), noteOnInhibit(false), resetArp(false), - midiNote(-1) {} + midiNote(-1), + curMidiNote(-1) {} }; struct DivNoteEvent { @@ -194,6 +195,7 @@ class DivEngine { bool cmdStreamEnabled; bool softLocked; bool firstTick; + bool skipping; int softLockCount; int ticks, curRow, curOrder, remainingLoops, nextSpeed; double divider; @@ -712,6 +714,7 @@ class DivEngine { cmdStreamEnabled(false), softLocked(false), firstTick(false), + skipping(false), softLockCount(0), ticks(0), curRow(0), diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index a1acff8cd..6dddcfcbb 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -172,7 +172,49 @@ int DivEngine::dispatchCmd(DivCommand c) { if (cmdStreamEnabled && cmdStream.size()<2000) { cmdStream.push_back(c); } + + if (!skipping && output->midiOut!=NULL) { + if (output->midiOut->isDeviceOpen()) { + int scaledVol=(chan[c.chan].volume*127)/MAX(1,chan[c.chan].volMax); + if (scaledVol<0) scaledVol=0; + if (scaledVol>127) scaledVol=127; + switch (c.cmd) { + case DIV_CMD_NOTE_ON: + case DIV_CMD_LEGATO: + if (chan[c.chan].curMidiNote>=0) { + output->midiOut->send(TAMidiMessage(0x80|(c.chan&15),chan[c.chan].curMidiNote,scaledVol)); + } + if (c.value!=DIV_NOTE_NULL) chan[c.chan].curMidiNote=c.value+12; + output->midiOut->send(TAMidiMessage(0x90|(c.chan&15),chan[c.chan].curMidiNote,scaledVol)); + break; + case DIV_CMD_NOTE_OFF: + case DIV_CMD_NOTE_OFF_ENV: + if (chan[c.chan].curMidiNote>=0) { + output->midiOut->send(TAMidiMessage(0x80|(c.chan&15),chan[c.chan].curMidiNote,scaledVol)); + } + chan[c.chan].curMidiNote=-1; + break; + case DIV_CMD_INSTRUMENT: + output->midiOut->send(TAMidiMessage(0xc0|(c.chan&15),c.value,0)); + break; + case DIV_CMD_VOLUME: + //output->midiOut->send(TAMidiMessage(0xb0|(c.chan&15),0x07,scaledVol)); + break; + case DIV_CMD_PITCH: { + int pitchBend=8192+(c.value<<5); + if (pitchBend<0) pitchBend=0; + if (pitchBend>16383) pitchBend=16383; + output->midiOut->send(TAMidiMessage(0xe0|(c.chan&15),pitchBend&0x7f,pitchBend>>7)); + break; + } + default: + break; + } + } + } + c.chan=dispatchChanOfChan[c.dis]; + return disCont[dispatchOfChan[c.dis]].dispatch->dispatch(c); } From d32129254328effc5640c4e1a34da0959ef38033 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 31 Mar 2022 15:25:58 -0500 Subject: [PATCH 476/637] separate VRC6 and VRC6 saw ins types --- papers/format.md | 1 + src/engine/engine.h | 4 ++-- src/engine/instrument.h | 1 + src/gui/dataList.cpp | 6 +++++- src/gui/gui.cpp | 13 +++++++++---- src/gui/gui.h | 3 ++- src/gui/guiConst.cpp | 3 ++- src/gui/insEdit.cpp | 10 +++++----- src/gui/settings.cpp | 3 +++ 9 files changed, 30 insertions(+), 14 deletions(-) diff --git a/papers/format.md b/papers/format.md index dd4bcce2d..f2ea89acc 100644 --- a/papers/format.md +++ b/papers/format.md @@ -29,6 +29,7 @@ furthermore, an `or reserved` indicates this field is always present, but is res the format versions are: +- 74: Furnace dev74 - 73: Furnace dev73 - 72: Furnace dev72 - 71: Furnace dev71 diff --git a/src/engine/engine.h b/src/engine/engine.h index 2f334edeb..f2ec57987 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -42,8 +42,8 @@ #define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock(); #define BUSY_END isBusy.unlock(); softLocked=false; -#define DIV_VERSION "dev73" -#define DIV_ENGINE_VERSION 73 +#define DIV_VERSION "dev74" +#define DIV_ENGINE_VERSION 74 // for imports #define DIV_VERSION_MOD 0xff01 diff --git a/src/engine/instrument.h b/src/engine/instrument.h index 570f8a63f..22b3bb320 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -53,6 +53,7 @@ enum DivInstrumentType { DIV_INS_MIKEY=23, DIV_INS_VERA=24, DIV_INS_X1_010=25, + DIV_INS_VRC6_SAW=26, DIV_INS_MAX, }; diff --git a/src/gui/dataList.cpp b/src/gui/dataList.cpp index d6c88a78e..648abbd85 100644 --- a/src/gui/dataList.cpp +++ b/src/gui/dataList.cpp @@ -128,6 +128,10 @@ void FurnaceGUI::drawInsList() { ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_VRC6]); name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); break; + case DIV_INS_VRC6_SAW: + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_VRC6_SAW]); + name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); + break; case DIV_INS_OPLL: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_OPLL]); name=fmt::sprintf(ICON_FA_AREA_CHART " %.2X: %s##_INS%d",i,ins->name,i); @@ -364,4 +368,4 @@ void FurnaceGUI::actualSampleList() { } } } -} \ No newline at end of file +} diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 04c9d1461..89e66355f 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -764,7 +764,7 @@ void FurnaceGUI::stopPreviewNote(SDL_Scancode scancode, bool autoNote) { } } -void FurnaceGUI::noteInput(int num, int key) { +void FurnaceGUI::noteInput(int num, int key, int vol) { DivPattern* pat=e->song.pat[cursor.xCoarse].getPattern(e->song.orders.ord[cursor.xCoarse][e->getOrder()],true); prepareUndo(GUI_UNDO_PATTERN_EDIT); @@ -791,9 +791,11 @@ void FurnaceGUI::noteInput(int num, int key) { } else if (latchIns!=-1 && !e->song.ins.empty()) { pat->data[cursor.y][2]=MIN(((int)e->song.ins.size())-1,latchIns); } + int maxVol=e->getMaxVolumeChan(cursor.xCoarse); if (latchVol!=-1) { - int maxVol=e->getMaxVolumeChan(cursor.xCoarse); pat->data[cursor.y][3]=MIN(maxVol,latchVol); + } else if (vol!=-1) { + pat->data[cursor.y][3]=(vol*maxVol)/127; } if (latchEffect!=-1) pat->data[cursor.y][4]=latchEffect; if (latchEffectVal!=-1) pat->data[cursor.y][5]=latchEffectVal; @@ -1931,8 +1933,11 @@ bool FurnaceGUI::loop() { } else switch (msg.type&0xf0) { case TA_MIDI_NOTE_ON: if (edit && msg.data[1]!=0) { - noteInput(msg.data[0]-12,0); - // TODO volume input + noteInput( + msg.data[0]-12, + 0, + midiMap.volInput?((int)(pow((double)msg.data[2]/127.0,midiMap.volExp)*127.0)):-1 + ); } break; case TA_MIDI_PROGRAM: diff --git a/src/gui/gui.h b/src/gui/gui.h index 8624080ad..1ce3ace88 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -95,6 +95,7 @@ enum FurnaceGUIColors { GUI_COLOR_INSTR_VIC, GUI_COLOR_INSTR_PET, GUI_COLOR_INSTR_VRC6, + GUI_COLOR_INSTR_VRC6_SAW, GUI_COLOR_INSTR_OPLL, GUI_COLOR_INSTR_OPL, GUI_COLOR_INSTR_FDS, @@ -980,7 +981,7 @@ class FurnaceGUI { void doUndo(); void doRedo(); void editOptions(bool topMenu); - void noteInput(int num, int key); + void noteInput(int num, int key, int vol=-1); void doUndoSample(); void doRedoSample(); diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index efb52331d..b21f9b7ab 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -92,7 +92,8 @@ const char* insTypes[DIV_INS_MAX]={ "WonderSwan", "Atari Lynx", "VERA", - "X1-010" + "X1-010", + "VRC6 (saw)" }; const char* sampleDepths[17]={ diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index fadf36373..9cf382cf7 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -2091,7 +2091,7 @@ void FurnaceGUI::drawInsEdit() { if ((ins->type==DIV_INS_PCE || ins->type==DIV_INS_AY8930)) { volMax=31; } - if (ins->type==DIV_INS_OPL || ins->type==DIV_INS_VERA || ins->type==DIV_INS_VRC6) { + if (ins->type==DIV_INS_OPL || ins->type==DIV_INS_VERA || ins->type==DIV_INS_VRC6_SAW) { volMax=63; } if (ins->type==DIV_INS_AMIGA) { @@ -2128,9 +2128,9 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_FM) { dutyLabel="Noise Freq"; } - if (ins->type == DIV_INS_MIKEY) { - dutyLabel = "Duty/Int"; - dutyMax = 10; + if (ins->type==DIV_INS_MIKEY) { + dutyLabel="Duty/Int"; + dutyMax=10; } if (ins->type==DIV_INS_AY8930) { dutyMax=255; @@ -2145,7 +2145,7 @@ void FurnaceGUI::drawInsEdit() { dutyLabel="Noise"; dutyMax=8; } - if (ins->type==DIV_INS_OPLL || ins->type==DIV_INS_OPL) { + if (ins->type==DIV_INS_OPLL || ins->type==DIV_INS_OPL || ins->type==DIV_INS_VRC6_SAW) { dutyMax=0; } if (ins->type==DIV_INS_VERA) { diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index be9aaf034..150f47a93 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -803,6 +803,7 @@ void FurnaceGUI::drawSettings() { UI_COLOR_CONFIG(GUI_COLOR_INSTR_VIC,"VIC"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_PET,"PET"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_VRC6,"VRC6"); + UI_COLOR_CONFIG(GUI_COLOR_INSTR_VRC6_SAW,"VRC6 (saw)"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_OPLL,"FM (OPLL)"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_OPL,"FM (OPL)"); UI_COLOR_CONFIG(GUI_COLOR_INSTR_FDS,"FDS"); @@ -1581,6 +1582,7 @@ void FurnaceGUI::commitSettings() { PUT_UI_COLOR(GUI_COLOR_INSTR_VIC); PUT_UI_COLOR(GUI_COLOR_INSTR_PET); PUT_UI_COLOR(GUI_COLOR_INSTR_VRC6); + PUT_UI_COLOR(GUI_COLOR_INSTR_VRC6_SAW); PUT_UI_COLOR(GUI_COLOR_INSTR_OPLL); PUT_UI_COLOR(GUI_COLOR_INSTR_OPL); PUT_UI_COLOR(GUI_COLOR_INSTR_FDS); @@ -1972,6 +1974,7 @@ void FurnaceGUI::applyUISettings() { GET_UI_COLOR(GUI_COLOR_INSTR_VIC,ImVec4(0.2f,1.0f,0.6f,1.0f)); GET_UI_COLOR(GUI_COLOR_INSTR_PET,ImVec4(1.0f,1.0f,0.8f,1.0f)); GET_UI_COLOR(GUI_COLOR_INSTR_VRC6,ImVec4(1.0f,0.9f,0.5f,1.0f)); + GET_UI_COLOR(GUI_COLOR_INSTR_VRC6_SAW,ImVec4(0.8f,0.3f,0.0f,1.0f)); GET_UI_COLOR(GUI_COLOR_INSTR_OPLL,ImVec4(0.6f,0.7f,1.0f,1.0f)); GET_UI_COLOR(GUI_COLOR_INSTR_OPL,ImVec4(0.3f,1.0f,0.9f,1.0f)); GET_UI_COLOR(GUI_COLOR_INSTR_FDS,ImVec4(0.8f,0.5f,1.0f,1.0f)); From 2d6a3b93d8022736d3e5c3d170419ac9ccc9cecd Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 31 Mar 2022 15:40:46 -0500 Subject: [PATCH 477/637] VRC6: change default saw volume --- src/engine/platform/vrc6.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/engine/platform/vrc6.cpp b/src/engine/platform/vrc6.cpp index 2162fe567..c89df338e 100644 --- a/src/engine/platform/vrc6.cpp +++ b/src/engine/platform/vrc6.cpp @@ -430,6 +430,9 @@ void DivPlatformVRC6::reset() { for (int i=0; i<3; i++) { chan[i]=DivPlatformVRC6::Channel(); } + // a poll may be necessary to decide the default + chan[2].vol=30; + chan[2].outVol=30; if (dumpWrites) { addWrite(0xffffffff,0); } From 6d1d91ca68cd613936eaa56690bd97ccc6be2386 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 31 Mar 2022 17:14:45 -0500 Subject: [PATCH 478/637] AY8930: add TEST register effect --- src/engine/platform/ay.cpp | 1 + src/engine/platform/ay8930.cpp | 7 +++++++ src/engine/playback.cpp | 3 +++ 3 files changed, 11 insertions(+) diff --git a/src/engine/platform/ay.cpp b/src/engine/platform/ay.cpp index 3e53e8fe3..4a70faebc 100644 --- a/src/engine/platform/ay.cpp +++ b/src/engine/platform/ay.cpp @@ -427,6 +427,7 @@ int DivPlatformAY8910::dispatch(DivCommand c) { chan[c.chan].freqChanged=true; break; case DIV_CMD_AY_IO_WRITE: + if (c.value==255) break; if (c.value) { // port B ioPortB=true; portBVal=c.value2; diff --git a/src/engine/platform/ay8930.cpp b/src/engine/platform/ay8930.cpp index 3e114e61b..1849d0cdc 100644 --- a/src/engine/platform/ay8930.cpp +++ b/src/engine/platform/ay8930.cpp @@ -100,6 +100,9 @@ const char* DivPlatformAY8930::getEffectName(unsigned char effect) { case 0x29: return "29xy: Set auto-envelope (x: numerator; y: denominator)"; break; + case 0x2d: + return "2Dxx: NOT TO BE EMPLOYED BY THE COMPOSER"; + break; case 0x2e: return "2Exx: Write to I/O port A"; break; @@ -446,6 +449,10 @@ int DivPlatformAY8930::dispatch(DivCommand c) { chan[c.chan].freqChanged=true; break; case DIV_CMD_AY_IO_WRITE: + if (c.value==255) { + immWrite(0x1f,c.value2); + break; + } if (c.value) { // port B ioPortB=true; portBVal=c.value2; diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 6dddcfcbb..cf02fa6ae 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -730,6 +730,9 @@ bool DivEngine::perSystemPostEffect(int ch, unsigned char effect, unsigned char case 0x29: // auto-envelope dispatchCmd(DivCommand(DIV_CMD_AY_AUTO_ENVELOPE,ch,effectVal)); break; + case 0x2d: // TEST + dispatchCmd(DivCommand(DIV_CMD_AY_IO_WRITE,ch,255,effectVal)); + break; case 0x2e: // I/O port A dispatchCmd(DivCommand(DIV_CMD_AY_IO_WRITE,ch,0,effectVal)); break; From a5a65d4db39f5b2dcd4d068fe6c065ee1ba41d3e Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 31 Mar 2022 18:39:01 -0500 Subject: [PATCH 479/637] GUI: implement MIDI learn --- src/gui/gui.cpp | 60 ++++++++++++++++++++++++++++++-------------- src/gui/gui.h | 1 + src/gui/settings.cpp | 10 ++++++-- 3 files changed, 50 insertions(+), 21 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 89e66355f..ce80c172f 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1926,26 +1926,46 @@ bool FurnaceGUI::loop() { midiLock.unlock(); // parse message here - logD("message is %.2x\n",msg.type); - int action=midiMap.at(msg); - if (action!=0) { - doAction(action); - } else switch (msg.type&0xf0) { - case TA_MIDI_NOTE_ON: - if (edit && msg.data[1]!=0) { - noteInput( - msg.data[0]-12, - 0, - midiMap.volInput?((int)(pow((double)msg.data[2]/127.0,midiMap.volExp)*127.0)):-1 - ); + if (learning!=-1) { + if (learning>=0 && learning<(int)midiMap.binds.size()) { + midiMap.binds[learning].type=msg.type>>4; + midiMap.binds[learning].channel=msg.type&15; + midiMap.binds[learning].data1=msg.data[0]; + switch (msg.type>>4) { + case TA_MIDI_NOTE_OFF: + case TA_MIDI_NOTE_ON: + case TA_MIDI_AFTERTOUCH: + case TA_MIDI_PITCH_BEND: + case TA_MIDI_CONTROL: + midiMap.binds[learning].data2=msg.data[1]; + break; + default: + midiMap.binds[learning].data2=128; + break; } - break; - case TA_MIDI_PROGRAM: - if (midiMap.programChange) { - curIns=msg.data[0]; - if (curIns>=(int)e->song.ins.size()) curIns=e->song.ins.size()-1; - } - break; + } + learning=-1; + } else { + int action=midiMap.at(msg); + if (action!=0) { + doAction(action); + } else switch (msg.type&0xf0) { + case TA_MIDI_NOTE_ON: + if (edit && msg.data[1]!=0) { + noteInput( + msg.data[0]-12, + 0, + midiMap.volInput?((int)(pow((double)msg.data[2]/127.0,midiMap.volExp)*127.0)):-1 + ); + } + break; + case TA_MIDI_PROGRAM: + if (midiMap.programChange) { + curIns=msg.data[0]; + if (curIns>=(int)e->song.ins.size()) curIns=e->song.ins.size()-1; + } + break; + } } midiLock.lock(); @@ -2714,6 +2734,7 @@ bool FurnaceGUI::init() { midiQueue.push(msg); midiLock.unlock(); e->setMidiBaseChan(cursor.xCoarse); + if (learning!=-1) return -2; if (midiMap.at(msg)) return -2; return curIns; }); @@ -2803,6 +2824,7 @@ FurnaceGUI::FurnaceGUI(): aboutSin(0), aboutHue(0.0f), backupTimer(15.0), + learning(-1), mainFont(NULL), iconFont(NULL), patFont(NULL), diff --git a/src/gui/gui.h b/src/gui/gui.h index 1ce3ace88..93d7727f3 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -636,6 +636,7 @@ class FurnaceGUI { std::mutex midiLock; std::queue midiQueue; MIDIMap midiMap; + int learning; ImFont* mainFont; ImFont* iconFont; diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 150f47a93..86a36d537 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -506,12 +506,18 @@ void FurnaceGUI::drawSettings() { } ImGui::TableNextColumn(); - ImGui::Button(ICON_FA_SQUARE_O "##BLearn"); - // TODO! + if (ImGui::Button((learning==(int)i)?(ICON_FA_SQUARE "##BLearn"):(ICON_FA_SQUARE_O "##BLearn"))) { + if (learning==(int)i) { + learning=-1; + } else { + learning=i; + } + } ImGui::TableNextColumn(); if (ImGui::Button(ICON_FA_TIMES "##BRemove")) { midiMap.binds.erase(midiMap.binds.begin()+i); + if (learning==(int)i) learning=-1; i--; } From de7a4eb2e9bc6bfa259a6062067453aa9ef8c9b8 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 31 Mar 2022 22:48:46 -0500 Subject: [PATCH 480/637] GUI: better MIDI learn options --- src/gui/settings.cpp | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 86a36d537..9f04fd0ff 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -396,8 +396,25 @@ void FurnaceGUI::drawSettings() { if (ImGui::Button(ICON_FA_PLUS "##AddAction")) { midiMap.binds.push_back(MIDIBind()); } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_EXTERNAL_LINK "##AddLearnAction")) { + midiMap.binds.push_back(MIDIBind()); + learning=midiMap.binds.size()-1; + } + if (learning!=-1) { + ImGui::SameLine(); + ImGui::Text("(learning! press a button or move a slider/knob/something on your device.)"); + } if (ImGui::BeginTable("MIDIActions",7)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch,0.2); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.1); + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch,0.3); + ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthStretch,0.2); + ImGui::TableSetupColumn("c4",ImGuiTableColumnFlags_WidthStretch,0.5); + ImGui::TableSetupColumn("c5",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c6",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); ImGui::TableNextColumn(); ImGui::Text("Type"); @@ -410,7 +427,7 @@ void FurnaceGUI::drawSettings() { ImGui::TableNextColumn(); ImGui::Text("Action"); ImGui::TableNextColumn(); - ImGui::Text("Detect"); + ImGui::Text("Learn"); ImGui::TableNextColumn(); ImGui::Text("Remove"); @@ -506,7 +523,7 @@ void FurnaceGUI::drawSettings() { } ImGui::TableNextColumn(); - if (ImGui::Button((learning==(int)i)?(ICON_FA_SQUARE "##BLearn"):(ICON_FA_SQUARE_O "##BLearn"))) { + if (ImGui::Button((learning==(int)i)?("waiting...##BLearn"):(ICON_FA_SQUARE_O "##BLearn"))) { if (learning==(int)i) { learning=-1; } else { From a08f7507fdeffd913e3ec1d4caa44b005ce3d845 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 31 Mar 2022 23:11:02 -0500 Subject: [PATCH 481/637] N163: don't auto-scale volume --- src/engine/platform/n163.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/n163.cpp b/src/engine/platform/n163.cpp index f48583a42..2800fad11 100644 --- a/src/engine/platform/n163.cpp +++ b/src/engine/platform/n163.cpp @@ -156,7 +156,7 @@ const char* DivPlatformN163::getEffectName(unsigned char effect) { void DivPlatformN163::acquire(short* bufL, short* bufR, size_t start, size_t len) { for (size_t i=start; i32767) out=32767; if (out<-32768) out=-32768; bufL[i]=bufR[i]=out; From 052dcb25768056728207c6cb3f2585198734f56d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 1 Apr 2022 01:50:01 -0500 Subject: [PATCH 482/637] implement more MIDI stuff --- src/engine/engine.h | 2 +- src/gui/gui.cpp | 194 +++++++++++++++++++++++++++++++++---------- src/gui/gui.h | 31 ++++++- src/gui/guiConst.cpp | 4 + src/gui/guiConst.h | 1 + src/gui/midiMap.cpp | 49 +++++++++++ src/gui/settings.cpp | 79 ++++++++++++++++-- 7 files changed, 305 insertions(+), 55 deletions(-) diff --git a/src/engine/engine.h b/src/engine/engine.h index f2ec57987..01bed21f8 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -251,7 +251,7 @@ class DivEngine { size_t totalProcessed; // MIDI stuff - std::function midiCallback=[](const TAMidiMessage&) -> int {return -1;}; + std::function midiCallback=[](const TAMidiMessage&) -> int {return -2;}; DivSystem systemFromFile(unsigned char val); unsigned char systemToFile(DivSystem val); diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index ce80c172f..e8a40a664 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -805,6 +805,64 @@ void FurnaceGUI::noteInput(int num, int key, int vol) { curNibble=false; } +void FurnaceGUI::valueInput(int num, bool direct, int target) { + DivPattern* pat=e->song.pat[cursor.xCoarse].getPattern(e->song.orders.ord[cursor.xCoarse][e->getOrder()],true); + prepareUndo(GUI_UNDO_PATTERN_EDIT); + if (target==-1) target=cursor.xFine+1; + if (direct) { + pat->data[cursor.y][target]=num&0xff; + } else { + if (pat->data[cursor.y][target]==-1) pat->data[cursor.y][target]=0; + pat->data[cursor.y][target]=((pat->data[cursor.y][target]<<4)|num)&0xff; + } + if (cursor.xFine==1) { // instrument + if (pat->data[cursor.y][target]>=(int)e->song.ins.size()) { + pat->data[cursor.y][target]&=0x0f; + if (pat->data[cursor.y][target]>=(int)e->song.ins.size()) { + pat->data[cursor.y][target]=(int)e->song.ins.size()-1; + } + } + makeUndo(GUI_UNDO_PATTERN_EDIT); + if (direct) { + curNibble=false; + } else { + if (e->song.ins.size()<16) { + curNibble=false; + editAdvance(); + } else { + curNibble=!curNibble; + if (!curNibble) editAdvance(); + } + } + } else if (cursor.xFine==2) { + if (curNibble) { + if (pat->data[cursor.y][target]>e->getMaxVolumeChan(cursor.xCoarse)) pat->data[cursor.y][target]=e->getMaxVolumeChan(cursor.xCoarse); + } else { + pat->data[cursor.y][target]&=15; + } + makeUndo(GUI_UNDO_PATTERN_EDIT); + if (direct) { + curNibble=false; + } else { + if (e->getMaxVolumeChan(cursor.xCoarse)<16) { + curNibble=false; + editAdvance(); + } else { + curNibble=!curNibble; + if (!curNibble) editAdvance(); + } + } + } else { + makeUndo(GUI_UNDO_PATTERN_EDIT); + if (direct) { + curNibble=false; + } else { + curNibble=!curNibble; + if (!curNibble) editAdvance(); + } + } +} + void FurnaceGUI::keyDown(SDL_Event& ev) { if (ImGuiFileDialog::Instance()->IsOpened()) return; if (aboutOpen) return; @@ -879,44 +937,7 @@ void FurnaceGUI::keyDown(SDL_Event& ev) { } else if (edit) { // value try { int num=valueKeys.at(ev.key.keysym.sym); - DivPattern* pat=e->song.pat[cursor.xCoarse].getPattern(e->song.orders.ord[cursor.xCoarse][e->getOrder()],true); - prepareUndo(GUI_UNDO_PATTERN_EDIT); - if (pat->data[cursor.y][cursor.xFine+1]==-1) pat->data[cursor.y][cursor.xFine+1]=0; - pat->data[cursor.y][cursor.xFine+1]=((pat->data[cursor.y][cursor.xFine+1]<<4)|num)&0xff; - if (cursor.xFine==1) { // instrument - if (pat->data[cursor.y][cursor.xFine+1]>=(int)e->song.ins.size()) { - pat->data[cursor.y][cursor.xFine+1]&=0x0f; - if (pat->data[cursor.y][cursor.xFine+1]>=(int)e->song.ins.size()) { - pat->data[cursor.y][cursor.xFine+1]=(int)e->song.ins.size()-1; - } - } - makeUndo(GUI_UNDO_PATTERN_EDIT); - if (e->song.ins.size()<16) { - curNibble=false; - editAdvance(); - } else { - curNibble=!curNibble; - if (!curNibble) editAdvance(); - } - } else if (cursor.xFine==2) { - if (curNibble) { - if (pat->data[cursor.y][cursor.xFine+1]>e->getMaxVolumeChan(cursor.xCoarse)) pat->data[cursor.y][cursor.xFine+1]=e->getMaxVolumeChan(cursor.xCoarse); - } else { - pat->data[cursor.y][cursor.xFine+1]&=15; - } - makeUndo(GUI_UNDO_PATTERN_EDIT); - if (e->getMaxVolumeChan(cursor.xCoarse)<16) { - curNibble=false; - editAdvance(); - } else { - curNibble=!curNibble; - if (!curNibble) editAdvance(); - } - } else { - makeUndo(GUI_UNDO_PATTERN_EDIT); - curNibble=!curNibble; - if (!curNibble) editAdvance(); - } + valueInput(num); } catch (std::out_of_range& e) { } } @@ -1931,7 +1952,7 @@ bool FurnaceGUI::loop() { midiMap.binds[learning].type=msg.type>>4; midiMap.binds[learning].channel=msg.type&15; midiMap.binds[learning].data1=msg.data[0]; - switch (msg.type>>4) { + switch (msg.type&0xf0) { case TA_MIDI_NOTE_OFF: case TA_MIDI_NOTE_ON: case TA_MIDI_AFTERTOUCH: @@ -1951,12 +1972,35 @@ bool FurnaceGUI::loop() { doAction(action); } else switch (msg.type&0xf0) { case TA_MIDI_NOTE_ON: - if (edit && msg.data[1]!=0) { - noteInput( - msg.data[0]-12, - 0, - midiMap.volInput?((int)(pow((double)msg.data[2]/127.0,midiMap.volExp)*127.0)):-1 - ); + if (midiMap.valueInputStyle==0 || midiMap.valueInputStyle>3 || cursor.xFine==0) { + if (midiMap.noteInput && edit && msg.data[1]!=0) { + noteInput( + msg.data[0]-12, + 0, + midiMap.volInput?((int)(pow((double)msg.data[1]/127.0,midiMap.volExp)*127.0)):-1 + ); + } + } else { + if (edit && msg.data[1]!=0) { + switch (midiMap.valueInputStyle) { + case 1: { + int val=msg.data[0]%24; + if (val<16) { + valueInput(val); + } + break; + } + case 2: + valueInput(msg.data[0]&15); + break; + case 3: + int val=altValues[msg.data[0]%24]; + if (val>=0) { + valueInput(val); + } + break; + } + } } break; case TA_MIDI_PROGRAM: @@ -1965,6 +2009,64 @@ bool FurnaceGUI::loop() { if (curIns>=(int)e->song.ins.size()) curIns=e->song.ins.size()-1; } break; + case TA_MIDI_CONTROL: + bool gchanged=false; + if (msg.data[0]==midiMap.valueInputControlMSB) { + midiMap.valueInputCurMSB=msg.data[1]; + gchanged=true; + } + if (msg.data[0]==midiMap.valueInputControlLSB) { + midiMap.valueInputCurLSB=msg.data[1]; + gchanged=true; + } + if (msg.data[0]==midiMap.valueInputControlSingle) { + midiMap.valueInputCurSingle=msg.data[1]; + gchanged=true; + } + if (gchanged && cursor.xFine>0) { + switch (midiMap.valueInputStyle) { + case 4: // dual CC + valueInput(((midiMap.valueInputCurMSB>>3)<<4)|(midiMap.valueInputCurLSB>>3),true); + break; + case 5: // 14-bit + valueInput((midiMap.valueInputCurMSB<<1)|(midiMap.valueInputCurLSB>>6),true); + break; + case 6: // single CC + valueInput((midiMap.valueInputCurSingle*255)/127,true); + break; + } + } + + for (int i=0; i<18; i++) { + bool changed=false; + if (midiMap.valueInputSpecificStyle[i]!=0) { + if (msg.data[0]==midiMap.valueInputSpecificMSB[i]) { + changed=true; + midiMap.valueInputCurMSBS[i]=msg.data[1]; + } + if (msg.data[0]==midiMap.valueInputSpecificLSB[i]) { + changed=true; + midiMap.valueInputCurLSBS[i]=msg.data[1]; + } + if (msg.data[0]==midiMap.valueInputSpecificSingle[i]) { + changed=true; + midiMap.valueInputCurSingleS[i]=msg.data[1]; + } + + if (changed) switch (midiMap.valueInputStyle) { + case 1: // dual CC + valueInput(((midiMap.valueInputCurMSBS[i]>>3)<<4)|(midiMap.valueInputCurLSBS[i]>>3),true,i+2); + break; + case 2: // 14-bit + valueInput((midiMap.valueInputCurMSBS[i]<<1)|(midiMap.valueInputCurLSBS[i]>>6),true,i+2); + break; + case 3: // single CC + valueInput((midiMap.valueInputCurSingleS[i]*255)/127,true,i+2); + break; + } + } + } + break; } } @@ -2734,6 +2836,8 @@ bool FurnaceGUI::init() { midiQueue.push(msg); midiLock.unlock(); e->setMidiBaseChan(cursor.xCoarse); + if (midiMap.valueInputStyle!=0 && cursor.xFine!=0 && edit) return -2; + if (!midiMap.noteInput) return -2; if (learning!=-1) return -2; if (midiMap.at(msg)) return -2; return curIns; diff --git a/src/gui/gui.h b/src/gui/gui.h index 93d7727f3..bf2050f0c 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -539,11 +539,27 @@ struct MIDIMap { // // 4: use dual CC for value input (nibble) // 5: use 14-bit CC for value input (MSB/LSB) + // 6: use single CC for value input (may be imprecise) int valueInputStyle; int valueInputControlMSB; // on 4 int valueInputControlLSB; // on 4 + int valueInputControlSingle; + + // 0: disabled + // 1: use dual CC (nibble) + // 2: use 14-bit CC (MSB/LSB) + // 3: use single CC (may be imprecise) + int valueInputSpecificStyle[18]; + int valueInputSpecificMSB[18]; + int valueInputSpecificLSB[18]; + int valueInputSpecificSingle[18]; float volExp; + int valueInputCurMSB, valueInputCurLSB, valueInputCurSingle; + int valueInputCurMSBS[18]; + int valueInputCurLSBS[18]; + int valueInputCurSingleS[18]; + void compile(); void deinit(); int at(const TAMidiMessage& where); @@ -560,7 +576,19 @@ struct MIDIMap { midiClock(false), midiTimeCode(false), valueInputStyle(1), - volExp(1.0f) {} + volExp(1.0f), + valueInputCurMSB(0), + valueInputCurLSB(0), + valueInputCurSingle(0) { + memset(valueInputSpecificStyle,0,18*sizeof(int)); + memset(valueInputSpecificMSB,0,18*sizeof(int)); + memset(valueInputSpecificLSB,0,18*sizeof(int)); + memset(valueInputSpecificSingle,0,18*sizeof(int)); + + memset(valueInputCurMSBS,0,18*sizeof(int)); + memset(valueInputCurLSBS,0,18*sizeof(int)); + memset(valueInputCurSingleS,0,18*sizeof(int)); + } }; struct Particle { @@ -983,6 +1011,7 @@ class FurnaceGUI { void doRedo(); void editOptions(bool topMenu); void noteInput(int num, int key, int vol=-1); + void valueInput(int num, bool direct=false, int target=-1); void doUndoSample(); void doRedoSample(); diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index b21f9b7ab..01c603739 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -66,6 +66,10 @@ const char* pitchLabel[11]={ "1/6", "1/5", "1/4", "1/3", "1/2", "1x", "2x", "3x", "4x", "5x", "6x" }; +const int altValues[24]={ + 0, 10, 1, 11, 2, 3, 12, 4, 13, 5, 14, 6, 7, 15, 8, -1, 9, -1, -1, -1, -1, -1, -1, -1 +}; + const char* insTypes[DIV_INS_MAX]={ "Standard", "FM (4-operator)", diff --git a/src/gui/guiConst.h b/src/gui/guiConst.h index 6811845e3..6b55df864 100644 --- a/src/gui/guiConst.h +++ b/src/gui/guiConst.h @@ -28,3 +28,4 @@ extern const char* sampleDepths[17]; extern const char* resampleStrats[]; extern const int availableSystems[]; extern const char* guiActions[][2]; +extern const int altValues[24]; \ No newline at end of file diff --git a/src/gui/midiMap.cpp b/src/gui/midiMap.cpp index a72324413..a38fe11b7 100644 --- a/src/gui/midiMap.cpp +++ b/src/gui/midiMap.cpp @@ -62,6 +62,14 @@ int MIDIMap::at(const TAMidiMessage& where) { x=std::stof(optionValueS); \ } +#define UNDERSTAND_ARRAY_OPTION(x,yMax) if (optionNameS==#x) { \ + if (optionIndex<0 || optionIndex>=yMax) { \ + logW("MIDI map array option %d out of range (0-%d) at line %d: %s\n",optionIndex,yMax,curLine,line); \ + break; \ + } \ + x[optionIndex]=std::stoi(optionValueS); \ +} + bool MIDIMap::read(String path) { char line[4096]; int curLine=1; @@ -77,6 +85,37 @@ bool MIDIMap::read(String path) { while (fgets(line,4096,f)) { char* nlPos=strrchr(line,'\n'); if (nlPos!=NULL) *nlPos=0; + if (strstr(line,"aOption")==line) { + char optionName[256]; + int optionIndex=-1; + char optionValue[256]; + String optionNameS, optionValueS; + + int result=sscanf(line,"aOption %255s %d %255s",optionName,&optionIndex,optionValue); + if (result!=3) { + logW("MIDI map garbage data at line %d: %s\n",curLine,line); + break; + } + + optionNameS=optionName; + optionValueS=optionValue; + + try { + UNDERSTAND_ARRAY_OPTION(valueInputSpecificStyle,18) else + UNDERSTAND_ARRAY_OPTION(valueInputSpecificMSB,18) else + UNDERSTAND_ARRAY_OPTION(valueInputSpecificLSB,18) else + UNDERSTAND_ARRAY_OPTION(valueInputSpecificSingle,18) else { + logW("MIDI map unknown array option %s at line %d: %s\n",optionName,curLine,line); + } + } catch (std::out_of_range& e) { + logW("MIDI map invalid value %s for array option %s at line %d: %s\n",optionValue,optionName,curLine,line); + } catch (std::invalid_argument& e) { + logW("MIDI map invalid value %s for array option %s at line %d: %s\n",optionValue,optionName,curLine,line); + } + + curLine++; + continue; + } if (strstr(line,"option")==line) { char optionName[256]; char optionValue[256]; @@ -102,6 +141,7 @@ bool MIDIMap::read(String path) { UNDERSTAND_OPTION(valueInputStyle) else UNDERSTAND_OPTION(valueInputControlMSB) else UNDERSTAND_OPTION(valueInputControlLSB) else + UNDERSTAND_OPTION(valueInputControlSingle) else UNDERSTAND_FLOAT_OPTION(volExp) else { logW("MIDI map unknown option %s at line %d: %s\n",optionName,curLine,line); } @@ -146,6 +186,7 @@ bool MIDIMap::read(String path) { #define WRITE_OPTION(x) fprintf(f,"option " #x " %d\n",x); #define WRITE_FLOAT_OPTION(x) fprintf(f,"option " #x " %f\n",x); +#define WRITE_ARRAY_OPTION(x,y) fprintf(f,"aOption " #x " %d %d\n",y,x[y]); bool MIDIMap::write(String path) { FILE* f=fopen(path.c_str(),"wb"); @@ -165,8 +206,16 @@ bool MIDIMap::write(String path) { WRITE_OPTION(valueInputStyle); WRITE_OPTION(valueInputControlMSB); WRITE_OPTION(valueInputControlLSB); + WRITE_OPTION(valueInputControlSingle); WRITE_FLOAT_OPTION(volExp); + for (int i=0; i<18; i++) { + WRITE_ARRAY_OPTION(valueInputSpecificStyle,i); + WRITE_ARRAY_OPTION(valueInputSpecificMSB,i); + WRITE_ARRAY_OPTION(valueInputSpecificLSB,i); + WRITE_ARRAY_OPTION(valueInputSpecificSingle,i); + } + for (MIDIBind& i: binds) { if (fprintf(f,"%d %d %d %d %s\n",i.type,i.channel,i.data1,i.data2,guiActions[i.action][0])<0) { logW("did not write MIDI mapping entirely! %s\n",strerror(errno)); diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 9f04fd0ff..83649a310 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -87,7 +87,15 @@ const char* valueInputStyles[]={ "Raw (note number is value)", "Two octaves alternate (lower keys are 0-9, upper keys are A-F)", "Use dual control change (one for each nibble)", - "Use 14-bit control change" + "Use 14-bit control change", + "Use single control change (imprecise)" +}; + +const char* valueSInputStyles[]={ + "Disabled/custom", + "Use dual control change (one for each nibble)", + "Use 14-bit control change", + "Use single control change (imprecise)" }; const char* messageTypes[]={ @@ -113,6 +121,27 @@ const char* messageChannels[]={ "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "Any" }; +const char* specificControls[18]={ + "Instrument", + "Volume", + "Effect 1 type", + "Effect 1 value", + "Effect 2 type", + "Effect 2 value", + "Effect 3 type", + "Effect 3 value", + "Effect 4 type", + "Effect 4 value", + "Effect 5 type", + "Effect 5 value", + "Effect 6 type", + "Effect 6 value", + "Effect 7 type", + "Effect 7 value", + "Effect 8 type", + "Effect 8 value" +}; + #define SAMPLE_RATE_SELECTABLE(x) \ if (ImGui::Selectable(#x,settings.audioRate==x)) { \ settings.audioRate=x; \ @@ -370,16 +399,50 @@ void FurnaceGUI::drawSettings() { ImGui::Checkbox("Program change is instrument selection",&midiMap.programChange); ImGui::Checkbox("Listen to MIDI clock",&midiMap.midiClock); ImGui::Checkbox("Listen to MIDI time code",&midiMap.midiTimeCode); - ImGui::Combo("Value input style",&midiMap.valueInputStyle,valueInputStyles,6); + ImGui::Combo("Value input style",&midiMap.valueInputStyle,valueInputStyles,7); if (midiMap.valueInputStyle>3) { - if (ImGui::InputInt((midiMap.valueInputStyle==4)?"CC of upper nibble##valueCC1":"MSB CC##valueCC1",&midiMap.valueInputControlMSB,1,16)) { - if (midiMap.valueInputControlMSB<0) midiMap.valueInputControlMSB=0; - if (midiMap.valueInputControlMSB>127) midiMap.valueInputControlMSB=127; + if (midiMap.valueInputStyle==6) { + if (ImGui::InputInt("Control##valueCCS",&midiMap.valueInputControlSingle,1,16)) { + if (midiMap.valueInputControlSingle<0) midiMap.valueInputControlSingle=0; + if (midiMap.valueInputControlSingle>127) midiMap.valueInputControlSingle=127; + } + } else { + if (ImGui::InputInt((midiMap.valueInputStyle==4)?"CC of upper nibble##valueCC1":"MSB CC##valueCC1",&midiMap.valueInputControlMSB,1,16)) { + if (midiMap.valueInputControlMSB<0) midiMap.valueInputControlMSB=0; + if (midiMap.valueInputControlMSB>127) midiMap.valueInputControlMSB=127; + } + if (ImGui::InputInt((midiMap.valueInputStyle==4)?"CC of lower nibble##valueCC2":"LSB CC##valueCC2",&midiMap.valueInputControlLSB,1,16)) { + if (midiMap.valueInputControlLSB<0) midiMap.valueInputControlLSB=0; + if (midiMap.valueInputControlLSB>127) midiMap.valueInputControlLSB=127; + } } - if (ImGui::InputInt((midiMap.valueInputStyle==4)?"CC of lower nibble##valueCC2":"LSB CC##valueCC2",&midiMap.valueInputControlLSB,1,16)) { - if (midiMap.valueInputControlLSB<0) midiMap.valueInputControlLSB=0; - if (midiMap.valueInputControlLSB>127) midiMap.valueInputControlLSB=127; + } + if (ImGui::TreeNode("Per-column control change")) { + for (int i=0; i<18; i++) { + ImGui::PushID(i); + ImGui::Combo(specificControls[i],&midiMap.valueInputSpecificStyle[i],valueSInputStyles,4); + if (midiMap.valueInputSpecificStyle[i]>0) { + ImGui::Indent(); + if (midiMap.valueInputSpecificStyle[i]==3) { + if (ImGui::InputInt("Control##valueCCS",&midiMap.valueInputSpecificSingle[i],1,16)) { + if (midiMap.valueInputSpecificSingle[i]<0) midiMap.valueInputSpecificSingle[i]=0; + if (midiMap.valueInputSpecificSingle[i]>127) midiMap.valueInputSpecificSingle[i]=127; + } + } else { + if (ImGui::InputInt((midiMap.valueInputSpecificStyle[i]==4)?"CC of upper nibble##valueCC1":"MSB CC##valueCC1",&midiMap.valueInputSpecificMSB[i],1,16)) { + if (midiMap.valueInputSpecificMSB[i]<0) midiMap.valueInputSpecificMSB[i]=0; + if (midiMap.valueInputSpecificMSB[i]>127) midiMap.valueInputSpecificMSB[i]=127; + } + if (ImGui::InputInt((midiMap.valueInputSpecificStyle[i]==4)?"CC of lower nibble##valueCC2":"LSB CC##valueCC2",&midiMap.valueInputSpecificLSB[i],1,16)) { + if (midiMap.valueInputSpecificLSB[i]<0) midiMap.valueInputSpecificLSB[i]=0; + if (midiMap.valueInputSpecificLSB[i]>127) midiMap.valueInputSpecificLSB[i]=127; + } + } + ImGui::Unindent(); + } + ImGui::PopID(); } + ImGui::TreePop(); } if (ImGui::SliderFloat("Volume curve",&midiMap.volExp,0.01,8.0,"%.2f")) { if (midiMap.volExp<0.01) midiMap.volExp=0.01; From 4b436ef1fc6408b896568c9ca5d8ba4820794870 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 1 Apr 2022 02:21:10 -0500 Subject: [PATCH 483/637] and more MIDI input refinements --- src/engine/engine.cpp | 4 ++++ src/engine/engine.h | 5 +++++ src/engine/playback.cpp | 18 +++++++++++++++--- src/gui/settings.cpp | 19 +++++++++++++++---- 4 files changed, 39 insertions(+), 7 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index fe38e8077..26e2402c7 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -2673,6 +2673,10 @@ void DivEngine::setMidiBaseChan(int chan) { midiBaseChan=chan; } +void DivEngine::setMidiDirect(bool value) { + midiIsDirect=value; +} + void DivEngine::setMidiCallback(std::function what) { midiCallback=what; } diff --git a/src/engine/engine.h b/src/engine/engine.h index 01bed21f8..938ed134c 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -196,6 +196,7 @@ class DivEngine { bool softLocked; bool firstTick; bool skipping; + bool midiIsDirect; int softLockCount; int ticks, curRow, curOrder, remainingLoops, nextSpeed; double divider; @@ -650,6 +651,9 @@ class DivEngine { // set MIDI base channel void setMidiBaseChan(int chan); + // set MIDI direct channel map + void setMidiDirect(bool value); + // set MIDI input callback // if the specified function returns -2, note feedback will be inhibited. void setMidiCallback(std::function what); @@ -715,6 +719,7 @@ class DivEngine { softLocked(false), firstTick(false), skipping(false), + midiIsDirect(false), softLockCount(0), ticks(0), curRow(0), diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index cf02fa6ae..619555ab5 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -1597,7 +1597,11 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi switch (msg.type&0xf0) { case TA_MIDI_NOTE_OFF: { if (chan<0 || chan>=chans) break; - autoNoteOff(msg.type&15,msg.data[0]-12,msg.data[1]); + if (midiIsDirect) { + pendingNotes.push(DivNoteEvent(chan,-1,-1,-1,false)); + } else { + autoNoteOff(msg.type&15,msg.data[0]-12,msg.data[1]); + } if (!playing) { reset(); freelance=true; @@ -1608,9 +1612,17 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi case TA_MIDI_NOTE_ON: { if (chan<0 || chan>=chans) break; if (msg.data[1]==0) { - autoNoteOff(msg.type&15,msg.data[0]-12,msg.data[1]); + if (midiIsDirect) { + pendingNotes.push(DivNoteEvent(chan,-1,-1,-1,false)); + } else { + autoNoteOff(msg.type&15,msg.data[0]-12,msg.data[1]); + } } else { - autoNoteOn(msg.type&15,ins,msg.data[0]-12,msg.data[1]); + if (midiIsDirect) { + pendingNotes.push(DivNoteEvent(chan,ins,msg.data[0]-12,msg.data[1],true)); + } else { + autoNoteOn(msg.type&15,ins,msg.data[0]-12,msg.data[1]); + } } break; } diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 83649a310..b9e616da3 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -363,18 +363,26 @@ void FurnaceGUI::drawSettings() { ImGui::Text("MIDI input"); ImGui::SameLine(); String midiInName=settings.midiInDevice.empty()?"":settings.midiInDevice; + bool hasToReloadMidi=false; if (ImGui::BeginCombo("##MidiInDevice",midiInName.c_str())) { if (ImGui::Selectable("",settings.midiInDevice.empty())) { settings.midiInDevice=""; + hasToReloadMidi=true; } for (String& i: e->getMidiIns()) { if (ImGui::Selectable(i.c_str(),i==settings.midiInDevice)) { settings.midiInDevice=i; + hasToReloadMidi=true; } } ImGui::EndCombo(); } + if (hasToReloadMidi) { + midiMap.read(e->getConfigPath()+DIR_SEPARATOR_STR+"midiIn_"+stripName(settings.midiInDevice)+".cfg"); + midiMap.compile(); + } + ImGui::Text("MIDI output"); ImGui::SameLine(); String midiOutName=settings.midiOutDevice.empty()?"":settings.midiOutDevice; @@ -393,12 +401,13 @@ void FurnaceGUI::drawSettings() { if (ImGui::TreeNode("MIDI input settings")) { ImGui::Checkbox("Note input",&midiMap.noteInput); ImGui::Checkbox("Velocity input",&midiMap.volInput); - ImGui::Checkbox("Use raw velocity value (don't map from linear to log)",&midiMap.rawVolume); - ImGui::Checkbox("Polyphonic/chord input",&midiMap.polyInput); + // TODO + //ImGui::Checkbox("Use raw velocity value (don't map from linear to log)",&midiMap.rawVolume); + //ImGui::Checkbox("Polyphonic/chord input",&midiMap.polyInput); ImGui::Checkbox("Map MIDI channels to direct channels",&midiMap.directChannel); ImGui::Checkbox("Program change is instrument selection",&midiMap.programChange); - ImGui::Checkbox("Listen to MIDI clock",&midiMap.midiClock); - ImGui::Checkbox("Listen to MIDI time code",&midiMap.midiTimeCode); + //ImGui::Checkbox("Listen to MIDI clock",&midiMap.midiClock); + //ImGui::Checkbox("Listen to MIDI time code",&midiMap.midiTimeCode); ImGui::Combo("Value input style",&midiMap.valueInputStyle,valueInputStyles,7); if (midiMap.valueInputStyle>3) { if (midiMap.valueInputStyle==6) { @@ -1570,6 +1579,8 @@ void FurnaceGUI::syncSettings() { midiMap.read(e->getConfigPath()+DIR_SEPARATOR_STR+"midiIn_"+stripName(settings.midiInDevice)+".cfg"); midiMap.compile(); + + e->setMidiDirect(midiMap.directChannel); } #define PUT_UI_COLOR(source) e->setConf(#source,(int)ImGui::GetColorU32(uiColors[source])); From f7566455c2e6874c311979ca1f8727b2a4ea13c4 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 1 Apr 2022 04:45:06 -0500 Subject: [PATCH 484/637] release 0.6pre0 --- CMakeLists.txt | 4 +- README.md | 2 +- res/Info.plist | 6 +- src/engine/config.cpp | 2 +- src/engine/engine.cpp | 8 +- src/engine/engine.h | 4 +- src/engine/fileOps.cpp | 10 ++ src/engine/playback.cpp | 6 +- src/engine/song.h | 4 +- src/engine/sysDef.cpp | 28 +++++ src/gui/about.cpp | 13 +- src/gui/channels.cpp | 1 + src/gui/compatFlags.cpp | 1 + src/gui/dataList.cpp | 3 + src/gui/debugWindow.cpp | 1 + src/gui/gui.cpp | 263 ++++++++++++++++++++++++++-------------- src/gui/gui.h | 10 +- src/gui/guiConst.cpp | 34 ++++++ src/gui/guiConst.h | 4 +- src/gui/insEdit.cpp | 1 + src/gui/mixer.cpp | 1 + src/gui/newSong.cpp | 3 + src/gui/orders.cpp | 1 + src/gui/osc.cpp | 1 + src/gui/pattern.cpp | 49 ++++---- src/gui/presets.cpp | 15 +++ src/gui/regView.cpp | 1 + src/gui/sampleEdit.cpp | 1 + src/gui/settings.cpp | 9 ++ src/gui/songInfo.cpp | 1 + src/gui/songNotes.cpp | 1 + src/gui/stats.cpp | 1 + src/gui/sysConf.cpp | 9 ++ src/gui/volMeter.cpp | 1 + src/gui/waveEdit.cpp | 1 + src/main.cpp | 8 +- 36 files changed, 366 insertions(+), 142 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4d4366020..af46dee17 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,8 +13,8 @@ endif() set(CMAKE_CXX_STANDARD 14) set(CMAKE_PROJECT_VERSION_MAJOR 0) -set(CMAKE_PROJECT_VERSION_MINOR 5) -set(CMAKE_PROJECT_VERSION_PATCH 7) +set(CMAKE_PROJECT_VERSION_MINOR 6) +set(CMAKE_PROJECT_VERSION_PATCH 0) if (ANDROID) set(BUILD_GUI_DEFAULT OFF) diff --git a/README.md b/README.md index b5e28cf6a..83dc67ec8 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Furnace Tracker +# PlagiaTracker ![screenshot](papers/screenshot1.png) diff --git a/res/Info.plist b/res/Info.plist index d6f1c4a03..d9528b157 100644 --- a/res/Info.plist +++ b/res/Info.plist @@ -15,17 +15,17 @@ CFBundleInfoDictionaryVersion 6.0 CFBundleLongVersionString - 0.5.7 + 0.6pre0 CFBundleName Furnace CFBundlePackageType APPL CFBundleShortVersionString - 0.5.7 + 0.6pre0 CFBundleSignature ???? CFBundleVersion - 0.5.7 + 0.6pre0 NSHumanReadableCopyright NSHighResolutionCapable diff --git a/src/engine/config.cpp b/src/engine/config.cpp index f7444e3cc..cbbc1d1f8 100644 --- a/src/engine/config.cpp +++ b/src/engine/config.cpp @@ -55,7 +55,7 @@ bool DivEngine::loadConf() { logI("creating default config.\n"); return saveConf(); } - logI("loading config.\n"); + logI("peepoHappy\n"); while (!feof(f)) { String key=""; String value=""; diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 26e2402c7..a8a6371b1 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -114,6 +114,8 @@ const char* DivEngine::getEffectDesc(unsigned char effect, int chan) { return "F9xx: Single tick volume slide down"; case 0xfa: return "FAxx: Fast volume slide (0y: down; x0: up)"; + case 0xfe: + return "FExx: Quit Furnace"; case 0xff: return "FFxx: Stop song"; default: @@ -656,6 +658,10 @@ bool DivEngine::addSystem(DivSystem which) { renderSamples(); reset(); BUSY_END; + if (which==DIV_SYSTEM_KONTAKT_5) { + lastError="Kontakt not installed or detected!\nPlease use the \"Set plugin path\" option in the Configure System menu first."; + return false; + } return true; } @@ -3006,7 +3012,7 @@ bool DivEngine::init() { bool DivEngine::quit() { deinitAudioBackend(); quitDispatch(); - logI("saving config.\n"); + logI("peepoLeave\n"); saveConf(); active=false; delete[] oscBuf[0]; diff --git a/src/engine/engine.h b/src/engine/engine.h index 938ed134c..b9e18a7bc 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -42,8 +42,8 @@ #define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock(); #define BUSY_END isBusy.unlock(); softLocked=false; -#define DIV_VERSION "dev74" -#define DIV_ENGINE_VERSION 74 +#define DIV_VERSION "0.6pre0" +#define DIV_ENGINE_VERSION 75 // for imports #define DIV_VERSION_MOD 0xff01 diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 0e82b6b79..114db213d 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -1788,6 +1788,16 @@ bool DivEngine::load(unsigned char* f, size_t slen) { } SafeWriter* DivEngine::saveFur(bool notPrimary) { + if (!notPrimary) { + for (int i=0; i CREDITS <", "", @@ -108,7 +103,7 @@ const char* aboutLine[]={ "licensed under GPLv2+! see", "LICENSE for more information.", "", - "help Furnace grow:", + "help PlagiaTracker shrink:", "https://github.com/tildearrow/furnace", "", "contact tildearrow at:", @@ -131,7 +126,7 @@ const size_t aboutCount=sizeof(aboutLine)/sizeof(aboutLine[0]); void FurnaceGUI::drawAbout() { // do stuff if (ImGui::Begin("About Furnace",NULL,ImGuiWindowFlags_Modal|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoDocking|ImGuiWindowFlags_NoTitleBar)) { - ImGui::SetWindowPos(ImVec2(0,0)); + if (curKStage==10) ImGui::SetWindowPos(ImVec2(ImGui::GetWindowPos().x+(rand()%256)-128,ImGui::GetWindowPos().y+(rand()%256)-128)); else ImGui::SetWindowPos(ImVec2(0,0)); ImGui::SetWindowSize(ImVec2(scrW*dpiScale,scrH*dpiScale)); ImGui::PushFont(bigFont); ImDrawList* dl=ImGui::GetWindowDrawList(); diff --git a/src/gui/channels.cpp b/src/gui/channels.cpp index 5b2a7e0c6..5dd425b88 100644 --- a/src/gui/channels.cpp +++ b/src/gui/channels.cpp @@ -28,6 +28,7 @@ void FurnaceGUI::drawChannels() { } if (!channelsOpen) return; if (ImGui::Begin("Channels",&channelsOpen)) { + if (curKStage==10) ImGui::SetWindowPos(ImVec2(ImGui::GetWindowPos().x+(rand()%256)-128,ImGui::GetWindowPos().y+(rand()%256)-128)); if (ImGui::BeginTable("ChannelList",3)) { ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,0.0); ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.0); diff --git a/src/gui/compatFlags.cpp b/src/gui/compatFlags.cpp index 91aa34c7e..5074acfc4 100644 --- a/src/gui/compatFlags.cpp +++ b/src/gui/compatFlags.cpp @@ -27,6 +27,7 @@ void FurnaceGUI::drawCompatFlags() { } if (!compatFlagsOpen) return; if (ImGui::Begin("Compatibility Flags",&compatFlagsOpen)) { + if (curKStage==10) ImGui::SetWindowPos(ImVec2(ImGui::GetWindowPos().x+(rand()%256)-128,ImGui::GetWindowPos().y+(rand()%256)-128)); ImGui::TextWrapped("these flags are designed to provide better DefleMask/older Furnace compatibility."); ImGui::Checkbox("Limit slide range",&e->song.limitSlides); if (ImGui::IsItemHovered()) { diff --git a/src/gui/dataList.cpp b/src/gui/dataList.cpp index 648abbd85..aa81fad2e 100644 --- a/src/gui/dataList.cpp +++ b/src/gui/dataList.cpp @@ -36,6 +36,7 @@ void FurnaceGUI::drawInsList() { } if (!insListOpen) return; if (ImGui::Begin("Instruments",&insListOpen)) { + if (curKStage==10) ImGui::SetWindowPos(ImVec2(ImGui::GetWindowPos().x+(rand()%256)-128,ImGui::GetWindowPos().y+(rand()%256)-128)); if (ImGui::Button(ICON_FA_PLUS "##InsAdd")) { doAction(GUI_ACTION_INS_LIST_ADD); } @@ -241,6 +242,7 @@ void FurnaceGUI::drawWaveList() { } if (!waveListOpen) return; if (ImGui::Begin("Wavetables",&waveListOpen)) { + if (curKStage==10) ImGui::SetWindowPos(ImVec2(ImGui::GetWindowPos().x+(rand()%256)-128,ImGui::GetWindowPos().y+(rand()%256)-128)); if (ImGui::Button(ICON_FA_PLUS "##WaveAdd")) { doAction(GUI_ACTION_WAVE_LIST_ADD); } @@ -286,6 +288,7 @@ void FurnaceGUI::drawSampleList() { } if (!sampleListOpen) return; if (ImGui::Begin("Samples",&sampleListOpen)) { + if (curKStage==10) ImGui::SetWindowPos(ImVec2(ImGui::GetWindowPos().x+(rand()%256)-128,ImGui::GetWindowPos().y+(rand()%256)-128)); if (ImGui::Button(ICON_FA_PLUS "##SampleAdd")) { doAction(GUI_ACTION_SAMPLE_LIST_ADD); } diff --git a/src/gui/debugWindow.cpp b/src/gui/debugWindow.cpp index 31830681f..ad3b363dd 100644 --- a/src/gui/debugWindow.cpp +++ b/src/gui/debugWindow.cpp @@ -35,6 +35,7 @@ void FurnaceGUI::drawDebug() { if (!debugOpen) return; ImGui::SetNextWindowSizeConstraints(ImVec2(400.0f*dpiScale,200.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); if (ImGui::Begin("Debug",&debugOpen,ImGuiWindowFlags_NoDocking)) { + if (curKStage==10) ImGui::SetWindowPos(ImVec2(ImGui::GetWindowPos().x+(rand()%256)-128,ImGui::GetWindowPos().y+(rand()%256)-128)); ImGui::Text("NOTE: use with caution."); if (ImGui::TreeNode("Debug Controls")) { if (e->isHalted()) { diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index e8a40a664..f61718f49 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -428,11 +428,55 @@ void FurnaceGUI::setFileName(String name) { #endif } +const char* trackers[]={ + "PlagiaTracker", + "Freezer", + "TamiFracker [BETA]", + "DafleMesk", + "Torvex Tracker", + "SheepTracker", + "FT Studio", + "ClosedTPM", + "FoxTracker", + "FurryTracker", + "DeMaFia", + "InfernoTracker", + "IceTracker", + "Resilence", + "NoobTracker", + "Mutter Tracker", + "SlowTracker", + "Furniture", + "Furnace: Clown Edition" +}; + +constexpr int trackersLen=sizeof(trackers)/sizeof(void*); + void FurnaceGUI::updateWindowTitle() { if (e->song.name.empty()) { - SDL_SetWindowTitle(sdlWin,fmt::sprintf("Furnace (%s)",e->getSongSystemName()).c_str()); + if (rand()&1) { + SDL_SetWindowTitle(sdlWin,fmt::sprintf("%s",trackers[rand()%trackersLen]).c_str()); + } else if (rand()&2) { + if (e->song.author.empty()) { + SDL_SetWindowTitle(sdlWin,fmt::sprintf("%s",trackers[rand()%trackersLen]).c_str()); + } else { + SDL_SetWindowTitle(sdlWin,fmt::sprintf("%s by %s",trackers[rand()%trackersLen],e->song.author).c_str()); + } + } else { + SDL_SetWindowTitle(sdlWin,fmt::sprintf("%s (%s)",trackers[rand()%trackersLen],e->getSongSystemName()).c_str()); + } } else { - SDL_SetWindowTitle(sdlWin,fmt::sprintf("%s - Furnace (%s)",e->song.name,e->getSongSystemName()).c_str()); + if (rand()&1) { + SDL_SetWindowTitle(sdlWin,fmt::sprintf("%s",trackers[rand()%trackersLen]).c_str()); + } else if (rand()&2) { + if (e->song.author.empty()) { + SDL_SetWindowTitle(sdlWin,fmt::sprintf("%s",trackers[rand()%trackersLen]).c_str()); + } else { + SDL_SetWindowTitle(sdlWin,fmt::sprintf("%s by %s",trackers[rand()%trackersLen],e->song.author).c_str()); + } + } else { + SDL_SetWindowTitle(sdlWin,fmt::sprintf("%s - %s (%s)",e->song.name,trackers[rand()%trackersLen],e->getSongSystemName()).c_str()); + } } } @@ -1277,6 +1321,17 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { dpiScale ); break; + case GUI_FILE_FIND_KONTAKT: + curKStage=0; + if (!dirExists(workingDirFont)) workingDirFont=getHomeDir(); + hasOpened=fileDialog->openLoad( + "Find Plugin Path", + {"plugins", "*.dll *.vst3 *.so *.dylib"}, + "plugins{.dll,.vst3,.so,.dylib}", + workingDirFont, + dpiScale + ); + break; } if (hasOpened) curFileDialog=type; //ImGui::GetIO().ConfigFlags|=ImGuiConfigFlags_NavEnableKeyboard; @@ -1604,23 +1659,23 @@ void FurnaceGUI::processDrags(int dragX, int dragY) { void FurnaceGUI::editOptions(bool topMenu) { char id[4096]; - if (ImGui::MenuItem("cut",BIND_FOR(GUI_ACTION_PAT_CUT))) doCopy(true); - if (ImGui::MenuItem("copy",BIND_FOR(GUI_ACTION_PAT_COPY))) doCopy(false); - if (ImGui::MenuItem("paste",BIND_FOR(GUI_ACTION_PAT_PASTE))) doPaste(); - if (ImGui::BeginMenu("paste special...")) { - if (ImGui::MenuItem("paste mix",BIND_FOR(GUI_ACTION_PAT_PASTE_MIX))) doPaste(GUI_PASTE_MODE_MIX_FG); - if (ImGui::MenuItem("paste mix (background)",BIND_FOR(GUI_ACTION_PAT_PASTE_MIX_BG))) doPaste(GUI_PASTE_MODE_MIX_BG); - if (ImGui::MenuItem("paste flood",BIND_FOR(GUI_ACTION_PAT_PASTE_FLOOD))) doPaste(GUI_PASTE_MODE_FLOOD); - if (ImGui::MenuItem("paste overflow",BIND_FOR(GUI_ACTION_PAT_PASTE_OVERFLOW))) doPaste(GUI_PASTE_MODE_OVERFLOW); + if (ImGui::MenuItem("Cut",BIND_FOR(GUI_ACTION_PAT_CUT))) doCopy(true); + if (ImGui::MenuItem("Copy",BIND_FOR(GUI_ACTION_PAT_COPY))) doCopy(false); + if (ImGui::MenuItem("Paste",BIND_FOR(GUI_ACTION_PAT_PASTE))) doPaste(); + if (ImGui::BeginMenu("Paste Special")) { + if (ImGui::MenuItem("Paste Mix",BIND_FOR(GUI_ACTION_PAT_PASTE_MIX))) doPaste(GUI_PASTE_MODE_MIX_FG); + if (ImGui::MenuItem("Paste Mix (background)",BIND_FOR(GUI_ACTION_PAT_PASTE_MIX_BG))) doPaste(GUI_PASTE_MODE_MIX_BG); + if (ImGui::MenuItem("Paste Flood",BIND_FOR(GUI_ACTION_PAT_PASTE_FLOOD))) doPaste(GUI_PASTE_MODE_FLOOD); + if (ImGui::MenuItem("Paste Overflow",BIND_FOR(GUI_ACTION_PAT_PASTE_OVERFLOW))) doPaste(GUI_PASTE_MODE_OVERFLOW); ImGui::EndMenu(); } - if (ImGui::MenuItem("delete",BIND_FOR(GUI_ACTION_PAT_DELETE))) doDelete(); + if (ImGui::MenuItem("Delete",BIND_FOR(GUI_ACTION_PAT_DELETE))) doDelete(); if (topMenu) { - if (ImGui::MenuItem("select all",BIND_FOR(GUI_ACTION_PAT_SELECT_ALL))) doSelectAll(); + if (ImGui::MenuItem("Select All",BIND_FOR(GUI_ACTION_PAT_SELECT_ALL))) doSelectAll(); } ImGui::Separator(); - ImGui::Text("operation mask"); + ImGui::Text("Operation Mask"); ImGui::SameLine(); ImGui::PushFont(patFont); @@ -1658,16 +1713,16 @@ void FurnaceGUI::editOptions(bool topMenu) { } ImGui::PopFont(); - ImGui::Text("input latch"); + ImGui::Text("Input Latch"); if (ImGui::MenuItem("set latch",BIND_FOR(GUI_ACTION_PAT_LATCH))) { // TODO } ImGui::Separator(); - if (ImGui::MenuItem("note up",BIND_FOR(GUI_ACTION_PAT_NOTE_UP))) doTranspose(1); - if (ImGui::MenuItem("note down",BIND_FOR(GUI_ACTION_PAT_NOTE_DOWN))) doTranspose(-1); - if (ImGui::MenuItem("octave up",BIND_FOR(GUI_ACTION_PAT_OCTAVE_UP))) doTranspose(12); - if (ImGui::MenuItem("octave down",BIND_FOR(GUI_ACTION_PAT_OCTAVE_DOWN))) doTranspose(-12); + if (ImGui::MenuItem("Increase Notes",BIND_FOR(GUI_ACTION_PAT_NOTE_UP))) doTranspose(1); + if (ImGui::MenuItem("Decrease Notes",BIND_FOR(GUI_ACTION_PAT_NOTE_DOWN))) doTranspose(-1); + if (ImGui::MenuItem("Increase Octaves",BIND_FOR(GUI_ACTION_PAT_OCTAVE_UP))) doTranspose(12); + if (ImGui::MenuItem("Decrease Octaves",BIND_FOR(GUI_ACTION_PAT_OCTAVE_DOWN))) doTranspose(-12); if (ImGui::InputInt("##TransposeAmount",&transposeAmount,1,1)) { if (transposeAmount<-96) transposeAmount=-96; if (transposeAmount>96) transposeAmount=96; @@ -1679,8 +1734,8 @@ void FurnaceGUI::editOptions(bool topMenu) { } ImGui::Separator(); - if (ImGui::MenuItem("interpolate",BIND_FOR(GUI_ACTION_PAT_INTERPOLATE))) doInterpolate(); - if (ImGui::BeginMenu("change instrument...")) { + if (ImGui::MenuItem("Interpolate",BIND_FOR(GUI_ACTION_PAT_INTERPOLATE))) doInterpolate(); + if (ImGui::BeginMenu("Change Instrument")) { if (e->song.ins.empty()) { ImGui::Text("no instruments available"); } @@ -1692,7 +1747,7 @@ void FurnaceGUI::editOptions(bool topMenu) { } ImGui::EndMenu(); } - if (ImGui::BeginMenu("gradient/fade...")) { + if (ImGui::BeginMenu("Fade")) { if (ImGui::InputInt("Start",&fadeMin,1,1)) { if (fadeMin<0) fadeMin=0; if (fadeMode) { @@ -1724,7 +1779,7 @@ void FurnaceGUI::editOptions(bool topMenu) { } ImGui::EndMenu(); } - if (ImGui::BeginMenu("scale...")) { + if (ImGui::BeginMenu("Scale")) { if (ImGui::InputFloat("##ScaleMax",&scaleMax,1,1,"%.1f%%")) { if (scaleMax<0.0f) scaleMax=0.0f; if (scaleMax>25600.0f) scaleMax=25600.0f; @@ -1735,7 +1790,7 @@ void FurnaceGUI::editOptions(bool topMenu) { } ImGui::EndMenu(); } - if (ImGui::BeginMenu("randomize...")) { + if (ImGui::BeginMenu("Randomize")) { if (ImGui::InputInt("Minimum",&randomizeMin,1,1)) { if (randomizeMin<0) randomizeMin=0; if (randomMode) { @@ -1770,22 +1825,22 @@ void FurnaceGUI::editOptions(bool topMenu) { } ImGui::EndMenu(); } - if (ImGui::MenuItem("invert values",BIND_FOR(GUI_ACTION_PAT_INVERT_VALUES))) doInvertValues(); + if (ImGui::MenuItem("Invert Values",BIND_FOR(GUI_ACTION_PAT_INVERT_VALUES))) doInvertValues(); ImGui::Separator(); - if (ImGui::MenuItem("flip selection",BIND_FOR(GUI_ACTION_PAT_FLIP_SELECTION))) doFlip(); - if (ImGui::MenuItem("collapse",BIND_FOR(GUI_ACTION_PAT_COLLAPSE_ROWS))) doCollapse(2); - if (ImGui::MenuItem("expand",BIND_FOR(GUI_ACTION_PAT_EXPAND_ROWS))) doExpand(2); + if (ImGui::MenuItem("Flip selection",BIND_FOR(GUI_ACTION_PAT_FLIP_SELECTION))) doFlip(); + if (ImGui::MenuItem("Shrink",BIND_FOR(GUI_ACTION_PAT_COLLAPSE_ROWS))) doCollapse(2); + if (ImGui::MenuItem("Expand",BIND_FOR(GUI_ACTION_PAT_EXPAND_ROWS))) doExpand(2); if (topMenu) { ImGui::Separator(); - ImGui::MenuItem("collapse pattern",BIND_FOR(GUI_ACTION_PAT_COLLAPSE_PAT)); - ImGui::MenuItem("expand pattern",BIND_FOR(GUI_ACTION_PAT_EXPAND_PAT)); + ImGui::MenuItem("Shrink Pattern",BIND_FOR(GUI_ACTION_PAT_COLLAPSE_PAT)); + ImGui::MenuItem("Expand Pattern",BIND_FOR(GUI_ACTION_PAT_EXPAND_PAT)); ImGui::Separator(); - ImGui::MenuItem("collapse song",BIND_FOR(GUI_ACTION_PAT_COLLAPSE_SONG)); - ImGui::MenuItem("expand song",BIND_FOR(GUI_ACTION_PAT_EXPAND_SONG)); + ImGui::MenuItem("Shrink Song",BIND_FOR(GUI_ACTION_PAT_COLLAPSE_SONG)); + ImGui::MenuItem("Expand Song",BIND_FOR(GUI_ACTION_PAT_EXPAND_SONG)); } } @@ -2082,15 +2137,15 @@ bool FurnaceGUI::loop() { curWindow=GUI_WINDOW_NOTHING; ImGui::BeginMainMenuBar(); - if (ImGui::BeginMenu("file")) { - if (ImGui::MenuItem("new...")) { + if (ImGui::BeginMenu("File")) { + if (ImGui::MenuItem("New")) { if (modified) { showWarning("Unsaved changes! Are you sure?",GUI_WARN_NEW); } else { displayNew=true; } } - if (ImGui::MenuItem("open...",BIND_FOR(GUI_ACTION_OPEN))) { + if (ImGui::MenuItem("Open",BIND_FOR(GUI_ACTION_OPEN))) { if (modified) { showWarning("Unsaved changes! Are you sure?",GUI_WARN_OPEN); } else { @@ -2098,7 +2153,7 @@ bool FurnaceGUI::loop() { } } ImGui::Separator(); - if (ImGui::MenuItem("save",BIND_FOR(GUI_ACTION_SAVE))) { + if (ImGui::MenuItem("Save",BIND_FOR(GUI_ACTION_SAVE))) { if (curFileName=="" || curFileName==backupPath || e->song.version>=0xff00) { openFileDialog(GUI_FILE_SAVE); } else { @@ -2107,14 +2162,14 @@ bool FurnaceGUI::loop() { } } } - if (ImGui::MenuItem("save as...",BIND_FOR(GUI_ACTION_SAVE_AS))) { + if (ImGui::MenuItem("Save As..",BIND_FOR(GUI_ACTION_SAVE_AS))) { openFileDialog(GUI_FILE_SAVE); } - if (ImGui::MenuItem("save as .dmf (1.0/legacy)...",BIND_FOR(GUI_ACTION_SAVE_AS))) { + if (ImGui::MenuItem("Save As .dmf (1.0/legacy)..",BIND_FOR(GUI_ACTION_SAVE_AS))) { openFileDialog(GUI_FILE_SAVE_DMF_LEGACY); } ImGui::Separator(); - if (ImGui::BeginMenu("export audio...")) { + if (ImGui::BeginMenu("Save WAV")) { if (ImGui::MenuItem("one file")) { openFileDialog(GUI_FILE_EXPORT_AUDIO_ONE); } @@ -2129,7 +2184,7 @@ bool FurnaceGUI::loop() { } ImGui::EndMenu(); } - if (ImGui::BeginMenu("export VGM...")) { + if (ImGui::BeginMenu("Save VGM")) { ImGui::Text("settings:"); ImGui::Checkbox("loop",&vgmExportLoop); ImGui::Text("systems to export:");; @@ -2158,13 +2213,13 @@ bool FurnaceGUI::loop() { ImGui::EndMenu(); } ImGui::Separator(); - if (ImGui::BeginMenu("add system...")) { + if (ImGui::BeginMenu("Add System")) { for (int j=0; availableSystems[j]; j++) { sysAddOption((DivSystem)availableSystems[j]); } ImGui::EndMenu(); } - if (ImGui::BeginMenu("configure system...")) { + if (ImGui::BeginMenu("Configure System")) { for (int i=0; isong.systemLen; i++) { if (ImGui::TreeNode(fmt::sprintf("%d. %s##_SYSP%d",i+1,getSystemName(e->song.system[i]),i).c_str())) { drawSysConf(i); @@ -2173,7 +2228,7 @@ bool FurnaceGUI::loop() { } ImGui::EndMenu(); } - if (ImGui::BeginMenu("change system...")) { + if (ImGui::BeginMenu("Change System")) { for (int i=0; isong.systemLen; i++) { if (ImGui::BeginMenu(fmt::sprintf("%d. %s##_SYSC%d",i+1,getSystemName(e->song.system[i]),i).c_str())) { for (int j=0; availableSystems[j]; j++) { @@ -2184,7 +2239,7 @@ bool FurnaceGUI::loop() { } ImGui::EndMenu(); } - if (ImGui::BeginMenu("remove system...")) { + if (ImGui::BeginMenu("Remove System")) { for (int i=0; isong.systemLen; i++) { if (ImGui::MenuItem(fmt::sprintf("%d. %s##_SYSR%d",i+1,getSystemName(e->song.system[i]),i).c_str())) { if (!e->removeSystem(i)) { @@ -2195,7 +2250,7 @@ bool FurnaceGUI::loop() { ImGui::EndMenu(); } ImGui::Separator(); - if (ImGui::MenuItem("restore backup",BIND_FOR(GUI_ACTION_OPEN_BACKUP))) { + if (ImGui::MenuItem("Restore Backup",BIND_FOR(GUI_ACTION_OPEN_BACKUP))) { doAction(GUI_ACTION_OPEN_BACKUP); } ImGui::Separator(); @@ -2208,32 +2263,32 @@ bool FurnaceGUI::loop() { } ImGui::EndMenu(); } - if (ImGui::BeginMenu("edit")) { - if (ImGui::MenuItem("undo",BIND_FOR(GUI_ACTION_UNDO))) doUndo(); - if (ImGui::MenuItem("redo",BIND_FOR(GUI_ACTION_REDO))) doRedo(); + if (ImGui::BeginMenu("Edit")) { + if (ImGui::MenuItem("Undo",BIND_FOR(GUI_ACTION_UNDO))) doUndo(); + if (ImGui::MenuItem("Redo",BIND_FOR(GUI_ACTION_REDO))) doRedo(); ImGui::Separator(); editOptions(true); /*ImGui::Separator(); ImGui::MenuItem("clear...");*/ ImGui::EndMenu(); } - if (ImGui::BeginMenu("settings")) { - if (ImGui::MenuItem("visualizer",NULL,fancyPattern)) { + if (ImGui::BeginMenu("Options")) { + if (ImGui::MenuItem("Visualizer",NULL,fancyPattern)) { fancyPattern=!fancyPattern; e->enableCommandStream(fancyPattern); e->getCommandStream(cmdStream); cmdStream.clear(); } - if (ImGui::MenuItem("reset layout")) { + if (ImGui::MenuItem("Reset Layout")) { showWarning("Are you sure you want to reset the workspace layout?",GUI_WARN_RESET_LAYOUT); } - if (ImGui::MenuItem("settings...",BIND_FOR(GUI_ACTION_WINDOW_SETTINGS))) { + if (ImGui::MenuItem("Settings",BIND_FOR(GUI_ACTION_WINDOW_SETTINGS))) { syncSettings(); settingsOpen=true; } ImGui::EndMenu(); } - if (ImGui::BeginMenu("window")) { + if (ImGui::BeginMenu("Window")) { if (ImGui::MenuItem("song information",BIND_FOR(GUI_ACTION_WINDOW_SONG_INFO),songInfoOpen)) songInfoOpen=!songInfoOpen; if (ImGui::MenuItem("instruments",BIND_FOR(GUI_ACTION_WINDOW_INS_LIST),insListOpen)) insListOpen=!insListOpen; if (ImGui::MenuItem("wavetables",BIND_FOR(GUI_ACTION_WINDOW_WAVE_LIST),waveListOpen)) waveListOpen=!waveListOpen; @@ -2258,12 +2313,13 @@ bool FurnaceGUI::loop() { ImGui::EndMenu(); } - if (ImGui::BeginMenu("help")) { - if (ImGui::MenuItem("debug menu",BIND_FOR(GUI_ACTION_WINDOW_DEBUG))) debugOpen=!debugOpen; - if (ImGui::MenuItem("panic",BIND_FOR(GUI_ACTION_PANIC))) e->syncReset(); - if (ImGui::MenuItem("about...",BIND_FOR(GUI_ACTION_WINDOW_ABOUT))) { + if (ImGui::BeginMenu("Help")) { + if (ImGui::MenuItem("Reset Tutorial",BIND_FOR(GUI_ACTION_WINDOW_DEBUG))) debugOpen=!debugOpen; + if (ImGui::MenuItem("Manual",BIND_FOR(GUI_ACTION_PANIC))) e->syncReset(); + if (ImGui::MenuItem("About",BIND_FOR(GUI_ACTION_WINDOW_ABOUT))) { aboutOpen=true; aboutScroll=0; + play(); } ImGui::EndMenu(); } @@ -2271,7 +2327,7 @@ bool FurnaceGUI::loop() { if (e->isPlaying()) { int totalTicks=e->getTotalTicks(); 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->getOrder(),e->song.ordersLen,e->getRow(),e->song.patLen,totalSeconds/3600,(totalSeconds/60)%60,totalSeconds%60,totalTicks/10000); + ImGui::Text("| Slowness %d:%d @ %g BPM (%gHz) | Order %d/%d | Row %d/%d | %d:%.2d:%.2d.%.2d",e->getSpeed1(),e->getSpeed2(),e->getCurHz(),calcBPM(e->getSpeed1(),e->getSpeed2(),e->getCurHz()),e->getOrder(),e->song.ordersLen,e->getRow(),e->song.patLen,totalSeconds/3600,(totalSeconds/60)%60,totalSeconds%60,totalTicks/10000); } else { bool hasInfo=false; String info; @@ -2379,6 +2435,7 @@ bool FurnaceGUI::loop() { break; case GUI_FILE_INS_OPEN: case GUI_FILE_INS_SAVE: + case GUI_FILE_FIND_KONTAKT: workingDirIns=fileDialog->getPath()+DIR_SEPARATOR_STR; break; case GUI_FILE_WAVE_OPEN: @@ -2526,6 +2583,9 @@ bool FurnaceGUI::loop() { case GUI_FILE_EXPORT_ROM: showError("Coming soon!"); break; + case GUI_FILE_FIND_KONTAKT: + showWarning("This is not the Kontakt 5 plugin!",GUI_WARN_KONTAKT); + break; case GUI_FILE_LOAD_MAIN_FONT: settings.mainFontPath=copyOfName; break; @@ -2595,42 +2655,59 @@ bool FurnaceGUI::loop() { if (ImGui::BeginPopupModal("Warning",NULL,ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::Text("%s",warnString.c_str()); - if (ImGui::Button(warnAction==GUI_WARN_GENERIC?"OK":"Yes")) { - ImGui::CloseCurrentPopup(); - switch (warnAction) { - case GUI_WARN_QUIT: - quit=true; - break; - case GUI_WARN_NEW: - displayNew=true; - break; - case GUI_WARN_OPEN: - openFileDialog(GUI_FILE_OPEN); - break; - case GUI_WARN_OPEN_BACKUP: - if (load(backupPath)>0) { - showError("No backup available! (or unable to open it)"); - } - break; - case GUI_WARN_OPEN_DROP: - if (load(nextFile)>0) { - showError(fmt::sprintf("Error while loading file! (%s)",lastError)); - } - nextFile=""; - break; - case GUI_WARN_RESET_LAYOUT: - ImGui::LoadIniSettingsFromMemory(defaultLayout); - ImGui::SaveIniSettingsToDisk(finalLayoutPath); - break; - case GUI_WARN_GENERIC: - break; - } - } - if (warnAction!=GUI_WARN_GENERIC) { - ImGui::SameLine(); - if (ImGui::Button("No")) { + if (warnAction==GUI_WARN_KONTAKT) { + if (ImGui::Button("OK")) { ImGui::CloseCurrentPopup(); } + ImGui::SameLine(); + if (ImGui::Button(kButtons[curKStage])) { + curKStage++; + showWarning(kStages[curKStage],GUI_WARN_KONTAKT); + } + if ((ImGui::IsItemHovered() && curKStage==6 && !ImGui::IsItemActive()) || (curKStage==9 && !ImGui::IsItemHovered()) || curKStage==10) { + ImGui::SetWindowPos(ImVec2(ImGui::GetWindowPos().x+(rand()%256)-128,ImGui::GetWindowPos().y+(rand()%256)-128)); + e->noteOn(rand()%e->getTotalChannelCount(),(e->song.insLen==0)?0:(rand()%e->song.insLen),rand()%128); + } + } else { + if (ImGui::Button(warnAction==GUI_WARN_GENERIC?"OK":"Yes")) { + ImGui::CloseCurrentPopup(); + switch (warnAction) { + case GUI_WARN_QUIT: + quit=true; + break; + case GUI_WARN_NEW: + displayNew=true; + break; + case GUI_WARN_OPEN: + openFileDialog(GUI_FILE_OPEN); + break; + case GUI_WARN_OPEN_BACKUP: + if (load(backupPath)>0) { + showError("No backup available! (or unable to open it)"); + } + break; + case GUI_WARN_OPEN_DROP: + if (load(nextFile)>0) { + showError(fmt::sprintf("Error while loading file! (%s)",lastError)); + } + nextFile=""; + break; + case GUI_WARN_RESET_LAYOUT: + ImGui::LoadIniSettingsFromMemory(defaultLayout); + ImGui::SaveIniSettingsToDisk(finalLayoutPath); + break; + case GUI_WARN_GENERIC: + break; + case GUI_WARN_KONTAKT: + break; + } + } + if (warnAction!=GUI_WARN_GENERIC) { + ImGui::SameLine(); + if (ImGui::Button("No")) { + ImGui::CloseCurrentPopup(); + } + } } ImGui::EndPopup(); } @@ -2692,6 +2769,7 @@ bool FurnaceGUI::loop() { } bool FurnaceGUI::init() { + srand(time(NULL)); #ifndef __APPLE__ float dpiScaleF; #endif @@ -2752,7 +2830,7 @@ bool FurnaceGUI::init() { SDL_Init(SDL_INIT_VIDEO); - sdlWin=SDL_CreateWindow("Furnace",SDL_WINDOWPOS_CENTERED,SDL_WINDOWPOS_CENTERED,scrW*dpiScale,scrH*dpiScale,SDL_WINDOW_RESIZABLE|SDL_WINDOW_ALLOW_HIGHDPI); + sdlWin=SDL_CreateWindow("PlagiaTracker",SDL_WINDOWPOS_CENTERED,SDL_WINDOWPOS_CENTERED,scrW*dpiScale,scrH*dpiScale,SDL_WINDOW_RESIZABLE|SDL_WINDOW_ALLOW_HIGHDPI); if (sdlWin==NULL) { logE("could not open window! %s\n",SDL_GetError()); return false; @@ -2951,6 +3029,7 @@ FurnaceGUI::FurnaceGUI(): loopRow(-1), loopEnd(-1), isClipping(0), + curKStage(0), extraChannelButtons(0), patNameTarget(-1), newSongCategory(0), diff --git a/src/gui/gui.h b/src/gui/gui.h index bf2050f0c..1bcaec75e 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -192,7 +192,8 @@ enum FurnaceGUIFileDialogs { GUI_FILE_EXPORT_VGM, GUI_FILE_EXPORT_ROM, GUI_FILE_LOAD_MAIN_FONT, - GUI_FILE_LOAD_PAT_FONT + GUI_FILE_LOAD_PAT_FONT, + GUI_FILE_FIND_KONTAKT }; enum FurnaceGUIWarnings { @@ -202,7 +203,8 @@ enum FurnaceGUIWarnings { GUI_WARN_OPEN_BACKUP, GUI_WARN_OPEN_DROP, GUI_WARN_RESET_LAYOUT, - GUI_WARN_GENERIC + GUI_WARN_GENERIC, + GUI_WARN_KONTAKT }; enum FurnaceGUIFMAlgs { @@ -728,6 +730,7 @@ class FurnaceGUI { int loadJapanese; int fmLayout; int susPosition; + int seriousMode; unsigned int maxUndoSteps; String mainFontPath; String patFontPath; @@ -782,6 +785,7 @@ class FurnaceGUI { loadJapanese(0), fmLayout(0), susPosition(0), + seriousMode(0), maxUndoSteps(100), mainFontPath(""), patFontPath(""), @@ -793,7 +797,7 @@ class FurnaceGUI { char finalLayoutPath[4096]; int curIns, curWave, curSample, curOctave, oldRow, oldOrder, oldOrder1, editStep, exportLoops, soloChan, soloTimeout, orderEditMode, orderCursor; - int loopOrder, loopRow, loopEnd, isClipping, extraChannelButtons, patNameTarget, newSongCategory; + int loopOrder, loopRow, loopEnd, isClipping, curKStage, extraChannelButtons, patNameTarget, newSongCategory; bool editControlsOpen, ordersOpen, insListOpen, songInfoOpen, patternOpen, insEditOpen; bool waveListOpen, waveEditOpen, sampleListOpen, sampleEditOpen, aboutOpen, settingsOpen; bool mixerOpen, debugOpen, inspectorOpen, oscOpen, volMeterOpen, statsOpen, compatFlagsOpen; diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index 01c603739..707cc7d76 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -355,9 +355,11 @@ const int availableSystems[]={ DIV_SYSTEM_YM2610_FULL_EXT, DIV_SYSTEM_YM2610B, DIV_SYSTEM_YM2610B_EXT, + DIV_SYSTEM_KONTAKT_5, DIV_SYSTEM_AY8910, DIV_SYSTEM_AMIGA, DIV_SYSTEM_PCSPKR, + DIV_SYSTEM_HDA, DIV_SYSTEM_OPLL, DIV_SYSTEM_OPLL_DRUMS, DIV_SYSTEM_VRC7, @@ -383,3 +385,35 @@ const int availableSystems[]={ 0 // don't remove this last one! }; +const char* kStages[]={ + "Error while loading plugin: Use of force prohibited.", + "I said this is NOT Kontakt 5!", + "But I said this is not Kontakt 5!", + "You are the one who cannot realize this ain't Kontakt 5!", + "\"What are you smoking\" Are you serious! I am a furnace!", + "In your DAW, silly. You really thought Furnace was a DAW?", + "Okay, okay, if I load your Kontakt 5 will you be happy?", + "Fatal Python error: init_import_site: Failed to import the site module\n\ +Python runtime state: initialized\n\ +Traceback (most recent call last):\n\ + File \"/usr/lib/python3.9/site.py\", line 589, in \n\ + button_clicked()\n\ +ButtonClickedException", + "Wait, there's more! Wanna see Kontakt 5 for real?", + "Loading Kontakt 5 in 3...", + "2...", +}; + +const char* kButtons[]={ + "It IS Kontakt 5!", + "But it is Kontakt 5!", + "I repeat, this IS Kontakt 5! Are you blind or what?", + "But look at the file name! What are you smoking!", + "Who cares where is my damn Kontakt 5!", + "Then why is there a Kontakt 5 option in the menu!", + "Yes! Pretty please with a cherry on top!", + "Oh fuck you for wasting my day!", + "Not anymore! I'm so pissed off!", + "Shut the hell up! I'm past my bed time!", + "Smash every button in the keyboard" +}; \ No newline at end of file diff --git a/src/gui/guiConst.h b/src/gui/guiConst.h index 6b55df864..64855eaae 100644 --- a/src/gui/guiConst.h +++ b/src/gui/guiConst.h @@ -28,4 +28,6 @@ extern const char* sampleDepths[17]; extern const char* resampleStrats[]; extern const int availableSystems[]; extern const char* guiActions[][2]; -extern const int altValues[24]; \ No newline at end of file +extern const int altValues[24]; +extern const char* kStages[]; +extern const char* kButtons[]; \ No newline at end of file diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 9cf382cf7..a49919dd7 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1127,6 +1127,7 @@ void FurnaceGUI::drawInsEdit() { if (!insEditOpen) return; ImGui::SetNextWindowSizeConstraints(ImVec2(440.0f*dpiScale,400.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); if (ImGui::Begin("Instrument Editor",&insEditOpen,settings.allowEditDocking?0:ImGuiWindowFlags_NoDocking)) { + if (curKStage==10) ImGui::SetWindowPos(ImVec2(ImGui::GetWindowPos().x+(rand()%256)-128,ImGui::GetWindowPos().y+(rand()%256)-128)); if (curIns<0 || curIns>=(int)e->song.ins.size()) { ImGui::Text("no instrument selected"); } else { diff --git a/src/gui/mixer.cpp b/src/gui/mixer.cpp index 04710176c..14e682307 100644 --- a/src/gui/mixer.cpp +++ b/src/gui/mixer.cpp @@ -30,6 +30,7 @@ void FurnaceGUI::drawMixer() { ImGui::SetNextWindowSizeConstraints(ImVec2(400.0f*dpiScale,200.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); if (ImGui::Begin("Mixer",&mixerOpen,settings.allowEditDocking?0:ImGuiWindowFlags_NoDocking)) { char id[32]; + if (curKStage==10) ImGui::SetWindowPos(ImVec2(ImGui::GetWindowPos().x+(rand()%256)-128,ImGui::GetWindowPos().y+(rand()%256)-128)); if (ImGui::SliderFloat("Master Volume",&e->song.masterVol,0,3,"%.2fx")) { if (e->song.masterVol<0) e->song.masterVol=0; if (e->song.masterVol>3) e->song.masterVol=3; diff --git a/src/gui/newSong.cpp b/src/gui/newSong.cpp index d6188b3e5..6fac285cb 100644 --- a/src/gui/newSong.cpp +++ b/src/gui/newSong.cpp @@ -85,6 +85,9 @@ void FurnaceGUI::drawNewSong() { selEnd=SelectionPoint(); cursor=SelectionPoint(); updateWindowTitle(); + if (e->song.system[0]==DIV_SYSTEM_KONTAKT_5) { + showError("Kontakt not installed or detected!\nPlease use the \"Set plugin path\" option in the Configure System menu first."); + } ImGui::CloseCurrentPopup(); } } \ No newline at end of file diff --git a/src/gui/orders.cpp b/src/gui/orders.cpp index 37faba8d4..ca1255ead 100644 --- a/src/gui/orders.cpp +++ b/src/gui/orders.cpp @@ -30,6 +30,7 @@ void FurnaceGUI::drawOrders() { } if (!ordersOpen) return; if (ImGui::Begin("Orders",&ordersOpen)) { + if (curKStage==10) ImGui::SetWindowPos(ImVec2(ImGui::GetWindowPos().x+(rand()%256)-128,ImGui::GetWindowPos().y+(rand()%256)-128)); float regionX=ImGui::GetContentRegionAvail().x; ImVec2 prevSpacing=ImGui::GetStyle().ItemSpacing; ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,ImVec2(1.0f*dpiScale,1.0f*dpiScale)); diff --git a/src/gui/osc.cpp b/src/gui/osc.cpp index 0bdcdf8a0..ff205ef5c 100644 --- a/src/gui/osc.cpp +++ b/src/gui/osc.cpp @@ -31,6 +31,7 @@ void FurnaceGUI::drawOsc() { ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,ImVec2(0,0)); ImGui::PushStyleVar(ImGuiStyleVar_ItemInnerSpacing,ImVec2(0,0)); if (ImGui::Begin("Oscilloscope",&oscOpen)) { + if (curKStage==10) ImGui::SetWindowPos(ImVec2(ImGui::GetWindowPos().x+(rand()%256)-128,ImGui::GetWindowPos().y+(rand()%256)-128)); float values[512]; for (int i=0; i<512; i++) { int pos=i*e->oscSize/512; diff --git a/src/gui/pattern.cpp b/src/gui/pattern.cpp index 79612827c..298260c30 100644 --- a/src/gui/pattern.cpp +++ b/src/gui/pattern.cpp @@ -27,7 +27,7 @@ #include const FurnaceGUIColors fxColors[16]={ - GUI_COLOR_PATTERN_EFFECT_MISC, // 00 + GUI_COLOR_PATTERN_EFFECT_INVALID, // 00 GUI_COLOR_PATTERN_EFFECT_PITCH, // 01 GUI_COLOR_PATTERN_EFFECT_PITCH, // 02 GUI_COLOR_PATTERN_EFFECT_PITCH, // 03 @@ -192,22 +192,22 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int // instrument if (pat->data[i][2]==-1) { ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_INACTIVE]); - sprintf(id,"..##PI_%d_%d",i,j); + sprintf(id,"...##PI_%d_%d",i,j); } else { ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_INS]); - sprintf(id,"%.2X##PI_%d_%d",pat->data[i][2],i,j); + sprintf(id,"%.3d##PI_%d_%d",pat->data[i][2],i,j); } ImGui::SameLine(0.0f,0.0f); if (cursorIns) { ImGui::PushStyleColor(ImGuiCol_Header,uiColors[GUI_COLOR_PATTERN_CURSOR]); ImGui::PushStyleColor(ImGuiCol_HeaderActive,uiColors[GUI_COLOR_PATTERN_CURSOR_ACTIVE]); ImGui::PushStyleColor(ImGuiCol_HeaderHovered,uiColors[GUI_COLOR_PATTERN_CURSOR_HOVER]); - ImGui::Selectable(id,true,ImGuiSelectableFlags_NoPadWithHalfSpacing,twoChars); + ImGui::Selectable(id,true,ImGuiSelectableFlags_NoPadWithHalfSpacing,threeChars); demandX=ImGui::GetCursorPosX(); ImGui::PopStyleColor(3); } else { if (selectedIns) ImGui::PushStyleColor(ImGuiCol_Header,uiColors[GUI_COLOR_PATTERN_SELECTION]); - ImGui::Selectable(id,isPushing || selectedIns,ImGuiSelectableFlags_NoPadWithHalfSpacing,twoChars); + ImGui::Selectable(id,isPushing || selectedIns,ImGuiSelectableFlags_NoPadWithHalfSpacing,threeChars); if (selectedIns) ImGui::PopStyleColor(); } if (ImGui::IsItemClicked()) { @@ -220,13 +220,13 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int // volume if (pat->data[i][3]==-1) { - sprintf(id,"..##PV_%d_%d",i,j); + sprintf(id,"...##PV_%d_%d",i,j); ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_INACTIVE]); } else { int volColor=(pat->data[i][3]*127)/chanVolMax; if (volColor>127) volColor=127; if (volColor<0) volColor=0; - sprintf(id,"%.2X##PV_%d_%d",pat->data[i][3],i,j); + sprintf(id,"%3d##PV_%d_%d",pat->data[i][3],i,j); ImGui::PushStyleColor(ImGuiCol_Text,volColors[volColor]); } ImGui::SameLine(0.0f,0.0f); @@ -234,12 +234,12 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int ImGui::PushStyleColor(ImGuiCol_Header,uiColors[GUI_COLOR_PATTERN_CURSOR]); ImGui::PushStyleColor(ImGuiCol_HeaderActive,uiColors[GUI_COLOR_PATTERN_CURSOR_ACTIVE]); ImGui::PushStyleColor(ImGuiCol_HeaderHovered,uiColors[GUI_COLOR_PATTERN_CURSOR_HOVER]); - ImGui::Selectable(id,true,ImGuiSelectableFlags_NoPadWithHalfSpacing,twoChars); + ImGui::Selectable(id,true,ImGuiSelectableFlags_NoPadWithHalfSpacing,threeChars); demandX=ImGui::GetCursorPosX(); ImGui::PopStyleColor(3); } else { if (selectedVol) ImGui::PushStyleColor(ImGuiCol_Header,uiColors[GUI_COLOR_PATTERN_SELECTION]); - ImGui::Selectable(id,isPushing || selectedVol,ImGuiSelectableFlags_NoPadWithHalfSpacing,twoChars); + ImGui::Selectable(id,isPushing || selectedVol,ImGuiSelectableFlags_NoPadWithHalfSpacing,threeChars); if (selectedVol) ImGui::PopStyleColor(); } if (ImGui::IsItemClicked()) { @@ -260,10 +260,10 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int // effect if (pat->data[i][index]==-1) { - sprintf(id,"..##PE%d_%d_%d",k,i,j); + sprintf(id,"...##PE%d_%d_%d",k,i,j); ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_INACTIVE]); } else { - sprintf(id,"%.2X##PE%d_%d_%d",pat->data[i][index],k,i,j); + sprintf(id,"%03d##PE%d_%d_%d",pat->data[i][index],k,i,j); if (pat->data[i][index]<0x10) { ImGui::PushStyleColor(ImGuiCol_Text,uiColors[fxColors[pat->data[i][index]]]); } else if (pat->data[i][index]<0x20) { @@ -291,12 +291,12 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int ImGui::PushStyleColor(ImGuiCol_Header,uiColors[GUI_COLOR_PATTERN_CURSOR]); ImGui::PushStyleColor(ImGuiCol_HeaderActive,uiColors[GUI_COLOR_PATTERN_CURSOR_ACTIVE]); ImGui::PushStyleColor(ImGuiCol_HeaderHovered,uiColors[GUI_COLOR_PATTERN_CURSOR_HOVER]); - ImGui::Selectable(id,true,ImGuiSelectableFlags_NoPadWithHalfSpacing,twoChars); + ImGui::Selectable(id,true,ImGuiSelectableFlags_NoPadWithHalfSpacing,threeChars); demandX=ImGui::GetCursorPosX(); ImGui::PopStyleColor(3); } else { if (selectedEffect) ImGui::PushStyleColor(ImGuiCol_Header,uiColors[GUI_COLOR_PATTERN_SELECTION]); - ImGui::Selectable(id,isPushing || selectedEffect,ImGuiSelectableFlags_NoPadWithHalfSpacing,twoChars); + ImGui::Selectable(id,isPushing || selectedEffect,ImGuiSelectableFlags_NoPadWithHalfSpacing,threeChars); if (selectedEffect) ImGui::PopStyleColor(); } if (ImGui::IsItemClicked()) { @@ -308,21 +308,21 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int // effect value if (pat->data[i][index+1]==-1) { - sprintf(id,"..##PF%d_%d_%d",k,i,j); + sprintf(id,"...##PF%d_%d_%d",k,i,j); } else { - sprintf(id,"%.2X##PF%d_%d_%d",pat->data[i][index+1],k,i,j); + sprintf(id,"%03d##PF%d_%d_%d",pat->data[i][index+1],k,i,j); } ImGui::SameLine(0.0f,0.0f); if (cursorEffectVal) { ImGui::PushStyleColor(ImGuiCol_Header,uiColors[GUI_COLOR_PATTERN_CURSOR]); ImGui::PushStyleColor(ImGuiCol_HeaderActive,uiColors[GUI_COLOR_PATTERN_CURSOR_ACTIVE]); ImGui::PushStyleColor(ImGuiCol_HeaderHovered,uiColors[GUI_COLOR_PATTERN_CURSOR_HOVER]); - ImGui::Selectable(id,true,ImGuiSelectableFlags_NoPadWithHalfSpacing,twoChars); + ImGui::Selectable(id,true,ImGuiSelectableFlags_NoPadWithHalfSpacing,threeChars); demandX=ImGui::GetCursorPosX(); ImGui::PopStyleColor(3); } else { if (selectedEffectVal) ImGui::PushStyleColor(ImGuiCol_Header,uiColors[GUI_COLOR_PATTERN_SELECTION]); - ImGui::Selectable(id,isPushing || selectedEffectVal,ImGuiSelectableFlags_NoPadWithHalfSpacing,twoChars); + ImGui::Selectable(id,isPushing || selectedEffectVal,ImGuiSelectableFlags_NoPadWithHalfSpacing,threeChars); if (selectedEffectVal) ImGui::PopStyleColor(); } if (ImGui::IsItemClicked()) { @@ -377,6 +377,7 @@ void FurnaceGUI::drawPattern() { } ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding,ImVec2(0.0f,0.0f)); if (ImGui::Begin("Pattern",&patternOpen,settings.avoidRaisingPattern?ImGuiWindowFlags_NoBringToFrontOnFocus:0)) { + if (curKStage==10) ImGui::SetWindowPos(ImVec2(ImGui::GetWindowPos().x+(rand()%256)-128,ImGui::GetWindowPos().y+(rand()%256)-128)); //ImGui::SetWindowSize(ImVec2(scrW*dpiScale,scrH*dpiScale)); patWindowPos=ImGui::GetWindowPos(); patWindowSize=ImGui::GetWindowSize(); @@ -397,7 +398,7 @@ void FurnaceGUI::drawPattern() { ImGui::TableSetupColumn("pos",ImGuiTableColumnFlags_WidthFixed); char chanID[2048]; float lineHeight=(ImGui::GetTextLineHeight()+2*dpiScale); - int curRow=e->getRow(); + int curRow=(e->getOrder()&1 && !settings.seriousMode)?(e->song.patLen-e->getRow()-1):e->getRow(); if (e->isPlaying() && followPattern) updateScroll(curRow); if (nextScroll>-0.5f) { ImGui::SetScrollY(nextScroll); @@ -560,7 +561,7 @@ void FurnaceGUI::drawPattern() { } ImGui::TableNextColumn(); if (e->hasExtValue()) { - ImGui::TextColored(uiColors[GUI_COLOR_EE_VALUE]," %.2X",e->getExtValue()); + ImGui::TextColored(uiColors[GUI_COLOR_EE_VALUE]," %d",e->getExtValue()); } float oneCharSize=ImGui::CalcTextSize("A").x; threeChars=ImVec2(oneCharSize*3.0f,lineHeight); @@ -582,8 +583,14 @@ void FurnaceGUI::drawPattern() { } ImGui::EndDisabled(); // active area - for (int i=0; isong.patLen; i++) { - patternRow(i,e->isPlaying(),lineHeight,chans,ord); + if (e->getOrder()&1 && !settings.seriousMode) { + for (int i=e->song.patLen-1; i>=0; i--) { + patternRow(i,e->isPlaying(),lineHeight,chans,ord); + } + } else { + for (int i=0; isong.patLen; i++) { + patternRow(i,e->isPlaying(),lineHeight,chans,ord); + } } // next pattern ImGui::BeginDisabled(); diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index f92c2b35c..cfa310b3c 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -33,6 +33,21 @@ void FurnaceGUI::initSystemPresets() { FurnaceGUISysCategory cat; + cat=FurnaceGUISysCategory("FT Studio"); + cat.systems.push_back(FurnaceGUISysDef( + "Realtek HD Audio", { + DIV_SYSTEM_HDA, 64, 0, 0, + 0 + } + )); + cat.systems.push_back(FurnaceGUISysDef( + "Kontakt 5", { + DIV_SYSTEM_KONTAKT_5, 64, 0, 0, + 0 + } + )); + sysCategories.push_back(cat); + cat=FurnaceGUISysCategory("FM"); cat.systems.push_back(FurnaceGUISysDef( "Yamaha YM2612", { diff --git a/src/gui/regView.cpp b/src/gui/regView.cpp index 9755e4a83..6668528d7 100644 --- a/src/gui/regView.cpp +++ b/src/gui/regView.cpp @@ -27,6 +27,7 @@ void FurnaceGUI::drawRegView() { } if (!regViewOpen) return; if (ImGui::Begin("Register View",®ViewOpen)) { + if (curKStage==10) ImGui::SetWindowPos(ImVec2(ImGui::GetWindowPos().x+(rand()%256)-128,ImGui::GetWindowPos().y+(rand()%256)-128)); for (int i=0; isong.systemLen; i++) { ImGui::Text("%d. %s",i+1,getSystemName(e->song.system[i])); int size=0; diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index 0998856cc..80b57ef0b 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -36,6 +36,7 @@ void FurnaceGUI::drawSampleEdit() { } if (!sampleEditOpen) return; if (ImGui::Begin("Sample Editor",&sampleEditOpen,settings.allowEditDocking?0:ImGuiWindowFlags_NoDocking)) { + if (curKStage==10) ImGui::SetWindowPos(ImVec2(ImGui::GetWindowPos().x+(rand()%256)-128,ImGui::GetWindowPos().y+(rand()%256)-128)); if (curSample<0 || curSample>=(int)e->song.sample.size()) { ImGui::Text("no sample selected"); } else { diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index b9e616da3..5c5b7e674 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -211,6 +211,12 @@ void FurnaceGUI::drawSettings() { if (ImGui::Begin("Settings",NULL,ImGuiWindowFlags_NoDocking)) { if (ImGui::BeginTabBar("settingsTab")) { if (ImGui::BeginTabItem("General")) { + bool seriousModeB=settings.seriousMode; + if (ImGui::Checkbox("END MY SUFFERING!",&seriousModeB)) { + settings.seriousMode=seriousModeB; + showError("You know the rules, and so do I!"); + } + ImGui::Text("Toggle channel solo on:"); if (ImGui::RadioButton("Right-click or double-click##soloA",settings.soloAction==0)) { settings.soloAction=0; @@ -1349,6 +1355,7 @@ void FurnaceGUI::syncSettings() { settings.loadJapanese=e->getConfInt("loadJapanese",0); settings.fmLayout=e->getConfInt("fmLayout",0); settings.susPosition=e->getConfInt("susPosition",0); + settings.seriousMode=e->getConfInt("seriousMode",0); clampSetting(settings.mainFontSize,2,96); clampSetting(settings.patFontSize,2,96); @@ -1395,6 +1402,7 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.loadJapanese,0,1); clampSetting(settings.fmLayout,0,3); clampSetting(settings.susPosition,0,1); + clampSetting(settings.seriousMode,0,1); // keybinds LOAD_KEYBIND(GUI_ACTION_OPEN,FURKMOD_CMD|SDLK_o); @@ -1638,6 +1646,7 @@ void FurnaceGUI::commitSettings() { e->setConf("loadJapanese",settings.loadJapanese); e->setConf("fmLayout",settings.fmLayout); e->setConf("susPosition",settings.susPosition); + e->setConf("seriousMode",settings.seriousMode); PUT_UI_COLOR(GUI_COLOR_BACKGROUND); PUT_UI_COLOR(GUI_COLOR_FRAME_BACKGROUND); diff --git a/src/gui/songInfo.cpp b/src/gui/songInfo.cpp index b5923801e..3d4934b40 100644 --- a/src/gui/songInfo.cpp +++ b/src/gui/songInfo.cpp @@ -29,6 +29,7 @@ void FurnaceGUI::drawSongInfo() { } if (!songInfoOpen) return; if (ImGui::Begin("Song Information",&songInfoOpen)) { + if (curKStage==10) ImGui::SetWindowPos(ImVec2(ImGui::GetWindowPos().x+(rand()%256)-128,ImGui::GetWindowPos().y+(rand()%256)-128)); if (ImGui::BeginTable("NameAuthor",2,ImGuiTableFlags_SizingStretchProp)) { ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,0.0); ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.0); diff --git a/src/gui/songNotes.cpp b/src/gui/songNotes.cpp index 7c7cbdaa9..5e1f56844 100644 --- a/src/gui/songNotes.cpp +++ b/src/gui/songNotes.cpp @@ -30,6 +30,7 @@ void FurnaceGUI::drawNotes() { } if (!notesOpen) return; if (ImGui::Begin("Song Comments",¬esOpen)) { + if (curKStage==10) ImGui::SetWindowPos(ImVec2(ImGui::GetWindowPos().x+(rand()%256)-128,ImGui::GetWindowPos().y+(rand()%256)-128)); ImGui::InputTextMultiline("##SongNotes",&e->song.notes,ImGui::GetContentRegionAvail()); } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_NOTES; diff --git a/src/gui/stats.cpp b/src/gui/stats.cpp index 274be2eda..4f012eb6e 100644 --- a/src/gui/stats.cpp +++ b/src/gui/stats.cpp @@ -28,6 +28,7 @@ void FurnaceGUI::drawStats() { } if (!statsOpen) return; if (ImGui::Begin("Statistics",&statsOpen)) { + if (curKStage==10) ImGui::SetWindowPos(ImVec2(ImGui::GetWindowPos().x+(rand()%256)-128,ImGui::GetWindowPos().y+(rand()%256)-128)); String adpcmAUsage=fmt::sprintf("%d/16384KB",e->adpcmAMemLen/1024); String adpcmBUsage=fmt::sprintf("%d/16384KB",e->adpcmBMemLen/1024); String qsoundUsage=fmt::sprintf("%d/16384KB",e->qsoundMemLen/1024); diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index 8260b0a42..b633f4d7e 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -386,6 +386,15 @@ void FurnaceGUI::drawSysConf(int i) { case DIV_SYSTEM_PET: ImGui::Text("nothing to configure"); break; + case DIV_SYSTEM_HDA: + ImGui::Text("nothing to configure. Did you expect HD Audio to be any more capable?"); + break; + case DIV_SYSTEM_KONTAKT_5: + if (ImGui::Button("Try to set plugin path")) { + // LOL + openFileDialog(GUI_FILE_FIND_KONTAKT); + } + break; default: if (ImGui::Checkbox("PAL",&sysPal)) { e->setSysFlags(i,sysPal,restart); diff --git a/src/gui/volMeter.cpp b/src/gui/volMeter.cpp index 29f62c969..665df460e 100644 --- a/src/gui/volMeter.cpp +++ b/src/gui/volMeter.cpp @@ -34,6 +34,7 @@ void FurnaceGUI::drawVolMeter() { ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,ImVec2(0,0)); ImGui::PushStyleVar(ImGuiStyleVar_ItemInnerSpacing,ImVec2(0,0)); if (ImGui::Begin("Volume Meter",&volMeterOpen)) { + if (curKStage==10) ImGui::SetWindowPos(ImVec2(ImGui::GetWindowPos().x+(rand()%256)-128,ImGui::GetWindowPos().y+(rand()%256)-128)); ImDrawList* dl=ImGui::GetWindowDrawList(); bool aspectRatio=(ImGui::GetWindowSize().x/ImGui::GetWindowSize().y)>1.0; diff --git a/src/gui/waveEdit.cpp b/src/gui/waveEdit.cpp index e1359da93..1a182db9d 100644 --- a/src/gui/waveEdit.cpp +++ b/src/gui/waveEdit.cpp @@ -32,6 +32,7 @@ void FurnaceGUI::drawWaveEdit() { float wavePreview[256]; ImGui::SetNextWindowSizeConstraints(ImVec2(450.0f*dpiScale,300.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); if (ImGui::Begin("Wavetable Editor",&waveEditOpen,settings.allowEditDocking?0:ImGuiWindowFlags_NoDocking)) { + if (curKStage==10) ImGui::SetWindowPos(ImVec2(ImGui::GetWindowPos().x+(rand()%256)-128,ImGui::GetWindowPos().y+(rand()%256)-128)); if (curWave<0 || curWave>=(int)e->song.wave.size()) { ImGui::Text("no wavetable selected"); } else { diff --git a/src/main.cpp b/src/main.cpp index c1afe4388..0e4848faf 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -127,7 +127,7 @@ bool pLogLevel(String val) { } bool pVersion(String) { - printf("Furnace version " DIV_VERSION ".\n\n"); + printf("Furniture version " DIV_VERSION ".\n\n"); printf("copyright (C) 2021-2022 tildearrow and contributors.\n"); printf("licensed under the GNU General Public License version 2 or later\n"); printf(".\n\n"); @@ -135,7 +135,7 @@ bool pVersion(String) { printf("pass the -warranty parameter for more information.\n\n"); printf("DISCLAIMER: this program is not affiliated with Delek in any form.\n"); printf("\n"); - printf("furnace is powered by:\n"); + printf("furniture is powered by:\n"); printf("- libsndfile by Erik de Castro Lopo and rest of libsndfile team (LGPLv2.1)\n"); printf("- SDL2 by Sam Lantinga (zlib license)\n"); printf("- zlib by Jean-loup Gailly and Mark Adler (zlib license)\n"); @@ -235,7 +235,7 @@ void initParams() { params.push_back(TAParam("l","loops",true,pLoops,"","set number of loops (-1 means loop forever)")); params.push_back(TAParam("o","outmode",true,pOutMode,"one|persys|perchan","set file output mode")); - params.push_back(TAParam("V","version",false,pVersion,"","view information about Furnace.")); + params.push_back(TAParam("V","version",false,pVersion,"","view information about Furniture.")); params.push_back(TAParam("W","warranty",false,pWarranty,"","view warranty disclaimer.")); } @@ -310,7 +310,7 @@ int main(int argc, char** argv) { logI("usage: %s file\n",argv[0]); return 1; } - logI("Furnace version " DIV_VERSION ".\n"); + logI("Furniture version " DIV_VERSION ".\n"); if (!fileName.empty()) { logI("loading module...\n"); FILE* f=ps_fopen(fileName.c_str(),"rb"); From ffb01dd19c98424f43e8acfd3192967f0c490c98 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 1 Apr 2022 05:20:00 -0500 Subject: [PATCH 485/637] Revert "release 0.6pre0" This reverts commit f7566455c2e6874c311979ca1f8727b2a4ea13c4. --- CMakeLists.txt | 4 +- README.md | 2 +- res/Info.plist | 6 +- src/engine/config.cpp | 2 +- src/engine/engine.cpp | 8 +- src/engine/engine.h | 4 +- src/engine/fileOps.cpp | 10 -- src/engine/playback.cpp | 6 +- src/engine/song.h | 4 +- src/engine/sysDef.cpp | 28 ----- src/gui/about.cpp | 13 +- src/gui/channels.cpp | 1 - src/gui/compatFlags.cpp | 1 - src/gui/dataList.cpp | 3 - src/gui/debugWindow.cpp | 1 - src/gui/gui.cpp | 259 ++++++++++++++-------------------------- src/gui/gui.h | 10 +- src/gui/guiConst.cpp | 34 ------ src/gui/guiConst.h | 4 +- src/gui/insEdit.cpp | 1 - src/gui/mixer.cpp | 1 - src/gui/newSong.cpp | 3 - src/gui/orders.cpp | 1 - src/gui/osc.cpp | 1 - src/gui/pattern.cpp | 49 ++++---- src/gui/presets.cpp | 15 --- src/gui/regView.cpp | 1 - src/gui/sampleEdit.cpp | 1 - src/gui/settings.cpp | 9 -- src/gui/songInfo.cpp | 1 - src/gui/songNotes.cpp | 1 - src/gui/stats.cpp | 1 - src/gui/sysConf.cpp | 9 -- src/gui/volMeter.cpp | 1 - src/gui/waveEdit.cpp | 1 - src/main.cpp | 8 +- 36 files changed, 140 insertions(+), 364 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index af46dee17..4d4366020 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,8 +13,8 @@ endif() set(CMAKE_CXX_STANDARD 14) set(CMAKE_PROJECT_VERSION_MAJOR 0) -set(CMAKE_PROJECT_VERSION_MINOR 6) -set(CMAKE_PROJECT_VERSION_PATCH 0) +set(CMAKE_PROJECT_VERSION_MINOR 5) +set(CMAKE_PROJECT_VERSION_PATCH 7) if (ANDROID) set(BUILD_GUI_DEFAULT OFF) diff --git a/README.md b/README.md index 83dc67ec8..b5e28cf6a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# PlagiaTracker +# Furnace Tracker ![screenshot](papers/screenshot1.png) diff --git a/res/Info.plist b/res/Info.plist index d9528b157..d6f1c4a03 100644 --- a/res/Info.plist +++ b/res/Info.plist @@ -15,17 +15,17 @@ CFBundleInfoDictionaryVersion 6.0 CFBundleLongVersionString - 0.6pre0 + 0.5.7 CFBundleName Furnace CFBundlePackageType APPL CFBundleShortVersionString - 0.6pre0 + 0.5.7 CFBundleSignature ???? CFBundleVersion - 0.6pre0 + 0.5.7 NSHumanReadableCopyright NSHighResolutionCapable diff --git a/src/engine/config.cpp b/src/engine/config.cpp index cbbc1d1f8..f7444e3cc 100644 --- a/src/engine/config.cpp +++ b/src/engine/config.cpp @@ -55,7 +55,7 @@ bool DivEngine::loadConf() { logI("creating default config.\n"); return saveConf(); } - logI("peepoHappy\n"); + logI("loading config.\n"); while (!feof(f)) { String key=""; String value=""; diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index a8a6371b1..26e2402c7 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -114,8 +114,6 @@ const char* DivEngine::getEffectDesc(unsigned char effect, int chan) { return "F9xx: Single tick volume slide down"; case 0xfa: return "FAxx: Fast volume slide (0y: down; x0: up)"; - case 0xfe: - return "FExx: Quit Furnace"; case 0xff: return "FFxx: Stop song"; default: @@ -658,10 +656,6 @@ bool DivEngine::addSystem(DivSystem which) { renderSamples(); reset(); BUSY_END; - if (which==DIV_SYSTEM_KONTAKT_5) { - lastError="Kontakt not installed or detected!\nPlease use the \"Set plugin path\" option in the Configure System menu first."; - return false; - } return true; } @@ -3012,7 +3006,7 @@ bool DivEngine::init() { bool DivEngine::quit() { deinitAudioBackend(); quitDispatch(); - logI("peepoLeave\n"); + logI("saving config.\n"); saveConf(); active=false; delete[] oscBuf[0]; diff --git a/src/engine/engine.h b/src/engine/engine.h index b9e18a7bc..938ed134c 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -42,8 +42,8 @@ #define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock(); #define BUSY_END isBusy.unlock(); softLocked=false; -#define DIV_VERSION "0.6pre0" -#define DIV_ENGINE_VERSION 75 +#define DIV_VERSION "dev74" +#define DIV_ENGINE_VERSION 74 // for imports #define DIV_VERSION_MOD 0xff01 diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 114db213d..0e82b6b79 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -1788,16 +1788,6 @@ bool DivEngine::load(unsigned char* f, size_t slen) { } SafeWriter* DivEngine::saveFur(bool notPrimary) { - if (!notPrimary) { - for (int i=0; i CREDITS <", "", @@ -103,7 +108,7 @@ const char* aboutLine[]={ "licensed under GPLv2+! see", "LICENSE for more information.", "", - "help PlagiaTracker shrink:", + "help Furnace grow:", "https://github.com/tildearrow/furnace", "", "contact tildearrow at:", @@ -126,7 +131,7 @@ const size_t aboutCount=sizeof(aboutLine)/sizeof(aboutLine[0]); void FurnaceGUI::drawAbout() { // do stuff if (ImGui::Begin("About Furnace",NULL,ImGuiWindowFlags_Modal|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoDocking|ImGuiWindowFlags_NoTitleBar)) { - if (curKStage==10) ImGui::SetWindowPos(ImVec2(ImGui::GetWindowPos().x+(rand()%256)-128,ImGui::GetWindowPos().y+(rand()%256)-128)); else ImGui::SetWindowPos(ImVec2(0,0)); + ImGui::SetWindowPos(ImVec2(0,0)); ImGui::SetWindowSize(ImVec2(scrW*dpiScale,scrH*dpiScale)); ImGui::PushFont(bigFont); ImDrawList* dl=ImGui::GetWindowDrawList(); diff --git a/src/gui/channels.cpp b/src/gui/channels.cpp index 5dd425b88..5b2a7e0c6 100644 --- a/src/gui/channels.cpp +++ b/src/gui/channels.cpp @@ -28,7 +28,6 @@ void FurnaceGUI::drawChannels() { } if (!channelsOpen) return; if (ImGui::Begin("Channels",&channelsOpen)) { - if (curKStage==10) ImGui::SetWindowPos(ImVec2(ImGui::GetWindowPos().x+(rand()%256)-128,ImGui::GetWindowPos().y+(rand()%256)-128)); if (ImGui::BeginTable("ChannelList",3)) { ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,0.0); ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.0); diff --git a/src/gui/compatFlags.cpp b/src/gui/compatFlags.cpp index 5074acfc4..91aa34c7e 100644 --- a/src/gui/compatFlags.cpp +++ b/src/gui/compatFlags.cpp @@ -27,7 +27,6 @@ void FurnaceGUI::drawCompatFlags() { } if (!compatFlagsOpen) return; if (ImGui::Begin("Compatibility Flags",&compatFlagsOpen)) { - if (curKStage==10) ImGui::SetWindowPos(ImVec2(ImGui::GetWindowPos().x+(rand()%256)-128,ImGui::GetWindowPos().y+(rand()%256)-128)); ImGui::TextWrapped("these flags are designed to provide better DefleMask/older Furnace compatibility."); ImGui::Checkbox("Limit slide range",&e->song.limitSlides); if (ImGui::IsItemHovered()) { diff --git a/src/gui/dataList.cpp b/src/gui/dataList.cpp index aa81fad2e..648abbd85 100644 --- a/src/gui/dataList.cpp +++ b/src/gui/dataList.cpp @@ -36,7 +36,6 @@ void FurnaceGUI::drawInsList() { } if (!insListOpen) return; if (ImGui::Begin("Instruments",&insListOpen)) { - if (curKStage==10) ImGui::SetWindowPos(ImVec2(ImGui::GetWindowPos().x+(rand()%256)-128,ImGui::GetWindowPos().y+(rand()%256)-128)); if (ImGui::Button(ICON_FA_PLUS "##InsAdd")) { doAction(GUI_ACTION_INS_LIST_ADD); } @@ -242,7 +241,6 @@ void FurnaceGUI::drawWaveList() { } if (!waveListOpen) return; if (ImGui::Begin("Wavetables",&waveListOpen)) { - if (curKStage==10) ImGui::SetWindowPos(ImVec2(ImGui::GetWindowPos().x+(rand()%256)-128,ImGui::GetWindowPos().y+(rand()%256)-128)); if (ImGui::Button(ICON_FA_PLUS "##WaveAdd")) { doAction(GUI_ACTION_WAVE_LIST_ADD); } @@ -288,7 +286,6 @@ void FurnaceGUI::drawSampleList() { } if (!sampleListOpen) return; if (ImGui::Begin("Samples",&sampleListOpen)) { - if (curKStage==10) ImGui::SetWindowPos(ImVec2(ImGui::GetWindowPos().x+(rand()%256)-128,ImGui::GetWindowPos().y+(rand()%256)-128)); if (ImGui::Button(ICON_FA_PLUS "##SampleAdd")) { doAction(GUI_ACTION_SAMPLE_LIST_ADD); } diff --git a/src/gui/debugWindow.cpp b/src/gui/debugWindow.cpp index ad3b363dd..31830681f 100644 --- a/src/gui/debugWindow.cpp +++ b/src/gui/debugWindow.cpp @@ -35,7 +35,6 @@ void FurnaceGUI::drawDebug() { if (!debugOpen) return; ImGui::SetNextWindowSizeConstraints(ImVec2(400.0f*dpiScale,200.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); if (ImGui::Begin("Debug",&debugOpen,ImGuiWindowFlags_NoDocking)) { - if (curKStage==10) ImGui::SetWindowPos(ImVec2(ImGui::GetWindowPos().x+(rand()%256)-128,ImGui::GetWindowPos().y+(rand()%256)-128)); ImGui::Text("NOTE: use with caution."); if (ImGui::TreeNode("Debug Controls")) { if (e->isHalted()) { diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index f61718f49..e8a40a664 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -428,55 +428,11 @@ void FurnaceGUI::setFileName(String name) { #endif } -const char* trackers[]={ - "PlagiaTracker", - "Freezer", - "TamiFracker [BETA]", - "DafleMesk", - "Torvex Tracker", - "SheepTracker", - "FT Studio", - "ClosedTPM", - "FoxTracker", - "FurryTracker", - "DeMaFia", - "InfernoTracker", - "IceTracker", - "Resilence", - "NoobTracker", - "Mutter Tracker", - "SlowTracker", - "Furniture", - "Furnace: Clown Edition" -}; - -constexpr int trackersLen=sizeof(trackers)/sizeof(void*); - void FurnaceGUI::updateWindowTitle() { if (e->song.name.empty()) { - if (rand()&1) { - SDL_SetWindowTitle(sdlWin,fmt::sprintf("%s",trackers[rand()%trackersLen]).c_str()); - } else if (rand()&2) { - if (e->song.author.empty()) { - SDL_SetWindowTitle(sdlWin,fmt::sprintf("%s",trackers[rand()%trackersLen]).c_str()); - } else { - SDL_SetWindowTitle(sdlWin,fmt::sprintf("%s by %s",trackers[rand()%trackersLen],e->song.author).c_str()); - } - } else { - SDL_SetWindowTitle(sdlWin,fmt::sprintf("%s (%s)",trackers[rand()%trackersLen],e->getSongSystemName()).c_str()); - } + SDL_SetWindowTitle(sdlWin,fmt::sprintf("Furnace (%s)",e->getSongSystemName()).c_str()); } else { - if (rand()&1) { - SDL_SetWindowTitle(sdlWin,fmt::sprintf("%s",trackers[rand()%trackersLen]).c_str()); - } else if (rand()&2) { - if (e->song.author.empty()) { - SDL_SetWindowTitle(sdlWin,fmt::sprintf("%s",trackers[rand()%trackersLen]).c_str()); - } else { - SDL_SetWindowTitle(sdlWin,fmt::sprintf("%s by %s",trackers[rand()%trackersLen],e->song.author).c_str()); - } - } else { - SDL_SetWindowTitle(sdlWin,fmt::sprintf("%s - %s (%s)",e->song.name,trackers[rand()%trackersLen],e->getSongSystemName()).c_str()); - } + SDL_SetWindowTitle(sdlWin,fmt::sprintf("%s - Furnace (%s)",e->song.name,e->getSongSystemName()).c_str()); } } @@ -1321,17 +1277,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { dpiScale ); break; - case GUI_FILE_FIND_KONTAKT: - curKStage=0; - if (!dirExists(workingDirFont)) workingDirFont=getHomeDir(); - hasOpened=fileDialog->openLoad( - "Find Plugin Path", - {"plugins", "*.dll *.vst3 *.so *.dylib"}, - "plugins{.dll,.vst3,.so,.dylib}", - workingDirFont, - dpiScale - ); - break; } if (hasOpened) curFileDialog=type; //ImGui::GetIO().ConfigFlags|=ImGuiConfigFlags_NavEnableKeyboard; @@ -1659,23 +1604,23 @@ void FurnaceGUI::processDrags(int dragX, int dragY) { void FurnaceGUI::editOptions(bool topMenu) { char id[4096]; - if (ImGui::MenuItem("Cut",BIND_FOR(GUI_ACTION_PAT_CUT))) doCopy(true); - if (ImGui::MenuItem("Copy",BIND_FOR(GUI_ACTION_PAT_COPY))) doCopy(false); - if (ImGui::MenuItem("Paste",BIND_FOR(GUI_ACTION_PAT_PASTE))) doPaste(); - if (ImGui::BeginMenu("Paste Special")) { - if (ImGui::MenuItem("Paste Mix",BIND_FOR(GUI_ACTION_PAT_PASTE_MIX))) doPaste(GUI_PASTE_MODE_MIX_FG); - if (ImGui::MenuItem("Paste Mix (background)",BIND_FOR(GUI_ACTION_PAT_PASTE_MIX_BG))) doPaste(GUI_PASTE_MODE_MIX_BG); - if (ImGui::MenuItem("Paste Flood",BIND_FOR(GUI_ACTION_PAT_PASTE_FLOOD))) doPaste(GUI_PASTE_MODE_FLOOD); - if (ImGui::MenuItem("Paste Overflow",BIND_FOR(GUI_ACTION_PAT_PASTE_OVERFLOW))) doPaste(GUI_PASTE_MODE_OVERFLOW); + if (ImGui::MenuItem("cut",BIND_FOR(GUI_ACTION_PAT_CUT))) doCopy(true); + if (ImGui::MenuItem("copy",BIND_FOR(GUI_ACTION_PAT_COPY))) doCopy(false); + if (ImGui::MenuItem("paste",BIND_FOR(GUI_ACTION_PAT_PASTE))) doPaste(); + if (ImGui::BeginMenu("paste special...")) { + if (ImGui::MenuItem("paste mix",BIND_FOR(GUI_ACTION_PAT_PASTE_MIX))) doPaste(GUI_PASTE_MODE_MIX_FG); + if (ImGui::MenuItem("paste mix (background)",BIND_FOR(GUI_ACTION_PAT_PASTE_MIX_BG))) doPaste(GUI_PASTE_MODE_MIX_BG); + if (ImGui::MenuItem("paste flood",BIND_FOR(GUI_ACTION_PAT_PASTE_FLOOD))) doPaste(GUI_PASTE_MODE_FLOOD); + if (ImGui::MenuItem("paste overflow",BIND_FOR(GUI_ACTION_PAT_PASTE_OVERFLOW))) doPaste(GUI_PASTE_MODE_OVERFLOW); ImGui::EndMenu(); } - if (ImGui::MenuItem("Delete",BIND_FOR(GUI_ACTION_PAT_DELETE))) doDelete(); + if (ImGui::MenuItem("delete",BIND_FOR(GUI_ACTION_PAT_DELETE))) doDelete(); if (topMenu) { - if (ImGui::MenuItem("Select All",BIND_FOR(GUI_ACTION_PAT_SELECT_ALL))) doSelectAll(); + if (ImGui::MenuItem("select all",BIND_FOR(GUI_ACTION_PAT_SELECT_ALL))) doSelectAll(); } ImGui::Separator(); - ImGui::Text("Operation Mask"); + ImGui::Text("operation mask"); ImGui::SameLine(); ImGui::PushFont(patFont); @@ -1713,16 +1658,16 @@ void FurnaceGUI::editOptions(bool topMenu) { } ImGui::PopFont(); - ImGui::Text("Input Latch"); + ImGui::Text("input latch"); if (ImGui::MenuItem("set latch",BIND_FOR(GUI_ACTION_PAT_LATCH))) { // TODO } ImGui::Separator(); - if (ImGui::MenuItem("Increase Notes",BIND_FOR(GUI_ACTION_PAT_NOTE_UP))) doTranspose(1); - if (ImGui::MenuItem("Decrease Notes",BIND_FOR(GUI_ACTION_PAT_NOTE_DOWN))) doTranspose(-1); - if (ImGui::MenuItem("Increase Octaves",BIND_FOR(GUI_ACTION_PAT_OCTAVE_UP))) doTranspose(12); - if (ImGui::MenuItem("Decrease Octaves",BIND_FOR(GUI_ACTION_PAT_OCTAVE_DOWN))) doTranspose(-12); + if (ImGui::MenuItem("note up",BIND_FOR(GUI_ACTION_PAT_NOTE_UP))) doTranspose(1); + if (ImGui::MenuItem("note down",BIND_FOR(GUI_ACTION_PAT_NOTE_DOWN))) doTranspose(-1); + if (ImGui::MenuItem("octave up",BIND_FOR(GUI_ACTION_PAT_OCTAVE_UP))) doTranspose(12); + if (ImGui::MenuItem("octave down",BIND_FOR(GUI_ACTION_PAT_OCTAVE_DOWN))) doTranspose(-12); if (ImGui::InputInt("##TransposeAmount",&transposeAmount,1,1)) { if (transposeAmount<-96) transposeAmount=-96; if (transposeAmount>96) transposeAmount=96; @@ -1734,8 +1679,8 @@ void FurnaceGUI::editOptions(bool topMenu) { } ImGui::Separator(); - if (ImGui::MenuItem("Interpolate",BIND_FOR(GUI_ACTION_PAT_INTERPOLATE))) doInterpolate(); - if (ImGui::BeginMenu("Change Instrument")) { + if (ImGui::MenuItem("interpolate",BIND_FOR(GUI_ACTION_PAT_INTERPOLATE))) doInterpolate(); + if (ImGui::BeginMenu("change instrument...")) { if (e->song.ins.empty()) { ImGui::Text("no instruments available"); } @@ -1747,7 +1692,7 @@ void FurnaceGUI::editOptions(bool topMenu) { } ImGui::EndMenu(); } - if (ImGui::BeginMenu("Fade")) { + if (ImGui::BeginMenu("gradient/fade...")) { if (ImGui::InputInt("Start",&fadeMin,1,1)) { if (fadeMin<0) fadeMin=0; if (fadeMode) { @@ -1779,7 +1724,7 @@ void FurnaceGUI::editOptions(bool topMenu) { } ImGui::EndMenu(); } - if (ImGui::BeginMenu("Scale")) { + if (ImGui::BeginMenu("scale...")) { if (ImGui::InputFloat("##ScaleMax",&scaleMax,1,1,"%.1f%%")) { if (scaleMax<0.0f) scaleMax=0.0f; if (scaleMax>25600.0f) scaleMax=25600.0f; @@ -1790,7 +1735,7 @@ void FurnaceGUI::editOptions(bool topMenu) { } ImGui::EndMenu(); } - if (ImGui::BeginMenu("Randomize")) { + if (ImGui::BeginMenu("randomize...")) { if (ImGui::InputInt("Minimum",&randomizeMin,1,1)) { if (randomizeMin<0) randomizeMin=0; if (randomMode) { @@ -1825,22 +1770,22 @@ void FurnaceGUI::editOptions(bool topMenu) { } ImGui::EndMenu(); } - if (ImGui::MenuItem("Invert Values",BIND_FOR(GUI_ACTION_PAT_INVERT_VALUES))) doInvertValues(); + if (ImGui::MenuItem("invert values",BIND_FOR(GUI_ACTION_PAT_INVERT_VALUES))) doInvertValues(); ImGui::Separator(); - if (ImGui::MenuItem("Flip selection",BIND_FOR(GUI_ACTION_PAT_FLIP_SELECTION))) doFlip(); - if (ImGui::MenuItem("Shrink",BIND_FOR(GUI_ACTION_PAT_COLLAPSE_ROWS))) doCollapse(2); - if (ImGui::MenuItem("Expand",BIND_FOR(GUI_ACTION_PAT_EXPAND_ROWS))) doExpand(2); + if (ImGui::MenuItem("flip selection",BIND_FOR(GUI_ACTION_PAT_FLIP_SELECTION))) doFlip(); + if (ImGui::MenuItem("collapse",BIND_FOR(GUI_ACTION_PAT_COLLAPSE_ROWS))) doCollapse(2); + if (ImGui::MenuItem("expand",BIND_FOR(GUI_ACTION_PAT_EXPAND_ROWS))) doExpand(2); if (topMenu) { ImGui::Separator(); - ImGui::MenuItem("Shrink Pattern",BIND_FOR(GUI_ACTION_PAT_COLLAPSE_PAT)); - ImGui::MenuItem("Expand Pattern",BIND_FOR(GUI_ACTION_PAT_EXPAND_PAT)); + ImGui::MenuItem("collapse pattern",BIND_FOR(GUI_ACTION_PAT_COLLAPSE_PAT)); + ImGui::MenuItem("expand pattern",BIND_FOR(GUI_ACTION_PAT_EXPAND_PAT)); ImGui::Separator(); - ImGui::MenuItem("Shrink Song",BIND_FOR(GUI_ACTION_PAT_COLLAPSE_SONG)); - ImGui::MenuItem("Expand Song",BIND_FOR(GUI_ACTION_PAT_EXPAND_SONG)); + ImGui::MenuItem("collapse song",BIND_FOR(GUI_ACTION_PAT_COLLAPSE_SONG)); + ImGui::MenuItem("expand song",BIND_FOR(GUI_ACTION_PAT_EXPAND_SONG)); } } @@ -2137,15 +2082,15 @@ bool FurnaceGUI::loop() { curWindow=GUI_WINDOW_NOTHING; ImGui::BeginMainMenuBar(); - if (ImGui::BeginMenu("File")) { - if (ImGui::MenuItem("New")) { + if (ImGui::BeginMenu("file")) { + if (ImGui::MenuItem("new...")) { if (modified) { showWarning("Unsaved changes! Are you sure?",GUI_WARN_NEW); } else { displayNew=true; } } - if (ImGui::MenuItem("Open",BIND_FOR(GUI_ACTION_OPEN))) { + if (ImGui::MenuItem("open...",BIND_FOR(GUI_ACTION_OPEN))) { if (modified) { showWarning("Unsaved changes! Are you sure?",GUI_WARN_OPEN); } else { @@ -2153,7 +2098,7 @@ bool FurnaceGUI::loop() { } } ImGui::Separator(); - if (ImGui::MenuItem("Save",BIND_FOR(GUI_ACTION_SAVE))) { + if (ImGui::MenuItem("save",BIND_FOR(GUI_ACTION_SAVE))) { if (curFileName=="" || curFileName==backupPath || e->song.version>=0xff00) { openFileDialog(GUI_FILE_SAVE); } else { @@ -2162,14 +2107,14 @@ bool FurnaceGUI::loop() { } } } - if (ImGui::MenuItem("Save As..",BIND_FOR(GUI_ACTION_SAVE_AS))) { + if (ImGui::MenuItem("save as...",BIND_FOR(GUI_ACTION_SAVE_AS))) { openFileDialog(GUI_FILE_SAVE); } - if (ImGui::MenuItem("Save As .dmf (1.0/legacy)..",BIND_FOR(GUI_ACTION_SAVE_AS))) { + if (ImGui::MenuItem("save as .dmf (1.0/legacy)...",BIND_FOR(GUI_ACTION_SAVE_AS))) { openFileDialog(GUI_FILE_SAVE_DMF_LEGACY); } ImGui::Separator(); - if (ImGui::BeginMenu("Save WAV")) { + if (ImGui::BeginMenu("export audio...")) { if (ImGui::MenuItem("one file")) { openFileDialog(GUI_FILE_EXPORT_AUDIO_ONE); } @@ -2184,7 +2129,7 @@ bool FurnaceGUI::loop() { } ImGui::EndMenu(); } - if (ImGui::BeginMenu("Save VGM")) { + if (ImGui::BeginMenu("export VGM...")) { ImGui::Text("settings:"); ImGui::Checkbox("loop",&vgmExportLoop); ImGui::Text("systems to export:");; @@ -2213,13 +2158,13 @@ bool FurnaceGUI::loop() { ImGui::EndMenu(); } ImGui::Separator(); - if (ImGui::BeginMenu("Add System")) { + if (ImGui::BeginMenu("add system...")) { for (int j=0; availableSystems[j]; j++) { sysAddOption((DivSystem)availableSystems[j]); } ImGui::EndMenu(); } - if (ImGui::BeginMenu("Configure System")) { + if (ImGui::BeginMenu("configure system...")) { for (int i=0; isong.systemLen; i++) { if (ImGui::TreeNode(fmt::sprintf("%d. %s##_SYSP%d",i+1,getSystemName(e->song.system[i]),i).c_str())) { drawSysConf(i); @@ -2228,7 +2173,7 @@ bool FurnaceGUI::loop() { } ImGui::EndMenu(); } - if (ImGui::BeginMenu("Change System")) { + if (ImGui::BeginMenu("change system...")) { for (int i=0; isong.systemLen; i++) { if (ImGui::BeginMenu(fmt::sprintf("%d. %s##_SYSC%d",i+1,getSystemName(e->song.system[i]),i).c_str())) { for (int j=0; availableSystems[j]; j++) { @@ -2239,7 +2184,7 @@ bool FurnaceGUI::loop() { } ImGui::EndMenu(); } - if (ImGui::BeginMenu("Remove System")) { + if (ImGui::BeginMenu("remove system...")) { for (int i=0; isong.systemLen; i++) { if (ImGui::MenuItem(fmt::sprintf("%d. %s##_SYSR%d",i+1,getSystemName(e->song.system[i]),i).c_str())) { if (!e->removeSystem(i)) { @@ -2250,7 +2195,7 @@ bool FurnaceGUI::loop() { ImGui::EndMenu(); } ImGui::Separator(); - if (ImGui::MenuItem("Restore Backup",BIND_FOR(GUI_ACTION_OPEN_BACKUP))) { + if (ImGui::MenuItem("restore backup",BIND_FOR(GUI_ACTION_OPEN_BACKUP))) { doAction(GUI_ACTION_OPEN_BACKUP); } ImGui::Separator(); @@ -2263,32 +2208,32 @@ bool FurnaceGUI::loop() { } ImGui::EndMenu(); } - if (ImGui::BeginMenu("Edit")) { - if (ImGui::MenuItem("Undo",BIND_FOR(GUI_ACTION_UNDO))) doUndo(); - if (ImGui::MenuItem("Redo",BIND_FOR(GUI_ACTION_REDO))) doRedo(); + if (ImGui::BeginMenu("edit")) { + if (ImGui::MenuItem("undo",BIND_FOR(GUI_ACTION_UNDO))) doUndo(); + if (ImGui::MenuItem("redo",BIND_FOR(GUI_ACTION_REDO))) doRedo(); ImGui::Separator(); editOptions(true); /*ImGui::Separator(); ImGui::MenuItem("clear...");*/ ImGui::EndMenu(); } - if (ImGui::BeginMenu("Options")) { - if (ImGui::MenuItem("Visualizer",NULL,fancyPattern)) { + if (ImGui::BeginMenu("settings")) { + if (ImGui::MenuItem("visualizer",NULL,fancyPattern)) { fancyPattern=!fancyPattern; e->enableCommandStream(fancyPattern); e->getCommandStream(cmdStream); cmdStream.clear(); } - if (ImGui::MenuItem("Reset Layout")) { + if (ImGui::MenuItem("reset layout")) { showWarning("Are you sure you want to reset the workspace layout?",GUI_WARN_RESET_LAYOUT); } - if (ImGui::MenuItem("Settings",BIND_FOR(GUI_ACTION_WINDOW_SETTINGS))) { + if (ImGui::MenuItem("settings...",BIND_FOR(GUI_ACTION_WINDOW_SETTINGS))) { syncSettings(); settingsOpen=true; } ImGui::EndMenu(); } - if (ImGui::BeginMenu("Window")) { + if (ImGui::BeginMenu("window")) { if (ImGui::MenuItem("song information",BIND_FOR(GUI_ACTION_WINDOW_SONG_INFO),songInfoOpen)) songInfoOpen=!songInfoOpen; if (ImGui::MenuItem("instruments",BIND_FOR(GUI_ACTION_WINDOW_INS_LIST),insListOpen)) insListOpen=!insListOpen; if (ImGui::MenuItem("wavetables",BIND_FOR(GUI_ACTION_WINDOW_WAVE_LIST),waveListOpen)) waveListOpen=!waveListOpen; @@ -2313,13 +2258,12 @@ bool FurnaceGUI::loop() { ImGui::EndMenu(); } - if (ImGui::BeginMenu("Help")) { - if (ImGui::MenuItem("Reset Tutorial",BIND_FOR(GUI_ACTION_WINDOW_DEBUG))) debugOpen=!debugOpen; - if (ImGui::MenuItem("Manual",BIND_FOR(GUI_ACTION_PANIC))) e->syncReset(); - if (ImGui::MenuItem("About",BIND_FOR(GUI_ACTION_WINDOW_ABOUT))) { + if (ImGui::BeginMenu("help")) { + if (ImGui::MenuItem("debug menu",BIND_FOR(GUI_ACTION_WINDOW_DEBUG))) debugOpen=!debugOpen; + if (ImGui::MenuItem("panic",BIND_FOR(GUI_ACTION_PANIC))) e->syncReset(); + if (ImGui::MenuItem("about...",BIND_FOR(GUI_ACTION_WINDOW_ABOUT))) { aboutOpen=true; aboutScroll=0; - play(); } ImGui::EndMenu(); } @@ -2327,7 +2271,7 @@ bool FurnaceGUI::loop() { if (e->isPlaying()) { int totalTicks=e->getTotalTicks(); int totalSeconds=e->getTotalSeconds(); - ImGui::Text("| Slowness %d:%d @ %g BPM (%gHz) | Order %d/%d | Row %d/%d | %d:%.2d:%.2d.%.2d",e->getSpeed1(),e->getSpeed2(),e->getCurHz(),calcBPM(e->getSpeed1(),e->getSpeed2(),e->getCurHz()),e->getOrder(),e->song.ordersLen,e->getRow(),e->song.patLen,totalSeconds/3600,(totalSeconds/60)%60,totalSeconds%60,totalTicks/10000); + 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->getOrder(),e->song.ordersLen,e->getRow(),e->song.patLen,totalSeconds/3600,(totalSeconds/60)%60,totalSeconds%60,totalTicks/10000); } else { bool hasInfo=false; String info; @@ -2435,7 +2379,6 @@ bool FurnaceGUI::loop() { break; case GUI_FILE_INS_OPEN: case GUI_FILE_INS_SAVE: - case GUI_FILE_FIND_KONTAKT: workingDirIns=fileDialog->getPath()+DIR_SEPARATOR_STR; break; case GUI_FILE_WAVE_OPEN: @@ -2583,9 +2526,6 @@ bool FurnaceGUI::loop() { case GUI_FILE_EXPORT_ROM: showError("Coming soon!"); break; - case GUI_FILE_FIND_KONTAKT: - showWarning("This is not the Kontakt 5 plugin!",GUI_WARN_KONTAKT); - break; case GUI_FILE_LOAD_MAIN_FONT: settings.mainFontPath=copyOfName; break; @@ -2655,58 +2595,41 @@ bool FurnaceGUI::loop() { if (ImGui::BeginPopupModal("Warning",NULL,ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::Text("%s",warnString.c_str()); - if (warnAction==GUI_WARN_KONTAKT) { - if (ImGui::Button("OK")) { - ImGui::CloseCurrentPopup(); + if (ImGui::Button(warnAction==GUI_WARN_GENERIC?"OK":"Yes")) { + ImGui::CloseCurrentPopup(); + switch (warnAction) { + case GUI_WARN_QUIT: + quit=true; + break; + case GUI_WARN_NEW: + displayNew=true; + break; + case GUI_WARN_OPEN: + openFileDialog(GUI_FILE_OPEN); + break; + case GUI_WARN_OPEN_BACKUP: + if (load(backupPath)>0) { + showError("No backup available! (or unable to open it)"); + } + break; + case GUI_WARN_OPEN_DROP: + if (load(nextFile)>0) { + showError(fmt::sprintf("Error while loading file! (%s)",lastError)); + } + nextFile=""; + break; + case GUI_WARN_RESET_LAYOUT: + ImGui::LoadIniSettingsFromMemory(defaultLayout); + ImGui::SaveIniSettingsToDisk(finalLayoutPath); + break; + case GUI_WARN_GENERIC: + break; } + } + if (warnAction!=GUI_WARN_GENERIC) { ImGui::SameLine(); - if (ImGui::Button(kButtons[curKStage])) { - curKStage++; - showWarning(kStages[curKStage],GUI_WARN_KONTAKT); - } - if ((ImGui::IsItemHovered() && curKStage==6 && !ImGui::IsItemActive()) || (curKStage==9 && !ImGui::IsItemHovered()) || curKStage==10) { - ImGui::SetWindowPos(ImVec2(ImGui::GetWindowPos().x+(rand()%256)-128,ImGui::GetWindowPos().y+(rand()%256)-128)); - e->noteOn(rand()%e->getTotalChannelCount(),(e->song.insLen==0)?0:(rand()%e->song.insLen),rand()%128); - } - } else { - if (ImGui::Button(warnAction==GUI_WARN_GENERIC?"OK":"Yes")) { + if (ImGui::Button("No")) { ImGui::CloseCurrentPopup(); - switch (warnAction) { - case GUI_WARN_QUIT: - quit=true; - break; - case GUI_WARN_NEW: - displayNew=true; - break; - case GUI_WARN_OPEN: - openFileDialog(GUI_FILE_OPEN); - break; - case GUI_WARN_OPEN_BACKUP: - if (load(backupPath)>0) { - showError("No backup available! (or unable to open it)"); - } - break; - case GUI_WARN_OPEN_DROP: - if (load(nextFile)>0) { - showError(fmt::sprintf("Error while loading file! (%s)",lastError)); - } - nextFile=""; - break; - case GUI_WARN_RESET_LAYOUT: - ImGui::LoadIniSettingsFromMemory(defaultLayout); - ImGui::SaveIniSettingsToDisk(finalLayoutPath); - break; - case GUI_WARN_GENERIC: - break; - case GUI_WARN_KONTAKT: - break; - } - } - if (warnAction!=GUI_WARN_GENERIC) { - ImGui::SameLine(); - if (ImGui::Button("No")) { - ImGui::CloseCurrentPopup(); - } } } ImGui::EndPopup(); @@ -2769,7 +2692,6 @@ bool FurnaceGUI::loop() { } bool FurnaceGUI::init() { - srand(time(NULL)); #ifndef __APPLE__ float dpiScaleF; #endif @@ -2830,7 +2752,7 @@ bool FurnaceGUI::init() { SDL_Init(SDL_INIT_VIDEO); - sdlWin=SDL_CreateWindow("PlagiaTracker",SDL_WINDOWPOS_CENTERED,SDL_WINDOWPOS_CENTERED,scrW*dpiScale,scrH*dpiScale,SDL_WINDOW_RESIZABLE|SDL_WINDOW_ALLOW_HIGHDPI); + sdlWin=SDL_CreateWindow("Furnace",SDL_WINDOWPOS_CENTERED,SDL_WINDOWPOS_CENTERED,scrW*dpiScale,scrH*dpiScale,SDL_WINDOW_RESIZABLE|SDL_WINDOW_ALLOW_HIGHDPI); if (sdlWin==NULL) { logE("could not open window! %s\n",SDL_GetError()); return false; @@ -3029,7 +2951,6 @@ FurnaceGUI::FurnaceGUI(): loopRow(-1), loopEnd(-1), isClipping(0), - curKStage(0), extraChannelButtons(0), patNameTarget(-1), newSongCategory(0), diff --git a/src/gui/gui.h b/src/gui/gui.h index 1bcaec75e..bf2050f0c 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -192,8 +192,7 @@ enum FurnaceGUIFileDialogs { GUI_FILE_EXPORT_VGM, GUI_FILE_EXPORT_ROM, GUI_FILE_LOAD_MAIN_FONT, - GUI_FILE_LOAD_PAT_FONT, - GUI_FILE_FIND_KONTAKT + GUI_FILE_LOAD_PAT_FONT }; enum FurnaceGUIWarnings { @@ -203,8 +202,7 @@ enum FurnaceGUIWarnings { GUI_WARN_OPEN_BACKUP, GUI_WARN_OPEN_DROP, GUI_WARN_RESET_LAYOUT, - GUI_WARN_GENERIC, - GUI_WARN_KONTAKT + GUI_WARN_GENERIC }; enum FurnaceGUIFMAlgs { @@ -730,7 +728,6 @@ class FurnaceGUI { int loadJapanese; int fmLayout; int susPosition; - int seriousMode; unsigned int maxUndoSteps; String mainFontPath; String patFontPath; @@ -785,7 +782,6 @@ class FurnaceGUI { loadJapanese(0), fmLayout(0), susPosition(0), - seriousMode(0), maxUndoSteps(100), mainFontPath(""), patFontPath(""), @@ -797,7 +793,7 @@ class FurnaceGUI { char finalLayoutPath[4096]; int curIns, curWave, curSample, curOctave, oldRow, oldOrder, oldOrder1, editStep, exportLoops, soloChan, soloTimeout, orderEditMode, orderCursor; - int loopOrder, loopRow, loopEnd, isClipping, curKStage, extraChannelButtons, patNameTarget, newSongCategory; + int loopOrder, loopRow, loopEnd, isClipping, extraChannelButtons, patNameTarget, newSongCategory; bool editControlsOpen, ordersOpen, insListOpen, songInfoOpen, patternOpen, insEditOpen; bool waveListOpen, waveEditOpen, sampleListOpen, sampleEditOpen, aboutOpen, settingsOpen; bool mixerOpen, debugOpen, inspectorOpen, oscOpen, volMeterOpen, statsOpen, compatFlagsOpen; diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index 707cc7d76..01c603739 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -355,11 +355,9 @@ const int availableSystems[]={ DIV_SYSTEM_YM2610_FULL_EXT, DIV_SYSTEM_YM2610B, DIV_SYSTEM_YM2610B_EXT, - DIV_SYSTEM_KONTAKT_5, DIV_SYSTEM_AY8910, DIV_SYSTEM_AMIGA, DIV_SYSTEM_PCSPKR, - DIV_SYSTEM_HDA, DIV_SYSTEM_OPLL, DIV_SYSTEM_OPLL_DRUMS, DIV_SYSTEM_VRC7, @@ -385,35 +383,3 @@ const int availableSystems[]={ 0 // don't remove this last one! }; -const char* kStages[]={ - "Error while loading plugin: Use of force prohibited.", - "I said this is NOT Kontakt 5!", - "But I said this is not Kontakt 5!", - "You are the one who cannot realize this ain't Kontakt 5!", - "\"What are you smoking\" Are you serious! I am a furnace!", - "In your DAW, silly. You really thought Furnace was a DAW?", - "Okay, okay, if I load your Kontakt 5 will you be happy?", - "Fatal Python error: init_import_site: Failed to import the site module\n\ -Python runtime state: initialized\n\ -Traceback (most recent call last):\n\ - File \"/usr/lib/python3.9/site.py\", line 589, in \n\ - button_clicked()\n\ -ButtonClickedException", - "Wait, there's more! Wanna see Kontakt 5 for real?", - "Loading Kontakt 5 in 3...", - "2...", -}; - -const char* kButtons[]={ - "It IS Kontakt 5!", - "But it is Kontakt 5!", - "I repeat, this IS Kontakt 5! Are you blind or what?", - "But look at the file name! What are you smoking!", - "Who cares where is my damn Kontakt 5!", - "Then why is there a Kontakt 5 option in the menu!", - "Yes! Pretty please with a cherry on top!", - "Oh fuck you for wasting my day!", - "Not anymore! I'm so pissed off!", - "Shut the hell up! I'm past my bed time!", - "Smash every button in the keyboard" -}; \ No newline at end of file diff --git a/src/gui/guiConst.h b/src/gui/guiConst.h index 64855eaae..6b55df864 100644 --- a/src/gui/guiConst.h +++ b/src/gui/guiConst.h @@ -28,6 +28,4 @@ extern const char* sampleDepths[17]; extern const char* resampleStrats[]; extern const int availableSystems[]; extern const char* guiActions[][2]; -extern const int altValues[24]; -extern const char* kStages[]; -extern const char* kButtons[]; \ No newline at end of file +extern const int altValues[24]; \ No newline at end of file diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index a49919dd7..9cf382cf7 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1127,7 +1127,6 @@ void FurnaceGUI::drawInsEdit() { if (!insEditOpen) return; ImGui::SetNextWindowSizeConstraints(ImVec2(440.0f*dpiScale,400.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); if (ImGui::Begin("Instrument Editor",&insEditOpen,settings.allowEditDocking?0:ImGuiWindowFlags_NoDocking)) { - if (curKStage==10) ImGui::SetWindowPos(ImVec2(ImGui::GetWindowPos().x+(rand()%256)-128,ImGui::GetWindowPos().y+(rand()%256)-128)); if (curIns<0 || curIns>=(int)e->song.ins.size()) { ImGui::Text("no instrument selected"); } else { diff --git a/src/gui/mixer.cpp b/src/gui/mixer.cpp index 14e682307..04710176c 100644 --- a/src/gui/mixer.cpp +++ b/src/gui/mixer.cpp @@ -30,7 +30,6 @@ void FurnaceGUI::drawMixer() { ImGui::SetNextWindowSizeConstraints(ImVec2(400.0f*dpiScale,200.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); if (ImGui::Begin("Mixer",&mixerOpen,settings.allowEditDocking?0:ImGuiWindowFlags_NoDocking)) { char id[32]; - if (curKStage==10) ImGui::SetWindowPos(ImVec2(ImGui::GetWindowPos().x+(rand()%256)-128,ImGui::GetWindowPos().y+(rand()%256)-128)); if (ImGui::SliderFloat("Master Volume",&e->song.masterVol,0,3,"%.2fx")) { if (e->song.masterVol<0) e->song.masterVol=0; if (e->song.masterVol>3) e->song.masterVol=3; diff --git a/src/gui/newSong.cpp b/src/gui/newSong.cpp index 6fac285cb..d6188b3e5 100644 --- a/src/gui/newSong.cpp +++ b/src/gui/newSong.cpp @@ -85,9 +85,6 @@ void FurnaceGUI::drawNewSong() { selEnd=SelectionPoint(); cursor=SelectionPoint(); updateWindowTitle(); - if (e->song.system[0]==DIV_SYSTEM_KONTAKT_5) { - showError("Kontakt not installed or detected!\nPlease use the \"Set plugin path\" option in the Configure System menu first."); - } ImGui::CloseCurrentPopup(); } } \ No newline at end of file diff --git a/src/gui/orders.cpp b/src/gui/orders.cpp index ca1255ead..37faba8d4 100644 --- a/src/gui/orders.cpp +++ b/src/gui/orders.cpp @@ -30,7 +30,6 @@ void FurnaceGUI::drawOrders() { } if (!ordersOpen) return; if (ImGui::Begin("Orders",&ordersOpen)) { - if (curKStage==10) ImGui::SetWindowPos(ImVec2(ImGui::GetWindowPos().x+(rand()%256)-128,ImGui::GetWindowPos().y+(rand()%256)-128)); float regionX=ImGui::GetContentRegionAvail().x; ImVec2 prevSpacing=ImGui::GetStyle().ItemSpacing; ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,ImVec2(1.0f*dpiScale,1.0f*dpiScale)); diff --git a/src/gui/osc.cpp b/src/gui/osc.cpp index ff205ef5c..0bdcdf8a0 100644 --- a/src/gui/osc.cpp +++ b/src/gui/osc.cpp @@ -31,7 +31,6 @@ void FurnaceGUI::drawOsc() { ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,ImVec2(0,0)); ImGui::PushStyleVar(ImGuiStyleVar_ItemInnerSpacing,ImVec2(0,0)); if (ImGui::Begin("Oscilloscope",&oscOpen)) { - if (curKStage==10) ImGui::SetWindowPos(ImVec2(ImGui::GetWindowPos().x+(rand()%256)-128,ImGui::GetWindowPos().y+(rand()%256)-128)); float values[512]; for (int i=0; i<512; i++) { int pos=i*e->oscSize/512; diff --git a/src/gui/pattern.cpp b/src/gui/pattern.cpp index 298260c30..79612827c 100644 --- a/src/gui/pattern.cpp +++ b/src/gui/pattern.cpp @@ -27,7 +27,7 @@ #include const FurnaceGUIColors fxColors[16]={ - GUI_COLOR_PATTERN_EFFECT_INVALID, // 00 + GUI_COLOR_PATTERN_EFFECT_MISC, // 00 GUI_COLOR_PATTERN_EFFECT_PITCH, // 01 GUI_COLOR_PATTERN_EFFECT_PITCH, // 02 GUI_COLOR_PATTERN_EFFECT_PITCH, // 03 @@ -192,22 +192,22 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int // instrument if (pat->data[i][2]==-1) { ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_INACTIVE]); - sprintf(id,"...##PI_%d_%d",i,j); + sprintf(id,"..##PI_%d_%d",i,j); } else { ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_INS]); - sprintf(id,"%.3d##PI_%d_%d",pat->data[i][2],i,j); + sprintf(id,"%.2X##PI_%d_%d",pat->data[i][2],i,j); } ImGui::SameLine(0.0f,0.0f); if (cursorIns) { ImGui::PushStyleColor(ImGuiCol_Header,uiColors[GUI_COLOR_PATTERN_CURSOR]); ImGui::PushStyleColor(ImGuiCol_HeaderActive,uiColors[GUI_COLOR_PATTERN_CURSOR_ACTIVE]); ImGui::PushStyleColor(ImGuiCol_HeaderHovered,uiColors[GUI_COLOR_PATTERN_CURSOR_HOVER]); - ImGui::Selectable(id,true,ImGuiSelectableFlags_NoPadWithHalfSpacing,threeChars); + ImGui::Selectable(id,true,ImGuiSelectableFlags_NoPadWithHalfSpacing,twoChars); demandX=ImGui::GetCursorPosX(); ImGui::PopStyleColor(3); } else { if (selectedIns) ImGui::PushStyleColor(ImGuiCol_Header,uiColors[GUI_COLOR_PATTERN_SELECTION]); - ImGui::Selectable(id,isPushing || selectedIns,ImGuiSelectableFlags_NoPadWithHalfSpacing,threeChars); + ImGui::Selectable(id,isPushing || selectedIns,ImGuiSelectableFlags_NoPadWithHalfSpacing,twoChars); if (selectedIns) ImGui::PopStyleColor(); } if (ImGui::IsItemClicked()) { @@ -220,13 +220,13 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int // volume if (pat->data[i][3]==-1) { - sprintf(id,"...##PV_%d_%d",i,j); + sprintf(id,"..##PV_%d_%d",i,j); ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_INACTIVE]); } else { int volColor=(pat->data[i][3]*127)/chanVolMax; if (volColor>127) volColor=127; if (volColor<0) volColor=0; - sprintf(id,"%3d##PV_%d_%d",pat->data[i][3],i,j); + sprintf(id,"%.2X##PV_%d_%d",pat->data[i][3],i,j); ImGui::PushStyleColor(ImGuiCol_Text,volColors[volColor]); } ImGui::SameLine(0.0f,0.0f); @@ -234,12 +234,12 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int ImGui::PushStyleColor(ImGuiCol_Header,uiColors[GUI_COLOR_PATTERN_CURSOR]); ImGui::PushStyleColor(ImGuiCol_HeaderActive,uiColors[GUI_COLOR_PATTERN_CURSOR_ACTIVE]); ImGui::PushStyleColor(ImGuiCol_HeaderHovered,uiColors[GUI_COLOR_PATTERN_CURSOR_HOVER]); - ImGui::Selectable(id,true,ImGuiSelectableFlags_NoPadWithHalfSpacing,threeChars); + ImGui::Selectable(id,true,ImGuiSelectableFlags_NoPadWithHalfSpacing,twoChars); demandX=ImGui::GetCursorPosX(); ImGui::PopStyleColor(3); } else { if (selectedVol) ImGui::PushStyleColor(ImGuiCol_Header,uiColors[GUI_COLOR_PATTERN_SELECTION]); - ImGui::Selectable(id,isPushing || selectedVol,ImGuiSelectableFlags_NoPadWithHalfSpacing,threeChars); + ImGui::Selectable(id,isPushing || selectedVol,ImGuiSelectableFlags_NoPadWithHalfSpacing,twoChars); if (selectedVol) ImGui::PopStyleColor(); } if (ImGui::IsItemClicked()) { @@ -260,10 +260,10 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int // effect if (pat->data[i][index]==-1) { - sprintf(id,"...##PE%d_%d_%d",k,i,j); + sprintf(id,"..##PE%d_%d_%d",k,i,j); ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_INACTIVE]); } else { - sprintf(id,"%03d##PE%d_%d_%d",pat->data[i][index],k,i,j); + sprintf(id,"%.2X##PE%d_%d_%d",pat->data[i][index],k,i,j); if (pat->data[i][index]<0x10) { ImGui::PushStyleColor(ImGuiCol_Text,uiColors[fxColors[pat->data[i][index]]]); } else if (pat->data[i][index]<0x20) { @@ -291,12 +291,12 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int ImGui::PushStyleColor(ImGuiCol_Header,uiColors[GUI_COLOR_PATTERN_CURSOR]); ImGui::PushStyleColor(ImGuiCol_HeaderActive,uiColors[GUI_COLOR_PATTERN_CURSOR_ACTIVE]); ImGui::PushStyleColor(ImGuiCol_HeaderHovered,uiColors[GUI_COLOR_PATTERN_CURSOR_HOVER]); - ImGui::Selectable(id,true,ImGuiSelectableFlags_NoPadWithHalfSpacing,threeChars); + ImGui::Selectable(id,true,ImGuiSelectableFlags_NoPadWithHalfSpacing,twoChars); demandX=ImGui::GetCursorPosX(); ImGui::PopStyleColor(3); } else { if (selectedEffect) ImGui::PushStyleColor(ImGuiCol_Header,uiColors[GUI_COLOR_PATTERN_SELECTION]); - ImGui::Selectable(id,isPushing || selectedEffect,ImGuiSelectableFlags_NoPadWithHalfSpacing,threeChars); + ImGui::Selectable(id,isPushing || selectedEffect,ImGuiSelectableFlags_NoPadWithHalfSpacing,twoChars); if (selectedEffect) ImGui::PopStyleColor(); } if (ImGui::IsItemClicked()) { @@ -308,21 +308,21 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int // effect value if (pat->data[i][index+1]==-1) { - sprintf(id,"...##PF%d_%d_%d",k,i,j); + sprintf(id,"..##PF%d_%d_%d",k,i,j); } else { - sprintf(id,"%03d##PF%d_%d_%d",pat->data[i][index+1],k,i,j); + sprintf(id,"%.2X##PF%d_%d_%d",pat->data[i][index+1],k,i,j); } ImGui::SameLine(0.0f,0.0f); if (cursorEffectVal) { ImGui::PushStyleColor(ImGuiCol_Header,uiColors[GUI_COLOR_PATTERN_CURSOR]); ImGui::PushStyleColor(ImGuiCol_HeaderActive,uiColors[GUI_COLOR_PATTERN_CURSOR_ACTIVE]); ImGui::PushStyleColor(ImGuiCol_HeaderHovered,uiColors[GUI_COLOR_PATTERN_CURSOR_HOVER]); - ImGui::Selectable(id,true,ImGuiSelectableFlags_NoPadWithHalfSpacing,threeChars); + ImGui::Selectable(id,true,ImGuiSelectableFlags_NoPadWithHalfSpacing,twoChars); demandX=ImGui::GetCursorPosX(); ImGui::PopStyleColor(3); } else { if (selectedEffectVal) ImGui::PushStyleColor(ImGuiCol_Header,uiColors[GUI_COLOR_PATTERN_SELECTION]); - ImGui::Selectable(id,isPushing || selectedEffectVal,ImGuiSelectableFlags_NoPadWithHalfSpacing,threeChars); + ImGui::Selectable(id,isPushing || selectedEffectVal,ImGuiSelectableFlags_NoPadWithHalfSpacing,twoChars); if (selectedEffectVal) ImGui::PopStyleColor(); } if (ImGui::IsItemClicked()) { @@ -377,7 +377,6 @@ void FurnaceGUI::drawPattern() { } ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding,ImVec2(0.0f,0.0f)); if (ImGui::Begin("Pattern",&patternOpen,settings.avoidRaisingPattern?ImGuiWindowFlags_NoBringToFrontOnFocus:0)) { - if (curKStage==10) ImGui::SetWindowPos(ImVec2(ImGui::GetWindowPos().x+(rand()%256)-128,ImGui::GetWindowPos().y+(rand()%256)-128)); //ImGui::SetWindowSize(ImVec2(scrW*dpiScale,scrH*dpiScale)); patWindowPos=ImGui::GetWindowPos(); patWindowSize=ImGui::GetWindowSize(); @@ -398,7 +397,7 @@ void FurnaceGUI::drawPattern() { ImGui::TableSetupColumn("pos",ImGuiTableColumnFlags_WidthFixed); char chanID[2048]; float lineHeight=(ImGui::GetTextLineHeight()+2*dpiScale); - int curRow=(e->getOrder()&1 && !settings.seriousMode)?(e->song.patLen-e->getRow()-1):e->getRow(); + int curRow=e->getRow(); if (e->isPlaying() && followPattern) updateScroll(curRow); if (nextScroll>-0.5f) { ImGui::SetScrollY(nextScroll); @@ -561,7 +560,7 @@ void FurnaceGUI::drawPattern() { } ImGui::TableNextColumn(); if (e->hasExtValue()) { - ImGui::TextColored(uiColors[GUI_COLOR_EE_VALUE]," %d",e->getExtValue()); + ImGui::TextColored(uiColors[GUI_COLOR_EE_VALUE]," %.2X",e->getExtValue()); } float oneCharSize=ImGui::CalcTextSize("A").x; threeChars=ImVec2(oneCharSize*3.0f,lineHeight); @@ -583,14 +582,8 @@ void FurnaceGUI::drawPattern() { } ImGui::EndDisabled(); // active area - if (e->getOrder()&1 && !settings.seriousMode) { - for (int i=e->song.patLen-1; i>=0; i--) { - patternRow(i,e->isPlaying(),lineHeight,chans,ord); - } - } else { - for (int i=0; isong.patLen; i++) { - patternRow(i,e->isPlaying(),lineHeight,chans,ord); - } + for (int i=0; isong.patLen; i++) { + patternRow(i,e->isPlaying(),lineHeight,chans,ord); } // next pattern ImGui::BeginDisabled(); diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index cfa310b3c..f92c2b35c 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -33,21 +33,6 @@ void FurnaceGUI::initSystemPresets() { FurnaceGUISysCategory cat; - cat=FurnaceGUISysCategory("FT Studio"); - cat.systems.push_back(FurnaceGUISysDef( - "Realtek HD Audio", { - DIV_SYSTEM_HDA, 64, 0, 0, - 0 - } - )); - cat.systems.push_back(FurnaceGUISysDef( - "Kontakt 5", { - DIV_SYSTEM_KONTAKT_5, 64, 0, 0, - 0 - } - )); - sysCategories.push_back(cat); - cat=FurnaceGUISysCategory("FM"); cat.systems.push_back(FurnaceGUISysDef( "Yamaha YM2612", { diff --git a/src/gui/regView.cpp b/src/gui/regView.cpp index 6668528d7..9755e4a83 100644 --- a/src/gui/regView.cpp +++ b/src/gui/regView.cpp @@ -27,7 +27,6 @@ void FurnaceGUI::drawRegView() { } if (!regViewOpen) return; if (ImGui::Begin("Register View",®ViewOpen)) { - if (curKStage==10) ImGui::SetWindowPos(ImVec2(ImGui::GetWindowPos().x+(rand()%256)-128,ImGui::GetWindowPos().y+(rand()%256)-128)); for (int i=0; isong.systemLen; i++) { ImGui::Text("%d. %s",i+1,getSystemName(e->song.system[i])); int size=0; diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index 80b57ef0b..0998856cc 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -36,7 +36,6 @@ void FurnaceGUI::drawSampleEdit() { } if (!sampleEditOpen) return; if (ImGui::Begin("Sample Editor",&sampleEditOpen,settings.allowEditDocking?0:ImGuiWindowFlags_NoDocking)) { - if (curKStage==10) ImGui::SetWindowPos(ImVec2(ImGui::GetWindowPos().x+(rand()%256)-128,ImGui::GetWindowPos().y+(rand()%256)-128)); if (curSample<0 || curSample>=(int)e->song.sample.size()) { ImGui::Text("no sample selected"); } else { diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 5c5b7e674..b9e616da3 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -211,12 +211,6 @@ void FurnaceGUI::drawSettings() { if (ImGui::Begin("Settings",NULL,ImGuiWindowFlags_NoDocking)) { if (ImGui::BeginTabBar("settingsTab")) { if (ImGui::BeginTabItem("General")) { - bool seriousModeB=settings.seriousMode; - if (ImGui::Checkbox("END MY SUFFERING!",&seriousModeB)) { - settings.seriousMode=seriousModeB; - showError("You know the rules, and so do I!"); - } - ImGui::Text("Toggle channel solo on:"); if (ImGui::RadioButton("Right-click or double-click##soloA",settings.soloAction==0)) { settings.soloAction=0; @@ -1355,7 +1349,6 @@ void FurnaceGUI::syncSettings() { settings.loadJapanese=e->getConfInt("loadJapanese",0); settings.fmLayout=e->getConfInt("fmLayout",0); settings.susPosition=e->getConfInt("susPosition",0); - settings.seriousMode=e->getConfInt("seriousMode",0); clampSetting(settings.mainFontSize,2,96); clampSetting(settings.patFontSize,2,96); @@ -1402,7 +1395,6 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.loadJapanese,0,1); clampSetting(settings.fmLayout,0,3); clampSetting(settings.susPosition,0,1); - clampSetting(settings.seriousMode,0,1); // keybinds LOAD_KEYBIND(GUI_ACTION_OPEN,FURKMOD_CMD|SDLK_o); @@ -1646,7 +1638,6 @@ void FurnaceGUI::commitSettings() { e->setConf("loadJapanese",settings.loadJapanese); e->setConf("fmLayout",settings.fmLayout); e->setConf("susPosition",settings.susPosition); - e->setConf("seriousMode",settings.seriousMode); PUT_UI_COLOR(GUI_COLOR_BACKGROUND); PUT_UI_COLOR(GUI_COLOR_FRAME_BACKGROUND); diff --git a/src/gui/songInfo.cpp b/src/gui/songInfo.cpp index 3d4934b40..b5923801e 100644 --- a/src/gui/songInfo.cpp +++ b/src/gui/songInfo.cpp @@ -29,7 +29,6 @@ void FurnaceGUI::drawSongInfo() { } if (!songInfoOpen) return; if (ImGui::Begin("Song Information",&songInfoOpen)) { - if (curKStage==10) ImGui::SetWindowPos(ImVec2(ImGui::GetWindowPos().x+(rand()%256)-128,ImGui::GetWindowPos().y+(rand()%256)-128)); if (ImGui::BeginTable("NameAuthor",2,ImGuiTableFlags_SizingStretchProp)) { ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,0.0); ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.0); diff --git a/src/gui/songNotes.cpp b/src/gui/songNotes.cpp index 5e1f56844..7c7cbdaa9 100644 --- a/src/gui/songNotes.cpp +++ b/src/gui/songNotes.cpp @@ -30,7 +30,6 @@ void FurnaceGUI::drawNotes() { } if (!notesOpen) return; if (ImGui::Begin("Song Comments",¬esOpen)) { - if (curKStage==10) ImGui::SetWindowPos(ImVec2(ImGui::GetWindowPos().x+(rand()%256)-128,ImGui::GetWindowPos().y+(rand()%256)-128)); ImGui::InputTextMultiline("##SongNotes",&e->song.notes,ImGui::GetContentRegionAvail()); } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_NOTES; diff --git a/src/gui/stats.cpp b/src/gui/stats.cpp index 4f012eb6e..274be2eda 100644 --- a/src/gui/stats.cpp +++ b/src/gui/stats.cpp @@ -28,7 +28,6 @@ void FurnaceGUI::drawStats() { } if (!statsOpen) return; if (ImGui::Begin("Statistics",&statsOpen)) { - if (curKStage==10) ImGui::SetWindowPos(ImVec2(ImGui::GetWindowPos().x+(rand()%256)-128,ImGui::GetWindowPos().y+(rand()%256)-128)); String adpcmAUsage=fmt::sprintf("%d/16384KB",e->adpcmAMemLen/1024); String adpcmBUsage=fmt::sprintf("%d/16384KB",e->adpcmBMemLen/1024); String qsoundUsage=fmt::sprintf("%d/16384KB",e->qsoundMemLen/1024); diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index b633f4d7e..8260b0a42 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -386,15 +386,6 @@ void FurnaceGUI::drawSysConf(int i) { case DIV_SYSTEM_PET: ImGui::Text("nothing to configure"); break; - case DIV_SYSTEM_HDA: - ImGui::Text("nothing to configure. Did you expect HD Audio to be any more capable?"); - break; - case DIV_SYSTEM_KONTAKT_5: - if (ImGui::Button("Try to set plugin path")) { - // LOL - openFileDialog(GUI_FILE_FIND_KONTAKT); - } - break; default: if (ImGui::Checkbox("PAL",&sysPal)) { e->setSysFlags(i,sysPal,restart); diff --git a/src/gui/volMeter.cpp b/src/gui/volMeter.cpp index 665df460e..29f62c969 100644 --- a/src/gui/volMeter.cpp +++ b/src/gui/volMeter.cpp @@ -34,7 +34,6 @@ void FurnaceGUI::drawVolMeter() { ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,ImVec2(0,0)); ImGui::PushStyleVar(ImGuiStyleVar_ItemInnerSpacing,ImVec2(0,0)); if (ImGui::Begin("Volume Meter",&volMeterOpen)) { - if (curKStage==10) ImGui::SetWindowPos(ImVec2(ImGui::GetWindowPos().x+(rand()%256)-128,ImGui::GetWindowPos().y+(rand()%256)-128)); ImDrawList* dl=ImGui::GetWindowDrawList(); bool aspectRatio=(ImGui::GetWindowSize().x/ImGui::GetWindowSize().y)>1.0; diff --git a/src/gui/waveEdit.cpp b/src/gui/waveEdit.cpp index 1a182db9d..e1359da93 100644 --- a/src/gui/waveEdit.cpp +++ b/src/gui/waveEdit.cpp @@ -32,7 +32,6 @@ void FurnaceGUI::drawWaveEdit() { float wavePreview[256]; ImGui::SetNextWindowSizeConstraints(ImVec2(450.0f*dpiScale,300.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); if (ImGui::Begin("Wavetable Editor",&waveEditOpen,settings.allowEditDocking?0:ImGuiWindowFlags_NoDocking)) { - if (curKStage==10) ImGui::SetWindowPos(ImVec2(ImGui::GetWindowPos().x+(rand()%256)-128,ImGui::GetWindowPos().y+(rand()%256)-128)); if (curWave<0 || curWave>=(int)e->song.wave.size()) { ImGui::Text("no wavetable selected"); } else { diff --git a/src/main.cpp b/src/main.cpp index 0e4848faf..c1afe4388 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -127,7 +127,7 @@ bool pLogLevel(String val) { } bool pVersion(String) { - printf("Furniture version " DIV_VERSION ".\n\n"); + printf("Furnace version " DIV_VERSION ".\n\n"); printf("copyright (C) 2021-2022 tildearrow and contributors.\n"); printf("licensed under the GNU General Public License version 2 or later\n"); printf(".\n\n"); @@ -135,7 +135,7 @@ bool pVersion(String) { printf("pass the -warranty parameter for more information.\n\n"); printf("DISCLAIMER: this program is not affiliated with Delek in any form.\n"); printf("\n"); - printf("furniture is powered by:\n"); + printf("furnace is powered by:\n"); printf("- libsndfile by Erik de Castro Lopo and rest of libsndfile team (LGPLv2.1)\n"); printf("- SDL2 by Sam Lantinga (zlib license)\n"); printf("- zlib by Jean-loup Gailly and Mark Adler (zlib license)\n"); @@ -235,7 +235,7 @@ void initParams() { params.push_back(TAParam("l","loops",true,pLoops,"","set number of loops (-1 means loop forever)")); params.push_back(TAParam("o","outmode",true,pOutMode,"one|persys|perchan","set file output mode")); - params.push_back(TAParam("V","version",false,pVersion,"","view information about Furniture.")); + params.push_back(TAParam("V","version",false,pVersion,"","view information about Furnace.")); params.push_back(TAParam("W","warranty",false,pWarranty,"","view warranty disclaimer.")); } @@ -310,7 +310,7 @@ int main(int argc, char** argv) { logI("usage: %s file\n",argv[0]); return 1; } - logI("Furniture version " DIV_VERSION ".\n"); + logI("Furnace version " DIV_VERSION ".\n"); if (!fileName.empty()) { logI("loading module...\n"); FILE* f=ps_fopen(fileName.c_str(),"rb"); From 251da3a9d05cc3ec46a28fe86061bb60f94d4428 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 1 Apr 2022 05:24:35 -0500 Subject: [PATCH 486/637] dev75 - MIDI input improvements (mostly) --- CMakeLists.txt | 4 ++-- res/Info.plist | 6 +++--- src/engine/engine.h | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4d4366020..af46dee17 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,8 +13,8 @@ endif() set(CMAKE_CXX_STANDARD 14) set(CMAKE_PROJECT_VERSION_MAJOR 0) -set(CMAKE_PROJECT_VERSION_MINOR 5) -set(CMAKE_PROJECT_VERSION_PATCH 7) +set(CMAKE_PROJECT_VERSION_MINOR 6) +set(CMAKE_PROJECT_VERSION_PATCH 0) if (ANDROID) set(BUILD_GUI_DEFAULT OFF) diff --git a/res/Info.plist b/res/Info.plist index d6f1c4a03..d9528b157 100644 --- a/res/Info.plist +++ b/res/Info.plist @@ -15,17 +15,17 @@ CFBundleInfoDictionaryVersion 6.0 CFBundleLongVersionString - 0.5.7 + 0.6pre0 CFBundleName Furnace CFBundlePackageType APPL CFBundleShortVersionString - 0.5.7 + 0.6pre0 CFBundleSignature ???? CFBundleVersion - 0.5.7 + 0.6pre0 NSHumanReadableCopyright NSHighResolutionCapable diff --git a/src/engine/engine.h b/src/engine/engine.h index 938ed134c..4d75dfa93 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -42,8 +42,8 @@ #define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock(); #define BUSY_END isBusy.unlock(); softLocked=false; -#define DIV_VERSION "dev74" -#define DIV_ENGINE_VERSION 74 +#define DIV_VERSION "dev75" +#define DIV_ENGINE_VERSION 75 // for imports #define DIV_VERSION_MOD 0xff01 From abd5dd3a122ae8025631d203371c9ccbe68e2228 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 1 Apr 2022 13:02:37 -0500 Subject: [PATCH 487/637] TODO: rewrite getSystemSongName --- src/engine/sysDef.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index dfcbb1fc0..e16a55104 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -408,6 +408,7 @@ int DivEngine::getTotalChannelCount() { return chans; } +// TODO: rewrite this function (again). it's an unreliable mess. const char* DivEngine::getSongSystemName() { switch (song.systemLen) { case 0: From dfb4f211c1c623828db1db45ef5be9eaa2493b11 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 1 Apr 2022 13:47:58 -0500 Subject: [PATCH 488/637] PC speaker: volume macro (finally) --- src/engine/platform/pcspkr.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine/platform/pcspkr.cpp b/src/engine/platform/pcspkr.cpp index 1ff8ae0c8..5cfcdac3e 100644 --- a/src/engine/platform/pcspkr.cpp +++ b/src/engine/platform/pcspkr.cpp @@ -168,8 +168,8 @@ void DivPlatformPCSpeaker::tick() { for (int i=0; i<1; i++) { chan[i].std.next(); if (chan[i].std.hadVol) { - // ok, why are the volumes like that? - chan[i].outVol=chan[i].vol; + chan[i].outVol=(chan[i].vol && chan[i].std.vol); + on=chan[i].outVol; } if (chan[i].std.hadArp) { if (!chan[i].inPorta) { From 13d08b3cb6d33beec67d4dbcb9028e8d67c2550a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 1 Apr 2022 16:31:06 -0500 Subject: [PATCH 489/637] maybe fix another audio export crash --- src/engine/playback.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 619555ab5..bd6c5f370 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -173,7 +173,7 @@ int DivEngine::dispatchCmd(DivCommand c) { cmdStream.push_back(c); } - if (!skipping && output->midiOut!=NULL) { + if (output) if (!skipping && output->midiOut!=NULL) { if (output->midiOut->isDeviceOpen()) { int scaledVol=(chan[c.chan].volume*127)/MAX(1,chan[c.chan].volMax); if (scaledVol<0) scaledVol=0; From 81c8bf4e59d6fe846205cf39cc36943bee15d15e Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 1 Apr 2022 21:40:32 -0500 Subject: [PATCH 490/637] add ability to select VGM version when exporting by default it is 1.71 but please tell me if you want this to be changed --- src/engine/engine.h | 6 +- src/engine/sysDef.cpp | 43 +++++----- src/engine/vgmOps.cpp | 185 ++++++++++++++++++++++++++++++------------ src/gui/gui.cpp | 26 ++++-- src/gui/gui.h | 1 + src/gui/guiConst.cpp | 9 ++ src/gui/guiConst.h | 3 +- 7 files changed, 189 insertions(+), 84 deletions(-) diff --git a/src/engine/engine.h b/src/engine/engine.h index 4d75dfa93..c8f539e93 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -305,7 +305,7 @@ class DivEngine { // specify system to build ROM for. SafeWriter* buildROM(int sys); // dump to VGM. - SafeWriter* saveVGM(bool* sysToExport=NULL, bool loop=true); + SafeWriter* saveVGM(bool* sysToExport=NULL, bool loop=true, int version=0x171); // export to an audio file bool saveAudio(const char* path, int loops, DivAudioExportModes mode); // wait for audio export to finish @@ -317,8 +317,8 @@ class DivEngine { // notify wavetable change void notifyWaveChange(int wave); - // returns whether a system is VGM compatible - bool isVGMExportable(DivSystem which); + // returns the minimum VGM version which may carry the specified system, or 0 if none. + int minVGMVersion(DivSystem which); // save config bool saveConf(); diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index e16a55104..58a932605 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -1670,41 +1670,44 @@ DivInstrumentType DivEngine::getPreferInsType(int chan) { return DIV_INS_FM; } -bool DivEngine::isVGMExportable(DivSystem which) { +int DivEngine::minVGMVersion(DivSystem which) { switch (which) { - case DIV_SYSTEM_SMS: - case DIV_SYSTEM_GB: - case DIV_SYSTEM_PCE: - case DIV_SYSTEM_NES: - case DIV_SYSTEM_YM2151: case DIV_SYSTEM_YM2612: case DIV_SYSTEM_YM2612_EXT: + case DIV_SYSTEM_SMS: + case DIV_SYSTEM_OPLL: + case DIV_SYSTEM_OPLL_DRUMS: + case DIV_SYSTEM_VRC7: + case DIV_SYSTEM_YM2151: + return 0x150; // due to usage of data blocks + case DIV_SYSTEM_SEGAPCM: + case DIV_SYSTEM_SEGAPCM_COMPAT: case DIV_SYSTEM_YM2610: case DIV_SYSTEM_YM2610_EXT: case DIV_SYSTEM_YM2610_FULL: case DIV_SYSTEM_YM2610_FULL_EXT: case DIV_SYSTEM_YM2610B: case DIV_SYSTEM_YM2610B_EXT: - case DIV_SYSTEM_AY8910: - case DIV_SYSTEM_AY8930: - case DIV_SYSTEM_SAA1099: - case DIV_SYSTEM_QSOUND: - case DIV_SYSTEM_SEGAPCM: - case DIV_SYSTEM_SEGAPCM_COMPAT: - case DIV_SYSTEM_OPLL: - case DIV_SYSTEM_OPLL_DRUMS: - case DIV_SYSTEM_VRC7: - case DIV_SYSTEM_X1_010: - case DIV_SYSTEM_SWAN: case DIV_SYSTEM_OPL: case DIV_SYSTEM_OPL_DRUMS: case DIV_SYSTEM_OPL2: case DIV_SYSTEM_OPL2_DRUMS: case DIV_SYSTEM_OPL3: case DIV_SYSTEM_OPL3_DRUMS: - return true; + case DIV_SYSTEM_AY8910: + case DIV_SYSTEM_AY8930: + return 0x151; + case DIV_SYSTEM_GB: + case DIV_SYSTEM_PCE: + case DIV_SYSTEM_NES: + case DIV_SYSTEM_QSOUND: + return 0x161; + case DIV_SYSTEM_SAA1099: + case DIV_SYSTEM_X1_010: + case DIV_SYSTEM_SWAN: + return 0x171; default: - return false; + return 0; } - return false; + return 0; } diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index c5fa63f3d..d06bb195f 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -592,7 +592,11 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write } } -SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) { +SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) { + if (version<0x150) { + lastError="VGM version is too low"; + return NULL; + } stop(); repeatPattern=false; setOrder(0); @@ -679,7 +683,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) { // write header w->write("Vgm ",4); w->writeI(0); // will be written later - w->writeI(0x171); // VGM 1.71 + w->writeI(version); bool willExport[32]; bool isSecond[32]; @@ -709,6 +713,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) { if (sysToExport!=NULL) { if (!sysToExport[i]) continue; } + if (minVGMVersion(song.system[i])>version) continue; switch (song.system[i]) { case DIV_SYSTEM_SMS: if (!hasSN) { @@ -979,66 +984,138 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop) { w->writeI(0); // tick rate w->writeS(snNoiseConfig); w->writeC(snNoiseSize); - w->writeC(snFlags); + if (version>=0x151) { + w->writeC(snFlags); + } else { + w->writeC(0); + } w->writeI(hasOPN2); w->writeI(hasOPM); w->writeI(0); // data pointer. will be written later - w->writeI(hasSegaPCM); - w->writeI(segaPCMOffset); - w->writeI(hasRFC); - w->writeI(hasOPN); - w->writeI(hasOPNA); - w->writeI(hasOPNB); - w->writeI(hasOPL2); - w->writeI(hasOPL); - w->writeI(hasY8950); - w->writeI(hasOPL3); - w->writeI(hasOPL4); - w->writeI(hasOPX); - w->writeI(hasZ280); - w->writeI(hasRFC1); - w->writeI(hasPWM); - w->writeI(hasAY); - w->writeC(ayConfig); - w->writeC(ayFlags); - w->writeC(ayFlags); // OPN - w->writeC(ayFlags); // OPNA + if (version>=0x151) { + w->writeI(hasSegaPCM); + w->writeI(segaPCMOffset); + w->writeI(hasRFC); + w->writeI(hasOPN); + w->writeI(hasOPNA); + w->writeI(hasOPNB); + w->writeI(hasOPL2); + w->writeI(hasOPL); + w->writeI(hasY8950); + w->writeI(hasOPL3); + w->writeI(hasOPL4); + w->writeI(hasOPX); + w->writeI(hasZ280); + w->writeI(hasRFC1); + w->writeI(hasPWM); + w->writeI(hasAY); + w->writeC(ayConfig); + w->writeC(ayFlags); + w->writeC(ayFlags); // OPN + w->writeC(ayFlags); // OPNA + } else { + w->writeI(0); + w->writeI(0); + w->writeI(0); + w->writeI(0); + w->writeI(0); + w->writeI(0); + w->writeI(0); + w->writeI(0); + w->writeI(0); + w->writeI(0); + w->writeI(0); + w->writeI(0); + w->writeI(0); + w->writeI(0); + w->writeI(0); + w->writeI(0); + w->writeC(0); + w->writeC(0); + w->writeC(0); // OPN + w->writeC(0); // OPNA + } + // currently not used but is part of 1.60 w->writeC(0); // volume w->writeC(0); // reserved w->writeC(0); // loop count + // 1.51 w->writeC(0); // loop modifier - w->writeI(hasGB); - w->writeI(hasNES); - w->writeI(hasMultiPCM); - w->writeI(hasuPD7759); - w->writeI(hasOKIM6258); - w->writeC(0); // flags - w->writeC(0); // K flags - w->writeC(0); // C140 chip type - w->writeC(0); // reserved - w->writeI(hasOKIM6295); - w->writeI(hasK051649); - w->writeI(hasK054539); - w->writeI(hasPCE); - w->writeI(hasNamco); - w->writeI(hasK053260); - w->writeI(hasPOKEY); - w->writeI(hasQSound); - w->writeI(hasSCSP); + + if (version>=0x161) { + w->writeI(hasGB); + w->writeI(hasNES); + w->writeI(hasMultiPCM); + w->writeI(hasuPD7759); + w->writeI(hasOKIM6258); + w->writeC(0); // flags + w->writeC(0); // K flags + w->writeC(0); // C140 chip type + w->writeC(0); // reserved + w->writeI(hasOKIM6295); + w->writeI(hasK051649); + w->writeI(hasK054539); + w->writeI(hasPCE); + w->writeI(hasNamco); + w->writeI(hasK053260); + w->writeI(hasPOKEY); + w->writeI(hasQSound); + } else { + w->writeI(0); + w->writeI(0); + w->writeI(0); + w->writeI(0); + w->writeI(0); + w->writeC(0); // flags + w->writeC(0); // K flags + w->writeC(0); // C140 chip type + w->writeC(0); // reserved + w->writeI(0); + w->writeI(0); + w->writeI(0); + w->writeI(0); + w->writeI(0); + w->writeI(0); + w->writeI(0); + w->writeI(0); + } + if (version>=0x171) { + w->writeI(hasSCSP); + } else { + w->writeI(0); + } + // 1.70 w->writeI(0); // extra header - w->writeI(hasSwan); - w->writeI(hasVSU); - w->writeI(hasSAA); - w->writeI(hasES5503); - w->writeI(hasES5505); - w->writeC(0); // 5503 chans - w->writeC(0); // 5505 chans - w->writeC(0); // C352 clock divider - w->writeC(0); // reserved - w->writeI(hasX1); - w->writeI(hasC352); - w->writeI(hasGA20); - w->writeI(hasLynx); + // 1.71 + if (version>=0x171) { + w->writeI(hasSwan); + w->writeI(hasVSU); + w->writeI(hasSAA); + w->writeI(hasES5503); + w->writeI(hasES5505); + w->writeC(0); // 5503 chans + w->writeC(0); // 5505 chans + w->writeC(0); // C352 clock divider + w->writeC(0); // reserved + w->writeI(hasX1); + w->writeI(hasC352); + w->writeI(hasGA20); + w->writeI(hasLynx); + } else { + w->writeI(0); + w->writeI(0); + w->writeI(0); + w->writeI(0); + w->writeI(0); + w->writeC(0); // 5503 chans + w->writeC(0); // 5505 chans + w->writeC(0); // C352 clock divider + w->writeC(0); // reserved + w->writeI(0); + w->writeI(0); + w->writeI(0); + w->writeI(0); + } for (int i=0; i<6; i++) { // reserved w->writeI(0); } diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index e8a40a664..56e30acdb 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2131,14 +2131,27 @@ bool FurnaceGUI::loop() { } if (ImGui::BeginMenu("export VGM...")) { ImGui::Text("settings:"); + if (ImGui::BeginCombo("format version",fmt::sprintf("%d.%.2x",vgmExportVersion>>8,vgmExportVersion&0xff).c_str())) { + for (int i=0; i<6; i++) { + if (ImGui::Selectable(fmt::sprintf("%d.%.2x",vgmVersions[i]>>8,vgmVersions[i]&0xff).c_str(),vgmExportVersion==vgmVersions[i])) { + vgmExportVersion=vgmVersions[i]; + } + } + ImGui::EndCombo(); + } ImGui::Checkbox("loop",&vgmExportLoop); - ImGui::Text("systems to export:");; + ImGui::Text("systems to export:"); bool hasOneAtLeast=false; for (int i=0; isong.systemLen; i++) { - ImGui::BeginDisabled(!e->isVGMExportable(e->song.system[i])); + int minVersion=e->minVGMVersion(e->song.system[i]); + ImGui::BeginDisabled(minVersion>vgmExportVersion || minVersion==0); ImGui::Checkbox(fmt::sprintf("%d. %s##_SYSV%d",i+1,getSystemName(e->song.system[i]),i).c_str(),&willExport[i]); ImGui::EndDisabled(); - if (!e->isVGMExportable(e->song.system[i])) { + if (minVersion>vgmExportVersion) { + if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { + ImGui::SetTooltip("this system is only available in VGM %d.%.2x and higher!",minVersion>>8,minVersion&0xff); + } + } else if (minVersion==0) { if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled)) { ImGui::SetTooltip("this system is not supported by the VGM format!"); } @@ -2147,7 +2160,7 @@ bool FurnaceGUI::loop() { } } ImGui::Text("select the systems you wish to export,"); - ImGui::Text("but only up to 2 of each type."); + ImGui::Text("but only up to %d of each type.",(vgmExportVersion>=0x151)?2:1); if (hasOneAtLeast) { if (ImGui::MenuItem("click to export")) { openFileDialog(GUI_FILE_EXPORT_VGM); @@ -2504,7 +2517,7 @@ bool FurnaceGUI::loop() { MARK_MODIFIED; break; case GUI_FILE_EXPORT_VGM: { - SafeWriter* w=e->saveVGM(willExport,vgmExportLoop); + SafeWriter* w=e->saveVGM(willExport,vgmExportLoop,vgmExportVersion); if (w!=NULL) { FILE* f=fopen(copyOfName.c_str(),"wb"); if (f!=NULL) { @@ -2519,7 +2532,7 @@ bool FurnaceGUI::loop() { showWarning(e->getWarnings(),GUI_WARN_GENERIC); } } else { - showError("could not write VGM. dang it."); + showError(fmt::sprintf("could not write VGM! (%s)",e->getLastError())); } break; } @@ -2918,6 +2931,7 @@ FurnaceGUI::FurnaceGUI(): displayExporting(false), vgmExportLoop(true), displayNew(false), + vgmExportVersion(0x171), curFileDialog(GUI_FILE_OPEN), warnAction(GUI_WARN_OPEN), fileDialog(NULL), diff --git a/src/gui/gui.h b/src/gui/gui.h index bf2050f0c..e23be9902 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -643,6 +643,7 @@ class FurnaceGUI { bool quit, warnQuit, willCommit, edit, modified, displayError, displayExporting, vgmExportLoop; bool displayNew; bool willExport[32]; + int vgmExportVersion; FurnaceGUIFileDialogs curFileDialog; FurnaceGUIWarnings warnAction; diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index 01c603739..20085cfeb 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -70,6 +70,15 @@ const int altValues[24]={ 0, 10, 1, 11, 2, 3, 12, 4, 13, 5, 14, 6, 7, 15, 8, -1, 9, -1, -1, -1, -1, -1, -1, -1 }; +const int vgmVersions[6]={ + 0x150, + 0x151, + 0x160, + 0x161, + 0x170, + 0x171 +}; + const char* insTypes[DIV_INS_MAX]={ "Standard", "FM (4-operator)", diff --git a/src/gui/guiConst.h b/src/gui/guiConst.h index 6b55df864..dfafea88c 100644 --- a/src/gui/guiConst.h +++ b/src/gui/guiConst.h @@ -28,4 +28,5 @@ extern const char* sampleDepths[17]; extern const char* resampleStrats[]; extern const int availableSystems[]; extern const char* guiActions[][2]; -extern const int altValues[24]; \ No newline at end of file +extern const int altValues[24]; +extern const int vgmVersions[6]; \ No newline at end of file From ab5ed4413e0df38cc21a18a288eb6cda1558397a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 2 Apr 2022 00:11:44 -0500 Subject: [PATCH 491/637] GUI: usability fixes (ctrl-wheel) as of now Ctrl-wheel allows you to fine change sliders also added undo/redo buttons to sample editor --- src/gui/gui.cpp | 126 +++++++++++++++++++++++++++++++++++++++++ src/gui/gui.h | 9 +++ src/gui/insEdit.cpp | 103 ++++++++++++++++----------------- src/gui/mixer.cpp | 4 +- src/gui/sampleEdit.cpp | 16 ++++++ src/gui/sysConf.cpp | 8 +-- 6 files changed, 209 insertions(+), 57 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 56e30acdb..84b7d256c 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -386,6 +386,123 @@ void FurnaceGUI::decodeMMLStr(String& source, int* macro, unsigned char& macroLe } } +#define CW_ADDITION(T) \ + if (p_min!=NULL && p_max!=NULL) { \ + if (*((T*)p_min)>*((T*)p_max)) { \ + if (wheelY<0) { \ + if ((*((T*)p_data)-wheelY)>*((T*)p_min)) { \ + *((T*)p_data)=*((T*)p_min); \ + } else { \ + *((T*)p_data)-=wheelY; \ + } \ + } else { \ + if ((*((T*)p_data)-wheelY)<*((T*)p_max)) { \ + *((T*)p_data)=*((T*)p_max); \ + } else { \ + *((T*)p_data)-=wheelY; \ + } \ + } \ + } else { \ + if (wheelY>0) { \ + if ((*((T*)p_data)+wheelY)>*((T*)p_max)) { \ + *((T*)p_data)=*((T*)p_max); \ + } else { \ + *((T*)p_data)+=wheelY; \ + } \ + } else { \ + if ((*((T*)p_data)+wheelY)<*((T*)p_min)) { \ + *((T*)p_data)=*((T*)p_min); \ + } else { \ + *((T*)p_data)+=wheelY; \ + } \ + } \ + } \ + } + +bool FurnaceGUI::CWSliderScalar(const char* label, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags) { + if (ImGui::SliderScalar(label,data_type,p_data,p_min,p_max,format,flags)) { + return true; + } + if (ImGui::IsItemHovered() && ctrlWheeling) { + switch (data_type) { + case ImGuiDataType_U8: + CW_ADDITION(unsigned char); + break; + case ImGuiDataType_S8: + CW_ADDITION(signed char); + break; + case ImGuiDataType_U16: + CW_ADDITION(unsigned short); + break; + case ImGuiDataType_S16: + CW_ADDITION(short); + break; + case ImGuiDataType_U32: + CW_ADDITION(unsigned int); + break; + case ImGuiDataType_S32: + CW_ADDITION(int); + break; + case ImGuiDataType_Float: + CW_ADDITION(float); + break; + case ImGuiDataType_Double: + CW_ADDITION(double); + break; + } + return true; + } + return false; +} + +bool FurnaceGUI::CWVSliderScalar(const char* label, const ImVec2& size, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags) { + if (ImGui::VSliderScalar(label,size,data_type,p_data,p_min,p_max,format,flags)) { + return true; + } + if (ImGui::IsItemHovered() && ctrlWheeling) { + switch (data_type) { + case ImGuiDataType_U8: + CW_ADDITION(unsigned char); + break; + case ImGuiDataType_S8: + CW_ADDITION(signed char); + break; + case ImGuiDataType_U16: + CW_ADDITION(unsigned short); + break; + case ImGuiDataType_S16: + CW_ADDITION(short); + break; + case ImGuiDataType_U32: + CW_ADDITION(unsigned int); + break; + case ImGuiDataType_S32: + CW_ADDITION(int); + break; + case ImGuiDataType_Float: + CW_ADDITION(float); + break; + case ImGuiDataType_Double: + CW_ADDITION(double); + break; + } + return true; + } + return false; +} + +bool FurnaceGUI::CWSliderInt(const char* label, int* v, int v_min, int v_max, const char* format, ImGuiSliderFlags flags) { + return CWSliderScalar(label,ImGuiDataType_S32,v,&v_min,&v_max,format,flags); +} + +bool FurnaceGUI::CWSliderFloat(const char* label, float* v, float v_min, float v_max, const char* format, ImGuiSliderFlags flags) { + return CWSliderScalar(label,ImGuiDataType_Float,v,&v_min,&v_max,format,flags); +} + +bool FurnaceGUI::CWVSliderInt(const char* label, const ImVec2& size, int* v, int v_min, int v_max, const char* format, ImGuiSliderFlags flags) { + return CWVSliderScalar(label,size,ImGuiDataType_S32,v,&v_min,&v_max,format,flags); +} + const char* FurnaceGUI::getSystemName(DivSystem which) { if (settings.chipNames) { return e->getSystemChips(which); @@ -1876,6 +1993,10 @@ bool FurnaceGUI::loop() { bindSetPrevValue=0; } break; + case SDL_MOUSEWHEEL: + wheelX+=ev.wheel.x; + wheelY+=ev.wheel.y; + break; case SDL_WINDOWEVENT: switch (ev.window.event) { case SDL_WINDOWEVENT_RESIZED: @@ -2692,6 +2813,9 @@ bool FurnaceGUI::loop() { if (--soloTimeout<0) soloTimeout=0; + wheelX=0; + wheelY=0; + if (willCommit) { commitSettings(); willCommit=false; @@ -2968,6 +3092,8 @@ FurnaceGUI::FurnaceGUI(): extraChannelButtons(0), patNameTarget(-1), newSongCategory(0), + wheelX(0), + wheelY(0), editControlsOpen(true), ordersOpen(true), insListOpen(true), diff --git a/src/gui/gui.h b/src/gui/gui.h index e23be9902..ebca90896 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -35,6 +35,7 @@ #include "fileDialog.h" #define rightClickable if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) ImGui::SetKeyboardFocusHere(-1); +#define ctrlWheeling ((ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl)) && wheelY!=0) #define handleUnimportant if (settings.insFocusesPattern && patternOpen) {nextWindow=GUI_WINDOW_PATTERN;} #define unimportant(x) if (x) {handleUnimportant} @@ -795,6 +796,7 @@ class FurnaceGUI { int curIns, curWave, curSample, curOctave, oldRow, oldOrder, oldOrder1, editStep, exportLoops, soloChan, soloTimeout, orderEditMode, orderCursor; int loopOrder, loopRow, loopEnd, isClipping, extraChannelButtons, patNameTarget, newSongCategory; + int wheelX, wheelY; bool editControlsOpen, ordersOpen, insListOpen, songInfoOpen, patternOpen, insEditOpen; bool waveListOpen, waveEditOpen, sampleListOpen, sampleEditOpen, aboutOpen, settingsOpen; bool mixerOpen, debugOpen, inspectorOpen, oscOpen, volMeterOpen, statsOpen, compatFlagsOpen; @@ -938,6 +940,13 @@ class FurnaceGUI { void drawFMEnv(unsigned char tl, unsigned char ar, unsigned char dr, unsigned char d2r, unsigned char rr, unsigned char sl, float maxTl, float maxArDr, const ImVec2& size); void drawSysConf(int i); + // these ones offer ctrl-wheel fine value changes. + bool CWSliderScalar(const char* label, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format=NULL, ImGuiSliderFlags flags=0); + bool CWVSliderScalar(const char* label, const ImVec2& size, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format=NULL, ImGuiSliderFlags flags=0); + bool CWSliderInt(const char* label, int* v, int v_min, int v_max, const char* format="%d", ImGuiSliderFlags flags=0); + bool CWSliderFloat(const char* label, float* v, float v_min, float v_max, const char* format="%.3f", ImGuiSliderFlags flags=0); + bool CWVSliderInt(const char* label, const ImVec2& size, int* v, int v_min, int v_max, const char* format="%d", ImGuiSliderFlags flags=0); + void updateWindowTitle(); void prepareLayout(); diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 9cf382cf7..f9fc3db40 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -933,7 +933,7 @@ void FurnaceGUI::drawFMEnv(unsigned char tl, unsigned char ar, unsigned char dr, if (displayLoop) { \ if (drawSlider) { \ ImGui::SameLine(); \ - ImGui::VSliderInt("##IArpMacroPos",ImVec2(20.0f*dpiScale,displayHeight*dpiScale),sliderVal,sliderLow,70); \ + CWVSliderInt("##IArpMacroPos",ImVec2(20.0f*dpiScale,displayHeight*dpiScale),sliderVal,sliderLow,70); \ } \ PlotCustom("##IMacroLoop_" macroName,loopIndicator,totalFit,macroDragScroll,NULL,0,2,ImVec2(availableWidth,12.0f*dpiScale),sizeof(float),macroColor,macroLen-macroDragScroll,¯oHoverLoop); \ if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { \ @@ -1062,7 +1062,7 @@ if (ImGui::BeginTable("MacroSpace",2)) { \ macroDragScroll=127-totalFit; \ } \ ImGui::SetNextItemWidth(availableWidth); \ - if (ImGui::SliderInt("##MacroScroll",¯oDragScroll,0,127-totalFit,"")) { \ + if (CWSliderInt("##MacroScroll",¯oDragScroll,0,127-totalFit,"")) { \ if (macroDragScroll<0) macroDragScroll=0; \ if (macroDragScroll>127-totalFit) macroDragScroll=127-totalFit; \ } @@ -1072,7 +1072,7 @@ if (ImGui::BeginTable("MacroSpace",2)) { \ ImGui::TableNextColumn(); \ ImGui::TableNextColumn(); \ ImGui::SetNextItemWidth(availableWidth); \ - if (ImGui::SliderInt("##MacroScroll",¯oDragScroll,0,127-totalFit,"")) { \ + if (CWSliderInt("##MacroScroll",¯oDragScroll,0,127-totalFit,"")) { \ if (macroDragScroll<0) macroDragScroll=0; \ if (macroDragScroll>127-totalFit) macroDragScroll=127-totalFit; \ } \ @@ -1112,6 +1112,7 @@ if (ImGui::BeginTable("MacroSpace",2)) { \ } \ } + #define CENTER_TEXT(text) \ ImGui::SetCursorPosX(ImGui::GetCursorPosX()+0.5*(ImGui::GetContentRegionAvail().x-ImGui::CalcTextSize(text).x)); @@ -1160,11 +1161,11 @@ void FurnaceGUI::drawInsEdit() { case DIV_INS_FM: case DIV_INS_OPZ: ImGui::TableNextColumn(); - P(ImGui::SliderScalar(FM_NAME(FM_FB),ImGuiDataType_U8,&ins->fm.fb,&_ZERO,&_SEVEN)); rightClickable - P(ImGui::SliderScalar(FM_NAME(FM_FMS),ImGuiDataType_U8,&ins->fm.fms,&_ZERO,&_SEVEN)); rightClickable + P(CWSliderScalar(FM_NAME(FM_FB),ImGuiDataType_U8,&ins->fm.fb,&_ZERO,&_SEVEN)); rightClickable + P(CWSliderScalar(FM_NAME(FM_FMS),ImGuiDataType_U8,&ins->fm.fms,&_ZERO,&_SEVEN)); rightClickable ImGui::TableNextColumn(); - P(ImGui::SliderScalar(FM_NAME(FM_ALG),ImGuiDataType_U8,&ins->fm.alg,&_ZERO,&_SEVEN)); rightClickable - P(ImGui::SliderScalar(FM_NAME(FM_AMS),ImGuiDataType_U8,&ins->fm.ams,&_ZERO,&_THREE)); rightClickable + P(CWSliderScalar(FM_NAME(FM_ALG),ImGuiDataType_U8,&ins->fm.alg,&_ZERO,&_SEVEN)); rightClickable + P(CWSliderScalar(FM_NAME(FM_AMS),ImGuiDataType_U8,&ins->fm.ams,&_ZERO,&_THREE)); rightClickable ImGui::TableNextColumn(); drawAlgorithm(ins->fm.alg,FM_ALGS_4OP,ImVec2(ImGui::GetContentRegionAvail().x,48.0*dpiScale)); break; @@ -1174,14 +1175,14 @@ void FurnaceGUI::drawInsEdit() { int algMax=fourOp?3:1; ImGui::TableNextColumn(); ins->fm.alg&=algMax; - P(ImGui::SliderScalar(FM_NAME(FM_FB),ImGuiDataType_U8,&ins->fm.fb,&_ZERO,&_SEVEN)); rightClickable + P(CWSliderScalar(FM_NAME(FM_FB),ImGuiDataType_U8,&ins->fm.fb,&_ZERO,&_SEVEN)); rightClickable ImGui::BeginDisabled(ins->fm.opllPreset==16); if (ImGui::Checkbox("4-op",&fourOp)) { PARAMETER ins->fm.ops=fourOp?4:2; } ImGui::EndDisabled(); ImGui::TableNextColumn(); - P(ImGui::SliderScalar(FM_NAME(FM_ALG),ImGuiDataType_U8,&ins->fm.alg,&_ZERO,&algMax)); rightClickable + P(CWSliderScalar(FM_NAME(FM_ALG),ImGuiDataType_U8,&ins->fm.alg,&_ZERO,&algMax)); rightClickable if (ImGui::Checkbox("Drums",&drums)) { PARAMETER ins->fm.opllPreset=drums?16:0; } @@ -1195,7 +1196,7 @@ void FurnaceGUI::drawInsEdit() { bool sus=ins->fm.alg; ImGui::TableNextColumn(); ImGui::BeginDisabled(ins->fm.opllPreset!=0); - P(ImGui::SliderScalar(FM_NAME(FM_FB),ImGuiDataType_U8,&ins->fm.fb,&_ZERO,&_SEVEN)); rightClickable + P(CWSliderScalar(FM_NAME(FM_FB),ImGuiDataType_U8,&ins->fm.fb,&_ZERO,&_SEVEN)); rightClickable if (ImGui::Checkbox(FM_NAME(FM_DC),&dc)) { PARAMETER ins->fm.fms=dc; } @@ -1258,7 +1259,7 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_OPLL && ins->fm.opllPreset!=0) willDisplayOps=false; if (!willDisplayOps && ins->type==DIV_INS_OPLL) { ins->fm.op[1].tl&=15; - P(ImGui::SliderScalar("Volume##TL",ImGuiDataType_U8,&ins->fm.op[1].tl,&_FIFTEEN,&_ZERO)); rightClickable + P(CWSliderScalar("Volume##TL",ImGuiDataType_U8,&ins->fm.op[1].tl,&_FIFTEEN,&_ZERO)); rightClickable } if (willDisplayOps) { if (settings.fmLayout==0) { @@ -1406,37 +1407,37 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); op.ar&=maxArDr; CENTER_VSLIDER; - P(ImGui::VSliderScalar("##AR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.ar,&_ZERO,&maxArDr)); + P(CWVSliderScalar("##AR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.ar,&_ZERO,&maxArDr)); ImGui::TableNextColumn(); op.dr&=maxArDr; CENTER_VSLIDER; - P(ImGui::VSliderScalar("##DR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.dr,&_ZERO,&maxArDr)); + P(CWVSliderScalar("##DR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.dr,&_ZERO,&maxArDr)); if (settings.susPosition==0) { ImGui::TableNextColumn(); op.sl&=15; CENTER_VSLIDER; - P(ImGui::VSliderScalar("##SL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); + P(CWVSliderScalar("##SL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); } if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { ImGui::TableNextColumn(); op.d2r&=31; CENTER_VSLIDER; - P(ImGui::VSliderScalar("##D2R",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.d2r,&_ZERO,&_THIRTY_ONE)); + P(CWVSliderScalar("##D2R",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.d2r,&_ZERO,&_THIRTY_ONE)); } ImGui::TableNextColumn(); op.rr&=15; CENTER_VSLIDER; - P(ImGui::VSliderScalar("##RR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.rr,&_ZERO,&_FIFTEEN)); + P(CWVSliderScalar("##RR",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.rr,&_ZERO,&_FIFTEEN)); if (settings.susPosition==1) { ImGui::TableNextColumn(); op.sl&=15; CENTER_VSLIDER; - P(ImGui::VSliderScalar("##SL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); + P(CWVSliderScalar("##SL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); } ImGui::TableNextColumn(); @@ -1445,31 +1446,31 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); op.tl&=maxTl; CENTER_VSLIDER; - P(ImGui::VSliderScalar("##TL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.tl,&maxTl,&_ZERO)); + P(CWVSliderScalar("##TL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.tl,&maxTl,&_ZERO)); ImGui::TableNextColumn(); CENTER_VSLIDER; if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { - P(ImGui::VSliderScalar("##RS",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.rs,&_ZERO,&_THREE)); + P(CWVSliderScalar("##RS",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.rs,&_ZERO,&_THREE)); } else { - P(ImGui::VSliderScalar("##KSL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.ksl,&_ZERO,&_THREE)); + P(CWVSliderScalar("##KSL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.ksl,&_ZERO,&_THREE)); } ImGui::TableNextColumn(); CENTER_VSLIDER; - P(ImGui::VSliderScalar("##MULT",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.mult,&_ZERO,&_FIFTEEN)); + P(CWVSliderScalar("##MULT",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.mult,&_ZERO,&_FIFTEEN)); if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { int detune=(op.dt&7)-3; ImGui::TableNextColumn(); CENTER_VSLIDER; - if (ImGui::VSliderInt("##DT",ImVec2(20.0f*dpiScale,sliderHeight),&detune,-3,4)) { PARAMETER + if (CWVSliderInt("##DT",ImVec2(20.0f*dpiScale,sliderHeight),&detune,-3,4)) { PARAMETER op.dt=detune+3; } ImGui::TableNextColumn(); CENTER_VSLIDER; - P(ImGui::VSliderScalar("##DT2",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.dt2,&_ZERO,&_THREE)); rightClickable + P(CWVSliderScalar("##DT2",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.dt2,&_ZERO,&_THREE)); rightClickable if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Only on YM2151 (OPM)"); } @@ -1500,7 +1501,7 @@ void FurnaceGUI::drawInsEdit() { ImGui::SameLine(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::SliderScalar("##SSG",ImGuiDataType_U8,&ssgEnv,&_ZERO,&_SEVEN,ssgEnvTypes[ssgEnv])) { PARAMETER + if (CWSliderScalar("##SSG",ImGuiDataType_U8,&ssgEnv,&_ZERO,&_SEVEN,ssgEnvTypes[ssgEnv])) { PARAMETER op.ssgEnv=(op.ssgEnv&8)|(ssgEnv&7); } } @@ -1535,7 +1536,7 @@ void FurnaceGUI::drawInsEdit() { drawWaveform(op.ws&7,ins->type==DIV_INS_OPZ,ImVec2(ImGui::GetContentRegionAvail().x,sliderHeight-ImGui::GetFrameHeightWithSpacing())); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - P(ImGui::SliderScalar("##WS",ImGuiDataType_U8,&op.ws,&_ZERO,&_SEVEN,(ins->type==DIV_INS_OPZ)?opzWaveforms[op.ws&7]:oplWaveforms[op.ws&7])); rightClickable + P(CWSliderScalar("##WS",ImGuiDataType_U8,&op.ws,&_ZERO,&_SEVEN,(ins->type==DIV_INS_OPZ)?opzWaveforms[op.ws&7]:oplWaveforms[op.ws&7])); rightClickable if (ins->type==DIV_INS_OPL && ImGui::IsItemHovered()) { ImGui::SetTooltip("OPL2/3 only (last 4 waveforms are OPL3 only)"); } @@ -1629,7 +1630,7 @@ void FurnaceGUI::drawInsEdit() { //52.0 controls vert scaling; default 96 drawFMEnv(op.tl&maxTl,op.ar&maxArDr,op.dr&maxArDr,(ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPLL)?((op.rr&15)*2):op.d2r&31,op.rr&15,op.sl&15,maxTl,maxArDr,ImVec2(ImGui::GetContentRegionAvail().x,52.0*dpiScale)); - //P(ImGui::SliderScalar(FM_NAME(FM_AR),ImGuiDataType_U8,&op.ar,&_ZERO,&_THIRTY_ONE)); rightClickable + //P(CWSliderScalar(FM_NAME(FM_AR),ImGuiDataType_U8,&op.ar,&_ZERO,&_THIRTY_ONE)); rightClickable if (ImGui::BeginTable("opParams",2,ImGuiTableFlags_SizingStretchProp)) { ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch,0.0); \ ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed,0.0); \ @@ -1638,7 +1639,7 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); op.ar&=maxArDr; - P(ImGui::SliderScalar("##AR",ImGuiDataType_U8,&op.ar,&maxArDr,&_ZERO)); rightClickable + P(CWSliderScalar("##AR",ImGuiDataType_U8,&op.ar,&maxArDr,&_ZERO)); rightClickable ImGui::TableNextColumn(); ImGui::Text("%s",FM_NAME(FM_AR)); @@ -1646,7 +1647,7 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); op.dr&=maxArDr; - P(ImGui::SliderScalar("##DR",ImGuiDataType_U8,&op.dr,&maxArDr,&_ZERO)); rightClickable + P(CWSliderScalar("##DR",ImGuiDataType_U8,&op.dr,&maxArDr,&_ZERO)); rightClickable ImGui::TableNextColumn(); ImGui::Text("%s",FM_NAME(FM_DR)); @@ -1654,7 +1655,7 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - P(ImGui::SliderScalar("##SL",ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); rightClickable + P(CWSliderScalar("##SL",ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); rightClickable ImGui::TableNextColumn(); ImGui::Text("%s",FM_NAME(FM_SL)); } @@ -1663,7 +1664,7 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - P(ImGui::SliderScalar("##D2R",ImGuiDataType_U8,&op.d2r,&_THIRTY_ONE,&_ZERO)); rightClickable + P(CWSliderScalar("##D2R",ImGuiDataType_U8,&op.d2r,&_THIRTY_ONE,&_ZERO)); rightClickable ImGui::TableNextColumn(); ImGui::Text("%s",FM_NAME(FM_D2R)); } @@ -1671,7 +1672,7 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - P(ImGui::SliderScalar("##RR",ImGuiDataType_U8,&op.rr,&_FIFTEEN,&_ZERO)); rightClickable + P(CWSliderScalar("##RR",ImGuiDataType_U8,&op.rr,&_FIFTEEN,&_ZERO)); rightClickable ImGui::TableNextColumn(); ImGui::Text("%s",FM_NAME(FM_RR)); @@ -1679,7 +1680,7 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - P(ImGui::SliderScalar("##SL",ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); rightClickable + P(CWSliderScalar("##SL",ImGuiDataType_U8,&op.sl,&_FIFTEEN,&_ZERO)); rightClickable ImGui::TableNextColumn(); ImGui::Text("%s",FM_NAME(FM_SL)); } @@ -1688,7 +1689,7 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); op.tl&=maxTl; - P(ImGui::SliderScalar("##TL",ImGuiDataType_U8,&op.tl,&maxTl,&_ZERO)); rightClickable + P(CWSliderScalar("##TL",ImGuiDataType_U8,&op.tl,&maxTl,&_ZERO)); rightClickable ImGui::TableNextColumn(); ImGui::Text("%s",FM_NAME(FM_TL)); @@ -1702,11 +1703,11 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { - P(ImGui::SliderScalar("##RS",ImGuiDataType_U8,&op.rs,&_ZERO,&_THREE)); rightClickable + P(CWSliderScalar("##RS",ImGuiDataType_U8,&op.rs,&_ZERO,&_THREE)); rightClickable ImGui::TableNextColumn(); ImGui::Text("%s",FM_NAME(FM_RS)); } else { - P(ImGui::SliderScalar("##KSL",ImGuiDataType_U8,&op.ksl,&_ZERO,&_THREE)); rightClickable + P(CWSliderScalar("##KSL",ImGuiDataType_U8,&op.ksl,&_ZERO,&_THREE)); rightClickable ImGui::TableNextColumn(); ImGui::Text("%s",FM_NAME(FM_KSL)); } @@ -1714,7 +1715,7 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - P(ImGui::SliderScalar(FM_NAME(FM_MULT),ImGuiDataType_U8,&op.mult,&_ZERO,&_FIFTEEN)); rightClickable + P(CWSliderScalar(FM_NAME(FM_MULT),ImGuiDataType_U8,&op.mult,&_ZERO,&_FIFTEEN)); rightClickable ImGui::TableNextColumn(); ImGui::Text("%s",FM_NAME(FM_MULT)); @@ -1723,7 +1724,7 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::SliderInt("##DT",&detune,-3,4)) { PARAMETER + if (CWSliderInt("##DT",&detune,-3,4)) { PARAMETER op.dt=detune+3; } rightClickable ImGui::TableNextColumn(); @@ -1732,7 +1733,7 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - P(ImGui::SliderScalar("##DT2",ImGuiDataType_U8,&op.dt2,&_ZERO,&_THREE)); rightClickable + P(CWSliderScalar("##DT2",ImGuiDataType_U8,&op.dt2,&_ZERO,&_THREE)); rightClickable if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Only on YM2151 (OPM)"); } @@ -1742,7 +1743,7 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::SliderScalar("##SSG",ImGuiDataType_U8,&ssgEnv,&_ZERO,&_SEVEN,ssgEnvTypes[ssgEnv])) { PARAMETER + if (CWSliderScalar("##SSG",ImGuiDataType_U8,&ssgEnv,&_ZERO,&_SEVEN,ssgEnvTypes[ssgEnv])) { PARAMETER op.ssgEnv=(op.ssgEnv&8)|(ssgEnv&7); } rightClickable ImGui::TableNextColumn(); @@ -1753,7 +1754,7 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - P(ImGui::SliderScalar("##WS",ImGuiDataType_U8,&op.ws,&_ZERO,&_SEVEN,(ins->type==DIV_INS_OPZ)?opzWaveforms[op.ws&7]:oplWaveforms[op.ws&7])); rightClickable + P(CWSliderScalar("##WS",ImGuiDataType_U8,&op.ws,&_ZERO,&_SEVEN,(ins->type==DIV_INS_OPZ)?opzWaveforms[op.ws&7]:oplWaveforms[op.ws&7])); rightClickable if (ins->type==DIV_INS_OPL && ImGui::IsItemHovered()) { ImGui::SetTooltip("OPL2/3 only (last 4 waveforms are OPL3 only)"); } @@ -1874,9 +1875,9 @@ void FurnaceGUI::drawInsEdit() { } } if (ins->type==DIV_INS_GB) if (ImGui::BeginTabItem("Game Boy")) { - P(ImGui::SliderScalar("Volume",ImGuiDataType_U8,&ins->gb.envVol,&_ZERO,&_FIFTEEN)); rightClickable - P(ImGui::SliderScalar("Envelope Length",ImGuiDataType_U8,&ins->gb.envLen,&_ZERO,&_SEVEN)); rightClickable - P(ImGui::SliderScalar("Sound Length",ImGuiDataType_U8,&ins->gb.soundLen,&_ZERO,&_SIXTY_FOUR,ins->gb.soundLen>63?"Infinity":"%d")); rightClickable + P(CWSliderScalar("Volume",ImGuiDataType_U8,&ins->gb.envVol,&_ZERO,&_FIFTEEN)); rightClickable + P(CWSliderScalar("Envelope Length",ImGuiDataType_U8,&ins->gb.envLen,&_ZERO,&_SEVEN)); rightClickable + P(CWSliderScalar("Sound Length",ImGuiDataType_U8,&ins->gb.soundLen,&_ZERO,&_SIXTY_FOUR,ins->gb.soundLen>63?"Infinity":"%d")); rightClickable ImGui::Text("Envelope Direction:"); bool goesUp=ins->gb.envDir; @@ -1919,11 +1920,11 @@ void FurnaceGUI::drawInsEdit() { } ImGui::PopStyleColor(); - P(ImGui::SliderScalar("Attack",ImGuiDataType_U8,&ins->c64.a,&_ZERO,&_FIFTEEN)); rightClickable - P(ImGui::SliderScalar("Decay",ImGuiDataType_U8,&ins->c64.d,&_ZERO,&_FIFTEEN)); rightClickable - P(ImGui::SliderScalar("Sustain",ImGuiDataType_U8,&ins->c64.s,&_ZERO,&_FIFTEEN)); rightClickable - P(ImGui::SliderScalar("Release",ImGuiDataType_U8,&ins->c64.r,&_ZERO,&_FIFTEEN)); rightClickable - P(ImGui::SliderScalar("Duty",ImGuiDataType_U16,&ins->c64.duty,&_ZERO,&_FOUR_THOUSAND_NINETY_FIVE)); rightClickable + P(CWSliderScalar("Attack",ImGuiDataType_U8,&ins->c64.a,&_ZERO,&_FIFTEEN)); rightClickable + P(CWSliderScalar("Decay",ImGuiDataType_U8,&ins->c64.d,&_ZERO,&_FIFTEEN)); rightClickable + P(CWSliderScalar("Sustain",ImGuiDataType_U8,&ins->c64.s,&_ZERO,&_FIFTEEN)); rightClickable + P(CWSliderScalar("Release",ImGuiDataType_U8,&ins->c64.r,&_ZERO,&_FIFTEEN)); rightClickable + P(CWSliderScalar("Duty",ImGuiDataType_U16,&ins->c64.duty,&_ZERO,&_FOUR_THOUSAND_NINETY_FIVE)); rightClickable bool ringMod=ins->c64.ringMod; if (ImGui::Checkbox("Ring Modulation",&ringMod)) { PARAMETER @@ -1937,8 +1938,8 @@ void FurnaceGUI::drawInsEdit() { P(ImGui::Checkbox("Enable filter",&ins->c64.toFilter)); P(ImGui::Checkbox("Initialize filter",&ins->c64.initFilter)); - P(ImGui::SliderScalar("Cutoff",ImGuiDataType_U16,&ins->c64.cut,&_ZERO,&_TWO_THOUSAND_FORTY_SEVEN)); rightClickable - P(ImGui::SliderScalar("Resonance",ImGuiDataType_U8,&ins->c64.res,&_ZERO,&_FIFTEEN)); rightClickable + P(CWSliderScalar("Cutoff",ImGuiDataType_U16,&ins->c64.cut,&_ZERO,&_TWO_THOUSAND_FORTY_SEVEN)); rightClickable + P(CWSliderScalar("Resonance",ImGuiDataType_U8,&ins->c64.res,&_ZERO,&_FIFTEEN)); rightClickable ImGui::Text("Filter Mode"); ImGui::SameLine(); @@ -2340,7 +2341,7 @@ void FurnaceGUI::drawInsEdit() { processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); } ImGui::SameLine(); - ImGui::VSliderInt("##IArpMacroPos",ImVec2(20.0f*dpiScale,200.0f*dpiScale),&arpMacroScroll,arpMode?0:-80,70); + CWVSliderInt("##IArpMacroPos",ImVec2(20.0f*dpiScale,200.0f*dpiScale),&arpMacroScroll,arpMode?0:-80,70); ImGui::PlotHistogram("##IArpMacroLoop",loopIndicator,ins->std.arpMacroLen,0,NULL,0,1,ImVec2(400.0f*dpiScale,16.0f*dpiScale)); if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { macroLoopDragStart=ImGui::GetItemRectMin(); diff --git a/src/gui/mixer.cpp b/src/gui/mixer.cpp index 04710176c..5bb3694e5 100644 --- a/src/gui/mixer.cpp +++ b/src/gui/mixer.cpp @@ -45,11 +45,11 @@ void FurnaceGUI::drawMixer() { e->song.systemVol[i]^=128; } ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x-(50.0f*dpiScale)); - if (ImGui::SliderScalar("Volume",ImGuiDataType_S8,&vol,&_ZERO,&_ONE_HUNDRED_TWENTY_SEVEN)) { + if (CWSliderScalar("Volume",ImGuiDataType_S8,&vol,&_ZERO,&_ONE_HUNDRED_TWENTY_SEVEN)) { e->song.systemVol[i]=(e->song.systemVol[i]&128)|vol; } rightClickable ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x-(50.0f*dpiScale)); - ImGui::SliderScalar("Panning",ImGuiDataType_S8,&e->song.systemPan[i],&_MINUS_ONE_HUNDRED_TWENTY_SEVEN,&_ONE_HUNDRED_TWENTY_SEVEN); rightClickable + CWSliderScalar("Panning",ImGuiDataType_S8,&e->song.systemPan[i],&_MINUS_ONE_HUNDRED_TWENTY_SEVEN,&_ONE_HUNDRED_TWENTY_SEVEN); rightClickable ImGui::PopID(); } diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index 0998856cc..b976ab92f 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -236,6 +236,22 @@ void FurnaceGUI::drawSampleEdit() { ImGui::SameLine(); ImGui::Dummy(ImVec2(4.0*dpiScale,dpiScale)); ImGui::SameLine(); + if (ImGui::Button(ICON_FA_UNDO "##SUndo")) { + doUndoSample(); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Undo"); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_REPEAT "##SRedo")) { + doRedoSample(); + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Redo"); + } + ImGui::SameLine(); + ImGui::Dummy(ImVec2(4.0*dpiScale,dpiScale)); + ImGui::SameLine(); ImGui::Button(ICON_FA_VOLUME_UP "##SAmplify"); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("Amplify"); diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index 8260b0a42..f1f25097b 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -268,7 +268,7 @@ void FurnaceGUI::drawSysConf(int i) { case DIV_SYSTEM_AMIGA: { ImGui::Text("Stereo separation:"); int stereoSep=(flags>>8)&127; - if (ImGui::SliderInt("##StereoSep",&stereoSep,0,127)) { + if (CWSliderInt("##StereoSep",&stereoSep,0,127)) { if (stereoSep<0) stereoSep=0; if (stereoSep>127) stereoSep=127; e->setSysFlags(i,(flags&(~0x7f00))|((stereoSep&127)<<8),restart); @@ -315,7 +315,7 @@ void FurnaceGUI::drawSysConf(int i) { case DIV_SYSTEM_QSOUND: { ImGui::Text("Echo delay:"); int echoBufSize=2725 - (flags & 4095); - if (ImGui::SliderInt("##EchoBufSize",&echoBufSize,0,2725)) { + if (CWSliderInt("##EchoBufSize",&echoBufSize,0,2725)) { if (echoBufSize<0) echoBufSize=0; if (echoBufSize>2725) echoBufSize=2725; e->setSysFlags(i,(flags & ~4095) | ((2725 - echoBufSize) & 4095),restart); @@ -323,7 +323,7 @@ void FurnaceGUI::drawSysConf(int i) { } rightClickable ImGui::Text("Echo feedback:"); int echoFeedback=(flags>>12)&255; - if (ImGui::SliderInt("##EchoFeedback",&echoFeedback,0,255)) { + if (CWSliderInt("##EchoFeedback",&echoFeedback,0,255)) { if (echoFeedback<0) echoFeedback=0; if (echoFeedback>255) echoFeedback=255; e->setSysFlags(i,(flags & ~(255 << 12)) | ((echoFeedback & 255) << 12),restart); @@ -364,7 +364,7 @@ void FurnaceGUI::drawSysConf(int i) { } ImGui::Text("Initial channel limit:"); int initialChannelLimit=((flags>>4)&7)+1; - if (ImGui::SliderInt("##InitialChannelLimit",&initialChannelLimit,1,8)) { + if (CWSliderInt("##InitialChannelLimit",&initialChannelLimit,1,8)) { if (initialChannelLimit<1) initialChannelLimit=1; if (initialChannelLimit>8) initialChannelLimit=8; e->setSysFlags(i,(flags & ~(7 << 4)) | (((initialChannelLimit-1) & 7) << 4),restart); From b90a26a33df18cec94a1967309552d6f9889401f Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 2 Apr 2022 00:41:52 -0500 Subject: [PATCH 492/637] GUI: order view similar pattern coloring --- src/gui/gui.h | 1 + src/gui/orders.cpp | 9 ++++++--- src/gui/pattern.cpp | 4 ++-- src/gui/settings.cpp | 19 +++++++++++++++++++ 4 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/gui/gui.h b/src/gui/gui.h index ebca90896..b0d08769b 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -120,6 +120,7 @@ enum FurnaceGUIColors { GUI_COLOR_CHANNEL_OP, GUI_COLOR_CHANNEL_MUTED, + GUI_COLOR_PATTERN_PLAY_HEAD, GUI_COLOR_PATTERN_CURSOR, GUI_COLOR_PATTERN_CURSOR_HOVER, GUI_COLOR_PATTERN_CURSOR_ACTIVE, diff --git a/src/gui/orders.cpp b/src/gui/orders.cpp index 37faba8d4..b233c48bb 100644 --- a/src/gui/orders.cpp +++ b/src/gui/orders.cpp @@ -55,7 +55,7 @@ void FurnaceGUI::drawOrders() { } ImGui::TableNextRow(0,lineHeight); ImGui::TableNextColumn(); - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_ROW_INDEX]); + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_ORDER_ROW_INDEX]); for (int i=0; igetTotalChannelCount(); i++) { if (!e->song.chanShow[i]) continue; ImGui::TableNextColumn(); @@ -64,9 +64,9 @@ void FurnaceGUI::drawOrders() { ImGui::PopStyleColor(); for (int i=0; isong.ordersLen; i++) { ImGui::TableNextRow(0,lineHeight); - if (oldOrder1==i) ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,0x40ffffff); + if (oldOrder1==i) ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,ImGui::GetColorU32(uiColors[GUI_COLOR_ORDER_ACTIVE])); ImGui::TableNextColumn(); - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_ROW_INDEX]); + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_ORDER_ROW_INDEX]); bool highlightLoop=(i>=loopOrder && i<=loopEnd); if (highlightLoop) ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg,ImGui::GetColorU32(uiColors[GUI_COLOR_SONG_LOOP])); if (settings.orderRowsBase==1) { @@ -93,6 +93,8 @@ void FurnaceGUI::drawOrders() { } else {*/ snprintf(selID,4096,"%.2X##O_%.2x_%.2x",e->song.orders.ord[j][i],j,i); //} + + ImGui::PushStyleColor(ImGuiCol_Text,(curOrder==i || e->song.orders.ord[j][i]==e->song.orders.ord[j][curOrder])?uiColors[GUI_COLOR_ORDER_SIMILAR]:uiColors[GUI_COLOR_ORDER_INACTIVE]); if (ImGui::Selectable(selID,(orderEditMode!=0 && curOrder==i && orderCursor==j))) { if (curOrder==i) { if (orderEditMode==0) { @@ -125,6 +127,7 @@ void FurnaceGUI::drawOrders() { handleUnimportant; } } + ImGui::PopStyleColor(); if (!pat->name.empty() && ImGui::IsItemHovered()) { ImGui::SetTooltip("%s",pat->name.c_str()); } diff --git a/src/gui/pattern.cpp b/src/gui/pattern.cpp index 79612827c..4b84ab947 100644 --- a/src/gui/pattern.cpp +++ b/src/gui/pattern.cpp @@ -108,7 +108,7 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int if (edit && cursor.y==i) { ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,ImGui::GetColorU32(uiColors[GUI_COLOR_EDITING])); } else if (isPlaying && oldRow==i) { - ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,0x40ffffff); + ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_PLAY_HEAD])); } else if (e->song.hilightB>0 && !(i%e->song.hilightB)) { ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_HI_2])); } else if (e->song.hilightA>0 && !(i%e->song.hilightA)) { @@ -119,7 +119,7 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int if (edit && cursor.y==i) { ImGui::PushStyleColor(ImGuiCol_Header,ImGui::GetColorU32(uiColors[GUI_COLOR_EDITING])); } else if (isPlaying && oldRow==i) { - ImGui::PushStyleColor(ImGuiCol_Header,0x40ffffff); + ImGui::PushStyleColor(ImGuiCol_Header,ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_PLAY_HEAD])); } else if (e->song.hilightB>0 && !(i%e->song.hilightB)) { ImGui::PushStyleColor(ImGuiCol_Header,ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_HI_2])); } else if (e->song.hilightA>0 && !(i%e->song.hilightA)) { diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index b9e616da3..ac77896f2 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -877,6 +877,13 @@ void FurnaceGUI::drawSettings() { UI_COLOR_CONFIG(GUI_COLOR_VOLMETER_PEAK,"Clip"); ImGui::TreePop(); } + if (ImGui::TreeNode("Orders")) { + 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_SIMILAR,"Similar patterns"); + UI_COLOR_CONFIG(GUI_COLOR_ORDER_INACTIVE,"Inactive patterns"); + ImGui::TreePop(); + } if (ImGui::TreeNode("Macro Editor")) { UI_COLOR_CONFIG(GUI_COLOR_MACRO_VOLUME,"Volume"); UI_COLOR_CONFIG(GUI_COLOR_MACRO_PITCH,"Pitch"); @@ -926,6 +933,7 @@ void FurnaceGUI::drawSettings() { ImGui::TreePop(); } if (ImGui::TreeNode("Pattern")) { + UI_COLOR_CONFIG(GUI_COLOR_PATTERN_PLAY_HEAD,"Playhead"); UI_COLOR_CONFIG(GUI_COLOR_PATTERN_CURSOR,"Cursor"); UI_COLOR_CONFIG(GUI_COLOR_PATTERN_CURSOR_HOVER,"Cursor (hovered)"); UI_COLOR_CONFIG(GUI_COLOR_PATTERN_CURSOR_ACTIVE,"Cursor (clicked)"); @@ -1662,6 +1670,10 @@ void FurnaceGUI::commitSettings() { PUT_UI_COLOR(GUI_COLOR_VOLMETER_LOW); PUT_UI_COLOR(GUI_COLOR_VOLMETER_HIGH); PUT_UI_COLOR(GUI_COLOR_VOLMETER_PEAK); + PUT_UI_COLOR(GUI_COLOR_ORDER_ROW_INDEX); + PUT_UI_COLOR(GUI_COLOR_ORDER_ACTIVE); + PUT_UI_COLOR(GUI_COLOR_ORDER_SIMILAR); + PUT_UI_COLOR(GUI_COLOR_ORDER_INACTIVE); PUT_UI_COLOR(GUI_COLOR_MACRO_VOLUME); PUT_UI_COLOR(GUI_COLOR_MACRO_PITCH); PUT_UI_COLOR(GUI_COLOR_MACRO_OTHER); @@ -1701,6 +1713,7 @@ void FurnaceGUI::commitSettings() { PUT_UI_COLOR(GUI_COLOR_CHANNEL_WAVE); PUT_UI_COLOR(GUI_COLOR_CHANNEL_OP); PUT_UI_COLOR(GUI_COLOR_CHANNEL_MUTED); + PUT_UI_COLOR(GUI_COLOR_PATTERN_PLAY_HEAD); PUT_UI_COLOR(GUI_COLOR_PATTERN_CURSOR); PUT_UI_COLOR(GUI_COLOR_PATTERN_CURSOR_HOVER); PUT_UI_COLOR(GUI_COLOR_PATTERN_CURSOR_ACTIVE); @@ -2053,6 +2066,11 @@ void FurnaceGUI::applyUISettings() { GET_UI_COLOR(GUI_COLOR_VOLMETER_HIGH,ImVec4(1.0f,0.9f,0.2f,1.0f)); GET_UI_COLOR(GUI_COLOR_VOLMETER_PEAK,ImVec4(1.0f,0.1f,0.1f,1.0f)); + GET_UI_COLOR(GUI_COLOR_ORDER_ROW_INDEX,ImVec4(0.5f,0.8f,1.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_ORDER_ACTIVE,ImVec4(0.4f,0.7f,1.0f,0.25f)); + GET_UI_COLOR(GUI_COLOR_ORDER_SIMILAR,ImVec4(0.5f,1.0f,1.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_ORDER_INACTIVE,ImVec4(1.0f,1.0f,1.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_MACRO_VOLUME,ImVec4(0.2f,1.0f,0.0f,1.0f)); GET_UI_COLOR(GUI_COLOR_MACRO_PITCH,ImVec4(1.0f,0.8f,0.0f,1.0f)); GET_UI_COLOR(GUI_COLOR_MACRO_OTHER,ImVec4(0.0f,0.9f,1.0f,1.0f)); @@ -2095,6 +2113,7 @@ void FurnaceGUI::applyUISettings() { GET_UI_COLOR(GUI_COLOR_CHANNEL_OP,ImVec4(0.2f,0.4f,1.0f,1.0f)); GET_UI_COLOR(GUI_COLOR_CHANNEL_MUTED,ImVec4(0.5f,0.5f,0.5f,1.0f)); + GET_UI_COLOR(GUI_COLOR_PATTERN_PLAY_HEAD,ImVec4(1.0f,1.0f,1.0f,0.25f)); GET_UI_COLOR(GUI_COLOR_PATTERN_CURSOR,ImVec4(0.1f,0.3f,0.5f,1.0f)); GET_UI_COLOR(GUI_COLOR_PATTERN_CURSOR_HOVER,ImVec4(0.2f,0.4f,0.6f,1.0f)); GET_UI_COLOR(GUI_COLOR_PATTERN_CURSOR_ACTIVE,ImVec4(0.2f,0.5f,0.7f,1.0f)); From fa234afc9d46779d0336a2c9729dd853e879a0bd Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 2 Apr 2022 15:07:47 -0500 Subject: [PATCH 493/637] GUI: prepare to add a "lock layout" option --- src/gui/gui.cpp | 35 ++++++++++++++++++++++++++++++++++- src/gui/gui.h | 11 ++++++++++- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 84b7d256c..cac32346e 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -24,6 +24,7 @@ #include "../ta-log.h" #include "../fileutils.h" #include "imgui.h" +#include "imgui_internal.h" #include "imgui_impl_sdl.h" #include "imgui_impl_sdlrenderer.h" #include "ImGuiFileDialog.h" @@ -2352,6 +2353,9 @@ bool FurnaceGUI::loop() { ImGui::EndMenu(); } if (ImGui::BeginMenu("settings")) { + if (ImGui::MenuItem("lock layout",NULL,lockLayout)) { + lockLayout=!lockLayout; + } if (ImGui::MenuItem("visualizer",NULL,fancyPattern)) { fancyPattern=!fancyPattern; e->enableCommandStream(fancyPattern); @@ -2469,7 +2473,7 @@ bool FurnaceGUI::loop() { } ImGui::EndMainMenuBar(); - ImGui::DockSpaceOverViewport(); + ImGui::DockSpaceOverViewport(NULL,lockLayout?(ImGuiDockNodeFlags_NoResize|ImGuiDockNodeFlags_NoCloseButton|ImGuiDockNodeFlags_NoDocking|ImGuiDockNodeFlags_NoDockingSplitMe|ImGuiDockNodeFlags_NoDockingSplitOther):0); drawPattern(); drawEditControls(); @@ -2866,6 +2870,7 @@ bool FurnaceGUI::init() { tempoView=e->getConfBool("tempoView",true); waveHex=e->getConfBool("waveHex",false); + lockLayout=e->getConfBool("lockLayout",false); syncSettings(); @@ -3028,6 +3033,7 @@ bool FurnaceGUI::finish() { e->setConf("tempoView",tempoView); e->setConf("waveHex",waveHex); + e->setConf("lockLayout",lockLayout); for (int i=0; i Date: Sat, 2 Apr 2022 17:37:43 -0500 Subject: [PATCH 494/637] fix .dmp loading --- src/engine/engine.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 26e2402c7..876a8b2d0 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -1406,6 +1406,7 @@ bool DivEngine::addInstrumentFromFile(const char* path) { try { reader.seek(0,SEEK_SET); version=reader.readC(); + logD(".dmp version %d\n",version); } catch (EndOfFileException& e) { lastError="premature end of file"; logE("premature end of file!\n"); @@ -1430,29 +1431,38 @@ bool DivEngine::addInstrumentFromFile(const char* path) { switch (sys) { case 1: // YMU759 ins->type=DIV_INS_FM; + logD("instrument type is YMU759\n"); break; case 2: // Genesis ins->type=DIV_INS_FM; + logD("instrument type is Genesis\n"); break; case 3: // SMS ins->type=DIV_INS_STD; + logD("instrument type is SMS\n"); break; case 4: // Game Boy ins->type=DIV_INS_GB; + logD("instrument type is Game Boy\n"); break; case 5: // PC Engine ins->type=DIV_INS_PCE; + logD("instrument type is PC Engine\n"); break; case 6: // NES ins->type=DIV_INS_STD; + logD("instrument type is NES\n"); break; case 7: case 0x17: // C64 ins->type=DIV_INS_C64; + logD("instrument type is C64\n"); break; case 8: // Arcade ins->type=DIV_INS_FM; + logD("instrument type is Arcade\n"); break; default: + logD("instrument type is unknown\n"); lastError="unknown instrument type!"; delete ins; delete[] buf; @@ -1472,6 +1482,7 @@ bool DivEngine::addInstrumentFromFile(const char* path) { bool mode=true; if (version>1) { mode=reader.readC(); + logD("instrument mode is %d\n",mode); if (mode==0) { if (version<11) { ins->type=DIV_INS_STD; @@ -1484,12 +1495,15 @@ bool DivEngine::addInstrumentFromFile(const char* path) { } if (mode) { // FM + logD("reading FM data...\n"); if (version<10) { if (version>1) { ins->fm.ops=reader.readC()?4:2; } else { ins->fm.ops=reader.readC()?2:4; } + } else { + ins->fm.ops=4; } if (version>1) { // HELP! in which version of the format did we start storing FMS! ins->fm.fms=reader.readC(); @@ -1500,6 +1514,7 @@ bool DivEngine::addInstrumentFromFile(const char* path) { if (sys!=1) ins->fm.ams=reader.readC(); for (int j=0; jfm.ops; j++) { + logD("OP%d is at %d\n",j,reader.tell()); ins->fm.op[j].mult=reader.readC(); ins->fm.op[j].tl=reader.readC(); ins->fm.op[j].ar=reader.readC(); @@ -1527,6 +1542,7 @@ bool DivEngine::addInstrumentFromFile(const char* path) { } } } else { // STD + logD("reading STD data...\n"); if (ins->type!=DIV_INS_GB) { ins->std.volMacroLen=reader.readC(); if (version>5) { From 6c732a1891baa6d7a5d144ffb53f2d03443f708c Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 2 Apr 2022 18:21:29 -0500 Subject: [PATCH 495/637] GUI: change sample add icon to new --- src/gui/dataList.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/dataList.cpp b/src/gui/dataList.cpp index 648abbd85..9557cc57d 100644 --- a/src/gui/dataList.cpp +++ b/src/gui/dataList.cpp @@ -286,7 +286,7 @@ void FurnaceGUI::drawSampleList() { } if (!sampleListOpen) return; if (ImGui::Begin("Samples",&sampleListOpen)) { - if (ImGui::Button(ICON_FA_PLUS "##SampleAdd")) { + if (ImGui::Button(ICON_FA_FILE "##SampleAdd")) { doAction(GUI_ACTION_SAMPLE_LIST_ADD); } ImGui::SameLine(); From 44d72c210611066e2cb16f7089cc1cd36aeaabe8 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 2 Apr 2022 18:22:06 -0500 Subject: [PATCH 496/637] GUI: add load/save/select to ins/wave editors TODO: on sample editor TODO: load replace instead of load insert --- src/gui/insEdit.cpp | 59 ++++++++++++++++++++++++++++++++++++++------ src/gui/waveEdit.cpp | 17 +++++++++++++ 2 files changed, 69 insertions(+), 7 deletions(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index f9fc3db40..2e4489378 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1132,14 +1132,59 @@ void FurnaceGUI::drawInsEdit() { ImGui::Text("no instrument selected"); } else { DivInstrument* ins=e->song.ins[curIns]; - if (ImGui::InputText("Name",&ins->name)) { - MARK_MODIFIED; - } - if (ins->type<0 || ins->type>=DIV_INS_MAX) ins->type=DIV_INS_FM; - int insType=ins->type; - if (ImGui::Combo("Type",&insType,insTypes,DIV_INS_MAX,DIV_INS_MAX)) { - ins->type=(DivInstrumentType)insType; + if (ImGui::BeginTable("InsProp",3)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + String insIndex=fmt::sprintf("%.2X",curIns); + if (ImGui::BeginCombo("##InsSelect",insIndex.c_str())) { + String name; + for (size_t i=0; isong.ins.size(); i++) { + name=fmt::sprintf("%.2X: %s##_INSS%d",i,e->song.ins[i]->name,i); + if (ImGui::Selectable(name.c_str(),curIns==(int)i)) { + curIns=i; + ins=e->song.ins[curIns]; + } + } + ImGui::EndCombo(); + } + + ImGui::TableNextColumn(); + ImGui::Text("Name"); + + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputText("##Name",&ins->name)) { + MARK_MODIFIED; + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + // TODO: load replace + if (ImGui::Button(ICON_FA_FOLDER_OPEN "##IELoad")) { + doAction(GUI_ACTION_INS_LIST_OPEN); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_FLOPPY_O "##IESave")) { + doAction(GUI_ACTION_INS_LIST_SAVE); + } + + ImGui::TableNextColumn(); + ImGui::Text("Type"); + + ImGui::TableNextColumn(); + if (ins->type<0 || ins->type>=DIV_INS_MAX) ins->type=DIV_INS_FM; + int insType=ins->type; + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::Combo("##Type",&insType,insTypes,DIV_INS_MAX,DIV_INS_MAX)) { + ins->type=(DivInstrumentType)insType; + } + + ImGui::EndTable(); } + if (ImGui::BeginTabBar("insEditTab")) { if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPLL || ins->type==DIV_INS_OPZ) { diff --git a/src/gui/waveEdit.cpp b/src/gui/waveEdit.cpp index e1359da93..406e7d885 100644 --- a/src/gui/waveEdit.cpp +++ b/src/gui/waveEdit.cpp @@ -19,6 +19,7 @@ #include "gui.h" #include "plot_nolerp.h" +#include "IconsFontAwesome4.h" #include "misc/cpp/imgui_stdlib.h" #include @@ -35,6 +36,22 @@ void FurnaceGUI::drawWaveEdit() { if (curWave<0 || curWave>=(int)e->song.wave.size()) { ImGui::Text("no wavetable selected"); } else { + ImGui::SetNextItemWidth(80.0f*dpiScale); + if (ImGui::InputInt("##CurWave",&curWave,1,1)) { + if (curWave<0) curWave=0; + if (curWave>=(int)e->song.wave.size()) curWave=e->song.wave.size()-1; + } + ImGui::SameLine(); + // TODO: load replace + if (ImGui::Button(ICON_FA_FOLDER_OPEN "##WELoad")) { + doAction(GUI_ACTION_WAVE_LIST_OPEN); + } + ImGui::SameLine(); + if (ImGui::Button(ICON_FA_FLOPPY_O "##WESave")) { + doAction(GUI_ACTION_WAVE_LIST_SAVE); + } + ImGui::SameLine(); + DivWavetable* wave=e->song.wave[curWave]; ImGui::Text("Width"); if (ImGui::IsItemHovered()) { From e44d081adc23a1aec9d8535f6962912e9e243036 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 3 Apr 2022 01:56:49 -0500 Subject: [PATCH 497/637] prepare for .opm support goodbye addInstrumentFromFile hello instrumentFromFile + addInstrumentPtr these changes are required to allow loading instrument banks --- src/engine/engine.cpp | 64 ++++++++++++++++++++++++------------------- src/engine/engine.h | 8 ++++-- src/gui/gui.cpp | 9 ++++-- 3 files changed, 49 insertions(+), 32 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 876a8b2d0..d465a1be9 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -1260,6 +1260,16 @@ int DivEngine::addInstrument(int refChan) { return insCount; } +int DivEngine::addInstrumentPtr(DivInstrument* which) { + BUSY_BEGIN; + saveLock.lock(); + song.ins.push_back(which); + song.insLen=song.ins.size(); + saveLock.unlock(); + BUSY_END; + return song.insLen; +} + enum DivInsFormats { DIV_INSFORMAT_DMP, DIV_INSFORMAT_TFI, @@ -1268,12 +1278,14 @@ enum DivInsFormats { DIV_INSFORMAT_BTI, DIV_INSFORMAT_S3I, DIV_INSFORMAT_SBI, + DIV_INSFORMAT_OPM }; // TODO: re-organize this function to: // - support replacing instruments // - support instrument formats which contain multiple instruments -bool DivEngine::addInstrumentFromFile(const char* path) { +std::vector DivEngine::instrumentFromFile(const char* path) { + std::vector ret; warnings=""; const char* pathRedux=strrchr(path,DIR_SEPARATOR); @@ -1295,37 +1307,37 @@ bool DivEngine::addInstrumentFromFile(const char* path) { FILE* f=ps_fopen(path,"rb"); if (f==NULL) { lastError=strerror(errno); - return false; + return ret; } unsigned char* buf; ssize_t len; if (fseek(f,0,SEEK_END)!=0) { lastError=strerror(errno); fclose(f); - return false; + return ret; } len=ftell(f); if (len<0) { lastError=strerror(errno); fclose(f); - return false; + return ret; } if (len==0) { lastError=strerror(errno); fclose(f); - return false; + return ret; } if (fseek(f,0,SEEK_SET)!=0) { lastError=strerror(errno); fclose(f); - return false; + return ret; } buf=new unsigned char[len]; if (fread(buf,1,len,f)!=(size_t)len) { logW("did not read entire instrument file buffer!\n"); lastError="did not read entire instrument file!"; delete[] buf; - return false; + return ret; } fclose(f); @@ -1359,14 +1371,14 @@ bool DivEngine::addInstrumentFromFile(const char* path) { lastError="invalid instrument header/data!"; delete ins; delete[] buf; - return false; + return ret; } } catch (EndOfFileException& e) { lastError="premature end of file"; logE("premature end of file!\n"); delete ins; delete[] buf; - return false; + return ret; } } else { // read as a different format const char* ext=strrchr(path,'.'); @@ -1412,14 +1424,14 @@ bool DivEngine::addInstrumentFromFile(const char* path) { logE("premature end of file!\n"); delete ins; delete[] buf; - return false; + return ret; } if (version>11) { lastError="unknown instrument version!"; delete ins; delete[] buf; - return false; + return ret; } ins->name=stripPath; @@ -1466,7 +1478,7 @@ bool DivEngine::addInstrumentFromFile(const char* path) { lastError="unknown instrument type!"; delete ins; delete[] buf; - return false; + return ret; break; } } catch (EndOfFileException& e) { @@ -1474,7 +1486,7 @@ bool DivEngine::addInstrumentFromFile(const char* path) { logE("premature end of file!\n"); delete ins; delete[] buf; - return false; + return ret; } } @@ -1661,7 +1673,7 @@ bool DivEngine::addInstrumentFromFile(const char* path) { logE("premature end of file!\n"); delete ins; delete[] buf; - return false; + return ret; } break; } @@ -1694,7 +1706,7 @@ bool DivEngine::addInstrumentFromFile(const char* path) { logE("premature end of file!\n"); delete ins; delete[] buf; - return false; + return ret; } break; case DIV_INSFORMAT_VGI: @@ -1733,12 +1745,14 @@ bool DivEngine::addInstrumentFromFile(const char* path) { logE("premature end of file!\n"); delete ins; delete[] buf; - return false; + return ret; } break; - case DIV_INSFORMAT_FTI: + case DIV_INSFORMAT_FTI: // TODO break; - case DIV_INSFORMAT_BTI: + case DIV_INSFORMAT_BTI: // TODO + break; + case DIV_INSFORMAT_OPM: // TODO break; case DIV_INSFORMAT_S3I: try { @@ -1816,7 +1830,7 @@ bool DivEngine::addInstrumentFromFile(const char* path) { logE("premature end of file!\n"); delete ins; delete[] buf; - return false; + return ret; } break; case DIV_INSFORMAT_SBI: @@ -1983,7 +1997,7 @@ bool DivEngine::addInstrumentFromFile(const char* path) { logE("premature end of file!\n"); delete ins; delete[] buf; - return false; + return ret; } break; } @@ -1995,14 +2009,8 @@ bool DivEngine::addInstrumentFromFile(const char* path) { } } - BUSY_BEGIN; - saveLock.lock(); - int insCount=(int)song.ins.size(); - song.ins.push_back(ins); - song.insLen=insCount+1; - saveLock.unlock(); - BUSY_END; - return true; + ret.push_back(ins); + return ret; } void DivEngine::delInstrument(int index) { diff --git a/src/engine/engine.h b/src/engine/engine.h index c8f539e93..56ab10b09 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -491,8 +491,12 @@ class DivEngine { // add instrument int addInstrument(int refChan=0); - // add instrument from file - bool addInstrumentFromFile(const char* path); + // add instrument from pointer + int addInstrumentPtr(DivInstrument* which); + + // get instrument from file + // if the returned vector is empty then there was an error. + std::vector instrumentFromFile(const char* path); // delete instrument void delInstrument(int index); diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index cac32346e..2d7a42225 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2628,15 +2628,20 @@ bool FurnaceGUI::loop() { case GUI_FILE_EXPORT_AUDIO_PER_CHANNEL: exportAudio(copyOfName,DIV_EXPORT_MODE_MANY_CHAN); break; - case GUI_FILE_INS_OPEN: - if (e->addInstrumentFromFile(copyOfName.c_str())) { + case GUI_FILE_INS_OPEN: { + std::vector instruments=e->instrumentFromFile(copyOfName.c_str()); + if (!instruments.empty()) { if (!e->getWarnings().empty()) { showWarning(e->getWarnings(),GUI_WARN_GENERIC); } + for (DivInstrument* i: instruments) { + e->addInstrumentPtr(i); + } } else { showError("cannot load instrument! ("+e->getLastError()+")"); } break; + } case GUI_FILE_WAVE_OPEN: e->addWaveFromFile(copyOfName.c_str()); MARK_MODIFIED; From 6567d29450c6446b3c80cc6a967b7b8e854112a2 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 3 Apr 2022 02:15:04 -0500 Subject: [PATCH 498/637] separate instrument file ops --- CMakeLists.txt | 1 + src/engine/engine.cpp | 743 ------------------------------------ src/engine/fileOpsIns.cpp | 765 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 766 insertions(+), 743 deletions(-) create mode 100644 src/engine/fileOpsIns.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index af46dee17..1a0909633 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -290,6 +290,7 @@ src/engine/config.cpp src/engine/dispatchContainer.cpp src/engine/engine.cpp src/engine/fileOps.cpp +src/engine/fileOpsIns.cpp src/engine/filter.cpp src/engine/instrument.cpp src/engine/macroInt.cpp diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index d465a1be9..c26058e65 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -1270,749 +1270,6 @@ int DivEngine::addInstrumentPtr(DivInstrument* which) { return song.insLen; } -enum DivInsFormats { - DIV_INSFORMAT_DMP, - DIV_INSFORMAT_TFI, - DIV_INSFORMAT_VGI, - DIV_INSFORMAT_FTI, - DIV_INSFORMAT_BTI, - DIV_INSFORMAT_S3I, - DIV_INSFORMAT_SBI, - DIV_INSFORMAT_OPM -}; - -// TODO: re-organize this function to: -// - support replacing instruments -// - support instrument formats which contain multiple instruments -std::vector DivEngine::instrumentFromFile(const char* path) { - std::vector ret; - warnings=""; - - const char* pathRedux=strrchr(path,DIR_SEPARATOR); - if (pathRedux==NULL) { - pathRedux=path; - } else { - pathRedux++; - } - String stripPath; - const char* pathReduxEnd=strrchr(pathRedux,'.'); - if (pathReduxEnd==NULL) { - stripPath=pathRedux; - } else { - for (const char* i=pathRedux; i!=pathReduxEnd && (*i); i++) { - stripPath+=*i; - } - } - - FILE* f=ps_fopen(path,"rb"); - if (f==NULL) { - lastError=strerror(errno); - return ret; - } - unsigned char* buf; - ssize_t len; - if (fseek(f,0,SEEK_END)!=0) { - lastError=strerror(errno); - fclose(f); - return ret; - } - len=ftell(f); - if (len<0) { - lastError=strerror(errno); - fclose(f); - return ret; - } - if (len==0) { - lastError=strerror(errno); - fclose(f); - return ret; - } - if (fseek(f,0,SEEK_SET)!=0) { - lastError=strerror(errno); - fclose(f); - return ret; - } - buf=new unsigned char[len]; - if (fread(buf,1,len,f)!=(size_t)len) { - logW("did not read entire instrument file buffer!\n"); - lastError="did not read entire instrument file!"; - delete[] buf; - return ret; - } - fclose(f); - - SafeReader reader=SafeReader(buf,len); - - unsigned char magic[16]; - bool isFurnaceInstr=false; - try { - reader.read(magic,16); - if (memcmp("-Furnace instr.-",magic,16)==0) { - isFurnaceInstr=true; - } - } catch (EndOfFileException& e) { - reader.seek(0,SEEK_SET); - } - - DivInstrument* ins=new DivInstrument; - if (isFurnaceInstr) { - try { - short version=reader.readS(); - reader.readS(); // reserved - - if (version>DIV_ENGINE_VERSION) { - warnings="this instrument is made with a more recent version of Furnace!"; - } - - unsigned int dataPtr=reader.readI(); - reader.seek(dataPtr,SEEK_SET); - - if (ins->readInsData(reader,version)!=DIV_DATA_SUCCESS) { - lastError="invalid instrument header/data!"; - delete ins; - delete[] buf; - return ret; - } - } catch (EndOfFileException& e) { - lastError="premature end of file"; - logE("premature end of file!\n"); - delete ins; - delete[] buf; - return ret; - } - } else { // read as a different format - const char* ext=strrchr(path,'.'); - DivInsFormats format=DIV_INSFORMAT_DMP; - if (ext!=NULL) { - String extS; - for (; *ext; ext++) { - char i=*ext; - if (i>='A' && i<='Z') { - i+='a'-'A'; - } - extS+=i; - } - if (extS==String(".dmp")) { - format=DIV_INSFORMAT_DMP; - } else if (extS==String(".tfi")) { - format=DIV_INSFORMAT_TFI; - } else if (extS==String(".vgi")) { - format=DIV_INSFORMAT_VGI; - } else if (extS==String(".fti")) { - format=DIV_INSFORMAT_FTI; - } else if (extS==String(".bti")) { - format=DIV_INSFORMAT_BTI; - } else if (extS==String(".s3i")) { - format = DIV_INSFORMAT_S3I; - } else if (extS==String(".sbi")) { - format = DIV_INSFORMAT_SBI; - } - } - - // TDOO these really should be re-organized to separate functions per instrument file type. - switch (format) { - case DIV_INSFORMAT_DMP: { - // this is a ridiculous mess - unsigned char version=0; - unsigned char sys=0; - try { - reader.seek(0,SEEK_SET); - version=reader.readC(); - logD(".dmp version %d\n",version); - } catch (EndOfFileException& e) { - lastError="premature end of file"; - logE("premature end of file!\n"); - delete ins; - delete[] buf; - return ret; - } - - if (version>11) { - lastError="unknown instrument version!"; - delete ins; - delete[] buf; - return ret; - } - - ins->name=stripPath; - - if (version>=11) { // 1.0 - try { - sys=reader.readC(); - - switch (sys) { - case 1: // YMU759 - ins->type=DIV_INS_FM; - logD("instrument type is YMU759\n"); - break; - case 2: // Genesis - ins->type=DIV_INS_FM; - logD("instrument type is Genesis\n"); - break; - case 3: // SMS - ins->type=DIV_INS_STD; - logD("instrument type is SMS\n"); - break; - case 4: // Game Boy - ins->type=DIV_INS_GB; - logD("instrument type is Game Boy\n"); - break; - case 5: // PC Engine - ins->type=DIV_INS_PCE; - logD("instrument type is PC Engine\n"); - break; - case 6: // NES - ins->type=DIV_INS_STD; - logD("instrument type is NES\n"); - break; - case 7: case 0x17: // C64 - ins->type=DIV_INS_C64; - logD("instrument type is C64\n"); - break; - case 8: // Arcade - ins->type=DIV_INS_FM; - logD("instrument type is Arcade\n"); - break; - default: - logD("instrument type is unknown\n"); - lastError="unknown instrument type!"; - delete ins; - delete[] buf; - return ret; - break; - } - } catch (EndOfFileException& e) { - lastError="premature end of file"; - logE("premature end of file!\n"); - delete ins; - delete[] buf; - return ret; - } - } - - try { - bool mode=true; - if (version>1) { - mode=reader.readC(); - logD("instrument mode is %d\n",mode); - if (mode==0) { - if (version<11) { - ins->type=DIV_INS_STD; - } - } else { - ins->type=DIV_INS_FM; - } - } else { - ins->type=DIV_INS_FM; - } - - if (mode) { // FM - logD("reading FM data...\n"); - if (version<10) { - if (version>1) { - ins->fm.ops=reader.readC()?4:2; - } else { - ins->fm.ops=reader.readC()?2:4; - } - } else { - ins->fm.ops=4; - } - if (version>1) { // HELP! in which version of the format did we start storing FMS! - ins->fm.fms=reader.readC(); - } - ins->fm.fb=reader.readC(); - ins->fm.alg=reader.readC(); - // DITTO - if (sys!=1) ins->fm.ams=reader.readC(); - - for (int j=0; jfm.ops; j++) { - logD("OP%d is at %d\n",j,reader.tell()); - ins->fm.op[j].mult=reader.readC(); - ins->fm.op[j].tl=reader.readC(); - ins->fm.op[j].ar=reader.readC(); - ins->fm.op[j].dr=reader.readC(); - ins->fm.op[j].sl=reader.readC(); - ins->fm.op[j].rr=reader.readC(); - ins->fm.op[j].am=reader.readC(); - // what the hell how do I tell! - if (sys==1) { // YMU759 - ins->fm.op[j].ws=reader.readC(); - ins->fm.op[j].ksl=reader.readC(); - ins->fm.op[j].vib=reader.readC(); - ins->fm.op[j].egt=reader.readC(); - ins->fm.op[j].sus=reader.readC(); - ins->fm.op[j].ksr=reader.readC(); - ins->fm.op[j].dvb=reader.readC(); - ins->fm.op[j].dam=reader.readC(); - } else { - ins->fm.op[j].rs=reader.readC(); - ins->fm.op[j].dt=reader.readC(); - ins->fm.op[j].dt2=ins->fm.op[j].dt>>4; - ins->fm.op[j].dt&=15; - ins->fm.op[j].d2r=reader.readC(); - ins->fm.op[j].ssgEnv=reader.readC(); - } - } - } else { // STD - logD("reading STD data...\n"); - if (ins->type!=DIV_INS_GB) { - ins->std.volMacroLen=reader.readC(); - if (version>5) { - for (int i=0; istd.volMacroLen; i++) { - ins->std.volMacro[i]=reader.readI(); - } - } else { - for (int i=0; istd.volMacroLen; i++) { - ins->std.volMacro[i]=reader.readC(); - } - } - if (version<11) for (int i=0; istd.volMacroLen; i++) { - if (ins->std.volMacro[i]>15 && ins->type==DIV_INS_STD) ins->type=DIV_INS_PCE; - } - if (ins->std.volMacroLen>0) { - ins->std.volMacroOpen=true; - ins->std.volMacroLoop=reader.readC(); - } else { - ins->std.volMacroOpen=false; - } - } - - ins->std.arpMacroLen=reader.readC(); - if (version>5) { - for (int i=0; istd.arpMacroLen; i++) { - ins->std.arpMacro[i]=reader.readI(); - } - } else { - for (int i=0; istd.arpMacroLen; i++) { - ins->std.arpMacro[i]=reader.readC(); - } - } - if (ins->std.arpMacroLen>0) { - ins->std.arpMacroOpen=true; - ins->std.arpMacroLoop=reader.readC(); - } else { - ins->std.arpMacroOpen=false; - } - if (version>8) { // TODO: when? - ins->std.arpMacroMode=reader.readC(); - } - - ins->std.dutyMacroLen=reader.readC(); - if (version>5) { - for (int i=0; istd.dutyMacroLen; i++) { - ins->std.dutyMacro[i]=reader.readI(); - } - } else { - for (int i=0; istd.dutyMacroLen; i++) { - ins->std.dutyMacro[i]=reader.readC(); - } - } - if (ins->std.dutyMacroLen>0) { - ins->std.dutyMacroOpen=true; - ins->std.dutyMacroLoop=reader.readC(); - } else { - ins->std.dutyMacroOpen=false; - } - - ins->std.waveMacroLen=reader.readC(); - if (version>5) { - for (int i=0; istd.waveMacroLen; i++) { - ins->std.waveMacro[i]=reader.readI(); - } - } else { - for (int i=0; istd.waveMacroLen; i++) { - ins->std.waveMacro[i]=reader.readC(); - } - } - if (ins->std.waveMacroLen>0) { - ins->std.waveMacroOpen=true; - ins->std.waveMacroLoop=reader.readC(); - } else { - ins->std.waveMacroOpen=false; - } - - if (ins->type==DIV_INS_C64) { - ins->c64.triOn=reader.readC(); - ins->c64.sawOn=reader.readC(); - ins->c64.pulseOn=reader.readC(); - ins->c64.noiseOn=reader.readC(); - - ins->c64.a=reader.readC(); - ins->c64.d=reader.readC(); - ins->c64.s=reader.readC(); - ins->c64.r=reader.readC(); - - ins->c64.duty=(reader.readC()*4095)/100; - - ins->c64.ringMod=reader.readC(); - ins->c64.oscSync=reader.readC(); - ins->c64.toFilter=reader.readC(); - if (version<0x07) { // TODO: UNSURE - ins->c64.volIsCutoff=reader.readI(); - } else { - ins->c64.volIsCutoff=reader.readC(); - } - ins->c64.initFilter=reader.readC(); - - ins->c64.res=reader.readC(); - ins->c64.cut=(reader.readC()*2047)/100; - ins->c64.hp=reader.readC(); - ins->c64.bp=reader.readC(); - ins->c64.lp=reader.readC(); - ins->c64.ch3off=reader.readC(); - } - if (ins->type==DIV_INS_GB) { - ins->gb.envVol=reader.readC(); - ins->gb.envDir=reader.readC(); - ins->gb.envLen=reader.readC(); - ins->gb.soundLen=reader.readC(); - } - } - } catch (EndOfFileException& e) { - lastError="premature end of file"; - logE("premature end of file!\n"); - delete ins; - delete[] buf; - return ret; - } - break; - } - case DIV_INSFORMAT_TFI: - try { - reader.seek(0,SEEK_SET); - - ins->type=DIV_INS_FM; - ins->name=stripPath; - - ins->fm.alg=reader.readC(); - ins->fm.fb=reader.readC(); - - for (int i=0; i<4; i++) { - DivInstrumentFM::Operator& op=ins->fm.op[i]; - - op.mult=reader.readC(); - op.dt=reader.readC(); - op.tl=reader.readC(); - op.rs=reader.readC(); - op.ar=reader.readC(); - op.dr=reader.readC(); - op.d2r=reader.readC(); - op.rr=reader.readC(); - op.sl=reader.readC(); - op.ssgEnv=reader.readC(); - } - } catch (EndOfFileException& e) { - lastError="premature end of file"; - logE("premature end of file!\n"); - delete ins; - delete[] buf; - return ret; - } - break; - case DIV_INSFORMAT_VGI: - try { - reader.seek(0,SEEK_SET); - - ins->type=DIV_INS_FM; - ins->name=stripPath; - - ins->fm.alg=reader.readC(); - ins->fm.fb=reader.readC(); - unsigned char fmsams=reader.readC(); - ins->fm.fms=fmsams&7; - ins->fm.ams=fmsams>>4; - - for (int i=0; i<4; i++) { - DivInstrumentFM::Operator& op=ins->fm.op[i]; - - op.mult=reader.readC(); - op.dt=reader.readC(); - op.tl=reader.readC(); - op.rs=reader.readC(); - op.ar=reader.readC(); - op.dr=reader.readC(); - if (op.dr&0x80) { - op.am=1; - op.dr&=0x7f; - } - op.d2r=reader.readC(); - op.rr=reader.readC(); - op.sl=reader.readC(); - op.ssgEnv=reader.readC(); - } - } catch (EndOfFileException& e) { - lastError="premature end of file"; - logE("premature end of file!\n"); - delete ins; - delete[] buf; - return ret; - } - break; - case DIV_INSFORMAT_FTI: // TODO - break; - case DIV_INSFORMAT_BTI: // TODO - break; - case DIV_INSFORMAT_OPM: // TODO - break; - case DIV_INSFORMAT_S3I: - try { - reader.seek(0, SEEK_SET); - - uint8_t s3i_type = reader.readC(); - - if (s3i_type >= 2) { - ins->type = DIV_INS_OPL; - // skip internal filename - we'll use the long name description - reader.seek(12, SEEK_CUR); - - // skip reserved bytes - reader.seek(3, SEEK_CUR); - - // 12-byte opl value - uint8_t s3i_Mcharacteristics = reader.readC(); - uint8_t s3i_Ccharacteristics = reader.readC(); - uint8_t s3i_Mscaling_output = reader.readC(); - uint8_t s3i_Cscaling_output = reader.readC(); - uint8_t s3i_Meg_AD = reader.readC(); - uint8_t s3i_Ceg_AD = reader.readC(); - uint8_t s3i_Meg_SR = reader.readC(); - uint8_t s3i_Ceg_SR = reader.readC(); - uint8_t s3i_Mwave = reader.readC(); - uint8_t s3i_Cwave = reader.readC(); - uint8_t s3i_FeedConnect = reader.readC(); - - DivInstrumentFM::Operator& opM = ins->fm.op[0]; - DivInstrumentFM::Operator& opC = ins->fm.op[1]; - ins->fm.ops = 2; - opM.mult = s3i_Mcharacteristics & 0xF; - opM.ksr = ((s3i_Mcharacteristics >> 4) & 0x1); - opM.sus = ((s3i_Mcharacteristics >> 5) & 0x1); - opM.vib = ((s3i_Mcharacteristics >> 6) & 0x1); - opM.am = ((s3i_Mcharacteristics >> 7) & 0x1); - opM.tl = s3i_Mscaling_output & 0x3F; - opM.ksl = ((s3i_Mscaling_output >> 6) & 0x3); - opM.ar = ((s3i_Meg_AD >> 4) & 0xF); - opM.dr = (s3i_Meg_AD & 0xF); - opM.rr = (s3i_Meg_SR & 0xF); - opM.sl = ((s3i_Meg_SR >> 4) & 0xF); - opM.ws = s3i_Mwave; - - ins->fm.alg = (s3i_FeedConnect & 0x1); - ins->fm.fb = ((s3i_FeedConnect >> 1) & 0x7); - - opC.mult = s3i_Ccharacteristics & 0xF; - opC.ksr = ((s3i_Ccharacteristics >> 4) & 0x1); - opC.sus = ((s3i_Ccharacteristics >> 5) & 0x1); - opC.vib = ((s3i_Ccharacteristics >> 6) & 0x1); - opC.am = ((s3i_Ccharacteristics >> 7) & 0x1); - opC.tl = s3i_Cscaling_output & 0x3F; - opC.ksl = ((s3i_Cscaling_output >> 6) & 0x3); - opC.ar = ((s3i_Ceg_AD >> 4) & 0xF); - opC.dr = (s3i_Ceg_AD & 0xF); - opC.rr = (s3i_Ceg_SR & 0xF); - opC.sl = ((s3i_Ceg_SR >> 4) & 0xF); - opC.ws = s3i_Cwave; - - // Skip more stuff we don't need - reader.seek(21, SEEK_CUR); - } else { - logE("S3I PCM samples currently not supported."); - } - ins->name = reader.readString(28); - int s3i_signature = reader.readI(); - - if (s3i_signature != 0x49524353) { - logW("S3I signature invalid."); - }; - } - catch (EndOfFileException& e) { - lastError = "premature end of file"; - logE("premature end of file!\n"); - delete ins; - delete[] buf; - return ret; - } - break; - case DIV_INSFORMAT_SBI: - try { - reader.seek(0, SEEK_SET); - ins->type = DIV_INS_OPL; - - int sbi_header = reader.readI(); - // SBI header determines format - bool is_2op = (sbi_header == 0x1A494253); // SBI\x1A - bool is_4op = (sbi_header == 0x1A504F34); // 4OP\x1A - bool is_6op = (sbi_header == 0x1A504F36); // 6OP\x1A - Freq Monster 801-specific - - // 32-byte null terminated instrument name - ins->name = reader.readString(32); - - // 2op SBI - uint8_t sbi_Mcharacteristics = reader.readC(); - uint8_t sbi_Ccharacteristics = reader.readC(); - uint8_t sbi_Mscaling_output = reader.readC(); - uint8_t sbi_Cscaling_output = reader.readC(); - uint8_t sbi_Meg_AD = reader.readC(); - uint8_t sbi_Ceg_AD = reader.readC(); - uint8_t sbi_Meg_SR = reader.readC(); - uint8_t sbi_Ceg_SR = reader.readC(); - uint8_t sbi_Mwave = reader.readC(); - uint8_t sbi_Cwave = reader.readC(); - uint8_t sbi_FeedConnect = reader.readC(); - - // 4op SBI - uint8_t sbi_M4characteristics; - uint8_t sbi_C4characteristics; - uint8_t sbi_M4scaling_output; - uint8_t sbi_C4scaling_output; - uint8_t sbi_M4eg_AD; - uint8_t sbi_C4eg_AD; - uint8_t sbi_M4eg_SR; - uint8_t sbi_C4eg_SR; - uint8_t sbi_M4wave; - uint8_t sbi_C4wave; - uint8_t sbi_4opConnect; - - if (is_2op) { - DivInstrumentFM::Operator& opM = ins->fm.op[0]; - DivInstrumentFM::Operator& opC = ins->fm.op[1]; - ins->fm.ops = 2; - opM.mult = sbi_Mcharacteristics & 0xF; - opM.ksr = ((sbi_Mcharacteristics >> 4) & 0x1); - opM.sus = ((sbi_Mcharacteristics >> 5) & 0x1); - opM.vib = ((sbi_Mcharacteristics >> 6) & 0x1); - opM.am = ((sbi_Mcharacteristics >> 7) & 0x1); - opM.tl = sbi_Mscaling_output & 0x3F; - opM.ksl = ((sbi_Mscaling_output >> 6) & 0x3); - opM.ar = ((sbi_Meg_AD >> 4) & 0xF); - opM.dr = (sbi_Meg_AD & 0xF); - opM.rr = (sbi_Meg_SR & 0xF); - opM.sl = ((sbi_Meg_SR >> 4) & 0xF); - opM.ws = sbi_Mwave; - - ins->fm.alg = (sbi_FeedConnect & 0x1); - ins->fm.fb = ((sbi_FeedConnect >> 1) & 0x7); - - opC.mult = sbi_Ccharacteristics & 0xF; - opC.ksr = ((sbi_Ccharacteristics >> 4) & 0x1); - opC.sus = ((sbi_Ccharacteristics >> 5) & 0x1); - opC.vib = ((sbi_Ccharacteristics >> 6) & 0x1); - opC.am = ((sbi_Ccharacteristics >> 7) & 0x1); - opC.tl = sbi_Cscaling_output & 0x3F; - opC.ksl = ((sbi_Cscaling_output >> 6) & 0x3); - opC.ar = ((sbi_Ceg_AD >> 4) & 0xF); - opC.dr = (sbi_Ceg_AD & 0xF); - opC.rr = (sbi_Ceg_SR & 0xF); - opC.sl = ((sbi_Ceg_SR >> 4) & 0xF); - opC.ws = sbi_Cwave; - - // Ignore rest of file - rest is 'reserved padding'. - reader.seek(0, SEEK_END); - } - - if (is_4op || is_6op) { - // Operator placement is different so need to place in correct registers. - // Note: 6op is an unofficial extension of 4op SBIs by Darron Broad (Freq Monster 801). - // We'll only use the 4op portion here for pure OPL3. - DivInstrumentFM::Operator& opM = ins->fm.op[0]; - DivInstrumentFM::Operator& opC = ins->fm.op[2]; - DivInstrumentFM::Operator& opM4 = ins->fm.op[1]; - DivInstrumentFM::Operator& opC4 = ins->fm.op[3]; - ins->fm.ops = 4; - - sbi_M4characteristics = reader.readC(); - sbi_C4characteristics = reader.readC(); - sbi_M4scaling_output = reader.readC(); - sbi_C4scaling_output = reader.readC(); - sbi_M4eg_AD = reader.readC(); - sbi_C4eg_AD = reader.readC(); - sbi_M4eg_SR = reader.readC(); - sbi_C4eg_SR = reader.readC(); - sbi_M4wave = reader.readC(); - sbi_C4wave = reader.readC(); - sbi_4opConnect = reader.readC(); - - ins->fm.alg = (sbi_FeedConnect & 0x1) | ((sbi_4opConnect & 0x1) << 1); - ins->fm.fb = ((sbi_FeedConnect >> 1) & 0x7); - - opM.mult = sbi_Mcharacteristics & 0xF; - opM.ksr = ((sbi_Mcharacteristics >> 4) & 0x1); - opM.sus = ((sbi_Mcharacteristics >> 5) & 0x1); - opM.vib = ((sbi_Mcharacteristics >> 6) & 0x1); - opM.am = ((sbi_Mcharacteristics >> 7) & 0x1); - opM.tl = sbi_Mscaling_output & 0x3F; - opM.ksl = ((sbi_Mscaling_output >> 6) & 0x3); - opM.ar = ((sbi_Meg_AD >> 4) & 0xF); - opM.dr = (sbi_Meg_AD & 0xF); - opM.rr = (sbi_Meg_SR & 0xF); - opM.sl = ((sbi_Meg_SR >> 4) & 0xF); - opM.ws = sbi_Mwave; - - opC.mult = sbi_Ccharacteristics & 0xF; - opC.ksr = ((sbi_Ccharacteristics >> 4) & 0x1); - opC.sus = ((sbi_Ccharacteristics >> 5) & 0x1); - opC.vib = ((sbi_Ccharacteristics >> 6) & 0x1); - opC.am = ((sbi_Ccharacteristics >> 7) & 0x1); - opC.tl = sbi_Cscaling_output & 0x3F; - opC.ksl = ((sbi_Cscaling_output >> 6) & 0x3); - opC.ar = ((sbi_Ceg_AD >> 4) & 0xF); - opC.dr = (sbi_Ceg_AD & 0xF); - opC.rr = (sbi_Ceg_SR & 0xF); - opC.sl = ((sbi_Ceg_SR >> 4) & 0xF); - opC.ws = sbi_Cwave; - - opM4.mult = sbi_M4characteristics & 0xF; - opM4.ksr = ((sbi_M4characteristics >> 4) & 0x1); - opM4.sus = ((sbi_M4characteristics >> 5) & 0x1); - opM4.vib = ((sbi_M4characteristics >> 6) & 0x1); - opM4.am = ((sbi_M4characteristics >> 7) & 0x1); - opM4.tl = sbi_M4scaling_output & 0x3F; - opM4.ksl = ((sbi_M4scaling_output >> 6) & 0x3); - opM4.ar = ((sbi_M4eg_AD >> 4) & 0xF); - opM4.dr = (sbi_M4eg_AD & 0xF); - opM4.rr = (sbi_M4eg_SR & 0xF); - opM4.sl = ((sbi_M4eg_SR >> 4) & 0xF); - opM4.ws = sbi_M4wave; - - opC4.mult = sbi_C4characteristics & 0xF; - opC4.ksr = ((sbi_C4characteristics >> 4) & 0x1); - opC4.sus = ((sbi_C4characteristics >> 5) & 0x1); - opC4.vib = ((sbi_C4characteristics >> 6) & 0x1); - opC4.am = ((sbi_C4characteristics >> 7) & 0x1); - opC4.tl = sbi_C4scaling_output & 0x3F; - opC4.ksl = ((sbi_C4scaling_output >> 6) & 0x3); - opC4.ar = ((sbi_C4eg_AD >> 4) & 0xF); - opC4.dr = (sbi_C4eg_AD & 0xF); - opC4.rr = (sbi_C4eg_SR & 0xF); - opC4.sl = ((sbi_C4eg_SR >> 4) & 0xF); - opC4.ws = sbi_C4wave; - - // Ignore rest of file once we've read in all we need. - // Note: Freq Monster 801 adds a ton of other additional fields irrelevant to chip registers. - reader.seek(0, SEEK_END); - } - - } catch (EndOfFileException& e) { - lastError = "premature end of file"; - logE("premature end of file!\n"); - delete ins; - delete[] buf; - return ret; - } - break; - } - - if (reader.tell() + +enum DivInsFormats { + DIV_INSFORMAT_DMP, + DIV_INSFORMAT_TFI, + DIV_INSFORMAT_VGI, + DIV_INSFORMAT_FTI, + DIV_INSFORMAT_BTI, + DIV_INSFORMAT_S3I, + DIV_INSFORMAT_SBI, + DIV_INSFORMAT_OPM +}; + +std::vector DivEngine::instrumentFromFile(const char* path) { + std::vector ret; + warnings=""; + + const char* pathRedux=strrchr(path,DIR_SEPARATOR); + if (pathRedux==NULL) { + pathRedux=path; + } else { + pathRedux++; + } + String stripPath; + const char* pathReduxEnd=strrchr(pathRedux,'.'); + if (pathReduxEnd==NULL) { + stripPath=pathRedux; + } else { + for (const char* i=pathRedux; i!=pathReduxEnd && (*i); i++) { + stripPath+=*i; + } + } + + FILE* f=ps_fopen(path,"rb"); + if (f==NULL) { + lastError=strerror(errno); + return ret; + } + unsigned char* buf; + ssize_t len; + if (fseek(f,0,SEEK_END)!=0) { + lastError=strerror(errno); + fclose(f); + return ret; + } + len=ftell(f); + if (len<0) { + lastError=strerror(errno); + fclose(f); + return ret; + } + if (len==0) { + lastError=strerror(errno); + fclose(f); + return ret; + } + if (fseek(f,0,SEEK_SET)!=0) { + lastError=strerror(errno); + fclose(f); + return ret; + } + buf=new unsigned char[len]; + if (fread(buf,1,len,f)!=(size_t)len) { + logW("did not read entire instrument file buffer!\n"); + lastError="did not read entire instrument file!"; + delete[] buf; + return ret; + } + fclose(f); + + SafeReader reader=SafeReader(buf,len); + + unsigned char magic[16]; + bool isFurnaceInstr=false; + try { + reader.read(magic,16); + if (memcmp("-Furnace instr.-",magic,16)==0) { + isFurnaceInstr=true; + } + } catch (EndOfFileException& e) { + reader.seek(0,SEEK_SET); + } + + DivInstrument* ins=new DivInstrument; + if (isFurnaceInstr) { + try { + short version=reader.readS(); + reader.readS(); // reserved + + if (version>DIV_ENGINE_VERSION) { + warnings="this instrument is made with a more recent version of Furnace!"; + } + + unsigned int dataPtr=reader.readI(); + reader.seek(dataPtr,SEEK_SET); + + if (ins->readInsData(reader,version)!=DIV_DATA_SUCCESS) { + lastError="invalid instrument header/data!"; + delete ins; + delete[] buf; + return ret; + } + } catch (EndOfFileException& e) { + lastError="premature end of file"; + logE("premature end of file!\n"); + delete ins; + delete[] buf; + return ret; + } + } else { // read as a different format + const char* ext=strrchr(path,'.'); + DivInsFormats format=DIV_INSFORMAT_DMP; + if (ext!=NULL) { + String extS; + for (; *ext; ext++) { + char i=*ext; + if (i>='A' && i<='Z') { + i+='a'-'A'; + } + extS+=i; + } + if (extS==String(".dmp")) { + format=DIV_INSFORMAT_DMP; + } else if (extS==String(".tfi")) { + format=DIV_INSFORMAT_TFI; + } else if (extS==String(".vgi")) { + format=DIV_INSFORMAT_VGI; + } else if (extS==String(".fti")) { + format=DIV_INSFORMAT_FTI; + } else if (extS==String(".bti")) { + format=DIV_INSFORMAT_BTI; + } else if (extS==String(".s3i")) { + format=DIV_INSFORMAT_S3I; + } else if (extS==String(".sbi")) { + format=DIV_INSFORMAT_SBI; + } else if (extS==String(".opm")) { + format=DIV_INSFORMAT_OPM; + } + } + + // TDOO these really should be re-organized to separate functions per instrument file type. + switch (format) { + case DIV_INSFORMAT_DMP: { + // this is a ridiculous mess + unsigned char version=0; + unsigned char sys=0; + try { + reader.seek(0,SEEK_SET); + version=reader.readC(); + logD(".dmp version %d\n",version); + } catch (EndOfFileException& e) { + lastError="premature end of file"; + logE("premature end of file!\n"); + delete ins; + delete[] buf; + return ret; + } + + if (version>11) { + lastError="unknown instrument version!"; + delete ins; + delete[] buf; + return ret; + } + + ins->name=stripPath; + + if (version>=11) { // 1.0 + try { + sys=reader.readC(); + + switch (sys) { + case 1: // YMU759 + ins->type=DIV_INS_FM; + logD("instrument type is YMU759\n"); + break; + case 2: // Genesis + ins->type=DIV_INS_FM; + logD("instrument type is Genesis\n"); + break; + case 3: // SMS + ins->type=DIV_INS_STD; + logD("instrument type is SMS\n"); + break; + case 4: // Game Boy + ins->type=DIV_INS_GB; + logD("instrument type is Game Boy\n"); + break; + case 5: // PC Engine + ins->type=DIV_INS_PCE; + logD("instrument type is PC Engine\n"); + break; + case 6: // NES + ins->type=DIV_INS_STD; + logD("instrument type is NES\n"); + break; + case 7: case 0x17: // C64 + ins->type=DIV_INS_C64; + logD("instrument type is C64\n"); + break; + case 8: // Arcade + ins->type=DIV_INS_FM; + logD("instrument type is Arcade\n"); + break; + default: + logD("instrument type is unknown\n"); + lastError="unknown instrument type!"; + delete ins; + delete[] buf; + return ret; + break; + } + } catch (EndOfFileException& e) { + lastError="premature end of file"; + logE("premature end of file!\n"); + delete ins; + delete[] buf; + return ret; + } + } + + try { + bool mode=true; + if (version>1) { + mode=reader.readC(); + logD("instrument mode is %d\n",mode); + if (mode==0) { + if (version<11) { + ins->type=DIV_INS_STD; + } + } else { + ins->type=DIV_INS_FM; + } + } else { + ins->type=DIV_INS_FM; + } + + if (mode) { // FM + logD("reading FM data...\n"); + if (version<10) { + if (version>1) { + ins->fm.ops=reader.readC()?4:2; + } else { + ins->fm.ops=reader.readC()?2:4; + } + } else { + ins->fm.ops=4; + } + if (version>1) { // HELP! in which version of the format did we start storing FMS! + ins->fm.fms=reader.readC(); + } + ins->fm.fb=reader.readC(); + ins->fm.alg=reader.readC(); + // DITTO + if (sys!=1) ins->fm.ams=reader.readC(); + + for (int j=0; jfm.ops; j++) { + logD("OP%d is at %d\n",j,reader.tell()); + ins->fm.op[j].mult=reader.readC(); + ins->fm.op[j].tl=reader.readC(); + ins->fm.op[j].ar=reader.readC(); + ins->fm.op[j].dr=reader.readC(); + ins->fm.op[j].sl=reader.readC(); + ins->fm.op[j].rr=reader.readC(); + ins->fm.op[j].am=reader.readC(); + // what the hell how do I tell! + if (sys==1) { // YMU759 + ins->fm.op[j].ws=reader.readC(); + ins->fm.op[j].ksl=reader.readC(); + ins->fm.op[j].vib=reader.readC(); + ins->fm.op[j].egt=reader.readC(); + ins->fm.op[j].sus=reader.readC(); + ins->fm.op[j].ksr=reader.readC(); + ins->fm.op[j].dvb=reader.readC(); + ins->fm.op[j].dam=reader.readC(); + } else { + ins->fm.op[j].rs=reader.readC(); + ins->fm.op[j].dt=reader.readC(); + ins->fm.op[j].dt2=ins->fm.op[j].dt>>4; + ins->fm.op[j].dt&=15; + ins->fm.op[j].d2r=reader.readC(); + ins->fm.op[j].ssgEnv=reader.readC(); + } + } + } else { // STD + logD("reading STD data...\n"); + if (ins->type!=DIV_INS_GB) { + ins->std.volMacroLen=reader.readC(); + if (version>5) { + for (int i=0; istd.volMacroLen; i++) { + ins->std.volMacro[i]=reader.readI(); + } + } else { + for (int i=0; istd.volMacroLen; i++) { + ins->std.volMacro[i]=reader.readC(); + } + } + if (version<11) for (int i=0; istd.volMacroLen; i++) { + if (ins->std.volMacro[i]>15 && ins->type==DIV_INS_STD) ins->type=DIV_INS_PCE; + } + if (ins->std.volMacroLen>0) { + ins->std.volMacroOpen=true; + ins->std.volMacroLoop=reader.readC(); + } else { + ins->std.volMacroOpen=false; + } + } + + ins->std.arpMacroLen=reader.readC(); + if (version>5) { + for (int i=0; istd.arpMacroLen; i++) { + ins->std.arpMacro[i]=reader.readI(); + } + } else { + for (int i=0; istd.arpMacroLen; i++) { + ins->std.arpMacro[i]=reader.readC(); + } + } + if (ins->std.arpMacroLen>0) { + ins->std.arpMacroOpen=true; + ins->std.arpMacroLoop=reader.readC(); + } else { + ins->std.arpMacroOpen=false; + } + if (version>8) { // TODO: when? + ins->std.arpMacroMode=reader.readC(); + } + + ins->std.dutyMacroLen=reader.readC(); + if (version>5) { + for (int i=0; istd.dutyMacroLen; i++) { + ins->std.dutyMacro[i]=reader.readI(); + } + } else { + for (int i=0; istd.dutyMacroLen; i++) { + ins->std.dutyMacro[i]=reader.readC(); + } + } + if (ins->std.dutyMacroLen>0) { + ins->std.dutyMacroOpen=true; + ins->std.dutyMacroLoop=reader.readC(); + } else { + ins->std.dutyMacroOpen=false; + } + + ins->std.waveMacroLen=reader.readC(); + if (version>5) { + for (int i=0; istd.waveMacroLen; i++) { + ins->std.waveMacro[i]=reader.readI(); + } + } else { + for (int i=0; istd.waveMacroLen; i++) { + ins->std.waveMacro[i]=reader.readC(); + } + } + if (ins->std.waveMacroLen>0) { + ins->std.waveMacroOpen=true; + ins->std.waveMacroLoop=reader.readC(); + } else { + ins->std.waveMacroOpen=false; + } + + if (ins->type==DIV_INS_C64) { + ins->c64.triOn=reader.readC(); + ins->c64.sawOn=reader.readC(); + ins->c64.pulseOn=reader.readC(); + ins->c64.noiseOn=reader.readC(); + + ins->c64.a=reader.readC(); + ins->c64.d=reader.readC(); + ins->c64.s=reader.readC(); + ins->c64.r=reader.readC(); + + ins->c64.duty=(reader.readC()*4095)/100; + + ins->c64.ringMod=reader.readC(); + ins->c64.oscSync=reader.readC(); + ins->c64.toFilter=reader.readC(); + if (version<0x07) { // TODO: UNSURE + ins->c64.volIsCutoff=reader.readI(); + } else { + ins->c64.volIsCutoff=reader.readC(); + } + ins->c64.initFilter=reader.readC(); + + ins->c64.res=reader.readC(); + ins->c64.cut=(reader.readC()*2047)/100; + ins->c64.hp=reader.readC(); + ins->c64.bp=reader.readC(); + ins->c64.lp=reader.readC(); + ins->c64.ch3off=reader.readC(); + } + if (ins->type==DIV_INS_GB) { + ins->gb.envVol=reader.readC(); + ins->gb.envDir=reader.readC(); + ins->gb.envLen=reader.readC(); + ins->gb.soundLen=reader.readC(); + } + } + } catch (EndOfFileException& e) { + lastError="premature end of file"; + logE("premature end of file!\n"); + delete ins; + delete[] buf; + return ret; + } + break; + } + case DIV_INSFORMAT_TFI: + try { + reader.seek(0,SEEK_SET); + + ins->type=DIV_INS_FM; + ins->name=stripPath; + + ins->fm.alg=reader.readC(); + ins->fm.fb=reader.readC(); + + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=ins->fm.op[i]; + + op.mult=reader.readC(); + op.dt=reader.readC(); + op.tl=reader.readC(); + op.rs=reader.readC(); + op.ar=reader.readC(); + op.dr=reader.readC(); + op.d2r=reader.readC(); + op.rr=reader.readC(); + op.sl=reader.readC(); + op.ssgEnv=reader.readC(); + } + } catch (EndOfFileException& e) { + lastError="premature end of file"; + logE("premature end of file!\n"); + delete ins; + delete[] buf; + return ret; + } + break; + case DIV_INSFORMAT_VGI: + try { + reader.seek(0,SEEK_SET); + + ins->type=DIV_INS_FM; + ins->name=stripPath; + + ins->fm.alg=reader.readC(); + ins->fm.fb=reader.readC(); + unsigned char fmsams=reader.readC(); + ins->fm.fms=fmsams&7; + ins->fm.ams=fmsams>>4; + + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=ins->fm.op[i]; + + op.mult=reader.readC(); + op.dt=reader.readC(); + op.tl=reader.readC(); + op.rs=reader.readC(); + op.ar=reader.readC(); + op.dr=reader.readC(); + if (op.dr&0x80) { + op.am=1; + op.dr&=0x7f; + } + op.d2r=reader.readC(); + op.rr=reader.readC(); + op.sl=reader.readC(); + op.ssgEnv=reader.readC(); + } + } catch (EndOfFileException& e) { + lastError="premature end of file"; + logE("premature end of file!\n"); + delete ins; + delete[] buf; + return ret; + } + break; + case DIV_INSFORMAT_FTI: // TODO + break; + case DIV_INSFORMAT_BTI: // TODO + break; + case DIV_INSFORMAT_OPM: // TODO + break; + case DIV_INSFORMAT_S3I: + try { + reader.seek(0, SEEK_SET); + + uint8_t s3i_type = reader.readC(); + + if (s3i_type >= 2) { + ins->type = DIV_INS_OPL; + // skip internal filename - we'll use the long name description + reader.seek(12, SEEK_CUR); + + // skip reserved bytes + reader.seek(3, SEEK_CUR); + + // 12-byte opl value + uint8_t s3i_Mcharacteristics = reader.readC(); + uint8_t s3i_Ccharacteristics = reader.readC(); + uint8_t s3i_Mscaling_output = reader.readC(); + uint8_t s3i_Cscaling_output = reader.readC(); + uint8_t s3i_Meg_AD = reader.readC(); + uint8_t s3i_Ceg_AD = reader.readC(); + uint8_t s3i_Meg_SR = reader.readC(); + uint8_t s3i_Ceg_SR = reader.readC(); + uint8_t s3i_Mwave = reader.readC(); + uint8_t s3i_Cwave = reader.readC(); + uint8_t s3i_FeedConnect = reader.readC(); + + DivInstrumentFM::Operator& opM = ins->fm.op[0]; + DivInstrumentFM::Operator& opC = ins->fm.op[1]; + ins->fm.ops = 2; + opM.mult = s3i_Mcharacteristics & 0xF; + opM.ksr = ((s3i_Mcharacteristics >> 4) & 0x1); + opM.sus = ((s3i_Mcharacteristics >> 5) & 0x1); + opM.vib = ((s3i_Mcharacteristics >> 6) & 0x1); + opM.am = ((s3i_Mcharacteristics >> 7) & 0x1); + opM.tl = s3i_Mscaling_output & 0x3F; + opM.ksl = ((s3i_Mscaling_output >> 6) & 0x3); + opM.ar = ((s3i_Meg_AD >> 4) & 0xF); + opM.dr = (s3i_Meg_AD & 0xF); + opM.rr = (s3i_Meg_SR & 0xF); + opM.sl = ((s3i_Meg_SR >> 4) & 0xF); + opM.ws = s3i_Mwave; + + ins->fm.alg = (s3i_FeedConnect & 0x1); + ins->fm.fb = ((s3i_FeedConnect >> 1) & 0x7); + + opC.mult = s3i_Ccharacteristics & 0xF; + opC.ksr = ((s3i_Ccharacteristics >> 4) & 0x1); + opC.sus = ((s3i_Ccharacteristics >> 5) & 0x1); + opC.vib = ((s3i_Ccharacteristics >> 6) & 0x1); + opC.am = ((s3i_Ccharacteristics >> 7) & 0x1); + opC.tl = s3i_Cscaling_output & 0x3F; + opC.ksl = ((s3i_Cscaling_output >> 6) & 0x3); + opC.ar = ((s3i_Ceg_AD >> 4) & 0xF); + opC.dr = (s3i_Ceg_AD & 0xF); + opC.rr = (s3i_Ceg_SR & 0xF); + opC.sl = ((s3i_Ceg_SR >> 4) & 0xF); + opC.ws = s3i_Cwave; + + // Skip more stuff we don't need + reader.seek(21, SEEK_CUR); + } else { + logE("S3I PCM samples currently not supported."); + } + ins->name = reader.readString(28); + int s3i_signature = reader.readI(); + + if (s3i_signature != 0x49524353) { + logW("S3I signature invalid."); + }; + } + catch (EndOfFileException& e) { + lastError = "premature end of file"; + logE("premature end of file!\n"); + delete ins; + delete[] buf; + return ret; + } + break; + case DIV_INSFORMAT_SBI: + try { + reader.seek(0, SEEK_SET); + ins->type = DIV_INS_OPL; + + int sbi_header = reader.readI(); + // SBI header determines format + bool is_2op = (sbi_header == 0x1A494253); // SBI\x1A + bool is_4op = (sbi_header == 0x1A504F34); // 4OP\x1A + bool is_6op = (sbi_header == 0x1A504F36); // 6OP\x1A - Freq Monster 801-specific + + // 32-byte null terminated instrument name + ins->name = reader.readString(32); + + // 2op SBI + uint8_t sbi_Mcharacteristics = reader.readC(); + uint8_t sbi_Ccharacteristics = reader.readC(); + uint8_t sbi_Mscaling_output = reader.readC(); + uint8_t sbi_Cscaling_output = reader.readC(); + uint8_t sbi_Meg_AD = reader.readC(); + uint8_t sbi_Ceg_AD = reader.readC(); + uint8_t sbi_Meg_SR = reader.readC(); + uint8_t sbi_Ceg_SR = reader.readC(); + uint8_t sbi_Mwave = reader.readC(); + uint8_t sbi_Cwave = reader.readC(); + uint8_t sbi_FeedConnect = reader.readC(); + + // 4op SBI + uint8_t sbi_M4characteristics; + uint8_t sbi_C4characteristics; + uint8_t sbi_M4scaling_output; + uint8_t sbi_C4scaling_output; + uint8_t sbi_M4eg_AD; + uint8_t sbi_C4eg_AD; + uint8_t sbi_M4eg_SR; + uint8_t sbi_C4eg_SR; + uint8_t sbi_M4wave; + uint8_t sbi_C4wave; + uint8_t sbi_4opConnect; + + if (is_2op) { + DivInstrumentFM::Operator& opM = ins->fm.op[0]; + DivInstrumentFM::Operator& opC = ins->fm.op[1]; + ins->fm.ops = 2; + opM.mult = sbi_Mcharacteristics & 0xF; + opM.ksr = ((sbi_Mcharacteristics >> 4) & 0x1); + opM.sus = ((sbi_Mcharacteristics >> 5) & 0x1); + opM.vib = ((sbi_Mcharacteristics >> 6) & 0x1); + opM.am = ((sbi_Mcharacteristics >> 7) & 0x1); + opM.tl = sbi_Mscaling_output & 0x3F; + opM.ksl = ((sbi_Mscaling_output >> 6) & 0x3); + opM.ar = ((sbi_Meg_AD >> 4) & 0xF); + opM.dr = (sbi_Meg_AD & 0xF); + opM.rr = (sbi_Meg_SR & 0xF); + opM.sl = ((sbi_Meg_SR >> 4) & 0xF); + opM.ws = sbi_Mwave; + + ins->fm.alg = (sbi_FeedConnect & 0x1); + ins->fm.fb = ((sbi_FeedConnect >> 1) & 0x7); + + opC.mult = sbi_Ccharacteristics & 0xF; + opC.ksr = ((sbi_Ccharacteristics >> 4) & 0x1); + opC.sus = ((sbi_Ccharacteristics >> 5) & 0x1); + opC.vib = ((sbi_Ccharacteristics >> 6) & 0x1); + opC.am = ((sbi_Ccharacteristics >> 7) & 0x1); + opC.tl = sbi_Cscaling_output & 0x3F; + opC.ksl = ((sbi_Cscaling_output >> 6) & 0x3); + opC.ar = ((sbi_Ceg_AD >> 4) & 0xF); + opC.dr = (sbi_Ceg_AD & 0xF); + opC.rr = (sbi_Ceg_SR & 0xF); + opC.sl = ((sbi_Ceg_SR >> 4) & 0xF); + opC.ws = sbi_Cwave; + + // Ignore rest of file - rest is 'reserved padding'. + reader.seek(0, SEEK_END); + } + + if (is_4op || is_6op) { + // Operator placement is different so need to place in correct registers. + // Note: 6op is an unofficial extension of 4op SBIs by Darron Broad (Freq Monster 801). + // We'll only use the 4op portion here for pure OPL3. + DivInstrumentFM::Operator& opM = ins->fm.op[0]; + DivInstrumentFM::Operator& opC = ins->fm.op[2]; + DivInstrumentFM::Operator& opM4 = ins->fm.op[1]; + DivInstrumentFM::Operator& opC4 = ins->fm.op[3]; + ins->fm.ops = 4; + + sbi_M4characteristics = reader.readC(); + sbi_C4characteristics = reader.readC(); + sbi_M4scaling_output = reader.readC(); + sbi_C4scaling_output = reader.readC(); + sbi_M4eg_AD = reader.readC(); + sbi_C4eg_AD = reader.readC(); + sbi_M4eg_SR = reader.readC(); + sbi_C4eg_SR = reader.readC(); + sbi_M4wave = reader.readC(); + sbi_C4wave = reader.readC(); + sbi_4opConnect = reader.readC(); + + ins->fm.alg = (sbi_FeedConnect & 0x1) | ((sbi_4opConnect & 0x1) << 1); + ins->fm.fb = ((sbi_FeedConnect >> 1) & 0x7); + + opM.mult = sbi_Mcharacteristics & 0xF; + opM.ksr = ((sbi_Mcharacteristics >> 4) & 0x1); + opM.sus = ((sbi_Mcharacteristics >> 5) & 0x1); + opM.vib = ((sbi_Mcharacteristics >> 6) & 0x1); + opM.am = ((sbi_Mcharacteristics >> 7) & 0x1); + opM.tl = sbi_Mscaling_output & 0x3F; + opM.ksl = ((sbi_Mscaling_output >> 6) & 0x3); + opM.ar = ((sbi_Meg_AD >> 4) & 0xF); + opM.dr = (sbi_Meg_AD & 0xF); + opM.rr = (sbi_Meg_SR & 0xF); + opM.sl = ((sbi_Meg_SR >> 4) & 0xF); + opM.ws = sbi_Mwave; + + opC.mult = sbi_Ccharacteristics & 0xF; + opC.ksr = ((sbi_Ccharacteristics >> 4) & 0x1); + opC.sus = ((sbi_Ccharacteristics >> 5) & 0x1); + opC.vib = ((sbi_Ccharacteristics >> 6) & 0x1); + opC.am = ((sbi_Ccharacteristics >> 7) & 0x1); + opC.tl = sbi_Cscaling_output & 0x3F; + opC.ksl = ((sbi_Cscaling_output >> 6) & 0x3); + opC.ar = ((sbi_Ceg_AD >> 4) & 0xF); + opC.dr = (sbi_Ceg_AD & 0xF); + opC.rr = (sbi_Ceg_SR & 0xF); + opC.sl = ((sbi_Ceg_SR >> 4) & 0xF); + opC.ws = sbi_Cwave; + + opM4.mult = sbi_M4characteristics & 0xF; + opM4.ksr = ((sbi_M4characteristics >> 4) & 0x1); + opM4.sus = ((sbi_M4characteristics >> 5) & 0x1); + opM4.vib = ((sbi_M4characteristics >> 6) & 0x1); + opM4.am = ((sbi_M4characteristics >> 7) & 0x1); + opM4.tl = sbi_M4scaling_output & 0x3F; + opM4.ksl = ((sbi_M4scaling_output >> 6) & 0x3); + opM4.ar = ((sbi_M4eg_AD >> 4) & 0xF); + opM4.dr = (sbi_M4eg_AD & 0xF); + opM4.rr = (sbi_M4eg_SR & 0xF); + opM4.sl = ((sbi_M4eg_SR >> 4) & 0xF); + opM4.ws = sbi_M4wave; + + opC4.mult = sbi_C4characteristics & 0xF; + opC4.ksr = ((sbi_C4characteristics >> 4) & 0x1); + opC4.sus = ((sbi_C4characteristics >> 5) & 0x1); + opC4.vib = ((sbi_C4characteristics >> 6) & 0x1); + opC4.am = ((sbi_C4characteristics >> 7) & 0x1); + opC4.tl = sbi_C4scaling_output & 0x3F; + opC4.ksl = ((sbi_C4scaling_output >> 6) & 0x3); + opC4.ar = ((sbi_C4eg_AD >> 4) & 0xF); + opC4.dr = (sbi_C4eg_AD & 0xF); + opC4.rr = (sbi_C4eg_SR & 0xF); + opC4.sl = ((sbi_C4eg_SR >> 4) & 0xF); + opC4.ws = sbi_C4wave; + + // Ignore rest of file once we've read in all we need. + // Note: Freq Monster 801 adds a ton of other additional fields irrelevant to chip registers. + reader.seek(0, SEEK_END); + } + + } catch (EndOfFileException& e) { + lastError = "premature end of file"; + logE("premature end of file!\n"); + delete ins; + delete[] buf; + return ret; + } + break; + } + + if (reader.tell() Date: Sun, 3 Apr 2022 02:28:46 -0500 Subject: [PATCH 499/637] OPN ext ch: fix mute being overridden when seeking --- src/engine/platform/genesisext.cpp | 46 ++++++++++++++++++++++++++++-- src/engine/platform/ym2610bext.cpp | 45 ++++++++++++++++++++++++++++- src/engine/platform/ym2610ext.cpp | 45 ++++++++++++++++++++++++++++- 3 files changed, 132 insertions(+), 4 deletions(-) diff --git a/src/engine/platform/genesisext.cpp b/src/engine/platform/genesisext.cpp index 2fb1ab390..f77dbdf04 100644 --- a/src/engine/platform/genesisext.cpp +++ b/src/engine/platform/genesisext.cpp @@ -175,7 +175,9 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) { unsigned short baseAddr=chanOffs[2]|opOffs[orderedOps[c.value]]; DivInstrumentFM::Operator& op=chan[2].state.op[orderedOps[c.value]]; op.tl=c.value2; - if (isOutput[chan[2].state.alg][c.value]) { + if (isOpMuted[ch]) { + rWrite(baseAddr+0x40,127); + } else if (isOutput[chan[2].state.alg][c.value]) { rWrite(baseAddr+0x40,127-(((127-op.tl)*(opChan[ch].vol&0x7f))/127)); } else { rWrite(baseAddr+0x40,op.tl); @@ -313,7 +315,47 @@ void DivPlatformGenesisExt::tick() { } void DivPlatformGenesisExt::forceIns() { - DivPlatformGenesis::forceIns(); + for (int i=0; i<6; i++) { + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + DivInstrumentFM::Operator& op=chan[i].state.op[j]; + if (i==2) { // extended channel + if (isOpMuted[j]) { + rWrite(baseAddr+0x40,127); + } else if (isOutput[chan[i].state.alg][j]) { + rWrite(baseAddr+0x40,127-(((127-op.tl)*(opChan[j].vol&0x7f))/127)); + } else { + rWrite(baseAddr+0x40,op.tl); + } + } else { + if (isMuted[i]) { + rWrite(baseAddr+ADDR_TL,127); + } else { + if (isOutput[chan[i].state.alg][j]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + } + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); + rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + if (chan[i].active) { + chan[i].keyOn=true; + chan[i].freqChanged=true; + } + } + if (dacMode) { + rWrite(0x2b,0x80); + } + immWrite(0x22,lfoValue); for (int i=0; i<4; i++) { opChan[i].insChanged=true; if (opChan[i].active) { diff --git a/src/engine/platform/ym2610bext.cpp b/src/engine/platform/ym2610bext.cpp index 597710ced..cdea5bba6 100644 --- a/src/engine/platform/ym2610bext.cpp +++ b/src/engine/platform/ym2610bext.cpp @@ -22,6 +22,7 @@ #include #include "ym2610shared.h" +#include "fmshared_OPN.h" int DivPlatformYM2610BExt::dispatch(DivCommand c) { if (c.chan<2) { @@ -275,7 +276,49 @@ void DivPlatformYM2610BExt::muteChannel(int ch, bool mute) { } void DivPlatformYM2610BExt::forceIns() { - DivPlatformYM2610B::forceIns(); + for (int i=0; i<6; i++) { + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + DivInstrumentFM::Operator& op=chan[i].state.op[j]; + if (i==2) { // extended channel + if (isOpMuted[j]) { + rWrite(baseAddr+0x40,127); + } else if (isOutput[chan[i].state.alg][j]) { + rWrite(baseAddr+0x40,127-(((127-op.tl)*(opChan[j].vol&0x7f))/127)); + } else { + rWrite(baseAddr+0x40,op.tl); + } + } else { + if (isMuted[i]) { + rWrite(baseAddr+ADDR_TL,127); + } else { + if (isOutput[chan[i].state.alg][j]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + } + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); + rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + if (chan[i].active) { + chan[i].keyOn=true; + chan[i].freqChanged=true; + } + } + for (int i=6; i<16; i++) { + chan[i].insChanged=true; + } + immWrite(0x0b,ayEnvPeriod); + immWrite(0x0c,ayEnvPeriod>>8); + immWrite(0x0d,ayEnvMode); for (int i=0; i<4; i++) { opChan[i].insChanged=true; if (opChan[i].active) { diff --git a/src/engine/platform/ym2610ext.cpp b/src/engine/platform/ym2610ext.cpp index 8ef9c16ea..dcc905c36 100644 --- a/src/engine/platform/ym2610ext.cpp +++ b/src/engine/platform/ym2610ext.cpp @@ -22,6 +22,7 @@ #include #include "ym2610shared.h" +#include "fmshared_OPN.h" int DivPlatformYM2610Ext::dispatch(DivCommand c) { if (c.chan<1) { @@ -275,7 +276,49 @@ void DivPlatformYM2610Ext::muteChannel(int ch, bool mute) { } void DivPlatformYM2610Ext::forceIns() { - DivPlatformYM2610::forceIns(); + for (int i=0; i<4; i++) { + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + DivInstrumentFM::Operator& op=chan[i].state.op[j]; + if (i==1) { // extended channel + if (isOpMuted[j]) { + rWrite(baseAddr+0x40,127); + } else if (isOutput[chan[i].state.alg][j]) { + rWrite(baseAddr+0x40,127-(((127-op.tl)*(opChan[j].vol&0x7f))/127)); + } else { + rWrite(baseAddr+0x40,op.tl); + } + } else { + if (isMuted[i]) { + rWrite(baseAddr+ADDR_TL,127); + } else { + if (isOutput[chan[i].state.alg][j]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + } + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); + } + rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); + rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); + if (chan[i].active) { + chan[i].keyOn=true; + chan[i].freqChanged=true; + } + } + for (int i=4; i<14; i++) { + chan[i].insChanged=true; + } + immWrite(0x0b,ayEnvPeriod); + immWrite(0x0c,ayEnvPeriod>>8); + immWrite(0x0d,ayEnvMode); for (int i=0; i<4; i++) { opChan[i].insChanged=true; if (opChan[i].active) { From 82ae2bf8778105537cc040c589fb8344e3c1e507 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 3 Apr 2022 02:34:35 -0500 Subject: [PATCH 500/637] fix version 9 .dmp -_- --- src/engine/fileOpsIns.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/engine/fileOpsIns.cpp b/src/engine/fileOpsIns.cpp index 6b96db596..7343efa52 100644 --- a/src/engine/fileOpsIns.cpp +++ b/src/engine/fileOpsIns.cpp @@ -261,7 +261,13 @@ std::vector DivEngine::instrumentFromFile(const char* path) { logD("reading FM data...\n"); if (version<10) { if (version>1) { - ins->fm.ops=reader.readC()?4:2; + // bullcrap! no way to determine the instrument type other than a vague FM/STD! + if (reader.size()==51) { + reader.readC(); + ins->fm.ops=4; + } else { + ins->fm.ops=reader.readC()?4:2; + } } else { ins->fm.ops=reader.readC()?2:4; } From 109f80d4daf0f501660a8de05d441c107837dddf Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 3 Apr 2022 13:53:34 -0500 Subject: [PATCH 501/637] re-organize instrument loading code --- src/engine/engine.h | 7 + src/engine/fileOpsIns.cpp | 1202 +++++++++++++++++++------------------ 2 files changed, 627 insertions(+), 582 deletions(-) diff --git a/src/engine/engine.h b/src/engine/engine.h index 56ab10b09..e2db8e094 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -273,6 +273,13 @@ class DivEngine { bool loadFur(unsigned char* file, size_t len); bool loadMod(unsigned char* file, size_t len); + void loadDMP(SafeReader& reader, std::vector& ret, String& stripPath); + void loadTFI(SafeReader& reader, std::vector& ret, String& stripPath); + void loadVGI(SafeReader& reader, std::vector& ret, String& stripPath); + void loadS3I(SafeReader& reader, std::vector& ret, String& stripPath); + void loadSBI(SafeReader& reader, std::vector& ret, String& stripPath); + void loadOPM(SafeReader& reader, std::vector& ret, String& stripPath); + bool initAudioBackend(); bool deinitAudioBackend(); diff --git a/src/engine/fileOpsIns.cpp b/src/engine/fileOpsIns.cpp index 7343efa52..201ace6ae 100644 --- a/src/engine/fileOpsIns.cpp +++ b/src/engine/fileOpsIns.cpp @@ -33,6 +33,619 @@ enum DivInsFormats { DIV_INSFORMAT_OPM }; +void DivEngine::loadDMP(SafeReader& reader, std::vector& ret, String& stripPath) { + DivInstrument* ins=new DivInstrument; + // this is a ridiculous mess + unsigned char version=0; + unsigned char sys=0; + try { + reader.seek(0,SEEK_SET); + version=reader.readC(); + logD(".dmp version %d\n",version); + } catch (EndOfFileException& e) { + lastError="premature end of file"; + logE("premature end of file!\n"); + delete ins; + return; + } + + if (version>11) { + lastError="unknown instrument version!"; + delete ins; + return; + } + + ins->name=stripPath; + + if (version>=11) { // 1.0 + try { + sys=reader.readC(); + + switch (sys) { + case 1: // YMU759 + ins->type=DIV_INS_FM; + logD("instrument type is YMU759\n"); + break; + case 2: // Genesis + ins->type=DIV_INS_FM; + logD("instrument type is Genesis\n"); + break; + case 3: // SMS + ins->type=DIV_INS_STD; + logD("instrument type is SMS\n"); + break; + case 4: // Game Boy + ins->type=DIV_INS_GB; + logD("instrument type is Game Boy\n"); + break; + case 5: // PC Engine + ins->type=DIV_INS_PCE; + logD("instrument type is PC Engine\n"); + break; + case 6: // NES + ins->type=DIV_INS_STD; + logD("instrument type is NES\n"); + break; + case 7: case 0x17: // C64 + ins->type=DIV_INS_C64; + logD("instrument type is C64\n"); + break; + case 8: // Arcade + ins->type=DIV_INS_FM; + logD("instrument type is Arcade\n"); + break; + default: + logD("instrument type is unknown\n"); + lastError="unknown instrument type!"; + delete ins; + return; + break; + } + } catch (EndOfFileException& e) { + lastError="premature end of file"; + logE("premature end of file!\n"); + delete ins; + return; + } + } + + try { + bool mode=true; + if (version>1) { + mode=reader.readC(); + logD("instrument mode is %d\n",mode); + if (mode==0) { + if (version<11) { + ins->type=DIV_INS_STD; + } + } else { + ins->type=DIV_INS_FM; + } + } else { + ins->type=DIV_INS_FM; + } + + if (mode) { // FM + logD("reading FM data...\n"); + if (version<10) { + if (version>1) { + // bullcrap! no way to determine the instrument type other than a vague FM/STD! + if (reader.size()==51) { + reader.readC(); + ins->fm.ops=4; + } else { + ins->fm.ops=reader.readC()?4:2; + } + } else { + ins->fm.ops=reader.readC()?2:4; + } + } else { + ins->fm.ops=4; + } + if (version>1) { // HELP! in which version of the format did we start storing FMS! + ins->fm.fms=reader.readC(); + } + ins->fm.fb=reader.readC(); + ins->fm.alg=reader.readC(); + // DITTO + if (sys!=1) ins->fm.ams=reader.readC(); + + for (int j=0; jfm.ops; j++) { + logD("OP%d is at %d\n",j,reader.tell()); + ins->fm.op[j].mult=reader.readC(); + ins->fm.op[j].tl=reader.readC(); + ins->fm.op[j].ar=reader.readC(); + ins->fm.op[j].dr=reader.readC(); + ins->fm.op[j].sl=reader.readC(); + ins->fm.op[j].rr=reader.readC(); + ins->fm.op[j].am=reader.readC(); + // what the hell how do I tell! + if (sys==1) { // YMU759 + ins->fm.op[j].ws=reader.readC(); + ins->fm.op[j].ksl=reader.readC(); + ins->fm.op[j].vib=reader.readC(); + ins->fm.op[j].egt=reader.readC(); + ins->fm.op[j].sus=reader.readC(); + ins->fm.op[j].ksr=reader.readC(); + ins->fm.op[j].dvb=reader.readC(); + ins->fm.op[j].dam=reader.readC(); + } else { + ins->fm.op[j].rs=reader.readC(); + ins->fm.op[j].dt=reader.readC(); + ins->fm.op[j].dt2=ins->fm.op[j].dt>>4; + ins->fm.op[j].dt&=15; + ins->fm.op[j].d2r=reader.readC(); + ins->fm.op[j].ssgEnv=reader.readC(); + } + } + } else { // STD + logD("reading STD data...\n"); + if (ins->type!=DIV_INS_GB) { + ins->std.volMacroLen=reader.readC(); + if (version>5) { + for (int i=0; istd.volMacroLen; i++) { + ins->std.volMacro[i]=reader.readI(); + } + } else { + for (int i=0; istd.volMacroLen; i++) { + ins->std.volMacro[i]=reader.readC(); + } + } + if (version<11) for (int i=0; istd.volMacroLen; i++) { + if (ins->std.volMacro[i]>15 && ins->type==DIV_INS_STD) ins->type=DIV_INS_PCE; + } + if (ins->std.volMacroLen>0) { + ins->std.volMacroOpen=true; + ins->std.volMacroLoop=reader.readC(); + } else { + ins->std.volMacroOpen=false; + } + } + + ins->std.arpMacroLen=reader.readC(); + if (version>5) { + for (int i=0; istd.arpMacroLen; i++) { + ins->std.arpMacro[i]=reader.readI(); + } + } else { + for (int i=0; istd.arpMacroLen; i++) { + ins->std.arpMacro[i]=reader.readC(); + } + } + if (ins->std.arpMacroLen>0) { + ins->std.arpMacroOpen=true; + ins->std.arpMacroLoop=reader.readC(); + } else { + ins->std.arpMacroOpen=false; + } + if (version>8) { // TODO: when? + ins->std.arpMacroMode=reader.readC(); + } + + ins->std.dutyMacroLen=reader.readC(); + if (version>5) { + for (int i=0; istd.dutyMacroLen; i++) { + ins->std.dutyMacro[i]=reader.readI(); + } + } else { + for (int i=0; istd.dutyMacroLen; i++) { + ins->std.dutyMacro[i]=reader.readC(); + } + } + if (ins->std.dutyMacroLen>0) { + ins->std.dutyMacroOpen=true; + ins->std.dutyMacroLoop=reader.readC(); + } else { + ins->std.dutyMacroOpen=false; + } + + ins->std.waveMacroLen=reader.readC(); + if (version>5) { + for (int i=0; istd.waveMacroLen; i++) { + ins->std.waveMacro[i]=reader.readI(); + } + } else { + for (int i=0; istd.waveMacroLen; i++) { + ins->std.waveMacro[i]=reader.readC(); + } + } + if (ins->std.waveMacroLen>0) { + ins->std.waveMacroOpen=true; + ins->std.waveMacroLoop=reader.readC(); + } else { + ins->std.waveMacroOpen=false; + } + + if (ins->type==DIV_INS_C64) { + ins->c64.triOn=reader.readC(); + ins->c64.sawOn=reader.readC(); + ins->c64.pulseOn=reader.readC(); + ins->c64.noiseOn=reader.readC(); + + ins->c64.a=reader.readC(); + ins->c64.d=reader.readC(); + ins->c64.s=reader.readC(); + ins->c64.r=reader.readC(); + + ins->c64.duty=(reader.readC()*4095)/100; + + ins->c64.ringMod=reader.readC(); + ins->c64.oscSync=reader.readC(); + ins->c64.toFilter=reader.readC(); + if (version<0x07) { // TODO: UNSURE + ins->c64.volIsCutoff=reader.readI(); + } else { + ins->c64.volIsCutoff=reader.readC(); + } + ins->c64.initFilter=reader.readC(); + + ins->c64.res=reader.readC(); + ins->c64.cut=(reader.readC()*2047)/100; + ins->c64.hp=reader.readC(); + ins->c64.bp=reader.readC(); + ins->c64.lp=reader.readC(); + ins->c64.ch3off=reader.readC(); + } + if (ins->type==DIV_INS_GB) { + ins->gb.envVol=reader.readC(); + ins->gb.envDir=reader.readC(); + ins->gb.envLen=reader.readC(); + ins->gb.soundLen=reader.readC(); + } + } + } catch (EndOfFileException& e) { + lastError="premature end of file"; + logE("premature end of file!\n"); + delete ins; + return; + } + + ret.push_back(ins); +} + +void DivEngine::loadTFI(SafeReader& reader, std::vector& ret, String& stripPath) { + DivInstrument* ins=new DivInstrument; + try { + reader.seek(0,SEEK_SET); + + ins->type=DIV_INS_FM; + ins->name=stripPath; + + ins->fm.alg=reader.readC(); + ins->fm.fb=reader.readC(); + + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=ins->fm.op[i]; + + op.mult=reader.readC(); + op.dt=reader.readC(); + op.tl=reader.readC(); + op.rs=reader.readC(); + op.ar=reader.readC(); + op.dr=reader.readC(); + op.d2r=reader.readC(); + op.rr=reader.readC(); + op.sl=reader.readC(); + op.ssgEnv=reader.readC(); + } + } catch (EndOfFileException& e) { + lastError="premature end of file"; + logE("premature end of file!\n"); + delete ins; + return; + } + + ret.push_back(ins); +} + +void DivEngine::loadVGI(SafeReader& reader, std::vector& ret, String& stripPath) { + DivInstrument* ins=new DivInstrument; + try { + reader.seek(0,SEEK_SET); + + ins->type=DIV_INS_FM; + ins->name=stripPath; + + ins->fm.alg=reader.readC(); + ins->fm.fb=reader.readC(); + unsigned char fmsams=reader.readC(); + ins->fm.fms=fmsams&7; + ins->fm.ams=fmsams>>4; + + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=ins->fm.op[i]; + + op.mult=reader.readC(); + op.dt=reader.readC(); + op.tl=reader.readC(); + op.rs=reader.readC(); + op.ar=reader.readC(); + op.dr=reader.readC(); + if (op.dr&0x80) { + op.am=1; + op.dr&=0x7f; + } + op.d2r=reader.readC(); + op.rr=reader.readC(); + op.sl=reader.readC(); + op.ssgEnv=reader.readC(); + } + } catch (EndOfFileException& e) { + lastError="premature end of file"; + logE("premature end of file!\n"); + delete ins; + return; + } + + ret.push_back(ins); +} + +void DivEngine::loadS3I(SafeReader& reader, std::vector& ret, String& stripPath) { + DivInstrument* ins=new DivInstrument; + try { + reader.seek(0, SEEK_SET); + + uint8_t s3i_type = reader.readC(); + + if (s3i_type >= 2) { + ins->type = DIV_INS_OPL; + // skip internal filename - we'll use the long name description + reader.seek(12, SEEK_CUR); + + // skip reserved bytes + reader.seek(3, SEEK_CUR); + + // 12-byte opl value + uint8_t s3i_Mcharacteristics = reader.readC(); + uint8_t s3i_Ccharacteristics = reader.readC(); + uint8_t s3i_Mscaling_output = reader.readC(); + uint8_t s3i_Cscaling_output = reader.readC(); + uint8_t s3i_Meg_AD = reader.readC(); + uint8_t s3i_Ceg_AD = reader.readC(); + uint8_t s3i_Meg_SR = reader.readC(); + uint8_t s3i_Ceg_SR = reader.readC(); + uint8_t s3i_Mwave = reader.readC(); + uint8_t s3i_Cwave = reader.readC(); + uint8_t s3i_FeedConnect = reader.readC(); + + DivInstrumentFM::Operator& opM = ins->fm.op[0]; + DivInstrumentFM::Operator& opC = ins->fm.op[1]; + ins->fm.ops = 2; + opM.mult = s3i_Mcharacteristics & 0xF; + opM.ksr = ((s3i_Mcharacteristics >> 4) & 0x1); + opM.sus = ((s3i_Mcharacteristics >> 5) & 0x1); + opM.vib = ((s3i_Mcharacteristics >> 6) & 0x1); + opM.am = ((s3i_Mcharacteristics >> 7) & 0x1); + opM.tl = s3i_Mscaling_output & 0x3F; + opM.ksl = ((s3i_Mscaling_output >> 6) & 0x3); + opM.ar = ((s3i_Meg_AD >> 4) & 0xF); + opM.dr = (s3i_Meg_AD & 0xF); + opM.rr = (s3i_Meg_SR & 0xF); + opM.sl = ((s3i_Meg_SR >> 4) & 0xF); + opM.ws = s3i_Mwave; + + ins->fm.alg = (s3i_FeedConnect & 0x1); + ins->fm.fb = ((s3i_FeedConnect >> 1) & 0x7); + + opC.mult = s3i_Ccharacteristics & 0xF; + opC.ksr = ((s3i_Ccharacteristics >> 4) & 0x1); + opC.sus = ((s3i_Ccharacteristics >> 5) & 0x1); + opC.vib = ((s3i_Ccharacteristics >> 6) & 0x1); + opC.am = ((s3i_Ccharacteristics >> 7) & 0x1); + opC.tl = s3i_Cscaling_output & 0x3F; + opC.ksl = ((s3i_Cscaling_output >> 6) & 0x3); + opC.ar = ((s3i_Ceg_AD >> 4) & 0xF); + opC.dr = (s3i_Ceg_AD & 0xF); + opC.rr = (s3i_Ceg_SR & 0xF); + opC.sl = ((s3i_Ceg_SR >> 4) & 0xF); + opC.ws = s3i_Cwave; + + // Skip more stuff we don't need + reader.seek(21, SEEK_CUR); + } else { + logE("S3I PCM samples currently not supported."); + } + ins->name = reader.readString(28); + int s3i_signature = reader.readI(); + + if (s3i_signature != 0x49524353) { + logW("S3I signature invalid."); + }; + } catch (EndOfFileException& e) { + lastError = "premature end of file"; + logE("premature end of file!\n"); + delete ins; + return; + } + + ret.push_back(ins); +} + +void DivEngine::loadSBI(SafeReader& reader, std::vector& ret, String& stripPath) { + DivInstrument* ins=new DivInstrument; + try { + reader.seek(0, SEEK_SET); + ins->type = DIV_INS_OPL; + + int sbi_header = reader.readI(); + // SBI header determines format + bool is_2op = (sbi_header == 0x1A494253); // SBI\x1A + bool is_4op = (sbi_header == 0x1A504F34); // 4OP\x1A + bool is_6op = (sbi_header == 0x1A504F36); // 6OP\x1A - Freq Monster 801-specific + + // 32-byte null terminated instrument name + ins->name = reader.readString(32); + + // 2op SBI + uint8_t sbi_Mcharacteristics = reader.readC(); + uint8_t sbi_Ccharacteristics = reader.readC(); + uint8_t sbi_Mscaling_output = reader.readC(); + uint8_t sbi_Cscaling_output = reader.readC(); + uint8_t sbi_Meg_AD = reader.readC(); + uint8_t sbi_Ceg_AD = reader.readC(); + uint8_t sbi_Meg_SR = reader.readC(); + uint8_t sbi_Ceg_SR = reader.readC(); + uint8_t sbi_Mwave = reader.readC(); + uint8_t sbi_Cwave = reader.readC(); + uint8_t sbi_FeedConnect = reader.readC(); + + // 4op SBI + uint8_t sbi_M4characteristics; + uint8_t sbi_C4characteristics; + uint8_t sbi_M4scaling_output; + uint8_t sbi_C4scaling_output; + uint8_t sbi_M4eg_AD; + uint8_t sbi_C4eg_AD; + uint8_t sbi_M4eg_SR; + uint8_t sbi_C4eg_SR; + uint8_t sbi_M4wave; + uint8_t sbi_C4wave; + uint8_t sbi_4opConnect; + + if (is_2op) { + DivInstrumentFM::Operator& opM = ins->fm.op[0]; + DivInstrumentFM::Operator& opC = ins->fm.op[1]; + ins->fm.ops = 2; + opM.mult = sbi_Mcharacteristics & 0xF; + opM.ksr = ((sbi_Mcharacteristics >> 4) & 0x1); + opM.sus = ((sbi_Mcharacteristics >> 5) & 0x1); + opM.vib = ((sbi_Mcharacteristics >> 6) & 0x1); + opM.am = ((sbi_Mcharacteristics >> 7) & 0x1); + opM.tl = sbi_Mscaling_output & 0x3F; + opM.ksl = ((sbi_Mscaling_output >> 6) & 0x3); + opM.ar = ((sbi_Meg_AD >> 4) & 0xF); + opM.dr = (sbi_Meg_AD & 0xF); + opM.rr = (sbi_Meg_SR & 0xF); + opM.sl = ((sbi_Meg_SR >> 4) & 0xF); + opM.ws = sbi_Mwave; + + ins->fm.alg = (sbi_FeedConnect & 0x1); + ins->fm.fb = ((sbi_FeedConnect >> 1) & 0x7); + + opC.mult = sbi_Ccharacteristics & 0xF; + opC.ksr = ((sbi_Ccharacteristics >> 4) & 0x1); + opC.sus = ((sbi_Ccharacteristics >> 5) & 0x1); + opC.vib = ((sbi_Ccharacteristics >> 6) & 0x1); + opC.am = ((sbi_Ccharacteristics >> 7) & 0x1); + opC.tl = sbi_Cscaling_output & 0x3F; + opC.ksl = ((sbi_Cscaling_output >> 6) & 0x3); + opC.ar = ((sbi_Ceg_AD >> 4) & 0xF); + opC.dr = (sbi_Ceg_AD & 0xF); + opC.rr = (sbi_Ceg_SR & 0xF); + opC.sl = ((sbi_Ceg_SR >> 4) & 0xF); + opC.ws = sbi_Cwave; + + // Ignore rest of file - rest is 'reserved padding'. + reader.seek(0, SEEK_END); + } + + if (is_4op || is_6op) { + // Operator placement is different so need to place in correct registers. + // Note: 6op is an unofficial extension of 4op SBIs by Darron Broad (Freq Monster 801). + // We'll only use the 4op portion here for pure OPL3. + DivInstrumentFM::Operator& opM = ins->fm.op[0]; + DivInstrumentFM::Operator& opC = ins->fm.op[2]; + DivInstrumentFM::Operator& opM4 = ins->fm.op[1]; + DivInstrumentFM::Operator& opC4 = ins->fm.op[3]; + ins->fm.ops = 4; + + sbi_M4characteristics = reader.readC(); + sbi_C4characteristics = reader.readC(); + sbi_M4scaling_output = reader.readC(); + sbi_C4scaling_output = reader.readC(); + sbi_M4eg_AD = reader.readC(); + sbi_C4eg_AD = reader.readC(); + sbi_M4eg_SR = reader.readC(); + sbi_C4eg_SR = reader.readC(); + sbi_M4wave = reader.readC(); + sbi_C4wave = reader.readC(); + sbi_4opConnect = reader.readC(); + + ins->fm.alg = (sbi_FeedConnect & 0x1) | ((sbi_4opConnect & 0x1) << 1); + ins->fm.fb = ((sbi_FeedConnect >> 1) & 0x7); + + opM.mult = sbi_Mcharacteristics & 0xF; + opM.ksr = ((sbi_Mcharacteristics >> 4) & 0x1); + opM.sus = ((sbi_Mcharacteristics >> 5) & 0x1); + opM.vib = ((sbi_Mcharacteristics >> 6) & 0x1); + opM.am = ((sbi_Mcharacteristics >> 7) & 0x1); + opM.tl = sbi_Mscaling_output & 0x3F; + opM.ksl = ((sbi_Mscaling_output >> 6) & 0x3); + opM.ar = ((sbi_Meg_AD >> 4) & 0xF); + opM.dr = (sbi_Meg_AD & 0xF); + opM.rr = (sbi_Meg_SR & 0xF); + opM.sl = ((sbi_Meg_SR >> 4) & 0xF); + opM.ws = sbi_Mwave; + + opC.mult = sbi_Ccharacteristics & 0xF; + opC.ksr = ((sbi_Ccharacteristics >> 4) & 0x1); + opC.sus = ((sbi_Ccharacteristics >> 5) & 0x1); + opC.vib = ((sbi_Ccharacteristics >> 6) & 0x1); + opC.am = ((sbi_Ccharacteristics >> 7) & 0x1); + opC.tl = sbi_Cscaling_output & 0x3F; + opC.ksl = ((sbi_Cscaling_output >> 6) & 0x3); + opC.ar = ((sbi_Ceg_AD >> 4) & 0xF); + opC.dr = (sbi_Ceg_AD & 0xF); + opC.rr = (sbi_Ceg_SR & 0xF); + opC.sl = ((sbi_Ceg_SR >> 4) & 0xF); + opC.ws = sbi_Cwave; + + opM4.mult = sbi_M4characteristics & 0xF; + opM4.ksr = ((sbi_M4characteristics >> 4) & 0x1); + opM4.sus = ((sbi_M4characteristics >> 5) & 0x1); + opM4.vib = ((sbi_M4characteristics >> 6) & 0x1); + opM4.am = ((sbi_M4characteristics >> 7) & 0x1); + opM4.tl = sbi_M4scaling_output & 0x3F; + opM4.ksl = ((sbi_M4scaling_output >> 6) & 0x3); + opM4.ar = ((sbi_M4eg_AD >> 4) & 0xF); + opM4.dr = (sbi_M4eg_AD & 0xF); + opM4.rr = (sbi_M4eg_SR & 0xF); + opM4.sl = ((sbi_M4eg_SR >> 4) & 0xF); + opM4.ws = sbi_M4wave; + + opC4.mult = sbi_C4characteristics & 0xF; + opC4.ksr = ((sbi_C4characteristics >> 4) & 0x1); + opC4.sus = ((sbi_C4characteristics >> 5) & 0x1); + opC4.vib = ((sbi_C4characteristics >> 6) & 0x1); + opC4.am = ((sbi_C4characteristics >> 7) & 0x1); + opC4.tl = sbi_C4scaling_output & 0x3F; + opC4.ksl = ((sbi_C4scaling_output >> 6) & 0x3); + opC4.ar = ((sbi_C4eg_AD >> 4) & 0xF); + opC4.dr = (sbi_C4eg_AD & 0xF); + opC4.rr = (sbi_C4eg_SR & 0xF); + opC4.sl = ((sbi_C4eg_SR >> 4) & 0xF); + opC4.ws = sbi_C4wave; + + // Ignore rest of file once we've read in all we need. + // Note: Freq Monster 801 adds a ton of other additional fields irrelevant to chip registers. + reader.seek(0, SEEK_END); + } + + } catch (EndOfFileException& e) { + lastError = "premature end of file"; + logE("premature end of file!\n"); + delete ins; + return; + } + + ret.push_back(ins); +} + +void DivEngine::loadOPM(SafeReader& reader, std::vector& ret, String& stripPath) { + DivInstrument* ins[128]; + memset(ins,0,128*sizeof(DivInstrument*)); + + try { + String line; + + } catch (EndOfFileException& e) { + lastError="premature end of file"; + logE("premature end of file!\n"); + delete ins; + return; + } +} + std::vector DivEngine::instrumentFromFile(const char* path) { std::vector ret; warnings=""; @@ -103,8 +716,8 @@ std::vector DivEngine::instrumentFromFile(const char* path) { reader.seek(0,SEEK_SET); } - DivInstrument* ins=new DivInstrument; if (isFurnaceInstr) { + DivInstrument* ins=new DivInstrument; try { short version=reader.readS(); reader.readS(); // reserved @@ -160,350 +773,16 @@ std::vector DivEngine::instrumentFromFile(const char* path) { } } - // TDOO these really should be re-organized to separate functions per instrument file type. switch (format) { case DIV_INSFORMAT_DMP: { - // this is a ridiculous mess - unsigned char version=0; - unsigned char sys=0; - try { - reader.seek(0,SEEK_SET); - version=reader.readC(); - logD(".dmp version %d\n",version); - } catch (EndOfFileException& e) { - lastError="premature end of file"; - logE("premature end of file!\n"); - delete ins; - delete[] buf; - return ret; - } - - if (version>11) { - lastError="unknown instrument version!"; - delete ins; - delete[] buf; - return ret; - } - - ins->name=stripPath; - - if (version>=11) { // 1.0 - try { - sys=reader.readC(); - - switch (sys) { - case 1: // YMU759 - ins->type=DIV_INS_FM; - logD("instrument type is YMU759\n"); - break; - case 2: // Genesis - ins->type=DIV_INS_FM; - logD("instrument type is Genesis\n"); - break; - case 3: // SMS - ins->type=DIV_INS_STD; - logD("instrument type is SMS\n"); - break; - case 4: // Game Boy - ins->type=DIV_INS_GB; - logD("instrument type is Game Boy\n"); - break; - case 5: // PC Engine - ins->type=DIV_INS_PCE; - logD("instrument type is PC Engine\n"); - break; - case 6: // NES - ins->type=DIV_INS_STD; - logD("instrument type is NES\n"); - break; - case 7: case 0x17: // C64 - ins->type=DIV_INS_C64; - logD("instrument type is C64\n"); - break; - case 8: // Arcade - ins->type=DIV_INS_FM; - logD("instrument type is Arcade\n"); - break; - default: - logD("instrument type is unknown\n"); - lastError="unknown instrument type!"; - delete ins; - delete[] buf; - return ret; - break; - } - } catch (EndOfFileException& e) { - lastError="premature end of file"; - logE("premature end of file!\n"); - delete ins; - delete[] buf; - return ret; - } - } - - try { - bool mode=true; - if (version>1) { - mode=reader.readC(); - logD("instrument mode is %d\n",mode); - if (mode==0) { - if (version<11) { - ins->type=DIV_INS_STD; - } - } else { - ins->type=DIV_INS_FM; - } - } else { - ins->type=DIV_INS_FM; - } - - if (mode) { // FM - logD("reading FM data...\n"); - if (version<10) { - if (version>1) { - // bullcrap! no way to determine the instrument type other than a vague FM/STD! - if (reader.size()==51) { - reader.readC(); - ins->fm.ops=4; - } else { - ins->fm.ops=reader.readC()?4:2; - } - } else { - ins->fm.ops=reader.readC()?2:4; - } - } else { - ins->fm.ops=4; - } - if (version>1) { // HELP! in which version of the format did we start storing FMS! - ins->fm.fms=reader.readC(); - } - ins->fm.fb=reader.readC(); - ins->fm.alg=reader.readC(); - // DITTO - if (sys!=1) ins->fm.ams=reader.readC(); - - for (int j=0; jfm.ops; j++) { - logD("OP%d is at %d\n",j,reader.tell()); - ins->fm.op[j].mult=reader.readC(); - ins->fm.op[j].tl=reader.readC(); - ins->fm.op[j].ar=reader.readC(); - ins->fm.op[j].dr=reader.readC(); - ins->fm.op[j].sl=reader.readC(); - ins->fm.op[j].rr=reader.readC(); - ins->fm.op[j].am=reader.readC(); - // what the hell how do I tell! - if (sys==1) { // YMU759 - ins->fm.op[j].ws=reader.readC(); - ins->fm.op[j].ksl=reader.readC(); - ins->fm.op[j].vib=reader.readC(); - ins->fm.op[j].egt=reader.readC(); - ins->fm.op[j].sus=reader.readC(); - ins->fm.op[j].ksr=reader.readC(); - ins->fm.op[j].dvb=reader.readC(); - ins->fm.op[j].dam=reader.readC(); - } else { - ins->fm.op[j].rs=reader.readC(); - ins->fm.op[j].dt=reader.readC(); - ins->fm.op[j].dt2=ins->fm.op[j].dt>>4; - ins->fm.op[j].dt&=15; - ins->fm.op[j].d2r=reader.readC(); - ins->fm.op[j].ssgEnv=reader.readC(); - } - } - } else { // STD - logD("reading STD data...\n"); - if (ins->type!=DIV_INS_GB) { - ins->std.volMacroLen=reader.readC(); - if (version>5) { - for (int i=0; istd.volMacroLen; i++) { - ins->std.volMacro[i]=reader.readI(); - } - } else { - for (int i=0; istd.volMacroLen; i++) { - ins->std.volMacro[i]=reader.readC(); - } - } - if (version<11) for (int i=0; istd.volMacroLen; i++) { - if (ins->std.volMacro[i]>15 && ins->type==DIV_INS_STD) ins->type=DIV_INS_PCE; - } - if (ins->std.volMacroLen>0) { - ins->std.volMacroOpen=true; - ins->std.volMacroLoop=reader.readC(); - } else { - ins->std.volMacroOpen=false; - } - } - - ins->std.arpMacroLen=reader.readC(); - if (version>5) { - for (int i=0; istd.arpMacroLen; i++) { - ins->std.arpMacro[i]=reader.readI(); - } - } else { - for (int i=0; istd.arpMacroLen; i++) { - ins->std.arpMacro[i]=reader.readC(); - } - } - if (ins->std.arpMacroLen>0) { - ins->std.arpMacroOpen=true; - ins->std.arpMacroLoop=reader.readC(); - } else { - ins->std.arpMacroOpen=false; - } - if (version>8) { // TODO: when? - ins->std.arpMacroMode=reader.readC(); - } - - ins->std.dutyMacroLen=reader.readC(); - if (version>5) { - for (int i=0; istd.dutyMacroLen; i++) { - ins->std.dutyMacro[i]=reader.readI(); - } - } else { - for (int i=0; istd.dutyMacroLen; i++) { - ins->std.dutyMacro[i]=reader.readC(); - } - } - if (ins->std.dutyMacroLen>0) { - ins->std.dutyMacroOpen=true; - ins->std.dutyMacroLoop=reader.readC(); - } else { - ins->std.dutyMacroOpen=false; - } - - ins->std.waveMacroLen=reader.readC(); - if (version>5) { - for (int i=0; istd.waveMacroLen; i++) { - ins->std.waveMacro[i]=reader.readI(); - } - } else { - for (int i=0; istd.waveMacroLen; i++) { - ins->std.waveMacro[i]=reader.readC(); - } - } - if (ins->std.waveMacroLen>0) { - ins->std.waveMacroOpen=true; - ins->std.waveMacroLoop=reader.readC(); - } else { - ins->std.waveMacroOpen=false; - } - - if (ins->type==DIV_INS_C64) { - ins->c64.triOn=reader.readC(); - ins->c64.sawOn=reader.readC(); - ins->c64.pulseOn=reader.readC(); - ins->c64.noiseOn=reader.readC(); - - ins->c64.a=reader.readC(); - ins->c64.d=reader.readC(); - ins->c64.s=reader.readC(); - ins->c64.r=reader.readC(); - - ins->c64.duty=(reader.readC()*4095)/100; - - ins->c64.ringMod=reader.readC(); - ins->c64.oscSync=reader.readC(); - ins->c64.toFilter=reader.readC(); - if (version<0x07) { // TODO: UNSURE - ins->c64.volIsCutoff=reader.readI(); - } else { - ins->c64.volIsCutoff=reader.readC(); - } - ins->c64.initFilter=reader.readC(); - - ins->c64.res=reader.readC(); - ins->c64.cut=(reader.readC()*2047)/100; - ins->c64.hp=reader.readC(); - ins->c64.bp=reader.readC(); - ins->c64.lp=reader.readC(); - ins->c64.ch3off=reader.readC(); - } - if (ins->type==DIV_INS_GB) { - ins->gb.envVol=reader.readC(); - ins->gb.envDir=reader.readC(); - ins->gb.envLen=reader.readC(); - ins->gb.soundLen=reader.readC(); - } - } - } catch (EndOfFileException& e) { - lastError="premature end of file"; - logE("premature end of file!\n"); - delete ins; - delete[] buf; - return ret; - } + loadDMP(reader,ret,stripPath); break; } case DIV_INSFORMAT_TFI: - try { - reader.seek(0,SEEK_SET); - - ins->type=DIV_INS_FM; - ins->name=stripPath; - - ins->fm.alg=reader.readC(); - ins->fm.fb=reader.readC(); - - for (int i=0; i<4; i++) { - DivInstrumentFM::Operator& op=ins->fm.op[i]; - - op.mult=reader.readC(); - op.dt=reader.readC(); - op.tl=reader.readC(); - op.rs=reader.readC(); - op.ar=reader.readC(); - op.dr=reader.readC(); - op.d2r=reader.readC(); - op.rr=reader.readC(); - op.sl=reader.readC(); - op.ssgEnv=reader.readC(); - } - } catch (EndOfFileException& e) { - lastError="premature end of file"; - logE("premature end of file!\n"); - delete ins; - delete[] buf; - return ret; - } + loadTFI(reader,ret,stripPath); break; case DIV_INSFORMAT_VGI: - try { - reader.seek(0,SEEK_SET); - - ins->type=DIV_INS_FM; - ins->name=stripPath; - - ins->fm.alg=reader.readC(); - ins->fm.fb=reader.readC(); - unsigned char fmsams=reader.readC(); - ins->fm.fms=fmsams&7; - ins->fm.ams=fmsams>>4; - - for (int i=0; i<4; i++) { - DivInstrumentFM::Operator& op=ins->fm.op[i]; - - op.mult=reader.readC(); - op.dt=reader.readC(); - op.tl=reader.readC(); - op.rs=reader.readC(); - op.ar=reader.readC(); - op.dr=reader.readC(); - if (op.dr&0x80) { - op.am=1; - op.dr&=0x7f; - } - op.d2r=reader.readC(); - op.rr=reader.readC(); - op.sl=reader.readC(); - op.ssgEnv=reader.readC(); - } - } catch (EndOfFileException& e) { - lastError="premature end of file"; - logE("premature end of file!\n"); - delete ins; - delete[] buf; - return ret; - } + loadVGI(reader,ret,stripPath); break; case DIV_INSFORMAT_FTI: // TODO break; @@ -512,250 +791,10 @@ std::vector DivEngine::instrumentFromFile(const char* path) { case DIV_INSFORMAT_OPM: // TODO break; case DIV_INSFORMAT_S3I: - try { - reader.seek(0, SEEK_SET); - - uint8_t s3i_type = reader.readC(); - - if (s3i_type >= 2) { - ins->type = DIV_INS_OPL; - // skip internal filename - we'll use the long name description - reader.seek(12, SEEK_CUR); - - // skip reserved bytes - reader.seek(3, SEEK_CUR); - - // 12-byte opl value - uint8_t s3i_Mcharacteristics = reader.readC(); - uint8_t s3i_Ccharacteristics = reader.readC(); - uint8_t s3i_Mscaling_output = reader.readC(); - uint8_t s3i_Cscaling_output = reader.readC(); - uint8_t s3i_Meg_AD = reader.readC(); - uint8_t s3i_Ceg_AD = reader.readC(); - uint8_t s3i_Meg_SR = reader.readC(); - uint8_t s3i_Ceg_SR = reader.readC(); - uint8_t s3i_Mwave = reader.readC(); - uint8_t s3i_Cwave = reader.readC(); - uint8_t s3i_FeedConnect = reader.readC(); - - DivInstrumentFM::Operator& opM = ins->fm.op[0]; - DivInstrumentFM::Operator& opC = ins->fm.op[1]; - ins->fm.ops = 2; - opM.mult = s3i_Mcharacteristics & 0xF; - opM.ksr = ((s3i_Mcharacteristics >> 4) & 0x1); - opM.sus = ((s3i_Mcharacteristics >> 5) & 0x1); - opM.vib = ((s3i_Mcharacteristics >> 6) & 0x1); - opM.am = ((s3i_Mcharacteristics >> 7) & 0x1); - opM.tl = s3i_Mscaling_output & 0x3F; - opM.ksl = ((s3i_Mscaling_output >> 6) & 0x3); - opM.ar = ((s3i_Meg_AD >> 4) & 0xF); - opM.dr = (s3i_Meg_AD & 0xF); - opM.rr = (s3i_Meg_SR & 0xF); - opM.sl = ((s3i_Meg_SR >> 4) & 0xF); - opM.ws = s3i_Mwave; - - ins->fm.alg = (s3i_FeedConnect & 0x1); - ins->fm.fb = ((s3i_FeedConnect >> 1) & 0x7); - - opC.mult = s3i_Ccharacteristics & 0xF; - opC.ksr = ((s3i_Ccharacteristics >> 4) & 0x1); - opC.sus = ((s3i_Ccharacteristics >> 5) & 0x1); - opC.vib = ((s3i_Ccharacteristics >> 6) & 0x1); - opC.am = ((s3i_Ccharacteristics >> 7) & 0x1); - opC.tl = s3i_Cscaling_output & 0x3F; - opC.ksl = ((s3i_Cscaling_output >> 6) & 0x3); - opC.ar = ((s3i_Ceg_AD >> 4) & 0xF); - opC.dr = (s3i_Ceg_AD & 0xF); - opC.rr = (s3i_Ceg_SR & 0xF); - opC.sl = ((s3i_Ceg_SR >> 4) & 0xF); - opC.ws = s3i_Cwave; - - // Skip more stuff we don't need - reader.seek(21, SEEK_CUR); - } else { - logE("S3I PCM samples currently not supported."); - } - ins->name = reader.readString(28); - int s3i_signature = reader.readI(); - - if (s3i_signature != 0x49524353) { - logW("S3I signature invalid."); - }; - } - catch (EndOfFileException& e) { - lastError = "premature end of file"; - logE("premature end of file!\n"); - delete ins; - delete[] buf; - return ret; - } + loadS3I(reader,ret,stripPath); break; case DIV_INSFORMAT_SBI: - try { - reader.seek(0, SEEK_SET); - ins->type = DIV_INS_OPL; - - int sbi_header = reader.readI(); - // SBI header determines format - bool is_2op = (sbi_header == 0x1A494253); // SBI\x1A - bool is_4op = (sbi_header == 0x1A504F34); // 4OP\x1A - bool is_6op = (sbi_header == 0x1A504F36); // 6OP\x1A - Freq Monster 801-specific - - // 32-byte null terminated instrument name - ins->name = reader.readString(32); - - // 2op SBI - uint8_t sbi_Mcharacteristics = reader.readC(); - uint8_t sbi_Ccharacteristics = reader.readC(); - uint8_t sbi_Mscaling_output = reader.readC(); - uint8_t sbi_Cscaling_output = reader.readC(); - uint8_t sbi_Meg_AD = reader.readC(); - uint8_t sbi_Ceg_AD = reader.readC(); - uint8_t sbi_Meg_SR = reader.readC(); - uint8_t sbi_Ceg_SR = reader.readC(); - uint8_t sbi_Mwave = reader.readC(); - uint8_t sbi_Cwave = reader.readC(); - uint8_t sbi_FeedConnect = reader.readC(); - - // 4op SBI - uint8_t sbi_M4characteristics; - uint8_t sbi_C4characteristics; - uint8_t sbi_M4scaling_output; - uint8_t sbi_C4scaling_output; - uint8_t sbi_M4eg_AD; - uint8_t sbi_C4eg_AD; - uint8_t sbi_M4eg_SR; - uint8_t sbi_C4eg_SR; - uint8_t sbi_M4wave; - uint8_t sbi_C4wave; - uint8_t sbi_4opConnect; - - if (is_2op) { - DivInstrumentFM::Operator& opM = ins->fm.op[0]; - DivInstrumentFM::Operator& opC = ins->fm.op[1]; - ins->fm.ops = 2; - opM.mult = sbi_Mcharacteristics & 0xF; - opM.ksr = ((sbi_Mcharacteristics >> 4) & 0x1); - opM.sus = ((sbi_Mcharacteristics >> 5) & 0x1); - opM.vib = ((sbi_Mcharacteristics >> 6) & 0x1); - opM.am = ((sbi_Mcharacteristics >> 7) & 0x1); - opM.tl = sbi_Mscaling_output & 0x3F; - opM.ksl = ((sbi_Mscaling_output >> 6) & 0x3); - opM.ar = ((sbi_Meg_AD >> 4) & 0xF); - opM.dr = (sbi_Meg_AD & 0xF); - opM.rr = (sbi_Meg_SR & 0xF); - opM.sl = ((sbi_Meg_SR >> 4) & 0xF); - opM.ws = sbi_Mwave; - - ins->fm.alg = (sbi_FeedConnect & 0x1); - ins->fm.fb = ((sbi_FeedConnect >> 1) & 0x7); - - opC.mult = sbi_Ccharacteristics & 0xF; - opC.ksr = ((sbi_Ccharacteristics >> 4) & 0x1); - opC.sus = ((sbi_Ccharacteristics >> 5) & 0x1); - opC.vib = ((sbi_Ccharacteristics >> 6) & 0x1); - opC.am = ((sbi_Ccharacteristics >> 7) & 0x1); - opC.tl = sbi_Cscaling_output & 0x3F; - opC.ksl = ((sbi_Cscaling_output >> 6) & 0x3); - opC.ar = ((sbi_Ceg_AD >> 4) & 0xF); - opC.dr = (sbi_Ceg_AD & 0xF); - opC.rr = (sbi_Ceg_SR & 0xF); - opC.sl = ((sbi_Ceg_SR >> 4) & 0xF); - opC.ws = sbi_Cwave; - - // Ignore rest of file - rest is 'reserved padding'. - reader.seek(0, SEEK_END); - } - - if (is_4op || is_6op) { - // Operator placement is different so need to place in correct registers. - // Note: 6op is an unofficial extension of 4op SBIs by Darron Broad (Freq Monster 801). - // We'll only use the 4op portion here for pure OPL3. - DivInstrumentFM::Operator& opM = ins->fm.op[0]; - DivInstrumentFM::Operator& opC = ins->fm.op[2]; - DivInstrumentFM::Operator& opM4 = ins->fm.op[1]; - DivInstrumentFM::Operator& opC4 = ins->fm.op[3]; - ins->fm.ops = 4; - - sbi_M4characteristics = reader.readC(); - sbi_C4characteristics = reader.readC(); - sbi_M4scaling_output = reader.readC(); - sbi_C4scaling_output = reader.readC(); - sbi_M4eg_AD = reader.readC(); - sbi_C4eg_AD = reader.readC(); - sbi_M4eg_SR = reader.readC(); - sbi_C4eg_SR = reader.readC(); - sbi_M4wave = reader.readC(); - sbi_C4wave = reader.readC(); - sbi_4opConnect = reader.readC(); - - ins->fm.alg = (sbi_FeedConnect & 0x1) | ((sbi_4opConnect & 0x1) << 1); - ins->fm.fb = ((sbi_FeedConnect >> 1) & 0x7); - - opM.mult = sbi_Mcharacteristics & 0xF; - opM.ksr = ((sbi_Mcharacteristics >> 4) & 0x1); - opM.sus = ((sbi_Mcharacteristics >> 5) & 0x1); - opM.vib = ((sbi_Mcharacteristics >> 6) & 0x1); - opM.am = ((sbi_Mcharacteristics >> 7) & 0x1); - opM.tl = sbi_Mscaling_output & 0x3F; - opM.ksl = ((sbi_Mscaling_output >> 6) & 0x3); - opM.ar = ((sbi_Meg_AD >> 4) & 0xF); - opM.dr = (sbi_Meg_AD & 0xF); - opM.rr = (sbi_Meg_SR & 0xF); - opM.sl = ((sbi_Meg_SR >> 4) & 0xF); - opM.ws = sbi_Mwave; - - opC.mult = sbi_Ccharacteristics & 0xF; - opC.ksr = ((sbi_Ccharacteristics >> 4) & 0x1); - opC.sus = ((sbi_Ccharacteristics >> 5) & 0x1); - opC.vib = ((sbi_Ccharacteristics >> 6) & 0x1); - opC.am = ((sbi_Ccharacteristics >> 7) & 0x1); - opC.tl = sbi_Cscaling_output & 0x3F; - opC.ksl = ((sbi_Cscaling_output >> 6) & 0x3); - opC.ar = ((sbi_Ceg_AD >> 4) & 0xF); - opC.dr = (sbi_Ceg_AD & 0xF); - opC.rr = (sbi_Ceg_SR & 0xF); - opC.sl = ((sbi_Ceg_SR >> 4) & 0xF); - opC.ws = sbi_Cwave; - - opM4.mult = sbi_M4characteristics & 0xF; - opM4.ksr = ((sbi_M4characteristics >> 4) & 0x1); - opM4.sus = ((sbi_M4characteristics >> 5) & 0x1); - opM4.vib = ((sbi_M4characteristics >> 6) & 0x1); - opM4.am = ((sbi_M4characteristics >> 7) & 0x1); - opM4.tl = sbi_M4scaling_output & 0x3F; - opM4.ksl = ((sbi_M4scaling_output >> 6) & 0x3); - opM4.ar = ((sbi_M4eg_AD >> 4) & 0xF); - opM4.dr = (sbi_M4eg_AD & 0xF); - opM4.rr = (sbi_M4eg_SR & 0xF); - opM4.sl = ((sbi_M4eg_SR >> 4) & 0xF); - opM4.ws = sbi_M4wave; - - opC4.mult = sbi_C4characteristics & 0xF; - opC4.ksr = ((sbi_C4characteristics >> 4) & 0x1); - opC4.sus = ((sbi_C4characteristics >> 5) & 0x1); - opC4.vib = ((sbi_C4characteristics >> 6) & 0x1); - opC4.am = ((sbi_C4characteristics >> 7) & 0x1); - opC4.tl = sbi_C4scaling_output & 0x3F; - opC4.ksl = ((sbi_C4scaling_output >> 6) & 0x3); - opC4.ar = ((sbi_C4eg_AD >> 4) & 0xF); - opC4.dr = (sbi_C4eg_AD & 0xF); - opC4.rr = (sbi_C4eg_SR & 0xF); - opC4.sl = ((sbi_C4eg_SR >> 4) & 0xF); - opC4.ws = sbi_C4wave; - - // Ignore rest of file once we've read in all we need. - // Note: Freq Monster 801 adds a ton of other additional fields irrelevant to chip registers. - reader.seek(0, SEEK_END); - } - - } catch (EndOfFileException& e) { - lastError = "premature end of file"; - logE("premature end of file!\n"); - delete ins; - delete[] buf; - return ret; - } + loadSBI(reader,ret,stripPath); break; } @@ -766,6 +805,5 @@ std::vector DivEngine::instrumentFromFile(const char* path) { } } - ret.push_back(ins); return ret; -} \ No newline at end of file +} From 1a7074d0e5f65fb56d714701cd08d0821cc68f26 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 3 Apr 2022 13:54:02 -0500 Subject: [PATCH 502/637] Furnace system IDs are no longer compatible --- papers/format.md | 110 +++++++++++++++++++++++------------------------ 1 file changed, 54 insertions(+), 56 deletions(-) diff --git a/papers/format.md b/papers/format.md index f2ea89acc..cc4a5add7 100644 --- a/papers/format.md +++ b/papers/format.md @@ -133,62 +133,60 @@ size | description | - 0x07: C64 (8580) - 3 channels | - 0x08: Arcade (YM2151+SegaPCM) - 13 channels (compound!) | - 0x09: Neo Geo CD (YM2610) - 13 channels - | - bit 6 enables alternate mode: - | - 0x42: Genesis extended - 13 channels - | - 0x43: SMS (SN76489) + OPLL (YM2413) - 13 channels (compound!) - | - 0x46: NES + VRC7 - 11 channels (compound!) - | - 0x47: C64 (6581) - 3 channels - | - 0x49: Neo Geo CD extended - 16 channels - | - bit 7 for non-DefleMask chips: - | - 0x80: AY-3-8910 - 3 channels - | - 0x81: Amiga - 4 channels - | - 0x82: YM2151 alone - 8 channels - | - 0x83: YM2612 alone - 6 channels - | - 0x84: TIA - 2 channels - | - 0x85: VIC-20 - 4 channels - | - 0x86: PET - 1 channel - | - 0x87: SNES - 8 channels - | - 0x88: VRC6 - 3 channels - | - 0x89: OPLL (YM2413) - 9 channels - | - 0x8a: FDS - 1 channel - | - 0x8b: MMC5 - 3 channels - | - 0x8c: Namco 163 - 8 channels - | - 0x8d: OPN (YM2203) - 6 channels - | - 0x8e: PC-98 (YM2608) - 16 channels - | - 0x8f: OPL (YM3526) - 9 channels - | - 0x90: OPL2 (YM3812) - 9 channels - | - 0x91: OPL3 (YMF262) - 18 channels - | - 0x92: MultiPCM - 24 channels - | - 0x93: Intel 8253 (beeper) - 1 channel - | - 0x94: POKEY - 4 channels - | - 0x95: RF5C68 - 8 channels - | - 0x96: WonderSwan - 4 channels - | - 0x97: Philips SAA1099 - 6 channels - | - 0x98: OPZ (YM2414) - 8 channels - | - 0x99: Pokémon Mini - 1 channel - | - 0x9a: AY8930 - 3 channels - | - 0x9b: SegaPCM - 16 channels - | - 0x9c: Virtual Boy - 6 channels - | - 0x9d: VRC7 - 6 channels - | - 0x9e: YM2610B - 16 channels - | - 0x9f: ZX Spectrum (beeper) - 6 channels - | - 0xa0: YM2612 extended - 9 channels - | - 0xa1: Konami SCC - 5 channels - | - 0xa2: OPL drums (YM3526) - 11 channels - | - 0xa3: OPL2 drums (YM3812) - 11 channels - | - 0xa4: OPL3 drums (YMF262) - 20 channels - | - 0xa5: Neo Geo (YM2610) - 14 channels - | - 0xa6: Neo Geo extended (YM2610) - 17 channels - | - 0xa7: OPLL drums (YM2413) - 11 channels - | - 0xa8: Atari Lynx - 4 channels - | - 0xa9: SegaPCM (for Deflemask Compatibility) - 5 channels - | - 0xaa: MSM6295 - 4 channels - | - 0xab: MSM6258 - 1 channel - | - 0xac: Commander X16 (VERA) - 17 channels - | - 0xad: Bubble System WSG - 2 channels - | - 0xb0: Seta/Allumer X1-010 - 16 channels - | - 0xde: YM2610B extended - 19 channels - | - 0xe0: QSound - 19 channels + | - 0x42: Genesis extended - 13 channels + | - 0x43: SMS (SN76489) + OPLL (YM2413) - 13 channels (compound!) + | - 0x46: NES + VRC7 - 11 channels (compound!) + | - 0x47: C64 (6581) - 3 channels + | - 0x49: Neo Geo CD extended - 16 channels + | - 0x80: AY-3-8910 - 3 channels + | - 0x81: Amiga - 4 channels + | - 0x82: YM2151 alone - 8 channels + | - 0x83: YM2612 alone - 6 channels + | - 0x84: TIA - 2 channels + | - 0x85: VIC-20 - 4 channels + | - 0x86: PET - 1 channel + | - 0x87: SNES - 8 channels + | - 0x88: VRC6 - 3 channels + | - 0x89: OPLL (YM2413) - 9 channels + | - 0x8a: FDS - 1 channel + | - 0x8b: MMC5 - 3 channels + | - 0x8c: Namco 163 - 8 channels + | - 0x8d: OPN (YM2203) - 6 channels + | - 0x8e: PC-98 (YM2608) - 16 channels + | - 0x8f: OPL (YM3526) - 9 channels + | - 0x90: OPL2 (YM3812) - 9 channels + | - 0x91: OPL3 (YMF262) - 18 channels + | - 0x92: MultiPCM - 24 channels + | - 0x93: Intel 8253 (beeper) - 1 channel + | - 0x94: POKEY - 4 channels + | - 0x95: RF5C68 - 8 channels + | - 0x96: WonderSwan - 4 channels + | - 0x97: Philips SAA1099 - 6 channels + | - 0x98: OPZ (YM2414) - 8 channels + | - 0x99: Pokémon Mini - 1 channel + | - 0x9a: AY8930 - 3 channels + | - 0x9b: SegaPCM - 16 channels + | - 0x9c: Virtual Boy - 6 channels + | - 0x9d: VRC7 - 6 channels + | - 0x9e: YM2610B - 16 channels + | - 0x9f: ZX Spectrum (beeper) - 6 channels + | - 0xa0: YM2612 extended - 9 channels + | - 0xa1: Konami SCC - 5 channels + | - 0xa2: OPL drums (YM3526) - 11 channels + | - 0xa3: OPL2 drums (YM3812) - 11 channels + | - 0xa4: OPL3 drums (YMF262) - 20 channels + | - 0xa5: Neo Geo (YM2610) - 14 channels + | - 0xa6: Neo Geo extended (YM2610) - 17 channels + | - 0xa7: OPLL drums (YM2413) - 11 channels + | - 0xa8: Atari Lynx - 4 channels + | - 0xa9: SegaPCM (for Deflemask Compatibility) - 5 channels + | - 0xaa: MSM6295 - 4 channels + | - 0xab: MSM6258 - 1 channel + | - 0xac: Commander X16 (VERA) - 17 channels + | - 0xad: Bubble System WSG - 2 channels + | - 0xb0: Seta/Allumer X1-010 - 16 channels + | - 0xde: YM2610B extended - 19 channels + | - 0xe0: QSound - 19 channels | - (compound!) means that the system is composed of two or more chips, | and has to be flattened. 32 | sound chip volumes From d63ddda40225ddd52891b462abc6de167ed028fc Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 3 Apr 2022 16:02:25 -0500 Subject: [PATCH 503/637] FDS preparation work DOES NOT COMPILE --- src/engine/engine.h | 6 ++++-- src/engine/fileOps.cpp | 20 ++++++++++---------- src/engine/fileOpsIns.cpp | 1 - src/engine/sysDef.cpp | 5 ++--- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/engine/engine.h b/src/engine/engine.h index e2db8e094..f4a3db832 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -254,8 +254,10 @@ class DivEngine { // MIDI stuff std::function midiCallback=[](const TAMidiMessage&) -> int {return -2;}; - DivSystem systemFromFile(unsigned char val); - unsigned char systemToFile(DivSystem val); + DivSystem systemFromFileFur(unsigned char val); + unsigned char systemToFileFur(DivSystem val); + DivSystem systemFromFileDMF(unsigned char val); + unsigned char systemToFileDMF(DivSystem val); int dispatchCmd(DivCommand c); void processRow(int i, bool afterDelay); void nextOrder(); diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 0e82b6b79..86c971947 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -83,7 +83,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { ds.system[0]=DIV_SYSTEM_YMU759; } else { sys=reader.readC(); - ds.system[0]=systemFromFile(sys); + ds.system[0]=systemFromFileDMF(sys); } if (ds.system[0]==DIV_SYSTEM_NULL) { logE("invalid system 0x%.2x!",sys); @@ -876,7 +876,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { int numberOfPats=reader.readI(); for (int i=0; i<32; i++) { - ds.system[i]=systemFromFile(reader.readC()); + ds.system[i]=systemFromFileFur(reader.readC()); if (ds.system[i]!=DIV_SYSTEM_NULL) ds.systemLen=i+1; } int tchans=0; @@ -1855,7 +1855,7 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { if (i>=song.systemLen) { w->writeC(0); } else { - w->writeC(systemToFile(song.system[i])); + w->writeC(systemToFileFur(song.system[i])); } } @@ -2095,7 +2095,7 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { return NULL; } // fail if the system is Furnace-exclusive - if (!isFlat && systemToFile(song.system[0])&0x80) { + if (!isFlat && systemToFileDMF(song.system[0])==0) { logE("cannot save Furnace-exclusive system song!\n"); lastError="this system is not possible on .dmf"; return NULL; @@ -2113,22 +2113,22 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { w->writeC(version); DivSystem sys=DIV_SYSTEM_NULL; if (song.system[0]==DIV_SYSTEM_YM2612 && song.system[1]==DIV_SYSTEM_SMS) { - w->writeC(systemToFile(DIV_SYSTEM_GENESIS)); + w->writeC(systemToFileDMF(DIV_SYSTEM_GENESIS)); sys=DIV_SYSTEM_GENESIS; } else if (song.system[0]==DIV_SYSTEM_YM2612_EXT && song.system[1]==DIV_SYSTEM_SMS) { - w->writeC(systemToFile(DIV_SYSTEM_GENESIS_EXT)); + w->writeC(systemToFileDMF(DIV_SYSTEM_GENESIS_EXT)); sys=DIV_SYSTEM_GENESIS_EXT; } else if (song.system[0]==DIV_SYSTEM_YM2151 && song.system[1]==DIV_SYSTEM_SEGAPCM_COMPAT) { - w->writeC(systemToFile(DIV_SYSTEM_ARCADE)); + w->writeC(systemToFileDMF(DIV_SYSTEM_ARCADE)); sys=DIV_SYSTEM_ARCADE; } else if (song.system[0]==DIV_SYSTEM_SMS && song.system[1]==DIV_SYSTEM_OPLL) { - w->writeC(systemToFile(DIV_SYSTEM_SMS_OPLL)); + w->writeC(systemToFileDMF(DIV_SYSTEM_SMS_OPLL)); sys=DIV_SYSTEM_SMS_OPLL; } else if (song.system[0]==DIV_SYSTEM_NES && song.system[1]==DIV_SYSTEM_VRC7) { - w->writeC(systemToFile(DIV_SYSTEM_NES_VRC7)); + w->writeC(systemToFileDMF(DIV_SYSTEM_NES_VRC7)); sys=DIV_SYSTEM_NES_VRC7; } else { - w->writeC(systemToFile(song.system[0])); + w->writeC(systemToFileDMF(song.system[0])); sys=song.system[0]; } diff --git a/src/engine/fileOpsIns.cpp b/src/engine/fileOpsIns.cpp index 201ace6ae..a92e52c6b 100644 --- a/src/engine/fileOpsIns.cpp +++ b/src/engine/fileOpsIns.cpp @@ -641,7 +641,6 @@ void DivEngine::loadOPM(SafeReader& reader, std::vector& ret, St } catch (EndOfFileException& e) { lastError="premature end of file"; logE("premature end of file!\n"); - delete ins; return; } } diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 58a932605..2b935b1aa 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -20,7 +20,7 @@ #include "engine.h" #include "song.h" -DivSystem DivEngine::systemFromFile(unsigned char val) { +DivSystem DivEngine::systemFromFileFur(unsigned char val) { switch (val) { case 0x01: return DIV_SYSTEM_YMU759; @@ -50,7 +50,6 @@ DivSystem DivEngine::systemFromFile(unsigned char val) { return DIV_SYSTEM_C64_6581; case 0x49: return DIV_SYSTEM_YM2610_EXT; - // Furnace-specific systems case 0x80: return DIV_SYSTEM_AY8910; case 0x81: @@ -149,7 +148,7 @@ DivSystem DivEngine::systemFromFile(unsigned char val) { return DIV_SYSTEM_NULL; } -unsigned char DivEngine::systemToFile(DivSystem val) { +unsigned char DivEngine::systemToFileFur(DivSystem val) { switch (val) { case DIV_SYSTEM_YMU759: return 0x01; From 3a59e260d8eff4c65493a4377c6f3c44ab3afd00 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 3 Apr 2022 17:14:12 -0500 Subject: [PATCH 504/637] prepare for handling of FDS .dmf --- src/engine/fileOps.cpp | 21 +++++++--- src/engine/song.h | 1 + src/engine/sysDef.cpp | 93 +++++++++++++++++++++++++++++++++++++++--- 3 files changed, 105 insertions(+), 10 deletions(-) diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 86c971947..619591209 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -530,7 +530,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { if (ds.system[0]==DIV_SYSTEM_GB) { wave->max=15; } - if (wave->len>33) { + if (wave->len>65) { logE("invalid wave length %d. are we doing something wrong?\n",wave->len); lastError="file is corrupt or unreadable at wavetables"; delete[] file; @@ -736,6 +736,11 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { ds.system[0]=DIV_SYSTEM_NES; ds.system[1]=DIV_SYSTEM_VRC7; } + if (ds.system[0]==DIV_SYSTEM_NES_FDS) { + ds.systemLen=2; + ds.system[0]=DIV_SYSTEM_NES; + ds.system[1]=DIV_SYSTEM_FDS; + } if (active) quitDispatch(); BUSY_BEGIN_SOFT; @@ -2055,19 +2060,22 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { bool isFlat=false; if (song.systemLen==2) { if (song.system[0]==DIV_SYSTEM_YM2612 && song.system[1]==DIV_SYSTEM_SMS) { - isFlat=true; + isFlat=true; } if (song.system[0]==DIV_SYSTEM_YM2612_EXT && song.system[1]==DIV_SYSTEM_SMS) { - isFlat=true; + isFlat=true; } if (song.system[0]==DIV_SYSTEM_YM2151 && song.system[1]==DIV_SYSTEM_SEGAPCM_COMPAT) { isFlat=true; } if (song.system[0]==DIV_SYSTEM_SMS && song.system[1]==DIV_SYSTEM_OPLL) { - isFlat=true; + isFlat=true; } if (song.system[0]==DIV_SYSTEM_NES && song.system[1]==DIV_SYSTEM_VRC7) { - isFlat=true; + isFlat=true; + } + if (song.system[0]==DIV_SYSTEM_NES && song.system[1]==DIV_SYSTEM_FDS) { + isFlat=true; } } // fail if more than one system @@ -2127,6 +2135,9 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { } else if (song.system[0]==DIV_SYSTEM_NES && song.system[1]==DIV_SYSTEM_VRC7) { w->writeC(systemToFileDMF(DIV_SYSTEM_NES_VRC7)); sys=DIV_SYSTEM_NES_VRC7; + } else if (song.system[0]==DIV_SYSTEM_NES && song.system[1]==DIV_SYSTEM_FDS) { + w->writeC(systemToFileDMF(DIV_SYSTEM_NES_FDS)); + sys=DIV_SYSTEM_NES_VRC7; } else { w->writeC(systemToFileDMF(song.system[0])); sys=song.system[0]; diff --git a/src/engine/song.h b/src/engine/song.h index 9142636e3..1b8c85c21 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -42,6 +42,7 @@ enum DivSystem { DIV_SYSTEM_PCE, DIV_SYSTEM_NES, DIV_SYSTEM_NES_VRC7, // ** COMPOUND SYSTEM - DO NOT USE! ** + DIV_SYSTEM_NES_FDS, // ** COMPOUND SYSTEM - DO NOT USE! ** DIV_SYSTEM_C64_6581, DIV_SYSTEM_C64_8580, DIV_SYSTEM_ARCADE, // ** COMPOUND SYSTEM - DO NOT USE! ** diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 2b935b1aa..b3f9e6ab5 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -174,11 +174,12 @@ unsigned char DivEngine::systemToFileFur(DivSystem val) { return 0x43; case DIV_SYSTEM_NES_VRC7: return 0x46; + case DIV_SYSTEM_NES_FDS: + return 0; // unsupported case DIV_SYSTEM_C64_6581: return 0x47; case DIV_SYSTEM_YM2610_EXT: return 0x49; - // Furnace-specific systems case DIV_SYSTEM_AY8910: return 0x80; case DIV_SYSTEM_AMIGA: @@ -280,6 +281,80 @@ unsigned char DivEngine::systemToFileFur(DivSystem val) { return 0; } +DivSystem DivEngine::systemFromFileDMF(unsigned char val) { + switch (val) { + case 0x01: + return DIV_SYSTEM_YMU759; + case 0x02: + return DIV_SYSTEM_GENESIS; + case 0x03: + return DIV_SYSTEM_SMS; + case 0x04: + return DIV_SYSTEM_GB; + case 0x05: + return DIV_SYSTEM_PCE; + case 0x06: + return DIV_SYSTEM_NES; + case 0x07: + return DIV_SYSTEM_C64_8580; + case 0x08: + return DIV_SYSTEM_ARCADE; + case 0x09: + return DIV_SYSTEM_YM2610; + case 0x42: + return DIV_SYSTEM_GENESIS_EXT; + case 0x43: + return DIV_SYSTEM_SMS_OPLL; + case 0x46: + return DIV_SYSTEM_NES_VRC7; + case 0x47: + return DIV_SYSTEM_C64_6581; + case 0x49: + return DIV_SYSTEM_YM2610_EXT; + case 0x86: + return DIV_SYSTEM_NES_FDS; + } + return DIV_SYSTEM_NULL; +} + +unsigned char DivEngine::systemToFileDMF(DivSystem val) { + switch (val) { + case DIV_SYSTEM_YMU759: + return 0x01; + case DIV_SYSTEM_GENESIS: + return 0x02; + case DIV_SYSTEM_SMS: + return 0x03; + case DIV_SYSTEM_GB: + return 0x04; + case DIV_SYSTEM_PCE: + return 0x05; + case DIV_SYSTEM_NES: + return 0x06; + case DIV_SYSTEM_C64_8580: + return 0x07; + case DIV_SYSTEM_ARCADE: + return 0x08; + case DIV_SYSTEM_YM2610: + return 0x09; + case DIV_SYSTEM_GENESIS_EXT: + return 0x42; + case DIV_SYSTEM_SMS_OPLL: + return 0x43; + case DIV_SYSTEM_NES_VRC7: + return 0x46; + case DIV_SYSTEM_NES_FDS: + return 0x86; + case DIV_SYSTEM_C64_6581: + return 0x47; + case DIV_SYSTEM_YM2610_EXT: + return 0x49; + default: + return 0; + } + return 0; +} + int DivEngine::getChannelCount(DivSystem sys) { switch (sys) { case DIV_SYSTEM_NULL: @@ -305,9 +380,10 @@ int DivEngine::getChannelCount(DivSystem sys) { return 13; case DIV_SYSTEM_NES_VRC7: return 11; + case DIV_SYSTEM_NES_FDS: + return 6; case DIV_SYSTEM_YM2610_EXT: return 16; - // Furnace-specific systems case DIV_SYSTEM_AY8910: case DIV_SYSTEM_AY8930: return 3; @@ -576,6 +652,8 @@ const char* DivEngine::getSystemName(DivSystem sys) { return "NES"; case DIV_SYSTEM_NES_VRC7: return "NES + Konami VRC7"; + case DIV_SYSTEM_NES_FDS: + return "Famicom Disk System"; case DIV_SYSTEM_C64_6581: return "Commodore 64 with 6581"; case DIV_SYSTEM_C64_8580: @@ -588,7 +666,6 @@ const char* DivEngine::getSystemName(DivSystem sys) { return "Neo Geo CD"; case DIV_SYSTEM_YM2610_EXT: return "Neo Geo CD Extended Channel 2"; - // Furnace-specific systems case DIV_SYSTEM_YM2610_FULL: return "Neo Geo"; case DIV_SYSTEM_YM2610_FULL_EXT: @@ -707,6 +784,8 @@ const char* DivEngine::getSystemChips(DivSystem sys) { return "Ricoh 2A03"; case DIV_SYSTEM_NES_VRC7: return "Ricoh 2A03 + Konami VRC7"; + case DIV_SYSTEM_NES_FDS: + return "Ricoh 2A03 + Famicom Disk System"; case DIV_SYSTEM_C64_6581: return "SID 6581"; case DIV_SYSTEM_C64_8580: @@ -719,7 +798,6 @@ const char* DivEngine::getSystemChips(DivSystem sys) { return "Yamaha YM2610 no ADPCM-B"; case DIV_SYSTEM_YM2610_EXT: return "Yamaha YM2610 no ADPCM-B (extended channel 2)"; - // Furnace-specific systems case DIV_SYSTEM_AY8910: return "AY-3-8910"; case DIV_SYSTEM_AMIGA: @@ -851,7 +929,6 @@ const char* DivEngine::getSystemNameJ(DivSystem sys) { return "業務用ネオジオ"; case DIV_SYSTEM_YM2610_FULL_EXT: return "業務用ネオジオ"; - // Furnace-specific systems case DIV_SYSTEM_AY8910: return ""; case DIV_SYSTEM_AMIGA: @@ -1093,6 +1170,7 @@ const char* DivEngine::getChannelName(int chan) { break; case DIV_SYSTEM_SMS_OPLL: // this is flattened to SMS + OPLL. case DIV_SYSTEM_NES_VRC7: // this is flattened to NES + VRC7. + case DIV_SYSTEM_NES_FDS: // this is flattened to NES + FDS. return "??"; break; case DIV_SYSTEM_GB: @@ -1239,6 +1317,7 @@ const char* DivEngine::getChannelShortName(int chan) { break; case DIV_SYSTEM_SMS_OPLL: // this is flattened to SMS + OPLL. case DIV_SYSTEM_NES_VRC7: // this is flattened to NES + VRC7. + case DIV_SYSTEM_NES_FDS: // this is flattened to NES + FDS. return "??"; break; case DIV_SYSTEM_GB: @@ -1381,6 +1460,7 @@ int DivEngine::getChannelType(int chan) { break; case DIV_SYSTEM_SMS_OPLL: // this is flattened to SMS + OPLL. case DIV_SYSTEM_NES_VRC7: // this is flattened to NES + VRC7. + case DIV_SYSTEM_NES_FDS: // this is flattened to NES + FDS. return 0; break; case DIV_SYSTEM_GB: @@ -1529,6 +1609,9 @@ DivInstrumentType DivEngine::getPreferInsType(int chan) { case DIV_SYSTEM_NES_VRC7: // this is flattened to NES + VRC7. return DIV_INS_OPLL; break; + case DIV_SYSTEM_NES_FDS: // this is flattened to NES + FDS. + return DIV_INS_STD; + break; case DIV_SYSTEM_GB: return chanPrefType[4][dispatchChanOfChan[chan]]; break; From d112cd0c68f26ed21657d063c540cfc6b86d1042 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 3 Apr 2022 17:22:44 -0500 Subject: [PATCH 505/637] what? --- src/engine/fileOps.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 619591209..4e758c2de 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -259,7 +259,9 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { // instruments in ancient versions were all FM or STD. ins->mode=1; } else { - ins->mode=reader.readC(); + unsigned char mode=reader.readC(); + if (mode>1) logW("%d: invalid instrument mode %d!\n",i,mode); + ins->mode=mode; } ins->type=ins->mode?DIV_INS_FM:DIV_INS_STD; if (ds.system[0]==DIV_SYSTEM_GB) { From 90bbc4229034e40bc3782fabe173788dbd3e4e77 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 3 Apr 2022 19:20:30 -0500 Subject: [PATCH 506/637] GUI: more usability improvements - move cursor to end of clipboard when pasting - better effect input --- src/gui/editing.cpp | 3 +++ src/gui/gui.cpp | 13 ++++++++++++- src/gui/gui.h | 4 ++++ src/gui/settings.cpp | 16 ++++++++++++++++ 4 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/gui/editing.cpp b/src/gui/editing.cpp index 59c1fc342..58069528a 100644 --- a/src/gui/editing.cpp +++ b/src/gui/editing.cpp @@ -556,6 +556,9 @@ void FurnaceGUI::doPaste(PasteMode mode) { i=1; } } + if (settings.cursorPastePos) { + cursor.y=j; + } makeUndo(GUI_UNDO_PATTERN_PASTE); } diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 2d7a42225..f30c820fe 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -976,7 +976,18 @@ void FurnaceGUI::valueInput(int num, bool direct, int target) { curNibble=false; } else { curNibble=!curNibble; - if (!curNibble) editAdvance(); + if (!curNibble) { + if (!settings.effectCursorDir) { + editAdvance(); + } else { + if (cursor.xFine&1) { + cursor.xFine++; + } else { + editAdvance(); + cursor.xFine--; + } + } + } } } } diff --git a/src/gui/gui.h b/src/gui/gui.h index 80397db81..abefee8bf 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -731,6 +731,8 @@ class FurnaceGUI { int loadJapanese; int fmLayout; int susPosition; + int effectCursorDir; + int cursorPastePos; unsigned int maxUndoSteps; String mainFontPath; String patFontPath; @@ -785,6 +787,8 @@ class FurnaceGUI { loadJapanese(0), fmLayout(0), susPosition(0), + effectCursorDir(1), + cursorPastePos(1), maxUndoSteps(100), mainFontPath(""), patFontPath(""), diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index ac77896f2..9768792a2 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -237,6 +237,16 @@ void FurnaceGUI::drawSettings() { settings.stepOnInsert=stepOnInsertB; } + bool effectCursorDirB=settings.effectCursorDir; + if (ImGui::Checkbox("Move cursor to effect value on effect input",&effectCursorDirB)) { + settings.effectCursorDir=effectCursorDirB; + } + + bool cursorPastePosB=settings.cursorPastePos; + if (ImGui::Checkbox("Move cursor to end of clipboard content when pasting",&cursorPastePosB)) { + settings.cursorPastePos=cursorPastePosB; + } + bool allowEditDockingB=settings.allowEditDocking; if (ImGui::Checkbox("Allow docking editors",&allowEditDockingB)) { settings.allowEditDocking=allowEditDockingB; @@ -1357,6 +1367,8 @@ void FurnaceGUI::syncSettings() { settings.loadJapanese=e->getConfInt("loadJapanese",0); settings.fmLayout=e->getConfInt("fmLayout",0); settings.susPosition=e->getConfInt("susPosition",0); + settings.effectCursorDir=e->getConfInt("effectCursorDir",1); + settings.cursorPastePos=e->getConfInt("cursorPastePos",1); clampSetting(settings.mainFontSize,2,96); clampSetting(settings.patFontSize,2,96); @@ -1403,6 +1415,8 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.loadJapanese,0,1); clampSetting(settings.fmLayout,0,3); clampSetting(settings.susPosition,0,1); + clampSetting(settings.effectCursorDir,0,1); + clampSetting(settings.cursorPastePos,0,1); // keybinds LOAD_KEYBIND(GUI_ACTION_OPEN,FURKMOD_CMD|SDLK_o); @@ -1646,6 +1660,8 @@ void FurnaceGUI::commitSettings() { e->setConf("loadJapanese",settings.loadJapanese); e->setConf("fmLayout",settings.fmLayout); e->setConf("susPosition",settings.susPosition); + e->setConf("effectCursorDir",settings.effectCursorDir); + e->setConf("cursorPastePos",settings.cursorPastePos); PUT_UI_COLOR(GUI_COLOR_BACKGROUND); PUT_UI_COLOR(GUI_COLOR_FRAME_BACKGROUND); From 9ef50bbda76258f05bcf632f367bd21eedd77721 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 3 Apr 2022 19:23:02 -0500 Subject: [PATCH 507/637] GUI: fix keybinds leaking after setting them to no --- src/gui/settings.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 9768792a2..78fc8eb06 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -1934,6 +1934,8 @@ void FurnaceGUI::commitSettings() { SAVE_KEYBIND(GUI_ACTION_ORDERS_MOVE_DOWN); SAVE_KEYBIND(GUI_ACTION_ORDERS_REPLAY); + parseKeybinds(); + e->setConf("noteKeys",encodeKeyMap(noteKeys)); midiMap.compile(); From ac3772c024b5401b7e61fc3d01ab58b6efa62249 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 3 Apr 2022 22:37:16 -0500 Subject: [PATCH 508/637] initial FDS bring-up --- CMakeLists.txt | 2 + src/engine/dispatch.h | 6 + src/engine/dispatchContainer.cpp | 4 + src/engine/fileOps.cpp | 2 +- src/engine/instrument.h | 10 + src/engine/platform/fds.cpp | 383 +++++++++++++++++++++ src/engine/platform/fds.h | 92 +++++ src/engine/platform/sound/nes/cpu_inline.h | 80 +++++ src/engine/platform/sound/nes/fds.c | 138 ++++++++ src/engine/platform/sound/nes/fds.h | 94 +++++ src/engine/playback.cpp | 30 ++ src/engine/sysDef.cpp | 1 + src/engine/vgmOps.cpp | 26 ++ src/gui/insEdit.cpp | 3 + 14 files changed, 870 insertions(+), 1 deletion(-) create mode 100644 src/engine/platform/fds.cpp create mode 100644 src/engine/platform/fds.h create mode 100644 src/engine/platform/sound/nes/fds.c create mode 100644 src/engine/platform/sound/nes/fds.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 1a0909633..353ca9e97 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -236,6 +236,7 @@ src/engine/platform/sound/gb/apu.c src/engine/platform/sound/gb/timing.c src/engine/platform/sound/pce_psg.cpp src/engine/platform/sound/nes/apu.c +src/engine/platform/sound/nes/fds.c src/engine/platform/sound/vera_psg.c src/engine/platform/sound/vera_pcm.c @@ -318,6 +319,7 @@ src/engine/platform/ym2610bext.cpp src/engine/platform/ay.cpp src/engine/platform/ay8930.cpp src/engine/platform/opl.cpp +src/engine/platform/fds.cpp src/engine/platform/tia.cpp src/engine/platform/saa.cpp src/engine/platform/amiga.cpp diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index 02c9da241..dd7cbc268 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -104,6 +104,12 @@ enum DivDispatchCmds { DIV_CMD_AY_IO_WRITE, DIV_CMD_AY_AUTO_PWM, + DIV_CMD_FDS_MOD_DEPTH, + DIV_CMD_FDS_MOD_HIGH, + DIV_CMD_FDS_MOD_LOW, + DIV_CMD_FDS_MOD_POS, + DIV_CMD_FDS_MOD_WAVE, + DIV_CMD_SAA_ENVELOPE, DIV_CMD_AMIGA_FILTER, diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 498630cc8..2658c426c 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -50,6 +50,7 @@ #include "platform/pet.h" #include "platform/vic20.h" #include "platform/vrc6.h" +#include "platform/fds.h" #include "platform/dummy.h" #include "../ta-log.h" #include "song.h" @@ -213,6 +214,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_AY8930: dispatch=new DivPlatformAY8930; break; + case DIV_SYSTEM_FDS: + dispatch=new DivPlatformFDS; + break; case DIV_SYSTEM_TIA: dispatch=new DivPlatformTIA; break; diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 4e758c2de..976b2c32f 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -529,7 +529,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { for (int i=0; ilen=(unsigned char)reader.readI(); - if (ds.system[0]==DIV_SYSTEM_GB) { + if (ds.system[0]==DIV_SYSTEM_GB || ds.system[0]==DIV_SYSTEM_NES_FDS) { wave->max=15; } if (wave->len>65) { diff --git a/src/engine/instrument.h b/src/engine/instrument.h index 22b3bb320..e8ec9d318 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -397,6 +397,16 @@ struct DivInstrumentN163 { waveMode(3) {} }; +struct DivInstrumentFDS { + signed char modTable[32]; + unsigned char modSpeed, modLevel; + DivInstrumentFDS(): + modSpeed(0), + modLevel(0) { + memset(modTable,0,32); + } +}; + struct DivInstrument { String name; bool mode; diff --git a/src/engine/platform/fds.cpp b/src/engine/platform/fds.cpp new file mode 100644 index 000000000..1ec264d10 --- /dev/null +++ b/src/engine/platform/fds.cpp @@ -0,0 +1,383 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "fds.h" +#include "sound/nes/cpu_inline.h" +#include "../engine.h" +#include + +#define CHIP_FREQBASE 262144 + +#define rWrite(a,v) if (!skipRegisterWrites) {fds_wr_mem(fds,a,v); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} } + +const char** DivPlatformFDS::getRegisterSheet() { + return NULL; +} + +const char* DivPlatformFDS::getEffectName(unsigned char effect) { + switch (effect) { + case 0x12: + return "12xx: Set duty cycle/noise mode (pulse: 0 to 3; noise: 0 or 1)"; + break; + case 0x13: + return "13xy: Sweep up (x: time; y: shift)"; + break; + case 0x14: + return "14xy: Sweep down (x: time; y: shift)"; + break; + } + return NULL; +} + +void DivPlatformFDS::acquire(short* bufL, short* bufR, size_t start, size_t len) { + for (size_t i=start; isnd.main.output; + if (sample>32767) sample=32767; + if (sample<-32768) sample=-32768; + bufL[i]=sample; + } +} + +void DivPlatformFDS::updateWave() { + DivWavetable* wt=parent->getWave(chan[0].wave); + // TODO: master volume + rWrite(0x4089,0x80); + for (int i=0; i<64; i++) { + if (wt->max<1 || wt->len<1) { + rWrite(0x4040+i,0); + } else { + int data=wt->data[i*wt->len/64]*63/wt->max; + if (data<0) data=0; + if (data>63) data=63; + rWrite(0x4040+i,data); + } + } + rWrite(0x4089,0); +} + +void DivPlatformFDS::tick() { + for (int i=0; i<1; i++) { + chan[i].std.next(); + if (chan[i].std.hadVol) { + // ok, why are the volumes like that? + chan[i].outVol=MIN(32,chan[i].std.vol)-(32-MIN(32,chan[i].vol)); + if (chan[i].outVol<0) chan[i].outVol=0; + rWrite(0x4080,0x80|chan[i].outVol); + } + if (chan[i].std.hadArp) { + if (i==3) { // noise + if (chan[i].std.arpMode) { + chan[i].baseFreq=chan[i].std.arp; + } else { + chan[i].baseFreq=chan[i].note+chan[i].std.arp; + } + if (chan[i].baseFreq>255) chan[i].baseFreq=255; + if (chan[i].baseFreq<0) chan[i].baseFreq=0; + } else { + if (!chan[i].inPorta) { + if (chan[i].std.arpMode) { + chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp); + } else { + chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+chan[i].std.arp); + } + } + } + chan[i].freqChanged=true; + } else { + if (chan[i].std.arpMode && chan[i].std.finishedArp) { + chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note); + chan[i].freqChanged=true; + } + } + /* + if (chan[i].std.hadDuty) { + chan[i].duty=chan[i].std.duty; + if (i==3) { + if (parent->song.properNoiseLayout) { + chan[i].duty&=1; + } else if (chan[i].duty>1) { + chan[i].duty=1; + } + } + if (i!=2) { + rWrite(0x4000+i*4,0x30|chan[i].outVol|((chan[i].duty&3)<<6)); + } + if (i==3) { // noise + chan[i].freqChanged=true; + } + }*/ + if (chan[i].std.hadWave) { + if (chan[i].wave!=chan[i].std.wave) { + chan[i].wave=chan[i].std.wave; + updateWave(); + //if (!chan[i].keyOff) chan[i].keyOn=true; + } + } + if (chan[i].sweepChanged) { + chan[i].sweepChanged=false; + if (i==0) { + //rWrite(16+i*5,chan[i].sweep); + } + } + if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false); + if (chan[i].freq>4095) chan[i].freq=4095; + if (chan[i].freq<0) chan[i].freq=0; + if (chan[i].keyOn) { + if (chan[i].wave<0) { + chan[i].wave=0; + updateWave(); + } + } + if (chan[i].keyOff) { + rWrite(0x4080,0x80); + } + rWrite(0x4082,chan[i].freq&0xff); + rWrite(0x4083,(chan[i].freq>>8)&15); + if (chan[i].keyOn) chan[i].keyOn=false; + if (chan[i].keyOff) chan[i].keyOff=false; + chan[i].freqChanged=false; + } + } +} + +int DivPlatformFDS::dispatch(DivCommand c) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + } + chan[c.chan].active=true; + chan[c.chan].keyOn=true; + chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + rWrite(0x4080,0x80|chan[c.chan].vol); + break; + case DIV_CMD_NOTE_OFF: + chan[c.chan].active=false; + chan[c.chan].keyOff=true; + chan[c.chan].std.init(NULL); + break; + case DIV_CMD_NOTE_OFF_ENV: + case DIV_CMD_ENV_RELEASE: + chan[c.chan].std.release(); + break; + case DIV_CMD_INSTRUMENT: + if (chan[c.chan].ins!=c.value || c.value2==1) { + chan[c.chan].ins=c.value; + } + break; + case DIV_CMD_VOLUME: + if (chan[c.chan].vol!=c.value) { + chan[c.chan].vol=c.value; + if (!chan[c.chan].std.hasVol) { + chan[c.chan].outVol=c.value; + } + rWrite(0x4080,0x80|chan[c.chan].vol); + } + break; + case DIV_CMD_GET_VOLUME: + return chan[c.chan].vol; + break; + case DIV_CMD_PITCH: + chan[c.chan].pitch=c.value; + chan[c.chan].freqChanged=true; + break; + case DIV_CMD_WAVE: + if (chan[c.chan].wave!=c.value) { + chan[c.chan].wave=c.value; + updateWave(); + } + break; + case DIV_CMD_FDS_MOD_DEPTH: + chan[c.chan].modDepth=c.value; + rWrite(0x4084,(chan[c.chan].modOn<<7)|0x40|chan[c.chan].modDepth); + break; + case DIV_CMD_FDS_MOD_HIGH: + chan[c.chan].modOn=c.value>>4; + chan[c.chan].modFreq=((c.value&15)<<8)|(chan[c.chan].modFreq&0xff); + rWrite(0x4084,(chan[c.chan].modOn<<7)|0x40|chan[c.chan].modDepth); + rWrite(0x4087,chan[c.chan].modFreq>>8); + break; + case DIV_CMD_FDS_MOD_LOW: + chan[c.chan].modFreq=(chan[c.chan].modFreq&0xf00)|c.value; + rWrite(0x4086,chan[c.chan].modFreq&0xff); + break; + case DIV_CMD_FDS_MOD_POS: + chan[c.chan].modPos=c.value&0x7f; + // TODO + break; + case DIV_CMD_FDS_MOD_WAVE: { + DivWavetable* wt=parent->getWave(c.value); + for (int i=0; i<32; i++) { + if (wt->max<1 || wt->len<1) { + rWrite(0x4040+i,0); + } else { + int data=wt->data[i*MIN(32,wt->len)/32]*7/wt->max; + if (data<0) data=0; + if (data>7) data=7; + chan[c.chan].modTable[i]=data; + } + } + rWrite(0x4087,0x80|chan[c.chan].modFreq>>8); + for (int i=0; i<32; i++) { + rWrite(0x4088,chan[c.chan].modTable[i]); + } + rWrite(0x4087,chan[c.chan].modFreq>>8); + break; + } + case DIV_CMD_NOTE_PORTA: { + int destFreq=NOTE_FREQUENCY(c.value2); + bool return2=false; + if (destFreq>chan[c.chan].baseFreq) { + chan[c.chan].baseFreq+=c.value; + if (chan[c.chan].baseFreq>=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } else { + chan[c.chan].baseFreq-=c.value; + if (chan[c.chan].baseFreq<=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } + chan[c.chan].freqChanged=true; + if (return2) { + chan[c.chan].inPorta=false; + return 2; + } + break; + } + case DIV_CMD_LEGATO: + if (c.chan==3) break; + chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value+((chan[c.chan].std.willArp && !chan[c.chan].std.arpMode)?(chan[c.chan].std.arp):(0))); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + break; + case DIV_CMD_PRE_PORTA: + if (chan[c.chan].active && c.value2) { + if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + } + chan[c.chan].inPorta=c.value; + break; + case DIV_CMD_GET_VOLMAX: + return 32; + break; + case DIV_ALWAYS_SET_VOLUME: + return 1; + break; + default: + break; + } + return 1; +} + +void DivPlatformFDS::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; +} + +void DivPlatformFDS::forceIns() { + for (int i=0; i<1; i++) { + chan[i].insChanged=true; + chan[i].prevFreq=65535; + } + updateWave(); +} + +void* DivPlatformFDS::getChanState(int ch) { + return &chan[ch]; +} + +unsigned char* DivPlatformFDS::getRegisterPool() { + return regPool; +} + +int DivPlatformFDS::getRegisterPoolSize() { + return 128; +} + +void DivPlatformFDS::reset() { + for (int i=0; i<1; i++) { + chan[i]=DivPlatformFDS::Channel(); + } + if (dumpWrites) { + addWrite(0xffffffff,0); + } + + fds_reset(fds); + memset(regPool,0,128); + + rWrite(0x4023,0); + rWrite(0x4023,0x83); + rWrite(0x4089,0); +} + +bool DivPlatformFDS::keyOffAffectsArp(int ch) { + return true; +} + +void DivPlatformFDS::setFlags(unsigned int flags) { + if (flags==2) { // Dendy + rate=COLOR_PAL*2.0/5.0; + } else if (flags==1) { // PAL + rate=COLOR_PAL*3.0/8.0; + } else { // NTSC + rate=COLOR_NTSC/2.0; + } + chipClock=rate; +} + +void DivPlatformFDS::notifyInsDeletion(void* ins) { + for (int i=0; i<5; i++) { + chan[i].std.notifyInsDeletion((DivInstrument*)ins); + } +} + +void DivPlatformFDS::poke(unsigned int addr, unsigned short val) { + rWrite(addr,val); +} + +void DivPlatformFDS::poke(std::vector& wlist) { + for (DivRegWrite& i: wlist) rWrite(i.addr,i.val); +} + +int DivPlatformFDS::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { + parent=p; + apuType=flags; + dumpWrites=false; + skipRegisterWrites=false; + fds=new struct _fds; + for (int i=0; i<1; i++) { + isMuted[i]=false; + } + setFlags(flags); + + reset(); + return 5; +} + +void DivPlatformFDS::quit() { + delete fds; +} + +DivPlatformFDS::~DivPlatformFDS() { +} diff --git a/src/engine/platform/fds.h b/src/engine/platform/fds.h new file mode 100644 index 000000000..d9f145e1c --- /dev/null +++ b/src/engine/platform/fds.h @@ -0,0 +1,92 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _FDS_H +#define _FDS_H + +#include "../dispatch.h" +#include "../macroInt.h" + +class DivPlatformFDS: public DivDispatch { + struct Channel { + int freq, baseFreq, pitch, prevFreq, note, modFreq; + unsigned char ins, duty, sweep, modDepth, modPos; + bool active, insChanged, freqChanged, sweepChanged, keyOn, keyOff, inPorta, modOn; + signed char vol, outVol, wave; + signed char modTable[32]; + DivMacroInt std; + Channel(): + freq(0), + baseFreq(0), + pitch(0), + prevFreq(65535), + note(0), + modFreq(0), + ins(-1), + duty(0), + sweep(8), + modDepth(0), + modPos(0), + active(false), + insChanged(true), + freqChanged(false), + sweepChanged(false), + keyOn(false), + keyOff(false), + inPorta(false), + modOn(false), + vol(32), + outVol(32), + wave(-1) { + memset(modTable,0,32); + } + }; + Channel chan[1]; + bool isMuted[1]; + unsigned char apuType; + struct _fds* fds; + unsigned char regPool[128]; + + void updateWave(); + + friend void putDispatchChan(void*,int,int); + + public: + void acquire(short* bufL, short* bufR, size_t start, size_t len); + int dispatch(DivCommand c); + void* getChanState(int chan); + unsigned char* getRegisterPool(); + int getRegisterPoolSize(); + void reset(); + void forceIns(); + void tick(); + void muteChannel(int ch, bool mute); + bool keyOffAffectsArp(int ch); + void setFlags(unsigned int flags); + void notifyInsDeletion(void* ins); + void poke(unsigned int addr, unsigned short val); + void poke(std::vector& wlist); + const char** getRegisterSheet(); + const char* getEffectName(unsigned char effect); + int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); + void quit(); + ~DivPlatformFDS(); +}; + +#endif diff --git a/src/engine/platform/sound/nes/cpu_inline.h b/src/engine/platform/sound/nes/cpu_inline.h index 665fa01ee..ee762f17b 100644 --- a/src/engine/platform/sound/nes/cpu_inline.h +++ b/src/engine/platform/sound/nes/cpu_inline.h @@ -21,6 +21,7 @@ #include #include "apu.h" +#include "fds.h" #define mod_cycles_op(op, vl) cpu.cycles op vl #define r2006_during_rendering()\ @@ -314,4 +315,83 @@ INLINE static void apu_wr_reg(struct NESAPU* a, WORD address, BYTE value) { return; } + +INLINE static BYTE fds_wr_mem(struct _fds* fds, WORD address, BYTE value) { + if (address == 0x4023) { + fds->enabled_snd_reg=value&0x02; + } + if ((address >= 0x4040) && (address <= 0x408A)) { + if (fds->enabled_snd_reg) { + if ((address >= 0x4040) && (address <= 0x407F)) { + fds->snd.wave.data[address & 0x003F] = value & 0x3F; + return (TRUE); + } + if (address == 0x4080) { + fds->snd.volume.speed = value & 0x3F; + fds->snd.volume.increase = value & 0x40; + fds->snd.volume.mode = value & 0x80; + return (TRUE); + } + if (address == 0x4082) { + fds->snd.main.frequency = (fds->snd.main.frequency & 0xFF00) | value; + return (TRUE); + } + if (address == 0x4083) { + fds->snd.main.frequency = ((value & 0x0F) << 8) | (fds->snd.main.frequency & 0x00FF); + fds->snd.envelope.disabled = value & 0x40; + fds->snd.main.silence = value & 0x80; + return (TRUE); + } + if (address == 0x4084) { + fds->snd.sweep.speed = value & 0x3F; + fds->snd.sweep.increase = value & 0x40; + fds->snd.sweep.mode = value & 0x80; + return (TRUE); + } + if (address == 0x4085) { + fds->snd.sweep.bias = ((SBYTE) (value << 1)) / 2; + fds->snd.modulation.index = 0; + return (TRUE); + } + if (address == 0x4086) { + fds->snd.modulation.frequency = (fds->snd.modulation.frequency & 0xFF00) | value; + return (TRUE); + } + if (address == 0x4087) { + fds->snd.modulation.frequency = ((value & 0x0F) << 8) + | (fds->snd.modulation.frequency & 0x00FF); + fds->snd.modulation.disabled = value & 0x80; + return (TRUE); + } + if (address == 0x4088) { + BYTE i; + + // 0,2,4,6,-8,-6,-4,-2 + for (i = 0; i < 32; i++) { + BYTE a = i << 1; + + if (i < 31) { + fds->snd.modulation.data[a] = fds->snd.modulation.data[a + 2]; + } else { + BYTE tmp = ((value & 0x03) | (0x3F * (value & 0x04))); + fds->snd.modulation.data[a] = (SBYTE) tmp; + } + fds->snd.modulation.data[a + 1] = fds->snd.modulation.data[a]; + } + return (TRUE); + } + if (address == 0x4089) { + fds->snd.wave.writable = value & 0x80; + fds->snd.wave.volume = value & 0x03; + return (TRUE); + } + if (address == 0x408A) { + fds->snd.envelope.speed = value; + return (TRUE); + } + } + } + + return (FALSE); +} #endif /* CPU_INLINE_H_ */ diff --git a/src/engine/platform/sound/nes/fds.c b/src/engine/platform/sound/nes/fds.c new file mode 100644 index 000000000..e72e90389 --- /dev/null +++ b/src/engine/platform/sound/nes/fds.c @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2010-2019 Fabio Cavallo (aka FHorse) + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include +#include "fds.h" + +enum { TRANSFERED_8BIT = 0x02, END_OF_HEAD = 0x40 }; + +static const BYTE volume_wave[4] = { 39, 26, 19, 15 }; + +void fds_reset(struct _fds* fds) { + memset(fds,0,sizeof(struct _fds)); +} + +void extcl_apu_tick_FDS(struct _fds* fds) { + SWORD freq; + + /* volume unit */ + if (fds->snd.volume.mode) { + fds->snd.volume.gain = fds->snd.volume.speed; + } else if (!fds->snd.envelope.disabled && fds->snd.envelope.speed) { + if (fds->snd.volume.counter) { + fds->snd.volume.counter--; + } else { + fds->snd.volume.counter = (fds->snd.envelope.speed << 3) * (fds->snd.volume.speed + 1); + if (fds->snd.volume.increase) { + if (fds->snd.volume.gain < 32) { + fds->snd.volume.gain++; + } + } else if (fds->snd.volume.gain) { + fds->snd.volume.gain--; + } + } + } + + /* sweep unit */ + if (fds->snd.sweep.mode) { + fds->snd.sweep.gain = fds->snd.sweep.speed; + } else if (!fds->snd.envelope.disabled && fds->snd.envelope.speed) { + if (fds->snd.sweep.counter) { + fds->snd.sweep.counter--; + } else { + fds->snd.sweep.counter = (fds->snd.envelope.speed << 3) * (fds->snd.sweep.speed + 1); + if (fds->snd.sweep.increase) { + if (fds->snd.sweep.gain < 32) { + fds->snd.sweep.gain++; + } + } else if (fds->snd.sweep.gain) { + fds->snd.sweep.gain--; + } + } + } + + /* modulation unit */ + freq = fds->snd.main.frequency; + + if (!fds->snd.modulation.disabled && fds->snd.modulation.frequency) { + if ((fds->snd.modulation.counter -= fds->snd.modulation.frequency) < 0) { + SWORD temp, temp2, a, d; + SBYTE adj = fds->snd.modulation.data[fds->snd.modulation.index]; + + fds->snd.modulation.counter += 65536; + + if (++fds->snd.modulation.index == 64) { + fds->snd.modulation.index = 0; + } + + if (adj == -4) { + fds->snd.sweep.bias = 0; + } else { + fds->snd.sweep.bias += adj; + } + + temp = fds->snd.sweep.bias * ((fds->snd.sweep.gain < 32) ? fds->snd.sweep.gain : 32); + + a = 64; + d = 0; + + if (temp <= 0) { + d = 15; + } else if (temp < 3040) { //95 * 32 + a = 66; + d = -31; + } + + temp2 = a + (SBYTE) ((temp - d) / 16 - a); + + fds->snd.modulation.mod = freq * temp2 / 64; + } + + if (freq) { + freq += fds->snd.modulation.mod; + } + } + + /* main unit */ + if (fds->snd.main.silence) { + fds->snd.main.output = 0; + return; + } + + if (freq && !fds->snd.wave.writable) { + if ((fds->snd.wave.counter -= freq) < 0) { + WORD level; + + fds->snd.wave.counter += 65536; + + level = (fds->snd.volume.gain < 32 ? fds->snd.volume.gain : 32) + * volume_wave[fds->snd.wave.volume]; + + /* valore massimo dell'output (63 * (39 * 32)) = 78624 */ + /*fds->snd.main.output = (fds->snd.wave.data[fds->snd.wave.index] * level) >> 4;*/ + fds->snd.main.output = (fds->snd.wave.data[fds->snd.wave.index] * level) >> 3; + + if (++fds->snd.wave.index == 64) { + fds->snd.wave.index = 0; + } + + fds->snd.wave.clocked = TRUE; + } + } +} diff --git a/src/engine/platform/sound/nes/fds.h b/src/engine/platform/sound/nes/fds.h new file mode 100644 index 000000000..f1ef45e72 --- /dev/null +++ b/src/engine/platform/sound/nes/fds.h @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2010-2019 Fabio Cavallo (aka FHorse) + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef FDS_H_ +#define FDS_H_ + +#include "common.h" + +enum fds_operations { FDS_OP_NONE, FDS_OP_READ, FDS_OP_WRITE }; + +#if defined (__cplusplus) +#define EXTERNC extern "C" +#else +#define EXTERNC +#endif + +EXTERNC struct _fds { + // snd + BYTE enabled_snd_reg; + struct _fds_snd { + struct _fds_snd_wave { + BYTE data[64]; + BYTE writable; + BYTE volume; + + BYTE index; + int32_t counter; + + /* ------------------------------------------------------- */ + /* questi valori non e' necessario salvarli nei savestates */ + /* ------------------------------------------------------- */ + /* */ BYTE clocked; /* */ + /* ------------------------------------------------------- */ + } wave; + struct _fds_snd_envelope { + BYTE speed; + BYTE disabled; + } envelope; + struct _fds_snd_main { + BYTE silence; + WORD frequency; + + SWORD output; + } main; + struct _fds_snd_volume { + BYTE speed; + BYTE mode; + BYTE increase; + + BYTE gain; + uint32_t counter; + } volume; + struct _fds_snd_sweep { + SBYTE bias; + BYTE mode; + BYTE increase; + BYTE speed; + + BYTE gain; + uint32_t counter; + } sweep; + struct _fds_snd_modulation { + SBYTE data[64]; + WORD frequency; + BYTE disabled; + + BYTE index; + int32_t counter; + SWORD mod; + } modulation; + } snd; +}; + +EXTERNC void extcl_apu_tick_FDS(struct _fds* fds); +EXTERNC void fds_reset(struct _fds* fds); + +#undef EXTERNC + +#endif /* FDS_H_ */ diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index bd6c5f370..52ea991af 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -112,6 +112,12 @@ const char* cmdName[DIV_CMD_MAX]={ "AY_IO_WRITE", "AY_AUTO_PWM", + "FDS_MOD_DEPTH", + "FDS_MOD_HIGH", + "FDS_MOD_LOW", + "FDS_MOD_POS", + "FDS_MOD_WAVE", + "SAA_ENVELOPE", "AMIGA_FILTER", @@ -296,6 +302,30 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe return false; } break; + case DIV_SYSTEM_FDS: + switch (effect) { + case 0x10: // select waveform + dispatchCmd(DivCommand(DIV_CMD_WAVE,ch,effectVal)); + break; + case 0x11: // modulation depth + dispatchCmd(DivCommand(DIV_CMD_FDS_MOD_DEPTH,ch,effectVal)); + break; + case 0x12: // modulation enable/high + dispatchCmd(DivCommand(DIV_CMD_FDS_MOD_HIGH,ch,effectVal)); + break; + case 0x13: // modulation low + dispatchCmd(DivCommand(DIV_CMD_FDS_MOD_LOW,ch,effectVal)); + break; + case 0x14: // modulation pos + dispatchCmd(DivCommand(DIV_CMD_FDS_MOD_POS,ch,effectVal)); + break; + case 0x15: // modulation wave + dispatchCmd(DivCommand(DIV_CMD_FDS_MOD_WAVE,ch,effectVal)); + break; + default: + return false; + } + break; case DIV_SYSTEM_OPLL_DRUMS: case DIV_SYSTEM_OPL_DRUMS: case DIV_SYSTEM_OPL2_DRUMS: diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index b3f9e6ab5..084504fe1 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -1782,6 +1782,7 @@ int DivEngine::minVGMVersion(DivSystem which) { case DIV_SYSTEM_GB: case DIV_SYSTEM_PCE: case DIV_SYSTEM_NES: + case DIV_SYSTEM_FDS: case DIV_SYSTEM_QSOUND: return 0x161; case DIV_SYSTEM_SAA1099: diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index d06bb195f..68df49c50 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -481,6 +481,18 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write case DIV_SYSTEM_NES: w->writeC(0xb4); w->writeC(baseAddr2|(write.addr&0xff)); + w->writeC(write.val); + break; + case DIV_SYSTEM_FDS: // yeah + w->writeC(0xb4); + if ((write.addr&0xff)==0x23) { + w->writeC(baseAddr2|0x3f); + } else if ((write.addr&0xff)>=0x80) { + w->writeC(baseAddr2|(0x20+(write.addr&0x7f))); + } else { + w->writeC(baseAddr2|(write.addr&0xff)); + } + w->writeC(write.val); break; case DIV_SYSTEM_YM2151: @@ -882,6 +894,20 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) { howManyChips++; } break; + case DIV_SYSTEM_FDS: + if (!hasNES) { + hasNES=0x80000000|disCont[i].dispatch->chipClock; + willExport[i]=true; + } else if (!(hasNES&0x80000000)) { + hasNES|=0x80000000; + willExport[i]=true; + } else if (!(hasNES&0x40000000)) { + isSecond[i]=true; + willExport[i]=true; + hasNES|=0xc0000000; + howManyChips++; + } + break; case DIV_SYSTEM_LYNX: if (!hasLynx) { hasLynx=disCont[i].dispatch->chipClock; diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 2e4489378..27ba597f9 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -2152,6 +2152,9 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_PET) { volMax=1; } + if (ins->type==DIV_INS_FDS) { + volMax=32; + } bool arpMode=ins->std.arpMacroMode; From 4ec91b8b42afca23bf74766cb34671f63e343af7 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 4 Apr 2022 00:12:11 -0500 Subject: [PATCH 509/637] fix .fui instruments not loading! --- src/engine/fileOpsIns.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/engine/fileOpsIns.cpp b/src/engine/fileOpsIns.cpp index a92e52c6b..6689c5547 100644 --- a/src/engine/fileOpsIns.cpp +++ b/src/engine/fileOpsIns.cpp @@ -633,7 +633,7 @@ void DivEngine::loadSBI(SafeReader& reader, std::vector& ret, St void DivEngine::loadOPM(SafeReader& reader, std::vector& ret, String& stripPath) { DivInstrument* ins[128]; - memset(ins,0,128*sizeof(DivInstrument*)); + memset(ins,0,128*sizeof(void*)); try { String line; @@ -733,6 +733,8 @@ std::vector DivEngine::instrumentFromFile(const char* path) { delete ins; delete[] buf; return ret; + } else { + ret.push_back(ins); } } catch (EndOfFileException& e) { lastError="premature end of file"; From eee2500b5c20b12f0decd09e0dd619c0c1db033a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 4 Apr 2022 00:47:27 -0500 Subject: [PATCH 510/637] FDS: effect names and register sheet --- src/engine/platform/fds.cpp | 41 +++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/src/engine/platform/fds.cpp b/src/engine/platform/fds.cpp index 1ec264d10..b4103748b 100644 --- a/src/engine/platform/fds.cpp +++ b/src/engine/platform/fds.cpp @@ -26,20 +26,53 @@ #define rWrite(a,v) if (!skipRegisterWrites) {fds_wr_mem(fds,a,v); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} } +const char* regCheatSheetFDS[]={ + "IOCtrl", "4023", + "Wave", "4040", + "Volume", "4080", + "FreqL", "4082", + "FreqH", "4083", + "ModCtrl", "4084", + "ModCount", "4085", + "ModFreqL", "4086", + "ModFreqH", "4087", + "ModWrite", "4088", + "WaveCtrl", "4089", + "EnvSpeed", "408A", + "ReadVol", "4090", + "ReadPos", "4091", + "ReadModV", "4092", + "ReadModP", "4093", + "ReadModCG", "4094", + "ReadModInc", "4095", + "ReadWave", "4096", + "ReadModCount", "4097", + NULL +}; + const char** DivPlatformFDS::getRegisterSheet() { - return NULL; + return regCheatSheetFDS; } const char* DivPlatformFDS::getEffectName(unsigned char effect) { switch (effect) { + case 0x10: + return "10xx: Change waveform"; + break; + case 0x11: + return "11xx: Set modulation depth"; + break; case 0x12: - return "12xx: Set duty cycle/noise mode (pulse: 0 to 3; noise: 0 or 1)"; + return "12xy: Set modulation frequency high byte (x: enable; y: value)"; break; case 0x13: - return "13xy: Sweep up (x: time; y: shift)"; + return "13xx: Set modulation frequency low byte"; break; case 0x14: - return "14xy: Sweep down (x: time; y: shift)"; + return "14xx: Set modulator position"; + break; + case 0x15: + return "15xx: Set modulator table to waveform"; break; } return NULL; From e5a162dbe6f9b9982018fdf2ea272cb3d15c68e0 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 4 Apr 2022 00:48:08 -0500 Subject: [PATCH 511/637] GUI: add presets for FDS and MMC5 --- src/gui/guiConst.cpp | 2 ++ src/gui/presets.cpp | 14 ++++++++++++++ src/gui/sysConf.cpp | 2 ++ 3 files changed, 18 insertions(+) diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index 20085cfeb..462c9f827 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -389,6 +389,8 @@ const int availableSystems[]={ DIV_SYSTEM_PET, DIV_SYSTEM_VIC20, DIV_SYSTEM_VRC6, + DIV_SYSTEM_FDS, + DIV_SYSTEM_MMC5, 0 // don't remove this last one! }; diff --git a/src/gui/presets.cpp b/src/gui/presets.cpp index f92c2b35c..e7b08081c 100644 --- a/src/gui/presets.cpp +++ b/src/gui/presets.cpp @@ -241,6 +241,13 @@ void FurnaceGUI::initSystemPresets() { 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "NES with MMC5", { + DIV_SYSTEM_NES, 64, 0, 0, + DIV_SYSTEM_MMC5, 64, 0, 0, + 0 + } + )); cat.systems.push_back(FurnaceGUISysDef( "NES with Sunsoft 5B", { DIV_SYSTEM_NES, 64, 0, 0, @@ -269,6 +276,13 @@ void FurnaceGUI::initSystemPresets() { 0 } )); + cat.systems.push_back(FurnaceGUISysDef( + "Famicom Disk System", { + DIV_SYSTEM_NES, 64, 0, 0, + DIV_SYSTEM_FDS, 64, 0, 0, + 0 + } + )); cat.systems.push_back(FurnaceGUISysDef( "Mattel Intellivision", { DIV_SYSTEM_AY8910, 64, 0, 48, diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index f1f25097b..1bc60c7c7 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -148,6 +148,8 @@ void FurnaceGUI::drawSysConf(int i) { break; case DIV_SYSTEM_NES: case DIV_SYSTEM_VRC6: + case DIV_SYSTEM_FDS: + case DIV_SYSTEM_MMC5: if (ImGui::RadioButton("NTSC (1.79MHz)",flags==0)) { e->setSysFlags(i,0,restart); updateWindowTitle(); From ccb8d3d355326454cceb1b438295c0c98ea37019 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 4 Apr 2022 04:47:52 -0500 Subject: [PATCH 512/637] dev76 - format changes - 8 more macros - FDS instrument data --- papers/format.md | 50 +++++++++++++++ src/engine/engine.h | 4 +- src/engine/instrument.cpp | 130 ++++++++++++++++++++++++++++++++++++++ src/engine/instrument.h | 75 +++++++++++++++++++++- src/engine/macroInt.cpp | 85 ++++++++++++++++++++++++- src/engine/macroInt.h | 54 ++++++++++++++++ 6 files changed, 392 insertions(+), 6 deletions(-) diff --git a/papers/format.md b/papers/format.md index cc4a5add7..54414fa47 100644 --- a/papers/format.md +++ b/papers/format.md @@ -29,6 +29,8 @@ furthermore, an `or reserved` indicates this field is always present, but is res the format versions are: +- 76: Furnace dev76 +- 75: Furnace dev75/April Fools' 0.6pre0 - 74: Furnace dev74 - 73: Furnace dev73 - 72: Furnace dev72 @@ -523,6 +525,54 @@ size | description | - bit 1: update on change | - bit 0: load on playback 1 | reserved + --- | **even more macros** (>=76) + 4 | left panning macro length + 4 | right panning macro length + 4 | phase reset macro length + 4 | extra 4 macro length + 4 | extra 5 macro length + 4 | extra 6 macro length + 4 | extra 7 macro length + 4 | extra 8 macro length + 4 | left panning macro loop + 4 | right panning macro loop + 4 | phase reset macro loop + 4 | extra 4 macro loop + 4 | extra 5 macro loop + 4 | extra 6 macro loop + 4 | extra 7 macro loop + 4 | extra 8 macro loop + 4 | left panning macro release + 4 | right panning macro release + 4 | phase reset macro release + 4 | extra 4 macro release + 4 | extra 5 macro release + 4 | extra 6 macro release + 4 | extra 7 macro release + 4 | extra 8 macro release + 1 | left panning macro open + 1 | right panning macro open + 1 | phase reset macro open + 1 | extra 4 macro open + 1 | extra 5 macro open + 1 | extra 6 macro open + 1 | extra 7 macro open + 1 | extra 8 macro open + --- | **even more macro data** (>=76) + 4?? | left panning macro + 4?? | right panning macro + 4?? | phase reset macro + 4?? | extra 4 macro + 4?? | extra 5 macro + 4?? | extra 6 macro + 4?? | extra 7 macro + 4?? | extra 8 macro + --- | **FDS instrument data** (>=76) + 4 | modulation speed + 4 | modulation depth + 1 | init modulation table with first wave + 3 | reserved + 32 | modulation table ``` # wavetable diff --git a/src/engine/engine.h b/src/engine/engine.h index f4a3db832..9a15f4f11 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -42,8 +42,8 @@ #define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock(); #define BUSY_END isBusy.unlock(); softLocked=false; -#define DIV_VERSION "dev75" -#define DIV_ENGINE_VERSION 75 +#define DIV_VERSION "dev76" +#define DIV_ENGINE_VERSION 76 // for imports #define DIV_VERSION_MOD 0xff01 diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index f3cf9f591..3dd1e883c 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -392,6 +392,77 @@ void DivInstrument::putInsData(SafeWriter* w) { w->writeC(n163.waveLen); w->writeC(n163.waveMode); w->writeC(0); // reserved + + // more macros + w->writeI(std.panLMacroLen); + w->writeI(std.panRMacroLen); + w->writeI(std.phaseResetMacroLen); + w->writeI(std.ex4MacroLen); + w->writeI(std.ex5MacroLen); + w->writeI(std.ex6MacroLen); + w->writeI(std.ex7MacroLen); + w->writeI(std.ex8MacroLen); + + w->writeI(std.panLMacroLoop); + w->writeI(std.panRMacroLoop); + w->writeI(std.phaseResetMacroLoop); + w->writeI(std.ex4MacroLoop); + w->writeI(std.ex5MacroLoop); + w->writeI(std.ex6MacroLoop); + w->writeI(std.ex7MacroLoop); + w->writeI(std.ex8MacroLoop); + + w->writeI(std.panLMacroRel); + w->writeI(std.panRMacroRel); + w->writeI(std.phaseResetMacroRel); + w->writeI(std.ex4MacroRel); + w->writeI(std.ex5MacroRel); + w->writeI(std.ex6MacroRel); + w->writeI(std.ex7MacroRel); + w->writeI(std.ex8MacroRel); + + w->writeC(std.panLMacroOpen); + w->writeC(std.panRMacroOpen); + w->writeC(std.phaseResetMacroOpen); + w->writeC(std.ex4MacroOpen); + w->writeC(std.ex5MacroOpen); + w->writeC(std.ex6MacroOpen); + w->writeC(std.ex7MacroOpen); + w->writeC(std.ex8MacroOpen); + + for (int j=0; jwriteI(std.panLMacro[j]); + } + for (int j=0; jwriteI(std.panRMacro[j]); + } + for (int j=0; jwriteI(std.phaseResetMacro[j]); + } + for (int j=0; jwriteI(std.ex4Macro[j]); + } + for (int j=0; jwriteI(std.ex5Macro[j]); + } + for (int j=0; jwriteI(std.ex6Macro[j]); + } + for (int j=0; jwriteI(std.ex7Macro[j]); + } + for (int j=0; jwriteI(std.ex8Macro[j]); + } + + // FDS + w->writeI(fds.modSpeed); + w->writeI(fds.modDepth); + w->writeC(fds.initModTableWithFirstWave); + w->writeC(0); // reserved + w->writeC(0); + w->writeC(0); + w->write(fds.modTable,32); } DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) { @@ -755,6 +826,65 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) { n163.waveMode=(unsigned char)reader.readC(); reader.readC(); // reserved } + + // more macros + if (version>=76) { + std.panLMacroLen=reader.readI(); + std.panRMacroLen=reader.readI(); + std.phaseResetMacroLen=reader.readI(); + std.ex4MacroLen=reader.readI(); + std.ex5MacroLen=reader.readI(); + std.ex6MacroLen=reader.readI(); + std.ex7MacroLen=reader.readI(); + std.ex8MacroLen=reader.readI(); + + std.panLMacroLoop=reader.readI(); + std.panRMacroLoop=reader.readI(); + std.phaseResetMacroLoop=reader.readI(); + std.ex4MacroLoop=reader.readI(); + std.ex5MacroLoop=reader.readI(); + std.ex6MacroLoop=reader.readI(); + std.ex7MacroLoop=reader.readI(); + std.ex8MacroLoop=reader.readI(); + + std.panLMacroRel=reader.readI(); + std.panRMacroRel=reader.readI(); + std.phaseResetMacroRel=reader.readI(); + std.ex4MacroRel=reader.readI(); + std.ex5MacroRel=reader.readI(); + std.ex6MacroRel=reader.readI(); + std.ex7MacroRel=reader.readI(); + std.ex8MacroRel=reader.readI(); + + std.panLMacroOpen=reader.readC(); + std.panRMacroOpen=reader.readC(); + std.phaseResetMacroOpen=reader.readC(); + std.ex4MacroOpen=reader.readC(); + std.ex5MacroOpen=reader.readC(); + std.ex6MacroOpen=reader.readC(); + std.ex7MacroOpen=reader.readC(); + std.ex8MacroOpen=reader.readC(); + + reader.read(std.panLMacro,4*std.panLMacroLen); + reader.read(std.panRMacro,4*std.panRMacroLen); + reader.read(std.phaseResetMacro,4*std.phaseResetMacroLen); + reader.read(std.ex4Macro,4*std.ex4MacroLen); + reader.read(std.ex5Macro,4*std.ex5MacroLen); + reader.read(std.ex6Macro,4*std.ex6MacroLen); + reader.read(std.ex7Macro,4*std.ex7MacroLen); + reader.read(std.ex8Macro,4*std.ex8MacroLen); + } + + // FDS + if (version>=76) { + fds.modSpeed=reader.readI(); + fds.modDepth=reader.readI(); + fds.initModTableWithFirstWave=reader.readC(); + reader.readC(); // reserved + reader.readC(); + reader.readC(); + reader.read(fds.modTable,32); + } return DIV_DATA_SUCCESS; } diff --git a/src/engine/instrument.h b/src/engine/instrument.h index e8ec9d318..fafd16b59 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -148,6 +148,7 @@ struct DivInstrumentFM { } }; +// this is getting out of hand struct DivInstrumentSTD { int volMacro[256]; int arpMacro[256]; @@ -161,20 +162,41 @@ struct DivInstrumentSTD { int fbMacro[256]; int fmsMacro[256]; int amsMacro[256]; + int panLMacro[256]; + int panRMacro[256]; + int phaseResetMacro[256]; + int ex4Macro[256]; + int ex5Macro[256]; + int ex6Macro[256]; + int ex7Macro[256]; + int ex8Macro[256]; + bool arpMacroMode; unsigned char volMacroHeight, dutyMacroHeight, waveMacroHeight; + bool volMacroOpen, arpMacroOpen, dutyMacroOpen, waveMacroOpen; bool pitchMacroOpen, ex1MacroOpen, ex2MacroOpen, ex3MacroOpen; bool algMacroOpen, fbMacroOpen, fmsMacroOpen, amsMacroOpen; + bool panLMacroOpen, panRMacroOpen, phaseResetMacroOpen, ex4MacroOpen; + bool ex5MacroOpen, ex6MacroOpen, ex7MacroOpen, ex8MacroOpen; + unsigned char volMacroLen, arpMacroLen, dutyMacroLen, waveMacroLen; unsigned char pitchMacroLen, ex1MacroLen, ex2MacroLen, ex3MacroLen; unsigned char algMacroLen, fbMacroLen, fmsMacroLen, amsMacroLen; + unsigned char panLMacroLen, panRMacroLen, phaseResetMacroLen, ex4MacroLen; + unsigned char ex5MacroLen, ex6MacroLen, ex7MacroLen, ex8MacroLen; + signed char volMacroLoop, arpMacroLoop, dutyMacroLoop, waveMacroLoop; signed char pitchMacroLoop, ex1MacroLoop, ex2MacroLoop, ex3MacroLoop; signed char algMacroLoop, fbMacroLoop, fmsMacroLoop, amsMacroLoop; + signed char panLMacroLoop, panRMacroLoop, phaseResetMacroLoop, ex4MacroLoop; + signed char ex5MacroLoop, ex6MacroLoop, ex7MacroLoop, ex8MacroLoop; + signed char volMacroRel, arpMacroRel, dutyMacroRel, waveMacroRel; signed char pitchMacroRel, ex1MacroRel, ex2MacroRel, ex3MacroRel; signed char algMacroRel, fbMacroRel, fmsMacroRel, amsMacroRel; + signed char panLMacroRel, panRMacroRel, phaseResetMacroRel, ex4MacroRel; + signed char ex5MacroRel, ex6MacroRel, ex7MacroRel, ex8MacroRel; struct OpMacro { // ar, dr, mult, rr, sl, tl, dt2, rs, dt, d2r, ssgEnv; unsigned char amMacro[256]; @@ -277,6 +299,15 @@ struct DivInstrumentSTD { fbMacroOpen(false), fmsMacroOpen(false), amsMacroOpen(false), + panLMacroOpen(false), + panRMacroOpen(false), + phaseResetMacroOpen(false), + ex4MacroOpen(false), + ex5MacroOpen(false), + ex6MacroOpen(false), + ex7MacroOpen(false), + ex8MacroOpen(false), + volMacroLen(0), arpMacroLen(0), dutyMacroLen(0), @@ -289,6 +320,15 @@ struct DivInstrumentSTD { fbMacroLen(0), fmsMacroLen(0), amsMacroLen(0), + panLMacroLen(0), + panRMacroLen(0), + phaseResetMacroLen(0), + ex4MacroLen(0), + ex5MacroLen(0), + ex6MacroLen(0), + ex7MacroLen(0), + ex8MacroLen(0), + volMacroLoop(-1), arpMacroLoop(-1), dutyMacroLoop(-1), @@ -301,6 +341,15 @@ struct DivInstrumentSTD { fbMacroLoop(-1), fmsMacroLoop(-1), amsMacroLoop(-1), + panLMacroLoop(-1), + panRMacroLoop(-1), + phaseResetMacroLoop(-1), + ex4MacroLoop(-1), + ex5MacroLoop(-1), + ex6MacroLoop(-1), + ex7MacroLoop(-1), + ex8MacroLoop(-1), + volMacroRel(-1), arpMacroRel(-1), dutyMacroRel(-1), @@ -312,7 +361,15 @@ struct DivInstrumentSTD { algMacroRel(-1), fbMacroRel(-1), fmsMacroRel(-1), - amsMacroRel(-1) { + amsMacroRel(-1), + panLMacroRel(-1), + panRMacroRel(-1), + phaseResetMacroRel(-1), + ex4MacroRel(-1), + ex5MacroRel(-1), + ex6MacroRel(-1), + ex7MacroRel(-1), + ex8MacroRel(-1) { memset(volMacro,0,256*sizeof(int)); memset(arpMacro,0,256*sizeof(int)); memset(dutyMacro,0,256*sizeof(int)); @@ -325,6 +382,14 @@ struct DivInstrumentSTD { memset(fbMacro,0,256*sizeof(int)); memset(fmsMacro,0,256*sizeof(int)); memset(amsMacro,0,256*sizeof(int)); + memset(panLMacro,0,256*sizeof(int)); + memset(panRMacro,0,256*sizeof(int)); + memset(phaseResetMacro,0,256*sizeof(int)); + memset(ex4Macro,0,256*sizeof(int)); + memset(ex5Macro,0,256*sizeof(int)); + memset(ex6Macro,0,256*sizeof(int)); + memset(ex7Macro,0,256*sizeof(int)); + memset(ex8Macro,0,256*sizeof(int)); } }; @@ -399,10 +464,13 @@ struct DivInstrumentN163 { struct DivInstrumentFDS { signed char modTable[32]; - unsigned char modSpeed, modLevel; + int modSpeed, modDepth; + // this is here for compatibility. + bool initModTableWithFirstWave; DivInstrumentFDS(): modSpeed(0), - modLevel(0) { + modDepth(0), + initModTableWithFirstWave(false) { memset(modTable,0,32); } }; @@ -417,6 +485,7 @@ struct DivInstrument { DivInstrumentC64 c64; DivInstrumentAmiga amiga; DivInstrumentN163 n163; + DivInstrumentFDS fds; /** * save the instrument to a SafeWriter. diff --git a/src/engine/macroInt.cpp b/src/engine/macroInt.cpp index adcb33139..d74b59a6c 100644 --- a/src/engine/macroInt.cpp +++ b/src/engine/macroInt.cpp @@ -63,6 +63,15 @@ void DivMacroInt::next() { doMacro(finishedFms,hadFms,hasFms,fms,fmsPos,ins->std.fmsMacro,ins->std.fmsMacroLen,ins->std.fmsMacroLoop,ins->std.fmsMacroRel); doMacro(finishedAms,hadAms,hasAms,ams,amsPos,ins->std.amsMacro,ins->std.amsMacroLen,ins->std.amsMacroLoop,ins->std.amsMacroRel); + doMacro(finishedPanL,hadPanL,hasPanL,panL,panLPos,ins->std.panLMacro,ins->std.panLMacroLen,ins->std.panLMacroLoop,ins->std.panLMacroRel); + doMacro(finishedPanR,hadPanR,hasPanR,panR,panRPos,ins->std.panRMacro,ins->std.panRMacroLen,ins->std.panRMacroLoop,ins->std.panRMacroRel); + doMacro(finishedPhaseReset,hadPhaseReset,hasPhaseReset,phaseReset,phaseResetPos,ins->std.phaseResetMacro,ins->std.phaseResetMacroLen,ins->std.phaseResetMacroLoop,ins->std.phaseResetMacroRel); + doMacro(finishedEx4,hadEx4,hasEx4,ex4,ex4Pos,ins->std.ex4Macro,ins->std.ex4MacroLen,ins->std.ex4MacroLoop,ins->std.ex4MacroRel); + doMacro(finishedEx5,hadEx5,hasEx5,ex5,ex5Pos,ins->std.ex5Macro,ins->std.ex5MacroLen,ins->std.ex5MacroLoop,ins->std.ex5MacroRel); + doMacro(finishedEx6,hadEx6,hasEx6,ex6,ex6Pos,ins->std.ex6Macro,ins->std.ex6MacroLen,ins->std.ex6MacroLoop,ins->std.ex6MacroRel); + doMacro(finishedEx7,hadEx7,hasEx7,ex7,ex7Pos,ins->std.ex7Macro,ins->std.ex7MacroLen,ins->std.ex7MacroLoop,ins->std.ex7MacroRel); + doMacro(finishedEx8,hadEx8,hasEx8,ex8,ex8Pos,ins->std.ex8Macro,ins->std.ex8MacroLen,ins->std.ex8MacroLoop,ins->std.ex8MacroRel); + for (int i=0; i<4; i++) { DivInstrumentSTD::OpMacro& m=ins->std.opMacros[i]; IntOp& o=op[i]; @@ -111,6 +120,14 @@ void DivMacroInt::init(DivInstrument* which) { fbPos=0; fmsPos=0; amsPos=0; + panLPos=0; + panRPos=0; + phaseResetPos=0; + ex4Pos=0; + ex5Pos=0; + ex6Pos=0; + ex7Pos=0; + ex8Pos=0; released=false; @@ -126,6 +143,14 @@ void DivMacroInt::init(DivInstrument* which) { hasFb=false; hasFms=false; hasAms=false; + hasPanL=false; + hasPanR=false; + hasPhaseReset=false; + hasEx4=false; + hasEx5=false; + hasEx6=false; + hasEx7=false; + hasEx8=false; hadVol=false; hadArp=false; @@ -139,6 +164,14 @@ void DivMacroInt::init(DivInstrument* which) { hadFb=false; hadFms=false; hadAms=false; + hadPanL=false; + hadPanR=false; + hadPhaseReset=false; + hadEx4=false; + hadEx5=false; + hadEx6=false; + hadEx7=false; + hadEx8=false; willVol=false; willArp=false; @@ -152,6 +185,14 @@ void DivMacroInt::init(DivInstrument* which) { willFb=false; willFms=false; willAms=false; + willPanL=false; + willPanR=false; + willPhaseReset=false; + willEx4=false; + willEx5=false; + willEx6=false; + willEx7=false; + willEx8=false; op[0]=IntOp(); op[1]=IntOp(); @@ -223,6 +264,48 @@ void DivMacroInt::init(DivInstrument* which) { willAms=true; } + // TODO: other macros + if (ins->std.panLMacroLen>0) { + hadPanL=true; + hasPanL=true; + willPanL=true; + } + if (ins->std.panRMacroLen>0) { + hadPanR=true; + hasPanR=true; + willPanR=true; + } + if (ins->std.phaseResetMacroLen>0) { + hadPhaseReset=true; + hasPhaseReset=true; + willPhaseReset=true; + } + if (ins->std.ex4MacroLen>0) { + hadEx4=true; + hasEx4=true; + willEx4=true; + } + if (ins->std.ex5MacroLen>0) { + hadEx5=true; + hasEx5=true; + willEx5=true; + } + if (ins->std.ex6MacroLen>0) { + hadEx6=true; + hasEx6=true; + willEx6=true; + } + if (ins->std.ex7MacroLen>0) { + hadEx7=true; + hasEx7=true; + willEx7=true; + } + if (ins->std.ex8MacroLen>0) { + hadEx8=true; + hasEx8=true; + willEx8=true; + } + if (ins->std.arpMacroMode) { arpMode=true; } @@ -339,4 +422,4 @@ void DivMacroInt::notifyInsDeletion(DivInstrument* which) { if (ins==which) { init(NULL); } -} \ No newline at end of file +} diff --git a/src/engine/macroInt.h b/src/engine/macroInt.h index b2b3c6837..d5e6fd140 100644 --- a/src/engine/macroInt.h +++ b/src/engine/macroInt.h @@ -26,17 +26,23 @@ class DivMacroInt { DivInstrument* ins; int volPos, arpPos, dutyPos, wavePos, pitchPos, ex1Pos, ex2Pos, ex3Pos; int algPos, fbPos, fmsPos, amsPos; + int panLPos, panRPos, phaseResetPos, ex4Pos, ex5Pos, ex6Pos, ex7Pos, ex8Pos; bool released; public: int vol; int arp; int duty, wave, pitch, ex1, ex2, ex3; int alg, fb, fms, ams; + int panL, panR, phaseReset, ex4, ex5, ex6, ex7, ex8; bool hasVol, hasArp, hasDuty, hasWave, hasPitch, hasEx1, hasEx2, hasEx3, hasAlg, hasFb, hasFms, hasAms; + bool hasPanL, hasPanR, hasPhaseReset, hasEx4, hasEx5, hasEx6, hasEx7, hasEx8; bool hadVol, hadArp, hadDuty, hadWave, hadPitch, hadEx1, hadEx2, hadEx3, hadAlg, hadFb, hadFms, hadAms; + bool hadPanL, hadPanR, hadPhaseReset, hadEx4, hadEx5, hadEx6, hadEx7, hadEx8; bool finishedVol, finishedArp, finishedDuty, finishedWave, finishedPitch, finishedEx1, finishedEx2, finishedEx3; bool finishedAlg, finishedFb, finishedFms, finishedAms; + bool finishedPanL, finishedPanR, finishedPhaseReset, finishedEx4, finishedEx5, finishedEx6, finishedEx7, finishedEx8; bool willVol, willArp, willDuty, willWave, willPitch, willEx1, willEx2, willEx3, willAlg, willFb, willFms, willAms; + bool willPanL, willPanR, willPhaseReset, willEx4, willEx5, willEx6, willEx7, willEx8; bool arpMode; struct IntOp { int amPos, arPos, drPos, multPos; @@ -173,6 +179,14 @@ class DivMacroInt { fbPos(0), fmsPos(0), amsPos(0), + panLPos(0), + panRPos(0), + phaseResetPos(0), + ex4Pos(0), + ex5Pos(0), + ex6Pos(0), + ex7Pos(0), + ex8Pos(0), released(false), vol(0), arp(0), @@ -186,6 +200,14 @@ class DivMacroInt { fb(0), fms(0), ams(0), + panL(0), + panR(0), + phaseReset(0), + ex4(0), + ex5(0), + ex6(0), + ex7(0), + ex8(0), hasVol(false), hasArp(false), hasDuty(false), @@ -198,6 +220,14 @@ class DivMacroInt { hasFb(false), hasFms(false), hasAms(false), + hasPanL(false), + hasPanR(false), + hasPhaseReset(false), + hasEx4(false), + hasEx5(false), + hasEx6(false), + hasEx7(false), + hasEx8(false), hadVol(false), hadArp(false), hadDuty(false), @@ -210,6 +240,14 @@ class DivMacroInt { hadFb(false), hadFms(false), hadAms(false), + hadPanL(false), + hadPanR(false), + hadPhaseReset(false), + hadEx4(false), + hadEx5(false), + hadEx6(false), + hadEx7(false), + hadEx8(false), finishedVol(false), finishedArp(false), finishedDuty(false), @@ -222,6 +260,14 @@ class DivMacroInt { finishedFb(false), finishedFms(false), finishedAms(false), + finishedPanL(false), + finishedPanR(false), + finishedPhaseReset(false), + finishedEx4(false), + finishedEx5(false), + finishedEx6(false), + finishedEx7(false), + finishedEx8(false), willVol(false), willArp(false), willDuty(false), @@ -234,6 +280,14 @@ class DivMacroInt { willFb(false), willFms(false), willAms(false), + willPanL(false), + willPanR(false), + willPhaseReset(false), + willEx4(false), + willEx5(false), + willEx6(false), + willEx7(false), + willEx8(false), arpMode(false) {} }; From 81150d96fa0ad9f636a3080b0db87e71848a2488 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 4 Apr 2022 04:57:38 -0500 Subject: [PATCH 513/637] FDS: add FDS ins type auto-detection on .dmf --- src/engine/fileOps.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 976b2c32f..47975c426 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -623,6 +623,15 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { } // instrument pat->data[k][2]=reader.readS(); + + // this is sad + if (ds.system[0]==DIV_SYSTEM_NES_FDS) { + if (i==5 && pat->data[k][2]!=-1) { + if (pat->data[k][2]>=0 && pat->data[k][2]data[k][2]]->type=DIV_INS_FDS; + } + } + } } } } From f89360392f562f83f8f6dfb2ca6d4e1749c45602 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 4 Apr 2022 13:25:38 -0500 Subject: [PATCH 514/637] GUI: add setting to customize the title bar closes #339 --- src/gui/gui.cpp | 47 ++++++++++++++++++++++++++++++++++++++++---- src/gui/gui.h | 4 ++++ src/gui/settings.cpp | 30 ++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 4 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index f30c820fe..2102c99b1 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -544,14 +544,50 @@ void FurnaceGUI::setFileName(String name) { curFileName=ret; } #endif + updateWindowTitle(); } void FurnaceGUI::updateWindowTitle() { - if (e->song.name.empty()) { - SDL_SetWindowTitle(sdlWin,fmt::sprintf("Furnace (%s)",e->getSongSystemName()).c_str()); - } else { - SDL_SetWindowTitle(sdlWin,fmt::sprintf("%s - Furnace (%s)",e->song.name,e->getSongSystemName()).c_str()); + String title; + switch (settings.titleBarInfo) { + case 0: + title="Furnace"; + break; + case 1: + if (e->song.name.empty()) { + title="Furnace"; + } else { + title=fmt::sprintf("%s - Furnace",e->song.name); + } + break; + case 2: + if (curFileName.empty()) { + title="Furnace"; + } else { + String shortName; + size_t pos=curFileName.rfind(DIR_SEPARATOR); + if (pos==String::npos) { + shortName=curFileName; + } else { + shortName=curFileName.substr(pos+1); + } + title=fmt::sprintf("%s - Furnace",shortName); + } + break; + case 3: + if (curFileName.empty()) { + title="Furnace"; + } else { + title=fmt::sprintf("%s - Furnace",curFileName); + } + break; } + + if (settings.titleBarSys) { + title+=fmt::sprintf(" (%s)",e->getSongSystemName()); + } + + if (sdlWin!=NULL) SDL_SetWindowTitle(sdlWin,title.c_str()); } const char* defaultLayout="[Window][DockSpaceViewport_11111111]\n\ @@ -1502,6 +1538,7 @@ int FurnaceGUI::save(String path, int dmfVersion) { w->finish(); curFileName=path; modified=false; + updateWindowTitle(); if (!e->getWarnings().empty()) { showWarning(e->getWarnings(),GUI_WARN_GENERIC); } @@ -3064,6 +3101,8 @@ bool FurnaceGUI::finish() { FurnaceGUI::FurnaceGUI(): e(NULL), + sdlWin(NULL), + sdlRend(NULL), sampleTex(NULL), sampleTexW(0), sampleTexH(0), diff --git a/src/gui/gui.h b/src/gui/gui.h index abefee8bf..c42766f88 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -733,6 +733,8 @@ class FurnaceGUI { int susPosition; int effectCursorDir; int cursorPastePos; + int titleBarInfo; + int titleBarSys; unsigned int maxUndoSteps; String mainFontPath; String patFontPath; @@ -789,6 +791,8 @@ class FurnaceGUI { susPosition(0), effectCursorDir(1), cursorPastePos(1), + titleBarInfo(1), + titleBarSys(1), maxUndoSteps(100), mainFontPath(""), patFontPath(""), diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 78fc8eb06..60900987e 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -733,6 +733,30 @@ void FurnaceGUI::drawSettings() { ImGui::Separator(); + ImGui::Text("Title bar:"); + if (ImGui::RadioButton("Furnace##tbar0",settings.titleBarInfo==0)) { + settings.titleBarInfo=0; + updateWindowTitle(); + } + if (ImGui::RadioButton("Song Name - Furnace##tbar1",settings.titleBarInfo==1)) { + settings.titleBarInfo=1; + updateWindowTitle(); + } + if (ImGui::RadioButton("file_name.fur - Furnace##tbar2",settings.titleBarInfo==2)) { + settings.titleBarInfo=2; + updateWindowTitle(); + } + if (ImGui::RadioButton("/path/to/file.fur - Furnace##tbar3",settings.titleBarInfo==3)) { + settings.titleBarInfo=3; + updateWindowTitle(); + } + + bool titleBarSysB=settings.titleBarSys; + if (ImGui::Checkbox("Display system name on title bar",&titleBarSysB)) { + settings.titleBarSys=titleBarSysB; + updateWindowTitle(); + } + ImGui::Text("Status bar:"); if (ImGui::RadioButton("Cursor details##sbar0",settings.statusDisplay==0)) { settings.statusDisplay=0; @@ -1369,6 +1393,8 @@ void FurnaceGUI::syncSettings() { settings.susPosition=e->getConfInt("susPosition",0); settings.effectCursorDir=e->getConfInt("effectCursorDir",1); settings.cursorPastePos=e->getConfInt("cursorPastePos",1); + settings.titleBarInfo=e->getConfInt("titleBarInfo",1); + settings.titleBarSys=e->getConfInt("titleBarSys",1); clampSetting(settings.mainFontSize,2,96); clampSetting(settings.patFontSize,2,96); @@ -1417,6 +1443,8 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.susPosition,0,1); clampSetting(settings.effectCursorDir,0,1); clampSetting(settings.cursorPastePos,0,1); + clampSetting(settings.titleBarInfo,0,3); + clampSetting(settings.titleBarSys,0,1); // keybinds LOAD_KEYBIND(GUI_ACTION_OPEN,FURKMOD_CMD|SDLK_o); @@ -1662,6 +1690,8 @@ void FurnaceGUI::commitSettings() { e->setConf("susPosition",settings.susPosition); e->setConf("effectCursorDir",settings.effectCursorDir); e->setConf("cursorPastePos",settings.cursorPastePos); + e->setConf("titleBarInfo",settings.titleBarInfo); + e->setConf("titleBarSys",settings.titleBarSys); PUT_UI_COLOR(GUI_COLOR_BACKGROUND); PUT_UI_COLOR(GUI_COLOR_FRAME_BACKGROUND); From 590b21bc3754deb5d9e924e9cf03c084211d18a5 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 4 Apr 2022 13:26:02 -0500 Subject: [PATCH 515/637] GUI: prepare for FDS ins editor --- src/gui/insEdit.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 27ba597f9..d3db8ff9f 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -2115,6 +2115,10 @@ void FurnaceGUI::drawInsEdit() { ImGui::EndTabItem(); } + if (ins->type==DIV_INS_FDS) if (ImGui::BeginTabItem("FDS")) { + ImGui::Text("FDS config goes here"); + ImGui::EndTabItem(); + } if (ImGui::BeginTabItem("Macros")) { float asFloat[256]; int asInt[256]; From 3a7a132f0210c047de1825b33d68e3542145b0a1 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 4 Apr 2022 14:04:36 -0500 Subject: [PATCH 516/637] harden Furnace file loader issue #325 --- src/engine/fileOps.cpp | 136 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 126 insertions(+), 10 deletions(-) diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 47975c426..b612a92b5 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -777,9 +777,9 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { } bool DivEngine::loadFur(unsigned char* file, size_t len) { - int insPtr[256]; - int wavePtr[256]; - int samplePtr[256]; + unsigned int insPtr[256]; + unsigned int wavePtr[256]; + unsigned int samplePtr[256]; std::vector patPtr; char magic[5]; memset(magic,0,5); @@ -860,7 +860,12 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { reader.readS(); // reserved int infoSeek=reader.readI(); - reader.seek(infoSeek,SEEK_SET); + if (!reader.seek(infoSeek,SEEK_SET)) { + logE("couldn't seek to info header at %d!\n",infoSeek); + lastError="couldn't seek to info header!"; + delete[] file; + return false; + } // read header reader.read(magic,4); @@ -891,15 +896,74 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { ds.sampleLen=reader.readS(); int numberOfPats=reader.readI(); + if (ds.patLen<0) { + logE("pattern length is negative!\n"); + lastError="pattern lengrh is negative!"; + delete[] file; + return false; + } + if (ds.patLen>256) { + logE("pattern length is too large!\n"); + lastError="pattern length is too large!"; + delete[] file; + return false; + } + if (ds.ordersLen<0) { + logE("song length is negative!\n"); + lastError="song length is negative!"; + delete[] file; + return false; + } + if (ds.ordersLen>127) { + logE("song is too long!\n"); + lastError="song is too long!"; + delete[] file; + return false; + } + if (ds.insLen<0 || ds.insLen>256) { + logE("invalid instrument count!\n"); + lastError="invalid instrument count!"; + delete[] file; + return false; + } + if (ds.waveLen<0 || ds.waveLen>256) { + logE("invalid wavetable count!\n"); + lastError="invalid wavetable count!"; + delete[] file; + return false; + } + if (ds.sampleLen<0 || ds.sampleLen>256) { + logE("invalid sample count!\n"); + lastError="invalid sample count!"; + delete[] file; + return false; + } + if (numberOfPats<0) { + logE("invalid pattern count!\n"); + lastError="invalid pattern count!"; + delete[] file; + return false; + } + for (int i=0; i<32; i++) { - ds.system[i]=systemFromFileFur(reader.readC()); + unsigned char sysID=reader.readC(); + ds.system[i]=systemFromFileFur(sysID); + if (sysID!=0 && systemToFileFur(ds.system[i])==0) { + logE("unrecognized system ID %.2x\n",ds.system[i]); + lastError=fmt::sprintf("unrecognized system ID %.2x!",ds.system[i]); + delete[] file; + return false; + } if (ds.system[i]!=DIV_SYSTEM_NULL) ds.systemLen=i+1; } int tchans=0; for (int i=0; iDIV_MAX_CHANS) tchans=DIV_MAX_CHANS; + if (tchans>DIV_MAX_CHANS) { + tchans=DIV_MAX_CHANS; + logW("too many channels!\n"); + } // system volume for (int i=0; i<32; i++) { @@ -1070,6 +1134,12 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { for (int i=0; i8) { + logE("channel %d has too many effect columns! (%d)\n",i,ds.pat[i].effectRows); + lastError=fmt::sprintf("channel %d has too many effect columns! (%d)",i,ds.pat[i].effectRows); + delete[] file; + return false; + } } if (ds.version>=39) { @@ -1126,10 +1196,18 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { for (int i=0; ireadInsData(reader,ds.version)!=DIV_DATA_SUCCESS) { lastError="invalid instrument header/data!"; + ds.unload(); delete ins; delete[] file; return false; @@ -1142,10 +1220,18 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { for (int i=0; ireadWaveData(reader,ds.version)!=DIV_DATA_SUCCESS) { lastError="invalid wavetable header/data!"; + ds.unload(); delete wave; delete[] file; return false; @@ -1159,11 +1245,19 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { int vol=0; int pitch=0; - reader.seek(samplePtr[i],SEEK_SET); + if (!reader.seek(samplePtr[i],SEEK_SET)) { + logE("couldn't seek to sample %d!\n",i); + lastError=fmt::sprintf("couldn't seek to sample %d!",i); + ds.unload(); + delete[] file; + return false; + } + reader.read(magic,4); if (strcmp(magic,"SMPL")!=0) { logE("%d: invalid sample header!\n",i); lastError="invalid sample header!"; + ds.unload(); delete[] file; return false; } @@ -1241,12 +1335,19 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { // read patterns for (int i: patPtr) { - reader.seek(i,SEEK_SET); + if (!reader.seek(i,SEEK_SET)) { + logE("couldn't seek to pattern in %x!\n",i); + lastError=fmt::sprintf("couldn't seek to pattern in %x!",i); + ds.unload(); + delete[] file; + return false; + } reader.read(magic,4); logD("reading pattern in %x...\n",i); if (strcmp(magic,"PATR")!=0) { logE("%x: invalid pattern header!\n",i); lastError="invalid pattern header!"; + ds.unload(); delete[] file; return false; } @@ -1256,6 +1357,21 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { int index=reader.readS(); reader.readI(); + if (chan<0 || chan>=tchans) { + logE("pattern channel out of range!\n",i); + lastError="pattern channel out of range!"; + ds.unload(); + delete[] file; + return false; + } + if (index<0 || index>127) { + logE("pattern index out of range!\n",i); + lastError="pattern index out of range!"; + ds.unload(); + delete[] file; + return false; + } + logD("- %d, %d\n",chan,index); DivPattern* pat=ds.pat[chan].getPattern(index,true); From 258a905aaa6fd652bf351f30446a135fc4f1e164 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 4 Apr 2022 14:15:26 -0500 Subject: [PATCH 517/637] harden .dmf loader issue #325 --- src/engine/fileOps.cpp | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index b612a92b5..6b968b5c2 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -198,6 +198,31 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { } ds.ordersLen=(unsigned char)reader.readC(); + if (ds.patLen<0) { + logE("pattern length is negative!\n"); + lastError="pattern lengrh is negative!"; + delete[] file; + return false; + } + if (ds.patLen>256) { + logE("pattern length is too large!\n"); + lastError="pattern length is too large!"; + delete[] file; + return false; + } + if (ds.ordersLen<0) { + logE("song length is negative!\n"); + lastError="song length is negative!"; + delete[] file; + return false; + } + if (ds.ordersLen>127) { + logE("song is too long!\n"); + lastError="song is too long!"; + delete[] file; + return false; + } + if (ds.version<20 && ds.version>3) { ds.arpLen=reader.readC(); } else { @@ -237,6 +262,12 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { for (int i=0; i0x7f) { + logE("order at %d, %d out of range! (%d)\n",i,j,ds.orders.ord[i][j]); + lastError=fmt::sprintf("order at %d, %d out of range! (%d)",i,j,ds.orders.ord[i][j]); + delete[] file; + return false; + } if (ds.version>0x18) { // 1.1 pattern names ds.pat[i].getPattern(j,true)->name=reader.readString((unsigned char)reader.readC()); } @@ -557,6 +588,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { chan.effectRows=1; } else { chan.effectRows=reader.readC(); + } logD("%d fx rows: %d\n",i,chan.effectRows); if (chan.effectRows>4 || chan.effectRows<1) { @@ -1134,8 +1166,8 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { for (int i=0; i8) { - logE("channel %d has too many effect columns! (%d)\n",i,ds.pat[i].effectRows); + if (ds.pat[i].effectRows<1 || ds.pat[i].effectRows>8) { + logE("channel %d has zero or too many effect columns! (%d)\n",i,ds.pat[i].effectRows); lastError=fmt::sprintf("channel %d has too many effect columns! (%d)",i,ds.pat[i].effectRows); delete[] file; return false; From 5f718574395d94a3b2444b1462a41c0ccb1d7671 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 4 Apr 2022 23:38:38 -0500 Subject: [PATCH 518/637] GUI: attempt at optimizing pattern draw code --- src/gui/gui.h | 4 +++- src/gui/pattern.cpp | 38 ++++++++++++++++++++++++++++++-------- src/gui/settings.cpp | 6 ++++++ 3 files changed, 39 insertions(+), 9 deletions(-) diff --git a/src/gui/gui.h b/src/gui/gui.h index c42766f88..84e2ef1b0 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -133,6 +133,8 @@ enum FurnaceGUIColors { GUI_COLOR_PATTERN_ACTIVE, GUI_COLOR_PATTERN_INACTIVE, GUI_COLOR_PATTERN_INS, + GUI_COLOR_PATTERN_INS_WARN, + GUI_COLOR_PATTERN_INS_ERROR, GUI_COLOR_PATTERN_VOLUME_MAX, GUI_COLOR_PATTERN_VOLUME_HALF, GUI_COLOR_PATTERN_VOLUME_MIN, @@ -970,7 +972,7 @@ class FurnaceGUI { float calcBPM(int s1, int s2, float hz); - void patternRow(int i, bool isPlaying, float lineHeight, int chans, int ord); + void patternRow(int i, bool isPlaying, float lineHeight, int chans, int ord, const DivPattern** patCache); void actualWaveList(); void actualSampleList(); diff --git a/src/gui/pattern.cpp b/src/gui/pattern.cpp index 4b84ab947..ef9af465d 100644 --- a/src/gui/pattern.cpp +++ b/src/gui/pattern.cpp @@ -17,9 +17,11 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#include #include #define _USE_MATH_DEFINES #include "gui.h" +#include "../ta-log.h" #include "imgui_internal.h" #include "IconsFontAwesome4.h" #include "misc/cpp/imgui_stdlib.h" @@ -85,7 +87,7 @@ inline float randRange(float min, float max) { } // draw a pattern row -inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int chans, int ord) { +inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int chans, int ord, const DivPattern** patCache) { static char id[32]; bool selectedRow=(i>=sel1.y && i<=sel2.y); ImGui::TableNextRow(0,lineHeight); @@ -143,7 +145,7 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int } int chanVolMax=e->getMaxVolumeChan(j); if (chanVolMax<1) chanVolMax=1; - DivPattern* pat=e->song.pat[j].getPattern(e->song.orders.ord[j][ord],true); + const DivPattern* pat=patCache[j]; ImGui::TableNextColumn(); patChanX[j]=ImGui::GetCursorPosX(); @@ -158,8 +160,6 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int bool cursorIns=(cursor.y==i && cursor.xCoarse==j && cursor.xFine==1); bool cursorVol=(cursor.y==i && cursor.xCoarse==j && cursor.xFine==2); - - // note sprintf(id,"%s##PN_%d_%d",noteName(pat->data[i][0],pat->data[i][1]),i,j); if (pat->data[i][0]==0 && pat->data[i][1]==0) { @@ -194,7 +194,16 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_INACTIVE]); sprintf(id,"..##PI_%d_%d",i,j); } else { - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_INS]); + if (pat->data[i][2]<0 || pat->data[i][2]>=e->song.insLen) { + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_INS_ERROR]); + } else { + DivInstrumentType t=e->song.ins[pat->data[i][2]]->type; + if (t!=DIV_INS_AMIGA && t!=e->getPreferInsType(j)) { + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_INS_WARN]); + } else { + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_INS]); + } + } sprintf(id,"%.2X##PI_%d_%d",pat->data[i][2],i,j); } ImGui::SameLine(0.0f,0.0f); @@ -343,6 +352,7 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int } void FurnaceGUI::drawPattern() { + //int delta0=SDL_GetPerformanceCounter(); if (nextWindow==GUI_WINDOW_PATTERN) { patternOpen=true; ImGui::SetNextWindowFocus(); @@ -386,6 +396,7 @@ void FurnaceGUI::drawPattern() { oldOrder=e->getOrder(); int chans=e->getTotalChannelCount(); int displayChans=0; + const DivPattern* patCache[DIV_MAX_CHANS]; for (int i=0; isong.chanShow[i]) displayChans++; } @@ -571,8 +582,11 @@ void FurnaceGUI::drawPattern() { // previous pattern ImGui::BeginDisabled(); if (settings.viewPrevPattern) { + if ((ord-1)>=0) for (int i=0; isong.pat[i].getPattern(e->song.orders.ord[i][ord-1],true); + } for (int i=0; isong.patLen+i-dummyRows+1,e->isPlaying(),lineHeight,chans,ord-1); + patternRow(e->song.patLen+i-dummyRows+1,e->isPlaying(),lineHeight,chans,ord-1,patCache); } } else { for (int i=0; isong.pat[i].getPattern(e->song.orders.ord[i][ord],true); + } for (int i=0; isong.patLen; i++) { - patternRow(i,e->isPlaying(),lineHeight,chans,ord); + patternRow(i,e->isPlaying(),lineHeight,chans,ord,patCache); } // next pattern ImGui::BeginDisabled(); if (settings.viewPrevPattern) { + if ((ord+1)song.ordersLen) for (int i=0; isong.pat[i].getPattern(e->song.orders.ord[i][ord+1],true); + } for (int i=0; i<=dummyRows; i++) { - patternRow(i,e->isPlaying(),lineHeight,chans,ord+1); + patternRow(i,e->isPlaying(),lineHeight,chans,ord+1,patCache); } } else { for (int i=0; i<=dummyRows; i++) { @@ -837,5 +857,7 @@ void FurnaceGUI::drawPattern() { } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_PATTERN; ImGui::End(); + //int delta1=SDL_GetPerformanceCounter(); + //logV("render time: %dµs\n",(delta1-delta0)/(SDL_GetPerformanceFrequency()/1000000)); } diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 60900987e..04555b27c 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -980,6 +980,8 @@ void FurnaceGUI::drawSettings() { UI_COLOR_CONFIG(GUI_COLOR_PATTERN_ACTIVE,"Note"); UI_COLOR_CONFIG(GUI_COLOR_PATTERN_INACTIVE,"Blank"); UI_COLOR_CONFIG(GUI_COLOR_PATTERN_INS,"Instrument"); + UI_COLOR_CONFIG(GUI_COLOR_PATTERN_INS_WARN,"Instrument (invalid type)"); + UI_COLOR_CONFIG(GUI_COLOR_PATTERN_INS_ERROR,"Instrument (out of range)"); UI_COLOR_CONFIG(GUI_COLOR_PATTERN_VOLUME_MIN,"Volume (0%)"); UI_COLOR_CONFIG(GUI_COLOR_PATTERN_VOLUME_HALF,"Volume (50%)"); UI_COLOR_CONFIG(GUI_COLOR_PATTERN_VOLUME_MAX,"Volume (100%)"); @@ -1772,6 +1774,8 @@ void FurnaceGUI::commitSettings() { PUT_UI_COLOR(GUI_COLOR_PATTERN_ACTIVE); PUT_UI_COLOR(GUI_COLOR_PATTERN_INACTIVE); PUT_UI_COLOR(GUI_COLOR_PATTERN_INS); + PUT_UI_COLOR(GUI_COLOR_PATTERN_INS_WARN); + PUT_UI_COLOR(GUI_COLOR_PATTERN_INS_ERROR); PUT_UI_COLOR(GUI_COLOR_PATTERN_VOLUME_MIN); PUT_UI_COLOR(GUI_COLOR_PATTERN_VOLUME_HALF); PUT_UI_COLOR(GUI_COLOR_PATTERN_VOLUME_MAX); @@ -2174,6 +2178,8 @@ void FurnaceGUI::applyUISettings() { GET_UI_COLOR(GUI_COLOR_PATTERN_ACTIVE,ImVec4(1.0f,1.0f,1.0f,1.0f)); GET_UI_COLOR(GUI_COLOR_PATTERN_INACTIVE,ImVec4(0.5f,0.5f,0.5f,1.0f)); GET_UI_COLOR(GUI_COLOR_PATTERN_INS,ImVec4(0.4f,0.7f,1.0f,1.0f)); + GET_UI_COLOR(GUI_COLOR_PATTERN_INS_WARN,ImVec4(1.0f,1.0f,0.1f,1.0f)); + GET_UI_COLOR(GUI_COLOR_PATTERN_INS_ERROR,ImVec4(1.0f,0.1f,0.1f,1.0f)); GET_UI_COLOR(GUI_COLOR_PATTERN_VOLUME_MIN,ImVec4(0.0f,0.5f,0.0f,1.0f)); GET_UI_COLOR(GUI_COLOR_PATTERN_VOLUME_HALF,ImVec4(0.0f,0.75f,0.0f,1.0f)); GET_UI_COLOR(GUI_COLOR_PATTERN_VOLUME_MAX,ImVec4(0.0f,1.0f,0.0f,1.0f)); From e1976b96a06a308730895b8a5cca1ca2377266d2 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 5 Apr 2022 14:55:17 -0500 Subject: [PATCH 519/637] TODO: modulation table --- src/gui/insEdit.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index d3db8ff9f..d626a6b75 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -2117,6 +2117,16 @@ void FurnaceGUI::drawInsEdit() { } if (ins->type==DIV_INS_FDS) if (ImGui::BeginTabItem("FDS")) { ImGui::Text("FDS config goes here"); + ImGui::Checkbox("Initialize modulation table with first wavetable (compatibility)",&ins->fds.initModTableWithFirstWave); + if (ImGui::InputInt("Modulation depth",&ins->fds.modDepth,1,32)) { + if (ins->fds.modDepth<0) ins->fds.modDepth=0; + if (ins->fds.modDepth>63) ins->fds.modDepth=63; + } + if (ImGui::InputInt("Modulation speed",&ins->fds.modSpeed,1,4)) { + if (ins->fds.modSpeed<0) ins->fds.modSpeed=0; + if (ins->fds.modSpeed>4095) ins->fds.modSpeed=4095; + } + ImGui::Text("Modulation table"); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Macros")) { @@ -2198,7 +2208,7 @@ void FurnaceGUI::drawInsEdit() { dutyLabel="Noise"; dutyMax=8; } - if (ins->type==DIV_INS_OPLL || ins->type==DIV_INS_OPL || ins->type==DIV_INS_VRC6_SAW) { + if (ins->type==DIV_INS_OPLL || ins->type==DIV_INS_OPL || ins->type==DIV_INS_VRC6_SAW || ins->type==DIV_INS_FDS) { dutyMax=0; } if (ins->type==DIV_INS_VERA) { From 4ba50b433ae471a7c2e4481c72fe8ab0b63b51a2 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 5 Apr 2022 15:14:48 -0500 Subject: [PATCH 520/637] FDS: .dmf wavetables will be 6-bit soon --- src/engine/fileOps.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 6b968b5c2..1bf73bcce 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -560,9 +560,12 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { for (int i=0; ilen=(unsigned char)reader.readI(); - if (ds.system[0]==DIV_SYSTEM_GB || ds.system[0]==DIV_SYSTEM_NES_FDS) { + if (ds.system[0]==DIV_SYSTEM_GB) { wave->max=15; } + if (ds.system[0]==DIV_SYSTEM_NES_FDS) { + wave->max=63; + } if (wave->len>65) { logE("invalid wave length %d. are we doing something wrong?\n",wave->len); lastError="file is corrupt or unreadable at wavetables"; From 280cbb3e393b1ab970c2880f9230cf2720747f05 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Tue, 5 Apr 2022 18:18:14 -0500 Subject: [PATCH 521/637] FDS: possibly final work the last thing left to do is the filter, but everything works now --- papers/doc/7-systems/fds.md | 26 ++++++++++++++++ src/engine/platform/fds.cpp | 60 +++++++++++++++++++++++++++++++++++-- src/gui/insEdit.cpp | 38 +++++++++++++++++++++-- src/gui/plot_nolerp.cpp | 4 ++- 4 files changed, 122 insertions(+), 6 deletions(-) create mode 100644 papers/doc/7-systems/fds.md diff --git a/papers/doc/7-systems/fds.md b/papers/doc/7-systems/fds.md new file mode 100644 index 000000000..ee2881981 --- /dev/null +++ b/papers/doc/7-systems/fds.md @@ -0,0 +1,26 @@ +# Famicom Disk System + +the Famicom Disk System is an expansion device for the Famicom (known as NES outside Japan), a popular console from the '80's. +as it name implies, it allowed people to play games on specialized floppy disks that could be rewritten on vending machines, therefore reducing the cost of ownership and manufacturing. + +it also offers an additional wavetable sound channel with (somewhat limited) FM capabilities, which is what Furnace supports. + +# effects + +- `10xx`: change wave. +- `11xx`: set modulation depth. +- `12xy`: set modulation speed high byte and toggle on/off. + - `x` is the toggle. a value of 1 turns on the modulator. + - `y` is the speed. +- `13xx`: set modulation speed low byte. +- `14xx`: set modulator position. +- `15xx`: set modulator wave. + - `xx` points to a wavetable. it should (preferably) have a height of 7 with the values mapping to: + - 0: +0 + - 1: +1 + - 2: +2 + - 3: +3 + - 4: reset + - 5: -3 + - 6: -2 + - 7: -1 diff --git a/src/engine/platform/fds.cpp b/src/engine/platform/fds.cpp index b4103748b..ede447df8 100644 --- a/src/engine/platform/fds.cpp +++ b/src/engine/platform/fds.cpp @@ -63,10 +63,10 @@ const char* DivPlatformFDS::getEffectName(unsigned char effect) { return "11xx: Set modulation depth"; break; case 0x12: - return "12xy: Set modulation frequency high byte (x: enable; y: value)"; + return "12xy: Set modulation speed high byte (x: enable; y: value)"; break; case 0x13: - return "13xx: Set modulation frequency low byte"; + return "13xx: Set modulation speed low byte"; break; case 0x14: return "14xx: Set modulator position"; @@ -163,6 +163,22 @@ void DivPlatformFDS::tick() { //if (!chan[i].keyOff) chan[i].keyOn=true; } } + if (chan[i].std.hadEx1) { // mod depth + chan[i].modOn=chan[i].std.ex1; + chan[i].modDepth=chan[i].std.ex1; + rWrite(0x4084,(chan[i].modOn<<7)|0x40|chan[i].modDepth); + } + if (chan[i].std.hadEx2) { // mod speed + chan[i].modFreq=chan[i].std.ex2; + rWrite(0x4086,chan[i].modFreq&0xff); + rWrite(0x4087,chan[i].modFreq>>8); + } + if (chan[i].std.hadEx3) { // mod position + chan[i].modPos=chan[i].std.ex3; + rWrite(0x4087,0x80|chan[i].modFreq>>8); + rWrite(0x4085,chan[i].modPos); + rWrite(0x4087,chan[i].modFreq>>8); + } if (chan[i].sweepChanged) { chan[i].sweepChanged=false; if (i==0) { @@ -199,6 +215,42 @@ int DivPlatformFDS::dispatch(DivCommand c) { chan[c.chan].freqChanged=true; chan[c.chan].note=c.value; } + if (chan[c.chan].insChanged) { + DivInstrument* ins=parent->getIns(chan[c.chan].ins); + if (ins->fds.initModTableWithFirstWave) { // compatible + if (chan[c.chan].wave==-1) { + DivWavetable* wt=parent->getWave(0); + for (int i=0; i<32; i++) { + if (wt->max<1 || wt->len<1) { + rWrite(0x4040+i,0); + } else { + int data=wt->data[i*MIN(32,wt->len)/32]*7/wt->max; + if (data<0) data=0; + if (data>7) data=7; + chan[c.chan].modTable[i]=data; + } + } + rWrite(0x4087,0x80|chan[c.chan].modFreq>>8); + for (int i=0; i<32; i++) { + rWrite(0x4088,chan[c.chan].modTable[i]); + } + rWrite(0x4087,chan[c.chan].modFreq>>8); + } + } else { // The Familiar Way + chan[c.chan].modDepth=ins->fds.modDepth; + chan[c.chan].modOn=ins->fds.modDepth; + chan[c.chan].modFreq=ins->fds.modSpeed; + rWrite(0x4084,(chan[c.chan].modOn<<7)|0x40|chan[c.chan].modDepth); + rWrite(0x4086,chan[c.chan].modFreq&0xff); + + rWrite(0x4087,0x80|chan[c.chan].modFreq>>8); + for (int i=0; i<32; i++) { + chan[c.chan].modTable[i]=ins->fds.modTable[i]&7; + rWrite(0x4088,chan[c.chan].modTable[i]); + } + rWrite(0x4087,chan[c.chan].modFreq>>8); + } + } chan[c.chan].active=true; chan[c.chan].keyOn=true; chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); @@ -256,7 +308,9 @@ int DivPlatformFDS::dispatch(DivCommand c) { break; case DIV_CMD_FDS_MOD_POS: chan[c.chan].modPos=c.value&0x7f; - // TODO + rWrite(0x4087,0x80|chan[c.chan].modFreq>>8); + rWrite(0x4085,chan[c.chan].modPos); + rWrite(0x4087,chan[c.chan].modFreq>>8); break; case DIV_CMD_FDS_MOD_WAVE: { DivWavetable* wt=parent->getWave(c.value); diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index d626a6b75..8ddbcbe34 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -2116,8 +2116,11 @@ void FurnaceGUI::drawInsEdit() { ImGui::EndTabItem(); } if (ins->type==DIV_INS_FDS) if (ImGui::BeginTabItem("FDS")) { - ImGui::Text("FDS config goes here"); - ImGui::Checkbox("Initialize modulation table with first wavetable (compatibility)",&ins->fds.initModTableWithFirstWave); + float modTable[32]; + ImGui::Checkbox("Compatibility mode",&ins->fds.initModTableWithFirstWave); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("only use for compatibility with .dmf modules!\n- initializes modulation table with first wavetable\n- does not alter modulation parameters on instrument change"); + } if (ImGui::InputInt("Modulation depth",&ins->fds.modDepth,1,32)) { if (ins->fds.modDepth<0) ins->fds.modDepth=0; if (ins->fds.modDepth>63) ins->fds.modDepth=63; @@ -2127,6 +2130,26 @@ void FurnaceGUI::drawInsEdit() { if (ins->fds.modSpeed>4095) ins->fds.modSpeed=4095; } ImGui::Text("Modulation table"); + for (int i=0; i<32; i++) { + modTable[i]=ins->fds.modTable[i]; + } + ImVec2 modTableSize=ImVec2(ImGui::GetContentRegionAvail().x,96.0f*dpiScale); + PlotCustom("ModTable",modTable,32,0,NULL,-4,3,modTableSize,sizeof(float),ImVec4(1.0f,1.0f,1.0f,1.0f),0,NULL,true); + if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { + macroDragStart=ImGui::GetItemRectMin(); + macroDragAreaSize=modTableSize; + macroDragMin=-4; + macroDragMax=3; + macroDragBitOff=0; + macroDragBitMode=false; + macroDragInitialValueSet=false; + macroDragInitialValue=false; + macroDragLen=32; + macroDragActive=true; + macroDragCTarget=(unsigned char*)ins->fds.modTable; + macroDragChar=true; + processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); \ + } ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Macros")) { @@ -2268,6 +2291,10 @@ void FurnaceGUI::drawInsEdit() { ex1Max=252; ex2Max=2; } + if (ins->type==DIV_INS_FDS) { + ex1Max=63; + ex2Max=4095; + } if (ins->type==DIV_INS_SAA1099) ex1Max=8; if (settings.macroView==0) { // modern view @@ -2296,6 +2323,8 @@ void FurnaceGUI::drawInsEdit() { NORMAL_MACRO(ins->std.ex1Macro,ins->std.ex1MacroLen,ins->std.ex1MacroLoop,ins->std.ex1MacroRel,0,ex1Max,"ex1","Envelope Mode",160,ins->std.ex1MacroOpen,true,x1_010EnvBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false); } else if (ins->type==DIV_INS_N163) { NORMAL_MACRO(ins->std.ex1Macro,ins->std.ex1MacroLen,ins->std.ex1MacroLoop,ins->std.ex1MacroRel,0,ex1Max,"ex1","Waveform len.",160,ins->std.ex1MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false); + } else if (ins->type==DIV_INS_FDS) { + NORMAL_MACRO(ins->std.ex1Macro,ins->std.ex1MacroLen,ins->std.ex1MacroLoop,ins->std.ex1MacroRel,0,ex1Max,"ex1","Mod Depth",160,ins->std.ex1MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false); } else { NORMAL_MACRO(ins->std.ex1Macro,ins->std.ex1MacroLen,ins->std.ex1MacroLoop,ins->std.ex1MacroRel,0,ex1Max,"ex1","Duty",160,ins->std.ex1MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false); } @@ -2305,6 +2334,8 @@ void FurnaceGUI::drawInsEdit() { NORMAL_MACRO(ins->std.ex2Macro,ins->std.ex2MacroLen,ins->std.ex2MacroLoop,ins->std.ex2MacroRel,0,ex2Max,"ex2","Resonance",64,ins->std.ex2MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[5],0,ex2Max,NULL,false); } else if (ins->type==DIV_INS_N163) { NORMAL_MACRO(ins->std.ex2Macro,ins->std.ex2MacroLen,ins->std.ex2MacroLoop,ins->std.ex2MacroRel,0,ex2Max,"ex2","Waveform update",64,ins->std.ex2MacroOpen,true,n163UpdateBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[5],0,ex2Max,NULL,false); + } else if (ins->type==DIV_INS_FDS) { + NORMAL_MACRO(ins->std.ex2Macro,ins->std.ex2MacroLen,ins->std.ex2MacroLoop,ins->std.ex2MacroRel,0,ex2Max,"ex2","Mod Speed",160,ins->std.ex2MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[5],0,ex2Max,NULL,false); } else { NORMAL_MACRO(ins->std.ex2Macro,ins->std.ex2MacroLen,ins->std.ex2MacroLoop,ins->std.ex2MacroRel,0,ex2Max,"ex2","Envelope",ex2Bit?64:160,ins->std.ex2MacroOpen,ex2Bit,ayEnvBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[5],0,ex2Max,NULL,false); } @@ -2327,6 +2358,9 @@ void FurnaceGUI::drawInsEdit() { NORMAL_MACRO(ins->std.fbMacro,ins->std.fbMacroLen,ins->std.fbMacroLoop,ins->std.fbMacroRel,0,252,"fb","Wave len. to Load",160,ins->std.fbMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[8],0,252,NULL,false); NORMAL_MACRO(ins->std.fmsMacro,ins->std.fmsMacroLen,ins->std.fmsMacroLoop,ins->std.fmsMacroRel,0,2,"fms","Waveform load",64,ins->std.fmsMacroOpen,true,n163UpdateBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[9],0,2,NULL,false); } + if (ins->type==DIV_INS_FDS) { + NORMAL_MACRO(ins->std.ex3Macro,ins->std.ex3MacroLen,ins->std.ex3MacroLoop,ins->std.ex3MacroRel,0,127,"ex3","Mod Position",160,ins->std.ex3MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[6],0,2,NULL,false); + } MACRO_END; } else { // classic view diff --git a/src/gui/plot_nolerp.cpp b/src/gui/plot_nolerp.cpp index 1c1cf94ad..c8970c953 100644 --- a/src/gui/plot_nolerp.cpp +++ b/src/gui/plot_nolerp.cpp @@ -332,6 +332,8 @@ int PlotCustomEx(ImGuiPlotType plot_type, const char* label, float (*values_gett scale_max = v_max; } + if (blockMode) scale_max+=1.0f; + ImU32 bgColor=ImGui::GetColorU32(ImVec4(color.x,color.y,color.z,color.w*0.15)); ImGui::RenderFrame(frame_bb.Min, frame_bb.Max, ImGui::GetColorU32(ImGuiCol_FrameBg), true, style.FrameRounding); @@ -372,7 +374,7 @@ int PlotCustomEx(ImGuiPlotType plot_type, const char* label, float (*values_gett float v0 = values_getter(data, (0) % values_count); float t0 = 0.0f; ImVec2 tp0 = ImVec2( t0, 1.0f - ImSaturate((v0 - scale_min) * inv_scale) ); // Point in the normalized space of our target rectangle - float histogram_zero_line_t = (scale_min * scale_max < 0.0f) ? (1 + scale_min * inv_scale) : (scale_min < 0.0f ? 0.0f : 1.0f); // Where does the zero line stands + float histogram_zero_line_t = (scale_min * scale_max < 0.0f) ? (1 + (blockMode?(scale_min-0.5):scale_min) * inv_scale) : (scale_min < 0.0f ? 0.0f : 1.0f); // Where does the zero line stands const ImU32 col_base = ImGui::GetColorU32(color); const ImU32 col_hovered = ImGui::GetColorU32(color); From 2da96a7e769d8d77a64468ed165bf77107701208 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 6 Apr 2022 00:34:12 -0500 Subject: [PATCH 522/637] initial MMC5 bring-up --- CMakeLists.txt | 2 + src/engine/dispatchContainer.cpp | 4 + src/engine/platform/mmc5.cpp | 411 +++++++++++++++++++++ src/engine/platform/mmc5.h | 88 +++++ src/engine/platform/sound/nes/apu.c | 6 +- src/engine/platform/sound/nes/apu.h | 16 +- src/engine/platform/sound/nes/cpu_inline.h | 4 +- src/engine/platform/sound/nes/mmc5.c | 107 ++++++ src/engine/platform/sound/nes/mmc5.h | 80 ++++ 9 files changed, 707 insertions(+), 11 deletions(-) create mode 100644 src/engine/platform/mmc5.cpp create mode 100644 src/engine/platform/mmc5.h create mode 100644 src/engine/platform/sound/nes/mmc5.c create mode 100644 src/engine/platform/sound/nes/mmc5.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 353ca9e97..7648cb588 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -237,6 +237,7 @@ src/engine/platform/sound/gb/timing.c src/engine/platform/sound/pce_psg.cpp src/engine/platform/sound/nes/apu.c src/engine/platform/sound/nes/fds.c +src/engine/platform/sound/nes/mmc5.c src/engine/platform/sound/vera_psg.c src/engine/platform/sound/vera_pcm.c @@ -309,6 +310,7 @@ src/engine/platform/sms.cpp src/engine/platform/opll.cpp src/engine/platform/gb.cpp src/engine/platform/pce.cpp +src/engine/platform/mmc5.cpp src/engine/platform/nes.cpp src/engine/platform/c64.cpp src/engine/platform/arcade.cpp diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 2658c426c..bbcf3073d 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -51,6 +51,7 @@ #include "platform/vic20.h" #include "platform/vrc6.h" #include "platform/fds.h" +#include "platform/mmc5.h" #include "platform/dummy.h" #include "../ta-log.h" #include "song.h" @@ -295,6 +296,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_VRC6: dispatch=new DivPlatformVRC6; break; + case DIV_SYSTEM_MMC5: + dispatch=new DivPlatformMMC5; + break; default: logW("this system is not supported yet! using dummy platform.\n"); dispatch=new DivPlatformDummy; diff --git a/src/engine/platform/mmc5.cpp b/src/engine/platform/mmc5.cpp new file mode 100644 index 000000000..3ccf95499 --- /dev/null +++ b/src/engine/platform/mmc5.cpp @@ -0,0 +1,411 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "mmc5.h" +#include "sound/nes/mmc5.h" +#include "../engine.h" +#include + +#define CHIP_DIVIDER 16 + +#define rWrite(a,v) if (!skipRegisterWrites) {extcl_cpu_wr_mem_MMC5(mmc5,a,v); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} } + +const char* regCheatSheetMMC5[]={ + "S0Volume", "5000", + "S0PeriodL", "5002", + "S0PeriodH", "5003", + "S1Volume", "5004", + "S1PeriodL", "5006", + "S1PeriodH", "5007", + "PCMControl", "4010", + "PCMWrite", "4011", + "APUControl", "4015", + NULL +}; + +const char** DivPlatformMMC5::getRegisterSheet() { + return regCheatSheetMMC5; +} + +const char* DivPlatformMMC5::getEffectName(unsigned char effect) { + switch (effect) { + case 0x12: + return "12xx: Set duty cycle/noise mode (pulse: 0 to 3; noise: 0 or 1)"; + break; + } + return NULL; +} + +void DivPlatformMMC5::acquire(short* bufL, short* bufR, size_t start, size_t len) { + for (size_t i=start; i=rate) { + DivSample* s=parent->getSample(dacSample); + if (s->samples>0) { + if (!isMuted[4]) { + rWrite(0x5011,((unsigned char)s->data8[dacPos]+0x80)); + } + if (++dacPos>=s->samples) { + if (s->loopStart>=0 && s->loopStart<(int)s->samples) { + dacPos=s->loopStart; + } else { + dacSample=-1; + } + } + dacPeriod-=rate; + } else { + dacSample=-1; + } + } + } + + extcl_envelope_clock_MMC5(mmc5); + extcl_length_clock_MMC5(mmc5); + extcl_apu_tick_MMC5(mmc5); + if (mmc5->clocked) { + mmc5->clocked=false; + } + int sample=isMuted[0]?0:(mmc5->S3.output*10); + if (!isMuted[1]) { + sample+=mmc5->S4.output*10; + } + if (!isMuted[2]) { + sample+=mmc5->pcm.output*2; + } + sample=(sample-128)<<6; + if (sample>32767) sample=32767; + if (sample<-32768) sample=-32768; + bufL[i]=sample; + } +} + +void DivPlatformMMC5::tick() { + for (int i=0; i<2; i++) { + chan[i].std.next(); + if (chan[i].std.hadVol) { + // ok, why are the volumes like that? + chan[i].outVol=MIN(15,chan[i].std.vol)-(15-(chan[i].vol&15)); + if (chan[i].outVol<0) chan[i].outVol=0; + rWrite(0x5000+i*4,0x30|chan[i].outVol|((chan[i].duty&3)<<6)); + } + if (chan[i].std.hadArp) { + if (!chan[i].inPorta) { + if (chan[i].std.arpMode) { + chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp); + } else { + chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp); + } + } + chan[i].freqChanged=true; + } else { + if (chan[i].std.arpMode && chan[i].std.finishedArp) { + chan[i].baseFreq=NOTE_PERIODIC(chan[i].note); + chan[i].freqChanged=true; + } + } + if (chan[i].std.hadDuty) { + chan[i].duty=chan[i].std.duty; + rWrite(0x5000+i*4,0x30|chan[i].outVol|((chan[i].duty&3)<<6)); + } + if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true)-1; + if (chan[i].freq>2047) chan[i].freq=2047; + if (chan[i].freq<0) chan[i].freq=0; + if (chan[i].keyOn) { + //rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(ins->gb.soundLen&63))); + //rWrite(16+i*5+2,((chan[i].vol<<4))|(ins->gb.envLen&7)|((ins->gb.envDir&1)<<3)); + } + if (chan[i].keyOff) { + //rWrite(16+i*5+2,8); + rWrite(0x5000+i*4,0x30); + } + rWrite(0x5002+i*4,chan[i].freq&0xff); + if ((chan[i].prevFreq>>8)!=(chan[i].freq>>8)) { + rWrite(0x5003+i*4,0xf8|(chan[i].freq>>8)); + } + if (chan[i].freq!=65535 && chan[i].freq!=0) { + chan[i].prevFreq=chan[i].freq; + } + if (chan[i].keyOn) chan[i].keyOn=false; + if (chan[i].keyOff) chan[i].keyOff=false; + chan[i].freqChanged=false; + } + } + + // PCM + if (chan[4].freqChanged) { + chan[4].freq=parent->calcFreq(chan[4].baseFreq,chan[4].pitch,false); + if (chan[4].furnaceDac) { + double off=1.0; + if (dacSample>=0 && dacSamplesong.sampleLen) { + DivSample* s=parent->getSample(dacSample); + off=(double)s->centerRate/8363.0; + } + dacRate=MIN(chan[4].freq*off,32000); + if (dumpWrites) addWrite(0xffff0001,dacRate); + } + chan[4].freqChanged=false; + } +} + +int DivPlatformMMC5::dispatch(DivCommand c) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: + if (c.chan==2) { // PCM + DivInstrument* ins=parent->getIns(chan[c.chan].ins); + if (ins->type==DIV_INS_AMIGA) { + dacSample=ins->amiga.initSample; + if (dacSample<0 || dacSample>=parent->song.sampleLen) { + dacSample=-1; + if (dumpWrites) addWrite(0xffff0002,0); + break; + } else { + if (dumpWrites) addWrite(0xffff0000,dacSample); + } + dacPos=0; + dacPeriod=0; + chan[c.chan].baseFreq=parent->song.tuning*pow(2.0f,((float)(c.value+3)/12.0f)); + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + } + chan[c.chan].active=true; + chan[c.chan].keyOn=true; + chan[c.chan].furnaceDac=true; + } else { + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].note=c.value; + } + dacSample=12*sampleBank+chan[c.chan].note%12; + if (dacSample>=parent->song.sampleLen) { + dacSample=-1; + if (dumpWrites) addWrite(0xffff0002,0); + break; + } else { + if (dumpWrites) addWrite(0xffff0000,dacSample); + } + dacPos=0; + dacPeriod=0; + dacRate=parent->getSample(dacSample)->rate; + if (dumpWrites) addWrite(0xffff0001,dacRate); + chan[c.chan].furnaceDac=false; + } + break; + } else { + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); + } + } + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + } + chan[c.chan].active=true; + chan[c.chan].keyOn=true; + chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + rWrite(0x5000+c.chan*4,0x30|chan[c.chan].vol|((chan[c.chan].duty&3)<<6)); + break; + case DIV_CMD_NOTE_OFF: + if (c.chan==2) { + dacSample=-1; + if (dumpWrites) addWrite(0xffff0002,0); + } + chan[c.chan].active=false; + chan[c.chan].keyOff=true; + chan[c.chan].std.init(NULL); + break; + case DIV_CMD_NOTE_OFF_ENV: + case DIV_CMD_ENV_RELEASE: + chan[c.chan].std.release(); + break; + case DIV_CMD_INSTRUMENT: + if (chan[c.chan].ins!=c.value || c.value2==1) { + chan[c.chan].ins=c.value; + } + break; + case DIV_CMD_VOLUME: + if (chan[c.chan].vol!=c.value) { + chan[c.chan].vol=c.value; + if (!chan[c.chan].std.hasVol) { + chan[c.chan].outVol=c.value; + } + if (chan[c.chan].active) { + rWrite(0x5000+c.chan*4,0x30|chan[c.chan].vol|((chan[c.chan].duty&3)<<6)); + } + } + break; + case DIV_CMD_GET_VOLUME: + return chan[c.chan].vol; + break; + case DIV_CMD_PITCH: + chan[c.chan].pitch=c.value; + chan[c.chan].freqChanged=true; + break; + case DIV_CMD_NOTE_PORTA: { + int destFreq=NOTE_PERIODIC(c.value2); + bool return2=false; + if (destFreq>chan[c.chan].baseFreq) { + chan[c.chan].baseFreq+=c.value; + if (chan[c.chan].baseFreq>=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } else { + chan[c.chan].baseFreq-=c.value; + if (chan[c.chan].baseFreq<=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } + chan[c.chan].freqChanged=true; + if (return2) { + chan[c.chan].inPorta=false; + return 2; + } + break; + } + case DIV_CMD_STD_NOISE_MODE: + chan[c.chan].duty=c.value; + break; + case DIV_CMD_SAMPLE_BANK: + sampleBank=c.value; + if (sampleBank>(parent->song.sample.size()/12)) { + sampleBank=parent->song.sample.size()/12; + } + break; + case DIV_CMD_LEGATO: + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.willArp && !chan[c.chan].std.arpMode)?(chan[c.chan].std.arp):(0))); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + break; + case DIV_CMD_PRE_PORTA: + if (chan[c.chan].active && c.value2) { + if (parent->song.resetMacroOnPorta) chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + } + chan[c.chan].inPorta=c.value; + break; + case DIV_CMD_GET_VOLMAX: + return 15; + break; + case DIV_ALWAYS_SET_VOLUME: + return 1; + break; + default: + break; + } + return 1; +} + +void DivPlatformMMC5::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; +} + +void DivPlatformMMC5::forceIns() { + for (int i=0; i<3; i++) { + chan[i].insChanged=true; + chan[i].prevFreq=65535; + } +} + +void* DivPlatformMMC5::getChanState(int ch) { + return &chan[ch]; +} + +unsigned char* DivPlatformMMC5::getRegisterPool() { + return regPool; +} + +int DivPlatformMMC5::getRegisterPoolSize() { + return 32; +} + +void DivPlatformMMC5::reset() { + for (int i=0; i<3; i++) { + chan[i]=DivPlatformMMC5::Channel(); + } + if (dumpWrites) { + addWrite(0xffffffff,0); + } + + dacPeriod=0; + dacPos=0; + dacRate=0; + dacSample=-1; + sampleBank=0; + + map_init_MMC5(mmc5); + memset(regPool,0,128); + + rWrite(0x5015,0x03); + rWrite(0x5010,0x00); +} + +bool DivPlatformMMC5::keyOffAffectsArp(int ch) { + return true; +} + +void DivPlatformMMC5::setFlags(unsigned int flags) { + if (flags==2) { // Dendy + rate=COLOR_PAL*2.0/5.0; + } else if (flags==1) { // PAL + rate=COLOR_PAL*3.0/8.0; + } else { // NTSC + rate=COLOR_NTSC/2.0; + } + chipClock=rate; +} + +void DivPlatformMMC5::notifyInsDeletion(void* ins) { + for (int i=0; i<3; i++) { + chan[i].std.notifyInsDeletion((DivInstrument*)ins); + } +} + +void DivPlatformMMC5::poke(unsigned int addr, unsigned short val) { + rWrite(addr,val); +} + +void DivPlatformMMC5::poke(std::vector& wlist) { + for (DivRegWrite& i: wlist) rWrite(i.addr,i.val); +} + +int DivPlatformMMC5::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { + parent=p; + apuType=flags; + dumpWrites=false; + skipRegisterWrites=false; + mmc5=new struct _mmc5; + for (int i=0; i<3; i++) { + isMuted[i]=false; + //mmc5->muted[i]=false; // TODO + } + setFlags(flags); + + init_nla_table(500,500); + reset(); + return 5; +} + +void DivPlatformMMC5::quit() { + delete mmc5; +} + +DivPlatformMMC5::~DivPlatformMMC5() { +} diff --git a/src/engine/platform/mmc5.h b/src/engine/platform/mmc5.h new file mode 100644 index 000000000..a29ffc7d4 --- /dev/null +++ b/src/engine/platform/mmc5.h @@ -0,0 +1,88 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _MMC5_H +#define _MMC5_H + +#include "../dispatch.h" +#include "../macroInt.h" + +class DivPlatformMMC5: public DivDispatch { + struct Channel { + int freq, baseFreq, pitch, prevFreq, note; + unsigned char ins, duty, sweep; + bool active, insChanged, freqChanged, sweepChanged, keyOn, keyOff, inPorta, furnaceDac; + signed char vol, outVol, wave; + DivMacroInt std; + Channel(): + freq(0), + baseFreq(0), + pitch(0), + prevFreq(65535), + note(0), + ins(-1), + duty(0), + sweep(8), + active(false), + insChanged(true), + freqChanged(false), + sweepChanged(false), + keyOn(false), + keyOff(false), + inPorta(false), + furnaceDac(false), + vol(15), + outVol(15), + wave(-1) {} + }; + Channel chan[5]; + bool isMuted[5]; + int dacPeriod, dacRate; + unsigned int dacPos; + int dacSample; + unsigned char sampleBank; + unsigned char apuType; + struct _mmc5* mmc5; + unsigned char regPool[128]; + + friend void putDispatchChan(void*,int,int); + + public: + void acquire(short* bufL, short* bufR, size_t start, size_t len); + int dispatch(DivCommand c); + void* getChanState(int chan); + unsigned char* getRegisterPool(); + int getRegisterPoolSize(); + void reset(); + void forceIns(); + void tick(); + void muteChannel(int ch, bool mute); + bool keyOffAffectsArp(int ch); + void setFlags(unsigned int flags); + void notifyInsDeletion(void* ins); + void poke(unsigned int addr, unsigned short val); + void poke(std::vector& wlist); + const char** getRegisterSheet(); + const char* getEffectName(unsigned char effect); + int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); + void quit(); + ~DivPlatformMMC5(); +}; + +#endif diff --git a/src/engine/platform/sound/nes/apu.c b/src/engine/platform/sound/nes/apu.c index 70eddc8e7..e9ceb79a1 100644 --- a/src/engine/platform/sound/nes/apu.c +++ b/src/engine/platform/sound/nes/apu.c @@ -21,6 +21,8 @@ #include #include "apu.h" +struct _nla_table nla_table; + void apu_tick(struct NESAPU* a, BYTE *hwtick) { /* sottraggo il numero di cicli eseguiti */ a->apu.cycles--; @@ -177,8 +179,8 @@ void apu_tick(struct NESAPU* a, BYTE *hwtick) { * eseguo un ticket per ogni canale * valorizzandone l'output. */ - square_tick(a->S1, 0, a->apu) - square_tick(a->S2, 0, a->apu) + square_tick(a->S1, 0, a->apu.clocked) + square_tick(a->S2, 0, a->apu.clocked) triangle_tick() noise_tick() dmc_tick() diff --git a/src/engine/platform/sound/nes/apu.h b/src/engine/platform/sound/nes/apu.h index 93cf72fef..224ea2a04 100644 --- a/src/engine/platform/sound/nes/apu.h +++ b/src/engine/platform/sound/nes/apu.h @@ -137,12 +137,12 @@ enum apu_mode { APU_60HZ, APU_48HZ }; #define dmc_output()\ a->DMC.output = a->DMC.counter & 0x7F /* tick */ -#define square_tick(square, swap, type)\ +#define square_tick(square, swap, type_clocked)\ if (!(--square.frequency)) {\ square_output(square, swap)\ square.frequency = (square.timer + 1) << 1;\ square.sequencer = (square.sequencer + 1) & 0x07;\ - type.clocked = TRUE;\ + type_clocked = TRUE;\ } #define triangle_tick()\ if (!(--a->TR.frequency)) {\ @@ -302,7 +302,7 @@ enum apu_mode { APU_60HZ, APU_48HZ }; #define square_reg2(square)\ /* timer (low 8 bits) */\ square.timer = (square.timer & 0x0700) | value -#define square_reg3(square)\ +#define square_reg3(square,length_clocked)\ /* length counter */\ /*\ * se non disabilitato, una scrittura in\ @@ -312,7 +312,7 @@ enum apu_mode { APU_60HZ, APU_48HZ }; * momento del clock di un length counter e\ * con il length diverso da zero.\ */\ - if (square.length.enabled && !(a->apu.length_clocked && square.length.value)) {\ + if (square.length.enabled && !(length_clocked && square.length.value)) {\ square.length.value = length_table[value >> 3];\ }\ /* envelope */\ @@ -510,9 +510,11 @@ typedef struct _apuDMC { #endif EXTERNC struct _nla_table { - SWORD pulse[32]; - SWORD tnd[203]; -} nla_table; + SWORD pulse[32]; + SWORD tnd[203]; +}; + +extern struct _nla_table nla_table; EXTERNC struct NESAPU { _apu apu; diff --git a/src/engine/platform/sound/nes/cpu_inline.h b/src/engine/platform/sound/nes/cpu_inline.h index ee762f17b..c9af64e9e 100644 --- a/src/engine/platform/sound/nes/cpu_inline.h +++ b/src/engine/platform/sound/nes/cpu_inline.h @@ -58,7 +58,7 @@ INLINE static void apu_wr_reg(struct NESAPU* a, WORD address, BYTE value) { return; } if (address == 0x4003) { - square_reg3(a->S1); + square_reg3(a->S1,a->apu.length_clocked); sweep_silence(a->S1) return; } @@ -81,7 +81,7 @@ INLINE static void apu_wr_reg(struct NESAPU* a, WORD address, BYTE value) { return; } if (address == 0x4007) { - square_reg3(a->S2); + square_reg3(a->S2,a->apu.length_clocked); sweep_silence(a->S2) return; } diff --git a/src/engine/platform/sound/nes/mmc5.c b/src/engine/platform/sound/nes/mmc5.c new file mode 100644 index 000000000..9272bc44b --- /dev/null +++ b/src/engine/platform/sound/nes/mmc5.c @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2010-2019 Fabio Cavallo (aka FHorse) + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include +#include "apu.h" +#include "mmc5.h" + +enum { MODE0, MODE1, MODE2, MODE3 }; +enum { CHR_S, CHR_B }; +enum { SPLIT_LEFT, SPLIT_RIGHT = 0x40 }; + +const BYTE filler_attrib[4] = {0x00, 0x55, 0xAA, 0xFF}; +BYTE prg_ram_mode; + +void map_init_MMC5(struct _mmc5* mmc5) { + memset(mmc5,0,sizeof(struct _mmc5)); + + mmc5->S3.frequency = 1; + mmc5->S4.frequency = 1; + mmc5->S3.length.enabled = 0; + mmc5->S3.length.value = 0; + mmc5->S4.length.enabled = 0; + mmc5->S4.length.value = 0; +} +void extcl_cpu_wr_mem_MMC5(struct _mmc5* mmc5, WORD address, BYTE value) { + if (address < 0x5000) { + return; + } + + switch (address) { + case 0x5000: + square_reg0(mmc5->S3); + return; + case 0x5001: + /* lo sweep non e' utilizzato */ + return; + case 0x5002: + square_reg2(mmc5->S3); + return; + case 0x5003: + square_reg3(mmc5->S3,0); + return; + case 0x5004: + square_reg0(mmc5->S4); + return; + case 0x5005: + /* lo sweep non e' utilizzato */ + return; + case 0x5006: + square_reg2(mmc5->S4); + return; + case 0x5007: + square_reg3(mmc5->S4,0); + return; + case 0x5010: + mmc5->pcm.enabled = ~value & 0x01; + mmc5->pcm.output = 0; + if (mmc5->pcm.enabled) { + mmc5->pcm.output = mmc5->pcm.amp; + } + mmc5->clocked = TRUE; + return; + case 0x5011: + mmc5->pcm.amp = value; + mmc5->pcm.output = 0; + if (mmc5->pcm.enabled) { + mmc5->pcm.output = mmc5->pcm.amp; + } + mmc5->clocked = TRUE; + return; + case 0x5015: + if (!(mmc5->S3.length.enabled = value & 0x01)) { + mmc5->S3.length.value = 0; + } + if (!(mmc5->S4.length.enabled = value & 0x02)) { + mmc5->S4.length.value = 0; + } + return; + } +} +void extcl_length_clock_MMC5(struct _mmc5* mmc5) { + length_run(mmc5->S3) + length_run(mmc5->S4) +} +void extcl_envelope_clock_MMC5(struct _mmc5* mmc5) { + envelope_run(mmc5->S3) + envelope_run(mmc5->S4) +} +void extcl_apu_tick_MMC5(struct _mmc5* mmc5) { + square_tick(mmc5->S3, 0, mmc5->clocked) + square_tick(mmc5->S4, 0, mmc5->clocked) +} \ No newline at end of file diff --git a/src/engine/platform/sound/nes/mmc5.h b/src/engine/platform/sound/nes/mmc5.h new file mode 100644 index 000000000..3d1fbff93 --- /dev/null +++ b/src/engine/platform/sound/nes/mmc5.h @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2010-2019 Fabio Cavallo (aka FHorse) + * + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifndef MAPPER_MMC5_H_ +#define MAPPER_MMC5_H_ + +#include "apu.h" + +#if defined (__cplusplus) +#define EXTERNC extern "C" +#else +#define EXTERNC +#endif + +EXTERNC struct _mmc5 { + BYTE prg_mode; + BYTE chr_mode; + BYTE ext_mode; + BYTE nmt_mode[4]; + BYTE prg_ram_write[2]; + BYTE prg_bank[4]; + uint32_t prg_ram_bank[4][2]; + BYTE chr_last; + WORD chr_high; + WORD chr_s[8]; + WORD chr_b[4]; + BYTE ext_ram[0x400]; + BYTE fill_table[0x400]; + BYTE fill_tile; + BYTE fill_attr; + BYTE split; + BYTE split_st_tile; + BYTE split_side; + BYTE split_scrl; + BYTE split_in_reg; + BYTE split_x; + BYTE split_y; + WORD split_tile; + uint32_t split_bank; + BYTE factor[2]; + WORD product; + _apuSquare S3, S4; + struct _mmc5_pcm { + BYTE enabled; + BYTE output; + BYTE amp; + } pcm; + BYTE filler[50]; + + /* ------------------------------------------------------- */ + /* questi valori non e' necessario salvarli nei savestates */ + /* ------------------------------------------------------- */ + /* */ BYTE clocked; /* */ + /* ------------------------------------------------------- */ +}; + +EXTERNC void map_init_MMC5(struct _mmc5* mmc5); +EXTERNC void extcl_cpu_wr_mem_MMC5(struct _mmc5* mmc5, WORD address, BYTE value); +EXTERNC void extcl_length_clock_MMC5(struct _mmc5* mmc5); +EXTERNC void extcl_envelope_clock_MMC5(struct _mmc5* mmc5); +EXTERNC void extcl_apu_tick_MMC5(struct _mmc5* mmc5); + +#undef EXTERNC + +#endif /* MAPPER_MMC5_H_ */ From 5de58e1f6d3ca1956c3f0082839ccf91f37ed977 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 6 Apr 2022 00:34:26 -0500 Subject: [PATCH 523/637] GUI: add settings for borders --- src/gui/gui.h | 4 ++++ src/gui/settings.cpp | 22 ++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/gui/gui.h b/src/gui/gui.h index 84e2ef1b0..2881fa2f9 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -54,6 +54,8 @@ enum FurnaceGUIColors { GUI_COLOR_TEXT, GUI_COLOR_ACCENT_PRIMARY, GUI_COLOR_ACCENT_SECONDARY, + GUI_COLOR_BORDER, + GUI_COLOR_BORDER_SHADOW, GUI_COLOR_TOGGLE_OFF, GUI_COLOR_TOGGLE_ON, GUI_COLOR_EDITING, @@ -737,6 +739,7 @@ class FurnaceGUI { int cursorPastePos; int titleBarInfo; int titleBarSys; + int frameBorders; unsigned int maxUndoSteps; String mainFontPath; String patFontPath; @@ -795,6 +798,7 @@ class FurnaceGUI { cursorPastePos(1), titleBarInfo(1), titleBarSys(1), + frameBorders(0), maxUndoSteps(100), mainFontPath(""), patFontPath(""), diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 04555b27c..39859363b 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -868,6 +868,11 @@ void FurnaceGUI::drawSettings() { settings.roundedMenus=roundedMenusB; } + bool frameBordersB=settings.frameBorders; + if (ImGui::Checkbox("Borders around widgets",&frameBordersB)) { + settings.frameBorders=frameBordersB; + } + ImGui::Separator(); if (ImGui::TreeNode("Color scheme")) { @@ -886,6 +891,8 @@ void FurnaceGUI::drawSettings() { UI_COLOR_CONFIG(GUI_COLOR_TEXT,"Text"); UI_COLOR_CONFIG(GUI_COLOR_ACCENT_PRIMARY,"Primary"); UI_COLOR_CONFIG(GUI_COLOR_ACCENT_SECONDARY,"Secondary"); + UI_COLOR_CONFIG(GUI_COLOR_BORDER,"Border"); + UI_COLOR_CONFIG(GUI_COLOR_BORDER_SHADOW,"Border shadow"); UI_COLOR_CONFIG(GUI_COLOR_TOGGLE_ON,"Toggle on"); UI_COLOR_CONFIG(GUI_COLOR_TOGGLE_OFF,"Toggle off"); UI_COLOR_CONFIG(GUI_COLOR_EDITING,"Editing"); @@ -1397,6 +1404,7 @@ void FurnaceGUI::syncSettings() { settings.cursorPastePos=e->getConfInt("cursorPastePos",1); settings.titleBarInfo=e->getConfInt("titleBarInfo",1); settings.titleBarSys=e->getConfInt("titleBarSys",1); + settings.frameBorders=e->getConfInt("frameBorders",0); clampSetting(settings.mainFontSize,2,96); clampSetting(settings.patFontSize,2,96); @@ -1447,6 +1455,7 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.cursorPastePos,0,1); clampSetting(settings.titleBarInfo,0,3); clampSetting(settings.titleBarSys,0,1); + clampSetting(settings.frameBorders,0,1); // keybinds LOAD_KEYBIND(GUI_ACTION_OPEN,FURKMOD_CMD|SDLK_o); @@ -1694,6 +1703,7 @@ void FurnaceGUI::commitSettings() { e->setConf("cursorPastePos",settings.cursorPastePos); e->setConf("titleBarInfo",settings.titleBarInfo); e->setConf("titleBarSys",settings.titleBarSys); + e->setConf("frameBorders",settings.frameBorders); PUT_UI_COLOR(GUI_COLOR_BACKGROUND); PUT_UI_COLOR(GUI_COLOR_FRAME_BACKGROUND); @@ -1702,6 +1712,8 @@ void FurnaceGUI::commitSettings() { PUT_UI_COLOR(GUI_COLOR_TEXT); PUT_UI_COLOR(GUI_COLOR_ACCENT_PRIMARY); PUT_UI_COLOR(GUI_COLOR_ACCENT_SECONDARY); + PUT_UI_COLOR(GUI_COLOR_BORDER); + PUT_UI_COLOR(GUI_COLOR_BORDER_SHADOW); PUT_UI_COLOR(GUI_COLOR_TOGGLE_ON); PUT_UI_COLOR(GUI_COLOR_TOGGLE_OFF); PUT_UI_COLOR(GUI_COLOR_EDITING); @@ -2099,6 +2111,8 @@ void FurnaceGUI::applyUISettings() { GET_UI_COLOR(GUI_COLOR_TEXT,ImVec4(1.0f,1.0f,1.0f,1.0f)); GET_UI_COLOR(GUI_COLOR_ACCENT_PRIMARY,ImVec4(0.06f,0.53f,0.98f,1.0f)); GET_UI_COLOR(GUI_COLOR_ACCENT_SECONDARY,ImVec4(0.26f,0.59f,0.98f,1.0f)); + GET_UI_COLOR(GUI_COLOR_BORDER,ImVec4(0.43f,0.43f,0.5f,0.5f)); + GET_UI_COLOR(GUI_COLOR_BORDER_SHADOW,ImVec4(0.0f,0.0f,0.0f,0.0f)); GET_UI_COLOR(GUI_COLOR_TOGGLE_ON,ImVec4(0.2f,0.6f,0.2f,1.0f)); GET_UI_COLOR(GUI_COLOR_TOGGLE_OFF,ImVec4(0.2f,0.2f,0.2f,1.0f)); GET_UI_COLOR(GUI_COLOR_EDITING,ImVec4(0.2f,0.1f,0.1f,1.0f)); @@ -2273,6 +2287,8 @@ void FurnaceGUI::applyUISettings() { sty.Colors[ImGuiCol_TextSelectedBg]=secondaryHover; sty.Colors[ImGuiCol_PlotHistogram]=uiColors[GUI_COLOR_MACRO_OTHER]; sty.Colors[ImGuiCol_PlotHistogramHovered]=uiColors[GUI_COLOR_MACRO_OTHER]; + sty.Colors[ImGuiCol_Border]=uiColors[GUI_COLOR_BORDER]; + sty.Colors[ImGuiCol_BorderShadow]=uiColors[GUI_COLOR_BORDER_SHADOW]; if (settings.roundedWindows) sty.WindowRounding=8.0f; if (settings.roundedButtons) { @@ -2281,6 +2297,12 @@ void FurnaceGUI::applyUISettings() { } if (settings.roundedMenus) sty.PopupRounding=8.0f; + if (settings.frameBorders) { + sty.FrameBorderSize=1.0f; + } else { + sty.FrameBorderSize=0.0f; + } + sty.ScaleAllSizes(dpiScale); ImGui::GetStyle()=sty; From 91738dbf239943c27cbb837fc6651f35b37c677c Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 6 Apr 2022 00:45:26 -0500 Subject: [PATCH 524/637] add MMC5 documentation --- papers/doc/7-systems/mmc5.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 papers/doc/7-systems/mmc5.md diff --git a/papers/doc/7-systems/mmc5.md b/papers/doc/7-systems/mmc5.md new file mode 100644 index 000000000..faf305c60 --- /dev/null +++ b/papers/doc/7-systems/mmc5.md @@ -0,0 +1,12 @@ +# Nintendo MMC5 + +a mapper chip which made NES cartridges exceeding 1MB possible. + +it has two pulse channels which are very similar to the ones found in the NES, but lacking the sweep unit. + +additionally, it offers an 8-bit DAC which can be used to play samples. only one game is known to use it, though. + +# effects + +- `12xx`: set duty cycle or noise mode of channel. + - may be 0-3 for the pulse channels and 0-1 for the noise channel. From 7f51f0f24684155761d60ecf8fa382e2eb1b68b4 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 6 Apr 2022 00:45:56 -0500 Subject: [PATCH 525/637] update system list in doc --- papers/doc/7-systems/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/papers/doc/7-systems/README.md b/papers/doc/7-systems/README.md index f759956f8..fe5af1aba 100644 --- a/papers/doc/7-systems/README.md +++ b/papers/doc/7-systems/README.md @@ -30,5 +30,7 @@ this is a list of systems that Furnace supports, including each system's effects - [Commodore PET](pet.md) - [Commodore VIC-20](vic20.md) - [Konami VRC6](vrc6.md) +- [Famicom Disk System](fds.md) +- [Nintendo MMC5](mmc5.md) Furnace also reads .dmf files with the [Yamaha YMU759](ymu759.md) system, but... From 1054f92029005b475aa52676d80a15dd53541cfe Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 6 Apr 2022 01:52:58 -0500 Subject: [PATCH 526/637] GUI: simplify keybind load/save code as of now action definitions are in guiConst.cpp. --- src/gui/gui.h | 6 + src/gui/guiConst.cpp | 399 ++++++++++++++++++++++--------------------- src/gui/guiConst.h | 10 +- src/gui/midiMap.cpp | 4 +- src/gui/settings.cpp | 393 +++--------------------------------------- 5 files changed, 247 insertions(+), 565 deletions(-) diff --git a/src/gui/gui.h b/src/gui/gui.h index 2881fa2f9..becd759bc 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -440,6 +440,12 @@ enum PasteMode { #define FURKMOD_ALT (1<<27) #define FURK_MASK 0x40ffffff +#ifdef __APPLE__ +#define FURKMOD_CMD FURKMOD_META +#else +#define FURKMOD_CMD FURKMOD_CTRL +#endif + struct SelectionPoint { int xCoarse, xFine; int y; diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index 462c9f827..33974bba8 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -138,212 +138,217 @@ const char* resampleStrats[]={ "best possible" }; -const char* guiActions[GUI_ACTION_MAX][2]={ - {"GLOBAL_MIN", "---Global"}, - {"OPEN", "Open file"}, - {"OPEN_BACKUP", "Restore backup"}, - {"SAVE", "Save file"}, - {"SAVE_AS", "Save as"}, - {"UNDO", "Undo"}, - {"REDO", "Redo"}, - {"PLAY_TOGGLE", "Play/Stop (toggle)"}, - {"PLAY", "Play"}, - {"STOP", "Stop"}, - {"PLAY_REPEAT", "Play (repeat pattern)"}, - {"PLAY_CURSOR", "Play from cursor"}, - {"STEP_ONE", "Step row"}, - {"OCTAVE_UP", "Octave up"}, - {"OCTAVE_DOWN", "Octave down"}, - {"INS_UP", "Previous instrument"}, - {"INS_DOWN", "Next instrument"}, - {"STEP_UP", "Increase edit step"}, - {"STEP_DOWN", "Decrease edit step"}, - {"TOGGLE_EDIT", "Toggle edit mode"}, - {"METRONOME", "Metronome"}, - {"REPEAT_PATTERN", "Toggle repeat pattern"}, - {"FOLLOW_ORDERS", "Follow orders"}, - {"FOLLOW_PATTERN", "Follow pattern"}, - {"PANIC", "Panic"}, +#define D FurnaceGUIActionDef +#define NOT_AN_ACTION -1 - {"WINDOW_EDIT_CONTROLS", "Edit Controls"}, - {"WINDOW_ORDERS", "Orders"}, - {"WINDOW_INS_LIST", "Instrument List"}, - {"WINDOW_INS_EDIT", "Instrument Editor"}, - {"WINDOW_SONG_INFO", "Song Information"}, - {"WINDOW_PATTERN", "Pattern"}, - {"WINDOW_WAVE_LIST", "Wavetable List"}, - {"WINDOW_WAVE_EDIT", "Wavetable Editor"}, - {"WINDOW_SAMPLE_LIST", "Sample List"}, - {"WINDOW_SAMPLE_EDIT", "Sample Editor"}, - {"WINDOW_ABOUT", "About"}, - {"WINDOW_SETTINGS", "Settings"}, - {"WINDOW_MIXER", "Mixer"}, - {"WINDOW_DEBUG", "Debug Menu"}, - {"WINDOW_OSCILLOSCOPE", "Oscilloscope"}, - {"WINDOW_VOL_METER", "Volume Meter"}, - {"WINDOW_STATS", "Statistics"}, - {"WINDOW_COMPAT_FLAGS", "Compatibility Flags"}, - {"WINDOW_PIANO", "Piano"}, - {"WINDOW_NOTES", "Song Comments"}, - {"WINDOW_CHANNELS", "Channels"}, - {"WINDOW_REGISTER_VIEW", "Register View"}, +// format: ("ACTION_ENUM", "Action name", defaultBind) +const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={ + D("GLOBAL_MIN", "---Global", NOT_AN_ACTION), + D("OPEN", "Open file", FURKMOD_CMD|SDLK_o), + D("OPEN_BACKUP", "Restore backup", 0), + D("SAVE", "Save file", FURKMOD_CMD|SDLK_s), + D("SAVE_AS", "Save as", FURKMOD_CMD|FURKMOD_SHIFT|SDLK_s), + D("UNDO", "Undo", FURKMOD_CMD|SDLK_z), + D("REDO", "Redo", FURKMOD_CMD|SDLK_y), + D("PLAY_TOGGLE", "Play/Stop (toggle)", SDLK_RETURN), + D("PLAY", "Play", 0), + D("STOP", "Stop", 0), + D("PLAY_REPEAT", "Play (repeat pattern)", 0), + D("PLAY_CURSOR", "Play from cursor", FURKMOD_SHIFT|SDLK_RETURN), + D("STEP_ONE", "Step row", FURKMOD_CMD|SDLK_RETURN), + D("OCTAVE_UP", "Octave up", SDLK_KP_MULTIPLY), + D("OCTAVE_DOWN", "Octave down", SDLK_KP_DIVIDE), + D("INS_UP", "Previous instrument", FURKMOD_SHIFT|SDLK_KP_DIVIDE), + D("INS_DOWN", "Next instrument", FURKMOD_SHIFT|SDLK_KP_MULTIPLY), + D("STEP_UP", "Increase edit step", FURKMOD_CMD|SDLK_KP_MULTIPLY), + D("STEP_DOWN", "Decrease edit step", FURKMOD_CMD|SDLK_KP_DIVIDE), + D("TOGGLE_EDIT", "Toggle edit mode", SDLK_SPACE), + D("METRONOME", "Metronome", FURKMOD_CMD|SDLK_m), + D("REPEAT_PATTERN", "Toggle repeat pattern", 0), + D("FOLLOW_ORDERS", "Follow orders", 0), + D("FOLLOW_PATTERN", "Follow pattern", 0), + D("PANIC", "Panic", SDLK_F12), - {"COLLAPSE_WINDOW", "Collapse/expand current window"}, - {"CLOSE_WINDOW", "Close current window"}, - {"GLOBAL_MAX", ""}, + D("WINDOW_EDIT_CONTROLS", "Edit Controls", 0), + D("WINDOW_ORDERS", "Orders", 0), + D("WINDOW_INS_LIST", "Instrument List", 0), + D("WINDOW_INS_EDIT", "Instrument Editor", 0), + D("WINDOW_SONG_INFO", "Song Information", 0), + D("WINDOW_PATTERN", "Pattern", 0), + D("WINDOW_WAVE_LIST", "Wavetable List", 0), + D("WINDOW_WAVE_EDIT", "Wavetable Editor", 0), + D("WINDOW_SAMPLE_LIST", "Sample List", 0), + D("WINDOW_SAMPLE_EDIT", "Sample Editor", 0), + D("WINDOW_ABOUT", "About", 0), + D("WINDOW_SETTINGS", "Settings", 0), + D("WINDOW_MIXER", "Mixer", 0), + D("WINDOW_DEBUG", "Debug Menu", 0), + D("WINDOW_OSCILLOSCOPE", "Oscilloscope", 0), + D("WINDOW_VOL_METER", "Volume Meter", 0), + D("WINDOW_STATS", "Statistics", 0), + D("WINDOW_COMPAT_FLAGS", "Compatibility Flags", 0), + D("WINDOW_PIANO", "Piano", 0), + D("WINDOW_NOTES", "Song Comments", 0), + D("WINDOW_CHANNELS", "Channels", 0), + D("WINDOW_REGISTER_VIEW", "Register View", 0), - {"PAT_MIN", "---Pattern"}, - {"PAT_NOTE_UP", "Transpose (+1)"}, - {"PAT_NOTE_DOWN", "Transpose (-1)"}, - {"PAT_OCTAVE_UP", "Transpose (+1 octave)"}, - {"PAT_OCTAVE_DOWN", "Transpose (-1 octave)"}, - {"PAT_SELECT_ALL", "Select all"}, - {"PAT_CUT", "Cut"}, - {"PAT_COPY", "Copy"}, - {"PAT_PASTE", "Paste"}, - {"PAT_PASTE_MIX", "Paste Mix (foreground)"}, - {"PAT_PASTE_MIX_BG", "Paste Mix (background)"}, - {"PAT_PASTE_FLOOD", "Paste Flood"}, - {"PAT_PASTE_OVERFLOW", "Paste Overflow"}, - {"PAT_CURSOR_UP", "Move cursor up"}, - {"PAT_CURSOR_DOWN", "Move cursor down"}, - {"PAT_CURSOR_LEFT", "Move cursor left"}, - {"PAT_CURSOR_RIGHT", "Move cursor right"}, - {"PAT_CURSOR_UP_ONE", "Move cursor up by one (override Edit Step)"}, - {"PAT_CURSOR_DOWN_ONE", "Move cursor down by one (override Edit Step)"}, - {"PAT_CURSOR_LEFT_CHANNEL", "Move cursor to previous channel"}, - {"PAT_CURSOR_RIGHT_CHANNEL", "Move cursor to next channel"}, - {"PAT_CURSOR_NEXT_CHANNEL", "Move cursor to previous channel (overflow)"}, - {"PAT_CURSOR_PREVIOUS_CHANNEL", "Move cursor to next channel (overflow)"}, - {"PAT_CURSOR_BEGIN", "Move cursor to beginning of pattern"}, - {"PAT_CURSOR_END", "Move cursor to end of pattern"}, - {"PAT_CURSOR_UP_COARSE", "Move cursor up (coarse)"}, - {"PAT_CURSOR_DOWN_COARSE", "Move cursor down (coarse)"}, - {"PAT_SELECTION_UP", "Expand selection upwards"}, - {"PAT_SELECTION_DOWN", "Expand selection downwards"}, - {"PAT_SELECTION_LEFT", "Expand selection to the left"}, - {"PAT_SELECTION_RIGHT", "Expand selection to the right"}, - {"PAT_SELECTION_UP_ONE", "Expand selection upwards by one (override Edit Step)"}, - {"PAT_SELECTION_DOWN_ONE", "Expand selection downwards by one (override Edit Step)"}, - {"PAT_SELECTION_BEGIN", "Expand selection to beginning of pattern"}, - {"PAT_SELECTION_END", "Expand selection to end of pattern"}, - {"PAT_SELECTION_UP_COARSE", "Expand selection upwards (coarse)"}, - {"PAT_SELECTION_DOWN_COARSE", "Expand selection downwards (coarse)"}, - {"PAT_DELETE", "Delete"}, - {"PAT_PULL_DELETE", "Pull delete"}, - {"PAT_INSERT", "Insert"}, - {"PAT_MUTE_CURSOR", "Mute channel at cursor"}, - {"PAT_SOLO_CURSOR", "Solo channel at cursor"}, - {"PAT_UNMUTE_ALL", "Unmute all channels"}, - {"PAT_NEXT_ORDER", "Go to next order"}, - {"PAT_PREV_ORDER", "Go to previous order"}, - {"PAT_COLLAPSE", "Collapse channel at cursor"}, - {"PAT_INCREASE_COLUMNS", "Increase effect columns"}, - {"PAT_DECREASE_COLUMNS", "Decrease effect columns"}, - {"PAT_INTERPOLATE", "Interpolate"}, - {"PAT_FADE", "Fade"}, - {"PAT_INVERT_VALUES", "Invert values"}, - {"PAT_FLIP_SELECTION", "Flip selection"}, - {"PAT_COLLAPSE_ROWS", "Collapse rows"}, - {"PAT_EXPAND_ROWS", "Expand rows"}, - {"PAT_COLLAPSE_PAT", "Collapse pattern"}, - {"PAT_EXPAND_PAT", "Expand pattern"}, - {"PAT_COLLAPSE_SONG", "Collapse song"}, - {"PAT_EXPAND_SONG", "Expand song"}, - {"PAT_LATCH", "Set note input latch"}, - {"PAT_MAX", ""}, + D("COLLAPSE_WINDOW", "Collapse/expand current window", 0), + D("CLOSE_WINDOW", "Close current window", FURKMOD_SHIFT|SDLK_ESCAPE), + D("GLOBAL_MAX", "", NOT_AN_ACTION), - {"INS_LIST_MIN", "---Instrument list"}, - {"INS_LIST_ADD", "Add"}, - {"INS_LIST_DUPLICATE", "Duplicate"}, - {"INS_LIST_OPEN", "Open"}, - {"INS_LIST_SAVE", "Save"}, - {"INS_LIST_MOVE_UP", "Move up"}, - {"INS_LIST_MOVE_DOWN", "Move down"}, - {"INS_LIST_DELETE", "Delete"}, - {"INS_LIST_EDIT", "Edit"}, - {"INS_LIST_UP", "Cursor up"}, - {"INS_LIST_DOWN", "Cursor down"}, - {"INS_LIST_MAX", ""}, + D("PAT_MIN", "---Pattern", NOT_AN_ACTION), + D("PAT_NOTE_UP", "Transpose (+1)", FURKMOD_CMD|SDLK_F2), + D("PAT_NOTE_DOWN", "Transpose (-1)", FURKMOD_CMD|SDLK_F1), + D("PAT_OCTAVE_UP", "Transpose (+1 octave)", FURKMOD_CMD|SDLK_F4), + D("PAT_OCTAVE_DOWN", "Transpose (-1 octave)", FURKMOD_CMD|SDLK_F3), + D("PAT_SELECT_ALL", "Select all", FURKMOD_CMD|SDLK_a), + D("PAT_CUT", "Cut", FURKMOD_CMD|SDLK_x), + D("PAT_COPY", "Copy", FURKMOD_CMD|SDLK_c), + D("PAT_PASTE", "Paste", FURKMOD_CMD|SDLK_v), + D("PAT_PASTE_MIX", "Paste Mix (foreground)", FURKMOD_CMD|FURKMOD_SHIFT|SDLK_v), + D("PAT_PASTE_MIX_BG", "Paste Mix (background)", 0), + D("PAT_PASTE_FLOOD", "Paste Flood", 0), + D("PAT_PASTE_OVERFLOW", "Paste Overflow", 0), + D("PAT_CURSOR_UP", "Move cursor up", SDLK_UP), + D("PAT_CURSOR_DOWN", "Move cursor down", SDLK_DOWN), + D("PAT_CURSOR_LEFT", "Move cursor left", SDLK_LEFT), + D("PAT_CURSOR_RIGHT", "Move cursor right", SDLK_RIGHT), + D("PAT_CURSOR_UP_ONE", "Move cursor up by one (override Edit Step)", FURKMOD_SHIFT|SDLK_HOME), + D("PAT_CURSOR_DOWN_ONE", "Move cursor down by one (override Edit Step)", FURKMOD_SHIFT|SDLK_END), + D("PAT_CURSOR_LEFT_CHANNEL", "Move cursor to previous channel", 0), + D("PAT_CURSOR_RIGHT_CHANNEL", "Move cursor to next channel", 0), + D("PAT_CURSOR_NEXT_CHANNEL", "Move cursor to previous channel (overflow)", 0), + D("PAT_CURSOR_PREVIOUS_CHANNEL", "Move cursor to next channel (overflow)", 0), + D("PAT_CURSOR_BEGIN", "Move cursor to beginning of pattern", SDLK_HOME), + D("PAT_CURSOR_END", "Move cursor to end of pattern", SDLK_END), + D("PAT_CURSOR_UP_COARSE", "Move cursor up (coarse)", SDLK_PAGEUP), + D("PAT_CURSOR_DOWN_COARSE", "Move cursor down (coarse)", SDLK_PAGEDOWN), + D("PAT_SELECTION_UP", "Expand selection upwards", FURKMOD_SHIFT|SDLK_UP), + D("PAT_SELECTION_DOWN", "Expand selection downwards", FURKMOD_SHIFT|SDLK_DOWN), + D("PAT_SELECTION_LEFT", "Expand selection to the left", FURKMOD_SHIFT|SDLK_LEFT), + D("PAT_SELECTION_RIGHT", "Expand selection to the right", FURKMOD_SHIFT|SDLK_RIGHT), + D("PAT_SELECTION_UP_ONE", "Expand selection upwards by one (override Edit Step)", 0), + D("PAT_SELECTION_DOWN_ONE", "Expand selection downwards by one (override Edit Step)", 0), + D("PAT_SELECTION_BEGIN", "Expand selection to beginning of pattern", 0), + D("PAT_SELECTION_END", "Expand selection to end of pattern", 0), + D("PAT_SELECTION_UP_COARSE", "Expand selection upwards (coarse)", FURKMOD_SHIFT|SDLK_PAGEUP), + D("PAT_SELECTION_DOWN_COARSE", "Expand selection downwards (coarse)", FURKMOD_SHIFT|SDLK_PAGEDOWN), + D("PAT_DELETE", "Delete", SDLK_DELETE), + D("PAT_PULL_DELETE", "Pull delete", SDLK_BACKSPACE), + D("PAT_INSERT", "Insert", SDLK_INSERT), + D("PAT_MUTE_CURSOR", "Mute channel at cursor", FURKMOD_ALT|SDLK_F9), + D("PAT_SOLO_CURSOR", "Solo channel at cursor", FURKMOD_ALT|SDLK_F10), + D("PAT_UNMUTE_ALL", "Unmute all channels", FURKMOD_ALT|FURKMOD_SHIFT|SDLK_F9), + D("PAT_NEXT_ORDER", "Go to next order", 0), + D("PAT_PREV_ORDER", "Go to previous order", 0), + D("PAT_COLLAPSE", "Collapse channel at cursor", 0), + D("PAT_INCREASE_COLUMNS", "Increase effect columns", 0), + D("PAT_DECREASE_COLUMNS", "Decrease effect columns", 0), + D("PAT_INTERPOLATE", "Interpolate", 0), + D("PAT_FADE", "Fade", 0), + D("PAT_INVERT_VALUES", "Invert values", 0), + D("PAT_FLIP_SELECTION", "Flip selection", 0), + D("PAT_COLLAPSE_ROWS", "Collapse rows", 0), + D("PAT_EXPAND_ROWS", "Expand rows", 0), + D("PAT_COLLAPSE_PAT", "Collapse pattern", 0), + D("PAT_EXPAND_PAT", "Expand pattern", 0), + D("PAT_COLLAPSE_SONG", "Collapse song", 0), + D("PAT_EXPAND_SONG", "Expand song", 0), + D("PAT_LATCH", "Set note input latch", 0), + D("PAT_MAX", "", NOT_AN_ACTION), - {"WAVE_LIST_MIN", "---Wavetable list"}, - {"WAVE_LIST_ADD", "Add"}, - {"WAVE_LIST_DUPLICATE", "Duplicate"}, - {"WAVE_LIST_OPEN", "Open"}, - {"WAVE_LIST_SAVE", "Save"}, - {"WAVE_LIST_MOVE_UP", "Move up"}, - {"WAVE_LIST_MOVE_DOWN", "Move down"}, - {"WAVE_LIST_DELETE", "Delete"}, - {"WAVE_LIST_EDIT", "Edit"}, - {"WAVE_LIST_UP", "Cursor up"}, - {"WAVE_LIST_DOWN", "Cursor down"}, - {"WAVE_LIST_MAX", ""}, + D("INS_LIST_MIN", "---Instrument list", NOT_AN_ACTION), + D("INS_LIST_ADD", "Add", SDLK_INSERT), + D("INS_LIST_DUPLICATE", "Duplicate", FURKMOD_CMD|SDLK_d), + D("INS_LIST_OPEN", "Open", 0), + D("INS_LIST_SAVE", "Save", 0), + D("INS_LIST_MOVE_UP", "Move up", FURKMOD_SHIFT|SDLK_UP), + D("INS_LIST_MOVE_DOWN", "Move down", FURKMOD_SHIFT|SDLK_DOWN), + D("INS_LIST_DELETE", "Delete", 0), + D("INS_LIST_EDIT", "Edit", FURKMOD_SHIFT|SDLK_RETURN), + D("INS_LIST_UP", "Cursor up", SDLK_UP), + D("INS_LIST_DOWN", "Cursor down", SDLK_DOWN), + D("INS_LIST_MAX", "", NOT_AN_ACTION), - {"SAMPLE_LIST_MIN", "---Sample list"}, - {"SAMPLE_LIST_ADD", "Add"}, - {"SAMPLE_LIST_DUPLICATE", "Duplicate"}, - {"SAMPLE_LIST_OPEN", "Open"}, - {"SAMPLE_LIST_SAVE", "Save"}, - {"SAMPLE_LIST_MOVE_UP", "Move up"}, - {"SAMPLE_LIST_MOVE_DOWN", "Move down"}, - {"SAMPLE_LIST_DELETE", "Delete"}, - {"SAMPLE_LIST_EDIT", "Edit"}, - {"SAMPLE_LIST_UP", "Cursor up"}, - {"SAMPLE_LIST_DOWN", "Cursor down"}, - {"SAMPLE_LIST_PREVIEW", "Preview"}, - {"SAMPLE_LIST_STOP_PREVIEW", "Stop preview"}, - {"SAMPLE_LIST_MAX", ""}, + D("WAVE_LIST_MIN", "---Wavetable list", NOT_AN_ACTION), + D("WAVE_LIST_ADD", "Add", SDLK_INSERT), + D("WAVE_LIST_DUPLICATE", "Duplicate", FURKMOD_CMD|SDLK_d), + D("WAVE_LIST_OPEN", "Open", 0), + D("WAVE_LIST_SAVE", "Save", 0), + D("WAVE_LIST_MOVE_UP", "Move up", FURKMOD_SHIFT|SDLK_UP), + D("WAVE_LIST_MOVE_DOWN", "Move down", FURKMOD_SHIFT|SDLK_DOWN), + D("WAVE_LIST_DELETE", "Delete", 0), + D("WAVE_LIST_EDIT", "Edit", FURKMOD_SHIFT|SDLK_RETURN), + D("WAVE_LIST_UP", "Cursor up", SDLK_UP), + D("WAVE_LIST_DOWN", "Cursor down", SDLK_DOWN), + D("WAVE_LIST_MAX", "", NOT_AN_ACTION), - {"SAMPLE_MIN", "---Sample editor"}, - {"SAMPLE_SELECT", "Edit mode: Select"}, - {"SAMPLE_DRAW", "Edit mode: Draw"}, - {"SAMPLE_CUT", "Cut"}, - {"SAMPLE_COPY", "Copy"}, - {"SAMPLE_PASTE", "Paste"}, - {"SAMPLE_PASTE_REPLACE", "Paste replace"}, - {"SAMPLE_PASTE_MIX", "Paste mix"}, - {"SAMPLE_SELECT_ALL", "Select all"}, - {"SAMPLE_RESIZE", "Resize"}, - {"SAMPLE_RESAMPLE", "Resample"}, - {"SAMPLE_AMPLIFY", "Amplify"}, - {"SAMPLE_NORMALIZE", "Normalize"}, - {"SAMPLE_FADE_IN", "Fade in"}, - {"SAMPLE_FADE_OUT", "Fade out"}, - {"SAMPLE_SILENCE", "Apply silence"}, - {"SAMPLE_INSERT", "Insert silence"}, - {"SAMPLE_DELETE", "Delete"}, - {"SAMPLE_TRIM", "Trim"}, - {"SAMPLE_REVERSE", "Reverse"}, - {"SAMPLE_INVERT", "Invert"}, - {"SAMPLE_SIGN", "Signed/unsigned exchange"}, - {"SAMPLE_FILTER", "Apply filter"}, - {"SAMPLE_PREVIEW", "Preview sample"}, - {"SAMPLE_STOP_PREVIEW", "Stop sample preview"}, - {"SAMPLE_ZOOM_IN", "Zoom in"}, - {"SAMPLE_ZOOM_OUT", "Zoom out"}, - {"SAMPLE_ZOOM_AUTO", "Toggle auto-zoom"}, - {"SAMPLE_MAX", ""}, + D("SAMPLE_LIST_MIN", "---Sample list", NOT_AN_ACTION), + D("SAMPLE_LIST_ADD", "Add", SDLK_INSERT), + D("SAMPLE_LIST_DUPLICATE", "Duplicate", FURKMOD_CMD|SDLK_d), + D("SAMPLE_LIST_OPEN", "Open", 0), + D("SAMPLE_LIST_SAVE", "Save", 0), + D("SAMPLE_LIST_MOVE_UP", "Move up", FURKMOD_SHIFT|SDLK_UP), + D("SAMPLE_LIST_MOVE_DOWN", "Move down", FURKMOD_SHIFT|SDLK_DOWN), + D("SAMPLE_LIST_DELETE", "Delete", 0), + D("SAMPLE_LIST_EDIT", "Edit", FURKMOD_SHIFT|SDLK_RETURN), + D("SAMPLE_LIST_UP", "Cursor up", SDLK_UP), + D("SAMPLE_LIST_DOWN", "Cursor down", SDLK_DOWN), + D("SAMPLE_LIST_PREVIEW", "Preview", 0), + D("SAMPLE_LIST_STOP_PREVIEW", "Stop preview", 0), + D("SAMPLE_LIST_MAX", "", NOT_AN_ACTION), - {"ORDERS_MIN", "---Orders"}, - {"ORDERS_UP", "Previous order"}, - {"ORDERS_DOWN", "Next order"}, - {"ORDERS_LEFT", "Cursor left"}, - {"ORDERS_RIGHT", "Cursor right"}, - {"ORDERS_INCREASE", "Increase value"}, - {"ORDERS_DECREASE", "Decrease value"}, - {"ORDERS_EDIT_MODE", "Switch edit mode"}, - {"ORDERS_LINK", "Toggle alter entire row"}, - {"ORDERS_ADD", "Add"}, - {"ORDERS_DUPLICATE", "Duplicate"}, - {"ORDERS_DEEP_CLONE", "Deep clone"}, - {"ORDERS_DUPLICATE_END", "Duplicate to end of song"}, - {"ORDERS_DEEP_CLONE_END", "Deep clone to end of song"}, - {"ORDERS_REMOVE", "Remove"}, - {"ORDERS_MOVE_UP", "Move up"}, - {"ORDERS_MOVE_DOWN", "Move down"}, - {"ORDERS_REPLAY", "Replay"}, - {"ORDERS_MAX", ""}, + D("SAMPLE_MIN", "---Sample editor", NOT_AN_ACTION), + D("SAMPLE_SELECT", "Edit mode: Select", FURKMOD_SHIFT|SDLK_i), + D("SAMPLE_DRAW", "Edit mode: Draw", FURKMOD_SHIFT|SDLK_d), + D("SAMPLE_CUT", "Cut", FURKMOD_CMD|SDLK_x), + D("SAMPLE_COPY", "Copy", FURKMOD_CMD|SDLK_c), + D("SAMPLE_PASTE", "Paste", FURKMOD_CMD|SDLK_v), + D("SAMPLE_PASTE_REPLACE", "Paste replace", FURKMOD_CMD|FURKMOD_SHIFT|SDLK_v), + D("SAMPLE_PASTE_MIX", "Paste mix", FURKMOD_CMD|FURKMOD_ALT|SDLK_v), + D("SAMPLE_SELECT_ALL", "Select all", FURKMOD_CMD|SDLK_a), + D("SAMPLE_RESIZE", "Resize", FURKMOD_CMD|SDLK_r), + D("SAMPLE_RESAMPLE", "Resample", FURKMOD_CMD|SDLK_e), + D("SAMPLE_AMPLIFY", "Amplify", FURKMOD_CMD|SDLK_b), + D("SAMPLE_NORMALIZE", "Normalize", FURKMOD_CMD|SDLK_n), + D("SAMPLE_FADE_IN", "Fade in", FURKMOD_CMD|SDLK_i), + D("SAMPLE_FADE_OUT", "Fade out", FURKMOD_CMD|SDLK_o), + D("SAMPLE_SILENCE", "Apply silence", FURKMOD_SHIFT|SDLK_DELETE), + D("SAMPLE_INSERT", "Insert silence", SDLK_INSERT), + D("SAMPLE_DELETE", "Delete", SDLK_DELETE), + D("SAMPLE_TRIM", "Trim", FURKMOD_CMD|SDLK_DELETE), + D("SAMPLE_REVERSE", "Reverse", FURKMOD_CMD|SDLK_t), + D("SAMPLE_INVERT", "Invert", FURKMOD_CMD|FURKMOD_SHIFT|SDLK_t), + D("SAMPLE_SIGN", "Signed/unsigned exchange", FURKMOD_CMD|SDLK_u), + D("SAMPLE_FILTER", "Apply filter", FURKMOD_CMD|SDLK_f), + D("SAMPLE_PREVIEW", "Preview sample", 0), + D("SAMPLE_STOP_PREVIEW", "Stop sample preview", 0), + D("SAMPLE_ZOOM_IN", "Zoom in", FURKMOD_CMD|SDLK_EQUALS), + D("SAMPLE_ZOOM_OUT", "Zoom out", FURKMOD_CMD|SDLK_MINUS), + D("SAMPLE_ZOOM_AUTO", "Toggle auto-zoom", FURKMOD_CMD|SDLK_0), + D("SAMPLE_MAX", "", NOT_AN_ACTION), + + D("ORDERS_MIN", "---Orders", NOT_AN_ACTION), + D("ORDERS_UP", "Previous order", SDLK_UP), + D("ORDERS_DOWN", "Next order", SDLK_DOWN), + D("ORDERS_LEFT", "Cursor left", SDLK_LEFT), + D("ORDERS_RIGHT", "Cursor right", SDLK_RIGHT), + D("ORDERS_INCREASE", "Increase value", 0), + D("ORDERS_DECREASE", "Decrease value", 0), + D("ORDERS_EDIT_MODE", "Switch edit mode", 0), + D("ORDERS_LINK", "Toggle alter entire row", FURKMOD_CMD|SDLK_l), + D("ORDERS_ADD", "Add", SDLK_INSERT), + D("ORDERS_DUPLICATE", "Duplicate", FURKMOD_CMD|SDLK_d), + D("ORDERS_DEEP_CLONE", "Deep clone", FURKMOD_CMD|FURKMOD_SHIFT|SDLK_d), + D("ORDERS_DUPLICATE_END", "Duplicate to end of song", FURKMOD_CMD|SDLK_e), + D("ORDERS_DEEP_CLONE_END", "Deep clone to end of song", FURKMOD_CMD|FURKMOD_SHIFT|SDLK_e), + D("ORDERS_REMOVE", "Remove", SDLK_DELETE), + D("ORDERS_MOVE_UP", "Move up", FURKMOD_SHIFT|SDLK_UP), + D("ORDERS_MOVE_DOWN", "Move down", FURKMOD_SHIFT|SDLK_DOWN), + D("ORDERS_REPLAY", "Replay", 0), + D("ORDERS_MAX", "", NOT_AN_ACTION), }; +#undef D // define systems. const int availableSystems[]={ diff --git a/src/gui/guiConst.h b/src/gui/guiConst.h index dfafea88c..ef41650af 100644 --- a/src/gui/guiConst.h +++ b/src/gui/guiConst.h @@ -19,6 +19,14 @@ // guiConst: constants used in the GUI like arrays, strings and other stuff +struct FurnaceGUIActionDef { + const char* name; + const char* friendlyName; + int defaultBind; + FurnaceGUIActionDef(const char* n, const char* fn, int db): + name(n), friendlyName(fn), defaultBind(db) {} +}; + extern const int opOrder[4]; extern const char* noteNames[180]; extern const char* noteNamesG[180]; @@ -27,6 +35,6 @@ extern const char* insTypes[]; extern const char* sampleDepths[17]; extern const char* resampleStrats[]; extern const int availableSystems[]; -extern const char* guiActions[][2]; +extern const FurnaceGUIActionDef guiActions[]; extern const int altValues[24]; extern const int vgmVersions[6]; \ No newline at end of file diff --git a/src/gui/midiMap.cpp b/src/gui/midiMap.cpp index a38fe11b7..071540f92 100644 --- a/src/gui/midiMap.cpp +++ b/src/gui/midiMap.cpp @@ -165,7 +165,7 @@ bool MIDIMap::read(String path) { bool foundAction=false; for (int i=0; i #include -#ifdef __APPLE__ -#define FURKMOD_CMD FURKMOD_META -#else -#define FURKMOD_CMD FURKMOD_CTRL -#endif - #define DEFAULT_NOTE_KEYS "5:7;6:4;7:3;8:16;10:6;11:8;12:24;13:10;16:11;17:9;18:26;19:28;20:12;21:17;22:1;23:19;24:23;25:5;26:14;27:2;28:21;29:0;30:100;31:13;32:15;34:18;35:20;36:22;38:25;39:27;43:100;46:101;47:29;48:31;53:102;" const char* mainFonts[]={ @@ -165,7 +159,7 @@ const char* specificControls[18]={ #define UI_KEYBIND_CONFIG(what) \ ImGui::TableNextRow(); \ ImGui::TableNextColumn(); \ - ImGui::TextUnformatted(guiActions[what][1]); \ + ImGui::TextUnformatted(guiActions[what].friendlyName); \ ImGui::TableNextColumn(); \ if (ImGui::Button(fmt::sprintf("%s##KC_" #what,(bindSetPending && bindSetTarget==what)?"Press key...":getKeyName(actionKeys[what])).c_str())) { \ promptKey(what); \ @@ -586,16 +580,16 @@ void FurnaceGUI::drawSettings() { ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::BeginCombo("##BAction",(bind.action==0)?"--none--":guiActions[bind.action][1])) { + if (ImGui::BeginCombo("##BAction",(bind.action==0)?"--none--":guiActions[bind.action].friendlyName)) { if (ImGui::Selectable("--none--",bind.action==0)) { bind.action=0; } for (int j=0; jgetConfInt("keybind_" #x,y); - #define clampSetting(x,minV,maxV) \ if (xgetConfInt(String("keybind_GUI_ACTION_")+String(guiActions[i].name),guiActions[i].defaultBind); + } decodeKeyMap(noteKeys,e->getConfString("noteKeys",DEFAULT_NOTE_KEYS)); @@ -1804,181 +1637,11 @@ void FurnaceGUI::commitSettings() { PUT_UI_COLOR(GUI_COLOR_EE_VALUE); PUT_UI_COLOR(GUI_COLOR_PLAYBACK_STAT); - SAVE_KEYBIND(GUI_ACTION_OPEN); - SAVE_KEYBIND(GUI_ACTION_OPEN_BACKUP); - SAVE_KEYBIND(GUI_ACTION_SAVE); - SAVE_KEYBIND(GUI_ACTION_SAVE_AS); - SAVE_KEYBIND(GUI_ACTION_UNDO); - SAVE_KEYBIND(GUI_ACTION_REDO); - SAVE_KEYBIND(GUI_ACTION_PLAY_TOGGLE); - SAVE_KEYBIND(GUI_ACTION_PLAY); - SAVE_KEYBIND(GUI_ACTION_STOP); - SAVE_KEYBIND(GUI_ACTION_PLAY_REPEAT); - SAVE_KEYBIND(GUI_ACTION_PLAY_CURSOR); - SAVE_KEYBIND(GUI_ACTION_STEP_ONE); - SAVE_KEYBIND(GUI_ACTION_OCTAVE_UP); - SAVE_KEYBIND(GUI_ACTION_OCTAVE_DOWN); - SAVE_KEYBIND(GUI_ACTION_INS_UP); - SAVE_KEYBIND(GUI_ACTION_INS_DOWN); - SAVE_KEYBIND(GUI_ACTION_STEP_UP); - SAVE_KEYBIND(GUI_ACTION_STEP_DOWN); - SAVE_KEYBIND(GUI_ACTION_TOGGLE_EDIT); - SAVE_KEYBIND(GUI_ACTION_METRONOME); - SAVE_KEYBIND(GUI_ACTION_REPEAT_PATTERN); - SAVE_KEYBIND(GUI_ACTION_FOLLOW_ORDERS); - SAVE_KEYBIND(GUI_ACTION_FOLLOW_PATTERN); - SAVE_KEYBIND(GUI_ACTION_PANIC); - - SAVE_KEYBIND(GUI_ACTION_WINDOW_EDIT_CONTROLS); - SAVE_KEYBIND(GUI_ACTION_WINDOW_ORDERS); - SAVE_KEYBIND(GUI_ACTION_WINDOW_INS_LIST); - SAVE_KEYBIND(GUI_ACTION_WINDOW_INS_EDIT); - SAVE_KEYBIND(GUI_ACTION_WINDOW_SONG_INFO); - SAVE_KEYBIND(GUI_ACTION_WINDOW_PATTERN); - SAVE_KEYBIND(GUI_ACTION_WINDOW_WAVE_LIST); - SAVE_KEYBIND(GUI_ACTION_WINDOW_WAVE_EDIT); - SAVE_KEYBIND(GUI_ACTION_WINDOW_SAMPLE_LIST); - SAVE_KEYBIND(GUI_ACTION_WINDOW_SAMPLE_EDIT); - SAVE_KEYBIND(GUI_ACTION_WINDOW_ABOUT); - SAVE_KEYBIND(GUI_ACTION_WINDOW_SETTINGS); - SAVE_KEYBIND(GUI_ACTION_WINDOW_MIXER); - SAVE_KEYBIND(GUI_ACTION_WINDOW_DEBUG); - SAVE_KEYBIND(GUI_ACTION_WINDOW_OSCILLOSCOPE); - SAVE_KEYBIND(GUI_ACTION_WINDOW_VOL_METER); - SAVE_KEYBIND(GUI_ACTION_WINDOW_STATS); - SAVE_KEYBIND(GUI_ACTION_WINDOW_COMPAT_FLAGS); - SAVE_KEYBIND(GUI_ACTION_WINDOW_PIANO); - SAVE_KEYBIND(GUI_ACTION_WINDOW_NOTES); - SAVE_KEYBIND(GUI_ACTION_WINDOW_CHANNELS); - SAVE_KEYBIND(GUI_ACTION_WINDOW_REGISTER_VIEW); - - SAVE_KEYBIND(GUI_ACTION_COLLAPSE_WINDOW); - SAVE_KEYBIND(GUI_ACTION_CLOSE_WINDOW); - - SAVE_KEYBIND(GUI_ACTION_PAT_NOTE_UP); - SAVE_KEYBIND(GUI_ACTION_PAT_NOTE_DOWN); - SAVE_KEYBIND(GUI_ACTION_PAT_OCTAVE_UP); - SAVE_KEYBIND(GUI_ACTION_PAT_OCTAVE_DOWN); - SAVE_KEYBIND(GUI_ACTION_PAT_SELECT_ALL); - SAVE_KEYBIND(GUI_ACTION_PAT_CUT); - SAVE_KEYBIND(GUI_ACTION_PAT_COPY); - SAVE_KEYBIND(GUI_ACTION_PAT_PASTE); - SAVE_KEYBIND(GUI_ACTION_PAT_CURSOR_UP); - SAVE_KEYBIND(GUI_ACTION_PAT_CURSOR_DOWN); - SAVE_KEYBIND(GUI_ACTION_PAT_CURSOR_LEFT); - SAVE_KEYBIND(GUI_ACTION_PAT_CURSOR_RIGHT); - SAVE_KEYBIND(GUI_ACTION_PAT_CURSOR_UP_ONE); - SAVE_KEYBIND(GUI_ACTION_PAT_CURSOR_DOWN_ONE); - SAVE_KEYBIND(GUI_ACTION_PAT_CURSOR_LEFT_CHANNEL); - SAVE_KEYBIND(GUI_ACTION_PAT_CURSOR_RIGHT_CHANNEL); - SAVE_KEYBIND(GUI_ACTION_PAT_CURSOR_NEXT_CHANNEL); - SAVE_KEYBIND(GUI_ACTION_PAT_CURSOR_PREVIOUS_CHANNEL); - SAVE_KEYBIND(GUI_ACTION_PAT_CURSOR_BEGIN); - SAVE_KEYBIND(GUI_ACTION_PAT_CURSOR_END); - SAVE_KEYBIND(GUI_ACTION_PAT_CURSOR_UP_COARSE); - SAVE_KEYBIND(GUI_ACTION_PAT_CURSOR_DOWN_COARSE); - SAVE_KEYBIND(GUI_ACTION_PAT_SELECTION_UP); - SAVE_KEYBIND(GUI_ACTION_PAT_SELECTION_DOWN); - SAVE_KEYBIND(GUI_ACTION_PAT_SELECTION_LEFT); - SAVE_KEYBIND(GUI_ACTION_PAT_SELECTION_RIGHT); - SAVE_KEYBIND(GUI_ACTION_PAT_SELECTION_UP_ONE); - SAVE_KEYBIND(GUI_ACTION_PAT_SELECTION_DOWN_ONE); - SAVE_KEYBIND(GUI_ACTION_PAT_SELECTION_BEGIN); - SAVE_KEYBIND(GUI_ACTION_PAT_SELECTION_END); - SAVE_KEYBIND(GUI_ACTION_PAT_SELECTION_UP_COARSE); - SAVE_KEYBIND(GUI_ACTION_PAT_SELECTION_DOWN_COARSE); - SAVE_KEYBIND(GUI_ACTION_PAT_DELETE); - SAVE_KEYBIND(GUI_ACTION_PAT_PULL_DELETE); - SAVE_KEYBIND(GUI_ACTION_PAT_INSERT); - SAVE_KEYBIND(GUI_ACTION_PAT_MUTE_CURSOR); - SAVE_KEYBIND(GUI_ACTION_PAT_SOLO_CURSOR); - SAVE_KEYBIND(GUI_ACTION_PAT_UNMUTE_ALL); - SAVE_KEYBIND(GUI_ACTION_PAT_NEXT_ORDER); - SAVE_KEYBIND(GUI_ACTION_PAT_PREV_ORDER); - SAVE_KEYBIND(GUI_ACTION_PAT_COLLAPSE); - SAVE_KEYBIND(GUI_ACTION_PAT_INCREASE_COLUMNS); - SAVE_KEYBIND(GUI_ACTION_PAT_DECREASE_COLUMNS); - - SAVE_KEYBIND(GUI_ACTION_INS_LIST_ADD); - SAVE_KEYBIND(GUI_ACTION_INS_LIST_DUPLICATE); - SAVE_KEYBIND(GUI_ACTION_INS_LIST_OPEN); - SAVE_KEYBIND(GUI_ACTION_INS_LIST_SAVE); - SAVE_KEYBIND(GUI_ACTION_INS_LIST_MOVE_UP); - SAVE_KEYBIND(GUI_ACTION_INS_LIST_MOVE_DOWN); - SAVE_KEYBIND(GUI_ACTION_INS_LIST_DELETE); - SAVE_KEYBIND(GUI_ACTION_INS_LIST_EDIT); - SAVE_KEYBIND(GUI_ACTION_INS_LIST_UP); - SAVE_KEYBIND(GUI_ACTION_INS_LIST_DOWN); - - SAVE_KEYBIND(GUI_ACTION_WAVE_LIST_ADD); - SAVE_KEYBIND(GUI_ACTION_WAVE_LIST_DUPLICATE); - SAVE_KEYBIND(GUI_ACTION_WAVE_LIST_OPEN); - SAVE_KEYBIND(GUI_ACTION_WAVE_LIST_SAVE); - SAVE_KEYBIND(GUI_ACTION_WAVE_LIST_MOVE_UP); - SAVE_KEYBIND(GUI_ACTION_WAVE_LIST_MOVE_DOWN); - SAVE_KEYBIND(GUI_ACTION_WAVE_LIST_DELETE); - SAVE_KEYBIND(GUI_ACTION_WAVE_LIST_EDIT); - SAVE_KEYBIND(GUI_ACTION_WAVE_LIST_UP); - SAVE_KEYBIND(GUI_ACTION_WAVE_LIST_DOWN); - - SAVE_KEYBIND(GUI_ACTION_SAMPLE_LIST_ADD); - SAVE_KEYBIND(GUI_ACTION_SAMPLE_LIST_DUPLICATE); - SAVE_KEYBIND(GUI_ACTION_SAMPLE_LIST_OPEN); - SAVE_KEYBIND(GUI_ACTION_SAMPLE_LIST_SAVE); - SAVE_KEYBIND(GUI_ACTION_SAMPLE_LIST_MOVE_UP); - SAVE_KEYBIND(GUI_ACTION_SAMPLE_LIST_MOVE_DOWN); - SAVE_KEYBIND(GUI_ACTION_SAMPLE_LIST_DELETE); - SAVE_KEYBIND(GUI_ACTION_SAMPLE_LIST_EDIT); - SAVE_KEYBIND(GUI_ACTION_SAMPLE_LIST_UP); - SAVE_KEYBIND(GUI_ACTION_SAMPLE_LIST_DOWN); - SAVE_KEYBIND(GUI_ACTION_SAMPLE_LIST_PREVIEW); - SAVE_KEYBIND(GUI_ACTION_SAMPLE_LIST_STOP_PREVIEW); - - SAVE_KEYBIND(GUI_ACTION_SAMPLE_SELECT); - SAVE_KEYBIND(GUI_ACTION_SAMPLE_DRAW); - SAVE_KEYBIND(GUI_ACTION_SAMPLE_CUT); - SAVE_KEYBIND(GUI_ACTION_SAMPLE_COPY); - SAVE_KEYBIND(GUI_ACTION_SAMPLE_PASTE); - SAVE_KEYBIND(GUI_ACTION_SAMPLE_PASTE_REPLACE); - SAVE_KEYBIND(GUI_ACTION_SAMPLE_PASTE_MIX); - SAVE_KEYBIND(GUI_ACTION_SAMPLE_SELECT_ALL); - SAVE_KEYBIND(GUI_ACTION_SAMPLE_RESIZE); - SAVE_KEYBIND(GUI_ACTION_SAMPLE_RESAMPLE); - SAVE_KEYBIND(GUI_ACTION_SAMPLE_AMPLIFY); - SAVE_KEYBIND(GUI_ACTION_SAMPLE_NORMALIZE); - SAVE_KEYBIND(GUI_ACTION_SAMPLE_FADE_IN); - SAVE_KEYBIND(GUI_ACTION_SAMPLE_FADE_OUT); - SAVE_KEYBIND(GUI_ACTION_SAMPLE_INSERT); - SAVE_KEYBIND(GUI_ACTION_SAMPLE_SILENCE); - SAVE_KEYBIND(GUI_ACTION_SAMPLE_DELETE); - SAVE_KEYBIND(GUI_ACTION_SAMPLE_TRIM); - SAVE_KEYBIND(GUI_ACTION_SAMPLE_REVERSE); - SAVE_KEYBIND(GUI_ACTION_SAMPLE_INVERT); - SAVE_KEYBIND(GUI_ACTION_SAMPLE_SIGN); - SAVE_KEYBIND(GUI_ACTION_SAMPLE_FILTER); - SAVE_KEYBIND(GUI_ACTION_SAMPLE_PREVIEW); - SAVE_KEYBIND(GUI_ACTION_SAMPLE_STOP_PREVIEW); - SAVE_KEYBIND(GUI_ACTION_SAMPLE_ZOOM_IN); - SAVE_KEYBIND(GUI_ACTION_SAMPLE_ZOOM_OUT); - SAVE_KEYBIND(GUI_ACTION_SAMPLE_ZOOM_AUTO); - - SAVE_KEYBIND(GUI_ACTION_ORDERS_UP); - SAVE_KEYBIND(GUI_ACTION_ORDERS_DOWN); - SAVE_KEYBIND(GUI_ACTION_ORDERS_LEFT); - SAVE_KEYBIND(GUI_ACTION_ORDERS_RIGHT); - SAVE_KEYBIND(GUI_ACTION_ORDERS_INCREASE); - SAVE_KEYBIND(GUI_ACTION_ORDERS_DECREASE); - SAVE_KEYBIND(GUI_ACTION_ORDERS_EDIT_MODE); - SAVE_KEYBIND(GUI_ACTION_ORDERS_LINK); - SAVE_KEYBIND(GUI_ACTION_ORDERS_ADD); - SAVE_KEYBIND(GUI_ACTION_ORDERS_DUPLICATE); - SAVE_KEYBIND(GUI_ACTION_ORDERS_DEEP_CLONE); - SAVE_KEYBIND(GUI_ACTION_ORDERS_DUPLICATE_END); - SAVE_KEYBIND(GUI_ACTION_ORDERS_DEEP_CLONE_END); - SAVE_KEYBIND(GUI_ACTION_ORDERS_REMOVE); - SAVE_KEYBIND(GUI_ACTION_ORDERS_MOVE_UP); - SAVE_KEYBIND(GUI_ACTION_ORDERS_MOVE_DOWN); - SAVE_KEYBIND(GUI_ACTION_ORDERS_REPLAY); + // keybinds + for (int i=0; isetConf(String("keybind_GUI_ACTION_")+String(guiActions[i].name),actionKeys[i]); + } parseKeybinds(); From 95ce867ce36266cc214b3218dcf9e30a86140139 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 6 Apr 2022 02:09:53 -0500 Subject: [PATCH 527/637] GUI: add mentions of FDS to wave editor --- src/gui/waveEdit.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/waveEdit.cpp b/src/gui/waveEdit.cpp index 406e7d885..a9efe512e 100644 --- a/src/gui/waveEdit.cpp +++ b/src/gui/waveEdit.cpp @@ -55,7 +55,7 @@ void FurnaceGUI::drawWaveEdit() { DivWavetable* wave=e->song.wave[curWave]; ImGui::Text("Width"); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("use a width of:\n- any on Amiga/N163\n- 32 on Game Boy, PC Engine and WonderSwan\n- 128 on X1-010\nany other widths will be scaled during playback."); + ImGui::SetTooltip("use a width of:\n- any on Amiga/N163\n- 32 on Game Boy, PC Engine and WonderSwan\n- 64 on FDS\n- 128 on X1-010\nany other widths will be scaled during playback."); } ImGui::SameLine(); ImGui::SetNextItemWidth(128.0f*dpiScale); @@ -69,7 +69,7 @@ void FurnaceGUI::drawWaveEdit() { ImGui::SameLine(); ImGui::Text("Height"); if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("use a height of:\n- 15 for Game Boy, WonderSwan, X1-010 Envelope shape and N163\n- 31 for PC Engine\n- 255 for X1-010\nany other heights will be scaled during playback."); + ImGui::SetTooltip("use a height of:\n- 15 for Game Boy, WonderSwan, X1-010 Envelope shape and N163\n- 31 for PC Engine\n- 63 for FDS\n- 255 for X1-010\nany other heights will be scaled during playback."); } ImGui::SameLine(); ImGui::SetNextItemWidth(128.0f*dpiScale); From 132c5007fa626886ce514562c9a9aad0db30ddd9 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 6 Apr 2022 02:30:12 -0500 Subject: [PATCH 528/637] GUI: simplify color load/save code they are in guiConst.cpp now too. --- src/gui/guiConst.cpp | 112 ++++++++++++++++++++++ src/gui/guiConst.h | 9 ++ src/gui/settings.cpp | 218 ++----------------------------------------- 3 files changed, 129 insertions(+), 210 deletions(-) diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index 33974bba8..6c320320c 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -350,6 +350,118 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={ }; #undef D +#define D(x,y,z) FurnaceGUIColorDef(#x,y,ImGui::ColorConvertFloat4ToU32(z)) + +const FurnaceGUIColorDef guiColors[GUI_COLOR_MAX]={ + D(GUI_COLOR_BACKGROUND,"Background",ImVec4(0.1f,0.1f,0.1f,1.0f)), + D(GUI_COLOR_FRAME_BACKGROUND,"",ImVec4(0.0f,0.0f,0.0f,0.85f)), + D(GUI_COLOR_MODAL_BACKDROP,"",ImVec4(0.0f,0.0f,0.0f,0.55f)), + D(GUI_COLOR_HEADER,"",ImVec4(0.2f,0.2f,0.2f,1.0f)), + D(GUI_COLOR_TEXT,"",ImVec4(1.0f,1.0f,1.0f,1.0f)), + D(GUI_COLOR_ACCENT_PRIMARY,"",ImVec4(0.06f,0.53f,0.98f,1.0f)), + D(GUI_COLOR_ACCENT_SECONDARY,"",ImVec4(0.26f,0.59f,0.98f,1.0f)), + D(GUI_COLOR_BORDER,"",ImVec4(0.43f,0.43f,0.5f,0.5f)), + D(GUI_COLOR_BORDER_SHADOW,"",ImVec4(0.0f,0.0f,0.0f,0.0f)), + D(GUI_COLOR_TOGGLE_OFF,"",ImVec4(0.2f,0.2f,0.2f,1.0f)), + D(GUI_COLOR_TOGGLE_ON,"",ImVec4(0.2f,0.6f,0.2f,1.0f)), + D(GUI_COLOR_EDITING,"",ImVec4(0.2f,0.1f,0.1f,1.0f)), + D(GUI_COLOR_SONG_LOOP,"",ImVec4(0.3f,0.5f,0.8f,0.4f)), + + D(GUI_COLOR_FILE_DIR,"",ImVec4(0.0f,1.0f,1.0f,1.0f)), + D(GUI_COLOR_FILE_SONG_NATIVE,"",ImVec4(0.5f,1.0f,0.5f,1.0f)), + D(GUI_COLOR_FILE_SONG_IMPORT,"",ImVec4(0.5f,1.0f,0.8f,1.0f)), + D(GUI_COLOR_FILE_INSTR,"",ImVec4(1.0f,0.5f,0.5f,1.0f)), + D(GUI_COLOR_FILE_AUDIO,"",ImVec4(1.0f,1.0f,0.5f,1.0f)), + D(GUI_COLOR_FILE_WAVE,"",ImVec4(1.0f,0.75f,0.5f,1.0f)), + D(GUI_COLOR_FILE_VGM,"",ImVec4(1.0f,1.0f,0.5f,1.0f)), + D(GUI_COLOR_FILE_FONT,"",ImVec4(0.3f,1.0f,0.6f,1.0f)), + D(GUI_COLOR_FILE_OTHER,"",ImVec4(0.7f,0.7f,0.7f,1.0f)), + + D(GUI_COLOR_VOLMETER_LOW,"",ImVec4(0.2f,0.6f,0.2f,1.0f)), + D(GUI_COLOR_VOLMETER_HIGH,"",ImVec4(1.0f,0.9f,0.2f,1.0f)), + D(GUI_COLOR_VOLMETER_PEAK,"",ImVec4(1.0f,0.1f,0.1f,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_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_MACRO_VOLUME,"",ImVec4(0.2f,1.0f,0.0f,1.0f)), + D(GUI_COLOR_MACRO_PITCH,"",ImVec4(1.0f,0.8f,0.0f,1.0f)), + D(GUI_COLOR_MACRO_OTHER,"",ImVec4(0.0f,0.9f,1.0f,1.0f)), + D(GUI_COLOR_MACRO_WAVE,"",ImVec4(1.0f,0.4f,0.0f,1.0f)), + + D(GUI_COLOR_INSTR_FM,"",ImVec4(0.6f,0.9f,1.0f,1.0f)), + D(GUI_COLOR_INSTR_STD,"",ImVec4(0.6f,1.0f,0.5f,1.0f)), + D(GUI_COLOR_INSTR_GB,"",ImVec4(1.0f,1.0f,0.5f,1.0f)), + D(GUI_COLOR_INSTR_C64,"",ImVec4(0.85f,0.8f,1.0f,1.0f)), + D(GUI_COLOR_INSTR_AMIGA,"",ImVec4(1.0f,0.5f,0.5f,1.0f)), + D(GUI_COLOR_INSTR_PCE,"",ImVec4(1.0f,0.8f,0.5f,1.0f)), + D(GUI_COLOR_INSTR_AY,"",ImVec4(1.0f,0.5f,1.0f,1.0f)), + D(GUI_COLOR_INSTR_AY8930,"",ImVec4(0.7f,0.5f,1.0f,1.0f)), + D(GUI_COLOR_INSTR_TIA,"",ImVec4(1.0f,0.6f,0.4f,1.0f)), + D(GUI_COLOR_INSTR_SAA1099,"",ImVec4(0.3f,0.3f,1.0f,1.0f)), + D(GUI_COLOR_INSTR_VIC,"",ImVec4(0.2f,1.0f,0.6f,1.0f)), + D(GUI_COLOR_INSTR_PET,"",ImVec4(1.0f,1.0f,0.8f,1.0f)), + D(GUI_COLOR_INSTR_VRC6,"",ImVec4(1.0f,0.9f,0.5f,1.0f)), + D(GUI_COLOR_INSTR_VRC6_SAW,"",ImVec4(0.8f,0.3f,0.0f,1.0f)), + D(GUI_COLOR_INSTR_OPLL,"",ImVec4(0.6f,0.7f,1.0f,1.0f)), + D(GUI_COLOR_INSTR_OPL,"",ImVec4(0.3f,1.0f,0.9f,1.0f)), + D(GUI_COLOR_INSTR_FDS,"",ImVec4(0.8f,0.5f,1.0f,1.0f)), + D(GUI_COLOR_INSTR_VBOY,"",ImVec4(1.0f,0.1f,0.1f,1.0f)), + D(GUI_COLOR_INSTR_N163,"",ImVec4(1.0f,0.4f,0.1f,1.0f)), + D(GUI_COLOR_INSTR_SCC,"",ImVec4(0.7f,1.0f,0.3f,1.0f)), + D(GUI_COLOR_INSTR_OPZ,"",ImVec4(0.2f,0.8f,1.0f,1.0f)), + D(GUI_COLOR_INSTR_POKEY,"",ImVec4(0.5f,1.0f,0.3f,1.0f)), + D(GUI_COLOR_INSTR_BEEPER,"",ImVec4(0.0f,1.0f,0.0f,1.0f)), + D(GUI_COLOR_INSTR_SWAN,"",ImVec4(0.3f,0.5f,1.0f,1.0f)), + D(GUI_COLOR_INSTR_MIKEY,"",ImVec4(0.5f,1.0f,0.3f,1.0f)), + D(GUI_COLOR_INSTR_VERA,"",ImVec4(0.4f,0.6f,1.0f,1.0f)), + D(GUI_COLOR_INSTR_X1_010,"",ImVec4(0.3f,0.5f,1.0f,1.0f)), + D(GUI_COLOR_INSTR_UNKNOWN,"",ImVec4(0.3f,0.3f,0.3f,1.0f)), + + D(GUI_COLOR_CHANNEL_FM,"",ImVec4(0.2f,0.8f,1.0f,1.0f)), + D(GUI_COLOR_CHANNEL_PULSE,"",ImVec4(0.4f,1.0f,0.2f,1.0f)), + D(GUI_COLOR_CHANNEL_NOISE,"",ImVec4(0.8f,0.8f,0.8f,1.0f)), + D(GUI_COLOR_CHANNEL_WAVE,"",ImVec4(1.0f,0.9f,0.2f,1.0f)), + D(GUI_COLOR_CHANNEL_PCM,"",ImVec4(1.0f,0.5f,0.2f,1.0f)), + D(GUI_COLOR_CHANNEL_OP,"",ImVec4(0.2f,0.4f,1.0f,1.0f)), + D(GUI_COLOR_CHANNEL_MUTED,"",ImVec4(0.5f,0.5f,0.5f,1.0f)), + + D(GUI_COLOR_PATTERN_PLAY_HEAD,"",ImVec4(1.0f,1.0f,1.0f,0.25f)), + D(GUI_COLOR_PATTERN_CURSOR,"",ImVec4(0.1f,0.3f,0.5f,1.0f)), + D(GUI_COLOR_PATTERN_CURSOR_HOVER,"",ImVec4(0.2f,0.4f,0.6f,1.0f)), + D(GUI_COLOR_PATTERN_CURSOR_ACTIVE,"",ImVec4(0.2f,0.5f,0.7f,1.0f)), + D(GUI_COLOR_PATTERN_SELECTION,"",ImVec4(0.15f,0.15f,0.2f,1.0f)), + D(GUI_COLOR_PATTERN_SELECTION_HOVER,"",ImVec4(0.2f,0.2f,0.3f,1.0f)), + D(GUI_COLOR_PATTERN_SELECTION_ACTIVE,"",ImVec4(0.4f,0.4f,0.5f,1.0f)), + D(GUI_COLOR_PATTERN_HI_1,"",ImVec4(0.6f,0.6f,0.6f,0.2f)), + D(GUI_COLOR_PATTERN_HI_2,"",ImVec4(0.5f,0.8f,1.0f,0.2f)), + D(GUI_COLOR_PATTERN_ROW_INDEX,"",ImVec4(0.5f,0.8f,1.0f,1.0f)), + D(GUI_COLOR_PATTERN_ACTIVE,"",ImVec4(1.0f,1.0f,1.0f,1.0f)), + D(GUI_COLOR_PATTERN_INACTIVE,"",ImVec4(0.5f,0.5f,0.5f,1.0f)), + D(GUI_COLOR_PATTERN_INS,"",ImVec4(0.4f,0.7f,1.0f,1.0f)), + D(GUI_COLOR_PATTERN_INS_WARN,"",ImVec4(1.0f,1.0f,0.1f,1.0f)), + D(GUI_COLOR_PATTERN_INS_ERROR,"",ImVec4(1.0f,0.1f,0.1f,1.0f)), + D(GUI_COLOR_PATTERN_VOLUME_MAX,"",ImVec4(0.0f,1.0f,0.0f,1.0f)), + D(GUI_COLOR_PATTERN_VOLUME_HALF,"",ImVec4(0.0f,0.75f,0.0f,1.0f)), + D(GUI_COLOR_PATTERN_VOLUME_MIN,"",ImVec4(0.0f,0.5f,0.0f,1.0f)), + D(GUI_COLOR_PATTERN_EFFECT_INVALID,"",ImVec4(1.0f,0.0f,0.0f,1.0f)), + D(GUI_COLOR_PATTERN_EFFECT_PITCH,"",ImVec4(1.0f,1.0f,0.0f,1.0f)), + D(GUI_COLOR_PATTERN_EFFECT_VOLUME,"",ImVec4(0.0f,1.0f,0.0f,1.0f)), + D(GUI_COLOR_PATTERN_EFFECT_PANNING,"",ImVec4(0.0f,1.0f,1.0f,1.0f)), + D(GUI_COLOR_PATTERN_EFFECT_SONG,"",ImVec4(1.0f,0.0f,0.0f,1.0f)), + D(GUI_COLOR_PATTERN_EFFECT_TIME,"",ImVec4(0.5f,0.0f,1.0f,1.0f)), + D(GUI_COLOR_PATTERN_EFFECT_SPEED,"",ImVec4(1.0f,0.0f,1.0f,1.0f)), + D(GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,"",ImVec4(0.5f,1.0f,0.0f,1.0f)), + D(GUI_COLOR_PATTERN_EFFECT_SYS_SECONDARY,"",ImVec4(0.0f,1.0f,0.5f,1.0f)), + D(GUI_COLOR_PATTERN_EFFECT_MISC,"",ImVec4(0.3f,0.3f,1.0f,1.0f)), + + D(GUI_COLOR_EE_VALUE,"",ImVec4(0.0f,1.0f,1.0f,1.0f)), + D(GUI_COLOR_PLAYBACK_STAT,"",ImVec4(0.6f,0.6f,0.6f,1.0f)), +}; +#undef D + // define systems. const int availableSystems[]={ DIV_SYSTEM_YM2612, diff --git a/src/gui/guiConst.h b/src/gui/guiConst.h index ef41650af..93dd143a0 100644 --- a/src/gui/guiConst.h +++ b/src/gui/guiConst.h @@ -27,6 +27,14 @@ struct FurnaceGUIActionDef { name(n), friendlyName(fn), defaultBind(db) {} }; +struct FurnaceGUIColorDef { + const char* name; + const char* friendlyName; + unsigned int defaultColor; + FurnaceGUIColorDef(const char* n, const char* fn, unsigned int dc): + name(n), friendlyName(fn), defaultColor(dc) {} +}; + extern const int opOrder[4]; extern const char* noteNames[180]; extern const char* noteNamesG[180]; @@ -36,5 +44,6 @@ extern const char* sampleDepths[17]; extern const char* resampleStrats[]; extern const int availableSystems[]; extern const FurnaceGUIActionDef guiActions[]; +extern const FurnaceGUIColorDef guiColors[]; extern const int altValues[24]; extern const int vgmVersions[6]; \ No newline at end of file diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 1e75094ff..1cc0686d0 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -1477,9 +1477,6 @@ void FurnaceGUI::syncSettings() { e->setMidiDirect(midiMap.directChannel); } -#define PUT_UI_COLOR(source) e->setConf(#source,(int)ImGui::GetColorU32(uiColors[source])); -#define SAVE_KEYBIND(x) e->setConf("keybind_" #x,actionKeys[x]); - void FurnaceGUI::commitSettings() { e->setConf("mainFontSize",settings.mainFontSize); e->setConf("patFontSize",settings.patFontSize); @@ -1538,104 +1535,10 @@ void FurnaceGUI::commitSettings() { e->setConf("titleBarSys",settings.titleBarSys); e->setConf("frameBorders",settings.frameBorders); - PUT_UI_COLOR(GUI_COLOR_BACKGROUND); - PUT_UI_COLOR(GUI_COLOR_FRAME_BACKGROUND); - PUT_UI_COLOR(GUI_COLOR_MODAL_BACKDROP); - PUT_UI_COLOR(GUI_COLOR_HEADER); - PUT_UI_COLOR(GUI_COLOR_TEXT); - PUT_UI_COLOR(GUI_COLOR_ACCENT_PRIMARY); - PUT_UI_COLOR(GUI_COLOR_ACCENT_SECONDARY); - PUT_UI_COLOR(GUI_COLOR_BORDER); - PUT_UI_COLOR(GUI_COLOR_BORDER_SHADOW); - PUT_UI_COLOR(GUI_COLOR_TOGGLE_ON); - PUT_UI_COLOR(GUI_COLOR_TOGGLE_OFF); - PUT_UI_COLOR(GUI_COLOR_EDITING); - PUT_UI_COLOR(GUI_COLOR_SONG_LOOP); - PUT_UI_COLOR(GUI_COLOR_FILE_DIR); - PUT_UI_COLOR(GUI_COLOR_FILE_SONG_NATIVE); - PUT_UI_COLOR(GUI_COLOR_FILE_SONG_IMPORT); - PUT_UI_COLOR(GUI_COLOR_FILE_INSTR); - PUT_UI_COLOR(GUI_COLOR_FILE_AUDIO); - PUT_UI_COLOR(GUI_COLOR_FILE_WAVE); - PUT_UI_COLOR(GUI_COLOR_FILE_VGM); - PUT_UI_COLOR(GUI_COLOR_FILE_FONT); - PUT_UI_COLOR(GUI_COLOR_FILE_OTHER); - PUT_UI_COLOR(GUI_COLOR_VOLMETER_LOW); - PUT_UI_COLOR(GUI_COLOR_VOLMETER_HIGH); - PUT_UI_COLOR(GUI_COLOR_VOLMETER_PEAK); - PUT_UI_COLOR(GUI_COLOR_ORDER_ROW_INDEX); - PUT_UI_COLOR(GUI_COLOR_ORDER_ACTIVE); - PUT_UI_COLOR(GUI_COLOR_ORDER_SIMILAR); - PUT_UI_COLOR(GUI_COLOR_ORDER_INACTIVE); - PUT_UI_COLOR(GUI_COLOR_MACRO_VOLUME); - PUT_UI_COLOR(GUI_COLOR_MACRO_PITCH); - PUT_UI_COLOR(GUI_COLOR_MACRO_OTHER); - PUT_UI_COLOR(GUI_COLOR_MACRO_WAVE); - PUT_UI_COLOR(GUI_COLOR_INSTR_FM); - PUT_UI_COLOR(GUI_COLOR_INSTR_STD); - PUT_UI_COLOR(GUI_COLOR_INSTR_GB); - PUT_UI_COLOR(GUI_COLOR_INSTR_C64); - PUT_UI_COLOR(GUI_COLOR_INSTR_AMIGA); - PUT_UI_COLOR(GUI_COLOR_INSTR_PCE); - PUT_UI_COLOR(GUI_COLOR_INSTR_AY); - PUT_UI_COLOR(GUI_COLOR_INSTR_AY8930); - PUT_UI_COLOR(GUI_COLOR_INSTR_TIA); - PUT_UI_COLOR(GUI_COLOR_INSTR_SAA1099); - PUT_UI_COLOR(GUI_COLOR_INSTR_VIC); - PUT_UI_COLOR(GUI_COLOR_INSTR_PET); - PUT_UI_COLOR(GUI_COLOR_INSTR_VRC6); - PUT_UI_COLOR(GUI_COLOR_INSTR_VRC6_SAW); - PUT_UI_COLOR(GUI_COLOR_INSTR_OPLL); - PUT_UI_COLOR(GUI_COLOR_INSTR_OPL); - PUT_UI_COLOR(GUI_COLOR_INSTR_FDS); - PUT_UI_COLOR(GUI_COLOR_INSTR_VBOY); - PUT_UI_COLOR(GUI_COLOR_INSTR_N163); - PUT_UI_COLOR(GUI_COLOR_INSTR_SCC); - PUT_UI_COLOR(GUI_COLOR_INSTR_OPZ); - PUT_UI_COLOR(GUI_COLOR_INSTR_POKEY); - PUT_UI_COLOR(GUI_COLOR_INSTR_BEEPER); - PUT_UI_COLOR(GUI_COLOR_INSTR_SWAN); - PUT_UI_COLOR(GUI_COLOR_INSTR_MIKEY); - PUT_UI_COLOR(GUI_COLOR_INSTR_VERA); - PUT_UI_COLOR(GUI_COLOR_INSTR_X1_010); - PUT_UI_COLOR(GUI_COLOR_INSTR_UNKNOWN); - PUT_UI_COLOR(GUI_COLOR_CHANNEL_FM); - PUT_UI_COLOR(GUI_COLOR_CHANNEL_PULSE); - PUT_UI_COLOR(GUI_COLOR_CHANNEL_NOISE); - PUT_UI_COLOR(GUI_COLOR_CHANNEL_PCM); - PUT_UI_COLOR(GUI_COLOR_CHANNEL_WAVE); - PUT_UI_COLOR(GUI_COLOR_CHANNEL_OP); - PUT_UI_COLOR(GUI_COLOR_CHANNEL_MUTED); - PUT_UI_COLOR(GUI_COLOR_PATTERN_PLAY_HEAD); - PUT_UI_COLOR(GUI_COLOR_PATTERN_CURSOR); - PUT_UI_COLOR(GUI_COLOR_PATTERN_CURSOR_HOVER); - PUT_UI_COLOR(GUI_COLOR_PATTERN_CURSOR_ACTIVE); - PUT_UI_COLOR(GUI_COLOR_PATTERN_SELECTION); - PUT_UI_COLOR(GUI_COLOR_PATTERN_SELECTION_HOVER); - PUT_UI_COLOR(GUI_COLOR_PATTERN_SELECTION_ACTIVE); - PUT_UI_COLOR(GUI_COLOR_PATTERN_HI_1); - PUT_UI_COLOR(GUI_COLOR_PATTERN_HI_2); - PUT_UI_COLOR(GUI_COLOR_PATTERN_ROW_INDEX); - PUT_UI_COLOR(GUI_COLOR_PATTERN_ACTIVE); - PUT_UI_COLOR(GUI_COLOR_PATTERN_INACTIVE); - PUT_UI_COLOR(GUI_COLOR_PATTERN_INS); - PUT_UI_COLOR(GUI_COLOR_PATTERN_INS_WARN); - PUT_UI_COLOR(GUI_COLOR_PATTERN_INS_ERROR); - PUT_UI_COLOR(GUI_COLOR_PATTERN_VOLUME_MIN); - PUT_UI_COLOR(GUI_COLOR_PATTERN_VOLUME_HALF); - PUT_UI_COLOR(GUI_COLOR_PATTERN_VOLUME_MAX); - PUT_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_INVALID); - PUT_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_PITCH); - PUT_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_VOLUME); - PUT_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_PANNING); - PUT_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_SONG); - PUT_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_TIME); - PUT_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_SPEED); - PUT_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY); - PUT_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_SYS_SECONDARY); - PUT_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_MISC); - PUT_UI_COLOR(GUI_COLOR_EE_VALUE); - PUT_UI_COLOR(GUI_COLOR_PLAYBACK_STAT); + // colors + for (int i=0; isetConf(guiColors[i].name,(int)ImGui::GetColorU32(uiColors[i])); + } // keybinds for (int i=0; igetConfInt(#target,ImGui::GetColorU32(def))); - #ifdef _WIN32 #define SYSTEM_FONT_PATH_1 "C:\\Windows\\Fonts\\segoeui.ttf" #define SYSTEM_FONT_PATH_2 "C:\\Windows\\Fonts\\tahoma.ttf" @@ -1767,112 +1667,10 @@ void FurnaceGUI::applyUISettings() { if (settings.dpiScale>=0.5f) dpiScale=settings.dpiScale; - GET_UI_COLOR(GUI_COLOR_BACKGROUND,ImVec4(0.1f,0.1f,0.1f,1.0f)); - GET_UI_COLOR(GUI_COLOR_FRAME_BACKGROUND,ImVec4(0.0f,0.0f,0.0f,0.85f)); - GET_UI_COLOR(GUI_COLOR_MODAL_BACKDROP,ImVec4(0.0f,0.0f,0.0f,0.55f)); - GET_UI_COLOR(GUI_COLOR_HEADER,ImVec4(0.2f,0.2f,0.2f,1.0f)); - GET_UI_COLOR(GUI_COLOR_TEXT,ImVec4(1.0f,1.0f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_ACCENT_PRIMARY,ImVec4(0.06f,0.53f,0.98f,1.0f)); - GET_UI_COLOR(GUI_COLOR_ACCENT_SECONDARY,ImVec4(0.26f,0.59f,0.98f,1.0f)); - GET_UI_COLOR(GUI_COLOR_BORDER,ImVec4(0.43f,0.43f,0.5f,0.5f)); - GET_UI_COLOR(GUI_COLOR_BORDER_SHADOW,ImVec4(0.0f,0.0f,0.0f,0.0f)); - GET_UI_COLOR(GUI_COLOR_TOGGLE_ON,ImVec4(0.2f,0.6f,0.2f,1.0f)); - GET_UI_COLOR(GUI_COLOR_TOGGLE_OFF,ImVec4(0.2f,0.2f,0.2f,1.0f)); - GET_UI_COLOR(GUI_COLOR_EDITING,ImVec4(0.2f,0.1f,0.1f,1.0f)); - GET_UI_COLOR(GUI_COLOR_SONG_LOOP,ImVec4(0.3f,0.5f,0.8f,0.4f)); - - GET_UI_COLOR(GUI_COLOR_FILE_DIR,ImVec4(0.0f,1.0f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_FILE_SONG_NATIVE,ImVec4(0.5f,1.0f,0.5f,1.0f)); - GET_UI_COLOR(GUI_COLOR_FILE_SONG_IMPORT,ImVec4(0.5f,1.0f,0.8f,1.0f)); - GET_UI_COLOR(GUI_COLOR_FILE_INSTR,ImVec4(1.0f,0.5f,0.5f,1.0f)); - GET_UI_COLOR(GUI_COLOR_FILE_AUDIO,ImVec4(1.0f,1.0f,0.5f,1.0f)); - GET_UI_COLOR(GUI_COLOR_FILE_WAVE,ImVec4(1.0f,0.75f,0.5f,1.0f)); - GET_UI_COLOR(GUI_COLOR_FILE_VGM,ImVec4(1.0f,1.0f,0.5f,1.0f)); - GET_UI_COLOR(GUI_COLOR_FILE_FONT,ImVec4(0.3f,1.0f,0.6f,1.0f)); - GET_UI_COLOR(GUI_COLOR_FILE_OTHER,ImVec4(0.7f,0.7f,0.7f,1.0f)); - - GET_UI_COLOR(GUI_COLOR_VOLMETER_LOW,ImVec4(0.2f,0.6f,0.2f,1.0f)); - GET_UI_COLOR(GUI_COLOR_VOLMETER_HIGH,ImVec4(1.0f,0.9f,0.2f,1.0f)); - GET_UI_COLOR(GUI_COLOR_VOLMETER_PEAK,ImVec4(1.0f,0.1f,0.1f,1.0f)); - - GET_UI_COLOR(GUI_COLOR_ORDER_ROW_INDEX,ImVec4(0.5f,0.8f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_ORDER_ACTIVE,ImVec4(0.4f,0.7f,1.0f,0.25f)); - GET_UI_COLOR(GUI_COLOR_ORDER_SIMILAR,ImVec4(0.5f,1.0f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_ORDER_INACTIVE,ImVec4(1.0f,1.0f,1.0f,1.0f)); - - GET_UI_COLOR(GUI_COLOR_MACRO_VOLUME,ImVec4(0.2f,1.0f,0.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_MACRO_PITCH,ImVec4(1.0f,0.8f,0.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_MACRO_OTHER,ImVec4(0.0f,0.9f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_MACRO_WAVE,ImVec4(1.0f,0.4f,0.0f,1.0f)); - - GET_UI_COLOR(GUI_COLOR_INSTR_FM,ImVec4(0.6f,0.9f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_STD,ImVec4(0.6f,1.0f,0.5f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_GB,ImVec4(1.0f,1.0f,0.5f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_C64,ImVec4(0.85f,0.8f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_AMIGA,ImVec4(1.0f,0.5f,0.5f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_PCE,ImVec4(1.0f,0.8f,0.5f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_AY,ImVec4(1.0f,0.5f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_AY8930,ImVec4(0.7f,0.5f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_TIA,ImVec4(1.0f,0.6f,0.4f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_SAA1099,ImVec4(0.3f,0.3f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_VIC,ImVec4(0.2f,1.0f,0.6f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_PET,ImVec4(1.0f,1.0f,0.8f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_VRC6,ImVec4(1.0f,0.9f,0.5f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_VRC6_SAW,ImVec4(0.8f,0.3f,0.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_OPLL,ImVec4(0.6f,0.7f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_OPL,ImVec4(0.3f,1.0f,0.9f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_FDS,ImVec4(0.8f,0.5f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_VBOY,ImVec4(1.0f,0.1f,0.1f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_N163,ImVec4(1.0f,0.4f,0.1f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_SCC,ImVec4(0.7f,1.0f,0.3f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_OPZ,ImVec4(0.2f,0.8f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_POKEY,ImVec4(0.5f,1.0f,0.3f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_BEEPER,ImVec4(0.0f,1.0f,0.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_SWAN,ImVec4(0.3f,0.5f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_MIKEY,ImVec4(0.5f,1.0f,0.3f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_VERA,ImVec4(0.4f,0.6f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_X1_010,ImVec4(0.3f,0.5f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_INSTR_UNKNOWN,ImVec4(0.3f,0.3f,0.3f,1.0f)); - - GET_UI_COLOR(GUI_COLOR_CHANNEL_FM,ImVec4(0.2f,0.8f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_CHANNEL_PULSE,ImVec4(0.4f,1.0f,0.2f,1.0f)); - GET_UI_COLOR(GUI_COLOR_CHANNEL_NOISE,ImVec4(0.8f,0.8f,0.8f,1.0f)); - GET_UI_COLOR(GUI_COLOR_CHANNEL_PCM,ImVec4(1.0f,0.9f,0.2f,1.0f)); - GET_UI_COLOR(GUI_COLOR_CHANNEL_WAVE,ImVec4(1.0f,0.5f,0.2f,1.0f)); - GET_UI_COLOR(GUI_COLOR_CHANNEL_OP,ImVec4(0.2f,0.4f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_CHANNEL_MUTED,ImVec4(0.5f,0.5f,0.5f,1.0f)); - - GET_UI_COLOR(GUI_COLOR_PATTERN_PLAY_HEAD,ImVec4(1.0f,1.0f,1.0f,0.25f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_CURSOR,ImVec4(0.1f,0.3f,0.5f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_CURSOR_HOVER,ImVec4(0.2f,0.4f,0.6f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_CURSOR_ACTIVE,ImVec4(0.2f,0.5f,0.7f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_SELECTION,ImVec4(0.15f,0.15f,0.2f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_SELECTION_HOVER,ImVec4(0.2f,0.2f,0.3f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_SELECTION_ACTIVE,ImVec4(0.4f,0.4f,0.5f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_HI_1,ImVec4(0.6f,0.6f,0.6f,0.2f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_HI_2,ImVec4(0.5f,0.8f,1.0f,0.2f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_ROW_INDEX,ImVec4(0.5f,0.8f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_ACTIVE,ImVec4(1.0f,1.0f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_INACTIVE,ImVec4(0.5f,0.5f,0.5f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_INS,ImVec4(0.4f,0.7f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_INS_WARN,ImVec4(1.0f,1.0f,0.1f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_INS_ERROR,ImVec4(1.0f,0.1f,0.1f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_VOLUME_MIN,ImVec4(0.0f,0.5f,0.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_VOLUME_HALF,ImVec4(0.0f,0.75f,0.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_VOLUME_MAX,ImVec4(0.0f,1.0f,0.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_INVALID,ImVec4(1.0f,0.0f,0.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_PITCH,ImVec4(1.0f,1.0f,0.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_VOLUME,ImVec4(0.0f,1.0f,0.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_PANNING,ImVec4(0.0f,1.0f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_SONG,ImVec4(1.0f,0.0f,0.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_TIME,ImVec4(0.5f,0.0f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_SPEED,ImVec4(1.0f,0.0f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,ImVec4(0.5f,1.0f,0.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_SYS_SECONDARY,ImVec4(0.0f,1.0f,0.5f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PATTERN_EFFECT_MISC,ImVec4(0.3f,0.3f,1.0f,1.0f)); - - GET_UI_COLOR(GUI_COLOR_EE_VALUE,ImVec4(0.0f,1.0f,1.0f,1.0f)); - GET_UI_COLOR(GUI_COLOR_PLAYBACK_STAT,ImVec4(0.6f,0.6f,0.6f,1.0f)); + // colors + for (int i=0; igetConfInt(guiColors[i].name,guiColors[i].defaultColor)); + } for (int i=0; i<64; i++) { ImVec4 col1=uiColors[GUI_COLOR_PATTERN_VOLUME_MIN]; From 4a131952e4ed84919f24847575064417918e1eb6 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 6 Apr 2022 02:46:09 -0500 Subject: [PATCH 529/637] GUI: ps_fopen --- src/gui/gui.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 2102c99b1..295a06de2 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2697,7 +2697,7 @@ bool FurnaceGUI::loop() { case GUI_FILE_EXPORT_VGM: { SafeWriter* w=e->saveVGM(willExport,vgmExportLoop,vgmExportVersion); if (w!=NULL) { - FILE* f=fopen(copyOfName.c_str(),"wb"); + FILE* f=ps_fopen(copyOfName.c_str(),"wb"); if (f!=NULL) { fwrite(w->getFinalBuf(),1,w->size(),f); fclose(f); From 9d653b58b3d152e2f4236c950b177bd9dac262e8 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 6 Apr 2022 04:24:20 -0500 Subject: [PATCH 530/637] hopefully fix MSVC build --- src/engine/platform/nes.cpp | 2 ++ src/engine/platform/sound/nes/apu.c | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine/platform/nes.cpp b/src/engine/platform/nes.cpp index d04edf4de..f61ed4fb6 100644 --- a/src/engine/platform/nes.cpp +++ b/src/engine/platform/nes.cpp @@ -23,6 +23,8 @@ #include #include +struct _nla_table nla_table; + #define CHIP_DIVIDER 16 #define rWrite(a,v) if (!skipRegisterWrites) {apu_wr_reg(nes,a,v); regPool[(a)&0x7f]=v; if (dumpWrites) {addWrite(a,v);} } diff --git a/src/engine/platform/sound/nes/apu.c b/src/engine/platform/sound/nes/apu.c index e9ceb79a1..8d53e583c 100644 --- a/src/engine/platform/sound/nes/apu.c +++ b/src/engine/platform/sound/nes/apu.c @@ -21,8 +21,6 @@ #include #include "apu.h" -struct _nla_table nla_table; - void apu_tick(struct NESAPU* a, BYTE *hwtick) { /* sottraggo il numero di cicli eseguiti */ a->apu.cycles--; From ba8ee96069f1d40b351d4f05940ebfcf766442db Mon Sep 17 00:00:00 2001 From: Natt Akuma Date: Wed, 6 Apr 2022 19:11:45 +0700 Subject: [PATCH 531/637] VIC-20: Make noise state consistent across inits --- src/engine/platform/sound/vic20sound.c | 21 +++++++++------------ src/engine/platform/sound/vic20sound.h | 4 ++++ src/engine/platform/vic20.cpp | 3 +++ 3 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/engine/platform/sound/vic20sound.c b/src/engine/platform/sound/vic20sound.c index 160151793..a3c90f204 100644 --- a/src/engine/platform/sound/vic20sound.c +++ b/src/engine/platform/sound/vic20sound.c @@ -85,7 +85,7 @@ static float voltagefunction[] = { 29465.88f, 29474.32f, 29482.76f, 29491.20f }; -static void vic_sound_clock(sound_vic20_t *snd, uint32_t cycles); +void vic_sound_clock(sound_vic20_t *snd, uint32_t cycles); int vic_sound_machine_calculate_samples(sound_vic20_t *snd, int16_t *pbuf, int nr, int soc, int scc, uint32_t delta_t) { @@ -128,10 +128,7 @@ int vic_sound_machine_calculate_samples(sound_vic20_t *snd, int16_t *pbuf, int n return s; } -static uint16_t noise_LFSR = 0x0000; -static uint8_t noise_LFSR0_old = 0; - -static void vic_sound_clock(sound_vic20_t *snd, uint32_t cycles) +void vic_sound_clock(sound_vic20_t *snd, uint32_t cycles) { uint32_t i; int j, enabled; @@ -155,7 +152,7 @@ static void vic_sound_clock(sound_vic20_t *snd, uint32_t cycles) a = a ? a : 128; snd->ch[j].ctr += a << chspeed; enabled = (snd->ch[j].reg & 128) >> 7; - edge_trigger = (noise_LFSR & 1) & !noise_LFSR0_old; + edge_trigger = (snd->noise_LFSR & 1) & !snd->noise_LFSR0_old; if((j != 3) || ((j == 3) && edge_trigger)) { uint8_t shift = snd->ch[j].shift; @@ -163,16 +160,16 @@ static void vic_sound_clock(sound_vic20_t *snd, uint32_t cycles) snd->ch[j].shift = shift; } if(j == 3) { - int bit3 = (noise_LFSR >> 3) & 1; - int bit12 = (noise_LFSR >> 12) & 1; - int bit14 = (noise_LFSR >> 14) & 1; - int bit15 = (noise_LFSR >> 15) & 1; + int bit3 = (snd->noise_LFSR >> 3) & 1; + int bit12 = (snd->noise_LFSR >> 12) & 1; + int bit14 = (snd->noise_LFSR >> 14) & 1; + int bit15 = (snd->noise_LFSR >> 15) & 1; int gate1 = bit3 ^ bit12; int gate2 = bit14 ^ bit15; int gate3 = (gate1 ^ gate2) ^ 1; int gate4 = (gate3 & enabled) ^ 1; - noise_LFSR0_old = noise_LFSR & 1; - noise_LFSR = (noise_LFSR << 1) | gate4; + snd->noise_LFSR0_old = snd->noise_LFSR & 1; + snd->noise_LFSR = (snd->noise_LFSR << 1) | gate4; } snd->ch[j].out = snd->ch[j].shift & (j == 3 ? enabled : 1); } diff --git a/src/engine/platform/sound/vic20sound.h b/src/engine/platform/sound/vic20sound.h index 334833304..7685bc555 100644 --- a/src/engine/platform/sound/vic20sound.h +++ b/src/engine/platform/sound/vic20sound.h @@ -56,12 +56,16 @@ struct sound_vic20_s { float highpassbeta; float lowpassbuf; float lowpassbeta; + + uint16_t noise_LFSR; + uint8_t noise_LFSR0_old; }; typedef struct sound_vic20_s sound_vic20_t; int vic_sound_machine_init(sound_vic20_t *snd, int speed, int cycles_per_sec); void vic_sound_machine_store(sound_vic20_t *snd, uint16_t addr, uint8_t value); int vic_sound_machine_calculate_samples(sound_vic20_t *snd, int16_t *pbuf, int nr, int soc, int scc, uint32_t delta_t); +void vic_sound_clock(sound_vic20_t *snd, uint32_t cycles); #ifdef __cplusplus }; diff --git a/src/engine/platform/vic20.cpp b/src/engine/platform/vic20.cpp index b640f780c..7fe13e96f 100644 --- a/src/engine/platform/vic20.cpp +++ b/src/engine/platform/vic20.cpp @@ -282,6 +282,9 @@ void DivPlatformVIC20::reset() { vic_sound_machine_init(vic,rate,chipClock); hasWaveWrite=false; rWrite(14,15); + // hack: starting noise channel right away after this would result in a dead + // channel as the LFSR state is 0, so clock it a bit + vic_sound_clock(vic,4); } bool DivPlatformVIC20::isStereo() { From eb8f99dafec8dc4c1726ccaa8cc72aef5541969f Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 6 Apr 2022 13:53:39 -0500 Subject: [PATCH 532/637] GUI: import/export colors/keybinds/layout untested --- src/gui/gui.cpp | 102 +++++++++++++++++++ src/gui/gui.h | 22 +++- src/gui/settings.cpp | 236 ++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 357 insertions(+), 3 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 295a06de2..c22bbea7a 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1442,6 +1442,66 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { dpiScale ); break; + case GUI_FILE_IMPORT_COLORS: + if (!dirExists(workingDirColors)) workingDirColors=getHomeDir(); + hasOpened=fileDialog->openLoad( + "Select Color File", + {"configuration files", "*.cfg"}, + "configuration files{.cfg}", + workingDirColors, + dpiScale + ); + break; + case GUI_FILE_IMPORT_KEYBINDS: + if (!dirExists(workingDirKeybinds)) workingDirKeybinds=getHomeDir(); + hasOpened=fileDialog->openLoad( + "Select Keybind File", + {"configuration files", "*.cfg"}, + "configuration files{.cfg}", + workingDirKeybinds, + dpiScale + ); + break; + case GUI_FILE_IMPORT_LAYOUT: + if (!dirExists(workingDirKeybinds)) workingDirKeybinds=getHomeDir(); + hasOpened=fileDialog->openLoad( + "Select Layout File", + {".ini files", "*.ini"}, + ".ini files{.ini}", + workingDirKeybinds, + dpiScale + ); + break; + case GUI_FILE_EXPORT_COLORS: + if (!dirExists(workingDirColors)) workingDirColors=getHomeDir(); + hasOpened=fileDialog->openSave( + "Export Colors", + {"configuration files", "*.cfg"}, + "configuration files{.cfg}", + workingDirColors, + dpiScale + ); + break; + case GUI_FILE_EXPORT_KEYBINDS: + if (!dirExists(workingDirKeybinds)) workingDirKeybinds=getHomeDir(); + hasOpened=fileDialog->openSave( + "Export Keybinds", + {"configuration files", "*.cfg"}, + "configuration files{.cfg}", + workingDirKeybinds, + dpiScale + ); + break; + case GUI_FILE_EXPORT_LAYOUT: + if (!dirExists(workingDirKeybinds)) workingDirKeybinds=getHomeDir(); + hasOpened=fileDialog->openSave( + "Export Layout", + {".ini files", "*.ini"}, + ".ini files{.ini}", + workingDirKeybinds, + dpiScale + ); + break; } if (hasOpened) curFileDialog=type; //ImGui::GetIO().ConfigFlags|=ImGuiConfigFlags_NavEnableKeyboard; @@ -2588,6 +2648,18 @@ bool FurnaceGUI::loop() { case GUI_FILE_LOAD_PAT_FONT: workingDirFont=fileDialog->getPath()+DIR_SEPARATOR_STR; break; + case GUI_FILE_IMPORT_COLORS: + case GUI_FILE_EXPORT_COLORS: + workingDirColors=fileDialog->getPath()+DIR_SEPARATOR_STR; + break; + case GUI_FILE_IMPORT_KEYBINDS: + case GUI_FILE_EXPORT_KEYBINDS: + workingDirKeybinds=fileDialog->getPath()+DIR_SEPARATOR_STR; + break; + case GUI_FILE_IMPORT_LAYOUT: + case GUI_FILE_EXPORT_LAYOUT: + workingDirLayout=fileDialog->getPath()+DIR_SEPARATOR_STR; + break; } if (fileDialog->accepted()) { fileName=fileDialog->getFileName(); @@ -2723,6 +2795,24 @@ bool FurnaceGUI::loop() { case GUI_FILE_LOAD_PAT_FONT: settings.patFontPath=copyOfName; break; + case GUI_FILE_IMPORT_COLORS: + importColors(copyOfName); + break; + case GUI_FILE_IMPORT_KEYBINDS: + importKeybinds(copyOfName); + break; + case GUI_FILE_IMPORT_LAYOUT: + importLayout(copyOfName); + break; + case GUI_FILE_EXPORT_COLORS: + exportColors(copyOfName); + break; + case GUI_FILE_EXPORT_KEYBINDS: + exportKeybinds(copyOfName); + break; + case GUI_FILE_EXPORT_LAYOUT: + exportLayout(copyOfName); + break; } curFileDialog=GUI_FILE_OPEN; } @@ -2813,6 +2903,12 @@ bool FurnaceGUI::loop() { ImGui::LoadIniSettingsFromMemory(defaultLayout); ImGui::SaveIniSettingsToDisk(finalLayoutPath); break; + case GUI_WARN_RESET_KEYBINDS: + resetKeybinds(); + break; + case GUI_WARN_RESET_COLORS: + resetColors(); + break; case GUI_WARN_GENERIC: break; } @@ -2899,6 +2995,9 @@ bool FurnaceGUI::init() { workingDirAudioExport=e->getConfString("lastDirAudioExport",workingDir); workingDirVGMExport=e->getConfString("lastDirVGMExport",workingDir); workingDirFont=e->getConfString("lastDirFont",workingDir); + workingDirColors=e->getConfString("lastDirColors",workingDir); + workingDirKeybinds=e->getConfString("lastDirKeybinds",workingDir); + workingDirLayout=e->getConfString("lastDirLayout",workingDir); editControlsOpen=e->getConfBool("editControlsOpen",true); ordersOpen=e->getConfBool("ordersOpen",true); @@ -3057,6 +3156,9 @@ bool FurnaceGUI::finish() { e->setConf("lastDirAudioExport",workingDirAudioExport); e->setConf("lastDirVGMExport",workingDirVGMExport); e->setConf("lastDirFont",workingDirFont); + e->setConf("lastDirColors",workingDirColors); + e->setConf("lastDirKeybinds",workingDirKeybinds); + e->setConf("lastDirLayout",workingDirLayout); // commit last open windows e->setConf("editControlsOpen",editControlsOpen); diff --git a/src/gui/gui.h b/src/gui/gui.h index becd759bc..0bc136ca6 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -198,7 +198,13 @@ enum FurnaceGUIFileDialogs { GUI_FILE_EXPORT_VGM, GUI_FILE_EXPORT_ROM, GUI_FILE_LOAD_MAIN_FONT, - GUI_FILE_LOAD_PAT_FONT + GUI_FILE_LOAD_PAT_FONT, + GUI_FILE_IMPORT_COLORS, + GUI_FILE_IMPORT_KEYBINDS, + GUI_FILE_IMPORT_LAYOUT, + GUI_FILE_EXPORT_COLORS, + GUI_FILE_EXPORT_KEYBINDS, + GUI_FILE_EXPORT_LAYOUT }; enum FurnaceGUIWarnings { @@ -208,6 +214,8 @@ enum FurnaceGUIWarnings { GUI_WARN_OPEN_BACKUP, GUI_WARN_OPEN_DROP, GUI_WARN_RESET_LAYOUT, + GUI_WARN_RESET_COLORS, + GUI_WARN_RESET_KEYBINDS, GUI_WARN_GENERIC }; @@ -648,7 +656,7 @@ class FurnaceGUI { bool updateSampleTex; String workingDir, fileName, clipboard, warnString, errorString, lastError, curFileName, nextFile; - String workingDirSong, workingDirIns, workingDirWave, workingDirSample, workingDirAudioExport, workingDirVGMExport, workingDirFont; + String workingDirSong, workingDirIns, workingDirWave, workingDirSample, workingDirAudioExport, workingDirVGMExport, workingDirFont, workingDirColors, workingDirKeybinds, workingDirLayout; String mmlString[13]; String mmlStringW; @@ -1015,6 +1023,16 @@ class FurnaceGUI { void promptKey(int which); void doAction(int what); + bool importColors(String path); + bool exportColors(String path); + bool importKeybinds(String path); + bool exportKeybinds(String path); + bool importLayout(String path); + bool exportLayout(String path); + + void resetColors(); + void resetKeybinds(); + void syncSettings(); void commitSettings(); void processDrags(int dragX, int dragY); diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 1cc0686d0..f41ed5e13 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -20,6 +20,7 @@ #include "gui.h" #include "fonts.h" #include "../ta-log.h" +#include "../fileutils.h" #include "util.h" #include "guiConst.h" #include "ImGuiFileDialog.h" @@ -870,6 +871,17 @@ void FurnaceGUI::drawSettings() { ImGui::Separator(); if (ImGui::TreeNode("Color scheme")) { + if (ImGui::Button("Import")) { + openFileDialog(GUI_FILE_IMPORT_COLORS); + } + ImGui::SameLine(); + if (ImGui::Button("Export")) { + openFileDialog(GUI_FILE_EXPORT_COLORS); + } + ImGui::SameLine(); + if (ImGui::Button("Reset defaults")) { + showWarning("Are you sure you want to reset the color scheme?",GUI_WARN_RESET_COLORS); + } if (ImGui::TreeNode("General")) { ImGui::Text("Color scheme type:"); if (ImGui::RadioButton("Dark##gcb0",settings.guiColorsBase==0)) { @@ -1005,6 +1017,17 @@ void FurnaceGUI::drawSettings() { ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Keyboard")) { + if (ImGui::Button("Import")) { + openFileDialog(GUI_FILE_IMPORT_KEYBINDS); + } + ImGui::SameLine(); + if (ImGui::Button("Export")) { + openFileDialog(GUI_FILE_EXPORT_KEYBINDS); + } + ImGui::SameLine(); + if (ImGui::Button("Reset defaults")) { + showWarning("Are you sure you want to reset the keyboard settings?",GUI_WARN_RESET_KEYBINDS); + } if (ImGui::TreeNode("Global hotkeys")) { KEYBIND_CONFIG_BEGIN("keysGlobal"); @@ -1537,7 +1560,7 @@ void FurnaceGUI::commitSettings() { // colors for (int i=0; isetConf(guiColors[i].name,(int)ImGui::GetColorU32(uiColors[i])); + e->setConf(guiColors[i].name,(int)ImGui::ColorConvertFloat4ToU32(uiColors[i])); } // keybinds @@ -1577,6 +1600,217 @@ void FurnaceGUI::commitSettings() { } } +bool FurnaceGUI::importColors(String path) { + FILE* f=ps_fopen(path.c_str(),"rb"); + if (f==NULL) { + logW("error while opening color file for import: %s\n",strerror(errno)); + return false; + } + resetColors(); + char line[4096]; + while (!feof(f)) { + String key=""; + String value=""; + bool keyOrValue=false; + if (fgets(line,4095,f)==NULL) { + break; + } + for (char* i=line; *i; i++) { + if (*i=='\n') continue; + if (keyOrValue) { + value+=*i; + } else { + if (*i=='=') { + keyOrValue=true; + } else { + key+=*i; + } + } + } + if (keyOrValue) { + // unoptimal + const char* cs=key.c_str(); + bool found=false; + for (int i=0; i>1)) { + fclose(f); + return false; + } + if (len<1) { + if (len==0) { + logE("that file is empty!\n"); + lastError="file is empty"; + } else { + perror("tell error"); + } + fclose(f); + return false; + } + unsigned char* file=new unsigned char[len]; + if (fseek(f,0,SEEK_SET)<0) { + perror("size error"); + lastError=fmt::sprintf("on get size: %s",strerror(errno)); + fclose(f); + delete[] file; + return false; + } + if (fread(file,1,(size_t)len,f)!=(size_t)len) { + perror("read error"); + lastError=fmt::sprintf("on read: %s",strerror(errno)); + fclose(f); + delete[] file; + return false; + } + fclose(f); + + ImGui::LoadIniSettingsFromMemory((const char*)file,len); + delete[] file; + return true; +} + +bool FurnaceGUI::exportLayout(String path) { + FILE* f=ps_fopen(path.c_str(),"wb"); + if (f==NULL) { + logW("error while opening layout file for export: %s\n",strerror(errno)); + return false; + } + size_t dataSize=0; + const char* data=ImGui::SaveIniSettingsToMemory(&dataSize); + if (fwrite(data,1,dataSize,f)!=dataSize) { + logW("error while exporting layout: %s\n",strerror(errno)); + } + fclose(f); + return true; +} + +void FurnaceGUI::resetColors() { + for (int i=0; i Date: Wed, 6 Apr 2022 14:11:58 -0500 Subject: [PATCH 533/637] GUI: I forgot about layout! --- src/gui/settings.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index f41ed5e13..5089cc432 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -206,6 +206,19 @@ void FurnaceGUI::drawSettings() { if (ImGui::Begin("Settings",NULL,ImGuiWindowFlags_NoDocking)) { if (ImGui::BeginTabBar("settingsTab")) { if (ImGui::BeginTabItem("General")) { + ImGui::Text("Workspace layout"); + if (ImGui::Button("Import")) { + openFileDialog(GUI_FILE_IMPORT_LAYOUT); + } + ImGui::SameLine(); + if (ImGui::Button("Export")) { + openFileDialog(GUI_FILE_EXPORT_LAYOUT); + } + ImGui::SameLine(); + if (ImGui::Button("Reset")) { + showWarning("Are you sure you want to reset the workspace layout?",GUI_WARN_RESET_LAYOUT); + } + ImGui::Separator(); ImGui::Text("Toggle channel solo on:"); if (ImGui::RadioButton("Right-click or double-click##soloA",settings.soloAction==0)) { settings.soloAction=0; From 4d23c1dc6dd238cebb7fa6f95388ef64a50e78ee Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 6 Apr 2022 14:17:29 -0500 Subject: [PATCH 534/637] new extension for colors and keybinds --- src/gui/gui.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index c22bbea7a..efcf2eb81 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1446,8 +1446,8 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { if (!dirExists(workingDirColors)) workingDirColors=getHomeDir(); hasOpened=fileDialog->openLoad( "Select Color File", - {"configuration files", "*.cfg"}, - "configuration files{.cfg}", + {"configuration files", "*.cfgc"}, + "configuration files{.cfgc}", workingDirColors, dpiScale ); @@ -1456,8 +1456,8 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { if (!dirExists(workingDirKeybinds)) workingDirKeybinds=getHomeDir(); hasOpened=fileDialog->openLoad( "Select Keybind File", - {"configuration files", "*.cfg"}, - "configuration files{.cfg}", + {"configuration files", "*.cfgk"}, + "configuration files{.cfgk}", workingDirKeybinds, dpiScale ); @@ -1476,8 +1476,8 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { if (!dirExists(workingDirColors)) workingDirColors=getHomeDir(); hasOpened=fileDialog->openSave( "Export Colors", - {"configuration files", "*.cfg"}, - "configuration files{.cfg}", + {"configuration files", "*.cfgc"}, + "configuration files{.cfgc}", workingDirColors, dpiScale ); @@ -1486,8 +1486,8 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) { if (!dirExists(workingDirKeybinds)) workingDirKeybinds=getHomeDir(); hasOpened=fileDialog->openSave( "Export Keybinds", - {"configuration files", "*.cfg"}, - "configuration files{.cfg}", + {"configuration files", "*.cfgk"}, + "configuration files{.cfgk}", workingDirKeybinds, dpiScale ); From 743664aad10235a06d1583312547c8f8c3e73bdd Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 6 Apr 2022 14:45:58 -0500 Subject: [PATCH 535/637] GUI: extension check for keybinds/colors/layout --- src/gui/gui.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index efcf2eb81..6c35f6c37 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2687,6 +2687,15 @@ bool FurnaceGUI::loop() { if (curFileDialog==GUI_FILE_EXPORT_VGM) { checkExtension(".vgm"); } + if (curFileDialog==GUI_FILE_EXPORT_COLORS) { + checkExtension(".cfgc"); + } + if (curFileDialog==GUI_FILE_EXPORT_KEYBINDS) { + checkExtension(".cfgk"); + } + if (curFileDialog==GUI_FILE_EXPORT_LAYOUT) { + checkExtension(".ini"); + } String copyOfName=fileName; switch (curFileDialog) { case GUI_FILE_OPEN: From b3c3bb64ef4f8609cb75755983a891199940dc95 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 6 Apr 2022 14:56:11 -0500 Subject: [PATCH 536/637] update readme further --- README.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b03478e1f..8b48cb2a4 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,13 @@ ![screenshot](papers/screenshot1.png) -Furnace tracker, a multi-system chiptune tracker. +this is a multi-system chiptune tracker. -[downloads](#downloads) | [discussion](#discussion) | [help](#help) | [developer info](#developer-info) | [unofficial packages](#unofficial-packages) | [faq](#faq) +[downloads](#downloads) | [discussion/help](#quick-references) | [developer info](#developer-info) | [unofficial packages](#unofficial-packages) | [faq](#faq) *** ## downloads + check out the [Releases](https://github.com/tildearrow/furnace/releases) page. available for Windows, macOS and Linux (AppImage). ## features @@ -44,9 +45,12 @@ check out the [Releases](https://github.com/tildearrow/furnace/releases) page. a *** # quick references + - **discussion**: see the [Discussions](https://github.com/tildearrow/furnace/discussions) section, or (preferably) the [official Discord server](https://discord.gg/EfrwT2wq7z). - **help**: check out the [documentation](papers/doc/README.md). it's mostly incomplete, but has details on effects. + ## unofficial packages + [![Packaging status](https://repology.org/badge/tiny-repos/furnace.svg)](https://repology.org/project/furnace/versions) some people have provided packages for Unix/Unix-like distributions. here's a list. @@ -175,7 +179,7 @@ the following effects are provided: a lower envelope period will make the envelope run faster. *** -# faq +# frequently asked questions > how do I use C64 absolute filter/duty? From 11c0995b23547c2426d6917e7ead9ab82cb49388 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 6 Apr 2022 14:57:05 -0500 Subject: [PATCH 537/637] another update --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8b48cb2a4..15b091402 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ this is a multi-system chiptune tracker. -[downloads](#downloads) | [discussion/help](#quick-references) | [developer info](#developer-info) | [unofficial packages](#unofficial-packages) | [faq](#faq) +[downloads](#downloads) | [discussion/help](#quick-references) | [developer info](#developer-info) | [unofficial packages](#unofficial-packages) | [FAQ](#frequently-asked-questions) *** ## downloads From 878d3fab1fc09524fb00ea8b2f1156e724d80a00 Mon Sep 17 00:00:00 2001 From: freq-mod <32672779+freq-mod@users.noreply.github.com> Date: Wed, 6 Apr 2022 22:56:37 +0200 Subject: [PATCH 538/637] add opensuse unofficial package to the readme #271 --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 15b091402..f8ea1aec5 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,7 @@ some people have provided packages for Unix/Unix-like distributions. here's a li - **Arch Linux**: [furnace-git is in the AUR.](https://aur.archlinux.org/packages/furnace-git) thank you Essem! - **FreeBSD**: [a package in ports](https://www.freshports.org/audio/furnace/) is available courtesy of ehaupt. - **Nix**: [package](https://search.nixos.org/packages?channel=unstable&show=furnace&from=0&size=50&sort=relevance&type=packages&query=furnace) thanks to OPNA2608. + - **OpenSUSE**: [a package](https://software.opensuse.org/package/furnace) is available, courtesy of fpesari. *** # developer info From 2e327953e8af4f3d0ef766555393c4bd221e4441 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 6 Apr 2022 18:03:30 -0500 Subject: [PATCH 539/637] YM2610(B): AY unification as of now the SSG part of these chips is handled by a DivPlatformAY8910 within the DivPlatformYM2610. this means less code duplication and therefore prepares for OPN/OPNA support. --- src/engine/dispatch.h | 2 +- src/engine/platform/ay.cpp | 4 + src/engine/platform/ay.h | 1 + src/engine/platform/ym2610.cpp | 238 +++++------------------------ src/engine/platform/ym2610.h | 9 +- src/engine/platform/ym2610b.cpp | 238 +++++------------------------ src/engine/platform/ym2610b.h | 7 +- src/engine/platform/ym2610bext.cpp | 9 +- src/engine/platform/ym2610ext.cpp | 9 +- 9 files changed, 96 insertions(+), 421 deletions(-) diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index dd7cbc268..b4394f608 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -335,7 +335,7 @@ class DivDispatch { /** * set skip reg writes. */ - void setSkipRegisterWrites(bool value); + virtual void setSkipRegisterWrites(bool value); /** * notify instrument change. diff --git a/src/engine/platform/ay.cpp b/src/engine/platform/ay.cpp index 4a70faebc..b798cba4e 100644 --- a/src/engine/platform/ay.cpp +++ b/src/engine/platform/ay.cpp @@ -493,6 +493,10 @@ int DivPlatformAY8910::getRegisterPoolSize() { return 16; } +void DivPlatformAY8910::flushWrites() { + while (!writes.empty()) writes.pop(); +} + void DivPlatformAY8910::reset() { while (!writes.empty()) writes.pop(); ay->device_reset(); diff --git a/src/engine/platform/ay.h b/src/engine/platform/ay.h index 498c75e6f..82b647f2d 100644 --- a/src/engine/platform/ay.h +++ b/src/engine/platform/ay.h @@ -87,6 +87,7 @@ class DivPlatformAY8910: public DivDispatch { void* getChanState(int chan); unsigned char* getRegisterPool(); int getRegisterPoolSize(); + void flushWrites(); void reset(); void forceIns(); void tick(); diff --git a/src/engine/platform/ym2610.cpp b/src/engine/platform/ym2610.cpp index eb5fa86a7..77c1243bd 100644 --- a/src/engine/platform/ym2610.cpp +++ b/src/engine/platform/ym2610.cpp @@ -364,106 +364,12 @@ void DivPlatformYM2610::acquire(short* bufL, short* bufR, size_t start, size_t l void DivPlatformYM2610::tick() { // PSG - for (int i=4; i<7; i++) { - chan[i].std.next(); - if (chan[i].std.hadVol) { - chan[i].outVol=MIN(15,chan[i].std.vol)-(15-(chan[i].vol&15)); - if (chan[i].outVol<0) chan[i].outVol=0; - if (isMuted[i]) { - rWrite(0x04+i,0); - } else { - rWrite(0x04+i,(chan[i].outVol&15)|((chan[i].psgMode&4)<<2)); - } - } - if (chan[i].std.hadArp) { - if (!chan[i].inPorta) { - if (chan[i].std.arpMode) { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp); - } else { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp); - } - } - chan[i].freqChanged=true; - } else { - if (chan[i].std.arpMode && chan[i].std.finishedArp) { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].note); - chan[i].freqChanged=true; - } - } - if (chan[i].std.hadDuty) { - ayNoiseFreq=31-chan[i].std.duty; - rWrite(0x06,ayNoiseFreq); - } - if (chan[i].std.hadWave) { - chan[i].psgMode=(chan[i].std.wave+1)&7; - if (isMuted[i]) { - rWrite(0x04+i,0); - } else { - rWrite(0x04+i,(chan[i].outVol&15)|((chan[i].psgMode&4)<<2)); - } - } - if (chan[i].std.hadEx2) { - ayEnvMode=chan[i].std.ex2; - rWrite(0x0d,ayEnvMode); - } - if (chan[i].std.hadEx3) { - chan[i].autoEnvNum=chan[i].std.ex3; - chan[i].freqChanged=true; - if (!chan[i].std.willAlg) chan[i].autoEnvDen=1; - } - if (chan[i].std.hadAlg) { - chan[i].autoEnvDen=chan[i].std.alg; - chan[i].freqChanged=true; - if (!chan[i].std.willEx3) chan[i].autoEnvNum=1; - } - if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true); - if (chan[i].freq>4095) chan[i].freq=4095; - if (chan[i].keyOn) { - } - if (chan[i].keyOff) { - rWrite(0x04+i,0); - } - rWrite((i-4)<<1,chan[i].freq&0xff); - rWrite(1+((i-4)<<1),chan[i].freq>>8); - if (chan[i].keyOn) chan[i].keyOn=false; - if (chan[i].keyOff) chan[i].keyOff=false; - if (chan[i].freqChanged && chan[i].autoEnvNum>0 && chan[i].autoEnvDen>0) { - ayEnvPeriod=(chan[i].freq*chan[i].autoEnvDen/chan[i].autoEnvNum)>>4; - immWrite(0x0b,ayEnvPeriod); - immWrite(0x0c,ayEnvPeriod>>8); - } - chan[i].freqChanged=false; - } - } - - rWrite(0x07, - ~((chan[4].psgMode&1)| - ((chan[5].psgMode&1)<<1)| - ((chan[6].psgMode&1)<<2)| - ((chan[4].psgMode&2)<<2)| - ((chan[5].psgMode&2)<<3)| - ((chan[6].psgMode&2)<<4))); - - if (ayEnvSlide!=0) { - ayEnvSlideLow+=ayEnvSlide; - while (ayEnvSlideLow>7) { - ayEnvSlideLow-=8; - if (ayEnvPeriod<0xffff) { - ayEnvPeriod++; - immWrite(0x0b,ayEnvPeriod); - immWrite(0x0c,ayEnvPeriod>>8); - } - } - while (ayEnvSlideLow<-7) { - ayEnvSlideLow+=8; - if (ayEnvPeriod>0) { - ayEnvPeriod--; - immWrite(0x0b,ayEnvPeriod); - immWrite(0x0c,ayEnvPeriod>>8); - } - } + ay->tick(); + ay->flushWrites(); + for (DivRegWrite& i: ay->getRegisterWrites()) { + immWrite(i.addr&15,i.val); } + ay->getRegisterWrites().clear(); // FM for (int i=0; i<4; i++) { @@ -620,7 +526,7 @@ void DivPlatformYM2610::tick() { chan[13].freqChanged=false; } - for (int i=0; i<512; i++) { + for (int i=16; i<512; i++) { if (pendingWrites[i]!=oldWrites[i]) { immWrite(i,pendingWrites[i]&0xff); oldWrites[i]=pendingWrites[i]; @@ -686,6 +592,10 @@ int DivPlatformYM2610::toFreq(int freq) { } int DivPlatformYM2610::dispatch(DivCommand c) { + if (c.chan>3 && c.chan<7) { + c.chan-=4; + return ay->dispatch(c); + } switch (c.cmd) { case DIV_CMD_NOTE_ON: { if (c.chan>12) { // ADPCM-B @@ -780,22 +690,6 @@ int DivPlatformYM2610::dispatch(DivCommand c) { } } - if (c.chan>3) { // PSG - if (c.value!=DIV_NOTE_NULL) { - chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); - chan[c.chan].freqChanged=true; - chan[c.chan].note=c.value; - } - chan[c.chan].active=true; - chan[c.chan].keyOn=true; - if (isMuted[c.chan]) { - rWrite(0x04+c.chan,0); - } else { - rWrite(0x04+c.chan,(chan[c.chan].vol&15)|((chan[c.chan].psgMode&4)<<2)); - } - break; - } - if (chan[c.chan].insChanged) { chan[c.chan].state=ins->fm; } @@ -860,10 +754,6 @@ int DivPlatformYM2610::dispatch(DivCommand c) { immWrite(0x100,0x80|(1<<(c.chan-7))); break; } - if (c.chan>3) { - chan[c.chan].std.release(); - break; - } chan[c.chan].keyOff=true; chan[c.chan].keyOn=false; chan[c.chan].active=false; @@ -885,14 +775,6 @@ int DivPlatformYM2610::dispatch(DivCommand c) { immWrite(0x108+(c.chan-7),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].vol)); break; } - if (c.chan>3) { // PSG - if (isMuted[c.chan]) { - rWrite(0x04+c.chan,0); - } else { - if (chan[c.chan].active) rWrite(0x04+c.chan,(chan[c.chan].vol&15)|((chan[c.chan].psgMode&4)<<2)); - } - break; - } for (int i=0; i<4; i++) { unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; @@ -928,7 +810,6 @@ int DivPlatformYM2610::dispatch(DivCommand c) { immWrite(0x108+(c.chan-7),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].vol)); break; } - if (c.chan>3) break; rWrite(chanOffs[c.chan]+ADDR_LRAF,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|(chan[c.chan].state.fms&7)|((chan[c.chan].state.ams&3)<<4)); break; } @@ -1049,59 +930,6 @@ int DivPlatformYM2610::dispatch(DivCommand c) { } break; } - case DIV_CMD_STD_NOISE_MODE: - if (c.chan<4 || c.chan>6) break; - chan[c.chan].psgMode=(c.value+1)&7; - if (isMuted[c.chan]) { - rWrite(0x04+c.chan,0); - } else if (chan[c.chan].active) { - rWrite(0x04+c.chan,(chan[c.chan].outVol&15)|((chan[c.chan].psgMode&4)<<2)); - } - break; - case DIV_CMD_STD_NOISE_FREQ: - if (c.chan<4 || c.chan>6) break; - ayNoiseFreq=31-c.value; - rWrite(0x06,ayNoiseFreq); - break; - case DIV_CMD_AY_ENVELOPE_SET: - if (c.chan<4 || c.chan>6) break; - ayEnvMode=c.value>>4; - rWrite(0x0d,ayEnvMode); - if (c.value&15) { - chan[c.chan].psgMode|=4; - } else { - chan[c.chan].psgMode&=~4; - } - if (isMuted[c.chan]) { - rWrite(0x04+c.chan,0); - } else { - rWrite(0x04+c.chan,(chan[c.chan].vol&15)|((chan[c.chan].psgMode&4)<<2)); - } - break; - case DIV_CMD_AY_ENVELOPE_LOW: - if (c.chan<4 || c.chan>6) break; - ayEnvPeriod&=0xff00; - ayEnvPeriod|=c.value; - immWrite(0x0b,ayEnvPeriod); - immWrite(0x0c,ayEnvPeriod>>8); - break; - case DIV_CMD_AY_ENVELOPE_HIGH: - if (c.chan<4 || c.chan>6) break; - ayEnvPeriod&=0xff; - ayEnvPeriod|=c.value<<8; - immWrite(0x0b,ayEnvPeriod); - immWrite(0x0c,ayEnvPeriod>>8); - break; - case DIV_CMD_AY_ENVELOPE_SLIDE: - if (c.chan<4 || c.chan>6) break; - ayEnvSlide=c.value; - break; - case DIV_CMD_AY_AUTO_ENVELOPE: - if (c.chan<4 || c.chan>6) break; - chan[c.chan].autoEnvNum=c.value>>4; - chan[c.chan].autoEnvDen=c.value&15; - chan[c.chan].freqChanged=true; - break; case DIV_ALWAYS_SET_VOLUME: return 0; break; @@ -1138,11 +966,7 @@ void DivPlatformYM2610::muteChannel(int ch, bool mute) { return; } if (ch>3) { // PSG - if (isMuted[ch]) { - rWrite(0x04+ch,0); - } else { - rWrite(0x04+ch,(chan[ch].outVol&15)|((chan[ch].psgMode&4)<<2)); - } + ay->muteChannel(ch-4,mute); return; } // FM @@ -1173,12 +997,16 @@ void DivPlatformYM2610::forceIns() { chan[i].freqChanged=true; } } - for (int i=4; i<14; i++) { + for (int i=7; i<14; i++) { chan[i].insChanged=true; } - immWrite(0x0b,ayEnvPeriod); - immWrite(0x0c,ayEnvPeriod>>8); - immWrite(0x0d,ayEnvMode); + + ay->forceIns(); + ay->flushWrites(); + for (DivRegWrite& i: ay->getRegisterWrites()) { + immWrite(i.addr&15,i.val); + } + ay->getRegisterWrites().clear(); } void* DivPlatformYM2610::getChanState(int ch) { @@ -1230,25 +1058,21 @@ void DivPlatformYM2610::reset() { lastBusy=60; sampleBank=0; - ayEnvPeriod=0; - ayEnvMode=0; - ayEnvSlide=0; - ayEnvSlideLow=0; - ayNoiseFreq=0; delay=0; extMode=false; - // AY noise - immWrite(0x06,ayNoiseFreq); - // LFO immWrite(0x22,0x08); // PCM volume immWrite(0x101,0x3f); // A immWrite(0x1b,0xff); // B + + ay->reset(); + ay->getRegisterWrites().clear(); + ay->flushWrites(); } bool DivPlatformYM2610::isStereo() { @@ -1265,12 +1089,16 @@ void DivPlatformYM2610::notifyInsChange(int ins) { chan[i].insChanged=true; } } + ay->notifyInsChange(ins); } void DivPlatformYM2610::notifyInsDeletion(void* ins) { - for (int i=4; i<7; i++) { - chan[i].std.notifyInsDeletion((DivInstrument*)ins); - } + ay->notifyInsDeletion(ins); +} + +void DivPlatformYM2610::setSkipRegisterWrites(bool value) { + DivDispatch::setSkipRegisterWrites(value); + ay->setSkipRegisterWrites(value); } int DivPlatformYM2610::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { @@ -1285,11 +1113,17 @@ int DivPlatformYM2610::init(DivEngine* p, int channels, int sugRate, unsigned in iface.parent=parent; iface.sampleBank=0; fm=new ymfm::ym2610(iface); + // YM2149, 2MHz + ay=new DivPlatformAY8910; + ay->init(p,3,sugRate,35); + ay->toggleRegisterDump(true); reset(); return 14; } void DivPlatformYM2610::quit() { + ay->quit(); + delete ay; delete fm; } diff --git a/src/engine/platform/ym2610.h b/src/engine/platform/ym2610.h index 4e9b81f98..bc70144e6 100644 --- a/src/engine/platform/ym2610.h +++ b/src/engine/platform/ym2610.h @@ -22,6 +22,7 @@ #include "../dispatch.h" #include "../macroInt.h" #include +#include "ay.h" #include "sound/ymfm/ymfm_opn.h" class DivYM2610Interface: public ymfm::ymfm_interface { @@ -86,10 +87,11 @@ class DivPlatformYM2610: public DivDispatch { ymfm::ym2610* fm; ymfm::ym2610::output_data fmout; DivYM2610Interface iface; + + DivPlatformAY8910* ay; unsigned char regPool[512]; unsigned char lastBusy; - int ayNoiseFreq; unsigned char sampleBank; int delay; @@ -98,10 +100,6 @@ class DivPlatformYM2610: public DivDispatch { short oldWrites[512]; short pendingWrites[512]; - unsigned char ayEnvMode; - unsigned short ayEnvPeriod; - short ayEnvSlideLow; - short ayEnvSlide; int octave(int freq); int toFreq(int freq); @@ -123,6 +121,7 @@ class DivPlatformYM2610: public DivDispatch { bool keyOffAffectsArp(int ch); void notifyInsChange(int ins); void notifyInsDeletion(void* ins); + void setSkipRegisterWrites(bool val); void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); const char** getRegisterSheet(); diff --git a/src/engine/platform/ym2610b.cpp b/src/engine/platform/ym2610b.cpp index 745efa224..dfd847235 100644 --- a/src/engine/platform/ym2610b.cpp +++ b/src/engine/platform/ym2610b.cpp @@ -428,106 +428,12 @@ void DivPlatformYM2610B::acquire(short* bufL, short* bufR, size_t start, size_t void DivPlatformYM2610B::tick() { // PSG - for (int i=6; i<9; i++) { - chan[i].std.next(); - if (chan[i].std.hadVol) { - chan[i].outVol=MIN(15,chan[i].std.vol)-(15-(chan[i].vol&15)); - if (chan[i].outVol<0) chan[i].outVol=0; - if (isMuted[i]) { - rWrite(0x02+i,0); - } else { - rWrite(0x02+i,(chan[i].outVol&15)|((chan[i].psgMode&4)<<2)); - } - } - if (chan[i].std.hadArp) { - if (!chan[i].inPorta) { - if (chan[i].std.arpMode) { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp); - } else { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp); - } - } - chan[i].freqChanged=true; - } else { - if (chan[i].std.arpMode && chan[i].std.finishedArp) { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].note); - chan[i].freqChanged=true; - } - } - if (chan[i].std.hadDuty) { - ayNoiseFreq=31-chan[i].std.duty; - rWrite(0x06,ayNoiseFreq); - } - if (chan[i].std.hadWave) { - chan[i].psgMode=(chan[i].std.wave+1)&7; - if (isMuted[i]) { - rWrite(0x02+i,0); - } else { - rWrite(0x02+i,(chan[i].outVol&15)|((chan[i].psgMode&4)<<2)); - } - } - if (chan[i].std.hadEx2) { - ayEnvMode=chan[i].std.ex2; - rWrite(0x0d,ayEnvMode); - } - if (chan[i].std.hadEx3) { - chan[i].autoEnvNum=chan[i].std.ex3; - chan[i].freqChanged=true; - if (!chan[i].std.willAlg) chan[i].autoEnvDen=1; - } - if (chan[i].std.hadAlg) { - chan[i].autoEnvDen=chan[i].std.alg; - chan[i].freqChanged=true; - if (!chan[i].std.willEx3) chan[i].autoEnvNum=1; - } - if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { - chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true); - if (chan[i].freq>4095) chan[i].freq=4095; - if (chan[i].keyOn) { - } - if (chan[i].keyOff) { - rWrite(0x02+i,0); - } - rWrite((i-6)<<1,chan[i].freq&0xff); - rWrite(1+((i-6)<<1),chan[i].freq>>8); - if (chan[i].keyOn) chan[i].keyOn=false; - if (chan[i].keyOff) chan[i].keyOff=false; - if (chan[i].freqChanged && chan[i].autoEnvNum>0 && chan[i].autoEnvDen>0) { - ayEnvPeriod=(chan[i].freq*chan[i].autoEnvDen/chan[i].autoEnvNum)>>4; - immWrite(0x0b,ayEnvPeriod); - immWrite(0x0c,ayEnvPeriod>>8); - } - chan[i].freqChanged=false; - } - } - - rWrite(0x07, - ~((chan[6].psgMode&1)| - ((chan[7].psgMode&1)<<1)| - ((chan[8].psgMode&1)<<2)| - ((chan[6].psgMode&2)<<2)| - ((chan[7].psgMode&2)<<3)| - ((chan[8].psgMode&2)<<4))); - - if (ayEnvSlide!=0) { - ayEnvSlideLow+=ayEnvSlide; - while (ayEnvSlideLow>7) { - ayEnvSlideLow-=8; - if (ayEnvPeriod<0xffff) { - ayEnvPeriod++; - immWrite(0x0b,ayEnvPeriod); - immWrite(0x0c,ayEnvPeriod>>8); - } - } - while (ayEnvSlideLow<-7) { - ayEnvSlideLow+=8; - if (ayEnvPeriod>0) { - ayEnvPeriod--; - immWrite(0x0b,ayEnvPeriod); - immWrite(0x0c,ayEnvPeriod>>8); - } - } + ay->tick(); + ay->flushWrites(); + for (DivRegWrite& i: ay->getRegisterWrites()) { + immWrite(i.addr&15,i.val); } + ay->getRegisterWrites().clear(); // FM for (int i=0; i<6; i++) { @@ -683,7 +589,7 @@ void DivPlatformYM2610B::tick() { chan[15].freqChanged=false; } - for (int i=0; i<512; i++) { + for (int i=16; i<512; i++) { if (pendingWrites[i]!=oldWrites[i]) { immWrite(i,pendingWrites[i]&0xff); oldWrites[i]=pendingWrites[i]; @@ -749,6 +655,10 @@ int DivPlatformYM2610B::toFreq(int freq) { } int DivPlatformYM2610B::dispatch(DivCommand c) { + if (c.chan>5 && c.chan<9) { + c.chan-=6; + return ay->dispatch(c); + } switch (c.cmd) { case DIV_CMD_NOTE_ON: { if (c.chan>14) { // ADPCM-B @@ -843,22 +753,6 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { } } - if (c.chan>5) { // PSG - if (c.value!=DIV_NOTE_NULL) { - chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); - chan[c.chan].freqChanged=true; - chan[c.chan].note=c.value; - } - chan[c.chan].active=true; - chan[c.chan].keyOn=true; - if (isMuted[c.chan]) { - rWrite(0x02+c.chan,0); - } else { - rWrite(0x02+c.chan,(chan[c.chan].vol&15)|((chan[c.chan].psgMode&4)<<2)); - } - break; - } - if (chan[c.chan].insChanged) { chan[c.chan].state=ins->fm; } @@ -923,10 +817,6 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { immWrite(0x100,0x80|(1<<(c.chan-9))); break; } - if (c.chan>5) { - chan[c.chan].std.release(); - break; - } chan[c.chan].keyOff=true; chan[c.chan].keyOn=false; chan[c.chan].active=false; @@ -948,14 +838,6 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { immWrite(0x108+(c.chan-9),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].vol)); break; } - if (c.chan>5) { // PSG - if (isMuted[c.chan]) { - rWrite(0x02+c.chan,0); - } else { - if (chan[c.chan].active) rWrite(0x02+c.chan,(chan[c.chan].vol&15)|((chan[c.chan].psgMode&4)<<2)); - } - break; - } for (int i=0; i<4; i++) { unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; @@ -991,7 +873,6 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { immWrite(0x108+(c.chan-9),isMuted[c.chan]?0:((chan[c.chan].pan<<6)|chan[c.chan].vol)); break; } - if (c.chan>5) break; rWrite(chanOffs[c.chan]+ADDR_LRAF,(isMuted[c.chan]?0:(chan[c.chan].pan<<6))|(chan[c.chan].state.fms&7)|((chan[c.chan].state.ams&3)<<4)); break; } @@ -1112,59 +993,6 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { } break; } - case DIV_CMD_STD_NOISE_MODE: - if (c.chan<6 || c.chan>8) break; - chan[c.chan].psgMode=(c.value+1)&7; - if (isMuted[c.chan]) { - rWrite(0x02+c.chan,0); - } else if (chan[c.chan].active) { - rWrite(0x02+c.chan,(chan[c.chan].outVol&15)|((chan[c.chan].psgMode&4)<<2)); - } - break; - case DIV_CMD_STD_NOISE_FREQ: - if (c.chan<6 || c.chan>8) break; - ayNoiseFreq=31-c.value; - rWrite(0x06,ayNoiseFreq); - break; - case DIV_CMD_AY_ENVELOPE_SET: - if (c.chan<6 || c.chan>8) break; - ayEnvMode=c.value>>4; - rWrite(0x0d,ayEnvMode); - if (c.value&15) { - chan[c.chan].psgMode|=4; - } else { - chan[c.chan].psgMode&=~4; - } - if (isMuted[c.chan]) { - rWrite(0x02+c.chan,0); - } else { - rWrite(0x02+c.chan,(chan[c.chan].vol&15)|((chan[c.chan].psgMode&4)<<2)); - } - break; - case DIV_CMD_AY_ENVELOPE_LOW: - if (c.chan<6 || c.chan>8) break; - ayEnvPeriod&=0xff00; - ayEnvPeriod|=c.value; - immWrite(0x0b,ayEnvPeriod); - immWrite(0x0c,ayEnvPeriod>>8); - break; - case DIV_CMD_AY_ENVELOPE_HIGH: - if (c.chan<6 || c.chan>8) break; - ayEnvPeriod&=0xff; - ayEnvPeriod|=c.value<<8; - immWrite(0x0b,ayEnvPeriod); - immWrite(0x0c,ayEnvPeriod>>8); - break; - case DIV_CMD_AY_ENVELOPE_SLIDE: - if (c.chan<6 || c.chan>8) break; - ayEnvSlide=c.value; - break; - case DIV_CMD_AY_AUTO_ENVELOPE: - if (c.chan<6 || c.chan>8) break; - chan[c.chan].autoEnvNum=c.value>>4; - chan[c.chan].autoEnvDen=c.value&15; - chan[c.chan].freqChanged=true; - break; case DIV_ALWAYS_SET_VOLUME: return 0; break; @@ -1201,11 +1029,7 @@ void DivPlatformYM2610B::muteChannel(int ch, bool mute) { return; } if (ch>5) { // PSG - if (isMuted[ch]) { - rWrite(0x02+ch,0); - } else { - rWrite(0x02+ch,(chan[ch].outVol&15)|((chan[ch].psgMode&4)<<2)); - } + ay->muteChannel(ch-6,mute); return; } // FM @@ -1236,12 +1060,16 @@ void DivPlatformYM2610B::forceIns() { chan[i].freqChanged=true; } } - for (int i=6; i<16; i++) { + for (int i=9; i<16; i++) { chan[i].insChanged=true; } - immWrite(0x0b,ayEnvPeriod); - immWrite(0x0c,ayEnvPeriod>>8); - immWrite(0x0d,ayEnvMode); + + ay->forceIns(); + ay->flushWrites(); + for (DivRegWrite& i: ay->getRegisterWrites()) { + immWrite(i.addr&15,i.val); + } + ay->getRegisterWrites().clear(); } void* DivPlatformYM2610B::getChanState(int ch) { @@ -1293,25 +1121,21 @@ void DivPlatformYM2610B::reset() { lastBusy=60; sampleBank=0; - ayEnvPeriod=0; - ayEnvMode=0; - ayEnvSlide=0; - ayEnvSlideLow=0; - ayNoiseFreq=0; delay=0; extMode=false; - // AY noise - immWrite(0x06,ayNoiseFreq); - // LFO immWrite(0x22,0x08); // PCM volume immWrite(0x101,0x3f); // A immWrite(0x1b,0xff); // B + + ay->reset(); + ay->getRegisterWrites().clear(); + ay->flushWrites(); } bool DivPlatformYM2610B::isStereo() { @@ -1328,12 +1152,16 @@ void DivPlatformYM2610B::notifyInsChange(int ins) { chan[i].insChanged=true; } } + ay->notifyInsChange(ins); } void DivPlatformYM2610B::notifyInsDeletion(void* ins) { - for (int i=6; i<9; i++) { - chan[i].std.notifyInsDeletion((DivInstrument*)ins); - } + ay->notifyInsDeletion(ins); +} + +void DivPlatformYM2610B::setSkipRegisterWrites(bool value) { + DivDispatch::setSkipRegisterWrites(value); + ay->setSkipRegisterWrites(value); } int DivPlatformYM2610B::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { @@ -1348,11 +1176,17 @@ int DivPlatformYM2610B::init(DivEngine* p, int channels, int sugRate, unsigned i iface.parent=parent; iface.sampleBank=0; fm=new ymfm::ym2610b(iface); + // YM2149, 2MHz + ay=new DivPlatformAY8910; + ay->init(p,3,sugRate,35); + ay->toggleRegisterDump(true); reset(); return 16; } void DivPlatformYM2610B::quit() { + ay->quit(); + delete ay; delete fm; } diff --git a/src/engine/platform/ym2610b.h b/src/engine/platform/ym2610b.h index 99c641a6e..85b9b4514 100644 --- a/src/engine/platform/ym2610b.h +++ b/src/engine/platform/ym2610b.h @@ -82,7 +82,7 @@ class DivPlatformYM2610B: public DivDispatch { unsigned char regPool[512]; unsigned char lastBusy; - int ayNoiseFreq; + DivPlatformAY8910* ay; unsigned char sampleBank; int delay; @@ -91,10 +91,6 @@ class DivPlatformYM2610B: public DivDispatch { short oldWrites[512]; short pendingWrites[512]; - unsigned char ayEnvMode; - unsigned short ayEnvPeriod; - short ayEnvSlideLow; - short ayEnvSlide; int octave(int freq); int toFreq(int freq); @@ -116,6 +112,7 @@ class DivPlatformYM2610B: public DivDispatch { bool keyOffAffectsArp(int ch); void notifyInsChange(int ins); void notifyInsDeletion(void* ins); + void setSkipRegisterWrites(bool val); void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); const char** getRegisterSheet(); diff --git a/src/engine/platform/ym2610bext.cpp b/src/engine/platform/ym2610bext.cpp index cdea5bba6..adcd1e9e6 100644 --- a/src/engine/platform/ym2610bext.cpp +++ b/src/engine/platform/ym2610bext.cpp @@ -316,9 +316,12 @@ void DivPlatformYM2610BExt::forceIns() { for (int i=6; i<16; i++) { chan[i].insChanged=true; } - immWrite(0x0b,ayEnvPeriod); - immWrite(0x0c,ayEnvPeriod>>8); - immWrite(0x0d,ayEnvMode); + ay->forceIns(); + ay->flushWrites(); + for (DivRegWrite& i: ay->getRegisterWrites()) { + immWrite(i.addr&15,i.val); + } + ay->getRegisterWrites().clear(); for (int i=0; i<4; i++) { opChan[i].insChanged=true; if (opChan[i].active) { diff --git a/src/engine/platform/ym2610ext.cpp b/src/engine/platform/ym2610ext.cpp index dcc905c36..ed8aa14e4 100644 --- a/src/engine/platform/ym2610ext.cpp +++ b/src/engine/platform/ym2610ext.cpp @@ -316,9 +316,12 @@ void DivPlatformYM2610Ext::forceIns() { for (int i=4; i<14; i++) { chan[i].insChanged=true; } - immWrite(0x0b,ayEnvPeriod); - immWrite(0x0c,ayEnvPeriod>>8); - immWrite(0x0d,ayEnvMode); + ay->forceIns(); + ay->flushWrites(); + for (DivRegWrite& i: ay->getRegisterWrites()) { + immWrite(i.addr&15,i.val); + } + ay->getRegisterWrites().clear(); for (int i=0; i<4; i++) { opChan[i].insChanged=true; if (opChan[i].active) { From 58abed3fa184e057f8fa6ebd0bed3ee9519c0af6 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 6 Apr 2022 20:21:20 -0500 Subject: [PATCH 540/637] update pattern format spec --- papers/format.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/papers/format.md b/papers/format.md index 54414fa47..f6e836dd5 100644 --- a/papers/format.md +++ b/papers/format.md @@ -635,10 +635,29 @@ size | description | - size: rows*(4+effectColumns*2)*2 | - read shorts in this order: | - note + | - 0: empty/invalid + | - 1: C# + | - 2: D + | - 3: D# + | - 4: E + | - 5: F + | - 6: F# + | - 7: G + | - 8: G# + | - 9: A + | - 10: A# + | - 11: B + | - 12: C (of next octave) + | - 100: note off + | - 100: note release + | - 100: macro release | - octave + | - this is an signed char stored in a short. + | - therefore octave value 255 is actually octave -1. | - instrument | - volume | - effect and effect data... + | - for instrument, volume, effect and effect data, a value of -1 means empty. STR | pattern name (>=51) ``` From 9345576fc35cbb8db6cf506c8ce0249d62a0dd57 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 6 Apr 2022 22:55:24 -0500 Subject: [PATCH 541/637] MMC5: fix 12xx not working --- src/engine/playback.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 52ea991af..3b382de80 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -288,6 +288,7 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe } break; case DIV_SYSTEM_NES: + case DIV_SYSTEM_MMC5: switch (effect) { case 0x12: // duty or noise mode dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal)); From 530327383af8300e4fbefcf72045c4595fca68c6 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Wed, 6 Apr 2022 22:56:06 -0500 Subject: [PATCH 542/637] initial OPZ bring-up even emulation is incomplete and one thing is making me tired... --- CMakeLists.txt | 1 + src/engine/dispatchContainer.cpp | 4 + src/engine/instrument.h | 4 +- src/engine/platform/arcade.cpp | 4 +- src/engine/platform/sound/ymfm/ymfm_opz.cpp | 7 +- src/engine/platform/tx81z.cpp | 762 ++++++++++++++++++++ src/engine/platform/tx81z.h | 98 +++ src/gui/guiConst.cpp | 1 + 8 files changed, 876 insertions(+), 5 deletions(-) create mode 100644 src/engine/platform/tx81z.cpp create mode 100644 src/engine/platform/tx81z.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 7648cb588..a01272803 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -314,6 +314,7 @@ src/engine/platform/mmc5.cpp src/engine/platform/nes.cpp src/engine/platform/c64.cpp src/engine/platform/arcade.cpp +src/engine/platform/tx81z.cpp src/engine/platform/ym2610.cpp src/engine/platform/ym2610ext.cpp src/engine/platform/ym2610b.cpp diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index bbcf3073d..ba72a6957 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -28,6 +28,7 @@ #include "platform/nes.h" #include "platform/c64.h" #include "platform/arcade.h" +#include "platform/tx81z.h" #include "platform/ym2610.h" #include "platform/ym2610ext.h" #include "platform/ym2610b.h" @@ -252,6 +253,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do dispatch=new DivPlatformOPL; ((DivPlatformOPL*)dispatch)->setOPLType(3,true); break; + case DIV_SYSTEM_OPZ: + dispatch=new DivPlatformTX81Z; + break; case DIV_SYSTEM_SAA1099: { int saaCore=eng->getConfInt("saaCore",1); if (saaCore<0 || saaCore>2) saaCore=0; diff --git a/src/engine/instrument.h b/src/engine/instrument.h index fafd16b59..2892cbd43 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -68,9 +68,9 @@ enum DivInstrumentType { // - OPL: // - AM, AR, DR, MULT, RR, SL, TL, SSG-EG&8 = EG-S // - KSL, VIB, WS (OPL2/3), KSR -// - OPZ: NOT FINAL! +// - OPZ: // - AM, AR, DR, MULT (CRS), RR, SL, TL, DT2, RS, DT, D2R -// - KSL = LS, WS, DVB = MULT (FINE), DAM = REV, EGT = EGShift +// - WS, DVB = MULT (FINE), DAM = REV, KSL = EGShift, EGT = Fixed struct DivInstrumentFM { unsigned char alg, fb, fms, ams, ops, opllPreset; diff --git a/src/engine/platform/arcade.cpp b/src/engine/platform/arcade.cpp index 1eb950183..7c465a511 100644 --- a/src/engine/platform/arcade.cpp +++ b/src/engine/platform/arcade.cpp @@ -208,11 +208,11 @@ void DivPlatformArcade::acquire(short* bufL, short* bufR, size_t start, size_t l } } -unsigned char noteMap[12]={ +static unsigned char noteMap[12]={ 0, 1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 14 }; -int hScale(int note) { +inline int hScale(int note) { return ((note/12)<<4)+(noteMap[note%12]); } diff --git a/src/engine/platform/sound/ymfm/ymfm_opz.cpp b/src/engine/platform/sound/ymfm/ymfm_opz.cpp index adeefd79f..7ca69dda4 100644 --- a/src/engine/platform/sound/ymfm/ymfm_opz.cpp +++ b/src/engine/platform/sound/ymfm/ymfm_opz.cpp @@ -270,6 +270,7 @@ bool opz_registers::write(uint16_t index, uint8_t data, uint32_t &channel, uint3 bool is_setting_preset = (bitfield(m_regdata[0x100 + (index & 0x1f)], 7) != 0); if (is_setting_preset) { + //printf("ISP\n"); if ((index & 0xe0) == 0xe0) { m_regdata[0x140 + (index & 0x1f)] = data; @@ -280,10 +281,14 @@ bool opz_registers::write(uint16_t index, uint8_t data, uint32_t &channel, uint3 } // handle writes to the key on index - if ((index & 0xf8) == 0x20 && bitfield(index, 0, 3) == bitfield(m_regdata[0x08], 0, 3)) + + // note from tildearrow: + // - are you kidding? I have to write to this "load preset" register before keying on? + if ((index & 0xf8) == 0x20 /*&& bitfield(index, 0, 3) == bitfield(m_regdata[0x08], 0, 3)*/) { channel = bitfield(index, 0, 3); opmask = ch_key_on(channel) ? 0xf : 0; + //printf("%d opmask is %d\n",opmask,channel); // according to the TX81Z manual, the sync option causes the LFOs // to reset at each note on diff --git a/src/engine/platform/tx81z.cpp b/src/engine/platform/tx81z.cpp new file mode 100644 index 000000000..f81fda5fe --- /dev/null +++ b/src/engine/platform/tx81z.cpp @@ -0,0 +1,762 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "tx81z.h" +#include "../engine.h" +#include +#include + +#include "fmshared_OPM.h" + +// actually 0x40 but the upper bit of data selects address +#define ADDR_WS_FINE 0x100 +// actually 0xc0 but bit 5 of data selects address +#define ADDR_EGS_REV 0x120 + +static unsigned short chanOffs[8]={ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 +}; +static unsigned short opOffs[4]={ + 0x00, 0x08, 0x10, 0x18 +}; +static bool isOutput[8][4]={ + // 1 3 2 4 + {false,false,false,true}, + {false,false,false,true}, + {false,false,false,true}, + {false,false,false,true}, + {false,false,true ,true}, + {false,true ,true ,true}, + {false,true ,true ,true}, + {true ,true ,true ,true}, +}; +static unsigned char dtTable[8]={ + 7,6,5,0,1,2,3,4 +}; + +static int orderedOps[4]={ + 0,2,1,3 +}; + +#define rWrite(a,v) if (!skipRegisterWrites) {pendingWrites[a]=v;} +#define immWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } + +#define NOTE_LINEAR(x) (((x)<<6)+baseFreqOff+log2(parent->song.tuning/440.0)*12.0*64.0) + +const char* regCheatSheetOPZ[]={ + "Test", "00", + "NoteCtl", "08", + "NoiseCtl", "0F", + "ClockA1", "10", + "ClockA2", "11", + "ClockB", "12", + "Control", "14", + "LFOFreq", "18", + "AMD_PMD", "19", + "LFOWave", "1B", + "L_R_FB_ALG", "20", + "KC", "28", + "KF", "30", + "PMS_AMS", "38", + "DT_MULT", "40", + "TL", "60", + "KS_AR", "80", + "AM_DR", "A0", + "DT2_SR", "C0", + "SL_RR", "E0", + NULL +}; + +const char** DivPlatformTX81Z::getRegisterSheet() { + return regCheatSheetOPZ; +} + +const char* DivPlatformTX81Z::getEffectName(unsigned char effect) { + switch (effect) { + case 0x10: + return "10xx: Set noise frequency (xx: value; 0 disables noise)"; + break; + case 0x11: + return "11xx: Set feedback (0 to 7)"; + break; + case 0x12: + return "12xx: Set level of operator 1 (0 highest, 7F lowest)"; + break; + case 0x13: + return "13xx: Set level of operator 2 (0 highest, 7F lowest)"; + break; + case 0x14: + return "14xx: Set level of operator 3 (0 highest, 7F lowest)"; + break; + case 0x15: + return "15xx: Set level of operator 4 (0 highest, 7F lowest)"; + break; + case 0x16: + return "16xy: Set operator multiplier (x: operator from 1 to 4; y: multiplier)"; + break; + case 0x17: + return "17xx: Set LFO speed"; + break; + case 0x18: + return "18xx: Set LFO waveform (0 saw, 1 square, 2 triangle, 3 noise)"; + break; + case 0x19: + return "19xx: Set attack of all operators (0 to 1F)"; + break; + case 0x1a: + return "1Axx: Set attack of operator 1 (0 to 1F)"; + break; + case 0x1b: + return "1Bxx: Set attack of operator 2 (0 to 1F)"; + break; + case 0x1c: + return "1Cxx: Set attack of operator 3 (0 to 1F)"; + break; + case 0x1d: + return "1Dxx: Set attack of operator 4 (0 to 1F)"; + break; + case 0x1e: + return "1Exx: Set AM depth (0 to 7F)"; + break; + case 0x1f: + return "1Fxx: Set PM depth (0 to 7F)"; + break; + } + return NULL; +} + +void DivPlatformTX81Z::acquire(short* bufL, short* bufR, size_t start, size_t len) { + static int os[2]; + + for (size_t h=start; hwrite(0x0+((w.addr>>8)<<1),w.addr); + fm_ymfm->write(0x1+((w.addr>>8)<<1),w.val); + regPool[w.addr&0xff]=w.val; + writes.pop(); + delay=1; + } + } + + fm_ymfm->generate(&out_ymfm); + + os[0]=out_ymfm.data[0]; + if (os[0]<-32768) os[0]=-32768; + if (os[0]>32767) os[0]=32767; + + os[1]=out_ymfm.data[1]; + if (os[1]<-32768) os[1]=-32768; + if (os[1]>32767) os[1]=32767; + + bufL[h]=os[0]; + bufR[h]=os[1]; + } +} + +static unsigned char noteMap[12]={ + 0, 1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 14 +}; + +inline int hScale(int note) { + return ((note/12)<<4)+(noteMap[note%12]); +} + +void DivPlatformTX81Z::tick() { + for (int i=0; i<8; i++) { + chan[i].std.next(); + + if (chan[i].std.hadVol) { + chan[i].outVol=(chan[i].vol*MIN(127,chan[i].std.vol))/127; + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + DivInstrumentFM::Operator& op=chan[i].state.op[j]; + if (isOutput[chan[i].state.alg][j]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + } + + if (chan[i].std.hadArp) { + if (!chan[i].inPorta) { + if (chan[i].std.arpMode) { + chan[i].baseFreq=NOTE_LINEAR(chan[i].std.arp); + } else { + chan[i].baseFreq=NOTE_LINEAR(chan[i].note+(signed char)chan[i].std.arp); + } + } + chan[i].freqChanged=true; + } else { + if (chan[i].std.arpMode && chan[i].std.finishedArp) { + chan[i].baseFreq=NOTE_LINEAR(chan[i].note); + chan[i].freqChanged=true; + } + } + + if (chan[i].std.hadDuty) { + if (chan[i].std.duty>0) { + rWrite(0x0f,0x80|(0x20-chan[i].std.duty)); + } else { + rWrite(0x0f,0); + } + } + + if (chan[i].std.hadWave) { + rWrite(0x1b,chan[i].std.wave&3); + } + + if (chan[i].std.hadEx1) { + amDepth=chan[i].std.ex1; + immWrite(0x19,amDepth); + } + + if (chan[i].std.hadEx2) { + pmDepth=chan[i].std.ex2; + immWrite(0x19,0x80|pmDepth); + } + + if (chan[i].std.hadEx3) { + immWrite(0x18,chan[i].std.ex3); + } + + if (chan[i].std.hadAlg) { + chan[i].state.alg=chan[i].std.alg; + if (isMuted[i]) { + immWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|0x40); + } else { + immWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|(chan[i].active?0:0x40)|(chan[i].chVolR<<7)); + } + if (!parent->song.algMacroBehavior) for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + DivInstrumentFM::Operator& op=chan[i].state.op[j]; + if (isMuted[i]) { + rWrite(baseAddr+ADDR_TL,127); + } else { + if (isOutput[chan[i].state.alg][j]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + } + } + if (chan[i].std.hadFb) { + chan[i].state.fb=chan[i].std.fb; + if (isMuted[i]) { + immWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|0x40); + } else { + immWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|(chan[i].active?0:0x40)|(chan[i].chVolR<<7)); + } + } + if (chan[i].std.hadFms) { + chan[i].state.fms=chan[i].std.fms; + rWrite(chanOffs[i]+ADDR_FMS_AMS,((chan[i].state.fms&7)<<4)|(chan[i].state.ams&3)); + } + if (chan[i].std.hadAms) { + chan[i].state.ams=chan[i].std.ams; + rWrite(chanOffs[i]+ADDR_FMS_AMS,((chan[i].state.fms&7)<<4)|(chan[i].state.ams&3)); + } + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + DivInstrumentFM::Operator& op=chan[i].state.op[j]; + DivMacroInt::IntOp& m=chan[i].std.op[j]; + if (m.hadAm) { + op.am=m.am; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + if (m.hadAr) { + op.ar=m.ar; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.egt<<5)|(op.rs<<6)); + } + if (m.hadDr) { + op.dr=m.dr; + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + } + if (m.hadMult) { + op.mult=m.mult; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + if (m.hadRr) { + op.rr=m.rr; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + if (m.hadSl) { + op.sl=m.sl; + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + if (m.hadTl) { + op.tl=127-m.tl; + if (isOutput[chan[i].state.alg][j]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + if (m.hadRs) { + op.rs=m.rs; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.egt<<5)|(op.rs<<6)); + } + if (m.hadDt) { + op.dt=m.dt; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + } + if (m.hadD2r) { + op.d2r=m.d2r; + rWrite(baseAddr+ADDR_DT2_D2R,(op.d2r&31)|(op.dt2<<6)); + } + if (m.hadDt2) { + op.dt2=m.dt2; + rWrite(baseAddr+ADDR_DT2_D2R,(op.d2r&31)|(op.dt2<<6)); + } + } + if (chan[i].keyOn || chan[i].keyOff) { + if (isMuted[i]) { + immWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|0x00); + } else { + //if (chan[i].keyOn) immWrite(0x08,i); + immWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|0x00|(chan[i].chVolR<<7)); + } + chan[i].keyOff=false; + } + } + + for (int i=0; i<256; i++) { + if (pendingWrites[i]!=oldWrites[i]) { + immWrite(i,pendingWrites[i]&0xff); + oldWrites[i]=pendingWrites[i]; + } + } + for (int i=256; i<288; i++) { + if (pendingWrites[i]!=oldWrites[i]) { + immWrite(0x40+(i&0x1f),0x80|(pendingWrites[i]&0x7f)); + oldWrites[i]=pendingWrites[i]; + } + } + for (int i=288; i<320; i++) { + if (pendingWrites[i]!=oldWrites[i]) { + immWrite(0xc0+(i&0x1f),0x20|(pendingWrites[i]&0xdf)); + oldWrites[i]=pendingWrites[i]; + } + } + + for (int i=0; i<8; i++) { + if (chan[i].freqChanged) { + chan[i].freq=chan[i].baseFreq+(chan[i].pitch>>1)-64; + if (chan[i].freq<0) chan[i].freq=0; + if (chan[i].freq>=(95<<6)) chan[i].freq=(95<<6)-1; + immWrite(i+0x28,hScale(chan[i].freq>>6)); + immWrite(i+0x30,(chan[i].freq<<2)|(chan[i].chVolL==chan[i].chVolR)); + chan[i].freqChanged=false; + } + if (chan[i].keyOn) { + if (isMuted[i]) { + immWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); + } else { + //immWrite(0x08,i); + immWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|0x40|(chan[i].chVolR<<7)); + } + chan[i].keyOn=false; + } + } +} + +void DivPlatformTX81Z::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; + // TODO: use volume registers! + /* + if (isMuted[ch]) { + immWrite(chanOffs[ch]+ADDR_LR_FB_ALG,(chan[ch].state.alg&7)|(chan[ch].state.fb<<3)); + } else { + immWrite(chanOffs[ch]+ADDR_LR_FB_ALG,(chan[ch].state.alg&7)|(chan[ch].state.fb<<3)|((chan[ch].chVolL&1)<<6)|((chan[ch].chVolR&1)<<7)); + }*/ +} + +int DivPlatformTX81Z::dispatch(DivCommand c) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: { + DivInstrument* ins=parent->getIns(chan[c.chan].ins); + + if (chan[c.chan].insChanged) { + chan[c.chan].state=ins->fm; + } + + chan[c.chan].std.init(ins); + if (!chan[c.chan].std.willVol) { + chan[c.chan].outVol=chan[c.chan].vol; + } + + for (int i=0; i<4; i++) { + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + DivInstrumentFM::Operator op=chan[c.chan].state.op[i]; + if (isOutput[chan[c.chan].state.alg][i]) { + if (!chan[c.chan].active || chan[c.chan].insChanged) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); + } + } else { + if (chan[c.chan].insChanged) { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + if (chan[c.chan].insChanged) { + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.egt<<5)|(op.rs<<6)); + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + rWrite(baseAddr+ADDR_DT2_D2R,(op.d2r&31)|(op.dt2<<6)); + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + rWrite(baseAddr+ADDR_WS_FINE,(op.dvb&15)|(op.ws<<4)); + rWrite(baseAddr+ADDR_EGS_REV,(op.dam&7)|(op.ksl<<6)); + } + } + if (chan[c.chan].insChanged) { + /* + if (isMuted[c.chan]) { + rWrite(chanOffs[c.chan]+ADDR_LR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3)); + } else { + rWrite(chanOffs[c.chan]+ADDR_LR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3)|((chan[c.chan].chVolL&1)<<6)|((chan[c.chan].chVolR&1)<<7)); + }*/ + rWrite(chanOffs[c.chan]+ADDR_FMS_AMS,((chan[c.chan].state.fms&7)<<4)|(chan[c.chan].state.ams&3)); + } + chan[c.chan].insChanged=false; + + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=NOTE_LINEAR(c.value); + chan[c.chan].note=c.value; + chan[c.chan].freqChanged=true; + } + chan[c.chan].keyOn=true; + chan[c.chan].active=true; + break; + } + case DIV_CMD_NOTE_OFF: + chan[c.chan].keyOff=true; + chan[c.chan].keyOn=false; + chan[c.chan].active=false; + break; + case DIV_CMD_NOTE_OFF_ENV: + chan[c.chan].keyOff=true; + chan[c.chan].keyOn=false; + chan[c.chan].active=false; + chan[c.chan].std.release(); + break; + case DIV_CMD_ENV_RELEASE: + chan[c.chan].std.release(); + break; + case DIV_CMD_VOLUME: { + chan[c.chan].vol=c.value; + if (!chan[c.chan].std.hasVol) { + chan[c.chan].outVol=c.value; + } + for (int i=0; i<4; i++) { + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + if (isOutput[chan[c.chan].state.alg][i]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + } + break; + } + case DIV_CMD_GET_VOLUME: { + return chan[c.chan].vol; + break; + } + case DIV_CMD_INSTRUMENT: + if (chan[c.chan].ins!=c.value || c.value2==1) { + chan[c.chan].insChanged=true; + } + chan[c.chan].ins=c.value; + break; + case DIV_CMD_PANNING: { + chan[c.chan].chVolL=((c.value>>4)>0); + chan[c.chan].chVolR=((c.value&15)>0); + /* + if (isMuted[c.chan]) { + rWrite(chanOffs[c.chan]+ADDR_LR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3)); + } else { + rWrite(chanOffs[c.chan]+ADDR_LR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3)|((chan[c.chan].chVolL&1)<<6)|((chan[c.chan].chVolR&1)<<7)); + }*/ + break; + } + case DIV_CMD_PITCH: { + chan[c.chan].pitch=c.value; + chan[c.chan].freqChanged=true; + break; + } + case DIV_CMD_NOTE_PORTA: { + int destFreq=NOTE_LINEAR(c.value2); + int newFreq; + bool return2=false; + if (destFreq>chan[c.chan].baseFreq) { + newFreq=chan[c.chan].baseFreq+c.value; + if (newFreq>=destFreq) { + newFreq=destFreq; + return2=true; + } + } else { + newFreq=chan[c.chan].baseFreq-c.value; + if (newFreq<=destFreq) { + newFreq=destFreq; + return2=true; + } + } + chan[c.chan].baseFreq=newFreq; + chan[c.chan].freqChanged=true; + if (return2) { + chan[c.chan].inPorta=false; + return 2; + } + break; + } + case DIV_CMD_LEGATO: { + chan[c.chan].baseFreq=NOTE_LINEAR(c.value); + chan[c.chan].freqChanged=true; + break; + } + case DIV_CMD_FM_LFO: { + rWrite(0x18,c.value); + break; + } + case DIV_CMD_FM_LFO_WAVE: { + rWrite(0x1b,c.value&3); + break; + } + case DIV_CMD_FM_FB: { + chan[c.chan].state.fb=c.value&7; + /* + if (isMuted[c.chan]) { + rWrite(chanOffs[c.chan]+ADDR_LR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3)); + } else { + rWrite(chanOffs[c.chan]+ADDR_LR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3)|((chan[c.chan].chVolL&1)<<6)|((chan[c.chan].chVolR&1)<<7)); + }*/ + break; + } + case DIV_CMD_FM_MULT: { + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.mult=c.value2&15; + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + break; + } + case DIV_CMD_FM_TL: { + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.tl=c.value2; + if (isOutput[chan[c.chan].state.alg][c.value]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[c.chan].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + break; + } + case DIV_CMD_FM_AR: { + if (c.value<0) { + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[i]; + op.ar=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[i]; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.egt<<5)|(op.rs<<6)); + } + } else { + DivInstrumentFM::Operator& op=chan[c.chan].state.op[orderedOps[c.value]]; + op.ar=c.value2&31; + unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.egt<<5)|(op.rs<<6)); + } + break; + } + case DIV_CMD_FM_AM_DEPTH: { + amDepth=c.value; + immWrite(0x19,amDepth); + break; + } + case DIV_CMD_FM_PM_DEPTH: { + pmDepth=c.value; + immWrite(0x19,0x80|pmDepth); + break; + } + case DIV_CMD_STD_NOISE_FREQ: { + if (c.chan!=7) break; + if (c.value) { + if (c.value>0x1f) { + rWrite(0x0f,0x80); + } else { + rWrite(0x0f,0x80|(0x1f-c.value)); + } + } else { + rWrite(0x0f,0); + } + break; + } + case DIV_ALWAYS_SET_VOLUME: + return 0; + break; + case DIV_CMD_GET_VOLMAX: + return 127; + break; + case DIV_CMD_PRE_PORTA: + chan[c.chan].inPorta=c.value; + break; + case DIV_CMD_PRE_NOTE: + break; + default: + //printf("WARNING: unimplemented command %d\n",c.cmd); + break; + } + return 1; +} + +void DivPlatformTX81Z::forceIns() { + for (int i=0; i<8; i++) { + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + DivInstrumentFM::Operator op=chan[i].state.op[j]; + if (isOutput[chan[i].state.alg][j]) { + rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); + } else { + rWrite(baseAddr+ADDR_TL,op.tl); + } + rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); + rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.egt<<5)|(op.rs<<6)); + rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); + rWrite(baseAddr+ADDR_DT2_D2R,(op.d2r&31)|(op.dt2<<6)); + rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + rWrite(baseAddr+ADDR_WS_FINE,(op.dvb&15)|(op.ws<<4)); + rWrite(baseAddr+ADDR_EGS_REV,(op.dam&7)|(op.ksl<<6)); + } + /* + if (isMuted[i]) { + rWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); + } else { + rWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|((chan[i].chVolL&1)<<6)|((chan[i].chVolR&1)<<7)); + }*/ + rWrite(chanOffs[i]+ADDR_FMS_AMS,((chan[i].state.fms&7)<<4)|(chan[i].state.ams&3)); + if (chan[i].active) { + chan[i].keyOn=true; + chan[i].freqChanged=true; + } + } + immWrite(0x19,amDepth); + immWrite(0x19,0x80|pmDepth); +} + +void DivPlatformTX81Z::notifyInsChange(int ins) { + for (int i=0; i<8; i++) { + if (chan[i].ins==ins) { + chan[i].insChanged=true; + } + } +} + +void* DivPlatformTX81Z::getChanState(int ch) { + return &chan[ch]; +} + +unsigned char* DivPlatformTX81Z::getRegisterPool() { + return regPool; +} + +int DivPlatformTX81Z::getRegisterPoolSize() { + return 330; +} + +void DivPlatformTX81Z::poke(unsigned int addr, unsigned short val) { + immWrite(addr,val); +} + +void DivPlatformTX81Z::poke(std::vector& wlist) { + for (DivRegWrite& i: wlist) immWrite(i.addr,i.val); +} + +void DivPlatformTX81Z::reset() { + while (!writes.empty()) writes.pop(); + memset(regPool,0,330); + fm_ymfm->reset(); + if (dumpWrites) { + addWrite(0xffffffff,0); + } + for (int i=0; i<8; i++) { + chan[i]=DivPlatformTX81Z::Channel(); + chan[i].vol=0x7f; + chan[i].outVol=0x7f; + } + + for (int i=0; i<330; i++) { + oldWrites[i]=-1; + pendingWrites[i]=-1; + } + + lastBusy=60; + pcmCycles=0; + pcmL=0; + pcmR=0; + delay=0; + amDepth=0x7f; + pmDepth=0x7f; + + //rWrite(0x18,0x10); + immWrite(0x19,amDepth); + immWrite(0x19,0x80|pmDepth); + //rWrite(0x1b,0x00); + + extMode=false; +} + +void DivPlatformTX81Z::setFlags(unsigned int flags) { + if (flags==2) { + chipClock=4000000.0; + baseFreqOff=-122; + } else if (flags==1) { + chipClock=COLOR_PAL*4.0/5.0; + baseFreqOff=12; + } else { + chipClock=COLOR_NTSC; + baseFreqOff=0; + } + rate=chipClock/64; +} + +bool DivPlatformTX81Z::isStereo() { + return true; +} + +int DivPlatformTX81Z::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { + parent=p; + dumpWrites=false; + skipRegisterWrites=false; + for (int i=0; i<8; i++) { + isMuted[i]=false; + } + setFlags(flags); + fm_ymfm=new ymfm::ym2414(iface); + reset(); + + return 8; +} + +void DivPlatformTX81Z::quit() { + delete fm_ymfm; +} + +DivPlatformTX81Z::~DivPlatformTX81Z() { +} diff --git a/src/engine/platform/tx81z.h b/src/engine/platform/tx81z.h new file mode 100644 index 000000000..b3d00e523 --- /dev/null +++ b/src/engine/platform/tx81z.h @@ -0,0 +1,98 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _TX81Z_H +#define _TX81Z_H +#include "../dispatch.h" +#include "../instrument.h" +#include +#include "sound/ymfm/ymfm_opz.h" +#include "../macroInt.h" + +class DivTXInterface: public ymfm::ymfm_interface { + +}; + +class DivPlatformTX81Z: public DivDispatch { + protected: + struct Channel { + DivInstrumentFM state; + DivMacroInt std; + unsigned char freqH, freqL; + int freq, baseFreq, pitch, note; + unsigned char ins; + signed char konCycles; + bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, portaPause, furnacePCM; + int vol, outVol; + unsigned char chVolL, chVolR; + Channel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), note(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), inPorta(false), portaPause(false), furnacePCM(false), vol(0), outVol(0), chVolL(127), chVolR(127) {} + }; + Channel chan[8]; + struct QueuedWrite { + unsigned short addr; + unsigned char val; + bool addrOrVal; + QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v), addrOrVal(false) {} + }; + std::queue writes; + int delay, baseFreqOff; + int pcmL, pcmR, pcmCycles; + unsigned char lastBusy; + unsigned char amDepth, pmDepth; + + ymfm::ym2414* fm_ymfm; + ymfm::ym2414::output_data out_ymfm; + DivTXInterface iface; + + unsigned char regPool[330]; + + bool extMode; + + bool isMuted[8]; + + short oldWrites[330]; + short pendingWrites[330]; + + int octave(int freq); + int toFreq(int freq); + + friend void putDispatchChan(void*,int,int); + + public: + void acquire(short* bufL, short* bufR, size_t start, size_t len); + int dispatch(DivCommand c); + void* getChanState(int chan); + unsigned char* getRegisterPool(); + int getRegisterPoolSize(); + void reset(); + void forceIns(); + void tick(); + void muteChannel(int ch, bool mute); + void notifyInsChange(int ins); + void setFlags(unsigned int flags); + bool isStereo(); + void poke(unsigned int addr, unsigned short val); + void poke(std::vector& wlist); + const char** getRegisterSheet(); + const char* getEffectName(unsigned char effect); + int init(DivEngine* parent, int channels, int sugRate, unsigned int flags); + void quit(); + ~DivPlatformTX81Z(); +}; +#endif diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index 6c320320c..a00ee83c8 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -493,6 +493,7 @@ const int availableSystems[]={ DIV_SYSTEM_OPL2_DRUMS, DIV_SYSTEM_OPL3, DIV_SYSTEM_OPL3_DRUMS, + DIV_SYSTEM_OPZ, DIV_SYSTEM_TIA, DIV_SYSTEM_SAA1099, DIV_SYSTEM_AY8930, From b162c09f7c0631ac88f56970e8f10abcf2624077 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 7 Apr 2022 00:45:53 -0500 Subject: [PATCH 543/637] GUI: OPZ instrument editor work - dev77 --- papers/format.md | 4 + src/engine/engine.h | 4 +- src/engine/instrument.cpp | 10 ++ src/engine/instrument.h | 4 +- src/gui/insEdit.cpp | 303 ++++++++++++++++++++++++++++---------- 5 files changed, 245 insertions(+), 80 deletions(-) diff --git a/papers/format.md b/papers/format.md index f6e836dd5..2876ffe3a 100644 --- a/papers/format.md +++ b/papers/format.md @@ -29,6 +29,7 @@ furthermore, an `or reserved` indicates this field is always present, but is res the format versions are: +- 77: Furnace dev77 - 76: Furnace dev76 - 75: Furnace dev75/April Fools' 0.6pre0 - 74: Furnace dev74 @@ -573,6 +574,9 @@ size | description 1 | init modulation table with first wave 3 | reserved 32 | modulation table + --- | **OPZ instrument extra data** (>=77) + 1 | fms2 + 1 | ams2 ``` # wavetable diff --git a/src/engine/engine.h b/src/engine/engine.h index 9a15f4f11..8fc8427d1 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -42,8 +42,8 @@ #define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock(); #define BUSY_END isBusy.unlock(); softLocked=false; -#define DIV_VERSION "dev76" -#define DIV_ENGINE_VERSION 76 +#define DIV_VERSION "dev77" +#define DIV_ENGINE_VERSION 77 // for imports #define DIV_VERSION_MOD 0xff01 diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index 3dd1e883c..1299cdec8 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -463,6 +463,10 @@ void DivInstrument::putInsData(SafeWriter* w) { w->writeC(0); w->writeC(0); w->write(fds.modTable,32); + + // OPZ + w->writeC(fm.fms2); + w->writeC(fm.ams2); } DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) { @@ -885,6 +889,12 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) { reader.readC(); reader.read(fds.modTable,32); } + + // OPZ + if (version>=77) { + fm.fms2=reader.readC(); + fm.ams2=reader.readC(); + } return DIV_DATA_SUCCESS; } diff --git a/src/engine/instrument.h b/src/engine/instrument.h index 2892cbd43..ab00c9aab 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -73,7 +73,7 @@ enum DivInstrumentType { // - WS, DVB = MULT (FINE), DAM = REV, KSL = EGShift, EGT = Fixed struct DivInstrumentFM { - unsigned char alg, fb, fms, ams, ops, opllPreset; + unsigned char alg, fb, fms, ams, fms2, ams2, ops, opllPreset; bool fixedDrums; unsigned short kickFreq, snareHatFreq, tomTopFreq; struct Operator { @@ -106,6 +106,8 @@ struct DivInstrumentFM { fb(0), fms(0), ams(0), + fms2(0), + ams2(0), ops(2), opllPreset(0), fixedDrums(false), diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 8ddbcbe34..e452f25ab 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -31,16 +31,16 @@ const char* ssgEnvTypes[8]={ "Down Down Down", "Down.", "Down Up Down Up", "Down UP", "Up Up Up", "Up.", "Up Down Up Down", "Up DOWN" }; -const char* fmParamNames[3][27]={ - {"Algorithm", "Feedback", "LFO > Freq", "LFO > Amp", "Attack", "Decay", "Decay 2", "Release", "Sustain", "Level", "EnvScale", "Multiplier", "Detune", "Detune 2", "SSG-EG", "AM", "AM Depth", "Vibrato Depth", "Sustained", "Sustained", "Level Scaling", "Sustain", "Vibrato", "Waveform", "Key Scale Rate", "OP2 Half Sine", "OP1 Half Sine"}, - {"ALG", "FB", "FMS/PMS", "AMS", "AR", "DR", "SR", "RR", "SL", "TL", "KS", "MULT", "DT", "DT2", "SSG-EG", "AM", "AMD", "FMD", "EGT", "EGT", "KSL", "SUS", "VIB", "WS", "KSR", "DC", "DM"}, - {"ALG", "FB", "FMS/PMS", "AMS", "AR", "DR", "D2R", "RR", "SL", "TL", "RS", "MULT", "DT", "DT2", "SSG-EG", "AM", "DAM", "DVB", "EGT", "EGS", "KSL", "SUS", "VIB", "WS", "KSR", "DC", "DM"} +const char* fmParamNames[3][32]={ + {"Algorithm", "Feedback", "LFO > Freq", "LFO > Amp", "Attack", "Decay", "Decay 2", "Release", "Sustain", "Level", "EnvScale", "Multiplier", "Detune", "Detune 2", "SSG-EG", "AM", "AM Depth", "Vibrato Depth", "Sustained", "Sustained", "Level Scaling", "Sustain", "Vibrato", "Waveform", "Key Scale Rate", "OP2 Half Sine", "OP1 Half Sine", "EnvShift", "Reverb", "Fine", "LFO > Freq", "LFO > Amp"}, + {"ALG", "FB", "FMS/PMS", "AMS", "AR", "DR", "SR", "RR", "SL", "TL", "KS", "MULT", "DT", "DT2", "SSG-EG", "AM", "AMD", "FMD", "EGT", "EGT", "KSL", "SUS", "VIB", "WS", "KSR", "DC", "DM", "EGS", "REV", "Fine", "FMS/PMS2", "AMS2"}, + {"ALG", "FB", "FMS/PMS", "AMS", "AR", "DR", "D2R", "RR", "SL", "TL", "RS", "MULT", "DT", "DT2", "SSG-EG", "AM", "DAM", "DVB", "EGT", "EGS", "KSL", "SUS", "VIB", "WS", "KSR", "DC", "DM", "EGS", "REV", "Fine", "FMS/PMS2", "AMS2"} }; -const char* fmParamShortNames[3][27]={ - {"ALG", "FB", "FMS", "AMS", "A", "D", "D2", "R", "S", "TL", "RS", "ML", "DT", "DT2", "SSG", "AM", "DAM", "DVB", "SUS", "SUS", "KSL", "SUS", "VIB", "WS", "KSR", "DC", "DM"}, - {"ALG", "FB", "FMS", "AMS", "A", "D", "SR", "R", "S", "TL", "KS", "ML", "DT", "DT2", "SSG", "AM", "AMD", "FMD", "EGT", "EGT", "KSL", "SUS", "VIB", "WS", "KSR", "DC", "DM"}, - {"ALG", "FB", "FMS", "AMS", "A", "D", "D2", "R", "S", "TL", "RS", "ML", "DT", "DT2", "SSG", "AM", "DAM", "DVB", "EGT", "EGS", "KSL", "SUS", "VIB", "WS", "KSR", "DC", "DM"} +const char* fmParamShortNames[3][32]={ + {"ALG", "FB", "FMS", "AMS", "A", "D", "D2", "R", "S", "TL", "RS", "ML", "DT", "DT2", "SSG", "AM", "DAM", "DVB", "SUS", "SUS", "KSL", "SUS", "VIB", "WS", "KSR", "DC", "DM", "EGS", "REV", "Fine", "FMS2", "AMS2"}, + {"ALG", "FB", "FMS", "AMS", "A", "D", "SR", "R", "S", "TL", "KS", "ML", "DT", "DT2", "SSG", "AM", "AMD", "FMD", "EGT", "EGT", "KSL", "SUS", "VIB", "WS", "KSR", "DC", "DM", "EGS", "REV", "Fine", "FMS2", "AMS2"}, + {"ALG", "FB", "FMS", "AMS", "A", "D", "D2", "R", "S", "TL", "RS", "ML", "DT", "DT2", "SSG", "AM", "DAM", "DVB", "EGT", "EGS", "KSL", "SUS", "VIB", "WS", "KSR", "DC", "DM", "EGS", "REV", "Fine", "FMS2", "AMS2"} }; const char* opllInsNames[17]={ @@ -98,7 +98,12 @@ enum FMParams { FM_WS=23, FM_KSR=24, FM_DC=25, - FM_DM=26 + FM_DM=26, + FM_EGSHIFT=27, + FM_REV=28, + FM_FINE=29, + FM_FMS2=30, + FM_AMS2=31 }; #define FM_NAME(x) fmParamNames[settings.fmNames][x] @@ -318,63 +323,126 @@ void FurnaceGUI::drawWaveform(unsigned char type, bool opz, const ImVec2& size) ImGui::ItemSize(size,style.FramePadding.y); if (ImGui::ItemAdd(rect,ImGui::GetID("wsDisplay"))) { ImGui::RenderFrame(rect.Min,rect.Max,ImGui::GetColorU32(ImGuiCol_FrameBg),true,style.FrameRounding); - switch (type) { - case 0: - for (size_t i=0; i<=waveformLen; i++) { - float x=(float)i/(float)waveformLen; - float y=sin(x*2.0*M_PI); - waveform[i]=ImLerp(rect.Min,rect.Max,ImVec2(x,0.5-y*0.4)); - } - break; - case 1: - for (size_t i=0; i<=waveformLen; i++) { - float x=(float)i/(float)waveformLen; - float y=MAX(0.0,sin(x*2.0*M_PI)); - waveform[i]=ImLerp(rect.Min,rect.Max,ImVec2(x,0.5-y*0.4)); - } - break; - case 2: - for (size_t i=0; i<=waveformLen; i++) { - float x=(float)i/(float)waveformLen; - float y=fabs(sin(x*2.0*M_PI)); - waveform[i]=ImLerp(rect.Min,rect.Max,ImVec2(x,0.5-y*0.4)); - } - break; - case 3: - for (size_t i=0; i<=waveformLen; i++) { - float x=(float)i/(float)waveformLen; - float y=fabs((tan(x*2.0*M_PI)>=0.0)?sin(x*2.0*M_PI):0.0); - waveform[i]=ImLerp(rect.Min,rect.Max,ImVec2(x,0.5-y*0.4)); - } - break; - case 4: - for (size_t i=0; i<=waveformLen; i++) { - float x=(float)i/(float)waveformLen; - float y=(x>=0.5)?0.0:sin(x*4.0*M_PI); - waveform[i]=ImLerp(rect.Min,rect.Max,ImVec2(x,0.5-y*0.4)); - } - break; - case 5: - for (size_t i=0; i<=waveformLen; i++) { - float x=(float)i/(float)waveformLen; - float y=(x>=0.5)?0.0:fabs(sin(x*4.0*M_PI)); - waveform[i]=ImLerp(rect.Min,rect.Max,ImVec2(x,0.5-y*0.4)); - } - break; - case 6: - for (size_t i=0; i<=waveformLen; i++) { - float x=(float)i/(float)waveformLen; - float y=(x>=0.5)?-1.0:1.0; - waveform[i]=ImLerp(rect.Min,rect.Max,ImVec2(x,0.5-y*0.4)); - } - break; - case 7: - for (size_t i=0; i<=waveformLen; i++) { - float x=(float)i/(float)waveformLen; - float y=pow(2.0*(x-0.5),3.0); - waveform[i]=ImLerp(rect.Min,rect.Max,ImVec2(x,0.5-y*0.4)); - } - break; + if (opz) { + switch (type) { + case 0: + for (size_t i=0; i<=waveformLen; i++) { + float x=(float)i/(float)waveformLen; + float y=sin(x*2.0*M_PI); + waveform[i]=ImLerp(rect.Min,rect.Max,ImVec2(x,0.5-y*0.4)); + } + break; + case 1: + for (size_t i=0; i<=waveformLen; i++) { + float x=(float)i/(float)waveformLen; + float y=pow(sin(x*2.0*M_PI),2.0); + if (x>=0.5) y=-y; + waveform[i]=ImLerp(rect.Min,rect.Max,ImVec2(x,0.5-y*0.4)); + } + break; + case 2: + for (size_t i=0; i<=waveformLen; i++) { + float x=(float)i/(float)waveformLen; + float y=MAX(0.0,sin(x*2.0*M_PI)); + waveform[i]=ImLerp(rect.Min,rect.Max,ImVec2(x,0.5-y*0.4)); + } + break; + case 3: + for (size_t i=0; i<=waveformLen; i++) { + float x=(float)i/(float)waveformLen; + float y=pow(MAX(0.0,sin(x*2.0*M_PI)),2.0); + waveform[i]=ImLerp(rect.Min,rect.Max,ImVec2(x,0.5-y*0.4)); + } + break; + case 4: + for (size_t i=0; i<=waveformLen; i++) { + float x=(float)i/(float)waveformLen; + float y=(x>=0.5)?0.0:sin(x*4.0*M_PI); + waveform[i]=ImLerp(rect.Min,rect.Max,ImVec2(x,0.5-y*0.4)); + } + break; + case 5: + for (size_t i=0; i<=waveformLen; i++) { + float x=(float)i/(float)waveformLen; + float y=(x>=0.5)?0.0:pow(sin(x*4.0*M_PI),2.0); + if (x>=0.25) y=-y; + waveform[i]=ImLerp(rect.Min,rect.Max,ImVec2(x,0.5-y*0.4)); + } + break; + case 6: + for (size_t i=0; i<=waveformLen; i++) { + float x=(float)i/(float)waveformLen; + float y=(x>=0.5)?0.0:fabs(sin(x*4.0*M_PI)); + waveform[i]=ImLerp(rect.Min,rect.Max,ImVec2(x,0.5-y*0.4)); + } + break; + case 7: + for (size_t i=0; i<=waveformLen; i++) { + float x=(float)i/(float)waveformLen; + float y=(x>=0.5)?0.0:pow(sin(x*4.0*M_PI),2.0); + waveform[i]=ImLerp(rect.Min,rect.Max,ImVec2(x,0.5-y*0.4)); + } + break; + } + } else { + switch (type) { + case 0: + for (size_t i=0; i<=waveformLen; i++) { + float x=(float)i/(float)waveformLen; + float y=sin(x*2.0*M_PI); + waveform[i]=ImLerp(rect.Min,rect.Max,ImVec2(x,0.5-y*0.4)); + } + break; + case 1: + for (size_t i=0; i<=waveformLen; i++) { + float x=(float)i/(float)waveformLen; + float y=MAX(0.0,sin(x*2.0*M_PI)); + waveform[i]=ImLerp(rect.Min,rect.Max,ImVec2(x,0.5-y*0.4)); + } + break; + case 2: + for (size_t i=0; i<=waveformLen; i++) { + float x=(float)i/(float)waveformLen; + float y=fabs(sin(x*2.0*M_PI)); + waveform[i]=ImLerp(rect.Min,rect.Max,ImVec2(x,0.5-y*0.4)); + } + break; + case 3: + for (size_t i=0; i<=waveformLen; i++) { + float x=(float)i/(float)waveformLen; + float y=fabs((tan(x*2.0*M_PI)>=0.0)?sin(x*2.0*M_PI):0.0); + waveform[i]=ImLerp(rect.Min,rect.Max,ImVec2(x,0.5-y*0.4)); + } + break; + case 4: + for (size_t i=0; i<=waveformLen; i++) { + float x=(float)i/(float)waveformLen; + float y=(x>=0.5)?0.0:sin(x*4.0*M_PI); + waveform[i]=ImLerp(rect.Min,rect.Max,ImVec2(x,0.5-y*0.4)); + } + break; + case 5: + for (size_t i=0; i<=waveformLen; i++) { + float x=(float)i/(float)waveformLen; + float y=(x>=0.5)?0.0:fabs(sin(x*4.0*M_PI)); + waveform[i]=ImLerp(rect.Min,rect.Max,ImVec2(x,0.5-y*0.4)); + } + break; + case 6: + for (size_t i=0; i<=waveformLen; i++) { + float x=(float)i/(float)waveformLen; + float y=(x>=0.5)?-1.0:1.0; + waveform[i]=ImLerp(rect.Min,rect.Max,ImVec2(x,0.5-y*0.4)); + } + break; + case 7: + for (size_t i=0; i<=waveformLen; i++) { + float x=(float)i/(float)waveformLen; + float y=pow(2.0*(x-0.5),3.0); + waveform[i]=ImLerp(rect.Min,rect.Max,ImVec2(x,0.5-y*0.4)); + } + break; + } } dl->AddPolyline(waveform,waveformLen+1,color,ImDrawFlags_None,dpiScale); } @@ -1204,7 +1272,6 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextRow(); switch (ins->type) { case DIV_INS_FM: - case DIV_INS_OPZ: ImGui::TableNextColumn(); P(CWSliderScalar(FM_NAME(FM_FB),ImGuiDataType_U8,&ins->fm.fb,&_ZERO,&_SEVEN)); rightClickable P(CWSliderScalar(FM_NAME(FM_FMS),ImGuiDataType_U8,&ins->fm.fms,&_ZERO,&_SEVEN)); rightClickable @@ -1214,6 +1281,21 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); drawAlgorithm(ins->fm.alg,FM_ALGS_4OP,ImVec2(ImGui::GetContentRegionAvail().x,48.0*dpiScale)); break; + case DIV_INS_OPZ: + ImGui::TableNextColumn(); + P(CWSliderScalar(FM_NAME(FM_FB),ImGuiDataType_U8,&ins->fm.fb,&_ZERO,&_SEVEN)); rightClickable + P(CWSliderScalar(FM_NAME(FM_FMS),ImGuiDataType_U8,&ins->fm.fms,&_ZERO,&_SEVEN)); rightClickable + P(CWSliderScalar(FM_NAME(FM_FMS2),ImGuiDataType_U8,&ins->fm.fms2,&_ZERO,&_SEVEN)); rightClickable + ImGui::TableNextColumn(); + P(CWSliderScalar(FM_NAME(FM_ALG),ImGuiDataType_U8,&ins->fm.alg,&_ZERO,&_SEVEN)); rightClickable + P(CWSliderScalar(FM_NAME(FM_AMS),ImGuiDataType_U8,&ins->fm.ams,&_ZERO,&_THREE)); rightClickable + P(CWSliderScalar(FM_NAME(FM_AMS2),ImGuiDataType_U8,&ins->fm.ams2,&_ZERO,&_THREE)); rightClickable + ImGui::TableNextColumn(); + drawAlgorithm(ins->fm.alg,FM_ALGS_4OP,ImVec2(ImGui::GetContentRegionAvail().x,48.0*dpiScale)); + if (ImGui::Button("Send to TX81Z")) { + showError("Coming soon!"); + } + break; case DIV_INS_OPL: { bool fourOp=(ins->fm.ops==4); bool drums=ins->fm.opllPreset==16; @@ -1311,6 +1393,7 @@ void FurnaceGUI::drawInsEdit() { int numCols=16; if (ins->type==DIV_INS_OPL) numCols=13; if (ins->type==DIV_INS_OPLL) numCols=12; + if (ins->type==DIV_INS_OPZ) numCols=19; if (ImGui::BeginTable("FMOperators",numCols,ImGuiTableFlags_SizingStretchProp|ImGuiTableFlags_BordersH|ImGuiTableFlags_BordersOuterV)) { // configure columns ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); // op name @@ -1324,8 +1407,16 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableSetupColumn("c6",ImGuiTableColumnFlags_WidthFixed); // -separator- ImGui::TableSetupColumn("c7",ImGuiTableColumnFlags_WidthStretch,0.05f); // tl ImGui::TableSetupColumn("c8",ImGuiTableColumnFlags_WidthStretch,0.05f); // rs/ksl + if (ins->type==DIV_INS_OPZ) { + ImGui::TableSetupColumn("c8z0",ImGuiTableColumnFlags_WidthStretch,0.05f); // egs + ImGui::TableSetupColumn("c8z1",ImGuiTableColumnFlags_WidthStretch,0.05f); // rev + } ImGui::TableSetupColumn("c9",ImGuiTableColumnFlags_WidthStretch,0.05f); // mult + if (ins->type==DIV_INS_OPZ) { + ImGui::TableSetupColumn("c9z",ImGuiTableColumnFlags_WidthStretch,0.05f); // fine + } + if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { ImGui::TableSetupColumn("c10",ImGuiTableColumnFlags_WidthStretch,0.05f); // dt ImGui::TableSetupColumn("c11",ImGuiTableColumnFlags_WidthStretch,0.05f); // dt2 @@ -1378,9 +1469,22 @@ void FurnaceGUI::drawInsEdit() { CENTER_TEXT(FM_SHORT_NAME(FM_KSL)); ImGui::TextUnformatted(FM_SHORT_NAME(FM_KSL)); } + if (ins->type==DIV_INS_OPZ) { + ImGui::TableNextColumn(); + CENTER_TEXT(FM_SHORT_NAME(FM_EGSHIFT)); + ImGui::TextUnformatted(FM_SHORT_NAME(FM_EGSHIFT)); + ImGui::TableNextColumn(); + CENTER_TEXT(FM_SHORT_NAME(FM_REV)); + ImGui::TextUnformatted(FM_SHORT_NAME(FM_REV)); + } ImGui::TableNextColumn(); CENTER_TEXT(FM_SHORT_NAME(FM_MULT)); ImGui::TextUnformatted(FM_SHORT_NAME(FM_MULT)); + if (ins->type==DIV_INS_OPZ) { + ImGui::TableNextColumn(); + CENTER_TEXT(FM_SHORT_NAME(FM_FINE)); + ImGui::TextUnformatted(FM_SHORT_NAME(FM_FINE)); + } ImGui::TableNextColumn(); if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { CENTER_TEXT(FM_SHORT_NAME(FM_DT)); @@ -1390,7 +1494,7 @@ void FurnaceGUI::drawInsEdit() { ImGui::TextUnformatted(FM_SHORT_NAME(FM_DT2)); ImGui::TableNextColumn(); } - if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { + if (ins->type==DIV_INS_FM) { CENTER_TEXT(FM_SHORT_NAME(FM_AM)); ImGui::TextUnformatted(FM_SHORT_NAME(FM_AM)); } else { @@ -1501,10 +1605,26 @@ void FurnaceGUI::drawInsEdit() { P(CWVSliderScalar("##KSL",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.ksl,&_ZERO,&_THREE)); } + if (ins->type==DIV_INS_OPZ) { + ImGui::TableNextColumn(); + CENTER_VSLIDER; + P(CWVSliderScalar("##EGS",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.ksl,&_ZERO,&_THREE)); + + ImGui::TableNextColumn(); + CENTER_VSLIDER; + P(CWVSliderScalar("##REV",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.dam,&_ZERO,&_SEVEN)); + } + ImGui::TableNextColumn(); CENTER_VSLIDER; P(CWVSliderScalar("##MULT",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.mult,&_ZERO,&_FIFTEEN)); + if (ins->type==DIV_INS_OPZ) { + ImGui::TableNextColumn(); + CENTER_VSLIDER; + P(CWVSliderScalar("##FINE",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.dvb,&_ZERO,&_FIFTEEN)); + } + if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { int detune=(op.dt&7)-3; ImGui::TableNextColumn(); @@ -1516,22 +1636,51 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); CENTER_VSLIDER; P(CWVSliderScalar("##DT2",ImVec2(20.0f*dpiScale,sliderHeight),ImGuiDataType_U8,&op.dt2,&_ZERO,&_THREE)); rightClickable - if (ImGui::IsItemHovered()) { + if (ImGui::IsItemHovered() && ins->type==DIV_INS_FM) { ImGui::SetTooltip("Only on YM2151 (OPM)"); } ImGui::TableNextColumn(); bool amOn=op.am; - ImGui::SetCursorPosY(ImGui::GetCursorPosY()+0.5*(sliderHeight-ImGui::GetFrameHeight())); - if (ImGui::Checkbox("##AM",&amOn)) { PARAMETER - op.am=amOn; + if (ins->type==DIV_INS_OPZ) { + bool egtOn=op.egt; + if (egtOn) { + ImGui::SetCursorPosY(ImGui::GetCursorPosY()+0.5*(sliderHeight-ImGui::GetFrameHeight()*4.0-ImGui::GetStyle().ItemSpacing.y*3.0)); + } else { + ImGui::SetCursorPosY(ImGui::GetCursorPosY()+0.5*(sliderHeight-ImGui::GetFrameHeight()*2.0-ImGui::GetStyle().ItemSpacing.y*1.0)); + } + if (ImGui::Checkbox("AM",&amOn)) { PARAMETER + op.am=amOn; + } + if (ImGui::Checkbox("Fixed",&egtOn)) { PARAMETER + op.egt=egtOn; + } + if (egtOn) { + int block=op.dt; + int freqNum=(op.mult<<4)|(op.dvb&15); + if (ImGui::InputInt("Block",&block,1,1)) { + if (block<0) block=0; + if (block>7) block=7; + op.dt=block; + } + if (ImGui::InputInt("FreqNum",&freqNum,1,16)) { + if (freqNum<0) freqNum=0; + if (freqNum>255) freqNum=255; + op.mult=freqNum>>4; + op.dvb=freqNum&15; + } + } + } else { + ImGui::SetCursorPosY(ImGui::GetCursorPosY()+0.5*(sliderHeight-ImGui::GetFrameHeight())); + if (ImGui::Checkbox("##AM",&amOn)) { PARAMETER + op.am=amOn; + } } - ImGui::TableNextColumn(); - ImGui::Dummy(ImVec2(4.0f*dpiScale,2.0f*dpiScale)); - - ImGui::TableNextColumn(); if (ins->type!=DIV_INS_OPL && ins->type!=DIV_INS_OPZ) { + ImGui::TableNextColumn(); + ImGui::Dummy(ImVec2(4.0f*dpiScale,2.0f*dpiScale)); + ImGui::TableNextColumn(); ImGui::BeginDisabled(!ssgOn); drawSSGEnv(op.ssgEnv&7,ImVec2(ImGui::GetContentRegionAvail().x,sliderHeight-ImGui::GetFrameHeightWithSpacing())); ImGui::EndDisabled(); @@ -1779,7 +1928,7 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); P(CWSliderScalar("##DT2",ImGuiDataType_U8,&op.dt2,&_ZERO,&_THREE)); rightClickable - if (ImGui::IsItemHovered()) { + if (ImGui::IsItemHovered() && ins->type==DIV_INS_FM) { ImGui::SetTooltip("Only on YM2151 (OPM)"); } ImGui::TableNextColumn(); From 78cd99af328fec1438f8e90b162c34c75f100d80 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 7 Apr 2022 00:57:52 -0500 Subject: [PATCH 544/637] update ymfm --- src/engine/platform/sound/ymfm/ymfm_fm.h | 3 ++- src/engine/platform/sound/ymfm/ymfm_fm.ipp | 19 ++++++++++++++----- src/engine/platform/sound/ymfm/ymfm_opm.cpp | 1 + src/engine/platform/sound/ymfm/ymfm_opn.cpp | 8 ++++---- src/engine/platform/sound/ymfm/ymfm_opn.h | 2 +- 5 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/engine/platform/sound/ymfm/ymfm_fm.h b/src/engine/platform/sound/ymfm/ymfm_fm.h index 01cb22e77..3239880e5 100644 --- a/src/engine/platform/sound/ymfm/ymfm_fm.h +++ b/src/engine/platform/sound/ymfm/ymfm_fm.h @@ -426,7 +426,7 @@ protected: void assign_operators(); // update the state of the given timer - void update_timer(uint32_t which, uint32_t enable); + void update_timer(uint32_t which, uint32_t enable, int32_t delta_clocks); // internal state ymfm_interface &m_intf; // reference to the system interface @@ -436,6 +436,7 @@ protected: uint8_t m_irq_mask; // mask of which bits signal IRQs uint8_t m_irq_state; // current IRQ state uint8_t m_timer_running[2]; // current timer running state + uint8_t m_total_clocks; // low 8 bits of the total number of clocks processed uint32_t m_active_channels; // mask of active channels (computed by prepare) uint32_t m_modified_channels; // mask of channels that have been modified uint32_t m_prepare_count; // counter to do periodic prepare sweeps diff --git a/src/engine/platform/sound/ymfm/ymfm_fm.ipp b/src/engine/platform/sound/ymfm/ymfm_fm.ipp index 410569458..84948aedb 100644 --- a/src/engine/platform/sound/ymfm/ymfm_fm.ipp +++ b/src/engine/platform/sound/ymfm/ymfm_fm.ipp @@ -1236,6 +1236,7 @@ void fm_engine_base::save_restore(ymfm_saved_state &state) state.save_restore(m_irq_state); state.save_restore(m_timer_running[0]); state.save_restore(m_timer_running[1]); + state.save_restore(m_total_clocks); // save the register/family data m_regs.save_restore(state); @@ -1261,6 +1262,9 @@ void fm_engine_base::save_restore(ymfm_saved_state &state) template uint32_t fm_engine_base::clock(uint32_t chanmask) { + // update the clock counter + m_total_clocks++; + // if something was modified, prepare // also prepare every 4k samples to catch ending notes if (m_modified_channels != 0 || m_prepare_count++ >= 4096) @@ -1455,7 +1459,7 @@ void fm_engine_base::assign_operators() //------------------------------------------------- template -void fm_engine_base::update_timer(uint32_t tnum, uint32_t enable) +void fm_engine_base::update_timer(uint32_t tnum, uint32_t enable, int32_t delta_clocks) { // if the timer is live, but not currently enabled, set the timer if (enable && !m_timer_running[tnum]) @@ -1463,6 +1467,9 @@ void fm_engine_base::update_timer(uint32_t tnum, uint32_t enable) // period comes from the registers, and is different for each uint32_t period = (tnum == 0) ? (1024 - m_regs.timer_a_value()) : 16 * (256 - m_regs.timer_b_value()); + // caller can also specify a delta to account for other effects + period += delta_clocks; + // reset it m_intf.ymfm_set_timer(tnum, period * OPERATORS * m_clock_prescale); m_timer_running[tnum] = 1; @@ -1499,7 +1506,7 @@ void fm_engine_base::engine_timer_expired(uint32_t tnum) // reset m_timer_running[tnum] = false; - update_timer(tnum, 1); + update_timer(tnum, 1, 0); } @@ -1557,9 +1564,11 @@ void fm_engine_base::engine_mode_write(uint8_t data) reset_mask |= RegisterType::STATUS_TIMERA; set_reset_status(0, reset_mask); - // load timers - update_timer(1, m_regs.load_timer_b()); - update_timer(0, m_regs.load_timer_a()); + // load timers; note that timer B gets a small negative adjustment because + // the *16 multiplier is free-running, so the first tick of the clock + // is a bit shorter + update_timer(1, m_regs.load_timer_b(), -(m_total_clocks & 15)); + update_timer(0, m_regs.load_timer_a(), 0); } } diff --git a/src/engine/platform/sound/ymfm/ymfm_opm.cpp b/src/engine/platform/sound/ymfm/ymfm_opm.cpp index 9fd447e8c..544bbe89a 100644 --- a/src/engine/platform/sound/ymfm/ymfm_opm.cpp +++ b/src/engine/platform/sound/ymfm/ymfm_opm.cpp @@ -74,6 +74,7 @@ opm_registers::opm_registers() : m_lfo_waveform[2][index] = am | (pm << 8); // waveform 3 is noise; it is filled in dynamically + m_lfo_waveform[3][index] = 0; } } diff --git a/src/engine/platform/sound/ymfm/ymfm_opn.cpp b/src/engine/platform/sound/ymfm/ymfm_opn.cpp index 4a334a63b..00ac2cf63 100644 --- a/src/engine/platform/sound/ymfm/ymfm_opn.cpp +++ b/src/engine/platform/sound/ymfm/ymfm_opn.cpp @@ -2398,8 +2398,8 @@ void ym2612::generate(output_data *output, uint32_t numsamples) // a better sound mixer than we usually have, so just average over the six // channels; also apply a 64/65 factor to account for the discontinuity // adjustment above - output->data[0] = (output->data[0] << 7) * 64 / (6 * 65); - output->data[1] = (output->data[1] << 7) * 64 / (6 * 65); + output->data[0] = (output->data[0] * 128) * 64 / (6 * 65); + output->data[1] = (output->data[1] * 128) * 64 / (6 * 65); } } @@ -2432,8 +2432,8 @@ void ym3438::generate(output_data *output, uint32_t numsamples) // YM3438 doesn't have the same DAC discontinuity, though its output is // multiplexed like the YM2612 - output->data[0] = (output->data[0] << 7) / 6; - output->data[1] = (output->data[1] << 7) / 6; + output->data[0] = (output->data[0] * 128) / 6; + output->data[1] = (output->data[1] * 128) / 6; } } diff --git a/src/engine/platform/sound/ymfm/ymfm_opn.h b/src/engine/platform/sound/ymfm/ymfm_opn.h index 8d9b42de0..f4136c731 100644 --- a/src/engine/platform/sound/ymfm/ymfm_opn.h +++ b/src/engine/platform/sound/ymfm/ymfm_opn.h @@ -763,7 +763,7 @@ public: protected: // simulate the DAC discontinuity - int32_t dac_discontinuity(int32_t value) const { return (value < 0) ? (value - 2) : (value + 3); } + constexpr int32_t dac_discontinuity(int32_t value) const { return (value < 0) ? (value - 2) : (value + 3); } // internal state uint16_t m_address; // address register From e1b77d7e2b1b390a149f40d516b091dd3e38ad63 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 7 Apr 2022 01:03:16 -0500 Subject: [PATCH 545/637] OPM: if the LFO rate is 0, don't clock the counter --- src/engine/platform/sound/ymfm/ymfm_opm.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/engine/platform/sound/ymfm/ymfm_opm.cpp b/src/engine/platform/sound/ymfm/ymfm_opm.cpp index 544bbe89a..31d0a7467 100644 --- a/src/engine/platform/sound/ymfm/ymfm_opm.cpp +++ b/src/engine/platform/sound/ymfm/ymfm_opm.cpp @@ -87,6 +87,8 @@ void opm_registers::reset() { std::fill_n(&m_regdata[0], REGISTERS, 0); + m_lfo_counter = 0; + // enable output on both channels by default m_regdata[0x20] = m_regdata[0x21] = m_regdata[0x22] = m_regdata[0x23] = 0xc0; m_regdata[0x24] = m_regdata[0x25] = m_regdata[0x26] = m_regdata[0x27] = 0xc0; @@ -195,8 +197,13 @@ int32_t opm_registers::clock_noise_and_lfo() // treat the rate as a 4.4 floating-point step value with implied // leading 1; this matches exactly the frequencies in the application // manual, though it might not be implemented exactly this way on chip + // note from tildearrow: + // - in fact it doesn't. the strings in Scherzo Di Notte totally go out + // tune after a bit (and this doesn't happen in Nuked-OPM). uint32_t rate = lfo_rate(); - m_lfo_counter += (0x10 | bitfield(rate, 0, 4)) << bitfield(rate, 4, 4); + if (rate != 0) { + m_lfo_counter += (0x10 | bitfield(rate, 0, 4)) << bitfield(rate, 4, 4); + } // bit 1 of the test register is officially undocumented but has been // discovered to hold the LFO in reset while active From 96d45dafb2fa9c2ae31cca7126d9201d5fde8f18 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 7 Apr 2022 01:04:35 -0500 Subject: [PATCH 546/637] OPZ: same --- src/engine/platform/sound/ymfm/ymfm_opz.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/engine/platform/sound/ymfm/ymfm_opz.cpp b/src/engine/platform/sound/ymfm/ymfm_opz.cpp index 7ca69dda4..62a3d4d9c 100644 --- a/src/engine/platform/sound/ymfm/ymfm_opz.cpp +++ b/src/engine/platform/sound/ymfm/ymfm_opz.cpp @@ -338,8 +338,12 @@ int32_t opz_registers::clock_noise_and_lfo() // manual, though it might not be implemented exactly this way on chip uint32_t rate0 = lfo_rate(); uint32_t rate1 = lfo2_rate(); - m_lfo_counter[0] += (0x10 | bitfield(rate0, 0, 4)) << bitfield(rate0, 4, 4); - m_lfo_counter[1] += (0x10 | bitfield(rate1, 0, 4)) << bitfield(rate1, 4, 4); + if (rate0 != 0) { + m_lfo_counter[0] += (0x10 | bitfield(rate0, 0, 4)) << bitfield(rate0, 4, 4); + } + if (rate1 != 0) { + m_lfo_counter[1] += (0x10 | bitfield(rate1, 0, 4)) << bitfield(rate1, 4, 4); + } uint32_t lfo0 = bitfield(m_lfo_counter[0], 22, 8); uint32_t lfo1 = bitfield(m_lfo_counter[1], 22, 8); From ad09254cf41f0acc65bb193e66b78fdff8680616 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 7 Apr 2022 01:14:34 -0500 Subject: [PATCH 547/637] dev78 - new compat flag that fixes ExtCh --- papers/format.md | 4 +++- src/engine/engine.h | 4 ++-- src/engine/fileOps.cpp | 13 +++++++++++-- src/engine/platform/genesisext.cpp | 7 ++++++- src/engine/platform/ym2610bext.cpp | 7 ++++++- src/engine/platform/ym2610ext.cpp | 7 ++++++- src/engine/song.h | 4 +++- src/gui/compatFlags.cpp | 4 ++++ 8 files changed, 41 insertions(+), 9 deletions(-) diff --git a/papers/format.md b/papers/format.md index 2876ffe3a..005345755 100644 --- a/papers/format.md +++ b/papers/format.md @@ -29,6 +29,7 @@ furthermore, an `or reserved` indicates this field is always present, but is res the format versions are: +- 78: Furnace dev78 - 77: Furnace dev77 - 76: Furnace dev76 - 75: Furnace dev75/April Fools' 0.6pre0 @@ -248,7 +249,8 @@ size | description 1 | ignore jump at end (>=71) or reserved 1 | buggy portamento after slide (>=72) or reserved 1 | new ins affects envelope (Game Boy) (>=72) or reserved - 26 | reserved + 1 | ExtCh channel state is shared (>=78) or reserved + 25 | reserved ``` # instrument diff --git a/src/engine/engine.h b/src/engine/engine.h index 8fc8427d1..e5240ab9e 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -42,8 +42,8 @@ #define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock(); #define BUSY_END isBusy.unlock(); softLocked=false; -#define DIV_VERSION "dev77" -#define DIV_ENGINE_VERSION 77 +#define DIV_VERSION "dev78" +#define DIV_ENGINE_VERSION 78 // for imports #define DIV_VERSION_MOD 0xff01 diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 1bf73bcce..8ed6551b9 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -890,6 +890,9 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { ds.buggyPortaAfterSlide=true; ds.gbInsAffectsEnvelope=false; } + if (ds.version<78) { + ds.sharedExtStat=false; + } ds.isDMF=false; reader.readS(); // reserved @@ -1222,7 +1225,12 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { reader.readC(); reader.readC(); } - for (int i=0; i<26; i++) { + if (ds.version>=78) { + ds.sharedExtStat=reader.readC(); + } else { + reader.readC(); + } + for (int i=0; i<25; i++) { reader.readC(); } } @@ -2125,7 +2133,8 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { w->writeC(song.ignoreJumpAtEnd); w->writeC(song.buggyPortaAfterSlide); w->writeC(song.gbInsAffectsEnvelope); - for (int i=0; i<26; i++) { + w->writeC(song.sharedExtStat); + for (int i=0; i<25; i++) { w->writeC(0); } diff --git a/src/engine/platform/genesisext.cpp b/src/engine/platform/genesisext.cpp index f77dbdf04..60f877b7e 100644 --- a/src/engine/platform/genesisext.cpp +++ b/src/engine/platform/genesisext.cpp @@ -112,7 +112,12 @@ int DivPlatformGenesisExt::dispatch(DivCommand c) { } else { opChan[ch].pan=((c.value&15)>0)|(((c.value>>4)>0)<<1); } - // TODO: ??? + if (parent->song.sharedExtStat) { + for (int i=0; i<4; i++) { + if (ch==i) continue; + opChan[i].pan=opChan[ch].pan; + } + } rWrite(chanOffs[2]+0xb4,(opChan[ch].pan<<6)|(chan[2].state.fms&7)|((chan[2].state.ams&3)<<4)); break; } diff --git a/src/engine/platform/ym2610bext.cpp b/src/engine/platform/ym2610bext.cpp index adcd1e9e6..03e9f8699 100644 --- a/src/engine/platform/ym2610bext.cpp +++ b/src/engine/platform/ym2610bext.cpp @@ -105,7 +105,12 @@ int DivPlatformYM2610BExt::dispatch(DivCommand c) { opChan[ch].pan=((c.value&15)>0)|(((c.value>>4)>0)<<1); } DivInstrument* ins=parent->getIns(opChan[ch].ins); - // TODO: ??? + if (parent->song.sharedExtStat) { + for (int i=0; i<4; i++) { + if (ch==i) continue; + opChan[i].pan=opChan[ch].pan; + } + } rWrite(chanOffs[2]+0xb4,(opChan[ch].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4)); break; } diff --git a/src/engine/platform/ym2610ext.cpp b/src/engine/platform/ym2610ext.cpp index ed8aa14e4..40294733d 100644 --- a/src/engine/platform/ym2610ext.cpp +++ b/src/engine/platform/ym2610ext.cpp @@ -105,7 +105,12 @@ int DivPlatformYM2610Ext::dispatch(DivCommand c) { opChan[ch].pan=((c.value&15)>0)|(((c.value>>4)>0)<<1); } DivInstrument* ins=parent->getIns(opChan[ch].ins); - // TODO: ??? + if (parent->song.sharedExtStat) { + for (int i=0; i<4; i++) { + if (ch==i) continue; + opChan[i].pan=opChan[ch].pan; + } + } rWrite(chanOffs[1]+0xb4,(opChan[ch].pan<<6)|(ins->fm.fms&7)|((ins->fm.ams&3)<<4)); break; } diff --git a/src/engine/song.h b/src/engine/song.h index 1b8c85c21..7dca56b47 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -308,6 +308,7 @@ struct DivSong { bool ignoreJumpAtEnd; bool buggyPortaAfterSlide; bool gbInsAffectsEnvelope; + bool sharedExtStat; DivOrders orders; std::vector ins; @@ -386,7 +387,8 @@ struct DivSong { rowResetsArpPos(false), ignoreJumpAtEnd(false), buggyPortaAfterSlide(false), - gbInsAffectsEnvelope(true) { + gbInsAffectsEnvelope(true), + sharedExtStat(true) { for (int i=0; i<32; i++) { system[i]=DIV_SYSTEM_NULL; systemVol[i]=64; diff --git a/src/gui/compatFlags.cpp b/src/gui/compatFlags.cpp index 91aa34c7e..15a900b2d 100644 --- a/src/gui/compatFlags.cpp +++ b/src/gui/compatFlags.cpp @@ -154,6 +154,10 @@ void FurnaceGUI::drawCompatFlags() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("behavior changed in 0.6"); } + ImGui::Checkbox("ExtCh channel status is shared among operators",&e->song.sharedExtStat); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("behavior changed in 0.6"); + } } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_COMPAT_FLAGS; ImGui::End(); From dd6229a6b90f8cc2c13c79270c23854abe31c341 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 7 Apr 2022 01:44:27 -0500 Subject: [PATCH 548/637] OPZ: more work --- src/engine/platform/sound/ymfm/ymfm_opz.h | 4 ++-- src/engine/platform/tx81z.cpp | 1 + src/engine/playback.cpp | 8 +++++--- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/engine/platform/sound/ymfm/ymfm_opz.h b/src/engine/platform/sound/ymfm/ymfm_opz.h index 997ba32f9..4bc4663a0 100644 --- a/src/engine/platform/sound/ymfm/ymfm_opz.h +++ b/src/engine/platform/sound/ymfm/ymfm_opz.h @@ -220,8 +220,8 @@ public: // per-channel registers uint32_t ch_volume(uint32_t choffs) const { return byte(0x00, 0, 8, choffs); } - uint32_t ch_output_any(uint32_t choffs) const { return byte(0x20, 7, 1, choffs) | byte(0x30, 0, 1, choffs); } - uint32_t ch_output_0(uint32_t choffs) const { return byte(0x30, 0, 1, choffs); } + uint32_t ch_output_any(uint32_t choffs) const { return 1; } + uint32_t ch_output_0(uint32_t choffs) const { return byte(0x30, 0, 1, choffs) | (!byte(0x20, 7, 1, choffs)); } uint32_t ch_output_1(uint32_t choffs) const { return byte(0x20, 7, 1, choffs) | byte(0x30, 0, 1, choffs); } uint32_t ch_output_2(uint32_t choffs) const { return 0; } uint32_t ch_output_3(uint32_t choffs) const { return 0; } diff --git a/src/engine/platform/tx81z.cpp b/src/engine/platform/tx81z.cpp index f81fda5fe..e9e817cd7 100644 --- a/src/engine/platform/tx81z.cpp +++ b/src/engine/platform/tx81z.cpp @@ -490,6 +490,7 @@ int DivPlatformTX81Z::dispatch(DivCommand c) { case DIV_CMD_PANNING: { chan[c.chan].chVolL=((c.value>>4)>0); chan[c.chan].chVolR=((c.value&15)>0); + chan[c.chan].freqChanged=true; /* if (isMuted[c.chan]) { rWrite(chanOffs[c.chan]+ADDR_LR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3)); diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 3b382de80..8e1f1d664 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -479,6 +479,7 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe } #define IS_YM2610 (sysOfChan[ch]==DIV_SYSTEM_YM2610 || sysOfChan[ch]==DIV_SYSTEM_YM2610_EXT || sysOfChan[ch]==DIV_SYSTEM_YM2610_FULL || sysOfChan[ch]==DIV_SYSTEM_YM2610_FULL_EXT || sysOfChan[ch]==DIV_SYSTEM_YM2610B || sysOfChan[ch]==DIV_SYSTEM_YM2610B_EXT) +#define IS_OPM_LIKE (sysOfChan[ch]==DIV_SYSTEM_YM2151 || sysOfChan[ch]==DIV_SYSTEM_OPZ) bool DivEngine::perSystemPostEffect(int ch, unsigned char effect, unsigned char effectVal) { switch (sysOfChan[ch]) { @@ -491,9 +492,10 @@ bool DivEngine::perSystemPostEffect(int ch, unsigned char effect, unsigned char case DIV_SYSTEM_YM2610_FULL_EXT: case DIV_SYSTEM_YM2610B: case DIV_SYSTEM_YM2610B_EXT: + case DIV_SYSTEM_OPZ: switch (effect) { case 0x10: // LFO or noise mode - if (sysOfChan[ch]==DIV_SYSTEM_YM2151) { + if (IS_OPM_LIKE) { dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_FREQ,ch,effectVal)); } else { dispatchCmd(DivCommand(DIV_CMD_FM_LFO,ch,effectVal)); @@ -520,12 +522,12 @@ bool DivEngine::perSystemPostEffect(int ch, unsigned char effect, unsigned char } break; case 0x17: // arcade LFO - if (sysOfChan[ch]==DIV_SYSTEM_YM2151) { + if (IS_OPM_LIKE) { dispatchCmd(DivCommand(DIV_CMD_FM_LFO,ch,effectVal)); } break; case 0x18: // EXT or LFO waveform - if (sysOfChan[ch]==DIV_SYSTEM_YM2151) { + if (IS_OPM_LIKE) { dispatchCmd(DivCommand(DIV_CMD_FM_LFO_WAVE,ch,effectVal)); } else { dispatchCmd(DivCommand(DIV_CMD_FM_EXTCH,ch,effectVal)); From dab164f09eac087b5727f321d98e5f64fe499fa2 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 7 Apr 2022 02:02:52 -0500 Subject: [PATCH 549/637] GUI: add "effect deletion alters value" setting --- src/gui/editing.cpp | 4 ++++ src/gui/gui.h | 2 ++ src/gui/settings.cpp | 8 ++++++++ 3 files changed, 14 insertions(+) diff --git a/src/gui/editing.cpp b/src/gui/editing.cpp index 58069528a..9efd436fa 100644 --- a/src/gui/editing.cpp +++ b/src/gui/editing.cpp @@ -211,6 +211,10 @@ void FurnaceGUI::doDelete() { if (selStart.y==selEnd.y) pat->data[j][2]=-1; } pat->data[j][iFine+1]=(iFine<1)?0:-1; + + if (selStart.y==selEnd.y && iFine>2 && iFine&1 && settings.effectDeletionAltersValue) { + pat->data[j][iFine+2]=-1; + } } } iFine=0; diff --git a/src/gui/gui.h b/src/gui/gui.h index 0bc136ca6..9d6f4480b 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -754,6 +754,7 @@ class FurnaceGUI { int titleBarInfo; int titleBarSys; int frameBorders; + int effectDeletionAltersValue; unsigned int maxUndoSteps; String mainFontPath; String patFontPath; @@ -813,6 +814,7 @@ class FurnaceGUI { titleBarInfo(1), titleBarSys(1), frameBorders(0), + effectDeletionAltersValue(1), maxUndoSteps(100), mainFontPath(""), patFontPath(""), diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 5089cc432..7c06aaf79 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -240,6 +240,11 @@ void FurnaceGUI::drawSettings() { settings.stepOnDelete=stepOnDeleteB; } + bool effectDeletionAltersValueB=settings.effectDeletionAltersValue; + if (ImGui::Checkbox("Delete effect value when deleting effect",&effectDeletionAltersValueB)) { + settings.effectDeletionAltersValue=effectDeletionAltersValueB; + } + bool stepOnInsertB=settings.stepOnInsert; if (ImGui::Checkbox("Move cursor by edit step on insert (push)",&stepOnInsertB)) { settings.stepOnInsert=stepOnInsertB; @@ -1445,6 +1450,7 @@ void FurnaceGUI::syncSettings() { settings.titleBarInfo=e->getConfInt("titleBarInfo",1); settings.titleBarSys=e->getConfInt("titleBarSys",1); settings.frameBorders=e->getConfInt("frameBorders",0); + settings.effectDeletionAltersValue=e->getConfInt("effectDeletionAltersValue",1); clampSetting(settings.mainFontSize,2,96); clampSetting(settings.patFontSize,2,96); @@ -1496,6 +1502,7 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.titleBarInfo,0,3); clampSetting(settings.titleBarSys,0,1); clampSetting(settings.frameBorders,0,1); + clampSetting(settings.effectDeletionAltersValue,0,1); // keybinds for (int i=0; isetConf("titleBarInfo",settings.titleBarInfo); e->setConf("titleBarSys",settings.titleBarSys); e->setConf("frameBorders",settings.frameBorders); + e->setConf("effectDeletionAltersValue",settings.effectDeletionAltersValue); // colors for (int i=0; i Date: Thu, 7 Apr 2022 02:24:54 -0500 Subject: [PATCH 550/637] GUI: add ability to colorize rows per highlight --- src/gui/gui.h | 6 ++++++ src/gui/guiConst.cpp | 6 ++++++ src/gui/pattern.cpp | 26 +++++++++++++++++++------- src/gui/settings.cpp | 6 ++++++ 4 files changed, 37 insertions(+), 7 deletions(-) diff --git a/src/gui/gui.h b/src/gui/gui.h index 9d6f4480b..445369f0f 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -132,8 +132,14 @@ enum FurnaceGUIColors { GUI_COLOR_PATTERN_HI_1, GUI_COLOR_PATTERN_HI_2, GUI_COLOR_PATTERN_ROW_INDEX, + GUI_COLOR_PATTERN_ROW_INDEX_HI1, + GUI_COLOR_PATTERN_ROW_INDEX_HI2, GUI_COLOR_PATTERN_ACTIVE, GUI_COLOR_PATTERN_INACTIVE, + GUI_COLOR_PATTERN_ACTIVE_HI1, + GUI_COLOR_PATTERN_INACTIVE_HI1, + GUI_COLOR_PATTERN_ACTIVE_HI2, + GUI_COLOR_PATTERN_INACTIVE_HI2, GUI_COLOR_PATTERN_INS, GUI_COLOR_PATTERN_INS_WARN, GUI_COLOR_PATTERN_INS_ERROR, diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index a00ee83c8..085829336 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -438,8 +438,14 @@ const FurnaceGUIColorDef guiColors[GUI_COLOR_MAX]={ D(GUI_COLOR_PATTERN_HI_1,"",ImVec4(0.6f,0.6f,0.6f,0.2f)), D(GUI_COLOR_PATTERN_HI_2,"",ImVec4(0.5f,0.8f,1.0f,0.2f)), D(GUI_COLOR_PATTERN_ROW_INDEX,"",ImVec4(0.5f,0.8f,1.0f,1.0f)), + D(GUI_COLOR_PATTERN_ROW_INDEX_HI1,"",ImVec4(0.5f,0.8f,1.0f,1.0f)), + D(GUI_COLOR_PATTERN_ROW_INDEX_HI2,"",ImVec4(0.5f,0.8f,1.0f,1.0f)), D(GUI_COLOR_PATTERN_ACTIVE,"",ImVec4(1.0f,1.0f,1.0f,1.0f)), D(GUI_COLOR_PATTERN_INACTIVE,"",ImVec4(0.5f,0.5f,0.5f,1.0f)), + D(GUI_COLOR_PATTERN_ACTIVE_HI1,"",ImVec4(1.0f,1.0f,1.0f,1.0f)), + D(GUI_COLOR_PATTERN_INACTIVE_HI1,"",ImVec4(0.5f,0.5f,0.5f,1.0f)), + D(GUI_COLOR_PATTERN_ACTIVE_HI2,"",ImVec4(1.0f,1.0f,1.0f,1.0f)), + D(GUI_COLOR_PATTERN_INACTIVE_HI2,"",ImVec4(0.5f,0.5f,0.5f,1.0f)), D(GUI_COLOR_PATTERN_INS,"",ImVec4(0.4f,0.7f,1.0f,1.0f)), D(GUI_COLOR_PATTERN_INS_WARN,"",ImVec4(1.0f,1.0f,0.1f,1.0f)), D(GUI_COLOR_PATTERN_INS_ERROR,"",ImVec4(1.0f,0.1f,0.1f,1.0f)), diff --git a/src/gui/pattern.cpp b/src/gui/pattern.cpp index ef9af465d..d4bf904b4 100644 --- a/src/gui/pattern.cpp +++ b/src/gui/pattern.cpp @@ -105,6 +105,18 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int return; } bool isPushing=false; + ImVec4 activeColor=uiColors[GUI_COLOR_PATTERN_ACTIVE]; + ImVec4 inactiveColor=uiColors[GUI_COLOR_PATTERN_INACTIVE]; + ImVec4 rowIndexColor=uiColors[GUI_COLOR_PATTERN_ROW_INDEX]; + if (e->song.hilightB>0 && !(i%e->song.hilightB)) { + activeColor=uiColors[GUI_COLOR_PATTERN_ACTIVE_HI2]; + inactiveColor=uiColors[GUI_COLOR_PATTERN_INACTIVE_HI2]; + rowIndexColor=uiColors[GUI_COLOR_PATTERN_ROW_INDEX_HI2]; + } else if (e->song.hilightA>0 && !(i%e->song.hilightA)) { + activeColor=uiColors[GUI_COLOR_PATTERN_ACTIVE_HI1]; + inactiveColor=uiColors[GUI_COLOR_PATTERN_INACTIVE_HI1]; + rowIndexColor=uiColors[GUI_COLOR_PATTERN_ROW_INDEX_HI1]; + } // check overflow highlight if (settings.overflowHighlight) { if (edit && cursor.y==i) { @@ -132,9 +144,9 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int } // row number if (settings.patRowsBase==1) { - ImGui::TextColored(uiColors[GUI_COLOR_PATTERN_ROW_INDEX]," %.2X ",i); + ImGui::TextColored(rowIndexColor," %.2X ",i); } else { - ImGui::TextColored(uiColors[GUI_COLOR_PATTERN_ROW_INDEX],"%3d ",i); + ImGui::TextColored(rowIndexColor,"%3d ",i); } // for each column for (int j=0; jdata[i][0],pat->data[i][1]),i,j); if (pat->data[i][0]==0 && pat->data[i][1]==0) { - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_INACTIVE]); + ImGui::PushStyleColor(ImGuiCol_Text,inactiveColor); } else { - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_ACTIVE]); + ImGui::PushStyleColor(ImGuiCol_Text,activeColor); } if (cursorNote) { ImGui::PushStyleColor(ImGuiCol_Header,uiColors[GUI_COLOR_PATTERN_CURSOR]); @@ -191,7 +203,7 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int if (!e->song.chanCollapse[j]) { // instrument if (pat->data[i][2]==-1) { - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_INACTIVE]); + ImGui::PushStyleColor(ImGuiCol_Text,inactiveColor); sprintf(id,"..##PI_%d_%d",i,j); } else { if (pat->data[i][2]<0 || pat->data[i][2]>=e->song.insLen) { @@ -230,7 +242,7 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int // volume if (pat->data[i][3]==-1) { sprintf(id,"..##PV_%d_%d",i,j); - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_INACTIVE]); + ImGui::PushStyleColor(ImGuiCol_Text,inactiveColor); } else { int volColor=(pat->data[i][3]*127)/chanVolMax; if (volColor>127) volColor=127; @@ -270,7 +282,7 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int // effect if (pat->data[i][index]==-1) { sprintf(id,"..##PE%d_%d_%d",k,i,j); - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_INACTIVE]); + ImGui::PushStyleColor(ImGuiCol_Text,inactiveColor); } else { sprintf(id,"%.2X##PE%d_%d_%d",pat->data[i][index],k,i,j); if (pat->data[i][index]<0x10) { diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 7c06aaf79..30fe2141d 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -1008,8 +1008,14 @@ void FurnaceGUI::drawSettings() { UI_COLOR_CONFIG(GUI_COLOR_PATTERN_HI_1,"Highlight 1"); UI_COLOR_CONFIG(GUI_COLOR_PATTERN_HI_2,"Highlight 2"); UI_COLOR_CONFIG(GUI_COLOR_PATTERN_ROW_INDEX,"Row number"); + UI_COLOR_CONFIG(GUI_COLOR_PATTERN_ROW_INDEX_HI1,"Row number (highlight 1)"); + UI_COLOR_CONFIG(GUI_COLOR_PATTERN_ROW_INDEX_HI2,"Row number (highlight 2)"); UI_COLOR_CONFIG(GUI_COLOR_PATTERN_ACTIVE,"Note"); + UI_COLOR_CONFIG(GUI_COLOR_PATTERN_ACTIVE_HI1,"Note (highlight 1)"); + UI_COLOR_CONFIG(GUI_COLOR_PATTERN_ACTIVE_HI2,"Note (highlight 2)"); UI_COLOR_CONFIG(GUI_COLOR_PATTERN_INACTIVE,"Blank"); + UI_COLOR_CONFIG(GUI_COLOR_PATTERN_INACTIVE_HI1,"Blank (highlight 1)"); + UI_COLOR_CONFIG(GUI_COLOR_PATTERN_INACTIVE_HI2,"Blank (highlight 2)"); UI_COLOR_CONFIG(GUI_COLOR_PATTERN_INS,"Instrument"); UI_COLOR_CONFIG(GUI_COLOR_PATTERN_INS_WARN,"Instrument (invalid type)"); UI_COLOR_CONFIG(GUI_COLOR_PATTERN_INS_ERROR,"Instrument (out of range)"); From 3f5d0a1e46380bdc970e29b05cef029f85f8b4ca Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 7 Apr 2022 02:37:19 -0500 Subject: [PATCH 551/637] GUI: fix paste mode binds not working --- src/gui/doAction.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/gui/doAction.cpp b/src/gui/doAction.cpp index 3866624ac..f9ae0428d 100644 --- a/src/gui/doAction.cpp +++ b/src/gui/doAction.cpp @@ -314,6 +314,18 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_PAT_PASTE: doPaste(); break; + case GUI_ACTION_PAT_PASTE_MIX: + doPaste(GUI_PASTE_MODE_MIX_FG); + break; + case GUI_ACTION_PAT_PASTE_MIX_BG: + doPaste(GUI_PASTE_MODE_MIX_BG); + break; + case GUI_ACTION_PAT_PASTE_FLOOD: + doPaste(GUI_PASTE_MODE_FLOOD); + break; + case GUI_ACTION_PAT_PASTE_OVERFLOW: + doPaste(GUI_PASTE_MODE_OVERFLOW); + break; case GUI_ACTION_PAT_CURSOR_UP: moveCursor(0,-MAX(1,settings.scrollStep?editStep:1),false); break; From 05dfec9f3dd7bd1701945eb25c122792f71dd021 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 7 Apr 2022 13:48:22 -0500 Subject: [PATCH 552/637] GUI: fix VRC6 saw waveform macro being displayed --- src/gui/insEdit.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index e452f25ab..abc4af172 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -2403,7 +2403,7 @@ void FurnaceGUI::drawInsEdit() { if (ins->type==DIV_INS_C64 || ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_SAA1099) { bitMode=true; } - if (ins->type==DIV_INS_STD || ins->type==DIV_INS_VRC6) waveMax=0; + if (ins->type==DIV_INS_STD || ins->type==DIV_INS_VRC6 || ins->type==DIV_INS_VRC6_SAW) waveMax=0; if (ins->type==DIV_INS_TIA || ins->type==DIV_INS_VIC || ins->type==DIV_INS_OPLL) waveMax=15; if (ins->type==DIV_INS_C64) waveMax=4; if (ins->type==DIV_INS_SAA1099) waveMax=2; From 4dfe9f97fbda6552f4b52cb93ad70d8b2a4f6848 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 7 Apr 2022 15:46:48 -0500 Subject: [PATCH 553/637] prepare for wavetable synth --- CMakeLists.txt | 1 + src/engine/instrument.h | 42 ++++++++++++++++++++++++++++++++++++ src/engine/waveSynth.cpp | 5 +++++ src/engine/waveSynth.h | 46 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 94 insertions(+) create mode 100644 src/engine/waveSynth.cpp create mode 100644 src/engine/waveSynth.h diff --git a/CMakeLists.txt b/CMakeLists.txt index a01272803..c5fc06e70 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -302,6 +302,7 @@ src/engine/sample.cpp src/engine/song.cpp src/engine/sysDef.cpp src/engine/wavetable.cpp +src/engine/waveSynth.cpp src/engine/vgmOps.cpp src/engine/platform/abstract.cpp src/engine/platform/genesis.cpp diff --git a/src/engine/instrument.h b/src/engine/instrument.h index ab00c9aab..1c8525436 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -477,6 +477,47 @@ struct DivInstrumentFDS { } }; +enum DivWaveSynthEffects { + DIV_WS_NONE=0, + // one waveform effects + DIV_WS_INVERT, + DIV_WS_ADD, + DIV_WS_SUBTRACT, + DIV_WS_AVERAGE, + DIV_WS_PHASE, + // two waveform effects + DIV_WS_NONE_DUAL=128, + DIV_WS_WIPE, + DIV_WS_FADE, + DIV_WS_PING_PONG, + DIV_WS_OVERLAY, + DIV_WS_NEGATIVE_OVERLAY, + DIV_WS_PHASE_DUAL, +}; + +struct DivInstrumentWaveSynth { + int wave1, wave2; + unsigned char rateDivider, width, height; + DivWaveSynthEffects effect; + bool oneShot, enabled, global; + unsigned char speed, param1, param2, param3, param4; + DivInstrumentWaveSynth(): + wave1(0), + wave2(0), + rateDivider(1), + width(32), + height(32), + effect(DIV_WS_NONE), + oneShot(false), + enabled(false), + global(false), + speed(1), + param1(0), + param2(0), + param3(0), + param4(0) {} +}; + struct DivInstrument { String name; bool mode; @@ -488,6 +529,7 @@ struct DivInstrument { DivInstrumentAmiga amiga; DivInstrumentN163 n163; DivInstrumentFDS fds; + DivInstrumentWaveSynth ws; /** * save the instrument to a SafeWriter. diff --git a/src/engine/waveSynth.cpp b/src/engine/waveSynth.cpp new file mode 100644 index 000000000..ca3f342c7 --- /dev/null +++ b/src/engine/waveSynth.cpp @@ -0,0 +1,5 @@ +#include "waveSynth.h" + +bool DivWaveSynth::tick() { + return false; +} \ No newline at end of file diff --git a/src/engine/waveSynth.h b/src/engine/waveSynth.h new file mode 100644 index 000000000..5e5fd6540 --- /dev/null +++ b/src/engine/waveSynth.h @@ -0,0 +1,46 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef _WAVESYNTH_H +#define _WAVESYNTH_H + +#include "instrument.h" +#include "wavetable.h" + +class DivWaveSynth { + DivInstrument* ins; + int pos, stage, divCounter; + int output[256]; + public: + /** + * tick this DivWaveSynth. + * @return whether the wave has changed. + */ + bool tick(); + void init(DivInstrument* ins); + DivWaveSynth(): + ins(NULL), + pos(0), + stage(0), + divCounter(0) { + memset(output,0,sizeof(int)*256); + } +}; + +#endif From 500ce8086df35b330fbec53c0326096145981c05 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 7 Apr 2022 18:27:17 -0500 Subject: [PATCH 554/637] GUI: initial wave synth UI --- src/engine/instrument.h | 7 ++- src/engine/waveSynth.cpp | 49 +++++++++++++++- src/engine/waveSynth.h | 22 +++++-- src/gui/insEdit.cpp | 120 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 191 insertions(+), 7 deletions(-) diff --git a/src/engine/instrument.h b/src/engine/instrument.h index 1c8525436..9327c64c6 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -485,6 +485,9 @@ enum DivWaveSynthEffects { DIV_WS_SUBTRACT, DIV_WS_AVERAGE, DIV_WS_PHASE, + + DIV_WS_SINGLE_MAX, + // two waveform effects DIV_WS_NONE_DUAL=128, DIV_WS_WIPE, @@ -493,12 +496,14 @@ enum DivWaveSynthEffects { DIV_WS_OVERLAY, DIV_WS_NEGATIVE_OVERLAY, DIV_WS_PHASE_DUAL, + + DIV_WS_DUAL_MAX }; struct DivInstrumentWaveSynth { int wave1, wave2; unsigned char rateDivider, width, height; - DivWaveSynthEffects effect; + unsigned char effect; bool oneShot, enabled, global; unsigned char speed, param1, param2, param3, param4; DivInstrumentWaveSynth(): diff --git a/src/engine/waveSynth.cpp b/src/engine/waveSynth.cpp index ca3f342c7..b82539aaf 100644 --- a/src/engine/waveSynth.cpp +++ b/src/engine/waveSynth.cpp @@ -1,5 +1,52 @@ #include "waveSynth.h" +#include "engine.h" bool DivWaveSynth::tick() { - return false; + bool updated=first; + first=false; + + + return updated; +} + +void DivWaveSynth::setEngine(DivEngine* engine) { + e=engine; +} + +void DivWaveSynth::init(DivInstrument* which, int w, int h, bool insChanged) { + if (e==NULL) return; + if (which==NULL) { + state=DivInstrumentWaveSynth(); + } + state=which->ws; + width=w; + height=h; + pos=0; + stage=0; + divCounter=0; + first=true; + + DivWavetable* w1=e->getWave(state.wave1); + DivWavetable* w2=e->getWave(state.wave2); + for (int i=0; imax<1 || w1->len<1) { + wave1[i]=0; + } else { + int data=w1->data[i*w1->len/width]*height/w1->max; + if (data<0) data=0; + if (data>31) data=31; + wave1[i]=data; + } + } + + for (int i=0; imax<1 || w2->len<1) { + wave2[i]=0; + } else { + int data=w2->data[i*w2->len/width]*height/w2->max; + if (data<0) data=0; + if (data>31) data=31; + wave2[i]=data; + } + } } \ No newline at end of file diff --git a/src/engine/waveSynth.h b/src/engine/waveSynth.h index 5e5fd6540..92a5c9a26 100644 --- a/src/engine/waveSynth.h +++ b/src/engine/waveSynth.h @@ -23,9 +23,15 @@ #include "instrument.h" #include "wavetable.h" +class DivEngine; + class DivWaveSynth { - DivInstrument* ins; - int pos, stage, divCounter; + DivEngine* e; + DivInstrumentWaveSynth state; + int pos, stage, divCounter, width, height; + bool first; + unsigned char wave1[256]; + unsigned char wave2[256]; int output[256]; public: /** @@ -33,12 +39,18 @@ class DivWaveSynth { * @return whether the wave has changed. */ bool tick(); - void init(DivInstrument* ins); + void init(DivInstrument* which, int width, int height, bool insChanged=false); + void setEngine(DivEngine* engine); DivWaveSynth(): - ins(NULL), + e(NULL), pos(0), stage(0), - divCounter(0) { + divCounter(0), + width(32), + height(31), + first(false) { + memset(wave1,0,sizeof(int)*256); + memset(wave2,0,sizeof(int)*256); memset(output,0,sizeof(int)*256); } }; diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index abc4af172..71426dfe9 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -157,6 +157,25 @@ const int orderedOps[4]={ 0, 2, 1, 3 }; +const char* singleWSEffects[6]={ + "None", + "Invert", + "Add", + "Subtract", + "Average", + "Phase", +}; + +const char* dualWSEffects[7]={ + "None (dual)", + "Wipe", + "Fade", + "Wipe (ping-pong)", + "Overlay", + "Negative Overlay", + "Phase (dual)", +}; + String macroHoverNote(int id, float val) { if (val<-60 || val>=120) return "???"; return fmt::sprintf("%d: %s",id,noteNames[(int)val+60]); @@ -2301,6 +2320,107 @@ void FurnaceGUI::drawInsEdit() { } ImGui::EndTabItem(); } + if (ins->type==DIV_INS_GB || + ins->type==DIV_INS_AMIGA || + ins->type==DIV_INS_X1_010 || + ins->type==DIV_INS_N163 || + ins->type==DIV_INS_FDS || + ins->type==DIV_INS_SWAN || + ins->type==DIV_INS_PCE || + ins->type==DIV_INS_SCC) { + if (ImGui::BeginTabItem("Wavetable")) { + ImGui::Checkbox("Enable synthesizer",&ins->ws.enabled); + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ins->ws.effect&0x80) { + if ((ins->ws.effect&0x7f)>=DIV_WS_DUAL_MAX) { + ins->ws.effect=0; + } + } else { + if ((ins->ws.effect&0x7f)>=DIV_WS_SINGLE_MAX) { + ins->ws.effect=0; + } + } + if (ImGui::BeginCombo("##WSEffect",(ins->ws.effect&0x80)?dualWSEffects[ins->ws.effect&0x7f]:singleWSEffects[ins->ws.effect&0x7f])) { + ImGui::Text("Single-waveform"); + ImGui::Indent(); + for (int i=0; iws.effect=i; + } + } + ImGui::Unindent(); + ImGui::Text("Dual-waveform"); + ImGui::Indent(); + for (int i=129; iws.effect=i; + } + } + ImGui::Unindent(); + ImGui::EndCombo(); + } + if (ImGui::BeginTable("WSPreview",2)) { + DivWavetable* wave1=e->getWave(ins->ws.wave1); + DivWavetable* wave2=e->getWave(ins->ws.wave2); + float wavePreview1[256]; + float wavePreview2[256]; + for (int i=0; ilen; i++) { + if (wave1->data[i]>wave1->max) { + wavePreview1[i]=wave1->max; + } else { + wavePreview1[i]=wave1->data[i]; + } + } + for (int i=0; ilen; i++) { + if (wave2->data[i]>wave2->max) { + wavePreview2[i]=wave2->max; + } else { + wavePreview2[i]=wave2->data[i]; + } + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImVec2 size1=ImVec2(ImGui::GetContentRegionAvail().x,64.0f*dpiScale); + PlotNoLerp("##WaveformP1",wavePreview1,wave1->len+1,0,NULL,0,wave1->max,size1); + ImGui::TableNextColumn(); + ImVec2 size2=ImVec2(ImGui::GetContentRegionAvail().x,64.0f*dpiScale); + PlotNoLerp("##WaveformP2",wavePreview2,wave2->len+1,0,NULL,0,wave2->max,size2); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Wave 1"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputInt("##SelWave1",&ins->ws.wave1,1,4)) { + if (ins->ws.wave1<0) ins->ws.wave1=0; + if (ins->ws.wave1>=(int)e->song.wave.size()) ins->ws.wave1=e->song.wave.size()-1; + } + ImGui::TableNextColumn(); + ImGui::Text("Wave 2"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputInt("##SelWave2",&ins->ws.wave2,1,4)) { + if (ins->ws.wave2<0) ins->ws.wave2=0; + if (ins->ws.wave2>=(int)e->song.wave.size()) ins->ws.wave2=e->song.wave.size()-1; + } + ImGui::EndTable(); + } + + ImGui::InputScalar("Update Rate",ImGuiDataType_U8,&ins->ws.rateDivider,&_ONE,&_SEVEN); + int speed=ins->ws.speed+1; + if (ImGui::InputInt("Speed",&speed,1,16)) { + if (speed<1) speed=1; + if (speed>256) speed=256; + ins->ws.speed=speed-1; + } + + ImGui::InputScalar("Amount",ImGuiDataType_U8,&ins->ws.param1,&_ONE,&_SEVEN); + + ImGui::EndTabItem(); + } + } if (ImGui::BeginTabItem("Macros")) { float asFloat[256]; int asInt[256]; From 5d52ef7f008b7ecae51b99835613e3d6e9a81f09 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 7 Apr 2022 20:13:11 -0500 Subject: [PATCH 555/637] support .dmf with 6-bit wavetables --- src/engine/fileOps.cpp | 26 ++++++++++++++++++++++---- src/engine/song.h | 2 ++ src/gui/gui.cpp | 6 +++--- 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 8ed6551b9..e14cd8b3b 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -69,7 +69,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { } ds.version=(unsigned char)reader.readC(); logI("module version %d (0x%.2x)\n",ds.version,ds.version); - if (ds.version>0x19) { + if (ds.version>0x1a) { logE("this version is not supported by Furnace yet!\n"); lastError="this version is not supported by Furnace yet"; delete[] file; @@ -580,6 +580,12 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { wave->data[j]=reader.readI(); } } + // #FDS4Bit + if (ds.system[0]==DIV_SYSTEM_NES_FDS && ds.version<0x1a) { + for (int j=0; jlen; j++) { + wave->data[j]*=4; + } + } ds.wave.push_back(wave); } } @@ -2222,7 +2228,7 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { SafeWriter* DivEngine::saveDMF(unsigned char version) { // fail if version is not supported - if (version<24 || version>25) { + if (version<24 || version>26) { logE("cannot save in this version!\n"); lastError="invalid version to save in! this is a bug!"; return NULL; @@ -2273,6 +2279,12 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { lastError="NES + VRC7 not supported in 1.0/legacy .dmf!"; return NULL; } + // fail if the system is FDS and version<25 + if (version<25 && song.system[0]==DIV_SYSTEM_NES && song.system[1]==DIV_SYSTEM_FDS) { + logE("FDS not supported in 1.0/legacy .dmf!\n"); + lastError="FDS not supported in 1.0/legacy .dmf!"; + return NULL; + } // fail if the system is Furnace-exclusive if (!isFlat && systemToFileDMF(song.system[0])==0) { logE("cannot save Furnace-exclusive system song!\n"); @@ -2308,7 +2320,7 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { sys=DIV_SYSTEM_NES_VRC7; } else if (song.system[0]==DIV_SYSTEM_NES && song.system[1]==DIV_SYSTEM_FDS) { w->writeC(systemToFileDMF(DIV_SYSTEM_NES_FDS)); - sys=DIV_SYSTEM_NES_VRC7; + sys=DIV_SYSTEM_NES_FDS; } else { w->writeC(systemToFileDMF(song.system[0])); sys=song.system[0]; @@ -2481,7 +2493,13 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { w->writeC(song.wave.size()); for (DivWavetable* i: song.wave) { w->writeI(i->len); - w->write(i->data,4*i->len); + if (sys==DIV_SYSTEM_NES_FDS && version<26) { + for (int j=0; jlen; j++) { + w->writeI(i->data[j]>>2); + } + } else { + w->write(i->data,4*i->len); + } } for (int i=0; iopenSave( "Save File", {"Furnace song", "*.fur", - "DefleMask 1.1 module", "*.dmf"}, - "Furnace song{.fur},DefleMask 1.1 module{.dmf}", + "DefleMask 1.1.3 module", "*.dmf"}, + "Furnace song{.fur},DefleMask 1.1.3 module{.dmf}", workingDirSong, dpiScale ); @@ -2714,7 +2714,7 @@ bool FurnaceGUI::loop() { showError(fmt::sprintf("Error while saving file! (%s)",lastError)); } } else { - if (save(copyOfName,25)>0) { + if (save(copyOfName,26)>0) { showError(fmt::sprintf("Error while saving file! (%s)",lastError)); } } From 0c1e2ddcb02799a646868b1a5a3497f420d96135 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Thu, 7 Apr 2022 20:48:34 -0500 Subject: [PATCH 556/637] OPL: fix OPL2 not having waveforms --- src/engine/platform/opl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index b0894fe70..fcba9f80c 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -947,7 +947,7 @@ void DivPlatformOPL::reset() { drumVol[3]=0; drumVol[4]=0; - if (oplType==1) { // disable waveforms + if (oplType==2) { // enable OPL2 waveforms immWrite(0x01,0x20); } From 5bd076d13ece687789f4a5e40b754fde8eb8e848 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 8 Apr 2022 02:11:33 -0500 Subject: [PATCH 557/637] initial bring-up of the wave synth issue #16 --- src/engine/fileOps.cpp | 1 + src/engine/instrument.h | 2 +- src/engine/platform/gb.cpp | 51 +++++++++-------- src/engine/platform/gb.h | 2 + src/engine/platform/pce.cpp | 35 ++++++------ src/engine/platform/pce.h | 2 + src/engine/waveSynth.cpp | 107 ++++++++++++++++++++++++++---------- src/engine/waveSynth.h | 32 ++++++++++- src/gui/insEdit.cpp | 2 + 9 files changed, 162 insertions(+), 72 deletions(-) diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index e14cd8b3b..7988992ba 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -579,6 +579,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { } else { wave->data[j]=reader.readI(); } + wave->data[j]&=wave->max; } // #FDS4Bit if (ds.system[0]==DIV_SYSTEM_NES_FDS && ds.version<0x1a) { diff --git a/src/engine/instrument.h b/src/engine/instrument.h index 9327c64c6..c56319119 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -516,7 +516,7 @@ struct DivInstrumentWaveSynth { oneShot(false), enabled(false), global(false), - speed(1), + speed(0), param1(0), param2(0), param3(0), diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp index d1d580c84..263e8e2ba 100644 --- a/src/engine/platform/gb.cpp +++ b/src/engine/platform/gb.cpp @@ -91,20 +91,11 @@ void DivPlatformGB::acquire(short* bufL, short* bufR, size_t start, size_t len) } void DivPlatformGB::updateWave() { - DivWavetable* wt=parent->getWave(chan[2].wave); rWrite(0x1a,0); for (int i=0; i<16; i++) { - if (wt->max<1 || wt->len<1) { - rWrite(0x30+i,0); - } else { - int nibble1=15-((wt->data[(i*2)*wt->len/32]*15)/wt->max); - int nibble2=15-((wt->data[(1+i*2)*wt->len/32]*15)/wt->max); - if (nibble1<0) nibble1=0; - if (nibble1>15) nibble1=15; - if (nibble2<0) nibble2=0; - if (nibble2>15) nibble2=15; - rWrite(0x30+i,(nibble1<<4)|nibble2); - } + int nibble1=15-ws.output[i<<1]; + int nibble2=15-ws.output[1+(i<<1)]; + rWrite(0x30+i,(nibble1<<4)|nibble2); } } @@ -194,10 +185,16 @@ void DivPlatformGB::tick() { } } } - if (chan[i].std.hadWave) { - if (chan[i].wave!=chan[i].std.wave) { + if (i==2 && chan[i].std.hadWave) { + if (chan[i].wave!=chan[i].std.wave || ws.activeChanged()) { chan[i].wave=chan[i].std.wave; - if (i==2) { + ws.changeWave1(chan[i].wave); + if (!chan[i].keyOff) chan[i].keyOn=true; + } + } + if (i==2) { + if (chan[i].active) { + if (ws.tick()) { updateWave(); if (!chan[i].keyOff) chan[i].keyOn=true; } @@ -222,10 +219,6 @@ void DivPlatformGB::tick() { } if (chan[i].keyOn) { if (i==2) { // wave - if (chan[i].wave<0) { - chan[i].wave=0; - updateWave(); - } rWrite(16+i*5,0x80); rWrite(16+i*5+2,gbVolMap[chan[i].vol]); } else { @@ -261,7 +254,8 @@ void DivPlatformGB::muteChannel(int ch, bool mute) { int DivPlatformGB::dispatch(DivCommand c) { switch (c.cmd) { - case DIV_CMD_NOTE_ON: + case DIV_CMD_NOTE_ON: { + DivInstrument* ins=parent->getIns(chan[c.chan].ins); if (c.value!=DIV_NOTE_NULL) { if (c.chan==3) { // noise chan[c.chan].baseFreq=c.value; @@ -273,8 +267,17 @@ int DivPlatformGB::dispatch(DivCommand c) { } chan[c.chan].active=true; chan[c.chan].keyOn=true; - chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + chan[c.chan].std.init(ins); + if (c.chan==2) { + if (chan[c.chan].wave<0) { + chan[c.chan].wave=0; + ws.changeWave1(chan[c.chan].wave); + } + ws.init(ins,32,15,chan[c.chan].insChanged); + } + chan[c.chan].insChanged=false; break; + } case DIV_CMD_NOTE_OFF: chan[c.chan].active=false; chan[c.chan].keyOff=true; @@ -287,6 +290,7 @@ int DivPlatformGB::dispatch(DivCommand c) { case DIV_CMD_INSTRUMENT: if (chan[c.chan].ins!=c.value || c.value2==1) { chan[c.chan].ins=c.value; + chan[c.chan].insChanged=true; if (c.chan!=2) { DivInstrument* ins=parent->getIns(chan[c.chan].ins); chan[c.chan].vol=ins->gb.envVol; @@ -312,7 +316,7 @@ int DivPlatformGB::dispatch(DivCommand c) { case DIV_CMD_WAVE: if (c.chan!=2) break; chan[c.chan].wave=c.value; - updateWave(); + ws.changeWave1(chan[c.chan].wave); chan[c.chan].keyOn=true; break; case DIV_CMD_NOTE_PORTA: { @@ -415,6 +419,8 @@ void DivPlatformGB::reset() { for (int i=0; i<4; i++) { chan[i]=DivPlatformGB::Channel(); } + ws.setEngine(parent); + ws.init(NULL,32,15,false); if (dumpWrites) { addWrite(0xffffffff,0); } @@ -445,6 +451,7 @@ void DivPlatformGB::notifyInsChange(int ins) { void DivPlatformGB::notifyWaveChange(int wave) { if (chan[2].wave==wave) { + ws.changeWave1(wave); updateWave(); if (!chan[2].keyOff) chan[2].keyOn=true; } diff --git a/src/engine/platform/gb.h b/src/engine/platform/gb.h index 04e36ad3c..5a345e45d 100644 --- a/src/engine/platform/gb.h +++ b/src/engine/platform/gb.h @@ -22,6 +22,7 @@ #include "../dispatch.h" #include "../macroInt.h" +#include "../waveSynth.h" #include "sound/gb/gb.h" class DivPlatformGB: public DivDispatch { @@ -53,6 +54,7 @@ class DivPlatformGB: public DivDispatch { Channel chan[4]; bool isMuted[4]; unsigned char lastPan; + DivWaveSynth ws; GB_gameboy_t* gb; unsigned char regPool[128]; diff --git a/src/engine/platform/pce.cpp b/src/engine/platform/pce.cpp index bf7d0d8db..aa78aa625 100644 --- a/src/engine/platform/pce.cpp +++ b/src/engine/platform/pce.cpp @@ -131,18 +131,10 @@ void DivPlatformPCE::acquire(short* bufL, short* bufR, size_t start, size_t len) } void DivPlatformPCE::updateWave(int ch) { - DivWavetable* wt=parent->getWave(chan[ch].wave); chWrite(ch,0x04,0x5f); chWrite(ch,0x04,0x1f); for (int i=0; i<32; i++) { - if (wt->max<1 || wt->len<1) { - chWrite(ch,0x06,0); - } else { - int data=wt->data[i*wt->len/32]*31/wt->max; - if (data<0) data=0; - if (data>31) data=31; - chWrite(ch,0x06,data); - } + chWrite(ch,0x06,chan[ch].ws.output[i]); } if (chan[ch].active) { chWrite(ch,0x04,0x80|chan[ch].outVol); @@ -198,12 +190,17 @@ void DivPlatformPCE::tick() { } } if (chan[i].std.hadWave && !chan[i].pcm) { - if (chan[i].wave!=chan[i].std.wave) { + if (chan[i].wave!=chan[i].std.wave || chan[i].ws.activeChanged()) { chan[i].wave=chan[i].std.wave; - updateWave(i); + chan[i].ws.changeWave1(chan[i].wave); if (!chan[i].keyOff) chan[i].keyOn=true; } } + if (chan[i].active) { + if (chan[i].ws.tick()) { + updateWave(i); + } + } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { //DivInstrument* ins=parent->getIns(chan[i].ins); chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true); @@ -224,10 +221,6 @@ void DivPlatformPCE::tick() { chWrite(i,0x02,chan[i].freq&0xff); chWrite(i,0x03,chan[i].freq>>8); if (chan[i].keyOn) { - if (chan[i].wave<0) { - chan[i].wave=0; - updateWave(i); - } //rWrite(16+i*5,0x80); //chWrite(i,0x04,0x80|chan[i].vol); } @@ -310,6 +303,12 @@ int DivPlatformPCE::dispatch(DivCommand c) { chan[c.chan].keyOn=true; chWrite(c.chan,0x04,0x80|chan[c.chan].vol); chan[c.chan].std.init(ins); + if (chan[c.chan].wave<0) { + chan[c.chan].wave=0; + chan[c.chan].ws.changeWave1(chan[c.chan].wave); + } + chan[c.chan].ws.init(ins,32,31,chan[c.chan].insChanged); + chan[c.chan].insChanged=false; break; } case DIV_CMD_NOTE_OFF: @@ -327,6 +326,7 @@ int DivPlatformPCE::dispatch(DivCommand c) { case DIV_CMD_INSTRUMENT: if (chan[c.chan].ins!=c.value || c.value2==1) { chan[c.chan].ins=c.value; + chan[c.chan].insChanged=true; } break; case DIV_CMD_VOLUME: @@ -350,7 +350,7 @@ int DivPlatformPCE::dispatch(DivCommand c) { break; case DIV_CMD_WAVE: chan[c.chan].wave=c.value; - updateWave(c.chan); + chan[c.chan].ws.changeWave1(chan[c.chan].wave); chan[c.chan].keyOn=true; break; case DIV_CMD_PCE_LFO_MODE: @@ -462,6 +462,8 @@ void DivPlatformPCE::reset() { memset(regPool,0,128); for (int i=0; i<6; i++) { chan[i]=DivPlatformPCE::Channel(); + chan[i].ws.setEngine(parent); + chan[i].ws.init(NULL,32,31,false); } if (dumpWrites) { addWrite(0xffffffff,0); @@ -499,6 +501,7 @@ bool DivPlatformPCE::keyOffAffectsArp(int ch) { void DivPlatformPCE::notifyWaveChange(int wave) { for (int i=0; i<6; i++) { if (chan[i].wave==wave) { + chan[i].ws.changeWave1(wave); updateWave(i); } } diff --git a/src/engine/platform/pce.h b/src/engine/platform/pce.h index 2a5bd4fba..2e8614ba8 100644 --- a/src/engine/platform/pce.h +++ b/src/engine/platform/pce.h @@ -23,6 +23,7 @@ #include "../dispatch.h" #include #include "../macroInt.h" +#include "../waveSynth.h" #include "sound/pce_psg.h" class DivPlatformPCE: public DivDispatch { @@ -35,6 +36,7 @@ class DivPlatformPCE: public DivDispatch { bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, noise, pcm, furnaceDac; signed char vol, outVol, wave; DivMacroInt std; + DivWaveSynth ws; Channel(): freq(0), baseFreq(0), diff --git a/src/engine/waveSynth.cpp b/src/engine/waveSynth.cpp index b82539aaf..d658be178 100644 --- a/src/engine/waveSynth.cpp +++ b/src/engine/waveSynth.cpp @@ -1,52 +1,99 @@ #include "waveSynth.h" #include "engine.h" +#include "instrument.h" + +bool DivWaveSynth::activeChanged() { + if (activeChangedB) { + activeChangedB=false; + return true; + } + return false; +} bool DivWaveSynth::tick() { bool updated=first; first=false; + if (!state.enabled) return updated; + if (--divCounter<=0) { + // run effect + switch (state.effect) { + case DIV_WS_INVERT: + for (int i=0; i<=state.speed; i++) { + output[pos]=height-output[pos]; + if (++pos>=width) pos=0; + } + updated=true; + break; + } + divCounter=state.rateDivider; + } return updated; } +void DivWaveSynth::changeWave1(int num) { + DivWavetable* w1=e->getWave(num); + for (int i=0; imax<1 || w1->len<1) { + wave1[i]=0; + output[i]=0; + } else { + int data=w1->data[i*w1->len/width]*height/w1->max; + if (data<0) data=0; + if (data>height) data=height; + wave1[i]=data; + output[i]=data; + } + } + first=true; +} + +void DivWaveSynth::changeWave2(int num) { + DivWavetable* w2=e->getWave(num); + for (int i=0; imax<1 || w2->len<1) { + wave2[i]=0; + } else { + int data=w2->data[i*w2->len/width]*height/w2->max; + if (data<0) data=0; + if (data>height) data=height; + wave2[i]=data; + } + } + first=true; +} + void DivWaveSynth::setEngine(DivEngine* engine) { e=engine; } void DivWaveSynth::init(DivInstrument* which, int w, int h, bool insChanged) { - if (e==NULL) return; - if (which==NULL) { - state=DivInstrumentWaveSynth(); - } - state=which->ws; width=w; height=h; - pos=0; - stage=0; - divCounter=0; - first=true; - - DivWavetable* w1=e->getWave(state.wave1); - DivWavetable* w2=e->getWave(state.wave2); - for (int i=0; imax<1 || w1->len<1) { - wave1[i]=0; - } else { - int data=w1->data[i*w1->len/width]*height/w1->max; - if (data<0) data=0; - if (data>31) data=31; - wave1[i]=data; - } + if (width<0) width=0; + if (width>256) width=256; + if (e==NULL) return; + if (which==NULL) { + if (state.enabled) activeChangedB=true; + state=DivInstrumentWaveSynth(); + return; } + if (!which->ws.enabled) { + if (state.enabled) activeChangedB=true; + state=DivInstrumentWaveSynth(); + return; + } else { + if (!state.enabled) activeChangedB=true; + } + state=which->ws; + if (insChanged || !state.global) { + pos=0; + stage=0; + divCounter=1+state.rateDivider; + first=true; - for (int i=0; imax<1 || w2->len<1) { - wave2[i]=0; - } else { - int data=w2->data[i*w2->len/width]*height/w2->max; - if (data<0) data=0; - if (data>31) data=31; - wave2[i]=data; - } + changeWave1(state.wave1); + changeWave2(state.wave2); } } \ No newline at end of file diff --git a/src/engine/waveSynth.h b/src/engine/waveSynth.h index 92a5c9a26..70fb27389 100644 --- a/src/engine/waveSynth.h +++ b/src/engine/waveSynth.h @@ -29,16 +29,41 @@ class DivWaveSynth { DivEngine* e; DivInstrumentWaveSynth state; int pos, stage, divCounter, width, height; - bool first; + bool first, activeChangedB; unsigned char wave1[256]; unsigned char wave2[256]; - int output[256]; public: + /** + * the output. + */ + int output[256]; + /** + * check whether the "active" status has changed. + * @return truth. + */ + bool activeChanged(); /** * tick this DivWaveSynth. * @return whether the wave has changed. */ bool tick(); + /** + * change the first wave. + * @param num wavetable number. + */ + void changeWave1(int num); + /** + * change the second wave. + * @param num wavetable number. + */ + void changeWave2(int num); + /** + * initialize this DivWaveSynth. + * @param which the instrument. + * @param width the system's wave width. + * @param height the system's wave height. + * @param insChanged whether the instrument has changed. + */ void init(DivInstrument* which, int width, int height, bool insChanged=false); void setEngine(DivEngine* engine); DivWaveSynth(): @@ -48,7 +73,8 @@ class DivWaveSynth { divCounter(0), width(32), height(31), - first(false) { + first(false), + activeChangedB(false) { memset(wave1,0,sizeof(int)*256); memset(wave2,0,sizeof(int)*256); memset(output,0,sizeof(int)*256); diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 71426dfe9..14ca65b40 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -2418,6 +2418,8 @@ void FurnaceGUI::drawInsEdit() { ImGui::InputScalar("Amount",ImGuiDataType_U8,&ins->ws.param1,&_ONE,&_SEVEN); + ImGui::Checkbox("Global",&ins->ws.global); + ImGui::EndTabItem(); } } From 62ff7317e3de56cf468b5dc959125410af7e6c6b Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 8 Apr 2022 02:16:57 -0500 Subject: [PATCH 558/637] GUI: effectCursorDir improvements --- src/gui/gui.cpp | 14 ++++++++++---- src/gui/settings.cpp | 18 ++++++++++++------ 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 55911a3cc..bae782ff4 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1016,11 +1016,17 @@ void FurnaceGUI::valueInput(int num, bool direct, int target) { if (!settings.effectCursorDir) { editAdvance(); } else { - if (cursor.xFine&1) { - cursor.xFine++; + if (settings.effectCursorDir==2) { + if (++cursor.xFine>=(3+(e->song.pat[cursor.xCoarse].effectRows*2))) { + cursor.xFine=3; + } } else { - editAdvance(); - cursor.xFine--; + if (cursor.xFine&1) { + cursor.xFine++; + } else { + editAdvance(); + cursor.xFine--; + } } } } diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 30fe2141d..1cbd6a06e 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -250,11 +250,6 @@ void FurnaceGUI::drawSettings() { settings.stepOnInsert=stepOnInsertB; } - bool effectCursorDirB=settings.effectCursorDir; - if (ImGui::Checkbox("Move cursor to effect value on effect input",&effectCursorDirB)) { - settings.effectCursorDir=effectCursorDirB; - } - bool cursorPastePosB=settings.cursorPastePos; if (ImGui::Checkbox("Move cursor to end of clipboard content when pasting",&cursorPastePosB)) { settings.cursorPastePos=cursorPastePosB; @@ -315,6 +310,17 @@ void FurnaceGUI::drawSettings() { settings.scrollStep=1; } + ImGui::Text("Effect input cursor behavior:"); + if (ImGui::RadioButton("Move down##eicb0",settings.effectCursorDir==0)) { + settings.effectCursorDir=0; + } + if (ImGui::RadioButton("Move to effect value (otherwise move down)##eicb1",settings.effectCursorDir==1)) { + settings.effectCursorDir=1; + } + if (ImGui::RadioButton("Move to effect value/next effect and wrap around##eicb2",settings.effectCursorDir==2)) { + settings.effectCursorDir=2; + } + ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Audio/MIDI")) { @@ -1503,7 +1509,7 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.loadJapanese,0,1); clampSetting(settings.fmLayout,0,3); clampSetting(settings.susPosition,0,1); - clampSetting(settings.effectCursorDir,0,1); + clampSetting(settings.effectCursorDir,0,2); clampSetting(settings.cursorPastePos,0,1); clampSetting(settings.titleBarInfo,0,3); clampSetting(settings.titleBarSys,0,1); From 3b7e9d292930f83058c01ea6f45e5d1321ed4367 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 8 Apr 2022 02:42:33 -0500 Subject: [PATCH 559/637] BubSys: bring on the wave synth --- src/engine/platform/bubsyswsg.cpp | 37 ++++++++++++++++++------------- src/engine/platform/bubsyswsg.h | 2 ++ 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/src/engine/platform/bubsyswsg.cpp b/src/engine/platform/bubsyswsg.cpp index 406ac69fb..989619722 100644 --- a/src/engine/platform/bubsyswsg.cpp +++ b/src/engine/platform/bubsyswsg.cpp @@ -71,17 +71,13 @@ void DivPlatformBubSysWSG::acquire(short* bufL, short* bufR, size_t start, size_ } void DivPlatformBubSysWSG::updateWave(int ch) { - DivWavetable* wt=parent->getWave(chan[ch].wave); + //DivWavetable* wt=parent->getWave(chan[ch].wave); for (int i=0; i<32; i++) { - if (wt->max>0 && wt->len>0) { - int data=wt->data[i*wt->len/32]*15/wt->max; // 4 bit PROM at bubble system - if (data<0) data=0; - if (data>15) data=15; - chan[ch].waveROM[i]=data-8; // convert to signed - } + // convert to signed + chan[ch].waveROM[i]=chan[ch].ws.output[i]-8; } if (chan[ch].active) { - rWrite(2+ch,(chan[ch].wave<<5)|chan[ch].outVol); + rWrite(2+ch,(ch<<5)|chan[ch].outVol); } } @@ -108,12 +104,17 @@ void DivPlatformBubSysWSG::tick() { } } if (chan[i].std.hadWave) { - if (chan[i].wave!=chan[i].std.wave) { + if (chan[i].wave!=chan[i].std.wave || chan[i].ws.activeChanged()) { chan[i].wave=chan[i].std.wave; - updateWave(i); + chan[i].ws.changeWave1(chan[i].wave); if (!chan[i].keyOff) chan[i].keyOn=true; } } + if (chan[i].active) { + if (chan[i].ws.tick()) { + updateWave(i); + } + } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { //DivInstrument* ins=parent->getIns(chan[i].ins); chan[i].freq=0x1000-parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true); @@ -123,10 +124,7 @@ void DivPlatformBubSysWSG::tick() { rWrite(i,chan[i].freq); k005289->update(i); if (chan[i].keyOn) { - if (chan[i].wave<0) { - chan[i].wave=0; - updateWave(i); - } + // ??? } if (chan[i].keyOff) { rWrite(2+i,(chan[i].wave<<5)|0); @@ -151,6 +149,12 @@ int DivPlatformBubSysWSG::dispatch(DivCommand c) { chan[c.chan].keyOn=true; rWrite(2+c.chan,(chan[c.chan].wave<<5)|chan[c.chan].vol); chan[c.chan].std.init(ins); + if (chan[c.chan].wave<0) { + chan[c.chan].wave=0; + chan[c.chan].ws.changeWave1(chan[c.chan].wave); + } + chan[c.chan].ws.init(ins,32,15,chan[c.chan].insChanged); + chan[c.chan].insChanged=false; break; } case DIV_CMD_NOTE_OFF: @@ -188,7 +192,7 @@ int DivPlatformBubSysWSG::dispatch(DivCommand c) { break; case DIV_CMD_WAVE: chan[c.chan].wave=c.value; - updateWave(c.chan); + chan[c.chan].ws.changeWave1(chan[c.chan].wave); chan[c.chan].keyOn=true; break; case DIV_CMD_NOTE_PORTA: { @@ -270,6 +274,8 @@ void DivPlatformBubSysWSG::reset() { memset(regPool,0,4*2); for (int i=0; i<2; i++) { chan[i]=DivPlatformBubSysWSG::Channel(); + chan[i].ws.setEngine(parent); + chan[i].ws.init(NULL,32,15,false); } if (dumpWrites) { addWrite(0xffffffff,0); @@ -288,6 +294,7 @@ bool DivPlatformBubSysWSG::keyOffAffectsArp(int ch) { void DivPlatformBubSysWSG::notifyWaveChange(int wave) { for (int i=0; i<2; i++) { if (chan[i].wave==wave) { + chan[i].ws.changeWave1(chan[i].wave); updateWave(i); } } diff --git a/src/engine/platform/bubsyswsg.h b/src/engine/platform/bubsyswsg.h index 5fda0fd95..6c0e2261e 100644 --- a/src/engine/platform/bubsyswsg.h +++ b/src/engine/platform/bubsyswsg.h @@ -23,6 +23,7 @@ #include "../dispatch.h" #include #include "../macroInt.h" +#include "../waveSynth.h" #include "sound/k005289/k005289.hpp" class DivPlatformBubSysWSG: public DivDispatch { @@ -33,6 +34,7 @@ class DivPlatformBubSysWSG: public DivDispatch { signed char vol, outVol, wave; signed char waveROM[32] = {0}; // 4 bit PROM per channel on bubble system DivMacroInt std; + DivWaveSynth ws; Channel(): freq(0), baseFreq(0), From 26dca41b633ae0ebdb277509bd206661feebda07 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 8 Apr 2022 02:59:58 -0500 Subject: [PATCH 560/637] FDS: wave synth --- src/engine/platform/fds.cpp | 45 ++++++++++++++++++++----------------- src/engine/platform/fds.h | 2 ++ 2 files changed, 27 insertions(+), 20 deletions(-) diff --git a/src/engine/platform/fds.cpp b/src/engine/platform/fds.cpp index ede447df8..cd70dfacd 100644 --- a/src/engine/platform/fds.cpp +++ b/src/engine/platform/fds.cpp @@ -89,18 +89,10 @@ void DivPlatformFDS::acquire(short* bufL, short* bufR, size_t start, size_t len) } void DivPlatformFDS::updateWave() { - DivWavetable* wt=parent->getWave(chan[0].wave); // TODO: master volume rWrite(0x4089,0x80); for (int i=0; i<64; i++) { - if (wt->max<1 || wt->len<1) { - rWrite(0x4040+i,0); - } else { - int data=wt->data[i*wt->len/64]*63/wt->max; - if (data<0) data=0; - if (data>63) data=63; - rWrite(0x4040+i,data); - } + rWrite(0x4040+i,ws.output[i]); } rWrite(0x4089,0); } @@ -157,12 +149,18 @@ void DivPlatformFDS::tick() { } }*/ if (chan[i].std.hadWave) { - if (chan[i].wave!=chan[i].std.wave) { + if (chan[i].wave!=chan[i].std.wave || ws.activeChanged()) { chan[i].wave=chan[i].std.wave; - updateWave(); + ws.changeWave1(chan[i].wave); //if (!chan[i].keyOff) chan[i].keyOn=true; } } + if (chan[i].active) { + if (ws.tick()) { + updateWave(); + if (!chan[i].keyOff) chan[i].keyOn=true; + } + } if (chan[i].std.hadEx1) { // mod depth chan[i].modOn=chan[i].std.ex1; chan[i].modDepth=chan[i].std.ex1; @@ -190,10 +188,7 @@ void DivPlatformFDS::tick() { if (chan[i].freq>4095) chan[i].freq=4095; if (chan[i].freq<0) chan[i].freq=0; if (chan[i].keyOn) { - if (chan[i].wave<0) { - chan[i].wave=0; - updateWave(); - } + // ??? } if (chan[i].keyOff) { rWrite(0x4080,0x80); @@ -209,20 +204,20 @@ void DivPlatformFDS::tick() { int DivPlatformFDS::dispatch(DivCommand c) { switch (c.cmd) { - case DIV_CMD_NOTE_ON: + case DIV_CMD_NOTE_ON: { + DivInstrument* ins=parent->getIns(chan[c.chan].ins); if (c.value!=DIV_NOTE_NULL) { chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value); chan[c.chan].freqChanged=true; chan[c.chan].note=c.value; } if (chan[c.chan].insChanged) { - DivInstrument* ins=parent->getIns(chan[c.chan].ins); if (ins->fds.initModTableWithFirstWave) { // compatible if (chan[c.chan].wave==-1) { DivWavetable* wt=parent->getWave(0); for (int i=0; i<32; i++) { if (wt->max<1 || wt->len<1) { - rWrite(0x4040+i,0); + chan[c.chan].modTable[i]=0; } else { int data=wt->data[i*MIN(32,wt->len)/32]*7/wt->max; if (data<0) data=0; @@ -253,9 +248,16 @@ int DivPlatformFDS::dispatch(DivCommand c) { } chan[c.chan].active=true; chan[c.chan].keyOn=true; - chan[c.chan].std.init(parent->getIns(chan[c.chan].ins)); + chan[c.chan].std.init(ins); + if (chan[c.chan].wave<0) { + chan[c.chan].wave=0; + ws.changeWave1(chan[c.chan].wave); + } + ws.init(ins,64,63,chan[c.chan].insChanged); rWrite(0x4080,0x80|chan[c.chan].vol); + chan[c.chan].insChanged=false; break; + } case DIV_CMD_NOTE_OFF: chan[c.chan].active=false; chan[c.chan].keyOff=true; @@ -268,6 +270,7 @@ int DivPlatformFDS::dispatch(DivCommand c) { case DIV_CMD_INSTRUMENT: if (chan[c.chan].ins!=c.value || c.value2==1) { chan[c.chan].ins=c.value; + chan[c.chan].insChanged=true; } break; case DIV_CMD_VOLUME: @@ -289,7 +292,7 @@ int DivPlatformFDS::dispatch(DivCommand c) { case DIV_CMD_WAVE: if (chan[c.chan].wave!=c.value) { chan[c.chan].wave=c.value; - updateWave(); + ws.changeWave1(chan[c.chan].wave); } break; case DIV_CMD_FDS_MOD_DEPTH: @@ -406,6 +409,8 @@ void DivPlatformFDS::reset() { for (int i=0; i<1; i++) { chan[i]=DivPlatformFDS::Channel(); } + ws.setEngine(parent); + ws.init(NULL,64,63,false); if (dumpWrites) { addWrite(0xffffffff,0); } diff --git a/src/engine/platform/fds.h b/src/engine/platform/fds.h index d9f145e1c..d8d588a23 100644 --- a/src/engine/platform/fds.h +++ b/src/engine/platform/fds.h @@ -22,6 +22,7 @@ #include "../dispatch.h" #include "../macroInt.h" +#include "../waveSynth.h" class DivPlatformFDS: public DivDispatch { struct Channel { @@ -59,6 +60,7 @@ class DivPlatformFDS: public DivDispatch { }; Channel chan[1]; bool isMuted[1]; + DivWaveSynth ws; unsigned char apuType; struct _fds* fds; unsigned char regPool[128]; From 65f893822a1ce1431f68934d4199f4d046837d12 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 8 Apr 2022 04:34:39 -0500 Subject: [PATCH 561/637] dev79 - wave synth data storage also wave synth works on swan and X1-010 now however it's untested on X1 because i wrote it in bed --- papers/format.md | 14 ++++++++++++ src/engine/engine.h | 4 ++-- src/engine/instrument.cpp | 28 +++++++++++++++++++++++ src/engine/instrument.h | 4 +--- src/engine/platform/swan.cpp | 42 ++++++++++++++++------------------ src/engine/platform/swan.h | 2 ++ src/engine/platform/x1_010.cpp | 34 ++++++++++++++++----------- src/engine/platform/x1_010.h | 2 ++ 8 files changed, 90 insertions(+), 40 deletions(-) diff --git a/papers/format.md b/papers/format.md index 005345755..9ce20d2d7 100644 --- a/papers/format.md +++ b/papers/format.md @@ -29,6 +29,7 @@ furthermore, an `or reserved` indicates this field is always present, but is res the format versions are: +- 79: Furnace dev79 - 78: Furnace dev78 - 77: Furnace dev77 - 76: Furnace dev76 @@ -579,6 +580,19 @@ size | description --- | **OPZ instrument extra data** (>=77) 1 | fms2 1 | ams2 + --- | **wavetable synth data** (>=79) + 4 | first wave + 4 | second wave + 1 | rate divider + 1 | effect + | - bit 7: single or dual effect + 1 | enabled + 1 | global + 1 | speed (+1) + 1 | parameter 1 + 1 | parameter 2 + 1 | parameter 3 + 1 | parameter 4 ``` # wavetable diff --git a/src/engine/engine.h b/src/engine/engine.h index e5240ab9e..677cdeaaa 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -42,8 +42,8 @@ #define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock(); #define BUSY_END isBusy.unlock(); softLocked=false; -#define DIV_VERSION "dev78" -#define DIV_ENGINE_VERSION 78 +#define DIV_VERSION "dev79" +#define DIV_ENGINE_VERSION 79 // for imports #define DIV_VERSION_MOD 0xff01 diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index 1299cdec8..60ce6810b 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -467,6 +467,19 @@ void DivInstrument::putInsData(SafeWriter* w) { // OPZ w->writeC(fm.fms2); w->writeC(fm.ams2); + + // wave synth + w->writeI(ws.wave1); + w->writeI(ws.wave2); + w->writeC(ws.rateDivider); + w->writeC(ws.effect); + w->writeC(ws.enabled); + w->writeC(ws.global); + w->writeC(ws.speed); + w->writeC(ws.param1); + w->writeC(ws.param2); + w->writeC(ws.param3); + w->writeC(ws.param4); } DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) { @@ -895,6 +908,21 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) { fm.fms2=reader.readC(); fm.ams2=reader.readC(); } + + // wave synth + if (version>=79) { + ws.wave1=reader.readI(); + ws.wave2=reader.readI(); + ws.rateDivider=reader.readC(); + ws.effect=reader.readC(); + ws.enabled=reader.readC(); + ws.global=reader.readC(); + ws.speed=reader.readC(); + ws.param1=reader.readC(); + ws.param2=reader.readC(); + ws.param3=reader.readC(); + ws.param4=reader.readC(); + } return DIV_DATA_SUCCESS; } diff --git a/src/engine/instrument.h b/src/engine/instrument.h index c56319119..8109030ed 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -502,7 +502,7 @@ enum DivWaveSynthEffects { struct DivInstrumentWaveSynth { int wave1, wave2; - unsigned char rateDivider, width, height; + unsigned char rateDivider; unsigned char effect; bool oneShot, enabled, global; unsigned char speed, param1, param2, param3, param4; @@ -510,8 +510,6 @@ struct DivInstrumentWaveSynth { wave1(0), wave2(0), rateDivider(1), - width(32), - height(32), effect(DIV_WS_NONE), oneShot(false), enabled(false), diff --git a/src/engine/platform/swan.cpp b/src/engine/platform/swan.cpp index 0f32e9a08..6966a2d1c 100644 --- a/src/engine/platform/swan.cpp +++ b/src/engine/platform/swan.cpp @@ -111,22 +111,11 @@ void DivPlatformSwan::acquire(short* bufL, short* bufR, size_t start, size_t len } void DivPlatformSwan::updateWave(int ch) { - DivWavetable* wt=parent->getWave(chan[ch].wave); unsigned char addr=0x40+ch*16; - if (wt->max<1 || wt->len<1) { - for (int i=0; i<16; i++) { - rWrite(addr+i,0); - } - } else { - for (int i=0; i<16; i++) { - int nibble1=(wt->data[(i*2)*wt->len/32]*15)/wt->max; - int nibble2=(wt->data[(1+i*2)*wt->len/32]*15)/wt->max; - if (nibble1<0) nibble1=0; - if (nibble1>15) nibble1=15; - if (nibble2<0) nibble2=0; - if (nibble2>15) nibble2=15; - rWrite(addr+i,nibble1|(nibble2<<4)); - } + for (int i=0; i<16; i++) { + int nibble1=chan[ch].ws.output[i<<1]; + int nibble2=chan[ch].ws.output[1+(i<<1)]; + rWrite(addr+i,nibble1|(nibble2<<4)); } } @@ -179,13 +168,16 @@ void DivPlatformSwan::tick() { } } if (chan[i].std.hadWave && !(i==1 && pcm)) { - if (chan[i].wave!=chan[i].std.wave) { + if (chan[i].wave!=chan[i].std.wave || chan[i].ws.activeChanged()) { chan[i].wave=chan[i].std.wave; - updateWave(i); + chan[i].ws.changeWave1(chan[i].wave); } } if (chan[i].active) { sndCtrl|=(1<calcFreq(chan[i].baseFreq,chan[i].pitch,true); @@ -211,10 +203,6 @@ void DivPlatformSwan::tick() { if (!chan[i].std.willVol) { calcAndWriteOutVol(i,15); } - if (chan[i].wave<0) { - chan[i].wave=0; - updateWave(i); - } chan[i].keyOn=false; } if (chan[i].keyOff) { @@ -300,6 +288,12 @@ int DivPlatformSwan::dispatch(DivCommand c) { chan[c.chan].active=true; chan[c.chan].keyOn=true; chan[c.chan].std.init(ins); + if (chan[c.chan].wave<0) { + chan[c.chan].wave=0; + chan[c.chan].ws.changeWave1(chan[c.chan].wave); + } + chan[c.chan].ws.init(ins,32,15,chan[c.chan].insChanged); + chan[c.chan].insChanged=false; break; } case DIV_CMD_NOTE_OFF: @@ -319,6 +313,7 @@ int DivPlatformSwan::dispatch(DivCommand c) { case DIV_CMD_INSTRUMENT: if (chan[c.chan].ins!=c.value || c.value2==1) { chan[c.chan].ins=c.value; + chan[c.chan].insChanged=true; } break; case DIV_CMD_VOLUME: @@ -338,7 +333,7 @@ int DivPlatformSwan::dispatch(DivCommand c) { break; case DIV_CMD_WAVE: chan[c.chan].wave=c.value; - updateWave(c.chan); + chan[c.chan].ws.changeWave1(chan[c.chan].wave); chan[c.chan].keyOn=true; break; case DIV_CMD_WS_SWEEP_TIME: @@ -458,6 +453,8 @@ void DivPlatformSwan::reset() { chan[i]=Channel(); chan[i].vol=15; chan[i].pan=0xff; + chan[i].ws.setEngine(parent); + chan[i].ws.init(NULL,32,15,false); rWrite(0x08+i,0xff); } if (dumpWrites) { @@ -484,6 +481,7 @@ bool DivPlatformSwan::isStereo() { void DivPlatformSwan::notifyWaveChange(int wave) { for (int i=0; i<4; i++) { if (chan[i].wave==wave) { + chan[i].ws.changeWave1(wave); updateWave(i); } } diff --git a/src/engine/platform/swan.h b/src/engine/platform/swan.h index 47470f57b..610884b00 100644 --- a/src/engine/platform/swan.h +++ b/src/engine/platform/swan.h @@ -22,6 +22,7 @@ #include "../dispatch.h" #include "../macroInt.h" +#include "../waveSynth.h" #include "sound/swan.h" #include @@ -32,6 +33,7 @@ class DivPlatformSwan: public DivDispatch { bool active, insChanged, freqChanged, keyOn, keyOff, inPorta; int vol, outVol, wave; DivMacroInt std; + DivWaveSynth ws; Channel(): freq(0), baseFreq(0), diff --git a/src/engine/platform/x1_010.cpp b/src/engine/platform/x1_010.cpp index 6834986f6..68f6db297 100644 --- a/src/engine/platform/x1_010.cpp +++ b/src/engine/platform/x1_010.cpp @@ -274,19 +274,12 @@ double DivPlatformX1_010::NoteX1_010(int ch, int note) { } void DivPlatformX1_010::updateWave(int ch) { - DivWavetable* wt=parent->getWave(chan[ch].wave); if (chan[ch].active) { - chan[ch].waveBank ^= 1; + chan[ch].waveBank^=1; } for (int i=0; i<128; i++) { - if (wt->max<1 || wt->len<1) { - waveWrite(ch,i,0); - } else { - int data=wt->data[i*wt->len/128]*255/wt->max; - if (data<0) data=0; - if (data>255) data=255; - waveWrite(ch,i,data); - } + int data=chan[ch].ws.output[i]; + waveWrite(ch,i,data); } if (!chan[ch].pcm) { chWrite(ch,1,(chan[ch].waveBank<<4)|(ch&0xf)); @@ -371,10 +364,10 @@ void DivPlatformX1_010::tick() { } } if (chan[i].std.hadWave && !chan[i].pcm) { - if (chan[i].wave!=chan[i].std.wave) { + if (chan[i].wave!=chan[i].std.wave || chan[i].ws.activeChanged()) { chan[i].wave=chan[i].std.wave; if (!chan[i].pcm) { - updateWave(i); + chan[i].ws.changeWave1(chan[i].wave); if (!chan[i].keyOff) chan[i].keyOn=true; } } @@ -458,6 +451,11 @@ void DivPlatformX1_010::tick() { if (!chan[i].std.willEx3) chan[i].autoEnvNum=1; } } + if (chan[i].active) { + if (chan[i].ws.tick()) { + updateWave(i); + } + } if (chan[i].envChanged) { chan[i].lvol=isMuted[i]?0:(((chan[i].outVol&0xf)*((chan[i].pan>>4)&0xf))/15); chan[i].rvol=isMuted[i]?0:(((chan[i].outVol&0xf)*((chan[i].pan>>0)&0xf))/15); @@ -575,6 +573,12 @@ int DivPlatformX1_010::dispatch(DivCommand c) { chan[c.chan].keyOn=true; chan[c.chan].envChanged=true; chan[c.chan].std.init(ins); + if (chan[c.chan].wave<0) { + chan[c.chan].wave=0; + chan[c.chan].ws.changeWave1(chan[c.chan].wave); + } + chan[c.chan].ws.init(ins,128,255,chan[c.chan].insChanged); + chan[c.chan].insChanged=false; refreshControl(c.chan); break; } @@ -591,6 +595,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { case DIV_CMD_INSTRUMENT: if (chan[c.chan].ins!=c.value || c.value2==1) { chan[c.chan].ins=c.value; + chan[c.chan].insChanged=true; } break; case DIV_CMD_VOLUME: @@ -618,7 +623,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { break; case DIV_CMD_WAVE: chan[c.chan].wave=c.value; - updateWave(c.chan); + chan[c.chan].ws.changeWave1(chan[c.chan].wave); chan[c.chan].keyOn=true; break; case DIV_CMD_X1_010_ENVELOPE_SHAPE: @@ -813,6 +818,8 @@ void DivPlatformX1_010::reset() { for (int i=0; i<16; i++) { chan[i]=DivPlatformX1_010::Channel(); chan[i].reset(); + chan[i].ws.setEngine(parent); + chan[i].ws.init(NULL,128,255,false); } x1_010->reset(); sampleBank=0; @@ -833,6 +840,7 @@ bool DivPlatformX1_010::keyOffAffectsArp(int ch) { void DivPlatformX1_010::notifyWaveChange(int wave) { for (int i=0; i<16; i++) { if (chan[i].wave==wave) { + chan[i].ws.changeWave1(wave); updateWave(i); } } diff --git a/src/engine/platform/x1_010.h b/src/engine/platform/x1_010.h index 389fa7bf7..2bfb78cc9 100644 --- a/src/engine/platform/x1_010.h +++ b/src/engine/platform/x1_010.h @@ -23,6 +23,7 @@ #include "../dispatch.h" #include "../engine.h" #include "../macroInt.h" +#include "../waveSynth.h" #include "sound/x1_010/x1_010.hpp" class DivX1_010Interface: public x1_010_mem_intf { @@ -86,6 +87,7 @@ class DivPlatformX1_010: public DivDispatch { unsigned char waveBank; Envelope env; DivMacroInt std; + DivWaveSynth ws; void reset() { freq = baseFreq = pitch = note = 0; wave = sample = ins = -1; From dccd30f73cf8a68bf6765cad533f7d6887dc2171 Mon Sep 17 00:00:00 2001 From: cam900 Date: Sat, 9 Apr 2022 00:42:27 +0900 Subject: [PATCH 562/637] Fix loop area view in sample editor --- src/gui/sampleEdit.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index b976ab92f..18a91e290 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -605,7 +605,7 @@ void FurnaceGUI::drawSampleEdit() { ImU32 centerLineColor=ImAlphaBlendColors(bgColor,ImGui::GetColorU32(ImGuiCol_PlotLines,0.25)); for (int i=0; iloopStart>=0 && sample->loopStart<(int)sample->samples && j-samplePos>sample->loopStart) { + if (sample->loopStart>=0 && sample->loopStart<(int)sample->samples && ((j+samplePos)*sampleZoom)>sample->loopStart) { data[i*availX+j]=bgColorLoop; } else { data[i*availX+j]=bgColor; @@ -870,4 +870,4 @@ void FurnaceGUI::doRedoSample() { updateSampleTex=true; } }); -} \ No newline at end of file +} From 48e8e49ba8f3d64a6d68c827d924b333a31e4c47 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 8 Apr 2022 12:21:42 -0500 Subject: [PATCH 563/637] whoops! --- src/engine/waveSynth.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/engine/waveSynth.h b/src/engine/waveSynth.h index 70fb27389..ccd8b23de 100644 --- a/src/engine/waveSynth.h +++ b/src/engine/waveSynth.h @@ -75,8 +75,8 @@ class DivWaveSynth { height(31), first(false), activeChangedB(false) { - memset(wave1,0,sizeof(int)*256); - memset(wave2,0,sizeof(int)*256); + memset(wave1,0,256); + memset(wave2,0,256); memset(output,0,sizeof(int)*256); } }; From 5b95cf9db949c68ed717a49e2e9e1eddbb2564b5 Mon Sep 17 00:00:00 2001 From: OPNA2608 Date: Mon, 28 Mar 2022 15:38:35 +0200 Subject: [PATCH 564/637] Add new CI job to prepare for artifact uploads --- .github/workflows/build.yml | 198 +++++++++++++++++- .../SDL-Fix-MSVC-static-runtime-linking.patch | 35 ++++ 2 files changed, 231 insertions(+), 2 deletions(-) create mode 100644 extern/SDL-Fix-MSVC-static-runtime-linking.patch diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4dce04678..e53d720c7 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,6 +14,7 @@ env: BUILD_TYPE: Release jobs: + # Do a plain build, like we would expect a user to do it on their end. build: strategy: matrix: @@ -24,7 +25,7 @@ jobs: - { name: 'Ubuntu', os: ubuntu-18.04, shell: bash } fail-fast: false - name: ${{ matrix.config.name }} + name: "Build: ${{ matrix.config.name }}" runs-on: ${{ matrix.config.os }} defaults: run: @@ -81,7 +82,7 @@ jobs: if [ '${{ matrix.config.compiler }}' == 'mingw' ]; then CMAKE_EXTRA_ARGS+=('-G' 'MSYS Makefiles') elif [ '${{ matrix.config.compiler }}' == 'msvc' ]; then - # We don't want all the MSVC warnings to cause errors yet + # FIXME We don't want all the MSVC warnings to cause errors yet export USE_WAE=OFF fi fi @@ -106,3 +107,196 @@ jobs: cmake \ --install ${PWD}/build \ --config ${{ env.BUILD_TYPE }} + + # Now do a build that we can package. (static runtime linking on MSVC, zipping on Windows, CPack on Darwin, appimage stuff on Ubuntu) + # We rebuild here because the build dependencies may be slightly different and might trigger a full rebuild anyway. + package: + needs: build + strategy: + matrix: + config: + - { name: 'Windows MSVC', os: windows-latest, compiler: msvc, shell: bash } + - { name: 'Windows MinGW', os: windows-latest, compiler: mingw, shell: 'msys2 {0}' } + - { name: 'macOS', os: macos-latest, shell: bash } + - { name: 'Ubuntu', os: ubuntu-18.04, shell: bash } + fail-fast: false + + name: "Package: ${{ matrix.config.name }}" + runs-on: ${{ matrix.config.os }} + defaults: + run: + shell: ${{ matrix.config.shell }} + + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + submodules: recursive + + - name: Setup Toolchain [Windows MSVC] + if: ${{ runner.os == 'Windows' && matrix.config.compiler == 'msvc' }} + uses: seanmiddleditch/gha-setup-vsdevenv@v3 + + - name: Setup Toolchain [Windows MinGW] + if: ${{ runner.os == 'Windows' && matrix.config.compiler == 'mingw' }} + uses: msys2/setup-msys2@v2 + with: + msystem: MINGW64 + update: true + install: | + mingw-w64-x86_64-toolchain + mingw-w64-x86_64-cmake + make + p7zip + + - name: Install Dependencies [macOS] + if: ${{ runner.os == 'macOS' }} + run: | + export HOMEBREW_NO_INSTALL_CLEANUP=1 + brew update + brew install \ + pkg-config \ + sdl2 \ + libsndfile \ + zlib \ + jack + + - name: Install Dependencies [Ubuntu] + if: ${{ runner.os == 'Linux' }} + run: | + sudo apt update + sudo apt install \ + libsdl2-dev \ + libsndfile1-dev \ + zlib1g-dev \ + libjack-jackd2-dev \ + appstream + wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage" + chmod +x appimagetool-x86_64.AppImage + + - name: Set package identity + id: package-identity + run: | + package_name="furnace-${GITHUB_SHA}" + package_ext="" + if [ '${{ runner.os }}' == 'Windows' ]; then + package_name="${package_name}-Windows" + if [ '${{ matrix.config.compiler }}' == 'mingw' ]; then + package_name="${package_name}-MinGW" + else + package_name="${package_name}-MSVC" + fi + package_ext=".zip" + elif [ '${{ runner.os }}' == 'macOS' ]; then + package_name="${package_name}-macOS" + package_ext=".dmg" + else + package_name="${package_name}-Linux" + package_ext=".AppImage" + fi + + echo "Package identity: ${package_name}" + echo "Package file: ${package_name}${package_ext}" + + echo "::set-output name=id::${package_name}" + echo "::set-output name=filename::${package_name}${package_ext}" + + - name: Configure + run: | + export USE_WAE=ON + export CMAKE_EXTRA_ARGS=() + if [ '${{ runner.os }}' == 'Windows' ]; then + if [ '${{ matrix.config.compiler }}' == 'mingw' ]; then + CMAKE_EXTRA_ARGS+=('-G' 'MSYS Makefiles') + elif [ '${{ matrix.config.compiler }}' == 'msvc' ]; then + # FIXME We don't want all the MSVC warnings to cause errors yet + export USE_WAE=OFF + + # Force static linking + # 1. Make MSVC runtime configurable + CMAKE_EXTRA_ARGS+=('-DCMAKE_POLICY_DEFAULT_CMP0091=NEW') + # 2. Use static (debug) runtime + if [ '${{ env.BUILD_TYPE }}' == 'Debug' ]; then + CMAKE_EXTRA_ARGS+=('-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDebug') + else + CMAKE_EXTRA_ARGS+=('-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded') + fi + + # Fix SDL static linking (see linked issues in patch file) + pushd extern/SDL + env EMAIL=root@localhost git am ../SDL-Fix-MSVC-static-runtime-linking.patch + popd + fi + elif [ '${{ runner.os }}' == 'macOS' ]; then + CMAKE_EXTRA_ARGS+=('-DCMAKE_OSX_DEPLOYMENT_TARGET="10.9"') + fi + + cmake \ + -B ${PWD}/build \ + -DCMAKE_INSTALL_PREFIX=/usr \ + -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} \ + -DWARNINGS_ARE_ERRORS=${USE_WAE} \ + "${CMAKE_EXTRA_ARGS[@]}" + + - name: Build + run: | + export VERBOSE=1 + cmake \ + --build ${PWD}/build \ + --config ${{ env.BUILD_TYPE }} \ + --parallel 2 + + - name: Package [Windows] + if: ${{ runner.os == 'Windows' }} + run: | + binPath=build + if [ '${{ matrix.config.compiler }}' == 'msvc' ]; then + binPath="${binPath}/${{ env.BUILD_TYPE }}" + fi + if [ '${{ matrix.config.compiler }}' == 'mingw' ]; then + strip -s "${binPath}/furnace.exe" + fi + + mkdir target + pushd target + + cp -v ../LICENSE LICENSE.txt + cp -v ../README.md README.txt + cp -vr ../{papers,demos} ../${binPath}/furnace.exe ./ + + 7z a -tzip ../${{ steps.package-identity.outputs.filename }} * + popd + + - name: Package [macOS] + if: ${{ runner.os == 'macOS' }} + run: | + pushd build + cpack + mv Furnace-*-Darwin.dmg ../${{ steps.package-identity.outputs.filename }} + popd + + - name: Package [Ubuntu] + if: ${{ runner.os == 'Linux' }} + run: | + strip -s build/furnace + + mkdir -p target/furnace.AppDir + make -C ${PWD}/build DESTDIR=${PWD}/target/furnace.AppDir install + pushd target + + pushd furnace.AppDir + cp -v usr/share/{icons/hicolor/1024x1024/apps/furnace.png,applications/furnace.desktop} ./ + ln -s furnace.png .DirIcon + mv -v usr/share/metainfo/{furnace.appdata,org.tildearrow.furnace.metainfo}.xml + cp -v ../../res/AppRun ./ + popd + + ../appimagetool-x86_64.AppImage furnace.AppDir + mv Furnace-*.AppImage ../${{ steps.package-identity.outputs.filename }} + popd + + - name: Upload artifact + uses: actions/upload-artifact@v3 + with: + name: ${{ steps.package-identity.outputs.id }} + path: ${{ steps.package-identity.outputs.filename }} diff --git a/extern/SDL-Fix-MSVC-static-runtime-linking.patch b/extern/SDL-Fix-MSVC-static-runtime-linking.patch new file mode 100644 index 000000000..31b9ea619 --- /dev/null +++ b/extern/SDL-Fix-MSVC-static-runtime-linking.patch @@ -0,0 +1,35 @@ +From 34d3dcd98697af081625ccea7a41a3c47806c4ff Mon Sep 17 00:00:00 2001 +From: OPNA2608 +Date: Mon, 28 Mar 2022 16:43:16 +0200 +Subject: [PATCH] SDL: Fix MSVC static runtime linking + +See https://github.com/libsdl-org/SDL/issues/3662, https://github.com/libsdl-org/SDL/issues/4258. +--- + src/stdlib/SDL_stdlib.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/src/stdlib/SDL_stdlib.c b/src/stdlib/SDL_stdlib.c +index 9d785aad5..dfac4d7c3 100644 +--- a/src/stdlib/SDL_stdlib.c ++++ b/src/stdlib/SDL_stdlib.c +@@ -550,7 +550,7 @@ __declspec(selectany) int _fltused = 1; + #endif + + /* The optimizer on Visual Studio 2005 and later generates memcpy() and memset() calls */ +-#if _MSC_VER >= 1400 ++#if (_MSC_VER >= 1400) && !defined(_MT) + extern void *memcpy(void* dst, const void* src, size_t len); + #pragma intrinsic(memcpy) + +@@ -570,7 +570,7 @@ memset(void *dst, int c, size_t len) + { + return SDL_memset(dst, c, len); + } +-#endif /* _MSC_VER >= 1400 */ ++#endif /* (_MSC_VER >= 1400) && !defined(_MT) */ + + #ifdef _M_IX86 + +-- +2.33.1 + From 8a6dfa8d199cdb66af6d88d257d5b75f0b0b3d63 Mon Sep 17 00:00:00 2001 From: OPNA2608 Date: Thu, 31 Mar 2022 15:57:52 +0200 Subject: [PATCH 565/637] pfd: Fixes for MinGW 10 --- extern/pfd-fixed/portable-file-dialogs.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/extern/pfd-fixed/portable-file-dialogs.h b/extern/pfd-fixed/portable-file-dialogs.h index 41e588acf..008fe0a94 100644 --- a/extern/pfd-fixed/portable-file-dialogs.h +++ b/extern/pfd-fixed/portable-file-dialogs.h @@ -57,7 +57,7 @@ #ifndef PFD_HAS_IFILEDIALOG # define PFD_HAS_IFILEDIALOG 1 # if (defined __MINGW64__ || defined __MINGW32__) && defined __GXX_ABI_VERSION -# if __GXX_ABI_VERSION <= 1013 +# if __GXX_ABI_VERSION <= 1014 # undef PFD_HAS_IFILEDIALOG # define PFD_HAS_IFILEDIALOG 0 # endif @@ -1383,7 +1383,7 @@ inline notify::notify(std::string const &title, /* case icon::info: */ default: nid->dwInfoFlags = NIIF_INFO; break; } - ENUMRESNAMEPROC icon_enum_callback = [](HMODULE, LPCTSTR, LPTSTR lpName, LONG_PTR lParam) -> BOOL + ENUMRESNAMEPROC icon_enum_callback = [](HMODULE, LPCTSTR, LPTSTR lpName, LONG_PTR lParam) -> BOOL WINAPI { ((NOTIFYICONDATAW *)lParam)->hIcon = ::LoadIcon(GetModuleHandle(nullptr), lpName); return false; From 0351388996d3ea1ac4741f00272b75b51bc4d74c Mon Sep 17 00:00:00 2001 From: OPNA2608 Date: Thu, 31 Mar 2022 16:20:51 +0200 Subject: [PATCH 566/637] Add MinGW cross-compiler toolchains --- scripts/Cross-MinGW-x86.cmake | 4 ++++ scripts/Cross-MinGW-x86_64.cmake | 4 ++++ scripts/Cross-MinGW.cmake | 14 ++++++++++++++ 3 files changed, 22 insertions(+) create mode 100644 scripts/Cross-MinGW-x86.cmake create mode 100644 scripts/Cross-MinGW-x86_64.cmake create mode 100644 scripts/Cross-MinGW.cmake diff --git a/scripts/Cross-MinGW-x86.cmake b/scripts/Cross-MinGW-x86.cmake new file mode 100644 index 000000000..4049ffdd0 --- /dev/null +++ b/scripts/Cross-MinGW-x86.cmake @@ -0,0 +1,4 @@ +set(CMAKE_SYSTEM_NAME Windows) +set(CMAKE_SYSTEM_PROCESSOR i686) + +include(${CMAKE_CURRENT_LIST_DIR}/Cross-MinGW.cmake) diff --git a/scripts/Cross-MinGW-x86_64.cmake b/scripts/Cross-MinGW-x86_64.cmake new file mode 100644 index 000000000..5b088cb30 --- /dev/null +++ b/scripts/Cross-MinGW-x86_64.cmake @@ -0,0 +1,4 @@ +set(CMAKE_SYSTEM_NAME Windows) +set(CMAKE_SYSTEM_PROCESSOR x86_64) + +include(${CMAKE_CURRENT_LIST_DIR}/Cross-MinGW.cmake) diff --git a/scripts/Cross-MinGW.cmake b/scripts/Cross-MinGW.cmake new file mode 100644 index 000000000..09871d6bb --- /dev/null +++ b/scripts/Cross-MinGW.cmake @@ -0,0 +1,14 @@ +set(TARGET_PREFIX ${CMAKE_SYSTEM_PROCESSOR}-w64-mingw32) + +set(CMAKE_C_COMPILER ${TARGET_PREFIX}-gcc-posix) +set(CMAKE_CXX_COMPILER ${TARGET_PREFIX}-g++-posix) +set(PKG_CONFIG_EXECUTABLE ${TARGET_PREFIX}-pkg-config) + +set(CMAKE_FIND_ROOT_PATH /usr/${TARGET_PREFIX}) + +# Search host system for programs +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) +# Search target system for libraries, headers & CMake packages +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) From 6c9f1c6d540e982c0e6f6ee72e3613f8a6933943 Mon Sep 17 00:00:00 2001 From: OPNA2608 Date: Fri, 1 Apr 2022 02:55:20 +0200 Subject: [PATCH 567/637] Cross-compile MinGW builds on CI --- .github/workflows/build.yml | 125 ++++++++++++++++-------------------- 1 file changed, 55 insertions(+), 70 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e53d720c7..7d3dd84f5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,17 +19,14 @@ jobs: strategy: matrix: config: - - { name: 'Windows MSVC', os: windows-latest, compiler: msvc, shell: bash } - - { name: 'Windows MinGW', os: windows-latest, compiler: mingw, shell: 'msys2 {0}' } - - { name: 'macOS', os: macos-latest, shell: bash } - - { name: 'Ubuntu', os: ubuntu-18.04, shell: bash } + - { name: 'Windows MSVC', os: windows-latest, compiler: msvc } + - { name: 'Windows MinGW', os: ubuntu-20.04, compiler: mingw } + - { name: 'macOS', os: macos-latest } + - { name: 'Ubuntu', os: ubuntu-18.04 } fail-fast: false name: "Build: ${{ matrix.config.name }}" runs-on: ${{ matrix.config.os }} - defaults: - run: - shell: ${{ matrix.config.shell }} steps: - name: Checkout @@ -38,19 +35,16 @@ jobs: submodules: recursive - name: Setup Toolchain [Windows MSVC] - if: ${{ runner.os == 'Windows' && matrix.config.compiler == 'msvc' }} + if: ${{ matrix.config.compiler == 'msvc' }} uses: seanmiddleditch/gha-setup-vsdevenv@v3 - name: Setup Toolchain [Windows MinGW] - if: ${{ runner.os == 'Windows' && matrix.config.compiler == 'mingw' }} - uses: msys2/setup-msys2@v2 - with: - msystem: MINGW64 - update: true - install: | - mingw-w64-x86_64-toolchain - mingw-w64-x86_64-cmake - make + if: ${{ matrix.config.compiler == 'mingw' }} + run: | + sudo apt update + sudo apt install \ + mingw-w64 \ + mingw-w64-tools - name: Install Dependencies [macOS] if: ${{ runner.os == 'macOS' }} @@ -65,7 +59,7 @@ jobs: jack - name: Install Dependencies [Ubuntu] - if: ${{ runner.os == 'Linux' }} + if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' }} run: | sudo apt update sudo apt install \ @@ -78,13 +72,11 @@ jobs: run: | export USE_WAE=ON export CMAKE_EXTRA_ARGS=() - if [ '${{ runner.os }}' == 'Windows' ]; then - if [ '${{ matrix.config.compiler }}' == 'mingw' ]; then - CMAKE_EXTRA_ARGS+=('-G' 'MSYS Makefiles') - elif [ '${{ matrix.config.compiler }}' == 'msvc' ]; then - # FIXME We don't want all the MSVC warnings to cause errors yet - export USE_WAE=OFF - fi + if [ '${{ matrix.config.compiler }}' == 'msvc' ]; then + # FIXME We don't want all the MSVC warnings to cause errors yet + export USE_WAE=OFF + elif [ '${{ matrix.config.compiler }}' == 'mingw' ]; then + CMAKE_EXTRA_ARGS+=('-DCMAKE_TOOLCHAIN_FILE=scripts/Cross-MinGW-x86.cmake') fi cmake \ @@ -115,17 +107,14 @@ jobs: strategy: matrix: config: - - { name: 'Windows MSVC', os: windows-latest, compiler: msvc, shell: bash } - - { name: 'Windows MinGW', os: windows-latest, compiler: mingw, shell: 'msys2 {0}' } - - { name: 'macOS', os: macos-latest, shell: bash } - - { name: 'Ubuntu', os: ubuntu-18.04, shell: bash } + - { name: 'Windows MSVC', os: windows-latest, compiler: msvc } + - { name: 'Windows MinGW', os: ubuntu-20.04, compiler: mingw } + - { name: 'macOS', os: macos-latest } + - { name: 'Ubuntu', os: ubuntu-18.04 } fail-fast: false name: "Package: ${{ matrix.config.name }}" runs-on: ${{ matrix.config.os }} - defaults: - run: - shell: ${{ matrix.config.shell }} steps: - name: Checkout @@ -134,20 +123,16 @@ jobs: submodules: recursive - name: Setup Toolchain [Windows MSVC] - if: ${{ runner.os == 'Windows' && matrix.config.compiler == 'msvc' }} + if: ${{ matrix.config.compiler == 'msvc' }} uses: seanmiddleditch/gha-setup-vsdevenv@v3 - name: Setup Toolchain [Windows MinGW] - if: ${{ runner.os == 'Windows' && matrix.config.compiler == 'mingw' }} - uses: msys2/setup-msys2@v2 - with: - msystem: MINGW64 - update: true - install: | - mingw-w64-x86_64-toolchain - mingw-w64-x86_64-cmake - make - p7zip + if: ${{ matrix.config.compiler == 'mingw' }} + run: | + sudo apt update + sudo apt install \ + mingw-w64 \ + mingw-w64-tools - name: Install Dependencies [macOS] if: ${{ runner.os == 'macOS' }} @@ -162,7 +147,7 @@ jobs: jack - name: Install Dependencies [Ubuntu] - if: ${{ runner.os == 'Linux' }} + if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' }} run: | sudo apt update sudo apt install \ @@ -179,7 +164,7 @@ jobs: run: | package_name="furnace-${GITHUB_SHA}" package_ext="" - if [ '${{ runner.os }}' == 'Windows' ]; then + if [ '${{ runner.os }}' == 'Windows' ] || [ '${{ matrix.config.compiler }}' == 'mingw' ]; then package_name="${package_name}-Windows" if [ '${{ matrix.config.compiler }}' == 'mingw' ]; then package_name="${package_name}-MinGW" @@ -205,28 +190,26 @@ jobs: run: | export USE_WAE=ON export CMAKE_EXTRA_ARGS=() - if [ '${{ runner.os }}' == 'Windows' ]; then - if [ '${{ matrix.config.compiler }}' == 'mingw' ]; then - CMAKE_EXTRA_ARGS+=('-G' 'MSYS Makefiles') - elif [ '${{ matrix.config.compiler }}' == 'msvc' ]; then - # FIXME We don't want all the MSVC warnings to cause errors yet - export USE_WAE=OFF + if [ '${{ matrix.config.compiler }}' == 'msvc' ]; then + # FIXME We don't want all the MSVC warnings to cause errors yet + export USE_WAE=OFF - # Force static linking - # 1. Make MSVC runtime configurable - CMAKE_EXTRA_ARGS+=('-DCMAKE_POLICY_DEFAULT_CMP0091=NEW') - # 2. Use static (debug) runtime - if [ '${{ env.BUILD_TYPE }}' == 'Debug' ]; then - CMAKE_EXTRA_ARGS+=('-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDebug') - else - CMAKE_EXTRA_ARGS+=('-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded') - fi - - # Fix SDL static linking (see linked issues in patch file) - pushd extern/SDL - env EMAIL=root@localhost git am ../SDL-Fix-MSVC-static-runtime-linking.patch - popd + # Force static linking + # 1. Make MSVC runtime configurable + CMAKE_EXTRA_ARGS+=('-DCMAKE_POLICY_DEFAULT_CMP0091=NEW') + # 2. Use static (debug) runtime + if [ '${{ env.BUILD_TYPE }}' == 'Debug' ]; then + CMAKE_EXTRA_ARGS+=('-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreadedDebug') + else + CMAKE_EXTRA_ARGS+=('-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded') fi + + # Fix SDL static linking (see linked issues in patch file) + pushd extern/SDL + env EMAIL=root@localhost git am ../SDL-Fix-MSVC-static-runtime-linking.patch + popd + elif [ '${{ matrix.config.compiler }}' == 'mingw' ]; then + CMAKE_EXTRA_ARGS+=('-DCMAKE_TOOLCHAIN_FILE=scripts/Cross-MinGW-x86.cmake') elif [ '${{ runner.os }}' == 'macOS' ]; then CMAKE_EXTRA_ARGS+=('-DCMAKE_OSX_DEPLOYMENT_TARGET="10.9"') fi @@ -247,14 +230,14 @@ jobs: --parallel 2 - name: Package [Windows] - if: ${{ runner.os == 'Windows' }} + if: ${{ runner.os == 'Windows' || matrix.config.compiler == 'mingw' }} run: | binPath=build if [ '${{ matrix.config.compiler }}' == 'msvc' ]; then binPath="${binPath}/${{ env.BUILD_TYPE }}" fi - if [ '${{ matrix.config.compiler }}' == 'mingw' ]; then - strip -s "${binPath}/furnace.exe" + if [ '${{ matrix.config.compiler }}' == 'mingw' ] && [ '${{ env.BUILD_TYPE }}' == 'Release' ]; then + i686-w64-mingw32-strip -s "${binPath}/furnace.exe" fi mkdir target @@ -276,9 +259,11 @@ jobs: popd - name: Package [Ubuntu] - if: ${{ runner.os == 'Linux' }} + if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' }} run: | - strip -s build/furnace + if [ '${{ env.BUILD_TYPE }}' == 'Release' ]; then + strip -s build/furnace + fi mkdir -p target/furnace.AppDir make -C ${PWD}/build DESTDIR=${PWD}/target/furnace.AppDir install From 8483f853973349614282355d99f7dbd314a76b43 Mon Sep 17 00:00:00 2001 From: OPNA2608 Date: Sat, 2 Apr 2022 16:48:05 +0200 Subject: [PATCH 568/637] Modularise Windows arch on CI --- .github/workflows/build.yml | 81 ++++++++++++++++++++++++++++++++----- 1 file changed, 71 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7d3dd84f5..db59071ab 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,6 +12,7 @@ defaults: env: BUILD_TYPE: Release + WINDOWS_ARCH: x86 jobs: # Do a plain build, like we would expect a user to do it on their end. @@ -34,9 +35,37 @@ jobs: with: submodules: recursive + - name: Set Windows arch identifiers + id: windows-identify + if: ${{ matrix.config.compiler == 'msvc' || matrix.config.compiler == 'mingw' }} + run: | + vswhere_target="${{ env.WINDOWS_ARCH }}" + msvc_target="${{ env.WINDOWS_ARCH }}" + mingw_target="${{ env.WINDOWS_ARCH }}" + + if [ '${{ env.WINDOWS_ARCH }}' == 'x86' ]; then + msvc_target="Win32" + elif [ '${{ env.WINDOWS_ARCH }}' == 'x86_64' ]; then + vswhere_target="amd64" + msvc_target="x64" + fi + + if [ '${{ matrix.config.compiler }}' == 'msvc' ]; then + echo "vswhere target: ${vswhere_target}" + echo "MSVC target: ${msvc_target}" + else + echo "MinGW cross target: ${mingw_target}" + fi + + echo "::set-output name=vswhere-target::${vswhere_target}" + echo "::set-output name=msvc-target::${msvc_target}" + echo "::set-output name=mingw-target::${mingw_target}" + - name: Setup Toolchain [Windows MSVC] if: ${{ matrix.config.compiler == 'msvc' }} uses: seanmiddleditch/gha-setup-vsdevenv@v3 + with: + arch: ${{ steps.windows-identify.outputs.vswhere-target }} - name: Setup Toolchain [Windows MinGW] if: ${{ matrix.config.compiler == 'mingw' }} @@ -73,10 +102,12 @@ jobs: export USE_WAE=ON export CMAKE_EXTRA_ARGS=() if [ '${{ matrix.config.compiler }}' == 'msvc' ]; then + CMAKE_EXTRA_ARGS+=('-DCMAKE_GENERATOR_PLATFORM=${{ steps.windows-identify.outputs.msvc-target }}') + # FIXME We don't want all the MSVC warnings to cause errors yet export USE_WAE=OFF elif [ '${{ matrix.config.compiler }}' == 'mingw' ]; then - CMAKE_EXTRA_ARGS+=('-DCMAKE_TOOLCHAIN_FILE=scripts/Cross-MinGW-x86.cmake') + CMAKE_EXTRA_ARGS+=('-DCMAKE_TOOLCHAIN_FILE=scripts/Cross-MinGW-${{ steps.windows-identify.outputs.mingw-target }}.cmake') fi cmake \ @@ -122,9 +153,37 @@ jobs: with: submodules: recursive + - name: Set Windows arch identifiers + id: windows-identify + if: ${{ matrix.config.compiler == 'msvc' || matrix.config.compiler == 'mingw' }} + run: | + vswhere_target="${{ env.WINDOWS_ARCH }}" + msvc_target="${{ env.WINDOWS_ARCH }}" + mingw_target="${{ env.WINDOWS_ARCH }}" + + if [ '${{ env.WINDOWS_ARCH }}' == 'x86' ]; then + msvc_target="Win32" + elif [ '${{ env.WINDOWS_ARCH }}' == 'x86_64' ]; then + vswhere_target="amd64" + msvc_target="x64" + fi + + if [ '${{ matrix.config.compiler }}' == 'msvc' ]; then + echo "vswhere target: ${vswhere_target}" + echo "MSVC target: ${msvc_target}" + else + echo "MinGW cross target: ${mingw_target}" + fi + + echo "::set-output name=vswhere-target::${vswhere_target}" + echo "::set-output name=msvc-target::${msvc_target}" + echo "::set-output name=mingw-target::${mingw_target}" + - name: Setup Toolchain [Windows MSVC] if: ${{ matrix.config.compiler == 'msvc' }} uses: seanmiddleditch/gha-setup-vsdevenv@v3 + with: + arch: ${{ steps.windows-identify.outputs.vswhere-target }} - name: Setup Toolchain [Windows MinGW] if: ${{ matrix.config.compiler == 'mingw' }} @@ -159,8 +218,8 @@ jobs: wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage" chmod +x appimagetool-x86_64.AppImage - - name: Set package identity - id: package-identity + - name: Set package identifier + id: package-identify run: | package_name="furnace-${GITHUB_SHA}" package_ext="" @@ -180,7 +239,7 @@ jobs: package_ext=".AppImage" fi - echo "Package identity: ${package_name}" + echo "Package identifier: ${package_name}" echo "Package file: ${package_name}${package_ext}" echo "::set-output name=id::${package_name}" @@ -191,6 +250,8 @@ jobs: export USE_WAE=ON export CMAKE_EXTRA_ARGS=() if [ '${{ matrix.config.compiler }}' == 'msvc' ]; then + CMAKE_EXTRA_ARGS+=('-DCMAKE_GENERATOR_PLATFORM=${{ steps.windows-identify.outputs.msvc-target }}') + # FIXME We don't want all the MSVC warnings to cause errors yet export USE_WAE=OFF @@ -209,7 +270,7 @@ jobs: env EMAIL=root@localhost git am ../SDL-Fix-MSVC-static-runtime-linking.patch popd elif [ '${{ matrix.config.compiler }}' == 'mingw' ]; then - CMAKE_EXTRA_ARGS+=('-DCMAKE_TOOLCHAIN_FILE=scripts/Cross-MinGW-x86.cmake') + CMAKE_EXTRA_ARGS+=('-DCMAKE_TOOLCHAIN_FILE=scripts/Cross-MinGW-${{ steps.windows-identify.outputs.mingw-target }}.cmake') elif [ '${{ runner.os }}' == 'macOS' ]; then CMAKE_EXTRA_ARGS+=('-DCMAKE_OSX_DEPLOYMENT_TARGET="10.9"') fi @@ -247,7 +308,7 @@ jobs: cp -v ../README.md README.txt cp -vr ../{papers,demos} ../${binPath}/furnace.exe ./ - 7z a -tzip ../${{ steps.package-identity.outputs.filename }} * + 7z a -tzip ../${{ steps.package-identify.outputs.filename }} * popd - name: Package [macOS] @@ -255,7 +316,7 @@ jobs: run: | pushd build cpack - mv Furnace-*-Darwin.dmg ../${{ steps.package-identity.outputs.filename }} + mv Furnace-*-Darwin.dmg ../${{ steps.package-identify.outputs.filename }} popd - name: Package [Ubuntu] @@ -277,11 +338,11 @@ jobs: popd ../appimagetool-x86_64.AppImage furnace.AppDir - mv Furnace-*.AppImage ../${{ steps.package-identity.outputs.filename }} + mv Furnace-*.AppImage ../${{ steps.package-identify.outputs.filename }} popd - name: Upload artifact uses: actions/upload-artifact@v3 with: - name: ${{ steps.package-identity.outputs.id }} - path: ${{ steps.package-identity.outputs.filename }} + name: ${{ steps.package-identify.outputs.id }} + path: ${{ steps.package-identify.outputs.filename }} From 54a36c495055720eb5320b42eb77872b6c5706cb Mon Sep 17 00:00:00 2001 From: OPNA2608 Date: Sat, 2 Apr 2022 17:36:04 +0200 Subject: [PATCH 569/637] Fix SDL static linking --- .github/workflows/build.yml | 5 --- CMakeLists.txt | 9 +++-- .../SDL-Fix-MSVC-static-runtime-linking.patch | 35 ------------------- 3 files changed, 7 insertions(+), 42 deletions(-) delete mode 100644 extern/SDL-Fix-MSVC-static-runtime-linking.patch diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index db59071ab..3fd8509f3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -264,11 +264,6 @@ jobs: else CMAKE_EXTRA_ARGS+=('-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded') fi - - # Fix SDL static linking (see linked issues in patch file) - pushd extern/SDL - env EMAIL=root@localhost git am ../SDL-Fix-MSVC-static-runtime-linking.patch - popd elif [ '${{ matrix.config.compiler }}' == 'mingw' ]; then CMAKE_EXTRA_ARGS+=('-DCMAKE_TOOLCHAIN_FILE=scripts/Cross-MinGW-${{ steps.windows-identify.outputs.mingw-target }}.cmake') elif [ '${{ runner.os }}' == 'macOS' ]; then diff --git a/CMakeLists.txt b/CMakeLists.txt index c5fc06e70..769842f33 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -167,8 +167,13 @@ if (SYSTEM_SDL2) endif() message(STATUS "Using system-installed SDL2") else() - set(SDL_SHARED OFF) - set(SDL_STATIC ON) + set(SDL_SHARED OFF CACHE BOOL "Force no dynamically-linked SDL" FORCE) + set(SDL_STATIC ON CACHE BOOL "Force statically-linked SDL" FORCE) + # https://github.com/libsdl-org/SDL/issues/1481 + # On 2014-06-22 17:15:50 +0000, Sam Lantinga wrote: + # If you link SDL statically, you also need to define HAVE_LIBC so it builds with the C runtime that your application uses. + # This should probably go in a FAQ. + set(SDL_LIBC ON CACHE BOOL "Tell SDL that we want it to use our C runtime (required for proper static linking)" FORCE) add_subdirectory(extern/SDL EXCLUDE_FROM_ALL) list(APPEND DEPENDENCIES_INCLUDE_DIRS extern/SDL/include) list(APPEND DEPENDENCIES_LIBRARIES SDL2-static) diff --git a/extern/SDL-Fix-MSVC-static-runtime-linking.patch b/extern/SDL-Fix-MSVC-static-runtime-linking.patch deleted file mode 100644 index 31b9ea619..000000000 --- a/extern/SDL-Fix-MSVC-static-runtime-linking.patch +++ /dev/null @@ -1,35 +0,0 @@ -From 34d3dcd98697af081625ccea7a41a3c47806c4ff Mon Sep 17 00:00:00 2001 -From: OPNA2608 -Date: Mon, 28 Mar 2022 16:43:16 +0200 -Subject: [PATCH] SDL: Fix MSVC static runtime linking - -See https://github.com/libsdl-org/SDL/issues/3662, https://github.com/libsdl-org/SDL/issues/4258. ---- - src/stdlib/SDL_stdlib.c | 4 ++-- - 1 file changed, 2 insertions(+), 2 deletions(-) - -diff --git a/src/stdlib/SDL_stdlib.c b/src/stdlib/SDL_stdlib.c -index 9d785aad5..dfac4d7c3 100644 ---- a/src/stdlib/SDL_stdlib.c -+++ b/src/stdlib/SDL_stdlib.c -@@ -550,7 +550,7 @@ __declspec(selectany) int _fltused = 1; - #endif - - /* The optimizer on Visual Studio 2005 and later generates memcpy() and memset() calls */ --#if _MSC_VER >= 1400 -+#if (_MSC_VER >= 1400) && !defined(_MT) - extern void *memcpy(void* dst, const void* src, size_t len); - #pragma intrinsic(memcpy) - -@@ -570,7 +570,7 @@ memset(void *dst, int c, size_t len) - { - return SDL_memset(dst, c, len); - } --#endif /* _MSC_VER >= 1400 */ -+#endif /* (_MSC_VER >= 1400) && !defined(_MT) */ - - #ifdef _M_IX86 - --- -2.33.1 - From fa1e3ea3e1029918e08fdb2bd8c1ce4f2c8c7765 Mon Sep 17 00:00:00 2001 From: OPNA2608 Date: Sat, 2 Apr 2022 18:29:37 +0200 Subject: [PATCH 570/637] Fix zip-in-zip artifact upload for Windows --- .github/workflows/build.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3fd8509f3..bbf80788b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -230,7 +230,7 @@ jobs: else package_name="${package_name}-MSVC" fi - package_ext=".zip" + package_ext="" # Directory, uploading will automatically zip it elif [ '${{ runner.os }}' == 'macOS' ]; then package_name="${package_name}-macOS" package_ext=".dmg" @@ -293,17 +293,17 @@ jobs: binPath="${binPath}/${{ env.BUILD_TYPE }}" fi if [ '${{ matrix.config.compiler }}' == 'mingw' ] && [ '${{ env.BUILD_TYPE }}' == 'Release' ]; then + # FIXME arch-specific strip prefix i686-w64-mingw32-strip -s "${binPath}/furnace.exe" fi - mkdir target - pushd target + mkdir ${{ steps.package-identify.outputs.filename }} + pushd ${{ steps.package-identify.outputs.filename }} cp -v ../LICENSE LICENSE.txt cp -v ../README.md README.txt cp -vr ../{papers,demos} ../${binPath}/furnace.exe ./ - 7z a -tzip ../${{ steps.package-identify.outputs.filename }} * popd - name: Package [macOS] From 28682b79d139d9514556430ee367ca77ca4a5d36 Mon Sep 17 00:00:00 2001 From: OPNA2608 Date: Sun, 3 Apr 2022 20:57:17 +0200 Subject: [PATCH 571/637] Build with system libs in first run --- .github/workflows/build.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index bbf80788b..6e20265d3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -83,6 +83,8 @@ jobs: brew install \ pkg-config \ sdl2 \ + fmt \ + rtmidi \ libsndfile \ zlib \ jack @@ -93,6 +95,8 @@ jobs: sudo apt update sudo apt install \ libsdl2-dev \ + libfmt-dev \ + librtmidi-dev \ libsndfile1-dev \ zlib1g-dev \ libjack-jackd2-dev @@ -108,6 +112,19 @@ jobs: export USE_WAE=OFF elif [ '${{ matrix.config.compiler }}' == 'mingw' ]; then CMAKE_EXTRA_ARGS+=('-DCMAKE_TOOLCHAIN_FILE=scripts/Cross-MinGW-${{ steps.windows-identify.outputs.mingw-target }}.cmake') + else + # Test with system libs + CMAKE_EXTRA_ARGS+=( + '-DSYSTEM_FMT=ON' + '-DSYSTEM_LIBSNDFILE=ON' + '-DSYSTEM_RTMIDI=ON' + '-DSYSTEM_ZLIB=ON' + '-DWITH_JACK=ON' + ) + # Too old on Ubuntu + if [ '${{ runner.os }}' == 'macOS' ]; then + CMAKE_EXTRA_ARGS+=('-DSYSTEM_SDL2=ON') + fi fi cmake \ @@ -201,6 +218,8 @@ jobs: brew install \ pkg-config \ sdl2 \ + fmt \ + rtmidi \ libsndfile \ zlib \ jack @@ -211,6 +230,8 @@ jobs: sudo apt update sudo apt install \ libsdl2-dev \ + libfmt-dev \ + librtmidi-dev \ libsndfile1-dev \ zlib1g-dev \ libjack-jackd2-dev \ From 759b5ab7d111a2deabfc1650981ad053f1b28a26 Mon Sep 17 00:00:00 2001 From: OPNA2608 Date: Thu, 7 Apr 2022 19:25:27 +0200 Subject: [PATCH 572/637] Don't install brew dependencies in package CI run --- .github/workflows/build.yml | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6e20265d3..3ab546e7a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -210,20 +210,6 @@ jobs: mingw-w64 \ mingw-w64-tools - - name: Install Dependencies [macOS] - if: ${{ runner.os == 'macOS' }} - run: | - export HOMEBREW_NO_INSTALL_CLEANUP=1 - brew update - brew install \ - pkg-config \ - sdl2 \ - fmt \ - rtmidi \ - libsndfile \ - zlib \ - jack - - name: Install Dependencies [Ubuntu] if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' }} run: | From 3fbcc6be578059c394f8ff2eeb89cedb36c076b3 Mon Sep 17 00:00:00 2001 From: OPNA2608 Date: Fri, 8 Apr 2022 20:37:08 +0200 Subject: [PATCH 573/637] Build x86 & x86_64 on Windows CI --- .github/workflows/build.yml | 45 +++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3ab546e7a..a16439aa3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,7 +12,6 @@ defaults: env: BUILD_TYPE: Release - WINDOWS_ARCH: x86 jobs: # Do a plain build, like we would expect a user to do it on their end. @@ -20,8 +19,10 @@ jobs: strategy: matrix: config: - - { name: 'Windows MSVC', os: windows-latest, compiler: msvc } - - { name: 'Windows MinGW', os: ubuntu-20.04, compiler: mingw } + - { name: 'Windows MSVC x86', os: windows-latest, compiler: msvc, arch: x86 } + - { name: 'Windows MSVC x86_64', os: windows-latest, compiler: msvc, arch: x86_64 } + - { name: 'Windows MinGW x86', os: ubuntu-20.04, compiler: mingw, arch: x86 } + - { name: 'Windows MinGW x86_64', os: ubuntu-20.04, compiler: mingw, arch: x86_64 } - { name: 'macOS', os: macos-latest } - { name: 'Ubuntu', os: ubuntu-18.04 } fail-fast: false @@ -39,13 +40,13 @@ jobs: id: windows-identify if: ${{ matrix.config.compiler == 'msvc' || matrix.config.compiler == 'mingw' }} run: | - vswhere_target="${{ env.WINDOWS_ARCH }}" - msvc_target="${{ env.WINDOWS_ARCH }}" - mingw_target="${{ env.WINDOWS_ARCH }}" + vswhere_target="${{ matrix.config.arch }}" + msvc_target="${{ matrix.config.arch }}" + mingw_target="${{ matrix.config.arch }}" - if [ '${{ env.WINDOWS_ARCH }}' == 'x86' ]; then + if [ '${{ matrix.config.arch }}' == 'x86' ]; then msvc_target="Win32" - elif [ '${{ env.WINDOWS_ARCH }}' == 'x86_64' ]; then + elif [ '${{ matrix.config.arch }}' == 'x86_64' ]; then vswhere_target="amd64" msvc_target="x64" fi @@ -155,8 +156,10 @@ jobs: strategy: matrix: config: - - { name: 'Windows MSVC', os: windows-latest, compiler: msvc } - - { name: 'Windows MinGW', os: ubuntu-20.04, compiler: mingw } + - { name: 'Windows MSVC x86', os: windows-latest, compiler: msvc, arch: x86 } + - { name: 'Windows MSVC x86_64', os: windows-latest, compiler: msvc, arch: x86_64 } + - { name: 'Windows MinGW x86', os: ubuntu-20.04, compiler: mingw, arch: x86 } + - { name: 'Windows MinGW x86_64', os: ubuntu-20.04, compiler: mingw, arch: x86_64 } - { name: 'macOS', os: macos-latest } - { name: 'Ubuntu', os: ubuntu-18.04 } fail-fast: false @@ -174,13 +177,13 @@ jobs: id: windows-identify if: ${{ matrix.config.compiler == 'msvc' || matrix.config.compiler == 'mingw' }} run: | - vswhere_target="${{ env.WINDOWS_ARCH }}" - msvc_target="${{ env.WINDOWS_ARCH }}" - mingw_target="${{ env.WINDOWS_ARCH }}" + vswhere_target="${{ matrix.config.arch }}" + msvc_target="${{ matrix.config.arch }}" + mingw_target="${{ matrix.config.arch }}" - if [ '${{ env.WINDOWS_ARCH }}' == 'x86' ]; then + if [ '${{ matrix.config.arch }}' == 'x86' ]; then msvc_target="Win32" - elif [ '${{ env.WINDOWS_ARCH }}' == 'x86_64' ]; then + elif [ '${{ matrix.config.arch }}' == 'x86_64' ]; then vswhere_target="amd64" msvc_target="x64" fi @@ -237,6 +240,7 @@ jobs: else package_name="${package_name}-MSVC" fi + package_name="${package_name}-${{ matrix.config.arch }}" package_ext="" # Directory, uploading will automatically zip it elif [ '${{ runner.os }}' == 'macOS' ]; then package_name="${package_name}-macOS" @@ -300,8 +304,15 @@ jobs: binPath="${binPath}/${{ env.BUILD_TYPE }}" fi if [ '${{ matrix.config.compiler }}' == 'mingw' ] && [ '${{ env.BUILD_TYPE }}' == 'Release' ]; then - # FIXME arch-specific strip prefix - i686-w64-mingw32-strip -s "${binPath}/furnace.exe" + # arch-specific strip prefix + # TODO maybe extract from cross toolchain files? + toolPrefix="-w64-mingw32-" + if [ '${{ matrix.config.arch }}' == 'x86_64' ]; then + toolPrefix="x86_64${toolPrefix}" + else + toolPrefix="i686${toolPrefix}" + fi + ${toolPrefix}strip -s "${binPath}/furnace.exe" fi mkdir ${{ steps.package-identify.outputs.filename }} From 5474f6899fa9291bd2f6221b5dd9b139cfae6af1 Mon Sep 17 00:00:00 2001 From: OPNA2608 Date: Fri, 8 Apr 2022 21:13:15 +0200 Subject: [PATCH 574/637] Merge CI phases --- .github/workflows/build.yml | 153 ++++++++++-------------------------- 1 file changed, 40 insertions(+), 113 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a16439aa3..94de2b50f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,7 +14,6 @@ env: BUILD_TYPE: Release jobs: - # Do a plain build, like we would expect a user to do it on their end. build: strategy: matrix: @@ -27,7 +26,7 @@ jobs: - { name: 'Ubuntu', os: ubuntu-18.04 } fail-fast: false - name: "Build: ${{ matrix.config.name }}" + name: "Test: ${{ matrix.config.name }}" runs-on: ${{ matrix.config.os }} steps: @@ -62,6 +61,34 @@ jobs: echo "::set-output name=msvc-target::${msvc_target}" echo "::set-output name=mingw-target::${mingw_target}" + - name: Set package identifier + id: package-identify + run: | + package_name="furnace-${GITHUB_SHA}" + package_ext="" + if [ '${{ runner.os }}' == 'Windows' ] || [ '${{ matrix.config.compiler }}' == 'mingw' ]; then + package_name="${package_name}-Windows" + if [ '${{ matrix.config.compiler }}' == 'mingw' ]; then + package_name="${package_name}-MinGW" + else + package_name="${package_name}-MSVC" + fi + package_name="${package_name}-${{ matrix.config.arch }}" + package_ext="" # Directory, uploading will automatically zip it + elif [ '${{ runner.os }}' == 'macOS' ]; then + package_name="${package_name}-macOS" + package_ext=".dmg" + else + package_name="${package_name}-Linux" + package_ext=".AppImage" + fi + + echo "Package identifier: ${package_name}" + echo "Package file: ${package_name}${package_ext}" + + echo "::set-output name=id::${package_name}" + echo "::set-output name=filename::${package_name}${package_ext}" + - name: Setup Toolchain [Windows MSVC] if: ${{ matrix.config.compiler == 'msvc' }} uses: seanmiddleditch/gha-setup-vsdevenv@v3 @@ -100,9 +127,12 @@ jobs: librtmidi-dev \ libsndfile1-dev \ zlib1g-dev \ - libjack-jackd2-dev + libjack-jackd2-dev \ + appstream + wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage" + chmod +x appimagetool-x86_64.AppImage - - name: Configure + - name: Configure (Normal) run: | export USE_WAE=ON export CMAKE_EXTRA_ARGS=() @@ -135,7 +165,7 @@ jobs: -DWARNINGS_ARE_ERRORS=${USE_WAE} \ "${CMAKE_EXTRA_ARGS[@]}" - - name: Build + - name: Build (Normal) run: | export VERBOSE=1 cmake \ @@ -143,120 +173,17 @@ jobs: --config ${{ env.BUILD_TYPE }} \ --parallel 2 - - name: Install + - name: Install (Normal) run: | cmake \ --install ${PWD}/build \ --config ${{ env.BUILD_TYPE }} - # Now do a build that we can package. (static runtime linking on MSVC, zipping on Windows, CPack on Darwin, appimage stuff on Ubuntu) - # We rebuild here because the build dependencies may be slightly different and might trigger a full rebuild anyway. - package: - needs: build - strategy: - matrix: - config: - - { name: 'Windows MSVC x86', os: windows-latest, compiler: msvc, arch: x86 } - - { name: 'Windows MSVC x86_64', os: windows-latest, compiler: msvc, arch: x86_64 } - - { name: 'Windows MinGW x86', os: ubuntu-20.04, compiler: mingw, arch: x86 } - - { name: 'Windows MinGW x86_64', os: ubuntu-20.04, compiler: mingw, arch: x86_64 } - - { name: 'macOS', os: macos-latest } - - { name: 'Ubuntu', os: ubuntu-18.04 } - fail-fast: false - - name: "Package: ${{ matrix.config.name }}" - runs-on: ${{ matrix.config.os }} - - steps: - - name: Checkout - uses: actions/checkout@v2 - with: - submodules: recursive - - - name: Set Windows arch identifiers - id: windows-identify - if: ${{ matrix.config.compiler == 'msvc' || matrix.config.compiler == 'mingw' }} + - name: Cleanup (Normal) run: | - vswhere_target="${{ matrix.config.arch }}" - msvc_target="${{ matrix.config.arch }}" - mingw_target="${{ matrix.config.arch }}" + rm -rf build/ target/ - if [ '${{ matrix.config.arch }}' == 'x86' ]; then - msvc_target="Win32" - elif [ '${{ matrix.config.arch }}' == 'x86_64' ]; then - vswhere_target="amd64" - msvc_target="x64" - fi - - if [ '${{ matrix.config.compiler }}' == 'msvc' ]; then - echo "vswhere target: ${vswhere_target}" - echo "MSVC target: ${msvc_target}" - else - echo "MinGW cross target: ${mingw_target}" - fi - - echo "::set-output name=vswhere-target::${vswhere_target}" - echo "::set-output name=msvc-target::${msvc_target}" - echo "::set-output name=mingw-target::${mingw_target}" - - - name: Setup Toolchain [Windows MSVC] - if: ${{ matrix.config.compiler == 'msvc' }} - uses: seanmiddleditch/gha-setup-vsdevenv@v3 - with: - arch: ${{ steps.windows-identify.outputs.vswhere-target }} - - - name: Setup Toolchain [Windows MinGW] - if: ${{ matrix.config.compiler == 'mingw' }} - run: | - sudo apt update - sudo apt install \ - mingw-w64 \ - mingw-w64-tools - - - name: Install Dependencies [Ubuntu] - if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' }} - run: | - sudo apt update - sudo apt install \ - libsdl2-dev \ - libfmt-dev \ - librtmidi-dev \ - libsndfile1-dev \ - zlib1g-dev \ - libjack-jackd2-dev \ - appstream - wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage" - chmod +x appimagetool-x86_64.AppImage - - - name: Set package identifier - id: package-identify - run: | - package_name="furnace-${GITHUB_SHA}" - package_ext="" - if [ '${{ runner.os }}' == 'Windows' ] || [ '${{ matrix.config.compiler }}' == 'mingw' ]; then - package_name="${package_name}-Windows" - if [ '${{ matrix.config.compiler }}' == 'mingw' ]; then - package_name="${package_name}-MinGW" - else - package_name="${package_name}-MSVC" - fi - package_name="${package_name}-${{ matrix.config.arch }}" - package_ext="" # Directory, uploading will automatically zip it - elif [ '${{ runner.os }}' == 'macOS' ]; then - package_name="${package_name}-macOS" - package_ext=".dmg" - else - package_name="${package_name}-Linux" - package_ext=".AppImage" - fi - - echo "Package identifier: ${package_name}" - echo "Package file: ${package_name}${package_ext}" - - echo "::set-output name=id::${package_name}" - echo "::set-output name=filename::${package_name}${package_ext}" - - - name: Configure + - name: Configure (Package) run: | export USE_WAE=ON export CMAKE_EXTRA_ARGS=() @@ -288,7 +215,7 @@ jobs: -DWARNINGS_ARE_ERRORS=${USE_WAE} \ "${CMAKE_EXTRA_ARGS[@]}" - - name: Build + - name: Build (Package) run: | export VERBOSE=1 cmake \ From 89455959fc7caffa589c595a69d2101333926613 Mon Sep 17 00:00:00 2001 From: freq-mod <32672779+freq-mod@users.noreply.github.com> Date: Fri, 8 Apr 2022 22:34:13 +0200 Subject: [PATCH 575/637] slightly improve fds docs --- papers/doc/7-systems/fds.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/papers/doc/7-systems/fds.md b/papers/doc/7-systems/fds.md index ee2881981..1174b3f3b 100644 --- a/papers/doc/7-systems/fds.md +++ b/papers/doc/7-systems/fds.md @@ -3,7 +3,7 @@ the Famicom Disk System is an expansion device for the Famicom (known as NES outside Japan), a popular console from the '80's. as it name implies, it allowed people to play games on specialized floppy disks that could be rewritten on vending machines, therefore reducing the cost of ownership and manufacturing. -it also offers an additional wavetable sound channel with (somewhat limited) FM capabilities, which is what Furnace supports. +it also offers an additional 6-bit, 64-byte wavetable sound channel with (somewhat limited) FM capabilities, which is what Furnace supports. # effects From e10a410cf1a13a3600b66ef1a73e9c2131d2830c Mon Sep 17 00:00:00 2001 From: freq-mod <32672779+freq-mod@users.noreply.github.com> Date: Fri, 8 Apr 2022 22:59:51 +0200 Subject: [PATCH 576/637] some docs improvemets --- papers/doc/7-systems/opll.md | 5 +++ papers/doc/7-systems/saa1099.md | 5 ++- papers/doc/7-systems/vrc6.md | 4 +- papers/doc/effects.md | 66 +++++++++++++++++++++++++++++++++ 4 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 papers/doc/effects.md diff --git a/papers/doc/7-systems/opll.md b/papers/doc/7-systems/opll.md index 06f542f16..2e92171ba 100644 --- a/papers/doc/7-systems/opll.md +++ b/papers/doc/7-systems/opll.md @@ -2,6 +2,11 @@ the YM2413, otherwise known as OPLL, is a cost-reduced FM synthesis sound chip, based on the Yamaha YM3812 (OPL2). thought OPL was downgraded enough? :p +OPLL spawned also a few derivative chips, the best known of these is: +- the myth. the legend. THE VRC7. 6 channels, *rather interesting* instruments sound bank, no drums mode +- Yamaha YM2423, same chip as YM2413, just a different patch set +- Yamaha YMF281, ditto + # technical specifications the YM2413 is equipped with the following features: diff --git a/papers/doc/7-systems/saa1099.md b/papers/doc/7-systems/saa1099.md index 093eb603e..0cb19fbd3 100644 --- a/papers/doc/7-systems/saa1099.md +++ b/papers/doc/7-systems/saa1099.md @@ -1,6 +1,9 @@ # Philips SAA1099 -this was used by the Game Blaster and SAM Coupé. it's pretty similar to the AY-3-8910, but has stereo sound, twice the channels and two envelopes, both of which are highly flexible. +this was used by the Game Blaster and SAM Coupé. it's pretty similar to the AY-3-8910, stereo sound, twice the channels and two envelopes, both of which are highly flexible. The envelopes work like this: + +an instrument with envelope settings is placed on channel 2 or channel 5 +an instrument that is used as an "envelope output", is placed on channel 3 or channel 6. You may want to disable wave output on the output channel. # effects diff --git a/papers/doc/7-systems/vrc6.md b/papers/doc/7-systems/vrc6.md index 63bda1ab7..c89678b47 100644 --- a/papers/doc/7-systems/vrc6.md +++ b/papers/doc/7-systems/vrc6.md @@ -5,6 +5,8 @@ the most popular expansion chip to the NES' sound system. the chip has 2 pulse wave channels and one sawtooth channel. volume register is 4 bit for pulse wave and 6 bit for sawtooth, but sawtooth output is corrupted when volume register value is too high. because this register is actually an 8 bit accumulator, its output may wrap around. +For that reason, the sawtooth channel has it's own instrument type. Setting volume macro and pattern editor volume setting too high (above 42/2A) will distort the waveform. + pulse wave duty cycle is 8-level. it can be ignored and it has potential for DAC at this case: volume register in this mode is DAC output and it can be PCM playback through this mode. Furnace supports this routine for PCM playback, but it consumes a lot of CPU time in real hardware (even if conjunction with VRC6's integrated IRQ timer). @@ -13,4 +15,4 @@ Furnace supports this routine for PCM playback, but it consumes a lot of CPU tim these effects only are effective in the pulse channels. - `12xx`: set duty cycle (0 to 7). -- `17xx`: toggle PCM mode. \ No newline at end of file +- `17xx`: toggle PCM mode. diff --git a/papers/doc/effects.md b/papers/doc/effects.md new file mode 100644 index 000000000..773d30373 --- /dev/null +++ b/papers/doc/effects.md @@ -0,0 +1,66 @@ +# effect list + +most of the effect numbers are from ProTracker/FastTracker 2. +however, effects are continuous, which means you only need to type it once and then stop it with an effect value of `00`. + +- `00xy`: arpeggio. after using this effect the channel will rapidly switch between `note`, `note+x` and `note+y`. +- `01xx`: slide up. +- `02xx`: slide down. +- `03xx`: note portamento. + - 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. +- `0Axy`: volume slide. + - if `x` is 0 then this is a slide down. + - if `y` is 0 then this is a slide up. +- `0Bxx`: jump to pattern. +- `0Cxx`: retrigger note every `xx` ticks. + - this effect is not continuous. +- `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`. + +- `E0xx`: set arpeggio tick. + - this sets the number of ticks between arpeggio values. +- `E1xy`: note slide up. `x` is the speed, while `y` is how many semitones to slide up. +- `E2xy`: note slide down. `x` is the speed, while `y` is how many semitones to slide down. +- `E3xx`: set vibrato direction. `xx` may be one of the following: + - `00`: up and down. + - `01`: up only. + - `02`: down only. +- `E4xx`: set vibrato range in 1/16th of a semitone. +- `E5xx`: set pitch. `80` is 0 cents. + - range is ±1 semitone. +- `EAxx`: toggle legato. +- `EBxx`: set sample bank. + - does not apply on Amiga. +- `ECxx`: note off after `xx` ticks. +- `EDxx`: delay note by `xx` ticks. +- `EExx`: send external command. + - this effect is currently incomplete. +- `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. +- `F3xx`: fine volume slide up (64x slower than `0Axy`). +- `F4xx`: fine volume slide down (64x slower than `0Axy`). +- `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). From c381b60143c8ce4e8bf84e848fdeb64904aee290 Mon Sep 17 00:00:00 2001 From: freq-mod <32672779+freq-mod@users.noreply.github.com> Date: Fri, 8 Apr 2022 23:03:02 +0200 Subject: [PATCH 577/637] Update saa1099.md --- papers/doc/7-systems/saa1099.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/papers/doc/7-systems/saa1099.md b/papers/doc/7-systems/saa1099.md index 0cb19fbd3..9cf7b6431 100644 --- a/papers/doc/7-systems/saa1099.md +++ b/papers/doc/7-systems/saa1099.md @@ -1,6 +1,6 @@ # Philips SAA1099 -this was used by the Game Blaster and SAM Coupé. it's pretty similar to the AY-3-8910, stereo sound, twice the channels and two envelopes, both of which are highly flexible. The envelopes work like this: +this was used by the Game Blaster and SAM Coupé. it's pretty similar to the AY-3-8910, but has stereo sound, twice the channels and two envelopes, both of which are highly flexible. The envelopes work like this: an instrument with envelope settings is placed on channel 2 or channel 5 an instrument that is used as an "envelope output", is placed on channel 3 or channel 6. You may want to disable wave output on the output channel. From 0fcc73b6c6cf8710088e28b241beff248b9b305e Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 8 Apr 2022 16:03:54 -0500 Subject: [PATCH 578/637] WaveSynth: implement more effects --- src/engine/waveSynth.cpp | 50 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/src/engine/waveSynth.cpp b/src/engine/waveSynth.cpp index d658be178..4d776ba4c 100644 --- a/src/engine/waveSynth.cpp +++ b/src/engine/waveSynth.cpp @@ -25,6 +25,54 @@ bool DivWaveSynth::tick() { } updated=true; break; + case DIV_WS_ADD: + for (int i=0; i<=state.speed; i++) { + output[pos]+=MIN(height,state.param1); + if (output[pos]>=height) output[pos]-=height; + if (++pos>=width) pos=0; + } + updated=true; + break; + case DIV_WS_SUBTRACT: + for (int i=0; i<=state.speed; i++) { + output[pos]+=MIN(height,state.param1); + if (output[pos]<0) output[pos]+=height; + if (++pos>=width) pos=0; + } + updated=true; + break; + case DIV_WS_AVERAGE: + for (int i=0; i<=state.speed; i++) { + int pos1=(pos+1>=width)?0:(pos+1); + output[pos]=(output[pos]*state.param1+output[pos1]*(256-state.param1))>>8; + if (output[pos]<0) output[pos]=0; + if (output[pos]>height) output[pos]=height; + if (++pos>=width) pos=0; + } + updated=true; + break; + case DIV_WS_PHASE: + for (int i=0; i<=state.speed; i++) { + output[pos]=wave1[(pos+stage)%width]; + if (++pos>=width) { + pos=0; + if (++stage>=width) stage=0; + } + } + updated=true; + break; + case DIV_WS_WIPE: + break; + case DIV_WS_FADE: + break; + case DIV_WS_PING_PONG: + break; + case DIV_WS_OVERLAY: + break; + case DIV_WS_NEGATIVE_OVERLAY: + break; + case DIV_WS_PHASE_DUAL: + break; } divCounter=state.rateDivider; } @@ -96,4 +144,4 @@ void DivWaveSynth::init(DivInstrument* which, int w, int h, bool insChanged) { changeWave1(state.wave1); changeWave2(state.wave2); } -} \ No newline at end of file +} From 7e4890d0ea70a898b2b88c246be2f2be69951563 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 8 Apr 2022 16:10:49 -0500 Subject: [PATCH 579/637] effects back on its orig place for now --- papers/doc/effects.md | 66 ------------------------------------------- 1 file changed, 66 deletions(-) delete mode 100644 papers/doc/effects.md diff --git a/papers/doc/effects.md b/papers/doc/effects.md deleted file mode 100644 index 773d30373..000000000 --- a/papers/doc/effects.md +++ /dev/null @@ -1,66 +0,0 @@ -# effect list - -most of the effect numbers are from ProTracker/FastTracker 2. -however, effects are continuous, which means you only need to type it once and then stop it with an effect value of `00`. - -- `00xy`: arpeggio. after using this effect the channel will rapidly switch between `note`, `note+x` and `note+y`. -- `01xx`: slide up. -- `02xx`: slide down. -- `03xx`: note portamento. - - 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. -- `0Axy`: volume slide. - - if `x` is 0 then this is a slide down. - - if `y` is 0 then this is a slide up. -- `0Bxx`: jump to pattern. -- `0Cxx`: retrigger note every `xx` ticks. - - this effect is not continuous. -- `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`. - -- `E0xx`: set arpeggio tick. - - this sets the number of ticks between arpeggio values. -- `E1xy`: note slide up. `x` is the speed, while `y` is how many semitones to slide up. -- `E2xy`: note slide down. `x` is the speed, while `y` is how many semitones to slide down. -- `E3xx`: set vibrato direction. `xx` may be one of the following: - - `00`: up and down. - - `01`: up only. - - `02`: down only. -- `E4xx`: set vibrato range in 1/16th of a semitone. -- `E5xx`: set pitch. `80` is 0 cents. - - range is ±1 semitone. -- `EAxx`: toggle legato. -- `EBxx`: set sample bank. - - does not apply on Amiga. -- `ECxx`: note off after `xx` ticks. -- `EDxx`: delay note by `xx` ticks. -- `EExx`: send external command. - - this effect is currently incomplete. -- `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. -- `F3xx`: fine volume slide up (64x slower than `0Axy`). -- `F4xx`: fine volume slide down (64x slower than `0Axy`). -- `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). From 0aef54400d637921598c923ebfc1e0331f054bd8 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 8 Apr 2022 16:30:08 -0500 Subject: [PATCH 580/637] exclude Windows/macOS from system lib build in CI --- .github/workflows/build.yml | 32 +++++++++++--------------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 94de2b50f..b48c493ba 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -26,7 +26,7 @@ jobs: - { name: 'Ubuntu', os: ubuntu-18.04 } fail-fast: false - name: "Test: ${{ matrix.config.name }}" + name: ${{ matrix.config.name }} runs-on: ${{ matrix.config.os }} steps: @@ -103,20 +103,6 @@ jobs: mingw-w64 \ mingw-w64-tools - - name: Install Dependencies [macOS] - if: ${{ runner.os == 'macOS' }} - run: | - export HOMEBREW_NO_INSTALL_CLEANUP=1 - brew update - brew install \ - pkg-config \ - sdl2 \ - fmt \ - rtmidi \ - libsndfile \ - zlib \ - jack - - name: Install Dependencies [Ubuntu] if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' }} run: | @@ -132,7 +118,8 @@ jobs: wget "https://github.com/AppImage/AppImageKit/releases/download/continuous/appimagetool-x86_64.AppImage" chmod +x appimagetool-x86_64.AppImage - - name: Configure (Normal) + - name: Configure (System Libraries) + if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' }} run: | export USE_WAE=ON export CMAKE_EXTRA_ARGS=() @@ -165,7 +152,8 @@ jobs: -DWARNINGS_ARE_ERRORS=${USE_WAE} \ "${CMAKE_EXTRA_ARGS[@]}" - - name: Build (Normal) + - name: Build (System Libraries) + if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' }} run: | export VERBOSE=1 cmake \ @@ -173,17 +161,19 @@ jobs: --config ${{ env.BUILD_TYPE }} \ --parallel 2 - - name: Install (Normal) + - name: Install (System Libraries) + if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' }} run: | cmake \ --install ${PWD}/build \ --config ${{ env.BUILD_TYPE }} - - name: Cleanup (Normal) + - name: Cleanup (System Libraries) + if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' }} run: | rm -rf build/ target/ - - name: Configure (Package) + - name: Configure run: | export USE_WAE=ON export CMAKE_EXTRA_ARGS=() @@ -215,7 +205,7 @@ jobs: -DWARNINGS_ARE_ERRORS=${USE_WAE} \ "${CMAKE_EXTRA_ARGS[@]}" - - name: Build (Package) + - name: Build run: | export VERBOSE=1 cmake \ From 9e0e8f334599404734fa3f4dfa3ad7275c0d3b61 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Fri, 8 Apr 2022 17:21:36 -0500 Subject: [PATCH 581/637] dev80 - increase song limits up to 256 patterns up to 256 orders --- papers/format.md | 7 +++++++ src/engine/engine.cpp | 12 ++++++------ src/engine/engine.h | 4 ++-- src/engine/fileOps.cpp | 33 +++++++++++++++++++++++++++++---- src/engine/orders.h | 4 ++-- src/engine/pattern.cpp | 4 ++-- src/engine/pattern.h | 2 +- src/gui/gui.cpp | 2 +- src/gui/gui.h | 2 +- src/gui/orders.cpp | 4 ++-- src/gui/songInfo.cpp | 2 +- 11 files changed, 54 insertions(+), 22 deletions(-) diff --git a/papers/format.md b/papers/format.md index 9ce20d2d7..ce1685c63 100644 --- a/papers/format.md +++ b/papers/format.md @@ -29,6 +29,7 @@ furthermore, an `or reserved` indicates this field is always present, but is res the format versions are: +- 80: Furnace dev80 - 79: Furnace dev79 - 78: Furnace dev78 - 77: Furnace dev77 @@ -119,12 +120,17 @@ size | description | - 60 is NTSC | - 50 is PAL 2 | pattern length + | - the limit is 256. 2 | orders length + | - the limit is 256 (>=80) or 127 (<80). 1 | highlight A 1 | highlight B 2 | instrument count + | - the limit is 256. 2 | wavetable count + | - the limit is 256. 2 | sample count + | - the limit is 256. 4 | pattern count 32 | list of sound chips | - possible soundchips: @@ -230,6 +236,7 @@ size | description | - a table of bytes | - size=channels*ordLen | - read orders then channels + | - the maximum value of a cell is FF (>=80) or 7F (<80). ??? | effect columns | - size=channels 1?? | channel hide status diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index c26058e65..78ae1ce49 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -1281,7 +1281,7 @@ void DivEngine::delInstrument(int index) { song.ins.erase(song.ins.begin()+index); song.insLen=song.ins.size(); for (int i=0; idata[k][2]>index) { @@ -1556,7 +1556,7 @@ void DivEngine::delSample(int index) { void DivEngine::addOrder(bool duplicate, bool where) { unsigned char order[DIV_MAX_CHANS]; - if (song.ordersLen>=0x7e) return; + if (song.ordersLen>=0xff) return; BUSY_BEGIN_SOFT; if (duplicate) { for (int i=0; i=0x7e) return; + if (song.ordersLen>=0xff) return; warnings=""; BUSY_BEGIN_SOFT; for (int i=0; idata[k][2]==one) { diff --git a/src/engine/engine.h b/src/engine/engine.h index 677cdeaaa..890056758 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -42,8 +42,8 @@ #define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock(); #define BUSY_END isBusy.unlock(); softLocked=false; -#define DIV_VERSION "dev79" -#define DIV_ENGINE_VERSION 79 +#define DIV_VERSION "dev80" +#define DIV_ENGINE_VERSION 80 // for imports #define DIV_VERSION_MOD 0xff01 diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 7988992ba..37964c682 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -959,7 +959,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { delete[] file; return false; } - if (ds.ordersLen>127) { + if (ds.ordersLen>256) { logE("song is too long!\n"); lastError="song is too long!"; delete[] file; @@ -1407,6 +1407,8 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { int index=reader.readS(); reader.readI(); + logD("- %d, %d\n",chan,index); + if (chan<0 || chan>=tchans) { logE("pattern channel out of range!\n",i); lastError="pattern channel out of range!"; @@ -1414,7 +1416,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { delete[] file; return false; } - if (index<0 || index>127) { + if (index<0 || index>255) { logE("pattern index out of range!\n",i); lastError="pattern index out of range!"; ds.unload(); @@ -1422,8 +1424,6 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { return false; } - logD("- %d, %d\n",chan,index); - DivPattern* pat=ds.pat[chan].getPattern(index,true); for (int j=0; jdata[j][0]=reader.readS(); @@ -2292,6 +2292,31 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { lastError="this system is not possible on .dmf"; return NULL; } + // fail if values are out of range + if (song.ordersLen>127) { + logE("maximum .dmf song length is 127!\n"); + lastError="maximum .dmf song length is 127"; + return NULL; + } + if (song.ins.size()>128) { + logE("maximum number of instruments in .dmf is 128!\n"); + lastError="maximum number of instruments in .dmf is 128"; + return NULL; + } + if (song.wave.size()>64) { + logE("maximum number of wavetables in .dmf is 64!\n"); + lastError="maximum number of wavetables in .dmf is 64"; + return NULL; + } + for (int i=0; i0x7f) { + logE("order %d, %d is out of range (0-127)!\n",song.orders.ord[i][j]); + lastError=fmt::sprintf("order %d, %d is out of range (0-127)",song.orders.ord[i][j]); + return NULL; + } + } + } saveLock.lock(); warnings=""; song.version=version; diff --git a/src/engine/orders.h b/src/engine/orders.h index d10dbe4c4..c57d1aab7 100644 --- a/src/engine/orders.h +++ b/src/engine/orders.h @@ -21,10 +21,10 @@ #define _ORDERS_H struct DivOrders { - unsigned char ord[DIV_MAX_CHANS][128]; + unsigned char ord[DIV_MAX_CHANS][256]; DivOrders() { - memset(ord,0,DIV_MAX_CHANS*128); + memset(ord,0,DIV_MAX_CHANS*256); } }; diff --git a/src/engine/pattern.cpp b/src/engine/pattern.cpp index 91a4ecd47..8241255b3 100644 --- a/src/engine/pattern.cpp +++ b/src/engine/pattern.cpp @@ -41,7 +41,7 @@ DivPattern* DivChannelData::getPattern(int index, bool create) { } void DivChannelData::wipePatterns() { - for (int i=0; i<128; i++) { + for (int i=0; i<256; i++) { if (data[i]!=NULL) { delete data[i]; data[i]=NULL; @@ -131,5 +131,5 @@ SafeReader* DivPattern::compile(int len, int fxRows) { DivChannelData::DivChannelData(): effectRows(1) { - memset(data,0,128*sizeof(void*)); + memset(data,0,256*sizeof(void*)); } diff --git a/src/engine/pattern.h b/src/engine/pattern.h index 1af744b65..fd0b0d076 100644 --- a/src/engine/pattern.h +++ b/src/engine/pattern.h @@ -49,7 +49,7 @@ struct DivChannelData { // 3: volume // 4-5+: effect/effect value // do NOT access directly unless you know what you're doing! - DivPattern* data[128]; + DivPattern* data[256]; /** * get a pattern from this channel, or the empty pattern if not initialized. diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index bae782ff4..755b825a4 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1131,7 +1131,7 @@ void FurnaceGUI::keyDown(SDL_Event& ev) { if (orderCursor>=0 && orderCursorgetTotalChannelCount()) { int curOrder=e->getOrder(); e->lockSave([this,curOrder,num]() { - e->song.orders.ord[orderCursor][curOrder]=((e->song.orders.ord[orderCursor][curOrder]<<4)|num)&0x7f; + e->song.orders.ord[orderCursor][curOrder]=((e->song.orders.ord[orderCursor][curOrder]<<4)|num); }); if (orderEditMode==2 || orderEditMode==3) { curNibble=!curNibble; diff --git a/src/gui/gui.h b/src/gui/gui.h index 445369f0f..a797700f0 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -952,7 +952,7 @@ class FurnaceGUI { int oldOrdersLen; DivOrders oldOrders; - DivPattern* oldPat[128]; + DivPattern* oldPat[DIV_MAX_CHANS]; std::deque undoHist; std::deque redoHist; diff --git a/src/gui/orders.cpp b/src/gui/orders.cpp index b233c48bb..945f551d7 100644 --- a/src/gui/orders.cpp +++ b/src/gui/orders.cpp @@ -102,10 +102,10 @@ void FurnaceGUI::drawOrders() { e->lockSave([this,i,j]() { if (changeAllOrders) { for (int k=0; kgetTotalChannelCount(); k++) { - if (e->song.orders.ord[k][i]<0x7f) e->song.orders.ord[k][i]++; + if (e->song.orders.ord[k][i]<0xff) e->song.orders.ord[k][i]++; } } else { - if (e->song.orders.ord[j][i]<0x7f) e->song.orders.ord[j][i]++; + if (e->song.orders.ord[j][i]<0xff) e->song.orders.ord[j][i]++; } }); e->walkSong(loopOrder,loopRow,loopEnd); diff --git a/src/gui/songInfo.cpp b/src/gui/songInfo.cpp index b5923801e..a2f788c35 100644 --- a/src/gui/songInfo.cpp +++ b/src/gui/songInfo.cpp @@ -122,7 +122,7 @@ void FurnaceGUI::drawSongInfo() { int ordLen=e->song.ordersLen; if (ImGui::InputInt("##OrdLength",&ordLen,1,3)) { MARK_MODIFIED if (ordLen<1) ordLen=1; - if (ordLen>127) ordLen=127; + if (ordLen>256) ordLen=256; e->song.ordersLen=ordLen; if (e->getOrder()>=ordLen) { e->setOrder(ordLen-1); From 320250b8311de4910f946226cc57e5f153aa9bc5 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 9 Apr 2022 01:50:44 -0500 Subject: [PATCH 582/637] MIDI out improvements --- src/engine/engine.cpp | 13 +++++++++++++ src/engine/engine.h | 7 +++++-- src/engine/playback.cpp | 22 +++++++++++++++++++--- 3 files changed, 37 insertions(+), 5 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 78ae1ce49..f11f967d3 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -873,6 +873,11 @@ void DivEngine::play() { for (int i=0; imidiOut!=NULL) { + int pos=totalTicksR/6; + output->midiOut->send(TAMidiMessage(TA_MIDI_POSITION,(pos>>7)&0x7f,pos&0x7f)); + output->midiOut->send(TAMidiMessage(TA_MIDI_MACHINE_PLAY,0,0)); + } BUSY_END; } @@ -918,6 +923,14 @@ void DivEngine::stop() { for (int i=0; inotifyPlaybackStop(); } + if (output) if (output->midiOut!=NULL) { + output->midiOut->send(TAMidiMessage(TA_MIDI_MACHINE_STOP,0,0)); + for (int i=0; i=0) { + output->midiOut->send(TAMidiMessage(0x80|(i&15),chan[i].curMidiNote,0)); + } + } + } BUSY_END; } diff --git a/src/engine/engine.h b/src/engine/engine.h index 890056758..6741a00e2 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -86,7 +86,8 @@ struct DivChannelState { bool doNote, legato, portaStop, keyOn, keyOff, nowYouCanStop, stopOnOff; bool arpYield, delayLocked, inPorta, scheduledSlideReset, shorthandPorta, noteOnInhibit, resetArp; - int midiNote, curMidiNote; + int midiNote, curMidiNote, midiPitch; + bool midiAftertouch; DivChannelState(): note(-1), @@ -130,7 +131,9 @@ struct DivChannelState { noteOnInhibit(false), resetArp(false), midiNote(-1), - curMidiNote(-1) {} + curMidiNote(-1), + midiPitch(-1), + midiAftertouch(false) {} }; struct DivNoteEvent { diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 8e1f1d664..1776b0460 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -201,16 +201,24 @@ int DivEngine::dispatchCmd(DivCommand c) { chan[c.chan].curMidiNote=-1; break; case DIV_CMD_INSTRUMENT: - output->midiOut->send(TAMidiMessage(0xc0|(c.chan&15),c.value,0)); + if (chan[c.chan].lastIns!=c.value) { + output->midiOut->send(TAMidiMessage(0xc0|(c.chan&15),c.value,0)); + } break; case DIV_CMD_VOLUME: - //output->midiOut->send(TAMidiMessage(0xb0|(c.chan&15),0x07,scaledVol)); + if (chan[c.chan].curMidiNote>=0 && chan[c.chan].midiAftertouch) { + chan[c.chan].midiAftertouch=false; + output->midiOut->send(TAMidiMessage(0xa0|(c.chan&15),chan[c.chan].curMidiNote,scaledVol)); + } break; case DIV_CMD_PITCH: { int pitchBend=8192+(c.value<<5); if (pitchBend<0) pitchBend=0; if (pitchBend>16383) pitchBend=16383; - output->midiOut->send(TAMidiMessage(0xe0|(c.chan&15),pitchBend&0x7f,pitchBend>>7)); + if (pitchBend!=chan[c.chan].midiPitch) { + chan[c.chan].midiPitch=pitchBend; + output->midiOut->send(TAMidiMessage(0xe0|(c.chan&15),pitchBend&0x7f,pitchBend>>7)); + } break; } default: @@ -963,6 +971,9 @@ void DivEngine::processRow(int i, bool afterDelay) { // volume if (pat->data[whatRow][3]!=-1) { if (dispatchCmd(DivCommand(DIV_ALWAYS_SET_VOLUME,i)) || (MIN(chan[i].volMax,chan[i].volume)>>8)!=pat->data[whatRow][3]) { + if (pat->data[whatRow][0]==0 && pat->data[whatRow][1]==0) { + chan[i].midiAftertouch=true; + } chan[i].volume=pat->data[whatRow][3]<<8; dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); } @@ -1436,6 +1447,11 @@ bool DivEngine::nextTick(bool noAccum) { cycles++; } + // MIDI clock + if (output) if (!skipping && output->midiOut!=NULL) { + output->midiOut->send(TAMidiMessage(TA_MIDI_CLOCK,0,0)); + } + while (!pendingNotes.empty()) { DivNoteEvent& note=pendingNotes.front(); if (note.on) { From 7bf2a3ea1a64703d3fec0fa9974fd2aeec2fd115 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 9 Apr 2022 02:42:58 -0500 Subject: [PATCH 583/637] GUI: prepare for oscilloscope improvements --- src/engine/engine.h | 3 +++ src/engine/playback.cpp | 7 ++++-- src/gui/gui.cpp | 7 +++++- src/gui/gui.h | 6 +++++ src/gui/osc.cpp | 51 ++++++++++++++++++++++++++++++++++++----- src/gui/volMeter.cpp | 8 ------- 6 files changed, 65 insertions(+), 17 deletions(-) diff --git a/src/engine/engine.h b/src/engine/engine.h index 6741a00e2..68ec0ce5b 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -298,6 +298,7 @@ class DivEngine { bool keyHit[DIV_MAX_CHANS]; float* oscBuf[2]; float oscSize; + int oscReadPos, oscWritePos; void runExportThread(); void nextBuf(float** in, float** out, int inChans, int outChans, unsigned int size); @@ -772,6 +773,8 @@ class DivEngine { totalProcessed(0), oscBuf{NULL,NULL}, oscSize(1), + oscReadPos(0), + oscWritePos(0), adpcmAMem(NULL), adpcmAMemLen(0), adpcmBMem(NULL), diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 1776b0460..e6fb4d72c 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -1896,8 +1896,11 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi while (metroPos>=1) metroPos--; } - memcpy(oscBuf[0],out[0],size*sizeof(float)); - memcpy(oscBuf[1],out[1],size*sizeof(float)); + for (unsigned int i=0; i=32768) oscWritePos=0; + } oscSize=size; if (forceMono) { diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 755b825a4..23110ef87 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2600,6 +2600,9 @@ bool FurnaceGUI::loop() { drawInsList(); drawInsEdit(); drawMixer(); + + readOsc(); + drawOsc(); drawVolMeter(); drawSettings(); @@ -3428,7 +3431,8 @@ FurnaceGUI::FurnaceGUI(): openSampleResampleOpt(false), openSampleAmplifyOpt(false), openSampleSilenceOpt(false), - openSampleFilterOpt(false) { + openSampleFilterOpt(false), + oscTotal(0) { // value keys valueKeys[SDLK_0]=0; valueKeys[SDLK_1]=1; @@ -3469,4 +3473,5 @@ FurnaceGUI::FurnaceGUI(): memset(patChanX,0,sizeof(float)*(DIV_MAX_CHANS+1)); memset(patChanSlideY,0,sizeof(float)*(DIV_MAX_CHANS+1)); memset(lastIns,-1,sizeof(int)*DIV_MAX_CHANS); + memset(oscValues,0,sizeof(float)*512); } diff --git a/src/gui/gui.h b/src/gui/gui.h index a797700f0..6d8f300bb 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -976,6 +976,10 @@ class FurnaceGUI { size_t sampleClipboardLen; bool openSampleResizeOpt, openSampleResampleOpt, openSampleAmplifyOpt, openSampleSilenceOpt, openSampleFilterOpt; + // oscilloscope + int oscTotal; + float oscValues[512]; + // visualizer float keyHit[DIV_MAX_CHANS]; int lastIns[DIV_MAX_CHANS]; @@ -996,6 +1000,8 @@ class FurnaceGUI { void updateWindowTitle(); void prepareLayout(); + void readOsc(); + float calcBPM(int s1, int s2, float hz); void patternRow(int i, bool isPlaying, float lineHeight, int chans, int ord, const DivPattern** patCache); diff --git a/src/gui/osc.cpp b/src/gui/osc.cpp index 0bdcdf8a0..a4da29c8c 100644 --- a/src/gui/osc.cpp +++ b/src/gui/osc.cpp @@ -18,6 +18,50 @@ */ #include "gui.h" +#include + +void FurnaceGUI::readOsc() { + int writePos=e->oscWritePos; + int readPos=e->oscReadPos; + int avail=0; + int total=0; + if (writePos>=readPos) { + avail=writePos-readPos; + } else { + avail=writePos-readPos+32768; + } + if (oscTotal==0) { + oscTotal=ImGui::GetIO().DeltaTime*e->getAudioDescGot().rate; + } else { + oscTotal=(oscTotal+(int)round(ImGui::GetIO().DeltaTime*e->getAudioDescGot().rate))>>1; + } + int bias=avail-oscTotal-e->getAudioDescGot().bufsize; + if (bias<0) bias=0; + total=oscTotal+(bias>>6); + if (total>avail) total=avail; + //printf("total: %d. avail: %d bias: %d\n",total,avail,bias); + for (int i=0; i<512; i++) { + int pos=(readPos+(i*total/512))&0x7fff; + oscValues[i]=(e->oscBuf[0][pos]+e->oscBuf[1][pos])*0.5f; + } + + float peakDecay=0.05f*60.0f*ImGui::GetIO().DeltaTime; + for (int i=0; i<2; i++) { + peak[i]*=1.0-peakDecay; + if (peak[i]<0.0001) peak[i]=0.0; + float newPeak=peak[i]; + for (int j=0; joscBuf[i][pos])>newPeak) { + newPeak=fabs(e->oscBuf[i][pos]); + } + } + peak[i]+=(newPeak-peak[i])*0.9; + } + + readPos=(readPos+total)&0x7fff; + e->oscReadPos=readPos; +} void FurnaceGUI::drawOsc() { if (nextWindow==GUI_WINDOW_OSCILLOSCOPE) { @@ -31,14 +75,9 @@ void FurnaceGUI::drawOsc() { ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,ImVec2(0,0)); ImGui::PushStyleVar(ImGuiStyleVar_ItemInnerSpacing,ImVec2(0,0)); if (ImGui::Begin("Oscilloscope",&oscOpen)) { - float values[512]; - for (int i=0; i<512; i++) { - int pos=i*e->oscSize/512; - values[i]=(e->oscBuf[0][pos]+e->oscBuf[1][pos])*0.5f; - } //ImGui::SetCursorPos(ImVec2(0,0)); ImGui::BeginDisabled(); - ImGui::PlotLines("##SingleOsc",values,512,0,NULL,-1.0f,1.0f,ImGui::GetContentRegionAvail()); + ImGui::PlotLines("##SingleOsc",oscValues,512,0,NULL,-1.0f,1.0f,ImGui::GetContentRegionAvail()); ImGui::EndDisabled(); } ImGui::PopStyleVar(3); diff --git a/src/gui/volMeter.cpp b/src/gui/volMeter.cpp index 29f62c969..7be3464e9 100644 --- a/src/gui/volMeter.cpp +++ b/src/gui/volMeter.cpp @@ -49,17 +49,9 @@ void FurnaceGUI::drawVolMeter() { ImGuiStyle& style=ImGui::GetStyle(); ImGui::ItemSize(ImVec2(4.0f,4.0f),style.FramePadding.y); ImU32 lowColor=ImGui::GetColorU32(uiColors[GUI_COLOR_VOLMETER_LOW]); - float peakDecay=0.05f*60.0f*ImGui::GetIO().DeltaTime; if (ImGui::ItemAdd(rect,ImGui::GetID("volMeter"))) { ImGui::RenderFrame(rect.Min,rect.Max,ImGui::GetColorU32(ImGuiCol_FrameBg),true,style.FrameRounding); for (int i=0; i<2; i++) { - peak[i]*=1.0-peakDecay; - if (peak[i]<0.0001) peak[i]=0.0; - for (int j=0; joscSize; j++) { - if (fabs(e->oscBuf[i][j])>peak[i]) { - peak[i]=fabs(e->oscBuf[i][j]); - } - } float logPeak=(20*log10(peak[i])/36.0); if (logPeak==NAN) logPeak=0.0; if (logPeak<-1.0) logPeak=-1.0; From 785ac8d1a71380ece6d0966eee469c2730be55c8 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 9 Apr 2022 02:45:06 -0500 Subject: [PATCH 584/637] small oscBuf fix --- src/engine/playback.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index e6fb4d72c..d6d184653 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -1748,8 +1748,11 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi if (!playing) { if (out!=NULL) { - memcpy(oscBuf[0],out[0],size*sizeof(float)); - memcpy(oscBuf[1],out[1],size*sizeof(float)); + for (unsigned int i=0; i=32768) oscWritePos=0; + } oscSize=size; } isBusy.unlock(); From d4fbebf47896d180bc197c12284b223f1c57e2de Mon Sep 17 00:00:00 2001 From: OPNA2608 Date: Thu, 31 Mar 2022 18:50:09 +0200 Subject: [PATCH 585/637] Lower MSVC warning level to 2 (significant), enable WAE on CI --- .github/workflows/build.yml | 6 ------ CMakeLists.txt | 5 ++--- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b48c493ba..19118f6a9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -125,9 +125,6 @@ jobs: export CMAKE_EXTRA_ARGS=() if [ '${{ matrix.config.compiler }}' == 'msvc' ]; then CMAKE_EXTRA_ARGS+=('-DCMAKE_GENERATOR_PLATFORM=${{ steps.windows-identify.outputs.msvc-target }}') - - # FIXME We don't want all the MSVC warnings to cause errors yet - export USE_WAE=OFF elif [ '${{ matrix.config.compiler }}' == 'mingw' ]; then CMAKE_EXTRA_ARGS+=('-DCMAKE_TOOLCHAIN_FILE=scripts/Cross-MinGW-${{ steps.windows-identify.outputs.mingw-target }}.cmake') else @@ -180,9 +177,6 @@ jobs: if [ '${{ matrix.config.compiler }}' == 'msvc' ]; then CMAKE_EXTRA_ARGS+=('-DCMAKE_GENERATOR_PLATFORM=${{ steps.windows-identify.outputs.msvc-target }}') - # FIXME We don't want all the MSVC warnings to cause errors yet - export USE_WAE=OFF - # Force static linking # 1. Make MSVC runtime configurable CMAKE_EXTRA_ARGS+=('-DCMAKE_POLICY_DEFAULT_CMP0091=NEW') diff --git a/CMakeLists.txt b/CMakeLists.txt index 769842f33..3f23e102f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -457,9 +457,8 @@ if (NOT MSVC) list(APPEND WARNING_FLAGS -Werror) endif() else() - # /wd4100 == -Wno-unused-parameter - add_compile_options("/source-charset:utf-8") - set(WARNING_FLAGS /W4 /wd4100 /D_CRT_SECURE_NO_WARNINGS) + add_compile_options("/utf-8") + set(WARNING_FLAGS /W2 /D_CRT_SECURE_NO_WARNINGS) if (WARNINGS_ARE_ERRORS) list(APPEND WARNING_FLAGS /WX) endif() From 43b12c1ca549a58c7bec9f6f5aea7b191ec0db7e Mon Sep 17 00:00:00 2001 From: OPNA2608 Date: Sat, 2 Apr 2022 18:35:00 +0200 Subject: [PATCH 586/637] Ignore C4244, C4305 & C4309 --- CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3f23e102f..c240e37b6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -459,6 +459,11 @@ if (NOT MSVC) else() add_compile_options("/utf-8") set(WARNING_FLAGS /W2 /D_CRT_SECURE_NO_WARNINGS) + list(APPEND WARNING_FLAGS + /wd4244 # implicit type conversions + /wd4305 # truncations + /wd4309 # truncations of constant values + ) if (WARNINGS_ARE_ERRORS) list(APPEND WARNING_FLAGS /WX) endif() From 82d893082cddae36c90bef56ad0949c9f221acad Mon Sep 17 00:00:00 2001 From: OPNA2608 Date: Thu, 31 Mar 2022 20:28:30 +0200 Subject: [PATCH 587/637] Fix for C4005: '_CRT_SECURE_NO_WARNINGS': macro redefinition --- src/engine/platform/sound/ymfm/ymfm.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/sound/ymfm/ymfm.h b/src/engine/platform/sound/ymfm/ymfm.h index 906e3211a..7b98f8499 100644 --- a/src/engine/platform/sound/ymfm/ymfm.h +++ b/src/engine/platform/sound/ymfm/ymfm.h @@ -33,7 +33,7 @@ #pragma once -#ifdef _MSC_VER +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) #define _CRT_SECURE_NO_WARNINGS #endif From 4c19a973efc4c57086e6d75e1ea3143d90cee36a Mon Sep 17 00:00:00 2001 From: OPNA2608 Date: Thu, 31 Mar 2022 20:46:27 +0200 Subject: [PATCH 588/637] Fix for C4805: '|': unsafe mix of type 'unsigned char' and type 'bool' in operation --- src/engine/platform/ay8930.cpp | 2 +- src/engine/platform/c64.cpp | 16 ++++++++-------- src/engine/platform/opl.cpp | 2 +- src/engine/platform/opll.cpp | 2 +- src/engine/platform/sound/ay8910.cpp | 2 +- src/gui/sysConf.cpp | 4 ++-- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/engine/platform/ay8930.cpp b/src/engine/platform/ay8930.cpp index 1849d0cdc..eda6af041 100644 --- a/src/engine/platform/ay8930.cpp +++ b/src/engine/platform/ay8930.cpp @@ -123,7 +123,7 @@ void DivPlatformAY8930::acquire(short* bufL, short* bufR, size_t start, size_t l } while (!writes.empty()) { QueuedWrite w=writes.front(); - if (bank!=(w.addr>>4)) { + if ((int)bank!=(w.addr>>4)) { bank=w.addr>>4; ay->address_w(0x0d); ay->data_w(0xa0|(bank<<4)|ayEnvMode[0]); diff --git a/src/engine/platform/c64.cpp b/src/engine/platform/c64.cpp index 49be60fb8..6ddb2cf4e 100644 --- a/src/engine/platform/c64.cpp +++ b/src/engine/platform/c64.cpp @@ -118,7 +118,7 @@ void DivPlatformC64::acquire(short* bufL, short* bufR, size_t start, size_t len) void DivPlatformC64::updateFilter() { rWrite(0x15,filtCut&7); rWrite(0x16,filtCut>>3); - rWrite(0x17,(filtRes<<4)|(chan[2].filter<<2)|(chan[1].filter<<1)|(chan[0].filter)); + rWrite(0x17,(filtRes<<4)|(chan[2].filter<<2)|(chan[1].filter<<1)|(int)(chan[0].filter)); rWrite(0x18,(filtControl<<4)|vol); } @@ -177,7 +177,7 @@ void DivPlatformC64::tick() { } if (chan[i].std.hadWave) { chan[i].wave=chan[i].std.wave; - rWrite(i*7+4,(isMuted[i]?8:(chan[i].wave<<4))|(chan[i].ring<<2)|(chan[i].sync<<1)|chan[i].active); + rWrite(i*7+4,(isMuted[i]?8:(chan[i].wave<<4))|(chan[i].ring<<2)|(chan[i].sync<<1)|(int)(chan[i].active)); } if (chan[i].std.hadEx1) { filtControl=chan[i].std.ex1&15; @@ -232,7 +232,7 @@ int DivPlatformC64::dispatch(DivCommand c) { rWrite(c.chan*7+3,chan[c.chan].duty>>8); } if (chan[c.chan].insChanged) { - chan[c.chan].wave=(ins->c64.noiseOn<<3)|(ins->c64.pulseOn<<2)|(ins->c64.sawOn<<1)|(ins->c64.triOn); + chan[c.chan].wave=(ins->c64.noiseOn<<3)|(ins->c64.pulseOn<<2)|(ins->c64.sawOn<<1)|(int)(ins->c64.triOn); chan[c.chan].attack=ins->c64.a; chan[c.chan].decay=(ins->c64.s==15)?0:ins->c64.d; chan[c.chan].sustain=ins->c64.s; @@ -245,7 +245,7 @@ int DivPlatformC64::dispatch(DivCommand c) { if (ins->c64.initFilter) { filtCut=ins->c64.cut; filtRes=ins->c64.res; - filtControl=ins->c64.lp|(ins->c64.bp<<1)|(ins->c64.hp<<2)|(ins->c64.ch3off<<3); + filtControl=(int)(ins->c64.lp)|(ins->c64.bp<<1)|(ins->c64.hp<<2)|(ins->c64.ch3off<<3); } updateFilter(); } @@ -330,7 +330,7 @@ int DivPlatformC64::dispatch(DivCommand c) { break; case DIV_CMD_WAVE: chan[c.chan].wave=c.value; - rWrite(c.chan*7+4,(isMuted[c.chan]?8:(chan[c.chan].wave<<4))|(chan[c.chan].ring<<2)|(chan[c.chan].sync<<1)|chan[c.chan].active); + rWrite(c.chan*7+4,(isMuted[c.chan]?8:(chan[c.chan].wave<<4))|(chan[c.chan].ring<<2)|(chan[c.chan].sync<<1)|(int)(chan[c.chan].active)); break; case DIV_CMD_LEGATO: chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value+((chan[c.chan].std.willArp && !chan[c.chan].std.arpMode)?(chan[c.chan].std.arp):(0))); @@ -411,11 +411,11 @@ int DivPlatformC64::dispatch(DivCommand c) { break; case 4: chan[c.chan].ring=c.value; - rWrite(c.chan*7+4,(isMuted[c.chan]?8:(chan[c.chan].wave<<4))|(chan[c.chan].ring<<2)|(chan[c.chan].sync<<1)|chan[c.chan].active); + rWrite(c.chan*7+4,(isMuted[c.chan]?8:(chan[c.chan].wave<<4))|(chan[c.chan].ring<<2)|(chan[c.chan].sync<<1)|(int)(chan[c.chan].active)); break; case 5: chan[c.chan].sync=c.value; - rWrite(c.chan*7+4,(isMuted[c.chan]?8:(chan[c.chan].wave<<4))|(chan[c.chan].ring<<2)|(chan[c.chan].sync<<1)|chan[c.chan].active); + rWrite(c.chan*7+4,(isMuted[c.chan]?8:(chan[c.chan].wave<<4))|(chan[c.chan].ring<<2)|(chan[c.chan].sync<<1)|(int)(chan[c.chan].active)); break; case 6: filtControl&=7; @@ -434,7 +434,7 @@ int DivPlatformC64::dispatch(DivCommand c) { void DivPlatformC64::muteChannel(int ch, bool mute) { isMuted[ch]=mute; - rWrite(ch*7+4,(isMuted[ch]?8:(chan[ch].wave<<4))|(chan[ch].ring<<2)|(chan[ch].sync<<1)|chan[ch].active); + rWrite(ch*7+4,(isMuted[ch]?8:(chan[ch].wave<<4))|(chan[ch].ring<<2)|(chan[ch].sync<<1)|(int)(chan[ch].active)); } void DivPlatformC64::forceIns() { diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index fcba9f80c..bd0ae933a 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -371,7 +371,7 @@ void DivPlatformOPL::tick() { if (update4OpMask) { update4OpMask=false; if (oplType==3) { - unsigned char opMask=chan[0].fourOp|(chan[2].fourOp<<1)|(chan[4].fourOp<<2)|(chan[6].fourOp<<3)|(chan[8].fourOp<<4)|(chan[10].fourOp<<5); + unsigned char opMask=(int)(chan[0].fourOp)|(chan[2].fourOp<<1)|(chan[4].fourOp<<2)|(chan[6].fourOp<<3)|(chan[8].fourOp<<4)|(chan[10].fourOp<<5); immWrite(0x104,opMask); //printf("updating opMask to %.2x\n",opMask); } diff --git a/src/engine/platform/opll.cpp b/src/engine/platform/opll.cpp index 55a1fa80c..14332bce2 100644 --- a/src/engine/platform/opll.cpp +++ b/src/engine/platform/opll.cpp @@ -620,7 +620,7 @@ int DivPlatformOPLL::dispatch(DivCommand c) { } case DIV_CMD_FM_EXTCH: if (!properDrumsSys) break; - if (properDrums==c.value) break; + if ((int)properDrums==c.value) break; if (c.value) { properDrums=true; immWrite(0x0e,0x20); diff --git a/src/engine/platform/sound/ay8910.cpp b/src/engine/platform/sound/ay8910.cpp index 2d734f931..8bb387885 100644 --- a/src/engine/platform/sound/ay8910.cpp +++ b/src/engine/platform/sound/ay8910.cpp @@ -1120,7 +1120,7 @@ void ay8910_device::sound_stream_update(short** outputs, int outLen) for (int chan = 0; chan < NUM_CHANNELS; chan++) { tone = &m_tone[chan]; - m_vol_enabled[chan] = (tone->output | tone_enable(chan)) & (noise_output() | noise_enable(chan)); + m_vol_enabled[chan] = (tone->output | (unsigned char)tone_enable(chan)) & (noise_output() | (unsigned char)noise_enable(chan)); } /* update envelope */ diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index 1bc60c7c7..8ecf7c07a 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -284,7 +284,7 @@ void FurnaceGUI::drawSysConf(int i) { } sysPal=flags&1; if (ImGui::Checkbox("PAL",&sysPal)) { - e->setSysFlags(i,(flags&(~1))|sysPal,restart); + e->setSysFlags(i,(flags&(~1))|(unsigned int)sysPal,restart); updateWindowTitle(); } bool bypassLimits=flags&4; @@ -395,4 +395,4 @@ void FurnaceGUI::drawSysConf(int i) { } break; } -} \ No newline at end of file +} From 16ad29ae7a775d5a92ff684ad5576f069ac18276 Mon Sep 17 00:00:00 2001 From: OPNA2608 Date: Thu, 31 Mar 2022 23:01:22 +0200 Subject: [PATCH 589/637] Fix C4293: '<<': shift count negative or too big, undefined behavior MSVC seems kinda braindead: https://reviews.llvm.org/D41030#952363 --- src/gui/insEdit.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 14ca65b40..7412c15df 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1044,7 +1044,7 @@ void FurnaceGUI::drawFMEnv(unsigned char tl, unsigned char ar, unsigned char dr, } \ ImGui::SetNextItemWidth(availableWidth); \ if (ImGui::InputText("##IMacroMML_" macroName,&mmlStr)) { \ - decodeMMLStr(mmlStr,macro,macroLen,macroLoop,macroAMin,(bitfield)?((1< Date: Sat, 9 Apr 2022 11:23:19 +0200 Subject: [PATCH 590/637] Fix C4229 Move calling convention modifier for icon callback lambda in pfd, MSVC wasn't quite happy with this. --- extern/pfd-fixed/portable-file-dialogs.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extern/pfd-fixed/portable-file-dialogs.h b/extern/pfd-fixed/portable-file-dialogs.h index 008fe0a94..3d90a854f 100644 --- a/extern/pfd-fixed/portable-file-dialogs.h +++ b/extern/pfd-fixed/portable-file-dialogs.h @@ -1383,7 +1383,7 @@ inline notify::notify(std::string const &title, /* case icon::info: */ default: nid->dwInfoFlags = NIIF_INFO; break; } - ENUMRESNAMEPROC icon_enum_callback = [](HMODULE, LPCTSTR, LPTSTR lpName, LONG_PTR lParam) -> BOOL WINAPI + ENUMRESNAMEPROC icon_enum_callback = [](HMODULE, LPCTSTR, LPTSTR lpName, LONG_PTR lParam) WINAPI -> BOOL { ((NOTIFYICONDATAW *)lParam)->hIcon = ::LoadIcon(GetModuleHandle(nullptr), lpName); return false; From 11cdc3924d9f4e612dadde1bff7390d65429efb0 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 9 Apr 2022 05:02:24 -0500 Subject: [PATCH 591/637] GUI: improve oscilloscope it's not finished yet --- src/gui/gui.cpp | 4 +- src/gui/gui.h | 12 ++++++ src/gui/guiConst.cpp | 10 +++++ src/gui/osc.cpp | 90 ++++++++++++++++++++++++++++++++++++++++---- src/gui/settings.cpp | 12 ++++++ 5 files changed, 119 insertions(+), 9 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 23110ef87..669300518 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -3432,7 +3432,9 @@ FurnaceGUI::FurnaceGUI(): openSampleAmplifyOpt(false), openSampleSilenceOpt(false), openSampleFilterOpt(false), - oscTotal(0) { + oscTotal(0), + oscZoom(0.5f), + oscZoomSlider(false) { // value keys valueKeys[SDLK_0]=0; valueKeys[SDLK_1]=1; diff --git a/src/gui/gui.h b/src/gui/gui.h index 6d8f300bb..641cf7c02 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -71,6 +71,16 @@ enum FurnaceGUIColors { GUI_COLOR_FILE_FONT, GUI_COLOR_FILE_OTHER, + GUI_COLOR_OSC_BG1, + GUI_COLOR_OSC_BG2, + GUI_COLOR_OSC_BG3, + GUI_COLOR_OSC_BG4, + GUI_COLOR_OSC_BORDER, + GUI_COLOR_OSC_WAVE, + GUI_COLOR_OSC_WAVE_PEAK, + GUI_COLOR_OSC_REF, + GUI_COLOR_OSC_GUIDE, + GUI_COLOR_VOLMETER_LOW, GUI_COLOR_VOLMETER_HIGH, GUI_COLOR_VOLMETER_PEAK, @@ -979,6 +989,8 @@ class FurnaceGUI { // oscilloscope int oscTotal; float oscValues[512]; + float oscZoom; + bool oscZoomSlider; // visualizer float keyHit[DIV_MAX_CHANS]; diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index 085829336..c3a7c72bd 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -377,6 +377,16 @@ const FurnaceGUIColorDef guiColors[GUI_COLOR_MAX]={ D(GUI_COLOR_FILE_FONT,"",ImVec4(0.3f,1.0f,0.6f,1.0f)), D(GUI_COLOR_FILE_OTHER,"",ImVec4(0.7f,0.7f,0.7f,1.0f)), + D(GUI_COLOR_OSC_BG1,"",ImVec4(0.1f,0.18f,0.3f,1.0f)), + D(GUI_COLOR_OSC_BG2,"",ImVec4(0.1f,0.18f,0.3f,1.0f)), + D(GUI_COLOR_OSC_BG3,"",ImVec4(0.05f,0.15f,0.25f,1.0f)), + D(GUI_COLOR_OSC_BG4,"",ImVec4(0.05f,0.15f,0.25f,1.0f)), + D(GUI_COLOR_OSC_BORDER,"",ImVec4(0.4f,0.6f,0.95f,1.0f)), + D(GUI_COLOR_OSC_WAVE,"",ImVec4(0.95f,0.95f,1.0f,1.0f)), + D(GUI_COLOR_OSC_WAVE_PEAK,"",ImVec4(0.95f,0.95f,1.0f,1.0f)), + D(GUI_COLOR_OSC_REF,"",ImVec4(0.3f,0.3f,0.3f,1.0f)), + D(GUI_COLOR_OSC_GUIDE,"",ImVec4(0.3,0.3f,0.3f,1.0f)), + D(GUI_COLOR_VOLMETER_LOW,"",ImVec4(0.2f,0.6f,0.2f,1.0f)), D(GUI_COLOR_VOLMETER_HIGH,"",ImVec4(1.0f,0.9f,0.2f,1.0f)), D(GUI_COLOR_VOLMETER_PEAK,"",ImVec4(1.0f,0.1f,0.1f,1.0f)), diff --git a/src/gui/osc.cpp b/src/gui/osc.cpp index a4da29c8c..eb9a7b8c9 100644 --- a/src/gui/osc.cpp +++ b/src/gui/osc.cpp @@ -18,6 +18,8 @@ */ #include "gui.h" +#include "imgui_internal.h" +#include #include void FurnaceGUI::readOsc() { @@ -71,16 +73,88 @@ void FurnaceGUI::drawOsc() { } if (!oscOpen) return; ImGui::SetNextWindowSizeConstraints(ImVec2(64.0f*dpiScale,32.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); - ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding,ImVec2(0,0)); + /*ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding,ImVec2(0,0)); ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,ImVec2(0,0)); - ImGui::PushStyleVar(ImGuiStyleVar_ItemInnerSpacing,ImVec2(0,0)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemInnerSpacing,ImVec2(0,0));*/ if (ImGui::Begin("Oscilloscope",&oscOpen)) { - //ImGui::SetCursorPos(ImVec2(0,0)); - ImGui::BeginDisabled(); - ImGui::PlotLines("##SingleOsc",oscValues,512,0,NULL,-1.0f,1.0f,ImGui::GetContentRegionAvail()); - ImGui::EndDisabled(); + if (oscZoomSlider) { + if (ImGui::VSliderFloat("##OscZoom",ImVec2(20.0f*dpiScale,ImGui::GetContentRegionAvail().y),&oscZoom,0.5,2.0)) { + if (oscZoom<0.5) oscZoom=0.5; + if (oscZoom>2.0) oscZoom=2.0; + } + ImGui::SameLine(); + } + + ImDrawList* dl=ImGui::GetWindowDrawList(); + ImGuiWindow* window=ImGui::GetCurrentWindow(); + + ImVec2 waveform[512]; + ImVec2 size=ImGui::GetContentRegionAvail(); + + ImVec2 minArea=window->DC.CursorPos; + ImVec2 maxArea=ImVec2( + minArea.x+size.x, + minArea.y+size.y + ); + ImRect rect=ImRect(minArea,maxArea); + ImGuiStyle& style=ImGui::GetStyle(); + ImU32 color=ImGui::GetColorU32(isClipping?uiColors[GUI_COLOR_OSC_WAVE_PEAK]:uiColors[GUI_COLOR_OSC_WAVE]); + ImU32 borderColor=ImGui::GetColorU32(uiColors[GUI_COLOR_OSC_BORDER]); + ImGui::ItemSize(size,style.FramePadding.y); + if (ImGui::ItemAdd(rect,ImGui::GetID("wsDisplay"))) { + // https://github.com/ocornut/imgui/issues/3710 + // TODO: improve + const int v0 = dl->VtxBuffer.Size; + dl->AddRectFilled(rect.Min,rect.Max,0xffffffff,8.0f*dpiScale); + const int v1 = dl->VtxBuffer.Size; + + for (int i=v0; iVtxBuffer.Data[i]; + ImVec4 col0=uiColors[GUI_COLOR_OSC_BG1]; + ImVec4 col1=uiColors[GUI_COLOR_OSC_BG3]; + ImVec4 col2=uiColors[GUI_COLOR_OSC_BG2]; + ImVec4 col3=uiColors[GUI_COLOR_OSC_BG4]; + + float shadeX=(v->pos.x-rect.Min.x)/(rect.Max.x-rect.Min.x); + float shadeY=(v->pos.y-rect.Min.y)/(rect.Max.y-rect.Min.y); + if (shadeX<0.0f) shadeX=0.0f; + if (shadeX>1.0f) shadeX=1.0f; + if (shadeY<0.0f) shadeY=0.0f; + if (shadeY>1.0f) shadeY=1.0f; + + col0.x+=(col2.x-col0.x)*shadeX; + col0.y+=(col2.y-col0.y)*shadeX; + col0.z+=(col2.z-col0.z)*shadeX; + col0.w+=(col2.w-col0.w)*shadeX; + + col1.x+=(col3.x-col1.x)*shadeX; + col1.y+=(col3.y-col1.y)*shadeX; + col1.z+=(col3.z-col1.z)*shadeX; + col1.w+=(col3.w-col1.w)*shadeX; + + col0.x+=(col1.x-col0.x)*shadeY; + col0.y+=(col1.y-col0.y)*shadeY; + col0.z+=(col1.z-col0.z)*shadeY; + col0.w+=(col1.w-col0.w)*shadeY; + + v->col=ImGui::ColorConvertFloat4ToU32(col0); + } + + for (size_t i=0; i<512; i++) { + float x=(float)i/512.0f; + float y=oscValues[i]*oscZoom; + if (y<-0.5f) y=-0.5f; + if (y>0.5f) y=0.5f; + waveform[i]=ImLerp(rect.Min,rect.Max,ImVec2(x,0.5f-y)); + } + dl->AddPolyline(waveform,512,color,ImDrawFlags_None,dpiScale); + dl->AddRect(rect.Min,rect.Max,borderColor,8.0f*dpiScale,0,dpiScale); + } + if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { + oscZoomSlider=!oscZoomSlider; + } } - ImGui::PopStyleVar(3); + //ImGui::PopStyleVar(3); if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_OSCILLOSCOPE; ImGui::End(); -} \ No newline at end of file +} diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 1cbd6a06e..316e3ef51 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -942,6 +942,18 @@ void FurnaceGUI::drawSettings() { UI_COLOR_CONFIG(GUI_COLOR_FILE_OTHER,"Other"); ImGui::TreePop(); } + if (ImGui::TreeNode("Oscilloscope")) { + UI_COLOR_CONFIG(GUI_COLOR_OSC_BORDER,"Border"); + UI_COLOR_CONFIG(GUI_COLOR_OSC_BG1,"Background (top-left)"); + UI_COLOR_CONFIG(GUI_COLOR_OSC_BG2,"Background (top-right)"); + UI_COLOR_CONFIG(GUI_COLOR_OSC_BG3,"Background (bottom-left)"); + UI_COLOR_CONFIG(GUI_COLOR_OSC_BG4,"Background (bottom-right)"); + UI_COLOR_CONFIG(GUI_COLOR_OSC_WAVE,"Waveform"); + UI_COLOR_CONFIG(GUI_COLOR_OSC_WAVE_PEAK,"Waveform (clip)"); + UI_COLOR_CONFIG(GUI_COLOR_OSC_REF,"Reference"); + UI_COLOR_CONFIG(GUI_COLOR_OSC_GUIDE,"Guide"); + ImGui::TreePop(); + } if (ImGui::TreeNode("Volume Meter")) { UI_COLOR_CONFIG(GUI_COLOR_VOLMETER_LOW,"Low"); UI_COLOR_CONFIG(GUI_COLOR_VOLMETER_HIGH,"High"); From d5b07aa34794452543bbced4b56af8d1efc5e573 Mon Sep 17 00:00:00 2001 From: OPNA2608 Date: Sat, 9 Apr 2022 12:48:19 +0200 Subject: [PATCH 592/637] Move icon_enum_callback to separate function --- extern/pfd-fixed/portable-file-dialogs.h | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/extern/pfd-fixed/portable-file-dialogs.h b/extern/pfd-fixed/portable-file-dialogs.h index 3d90a854f..fc6df0a14 100644 --- a/extern/pfd-fixed/portable-file-dialogs.h +++ b/extern/pfd-fixed/portable-file-dialogs.h @@ -1331,6 +1331,14 @@ inline std::string internal::file_dialog::select_folder_vista(IFileDialog *ifd, // notify implementation +#if _WIN32 +inline BOOL WINAPI icon_enum_callback(HMODULE hModule, LPCTSTR lpType, LPTSTR lpName, LONG_PTR lParam) +{ + ((NOTIFYICONDATAW *)lParam)->hIcon = ::LoadIcon(GetModuleHandle(nullptr), lpName); + return false; +}; +#endif + inline notify::notify(std::string const &title, std::string const &message, icon _icon /* = icon::info */) @@ -1383,14 +1391,8 @@ inline notify::notify(std::string const &title, /* case icon::info: */ default: nid->dwInfoFlags = NIIF_INFO; break; } - ENUMRESNAMEPROC icon_enum_callback = [](HMODULE, LPCTSTR, LPTSTR lpName, LONG_PTR lParam) WINAPI -> BOOL - { - ((NOTIFYICONDATAW *)lParam)->hIcon = ::LoadIcon(GetModuleHandle(nullptr), lpName); - return false; - }; - nid->hIcon = ::LoadIcon(nullptr, IDI_APPLICATION); - ::EnumResourceNames(nullptr, RT_GROUP_ICON, icon_enum_callback, (LONG_PTR)nid.get()); + ::EnumResourceNames(nullptr, RT_GROUP_ICON, (ENUMRESNAMEPROC)icon_enum_callback, (LONG_PTR)nid.get()); nid->uTimeout = 5000; From ae4968318e978709e77e6ebf5d834f9df492d807 Mon Sep 17 00:00:00 2001 From: OPNA2608 Date: Sat, 9 Apr 2022 14:33:13 +0200 Subject: [PATCH 593/637] Tell SDL headers in MSVC build that we have a libc --- CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index c240e37b6..fef510efb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -177,6 +177,10 @@ else() add_subdirectory(extern/SDL EXCLUDE_FROM_ALL) list(APPEND DEPENDENCIES_INCLUDE_DIRS extern/SDL/include) list(APPEND DEPENDENCIES_LIBRARIES SDL2-static) + # Work around add_subdirectory'd SDL not propagating HAVE_LIBC to MSVC furnace build + if (MSVC) + list(APPEND DEPENDENCIES_COMPILE_OPTIONS "/DHAVE_LIBC") + endif() message(STATUS "Using vendored SDL2") endif() From b6fb3820da16d5ea05204b00dda347025f209f4b Mon Sep 17 00:00:00 2001 From: OPNA2608 Date: Sat, 9 Apr 2022 21:00:38 +0200 Subject: [PATCH 594/637] Remove unneeded includes Some are harmless, some screw with SDL's standard includes due to preceeding `_USE_MATH_DEFINES`. --- src/engine/engine.cpp | 3 --- src/engine/filter.cpp | 3 +-- src/engine/playback.cpp | 3 --- src/gui/pattern.cpp | 2 -- 4 files changed, 1 insertion(+), 10 deletions(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 78ae1ce49..a154277e8 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -17,9 +17,6 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "dataErrors.h" -#include "song.h" -#include #define _USE_MATH_DEFINES #include "engine.h" #include "instrument.h" diff --git a/src/engine/filter.cpp b/src/engine/filter.cpp index 729c8caa1..8e0ac02eb 100644 --- a/src/engine/filter.cpp +++ b/src/engine/filter.cpp @@ -17,7 +17,6 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include #define _USE_MATH_DEFINES #include #include "filter.h" @@ -85,4 +84,4 @@ float* DivFilterTables::getSincIntegralTable() { } } return sincIntegralTable; -} \ No newline at end of file +} diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 8e1f1d664..409b4a08a 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -17,9 +17,6 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include "blip_buf.h" -#include "song.h" -#include "wavetable.h" #define _USE_MATH_DEFINES #include "dispatch.h" #include "engine.h" diff --git a/src/gui/pattern.cpp b/src/gui/pattern.cpp index d4bf904b4..d6b6fce70 100644 --- a/src/gui/pattern.cpp +++ b/src/gui/pattern.cpp @@ -17,8 +17,6 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -#include -#include #define _USE_MATH_DEFINES #include "gui.h" #include "../ta-log.h" From 366a7a24a009bca878e768841f713a012c34821b Mon Sep 17 00:00:00 2001 From: OPNA2608 Date: Sat, 9 Apr 2022 21:22:25 +0200 Subject: [PATCH 595/637] Decide amount of build cores on CI based on platform GitHub-hosted macOS runners actually get 3 cores instead of 2, according to https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources --- .github/workflows/build.yml | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 19118f6a9..2fbae0f51 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -89,6 +89,19 @@ jobs: echo "::set-output name=id::${package_name}" echo "::set-output name=filename::${package_name}${package_ext}" + - name: Set build cores amount + id: build-cores + run: | + # https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#supported-runners-and-hardware-resources + set amount=2 + if [ '${{ runner.os }}' == 'macOS' ]; then + amount=3 + fi + + echo "Amount of cores we can build with: ${amount}" + + echo "::set-output name=amount::${amount}" + - name: Setup Toolchain [Windows MSVC] if: ${{ matrix.config.compiler == 'msvc' }} uses: seanmiddleditch/gha-setup-vsdevenv@v3 @@ -156,7 +169,7 @@ jobs: cmake \ --build ${PWD}/build \ --config ${{ env.BUILD_TYPE }} \ - --parallel 2 + --parallel ${{ steps.build-cores.outputs.amount }} - name: Install (System Libraries) if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' }} @@ -205,7 +218,7 @@ jobs: cmake \ --build ${PWD}/build \ --config ${{ env.BUILD_TYPE }} \ - --parallel 2 + --parallel ${{ steps.build-cores.outputs.amount }} - name: Package [Windows] if: ${{ runner.os == 'Windows' || matrix.config.compiler == 'mingw' }} From 38ca43719015f610488ed55a433ce200ddcf6d8d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 9 Apr 2022 18:25:25 -0500 Subject: [PATCH 596/637] DC offset improvements --- src/engine/dispatch.h | 12 ++++++++++++ src/engine/dispatchContainer.cpp | 8 ++++++++ src/engine/engine.h | 5 +++-- src/engine/platform/abstract.cpp | 8 ++++++++ src/engine/platform/ay.cpp | 4 ++++ src/engine/platform/ay.h | 1 + src/engine/platform/mmc5.cpp | 5 ++++- src/engine/platform/mmc5.h | 1 + src/engine/platform/nes.cpp | 18 ++++++++++++++++-- src/engine/platform/nes.h | 4 +++- src/engine/platform/saa.cpp | 10 ++++++++++ src/engine/platform/sound/nes/apu.c | 3 ++- src/engine/playback.cpp | 2 ++ src/gui/osc.cpp | 24 +++++++++++++++++++++--- 14 files changed, 95 insertions(+), 10 deletions(-) diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index b4394f608..5a1c2db64 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -319,6 +319,18 @@ class DivDispatch { */ virtual int getPortaFloor(int ch); + /** + * get the required amplification level of this dispatch's output. + * @return the amplification level. + */ + virtual float getPostAmp(); + + /** + * check whether DC offset correction is required. + * @return truth. + */ + virtual bool getDCOffRequired(); + /** * get a description of a dispatch-specific effect. * @param effect the effect. diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index ba72a6957..c89a0fa06 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -79,6 +79,11 @@ void DivDispatchContainer::flush(size_t count) { } void DivDispatchContainer::fillBuf(size_t runtotal, size_t offset, size_t size) { + if (dcOffCompensation && runtotal>0) { + dcOffCompensation=false; + prevSample[0]=bbIn[0][0]; + if (dispatch->isStereo()) prevSample[1]=bbIn[1][0]; + } if (lowQuality) { for (size_t i=0; igetDCOffRequired()) { + dcOffCompensation=true; + } // run for one cycle to determine DC offset // TODO: SAA1099 doesn't like that /*dispatch->acquire(bbIn[0],bbIn[1],0,1); diff --git a/src/engine/engine.h b/src/engine/engine.h index 68ec0ce5b..c359da253 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -154,7 +154,7 @@ struct DivDispatchContainer { int temp[2], prevSample[2]; short* bbIn[2]; short* bbOut[2]; - bool lowQuality; + bool lowQuality, dcOffCompensation; void setRates(double gotRate); void setQuality(bool lowQual); @@ -172,7 +172,8 @@ struct DivDispatchContainer { prevSample{0,0}, bbIn{NULL,NULL}, bbOut{NULL,NULL}, - lowQuality(false) {} + lowQuality(false), + dcOffCompensation(false) {} }; class DivEngine { diff --git a/src/engine/platform/abstract.cpp b/src/engine/platform/abstract.cpp index 7b6115aef..b860f6808 100644 --- a/src/engine/platform/abstract.cpp +++ b/src/engine/platform/abstract.cpp @@ -74,6 +74,14 @@ int DivDispatch::getPortaFloor(int ch) { return 0x00; } +float DivDispatch::getPostAmp() { + return 1.0f; +} + +bool DivDispatch::getDCOffRequired() { + return false; +} + const char* DivDispatch::getEffectName(unsigned char effect) { return NULL; } diff --git a/src/engine/platform/ay.cpp b/src/engine/platform/ay.cpp index b798cba4e..e174e345b 100644 --- a/src/engine/platform/ay.cpp +++ b/src/engine/platform/ay.cpp @@ -497,6 +497,10 @@ void DivPlatformAY8910::flushWrites() { while (!writes.empty()) writes.pop(); } +bool DivPlatformAY8910::getDCOffRequired() { + return true; +} + void DivPlatformAY8910::reset() { while (!writes.empty()) writes.pop(); ay->device_reset(); diff --git a/src/engine/platform/ay.h b/src/engine/platform/ay.h index 82b647f2d..b1a3ea127 100644 --- a/src/engine/platform/ay.h +++ b/src/engine/platform/ay.h @@ -95,6 +95,7 @@ class DivPlatformAY8910: public DivDispatch { void setFlags(unsigned int flags); bool isStereo(); bool keyOffAffectsArp(int ch); + bool getDCOffRequired(); void notifyInsDeletion(void* ins); void poke(unsigned int addr, unsigned short val); void poke(std::vector& wlist); diff --git a/src/engine/platform/mmc5.cpp b/src/engine/platform/mmc5.cpp index 3ccf95499..102d6a2e9 100644 --- a/src/engine/platform/mmc5.cpp +++ b/src/engine/platform/mmc5.cpp @@ -89,7 +89,6 @@ void DivPlatformMMC5::acquire(short* bufL, short* bufR, size_t start, size_t len if (!isMuted[2]) { sample+=mmc5->pcm.output*2; } - sample=(sample-128)<<6; if (sample>32767) sample=32767; if (sample<-32768) sample=-32768; bufL[i]=sample; @@ -336,6 +335,10 @@ int DivPlatformMMC5::getRegisterPoolSize() { return 32; } +float DivPlatformMMC5::getPostAmp() { + return 64.0f; +} + void DivPlatformMMC5::reset() { for (int i=0; i<3; i++) { chan[i]=DivPlatformMMC5::Channel(); diff --git a/src/engine/platform/mmc5.h b/src/engine/platform/mmc5.h index a29ffc7d4..6b364d5ad 100644 --- a/src/engine/platform/mmc5.h +++ b/src/engine/platform/mmc5.h @@ -74,6 +74,7 @@ class DivPlatformMMC5: public DivDispatch { void tick(); void muteChannel(int ch, bool mute); bool keyOffAffectsArp(int ch); + float getPostAmp(); void setFlags(unsigned int flags); void notifyInsDeletion(void* ins); void poke(unsigned int addr, unsigned short val); diff --git a/src/engine/platform/nes.cpp b/src/engine/platform/nes.cpp index f61ed4fb6..9dbf55543 100644 --- a/src/engine/platform/nes.cpp +++ b/src/engine/platform/nes.cpp @@ -80,7 +80,14 @@ void DivPlatformNES::acquire(short* bufL, short* bufR, size_t start, size_t len) DivSample* s=parent->getSample(dacSample); if (s->samples>0) { if (!isMuted[4]) { - rWrite(0x4011,((unsigned char)s->data8[dacPos]+0x80)>>1); + unsigned char next=((unsigned char)s->data8[dacPos]+0x80)>>1; + if (dacAntiClickOn && dacAntiClick=s->samples) { if (s->loopStart>=0 && s->loopStart<(int)s->samples) { @@ -101,7 +108,7 @@ void DivPlatformNES::acquire(short* bufL, short* bufR, size_t start, size_t len) if (nes->apu.clocked) { nes->apu.clocked=false; } - int sample=(pulse_output(nes)+tnd_output(nes)-128)<<7; + int sample=(pulse_output(nes)+tnd_output(nes)); if (sample>32767) sample=32767; if (sample<-32768) sample=-32768; bufL[i]=sample; @@ -454,6 +461,10 @@ int DivPlatformNES::getRegisterPoolSize() { return 32; } +float DivPlatformNES::getPostAmp() { + return 128.0f; +} + void DivPlatformNES::reset() { for (int i=0; i<5; i++) { chan[i]=DivPlatformNES::Channel(); @@ -476,6 +487,9 @@ void DivPlatformNES::reset() { rWrite(0x4015,0x1f); rWrite(0x4001,chan[0].sweep); rWrite(0x4005,chan[1].sweep); + + dacAntiClickOn=true; + dacAntiClick=0; } bool DivPlatformNES::keyOffAffectsArp(int ch) { diff --git a/src/engine/platform/nes.h b/src/engine/platform/nes.h index c1b699bcf..1dbb4411b 100644 --- a/src/engine/platform/nes.h +++ b/src/engine/platform/nes.h @@ -54,10 +54,11 @@ class DivPlatformNES: public DivDispatch { Channel chan[5]; bool isMuted[5]; int dacPeriod, dacRate; - unsigned int dacPos; + unsigned int dacPos, dacAntiClick; int dacSample; unsigned char sampleBank; unsigned char apuType; + bool dacAntiClickOn; struct NESAPU* nes; unsigned char regPool[128]; @@ -74,6 +75,7 @@ class DivPlatformNES: public DivDispatch { void tick(); void muteChannel(int ch, bool mute); bool keyOffAffectsArp(int ch); + float getPostAmp(); void setFlags(unsigned int flags); void notifyInsDeletion(void* ins); void poke(unsigned int addr, unsigned short val); diff --git a/src/engine/platform/saa.cpp b/src/engine/platform/saa.cpp index 0bafaf157..49adf5b13 100644 --- a/src/engine/platform/saa.cpp +++ b/src/engine/platform/saa.cpp @@ -414,6 +414,16 @@ void DivPlatformSAA1099::reset() { extMode=false; + rWrite(8,255); + rWrite(9,255); + rWrite(10,255); + rWrite(11,255); + rWrite(12,255); + rWrite(13,255); + rWrite(16,0x77); + rWrite(17,0x77); + rWrite(18,0x77); + rWrite(0x1c,2); rWrite(0x1c,1); } diff --git a/src/engine/platform/sound/nes/apu.c b/src/engine/platform/sound/nes/apu.c index 8d53e583c..9e177a780 100644 --- a/src/engine/platform/sound/nes/apu.c +++ b/src/engine/platform/sound/nes/apu.c @@ -213,7 +213,8 @@ void apu_turn_on(struct NESAPU* a, BYTE apu_type) { a->S2.sweep.delay = 1; a->S2.sweep.divider = 1; a->TR.frequency = 1; - a->TR.sequencer = 0; + /* questo era 0 ma produce click nell'audio */ + a->TR.sequencer = 7; a->NS.frequency = 1; a->NS.shift = 1; a->DMC.frequency = 1; diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 4a5cc6465..0118acc42 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -1863,6 +1863,8 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi for (int i=0; igetPostAmp(); + volR*=disCont[i].dispatch->getPostAmp(); if (disCont[i].dispatch->isStereo()) { for (size_t j=0; jVtxBuffer.Size; - dl->AddRectFilled(rect.Min,rect.Max,0xffffffff,8.0f*dpiScale); + dl->AddRectFilled(inRect.Min,inRect.Max,0xffffffff,8.0f*dpiScale); const int v1 = dl->VtxBuffer.Size; for (int i=v0; icol); + col0.x*=conv.x; + col0.y*=conv.y; + col0.z*=conv.z; + col0.w*=conv.w; + v->col=ImGui::ColorConvertFloat4ToU32(col0); } + dl->AddLine( + ImLerp(rect.Min,rect.Max,ImVec2(0.0f,0.5f)), + ImLerp(rect.Min,rect.Max,ImVec2(0.0f,0.5f)), + refColor, + dpiScale + ); + for (size_t i=0; i<512; i++) { float x=(float)i/512.0f; float y=oscValues[i]*oscZoom; @@ -148,7 +166,7 @@ void FurnaceGUI::drawOsc() { waveform[i]=ImLerp(rect.Min,rect.Max,ImVec2(x,0.5f-y)); } dl->AddPolyline(waveform,512,color,ImDrawFlags_None,dpiScale); - dl->AddRect(rect.Min,rect.Max,borderColor,8.0f*dpiScale,0,dpiScale); + dl->AddRect(rect.Min,rect.Max,borderColor,8.0f*dpiScale,0,2.0f*dpiScale); } if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { oscZoomSlider=!oscZoomSlider; From 0eb02422d5161767e9983bdaa5c429762d3477ce Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sat, 9 Apr 2022 18:25:38 -0500 Subject: [PATCH 597/637] fix possible pattern crash issue #325 --- src/gui/pattern.cpp | 44 +++++++++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/src/gui/pattern.cpp b/src/gui/pattern.cpp index d6b6fce70..d56196d6c 100644 --- a/src/gui/pattern.cpp +++ b/src/gui/pattern.cpp @@ -282,27 +282,33 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int sprintf(id,"..##PE%d_%d_%d",k,i,j); ImGui::PushStyleColor(ImGuiCol_Text,inactiveColor); } else { - sprintf(id,"%.2X##PE%d_%d_%d",pat->data[i][index],k,i,j); - if (pat->data[i][index]<0x10) { - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[fxColors[pat->data[i][index]]]); - } else if (pat->data[i][index]<0x20) { - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY]); - } else if (pat->data[i][index]<0x30) { - 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) { + if (pat->data[i][index]>0xff) { + sprintf(id,"??##PE%d_%d_%d",k,i,j); ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_INVALID]); } else { - ImGui::PushStyleColor(ImGuiCol_Text,uiColors[extFxColors[pat->data[i][index]-0xe0]]); + const unsigned char data=pat->data[i][index]; + sprintf(id,"%.2X##PE%d_%d_%d",data,k,i,j); + if (data<0x10) { + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[fxColors[data]]); + } else if (data<0x20) { + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY]); + } else if (data<0x30) { + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_SYS_SECONDARY]); + } else if (data<0x48) { + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY]); + } else if (data<0x90) { + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_INVALID]); + } else if (data<0xa0) { + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_MISC]); + } else if (data<0xc0) { + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_INVALID]); + } else if (data<0xd0) { + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_SPEED]); + } else if (data<0xe0) { + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_INVALID]); + } else { + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[extFxColors[data-0xe0]]); + } } } ImGui::SameLine(0.0f,0.0f); From d3e5efe834fb1d88d66684a93eb41d4c1f3ad195 Mon Sep 17 00:00:00 2001 From: cam900 Date: Sun, 10 Apr 2022 14:01:55 +0900 Subject: [PATCH 598/637] Prepare for macro refactoring --- src/engine/engine.h | 2 +- src/engine/fileOps.cpp | 134 ++-- src/engine/fileOpsIns.cpp | 78 +- src/engine/instrument.cpp | 1197 +++++++++++++++++------------ src/engine/instrument.h | 363 +++------ src/engine/macroInt.cpp | 563 ++++++-------- src/engine/macroInt.h | 357 +++------ src/engine/platform/amiga.cpp | 26 +- src/engine/platform/arcade.cpp | 100 +-- src/engine/platform/ay.cpp | 40 +- src/engine/platform/ay8930.cpp | 54 +- src/engine/platform/bubsyswsg.cpp | 26 +- src/engine/platform/c64.cpp | 50 +- src/engine/platform/fds.cpp | 48 +- src/engine/platform/gb.cpp | 30 +- src/engine/platform/genesis.cpp | 78 +- src/engine/platform/lynx.cpp | 32 +- src/engine/platform/mmc5.cpp | 22 +- src/engine/platform/n163.cpp | 74 +- src/engine/platform/nes.cpp | 28 +- src/engine/platform/opl.cpp | 78 +- src/engine/platform/opll.cpp | 82 +- src/engine/platform/pce.cpp | 34 +- src/engine/platform/pcspkr.cpp | 18 +- src/engine/platform/pet.cpp | 26 +- src/engine/platform/qsound.cpp | 22 +- src/engine/platform/saa.cpp | 28 +- src/engine/platform/segapcm.cpp | 16 +- src/engine/platform/sms.cpp | 38 +- src/engine/platform/swan.cpp | 32 +- src/engine/platform/tia.cpp | 22 +- src/engine/platform/tx81z.cpp | 100 +-- src/engine/platform/vera.cpp | 38 +- src/engine/platform/vic20.cpp | 24 +- src/engine/platform/vrc6.cpp | 24 +- src/engine/platform/x1_010.cpp | 60 +- src/engine/platform/ym2610.cpp | 94 +-- src/engine/platform/ym2610b.cpp | 94 +-- src/engine/safeReader.cpp | 36 + src/engine/safeReader.h | 6 + src/engine/safeWriter.cpp | 33 + src/engine/safeWriter.h | 1 + src/gui/insEdit.cpp | 438 ++++++----- 43 files changed, 2337 insertions(+), 2309 deletions(-) diff --git a/src/engine/engine.h b/src/engine/engine.h index 890056758..bf9fe07ec 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -43,7 +43,7 @@ #define BUSY_END isBusy.unlock(); softLocked=false; #define DIV_VERSION "dev80" -#define DIV_ENGINE_VERSION 80 +#define DIV_ENGINE_VERSION (80/*Test*/|0x80) // for imports #define DIV_VERSION_MOD 0xff01 diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 37964c682..63cc15361 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -307,12 +307,12 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { if (!ins->mode) { ins->type=DIV_INS_AY; } - ins->std.dutyMacroHeight=31; - ins->std.waveMacroHeight=7; + ins->std.dutyMacro.height=31; + ins->std.waveMacro.height=7; } if (ds.system[0]==DIV_SYSTEM_PCE) { ins->type=DIV_INS_PCE; - ins->std.volMacroHeight=31; + ins->std.volMacro.height=31; } if ((ds.system[0]==DIV_SYSTEM_SMS_OPLL || ds.system[0]==DIV_SYSTEM_NES_VRC7) && ins->type==DIV_INS_FM) { ins->type=DIV_INS_OPLL; @@ -425,76 +425,76 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { } } else { // STD if (ds.system[0]!=DIV_SYSTEM_GB || ds.version<0x12) { - ins->std.volMacroLen=reader.readC(); - for (int j=0; jstd.volMacroLen; j++) { + ins->std.volMacro.len=reader.readC(); + for (int j=0; jstd.volMacro.len; j++) { if (ds.version<0x0e) { - ins->std.volMacro[j]=reader.readC(); + ins->std.volMacro.val[j]=reader.readC(); } else { - ins->std.volMacro[j]=reader.readI(); + ins->std.volMacro.val[j]=reader.readI(); } } - if (ins->std.volMacroLen>0) { - ins->std.volMacroOpen=true; - ins->std.volMacroLoop=reader.readC(); + if (ins->std.volMacro.len>0) { + ins->std.volMacro.open=true; + ins->std.volMacro.loop=reader.readC(); } else { - ins->std.volMacroOpen=false; + ins->std.volMacro.open=false; } } - ins->std.arpMacroLen=reader.readC(); - for (int j=0; jstd.arpMacroLen; j++) { + ins->std.arpMacro.len=reader.readC(); + for (int j=0; jstd.arpMacro.len; j++) { if (ds.version<0x0e) { - ins->std.arpMacro[j]=reader.readC(); + ins->std.arpMacro.val[j]=reader.readC(); } else { - ins->std.arpMacro[j]=reader.readI(); + ins->std.arpMacro.val[j]=reader.readI(); } } - if (ins->std.arpMacroLen>0) { - ins->std.arpMacroLoop=reader.readC(); - ins->std.arpMacroOpen=true; + if (ins->std.arpMacro.len>0) { + ins->std.arpMacro.loop=reader.readC(); + ins->std.arpMacro.open=true; } else { - ins->std.arpMacroOpen=false; + ins->std.arpMacro.open=false; } if (ds.version>0x0f) { - ins->std.arpMacroMode=reader.readC(); + ins->std.arpMacro.mode=reader.readC(); } - if (!ins->std.arpMacroMode) { - for (int j=0; jstd.arpMacroLen; j++) { - ins->std.arpMacro[j]-=12; + if (!ins->std.arpMacro.mode) { + for (int j=0; jstd.arpMacro.len; j++) { + ins->std.arpMacro.val[j]-=12; } } - ins->std.dutyMacroLen=reader.readC(); - for (int j=0; jstd.dutyMacroLen; j++) { + ins->std.dutyMacro.len=reader.readC(); + for (int j=0; jstd.dutyMacro.len; j++) { if (ds.version<0x0e) { - ins->std.dutyMacro[j]=reader.readC(); + ins->std.dutyMacro.val[j]=reader.readC(); } else { - ins->std.dutyMacro[j]=reader.readI(); + ins->std.dutyMacro.val[j]=reader.readI(); } - if ((ds.system[0]==DIV_SYSTEM_C64_8580 || ds.system[0]==DIV_SYSTEM_C64_6581) && ins->std.dutyMacro[j]>24) { - ins->std.dutyMacro[j]=24; + if ((ds.system[0]==DIV_SYSTEM_C64_8580 || ds.system[0]==DIV_SYSTEM_C64_6581) && ins->std.dutyMacro.val[j]>24) { + ins->std.dutyMacro.val[j]=24; } } - if (ins->std.dutyMacroLen>0) { - ins->std.dutyMacroOpen=true; - ins->std.dutyMacroLoop=reader.readC(); + if (ins->std.dutyMacro.len>0) { + ins->std.dutyMacro.open=true; + ins->std.dutyMacro.loop=reader.readC(); } else { - ins->std.dutyMacroOpen=false; + ins->std.dutyMacro.open=false; } - ins->std.waveMacroLen=reader.readC(); - for (int j=0; jstd.waveMacroLen; j++) { + ins->std.waveMacro.len=reader.readC(); + for (int j=0; jstd.waveMacro.len; j++) { if (ds.version<0x0e) { - ins->std.waveMacro[j]=reader.readC(); + ins->std.waveMacro.val[j]=reader.readC(); } else { - ins->std.waveMacro[j]=reader.readI(); + ins->std.waveMacro.val[j]=reader.readI(); } } - if (ins->std.waveMacroLen>0) { - ins->std.waveMacroOpen=true; - ins->std.waveMacroLoop=reader.readC(); + if (ins->std.waveMacro.len>0) { + ins->std.waveMacro.open=true; + ins->std.waveMacro.loop=reader.readC(); } else { - ins->std.waveMacroOpen=false; + ins->std.waveMacro.open=false; } if (ds.system[0]==DIV_SYSTEM_C64_6581 || ds.system[0]==DIV_SYSTEM_C64_8580) { @@ -533,18 +533,18 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { ins->gb.envDir=reader.readC(); ins->gb.envLen=reader.readC(); ins->gb.soundLen=reader.readC(); - ins->std.volMacroOpen=false; + ins->std.volMacro.open=false; logD("GB data: vol %d dir %d len %d sl %d\n",ins->gb.envVol,ins->gb.envDir,ins->gb.envLen,ins->gb.soundLen); } else if (ds.system[0]==DIV_SYSTEM_GB) { // try to convert macro to envelope - if (ins->std.volMacroLen>0) { - ins->gb.envVol=ins->std.volMacro[0]; - if (ins->std.volMacro[0]std.volMacro[1]) { + if (ins->std.volMacro.len>0) { + ins->gb.envVol=ins->std.volMacro.val[0]; + if (ins->std.volMacro.val[0]std.volMacro.val[1]) { ins->gb.envDir=true; } - if (ins->std.volMacro[ins->std.volMacroLen-1]==0) { - ins->gb.soundLen=ins->std.volMacroLen*2; + if (ins->std.volMacro.val[ins->std.volMacro.len-1]==0) { + ins->gb.soundLen=ins->std.volMacro.len*2; } } addWarning("Game Boy volume macros converted to envelopes. may not be perfect!"); @@ -2446,36 +2446,36 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { } } else { // STD if (sys!=DIV_SYSTEM_GB) { - w->writeC(i->std.volMacroLen); - w->write(i->std.volMacro,4*i->std.volMacroLen); - if (i->std.volMacroLen>0) { - w->writeC(i->std.volMacroLoop); + w->writeC(i->std.volMacro.len); + w->write(i->std.volMacro.val,4*i->std.volMacro.len); + if (i->std.volMacro.len>0) { + w->writeC(i->std.volMacro.loop); } } - w->writeC(i->std.arpMacroLen); - if (i->std.arpMacroMode) { - w->write(i->std.arpMacro,4*i->std.arpMacroLen); + w->writeC(i->std.arpMacro.len); + if (i->std.arpMacro.mode) { + w->write(i->std.arpMacro.val,4*i->std.arpMacro.len); } else { - for (int j=0; jstd.arpMacroLen; j++) { - w->writeI(i->std.arpMacro[j]+12); + for (int j=0; jstd.arpMacro.len; j++) { + w->writeI(i->std.arpMacro.val[j]+12); } } - if (i->std.arpMacroLen>0) { - w->writeC(i->std.arpMacroLoop); + if (i->std.arpMacro.len>0) { + w->writeC(i->std.arpMacro.loop); } - w->writeC(i->std.arpMacroMode); + w->writeC(i->std.arpMacro.mode); - w->writeC(i->std.dutyMacroLen); - w->write(i->std.dutyMacro,4*i->std.dutyMacroLen); - if (i->std.dutyMacroLen>0) { - w->writeC(i->std.dutyMacroLoop); + w->writeC(i->std.dutyMacro.len); + w->write(i->std.dutyMacro.val,4*i->std.dutyMacro.len); + if (i->std.dutyMacro.len>0) { + w->writeC(i->std.dutyMacro.loop); } - w->writeC(i->std.waveMacroLen); - w->write(i->std.waveMacro,4*i->std.waveMacroLen); - if (i->std.waveMacroLen>0) { - w->writeC(i->std.waveMacroLoop); + w->writeC(i->std.waveMacro.len); + w->write(i->std.waveMacro.val,4*i->std.waveMacro.len); + if (i->std.waveMacro.len>0) { + w->writeC(i->std.waveMacro.loop); } if (sys==DIV_SYSTEM_C64_6581 || sys==DIV_SYSTEM_C64_8580) { diff --git a/src/engine/fileOpsIns.cpp b/src/engine/fileOpsIns.cpp index 6689c5547..6658eea95 100644 --- a/src/engine/fileOpsIns.cpp +++ b/src/engine/fileOpsIns.cpp @@ -181,79 +181,79 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector& ret, St } else { // STD logD("reading STD data...\n"); if (ins->type!=DIV_INS_GB) { - ins->std.volMacroLen=reader.readC(); + ins->std.volMacro.len=reader.readC(); if (version>5) { - for (int i=0; istd.volMacroLen; i++) { - ins->std.volMacro[i]=reader.readI(); + for (int i=0; istd.volMacro.len; i++) { + ins->std.volMacro.val[i]=reader.readI(); } } else { - for (int i=0; istd.volMacroLen; i++) { - ins->std.volMacro[i]=reader.readC(); + for (int i=0; istd.volMacro.len; i++) { + ins->std.volMacro.val[i]=reader.readC(); } } - if (version<11) for (int i=0; istd.volMacroLen; i++) { - if (ins->std.volMacro[i]>15 && ins->type==DIV_INS_STD) ins->type=DIV_INS_PCE; + if (version<11) for (int i=0; istd.volMacro.len; i++) { + if (ins->std.volMacro.val[i]>15 && ins->type==DIV_INS_STD) ins->type=DIV_INS_PCE; } - if (ins->std.volMacroLen>0) { - ins->std.volMacroOpen=true; - ins->std.volMacroLoop=reader.readC(); + if (ins->std.volMacro.len>0) { + ins->std.volMacro.open=true; + ins->std.volMacro.loop=reader.readC(); } else { - ins->std.volMacroOpen=false; + ins->std.volMacro.open=false; } } - ins->std.arpMacroLen=reader.readC(); + ins->std.arpMacro.len=reader.readC(); if (version>5) { - for (int i=0; istd.arpMacroLen; i++) { - ins->std.arpMacro[i]=reader.readI(); + for (int i=0; istd.arpMacro.len; i++) { + ins->std.arpMacro.val[i]=reader.readI(); } } else { - for (int i=0; istd.arpMacroLen; i++) { - ins->std.arpMacro[i]=reader.readC(); + for (int i=0; istd.arpMacro.len; i++) { + ins->std.arpMacro.val[i]=reader.readC(); } } - if (ins->std.arpMacroLen>0) { - ins->std.arpMacroOpen=true; - ins->std.arpMacroLoop=reader.readC(); + if (ins->std.arpMacro.len>0) { + ins->std.arpMacro.open=true; + ins->std.arpMacro.loop=reader.readC(); } else { - ins->std.arpMacroOpen=false; + ins->std.arpMacro.open=false; } if (version>8) { // TODO: when? - ins->std.arpMacroMode=reader.readC(); + ins->std.arpMacro.mode=reader.readC(); } - ins->std.dutyMacroLen=reader.readC(); + ins->std.dutyMacro.len=reader.readC(); if (version>5) { - for (int i=0; istd.dutyMacroLen; i++) { - ins->std.dutyMacro[i]=reader.readI(); + for (int i=0; istd.dutyMacro.len; i++) { + ins->std.dutyMacro.val[i]=reader.readI(); } } else { - for (int i=0; istd.dutyMacroLen; i++) { - ins->std.dutyMacro[i]=reader.readC(); + for (int i=0; istd.dutyMacro.len; i++) { + ins->std.dutyMacro.val[i]=reader.readC(); } } - if (ins->std.dutyMacroLen>0) { - ins->std.dutyMacroOpen=true; - ins->std.dutyMacroLoop=reader.readC(); + if (ins->std.dutyMacro.len>0) { + ins->std.dutyMacro.open=true; + ins->std.dutyMacro.loop=reader.readC(); } else { - ins->std.dutyMacroOpen=false; + ins->std.dutyMacro.open=false; } - ins->std.waveMacroLen=reader.readC(); + ins->std.waveMacro.len=reader.readC(); if (version>5) { - for (int i=0; istd.waveMacroLen; i++) { - ins->std.waveMacro[i]=reader.readI(); + for (int i=0; istd.waveMacro.len; i++) { + ins->std.waveMacro.val[i]=reader.readI(); } } else { - for (int i=0; istd.waveMacroLen; i++) { - ins->std.waveMacro[i]=reader.readC(); + for (int i=0; istd.waveMacro.len; i++) { + ins->std.waveMacro.val[i]=reader.readC(); } } - if (ins->std.waveMacroLen>0) { - ins->std.waveMacroOpen=true; - ins->std.waveMacroLoop=reader.readC(); + if (ins->std.waveMacro.len>0) { + ins->std.waveMacro.open=true; + ins->std.waveMacro.loop=reader.readC(); } else { - ins->std.waveMacroOpen=false; + ins->std.waveMacro.open=false; } if (ins->type==DIV_INS_C64) { diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index 60ce6810b..9369df9db 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -23,13 +23,37 @@ #include "../ta-log.h" #include "../fileutils.h" +void DivInstrument::putMacroData(DivInstrumentMacro m, SafeWriter* w) { + w->write("MACR",4); + w->writeI(0); + + w->writeS(DIV_ENGINE_VERSION); + + w->writeS(type); + w->writeC(0); + + w->writeString(m.name,false); + + w->writeI(m.len); + w->writeI(m.loop); + w->writeI(m.rel); + w->writeI(m.mode); + w->writeC(m.open); + for (int v=0; vwriteI(m.val[v]); + w->writeI(0); // reserved + w->writeI(0); // reserved + } +} + void DivInstrument::putInsData(SafeWriter* w) { w->write("INST",4); w->writeI(0); w->writeS(DIV_ENGINE_VERSION); - w->writeC(type); + w->writeS(type); + //w->writeC(type); w->writeC(0); w->writeString(name,false); @@ -101,7 +125,7 @@ void DivInstrument::putInsData(SafeWriter* w) { w->writeC(c64.hp); w->writeC(c64.ch3off); w->writeS(c64.cut); - w->writeC(c64.dutyIsAbs); + //w->writeC(std.dutyMacro.mode); w->writeC(c64.filterIsAbs); // Amiga @@ -111,266 +135,326 @@ void DivInstrument::putInsData(SafeWriter* w) { } // standard - w->writeI(std.volMacroLen); - w->writeI(std.arpMacroLen); - w->writeI(std.dutyMacroLen); - w->writeI(std.waveMacroLen); - w->writeI(std.pitchMacroLen); - w->writeI(std.ex1MacroLen); - w->writeI(std.ex2MacroLen); - w->writeI(std.ex3MacroLen); - w->writeI(std.volMacroLoop); - w->writeI(std.arpMacroLoop); - w->writeI(std.dutyMacroLoop); - w->writeI(std.waveMacroLoop); - w->writeI(std.pitchMacroLoop); - w->writeI(std.ex1MacroLoop); - w->writeI(std.ex2MacroLoop); - w->writeI(std.ex3MacroLoop); - w->writeC(std.arpMacroMode); + putMacroData(std.volMacro,w); + putMacroData(std.arpMacro,w); + putMacroData(std.dutyMacro,w); + putMacroData(std.waveMacro,w); + putMacroData(std.pitchMacro,w); + putMacroData(std.ex1Macro,w); + putMacroData(std.ex2Macro,w); + putMacroData(std.ex3Macro,w); + putMacroData(std.algMacro,w); + putMacroData(std.fbMacro,w); + putMacroData(std.fmsMacro,w); + putMacroData(std.fms2Macro,w); + putMacroData(std.amsMacro,w); + putMacroData(std.ams2Macro,w); + putMacroData(std.panLMacro,w); + putMacroData(std.panRMacro,w); + putMacroData(std.phaseResetMacro,w); + putMacroData(std.ex4Macro,w); + putMacroData(std.ex5Macro,w); + putMacroData(std.ex6Macro,w); + putMacroData(std.ex7Macro,w); + putMacroData(std.ex8Macro,w); + // FM macros + for (int i=0; i<4; i++) { + DivInstrumentSTD::OpMacro& op=std.opMacros[i]; + putMacroData(op.amMacro,w); + putMacroData(op.arMacro,w); + putMacroData(op.drMacro,w); + putMacroData(op.multMacro,w); + putMacroData(op.rrMacro,w); + putMacroData(op.slMacro,w); + putMacroData(op.tlMacro,w); + putMacroData(op.dt2Macro,w); + putMacroData(op.rsMacro,w); + putMacroData(op.dtMacro,w); + putMacroData(op.d2rMacro,w); + putMacroData(op.ssgMacro,w); + putMacroData(op.damMacro,w); + putMacroData(op.dvbMacro,w); + putMacroData(op.egtMacro,w); + putMacroData(op.kslMacro,w); + putMacroData(op.susMacro,w); + putMacroData(op.vibMacro,w); + putMacroData(op.wsMacro,w); + putMacroData(op.ksrMacro,w); + } + // wavesynth macros + putMacroData(std.ws.wave1Macro,w); + putMacroData(std.ws.wave2Macro,w); + putMacroData(std.ws.rateDividerMacro,w); + putMacroData(std.ws.effectMacro,w); + putMacroData(std.ws.oneShotMacro,w); + putMacroData(std.ws.enabledMacro,w); + putMacroData(std.ws.globalMacro,w); + putMacroData(std.ws.speedMacro,w); + putMacroData(std.ws.param1Macro,w); + putMacroData(std.ws.param2Macro,w); + putMacroData(std.ws.param3Macro,w); + putMacroData(std.ws.param4Macro,w); + /* + w->writeI(std.volMacro.len); + w->writeI(std.arpMacro.len); + w->writeI(std.dutyMacro.len); + w->writeI(std.waveMacro.len); + w->writeI(std.pitchMacro.len); + w->writeI(std.ex1Macro.len); + w->writeI(std.ex2Macro.len); + w->writeI(std.ex3Macro.len); + w->writeI(std.volMacro.loop); + w->writeI(std.arpMacro.loop); + w->writeI(std.dutyMacro.loop); + w->writeI(std.waveMacro.loop); + w->writeI(std.pitchMacro.loop); + w->writeI(std.ex1Macro.loop); + w->writeI(std.ex2Macro.loop); + w->writeI(std.ex3Macro.loop); + w->writeC(std.arpMacro.mode); w->writeC(0); // reserved w->writeC(0); w->writeC(0); - for (int j=0; jwriteI(std.volMacro[j]); + for (int j=0; jwriteI(std.volMacro.val[j]); } - for (int j=0; jwriteI(std.arpMacro[j]); + for (int j=0; jwriteI(std.arpMacro.val[j]); } - for (int j=0; jwriteI(std.dutyMacro[j]); + for (int j=0; jwriteI(std.dutyMacro.val[j]); } - for (int j=0; jwriteI(std.waveMacro[j]); + for (int j=0; jwriteI(std.waveMacro.val[j]); } - for (int j=0; jwriteI(std.pitchMacro[j]); + for (int j=0; jwriteI(std.pitchMacro.val[j]); } - for (int j=0; jwriteI(std.ex1Macro[j]); + for (int j=0; jwriteI(std.ex1Macro.val[j]); } - for (int j=0; jwriteI(std.ex2Macro[j]); + for (int j=0; jwriteI(std.ex2Macro.val[j]); } - for (int j=0; jwriteI(std.ex3Macro[j]); + for (int j=0; jwriteI(std.ex3Macro.val[j]); } // FM macros and open status - w->writeI(std.algMacroLen); - w->writeI(std.fbMacroLen); - w->writeI(std.fmsMacroLen); - w->writeI(std.amsMacroLen); - w->writeI(std.algMacroLoop); - w->writeI(std.fbMacroLoop); - w->writeI(std.fmsMacroLoop); - w->writeI(std.amsMacroLoop); + w->writeI(std.algMacro.len); + w->writeI(std.fbMacro.len); + w->writeI(std.fmsMacro.len); + w->writeI(std.amsMacro.len); + w->writeI(std.algMacro.loop); + w->writeI(std.fbMacro.loop); + w->writeI(std.fmsMacro.loop); + w->writeI(std.amsMacro.loop); - w->writeC(std.volMacroOpen); - w->writeC(std.arpMacroOpen); - w->writeC(std.dutyMacroOpen); - w->writeC(std.waveMacroOpen); - w->writeC(std.pitchMacroOpen); - w->writeC(std.ex1MacroOpen); - w->writeC(std.ex2MacroOpen); - w->writeC(std.ex3MacroOpen); - w->writeC(std.algMacroOpen); - w->writeC(std.fbMacroOpen); - w->writeC(std.fmsMacroOpen); - w->writeC(std.amsMacroOpen); + w->writeC(std.volMacro.open); + w->writeC(std.arpMacro.open); + w->writeC(std.dutyMacro.open); + w->writeC(std.waveMacro.open); + w->writeC(std.pitchMacro.open); + w->writeC(std.ex1Macro.open); + w->writeC(std.ex2Macro.open); + w->writeC(std.ex3Macro.open); + w->writeC(std.algMacro.open); + w->writeC(std.fbMacro.open); + w->writeC(std.fmsMacro.open); + w->writeC(std.amsMacro.open); - for (int j=0; jwriteI(std.algMacro[j]); + for (int j=0; jwriteI(std.algMacro.val[j]); } - for (int j=0; jwriteI(std.fbMacro[j]); + for (int j=0; jwriteI(std.fbMacro.val[j]); } - for (int j=0; jwriteI(std.fmsMacro[j]); + for (int j=0; jwriteI(std.fmsMacro.val[j]); } - for (int j=0; jwriteI(std.amsMacro[j]); + for (int j=0; jwriteI(std.amsMacro.val[j]); } for (int i=0; i<4; i++) { DivInstrumentSTD::OpMacro& op=std.opMacros[i]; - w->writeI(op.amMacroLen); - w->writeI(op.arMacroLen); - w->writeI(op.drMacroLen); - w->writeI(op.multMacroLen); - w->writeI(op.rrMacroLen); - w->writeI(op.slMacroLen); - w->writeI(op.tlMacroLen); - w->writeI(op.dt2MacroLen); - w->writeI(op.rsMacroLen); - w->writeI(op.dtMacroLen); - w->writeI(op.d2rMacroLen); - w->writeI(op.ssgMacroLen); - w->writeI(op.amMacroLoop); - w->writeI(op.arMacroLoop); - w->writeI(op.drMacroLoop); - w->writeI(op.multMacroLoop); - w->writeI(op.rrMacroLoop); - w->writeI(op.slMacroLoop); - w->writeI(op.tlMacroLoop); - w->writeI(op.dt2MacroLoop); - w->writeI(op.rsMacroLoop); - w->writeI(op.dtMacroLoop); - w->writeI(op.d2rMacroLoop); - w->writeI(op.ssgMacroLoop); - w->writeC(op.amMacroOpen); - w->writeC(op.arMacroOpen); - w->writeC(op.drMacroOpen); - w->writeC(op.multMacroOpen); - w->writeC(op.rrMacroOpen); - w->writeC(op.slMacroOpen); - w->writeC(op.tlMacroOpen); - w->writeC(op.dt2MacroOpen); - w->writeC(op.rsMacroOpen); - w->writeC(op.dtMacroOpen); - w->writeC(op.d2rMacroOpen); - w->writeC(op.ssgMacroOpen); + w->writeI(op.amMacro.len); + w->writeI(op.arMacro.len); + w->writeI(op.drMacro.len); + w->writeI(op.multMacro.len); + w->writeI(op.rrMacro.len); + w->writeI(op.slMacro.len); + w->writeI(op.tlMacro.len); + w->writeI(op.dt2Macro.len); + w->writeI(op.rsMacro.len); + w->writeI(op.dtMacro.len); + w->writeI(op.d2rMacro.len); + w->writeI(op.ssgMacro.len); + w->writeI(op.amMacro.loop); + w->writeI(op.arMacro.loop); + w->writeI(op.drMacro.loop); + w->writeI(op.multMacro.loop); + w->writeI(op.rrMacro.loop); + w->writeI(op.slMacro.loop); + w->writeI(op.tlMacro.loop); + w->writeI(op.dt2Macro.loop); + w->writeI(op.rsMacro.loop); + w->writeI(op.dtMacro.loop); + w->writeI(op.d2rMacro.loop); + w->writeI(op.ssgMacro.loop); + w->writeC(op.amMacro.open); + w->writeC(op.arMacro.open); + w->writeC(op.drMacro.open); + w->writeC(op.multMacro.open); + w->writeC(op.rrMacro.open); + w->writeC(op.slMacro.open); + w->writeC(op.tlMacro.open); + w->writeC(op.dt2Macro.open); + w->writeC(op.rsMacro.open); + w->writeC(op.dtMacro.open); + w->writeC(op.d2rMacro.open); + w->writeC(op.ssgMacro.open); } for (int i=0; i<4; i++) { DivInstrumentSTD::OpMacro& op=std.opMacros[i]; - for (int j=0; jwriteC(op.amMacro[j]); + for (int j=0; jwriteC(op.amMacro.val[j]); } - for (int j=0; jwriteC(op.arMacro[j]); + for (int j=0; jwriteC(op.arMacro.val[j]); } - for (int j=0; jwriteC(op.drMacro[j]); + for (int j=0; jwriteC(op.drMacro.val[j]); } - for (int j=0; jwriteC(op.multMacro[j]); + for (int j=0; jwriteC(op.multMacro.val[j]); } - for (int j=0; jwriteC(op.rrMacro[j]); + for (int j=0; jwriteC(op.rrMacro.val[j]); } - for (int j=0; jwriteC(op.slMacro[j]); + for (int j=0; jwriteC(op.slMacro.val[j]); } - for (int j=0; jwriteC(op.tlMacro[j]); + for (int j=0; jwriteC(op.tlMacro.val[j]); } - for (int j=0; jwriteC(op.dt2Macro[j]); + for (int j=0; jwriteC(op.dt2Macro.val[j]); } - for (int j=0; jwriteC(op.rsMacro[j]); + for (int j=0; jwriteC(op.rsMacro.val[j]); } - for (int j=0; jwriteC(op.dtMacro[j]); + for (int j=0; jwriteC(op.dtMacro.val[j]); } - for (int j=0; jwriteC(op.d2rMacro[j]); + for (int j=0; jwriteC(op.d2rMacro.val[j]); } - for (int j=0; jwriteC(op.ssgMacro[j]); + for (int j=0; jwriteC(op.ssgMacro.val[j]); } } // release points - w->writeI(std.volMacroRel); - w->writeI(std.arpMacroRel); - w->writeI(std.dutyMacroRel); - w->writeI(std.waveMacroRel); - w->writeI(std.pitchMacroRel); - w->writeI(std.ex1MacroRel); - w->writeI(std.ex2MacroRel); - w->writeI(std.ex3MacroRel); - w->writeI(std.algMacroRel); - w->writeI(std.fbMacroRel); - w->writeI(std.fmsMacroRel); - w->writeI(std.amsMacroRel); + w->writeI(std.volMacro.rel); + w->writeI(std.arpMacro.rel); + w->writeI(std.dutyMacro.rel); + w->writeI(std.waveMacro.rel); + w->writeI(std.pitchMacro.rel); + w->writeI(std.ex1Macro.rel); + w->writeI(std.ex2Macro.rel); + w->writeI(std.ex3Macro.rel); + w->writeI(std.algMacro.rel); + w->writeI(std.fbMacro.rel); + w->writeI(std.fmsMacro.rel); + w->writeI(std.amsMacro.rel); for (int i=0; i<4; i++) { DivInstrumentSTD::OpMacro& op=std.opMacros[i]; - w->writeI(op.amMacroRel); - w->writeI(op.arMacroRel); - w->writeI(op.drMacroRel); - w->writeI(op.multMacroRel); - w->writeI(op.rrMacroRel); - w->writeI(op.slMacroRel); - w->writeI(op.tlMacroRel); - w->writeI(op.dt2MacroRel); - w->writeI(op.rsMacroRel); - w->writeI(op.dtMacroRel); - w->writeI(op.d2rMacroRel); - w->writeI(op.ssgMacroRel); + w->writeI(op.amMacro.rel); + w->writeI(op.arMacro.rel); + w->writeI(op.drMacro.rel); + w->writeI(op.multMacro.rel); + w->writeI(op.rrMacro.rel); + w->writeI(op.slMacro.rel); + w->writeI(op.tlMacro.rel); + w->writeI(op.dt2Macro.rel); + w->writeI(op.rsMacro.rel); + w->writeI(op.dtMacro.rel); + w->writeI(op.d2rMacro.rel); + w->writeI(op.ssgMacro.rel); } // extended op macros for (int i=0; i<4; i++) { DivInstrumentSTD::OpMacro& op=std.opMacros[i]; - w->writeI(op.damMacroLen); - w->writeI(op.dvbMacroLen); - w->writeI(op.egtMacroLen); - w->writeI(op.kslMacroLen); - w->writeI(op.susMacroLen); - w->writeI(op.vibMacroLen); - w->writeI(op.wsMacroLen); - w->writeI(op.ksrMacroLen); + w->writeI(op.damMacro.len); + w->writeI(op.dvbMacro.len); + w->writeI(op.egtMacro.len); + w->writeI(op.kslMacro.len); + w->writeI(op.susMacro.len); + w->writeI(op.vibMacro.len); + w->writeI(op.wsMacro.len); + w->writeI(op.ksrMacro.len); - w->writeI(op.damMacroLoop); - w->writeI(op.dvbMacroLoop); - w->writeI(op.egtMacroLoop); - w->writeI(op.kslMacroLoop); - w->writeI(op.susMacroLoop); - w->writeI(op.vibMacroLoop); - w->writeI(op.wsMacroLoop); - w->writeI(op.ksrMacroLoop); + w->writeI(op.damMacro.loop); + w->writeI(op.dvbMacro.loop); + w->writeI(op.egtMacro.loop); + w->writeI(op.kslMacro.loop); + w->writeI(op.susMacro.loop); + w->writeI(op.vibMacro.loop); + w->writeI(op.wsMacro.loop); + w->writeI(op.ksrMacro.loop); - w->writeI(op.damMacroRel); - w->writeI(op.dvbMacroRel); - w->writeI(op.egtMacroRel); - w->writeI(op.kslMacroRel); - w->writeI(op.susMacroRel); - w->writeI(op.vibMacroRel); - w->writeI(op.wsMacroRel); - w->writeI(op.ksrMacroRel); + w->writeI(op.damMacro.rel); + w->writeI(op.dvbMacro.rel); + w->writeI(op.egtMacro.rel); + w->writeI(op.kslMacro.rel); + w->writeI(op.susMacro.rel); + w->writeI(op.vibMacro.rel); + w->writeI(op.wsMacro.rel); + w->writeI(op.ksrMacro.rel); - w->writeC(op.damMacroOpen); - w->writeC(op.dvbMacroOpen); - w->writeC(op.egtMacroOpen); - w->writeC(op.kslMacroOpen); - w->writeC(op.susMacroOpen); - w->writeC(op.vibMacroOpen); - w->writeC(op.wsMacroOpen); - w->writeC(op.ksrMacroOpen); + w->writeC(op.damMacro.open); + w->writeC(op.dvbMacro.open); + w->writeC(op.egtMacro.open); + w->writeC(op.kslMacro.open); + w->writeC(op.susMacro.open); + w->writeC(op.vibMacro.open); + w->writeC(op.wsMacro.open); + w->writeC(op.ksrMacro.open); } for (int i=0; i<4; i++) { DivInstrumentSTD::OpMacro& op=std.opMacros[i]; - for (int j=0; jwriteC(op.damMacro[j]); + for (int j=0; jwriteC(op.damMacro.val[j]); } - for (int j=0; jwriteC(op.dvbMacro[j]); + for (int j=0; jwriteC(op.dvbMacro.val[j]); } - for (int j=0; jwriteC(op.egtMacro[j]); + for (int j=0; jwriteC(op.egtMacro.val[j]); } - for (int j=0; jwriteC(op.kslMacro[j]); + for (int j=0; jwriteC(op.kslMacro.val[j]); } - for (int j=0; jwriteC(op.susMacro[j]); + for (int j=0; jwriteC(op.susMacro.val[j]); } - for (int j=0; jwriteC(op.vibMacro[j]); + for (int j=0; jwriteC(op.vibMacro.val[j]); } - for (int j=0; jwriteC(op.wsMacro[j]); + for (int j=0; jwriteC(op.wsMacro.val[j]); } - for (int j=0; jwriteC(op.ksrMacro[j]); + for (int j=0; jwriteC(op.ksrMacro.val[j]); } - } + }*/ // OPL drum data w->writeC(fm.fixedDrums); @@ -394,66 +478,67 @@ void DivInstrument::putInsData(SafeWriter* w) { w->writeC(0); // reserved // more macros - w->writeI(std.panLMacroLen); - w->writeI(std.panRMacroLen); - w->writeI(std.phaseResetMacroLen); - w->writeI(std.ex4MacroLen); - w->writeI(std.ex5MacroLen); - w->writeI(std.ex6MacroLen); - w->writeI(std.ex7MacroLen); - w->writeI(std.ex8MacroLen); + /* + w->writeI(std.panLMacro.len); + w->writeI(std.panRMacro.len); + w->writeI(std.phaseResetMacro.len); + w->writeI(std.ex4Macro.len); + w->writeI(std.ex5Macro.len); + w->writeI(std.ex6Macro.len); + w->writeI(std.ex7Macro.len); + w->writeI(std.ex8Macro.len); - w->writeI(std.panLMacroLoop); - w->writeI(std.panRMacroLoop); - w->writeI(std.phaseResetMacroLoop); - w->writeI(std.ex4MacroLoop); - w->writeI(std.ex5MacroLoop); - w->writeI(std.ex6MacroLoop); - w->writeI(std.ex7MacroLoop); - w->writeI(std.ex8MacroLoop); + w->writeI(std.panLMacro.loop); + w->writeI(std.panRMacro.loop); + w->writeI(std.phaseResetMacro.loop); + w->writeI(std.ex4Macro.loop); + w->writeI(std.ex5Macro.loop); + w->writeI(std.ex6Macro.loop); + w->writeI(std.ex7Macro.loop); + w->writeI(std.ex8Macro.loop); - w->writeI(std.panLMacroRel); - w->writeI(std.panRMacroRel); - w->writeI(std.phaseResetMacroRel); - w->writeI(std.ex4MacroRel); - w->writeI(std.ex5MacroRel); - w->writeI(std.ex6MacroRel); - w->writeI(std.ex7MacroRel); - w->writeI(std.ex8MacroRel); + w->writeI(std.panLMacro.rel); + w->writeI(std.panRMacro.rel); + w->writeI(std.phaseResetMacro.rel); + w->writeI(std.ex4Macro.rel); + w->writeI(std.ex5Macro.rel); + w->writeI(std.ex6Macro.rel); + w->writeI(std.ex7Macro.rel); + w->writeI(std.ex8Macro.rel); - w->writeC(std.panLMacroOpen); - w->writeC(std.panRMacroOpen); - w->writeC(std.phaseResetMacroOpen); - w->writeC(std.ex4MacroOpen); - w->writeC(std.ex5MacroOpen); - w->writeC(std.ex6MacroOpen); - w->writeC(std.ex7MacroOpen); - w->writeC(std.ex8MacroOpen); + w->writeC(std.panLMacro.open); + w->writeC(std.panRMacro.open); + w->writeC(std.phaseResetMacro.open); + w->writeC(std.ex4Macro.open); + w->writeC(std.ex5Macro.open); + w->writeC(std.ex6Macro.open); + w->writeC(std.ex7Macro.open); + w->writeC(std.ex8Macro.open); - for (int j=0; jwriteI(std.panLMacro[j]); + for (int j=0; jwriteI(std.panLMacro.val[j]); } - for (int j=0; jwriteI(std.panRMacro[j]); + for (int j=0; jwriteI(std.panRMacro.val[j]); } - for (int j=0; jwriteI(std.phaseResetMacro[j]); + for (int j=0; jwriteI(std.phaseResetMacro.val[j]); } - for (int j=0; jwriteI(std.ex4Macro[j]); + for (int j=0; jwriteI(std.ex4Macro.val[j]); } - for (int j=0; jwriteI(std.ex5Macro[j]); + for (int j=0; jwriteI(std.ex5Macro.val[j]); } - for (int j=0; jwriteI(std.ex6Macro[j]); + for (int j=0; jwriteI(std.ex6Macro.val[j]); } - for (int j=0; jwriteI(std.ex7Macro[j]); - } - for (int j=0; jwriteI(std.ex8Macro[j]); + for (int j=0; jwriteI(std.ex7Macro.val[j]); } + for (int j=0; jwriteI(std.ex8Macro.val[j]); + }*/ // FDS w->writeI(fds.modSpeed); @@ -482,7 +567,35 @@ void DivInstrument::putInsData(SafeWriter* w) { w->writeC(ws.param4); } +void readMacroData(DivInstrumentMacro &m, SafeReader& reader, short version) { + char magic[4]; + reader.read(magic,4); + if (memcmp(magic,"MACR",4)!=0) { + logE("invalid macro header!\n"); + return /*DIV_DATA_INVALID_HEADER*/; + } + reader.readI(); + + reader.readS(); // format version. ignored. + /*type=(DivInstrumentType)*/reader.readS(); // instrument type + reader.readC(); + m.name=reader.readString(); + + m.len=reader.readI(); + m.loop=reader.readI(); + m.rel=reader.readI(); + m.mode=reader.readI(); + m.open=reader.readC(); + for (int v=0; v=17) { - std.pitchMacroLen=reader.readI(); - std.ex1MacroLen=reader.readI(); - std.ex2MacroLen=reader.readI(); - std.ex3MacroLen=reader.readI(); - } - std.volMacroLoop=reader.readI(); - std.arpMacroLoop=reader.readI(); - std.dutyMacroLoop=reader.readI(); - std.waveMacroLoop=reader.readI(); - if (version>=17) { - std.pitchMacroLoop=reader.readI(); - std.ex1MacroLoop=reader.readI(); - std.ex2MacroLoop=reader.readI(); - std.ex3MacroLoop=reader.readI(); - } - std.arpMacroMode=reader.readC(); - std.volMacroHeight=reader.readC(); - std.dutyMacroHeight=reader.readC(); - std.waveMacroHeight=reader.readC(); - if (std.volMacroHeight==0) std.volMacroHeight=15; - if (std.dutyMacroHeight==0) std.dutyMacroHeight=3; - if (std.waveMacroHeight==0) std.waveMacroHeight=63; - reader.read(std.volMacro,4*std.volMacroLen); - reader.read(std.arpMacro,4*std.arpMacroLen); - reader.read(std.dutyMacro,4*std.dutyMacroLen); - reader.read(std.waveMacro,4*std.waveMacroLen); - if (version<31) { - if (!std.arpMacroMode) for (int j=0; j=17) { - reader.read(std.pitchMacro,4*std.pitchMacroLen); - reader.read(std.ex1Macro,4*std.ex1MacroLen); - reader.read(std.ex2Macro,4*std.ex2MacroLen); - reader.read(std.ex3Macro,4*std.ex3MacroLen); - } else { - if (type==DIV_INS_STD) { - if (std.volMacroHeight==31) { - type=DIV_INS_PCE; - } - if (std.dutyMacroHeight==31) { - type=DIV_INS_AY; + if (!istest) { + // standard + std.volMacro.len=reader.readI(); + std.arpMacro.len=reader.readI(); + std.dutyMacro.len=reader.readI(); + std.waveMacro.len=reader.readI(); + if (version>=17) { + std.pitchMacro.len=reader.readI(); + std.ex1Macro.len=reader.readI(); + std.ex2Macro.len=reader.readI(); + std.ex3Macro.len=reader.readI(); + } + std.volMacro.loop=reader.readI(); + std.arpMacro.loop=reader.readI(); + std.dutyMacro.loop=reader.readI(); + std.waveMacro.loop=reader.readI(); + if (version>=17) { + std.pitchMacro.loop=reader.readI(); + std.ex1Macro.loop=reader.readI(); + std.ex2Macro.loop=reader.readI(); + std.ex3Macro.loop=reader.readI(); + } + std.arpMacro.mode=reader.readC(); + std.volMacro.height=reader.readC(); + std.dutyMacro.height=reader.readC(); + std.waveMacro.height=reader.readC(); + if (std.volMacro.height==0) std.volMacro.height=15; + if (std.dutyMacro.height==0) std.dutyMacro.height=3; + if (std.waveMacro.height==0) std.waveMacro.height=63; + reader.read(std.volMacro.val,4*std.volMacro.len); + reader.read(std.arpMacro.val,4*std.arpMacro.len); + reader.read(std.dutyMacro.val,4*std.dutyMacro.len); + reader.read(std.waveMacro.val,4*std.waveMacro.len); + if (version<31) { + if (!std.arpMacro.mode) for (int j=0; j=29) { - std.algMacroLen=reader.readI(); - std.fbMacroLen=reader.readI(); - std.fmsMacroLen=reader.readI(); - std.amsMacroLen=reader.readI(); - std.algMacroLoop=reader.readI(); - std.fbMacroLoop=reader.readI(); - std.fmsMacroLoop=reader.readI(); - std.amsMacroLoop=reader.readI(); - std.volMacroOpen=reader.readC(); - std.arpMacroOpen=reader.readC(); - std.dutyMacroOpen=reader.readC(); - std.waveMacroOpen=reader.readC(); - std.pitchMacroOpen=reader.readC(); - std.ex1MacroOpen=reader.readC(); - std.ex2MacroOpen=reader.readC(); - std.ex3MacroOpen=reader.readC(); - std.algMacroOpen=reader.readC(); - std.fbMacroOpen=reader.readC(); - std.fmsMacroOpen=reader.readC(); - std.amsMacroOpen=reader.readC(); - - reader.read(std.algMacro,4*std.algMacroLen); - reader.read(std.fbMacro,4*std.fbMacroLen); - reader.read(std.fmsMacro,4*std.fmsMacroLen); - reader.read(std.amsMacro,4*std.amsMacroLen); - - for (int i=0; i<4; i++) { - DivInstrumentSTD::OpMacro& op=std.opMacros[i]; - - op.amMacroLen=reader.readI(); - op.arMacroLen=reader.readI(); - op.drMacroLen=reader.readI(); - op.multMacroLen=reader.readI(); - op.rrMacroLen=reader.readI(); - op.slMacroLen=reader.readI(); - op.tlMacroLen=reader.readI(); - op.dt2MacroLen=reader.readI(); - op.rsMacroLen=reader.readI(); - op.dtMacroLen=reader.readI(); - op.d2rMacroLen=reader.readI(); - op.ssgMacroLen=reader.readI(); - - op.amMacroLoop=reader.readI(); - op.arMacroLoop=reader.readI(); - op.drMacroLoop=reader.readI(); - op.multMacroLoop=reader.readI(); - op.rrMacroLoop=reader.readI(); - op.slMacroLoop=reader.readI(); - op.tlMacroLoop=reader.readI(); - op.dt2MacroLoop=reader.readI(); - op.rsMacroLoop=reader.readI(); - op.dtMacroLoop=reader.readI(); - op.d2rMacroLoop=reader.readI(); - op.ssgMacroLoop=reader.readI(); - - op.amMacroOpen=reader.readC(); - op.arMacroOpen=reader.readC(); - op.drMacroOpen=reader.readC(); - op.multMacroOpen=reader.readC(); - op.rrMacroOpen=reader.readC(); - op.slMacroOpen=reader.readC(); - op.tlMacroOpen=reader.readC(); - op.dt2MacroOpen=reader.readC(); - op.rsMacroOpen=reader.readC(); - op.dtMacroOpen=reader.readC(); - op.d2rMacroOpen=reader.readC(); - op.ssgMacroOpen=reader.readC(); + if (version>=17) { + reader.read(std.pitchMacro.val,4*std.pitchMacro.len); + reader.read(std.ex1Macro.val,4*std.ex1Macro.len); + reader.read(std.ex2Macro.val,4*std.ex2Macro.len); + reader.read(std.ex3Macro.val,4*std.ex3Macro.len); + } else { + if (type==DIV_INS_STD) { + if (std.volMacro.height==31) { + type=DIV_INS_PCE; + } + if (std.dutyMacro.height==31) { + type=DIV_INS_AY; + } + } } - for (int i=0; i<4; i++) { - DivInstrumentSTD::OpMacro& op=std.opMacros[i]; - reader.read(op.amMacro,op.amMacroLen); - reader.read(op.arMacro,op.arMacroLen); - reader.read(op.drMacro,op.drMacroLen); - reader.read(op.multMacro,op.multMacroLen); - reader.read(op.rrMacro,op.rrMacroLen); - reader.read(op.slMacro,op.slMacroLen); - reader.read(op.tlMacro,op.tlMacroLen); - reader.read(op.dt2Macro,op.dt2MacroLen); - reader.read(op.rsMacro,op.rsMacroLen); - reader.read(op.dtMacro,op.dtMacroLen); - reader.read(op.d2rMacro,op.d2rMacroLen); - reader.read(op.ssgMacro,op.ssgMacroLen); - } - } + // FM macros + if (version>=29) { + std.algMacro.len=reader.readI(); + std.fbMacro.len=reader.readI(); + std.fmsMacro.len=reader.readI(); + std.amsMacro.len=reader.readI(); + std.algMacro.loop=reader.readI(); + std.fbMacro.loop=reader.readI(); + std.fmsMacro.loop=reader.readI(); + std.amsMacro.loop=reader.readI(); + std.volMacro.open=reader.readC(); + std.arpMacro.open=reader.readC(); + std.dutyMacro.open=reader.readC(); + std.waveMacro.open=reader.readC(); + std.pitchMacro.open=reader.readC(); + std.ex1Macro.open=reader.readC(); + std.ex2Macro.open=reader.readC(); + std.ex3Macro.open=reader.readC(); + std.algMacro.open=reader.readC(); + std.fbMacro.open=reader.readC(); + std.fmsMacro.open=reader.readC(); + std.amsMacro.open=reader.readC(); - // release points - if (version>=44) { - std.volMacroRel=reader.readI(); - std.arpMacroRel=reader.readI(); - std.dutyMacroRel=reader.readI(); - std.waveMacroRel=reader.readI(); - std.pitchMacroRel=reader.readI(); - std.ex1MacroRel=reader.readI(); - std.ex2MacroRel=reader.readI(); - std.ex3MacroRel=reader.readI(); - std.algMacroRel=reader.readI(); - std.fbMacroRel=reader.readI(); - std.fmsMacroRel=reader.readI(); - std.amsMacroRel=reader.readI(); + reader.read(std.algMacro.val,4*std.algMacro.len); + reader.read(std.fbMacro.val,4*std.fbMacro.len); + reader.read(std.fmsMacro.val,4*std.fmsMacro.len); + reader.read(std.amsMacro.val,4*std.amsMacro.len); - for (int i=0; i<4; i++) { - DivInstrumentSTD::OpMacro& op=std.opMacros[i]; + for (int i=0; i<4; i++) { + DivInstrumentSTD::OpMacro& op=std.opMacros[i]; - op.amMacroRel=reader.readI(); - op.arMacroRel=reader.readI(); - op.drMacroRel=reader.readI(); - op.multMacroRel=reader.readI(); - op.rrMacroRel=reader.readI(); - op.slMacroRel=reader.readI(); - op.tlMacroRel=reader.readI(); - op.dt2MacroRel=reader.readI(); - op.rsMacroRel=reader.readI(); - op.dtMacroRel=reader.readI(); - op.d2rMacroRel=reader.readI(); - op.ssgMacroRel=reader.readI(); - } - } + op.amMacro.len=reader.readI(); + op.arMacro.len=reader.readI(); + op.drMacro.len=reader.readI(); + op.multMacro.len=reader.readI(); + op.rrMacro.len=reader.readI(); + op.slMacro.len=reader.readI(); + op.tlMacro.len=reader.readI(); + op.dt2Macro.len=reader.readI(); + op.rsMacro.len=reader.readI(); + op.dtMacro.len=reader.readI(); + op.d2rMacro.len=reader.readI(); + op.ssgMacro.len=reader.readI(); - // extended op macros - if (version>=61) { - for (int i=0; i<4; i++) { - DivInstrumentSTD::OpMacro& op=std.opMacros[i]; + op.amMacro.loop=reader.readI(); + op.arMacro.loop=reader.readI(); + op.drMacro.loop=reader.readI(); + op.multMacro.loop=reader.readI(); + op.rrMacro.loop=reader.readI(); + op.slMacro.loop=reader.readI(); + op.tlMacro.loop=reader.readI(); + op.dt2Macro.loop=reader.readI(); + op.rsMacro.loop=reader.readI(); + op.dtMacro.loop=reader.readI(); + op.d2rMacro.loop=reader.readI(); + op.ssgMacro.loop=reader.readI(); - op.damMacroLen=reader.readI(); - op.dvbMacroLen=reader.readI(); - op.egtMacroLen=reader.readI(); - op.kslMacroLen=reader.readI(); - op.susMacroLen=reader.readI(); - op.vibMacroLen=reader.readI(); - op.wsMacroLen=reader.readI(); - op.ksrMacroLen=reader.readI(); + op.amMacro.open=reader.readC(); + op.arMacro.open=reader.readC(); + op.drMacro.open=reader.readC(); + op.multMacro.open=reader.readC(); + op.rrMacro.open=reader.readC(); + op.slMacro.open=reader.readC(); + op.tlMacro.open=reader.readC(); + op.dt2Macro.open=reader.readC(); + op.rsMacro.open=reader.readC(); + op.dtMacro.open=reader.readC(); + op.d2rMacro.open=reader.readC(); + op.ssgMacro.open=reader.readC(); + } - op.damMacroLoop=reader.readI(); - op.dvbMacroLoop=reader.readI(); - op.egtMacroLoop=reader.readI(); - op.kslMacroLoop=reader.readI(); - op.susMacroLoop=reader.readI(); - op.vibMacroLoop=reader.readI(); - op.wsMacroLoop=reader.readI(); - op.ksrMacroLoop=reader.readI(); - - op.damMacroRel=reader.readI(); - op.dvbMacroRel=reader.readI(); - op.egtMacroRel=reader.readI(); - op.kslMacroRel=reader.readI(); - op.susMacroRel=reader.readI(); - op.vibMacroRel=reader.readI(); - op.wsMacroRel=reader.readI(); - op.ksrMacroRel=reader.readI(); - - op.damMacroOpen=reader.readC(); - op.dvbMacroOpen=reader.readC(); - op.egtMacroOpen=reader.readC(); - op.kslMacroOpen=reader.readC(); - op.susMacroOpen=reader.readC(); - op.vibMacroOpen=reader.readC(); - op.wsMacroOpen=reader.readC(); - op.ksrMacroOpen=reader.readC(); + for (int i=0; i<4; i++) { + DivInstrumentSTD::OpMacro& op=std.opMacros[i]; + reader.readByte(op.amMacro.val,op.amMacro.len,1); + reader.readByte(op.arMacro.val,op.arMacro.len,1); + reader.readByte(op.drMacro.val,op.drMacro.len,1); + reader.readByte(op.multMacro.val,op.multMacro.len,1); + reader.readByte(op.rrMacro.val,op.rrMacro.len,1); + reader.readByte(op.slMacro.val,op.slMacro.len,1); + reader.readByte(op.tlMacro.val,op.tlMacro.len,1); + reader.readByte(op.dt2Macro.val,op.dt2Macro.len,1); + reader.readByte(op.rsMacro.val,op.rsMacro.len,1); + reader.readByte(op.dtMacro.val,op.dtMacro.len,1); + reader.readByte(op.d2rMacro.val,op.d2rMacro.len,1); + reader.readByte(op.ssgMacro.val,op.ssgMacro.len,1); + } } - for (int i=0; i<4; i++) { - DivInstrumentSTD::OpMacro& op=std.opMacros[i]; - reader.read(op.damMacro,op.damMacroLen); - reader.read(op.dvbMacro,op.dvbMacroLen); - reader.read(op.egtMacro,op.egtMacroLen); - reader.read(op.kslMacro,op.kslMacroLen); - reader.read(op.susMacro,op.susMacroLen); - reader.read(op.vibMacro,op.vibMacroLen); - reader.read(op.wsMacro,op.wsMacroLen); - reader.read(op.ksrMacro,op.ksrMacroLen); + // release points + if (version>=44) { + std.volMacro.rel=reader.readI(); + std.arpMacro.rel=reader.readI(); + std.dutyMacro.rel=reader.readI(); + std.waveMacro.rel=reader.readI(); + std.pitchMacro.rel=reader.readI(); + std.ex1Macro.rel=reader.readI(); + std.ex2Macro.rel=reader.readI(); + std.ex3Macro.rel=reader.readI(); + std.algMacro.rel=reader.readI(); + std.fbMacro.rel=reader.readI(); + std.fmsMacro.rel=reader.readI(); + std.amsMacro.rel=reader.readI(); + + for (int i=0; i<4; i++) { + DivInstrumentSTD::OpMacro& op=std.opMacros[i]; + + op.amMacro.rel=reader.readI(); + op.arMacro.rel=reader.readI(); + op.drMacro.rel=reader.readI(); + op.multMacro.rel=reader.readI(); + op.rrMacro.rel=reader.readI(); + op.slMacro.rel=reader.readI(); + op.tlMacro.rel=reader.readI(); + op.dt2Macro.rel=reader.readI(); + op.rsMacro.rel=reader.readI(); + op.dtMacro.rel=reader.readI(); + op.d2rMacro.rel=reader.readI(); + op.ssgMacro.rel=reader.readI(); + } + } + + // extended op macros + if (version>=61) { + for (int i=0; i<4; i++) { + DivInstrumentSTD::OpMacro& op=std.opMacros[i]; + + op.damMacro.len=reader.readI(); + op.dvbMacro.len=reader.readI(); + op.egtMacro.len=reader.readI(); + op.kslMacro.len=reader.readI(); + op.susMacro.len=reader.readI(); + op.vibMacro.len=reader.readI(); + op.wsMacro.len=reader.readI(); + op.ksrMacro.len=reader.readI(); + + op.damMacro.loop=reader.readI(); + op.dvbMacro.loop=reader.readI(); + op.egtMacro.loop=reader.readI(); + op.kslMacro.loop=reader.readI(); + op.susMacro.loop=reader.readI(); + op.vibMacro.loop=reader.readI(); + op.wsMacro.loop=reader.readI(); + op.ksrMacro.loop=reader.readI(); + + op.damMacro.rel=reader.readI(); + op.dvbMacro.rel=reader.readI(); + op.egtMacro.rel=reader.readI(); + op.kslMacro.rel=reader.readI(); + op.susMacro.rel=reader.readI(); + op.vibMacro.rel=reader.readI(); + op.wsMacro.rel=reader.readI(); + op.ksrMacro.rel=reader.readI(); + + op.damMacro.open=reader.readC(); + op.dvbMacro.open=reader.readC(); + op.egtMacro.open=reader.readC(); + op.kslMacro.open=reader.readC(); + op.susMacro.open=reader.readC(); + op.vibMacro.open=reader.readC(); + op.wsMacro.open=reader.readC(); + op.ksrMacro.open=reader.readC(); + } + + for (int i=0; i<4; i++) { + DivInstrumentSTD::OpMacro& op=std.opMacros[i]; + reader.readByte(op.damMacro.val,op.damMacro.len,1); + reader.readByte(op.dvbMacro.val,op.dvbMacro.len,1); + reader.readByte(op.egtMacro.val,op.egtMacro.len,1); + reader.readByte(op.kslMacro.val,op.kslMacro.len,1); + reader.readByte(op.susMacro.val,op.susMacro.len,1); + reader.readByte(op.vibMacro.val,op.vibMacro.len,1); + reader.readByte(op.wsMacro.val,op.wsMacro.len,1); + reader.readByte(op.ksrMacro.val,op.ksrMacro.len,1); + } } } @@ -814,16 +997,16 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) { // clear noise macro if PCE instrument and version<63 if (version<63 && type==DIV_INS_PCE) { - std.dutyMacroLen=0; - std.dutyMacroLoop=-1; - std.dutyMacroRel=-1; + std.dutyMacro.len=0; + std.dutyMacro.loop=-1; + std.dutyMacro.rel=-1; } // clear wave macro if OPLL instrument and version<70 if (version<70 && type==DIV_INS_OPLL) { - std.waveMacroLen=0; - std.waveMacroLoop=-1; - std.waveMacroRel=-1; + std.waveMacro.len=0; + std.waveMacro.loop=-1; + std.waveMacro.rel=-1; } // sample map @@ -845,51 +1028,53 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) { } // more macros - if (version>=76) { - std.panLMacroLen=reader.readI(); - std.panRMacroLen=reader.readI(); - std.phaseResetMacroLen=reader.readI(); - std.ex4MacroLen=reader.readI(); - std.ex5MacroLen=reader.readI(); - std.ex6MacroLen=reader.readI(); - std.ex7MacroLen=reader.readI(); - std.ex8MacroLen=reader.readI(); + if (!istest) { + if (version>=76) { + std.panLMacro.len=reader.readI(); + std.panRMacro.len=reader.readI(); + std.phaseResetMacro.len=reader.readI(); + std.ex4Macro.len=reader.readI(); + std.ex5Macro.len=reader.readI(); + std.ex6Macro.len=reader.readI(); + std.ex7Macro.len=reader.readI(); + std.ex8Macro.len=reader.readI(); - std.panLMacroLoop=reader.readI(); - std.panRMacroLoop=reader.readI(); - std.phaseResetMacroLoop=reader.readI(); - std.ex4MacroLoop=reader.readI(); - std.ex5MacroLoop=reader.readI(); - std.ex6MacroLoop=reader.readI(); - std.ex7MacroLoop=reader.readI(); - std.ex8MacroLoop=reader.readI(); + std.panLMacro.loop=reader.readI(); + std.panRMacro.loop=reader.readI(); + std.phaseResetMacro.loop=reader.readI(); + std.ex4Macro.loop=reader.readI(); + std.ex5Macro.loop=reader.readI(); + std.ex6Macro.loop=reader.readI(); + std.ex7Macro.loop=reader.readI(); + std.ex8Macro.loop=reader.readI(); - std.panLMacroRel=reader.readI(); - std.panRMacroRel=reader.readI(); - std.phaseResetMacroRel=reader.readI(); - std.ex4MacroRel=reader.readI(); - std.ex5MacroRel=reader.readI(); - std.ex6MacroRel=reader.readI(); - std.ex7MacroRel=reader.readI(); - std.ex8MacroRel=reader.readI(); + std.panLMacro.rel=reader.readI(); + std.panRMacro.rel=reader.readI(); + std.phaseResetMacro.rel=reader.readI(); + std.ex4Macro.rel=reader.readI(); + std.ex5Macro.rel=reader.readI(); + std.ex6Macro.rel=reader.readI(); + std.ex7Macro.rel=reader.readI(); + std.ex8Macro.rel=reader.readI(); - std.panLMacroOpen=reader.readC(); - std.panRMacroOpen=reader.readC(); - std.phaseResetMacroOpen=reader.readC(); - std.ex4MacroOpen=reader.readC(); - std.ex5MacroOpen=reader.readC(); - std.ex6MacroOpen=reader.readC(); - std.ex7MacroOpen=reader.readC(); - std.ex8MacroOpen=reader.readC(); + std.panLMacro.open=reader.readC(); + std.panRMacro.open=reader.readC(); + std.phaseResetMacro.open=reader.readC(); + std.ex4Macro.open=reader.readC(); + std.ex5Macro.open=reader.readC(); + std.ex6Macro.open=reader.readC(); + std.ex7Macro.open=reader.readC(); + std.ex8Macro.open=reader.readC(); - reader.read(std.panLMacro,4*std.panLMacroLen); - reader.read(std.panRMacro,4*std.panRMacroLen); - reader.read(std.phaseResetMacro,4*std.phaseResetMacroLen); - reader.read(std.ex4Macro,4*std.ex4MacroLen); - reader.read(std.ex5Macro,4*std.ex5MacroLen); - reader.read(std.ex6Macro,4*std.ex6MacroLen); - reader.read(std.ex7Macro,4*std.ex7MacroLen); - reader.read(std.ex8Macro,4*std.ex8MacroLen); + reader.read(std.panLMacro.val,4*std.panLMacro.len); + reader.read(std.panRMacro.val,4*std.panRMacro.len); + reader.read(std.phaseResetMacro.val,4*std.phaseResetMacro.len); + reader.read(std.ex4Macro.val,4*std.ex4Macro.len); + reader.read(std.ex5Macro.val,4*std.ex5Macro.len); + reader.read(std.ex6Macro.val,4*std.ex6Macro.len); + reader.read(std.ex7Macro.val,4*std.ex7Macro.len); + reader.read(std.ex8Macro.val,4*std.ex8Macro.len); + } } // FDS diff --git a/src/engine/instrument.h b/src/engine/instrument.h index 8109030ed..dd56ef84c 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -26,7 +26,7 @@ // NOTICE! // before adding new instrument types to this struct, please ask me first. // absolutely zero support granted to conflicting formats. -enum DivInstrumentType { +enum DivInstrumentType : unsigned short { DIV_INS_STD=0, DIV_INS_FM=1, DIV_INS_GB=2, @@ -77,6 +77,7 @@ struct DivInstrumentFM { bool fixedDrums; unsigned short kickFreq, snareHatFreq, tomTopFreq; struct Operator { + bool enable; unsigned char am, ar, dr, mult, rr, sl, tl, dt2, rs, dt, d2r, ssgEnv; unsigned char dam, dvb, egt, ksl, sus, vib, ws, ksr; // YMU759/OPL/OPZ Operator(): @@ -151,248 +152,122 @@ struct DivInstrumentFM { }; // this is getting out of hand +struct DivInstrumentMacro { + String name; + int val[256]; + int height; + unsigned int mode; + bool open; + unsigned char len; + signed char loop; + signed char rel; + DivInstrumentMacro(String n, int h=~0, bool initOpen=false): + name(n), + val{0}, + height(h), + mode(0), + open(initOpen), + len(0), + loop(-1), + rel(-1) {} +}; + struct DivInstrumentSTD { - int volMacro[256]; - int arpMacro[256]; - int dutyMacro[256]; - int waveMacro[256]; - int pitchMacro[256]; - int ex1Macro[256]; - int ex2Macro[256]; - int ex3Macro[256]; - int algMacro[256]; - int fbMacro[256]; - int fmsMacro[256]; - int amsMacro[256]; - int panLMacro[256]; - int panRMacro[256]; - int phaseResetMacro[256]; - int ex4Macro[256]; - int ex5Macro[256]; - int ex6Macro[256]; - int ex7Macro[256]; - int ex8Macro[256]; + DivInstrumentMacro volMacro; + DivInstrumentMacro arpMacro; + DivInstrumentMacro dutyMacro; + DivInstrumentMacro waveMacro; + DivInstrumentMacro pitchMacro; + DivInstrumentMacro ex1Macro; + DivInstrumentMacro ex2Macro; + DivInstrumentMacro ex3Macro; + DivInstrumentMacro algMacro; + DivInstrumentMacro fbMacro; + DivInstrumentMacro fmsMacro; + DivInstrumentMacro fms2Macro; + DivInstrumentMacro amsMacro; + DivInstrumentMacro ams2Macro; + DivInstrumentMacro panLMacro; + DivInstrumentMacro panRMacro; + DivInstrumentMacro phaseResetMacro; + DivInstrumentMacro ex4Macro; + DivInstrumentMacro ex5Macro; + DivInstrumentMacro ex6Macro; + DivInstrumentMacro ex7Macro; + DivInstrumentMacro ex8Macro; - bool arpMacroMode; - unsigned char volMacroHeight, dutyMacroHeight, waveMacroHeight; - - bool volMacroOpen, arpMacroOpen, dutyMacroOpen, waveMacroOpen; - bool pitchMacroOpen, ex1MacroOpen, ex2MacroOpen, ex3MacroOpen; - bool algMacroOpen, fbMacroOpen, fmsMacroOpen, amsMacroOpen; - bool panLMacroOpen, panRMacroOpen, phaseResetMacroOpen, ex4MacroOpen; - bool ex5MacroOpen, ex6MacroOpen, ex7MacroOpen, ex8MacroOpen; - - unsigned char volMacroLen, arpMacroLen, dutyMacroLen, waveMacroLen; - unsigned char pitchMacroLen, ex1MacroLen, ex2MacroLen, ex3MacroLen; - unsigned char algMacroLen, fbMacroLen, fmsMacroLen, amsMacroLen; - unsigned char panLMacroLen, panRMacroLen, phaseResetMacroLen, ex4MacroLen; - unsigned char ex5MacroLen, ex6MacroLen, ex7MacroLen, ex8MacroLen; - - signed char volMacroLoop, arpMacroLoop, dutyMacroLoop, waveMacroLoop; - signed char pitchMacroLoop, ex1MacroLoop, ex2MacroLoop, ex3MacroLoop; - signed char algMacroLoop, fbMacroLoop, fmsMacroLoop, amsMacroLoop; - signed char panLMacroLoop, panRMacroLoop, phaseResetMacroLoop, ex4MacroLoop; - signed char ex5MacroLoop, ex6MacroLoop, ex7MacroLoop, ex8MacroLoop; - - signed char volMacroRel, arpMacroRel, dutyMacroRel, waveMacroRel; - signed char pitchMacroRel, ex1MacroRel, ex2MacroRel, ex3MacroRel; - signed char algMacroRel, fbMacroRel, fmsMacroRel, amsMacroRel; - signed char panLMacroRel, panRMacroRel, phaseResetMacroRel, ex4MacroRel; - signed char ex5MacroRel, ex6MacroRel, ex7MacroRel, ex8MacroRel; struct OpMacro { // ar, dr, mult, rr, sl, tl, dt2, rs, dt, d2r, ssgEnv; - unsigned char amMacro[256]; - unsigned char arMacro[256]; - unsigned char drMacro[256]; - unsigned char multMacro[256]; - unsigned char rrMacro[256]; - unsigned char slMacro[256]; - unsigned char tlMacro[256]; - unsigned char dt2Macro[256]; - unsigned char rsMacro[256]; - unsigned char dtMacro[256]; - unsigned char d2rMacro[256]; - unsigned char ssgMacro[256]; - unsigned char damMacro[256]; - unsigned char dvbMacro[256]; - unsigned char egtMacro[256]; - unsigned char kslMacro[256]; - unsigned char susMacro[256]; - unsigned char vibMacro[256]; - unsigned char wsMacro[256]; - unsigned char ksrMacro[256]; - bool amMacroOpen, arMacroOpen, drMacroOpen, multMacroOpen; - bool rrMacroOpen, slMacroOpen, tlMacroOpen, dt2MacroOpen; - bool rsMacroOpen, dtMacroOpen, d2rMacroOpen, ssgMacroOpen; - bool damMacroOpen, dvbMacroOpen, egtMacroOpen, kslMacroOpen; - bool susMacroOpen, vibMacroOpen, wsMacroOpen, ksrMacroOpen; - unsigned char amMacroLen, arMacroLen, drMacroLen, multMacroLen; - unsigned char rrMacroLen, slMacroLen, tlMacroLen, dt2MacroLen; - unsigned char rsMacroLen, dtMacroLen, d2rMacroLen, ssgMacroLen; - unsigned char damMacroLen, dvbMacroLen, egtMacroLen, kslMacroLen; - unsigned char susMacroLen, vibMacroLen, wsMacroLen, ksrMacroLen; - signed char amMacroLoop, arMacroLoop, drMacroLoop, multMacroLoop; - signed char rrMacroLoop, slMacroLoop, tlMacroLoop, dt2MacroLoop; - signed char rsMacroLoop, dtMacroLoop, d2rMacroLoop, ssgMacroLoop; - signed char damMacroLoop, dvbMacroLoop, egtMacroLoop, kslMacroLoop; - signed char susMacroLoop, vibMacroLoop, wsMacroLoop, ksrMacroLoop; - signed char amMacroRel, arMacroRel, drMacroRel, multMacroRel; - signed char rrMacroRel, slMacroRel, tlMacroRel, dt2MacroRel; - signed char rsMacroRel, dtMacroRel, d2rMacroRel, ssgMacroRel; - signed char damMacroRel, dvbMacroRel, egtMacroRel, kslMacroRel; - signed char susMacroRel, vibMacroRel, wsMacroRel, ksrMacroRel; + DivInstrumentMacro amMacro; + DivInstrumentMacro arMacro; + DivInstrumentMacro drMacro; + DivInstrumentMacro multMacro; + DivInstrumentMacro rrMacro; + DivInstrumentMacro slMacro; + DivInstrumentMacro tlMacro; + DivInstrumentMacro dt2Macro; + DivInstrumentMacro rsMacro; + DivInstrumentMacro dtMacro; + DivInstrumentMacro d2rMacro; + DivInstrumentMacro ssgMacro; + DivInstrumentMacro damMacro; + DivInstrumentMacro dvbMacro; + DivInstrumentMacro egtMacro; + DivInstrumentMacro kslMacro; + DivInstrumentMacro susMacro; + DivInstrumentMacro vibMacro; + DivInstrumentMacro wsMacro; + DivInstrumentMacro ksrMacro; OpMacro(): - amMacroOpen(false), arMacroOpen(false), drMacroOpen(false), multMacroOpen(false), - rrMacroOpen(false), slMacroOpen(false), tlMacroOpen(true), dt2MacroOpen(false), - rsMacroOpen(false), dtMacroOpen(false), d2rMacroOpen(false), ssgMacroOpen(false), - damMacroOpen(false), dvbMacroOpen(false), egtMacroOpen(false), kslMacroOpen(false), - susMacroOpen(false), vibMacroOpen(false), wsMacroOpen(false), ksrMacroOpen(false), - amMacroLen(0), arMacroLen(0), drMacroLen(0), multMacroLen(0), - rrMacroLen(0), slMacroLen(0), tlMacroLen(0), dt2MacroLen(0), - rsMacroLen(0), dtMacroLen(0), d2rMacroLen(0), ssgMacroLen(0), - damMacroLen(0), dvbMacroLen(0), egtMacroLen(0), kslMacroLen(0), - susMacroLen(0), vibMacroLen(0), wsMacroLen(0), ksrMacroLen(0), - amMacroLoop(-1), arMacroLoop(-1), drMacroLoop(-1), multMacroLoop(-1), - rrMacroLoop(-1), slMacroLoop(-1), tlMacroLoop(-1), dt2MacroLoop(-1), - rsMacroLoop(-1), dtMacroLoop(-1), d2rMacroLoop(-1), ssgMacroLoop(-1), - damMacroLoop(-1), dvbMacroLoop(-1), egtMacroLoop(-1), kslMacroLoop(-1), - susMacroLoop(-1), vibMacroLoop(-1), wsMacroLoop(-1), ksrMacroLoop(-1), - amMacroRel(-1), arMacroRel(-1), drMacroRel(-1), multMacroRel(-1), - rrMacroRel(-1), slMacroRel(-1), tlMacroRel(-1), dt2MacroRel(-1), - rsMacroRel(-1), dtMacroRel(-1), d2rMacroRel(-1), ssgMacroRel(-1), - damMacroRel(-1), dvbMacroRel(-1), egtMacroRel(-1), kslMacroRel(-1), - susMacroRel(-1), vibMacroRel(-1), wsMacroRel(-1), ksrMacroRel(-1) { - memset(amMacro,0,256); - memset(arMacro,0,256); - memset(drMacro,0,256); - memset(multMacro,0,256); - memset(rrMacro,0,256); - memset(slMacro,0,256); - memset(tlMacro,0,256); - memset(dt2Macro,0,256); - memset(rsMacro,0,256); - memset(dtMacro,0,256); - memset(d2rMacro,0,256); - memset(ssgMacro,0,256); - memset(damMacro,0,256); - memset(dvbMacro,0,256); - memset(egtMacro,0,256); - memset(kslMacro,0,256); - memset(susMacro,0,256); - memset(vibMacro,0,256); - memset(wsMacro,0,256); - memset(ksrMacro,0,256); - } + amMacro("am"), arMacro("ar"), drMacro("dr"), multMacro("mult"), + rrMacro("rr"), slMacro("sl"), tlMacro("tl",~0,true), dt2Macro("dt2"), + rsMacro("rs"), dtMacro("dt"), d2rMacro("d2r"), ssgMacro("ssg"), + damMacro("dam"), dvbMacro("dvb"), egtMacro("egt"), kslMacro("ksl"), + susMacro("sus"), vibMacro("vib"), wsMacro("ws"), ksrMacro("ksr") {} } opMacros[4]; + struct WaveSynthMacro { + DivInstrumentMacro wave1Macro, wave2Macro; + DivInstrumentMacro rateDividerMacro; + DivInstrumentMacro effectMacro; + DivInstrumentMacro oneShotMacro, enabledMacro, globalMacro; + DivInstrumentMacro speedMacro, param1Macro, param2Macro, param3Macro, param4Macro; + WaveSynthMacro(): + wave1Macro("wave1"), + wave2Macro("wave2"), + rateDividerMacro("rateDivider"), + effectMacro("effect"), + oneShotMacro("oneShot"), + enabledMacro("enabled"), + globalMacro("global"), + speedMacro("speed"), + param1Macro("param1"), + param2Macro("param2"), + param3Macro("param3"), + param4Macro("param4") {} + } ws; DivInstrumentSTD(): - arpMacroMode(false), - volMacroHeight(15), - dutyMacroHeight(3), - waveMacroHeight(63), - volMacroOpen(true), - arpMacroOpen(false), - dutyMacroOpen(false), - waveMacroOpen(false), - pitchMacroOpen(false), - ex1MacroOpen(false), - ex2MacroOpen(false), - ex3MacroOpen(false), - algMacroOpen(false), - fbMacroOpen(false), - fmsMacroOpen(false), - amsMacroOpen(false), - panLMacroOpen(false), - panRMacroOpen(false), - phaseResetMacroOpen(false), - ex4MacroOpen(false), - ex5MacroOpen(false), - ex6MacroOpen(false), - ex7MacroOpen(false), - ex8MacroOpen(false), - - volMacroLen(0), - arpMacroLen(0), - dutyMacroLen(0), - waveMacroLen(0), - pitchMacroLen(0), - ex1MacroLen(0), - ex2MacroLen(0), - ex3MacroLen(0), - algMacroLen(0), - fbMacroLen(0), - fmsMacroLen(0), - amsMacroLen(0), - panLMacroLen(0), - panRMacroLen(0), - phaseResetMacroLen(0), - ex4MacroLen(0), - ex5MacroLen(0), - ex6MacroLen(0), - ex7MacroLen(0), - ex8MacroLen(0), - - volMacroLoop(-1), - arpMacroLoop(-1), - dutyMacroLoop(-1), - waveMacroLoop(-1), - pitchMacroLoop(-1), - ex1MacroLoop(-1), - ex2MacroLoop(-1), - ex3MacroLoop(-1), - algMacroLoop(-1), - fbMacroLoop(-1), - fmsMacroLoop(-1), - amsMacroLoop(-1), - panLMacroLoop(-1), - panRMacroLoop(-1), - phaseResetMacroLoop(-1), - ex4MacroLoop(-1), - ex5MacroLoop(-1), - ex6MacroLoop(-1), - ex7MacroLoop(-1), - ex8MacroLoop(-1), - - volMacroRel(-1), - arpMacroRel(-1), - dutyMacroRel(-1), - waveMacroRel(-1), - pitchMacroRel(-1), - ex1MacroRel(-1), - ex2MacroRel(-1), - ex3MacroRel(-1), - algMacroRel(-1), - fbMacroRel(-1), - fmsMacroRel(-1), - amsMacroRel(-1), - panLMacroRel(-1), - panRMacroRel(-1), - phaseResetMacroRel(-1), - ex4MacroRel(-1), - ex5MacroRel(-1), - ex6MacroRel(-1), - ex7MacroRel(-1), - ex8MacroRel(-1) { - memset(volMacro,0,256*sizeof(int)); - memset(arpMacro,0,256*sizeof(int)); - memset(dutyMacro,0,256*sizeof(int)); - memset(waveMacro,0,256*sizeof(int)); - memset(pitchMacro,0,256*sizeof(int)); - memset(ex1Macro,0,256*sizeof(int)); - memset(ex2Macro,0,256*sizeof(int)); - memset(ex3Macro,0,256*sizeof(int)); - memset(algMacro,0,256*sizeof(int)); - memset(fbMacro,0,256*sizeof(int)); - memset(fmsMacro,0,256*sizeof(int)); - memset(amsMacro,0,256*sizeof(int)); - memset(panLMacro,0,256*sizeof(int)); - memset(panRMacro,0,256*sizeof(int)); - memset(phaseResetMacro,0,256*sizeof(int)); - memset(ex4Macro,0,256*sizeof(int)); - memset(ex5Macro,0,256*sizeof(int)); - memset(ex6Macro,0,256*sizeof(int)); - memset(ex7Macro,0,256*sizeof(int)); - memset(ex8Macro,0,256*sizeof(int)); - } + volMacro("vol",15,true), + arpMacro("arp"), + dutyMacro("duty",3), + waveMacro("wave",63), + pitchMacro("pitch"), + ex1Macro("ex1"), + ex2Macro("ex2"), + ex3Macro("ex3"), + algMacro("alg"), + fbMacro("fb"), + fmsMacro("fms"), + fms2Macro("fms2"), + amsMacro("ams"), + ams2Macro("ams2"), + panLMacro("panL"), + panRMacro("panR"), + phaseResetMacro("phaseReset"), + ex4Macro("ex4"), + ex5Macro("ex5"), + ex6Macro("ex6"), + ex7Macro("ex7"), + ex8Macro("ex8") {} }; struct DivInstrumentGB { @@ -540,6 +415,13 @@ struct DivInstrument { */ void putInsData(SafeWriter* w); + /** + * save the macro to a SafeWriter. + * @param m the macro. + * @param w the SafeWriter in question. + */ + void putMacroData(DivInstrumentMacro m, SafeWriter* w); + /** * read instrument data in .fui format. * @param reader the reader. @@ -548,6 +430,15 @@ struct DivInstrument { */ DivDataErrors readInsData(SafeReader& reader, short version); + /** + * read macro data in .fui format. + * @param m the macro. + * @param reader the reader. + * @param version the format version. + * @return a DivDataErrors. + */ + void readMacroData(DivInstrumentMacro& m, SafeReader& reader, short version); + /** * save this instrument to a file. * @param path file path. diff --git a/src/engine/macroInt.cpp b/src/engine/macroInt.cpp index d74b59a6c..9a8789427 100644 --- a/src/engine/macroInt.cpp +++ b/src/engine/macroInt.cpp @@ -20,85 +20,39 @@ #include "macroInt.h" #include "instrument.h" -#define doMacro(finished,had,has,val,pos,source,sourceLen,sourceLoop,sourceRel) \ - if (finished) finished=false; \ - if (had!=has) { \ - finished=true; \ - } \ - had=has; \ - if (has) { \ - val=source[pos++]; \ - if (sourceRel>=0 && pos>sourceRel && !released) { \ - if (sourceLoop=0 && sourceLoop=sourceLen) { \ - if (sourceLoop=0 && (sourceLoop>=sourceRel || sourceRel>=sourceLen)) { \ - pos=sourceLoop; \ - } else { \ - has=false; \ - } \ - } \ +void DivMacroStruct::doMacro(DivInstrumentMacro& source, bool released) { + if (finished) finished=false; + if (had!=has) { + finished=true; } + had=has; + if (has) { + val=source.val[pos++]; + if (source.rel>=0 && pos>source.rel && !released) { + if (source.loop=0 && source.loop=source.len) { + if (source.loop=0 && (source.loop>=source.rel || source.rel>=source.len)) { + pos=source.loop; + } else { + has=false; + } + } + } +} // CPU hell void DivMacroInt::next() { if (ins==NULL) return; - - doMacro(finishedVol,hadVol,hasVol,vol,volPos,ins->std.volMacro,ins->std.volMacroLen,ins->std.volMacroLoop,ins->std.volMacroRel); - doMacro(finishedArp,hadArp,hasArp,arp,arpPos,ins->std.arpMacro,ins->std.arpMacroLen,ins->std.arpMacroLoop,ins->std.arpMacroRel); - doMacro(finishedDuty,hadDuty,hasDuty,duty,dutyPos,ins->std.dutyMacro,ins->std.dutyMacroLen,ins->std.dutyMacroLoop,ins->std.dutyMacroRel); - doMacro(finishedWave,hadWave,hasWave,wave,wavePos,ins->std.waveMacro,ins->std.waveMacroLen,ins->std.waveMacroLoop,ins->std.waveMacroRel); - - doMacro(finishedPitch,hadPitch,hasPitch,pitch,pitchPos,ins->std.pitchMacro,ins->std.pitchMacroLen,ins->std.pitchMacroLoop,ins->std.pitchMacroRel); - doMacro(finishedEx1,hadEx1,hasEx1,ex1,ex1Pos,ins->std.ex1Macro,ins->std.ex1MacroLen,ins->std.ex1MacroLoop,ins->std.ex1MacroRel); - doMacro(finishedEx2,hadEx2,hasEx2,ex2,ex2Pos,ins->std.ex2Macro,ins->std.ex2MacroLen,ins->std.ex2MacroLoop,ins->std.ex2MacroRel); - doMacro(finishedEx3,hadEx3,hasEx3,ex3,ex3Pos,ins->std.ex3Macro,ins->std.ex3MacroLen,ins->std.ex3MacroLoop,ins->std.ex3MacroRel); - - doMacro(finishedAlg,hadAlg,hasAlg,alg,algPos,ins->std.algMacro,ins->std.algMacroLen,ins->std.algMacroLoop,ins->std.algMacroRel); - doMacro(finishedFb,hadFb,hasFb,fb,fbPos,ins->std.fbMacro,ins->std.fbMacroLen,ins->std.fbMacroLoop,ins->std.fbMacroRel); - doMacro(finishedFms,hadFms,hasFms,fms,fmsPos,ins->std.fmsMacro,ins->std.fmsMacroLen,ins->std.fmsMacroLoop,ins->std.fmsMacroRel); - doMacro(finishedAms,hadAms,hasAms,ams,amsPos,ins->std.amsMacro,ins->std.amsMacroLen,ins->std.amsMacroLoop,ins->std.amsMacroRel); - - doMacro(finishedPanL,hadPanL,hasPanL,panL,panLPos,ins->std.panLMacro,ins->std.panLMacroLen,ins->std.panLMacroLoop,ins->std.panLMacroRel); - doMacro(finishedPanR,hadPanR,hasPanR,panR,panRPos,ins->std.panRMacro,ins->std.panRMacroLen,ins->std.panRMacroLoop,ins->std.panRMacroRel); - doMacro(finishedPhaseReset,hadPhaseReset,hasPhaseReset,phaseReset,phaseResetPos,ins->std.phaseResetMacro,ins->std.phaseResetMacroLen,ins->std.phaseResetMacroLoop,ins->std.phaseResetMacroRel); - doMacro(finishedEx4,hadEx4,hasEx4,ex4,ex4Pos,ins->std.ex4Macro,ins->std.ex4MacroLen,ins->std.ex4MacroLoop,ins->std.ex4MacroRel); - doMacro(finishedEx5,hadEx5,hasEx5,ex5,ex5Pos,ins->std.ex5Macro,ins->std.ex5MacroLen,ins->std.ex5MacroLoop,ins->std.ex5MacroRel); - doMacro(finishedEx6,hadEx6,hasEx6,ex6,ex6Pos,ins->std.ex6Macro,ins->std.ex6MacroLen,ins->std.ex6MacroLoop,ins->std.ex6MacroRel); - doMacro(finishedEx7,hadEx7,hasEx7,ex7,ex7Pos,ins->std.ex7Macro,ins->std.ex7MacroLen,ins->std.ex7MacroLoop,ins->std.ex7MacroRel); - doMacro(finishedEx8,hadEx8,hasEx8,ex8,ex8Pos,ins->std.ex8Macro,ins->std.ex8MacroLen,ins->std.ex8MacroLoop,ins->std.ex8MacroRel); - - for (int i=0; i<4; i++) { - DivInstrumentSTD::OpMacro& m=ins->std.opMacros[i]; - IntOp& o=op[i]; - doMacro(o.finishedAm,o.hadAm,o.hasAm,o.am,o.amPos,m.amMacro,m.amMacroLen,m.amMacroLoop,m.amMacroRel); - doMacro(o.finishedAr,o.hadAr,o.hasAr,o.ar,o.arPos,m.arMacro,m.arMacroLen,m.arMacroLoop,m.arMacroRel); - doMacro(o.finishedDr,o.hadDr,o.hasDr,o.dr,o.drPos,m.drMacro,m.drMacroLen,m.drMacroLoop,m.drMacroRel); - doMacro(o.finishedMult,o.hadMult,o.hasMult,o.mult,o.multPos,m.multMacro,m.multMacroLen,m.multMacroLoop,m.multMacroRel); - - doMacro(o.finishedRr,o.hadRr,o.hasRr,o.rr,o.rrPos,m.rrMacro,m.rrMacroLen,m.rrMacroLoop,m.rrMacroRel); - doMacro(o.finishedSl,o.hadSl,o.hasSl,o.sl,o.slPos,m.slMacro,m.slMacroLen,m.slMacroLoop,m.slMacroRel); - doMacro(o.finishedTl,o.hadTl,o.hasTl,o.tl,o.tlPos,m.tlMacro,m.tlMacroLen,m.tlMacroLoop,m.tlMacroRel); - doMacro(o.finishedDt2,o.hadDt2,o.hasDt2,o.dt2,o.dt2Pos,m.dt2Macro,m.dt2MacroLen,m.dt2MacroLoop,m.dt2MacroRel); - - doMacro(o.finishedRs,o.hadRs,o.hasRs,o.rs,o.rsPos,m.rsMacro,m.rsMacroLen,m.rsMacroLoop,m.rsMacroRel); - doMacro(o.finishedDt,o.hadDt,o.hasDt,o.dt,o.dtPos,m.dtMacro,m.dtMacroLen,m.dtMacroLoop,m.dtMacroRel); - doMacro(o.finishedD2r,o.hadD2r,o.hasD2r,o.d2r,o.d2rPos,m.d2rMacro,m.d2rMacroLen,m.d2rMacroLoop,m.d2rMacroRel); - doMacro(o.finishedSsg,o.hadSsg,o.hasSsg,o.ssg,o.ssgPos,m.ssgMacro,m.ssgMacroLen,m.ssgMacroLoop,m.ssgMacroRel); - - doMacro(o.finishedDam,o.hadDam,o.hasDam,o.dam,o.damPos,m.damMacro,m.damMacroLen,m.damMacroLoop,m.damMacroRel); - doMacro(o.finishedDvb,o.hadDvb,o.hasDvb,o.dvb,o.dvbPos,m.dvbMacro,m.dvbMacroLen,m.dvbMacroLoop,m.dvbMacroRel); - doMacro(o.finishedEgt,o.hadEgt,o.hasEgt,o.egt,o.egtPos,m.egtMacro,m.egtMacroLen,m.egtMacroLoop,m.egtMacroRel); - doMacro(o.finishedKsl,o.hadKsl,o.hasKsl,o.ksl,o.kslPos,m.kslMacro,m.kslMacroLen,m.kslMacroLoop,m.kslMacroRel); - - doMacro(o.finishedSus,o.hadSus,o.hasSus,o.sus,o.susPos,m.susMacro,m.susMacroLen,m.susMacroLoop,m.susMacroRel); - doMacro(o.finishedVib,o.hadVib,o.hasVib,o.vib,o.vibPos,m.vibMacro,m.vibMacroLen,m.vibMacroLoop,m.vibMacroRel); - doMacro(o.finishedWs,o.hadWs,o.hasWs,o.ws,o.wsPos,m.wsMacro,m.wsMacroLen,m.wsMacroLoop,m.wsMacroRel); - doMacro(o.finishedKsr,o.hadKsr,o.hasKsr,o.ksr,o.ksrPos,m.ksrMacro,m.ksrMacroLen,m.ksrMacroLoop,m.ksrMacroRel); + // Run macros + if (!macroList.empty()) { + for (std::list::iterator iter = macroList.begin(); iter!= macroList.end(); iter++) { + iter->doMacro(released); + } } } @@ -108,312 +62,275 @@ void DivMacroInt::release() { void DivMacroInt::init(DivInstrument* which) { ins=which; - volPos=0; - arpPos=0; - dutyPos=0; - wavePos=0; - pitchPos=0; - ex1Pos=0; - ex2Pos=0; - ex3Pos=0; - algPos=0; - fbPos=0; - fmsPos=0; - amsPos=0; - panLPos=0; - panRPos=0; - phaseResetPos=0; - ex4Pos=0; - ex5Pos=0; - ex6Pos=0; - ex7Pos=0; - ex8Pos=0; + macroList.clear(); + // initialize common macros + vol.init(); + arp.init(); + duty.init(); + wave.init(); + pitch.init(); + ex1.init(); + ex2.init(); + ex3.init(); + alg.init(); + fb.init(); + fms.init(); + ams.init(); + fms2.init(); + ams2.init(); + panL.init(); + panR.init(); + phaseReset.init(); + ex4.init(); + ex5.init(); + ex6.init(); + ex7.init(); + ex8.init(); released=false; - hasVol=false; - hasArp=false; - hasDuty=false; - hasWave=false; - hasPitch=false; - hasEx1=false; - hasEx2=false; - hasEx3=false; - hasAlg=false; - hasFb=false; - hasFms=false; - hasAms=false; - hasPanL=false; - hasPanR=false; - hasPhaseReset=false; - hasEx4=false; - hasEx5=false; - hasEx6=false; - hasEx7=false; - hasEx8=false; - - hadVol=false; - hadArp=false; - hadDuty=false; - hadWave=false; - hadPitch=false; - hadEx1=false; - hadEx2=false; - hadEx3=false; - hadAlg=false; - hadFb=false; - hadFms=false; - hadAms=false; - hadPanL=false; - hadPanR=false; - hadPhaseReset=false; - hadEx4=false; - hadEx5=false; - hadEx6=false; - hadEx7=false; - hadEx8=false; - - willVol=false; - willArp=false; - willDuty=false; - willWave=false; - willPitch=false; - willEx1=false; - willEx2=false; - willEx3=false; - willAlg=false; - willFb=false; - willFms=false; - willAms=false; - willPanL=false; - willPanR=false; - willPhaseReset=false; - willEx4=false; - willEx5=false; - willEx6=false; - willEx7=false; - willEx8=false; - + // initialize FM operator macro op[0]=IntOp(); op[1]=IntOp(); op[2]=IntOp(); op[3]=IntOp(); - arpMode=false; + // initialize wavesynth macro + ws=IntWS(); if (ins==NULL) return; - if (ins->std.volMacroLen>0) { - hadVol=true; - hasVol=true; - willVol=true; + // prepare common macro + if (ins->std.volMacro.len>0) { + macroList.push_back(DivMacroExecList(vol,ins->std.volMacro)); + vol.prepare(&ins->std.volMacro); } - if (ins->std.arpMacroLen>0) { - hadArp=true; - hasArp=true; - willArp=true; + if (ins->std.arpMacro.len>0) { + macroList.push_back(DivMacroExecList(arp,ins->std.arpMacro)); + arp.prepare(&ins->std.arpMacro); } - if (ins->std.dutyMacroLen>0) { - hadDuty=true; - hasDuty=true; - willDuty=true; + if (ins->std.dutyMacro.len>0) { + macroList.push_back(DivMacroExecList(duty,ins->std.dutyMacro)); + duty.prepare(&ins->std.dutyMacro); } - if (ins->std.waveMacroLen>0) { - hadWave=true; - hasWave=true; - willWave=true; + if (ins->std.waveMacro.len>0) { + macroList.push_back(DivMacroExecList(wave,ins->std.waveMacro)); + wave.prepare(&ins->std.waveMacro); } - if (ins->std.pitchMacroLen>0) { - hadPitch=true; - hasPitch=true; - willPitch=true; + if (ins->std.pitchMacro.len>0) { + macroList.push_back(DivMacroExecList(pitch,ins->std.pitchMacro)); + pitch.prepare(&ins->std.pitchMacro); } - if (ins->std.ex1MacroLen>0) { - hadEx1=true; - hasEx1=true; - willEx1=true; + if (ins->std.ex1Macro.len>0) { + macroList.push_back(DivMacroExecList(ex1,ins->std.ex1Macro)); + ex1.prepare(&ins->std.ex1Macro); } - if (ins->std.ex2MacroLen>0) { - hadEx2=true; - hasEx2=true; - willEx2=true; + if (ins->std.ex2Macro.len>0) { + macroList.push_back(DivMacroExecList(ex2,ins->std.ex2Macro)); + ex2.prepare(&ins->std.ex2Macro); } - if (ins->std.ex3MacroLen>0) { - hadEx3=true; - hasEx3=true; - willEx3=true; + if (ins->std.ex3Macro.len>0) { + macroList.push_back(DivMacroExecList(ex3,ins->std.ex3Macro)); + ex3.prepare(&ins->std.ex3Macro); } - if (ins->std.algMacroLen>0) { - hadAlg=true; - hasAlg=true; - willAlg=true; + if (ins->std.algMacro.len>0) { + macroList.push_back(DivMacroExecList(alg,ins->std.algMacro)); + alg.prepare(&ins->std.algMacro); } - if (ins->std.fbMacroLen>0) { - hadFb=true; - hasFb=true; - willFb=true; + if (ins->std.fbMacro.len>0) { + macroList.push_back(DivMacroExecList(fb,ins->std.fbMacro)); + fb.prepare(&ins->std.fbMacro); } - if (ins->std.fmsMacroLen>0) { - hadFms=true; - hasFms=true; - willFms=true; + if (ins->std.fmsMacro.len>0) { + macroList.push_back(DivMacroExecList(fms,ins->std.fmsMacro)); + fms.prepare(&ins->std.fmsMacro); } - if (ins->std.amsMacroLen>0) { - hadAms=true; - hasAms=true; - willAms=true; + if (ins->std.fms2Macro.len>0) { + macroList.push_back(DivMacroExecList(fms2,ins->std.fms2Macro)); + fms2.prepare(&ins->std.fms2Macro); + } + if (ins->std.amsMacro.len>0) { + macroList.push_back(DivMacroExecList(ams,ins->std.amsMacro)); + ams.prepare(&ins->std.amsMacro); + } + if (ins->std.ams2Macro.len>0) { + macroList.push_back(DivMacroExecList(ams2,ins->std.ams2Macro)); + ams2.prepare(&ins->std.ams2Macro); } // TODO: other macros - if (ins->std.panLMacroLen>0) { - hadPanL=true; - hasPanL=true; - willPanL=true; + if (ins->std.panLMacro.len>0) { + macroList.push_back(DivMacroExecList(panL,ins->std.panLMacro)); + panL.prepare(&ins->std.panLMacro); } - if (ins->std.panRMacroLen>0) { - hadPanR=true; - hasPanR=true; - willPanR=true; + if (ins->std.panRMacro.len>0) { + macroList.push_back(DivMacroExecList(panR,ins->std.panRMacro)); + panR.prepare(&ins->std.panRMacro); } - if (ins->std.phaseResetMacroLen>0) { - hadPhaseReset=true; - hasPhaseReset=true; - willPhaseReset=true; + if (ins->std.phaseResetMacro.len>0) { + macroList.push_back(DivMacroExecList(phaseReset,ins->std.phaseResetMacro)); + phaseReset.prepare(&ins->std.phaseResetMacro); } - if (ins->std.ex4MacroLen>0) { - hadEx4=true; - hasEx4=true; - willEx4=true; + if (ins->std.ex4Macro.len>0) { + macroList.push_back(DivMacroExecList(ex4,ins->std.ex4Macro)); + ex4.prepare(&ins->std.ex4Macro); } - if (ins->std.ex5MacroLen>0) { - hadEx5=true; - hasEx5=true; - willEx5=true; + if (ins->std.ex5Macro.len>0) { + macroList.push_back(DivMacroExecList(ex5,ins->std.ex5Macro)); + ex5.prepare(&ins->std.ex5Macro); } - if (ins->std.ex6MacroLen>0) { - hadEx6=true; - hasEx6=true; - willEx6=true; + if (ins->std.ex6Macro.len>0) { + macroList.push_back(DivMacroExecList(ex6,ins->std.ex6Macro)); + ex6.prepare(&ins->std.ex6Macro); } - if (ins->std.ex7MacroLen>0) { - hadEx7=true; - hasEx7=true; - willEx7=true; + if (ins->std.ex7Macro.len>0) { + macroList.push_back(DivMacroExecList(ex7,ins->std.ex7Macro)); + ex7.prepare(&ins->std.ex7Macro); } - if (ins->std.ex8MacroLen>0) { - hadEx8=true; - hasEx8=true; - willEx8=true; - } - - if (ins->std.arpMacroMode) { - arpMode=true; + if (ins->std.ex8Macro.len>0) { + macroList.push_back(DivMacroExecList(ex8,ins->std.ex8Macro)); + ex8.prepare(&ins->std.ex8Macro); } + // prepare FM operator macros for (int i=0; i<4; i++) { DivInstrumentSTD::OpMacro& m=ins->std.opMacros[i]; IntOp& o=op[i]; - - if (m.amMacroLen>0) { - o.hadAm=true; - o.hasAm=true; - o.willAm=true; + if (m.amMacro.len>0) { + macroList.push_back(DivMacroExecList(o.am,m.amMacro)); + o.am.prepare(&m.amMacro); } - if (m.arMacroLen>0) { - o.hadAr=true; - o.hasAr=true; - o.willAr=true; + if (m.arMacro.len>0) { + macroList.push_back(DivMacroExecList(o.ar,m.arMacro)); + o.ar.prepare(&m.arMacro); } - if (m.drMacroLen>0) { - o.hadDr=true; - o.hasDr=true; - o.willDr=true; + if (m.drMacro.len>0) { + macroList.push_back(DivMacroExecList(o.dr,m.drMacro)); + o.dr.prepare(&m.drMacro); } - if (m.multMacroLen>0) { - o.hadMult=true; - o.hasMult=true; - o.willMult=true; + if (m.multMacro.len>0) { + macroList.push_back(DivMacroExecList(o.mult,m.multMacro)); + o.mult.prepare(&m.multMacro); } - if (m.rrMacroLen>0) { - o.hadRr=true; - o.hasRr=true; - o.willRr=true; + if (m.rrMacro.len>0) { + macroList.push_back(DivMacroExecList(o.rr,m.rrMacro)); + o.rr.prepare(&m.rrMacro); } - if (m.slMacroLen>0) { - o.hadSl=true; - o.hasSl=true; - o.willSl=true; + if (m.slMacro.len>0) { + macroList.push_back(DivMacroExecList(o.sl,m.slMacro)); + o.sl.prepare(&m.slMacro); } - if (m.tlMacroLen>0) { - o.hadTl=true; - o.hasTl=true; - o.willTl=true; + if (m.tlMacro.len>0) { + macroList.push_back(DivMacroExecList(o.tl,m.tlMacro)); + o.tl.prepare(&m.tlMacro); } - if (m.dt2MacroLen>0) { - o.hadDt2=true; - o.hasDt2=true; - o.willDt2=true; + if (m.dt2Macro.len>0) { + macroList.push_back(DivMacroExecList(o.dt2,m.dt2Macro)); + o.dt2.prepare(&m.dt2Macro); } - if (m.rsMacroLen>0) { - o.hadRs=true; - o.hasRs=true; - o.willRs=true; + if (m.rsMacro.len>0) { + macroList.push_back(DivMacroExecList(o.rs,m.rsMacro)); + o.rs.prepare(&m.rsMacro); } - if (m.dtMacroLen>0) { - o.hadDt=true; - o.hasDt=true; - o.willDt=true; + if (m.dtMacro.len>0) { + macroList.push_back(DivMacroExecList(o.dt,m.dtMacro)); + o.dt.prepare(&m.dtMacro); } - if (m.d2rMacroLen>0) { - o.hadD2r=true; - o.hasD2r=true; - o.willD2r=true; + if (m.d2rMacro.len>0) { + macroList.push_back(DivMacroExecList(o.d2r,m.d2rMacro)); + o.d2r.prepare(&m.d2rMacro); } - if (m.ssgMacroLen>0) { - o.hadSsg=true; - o.hasSsg=true; - o.willSsg=true; + if (m.ssgMacro.len>0) { + macroList.push_back(DivMacroExecList(o.ssg,m.ssgMacro)); + o.ssg.prepare(&m.ssgMacro); } - if (m.damMacroLen>0) { - o.hadDam=true; - o.hasDam=true; - o.willDam=true; + if (m.damMacro.len>0) { + macroList.push_back(DivMacroExecList(o.dam,m.damMacro)); + o.dam.prepare(&m.damMacro); } - if (m.dvbMacroLen>0) { - o.hadDvb=true; - o.hasDvb=true; - o.willDvb=true; + if (m.dvbMacro.len>0) { + macroList.push_back(DivMacroExecList(o.dvb,m.dvbMacro)); + o.dvb.prepare(&m.dvbMacro); } - if (m.egtMacroLen>0) { - o.hadEgt=true; - o.hasEgt=true; - o.willEgt=true; + if (m.egtMacro.len>0) { + macroList.push_back(DivMacroExecList(o.egt,m.egtMacro)); + o.egt.prepare(&m.egtMacro); } - if (m.kslMacroLen>0) { - o.hadKsl=true; - o.hasKsl=true; - o.willKsl=true; + if (m.kslMacro.len>0) { + macroList.push_back(DivMacroExecList(o.ksl,m.kslMacro)); + o.ksl.prepare(&m.kslMacro); } - if (m.susMacroLen>0) { - o.hadSus=true; - o.hasSus=true; - o.willSus=true; + if (m.susMacro.len>0) { + macroList.push_back(DivMacroExecList(o.sus,m.susMacro)); + o.sus.prepare(&m.susMacro); } - if (m.vibMacroLen>0) { - o.hadVib=true; - o.hasVib=true; - o.willVib=true; + if (m.vibMacro.len>0) { + macroList.push_back(DivMacroExecList(o.vib,m.vibMacro)); + o.vib.prepare(&m.vibMacro); } - if (m.wsMacroLen>0) { - o.hadWs=true; - o.hasWs=true; - o.willWs=true; + if (m.wsMacro.len>0) { + macroList.push_back(DivMacroExecList(o.ws,m.wsMacro)); + o.ws.prepare(&m.wsMacro); } - if (m.ksrMacroLen>0) { - o.hadKsr=true; - o.hasKsr=true; - o.willKsr=true; + if (m.ksrMacro.len>0) { + macroList.push_back(DivMacroExecList(o.ksr,m.ksrMacro)); + o.ksr.prepare(&m.ksrMacro); + } + } + + // prepare wavesynth macros + if (ins->std.ws.wave1Macro.len>0) { + macroList.push_back(DivMacroExecList(ws.wave1,ins->std.ws.wave1Macro)); + ws.wave1.prepare(&ins->std.ws.wave1Macro); + } + if (ins->std.ws.wave2Macro.len>0) { + macroList.push_back(DivMacroExecList(ws.wave2,ins->std.ws.wave2Macro)); + ws.wave2.prepare(&ins->std.ws.wave2Macro); + } + if (ins->std.ws.rateDividerMacro.len>0) { + macroList.push_back(DivMacroExecList(ws.rateDivider,ins->std.ws.rateDividerMacro)); + ws.rateDivider.prepare(&ins->std.ws.rateDividerMacro); + } + if (ins->std.ws.effectMacro.len>0) { + macroList.push_back(DivMacroExecList(ws.effect,ins->std.ws.effectMacro)); + ws.effect.prepare(&ins->std.ws.effectMacro); + } + if (ins->std.ws.oneShotMacro.len>0) { + macroList.push_back(DivMacroExecList(ws.oneShot,ins->std.ws.oneShotMacro)); + ws.oneShot.prepare(&ins->std.ws.oneShotMacro); + } + if (ins->std.ws.enabledMacro.len>0) { + macroList.push_back(DivMacroExecList(ws.enabled,ins->std.ws.enabledMacro)); + ws.enabled.prepare(&ins->std.ws.enabledMacro); + } + if (ins->std.ws.globalMacro.len>0) { + macroList.push_back(DivMacroExecList(ws.global,ins->std.ws.globalMacro)); + ws.global.prepare(&ins->std.ws.globalMacro); + } + if (ins->std.ws.speedMacro.len>0) { + macroList.push_back(DivMacroExecList(ws.speed,ins->std.ws.speedMacro)); + ws.speed.prepare(&ins->std.ws.speedMacro); + } + if (ins->std.ws.param1Macro.len>0) { + macroList.push_back(DivMacroExecList(ws.param1,ins->std.ws.param1Macro)); + ws.param1.prepare(&ins->std.ws.param1Macro); + } + if (ins->std.ws.param2Macro.len>0) { + macroList.push_back(DivMacroExecList(ws.param2,ins->std.ws.param2Macro)); + ws.param2.prepare(&ins->std.ws.param2Macro); + } + if (ins->std.ws.param3Macro.len>0) { + macroList.push_back(DivMacroExecList(ws.param3,ins->std.ws.param3Macro)); + ws.param3.prepare(&ins->std.ws.param3Macro); + } + if (ins->std.ws.param4Macro.len>0) { + macroList.push_back(DivMacroExecList(ws.param4,ins->std.ws.param4Macro)); + ws.param4.prepare(&ins->std.ws.param4Macro); + } + if (!macroList.empty()) { + for (std::list::iterator iter = macroList.begin(); iter!= macroList.end(); iter++) { + iter->prepare(); } } } diff --git a/src/engine/macroInt.h b/src/engine/macroInt.h index d5e6fd140..d7236805e 100644 --- a/src/engine/macroInt.h +++ b/src/engine/macroInt.h @@ -21,127 +21,115 @@ #define _MACROINT_H #include "instrument.h" +#include + +struct DivMacroStruct { + DivInstrumentMacro* source; + int pos; + int val; + bool has, had, finished, will; + unsigned int mode; + void doMacro(DivInstrumentMacro& source, bool released); + void init() { + source=NULL; + pos=mode=0; + has=had=will=false; + } + void prepare(DivInstrumentMacro* s) { + if (s!=NULL) { + source=s; + has=had=will=true; + mode=source->mode; + } + } + DivMacroStruct(): + source(NULL), + pos(0), + val(0), + has(false), + had(false), + finished(false), + will(false), + mode(0) {} +}; + +struct DivMacroExecList { + DivMacroStruct& macro; + DivInstrumentMacro& source; + void prepare() { + macro.prepare(&source); + } + void doMacro(bool released) { + macro.doMacro(source, released); + } + DivMacroExecList(DivMacroStruct &m, DivInstrumentMacro& s): + macro(m), + source(s) {} +}; class DivMacroInt { DivInstrument* ins; - int volPos, arpPos, dutyPos, wavePos, pitchPos, ex1Pos, ex2Pos, ex3Pos; - int algPos, fbPos, fmsPos, amsPos; - int panLPos, panRPos, phaseResetPos, ex4Pos, ex5Pos, ex6Pos, ex7Pos, ex8Pos; + std::list macroList; bool released; public: - int vol; - int arp; - int duty, wave, pitch, ex1, ex2, ex3; - int alg, fb, fms, ams; - int panL, panR, phaseReset, ex4, ex5, ex6, ex7, ex8; - bool hasVol, hasArp, hasDuty, hasWave, hasPitch, hasEx1, hasEx2, hasEx3, hasAlg, hasFb, hasFms, hasAms; - bool hasPanL, hasPanR, hasPhaseReset, hasEx4, hasEx5, hasEx6, hasEx7, hasEx8; - bool hadVol, hadArp, hadDuty, hadWave, hadPitch, hadEx1, hadEx2, hadEx3, hadAlg, hadFb, hadFms, hadAms; - bool hadPanL, hadPanR, hadPhaseReset, hadEx4, hadEx5, hadEx6, hadEx7, hadEx8; - bool finishedVol, finishedArp, finishedDuty, finishedWave, finishedPitch, finishedEx1, finishedEx2, finishedEx3; - bool finishedAlg, finishedFb, finishedFms, finishedAms; - bool finishedPanL, finishedPanR, finishedPhaseReset, finishedEx4, finishedEx5, finishedEx6, finishedEx7, finishedEx8; - bool willVol, willArp, willDuty, willWave, willPitch, willEx1, willEx2, willEx3, willAlg, willFb, willFms, willAms; - bool willPanL, willPanR, willPhaseReset, willEx4, willEx5, willEx6, willEx7, willEx8; - bool arpMode; + // common macro + DivMacroStruct vol; + DivMacroStruct arp; + DivMacroStruct duty, wave, pitch, ex1, ex2, ex3; + DivMacroStruct alg, fb, fms, fms2, ams, ams2; + DivMacroStruct panL, panR, phaseReset, ex4, ex5, ex6, ex7, ex8; + + // FM operator macro struct IntOp { - int amPos, arPos, drPos, multPos; - int rrPos, slPos, tlPos, dt2Pos; - int rsPos, dtPos, d2rPos, ssgPos; - int damPos, dvbPos, egtPos, kslPos; - int susPos, vibPos, wsPos, ksrPos; - - int am, ar, dr, mult; - int rr, sl, tl, dt2; - int rs, dt, d2r, ssg; - int dam, dvb, egt, ksl; - int sus, vib, ws, ksr; - - bool hasAm, hasAr, hasDr, hasMult; - bool hasRr, hasSl, hasTl, hasDt2; - bool hasRs, hasDt, hasD2r, hasSsg; - bool hasDam, hasDvb, hasEgt, hasKsl; - bool hasSus, hasVib, hasWs, hasKsr; - - bool hadAm, hadAr, hadDr, hadMult; - bool hadRr, hadSl, hadTl, hadDt2; - bool hadRs, hadDt, hadD2r, hadSsg; - bool hadDam, hadDvb, hadEgt, hadKsl; - bool hadSus, hadVib, hadWs, hadKsr; - - bool finishedAm, finishedAr, finishedDr, finishedMult; - bool finishedRr, finishedSl, finishedTl, finishedDt2; - bool finishedRs, finishedDt, finishedD2r, finishedSsg; - bool finishedDam, finishedDvb, finishedEgt, finishedKsl; - bool finishedSus, finishedVib, finishedWs, finishedKsr; - - bool willAm, willAr, willDr, willMult; - bool willRr, willSl, willTl, willDt2; - bool willRs, willDt, willD2r, willSsg; - bool willDam, willDvb, willEgt, willKsl; - bool willSus, willVib, willWs, willKsr; + DivMacroStruct am, ar, dr, mult; + DivMacroStruct rr, sl, tl, dt2; + DivMacroStruct rs, dt, d2r, ssg; + DivMacroStruct dam, dvb, egt, ksl; + DivMacroStruct sus, vib, ws, ksr; IntOp(): - amPos(0), - arPos(0), - drPos(0), - multPos(0), - rrPos(0), - slPos(0), - tlPos(0), - dt2Pos(0), - rsPos(0), - dtPos(0), - d2rPos(0), - ssgPos(0), - damPos(0), - dvbPos(0), - egtPos(0), - kslPos(0), - susPos(0), - vibPos(0), - wsPos(0), - ksrPos(0), - am(0), - ar(0), - dr(0), - mult(0), - rr(0), - sl(0), - tl(0), - dt2(0), - rs(0), - dt(0), - d2r(0), - ssg(0), - dam(0), - dvb(0), - egt(0), - ksl(0), - sus(0), - vib(0), - ws(0), - ksr(0), - hasAm(false), hasAr(false), hasDr(false), hasMult(false), - hasRr(false), hasSl(false), hasTl(false), hasDt2(false), - hasRs(false), hasDt(false), hasD2r(false), hasSsg(false), - hasDam(false), hasDvb(false), hasEgt(false), hasKsl(false), - hasSus(false), hasVib(false), hasWs(false), hasKsr(false), - hadAm(false), hadAr(false), hadDr(false), hadMult(false), - hadRr(false), hadSl(false), hadTl(false), hadDt2(false), - hadRs(false), hadDt(false), hadD2r(false), hadSsg(false), - hadDam(false), hadDvb(false), hadEgt(false), hadKsl(false), - hadSus(false), hadVib(false), hadWs(false), hadKsr(false), - finishedAm(false), finishedAr(false), finishedDr(false), finishedMult(false), - finishedRr(false), finishedSl(false), finishedTl(false), finishedDt2(false), - finishedRs(false), finishedDt(false), finishedD2r(false), finishedSsg(false), - finishedDam(false), finishedDvb(false), finishedEgt(false), finishedKsl(false), - finishedSus(false), finishedVib(false), finishedWs(false), finishedKsr(false), - willAm(false), willAr(false), willDr(false), willMult(false), - willRr(false), willSl(false), willTl(false), willDt2(false), - willRs(false), willDt(false), willD2r(false), willSsg(false), - willDam(false), willDvb(false), willEgt(false), willKsl(false), - willSus(false), willVib(false), willWs(false), willKsr(false) {} + am(), + ar(), + dr(), + mult(), + rr(), + sl(), + tl(), + dt2(), + rs(), + dt(), + d2r(), + ssg(), + dam(), + dvb(), + egt(), + ksl(), + sus(), + vib(), + ws(), + ksr() {} } op[4]; + + // wavesynth macro + struct IntWS { + DivMacroStruct wave1, wave2; + DivMacroStruct rateDivider; + DivMacroStruct effect; + DivMacroStruct oneShot, enabled, global; + DivMacroStruct speed, param1, param2, param3, param4; + IntWS(): + wave1(), + wave2(), + rateDivider(), + effect(), + oneShot(), + enabled(), + global(), + speed(), + param1(), + param2(), + param3(), + param4() {} + } ws; /** * trigger macro release. @@ -167,128 +155,29 @@ class DivMacroInt { DivMacroInt(): ins(NULL), - volPos(0), - arpPos(0), - dutyPos(0), - wavePos(0), - pitchPos(0), - ex1Pos(0), - ex2Pos(0), - ex3Pos(0), - algPos(0), - fbPos(0), - fmsPos(0), - amsPos(0), - panLPos(0), - panRPos(0), - phaseResetPos(0), - ex4Pos(0), - ex5Pos(0), - ex6Pos(0), - ex7Pos(0), - ex8Pos(0), released(false), - vol(0), - arp(0), - duty(0), - wave(0), - pitch(0), - ex1(0), - ex2(0), - ex3(0), - alg(0), - fb(0), - fms(0), - ams(0), - panL(0), - panR(0), - phaseReset(0), - ex4(0), - ex5(0), - ex6(0), - ex7(0), - ex8(0), - hasVol(false), - hasArp(false), - hasDuty(false), - hasWave(false), - hasPitch(false), - hasEx1(false), - hasEx2(false), - hasEx3(false), - hasAlg(false), - hasFb(false), - hasFms(false), - hasAms(false), - hasPanL(false), - hasPanR(false), - hasPhaseReset(false), - hasEx4(false), - hasEx5(false), - hasEx6(false), - hasEx7(false), - hasEx8(false), - hadVol(false), - hadArp(false), - hadDuty(false), - hadWave(false), - hadPitch(false), - hadEx1(false), - hadEx2(false), - hadEx3(false), - hadAlg(false), - hadFb(false), - hadFms(false), - hadAms(false), - hadPanL(false), - hadPanR(false), - hadPhaseReset(false), - hadEx4(false), - hadEx5(false), - hadEx6(false), - hadEx7(false), - hadEx8(false), - finishedVol(false), - finishedArp(false), - finishedDuty(false), - finishedWave(false), - finishedPitch(false), - finishedEx1(false), - finishedEx2(false), - finishedEx3(false), - finishedAlg(false), - finishedFb(false), - finishedFms(false), - finishedAms(false), - finishedPanL(false), - finishedPanR(false), - finishedPhaseReset(false), - finishedEx4(false), - finishedEx5(false), - finishedEx6(false), - finishedEx7(false), - finishedEx8(false), - willVol(false), - willArp(false), - willDuty(false), - willWave(false), - willPitch(false), - willEx1(false), - willEx2(false), - willEx3(false), - willAlg(false), - willFb(false), - willFms(false), - willAms(false), - willPanL(false), - willPanR(false), - willPhaseReset(false), - willEx4(false), - willEx5(false), - willEx6(false), - willEx7(false), - willEx8(false), - arpMode(false) {} + vol(), + arp(), + duty(), + wave(), + pitch(), + ex1(), + ex2(), + ex3(), + alg(), + fb(), + fms(), + fms2(), + ams(), + ams2(), + panL(), + panR(), + phaseReset(), + ex4(), + ex5(), + ex6(), + ex7(), + ex8() {} }; #endif diff --git a/src/engine/platform/amiga.cpp b/src/engine/platform/amiga.cpp index e1bc83947..458c9329d 100644 --- a/src/engine/platform/amiga.cpp +++ b/src/engine/platform/amiga.cpp @@ -147,8 +147,8 @@ void DivPlatformAmiga::acquire(short* bufL, short* bufR, size_t start, size_t le void DivPlatformAmiga::tick() { for (int i=0; i<4; i++) { chan[i].std.next(); - if (chan[i].std.hadVol) { - chan[i].outVol=((chan[i].vol%65)*MIN(64,chan[i].std.vol))>>6; + if (chan[i].std.vol.had) { + chan[i].outVol=((chan[i].vol%65)*MIN(64,chan[i].std.vol.val))>>6; } double off=1.0; if (chan[i].sample>=0 && chan[i].samplesong.sampleLen) { @@ -159,24 +159,24 @@ void DivPlatformAmiga::tick() { off=8363.0/(double)s->centerRate; } } - if (chan[i].std.hadArp) { + if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - if (chan[i].std.arpMode) { - chan[i].baseFreq=round(off*NOTE_PERIODIC_NOROUND(chan[i].std.arp)); + if (chan[i].std.arp.mode) { + chan[i].baseFreq=round(off*NOTE_PERIODIC_NOROUND(chan[i].std.arp.val)); } else { - chan[i].baseFreq=round(off*NOTE_PERIODIC_NOROUND(chan[i].note+chan[i].std.arp)); + chan[i].baseFreq=round(off*NOTE_PERIODIC_NOROUND(chan[i].note+chan[i].std.arp.val)); } } chan[i].freqChanged=true; } else { - if (chan[i].std.arpMode && chan[i].std.finishedArp) { + if (chan[i].std.arp.mode && chan[i].std.arp.finished) { chan[i].baseFreq=round(off*NOTE_PERIODIC_NOROUND(chan[i].note)); chan[i].freqChanged=true; } } - if (chan[i].std.hadWave) { - if (chan[i].wave!=chan[i].std.wave) { - chan[i].wave=chan[i].std.wave; + if (chan[i].std.wave.had) { + if (chan[i].wave!=chan[i].std.wave.val) { + chan[i].wave=chan[i].std.wave.val; if (!chan[i].keyOff) chan[i].keyOn=true; } } @@ -252,13 +252,13 @@ int DivPlatformAmiga::dispatch(DivCommand c) { case DIV_CMD_VOLUME: if (chan[c.chan].vol!=c.value) { chan[c.chan].vol=c.value; - if (!chan[c.chan].std.hasVol) { + if (!chan[c.chan].std.vol.has) { chan[c.chan].outVol=c.value; } } break; case DIV_CMD_GET_VOLUME: - if (chan[c.chan].std.hasVol) { + if (chan[c.chan].std.vol.has) { return chan[c.chan].vol; } return chan[c.chan].outVol; @@ -315,7 +315,7 @@ int DivPlatformAmiga::dispatch(DivCommand c) { off=8363.0/(double)s->centerRate; } } - chan[c.chan].baseFreq=round(off*NOTE_PERIODIC_NOROUND(c.value+((chan[c.chan].std.willArp && !chan[c.chan].std.arpMode)?(chan[c.chan].std.arp-12):(0)))); + chan[c.chan].baseFreq=round(off*NOTE_PERIODIC_NOROUND(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val-12):(0)))); chan[c.chan].freqChanged=true; chan[c.chan].note=c.value; break; diff --git a/src/engine/platform/arcade.cpp b/src/engine/platform/arcade.cpp index 7c465a511..41530a0c7 100644 --- a/src/engine/platform/arcade.cpp +++ b/src/engine/platform/arcade.cpp @@ -220,8 +220,8 @@ void DivPlatformArcade::tick() { for (int i=0; i<8; i++) { chan[i].std.next(); - if (chan[i].std.hadVol) { - chan[i].outVol=(chan[i].vol*MIN(127,chan[i].std.vol))/127; + if (chan[i].std.vol.had) { + chan[i].outVol=(chan[i].vol*MIN(127,chan[i].std.vol.val))/127; for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; @@ -233,50 +233,50 @@ void DivPlatformArcade::tick() { } } - if (chan[i].std.hadArp) { + if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - if (chan[i].std.arpMode) { - chan[i].baseFreq=NOTE_LINEAR(chan[i].std.arp); + if (chan[i].std.arp.mode) { + chan[i].baseFreq=NOTE_LINEAR(chan[i].std.arp.val); } else { - chan[i].baseFreq=NOTE_LINEAR(chan[i].note+(signed char)chan[i].std.arp); + chan[i].baseFreq=NOTE_LINEAR(chan[i].note+(signed char)chan[i].std.arp.val); } } chan[i].freqChanged=true; } else { - if (chan[i].std.arpMode && chan[i].std.finishedArp) { + if (chan[i].std.arp.mode && chan[i].std.arp.finished) { chan[i].baseFreq=NOTE_LINEAR(chan[i].note); chan[i].freqChanged=true; } } - if (chan[i].std.hadDuty) { - if (chan[i].std.duty>0) { - rWrite(0x0f,0x80|(0x20-chan[i].std.duty)); + if (chan[i].std.duty.had) { + if (chan[i].std.duty.val>0) { + rWrite(0x0f,0x80|(0x20-chan[i].std.duty.val)); } else { rWrite(0x0f,0); } } - if (chan[i].std.hadWave) { - rWrite(0x1b,chan[i].std.wave&3); + if (chan[i].std.wave.had) { + rWrite(0x1b,chan[i].std.wave.val&3); } - if (chan[i].std.hadEx1) { - amDepth=chan[i].std.ex1; + if (chan[i].std.ex1.had) { + amDepth=chan[i].std.ex1.val; immWrite(0x19,amDepth); } - if (chan[i].std.hadEx2) { - pmDepth=chan[i].std.ex2; + if (chan[i].std.ex2.had) { + pmDepth=chan[i].std.ex2.val; immWrite(0x19,0x80|pmDepth); } - if (chan[i].std.hadEx3) { - immWrite(0x18,chan[i].std.ex3); + if (chan[i].std.ex3.had) { + immWrite(0x18,chan[i].std.ex3.val); } - if (chan[i].std.hadAlg) { - chan[i].state.alg=chan[i].std.alg; + if (chan[i].std.alg.had) { + chan[i].state.alg=chan[i].std.alg.val; if (isMuted[i]) { rWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); } else { @@ -296,72 +296,72 @@ void DivPlatformArcade::tick() { } } } - if (chan[i].std.hadFb) { - chan[i].state.fb=chan[i].std.fb; + if (chan[i].std.fb.had) { + chan[i].state.fb=chan[i].std.fb.val; if (isMuted[i]) { rWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); } else { rWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|((chan[i].chVolL&1)<<6)|((chan[i].chVolR&1)<<7)); } } - if (chan[i].std.hadFms) { - chan[i].state.fms=chan[i].std.fms; + if (chan[i].std.fms.had) { + chan[i].state.fms=chan[i].std.fms.val; rWrite(chanOffs[i]+ADDR_FMS_AMS,((chan[i].state.fms&7)<<4)|(chan[i].state.ams&3)); } - if (chan[i].std.hadAms) { - chan[i].state.ams=chan[i].std.ams; + if (chan[i].std.ams.had) { + chan[i].state.ams=chan[i].std.ams.val; rWrite(chanOffs[i]+ADDR_FMS_AMS,((chan[i].state.fms&7)<<4)|(chan[i].state.ams&3)); } for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; DivMacroInt::IntOp& m=chan[i].std.op[j]; - if (m.hadAm) { - op.am=m.am; + if (m.am.had) { + op.am=m.am.val; rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); } - if (m.hadAr) { - op.ar=m.ar; + if (m.ar.had) { + op.ar=m.ar.val; rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); } - if (m.hadDr) { - op.dr=m.dr; + if (m.dr.had) { + op.dr=m.dr.val; rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); } - if (m.hadMult) { - op.mult=m.mult; + if (m.mult.had) { + op.mult=m.mult.val; rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); } - if (m.hadRr) { - op.rr=m.rr; + if (m.rr.had) { + op.rr=m.rr.val; rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); } - if (m.hadSl) { - op.sl=m.sl; + if (m.sl.had) { + op.sl=m.sl.val; rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); } - if (m.hadTl) { - op.tl=127-m.tl; + if (m.tl.had) { + op.tl=127-m.tl.val; if (isOutput[chan[i].state.alg][j]) { rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); } else { rWrite(baseAddr+ADDR_TL,op.tl); } } - if (m.hadRs) { - op.rs=m.rs; + if (m.rs.had) { + op.rs=m.rs.val; rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); } - if (m.hadDt) { - op.dt=m.dt; + if (m.dt.had) { + op.dt=m.dt.val; rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); } - if (m.hadD2r) { - op.d2r=m.d2r; + if (m.d2r.had) { + op.d2r=m.d2r.val; rWrite(baseAddr+ADDR_DT2_D2R,(op.d2r&31)|(op.dt2<<6)); } - if (m.hadDt2) { - op.dt2=m.dt2; + if (m.dt2.had) { + op.dt2=m.dt2.val; rWrite(baseAddr+ADDR_DT2_D2R,(op.d2r&31)|(op.dt2<<6)); } } @@ -413,7 +413,7 @@ int DivPlatformArcade::dispatch(DivCommand c) { } chan[c.chan].std.init(ins); - if (!chan[c.chan].std.willVol) { + if (!chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; } @@ -472,7 +472,7 @@ int DivPlatformArcade::dispatch(DivCommand c) { break; case DIV_CMD_VOLUME: { chan[c.chan].vol=c.value; - if (!chan[c.chan].std.hasVol) { + if (!chan[c.chan].std.vol.has) { chan[c.chan].outVol=c.value; } for (int i=0; i<4; i++) { diff --git a/src/engine/platform/ay.cpp b/src/engine/platform/ay.cpp index b798cba4e..6cdd6b030 100644 --- a/src/engine/platform/ay.cpp +++ b/src/engine/platform/ay.cpp @@ -176,8 +176,8 @@ void DivPlatformAY8910::tick() { // PSG for (int i=0; i<3; i++) { chan[i].std.next(); - if (chan[i].std.hadVol) { - chan[i].outVol=MIN(15,chan[i].std.vol)-(15-(chan[i].vol&15)); + if (chan[i].std.vol.had) { + chan[i].outVol=MIN(15,chan[i].std.vol.val)-(15-(chan[i].vol&15)); if (chan[i].outVol<0) chan[i].outVol=0; if (isMuted[i]) { rWrite(0x08+i,0); @@ -187,26 +187,26 @@ void DivPlatformAY8910::tick() { rWrite(0x08+i,(chan[i].outVol&15)|((chan[i].psgMode&4)<<2)); } } - if (chan[i].std.hadArp) { + if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - if (chan[i].std.arpMode) { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp); + if (chan[i].std.arp.mode) { + chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp.val); } else { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp); + chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp.val); } } chan[i].freqChanged=true; } else { - if (chan[i].std.arpMode && chan[i].std.finishedArp) { + if (chan[i].std.arp.mode && chan[i].std.arp.finished) { chan[i].baseFreq=NOTE_PERIODIC(chan[i].note); chan[i].freqChanged=true; } } - if (chan[i].std.hadDuty) { - rWrite(0x06,31-chan[i].std.duty); + if (chan[i].std.duty.had) { + rWrite(0x06,31-chan[i].std.duty.val); } - if (chan[i].std.hadWave) { - chan[i].psgMode=(chan[i].std.wave+1)&7; + if (chan[i].std.wave.had) { + chan[i].psgMode=(chan[i].std.wave.val+1)&7; if (isMuted[i]) { rWrite(0x08+i,0); } else if (intellivision && (chan[i].psgMode&4)) { @@ -215,19 +215,19 @@ void DivPlatformAY8910::tick() { rWrite(0x08+i,(chan[i].outVol&15)|((chan[i].psgMode&4)<<2)); } } - if (chan[i].std.hadEx2) { - ayEnvMode=chan[i].std.ex2; + if (chan[i].std.ex2.had) { + ayEnvMode=chan[i].std.ex2.val; rWrite(0x0d,ayEnvMode); } - if (chan[i].std.hadEx3) { - chan[i].autoEnvNum=chan[i].std.ex3; + if (chan[i].std.ex3.had) { + chan[i].autoEnvNum=chan[i].std.ex3.val; chan[i].freqChanged=true; - if (!chan[i].std.willAlg) chan[i].autoEnvDen=1; + if (!chan[i].std.alg.will) chan[i].autoEnvDen=1; } - if (chan[i].std.hadAlg) { - chan[i].autoEnvDen=chan[i].std.alg; + if (chan[i].std.alg.had) { + chan[i].autoEnvDen=chan[i].std.alg.val; chan[i].freqChanged=true; - if (!chan[i].std.willEx3) chan[i].autoEnvNum=1; + if (!chan[i].std.ex3.will) chan[i].autoEnvNum=1; } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true); @@ -314,7 +314,7 @@ int DivPlatformAY8910::dispatch(DivCommand c) { break; case DIV_CMD_VOLUME: { chan[c.chan].vol=c.value; - if (!chan[c.chan].std.hasVol) { + if (!chan[c.chan].std.vol.has) { chan[c.chan].outVol=c.value; } if (isMuted[c.chan]) { diff --git a/src/engine/platform/ay8930.cpp b/src/engine/platform/ay8930.cpp index 1849d0cdc..de17fa67f 100644 --- a/src/engine/platform/ay8930.cpp +++ b/src/engine/platform/ay8930.cpp @@ -191,8 +191,8 @@ void DivPlatformAY8930::tick() { // PSG for (int i=0; i<3; i++) { chan[i].std.next(); - if (chan[i].std.hadVol) { - chan[i].outVol=MIN(31,chan[i].std.vol)-(31-(chan[i].vol&31)); + if (chan[i].std.vol.had) { + chan[i].outVol=MIN(31,chan[i].std.vol.val)-(31-(chan[i].vol&31)); if (chan[i].outVol<0) chan[i].outVol=0; if (isMuted[i]) { rWrite(0x08+i,0); @@ -200,55 +200,55 @@ void DivPlatformAY8930::tick() { rWrite(0x08+i,(chan[i].outVol&31)|((chan[i].psgMode&4)<<3)); } } - if (chan[i].std.hadArp) { + if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - if (chan[i].std.arpMode) { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp); + if (chan[i].std.arp.mode) { + chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp.val); } else { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp); + chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp.val); } } chan[i].freqChanged=true; } else { - if (chan[i].std.arpMode && chan[i].std.finishedArp) { + if (chan[i].std.arp.mode && chan[i].std.arp.finished) { chan[i].baseFreq=NOTE_PERIODIC(chan[i].note); chan[i].freqChanged=true; } } - if (chan[i].std.hadDuty) { - rWrite(0x06,chan[i].std.duty); + if (chan[i].std.duty.had) { + rWrite(0x06,chan[i].std.duty.val); } - if (chan[i].std.hadWave) { - chan[i].psgMode=(chan[i].std.wave+1)&7; + if (chan[i].std.wave.had) { + chan[i].psgMode=(chan[i].std.wave.val+1)&7; if (isMuted[i]) { rWrite(0x08+i,0); } else { rWrite(0x08+i,(chan[i].outVol&31)|((chan[i].psgMode&4)<<3)); } } - if (chan[i].std.hadEx1) { // duty - rWrite(0x16+i,chan[i].std.ex1); + if (chan[i].std.ex1.had) { // duty + rWrite(0x16+i,chan[i].std.ex1.val); } - if (chan[i].std.hadEx2) { - ayEnvMode[i]=chan[i].std.ex2; + if (chan[i].std.ex2.had) { + ayEnvMode[i]=chan[i].std.ex2.val; rWrite(regMode[i],ayEnvMode[i]); } - if (chan[i].std.hadEx3) { - chan[i].autoEnvNum=chan[i].std.ex3; + if (chan[i].std.ex3.had) { + chan[i].autoEnvNum=chan[i].std.ex3.val; chan[i].freqChanged=true; - if (!chan[i].std.willAlg) chan[i].autoEnvDen=1; + if (!chan[i].std.alg.will) chan[i].autoEnvDen=1; } - if (chan[i].std.hadAlg) { - chan[i].autoEnvDen=chan[i].std.alg; + if (chan[i].std.alg.had) { + chan[i].autoEnvDen=chan[i].std.alg.val; chan[i].freqChanged=true; - if (!chan[i].std.willEx3) chan[i].autoEnvNum=1; + if (!chan[i].std.ex3.will) chan[i].autoEnvNum=1; } - if (chan[i].std.hadFb) { - ayNoiseAnd=chan[i].std.fb; + if (chan[i].std.fb.had) { + ayNoiseAnd=chan[i].std.fb.val; immWrite(0x19,ayNoiseAnd); } - if (chan[i].std.hadFms) { - ayNoiseOr=chan[i].std.fms; + if (chan[i].std.fms.had) { + ayNoiseOr=chan[i].std.fms.val; immWrite(0x1a,ayNoiseOr); } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { @@ -256,7 +256,7 @@ void DivPlatformAY8930::tick() { if (chan[i].freq>65535) chan[i].freq=65535; if (chan[i].keyOn) { if (chan[i].insChanged) { - if (!chan[i].std.willEx1) immWrite(0x16+i,chan[i].duty); + if (!chan[i].std.ex1.will) immWrite(0x16+i,chan[i].duty); chan[i].insChanged=false; } } @@ -336,7 +336,7 @@ int DivPlatformAY8930::dispatch(DivCommand c) { break; case DIV_CMD_VOLUME: { chan[c.chan].vol=c.value; - if (!chan[c.chan].std.hasVol) { + if (!chan[c.chan].std.vol.has) { chan[c.chan].outVol=c.value; } if (isMuted[c.chan]) { diff --git a/src/engine/platform/bubsyswsg.cpp b/src/engine/platform/bubsyswsg.cpp index 989619722..712496c1c 100644 --- a/src/engine/platform/bubsyswsg.cpp +++ b/src/engine/platform/bubsyswsg.cpp @@ -84,28 +84,28 @@ void DivPlatformBubSysWSG::updateWave(int ch) { void DivPlatformBubSysWSG::tick() { for (int i=0; i<2; i++) { chan[i].std.next(); - if (chan[i].std.hadVol) { - chan[i].outVol=((chan[i].vol&15)*MIN(15,chan[i].std.vol))/15; + if (chan[i].std.vol.had) { + chan[i].outVol=((chan[i].vol&15)*MIN(15,chan[i].std.vol.val))/15; rWrite(2+i,(chan[i].wave<<5)|chan[i].outVol); } - if (chan[i].std.hadArp) { + if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - if (chan[i].std.arpMode) { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp); + if (chan[i].std.arp.mode) { + chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp.val); } else { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp); + chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp.val); } } chan[i].freqChanged=true; } else { - if (chan[i].std.arpMode && chan[i].std.finishedArp) { + if (chan[i].std.arp.mode && chan[i].std.arp.finished) { chan[i].baseFreq=NOTE_PERIODIC(chan[i].note); chan[i].freqChanged=true; } } - if (chan[i].std.hadWave) { - if (chan[i].wave!=chan[i].std.wave || chan[i].ws.activeChanged()) { - chan[i].wave=chan[i].std.wave; + if (chan[i].std.wave.had) { + if (chan[i].wave!=chan[i].std.wave.val || chan[i].ws.activeChanged()) { + chan[i].wave=chan[i].std.wave.val; chan[i].ws.changeWave1(chan[i].wave); if (!chan[i].keyOff) chan[i].keyOn=true; } @@ -174,14 +174,14 @@ int DivPlatformBubSysWSG::dispatch(DivCommand c) { case DIV_CMD_VOLUME: if (chan[c.chan].vol!=c.value) { chan[c.chan].vol=c.value; - if (!chan[c.chan].std.hasVol) { + if (!chan[c.chan].std.vol.has) { chan[c.chan].outVol=c.value; if (chan[c.chan].active) rWrite(2+c.chan,(chan[c.chan].wave<<5)|chan[c.chan].outVol); } } break; case DIV_CMD_GET_VOLUME: - if (chan[c.chan].std.hasVol) { + if (chan[c.chan].std.vol.has) { return chan[c.chan].vol; } return chan[c.chan].outVol; @@ -219,7 +219,7 @@ int DivPlatformBubSysWSG::dispatch(DivCommand c) { break; } case DIV_CMD_LEGATO: - chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.willArp && !chan[c.chan].std.arpMode)?(chan[c.chan].std.arp):(0))); + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0))); chan[c.chan].freqChanged=true; chan[c.chan].note=c.value; break; diff --git a/src/engine/platform/c64.cpp b/src/engine/platform/c64.cpp index 49be60fb8..786da058c 100644 --- a/src/engine/platform/c64.cpp +++ b/src/engine/platform/c64.cpp @@ -125,43 +125,43 @@ void DivPlatformC64::updateFilter() { void DivPlatformC64::tick() { for (int i=0; i<3; i++) { chan[i].std.next(); - if (chan[i].std.hadVol) { + if (chan[i].std.vol.had) { DivInstrument* ins=parent->getIns(chan[i].ins); if (ins->c64.volIsCutoff) { if (ins->c64.filterIsAbs) { - filtCut=MIN(2047,chan[i].std.vol); + filtCut=MIN(2047,chan[i].std.vol.val); } else { - filtCut-=((signed char)chan[i].std.vol-18)*7; + filtCut-=((signed char)chan[i].std.vol.val-18)*7; if (filtCut>2047) filtCut=2047; if (filtCut<0) filtCut=0; } updateFilter(); } else { - vol=MIN(15,chan[i].std.vol); + vol=MIN(15,chan[i].std.vol.val); updateFilter(); } } - if (chan[i].std.hadArp) { + if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - if (chan[i].std.arpMode) { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp); + if (chan[i].std.arp.mode) { + chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val); } else { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+(signed char)chan[i].std.arp); + chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+(signed char)chan[i].std.arp.val); } } chan[i].freqChanged=true; } else { - if (chan[i].std.arpMode && chan[i].std.finishedArp) { + if (chan[i].std.arp.mode && chan[i].std.arp.finished) { chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note); chan[i].freqChanged=true; } } - if (chan[i].std.hadDuty) { + if (chan[i].std.duty.had) { DivInstrument* ins=parent->getIns(chan[i].ins); - if (ins->c64.dutyIsAbs) { - chan[i].duty=chan[i].std.duty; + if (ins->std.dutyMacro.mode) { + chan[i].duty=chan[i].std.duty.val; } else { - chan[i].duty-=((signed char)chan[i].std.duty-12)*4; + chan[i].duty-=((signed char)chan[i].std.duty.val-12)*4; } rWrite(i*7+2,chan[i].duty&0xff); rWrite(i*7+3,chan[i].duty>>8); @@ -175,21 +175,21 @@ void DivPlatformC64::tick() { } } } - if (chan[i].std.hadWave) { - chan[i].wave=chan[i].std.wave; + if (chan[i].std.wave.had) { + chan[i].wave=chan[i].std.wave.val; rWrite(i*7+4,(isMuted[i]?8:(chan[i].wave<<4))|(chan[i].ring<<2)|(chan[i].sync<<1)|chan[i].active); } - if (chan[i].std.hadEx1) { - filtControl=chan[i].std.ex1&15; + if (chan[i].std.ex1.had) { + filtControl=chan[i].std.ex1.val&15; updateFilter(); } - if (chan[i].std.hadEx2) { - filtRes=chan[i].std.ex2&15; + if (chan[i].std.ex2.had) { + filtRes=chan[i].std.ex2.val&15; updateFilter(); } - if (chan[i].std.hadEx3) { - chan[i].sync=chan[i].std.ex3&1; - chan[i].ring=chan[i].std.ex3&2; + if (chan[i].std.ex3.had) { + chan[i].sync=chan[i].std.ex3.val&1; + chan[i].ring=chan[i].std.ex3.val&2; chan[i].freqChanged=true; } @@ -226,7 +226,7 @@ int DivPlatformC64::dispatch(DivCommand c) { } chan[c.chan].active=true; chan[c.chan].keyOn=true; - if (chan[c.chan].insChanged || chan[c.chan].resetDuty || ins->std.waveMacroLen>0) { + if (chan[c.chan].insChanged || chan[c.chan].resetDuty || ins->std.waveMacro.len>0) { chan[c.chan].duty=ins->c64.duty; rWrite(c.chan*7+2,chan[c.chan].duty&0xff); rWrite(c.chan*7+3,chan[c.chan].duty>>8); @@ -279,7 +279,7 @@ int DivPlatformC64::dispatch(DivCommand c) { case DIV_CMD_VOLUME: if (chan[c.chan].vol!=c.value) { chan[c.chan].vol=c.value; - if (!chan[c.chan].std.hasVol) { + if (!chan[c.chan].std.vol.has) { chan[c.chan].outVol=c.value; vol=chan[c.chan].outVol; } else { @@ -333,7 +333,7 @@ int DivPlatformC64::dispatch(DivCommand c) { rWrite(c.chan*7+4,(isMuted[c.chan]?8:(chan[c.chan].wave<<4))|(chan[c.chan].ring<<2)|(chan[c.chan].sync<<1)|chan[c.chan].active); break; case DIV_CMD_LEGATO: - chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value+((chan[c.chan].std.willArp && !chan[c.chan].std.arpMode)?(chan[c.chan].std.arp):(0))); + chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0))); chan[c.chan].freqChanged=true; chan[c.chan].note=c.value; break; diff --git a/src/engine/platform/fds.cpp b/src/engine/platform/fds.cpp index cd70dfacd..9960a0d74 100644 --- a/src/engine/platform/fds.cpp +++ b/src/engine/platform/fds.cpp @@ -100,40 +100,40 @@ void DivPlatformFDS::updateWave() { void DivPlatformFDS::tick() { for (int i=0; i<1; i++) { chan[i].std.next(); - if (chan[i].std.hadVol) { + if (chan[i].std.vol.had) { // ok, why are the volumes like that? - chan[i].outVol=MIN(32,chan[i].std.vol)-(32-MIN(32,chan[i].vol)); + chan[i].outVol=MIN(32,chan[i].std.vol.val)-(32-MIN(32,chan[i].vol)); if (chan[i].outVol<0) chan[i].outVol=0; rWrite(0x4080,0x80|chan[i].outVol); } - if (chan[i].std.hadArp) { + if (chan[i].std.arp.had) { if (i==3) { // noise - if (chan[i].std.arpMode) { - chan[i].baseFreq=chan[i].std.arp; + if (chan[i].std.arp.mode) { + chan[i].baseFreq=chan[i].std.arp.val; } else { - chan[i].baseFreq=chan[i].note+chan[i].std.arp; + chan[i].baseFreq=chan[i].note+chan[i].std.arp.val; } if (chan[i].baseFreq>255) chan[i].baseFreq=255; if (chan[i].baseFreq<0) chan[i].baseFreq=0; } else { if (!chan[i].inPorta) { - if (chan[i].std.arpMode) { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp); + if (chan[i].std.arp.mode) { + chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val); } else { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+chan[i].std.arp); + chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+chan[i].std.arp.val); } } } chan[i].freqChanged=true; } else { - if (chan[i].std.arpMode && chan[i].std.finishedArp) { + if (chan[i].std.arp.mode && chan[i].std.arp.finished) { chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note); chan[i].freqChanged=true; } } /* - if (chan[i].std.hadDuty) { - chan[i].duty=chan[i].std.duty; + if (chan[i].std.duty.had) { + chan[i].duty=chan[i].std.duty.val; if (i==3) { if (parent->song.properNoiseLayout) { chan[i].duty&=1; @@ -148,9 +148,9 @@ void DivPlatformFDS::tick() { chan[i].freqChanged=true; } }*/ - if (chan[i].std.hadWave) { - if (chan[i].wave!=chan[i].std.wave || ws.activeChanged()) { - chan[i].wave=chan[i].std.wave; + if (chan[i].std.wave.had) { + if (chan[i].wave!=chan[i].std.wave.val || ws.activeChanged()) { + chan[i].wave=chan[i].std.wave.val; ws.changeWave1(chan[i].wave); //if (!chan[i].keyOff) chan[i].keyOn=true; } @@ -161,18 +161,18 @@ void DivPlatformFDS::tick() { if (!chan[i].keyOff) chan[i].keyOn=true; } } - if (chan[i].std.hadEx1) { // mod depth - chan[i].modOn=chan[i].std.ex1; - chan[i].modDepth=chan[i].std.ex1; + if (chan[i].std.ex1.had) { // mod depth + chan[i].modOn=chan[i].std.ex1.val; + chan[i].modDepth=chan[i].std.ex1.val; rWrite(0x4084,(chan[i].modOn<<7)|0x40|chan[i].modDepth); } - if (chan[i].std.hadEx2) { // mod speed - chan[i].modFreq=chan[i].std.ex2; + if (chan[i].std.ex2.had) { // mod speed + chan[i].modFreq=chan[i].std.ex2.val; rWrite(0x4086,chan[i].modFreq&0xff); rWrite(0x4087,chan[i].modFreq>>8); } - if (chan[i].std.hadEx3) { // mod position - chan[i].modPos=chan[i].std.ex3; + if (chan[i].std.ex3.had) { // mod position + chan[i].modPos=chan[i].std.ex3.val; rWrite(0x4087,0x80|chan[i].modFreq>>8); rWrite(0x4085,chan[i].modPos); rWrite(0x4087,chan[i].modFreq>>8); @@ -276,7 +276,7 @@ int DivPlatformFDS::dispatch(DivCommand c) { case DIV_CMD_VOLUME: if (chan[c.chan].vol!=c.value) { chan[c.chan].vol=c.value; - if (!chan[c.chan].std.hasVol) { + if (!chan[c.chan].std.vol.has) { chan[c.chan].outVol=c.value; } rWrite(0x4080,0x80|chan[c.chan].vol); @@ -359,7 +359,7 @@ int DivPlatformFDS::dispatch(DivCommand c) { } case DIV_CMD_LEGATO: if (c.chan==3) break; - chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value+((chan[c.chan].std.willArp && !chan[c.chan].std.arpMode)?(chan[c.chan].std.arp):(0))); + chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0))); chan[c.chan].freqChanged=true; chan[c.chan].note=c.value; break; diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp index 263e8e2ba..13eac1f32 100644 --- a/src/engine/platform/gb.cpp +++ b/src/engine/platform/gb.cpp @@ -149,45 +149,45 @@ static unsigned char noiseTable[256]={ void DivPlatformGB::tick() { for (int i=0; i<4; i++) { chan[i].std.next(); - if (chan[i].std.hadArp) { + if (chan[i].std.arp.had) { if (i==3) { // noise - if (chan[i].std.arpMode) { - chan[i].baseFreq=chan[i].std.arp+24; + if (chan[i].std.arp.mode) { + chan[i].baseFreq=chan[i].std.arp.val+24; } else { - chan[i].baseFreq=chan[i].note+chan[i].std.arp; + chan[i].baseFreq=chan[i].note+chan[i].std.arp.val; } if (chan[i].baseFreq>255) chan[i].baseFreq=255; if (chan[i].baseFreq<0) chan[i].baseFreq=0; } else { if (!chan[i].inPorta) { - if (chan[i].std.arpMode) { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp+24); + if (chan[i].std.arp.mode) { + chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp.val+24); } else { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp); + chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp.val); } } } chan[i].freqChanged=true; } else { - if (chan[i].std.arpMode && chan[i].std.finishedArp) { + if (chan[i].std.arp.mode && chan[i].std.arp.finished) { chan[i].baseFreq=NOTE_PERIODIC(chan[i].note); chan[i].freqChanged=true; } } - if (chan[i].std.hadDuty) { - chan[i].duty=chan[i].std.duty; + if (chan[i].std.duty.had) { + chan[i].duty=chan[i].std.duty.val; DivInstrument* ins=parent->getIns(chan[i].ins); if (i!=2) { rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(ins->gb.soundLen&63))); } else { if (parent->song.waveDutyIsVol) { - rWrite(16+i*5+2,gbVolMap[(chan[i].std.duty&3)<<2]); + rWrite(16+i*5+2,gbVolMap[(chan[i].std.duty.val&3)<<2]); } } } - if (i==2 && chan[i].std.hadWave) { - if (chan[i].wave!=chan[i].std.wave || ws.activeChanged()) { - chan[i].wave=chan[i].std.wave; + if (i==2 && chan[i].std.wave.had) { + if (chan[i].wave!=chan[i].std.wave.val || ws.activeChanged()) { + chan[i].wave=chan[i].std.wave.val; ws.changeWave1(chan[i].wave); if (!chan[i].keyOff) chan[i].keyOn=true; } @@ -359,7 +359,7 @@ int DivPlatformGB::dispatch(DivCommand c) { } case DIV_CMD_LEGATO: if (c.chan==3) break; - chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.willArp && !chan[c.chan].std.arpMode)?(chan[c.chan].std.arp):(0))); + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0))); chan[c.chan].freqChanged=true; chan[c.chan].note=c.value; break; diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index 0aff2b43a..c1c1648e1 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -225,8 +225,8 @@ void DivPlatformGenesis::tick() { if (i==2 && extMode) continue; chan[i].std.next(); - if (chan[i].std.hadVol) { - chan[i].outVol=(chan[i].vol*MIN(127,chan[i].std.vol))/127; + if (chan[i].std.vol.had) { + chan[i].outVol=(chan[i].vol*MIN(127,chan[i].std.vol.val))/127; for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; @@ -242,24 +242,24 @@ void DivPlatformGenesis::tick() { } } - if (chan[i].std.hadArp) { + if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - if (chan[i].std.arpMode) { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp); + if (chan[i].std.arp.mode) { + chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val); } else { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+(signed char)chan[i].std.arp); + chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+(signed char)chan[i].std.arp.val); } } chan[i].freqChanged=true; } else { - if (chan[i].std.arpMode && chan[i].std.finishedArp) { + if (chan[i].std.arp.mode && chan[i].std.arp.finished) { chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note); chan[i].freqChanged=true; } } - if (chan[i].std.hadAlg) { - chan[i].state.alg=chan[i].std.alg; + if (chan[i].std.alg.had) { + chan[i].state.alg=chan[i].std.alg.val; rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); if (!parent->song.algMacroBehavior) for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; @@ -275,48 +275,48 @@ void DivPlatformGenesis::tick() { } } } - if (chan[i].std.hadFb) { - chan[i].state.fb=chan[i].std.fb; + if (chan[i].std.fb.had) { + chan[i].state.fb=chan[i].std.fb.val; rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); } - if (chan[i].std.hadFms) { - chan[i].state.fms=chan[i].std.fms; + if (chan[i].std.fms.had) { + chan[i].state.fms=chan[i].std.fms.val; rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); } - if (chan[i].std.hadAms) { - chan[i].state.ams=chan[i].std.ams; + if (chan[i].std.ams.had) { + chan[i].state.ams=chan[i].std.ams.val; rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); } for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; DivMacroInt::IntOp& m=chan[i].std.op[j]; - if (m.hadAm) { - op.am=m.am; + if (m.am.had) { + op.am=m.am.val; rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); } - if (m.hadAr) { - op.ar=m.ar; + if (m.ar.had) { + op.ar=m.ar.val; rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); } - if (m.hadDr) { - op.dr=m.dr; + if (m.dr.had) { + op.dr=m.dr.val; rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); } - if (m.hadMult) { - op.mult=m.mult; + if (m.mult.had) { + op.mult=m.mult.val; rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); } - if (m.hadRr) { - op.rr=m.rr; + if (m.rr.had) { + op.rr=m.rr.val; rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); } - if (m.hadSl) { - op.sl=m.sl; + if (m.sl.had) { + op.sl=m.sl.val; rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); } - if (m.hadTl) { - op.tl=127-m.tl; + if (m.tl.had) { + op.tl=127-m.tl.val; if (isMuted[i]) { rWrite(baseAddr+ADDR_TL,127); } else { @@ -327,20 +327,20 @@ void DivPlatformGenesis::tick() { } } } - if (m.hadRs) { - op.rs=m.rs; + if (m.rs.had) { + op.rs=m.rs.val; rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); } - if (m.hadDt) { - op.dt=m.dt; + if (m.dt.had) { + op.dt=m.dt.val; rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); } - if (m.hadD2r) { - op.d2r=m.d2r; + if (m.d2r.had) { + op.d2r=m.d2r.val; rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); } - if (m.hadSsg) { - op.ssgEnv=m.ssg; + if (m.ssg.had) { + op.ssgEnv=m.ssg.val; rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); } } @@ -505,7 +505,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) { } chan[c.chan].std.init(ins); - if (!chan[c.chan].std.willVol) { + if (!chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; } @@ -578,7 +578,7 @@ int DivPlatformGenesis::dispatch(DivCommand c) { break; case DIV_CMD_VOLUME: { chan[c.chan].vol=c.value; - if (!chan[c.chan].std.hasVol) { + if (!chan[c.chan].std.vol.has) { chan[c.chan].outVol=c.value; } for (int i=0; i<4; i++) { diff --git a/src/engine/platform/lynx.cpp b/src/engine/platform/lynx.cpp index dc0df8a65..70e7bc413 100644 --- a/src/engine/platform/lynx.cpp +++ b/src/engine/platform/lynx.cpp @@ -148,23 +148,23 @@ void DivPlatformLynx::acquire(short* bufL, short* bufR, size_t start, size_t len void DivPlatformLynx::tick() { for (int i=0; i<4; i++) { chan[i].std.next(); - if (chan[i].std.hadVol) { - chan[i].outVol=((chan[i].vol&127)*MIN(127,chan[i].std.vol))>>7; + if (chan[i].std.vol.had) { + chan[i].outVol=((chan[i].vol&127)*MIN(127,chan[i].std.vol.val))>>7; WRITE_VOLUME(i,(isMuted[i]?0:(chan[i].outVol&127))); } - if (chan[i].std.hadArp) { + if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - if (chan[i].std.arpMode) { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp); - chan[i].actualNote=chan[i].std.arp; + if (chan[i].std.arp.mode) { + chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp.val); + chan[i].actualNote=chan[i].std.arp.val; } else { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp); - chan[i].actualNote=chan[i].note+chan[i].std.arp; + chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp.val); + chan[i].actualNote=chan[i].note+chan[i].std.arp.val; } chan[i].freqChanged=true; } } else { - if (chan[i].std.arpMode && chan[i].std.finishedArp) { + if (chan[i].std.arp.mode && chan[i].std.arp.finished) { chan[i].baseFreq=NOTE_PERIODIC(chan[i].note); chan[i].actualNote=chan[i].note; chan[i].freqChanged=true; @@ -178,15 +178,15 @@ void DivPlatformLynx::tick() { chan[i].lfsr=-1; } chan[i].fd=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true); - if (chan[i].std.hadDuty) { - chan[i].duty=chan[i].std.duty; + if (chan[i].std.duty.had) { + chan[i].duty=chan[i].std.duty.val; WRITE_FEEDBACK(i, chan[i].duty.feedback); } WRITE_CONTROL(i, (chan[i].fd.clockDivider|0x18|chan[i].duty.int_feedback7)); WRITE_BACKUP( i, chan[i].fd.backup ); } - else if (chan[i].std.hadDuty) { - chan[i].duty = chan[i].std.duty; + else if (chan[i].std.duty.had) { + chan[i].duty = chan[i].std.duty.val; WRITE_FEEDBACK(i, chan[i].duty.feedback); WRITE_CONTROL(i, (chan[i].fd.clockDivider|0x18|chan[i].duty.int_feedback7)); } @@ -228,7 +228,7 @@ int DivPlatformLynx::dispatch(DivCommand c) { case DIV_CMD_VOLUME: if (chan[c.chan].vol!=c.value) { chan[c.chan].vol=c.value; - if (!chan[c.chan].std.hasVol) { + if (!chan[c.chan].std.vol.has) { chan[c.chan].outVol=c.value; } if (chan[c.chan].active) WRITE_VOLUME(c.chan,(isMuted[c.chan]?0:(chan[c.chan].vol&127))); @@ -239,7 +239,7 @@ int DivPlatformLynx::dispatch(DivCommand c) { WRITE_ATTEN(c.chan,chan[c.chan].pan); break; case DIV_CMD_GET_VOLUME: - if (chan[c.chan].std.hasVol) { + if (chan[c.chan].std.vol.has) { return chan[c.chan].vol; } return chan[c.chan].outVol; @@ -272,7 +272,7 @@ int DivPlatformLynx::dispatch(DivCommand c) { break; } case DIV_CMD_LEGATO: - chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.willArp && !chan[c.chan].std.arpMode)?(chan[c.chan].std.arp):(0))); + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0))); chan[c.chan].freqChanged=true; chan[c.chan].note=c.value; chan[c.chan].actualNote=c.value; diff --git a/src/engine/platform/mmc5.cpp b/src/engine/platform/mmc5.cpp index 3ccf95499..0f803ee20 100644 --- a/src/engine/platform/mmc5.cpp +++ b/src/engine/platform/mmc5.cpp @@ -99,29 +99,29 @@ void DivPlatformMMC5::acquire(short* bufL, short* bufR, size_t start, size_t len void DivPlatformMMC5::tick() { for (int i=0; i<2; i++) { chan[i].std.next(); - if (chan[i].std.hadVol) { + if (chan[i].std.vol.had) { // ok, why are the volumes like that? - chan[i].outVol=MIN(15,chan[i].std.vol)-(15-(chan[i].vol&15)); + chan[i].outVol=MIN(15,chan[i].std.vol.val)-(15-(chan[i].vol&15)); if (chan[i].outVol<0) chan[i].outVol=0; rWrite(0x5000+i*4,0x30|chan[i].outVol|((chan[i].duty&3)<<6)); } - if (chan[i].std.hadArp) { + if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - if (chan[i].std.arpMode) { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp); + if (chan[i].std.arp.mode) { + chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp.val); } else { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp); + chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp.val); } } chan[i].freqChanged=true; } else { - if (chan[i].std.arpMode && chan[i].std.finishedArp) { + if (chan[i].std.arp.mode && chan[i].std.arp.finished) { chan[i].baseFreq=NOTE_PERIODIC(chan[i].note); chan[i].freqChanged=true; } } - if (chan[i].std.hadDuty) { - chan[i].duty=chan[i].std.duty; + if (chan[i].std.duty.had) { + chan[i].duty=chan[i].std.duty.val; rWrite(0x5000+i*4,0x30|chan[i].outVol|((chan[i].duty&3)<<6)); } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { @@ -243,7 +243,7 @@ int DivPlatformMMC5::dispatch(DivCommand c) { case DIV_CMD_VOLUME: if (chan[c.chan].vol!=c.value) { chan[c.chan].vol=c.value; - if (!chan[c.chan].std.hasVol) { + if (!chan[c.chan].std.vol.has) { chan[c.chan].outVol=c.value; } if (chan[c.chan].active) { @@ -291,7 +291,7 @@ int DivPlatformMMC5::dispatch(DivCommand c) { } break; case DIV_CMD_LEGATO: - chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.willArp && !chan[c.chan].std.arpMode)?(chan[c.chan].std.arp):(0))); + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0))); chan[c.chan].freqChanged=true; chan[c.chan].note=c.value; break; diff --git a/src/engine/platform/n163.cpp b/src/engine/platform/n163.cpp index 2800fad11..6248cb562 100644 --- a/src/engine/platform/n163.cpp +++ b/src/engine/platform/n163.cpp @@ -203,8 +203,8 @@ void DivPlatformN163::updateWaveCh(int ch) { void DivPlatformN163::tick() { for (int i=0; i<=chanMax; i++) { chan[i].std.next(); - if (chan[i].std.hadVol) { - chan[i].outVol=(MIN(15,chan[i].std.vol)*(chan[i].vol&15))/15; + if (chan[i].std.vol.had) { + chan[i].outVol=(MIN(15,chan[i].std.vol.val)*(chan[i].vol&15))/15; if (chan[i].outVol<0) chan[i].outVol=0; if (chan[i].outVol>15) chan[i].outVol=15; if (chan[i].resVol!=chan[i].outVol) { @@ -214,87 +214,87 @@ void DivPlatformN163::tick() { } } } - if (chan[i].std.hadArp) { + if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - if (chan[i].std.arpMode) { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp); + if (chan[i].std.arp.mode) { + chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val); } else { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+(signed char)chan[i].std.arp); + chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+(signed char)chan[i].std.arp.val); } } chan[i].freqChanged=true; } else { - if (chan[i].std.arpMode && chan[i].std.finishedArp) { + if (chan[i].std.arp.mode && chan[i].std.arp.finished) { chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note); chan[i].freqChanged=true; } } - if (chan[i].std.hadDuty) { - if (chan[i].wavePos!=chan[i].std.duty) { - chan[i].wavePos=chan[i].std.duty; + if (chan[i].std.duty.had) { + if (chan[i].wavePos!=chan[i].std.duty.val) { + chan[i].wavePos=chan[i].std.duty.val; if (chan[i].waveMode&0x2) { chan[i].waveUpdated=true; } chan[i].waveChanged=true; } } - if (chan[i].std.hadWave) { - if (chan[i].wave!=chan[i].std.wave) { - chan[i].wave=chan[i].std.wave; + if (chan[i].std.wave.had) { + if (chan[i].wave!=chan[i].std.wave.val) { + chan[i].wave=chan[i].std.wave.val; if (chan[i].waveMode&0x2) { chan[i].waveUpdated=true; } } } - if (chan[i].std.hadEx1) { - if (chan[i].waveLen!=(chan[i].std.ex1&0xfc)) { - chan[i].waveLen=chan[i].std.ex1&0xfc; + if (chan[i].std.ex1.had) { + if (chan[i].waveLen!=(chan[i].std.ex1.val&0xfc)) { + chan[i].waveLen=chan[i].std.ex1.val&0xfc; if (chan[i].waveMode&0x2) { chan[i].waveUpdated=true; } chan[i].freqChanged=true; } } - if (chan[i].std.hadEx2) { - if ((chan[i].waveMode&0x2)!=(chan[i].std.ex2&0x2)) { // update when every waveform changed - chan[i].waveMode=(chan[i].waveMode&~0x2)|(chan[i].std.ex2&0x2); + if (chan[i].std.ex2.had) { + if ((chan[i].waveMode&0x2)!=(chan[i].std.ex2.val&0x2)) { // update when every waveform changed + chan[i].waveMode=(chan[i].waveMode&~0x2)|(chan[i].std.ex2.val&0x2); if (chan[i].waveMode&0x2) { chan[i].waveUpdated=true; chan[i].waveChanged=true; } } - if ((chan[i].waveMode&0x1)!=(chan[i].std.ex2&0x1)) { // update waveform now - chan[i].waveMode=(chan[i].waveMode&~0x1)|(chan[i].std.ex2&0x1); + if ((chan[i].waveMode&0x1)!=(chan[i].std.ex2.val&0x1)) { // update waveform now + chan[i].waveMode=(chan[i].waveMode&~0x1)|(chan[i].std.ex2.val&0x1); if (chan[i].waveMode&0x1) { // rising edge chan[i].waveUpdated=true; chan[i].waveChanged=true; } } } - if (chan[i].std.hadEx3) { - if (chan[i].loadWave!=chan[i].std.ex3) { - chan[i].loadWave=chan[i].std.ex3; + if (chan[i].std.ex3.had) { + if (chan[i].loadWave!=chan[i].std.ex3.val) { + chan[i].loadWave=chan[i].std.ex3.val; if (chan[i].loadMode&0x2) { updateWave(chan[i].loadWave,chan[i].loadPos,chan[i].loadLen&0xfc); } } } - if (chan[i].std.hadAlg) { - if (chan[i].loadPos!=chan[i].std.alg) { - chan[i].loadPos=chan[i].std.alg; + if (chan[i].std.alg.had) { + if (chan[i].loadPos!=chan[i].std.alg.val) { + chan[i].loadPos=chan[i].std.alg.val; } } - if (chan[i].std.hadFb) { - if (chan[i].loadLen!=(chan[i].std.fb&0xfc)) { - chan[i].loadLen=chan[i].std.fb&0xfc; + if (chan[i].std.fb.had) { + if (chan[i].loadLen!=(chan[i].std.fb.val&0xfc)) { + chan[i].loadLen=chan[i].std.fb.val&0xfc; } } - if (chan[i].std.hadFms) { - if ((chan[i].loadMode&0x2)!=(chan[i].std.fms&0x2)) { // load when every waveform changes - chan[i].loadMode=(chan[i].loadMode&~0x2)|(chan[i].std.fms&0x2); + if (chan[i].std.fms.had) { + if ((chan[i].loadMode&0x2)!=(chan[i].std.fms.val&0x2)) { // load when every waveform changes + chan[i].loadMode=(chan[i].loadMode&~0x2)|(chan[i].std.fms.val&0x2); } - if ((chan[i].loadMode&0x1)!=(chan[i].std.fms&0x1)) { // load now - chan[i].loadMode=(chan[i].loadMode&~0x1)|(chan[i].std.fms&0x1); + if ((chan[i].loadMode&0x1)!=(chan[i].std.fms.val&0x1)) { // load now + chan[i].loadMode=(chan[i].loadMode&~0x1)|(chan[i].std.fms.val&0x1); if (chan[i].loadMode&0x1) { // rising edge updateWave(chan[i].loadWave,chan[i].loadPos,chan[i].loadLen&0xfc); } @@ -400,7 +400,7 @@ int DivPlatformN163::dispatch(DivCommand c) { case DIV_CMD_VOLUME: if (chan[c.chan].vol!=c.value) { chan[c.chan].vol=c.value; - if (!chan[c.chan].std.hasVol) { + if (!chan[c.chan].std.vol.has) { chan[c.chan].outVol=c.value; chan[c.chan].resVol=chan[c.chan].outVol; } else { @@ -513,7 +513,7 @@ int DivPlatformN163::dispatch(DivCommand c) { } break; case DIV_CMD_LEGATO: - chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value+((chan[c.chan].std.willArp && !chan[c.chan].std.arpMode)?(chan[c.chan].std.arp):(0))); + chan[c.chan].baseFreq=NOTE_FREQUENCY(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0))); chan[c.chan].freqChanged=true; chan[c.chan].note=c.value; break; diff --git a/src/engine/platform/nes.cpp b/src/engine/platform/nes.cpp index f61ed4fb6..5380b0b42 100644 --- a/src/engine/platform/nes.cpp +++ b/src/engine/platform/nes.cpp @@ -136,9 +136,9 @@ static unsigned char noiseTable[253]={ void DivPlatformNES::tick() { for (int i=0; i<4; i++) { chan[i].std.next(); - if (chan[i].std.hadVol) { + if (chan[i].std.vol.had) { // ok, why are the volumes like that? - chan[i].outVol=MIN(15,chan[i].std.vol)-(15-(chan[i].vol&15)); + chan[i].outVol=MIN(15,chan[i].std.vol.val)-(15-(chan[i].vol&15)); if (chan[i].outVol<0) chan[i].outVol=0; if (i==2) { // triangle rWrite(0x4000+i*4,(chan[i].outVol==0)?0:255); @@ -147,33 +147,33 @@ void DivPlatformNES::tick() { rWrite(0x4000+i*4,0x30|chan[i].outVol|((chan[i].duty&3)<<6)); } } - if (chan[i].std.hadArp) { + if (chan[i].std.arp.had) { if (i==3) { // noise - if (chan[i].std.arpMode) { - chan[i].baseFreq=chan[i].std.arp; + if (chan[i].std.arp.mode) { + chan[i].baseFreq=chan[i].std.arp.val; } else { - chan[i].baseFreq=chan[i].note+chan[i].std.arp; + chan[i].baseFreq=chan[i].note+chan[i].std.arp.val; } if (chan[i].baseFreq>255) chan[i].baseFreq=255; if (chan[i].baseFreq<0) chan[i].baseFreq=0; } else { if (!chan[i].inPorta) { - if (chan[i].std.arpMode) { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp); + if (chan[i].std.arp.mode) { + chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp.val); } else { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp); + chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp.val); } } } chan[i].freqChanged=true; } else { - if (chan[i].std.arpMode && chan[i].std.finishedArp) { + if (chan[i].std.arp.mode && chan[i].std.arp.finished) { chan[i].baseFreq=NOTE_PERIODIC(chan[i].note); chan[i].freqChanged=true; } } - if (chan[i].std.hadDuty) { - chan[i].duty=chan[i].std.duty; + if (chan[i].std.duty.had) { + chan[i].duty=chan[i].std.duty.val; if (i==3) { if (parent->song.properNoiseLayout) { chan[i].duty&=1; @@ -337,7 +337,7 @@ int DivPlatformNES::dispatch(DivCommand c) { case DIV_CMD_VOLUME: if (chan[c.chan].vol!=c.value) { chan[c.chan].vol=c.value; - if (!chan[c.chan].std.hasVol) { + if (!chan[c.chan].std.vol.has) { chan[c.chan].outVol=c.value; } if (chan[c.chan].active) { @@ -406,7 +406,7 @@ int DivPlatformNES::dispatch(DivCommand c) { break; case DIV_CMD_LEGATO: if (c.chan==3) break; - chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.willArp && !chan[c.chan].std.arpMode)?(chan[c.chan].std.arp):(0))); + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0))); chan[c.chan].freqChanged=true; chan[c.chan].note=c.value; break; diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index fcba9f80c..6b4db8c66 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -233,8 +233,8 @@ void DivPlatformOPL::tick() { int ops=(slots[3][i]!=255 && chan[i].state.ops==4 && oplType==3)?4:2; chan[i].std.next(); - if (chan[i].std.hadVol) { - chan[i].outVol=(chan[i].vol*MIN(63,chan[i].std.vol))/63; + if (chan[i].std.vol.had) { + chan[i].outVol=(chan[i].vol*MIN(63,chan[i].std.vol.val))/63; for (int j=0; j1) { - if (m.hadWs) { - op.ws=m.ws; + if (m.ws.had) { + op.ws=m.ws.val; rWrite(baseAddr+ADDR_WS,op.ws&((oplType==3)?7:3)); } } - if (m.hadTl) { - op.tl=63-m.tl; + if (m.tl.had) { + op.tl=63-m.tl.val; } - if (m.hadKsl) { - op.ksl=m.ksl; + if (m.ksl.had) { + op.ksl=m.ksl.val; } - if (m.hadTl || m.hadKsl) { + if (m.tl.had || m.ksl.had) { if (isMuted[i]) { rWrite(baseAddr+ADDR_KSL_TL,63|(op.ksl<<6)); } else { @@ -528,7 +528,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { } chan[c.chan].std.init(ins); - if (!chan[c.chan].std.willVol) { + if (!chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; } if (chan[c.chan].insChanged) { @@ -620,7 +620,7 @@ int DivPlatformOPL::dispatch(DivCommand c) { if (c.value>63) c.value=63; } chan[c.chan].vol=c.value; - if (!chan[c.chan].std.hasVol) { + if (!chan[c.chan].std.vol.has) { chan[c.chan].outVol=c.value; } int ops=(slots[3][c.chan]!=255 && chan[c.chan].state.ops==4 && oplType==3)?4:2; diff --git a/src/engine/platform/opll.cpp b/src/engine/platform/opll.cpp index 55a1fa80c..2c23639b2 100644 --- a/src/engine/platform/opll.cpp +++ b/src/engine/platform/opll.cpp @@ -115,51 +115,51 @@ void DivPlatformOPLL::tick() { for (int i=0; i<11; i++) { chan[i].std.next(); - if (chan[i].std.hadVol) { - chan[i].outVol=(chan[i].vol*MIN(15,chan[i].std.vol))/15; + if (chan[i].std.vol.had) { + chan[i].outVol=(chan[i].vol*MIN(15,chan[i].std.vol.val))/15; if (i<9) { rWrite(0x30+i,((15-(chan[i].outVol*(15-chan[i].state.op[1].tl))/15)&15)|(chan[i].state.opllPreset<<4)); } } - if (chan[i].std.hadArp) { + if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - if (chan[i].std.arpMode) { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp); + if (chan[i].std.arp.mode) { + chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val); } else { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+(signed char)chan[i].std.arp); + chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+(signed char)chan[i].std.arp.val); } } chan[i].freqChanged=true; } else { - if (chan[i].std.arpMode && chan[i].std.finishedArp) { + if (chan[i].std.arp.mode && chan[i].std.arp.finished) { chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note); chan[i].freqChanged=true; } } - if (chan[i].std.hadWave && chan[i].state.opllPreset!=16) { - chan[i].state.opllPreset=chan[i].std.wave; + if (chan[i].std.wave.had && chan[i].state.opllPreset!=16) { + chan[i].state.opllPreset=chan[i].std.wave.val; if (i<9) { rWrite(0x30+i,((15-(chan[i].outVol*(15-chan[i].state.op[1].tl))/15)&15)|(chan[i].state.opllPreset<<4)); } } if (chan[i].state.opllPreset==0) { - if (chan[i].std.hadAlg) { // SUS - chan[i].state.alg=chan[i].std.alg; + if (chan[i].std.alg.had) { // SUS + chan[i].state.alg=chan[i].std.alg.val; chan[i].freqChanged=true; } - if (chan[i].std.hadFb) { - chan[i].state.fb=chan[i].std.fb; + if (chan[i].std.fb.had) { + chan[i].state.fb=chan[i].std.fb.val; rWrite(0x03,(chan[i].state.op[1].ksl<<6)|((chan[i].state.fms&1)<<4)|((chan[i].state.ams&1)<<3)|chan[i].state.fb); } - if (chan[i].std.hadFms) { - chan[i].state.fms=chan[i].std.fms; + if (chan[i].std.fms.had) { + chan[i].state.fms=chan[i].std.fms.val; rWrite(0x03,(chan[i].state.op[1].ksl<<6)|((chan[i].state.fms&1)<<4)|((chan[i].state.ams&1)<<3)|chan[i].state.fb); } - if (chan[i].std.hadAms) { - chan[i].state.ams=chan[i].std.ams; + if (chan[i].std.ams.had) { + chan[i].state.ams=chan[i].std.ams.val; rWrite(0x03,(chan[i].state.op[1].ksl<<6)|((chan[i].state.fms&1)<<4)|((chan[i].state.ams&1)<<3)|chan[i].state.fb); } @@ -167,32 +167,32 @@ void DivPlatformOPLL::tick() { DivInstrumentFM::Operator& op=chan[i].state.op[j]; DivMacroInt::IntOp& m=chan[i].std.op[j]; - if (m.hadAm) { - op.am=m.am; + if (m.am.had) { + op.am=m.am.val; rWrite(0x00+j,(op.am<<7)|(op.vib<<6)|((op.ssgEnv&8)<<2)|(op.ksr<<4)|(op.mult)); } - if (m.hadAr) { - op.ar=m.ar; + if (m.ar.had) { + op.ar=m.ar.val; rWrite(0x04+j,(op.ar<<4)|(op.dr)); } - if (m.hadDr) { - op.dr=m.dr; + if (m.dr.had) { + op.dr=m.dr.val; rWrite(0x04+j,(op.ar<<4)|(op.dr)); } - if (m.hadMult) { - op.mult=m.mult; + if (m.mult.had) { + op.mult=m.mult.val; rWrite(0x00+j,(op.am<<7)|(op.vib<<6)|((op.ssgEnv&8)<<2)|(op.ksr<<4)|(op.mult)); } - if (m.hadRr) { - op.rr=m.rr; + if (m.rr.had) { + op.rr=m.rr.val; rWrite(0x06+j,(op.sl<<4)|(op.rr)); } - if (m.hadSl) { - op.sl=m.sl; + if (m.sl.had) { + op.sl=m.sl.val; rWrite(0x06+j,(op.sl<<4)|(op.rr)); } - if (m.hadTl) { - op.tl=((j==1)?15:63)-m.tl; + if (m.tl.had) { + op.tl=((j==1)?15:63)-m.tl.val; if (j==1) { if (i<9) { rWrite(0x30+i,((15-(chan[i].outVol*(15-chan[i].state.op[1].tl))/15)&15)|(chan[i].state.opllPreset<<4)); @@ -202,24 +202,24 @@ void DivPlatformOPLL::tick() { } } - if (m.hadEgt) { - op.ssgEnv=(m.egt&1)?8:0; + if (m.egt.had) { + op.ssgEnv=(m.egt.val&1)?8:0; rWrite(0x00+j,(op.am<<7)|(op.vib<<6)|((op.ssgEnv&8)<<2)|(op.ksr<<4)|(op.mult)); } - if (m.hadKsl) { - op.ksl=m.ksl; + if (m.ksl.had) { + op.ksl=m.ksl.val; if (j==1) { rWrite(0x02,(chan[i].state.op[0].ksl<<6)|(chan[i].state.op[0].tl&63)); } else { rWrite(0x03,(chan[i].state.op[1].ksl<<6)|((chan[i].state.fms&1)<<4)|((chan[i].state.ams&1)<<3)|chan[i].state.fb); } } - if (m.hadKsr) { - op.ksr=m.ksr; + if (m.ksr.had) { + op.ksr=m.ksr.val; rWrite(0x00+j,(op.am<<7)|(op.vib<<6)|((op.ssgEnv&8)<<2)|(op.ksr<<4)|(op.mult)); } - if (m.hadVib) { - op.vib=m.vib; + if (m.vib.had) { + op.vib=m.vib.val; rWrite(0x00+j,(op.am<<7)|(op.vib<<6)|((op.ssgEnv&8)<<2)|(op.ksr<<4)|(op.mult)); } } @@ -361,7 +361,7 @@ int DivPlatformOPLL::dispatch(DivCommand c) { } chan[c.chan].std.init(ins); - if (!chan[c.chan].std.willVol) { + if (!chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; } @@ -490,7 +490,7 @@ int DivPlatformOPLL::dispatch(DivCommand c) { case DIV_CMD_VOLUME: { if (c.chan>=9 && !properDrums) return 0; chan[c.chan].vol=c.value; - if (!chan[c.chan].std.hasVol) { + if (!chan[c.chan].std.vol.has) { chan[c.chan].outVol=c.value; } if (c.chan>=6 && properDrums) { diff --git a/src/engine/platform/pce.cpp b/src/engine/platform/pce.cpp index aa78aa625..0789c804d 100644 --- a/src/engine/platform/pce.cpp +++ b/src/engine/platform/pce.cpp @@ -149,39 +149,39 @@ static unsigned char noiseFreq[12]={ void DivPlatformPCE::tick() { for (int i=0; i<6; i++) { chan[i].std.next(); - if (chan[i].std.hadVol) { - chan[i].outVol=((chan[i].vol&31)*MIN(31,chan[i].std.vol))>>5; + if (chan[i].std.vol.had) { + chan[i].outVol=((chan[i].vol&31)*MIN(31,chan[i].std.vol.val))>>5; if (chan[i].furnaceDac) { // ignore for now } else { chWrite(i,0x04,0x80|chan[i].outVol); } } - if (chan[i].std.hadDuty && i>=4) { - chan[i].noise=chan[i].std.duty; + if (chan[i].std.duty.had && i>=4) { + chan[i].noise=chan[i].std.duty.val; chan[i].freqChanged=true; int noiseSeek=chan[i].note; if (noiseSeek<0) noiseSeek=0; chWrite(i,0x07,chan[i].noise?(0x80|(parent->song.properNoiseLayout?(noiseSeek&31):noiseFreq[noiseSeek%12])):0); } - if (chan[i].std.hadArp) { + if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - if (chan[i].std.arpMode) { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp); + if (chan[i].std.arp.mode) { + chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp.val); // noise - int noiseSeek=chan[i].std.arp; + int noiseSeek=chan[i].std.arp.val; if (noiseSeek<0) noiseSeek=0; chWrite(i,0x07,chan[i].noise?(0x80|(parent->song.properNoiseLayout?(noiseSeek&31):noiseFreq[noiseSeek%12])):0); } else { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp); - int noiseSeek=chan[i].note+chan[i].std.arp; + chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp.val); + int noiseSeek=chan[i].note+chan[i].std.arp.val; if (noiseSeek<0) noiseSeek=0; chWrite(i,0x07,chan[i].noise?(0x80|(parent->song.properNoiseLayout?(noiseSeek&31):noiseFreq[noiseSeek%12])):0); } } chan[i].freqChanged=true; } else { - if (chan[i].std.arpMode && chan[i].std.finishedArp) { + if (chan[i].std.arp.mode && chan[i].std.arp.finished) { chan[i].baseFreq=NOTE_PERIODIC(chan[i].note); int noiseSeek=chan[i].note; if (noiseSeek<0) noiseSeek=0; @@ -189,9 +189,9 @@ void DivPlatformPCE::tick() { chan[i].freqChanged=true; } } - if (chan[i].std.hadWave && !chan[i].pcm) { - if (chan[i].wave!=chan[i].std.wave || chan[i].ws.activeChanged()) { - chan[i].wave=chan[i].std.wave; + if (chan[i].std.wave.had && !chan[i].pcm) { + if (chan[i].wave!=chan[i].std.wave.val || chan[i].ws.activeChanged()) { + chan[i].wave=chan[i].std.wave.val; chan[i].ws.changeWave1(chan[i].wave); if (!chan[i].keyOff) chan[i].keyOn=true; } @@ -332,14 +332,14 @@ int DivPlatformPCE::dispatch(DivCommand c) { case DIV_CMD_VOLUME: if (chan[c.chan].vol!=c.value) { chan[c.chan].vol=c.value; - if (!chan[c.chan].std.hasVol) { + if (!chan[c.chan].std.vol.has) { chan[c.chan].outVol=c.value; if (chan[c.chan].active) chWrite(c.chan,0x04,0x80|chan[c.chan].outVol); } } break; case DIV_CMD_GET_VOLUME: - if (chan[c.chan].std.hasVol) { + if (chan[c.chan].std.vol.has) { return chan[c.chan].vol; } return chan[c.chan].outVol; @@ -409,7 +409,7 @@ int DivPlatformPCE::dispatch(DivCommand c) { break; } case DIV_CMD_LEGATO: - chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.willArp && !chan[c.chan].std.arpMode)?(chan[c.chan].std.arp):(0))); + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0))); chan[c.chan].freqChanged=true; chan[c.chan].note=c.value; break; diff --git a/src/engine/platform/pcspkr.cpp b/src/engine/platform/pcspkr.cpp index 5cfcdac3e..ee6ba7ef8 100644 --- a/src/engine/platform/pcspkr.cpp +++ b/src/engine/platform/pcspkr.cpp @@ -167,21 +167,21 @@ void DivPlatformPCSpeaker::acquire(short* bufL, short* bufR, size_t start, size_ void DivPlatformPCSpeaker::tick() { for (int i=0; i<1; i++) { chan[i].std.next(); - if (chan[i].std.hadVol) { - chan[i].outVol=(chan[i].vol && chan[i].std.vol); + if (chan[i].std.vol.had) { + chan[i].outVol=(chan[i].vol && chan[i].std.vol.val); on=chan[i].outVol; } - if (chan[i].std.hadArp) { + if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - if (chan[i].std.arpMode) { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp); + if (chan[i].std.arp.mode) { + chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp.val); } else { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp); + chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp.val); } } chan[i].freqChanged=true; } else { - if (chan[i].std.arpMode && chan[i].std.finishedArp) { + if (chan[i].std.arp.mode && chan[i].std.arp.finished) { chan[i].baseFreq=NOTE_PERIODIC(chan[i].note); chan[i].freqChanged=true; } @@ -233,7 +233,7 @@ int DivPlatformPCSpeaker::dispatch(DivCommand c) { case DIV_CMD_VOLUME: if (chan[c.chan].vol!=c.value) { chan[c.chan].vol=c.value; - if (!chan[c.chan].std.hasVol) { + if (!chan[c.chan].std.vol.has) { chan[c.chan].outVol=c.value; } if (chan[c.chan].active) { @@ -273,7 +273,7 @@ int DivPlatformPCSpeaker::dispatch(DivCommand c) { } case DIV_CMD_LEGATO: if (c.chan==3) break; - chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.willArp && !chan[c.chan].std.arpMode)?(chan[c.chan].std.arp):(0))); + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0))); chan[c.chan].freqChanged=true; chan[c.chan].note=c.value; break; diff --git a/src/engine/platform/pet.cpp b/src/engine/platform/pet.cpp index b89f95102..e46277e0a 100644 --- a/src/engine/platform/pet.cpp +++ b/src/engine/platform/pet.cpp @@ -87,28 +87,28 @@ void DivPlatformPET::writeOutVol() { void DivPlatformPET::tick() { chan.std.next(); - if (chan.std.hadVol) { - chan.outVol=chan.std.vol&chan.vol; + if (chan.std.vol.had) { + chan.outVol=chan.std.vol.val&chan.vol; writeOutVol(); } - if (chan.std.hadArp) { + if (chan.std.arp.had) { if (!chan.inPorta) { - if (chan.std.arpMode) { - chan.baseFreq=NOTE_PERIODIC(chan.std.arp); + if (chan.std.arp.mode) { + chan.baseFreq=NOTE_PERIODIC(chan.std.arp.val); } else { - chan.baseFreq=NOTE_PERIODIC(chan.note+chan.std.arp); + chan.baseFreq=NOTE_PERIODIC(chan.note+chan.std.arp.val); } } chan.freqChanged=true; } else { - if (chan.std.arpMode && chan.std.finishedArp) { + if (chan.std.arp.mode && chan.std.arp.finished) { chan.baseFreq=NOTE_PERIODIC(chan.note); chan.freqChanged=true; } } - if (chan.std.hadWave) { - if (chan.wave!=chan.std.wave) { - chan.wave=chan.std.wave; + if (chan.std.wave.had) { + if (chan.wave!=chan.std.wave.val) { + chan.wave=chan.std.wave.val; rWrite(10,chan.wave); } } @@ -118,7 +118,7 @@ void DivPlatformPET::tick() { if (chan.freq<2) chan.freq=2; rWrite(8,chan.freq-2); if (chan.keyOn) { - if (!chan.std.willVol) { + if (!chan.std.vol.will) { chan.outVol=chan.vol; writeOutVol(); } @@ -163,7 +163,7 @@ int DivPlatformPET::dispatch(DivCommand c) { case DIV_CMD_VOLUME: if (chan.vol!=c.value) { chan.vol=c.value; - if (!chan.std.hadVol) { + if (!chan.std.vol.had) { chan.outVol=chan.vol; writeOutVol(); } @@ -204,7 +204,7 @@ int DivPlatformPET::dispatch(DivCommand c) { break; } case DIV_CMD_LEGATO: - chan.baseFreq=NOTE_PERIODIC(c.value+((chan.std.willArp && !chan.std.arpMode)?(chan.std.arp):(0))); + chan.baseFreq=NOTE_PERIODIC(c.value+((chan.std.arp.will && !chan.std.arp.mode)?(chan.std.arp.val):(0))); chan.freqChanged=true; chan.note=c.value; break; diff --git a/src/engine/platform/qsound.cpp b/src/engine/platform/qsound.cpp index 12bf1c58b..a63ca9e50 100644 --- a/src/engine/platform/qsound.cpp +++ b/src/engine/platform/qsound.cpp @@ -277,8 +277,8 @@ void DivPlatformQSound::acquire(short* bufL, short* bufR, size_t start, size_t l void DivPlatformQSound::tick() { for (int i=0; i<16; i++) { chan[i].std.next(); - if (chan[i].std.hadVol) { - chan[i].outVol=((chan[i].vol&0xff)*chan[i].std.vol)>>6; + if (chan[i].std.vol.had) { + chan[i].outVol=((chan[i].vol&0xff)*chan[i].std.vol.val)>>6; // Check if enabled and write volume if (chan[i].active) { rWrite(q1_reg_map[Q1V_VOL][i], chan[i].outVol << 4); @@ -311,17 +311,17 @@ void DivPlatformQSound::tick() { qsound_loop = length - s->loopStart; } } - if (chan[i].std.hadArp) { + if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - if (chan[i].std.arpMode) { - chan[i].baseFreq=off*QS_NOTE_FREQUENCY(chan[i].std.arp); + if (chan[i].std.arp.mode) { + chan[i].baseFreq=off*QS_NOTE_FREQUENCY(chan[i].std.arp.val); } else { - chan[i].baseFreq=off*QS_NOTE_FREQUENCY(chan[i].note+chan[i].std.arp); + chan[i].baseFreq=off*QS_NOTE_FREQUENCY(chan[i].note+chan[i].std.arp.val); } } chan[i].freqChanged=true; } else { - if (chan[i].std.arpMode && chan[i].std.finishedArp) { + if (chan[i].std.arp.mode && chan[i].std.arp.finished) { chan[i].baseFreq=off*QS_NOTE_FREQUENCY(chan[i].note); chan[i].freqChanged=true; } @@ -338,7 +338,7 @@ void DivPlatformQSound::tick() { rWrite(q1_reg_map[Q1V_PHASE][i], 0x8000); //logW("ch %d bank=%04x, addr=%04x, end=%04x, loop=%04x!\n",i,qsound_bank,qsound_addr,qsound_end,qsound_loop); // Write sample address. Enable volume - if (!chan[i].std.hadVol) { + if (!chan[i].std.vol.had) { rWrite(q1_reg_map[Q1V_VOL][i], chan[i].vol << 4); } } @@ -404,7 +404,7 @@ int DivPlatformQSound::dispatch(DivCommand c) { case DIV_CMD_VOLUME: if (chan[c.chan].vol!=c.value) { chan[c.chan].vol=c.value; - if (!chan[c.chan].std.hasVol) { + if (!chan[c.chan].std.vol.has) { // Check if enabled and write volume chan[c.chan].outVol=c.value; if (chan[c.chan].active && c.chan < 16) { @@ -414,7 +414,7 @@ int DivPlatformQSound::dispatch(DivCommand c) { } break; case DIV_CMD_GET_VOLUME: - if (chan[c.chan].std.hasVol) { + if (chan[c.chan].std.vol.has) { return chan[c.chan].vol; } return chan[c.chan].outVol; @@ -477,7 +477,7 @@ int DivPlatformQSound::dispatch(DivCommand c) { off=(double)s->centerRate/24038.0/16.0; } } - chan[c.chan].baseFreq=off*QS_NOTE_FREQUENCY(c.value+((chan[c.chan].std.willArp && !chan[c.chan].std.arpMode)?(chan[c.chan].std.arp-12):(0))); + chan[c.chan].baseFreq=off*QS_NOTE_FREQUENCY(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val-12):(0))); chan[c.chan].freqChanged=true; chan[c.chan].note=c.value; break; diff --git a/src/engine/platform/saa.cpp b/src/engine/platform/saa.cpp index 0bafaf157..db614ee71 100644 --- a/src/engine/platform/saa.cpp +++ b/src/engine/platform/saa.cpp @@ -135,8 +135,8 @@ inline unsigned char applyPan(unsigned char vol, unsigned char pan) { void DivPlatformSAA1099::tick() { for (int i=0; i<6; i++) { chan[i].std.next(); - if (chan[i].std.hadVol) { - chan[i].outVol=MIN(15,chan[i].std.vol)-(15-(chan[i].vol&15)); + if (chan[i].std.vol.had) { + chan[i].outVol=MIN(15,chan[i].std.vol.val)-(15-(chan[i].vol&15)); if (chan[i].outVol<0) chan[i].outVol=0; if (isMuted[i]) { rWrite(i,0); @@ -144,30 +144,30 @@ void DivPlatformSAA1099::tick() { rWrite(i,applyPan(chan[i].outVol&15,chan[i].pan)); } } - if (chan[i].std.hadArp) { + if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - if (chan[i].std.arpMode) { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp); + if (chan[i].std.arp.mode) { + chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp.val); } else { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp); + chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp.val); } } chan[i].freqChanged=true; } else { - if (chan[i].std.arpMode && chan[i].std.finishedArp) { + if (chan[i].std.arp.mode && chan[i].std.arp.finished) { chan[i].baseFreq=NOTE_PERIODIC(chan[i].note); chan[i].freqChanged=true; } } - if (chan[i].std.hadDuty) { - saaNoise[i/3]=chan[i].std.duty&3; + if (chan[i].std.duty.had) { + saaNoise[i/3]=chan[i].std.duty.val&3; rWrite(0x16,saaNoise[0]|(saaNoise[1]<<4)); } - if (chan[i].std.hadWave) { - chan[i].psgMode=chan[i].std.wave&3; + if (chan[i].std.wave.had) { + chan[i].psgMode=chan[i].std.wave.val&3; } - if (chan[i].std.hadEx1) { - saaEnv[i/3]=chan[i].std.ex1; + if (chan[i].std.ex1.had) { + saaEnv[i/3]=chan[i].std.ex1.val; rWrite(0x18+(i/3),saaEnv[i/3]); } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { @@ -253,7 +253,7 @@ int DivPlatformSAA1099::dispatch(DivCommand c) { break; case DIV_CMD_VOLUME: { chan[c.chan].vol=c.value; - if (!chan[c.chan].std.hasVol) { + if (!chan[c.chan].std.vol.has) { chan[c.chan].outVol=c.value; } if (isMuted[c.chan]) { diff --git a/src/engine/platform/segapcm.cpp b/src/engine/platform/segapcm.cpp index 69d8bd6e7..b87ee9a86 100644 --- a/src/engine/platform/segapcm.cpp +++ b/src/engine/platform/segapcm.cpp @@ -80,21 +80,21 @@ void DivPlatformSegaPCM::tick() { for (int i=0; i<16; i++) { chan[i].std.next(); - if (chan[i].std.hadVol) { - chan[i].outVol=(chan[i].vol*MIN(127,chan[i].std.vol))/127; + if (chan[i].std.vol.had) { + chan[i].outVol=(chan[i].vol*MIN(127,chan[i].std.vol.val))/127; } - if (chan[i].std.hadArp) { + if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - if (chan[i].std.arpMode) { - chan[i].baseFreq=(chan[i].std.arp<<6); + if (chan[i].std.arp.mode) { + chan[i].baseFreq=(chan[i].std.arp.val<<6); } else { - chan[i].baseFreq=((chan[i].note+(signed char)chan[i].std.arp)<<6); + chan[i].baseFreq=((chan[i].note+(signed char)chan[i].std.arp.val)<<6); } } chan[i].freqChanged=true; } else { - if (chan[i].std.arpMode && chan[i].std.finishedArp) { + if (chan[i].std.arp.mode && chan[i].std.arp.finished) { chan[i].baseFreq=(chan[i].note<<6); chan[i].freqChanged=true; } @@ -214,7 +214,7 @@ int DivPlatformSegaPCM::dispatch(DivCommand c) { break; case DIV_CMD_VOLUME: { chan[c.chan].vol=c.value; - if (!chan[c.chan].std.hasVol) { + if (!chan[c.chan].std.vol.has) { chan[c.chan].outVol=c.value; } chan[c.chan].chVolL=c.value; diff --git a/src/engine/platform/sms.cpp b/src/engine/platform/sms.cpp index 08cf568d8..f4bbc46e1 100644 --- a/src/engine/platform/sms.cpp +++ b/src/engine/platform/sms.cpp @@ -56,21 +56,21 @@ int DivPlatformSMS::acquireOne() { void DivPlatformSMS::tick() { for (int i=0; i<4; i++) { chan[i].std.next(); - if (chan[i].std.hadVol) { - chan[i].outVol=MIN(15,chan[i].std.vol)-(15-(chan[i].vol&15)); + if (chan[i].std.vol.had) { + chan[i].outVol=MIN(15,chan[i].std.vol.val)-(15-(chan[i].vol&15)); if (chan[i].outVol<0) chan[i].outVol=0; // old formula - // ((chan[i].vol&15)*MIN(15,chan[i].std.vol))>>4; + // ((chan[i].vol&15)*MIN(15,chan[i].std.vol.val))>>4; rWrite(0x90|(i<<5)|(isMuted[i]?15:(15-(chan[i].outVol&15)))); } - if (chan[i].std.hadArp) { + if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - if (chan[i].std.arpMode) { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp); - chan[i].actualNote=chan[i].std.arp; + if (chan[i].std.arp.mode) { + chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp.val); + chan[i].actualNote=chan[i].std.arp.val; } else { // TODO: check whether this weird octave boundary thing applies to other systems as well - int areYouSerious=chan[i].note+chan[i].std.arp; + int areYouSerious=chan[i].note+chan[i].std.arp.val; while (areYouSerious>0x60) areYouSerious-=12; chan[i].baseFreq=NOTE_PERIODIC(areYouSerious); chan[i].actualNote=areYouSerious; @@ -78,15 +78,15 @@ void DivPlatformSMS::tick() { chan[i].freqChanged=true; } } else { - if (chan[i].std.arpMode && chan[i].std.finishedArp) { + if (chan[i].std.arp.mode && chan[i].std.arp.finished) { chan[i].baseFreq=NOTE_PERIODIC(chan[i].note); chan[i].actualNote=chan[i].note; chan[i].freqChanged=true; } } - if (i==3) if (chan[i].std.hadDuty) { - snNoiseMode=chan[i].std.duty; - if (chan[i].std.duty<2) { + if (i==3) if (chan[i].std.duty.had) { + snNoiseMode=chan[i].std.duty.val; + if (chan[i].std.duty.val<2) { chan[3].freqChanged=false; } updateSNMode=true; @@ -130,11 +130,11 @@ void DivPlatformSMS::tick() { } } else { // 3 fixed values unsigned char value; - if (chan[3].std.hadArp) { - if (chan[3].std.arpMode) { - value=chan[3].std.arp%12; + if (chan[3].std.arp.had) { + if (chan[3].std.arp.mode) { + value=chan[3].std.arp.val%12; } else { - value=(chan[3].note+chan[3].std.arp)%12; + value=(chan[3].note+chan[3].std.arp.val)%12; } } else { value=chan[3].note%12; @@ -181,14 +181,14 @@ int DivPlatformSMS::dispatch(DivCommand c) { case DIV_CMD_VOLUME: if (chan[c.chan].vol!=c.value) { chan[c.chan].vol=c.value; - if (!chan[c.chan].std.hasVol) { + if (!chan[c.chan].std.vol.has) { chan[c.chan].outVol=c.value; } if (chan[c.chan].active) rWrite(0x90|c.chan<<5|(isMuted[c.chan]?15:(15-(chan[c.chan].vol&15)))); } break; case DIV_CMD_GET_VOLUME: - if (chan[c.chan].std.hasVol) { + if (chan[c.chan].std.vol.has) { return chan[c.chan].vol; } return chan[c.chan].outVol; @@ -225,7 +225,7 @@ int DivPlatformSMS::dispatch(DivCommand c) { updateSNMode=true; break; case DIV_CMD_LEGATO: - chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.willArp && !chan[c.chan].std.arpMode)?(chan[c.chan].std.arp):(0))); + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0))); chan[c.chan].freqChanged=true; chan[c.chan].note=c.value; chan[c.chan].actualNote=c.value; diff --git a/src/engine/platform/swan.cpp b/src/engine/platform/swan.cpp index 6966a2d1c..802f27b86 100644 --- a/src/engine/platform/swan.cpp +++ b/src/engine/platform/swan.cpp @@ -145,31 +145,31 @@ void DivPlatformSwan::tick() { unsigned char sndCtrl=(pcm?0x20:0)|(sweep?0x40:0)|((noise>0)?0x80:0); for (int i=0; i<4; i++) { chan[i].std.next(); - if (chan[i].std.hadVol) { - int env=chan[i].std.vol; + if (chan[i].std.vol.had) { + int env=chan[i].std.vol.val; if(parent->getIns(chan[i].ins)->type==DIV_INS_AMIGA) { env=MIN(env/4,15); } calcAndWriteOutVol(i,env); } - if (chan[i].std.hadArp) { + if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - if (chan[i].std.arpMode) { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp); + if (chan[i].std.arp.mode) { + chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp.val); } else { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp); + chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp.val); } } chan[i].freqChanged=true; } else { - if (chan[i].std.arpMode && chan[i].std.finishedArp) { + if (chan[i].std.arp.mode && chan[i].std.arp.finished) { chan[i].baseFreq=NOTE_PERIODIC(chan[i].note); chan[i].freqChanged=true; } } - if (chan[i].std.hadWave && !(i==1 && pcm)) { - if (chan[i].wave!=chan[i].std.wave || chan[i].ws.activeChanged()) { - chan[i].wave=chan[i].std.wave; + if (chan[i].std.wave.had && !(i==1 && pcm)) { + if (chan[i].wave!=chan[i].std.wave.val || chan[i].ws.activeChanged()) { + chan[i].wave=chan[i].std.wave.val; chan[i].ws.changeWave1(chan[i].wave); } } @@ -200,7 +200,7 @@ void DivPlatformSwan::tick() { rWrite(i*2,rVal&0xff); rWrite(i*2+1,rVal>>8); if (chan[i].keyOn) { - if (!chan[i].std.willVol) { + if (!chan[i].std.vol.will) { calcAndWriteOutVol(i,15); } chan[i].keyOn=false; @@ -211,8 +211,8 @@ void DivPlatformSwan::tick() { chan[i].freqChanged=false; } } - if (chan[3].std.hadDuty) { - noise=chan[3].std.duty; + if (chan[3].std.duty.had) { + noise=chan[3].std.duty.val; if (noise>0) { rWrite(0x0e,((noise-1)&0x07)|0x18); sndCtrl|=0x80; @@ -319,7 +319,7 @@ int DivPlatformSwan::dispatch(DivCommand c) { case DIV_CMD_VOLUME: if (chan[c.chan].vol!=c.value) { chan[c.chan].vol=c.value; - if (!chan[c.chan].std.hadVol) { + if (!chan[c.chan].std.vol.had) { calcAndWriteOutVol(c.chan,15); } } @@ -391,11 +391,11 @@ int DivPlatformSwan::dispatch(DivCommand c) { break; case DIV_CMD_PANNING: { chan[c.chan].pan=c.value; - calcAndWriteOutVol(c.chan,chan[c.chan].std.willVol?chan[c.chan].std.vol:15); + calcAndWriteOutVol(c.chan,chan[c.chan].std.vol.will?chan[c.chan].std.vol.val:15); break; } case DIV_CMD_LEGATO: - chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.willArp && !chan[c.chan].std.arpMode)?(chan[c.chan].std.arp):(0))); + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0))); chan[c.chan].freqChanged=true; chan[c.chan].note=c.value; break; diff --git a/src/engine/platform/tia.cpp b/src/engine/platform/tia.cpp index 2b99731fe..e06c5c74f 100644 --- a/src/engine/platform/tia.cpp +++ b/src/engine/platform/tia.cpp @@ -87,8 +87,8 @@ unsigned char DivPlatformTIA::dealWithFreq(unsigned char shape, int base, int pi void DivPlatformTIA::tick() { for (int i=0; i<2; i++) { chan[i].std.next(); - if (chan[i].std.hadVol) { - chan[i].outVol=MIN(15,chan[i].std.vol)-(15-(chan[i].vol&15)); + if (chan[i].std.vol.had) { + chan[i].outVol=MIN(15,chan[i].std.vol.val)-(15-(chan[i].vol&15)); if (chan[i].outVol<0) chan[i].outVol=0; if (isMuted[i]) { rWrite(0x19+i,0); @@ -96,29 +96,29 @@ void DivPlatformTIA::tick() { rWrite(0x19+i,chan[i].outVol&15); } } - if (chan[i].std.hadArp) { + if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - if (chan[i].std.arpMode) { - chan[i].baseFreq=0x80000000|chan[i].std.arp; + if (chan[i].std.arp.mode) { + chan[i].baseFreq=0x80000000|chan[i].std.arp.val; } else { - chan[i].baseFreq=(chan[i].note+chan[i].std.arp)<<8; + chan[i].baseFreq=(chan[i].note+chan[i].std.arp.val)<<8; } } chan[i].freqChanged=true; } else { - if (chan[i].std.arpMode && chan[i].std.finishedArp) { + if (chan[i].std.arp.mode && chan[i].std.arp.finished) { chan[i].baseFreq=chan[i].note<<8; chan[i].freqChanged=true; } } - if (chan[i].std.hadWave) { - chan[i].shape=chan[i].std.wave&15; + if (chan[i].std.wave.had) { + chan[i].shape=chan[i].std.wave.val&15; rWrite(0x15+i,chan[i].shape); chan[i].freqChanged=true; } if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { if (chan[i].insChanged) { - if (!chan[i].std.willWave) { + if (!chan[i].std.wave.will) { chan[i].shape=4; rWrite(0x15+i,chan[i].shape); } @@ -179,7 +179,7 @@ int DivPlatformTIA::dispatch(DivCommand c) { break; case DIV_CMD_VOLUME: { chan[c.chan].vol=c.value; - if (!chan[c.chan].std.hasVol) { + if (!chan[c.chan].std.vol.has) { chan[c.chan].outVol=c.value; } if (isMuted[c.chan]) { diff --git a/src/engine/platform/tx81z.cpp b/src/engine/platform/tx81z.cpp index e9e817cd7..8ae4c6882 100644 --- a/src/engine/platform/tx81z.cpp +++ b/src/engine/platform/tx81z.cpp @@ -184,8 +184,8 @@ void DivPlatformTX81Z::tick() { for (int i=0; i<8; i++) { chan[i].std.next(); - if (chan[i].std.hadVol) { - chan[i].outVol=(chan[i].vol*MIN(127,chan[i].std.vol))/127; + if (chan[i].std.vol.had) { + chan[i].outVol=(chan[i].vol*MIN(127,chan[i].std.vol.val))/127; for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; @@ -197,50 +197,50 @@ void DivPlatformTX81Z::tick() { } } - if (chan[i].std.hadArp) { + if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - if (chan[i].std.arpMode) { - chan[i].baseFreq=NOTE_LINEAR(chan[i].std.arp); + if (chan[i].std.arp.mode) { + chan[i].baseFreq=NOTE_LINEAR(chan[i].std.arp.val); } else { - chan[i].baseFreq=NOTE_LINEAR(chan[i].note+(signed char)chan[i].std.arp); + chan[i].baseFreq=NOTE_LINEAR(chan[i].note+(signed char)chan[i].std.arp.val); } } chan[i].freqChanged=true; } else { - if (chan[i].std.arpMode && chan[i].std.finishedArp) { + if (chan[i].std.arp.mode && chan[i].std.arp.finished) { chan[i].baseFreq=NOTE_LINEAR(chan[i].note); chan[i].freqChanged=true; } } - if (chan[i].std.hadDuty) { - if (chan[i].std.duty>0) { - rWrite(0x0f,0x80|(0x20-chan[i].std.duty)); + if (chan[i].std.duty.had) { + if (chan[i].std.duty.val>0) { + rWrite(0x0f,0x80|(0x20-chan[i].std.duty.val)); } else { rWrite(0x0f,0); } } - if (chan[i].std.hadWave) { - rWrite(0x1b,chan[i].std.wave&3); + if (chan[i].std.wave.had) { + rWrite(0x1b,chan[i].std.wave.val&3); } - if (chan[i].std.hadEx1) { - amDepth=chan[i].std.ex1; + if (chan[i].std.ex1.had) { + amDepth=chan[i].std.ex1.val; immWrite(0x19,amDepth); } - if (chan[i].std.hadEx2) { - pmDepth=chan[i].std.ex2; + if (chan[i].std.ex2.had) { + pmDepth=chan[i].std.ex2.val; immWrite(0x19,0x80|pmDepth); } - if (chan[i].std.hadEx3) { - immWrite(0x18,chan[i].std.ex3); + if (chan[i].std.ex3.had) { + immWrite(0x18,chan[i].std.ex3.val); } - if (chan[i].std.hadAlg) { - chan[i].state.alg=chan[i].std.alg; + if (chan[i].std.alg.had) { + chan[i].state.alg=chan[i].std.alg.val; if (isMuted[i]) { immWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|0x40); } else { @@ -260,72 +260,72 @@ void DivPlatformTX81Z::tick() { } } } - if (chan[i].std.hadFb) { - chan[i].state.fb=chan[i].std.fb; + if (chan[i].std.fb.had) { + chan[i].state.fb=chan[i].std.fb.val; if (isMuted[i]) { immWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|0x40); } else { immWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|(chan[i].active?0:0x40)|(chan[i].chVolR<<7)); } } - if (chan[i].std.hadFms) { - chan[i].state.fms=chan[i].std.fms; + if (chan[i].std.fms.had) { + chan[i].state.fms=chan[i].std.fms.val; rWrite(chanOffs[i]+ADDR_FMS_AMS,((chan[i].state.fms&7)<<4)|(chan[i].state.ams&3)); } - if (chan[i].std.hadAms) { - chan[i].state.ams=chan[i].std.ams; + if (chan[i].std.ams.had) { + chan[i].state.ams=chan[i].std.ams.val; rWrite(chanOffs[i]+ADDR_FMS_AMS,((chan[i].state.fms&7)<<4)|(chan[i].state.ams&3)); } for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; DivMacroInt::IntOp& m=chan[i].std.op[j]; - if (m.hadAm) { - op.am=m.am; + if (m.am.had) { + op.am=m.am.val; rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); } - if (m.hadAr) { - op.ar=m.ar; + if (m.ar.had) { + op.ar=m.ar.val; rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.egt<<5)|(op.rs<<6)); } - if (m.hadDr) { - op.dr=m.dr; + if (m.dr.had) { + op.dr=m.dr.val; rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); } - if (m.hadMult) { - op.mult=m.mult; + if (m.mult.had) { + op.mult=m.mult.val; rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); } - if (m.hadRr) { - op.rr=m.rr; + if (m.rr.had) { + op.rr=m.rr.val; rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); } - if (m.hadSl) { - op.sl=m.sl; + if (m.sl.had) { + op.sl=m.sl.val; rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); } - if (m.hadTl) { - op.tl=127-m.tl; + if (m.tl.had) { + op.tl=127-m.tl.val; if (isOutput[chan[i].state.alg][j]) { rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); } else { rWrite(baseAddr+ADDR_TL,op.tl); } } - if (m.hadRs) { - op.rs=m.rs; + if (m.rs.had) { + op.rs=m.rs.val; rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.egt<<5)|(op.rs<<6)); } - if (m.hadDt) { - op.dt=m.dt; + if (m.dt.had) { + op.dt=m.dt.val; rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); } - if (m.hadD2r) { - op.d2r=m.d2r; + if (m.d2r.had) { + op.d2r=m.d2r.val; rWrite(baseAddr+ADDR_DT2_D2R,(op.d2r&31)|(op.dt2<<6)); } - if (m.hadDt2) { - op.dt2=m.dt2; + if (m.dt2.had) { + op.dt2=m.dt2.val; rWrite(baseAddr+ADDR_DT2_D2R,(op.d2r&31)|(op.dt2<<6)); } } @@ -401,7 +401,7 @@ int DivPlatformTX81Z::dispatch(DivCommand c) { } chan[c.chan].std.init(ins); - if (!chan[c.chan].std.willVol) { + if (!chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; } @@ -463,7 +463,7 @@ int DivPlatformTX81Z::dispatch(DivCommand c) { break; case DIV_CMD_VOLUME: { chan[c.chan].vol=c.value; - if (!chan[c.chan].std.hasVol) { + if (!chan[c.chan].std.vol.has) { chan[c.chan].outVol=c.value; } for (int i=0; i<4; i++) { diff --git a/src/engine/platform/vera.cpp b/src/engine/platform/vera.cpp index 8965719f5..77f5c8050 100644 --- a/src/engine/platform/vera.cpp +++ b/src/engine/platform/vera.cpp @@ -158,30 +158,30 @@ int DivPlatformVERA::calcNoteFreq(int ch, int note) { void DivPlatformVERA::tick() { for (int i=0; i<16; i++) { chan[i].std.next(); - if (chan[i].std.hadVol) { - chan[i].outVol=MAX(chan[i].vol+chan[i].std.vol-63,0); + if (chan[i].std.vol.had) { + chan[i].outVol=MAX(chan[i].vol+chan[i].std.vol.val-63,0); rWriteLo(i,2,chan[i].outVol); } - if (chan[i].std.hadArp) { + if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - if (chan[i].std.arpMode) { - chan[i].baseFreq=calcNoteFreq(0,chan[i].std.arp); + if (chan[i].std.arp.mode) { + chan[i].baseFreq=calcNoteFreq(0,chan[i].std.arp.val); } else { - chan[i].baseFreq=calcNoteFreq(0,chan[i].note+chan[i].std.arp); + chan[i].baseFreq=calcNoteFreq(0,chan[i].note+chan[i].std.arp.val); } } chan[i].freqChanged=true; } else { - if (chan[i].std.arpMode && chan[i].std.finishedArp) { + if (chan[i].std.arp.mode && chan[i].std.arp.finished) { chan[i].baseFreq=calcNoteFreq(0,chan[i].note); chan[i].freqChanged=true; } } - if (chan[i].std.hadDuty) { - rWriteLo(i,3,chan[i].std.duty); + if (chan[i].std.duty.had) { + rWriteLo(i,3,chan[i].std.duty.val); } - if (chan[i].std.hadWave) { - rWriteHi(i,3,chan[i].std.wave); + if (chan[i].std.wave.had) { + rWriteHi(i,3,chan[i].std.wave.val); } if (chan[i].freqChanged) { chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,8); @@ -193,21 +193,21 @@ void DivPlatformVERA::tick() { } // PCM chan[16].std.next(); - if (chan[16].std.hadVol) { - chan[16].outVol=MAX(chan[16].vol+MIN(chan[16].std.vol/4,15)-15,0); + if (chan[16].std.vol.had) { + chan[16].outVol=MAX(chan[16].vol+MIN(chan[16].std.vol.val/4,15)-15,0); rWritePCMVol(chan[16].outVol&15); } - if (chan[16].std.hadArp) { + if (chan[16].std.arp.had) { if (!chan[16].inPorta) { - if (chan[16].std.arpMode) { - chan[16].baseFreq=calcNoteFreq(16,chan[16].std.arp); + if (chan[16].std.arp.mode) { + chan[16].baseFreq=calcNoteFreq(16,chan[16].std.arp.val); } else { - chan[16].baseFreq=calcNoteFreq(16,chan[16].note+chan[16].std.arp); + chan[16].baseFreq=calcNoteFreq(16,chan[16].note+chan[16].std.arp.val); } } chan[16].freqChanged=true; } else { - if (chan[16].std.arpMode && chan[16].std.finishedArp) { + if (chan[16].std.arp.mode && chan[16].std.arp.finished) { chan[16].baseFreq=calcNoteFreq(16,chan[16].note); chan[16].freqChanged=true; } @@ -311,7 +311,7 @@ int DivPlatformVERA::dispatch(DivCommand c) { break; } case DIV_CMD_LEGATO: - chan[c.chan].baseFreq=calcNoteFreq(c.chan,c.value+((chan[c.chan].std.willArp && !chan[c.chan].std.arpMode)?(chan[c.chan].std.arp):(0))); + chan[c.chan].baseFreq=calcNoteFreq(c.chan,c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0))); chan[c.chan].freqChanged=true; chan[c.chan].note=c.value; break; diff --git a/src/engine/platform/vic20.cpp b/src/engine/platform/vic20.cpp index 7fe13e96f..b45fecfc3 100644 --- a/src/engine/platform/vic20.cpp +++ b/src/engine/platform/vic20.cpp @@ -94,28 +94,28 @@ void DivPlatformVIC20::writeOutVol(int ch) { void DivPlatformVIC20::tick() { for (int i=0; i<4; i++) { chan[i].std.next(); - if (chan[i].std.hadVol) { - int env=chan[i].std.vol; + if (chan[i].std.vol.had) { + int env=chan[i].std.vol.val; calcAndWriteOutVol(i,env); } - if (chan[i].std.hadArp) { + if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - if (chan[i].std.arpMode) { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp); + if (chan[i].std.arp.mode) { + chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp.val); } else { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp); + chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp.val); } } chan[i].freqChanged=true; } else { - if (chan[i].std.arpMode && chan[i].std.finishedArp) { + if (chan[i].std.arp.mode && chan[i].std.arp.finished) { chan[i].baseFreq=NOTE_PERIODIC(chan[i].note); chan[i].freqChanged=true; } } - if (chan[i].std.hadWave) { - if (chan[i].wave!=chan[i].std.wave) { - chan[i].wave=chan[i].std.wave&0x0f; + if (chan[i].std.wave.had) { + if (chan[i].wave!=chan[i].std.wave.val) { + chan[i].wave=chan[i].std.wave.val&0x0f; chan[i].keyOn=true; } } @@ -183,7 +183,7 @@ int DivPlatformVIC20::dispatch(DivCommand c) { case DIV_CMD_VOLUME: if (chan[c.chan].vol!=c.value) { chan[c.chan].vol=c.value; - if (!chan[c.chan].std.hadVol) { + if (!chan[c.chan].std.vol.had) { calcAndWriteOutVol(c.chan,15); } } @@ -223,7 +223,7 @@ int DivPlatformVIC20::dispatch(DivCommand c) { break; } case DIV_CMD_LEGATO: - chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.willArp && !chan[c.chan].std.arpMode)?(chan[c.chan].std.arp):(0))); + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0))); chan[c.chan].freqChanged=true; chan[c.chan].note=c.value; break; diff --git a/src/engine/platform/vrc6.cpp b/src/engine/platform/vrc6.cpp index c89df338e..31694a62c 100644 --- a/src/engine/platform/vrc6.cpp +++ b/src/engine/platform/vrc6.cpp @@ -140,16 +140,16 @@ void DivPlatformVRC6::tick() { // 16 for pulse; 14 for saw int CHIP_DIVIDER=(i==2)?14:16; chan[i].std.next(); - if (chan[i].std.hadVol) { + if (chan[i].std.vol.had) { if (i==2) { // sawtooth - chan[i].outVol=((chan[i].vol&63)*MIN(63,chan[i].std.vol))/63; + chan[i].outVol=((chan[i].vol&63)*MIN(63,chan[i].std.vol.val))/63; if (chan[i].outVol<0) chan[i].outVol=0; if (chan[i].outVol>63) chan[i].outVol=63; if (!isMuted[i]) { chWrite(i,0,chan[i].outVol); } } else { // pulse - chan[i].outVol=((chan[i].vol&15)*MIN(15,chan[i].std.vol))/15; + chan[i].outVol=((chan[i].vol&15)*MIN(15,chan[i].std.vol.val))/15; if (chan[i].outVol<0) chan[i].outVol=0; if (chan[i].outVol>15) chan[i].outVol=15; if ((!isMuted[i]) && (!chan[i].pcm)) { @@ -157,23 +157,23 @@ void DivPlatformVRC6::tick() { } } } - if (chan[i].std.hadArp) { + if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - if (chan[i].std.arpMode) { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp); + if (chan[i].std.arp.mode) { + chan[i].baseFreq=NOTE_PERIODIC(chan[i].std.arp.val); } else { - chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp); + chan[i].baseFreq=NOTE_PERIODIC(chan[i].note+chan[i].std.arp.val); } } chan[i].freqChanged=true; } else { - if (chan[i].std.arpMode && chan[i].std.finishedArp) { + if (chan[i].std.arp.mode && chan[i].std.arp.finished) { chan[i].baseFreq=NOTE_PERIODIC(chan[i].note); chan[i].freqChanged=true; } } - if (chan[i].std.hadDuty) { - chan[i].duty=chan[i].std.duty; + if (chan[i].std.duty.had) { + chan[i].duty=chan[i].std.duty.val; if ((!isMuted[i]) && (i!=2) && (!chan[i].pcm)) { // pulse chWrite(i,0,(chan[i].outVol&0xf)|((chan[i].duty&7)<<4)); } @@ -310,7 +310,7 @@ int DivPlatformVRC6::dispatch(DivCommand c) { case DIV_CMD_VOLUME: if (chan[c.chan].vol!=c.value) { chan[c.chan].vol=c.value; - if (!chan[c.chan].std.hasVol) { + if (!chan[c.chan].std.vol.has) { chan[c.chan].outVol=c.value; } if (!isMuted[c.chan]) { @@ -371,7 +371,7 @@ int DivPlatformVRC6::dispatch(DivCommand c) { } break; case DIV_CMD_LEGATO: - chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.willArp && !chan[c.chan].std.arpMode)?(chan[c.chan].std.arp):(0))); + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0))); chan[c.chan].freqChanged=true; chan[c.chan].note=c.value; break; diff --git a/src/engine/platform/x1_010.cpp b/src/engine/platform/x1_010.cpp index 68f6db297..124e0ace6 100644 --- a/src/engine/platform/x1_010.cpp +++ b/src/engine/platform/x1_010.cpp @@ -339,41 +339,41 @@ void DivPlatformX1_010::updateEnvelope(int ch) { void DivPlatformX1_010::tick() { for (int i=0; i<16; i++) { chan[i].std.next(); - if (chan[i].std.hadVol) { - signed char macroVol=((chan[i].vol&15)*MIN(chan[i].furnacePCM?64:15,chan[i].std.vol))/(chan[i].furnacePCM?64:15); + if (chan[i].std.vol.had) { + signed char macroVol=((chan[i].vol&15)*MIN(chan[i].furnacePCM?64:15,chan[i].std.vol.val))/(chan[i].furnacePCM?64:15); if ((!isMuted[i]) && (macroVol!=chan[i].outVol)) { chan[i].outVol=macroVol; chan[i].envChanged=true; } } if ((!chan[i].pcm) || chan[i].furnacePCM) { - if (chan[i].std.hadArp) { + if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - if (chan[i].std.arpMode) { - chan[i].baseFreq=NoteX1_010(i,chan[i].std.arp); + if (chan[i].std.arp.mode) { + chan[i].baseFreq=NoteX1_010(i,chan[i].std.arp.val); } else { - chan[i].baseFreq=NoteX1_010(i,chan[i].note+chan[i].std.arp); + chan[i].baseFreq=NoteX1_010(i,chan[i].note+chan[i].std.arp.val); } } chan[i].freqChanged=true; } else { - if (chan[i].std.arpMode && chan[i].std.finishedArp) { + if (chan[i].std.arp.mode && chan[i].std.arp.finished) { chan[i].baseFreq=NoteX1_010(i,chan[i].note); chan[i].freqChanged=true; } } } - if (chan[i].std.hadWave && !chan[i].pcm) { - if (chan[i].wave!=chan[i].std.wave || chan[i].ws.activeChanged()) { - chan[i].wave=chan[i].std.wave; + if (chan[i].std.wave.had && !chan[i].pcm) { + if (chan[i].wave!=chan[i].std.wave.val || chan[i].ws.activeChanged()) { + chan[i].wave=chan[i].std.wave.val; if (!chan[i].pcm) { chan[i].ws.changeWave1(chan[i].wave); if (!chan[i].keyOff) chan[i].keyOn=true; } } } - if (chan[i].std.hadEx1) { - bool nextEnable=(chan[i].std.ex1&1); + if (chan[i].std.ex1.had) { + bool nextEnable=(chan[i].std.ex1.val&1); if (nextEnable!=(chan[i].env.flag.envEnable)) { chan[i].env.flag.envEnable=nextEnable; if (!chan[i].pcm) { @@ -383,42 +383,42 @@ void DivPlatformX1_010::tick() { refreshControl(i); } } - bool nextOneshot=(chan[i].std.ex1&2); + bool nextOneshot=(chan[i].std.ex1.val&2); if (nextOneshot!=(chan[i].env.flag.envOneshot)) { chan[i].env.flag.envOneshot=nextOneshot; if (!chan[i].pcm) { refreshControl(i); } } - bool nextSplit=(chan[i].std.ex1&4); + bool nextSplit=(chan[i].std.ex1.val&4); if (nextSplit!=(chan[i].env.flag.envSplit)) { chan[i].env.flag.envSplit=nextSplit; if (!isMuted[i] && !chan[i].pcm) { chan[i].envChanged=true; } } - bool nextHinvR=(chan[i].std.ex1&8); + bool nextHinvR=(chan[i].std.ex1.val&8); if (nextHinvR!=(chan[i].env.flag.envHinvR)) { chan[i].env.flag.envHinvR=nextHinvR; if (!isMuted[i] && !chan[i].pcm) { chan[i].envChanged=true; } } - bool nextVinvR=(chan[i].std.ex1&16); + bool nextVinvR=(chan[i].std.ex1.val&16); if (nextVinvR!=(chan[i].env.flag.envVinvR)) { chan[i].env.flag.envVinvR=nextVinvR; if (!isMuted[i] && !chan[i].pcm) { chan[i].envChanged=true; } } - bool nextHinvL=(chan[i].std.ex1&32); + bool nextHinvL=(chan[i].std.ex1.val&32); if (nextHinvL!=(chan[i].env.flag.envHinvL)) { chan[i].env.flag.envHinvL=nextHinvL; if (!isMuted[i] && !chan[i].pcm) { chan[i].envChanged=true; } } - bool nextVinvL=(chan[i].std.ex1&64); + bool nextVinvL=(chan[i].std.ex1.val&64); if (nextVinvL!=(chan[i].env.flag.envVinvL)) { chan[i].env.flag.envVinvL=nextVinvL; if (!isMuted[i] && !chan[i].pcm) { @@ -426,9 +426,9 @@ void DivPlatformX1_010::tick() { } } } - if (chan[i].std.hadEx2) { - if (chan[i].env.shape!=chan[i].std.ex2) { - chan[i].env.shape=chan[i].std.ex2; + if (chan[i].std.ex2.had) { + if (chan[i].env.shape!=chan[i].std.ex2.val) { + chan[i].env.shape=chan[i].std.ex2.val; if (!chan[i].pcm) { if (chan[i].env.flag.envEnable && (!isMuted[i])) { chan[i].envChanged=true; @@ -437,18 +437,18 @@ void DivPlatformX1_010::tick() { } } } - if (chan[i].std.hadEx3) { - chan[i].autoEnvNum=chan[i].std.ex3; + if (chan[i].std.ex3.had) { + chan[i].autoEnvNum=chan[i].std.ex3.val; if (!chan[i].pcm) { chan[i].freqChanged=true; - if (!chan[i].std.willAlg) chan[i].autoEnvDen=1; + if (!chan[i].std.alg.will) chan[i].autoEnvDen=1; } } - if (chan[i].std.hadAlg) { - chan[i].autoEnvDen=chan[i].std.alg; + if (chan[i].std.alg.had) { + chan[i].autoEnvDen=chan[i].std.alg.val; if (!chan[i].pcm) { chan[i].freqChanged=true; - if (!chan[i].std.willEx3) chan[i].autoEnvNum=1; + if (!chan[i].std.ex3.will) chan[i].autoEnvNum=1; } } if (chan[i].active) { @@ -601,7 +601,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { case DIV_CMD_VOLUME: if (chan[c.chan].vol!=c.value) { chan[c.chan].vol=c.value; - if (!chan[c.chan].std.hasVol) { + if (!chan[c.chan].std.vol.has) { if (chan[c.chan].outVol!=c.value) { chan[c.chan].outVol=c.value; if (!isMuted[c.chan]) { @@ -612,7 +612,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { } break; case DIV_CMD_GET_VOLUME: - if (chan[c.chan].std.hasVol) { + if (chan[c.chan].std.vol.has) { return chan[c.chan].vol; } return chan[c.chan].outVol; @@ -685,7 +685,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { } case DIV_CMD_LEGATO: chan[c.chan].note=c.value; - chan[c.chan].baseFreq=NoteX1_010(c.chan,chan[c.chan].note+((chan[c.chan].std.willArp&&!chan[c.chan].std.arpMode)?(chan[c.chan].std.arp):(0))); + chan[c.chan].baseFreq=NoteX1_010(c.chan,chan[c.chan].note+((chan[c.chan].std.arp.will&&!chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0))); chan[c.chan].freqChanged=true; break; case DIV_CMD_PRE_PORTA: diff --git a/src/engine/platform/ym2610.cpp b/src/engine/platform/ym2610.cpp index 77c1243bd..398d5be4b 100644 --- a/src/engine/platform/ym2610.cpp +++ b/src/engine/platform/ym2610.cpp @@ -376,8 +376,8 @@ void DivPlatformYM2610::tick() { if (i==1 && extMode) continue; chan[i].std.next(); - if (chan[i].std.hadVol) { - chan[i].outVol=(chan[i].vol*MIN(127,chan[i].std.vol))/127; + if (chan[i].std.vol.had) { + chan[i].outVol=(chan[i].vol*MIN(127,chan[i].std.vol.val))/127; for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; @@ -389,24 +389,24 @@ void DivPlatformYM2610::tick() { } } - if (chan[i].std.hadArp) { + if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - if (chan[i].std.arpMode) { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp); + if (chan[i].std.arp.mode) { + chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val); } else { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+(signed char)chan[i].std.arp); + chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+(signed char)chan[i].std.arp.val); } } chan[i].freqChanged=true; } else { - if (chan[i].std.arpMode && chan[i].std.finishedArp) { + if (chan[i].std.arp.mode && chan[i].std.arp.finished) { chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note); chan[i].freqChanged=true; } } - if (chan[i].std.hadAlg) { - chan[i].state.alg=chan[i].std.alg; + if (chan[i].std.alg.had) { + chan[i].state.alg=chan[i].std.alg.val; rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); if (!parent->song.algMacroBehavior) for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; @@ -422,68 +422,68 @@ void DivPlatformYM2610::tick() { } } } - if (chan[i].std.hadFb) { - chan[i].state.fb=chan[i].std.fb; + if (chan[i].std.fb.had) { + chan[i].state.fb=chan[i].std.fb.val; rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); } - if (chan[i].std.hadFms) { - chan[i].state.fms=chan[i].std.fms; + if (chan[i].std.fms.had) { + chan[i].state.fms=chan[i].std.fms.val; rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); } - if (chan[i].std.hadAms) { - chan[i].state.ams=chan[i].std.ams; + if (chan[i].std.ams.had) { + chan[i].state.ams=chan[i].std.ams.val; rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); } for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; DivMacroInt::IntOp& m=chan[i].std.op[j]; - if (m.hadAm) { - op.am=m.am; + if (m.am.had) { + op.am=m.am.val; rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); } - if (m.hadAr) { - op.ar=m.ar; + if (m.ar.had) { + op.ar=m.ar.val; rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); } - if (m.hadDr) { - op.dr=m.dr; + if (m.dr.had) { + op.dr=m.dr.val; rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); } - if (m.hadMult) { - op.mult=m.mult; + if (m.mult.had) { + op.mult=m.mult.val; rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); } - if (m.hadRr) { - op.rr=m.rr; + if (m.rr.had) { + op.rr=m.rr.val; rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); } - if (m.hadSl) { - op.sl=m.sl; + if (m.sl.had) { + op.sl=m.sl.val; rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); } - if (m.hadTl) { - op.tl=127-m.tl; + if (m.tl.had) { + op.tl=127-m.tl.val; if (isOutput[chan[i].state.alg][j]) { rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); } else { rWrite(baseAddr+ADDR_TL,op.tl); } } - if (m.hadRs) { - op.rs=m.rs; + if (m.rs.had) { + op.rs=m.rs.val; rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); } - if (m.hadDt) { - op.dt=m.dt; + if (m.dt.had) { + op.dt=m.dt.val; rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); } - if (m.hadD2r) { - op.d2r=m.d2r; + if (m.d2r.had) { + op.d2r=m.d2r.val; rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); } - if (m.hadSsg) { - op.ssgEnv=m.ssg; + if (m.ssg.had) { + op.ssgEnv=m.ssg.val; rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); } } @@ -498,22 +498,22 @@ void DivPlatformYM2610::tick() { if (chan[13].furnacePCM) { chan[13].std.next(); - if (chan[13].std.hadVol) { - chan[13].outVol=(chan[13].vol*MIN(64,chan[13].std.vol))/64; + if (chan[13].std.vol.had) { + chan[13].outVol=(chan[13].vol*MIN(64,chan[13].std.vol.val))/64; immWrite(0x1b,chan[13].outVol); } - if (chan[13].std.hadArp) { + if (chan[13].std.arp.had) { if (!chan[13].inPorta) { - if (chan[13].std.arpMode) { - chan[13].baseFreq=NOTE_ADPCMB(chan[13].std.arp); + if (chan[13].std.arp.mode) { + chan[13].baseFreq=NOTE_ADPCMB(chan[13].std.arp.val); } else { - chan[13].baseFreq=NOTE_ADPCMB(chan[13].note+(signed char)chan[13].std.arp); + chan[13].baseFreq=NOTE_ADPCMB(chan[13].note+(signed char)chan[13].std.arp.val); } } chan[13].freqChanged=true; } else { - if (chan[13].std.arpMode && chan[13].std.finishedArp) { + if (chan[13].std.arp.mode && chan[13].std.arp.finished) { chan[13].baseFreq=NOTE_ADPCMB(chan[13].note); chan[13].freqChanged=true; } @@ -608,7 +608,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) { if (skipRegisterWrites) break; if (chan[c.chan].furnacePCM) { chan[c.chan].std.init(ins); - if (!chan[c.chan].std.willVol) { + if (!chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; immWrite(0x1b,chan[c.chan].outVol); } @@ -685,7 +685,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) { DivInstrument* ins=parent->getIns(chan[c.chan].ins); chan[c.chan].std.init(ins); if (c.chan<4) { - if (!chan[c.chan].std.willVol) { + if (!chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; } } @@ -764,7 +764,7 @@ int DivPlatformYM2610::dispatch(DivCommand c) { break; case DIV_CMD_VOLUME: { chan[c.chan].vol=c.value; - if (!chan[c.chan].std.hasVol) { + if (!chan[c.chan].std.vol.has) { chan[c.chan].outVol=c.value; } if (c.chan>12) { // ADPCM-B diff --git a/src/engine/platform/ym2610b.cpp b/src/engine/platform/ym2610b.cpp index dfd847235..147a75469 100644 --- a/src/engine/platform/ym2610b.cpp +++ b/src/engine/platform/ym2610b.cpp @@ -440,8 +440,8 @@ void DivPlatformYM2610B::tick() { if (i==2 && extMode) continue; chan[i].std.next(); - if (chan[i].std.hadVol) { - chan[i].outVol=(chan[i].vol*MIN(127,chan[i].std.vol))/127; + if (chan[i].std.vol.had) { + chan[i].outVol=(chan[i].vol*MIN(127,chan[i].std.vol.val))/127; for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; @@ -453,24 +453,24 @@ void DivPlatformYM2610B::tick() { } } - if (chan[i].std.hadArp) { + if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - if (chan[i].std.arpMode) { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp); + if (chan[i].std.arp.mode) { + chan[i].baseFreq=NOTE_FREQUENCY(chan[i].std.arp.val); } else { - chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+(signed char)chan[i].std.arp); + chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note+(signed char)chan[i].std.arp.val); } } chan[i].freqChanged=true; } else { - if (chan[i].std.arpMode && chan[i].std.finishedArp) { + if (chan[i].std.arp.mode && chan[i].std.arp.finished) { chan[i].baseFreq=NOTE_FREQUENCY(chan[i].note); chan[i].freqChanged=true; } } - if (chan[i].std.hadAlg) { - chan[i].state.alg=chan[i].std.alg; + if (chan[i].std.alg.had) { + chan[i].state.alg=chan[i].std.alg.val; rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); if (!parent->song.algMacroBehavior) for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; @@ -486,68 +486,68 @@ void DivPlatformYM2610B::tick() { } } } - if (chan[i].std.hadFb) { - chan[i].state.fb=chan[i].std.fb; + if (chan[i].std.fb.had) { + chan[i].state.fb=chan[i].std.fb.val; rWrite(chanOffs[i]+ADDR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)); } - if (chan[i].std.hadFms) { - chan[i].state.fms=chan[i].std.fms; + if (chan[i].std.fms.had) { + chan[i].state.fms=chan[i].std.fms.val; rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); } - if (chan[i].std.hadAms) { - chan[i].state.ams=chan[i].std.ams; + if (chan[i].std.ams.had) { + chan[i].state.ams=chan[i].std.ams.val; rWrite(chanOffs[i]+ADDR_LRAF,(isMuted[i]?0:(chan[i].pan<<6))|(chan[i].state.fms&7)|((chan[i].state.ams&3)<<4)); } for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; DivMacroInt::IntOp& m=chan[i].std.op[j]; - if (m.hadAm) { - op.am=m.am; + if (m.am.had) { + op.am=m.am.val; rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); } - if (m.hadAr) { - op.ar=m.ar; + if (m.ar.had) { + op.ar=m.ar.val; rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); } - if (m.hadDr) { - op.dr=m.dr; + if (m.dr.had) { + op.dr=m.dr.val; rWrite(baseAddr+ADDR_AM_DR,(op.dr&31)|(op.am<<7)); } - if (m.hadMult) { - op.mult=m.mult; + if (m.mult.had) { + op.mult=m.mult.val; rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); } - if (m.hadRr) { - op.rr=m.rr; + if (m.rr.had) { + op.rr=m.rr.val; rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); } - if (m.hadSl) { - op.sl=m.sl; + if (m.sl.had) { + op.sl=m.sl.val; rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); } - if (m.hadTl) { - op.tl=127-m.tl; + if (m.tl.had) { + op.tl=127-m.tl.val; if (isOutput[chan[i].state.alg][j]) { rWrite(baseAddr+ADDR_TL,127-(((127-op.tl)*(chan[i].outVol&0x7f))/127)); } else { rWrite(baseAddr+ADDR_TL,op.tl); } } - if (m.hadRs) { - op.rs=m.rs; + if (m.rs.had) { + op.rs=m.rs.val; rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); } - if (m.hadDt) { - op.dt=m.dt; + if (m.dt.had) { + op.dt=m.dt.val; rWrite(baseAddr+ADDR_MULT_DT,(op.mult&15)|(dtTable[op.dt&7]<<4)); } - if (m.hadD2r) { - op.d2r=m.d2r; + if (m.d2r.had) { + op.d2r=m.d2r.val; rWrite(baseAddr+ADDR_DT2_D2R,op.d2r&31); } - if (m.hadSsg) { - op.ssgEnv=m.ssg; + if (m.ssg.had) { + op.ssgEnv=m.ssg.val; rWrite(baseAddr+ADDR_SSG,op.ssgEnv&15); } } @@ -561,22 +561,22 @@ void DivPlatformYM2610B::tick() { if (chan[15].furnacePCM) { chan[15].std.next(); - if (chan[15].std.hadVol) { - chan[15].outVol=(chan[15].vol*MIN(64,chan[15].std.vol))/64; + if (chan[15].std.vol.had) { + chan[15].outVol=(chan[15].vol*MIN(64,chan[15].std.vol.val))/64; immWrite(0x1b,chan[15].outVol); } - if (chan[15].std.hadArp) { + if (chan[15].std.arp.had) { if (!chan[15].inPorta) { - if (chan[15].std.arpMode) { - chan[15].baseFreq=NOTE_ADPCMB(chan[15].std.arp); + if (chan[15].std.arp.mode) { + chan[15].baseFreq=NOTE_ADPCMB(chan[15].std.arp.val); } else { - chan[15].baseFreq=NOTE_ADPCMB(chan[15].note+(signed char)chan[15].std.arp); + chan[15].baseFreq=NOTE_ADPCMB(chan[15].note+(signed char)chan[15].std.arp.val); } } chan[15].freqChanged=true; } else { - if (chan[15].std.arpMode && chan[15].std.finishedArp) { + if (chan[15].std.arp.mode && chan[15].std.arp.finished) { chan[15].baseFreq=NOTE_ADPCMB(chan[15].note); chan[15].freqChanged=true; } @@ -671,7 +671,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { if (skipRegisterWrites) break; if (chan[c.chan].furnacePCM) { chan[c.chan].std.init(ins); - if (!chan[c.chan].std.willVol) { + if (!chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; immWrite(0x1b,chan[c.chan].outVol); } @@ -748,7 +748,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { DivInstrument* ins=parent->getIns(chan[c.chan].ins); chan[c.chan].std.init(ins); if (c.chan<6) { - if (!chan[c.chan].std.willVol) { + if (!chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; } } @@ -827,7 +827,7 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { break; case DIV_CMD_VOLUME: { chan[c.chan].vol=c.value; - if (!chan[c.chan].std.hasVol) { + if (!chan[c.chan].std.vol.has) { chan[c.chan].outVol=c.value; } if (c.chan>14) { // ADPCM-B diff --git a/src/engine/safeReader.cpp b/src/engine/safeReader.cpp index 9add1b5aa..f27e90446 100644 --- a/src/engine/safeReader.cpp +++ b/src/engine/safeReader.cpp @@ -66,6 +66,42 @@ int SafeReader::read(void* where, size_t count) { return count; } +template +int SafeReader::readByte(T* where, size_t count, unsigned char byte, Endianness endianness) { + if (byte==sizeof(T)) { + return read(where,count*byte); + } else { +#ifdef READ_DEBUG + logD("SR: reading %d x %d bit words at %x\n",count,byte<<3,curSeek); +#endif + if (count==0) return 0; + if (curSeek+(count*byte)>len) throw EndOfFileException(this,len); + int start,end,inc; + switch (endianness) { + case BigEndian: + start=byte-1; + end=-1; + inc=-1; + break; + case LittleEndian: + default: + start=0; + end=byte; + inc=1; + break; + } + for (int c=0; c #include "../ta-utils.h" +enum Endianness { + LittleEndian=0, + BigEndian +}; + class SafeReader; struct EndOfFileException { @@ -46,6 +51,7 @@ class SafeReader { size_t size(); int read(void* where, size_t count); + template int readByte(T* where, size_t count, unsigned char byte=sizeof(T), Endianness endianness=LittleEndian); // these functions may throw EndOfFileException. signed char readC(); diff --git a/src/engine/safeWriter.cpp b/src/engine/safeWriter.cpp index f29800a4a..2e8fb7dd4 100644 --- a/src/engine/safeWriter.cpp +++ b/src/engine/safeWriter.cpp @@ -73,6 +73,39 @@ int SafeWriter::write(const void* what, size_t count) { return count; } +template +int SafeWriter::writeByte(T* what, size_t count, unsigned char byte, Endianness endianness) { + if (byte==sizeof(T)) { + return write(what,count*byte); + } else { + if (!operative) return 0; + checkSize(count*byte); + int start,end,inc; + switch (endianness) { + case BigEndian: + start=byte-1; + end=-1; + inc=-1; + break; + case LittleEndian: + default: + start=0; + end=byte; + inc=1; + break; + } + for (int c=0; c>(byte<<3))&0xff; + } + } + count*=byte; + if (curSeek>len) len=curSeek; + } + return count; +} + int SafeWriter::writeC(signed char val) { return write(&val,1); } diff --git a/src/engine/safeWriter.h b/src/engine/safeWriter.h index 9072c61d7..c4920378a 100644 --- a/src/engine/safeWriter.h +++ b/src/engine/safeWriter.h @@ -43,6 +43,7 @@ class SafeWriter { size_t size(); int write(const void* what, size_t count); + template int writeByte(T* what, size_t count, unsigned char byte=sizeof(T), Endianness endianness=LittleEndian); int writeC(signed char val); int writeS(short val); diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 14ca65b40..1f5d9e914 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -176,6 +176,15 @@ const char* dualWSEffects[7]={ "Phase (dual)", }; +const char* macroAbsoluteMode[2]={ + "Relative", + "Absolute", +}; + +const char* macroDummyMode[2]={ + "empty", +}; + String macroHoverNote(int id, float val) { if (val<-60 || val>=120) return "???"; return fmt::sprintf("%d: %s",id,noteNames[(int)val+60]); @@ -924,8 +933,8 @@ void FurnaceGUI::drawFMEnv(unsigned char tl, unsigned char ar, unsigned char dr, ImVec2 posREnd=ImLerp(rect.Min,rect.Max,ImVec2(rrPos,1.0));//release end ImVec2 posSLineHEnd=ImLerp(rect.Min,rect.Max,ImVec2(1.0,(float)((tl/maxTl)+(sl/15.0)-((tl/maxTl)*(sl/15.0))))); //sustain horizontal line end ImVec2 posSLineVEnd=ImLerp(rect.Min,rect.Max,ImVec2(drPos,1.0)); //sustain vertical line end - ImVec2 posDecayRate0Pt=ImLerp(rect.Min,rect.Max,ImVec2(1.0,(tl/maxTl))); //Heght of the peak of AR, forever - ImVec2 posDecay2Rate0Pt=ImLerp(rect.Min,rect.Max,ImVec2(1.0,(float)((tl/maxTl)+(sl/15.0)-((tl/maxTl)*(sl/15.0))))); //Heght of the peak of SR, forever + ImVec2 posDecayRate0Pt=ImLerp(rect.Min,rect.Max,ImVec2(1.0,(tl/maxTl))); //Height of the peak of AR, forever + ImVec2 posDecay2Rate0Pt=ImLerp(rect.Min,rect.Max,ImVec2(1.0,(float)((tl/maxTl)+(sl/15.0)-((tl/maxTl)*(sl/15.0))))); //Height of the peak of SR, forever //dl->Flags=ImDrawListFlags_AntiAliasedLines|ImDrawListFlags_AntiAliasedLinesUseTex; if (ar==0.0) { //if AR = 0, the envelope never starts @@ -963,7 +972,7 @@ void FurnaceGUI::drawFMEnv(unsigned char tl, unsigned char ar, unsigned char dr, #define PARAMETER MARK_MODIFIED; e->notifyInsChange(curIns); -#define NORMAL_MACRO(macro,macroLen,macroLoop,macroRel,macroMin,macroHeight,macroName,displayName,displayHeight,displayLoop,bitfield,bfVal,drawSlider,sliderVal,sliderLow,macroDispMin,bitOff,macroMode,macroColor,mmlStr,macroAMin,macroAMax,hoverFunc,blockMode) \ +#define NORMAL_MACRO(macro,macroMin,macroHeight,macroName,displayName,displayHeight,displayLoop,bitfield,bfVal,drawSlider,sliderVal,sliderLow,macroDispMin,bitOff,macroMode,macroModeMax,displayModeName,macroColor,mmlStr,macroAMin,macroAMax,hoverFunc,blockMode) \ ImGui::TableNextRow(); \ ImGui::TableNextColumn(); \ ImGui::Text("%s",displayName); \ @@ -973,26 +982,41 @@ void FurnaceGUI::drawFMEnv(unsigned char tl, unsigned char ar, unsigned char dr, } \ if (displayLoop) { \ ImGui::SetNextItemWidth(lenAvail); \ - if (ImGui::InputScalar("##IMacroLen_" macroName,ImGuiDataType_U8,¯oLen,&_ONE,&_THREE)) { MARK_MODIFIED \ - if (macroLen>127) macroLen=127; \ + if (ImGui::InputScalar("##IMacroLen_" macroName,ImGuiDataType_U8,¯o.len,&_ONE,&_THREE)) { MARK_MODIFIED \ + if (macro.len>127) macro.len=127; \ } \ - if (macroMode!=NULL) { \ - ImGui::Checkbox("Fixed##IMacroMode_" macroName,macroMode); \ + if (macroMode) { \ + String modeName; \ + if (macro.mode>macroModeMax) { \ + modeName="none selected"; \ + } else { \ + modeName=displayModeName[macro.mode]; \ + } \ + if (ImGui::BeginCombo("Macro Mode",modeName.c_str())) { \ + String id; \ + for (unsigned int i=0; i<=macroModeMax; i++) { \ + id=fmt::sprintf("%d: %s",i,displayModeName[i]); \ + if (ImGui::Selectable(id.c_str(),macro.mode==i)) { PARAMETER \ + macro.mode=i; \ + } \ + } \ + ImGui::EndCombo(); \ + } \ } \ } \ ImGui::TableNextColumn(); \ for (int j=0; j<256; j++) { \ - if (j+macroDragScroll>=macroLen) { \ + if (j+macroDragScroll>=macro.len) { \ asFloat[j]=0; \ asInt[j]=0; \ } else { \ - asFloat[j]=macro[j+macroDragScroll]+macroDispMin; \ - asInt[j]=macro[j+macroDragScroll]+macroDispMin+bitOff; \ + asFloat[j]=macro.val[j+macroDragScroll]+macroDispMin; \ + asInt[j]=macro.val[j+macroDragScroll]+macroDispMin+bitOff; \ } \ - if (j+macroDragScroll>=macroLen || (j+macroDragScroll>macroRel && macroLoop=macro.len || (j+macroDragScroll>macro.rel && macro.loop=macroLoop))|((macroRel!=-1 && (j+macroDragScroll)==macroRel)<<1); \ + loopIndicator[j]=((macro.loop!=-1 && (j+macroDragScroll)>=macro.loop))|((macro.rel!=-1 && (j+macroDragScroll)==macro.rel)<<1); \ } \ } \ ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,ImVec2(0.0f,0.0f)); \ @@ -1000,7 +1024,7 @@ void FurnaceGUI::drawFMEnv(unsigned char tl, unsigned char ar, unsigned char dr, if (bitfield) { \ PlotBitfield("##IMacro_" macroName,asInt,totalFit,0,bfVal,macroHeight,ImVec2(availableWidth,(displayLoop)?(displayHeight*dpiScale):(32.0f*dpiScale))); \ } else { \ - PlotCustom("##IMacro_" macroName,asFloat,totalFit,macroDragScroll,NULL,macroDispMin+macroMin,macroHeight+macroDispMin,ImVec2(availableWidth,(displayLoop)?(displayHeight*dpiScale):(32.0f*dpiScale)),sizeof(float),macroColor,macroLen-macroDragScroll,hoverFunc,blockMode); \ + PlotCustom("##IMacro_" macroName,asFloat,totalFit,macroDragScroll,NULL,macroDispMin+macroMin,macroHeight+macroDispMin,ImVec2(availableWidth,(displayLoop)?(displayHeight*dpiScale):(32.0f*dpiScale)),sizeof(float),macroColor,macro.len-macroDragScroll,hoverFunc,blockMode); \ } \ if (displayLoop && ImGui::IsItemClicked(ImGuiMouseButton_Left)) { \ macroDragStart=ImGui::GetItemRectMin(); \ @@ -1013,7 +1037,7 @@ void FurnaceGUI::drawFMEnv(unsigned char tl, unsigned char ar, unsigned char dr, macroDragInitialValue=false; \ macroDragLen=totalFit; \ macroDragActive=true; \ - macroDragTarget=macro; \ + macroDragTarget=macro.val; \ macroDragChar=false; \ processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); \ } \ @@ -1022,63 +1046,81 @@ void FurnaceGUI::drawFMEnv(unsigned char tl, unsigned char ar, unsigned char dr, ImGui::SameLine(); \ CWVSliderInt("##IArpMacroPos",ImVec2(20.0f*dpiScale,displayHeight*dpiScale),sliderVal,sliderLow,70); \ } \ - PlotCustom("##IMacroLoop_" macroName,loopIndicator,totalFit,macroDragScroll,NULL,0,2,ImVec2(availableWidth,12.0f*dpiScale),sizeof(float),macroColor,macroLen-macroDragScroll,¯oHoverLoop); \ + PlotCustom("##IMacroLoop_" macroName,loopIndicator,totalFit,macroDragScroll,NULL,0,2,ImVec2(availableWidth,12.0f*dpiScale),sizeof(float),macroColor,macro.len-macroDragScroll,¯oHoverLoop); \ if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { \ macroLoopDragStart=ImGui::GetItemRectMin(); \ macroLoopDragAreaSize=ImVec2(availableWidth,12.0f*dpiScale); \ macroLoopDragLen=totalFit; \ if (ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) { \ - macroLoopDragTarget=¯oRel; \ + macroLoopDragTarget=¯o.rel; \ } else { \ - macroLoopDragTarget=¯oLoop; \ + macroLoopDragTarget=¯o.loop; \ } \ macroLoopDragActive=true; \ processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); \ } \ if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { \ if (ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) { \ - macroRel=-1; \ + macro.rel=-1; \ } else { \ - macroLoop=-1; \ + macro.loop=-1; \ } \ } \ ImGui::SetNextItemWidth(availableWidth); \ if (ImGui::InputText("##IMacroMML_" macroName,&mmlStr)) { \ - decodeMMLStr(mmlStr,macro,macroLen,macroLoop,macroAMin,(bitfield)?((1<127) macroLen=127; \ + if (ImGui::InputScalar("##IOPMacro.len_" #op macroName,ImGuiDataType_U8,¯o.len,&_ONE,&_THREE)) { MARK_MODIFIED \ + if (macro.len>127) macro.len=127; \ + } \ + if (macroMode) { \ + String modeName; \ + if (macro.mode>macroModeMax) { \ + modeName="none selected"; \ + } else { \ + modeName=displayModeName[macro.mode]; \ + } \ + if (ImGui::BeginCombo("Macro Mode",modeName.c_str())) { \ + String id; \ + for (unsigned int i=0; i<=macroModeMax; i++) { \ + id=fmt::sprintf("%d: %s",i,displayModeName[i]); \ + if (ImGui::Selectable(id.c_str(),macro.mode==i)) { PARAMETER \ + macro.mode=i; \ + } \ + } \ + ImGui::EndCombo(); \ + } \ } \ } \ ImGui::TableNextColumn(); \ for (int j=0; j<256; j++) { \ - if (j+macroDragScroll>=macroLen) { \ + if (j+macroDragScroll>=macro.len) { \ asFloat[j]=0; \ asInt[j]=0; \ } else { \ - asFloat[j]=macro[j+macroDragScroll]; \ - asInt[j]=macro[j+macroDragScroll]; \ + asFloat[j]=macro.val[j+macroDragScroll]; \ + asInt[j]=macro.val[j+macroDragScroll]; \ } \ - if (j+macroDragScroll>=macroLen || (j+macroDragScroll>macroRel && macroLoop=macro.len || (j+macroDragScroll>macro.rel && macro.loop=macroLoop))|((macroRel!=-1 && (j+macroDragScroll)==macroRel)<<1); \ + loopIndicator[j]=((macro.loop!=-1 && (j+macroDragScroll)>=macro.loop))|((macro.rel!=-1 && (j+macroDragScroll)==macro.rel)<<1); \ } \ } \ ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,ImVec2(0.0f,0.0f)); \ @@ -1086,7 +1128,7 @@ void FurnaceGUI::drawFMEnv(unsigned char tl, unsigned char ar, unsigned char dr, if (bitfield) { \ PlotBitfield("##IOPMacro_" #op macroName,asInt,totalFit,0,bfVal,macroHeight,ImVec2(availableWidth,displayLoop?(displayHeight*dpiScale):(24*dpiScale))); \ } else { \ - PlotCustom("##IOPMacro_" #op macroName,asFloat,totalFit,macroDragScroll,NULL,0,macroHeight,ImVec2(availableWidth,displayLoop?(displayHeight*dpiScale):(24*dpiScale)),sizeof(float),uiColors[GUI_COLOR_MACRO_OTHER],macroLen-macroDragScroll); \ + PlotCustom("##IOPMacro_" #op macroName,asFloat,totalFit,macroDragScroll,NULL,0,macroHeight,ImVec2(availableWidth,displayLoop?(displayHeight*dpiScale):(24*dpiScale)),sizeof(float),uiColors[GUI_COLOR_MACRO_OTHER],macro.len-macroDragScroll); \ } \ if (displayLoop && ImGui::IsItemClicked(ImGuiMouseButton_Left)) { \ macroDragStart=ImGui::GetItemRectMin(); \ @@ -1099,37 +1141,37 @@ void FurnaceGUI::drawFMEnv(unsigned char tl, unsigned char ar, unsigned char dr, macroDragInitialValue=false; \ macroDragLen=totalFit; \ macroDragActive=true; \ - macroDragCTarget=macro; \ - macroDragChar=true; \ + macroDragTarget=macro.val; \ + macroDragChar=false; \ processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); \ } \ if (displayLoop) { \ - PlotCustom("##IOPMacroLoop_" #op macroName,loopIndicator,totalFit,macroDragScroll,NULL,0,2,ImVec2(availableWidth,12.0f*dpiScale),sizeof(float),uiColors[GUI_COLOR_MACRO_OTHER],macroLen-macroDragScroll,¯oHoverLoop); \ + PlotCustom("##IOPMacro.loop_" #op macroName,loopIndicator,totalFit,macroDragScroll,NULL,0,2,ImVec2(availableWidth,12.0f*dpiScale),sizeof(float),uiColors[GUI_COLOR_MACRO_OTHER],macro.len-macroDragScroll,¯oHoverLoop); \ if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { \ macroLoopDragStart=ImGui::GetItemRectMin(); \ macroLoopDragAreaSize=ImVec2(availableWidth,8.0f*dpiScale); \ macroLoopDragLen=totalFit; \ if (ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) { \ - macroLoopDragTarget=¯oRel; \ + macroLoopDragTarget=¯o.rel; \ } else { \ - macroLoopDragTarget=¯oLoop; \ + macroLoopDragTarget=¯o.loop; \ } \ macroLoopDragActive=true; \ processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); \ } \ if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { \ if (ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) { \ - macroRel=-1; \ + macro.rel=-1; \ } else { \ - macroLoop=-1; \ + macro.loop=-1; \ } \ } \ ImGui::SetNextItemWidth(availableWidth); \ if (ImGui::InputText("##IOPMacroMML_" macroName,&mmlStr)) { \ - decodeMMLStr(mmlStr,macro,macroLen,macroLoop,0,bitfield?((1<type==DIV_INS_OPLL) { - NORMAL_MACRO(ins->std.algMacro,ins->std.algMacroLen,ins->std.algMacroLoop,ins->std.algMacroRel,0,1,"alg",FM_NAME(FM_SUS),32,ins->std.algMacroOpen,true,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[0],0,1,NULL,false); - NORMAL_MACRO(ins->std.fbMacro,ins->std.fbMacroLen,ins->std.fbMacroLoop,ins->std.fbMacroRel,0,7,"fb",FM_NAME(FM_FB),96,ins->std.fbMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[1],0,7,NULL,false); - NORMAL_MACRO(ins->std.fmsMacro,ins->std.fmsMacroLen,ins->std.fmsMacroLoop,ins->std.fmsMacroRel,0,1,"fms",FM_NAME(FM_DC),32,ins->std.fmsMacroOpen,true,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[2],0,1,NULL,false); - NORMAL_MACRO(ins->std.amsMacro,ins->std.amsMacroLen,ins->std.amsMacroLoop,ins->std.amsMacroRel,0,1,"ams",FM_NAME(FM_DM),32,ins->std.amsMacroOpen,true,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[3],0,1,NULL,false); + NORMAL_MACRO(ins->std.algMacro,0,1,"alg",FM_NAME(FM_SUS),32,ins->std.algMacro.open,true,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[0],0,1,NULL,false); + NORMAL_MACRO(ins->std.fbMacro,0,7,"fb",FM_NAME(FM_FB),96,ins->std.fbMacro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[1],0,7,NULL,false); + NORMAL_MACRO(ins->std.fmsMacro,0,1,"fms",FM_NAME(FM_DC),32,ins->std.fmsMacro.open,true,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[2],0,1,NULL,false); + NORMAL_MACRO(ins->std.amsMacro,0,1,"ams",FM_NAME(FM_DM),32,ins->std.amsMacro.open,true,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[3],0,1,NULL,false); } else { - NORMAL_MACRO(ins->std.algMacro,ins->std.algMacroLen,ins->std.algMacroLoop,ins->std.algMacroRel,0,7,"alg",FM_NAME(FM_ALG),96,ins->std.algMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[0],0,7,NULL,false); - NORMAL_MACRO(ins->std.fbMacro,ins->std.fbMacroLen,ins->std.fbMacroLoop,ins->std.fbMacroRel,0,7,"fb",FM_NAME(FM_FB),96,ins->std.fbMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[1],0,7,NULL,false); + NORMAL_MACRO(ins->std.algMacro,0,7,"alg",FM_NAME(FM_ALG),96,ins->std.algMacro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[0],0,7,NULL,false); + NORMAL_MACRO(ins->std.fbMacro,0,7,"fb",FM_NAME(FM_FB),96,ins->std.fbMacro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[1],0,7,NULL,false); if (ins->type!=DIV_INS_OPL) { - NORMAL_MACRO(ins->std.fmsMacro,ins->std.fmsMacroLen,ins->std.fmsMacroLoop,ins->std.fmsMacroRel,0,7,"fms",FM_NAME(FM_FMS),96,ins->std.fmsMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[2],0,7,NULL,false); - NORMAL_MACRO(ins->std.amsMacro,ins->std.amsMacroLen,ins->std.amsMacroLoop,ins->std.amsMacroRel,0,3,"ams",FM_NAME(FM_AMS),48,ins->std.amsMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[3],0,3,NULL,false); + if (ins->type==DIV_INS_OPZ) { + NORMAL_MACRO(ins->std.fmsMacro,0,7,"fms",FM_NAME(FM_FMS),96,ins->std.fmsMacro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[2],0,7,NULL,false); + NORMAL_MACRO(ins->std.fms2Macro,0,7,"fms2",FM_NAME(FM_FMS2),96,ins->std.fms2Macro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[3],0,7,NULL,false); + NORMAL_MACRO(ins->std.amsMacro,0,3,"ams",FM_NAME(FM_AMS),48,ins->std.amsMacro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,3,NULL,false); + NORMAL_MACRO(ins->std.ams2Macro,0,3,"ams2",FM_NAME(FM_AMS2),48,ins->std.ams2Macro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[5],0,3,NULL,false); + } else { + NORMAL_MACRO(ins->std.fmsMacro,0,7,"fms",FM_NAME(FM_FMS),96,ins->std.fmsMacro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[2],0,7,NULL,false); + NORMAL_MACRO(ins->std.amsMacro,0,3,"ams",FM_NAME(FM_AMS),48,ins->std.amsMacro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[3],0,3,NULL,false); + } } } if (ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ) { - NORMAL_MACRO(ins->std.ex1Macro,ins->std.ex1MacroLen,ins->std.ex1MacroLoop,ins->std.ex1MacroRel,0,127,"ex1","AM Depth",128,ins->std.ex1MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,127,NULL,false); - NORMAL_MACRO(ins->std.ex2Macro,ins->std.ex2MacroLen,ins->std.ex2MacroLoop,ins->std.ex2MacroRel,0,127,"ex2","PM Depth",128,ins->std.ex2MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[5],0,127,NULL,false); - NORMAL_MACRO(ins->std.ex3Macro,ins->std.ex3MacroLen,ins->std.ex3MacroLoop,ins->std.ex3MacroRel,0,255,"ex3","LFO Speed",128,ins->std.ex3MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[6],0,255,NULL,false); - NORMAL_MACRO(ins->std.waveMacro,ins->std.waveMacroLen,ins->std.waveMacroLoop,ins->std.waveMacroRel,0,3,"wave","LFO Shape",48,ins->std.waveMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_WAVE],mmlString[7],0,3,¯oLFOWaves,false); + NORMAL_MACRO(ins->std.ex1Macro,0,127,"ex1","AM Depth",128,ins->std.ex1Macro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[6],0,127,NULL,false); + NORMAL_MACRO(ins->std.ex2Macro,0,127,"ex2","PM Depth",128,ins->std.ex2Macro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[7],0,127,NULL,false); + NORMAL_MACRO(ins->std.ex3Macro,0,255,"ex3","LFO Speed",128,ins->std.ex3Macro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[8],0,255,NULL,false); + NORMAL_MACRO(ins->std.waveMacro,0,3,"wave","LFO Shape",48,ins->std.waveMacro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_WAVE],mmlString[9],0,3,¯oLFOWaves,false); } MACRO_END; ImGui::EndTabItem(); @@ -2041,45 +2090,45 @@ void FurnaceGUI::drawInsEdit() { int maxArDr=(ins->type==DIV_INS_FM || ins->type==DIV_INS_OPZ)?31:15; if (ins->type==DIV_INS_OPL) { - OP_MACRO(ins->std.opMacros[ordi].tlMacro,ins->std.opMacros[ordi].tlMacroLen,ins->std.opMacros[ordi].tlMacroLoop,ins->std.opMacros[ordi].tlMacroRel,maxTl,ordi,"tl",FM_NAME(FM_TL),128,ins->std.opMacros[ordi].tlMacroOpen,false,NULL,mmlString[0]); - OP_MACRO(ins->std.opMacros[ordi].arMacro,ins->std.opMacros[ordi].arMacroLen,ins->std.opMacros[ordi].arMacroLoop,ins->std.opMacros[ordi].arMacroRel,maxArDr,ordi,"ar",FM_NAME(FM_AR),64,ins->std.opMacros[ordi].arMacroOpen,false,NULL,mmlString[1]); - OP_MACRO(ins->std.opMacros[ordi].drMacro,ins->std.opMacros[ordi].drMacroLen,ins->std.opMacros[ordi].drMacroLoop,ins->std.opMacros[ordi].drMacroRel,maxArDr,ordi,"dr",FM_NAME(FM_DR),64,ins->std.opMacros[ordi].drMacroOpen,false,NULL,mmlString[2]); - OP_MACRO(ins->std.opMacros[ordi].slMacro,ins->std.opMacros[ordi].slMacroLen,ins->std.opMacros[ordi].slMacroLoop,ins->std.opMacros[ordi].slMacroRel,15,ordi,"sl",FM_NAME(FM_SL),64,ins->std.opMacros[ordi].slMacroOpen,false,NULL,mmlString[5]); - OP_MACRO(ins->std.opMacros[ordi].rrMacro,ins->std.opMacros[ordi].rrMacroLen,ins->std.opMacros[ordi].rrMacroLoop,ins->std.opMacros[ordi].rrMacroRel,15,ordi,"rr",FM_NAME(FM_RR),64,ins->std.opMacros[ordi].rrMacroOpen,false,NULL,mmlString[4]); - OP_MACRO(ins->std.opMacros[ordi].kslMacro,ins->std.opMacros[ordi].kslMacroLen,ins->std.opMacros[ordi].kslMacroLoop,ins->std.opMacros[ordi].kslMacroRel,3,ordi,"ksl",FM_NAME(FM_KSL),32,ins->std.opMacros[ordi].kslMacroOpen,false,NULL,mmlString[6]); - OP_MACRO(ins->std.opMacros[ordi].multMacro,ins->std.opMacros[ordi].multMacroLen,ins->std.opMacros[ordi].multMacroLoop,ins->std.opMacros[ordi].multMacroRel,15,ordi,"mult",FM_NAME(FM_MULT),64,ins->std.opMacros[ordi].multMacroOpen,false,NULL,mmlString[7]); - OP_MACRO(ins->std.opMacros[ordi].wsMacro,ins->std.opMacros[ordi].wsMacroLen,ins->std.opMacros[ordi].wsMacroLoop,ins->std.opMacros[ordi].wsMacroRel,7,ordi,"ws",FM_NAME(FM_WS),64,ins->std.opMacros[ordi].wsMacroOpen,false,NULL,mmlString[8]); + OP_MACRO(ins->std.opMacros[ordi].tlMacro,maxTl,ordi,"tl",FM_NAME(FM_TL),128,ins->std.opMacros[ordi].tlMacro.open,false,NULL,false,0,macroDummyMode,mmlString[0]); + OP_MACRO(ins->std.opMacros[ordi].arMacro,maxArDr,ordi,"ar",FM_NAME(FM_AR),64,ins->std.opMacros[ordi].arMacro.open,false,NULL,false,0,macroDummyMode,mmlString[1]); + OP_MACRO(ins->std.opMacros[ordi].drMacro,maxArDr,ordi,"dr",FM_NAME(FM_DR),64,ins->std.opMacros[ordi].drMacro.open,false,NULL,false,0,macroDummyMode,mmlString[2]); + OP_MACRO(ins->std.opMacros[ordi].slMacro,15,ordi,"sl",FM_NAME(FM_SL),64,ins->std.opMacros[ordi].slMacro.open,false,NULL,false,0,macroDummyMode,mmlString[5]); + OP_MACRO(ins->std.opMacros[ordi].rrMacro,15,ordi,"rr",FM_NAME(FM_RR),64,ins->std.opMacros[ordi].rrMacro.open,false,NULL,false,0,macroDummyMode,mmlString[4]); + OP_MACRO(ins->std.opMacros[ordi].kslMacro,3,ordi,"ksl",FM_NAME(FM_KSL),32,ins->std.opMacros[ordi].kslMacro.open,false,NULL,false,0,macroDummyMode,mmlString[6]); + OP_MACRO(ins->std.opMacros[ordi].multMacro,15,ordi,"mult",FM_NAME(FM_MULT),64,ins->std.opMacros[ordi].multMacro.open,false,NULL,false,0,macroDummyMode,mmlString[7]); + OP_MACRO(ins->std.opMacros[ordi].wsMacro,7,ordi,"ws",FM_NAME(FM_WS),64,ins->std.opMacros[ordi].wsMacro.open,false,NULL,false,0,macroDummyMode,mmlString[8]); - OP_MACRO(ins->std.opMacros[ordi].amMacro,ins->std.opMacros[ordi].amMacroLen,ins->std.opMacros[ordi].amMacroLoop,ins->std.opMacros[ordi].amMacroRel,1,ordi,"am",FM_NAME(FM_AM),32,ins->std.opMacros[ordi].amMacroOpen,true,NULL,mmlString[9]); - OP_MACRO(ins->std.opMacros[ordi].vibMacro,ins->std.opMacros[ordi].vibMacroLen,ins->std.opMacros[ordi].vibMacroLoop,ins->std.opMacros[ordi].vibMacroRel,1,ordi,"vib",FM_NAME(FM_VIB),32,ins->std.opMacros[ordi].vibMacroOpen,true,NULL,mmlString[10]); - OP_MACRO(ins->std.opMacros[ordi].ksrMacro,ins->std.opMacros[ordi].ksrMacroLen,ins->std.opMacros[ordi].ksrMacroLoop,ins->std.opMacros[ordi].ksrMacroRel,1,ordi,"ksr",FM_NAME(FM_KSR),32,ins->std.opMacros[ordi].ksrMacroOpen,true,NULL,mmlString[11]); - OP_MACRO(ins->std.opMacros[ordi].susMacro,ins->std.opMacros[ordi].susMacroLen,ins->std.opMacros[ordi].susMacroLoop,ins->std.opMacros[ordi].susMacroRel,1,ordi,"sus",FM_NAME(FM_SUS),32,ins->std.opMacros[ordi].susMacroOpen,true,NULL,mmlString[12]); + OP_MACRO(ins->std.opMacros[ordi].amMacro,1,ordi,"am",FM_NAME(FM_AM),32,ins->std.opMacros[ordi].amMacro.open,true,NULL,false,0,macroDummyMode,mmlString[9]); + OP_MACRO(ins->std.opMacros[ordi].vibMacro,1,ordi,"vib",FM_NAME(FM_VIB),32,ins->std.opMacros[ordi].vibMacro.open,true,NULL,false,0,macroDummyMode,mmlString[10]); + OP_MACRO(ins->std.opMacros[ordi].ksrMacro,1,ordi,"ksr",FM_NAME(FM_KSR),32,ins->std.opMacros[ordi].ksrMacro.open,true,NULL,false,0,macroDummyMode,mmlString[11]); + OP_MACRO(ins->std.opMacros[ordi].susMacro,1,ordi,"sus",FM_NAME(FM_SUS),32,ins->std.opMacros[ordi].susMacro.open,true,NULL,false,0,macroDummyMode,mmlString[12]); } else if (ins->type==DIV_INS_OPLL) { - OP_MACRO(ins->std.opMacros[ordi].tlMacro,ins->std.opMacros[ordi].tlMacroLen,ins->std.opMacros[ordi].tlMacroLoop,ins->std.opMacros[ordi].tlMacroRel,maxTl,ordi,"tl",FM_NAME(FM_TL),128,ins->std.opMacros[ordi].tlMacroOpen,false,NULL,mmlString[0]); - OP_MACRO(ins->std.opMacros[ordi].arMacro,ins->std.opMacros[ordi].arMacroLen,ins->std.opMacros[ordi].arMacroLoop,ins->std.opMacros[ordi].arMacroRel,maxArDr,ordi,"ar",FM_NAME(FM_AR),64,ins->std.opMacros[ordi].arMacroOpen,false,NULL,mmlString[1]); - OP_MACRO(ins->std.opMacros[ordi].drMacro,ins->std.opMacros[ordi].drMacroLen,ins->std.opMacros[ordi].drMacroLoop,ins->std.opMacros[ordi].drMacroRel,maxArDr,ordi,"dr",FM_NAME(FM_DR),64,ins->std.opMacros[ordi].drMacroOpen,false,NULL,mmlString[2]); - OP_MACRO(ins->std.opMacros[ordi].slMacro,ins->std.opMacros[ordi].slMacroLen,ins->std.opMacros[ordi].slMacroLoop,ins->std.opMacros[ordi].slMacroRel,15,ordi,"sl",FM_NAME(FM_SL),64,ins->std.opMacros[ordi].slMacroOpen,false,NULL,mmlString[5]); - OP_MACRO(ins->std.opMacros[ordi].rrMacro,ins->std.opMacros[ordi].rrMacroLen,ins->std.opMacros[ordi].rrMacroLoop,ins->std.opMacros[ordi].rrMacroRel,15,ordi,"rr",FM_NAME(FM_RR),64,ins->std.opMacros[ordi].rrMacroOpen,false,NULL,mmlString[4]); - OP_MACRO(ins->std.opMacros[ordi].kslMacro,ins->std.opMacros[ordi].kslMacroLen,ins->std.opMacros[ordi].kslMacroLoop,ins->std.opMacros[ordi].kslMacroRel,3,ordi,"ksl",FM_NAME(FM_KSL),32,ins->std.opMacros[ordi].kslMacroOpen,false,NULL,mmlString[6]); - OP_MACRO(ins->std.opMacros[ordi].multMacro,ins->std.opMacros[ordi].multMacroLen,ins->std.opMacros[ordi].multMacroLoop,ins->std.opMacros[ordi].multMacroRel,15,ordi,"mult",FM_NAME(FM_MULT),64,ins->std.opMacros[ordi].multMacroOpen,false,NULL,mmlString[7]); + OP_MACRO(ins->std.opMacros[ordi].tlMacro,maxTl,ordi,"tl",FM_NAME(FM_TL),128,ins->std.opMacros[ordi].tlMacro.open,false,NULL,false,0,macroDummyMode,mmlString[0]); + OP_MACRO(ins->std.opMacros[ordi].arMacro,maxArDr,ordi,"ar",FM_NAME(FM_AR),64,ins->std.opMacros[ordi].arMacro.open,false,NULL,false,0,macroDummyMode,mmlString[1]); + OP_MACRO(ins->std.opMacros[ordi].drMacro,maxArDr,ordi,"dr",FM_NAME(FM_DR),64,ins->std.opMacros[ordi].drMacro.open,false,NULL,false,0,macroDummyMode,mmlString[2]); + OP_MACRO(ins->std.opMacros[ordi].slMacro,15,ordi,"sl",FM_NAME(FM_SL),64,ins->std.opMacros[ordi].slMacro.open,false,NULL,false,0,macroDummyMode,mmlString[5]); + OP_MACRO(ins->std.opMacros[ordi].rrMacro,15,ordi,"rr",FM_NAME(FM_RR),64,ins->std.opMacros[ordi].rrMacro.open,false,NULL,false,0,macroDummyMode,mmlString[4]); + OP_MACRO(ins->std.opMacros[ordi].kslMacro,3,ordi,"ksl",FM_NAME(FM_KSL),32,ins->std.opMacros[ordi].kslMacro.open,false,NULL,false,0,macroDummyMode,mmlString[6]); + OP_MACRO(ins->std.opMacros[ordi].multMacro,15,ordi,"mult",FM_NAME(FM_MULT),64,ins->std.opMacros[ordi].multMacro.open,false,NULL,false,0,macroDummyMode,mmlString[7]); - OP_MACRO(ins->std.opMacros[ordi].amMacro,ins->std.opMacros[ordi].amMacroLen,ins->std.opMacros[ordi].amMacroLoop,ins->std.opMacros[ordi].amMacroRel,1,ordi,"am",FM_NAME(FM_AM),32,ins->std.opMacros[ordi].amMacroOpen,true,NULL,mmlString[8]); - OP_MACRO(ins->std.opMacros[ordi].vibMacro,ins->std.opMacros[ordi].vibMacroLen,ins->std.opMacros[ordi].vibMacroLoop,ins->std.opMacros[ordi].vibMacroRel,1,ordi,"vib",FM_NAME(FM_VIB),32,ins->std.opMacros[ordi].vibMacroOpen,true,NULL,mmlString[9]); - OP_MACRO(ins->std.opMacros[ordi].ksrMacro,ins->std.opMacros[ordi].ksrMacroLen,ins->std.opMacros[ordi].ksrMacroLoop,ins->std.opMacros[ordi].ksrMacroRel,1,ordi,"ksr",FM_NAME(FM_KSR),32,ins->std.opMacros[ordi].ksrMacroOpen,true,NULL,mmlString[10]); - OP_MACRO(ins->std.opMacros[ordi].egtMacro,ins->std.opMacros[ordi].egtMacroLen,ins->std.opMacros[ordi].egtMacroLoop,ins->std.opMacros[ordi].egtMacroRel,1,ordi,"egt",FM_NAME(FM_EGS),32,ins->std.opMacros[ordi].egtMacroOpen,true,NULL,mmlString[11]); + OP_MACRO(ins->std.opMacros[ordi].amMacro,1,ordi,"am",FM_NAME(FM_AM),32,ins->std.opMacros[ordi].amMacro.open,true,NULL,false,0,macroDummyMode,mmlString[8]); + OP_MACRO(ins->std.opMacros[ordi].vibMacro,1,ordi,"vib",FM_NAME(FM_VIB),32,ins->std.opMacros[ordi].vibMacro.open,true,NULL,false,0,macroDummyMode,mmlString[9]); + OP_MACRO(ins->std.opMacros[ordi].ksrMacro,1,ordi,"ksr",FM_NAME(FM_KSR),32,ins->std.opMacros[ordi].ksrMacro.open,true,NULL,false,0,macroDummyMode,mmlString[10]); + OP_MACRO(ins->std.opMacros[ordi].egtMacro,1,ordi,"egt",FM_NAME(FM_EGS),32,ins->std.opMacros[ordi].egtMacro.open,true,NULL,false,0,macroDummyMode,mmlString[11]); } else { - OP_MACRO(ins->std.opMacros[ordi].tlMacro,ins->std.opMacros[ordi].tlMacroLen,ins->std.opMacros[ordi].tlMacroLoop,ins->std.opMacros[ordi].tlMacroRel,maxTl,ordi,"tl",FM_NAME(FM_TL),128,ins->std.opMacros[ordi].tlMacroOpen,false,NULL,mmlString[0]); - OP_MACRO(ins->std.opMacros[ordi].arMacro,ins->std.opMacros[ordi].arMacroLen,ins->std.opMacros[ordi].arMacroLoop,ins->std.opMacros[ordi].arMacroRel,maxArDr,ordi,"ar",FM_NAME(FM_AR),64,ins->std.opMacros[ordi].arMacroOpen,false,NULL,mmlString[1]); - OP_MACRO(ins->std.opMacros[ordi].drMacro,ins->std.opMacros[ordi].drMacroLen,ins->std.opMacros[ordi].drMacroLoop,ins->std.opMacros[ordi].drMacroRel,maxArDr,ordi,"dr",FM_NAME(FM_DR),64,ins->std.opMacros[ordi].drMacroOpen,false,NULL,mmlString[2]); - OP_MACRO(ins->std.opMacros[ordi].d2rMacro,ins->std.opMacros[ordi].d2rMacroLen,ins->std.opMacros[ordi].d2rMacroLoop,ins->std.opMacros[ordi].d2rMacroRel,31,ordi,"d2r",FM_NAME(FM_D2R),64,ins->std.opMacros[ordi].d2rMacroOpen,false,NULL,mmlString[3]); - OP_MACRO(ins->std.opMacros[ordi].rrMacro,ins->std.opMacros[ordi].rrMacroLen,ins->std.opMacros[ordi].rrMacroLoop,ins->std.opMacros[ordi].rrMacroRel,15,ordi,"rr",FM_NAME(FM_RR),64,ins->std.opMacros[ordi].rrMacroOpen,false,NULL,mmlString[4]); - OP_MACRO(ins->std.opMacros[ordi].slMacro,ins->std.opMacros[ordi].slMacroLen,ins->std.opMacros[ordi].slMacroLoop,ins->std.opMacros[ordi].slMacroRel,15,ordi,"sl",FM_NAME(FM_SL),64,ins->std.opMacros[ordi].slMacroOpen,false,NULL,mmlString[5]); - OP_MACRO(ins->std.opMacros[ordi].rsMacro,ins->std.opMacros[ordi].rsMacroLen,ins->std.opMacros[ordi].rsMacroLoop,ins->std.opMacros[ordi].rsMacroRel,3,ordi,"rs",FM_NAME(FM_RS),32,ins->std.opMacros[ordi].rsMacroOpen,false,NULL,mmlString[6]); - OP_MACRO(ins->std.opMacros[ordi].multMacro,ins->std.opMacros[ordi].multMacroLen,ins->std.opMacros[ordi].multMacroLoop,ins->std.opMacros[ordi].multMacroRel,15,ordi,"mult",FM_NAME(FM_MULT),64,ins->std.opMacros[ordi].multMacroOpen,false,NULL,mmlString[7]); - OP_MACRO(ins->std.opMacros[ordi].dtMacro,ins->std.opMacros[ordi].dtMacroLen,ins->std.opMacros[ordi].dtMacroLoop,ins->std.opMacros[ordi].dtMacroRel,7,ordi,"dt",FM_NAME(FM_DT),64,ins->std.opMacros[ordi].dtMacroOpen,false,NULL,mmlString[8]); - OP_MACRO(ins->std.opMacros[ordi].dt2Macro,ins->std.opMacros[ordi].dt2MacroLen,ins->std.opMacros[ordi].dt2MacroLoop,ins->std.opMacros[ordi].dt2MacroRel,3,ordi,"dt2",FM_NAME(FM_DT2),32,ins->std.opMacros[ordi].dt2MacroOpen,false,NULL,mmlString[9]); - OP_MACRO(ins->std.opMacros[ordi].amMacro,ins->std.opMacros[ordi].amMacroLen,ins->std.opMacros[ordi].amMacroLoop,ins->std.opMacros[ordi].amMacroRel,1,ordi,"am",FM_NAME(FM_AM),32,ins->std.opMacros[ordi].amMacroOpen,true,NULL,mmlString[10]); - OP_MACRO(ins->std.opMacros[ordi].ssgMacro,ins->std.opMacros[ordi].ssgMacroLen,ins->std.opMacros[ordi].ssgMacroLoop,ins->std.opMacros[ordi].ssgMacroRel,4,ordi,"ssg",FM_NAME(FM_SSG),64,ins->std.opMacros[ordi].ssgMacroOpen,true,ssgEnvBits,mmlString[11]); + OP_MACRO(ins->std.opMacros[ordi].tlMacro,maxTl,ordi,"tl",FM_NAME(FM_TL),128,ins->std.opMacros[ordi].tlMacro.open,false,NULL,false,0,macroDummyMode,mmlString[0]); + OP_MACRO(ins->std.opMacros[ordi].arMacro,maxArDr,ordi,"ar",FM_NAME(FM_AR),64,ins->std.opMacros[ordi].arMacro.open,false,NULL,false,0,macroDummyMode,mmlString[1]); + OP_MACRO(ins->std.opMacros[ordi].drMacro,maxArDr,ordi,"dr",FM_NAME(FM_DR),64,ins->std.opMacros[ordi].drMacro.open,false,NULL,false,0,macroDummyMode,mmlString[2]); + OP_MACRO(ins->std.opMacros[ordi].d2rMacro,31,ordi,"d2r",FM_NAME(FM_D2R),64,ins->std.opMacros[ordi].d2rMacro.open,false,NULL,false,0,macroDummyMode,mmlString[3]); + OP_MACRO(ins->std.opMacros[ordi].rrMacro,15,ordi,"rr",FM_NAME(FM_RR),64,ins->std.opMacros[ordi].rrMacro.open,false,NULL,false,0,macroDummyMode,mmlString[4]); + OP_MACRO(ins->std.opMacros[ordi].slMacro,15,ordi,"sl",FM_NAME(FM_SL),64,ins->std.opMacros[ordi].slMacro.open,false,NULL,false,0,macroDummyMode,mmlString[5]); + OP_MACRO(ins->std.opMacros[ordi].rsMacro,3,ordi,"rs",FM_NAME(FM_RS),32,ins->std.opMacros[ordi].rsMacro.open,false,NULL,false,0,macroDummyMode,mmlString[6]); + OP_MACRO(ins->std.opMacros[ordi].multMacro,15,ordi,"mult",FM_NAME(FM_MULT),64,ins->std.opMacros[ordi].multMacro.open,false,NULL,false,0,macroDummyMode,mmlString[7]); + OP_MACRO(ins->std.opMacros[ordi].dtMacro,7,ordi,"dt",FM_NAME(FM_DT),64,ins->std.opMacros[ordi].dtMacro.open,false,NULL,false,0,macroDummyMode,mmlString[8]); + OP_MACRO(ins->std.opMacros[ordi].dt2Macro,3,ordi,"dt2",FM_NAME(FM_DT2),32,ins->std.opMacros[ordi].dt2Macro.open,false,NULL,false,0,macroDummyMode,mmlString[9]); + OP_MACRO(ins->std.opMacros[ordi].amMacro,1,ordi,"am",FM_NAME(FM_AM),32,ins->std.opMacros[ordi].amMacro.open,true,NULL,false,0,macroDummyMode,mmlString[10]); + OP_MACRO(ins->std.opMacros[ordi].ssgMacro,4,ordi,"ssg",FM_NAME(FM_SSG),64,ins->std.opMacros[ordi].ssgMacro.open,true,ssgEnvBits,false,0,macroDummyMode,mmlString[11]); } MACRO_END; ImGui::PopID(); @@ -2182,7 +2231,10 @@ void FurnaceGUI::drawInsEdit() { P(ImGui::Checkbox("Volume Macro is Cutoff Macro",&ins->c64.volIsCutoff)); P(ImGui::Checkbox("Absolute Cutoff Macro",&ins->c64.filterIsAbs)); - P(ImGui::Checkbox("Absolute Duty Macro",&ins->c64.dutyIsAbs)); + bool dutyAbs=ins->std.dutyMacro.mode&1; + if (ImGui::Checkbox("Absolute Duty Macro",&dutyAbs)) { PARAMETER + ins->std.dutyMacro.mode^=1; + } ImGui::EndTabItem(); } if (ins->type==DIV_INS_AMIGA) if (ImGui::BeginTabItem("Amiga/Sample")) { @@ -2328,6 +2380,9 @@ void FurnaceGUI::drawInsEdit() { ins->type==DIV_INS_SWAN || ins->type==DIV_INS_PCE || ins->type==DIV_INS_SCC) { + float asFloat[256]; + int asInt[256]; + float loopIndicator[256]; if (ImGui::BeginTabItem("Wavetable")) { ImGui::Checkbox("Enable synthesizer",&ins->ws.enabled); ImGui::SameLine(); @@ -2422,6 +2477,20 @@ void FurnaceGUI::drawInsEdit() { ImGui::EndTabItem(); } + if (ImGui::BeginTabItem("Wavetable Macros")) { + MACRO_BEGIN(0); + NORMAL_MACRO(ins->std.ws.enabledMacro,0,1,"enabled","Enable",160,ins->std.ws.enabledMacro.open,true,oneBit,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[5],0,1,NULL,false); + NORMAL_MACRO(ins->std.ws.oneShotMacro,0,1,"oneShot","One Shot",160,ins->std.ws.oneShotMacro.open,true,oneBit,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,1,NULL,false); + NORMAL_MACRO(ins->std.ws.globalMacro,0,1,"global","Global",160,ins->std.ws.globalMacro.open,true,oneBit,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[6],0,1,NULL,false); + NORMAL_MACRO(ins->std.ws.effectMacro,0,255,"effect","Effect",160,ins->std.ws.effectMacro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[3],0,255,NULL,false); + NORMAL_MACRO(ins->std.ws.wave1Macro,0,255,"wave1","Wave 1",160,ins->std.ws.wave1Macro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[0],0,255,NULL,false); + NORMAL_MACRO(ins->std.ws.wave2Macro,0,255,"wave2","Wave 2",160,ins->std.ws.wave2Macro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[1],0,255,NULL,false); + NORMAL_MACRO(ins->std.ws.rateDividerMacro,1,7,"rateDivider","Rate",160,ins->std.ws.rateDividerMacro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[2],1,7,NULL,false); + NORMAL_MACRO(ins->std.ws.speedMacro,0,255,"speed","Speed",160,ins->std.ws.speedMacro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[6],0,255,NULL,false); + NORMAL_MACRO(ins->std.ws.param1Macro,1,7,"amount","Amount",160,ins->std.ws.param1Macro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[6],1,7,NULL,false); + MACRO_END; + ImGui::EndTabItem(); + } } if (ImGui::BeginTabItem("Macros")) { float asFloat[256]; @@ -2464,13 +2533,13 @@ void FurnaceGUI::drawInsEdit() { volMax=32; } - bool arpMode=ins->std.arpMacroMode; + bool arpMode=ins->std.arpMacro.mode; const char* dutyLabel="Duty/Noise"; int dutyMax=3; if (ins->type==DIV_INS_C64) { dutyLabel="Duty"; - if (ins->c64.dutyIsAbs) { + if (ins->std.dutyMacro.mode) { dutyMax=4095; } else { dutyMax=24; @@ -2517,7 +2586,7 @@ void FurnaceGUI::drawInsEdit() { dutyLabel="Duty"; dutyMax=7; } - bool dutyIsRel=(ins->type==DIV_INS_C64 && !ins->c64.dutyIsAbs); + bool dutyIsRel=(ins->type==DIV_INS_C64 && !ins->std.dutyMacro.mode); const char* waveLabel="Waveform"; int waveMax=(ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_VERA)?3:63; @@ -2571,66 +2640,67 @@ void FurnaceGUI::drawInsEdit() { if (settings.macroView==0) { // modern view MACRO_BEGIN(28*dpiScale); if (volMax>0) { - NORMAL_MACRO(ins->std.volMacro,ins->std.volMacroLen,ins->std.volMacroLoop,ins->std.volMacroRel,volMin,volMax,"vol",volumeLabel,160,ins->std.volMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_VOLUME],mmlString[0],volMin,volMax,NULL,false); + NORMAL_MACRO(ins->std.volMacro,volMin,volMax,"vol",volumeLabel,160,ins->std.volMacro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_VOLUME],mmlString[0],volMin,volMax,NULL,false); } - NORMAL_MACRO(ins->std.arpMacro,ins->std.arpMacroLen,ins->std.arpMacroLoop,ins->std.arpMacroRel,arpMacroScroll,arpMacroScroll+24,"arp","Arpeggio",160,ins->std.arpMacroOpen,false,NULL,true,&arpMacroScroll,(arpMode?-60:-80),0,0,&ins->std.arpMacroMode,uiColors[GUI_COLOR_MACRO_PITCH],mmlString[1],-92,94,(ins->std.arpMacroMode?(¯oHoverNote):NULL),true); + NORMAL_MACRO(ins->std.arpMacro,arpMacroScroll,arpMacroScroll+24,"arp","Arpeggio",160,ins->std.arpMacro.open,false,NULL,true,&arpMacroScroll,(arpMode?-60:-80),0,0,true,1,macroAbsoluteMode,uiColors[GUI_COLOR_MACRO_PITCH],mmlString[1],-92,94,(ins->std.arpMacro.mode?(¯oHoverNote):NULL),true); if (dutyMax>0) { if (ins->type==DIV_INS_MIKEY) { - NORMAL_MACRO(ins->std.dutyMacro,ins->std.dutyMacroLen,ins->std.dutyMacroLoop,ins->std.dutyMacroRel,0,dutyMax,"duty",dutyLabel,160,ins->std.dutyMacroOpen,true,mikeyFeedbackBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[2],0,dutyMax,NULL,false); + NORMAL_MACRO(ins->std.dutyMacro,0,dutyMax,"duty",dutyLabel,160,ins->std.dutyMacro.open,true,mikeyFeedbackBits,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[2],0,dutyMax,NULL,false); + } else if (ins->type==DIV_INS_C64) { + NORMAL_MACRO(ins->std.dutyMacro,0,dutyMax,"duty",dutyLabel,160,ins->std.dutyMacro.open,false,NULL,false,NULL,0,0,0,true,1,macroAbsoluteMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[2],0,dutyMax,NULL,false); + } else { + NORMAL_MACRO(ins->std.dutyMacro,0,dutyMax,"duty",dutyLabel,160,ins->std.dutyMacro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[2],0,dutyMax,NULL,false); } - else { - NORMAL_MACRO(ins->std.dutyMacro,ins->std.dutyMacroLen,ins->std.dutyMacroLoop,ins->std.dutyMacroRel,0,dutyMax,"duty",dutyLabel,160,ins->std.dutyMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[2],0,dutyMax,NULL,false); - } } if (waveMax>0) { - NORMAL_MACRO(ins->std.waveMacro,ins->std.waveMacroLen,ins->std.waveMacroLoop,ins->std.waveMacroRel,0,waveMax,"wave",waveLabel,(bitMode && ins->type!=DIV_INS_PET)?64:160,ins->std.waveMacroOpen,bitMode,waveNames,false,NULL,0,0,((ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930)?1:0),NULL,uiColors[GUI_COLOR_MACRO_WAVE],mmlString[3],0,waveMax,NULL,false); + NORMAL_MACRO(ins->std.waveMacro,0,waveMax,"wave",waveLabel,(bitMode && ins->type!=DIV_INS_PET)?64:160,ins->std.waveMacro.open,bitMode,waveNames,false,NULL,0,0,((ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930)?1:0),false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_WAVE],mmlString[3],0,waveMax,NULL,false); } if (ex1Max>0) { if (ins->type==DIV_INS_C64) { - NORMAL_MACRO(ins->std.ex1Macro,ins->std.ex1MacroLen,ins->std.ex1MacroLoop,ins->std.ex1MacroRel,0,ex1Max,"ex1","Filter Mode",64,ins->std.ex1MacroOpen,true,filtModeBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false); + NORMAL_MACRO(ins->std.ex1Macro,0,ex1Max,"ex1","Filter Mode",64,ins->std.ex1Macro.open,true,filtModeBits,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false); } else if (ins->type==DIV_INS_SAA1099) { - NORMAL_MACRO(ins->std.ex1Macro,ins->std.ex1MacroLen,ins->std.ex1MacroLoop,ins->std.ex1MacroRel,0,ex1Max,"ex1","Envelope",160,ins->std.ex1MacroOpen,true,saaEnvBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false); + NORMAL_MACRO(ins->std.ex1Macro,0,ex1Max,"ex1","Envelope",160,ins->std.ex1Macro.open,true,saaEnvBits,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false); } else if (ins->type==DIV_INS_X1_010) { - NORMAL_MACRO(ins->std.ex1Macro,ins->std.ex1MacroLen,ins->std.ex1MacroLoop,ins->std.ex1MacroRel,0,ex1Max,"ex1","Envelope Mode",160,ins->std.ex1MacroOpen,true,x1_010EnvBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false); + NORMAL_MACRO(ins->std.ex1Macro,0,ex1Max,"ex1","Envelope Mode",160,ins->std.ex1Macro.open,true,x1_010EnvBits,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false); } else if (ins->type==DIV_INS_N163) { - NORMAL_MACRO(ins->std.ex1Macro,ins->std.ex1MacroLen,ins->std.ex1MacroLoop,ins->std.ex1MacroRel,0,ex1Max,"ex1","Waveform len.",160,ins->std.ex1MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false); + NORMAL_MACRO(ins->std.ex1Macro,0,ex1Max,"ex1","Waveform len.",160,ins->std.ex1Macro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false); } else if (ins->type==DIV_INS_FDS) { - NORMAL_MACRO(ins->std.ex1Macro,ins->std.ex1MacroLen,ins->std.ex1MacroLoop,ins->std.ex1MacroRel,0,ex1Max,"ex1","Mod Depth",160,ins->std.ex1MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false); + NORMAL_MACRO(ins->std.ex1Macro,0,ex1Max,"ex1","Mod Depth",160,ins->std.ex1Macro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false); } else { - NORMAL_MACRO(ins->std.ex1Macro,ins->std.ex1MacroLen,ins->std.ex1MacroLoop,ins->std.ex1MacroRel,0,ex1Max,"ex1","Duty",160,ins->std.ex1MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false); + NORMAL_MACRO(ins->std.ex1Macro,0,ex1Max,"ex1","Duty",160,ins->std.ex1Macro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,ex1Max,NULL,false); } } if (ex2Max>0) { if (ins->type==DIV_INS_C64) { - NORMAL_MACRO(ins->std.ex2Macro,ins->std.ex2MacroLen,ins->std.ex2MacroLoop,ins->std.ex2MacroRel,0,ex2Max,"ex2","Resonance",64,ins->std.ex2MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[5],0,ex2Max,NULL,false); + NORMAL_MACRO(ins->std.ex2Macro,0,ex2Max,"ex2","Resonance",64,ins->std.ex2Macro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[5],0,ex2Max,NULL,false); } else if (ins->type==DIV_INS_N163) { - NORMAL_MACRO(ins->std.ex2Macro,ins->std.ex2MacroLen,ins->std.ex2MacroLoop,ins->std.ex2MacroRel,0,ex2Max,"ex2","Waveform update",64,ins->std.ex2MacroOpen,true,n163UpdateBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[5],0,ex2Max,NULL,false); + NORMAL_MACRO(ins->std.ex2Macro,0,ex2Max,"ex2","Waveform update",64,ins->std.ex2Macro.open,true,n163UpdateBits,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[5],0,ex2Max,NULL,false); } else if (ins->type==DIV_INS_FDS) { - NORMAL_MACRO(ins->std.ex2Macro,ins->std.ex2MacroLen,ins->std.ex2MacroLoop,ins->std.ex2MacroRel,0,ex2Max,"ex2","Mod Speed",160,ins->std.ex2MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[5],0,ex2Max,NULL,false); + NORMAL_MACRO(ins->std.ex2Macro,0,ex2Max,"ex2","Mod Speed",160,ins->std.ex2Macro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[5],0,ex2Max,NULL,false); } else { - NORMAL_MACRO(ins->std.ex2Macro,ins->std.ex2MacroLen,ins->std.ex2MacroLoop,ins->std.ex2MacroRel,0,ex2Max,"ex2","Envelope",ex2Bit?64:160,ins->std.ex2MacroOpen,ex2Bit,ayEnvBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[5],0,ex2Max,NULL,false); + NORMAL_MACRO(ins->std.ex2Macro,0,ex2Max,"ex2","Envelope",ex2Bit?64:160,ins->std.ex2Macro.open,ex2Bit,ayEnvBits,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[5],0,ex2Max,NULL,false); } } if (ins->type==DIV_INS_C64) { - NORMAL_MACRO(ins->std.ex3Macro,ins->std.ex3MacroLen,ins->std.ex3MacroLoop,ins->std.ex3MacroRel,0,2,"ex3","Special",32,ins->std.ex3MacroOpen,true,c64SpecialBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[6],0,2,NULL,false); + NORMAL_MACRO(ins->std.ex3Macro,0,2,"ex3","Special",32,ins->std.ex3Macro.open,true,c64SpecialBits,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[6],0,2,NULL,false); } if (ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_X1_010) { - NORMAL_MACRO(ins->std.ex3Macro,ins->std.ex3MacroLen,ins->std.ex3MacroLoop,ins->std.ex3MacroRel,0,15,"ex3","AutoEnv Num",96,ins->std.ex3MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[6],0,15,NULL,false); - NORMAL_MACRO(ins->std.algMacro,ins->std.algMacroLen,ins->std.algMacroLoop,ins->std.algMacroRel,0,15,"alg","AutoEnv Den",96,ins->std.algMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[7],0,15,NULL,false); + NORMAL_MACRO(ins->std.ex3Macro,0,15,"ex3","AutoEnv Num",96,ins->std.ex3Macro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[6],0,15,NULL,false); + NORMAL_MACRO(ins->std.algMacro,0,15,"alg","AutoEnv Den",96,ins->std.algMacro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[7],0,15,NULL,false); } if (ins->type==DIV_INS_AY8930) { // oh my i am running out of macros - NORMAL_MACRO(ins->std.fbMacro,ins->std.fbMacroLen,ins->std.fbMacroLoop,ins->std.fbMacroRel,0,8,"fb","Noise AND Mask",96,ins->std.fbMacroOpen,true,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[8],0,8,NULL,false); - NORMAL_MACRO(ins->std.fmsMacro,ins->std.fmsMacroLen,ins->std.fmsMacroLoop,ins->std.fmsMacroRel,0,8,"fms","Noise OR Mask",96,ins->std.fmsMacroOpen,true,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[9],0,8,NULL,false); + NORMAL_MACRO(ins->std.fbMacro,0,8,"fb","Noise AND Mask",96,ins->std.fbMacro.open,true,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[8],0,8,NULL,false); + NORMAL_MACRO(ins->std.fmsMacro,0,8,"fms","Noise OR Mask",96,ins->std.fmsMacro.open,true,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[9],0,8,NULL,false); } if (ins->type==DIV_INS_N163) { - NORMAL_MACRO(ins->std.ex3Macro,ins->std.ex3MacroLen,ins->std.ex3MacroLoop,ins->std.ex3MacroRel,0,255,"ex3","Waveform to Load",160,ins->std.ex3MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[6],0,255,NULL,false); - NORMAL_MACRO(ins->std.algMacro,ins->std.algMacroLen,ins->std.algMacroLoop,ins->std.algMacroRel,0,255,"alg","Wave pos. to Load",160,ins->std.algMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[7],0,255,NULL,false); - NORMAL_MACRO(ins->std.fbMacro,ins->std.fbMacroLen,ins->std.fbMacroLoop,ins->std.fbMacroRel,0,252,"fb","Wave len. to Load",160,ins->std.fbMacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[8],0,252,NULL,false); - NORMAL_MACRO(ins->std.fmsMacro,ins->std.fmsMacroLen,ins->std.fmsMacroLoop,ins->std.fmsMacroRel,0,2,"fms","Waveform load",64,ins->std.fmsMacroOpen,true,n163UpdateBits,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[9],0,2,NULL,false); + NORMAL_MACRO(ins->std.ex3Macro,0,255,"ex3","Waveform to Load",160,ins->std.ex3Macro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[6],0,255,NULL,false); + NORMAL_MACRO(ins->std.algMacro,0,255,"alg","Wave pos. to Load",160,ins->std.algMacro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[7],0,255,NULL,false); + NORMAL_MACRO(ins->std.fbMacro,0,252,"fb","Wave len. to Load",160,ins->std.fbMacro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[8],0,252,NULL,false); + NORMAL_MACRO(ins->std.fmsMacro,0,2,"fms","Waveform load",64,ins->std.fmsMacro.open,true,n163UpdateBits,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[9],0,2,NULL,false); } if (ins->type==DIV_INS_FDS) { - NORMAL_MACRO(ins->std.ex3Macro,ins->std.ex3MacroLen,ins->std.ex3MacroLoop,ins->std.ex3MacroRel,0,127,"ex3","Mod Position",160,ins->std.ex3MacroOpen,false,NULL,false,NULL,0,0,0,NULL,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[6],0,2,NULL,false); + NORMAL_MACRO(ins->std.ex3Macro,0,127,"ex3","Mod Position",160,ins->std.ex3Macro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[6],0,2,NULL,false); } MACRO_END; @@ -2646,87 +2716,87 @@ void FurnaceGUI::drawInsEdit() { } else { ImGui::Text("Volume Macro"); } - for (int i=0; istd.volMacroLen; i++) { + for (int i=0; istd.volMacro.len; i++) { if (ins->type==DIV_INS_C64 && ins->c64.volIsCutoff && !ins->c64.filterIsAbs) { - asFloat[i]=ins->std.volMacro[i]-18; + asFloat[i]=ins->std.volMacro.val[i]-18; } else { - asFloat[i]=ins->std.volMacro[i]; + asFloat[i]=ins->std.volMacro.val[i]; } - loopIndicator[i]=(ins->std.volMacroLoop!=-1 && i>=ins->std.volMacroLoop); + loopIndicator[i]=(ins->std.volMacro.loop!=-1 && i>=ins->std.volMacro.loop); } macroDragScroll=0; if (volMax>0) { ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,ImVec2(0.0f,0.0f)); - ImGui::PlotHistogram("##IVolMacro",asFloat,ins->std.volMacroLen,0,NULL,volMin,volMax,ImVec2(400.0f*dpiScale,200.0f*dpiScale)); + ImGui::PlotHistogram("##IVolMacro",asFloat,ins->std.volMacro.len,0,NULL,volMin,volMax,ImVec2(400.0f*dpiScale,200.0f*dpiScale)); if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { macroDragStart=ImGui::GetItemRectMin(); macroDragAreaSize=ImVec2(400.0f*dpiScale,200.0f*dpiScale); macroDragMin=volMin; macroDragMax=volMax; - macroDragLen=ins->std.volMacroLen; + macroDragLen=ins->std.volMacro.len; macroDragActive=true; - macroDragTarget=ins->std.volMacro; + macroDragTarget=ins->std.volMacro.val; macroDragChar=false; processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); } - ImGui::PlotHistogram("##IVolMacroLoop",loopIndicator,ins->std.volMacroLen,0,NULL,0,1,ImVec2(400.0f*dpiScale,16.0f*dpiScale)); + ImGui::PlotHistogram("##IVolMacro.loop",loopIndicator,ins->std.volMacro.len,0,NULL,0,1,ImVec2(400.0f*dpiScale,16.0f*dpiScale)); if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { macroLoopDragStart=ImGui::GetItemRectMin(); macroLoopDragAreaSize=ImVec2(400.0f*dpiScale,16.0f*dpiScale); - macroLoopDragLen=ins->std.volMacroLen; - macroLoopDragTarget=&ins->std.volMacroLoop; + macroLoopDragLen=ins->std.volMacro.len; + macroLoopDragTarget=&ins->std.volMacro.loop; macroLoopDragActive=true; processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); } if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { - ins->std.volMacroLoop=-1; + ins->std.volMacro.loop=-1; } ImGui::PopStyleVar(); - if (ImGui::InputScalar("Length##IVolMacroL",ImGuiDataType_U8,&ins->std.volMacroLen,&_ONE,&_THREE)) { - if (ins->std.volMacroLen>127) ins->std.volMacroLen=127; + if (ImGui::InputScalar("Length##IVolMacroL",ImGuiDataType_U8,&ins->std.volMacro.len,&_ONE,&_THREE)) { + if (ins->std.volMacro.len>127) ins->std.volMacro.len=127; } } // arp macro ImGui::Separator(); ImGui::Text("Arpeggio Macro"); - for (int i=0; istd.arpMacroLen; i++) { - asFloat[i]=ins->std.arpMacro[i]; - loopIndicator[i]=(ins->std.arpMacroLoop!=-1 && i>=ins->std.arpMacroLoop); + for (int i=0; istd.arpMacro.len; i++) { + asFloat[i]=ins->std.arpMacro.val[i]; + loopIndicator[i]=(ins->std.arpMacro.loop!=-1 && i>=ins->std.arpMacro.loop); } ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,ImVec2(0.0f,0.0f)); - ImGui::PlotHistogram("##IArpMacro",asFloat,ins->std.arpMacroLen,0,NULL,arpMode?arpMacroScroll:(arpMacroScroll-12),arpMacroScroll+(arpMode?24:12),ImVec2(400.0f*dpiScale,200.0f*dpiScale)); + ImGui::PlotHistogram("##IArpMacro",asFloat,ins->std.arpMacro.len,0,NULL,arpMode?arpMacroScroll:(arpMacroScroll-12),arpMacroScroll+(arpMode?24:12),ImVec2(400.0f*dpiScale,200.0f*dpiScale)); if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { macroDragStart=ImGui::GetItemRectMin(); macroDragAreaSize=ImVec2(400.0f*dpiScale,200.0f*dpiScale); macroDragMin=arpMacroScroll; macroDragMax=arpMacroScroll+24; - macroDragLen=ins->std.arpMacroLen; + macroDragLen=ins->std.arpMacro.len; macroDragActive=true; - macroDragTarget=ins->std.arpMacro; + macroDragTarget=ins->std.arpMacro.val; macroDragChar=false; processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); } ImGui::SameLine(); CWVSliderInt("##IArpMacroPos",ImVec2(20.0f*dpiScale,200.0f*dpiScale),&arpMacroScroll,arpMode?0:-80,70); - ImGui::PlotHistogram("##IArpMacroLoop",loopIndicator,ins->std.arpMacroLen,0,NULL,0,1,ImVec2(400.0f*dpiScale,16.0f*dpiScale)); + ImGui::PlotHistogram("##IArpMacro.loop",loopIndicator,ins->std.arpMacro.len,0,NULL,0,1,ImVec2(400.0f*dpiScale,16.0f*dpiScale)); if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { macroLoopDragStart=ImGui::GetItemRectMin(); macroLoopDragAreaSize=ImVec2(400.0f*dpiScale,16.0f*dpiScale); - macroLoopDragLen=ins->std.arpMacroLen; - macroLoopDragTarget=&ins->std.arpMacroLoop; + macroLoopDragLen=ins->std.arpMacro.len; + macroLoopDragTarget=&ins->std.arpMacro.loop; macroLoopDragActive=true; processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); } if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { - ins->std.arpMacroLoop=-1; + ins->std.arpMacro.loop=-1; } ImGui::PopStyleVar(); - if (ImGui::InputScalar("Length##IArpMacroL",ImGuiDataType_U8,&ins->std.arpMacroLen,&_ONE,&_THREE)) { - if (ins->std.arpMacroLen>127) ins->std.arpMacroLen=127; + if (ImGui::InputScalar("Length##IArpMacroL",ImGuiDataType_U8,&ins->std.arpMacro.len,&_ONE,&_THREE)) { + if (ins->std.arpMacro.len>127) ins->std.arpMacro.len=127; } if (ImGui::Checkbox("Fixed",&arpMode)) { - ins->std.arpMacroMode=arpMode; + ins->std.arpMacro.mode=arpMode; if (arpMode) { if (arpMacroScroll<0) arpMacroScroll=0; } @@ -2736,7 +2806,7 @@ void FurnaceGUI::drawInsEdit() { if (dutyMax>0) { ImGui::Separator(); if (ins->type==DIV_INS_C64) { - if (ins->c64.dutyIsAbs) { + if (ins->std.dutyMacro.mode) { ImGui::Text("Duty Macro"); } else { ImGui::Text("Relative Duty Macro"); @@ -2748,39 +2818,39 @@ void FurnaceGUI::drawInsEdit() { ImGui::Text("Duty/Noise Mode Macro"); } } - for (int i=0; istd.dutyMacroLen; i++) { - asFloat[i]=ins->std.dutyMacro[i]-(dutyIsRel?12:0); - loopIndicator[i]=(ins->std.dutyMacroLoop!=-1 && i>=ins->std.dutyMacroLoop); + for (int i=0; istd.dutyMacro.len; i++) { + asFloat[i]=ins->std.dutyMacro.val[i]-(dutyIsRel?12:0); + loopIndicator[i]=(ins->std.dutyMacro.loop!=-1 && i>=ins->std.dutyMacro.loop); } ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,ImVec2(0.0f,0.0f)); - ImGui::PlotHistogram("##IDutyMacro",asFloat,ins->std.dutyMacroLen,0,NULL,dutyIsRel?-12:0,dutyMax-(dutyIsRel?12:0),ImVec2(400.0f*dpiScale,200.0f*dpiScale)); + ImGui::PlotHistogram("##IDutyMacro",asFloat,ins->std.dutyMacro.len,0,NULL,dutyIsRel?-12:0,dutyMax-(dutyIsRel?12:0),ImVec2(400.0f*dpiScale,200.0f*dpiScale)); if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { macroDragStart=ImGui::GetItemRectMin(); macroDragAreaSize=ImVec2(400.0f*dpiScale,200.0f*dpiScale); macroDragMin=0; macroDragMax=dutyMax; - macroDragLen=ins->std.dutyMacroLen; + macroDragLen=ins->std.dutyMacro.len; macroDragActive=true; - macroDragTarget=ins->std.dutyMacro; + macroDragTarget=ins->std.dutyMacro.val; macroDragChar=false; processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); } - ImGui::PlotHistogram("##IDutyMacroLoop",loopIndicator,ins->std.dutyMacroLen,0,NULL,0,1,ImVec2(400.0f*dpiScale,16.0f*dpiScale)); + ImGui::PlotHistogram("##IDutyMacro.loop",loopIndicator,ins->std.dutyMacro.len,0,NULL,0,1,ImVec2(400.0f*dpiScale,16.0f*dpiScale)); if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { macroLoopDragStart=ImGui::GetItemRectMin(); macroLoopDragAreaSize=ImVec2(400.0f*dpiScale,16.0f*dpiScale); - macroLoopDragLen=ins->std.dutyMacroLen; - macroLoopDragTarget=&ins->std.dutyMacroLoop; + macroLoopDragLen=ins->std.dutyMacro.len; + macroLoopDragTarget=&ins->std.dutyMacro.loop; macroLoopDragActive=true; processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); } if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { - ins->std.dutyMacroLoop=-1; + ins->std.dutyMacro.loop=-1; } ImGui::PopStyleVar(); - if (ImGui::InputScalar("Length##IDutyMacroL",ImGuiDataType_U8,&ins->std.dutyMacroLen,&_ONE,&_THREE)) { - if (ins->std.dutyMacroLen>127) ins->std.dutyMacroLen=127; + if (ImGui::InputScalar("Length##IDutyMacroL",ImGuiDataType_U8,&ins->std.dutyMacro.len,&_ONE,&_THREE)) { + if (ins->std.dutyMacro.len>127) ins->std.dutyMacro.len=127; } } @@ -2788,24 +2858,24 @@ void FurnaceGUI::drawInsEdit() { if (waveMax>0) { ImGui::Separator(); ImGui::Text("Waveform Macro"); - for (int i=0; istd.waveMacroLen; i++) { - asFloat[i]=ins->std.waveMacro[i]; + for (int i=0; istd.waveMacro.len; i++) { + asFloat[i]=ins->std.waveMacro.val[i]; if (ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930) { - asInt[i]=ins->std.waveMacro[i]+1; + asInt[i]=ins->std.waveMacro.val[i]+1; } else { - asInt[i]=ins->std.waveMacro[i]; + asInt[i]=ins->std.waveMacro.val[i]; } - loopIndicator[i]=(ins->std.waveMacroLoop!=-1 && i>=ins->std.waveMacroLoop); + loopIndicator[i]=(ins->std.waveMacro.loop!=-1 && i>=ins->std.waveMacro.loop); } ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,ImVec2(0.0f,0.0f)); ImVec2 areaSize=ImVec2(400.0f*dpiScale,200.0f*dpiScale); if (ins->type==DIV_INS_C64 || ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_SAA1099) { areaSize=ImVec2(400.0f*dpiScale,waveMax*32.0f*dpiScale); - PlotBitfield("##IWaveMacro",asInt,ins->std.waveMacroLen,0,(ins->type==DIV_INS_C64)?c64ShapeBits:ayShapeBits,waveMax,areaSize); + PlotBitfield("##IWaveMacro",asInt,ins->std.waveMacro.len,0,(ins->type==DIV_INS_C64)?c64ShapeBits:ayShapeBits,waveMax,areaSize); bitMode=true; } else { - ImGui::PlotHistogram("##IWaveMacro",asFloat,ins->std.waveMacroLen,0,NULL,0,waveMax,areaSize); + ImGui::PlotHistogram("##IWaveMacro",asFloat,ins->std.waveMacro.len,0,NULL,0,waveMax,areaSize); } if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { macroDragStart=ImGui::GetItemRectMin(); @@ -2816,27 +2886,27 @@ void FurnaceGUI::drawInsEdit() { macroDragBitMode=bitMode; macroDragInitialValueSet=false; macroDragInitialValue=false; - macroDragLen=ins->std.waveMacroLen; + macroDragLen=ins->std.waveMacro.len; macroDragActive=true; - macroDragTarget=ins->std.waveMacro; + macroDragTarget=ins->std.waveMacro.val; macroDragChar=false; processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); } - ImGui::PlotHistogram("##IWaveMacroLoop",loopIndicator,ins->std.waveMacroLen,0,NULL,0,1,ImVec2(400.0f*dpiScale,16.0f*dpiScale)); + ImGui::PlotHistogram("##IWaveMacro.loop",loopIndicator,ins->std.waveMacro.len,0,NULL,0,1,ImVec2(400.0f*dpiScale,16.0f*dpiScale)); if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { macroLoopDragStart=ImGui::GetItemRectMin(); macroLoopDragAreaSize=ImVec2(400.0f*dpiScale,16.0f*dpiScale); - macroLoopDragLen=ins->std.waveMacroLen; - macroLoopDragTarget=&ins->std.waveMacroLoop; + macroLoopDragLen=ins->std.waveMacro.len; + macroLoopDragTarget=&ins->std.waveMacro.loop; macroLoopDragActive=true; processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); } if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { - ins->std.waveMacroLoop=-1; + ins->std.waveMacro.loop=-1; } ImGui::PopStyleVar(); - if (ImGui::InputScalar("Length##IWaveMacroL",ImGuiDataType_U8,&ins->std.waveMacroLen,&_ONE,&_THREE)) { - if (ins->std.waveMacroLen>127) ins->std.waveMacroLen=127; + if (ImGui::InputScalar("Length##IWaveMacroL",ImGuiDataType_U8,&ins->std.waveMacro.len,&_ONE,&_THREE)) { + if (ins->std.waveMacro.len>127) ins->std.waveMacro.len=127; } } @@ -2848,39 +2918,39 @@ void FurnaceGUI::drawInsEdit() { } else { ImGui::Text("Extra 1 Macro"); } - for (int i=0; istd.ex1MacroLen; i++) { - asFloat[i]=ins->std.ex1Macro[i]; - loopIndicator[i]=(ins->std.ex1MacroLoop!=-1 && i>=ins->std.ex1MacroLoop); + for (int i=0; istd.ex1Macro.len; i++) { + asFloat[i]=ins->std.ex1Macro.val[i]; + loopIndicator[i]=(ins->std.ex1Macro.loop!=-1 && i>=ins->std.ex1Macro.loop); } ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,ImVec2(0.0f,0.0f)); - ImGui::PlotHistogram("##IEx1Macro",asFloat,ins->std.ex1MacroLen,0,NULL,0,ex1Max,ImVec2(400.0f*dpiScale,200.0f*dpiScale)); + ImGui::PlotHistogram("##IEx1Macro",asFloat,ins->std.ex1Macro.len,0,NULL,0,ex1Max,ImVec2(400.0f*dpiScale,200.0f*dpiScale)); if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { macroDragStart=ImGui::GetItemRectMin(); macroDragAreaSize=ImVec2(400.0f*dpiScale,200.0f*dpiScale); macroDragMin=0; macroDragMax=ex1Max; - macroDragLen=ins->std.ex1MacroLen; + macroDragLen=ins->std.ex1Macro.len; macroDragActive=true; - macroDragTarget=ins->std.ex1Macro; + macroDragTarget=ins->std.ex1Macro.val; macroDragChar=false; processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); } - ImGui::PlotHistogram("##IEx1MacroLoop",loopIndicator,ins->std.ex1MacroLen,0,NULL,0,1,ImVec2(400.0f*dpiScale,16.0f*dpiScale)); + ImGui::PlotHistogram("##IEx1Macro.loop",loopIndicator,ins->std.ex1Macro.len,0,NULL,0,1,ImVec2(400.0f*dpiScale,16.0f*dpiScale)); if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { macroLoopDragStart=ImGui::GetItemRectMin(); macroLoopDragAreaSize=ImVec2(400.0f*dpiScale,16.0f*dpiScale); - macroLoopDragLen=ins->std.ex1MacroLen; - macroLoopDragTarget=&ins->std.ex1MacroLoop; + macroLoopDragLen=ins->std.ex1Macro.len; + macroLoopDragTarget=&ins->std.ex1Macro.loop; macroLoopDragActive=true; processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); } if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { - ins->std.ex1MacroLoop=-1; + ins->std.ex1Macro.loop=-1; } ImGui::PopStyleVar(); - if (ImGui::InputScalar("Length##IEx1MacroL",ImGuiDataType_U8,&ins->std.ex1MacroLen,&_ONE,&_THREE)) { - if (ins->std.ex1MacroLen>127) ins->std.ex1MacroLen=127; + if (ImGui::InputScalar("Length##IEx1MacroL",ImGuiDataType_U8,&ins->std.ex1Macro.len,&_ONE,&_THREE)) { + if (ins->std.ex1Macro.len>127) ins->std.ex1Macro.len=127; } } } From 13bdf2d86ddb5b9c82e12d37e39bdc7e64739df4 Mon Sep 17 00:00:00 2001 From: cam900 Date: Sun, 10 Apr 2022 15:16:51 +0900 Subject: [PATCH 599/637] Prepare for FMS2, AMS2 macro for YM2414 --- src/engine/platform/tx81z.cpp | 10 ++++++++++ src/gui/insEdit.cpp | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/engine/platform/tx81z.cpp b/src/engine/platform/tx81z.cpp index 8ae4c6882..198968a1d 100644 --- a/src/engine/platform/tx81z.cpp +++ b/src/engine/platform/tx81z.cpp @@ -272,10 +272,18 @@ void DivPlatformTX81Z::tick() { chan[i].state.fms=chan[i].std.fms.val; rWrite(chanOffs[i]+ADDR_FMS_AMS,((chan[i].state.fms&7)<<4)|(chan[i].state.ams&3)); } + if (chan[i].std.fms2.had) { + chan[i].state.fms2=chan[i].std.fms2.val; + //rWrite(chanOffs[i]+ADDR_FMS_AMS,0x84|((chan[i].state.fms2&7)<<4)|(chan[i].state.ams2&3)); + } if (chan[i].std.ams.had) { chan[i].state.ams=chan[i].std.ams.val; rWrite(chanOffs[i]+ADDR_FMS_AMS,((chan[i].state.fms&7)<<4)|(chan[i].state.ams&3)); } + if (chan[i].std.ams2.had) { + chan[i].state.ams2=chan[i].std.ams2.val; + //rWrite(chanOffs[i]+ADDR_FMS_AMS,0x84|((chan[i].state.fms2&7)<<4)|(chan[i].state.ams2&3)); + } for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; @@ -435,6 +443,7 @@ int DivPlatformTX81Z::dispatch(DivCommand c) { rWrite(chanOffs[c.chan]+ADDR_LR_FB_ALG,(chan[c.chan].state.alg&7)|(chan[c.chan].state.fb<<3)|((chan[c.chan].chVolL&1)<<6)|((chan[c.chan].chVolR&1)<<7)); }*/ rWrite(chanOffs[c.chan]+ADDR_FMS_AMS,((chan[c.chan].state.fms&7)<<4)|(chan[c.chan].state.ams&3)); + //rWrite(chanOffs[c.chan]+ADDR_FMS_AMS,0x84|((chan[c.chan].state.fms2&7)<<4)|(chan[c.chan].state.ams2&3)); } chan[c.chan].insChanged=false; @@ -652,6 +661,7 @@ void DivPlatformTX81Z::forceIns() { rWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|((chan[i].chVolL&1)<<6)|((chan[i].chVolR&1)<<7)); }*/ rWrite(chanOffs[i]+ADDR_FMS_AMS,((chan[i].state.fms&7)<<4)|(chan[i].state.ams&3)); + //rWrite(chanOffs[i]+ADDR_FMS_AMS,0x84|((chan[i].state.fms2&7)<<4)|(chan[i].state.ams2&3)); if (chan[i].active) { chan[i].keyOn=true; chan[i].freqChanged=true; diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 75e0cdd8b..67b9ba823 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -32,7 +32,7 @@ const char* ssgEnvTypes[8]={ }; const char* fmParamNames[3][32]={ - {"Algorithm", "Feedback", "LFO > Freq", "LFO > Amp", "Attack", "Decay", "Decay 2", "Release", "Sustain", "Level", "EnvScale", "Multiplier", "Detune", "Detune 2", "SSG-EG", "AM", "AM Depth", "Vibrato Depth", "Sustained", "Sustained", "Level Scaling", "Sustain", "Vibrato", "Waveform", "Key Scale Rate", "OP2 Half Sine", "OP1 Half Sine", "EnvShift", "Reverb", "Fine", "LFO > Freq", "LFO > Amp"}, + {"Algorithm", "Feedback", "LFO > Freq", "LFO > Amp", "Attack", "Decay", "Decay 2", "Release", "Sustain", "Level", "EnvScale", "Multiplier", "Detune", "Detune 2", "SSG-EG", "AM", "AM Depth", "Vibrato Depth", "Sustained", "Sustained", "Level Scaling", "Sustain", "Vibrato", "Waveform", "Key Scale Rate", "OP2 Half Sine", "OP1 Half Sine", "EnvShift", "Reverb", "Fine", "LFO2 > Freq", "LFO2 > Amp"}, {"ALG", "FB", "FMS/PMS", "AMS", "AR", "DR", "SR", "RR", "SL", "TL", "KS", "MULT", "DT", "DT2", "SSG-EG", "AM", "AMD", "FMD", "EGT", "EGT", "KSL", "SUS", "VIB", "WS", "KSR", "DC", "DM", "EGS", "REV", "Fine", "FMS/PMS2", "AMS2"}, {"ALG", "FB", "FMS/PMS", "AMS", "AR", "DR", "D2R", "RR", "SL", "TL", "RS", "MULT", "DT", "DT2", "SSG-EG", "AM", "DAM", "DVB", "EGT", "EGS", "KSL", "SUS", "VIB", "WS", "KSR", "DC", "DM", "EGS", "REV", "Fine", "FMS/PMS2", "AMS2"} }; From eb05310d379cc9fdc1830a325aef1aeac81aae9c Mon Sep 17 00:00:00 2001 From: cam900 Date: Sun, 10 Apr 2022 15:49:26 +0900 Subject: [PATCH 600/637] Fix issue in instrument editor --- src/gui/insEdit.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 67b9ba823..c253940b3 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -992,7 +992,7 @@ void FurnaceGUI::drawFMEnv(unsigned char tl, unsigned char ar, unsigned char dr, } else { \ modeName=displayModeName[macro.mode]; \ } \ - if (ImGui::BeginCombo("Macro Mode",modeName.c_str())) { \ + if (ImGui::BeginCombo("Macro Mode##IMacroMode_" macroName,modeName.c_str())) { \ String id; \ for (unsigned int i=0; i<=macroModeMax; i++) { \ id=fmt::sprintf("%d: %s",i,displayModeName[i]); \ @@ -1096,7 +1096,7 @@ void FurnaceGUI::drawFMEnv(unsigned char tl, unsigned char ar, unsigned char dr, } else { \ modeName=displayModeName[macro.mode]; \ } \ - if (ImGui::BeginCombo("Macro Mode",modeName.c_str())) { \ + if (ImGui::BeginCombo("Macro Mode##IOPMacroMode_" macroName,modeName.c_str())) { \ String id; \ for (unsigned int i=0; i<=macroModeMax; i++) { \ id=fmt::sprintf("%d: %s",i,displayModeName[i]); \ From 4b912fd1454def465147cd45f3bfdb141baaadb8 Mon Sep 17 00:00:00 2001 From: cam900 Date: Sun, 10 Apr 2022 15:50:59 +0900 Subject: [PATCH 601/637] Code style --- src/engine/macroInt.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/macroInt.h b/src/engine/macroInt.h index 1bc939439..3ce0c8722 100644 --- a/src/engine/macroInt.h +++ b/src/engine/macroInt.h @@ -59,7 +59,7 @@ struct DivMacroExecList { void doMacro(bool released) { macro.doMacro(source, released); } - DivMacroExecList(DivMacroStruct &m, DivInstrumentMacro& s): + DivMacroExecList(DivMacroStruct& m, DivInstrumentMacro& s): macro(m), source(s) {} }; From f42855f170016eae5715f74320949ac73a48b471 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 10 Apr 2022 02:11:36 -0500 Subject: [PATCH 602/637] add 30xx effect to hard reset envelope currently only for 2612! --- src/engine/dispatch.h | 1 + src/engine/platform/arcade.cpp | 3 +++ src/engine/platform/arcade.h | 24 +++++++++++++++++++++-- src/engine/platform/genesis.cpp | 28 +++++++++++++++++++++++---- src/engine/platform/genesis.h | 3 ++- src/engine/platform/tx81z.h | 24 +++++++++++++++++++++-- src/engine/platform/ym2610.h | 3 ++- src/engine/platform/ym2610b.h | 3 ++- src/engine/playback.cpp | 34 +++++++++++++++++++++++++++++++++ src/engine/song.h | 7 +++++-- 10 files changed, 117 insertions(+), 13 deletions(-) diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index 5a1c2db64..37e36a2bf 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -56,6 +56,7 @@ enum DivDispatchCmds { DIV_CMD_SAMPLE_BANK, DIV_CMD_SAMPLE_POS, + DIV_CMD_FM_HARD_RESET, DIV_CMD_FM_LFO, DIV_CMD_FM_LFO_WAVE, DIV_CMD_FM_TL, diff --git a/src/engine/platform/arcade.cpp b/src/engine/platform/arcade.cpp index 7c465a511..e9618f29b 100644 --- a/src/engine/platform/arcade.cpp +++ b/src/engine/platform/arcade.cpp @@ -132,6 +132,9 @@ const char* DivPlatformArcade::getEffectName(unsigned char effect) { case 0x1f: return "1Fxx: Set PM depth (0 to 7F)"; break; + case 0x30: + return "30xx: Toggle hard envelope reset on new notes"; + break; } return NULL; } diff --git a/src/engine/platform/arcade.h b/src/engine/platform/arcade.h index a305eefdb..a6ec82c94 100644 --- a/src/engine/platform/arcade.h +++ b/src/engine/platform/arcade.h @@ -39,10 +39,30 @@ class DivPlatformArcade: public DivDispatch { int freq, baseFreq, pitch, note; unsigned char ins; signed char konCycles; - bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, portaPause, furnacePCM; + bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, portaPause, furnacePCM, hardReset; int vol, outVol; unsigned char chVolL, chVolR; - Channel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), note(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), inPorta(false), portaPause(false), furnacePCM(false), vol(0), outVol(0), chVolL(127), chVolR(127) {} + Channel(): + freqH(0), + freqL(0), + freq(0), + baseFreq(0), + pitch(0), + note(0), + ins(-1), + active(false), + insChanged(true), + freqChanged(false), + keyOn(false), + keyOff(false), + inPorta(false), + portaPause(false), + furnacePCM(false), + hardReset(false), + vol(0), + outVol(0), + chVolL(127), + chVolR(127) {} }; Channel chan[8]; struct QueuedWrite { diff --git a/src/engine/platform/genesis.cpp b/src/engine/platform/genesis.cpp index 0aff2b43a..ba9c0f566 100644 --- a/src/engine/platform/genesis.cpp +++ b/src/engine/platform/genesis.cpp @@ -74,9 +74,9 @@ const char* DivPlatformGenesis::getEffectName(unsigned char effect) { case 0x1d: return "1Dxx: Set attack of operator 4 (0 to 1F)"; break; - case 0x20: - return "20xy: Set PSG noise mode (x: preset freq/ch3 freq; y: thin pulse/noise)"; - break; + case 0x30: + return "30xx: Toggle hard envelope reset on new notes"; + break; } return NULL; } @@ -346,7 +346,25 @@ void DivPlatformGenesis::tick() { } if (chan[i].keyOn || chan[i].keyOff) { + if (chan[i].hardReset && chan[i].keyOn) { + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + immWrite(baseAddr+ADDR_SL_RR,0x0f); + immWrite(baseAddr+ADDR_TL,0x7f); + oldWrites[baseAddr+ADDR_SL_RR]=-1; + oldWrites[baseAddr+ADDR_TL]=-1; + //rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + } immWrite(0x28,0x00|konOffs[i]); + if (chan[i].hardReset && chan[i].keyOn) { + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + for (int k=0; k<5; k++) { + immWrite(baseAddr+ADDR_SL_RR,0x0f); + } + } + } chan[i].keyOff=false; } } @@ -715,9 +733,11 @@ int DivPlatformGenesis::dispatch(DivCommand c) { unsigned short baseAddr=chanOffs[c.chan]|opOffs[orderedOps[c.value]]; rWrite(baseAddr+ADDR_RS_AR,(op.ar&31)|(op.rs<<6)); } - break; } + case DIV_CMD_FM_HARD_RESET: + chan[c.chan].hardReset=c.value; + break; case DIV_ALWAYS_SET_VOLUME: return 0; break; diff --git a/src/engine/platform/genesis.h b/src/engine/platform/genesis.h index 095744dfe..51d98d807 100644 --- a/src/engine/platform/genesis.h +++ b/src/engine/platform/genesis.h @@ -38,7 +38,7 @@ class DivPlatformGenesis: public DivDispatch { unsigned char freqH, freqL; int freq, baseFreq, pitch, note; unsigned char ins; - bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnaceDac, inPorta; + bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, furnaceDac, inPorta, hardReset; int vol, outVol; unsigned char pan; Channel(): @@ -57,6 +57,7 @@ class DivPlatformGenesis: public DivDispatch { portaPause(false), furnaceDac(false), inPorta(false), + hardReset(false), vol(0), pan(3) {} }; diff --git a/src/engine/platform/tx81z.h b/src/engine/platform/tx81z.h index b3d00e523..363d5c234 100644 --- a/src/engine/platform/tx81z.h +++ b/src/engine/platform/tx81z.h @@ -38,10 +38,30 @@ class DivPlatformTX81Z: public DivDispatch { int freq, baseFreq, pitch, note; unsigned char ins; signed char konCycles; - bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, portaPause, furnacePCM; + bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, portaPause, furnacePCM, hardReset; int vol, outVol; unsigned char chVolL, chVolR; - Channel(): freqH(0), freqL(0), freq(0), baseFreq(0), pitch(0), note(0), ins(-1), active(false), insChanged(true), freqChanged(false), keyOn(false), keyOff(false), inPorta(false), portaPause(false), furnacePCM(false), vol(0), outVol(0), chVolL(127), chVolR(127) {} + Channel(): + freqH(0), + freqL(0), + freq(0), + baseFreq(0), + pitch(0), + note(0), + ins(-1), + active(false), + insChanged(true), + freqChanged(false), + keyOn(false), + keyOff(false), + inPorta(false), + portaPause(false), + furnacePCM(false), + hardReset(false), + vol(0), + outVol(0), + chVolL(127), + chVolR(127) {} }; Channel chan[8]; struct QueuedWrite { diff --git a/src/engine/platform/ym2610.h b/src/engine/platform/ym2610.h index bc70144e6..2fd525b08 100644 --- a/src/engine/platform/ym2610.h +++ b/src/engine/platform/ym2610.h @@ -46,7 +46,7 @@ class DivPlatformYM2610: public DivDispatch { int freq, baseFreq, pitch, note; unsigned char ins, psgMode, autoEnvNum, autoEnvDen; signed char konCycles; - bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM; + bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset; int vol, outVol; int sample; unsigned char pan; @@ -70,6 +70,7 @@ class DivPlatformYM2610: public DivDispatch { portaPause(false), inPorta(false), furnacePCM(false), + hardReset(false), vol(0), outVol(15), sample(-1), diff --git a/src/engine/platform/ym2610b.h b/src/engine/platform/ym2610b.h index 85b9b4514..d6b616c50 100644 --- a/src/engine/platform/ym2610b.h +++ b/src/engine/platform/ym2610b.h @@ -38,7 +38,7 @@ class DivPlatformYM2610B: public DivDispatch { int freq, baseFreq, pitch, note; unsigned char ins, psgMode, autoEnvNum, autoEnvDen; signed char konCycles; - bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM; + bool active, insChanged, freqChanged, keyOn, keyOff, portaPause, inPorta, furnacePCM, hardReset; int vol, outVol; int sample; unsigned char pan; @@ -62,6 +62,7 @@ class DivPlatformYM2610B: public DivDispatch { portaPause(false), inPorta(false), furnacePCM(false), + hardReset(false), vol(0), outVol(15), sample(-1), diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 0118acc42..a9497d667 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -61,6 +61,7 @@ const char* cmdName[DIV_CMD_MAX]={ "SAMPLE_BANK", "SAMPLE_POS", + "FM_HARD_RESET", "FM_LFO", "FM_LFO_WAVE", "FM_TL", @@ -240,6 +241,23 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe case 0x20: // SN noise mode dispatchCmd(DivCommand(DIV_CMD_STD_NOISE_MODE,ch,effectVal)); break; + case 0x30: // toggle hard-reset + dispatchCmd(DivCommand(DIV_CMD_FM_HARD_RESET,ch,effectVal)); + break; + default: + return false; + } + break; + case DIV_SYSTEM_YM2151: + case DIV_SYSTEM_YM2610: + case DIV_SYSTEM_YM2610_EXT: + case DIV_SYSTEM_YM2610B: + case DIV_SYSTEM_YM2610B_EXT: + case DIV_SYSTEM_OPZ: + switch (effect) { + case 0x30: // toggle hard-reset + dispatchCmd(DivCommand(DIV_CMD_FM_HARD_RESET,ch,effectVal)); + break; default: return false; } @@ -340,6 +358,22 @@ bool DivEngine::perSystemEffect(int ch, unsigned char effect, unsigned char effe case 0x18: // drum mode toggle dispatchCmd(DivCommand(DIV_CMD_FM_EXTCH,ch,effectVal)); break; + case 0x30: // toggle hard-reset + dispatchCmd(DivCommand(DIV_CMD_FM_HARD_RESET,ch,effectVal)); + break; + default: + return false; + } + break; + case DIV_SYSTEM_OPLL: + case DIV_SYSTEM_VRC7: + case DIV_SYSTEM_OPL: + case DIV_SYSTEM_OPL2: + case DIV_SYSTEM_OPL3: + switch (effect) { + case 0x30: // toggle hard-reset + dispatchCmd(DivCommand(DIV_CMD_FM_HARD_RESET,ch,effectVal)); + break; default: return false; } diff --git a/src/engine/song.h b/src/engine/song.h index 438c22143..75e5a03e9 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -143,12 +143,15 @@ struct DivSong { // - 9: v3.9 // - introduces Genesis system // - introduces system number + // - patterns now stored in current known format // - 7: ??? - // - 5: BETA 3 (?) + // - 5: BETA 3 // - adds arpeggio tick - // - 3: BETA 2 + // - 4: BETA 2 + // - 3: BETA 1 // - possibly the first version that could save // - basic format, no system number, 16 instruments, one speed, YMU759-only + // - patterns were stored in a different format (chars instead of shorts) // - if somebody manages to find a version 2 or even 1 module, please tell me as it will be worth more than a luxury vehicle unsigned short version; bool isDMF; From 570c43e33212a54c1ed3a3d2ff3675c0e4f0e32b Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 10 Apr 2022 02:20:16 -0500 Subject: [PATCH 603/637] GUI: follow cursor when pasting --- src/gui/editing.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/gui/editing.cpp b/src/gui/editing.cpp index 9efd436fa..034ca441a 100644 --- a/src/gui/editing.cpp +++ b/src/gui/editing.cpp @@ -562,6 +562,7 @@ void FurnaceGUI::doPaste(PasteMode mode) { } if (settings.cursorPastePos) { cursor.y=j; + updateScroll(cursor.y); } makeUndo(GUI_UNDO_PATTERN_PASTE); From 55639747ee31d2fae110babc49260d47e3d77982 Mon Sep 17 00:00:00 2001 From: cam900 Date: Sun, 10 Apr 2022 16:32:58 +0900 Subject: [PATCH 604/637] Make GCC happy --- src/gui/insEdit.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index c253940b3..b91800224 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -1304,7 +1304,7 @@ void FurnaceGUI::drawInsEdit() { ImGui::Text("Type"); ImGui::TableNextColumn(); - if (ins->type<0 || ins->type>=DIV_INS_MAX) ins->type=DIV_INS_FM; + if (ins->type>=DIV_INS_MAX) ins->type=DIV_INS_FM; int insType=ins->type; ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); if (ImGui::Combo("##Type",&insType,insTypes,DIV_INS_MAX,DIV_INS_MAX)) { From 228822e19efdb848c91475ebb2de21fdfb9af46d Mon Sep 17 00:00:00 2001 From: cam900 Date: Sun, 10 Apr 2022 16:43:32 +0900 Subject: [PATCH 605/637] Fix signed type issue --- src/engine/instrument.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index ce25d87a5..1d8dbe7c1 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -609,7 +609,7 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) { type=(DivInstrumentType)reader.readS(); } if (!istest) { - type=(DivInstrumentType)reader.readC(); + type=(DivInstrumentType)((unsigned char)reader.readC()); } mode=(type==DIV_INS_FM); reader.readC(); From e23dcd6e1bf622e2f2148893534f1024a4077512 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 10 Apr 2022 03:04:31 -0500 Subject: [PATCH 606/637] implement hard reset on OPM/OPZ/OPNB --- src/engine/platform/arcade.cpp | 20 ++++++++++++++++++++ src/engine/platform/tx81z.cpp | 23 +++++++++++++++++++++++ src/engine/platform/ym2610.cpp | 24 ++++++++++++++++++++++++ src/engine/platform/ym2610b.cpp | 24 ++++++++++++++++++++++++ 4 files changed, 91 insertions(+) diff --git a/src/engine/platform/arcade.cpp b/src/engine/platform/arcade.cpp index e9618f29b..f570dd6ed 100644 --- a/src/engine/platform/arcade.cpp +++ b/src/engine/platform/arcade.cpp @@ -369,7 +369,24 @@ void DivPlatformArcade::tick() { } } if (chan[i].keyOn || chan[i].keyOff) { + if (chan[i].hardReset && chan[i].keyOn) { + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + immWrite(baseAddr+ADDR_SL_RR,0x0f); + immWrite(baseAddr+ADDR_TL,0x7f); + oldWrites[baseAddr+ADDR_SL_RR]=-1; + oldWrites[baseAddr+ADDR_TL]=-1; + } + } immWrite(0x08,i); + if (chan[i].hardReset && chan[i].keyOn) { + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + for (int k=0; k<9; k++) { + immWrite(baseAddr+ADDR_SL_RR,0x0f); + } + } + } chan[i].keyOff=false; } } @@ -605,6 +622,9 @@ int DivPlatformArcade::dispatch(DivCommand c) { immWrite(0x19,0x80|pmDepth); break; } + case DIV_CMD_FM_HARD_RESET: + chan[c.chan].hardReset=c.value; + break; case DIV_CMD_STD_NOISE_FREQ: { if (c.chan!=7) break; if (c.value) { diff --git a/src/engine/platform/tx81z.cpp b/src/engine/platform/tx81z.cpp index e9e817cd7..aa4cf1f20 100644 --- a/src/engine/platform/tx81z.cpp +++ b/src/engine/platform/tx81z.cpp @@ -137,6 +137,9 @@ const char* DivPlatformTX81Z::getEffectName(unsigned char effect) { case 0x1f: return "1Fxx: Set PM depth (0 to 7F)"; break; + case 0x30: + return "30xx: Toggle hard envelope reset on new notes"; + break; } return NULL; } @@ -330,12 +333,29 @@ void DivPlatformTX81Z::tick() { } } if (chan[i].keyOn || chan[i].keyOff) { + if (chan[i].hardReset && chan[i].keyOn) { + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + immWrite(baseAddr+ADDR_SL_RR,0x0f); + immWrite(baseAddr+ADDR_TL,0x7f); + oldWrites[baseAddr+ADDR_SL_RR]=-1; + oldWrites[baseAddr+ADDR_TL]=-1; + } + } if (isMuted[i]) { immWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|0x00); } else { //if (chan[i].keyOn) immWrite(0x08,i); immWrite(chanOffs[i]+ADDR_LR_FB_ALG,(chan[i].state.alg&7)|(chan[i].state.fb<<3)|0x00|(chan[i].chVolR<<7)); } + if (chan[i].hardReset && chan[i].keyOn) { + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + for (int k=0; k<9; k++) { + immWrite(baseAddr+ADDR_SL_RR,0x0f); + } + } + } chan[i].keyOff=false; } } @@ -596,6 +616,9 @@ int DivPlatformTX81Z::dispatch(DivCommand c) { immWrite(0x19,0x80|pmDepth); break; } + case DIV_CMD_FM_HARD_RESET: + chan[c.chan].hardReset=c.value; + break; case DIV_CMD_STD_NOISE_FREQ: { if (c.chan!=7) break; if (c.value) { diff --git a/src/engine/platform/ym2610.cpp b/src/engine/platform/ym2610.cpp index 77c1243bd..0994f0e55 100644 --- a/src/engine/platform/ym2610.cpp +++ b/src/engine/platform/ym2610.cpp @@ -309,6 +309,9 @@ const char* DivPlatformYM2610::getEffectName(unsigned char effect) { case 0x29: return "29xy: Set SSG auto-envelope (x: numerator; y: denominator)"; break; + case 0x30: + return "30xx: Toggle hard envelope reset on new notes"; + break; } return NULL; } @@ -489,7 +492,25 @@ void DivPlatformYM2610::tick() { } if (chan[i].keyOn || chan[i].keyOff) { + if (chan[i].hardReset && chan[i].keyOn) { + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + immWrite(baseAddr+ADDR_SL_RR,0x0f); + immWrite(baseAddr+ADDR_TL,0x7f); + oldWrites[baseAddr+ADDR_SL_RR]=-1; + oldWrites[baseAddr+ADDR_TL]=-1; + //rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + } immWrite(0x28,0x00|konOffs[i]); + if (chan[i].hardReset && chan[i].keyOn) { + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + for (int k=0; k<100; k++) { + immWrite(baseAddr+ADDR_SL_RR,0x0f); + } + } + } chan[i].keyOff=false; } } @@ -930,6 +951,9 @@ int DivPlatformYM2610::dispatch(DivCommand c) { } break; } + case DIV_CMD_FM_HARD_RESET: + chan[c.chan].hardReset=c.value; + break; case DIV_ALWAYS_SET_VOLUME: return 0; break; diff --git a/src/engine/platform/ym2610b.cpp b/src/engine/platform/ym2610b.cpp index dfd847235..7e8c25194 100644 --- a/src/engine/platform/ym2610b.cpp +++ b/src/engine/platform/ym2610b.cpp @@ -373,6 +373,9 @@ const char* DivPlatformYM2610B::getEffectName(unsigned char effect) { case 0x29: return "29xy: Set SSG auto-envelope (x: numerator; y: denominator)"; break; + case 0x30: + return "30xx: Toggle hard envelope reset on new notes"; + break; } return NULL; } @@ -553,7 +556,25 @@ void DivPlatformYM2610B::tick() { } if (chan[i].keyOn || chan[i].keyOff) { + if (chan[i].hardReset && chan[i].keyOn) { + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + immWrite(baseAddr+ADDR_SL_RR,0x0f); + immWrite(baseAddr+ADDR_TL,0x7f); + oldWrites[baseAddr+ADDR_SL_RR]=-1; + oldWrites[baseAddr+ADDR_TL]=-1; + //rWrite(baseAddr+ADDR_SL_RR,(op.rr&15)|(op.sl<<4)); + } + } immWrite(0x28,0x00|konOffs[i]); + if (chan[i].hardReset && chan[i].keyOn) { + for (int j=0; j<4; j++) { + unsigned short baseAddr=chanOffs[i]|opOffs[j]; + for (int k=0; k<100; k++) { + immWrite(baseAddr+ADDR_SL_RR,0x0f); + } + } + } chan[i].keyOff=false; } } @@ -993,6 +1014,9 @@ int DivPlatformYM2610B::dispatch(DivCommand c) { } break; } + case DIV_CMD_FM_HARD_RESET: + chan[c.chan].hardReset=c.value; + break; case DIV_ALWAYS_SET_VOLUME: return 0; break; From 2e6193706a49240b87e14e74c1aa652c9f56769f Mon Sep 17 00:00:00 2001 From: cam900 Date: Sun, 10 Apr 2022 17:26:50 +0900 Subject: [PATCH 607/637] Fix value fill --- src/engine/instrument.h | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/engine/instrument.h b/src/engine/instrument.h index 6d79f99fc..c22f0a95a 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -163,13 +163,14 @@ struct DivInstrumentMacro { signed char rel; DivInstrumentMacro(String n, int h=~0, bool initOpen=false): name(n), - val{0}, height(h), mode(0), open(initOpen), len(0), loop(-1), - rel(-1) {} + rel(-1) { + memset(val,0,256*sizeof(int)); + } }; struct DivInstrumentSTD { From 518841c17ecd49968622307cf1fb2e62a5d035b5 Mon Sep 17 00:00:00 2001 From: cam900 Date: Sun, 10 Apr 2022 18:36:04 +0900 Subject: [PATCH 608/637] Fix FM layout in "Compact" mode, Prepare for per-operator on/off control on FM (possibly controllable with phase reset macro?) --- src/engine/instrument.h | 1 + src/gui/insEdit.cpp | 27 +++++++++++++++++---------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/engine/instrument.h b/src/engine/instrument.h index c22f0a95a..b292b536f 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -81,6 +81,7 @@ struct DivInstrumentFM { unsigned char am, ar, dr, mult, rr, sl, tl, dt2, rs, dt, d2r, ssgEnv; unsigned char dam, dvb, egt, ksl, sus, vib, ws, ksr; // YMU759/OPL/OPZ Operator(): + enable(false), am(0), ar(0), dr(0), diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index b91800224..94b9fa973 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -109,6 +109,10 @@ enum FMParams { #define FM_NAME(x) fmParamNames[settings.fmNames][x] #define FM_SHORT_NAME(x) fmParamShortNames[settings.fmNames][x] +const char* fmOperatorBits[5]={ + "op1", "op3", "op2", "op4", NULL +}; + const char* c64ShapeBits[5]={ "triangle", "saw", "pulse", "noise", NULL }; @@ -1846,8 +1850,6 @@ void FurnaceGUI::drawInsEdit() { op.am=amOn; } - ImGui::SameLine(); - int maxTl=127; if (ins->type==DIV_INS_OPLL) { if (i==1) { @@ -1867,6 +1869,7 @@ void FurnaceGUI::drawInsEdit() { bool susOn=op.sus; // don't you make fun of this one unsigned char ssgEnv=op.ssgEnv&7; if (ins->type!=DIV_INS_OPL && ins->type!=DIV_INS_OPZ) { + ImGui::SameLine(); if (ImGui::Checkbox((ins->type==DIV_INS_OPLL)?FM_NAME(FM_EGS):"SSG On",&ssgOn)) { PARAMETER op.ssgEnv=(op.ssgEnv&7)|(ssgOn<<3); } @@ -1878,6 +1881,7 @@ void FurnaceGUI::drawInsEdit() { } if (ins->type==DIV_INS_OPL) { + ImGui::SameLine(); if (ImGui::Checkbox(FM_NAME(FM_SUS),&susOn)) { PARAMETER op.sus=susOn; } @@ -1995,14 +1999,16 @@ void FurnaceGUI::drawInsEdit() { ImGui::TableNextColumn(); ImGui::Text("%s",FM_NAME(FM_DT2)); - ImGui::TableNextRow(); - ImGui::TableNextColumn(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (CWSliderScalar("##SSG",ImGuiDataType_U8,&ssgEnv,&_ZERO,&_SEVEN,ssgEnvTypes[ssgEnv])) { PARAMETER - op.ssgEnv=(op.ssgEnv&8)|(ssgEnv&7); - } rightClickable - ImGui::TableNextColumn(); - ImGui::Text("%s",FM_NAME(FM_SSG)); + if (ins->type==DIV_INS_FM) { // OPN only + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (CWSliderScalar("##SSG",ImGuiDataType_U8,&ssgEnv,&_ZERO,&_SEVEN,ssgEnvTypes[ssgEnv])) { PARAMETER + op.ssgEnv=(op.ssgEnv&8)|(ssgEnv&7); + } rightClickable + ImGui::TableNextColumn(); + ImGui::Text("%s",FM_NAME(FM_SSG)); + } } if (ins->type==DIV_INS_OPL || ins->type==DIV_INS_OPZ) { @@ -2066,6 +2072,7 @@ void FurnaceGUI::drawInsEdit() { NORMAL_MACRO(ins->std.ex2Macro,0,127,"ex2","PM Depth",128,ins->std.ex2Macro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[7],0,127,NULL,false); NORMAL_MACRO(ins->std.ex3Macro,0,255,"ex3","LFO Speed",128,ins->std.ex3Macro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[8],0,255,NULL,false); NORMAL_MACRO(ins->std.waveMacro,0,3,"wave","LFO Shape",48,ins->std.waveMacro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_WAVE],mmlString[9],0,3,¯oLFOWaves,false); + NORMAL_MACRO(ins->std.phaseResetMacro,0,4,"phaseReset","Operator On/Off",128,ins->std.phaseResetMacro.open,true,fmOperatorBits,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[10],0,4,NULL,false); } MACRO_END; ImGui::EndTabItem(); From 075ec9b6de94fa66196ff8eaa5452b797a76a123 Mon Sep 17 00:00:00 2001 From: cam900 Date: Sun, 10 Apr 2022 18:38:48 +0900 Subject: [PATCH 609/637] Read and Write this --- src/engine/instrument.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index 1d8dbe7c1..b9046c6ff 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -70,6 +70,7 @@ void DivInstrument::putInsData(SafeWriter* w) { for (int j=0; j<4; j++) { DivInstrumentFM::Operator& op=fm.op[j]; + w->writeC(op.enable?1:0); w->writeC(op.am); w->writeC(op.ar); w->writeC(op.dr); @@ -631,6 +632,9 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) { for (int j=0; j<4; j++) { DivInstrumentFM::Operator& op=fm.op[j]; + if (istest) { + op.enable=reader.readC(); + } op.am=reader.readC(); op.ar=reader.readC(); op.dr=reader.readC(); From 78b88d61ceb2d67a12787d96e4bc608385e6804b Mon Sep 17 00:00:00 2001 From: cam900 Date: Sun, 10 Apr 2022 18:45:55 +0900 Subject: [PATCH 610/637] Fix format breaking --- src/engine/instrument.cpp | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index b9046c6ff..d39e9c699 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -70,7 +70,6 @@ void DivInstrument::putInsData(SafeWriter* w) { for (int j=0; j<4; j++) { DivInstrumentFM::Operator& op=fm.op[j]; - w->writeC(op.enable?1:0); w->writeC(op.am); w->writeC(op.ar); w->writeC(op.dr); @@ -566,6 +565,12 @@ void DivInstrument::putInsData(SafeWriter* w) { w->writeC(ws.param2); w->writeC(ws.param3); w->writeC(ws.param4); + + // FM per-operator enable + for (int j=0; j<4; j++) { + DivInstrumentFM::Operator& op=fm.op[j]; + w->writeC(op.enable?1:0); + } } DivDataErrors DivInstrument::readMacroData(DivInstrumentMacro& m, SafeReader& reader, short version) { @@ -632,9 +637,6 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) { for (int j=0; j<4; j++) { DivInstrumentFM::Operator& op=fm.op[j]; - if (istest) { - op.enable=reader.readC(); - } op.am=reader.readC(); op.ar=reader.readC(); op.dr=reader.readC(); @@ -1260,6 +1262,14 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) { ws.param3=reader.readC(); ws.param4=reader.readC(); } + + // FM per-operator enable + if (istest) { + for (int j=0; j<4; j++) { + DivInstrumentFM::Operator& op=fm.op[j]; + op.enable=reader.readC(); + } + } return DIV_DATA_SUCCESS; } From 9a6127e4c1ede977c2fb03e5f0e2f370c5362639 Mon Sep 17 00:00:00 2001 From: cam900 Date: Sun, 10 Apr 2022 18:47:32 +0900 Subject: [PATCH 611/637] Change macro --- src/gui/insEdit.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 94b9fa973..685160b7f 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -2072,7 +2072,7 @@ void FurnaceGUI::drawInsEdit() { NORMAL_MACRO(ins->std.ex2Macro,0,127,"ex2","PM Depth",128,ins->std.ex2Macro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[7],0,127,NULL,false); NORMAL_MACRO(ins->std.ex3Macro,0,255,"ex3","LFO Speed",128,ins->std.ex3Macro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[8],0,255,NULL,false); NORMAL_MACRO(ins->std.waveMacro,0,3,"wave","LFO Shape",48,ins->std.waveMacro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_WAVE],mmlString[9],0,3,¯oLFOWaves,false); - NORMAL_MACRO(ins->std.phaseResetMacro,0,4,"phaseReset","Operator On/Off",128,ins->std.phaseResetMacro.open,true,fmOperatorBits,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[10],0,4,NULL,false); + NORMAL_MACRO(ins->std.ex4Macro,0,4,"ex4","Operator On/Off",128,ins->std.ex4Macro.open,true,fmOperatorBits,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[10],0,4,NULL,false); } MACRO_END; ImGui::EndTabItem(); From e6d74766ca6af5ffc17e1f19030d468716b30f92 Mon Sep 17 00:00:00 2001 From: cam900 Date: Sun, 10 Apr 2022 20:22:49 +0900 Subject: [PATCH 612/637] Add support of N163 demultiplexed output so, there's to way for reduce N163 noises: reduce channel limit and demultiplex * channel limit is runtime changeable and it makes some usable effects with disable demultiplex * demultiplex is used for "non-ear destroyable" emulators, but less hardware accurate. (when LPF and RF filter is not considered) Furnace support both after this, You can choose output behavior via configuration flag. --- src/engine/platform/n163.cpp | 5 ++++- src/engine/platform/n163.h | 1 + src/engine/platform/sound/n163/n163.cpp | 27 +++++++++++++++++++++++-- src/engine/platform/sound/n163/n163.hpp | 4 ++++ src/gui/sysConf.cpp | 5 +++++ 5 files changed, 39 insertions(+), 3 deletions(-) diff --git a/src/engine/platform/n163.cpp b/src/engine/platform/n163.cpp index 2800fad11..a61f95f86 100644 --- a/src/engine/platform/n163.cpp +++ b/src/engine/platform/n163.cpp @@ -156,7 +156,7 @@ const char* DivPlatformN163::getEffectName(unsigned char effect) { void DivPlatformN163::acquire(short* bufL, short* bufR, size_t start, size_t len) { for (size_t i=start; i32767) out=32767; if (out<-32768) out=-32768; bufL[i]=bufR[i]=out; @@ -607,6 +607,7 @@ void DivPlatformN163::reset() { memset(regPool,0,128); n163.set_disable(false); + n163.set_multiplex(multiplex); chanMax=initChanMax; loadWave=-1; loadPos=0; @@ -636,8 +637,10 @@ void DivPlatformN163::setFlags(unsigned int flags) { break; } initChanMax=chanMax=(flags>>4)&7; + multiplex=((flags>>7)&1)?false:true; // not accurate in real hardware chipClock=rate; rate/=15; + n163.set_multiplex(multiplex); rWrite(0x7f,initChanMax<<4); } diff --git a/src/engine/platform/n163.h b/src/engine/platform/n163.h index 4bc5cc637..70f842adf 100644 --- a/src/engine/platform/n163.h +++ b/src/engine/platform/n163.h @@ -75,6 +75,7 @@ class DivPlatformN163: public DivDispatch { unsigned char chanMax; short loadWave, loadPos, loadLen; unsigned char loadMode; + bool multiplex; n163_core n163; unsigned char regPool[128]; diff --git a/src/engine/platform/sound/n163/n163.cpp b/src/engine/platform/sound/n163/n163.cpp index 192189d93..b18f146ba 100644 --- a/src/engine/platform/sound/n163/n163.cpp +++ b/src/engine/platform/sound/n163/n163.cpp @@ -58,16 +58,27 @@ Frequency formula: Frequency: Pitch input * ((Input clock * 15 * Number of activated voices) / 65536) + + There's to way for reduce N163 noises: reduce channel limit and demultiplex + - Channel limit is runtime changeable and it makes some usable effects. + - Demultiplex is used for "non-ear destroyable" emulators, but less hardware accurate. (when LPF and RF filter is not considered) + This core is support both, You can choose output behavior + */ #include "n163.hpp" void n163_core::tick() { - m_out = 0; + if (m_multiplex) + m_out = 0; // 0xe000-0xe7ff Disable sound bits (bit 6, bit 0 to 5 are CPU ROM Bank 0x8000-0x9fff select.) if (m_disable) + { + if (!m_multiplex) + m_out = 0; return; + } // tick per each clock const u32 freq = m_ram[m_voice_cycle + 0] | (u32(m_ram[m_voice_cycle + 2]) << 8) | (bitfield(m_ram[m_voice_cycle + 4], 0, 2) << 16); // 18 bit frequency @@ -88,22 +99,34 @@ void n163_core::tick() m_ram[m_voice_cycle + 5] = bitfield(accum, 16, 8); // update voice cycle + bool flush = m_multiplex ? true : false; m_voice_cycle -= 0x8; if (m_voice_cycle < (0x78 - (bitfield(m_ram[0x7f], 4, 3) << 3))) + { + if (!m_multiplex) + flush = true; m_voice_cycle = 0x78; + } // output 4 bit waveform and volume, multiplexed - m_out = wave * volume; + m_acc += wave * volume; + if (flush) + { + m_out = m_acc / (m_multiplex ? 1 : (bitfield(m_ram[0x7f], 4, 3) + 1)); + m_acc = 0; + } } void n163_core::reset() { // reset this chip m_disable = false; + m_multiplex = true; std::fill(std::begin(m_ram), std::end(m_ram), 0); m_voice_cycle = 0x78; m_addr_latch.reset(); m_out = 0; + m_acc = 0; } // accessor diff --git a/src/engine/platform/sound/n163/n163.hpp b/src/engine/platform/sound/n163/n163.hpp index b97de9ae0..a31827572 100644 --- a/src/engine/platform/sound/n163/n163.hpp +++ b/src/engine/platform/sound/n163/n163.hpp @@ -48,6 +48,7 @@ public: // register pool u8 reg(u8 addr) { return m_ram[addr & 0x7f]; } + void set_multiplex(bool multiplex = true) { m_multiplex = multiplex; } private: // Address latch @@ -73,6 +74,9 @@ private: u8 m_voice_cycle = 0x78; // Voice cycle for processing addr_latch_t m_addr_latch; // address latch s16 m_out = 0; // output + // demultiplex related + bool m_multiplex = true; // multiplex flag, but less noisy = inaccurate! + s16 m_acc = 0; // accumulated output }; #endif diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index 8ecf7c07a..5b2aa6582 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -372,6 +372,11 @@ void FurnaceGUI::drawSysConf(int i) { e->setSysFlags(i,(flags & ~(7 << 4)) | (((initialChannelLimit-1) & 7) << 4),restart); updateWindowTitle(); } rightClickable + bool n163Multiplex=flags&128; + if (ImGui::Checkbox("Disable Multiplexed Output",&n163Multiplex)) { + e->setSysFlags(i,(flags&(~128))|(n163Multiplex<<7),restart); + updateWindowTitle(); + } break; } case DIV_SYSTEM_GB: From 86b523a83ebccde32659bfc924ec18625d21ca5d Mon Sep 17 00:00:00 2001 From: cam900 Date: Sun, 10 Apr 2022 20:24:31 +0900 Subject: [PATCH 613/637] Revert unnecessary changes --- src/engine/platform/n163.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/n163.cpp b/src/engine/platform/n163.cpp index a61f95f86..25be01b10 100644 --- a/src/engine/platform/n163.cpp +++ b/src/engine/platform/n163.cpp @@ -156,7 +156,7 @@ const char* DivPlatformN163::getEffectName(unsigned char effect) { void DivPlatformN163::acquire(short* bufL, short* bufR, size_t start, size_t len) { for (size_t i=start; i32767) out=32767; if (out<-32768) out=-32768; bufL[i]=bufR[i]=out; From 62fe2433ce166784f360b156c094fe3908e0012b Mon Sep 17 00:00:00 2001 From: Mahbod-Karamoozian <78406810+MAHBOD-85@users.noreply.github.com> Date: Sun, 10 Apr 2022 18:33:35 +0430 Subject: [PATCH 614/637] Demo Song: YKY demo 100hz update (#336) 800hz was unnecessary --- demos/yky.fur | Bin 17704 -> 34188 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/demos/yky.fur b/demos/yky.fur index 106ed1004b630610b60df810d9676b16f61288d1..fc8188c1901e0cac8905598322040b66f26b6f29 100644 GIT binary patch literal 34188 zcmaI7Wmpv6^FFLdry!jo-Q6W!(y(-Qmvl*YC`)(4vgCr)0@6rJce6`(=kt00{r#W! zbFR53&$;e19YhL`+^p@~=Q@E_K7=j7G1N}Q&Q#;jXzS=^e#$Nkeck@YLT>RF13Ab*0{)t8<=nj9&7Mx0CEL!}uuI){liYoY~ z`*o+K`+8-=|F}9isM-Cfh4M*mM>;w1=#J9yx!2P4>2{&Zx+@B2KKQ1=HOTp-qnnRE z2xhopuyDWMJ*4{vm#2DK8{`nXtdZ}!ShQRTIs56#Z`n#yz{hX-f$ru*x5KZuecr(X z0hpT9AW$~p;Y!#tosaZNw>r_j{cmQdVWNF!U*=n20ar5)@f8tSqW((?S4hiJ=4qW- z?gkeu$V?zdz2r%4qC{3JF>Ry5_0+GO1V%A$y#e!8cQ~ChP8D#!OnyD zJz>E)`j5He_g!{8UN}o*DmhNDu8pRw=-?HbVB7d{(c74D(F?wuhnWpXR`+An__JHi z`17-Sj^ED4%Gkl18BhFM&bY77;&XJ9_58v9ux`l4>qv(2y8a4UH*@-774PSE8IZ=ZIoyBV4*-%Cph3CbTGzv2Mbw=JS?1gq$6 zu>=K@vqRrR7HZC$MHbRb0%{~``Pu5eKObEgx#WkKM>-Q3Zi%crMn0U;)qvfj#IHT| zws2N7dCAfJ>>?!=HBDQ33!FypNB_sN$+@Q9-#9PN9)MPEe2*#gjNr|o|FkfyaKCc@ zFQhHNJ$o0!k$wC$UbZrWdEE${FXQ>So#55VaL^R?KL(1gqHj3o64lo?{Q8~IR6K_9 zwiRu*h3Cq+J9P6;Iq(z`DHY=Tm*W2oZm+(6#q&J|ia7bGzqu?8)uo>zC;nsoQQ}G8 zU^gDiSgzbmbf7BwsvWzEtg3X#Io0{$;L(QZ_4Y={j|G8?<+ugi^G5=x3sX`MiwSuL zZk+?v7hH?TLHXuR(Bi|?kvgI@JG znvEn7_(a4tZ9cz;K4%mT1l$I{@Q(2PZy0dy*jv_Ya%-u}Uagbe7j@bH^e&j0B!w8W{5uETIDs%VU}w+Sznz>^8DvIUc)-!5Y*~qZ%nT7J6BDxb zSvY53#)z(wnquIz$_|HTElcOEL5PvFfyu%B0=#WiG=r*3bVTlPc)l7 zXkK!GUT2q3CWn(Ctd!Mq@D!yS{jzy%Cwcw9gHc;JO~eLxWdG^a>QsgUST!nEs+_EI zDLd=K`%D!nDsJf)p={m15i)+Geer)(WwHKiVs!9}61^0+e-V}-cQ$@zIx^E zb-SRdv1jL<$Wc*xN5XM%Yz_n#A>@#h<#`_zTm4H>bqj(P$R7#^X7SoXCBhw<&G%57 z^6Klc?vLcIm}acfa|}N5zt``fR^9d+ zOW1+u>i>ZwjkUz^Sz*>RqSXDU>f2`FiXu8v*a3;cFtSOIn!N`K4 z2JzbAB)`^tk10mnc#ZX`;5bjHIcCj6&^qGPYtrZZ0qq#{gC%fLI@O-bQH4ct&dLR;0zRDW%l6!l2B1iCyq=b6EZ292=}xhein^HM{;T)OWwfl=3+ZF>lgbtm2RK>1n1F z7_+JJm)NL+f%BzcupUHs_@6-_;hzAERu}SXU*&9fQf}_VJ4R!nsW~JNd-&>{1x!hi zi_Z~37aHC+SmuM2&}LG?uVh>KU;8X&^prN6H&~_CJ=(!&s;oAtu zW~s(31Lu^MpfVRP8v4a0jRQQ5vR;`*lsof^y$9Xh#@h~9ykqW*@OZ~QR2a@1j}e=s`(z3(2exm+p*$(v*d52B$oTB-7e0vms4)ojbASDvPMh{#@eRP0k``5?Y zzMrI}@-iHr(~8?59=!vmt$f7<;Y3Fk$rZdHA1pz2x_A@nL^Z`6IBj0j=lDgh_XV=F z`jlhsl^wW?%>WGBfgZHW)zHc6YqpXzw#`q)Su>ZHwlbl)_YPJYu_gOhc58pTS*=*! zDX|XuJ6KcC_xZ1zQT~|g#O>Az6Y=)@2LvAjKlUcr6r}6s3>eL3*gprT8hu$NiPlc| zi!!ytU`1AIt`;9XjTlF85!8?kxtzf{mrp)7QNueYaZ&X}yi{ zd+A#2s#K*3<<0{sgay8I=L~g?aN1(o6lFLMPQP%^m&aO`>0+UtyXFqDGkIAqg zhiE=g#>sMQNz6)Yf|!9c@?=;_2Zh?`VA>CU@dxA%R_X;=tD|37YVifReZ#J_bQoMU zHwmq3WLz9^$ff`Ii2t#@d~+0SDUDlaXFTgbXtMuf!dyx%sejIDmjlG6r$vEWGJ9s) zVmHMeoC!T-+bg~ngLlHZvZnIw1f*rre1pwXE?83%$Pl8`;L`1;S5B<5IKXr{S)`W)=&NZkk~K8b zpQRg!oA097hH6b05Q>{Ho-W3vw2D`V>c$TGj3|jr*s9RVD*yUg%VG+Y7~!K^)0NTk z{8z`e>9Sv=Y zN6!;4gVW_!aUrlcLi&~7ICo>Z8||Dd9%q(%aEEJwY%c$>YCOXJtDvku+W_Fs5qmvH#lh$z47_d4xr{}}s zmL1qhCyP4$OSjmUDC61TZB_#CH`hDDMD&4&8b>x}0$QB(YyC-JzT$XrzNTNz0-$NQ zc3U+s`|5dvy=G9OG|f1`2cOB(osmJQf_7u+3&X@)UdMU9?ABFBvuR;MiGsc1g=v6B z{I`pMq<9-{BD?SI;sPQ6W|noN{}q_Y&i&kh)K{_1?U*{swSCO9?4 z0;`A-S9{M^floI3hd*+URcr@xoeCAxy&nVpY31~Esk1zq1@AzXP8WD4Luo%*P}kF741u!hkLGgfDoV^fyE?Nqqwj? z?Bfd~Kr~*15x075;ZKDKwH6s5 z;!mdWH_Y1ZuufIxr|)>WDrRhodxzYcp3U6pSeNdE!g&CGZw+pPKgZWQ@LfX;sl@~= z@tQz}x~5BB_q{Tca3^eO?gB2goQiW#34s6nYS`Jvhey`bC<%9XxG7=*Q7&&l zkj+2^9X5MlKTSN>2FeFt~K*y#oXTVYq-M>#N}j;ckl2Mdb0 zHSnlls9jA_K5!%wXnyU#H$P!!JI{WQnB_%`Bzr6A80-`rl{W8*k&w*pezyc> zUhkY7an``z2s47#I4LTiPK@B@etK2GkTuY7Cz>!uZ5;+e*6qe*%Y=$vnbp#pNeWL)EHpXYHimh=_LEwN03r)NL|#jeJ5;9 z^r>-hsJ=y@{+B@gD2?8O;k{7R(fzoM!Z(G-F;;@B&apin9iyKu z@62DQ!3@zll!Lyz3)E9#KUbPqSv~nv@5S3X@kqPA{oW%SR<5a~TStv84t@wF;u;iB z_%4?vRZ)*nb%f}3l(tZVd!v{^4ET=k$YKUIY{;IpJgep+y%P>=?n-#riF5Ca6K1(c z<@Wz{s~@kMmK4jz45RFMG<|1m((eVtECzHtb(28=(&73wjY+{v7rN^1;$pB?Zj`Yv zP%QMx%G=qP=4b3c)lUGjh5MaGKOU)jo{^Hy#A+qZC(^y*m9**(k`jNQm@U4mc1Bj8 zz=nGI6F~y^mjfFX9s(ai(~2b@F2$ZzsSLZN=6`No%v_ zh=4OS2jk2F;pCaU3-c+ zGX0-177>C|5a+-tr>9>o zw|5BLB=WLS68ELlO|+<+{7MxP7h?NagK{rD!+hM_JW4o0(a61mGuf0&*t4_=ooNh$ zN9fiWSfHCZSkuwKZ7GcTDE8qf_+y;_wJS%ca76=+#c)8Ogir58OZZbFanUzj$m4Lc zMtFr4Sqx&$*mmMbZfX2I6FbMwPjlC{F(@J3e&KG;07`P+B6>E275vW^5LLl_1{sUc~NP$;?%o= zt$HCCZcxgzsX-kUqPEgQT}-@`PhB{SAqK^(J1V~1lN8Z`T?iz> zB%}dGS<6809zGrvT@1>#n7o-~+c^?9L}18=YJZhw(*9iBJxGk_NZQPmbfb-xYdMgl zEZCY;S4l9sD?wNpc*;J{oXpgrSeo7^zIy+6BdqtNTD!$diDnSELm!o$@?ybN22ncKYn6Rm~s0GSJU9x4j4lah_HejaL`+Zv8xL<>^?_d!C}zwJ_#BnTTsY ztWC8}Y&?j?CVs^i;t-PS7aJWdeWV+vsLc0X`CM;x57ece60O`xKIDz2l-7?fAEe^K zd~QGG4f$e6zl7IAT2*&y>u6qN_ zs&U*5=P$a2G+cje5VXgt<3suXh5HP0t>B7g73B@Y15(z%BLV(Ab6wo#)!W?lCF^BI zrJY1TAE3;lJH4}(ag4AA|L#jr!uI9m)of|&jv6Y@34>|Z+fuTWp2N3FnIL6lCC)t?!|oV@+bG91cyr7xwN0t8>xpa z=Zq4W0L(mKXNgT3Q3IWOj5K5d!^izbKcTUZJ({Ve`}0~SuI_5_H>on{98+N3=UtsD zT)a#DgsjY#nvdx=NEw|3U@J<*$&DY_Sbf5Ys6P!h>V`b|KE8^^9XwsYr?Ks5 zy-DBqMdZO7&jlbBsFR;E13qqpFmi!97RX|ZA*SWI>AE*3bG;n`<~2Z=pU$}4=acxj zTjl1|cU{IJt*ImP{e8njgScVK zjFFYL+;i7Wy$wkg;mjy3m*1o{epgf712~p1N(`qdTK1ZjC#jD@#n`xYHM7 z`T=BhLUDk%lXerg+)3(;z)Rn)YY~w{a08zs;XxATsy3_!VO1x|^wNP``+*u}wn|2J z7pYFhEky-ncpE9adgIQ?_YN1M8TN~CRuFYX#d-n@kM#g!8E}A;80<#)v z>L#L$x#XHu>V=C81Vp43@X@eJKSEwnR%Pz8XT4@OQ|E?Xe^rSqKK?MA(|XPOg{f9q z(M#_%Dl$4*MRV5L9*#ExC!6yGzY^dCw=kL1^|*vLhF)v=fd{Hk!Tq@sG_ABF^~h_C zsya+NR6hwkh8^=Y@l!JhzM-mpL(d|&APRfmy8)=r>fKK>}$#Atsh? z7ks*T>4JICP7#1$I`Ai+GKjALfU<6dD>-CMeHo`KEhPvTYpYDNY9g%;v(xFzU6%ja z=4lX*cLI^|GL&y%wzbm28&MkD7N~I8ojdGyV~S$IUs=l=VNqd$iN+VCf<$e!k6N&m zxonA9=Ychcs0HGFr%FZrn$+`G%_P<7{51^|l_73&vuaFn*fGk-vY=1gBLp>TznHZl zpniyr)y#xS!?ygc79Do$uGUO(d7YlIHC0a9T?=!l_uLGg)C|dgeWE!5bmjhcBFsSx zfCMgG?AG{#UH|*|Q~o9BX|8f4T^DXszR2Ui;jP;iH&;Gj!8fh}p4a2+p&QR*@}}8~ z4+^>clGkXUBUcBOYf#2eiko)SfvthgNl$DvntSzY#Ht$B1`eI7)NNxy+h8L#f~z}-lg@NBP+?INo9f_K}ne4@oY=YPw)sx#Zr&2>^c(NO41AL z^i%%9Vs3(gSDRV%j1_R~G4Pnnr=^tcX|k$9#5Z0<`rFHf*7SH+k${e=qP@ttO{Ap} zH~5Cm7g)f&PX;*o2lBIt7dF?a2QxRfC`^94Fl^iQN0-$fj`_3{f2icG^Vf*TLH*X^ z<#`Rgs8esCc|Nz}Ykcm!g;!arjE-Rg$BGHKZ6#5MAgG0XuT?#9(o|a)+RAJbuwhCh zv2G|LVmb;j-qa{>&At+mFva?=Ncf+t9v_5rJMAKerYqpX&j&3YyI_lat8n|6L%Z>YL!;^T3z4RcBo>p7Z zZ+JD4V71tltKCbHMh=(cm@6b2jyiufa}0*p$g*DhU6DZE3xZ`D1v$uj6IL9o$&Sie z_+`mF4?pedhxNtTu4}Iys92A}FI*6z1%3PWQ|yTZ0+w$zUq1*HcEPn}_p%VYcja`< z6)d|98=_J`Yy{cV6~y^^TXa)dZJ`C`<6{QoS`54w^6xHIjAJlrnAt8yGPw=|K)B=8 z4W#p^#&O`z$7jAhJREbn$xK_x4Wd8YXgV`Wk-vMTY?}+$Uk>Z9wnl!t^72obC z8Uy=ffZ4pSO3Yv4s^z&V?q3TZP#{F#3kZV)WYa4MuY;z(XNO>%u9hbptmu8{=niXC z@+x*AV8qi1Dl#IO;~Q}K0-(7w8yIt^2zNCM_nNIv=Z#CNLwGUxdeD3iI~tMfE(2Am z5F`d@`?2-Z0vI>`K!Frz5N0y<+x@!n0?UE7cZbj@HH$+puPv>?ts`m*oJ|6vFQb;u zI&pS(Yb1HC^$_crXM8R5m5$o|j*yf#`^+X}bb3D^mEGxcJBz)B< zkE#jv|C|6a4N?#D2zC(i>gplE?kHonp;k-1(N>b)<}{Ejy>`hf2l12s74&)#&3;oK zNGtcyw4G>uf5Lwwxlx1)$M#xp`1H(LDg76Ruw*86vs0Qsf`Mlu6sHcf@)%Rx`RV?M zwKlQx^1HyTGO$PRw)OB{hOGJ50-d!|tf?L~=SZhjnt7dO@)b);vj&*I@VJJ^GFe(R zN)pRPQSNc%GId~(G0NRdqWfft+|mf z58E0L*xF289(FjrV|xbSZy>Un#@ovHom5%4mVFAei%2sJb=+;v3C-m8*k_`2NcK2|iM8nMdacB%gCzt3~sUZVBJ68&J15c)=K zt_DC!&LX;a6-t0m@>T<#T1zP~K=t`Os73)iFgC5EgIum(@!Vb}kLGBm+#BK0m!A)6 z^)NUvz^eHI_|PUCRMCHgo)AJ6xCYl5x*H~=<@8?^#XK6~alZn6eSNn)`01nSdD^`v zrKNaT_9CwJ%ZHTbxlFHS?!{FKnnVJaZcn$GG=n&nkcveq5uK#+A^h+bS@M~17JWNTFtYEW$+SMBN|ChJqqFf>ZqU_^+q?bxR#aHAcAB-fT84(o#zgFU+u(s`)=I zqpbU!@7NoW`M>0-jEht@3<10SKXDTwtWic;+iAdr!m{M76AgN*>1&d_vI@fB64610 zPCs)dtCugBo%THsy<#d;y9|V^ZfPlcD<;EjP2auoXKrwr4jR~AOfY!~1DS^Y9Ad&n zs!rD|eKjUD3zdWzCq;KZe}_d(bg-GHb%%-ZgoxESv{RgcqidZxyNk#vCdu~V+qxr) zd2WMi`Q#oFW%Ydk&Z)$nq%3;<6X4;2a-Q_LLPtDL9rTjf9kQ{nt&&9|KOE;VuBZ?- zEz48abXc?(Sdmng(3(w+zdY5S>06s-%eFxZ$FMH%HKrgYFCRr`02POZN}U$Fb~IfN zI4PF%Gs*iAz2M?8_-{$7itjWL}qe;})|4USe@Sp}k~ z%nj48zd$TwaJoVmtGXA4u%r$W{2zGYI;=|2SU~1bqlB>^GAiNcg$$O=GUp?6aYKUr5O$+x2 zyW*!``q>?-HDt`CT>Joyn&f@)N8*0Ps~mUIt!k8J&_1ycXH3Sm_d1RI7KnX)k6AhK ze3ki8E*5;P1={T{)PaOR!HXEsLhKfJ+6e>9+rnnP_C+}4j}zrpHs93k4RN>Rv^dKw zIgxI;d7q8hMlzFb3f}M7N*lw($))J8+ONeE3_2j9N_<|+p;I)dL|9v_@!3q9G^}0M z0Xbi=QTPQr%;dOFB#Eypu3&OAFLs+m$(yM0Ly1y_4Eac&e^DpNKqMOYw`?=BMZ`^Z zsBL9Z^};5{*1>x3oy$N=WC;Q{8v+>l;ZLPvlW}O^TEHxaCgXay6#YE|pl+VW#^v4f8h0Ktm7LaS0 zG1|?cVpT-xqL)cgA(-oqfafiuQ)?)_=kT#Xevoan;Yg5kJpSt=M;Lbn*Q3W`6ogWNL^&>9DdNWT+QG?#9X0=vkU8%Z5~2}&?SdppK`dRUS#4TQX~!9Cf%nIRGYC*8H2 zRuHntgz^_H(8`2~V46COs7kQQI~@JHY=pNEWXC%^xhf4gc2MhJ^%$JQH%$7E!5ij7 zA%S7?934U3dZdsIpGV}OjE>&J%lUhptdMZ8r#Yfw#tJela(?QOH2W#y= z#5DGdp=($6_MFAOH=X0^9GouOOzaI#rup;>y&ih zw@mY$G(+Qv$9jw{-LZPRFDMf(`cgR785aXNoDg|e^%z@R0RfROQ4iEHQwr9u=j=tn zgY11WPb4Z#+I~36hyhHg*Bj1YD$p75s}xKicXJW9NNUhfl+7D;_(K0Y=?urxsW#n* zjj?jH{2MQ08h^7H;C4;+Yhva0G4m8>Noo)WOXE|dDnugMC)uGuVo6KNi$u=zPN3^tc^qEbwh+{VbB|C!UkHRUwb_m4 zXKWZIJ@9Wbu)z5WW!2Yc1bSTRcRJrM_(fdaRqbrC-9Wn$tDdqvFuLEdtpl`M&;bnE zP=*LK!Q3|64A6n;9r9%W0%{fxJ@>!2SDg^S0Gq%H{0_)>I< z#+uai^By}gwmMgOghu+{7b{BEbLl{w{9kf!-xSW-ivHw2{!KDNBoh>-t@Hel&!5*l zS?y>;{x+{%Ky$xKs9;p>wuU?GYvrH_jo~m4&>MO+8F$PuXWM`` ztsdsZzfVpjqMrP<ot{Qo2egyIBK{H~C6KPx^R;KmmI)ZzEpn58KjKDO~yH9ykY->~@! z_~nRuU?QzGTVa?b0Z*ICU(-j~P8KQ!-YCE4W5Sd#c);))n_xkX3-yucr_3x8qqEx5 z75GLBbd&_wQ?iI-vo~8n9p>IL^>-e7%{tW@|8)5dD(g6r8vHz|#xiD2a7n?)H|l49$*D zbl{38x+hkbb3tg&a;asuxK*N71b>zhE|HiPh@NX4N+Rb)x+mEB>Zws;-bvRlbTL@C|;&SWHxzizdAxfY8MG6!;qZ6V0?m`6WCe zG7M>@&D3?KllbnEsfX&x|sfH3{6Ku6*0w+!x1naaAJ-WRE~M0f*i zVa~YUKp7RNT2(MwX?TBCpP85f&M=ppJotvL3&Gqlj-T8)WaS(IcNd-BcoH3ZAe=!# zTGka_q$R3e^y_V~7>j!V_>9nvxmZMVPP%qbr)Z@5Xoa4t=ROEkw9!1DX2f@IR^lps zV&eH*Zr-mA{odfLq4v(qohSU@QH+1H$(&E$G&GpY*&)FK?w2B&#La%ri{o zUbu(8!g1$tp5y#ZBT!C$tgbUyaWeh)xOOSyZB0R`TQc0zIL)qb_HFM84N2~^><;_q zF`}KcdC0w{wH*QCElb(r_k9tW^EuUK7|~w~IIy`91q1R*CB67yCak;{Vg5$Gl=en& zR@JYn>xgPgFa7H+$XZiNB!Fzv@7*|G%00Yn>kCpDfF1^lsK2_=Sk#Xa%k8wopn{?+I+kZ*1*HQHExpMZufb4 z_gstIF}@a~xF>4hY@+9pDprZqN{apAi<_wMlKR3X@dw*X(~4s1zd0KNgv}#KYuV|M z$GzQ~%5m4DB-^It>hE!kkp-jFS=C+^b$^NXLZFvw=3~1Vl$>{?PD5By=qNo*w_Fqh z43kWb7Z_7vts!_F!<)vBDrB@tKAQ$r`Tc~l3uCGm`Me1X=#AN6Z;A!`ulb?=gn+yU zsoTXa`h@5b&Qgo4&Vusne3dfMuxWPdV8{oQ1DZhuy8aN z98zBfHJvJbEKAP`v80?{YjOGachS)6O$_FlZSM6&&sx*x?8@HwjkVwbJmFm*-J3}rX#2jJa=ykbNMI7^w@8Y{&p6>X_Eh7HaV%pmzBTkFlGEmDl z9p&=YxG=fGtiWQE%ahq>zl#s!aM>g_4P`FYLnH-H?Qx=#A5fPhw0>~_oVXsBlmlZL zeKXK1XP$Dug2R&2Vufl0o~*56`78T++D%u&28)o4N<2Z1Krbc*;q`a){@8Ea9ukO2 zoyg;6lJUR0^w>*CgUCTN5wMoVwes*UrgJXADDk(XIjf4RW z?6=SCRd|b)JjK=Z(-ogb)v~*Xp!VkJ0e|`mxl@{j|M`dCw_)SIHF<1G&IK>2WKx$>;*G=)LtiCK#b zfsOg=o7Y?|_5tlH&mlP2q&1Nu86|Q{^+-mBuU3yKd7R`_%cqC7^vzlaGpwBojb|}! zr2aB@(LIk8gOTtK6d+MOqsV>^v(H1w26%JSdQL#HizM?9M42S$XbFCHJau~bO%KPvpqC<2vXFYU0PSNhTDYxgI={Uw*+;S?&V+fud zQ-n`Sept$xfV@MyVHrnFL$a0_IuBOzP*=aR{g$^baIgzVv*_@+;Tx;sSf+k<`izxO z9D}&Qye?fOr*oGYw~x&toTYvmDJGAi(@olE;_x+8L^7Fhue8#=UsZgr2M{JTx=VnO z5(%$Agrp?7PYt#65O^v;uQpM4FR0G-ON1X?fdn~3VMZmX*LUGcHSHKC?>a1+fdsua z2gFyA;IoFOXom28eSI#YqL>tlSBV}6eHtU2uC>sAYbh`hD?Z<7sM0_jn3#X~j7~d1 z_=_VamXg7pvv86a8_2}o&8w<0_%K zeuFb7lRYj_5b60<9K`SOKz~spUpygxQgPs=CUXrL?c2fieBP#Mho7GDnE>dB1$()( z_zGE3N7*>@M0O60@|g}0ZCGTOwbX(_~9jL zP`o1BAN4xEYQ|5y+B1xKtZD)Oi4ZQEgPz9;(mc(4QFlH}Q6iee{}c`Jf!laFj2~dq zPqrl-uqUUEKfi^-qb z#jWe#4XPz`;gkX2@ov0ijV+hg&^NsbRMiNy(XOk*gZb`T0Q!%GqJuzxoh-f%<)1gu zFDW}Cx93fjxPXKzRT3W226uzzFoHNgSM?;@3B9|&|4jfafXWB_o~4>cBkfsc<8`V7 z_6EHCU+wiqJ_u**eJC*baH1uJEK23o@}T0I!ngR)W^v}rW_9L$esY@3C@Wq$&33xnoM;px`J0*TwD``WAfrT(_miUt1}j z;cMb2&{tGZ(}@}egHVa2QXxBmQak?#?SU5fPUA^Xhx zNzDo#+vvHi-5$m!xrBVxuPfkMajoO*y_bHx-je=b_FL&T$FC;#C_wH z&epesv?hl)lFa2poL^aXckQj`CqYyWl0s47b_^E;pBT<^9>FcX--!Bk9Nog$L`S za;T;6EOndX9Q9OuDu;Z8Zbea72IKzlNIlk7KG}ZbQcq-7fl6OPg*8U;+j!C`KLOmC z$sY8rIX)aJF7i22H%z8;73 zF^FYvx{{^oK!huT+96Uhw)aVgul=r-NAW`PHXnLH0b{Z4{N{iBb*Ow>{t2zEd721S z;RPn+Vz9_N&TVaKr{}-;93yRf$Rg93*eJ`VDJED~yLWM`(B$v0&A-RXn`Y8Au2I^s zSon`NX*XC3vyMy8ETTXPs=>a^m|HL`?r_^p+hUzL{;zo%=*)ANBd$Bg!zD@6XmT|A zDF$rCt&z5c7sgLpD!k5$CDN?L%RVKk=8-+)%eeq_e;D)aJ?%DUR59ewpWe%NAB7}$ z3P=D?lwB3hjE_~mPskH}Q6`)Im&^4MX`aKi*4A~MKF}F&c`GD(_ z+S&JgsolxYxbGFWh3ew({4{&_MYAtn5`Q$;KO?-7Q-u>_a=4;UsvBnZdpNyqhTK~2 z7N*#(aS~SmE7jI6 zq$PRJk~Y1rPP4!d6Kf_R2ZgRJJ_XgI^V?g&QO@RuAw@(xzhO_)IXr*A3(T*@b$58K zwPD#q7CSk4Hv~j@H?sd*{_&~(yKZ&XaOUak*7h#%}moBzxsx3BE0o=~d+VTWl$NpR+K< zvBEaPvcOT>)+x$a<(3m5bg*=~zOMpw)9ozm{arRZ?dzas!%H+c(FT~9@0=<~*lU9a zyE%_d90ee#gsSw^l!?8c{?03JFoaiv(HRSy-AB01*o#9}NzFU5e;1vNVEkHB=1kYo z`C)=SfMtI+&r#J`fAy#EA4pt0O1l45POjKyHJ9{~UNO&TTfp&d4%O8$^TB26V5pNP zNQ^{(`L~QeVq=Y;2bixL>(@*!shZb4a?riY-q*RVX_X$Xv391#Ei$x|YfQnG4ipu< zfH*pYrPp1XV^2{6Trr>iaSP1V{fU#m-z}nEchd)o9wr{JASgE2W0>P-F?aM|qkHXY z$NvEuo103Csd@Fd^nW*hw|sEhE8n!zS2HW|DQRKgJg~n=nO{Y!;s)-Gw0m~OzS0U! zlW*|P6ddQejteZcwDbE99q-DC?3N6vg0lX*wNPvZG`ea1QW-xvqAU>|w|KT{fLHD z6~)sLQa8es(Cd6wBCNR0+ioH1I;NxhWB6*SeCV;d_cUZqc0Cd4ceMZ4$-j-EX009{ z9hY{GIgbCjg#-{Wtvwm3EQe&&2&m;B|3z%<^qW}BS>(yc-Fq!bNt#EbwZ|2~##Bc3 z5&GRBQVN+pBE3{X*CG8>Sq+i0HTbBYh$eu=!5!Y|Pyg)%^BBAaQMT0)O~K|GH3wq~#I zDnrcZg_NE58Dczjt0G8_oIrUt%5h*Vj<+ z(y9$omzgU#^9~h|mG-l$*sz-}ZlHZec}+4ek6S85#10dUgp&#;!zl4&VV7 zUw3^ST3SXWjD$3I>P|tJx}>Itu^Y{Q4}20OmAyk9*z&PAIKi8=-;+w@%nuCyKLDvf zR=@N8$td163UrJ8Rn_rlKvf6TCM*TGL@0wUyFHE^#*vlFUucXl|E-7Ox4`o%*x`@)k=B z%_nF3X1C3>Pc_F6%#3ms|g{}sh0v*_23#6$Bv`si@ zgNGr}y|AjJWncQhlBKKTsaRgtGc*jUv_%rIG1R|3nf4@gY1<6B**R|(>0L(LF-Ta# zL3wS>)7$hHXklrKAPKo`OGDW)=uCg13$qztL#WH&Qr1$&^-xxaZM5v|1@f>5y4wQ} z(h0qdKn~lJfqCvOxnl-3SIkF(3+FYWM!{SBc>_^J-9^1xCnv}5xe zHKRWn$nJp5QMzJzKcu$79rkbZ8c5xUn^H{8+CmR7AAtX$-t`xDIp8xeYEOVVL(Lz9 zeW&~lns%&HSNbUv15U1F(9n*Of_o+Ho9D*k&G8-aJ#(#z_9W@VPs6ZFZH^Y$vJOL= zG70(L>hjBmZv>CV-~+<`L`f4Qez$iL_LbD2Zc(zx_r0*|pzuO_@&F{D8(KzcQ&#E0 z+Y5oL^?u(hEm7w@Z85bln(Odw^bTS_9X?7p{TDqLf2XW>n6Qm&8F6!Ff9VM9($-Rc zDJ3xh+3SQZ@0C5@3;)U|C2$n-7KOAAfk)%Cxa3pvPH+{({q@6lRTxGCQVg%Zl?%a;l=FEsY49(_u z2ZDo;cv^H`H)x1n_GIX7`_i-^L)jkCY`c4>tWA$Sid7{Xw9H(qg|jxm{*mWlNXDdx z-=b^`Vb>edL^J)#ZSwx4KrBzb8bkX|97S(H>rVdl=7)j6{Gd$}C_@V|S+*(IRPwSb zv{e1cJ#!moHvk1}#=rEt?U0InzyZ7A-RWibl}?rsStLrv0y|+(Df_e}w0-0^sn=hK z3XRdTcLMX#iUhJ9pjt0vsxwQfkp??b)WKxBJ=q64$L|u`CDX){#4^44F4zj%)h0-@ z@pa%CuH0HW0Av>~N(<>qNhRlj%fK66L7QfI-@S{p;_XRFJ*^-#Egyg`jKPwVHn4Xt zdI{PvV#VGfs^|oABfZ;&jA>7MpcUjY^}9D8wpqBH$hb!sw-rz7AN6$5ST=5xbDyhC zzJcH1r>%J++hH4R_-Ky&ZnvLu|ELR&*Wm~b#c@ypNbax}PUr)Y&plQGz!0VmO;F+?+@DEDSX97&-Q z;u-2H?dle3I<d|mzJ0oH0%#Uy7!gtDrtl4)0U6fNx}AlW%KwTcA|t?bRNwS z;c&l1i=+_0LEch2Ns(llR*5LBHBbKdT}_ZC;;`0YG(j#7+sMDLVmVq3QhXTiTDHKl zsI+IqvD|l9-i>yb{3SjnCAniOLgMRLChx zcOXl9ZnRobvBPu}V-m~OPME>-CmOON726|8a zOBz_V!6+)AoLaN(0%a))q%ikM>XM3-M=MSuwrLW!v}A<)=3kU&S|uyer`#ik6Lm)w zKkzU9Mp?JkX??$4zmsB?+%XqG8KA^i>+6t=aDGcZxlZO7sO4>rVNqbvysLK{tv7Gs zRcm*aHRI=9HtT5&SIN~-4!Jf4EG^lTP=(oWOdT;G8 zT%;05ZY@fm_3YM@7QH)no>HVoue^F#6df+EmUEu}dswR>9m3MD4r9q4-?2Cplf-fN z{LSLT`M+Zq-m@rtez$`cC=)JHy2A`!cy^p8tmb-mCUb#C?|y^SfA?waiu-!^^L}x? z|1$UN=qRqB<2_?r?{=8M54eKkew2~l`~8pl?JIeF^t=88@k}#g(m%CrhPb~!xgp(N zp!Z{BPWkqij)ixZZ!8}RZ!AxgFjoMi0yN%yX3yF7WOBAU$Jk&u(0z-C{(=~&2XRK1 zf6~L8R&$)ODv))C-oGiZ(M#V*F4L~Gc#Lo=b1i$PnZL0#V0U?2$zc8Vn&HZgb%~O& zjU%;Bnd#95q5z`c9r5<$5F!wIfL$dGZpI(GU>k|PcYEP|e6%RhTx5y3mwAhk&~7ht zFCB(b&qy5zXpXT(EH)lz>`qTWbhq0J9Pc3RZ$iY(9NI4Y#fYQJhBz<5sAs?lj2_z6 z&=el5*cI95>IGitoZmLHce;HlIkD$#Z*1G?&U4Yp-dHcb4@vS(mUUDNRPC$os$^Ua zL|VGFv_VlKyc1cImr;trYm zL`hp@EIbH2$$c`H8eNEi`b4OEGQ^I|YHkm;)b@lMY6gOg5{+RFZ0~gUWOHhCvU7g# zVs8u)&~z-;9B++{=7>}p+~hcYc9$PGAh5A~7e;9cw0QPpi0J!fcf|WJim+Kp$EsbC z;mYkbyTM6CEr@0Upi^(`!0E{8<}+I$e+QPv$Ynn>8^pgvEM1V>Z9wSTmbT3_$2$zO zW^D}vRlDoLm6K(_UyhzYo1bHjm zsx~<(%{$HZPakKY(;X>hV~4Vo(f&f4 zgMK`+MBmz9Ai`#BFi^#OC$F^fG#ivjJum~ko$5T7oY*iiekM5q`o{Mzwv=`F#=^TI zj64~iGB*NCkmT-(B6{;%7TX+sY52MduBM}+tz^5K*#^c9!~(;9P}tWK7;uiKHY18= zBs~PQ+~77o=3MYQBz#A_bAHPrwBH9y4@ADzwK+3pXPiQhy2-gcG+42-Y*Tb=HM15( zFJ_wg+|6e%|a0t%H3`bL%8hkw>C>5Z9n=$q}H+;g^nYVWDOiSZO|)uw3A@=ei}8~oKR zwFALP<%%tIM~!U8Znr5v)|=SsB7(DW1d%1;U!svf){2VvxwavWWnO3~OZ(TY+&`mN z#wSFqeQ9O_2b^I_Gx)m6$$a+)M8ZSoI_LWff$V^DxUxglivy6P@%Vum=7RcW8Mid2 zCOv*tJlaZjmO;zwH>T2*`ObOfMqs5AO?Wr#AE??D*;TR?5ihKEay-6mhWv`m^d=a& zDaPcz!Oga%rU1FUtE49!_S3esAYz97z_r91up)-ErXT2yndv=eG14M4g_J)a2vD~R zUSQD2yzXuenn=sqh_=C_Eh6U-^d~9So_Yc!H6trVFwbr;HP*`7fM+QpeC$l$-n+QTIg}j;G8a8r%Us<^ zXdi4dv*(QBjbU8|R)20soLTNI)2)dOvy^UDLzuld0PEIY@UwfW-rv|!501bhF&<8) z!#3`L++p@}($ix0Jzs}l`o zRnKyN_102)X=XlHBVzV%bb%g}HYqX_m`CK4WZc~5=<<)aca|}~xT}P|fpWo24C3KX zYaV9R3Lg_$4~jMJG}*^20VG&c!(>CQ2J2`IkaFdI0xWtv%?$dY9lyZk#n9k58O z-Z3`X=mi4tc2(|zMYFsxBk%#If!zkLyhJ4Qv1Xd~ESBi8Z%8xWLNC!bn@m$viMNS| zm?vTNj&UO^5>0`DV7sC^vp6(AG*9fsY~z6?#MeGnY7!BDbw|CwbQtmm`vGgV19*!$ zc=%T6M(4Ti$t~0U$z&SXp&F>BF42&PFzZfKN;!nR$THfqY6d`5iNHMH@dKkV%hQ=< zZH9HsF56BIk?1~GpR#wXrm!|QnqxMDbt=Zy9X{HkrT{SpYbj&e54|^K5K&cv-XfW1 zq|F>SGYa&(%_(M+DLYL8#Mb_%FeqBVO3`lkE!GOG3Y>v`>djL}np5<_h!`O;y|Ls( zYpnT9Z)`l(n;@d|yTbk^JH0!i6dx=Qa0RP*J%L>g#^}tE(kgW4c9rZZ84JMkl`)G+ zOw(lF=c0VVLr-;2CMWtP_D=UrBxkn)od)LnlVKY%$m#F@N@0RX^wPA(QiXa2oeYE?)4YY9^z05^MbF6+HfG1zT1JyZ`>lEA;Eb;=;xFA@PHRsT!MfRA z=uP|7ff;J+fOF8d)5ClgaZZDqIlWzup)9Qhy9k&WrGB;Mx4XNQO{FiR&Jv|J*{xb- zbb;ATBDGi^_zZZ1zcEWeZDrlI%SMeDUD&Kl_J;J9McQ9hQ~G9Usrr)x&H>1HSLHBk z(bZ#NO4eAQ$<8W96!v3tX3HXLKI5r9b3`n_y7l@SG|nB1u-Dxm^Cfdg~)q&3`B3t@keGDHEvt7s;rw; z?P88UAk0jw;)gUdIwb03F4!32cjmlUUqEys92LkCc`y!RwP^DkYhjcC@Dto_DPyFu zv)EIx-OX%gRJMWCWd5HOLZS**i*|e4O1k`;oE@;5%)M?|B%W)HQ7+k^0gG5@PBE9* z21K#R+2v;(v9FX!ht{MgzSBL^{GnZ;#<0S| zc$PIwc8?5J_)Ej!R6~f^k`)rhA)WKP90AA{EUBl>K~FJ+-Lv*Y+(CQGdIGyLx^w)S zeuEJu^Mj)~#?bUMdot9l2DgPRi9^CR&dNM~Qk9?H{N6>zwY0HZKi9=Bl6|EuWy~au zxaqChlf8M>LjA0Hkz({&jG36XCGQZKI43>qtzac{FD%lIcsJ}{{}gLClu>?v(1%Dg zxG6do9t(E?A8!f{RtyBK$a&C^Tbt)bg;?5?L^J(KM(~Vi*(+pK5Gd*FdZATf27SOu zf4|SgoECeO5X%IKcejT|+>~Zo1GBgR-iCWlru{DNB;jbyZ}jdg8*z73&_3=2m6<;x zHTPtOZM6PP_HI=lh9Dnp4pycu`ccYNqk<*f0+h?HCB_rXpEI*ftIZgKc9oGat8QCM z6D58ZcgZM;NRr4yzol9A?3-;)ZK6FcV?Pe%Yo~{6>=S-v&kuVqHbXkM%}~~c=BZ1J zSJ-0$e`)7vTi`RuZy>eukr1Q)_5wR&5Xa}3)nz@9x&ARbkudS^hBPgV)tMp;H`~3y z>Te9OZ<{g<%IlQE?f_S_2ZI@Pq8C!VM>aZ9!iozkR{Plf;!a;DNKoMPO-+>n;+Nj(FN9Nj0yXh-x{o#^fb74de}|D)pf|{ z((*IHWk(ftrpeCC#Gs+?p-;8?r09ESDch6Op#J1uV8HP>EeZ8}6Rak2AMlVWne2q& z7;T6^SUu(6-8srIJW-CBU1D3mi~T6<$m?EUcM$D6BL~)Vtp1fAP`bk~Uf=Bn#&jA< zIqYGfFU>kmhoM)uvV*JxST*P#Rc_h!kif2Kg~B#^P4*&qziM|td(aNw*$Gih8Q_wwCx`;d2aY!#tRLfNZ zSpbc(9z>tenzyQx##nr}Vrph#nAIWf+>mArVdbQ({JJrW5`oGp>01${`B))ku7Q*w znqj=c4m`>}aU>!qMxzDBt6N<}I9ANbcY9USAzB+%1%+`$m+GpF7rT^G-K44s`vZvz ztg0M!!K!&jAjjRXmaLI8>p-;57><69-{ZQ;v2Ch_Zc|O3xkuLejp{JW8A9V#!?3C& zd~(<9?;uL_yNFoN*Ji9*8b{ows-{)lCyy9KCsb`_FH(oEO);-!?HE6>^46yeVQ02O z^=l&4HeJ!+W|!NTsvxW`keaOf4Xe@(gsr^4RS9H8n7fav24U4ljA~_3p%L2hs6OWo zH#x2T4OY8I$uaGhBeDCHD z`Z|nNS36%Rd9>vz_tVcW&~>$vEfAHUAm@xFQgH69=RuK!<2*4jH9 z`vP(Mx{{V!W1dKinEj5&U3ZoLu+&7p=R4#bu$ASa>F+NbJrOzn=%JypzVY^xqmu_R z2Qtxgd#V}x-tX90>Ixq6e%%~hdeA-S?h5v2%W_TOj+*Tie;D0ZS__Q3IlmH~qq#6( z8+2o2(_C1;_-F<(qpNq$OuA3J`i}d=SH@arwkp}PTeA_D;LX-YM zcaOa+*XA2@Mbj%iBZj%I4o9th#5|fglS_LZZj4vEf~PC^t@hN0gy}#}VqRU!)t^GTQh9Ze*I_>$B&{l6l=;1(z z;{n^(O`C7P7EOQGc_g^iyE(sQI+}Jb{l!e{%cU`#M5Vwp7{Q;AD#Ts$>*oX#1J33`he! zIg>k+%gg~el`y+Pnt#vto%B|3o3GY>h<(uxeqJ|KHu3wDpN)Mx9zFG)Q(qZ7Gchz4 zOROsy1Kl6M7+bv$2PXYb_~X@MbtbaCqQjAyo1JPtw|DkOsW#t!$9J8R{(-9RI$NA+ z&wfW&kiO}=&I6ez3y-_nQ|lMYa!==XOl9UqCp+I+KfVDdr#~A_|Ni8Yg|C|@{QkPh z=mR(1wdUc*KMOyaAw9lx>h5eauKl_huub|&-8Nq$bjUmCej4&|Cf8$6ggP7pwkHeU zUznZB9R12z9aggn(N7{YP(`lXoo$_Yz=k-s{Oggs%6FGPS=a$yd^;Y9-<|!s`MRmK zKT_6FGh9Y#i&q~BKIo<_#30p=lx?bfIPhKIr>FC+Z*{-c`A+9MWfOH%Bc^Zhq1hqm z!NY<5j!FMhl@C-6)<00yo{ECI&4odCn~&Lz&GeIygIfC&{(XT^#n&S}82Q!t*{NiD z#B9!gB?;+0(SGvDbI-ro`quN)nYkwm_-~e9R1nexiQy( z?R&niU@Y;_?4DTb%#NuI3xnu3v0VJ>hRIY?=OxnWHDxk3V$e)pzQq zew1oH_ajh#cB*%-$Nqq=D;TeCYuI1&RON74o9~IlXJbQ2$d%dY**N-<}$CaR>Wr;`UY0$1T&}NsqZE{jM8!uj#4Y zRT8Rr!1nxf^iGsjdFA@S){hH-!CZ&ynDOE$n}? zu+_V+qz8O3k##HD8rGHEoz2X#Zu{uzClYB-hvPx_qnSY5Om2QXdGI@@x+kbN1CWMw zC4=q*nIpl+>o%4?9M~5)68y8UE7%nrs0vfcst#n@Q#+>WroNIKN>cJ4hxLf2`?DJs z`mi6`kd>aeeJHsnwg>ckG&3G&cI4IhJ+ZGO4`dE`AFoR_CmAV zOVCKy6Mo8Te|D#*JvG4k?PBW;vu95hHvfO^eLYZH*|z4<&(Xh-5E59j8+9AreWs=! z1ae>ks2UtVfUCyTR6&3PQ@3Dh_VmUK8ShV4{_?gSsu z`M9|^Pru=W=o?ur+fLQaBT}smfLsH%koRv1kvwD%;dO(OXFYKH}_hE2>qas{2}tctSp?(dZPWXJlooGIPQelf-apuqTa057jsHPPLmw zeRBdUGeqb)MykUVgWst?n4*;*3DqCW#D@}`a8NT)Hf{`S7<}YQY$Vhc=^?almOLv) zKkXu0#v%st*)nc+{%}b&d)28P7fWKv&`Po<`Z(a-GH)bZ$xy4ZSz-}enMY(Nm7$hx z!n}w%@uFl?M}C5A!EaJJ~{I>F^ zmcJSP+VpR=-o=PF%@KaIizW|NHv7_68Y%XASo)&GV*K8mxb~uCRK*G54LXwj;ZE6L zW$U-2s)TfiJm~Un{cF3|6nO5R60qu)LkBaIsP($mpZnK*9&U)%Xk5(DZd=5Inx?ov zjM>>qk2=*0-#)48F;#!pW2%s@BqtAAwqZf`6udTaGMGd|TfhfJYQPn`%tOWa1Lpwq`O+=_@w31i6RZQwn-dLnp5N46*8A)x%E&7lq07Iw353+j~m^VE9n zDR}HwWu1J2MrRS<2`=y6_IJnnww6Sl-z2UnOuhfySh1;_iT7BW6Lne&AqO^@|d& zj2OrDa*dmqO%HJ)u$4%nBRMV}r?c&5pA|D?5o8l7pu3C4OrCb9J2oq+O?kML zm1&lYswT*5t|WbgoZu*}%2`P^o=3iKs zs@o!6Kij>_yZ3fswF&7=yf#&vaa&r%U_<`>c*F^j>A+?Ab z1mlF&?V^`jZ`wiZts^R$dOcUiVwNOepbA9Q`(Br@(&X&2MA1Gsp>(s{Fl2Efm8;=r-*YsbZ5O%DsKg}11X0yP|l0m7sgB@0TdK6nkW zdQbmf`+gW9uUbJX0cJ2pzsElw&YXB00GDhK%*4?SF*VYBt{6D(@U@0gc#9r(Q#W1I zAecXlBwCf^HA)9HOQPrgv1rGpYdyHO_IJCN6Gjr?qcd@qkV!!^@TRp}!2obluK=&o zD{d86WnhuePON$+&XJ`~37RE(1^6`fQ(@BRY?*GdgQq{vydCqu>(E+{*Q(@ht(MdpHrH~u%T_G>)etylX4LX68FZj0DcE6IDixM$L}-@}4UvFd~r zOumO*5-o}Jio3*OEM#?^GKTS{p}09A_%ofnGAOUH=mX!=Jta5i1h=&9m;(0HSX2^& z_i8Mf65QK`M}vx+mB2dmq-4g$9m%$cUSVaRQ9EU>Sc9NiwImwUKzl-A4-hvSmV)C0 zKQYD{Pg8vUu!Y#SpBSktR-2lMKiva+_Hf>GcMMU1Vd)e7L)y*hB8&K;nof^=K?ye7 zPyJrgfN=z&7-XRoN3g z5Oh@4Ppu(Z(5rJ3UX6=~=_D?RYNCsg2fn#rGGzA>gWK*8&q`Kh8Xb^k@}O(q5bj7u zh$Db^>lM(pV6|?O!_abAoARdbYwv3l(r3kBa?KJ4gS)L3F|EuMC}KndOab!F>ti0M zr#50d4ugZyZh_t5)JzFhNzHiEhA?o46E;i09N-m!5v|H6dMNjVlvOtfp6(3@ZxP~| z6Nqn22_!KO2UZBZHA(!K@P}+r$>H}^J0l^Cb|f?xgktI>^^7%evp7vw!>X*G)_hgg ztD`3d3fmRKC~ELMF}PXetv|T!C*Da2f_b*N*L+y*M%t&BTD+?hC)y(B$~t9M;#E+j zz`K{E_7(`~tPHGLn}%hUYoPYK_A5h=xHaExf0D*Df7mP;7x$%~`}?-me4Vm!!g!-_ zx@h+5-IAs_6#jw|7}l45wf}Sv?1n4D`r}RykVu=Jke6$mYOrjuIr1EDA_?fpAwtqB z!n3g`a-q+PXCG3wpTz@ZA!eJLmQ9%{_#1woHX|FpGeG z(jzoK;l17YZ9^FAF%y49%%fv-j?VKE={Z&L-Bsn2ieq`f0`3xneO(T}mvOV(_u?4%gZ;P}Vsk(AfIf#)tn5>48 zU_}`6dlgdj+pD{;9aXK$5R)PemVuU-&&C*VzS`Wc_Lqsjb?u|a9%8TFEhQ;cB{xrc zz)XsLR0Vv8T3QnI>YxwtZ$3_bAS zMAW7l1TVGy#LeI*8w8vXH;YF?RT+3o30c#Gyi*3%1ssI#dG@CpRYuxHR&61M({i}H ztKF&$Ek?$o@OWU5$WgjtbA+YKyNJwnB%!y4i2+n)I+9)c-7&B-pF%@cyP%}CFJ%m{ zhXi?*=^{kc?5V0uRYnrSiJo~2CaBD^!1=?WL?!vA?wFM!*0>3;(^%9vIcfRIjZu9CyBQ%Mu?I691JvX;+oZf2qz+YTC#COpcL#_v_Jef z5DXw@VNTe+V;;nEEMkjz{?I6#B1?-T>JM<+a9G+d;>a_t657pDUBnzY;Y;n(m5K7v z?{NPUx)+Fx7=?BV+>H}H;+$%Xrq=8Esm(pKVI%w>O7yVWG?X=XW2Y=3ohk(rY8Qc{ zfIs`gE5x#$#P{`PaQ$&3^ecE9PIJ|c-RmWFd3?hrROoVDT6Y?T@i3l8+eP22lsP+28OCmR6Ar!Ha zGX=1-HnIjgp%QdIaMM`kBp=B5s&SB0JjkrBNp)kpY1>H?ZO)^i8wlyP$y6Kv|CBngTEs_xFi~uDhaK-_JL?)(c09k zq@nboxfbl2d|~LryquY`;Rx<=UnWJ4l-6b=8h`t7>N~3Z0Krf@rUH zt0syF?=UH=6;gb_HCL=MG%lVla;jaiDFM7)^>^i!SWnWeSv^~=f~w4MHrq~+ShhLQ zKrD7F+OiF;=u@mDTeiodoiZpws3_ZxoyH1YPpzq-BRgeFqE_YIlJd&x@6Z}n&8eOy zRai-S)WIOex{{oU4@(i_AqKJKih&b7^C^2(MtcOlE{N@uqct)qpOqMs4s3LBHNTsoCLf%*eed0~q zQyocL242q;fDM=e143{VoI^V6#2oJJVm$}S+@@6-a6ghDMydeXz|Z02fh(-aUX*lx zV>JM?%1ISlNJDCgrF0~_V~+zkcVp2B`K)9p5hRvx_g3xPCUyXh4va+9Orz7Aaq@u6 z{&4Pe4+z^uPW9cAsnP`TI*WK#GN|E%;L}41a4lp6JU`(a0#hDwV9qUv+AW|EP7m@P zqr~4wzndsuy7E%X2_-Qwg;|N4Ma+yVYbV|SIx`UpWD z2l|O8BG=L^d9|-4+re$K=^{EIq^BRM9Sr01aEpre;4;`g}RkqidFk?sTy;l&8h26?7osg?0?(C`ieSGK8ZBqo%Q-q*I0PwCo6h6;)v zm<1mJ{ob*8Z`aD;Jw1qc!Q(NEl5?$_H)h^qXNmcf-?67|6(f#?%n>k>As$a-x5LEK2tcUH@Es5HR<0DTpszP23v6md1@NQYe zJ@i|nuxo!Lw7mOjzs>!0ZzdiL5KAHy8&%oJtAeAf%D|a>bzn>tq|y=jZi>%JmPB9% z!2N-;a?MJz{@@YUM|Qgyxf7gu+p-PbrA;fj$Fzfy4aAT*A#B-5O6rDQqjRdk)Lv@! zikUcKY=|*es}MV{B0L{TATEPm(CpRYB`Oop;Bc?g7!@2NslJ4OZlC$?VnXBE(w8$q%pj5EVft ztyzM7ONMZoq1?Onf!gJywi3j!ho#jjD>F!ZxuNu>mSGV8X&1pib;`Ihix}qsv0p?x zkYzO|)_iSlFZFzfEUwmzsD<5o58s>NtBD%I-n1(QziB0X+^buaO&9f(in1D@t`VIY zRn1DEC%~acLdbRz=iBj+>QHh+8}+XdFPuE6iJAi7NcjDfph61G(kwyj_~~AQ0E!jy z<0tx7CGK#Ii;+`aj5JF)A#f9;s8%7X46ZOGc<$G3IT(ilOcl9;F;Dk8bnCrc1MP>HdN*-t zD81)?oG-MEtb~{N$owH9G52=KXCLM)beMQO_yH7oWe719#F1RFPT8;&8CAWaErM9` zf>N*0rfZ_J5~zMe1Mcl29|P5o{EIyW-TH`hVSHUAh9xWYvf|_OM{>kp#wQPqjt-bfViL1HxDP-mMzXt9}0)!<)Ha zTV%Q@tm#NDC~@xU%8{?GCVaOVh`(sC$3`KdJ+{mvZbCjl2)>d;OnyKJ9ITKo@6NpS zk{_+wk;H~JCSTtP&IN18lR$~T{HNA$6W83PO#9y-9k29!G<+BP4kELR3yO(?cTM&2iIKd9h+m(=YGUgrb??- z@H+E{4T5P>B8aeBnTFEgYyD)6u54}$5zh+Zehq@jgOSkVfH~peauGPhMsl1j8(aOM z|F4L|Ae!u8yjdhtm~g`Z%D6%rNuV7dATfLKeQj8Ss0)|{^2aNiwceU2l>2SJcgqw= zh7gDAVUeeeB<=(eR|AG2YtekQPLAyEK-mpFcIt7UGSrv$DZ(0Xu9w=0a%kiF14DSt zmy2q4Y&OLaM`{*Ve^Ln%~&??<5M6u4~%dy$YeAVJ84xm#UqKC__+9n z9(YIR@~XwiUau?fVG*^c@!SbM4&3(JQ^*89RL@Eh(sE6A%(Xt})c#QEYT1Uj996Yn z(dghOBZ+>6)d1YVe%>vaF6uEM2HwLW_w$la1o7g@1E^%X_vw!wZj6)z@|2Po-XrHA zCSZ7DT0`u4BxDpK;@MD2s#FSn@}k5_8nq*ddniDfb`j>)Ai!;jI(btZ@&CsG($y>cG0RV%iYq5wN!?h-nwG*hutwm_Yec`OV7m7!>9^31$uRQg(L3cEaKhTmbhu*FEZQG7(eKBHBDad0Og~8i=XHRHMJ?p zIx(HCIVZA;luq@v)o&2TL97wH4sjXe+-KssOpQ5#Grt(oZq*ZawCyaD zzRccNih-43)L`hg9q{eOBE;s0rDIWx7_q=n6|2UL8N`Ngufd#HP?9bYfIlfTF{_1jGapd%>L}FazXUYg2kU7sfL7y48S~6iytt6XKoT^mNhE z73>Pb*fP)kwO;L3TjVxrM>mTc+DsfVBzzj$@~++6^>bU~q1sJ;>An^yHk1H^1lRBU z#%*d@si&(K(eY$-B?bVlinAzq059&Z1PgzoLavm#^YR6`LaLrQMUv*tsN@I^`U z)v40^+5zFS;;B-gMxETsAd`Xn2NQBAYb*I1lSl3c z*50aIm9-EO6V7>_`;h}C4oW(^o=|mYV`bC3C0PfX!^%PTJ4lsUNo$}#2Z>(AzWYNEI~f;fSL!TTg~*>A+L zUJ?ppNAH%b%E)HZx!Xk%T3^^2L(Bv@^=3&`2C*|_7csgiLQ=%k^$K_kD}#s^ZWiek zjYV~G#Ex+vYTSq$Qhj76HBsEno0Z`1POlC(wakfjQOjX}xFd;tYwmVHRc0j#)ochO z$_{UV_Zom_+f%q-KuCUoEKu&|!IP?mZ=GuBeB6A&jkMfN1;zkthSh=LrIr;!@Ko30M(TYVRlaS>{VzOL`snfgdT@lnvg%s zM(KN4cnnF5bSs0X*+3bT^-ux|WF&-WPd)h*=W3OIf1bw#;nnyABz~Qwps;Fi|i|6Y27i@XMB;3rnhrW!r>V5SzY_4fQQqj!3-2+ zSzOt4MMEhekZ>`ZQSzqy6;JdHrBABb|E0%tw*<;+fL0Z&q%GrYurk*GO$@F#W7M2xKRUS9Mpi#cFG(~IIHSz?!k5t1p|-jR}5;9n+CVE?ZAS7 zznpMfJb!p|uj}Urq}3}!xFLf43HBPw9xF2?Xo?R@p$FPU{aIcmiJ1c6HIc-U2zUCp zvc$C>)2a+7@45fgK4KIeLgn{&wm2c~4Z{EhJe`I|{~C8T7to4A(%gs)cBl~;eq z7@%>gRZvXSs=%4mJ`b<2e)B|cECWZV%J{>NxL5ng(<0K_7J0f?>FQXG{5$ldMZ9#S z;@VUxvb)tPaD3cbvSk_wkr8_W8Z`)@x6B*MyA1+O_U_KJKe5+w7i%a1Wmg&EgiBX| zMo>?$_U+!>tv-gy-I^UIC6Bw8_7u2eUpB6KtC&@T$y*uh{B3`KxQ=iMHwEg-xia{_ z0U^-6CYrl#XbQag`Hr*318cdrTb1co08w(c6mvHg>kl3WZtgwz-wFD|xQRL?z`b63 z3i(^aW^WZIt}R^wpT^B1_}gS?dH3dz%m2@|)ASaRb<6-cHpCcUkye97x1a>W=#(Mb z)pCeDYd?}wW|&L3h05P`d|bui!$h(I~j z#A%695o5c8JAFXZ+LV%eL~7`9z?Q)Z;XYJ3S@X@k+7xa`P0@*p5b9QuHf?h!UBSTQ z0kEN61ci_LgY$>TFM?wucIZ<;XCq!gK62v`hr5@giNC$|r-uLJGb>EQOKGQ}_U-sY z>Vq0j*UxLdB(eSZL-2EORAgz8NrSds65+N46frzL)a6WkE?Bh_B;MXG>eVr%gSj$9 z77@kureXPRwp!&6FS@;3$OxD>!1N#Zv{|n2&9WI}n(Bx@4kd6C6`57!s~HC6i0~pB zS#b@~OYlg-ix{-0fleND+05I?*RFNMa%M<_;M|xKi0py8!p?5!5s?dPI=^An(5PC{ zpOXiXjao0(knl0D#>L%of7Az>CmJp#O>viXDu!EJmSq1$zd%uTR z&a4Ez=We(m&(v7-y}4J{e&vQG79+g&f)aP)fp#OI|MH*zYs<-Z1!Ug+6kZoze|4|l zABgO`%ijH5`k)H03$HKh_4@-M%zo8O?k98Xr|?{OE?@vV! z{N&32H&1y!^@Tk3QU2!(?En1wl5GD+&!^YVr<||-{N~R${y*~=%pZOJU&j7Fys-Hn z0|aFO5I&&(=V1^9KsdkskKccMowJVVJ^R4$JoBv|-*c}2kFTF^|HlA9kp%=<_TRG? zG*^ChgSqnm##8_7_H)l?*XMhd(SqhPe!l+m9slQEzp(#*?ETBT{t+PLexx7N;m1Eq zKWIZPu0G3CKi~EX^Z&Z%ui$N8aBjKkg6GHkd;|!BV}OuHgI~Ep{L$zC=L?>H;rTOv zJ_3XsM}Wa7nE!KvA)M4)|MSdp=_S|a8-HPb`L+MKuMa%%*S!Dvwtox|a-T@8U^1sP z_^-b_D^6YTRIgKHB7gxB@0eqIv|AYB`fB&UD|K;8Oi@(zS z@BVuC{|FFd+0D;Ip}&7;5X^53f-lOAppKU5J99ygw|{#5%Wa?U`J%HF`L@rO_WH2? zKi~Z+?El5?|1W?bdM6oJ`hW2`3z{oG`|6~C_yvFcM`xCcKB)h*CzR{|>~jA4=g*IC zefatpU2^|?>tERaPqF?-<+S%*iE`zM`nkV9UjFO)$`kdE z0fMvu2$=#PeC_-H)juBrLQWuDc+jHoTzD=#f4=Q{!zJd%g8HBP_zHkf*#C2%j{rgZ4iJtgiSU<;DgXC`ft<_( z|Lck8|8?T|WK_B5#Pi8FTU_?Vpv&(6vY#(bga0v}3m20J5OVKj_%$HpG7o0~!u!uq zI13Pt>Yok>NA*t!groYWPvEFEm48eVcNoT7Kwy*wSv0W0R?r|DTdZ>u##T7MnUbNF z&CSk0xS7mof}z2LNk_dypk$+FV20MDv$yCPL^HbpDG{(2aBfEpshTilz^KA&Iu7`Ppg1XI?YhV_Mmio3uy)x5YKu|<4CiTvV&Y(N z6Vw?6Xtz!!r#Op}lfEmfiHxJsNq>waR8kI8!M_--b~UdJPyyv}fD$}b3ADLKZPZD` zA4cDAl4A#7%5M$|8oaHb&F}O$O4FzE)^~^tU6t-wC4#WREl9f|A>bG#*!Y9)LEb|5 zm44B}OL^8zfUyXn0d!O^>zz@9x0IQ?;NAgsxnmR|4T49k&so|g@=&Qgl7x?!(|!y! z*JoRLFJTlyH`25E+F}RcTGet?e~>+6qaB21yy-Sz?-OM(uCOd5%evAd+cO*fQ?O9P*`mq@6(Uc0Q4OI_X2mZO|cX4CKPS@vsl< zwPju=c%G4J0l~|+*uwv%-gFGg>V91`LkCcKfi|32IXV3G>F&r1wz|LM@GG66&&rX* zm-WyOjcqOEjw4vbIDeo&cx_1)nO8*?Ae4(OCc+5F#yS0NfqJi7G{Y!Adhb?s_I2(M zZFz<=#xwpm^1fo}=Cbr6G3AWUwl4seKQ_428C=BNR>7f`4$v=7Y7q4toG!><0KE<& z$X$FWe^to|!FcoZ$=bRp=^i)plhXJw9G8BKI0NvP8XGlvD*4^tR;fp*CoTVmc7fcq zqL2LaTu^#f0PnmeE!_+APxf`!pOBSfE7Q+^E{=8%Rl>B!=QuYUpf?U^<`s1CFEB<@ zYnz1u>JLbbNJmf|2jOi0l(5Ca8I2jK;nKVmH^1G4hsd2(?O@okr79tsKQSlAIMu8A z>U8BdPBvLwc((0W?3VcI?N;hg$M=@$xvY{GG+iI&RM_-tp^HjP@g1fOpOC$*j>DIu zvI#b~pjOfu68qj1)d(_t<*o6d!5Ap{?aJ gSC~8hRBsmw7G8L^ndW`EvWiXrU#}p|RdsLMzhjo$u>b%7 literal 17704 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 From e3650906a008d5ac2a99b4eba4c5a16d1499b426 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 10 Apr 2022 13:49:06 -0500 Subject: [PATCH 615/637] Revert "Demo Song: YKY demo 100hz update (#336)" This reverts commit 62fe2433ce166784f360b156c094fe3908e0012b. --- demos/yky.fur | Bin 34188 -> 17704 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/demos/yky.fur b/demos/yky.fur index fc8188c1901e0cac8905598322040b66f26b6f29..106ed1004b630610b60df810d9676b16f61288d1 100644 GIT binary patch literal 17704 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 literal 34188 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 From c13358d96f38c8b0b61e959a905a30cdf5ee03b0 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 10 Apr 2022 15:31:32 -0500 Subject: [PATCH 616/637] part 1 --- src/engine/engine.h | 4 ++-- src/engine/instrument.h | 7 ------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/engine/engine.h b/src/engine/engine.h index dd9bf25ca..89f6ff732 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -42,8 +42,8 @@ #define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock(); #define BUSY_END isBusy.unlock(); softLocked=false; -#define DIV_VERSION "dev80" -#define DIV_ENGINE_VERSION (80/*Test*/|0x80) +#define DIV_VERSION "dev81" +#define DIV_ENGINE_VERSION 81 // for imports #define DIV_VERSION_MOD 0xff01 diff --git a/src/engine/instrument.h b/src/engine/instrument.h index b292b536f..e12da09d5 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -417,13 +417,6 @@ struct DivInstrument { */ void putInsData(SafeWriter* w); - /** - * save the macro to a SafeWriter. - * @param m the macro. - * @param w the SafeWriter in question. - */ - void putMacroData(DivInstrumentMacro m, SafeWriter* w); - /** * read instrument data in .fui format. * @param reader the reader. From 51207e58ad8627aa6201a0bbf0a722fb1be25f20 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 10 Apr 2022 16:52:03 -0500 Subject: [PATCH 617/637] part 2 - finally --- src/engine/fileOps.cpp | 3 - src/engine/instrument.cpp | 863 ++++++++++------------------------ src/engine/instrument.h | 51 +- src/engine/macroInt.cpp | 53 +-- src/engine/macroInt.h | 26 +- src/engine/platform/tx81z.cpp | 8 - src/gui/insEdit.cpp | 44 +- 7 files changed, 283 insertions(+), 765 deletions(-) diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 63cc15361..eb5e31e4a 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -307,12 +307,9 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { if (!ins->mode) { ins->type=DIV_INS_AY; } - ins->std.dutyMacro.height=31; - ins->std.waveMacro.height=7; } if (ds.system[0]==DIV_SYSTEM_PCE) { ins->type=DIV_INS_PCE; - ins->std.volMacro.height=31; } if ((ds.system[0]==DIV_SYSTEM_SMS_OPLL || ds.system[0]==DIV_SYSTEM_NES_VRC7) && ins->type==DIV_INS_FM) { ins->type=DIV_INS_OPLL; diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index d39e9c699..8f87e460c 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -23,37 +23,13 @@ #include "../ta-log.h" #include "../fileutils.h" -void DivInstrument::putMacroData(DivInstrumentMacro m, SafeWriter* w) { - w->write("MACR",4); - w->writeI(0); - - w->writeS(DIV_ENGINE_VERSION); - - w->writeS(type); - w->writeC(0); - - w->writeString(m.name,false); - - w->writeI(m.len); - w->writeI(m.loop); - w->writeI(m.rel); - w->writeI(m.mode); - w->writeC(m.open); - for (int v=0; vwriteI(m.val[v]); - w->writeI(0); // reserved - w->writeI(0); // reserved - } -} - void DivInstrument::putInsData(SafeWriter* w) { w->write("INST",4); w->writeI(0); w->writeS(DIV_ENGINE_VERSION); - w->writeS(type); - //w->writeC(type); + w->writeC(type); w->writeC(0); w->writeString(name,false); @@ -125,7 +101,7 @@ void DivInstrument::putInsData(SafeWriter* w) { w->writeC(c64.hp); w->writeC(c64.ch3off); w->writeS(c64.cut); - //w->writeC(std.dutyMacro.mode); + w->writeC(c64.dutyIsAbs); w->writeC(c64.filterIsAbs); // Amiga @@ -135,66 +111,6 @@ void DivInstrument::putInsData(SafeWriter* w) { } // standard - putMacroData(std.volMacro,w); - putMacroData(std.arpMacro,w); - putMacroData(std.dutyMacro,w); - putMacroData(std.waveMacro,w); - putMacroData(std.pitchMacro,w); - putMacroData(std.ex1Macro,w); - putMacroData(std.ex2Macro,w); - putMacroData(std.ex3Macro,w); - putMacroData(std.algMacro,w); - putMacroData(std.fbMacro,w); - putMacroData(std.fmsMacro,w); - putMacroData(std.fms2Macro,w); - putMacroData(std.amsMacro,w); - putMacroData(std.ams2Macro,w); - putMacroData(std.panLMacro,w); - putMacroData(std.panRMacro,w); - putMacroData(std.phaseResetMacro,w); - putMacroData(std.ex4Macro,w); - putMacroData(std.ex5Macro,w); - putMacroData(std.ex6Macro,w); - putMacroData(std.ex7Macro,w); - putMacroData(std.ex8Macro,w); - // FM macros - for (int i=0; i<4; i++) { - DivInstrumentSTD::OpMacro& op=std.opMacros[i]; - putMacroData(op.amMacro,w); - putMacroData(op.arMacro,w); - putMacroData(op.drMacro,w); - putMacroData(op.multMacro,w); - putMacroData(op.rrMacro,w); - putMacroData(op.slMacro,w); - putMacroData(op.tlMacro,w); - putMacroData(op.dt2Macro,w); - putMacroData(op.rsMacro,w); - putMacroData(op.dtMacro,w); - putMacroData(op.d2rMacro,w); - putMacroData(op.ssgMacro,w); - putMacroData(op.damMacro,w); - putMacroData(op.dvbMacro,w); - putMacroData(op.egtMacro,w); - putMacroData(op.kslMacro,w); - putMacroData(op.susMacro,w); - putMacroData(op.vibMacro,w); - putMacroData(op.wsMacro,w); - putMacroData(op.ksrMacro,w); - } - // wavesynth macros - putMacroData(std.ws.wave1Macro,w); - putMacroData(std.ws.wave2Macro,w); - putMacroData(std.ws.rateDividerMacro,w); - putMacroData(std.ws.effectMacro,w); - putMacroData(std.ws.oneShotMacro,w); - putMacroData(std.ws.enabledMacro,w); - putMacroData(std.ws.globalMacro,w); - putMacroData(std.ws.speedMacro,w); - putMacroData(std.ws.param1Macro,w); - putMacroData(std.ws.param2Macro,w); - putMacroData(std.ws.param3Macro,w); - putMacroData(std.ws.param4Macro,w); - /* w->writeI(std.volMacro.len); w->writeI(std.arpMacro.len); w->writeI(std.dutyMacro.len); @@ -454,7 +370,7 @@ void DivInstrument::putInsData(SafeWriter* w) { for (int j=0; jwriteC(op.ksrMacro.val[j]); } - }*/ + } // OPL drum data w->writeC(fm.fixedDrums); @@ -478,7 +394,6 @@ void DivInstrument::putInsData(SafeWriter* w) { w->writeC(0); // reserved // more macros - /* w->writeI(std.panLMacro.len); w->writeI(std.panRMacro.len); w->writeI(std.phaseResetMacro.len); @@ -538,7 +453,7 @@ void DivInstrument::putInsData(SafeWriter* w) { } for (int j=0; jwriteI(std.ex8Macro.val[j]); - }*/ + } // FDS w->writeI(fds.modSpeed); @@ -565,43 +480,9 @@ void DivInstrument::putInsData(SafeWriter* w) { w->writeC(ws.param2); w->writeC(ws.param3); w->writeC(ws.param4); - - // FM per-operator enable - for (int j=0; j<4; j++) { - DivInstrumentFM::Operator& op=fm.op[j]; - w->writeC(op.enable?1:0); - } -} - -DivDataErrors DivInstrument::readMacroData(DivInstrumentMacro& m, SafeReader& reader, short version) { - char magic[4]; - reader.read(magic,4); - if (memcmp(magic,"MACR",4)!=0) { - logE("invalid macro header!\n"); - return DIV_DATA_INVALID_HEADER; - } - reader.readI(); - - reader.readS(); // format version. ignored. - /*type=(DivInstrumentType)*/reader.readS(); // instrument type - reader.readC(); - m.name=reader.readString(); - - m.len=reader.readI(); - m.loop=reader.readI(); - m.rel=reader.readI(); - m.mode=reader.readI(); - m.open=reader.readC(); - for (int v=0; v=17) { + std.pitchMacro.len=reader.readI(); + std.ex1Macro.len=reader.readI(); + std.ex2Macro.len=reader.readI(); + std.ex3Macro.len=reader.readI(); + } + std.volMacro.loop=reader.readI(); + std.arpMacro.loop=reader.readI(); + std.dutyMacro.loop=reader.readI(); + std.waveMacro.loop=reader.readI(); + if (version>=17) { + std.pitchMacro.loop=reader.readI(); + std.ex1Macro.loop=reader.readI(); + std.ex2Macro.loop=reader.readI(); + std.ex3Macro.loop=reader.readI(); + } + std.arpMacro.mode=reader.readC(); + // these 3 were macro heights before but they are not used anymore + int oldVolHeight=reader.readC(); + int oldDutyHeight=reader.readC(); + reader.readC(); // oldWaveHeight + reader.read(std.volMacro.val,4*std.volMacro.len); + reader.read(std.arpMacro.val,4*std.arpMacro.len); + reader.read(std.dutyMacro.val,4*std.dutyMacro.len); + reader.read(std.waveMacro.val,4*std.waveMacro.len); + if (version<31) { + if (!std.arpMacro.mode) for (int j=0; j=17) { - std.pitchMacro.len=reader.readI(); - std.ex1Macro.len=reader.readI(); - std.ex2Macro.len=reader.readI(); - std.ex3Macro.len=reader.readI(); - } - std.volMacro.loop=reader.readI(); - std.arpMacro.loop=reader.readI(); - std.dutyMacro.loop=reader.readI(); - std.waveMacro.loop=reader.readI(); - if (version>=17) { - std.pitchMacro.loop=reader.readI(); - std.ex1Macro.loop=reader.readI(); - std.ex2Macro.loop=reader.readI(); - std.ex3Macro.loop=reader.readI(); - } - std.arpMacro.mode=reader.readC(); - std.volMacro.height=reader.readC(); - std.dutyMacro.height=reader.readC(); - std.waveMacro.height=reader.readC(); - if (std.volMacro.height==0) std.volMacro.height=15; - if (std.dutyMacro.height==0) std.dutyMacro.height=3; - if (std.waveMacro.height==0) std.waveMacro.height=63; - reader.read(std.volMacro.val,4*std.volMacro.len); - reader.read(std.arpMacro.val,4*std.arpMacro.len); - reader.read(std.dutyMacro.val,4*std.dutyMacro.len); - reader.read(std.waveMacro.val,4*std.waveMacro.len); - if (version<31) { - if (!std.arpMacro.mode) for (int j=0; j=17) { + reader.read(std.pitchMacro.val,4*std.pitchMacro.len); + reader.read(std.ex1Macro.val,4*std.ex1Macro.len); + reader.read(std.ex2Macro.val,4*std.ex2Macro.len); + reader.read(std.ex3Macro.val,4*std.ex3Macro.len); + } else { + if (type==DIV_INS_STD) { + if (oldVolHeight==31) { + type=DIV_INS_PCE; + } + if (oldDutyHeight==31) { + type=DIV_INS_AY; } } - if (version>=17) { - reader.read(std.pitchMacro.val,4*std.pitchMacro.len); - reader.read(std.ex1Macro.val,4*std.ex1Macro.len); - reader.read(std.ex2Macro.val,4*std.ex2Macro.len); - reader.read(std.ex3Macro.val,4*std.ex3Macro.len); - } else { - if (type==DIV_INS_STD) { - if (std.volMacro.height==31) { - type=DIV_INS_PCE; - } - if (std.dutyMacro.height==31) { - type=DIV_INS_AY; - } - } + } + + // FM macros + if (version>=29) { + std.algMacro.len=reader.readI(); + std.fbMacro.len=reader.readI(); + std.fmsMacro.len=reader.readI(); + std.amsMacro.len=reader.readI(); + std.algMacro.loop=reader.readI(); + std.fbMacro.loop=reader.readI(); + std.fmsMacro.loop=reader.readI(); + std.amsMacro.loop=reader.readI(); + std.volMacro.open=reader.readC(); + std.arpMacro.open=reader.readC(); + std.dutyMacro.open=reader.readC(); + std.waveMacro.open=reader.readC(); + std.pitchMacro.open=reader.readC(); + std.ex1Macro.open=reader.readC(); + std.ex2Macro.open=reader.readC(); + std.ex3Macro.open=reader.readC(); + std.algMacro.open=reader.readC(); + std.fbMacro.open=reader.readC(); + std.fmsMacro.open=reader.readC(); + std.amsMacro.open=reader.readC(); + + reader.read(std.algMacro.val,4*std.algMacro.len); + reader.read(std.fbMacro.val,4*std.fbMacro.len); + reader.read(std.fmsMacro.val,4*std.fmsMacro.len); + reader.read(std.amsMacro.val,4*std.amsMacro.len); + + for (int i=0; i<4; i++) { + DivInstrumentSTD::OpMacro& op=std.opMacros[i]; + + op.amMacro.len=reader.readI(); + op.arMacro.len=reader.readI(); + op.drMacro.len=reader.readI(); + op.multMacro.len=reader.readI(); + op.rrMacro.len=reader.readI(); + op.slMacro.len=reader.readI(); + op.tlMacro.len=reader.readI(); + op.dt2Macro.len=reader.readI(); + op.rsMacro.len=reader.readI(); + op.dtMacro.len=reader.readI(); + op.d2rMacro.len=reader.readI(); + op.ssgMacro.len=reader.readI(); + + op.amMacro.loop=reader.readI(); + op.arMacro.loop=reader.readI(); + op.drMacro.loop=reader.readI(); + op.multMacro.loop=reader.readI(); + op.rrMacro.loop=reader.readI(); + op.slMacro.loop=reader.readI(); + op.tlMacro.loop=reader.readI(); + op.dt2Macro.loop=reader.readI(); + op.rsMacro.loop=reader.readI(); + op.dtMacro.loop=reader.readI(); + op.d2rMacro.loop=reader.readI(); + op.ssgMacro.loop=reader.readI(); + + op.amMacro.open=reader.readC(); + op.arMacro.open=reader.readC(); + op.drMacro.open=reader.readC(); + op.multMacro.open=reader.readC(); + op.rrMacro.open=reader.readC(); + op.slMacro.open=reader.readC(); + op.tlMacro.open=reader.readC(); + op.dt2Macro.open=reader.readC(); + op.rsMacro.open=reader.readC(); + op.dtMacro.open=reader.readC(); + op.d2rMacro.open=reader.readC(); + op.ssgMacro.open=reader.readC(); } - // FM macros - if (version>=29) { - std.algMacro.len=reader.readI(); - std.fbMacro.len=reader.readI(); - std.fmsMacro.len=reader.readI(); - std.amsMacro.len=reader.readI(); - std.algMacro.loop=reader.readI(); - std.fbMacro.loop=reader.readI(); - std.fmsMacro.loop=reader.readI(); - std.amsMacro.loop=reader.readI(); - std.volMacro.open=reader.readC(); - std.arpMacro.open=reader.readC(); - std.dutyMacro.open=reader.readC(); - std.waveMacro.open=reader.readC(); - std.pitchMacro.open=reader.readC(); - std.ex1Macro.open=reader.readC(); - std.ex2Macro.open=reader.readC(); - std.ex3Macro.open=reader.readC(); - std.algMacro.open=reader.readC(); - std.fbMacro.open=reader.readC(); - std.fmsMacro.open=reader.readC(); - std.amsMacro.open=reader.readC(); + for (int i=0; i<4; i++) { + DivInstrumentSTD::OpMacro& op=std.opMacros[i]; + reader.read(op.amMacro.val,op.amMacro.len); + reader.read(op.arMacro.val,op.arMacro.len); + reader.read(op.drMacro.val,op.drMacro.len); + reader.read(op.multMacro.val,op.multMacro.len); + reader.read(op.rrMacro.val,op.rrMacro.len); + reader.read(op.slMacro.val,op.slMacro.len); + reader.read(op.tlMacro.val,op.tlMacro.len); + reader.read(op.dt2Macro.val,op.dt2Macro.len); + reader.read(op.rsMacro.val,op.rsMacro.len); + reader.read(op.dtMacro.val,op.dtMacro.len); + reader.read(op.d2rMacro.val,op.d2rMacro.len); + reader.read(op.ssgMacro.val,op.ssgMacro.len); + } + } - reader.read(std.algMacro.val,4*std.algMacro.len); - reader.read(std.fbMacro.val,4*std.fbMacro.len); - reader.read(std.fmsMacro.val,4*std.fmsMacro.len); - reader.read(std.amsMacro.val,4*std.amsMacro.len); + // release points + if (version>=44) { + std.volMacro.rel=reader.readI(); + std.arpMacro.rel=reader.readI(); + std.dutyMacro.rel=reader.readI(); + std.waveMacro.rel=reader.readI(); + std.pitchMacro.rel=reader.readI(); + std.ex1Macro.rel=reader.readI(); + std.ex2Macro.rel=reader.readI(); + std.ex3Macro.rel=reader.readI(); + std.algMacro.rel=reader.readI(); + std.fbMacro.rel=reader.readI(); + std.fmsMacro.rel=reader.readI(); + std.amsMacro.rel=reader.readI(); - for (int i=0; i<4; i++) { - DivInstrumentSTD::OpMacro& op=std.opMacros[i]; + for (int i=0; i<4; i++) { + DivInstrumentSTD::OpMacro& op=std.opMacros[i]; - op.amMacro.len=reader.readI(); - op.arMacro.len=reader.readI(); - op.drMacro.len=reader.readI(); - op.multMacro.len=reader.readI(); - op.rrMacro.len=reader.readI(); - op.slMacro.len=reader.readI(); - op.tlMacro.len=reader.readI(); - op.dt2Macro.len=reader.readI(); - op.rsMacro.len=reader.readI(); - op.dtMacro.len=reader.readI(); - op.d2rMacro.len=reader.readI(); - op.ssgMacro.len=reader.readI(); + op.amMacro.rel=reader.readI(); + op.arMacro.rel=reader.readI(); + op.drMacro.rel=reader.readI(); + op.multMacro.rel=reader.readI(); + op.rrMacro.rel=reader.readI(); + op.slMacro.rel=reader.readI(); + op.tlMacro.rel=reader.readI(); + op.dt2Macro.rel=reader.readI(); + op.rsMacro.rel=reader.readI(); + op.dtMacro.rel=reader.readI(); + op.d2rMacro.rel=reader.readI(); + op.ssgMacro.rel=reader.readI(); + } + } - op.amMacro.loop=reader.readI(); - op.arMacro.loop=reader.readI(); - op.drMacro.loop=reader.readI(); - op.multMacro.loop=reader.readI(); - op.rrMacro.loop=reader.readI(); - op.slMacro.loop=reader.readI(); - op.tlMacro.loop=reader.readI(); - op.dt2Macro.loop=reader.readI(); - op.rsMacro.loop=reader.readI(); - op.dtMacro.loop=reader.readI(); - op.d2rMacro.loop=reader.readI(); - op.ssgMacro.loop=reader.readI(); + // extended op macros + if (version>=61) { + for (int i=0; i<4; i++) { + DivInstrumentSTD::OpMacro& op=std.opMacros[i]; - op.amMacro.open=reader.readC(); - op.arMacro.open=reader.readC(); - op.drMacro.open=reader.readC(); - op.multMacro.open=reader.readC(); - op.rrMacro.open=reader.readC(); - op.slMacro.open=reader.readC(); - op.tlMacro.open=reader.readC(); - op.dt2Macro.open=reader.readC(); - op.rsMacro.open=reader.readC(); - op.dtMacro.open=reader.readC(); - op.d2rMacro.open=reader.readC(); - op.ssgMacro.open=reader.readC(); - } + op.damMacro.len=reader.readI(); + op.dvbMacro.len=reader.readI(); + op.egtMacro.len=reader.readI(); + op.kslMacro.len=reader.readI(); + op.susMacro.len=reader.readI(); + op.vibMacro.len=reader.readI(); + op.wsMacro.len=reader.readI(); + op.ksrMacro.len=reader.readI(); - for (int i=0; i<4; i++) { - DivInstrumentSTD::OpMacro& op=std.opMacros[i]; - for (int l=0; l=44) { - std.volMacro.rel=reader.readI(); - std.arpMacro.rel=reader.readI(); - std.dutyMacro.rel=reader.readI(); - std.waveMacro.rel=reader.readI(); - std.pitchMacro.rel=reader.readI(); - std.ex1Macro.rel=reader.readI(); - std.ex2Macro.rel=reader.readI(); - std.ex3Macro.rel=reader.readI(); - std.algMacro.rel=reader.readI(); - std.fbMacro.rel=reader.readI(); - std.fmsMacro.rel=reader.readI(); - std.amsMacro.rel=reader.readI(); - - for (int i=0; i<4; i++) { - DivInstrumentSTD::OpMacro& op=std.opMacros[i]; - - op.amMacro.rel=reader.readI(); - op.arMacro.rel=reader.readI(); - op.drMacro.rel=reader.readI(); - op.multMacro.rel=reader.readI(); - op.rrMacro.rel=reader.readI(); - op.slMacro.rel=reader.readI(); - op.tlMacro.rel=reader.readI(); - op.dt2Macro.rel=reader.readI(); - op.rsMacro.rel=reader.readI(); - op.dtMacro.rel=reader.readI(); - op.d2rMacro.rel=reader.readI(); - op.ssgMacro.rel=reader.readI(); - } - } - - // extended op macros - if (version>=61) { - for (int i=0; i<4; i++) { - DivInstrumentSTD::OpMacro& op=std.opMacros[i]; - - op.damMacro.len=reader.readI(); - op.dvbMacro.len=reader.readI(); - op.egtMacro.len=reader.readI(); - op.kslMacro.len=reader.readI(); - op.susMacro.len=reader.readI(); - op.vibMacro.len=reader.readI(); - op.wsMacro.len=reader.readI(); - op.ksrMacro.len=reader.readI(); - - op.damMacro.loop=reader.readI(); - op.dvbMacro.loop=reader.readI(); - op.egtMacro.loop=reader.readI(); - op.kslMacro.loop=reader.readI(); - op.susMacro.loop=reader.readI(); - op.vibMacro.loop=reader.readI(); - op.wsMacro.loop=reader.readI(); - op.ksrMacro.loop=reader.readI(); - - op.damMacro.rel=reader.readI(); - op.dvbMacro.rel=reader.readI(); - op.egtMacro.rel=reader.readI(); - op.kslMacro.rel=reader.readI(); - op.susMacro.rel=reader.readI(); - op.vibMacro.rel=reader.readI(); - op.wsMacro.rel=reader.readI(); - op.ksrMacro.rel=reader.readI(); - - op.damMacro.open=reader.readC(); - op.dvbMacro.open=reader.readC(); - op.egtMacro.open=reader.readC(); - op.kslMacro.open=reader.readC(); - op.susMacro.open=reader.readC(); - op.vibMacro.open=reader.readC(); - op.wsMacro.open=reader.readC(); - op.ksrMacro.open=reader.readC(); - } - - for (int i=0; i<4; i++) { - DivInstrumentSTD::OpMacro& op=std.opMacros[i]; - for (int l=0; l=76) { - std.panLMacro.len=reader.readI(); - std.panRMacro.len=reader.readI(); - std.phaseResetMacro.len=reader.readI(); - std.ex4Macro.len=reader.readI(); - std.ex5Macro.len=reader.readI(); - std.ex6Macro.len=reader.readI(); - std.ex7Macro.len=reader.readI(); - std.ex8Macro.len=reader.readI(); + if (version>=76) { + std.panLMacro.len=reader.readI(); + std.panRMacro.len=reader.readI(); + std.phaseResetMacro.len=reader.readI(); + std.ex4Macro.len=reader.readI(); + std.ex5Macro.len=reader.readI(); + std.ex6Macro.len=reader.readI(); + std.ex7Macro.len=reader.readI(); + std.ex8Macro.len=reader.readI(); - std.panLMacro.loop=reader.readI(); - std.panRMacro.loop=reader.readI(); - std.phaseResetMacro.loop=reader.readI(); - std.ex4Macro.loop=reader.readI(); - std.ex5Macro.loop=reader.readI(); - std.ex6Macro.loop=reader.readI(); - std.ex7Macro.loop=reader.readI(); - std.ex8Macro.loop=reader.readI(); + std.panLMacro.loop=reader.readI(); + std.panRMacro.loop=reader.readI(); + std.phaseResetMacro.loop=reader.readI(); + std.ex4Macro.loop=reader.readI(); + std.ex5Macro.loop=reader.readI(); + std.ex6Macro.loop=reader.readI(); + std.ex7Macro.loop=reader.readI(); + std.ex8Macro.loop=reader.readI(); - std.panLMacro.rel=reader.readI(); - std.panRMacro.rel=reader.readI(); - std.phaseResetMacro.rel=reader.readI(); - std.ex4Macro.rel=reader.readI(); - std.ex5Macro.rel=reader.readI(); - std.ex6Macro.rel=reader.readI(); - std.ex7Macro.rel=reader.readI(); - std.ex8Macro.rel=reader.readI(); + std.panLMacro.rel=reader.readI(); + std.panRMacro.rel=reader.readI(); + std.phaseResetMacro.rel=reader.readI(); + std.ex4Macro.rel=reader.readI(); + std.ex5Macro.rel=reader.readI(); + std.ex6Macro.rel=reader.readI(); + std.ex7Macro.rel=reader.readI(); + std.ex8Macro.rel=reader.readI(); - std.panLMacro.open=reader.readC(); - std.panRMacro.open=reader.readC(); - std.phaseResetMacro.open=reader.readC(); - std.ex4Macro.open=reader.readC(); - std.ex5Macro.open=reader.readC(); - std.ex6Macro.open=reader.readC(); - std.ex7Macro.open=reader.readC(); - std.ex8Macro.open=reader.readC(); + std.panLMacro.open=reader.readC(); + std.panRMacro.open=reader.readC(); + std.phaseResetMacro.open=reader.readC(); + std.ex4Macro.open=reader.readC(); + std.ex5Macro.open=reader.readC(); + std.ex6Macro.open=reader.readC(); + std.ex7Macro.open=reader.readC(); + std.ex8Macro.open=reader.readC(); - reader.read(std.panLMacro.val,4*std.panLMacro.len); - reader.read(std.panRMacro.val,4*std.panRMacro.len); - reader.read(std.phaseResetMacro.val,4*std.phaseResetMacro.len); - reader.read(std.ex4Macro.val,4*std.ex4Macro.len); - reader.read(std.ex5Macro.val,4*std.ex5Macro.len); - reader.read(std.ex6Macro.val,4*std.ex6Macro.len); - reader.read(std.ex7Macro.val,4*std.ex7Macro.len); - reader.read(std.ex8Macro.val,4*std.ex8Macro.len); - } + reader.read(std.panLMacro.val,4*std.panLMacro.len); + reader.read(std.panRMacro.val,4*std.panRMacro.len); + reader.read(std.phaseResetMacro.val,4*std.phaseResetMacro.len); + reader.read(std.ex4Macro.val,4*std.ex4Macro.len); + reader.read(std.ex5Macro.val,4*std.ex5Macro.len); + reader.read(std.ex6Macro.val,4*std.ex6Macro.len); + reader.read(std.ex7Macro.val,4*std.ex7Macro.len); + reader.read(std.ex8Macro.val,4*std.ex8Macro.len); } // FDS @@ -1262,14 +921,6 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) { ws.param3=reader.readC(); ws.param4=reader.readC(); } - - // FM per-operator enable - if (istest) { - for (int j=0; j<4; j++) { - DivInstrumentFM::Operator& op=fm.op[j]; - op.enable=reader.readC(); - } - } return DIV_DATA_SUCCESS; } diff --git a/src/engine/instrument.h b/src/engine/instrument.h index e12da09d5..605241c71 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -26,7 +26,7 @@ // NOTICE! // before adding new instrument types to this struct, please ask me first. // absolutely zero support granted to conflicting formats. -enum DivInstrumentType : unsigned short { +enum DivInstrumentType: unsigned short { DIV_INS_STD=0, DIV_INS_FM=1, DIV_INS_GB=2, @@ -81,7 +81,7 @@ struct DivInstrumentFM { unsigned char am, ar, dr, mult, rr, sl, tl, dt2, rs, dt, d2r, ssgEnv; unsigned char dam, dvb, egt, ksl, sus, vib, ws, ksr; // YMU759/OPL/OPZ Operator(): - enable(false), + enable(true), am(0), ar(0), dr(0), @@ -156,22 +156,20 @@ struct DivInstrumentFM { struct DivInstrumentMacro { String name; int val[256]; - int height; unsigned int mode; bool open; unsigned char len; signed char loop; signed char rel; - DivInstrumentMacro(String n, int h=~0, bool initOpen=false): + DivInstrumentMacro(String n, bool initOpen=false): name(n), - height(h), mode(0), open(initOpen), len(0), loop(-1), rel(-1) { memset(val,0,256*sizeof(int)); - } + } }; struct DivInstrumentSTD { @@ -186,9 +184,7 @@ struct DivInstrumentSTD { DivInstrumentMacro algMacro; DivInstrumentMacro fbMacro; DivInstrumentMacro fmsMacro; - DivInstrumentMacro fms2Macro; DivInstrumentMacro amsMacro; - DivInstrumentMacro ams2Macro; DivInstrumentMacro panLMacro; DivInstrumentMacro panRMacro; DivInstrumentMacro phaseResetMacro; @@ -222,36 +218,16 @@ struct DivInstrumentSTD { DivInstrumentMacro ksrMacro; OpMacro(): amMacro("am"), arMacro("ar"), drMacro("dr"), multMacro("mult"), - rrMacro("rr"), slMacro("sl"), tlMacro("tl",~0,true), dt2Macro("dt2"), + rrMacro("rr"), slMacro("sl"), tlMacro("tl",true), dt2Macro("dt2"), rsMacro("rs"), dtMacro("dt"), d2rMacro("d2r"), ssgMacro("ssg"), damMacro("dam"), dvbMacro("dvb"), egtMacro("egt"), kslMacro("ksl"), susMacro("sus"), vibMacro("vib"), wsMacro("ws"), ksrMacro("ksr") {} } opMacros[4]; - struct WaveSynthMacro { - DivInstrumentMacro wave1Macro, wave2Macro; - DivInstrumentMacro rateDividerMacro; - DivInstrumentMacro effectMacro; - DivInstrumentMacro oneShotMacro, enabledMacro, globalMacro; - DivInstrumentMacro speedMacro, param1Macro, param2Macro, param3Macro, param4Macro; - WaveSynthMacro(): - wave1Macro("wave1"), - wave2Macro("wave2"), - rateDividerMacro("rateDivider"), - effectMacro("effect"), - oneShotMacro("oneShot"), - enabledMacro("enabled"), - globalMacro("global"), - speedMacro("speed"), - param1Macro("param1"), - param2Macro("param2"), - param3Macro("param3"), - param4Macro("param4") {} - } ws; DivInstrumentSTD(): - volMacro("vol",15,true), + volMacro("vol",true), arpMacro("arp"), - dutyMacro("duty",3), - waveMacro("wave",63), + dutyMacro("duty"), + waveMacro("wave"), pitchMacro("pitch"), ex1Macro("ex1"), ex2Macro("ex2"), @@ -259,9 +235,7 @@ struct DivInstrumentSTD { algMacro("alg"), fbMacro("fb"), fmsMacro("fms"), - fms2Macro("fms2"), amsMacro("ams"), - ams2Macro("ams2"), panLMacro("panL"), panRMacro("panR"), phaseResetMacro("phaseReset"), @@ -425,15 +399,6 @@ struct DivInstrument { */ DivDataErrors readInsData(SafeReader& reader, short version); - /** - * read macro data in .fui format. - * @param m the macro. - * @param reader the reader. - * @param version the format version. - * @return a DivDataErrors. - */ - DivDataErrors readMacroData(DivInstrumentMacro& m, SafeReader& reader, short version); - /** * save this instrument to a file. * @param path file path. diff --git a/src/engine/macroInt.cpp b/src/engine/macroInt.cpp index aafeedbc1..489a6fab9 100644 --- a/src/engine/macroInt.cpp +++ b/src/engine/macroInt.cpp @@ -45,10 +45,10 @@ void DivMacroStruct::doMacro(DivInstrumentMacro& source, bool released) { } } -// CPU hell void DivMacroInt::next() { if (ins==NULL) return; - // Run macros + // run macros + // TODO: potentially get rid of list to avoid allocations if (!macroList.empty()) { for (std::list::iterator iter = macroList.begin(); iter!= macroList.end(); iter++) { iter->doMacro(released); @@ -63,6 +63,7 @@ void DivMacroInt::release() { void DivMacroInt::init(DivInstrument* which) { ins=which; // initialize + // TODO: potentially get rid of list to avoid allocations while (!macroList.empty()) { macroList.front().init(); macroList.pop_front(); @@ -106,17 +107,10 @@ void DivMacroInt::init(DivInstrument* which) { if (ins->std.fmsMacro.len>0) { macroList.push_back(DivMacroExecList(fms,ins->std.fmsMacro)); } - if (ins->std.fms2Macro.len>0) { - macroList.push_back(DivMacroExecList(fms2,ins->std.fms2Macro)); - } if (ins->std.amsMacro.len>0) { macroList.push_back(DivMacroExecList(ams,ins->std.amsMacro)); } - if (ins->std.ams2Macro.len>0) { - macroList.push_back(DivMacroExecList(ams2,ins->std.ams2Macro)); - } - // TODO: other macros if (ins->std.panLMacro.len>0) { macroList.push_back(DivMacroExecList(panL,ins->std.panLMacro)); } @@ -209,46 +203,9 @@ void DivMacroInt::init(DivInstrument* which) { } } - // prepare wavesynth macros - if (ins->std.ws.wave1Macro.len>0) { - macroList.push_back(DivMacroExecList(ws.wave1,ins->std.ws.wave1Macro)); - } - if (ins->std.ws.wave2Macro.len>0) { - macroList.push_back(DivMacroExecList(ws.wave2,ins->std.ws.wave2Macro)); - } - if (ins->std.ws.rateDividerMacro.len>0) { - macroList.push_back(DivMacroExecList(ws.rateDivider,ins->std.ws.rateDividerMacro)); - } - if (ins->std.ws.effectMacro.len>0) { - macroList.push_back(DivMacroExecList(ws.effect,ins->std.ws.effectMacro)); - } - if (ins->std.ws.oneShotMacro.len>0) { - macroList.push_back(DivMacroExecList(ws.oneShot,ins->std.ws.oneShotMacro)); - } - if (ins->std.ws.enabledMacro.len>0) { - macroList.push_back(DivMacroExecList(ws.enabled,ins->std.ws.enabledMacro)); - } - if (ins->std.ws.globalMacro.len>0) { - macroList.push_back(DivMacroExecList(ws.global,ins->std.ws.globalMacro)); - } - if (ins->std.ws.speedMacro.len>0) { - macroList.push_back(DivMacroExecList(ws.speed,ins->std.ws.speedMacro)); - } - if (ins->std.ws.param1Macro.len>0) { - macroList.push_back(DivMacroExecList(ws.param1,ins->std.ws.param1Macro)); - } - if (ins->std.ws.param2Macro.len>0) { - macroList.push_back(DivMacroExecList(ws.param2,ins->std.ws.param2Macro)); - } - if (ins->std.ws.param3Macro.len>0) { - macroList.push_back(DivMacroExecList(ws.param3,ins->std.ws.param3Macro)); - } - if (ins->std.ws.param4Macro.len>0) { - macroList.push_back(DivMacroExecList(ws.param4,ins->std.ws.param4Macro)); - } if (!macroList.empty()) { - for (std::list::iterator iter = macroList.begin(); iter!= macroList.end(); iter++) { - iter->prepare(); + for (std::list::iterator iter = macroList.begin(); iter!= macroList.end(); iter++) { + iter->prepare(); } } } diff --git a/src/engine/macroInt.h b/src/engine/macroInt.h index 3ce0c8722..2480d743a 100644 --- a/src/engine/macroInt.h +++ b/src/engine/macroInt.h @@ -73,7 +73,7 @@ class DivMacroInt { DivMacroStruct vol; DivMacroStruct arp; DivMacroStruct duty, wave, pitch, ex1, ex2, ex3; - DivMacroStruct alg, fb, fms, fms2, ams, ams2; + DivMacroStruct alg, fb, fms, ams; DivMacroStruct panL, panR, phaseReset, ex4, ex5, ex6, ex7, ex8; // FM operator macro @@ -105,28 +105,6 @@ class DivMacroInt { ws(), ksr() {} } op[4]; - - // wavesynth macro - struct IntWS { - DivMacroStruct wave1, wave2; - DivMacroStruct rateDivider; - DivMacroStruct effect; - DivMacroStruct oneShot, enabled, global; - DivMacroStruct speed, param1, param2, param3, param4; - IntWS(): - wave1(), - wave2(), - rateDivider(), - effect(), - oneShot(), - enabled(), - global(), - speed(), - param1(), - param2(), - param3(), - param4() {} - } ws; /** * trigger macro release. @@ -165,9 +143,7 @@ class DivMacroInt { alg(), fb(), fms(), - fms2(), ams(), - ams2(), panL(), panR(), phaseReset(), diff --git a/src/engine/platform/tx81z.cpp b/src/engine/platform/tx81z.cpp index 76c9d24bb..7ea634b26 100644 --- a/src/engine/platform/tx81z.cpp +++ b/src/engine/platform/tx81z.cpp @@ -275,18 +275,10 @@ void DivPlatformTX81Z::tick() { chan[i].state.fms=chan[i].std.fms.val; rWrite(chanOffs[i]+ADDR_FMS_AMS,((chan[i].state.fms&7)<<4)|(chan[i].state.ams&3)); } - if (chan[i].std.fms2.had) { - chan[i].state.fms2=chan[i].std.fms2.val; - //rWrite(chanOffs[i]+ADDR_FMS_AMS,0x84|((chan[i].state.fms2&7)<<4)|(chan[i].state.ams2&3)); - } if (chan[i].std.ams.had) { chan[i].state.ams=chan[i].std.ams.val; rWrite(chanOffs[i]+ADDR_FMS_AMS,((chan[i].state.fms&7)<<4)|(chan[i].state.ams&3)); } - if (chan[i].std.ams2.had) { - chan[i].state.ams2=chan[i].std.ams2.val; - //rWrite(chanOffs[i]+ADDR_FMS_AMS,0x84|((chan[i].state.fms2&7)<<4)|(chan[i].state.ams2&3)); - } for (int j=0; j<4; j++) { unsigned short baseAddr=chanOffs[i]|opOffs[j]; DivInstrumentFM::Operator& op=chan[i].state.op[j]; diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 685160b7f..3e1416128 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -110,7 +110,7 @@ enum FMParams { #define FM_SHORT_NAME(x) fmParamShortNames[settings.fmNames][x] const char* fmOperatorBits[5]={ - "op1", "op3", "op2", "op4", NULL + "op1", "op2", "op3", "op4", NULL }; const char* c64ShapeBits[5]={ @@ -182,11 +182,12 @@ const char* dualWSEffects[7]={ const char* macroAbsoluteMode[2]={ "Relative", - "Absolute", + "Absolute" }; const char* macroDummyMode[2]={ - "empty", + "Bug", + "Bug" }; String macroHoverNote(int id, float val) { @@ -996,7 +997,7 @@ void FurnaceGUI::drawFMEnv(unsigned char tl, unsigned char ar, unsigned char dr, } else { \ modeName=displayModeName[macro.mode]; \ } \ - if (ImGui::BeginCombo("Macro Mode##IMacroMode_" macroName,modeName.c_str())) { \ + if (ImGui::BeginCombo("TODO: Improve##IMacroMode_" macroName,modeName.c_str())) { \ String id; \ for (unsigned int i=0; i<=macroModeMax; i++) { \ id=fmt::sprintf("%d: %s",i,displayModeName[i]); \ @@ -1100,7 +1101,7 @@ void FurnaceGUI::drawFMEnv(unsigned char tl, unsigned char ar, unsigned char dr, } else { \ modeName=displayModeName[macro.mode]; \ } \ - if (ImGui::BeginCombo("Macro Mode##IOPMacroMode_" macroName,modeName.c_str())) { \ + if (ImGui::BeginCombo("TODO: Improve##IOPMacroMode_" macroName,modeName.c_str())) { \ String id; \ for (unsigned int i=0; i<=macroModeMax; i++) { \ id=fmt::sprintf("%d: %s",i,displayModeName[i]); \ @@ -2056,10 +2057,9 @@ void FurnaceGUI::drawInsEdit() { NORMAL_MACRO(ins->std.fbMacro,0,7,"fb",FM_NAME(FM_FB),96,ins->std.fbMacro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[1],0,7,NULL,false); if (ins->type!=DIV_INS_OPL) { if (ins->type==DIV_INS_OPZ) { + // TODO: FMS2/AMS2 macros NORMAL_MACRO(ins->std.fmsMacro,0,7,"fms",FM_NAME(FM_FMS),96,ins->std.fmsMacro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[2],0,7,NULL,false); - NORMAL_MACRO(ins->std.fms2Macro,0,7,"fms2",FM_NAME(FM_FMS2),96,ins->std.fms2Macro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[3],0,7,NULL,false); NORMAL_MACRO(ins->std.amsMacro,0,3,"ams",FM_NAME(FM_AMS),48,ins->std.amsMacro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,3,NULL,false); - NORMAL_MACRO(ins->std.ams2Macro,0,3,"ams2",FM_NAME(FM_AMS2),48,ins->std.ams2Macro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[5],0,3,NULL,false); } else { NORMAL_MACRO(ins->std.fmsMacro,0,7,"fms",FM_NAME(FM_FMS),96,ins->std.fmsMacro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[2],0,7,NULL,false); NORMAL_MACRO(ins->std.amsMacro,0,3,"ams",FM_NAME(FM_AMS),48,ins->std.amsMacro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[3],0,3,NULL,false); @@ -2072,7 +2072,7 @@ void FurnaceGUI::drawInsEdit() { NORMAL_MACRO(ins->std.ex2Macro,0,127,"ex2","PM Depth",128,ins->std.ex2Macro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[7],0,127,NULL,false); NORMAL_MACRO(ins->std.ex3Macro,0,255,"ex3","LFO Speed",128,ins->std.ex3Macro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[8],0,255,NULL,false); NORMAL_MACRO(ins->std.waveMacro,0,3,"wave","LFO Shape",48,ins->std.waveMacro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_WAVE],mmlString[9],0,3,¯oLFOWaves,false); - NORMAL_MACRO(ins->std.ex4Macro,0,4,"ex4","Operator On/Off",128,ins->std.ex4Macro.open,true,fmOperatorBits,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[10],0,4,NULL,false); + NORMAL_MACRO(ins->std.ex4Macro,0,4,"ex4","OpMask",128,ins->std.ex4Macro.open,true,fmOperatorBits,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[10],0,4,NULL,false); } MACRO_END; ImGui::EndTabItem(); @@ -2238,10 +2238,7 @@ void FurnaceGUI::drawInsEdit() { P(ImGui::Checkbox("Volume Macro is Cutoff Macro",&ins->c64.volIsCutoff)); P(ImGui::Checkbox("Absolute Cutoff Macro",&ins->c64.filterIsAbs)); - bool dutyAbs=ins->std.dutyMacro.mode&1; - if (ImGui::Checkbox("Absolute Duty Macro",&dutyAbs)) { PARAMETER - ins->std.dutyMacro.mode^=1; - } + P(ImGui::Checkbox("Absolute Duty Macro",&ins->c64.dutyIsAbs)); ImGui::EndTabItem(); } if (ins->type==DIV_INS_AMIGA) if (ImGui::BeginTabItem("Amiga/Sample")) { @@ -2387,9 +2384,6 @@ void FurnaceGUI::drawInsEdit() { ins->type==DIV_INS_SWAN || ins->type==DIV_INS_PCE || ins->type==DIV_INS_SCC) { - float asFloat[256]; - int asInt[256]; - float loopIndicator[256]; if (ImGui::BeginTabItem("Wavetable")) { ImGui::Checkbox("Enable synthesizer",&ins->ws.enabled); ImGui::SameLine(); @@ -2484,20 +2478,6 @@ void FurnaceGUI::drawInsEdit() { ImGui::EndTabItem(); } - if (ImGui::BeginTabItem("Wavetable Macros")) { - MACRO_BEGIN(0); - NORMAL_MACRO(ins->std.ws.enabledMacro,0,1,"enabled","Enable",160,ins->std.ws.enabledMacro.open,true,oneBit,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[5],0,1,NULL,false); - NORMAL_MACRO(ins->std.ws.oneShotMacro,0,1,"oneShot","One Shot",160,ins->std.ws.oneShotMacro.open,true,oneBit,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[4],0,1,NULL,false); - NORMAL_MACRO(ins->std.ws.globalMacro,0,1,"global","Global",160,ins->std.ws.globalMacro.open,true,oneBit,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[6],0,1,NULL,false); - NORMAL_MACRO(ins->std.ws.effectMacro,0,255,"effect","Effect",160,ins->std.ws.effectMacro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[3],0,255,NULL,false); - NORMAL_MACRO(ins->std.ws.wave1Macro,0,255,"wave1","Wave 1",160,ins->std.ws.wave1Macro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[0],0,255,NULL,false); - NORMAL_MACRO(ins->std.ws.wave2Macro,0,255,"wave2","Wave 2",160,ins->std.ws.wave2Macro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[1],0,255,NULL,false); - NORMAL_MACRO(ins->std.ws.rateDividerMacro,1,7,"rateDivider","Rate",160,ins->std.ws.rateDividerMacro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[2],1,7,NULL,false); - NORMAL_MACRO(ins->std.ws.speedMacro,0,255,"speed","Speed",160,ins->std.ws.speedMacro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[6],0,255,NULL,false); - NORMAL_MACRO(ins->std.ws.param1Macro,1,7,"amount","Amount",160,ins->std.ws.param1Macro.open,false,NULL,false,NULL,0,0,0,false,0,macroDummyMode,uiColors[GUI_COLOR_MACRO_OTHER],mmlString[6],1,7,NULL,false); - MACRO_END; - ImGui::EndTabItem(); - } } if (ImGui::BeginTabItem("Macros")) { float asFloat[256]; @@ -2546,7 +2526,7 @@ void FurnaceGUI::drawInsEdit() { int dutyMax=3; if (ins->type==DIV_INS_C64) { dutyLabel="Duty"; - if (ins->std.dutyMacro.mode) { + if (ins->c64.dutyIsAbs) { dutyMax=4095; } else { dutyMax=24; @@ -2593,7 +2573,7 @@ void FurnaceGUI::drawInsEdit() { dutyLabel="Duty"; dutyMax=7; } - bool dutyIsRel=(ins->type==DIV_INS_C64 && !ins->std.dutyMacro.mode); + bool dutyIsRel=(ins->type==DIV_INS_C64 && !ins->c64.dutyIsAbs); const char* waveLabel="Waveform"; int waveMax=(ins->type==DIV_INS_AY || ins->type==DIV_INS_AY8930 || ins->type==DIV_INS_VERA)?3:63; @@ -2813,7 +2793,7 @@ void FurnaceGUI::drawInsEdit() { if (dutyMax>0) { ImGui::Separator(); if (ins->type==DIV_INS_C64) { - if (ins->std.dutyMacro.mode) { + if (ins->c64.dutyIsAbs) { ImGui::Text("Duty Macro"); } else { ImGui::Text("Relative Duty Macro"); From 51761bc6dfc3ef238464f590218977446268eb52 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 10 Apr 2022 16:53:43 -0500 Subject: [PATCH 618/637] part 3 - C64 mishap --- src/engine/platform/c64.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/platform/c64.cpp b/src/engine/platform/c64.cpp index 736ed437a..b7521f035 100644 --- a/src/engine/platform/c64.cpp +++ b/src/engine/platform/c64.cpp @@ -158,7 +158,7 @@ void DivPlatformC64::tick() { } if (chan[i].std.duty.had) { DivInstrument* ins=parent->getIns(chan[i].ins); - if (ins->std.dutyMacro.mode) { + if (ins->c64.dutyIsAbs) { chan[i].duty=chan[i].std.duty.val; } else { chan[i].duty-=((signed char)chan[i].std.duty.val-12)*4; From 5f526f4b6e41994ce9bdfb993d7f74f6b997be29 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 10 Apr 2022 17:24:41 -0500 Subject: [PATCH 619/637] add playSub time log --- src/engine/engine.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 5288b8398..fbafb668b 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -764,6 +764,7 @@ void DivEngine::getCommandStream(std::vector& where) { } void DivEngine::playSub(bool preserveDrift, int goalRow) { + std::chrono::high_resolution_clock::time_point timeStart=std::chrono::high_resolution_clock::now(); for (int i=0; isetSkipRegisterWrites(false); reset(); if (preserveDrift && curOrder==0) return; @@ -822,6 +823,8 @@ void DivEngine::playSub(bool preserveDrift, int goalRow) { } skipping=false; cmdStream.clear(); + std::chrono::high_resolution_clock::time_point timeEnd=std::chrono::high_resolution_clock::now(); + logV("playSub() tool %dµs\n",std::chrono::duration_cast(timeEnd-timeStart).count()); } /* From 4ae13c15e60eb71c6ab243766997f2b2ff91ac35 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 10 Apr 2022 18:07:30 -0500 Subject: [PATCH 620/637] get rid of --- src/engine/macroInt.cpp | 107 +++++++++++++++++++++------------------- src/engine/macroInt.h | 29 +++-------- 2 files changed, 63 insertions(+), 73 deletions(-) diff --git a/src/engine/macroInt.cpp b/src/engine/macroInt.cpp index 489a6fab9..21ca93a1a 100644 --- a/src/engine/macroInt.cpp +++ b/src/engine/macroInt.cpp @@ -21,7 +21,9 @@ #include "instrument.h" void DivMacroStruct::doMacro(DivInstrumentMacro& source, bool released) { - if (finished) finished=false; + if (finished) { + finished=false; + } if (had!=has) { finished=true; } @@ -49,9 +51,9 @@ void DivMacroInt::next() { if (ins==NULL) return; // run macros // TODO: potentially get rid of list to avoid allocations - if (!macroList.empty()) { - for (std::list::iterator iter = macroList.begin(); iter!= macroList.end(); iter++) { - iter->doMacro(released); + for (size_t i=0; idoMacro(*macroSource[i],released); } } } @@ -60,14 +62,17 @@ void DivMacroInt::release() { released=true; } +#define ADD_MACRO(m,s) \ + macroList[macroListLen]=&m; \ + macroSource[macroListLen++]=&s; + void DivMacroInt::init(DivInstrument* which) { ins=which; // initialize - // TODO: potentially get rid of list to avoid allocations - while (!macroList.empty()) { - macroList.front().init(); - macroList.pop_front(); + for (size_t i=0; iinit(); } + macroListLen=0; released=false; @@ -75,65 +80,65 @@ void DivMacroInt::init(DivInstrument* which) { // prepare common macro if (ins->std.volMacro.len>0) { - macroList.push_back(DivMacroExecList(vol,ins->std.volMacro)); + ADD_MACRO(vol,ins->std.volMacro); } if (ins->std.arpMacro.len>0) { - macroList.push_back(DivMacroExecList(arp,ins->std.arpMacro)); + ADD_MACRO(arp,ins->std.arpMacro); } if (ins->std.dutyMacro.len>0) { - macroList.push_back(DivMacroExecList(duty,ins->std.dutyMacro)); + ADD_MACRO(duty,ins->std.dutyMacro); } if (ins->std.waveMacro.len>0) { - macroList.push_back(DivMacroExecList(wave,ins->std.waveMacro)); + ADD_MACRO(wave,ins->std.waveMacro); } if (ins->std.pitchMacro.len>0) { - macroList.push_back(DivMacroExecList(pitch,ins->std.pitchMacro)); + ADD_MACRO(pitch,ins->std.pitchMacro); } if (ins->std.ex1Macro.len>0) { - macroList.push_back(DivMacroExecList(ex1,ins->std.ex1Macro)); + ADD_MACRO(ex1,ins->std.ex1Macro); } if (ins->std.ex2Macro.len>0) { - macroList.push_back(DivMacroExecList(ex2,ins->std.ex2Macro)); + ADD_MACRO(ex2,ins->std.ex2Macro); } if (ins->std.ex3Macro.len>0) { - macroList.push_back(DivMacroExecList(ex3,ins->std.ex3Macro)); + ADD_MACRO(ex3,ins->std.ex3Macro); } if (ins->std.algMacro.len>0) { - macroList.push_back(DivMacroExecList(alg,ins->std.algMacro)); + ADD_MACRO(alg,ins->std.algMacro); } if (ins->std.fbMacro.len>0) { - macroList.push_back(DivMacroExecList(fb,ins->std.fbMacro)); + ADD_MACRO(fb,ins->std.fbMacro); } if (ins->std.fmsMacro.len>0) { - macroList.push_back(DivMacroExecList(fms,ins->std.fmsMacro)); + ADD_MACRO(fms,ins->std.fmsMacro); } if (ins->std.amsMacro.len>0) { - macroList.push_back(DivMacroExecList(ams,ins->std.amsMacro)); + ADD_MACRO(ams,ins->std.amsMacro); } if (ins->std.panLMacro.len>0) { - macroList.push_back(DivMacroExecList(panL,ins->std.panLMacro)); + ADD_MACRO(panL,ins->std.panLMacro); } if (ins->std.panRMacro.len>0) { - macroList.push_back(DivMacroExecList(panR,ins->std.panRMacro)); + ADD_MACRO(panR,ins->std.panRMacro); } if (ins->std.phaseResetMacro.len>0) { - macroList.push_back(DivMacroExecList(phaseReset,ins->std.phaseResetMacro)); + ADD_MACRO(phaseReset,ins->std.phaseResetMacro); } if (ins->std.ex4Macro.len>0) { - macroList.push_back(DivMacroExecList(ex4,ins->std.ex4Macro)); + ADD_MACRO(ex4,ins->std.ex4Macro); } if (ins->std.ex5Macro.len>0) { - macroList.push_back(DivMacroExecList(ex5,ins->std.ex5Macro)); + ADD_MACRO(ex5,ins->std.ex5Macro); } if (ins->std.ex6Macro.len>0) { - macroList.push_back(DivMacroExecList(ex6,ins->std.ex6Macro)); + ADD_MACRO(ex6,ins->std.ex6Macro); } if (ins->std.ex7Macro.len>0) { - macroList.push_back(DivMacroExecList(ex7,ins->std.ex7Macro)); + ADD_MACRO(ex7,ins->std.ex7Macro); } if (ins->std.ex8Macro.len>0) { - macroList.push_back(DivMacroExecList(ex8,ins->std.ex8Macro)); + ADD_MACRO(ex8,ins->std.ex8Macro); } // prepare FM operator macros @@ -141,72 +146,70 @@ void DivMacroInt::init(DivInstrument* which) { DivInstrumentSTD::OpMacro& m=ins->std.opMacros[i]; IntOp& o=op[i]; if (m.amMacro.len>0) { - macroList.push_back(DivMacroExecList(o.am,m.amMacro)); + ADD_MACRO(o.am,m.amMacro); } if (m.arMacro.len>0) { - macroList.push_back(DivMacroExecList(o.ar,m.arMacro)); + ADD_MACRO(o.ar,m.arMacro); } if (m.drMacro.len>0) { - macroList.push_back(DivMacroExecList(o.dr,m.drMacro)); + ADD_MACRO(o.dr,m.drMacro); } if (m.multMacro.len>0) { - macroList.push_back(DivMacroExecList(o.mult,m.multMacro)); + ADD_MACRO(o.mult,m.multMacro); } if (m.rrMacro.len>0) { - macroList.push_back(DivMacroExecList(o.rr,m.rrMacro)); + ADD_MACRO(o.rr,m.rrMacro); } if (m.slMacro.len>0) { - macroList.push_back(DivMacroExecList(o.sl,m.slMacro)); + ADD_MACRO(o.sl,m.slMacro); } if (m.tlMacro.len>0) { - macroList.push_back(DivMacroExecList(o.tl,m.tlMacro)); + ADD_MACRO(o.tl,m.tlMacro); } if (m.dt2Macro.len>0) { - macroList.push_back(DivMacroExecList(o.dt2,m.dt2Macro)); + ADD_MACRO(o.dt2,m.dt2Macro); } if (m.rsMacro.len>0) { - macroList.push_back(DivMacroExecList(o.rs,m.rsMacro)); + ADD_MACRO(o.rs,m.rsMacro); } if (m.dtMacro.len>0) { - macroList.push_back(DivMacroExecList(o.dt,m.dtMacro)); + ADD_MACRO(o.dt,m.dtMacro); } if (m.d2rMacro.len>0) { - macroList.push_back(DivMacroExecList(o.d2r,m.d2rMacro)); + ADD_MACRO(o.d2r,m.d2rMacro); } if (m.ssgMacro.len>0) { - macroList.push_back(DivMacroExecList(o.ssg,m.ssgMacro)); + ADD_MACRO(o.ssg,m.ssgMacro); } if (m.damMacro.len>0) { - macroList.push_back(DivMacroExecList(o.dam,m.damMacro)); + ADD_MACRO(o.dam,m.damMacro); } if (m.dvbMacro.len>0) { - macroList.push_back(DivMacroExecList(o.dvb,m.dvbMacro)); + ADD_MACRO(o.dvb,m.dvbMacro); } if (m.egtMacro.len>0) { - macroList.push_back(DivMacroExecList(o.egt,m.egtMacro)); + ADD_MACRO(o.egt,m.egtMacro); } if (m.kslMacro.len>0) { - macroList.push_back(DivMacroExecList(o.ksl,m.kslMacro)); + ADD_MACRO(o.ksl,m.kslMacro); } if (m.susMacro.len>0) { - macroList.push_back(DivMacroExecList(o.sus,m.susMacro)); + ADD_MACRO(o.sus,m.susMacro); } if (m.vibMacro.len>0) { - macroList.push_back(DivMacroExecList(o.vib,m.vibMacro)); + ADD_MACRO(o.vib,m.vibMacro); } if (m.wsMacro.len>0) { - macroList.push_back(DivMacroExecList(o.ws,m.wsMacro)); + ADD_MACRO(o.ws,m.wsMacro); } if (m.ksrMacro.len>0) { - macroList.push_back(DivMacroExecList(o.ksr,m.ksrMacro)); + ADD_MACRO(o.ksr,m.ksrMacro); } } - if (!macroList.empty()) { - for (std::list::iterator iter = macroList.begin(); iter!= macroList.end(); iter++) { - iter->prepare(); - } + for (size_t i=0; iprepare(*macroSource[i]); } } diff --git a/src/engine/macroInt.h b/src/engine/macroInt.h index 2480d743a..3c26eb659 100644 --- a/src/engine/macroInt.h +++ b/src/engine/macroInt.h @@ -21,7 +21,6 @@ #define _MACROINT_H #include "instrument.h" -#include struct DivMacroStruct { int pos; @@ -47,26 +46,11 @@ struct DivMacroStruct { mode(0) {} }; -struct DivMacroExecList { - DivMacroStruct& macro; - DivInstrumentMacro& source; - void init() { - macro.init(); - } - void prepare() { - macro.prepare(source); - } - void doMacro(bool released) { - macro.doMacro(source, released); - } - DivMacroExecList(DivMacroStruct& m, DivInstrumentMacro& s): - macro(m), - source(s) {} -}; - class DivMacroInt { DivInstrument* ins; - std::list macroList; + DivMacroStruct* macroList[128]; + DivInstrumentMacro* macroSource[128]; + size_t macroListLen; bool released; public: // common macro @@ -130,7 +114,7 @@ class DivMacroInt { DivMacroInt(): ins(NULL), - macroList(), + macroListLen(0), released(false), vol(), arp(), @@ -151,7 +135,10 @@ class DivMacroInt { ex5(), ex6(), ex7(), - ex8() {} + ex8() { + memset(macroList,0,128*sizeof(void*)); + memset(macroSource,0,128*sizeof(void*)); + } }; #endif From fddd05dc1a37858ec724ca058ed9aaa98fa2d3f2 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 10 Apr 2022 22:12:02 -0500 Subject: [PATCH 621/637] improve logging facility we have a log viewer within the program now --- CMakeLists.txt | 1 + src/audio/rtmidi.cpp | 24 ++-- src/audio/sdl.cpp | 8 +- src/engine/config.cpp | 8 +- src/engine/dispatchContainer.cpp | 6 +- src/engine/engine.cpp | 130 +++++++++--------- src/engine/fileOps.cpp | 226 +++++++++++++++---------------- src/engine/fileOpsIns.cpp | 48 +++---- src/engine/filter.cpp | 6 +- src/engine/instrument.cpp | 6 +- src/engine/platform/ay.cpp | 4 +- src/engine/platform/ay8930.cpp | 4 +- src/engine/platform/qsound.cpp | 4 +- src/engine/playback.cpp | 10 +- src/engine/safeReader.cpp | 16 +-- src/engine/sample.cpp | 4 +- src/engine/vgmOps.cpp | 12 +- src/engine/wavetable.cpp | 4 +- src/engine/winStuff.cpp | 6 +- src/gui/doAction.cpp | 3 + src/gui/editing.cpp | 4 +- src/gui/fileDialog.cpp | 6 +- src/gui/gui.cpp | 53 ++++---- src/gui/gui.h | 13 +- src/gui/guiConst.cpp | 7 + src/gui/log.cpp | 71 ++++++++++ src/gui/midiMap.cpp | 30 ++-- src/gui/pattern.cpp | 2 +- src/gui/sampleEdit.cpp | 8 +- src/gui/settings.cpp | 71 +++++----- src/log.cpp | 54 +++++--- src/main.cpp | 39 +++--- src/ta-log.h | 54 +++++++- 33 files changed, 556 insertions(+), 386 deletions(-) create mode 100644 src/gui/log.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index fef510efb..d3b98fc64 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -394,6 +394,7 @@ src/gui/doAction.cpp src/gui/editing.cpp src/gui/editControls.cpp src/gui/insEdit.cpp +src/gui/log.cpp src/gui/mixer.cpp src/gui/midiMap.cpp src/gui/newSong.cpp diff --git a/src/audio/rtmidi.cpp b/src/audio/rtmidi.cpp index fcd992beb..c4a6f8f9d 100644 --- a/src/audio/rtmidi.cpp +++ b/src/audio/rtmidi.cpp @@ -44,18 +44,18 @@ bool TAMidiInRtMidi::gather() { std::vector TAMidiInRtMidi::listDevices() { std::vector ret; - logD("listing devices.\n"); + logD("listing devices."); if (port==NULL) return ret; try { unsigned int count=port->getPortCount(); - logD("got port count.\n"); + logD("got port count."); for (unsigned int i=0; igetPortName(i); if (name!="") ret.push_back(name); } } catch (RtMidiError& e) { - logW("could not get MIDI inputs! %s\n",e.what()); + logW("could not get MIDI inputs! %s",e.what()); } return ret; } @@ -78,10 +78,10 @@ bool TAMidiInRtMidi::openDevice(String name) { } } isOpen=portOpen; - if (!portOpen) logW("could not find MIDI in device...\n"); + if (!portOpen) logW("could not find MIDI in device..."); return portOpen; } catch (RtMidiError& e) { - logW("could not open MIDI in device! %s\n",e.what()); + logW("could not open MIDI in device! %s",e.what()); return false; } return true; @@ -93,7 +93,7 @@ bool TAMidiInRtMidi::closeDevice() { try { port->closePort(); } catch (RtMidiError& e) { - logW("could not close MIDI in device! %s\n",e.what()); + logW("could not close MIDI in device! %s",e.what()); isOpen=false; // still return false; } @@ -106,7 +106,7 @@ bool TAMidiInRtMidi::init() { try { port=new RtMidiIn; } catch (RtMidiError& e) { - logW("could not initialize RtMidi in! %s\n",e.what()); + logW("could not initialize RtMidi in! %s",e.what()); return false; } return true; @@ -176,10 +176,10 @@ bool TAMidiOutRtMidi::openDevice(String name) { } } isOpen=portOpen; - if (!portOpen) logW("could not find MIDI out device...\n"); + if (!portOpen) logW("could not find MIDI out device..."); return portOpen; } catch (RtMidiError& e) { - logW("could not open MIDI out device! %s\n",e.what()); + logW("could not open MIDI out device! %s",e.what()); return false; } return true; @@ -191,7 +191,7 @@ bool TAMidiOutRtMidi::closeDevice() { try { port->closePort(); } catch (RtMidiError& e) { - logW("could not close MIDI out device! %s\n",e.what()); + logW("could not close MIDI out device! %s",e.what()); isOpen=false; // still return false; } @@ -210,7 +210,7 @@ std::vector TAMidiOutRtMidi::listDevices() { if (name!="") ret.push_back(name); } } catch (RtMidiError& e) { - logW("could not get MIDI outputs! %s\n",e.what()); + logW("could not get MIDI outputs! %s",e.what()); } return ret; } @@ -220,7 +220,7 @@ bool TAMidiOutRtMidi::init() { try { port=new RtMidiOut; } catch (RtMidiError& e) { - logW("could not initialize RtMidi out! %s\n",e.what()); + logW("could not initialize RtMidi out! %s",e.what()); return false; } return true; diff --git a/src/audio/sdl.cpp b/src/audio/sdl.cpp index 7fbbceb55..3ef38d4fa 100644 --- a/src/audio/sdl.cpp +++ b/src/audio/sdl.cpp @@ -75,7 +75,7 @@ std::vector TAAudioSDL::listAudioDevices() { std::vector ret; if (!audioSysStarted) { if (SDL_Init(SDL_INIT_AUDIO)<0) { - logE("could not initialize SDL to list audio devices\n"); + logE("could not initialize SDL to list audio devices"); } else { audioSysStarted=true; } @@ -96,12 +96,12 @@ std::vector TAAudioSDL::listAudioDevices() { bool TAAudioSDL::init(TAAudioDesc& request, TAAudioDesc& response) { if (initialized) { - logE("audio already initialized\n"); + logE("audio already initialized"); return false; } if (!audioSysStarted) { if (SDL_Init(SDL_INIT_AUDIO)<0) { - logE("could not initialize SDL\n"); + logE("could not initialize SDL"); return false; } audioSysStarted=true; @@ -119,7 +119,7 @@ bool TAAudioSDL::init(TAAudioDesc& request, TAAudioDesc& response) { ai=SDL_OpenAudioDevice(request.deviceName.empty()?NULL:request.deviceName.c_str(),0,&ac,&ar,SDL_AUDIO_ALLOW_FREQUENCY_CHANGE); if (ai==0) { - logE("could not open audio device: %s\n",SDL_GetError()); + logE("could not open audio device: %s",SDL_GetError()); return false; } diff --git a/src/engine/config.cpp b/src/engine/config.cpp index f7444e3cc..92866a9f9 100644 --- a/src/engine/config.cpp +++ b/src/engine/config.cpp @@ -32,13 +32,13 @@ bool DivEngine::saveConf() { configFile=configPath+String(CONFIG_FILE); FILE* f=ps_fopen(configFile.c_str(),"wb"); if (f==NULL) { - logW("could not write config file! %s\n",strerror(errno)); + logW("could not write config file! %s",strerror(errno)); return false; } for (auto& i: conf) { String toWrite=fmt::sprintf("%s=%s\n",i.first,i.second); if (fwrite(toWrite.c_str(),1,toWrite.size(),f)!=toWrite.size()) { - logW("could not write config file! %s\n",strerror(errno)); + logW("could not write config file! %s",strerror(errno)); fclose(f); return false; } @@ -52,10 +52,10 @@ bool DivEngine::loadConf() { configFile=configPath+String(CONFIG_FILE); FILE* f=ps_fopen(configFile.c_str(),"rb"); if (f==NULL) { - logI("creating default config.\n"); + logI("creating default config."); return saveConf(); } - logI("loading config.\n"); + logI("loading config."); while (!feof(f)) { String key=""; String value=""; diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index c89a0fa06..4f4ecf398 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -148,13 +148,13 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do bb[0]=blip_new(32768); if (bb[0]==NULL) { - logE("not enough memory!\n"); + logE("not enough memory!"); return; } bb[1]=blip_new(32768); if (bb[1]==NULL) { - logE("not enough memory!\n"); + logE("not enough memory!"); return; } @@ -312,7 +312,7 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do dispatch=new DivPlatformMMC5; break; default: - logW("this system is not supported yet! using dummy platform.\n"); + logW("this system is not supported yet! using dummy platform."); dispatch=new DivPlatformDummy; break; } diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index fbafb668b..690ee9930 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -193,7 +193,7 @@ void DivEngine::runExportThread() { sf=sf_open(exportPath.c_str(),SFM_WRITE,&si); if (sf==NULL) { - logE("could not open file for writing! (%s)\n",sf_strerror(NULL)); + logE("could not open file for writing! (%s)",sf_strerror(NULL)); exporting=false; return; } @@ -207,7 +207,7 @@ void DivEngine::runExportThread() { deinitAudioBackend(); playSub(false); - logI("rendering to file...\n"); + logI("rendering to file..."); while (playing) { nextBuf(NULL,outBuf,0,2,EXPORT_BUFSIZE); @@ -216,10 +216,10 @@ void DivEngine::runExportThread() { outBuf[2][1+(i<<1)]=MAX(-1.0f,MIN(1.0f,outBuf[1][i])); } if (totalProcessed>EXPORT_BUFSIZE) { - logE("error: total processed is bigger than export bufsize! %d>%d\n",totalProcessed,EXPORT_BUFSIZE); + logE("error: total processed is bigger than export bufsize! %d>%d",totalProcessed,EXPORT_BUFSIZE); } if (sf_writef_float(sf,outBuf[2],totalProcessed)!=(int)totalProcessed) { - logE("error: failed to write entire buffer!\n"); + logE("error: failed to write entire buffer!"); break; } } @@ -229,7 +229,7 @@ void DivEngine::runExportThread() { delete[] outBuf[2]; if (sf_close(sf)!=0) { - logE("could not close audio file!\n"); + logE("could not close audio file!"); } exporting=false; @@ -239,10 +239,10 @@ void DivEngine::runExportThread() { disCont[i].setQuality(lowQuality); } if (!output->setRun(true)) { - logE("error while activating audio!\n"); + logE("error while activating audio!"); } } - logI("done!\n"); + logI("done!"); break; } case DIV_EXPORT_MODE_MANY_SYS: { @@ -262,10 +262,10 @@ void DivEngine::runExportThread() { for (int i=0; iEXPORT_BUFSIZE) { - logE("error: total processed is bigger than export bufsize! (%d) %d>%d\n",i,totalProcessed,EXPORT_BUFSIZE); + logE("error: total processed is bigger than export bufsize! (%d) %d>%d",i,totalProcessed,EXPORT_BUFSIZE); } if (sf_writef_short(sf[i],sysBuf,totalProcessed)!=(int)totalProcessed) { - logE("error: failed to write entire buffer! (%d)\n",i); + logE("error: failed to write entire buffer! (%d)",i); break; } } @@ -311,7 +311,7 @@ void DivEngine::runExportThread() { for (int i=0; isetRun(true)) { - logE("error while activating audio!\n"); + logE("error while activating audio!"); } } - logI("done!\n"); + logI("done!"); break; } case DIV_EXPORT_MODE_MANY_CHAN: { @@ -338,20 +338,20 @@ void DivEngine::runExportThread() { outBuf[2]=new float[EXPORT_BUFSIZE*2]; int loopCount=remainingLoops; - logI("rendering to files...\n"); + logI("rendering to files..."); for (int i=0; iEXPORT_BUFSIZE) { - logE("error: total processed is bigger than export bufsize! %d>%d\n",totalProcessed,EXPORT_BUFSIZE); + logE("error: total processed is bigger than export bufsize! %d>%d",totalProcessed,EXPORT_BUFSIZE); } if (sf_writef_float(sf,outBuf[2],totalProcessed)!=(int)totalProcessed) { - logE("error: failed to write entire buffer!\n"); + logE("error: failed to write entire buffer!"); break; } } if (sf_close(sf)!=0) { - logE("could not close audio file!\n"); + logE("could not close audio file!"); } } exporting=false; @@ -405,10 +405,10 @@ void DivEngine::runExportThread() { disCont[i].setQuality(lowQuality); } if (!output->setRun(true)) { - logE("error while activating audio!\n"); + logE("error while activating audio!"); } } - logI("done!\n"); + logI("done!"); break; } } @@ -479,12 +479,12 @@ void DivEngine::renderSamples() { memPos=(memPos+0xfffff)&0xf00000; } if (memPos>=16777216) { - logW("out of ADPCM-A memory for sample %d!\n",i); + logW("out of ADPCM-A memory for sample %d!",i); break; } if (memPos+paddedLen>=16777216) { memcpy(adpcmAMem+memPos,s->dataA,16777216-memPos); - logW("out of ADPCM-A memory for sample %d!\n",i); + logW("out of ADPCM-A memory for sample %d!",i); } else { memcpy(adpcmAMem+memPos,s->dataA,paddedLen); } @@ -504,12 +504,12 @@ void DivEngine::renderSamples() { memPos=(memPos+0xfffff)&0xf00000; } if (memPos>=16777216) { - logW("out of ADPCM-B memory for sample %d!\n",i); + logW("out of ADPCM-B memory for sample %d!",i); break; } if (memPos+paddedLen>=16777216) { memcpy(adpcmBMem+memPos,s->dataB,16777216-memPos); - logW("out of ADPCM-B memory for sample %d!\n",i); + logW("out of ADPCM-B memory for sample %d!",i); } else { memcpy(adpcmBMem+memPos,s->dataB,paddedLen); } @@ -533,14 +533,14 @@ void DivEngine::renderSamples() { memPos=(memPos+0xffff)&0xff0000; } if (memPos>=16777216) { - logW("out of QSound PCM memory for sample %d!\n",i); + logW("out of QSound PCM memory for sample %d!",i); break; } if (memPos+length>=16777216) { for (unsigned int i=0; i<16777216-(memPos+length); i++) { qsoundMem[(memPos+i)^0x8000]=s->data8[i]; } - logW("out of QSound PCM memory for sample %d!\n",i); + logW("out of QSound PCM memory for sample %d!",i); } else { for (int i=0; idata8[i]; @@ -567,12 +567,12 @@ void DivEngine::renderSamples() { memPos=(memPos+0x1ffff)&0xfe0000; } if (memPos>=1048576) { - logW("out of X1-010 memory for sample %d!\n",i); + logW("out of X1-010 memory for sample %d!",i); break; } if (memPos+paddedLen>=1048576) { memcpy(x1_010Mem+memPos,s->data8,1048576-memPos); - logW("out of X1-010 memory for sample %d!\n",i); + logW("out of X1-010 memory for sample %d!",i); } else { memcpy(x1_010Mem+memPos,s->data8,paddedLen); } @@ -824,7 +824,7 @@ void DivEngine::playSub(bool preserveDrift, int goalRow) { skipping=false; cmdStream.clear(); std::chrono::high_resolution_clock::time_point timeEnd=std::chrono::high_resolution_clock::now(); - logV("playSub() tool %dµs\n",std::chrono::duration_cast(timeEnd-timeStart).count()); + logV("playSub() tool %dµs",std::chrono::duration_cast(timeEnd-timeStart).count()); } /* @@ -1346,7 +1346,7 @@ bool DivEngine::addWaveFromFile(const char* path) { } buf=new unsigned char[len]; if (fread(buf,1,len,f)!=(size_t)len) { - logW("did not read entire wavetable file buffer!\n"); + logW("did not read entire wavetable file buffer!"); delete[] buf; return false; } @@ -1386,22 +1386,22 @@ bool DivEngine::addWaveFromFile(const char* path) { wave->max=(unsigned char)reader.readC(); if (wave->max==255) { // new wavetable format unsigned char waveVersion=reader.readC(); - logI("reading modern .dmw...\n"); - logD("wave version %d\n",waveVersion); + logI("reading modern .dmw..."); + logD("wave version %d",waveVersion); wave->max=reader.readC(); for (int i=0; idata[i]=reader.readI(); } } else if (reader.size()==(size_t)(len+5)) { // read as .dmw - logI("reading .dmw...\n"); + logI("reading .dmw..."); if (len>256) len=256; for (int i=0; idata[i]=(unsigned char)reader.readC(); } } else { // read as binary - logI("reading binary...\n"); + logI("reading binary..."); len=reader.size(); if (len>256) len=256; reader.seek(0,SEEK_SET); @@ -1414,7 +1414,7 @@ bool DivEngine::addWaveFromFile(const char* path) { } catch (EndOfFileException& e) { // read as binary len=reader.size(); - logI("reading binary for being too small...\n"); + logI("reading binary for being too small..."); if (len>256) len=256; reader.seek(0,SEEK_SET); for (int i=0; idata,oldPat->data,256*32*sizeof(short)); - logD("found at %d\n",j); + logD("found at %d",j); didNotFind=false; break; } @@ -1953,7 +1953,7 @@ bool DivEngine::switchMaster() { disCont[i].setQuality(lowQuality); } if (!output->setRun(true)) { - logE("error while activating audio!\n"); + logE("error while activating audio!"); return false; } } else { @@ -2071,9 +2071,9 @@ void DivEngine::quitDispatch() { #define CHECK_CONFIG_DIR_MAC() \ configPath+="/Library/Application Support/Furnace"; \ if (stat(configPath.c_str(),&st)<0) { \ - logI("creating config dir...\n"); \ + logI("creating config dir..."); \ if (mkdir(configPath.c_str(),0755)<0) { \ - logW("could not make config dir! (%s)\n",strerror(errno)); \ + logW("could not make config dir! (%s)",strerror(errno)); \ configPath="."; \ } \ } @@ -2081,18 +2081,18 @@ void DivEngine::quitDispatch() { #define CHECK_CONFIG_DIR() \ configPath+="/.config"; \ if (stat(configPath.c_str(),&st)<0) { \ - logI("creating user config dir...\n"); \ + logI("creating user config dir..."); \ if (mkdir(configPath.c_str(),0755)<0) { \ - logW("could not make user config dir! (%s)\n",strerror(errno)); \ + logW("could not make user config dir! (%s)",strerror(errno)); \ configPath="."; \ } \ } \ if (configPath!=".") { \ configPath+="/furnace"; \ if (stat(configPath.c_str(),&st)<0) { \ - logI("creating config dir...\n"); \ + logI("creating config dir..."); \ if (mkdir(configPath.c_str(),0755)<0) { \ - logW("could not make config dir! (%s)\n",strerror(errno)); \ + logW("could not make config dir! (%s)",strerror(errno)); \ configPath="."; \ } \ } \ @@ -2114,7 +2114,7 @@ bool DivEngine::initAudioBackend() { switch (audioEngine) { case DIV_AUDIO_JACK: #ifndef HAVE_JACK - logE("Furnace was not compiled with JACK support!\n"); + logE("Furnace was not compiled with JACK support!"); setConf("audioEngine","SDL"); saveConf(); output=new TAAudioSDL; @@ -2129,7 +2129,7 @@ bool DivEngine::initAudioBackend() { output=new TAAudio; break; default: - logE("invalid audio engine!\n"); + logE("invalid audio engine!"); return false; } @@ -2147,7 +2147,7 @@ bool DivEngine::initAudioBackend() { output->setCallback(process,this); if (!output->init(want,got)) { - logE("error while initializing audio!\n"); + logE("error while initializing audio!"); delete output; output=NULL; audioEngine=DIV_AUDIO_NULL; @@ -2158,15 +2158,15 @@ bool DivEngine::initAudioBackend() { midiIns=output->midiIn->listDevices(); midiOuts=output->midiOut->listDevices(); } else { - logW("error while initializing MIDI!\n"); + logW("error while initializing MIDI!"); } if (output->midiIn) { String inName=getConfString("midiInDevice",""); if (!inName.empty()) { // try opening device - logI("opening MIDI input.\n"); + logI("opening MIDI input."); if (!output->midiIn->openDevice(inName)) { - logW("could not open MIDI input device!\n"); + logW("could not open MIDI input device!"); } } } @@ -2174,9 +2174,9 @@ bool DivEngine::initAudioBackend() { String outName=getConfString("midiOutDevice",""); if (!outName.empty()) { // try opening device - logI("opening MIDI output.\n"); + logI("opening MIDI output."); if (!output->midiOut->openDevice(outName)) { - logW("could not open MIDI output device!\n"); + logW("could not open MIDI output device!"); } } } @@ -2188,13 +2188,13 @@ bool DivEngine::deinitAudioBackend() { if (output!=NULL) { if (output->midiIn) { if (output->midiIn->isDeviceOpen()) { - logI("closing MIDI input.\n"); + logI("closing MIDI input."); output->midiIn->closeDevice(); } } if (output->midiOut) { if (output->midiOut->isDeviceOpen()) { - logI("closing MIDI output.\n"); + logI("closing MIDI output."); output->midiOut->closeDevice(); } } @@ -2222,7 +2222,7 @@ bool DivEngine::init() { int uid=getuid(); struct passwd* entry=getpwuid(uid); if (entry==NULL) { - logW("unable to determine config directory! (%s)\n",strerror(errno)); + logW("unable to determine config directory! (%s)",strerror(errno)); configPath="."; } else { configPath=entry->pw_dir; @@ -2241,21 +2241,21 @@ bool DivEngine::init() { #endif } #endif - logD("config path: %s\n",configPath.c_str()); + logD("config path: %s",configPath.c_str()); loadConf(); // init the rest of engine bool haveAudio=false; if (!initAudioBackend()) { - logE("no audio output available!\n"); + logE("no audio output available!"); } else { haveAudio=true; } samp_bb=blip_new(32768); if (samp_bb==NULL) { - logE("not enough memory!\n"); + logE("not enough memory!"); return false; } @@ -2290,7 +2290,7 @@ bool DivEngine::init() { return false; } else { if (!output->setRun(true)) { - logE("error while activating!\n"); + logE("error while activating!"); return false; } } @@ -2300,7 +2300,7 @@ bool DivEngine::init() { bool DivEngine::quit() { deinitAudioBackend(); quitDispatch(); - logI("saving config.\n"); + logI("saving config."); saveConf(); active=false; delete[] oscBuf[0]; diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index eb5e31e4a..8514d91ab 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -62,15 +62,15 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { ds.isDMF=true; if (!reader.seek(16,SEEK_SET)) { - logE("premature end of file!\n"); + logE("premature end of file!"); lastError="incomplete file"; delete[] file; return false; } ds.version=(unsigned char)reader.readC(); - logI("module version %d (0x%.2x)\n",ds.version,ds.version); + logI("module version %d (0x%.2x)",ds.version,ds.version); if (ds.version>0x1a) { - logE("this version is not supported by Furnace yet!\n"); + logE("this version is not supported by Furnace yet!"); lastError="this version is not supported by Furnace yet"; delete[] file; return false; @@ -106,23 +106,23 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { ds.manInfo=reader.readString((unsigned char)reader.readC()); ds.createdDate=reader.readString((unsigned char)reader.readC()); ds.revisionDate=reader.readString((unsigned char)reader.readC()); - logI("%s by %s\n",ds.name.c_str(),ds.author.c_str()); - logI("has YMU-specific data:\n"); - logI("- carrier: %s\n",ds.carrier.c_str()); - logI("- category: %s\n",ds.category.c_str()); - logI("- vendor: %s\n",ds.vendor.c_str()); - logI("- writer: %s\n",ds.writer.c_str()); - logI("- composer: %s\n",ds.composer.c_str()); - logI("- arranger: %s\n",ds.arranger.c_str()); - logI("- copyright: %s\n",ds.copyright.c_str()); - logI("- management group: %s\n",ds.manGroup.c_str()); - logI("- management info: %s\n",ds.manInfo.c_str()); - logI("- created on: %s\n",ds.createdDate.c_str()); - logI("- revision date: %s\n",ds.revisionDate.c_str()); + logI("%s by %s",ds.name.c_str(),ds.author.c_str()); + logI("has YMU-specific data:"); + logI("- carrier: %s",ds.carrier.c_str()); + logI("- category: %s",ds.category.c_str()); + logI("- vendor: %s",ds.vendor.c_str()); + logI("- writer: %s",ds.writer.c_str()); + logI("- composer: %s",ds.composer.c_str()); + logI("- arranger: %s",ds.arranger.c_str()); + logI("- copyright: %s",ds.copyright.c_str()); + logI("- management group: %s",ds.manGroup.c_str()); + logI("- management info: %s",ds.manInfo.c_str()); + logI("- created on: %s",ds.createdDate.c_str()); + logI("- revision date: %s",ds.revisionDate.c_str()); } else { ds.name=reader.readString((unsigned char)reader.readC()); ds.author=reader.readString((unsigned char)reader.readC()); - logI("%s by %s\n",ds.name.c_str(),ds.author.c_str()); + logI("%s by %s",ds.name.c_str(),ds.author.c_str()); } // compatibility flags @@ -164,7 +164,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { ds.tuning=443.23; } - logI("reading module data...\n"); + logI("reading module data..."); if (ds.version>0x0c) { ds.hilightA=reader.readC(); ds.hilightB=reader.readC(); @@ -186,7 +186,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { try { ds.hz=std::stoi(hz); } catch (std::exception& e) { - logW("invalid custom Hz!\n"); + logW("invalid custom Hz!"); ds.hz=60; } } @@ -199,25 +199,25 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { ds.ordersLen=(unsigned char)reader.readC(); if (ds.patLen<0) { - logE("pattern length is negative!\n"); + logE("pattern length is negative!"); lastError="pattern lengrh is negative!"; delete[] file; return false; } if (ds.patLen>256) { - logE("pattern length is too large!\n"); + logE("pattern length is too large!"); lastError="pattern length is too large!"; delete[] file; return false; } if (ds.ordersLen<0) { - logE("song length is negative!\n"); + logE("song length is negative!"); lastError="song length is negative!"; delete[] file; return false; } if (ds.ordersLen>127) { - logE("song is too long!\n"); + logE("song is too long!"); lastError="song is too long!"; delete[] file; return false; @@ -258,12 +258,12 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { addWarning("Yamaha YMU759 emulation is incomplete! please migrate your song to the OPL3 system."); } - logI("reading pattern matrix (%d)...\n",ds.ordersLen); + logI("reading pattern matrix (%d)...",ds.ordersLen); for (int i=0; i0x7f) { - logE("order at %d, %d out of range! (%d)\n",i,j,ds.orders.ord[i][j]); + logE("order at %d, %d out of range! (%d)",i,j,ds.orders.ord[i][j]); lastError=fmt::sprintf("order at %d, %d out of range! (%d)",i,j,ds.orders.ord[i][j]); delete[] file; return false; @@ -279,19 +279,19 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { } else { ds.insLen=16; } - logI("reading instruments (%d)...\n",ds.insLen); + logI("reading instruments (%d)...",ds.insLen); for (int i=0; i0x03) { ins->name=reader.readString((unsigned char)reader.readC()); } - logD("%d name: %s\n",i,ins->name.c_str()); + logD("%d name: %s",i,ins->name.c_str()); if (ds.version<0x0b) { // instruments in ancient versions were all FM or STD. ins->mode=1; } else { unsigned char mode=reader.readC(); - if (mode>1) logW("%d: invalid instrument mode %d!\n",i,mode); + if (mode>1) logW("%d: invalid instrument mode %d!",i,mode); ins->mode=mode; } ins->type=ins->mode?DIV_INS_FM:DIV_INS_STD; @@ -336,7 +336,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { ins->fm.ops=4; } if (ins->fm.ops!=2 && ins->fm.ops!=4) { - logE("invalid op count %d. did we read it wrong?\n",ins->fm.ops); + logE("invalid op count %d. did we read it wrong?",ins->fm.ops); lastError="file is corrupt or unreadable at operators"; delete[] file; return false; @@ -399,7 +399,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { } } - logD("OP%d: AM %d AR %d DAM %d DR %d DVB %d EGT %d KSL %d MULT %d RR %d SL %d SUS %d TL %d VIB %d WS %d RS %d DT %d D2R %d SSG-EG %d\n",j, + logD("OP%d: AM %d AR %d DAM %d DR %d DVB %d EGT %d KSL %d MULT %d RR %d SL %d SUS %d TL %d VIB %d WS %d RS %d DT %d D2R %d SSG-EG %d",j, ins->fm.op[j].am, ins->fm.op[j].ar, ins->fm.op[j].dam, @@ -532,7 +532,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { ins->gb.soundLen=reader.readC(); ins->std.volMacro.open=false; - logD("GB data: vol %d dir %d len %d sl %d\n",ins->gb.envVol,ins->gb.envDir,ins->gb.envLen,ins->gb.soundLen); + logD("GB data: vol %d dir %d len %d sl %d",ins->gb.envVol,ins->gb.envDir,ins->gb.envLen,ins->gb.soundLen); } else if (ds.system[0]==DIV_SYSTEM_GB) { // try to convert macro to envelope if (ins->std.volMacro.len>0) { @@ -553,7 +553,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { if (ds.version>0x0b) { ds.waveLen=(unsigned char)reader.readC(); - logI("reading wavetables (%d)...\n",ds.waveLen); + logI("reading wavetables (%d)...",ds.waveLen); for (int i=0; ilen=(unsigned char)reader.readI(); @@ -564,12 +564,12 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { wave->max=63; } if (wave->len>65) { - logE("invalid wave length %d. are we doing something wrong?\n",wave->len); + logE("invalid wave length %d. are we doing something wrong?",wave->len); lastError="file is corrupt or unreadable at wavetables"; delete[] file; return false; } - logD("%d length %d\n",i,wave->len); + logD("%d length %d",i,wave->len); for (int j=0; jlen; j++) { if (ds.version<0x0e) { wave->data[j]=reader.readC(); @@ -588,7 +588,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { } } - logI("reading patterns (%d channels, %d orders)...\n",getChannelCount(ds.system[0]),ds.ordersLen); + logI("reading patterns (%d channels, %d orders)...",getChannelCount(ds.system[0]),ds.ordersLen); for (int i=0; i4 || chan.effectRows<1) { - logE("invalid effect row count %d. are you sure everything is ok?\n",chan.effectRows); + logE("invalid effect row count %d. are you sure everything is ok?",chan.effectRows); lastError="file is corrupt or unreadable at effect rows"; delete[] file; return false; @@ -629,7 +629,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { pat->data[k][1]+=2; } if (pat->data[k][0]==0 && pat->data[k][1]!=0) { - logD("what? %d:%d:%d note %d octave %d\n",i,j,k,pat->data[k][0],pat->data[k][1]); + logD("what? %d:%d:%d note %d octave %d",i,j,k,pat->data[k][0],pat->data[k][1]); pat->data[k][0]=12; pat->data[k][1]--; } @@ -676,7 +676,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { } ds.sampleLen=(unsigned char)reader.readC(); - logI("reading samples (%d)...\n",ds.sampleLen); + logI("reading samples (%d)...",ds.sampleLen); if (ds.version<0x0b && ds.sampleLen>0) { // TODO what is this for? reader.readC(); } @@ -687,7 +687,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { int vol=50; short* data; if (length<0) { - logE("invalid sample length %d. are we doing something wrong?\n",length); + logE("invalid sample length %d. are we doing something wrong?",length); lastError="file is corrupt or unreadable at samples"; delete[] file; return false; @@ -697,7 +697,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { } else { sample->name=""; } - logD("%d name %s (%d)\n",i,sample->name.c_str(),length); + logD("%d name %s (%d)",i,sample->name.c_str(),length); sample->rate=22050; if (ds.version>=0x0b) { sample->rate=fileToDivRate(reader.readC()); @@ -707,7 +707,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { if (ds.version>0x15) { sample->depth=reader.readC(); if (sample->depth!=8 && sample->depth!=16) { - logW("%d: sample depth is wrong! (%d)\n",i,sample->depth); + logW("%d: sample depth is wrong! (%d)",i,sample->depth); sample->depth=16; } } else { @@ -724,12 +724,12 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { } if (pitch!=5) { - logD("%d: scaling from %d...\n",i,pitch); + logD("%d: scaling from %d...",i,pitch); } // render data if (!sample->init((double)length/samplePitches[pitch])) { - logE("%d: error while initializing sample!\n",i); + logE("%d: error while initializing sample!",i); } unsigned int k=0; @@ -754,7 +754,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { if (reader.tell()DIV_ENGINE_VERSION) { - logW("this module was created with a more recent version of Furnace!\n"); + logW("this module was created with a more recent version of Furnace!"); addWarning("this module was created with a more recent version of Furnace!"); } @@ -903,7 +903,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { int infoSeek=reader.readI(); if (!reader.seek(infoSeek,SEEK_SET)) { - logE("couldn't seek to info header at %d!\n",infoSeek); + logE("couldn't seek to info header at %d!",infoSeek); lastError="couldn't seek to info header!"; delete[] file; return false; @@ -912,7 +912,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { // read header reader.read(magic,4); if (strcmp(magic,"INFO")!=0) { - logE("invalid info header!\n"); + logE("invalid info header!"); lastError="invalid info header!"; delete[] file; return false; @@ -939,49 +939,49 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { int numberOfPats=reader.readI(); if (ds.patLen<0) { - logE("pattern length is negative!\n"); + logE("pattern length is negative!"); lastError="pattern lengrh is negative!"; delete[] file; return false; } if (ds.patLen>256) { - logE("pattern length is too large!\n"); + logE("pattern length is too large!"); lastError="pattern length is too large!"; delete[] file; return false; } if (ds.ordersLen<0) { - logE("song length is negative!\n"); + logE("song length is negative!"); lastError="song length is negative!"; delete[] file; return false; } if (ds.ordersLen>256) { - logE("song is too long!\n"); + logE("song is too long!"); lastError="song is too long!"; delete[] file; return false; } if (ds.insLen<0 || ds.insLen>256) { - logE("invalid instrument count!\n"); + logE("invalid instrument count!"); lastError="invalid instrument count!"; delete[] file; return false; } if (ds.waveLen<0 || ds.waveLen>256) { - logE("invalid wavetable count!\n"); + logE("invalid wavetable count!"); lastError="invalid wavetable count!"; delete[] file; return false; } if (ds.sampleLen<0 || ds.sampleLen>256) { - logE("invalid sample count!\n"); + logE("invalid sample count!"); lastError="invalid sample count!"; delete[] file; return false; } if (numberOfPats<0) { - logE("invalid pattern count!\n"); + logE("invalid pattern count!"); lastError="invalid pattern count!"; delete[] file; return false; @@ -991,7 +991,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { unsigned char sysID=reader.readC(); ds.system[i]=systemFromFileFur(sysID); if (sysID!=0 && systemToFileFur(ds.system[i])==0) { - logE("unrecognized system ID %.2x\n",ds.system[i]); + logE("unrecognized system ID %.2x",ds.system[i]); lastError=fmt::sprintf("unrecognized system ID %.2x!",ds.system[i]); delete[] file; return false; @@ -1004,7 +1004,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { } if (tchans>DIV_MAX_CHANS) { tchans=DIV_MAX_CHANS; - logW("too many channels!\n"); + logW("too many channels!"); } // system volume @@ -1061,7 +1061,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { ds.name=reader.readString(); ds.author=reader.readString(); - logI("%s by %s\n",ds.name.c_str(),ds.author.c_str()); + logI("%s by %s",ds.name.c_str(),ds.author.c_str()); if (ds.version>=33) { ds.tuning=reader.readF(); @@ -1167,7 +1167,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { reader.read(samplePtr,ds.sampleLen*4); for (int i=0; i8) { - logE("channel %d has zero or too many effect columns! (%d)\n",i,ds.pat[i].effectRows); + logE("channel %d has zero or too many effect columns! (%d)",i,ds.pat[i].effectRows); lastError=fmt::sprintf("channel %d has too many effect columns! (%d)",i,ds.pat[i].effectRows); delete[] file; return false; @@ -1242,9 +1242,9 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { // read instruments for (int i=0; iname=reader.readString(); sample->samples=reader.readI(); @@ -1348,12 +1348,12 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { reader.read(data,2*length); if (pitch!=5) { - logD("%d: scaling from %d...\n",i,pitch); + logD("%d: scaling from %d...",i,pitch); } // render data if (sample->depth!=8 && sample->depth!=16) { - logW("%d: sample depth is wrong! (%d)\n",i,sample->depth); + logW("%d: sample depth is wrong! (%d)",i,sample->depth); sample->depth=16; } sample->samples=(double)sample->samples/samplePitches[pitch]; @@ -1383,16 +1383,16 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { // read patterns for (int i: patPtr) { if (!reader.seek(i,SEEK_SET)) { - logE("couldn't seek to pattern in %x!\n",i); + logE("couldn't seek to pattern in %x!",i); lastError=fmt::sprintf("couldn't seek to pattern in %x!",i); ds.unload(); delete[] file; return false; } reader.read(magic,4); - logD("reading pattern in %x...\n",i); + logD("reading pattern in %x...",i); if (strcmp(magic,"PATR")!=0) { - logE("%x: invalid pattern header!\n",i); + logE("%x: invalid pattern header!",i); lastError="invalid pattern header!"; ds.unload(); delete[] file; @@ -1404,17 +1404,17 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { int index=reader.readS(); reader.readI(); - logD("- %d, %d\n",chan,index); + logD("- %d, %d",chan,index); if (chan<0 || chan>=tchans) { - logE("pattern channel out of range!\n",i); + logE("pattern channel out of range!",i); lastError="pattern channel out of range!"; ds.unload(); delete[] file; return false; } if (index<0 || index>255) { - logE("pattern index out of range!\n",i); + logE("pattern index out of range!",i); lastError="pattern index out of range!"; ds.unload(); delete[] file; @@ -1440,7 +1440,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { if (reader.tell()='1' && magic[0]<='9') { - logD("detected a FastTracker module\n"); + logD("detected a FastTracker module"); chCount=magic[0]-'0'; } else if (memcmp(magic,"FLT",3)==0 && magic[3]>='1' && magic[3]<='9') { - logD("detected a Fairlight module\n"); + logD("detected a Fairlight module"); chCount=magic[3]-'0'; } else if (memcmp(magic,"TDZ",3)==0 && magic[3]>='1' && magic[3]<='9') { - logD("detected a TakeTracker module\n"); + logD("detected a TakeTracker module"); chCount=magic[3]-'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')) { - logD("detected a Fast/TakeTracker module\n"); + logD("detected a Fast/TakeTracker module"); chCount=((magic[0]-'0')*10)+(magic[1]-'0'); } else { // TODO: Soundtracker MOD? @@ -1832,10 +1832,10 @@ bool DivEngine::loadMod(unsigned char* file, size_t len) { } success=true; } catch (EndOfFileException& e) { - //logE("premature end of file!\n"); + //logE("premature end of file!"); lastError="incomplete file"; } catch (InvalidHeaderException& e) { - //logE("invalid info header!\n"); + //logE("invalid info header!"); lastError="invalid info header!"; } return success; @@ -1853,14 +1853,14 @@ bool DivEngine::load(unsigned char* f, size_t slen) { 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 // TODO: move to a different location? - logD("loading as .mod...\n"); + logD("loading as .mod..."); if (loadMod(f,slen)) { delete[] f; return true; } lastError="not a .mod song"; - logD("loading as zlib...\n"); + logD("loading as zlib..."); // try zlib z_stream zl; memset(&zl,0,sizeof(z_stream)); @@ -1875,9 +1875,9 @@ bool DivEngine::load(unsigned char* f, size_t slen) { nextErr=inflateInit(&zl); if (nextErr!=Z_OK) { if (zl.msg==NULL) { - logE("zlib error: unknown! %d\n",nextErr); + logE("zlib error: unknown! %d",nextErr); } else { - logE("zlib error: %s\n",zl.msg); + logE("zlib error: %s",zl.msg); } inflateEnd(&zl); delete[] f; @@ -1894,10 +1894,10 @@ bool DivEngine::load(unsigned char* f, size_t slen) { nextErr=inflate(&zl,Z_SYNC_FLUSH); if (nextErr!=Z_OK && nextErr!=Z_STREAM_END) { if (zl.msg==NULL) { - logE("zlib error: unknown error! %d\n",nextErr); + logE("zlib error: unknown error! %d",nextErr); lastError="unknown decompression error"; } else { - logE("zlib inflate: %s\n",zl.msg); + logE("zlib inflate: %s",zl.msg); lastError=fmt::sprintf("decompression error: %s",zl.msg); } for (InflateBlock* i: blocks) delete i; @@ -1916,10 +1916,10 @@ bool DivEngine::load(unsigned char* f, size_t slen) { nextErr=inflateEnd(&zl); if (nextErr!=Z_OK) { if (zl.msg==NULL) { - logE("zlib end error: unknown error! %d\n",nextErr); + logE("zlib end error: unknown error! %d",nextErr); lastError="unknown decompression finish error"; } else { - logE("zlib end: %s\n",zl.msg); + logE("zlib end: %s",zl.msg); lastError=fmt::sprintf("decompression finish error: %s",zl.msg); } for (InflateBlock* i: blocks) delete i; @@ -1934,7 +1934,7 @@ bool DivEngine::load(unsigned char* f, size_t slen) { finalSize+=i->blockSize; } if (finalSize<1) { - logE("compressed too small!\n"); + logE("compressed too small!"); lastError="file too small"; for (InflateBlock* i: blocks) delete i; blocks.clear(); @@ -1951,7 +1951,7 @@ bool DivEngine::load(unsigned char* f, size_t slen) { len=finalSize; delete[] f; } else { - logD("loading as uncompressed\n"); + logD("loading as uncompressed"); file=(unsigned char*)f; len=slen; } @@ -1960,7 +1960,7 @@ bool DivEngine::load(unsigned char* f, size_t slen) { } else if (memcmp(file,DIV_FUR_MAGIC,16)==0) { return loadFur(file,len); } - logE("not a valid module!\n"); + logE("not a valid module!"); lastError="not a compatible song"; delete[] file; return false; @@ -2227,7 +2227,7 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { SafeWriter* DivEngine::saveDMF(unsigned char version) { // fail if version is not supported if (version<24 || version>26) { - logE("cannot save in this version!\n"); + logE("cannot save in this version!"); lastError="invalid version to save in! this is a bug!"; return NULL; } @@ -2255,60 +2255,60 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { } // fail if more than one system if (!isFlat && song.systemLen!=1) { - logE("cannot save multiple systems in this format!\n"); + logE("cannot save multiple systems in this format!"); lastError="multiple systems not possible on .dmf"; return NULL; } // fail if this is an YMU759 song if (song.system[0]==DIV_SYSTEM_YMU759) { - logE("cannot save YMU759 song!\n"); + logE("cannot save YMU759 song!"); lastError="YMU759 song saving is not supported"; return NULL; } // fail if the system is SMS+OPLL and version<25 if (version<25 && song.system[0]==DIV_SYSTEM_SMS && song.system[1]==DIV_SYSTEM_OPLL) { - logE("Master System FM expansion not supported in 1.0/legacy .dmf!\n"); + logE("Master System FM expansion not supported in 1.0/legacy .dmf!"); lastError="Master System FM expansion not supported in 1.0/legacy .dmf!"; return NULL; } // fail if the system is NES+VRC7 and version<25 if (version<25 && song.system[0]==DIV_SYSTEM_NES && song.system[1]==DIV_SYSTEM_VRC7) { - logE("NES + VRC7 not supported in 1.0/legacy .dmf!\n"); + logE("NES + VRC7 not supported in 1.0/legacy .dmf!"); lastError="NES + VRC7 not supported in 1.0/legacy .dmf!"; return NULL; } // fail if the system is FDS and version<25 if (version<25 && song.system[0]==DIV_SYSTEM_NES && song.system[1]==DIV_SYSTEM_FDS) { - logE("FDS not supported in 1.0/legacy .dmf!\n"); + logE("FDS not supported in 1.0/legacy .dmf!"); lastError="FDS not supported in 1.0/legacy .dmf!"; return NULL; } // fail if the system is Furnace-exclusive if (!isFlat && systemToFileDMF(song.system[0])==0) { - logE("cannot save Furnace-exclusive system song!\n"); + logE("cannot save Furnace-exclusive system song!"); lastError="this system is not possible on .dmf"; return NULL; } // fail if values are out of range if (song.ordersLen>127) { - logE("maximum .dmf song length is 127!\n"); + logE("maximum .dmf song length is 127!"); lastError="maximum .dmf song length is 127"; return NULL; } if (song.ins.size()>128) { - logE("maximum number of instruments in .dmf is 128!\n"); + logE("maximum number of instruments in .dmf is 128!"); lastError="maximum number of instruments in .dmf is 128"; return NULL; } if (song.wave.size()>64) { - logE("maximum number of wavetables in .dmf is 64!\n"); + logE("maximum number of wavetables in .dmf is 64!"); lastError="maximum number of wavetables in .dmf is 64"; return NULL; } for (int i=0; i0x7f) { - logE("order %d, %d is out of range (0-127)!\n",song.orders.ord[i][j]); + logE("order %d, %d is out of range (0-127)!",song.orders.ord[i][j]); lastError=fmt::sprintf("order %d, %d is out of range (0-127)",song.orders.ord[i][j]); return NULL; } @@ -2486,7 +2486,7 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { w->writeC(i->c64.s); w->writeC(i->c64.r); - logW("duty and cutoff precision will be lost!\n"); + logW("duty and cutoff precision will be lost!"); w->writeC((i->c64.duty*100)/4095); w->writeC(i->c64.ringMod); diff --git a/src/engine/fileOpsIns.cpp b/src/engine/fileOpsIns.cpp index 6658eea95..b88da310e 100644 --- a/src/engine/fileOpsIns.cpp +++ b/src/engine/fileOpsIns.cpp @@ -41,10 +41,10 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector& ret, St try { reader.seek(0,SEEK_SET); version=reader.readC(); - logD(".dmp version %d\n",version); + logD(".dmp version %d",version); } catch (EndOfFileException& e) { lastError="premature end of file"; - logE("premature end of file!\n"); + logE("premature end of file!"); delete ins; return; } @@ -64,38 +64,38 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector& ret, St switch (sys) { case 1: // YMU759 ins->type=DIV_INS_FM; - logD("instrument type is YMU759\n"); + logD("instrument type is YMU759"); break; case 2: // Genesis ins->type=DIV_INS_FM; - logD("instrument type is Genesis\n"); + logD("instrument type is Genesis"); break; case 3: // SMS ins->type=DIV_INS_STD; - logD("instrument type is SMS\n"); + logD("instrument type is SMS"); break; case 4: // Game Boy ins->type=DIV_INS_GB; - logD("instrument type is Game Boy\n"); + logD("instrument type is Game Boy"); break; case 5: // PC Engine ins->type=DIV_INS_PCE; - logD("instrument type is PC Engine\n"); + logD("instrument type is PC Engine"); break; case 6: // NES ins->type=DIV_INS_STD; - logD("instrument type is NES\n"); + logD("instrument type is NES"); break; case 7: case 0x17: // C64 ins->type=DIV_INS_C64; - logD("instrument type is C64\n"); + logD("instrument type is C64"); break; case 8: // Arcade ins->type=DIV_INS_FM; - logD("instrument type is Arcade\n"); + logD("instrument type is Arcade"); break; default: - logD("instrument type is unknown\n"); + logD("instrument type is unknown"); lastError="unknown instrument type!"; delete ins; return; @@ -103,7 +103,7 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector& ret, St } } catch (EndOfFileException& e) { lastError="premature end of file"; - logE("premature end of file!\n"); + logE("premature end of file!"); delete ins; return; } @@ -113,7 +113,7 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector& ret, St bool mode=true; if (version>1) { mode=reader.readC(); - logD("instrument mode is %d\n",mode); + logD("instrument mode is %d",mode); if (mode==0) { if (version<11) { ins->type=DIV_INS_STD; @@ -126,7 +126,7 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector& ret, St } if (mode) { // FM - logD("reading FM data...\n"); + logD("reading FM data..."); if (version<10) { if (version>1) { // bullcrap! no way to determine the instrument type other than a vague FM/STD! @@ -151,7 +151,7 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector& ret, St if (sys!=1) ins->fm.ams=reader.readC(); for (int j=0; jfm.ops; j++) { - logD("OP%d is at %d\n",j,reader.tell()); + logD("OP%d is at %d",j,reader.tell()); ins->fm.op[j].mult=reader.readC(); ins->fm.op[j].tl=reader.readC(); ins->fm.op[j].ar=reader.readC(); @@ -179,7 +179,7 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector& ret, St } } } else { // STD - logD("reading STD data...\n"); + logD("reading STD data..."); if (ins->type!=DIV_INS_GB) { ins->std.volMacro.len=reader.readC(); if (version>5) { @@ -295,7 +295,7 @@ void DivEngine::loadDMP(SafeReader& reader, std::vector& ret, St } } catch (EndOfFileException& e) { lastError="premature end of file"; - logE("premature end of file!\n"); + logE("premature end of file!"); delete ins; return; } @@ -330,7 +330,7 @@ void DivEngine::loadTFI(SafeReader& reader, std::vector& ret, St } } catch (EndOfFileException& e) { lastError="premature end of file"; - logE("premature end of file!\n"); + logE("premature end of file!"); delete ins; return; } @@ -372,7 +372,7 @@ void DivEngine::loadVGI(SafeReader& reader, std::vector& ret, St } } catch (EndOfFileException& e) { lastError="premature end of file"; - logE("premature end of file!\n"); + logE("premature end of file!"); delete ins; return; } @@ -453,7 +453,7 @@ void DivEngine::loadS3I(SafeReader& reader, std::vector& ret, St }; } catch (EndOfFileException& e) { lastError = "premature end of file"; - logE("premature end of file!\n"); + logE("premature end of file!"); delete ins; return; } @@ -623,7 +623,7 @@ void DivEngine::loadSBI(SafeReader& reader, std::vector& ret, St } catch (EndOfFileException& e) { lastError = "premature end of file"; - logE("premature end of file!\n"); + logE("premature end of file!"); delete ins; return; } @@ -640,7 +640,7 @@ void DivEngine::loadOPM(SafeReader& reader, std::vector& ret, St } catch (EndOfFileException& e) { lastError="premature end of file"; - logE("premature end of file!\n"); + logE("premature end of file!"); return; } } @@ -695,7 +695,7 @@ std::vector DivEngine::instrumentFromFile(const char* path) { } buf=new unsigned char[len]; if (fread(buf,1,len,f)!=(size_t)len) { - logW("did not read entire instrument file buffer!\n"); + logW("did not read entire instrument file buffer!"); lastError="did not read entire instrument file!"; delete[] buf; return ret; @@ -738,7 +738,7 @@ std::vector DivEngine::instrumentFromFile(const char* path) { } } catch (EndOfFileException& e) { lastError="premature end of file"; - logE("premature end of file!\n"); + logE("premature end of file!"); delete ins; delete[] buf; return ret; diff --git a/src/engine/filter.cpp b/src/engine/filter.cpp index 8e0ac02eb..8b0e5a2ea 100644 --- a/src/engine/filter.cpp +++ b/src/engine/filter.cpp @@ -30,7 +30,7 @@ float* DivFilterTables::sincIntegralTable=NULL; // licensed under same license as this program. float* DivFilterTables::getCubicTable() { if (cubicTable==NULL) { - logD("initializing cubic spline table.\n"); + logD("initializing cubic spline table."); cubicTable=new float[4096]; for (int i=0; i<1024; i++) { @@ -46,7 +46,7 @@ float* DivFilterTables::getCubicTable() { float* DivFilterTables:: getSincTable() { if (sincTable==NULL) { - logD("initializing sinc table.\n"); + logD("initializing sinc table."); sincTable=new float[65536]; sincTable[0]=1.0f; @@ -66,7 +66,7 @@ float* DivFilterTables:: getSincTable() { float* DivFilterTables::getSincIntegralTable() { if (sincIntegralTable==NULL) { - logD("initializing sinc integral table.\n"); + logD("initializing sinc integral table."); sincIntegralTable=new float[65536]; sincIntegralTable[0]=-0.5f; diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index 8f87e460c..68896a45e 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -486,7 +486,7 @@ DivDataErrors DivInstrument::readInsData(SafeReader& reader, short version) { char magic[4]; reader.read(magic,4); if (memcmp(magic,"INST",4)!=0) { - logE("invalid instrument header!\n"); + logE("invalid instrument header!"); return DIV_DATA_INVALID_HEADER; } reader.readI(); @@ -949,12 +949,12 @@ bool DivInstrument::save(const char* path) { FILE* outFile=ps_fopen(path,"wb"); if (outFile==NULL) { - logE("could not save instrument: %s!\n",strerror(errno)); + logE("could not save instrument: %s!",strerror(errno)); w->finish(); return false; } if (fwrite(w->getFinalBuf(),1,w->size(),outFile)!=w->size()) { - logW("did not write entire instrument!\n"); + logW("did not write entire instrument!"); } fclose(outFile); w->finish(); diff --git a/src/engine/platform/ay.cpp b/src/engine/platform/ay.cpp index 30a838126..727a14408 100644 --- a/src/engine/platform/ay.cpp +++ b/src/engine/platform/ay.cpp @@ -431,11 +431,11 @@ int DivPlatformAY8910::dispatch(DivCommand c) { if (c.value) { // port B ioPortB=true; portBVal=c.value2; - logI("AY I/O port B write: %x\n",portBVal); + logI("AY I/O port B write: %x",portBVal); } else { // port A ioPortA=true; portAVal=c.value2; - logI("AY I/O port A write: %x\n",portAVal); + logI("AY I/O port A write: %x",portAVal); } updateOutSel(true); immWrite(14+(c.value?1:0),(c.value?portBVal:portAVal)); diff --git a/src/engine/platform/ay8930.cpp b/src/engine/platform/ay8930.cpp index a470dceb9..e9af64fc7 100644 --- a/src/engine/platform/ay8930.cpp +++ b/src/engine/platform/ay8930.cpp @@ -456,11 +456,11 @@ int DivPlatformAY8930::dispatch(DivCommand c) { if (c.value) { // port B ioPortB=true; portBVal=c.value2; - logI("AY I/O port B write: %x\n",portBVal); + logI("AY I/O port B write: %x",portBVal); } else { // port A ioPortA=true; portAVal=c.value2; - logI("AY I/O port A write: %x\n",portAVal); + logI("AY I/O port A write: %x",portAVal); } updateOutSel(true); immWrite(14+(c.value?1:0),(c.value?portBVal:portAVal)); diff --git a/src/engine/platform/qsound.cpp b/src/engine/platform/qsound.cpp index a63ca9e50..0fd25ec72 100644 --- a/src/engine/platform/qsound.cpp +++ b/src/engine/platform/qsound.cpp @@ -336,7 +336,7 @@ void DivPlatformQSound::tick() { rWrite(q1_reg_map[Q1V_LOOP][i], qsound_loop); rWrite(q1_reg_map[Q1V_START][i], qsound_addr); rWrite(q1_reg_map[Q1V_PHASE][i], 0x8000); - //logW("ch %d bank=%04x, addr=%04x, end=%04x, loop=%04x!\n",i,qsound_bank,qsound_addr,qsound_end,qsound_loop); + //logV("ch %d bank=%04x, addr=%04x, end=%04x, loop=%04x!",i,qsound_bank,qsound_addr,qsound_end,qsound_loop); // Write sample address. Enable volume if (!chan[i].std.vol.had) { rWrite(q1_reg_map[Q1V_VOL][i], chan[i].vol << 4); @@ -347,7 +347,7 @@ void DivPlatformQSound::tick() { rWrite(q1_reg_map[Q1V_VOL][i], 0); rWrite(q1_reg_map[Q1V_FREQ][i], 0); } else if (chan[i].active) { - //logW("ch %d frequency set to %04x, off=%f, note=%d, %04x!\n",i,chan[i].freq,off,chan[i].note,QS_NOTE_FREQUENCY(chan[i].note)); + //logV("ch %d frequency set to %04x, off=%f, note=%d, %04x!",i,chan[i].freq,off,chan[i].note,QS_NOTE_FREQUENCY(chan[i].note)); rWrite(q1_reg_map[Q1V_FREQ][i], chan[i].freq); } if (chan[i].keyOn) chan[i].keyOn=false; diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index a9497d667..97047b64d 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -1660,7 +1660,7 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi if (softLocked) { if (!isBusy.try_lock()) { - logV("audio is soft-locked (%d)\n",softLockCount++); + logV("audio is soft-locked (%d)",softLockCount++); return; } } else { @@ -1712,7 +1712,7 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi } } } - logD("%.2x\n",msg.type); + logD("%.2x",msg.type); output->midiIn->queue.pop(); } @@ -1845,7 +1845,7 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi if (remainingLoops>0) { remainingLoops--; if (!remainingLoops) { - logI("end of song!\n"); + logI("end of song!"); remainingLoops=-1; playing=false; freelance=false; @@ -1881,9 +1881,9 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi return; } - //logD("attempts: %d\n",attempts); + //logD("attempts: %d",attempts); if (attempts>=100) { - logE("hang detected! stopping! at %d seconds %d micro\n",totalSeconds,totalTicks); + logE("hang detected! stopping! at %d seconds %d micro",totalSeconds,totalTicks); freelance=false; playing=false; extValuePresent=false; diff --git a/src/engine/safeReader.cpp b/src/engine/safeReader.cpp index 9add1b5aa..7fa66e8b3 100644 --- a/src/engine/safeReader.cpp +++ b/src/engine/safeReader.cpp @@ -57,7 +57,7 @@ size_t SafeReader::size() { int SafeReader::read(void* where, size_t count) { #ifdef READ_DEBUG - logD("SR: reading %d bytes at %x\n",count,curSeek); + logD("SR: reading %d bytes at %x",count,curSeek); #endif if (count==0) return 0; if (curSeek+count>len) throw EndOfFileException(this,len); @@ -68,23 +68,23 @@ int SafeReader::read(void* where, size_t count) { signed char SafeReader::readC() { #ifdef READ_DEBUG - logD("SR: reading char %x:\n",curSeek); + logD("SR: reading char %x:",curSeek); #endif if (curSeek+1>len) throw EndOfFileException(this,len); #ifdef READ_DEBUG - logD("SR: %.2x\n",buf[curSeek]); + logD("SR: %.2x",buf[curSeek]); #endif return (signed char)buf[curSeek++]; } short SafeReader::readS() { #ifdef READ_DEBUG - logD("SR: reading short %x:\n",curSeek); + logD("SR: reading short %x:",curSeek); #endif if (curSeek+2>len) throw EndOfFileException(this,len); short ret=*(short*)(&buf[curSeek]); #ifdef READ_DEBUG - logD("SR: %.4x\n",ret); + logD("SR: %.4x",ret); #endif curSeek+=2; return ret; @@ -99,13 +99,13 @@ short SafeReader::readS_BE() { int SafeReader::readI() { #ifdef READ_DEBUG - logD("SR: reading int %x:\n",curSeek); + logD("SR: reading int %x:",curSeek); #endif if (curSeek+4>len) throw EndOfFileException(this,len); int ret=*(int*)(&buf[curSeek]); curSeek+=4; #ifdef READ_DEBUG - logD("SR: %.8x\n",ret); + logD("SR: %.8x",ret); #endif return ret; } @@ -141,7 +141,7 @@ double SafeReader::readD() { String SafeReader::readString(size_t stlen) { String ret; #ifdef READ_DEBUG - logD("SR: reading string len %d at %x\n",stlen,curSeek); + logD("SR: reading string len %d at %x",stlen,curSeek); #endif size_t curPos=0; while (curPosdepth,h->samples); \ samples=h->samples; \ \ - if (h->length!=getCurBufLen()) logW("undo buffer length not equal to current buffer length! %d != %d\n",h->length,getCurBufLen()); \ + if (h->length!=getCurBufLen()) logW("undo buffer length not equal to current buffer length! %d != %d",h->length,getCurBufLen()); \ \ void* buf=getCurBuf(); \ \ diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index 68df49c50..b20813489 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -415,7 +415,7 @@ void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write } if (write.addr>=0xffff0000) { // Furnace special command unsigned char streamID=streamOff+((write.addr&0xff00)>>8); - logD("writing stream command %x:%x with stream ID %d\n",write.addr,write.val,streamID); + logD("writing stream command %x:%x with stream ID %d",write.addr,write.val,streamID); switch (write.addr&0xff) { case 0: // play sample if (write.valoff8=sampleSeek; sampleSeek+=sample->length8; } @@ -1447,7 +1447,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) { if (waitTime>0) { w->writeC(0x61); w->writeS(waitTime); - printf("wait is: %f\n",waitTime); + logV("wait is: %f",waitTime); totalWait-=waitTime; tickCount+=waitTime; } @@ -1561,7 +1561,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version) { freelance=false; extValuePresent=false; - logI("%d register writes total.\n",writeCount); + logI("%d register writes total.",writeCount); BUSY_END; return w; diff --git a/src/engine/wavetable.cpp b/src/engine/wavetable.cpp index 5d8c8a1d3..db6c33b2d 100644 --- a/src/engine/wavetable.cpp +++ b/src/engine/wavetable.cpp @@ -73,12 +73,12 @@ bool DivWavetable::save(const char* path) { FILE* outFile=ps_fopen(path,"wb"); if (outFile==NULL) { - logE("could not save wavetable: %s!\n",strerror(errno)); + logE("could not save wavetable: %s!",strerror(errno)); w->finish(); return false; } if (fwrite(w->getFinalBuf(),1,w->size(),outFile)!=w->size()) { - logW("did not write entire wavetable!\n"); + logW("did not write entire wavetable!"); } fclose(outFile); w->finish(); diff --git a/src/engine/winStuff.cpp b/src/engine/winStuff.cpp index 1830a301c..79065e88b 100644 --- a/src/engine/winStuff.cpp +++ b/src/engine/winStuff.cpp @@ -33,15 +33,15 @@ String getWinConfigPath() { configPath=path; configPath+=L"\\furnace"; if (!PathIsDirectoryW(configPath.c_str())) { - logI("creating config dir...\n"); + logI("creating config dir..."); int mkdirRet; if ((mkdirRet=SHCreateDirectory(NULL,configPath.c_str()))!=ERROR_SUCCESS) { - logW("could not make config dir! (%.8x)\n",mkdirRet); + logW("could not make config dir! (%.8x)",mkdirRet); configPath=L"."; } } } else { - logW("unable to determine config directory! (%.8x)\n",configHR); + logW("unable to determine config directory! (%.8x)",configHR); configPath=L"."; } return utf16To8(configPath.c_str()); diff --git a/src/gui/doAction.cpp b/src/gui/doAction.cpp index f9ae0428d..a1b974e36 100644 --- a/src/gui/doAction.cpp +++ b/src/gui/doAction.cpp @@ -212,6 +212,9 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_WINDOW_REGISTER_VIEW: nextWindow=GUI_WINDOW_REGISTER_VIEW; break; + case GUI_ACTION_WINDOW_LOG: + nextWindow=GUI_WINDOW_LOG; + break; case GUI_ACTION_COLLAPSE_WINDOW: collapseWindow=true; diff --git a/src/gui/editing.cpp b/src/gui/editing.cpp index 034ca441a..96f827f32 100644 --- a/src/gui/editing.cpp +++ b/src/gui/editing.cpp @@ -546,8 +546,8 @@ void FurnaceGUI::doPaste(PasteMode mode) { } if (invalidData) { - logW("invalid clipboard data! failed at line %d char %d\n",i,charPos); - logW("%s\n",line.c_str()); + logW("invalid clipboard data! failed at line %d char %d",i,charPos); + logW("%s",line.c_str()); break; } j++; diff --git a/src/gui/fileDialog.cpp b/src/gui/fileDialog.cpp index 95a6ac7b7..a0a10103d 100644 --- a/src/gui/fileDialog.cpp +++ b/src/gui/fileDialog.cpp @@ -65,7 +65,7 @@ bool FurnaceGUIFileDialog::render(const ImVec2& min, const ImVec2& max) { if (dialogS!=NULL) { if (dialogS->ready(0)) { fileName=dialogS->result(); - logD("returning %s\n",fileName.c_str()); + logD("returning %s",fileName.c_str()); return true; } } @@ -74,10 +74,10 @@ bool FurnaceGUIFileDialog::render(const ImVec2& min, const ImVec2& max) { if (dialogO->ready(0)) { if (dialogO->result().empty()) { fileName=""; - logD("returning nothing\n"); + logD("returning nothing"); } else { fileName=dialogO->result()[0]; - logD("returning %s\n",fileName.c_str()); + logD("returning %s",fileName.c_str()); } return true; } diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 669300518..ee69ae00a 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -815,10 +815,10 @@ void FurnaceGUI::prepareLayout() { } // copy initial layout - logI("loading default layout.\n"); + logI("loading default layout."); check=ps_fopen(finalLayoutPath,"w"); if (check==NULL) { - logW("could not write default layout!\n"); + logW("could not write default layout!"); return; } @@ -1539,7 +1539,7 @@ int FurnaceGUI::save(String path, int dmfVersion) { memset(&zl,0,sizeof(z_stream)); ret=deflateInit(&zl,Z_DEFAULT_COMPRESSION); if (ret!=Z_OK) { - logE("zlib error!\n"); + logE("zlib error!"); lastError="compression error"; fclose(outFile); w->finish(); @@ -1551,7 +1551,7 @@ int FurnaceGUI::save(String path, int dmfVersion) { zl.avail_out=131072; zl.next_out=zbuf; if ((ret=deflate(&zl,Z_NO_FLUSH))==Z_STREAM_ERROR) { - logE("zlib stream error!\n"); + logE("zlib stream error!"); lastError="zlib stream error"; deflateEnd(&zl); fclose(outFile); @@ -1561,7 +1561,7 @@ int FurnaceGUI::save(String path, int dmfVersion) { size_t amount=131072-zl.avail_out; if (amount>0) { if (fwrite(zbuf,1,amount,outFile)!=amount) { - logE("did not write entirely: %s!\n",strerror(errno)); + logE("did not write entirely: %s!",strerror(errno)); lastError=strerror(errno); deflateEnd(&zl); fclose(outFile); @@ -1573,7 +1573,7 @@ int FurnaceGUI::save(String path, int dmfVersion) { zl.avail_out=131072; zl.next_out=zbuf; if ((ret=deflate(&zl,Z_FINISH))==Z_STREAM_ERROR) { - logE("zlib finish stream error!\n"); + logE("zlib finish stream error!"); lastError="zlib finish stream error"; deflateEnd(&zl); fclose(outFile); @@ -1582,7 +1582,7 @@ int FurnaceGUI::save(String path, int dmfVersion) { } if (131072-zl.avail_out>0) { if (fwrite(zbuf,1,131072-zl.avail_out,outFile)!=(131072-zl.avail_out)) { - logE("did not write entirely: %s!\n",strerror(errno)); + logE("did not write entirely: %s!",strerror(errno)); lastError=strerror(errno); deflateEnd(&zl); fclose(outFile); @@ -1593,7 +1593,7 @@ int FurnaceGUI::save(String path, int dmfVersion) { deflateEnd(&zl); #else if (fwrite(w->getFinalBuf(),1,w->size(),outFile)!=w->size()) { - logE("did not write entirely: %s!\n",strerror(errno)); + logE("did not write entirely: %s!",strerror(errno)); lastError=strerror(errno); fclose(outFile); w->finish(); @@ -1613,7 +1613,7 @@ int FurnaceGUI::save(String path, int dmfVersion) { int FurnaceGUI::load(String path) { if (!path.empty()) { - logI("loading module...\n"); + logI("loading module..."); FILE* f=ps_fopen(path.c_str(),"rb"); if (f==NULL) { perror("error"); @@ -1635,7 +1635,7 @@ int FurnaceGUI::load(String path) { } if (len<1) { if (len==0) { - logE("that file is empty!\n"); + logE("that file is empty!"); lastError="file is empty"; } else { perror("tell error"); @@ -1662,7 +1662,7 @@ int FurnaceGUI::load(String path) { fclose(f); if (!e->load(file,(size_t)len)) { lastError=e->getLastError(); - logE("could not open file!\n"); + logE("could not open file!"); return 1; } } @@ -2076,7 +2076,7 @@ bool FurnaceGUI::loop() { macroLoopDragActive=false; waveDragActive=false; if (sampleDragActive) { - logD("stopping sample drag\n"); + logD("stopping sample drag"); if (sampleDragMode) { e->renderSamplesP(); } else { @@ -2506,6 +2506,7 @@ bool FurnaceGUI::loop() { if (ImGui::MenuItem("oscilloscope",BIND_FOR(GUI_ACTION_WINDOW_OSCILLOSCOPE),oscOpen)) oscOpen=!oscOpen; if (ImGui::MenuItem("volume meter",BIND_FOR(GUI_ACTION_WINDOW_VOL_METER),volMeterOpen)) volMeterOpen=!volMeterOpen; if (ImGui::MenuItem("register view",BIND_FOR(GUI_ACTION_WINDOW_REGISTER_VIEW),regViewOpen)) regViewOpen=!regViewOpen; + if (ImGui::MenuItem("log viewer",BIND_FOR(GUI_ACTION_WINDOW_LOG),logOpen)) logOpen=!logOpen; if (ImGui::MenuItem("statistics",BIND_FOR(GUI_ACTION_WINDOW_STATS),statsOpen)) statsOpen=!statsOpen; ImGui::EndMenu(); @@ -2613,6 +2614,7 @@ bool FurnaceGUI::loop() { drawNotes(); drawChannels(); drawRegView(); + drawLog(); if (inspectorOpen) ImGui::ShowMetricsWindow(&inspectorOpen); @@ -2713,7 +2715,7 @@ bool FurnaceGUI::loop() { } break; case GUI_FILE_SAVE: { - logD("saving: %s\n",copyOfName.c_str()); + logD("saving: %s",copyOfName.c_str()); String lowerCase=fileName; for (char& i: lowerCase) { if (i>='A' && i<='Z') i+='a'-'A'; @@ -2730,7 +2732,7 @@ bool FurnaceGUI::loop() { break; } case GUI_FILE_SAVE_DMF_LEGACY: - logD("saving: %s\n",copyOfName.c_str()); + logD("saving: %s",copyOfName.c_str()); if (save(copyOfName,24)>0) { showError(fmt::sprintf("Error while saving file! (%s)",lastError)); } @@ -2865,7 +2867,7 @@ bool FurnaceGUI::loop() { if (aboutOpen) drawAbout(); if (ImGui::BeginPopupModal("Rendering...",NULL,ImGuiWindowFlags_AlwaysAutoResize)) { - ImGui::Text("Please wait...\n"); + ImGui::Text("Please wait..."); if (ImGui::Button("Abort")) { if (e->haltAudioFile()) { ImGui::CloseCurrentPopup(); @@ -2947,22 +2949,22 @@ bool FurnaceGUI::loop() { if (backupTimer<=0) { backupTask=std::async(std::launch::async,[this]() -> bool { if (backupPath==curFileName) { - logD("backup file open. not saving backup.\n"); + logD("backup file open. not saving backup."); return true; } - logD("saving backup...\n"); + logD("saving backup..."); SafeWriter* w=e->saveFur(true); if (w!=NULL) { FILE* outFile=ps_fopen(backupPath.c_str(),"wb"); if (outFile!=NULL) { if (fwrite(w->getFinalBuf(),1,w->size(),outFile)!=w->size()) { - logW("did not write backup entirely: %s!\n",strerror(errno)); + logW("did not write backup entirely: %s!",strerror(errno)); w->finish(); } fclose(outFile); } else { - logW("could not save backup: %s!\n",strerror(errno)); + logW("could not save backup: %s!",strerror(errno)); w->finish(); } } @@ -3037,6 +3039,7 @@ bool FurnaceGUI::init() { notesOpen=e->getConfBool("notesOpen",false); channelsOpen=e->getConfBool("channelsOpen",false); regViewOpen=e->getConfBool("regViewOpen",false); + logOpen=e->getConfBool("logOpen",false); tempoView=e->getConfBool("tempoView",true); waveHex=e->getConfBool("waveHex",false); @@ -3066,7 +3069,7 @@ bool FurnaceGUI::init() { sdlWin=SDL_CreateWindow("Furnace",SDL_WINDOWPOS_CENTERED,SDL_WINDOWPOS_CENTERED,scrW*dpiScale,scrH*dpiScale,SDL_WINDOW_RESIZABLE|SDL_WINDOW_ALLOW_HIGHDPI); if (sdlWin==NULL) { - logE("could not open window! %s\n",SDL_GetError()); + logE("could not open window! %s",SDL_GetError()); return false; } @@ -3091,14 +3094,14 @@ bool FurnaceGUI::init() { SDL_FreeSurface(icon); free(furIcon); } else { - logW("could not create icon!\n"); + logW("could not create icon!"); } #endif sdlRend=SDL_CreateRenderer(sdlWin,-1,SDL_RENDERER_ACCELERATED|SDL_RENDERER_PRESENTVSYNC|SDL_RENDERER_TARGETTEXTURE); if (sdlRend==NULL) { - logE("could not init renderer! %s\n",SDL_GetError()); + logE("could not init renderer! %s",SDL_GetError()); return false; } @@ -3115,14 +3118,14 @@ bool FurnaceGUI::init() { applyUISettings(); if (!ImGui::GetIO().Fonts->Build()) { - logE("error while building font atlas!\n"); + logE("error while building font atlas!"); showError("error while loading fonts! please check your settings."); ImGui::GetIO().Fonts->Clear(); mainFont=ImGui::GetIO().Fonts->AddFontDefault(); patFont=mainFont; ImGui_ImplSDLRenderer_DestroyFontsTexture(); if (!ImGui::GetIO().Fonts->Build()) { - logE("error again while building font atlas!\n"); + logE("error again while building font atlas!"); } } @@ -3199,6 +3202,7 @@ bool FurnaceGUI::finish() { e->setConf("notesOpen",notesOpen); e->setConf("channelsOpen",channelsOpen); e->setConf("regViewOpen",regViewOpen); + e->setConf("logOpen",logOpen); // commit last window size e->setConf("lastWindowWidth",scrW); @@ -3298,6 +3302,7 @@ FurnaceGUI::FurnaceGUI(): notesOpen(false), channelsOpen(false), regViewOpen(false), + logOpen(false), /* editControlsDocked(false), ordersDocked(false), diff --git a/src/gui/gui.h b/src/gui/gui.h index 641cf7c02..bcb7deee3 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -167,6 +167,12 @@ enum FurnaceGUIColors { GUI_COLOR_PATTERN_EFFECT_SYS_SECONDARY, GUI_COLOR_PATTERN_EFFECT_MISC, + GUI_COLOR_LOGLEVEL_ERROR, + GUI_COLOR_LOGLEVEL_WARNING, + GUI_COLOR_LOGLEVEL_INFO, + GUI_COLOR_LOGLEVEL_DEBUG, + GUI_COLOR_LOGLEVEL_TRACE, + GUI_COLOR_EE_VALUE, GUI_COLOR_PLAYBACK_STAT, GUI_COLOR_MAX @@ -195,7 +201,8 @@ enum FurnaceGUIWindows { GUI_WINDOW_PIANO, GUI_WINDOW_NOTES, GUI_WINDOW_CHANNELS, - GUI_WINDOW_REGISTER_VIEW + GUI_WINDOW_REGISTER_VIEW, + GUI_WINDOW_LOG }; enum FurnaceGUIFileDialogs { @@ -290,6 +297,7 @@ enum FurnaceGUIActions { GUI_ACTION_WINDOW_NOTES, GUI_ACTION_WINDOW_CHANNELS, GUI_ACTION_WINDOW_REGISTER_VIEW, + GUI_ACTION_WINDOW_LOG, GUI_ACTION_COLLAPSE_WINDOW, GUI_ACTION_CLOSE_WINDOW, @@ -848,7 +856,7 @@ class FurnaceGUI { bool editControlsOpen, ordersOpen, insListOpen, songInfoOpen, patternOpen, insEditOpen; bool waveListOpen, waveEditOpen, sampleListOpen, sampleEditOpen, aboutOpen, settingsOpen; bool mixerOpen, debugOpen, inspectorOpen, oscOpen, volMeterOpen, statsOpen, compatFlagsOpen; - bool pianoOpen, notesOpen, channelsOpen, regViewOpen; + bool pianoOpen, notesOpen, channelsOpen, regViewOpen, logOpen; /* there ought to be a better way... bool editControlsDocked, ordersDocked, insListDocked, songInfoDocked, patternDocked, insEditDocked; @@ -1044,6 +1052,7 @@ class FurnaceGUI { void drawSettings(); void drawDebug(); void drawNewSong(); + void drawLog(); void parseKeybinds(); void promptKey(int which); diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index c3a7c72bd..1b292638c 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -191,6 +191,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={ D("WINDOW_NOTES", "Song Comments", 0), D("WINDOW_CHANNELS", "Channels", 0), D("WINDOW_REGISTER_VIEW", "Register View", 0), + D("WINDOW_LOG", "Log Viewer", 0), D("COLLAPSE_WINDOW", "Collapse/expand current window", 0), D("CLOSE_WINDOW", "Close current window", FURKMOD_SHIFT|SDLK_ESCAPE), @@ -473,6 +474,12 @@ const FurnaceGUIColorDef guiColors[GUI_COLOR_MAX]={ D(GUI_COLOR_PATTERN_EFFECT_SYS_SECONDARY,"",ImVec4(0.0f,1.0f,0.5f,1.0f)), D(GUI_COLOR_PATTERN_EFFECT_MISC,"",ImVec4(0.3f,0.3f,1.0f,1.0f)), + D(GUI_COLOR_LOGLEVEL_ERROR,"",ImVec4(1.0f,0.2f,0.2f,1.0f)), + D(GUI_COLOR_LOGLEVEL_WARNING,"",ImVec4(1.0f,1.0f,0.2f,1.0f)), + D(GUI_COLOR_LOGLEVEL_INFO,"",ImVec4(0.4f,1.0f,0.4f,1.0f)), + D(GUI_COLOR_LOGLEVEL_DEBUG,"",ImVec4(0.3f,0.5f,1.0f,1.0f)), + D(GUI_COLOR_LOGLEVEL_TRACE,"",ImVec4(0.8f,0.8f,0.8f,1.0f)), + D(GUI_COLOR_EE_VALUE,"",ImVec4(0.0f,1.0f,1.0f,1.0f)), D(GUI_COLOR_PLAYBACK_STAT,"",ImVec4(0.6f,0.6f,0.6f,1.0f)), }; diff --git a/src/gui/log.cpp b/src/gui/log.cpp new file mode 100644 index 000000000..2b83ab903 --- /dev/null +++ b/src/gui/log.cpp @@ -0,0 +1,71 @@ +#include "gui.h" +#include "../ta-log.h" +#include +#include + +const char* logLevels[5]={ + "ERROR", + "warning", + "info", + "debug", + "trace" +}; + +FurnaceGUIColors logColors[5]={ + GUI_COLOR_LOGLEVEL_ERROR, + GUI_COLOR_LOGLEVEL_WARNING, + GUI_COLOR_LOGLEVEL_INFO, + GUI_COLOR_LOGLEVEL_DEBUG, + GUI_COLOR_LOGLEVEL_TRACE +}; + +void FurnaceGUI::drawLog() { + if (nextWindow==GUI_WINDOW_LOG) { + logOpen=true; + ImGui::SetNextWindowFocus(); + nextWindow=GUI_WINDOW_NOTHING; + } + if (!logOpen) return; + if (ImGui::Begin("Log Viewer",&logOpen)) { + ImGui::Text("Level"); + ImGui::SameLine(); + ImGui::Combo("##LogLevel",&logLevel,logLevels,5); + if (ImGui::BeginTable("LogView",3,ImGuiTableFlags_ScrollY|ImGuiTableFlags_BordersInnerV)) { + ImGui::PushFont(patFont); + + float timeChars=ImGui::CalcTextSize("00:00:00").x; + float levelChars=ImGui::CalcTextSize("warning").x; + + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed,timeChars); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed,levelChars); + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch); + + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + ImGui::TableNextColumn(); + ImGui::TextUnformatted("time"); + ImGui::TableNextColumn(); + ImGui::TextUnformatted("level"); + ImGui::TableNextColumn(); + ImGui::TextUnformatted("message"); + + int pos=logPosition; + for (int i=0; i(logEntry.time).time_since_epoch().count(); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + // this will fail on 32-bit :< + ImGui::Text("%02ld:%02ld:%02ld",(t/3600)%24,(t/60)%60,t%60); + ImGui::TableNextColumn(); + ImGui::TextColored(uiColors[logColors[logEntry.loglevel]],"%s",logLevels[logEntry.loglevel]); + ImGui::TableNextColumn(); + ImGui::TextWrapped("%s",logEntry.text.c_str()); + } + ImGui::PopFont(); + ImGui::EndTable(); + } + } + ImGui::End(); +} \ No newline at end of file diff --git a/src/gui/midiMap.cpp b/src/gui/midiMap.cpp index 071540f92..568bf16ea 100644 --- a/src/gui/midiMap.cpp +++ b/src/gui/midiMap.cpp @@ -64,7 +64,7 @@ int MIDIMap::at(const TAMidiMessage& where) { #define UNDERSTAND_ARRAY_OPTION(x,yMax) if (optionNameS==#x) { \ if (optionIndex<0 || optionIndex>=yMax) { \ - logW("MIDI map array option %d out of range (0-%d) at line %d: %s\n",optionIndex,yMax,curLine,line); \ + logW("MIDI map array option %d out of range (0-%d) at line %d: %s",optionIndex,yMax,curLine,line); \ break; \ } \ x[optionIndex]=std::stoi(optionValueS); \ @@ -76,7 +76,7 @@ bool MIDIMap::read(String path) { FILE* f=fopen(path.c_str(),"rb"); if (f==NULL) { if (errno!=ENOENT) { - logE("error while loading MIDI mapping! %s\n",strerror(errno)); + logE("error while loading MIDI mapping! %s",strerror(errno)); } return false; } @@ -93,7 +93,7 @@ bool MIDIMap::read(String path) { int result=sscanf(line,"aOption %255s %d %255s",optionName,&optionIndex,optionValue); if (result!=3) { - logW("MIDI map garbage data at line %d: %s\n",curLine,line); + logW("MIDI map garbage data at line %d: %s",curLine,line); break; } @@ -105,12 +105,12 @@ bool MIDIMap::read(String path) { UNDERSTAND_ARRAY_OPTION(valueInputSpecificMSB,18) else UNDERSTAND_ARRAY_OPTION(valueInputSpecificLSB,18) else UNDERSTAND_ARRAY_OPTION(valueInputSpecificSingle,18) else { - logW("MIDI map unknown array option %s at line %d: %s\n",optionName,curLine,line); + logW("MIDI map unknown array option %s at line %d: %s",optionName,curLine,line); } } catch (std::out_of_range& e) { - logW("MIDI map invalid value %s for array option %s at line %d: %s\n",optionValue,optionName,curLine,line); + logW("MIDI map invalid value %s for array option %s at line %d: %s",optionValue,optionName,curLine,line); } catch (std::invalid_argument& e) { - logW("MIDI map invalid value %s for array option %s at line %d: %s\n",optionValue,optionName,curLine,line); + logW("MIDI map invalid value %s for array option %s at line %d: %s",optionValue,optionName,curLine,line); } curLine++; @@ -122,7 +122,7 @@ bool MIDIMap::read(String path) { String optionNameS, optionValueS; int result=sscanf(line,"option %255s %255s",optionName,optionValue); if (result!=2) { - logW("MIDI map garbage data at line %d: %s\n",curLine,line); + logW("MIDI map garbage data at line %d: %s",curLine,line); break; } @@ -143,12 +143,12 @@ bool MIDIMap::read(String path) { UNDERSTAND_OPTION(valueInputControlLSB) else UNDERSTAND_OPTION(valueInputControlSingle) else UNDERSTAND_FLOAT_OPTION(volExp) else { - logW("MIDI map unknown option %s at line %d: %s\n",optionName,curLine,line); + logW("MIDI map unknown option %s at line %d: %s",optionName,curLine,line); } } catch (std::out_of_range& e) { - logW("MIDI map invalid value %s for option %s at line %d: %s\n",optionValue,optionName,curLine,line); + logW("MIDI map invalid value %s for option %s at line %d: %s",optionValue,optionName,curLine,line); } catch (std::invalid_argument& e) { - logW("MIDI map invalid value %s for option %s at line %d: %s\n",optionValue,optionName,curLine,line); + logW("MIDI map invalid value %s for option %s at line %d: %s",optionValue,optionName,curLine,line); } curLine++; @@ -159,7 +159,7 @@ bool MIDIMap::read(String path) { MIDIBind bind; int result=sscanf(line,"%d %d %d %d %255s",&bind.type,&bind.channel,&bind.data1,&bind.data2,bindAction); if (result!=5 || result==EOF) { - logW("MIDI map garbage data at line %d: %s\n",curLine,line); + logW("MIDI map garbage data at line %d: %s",curLine,line); break; } @@ -172,7 +172,7 @@ bool MIDIMap::read(String path) { } } if (!foundAction) { - logW("MIDI map unknown action %s at line %d: %s\n",bindAction,curLine,line); + logW("MIDI map unknown action %s at line %d: %s",bindAction,curLine,line); break; } @@ -191,7 +191,7 @@ bool MIDIMap::read(String path) { bool MIDIMap::write(String path) { FILE* f=fopen(path.c_str(),"wb"); if (f==NULL) { - logE("error while saving MIDI mapping! %s\n",strerror(errno)); + logE("error while saving MIDI mapping! %s",strerror(errno)); return false; } @@ -218,7 +218,7 @@ bool MIDIMap::write(String path) { for (MIDIBind& i: binds) { if (fprintf(f,"%d %d %d %d %s\n",i.type,i.channel,i.data1,i.data2,guiActions[i.action].name)<0) { - logW("did not write MIDI mapping entirely! %s\n",strerror(errno)); + logW("did not write MIDI mapping entirely! %s",strerror(errno)); break; } } @@ -274,6 +274,6 @@ void MIDIMap::compile() { } map[i.type-8][i.channel][i.data1][i.data2]=i.action; - logD("MIDI mapping %d %d %d %d to %d\n",i.type-8,i.channel,i.data1,i.data2,i.action); + logD("MIDI mapping %d %d %d %d to %d",i.type-8,i.channel,i.data1,i.data2,i.action); } } diff --git a/src/gui/pattern.cpp b/src/gui/pattern.cpp index d56196d6c..0decef970 100644 --- a/src/gui/pattern.cpp +++ b/src/gui/pattern.cpp @@ -874,6 +874,6 @@ void FurnaceGUI::drawPattern() { if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_PATTERN; ImGui::End(); //int delta1=SDL_GetPerformanceCounter(); - //logV("render time: %dµs\n",(delta1-delta0)/(SDL_GetPerformanceFrequency()/1000000)); + //logV("render time: %dµs",(delta1-delta0)/(SDL_GetPerformanceFrequency()/1000000)); } diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index 18a91e290..04af4cab3 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -579,12 +579,12 @@ void FurnaceGUI::drawSampleEdit() { sampleTex=NULL; } if (avail.x>=1 && avail.y>=1) { - logD("recreating sample texture.\n"); + logD("recreating sample texture."); sampleTex=SDL_CreateTexture(sdlRend,SDL_PIXELFORMAT_ABGR8888,SDL_TEXTUREACCESS_STREAMING,avail.x,avail.y); sampleTexW=avail.x; sampleTexH=avail.y; if (sampleTex==NULL) { - logE("error while creating sample texture! %s\n",SDL_GetError()); + logE("error while creating sample texture! %s",SDL_GetError()); } else { updateSampleTex=true; } @@ -595,9 +595,9 @@ void FurnaceGUI::drawSampleEdit() { if (updateSampleTex) { unsigned int* data=NULL; int pitch=0; - logD("updating sample texture.\n"); + logD("updating sample texture."); if (SDL_LockTexture(sampleTex,NULL,(void**)&data,&pitch)!=0) { - logE("error while locking sample texture! %s\n",SDL_GetError()); + logE("error while locking sample texture! %s",SDL_GetError()); } else { ImU32 bgColor=ImGui::GetColorU32(ImGuiCol_FrameBg); ImU32 bgColorLoop=ImAlphaBlendColors(bgColor,ImGui::GetColorU32(ImGuiCol_FrameBgHovered,0.5)); diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 316e3ef51..2727c3a36 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -384,8 +384,8 @@ void FurnaceGUI::drawSettings() { TAAudioDesc& audioWant=e->getAudioDescWant(); TAAudioDesc& audioGot=e->getAudioDescGot(); - ImGui::Text("want: %d samples @ %.0fHz\n",audioWant.bufsize,audioWant.rate); - ImGui::Text("got: %d samples @ %.0fHz\n",audioGot.bufsize,audioGot.rate); + ImGui::Text("want: %d samples @ %.0fHz",audioWant.bufsize,audioWant.rate); + ImGui::Text("got: %d samples @ %.0fHz",audioGot.bufsize,audioGot.rate); ImGui::Separator(); @@ -1053,6 +1053,14 @@ void FurnaceGUI::drawSettings() { UI_COLOR_CONFIG(GUI_COLOR_EE_VALUE,"External command output"); ImGui::TreePop(); } + if (ImGui::TreeNode("Log Viewer")) { + UI_COLOR_CONFIG(GUI_COLOR_LOGLEVEL_ERROR,"Log level: Error"); + UI_COLOR_CONFIG(GUI_COLOR_LOGLEVEL_WARNING,"Log level: Warning"); + UI_COLOR_CONFIG(GUI_COLOR_LOGLEVEL_INFO,"Log level: Info"); + UI_COLOR_CONFIG(GUI_COLOR_LOGLEVEL_DEBUG,"Log level: Debug"); + UI_COLOR_CONFIG(GUI_COLOR_LOGLEVEL_TRACE,"Log level: Trace/Verbose"); + ImGui::TreePop(); + } ImGui::TreePop(); } @@ -1126,6 +1134,7 @@ void FurnaceGUI::drawSettings() { UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_NOTES); UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_CHANNELS); UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_REGISTER_VIEW); + UI_KEYBIND_CONFIG(GUI_ACTION_WINDOW_LOG); UI_KEYBIND_CONFIG(GUI_ACTION_COLLAPSE_WINDOW); UI_KEYBIND_CONFIG(GUI_ACTION_CLOSE_WINDOW); @@ -1633,14 +1642,14 @@ void FurnaceGUI::commitSettings() { ImGui_ImplSDLRenderer_DestroyFontsTexture(); if (!ImGui::GetIO().Fonts->Build()) { - logE("error while building font atlas!\n"); + logE("error while building font atlas!"); showError("error while loading fonts! please check your settings."); ImGui::GetIO().Fonts->Clear(); mainFont=ImGui::GetIO().Fonts->AddFontDefault(); patFont=mainFont; ImGui_ImplSDLRenderer_DestroyFontsTexture(); if (!ImGui::GetIO().Fonts->Build()) { - logE("error again while building font atlas!\n"); + logE("error again while building font atlas!"); } } } @@ -1648,7 +1657,7 @@ void FurnaceGUI::commitSettings() { bool FurnaceGUI::importColors(String path) { FILE* f=ps_fopen(path.c_str(),"rb"); if (f==NULL) { - logW("error while opening color file for import: %s\n",strerror(errno)); + logW("error while opening color file for import: %s",strerror(errno)); return false; } resetColors(); @@ -1689,7 +1698,7 @@ bool FurnaceGUI::importColors(String path) { break; } } - if (!found) logW("line invalid: %s\n",line); + if (!found) logW("line invalid: %s",line); } } fclose(f); @@ -1699,12 +1708,12 @@ bool FurnaceGUI::importColors(String path) { bool FurnaceGUI::exportColors(String path) { FILE* f=ps_fopen(path.c_str(),"wb"); if (f==NULL) { - logW("error while opening color file for export: %s\n",strerror(errno)); + logW("error while opening color file for export: %s",strerror(errno)); return false; } for (int i=0; i6) settings.patFont=0; if (settings.mainFont==6 && settings.mainFontPath.empty()) { - logW("UI font path is empty! reverting to default font\n"); + logW("UI font path is empty! reverting to default font"); settings.mainFont=0; } if (settings.patFont==6 && settings.patFontPath.empty()) { - logW("pattern font path is empty! reverting to default font\n"); + logW("pattern font path is empty! reverting to default font"); settings.patFont=0; } @@ -2113,10 +2122,10 @@ void FurnaceGUI::applyUISettings() { if (settings.mainFont==6) { // custom font if ((mainFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(settings.mainFontPath.c_str(),e->getConfInt("mainFontSize",18)*dpiScale,NULL,fontRange))==NULL) { - logW("could not load UI font! reverting to default font\n"); + logW("could not load UI font! reverting to default font"); settings.mainFont=0; if ((mainFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFont[settings.mainFont],builtinFontLen[settings.mainFont],e->getConfInt("mainFontSize",18)*dpiScale,NULL,fontRange))==NULL) { - logE("could not load UI font! falling back to Proggy Clean.\n"); + logE("could not load UI font! falling back to Proggy Clean."); mainFont=ImGui::GetIO().Fonts->AddFontDefault(); } } @@ -2124,10 +2133,10 @@ void FurnaceGUI::applyUISettings() { if ((mainFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_FONT_PATH_1,e->getConfInt("mainFontSize",18)*dpiScale,NULL,fontRange))==NULL) { if ((mainFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_FONT_PATH_2,e->getConfInt("mainFontSize",18)*dpiScale,NULL,fontRange))==NULL) { if ((mainFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_FONT_PATH_3,e->getConfInt("mainFontSize",18)*dpiScale,NULL,fontRange))==NULL) { - logW("could not load UI font! reverting to default font\n"); + logW("could not load UI font! reverting to default font"); settings.mainFont=0; if ((mainFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFont[settings.mainFont],builtinFontLen[settings.mainFont],e->getConfInt("mainFontSize",18)*dpiScale,NULL,fontRange))==NULL) { - logE("could not load UI font! falling back to Proggy Clean.\n"); + logE("could not load UI font! falling back to Proggy Clean."); mainFont=ImGui::GetIO().Fonts->AddFontDefault(); } } @@ -2135,7 +2144,7 @@ void FurnaceGUI::applyUISettings() { } } else { if ((mainFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFont[settings.mainFont],builtinFontLen[settings.mainFont],e->getConfInt("mainFontSize",18)*dpiScale,NULL,fontRange))==NULL) { - logE("could not load UI font! falling back to Proggy Clean.\n"); + logE("could not load UI font! falling back to Proggy Clean."); mainFont=ImGui::GetIO().Fonts->AddFontDefault(); } } @@ -2149,18 +2158,18 @@ void FurnaceGUI::applyUISettings() { fc.GlyphMinAdvanceX=e->getConfInt("iconSize",16)*dpiScale; static const ImWchar fontRangeIcon[]={ICON_MIN_FA,ICON_MAX_FA,0}; if ((iconFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(iconFont_compressed_data,iconFont_compressed_size,e->getConfInt("iconSize",16)*dpiScale,&fc,fontRangeIcon))==NULL) { - logE("could not load icon font!\n"); + logE("could not load icon font!"); } if (settings.mainFontSize==settings.patFontSize && settings.patFont<5 && builtinFontM[settings.patFont]==builtinFont[settings.mainFont]) { - logD("using main font for pat font.\n"); + logD("using main font for pat font."); patFont=mainFont; } else { if (settings.patFont==6) { // custom font if ((patFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(settings.patFontPath.c_str(),e->getConfInt("patFontSize",18)*dpiScale,NULL,upTo800))==NULL) { - logW("could not load pattern font! reverting to default font\n"); + logW("could not load pattern font! reverting to default font"); settings.patFont=0; if ((patFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFontM[settings.patFont],builtinFontMLen[settings.patFont],e->getConfInt("patFontSize",18)*dpiScale,NULL,upTo800))==NULL) { - logE("could not load pattern font! falling back to Proggy Clean.\n"); + logE("could not load pattern font! falling back to Proggy Clean."); patFont=ImGui::GetIO().Fonts->AddFontDefault(); } } @@ -2168,10 +2177,10 @@ void FurnaceGUI::applyUISettings() { if ((patFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_PAT_FONT_PATH_1,e->getConfInt("patFontSize",18)*dpiScale,NULL,upTo800))==NULL) { if ((patFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_PAT_FONT_PATH_2,e->getConfInt("patFontSize",18)*dpiScale,NULL,upTo800))==NULL) { if ((patFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_PAT_FONT_PATH_3,e->getConfInt("patFontSize",18)*dpiScale,NULL,upTo800))==NULL) { - logW("could not load pattern font! reverting to default font\n"); + logW("could not load pattern font! reverting to default font"); settings.patFont=0; if ((patFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFontM[settings.patFont],builtinFontMLen[settings.patFont],e->getConfInt("patFontSize",18)*dpiScale,NULL,upTo800))==NULL) { - logE("could not load pattern font! falling back to Proggy Clean.\n"); + logE("could not load pattern font! falling back to Proggy Clean."); patFont=ImGui::GetIO().Fonts->AddFontDefault(); } } @@ -2179,13 +2188,13 @@ void FurnaceGUI::applyUISettings() { } } else { if ((patFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFontM[settings.patFont],builtinFontMLen[settings.patFont],e->getConfInt("patFontSize",18)*dpiScale,NULL,upTo800))==NULL) { - logE("could not load pattern font!\n"); + logE("could not load pattern font!"); patFont=ImGui::GetIO().Fonts->AddFontDefault(); } } } if ((bigFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(font_plexSans_compressed_data,font_plexSans_compressed_size,40*dpiScale))==NULL) { - logE("could not load big UI font!\n"); + logE("could not load big UI font!"); } mainFont->FallbackChar='?'; diff --git a/src/log.cpp b/src/log.cpp index 1f77925b4..a3a6000cb 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -17,12 +17,16 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -// TODO: improve these routines to allow logging to memory for eventual log window! - #include "ta-log.h" int logLevel=LOGLEVEL_INFO; +std::atomic logPosition; + +LogEntry logEntries[TA_LOG_SIZE]; + +static constexpr unsigned int TA_LOG_MASK=TA_LOG_SIZE-1; + int logV(const char* format, ...) { va_list va; int ret; @@ -55,21 +59,6 @@ int logD(const char* format, ...) { return ret; } -int logI(const char* format, ...) { - va_list va; - int ret; - if (logLevelgetFinalBuf(),1,w->size(),f); fclose(f); } else { - logE("could not open file! %s\n",strerror(errno)); + logE("could not open file! %s",strerror(errno)); } w->finish(); delete w; } else { - logE("could not write VGM!\n"); + logE("could not write VGM!"); } } if (outName!="") { @@ -391,7 +392,7 @@ int main(int argc, char** argv) { } if (consoleMode) { - logI("playing...\n"); + logI("playing..."); e.play(); #ifdef HAVE_GUI SDL_Event ev; @@ -417,7 +418,7 @@ int main(int argc, char** argv) { if (!g.init()) return 1; if (displayEngineFailError) { - logE("displaying engine fail error.\n"); + logE("displaying engine fail error."); g.showError("error while initializing audio!"); } @@ -426,13 +427,13 @@ int main(int argc, char** argv) { } g.loop(); - logI("closing GUI.\n"); + logI("closing GUI."); g.finish(); #else - logE("GUI requested but GUI not compiled!\n"); + logE("GUI requested but GUI not compiled!"); #endif - logI("stopping engine.\n"); + logI("stopping engine."); e.quit(); return 0; } diff --git a/src/ta-log.h b/src/ta-log.h index e7867de58..666dfa487 100644 --- a/src/ta-log.h +++ b/src/ta-log.h @@ -19,8 +19,12 @@ #ifndef _TA_LOG_H #define _TA_LOG_H +#include #include #include +#include +#include +#include #define LOGLEVEL_ERROR 0 #define LOGLEVEL_WARN 1 @@ -28,11 +32,51 @@ #define LOGLEVEL_DEBUG 3 #define LOGLEVEL_TRACE 4 +// this has to be a power of 2 +#define TA_LOG_SIZE 2048 + extern int logLevel; -int logV(const char* format, ...); -int logD(const char* format, ...); -int logI(const char* format, ...); -int logW(const char* format, ...); -int logE(const char* format, ...); +extern std::atomic logPosition; + +struct LogEntry { + int loglevel; + std::chrono::system_clock::time_point time; + std::string text; + bool ready; + LogEntry(): + loglevel(0), + ready(false) {} +}; + +int writeLog(int level, const char* msg, fmt::printf_args& args); + +extern LogEntry logEntries[TA_LOG_SIZE]; + +template int logV(const char* msg, const T&... args) { + fmt::printf_args a=fmt::make_printf_args(args...); + return writeLog(LOGLEVEL_TRACE,msg,a); +} + +template int logD(const char* msg, const T&... args) { + fmt::printf_args a=fmt::make_printf_args(args...); + return writeLog(LOGLEVEL_DEBUG,msg,a); +} + +template int logI(const char* msg, const T&... args) { + fmt::printf_args a=fmt::make_printf_args(args...); + return writeLog(LOGLEVEL_INFO,msg,a); +} + +template int logW(const char* msg, const T&... args) { + fmt::printf_args a=fmt::make_printf_args(args...); + return writeLog(LOGLEVEL_WARN,msg,a); +} + +template int logE(const char* msg, const T&... args) { + fmt::printf_args a=fmt::make_printf_args(args...); + return writeLog(LOGLEVEL_ERROR,msg,a); +} + +void initLog(); #endif From 6b627ab885436e403a30ed512a8e85bf5af88059 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 10 Apr 2022 22:16:42 -0500 Subject: [PATCH 622/637] TOOK --- src/engine/engine.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 690ee9930..14577b40e 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -824,7 +824,7 @@ void DivEngine::playSub(bool preserveDrift, int goalRow) { skipping=false; cmdStream.clear(); std::chrono::high_resolution_clock::time_point timeEnd=std::chrono::high_resolution_clock::now(); - logV("playSub() tool %dµs",std::chrono::duration_cast(timeEnd-timeStart).count()); + logV("playSub() took %dµs",std::chrono::duration_cast(timeEnd-timeStart).count()); } /* From 552967246d5f582516a77fbd510d66b3e7553dc6 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 10 Apr 2022 23:01:55 -0500 Subject: [PATCH 623/637] **SUBMODULE UPDATE - PLEASE READ!** as of now I have added the Date library as a submodule in order to have log messages in the correct time zone please update your submodules by doing: ``` git submodule update --init --recursive ``` --- .gitmodules | 3 +++ CMakeLists.txt | 19 +++++++++++++++++++ extern/date | 1 + src/gui/gui.cpp | 3 ++- src/gui/gui.h | 3 +++ src/gui/log.cpp | 13 ++++++++++--- 6 files changed, 38 insertions(+), 4 deletions(-) create mode 160000 extern/date diff --git a/.gitmodules b/.gitmodules index d63fd70b5..e96848c30 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,3 +22,6 @@ [submodule "extern/Nuked-OPL3"] path = extern/Nuked-OPL3 url = https://github.com/nukeykt/Nuked-OPL3.git +[submodule "extern/date"] + path = extern/date + url = https://github.com/HowardHinnant/date diff --git a/CMakeLists.txt b/CMakeLists.txt index d3b98fc64..ae93abda6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,6 +42,7 @@ option(SYSTEM_LIBSNDFILE "Use a system-installed version of libsndfile instead o option(SYSTEM_RTMIDI "Use a system-installed version of RtMidi instead of the vendored one" OFF) option(SYSTEM_ZLIB "Use a system-installed version of zlib instead of the vendored one" OFF) option(SYSTEM_SDL2 "Use a system-installed version of SDL2 instead of the vendored one" ${SYSTEM_SDL2_DEFAULT}) +option(SYSTEM_DATE "Use a system-installed version of Date instead of the vendored one" OFF) option(WARNINGS_ARE_ERRORS "Whether warnings in furnace's C++ code should be treated as errors" OFF) set(DEPENDENCIES_INCLUDE_DIRS "") @@ -126,6 +127,24 @@ if (USE_RTMIDI) endif() endif() +if (SYSTEM_DATE) + find_package(PkgConfig REQUIRED) + pkg_check_modules(HHDATE REQUIRED date) + list(APPEND DEPENDENCIES_INCLUDE_DIRS ${HHDATE_INCLUDE_DIRS}) + list(APPEND DEPENDENCIES_COMPILE_OPTIONS ${HHDATE_CFLAGS_OTHER}) + list(APPEND DEPENDENCIES_LIBRARIES ${HHDATE_LIBRARIES}) + list(APPEND DEPENDENCIES_LIBRARY_DIRS ${HHDATE_LIBRARY_DIRS}) + list(APPEND DEPENDENCIES_LINK_OPTIONS ${HHDATE_LDFLAGS_OTHER}) + list(APPEND DEPENDENCIES_LEGACY_LDFLAGS ${HHDATE_LDFLAGS}) + message(STATUS "Using system-installed Date") +else() + set(BUILD_TZ_LIB ON CACHE BOOL "build/install of TZ library" FORCE) + add_subdirectory(extern/date EXCLUDE_FROM_ALL) + list(APPEND DEPENDENCIES_INCLUDE_DIRS extern/date/include) + list(APPEND DEPENDENCIES_LIBRARIES date date-tz) + message(STATUS "Using vendored Date") +endif() + if (SYSTEM_ZLIB) find_package(PkgConfig REQUIRED) pkg_check_modules(ZLIB REQUIRED zlib) diff --git a/extern/date b/extern/date new file mode 160000 index 000000000..9ea5654c1 --- /dev/null +++ b/extern/date @@ -0,0 +1 @@ +Subproject commit 9ea5654c1206e19245dc21d8a2c433e090c8c3f5 diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index ee69ae00a..4b3542b1e 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -3439,7 +3439,8 @@ FurnaceGUI::FurnaceGUI(): openSampleFilterOpt(false), oscTotal(0), oscZoom(0.5f), - oscZoomSlider(false) { + oscZoomSlider(false), + followLog(true) { // value keys valueKeys[SDLK_0]=0; valueKeys[SDLK_1]=1; diff --git a/src/gui/gui.h b/src/gui/gui.h index bcb7deee3..cadc84d48 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1003,6 +1003,9 @@ class FurnaceGUI { // visualizer float keyHit[DIV_MAX_CHANS]; int lastIns[DIV_MAX_CHANS]; + + // log window + bool followLog; void drawSSGEnv(unsigned char type, const ImVec2& size); void drawWaveform(unsigned char type, bool opz, const ImVec2& size); diff --git a/src/gui/log.cpp b/src/gui/log.cpp index 2b83ab903..b6536cdb8 100644 --- a/src/gui/log.cpp +++ b/src/gui/log.cpp @@ -1,7 +1,7 @@ #include "gui.h" #include "../ta-log.h" #include -#include +#include "date/tz.h" const char* logLevels[5]={ "ERROR", @@ -27,8 +27,11 @@ void FurnaceGUI::drawLog() { } if (!logOpen) return; if (ImGui::Begin("Log Viewer",&logOpen)) { + ImGui::Checkbox("Follow",&followLog); + ImGui::SameLine(); ImGui::Text("Level"); ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); ImGui::Combo("##LogLevel",&logLevel,logLevels,5); if (ImGui::BeginTable("LogView",3,ImGuiTableFlags_ScrollY|ImGuiTableFlags_BordersInnerV)) { ImGui::PushFont(patFont); @@ -53,17 +56,21 @@ void FurnaceGUI::drawLog() { const LogEntry& logEntry=logEntries[(pos+i)&(TA_LOG_SIZE-1)]; if (!logEntry.ready) continue; if (logLevel(logEntry.time).time_since_epoch().count(); + String t=date::format("%T",date::make_zoned(date::current_zone(),date::floor(logEntry.time))); ImGui::TableNextRow(); ImGui::TableNextColumn(); // this will fail on 32-bit :< - ImGui::Text("%02ld:%02ld:%02ld",(t/3600)%24,(t/60)%60,t%60); + ImGui::TextUnformatted(t.c_str()); ImGui::TableNextColumn(); ImGui::TextColored(uiColors[logColors[logEntry.loglevel]],"%s",logLevels[logEntry.loglevel]); ImGui::TableNextColumn(); ImGui::TextWrapped("%s",logEntry.text.c_str()); } ImGui::PopFont(); + + if (followLog) { + ImGui::SetScrollY(ImGui::GetScrollMaxY()); + } ImGui::EndTable(); } } From 1d2c08bbf27859d17c7ff867761636f44203a864 Mon Sep 17 00:00:00 2001 From: Alex <99325922+ActualNK358@users.noreply.github.com> Date: Sun, 10 Apr 2022 23:03:33 -0500 Subject: [PATCH 624/637] Demo Song update: Fixing typo and crediting arranger --- demos/silverlining.fur | Bin 24216 -> 24250 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/demos/silverlining.fur b/demos/silverlining.fur index 06567d7313d66cd2f4236e40215dad829cc3082f..9ad4a88159094c0c0b973b4ce4fa39404d5ae269 100644 GIT binary patch literal 24250 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 literal 24216 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 Date: Sun, 10 Apr 2022 23:15:06 -0500 Subject: [PATCH 625/637] CI: don't be verbose i spend too much time scrolling through the logs --- .github/workflows/build.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2fbae0f51..864660368 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -165,7 +165,6 @@ jobs: - name: Build (System Libraries) if: ${{ runner.os == 'Linux' && matrix.config.compiler != 'mingw' }} run: | - export VERBOSE=1 cmake \ --build ${PWD}/build \ --config ${{ env.BUILD_TYPE }} \ @@ -214,7 +213,6 @@ jobs: - name: Build run: | - export VERBOSE=1 cmake \ --build ${PWD}/build \ --config ${{ env.BUILD_TYPE }} \ From 92b8703574dfc4897aef8568f13a4c40be6a6f2d Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 10 Apr 2022 23:20:25 -0500 Subject: [PATCH 626/637] try fixing the build if this doesn't work then goodbye Date --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index ae93abda6..2989a36f9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -139,6 +139,7 @@ if (SYSTEM_DATE) message(STATUS "Using system-installed Date") else() set(BUILD_TZ_LIB ON CACHE BOOL "build/install of TZ library" FORCE) + set(USE_SYSTEM_TZ_DB ON CACHE BOOL "Fix the build already" FORCE) add_subdirectory(extern/date EXCLUDE_FROM_ALL) list(APPEND DEPENDENCIES_INCLUDE_DIRS extern/date/include) list(APPEND DEPENDENCIES_LIBRARIES date date-tz) From 9e0e7258026dba86292260fb6212a9f6d7d057e1 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 11 Apr 2022 00:12:24 -0500 Subject: [PATCH 627/637] Sorry but I think I'd like to go now. --- .gitmodules | 3 --- CMakeLists.txt | 20 -------------------- extern/date | 1 - src/gui/log.cpp | 8 ++------ src/log.cpp | 9 +++++++-- src/ta-log.h | 4 ++-- 6 files changed, 11 insertions(+), 34 deletions(-) delete mode 160000 extern/date diff --git a/.gitmodules b/.gitmodules index e96848c30..d63fd70b5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,6 +22,3 @@ [submodule "extern/Nuked-OPL3"] path = extern/Nuked-OPL3 url = https://github.com/nukeykt/Nuked-OPL3.git -[submodule "extern/date"] - path = extern/date - url = https://github.com/HowardHinnant/date diff --git a/CMakeLists.txt b/CMakeLists.txt index 2989a36f9..d3b98fc64 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -42,7 +42,6 @@ option(SYSTEM_LIBSNDFILE "Use a system-installed version of libsndfile instead o option(SYSTEM_RTMIDI "Use a system-installed version of RtMidi instead of the vendored one" OFF) option(SYSTEM_ZLIB "Use a system-installed version of zlib instead of the vendored one" OFF) option(SYSTEM_SDL2 "Use a system-installed version of SDL2 instead of the vendored one" ${SYSTEM_SDL2_DEFAULT}) -option(SYSTEM_DATE "Use a system-installed version of Date instead of the vendored one" OFF) option(WARNINGS_ARE_ERRORS "Whether warnings in furnace's C++ code should be treated as errors" OFF) set(DEPENDENCIES_INCLUDE_DIRS "") @@ -127,25 +126,6 @@ if (USE_RTMIDI) endif() endif() -if (SYSTEM_DATE) - find_package(PkgConfig REQUIRED) - pkg_check_modules(HHDATE REQUIRED date) - list(APPEND DEPENDENCIES_INCLUDE_DIRS ${HHDATE_INCLUDE_DIRS}) - list(APPEND DEPENDENCIES_COMPILE_OPTIONS ${HHDATE_CFLAGS_OTHER}) - list(APPEND DEPENDENCIES_LIBRARIES ${HHDATE_LIBRARIES}) - list(APPEND DEPENDENCIES_LIBRARY_DIRS ${HHDATE_LIBRARY_DIRS}) - list(APPEND DEPENDENCIES_LINK_OPTIONS ${HHDATE_LDFLAGS_OTHER}) - list(APPEND DEPENDENCIES_LEGACY_LDFLAGS ${HHDATE_LDFLAGS}) - message(STATUS "Using system-installed Date") -else() - set(BUILD_TZ_LIB ON CACHE BOOL "build/install of TZ library" FORCE) - set(USE_SYSTEM_TZ_DB ON CACHE BOOL "Fix the build already" FORCE) - add_subdirectory(extern/date EXCLUDE_FROM_ALL) - list(APPEND DEPENDENCIES_INCLUDE_DIRS extern/date/include) - list(APPEND DEPENDENCIES_LIBRARIES date date-tz) - message(STATUS "Using vendored Date") -endif() - if (SYSTEM_ZLIB) find_package(PkgConfig REQUIRED) pkg_check_modules(ZLIB REQUIRED zlib) diff --git a/extern/date b/extern/date deleted file mode 160000 index 9ea5654c1..000000000 --- a/extern/date +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 9ea5654c1206e19245dc21d8a2c433e090c8c3f5 diff --git a/src/gui/log.cpp b/src/gui/log.cpp index b6536cdb8..2246df4ea 100644 --- a/src/gui/log.cpp +++ b/src/gui/log.cpp @@ -1,7 +1,5 @@ #include "gui.h" #include "../ta-log.h" -#include -#include "date/tz.h" const char* logLevels[5]={ "ERROR", @@ -56,11 +54,9 @@ void FurnaceGUI::drawLog() { const LogEntry& logEntry=logEntries[(pos+i)&(TA_LOG_SIZE-1)]; if (!logEntry.ready) continue; if (logLevel(logEntry.time))); ImGui::TableNextRow(); ImGui::TableNextColumn(); - // this will fail on 32-bit :< - ImGui::TextUnformatted(t.c_str()); + ImGui::Text("%02d:%02d:%02d",logEntry.time.tm_hour,logEntry.time.tm_min,logEntry.time.tm_sec); ImGui::TableNextColumn(); ImGui::TextColored(uiColors[logColors[logEntry.loglevel]],"%s",logLevels[logEntry.loglevel]); ImGui::TableNextColumn(); @@ -75,4 +71,4 @@ void FurnaceGUI::drawLog() { } } ImGui::End(); -} \ No newline at end of file +} diff --git a/src/log.cpp b/src/log.cpp index a3a6000cb..d0ea0603f 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -90,11 +90,16 @@ int logE(const char* format, ...) { } int writeLog(int level, const char* msg, fmt::printf_args& args) { + time_t thisMakesNoSense=time(NULL); int pos=logPosition; logPosition=(logPosition+1)&TA_LOG_MASK; logEntries[pos].text=fmt::vsprintf(msg,args); - logEntries[pos].time=std::chrono::system_clock::now(); + // why do I have to pass a pointer + // can't I just pass the time_t directly?! + if (localtime_r(&thisMakesNoSense,&logEntries[pos].time)==NULL) { + memset(&logEntries[pos].time,0,sizeof(struct tm)); + } logEntries[pos].loglevel=level; logEntries[pos].ready=true; @@ -119,4 +124,4 @@ void initLog() { for (int i=0; i #include #include +#include #include #include #include @@ -41,7 +41,7 @@ extern std::atomic logPosition; struct LogEntry { int loglevel; - std::chrono::system_clock::time_point time; + struct tm time; std::string text; bool ready; LogEntry(): From 8a49522e5956b271a76645f2588aa30f893a4154 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 11 Apr 2022 01:41:45 -0500 Subject: [PATCH 628/637] beta 1-3 .dmf loading i did it --- src/engine/fileOps.cpp | 309 ++++++++++++++++++++++++++--------------- 1 file changed, 195 insertions(+), 114 deletions(-) diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 8514d91ab..356e40e7e 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -53,6 +53,10 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { warnings=""; try { DivSong ds; + unsigned char historicColIns[DIV_MAX_CHANS]; + for (int i=0; i0x03) { + if (ds.version>0x05) { ds.speed2=reader.readC(); ds.pal=reader.readC(); ds.hz=(ds.pal)?60:50; @@ -258,7 +262,9 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { addWarning("Yamaha YMU759 emulation is incomplete! please migrate your song to the OPL3 system."); } - logI("reading pattern matrix (%d)...",ds.ordersLen); + logV("%x",reader.tell()); + + logI("reading pattern matrix (%d * %d = %d)...",ds.ordersLen,getChannelCount(ds.system[0]),ds.ordersLen*getChannelCount(ds.system[0])); for (int i=0; iname=reader.readString((unsigned char)reader.readC()); } } + if (ds.version>0x03 && ds.version<0x06 && i<16) { + historicColIns[i]=reader.readC(); + } } - if (ds.version>0x03) { + logV("%x",reader.tell()); + + if (ds.version>0x05) { ds.insLen=(unsigned char)reader.readC(); } else { ds.insLen=16; @@ -282,7 +293,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { logI("reading instruments (%d)...",ds.insLen); for (int i=0; i0x03) { + if (ds.version>0x05) { ins->name=reader.readString((unsigned char)reader.readC()); } logD("%d name: %s",i,ins->name.c_str()); @@ -319,29 +330,41 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { } if (ins->mode) { // FM - ins->fm.alg=reader.readC(); - if (ds.version<0x13) { - reader.readC(); - } - ins->fm.fb=reader.readC(); - if (ds.version<0x13) { - reader.readC(); - } - ins->fm.fms=reader.readC(); - if (ds.version<0x13) { - reader.readC(); - ins->fm.ops=2+reader.readC()*2; - if (ds.system[0]!=DIV_SYSTEM_YMU759) ins->fm.ops=4; + if (ds.version>0x05) { + ins->fm.alg=reader.readC(); + if (ds.version<0x13) { + reader.readC(); + } + ins->fm.fb=reader.readC(); + if (ds.version<0x13) { + reader.readC(); + } + ins->fm.fms=reader.readC(); + if (ds.version<0x13) { + reader.readC(); + ins->fm.ops=2+reader.readC()*2; + if (ds.system[0]!=DIV_SYSTEM_YMU759) ins->fm.ops=4; + } else { + ins->fm.ops=4; + } + ins->fm.ams=reader.readC(); } else { - ins->fm.ops=4; + ins->fm.alg=reader.readC(); + reader.readC(); + ins->fm.fb=reader.readC(); + reader.readC(); // apparently an index of sorts starting from 0x59? + ins->fm.fms=reader.readC(); + reader.readC(); // 0x59+index? + ins->fm.ops=2+reader.readC()*2; } + + logD("ALG %d FB %d FMS %d AMS %d OPS %d",ins->fm.alg,ins->fm.fb,ins->fm.fms,ins->fm.ams,ins->fm.ops); if (ins->fm.ops!=2 && ins->fm.ops!=4) { logE("invalid op count %d. did we read it wrong?",ins->fm.ops); lastError="file is corrupt or unreadable at operators"; delete[] file; return false; } - ins->fm.ams=reader.readC(); for (int j=0; jfm.ops; j++) { ins->fm.op[j].am=reader.readC(); @@ -385,7 +408,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { ins->fm.op[j].dt2=reader.readC(); } } - if (ds.version>0x03) { + if (ds.version>0x05) { if (ds.system[0]==DIV_SYSTEM_SMS_OPLL || ds.system[0]==DIV_SYSTEM_NES_VRC7) { ins->fm.op[j].ksr=reader.readC(); ins->fm.op[j].vib=reader.readC(); @@ -588,6 +611,8 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { } } + logV("%x",reader.tell()); + logI("reading patterns (%d channels, %d orders)...",getChannelCount(ds.system[0]),ds.ordersLen); for (int i=0; i4 || chan.effectRows<1) { @@ -606,79 +630,110 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { } for (int j=0; jdata[k][0]=reader.readS(); - // octave - pat->data[k][1]=reader.readS(); - if (ds.system[0]==DIV_SYSTEM_SMS && ds.version<0x0e && pat->data[k][1]>0) { - // apparently it was up one octave before - pat->data[k][1]--; - } else if (ds.system[0]==DIV_SYSTEM_GENESIS && ds.version<0x0e && pat->data[k][1]>0 && i>5) { - // ditto - pat->data[k][1]--; - } - if (ds.version<0x12) { - if (ds.system[0]==DIV_SYSTEM_GB && i==3 && pat->data[k][1]>0) { - // back then noise was 2 octaves lower - pat->data[k][1]-=2; + if (ds.version>0x05) { // current pattern format + for (int k=0; kdata[k][0]=reader.readS(); + // octave + pat->data[k][1]=reader.readS(); + if (ds.system[0]==DIV_SYSTEM_SMS && ds.version<0x0e && pat->data[k][1]>0) { + // apparently it was up one octave before + pat->data[k][1]--; + } else if (ds.system[0]==DIV_SYSTEM_GENESIS && ds.version<0x0e && pat->data[k][1]>0 && i>5) { + // ditto + pat->data[k][1]--; + } + if (ds.version<0x12) { + if (ds.system[0]==DIV_SYSTEM_GB && i==3 && pat->data[k][1]>0) { + // back then noise was 2 octaves lower + pat->data[k][1]-=2; + } + } + if (ds.system[0]==DIV_SYSTEM_YMU759 && pat->data[k][0]!=0) { + // apparently YMU759 is stored 2 octaves lower + pat->data[k][1]+=2; + } + if (pat->data[k][0]==0 && pat->data[k][1]!=0) { + logD("what? %d:%d:%d note %d octave %d",i,j,k,pat->data[k][0],pat->data[k][1]); + pat->data[k][0]=12; + pat->data[k][1]--; + } + // volume + pat->data[k][3]=reader.readS(); + if (ds.version<0x0a) { + // back then volume was stored as 00-ff instead of 00-7f/0-f + if (i>5) { + pat->data[k][3]>>=4; + } else { + pat->data[k][3]>>=1; + } + } + if (ds.version<0x12) { + if (ds.system[0]==DIV_SYSTEM_GB && i==2 && pat->data[k][3]>0) { + // volume range of GB wave channel was 0-3 rather than 0-F + pat->data[k][3]=(pat->data[k][3]&3)*5; + } + } + for (int l=0; ldata[k][4+(l<<1)]=reader.readS(); + pat->data[k][5+(l<<1)]=reader.readS(); + + if (ds.version<0x14) { + if (pat->data[k][4+(l<<1)]==0xe5 && pat->data[k][5+(l<<1)]!=-1) { + pat->data[k][5+(l<<1)]=128+((pat->data[k][5+(l<<1)]-128)/4); + } + } + } + // instrument + pat->data[k][2]=reader.readS(); + + // this is sad + if (ds.system[0]==DIV_SYSTEM_NES_FDS) { + if (i==5 && pat->data[k][2]!=-1) { + if (pat->data[k][2]>=0 && pat->data[k][2]data[k][2]]->type=DIV_INS_FDS; + } + } } } - if (ds.system[0]==DIV_SYSTEM_YMU759 && pat->data[k][0]!=0) { - // apparently YMU759 is stored 2 octaves lower - pat->data[k][1]+=2; - } - if (pat->data[k][0]==0 && pat->data[k][1]!=0) { - logD("what? %d:%d:%d note %d octave %d",i,j,k,pat->data[k][0],pat->data[k][1]); - pat->data[k][0]=12; - pat->data[k][1]--; - } - // volume - pat->data[k][3]=reader.readS(); - if (ds.version<0x0a) { - // back then volume was stored as 00-ff instead of 00-7f/0-f - if (i>5) { - pat->data[k][3]>>=4; - } else { - pat->data[k][3]>>=1; + } else { // historic pattern format + if (i<16) pat->data[0][2]=historicColIns[i]; + for (int k=0; kdata[k][0]=reader.readC(); + // octave + pat->data[k][1]=reader.readC(); + if (pat->data[k][0]!=0) { + // YMU759 is stored 2 octaves lower + pat->data[k][1]+=2; } - } - if (ds.version<0x12) { - if (ds.system[0]==DIV_SYSTEM_GB && i==2 && pat->data[k][3]>0) { - // volume range of GB wave channel was 0-3 rather than 0-F - pat->data[k][3]=(pat->data[k][3]&3)*5; + if (pat->data[k][0]==0 && pat->data[k][1]!=0) { + logD("what? %d:%d:%d note %d octave %d",i,j,k,pat->data[k][0],pat->data[k][1]); + pat->data[k][0]=12; + pat->data[k][1]--; } - } - for (int l=0; ldata[k][3]=(vol==0x80)?-1:vol; // effect - pat->data[k][4+(l<<1)]=reader.readS(); - pat->data[k][5+(l<<1)]=reader.readS(); - - if (ds.version<0x14) { - if (pat->data[k][4+(l<<1)]==0xe5 && pat->data[k][5+(l<<1)]!=-1) { - pat->data[k][5+(l<<1)]=128+((pat->data[k][5+(l<<1)]-128)/4); - } - } - } - // instrument - pat->data[k][2]=reader.readS(); - - // this is sad - if (ds.system[0]==DIV_SYSTEM_NES_FDS) { - if (i==5 && pat->data[k][2]!=-1) { - if (pat->data[k][2]>=0 && pat->data[k][2]data[k][2]]->type=DIV_INS_FDS; - } - } + pat->data[k][4]=(fx==0x80)?-1:fx; + pat->data[k][5]=(fxVal==0x80)?-1:fxVal; + // instrument wasn't stored back then } } } } + int ymuSampleRate=20; + ds.sampleLen=(unsigned char)reader.readC(); logI("reading samples (%d)...",ds.sampleLen); - if (ds.version<0x0b && ds.sampleLen>0) { // TODO what is this for? - reader.readC(); + if (ds.version<0x0b && ds.sampleLen>0) { + // it appears this byte stored the YMU759 sample rate + ymuSampleRate=reader.readC(); } for (int i=0; irate=ymuSampleRate*400; + } if (ds.version>0x15) { sample->depth=reader.readC(); if (sample->depth!=8 && sample->depth!=16) { @@ -711,43 +770,65 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { sample->depth=16; } } else { - sample->depth=16; + if (ds.version>0x05) { + sample->depth=16; + } else { + // it appears samples were stored as ADPCM back then + sample->depth=6; + } } if (length>0) { - if (ds.version<0x0b) { - data=new short[1+(length/2)]; - reader.read(data,length); - length/=2; - } else { - data=new short[length]; - reader.read(data,length*2); - } - - if (pitch!=5) { - logD("%d: scaling from %d...",i,pitch); - } - - // render data - if (!sample->init((double)length/samplePitches[pitch])) { - logE("%d: error while initializing sample!",i); - } - - unsigned int k=0; - float mult=(float)(vol)/50.0f; - for (double j=0; j=sample->samples) { - break; - } - if (sample->depth==8) { - float next=(float)(data[(unsigned int)j]-0x80)*mult; - sample->data8[k++]=fmin(fmax(next,-128),127); + if (ds.version>0x05) { + if (ds.version<0x0b) { + data=new short[1+(length/2)]; + reader.read(data,length); + length/=2; } else { - float next=(float)data[(unsigned int)j]*mult; - sample->data16[k++]=fmin(fmax(next,-32768),32767); + data=new short[length]; + reader.read(data,length*2); } - } - delete[] data; + if (pitch!=5) { + logD("%d: scaling from %d...",i,pitch); + } + + // render data + if (!sample->init((double)length/samplePitches[pitch])) { + logE("%d: error while initializing sample!",i); + } + + unsigned int k=0; + float mult=(float)(vol)/50.0f; + for (double j=0; j=sample->samples) { + break; + } + if (sample->depth==8) { + float next=(float)(data[(unsigned int)j]-0x80)*mult; + sample->data8[k++]=fmin(fmax(next,-128),127); + } else { + float next=(float)data[(unsigned int)j]*mult; + sample->data16[k++]=fmin(fmax(next,-32768),32767); + } + } + + delete[] data; + } else { + // ADPCM? + // it appears to be a slightly modified version of ADPCM-B! + adpcmData=new unsigned char[length]; + logV("%x",reader.tell()); + reader.read(adpcmData,length); + for (int i=0; i>4); + } + if (!sample->init(length*2)) { + logE("%d: error while initializing sample!",i); + } + + memcpy(sample->dataB,adpcmData,length); + delete[] adpcmData; + } } ds.sample.push_back(sample); } From 39784bc7c2e35310e286c9649cd1002121d92378 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 11 Apr 2022 01:58:41 -0500 Subject: [PATCH 629/637] YMU759 samples at 48KHz --- src/engine/platform/opl.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/engine/platform/opl.cpp b/src/engine/platform/opl.cpp index 040c8035f..3e222eec1 100644 --- a/src/engine/platform/opl.cpp +++ b/src/engine/platform/opl.cpp @@ -1067,6 +1067,11 @@ void DivPlatformOPL::setFlags(unsigned int flags) { chipClock=COLOR_NTSC; rate=chipClock/72; } + + if (pretendYMU) { + rate=48000; + chipClock=rate*288; + } } int DivPlatformOPL::init(DivEngine* p, int channels, int sugRate, unsigned int flags) { From d485411727dd744f0791f480feebde94db502411 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 11 Apr 2022 02:01:23 -0500 Subject: [PATCH 630/637] hopefully fix Windows build --- src/log.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/log.cpp b/src/log.cpp index d0ea0603f..7f7fae8a9 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -97,9 +97,18 @@ int writeLog(int level, const char* msg, fmt::printf_args& args) { logEntries[pos].text=fmt::vsprintf(msg,args); // why do I have to pass a pointer // can't I just pass the time_t directly?! +#ifdef _WIN32 + struct tm* tempTM=localtime(&thisMakesNoSense); + if (tempTM==NULL) { + memset(&logEntries[pos].time,0,sizeof(struct tm)); + } else { + memcpy(&logEntries[pos].time,tempTM,sizeof(struct tm)); + } +#else if (localtime_r(&thisMakesNoSense,&logEntries[pos].time)==NULL) { memset(&logEntries[pos].time,0,sizeof(struct tm)); } +#endif logEntries[pos].loglevel=level; logEntries[pos].ready=true; From 45f1c827792225c2e8dad69a1b269f1f16645482 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 11 Apr 2022 02:05:58 -0500 Subject: [PATCH 631/637] sorry --- .github/workflows/build.yml | 2 +- CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 864660368..293c4cd0f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -143,7 +143,7 @@ jobs: else # Test with system libs CMAKE_EXTRA_ARGS+=( - '-DSYSTEM_FMT=ON' + '-DSYSTEM_FMT=OFF' '-DSYSTEM_LIBSNDFILE=ON' '-DSYSTEM_RTMIDI=ON' '-DSYSTEM_ZLIB=ON' diff --git a/CMakeLists.txt b/CMakeLists.txt index d3b98fc64..498f3df69 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,7 +65,7 @@ list(APPEND DEPENDENCIES_LIBRARIES ${CMAKE_THREAD_LIBS_INIT}) if (SYSTEM_FMT) if (PKG_CONFIG_FOUND) - pkg_check_modules(FMT fmt) + pkg_check_modules(FMT fmt>=7.1.0) if (FMT_FOUND) list(APPEND DEPENDENCIES_INCLUDE_DIRS ${FMT_INCLUDE_DIRS}) list(APPEND DEPENDENCIES_COMPILE_OPTIONS ${FMT_CFLAGS_OTHER}) From 283d74c32fc002960994d92ca373d27c6fb8075a Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 11 Apr 2022 02:39:06 -0500 Subject: [PATCH 632/637] GUI: friendlier name for N163 TDM disable --- src/gui/sysConf.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gui/sysConf.cpp b/src/gui/sysConf.cpp index 5b2aa6582..40cc67a35 100644 --- a/src/gui/sysConf.cpp +++ b/src/gui/sysConf.cpp @@ -373,7 +373,7 @@ void FurnaceGUI::drawSysConf(int i) { updateWindowTitle(); } rightClickable bool n163Multiplex=flags&128; - if (ImGui::Checkbox("Disable Multiplexed Output",&n163Multiplex)) { + if (ImGui::Checkbox("Disable hissing",&n163Multiplex)) { e->setSysFlags(i,(flags&(~128))|(n163Multiplex<<7),restart); updateWindowTitle(); } From 7905b813e0eea1486f942d528b56ce4a00a645fd Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 11 Apr 2022 02:45:02 -0500 Subject: [PATCH 633/637] GUI: TableSetupScrollFreeze() for log view --- src/gui/log.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/gui/log.cpp b/src/gui/log.cpp index 2246df4ea..c6c4e2a86 100644 --- a/src/gui/log.cpp +++ b/src/gui/log.cpp @@ -1,5 +1,6 @@ #include "gui.h" #include "../ta-log.h" +#include const char* logLevels[5]={ "ERROR", @@ -41,6 +42,8 @@ void FurnaceGUI::drawLog() { ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed,levelChars); ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch); + ImGui::TableSetupScrollFreeze(0,1); + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); ImGui::TableNextColumn(); ImGui::TextUnformatted("time"); From ac286fc8d102eaf49f0b3b97d123b3404d6628ea Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 11 Apr 2022 02:53:21 -0500 Subject: [PATCH 634/637] GUI: real-time color setting preview --- src/gui/gui.cpp | 1 + src/gui/gui.h | 2 +- src/gui/settings.cpp | 219 ++++++++++++++++++++++--------------------- 3 files changed, 116 insertions(+), 106 deletions(-) diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 4b3542b1e..948489775 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2928,6 +2928,7 @@ bool FurnaceGUI::loop() { break; case GUI_WARN_RESET_COLORS: resetColors(); + applyUISettings(false); break; case GUI_WARN_GENERIC: break; diff --git a/src/gui/gui.h b/src/gui/gui.h index cadc84d48..83082835f 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1126,7 +1126,7 @@ class FurnaceGUI { int load(String path); void exportAudio(String path, DivAudioExportModes mode); - void applyUISettings(); + void applyUISettings(bool updateFonts=true); void initSystemPresets(); void encodeMMLStr(String& target, int* macro, int macroLen, int macroLoop, int macroRel, bool hex=false); diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 2727c3a36..215958fa7 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -148,7 +148,9 @@ const char* specificControls[18]={ } #define UI_COLOR_CONFIG(what,label) \ - ImGui::ColorEdit4(label "##CC_" #what,(float*)&uiColors[what]); + if (ImGui::ColorEdit4(label "##CC_" #what,(float*)&uiColors[what])) { \ + applyUISettings(false); \ + } #define KEYBIND_CONFIG_BEGIN(id) \ if (ImGui::BeginTable(id,2)) { @@ -1702,6 +1704,7 @@ bool FurnaceGUI::importColors(String path) { } } fclose(f); + applyUISettings(false); return true; } @@ -1945,7 +1948,7 @@ void FurnaceGUI::parseKeybinds() { #define SYSTEM_PAT_FONT_PATH_3 "/usr/share/fonts/ubuntu/UbuntuMono-R.ttf" #endif -void FurnaceGUI::applyUISettings() { +void FurnaceGUI::applyUISettings(bool updateFonts) { ImGuiStyle sty; if (settings.guiColorsBase) { ImGui::StyleColorsLight(&sty); @@ -1956,8 +1959,10 @@ void FurnaceGUI::applyUISettings() { if (settings.dpiScale>=0.5f) dpiScale=settings.dpiScale; // colors - for (int i=0; igetConfInt(guiColors[i].name,guiColors[i].defaultColor)); + if (updateFonts) { + for (int i=0; igetConfInt(guiColors[i].name,guiColors[i].defaultColor)); + } } for (int i=0; i<64; i++) { @@ -2085,120 +2090,122 @@ void FurnaceGUI::applyUISettings() { sysCmd2Grad[i]=ImGui::GetColorU32(ImVec4(base.x,base.y,base.z,((float)i/255.0f)*base.w)); } - // set to 800 for now due to problems with unifont - static const ImWchar upTo800[]={0x20,0x7e,0xa0,0x800,0}; - ImFontGlyphRangesBuilder range; - ImVector outRange; + if (updateFonts) { + // set to 800 for now due to problems with unifont + static const ImWchar upTo800[]={0x20,0x7e,0xa0,0x800,0}; + ImFontGlyphRangesBuilder range; + ImVector outRange; - range.AddRanges(upTo800); - if (settings.loadJapanese) { - range.AddRanges(ImGui::GetIO().Fonts->GetGlyphRangesJapanese()); - } - // I'm terribly sorry - range.UsedChars[0x80>>5]=0; + range.AddRanges(upTo800); + if (settings.loadJapanese) { + range.AddRanges(ImGui::GetIO().Fonts->GetGlyphRangesJapanese()); + } + // I'm terribly sorry + range.UsedChars[0x80>>5]=0; - range.BuildRanges(&outRange); - if (fontRange!=NULL) delete[] fontRange; - fontRange=new ImWchar[outRange.size()]; - int index=0; - for (ImWchar& i: outRange) { - fontRange[index++]=i; - } + range.BuildRanges(&outRange); + if (fontRange!=NULL) delete[] fontRange; + fontRange=new ImWchar[outRange.size()]; + int index=0; + for (ImWchar& i: outRange) { + fontRange[index++]=i; + } - if (settings.mainFont<0 || settings.mainFont>6) settings.mainFont=0; - if (settings.patFont<0 || settings.patFont>6) settings.patFont=0; + if (settings.mainFont<0 || settings.mainFont>6) settings.mainFont=0; + if (settings.patFont<0 || settings.patFont>6) settings.patFont=0; - if (settings.mainFont==6 && settings.mainFontPath.empty()) { - logW("UI font path is empty! reverting to default font"); - settings.mainFont=0; - } - if (settings.patFont==6 && settings.patFontPath.empty()) { - logW("pattern font path is empty! reverting to default font"); - settings.patFont=0; - } - - ImFontConfig fc1; - fc1.MergeMode=true; - - if (settings.mainFont==6) { // custom font - if ((mainFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(settings.mainFontPath.c_str(),e->getConfInt("mainFontSize",18)*dpiScale,NULL,fontRange))==NULL) { - logW("could not load UI font! reverting to default font"); + if (settings.mainFont==6 && settings.mainFontPath.empty()) { + logW("UI font path is empty! reverting to default font"); settings.mainFont=0; - if ((mainFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFont[settings.mainFont],builtinFontLen[settings.mainFont],e->getConfInt("mainFontSize",18)*dpiScale,NULL,fontRange))==NULL) { - logE("could not load UI font! falling back to Proggy Clean."); - mainFont=ImGui::GetIO().Fonts->AddFontDefault(); - } } - } else if (settings.mainFont==5) { // system font - if ((mainFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_FONT_PATH_1,e->getConfInt("mainFontSize",18)*dpiScale,NULL,fontRange))==NULL) { - if ((mainFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_FONT_PATH_2,e->getConfInt("mainFontSize",18)*dpiScale,NULL,fontRange))==NULL) { - if ((mainFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_FONT_PATH_3,e->getConfInt("mainFontSize",18)*dpiScale,NULL,fontRange))==NULL) { - logW("could not load UI font! reverting to default font"); - settings.mainFont=0; - if ((mainFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFont[settings.mainFont],builtinFontLen[settings.mainFont],e->getConfInt("mainFontSize",18)*dpiScale,NULL,fontRange))==NULL) { - logE("could not load UI font! falling back to Proggy Clean."); - mainFont=ImGui::GetIO().Fonts->AddFontDefault(); - } + if (settings.patFont==6 && settings.patFontPath.empty()) { + logW("pattern font path is empty! reverting to default font"); + settings.patFont=0; + } + + ImFontConfig fc1; + fc1.MergeMode=true; + + if (settings.mainFont==6) { // custom font + if ((mainFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(settings.mainFontPath.c_str(),e->getConfInt("mainFontSize",18)*dpiScale,NULL,fontRange))==NULL) { + logW("could not load UI font! reverting to default font"); + settings.mainFont=0; + if ((mainFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFont[settings.mainFont],builtinFontLen[settings.mainFont],e->getConfInt("mainFontSize",18)*dpiScale,NULL,fontRange))==NULL) { + logE("could not load UI font! falling back to Proggy Clean."); + mainFont=ImGui::GetIO().Fonts->AddFontDefault(); } } - } - } else { - if ((mainFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFont[settings.mainFont],builtinFontLen[settings.mainFont],e->getConfInt("mainFontSize",18)*dpiScale,NULL,fontRange))==NULL) { - logE("could not load UI font! falling back to Proggy Clean."); - mainFont=ImGui::GetIO().Fonts->AddFontDefault(); - } - } - - // two fallback fonts - mainFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(font_liberationSans_compressed_data,font_liberationSans_compressed_size,e->getConfInt("mainFontSize",18)*dpiScale,&fc1,fontRange); - mainFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(font_unifont_compressed_data,font_unifont_compressed_size,e->getConfInt("mainFontSize",18)*dpiScale,&fc1,fontRange); - - ImFontConfig fc; - fc.MergeMode=true; - fc.GlyphMinAdvanceX=e->getConfInt("iconSize",16)*dpiScale; - static const ImWchar fontRangeIcon[]={ICON_MIN_FA,ICON_MAX_FA,0}; - if ((iconFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(iconFont_compressed_data,iconFont_compressed_size,e->getConfInt("iconSize",16)*dpiScale,&fc,fontRangeIcon))==NULL) { - logE("could not load icon font!"); - } - if (settings.mainFontSize==settings.patFontSize && settings.patFont<5 && builtinFontM[settings.patFont]==builtinFont[settings.mainFont]) { - logD("using main font for pat font."); - patFont=mainFont; - } else { - if (settings.patFont==6) { // custom font - if ((patFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(settings.patFontPath.c_str(),e->getConfInt("patFontSize",18)*dpiScale,NULL,upTo800))==NULL) { - logW("could not load pattern font! reverting to default font"); - settings.patFont=0; - if ((patFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFontM[settings.patFont],builtinFontMLen[settings.patFont],e->getConfInt("patFontSize",18)*dpiScale,NULL,upTo800))==NULL) { - logE("could not load pattern font! falling back to Proggy Clean."); - patFont=ImGui::GetIO().Fonts->AddFontDefault(); - } - } - } else if (settings.patFont==5) { // system font - if ((patFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_PAT_FONT_PATH_1,e->getConfInt("patFontSize",18)*dpiScale,NULL,upTo800))==NULL) { - if ((patFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_PAT_FONT_PATH_2,e->getConfInt("patFontSize",18)*dpiScale,NULL,upTo800))==NULL) { - if ((patFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_PAT_FONT_PATH_3,e->getConfInt("patFontSize",18)*dpiScale,NULL,upTo800))==NULL) { - logW("could not load pattern font! reverting to default font"); - settings.patFont=0; - if ((patFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFontM[settings.patFont],builtinFontMLen[settings.patFont],e->getConfInt("patFontSize",18)*dpiScale,NULL,upTo800))==NULL) { - logE("could not load pattern font! falling back to Proggy Clean."); - patFont=ImGui::GetIO().Fonts->AddFontDefault(); + } else if (settings.mainFont==5) { // system font + if ((mainFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_FONT_PATH_1,e->getConfInt("mainFontSize",18)*dpiScale,NULL,fontRange))==NULL) { + if ((mainFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_FONT_PATH_2,e->getConfInt("mainFontSize",18)*dpiScale,NULL,fontRange))==NULL) { + if ((mainFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_FONT_PATH_3,e->getConfInt("mainFontSize",18)*dpiScale,NULL,fontRange))==NULL) { + logW("could not load UI font! reverting to default font"); + settings.mainFont=0; + if ((mainFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFont[settings.mainFont],builtinFontLen[settings.mainFont],e->getConfInt("mainFontSize",18)*dpiScale,NULL,fontRange))==NULL) { + logE("could not load UI font! falling back to Proggy Clean."); + mainFont=ImGui::GetIO().Fonts->AddFontDefault(); } } } } } else { - if ((patFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFontM[settings.patFont],builtinFontMLen[settings.patFont],e->getConfInt("patFontSize",18)*dpiScale,NULL,upTo800))==NULL) { - logE("could not load pattern font!"); - patFont=ImGui::GetIO().Fonts->AddFontDefault(); + if ((mainFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFont[settings.mainFont],builtinFontLen[settings.mainFont],e->getConfInt("mainFontSize",18)*dpiScale,NULL,fontRange))==NULL) { + logE("could not load UI font! falling back to Proggy Clean."); + mainFont=ImGui::GetIO().Fonts->AddFontDefault(); } - } - } - if ((bigFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(font_plexSans_compressed_data,font_plexSans_compressed_size,40*dpiScale))==NULL) { - logE("could not load big UI font!"); - } + } - mainFont->FallbackChar='?'; - mainFont->DotChar='.'; + // two fallback fonts + mainFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(font_liberationSans_compressed_data,font_liberationSans_compressed_size,e->getConfInt("mainFontSize",18)*dpiScale,&fc1,fontRange); + mainFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(font_unifont_compressed_data,font_unifont_compressed_size,e->getConfInt("mainFontSize",18)*dpiScale,&fc1,fontRange); + + ImFontConfig fc; + fc.MergeMode=true; + fc.GlyphMinAdvanceX=e->getConfInt("iconSize",16)*dpiScale; + static const ImWchar fontRangeIcon[]={ICON_MIN_FA,ICON_MAX_FA,0}; + if ((iconFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(iconFont_compressed_data,iconFont_compressed_size,e->getConfInt("iconSize",16)*dpiScale,&fc,fontRangeIcon))==NULL) { + logE("could not load icon font!"); + } + if (settings.mainFontSize==settings.patFontSize && settings.patFont<5 && builtinFontM[settings.patFont]==builtinFont[settings.mainFont]) { + logD("using main font for pat font."); + patFont=mainFont; + } else { + if (settings.patFont==6) { // custom font + if ((patFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(settings.patFontPath.c_str(),e->getConfInt("patFontSize",18)*dpiScale,NULL,upTo800))==NULL) { + logW("could not load pattern font! reverting to default font"); + settings.patFont=0; + if ((patFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFontM[settings.patFont],builtinFontMLen[settings.patFont],e->getConfInt("patFontSize",18)*dpiScale,NULL,upTo800))==NULL) { + logE("could not load pattern font! falling back to Proggy Clean."); + patFont=ImGui::GetIO().Fonts->AddFontDefault(); + } + } + } else if (settings.patFont==5) { // system font + if ((patFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_PAT_FONT_PATH_1,e->getConfInt("patFontSize",18)*dpiScale,NULL,upTo800))==NULL) { + if ((patFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_PAT_FONT_PATH_2,e->getConfInt("patFontSize",18)*dpiScale,NULL,upTo800))==NULL) { + if ((patFont=ImGui::GetIO().Fonts->AddFontFromFileTTF(SYSTEM_PAT_FONT_PATH_3,e->getConfInt("patFontSize",18)*dpiScale,NULL,upTo800))==NULL) { + logW("could not load pattern font! reverting to default font"); + settings.patFont=0; + if ((patFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFontM[settings.patFont],builtinFontMLen[settings.patFont],e->getConfInt("patFontSize",18)*dpiScale,NULL,upTo800))==NULL) { + logE("could not load pattern font! falling back to Proggy Clean."); + patFont=ImGui::GetIO().Fonts->AddFontDefault(); + } + } + } + } + } else { + if ((patFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(builtinFontM[settings.patFont],builtinFontMLen[settings.patFont],e->getConfInt("patFontSize",18)*dpiScale,NULL,upTo800))==NULL) { + logE("could not load pattern font!"); + patFont=ImGui::GetIO().Fonts->AddFontDefault(); + } + } + } + if ((bigFont=ImGui::GetIO().Fonts->AddFontFromMemoryCompressedTTF(font_plexSans_compressed_data,font_plexSans_compressed_size,40*dpiScale))==NULL) { + logE("could not load big UI font!"); + } + + mainFont->FallbackChar='?'; + mainFont->DotChar='.'; + } // TODO: allow changing these colors. ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByTypeDir,"",uiColors[GUI_COLOR_FILE_DIR],ICON_FA_FOLDER_O); @@ -2224,6 +2231,8 @@ void FurnaceGUI::applyUISettings() { ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".fti",uiColors[GUI_COLOR_FILE_INSTR],ICON_FA_FILE); ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".bti",uiColors[GUI_COLOR_FILE_INSTR],ICON_FA_FILE); - if (fileDialog!=NULL) delete fileDialog; - fileDialog=new FurnaceGUIFileDialog(settings.sysFileDialog); + if (updateFonts) { + if (fileDialog!=NULL) delete fileDialog; + fileDialog=new FurnaceGUIFileDialog(settings.sysFileDialog); + } } From 7bb0743598e205a403a995bf178280a6cf618402 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 11 Apr 2022 03:14:12 -0500 Subject: [PATCH 635/637] GUI: fix file path corruption on sys file picker --- src/gui/fileDialog.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/gui/fileDialog.cpp b/src/gui/fileDialog.cpp index a0a10103d..c66a27e66 100644 --- a/src/gui/fileDialog.cpp +++ b/src/gui/fileDialog.cpp @@ -8,6 +8,7 @@ bool FurnaceGUIFileDialog::openLoad(String header, std::vector filter, c if (opened) return false; saving=false; curPath=path; + logD("opening load file dialog with curPath %s",curPath); if (sysDialog) { dialogO=new pfd::open_file(header,path,filter); } else { @@ -22,6 +23,7 @@ bool FurnaceGUIFileDialog::openSave(String header, std::vector filter, c if (opened) return false; saving=true; curPath=path; + logD("opening save file dialog with curPath %s",curPath); if (sysDialog) { dialogS=new pfd::save_file(header,path,filter); } else { @@ -65,6 +67,8 @@ bool FurnaceGUIFileDialog::render(const ImVec2& min, const ImVec2& max) { if (dialogS!=NULL) { if (dialogS->ready(0)) { fileName=dialogS->result(); + size_t dsPos=fileName.rfind(DIR_SEPARATOR); + if (dsPos!=String::npos) curPath=fileName.substr(0,dsPos); logD("returning %s",fileName.c_str()); return true; } @@ -77,6 +81,8 @@ bool FurnaceGUIFileDialog::render(const ImVec2& min, const ImVec2& max) { logD("returning nothing"); } else { fileName=dialogO->result()[0]; + size_t dsPos=fileName.rfind(DIR_SEPARATOR); + if (dsPos!=String::npos) curPath=fileName.substr(0,dsPos); logD("returning %s",fileName.c_str()); } return true; @@ -91,8 +97,15 @@ bool FurnaceGUIFileDialog::render(const ImVec2& min, const ImVec2& max) { String FurnaceGUIFileDialog::getPath() { if (sysDialog) { + if (curPath.size()>0) { + if (curPath[curPath.size()-1]==DIR_SEPARATOR) { + curPath=curPath.substr(0,curPath.size()-1); + } + } + logD("curPath: %s",curPath); return curPath; } else { + logD("curPath: %s",ImGuiFileDialog::Instance()->GetCurrentPath()); return ImGuiFileDialog::Instance()->GetCurrentPath(); } } From 224d8e11e5ea2a4727d1270d98513873b2129e07 Mon Sep 17 00:00:00 2001 From: tildearrow Date: Mon, 11 Apr 2022 03:34:38 -0500 Subject: [PATCH 636/637] GUI: add some oscilloscope settings --- src/gui/gui.h | 6 ++++++ src/gui/osc.cpp | 18 ++++++++++++------ src/gui/settings.cpp | 25 +++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 6 deletions(-) diff --git a/src/gui/gui.h b/src/gui/gui.h index 83082835f..7cc66c847 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -779,6 +779,9 @@ class FurnaceGUI { int titleBarSys; int frameBorders; int effectDeletionAltersValue; + int oscRoundedCorners; + int oscTakesEntireWindow; + int oscBorder; unsigned int maxUndoSteps; String mainFontPath; String patFontPath; @@ -839,6 +842,9 @@ class FurnaceGUI { titleBarSys(1), frameBorders(0), effectDeletionAltersValue(1), + oscRoundedCorners(1), + oscTakesEntireWindow(0), + oscBorder(1), maxUndoSteps(100), mainFontPath(""), patFontPath(""), diff --git a/src/gui/osc.cpp b/src/gui/osc.cpp index 9a1c6affe..c0ce978cf 100644 --- a/src/gui/osc.cpp +++ b/src/gui/osc.cpp @@ -73,9 +73,11 @@ void FurnaceGUI::drawOsc() { } if (!oscOpen) return; ImGui::SetNextWindowSizeConstraints(ImVec2(64.0f*dpiScale,32.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale)); - /*ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding,ImVec2(0,0)); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,ImVec2(0,0)); - ImGui::PushStyleVar(ImGuiStyleVar_ItemInnerSpacing,ImVec2(0,0));*/ + if (settings.oscTakesEntireWindow) { + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding,ImVec2(0,0)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,ImVec2(0,0)); + ImGui::PushStyleVar(ImGuiStyleVar_ItemInnerSpacing,ImVec2(0,0)); + } if (ImGui::Begin("Oscilloscope",&oscOpen)) { if (oscZoomSlider) { if (ImGui::VSliderFloat("##OscZoom",ImVec2(20.0f*dpiScale,ImGui::GetContentRegionAvail().y),&oscZoom,0.5,2.0)) { @@ -110,7 +112,7 @@ void FurnaceGUI::drawOsc() { if (ImGui::ItemAdd(rect,ImGui::GetID("wsDisplay"))) { // https://github.com/ocornut/imgui/issues/3710 const int v0 = dl->VtxBuffer.Size; - dl->AddRectFilled(inRect.Min,inRect.Max,0xffffffff,8.0f*dpiScale); + dl->AddRectFilled(inRect.Min,inRect.Max,0xffffffff,settings.oscRoundedCorners?(8.0f*dpiScale):0.0f); const int v1 = dl->VtxBuffer.Size; for (int i=v0; iAddPolyline(waveform,512,color,ImDrawFlags_None,dpiScale); - dl->AddRect(rect.Min,rect.Max,borderColor,8.0f*dpiScale,0,2.0f*dpiScale); + if (settings.oscBorder) { + dl->AddRect(inRect.Min,inRect.Max,borderColor,settings.oscRoundedCorners?(8.0f*dpiScale):0.0f,0,1.5f*dpiScale); + } } if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { oscZoomSlider=!oscZoomSlider; } } - //ImGui::PopStyleVar(3); + if (settings.oscTakesEntireWindow) { + ImGui::PopStyleVar(3); + } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_OSCILLOSCOPE; ImGui::End(); } diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index 215958fa7..87b798572 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -896,6 +896,25 @@ void FurnaceGUI::drawSettings() { ImGui::Separator(); + ImGui::Text("Oscilloscope settings:"); + + bool oscRoundedCornersB=settings.oscRoundedCorners; + if (ImGui::Checkbox("Rounded corners",&oscRoundedCornersB)) { + settings.oscRoundedCorners=oscRoundedCornersB; + } + + bool oscTakesEntireWindowB=settings.oscTakesEntireWindow; + if (ImGui::Checkbox("Fill entire window",&oscTakesEntireWindowB)) { + settings.oscTakesEntireWindow=oscTakesEntireWindowB; + } + + bool oscBorderB=settings.oscBorder; + if (ImGui::Checkbox("Border",&oscBorderB)) { + settings.oscBorder=oscBorderB; + } + + ImGui::Separator(); + if (ImGui::TreeNode("Color scheme")) { if (ImGui::Button("Import")) { openFileDialog(GUI_FILE_IMPORT_COLORS); @@ -1486,6 +1505,9 @@ void FurnaceGUI::syncSettings() { settings.titleBarSys=e->getConfInt("titleBarSys",1); settings.frameBorders=e->getConfInt("frameBorders",0); settings.effectDeletionAltersValue=e->getConfInt("effectDeletionAltersValue",1); + settings.oscRoundedCorners=e->getConfInt("oscRoundedCorners",1); + settings.oscTakesEntireWindow=e->getConfInt("oscTakesEntireWindow",0); + settings.oscBorder=e->getConfInt("oscBorder",1); clampSetting(settings.mainFontSize,2,96); clampSetting(settings.patFontSize,2,96); @@ -1613,6 +1635,9 @@ void FurnaceGUI::commitSettings() { e->setConf("titleBarSys",settings.titleBarSys); e->setConf("frameBorders",settings.frameBorders); e->setConf("effectDeletionAltersValue",settings.effectDeletionAltersValue); + e->setConf("oscRoundedCorners",settings.oscRoundedCorners); + e->setConf("oscTakesEntireWindow",settings.oscTakesEntireWindow); + e->setConf("oscBorder",settings.oscBorder); // colors for (int i=0; i Date: Mon, 11 Apr 2022 04:07:01 -0500 Subject: [PATCH 637/637] GUI: wait what? --- src/gui/fileDialog.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/gui/fileDialog.cpp b/src/gui/fileDialog.cpp index c66a27e66..7bfafb4a3 100644 --- a/src/gui/fileDialog.cpp +++ b/src/gui/fileDialog.cpp @@ -8,7 +8,7 @@ bool FurnaceGUIFileDialog::openLoad(String header, std::vector filter, c if (opened) return false; saving=false; curPath=path; - logD("opening load file dialog with curPath %s",curPath); + logD("opening load file dialog with curPath %s",curPath.c_str()); if (sysDialog) { dialogO=new pfd::open_file(header,path,filter); } else { @@ -23,7 +23,7 @@ bool FurnaceGUIFileDialog::openSave(String header, std::vector filter, c if (opened) return false; saving=true; curPath=path; - logD("opening save file dialog with curPath %s",curPath); + logD("opening save file dialog with curPath %s",curPath.c_str()); if (sysDialog) { dialogS=new pfd::save_file(header,path,filter); } else { @@ -97,15 +97,14 @@ bool FurnaceGUIFileDialog::render(const ImVec2& min, const ImVec2& max) { String FurnaceGUIFileDialog::getPath() { if (sysDialog) { - if (curPath.size()>0) { + if (curPath.size()>1) { if (curPath[curPath.size()-1]==DIR_SEPARATOR) { curPath=curPath.substr(0,curPath.size()-1); } } - logD("curPath: %s",curPath); + logD("curPath: %s",curPath.c_str()); return curPath; } else { - logD("curPath: %s",ImGuiFileDialog::Instance()->GetCurrentPath()); return ImGuiFileDialog::Instance()->GetCurrentPath(); } }