diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ffe48ee1..bcd989c2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,7 +11,7 @@ defaults: shell: bash env: - BUILD_TYPE: Debug + BUILD_TYPE: Release jobs: build: @@ -275,6 +275,7 @@ jobs: cp -v ../LICENSE LICENSE.txt cp -v ../README.md README.txt cp -vr ../{papers,demos,instruments} ../${binPath}/furnace.exe ./ + sha256sum ../${binPath}/furnace.exe > checksum.txt popd diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 31165511..a3973f40 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -29,6 +29,7 @@ the coding style is described here: - no spaces in operations except for `||` and `&&` - no space between variable name and assignment - space between macro in string literals + - space after comment delimiter - C++ pointer style: `void* variable` rather than `void *variable` - indent switch cases - preprocessor directives not intended diff --git a/demos/C64 junk.fur b/demos/C64 junk.fur index fb8e53b0..8c8ae4f4 100644 Binary files a/demos/C64 junk.fur and b/demos/C64 junk.fur differ diff --git a/demos/FDS TEST.fur b/demos/FDS TEST.fur index 54bdd36c..5e6c15dc 100644 Binary files a/demos/FDS TEST.fur and b/demos/FDS TEST.fur differ diff --git a/demos/GranularFurn.fur b/demos/GranularFurn.fur deleted file mode 100644 index e3d3c81d..00000000 Binary files a/demos/GranularFurn.fur and /dev/null differ diff --git a/demos/TimeTrial.fur b/demos/TimeTrial.fur new file mode 100644 index 00000000..98e0ea86 Binary files /dev/null and b/demos/TimeTrial.fur differ diff --git a/demos/atari breakbeat.fur b/demos/atari breakbeat.fur new file mode 100644 index 00000000..dd4183be Binary files /dev/null and b/demos/atari breakbeat.fur differ diff --git a/demos/c64 ring test.fur b/demos/c64 ring test.fur deleted file mode 100644 index 72304532..00000000 Binary files a/demos/c64 ring test.fur and /dev/null differ diff --git a/demos/duty fun.fur b/demos/duty fun.fur new file mode 100644 index 00000000..e1a2dcbf Binary files /dev/null and b/demos/duty fun.fur differ diff --git a/demos/game boy thing.fur b/demos/game boy thing.fur deleted file mode 100644 index 297ca9f9..00000000 Binary files a/demos/game boy thing.fur and /dev/null differ diff --git a/demos/genesis thing.fur b/demos/genesis thing.fur new file mode 100644 index 00000000..bacbfd0c Binary files /dev/null and b/demos/genesis thing.fur differ diff --git a/demos/overdrive.fur b/demos/overdrive.fur new file mode 100644 index 00000000..7601eb52 Binary files /dev/null and b/demos/overdrive.fur differ diff --git a/demos/snowdin.fur b/demos/snowdin.fur new file mode 100755 index 00000000..18697549 Binary files /dev/null and b/demos/snowdin.fur differ diff --git a/demos/very chill snes.fur b/demos/very chill snes.fur new file mode 100644 index 00000000..6681ed7f Binary files /dev/null and b/demos/very chill snes.fur differ diff --git a/extern/imgui_patched/imgui.cpp b/extern/imgui_patched/imgui.cpp index 3539ca81..612a88d2 100644 --- a/extern/imgui_patched/imgui.cpp +++ b/extern/imgui_patched/imgui.cpp @@ -3970,6 +3970,7 @@ void ImGui::UpdateMouseMovingWindowNewFrame() { MarkIniSettingsDirty(moving_window); SetWindowPos(moving_window, pos, ImGuiCond_Always); + g.InertialScrollInhibited=true; if (moving_window->ViewportOwned) // Synchronize viewport immediately because some overlays may relies on clipping rectangle before we Begin() into the window. { moving_window->Viewport->Pos = pos; @@ -6025,6 +6026,7 @@ static bool ImGui::UpdateWindowManualResize(ImGuiWindow* window, const ImVec2& s if (size_target.x != FLT_MAX) { window->SizeFull = size_target; + g.InertialScrollInhibited=true; MarkIniSettingsDirty(window); } if (pos_target.x != FLT_MAX) diff --git a/extern/vgsound_emu-modified/vgsound_emu/src/k007232/k007232.cpp b/extern/vgsound_emu-modified/vgsound_emu/src/k007232/k007232.cpp index 3798f4c8..02aa3460 100644 --- a/extern/vgsound_emu-modified/vgsound_emu/src/k007232/k007232.cpp +++ b/extern/vgsound_emu-modified/vgsound_emu/src/k007232/k007232.cpp @@ -126,7 +126,7 @@ void k007232_core::voice_t::write(u8 address, u8 data) m_start = (m_start & ~0x0ff00) | (u32(data) << 8); break; case 4: // start address bit 16 - m_start = (m_start & ~0x10000) | (u32(bitfield(data, 16)) << 16); + m_start = (m_start & ~0x10000) | (u32(bitfield(data, 0)) << 16); break; case 5: // keyon trigger keyon(); diff --git a/extern/vgsound_emu-modified/vgsound_emu/src/k053260/k053260.cpp b/extern/vgsound_emu-modified/vgsound_emu/src/k053260/k053260.cpp index cccdbbf5..bf056b81 100644 --- a/extern/vgsound_emu-modified/vgsound_emu/src/k053260/k053260.cpp +++ b/extern/vgsound_emu-modified/vgsound_emu/src/k053260/k053260.cpp @@ -59,6 +59,7 @@ void k053260_core::voice_t::tick() { m_bitpos -= 8; } + m_counter = bitfield(m_pitch, 0, 12); } m_data = m_host.m_intf.read_sample(bitfield(m_addr, 0, 21)); // fetch ROM if (update) @@ -76,6 +77,7 @@ void k053260_core::voice_t::tick() if (m_loop) { m_addr = m_start; + m_remain = m_length; m_adpcm_buf = 0; } else diff --git a/papers/format.md b/papers/format.md index e29b1289..685e1aef 100644 --- a/papers/format.md +++ b/papers/format.md @@ -32,6 +32,8 @@ these fields are 0 in format versions prior to 100 (0.6pre1). the format versions are: +- 129: Furnace dev129 +- 128: Furnace dev128 - 127: Furnace dev127 - 126: Furnace dev126 - 125: Furnace dev125 @@ -1120,9 +1122,11 @@ size | description | - 16: 16-bit PCM 1 | loop direction (>=123) or reserved | - 0: forward - | - 0: backward - | - 0: ping-pong - 2 | reserved + | - 1: backward + | - 2: ping-pong + 1 | flags (>=129) or reserved + | - 0: BRR emphasis + 1 | reserved 4 | loop start | - -1 means no loop 4 | loop end diff --git a/src/audio/sdlAudio.cpp b/src/audio/sdlAudio.cpp index f1d0e1e9..2915f034 100644 --- a/src/audio/sdlAudio.cpp +++ b/src/audio/sdlAudio.cpp @@ -106,6 +106,13 @@ bool TAAudioSDL::init(TAAudioDesc& request, TAAudioDesc& response) { audioSysStarted=true; } + const char* audioDriver=SDL_GetCurrentAudioDriver(); + if (audioDriver==NULL) { + logD("SDL audio driver: NULL!"); + } else { + logD("SDL audio driver: %s",audioDriver); + } + desc=request; desc.outFormat=TA_AUDIO_FORMAT_F32; diff --git a/src/engine/brrUtils.c b/src/engine/brrUtils.c index e3b20c26..c4d07b39 100644 --- a/src/engine/brrUtils.c +++ b/src/engine/brrUtils.c @@ -21,6 +21,8 @@ */ #include "brrUtils.h" +#include +#include #define NEXT_SAMPLE buf[j]-(buf[j]>>3) @@ -56,384 +58,257 @@ last2=last1; \ last1=nextDec; \ -long brrEncode(short* buf, unsigned char* out, long len, long loopStart) { +void brrEncodeBlock(const short* buf, unsigned char* out, unsigned char range, unsigned char filter, short* last1, short* last2, int* errorSum) { + // encode one block using BRR + unsigned char nibble=0; + int preOut=0; + int pred=0; + int nextDec=0; + int nextError=0; + *errorSum=0; + for (int j=0; j<16; j++) { + short s=NEXT_SAMPLE; + switch (filter) { + case 0: // no filter + pred=s; + break; + case 1: // simple + pred=s-(((int)(*last1*2)*15)>>4); + break; + case 2: // complex + pred=s+(((int)(*last2*2)*15)>>4)-(((int)(*last1*2)*61)>>5); + break; + case 3: + pred=s+(((int)(*last2*2)*13)>>4)-(((int)(*last1*2)*115)>>6); + break; + } + + if (pred<-32768) pred=-32768; + if (pred>32767) pred=32767; + + preOut=pred>>range; + if (range) { + if (pred&(1<<(range>>1))) preOut++; + if (filter==0 && range>=12) if (preOut<-7) preOut=-7; + } + if (preOut>7) preOut=7; + if (preOut<-8) preOut=-8; + + nibble=preOut&15; + if (j&1) { + out[j>>1]|=nibble; + } else { + out[j>>1]=nibble<<4; + } + + // roll last1/last2 + nextDec=nibble; + if (nextDec&8) nextDec|=0xfffffff0; + + if (range>=13) { /* invalid shift */ + nextDec=(nextDec<0)?0xfffff800:0; + } else { + nextDec<<=range; /* range */ + nextDec>>=1; + } + + switch (filter) { /* filter */ + case 0: + break; + case 1: + nextDec+=(*last1)+((-(*last1))>>4); + break; + case 2: + nextDec+=(*last1)*2+((-(*last1)*3)>>5)-(*last2)+((*last2)>>4); + break; + case 3: + nextDec+=(*last1)*2+((-(*last1)*13)>>6)-(*last2)+(((*last2)*3)>>4); + break; + } + + nextDec&=0x7fff; + if (nextDec&0x4000) nextDec|=0xffff8000; + + nextError=s-(nextDec<<1); + if (nextError<0) nextError=-nextError; + *errorSum+=nextError; + + *last2=*last1; + *last1=nextDec; + } +} + +long brrEncode(short* buf, unsigned char* out, long len, long loopStart, unsigned char emphasis) { if (len==0) return 0; // encoding process: // 1. read next group of 16 samples // 2. is this the first block? // - if yes, don't filter. output and then go to 1 - // 3. is this the loop block? - // - if yes, don't filter. output and then go to 1 - // 4. try encoding using 4 filters - // - perform linear prediction - // - calculate range - // - decode and apply correction to achieve low error + // 4. try encoding using 3 filters and 12 ranges (besides no filter) // 5. which one of these yields the least amount of error? // 6. output the one which does - // 7. is this the last block? - // - if yes, mark end and finish - // 8. go to 1 + // 7. do we still have more to encode? + // - if so, go to 1 + // 8. is loop point set? + // - if not, end process here + // 9. is transition between last block and loop block smooth? + // - if not, encode the loop block again and output it long total=0; - unsigned char next0[8]; - unsigned char next1[8]; - unsigned char next2[8]; - unsigned char next3[8]; unsigned char filter=0; - unsigned char range0=0; - unsigned char range1=0; - unsigned char range2=0; - unsigned char range3=0; - unsigned char o=0; - int pred1[16]; - int pred2[16]; - int pred3[16]; - short o1=0; - short o2=0; - short o0=0; - short o1f1=0; - short o1f2=0; - short o1f3=0; - //short o2f1=0; - short o2f2=0; - short o2f3=0; + unsigned char range=0; - int last1=0; - int last2=0; - int nextDec=0; - int maxError[4]; - int avgError[4]; + short x0=0; + short x1=0; + short x2=0; + int emphOut=0; + + short in[17]; + + short last1[4][13]; + short last2[4][13]; + int avgError[4][13]; + unsigned char possibleOut[4][13][8]; + + memset(in,0,16*sizeof(short)); + memset(last1,0,4*13*sizeof(short)); + memset(last2,0,4*13*sizeof(short)); + memset(avgError,0,4*13*sizeof(int)); + memset(possibleOut,0,4*13*8); - len&=~15; - loopStart&=~15; for (long i=0; i((8<>range0; - if (range0) if (s&(1<<(range1>>1))) o0++; - if (o0>7) o0=7; - if (o0<-8) o0=-8; - if (range0>=12) if (o0<-7) o0=-7; - o=o0&15; - if (j&1) { - next0[j>>1]|=o; - } else { - next0[j>>1]=o<<4; - } - } - - // encode with filter - if (i /*&& i!=loopStart*/) { - // 1: x = o0 - o1 * 15/16 - // 2: x = o0 + o2 * 15/16 - o1 * 61/32 - // 3: x = o0 + o2 * 13/16 - o1 * 115/64 - range1=0; - range2=0; - range3=0; - //o2f1=o2; - o2f2=o2; - o2f3=o2; - o1f1=o1; - o1f2=o1; - o1f3=o1; - // first pass - for (int j=0; j<16; j++) { - int s=NEXT_SAMPLE; - - pred1[j]=s-(((int)o1*15)>>4); - if (pred1[j]<-32768) pred1[j]=-32768; - if (pred1[j]>32767) pred1[j]=32767; - - pred2[j]=s+(((int)o2*15)>>4)-(((int)o1*61)>>5); - if (pred2[j]<-32768) pred2[j]=-32768; - if (pred2[j]>32767) pred2[j]=32767; - - pred3[j]=s+(((int)o2*13)>>4)-(((int)o1*115)>>6); - if (pred3[j]<-32768) pred3[j]=-32768; - if (pred3[j]>32767) pred3[j]=32767; - - o2=o1; - o1=s; - } - // calculate range of values - for (int j=0; j<16; j++) { - short s=pred1[j]; - if (s<0) s=-s; - while (range1<12 && s>((8<((8<((8<>4); - if (pred1[j]<-32768) pred1[j]=-32768; - if (pred1[j]>32767) pred1[j]=32767; - - o0=pred1[j]>>range1; - if (range1) if (pred1[j]&(1<<(range1>>1))) o0++; - if (o0>7) o0=7; - if (o0<-8) o0=-8; - o=o0&15; - if (j&1) { - next1[j>>1]|=o; + if (i+17>len) { + long p=i; + for (int j=0; j<17; j++) { + if (p>=len) { + if (loopStart<0 || loopStart>=len) { + in[j]=0; + } else { + p=loopStart; + in[j]=buf[p++]; + } } else { - next1[j>>1]=o<<4; - } - - nextDec=o; - DO_ONE_DEC(range1); - //o2f1=last2<<1; - o1f1=last1<<1; - } - last1=prevLast1; - last2=prevLast2; - filter=2; - for (int j=0; j<16; j++) { - int s=NEXT_SAMPLE; - pred2[j]=s+(((int)o2f2*15)>>4)-(((int)o1f2*61)>>5); - if (pred2[j]<-32768) pred2[j]=-32768; - if (pred2[j]>32767) pred2[j]=32767; - - o0=pred2[j]>>range2; - if (range2) if (pred2[j]&(1<<(range2>>1))) o0++; - if (o0>7) o0=7; - if (o0<-8) o0=-8; - o=o0&15; - if (j&1) { - next2[j>>1]|=o; - } else { - next2[j>>1]=o<<4; - } - - nextDec=o; - DO_ONE_DEC(range2); - o2f2=last2<<1; - o1f2=last1<<1; - } - last1=prevLast1; - last2=prevLast2; - filter=3; - for (int j=0; j<16; j++) { - int s=NEXT_SAMPLE; - pred3[j]=s+(((int)o2f3*13)>>4)-(((int)o1f3*115)>>6); - if (pred3[j]<-32768) pred3[j]=-32768; - if (pred3[j]>32767) pred3[j]=32767; - - o0=pred3[j]>>range3; - if (range3) if (pred3[j]&(1<<(range3>>1))) o0++; - if (o0>7) o0=7; - if (o0<-8) o0=-8; - o=o0&15; - if (j&1) { - next3[j>>1]|=o; - } else { - next3[j>>1]=o<<4; - } - - nextDec=o; - DO_ONE_DEC(range3); - o2f3=last2<<1; - o1f3=last1<<1; - } - last1=prevLast1; - last2=prevLast2; - - // find best filter - int error=0; - - maxError[0]=0; - maxError[1]=0; - maxError[2]=0; - maxError[3]=0; - avgError[0]=0; - avgError[1]=0; - avgError[2]=0; - avgError[3]=0; - - // test filter 0 - filter=0; - for (int j=0; j<16; j++) { - int s=NEXT_SAMPLE; - if (j&1) { - nextDec=next0[j>>1]&15; - } else { - nextDec=next0[j>>1]>>4; - } - DO_ONE_DEC(range0); - error=s-(nextDec<<1); - if (error<0) error=-error; - avgError[0]+=error; - if (error>maxError[0]) maxError[0]=error; - } - last1=prevLast1; - last2=prevLast2; - - // test filter 1 - filter=1; - for (int j=0; j<16; j++) { - int s=NEXT_SAMPLE; - if (j&1) { - nextDec=next1[j>>1]&15; - } else { - nextDec=next1[j>>1]>>4; - } - DO_ONE_DEC(range1); - error=s-(nextDec<<1); - if (error<0) error=-error; - avgError[1]+=error; - if (error>maxError[1]) maxError[1]=error; - } - last1=prevLast1; - last2=prevLast2; - - // test filter 2 - filter=2; - for (int j=0; j<16; j++) { - int s=NEXT_SAMPLE; - if (j&1) { - nextDec=next2[j>>1]&15; - } else { - nextDec=next2[j>>1]>>4; - } - DO_ONE_DEC(range2); - error=s-(nextDec<<1); - if (error<0) error=-error; - avgError[2]+=error; - if (error>maxError[2]) maxError[2]=error; - } - last1=prevLast1; - last2=prevLast2; - - // test filter 3 - filter=3; - for (int j=0; j<16; j++) { - int s=NEXT_SAMPLE; - if (j&1) { - nextDec=next3[j>>1]&15; - } else { - nextDec=next3[j>>1]>>4; - } - DO_ONE_DEC(range3); - error=s-(nextDec<<1); - if (error<0) error=-error; - avgError[3]+=error; - if (error>maxError[3]) maxError[3]=error; - } - last1=prevLast1; - last2=prevLast2; - - // pick best filter - int candError=0x7fffffff; - for (int j=0; j<4; j++) { - if (avgError[j] %d\n",i>>4,avgError[0],avgError[1],avgError[2],avgError[3],filter); } else { - // don't filter on the first or loop block + memcpy(in,&buf[i],17*sizeof(short)); + } + + // emphasis + if (emphasis) { + for (int j=0; j<17; j++) { + x0=x1; + x1=x2; + x2=in[j]; + + if (j==0) continue; + emphOut=((x1<<11)-x0*370-in[j]*374)/1305; + if (emphOut<-32768) emphOut=-32768; + if (emphOut>32767) emphOut=32767; + in[j-1]=emphOut; + } + } + + // encode + for (int j=0; j<4; j++) { + for (int k=0; k<13; k++) { + brrEncodeBlock(in,possibleOut[j][k],k,j,&last1[j][k],&last2[j][k],&avgError[j][k]); + } + } + + // find best filter/range + int candError=0x7fffffff; + if (i==0) { filter=0; + for (int k=0; k<13; k++) { + if (avgError[0][k]>4; - DO_ONE_DEC(range0); - nextDec=next0[j]&15; - DO_ONE_DEC(range0); - } - o2=last2<<1; - o1=last1<<1; - - out[0]=(range0<<4)|(filter<<2)|((i+16>=len)?((loopStart>=0)?3:1):0); - out[1]=next0[0]; - out[2]=next0[1]; - out[3]=next0[2]; - out[4]=next0[3]; - out[5]=next0[4]; - out[6]=next0[5]; - out[7]=next0[6]; - out[8]=next0[7]; - break; - case 1: - for (int j=0; j<8; j++) { - nextDec=next1[j]>>4; - DO_ONE_DEC(range1); - nextDec=next1[j]&15; - DO_ONE_DEC(range1); - } - o2=last2<<1; - o1=last1<<1; - out[0]=(range1<<4)|(filter<<2)|((i+16>=len)?((loopStart>=0)?3:1):0); - out[1]=next1[0]; - out[2]=next1[1]; - out[3]=next1[2]; - out[4]=next1[3]; - out[5]=next1[4]; - out[6]=next1[5]; - out[7]=next1[6]; - out[8]=next1[7]; - break; - case 2: - for (int j=0; j<8; j++) { - nextDec=next2[j]>>4; - DO_ONE_DEC(range2); - nextDec=next2[j]&15; - DO_ONE_DEC(range2); - } - o2=last2<<1; - o1=last1<<1; - out[0]=(range2<<4)|(filter<<2)|((i+16>=len)?((loopStart>=0)?3:1):0); - out[1]=next2[0]; - out[2]=next2[1]; - out[3]=next2[2]; - out[4]=next2[3]; - out[5]=next2[4]; - out[6]=next2[5]; - out[7]=next2[6]; - out[8]=next2[7]; - break; - case 3: - for (int j=0; j<8; j++) { - nextDec=next3[j]>>4; - DO_ONE_DEC(range3); - nextDec=next3[j]&15; - DO_ONE_DEC(range3); - } - o2=last2<<1; - o1=last1<<1; - out[0]=(range3<<4)|(filter<<2)|((i+16>=len)?((loopStart>=0)?3:1):0); - out[1]=next3[0]; - out[2]=next3[1]; - out[3]=next3[2]; - out[4]=next3[3]; - out[5]=next3[4]; - out[6]=next3[5]; - out[7]=next3[6]; - out[8]=next3[7]; - break; + // write + out[0]=(range<<4)|(filter<<2)|((i+16>=len && loopStart<0)?1:0); + for (int j=0; j<8; j++) { + out[j+1]=possibleOut[filter][range][j]; + } + + for (int j=0; j<4; j++) { + for (int k=0; k<13; k++) { + last1[j][k]=last1[filter][range]; + last2[j][k]=last2[filter][range]; + } + } + out+=9; + total+=9; + } + // encode loop block + if (loopStart>=0) { + long p=loopStart; + for (int i=0; i<17; i++) { + if (p>=len) { + p=loopStart; + } + in[i]=buf[p++]; + } + + if (emphasis) { + for (int j=0; j<17; j++) { + x0=x1; + x1=x2; + x2=in[j]; + + if (j==0) continue; + emphOut=((x1<<11)-x0*370-in[j]*374)/1305; + if (emphOut<-32768) emphOut=-32768; + if (emphOut>32767) emphOut=32767; + in[j-1]=emphOut; + } + } + + // encode (filter 0/1 only) + for (int j=0; j<2; j++) { + for (int k=0; k<13; k++) { + brrEncodeBlock(in,possibleOut[j][k],k,j,&last1[j][k],&last2[j][k],&avgError[j][k]); + } + } + + // find best filter/range + int candError=0x7fffffff; + for (int j=0; j<2; j++) { + for (int k=0; k<13; k++) { + if (avgError[j][k]=total)?0:outOrig[i]; + + if (i==0) continue; + + outOrig[i-1]=(x0*370+x1*1305+x2*374)>>11; + } + } + return total; } diff --git a/src/engine/brrUtils.h b/src/engine/brrUtils.h index 5cc898e1..e4adc3a6 100644 --- a/src/engine/brrUtils.h +++ b/src/engine/brrUtils.h @@ -30,21 +30,23 @@ extern "C" { /** * read len samples from buf, encode in BRR and output to out. * @param buf input data. - * @param out output buffer. shall be at least 9*(len/16) shorts in size. + * @param out output buffer. shall be at least 9*((15+len)/16) shorts in size (9 more if loopStart is not -1!) * @param len input length (should be a multiple of 16. if it isn't, the output will be padded). * @param loopStart beginning of loop area (may be -1 for no loop). this is used to ensure the respective block has no filter in order to loop properly. + * @param emphasis apply filter to compensate for Gaussian interpolation high frequency loss. * @return number of written samples. */ -long brrEncode(short* buf, unsigned char* out, long len, long loopStart); +long brrEncode(short* buf, unsigned char* out, long len, long loopStart, unsigned char emphasis); /** * read len bytes from buf, decode BRR and output to out. * @param buf input data. * @param out output buffer. shall be at least 16*(len/9) shorts in size. * @param len input length (shall be a multiple of 9). + * @param emphasis apply filter to simulate Gaussian interpolation high frequency loss. * @return number of written bytes. */ -long brrDecode(unsigned char* buf, short* out, long len); +long brrDecode(unsigned char* buf, short* out, long len, unsigned char emphasis); #ifdef __cplusplus } diff --git a/src/engine/defines.h b/src/engine/defines.h new file mode 100644 index 00000000..2cd4b692 --- /dev/null +++ b/src/engine/defines.h @@ -0,0 +1,36 @@ +/** + * 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 _DEFINES_H +#define _DEFINES_H + +// global +#define DIV_MAX_CHIPS 32 +#define DIV_MAX_CHANS 128 +#define DIV_MAX_PATTERNS 256 + +// in-pattern +#define DIV_MAX_ROWS 256 +#define DIV_MAX_COLS 32 +#define DIV_MAX_EFFECTS 8 + +// sample related +#define DIV_MAX_SAMPLE_TYPE 4 + +#endif diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 585f3264..430bcc94 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -884,10 +884,10 @@ void DivEngine::runExportThread() { break; } case DIV_EXPORT_MODE_MANY_SYS: { - SNDFILE* sf[32]; - SF_INFO si[32]; - String fname[32]; - SFWrapper sfWrap[32]; + SNDFILE* sf[DIV_MAX_CHIPS]; + SF_INFO si[DIV_MAX_CHIPS]; + String fname[DIV_MAX_CHIPS]; + SFWrapper sfWrap[DIV_MAX_CHIPS]; for (int i=0; iord[dest][i]^=curOrders->ord[src][i]; curOrders->ord[src][i]^=curOrders->ord[dest][i]; curOrders->ord[dest][i]^=curOrders->ord[src][i]; @@ -1487,7 +1487,7 @@ void DivEngine::swapChannels(int src, int dest) { void DivEngine::stompChannel(int ch) { logV("stomping channel %d",ch); - for (int i=0; i<256; i++) { + for (int i=0; iord[ch][i]=0; } curPat[ch].wipePatterns(); @@ -1649,8 +1649,8 @@ void DivEngine::changeSystem(int index, DivSystem which, bool preserveOrder) { } bool DivEngine::addSystem(DivSystem which) { - if (song.systemLen>32) { - lastError="max number of systems is 32"; + if (song.systemLen>DIV_MAX_CHIPS) { + lastError=fmt::sprintf("max number of systems is %d",DIV_MAX_CHIPS); return false; } if (chans+getChannelCount(which)>DIV_MAX_CHANS) { @@ -1786,7 +1786,7 @@ bool DivEngine::swapSystem(int src, int dest, bool preserveOrder) { for (size_t i=0; iorders; - DivPattern* prevPat[DIV_MAX_CHANS][256]; + DivPattern* prevPat[DIV_MAX_CHANS][DIV_MAX_PATTERNS]; unsigned char prevEffectCols[DIV_MAX_CHANS]; String prevChanName[DIV_MAX_CHANS]; String prevChanShortName[DIV_MAX_CHANS]; @@ -1794,7 +1794,7 @@ bool DivEngine::swapSystem(int src, int dest, bool preserveOrder) { unsigned char prevChanCollapse[DIV_MAX_CHANS]; for (int j=0; jpat[j].data[k]; } prevEffectCols[j]=song.subsong[i]->pat[j].effectCols; @@ -1806,7 +1806,7 @@ bool DivEngine::swapSystem(int src, int dest, bool preserveOrder) { } for (int j=0; jorders.ord[j][k]=prevOrders.ord[swappedChannels[j]][k]; song.subsong[i]->pat[j].data[k]=prevPat[swappedChannels[j]][k]; } @@ -2768,7 +2768,7 @@ void DivEngine::delInstrument(int index) { song.insLen=song.ins.size(); for (int i=0; ipat[i].data[k]==NULL) continue; for (int l=0; lpatLen; l++) { if (song.subsong[j]->pat[i].data[k]->data[l][2]>index) { @@ -3468,7 +3468,7 @@ void DivEngine::delSample(int index) { void DivEngine::addOrder(bool duplicate, bool where) { unsigned char order[DIV_MAX_CHANS]; - if (curSubSong->ordersLen>=0xff) return; + if (curSubSong->ordersLen>=(DIV_MAX_PATTERNS-1)) return; memset(order,0,DIV_MAX_CHANS); BUSY_BEGIN_SOFT; if (duplicate) { @@ -3476,14 +3476,14 @@ void DivEngine::addOrder(bool duplicate, bool where) { order[i]=curOrders->ord[i][curOrder]; } } else { - bool used[256]; + bool used[DIV_MAX_PATTERNS]; for (int i=0; iordersLen; j++) { used[curOrders->ord[i][j]]=true; } - order[i]=0xff; - for (int j=0; j<256; j++) { + order[i]=(DIV_MAX_PATTERNS-1); + for (int j=0; jordersLen>=0xff) return; + if (curSubSong->ordersLen>=(DIV_MAX_PATTERNS-1)) return; warnings=""; BUSY_BEGIN_SOFT; for (int i=0; iord[i][curOrder]; // find free slot - for (int j=0; j<256; j++) { + for (int j=0; jdata,oldPat->data,256*32*sizeof(short)); + memcpy(pat->data,oldPat->data,DIV_MAX_ROWS*DIV_MAX_COLS*sizeof(short)); logD("found at %d",j); didNotFind=false; break; @@ -3629,7 +3629,7 @@ void DivEngine::moveOrderDown() { void DivEngine::exchangeIns(int one, int two) { for (int i=0; ipat[i].data[k]==NULL) continue; for (int l=0; lpatLen; l++) { if (song.subsong[j]->pat[i].data[k]->data[l][2]==one) { diff --git a/src/engine/engine.h b/src/engine/engine.h index ab028aca..12150fbb 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -47,8 +47,8 @@ #define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock(); #define BUSY_END isBusy.unlock(); softLocked=false; -#define DIV_VERSION "dev128" -#define DIV_ENGINE_VERSION 128 +#define DIV_VERSION "dev129" +#define DIV_ENGINE_VERSION 129 // for imports #define DIV_VERSION_MOD 0xff01 #define DIV_VERSION_FC 0xff02 @@ -318,7 +318,7 @@ enum DivChanTypes { extern const char* cmdName[]; class DivEngine { - DivDispatchContainer disCont[32]; + DivDispatchContainer disCont[DIV_MAX_CHIPS]; TAAudio* output; TAAudioDesc want, got; String exportPath; diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index e01a5710..b39aa344 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -1581,7 +1581,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { unsigned int wavePtr[256]; unsigned int samplePtr[256]; unsigned int subSongPtr[256]; - unsigned int sysFlagsPtr[32]; + unsigned int sysFlagsPtr[DIV_MAX_CHIPS]; std::vector patPtr; int numberOfSubSongs=0; char magic[5]; @@ -1760,7 +1760,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { delete[] file; return false; } - if (subSong->patLen>256) { + if (subSong->patLen>DIV_MAX_ROWS) { logE("pattern length is too large!"); lastError="pattern length is too large!"; delete[] file; @@ -1772,7 +1772,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { delete[] file; return false; } - if (subSong->ordersLen>256) { + if (subSong->ordersLen>DIV_MAX_PATTERNS) { logE("song is too long!"); lastError="song is too long!"; delete[] file; @@ -1804,7 +1804,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { } logD("systems:"); - for (int i=0; i<32; i++) { + for (int i=0; i32) ds.systemLen=32; + if (++ds.systemLen>DIV_MAX_CHIPS) ds.systemLen=DIV_MAX_CHIPS; if (ds.system[i]==DIV_SYSTEM_GENESIS) { ds.system[i]=DIV_SYSTEM_YM2612; @@ -2000,7 +2000,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { for (int i=0; ipat[i].effectCols=reader.readC(); - if (subSong->pat[i].effectCols<1 || subSong->pat[i].effectCols>8) { + if (subSong->pat[i].effectCols<1 || subSong->pat[i].effectCols>DIV_MAX_EFFECTS) { logE("channel %d has zero or too many effect columns! (%d)",i,subSong->pat[i].effectCols); lastError=fmt::sprintf("channel %d has too many effect columns! (%d)",i,subSong->pat[i].effectCols); delete[] file; @@ -2198,7 +2198,7 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { // read system flags if (ds.version>=119) { logD("reading chip flags..."); - for (int i=0; i<32; i++) { + for (int i=0; i255) { + if (index<0 || index>(DIV_MAX_PATTERNS-1)) { logE("pattern index out of range!",i); lastError="pattern index out of range!"; ds.unload(); @@ -4175,14 +4175,14 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { // fail if values are out of range /* - if (subSong->ordersLen>256) { - logE("maximum song length is 256!"); - lastError="maximum song length is 256"; + if (subSong->ordersLen>DIV_MAX_PATTERNS) { + logE("maximum song length is %d!",DIV_MAX_PATTERNS); + lastError=fmt::sprintf("maximum song length is %d",DIV_MAX_PATTERNS); return NULL; } - if (subSong->patLen>256) { - logE("maximum pattern length is 256!"); - lastError="maximum pattern length is 256"; + if (subSong->patLen>DIV_MAX_ROWS) { + logE("maximum pattern length is %d!",DIV_MAX_ROWS); + lastError=fmt::sprintf("maximum pattern length is %d",DIV_MAX_ROWS); return NULL; } */ @@ -4236,18 +4236,18 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { for (int i=0; ipat[i].data[k]==NULL) continue; patsToWrite.push_back(PatToWrite(j,i,k)); } } } } else { - bool alreadyAdded[256]; + bool alreadyAdded[DIV_MAX_PATTERNS]; for (int i=0; iordersLen; k++) { if (alreadyAdded[subs->orders.ord[i][k]]) continue; patsToWrite.push_back(PatToWrite(j,i,subs->orders.ord[i][k])); @@ -4276,7 +4276,7 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { w->writeS(song.sampleLen); w->writeI(patsToWrite.size()); - for (int i=0; i<32; i++) { + for (int i=0; i=song.systemLen) { w->writeC(0); } else { @@ -4284,17 +4284,17 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { } } - for (int i=0; i<32; i++) { + for (int i=0; iwriteC(song.systemVol[i]); } - for (int i=0; i<32; i++) { + for (int i=0; iwriteC(song.systemPan[i]); } // chip flags (we'll seek here later) sysFlagsPtrSeek=w->tell(); - for (int i=0; i<32; i++) { + for (int i=0; iwriteI(0); } diff --git a/src/engine/orders.h b/src/engine/orders.h index c57d1aab..78a05f0c 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][256]; + unsigned char ord[DIV_MAX_CHANS][DIV_MAX_PATTERNS]; DivOrders() { - memset(ord,0,DIV_MAX_CHANS*256); + memset(ord,0,DIV_MAX_CHANS*DIV_MAX_PATTERNS); } }; diff --git a/src/engine/pattern.cpp b/src/engine/pattern.cpp index 77255e08..6acdc284 100644 --- a/src/engine/pattern.cpp +++ b/src/engine/pattern.cpp @@ -23,8 +23,8 @@ static DivPattern emptyPat; DivPattern::DivPattern() { - memset(data,-1,256*32*sizeof(short)); - for (int i=0; i<256; i++) { + memset(data,-1,DIV_MAX_ROWS*DIV_MAX_COLS*sizeof(short)); + for (int i=0; i> DivChannelData::optimize() { std::vector> ret; - for (int i=0; i<256; i++) { + for (int i=0; idata,data[j]->data,256*32*sizeof(short))==0) { + if (memcmp(data[i]->data,data[j]->data,DIV_MAX_ROWS*DIV_MAX_COLS*sizeof(short))==0) { delete data[j]; data[j]=NULL; logV("%d == %d",i,j); @@ -63,15 +63,15 @@ std::vector> DivChannelData::optimize() { std::vector> DivChannelData::rearrange() { std::vector> ret; - for (int i=0; i<256; i++) { + for (int i=0; i %d",j,i); ret.push_back(std::pair(j,i)); - if (++i>=256) break; + if (++i>=DIV_MAX_PATTERNS) break; } } } @@ -80,7 +80,7 @@ std::vector> DivChannelData::rearrange() { } void DivChannelData::wipePatterns() { - for (int i=0; i<256; i++) { + for (int i=0; i -#define rWrite(a,v) if (!skipRegisterWrites) {if (isFP) {sid_fp.write(a,v);} else {sid.write(a,v);}; regPool[(a)&0x1f]=v; if (dumpWrites) {addWrite(a,v);} } +#define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } #define CHIP_FREQBASE 524288 @@ -66,6 +66,16 @@ const char** DivPlatformC64::getRegisterSheet() { void DivPlatformC64::acquire(short* bufL, short* bufR, size_t start, size_t len) { int dcOff=isFP?0:sid.get_dc(0); for (size_t i=start; i=4) { @@ -483,6 +493,7 @@ float DivPlatformC64::getPostAmp() { } void DivPlatformC64::reset() { + while (!writes.empty()) writes.pop(); for (int i=0; i<3; i++) { chan[i]=DivPlatformC64::Channel(); chan[i].std.setEngine(parent); diff --git a/src/engine/platform/c64.h b/src/engine/platform/c64.h index 22513e06..9066ab03 100644 --- a/src/engine/platform/c64.h +++ b/src/engine/platform/c64.h @@ -21,6 +21,7 @@ #define _C64_H #include "../dispatch.h" +#include #include "../macroInt.h" #include "sound/c64/sid.h" #include "sound/c64_fp/SID.h" @@ -61,6 +62,12 @@ class DivPlatformC64: public DivDispatch { Channel chan[3]; DivDispatchOscBuffer* oscBuf[3]; bool isMuted[3]; + struct QueuedWrite { + unsigned char addr; + unsigned char val; + QueuedWrite(unsigned char a, unsigned char v): addr(a), val(v) {} + }; + std::queue writes; unsigned char filtControl, filtRes, vol; unsigned char writeOscBuf; diff --git a/src/engine/platform/namcowsg.cpp b/src/engine/platform/namcowsg.cpp index c1466b90..f9e8c6ea 100644 --- a/src/engine/platform/namcowsg.cpp +++ b/src/engine/platform/namcowsg.cpp @@ -484,9 +484,6 @@ void DivPlatformNamcoWSG::reset() { namco->set_voices(chans); namco->set_stereo((devType==2 || devType==30)); namco->device_start(NULL); - lastPan=0xff; - cycles=0; - curChan=-1; } bool DivPlatformNamcoWSG::isStereo() { diff --git a/src/engine/platform/namcowsg.h b/src/engine/platform/namcowsg.h index 56387ee6..d83aa2fe 100644 --- a/src/engine/platform/namcowsg.h +++ b/src/engine/platform/namcowsg.h @@ -53,9 +53,7 @@ class DivPlatformNamcoWSG: public DivDispatch { QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v) {} }; std::queue writes; - unsigned char lastPan; - int cycles, curChan, delay; namco_audio_device* namco; int devType, chans; unsigned char regPool[512]; diff --git a/src/engine/platform/qsound.cpp b/src/engine/platform/qsound.cpp index cc411fa5..e513d531 100644 --- a/src/engine/platform/qsound.cpp +++ b/src/engine/platform/qsound.cpp @@ -783,7 +783,7 @@ void DivPlatformQSound::renderSamples(int sysID) { for (int i=0; idataQSoundA[i]; } - sampleLoaded[i]=true; + sampleLoadedBS[i]=true; } offBS[i]=memPos; memPos+=length+16; diff --git a/src/engine/platform/sms.cpp b/src/engine/platform/sms.cpp index 0ce4c836..d6da280c 100644 --- a/src/engine/platform/sms.cpp +++ b/src/engine/platform/sms.cpp @@ -125,6 +125,8 @@ double DivPlatformSMS::NOTE_SN(int ch, int note) { if (parent->song.linearPitch==2 || !easyNoise) { return NOTE_PERIODIC(note); } + int easyStartingPeriod=16; + int easyThreshold=round(12.0*log((chipClock/(easyStartingPeriod*CHIP_DIVIDER))/(0.0625*parent->song.tuning))/log(2.0))-3; if (note>easyThreshold) { return MAX(0,easyStartingPeriod-(note-easyThreshold)); } @@ -132,19 +134,23 @@ double DivPlatformSMS::NOTE_SN(int ch, int note) { } int DivPlatformSMS::snCalcFreq(int ch) { - if (parent->song.linearPitch==2 && easyNoise && chan[ch].baseFreq+chan[ch].pitch+chan[ch].pitch2>(easyThreshold<<7)) { - int ret=(((easyStartingPeriod<<7)+0x40)-(chan[ch].baseFreq+chan[ch].pitch+chan[ch].pitch2-(easyThreshold<<7)))>>7; + double CHIP_DIVIDER=toneDivider; + if (ch==3) CHIP_DIVIDER=noiseDivider; + int easyStartingPeriod=16; + int easyThreshold=round(128.0*12.0*log((chipClock/(easyStartingPeriod*CHIP_DIVIDER))/(0.0625*parent->song.tuning))/log(2.0))-384+64; + if (parent->song.linearPitch==2 && easyNoise && chan[ch].baseFreq+chan[ch].pitch+chan[ch].pitch2>(easyThreshold)) { + int ret=(((easyStartingPeriod<<7))-(chan[ch].baseFreq+chan[ch].pitch+chan[ch].pitch2-(easyThreshold)))>>7; if (ret<0) ret=0; return ret; } - return parent->calcFreq(chan[ch].baseFreq,chan[ch].pitch,true,0,chan[ch].pitch2,chipClock,ch==3?noiseDivider:toneDivider); + return parent->calcFreq(chan[ch].baseFreq,chan[ch].pitch,true,0,chan[ch].pitch2,chipClock,CHIP_DIVIDER); } void DivPlatformSMS::tick(bool sysTick) { for (int i=0; i<4; i++) { chan[i].std.next(); if (chan[i].std.vol.had) { - chan[i].outVol=VOL_SCALE_LOG_BROKEN(chan[i].std.vol.val,chan[i].vol,15); + chan[i].outVol=VOL_SCALE_LOG(chan[i].std.vol.val,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.val))>>4; @@ -152,10 +158,9 @@ void DivPlatformSMS::tick(bool sysTick) { } if (chan[i].std.arp.had) { if (!chan[i].inPorta) { - // TODO: check whether this weird octave boundary thing applies to other systems as well // TODO: add compatibility flag. this is horrible. int areYouSerious=parent->calcArp(chan[i].note,chan[i].std.arp.val); - while (areYouSerious>0x60) areYouSerious-=12; + if (!easyNoise) while (areYouSerious>0x60) areYouSerious-=12; chan[i].baseFreq=NOTE_SN(i,areYouSerious); chan[i].actualNote=areYouSerious; chan[i].freqChanged=true; @@ -422,7 +427,7 @@ void DivPlatformSMS::reset() { YMPSG_Init(&sn_nuked,isRealSN,12,isRealSN?13:15,isRealSN?16383:32767); snNoiseMode=3; rWrite(0,0xe7); - updateSNMode=false; + updateSNMode=true; oldValue=0xff; lastPan=0xff; if (stereo) { @@ -464,40 +469,27 @@ void DivPlatformSMS::setFlags(const DivConfig& flags) { switch (flags.getInt("clockSel",0)) { case 1: chipClock=COLOR_PAL*4.0/5.0; - easyThreshold=84; - easyStartingPeriod=13; break; case 2: chipClock=4000000; - easyThreshold=86; - easyStartingPeriod=13; break; case 3: chipClock=COLOR_NTSC/2.0; - easyThreshold=72; - easyStartingPeriod=13; break; case 4: chipClock=3000000; - easyThreshold=81; - easyStartingPeriod=13; break; case 5: chipClock=2000000; - easyThreshold=74; - easyStartingPeriod=13; break; case 6: chipClock=COLOR_NTSC/8.0; - easyThreshold=48; - easyStartingPeriod=13; break; default: chipClock=COLOR_NTSC; - easyThreshold=84; - easyStartingPeriod=13; break; } + CHECK_CUSTOM_CLOCK; resetPhase=!flags.getBool("noPhaseReset",false); easyNoise=!flags.getBool("noEasyNoise",false); divider=16; @@ -568,7 +560,7 @@ void DivPlatformSMS::setFlags(const DivConfig& flags) { stereo=false; break; } - CHECK_CUSTOM_CLOCK; + rate=chipClock/divider; for (int i=0; i<4; i++) { oscBuf[i]->rate=rate; diff --git a/src/engine/platform/sms.h b/src/engine/platform/sms.h index 2c3e1746..5c0f703e 100644 --- a/src/engine/platform/sms.h +++ b/src/engine/platform/sms.h @@ -52,8 +52,6 @@ class DivPlatformSMS: public DivDispatch { int divider=16; double toneDivider=64.0; double noiseDivider=64.0; - int easyThreshold; - int easyStartingPeriod; bool updateSNMode; bool resetPhase; bool isRealSN; diff --git a/src/engine/platform/snes.cpp b/src/engine/platform/snes.cpp index 2dd64de3..2ab2c62c 100644 --- a/src/engine/platform/snes.cpp +++ b/src/engine/platform/snes.cpp @@ -214,13 +214,13 @@ void DivPlatformSNES::tick(bool sysTick) { loop=start; } else if (chan[i].sample>=0 && chan[i].samplesong.sampleLen) { start=sampleOff[chan[i].sample]; - end=MIN(start+MAX(s->lengthBRR,1),getSampleMemCapacity()); + end=MIN(start+MAX(s->lengthBRR+((s->loop && s->depth!=DIV_SAMPLE_DEPTH_BRR)?9:0),1),getSampleMemCapacity()); loop=MAX(start,end-1); if (chan[i].audPos>0) { start=start+MIN(chan[i].audPos,s->lengthBRR-1)/16*9; } if (s->loopStart>=0) { - loop=start+s->loopStart/16*9; + loop=((s->depth!=DIV_SAMPLE_DEPTH_BRR)?9:0)+start+((s->loopStart/16)*9); } } else { start=0; @@ -817,7 +817,7 @@ void DivPlatformSNES::renderSamples(int sysID) { continue; } - int length=s->lengthBRR; + int length=s->lengthBRR+((s->loop && s->depth!=DIV_SAMPLE_DEPTH_BRR)?9:0); int actualLength=MIN((int)(getSampleMemCapacity()-memPos)/9*9,length); if (actualLength>0) { sampleOff[i]=memPos; diff --git a/src/engine/platform/vic20.cpp b/src/engine/platform/vic20.cpp index 52a08836..20ff0c8b 100644 --- a/src/engine/platform/vic20.cpp +++ b/src/engine/platform/vic20.cpp @@ -80,7 +80,7 @@ void DivPlatformVIC20::calcAndWriteOutVol(int ch, int env) { } void DivPlatformVIC20::writeOutVol(int ch) { - if (!isMuted[ch]) { + if (!isMuted[ch] && chan[ch].active) { rWrite(14,chan[ch].outVol); } } diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index d21f8739..3a81d25b 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -1581,7 +1581,11 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi if (disCont[i].lastAvail>0) { disCont[i].flush(disCont[i].lastAvail); } - disCont[i].runtotal=blip_clocks_needed(disCont[i].bb[0],size-disCont[i].lastAvail); + if (sizedisCont[i].bbInLen) { logV("growing dispatch %d bbIn to %d",i,disCont[i].runtotal+256); delete[] disCont[i].bbIn[0]; diff --git a/src/engine/sample.cpp b/src/engine/sample.cpp index 5460aed3..08dee993 100644 --- a/src/engine/sample.cpp +++ b/src/engine/sample.cpp @@ -52,14 +52,14 @@ void DivSample::putSampleData(SafeWriter* w) { w->writeI(centerRate); w->writeC(depth); w->writeC(loopMode); + w->writeC(brrEmphasis); w->writeC(0); // reserved - w->writeC(0); w->writeI(loop?loopStart:-1); w->writeI(loop?loopEnd:-1); - for (int i=0; i<4; i++) { + for (int i=0; iwriteI(out); @@ -125,17 +125,21 @@ DivDataErrors DivSample::readSampleData(SafeReader& reader, short version) { reader.readC(); } + if (version>=129) { + brrEmphasis=reader.readC(); + } else { + reader.readC(); + } // reserved reader.readC(); - reader.readC(); loopStart=reader.readI(); loopEnd=reader.readI(); loop=(loopStart>=0)&&(loopEnd>=0); - for (int i=0; i<4; i++) { + for (int i=0; i @@ -60,10 +61,10 @@ struct DivSampleHistory { unsigned int length, samples; DivSampleDepth depth; int rate, centerRate, loopStart, loopEnd; - bool loop; + bool loop, brrEmphasis; DivSampleLoopMode loopMode; bool hasSample; - DivSampleHistory(void* d, unsigned int l, unsigned int s, DivSampleDepth de, int r, int cr, int ls, int le, bool lp, DivSampleLoopMode lm): + DivSampleHistory(void* d, unsigned int l, unsigned int s, DivSampleDepth de, int r, int cr, int ls, int le, bool lp, bool be, DivSampleLoopMode lm): data((unsigned char*)d), length(l), samples(s), @@ -73,9 +74,10 @@ struct DivSampleHistory { loopStart(ls), loopEnd(le), loop(lp), + brrEmphasis(be), loopMode(lm), hasSample(true) {} - DivSampleHistory(DivSampleDepth de, int r, int cr, int ls, int le, bool lp, DivSampleLoopMode lm): + DivSampleHistory(DivSampleDepth de, int r, int cr, int ls, int le, bool lp, bool be, DivSampleLoopMode lm): data(NULL), length(0), samples(0), @@ -85,6 +87,7 @@ struct DivSampleHistory { loopStart(ls), loopEnd(le), loop(lp), + brrEmphasis(be), loopMode(lm), hasSample(false) {} ~DivSampleHistory(); @@ -106,14 +109,14 @@ struct DivSample { // - 10: VOX ADPCM // - 16: 16-bit PCM DivSampleDepth depth; - bool loop; + bool loop, brrEmphasis; // valid values are: // - 0: Forward loop // - 1: Backward loop // - 2: Pingpong loop DivSampleLoopMode loopMode; - bool renderOn[4][32]; + bool renderOn[DIV_MAX_SAMPLE_TYPE][DIV_MAX_CHIPS]; // these are the new data structures. signed char* data8; // 8 @@ -306,6 +309,7 @@ struct DivSample { loopOffP(0), depth(DIV_SAMPLE_DEPTH_16BIT), loop(false), + brrEmphasis(true), loopMode(DIV_SAMPLE_LOOP_FORWARD), data8(NULL), data16(NULL), @@ -328,11 +332,10 @@ struct DivSample { lengthBRR(0), lengthVOX(0), samples(0) { - for (int i=0; i<32; i++) { - renderOn[0][i]=true; - renderOn[1][i]=true; - renderOn[2][i]=true; - renderOn[3][i]=true; + for (int i=0; i> clearOuts=pat[i].optimize(); for (auto& j: clearOuts) { - for (int k=0; k<256; k++) { + for (int k=0; k> clearOuts=pat[i].rearrange(); for (auto& j: clearOuts) { - for (int k=0; k<256; k++) { + for (int k=0; k #include -#define DIV_MAX_CHANS 128 - +#include "defines.h" #include "../ta-utils.h" #include "config.h" #include "orders.h" @@ -230,11 +229,11 @@ struct DivSong { bool isDMF; // system - DivSystem system[32]; + DivSystem system[DIV_MAX_CHIPS]; unsigned char systemLen; - signed char systemVol[32]; - signed char systemPan[32]; - DivConfig systemFlags[32]; + signed char systemVol[DIV_MAX_CHIPS]; + signed char systemPan[DIV_MAX_CHIPS]; + DivConfig systemFlags[DIV_MAX_CHIPS]; // song information String name, author, systemName; @@ -428,7 +427,7 @@ struct DivSong { snNoLowPeriods(false), disableSampleMacro(false), autoSystem(true) { - for (int i=0; i<32; i++) { + for (int i=0; iwriteI(0); // will be written later w->writeI(version); - bool willExport[32]; - bool isSecond[32]; - int streamIDs[32]; + bool willExport[DIV_MAX_CHIPS]; + bool isSecond[DIV_MAX_CHIPS]; + int streamIDs[DIV_MAX_CHIPS]; double loopTimer[DIV_MAX_CHANS]; double loopFreq[DIV_MAX_CHANS]; int loopSample[DIV_MAX_CHANS]; bool sampleDir[DIV_MAX_CHANS]; std::vector chipVol; - std::vector delayedWrites[32]; + std::vector delayedWrites[DIV_MAX_CHIPS]; std::vector> sortedWrites; for (int i=0; i256 || min!=0 || max>255) { - logV("invalid len/min/max: %d %d %d",len,min,max); + if (len>256) { + logE("invalid len: %d",len); return DIV_DATA_INVALID_DATA; } + if (min!=0) { + logW("invalid min %d",min); + min=0; + } + + if (max>255) { + logW("invalid max %d",max); + max=255; + } + for (int i=0; i", "potatoTeto", "psxdominator", "Raijin", diff --git a/src/gui/dataList.cpp b/src/gui/dataList.cpp index face7424..0a5507ad 100644 --- a/src/gui/dataList.cpp +++ b/src/gui/dataList.cpp @@ -47,6 +47,9 @@ void FurnaceGUI::drawInsList(bool asChild) { if (ImGui::Button(ICON_FA_PLUS "##InsAdd")) { if (!settings.unifiedDataView) doAction(GUI_ACTION_INS_LIST_ADD); } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Add"); + } if (settings.unifiedDataView) { if (ImGui::BeginPopupContextItem("UnifiedAdd",ImGuiMouseButton_Left)) { if (ImGui::MenuItem("instrument")) { @@ -70,6 +73,9 @@ void FurnaceGUI::drawInsList(bool asChild) { if (ImGui::Button(ICON_FA_FILES_O "##InsClone")) { if (!settings.unifiedDataView) doAction(GUI_ACTION_INS_LIST_DUPLICATE); } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Duplicate"); + } if (settings.unifiedDataView) { if (ImGui::BeginPopupContextItem("UnifiedClone",ImGuiMouseButton_Left)) { if (ImGui::MenuItem("instrument")) { @@ -88,6 +94,9 @@ void FurnaceGUI::drawInsList(bool asChild) { if (ImGui::Button(ICON_FA_FOLDER_OPEN "##InsLoad")) { if (!settings.unifiedDataView) doAction(GUI_ACTION_INS_LIST_OPEN); } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Open"); + } if (settings.unifiedDataView) { if (ImGui::BeginPopupContextItem("UnifiedLoad",ImGuiMouseButton_Left)) { if (ImGui::MenuItem("instrument")) { @@ -127,6 +136,9 @@ void FurnaceGUI::drawInsList(bool asChild) { if (ImGui::Button(ICON_FA_FLOPPY_O "##InsSave")) { if (!settings.unifiedDataView) doAction(GUI_ACTION_INS_LIST_SAVE); } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Save"); + } if (settings.unifiedDataView) { if (ImGui::BeginPopupContextItem("UnifiedSave",ImGuiMouseButton_Left)) { if (ImGui::MenuItem("instrument")) { @@ -166,15 +178,24 @@ void FurnaceGUI::drawInsList(bool asChild) { if (ImGui::ArrowButton("InsUp",ImGuiDir_Up)) { doAction(GUI_ACTION_INS_LIST_MOVE_UP); } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Move up"); + } ImGui::SameLine(); if (ImGui::ArrowButton("InsDown",ImGuiDir_Down)) { doAction(GUI_ACTION_INS_LIST_MOVE_DOWN); } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Move down"); + } } ImGui::SameLine(); if (ImGui::Button(ICON_FA_TIMES "##InsDelete")) { if (!settings.unifiedDataView) doAction(GUI_ACTION_INS_LIST_DELETE); } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Delete"); + } if (settings.unifiedDataView) { if (ImGui::BeginPopupContextItem("UnifiedDelete",ImGuiMouseButton_Left)) { if (ImGui::MenuItem("instrument")) { @@ -521,14 +542,23 @@ void FurnaceGUI::drawWaveList(bool asChild) { if (ImGui::Button(ICON_FA_PLUS "##WaveAdd")) { doAction(GUI_ACTION_WAVE_LIST_ADD); } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Add"); + } ImGui::SameLine(); if (ImGui::Button(ICON_FA_FILES_O "##WaveClone")) { doAction(GUI_ACTION_WAVE_LIST_DUPLICATE); } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Duplicate"); + } ImGui::SameLine(); if (ImGui::Button(ICON_FA_FOLDER_OPEN "##WaveLoad")) { doAction(GUI_ACTION_WAVE_LIST_OPEN); } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Open"); + } if (ImGui::BeginPopupContextItem("WaveOpenOpt")) { if (ImGui::MenuItem("replace...")) { doAction((curWave>=0 && curWave<(int)e->song.wave.size())?GUI_ACTION_WAVE_LIST_OPEN_REPLACE:GUI_ACTION_WAVE_LIST_OPEN); @@ -539,6 +569,9 @@ void FurnaceGUI::drawWaveList(bool asChild) { if (ImGui::Button(ICON_FA_FLOPPY_O "##WaveSave")) { doAction(GUI_ACTION_WAVE_LIST_SAVE); } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Save"); + } if (!settings.unifiedDataView) { if (ImGui::BeginPopupContextItem("WaveSaveFormats",ImGuiMouseButton_Right)) { if (ImGui::MenuItem("save as .dmw...")) { @@ -554,14 +587,23 @@ void FurnaceGUI::drawWaveList(bool asChild) { if (ImGui::ArrowButton("WaveUp",ImGuiDir_Up)) { doAction(GUI_ACTION_WAVE_LIST_MOVE_UP); } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Move up"); + } ImGui::SameLine(); if (ImGui::ArrowButton("WaveDown",ImGuiDir_Down)) { doAction(GUI_ACTION_WAVE_LIST_MOVE_DOWN); } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Move down"); + } ImGui::SameLine(); if (ImGui::Button(ICON_FA_TIMES "##WaveDelete")) { doAction(GUI_ACTION_WAVE_LIST_DELETE); } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Delete"); + } ImGui::Separator(); if (ImGui::BeginTable("WaveListScroll",1,ImGuiTableFlags_ScrollY)) { actualWaveList(); @@ -598,14 +640,23 @@ void FurnaceGUI::drawSampleList(bool asChild) { if (ImGui::Button(ICON_FA_FILE "##SampleAdd")) { doAction(GUI_ACTION_SAMPLE_LIST_ADD); } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Add"); + } ImGui::SameLine(); if (ImGui::Button(ICON_FA_FILES_O "##SampleClone")) { doAction(GUI_ACTION_SAMPLE_LIST_DUPLICATE); } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Duplicate"); + } ImGui::SameLine(); if (ImGui::Button(ICON_FA_FOLDER_OPEN "##SampleLoad")) { doAction(GUI_ACTION_SAMPLE_LIST_OPEN); } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Open"); + } if (ImGui::BeginPopupContextItem("SampleOpenOpt")) { if (ImGui::MenuItem("replace...")) { doAction((curSample>=0 && curSample<(int)e->song.sample.size())?GUI_ACTION_SAMPLE_LIST_OPEN_REPLACE:GUI_ACTION_SAMPLE_LIST_OPEN); @@ -623,26 +674,44 @@ void FurnaceGUI::drawSampleList(bool asChild) { if (ImGui::Button(ICON_FA_FLOPPY_O "##SampleSave")) { doAction(GUI_ACTION_SAMPLE_LIST_SAVE); } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Save"); + } ImGui::SameLine(); if (ImGui::ArrowButton("SampleUp",ImGuiDir_Up)) { doAction(GUI_ACTION_SAMPLE_LIST_MOVE_UP); } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Move up"); + } ImGui::SameLine(); if (ImGui::ArrowButton("SampleDown",ImGuiDir_Down)) { doAction(GUI_ACTION_SAMPLE_LIST_MOVE_DOWN); } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Move down"); + } ImGui::SameLine(); if (ImGui::Button(ICON_FA_TIMES "##SampleDelete")) { doAction(GUI_ACTION_SAMPLE_LIST_DELETE); } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Delete"); + } ImGui::SameLine(); if (ImGui::Button(ICON_FA_VOLUME_UP "##PreviewSampleL")) { doAction(GUI_ACTION_SAMPLE_LIST_PREVIEW); } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Preview"); + } ImGui::SameLine(); if (ImGui::Button(ICON_FA_VOLUME_OFF "##StopSampleL")) { doAction(GUI_ACTION_SAMPLE_LIST_STOP_PREVIEW); } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Stop preview"); + } ImGui::Separator(); if (ImGui::BeginTable("SampleListScroll",1,ImGuiTableFlags_ScrollY)) { actualSampleList(); @@ -684,20 +753,48 @@ void FurnaceGUI::actualWaveList() { void FurnaceGUI::actualSampleList() { for (int i=0; i<(int)e->song.sample.size(); i++) { + bool memWarning=false; + DivSample* sample=e->song.sample[i]; ImGui::TableNextRow(); ImGui::TableNextColumn(); + for (int j=0; jsong.systemLen; j++) { + DivDispatch* dispatch=e->getDispatch(j); + if (dispatch==NULL) continue; + + for (int k=0; kgetSampleMemCapacity(k)==0) continue; + if (!dispatch->isSampleLoaded(k,i) && sample->renderOn[k][j]) { + memWarning=true; + break; + } + } + if (memWarning) break; + } + if (memWarning) ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_SAMPLE_CHIP_WARNING]); if (ImGui::Selectable(fmt::sprintf("%d: %s##_SAM%d",i,sample->name,i).c_str(),curSample==i)) { curSample=i; samplePos=0; updateSampleTex=true; } - if (wantScrollList && curSample==i) ImGui::SetScrollHereY(); if (ImGui::IsItemHovered() && !mobileUI) { + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_TEXT]); ImGui::SetTooltip("Bank %d: %s",i/12,sampleNote[i%12]); if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { sampleEditOpen=true; } + ImGui::PopStyleColor(); } + if (memWarning) { + ImGui::SameLine(); + ImGui::Text(ICON_FA_EXCLAMATION_TRIANGLE); + if (ImGui::IsItemHovered() && !mobileUI) { + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_TEXT]); + ImGui::SetTooltip("out of memory for this sample!"); + ImGui::PopStyleColor(); + } + ImGui::PopStyleColor(); + } + if (wantScrollList && curSample==i) ImGui::SetScrollHereY(); } } diff --git a/src/gui/doAction.cpp b/src/gui/doAction.cpp index 14fb4466..89c61564 100644 --- a/src/gui/doAction.cpp +++ b/src/gui/doAction.cpp @@ -174,6 +174,9 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_PANIC: e->syncReset(); break; + case GUI_ACTION_CLEAR: + showWarning("Are you sure you want to clear... (cannot be undone!)",GUI_WARN_CLEAR); + break; case GUI_ACTION_WINDOW_EDIT_CONTROLS: nextWindow=GUI_WINDOW_EDIT_CONTROLS; @@ -522,7 +525,7 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_PAT_INCREASE_COLUMNS: if (cursor.xCoarse<0 || cursor.xCoarse>=e->getTotalChannelCount()) break; e->curPat[cursor.xCoarse].effectCols++; - if (e->curPat[cursor.xCoarse].effectCols>8) e->curPat[cursor.xCoarse].effectCols=8; + if (e->curPat[cursor.xCoarse].effectCols>DIV_MAX_EFFECTS) e->curPat[cursor.xCoarse].effectCols=DIV_MAX_EFFECTS; break; case GUI_ACTION_PAT_DECREASE_COLUMNS: if (cursor.xCoarse<0 || cursor.xCoarse>=e->getTotalChannelCount()) break; @@ -554,6 +557,10 @@ void FurnaceGUI::doAction(int what) { break; case GUI_ACTION_PAT_LATCH: // TODO break; + case GUI_ACTION_PAT_SCROLL_MODE: // TODO + break; + case GUI_ACTION_PAT_CLEAR_LATCH: // TODO + break; case GUI_ACTION_INS_LIST_ADD: curIns=e->addInstrument(cursor.xCoarse); diff --git a/src/gui/editControls.cpp b/src/gui/editControls.cpp index a7582ff3..b10c85ea 100644 --- a/src/gui/editControls.cpp +++ b/src/gui/editControls.cpp @@ -19,31 +19,317 @@ #include "gui.h" #include "IconsFontAwesome4.h" -#include +#include + +// 0: all directions +// 1: half +// 2: half +// 3: quarter +const float mobileButtonAngles[4][8]={ + {0.0, 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875}, + {0.8333, 0.0, 0.1667, 0.8, 0.9, 0.0, 0.1, 0.2}, + {0.0833, 0.25, 0.4167, 0.45, 0.35, 0.25, 0.15, 0.05}, + {0.25, 0.125, 0.0, 0.25, 0.1875, 0.125, 0.0625, 0.0} +}; + +const float mobileButtonDistances[4][8]={ + {1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0}, + {0.8, 0.75, 0.8, 1.5, 1.5, 1.5, 1.5, 1.5}, + {0.8, 0.75, 0.8, 1.5, 1.5, 1.5, 1.5, 1.5}, + {0.9, 1.0, 0.9, 1.78, 1.82, 1.95, 1.82, 1.78} +}; + +const char* mobileButtonLabels[32]={ + // page 1 + "cut", + "copy", + "paste", + "delete", + "select\nall", + "piano", + "undo", + "redo", + + // page 2 + "paste\nmix", + "paste\nmix bg", + "paste\nins", + "paste\nins bg", + "paste\nflood", + "paste\noverflow", + "transpose\nnotes", + "transpose\nvalues", + + // page 3 + "change\nins", + "find/\nreplace", + "collapse", + "expand", + "flip", + "invert", + "interpolate", + "scale", + + // page 4 + "fade", + "randomize", + "opmask", + "scroll\nmode", + "input\nlatch", + "set\nlatch", + "clear\nlatch", + "clear" +}; + +const int mobileButtonActions[32]={ + // page 1 + GUI_ACTION_PAT_CUT, + GUI_ACTION_PAT_COPY, + GUI_ACTION_PAT_PASTE, + GUI_ACTION_PAT_DELETE, + GUI_ACTION_PAT_SELECT_ALL, + 0, + GUI_ACTION_UNDO, + GUI_ACTION_REDO, + + // page 2 + GUI_ACTION_PAT_PASTE_MIX, + GUI_ACTION_PAT_PASTE_MIX_BG, + 0, + 0, + GUI_ACTION_PAT_PASTE_FLOOD, + GUI_ACTION_PAT_PASTE_OVERFLOW, + 0, + 0, + + // page 3 + 0, + GUI_ACTION_WINDOW_FIND, + GUI_ACTION_PAT_COLLAPSE_ROWS, + GUI_ACTION_PAT_EXPAND_ROWS, + GUI_ACTION_PAT_FLIP_SELECTION, + GUI_ACTION_PAT_INVERT_VALUES, + GUI_ACTION_PAT_INTERPOLATE, + 0, + + // page 4 + GUI_ACTION_PAT_FADE, + 0, + 0, + GUI_ACTION_PAT_SCROLL_MODE, + 0, + GUI_ACTION_PAT_LATCH, + GUI_ACTION_PAT_CLEAR_LATCH, + GUI_ACTION_CLEAR +}; + +const bool mobileButtonPersist[32]={ + // page 1 + false, + false, + false, + false, + true, + true, + true, + true, + + // page 2 + false, + false, + false, + false, + false, + false, + false, + false, + + // page 3 + false, + false, + false, + false, + false, + false, + false, + false, + + // page 4 + false, + false, + false, + true, + false, + false, + false, + false, +}; void FurnaceGUI::drawMobileControls() { float timeScale=1.0f/(60.0f*ImGui::GetIO().DeltaTime); - if (mobileMenuOpen) { - if (mobileMenuPos<0.999f) { - WAKE_UP; - mobileMenuPos+=MIN(0.1,(1.0-mobileMenuPos)*0.65)*timeScale; + if (dragMobileMenu) { + if (portrait) { + mobileMenuPos=(dragMobileMenuOrigin.y-ImGui::GetMousePos().y)/(canvasH*0.65); } else { - mobileMenuPos=1.0f; + mobileMenuPos=(ImGui::GetMousePos().x-dragMobileMenuOrigin.x)/(canvasW*0.65); } + if (mobileMenuPos<0.0f) mobileMenuPos=0.0f; + if (mobileMenuPos>1.0f) mobileMenuPos=1.0f; } else { - if (mobileMenuPos>0.001f) { - WAKE_UP; - mobileMenuPos-=MIN(0.1,mobileMenuPos*0.65)*timeScale; + if (mobileMenuOpen) { + if (mobileMenuPos<0.999f) { + WAKE_UP; + mobileMenuPos+=MIN(0.1,(1.0-mobileMenuPos)*0.65)*timeScale; + } else { + mobileMenuPos=1.0f; + } } else { - mobileMenuPos=0.0f; + if (mobileMenuPos>0.001f) { + WAKE_UP; + mobileMenuPos-=MIN(0.1,mobileMenuPos*0.65)*timeScale; + } else { + mobileMenuPos=0.0f; + } } } + + if (dragMobileEditButton) { + if (ImGui::GetIO().MouseDragMaxDistanceSqr[ImGuiMouseButton_Left]>ImGui::GetIO().ConfigInertialScrollToleranceSqr) { + mobileEditButtonPos.x=((ImGui::GetMousePos().x/canvasW)-((portrait?0.16*canvasW:0.16*canvasH)/2)/canvasW); + mobileEditButtonPos.y=((ImGui::GetMousePos().y/canvasH)-((portrait?0.16*canvasW:0.16*canvasH)/2)/canvasH); + } + } + + if (mobileEditButtonPos.x<0) mobileEditButtonPos.x=0; + if (mobileEditButtonPos.x>1) mobileEditButtonPos.x=1; + if (mobileEditButtonPos.y<0) mobileEditButtonPos.y=0; + if (mobileEditButtonPos.y>1) mobileEditButtonPos.y=1; + + if (mobileEdit) { + mobileEditAnim+=ImGui::GetIO().DeltaTime*2.4; + if (mobileEditAnim>1.0f) { + mobileEditAnim=1.0f; + } else { + WAKE_UP; + } + } else { + mobileEditAnim-=ImGui::GetIO().DeltaTime*2.4; + if (mobileEditAnim<0.0f) { + mobileEditAnim=0.0f; + } else { + WAKE_UP; + } + } + + if (curWindowLast==GUI_WINDOW_PATTERN) { + if (mobileEditAnim>0.0f) { + ImGui::SetNextWindowPos(ImVec2(0.0f,0.0f)); + ImGui::SetNextWindowSize(ImVec2(canvasW,canvasH)); + } else { + ImGui::SetNextWindowPos(ImVec2((mobileEditButtonPos.x+(portrait?0:(mobileMenuPos*0.65)))*canvasW,(mobileEditButtonPos.y-(portrait?(mobileMenuPos*0.65):0))*canvasH)); + ImGui::SetNextWindowSize(portrait?ImVec2(0.16*canvasW,0.16*canvasW):ImVec2(0.16*canvasH,0.16*canvasH)); + } + + ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding,ImVec2(0.0f,0.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding,mobileEditButtonSize.x); + if (ImGui::Begin("MobileEdit",NULL,ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoScrollWithMouse|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoBackground|ImGuiWindowFlags_NoDecoration)) { + bool mobileEditWas=mobileEdit; + if (ImGui::IsMouseReleased(ImGuiMouseButton_Left) && mobileEdit) { + mobileEdit=false; + } + + if (mobileEditAnim>0.0f) { + int curButtonPos=0; + float buttonDir, buttonDist; + float buttonMirrorX=1.0f; + float buttonMirrorY=1.0f; + + int buttonLayout=0; + + ImVec2 scaledButtonPos=ImVec2( + mobileEditButtonPos.x+((mobileEditButtonSize.x*0.5)/(float)canvasW), + mobileEditButtonPos.y+((mobileEditButtonSize.y*0.5)/(float)canvasH) + ); + + if (scaledButtonPos.x>0.25 && + scaledButtonPos.x<0.75 && + scaledButtonPos.y>0.2 && + scaledButtonPos.y<0.8) { + buttonLayout=0; + } else if (scaledButtonPos.x>0.4 && scaledButtonPos.x<0.6) { + buttonLayout=2; + } else if (scaledButtonPos.y>0.25 && scaledButtonPos.y<0.75) { + buttonLayout=1; + } else { + buttonLayout=3; + } + + switch (buttonLayout) { + case 1: + if (mobileEditButtonPos.x>0.5) buttonMirrorX=-1.0f; + break; + case 2: + if (mobileEditButtonPos.y>0.5) buttonMirrorY=-1.0f; + break; + case 3: + if (mobileEditButtonPos.x>0.5) buttonMirrorX=-1.0f; + if (mobileEditButtonPos.y>0.5) buttonMirrorY=-1.0f; + break; + } + + for (int i=0; i<8; i++) { + float anim=(mobileEditAnim*1.5)-(float)i*0.05; + if (anim<0.0f) anim=0.0f; + if (anim>1.0f) anim=1.0f; + anim=5*anim-7*pow(anim,2.0f)+3*pow(anim,3.0f); + + buttonDir=mobileButtonAngles[buttonLayout][curButtonPos]; + buttonDist=mobileButtonDistances[buttonLayout][curButtonPos]*mobileEditButtonSize.x*1.6f; + + ImGui::SetCursorPos(ImVec2( + (mobileEditButtonPos.x*canvasW)+cos(buttonDir*2.0*M_PI)*buttonDist*buttonMirrorX*anim, + (mobileEditButtonPos.y*canvasH)+sin(buttonDir*2.0*M_PI)*buttonDist*buttonMirrorY*anim + )); + if (ImGui::Button(mobileButtonLabels[i+mobileEditPage*8],mobileEditButtonSize)) { + if (mobileButtonActions[i+mobileEditPage*8]) { + doAction(mobileButtonActions[i+mobileEditPage*8]); + } + if (mobileButtonPersist[i+mobileEditPage*8]) { + if (mobileMenuPos<=0.0) mobileEdit=true; + } + } + + curButtonPos++; + } + + ImGui::SetCursorPos(ImVec2(mobileEditButtonPos.x*canvasW,mobileEditButtonPos.y*canvasH)); + } else { + float avail=portrait?ImGui::GetContentRegionAvail().y:ImGui::GetContentRegionAvail().x; + mobileEditButtonSize=ImVec2(avail,avail); + } + + if (ImGui::Button(ICON_FA_PENCIL "##Edit",mobileEditButtonSize)) { + // click + if (mobileEditWas) { + if (++mobileEditPage>3) mobileEditPage=0; + } + if (ImGui::GetIO().MouseDragMaxDistanceSqr[ImGuiMouseButton_Left]<=ImGui::GetIO().ConfigInertialScrollToleranceSqr) { + if (mobileMenuPos<=0.0) mobileEdit=true; + } + } + if (ImGui::IsItemClicked() && !mobileEdit && mobileMenuPos<=0.0) { + dragMobileEditButton=true; + } + } + ImGui::End(); + ImGui::PopStyleVar(2); + } + ImGui::SetNextWindowPos(portrait?ImVec2(0.0f,((1.0-mobileMenuPos*0.65)*canvasH)-(0.16*canvasW)):ImVec2(0.5*canvasW*mobileMenuPos,0.0f)); ImGui::SetNextWindowSize(portrait?ImVec2(canvasW,0.16*canvasW):ImVec2(0.16*canvasH,canvasH)); if (ImGui::Begin("Mobile Controls",NULL,ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoScrollWithMouse|globalWinFlags)) { float avail=portrait?ImGui::GetContentRegionAvail().y:ImGui::GetContentRegionAvail().x; ImVec2 buttonSize=ImVec2(avail,avail); - const char* mobButtonName=ICON_FA_CHEVRON_RIGHT "##MobileMenu"; if (portrait) mobButtonName=ICON_FA_CHEVRON_UP "##MobileMenu"; if (mobileMenuOpen) { @@ -54,7 +340,20 @@ void FurnaceGUI::drawMobileControls() { } } if (ImGui::Button(mobButtonName,buttonSize)) { - mobileMenuOpen=!mobileMenuOpen; + if (!dragMobileMenu) { + mobileMenuOpen=!mobileMenuOpen; + } + } + if (ImGui::IsItemActive() && ImGui::GetIO().MouseDragMaxDistanceSqr[ImGuiMouseButton_Left]>ImGui::GetIO().ConfigInertialScrollToleranceSqr*2.0f) { + if (!dragMobileMenu) { + dragMobileMenu=true; + dragMobileMenuOrigin=ImGui::GetMousePos(); + if (portrait) { + dragMobileMenuOrigin.y+=mobileMenuPos*canvasH*0.65f; + } else { + dragMobileMenuOrigin.x-=mobileMenuPos*canvasW*0.65f; + } + } } if (!portrait) ImGui::Separator(); @@ -321,11 +620,17 @@ void FurnaceGUI::drawEditControls() { if (ImGui::Button(ICON_FA_PLAY "##Play")) { play(); } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Play"); + } popToggleColors(); ImGui::SameLine(); if (ImGui::Button(ICON_FA_STOP "##Stop")) { stop(); } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Stop"); + } ImGui::SameLine(); ImGui::Checkbox("Edit",&edit); ImGui::SameLine(); @@ -349,6 +654,9 @@ void FurnaceGUI::drawEditControls() { e->stepOne(cursor.y); pendingStepUpdate=true; } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Step one row"); + } ImGui::SameLine(); pushToggleColors(noteInputPoly); @@ -356,6 +664,9 @@ void FurnaceGUI::drawEditControls() { noteInputPoly=!noteInputPoly; e->setAutoNotePoly(noteInputPoly); } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Polyphony"); + } popToggleColors(); } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS; @@ -366,17 +677,26 @@ void FurnaceGUI::drawEditControls() { if (ImGui::Button(ICON_FA_STOP "##Stop")) { stop(); } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Stop"); + } ImGui::SameLine(); pushToggleColors(e->isPlaying()); if (ImGui::Button(ICON_FA_PLAY "##Play")) { play(); } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Play"); + } popToggleColors(); ImGui::SameLine(); if (ImGui::Button(ICON_FA_ARROW_DOWN "##StepOne")) { e->stepOne(cursor.y); pendingStepUpdate=true; } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Step one row"); + } ImGui::SameLine(); bool repeatPattern=e->getRepeatPattern(); @@ -384,6 +704,9 @@ void FurnaceGUI::drawEditControls() { if (ImGui::Button(ICON_FA_REPEAT "##RepeatPattern")) { e->setRepeatPattern(!repeatPattern); } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Repeat pattern"); + } popToggleColors(); ImGui::SameLine(); @@ -391,6 +714,9 @@ void FurnaceGUI::drawEditControls() { if (ImGui::Button(ICON_FA_CIRCLE "##Edit")) { edit=!edit; } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Edit"); + } popToggleColors(); ImGui::SameLine(); @@ -399,6 +725,9 @@ void FurnaceGUI::drawEditControls() { if (ImGui::Button(ICON_FA_BELL_O "##Metronome")) { e->setMetronome(!metro); } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Metronome"); + } popToggleColors(); ImGui::SameLine(); @@ -441,6 +770,9 @@ void FurnaceGUI::drawEditControls() { noteInputPoly=!noteInputPoly; e->setAutoNotePoly(noteInputPoly); } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Polyphony"); + } popToggleColors(); } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS; @@ -453,26 +785,41 @@ void FurnaceGUI::drawEditControls() { if (ImGui::Button(ICON_FA_PLAY "##Play",buttonSize)) { play(); } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Play"); + } popToggleColors(); if (ImGui::Button(ICON_FA_STOP "##Stop",buttonSize)) { stop(); } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Stop"); + } if (ImGui::Button(ICON_FA_ARROW_DOWN "##StepOne",buttonSize)) { e->stepOne(cursor.y); pendingStepUpdate=true; } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Step one row"); + } bool repeatPattern=e->getRepeatPattern(); pushToggleColors(repeatPattern); if (ImGui::Button(ICON_FA_REPEAT "##RepeatPattern",buttonSize)) { e->setRepeatPattern(!repeatPattern); } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Repeat pattern"); + } popToggleColors(); pushToggleColors(edit); if (ImGui::Button(ICON_FA_CIRCLE "##Edit",buttonSize)) { edit=!edit; } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Edit"); + } popToggleColors(); bool metro=e->getMetronome(); @@ -480,9 +827,15 @@ void FurnaceGUI::drawEditControls() { if (ImGui::Button(ICON_FA_BELL_O "##Metronome",buttonSize)) { e->setMetronome(!metro); } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Metronome"); + } popToggleColors(); ImGui::Text("Oct."); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Octave"); + } float avail=ImGui::GetContentRegionAvail().x; ImGui::SetNextItemWidth(avail); if (ImGui::InputInt("##Octave",&curOctave,0,0)) { @@ -507,15 +860,24 @@ void FurnaceGUI::drawEditControls() { } ImGui::Text("Foll."); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Follow"); + } pushToggleColors(followOrders); if (ImGui::Button("Ord##FollowOrders",buttonSize)) { handleUnimportant followOrders=!followOrders; } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Orders"); + } popToggleColors(); pushToggleColors(followPattern); if (ImGui::Button("Pat##FollowPattern",buttonSize)) { handleUnimportant followPattern=!followPattern; } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Pattern"); + } popToggleColors(); pushToggleColors(noteInputPoly); @@ -523,6 +885,9 @@ void FurnaceGUI::drawEditControls() { noteInputPoly=!noteInputPoly; e->setAutoNotePoly(noteInputPoly); } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Polyphony"); + } popToggleColors(); } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS; @@ -535,33 +900,51 @@ void FurnaceGUI::drawEditControls() { if (ImGui::Button(ICON_FA_STOP "##Stop")) { stop(); } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Stop"); + } popToggleColors(); } else { if (ImGui::Button(ICON_FA_PLAY "##Play")) { play(oldRow); } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Play"); + } } ImGui::SameLine(); if (ImGui::Button(ICON_FA_PLAY_CIRCLE "##PlayAgain")) { e->setRepeatPattern(false); play(); } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Play from the beginning of this pattern"); + } ImGui::SameLine(); if (ImGui::Button(ICON_FA_STEP_FORWARD "##PlayRepeat")) { e->setRepeatPattern(true); play(); } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Repeat from the beginning of this pattern"); + } ImGui::SameLine(); if (ImGui::Button(ICON_FA_ARROW_DOWN "##StepOne")) { e->stepOne(cursor.y); pendingStepUpdate=true; } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Step one row"); + } ImGui::SameLine(); pushToggleColors(edit); if (ImGui::Button(ICON_FA_CIRCLE "##Edit")) { edit=!edit; } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Edit"); + } popToggleColors(); bool metro=e->getMetronome(); @@ -570,6 +953,9 @@ void FurnaceGUI::drawEditControls() { if (ImGui::Button(ICON_FA_BELL_O "##Metronome")) { e->setMetronome(!metro); } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Metronome"); + } popToggleColors(); ImGui::SameLine(); @@ -578,6 +964,9 @@ void FurnaceGUI::drawEditControls() { if (ImGui::Button(ICON_FA_REPEAT "##RepeatPattern")) { e->setRepeatPattern(!repeatPattern); } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Repeat pattern"); + } popToggleColors(); ImGui::SameLine(); @@ -586,6 +975,9 @@ void FurnaceGUI::drawEditControls() { noteInputPoly=!noteInputPoly; e->setAutoNotePoly(noteInputPoly); } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Polyphony"); + } popToggleColors(); } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_EDIT_CONTROLS; diff --git a/src/gui/editing.cpp b/src/gui/editing.cpp index 17d2886a..f18efece 100644 --- a/src/gui/editing.cpp +++ b/src/gui/editing.cpp @@ -119,7 +119,7 @@ void FurnaceGUI::makeUndo(ActionType action) { for (int i=0; igetTotalChannelCount(); i++) { DivPattern* p=e->curPat[i].getPattern(e->curOrders->ord[i][curOrder],false); for (int j=0; jcurSubSong->patLen; j++) { - for (int k=0; k<32; k++) { + for (int k=0; kdata[j][k]!=oldPat[i]->data[j][k]) { s.pat.push_back(UndoPatternData(subSong,i,e->curOrders->ord[i][curOrder],j,k,oldPat[i]->data[j][k],p->data[j][k])); } diff --git a/src/gui/findReplace.cpp b/src/gui/findReplace.cpp index 91b49421..e66108cf 100644 --- a/src/gui/findReplace.cpp +++ b/src/gui/findReplace.cpp @@ -220,20 +220,20 @@ void FurnaceGUI::doReplace() { UndoStep us; us.type=GUI_UNDO_REPLACE; - short prevVal[32]; - memset(prevVal,0,32*sizeof(short)); + short prevVal[DIV_MAX_COLS]; + memset(prevVal,0,DIV_MAX_COLS*sizeof(short)); for (FurnaceGUIQueryResult& i: curQueryResults) { int patIndex=e->song.subsong[i.subsong]->orders.ord[i.x][i.order]; DivPattern* p=e->song.subsong[i.subsong]->pat[i.x].getPattern(patIndex,true); if (touched[i.x]==NULL) { - touched[i.x]=new bool[256*256]; - memset(touched[i.x],0,256*256*sizeof(bool)); + touched[i.x]=new bool[DIV_MAX_PATTERNS*DIV_MAX_ROWS]; + memset(touched[i.x],0,DIV_MAX_PATTERNS*DIV_MAX_ROWS*sizeof(bool)); } if (touched[i.x][(patIndex<<8)|i.y]) continue; touched[i.x][(patIndex<<8)|i.y]=true; - memcpy(prevVal,p->data[i.y],32*sizeof(short)); + memcpy(prevVal,p->data[i.y],DIV_MAX_COLS*sizeof(short)); if (queryReplaceNoteDo) { switch (queryReplaceNoteMode) { @@ -462,7 +462,7 @@ void FurnaceGUI::doReplace() { } // issue undo step - for (int j=0; j<32; j++) { + for (int j=0; jdata[i.y][j]!=prevVal[j]) { us.pat.push_back(UndoPatternData(i.subsong,i.x,patIndex,i.y,j,prevVal[j],p->data[i.y][j])); } diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index f0d96ee2..6753fdfd 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1040,13 +1040,13 @@ void FurnaceGUI::noteInput(int num, int key, int vol) { prepareUndo(GUI_UNDO_PATTERN_EDIT); - if (key==100) { // note off + if (key==GUI_NOTE_OFF) { // note off pat->data[cursor.y][0]=100; pat->data[cursor.y][1]=0; - } else if (key==101) { // note off + env release + } else if (key==GUI_NOTE_OFF_RELEASE) { // note off + env release pat->data[cursor.y][0]=101; pat->data[cursor.y][1]=0; - } else if (key==102) { // env release only + } else if (key==GUI_NOTE_RELEASE) { // env release only pat->data[cursor.y][0]=102; pat->data[cursor.y][1]=0; } else { @@ -2931,6 +2931,17 @@ void FurnaceGUI::pointUp(int x, int y, int button) { } orderScrollLocked=false; orderScrollTolerance=false; + if (dragMobileMenu) { + dragMobileMenu=false; + if (mobileMenuOpen) { + mobileMenuOpen=(mobileMenuPos>=0.85f); + } else { + mobileMenuOpen=(mobileMenuPos>=0.15f); + } + } + if (dragMobileEditButton) { + dragMobileEditButton=false; + } if (selecting) { if (!selectingFull) cursor=selEnd; finishSelection(); @@ -3616,7 +3627,7 @@ bool FurnaceGUI::loop() { editOptions(true); ImGui::Separator(); if (ImGui::MenuItem("clear...")) { - showWarning("Are you sure you want to clear... (cannot be undone!)",GUI_WARN_CLEAR); + doAction(GUI_ACTION_CLEAR); } ImGui::EndMenu(); } @@ -3771,7 +3782,6 @@ bool FurnaceGUI::loop() { globalWinFlags=ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoBringToFrontOnFocus; //globalWinFlags=ImGuiWindowFlags_NoTitleBar; // scene handling goes here! - pianoOpen=true; drawMobileControls(); switch (mobScene) { case GUI_SCENE_ORDERS: @@ -3813,6 +3823,9 @@ bool FurnaceGUI::loop() { drawPattern(); drawPiano(); drawMobileOrderSel(); + + globalWinFlags=0; + drawFindReplace(); break; } @@ -3820,6 +3833,7 @@ bool FurnaceGUI::loop() { drawSettings(); drawDebug(); drawLog(); + drawStats(); } else { globalWinFlags=0; ImGui::DockSpaceOverViewport(NULL,lockLayout?(ImGuiDockNodeFlags_NoWindowMenuButton|ImGuiDockNodeFlags_NoMove|ImGuiDockNodeFlags_NoResize|ImGuiDockNodeFlags_NoCloseButton|ImGuiDockNodeFlags_NoDocking|ImGuiDockNodeFlags_NoDockingSplitMe|ImGuiDockNodeFlags_NoDockingSplitOther):0); @@ -4755,7 +4769,7 @@ bool FurnaceGUI::loop() { if (ImGui::Button("Orders")) { stop(); e->lockEngine([this]() { - memset(e->curOrders->ord,0,DIV_MAX_CHANS*256); + memset(e->curOrders->ord,0,DIV_MAX_CHANS*DIV_MAX_PATTERNS); e->curSubSong->ordersLen=1; }); e->setOrder(0); @@ -4771,8 +4785,8 @@ bool FurnaceGUI::loop() { e->lockEngine([this]() { for (int i=0; igetTotalChannelCount(); i++) { DivPattern* pat=e->curPat[i].getPattern(e->curOrders->ord[i][curOrder],true); - memset(pat->data,-1,256*32*sizeof(short)); - for (int j=0; j<256; j++) { + memset(pat->data,-1,DIV_MAX_ROWS*DIV_MAX_COLS*sizeof(short)); + for (int j=0; jdata[j][0]=0; pat->data[j][1]=0; } @@ -5139,7 +5153,11 @@ bool FurnaceGUI::init() { volMeterOpen=e->getConfBool("volMeterOpen",true); statsOpen=e->getConfBool("statsOpen",false); compatFlagsOpen=e->getConfBool("compatFlagsOpen",false); +#ifdef IS_MOBILE + pianoOpen=e->getConfBool("pianoOpen",true); +#else pianoOpen=e->getConfBool("pianoOpen",false); +#endif notesOpen=e->getConfBool("notesOpen",false); channelsOpen=e->getConfBool("channelsOpen",false); patManagerOpen=e->getConfBool("patManagerOpen",false); @@ -5648,14 +5666,19 @@ FurnaceGUI::FurnaceGUI(): pendingInsSingle(false), displayPendingRawSample(false), snesFilterHex(false), + mobileEdit(false), vgmExportVersion(0x171), drawHalt(10), zsmExportTickRate(60), macroPointSize(16), waveEditStyle(0), displayInsTypeListMakeInsSample(-1), + mobileEditPage(0), mobileMenuPos(0.0f), autoButtonSize(0.0f), + mobileEditAnim(0.0f), + mobileEditButtonPos(0.7f,0.7f), + mobileEditButtonSize(60.0f,60.0f), curSysSection(NULL), pendingRawSampleDepth(8), pendingRawSampleChannels(1), @@ -5785,12 +5808,15 @@ FurnaceGUI::FurnaceGUI(): keepLoopAlive(false), orderScrollLocked(false), orderScrollTolerance(false), + dragMobileMenu(false), + dragMobileEditButton(false), curWindow(GUI_WINDOW_NOTHING), nextWindow(GUI_WINDOW_NOTHING), curWindowLast(GUI_WINDOW_NOTHING), curWindowThreadSafe(GUI_WINDOW_NOTHING), lastPatternWidth(0.0f), longThreshold(0.48f), + buttonLongThreshold(0.20f), latchNote(-1), latchIns(-2), latchVol(-1), @@ -5877,6 +5903,7 @@ FurnaceGUI::FurnaceGUI(): orderScroll(0.0f), orderScrollSlideOrigin(0.0f), orderScrollRealOrigin(0.0f,0.0f), + dragMobileMenuOrigin(0.0f,0.0f), layoutTimeBegin(0), layoutTimeEnd(0), layoutTimeDelta(0), @@ -5913,6 +5940,7 @@ FurnaceGUI::FurnaceGUI(): sampleSelStart(-1), sampleSelEnd(-1), sampleInfo(true), + sampleCompatRate(false), sampleDragActive(false), sampleDragMode(false), sampleDrag16(false), @@ -5959,8 +5987,8 @@ FurnaceGUI::FurnaceGUI(): pianoOptionsSet(false), pianoOffset(6), pianoOffsetEdit(9), - pianoView(2), - pianoInputPadMode(2), + pianoView(PIANO_LAYOUT_AUTOMATIC), + pianoInputPadMode(PIANO_INPUT_PAD_SPLIT_AUTO), #else pianoOctaves(7), pianoOctavesEdit(4), @@ -5968,8 +5996,8 @@ FurnaceGUI::FurnaceGUI(): pianoSharePosition(true), pianoOffset(6), pianoOffsetEdit(6), - pianoView(0), - pianoInputPadMode(0), + pianoView(PIANO_LAYOUT_STANDARD), + pianoInputPadMode(PIANO_INPUT_PAD_DISABLE), #endif hasACED(false), waveGenBaseShape(0), @@ -6012,7 +6040,7 @@ FurnaceGUI::FurnaceGUI(): valueKeys[SDLK_KP_8]=8; valueKeys[SDLK_KP_9]=9; - memset(willExport,1,32*sizeof(bool)); + memset(willExport,1,DIV_MAX_CHIPS*sizeof(bool)); peak[0]=0; peak[1]=0; diff --git a/src/gui/gui.h b/src/gui/gui.h index 8d233f9a..ec8f8b7f 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -54,7 +54,7 @@ } #define CHECK_LONG_HOLD (mobileUI && ImGui::GetIO().MouseDown[ImGuiMouseButton_Left] && ImGui::GetIO().MouseDownDuration[ImGuiMouseButton_Left]>=longThreshold && ImGui::GetIO().MouseDownDurationPrev[ImGuiMouseButton_Left]=buttonLongThreshold && ImGui::GetIO().MouseDownDurationPrev[ImGuiMouseButton_Left] curWindowThreadSafe; float peak[2]; float patChanX[DIV_MAX_CHANS+1]; float patChanSlideY[DIV_MAX_CHANS+1]; float lastPatternWidth, longThreshold; + float buttonLongThreshold; String nextDesc; String nextDescName; @@ -1581,6 +1594,7 @@ class FurnaceGUI { float nextScroll, nextAddScroll, orderScroll, orderScrollSlideOrigin; ImVec2 orderScrollRealOrigin; + ImVec2 dragMobileMenuOrigin; int layoutTimeBegin, layoutTimeEnd, layoutTimeDelta; int renderTimeBegin, renderTimeEnd, renderTimeDelta; @@ -1615,7 +1629,7 @@ class FurnaceGUI { int resampleStrat; float amplifyVol; int sampleSelStart, sampleSelEnd; - bool sampleInfo; + bool sampleInfo, sampleCompatRate; bool sampleDragActive, sampleDragMode, sampleDrag16, sampleZoomAuto; void* sampleDragTarget; ImVec2 sampleDragStart; @@ -1673,6 +1687,21 @@ class FurnaceGUI { bool followLog; // piano + enum PianoLayoutMode { + PIANO_LAYOUT_STANDARD = 0, + PIANO_LAYOUT_CONTINUOUS, + PIANO_LAYOUT_AUTOMATIC, + PIANO_LAYOUT_MAX + }; + + enum PianoInputPadMode { + PIANO_INPUT_PAD_DISABLE = 0, + PIANO_INPUT_PAD_REPLACE, + PIANO_INPUT_PAD_SPLIT_AUTO, + PIANO_INPUT_PAD_SPLIT_VISIBLE, + PIANO_INPUT_PAD_MAX + }; + int pianoOctaves, pianoOctavesEdit; bool pianoOptions, pianoSharePosition, pianoOptionsSet; float pianoKeyHit[180]; diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index 07716653..906f207c 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -483,6 +483,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={ D("FULLSCREEN", "Toggle full-screen", SDLK_F11), D("TX81Z_REQUEST", "Request voice from TX81Z", 0), D("PANIC", "Panic", SDLK_F12), + D("CLEAR", "Clear song data", 0), D("WINDOW_EDIT_CONTROLS", "Edit Controls", 0), D("WINDOW_ORDERS", "Orders", 0), @@ -582,6 +583,8 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={ D("PAT_COLLAPSE_SONG", "Collapse song", 0), D("PAT_EXPAND_SONG", "Expand song", 0), D("PAT_LATCH", "Set note input latch", 0), + D("PAT_SCROLL_MODE", "Change mobile scroll mode", 0), + D("PAT_CLEAR_LATCH", "Clear note input latch", 0), D("PAT_MAX", "", NOT_AN_ACTION), D("INS_LIST_MIN", "---Instrument list", NOT_AN_ACTION), diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index bf472751..7e48b246 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -2166,10 +2166,16 @@ void FurnaceGUI::drawInsEdit() { if (ImGui::Button(ICON_FA_FOLDER_OPEN "##IELoad")) { doAction(GUI_ACTION_INS_LIST_OPEN_REPLACE); } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Open"); + } ImGui::SameLine(); if (ImGui::Button(ICON_FA_FLOPPY_O "##IESave")) { doAction(GUI_ACTION_INS_LIST_SAVE); } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Save"); + } if (ImGui::BeginPopupContextItem("InsSaveFormats",ImGuiMouseButton_Right)) { if (ImGui::MenuItem("save in legacy format...")) { doAction(GUI_ACTION_INS_LIST_SAVE_OLD); diff --git a/src/gui/orders.cpp b/src/gui/orders.cpp index 32a4733d..f3349d71 100644 --- a/src/gui/orders.cpp +++ b/src/gui/orders.cpp @@ -177,10 +177,10 @@ void FurnaceGUI::drawOrders() { e->lockSave([this,i,j]() { if (changeAllOrders) { for (int k=0; kgetTotalChannelCount(); k++) { - if (e->curOrders->ord[k][i]<0xff) e->curOrders->ord[k][i]++; + if (e->curOrders->ord[k][i]<(unsigned char)(DIV_MAX_PATTERNS-1)) e->curOrders->ord[k][i]++; } } else { - if (e->curOrders->ord[j][i]<0xff) e->curOrders->ord[j][i]++; + if (e->curOrders->ord[j][i]<(unsigned char)(DIV_MAX_PATTERNS-1)) e->curOrders->ord[j][i]++; } }); e->walkSong(loopOrder,loopRow,loopEnd); diff --git a/src/gui/patManager.cpp b/src/gui/patManager.cpp index 22d5b645..161a42cb 100644 --- a/src/gui/patManager.cpp +++ b/src/gui/patManager.cpp @@ -30,8 +30,8 @@ void FurnaceGUI::drawPatManager() { } if (!patManagerOpen) return; char id[1024]; - unsigned char isUsed[256]; - bool isNull[256]; + unsigned char isUsed[DIV_MAX_PATTERNS]; + bool isNull[DIV_MAX_PATTERNS]; if (ImGui::Begin("Pattern Manager",&patManagerOpen,globalWinFlags)) { ImGui::Text("Global Tasks"); @@ -52,19 +52,19 @@ void FurnaceGUI::drawPatManager() { for (int i=0; igetTotalChannelCount(); i++) { ImGui::TableNextRow(); - memset(isUsed,0,256); - memset(isNull,0,256*sizeof(bool)); + memset(isUsed,0,DIV_MAX_PATTERNS); + memset(isNull,0,DIV_MAX_PATTERNS*sizeof(bool)); for (int j=0; jcurSubSong->ordersLen; j++) { isUsed[e->curSubSong->orders.ord[i][j]]++; } - for (int j=0; j<256; j++) { + for (int j=0; jcurSubSong->pat[i].data[j]==NULL); } ImGui::TableNextColumn(); ImGui::Text("%s",e->getChannelShortName(i)); ImGui::PushID(1000+i); - for (int k=0; k<256; k++) { + for (int k=0; kcurPat[i].effectCols>=8); + ImGui::BeginDisabled(e->curPat[i].effectCols>=DIV_MAX_EFFECTS); snprintf(chanID,2048,">##_RCH%d",i); if (ImGui::SmallButton(chanID)) { e->curPat[i].effectCols++; - if (e->curPat[i].effectCols>8) e->curPat[i].effectCols=8; + if (e->curPat[i].effectCols>DIV_MAX_EFFECTS) e->curPat[i].effectCols=DIV_MAX_EFFECTS; } ImGui::EndDisabled(); } diff --git a/src/gui/piano.cpp b/src/gui/piano.cpp index 7f1360cc..d999d7fd 100644 --- a/src/gui/piano.cpp +++ b/src/gui/piano.cpp @@ -63,7 +63,7 @@ void FurnaceGUI::drawPiano() { if (ImGui::BeginTable("PianoLayout",((pianoOptions && (!mobileUI || !portrait))?2:1),ImGuiTableFlags_BordersInnerV)) { int& off=(e->isPlaying() || pianoSharePosition)?pianoOffset:pianoOffsetEdit; int& oct=(e->isPlaying() || pianoSharePosition)?pianoOctaves:pianoOctavesEdit; - bool view=(pianoView==2)?(!e->isPlaying()):pianoView; + bool view=(pianoView==PIANO_LAYOUT_AUTOMATIC)?(!e->isPlaying()):pianoView; if (pianoOptions && (!mobileUI || !portrait)) { ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); } @@ -76,11 +76,11 @@ void FurnaceGUI::drawPiano() { ImVec2 optionSize=ImVec2((mobileUI && portrait)?((ImGui::GetContentRegionAvail().x-ImGui::GetStyle().ItemSpacing.x*5.0f)/6.0f):(1.2f*optionSizeY),optionSizeY); if (pianoOptionsSet) { if (ImGui::Button("OFF##PianoNOff",optionSize)) { - if (edit) noteInput(0,100); + if (edit) noteInput(0,GUI_NOTE_OFF); } ImGui::SameLine(); if (ImGui::Button("===##PianoNRel",optionSize)) { - if (edit) noteInput(0,101); + if (edit) noteInput(0,GUI_NOTE_OFF_RELEASE); } } else { if (ImGui::Button(ICON_FA_ARROW_LEFT "##PianoLeft",optionSize)) { @@ -95,29 +95,32 @@ void FurnaceGUI::drawPiano() { } ImGui::SameLine(); ImGui::Button(ICON_FA_ELLIPSIS_V "##PianoOptions",optionSize); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Options"); + } if (ImGui::BeginPopupContextItem("PianoOptions",ImGuiPopupFlags_MouseButtonLeft)) { ImGui::Text("Key layout:"); - if (ImGui::RadioButton("Automatic",pianoView==2)) { - pianoView=2; + if (ImGui::RadioButton("Automatic",pianoView==PIANO_LAYOUT_AUTOMATIC)) { + pianoView=PIANO_LAYOUT_AUTOMATIC; } - if (ImGui::RadioButton("Standard",pianoView==0)) { - pianoView=0; + if (ImGui::RadioButton("Standard",pianoView==PIANO_LAYOUT_STANDARD)) { + pianoView=PIANO_LAYOUT_STANDARD; } - if (ImGui::RadioButton("Continuous",pianoView==1)) { - pianoView=1; + if (ImGui::RadioButton("Continuous",pianoView==PIANO_LAYOUT_CONTINUOUS)) { + pianoView=PIANO_LAYOUT_CONTINUOUS; } ImGui::Text("Value input pad:"); - if (ImGui::RadioButton("Disabled",pianoInputPadMode==0)) { - pianoInputPadMode=0; + if (ImGui::RadioButton("Disabled",pianoInputPadMode==PIANO_INPUT_PAD_DISABLE)) { + pianoInputPadMode=PIANO_INPUT_PAD_DISABLE; } - if (ImGui::RadioButton("Replace piano",pianoInputPadMode==1)) { - pianoInputPadMode=1; + if (ImGui::RadioButton("Replace piano",pianoInputPadMode==PIANO_INPUT_PAD_REPLACE)) { + pianoInputPadMode=PIANO_INPUT_PAD_REPLACE; } - if (ImGui::RadioButton("Split (automatic)",pianoInputPadMode==2)) { - pianoInputPadMode=2; + if (ImGui::RadioButton("Split (automatic)",pianoInputPadMode==PIANO_INPUT_PAD_SPLIT_AUTO)) { + pianoInputPadMode=PIANO_INPUT_PAD_SPLIT_AUTO; } - if (ImGui::RadioButton("Split (always visible)",pianoInputPadMode==3)) { - pianoInputPadMode=3; + if (ImGui::RadioButton("Split (always visible)",pianoInputPadMode==PIANO_INPUT_PAD_SPLIT_VISIBLE)) { + pianoInputPadMode=PIANO_INPUT_PAD_SPLIT_VISIBLE; } ImGui::Checkbox("Share play/edit offset/range",&pianoSharePosition); ImGui::EndPopup(); @@ -129,7 +132,7 @@ void FurnaceGUI::drawPiano() { if (pianoOptionsSet) { if (ImGui::Button("REL##PianoNMRel",optionSize)) { - if (edit) noteInput(0,102); + if (edit) noteInput(0,GUI_NOTE_RELEASE); } ImGui::SameLine(); if (ImGui::Button(ICON_FA_TIMES "##PianoDelP",optionSize)) { @@ -158,7 +161,7 @@ void FurnaceGUI::drawPiano() { } ImGui::TableNextColumn(); - if (pianoInputPadMode==1 && cursor.xFine>0 && curWindow==GUI_WINDOW_PATTERN) { + if (pianoInputPadMode==PIANO_INPUT_PAD_REPLACE && cursor.xFine>0 && curWindow==GUI_WINDOW_PATTERN) { ImVec2 buttonSize=ImGui::GetContentRegionAvail(); if (ImGui::BeginTable("InputPadP",8,ImGuiTableFlags_SizingFixedSame)) { ImGui::TableNextRow(); @@ -431,7 +434,7 @@ void FurnaceGUI::drawPiano() { ImGui::End(); // draw input pad if necessary - if (curWindow==GUI_WINDOW_PATTERN && ((pianoInputPadMode==2 && cursor.xFine>0) || pianoInputPadMode==3)) { + if (curWindow==GUI_WINDOW_PATTERN && ((pianoInputPadMode==PIANO_INPUT_PAD_SPLIT_AUTO && cursor.xFine>0) || pianoInputPadMode==PIANO_INPUT_PAD_SPLIT_VISIBLE)) { if (ImGui::Begin("Input Pad",NULL,ImGuiWindowFlags_NoTitleBar)) { ImGui::BeginDisabled(cursor.xFine==0); if (ImGui::BeginTable("InputPad",3,ImGuiTableFlags_Borders)) { diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index fb50fd56..beca3102 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -118,10 +118,16 @@ void FurnaceGUI::drawSampleEdit() { if (ImGui::Button(ICON_FA_FOLDER_OPEN "##SELoad")) { doAction(GUI_ACTION_SAMPLE_LIST_OPEN_REPLACE); } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Open"); + } ImGui::SameLine(); if (ImGui::Button(ICON_FA_FLOPPY_O "##SESave")) { doAction(GUI_ACTION_SAMPLE_LIST_SAVE); } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Save"); + } ImGui::SameLine(); @@ -134,7 +140,32 @@ void FurnaceGUI::drawSampleEdit() { ImGui::Separator(); - if (ImGui::BeginTable("SampleProps",4,ImGuiTableFlags_SizingStretchSame|ImGuiTableFlags_BordersV|ImGuiTableFlags_BordersOuterH)) { + bool isChipVisible[DIV_MAX_CHIPS]; + bool isTypeVisible[DIV_MAX_SAMPLE_TYPE]; + bool isMemVisible[DIV_MAX_SAMPLE_TYPE][DIV_MAX_CHIPS]; + bool isMemWarning[DIV_MAX_SAMPLE_TYPE][DIV_MAX_CHIPS]; + memset(isChipVisible,0,DIV_MAX_CHIPS*sizeof(bool)); + memset(isTypeVisible,0,DIV_MAX_SAMPLE_TYPE*sizeof(bool)); + memset(isMemVisible,0,DIV_MAX_CHIPS*DIV_MAX_SAMPLE_TYPE*sizeof(bool)); + memset(isMemWarning,0,DIV_MAX_CHIPS*DIV_MAX_SAMPLE_TYPE*sizeof(bool)); + for (int i=0; isong.systemLen; i++) { + DivDispatch* dispatch=e->getDispatch(i); + if (dispatch==NULL) continue; + + for (int j=0; jgetSampleMemCapacity(j)==0) continue; + isChipVisible[i]=true; + isTypeVisible[j]=true; + isMemVisible[j][i]=true; + if (!dispatch->isSampleLoaded(j,curSample)) isMemWarning[j][i]=true; + } + } + int selColumns=1; + for (int i=0; i1)?4:3,ImGuiTableFlags_SizingStretchSame|ImGuiTableFlags_BordersV|ImGuiTableFlags_BordersOuterH)) { ImGui::TableNextRow(ImGuiTableRowFlags_Headers); ImGui::TableNextColumn(); if (ImGui::Button(sampleInfo?(ICON_FA_CHEVRON_UP "##SECollapse"):(ICON_FA_CHEVRON_DOWN "##SECollapse"))) { @@ -143,7 +174,20 @@ void FurnaceGUI::drawSampleEdit() { ImGui::SameLine(); ImGui::Text("Info"); ImGui::TableNextColumn(); - ImGui::Text("Rate"); + pushToggleColors(!sampleCompatRate); + if (ImGui::Button("Rate")) { + sampleCompatRate=false; + } + popToggleColors(); + ImGui::SameLine(); + pushToggleColors(sampleCompatRate); + if (ImGui::Button("Compat Rate")) { + sampleCompatRate=true; + } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("used in DefleMask-compatible sample mode (17xx), in where samples are mapped to an octave."); + } + popToggleColors(); ImGui::TableNextColumn(); bool doLoop=(sample->isLoopable()); if (ImGui::Checkbox("Loop",&doLoop)) { MARK_MODIFIED @@ -164,8 +208,11 @@ void FurnaceGUI::drawSampleEdit() { if (ImGui::IsItemHovered() && sample->depth==DIV_SAMPLE_DEPTH_BRR) { ImGui::SetTooltip("changing the loop in a BRR sample may result in glitches!"); } - ImGui::TableNextColumn(); - ImGui::Text("Chips"); + + if (selColumns>1) { + ImGui::TableNextColumn(); + ImGui::Text("Chips"); + } if (sampleInfo) { ImGui::TableNextRow(); @@ -190,21 +237,135 @@ void FurnaceGUI::drawSampleEdit() { ImGui::EndCombo(); } - ImGui::TableNextColumn(); - ImGui::Text("C-4 (Hz)"); - ImGui::SameLine(); - ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::InputInt("##SampleCenter",&sample->centerRate,10,200)) { MARK_MODIFIED - if (sample->centerRate<100) sample->centerRate=100; - if (sample->centerRate>65535) sample->centerRate=65535; + bool isThereSNES=false; + for (int i=0; isong.systemLen; i++) { + if (e->song.system[i]==DIV_SYSTEM_SNES) { + isThereSNES=true; + break; + } + } + if (sample->depth==DIV_SAMPLE_DEPTH_BRR || isThereSNES) { + bool be=sample->brrEmphasis; + if (ImGui::Checkbox("BRR emphasis",&be)) { + sample->prepareUndo(true); + sample->brrEmphasis=be; + e->renderSamplesP(); + updateSampleTex=true; + MARK_MODIFIED; + } + if (ImGui::IsItemHovered()) { + if (sample->depth==DIV_SAMPLE_DEPTH_BRR) { + ImGui::SetTooltip("this is a BRR sample.\nenabling this option will muffle it (only affects non-SNES chips)."); + } else { + ImGui::SetTooltip("enable this option to slightly boost high frequencies\nto compensate for the SNES' Gaussian filter's muffle."); + } + } } - ImGui::Text("Compat"); + int targetRate=sampleCompatRate?sample->rate:sample->centerRate; + int sampleNote=round(64.0+(128.0*12.0*log((double)targetRate/8363.0)/log(2.0))); + int sampleNoteCoarse=60+(sampleNote>>7); + int sampleNoteFine=(sampleNote&127)-64; + + if (sampleNoteCoarse<0) { + sampleNoteCoarse=0; + sampleNoteFine=-64; + } + if (sampleNoteCoarse>119) { + sampleNoteCoarse=119; + sampleNoteFine=63; + } + + bool coarseChanged=false; + + ImGui::TableNextColumn(); + ImGui::Text("Hz"); ImGui::SameLine(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::InputInt("##SampleRate",&sample->rate,10,200)) { MARK_MODIFIED - if (sample->rate<100) sample->rate=100; - if (sample->rate>96000) sample->rate=96000; + if (ImGui::InputInt("##SampleRate",&targetRate,10,200)) { MARK_MODIFIED + if (targetRate<100) targetRate=100; + if (targetRate>384000) targetRate=384000; + + if (sampleCompatRate) { + sample->rate=targetRate; + } else { + sample->centerRate=targetRate; + } + } + + ImGui::Text("Note"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::BeginCombo("##SampleNote",noteNames[sampleNoteCoarse+60])) { + char temp[1024]; + for (int i=0; i<120; i++) { + snprintf(temp,1023,"%s##_SRN%d",noteNames[i+60],i); + if (ImGui::Selectable(temp,i==sampleNoteCoarse)) { + sampleNoteCoarse=i; + coarseChanged=true; + } + } + ImGui::EndCombo(); + } else if (ImGui::IsItemHovered()) { + if (wheelY!=0) { + sampleNoteCoarse-=wheelY; + if (sampleNoteCoarse<0) { + sampleNoteCoarse=0; + sampleNoteFine=-64; + } + if (sampleNoteCoarse>119) { + sampleNoteCoarse=119; + sampleNoteFine=63; + } + coarseChanged=true; + } + } + + if (coarseChanged) { MARK_MODIFIED + sampleNote=((sampleNoteCoarse-60)<<7)+sampleNoteFine; + + targetRate=8363.0*pow(2.0,(double)sampleNote/(128.0*12.0)); + if (targetRate<100) targetRate=100; + if (targetRate>384000) targetRate=384000; + + if (sampleCompatRate) { + sample->rate=targetRate; + } else { + sample->centerRate=targetRate; + } + } + + ImGui::Text("Fine"); + ImGui::SameLine(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + int prevFine=sampleNoteFine; + int prevSampleRate=targetRate; + if (ImGui::InputInt("##SampleFine",&sampleNoteFine,1,10)) { MARK_MODIFIED + if (sampleNoteFine>63) sampleNoteFine=63; + if (sampleNoteFine<-64) sampleNoteFine=-64; + + sampleNote=((sampleNoteCoarse-60)<<7)+sampleNoteFine; + + targetRate=round(8363.0*pow(2.0,(double)sampleNote/(128.0*12.0))); + + if (targetRate==prevSampleRate) { + if (prevFine==sampleNoteFine) { + // do nothing + } else if (prevFine>sampleNoteFine) { // coarse incr/decr due to precision loss + targetRate--; + } else { + targetRate++; + } + } + + if (targetRate<100) targetRate=100; + if (targetRate>384000) targetRate=384000; + + if (sampleCompatRate) { + sample->rate=targetRate; + } else { + sample->centerRate=targetRate; + } } ImGui::TableNextColumn(); @@ -272,35 +433,8 @@ void FurnaceGUI::drawSampleEdit() { } ImGui::EndDisabled(); - ImGui::TableNextColumn(); - - bool isChipVisible[32]; - bool isTypeVisible[4]; - bool isMemVisible[4][32]; - bool isMemWarning[4][32]; - memset(isChipVisible,0,32*sizeof(bool)); - memset(isTypeVisible,0,4*sizeof(bool)); - memset(isMemVisible,0,32*4*sizeof(bool)); - memset(isMemWarning,0,32*4*sizeof(bool)); - for (int i=0; isong.systemLen; i++) { - DivDispatch* dispatch=e->getDispatch(i); - if (dispatch==NULL) continue; - - for (int j=0; j<4; j++) { - if (dispatch->getSampleMemCapacity(j)==0) continue; - isChipVisible[i]=true; - isTypeVisible[j]=true; - isMemVisible[j][i]=true; - if (!dispatch->isSampleLoaded(j,curSample)) isMemWarning[j][i]=true; - } - } - int selColumns=1; - for (int i=0; i<32; i++) { - if (isChipVisible[i]) selColumns++; - } - if (selColumns<=1) { - ImGui::Text("NO CHIPS LESS GOOO"); - } else { + if (selColumns>1) { + ImGui::TableNextColumn(); if (ImGui::BeginTable("SEChipSel",selColumns,ImGuiTableFlags_SizingFixedSame)) { ImGui::TableNextRow(); ImGui::TableNextColumn(); @@ -310,7 +444,7 @@ void FurnaceGUI::drawSampleEdit() { ImGui::Text("%d",i+1); } char id[1024]; - for (int i=0; i<4; i++) { + for (int i=0; iDC.CursorPos; + ImVec2 maxArea=ImVec2( + minArea.x+size.x, + minArea.y+size.y + ); + ImRect rect=ImRect(minArea,maxArea); + ImGuiStyle& style=ImGui::GetStyle(); + + ImGui::ItemSize(size,style.FramePadding.y); + if (ImGui::ItemAdd(rect,ImGui::GetID("SETime"))) { + dl->AddText(minArea,0xffffffff,"0"); + } + + ImVec2 avail=ImGui::GetContentRegionAvail(); // sample view size determined here // don't do this. reason: mobile. /*if (ImGui::GetContentRegionAvail().y>(ImGui::GetContentRegionAvail().x*0.5f)) { avail=ImVec2(ImGui::GetContentRegionAvail().x,ImGui::GetContentRegionAvail().x*0.5f); @@ -1075,6 +1228,8 @@ void FurnaceGUI::drawSampleEdit() { } String statusBar=sampleDragMode?"Draw":"Select"; + String statusBar2=""; + String statusBar3=fmt::sprintf("%d samples, %d bytes",sample->samples,sample->getCurBufLen()); bool drawSelection=false; if (!sampleDragMode) { @@ -1155,12 +1310,15 @@ void FurnaceGUI::drawSampleEdit() { } posY=(0.5-pos.y/rectSize.y)*((sample->depth==DIV_SAMPLE_DEPTH_8BIT)?255:32767); if (posX>=0) { - statusBar+=fmt::sprintf(" | (%d, %d)",posX,posY); + statusBar2=fmt::sprintf("(%d, %d)",posX,posY); } } if (e->isPreviewingSample()) { - statusBar+=fmt::sprintf(" | %.2fHz",e->getSamplePreviewRate()); + if (!statusBar2.empty()) { + statusBar2+=" | "; + } + statusBar2+=fmt::sprintf("%.2fHz",e->getSamplePreviewRate()); int start=sampleSelStart; int end=sampleSelEnd; @@ -1243,7 +1401,23 @@ void FurnaceGUI::drawSampleEdit() { } ImGui::SetCursorPosY(ImGui::GetCursorPosY()+ImGui::GetStyle().ScrollbarSize); - ImGui::Text("%s",statusBar.c_str()); + ImGui::PushStyleVar(ImGuiStyleVar_CellPadding,ImVec2(0,0)); + if (ImGui::BeginTable("SEStatus",3,ImGuiTableFlags_BordersInnerV)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch,0.7); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.3); + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthFixed); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::TextUnformatted(statusBar.c_str()); + ImGui::TableNextColumn(); + ImGui::TextUnformatted(statusBar2.c_str()); + ImGui::TableNextColumn(); + ImGui::TextUnformatted(statusBar3.c_str()); + + ImGui::EndTable(); + } + ImGui::PopStyleVar(); } } } diff --git a/src/gui/songInfo.cpp b/src/gui/songInfo.cpp index 493ad597..e2046857 100644 --- a/src/gui/songInfo.cpp +++ b/src/gui/songInfo.cpp @@ -180,7 +180,7 @@ void FurnaceGUI::drawSongInfo(bool asChild) { int patLen=e->curSubSong->patLen; if (ImGui::InputInt("##PatLength",&patLen,1,3)) { MARK_MODIFIED if (patLen<1) patLen=1; - if (patLen>256) patLen=256; + if (patLen>DIV_MAX_PATTERNS) patLen=DIV_MAX_PATTERNS; e->curSubSong->patLen=patLen; } @@ -192,7 +192,7 @@ void FurnaceGUI::drawSongInfo(bool asChild) { int ordLen=e->curSubSong->ordersLen; if (ImGui::InputInt("##OrdLength",&ordLen,1,3)) { MARK_MODIFIED if (ordLen<1) ordLen=1; - if (ordLen>256) ordLen=256; + if (ordLen>DIV_MAX_PATTERNS) ordLen=DIV_MAX_PATTERNS; e->curSubSong->ordersLen=ordLen; if (curOrder>=ordLen) { setOrder(ordLen-1); diff --git a/src/gui/subSongs.cpp b/src/gui/subSongs.cpp index a6eb70f0..9801ab57 100644 --- a/src/gui/subSongs.cpp +++ b/src/gui/subSongs.cpp @@ -50,10 +50,16 @@ void FurnaceGUI::drawSubSongs() { if (ImGui::SmallButton(ICON_FA_ARROW_UP "##SubUp")) { e->moveSubSongUp(i); } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Move up"); + } ImGui::SameLine(); if (ImGui::SmallButton(ICON_FA_ARROW_DOWN "##SubDown")) { e->moveSubSongDown(i); } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Move down"); + } ImGui::PopID(); } ImGui::EndTable(); @@ -79,6 +85,9 @@ void FurnaceGUI::drawSubSongs() { MARK_MODIFIED; } } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Add"); + } ImGui::SameLine(); if (ImGui::Button(ICON_FA_MINUS "##SubSongDel")) { if (e->song.subsong.size()<=1) { @@ -87,6 +96,9 @@ void FurnaceGUI::drawSubSongs() { showWarning("are you sure you want to remove this subsong?",GUI_WARN_SUBSONG_DEL); } } + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("Remove"); + } ImGui::Text("Name"); ImGui::SameLine(); diff --git a/src/gui/sysManager.cpp b/src/gui/sysManager.cpp index 1de5ac1f..dda5ed96 100644 --- a/src/gui/sysManager.cpp +++ b/src/gui/sysManager.cpp @@ -112,7 +112,7 @@ void FurnaceGUI::drawSysManager() { ImGui::EndDisabled(); ImGui::PopID(); } - if (e->song.systemLen<32) { + if (e->song.systemLen0 && wave->max!=waveGenScaleY) e->lockEngine([this,wave]() { + if (waveGenScaleY>0 && wave->max!=(waveGenScaleY-1)) e->lockEngine([this,wave]() { for (int i=0; ilen; i++) { wave->data[i]=(wave->data[i]*(waveGenScaleY+1))/(wave->max+1); } - wave->max=waveGenScaleY; + wave->max=waveGenScaleY-1; MARK_MODIFIED; }); }