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/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 4c4f8f3a..5e0834a7 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]; } @@ -2770,7 +2770,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) { @@ -3470,7 +3470,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) { @@ -3478,14 +3478,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; @@ -3631,7 +3631,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 3b888121..12150fbb 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -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; isong.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,7 +158,6 @@ 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; @@ -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,38 +469,24 @@ 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; @@ -569,6 +560,7 @@ void DivPlatformSMS::setFlags(const DivConfig& flags) { stereo=false; break; } + 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 ce3ee5e7..41f0efe4 100644 --- a/src/engine/platform/sms.h +++ b/src/engine/platform/sms.h @@ -65,8 +65,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/playback.cpp b/src/engine/playback.cpp index 216607bd..41a57e44 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -1592,7 +1592,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 148e63fb..08dee993 100644 --- a/src/engine/sample.cpp +++ b/src/engine/sample.cpp @@ -57,9 +57,9 @@ void DivSample::putSampleData(SafeWriter* w) { w->writeI(loop?loopStart:-1); w->writeI(loop?loopEnd:-1); - for (int i=0; i<4; i++) { + for (int i=0; iwriteI(out); @@ -137,9 +137,9 @@ DivDataErrors DivSample::readSampleData(SafeReader& reader, short version) { loopEnd=reader.readI(); loop=(loopStart>=0)&&(loopEnd>=0); - for (int i=0; i<4; i++) { + for (int i=0; i @@ -116,7 +117,7 @@ struct DivSample { // - 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 @@ -332,11 +333,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" @@ -231,11 +230,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; @@ -429,7 +428,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 6d23fa2a..81ceeb2c 100644 --- a/src/gui/dataList.cpp +++ b/src/gui/dataList.cpp @@ -693,7 +693,7 @@ void FurnaceGUI::actualSampleList() { DivDispatch* dispatch=e->getDispatch(j); if (dispatch==NULL) continue; - for (int k=0; k<4; k++) { + for (int k=0; kgetSampleMemCapacity(k)==0) continue; if (!dispatch->isSampleLoaded(k,i) && sample->renderOn[k][j]) { memWarning=true; diff --git a/src/gui/doAction.cpp b/src/gui/doAction.cpp index 14fb4466..a12366ac 100644 --- a/src/gui/doAction.cpp +++ b/src/gui/doAction.cpp @@ -522,7 +522,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; diff --git a/src/gui/editControls.cpp b/src/gui/editControls.cpp index 05c5d4c9..19010e3d 100644 --- a/src/gui/editControls.cpp +++ b/src/gui/editControls.cpp @@ -19,7 +19,22 @@ #include "gui.h" #include "IconsFontAwesome4.h" -#include +#include + +// 0: all directions +// 1: half +// 2: quarter +float mobileButtonAngles[3][8]={ + {0.0, 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875}, + {0.8, 0.933333, 0.066667, 0.2, 0.8, 0.933333, 0.066667, 0.2}, + {0.75, 0.833333, 0.916667, 0.0, 0.75, 0.833333, 0.916667, 0.0} +}; + +float mobileButtonDistances[3][8]={ + {1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0}, + {1.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0, 2.0}, + {1.0, 1.0, 1.0, 1.0, 2.0, 2.0, 2.0, 2.0} +}; void FurnaceGUI::drawMobileControls() { float timeScale=1.0f/(60.0f*ImGui::GetIO().DeltaTime); @@ -48,12 +63,98 @@ void FurnaceGUI::drawMobileControls() { } } } + + 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.0; + if (mobileEditAnim>1.0f) { + mobileEditAnim=1.0f; + } else { + WAKE_UP; + } + } else { + mobileEditAnim-=ImGui::GetIO().DeltaTime*2.0; + if (mobileEditAnim<0.0f) { + mobileEditAnim=0.0f; + } else { + WAKE_UP; + } + } + + if (mobileEditAnim>0.0f) { + ImGui::SetNextWindowPos(ImVec2(0.0f,0.0f)); + ImGui::SetNextWindowSize(ImVec2(canvasW,canvasH)); + } else { + ImGui::SetNextWindowPos(ImVec2(mobileEditButtonPos.x*canvasW, mobileEditButtonPos.y*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)); + if (ImGui::Begin("MobileEdit",NULL,ImGuiWindowFlags_NoScrollbar|ImGuiWindowFlags_NoScrollWithMouse|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoBackground|ImGuiWindowFlags_NoDecoration)) { + 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; + + for (int i=0; i<8; i++) { + float anim=(mobileEditAnim*5)-(float)i*0.5; + if (anim<0.0f) anim=0.0f; + if (anim>1.0f) anim=1.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 + )); + ImGui::Button(fmt::sprintf("%d",i+1).c_str(),mobileEditButtonSize); + + 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("Edit",mobileEditButtonSize)) { + // click + if (ImGui::GetIO().MouseDragMaxDistanceSqr[ImGuiMouseButton_Left]<=ImGui::GetIO().ConfigInertialScrollToleranceSqr) { + mobileEdit=true; + } + } + if (ImGui::IsItemClicked() && !mobileEdit) { + dragMobileEditButton=true; + } + } + ImGui::End(); + ImGui::PopStyleVar(); + 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) { 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 8c5264c5..1487b50f 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2939,6 +2939,9 @@ void FurnaceGUI::pointUp(int x, int y, int button) { mobileMenuOpen=(mobileMenuPos>=0.15f); } } + if (dragMobileEditButton) { + dragMobileEditButton=false; + } if (selecting) { if (!selectingFull) cursor=selEnd; finishSelection(); @@ -4763,7 +4766,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); @@ -4779,8 +4782,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; } @@ -5656,6 +5659,7 @@ FurnaceGUI::FurnaceGUI(): pendingInsSingle(false), displayPendingRawSample(false), snesFilterHex(false), + mobileEdit(false), vgmExportVersion(0x171), drawHalt(10), zsmExportTickRate(60), @@ -5664,6 +5668,9 @@ FurnaceGUI::FurnaceGUI(): displayInsTypeListMakeInsSample(-1), 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), @@ -5794,12 +5801,14 @@ FurnaceGUI::FurnaceGUI(): 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), @@ -5923,6 +5932,7 @@ FurnaceGUI::FurnaceGUI(): sampleSelStart(-1), sampleSelEnd(-1), sampleInfo(true), + sampleCompatRate(false), sampleDragActive(false), sampleDragMode(false), sampleDrag16(false), @@ -6022,7 +6032,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 587ff03a..e5e58985 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; @@ -1616,7 +1619,7 @@ class FurnaceGUI { int resampleStrat; float amplifyVol; int sampleSelStart, sampleSelEnd; - bool sampleInfo; + bool sampleInfo, sampleCompatRate; bool sampleDragActive, sampleDragMode, sampleDrag16, sampleZoomAuto; void* sampleDragTarget; ImVec2 sampleDragStart; 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/sampleEdit.cpp b/src/gui/sampleEdit.cpp index 5f893cac..333a88a4 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -134,7 +134,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 +168,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 +202,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(); @@ -216,21 +257,110 @@ void FurnaceGUI::drawSampleEdit() { } } - 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; + 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; } - ImGui::Text("Compat"); + 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(); @@ -298,35 +428,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(); @@ -336,7 +439,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); @@ -1101,6 +1223,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) { @@ -1181,12 +1305,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; @@ -1269,7 +1396,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/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; }); }