diff --git a/CMakeLists.txt b/CMakeLists.txt index f8c317d1..6e84fd40 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -433,6 +433,8 @@ src/engine/platform/sound/vic20sound.c src/engine/platform/sound/ymz280b.cpp +src/engine/platform/sound/vsu.cpp + src/engine/platform/sound/rf5c68.cpp src/engine/platform/sound/oki/msm5232.cpp @@ -504,6 +506,7 @@ src/engine/platform/x1_010.cpp src/engine/platform/lynx.cpp src/engine/platform/su.cpp src/engine/platform/swan.cpp +src/engine/platform/vb.cpp src/engine/platform/vera.cpp src/engine/platform/zxbeeper.cpp src/engine/platform/bubsyswsg.cpp diff --git a/TODO.md b/TODO.md new file mode 100644 index 00000000..ca2ca143 --- /dev/null +++ b/TODO.md @@ -0,0 +1,11 @@ +# to-do for 0.6pre2 + +- POKEY +- Pokémon Mini +- Virtual Boy +- T6W28 +- (maybe) YM2612 CSM (no DualPCM) +- port presets to new format +- bug fixes +- (maybe) ExtCh FM macros? +- (maybe) advanced linear arpeggio? (run arp+slide simultaneously) diff --git a/demos/Solar_Man_Genesis.fur b/demos/Solar_Man_Genesis.fur new file mode 100644 index 00000000..a50427a8 Binary files /dev/null and b/demos/Solar_Man_Genesis.fur differ diff --git a/papers/export-tech.md b/papers/export-tech.md index dbfadece..95dd7c76 100644 --- a/papers/export-tech.md +++ b/papers/export-tech.md @@ -13,14 +13,70 @@ then read data. ## binary command stream -read channel, command and values. - -if channel is 80 or higher, then it is a special command: +Furnace Command Stream, split version. ``` -fb xx xx xx xx: set tick rate -fc xx xx: wait xxxx ticks -fd xx: wait xx ticks -fe: wait one tick -ff: stop +size | description +-----|------------------------------------ + 4 | "FCS\0" format magic + 4 | channel count + 4?? | pointers to channel data + 1?? | preset delays + | - 16 values + 1?? | speed dial commands + | - 16 values + ??? | channel data ``` + +read command and values (if any). +the list of commands follows. + +``` +hex | description +----|------------------------------------ + 00 | note on: C-(-5) + 01 | note on: C#(-5) + 02 | note on: D-(-5) + .. | ... + b1 | note on: A-9 + b2 | note on: A#9 + b3 | note on: B-9 + b4 | note on: null +----|------------------------------------ + b5 | note off + b6 | note off env + b7 | env release + b8 | instrument // (ins, force) + be | panning // (left, right) + c0 | pre porta // (inporta, isportaorslide) + c2 | vibrato // (speed, depth) + c3 | vibrato range // (range) + c4 | vibrato shape // (shape) + c5 | pitch // (pitch) + c6 | arpeggio // (note1, note2) + c7 | volume // (vol) + c8 | vol slide // (amount, onetick) + c9 | porta // (target, speed) + ca | legato // (note) +----|------------------------------------ + d0 | speed dial command 0 + d1 | speed dial command 1 + .. | ... + df | speed dial command 15 +----|------------------------------------ + e0 | preset delay 0 + e1 | preset delay 1 + .. | ... + ef | preset delay 15 +----|------------------------------------ + f7 | full command (command and data follows) + f8 | go to sub-block (offset follows) + f9 | return from sub-block + fa | jump (offset follows) + fb | set tick rate (4 bytes) + fc | wait (16-bit) + fd | wait (8-bit) + fe | wait one tick + ff | stop +``` + diff --git a/papers/format.md b/papers/format.md index 019f6b1c..67f30eb2 100644 --- a/papers/format.md +++ b/papers/format.md @@ -32,7 +32,9 @@ these fields are 0 in format versions prior to 100 (0.6pre1). the format versions are: -- 119: Furnace dev119 (still not released) +- 121: Furnace dev121 +- 120: Furnace dev120 +- 119: Furnace dev119 - 118: Furnace dev118 - 117: Furnace dev117 - 116: Furnace 0.6pre1.5 @@ -350,7 +352,9 @@ size | description 1 | 0B/0D effect treatment (>=113) or reserved 1 | automatic system name detection (>=115) or reserved | - this one isn't a compatibility flag, but it's here for convenience... - 3 | reserved + 1 | disable sample macro (>=117) or reserved + 1 | broken outVol episode 2 (>=121) or reserved + 1 | reserved --- | **virtual tempo data** 2 | virtual tempo numerator of first song (>=96) or reserved 2 | virtual tempo denominator of first song (>=96) or reserved @@ -437,6 +441,11 @@ notes: - the entire instrument is stored, regardless of instrument type. - the macro range varies depending on the instrument type. - "macro open" indicates whether the macro is collapsed or not in the instrument editor. + - as of format version 120, bit 1-2 indicates macro mode: + - 0: sequence (normal) + - 1: ADSR + - 2: LFO + - see sub-section for information on how to interpret parameters. - FM operator order is: - 1/3/2/4 (internal order) for OPN, OPM, OPZ and OPL 4-op - 1/2/?/? (? = unused) for OPL 2-op and OPLL @@ -1024,6 +1033,29 @@ size | description 1 | KSR macro delay ``` +## interpreting macro mode values + +- sequence (normal): I think this is obvious... +- ADSR: + - `val[0]`: bottom + - `val[1]`: top + - `val[2]`: attack + - `val[3]`: hold time + - `val[4]`: decay + - `val[5]`: sustain level + - `val[6]`: sustain hold time + - `val[7]`: decay 2 + - `val[8]`: release +- LFO: + - `val[11]`: speed + - `val[12]`: waveform + - 0: triangle + - 1: saw + - 2: pulse + - `val[13]`: phase + - `val[14]`: loop + - `val[15]`: global (not sure how will I implement this) + # wavetable ``` diff --git a/src/engine/dispatchContainer.cpp b/src/engine/dispatchContainer.cpp index 5614be63..65560a2b 100644 --- a/src/engine/dispatchContainer.cpp +++ b/src/engine/dispatchContainer.cpp @@ -68,6 +68,7 @@ #include "platform/ymz280b.h" #include "platform/rf5c68.h" #include "platform/snes.h" +#include "platform/vb.h" #include "platform/pcmdac.h" #include "platform/dummy.h" #include "../ta-log.h" @@ -343,6 +344,9 @@ void DivDispatchContainer::init(DivSystem sys, DivEngine* eng, int chanCount, do case DIV_SYSTEM_SWAN: dispatch=new DivPlatformSwan; break; + case DIV_SYSTEM_VBOY: + dispatch=new DivPlatformVB; + break; case DIV_SYSTEM_VERA: dispatch=new DivPlatformVERA; break; diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index db882070..5be458be 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -274,29 +274,61 @@ double DivEngine::benchmarkSeek() { return tAvg; } -#define WRITE_TICK \ - if (!wroteTick) { \ - wroteTick=true; \ - if (binary) { \ - if (tick-lastTick>255) { \ - w->writeC(0xfc); \ - w->writeS(tick-lastTick); \ - } else if (tick-lastTick>1) { \ - w->writeC(0xfd); \ - w->writeC(tick-lastTick); \ - } else { \ - w->writeC(0xfe); \ +#define WRITE_TICK(x) \ + if (binary) { \ + if (!wroteTick[x]) { \ + wroteTick[x]=true; \ + if (tick-lastTick[x]>255) { \ + chanStream[x]->writeC(0xfc); \ + chanStream[x]->writeS(tick-lastTick[x]); \ + } else if (tick-lastTick[x]>1) { \ + delayPopularity[tick-lastTick[x]]++; \ + chanStream[x]->writeC(0xfd); \ + chanStream[x]->writeC(tick-lastTick[x]); \ + } else if (tick-lastTick[x]>0) { \ + chanStream[x]->writeC(0xfe); \ } \ - } else { \ + lastTick[x]=tick; \ + } \ + } else { \ + if (!wroteTickGlobal) { \ + wroteTickGlobal=true; \ w->writeText(fmt::sprintf(">> TICK %d\n",tick)); \ } \ - lastTick=tick; \ } void writePackedCommandValues(SafeWriter* w, const DivCommand& c) { - w->writeC(c.cmd); switch (c.cmd) { case DIV_CMD_NOTE_ON: + if (c.value==DIV_NOTE_NULL) { + w->writeC(0xb4); + } else { + w->writeC(CLAMP(c.value+60,0,0xb3)); + } + break; + case DIV_CMD_NOTE_OFF: + case DIV_CMD_NOTE_OFF_ENV: + case DIV_CMD_ENV_RELEASE: + case DIV_CMD_INSTRUMENT: + case DIV_CMD_PANNING: + case DIV_CMD_PRE_PORTA: + case DIV_CMD_HINT_VIBRATO: + case DIV_CMD_HINT_VIBRATO_RANGE: + case DIV_CMD_HINT_VIBRATO_SHAPE: + case DIV_CMD_HINT_PITCH: + case DIV_CMD_HINT_ARPEGGIO: + case DIV_CMD_HINT_VOLUME: + case DIV_CMD_HINT_PORTA: + case DIV_CMD_HINT_VOL_SLIDE: + case DIV_CMD_HINT_LEGATO: + w->writeC((unsigned char)c.cmd+0xb4); + break; + default: + w->writeC(0xf0); // unoptimized extended command + w->writeC(c.cmd); + break; + } + switch (c.cmd) { case DIV_CMD_HINT_LEGATO: if (c.value==DIV_NOTE_NULL) { w->writeC(0xff); @@ -304,6 +336,7 @@ void writePackedCommandValues(SafeWriter* w, const DivCommand& c) { w->writeC(c.value+60); } break; + case DIV_CMD_NOTE_ON: case DIV_CMD_NOTE_OFF: case DIV_CMD_NOTE_OFF_ENV: case DIV_CMD_ENV_RELEASE: @@ -313,6 +346,21 @@ void writePackedCommandValues(SafeWriter* w, const DivCommand& c) { case DIV_CMD_HINT_VIBRATO_SHAPE: case DIV_CMD_HINT_PITCH: case DIV_CMD_HINT_VOLUME: + w->writeC(c.value); + break; + case DIV_CMD_PANNING: + case DIV_CMD_HINT_VIBRATO: + case DIV_CMD_HINT_ARPEGGIO: + case DIV_CMD_HINT_PORTA: + w->writeC(c.value); + w->writeC(c.value2); + break; + case DIV_CMD_PRE_PORTA: + w->writeC((c.value?0x80:0)|(c.value2?0x40:0)); + break; + case DIV_CMD_HINT_VOL_SLIDE: + w->writeS(c.value); + break; case DIV_CMD_SAMPLE_MODE: case DIV_CMD_SAMPLE_FREQ: case DIV_CMD_SAMPLE_BANK: @@ -348,12 +396,18 @@ void writePackedCommandValues(SafeWriter* w, const DivCommand& c) { case DIV_CMD_AY_NOISE_MASK_AND: case DIV_CMD_AY_NOISE_MASK_OR: case DIV_CMD_AY_AUTO_ENVELOPE: + case DIV_CMD_FDS_MOD_DEPTH: + case DIV_CMD_FDS_MOD_HIGH: + case DIV_CMD_FDS_MOD_LOW: + case DIV_CMD_FDS_MOD_POS: + case DIV_CMD_FDS_MOD_WAVE: + case DIV_CMD_SAA_ENVELOPE: + case DIV_CMD_AMIGA_FILTER: + case DIV_CMD_AMIGA_AM: + case DIV_CMD_AMIGA_PM: + w->writeC(1); // length w->writeC(c.value); break; - case DIV_CMD_PANNING: - case DIV_CMD_HINT_VIBRATO: - case DIV_CMD_HINT_ARPEGGIO: - case DIV_CMD_HINT_PORTA: case DIV_CMD_FM_TL: case DIV_CMD_FM_AM: case DIV_CMD_FM_AR: @@ -375,25 +429,27 @@ void writePackedCommandValues(SafeWriter* w, const DivCommand& c) { case DIV_CMD_FM_FINE: case DIV_CMD_AY_IO_WRITE: case DIV_CMD_AY_AUTO_PWM: + w->writeC(2); // length w->writeC(c.value); w->writeC(c.value2); break; - case DIV_CMD_PRE_PORTA: - w->writeC((c.value?0x80:0)|(c.value2?0x40:0)); - break; - case DIV_CMD_HINT_VOL_SLIDE: case DIV_CMD_C64_FINE_DUTY: case DIV_CMD_C64_FINE_CUTOFF: + case DIV_CMD_LYNX_LFSR_LOAD: + w->writeC(2); // length w->writeS(c.value); break; case DIV_CMD_FM_FIXFREQ: + w->writeC(2); // length w->writeS((c.value<<12)|(c.value2&0x7ff)); break; case DIV_CMD_NES_SWEEP: + w->writeC(1); // length w->writeC((c.value?8:0)|(c.value2&0x77)); break; default: logW("unimplemented command %s!",cmdName[c.cmd]); + w->writeC(0); // length break; } } @@ -411,12 +467,44 @@ SafeWriter* DivEngine::saveCommand(bool binary) { walkSong(loopOrder,loopRow,loopEnd); logI("loop point: %d %d",loopOrder,loopRow); + int cmdPopularity[256]; + int delayPopularity[256]; + + int sortedCmdPopularity[16]; + int sortedDelayPopularity[16]; + unsigned char sortedCmd[16]; + unsigned char sortedDelay[16]; + + SafeWriter* chanStream[DIV_MAX_CHANS]; + unsigned int chanStreamOff[DIV_MAX_CHANS]; + bool wroteTick[DIV_MAX_CHANS]; + + memset(cmdPopularity,0,256*sizeof(int)); + memset(delayPopularity,0,256*sizeof(int)); + memset(chanStream,0,DIV_MAX_CHANS*sizeof(void*)); + memset(chanStreamOff,0,DIV_MAX_CHANS*sizeof(unsigned int)); + memset(sortedCmdPopularity,0,16*sizeof(int)); + memset(sortedDelayPopularity,0,16*sizeof(int)); + memset(sortedCmd,0,16); + memset(sortedDelay,0,16); + SafeWriter* w=new SafeWriter; w->init(); // write header if (binary) { w->write("FCS",4); + w->writeI(chans); + // offsets + for (int i=0; iinit(); + w->writeI(0); + } + // preset delays and speed dial + for (int i=0; i<32; i++) { + w->writeC(0); + } } else { w->writeText("# Furnace Command Stream\n\n"); @@ -451,19 +539,22 @@ SafeWriter* DivEngine::saveCommand(bool binary) { bool oldCmdStreamEnabled=cmdStreamEnabled; cmdStreamEnabled=true; double curDivider=divider; - int lastTick=0; + int lastTick[DIV_MAX_CHANS]; + + memset(lastTick,0,DIV_MAX_CHANS*sizeof(int)); while (!done) { if (nextTick(false,true) || !playing) { done=true; } // get command stream - bool wroteTick=false; + bool wroteTickGlobal=false; + memset(wroteTick,0,DIV_MAX_CHANS*sizeof(bool)); if (curDivider!=divider) { curDivider=divider; - WRITE_TICK; + WRITE_TICK(0); if (binary) { - w->writeC(0xfb); - w->writeI((int)(curDivider*65536)); + chanStream[0]->writeC(0xfb); + chanStream[0]->writeI((int)(curDivider*65536)); } else { w->writeText(fmt::sprintf(">> SET_RATE %f\n",curDivider)); } @@ -486,10 +577,10 @@ SafeWriter* DivEngine::saveCommand(bool binary) { case DIV_CMD_PRE_NOTE: break; default: - WRITE_TICK; + WRITE_TICK(i.chan); if (binary) { - w->writeC(i.chan); - writePackedCommandValues(w,i); + cmdPopularity[i.cmd]++; + writePackedCommandValues(chanStream[i.chan],i); } else { w->writeText(fmt::sprintf(" %d: %s %d %d\n",i.chan,cmdName[i.cmd],i.value,i.value2)); } @@ -502,7 +593,185 @@ SafeWriter* DivEngine::saveCommand(bool binary) { cmdStreamEnabled=oldCmdStreamEnabled; if (binary) { - w->writeC(0xff); + int sortCand=-1; + int sortPos=0; + while (sortPos<16) { + sortCand=-1; + for (int i=DIV_CMD_SAMPLE_MODE; i<256; i++) { + if (cmdPopularity[i]) { + if (sortCand==-1) { + sortCand=i; + } else if (cmdPopularity[sortCand]writeC(0xff); + // optimize stream + SafeWriter* oldStream=chanStream[i]; + SafeReader* reader=oldStream->toReader(); + chanStream[i]=new SafeWriter; + chanStream[i]->init(); + + while (1) { + try { + unsigned char next=reader->readC(); + switch (next) { + case 0xb8: // instrument + case 0xc0: // pre porta + case 0xc3: // vibrato range + case 0xc4: // vibrato shape + case 0xc5: // pitch + case 0xc7: // volume + case 0xca: // legato + chanStream[i]->writeC(next); + next=reader->readC(); + chanStream[i]->writeC(next); + break; + case 0xbe: // panning + case 0xc2: // vibrato + case 0xc6: // arpeggio + case 0xc8: // vol slide + case 0xc9: // porta + chanStream[i]->writeC(next); + next=reader->readC(); + chanStream[i]->writeC(next); + next=reader->readC(); + chanStream[i]->writeC(next); + break; + case 0xf0: { // full command (pre) + unsigned char cmd=reader->readC(); + bool foundShort=false; + for (int j=0; j<16; j++) { + if (sortedCmd[j]==cmd) { + chanStream[i]->writeC(0xd0+j); + foundShort=true; + break; + } + } + if (!foundShort) { + chanStream[i]->writeC(0xf7); // full command + chanStream[i]->writeC(cmd); + } + + unsigned char cmdLen=reader->readC(); + logD("cmdLen: %d",cmdLen); + for (unsigned char j=0; jreadC(); + chanStream[i]->writeC(next); + } + break; + } + case 0xfb: // tick rate + chanStream[i]->writeC(next); + next=reader->readC(); + chanStream[i]->writeC(next); + next=reader->readC(); + chanStream[i]->writeC(next); + next=reader->readC(); + chanStream[i]->writeC(next); + next=reader->readC(); + chanStream[i]->writeC(next); + break; + case 0xfc: { // 16-bit wait + unsigned short delay=reader->readS(); + bool foundShort=false; + for (int j=0; j<16; j++) { + if (sortedDelay[j]==delay) { + chanStream[i]->writeC(0xe0+j); + foundShort=true; + break; + } + } + if (!foundShort) { + chanStream[i]->writeC(next); + chanStream[i]->writeS(delay); + } + break; + } + case 0xfd: { // 8-bit wait + unsigned char delay=reader->readC(); + bool foundShort=false; + for (int j=0; j<16; j++) { + if (sortedDelay[j]==delay) { + chanStream[i]->writeC(0xe0+j); + foundShort=true; + break; + } + } + if (!foundShort) { + chanStream[i]->writeC(next); + chanStream[i]->writeC(delay); + } + break; + } + default: + chanStream[i]->writeC(next); + break; + } + } catch (EndOfFileException& e) { + break; + } + } + + oldStream->finish(); + delete oldStream; + } + + for (int i=0; itell(); + logI("- %d: off %x size %ld",i,chanStreamOff[i],chanStream[i]->size()); + w->write(chanStream[i]->getFinalBuf(),chanStream[i]->size()); + chanStream[i]->finish(); + delete chanStream[i]; + } + + w->seek(8,SEEK_SET); + for (int i=0; iwriteI(chanStreamOff[i]); + } + + logD("delay popularity:"); + for (int i=0; i<16; i++) { + w->writeC(sortedDelay[i]); + if (sortedDelayPopularity[i]) logD("- %d: %d",sortedDelay[i],sortedDelayPopularity[i]); + } + + logD("command popularity:"); + for (int i=0; i<16; i++) { + w->writeC(sortedCmd[i]); + if (sortedCmdPopularity[i]) logD("- %s: %d",cmdName[sortedCmd[i]],sortedCmdPopularity[i]); + } } else { if (!playing) { w->writeText(">> END\n"); @@ -985,6 +1254,16 @@ int DivEngine::loadSampleROM(String path, ssize_t expectedSize, unsigned char*& return 0; } +unsigned int DivEngine::getSampleFormatMask() { + unsigned int formatMask=1U<<16; // 16-bit is always on + for (int i=0; isampleFormatMask; + } + return formatMask; +} + int DivEngine::loadSampleROMs() { if (yrw801ROM!=NULL) { delete[] yrw801ROM; @@ -1016,6 +1295,8 @@ void DivEngine::renderSamples() { sPreview.pos=0; sPreview.dir=false; + logD("rendering samples..."); + // step 0: make sample format mask unsigned int formatMask=1U<<16; // 16-bit is always on for (int i=0; isetSkipRegisterWrites(true); + logV("goal: %d goalRow: %d",goal,goalRow); while (playing && curOrdervirtualTempoN)/MAX(1,curSubSong->virtualTempoD))<1 && curRow>=goalRow) break; } for (int i=0; isetSkipRegisterWrites(false); if (goal>0 || goalRow>0) { @@ -1734,6 +2051,7 @@ void DivEngine::playSub(bool preserveDrift, int goalRow) { subticks=1; prevOrder=curOrder; prevRow=curRow; + tempoAccum=0; } skipping=false; cmdStream.clear(); @@ -3256,7 +3574,7 @@ void DivEngine::exchangeIns(int one, int two) { for (size_t j=0; jpat[i].data[k]==NULL) continue; - for (int l=0; lpatLen; l++) { + for (int l=0; lpatLen; l++) { if (song.subsong[j]->pat[i].data[k]->data[l][2]==one) { song.subsong[j]->pat[i].data[k]->data[l][2]=two; } else if (song.subsong[j]->pat[i].data[k]->data[l][2]==two) { @@ -3304,6 +3622,7 @@ bool DivEngine::moveSampleUp(int which) { song.sample[which]=song.sample[which-1]; song.sample[which-1]=prev; saveLock.unlock(); + renderSamples(); BUSY_END; return true; } @@ -3344,6 +3663,7 @@ bool DivEngine::moveSampleDown(int which) { song.sample[which]=song.sample[which+1]; song.sample[which+1]=prev; saveLock.unlock(); + renderSamples(); BUSY_END; return true; } diff --git a/src/engine/engine.h b/src/engine/engine.h index 3a03c427..e524f0a4 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 "dev119" -#define DIV_ENGINE_VERSION 119 +#define DIV_VERSION "dev121" +#define DIV_ENGINE_VERSION 121 // for imports #define DIV_VERSION_MOD 0xff01 #define DIV_VERSION_FC 0xff02 @@ -906,6 +906,9 @@ class DivEngine { // load sample ROMs int loadSampleROMs(); + // get the sample format mask + unsigned int getSampleFormatMask(); + // UNSAFE render samples - only execute when locked void renderSamples(); @@ -955,6 +958,9 @@ class DivEngine { // get warnings String getWarnings(); + // get debug info + String getPlaybackDebugInfo(); + // switch master bool switchMaster(); diff --git a/src/engine/fileOps.cpp b/src/engine/fileOps.cpp index 3ffb586e..a13182e3 100644 --- a/src/engine/fileOps.cpp +++ b/src/engine/fileOps.cpp @@ -174,7 +174,8 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { ds.noOPN2Vol=true; ds.newVolumeScaling=false; ds.volMacroLinger=false; - ds.brokenOutVol=true; // ??? + ds.brokenOutVol=true; + ds.brokenOutVol2=true; ds.e1e2StopOnSameNote=true; ds.brokenPortaArp=false; ds.snNoLowPeriods=true; @@ -206,6 +207,9 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { ds.tuning=433.2; }*/ + // Game Boy arp+soundLen screwery + ds.systemFlags[0].set("enoughAlready",true); + logI("reading module data..."); if (ds.version>0x0c) { ds.subsong[0]->hilightA=reader.readC(); @@ -490,6 +494,19 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) { ins->fm.op[j].ssgEnv ); } + + // swap alg operator 2 and 3 if YMU759 + if (ds.system[0]==DIV_SYSTEM_YMU759 && ins->fm.ops==4) { + DivInstrumentFM::Operator oldOp=ins->fm.op[2]; + ins->fm.op[2]=ins->fm.op[1]; + ins->fm.op[1]=oldOp; + + if (ins->fm.alg==1) { + ins->fm.alg=2; + } else if (ins->fm.alg==2) { + ins->fm.alg=1; + } + } } else { // STD if (ds.system[0]!=DIV_SYSTEM_GB || ds.version<0x12) { ins->std.volMacro.len=reader.readC(); @@ -1686,6 +1703,9 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { if (ds.version<117) { ds.disableSampleMacro=true; } + if (ds.version<121) { + ds.brokenOutVol2=false; + } ds.isDMF=false; reader.readS(); // reserved @@ -2123,7 +2143,12 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) { } else { reader.readC(); } - for (int i=0; i<2; i++) { + if (ds.version>=121) { + ds.brokenOutVol2=reader.readC(); + } else { + reader.readC(); + } + for (int i=0; i<1; i++) { reader.readC(); } } @@ -4461,7 +4486,8 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) { w->writeC(song.jumpTreatment); w->writeC(song.autoSystem); w->writeC(song.disableSampleMacro); - for (int i=0; i<2; i++) { + w->writeC(song.brokenOutVol2); + for (int i=0; i<1; i++) { w->writeC(0); } @@ -4977,8 +5003,6 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { } } - // TODO: take care of new arp macro format - w->writeC(i->std.arpMacro.len); bool arpMacroMode=false; int arpMacroHowManyFixed=0; int realArpMacroLen=i->std.arpMacro.len; @@ -4996,13 +5020,25 @@ SafeWriter* DivEngine::saveDMF(unsigned char version) { } } + if (realArpMacroLen>127) realArpMacroLen=127; + + w->writeC(realArpMacroLen); + if (arpMacroMode) { for (int j=0; jwriteI(i->std.arpMacro.val[j]); + if ((i->std.arpMacro.val[j]&0xc0000000)==0x40000000 || (i->std.arpMacro.val[j]&0xc0000000)==0x80000000) { + w->writeI(i->std.arpMacro.val[j]^0x40000000); + } else { + w->writeI(i->std.arpMacro.val[j]); + } } } else { for (int j=0; jwriteI(i->std.arpMacro.val[j]+12); + if ((i->std.arpMacro.val[j]&0xc0000000)==0x40000000 || (i->std.arpMacro.val[j]&0xc0000000)==0x80000000) { + w->writeI((i->std.arpMacro.val[j]^0x40000000)+12); + } else { + w->writeI(i->std.arpMacro.val[j]+12); + } } } if (realArpMacroLen>0) { diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index abb57edb..fe420bd5 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -1501,12 +1501,11 @@ bool DivInstrument::saveDMP(const char* path) { if (std.volMacro.len>0) w->writeC(std.volMacro.loop); } - w->writeC(std.arpMacro.len); bool arpMacroMode=false; int arpMacroHowManyFixed=0; int realArpMacroLen=std.arpMacro.len; - for (int i=0; i127) realArpMacroLen=127; + + w->writeC(realArpMacroLen); + if (arpMacroMode) { - for (int i=0; iwriteI(std.arpMacro.val[i]); + for (int j=0; jwriteI(std.arpMacro.val[j]^0x40000000); + } else { + w->writeI(std.arpMacro.val[j]); + } } } else { - for (int i=0; iwriteI(std.arpMacro.val[i]+12); + for (int j=0; jwriteI((std.arpMacro.val[j]^0x40000000)+12); + } else { + w->writeI(std.arpMacro.val[j]+12); + } } } if (realArpMacroLen>0) { diff --git a/src/engine/instrument.h b/src/engine/instrument.h index 2543c6be..10b6f537 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -179,11 +179,13 @@ struct DivInstrumentMacro { String name; int val[256]; unsigned int mode; - bool open; + unsigned char open; unsigned char len, delay, speed, loop, rel; // the following variables are used by the GUI and not saved in the file int vScroll, vZoom; + int typeMemory[16]; + unsigned char lenMemory; explicit DivInstrumentMacro(const String& n, bool initOpen=false): name(n), @@ -195,8 +197,10 @@ struct DivInstrumentMacro { loop(255), rel(255), vScroll(0), - vZoom(-1) { + vZoom(-1), + lenMemory(0) { memset(val,0,256*sizeof(int)); + memset(typeMemory,0,16*sizeof(int)); } }; diff --git a/src/engine/macroInt.cpp b/src/engine/macroInt.cpp index 955481ce..9e619cb8 100644 --- a/src/engine/macroInt.cpp +++ b/src/engine/macroInt.cpp @@ -21,10 +21,28 @@ #include "instrument.h" #include "engine.h" +#define ADSR_LOW source.val[0] +#define ADSR_HIGH source.val[1] +#define ADSR_AR source.val[2] +#define ADSR_HT source.val[3] +#define ADSR_DR source.val[4] +#define ADSR_SL source.val[5] +#define ADSR_ST source.val[6] +#define ADSR_SR source.val[7] +#define ADSR_RR source.val[8] + +#define LFO_SPEED source.val[11] +#define LFO_WAVE source.val[12] +#define LFO_PHASE source.val[13] +#define LFO_LOOP source.val[14] +#define LFO_GLOBAL source.val[15] + void DivMacroStruct::prepare(DivInstrumentMacro& source, DivEngine* e) { has=had=actualHad=will=true; mode=source.mode; + type=(source.open>>1)&3; linger=(source.name=="vol" && e->song.volMacroLinger); + lfoPos=LFO_PHASE; } void DivMacroStruct::doMacro(DivInstrumentMacro& source, bool released, bool tick) { @@ -53,24 +71,85 @@ void DivMacroStruct::doMacro(DivInstrumentMacro& source, bool released, bool tic } actualHad=has; had=actualHad; + if (has) { - lastPos=pos; - val=source.val[pos++]; - if (pos>source.rel && !released) { - if (source.loopsource.rel && !released) { + if (source.loop=source.len) { + if (source.loop=source.rel || source.rel>=source.len)) { + pos=source.loop; + } else if (linger) { + pos--; + } else { + has=false; + } } } - if (pos>=source.len) { - if (source.loop=source.rel || source.rel>=source.len)) { - pos=source.loop; - } else if (linger) { - pos--; - } else { - has=false; + if (type==1) { // ADSR + if (released && lastPos<3) lastPos=3; + switch (lastPos) { + case 0: // attack + pos+=ADSR_AR; + if (pos>255) { + pos=255; + lastPos=1; + delay=ADSR_HT; + } + break; + case 1: // decay + pos-=ADSR_DR; + if (pos<=ADSR_SL) { + pos=ADSR_SL; + lastPos=2; + delay=ADSR_ST; + } + break; + case 2: // sustain + pos-=ADSR_SR; + if (pos<0) { + pos=0; + lastPos=4; + } + break; + case 3: // release + pos-=ADSR_RR; + if (pos<0) { + pos=0; + lastPos=4; + } + break; + case 4: // end + pos=0; + if (!linger) has=false; + break; } + val=ADSR_LOW+((pos+(ADSR_HIGH-ADSR_LOW)*pos)>>8); + } + if (type==2) { // LFO + lfoPos+=LFO_SPEED; + lfoPos&=1023; + + int lfoOut=0; + switch (LFO_WAVE&3) { + case 0: // triangle + lfoOut=((lfoPos&512)?(1023-lfoPos):(lfoPos))>>1; + break; + case 1: // saw + lfoOut=lfoPos>>2; + break; + case 2: // pulse + lfoOut=(lfoPos&512)?255:0; + break; + } + val=ADSR_LOW+((lfoOut+(ADSR_HIGH-ADSR_LOW)*lfoOut)>>8); } } } @@ -253,7 +332,14 @@ void DivMacroInt::init(DivInstrument* which) { for (size_t i=0; iprepare(*macroSource[i],e); - hasRelease=(macroSource[i]->rellen); + // check ADSR mode + if ((macroSource[i]->open&6)==4) { + hasRelease=false; + } else if ((macroSource[i]->open&6)==2) { + hasRelease=true; + } else { + hasRelease=(macroSource[i]->rellen); + } } else { hasRelease=false; } diff --git a/src/engine/macroInt.h b/src/engine/macroInt.h index 5208dc54..ac829a57 100644 --- a/src/engine/macroInt.h +++ b/src/engine/macroInt.h @@ -25,13 +25,13 @@ class DivEngine; struct DivMacroStruct { - int pos, lastPos, delay; + int pos, lastPos, lfoPos, delay; int val; bool has, had, actualHad, finished, will, linger, began; - unsigned int mode; + unsigned int mode, type; void doMacro(DivInstrumentMacro& source, bool released, bool tick); void init() { - pos=lastPos=mode=delay=0; + pos=lastPos=lfoPos=mode=type=delay=0; has=had=actualHad=will=false; linger=false; began=true; @@ -42,6 +42,7 @@ struct DivMacroStruct { DivMacroStruct(): pos(0), lastPos(0), + lfoPos(0), delay(0), val(0), has(false), @@ -51,7 +52,8 @@ struct DivMacroStruct { will(false), linger(false), began(true), - mode(0) {} + mode(0), + type(0) {} }; class DivMacroInt { diff --git a/src/engine/platform/ay.cpp b/src/engine/platform/ay.cpp index b01d6a9f..ab2c2c72 100644 --- a/src/engine/platform/ay.cpp +++ b/src/engine/platform/ay.cpp @@ -776,6 +776,7 @@ void DivPlatformAY8910::setFlags(const DivConfig& flags) { if (extMode) { chipClock=extClock; rate=chipClock/extDiv; + clockSel=false; } else { clockSel=flags.getBool("halfClock",false); switch (flags.getInt("clockSel",0)) { diff --git a/src/engine/platform/c64.cpp b/src/engine/platform/c64.cpp index 6e98fd8a..9fb10d41 100644 --- a/src/engine/platform/c64.cpp +++ b/src/engine/platform/c64.cpp @@ -129,18 +129,6 @@ void DivPlatformC64::tick(bool sysTick) { rWrite(i*7+2,chan[i].duty&0xff); rWrite(i*7+3,chan[i].duty>>8); } - if (sysTick) { - if (chan[i].testWhen>0) { - if (--chan[i].testWhen<1) { - if (!chan[i].resetMask && !chan[i].inPorta) { - DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_C64); - rWrite(i*7+5,0); - rWrite(i*7+6,0); - rWrite(i*7+4,(chan[i].wave<<4)|(ins->c64.noTest?0:8)|(chan[i].test<<3)|(chan[i].ring<<2)|(chan[i].sync<<1)); - } - } - } - } if (chan[i].std.wave.had) { chan[i].wave=chan[i].std.wave.val; rWrite(i*7+4,(chan[i].wave<<4)|(chan[i].test<<3)|(chan[i].ring<<2)|(chan[i].sync<<1)|(int)(chan[i].active)); @@ -173,6 +161,19 @@ void DivPlatformC64::tick(bool sysTick) { rWrite(i*7+4,(chan[i].wave<<4)|(chan[i].test<<3)|(chan[i].ring<<2)|(chan[i].sync<<1)|(int)(chan[i].active)); } + if (sysTick) { + if (chan[i].testWhen>0) { + if (--chan[i].testWhen<1) { + if (!chan[i].resetMask && !chan[i].inPorta) { + DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_C64); + rWrite(i*7+5,0); + rWrite(i*7+6,0); + rWrite(i*7+4,(chan[i].wave<<4)|(ins->c64.noTest?0:8)|(chan[i].test<<3)|(chan[i].ring<<2)|(chan[i].sync<<1)); + } + } + } + } + if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,8,chan[i].pitch2,chipClock,CHIP_FREQBASE); if (chan[i].freq>0xffff) chan[i].freq=0xffff; diff --git a/src/engine/platform/gb.cpp b/src/engine/platform/gb.cpp index 0c9ab7b3..6a88d0f7 100644 --- a/src/engine/platform/gb.cpp +++ b/src/engine/platform/gb.cpp @@ -169,7 +169,9 @@ void DivPlatformGB::tick(bool sysTick) { if (chan[i].baseFreq>255) chan[i].baseFreq=255; if (chan[i].baseFreq<0) chan[i].baseFreq=0; } else { - chan[i].baseFreq=NOTE_PERIODIC(parent->calcArp(chan[i].note,chan[i].std.arp.val,24)); + if (!chan[i].inPorta) { + chan[i].baseFreq=NOTE_PERIODIC(parent->calcArp(chan[i].note,chan[i].std.arp.val,24)); + } } chan[i].freqChanged=true; } @@ -313,6 +315,9 @@ void DivPlatformGB::tick(bool sysTick) { rWrite(16+i*5+3,(2048-chan[i].freq)&0xff); rWrite(16+i*5+4,(((2048-chan[i].freq)>>8)&7)|((chan[i].keyOn||chan[i].keyOff)?0x80:0x00)|((chan[i].soundLen<63)<<6)); } + if (enoughAlready) { // more compat garbage + rWrite(16+i*5+1,((chan[i].duty&3)<<6)|(63-(chan[i].soundLen&63))); + } if (chan[i].keyOn) chan[i].keyOn=false; if (chan[i].keyOff) chan[i].keyOff=false; chan[i].freqChanged=false; @@ -645,6 +650,7 @@ void DivPlatformGB::setFlags(const DivConfig& flags) { model=GB_MODEL_AGB; break; } + enoughAlready=flags.getBool("enoughAlready",false); } int DivPlatformGB::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) { diff --git a/src/engine/platform/gb.h b/src/engine/platform/gb.h index d0a2c1f3..17a17da1 100644 --- a/src/engine/platform/gb.h +++ b/src/engine/platform/gb.h @@ -76,6 +76,7 @@ class DivPlatformGB: public DivDispatch { DivDispatchOscBuffer* oscBuf[4]; bool isMuted[4]; bool antiClickEnabled; + bool enoughAlready; unsigned char lastPan; DivWaveSynth ws; struct QueuedWrite { diff --git a/src/engine/platform/msm5232.cpp b/src/engine/platform/msm5232.cpp index f311b113..84e8d91b 100644 --- a/src/engine/platform/msm5232.cpp +++ b/src/engine/platform/msm5232.cpp @@ -17,6 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +#define _USE_MATH_DEFINES #include "msm5232.h" #include "../engine.h" #include @@ -52,13 +53,26 @@ void DivPlatformMSM5232::acquire(short* bufL, short* bufR, size_t start, size_t regPool[w.addr&0x0f]=w.val; writes.pop(); } - memset(temp,0,16*sizeof(short)); - /*for (int i=0; i<8; i++) { - oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP((pce->channel[i].blip_prev_samp[0]+pce->channel[i].blip_prev_samp[1])<<1,-32768,32767); - }*/ + for (int i=0; i<8; i++) { + int o=( + ((regPool[12+(i>>4)]&1)?((msm->vo16[i]*partVolume[3+(i&4)])>>8):0)+ + ((regPool[12+(i>>4)]&2)?((msm->vo8[i]*partVolume[2+(i&4)])>>8):0)+ + ((regPool[12+(i>>4)]&4)?((msm->vo4[i]*partVolume[1+(i&4)])>>8):0)+ + ((regPool[12+(i>>4)]&8)?((msm->vo2[i]*partVolume[i&4])>>8):0) + )<<3; + oscBuf[i]->data[oscBuf[i]->needle++]=CLAMP(o,-32768,32767); + } - msm->sound_stream_update(temp); + clockDriftLFOPos+=clockDriftLFOSpeed; + clockDriftLFOPos&=(1U<<21)-1; + clockDriftAccum+=clockDriftLFOWave[clockDriftLFOPos>>13]; + if (clockDriftAccum>=2048) { + clockDriftAccum-=2048; + } else { + memset(temp,0,16*sizeof(short)); + msm->sound_stream_update(temp); + } //printf("tempL: %d tempR: %d\n",tempL,tempR); bufL[h]=0; @@ -229,10 +243,22 @@ int DivPlatformMSM5232::dispatch(DivCommand c) { } break; } + case DIV_CMD_WAVE: + groupControl[c.chan>>2]=c.value&0x1f; + updateGroup[c.chan>>2]=true; + break; case DIV_CMD_STD_NOISE_MODE: chan[c.chan].noise=c.value; chan[c.chan].freqChanged=true; break; + case DIV_CMD_FM_AR: + groupAR[c.chan>>2]=attackMap[c.value&7]; + updateGroupAR[c.chan>>2]=true; + break; + case DIV_CMD_FM_DR: + groupDR[c.chan>>2]=decayMap[c.value&15]; + updateGroupDR[c.chan>>2]=true; + break; case DIV_CMD_LEGATO: chan[c.chan].baseFreq=NOTE_LINEAR(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0))); chan[c.chan].freqChanged=true; @@ -310,6 +336,8 @@ void DivPlatformMSM5232::reset() { cycles=0; curChan=-1; delay=500; + clockDriftLFOPos=0; + clockDriftAccum=0; for (int i=0; i<2; i++) { groupControl[i]=15|(groupEnv[i]?0x20:0); @@ -381,6 +409,11 @@ void DivPlatformMSM5232::setFlags(const DivConfig& flags) { capacitance[6]*0.000000001, capacitance[7]*0.000000001 ); + + for (int i=0; i<256; i++) { + clockDriftLFOWave[i]=(1.0+sin(M_PI*(double)i/128.0))*flags.getInt("vibDepth",0.0f); + } + clockDriftLFOSpeed=flags.getInt("vibSpeed",0); } void DivPlatformMSM5232::poke(unsigned int addr, unsigned short val) { diff --git a/src/engine/platform/msm5232.h b/src/engine/platform/msm5232.h index 34412b9a..3e2bf71a 100644 --- a/src/engine/platform/msm5232.h +++ b/src/engine/platform/msm5232.h @@ -57,6 +57,7 @@ class DivPlatformMSM5232: public DivDispatch { DivDispatchOscBuffer* oscBuf[8]; int partVolume[8]; int initPartVolume[8]; + int clockDriftLFOWave[256]; double capacitance[8]; bool isMuted[8]; bool updateGroup[2]; @@ -73,7 +74,8 @@ class DivPlatformMSM5232: public DivDispatch { }; std::queue writes; - int cycles, curChan, delay, detune; + int cycles, curChan, delay, detune, clockDriftAccum; + unsigned int clockDriftLFOPos, clockDriftLFOSpeed; short temp[16]; msm5232_device* msm; unsigned char regPool[128]; diff --git a/src/engine/platform/msm6258.cpp b/src/engine/platform/msm6258.cpp index 3d8ec38c..17e88ae6 100644 --- a/src/engine/platform/msm6258.cpp +++ b/src/engine/platform/msm6258.cpp @@ -167,7 +167,7 @@ int DivPlatformMSM6258::dispatch(DivCommand c) { chan[c.chan].sample=-1; chan[c.chan].macroInit(NULL); chan[c.chan].outVol=chan[c.chan].vol; - if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) { + if ((12*sampleBank+c.value%12)<0 || (12*sampleBank+c.value%12)>=parent->song.sampleLen) { break; } //DivSample* s=parent->getSample(12*sampleBank+c.value%12); diff --git a/src/engine/platform/msm6295.cpp b/src/engine/platform/msm6295.cpp index 0f570d9c..d22aabd5 100644 --- a/src/engine/platform/msm6295.cpp +++ b/src/engine/platform/msm6295.cpp @@ -158,7 +158,7 @@ int DivPlatformMSM6295::dispatch(DivCommand c) { chan[c.chan].sample=-1; chan[c.chan].macroInit(NULL); chan[c.chan].outVol=chan[c.chan].vol; - if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) { + if ((12*sampleBank+c.value%12)<0 || (12*sampleBank+c.value%12)>=parent->song.sampleLen) { break; } //DivSample* s=parent->getSample(12*sampleBank+c.value%12); diff --git a/src/engine/platform/nes.cpp b/src/engine/platform/nes.cpp index 5749167b..a338cca8 100644 --- a/src/engine/platform/nes.cpp +++ b/src/engine/platform/nes.cpp @@ -416,10 +416,12 @@ int DivPlatformNES::dispatch(DivCommand c) { if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; } - if (c.chan==2) { - rWrite(0x4000+c.chan*4,0xff); - } else { - rWrite(0x4000+c.chan*4,0x30|chan[c.chan].vol|((chan[c.chan].duty&3)<<6)); + if (!parent->song.brokenOutVol2) { + if (c.chan==2) { + rWrite(0x4000+c.chan*4,0xff); + } else { + rWrite(0x4000+c.chan*4,0x30|chan[c.chan].vol|((chan[c.chan].duty&3)<<6)); + } } break; case DIV_CMD_NOTE_OFF: diff --git a/src/engine/platform/sms.cpp b/src/engine/platform/sms.cpp index ffafc0cb..83ce707f 100644 --- a/src/engine/platform/sms.cpp +++ b/src/engine/platform/sms.cpp @@ -19,6 +19,7 @@ #include "sms.h" #include "../engine.h" +#include "../../ta-log.h" #include #define rWrite(a,v) {if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(0x200+a,v);}}} @@ -248,7 +249,9 @@ int DivPlatformSMS::dispatch(DivCommand c) { chan[c.chan].actualNote=c.value; } chan[c.chan].active=true; - rWrite(0,0x90|c.chan<<5|(isMuted[c.chan]?15:(15-(chan[c.chan].vol&15)))); + if (!parent->song.brokenOutVol2) { + rWrite(0,0x90|c.chan<<5|(isMuted[c.chan]?15:(15-(chan[c.chan].vol&15)))); + } chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_STD)); if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { chan[c.chan].outVol=chan[c.chan].vol; diff --git a/src/engine/platform/snes.cpp b/src/engine/platform/snes.cpp index 0edc88aa..8ea97c93 100644 --- a/src/engine/platform/snes.cpp +++ b/src/engine/platform/snes.cpp @@ -69,14 +69,16 @@ void DivPlatformSNES::acquire(short* bufL, short* bufR, size_t start, size_t len short out[2]; short chOut[16]; for (size_t h=start; hgetSample(chan[i].sample); double off=(s->centerRate>=1)?((double)s->centerRate/8363.0):1.0; @@ -205,6 +232,7 @@ void DivPlatformSNES::tick(bool sysTick) { sampleMem[tabAddr+2]=loop&0xff; sampleMem[tabAddr+3]=loop>>8; kon|=(1<>1; chan[c.chan].panR=c.value2>>1; - writeOutVol(c.chan); + chan[c.chan].shallWriteVol=true; break; case DIV_CMD_PITCH: chan[c.chan].pitch=c.value; @@ -418,7 +462,7 @@ int DivPlatformSNES::dispatch(DivCommand c) { case DIV_CMD_SNES_INVERT: chan[c.chan].invertL=(c.value>>4); chan[c.chan].invertR=c.chan&15; - writeOutVol(c.chan); + chan[c.chan].shallWriteVol=true; break; case DIV_CMD_SNES_GAIN_MODE: if (c.value) { @@ -443,7 +487,7 @@ int DivPlatformSNES::dispatch(DivCommand c) { } else { chan[c.chan].state.useEnv=true; } - writeEnv(c.chan); + chan[c.chan].shallWriteEnv=true; break; case DIV_CMD_SNES_GAIN: if (chan[c.chan].state.gainMode==DivInstrumentSNES::GAIN_MODE_DIRECT) { @@ -451,7 +495,7 @@ int DivPlatformSNES::dispatch(DivCommand c) { } else { chan[c.chan].state.gain=c.value&0x1f; } - if (!chan[c.chan].state.useEnv) writeEnv(c.chan); + if (!chan[c.chan].state.useEnv) chan[c.chan].shallWriteEnv=true; break; case DIV_CMD_STD_NOISE_FREQ: noiseFreq=c.value&0x1f; @@ -459,19 +503,19 @@ int DivPlatformSNES::dispatch(DivCommand c) { break; case DIV_CMD_FM_AR: chan[c.chan].state.a=c.value&15; - if (chan[c.chan].state.useEnv) writeEnv(c.chan); + if (chan[c.chan].state.useEnv) chan[c.chan].shallWriteEnv=true; break; case DIV_CMD_FM_DR: chan[c.chan].state.d=c.value&7; - if (chan[c.chan].state.useEnv) writeEnv(c.chan); + if (chan[c.chan].state.useEnv) chan[c.chan].shallWriteEnv=true; break; case DIV_CMD_FM_SL: chan[c.chan].state.s=c.value&7; - if (chan[c.chan].state.useEnv) writeEnv(c.chan); + if (chan[c.chan].state.useEnv) chan[c.chan].shallWriteEnv=true; break; case DIV_CMD_FM_RR: chan[c.chan].state.r=c.value&0x1f; - if (chan[c.chan].state.useEnv) writeEnv(c.chan); + if (chan[c.chan].state.useEnv) chan[c.chan].shallWriteEnv=true; break; case DIV_CMD_SNES_ECHO: chan[c.chan].echo=c.value; @@ -582,7 +626,7 @@ void DivPlatformSNES::writeEnv(int ch) { void DivPlatformSNES::muteChannel(int ch, bool mute) { isMuted[ch]=mute; - writeOutVol(ch); + chan[ch].shallWriteVol=true; } void DivPlatformSNES::forceIns() { @@ -760,6 +804,10 @@ void DivPlatformSNES::renderSamples() { if (actualLength>0) { sampleOff[i]=memPos; memcpy(©OfSampleMem[memPos],s->dataBRR,actualLength); + // inject loop if needed + if (s->loop) { + copyOfSampleMem[memPos+actualLength-9]|=3; + } memPos+=actualLength; } if (actualLengthmute) { - o16 += ( (out16-(1<<(STEP_SH-1))) * voi->egvol) >> STEP_SH; - o8 += ( (out8 -(1<<(STEP_SH-1))) * voi->egvol) >> STEP_SH; - o4 += ( (out4 -(1<<(STEP_SH-1))) * voi->egvol) >> STEP_SH; - o2 += ( (out2 -(1<<(STEP_SH-1))) * voi->egvol) >> STEP_SH; + o16 += vo16[groupidx*4+(4-i)] = ( (out16-(1<<(STEP_SH-1))) * voi->egvol) >> STEP_SH; + o8 += vo8 [groupidx*4+(4-i)] = ( (out8 -(1<<(STEP_SH-1))) * voi->egvol) >> STEP_SH; + o4 += vo4 [groupidx*4+(4-i)] = ( (out4 -(1<<(STEP_SH-1))) * voi->egvol) >> STEP_SH; + o2 += vo2 [groupidx*4+(4-i)] = ( (out2 -(1<<(STEP_SH-1))) * voi->egvol) >> STEP_SH; if (i == 1 && groupidx == 1) { diff --git a/src/engine/platform/sound/oki/msm5232.h b/src/engine/platform/sound/oki/msm5232.h index ecf8d1e8..641b460e 100644 --- a/src/engine/platform/sound/oki/msm5232.h +++ b/src/engine/platform/sound/oki/msm5232.h @@ -33,6 +33,11 @@ public: int get_rate(); + int vo16[8]; + int vo8[8]; + int vo4[8]; + int vo2[8]; + private: struct VOICE { uint8_t mode; diff --git a/src/engine/platform/sound/t6w28/T6W28_Apu.cpp b/src/engine/platform/sound/t6w28/T6W28_Apu.cpp new file mode 100644 index 00000000..110bb5ae --- /dev/null +++ b/src/engine/platform/sound/t6w28/T6W28_Apu.cpp @@ -0,0 +1,361 @@ +// T6W28_Snd_Emu + +#include "T6W28_Apu.h" + +#undef require +#define require( expr ) assert( expr ) + +/* Copyright (C) 2003-2006 Shay Green. This module is free software; you +can redistribute it and/or modify it under the terms of the GNU Lesser +General Public License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. This +module 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 Lesser General Public License for +more details. You should have received a copy of the GNU Lesser General +Public License along with this module; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ + +// T6W28_Osc + +namespace MDFN_IEN_NGP +{ + +T6W28_Osc::T6W28_Osc() +{ + outputs [0] = NULL; // always stays NULL + outputs [1] = NULL; + outputs [2] = NULL; + outputs [3] = NULL; +} + +void T6W28_Osc::reset() +{ + delay = 0; + last_amp_left = 0; + last_amp_right = 0; + + volume_left = 0; + volume_right = 0; +} + +// T6W28_Square + +blip_inline void T6W28_Square::reset() +{ + period = 0; + phase = 0; + T6W28_Osc::reset(); +} + +void T6W28_Square::run( sms_time_t time, sms_time_t end_time ) +{ + if ((!volume_left && !volume_right) || period <= 128 ) + { + // ignore 16kHz and higher + if ( last_amp_left ) + { + synth->offset( time, -last_amp_left, outputs[2] ); + last_amp_left = 0; + } + + if ( last_amp_right ) + { + synth->offset( time, -last_amp_right, outputs[1] ); + last_amp_right = 0; + } + + time += delay; + if ( !period ) + { + time = end_time; + } + else if ( time < end_time ) + { + // keep calculating phase + int count = (end_time - time + period - 1) / period; + phase = (phase + count) & 1; + time += count * period; + } + } + else + { + int amp_left = phase ? volume_left : -volume_left; + int amp_right = phase ? volume_right : -volume_right; + + { + int delta_left = amp_left - last_amp_left; + int delta_right = amp_right - last_amp_right; + + if ( delta_left ) + { + last_amp_left = amp_left; + synth->offset( time, delta_left, outputs[2] ); + } + + if ( delta_right ) + { + last_amp_right = amp_right; + synth->offset( time, delta_right, outputs[1] ); + } + } + + time += delay; + if ( time < end_time ) + { + Blip_Buffer* const output_left = this->outputs[2]; + Blip_Buffer* const output_right = this->outputs[1]; + + int delta_left = amp_left * 2; + int delta_right = amp_right * 2; + do + { + delta_left = -delta_left; + delta_right = -delta_right; + + synth->offset_inline( time, delta_left, output_left ); + synth->offset_inline( time, delta_right, output_right ); + time += period; + phase ^= 1; + } + while ( time < end_time ); + + this->last_amp_left = phase ? volume_left : -volume_left; + this->last_amp_right = phase ? volume_right : -volume_right; + } + } + delay = time - end_time; +} + +// T6W28_Noise + +static const int noise_periods [3] = { 0x100, 0x200, 0x400 }; + +blip_inline void T6W28_Noise::reset() +{ + period = &noise_periods [0]; + shifter = 0x4000; + tap = 13; + period_extra = 0; + T6W28_Osc::reset(); +} + +void T6W28_Noise::run( sms_time_t time, sms_time_t end_time ) +{ + int amp_left = volume_left; + int amp_right = volume_right; + + if ( shifter & 1 ) + { + amp_left = -amp_left; + amp_right = -amp_right; + } + + { + int delta_left = amp_left - last_amp_left; + int delta_right = amp_right - last_amp_right; + + if ( delta_left ) + { + last_amp_left = amp_left; + synth.offset( time, delta_left, outputs[2] ); + } + + if ( delta_right ) + { + last_amp_right = amp_right; + synth.offset( time, delta_right, outputs[1] ); + } + } + + time += delay; + + if ( !volume_left && !volume_right ) + time = end_time; + + if ( time < end_time ) + { + Blip_Buffer* const output_left = this->outputs[2]; + Blip_Buffer* const output_right = this->outputs[1]; + + unsigned l_shifter = this->shifter; + int delta_left = amp_left * 2; + int delta_right = amp_right * 2; + + int l_period = *this->period * 2; + if ( !l_period ) + l_period = 16; + + do + { + int changed = (l_shifter + 1) & 2; // set if prev and next bits differ + l_shifter = (((l_shifter << 14) ^ (l_shifter << tap)) & 0x4000) | (l_shifter >> 1); + if ( changed ) + { + delta_left = -delta_left; + synth.offset_inline( time, delta_left, output_left ); + + delta_right = -delta_right; + synth.offset_inline( time, delta_right, output_right ); + } + time += l_period; + } + while ( time < end_time ); + + this->shifter = l_shifter; + this->last_amp_left = delta_left >> 1; + this->last_amp_right = delta_right >> 1; + } + delay = time - end_time; +} + +// T6W28_Apu + +T6W28_Apu::T6W28_Apu() +{ + for ( int i = 0; i < 3; i++ ) + { + squares [i].synth = &square_synth; + oscs [i] = &squares [i]; + } + oscs [3] = &noise; + + volume( 1.0 ); + reset(); +} + +T6W28_Apu::~T6W28_Apu() +{ +} + + +void T6W28_Apu::osc_output( int index, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ) +{ + require( (unsigned) index < osc_count ); + require( (center && left && right) || (!center && !left && !right) ); + T6W28_Osc& osc = *oscs [index]; + osc.outputs [1] = right; + osc.outputs [2] = left; + osc.outputs [3] = center; +} + +void T6W28_Apu::output( Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ) +{ + for ( int i = 0; i < osc_count; i++ ) + osc_output( i, center, left, right ); +} + +void T6W28_Apu::reset() +{ + last_time = 0; + latch_left = 0; + latch_right = 0; + + squares [0].reset(); + squares [1].reset(); + squares [2].reset(); + noise.reset(); +} + +void T6W28_Apu::run_until( sms_time_t end_time ) +{ + require( end_time >= last_time ); // end_time must not be before previous time + + if ( end_time > last_time ) + { + // run oscillators + for ( int i = 0; i < osc_count; ++i ) + { + T6W28_Osc& osc = *oscs [i]; + if ( osc.outputs[1] ) + { + if ( i < 3 ) + squares [i].run( last_time, end_time ); + else + noise.run( last_time, end_time ); + } + } + + last_time = end_time; + } +} + +bool T6W28_Apu::end_frame( sms_time_t end_time ) +{ + if ( end_time > last_time ) + run_until( end_time ); + + assert( last_time >= end_time ); + last_time -= end_time; + + return(1); +} + +static const unsigned char volumes [16] = { + // volumes [i] = 64 * pow( 1.26, 15 - i ) / pow( 1.26, 15 ) + 64, 50, 39, 31, 24, 19, 15, 12, 9, 7, 5, 4, 3, 2, 1, 0 +}; + +void T6W28_Apu::write_data_left( sms_time_t time, int data ) +{ + require( (unsigned) data <= 0xFF ); + + run_until( time ); + + if ( data & 0x80 ) + latch_left = data; + + int index = (latch_left >> 5) & 3; + + if ( latch_left & 0x10 ) + { + oscs [index]->volume_left = volumes [data & 15]; + } + else if ( index < 3 ) + { + T6W28_Square& sq = squares [index]; + if ( data & 0x80 ) + sq.period = (sq.period & 0xFF00) | (data << 4 & 0x00FF); + else + sq.period = (sq.period & 0x00FF) | (data << 8 & 0x3F00); + } +} + +void T6W28_Apu::write_data_right( sms_time_t time, int data ) +{ + require( (unsigned) data <= 0xFF ); + + run_until( time ); + + if ( data & 0x80 ) + latch_right = data; + + int index = (latch_right >> 5) & 3; + //printf("%d\n", index); + + if ( latch_right & 0x10 ) + { + oscs [index]->volume_right = volumes [data & 15]; + } + else if ( index == 2 ) + { + if ( data & 0x80 ) + noise.period_extra = (noise.period_extra & 0xFF00) | (data << 4 & 0x00FF); + else + noise.period_extra = (noise.period_extra & 0x00FF) | (data << 8 & 0x3F00); + } + else if(index == 3) + { + int select = data & 3; + if ( select < 3 ) + noise.period = &noise_periods [select]; + else + noise.period = &noise.period_extra; + + int const tap_disabled = 16; + noise.tap = (data & 0x04) ? 13 : tap_disabled; + noise.shifter = 0x4000; + } +} + +} diff --git a/src/engine/platform/sound/t6w28/T6W28_Apu.h b/src/engine/platform/sound/t6w28/T6W28_Apu.h new file mode 100644 index 00000000..e783fa81 --- /dev/null +++ b/src/engine/platform/sound/t6w28/T6W28_Apu.h @@ -0,0 +1,89 @@ +// T6W28_Snd_Emu + +#ifndef SMS_APU_H +#define SMS_APU_H + +namespace MDFN_IEN_NGP +{ + +typedef long sms_time_t; // clock cycle count + +} + +#include "T6W28_Oscs.h" + +namespace MDFN_IEN_NGP +{ + +typedef struct +{ + int sq_period[3]; + int sq_phase[3]; + unsigned int noise_period; + unsigned int noise_period_extra; + unsigned int noise_shifter; + unsigned int noise_tap; + + int delay[4]; + int volume_left[4]; + int volume_right[4]; + unsigned char latch_left, latch_right; +} T6W28_ApuState; + +class T6W28_Apu { +public: + + // Outputs can be assigned to a single buffer for mono output, or to three + // buffers for stereo output (using Stereo_Buffer to do the mixing). + + // Assign all oscillator outputs to specified buffer(s). If buffer + // is NULL, silences all oscillators. + void output( Blip_Buffer* mono ); + void output( Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ); + + // Assign single oscillator output to buffer(s). Valid indicies are 0 to 3, + // which refer to Square 1, Square 2, Square 3, and Noise. If buffer is NULL, + // silences oscillator. + enum { osc_count = 4 }; + void osc_output( int index, Blip_Buffer* mono ); + void osc_output( int index, Blip_Buffer* center, Blip_Buffer* left, Blip_Buffer* right ); + + // Reset oscillators and internal state + void reset(); + + // Write to data port + void write_data_left( sms_time_t, int ); + void write_data_right( sms_time_t, int ); + + // Run all oscillators up to specified time, end current frame, then + // start a new frame at time 0. Returns true if any oscillators added + // sound to one of the left/right buffers, false if they only added + // to the center buffer. + bool end_frame( sms_time_t ); + +public: + T6W28_Apu(); + ~T6W28_Apu(); +private: + // noncopyable + T6W28_Apu( const T6W28_Apu& ); + T6W28_Apu& operator = ( const T6W28_Apu& ); + + T6W28_Osc* oscs [osc_count]; + T6W28_Square squares [3]; + //T6W28_Square::Synth square_synth; // used by squares + sms_time_t last_time; + int latch_left, latch_right; + T6W28_Noise noise; + + void run_until( sms_time_t ); +}; + +inline void T6W28_Apu::output( Blip_Buffer* b ) { output( b, b, b ); } + +inline void T6W28_Apu::osc_output( int i, Blip_Buffer* b ) { osc_output( i, b, b, b ); } + +} + +#endif + diff --git a/src/engine/platform/sound/t6w28/T6W28_Oscs.h b/src/engine/platform/sound/t6w28/T6W28_Oscs.h new file mode 100644 index 00000000..876b3ed1 --- /dev/null +++ b/src/engine/platform/sound/t6w28/T6W28_Oscs.h @@ -0,0 +1,50 @@ + +// Private oscillators used by T6W28_Apu + +// T6W28_Snd_Emu + +#ifndef SMS_OSCS_H +#define SMS_OSCS_H + +namespace MDFN_IEN_NGP +{ + +struct T6W28_Osc +{ + int output_select; + + int delay; + int last_amp_left; + int last_amp_right; + + int volume_left; + int volume_right; + + T6W28_Osc(); + void reset(); +}; + +struct T6W28_Square : T6W28_Osc +{ + int period; + int phase; + + void reset(); + void run( sms_time_t, sms_time_t ); +}; + +struct T6W28_Noise : T6W28_Osc +{ + const int* period; + int period_extra; + unsigned shifter; + unsigned tap; + + void reset(); + void run( sms_time_t, sms_time_t ); +}; + +} + +#endif + diff --git a/src/engine/platform/sound/vsu.cpp b/src/engine/platform/sound/vsu.cpp new file mode 100644 index 00000000..9e3832f9 --- /dev/null +++ b/src/engine/platform/sound/vsu.cpp @@ -0,0 +1,499 @@ +/******************************************************************************/ +/* Mednafen Virtual Boy Emulation Module */ +/******************************************************************************/ +/* vsu.cpp: +** Copyright (C) 2010-2016 Mednafen Team +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the GNU General Public License +** as published by the Free Software Foundation; either version 2 +** of the License, or (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software Foundation, Inc., +** 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "vsu.h" +#include +#include +#include + +static const unsigned int Tap_LUT[8] = { 15 - 1, 11 - 1, 14 - 1, 5 - 1, 9 - 1, 7 - 1, 10 - 1, 12 - 1 }; + +#define MDFN_UNLIKELY(x) x + +VSU::VSU() +{ + for(int ch = 0; ch < 6; ch++) + { + for(int lr = 0; lr < 2; lr++) + last_output[ch][lr] = 0; + } +} + +VSU::~VSU() +{ + +} + +void VSU::SetSoundRate(double rate) +{ + /* + for(int y = 0; y < 2; y++) + { + sbuf[y].set_sample_rate(rate ? rate : 44100, 50); + sbuf[y].clock_rate((long)(VB_MASTER_CLOCK / 4)); + sbuf[y].bass_freq(20); + } + */ +} + +void VSU::Power(void) +{ + SweepControl = 0; + SweepModCounter = 0; + SweepModClockDivider = 1; + + for(int ch = 0; ch < 6; ch++) + { + IntlControl[ch] = 0; + LeftLevel[ch] = 0; + RightLevel[ch] = 0; + Frequency[ch] = 0; + EnvControl[ch] = 0; + RAMAddress[ch] = 0; + + EffFreq[ch] = 0; + Envelope[ch] = 0; + WavePos[ch] = 0; + FreqCounter[ch] = 1; + IntervalCounter[ch] = 0; + EnvelopeCounter[ch] = 1; + + EffectsClockDivider[ch] = 4800; + IntervalClockDivider[ch] = 4; + EnvelopeClockDivider[ch] = 4; + + LatcherClockDivider[ch] = 120; + } + + ModWavePos = 0; + + NoiseLatcherClockDivider = 120; + NoiseLatcher = 0; + + lfsr = 0; + + memset(WaveData, 0, sizeof(WaveData)); + memset(ModData, 0, sizeof(ModData)); + + last_ts = 0; +} + +void VSU::Write(int timestamp, unsigned int A, unsigned char V) +{ + if(MDFN_UNLIKELY(A & 0x3)) + { + return; + } + // + // + A &= 0x7FF; + + //Update(timestamp); + + //printf("VSU Write: %d, %08x %02x\n", timestamp, A, V); + + if(A < 0x280) + WaveData[A >> 7][(A >> 2) & 0x1F] = V & 0x3F; + else if(A < 0x400) + { + //if(A >= 0x300) + //printf("Modulation mirror write? %08x %02x\n", A, V); + ModData[(A >> 2) & 0x1F] = V; + } + else if(A < 0x600) + { + int ch = (A >> 6) & 0xF; + + //if(ch < 6) + //printf("Ch: %d, Reg: %d, Value: %02x\n", ch, (A >> 2) & 0xF, V); + + if(ch > 5) + { + if(A == 0x580 && (V & 1)) + { + //puts("STOP, HAMMER TIME"); + for(int i = 0; i < 6; i++) + IntlControl[i] &= ~0x80; + } + } + else + switch((A >> 2) & 0xF) + { + case 0x0: IntlControl[ch] = V & ~0x40; + + if(V & 0x80) + { + EffFreq[ch] = Frequency[ch]; + if(ch == 5) + FreqCounter[ch] = 10 * (2048 - EffFreq[ch]); + else + FreqCounter[ch] = 2048 - EffFreq[ch]; + IntervalCounter[ch] = (V & 0x1F) + 1; + EnvelopeCounter[ch] = (EnvControl[ch] & 0x7) + 1; + + if(ch == 4) + { + SweepModCounter = (SweepControl >> 4) & 7; + SweepModClockDivider = (SweepControl & 0x80) ? 8 : 1; + ModWavePos = 0; + } + + WavePos[ch] = 0; + + if(ch == 5) // Not sure if this is correct. + lfsr = 1; + + //if(!(IntlControl[ch] & 0x80)) + // Envelope[ch] = (EnvControl[ch] >> 4) & 0xF; + + EffectsClockDivider[ch] = 4800; + IntervalClockDivider[ch] = 4; + EnvelopeClockDivider[ch] = 4; + } + break; + + case 0x1: LeftLevel[ch] = (V >> 4) & 0xF; + RightLevel[ch] = (V >> 0) & 0xF; + break; + + case 0x2: Frequency[ch] &= 0xFF00; + Frequency[ch] |= V << 0; + EffFreq[ch] &= 0xFF00; + EffFreq[ch] |= V << 0; + break; + + case 0x3: Frequency[ch] &= 0x00FF; + Frequency[ch] |= (V & 0x7) << 8; + EffFreq[ch] &= 0x00FF; + EffFreq[ch] |= (V & 0x7) << 8; + break; + + case 0x4: EnvControl[ch] &= 0xFF00; + EnvControl[ch] |= V << 0; + + Envelope[ch] = (V >> 4) & 0xF; + break; + + case 0x5: EnvControl[ch] &= 0x00FF; + if(ch == 4) + EnvControl[ch] |= (V & 0x73) << 8; + else if(ch == 5) + { + EnvControl[ch] |= (V & 0x73) << 8; + lfsr = 1; + } + else + EnvControl[ch] |= (V & 0x03) << 8; + break; + + case 0x6: RAMAddress[ch] = V & 0xF; + break; + + case 0x7: if(ch == 4) + { + SweepControl = V; + } + break; + } + } +} + +inline void VSU::CalcCurrentOutput(int ch, int &left, int &right) +{ + if(!(IntlControl[ch] & 0x80)) + { + left = right = 0; + return; + } + + int WD; + int l_ol, r_ol; + + if(ch == 5) + WD = NoiseLatcher; //(NoiseLatcher << 6) - NoiseLatcher; + else + { + if(RAMAddress[ch] > 4) + WD = 0; + else + WD = WaveData[RAMAddress[ch]][WavePos[ch]]; // - 0x20; + } + l_ol = Envelope[ch] * LeftLevel[ch]; + if(l_ol) + { + l_ol >>= 3; + l_ol += 1; + } + + r_ol = Envelope[ch] * RightLevel[ch]; + if(r_ol) + { + r_ol >>= 3; + r_ol += 1; + } + + left = WD * l_ol; + right = WD * r_ol; +} + +void VSU::Update(int timestamp) +{ + //puts("VSU Start"); + int left, right; + + for(int ch = 0; ch < 6; ch++) + { + int clocks = timestamp - last_ts; + //int running_timestamp = last_ts; + + // Output sound here + CalcCurrentOutput(ch, left, right); + /*Synth.offset_inline(running_timestamp, left - last_output[ch][0], &sbuf[0]); + Synth.offset_inline(running_timestamp, right - last_output[ch][1], &sbuf[1]);*/ + last_output[ch][0] = left; + last_output[ch][1] = right; + + if(!(IntlControl[ch] & 0x80)) + continue; + + while(clocks > 0) + { + int chunk_clocks = clocks; + + if(chunk_clocks > EffectsClockDivider[ch]) + chunk_clocks = EffectsClockDivider[ch]; + + if(ch == 5) + { + if(chunk_clocks > NoiseLatcherClockDivider) + chunk_clocks = NoiseLatcherClockDivider; + } + else + { + if(EffFreq[ch] >= 2040) + { + if(chunk_clocks > LatcherClockDivider[ch]) + chunk_clocks = LatcherClockDivider[ch]; + } + else + { + if(chunk_clocks > FreqCounter[ch]) + chunk_clocks = FreqCounter[ch]; + } + } + + if(ch == 5 && chunk_clocks > NoiseLatcherClockDivider) + chunk_clocks = NoiseLatcherClockDivider; + + FreqCounter[ch] -= chunk_clocks; + while(FreqCounter[ch] <= 0) + { + if(ch == 5) + { + int feedback = ((lfsr >> 7) & 1) ^ ((lfsr >> Tap_LUT[(EnvControl[5] >> 12) & 0x7]) & 1) ^ 1; + lfsr = ((lfsr << 1) & 0x7FFF) | feedback; + + FreqCounter[ch] += 10 * (2048 - EffFreq[ch]); + } + else + { + FreqCounter[ch] += 2048 - EffFreq[ch]; + WavePos[ch] = (WavePos[ch] + 1) & 0x1F; + } + } + + LatcherClockDivider[ch] -= chunk_clocks; + while(LatcherClockDivider[ch] <= 0) + LatcherClockDivider[ch] += 120; + + if(ch == 5) + { + NoiseLatcherClockDivider -= chunk_clocks; + if(!NoiseLatcherClockDivider) + { + NoiseLatcherClockDivider = 120; + NoiseLatcher = ((lfsr & 1) << 6) - (lfsr & 1); + } + } + + EffectsClockDivider[ch] -= chunk_clocks; + while(EffectsClockDivider[ch] <= 0) + { + EffectsClockDivider[ch] += 4800; + + IntervalClockDivider[ch]--; + while(IntervalClockDivider[ch] <= 0) + { + IntervalClockDivider[ch] += 4; + + if(IntlControl[ch] & 0x20) + { + IntervalCounter[ch]--; + if(!IntervalCounter[ch]) + { + IntlControl[ch] &= ~0x80; + } + } + + EnvelopeClockDivider[ch]--; + while(EnvelopeClockDivider[ch] <= 0) + { + EnvelopeClockDivider[ch] += 4; + + if(EnvControl[ch] & 0x0100) // Enveloping enabled? + { + EnvelopeCounter[ch]--; + if(!EnvelopeCounter[ch]) + { + EnvelopeCounter[ch] = (EnvControl[ch] & 0x7) + 1; + + if(EnvControl[ch] & 0x0008) // Grow + { + if(Envelope[ch] < 0xF || (EnvControl[ch] & 0x200)) + Envelope[ch] = (Envelope[ch] + 1) & 0xF; + } + else // Decay + { + if(Envelope[ch] > 0 || (EnvControl[ch] & 0x200)) + Envelope[ch] = (Envelope[ch] - 1) & 0xF; + } + } + } + + } // end while(EnvelopeClockDivider[ch] <= 0) + } // end while(IntervalClockDivider[ch] <= 0) + + if(ch == 4) + { + SweepModClockDivider--; + while(SweepModClockDivider <= 0) + { + SweepModClockDivider += (SweepControl & 0x80) ? 8 : 1; + + if(((SweepControl >> 4) & 0x7) && (EnvControl[ch] & 0x4000)) + { + if(SweepModCounter) + SweepModCounter--; + + if(!SweepModCounter) + { + SweepModCounter = (SweepControl >> 4) & 0x7; + + if(EnvControl[ch] & 0x1000) // Modulation + { + if(ModWavePos < 32 || (EnvControl[ch] & 0x2000)) + { + ModWavePos &= 0x1F; + + EffFreq[ch] = (Frequency[ch] + (signed char)ModData[ModWavePos]) & 0x7FF; + ModWavePos++; + } + } + else // Sweep + { + int delta = EffFreq[ch] >> (SweepControl & 0x7); + int NewFreq = EffFreq[ch] + ((SweepControl & 0x8) ? delta : -delta); + + //printf("Sweep(%d): Old: %d, New: %d\n", ch, EffFreq[ch], NewFreq); + + if(NewFreq < 0) + EffFreq[ch] = 0; + else if(NewFreq > 0x7FF) + { + //EffFreq[ch] = 0x7FF; + IntlControl[ch] &= ~0x80; + } + else + EffFreq[ch] = NewFreq; + } + } + } + } // end while(SweepModClockDivider <= 0) + } // end if(ch == 4) + } // end while(EffectsClockDivider[ch] <= 0) + clocks -= chunk_clocks; + //running_timestamp += chunk_clocks; + + // Output sound here too. + CalcCurrentOutput(ch, left, right); + /* + Synth.offset_inline(running_timestamp, left - last_output[ch][0], &sbuf[0]); + Synth.offset_inline(running_timestamp, right - last_output[ch][1], &sbuf[1]); + */ + last_output[ch][0] = left; + last_output[ch][1] = right; + } + } + last_ts = timestamp; + //puts("VSU End"); +} + +int VSU::EndFrame(int timestamp) +{ + int ret = 0; + + Update(timestamp); + last_ts = 0; + + /* + if(SoundBuf) + { + for(int y = 0; y < 2; y++) + { + sbuf[y].end_frame(timestamp); + ret = sbuf[y].read_samples(SoundBuf + y, SoundBufMaxSize, 1); + } + } + */ + + return ret; +} + +unsigned char VSU::PeekWave(const unsigned int which, unsigned int Address) +{ + assert(which <= 4); + + Address &= 0x1F; + + return(WaveData[which][Address]); +} + +void VSU::PokeWave(const unsigned int which, unsigned int Address, unsigned char value) +{ + assert(which <= 4); + + Address &= 0x1F; + + WaveData[which][Address] = value & 0x3F; +} + +unsigned char VSU::PeekModWave(unsigned int Address) +{ + Address &= 0x1F; + return(ModData[Address]); +} + +void VSU::PokeModWave(unsigned int Address, unsigned char value) +{ + Address &= 0x1F; + + ModData[Address] = value & 0xFF; +} diff --git a/src/engine/platform/sound/vsu.h b/src/engine/platform/sound/vsu.h new file mode 100644 index 00000000..ab43cb65 --- /dev/null +++ b/src/engine/platform/sound/vsu.h @@ -0,0 +1,97 @@ +/******************************************************************************/ +/* Mednafen Virtual Boy Emulation Module */ +/******************************************************************************/ +/* vsu.h: +** Copyright (C) 2010-2016 Mednafen Team +** +** This program is free software; you can redistribute it and/or +** modify it under the terms of the GNU General Public License +** as published by the Free Software Foundation; either version 2 +** of the License, or (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program; if not, write to the Free Software Foundation, Inc., +** 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef __VB_VSU_H +#define __VB_VSU_H + +class VSU +{ + public: + + int last_output[6][2]; + + VSU(); + ~VSU(); + + void SetSoundRate(double rate); + + void Power(void); + + void Write(int timestamp, unsigned int A, unsigned char V); + + int EndFrame(int timestamp); + + unsigned char PeekWave(const unsigned int which, unsigned int Address); + void PokeWave(const unsigned int which, unsigned int Address, unsigned char value); + + unsigned char PeekModWave(unsigned int Address); + void PokeModWave(unsigned int Address, unsigned char value); + + private: + + void CalcCurrentOutput(int ch, int &left, int &right); + + void Update(int timestamp); + + unsigned char IntlControl[6]; + unsigned char LeftLevel[6]; + unsigned char RightLevel[6]; + unsigned short Frequency[6]; + unsigned short EnvControl[6]; // Channel 5/6 extra functionality tacked on too. + + unsigned char RAMAddress[6]; + + unsigned char SweepControl; + + unsigned char WaveData[5][0x20]; + + unsigned char ModData[0x20]; + + // + // + // + int EffFreq[6]; + int Envelope[6]; + + int WavePos[6]; + int ModWavePos; + + int LatcherClockDivider[6]; + + int FreqCounter[6]; + int IntervalCounter[6]; + int EnvelopeCounter[6]; + int SweepModCounter; + + int EffectsClockDivider[6]; + int IntervalClockDivider[6]; + int EnvelopeClockDivider[6]; + int SweepModClockDivider; + + int NoiseLatcherClockDivider; + unsigned int NoiseLatcher; + + unsigned int lfsr; + + int last_ts; +}; + +#endif diff --git a/src/engine/platform/su.cpp b/src/engine/platform/su.cpp index 66b91ef5..c00847fc 100644 --- a/src/engine/platform/su.cpp +++ b/src/engine/platform/su.cpp @@ -518,7 +518,7 @@ void DivPlatformSoundUnit::setFlags(const DivConfig& flags) { bool echoOn=flags.getBool("echo",false); initIlCtrl=3|(echoOn?4:0); initIlSize=((flags.getInt("echoDelay",0))&63)|(echoOn?0x40:0)|(flags.getBool("swapEcho",false)?0x80:0); - initFil1=flags.getInt("echoFeedback",0); + initFil1=flags.getInt("echoFeedback",0)|(flags.getInt("echoResolution",0)<<4); initEchoVol=flags.getInt("echoVol",0); sampleMemSize=flags.getInt("sampleMemSize",0); diff --git a/src/engine/platform/vb.cpp b/src/engine/platform/vb.cpp new file mode 100644 index 00000000..e3d5a6a0 --- /dev/null +++ b/src/engine/platform/vb.cpp @@ -0,0 +1,647 @@ +/** + * Furnace Tracker - multi-system chiptune tracker + * Copyright (C) 2021-2022 tildearrow and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "vb.h" +#include "../engine.h" +#include + +//#define rWrite(a,v) pendingWrites[a]=v; +#define rWrite(a,v) if (!skipRegisterWrites) {writes.emplace(a,v); if (dumpWrites) {addWrite(a,v);} } +#define chWrite(c,a,v) rWrite(0x400+((c)<<6)+((a)<<2),v); + +#define CHIP_DIVIDER 16 + +const char* regCheatSheetVB[]={ + "Wave0", "000", + "Wave1", "080", + "Wave2", "100", + "Wave3", "180", + "Wave4", "200", + "ModTable", "280", + + "S0INT", "400", + "S0LRV", "404", + "S0FQL", "408", + "S0FQH", "40C", + "S0EV0", "410", + "S0EV1", "414", + "S0RAM", "418", + + "S1INT", "440", + "S1LRV", "444", + "S1FQL", "448", + "S1FQH", "44C", + "S1EV0", "450", + "S1EV1", "454", + "S1RAM", "458", + + "S2INT", "480", + "S2LRV", "484", + "S2FQL", "488", + "S2FQH", "48C", + "S2EV0", "480", + "S2EV1", "484", + "S2RAM", "488", + + "S3INT", "4C0", + "S3LRV", "4C4", + "S3FQL", "4C8", + "S3FQH", "4CC", + "S3EV0", "4C0", + "S3EV1", "4C4", + "S3RAM", "4C8", + + "S4INT", "500", + "S4LRV", "504", + "S4FQL", "508", + "S4FQH", "50C", + "S4EV0", "510", + "S4EV1", "514", + "S4RAM", "518", + + "S4SWP", "51C", + + "S5INT", "540", + "S5LRV", "544", + "S5FQL", "548", + "S5FQH", "54C", + "S5EV0", "550", + "S5EV1", "554", + "S5RAM", "558", + NULL +}; + +const char** DivPlatformVB::getRegisterSheet() { + return regCheatSheetVB; +} + +void DivPlatformVB::acquire(short* bufL, short* bufR, size_t start, size_t len) { + for (size_t h=start; hrate) { + DivSample* s=parent->getSample(chan[i].dacSample); + if (s->samples<=0) { + chan[i].dacSample=-1; + continue; + } + chWrite(i,0x07,0); + signed char dacData=((signed char)((unsigned char)s->data8[chan[i].dacPos]^0x80))>>3; + chan[i].dacOut=CLAMP(dacData,-16,15); + if (!isMuted[i]) { + chWrite(i,0x04,parent->song.disableSampleMacro?0xdf:(0xc0|chan[i].outVol)); + chWrite(i,0x06,chan[i].dacOut&0x1f); + } else { + chWrite(i,0x04,0xc0); + chWrite(i,0x06,0x10); + } + chan[i].dacPos++; + if (s->isLoopable() && chan[i].dacPos>=(unsigned int)s->loopEnd) { + chan[i].dacPos=s->loopStart; + } else if (chan[i].dacPos>=s->samples) { + chan[i].dacSample=-1; + } + chan[i].dacPeriod-=rate; + } + } + } + */ + + // VB part + cycles=0; + while (!writes.empty()) { + QueuedWrite w=writes.front(); + vb->Write(cycles,w.addr,w.val); + regPool[w.addr]=w.val; + //cycles+=2; + writes.pop(); + } + vb->EndFrame(16); + + tempL=0; + tempR=0; + for (int i=0; i<6; i++) { + oscBuf[i]->data[oscBuf[i]->needle++]=(vb->last_output[i][0]+vb->last_output[i][1])*8; + tempL+=vb->last_output[i][0]; + tempR+=vb->last_output[i][1]; + } + + if (tempL<-32768) tempL=-32768; + if (tempL>32767) tempL=32767; + if (tempR<-32768) tempR=-32768; + if (tempR>32767) tempR=32767; + + bufL[h]=tempL; + bufR[h]=tempR; + } +} + +void DivPlatformVB::updateWave(int ch) { + if (ch>=5) return; + + if (chan[ch].pcm) { + chan[ch].deferredWaveUpdate=true; + return; + } + for (int i=0; i<32; i++) { + rWrite((ch<<7)+(i<<2),chan[ch].ws.output[i]); + //chWrite(ch,0x06,chan[ch].ws.output[(i+chan[ch].antiClickWavePos)&31]); + } + chan[ch].antiClickWavePos&=31; + if (chan[ch].active) { + //chWrite(ch,0x04,0x80|chan[ch].outVol); + } + if (chan[ch].deferredWaveUpdate) { + chan[ch].deferredWaveUpdate=false; + } +} + +// TODO: in octave 6 the noise table changes to a tonal one +static unsigned char noiseFreq[12]={ + 4,13,15,18,21,23,25,27,29,31,0,2 +}; + +void DivPlatformVB::tick(bool sysTick) { + for (int i=0; i<6; i++) { + // anti-click + if (antiClickEnabled && sysTick && chan[i].freq>0) { + chan[i].antiClickPeriodCount+=(chipClock/MAX(parent->getCurHz(),1.0f)); + chan[i].antiClickWavePos+=chan[i].antiClickPeriodCount/chan[i].freq; + chan[i].antiClickPeriodCount%=chan[i].freq; + } + + chan[i].std.next(); + if (chan[i].std.vol.had) { + chan[i].outVol=VOL_SCALE_LINEAR(chan[i].vol&15,MIN(15,chan[i].std.vol.val),15); + if (chan[i].furnaceDac && chan[i].pcm) { + // ignore for now + } else { + chWrite(i,0x04,chan[i].outVol<<4); + } + } + if (chan[i].std.duty.had && i>=4) { + chan[i].noise=chan[i].std.duty.val; + chan[i].freqChanged=true; + int noiseSeek=chan[i].note; + if (noiseSeek<0) noiseSeek=0; + chWrite(i,0x07,chan[i].noise?(0x80|(parent->song.properNoiseLayout?(noiseSeek&31):noiseFreq[noiseSeek%12])):0); + } + if (chan[i].std.arp.had) { + if (!chan[i].inPorta) { + int noiseSeek=parent->calcArp(chan[i].note,chan[i].std.arp.val); + chan[i].baseFreq=NOTE_PERIODIC(noiseSeek); + if (noiseSeek<0) noiseSeek=0; + chWrite(i,0x07,chan[i].noise?(0x80|(parent->song.properNoiseLayout?(noiseSeek&31):noiseFreq[noiseSeek%12])):0); + } + chan[i].freqChanged=true; + } + if (chan[i].std.wave.had && !chan[i].pcm) { + if (chan[i].wave!=chan[i].std.wave.val || chan[i].ws.activeChanged()) { + chan[i].wave=chan[i].std.wave.val; + chan[i].ws.changeWave1(chan[i].wave); + if (!chan[i].keyOff) chan[i].keyOn=true; + } + } + if (chan[i].std.panL.had) { + chan[i].pan&=0x0f; + chan[i].pan|=(chan[i].std.panL.val&15)<<4; + } + if (chan[i].std.panR.had) { + chan[i].pan&=0xf0; + chan[i].pan|=chan[i].std.panR.val&15; + } + if (chan[i].std.panL.had || chan[i].std.panR.had) { + chWrite(i,0x01,isMuted[i]?0:chan[i].pan); + } + if (chan[i].std.pitch.had) { + if (chan[i].std.pitch.mode) { + chan[i].pitch2+=chan[i].std.pitch.val; + CLAMP_VAR(chan[i].pitch2,-32768,32767); + } else { + chan[i].pitch2=chan[i].std.pitch.val; + } + chan[i].freqChanged=true; + } + if (chan[i].std.phaseReset.had && chan[i].std.phaseReset.val==1) { + if (chan[i].furnaceDac && chan[i].pcm) { + if (chan[i].active && chan[i].dacSample>=0 && chan[i].dacSamplesong.sampleLen) { + chan[i].dacPos=0; + chan[i].dacPeriod=0; + //chWrite(i,0x04,parent->song.disableSampleMacro?0xdf:(0xc0|chan[i].vol)); + addWrite(0xffff0000+(i<<8),chan[i].dacSample); + chan[i].keyOn=true; + } + } + chan[i].antiClickWavePos=0; + chan[i].antiClickPeriodCount=0; + } + if (chan[i].active) { + if (chan[i].ws.tick() || (chan[i].std.phaseReset.had && chan[i].std.phaseReset.val==1) || chan[i].deferredWaveUpdate) { + updateWave(i); + } + } + if (chan[i].freqChanged || chan[i].keyOn || chan[i].keyOff) { + //DivInstrument* ins=parent->getIns(chan[i].ins,DIV_INS_PCE); + chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,true,0,chan[i].pitch2,chipClock,CHIP_DIVIDER); + if (chan[i].furnaceDac && chan[i].pcm) { + double off=1.0; + if (chan[i].dacSample>=0 && chan[i].dacSamplesong.sampleLen) { + DivSample* s=parent->getSample(chan[i].dacSample); + if (s->centerRate<1) { + off=1.0; + } else { + off=8363.0/(double)s->centerRate; + } + } + chan[i].dacRate=((double)chipClock/2)/MAX(1,off*chan[i].freq); + if (dumpWrites) addWrite(0xffff0001+(i<<8),chan[i].dacRate); + } + if (chan[i].freq<1) chan[i].freq=1; + if (chan[i].freq>2047) chan[i].freq=2047; + chan[i].freq=2048-chan[i].freq; + chWrite(i,0x02,chan[i].freq&0xff); + chWrite(i,0x03,chan[i].freq>>8); + if (chan[i].keyOn) { + } + if (chan[i].keyOff) { + chWrite(i,0x04,0); + } + if (chan[i].keyOn) chan[i].keyOn=false; + if (chan[i].keyOff) chan[i].keyOff=false; + chan[i].freqChanged=false; + } + } +} + +int DivPlatformVB::dispatch(DivCommand c) { + switch (c.cmd) { + case DIV_CMD_NOTE_ON: { + DivInstrument* ins=parent->getIns(chan[c.chan].ins,DIV_INS_PCE); + chan[c.chan].macroVolMul=ins->type==DIV_INS_AMIGA?64:31; + if (ins->type==DIV_INS_AMIGA || ins->amiga.useSample) { + chan[c.chan].pcm=true; + } else if (chan[c.chan].furnaceDac) { + chan[c.chan].pcm=false; + } + if (chan[c.chan].pcm) { + if (ins->type==DIV_INS_AMIGA || ins->amiga.useSample) { + chan[c.chan].furnaceDac=true; + if (skipRegisterWrites) break; + chan[c.chan].dacSample=ins->amiga.getSample(c.value); + if (chan[c.chan].dacSample<0 || chan[c.chan].dacSample>=parent->song.sampleLen) { + chan[c.chan].dacSample=-1; + if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0); + break; + } else { + if (dumpWrites) { + //chWrite(c.chan,0x04,parent->song.disableSampleMacro?0xdf:(0xc0|chan[c.chan].vol)); + addWrite(0xffff0000+(c.chan<<8),chan[c.chan].dacSample); + } + } + chan[c.chan].dacPos=0; + chan[c.chan].dacPeriod=0; + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + } + chan[c.chan].active=true; + chan[c.chan].macroInit(ins); + if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { + chan[c.chan].outVol=chan[c.chan].vol; + } + //chan[c.chan].keyOn=true; + } else { + chan[c.chan].furnaceDac=false; + if (skipRegisterWrites) break; + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].note=c.value; + } + chan[c.chan].dacSample=12*sampleBank+chan[c.chan].note%12; + if (chan[c.chan].dacSample>=parent->song.sampleLen) { + chan[c.chan].dacSample=-1; + if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0); + break; + } else { + if (dumpWrites) addWrite(0xffff0000+(c.chan<<8),chan[c.chan].dacSample); + } + chan[c.chan].dacPos=0; + chan[c.chan].dacPeriod=0; + chan[c.chan].dacRate=parent->getSample(chan[c.chan].dacSample)->rate; + if (dumpWrites) { + //chWrite(c.chan,0x04,parent->song.disableSampleMacro?0xdf:(0xc0|chan[c.chan].vol)); + addWrite(0xffff0001+(c.chan<<8),chan[c.chan].dacRate); + } + } + break; + } + if (c.value!=DIV_NOTE_NULL) { + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + } + chan[c.chan].active=true; + chan[c.chan].keyOn=true; + chan[c.chan].macroInit(ins); + if (!parent->song.brokenOutVol && !chan[c.chan].std.vol.will) { + chan[c.chan].outVol=chan[c.chan].vol; + } + if (chan[c.chan].wave<0) { + chan[c.chan].wave=0; + chan[c.chan].ws.changeWave1(chan[c.chan].wave); + } + chan[c.chan].ws.init(ins,32,63,chan[c.chan].insChanged); + chan[c.chan].insChanged=false; + break; + } + case DIV_CMD_NOTE_OFF: + chan[c.chan].dacSample=-1; + if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0); + chan[c.chan].pcm=false; + chan[c.chan].active=false; + chan[c.chan].keyOff=true; + chan[c.chan].macroInit(NULL); + break; + case DIV_CMD_NOTE_OFF_ENV: + case DIV_CMD_ENV_RELEASE: + chan[c.chan].std.release(); + break; + case DIV_CMD_INSTRUMENT: + if (chan[c.chan].ins!=c.value || c.value2==1) { + chan[c.chan].ins=c.value; + chan[c.chan].insChanged=true; + } + break; + case DIV_CMD_VOLUME: + if (chan[c.chan].vol!=c.value) { + chan[c.chan].vol=c.value; + if (!chan[c.chan].std.vol.has) { + chan[c.chan].outVol=c.value; + if (chan[c.chan].active && !chan[c.chan].pcm) { + chWrite(c.chan,0x04,chan[c.chan].outVol<<4); + } + } + } + break; + case DIV_CMD_GET_VOLUME: + if (chan[c.chan].std.vol.has) { + return chan[c.chan].vol; + } + return chan[c.chan].outVol; + break; + case DIV_CMD_PITCH: + chan[c.chan].pitch=c.value; + chan[c.chan].freqChanged=true; + break; + case DIV_CMD_WAVE: + chan[c.chan].wave=c.value; + chan[c.chan].ws.changeWave1(chan[c.chan].wave); + chan[c.chan].keyOn=true; + break; + case DIV_CMD_PCE_LFO_MODE: + if (c.value==0) { + lfoMode=0; + } else { + lfoMode=c.value; + } + rWrite(0x08,lfoSpeed); + rWrite(0x09,lfoMode); + break; + case DIV_CMD_PCE_LFO_SPEED: + lfoSpeed=255-c.value; + rWrite(0x08,lfoSpeed); + rWrite(0x09,lfoMode); + break; + case DIV_CMD_NOTE_PORTA: { + int destFreq=NOTE_PERIODIC(c.value2); + bool return2=false; + if (destFreq>chan[c.chan].baseFreq) { + chan[c.chan].baseFreq+=c.value; + if (chan[c.chan].baseFreq>=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } else { + chan[c.chan].baseFreq-=c.value; + if (chan[c.chan].baseFreq<=destFreq) { + chan[c.chan].baseFreq=destFreq; + return2=true; + } + } + chan[c.chan].freqChanged=true; + if (return2) { + chan[c.chan].inPorta=false; + return 2; + } + break; + } + case DIV_CMD_STD_NOISE_MODE: + chan[c.chan].noise=c.value; + chWrite(c.chan,0x07,chan[c.chan].noise?(0x80|chan[c.chan].note):0); + break; + case DIV_CMD_SAMPLE_MODE: + chan[c.chan].pcm=c.value; + break; + case DIV_CMD_SAMPLE_BANK: + sampleBank=c.value; + if (sampleBank>(parent->song.sample.size()/12)) { + sampleBank=parent->song.sample.size()/12; + } + break; + case DIV_CMD_PANNING: { + chan[c.chan].pan=(c.value&0xf0)|(c.value2>>4); + chWrite(c.chan,0x01,isMuted[c.chan]?0:chan[c.chan].pan); + break; + } + case DIV_CMD_LEGATO: + chan[c.chan].baseFreq=NOTE_PERIODIC(c.value+((chan[c.chan].std.arp.will && !chan[c.chan].std.arp.mode)?(chan[c.chan].std.arp.val):(0))); + chan[c.chan].freqChanged=true; + chan[c.chan].note=c.value; + break; + case DIV_CMD_PRE_PORTA: + if (chan[c.chan].active && c.value2) { + if (parent->song.resetMacroOnPorta) chan[c.chan].macroInit(parent->getIns(chan[c.chan].ins,DIV_INS_PCE)); + } + if (!chan[c.chan].inPorta && c.value && !parent->song.brokenPortaArp && chan[c.chan].std.arp.will) chan[c.chan].baseFreq=NOTE_PERIODIC(chan[c.chan].note); + chan[c.chan].inPorta=c.value; + break; + case DIV_CMD_GET_VOLMAX: + return 15; + break; + case DIV_ALWAYS_SET_VOLUME: + return 1; + break; + default: + break; + } + return 1; +} + +void DivPlatformVB::muteChannel(int ch, bool mute) { + isMuted[ch]=mute; + chWrite(ch,0x01,isMuted[ch]?0:chan[ch].pan); + if (!isMuted[ch] && (chan[ch].pcm && chan[ch].dacSample!=-1)) { + //chWrite(ch,0x04,parent->song.disableSampleMacro?0xdf:(0xc0|chan[ch].outVol)); + //chWrite(ch,0x06,chan[ch].dacOut&0x1f); + } +} + +void DivPlatformVB::forceIns() { + for (int i=0; i<6; i++) { + chan[i].insChanged=true; + chan[i].freqChanged=true; + updateWave(i); + chWrite(i,0x01,isMuted[i]?0:chan[i].pan); + } +} + +void* DivPlatformVB::getChanState(int ch) { + return &chan[ch]; +} + +DivMacroInt* DivPlatformVB::getChanMacroInt(int ch) { + return &chan[ch].std; +} + +DivDispatchOscBuffer* DivPlatformVB::getOscBuffer(int ch) { + return oscBuf[ch]; +} + +unsigned char* DivPlatformVB::getRegisterPool() { + return regPool; +} + +int DivPlatformVB::getRegisterPoolSize() { + return 0x600; +} + +int DivPlatformVB::getRegisterPoolDepth() { + return 8; +} + +void DivPlatformVB::reset() { + while (!writes.empty()) writes.pop(); + memset(regPool,0,0x600); + for (int i=0; i<6; i++) { + chan[i]=DivPlatformVB::Channel(); + chan[i].std.setEngine(parent); + chan[i].ws.setEngine(parent); + chan[i].ws.init(NULL,32,63,false); + } + if (dumpWrites) { + addWrite(0xffffffff,0); + } + vb->Power(); + lastPan=0xff; + tempL=0; + tempR=0; + cycles=0; + curChan=-1; + sampleBank=0; + lfoMode=0; + lfoSpeed=255; + // set per-channel initial values + for (int i=0; i<6; i++) { + chWrite(i,0x01,isMuted[i]?0:chan[i].pan); + chWrite(i,0x05,0x00); + chWrite(i,0x00,0x80); + chWrite(i,0x06,i); + } + delay=500; +} + +bool DivPlatformVB::isStereo() { + return true; +} + +bool DivPlatformVB::keyOffAffectsArp(int ch) { + return true; +} + +float DivPlatformVB::getPostAmp() { + return 6.0f; +} + +void DivPlatformVB::notifyWaveChange(int wave) { + for (int i=0; i<6; i++) { + if (chan[i].wave==wave) { + chan[i].ws.changeWave1(wave); + updateWave(i); + } + } +} + +void DivPlatformVB::notifyInsDeletion(void* ins) { + for (int i=0; i<6; i++) { + chan[i].std.notifyInsDeletion((DivInstrument*)ins); + } +} + +void DivPlatformVB::setFlags(const DivConfig& flags) { + chipClock=5000000.0; + antiClickEnabled=!flags.getBool("noAntiClick",false); + rate=chipClock/16; + for (int i=0; i<6; i++) { + oscBuf[i]->rate=rate; + } + + if (vb!=NULL) { + delete vb; + vb=NULL; + } + vb=new VSU; +} + +void DivPlatformVB::poke(unsigned int addr, unsigned short val) { + rWrite(addr,val); +} + +void DivPlatformVB::poke(std::vector& wlist) { + for (DivRegWrite& i: wlist) rWrite(i.addr,i.val); +} + +int DivPlatformVB::init(DivEngine* p, int channels, int sugRate, const DivConfig& flags) { + parent=p; + dumpWrites=false; + skipRegisterWrites=false; + for (int i=0; i<6; i++) { + isMuted[i]=false; + oscBuf[i]=new DivDispatchOscBuffer; + } + vb=NULL; + setFlags(flags); + reset(); + return 6; +} + +void DivPlatformVB::quit() { + for (int i=0; i<6; i++) { + delete oscBuf[i]; + } + if (vb!=NULL) { + delete vb; + vb=NULL; + } +} + +DivPlatformVB::~DivPlatformVB() { +} diff --git a/src/engine/platform/vb.h b/src/engine/platform/vb.h new file mode 100644 index 00000000..d01b9c05 --- /dev/null +++ b/src/engine/platform/vb.h @@ -0,0 +1,123 @@ +/** + * 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 _PLATFORM_VB_H +#define _PLATFORM_VB_H + +#include "../dispatch.h" +#include +#include "../macroInt.h" +#include "../waveSynth.h" +#include "sound/vsu.h" + +class DivPlatformVB: public DivDispatch { + struct Channel { + int freq, baseFreq, pitch, pitch2, note, antiClickPeriodCount, antiClickWavePos; + int dacPeriod, dacRate, dacOut; + unsigned int dacPos; + int dacSample, ins; + unsigned char pan; + bool active, insChanged, freqChanged, keyOn, keyOff, inPorta, noise, pcm, furnaceDac, deferredWaveUpdate; + signed char vol, outVol, wave; + int macroVolMul; + DivMacroInt std; + DivWaveSynth ws; + void macroInit(DivInstrument* which) { + std.init(which); + pitch2=0; + } + Channel(): + freq(0), + baseFreq(0), + pitch(0), + pitch2(0), + note(0), + antiClickPeriodCount(0), + antiClickWavePos(0), + dacPeriod(0), + dacRate(0), + dacOut(0), + dacPos(0), + dacSample(-1), + ins(-1), + pan(255), + active(false), + insChanged(true), + freqChanged(false), + keyOn(false), + keyOff(false), + inPorta(false), + noise(false), + pcm(false), + furnaceDac(false), + deferredWaveUpdate(false), + vol(31), + outVol(31), + wave(-1), + macroVolMul(31) {} + }; + Channel chan[6]; + DivDispatchOscBuffer* oscBuf[6]; + bool isMuted[6]; + bool antiClickEnabled; + struct QueuedWrite { + unsigned short addr; + unsigned char val; + QueuedWrite(unsigned short a, unsigned char v): addr(a), val(v) {} + }; + std::queue writes; + unsigned char lastPan; + + int cycles, curChan, delay; + int tempL; + int tempR; + unsigned char sampleBank, lfoMode, lfoSpeed; + VSU* vb; + unsigned char regPool[0x600]; + void updateWave(int ch); + friend void putDispatchChip(void*,int); + friend void putDispatchChan(void*,int,int); + public: + void acquire(short* bufL, short* bufR, size_t start, size_t len); + int dispatch(DivCommand c); + void* getChanState(int chan); + DivMacroInt* getChanMacroInt(int ch); + DivDispatchOscBuffer* getOscBuffer(int chan); + unsigned char* getRegisterPool(); + int getRegisterPoolSize(); + int getRegisterPoolDepth(); + void reset(); + void forceIns(); + void tick(bool sysTick=true); + void muteChannel(int ch, bool mute); + bool isStereo(); + bool keyOffAffectsArp(int ch); + float getPostAmp(); + void setFlags(const DivConfig& flags); + void notifyWaveChange(int wave); + void notifyInsDeletion(void* ins); + void poke(unsigned int addr, unsigned short val); + void poke(std::vector& wlist); + const char** getRegisterSheet(); + int init(DivEngine* parent, int channels, int sugRate, const DivConfig& flags); + void quit(); + ~DivPlatformVB(); +}; + +#endif diff --git a/src/engine/platform/x1_010.cpp b/src/engine/platform/x1_010.cpp index 29e5edbd..6f220242 100644 --- a/src/engine/platform/x1_010.cpp +++ b/src/engine/platform/x1_010.cpp @@ -476,6 +476,7 @@ void DivPlatformX1_010::tick(bool sysTick) { } } chan[i].freq=parent->calcFreq(chan[i].baseFreq,chan[i].pitch,false,2,chan[i].pitch2,chipClock,chan[i].pcm?off:CHIP_FREQBASE); + if (chan[i].fixedFreq) chan[i].freq=chan[i].fixedFreq; if (chan[i].pcm) { if (chan[i].freq<1) chan[i].freq=1; if (chan[i].freq>255) chan[i].freq=255; @@ -554,11 +555,13 @@ int DivPlatformX1_010::dispatch(DivCommand c) { if (c.value!=DIV_NOTE_NULL) { chan[c.chan].note=c.value; chan[c.chan].baseFreq=NoteX1_010(c.chan,chan[c.chan].note); + chan[c.chan].fixedFreq=0; chan[c.chan].freqChanged=true; } } else { chan[c.chan].macroInit(NULL); chan[c.chan].outVol=chan[c.chan].vol; + // huh? if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) { chWrite(c.chan,0,0); // reset chWrite(c.chan,1,0); @@ -571,7 +574,8 @@ int DivPlatformX1_010::dispatch(DivCommand c) { } else { chan[c.chan].macroInit(NULL); chan[c.chan].outVol=chan[c.chan].vol; - if ((12*sampleBank+c.value%12)>=parent->song.sampleLen) { + chan[c.chan].sample=12*sampleBank+c.value%12; + if (chan[c.chan].sample<0 || chan[c.chan].sample>=parent->song.sampleLen) { chWrite(c.chan,0,0); // reset chWrite(c.chan,1,0); chWrite(c.chan,2,0); @@ -579,7 +583,7 @@ int DivPlatformX1_010::dispatch(DivCommand c) { chWrite(c.chan,5,0); break; } - DivSample* s=parent->getSample(12*sampleBank+c.value%12); + DivSample* s=parent->getSample(chan[c.chan].sample); if (isBanked) { bankSlot[chan[c.chan].bankSlot]=sampleOffX1[chan[c.chan].sample]>>17; unsigned int bankedOffs=(chan[c.chan].bankSlot<<17)|(sampleOffX1[chan[c.chan].sample]&0x1ffff); @@ -591,12 +595,14 @@ int DivPlatformX1_010::dispatch(DivCommand c) { int end=(sampleOffX1[chan[c.chan].sample]+s->length8+0xfff)&~0xfff; // padded chWrite(c.chan,5,(0x100-(end>>12))&0xff); } - chan[c.chan].baseFreq=(((unsigned int)s->rate)<<4)/(chipClock/512); + // ???? + chan[c.chan].fixedFreq=(((unsigned int)s->rate)<<4)/(chipClock/512); chan[c.chan].freqChanged=true; } } else if (c.value!=DIV_NOTE_NULL) { chan[c.chan].note=c.value; chan[c.chan].baseFreq=NoteX1_010(c.chan,chan[c.chan].note); + chan[c.chan].fixedFreq=0; chan[c.chan].freqChanged=true; } chan[c.chan].active=true; diff --git a/src/engine/platform/x1_010.h b/src/engine/platform/x1_010.h index 94d03011..ff5557bb 100644 --- a/src/engine/platform/x1_010.h +++ b/src/engine/platform/x1_010.h @@ -68,7 +68,7 @@ class DivPlatformX1_010: public DivDispatch, public vgsound_emu_mem_intf { slide(0), slidefrac(0) {} }; - int freq, baseFreq, pitch, pitch2, note; + int freq, baseFreq, fixedFreq, pitch, pitch2, note; int wave, sample, ins; unsigned char pan, autoEnvNum, autoEnvDen; bool active, insChanged, envChanged, freqChanged, keyOn, keyOff, inPorta, furnacePCM, pcm; @@ -95,7 +95,7 @@ class DivPlatformX1_010: public DivDispatch, public vgsound_emu_mem_intf { pitch2=0; } Channel(): - freq(0), baseFreq(0), pitch(0), pitch2(0), note(0), + freq(0), baseFreq(0), fixedFreq(0), pitch(0), pitch2(0), note(0), wave(-1), sample(-1), ins(-1), pan(255), autoEnvNum(0), autoEnvDen(0), active(false), insChanged(true), envChanged(true), freqChanged(false), keyOn(false), keyOff(false), inPorta(false), furnacePCM(false), pcm(false), diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index dc7670b5..116694e5 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -461,7 +461,7 @@ void DivEngine::processRow(int i, bool afterDelay) { } if (returnAfterPre) return; } else { - logV("honoring delay at position %d",whatRow); + //logV("honoring delay at position %d",whatRow); } if (chan[i].delayLocked) return; @@ -1157,6 +1157,8 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { break; } } + // under no circumstances shall the accumulator become this large + if (tempoAccum>1023) tempoAccum=1023; } // process stuff for (int i=0; i=100) { + if (attempts>=(int)size) { logE("hang detected! stopping! at %d seconds %d micro",totalSeconds,totalTicks); freelance=false; playing=false; diff --git a/src/engine/song.h b/src/engine/song.h index bb69598a..b168c883 100644 --- a/src/engine/song.h +++ b/src/engine/song.h @@ -315,6 +315,7 @@ struct DivSong { bool newVolumeScaling; bool volMacroLinger; bool brokenOutVol; + bool brokenOutVol2; bool e1e2StopOnSameNote; bool brokenPortaArp; bool snNoLowPeriods; @@ -421,6 +422,7 @@ struct DivSong { newVolumeScaling(true), volMacroLinger(true), brokenOutVol(false), + brokenOutVol2(false), e1e2StopOnSameNote(false), brokenPortaArp(false), snNoLowPeriods(false), diff --git a/src/engine/sysDef.cpp b/src/engine/sysDef.cpp index 0344def0..fc2bbfd1 100644 --- a/src/engine/sysDef.cpp +++ b/src/engine/sysDef.cpp @@ -1181,12 +1181,14 @@ void DivEngine::registerSystems() { ); sysDefs[DIV_SYSTEM_VBOY]=new DivSysDef( - "Virtual Boy", NULL, 0x9c, 0, 6, false, true, 0, false, 1U<writeC(write.addr&0xff); w->writeC(write.val&0xff); break; + case DIV_SYSTEM_VBOY: + w->writeC(0xc7); + w->writeS_BE(baseAddr2S|(write.addr>>2)); + w->writeC(write.val&0xff); + break; case DIV_SYSTEM_OPL: case DIV_SYSTEM_OPL_DRUMS: w->writeC(0x0b|baseAddr1); @@ -1271,6 +1276,17 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p howManyChips++; } break; + case DIV_SYSTEM_VBOY: + if (!hasVSU) { + hasVSU=disCont[i].dispatch->chipClock; + willExport[i]=true; + } else if (!(hasVSU&0x40000000)) { + isSecond[i]=true; + willExport[i]=true; + hasVSU|=0x40000000; + howManyChips++; + } + break; case DIV_SYSTEM_OPL: case DIV_SYSTEM_OPL_DRUMS: if (!hasOPL) { diff --git a/src/gui/about.cpp b/src/gui/about.cpp index 7e5dfdf2..d14cd4d1 100644 --- a/src/gui/about.cpp +++ b/src/gui/about.cpp @@ -129,11 +129,13 @@ const char* aboutLine[]={ "MAME SAA1099 by Juergen Buchmueller and Manuel Abadia", "MAME Namco WSG by Nicola Salmoria and Aaron Giles", "MAME RF5C68 core by Olivier Galibert and Aaron Giles", + "MAME MSM5232 core by Jarek Burczynski and Hiromitsu Shioya", "MAME MSM6258 core by Barry Rodewald", "MAME YMZ280B core by Aaron Giles", "SAASound by Dave Hooper and Simon Owen", "SameBoy by Lior Halphon", "Mednafen PCE and WonderSwan audio cores", + "SNES DSP core by Blargg", "puNES (NES, MMC5 and FDS) by FHorse", "NSFPlay (NES and FDS) by Brad Smith and Brezza", "reSID by Dag Lem", diff --git a/src/gui/compatFlags.cpp b/src/gui/compatFlags.cpp index fcea0fa0..e3ad642c 100644 --- a/src/gui/compatFlags.cpp +++ b/src/gui/compatFlags.cpp @@ -139,6 +139,10 @@ void FurnaceGUI::drawCompatFlags() { if (ImGui::IsItemHovered()) { ImGui::SetTooltip("if enabled, no checks for the presence of a volume macro will be made.\nthis will cause the last macro value to linger unless a value in the volume column is present."); } + ImGui::Checkbox("Broken output volume - Episode 2 (PLEASE KEEP ME DISABLED)",&e->song.brokenOutVol2); + if (ImGui::IsItemHovered()) { + ImGui::SetTooltip("these compatibility flags are getting SO damn ridiculous and out of control.\nas you may have guessed, this one exists due to yet ANOTHER DefleMask-specific behavior.\nplease keep this off at all costs, because I will not support it when ROM export comes.\noh, and don't start an argument out of it. Furnace isn't a DefleMask replacement, and no,\nI am not trying to make it look like one with all these flags.\n\noh, and what about the other flags that don't have to do with DefleMask?\nthose are for .mod import, future FamiTracker import and personal taste!\n\nend of rant"); + } ImGui::Checkbox("Treat SN76489 periods under 8 as 1",&e->song.snNoLowPeriods); if (ImGui::IsItemHovered()) { ImGui::SetTooltip("when enabled, any SN period under 8 will be written as 1 instead.\nthis replicates DefleMask behavior, but reduces available period range."); diff --git a/src/gui/dataList.cpp b/src/gui/dataList.cpp index defb68d0..9a1740be 100644 --- a/src/gui/dataList.cpp +++ b/src/gui/dataList.cpp @@ -199,7 +199,7 @@ void FurnaceGUI::drawInsList(bool asChild) { int curRow=0; for (int i=-1; i<(int)e->song.ins.size(); i++) { ImGui::PushID(i); - String name=ICON_FA_CIRCLE_O " - None -"; + String name=ICON_FA_CIRCLE_O; const char* insType="Bug!"; if (i>=0) { DivInstrument* ins=e->song.ins[i]; @@ -208,183 +208,183 @@ void FurnaceGUI::drawInsList(bool asChild) { switch (ins->type) { case DIV_INS_FM: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_FM]); - name=fmt::sprintf(ICON_FA_AREA_CHART " %.2X: %s##_INS%d",i,ins->name,i); + name=fmt::sprintf(ICON_FA_AREA_CHART "##_INS%d",i); break; case DIV_INS_STD: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_STD]); - name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); + name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i); break; case DIV_INS_GB: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_GB]); - name=fmt::sprintf(ICON_FA_GAMEPAD " %.2X: %s##_INS%d",i,ins->name,i); + name=fmt::sprintf(ICON_FA_GAMEPAD "##_INS%d",i); break; case DIV_INS_C64: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_C64]); - name=fmt::sprintf(ICON_FA_KEYBOARD_O " %.2X: %s##_INS%d",i,ins->name,i); + name=fmt::sprintf(ICON_FA_KEYBOARD_O "##_INS%d",i); break; case DIV_INS_AMIGA: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_AMIGA]); - name=fmt::sprintf(ICON_FA_VOLUME_UP " %.2X: %s##_INS%d",i,ins->name,i); + name=fmt::sprintf(ICON_FA_VOLUME_UP "##_INS%d",i); break; case DIV_INS_PCE: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_PCE]); - name=fmt::sprintf(ICON_FA_ID_BADGE " %.2X: %s##_INS%d",i,ins->name,i); + name=fmt::sprintf(ICON_FA_ID_BADGE "##_INS%d",i); break; case DIV_INS_AY: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_AY]); - name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); + name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i); break; case DIV_INS_AY8930: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_AY8930]); - name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); + name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i); break; case DIV_INS_TIA: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_TIA]); - name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); + name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i); break; case DIV_INS_SAA1099: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_SAA1099]); - name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); + name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i); break; case DIV_INS_VIC: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_VIC]); - name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); + name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i); break; case DIV_INS_PET: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_PET]); - name=fmt::sprintf(ICON_FA_SQUARE " %.2X: %s##_INS%d",i,ins->name,i); + name=fmt::sprintf(ICON_FA_SQUARE "##_INS%d",i); break; case DIV_INS_VRC6: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_VRC6]); - name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); + name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i); break; case DIV_INS_VRC6_SAW: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_VRC6_SAW]); - name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); + name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i); break; case DIV_INS_OPLL: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_OPLL]); - name=fmt::sprintf(ICON_FA_AREA_CHART " %.2X: %s##_INS%d",i,ins->name,i); + name=fmt::sprintf(ICON_FA_AREA_CHART "##_INS%d",i); break; case DIV_INS_OPL: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_OPL]); - name=fmt::sprintf(ICON_FA_AREA_CHART " %.2X: %s##_INS%d",i,ins->name,i); + name=fmt::sprintf(ICON_FA_AREA_CHART "##_INS%d",i); break; case DIV_INS_FDS: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_FDS]); - name=fmt::sprintf(ICON_FA_FLOPPY_O " %.2X: %s##_INS%d",i,ins->name,i); + name=fmt::sprintf(ICON_FA_FLOPPY_O "##_INS%d",i); break; case DIV_INS_VBOY: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_VBOY]); - name=fmt::sprintf(ICON_FA_BINOCULARS " %.2X: %s##_INS%d",i,ins->name,i); + name=fmt::sprintf(ICON_FA_BINOCULARS "##_INS%d",i); break; case DIV_INS_N163: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_N163]); - name=fmt::sprintf(ICON_FA_CALCULATOR " %.2X: %s##_INS%d",i,ins->name,i); + name=fmt::sprintf(ICON_FA_CALCULATOR "##_INS%d",i); break; case DIV_INS_SCC: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_SCC]); - name=fmt::sprintf(ICON_FA_CALCULATOR " %.2X: %s##_INS%d",i,ins->name,i); + name=fmt::sprintf(ICON_FA_CALCULATOR "##_INS%d",i); break; case DIV_INS_OPZ: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_OPZ]); - name=fmt::sprintf(ICON_FA_AREA_CHART " %.2X: %s##_INS%d",i,ins->name,i); + name=fmt::sprintf(ICON_FA_AREA_CHART "##_INS%d",i); break; case DIV_INS_POKEY: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_POKEY]); - name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); + name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i); break; case DIV_INS_BEEPER: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_BEEPER]); - name=fmt::sprintf(ICON_FA_SQUARE " %.2X: %s##_INS%d",i,ins->name,i); + name=fmt::sprintf(ICON_FA_SQUARE "##_INS%d",i); break; case DIV_INS_SWAN: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_SWAN]); - name=fmt::sprintf(ICON_FA_GAMEPAD " %.2X: %s##_INS%d",i,ins->name,i); + name=fmt::sprintf(ICON_FA_GAMEPAD "##_INS%d",i); break; case DIV_INS_MIKEY: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_MIKEY]); - name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); + name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i); break; case DIV_INS_VERA: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_VERA]); - name=fmt::sprintf(ICON_FA_KEYBOARD_O " %.2X: %s##_INS%d",i,ins->name,i); + name=fmt::sprintf(ICON_FA_KEYBOARD_O "##_INS%d",i); break; case DIV_INS_X1_010: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_X1_010]); - name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); + name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i); break; case DIV_INS_ES5506: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_ES5506]); - name=fmt::sprintf(ICON_FA_VOLUME_UP " %.2X: %s##_INS%d",i,ins->name,i); + name=fmt::sprintf(ICON_FA_VOLUME_UP "##_INS%d",i); break; case DIV_INS_MULTIPCM: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_MULTIPCM]); - name=fmt::sprintf(ICON_FA_VOLUME_UP " %.2X: %s##_INS%d",i,ins->name,i); + name=fmt::sprintf(ICON_FA_VOLUME_UP "##_INS%d",i); break; case DIV_INS_SNES: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_SNES]); - name=fmt::sprintf(ICON_FA_VOLUME_UP " %.2X: %s##_INS%d",i,ins->name,i); + name=fmt::sprintf(ICON_FA_VOLUME_UP "##_INS%d",i); break; case DIV_INS_SU: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_SU]); - name=fmt::sprintf(ICON_FA_MICROCHIP " %.2X: %s##_INS%d",i,ins->name,i); + name=fmt::sprintf(ICON_FA_MICROCHIP "##_INS%d",i); break; case DIV_INS_NAMCO: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_NAMCO]); - name=fmt::sprintf(ICON_FA_PIE_CHART " %.2X: %s##_INS%d",i,ins->name,i); + name=fmt::sprintf(ICON_FA_PIE_CHART "##_INS%d",i); break; case DIV_INS_OPL_DRUMS: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_OPL_DRUMS]); - name=fmt::sprintf(ICON_FA_COFFEE " %.2X: %s##_INS%d",i,ins->name,i); + name=fmt::sprintf(ICON_FA_COFFEE "##_INS%d",i); break; case DIV_INS_OPM: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_OPM]); - name=fmt::sprintf(ICON_FA_AREA_CHART " %.2X: %s##_INS%d",i,ins->name,i); + name=fmt::sprintf(ICON_FA_AREA_CHART "##_INS%d",i); break; case DIV_INS_NES: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_NES]); - name=fmt::sprintf(ICON_FA_GAMEPAD " %.2X: %s##_INS%d",i,ins->name,i); + name=fmt::sprintf(ICON_FA_GAMEPAD "##_INS%d",i); break; case DIV_INS_MSM6258: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_MSM6258]); - name=fmt::sprintf(ICON_FA_VOLUME_UP " %.2X: %s##_INS%d",i,ins->name,i); + name=fmt::sprintf(ICON_FA_VOLUME_UP "##_INS%d",i); break; case DIV_INS_MSM6295: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_MSM6295]); - name=fmt::sprintf(ICON_FA_VOLUME_UP " %.2X: %s##_INS%d",i,ins->name,i); + name=fmt::sprintf(ICON_FA_VOLUME_UP "##_INS%d",i); break; case DIV_INS_ADPCMA: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_ADPCMA]); - name=fmt::sprintf(ICON_FA_VOLUME_UP " %.2X: %s##_INS%d",i,ins->name,i); + name=fmt::sprintf(ICON_FA_VOLUME_UP "##_INS%d",i); break; case DIV_INS_ADPCMB: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_ADPCMB]); - name=fmt::sprintf(ICON_FA_VOLUME_UP " %.2X: %s##_INS%d",i,ins->name,i); + name=fmt::sprintf(ICON_FA_VOLUME_UP "##_INS%d",i); break; case DIV_INS_SEGAPCM: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_SEGAPCM]); - name=fmt::sprintf(ICON_FA_VOLUME_UP " %.2X: %s##_INS%d",i,ins->name,i); + name=fmt::sprintf(ICON_FA_VOLUME_UP "##_INS%d",i); break; case DIV_INS_QSOUND: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_QSOUND]); - name=fmt::sprintf(ICON_FA_VOLUME_UP " %.2X: %s##_INS%d",i,ins->name,i); + name=fmt::sprintf(ICON_FA_VOLUME_UP "##_INS%d",i); break; case DIV_INS_YMZ280B: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_YMZ280B]); - name=fmt::sprintf(ICON_FA_VOLUME_UP " %.2X: %s##_INS%d",i,ins->name,i); + name=fmt::sprintf(ICON_FA_VOLUME_UP "##_INS%d",i); break; case DIV_INS_RF5C68: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_RF5C68]); - name=fmt::sprintf(ICON_FA_VOLUME_UP " %.2X: %s##_INS%d",i,ins->name,i); + name=fmt::sprintf(ICON_FA_VOLUME_UP "##_INS%d",i); break; case DIV_INS_MSM5232: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_MSM5232]); - name=fmt::sprintf(ICON_FA_BAR_CHART " %.2X: %s##_INS%d",i,ins->name,i); + name=fmt::sprintf(ICON_FA_BAR_CHART "##_INS%d",i); break; default: ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_INSTR_UNKNOWN]); - name=fmt::sprintf(ICON_FA_QUESTION " %.2X: %s##_INS%d",i,ins->name,i); + name=fmt::sprintf(ICON_FA_QUESTION "##_INS%d",i); break; } } else { @@ -400,7 +400,6 @@ void FurnaceGUI::drawInsList(bool asChild) { curIns=i; wavePreviewInit=true; } - ImGui::PopStyleColor(); if (wantScrollList && curIns==i) ImGui::SetScrollHereY(); if (settings.insFocusesPattern && patternOpen && ImGui::IsItemActivated()) { nextWindow=GUI_WINDOW_PATTERN; @@ -408,7 +407,9 @@ void FurnaceGUI::drawInsList(bool asChild) { wavePreviewInit=true; } if (ImGui::IsItemHovered() && i>=0) { + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_TEXT]); ImGui::SetTooltip("%s",insType); + ImGui::PopStyleColor(); if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) { insEditOpen=true; nextWindow=GUI_WINDOW_INS_EDIT; @@ -417,6 +418,7 @@ void FurnaceGUI::drawInsList(bool asChild) { if (i>=0) { if (ImGui::BeginPopupContextItem("InsRightMenu")) { curIns=i; + ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_TEXT]); if (ImGui::MenuItem("replace...")) { doAction((curIns>=0 && curIns<(int)e->song.ins.size())?GUI_ACTION_INS_LIST_OPEN_REPLACE:GUI_ACTION_INS_LIST_OPEN); } @@ -429,9 +431,21 @@ void FurnaceGUI::drawInsList(bool asChild) { if (ImGui::MenuItem("delete")) { doAction(GUI_ACTION_INS_LIST_DELETE); } + ImGui::PopStyleColor(); ImGui::EndPopup(); } } + if (i>=0) { + if (i<(int)e->song.ins.size()) { + DivInstrument* ins=e->song.ins[i]; + ImGui::SameLine(); + ImGui::Text("%.2X: %s",i,ins->name.c_str()); + } + } else { + ImGui::SameLine(); + ImGui::Text("- None -"); + } + ImGui::PopStyleColor(); if (settings.horizontalDataView) { if (++curRow>=availableRows) curRow=0; } diff --git a/src/gui/debugWindow.cpp b/src/gui/debugWindow.cpp index 2ad6c4c4..03cbd354 100644 --- a/src/gui/debugWindow.cpp +++ b/src/gui/debugWindow.cpp @@ -30,6 +30,12 @@ void FurnaceGUI::drawDebug() { static int bpRow; static int bpTick; static bool bpOn; + + static double ptcClock; + static double ptcDivider; + static int ptcOctave; + static int ptcMode; + static int ptcBlockBits; if (nextWindow==GUI_WINDOW_DEBUG) { debugOpen=true; ImGui::SetNextWindowFocus(); @@ -98,7 +104,7 @@ void FurnaceGUI::drawDebug() { ImGui::Columns(); ImGui::TreePop(); } - if (ImGui::TreeNode("Playback Status")) { + if (ImGui::TreeNode("Channel Status")) { ImGui::Text("for best results set latency to minimum or use the Frame Advance button."); ImGui::Columns(e->getTotalChannelCount()); for (int i=0; igetTotalChannelCount(); i++) { @@ -160,6 +166,11 @@ void FurnaceGUI::drawDebug() { ImGui::Columns(); ImGui::TreePop(); } + if (ImGui::TreeNode("Playback Status")) { + String pdi=e->getPlaybackDebugInfo(); + ImGui::TextWrapped("%s",pdi.c_str()); + ImGui::TreePop(); + } if (ImGui::TreeNode("Sample Debug")) { for (int i=0; isong.sampleLen; i++) { DivSample* sample=e->getSample(i); @@ -296,6 +307,78 @@ void FurnaceGUI::drawDebug() { } ImGui::TreePop(); } + if (ImGui::TreeNode("Pitch Table Calculator")) { + ImGui::InputDouble("Clock",&ptcClock); + ImGui::InputDouble("Divider/FreqBase",&ptcDivider); + ImGui::InputInt("Octave",&ptcOctave); + if (ImGui::RadioButton("Frequency",ptcMode==0)) ptcMode=0; + ImGui::SameLine(); + if (ImGui::RadioButton("Period",ptcMode==1)) ptcMode=1; + ImGui::SameLine(); + if (ImGui::RadioButton("FreqNum/Block",ptcMode==2)) ptcMode=2; + + if (ptcMode==2) { + if (ImGui::InputInt("FreqNum Bits",&ptcBlockBits)) { + if (ptcBlockBits<0) ptcBlockBits=0; + if (ptcBlockBits>13) ptcBlockBits=13; + } + } + + if (ImGui::BeginTable("PitchTable",7)) { + ImGui::TableNextRow(ImGuiTableRowFlags_Headers); + ImGui::TableNextColumn(); + ImGui::Text("Note"); + ImGui::TableNextColumn(); + ImGui::Text("Pitch"); + + ImGui::TableNextColumn(); + ImGui::Text("Base"); + ImGui::TableNextColumn(); + ImGui::Text("Hex"); + + ImGui::TableNextColumn(); + ImGui::Text("Final"); + + ImGui::TableNextColumn(); + ImGui::Text("Hex"); + + ImGui::TableNextColumn(); + ImGui::Text("Delta"); + + int lastFinal=0; + for (int i=0; i<12; i++) { + int note=(12*ptcOctave)+i; + int pitch=0; + + int base=e->calcBaseFreq(ptcClock,ptcDivider,note,ptcMode==1); + int final=e->calcFreq(base,pitch,ptcMode==1,0,0,ptcClock,ptcDivider,(ptcMode==2)?ptcBlockBits:0); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("%d",note); + ImGui::TableNextColumn(); + ImGui::Text("%d",pitch); + + ImGui::TableNextColumn(); + ImGui::Text("%d",base); + ImGui::TableNextColumn(); + ImGui::Text("%x",base); + + ImGui::TableNextColumn(); + ImGui::Text("%d",final); + ImGui::TableNextColumn(); + ImGui::Text("%x",final); + + ImGui::TableNextColumn(); + ImGui::Text("%d",final-lastFinal); + + lastFinal=final; + } + + ImGui::EndTable(); + } + ImGui::TreePop(); + } if (ImGui::TreeNode("Playground")) { if (pgSys<0 || pgSys>=e->song.systemLen) pgSys=0; if (ImGui::BeginCombo("Chip",fmt::sprintf("%d. %s",pgSys+1,e->getSystemName(e->song.system[pgSys])).c_str())) { diff --git a/src/gui/doAction.cpp b/src/gui/doAction.cpp index 31d0ff48..6b35f912 100644 --- a/src/gui/doAction.cpp +++ b/src/gui/doAction.cpp @@ -1439,6 +1439,12 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_ORDERS_REMOVE: prepareUndo(GUI_UNDO_CHANGE_ORDER); e->deleteOrder(); + if (curOrder>=e->curSubSong->ordersLen) { + curOrder=e->curSubSong->ordersLen-1; + oldOrder=curOrder; + oldOrder1=curOrder; + e->setOrder(curOrder); + } makeUndo(GUI_UNDO_CHANGE_ORDER); break; case GUI_ACTION_ORDERS_MOVE_UP: diff --git a/src/gui/editing.cpp b/src/gui/editing.cpp index 153d70da..17d2886a 100644 --- a/src/gui/editing.cpp +++ b/src/gui/editing.cpp @@ -565,6 +565,8 @@ void FurnaceGUI::doPaste(PasteMode mode, int arg) { if (settings.cursorPastePos) { cursor.y=j; if (cursor.y>=e->curSubSong->patLen) cursor.y=e->curSubSong->patLen-1; + selStart=cursor; + selEnd=cursor; updateScroll(cursor.y); } diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 63a6fbff..f9e1237f 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -1993,6 +1993,10 @@ void FurnaceGUI::showError(String what) { displayError=true; } +String FurnaceGUI::getLastError() { + return lastError; +} + // what monster did I just create here? #define B30(tt) (macroDragBit30?((((tt)&0xc0000000)==0x40000000 || ((tt)&0xc0000000)==0x80000000)?0x40000000:0):0) @@ -3291,6 +3295,11 @@ bool FurnaceGUI::loop() { } if (recentFile.empty()) { ImGui::Text("nothing here yet"); + } else { + ImGui::Separator(); + if (ImGui::MenuItem("clear history")) { + showWarning("Are you sure you want to clear the recent file list?",GUI_WARN_CLEAR_HISTORY); + } } ImGui::EndMenu(); } @@ -4630,6 +4639,16 @@ bool FurnaceGUI::loop() { ImGui::CloseCurrentPopup(); } break; + case GUI_WARN_CLEAR_HISTORY: + if (ImGui::Button("Yes")) { + recentFile.clear(); + ImGui::CloseCurrentPopup(); + } + ImGui::SameLine(); + if (ImGui::Button("No")) { + ImGui::CloseCurrentPopup(); + } + break; case GUI_WARN_GENERIC: if (ImGui::Button("OK")) { ImGui::CloseCurrentPopup(); @@ -5002,7 +5021,7 @@ bool FurnaceGUI::init() { sdlWin=SDL_CreateWindow("Furnace",scrX,scrY,scrW*dpiScale,scrH*dpiScale,SDL_WINDOW_RESIZABLE|SDL_WINDOW_ALLOW_HIGHDPI|(scrMax?SDL_WINDOW_MAXIMIZED:0)|(fullScreen?SDL_WINDOW_FULLSCREEN_DESKTOP:0)); if (sdlWin==NULL) { - logE("could not open window! %s",SDL_GetError()); + lastError=fmt::sprintf("could not open window! %s",SDL_GetError()); return false; } @@ -5057,7 +5076,7 @@ bool FurnaceGUI::init() { sdlRend=SDL_CreateRenderer(sdlWin,-1,SDL_RENDERER_ACCELERATED|SDL_RENDERER_PRESENTVSYNC|SDL_RENDERER_TARGETTEXTURE); if (sdlRend==NULL) { - logE("could not init renderer! %s",SDL_GetError()); + lastError=fmt::sprintf("could not init renderer! %s",SDL_GetError()); return false; } @@ -5401,6 +5420,7 @@ FurnaceGUI::FurnaceGUI(): curWindow(GUI_WINDOW_NOTHING), nextWindow(GUI_WINDOW_NOTHING), curWindowLast(GUI_WINDOW_NOTHING), + lastPatternWidth(0.0f), nextDesc(NULL), latchNote(-1), latchIns(-2), diff --git a/src/gui/gui.h b/src/gui/gui.h index fa00d76c..bb23ccee 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -355,6 +355,7 @@ enum FurnaceGUIWarnings { GUI_WARN_CLEAR, GUI_WARN_SUBSONG_DEL, GUI_WARN_SYSTEM_DEL, + GUI_WARN_CLEAR_HISTORY, GUI_WARN_GENERIC }; @@ -1208,6 +1209,7 @@ class FurnaceGUI { int midiOutClock; int midiOutMode; int maxRecentFile; + int centerPattern; unsigned int maxUndoSteps; String mainFontPath; String patFontPath; @@ -1333,6 +1335,7 @@ class FurnaceGUI { midiOutClock(0), midiOutMode(1), maxRecentFile(10), + centerPattern(0), maxUndoSteps(100), mainFontPath(""), patFontPath(""), @@ -1372,6 +1375,7 @@ class FurnaceGUI { float peak[2]; float patChanX[DIV_MAX_CHANS+1]; float patChanSlideY[DIV_MAX_CHANS+1]; + float lastPatternWidth; const int* nextDesc; String nextDescName; @@ -1801,6 +1805,7 @@ class FurnaceGUI { public: void showWarning(String what, FurnaceGUIWarnings type); void showError(String what); + String getLastError(); const char* noteNameNormal(short note, short octave); const char* noteName(short note, short octave); bool decodeNote(const char* what, short& note, short& octave); diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index dd659e8c..7afefffd 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -936,6 +936,7 @@ const int availableSystems[]={ DIV_SYSTEM_QSOUND, DIV_SYSTEM_X1_010, DIV_SYSTEM_SWAN, + DIV_SYSTEM_VBOY, DIV_SYSTEM_VERA, DIV_SYSTEM_BUBSYS_WSG, DIV_SYSTEM_N163, @@ -1009,6 +1010,7 @@ const int chipsWave[]={ DIV_SYSTEM_PCE, DIV_SYSTEM_X1_010, DIV_SYSTEM_SWAN, + DIV_SYSTEM_VBOY, DIV_SYSTEM_BUBSYS_WSG, DIV_SYSTEM_N163, DIV_SYSTEM_FDS, diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 96c0a43f..e1bd4ab3 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -203,6 +203,17 @@ enum FMParams { #define FM_NAME(x) fmParamNames[settings.fmNames][x] #define FM_SHORT_NAME(x) fmParamShortNames[settings.fmNames][x] +const char* macroTypeLabels[4]={ + ICON_FA_BAR_CHART "##IMacroType", + ICON_FA_AREA_CHART "##IMacroType", + ICON_FA_LINE_CHART "##IMacroType", + ICON_FA_SIGN_OUT "##IMacroType" +}; + +const char* macroLFOShapes[4]={ + "Triangle", "Saw", "Square", "How did you even" +}; + const char* fmOperatorBits[5]={ "op1", "op2", "op3", "op4", NULL }; @@ -346,7 +357,7 @@ String macroHoverNote(int id, float val, void* u) { } String macroHover(int id, float val, void* u) { - return fmt::sprintf("%d: %d",id,val); + return fmt::sprintf("%d: %d",id,(int)val); } String macroHoverLoop(int id, float val, void* u) { @@ -360,6 +371,22 @@ String macroHoverBit30(int id, float val, void* u) { return "Relative"; } +String macroHoverGain(int id, float val, void* u) { + if (val>=224.0f) { + return fmt::sprintf("%d: +%d (exponential)",id,(int)(val-224)); + } + if (val>=192.0f) { + return fmt::sprintf("%d: +%d (linear)",id,(int)(val-192)); + } + if (val>=160.0f) { + return fmt::sprintf("%d: -%d (exponential)",id,(int)(val-160)); + } + if (val>=128.0f) { + return fmt::sprintf("%d: -%d (linear)",id,(int)(val-128)); + } + return fmt::sprintf("%d: %d (direct)",id,(int)val); +} + String macroHoverES5506FilterMode(int id, float val, void* u) { String mode="???"; switch (((int)val)&3) { @@ -1314,22 +1341,67 @@ void FurnaceGUI::drawMacros(std::vector& macros) { ImGui::TableNextColumn(); ImGui::Text("%s",i.displayName); ImGui::SameLine(); - if (ImGui::SmallButton(i.macro->open?(ICON_FA_CHEVRON_UP "##IMacroOpen"):(ICON_FA_CHEVRON_DOWN "##IMacroOpen"))) { - i.macro->open=!i.macro->open; + if (ImGui::SmallButton((i.macro->open&1)?(ICON_FA_CHEVRON_UP "##IMacroOpen"):(ICON_FA_CHEVRON_DOWN "##IMacroOpen"))) { + i.macro->open^=1; } - if (i.macro->open) { - ImGui::SetNextItemWidth(lenAvail); - int macroLen=i.macro->len; - if (ImGui::InputScalar("##IMacroLen",ImGuiDataType_U8,¯oLen,&_ONE,&_THREE)) { MARK_MODIFIED - if (macroLen<0) macroLen=0; - if (macroLen>255) macroLen=255; - i.macro->len=macroLen; + if (i.macro->open&1) { + if ((i.macro->open&6)==0) { + ImGui::SetNextItemWidth(lenAvail); + int macroLen=i.macro->len; + if (ImGui::InputScalar("##IMacroLen",ImGuiDataType_U8,¯oLen,&_ONE,&_THREE)) { MARK_MODIFIED + if (macroLen<0) macroLen=0; + if (macroLen>255) macroLen=255; + i.macro->len=macroLen; + } } - if (ImGui::Button(ICON_FA_BAR_CHART "##IMacroType")) { - + if (ImGui::Button(macroTypeLabels[(i.macro->open>>1)&3])) { + unsigned char prevOpen=i.macro->open; + i.macro->open+=2; + if (i.macro->open>=6) { + i.macro->open-=6; + } + + // check whether macro type is now ADSR/LFO or sequence + if (((prevOpen&6)?1:0)!=((i.macro->open&6)?1:0)) { + // swap memory + // this way the macro isn't corrupted if the user decides to go + // back to sequence mode + i.macro->len^=i.macro->lenMemory; + i.macro->lenMemory^=i.macro->len; + i.macro->len^=i.macro->lenMemory; + + for (int j=0; j<16; j++) { + i.macro->val[j]^=i.macro->typeMemory[j]; + i.macro->typeMemory[j]^=i.macro->val[j]; + i.macro->val[j]^=i.macro->typeMemory[j]; + } + + // if ADSR/LFO, populate min/max + if (i.macro->open&6) { + i.macro->val[0]=i.min; + i.macro->val[1]=i.max; + } + } + PARAMETER; } if (ImGui::IsItemHovered()) { - ImGui::SetTooltip("Coming soon!"); + switch (i.macro->open&6) { + case 0: + ImGui::SetTooltip("Macro type: Sequence"); + break; + case 2: + ImGui::SetTooltip("Macro type: ADSR"); + break; + case 4: + ImGui::SetTooltip("Macro type: LFO"); + break; + default: + ImGui::SetTooltip("Macro type: What's going on here?"); + break; + } + } + if (i.macro->open&6) { + i.macro->len=16; } ImGui::SameLine(); ImGui::Button(ICON_FA_ELLIPSIS_H "##IMacroSet"); @@ -1360,204 +1432,371 @@ void FurnaceGUI::drawMacros(std::vector& macros) { // macro area ImGui::TableNextColumn(); - for (int j=0; j<256; j++) { - bit30Indicator[j]=0; - if (j+macroDragScroll>=i.macro->len) { - asFloat[j]=0; - asInt[j]=0; - } else { - asFloat[j]=deBit30(i.macro->val[j+macroDragScroll]); - asInt[j]=deBit30(i.macro->val[j+macroDragScroll])+i.bitOffset; - if (i.bit30) bit30Indicator[j]=enBit30(i.macro->val[j+macroDragScroll]); + if ((i.macro->open&6)==0) { + for (int j=0; j<256; j++) { + bit30Indicator[j]=0; + if (j+macroDragScroll>=i.macro->len) { + asFloat[j]=0; + asInt[j]=0; + } else { + asFloat[j]=deBit30(i.macro->val[j+macroDragScroll]); + asInt[j]=deBit30(i.macro->val[j+macroDragScroll])+i.bitOffset; + if (i.bit30) bit30Indicator[j]=enBit30(i.macro->val[j+macroDragScroll]); + } + if (j+macroDragScroll>=i.macro->len || (j+macroDragScroll>i.macro->rel && i.macro->looprel)) { + loopIndicator[j]=0; + } else { + loopIndicator[j]=((i.macro->loop!=255 && (j+macroDragScroll)>=i.macro->loop))|((i.macro->rel!=255 && (j+macroDragScroll)==i.macro->rel)<<1); + } } - if (j+macroDragScroll>=i.macro->len || (j+macroDragScroll>i.macro->rel && i.macro->looprel)) { - loopIndicator[j]=0; - } else { - loopIndicator[j]=((i.macro->loop!=255 && (j+macroDragScroll)>=i.macro->loop))|((i.macro->rel!=255 && (j+macroDragScroll)==i.macro->rel)<<1); - } - } - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,ImVec2(0.0f,0.0f)); + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,ImVec2(0.0f,0.0f)); - if (i.macro->vZoom<1) { - if (i.macro->name=="arp") { - i.macro->vZoom=24; - i.macro->vScroll=120-12; - } else if (i.macro->name=="pitch") { - i.macro->vZoom=128; - i.macro->vScroll=2048-64; - } else { + if (i.macro->vZoom<1) { + if (i.macro->name=="arp") { + i.macro->vZoom=24; + i.macro->vScroll=120-12; + } else if (i.macro->name=="pitch") { + i.macro->vZoom=128; + i.macro->vScroll=2048-64; + } else { + i.macro->vZoom=i.max-i.min; + i.macro->vScroll=0; + } + } + if (i.macro->vZoom>(i.max-i.min)) { i.macro->vZoom=i.max-i.min; - i.macro->vScroll=0; } - } - if (i.macro->vZoom>(i.max-i.min)) { - i.macro->vZoom=i.max-i.min; - } - memset(doHighlight,0,256*sizeof(bool)); - if (e->isRunning()) for (int j=0; jgetTotalChannelCount(); j++) { - DivChannelState* chanState=e->getChanState(j); - if (chanState==NULL) continue; + memset(doHighlight,0,256*sizeof(bool)); + if (e->isRunning()) for (int j=0; jgetTotalChannelCount(); j++) { + DivChannelState* chanState=e->getChanState(j); + if (chanState==NULL) continue; - if (chanState->keyOff) continue; - if (chanState->lastIns!=curIns) continue; + if (chanState->keyOff) continue; + if (chanState->lastIns!=curIns) continue; - DivMacroInt* macroInt=e->getMacroInt(j); - if (macroInt==NULL) continue; + DivMacroInt* macroInt=e->getMacroInt(j); + if (macroInt==NULL) continue; - DivMacroStruct* macroStruct=macroInt->structByName(i.macro->name); - if (macroStruct==NULL) continue; + DivMacroStruct* macroStruct=macroInt->structByName(i.macro->name); + if (macroStruct==NULL) continue; - if (macroStruct->lastPos>i.macro->len) continue; - if (macroStruct->lastPoslastPos>255) continue; - if (!macroStruct->actualHad) continue; + if (macroStruct->lastPos>i.macro->len) continue; + if (macroStruct->lastPoslastPos>255) continue; + if (!macroStruct->actualHad) continue; - doHighlight[macroStruct->lastPos-macroDragScroll]=true; - } + doHighlight[macroStruct->lastPos-macroDragScroll]=true; + } - if (i.isBitfield) { - PlotBitfield("##IMacro",asInt,totalFit,0,i.bitfieldBits,i.max,ImVec2(availableWidth,(i.macro->open)?(i.height*dpiScale):(32.0f*dpiScale)),sizeof(float),doHighlight); - } else { - PlotCustom("##IMacro",asFloat,totalFit,macroDragScroll,NULL,i.min+i.macro->vScroll,i.min+i.macro->vScroll+i.macro->vZoom,ImVec2(availableWidth,(i.macro->open)?(i.height*dpiScale):(32.0f*dpiScale)),sizeof(float),i.color,i.macro->len-macroDragScroll,i.hoverFunc,i.hoverFuncUser,i.blockMode,i.macro->open?genericGuide:NULL,doHighlight); - } - if (i.macro->open && (ImGui::IsItemClicked(ImGuiMouseButton_Left) || ImGui::IsItemClicked(ImGuiMouseButton_Right))) { - macroDragStart=ImGui::GetItemRectMin(); - macroDragAreaSize=ImVec2(availableWidth,i.height*dpiScale); if (i.isBitfield) { - macroDragMin=i.min; - macroDragMax=i.max; + PlotBitfield("##IMacro",asInt,totalFit,0,i.bitfieldBits,i.max,ImVec2(availableWidth,(i.macro->open&1)?(i.height*dpiScale):(32.0f*dpiScale)),sizeof(float),doHighlight); } else { - macroDragMin=i.min+i.macro->vScroll; - macroDragMax=i.min+i.macro->vScroll+i.macro->vZoom; + PlotCustom("##IMacro",asFloat,totalFit,macroDragScroll,NULL,i.min+i.macro->vScroll,i.min+i.macro->vScroll+i.macro->vZoom,ImVec2(availableWidth,(i.macro->open&1)?(i.height*dpiScale):(32.0f*dpiScale)),sizeof(float),i.color,i.macro->len-macroDragScroll,i.hoverFunc,i.hoverFuncUser,i.blockMode,(i.macro->open&1)?genericGuide:NULL,doHighlight); } - macroDragBitOff=i.bitOffset; - macroDragBitMode=i.isBitfield; - macroDragInitialValueSet=false; - macroDragInitialValue=false; - macroDragLen=totalFit; - macroDragActive=true; - macroDragBit30=i.bit30; - macroDragSettingBit30=false; - macroDragTarget=i.macro->val; - macroDragChar=false; - macroDragLineMode=(i.isBitfield)?false:ImGui::IsItemClicked(ImGuiMouseButton_Right); - macroDragLineInitial=ImVec2(0,0); - lastMacroDesc=i; - processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); - } - if (i.macro->open) { - if (ImGui::IsItemHovered()) { - if (ctrlWheeling) { - if (ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) { - i.macro->vZoom+=wheelY*(1+(i.macro->vZoom>>4)); - if (i.macro->vZoom<1) i.macro->vZoom=1; - if (i.macro->vZoom>(i.max-i.min)) i.macro->vZoom=i.max-i.min; - if ((i.macro->vScroll+i.macro->vZoom)>(i.max-i.min)) { - i.macro->vScroll=(i.max-i.min)-i.macro->vZoom; - } - } else { - macroPointSize+=wheelY; - if (macroPointSize<1) macroPointSize=1; - if (macroPointSize>256) macroPointSize=256; - } - } else if ((ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) && wheelY!=0) { - i.macro->vScroll+=wheelY*(1+(i.macro->vZoom>>4)); - if (i.macro->vScroll<0) i.macro->vScroll=0; - if (i.macro->vScroll>((i.max-i.min)-i.macro->vZoom)) i.macro->vScroll=(i.max-i.min)-i.macro->vZoom; - } - } - - // slider - if (!i.isBitfield) { - if (settings.oldMacroVSlider) { - ImGui::SameLine(0.0f); - if (ImGui::VSliderInt("IMacroVScroll",ImVec2(20.0f*dpiScale,i.height*dpiScale),&i.macro->vScroll,0,(i.max-i.min)-i.macro->vZoom,"")) { - if (i.macro->vScroll<0) i.macro->vScroll=0; - if (i.macro->vScroll>((i.max-i.min)-i.macro->vZoom)) i.macro->vScroll=(i.max-i.min)-i.macro->vZoom; - } - if (ImGui::IsItemHovered() && ctrlWheeling) { - i.macro->vScroll+=wheelY*(1+(i.macro->vZoom>>4)); - if (i.macro->vScroll<0) i.macro->vScroll=0; - if (i.macro->vScroll>((i.max-i.min)-i.macro->vZoom)) i.macro->vScroll=(i.max-i.min)-i.macro->vZoom; - } + if ((i.macro->open&1) && (ImGui::IsItemClicked(ImGuiMouseButton_Left) || ImGui::IsItemClicked(ImGuiMouseButton_Right))) { + macroDragStart=ImGui::GetItemRectMin(); + macroDragAreaSize=ImVec2(availableWidth,i.height*dpiScale); + if (i.isBitfield) { + macroDragMin=i.min; + macroDragMax=i.max; } else { - ImS64 scrollV=(i.max-i.min-i.macro->vZoom)-i.macro->vScroll; - ImS64 availV=i.macro->vZoom; - ImS64 contentsV=(i.max-i.min); - - ImGui::SameLine(0.0f); - ImGui::SetCursorPosX(ImGui::GetCursorPosX()-ImGui::GetStyle().ItemSpacing.x); - ImRect scrollbarPos=ImRect(ImGui::GetCursorScreenPos(),ImGui::GetCursorScreenPos()); - scrollbarPos.Max.x+=ImGui::GetStyle().ScrollbarSize; - scrollbarPos.Max.y+=i.height*dpiScale; - ImGui::Dummy(ImVec2(ImGui::GetStyle().ScrollbarSize,i.height*dpiScale)); - if (ImGui::IsItemHovered() && ctrlWheeling) { - i.macro->vScroll+=wheelY*(1+(i.macro->vZoom>>4)); - if (i.macro->vScroll<0) i.macro->vScroll=0; - if (i.macro->vScroll>((i.max-i.min)-i.macro->vZoom)) i.macro->vScroll=(i.max-i.min)-i.macro->vZoom; - } - - ImGuiID scrollbarID=ImGui::GetID("IMacroVScroll"); - ImGui::KeepAliveID(scrollbarID); - if (ImGui::ScrollbarEx(scrollbarPos,scrollbarID,ImGuiAxis_Y,&scrollV,availV,contentsV,0)) { - i.macro->vScroll=(i.max-i.min-i.macro->vZoom)-scrollV; - } + macroDragMin=i.min+i.macro->vScroll; + macroDragMax=i.min+i.macro->vScroll+i.macro->vZoom; } - } - - // bit 30 area - if (i.bit30) { - PlotCustom("##IMacroBit30",bit30Indicator,totalFit,macroDragScroll,NULL,0,1,ImVec2(availableWidth,12.0f*dpiScale),sizeof(float),i.color,i.macro->len-macroDragScroll,¯oHoverBit30); - if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { - macroDragStart=ImGui::GetItemRectMin(); - macroDragAreaSize=ImVec2(availableWidth,12.0f*dpiScale); - macroDragInitialValueSet=false; - macroDragInitialValue=false; - macroDragLen=totalFit; - macroDragActive=true; - macroDragBit30=i.bit30; - macroDragSettingBit30=true; - macroDragTarget=i.macro->val; - macroDragChar=false; - macroDragLineMode=false; - macroDragLineInitial=ImVec2(0,0); - lastMacroDesc=i; - processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); - } - } - - // loop area - PlotCustom("##IMacroLoop",loopIndicator,totalFit,macroDragScroll,NULL,0,2,ImVec2(availableWidth,12.0f*dpiScale),sizeof(float),i.color,i.macro->len-macroDragScroll,¯oHoverLoop); - if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { - macroLoopDragStart=ImGui::GetItemRectMin(); - macroLoopDragAreaSize=ImVec2(availableWidth,12.0f*dpiScale); - macroLoopDragLen=totalFit; - if (ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) { - macroLoopDragTarget=&i.macro->rel; - } else { - macroLoopDragTarget=&i.macro->loop; - } - macroLoopDragActive=true; + macroDragBitOff=i.bitOffset; + macroDragBitMode=i.isBitfield; + macroDragInitialValueSet=false; + macroDragInitialValue=false; + macroDragLen=totalFit; + macroDragActive=true; + macroDragBit30=i.bit30; + macroDragSettingBit30=false; + macroDragTarget=i.macro->val; + macroDragChar=false; + macroDragLineMode=(i.isBitfield)?false:ImGui::IsItemClicked(ImGuiMouseButton_Right); + macroDragLineInitial=ImVec2(0,0); + lastMacroDesc=i; processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); } - if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { - if (ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) { - i.macro->rel=255; - } else { - i.macro->loop=255; + if ((i.macro->open&1)) { + if (ImGui::IsItemHovered()) { + if (ctrlWheeling) { + if (ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) { + i.macro->vZoom+=wheelY*(1+(i.macro->vZoom>>4)); + if (i.macro->vZoom<1) i.macro->vZoom=1; + if (i.macro->vZoom>(i.max-i.min)) i.macro->vZoom=i.max-i.min; + if ((i.macro->vScroll+i.macro->vZoom)>(i.max-i.min)) { + i.macro->vScroll=(i.max-i.min)-i.macro->vZoom; + } + } else { + macroPointSize+=wheelY; + if (macroPointSize<1) macroPointSize=1; + if (macroPointSize>256) macroPointSize=256; + } + } else if ((ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) && wheelY!=0) { + i.macro->vScroll+=wheelY*(1+(i.macro->vZoom>>4)); + if (i.macro->vScroll<0) i.macro->vScroll=0; + if (i.macro->vScroll>((i.max-i.min)-i.macro->vZoom)) i.macro->vScroll=(i.max-i.min)-i.macro->vZoom; + } + } + + // slider + if (!i.isBitfield) { + if (settings.oldMacroVSlider) { + ImGui::SameLine(0.0f); + if (ImGui::VSliderInt("IMacroVScroll",ImVec2(20.0f*dpiScale,i.height*dpiScale),&i.macro->vScroll,0,(i.max-i.min)-i.macro->vZoom,"")) { + if (i.macro->vScroll<0) i.macro->vScroll=0; + if (i.macro->vScroll>((i.max-i.min)-i.macro->vZoom)) i.macro->vScroll=(i.max-i.min)-i.macro->vZoom; + } + if (ImGui::IsItemHovered() && ctrlWheeling) { + i.macro->vScroll+=wheelY*(1+(i.macro->vZoom>>4)); + if (i.macro->vScroll<0) i.macro->vScroll=0; + if (i.macro->vScroll>((i.max-i.min)-i.macro->vZoom)) i.macro->vScroll=(i.max-i.min)-i.macro->vZoom; + } + } else { + ImS64 scrollV=(i.max-i.min-i.macro->vZoom)-i.macro->vScroll; + ImS64 availV=i.macro->vZoom; + ImS64 contentsV=(i.max-i.min); + + ImGui::SameLine(0.0f); + ImGui::SetCursorPosX(ImGui::GetCursorPosX()-ImGui::GetStyle().ItemSpacing.x); + ImRect scrollbarPos=ImRect(ImGui::GetCursorScreenPos(),ImGui::GetCursorScreenPos()); + scrollbarPos.Max.x+=ImGui::GetStyle().ScrollbarSize; + scrollbarPos.Max.y+=i.height*dpiScale; + ImGui::Dummy(ImVec2(ImGui::GetStyle().ScrollbarSize,i.height*dpiScale)); + if (ImGui::IsItemHovered() && ctrlWheeling) { + i.macro->vScroll+=wheelY*(1+(i.macro->vZoom>>4)); + if (i.macro->vScroll<0) i.macro->vScroll=0; + if (i.macro->vScroll>((i.max-i.min)-i.macro->vZoom)) i.macro->vScroll=(i.max-i.min)-i.macro->vZoom; + } + + ImGuiID scrollbarID=ImGui::GetID("IMacroVScroll"); + ImGui::KeepAliveID(scrollbarID); + if (ImGui::ScrollbarEx(scrollbarPos,scrollbarID,ImGuiAxis_Y,&scrollV,availV,contentsV,0)) { + i.macro->vScroll=(i.max-i.min-i.macro->vZoom)-scrollV; + } + } + } + + // bit 30 area + if (i.bit30) { + PlotCustom("##IMacroBit30",bit30Indicator,totalFit,macroDragScroll,NULL,0,1,ImVec2(availableWidth,12.0f*dpiScale),sizeof(float),i.color,i.macro->len-macroDragScroll,¯oHoverBit30); + if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { + macroDragStart=ImGui::GetItemRectMin(); + macroDragAreaSize=ImVec2(availableWidth,12.0f*dpiScale); + macroDragInitialValueSet=false; + macroDragInitialValue=false; + macroDragLen=totalFit; + macroDragActive=true; + macroDragBit30=i.bit30; + macroDragSettingBit30=true; + macroDragTarget=i.macro->val; + macroDragChar=false; + macroDragLineMode=false; + macroDragLineInitial=ImVec2(0,0); + lastMacroDesc=i; + processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); + } + } + + // loop area + PlotCustom("##IMacroLoop",loopIndicator,totalFit,macroDragScroll,NULL,0,2,ImVec2(availableWidth,12.0f*dpiScale),sizeof(float),i.color,i.macro->len-macroDragScroll,¯oHoverLoop); + if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) { + macroLoopDragStart=ImGui::GetItemRectMin(); + macroLoopDragAreaSize=ImVec2(availableWidth,12.0f*dpiScale); + macroLoopDragLen=totalFit; + if (ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) { + macroLoopDragTarget=&i.macro->rel; + } else { + macroLoopDragTarget=&i.macro->loop; + } + macroLoopDragActive=true; + processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); + } + if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) { + if (ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) { + i.macro->rel=255; + } else { + i.macro->loop=255; + } + } + ImGui::SetNextItemWidth(availableWidth); + String& mmlStr=mmlString[index]; + if (ImGui::InputText("##IMacroMML",&mmlStr)) { + decodeMMLStr(mmlStr,i.macro->val,i.macro->len,i.macro->loop,i.min,(i.isBitfield)?((1<<(i.isBitfield?i.max:0))-1):i.max,i.macro->rel,i.bit30); + } + if (!ImGui::IsItemActive()) { + encodeMMLStr(mmlStr,i.macro->val,i.macro->len,i.macro->loop,i.macro->rel,false,i.bit30); } } - ImGui::SetNextItemWidth(availableWidth); - String& mmlStr=mmlString[index]; - if (ImGui::InputText("##IMacroMML",&mmlStr)) { - decodeMMLStr(mmlStr,i.macro->val,i.macro->len,i.macro->loop,i.min,(i.isBitfield)?((1<<(i.isBitfield?i.max:0))-1):i.max,i.macro->rel,i.bit30); + ImGui::PopStyleVar(); + } else { + if (i.macro->open&2) { + if (ImGui::BeginTable("MacroADSR",4)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.3); + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthStretch,0.3); + //ImGui::TableSetupColumn("c4",ImGuiTableColumnFlags_WidthStretch,0.4); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Bottom"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputInt("##MABottom",&i.macro->val[0],1,16)) { PARAMETER + if (i.macro->val[0]val[0]=i.min; + if (i.macro->val[0]>i.max) i.macro->val[0]=i.max; + } + + ImGui::TableNextColumn(); + ImGui::Text("Top"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputInt("##MATop",&i.macro->val[1],1,16)) { PARAMETER + if (i.macro->val[1]val[1]=i.min; + if (i.macro->val[1]>i.max) i.macro->val[1]=i.max; + } + + /*ImGui::TableNextColumn(); + ImGui::Text("the envelope goes here");*/ + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Attack"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (CWSliderInt("##MAAR",&i.macro->val[2],0,255)) { PARAMETER + if (i.macro->val[2]<0) i.macro->val[2]=0; + if (i.macro->val[2]>255) i.macro->val[2]=255; + } + + ImGui::TableNextColumn(); + ImGui::Text("Sustain"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (CWSliderInt("##MASL",&i.macro->val[5],0,255)) { PARAMETER + if (i.macro->val[5]<0) i.macro->val[5]=0; + if (i.macro->val[5]>255) i.macro->val[5]=255; + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Hold"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (CWSliderInt("##MAHT",&i.macro->val[3],0,255)) { PARAMETER + if (i.macro->val[3]<0) i.macro->val[3]=0; + if (i.macro->val[3]>255) i.macro->val[3]=255; + } + + ImGui::TableNextColumn(); + ImGui::Text("SusTime"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (CWSliderInt("##MAST",&i.macro->val[6],0,255)) { PARAMETER + if (i.macro->val[6]<0) i.macro->val[6]=0; + if (i.macro->val[6]>255) i.macro->val[6]=255; + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Decay"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (CWSliderInt("##MADR",&i.macro->val[4],0,255)) { PARAMETER + if (i.macro->val[4]<0) i.macro->val[4]=0; + if (i.macro->val[4]>255) i.macro->val[4]=255; + } + + ImGui::TableNextColumn(); + ImGui::Text("SusDecay"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (CWSliderInt("##MASR",&i.macro->val[7],0,255)) { PARAMETER + if (i.macro->val[7]<0) i.macro->val[7]=0; + if (i.macro->val[7]>255) i.macro->val[7]=255; + } + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::TableNextColumn(); + + ImGui::TableNextColumn(); + ImGui::Text("Release"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (CWSliderInt("##MARR",&i.macro->val[8],0,255)) { PARAMETER + if (i.macro->val[8]<0) i.macro->val[8]=0; + if (i.macro->val[8]>255) i.macro->val[8]=255; + } + + ImGui::EndTable(); + } } - if (!ImGui::IsItemActive()) { - encodeMMLStr(mmlStr,i.macro->val,i.macro->len,i.macro->loop,i.macro->rel,false,i.bit30); + if (i.macro->open&4) { + if (ImGui::BeginTable("MacroLFO",4)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.3); + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthStretch,0.3); + //ImGui::TableSetupColumn("c4",ImGuiTableColumnFlags_WidthStretch,0.4); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Bottom"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputInt("##MABottom",&i.macro->val[0],1,16)) { PARAMETER + if (i.macro->val[0]val[0]=i.min; + if (i.macro->val[0]>i.max) i.macro->val[0]=i.max; + } + + ImGui::TableNextColumn(); + ImGui::Text("Top"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (ImGui::InputInt("##MATop",&i.macro->val[1],1,16)) { PARAMETER + if (i.macro->val[1]val[1]=i.min; + if (i.macro->val[1]>i.max) i.macro->val[1]=i.max; + } + + /*ImGui::TableNextColumn(); + ImGui::Text("the envelope goes here");*/ + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("Speed"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (CWSliderInt("##MLSpeed",&i.macro->val[11],0,255)) { PARAMETER + if (i.macro->val[11]<0) i.macro->val[11]=0; + if (i.macro->val[11]>255) i.macro->val[11]=255; + } + + ImGui::TableNextColumn(); + ImGui::Text("Phase"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (CWSliderInt("##MLPhase",&i.macro->val[13],0,1023)) { PARAMETER + if (i.macro->val[13]<0) i.macro->val[13]=0; + if (i.macro->val[13]>1023) i.macro->val[13]=1023; + } + + ImGui::TableNextColumn(); + ImGui::Text("Shape"); + ImGui::TableNextColumn(); + ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); + if (CWSliderInt("##MLShape",&i.macro->val[12],0,2,macroLFOShapes[i.macro->val[12]&3])) { PARAMETER + if (i.macro->val[12]<0) i.macro->val[12]=0; + if (i.macro->val[12]>2) i.macro->val[12]=2; + } + + ImGui::EndTable(); + } } } - ImGui::PopStyleVar(); ImGui::PopID(); index++; } @@ -1677,7 +1916,47 @@ void FurnaceGUI::drawInsEdit() { } if (ImGui::Begin("Instrument Editor",&insEditOpen,globalWinFlags|(settings.allowEditDocking?0:ImGuiWindowFlags_NoDocking))) { if (curIns<0 || curIns>=(int)e->song.ins.size()) { + ImGui::SetCursorPosY(ImGui::GetCursorPosY()+(ImGui::GetContentRegionAvail().y-ImGui::GetFrameHeightWithSpacing()*2.0f)*0.5f); + CENTER_TEXT("no instrument selected"); ImGui::Text("no instrument selected"); + if (ImGui::BeginTable("noAssetCenter",3)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch,0.5f); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch,0.5f); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::TableNextColumn(); + + if (e->song.ins.size()>0) { + if (ImGui::BeginCombo("##InsSelect","select one...")) { + String name; + for (size_t i=0; isong.ins.size(); i++) { + name=fmt::sprintf("%.2X: %s##_INSS%d",i,e->song.ins[i]->name,i); + if (ImGui::Selectable(name.c_str(),curIns==(int)i)) { + curIns=i; + wavePreviewInit=true; + } + } + ImGui::EndCombo(); + } + ImGui::SameLine(); + ImGui::TextUnformatted("or"); + ImGui::SameLine(); + } + if (ImGui::Button("Open")) { + doAction(GUI_ACTION_INS_LIST_OPEN); + } + ImGui::SameLine(); + ImGui::TextUnformatted("or"); + ImGui::SameLine(); + if (ImGui::Button("Create New")) { + doAction(GUI_ACTION_INS_LIST_ADD); + } + + ImGui::TableNextColumn(); + ImGui::EndTable(); + } } else { DivInstrument* ins=e->song.ins[curIns]; if (settings.insEditColorize) { @@ -4342,6 +4621,7 @@ void FurnaceGUI::drawInsEdit() { ins->type==DIV_INS_FDS || (ins->type==DIV_INS_SWAN && !ins->amiga.useSample) || (ins->type==DIV_INS_PCE && !ins->amiga.useSample) || + (ins->type==DIV_INS_VBOY) || ins->type==DIV_INS_SCC || ins->type==DIV_INS_SNES || ins->type==DIV_INS_NAMCO) { @@ -4593,7 +4873,7 @@ void FurnaceGUI::drawInsEdit() { } if (ins->type==DIV_INS_TIA || ins->type==DIV_INS_AMIGA || ins->type==DIV_INS_SCC || ins->type==DIV_INS_PET || ins->type==DIV_INS_VIC || ins->type==DIV_INS_SEGAPCM || - ins->type==DIV_INS_FM) { + ins->type==DIV_INS_FM || ins->type==DIV_INS_VBOY) { dutyMax=0; } if (ins->type==DIV_INS_PCE || ins->type==DIV_INS_NAMCO) { @@ -4758,7 +5038,7 @@ void FurnaceGUI::drawInsEdit() { } if (ins->type==DIV_INS_SNES) { ex1Max=5; - ex2Max=5; + ex2Max=255; } if (ins->type==DIV_INS_MSM5232) { ex1Max=5; @@ -4783,7 +5063,9 @@ void FurnaceGUI::drawInsEdit() { panMax=1; panSingle=true; } - if (ins->type==DIV_INS_X1_010 || ins->type==DIV_INS_PCE || ins->type==DIV_INS_MIKEY || ins->type==DIV_INS_SAA1099 || ins->type==DIV_INS_NAMCO || ins->type==DIV_INS_RF5C68) { + if (ins->type==DIV_INS_X1_010 || ins->type==DIV_INS_PCE || ins->type==DIV_INS_MIKEY || + ins->type==DIV_INS_SAA1099 || ins->type==DIV_INS_NAMCO || ins->type==DIV_INS_RF5C68 || + ins->type==DIV_INS_VBOY) { panMax=15; } if (ins->type==DIV_INS_SEGAPCM) { @@ -4934,7 +5216,7 @@ void FurnaceGUI::drawInsEdit() { } else if (ins->type==DIV_INS_QSOUND) { macroList.push_back(FurnaceGUIMacroDesc("Echo Length",&ins->std.ex2Macro,0,ex2Max,160,uiColors[GUI_COLOR_MACRO_OTHER])); } else if (ins->type==DIV_INS_SNES) { - macroList.push_back(FurnaceGUIMacroDesc("Gain Mode",&ins->std.ex2Macro,0,ex2Max,64,uiColors[GUI_COLOR_MACRO_VOLUME],false,NULL,NULL,false,snesGainModes)); + macroList.push_back(FurnaceGUIMacroDesc("Gain",&ins->std.ex2Macro,0,ex2Max,256,uiColors[GUI_COLOR_MACRO_VOLUME],false,NULL,macroHoverGain,false)); } else if (ins->type==DIV_INS_MSM5232) { macroList.push_back(FurnaceGUIMacroDesc("Group Decay",&ins->std.ex2Macro,0,ex2Max,160,uiColors[GUI_COLOR_MACRO_OTHER])); } else { @@ -4976,9 +5258,6 @@ void FurnaceGUI::drawInsEdit() { macroList.push_back(FurnaceGUIMacroDesc("Envelope mode",&ins->std.ex8Macro,0,2,64,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,es5506EnvelopeModes)); macroList.push_back(FurnaceGUIMacroDesc("Control",&ins->std.algMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true,es5506ControlModes)); } - if (ins->type==DIV_INS_SNES) { - macroList.push_back(FurnaceGUIMacroDesc("Gain Rate",&ins->std.ex3Macro,0,127,160,uiColors[GUI_COLOR_MACRO_VOLUME])); - } if (ins->type==DIV_INS_MSM5232) { macroList.push_back(FurnaceGUIMacroDesc("Noise",&ins->std.ex3Macro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true)); } diff --git a/src/gui/pattern.cpp b/src/gui/pattern.cpp index ae194925..385441d4 100644 --- a/src/gui/pattern.cpp +++ b/src/gui/pattern.cpp @@ -403,6 +403,12 @@ void FurnaceGUI::drawPattern() { ImGui::PushStyleColor(ImGuiCol_Header,uiColors[GUI_COLOR_PATTERN_SELECTION]); ImGui::PushStyleColor(ImGuiCol_HeaderHovered,uiColors[GUI_COLOR_PATTERN_SELECTION_HOVER]); ImGui::PushStyleColor(ImGuiCol_HeaderActive,uiColors[GUI_COLOR_PATTERN_SELECTION_ACTIVE]); + if (settings.centerPattern) { + float centerOff=(ImGui::GetContentRegionAvail().x-lastPatternWidth)*0.5; + if (centerOff>0.0f) { + ImGui::SetCursorPosX(ImGui::GetCursorPosX()+centerOff); + } + } if (ImGui::BeginTable("PatternView",displayChans+2,ImGuiTableFlags_BordersInnerV|ImGuiTableFlags_ScrollX|ImGuiTableFlags_ScrollY|ImGuiTableFlags_NoPadInnerX|ImGuiTableFlags_NoBordersInFrozenArea)) { ImGui::TableSetupColumn("pos",ImGuiTableColumnFlags_WidthFixed); char chanID[2048]; @@ -427,6 +433,7 @@ void FurnaceGUI::drawPattern() { } ImGui::TableNextRow(); ImGui::TableNextColumn(); + float lpwStart=ImGui::GetCursorPosX(); if (ImGui::Selectable((extraChannelButtons==2)?" --##ExtraChannelButtons":" ++##ExtraChannelButtons",false,ImGuiSelectableFlags_NoPadWithHalfSpacing,ImVec2(0.0f,lineHeight+1.0f*dpiScale))) { if (++extraChannelButtons>2) extraChannelButtons=0; } @@ -839,6 +846,7 @@ void FurnaceGUI::drawPattern() { } } ImGui::TableNextColumn(); + lastPatternWidth=ImGui::GetCursorPosX()-lpwStart+ImGui::GetStyle().ScrollbarSize; if (e->hasExtValue()) { ImGui::TextColored(uiColors[GUI_COLOR_EE_VALUE]," %.2X",e->getExtValue()); } @@ -1202,7 +1210,7 @@ void FurnaceGUI::drawPattern() { } ImGui::PopStyleVar(); if (patternOpen) { - if (!inhibitMenu && ImGui::IsItemClicked(ImGuiMouseButton_Right)) ImGui::OpenPopup("patternActionMenu"); + if (!inhibitMenu && ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows) && ImGui::IsMouseClicked(ImGuiMouseButton_Right)) ImGui::OpenPopup("patternActionMenu"); if (ImGui::BeginPopup("patternActionMenu",ImGuiWindowFlags_NoMove|ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings)) { editOptions(false); ImGui::EndPopup(); diff --git a/src/gui/sampleEdit.cpp b/src/gui/sampleEdit.cpp index f04060e0..da3109f6 100644 --- a/src/gui/sampleEdit.cpp +++ b/src/gui/sampleEdit.cpp @@ -28,6 +28,9 @@ #include "sampleUtil.h" #include "util.h" +#define CENTER_TEXT(text) \ + ImGui::SetCursorPosX(ImGui::GetCursorPosX()+0.5*(ImGui::GetContentRegionAvail().x-ImGui::CalcTextSize(text).x)); + void FurnaceGUI::drawSampleEdit() { if (nextWindow==GUI_WINDOW_SAMPLE_EDIT) { sampleEditOpen=true; @@ -43,7 +46,40 @@ void FurnaceGUI::drawSampleEdit() { } if (ImGui::Begin("Sample Editor",&sampleEditOpen,globalWinFlags|(settings.allowEditDocking?0:ImGuiWindowFlags_NoDocking))) { if (curSample<0 || curSample>=(int)e->song.sample.size()) { + ImGui::SetCursorPosY(ImGui::GetCursorPosY()+(ImGui::GetContentRegionAvail().y-ImGui::GetFrameHeightWithSpacing()*2.0f)*0.5f); + CENTER_TEXT("no sample selected"); ImGui::Text("no sample selected"); + if (ImGui::BeginTable("noAssetCenter",3)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch,0.5f); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch,0.5f); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::TableNextColumn(); + + if (e->song.sample.size()>0) { + if (ImGui::BeginCombo("##SampleSelect","select one...")) { + actualSampleList(); + ImGui::EndCombo(); + } + ImGui::SameLine(); + ImGui::TextUnformatted("or"); + ImGui::SameLine(); + } + if (ImGui::Button("Open")) { + doAction(GUI_ACTION_SAMPLE_LIST_OPEN); + } + ImGui::SameLine(); + ImGui::TextUnformatted("or"); + ImGui::SameLine(); + if (ImGui::Button("Create New")) { + doAction(GUI_ACTION_SAMPLE_LIST_ADD); + } + + ImGui::TableNextColumn(); + ImGui::EndTable(); + } } else { DivSample* sample=e->song.sample[curSample]; String sampleType="Invalid"; @@ -120,6 +156,9 @@ void FurnaceGUI::drawSampleEdit() { sample->loopEnd=sample->samples; } updateSampleTex=true; + if (e->getSampleFormatMask()&(1U<renderSamplesP(); + } } if (ImGui::IsItemHovered() && sample->depth==DIV_SAMPLE_DEPTH_BRR) { ImGui::SetTooltip("changing the loop in a BRR sample may result in glitches!"); @@ -151,7 +190,7 @@ void FurnaceGUI::drawSampleEdit() { ImGui::Text("Loop Start"); ImGui::SameLine(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::InputInt("##LoopStartPosition",&sample->loopStart,1,10)) { MARK_MODIFIED + if (ImGui::InputInt("##LoopStartPosition",&sample->loopStart,1,16)) { MARK_MODIFIED if (sample->loopStart<0) { sample->loopStart=0; } @@ -159,6 +198,9 @@ void FurnaceGUI::drawSampleEdit() { sample->loopStart=sample->loopEnd; } updateSampleTex=true; + if (e->getSampleFormatMask()&(1U<renderSamplesP(); + } } if (ImGui::IsItemActive()) { keepLoopAlive=true; @@ -170,7 +212,7 @@ void FurnaceGUI::drawSampleEdit() { ImGui::Text("Loop End"); ImGui::SameLine(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::InputInt("##LoopEndPosition",&sample->loopEnd,1,10)) { MARK_MODIFIED + if (ImGui::InputInt("##LoopEndPosition",&sample->loopEnd,1,16)) { MARK_MODIFIED if (sample->loopEndloopStart) { sample->loopEnd=sample->loopStart; } @@ -178,6 +220,9 @@ void FurnaceGUI::drawSampleEdit() { sample->loopEnd=sample->samples; } updateSampleTex=true; + if (e->getSampleFormatMask()&(1U<renderSamplesP(); + } } if (ImGui::IsItemActive()) { keepLoopAlive=true; @@ -696,6 +741,9 @@ void FurnaceGUI::drawSampleEdit() { sample->loopEnd=sample->samples; } updateSampleTex=true; + if (e->getSampleFormatMask()&(1U<renderSamplesP(); + } } if (ImGui::IsItemHovered() && sample->depth==DIV_SAMPLE_DEPTH_BRR) { ImGui::SetTooltip("changing the loop in a BRR sample may result in glitches!"); @@ -728,13 +776,16 @@ void FurnaceGUI::drawSampleEdit() { ImGui::Text("Loop Start"); ImGui::SameLine(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::InputInt("##LoopStartPosition",&sample->loopStart,1,10)) { MARK_MODIFIED + if (ImGui::InputInt("##LoopStartPosition",&sample->loopStart,1,16)) { MARK_MODIFIED if (sample->loopStart<0) { sample->loopStart=0; } if (sample->loopStart>sample->loopEnd) { sample->loopStart=sample->loopEnd; } + if (e->getSampleFormatMask()&(1U<renderSamplesP(); + } updateSampleTex=true; } if (ImGui::IsItemActive()) { @@ -747,13 +798,16 @@ void FurnaceGUI::drawSampleEdit() { ImGui::Text("Loop End"); ImGui::SameLine(); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x); - if (ImGui::InputInt("##LoopEndPosition",&sample->loopEnd,1,10)) { MARK_MODIFIED + if (ImGui::InputInt("##LoopEndPosition",&sample->loopEnd,1,16)) { MARK_MODIFIED if (sample->loopEndloopStart) { sample->loopEnd=sample->loopStart; } if (sample->loopEnd>=(int)sample->samples) { sample->loopEnd=sample->samples; } + if (e->getSampleFormatMask()&(1U<renderSamplesP(); + } updateSampleTex=true; } if (ImGui::IsItemActive()) { diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index e4c40cae..f16f2588 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -1443,6 +1443,11 @@ void FurnaceGUI::drawSettings() { settings.germanNotation=germanNotationB; } + bool centerPatternB=settings.centerPattern; + if (ImGui::Checkbox("Center pattern view",¢erPatternB)) { + settings.centerPattern=centerPatternB; + } + bool unsignedDetuneB=settings.unsignedDetune; if (ImGui::Checkbox("Unsigned FM detune values",&unsignedDetuneB)) { settings.unsignedDetune=unsignedDetuneB; @@ -2363,6 +2368,7 @@ void FurnaceGUI::syncSettings() { settings.maxRecentFile=e->getConfInt("maxRecentFile",10); settings.midiOutClock=e->getConfInt("midiOutClock",0); settings.midiOutMode=e->getConfInt("midiOutMode",1); + settings.centerPattern=e->getConfInt("centerPattern",0); clampSetting(settings.mainFontSize,2,96); clampSetting(settings.patFontSize,2,96); @@ -2466,6 +2472,7 @@ void FurnaceGUI::syncSettings() { clampSetting(settings.maxRecentFile,0,30); clampSetting(settings.midiOutClock,0,1); clampSetting(settings.midiOutMode,0,2); + clampSetting(settings.centerPattern,0,1); String initialSys2=e->getConfString("initialSys2",""); if (initialSys2.empty()) { @@ -2630,6 +2637,7 @@ void FurnaceGUI::commitSettings() { e->setConf("maxRecentFile",settings.maxRecentFile); e->setConf("midiOutClock",settings.midiOutClock); e->setConf("midiOutMode",settings.midiOutMode); + e->setConf("centerPattern",settings.centerPattern); // colors for (int i=0; ilockSave([&]() { flags.set("chipType",chipType); flags.set("noAntiClick",noAntiClick); + flags.set("enoughAlready",enoughAlready); }); } break; @@ -1309,6 +1314,8 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo } case DIV_SYSTEM_MSM5232: { int detune=flags.getInt("detune",0); + int vibSpeed=flags.getInt("vibSpeed",0); + float vibDepth=flags.getFloat("vibDepth",0.0f); bool groupEnv[2]; int groupVol[8]; float capValue[8]; @@ -1388,8 +1395,23 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo altered=true; } + ImGui::Text("Global vibrato:"); + + if (CWSliderInt("Speed",&vibSpeed,0,256)) { + if (vibSpeed<0) vibSpeed=0; + if (vibSpeed>256) vibSpeed=256; + altered=true; + } rightClickable + if (CWSliderFloat("Depth",&vibDepth,0.0f,256.0f)) { + if (vibDepth<0) vibDepth=0; + if (vibDepth>256) vibDepth=256; + altered=true; + } rightClickable + if (altered) { flags.set("detune",detune); + flags.set("vibSpeed",vibSpeed); + flags.set("vibDepth",vibDepth); flags.set("capValue0",capValue[0]); flags.set("capValue1",capValue[1]); diff --git a/src/gui/waveEdit.cpp b/src/gui/waveEdit.cpp index 87f5ad38..b9b8d65b 100644 --- a/src/gui/waveEdit.cpp +++ b/src/gui/waveEdit.cpp @@ -22,6 +22,7 @@ #include "plot_nolerp.h" #include "IconsFontAwesome4.h" #include "misc/cpp/imgui_stdlib.h" +#include #include #include @@ -175,7 +176,40 @@ void FurnaceGUI::drawWaveEdit() { } if (ImGui::Begin("Wavetable Editor",&waveEditOpen,globalWinFlags|(settings.allowEditDocking?0:ImGuiWindowFlags_NoDocking))) { if (curWave<0 || curWave>=(int)e->song.wave.size()) { + ImGui::SetCursorPosY(ImGui::GetCursorPosY()+(ImGui::GetContentRegionAvail().y-ImGui::GetFrameHeightWithSpacing()*2.0f)*0.5f); + CENTER_TEXT("no wavetable selected"); ImGui::Text("no wavetable selected"); + if (ImGui::BeginTable("noAssetCenter",3)) { + ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthStretch,0.5f); + ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthFixed); + ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthStretch,0.5f); + + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::TableNextColumn(); + + if (e->song.wave.size()>0) { + if (ImGui::BeginCombo("##WaveSelect","select one...")) { + actualWaveList(); + ImGui::EndCombo(); + } + ImGui::SameLine(); + ImGui::TextUnformatted("or"); + ImGui::SameLine(); + } + if (ImGui::Button("Open")) { + doAction(GUI_ACTION_WAVE_LIST_OPEN); + } + ImGui::SameLine(); + ImGui::TextUnformatted("or"); + ImGui::SameLine(); + if (ImGui::Button("Create New")) { + doAction(GUI_ACTION_WAVE_LIST_ADD); + } + + ImGui::TableNextColumn(); + ImGui::EndTable(); + } } else { DivWavetable* wave=e->song.wave[curWave]; diff --git a/src/main.cpp b/src/main.cpp index 79229621..aadc8ded 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -171,6 +171,7 @@ TAParamResult pVersion(String) { printf("- MAME SAA1099 emulation core by Juergen Buchmueller and Manuel Abadia (BSD 3-clause)\n"); printf("- MAME Namco WSG by Nicola Salmoria and Aaron Giles (BSD 3-clause)\n"); printf("- MAME RF5C68 core by Olivier Galibert and Aaron Giles (BSD 3-clause)\n"); + printf("- MAME MSM5232 core by Jarek Burczynski and Hiromitsu Shioya (GPLv2)\n"); printf("- MAME MSM6258 core by Barry Rodewald (BSD 3-clause)\n"); printf("- MAME YMZ280B core by Aaron Giles (BSD 3-clause)\n"); printf("- QSound core by superctr (BSD 3-clause)\n"); @@ -179,6 +180,7 @@ TAParamResult pVersion(String) { printf("- SAASound by Dave Hooper and Simon Owen (BSD 3-clause)\n"); printf("- SameBoy by Lior Halphon (MIT)\n"); printf("- Mednafen PCE and WonderSwan by Mednafen Team (GPLv2)\n"); + printf("- SNES DSP core by Blargg (LGPLv2.1)\n"); printf("- puNES by FHorse (GPLv2)\n"); printf("- NSFPlay by Brad Smith and Brezza (unknown open-source license)\n"); printf("- reSID by Dag Lem (GPLv2)\n"); @@ -549,7 +551,7 @@ int main(int argc, char** argv) { #ifdef HAVE_GUI g.bindEngine(&e); if (!g.init()) { - reportError("error while starting GUI!"); + reportError(g.getLastError()); return 1; }