diff --git a/src/engine/cmdStream.cpp b/src/engine/cmdStream.cpp index d8c77030f..40542ef98 100644 --- a/src/engine/cmdStream.cpp +++ b/src/engine/cmdStream.cpp @@ -215,6 +215,10 @@ bool DivCSPlayer::tick() { case DIV_CMD_HINT_VOL_SLIDE: arg0=(short)stream.readS(); break; + case DIV_CMD_HINT_VOL_SLIDE_TARGET: + arg0=(short)stream.readS(); + arg1=(short)stream.readS(); + break; case DIV_CMD_HINT_LEGATO: arg0=(unsigned char)stream.readC(); if (arg0==0xff) { @@ -321,6 +325,11 @@ bool DivCSPlayer::tick() { break; case DIV_CMD_HINT_VOL_SLIDE: chan[i].volSpeed=arg0; + chan[i].volSpeedTarget=-1; + break; + case DIV_CMD_HINT_VOL_SLIDE_TARGET: + chan[i].volSpeed=arg0; + chan[i].volSpeedTarget=arg0==0 ? -1 : arg1; break; case DIV_CMD_HINT_PITCH: chan[i].pitch=arg0; @@ -356,13 +365,29 @@ bool DivCSPlayer::tick() { if (sendVolume || chan[i].volSpeed!=0) { chan[i].volume+=chan[i].volSpeed; + if (chan[i].volSpeedTarget!=-1) { + bool atTarget=false; + if (chan[i].volSpeed>0) { + atTarget=(chan[i].volume>=chan[i].volSpeedTarget); + } else if (chan[i].volSpeed<0) { + atTarget=(chan[i].volume<=chan[i].volSpeedTarget); + } else { + atTarget=true; + chan[i].volSpeedTarget=chan[i].volume; + } + + if (atTarget) { + chan[i].volume=chan[i].volSpeedTarget; + chan[i].volSpeed=0; + chan[i].volSpeedTarget=-1; + } + } if (chan[i].volume<0) { chan[i].volume=0; } if (chan[i].volume>chan[i].volMax) { chan[i].volume=chan[i].volMax; } - e->dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); } diff --git a/src/engine/cmdStream.h b/src/engine/cmdStream.h index 4704b41b9..2d8e6e97e 100644 --- a/src/engine/cmdStream.h +++ b/src/engine/cmdStream.h @@ -34,7 +34,7 @@ struct DivCSChannelState { int lastWaitLen; int note, pitch; - int volume, volMax, volSpeed; + int volume, volMax, volSpeed, volSpeedTarget; int vibratoDepth, vibratoRate, vibratoPos; int portaTarget, portaSpeed; unsigned char arp, arpStage, arpTicks; @@ -56,6 +56,7 @@ struct DivCSChannelState { volume(0x7f00), volMax(0), volSpeed(0), + volSpeedTarget(-1), vibratoDepth(0), vibratoRate(0), vibratoPos(0), diff --git a/src/engine/cmdStreamOps.cpp b/src/engine/cmdStreamOps.cpp index 0710e650e..1f3d81ee2 100644 --- a/src/engine/cmdStreamOps.cpp +++ b/src/engine/cmdStreamOps.cpp @@ -59,6 +59,7 @@ void writePackedCommandValues(SafeWriter* w, const DivCommand& c) { case DIV_CMD_HINT_VOLUME: case DIV_CMD_HINT_PORTA: case DIV_CMD_HINT_VOL_SLIDE: + case DIV_CMD_HINT_VOL_SLIDE_TARGET: case DIV_CMD_HINT_LEGATO: w->writeC((unsigned char)c.cmd+0xb4); break; @@ -100,6 +101,10 @@ void writePackedCommandValues(SafeWriter* w, const DivCommand& c) { case DIV_CMD_HINT_VOL_SLIDE: w->writeS(c.value); break; + case DIV_CMD_HINT_VOL_SLIDE_TARGET: + w->writeS(c.value); + w->writeS(c.value2); + break; case DIV_CMD_SAMPLE_MODE: case DIV_CMD_SAMPLE_FREQ: case DIV_CMD_SAMPLE_BANK: diff --git a/src/engine/dispatch.h b/src/engine/dispatch.h index e89d3d6f7..031beeef9 100644 --- a/src/engine/dispatch.h +++ b/src/engine/dispatch.h @@ -67,6 +67,7 @@ enum DivDispatchCmds { DIV_CMD_HINT_ARPEGGIO, // (note1, note2) DIV_CMD_HINT_VOLUME, // (vol) DIV_CMD_HINT_VOL_SLIDE, // (amount, oneTick) + DIV_CMD_HINT_VOL_SLIDE_TARGET, // (amount, target) DIV_CMD_HINT_PORTA, // (target, speed) DIV_CMD_HINT_LEGATO, // (note) diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 92cc5ab87..34c8d9edb 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -98,6 +98,10 @@ const char* DivEngine::getEffectDesc(unsigned char effect, int chan, bool notNul break; case 0xc0: case 0xc1: case 0xc2: case 0xc3: return _("Cxxx: Set tick rate (hz)"); + case 0xd3: + return _("D3xx: Volume portamento"); + case 0xd4: + return _("D4xx: Volume portamento (fast)"); case 0xdc: return _("DCxx: Delayed mute"); case 0xe0: diff --git a/src/engine/engine.h b/src/engine/engine.h index c29dc59b2..95cf30210 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -133,7 +133,7 @@ struct DivAudioExportOptions { struct DivChannelState { std::vector delayed; int note, oldNote, lastIns, pitch, portaSpeed, portaNote; - int volume, volSpeed, cut, volCut, legatoDelay, legatoTarget, rowDelay, volMax; + int volume, volSpeed, volSpeedTarget, cut, volCut, legatoDelay, legatoTarget, rowDelay, volMax; int delayOrder, delayRow, retrigSpeed, retrigTick; int vibratoDepth, vibratoRate, vibratoPos, vibratoPosGiant, vibratoShape, vibratoFine; int tremoloDepth, tremoloRate, tremoloPos; @@ -157,6 +157,7 @@ struct DivChannelState { portaNote(-1), volume(0x7f00), volSpeed(0), + volSpeedTarget(-1), cut(-1), volCut(-1), legatoDelay(-1), diff --git a/src/engine/instrument.cpp b/src/engine/instrument.cpp index ff34b77fc..94b768121 100644 --- a/src/engine/instrument.cpp +++ b/src/engine/instrument.cpp @@ -409,6 +409,141 @@ void DivInstrument::writeFeatureFM(SafeWriter* w, bool fui) { FEATURE_END; } +bool MemPatch::calcDiff(const void* pre, const void* post, size_t inputSize) { + bool diffValid=false; + size_t firstDiff=0; + size_t lastDiff=0; + const unsigned char* preBytes=(const unsigned char*)pre; + const unsigned char* postBytes=(const unsigned char*)post; + + // @NOTE: consider/profile using a memcmp==0 check to early-out, if it's potentially faster + // for the common case, which is no change + for (size_t ii=0; iitargetSize) { + logW("MemPatch (offset %d, size %d) exceeds target size (%d), can't apply!",offset,size,targetSize); + return; + } + + unsigned char* targetBytes=(unsigned char*)target; + + // swap this->data and its segment on target + for (size_t ii=0; iiname); + } + podPatch.applyAndReverse((DivInstrumentPOD*)target, sizeof(DivInstrumentPOD)); +} + +bool DivInstrumentUndoStep::makeUndoPatch(size_t processTime_, const DivInstrument* pre, const DivInstrument* post) { + processTime=processTime_; + + // create the patch that will make post into pre + podPatch.calcDiff((const DivInstrumentPOD*)post, (const DivInstrumentPOD*)pre, sizeof(DivInstrumentPOD)); + if (pre->name!=post->name) { + nameValid=true; + name=pre->name; + } + + return nameValid || podPatch.isValid(); +} + +bool DivInstrument::recordUndoStepIfChanged(size_t processTime, const DivInstrument* old) { + DivInstrumentUndoStep step; + + // generate a patch to go back to old + if (step.makeUndoPatch(processTime, old, this)) { + + // make room + if (undoHist.size()>=undoHist.capacity()) { + delete undoHist.front(); + undoHist.pop_front(); + } + + // clear redo + while (!redoHist.empty()) { + delete redoHist.back(); + redoHist.pop_back(); + } + + DivInstrumentUndoStep* stepPtr=new DivInstrumentUndoStep; + *stepPtr=step; + step.podPatch.data=NULL; // don't let it delete the data ptr that's been copied! + undoHist.push_back(stepPtr); + + // logI("DivInstrument::undoHist push (%u off, %u size)", stepPtr->podPatch.offset, stepPtr->podPatch.size); + return true; + } + + return false; +} + +int DivInstrument::undo() { + if (undoHist.empty()) return 0; + + DivInstrumentUndoStep* step=undoHist.back(); + undoHist.pop_back(); + // logI("DivInstrument::undo (%u off, %u size)", step->podPatch.offset, step->podPatch.size); + step->applyAndReverse(this); + + // make room + if (redoHist.size()>=redoHist.capacity()) { + DivInstrumentUndoStep* step=redoHist.front(); + delete step; + redoHist.pop_front(); + } + redoHist.push_back(step); + + return 1; +} + +int DivInstrument::redo() { + if (redoHist.empty()) return 0; + + DivInstrumentUndoStep* step = redoHist.back(); + redoHist.pop_back(); + // logI("DivInstrument::redo (%u off, %u size)", step->podPatch.offset, step->podPatch.size); + step->applyAndReverse(this); + + // make room + if (undoHist.size()>=undoHist.capacity()) { + DivInstrumentUndoStep* step=undoHist.front(); + delete step; + undoHist.pop_front(); + } + undoHist.push_back(step); + + return 1; +} + void DivInstrument::writeMacro(SafeWriter* w, const DivInstrumentMacro& m) { if (!m.len) return; @@ -3538,3 +3673,28 @@ bool DivInstrument::saveDMP(const char* path) { w->finish(); return true; } + +DivInstrument::~DivInstrument() { + // free undoHist/redoHist + while (!undoHist.empty()) { + delete undoHist.back(); + undoHist.pop_back(); + } + while (!redoHist.empty()) { + delete redoHist.back(); + redoHist.pop_back(); + } +} + +DivInstrument::DivInstrument( const DivInstrument& ins ) { + // undo/redo history is specifically not copied + *(DivInstrumentPOD*)this=ins; + name=ins.name; +} + +DivInstrument& DivInstrument::operator=( const DivInstrument& ins ) { + // undo/redo history is specifically not copied + *(DivInstrumentPOD*)this=ins; + name=ins.name; + return *this; +} \ No newline at end of file diff --git a/src/engine/instrument.h b/src/engine/instrument.h index d18cbc329..dbd8e4455 100644 --- a/src/engine/instrument.h +++ b/src/engine/instrument.h @@ -23,8 +23,10 @@ #include "dataErrors.h" #include "../ta-utils.h" #include "../pch.h" +#include "../fixedQueue.h" struct DivSong; +struct DivInstrument; // NOTICE! // before adding new instrument types to this struct, please ask me first. @@ -951,7 +953,7 @@ struct DivInstrumentSID3 } }; -struct DivInstrument { +struct DivInstrumentPOD { String name; DivInstrumentType type; DivInstrumentFM fm; @@ -972,6 +974,77 @@ struct DivInstrument { DivInstrumentSID2 sid2; DivInstrumentSID3 sid3; + DivInstrumentPOD() : + type(DIV_INS_FM) { + } +}; + +struct MemPatch { + MemPatch() : + data(NULL) + , offset(0) + , size(0) { + } + + ~MemPatch() { + if (data) { + delete[] data; + data=NULL; + } + } + + bool calcDiff(const void* pre, const void* post, size_t size); + void applyAndReverse(void* target, size_t inputSize); + bool isValid() const { return size>0; } + + unsigned char* data; + size_t offset; + size_t size; +}; + +struct DivInstrumentUndoStep { + DivInstrumentUndoStep() : + name(""), + nameValid(false), + processTime(0) { + } + + MemPatch podPatch; + String name; + bool nameValid; + size_t processTime; + + void applyAndReverse(DivInstrument* target); + bool makeUndoPatch(size_t processTime_, const DivInstrument* pre, const DivInstrument* post); +}; + +struct DivInstrument : DivInstrumentPOD { + String name; + + DivInstrument() : + name("") { + // clear and construct DivInstrumentPOD so it doesn't have any garbage in the padding + memset((unsigned char*)(DivInstrumentPOD*)this, 0, sizeof(DivInstrumentPOD)); + new ((DivInstrumentPOD*)this) DivInstrumentPOD; + } + + ~DivInstrument(); + + /** + * copy/assignment to specifically avoid leaking or dangling pointers to undo step + */ + DivInstrument( const DivInstrument& ins ); + DivInstrument& operator=( const DivInstrument& ins ); + + /** + * undo stuff + */ + FixedQueue undoHist; + FixedQueue redoHist; + bool recordUndoStepIfChanged(size_t processTime, const DivInstrument* old); + int undo(); + int redo(); + /** * these are internal functions. */ @@ -1058,9 +1131,5 @@ struct DivInstrument { * @return whether it was successful. */ bool saveDMP(const char* path); - DivInstrument(): - name(""), - type(DIV_INS_FM) { - } }; #endif diff --git a/src/engine/platform/ay.cpp b/src/engine/platform/ay.cpp index 4e5357722..11ce83389 100644 --- a/src/engine/platform/ay.cpp +++ b/src/engine/platform/ay.cpp @@ -423,11 +423,11 @@ void DivPlatformAY8910::tick(bool sysTick) { chan[i].tfx.counter = 0; chan[i].tfx.out = 0; if (chan[i].nextPSGMode.val&8) { - if (dumpWrites) addWrite(0xffff0002+(i<<8),0); + //if (dumpWrites) addWrite(0xffff0002+(i<<8),0); if (chan[i].dac.sample<0 || chan[i].dac.sample>=parent->song.sampleLen) { if (dumpWrites) { rWrite(0x08+i,0); - addWrite(0xffff0000+(i<<8),chan[i].dac.sample); + //addWrite(0xffff0000+(i<<8),chan[i].dac.sample); } if (chan[i].dac.setPos) { chan[i].dac.setPos=false; @@ -517,7 +517,7 @@ void DivPlatformAY8910::tick(bool sysTick) { } } chan[i].dac.rate=((double)rate*((sunsoft||clockSel)?8.0:16.0))/(double)(MAX(1,off*chan[i].freq)); - if (dumpWrites) addWrite(0xffff0001+(i<<8),chan[i].dac.rate); + //if (dumpWrites) addWrite(0xffff0001+(i<<8),chan[i].dac.rate); } if (chan[i].freq<0) chan[i].freq=0; if (chan[i].freq>4095) chan[i].freq=4095; @@ -604,12 +604,12 @@ int DivPlatformAY8910::dispatch(DivCommand c) { } if (chan[c.chan].dac.sample<0 || chan[c.chan].dac.sample>=parent->song.sampleLen) { chan[c.chan].dac.sample=-1; - if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0); + //if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0); break; } else { if (dumpWrites) { rWrite(0x08+c.chan,0); - addWrite(0xffff0000+(c.chan<<8),chan[c.chan].dac.sample); + //addWrite(0xffff0000+(c.chan<<8),chan[c.chan].dac.sample); } } if (chan[c.chan].dac.setPos) { @@ -637,10 +637,10 @@ int DivPlatformAY8910::dispatch(DivCommand c) { chan[c.chan].dac.sample=12*sampleBank+chan[c.chan].note%12; if (chan[c.chan].dac.sample>=parent->song.sampleLen) { chan[c.chan].dac.sample=-1; - if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0); + //if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0); break; } else { - if (dumpWrites) addWrite(0xffff0000+(c.chan<<8),chan[c.chan].dac.sample); + //if (dumpWrites) addWrite(0xffff0000+(c.chan<<8),chan[c.chan].dac.sample); } if (chan[c.chan].dac.setPos) { chan[c.chan].dac.setPos=false; @@ -651,7 +651,7 @@ int DivPlatformAY8910::dispatch(DivCommand c) { chan[c.chan].dac.rate=parent->getSample(chan[c.chan].dac.sample)->rate*2048; if (dumpWrites) { rWrite(0x08+c.chan,0); - addWrite(0xffff0001+(c.chan<<8),chan[c.chan].dac.rate); + //addWrite(0xffff0001+(c.chan<<8),chan[c.chan].dac.rate); } chan[c.chan].dac.furnaceDAC=false; } @@ -686,7 +686,7 @@ int DivPlatformAY8910::dispatch(DivCommand c) { } case DIV_CMD_NOTE_OFF: chan[c.chan].dac.sample=-1; - if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0); + //if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0); chan[c.chan].nextPSGMode.val&=~8; chan[c.chan].keyOff=true; chan[c.chan].active=false; @@ -867,7 +867,7 @@ int DivPlatformAY8910::dispatch(DivCommand c) { case DIV_CMD_SAMPLE_POS: chan[c.chan].dac.pos=c.value; chan[c.chan].dac.setPos=true; - if (dumpWrites) addWrite(0xffff0005,chan[c.chan].dac.pos); + //if (dumpWrites) addWrite(0xffff0005,chan[c.chan].dac.pos); break; case DIV_CMD_MACRO_OFF: chan[c.chan].std.mask(c.value,true); diff --git a/src/engine/playback.cpp b/src/engine/playback.cpp index 70cac4d6b..0ce2bd699 100644 --- a/src/engine/playback.cpp +++ b/src/engine/playback.cpp @@ -67,6 +67,7 @@ const char* cmdName[]={ "HINT_ARPEGGIO", "HINT_VOLUME", "HINT_VOL_SLIDE", + "HINT_VOL_SLIDE_TARGET", "HINT_PORTA", "HINT_LEGATO", @@ -662,7 +663,23 @@ void DivEngine::processRow(int i, bool afterDelay) { } // volume - if (pat->data[whatRow][3]!=-1) { + int volPortaTarget=-1; + bool noApplyVolume=false; + for (int j=0; jdata[whatRow][4+(j<<1)]; + if (effect==0xd3 || effect==0xd4) { // vol porta + volPortaTarget=pat->data[whatRow][3]<<8; // can be -256 + + short effectVal=pat->data[whatRow][5+(j<<1)]; + if (effectVal==-1) effectVal=0; + effectVal&=255; + + noApplyVolume=effectVal>0; // "D3.." or "D300" shouldn't stop volume from applying + break; // technically you could have both D3 and D4... let's not care + } + } + + if (pat->data[whatRow][3]!=-1 && !noApplyVolume) { if (!song.oldAlwaysSetVolume || disCont[dispatchOfChan[i]].dispatch->getLegacyAlwaysSetVolume() || (MIN(chan[i].volMax,chan[i].volume)>>8)!=pat->data[whatRow][3]) { if (pat->data[whatRow][0]==0 && pat->data[whatRow][1]==0) { chan[i].midiAftertouch=true; @@ -851,6 +868,7 @@ void DivEngine::processRow(int i, bool afterDelay) { } else { chan[i].volSpeed=0; } + chan[i].volSpeedTarget=-1; dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed)); break; case 0x06: // vol slide + porta @@ -892,6 +910,7 @@ void DivEngine::processRow(int i, bool afterDelay) { } else { chan[i].volSpeed=0; } + chan[i].volSpeedTarget=-1; dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed)); break; case 0x07: // tremolo @@ -902,6 +921,7 @@ void DivEngine::processRow(int i, bool afterDelay) { chan[i].tremoloRate=effectVal>>4; if (chan[i].tremoloDepth!=0) { chan[i].volSpeed=0; + chan[i].volSpeedTarget=-1; } else { dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8)); @@ -921,6 +941,7 @@ void DivEngine::processRow(int i, bool afterDelay) { } else { chan[i].volSpeed=0; } + chan[i].volSpeedTarget=-1; dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed)); break; case 0x00: // arpeggio @@ -963,6 +984,22 @@ void DivEngine::processRow(int i, bool afterDelay) { chan[i].cutType=0; } break; + case 0xd3: // volume portamento (vol porta) + // tremolo and vol slides are incompatible + chan[i].tremoloDepth=0; + chan[i].tremoloRate=0; + chan[i].volSpeed=volPortaTarget<0 ? 0 : volPortaTarget>chan[i].volume ? effectVal : -effectVal; + chan[i].volSpeedTarget=chan[i].volSpeed==0 ? -1 : volPortaTarget; + dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE_TARGET,i,chan[i].volSpeed,chan[i].volSpeedTarget)); + break; + case 0xd4: // volume portamento fast (vol porta fast) + // tremolo and vol slides are incompatible + chan[i].tremoloDepth=0; + chan[i].tremoloRate=0; + chan[i].volSpeed=volPortaTarget<0 ? 0 : volPortaTarget>chan[i].volume ? 256*effectVal : -256*effectVal; + chan[i].volSpeedTarget=chan[i].volSpeed==0 ? -1 : volPortaTarget; + dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE_TARGET,i,chan[i].volSpeed,chan[i].volSpeedTarget)); + break; case 0xe0: // arp speed if (effectVal>0) { curSubSong->arpLen=effectVal; @@ -1100,6 +1137,7 @@ void DivEngine::processRow(int i, bool afterDelay) { chan[i].tremoloDepth=0; chan[i].tremoloRate=0; chan[i].volSpeed=effectVal; + chan[i].volSpeedTarget=-1; dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed)); break; case 0xf4: // fine volume ramp down @@ -1107,6 +1145,7 @@ void DivEngine::processRow(int i, bool afterDelay) { chan[i].tremoloDepth=0; chan[i].tremoloRate=0; chan[i].volSpeed=-effectVal; + chan[i].volSpeedTarget=-1; dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed)); break; case 0xf5: // disable macro @@ -1120,12 +1159,14 @@ void DivEngine::processRow(int i, bool afterDelay) { break; case 0xf8: // single volume ramp up chan[i].volSpeed=0; // add compat flag? + chan[i].volSpeedTarget=-1; chan[i].volume=MIN(chan[i].volume+effectVal*256,chan[i].volMax); dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8)); break; case 0xf9: // single volume ramp down chan[i].volSpeed=0; // add compat flag? + chan[i].volSpeedTarget=-1; chan[i].volume=MAX(chan[i].volume-effectVal*256,0); dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8)); @@ -1143,9 +1184,9 @@ void DivEngine::processRow(int i, bool afterDelay) { } else { chan[i].volSpeed=0; } + chan[i].volSpeedTarget=-1; dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed)); break; - case 0xfc: // delayed note release if (song.delayBehavior==2 || effectVal0) { + atTarget=(chan[i].volume>=chan[i].volSpeedTarget); + } else if (chan[i].volSpeed<0) { + atTarget=(chan[i].volume<=chan[i].volSpeedTarget); + } else { + atTarget=true; + chan[i].volSpeedTarget=chan[i].volume; + } + + if (atTarget) { + chan[i].volume=chan[i].volSpeedTarget; + chan[i].volSpeed=0; + chan[i].volSpeedTarget=-1; + dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8)); + dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); + dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,0)); + } + } if (chan[i].volume>chan[i].volMax) { chan[i].volume=chan[i].volMax; chan[i].volSpeed=0; + chan[i].volSpeedTarget=-1; dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8)); dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,0)); @@ -1627,6 +1689,7 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) { } else { chan[i].volume=0; } + chan[i].volSpeedTarget=-1; dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8)); dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8)); } else { diff --git a/src/engine/vgmOps.cpp b/src/engine/vgmOps.cpp index 9529631a1..0050ab6e6 100644 --- a/src/engine/vgmOps.cpp +++ b/src/engine/vgmOps.cpp @@ -2169,8 +2169,8 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p w->writeI(0); w->write(writeADPCM_Y8950[i]->getSampleMem(0),writeADPCM_Y8950[i]->getSampleMemUsage(0)); } - if (writeQSound[i]!=NULL && writeQSound[i]->getSampleMemUsage()>0) { - unsigned int blockSize=(writeQSound[i]->getSampleMemUsage()+0xffff)&(~0xffff); + if (writeQSound[i]!=NULL && writeQSound[i]->getSampleMemUsage(1)>0) { + unsigned int blockSize=(writeQSound[i]->getSampleMemUsage(1)+0xffff)&(~0xffff); if (blockSize > 0x1000000) { blockSize = 0x1000000; } @@ -2178,7 +2178,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p w->writeC(0x66); w->writeC(0x8F); w->writeI((blockSize+8)|(i*0x80000000)); - w->writeI(writeQSound[i]->getSampleMemCapacity()); + w->writeI(writeQSound[i]->getSampleMemCapacity(1)); w->writeI(0); w->write(writeQSound[i]->getSampleMem(),blockSize); } @@ -2679,7 +2679,8 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p w->writeWString(ws,false); // japanese author name w->writeS(0); // date w->writeWString(L"Furnace (chiptune tracker)",false); // ripper - w->writeS(0); // notes + ws=utf8To16(song.notes.c_str()); + w->writeWString(ws,false); // notes int gd3Len=w->tell()-gd3Off-12; diff --git a/src/fixedQueue.h b/src/fixedQueue.h index 68f883edc..f67f70946 100644 --- a/src/fixedQueue.h +++ b/src/fixedQueue.h @@ -42,6 +42,7 @@ template struct FixedQueue { void clear(); bool empty(); size_t size(); + size_t capacity(); FixedQueue(): readPos(0), writePos(0) {} @@ -177,4 +178,8 @@ template size_t FixedQueue::size() { return writePos-readPos; } +template size_t FixedQueue::capacity() { + return items-1; +} + #endif diff --git a/src/gui/about.cpp b/src/gui/about.cpp index 1c407c009..2b2d9c0b3 100644 --- a/src/gui/about.cpp +++ b/src/gui/about.cpp @@ -35,6 +35,7 @@ const char* aboutLine[]={ _N("-- program --"), "tildearrow", _N("A M 4 N (intro tune)"), + "Adam Lederer", "akumanatt", "cam900", "djtuBIG-MaliceX", diff --git a/src/gui/csPlayer.cpp b/src/gui/csPlayer.cpp index 6bbae43ce..7be1f4e1d 100644 --- a/src/gui/csPlayer.cpp +++ b/src/gui/csPlayer.cpp @@ -161,6 +161,8 @@ void FurnaceGUI::drawCSPlayer() { ImGui::TableNextColumn(); ImGui::Text(_("vols")); ImGui::TableNextColumn(); + ImGui::Text(_("volst")); + ImGui::TableNextColumn(); ImGui::Text(_("vib")); ImGui::TableNextColumn(); ImGui::Text(_("porta")); @@ -189,6 +191,8 @@ void FurnaceGUI::drawCSPlayer() { ImGui::TableNextColumn(); ImGui::Text("%+d",state->volSpeed); ImGui::TableNextColumn(); + ImGui::Text("%+d",state->volSpeedTarget); + ImGui::TableNextColumn(); ImGui::Text("%d/%d (%d)",state->vibratoDepth,state->vibratoRate,state->vibratoPos); ImGui::TableNextColumn(); ImGui::Text("-> %d (%d)",state->portaTarget,state->portaSpeed); diff --git a/src/gui/debugWindow.cpp b/src/gui/debugWindow.cpp index b08c60532..b28126fc5 100644 --- a/src/gui/debugWindow.cpp +++ b/src/gui/debugWindow.cpp @@ -145,6 +145,7 @@ void FurnaceGUI::drawDebug() { ImGui::Text("- portaNote = %d",ch->portaNote); ImGui::Text("- volume = %.4x",ch->volume); ImGui::Text("- volSpeed = %d",ch->volSpeed); + ImGui::Text("- volSpeedTarget = %d",ch->volSpeedTarget); ImGui::Text("- cut = %d",ch->cut); ImGui::Text("- rowDelay = %d",ch->rowDelay); ImGui::Text("- volMax = %.4x",ch->volMax); diff --git a/src/gui/doAction.cpp b/src/gui/doAction.cpp index 00bfe95ea..48edf51e2 100644 --- a/src/gui/doAction.cpp +++ b/src/gui/doAction.cpp @@ -73,6 +73,8 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_UNDO: if (curWindow==GUI_WINDOW_SAMPLE_EDIT) { doUndoSample(); + } else if (curWindow==GUI_WINDOW_INS_EDIT) { + doUndoInstrument(); } else { doUndo(); } @@ -80,6 +82,8 @@ void FurnaceGUI::doAction(int what) { case GUI_ACTION_REDO: if (curWindow==GUI_WINDOW_SAMPLE_EDIT) { doRedoSample(); + } else if (curWindow==GUI_WINDOW_INS_EDIT) { + doRedoInstrument(); } else { doRedo(); } @@ -677,30 +681,7 @@ void FurnaceGUI::doAction(int what) { latchNibble=false; break; case GUI_ACTION_PAT_ABSORB_INSTRUMENT: { - DivPattern* pat=e->curPat[cursor.xCoarse].getPattern(e->curOrders->ord[cursor.xCoarse][curOrder],false); - if (!pat) break; - bool foundIns=false; - bool foundOctave=false; - for (int i=cursor.y; i>=0 && !(foundIns && foundOctave); i--) { - // absorb most recent instrument - if (!foundIns && pat->data[i][2] >= 0) { - curIns=pat->data[i][2]; - foundIns=true; - } - // absorb most recent octave (i.e. set curOctave such that the "main row" (QWERTY) of notes - // will result in an octave number equal to the previous note). - if (!foundOctave && pat->data[i][0] != 0) { - // decode octave data (was signed cast to unsigned char) - int octave=pat->data[i][1]; - if (octave>128) octave-=256; - // @NOTE the special handling when note==12, which is really an octave above what's - // stored in the octave data. without this handling, if you press Q, then - // "ABSORB_INSTRUMENT", then Q again, you'd get a different octave! - if (pat->data[i][0]==12) octave++; - curOctave=CLAMP(octave-1, GUI_EDIT_OCTAVE_MIN, GUI_EDIT_OCTAVE_MAX); - foundOctave=true; - } - } + doAbsorbInstrument(); break; } diff --git a/src/gui/editControls.cpp b/src/gui/editControls.cpp index 34beff48b..40269259e 100644 --- a/src/gui/editControls.cpp +++ b/src/gui/editControls.cpp @@ -508,7 +508,7 @@ void FurnaceGUI::drawMobileControls() { mobileMenuOpen=false; doAction(GUI_ACTION_SAVE_AS); } - + ImGui::SameLine(); if (ImGui::Button(_("Export"))) { doAction(GUI_ACTION_EXPORT); } @@ -533,6 +533,10 @@ void FurnaceGUI::drawMobileControls() { drawSpeed(true); ImGui::EndTabItem(); } + if (ImGui::BeginTabItem(_("Comments"))) { + drawNotes(true); + ImGui::EndTabItem(); + } ImGui::EndTabBar(); } break; diff --git a/src/gui/editing.cpp b/src/gui/editing.cpp index 1a4449098..8a49f4ecf 100644 --- a/src/gui/editing.cpp +++ b/src/gui/editing.cpp @@ -1823,6 +1823,52 @@ void FurnaceGUI::doExpandSong(int multiplier) { if (e->isPlaying()) e->play(); } +void FurnaceGUI::doAbsorbInstrument() { + bool foundIns=false; + bool foundOctave=false; + auto foundAll = [&]() { return foundIns && foundOctave; }; + + // search this order and all prior until we find all the data we need + int orderIdx=curOrder; + for (; orderIdx>=0 && !foundAll(); orderIdx--) { + DivPattern* pat=e->curPat[cursor.xCoarse].getPattern(e->curOrders->ord[cursor.xCoarse][orderIdx],false); + if (!pat) continue; + + // start on current row when searching current order, but start from end when searching + // prior orders. + int searchStartRow=orderIdx==curOrder ? cursor.y : e->curSubSong->patLen-1; + for (int i=searchStartRow; i>=0 && !foundAll(); i--) { + + // absorb most recent instrument + if (!foundIns && pat->data[i][2] >= 0) { + foundIns=true; + curIns=pat->data[i][2]; + } + + // absorb most recent octave (i.e. set curOctave such that the "main row" (QWERTY) of + // notes will result in an octave number equal to the previous note). + if (!foundOctave && pat->data[i][0] != 0) { + foundOctave=true; + + // decode octave data (was signed cast to unsigned char) + int octave=pat->data[i][1]; + if (octave>128) octave-=256; + + // @NOTE the special handling when note==12, which is really an octave above what's + // stored in the octave data. without this handling, if you press Q, then + // "ABSORB_INSTRUMENT", then Q again, you'd get a different octave! + if (pat->data[i][0]==12) octave++; + curOctave=CLAMP(octave-1, GUI_EDIT_OCTAVE_MIN, GUI_EDIT_OCTAVE_MAX); + } + } + } + + // if no instrument has been set at this point, the only way to match it is to use "none" + if (!foundIns) curIns=-1; + + logD("doAbsorbInstrument -- searched %d orders", curOrder-orderIdx); +} + void FurnaceGUI::doDrag() { int len=dragEnd.xCoarse-dragStart.xCoarse+1; diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 254065992..608be1e56 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -3739,6 +3739,7 @@ bool FurnaceGUI::loop() { ImGui::GetIO().AddKeyEvent(ImGuiKey_Backspace,false); injectBackUp=false; } + while (SDL_PollEvent(&ev)) { WAKE_UP; ImGui_ImplSDL2_ProcessEvent(&ev); @@ -3755,13 +3756,16 @@ bool FurnaceGUI::loop() { } case SDL_MOUSEBUTTONUP: pointUp(ev.button.x,ev.button.y,ev.button.button); + insEditMayBeDirty=true; break; case SDL_MOUSEBUTTONDOWN: pointDown(ev.button.x,ev.button.y,ev.button.button); + insEditMayBeDirty=true; break; case SDL_MOUSEWHEEL: wheelX+=ev.wheel.x; wheelY+=ev.wheel.y; + insEditMayBeDirty=true; break; case SDL_WINDOWEVENT: switch (ev.window.event) { @@ -3838,12 +3842,14 @@ bool FurnaceGUI::loop() { if (!ImGui::GetIO().WantCaptureKeyboard) { keyDown(ev); } + insEditMayBeDirty=true; #ifdef IS_MOBILE injectBackUp=true; #endif break; case SDL_KEYUP: // for now + insEditMayBeDirty=true; break; case SDL_DROPFILE: if (ev.drop.file!=NULL) { @@ -4482,7 +4488,7 @@ bool FurnaceGUI::loop() { } else { if (ImGui::BeginMenu(_("add chip..."))) { exitDisabledTimer=1; - DivSystem picked=systemPicker(); + DivSystem picked=systemPicker(false); if (picked!=DIV_SYSTEM_NULL) { if (!e->addSystem(picked)) { showError(fmt::sprintf(_("cannot add chip! (%s)"),e->getLastError())); @@ -4513,7 +4519,7 @@ bool FurnaceGUI::loop() { ImGui::Checkbox(_("Preserve channel positions"),&preserveChanPos); for (int i=0; isong.systemLen; i++) { if (ImGui::BeginMenu(fmt::sprintf("%d. %s##_SYSC%d",i+1,getSystemName(e->song.system[i]),i).c_str())) { - DivSystem picked=systemPicker(); + DivSystem picked=systemPicker(false); if (picked!=DIV_SYSTEM_NULL) { if (e->changeSystem(i,picked,preserveChanPos)) { MARK_MODIFIED; @@ -5856,6 +5862,7 @@ bool FurnaceGUI::loop() { centerNextWindow(_("Rendering..."),canvasW,canvasH); if (ImGui::BeginPopupModal(_("Rendering..."),NULL,ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove)) { + WAKE_UP; if(audioExportOptions.mode != DIV_EXPORT_MODE_MANY_CHAN) { ImGui::Text(_("Please wait...")); @@ -5916,7 +5923,7 @@ bool FurnaceGUI::loop() { ImGui::Text(_("Channel %d of %d"), curFile + 1, totalFiles); } - ImGui::ProgressBar(curProgress,ImVec2(-FLT_MIN,0), fmt::sprintf("%.2f%%", curProgress * 100.0f).c_str()); + ImGui::ProgressBar(curProgress,ImVec2(320.0f*dpiScale,0), fmt::sprintf("%.2f%%", curProgress * 100.0f).c_str()); if (ImGui::Button(_("Abort"))) { if (e->haltAudioFile()) { @@ -7241,6 +7248,11 @@ bool FurnaceGUI::loop() { willCommit=false; } + // To check for instrument editor modification, we need an up-to-date `insEditMayBeDirty` + // (based on incoming user input events), and we want any possible instrument modifications + // to already have been made. + checkRecordInstrumentUndoStep(); + if (shallDetectScale) { if (--shallDetectScale<1) { if (settings.dpiScale<0.5f) { @@ -8414,6 +8426,8 @@ FurnaceGUI::FurnaceGUI(): localeRequiresChineseTrad(false), localeRequiresKorean(false), prevInsData(NULL), + cachedCurInsPtr(NULL), + insEditMayBeDirty(false), pendingLayoutImport(NULL), pendingLayoutImportLen(0), pendingLayoutImportStep(0), diff --git a/src/gui/gui.h b/src/gui/gui.h index 29eb4e1a5..f6b2cb9a9 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -2270,6 +2270,9 @@ class FurnaceGUI { std::vector localeExtraRanges; DivInstrument* prevInsData; + DivInstrument cachedCurIns; + DivInstrument* cachedCurInsPtr; + bool insEditMayBeDirty; unsigned char* pendingLayoutImport; size_t pendingLayoutImportLen; @@ -2834,7 +2837,7 @@ class FurnaceGUI { void drawMemory(); void drawCompatFlags(); void drawPiano(); - void drawNotes(); + void drawNotes(bool asChild=false); void drawChannels(); void drawPatManager(); void drawSysManager(); @@ -2924,13 +2927,14 @@ class FurnaceGUI { void doExpand(int multiplier, const SelectionPoint& sStart, const SelectionPoint& sEnd); void doCollapseSong(int divider); void doExpandSong(int multiplier); + void doAbsorbInstrument(); void doUndo(); void doRedo(); void doFind(); void doReplace(); void doDrag(); void editOptions(bool topMenu); - DivSystem systemPicker(); + DivSystem systemPicker(bool fullWidth); void noteInput(int num, int key, int vol=-1); void valueInput(int num, bool direct=false, int target=-1); void orderInput(int num); @@ -2940,6 +2944,10 @@ class FurnaceGUI { void doUndoSample(); void doRedoSample(); + void checkRecordInstrumentUndoStep(); + void doUndoInstrument(); + void doRedoInstrument(); + void play(int row=0); void setOrder(unsigned char order, bool forced=false); void stop(); diff --git a/src/gui/guiConst.cpp b/src/gui/guiConst.cpp index dc5e97ea7..49af3205a 100644 --- a/src/gui/guiConst.cpp +++ b/src/gui/guiConst.cpp @@ -474,8 +474,8 @@ const FurnaceGUIColors fxColors[256]={ GUI_COLOR_PATTERN_EFFECT_INVALID, GUI_COLOR_PATTERN_EFFECT_INVALID, GUI_COLOR_PATTERN_EFFECT_INVALID, - GUI_COLOR_PATTERN_EFFECT_INVALID, - GUI_COLOR_PATTERN_EFFECT_INVALID, + GUI_COLOR_PATTERN_EFFECT_VOLUME, + GUI_COLOR_PATTERN_EFFECT_VOLUME, GUI_COLOR_PATTERN_EFFECT_INVALID, GUI_COLOR_PATTERN_EFFECT_INVALID, GUI_COLOR_PATTERN_EFFECT_INVALID, diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index 4082670a0..fbec0da97 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -6438,6 +6438,7 @@ void FurnaceGUI::drawInsEdit() { ImGui::SetNextWindowSizeConstraints(ImVec2(440.0f*dpiScale,400.0f*dpiScale),ImVec2(canvasW,canvasH)); } if (ImGui::Begin("Instrument Editor",&insEditOpen,globalWinFlags|(settings.allowEditDocking?0:ImGuiWindowFlags_NoDocking),_("Instrument Editor"))) { + DivInstrument* ins=NULL; if (curIns==-2) { ImGui::SetCursorPosY(ImGui::GetCursorPosY()+(ImGui::GetContentRegionAvail().y-ImGui::GetFrameHeightWithSpacing()+ImGui::GetStyle().ItemSpacing.y)*0.5f); CENTER_TEXT(_("waiting...")); @@ -6465,6 +6466,7 @@ void FurnaceGUI::drawInsEdit() { curIns=i; wavePreviewInit=true; updateFMPreview=true; + ins = e->song.ins[curIns]; } } ImGui::EndCombo(); @@ -6487,7 +6489,7 @@ void FurnaceGUI::drawInsEdit() { ImGui::EndTable(); } } else { - DivInstrument* ins=e->song.ins[curIns]; + ins=e->song.ins[curIns]; if (updateFMPreview) { renderFMPreview(ins); updateFMPreview=false; @@ -8146,6 +8148,8 @@ void FurnaceGUI::drawInsEdit() { macroList.push_back(FurnaceGUIMacroDesc(_("Noise"),&ins->std.dutyMacro,0,8,160,uiColors[GUI_COLOR_MACRO_NOISE])); } macroList.push_back(FurnaceGUIMacroDesc(_("Waveform"),&ins->std.waveMacro,0,waveCount,160,uiColors[GUI_COLOR_MACRO_WAVE],false,NULL,NULL,false,NULL)); + macroList.push_back(FurnaceGUIMacroDesc(_("Panning (left)"),&ins->std.panLMacro,0,15,46,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL)); + macroList.push_back(FurnaceGUIMacroDesc(_("Panning (right)"),&ins->std.panRMacro,0,15,46,uiColors[GUI_COLOR_MACRO_OTHER])); macroList.push_back(FurnaceGUIMacroDesc(_("Pitch"),&ins->std.pitchMacro,-2048,2047,160,uiColors[GUI_COLOR_MACRO_PITCH],true,macroRelativeMode)); macroList.push_back(FurnaceGUIMacroDesc(_("Phase Reset"),&ins->std.phaseResetMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true)); break; @@ -8703,6 +8707,63 @@ void FurnaceGUI::drawInsEdit() { ImGui::EndPopup(); } } + if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_INS_EDIT; ImGui::End(); } + +void FurnaceGUI::checkRecordInstrumentUndoStep() { + if (insEditOpen && curIns>=0 && curIns<(int)e->song.ins.size()) { + DivInstrument* ins=e->song.ins[curIns]; + + // invalidate cachedCurIns/any possible changes if the cachedCurIns was referencing a different + // instrument altgoether + bool insChanged=ins!=cachedCurInsPtr; + if (insChanged) { + insEditMayBeDirty=false; + cachedCurInsPtr=ins; + cachedCurIns=*ins; + } + + cachedCurInsPtr=ins; + + // check against the last cached to see if diff -- note that modifications to instruments + // happen outside drawInsEdit (e.g. cursor inputs are processed and can directly modify + // macro data). but don't check until we think the user input is complete. + bool delayDiff=ImGui::IsMouseDown(ImGuiMouseButton_Left) || ImGui::IsMouseDown(ImGuiMouseButton_Right) || ImGui::GetIO().WantCaptureKeyboard; + if (!delayDiff && insEditMayBeDirty) { + bool hasChange=ins->recordUndoStepIfChanged(e->processTime, &cachedCurIns); + if (hasChange) { + cachedCurIns=*ins; + } + insEditMayBeDirty=false; + } + } else { + cachedCurInsPtr=NULL; + insEditMayBeDirty=false; + } +} + +void FurnaceGUI::doUndoInstrument() { + if (!insEditOpen) return; + if (curIns<0 || curIns>=(int)e->song.ins.size()) return; + DivInstrument* ins=e->song.ins[curIns]; + // is locking the engine necessary? copied from doUndoSample + e->lockEngine([this,ins]() { + ins->undo(); + cachedCurInsPtr=ins; + cachedCurIns=*ins; + }); +} + +void FurnaceGUI::doRedoInstrument() { + if (!insEditOpen) return; + if (curIns<0 || curIns>=(int)e->song.ins.size()) return; + DivInstrument* ins=e->song.ins[curIns]; + // is locking the engine necessary? copied from doRedoSample + e->lockEngine([this,ins]() { + ins->redo(); + cachedCurInsPtr=ins; + cachedCurIns=*ins; + }); +} diff --git a/src/gui/pattern.cpp b/src/gui/pattern.cpp index 4111a4d83..b2c4ee9f3 100644 --- a/src/gui/pattern.cpp +++ b/src/gui/pattern.cpp @@ -1507,6 +1507,7 @@ void FurnaceGUI::drawPattern() { i.cmd==DIV_CMD_HINT_PORTA || i.cmd==DIV_CMD_HINT_LEGATO || i.cmd==DIV_CMD_HINT_VOL_SLIDE || + i.cmd==DIV_CMD_HINT_VOL_SLIDE_TARGET || i.cmd==DIV_CMD_HINT_ARPEGGIO || i.cmd==DIV_CMD_HINT_PITCH || i.cmd==DIV_CMD_HINT_VIBRATO || diff --git a/src/gui/settings.cpp b/src/gui/settings.cpp index ab3523bc8..33151945f 100644 --- a/src/gui/settings.cpp +++ b/src/gui/settings.cpp @@ -1084,15 +1084,19 @@ void FurnaceGUI::drawSettings() { ImGui::PushID(i); ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x-ImGui::CalcTextSize(_("Invert")).x-ImGui::GetFrameHeightWithSpacing()*2.0-ImGui::GetStyle().ItemSpacing.x*2.0); - if (ImGui::BeginCombo("##System",getSystemName(sysID))) { - for (int j=0; availableSystems[j]; j++) { - if (ImGui::Selectable(getSystemName((DivSystem)availableSystems[j]),sysID==availableSystems[j])) { - sysID=(DivSystem)availableSystems[j]; - settings.initialSys.set(fmt::sprintf("id%d",i),(int)e->systemToFileFur(sysID)); - settings.initialSys.set(fmt::sprintf("flags%d",i),""); - settingsChanged=true; - } + if (ImGui::BeginCombo("##System",getSystemName(sysID),ImGuiComboFlags_HeightLargest)) { + + sysID=systemPicker(true); + + if (sysID!=DIV_SYSTEM_NULL) + { + settings.initialSys.set(fmt::sprintf("id%d",i),(int)e->systemToFileFur(sysID)); + settings.initialSys.set(fmt::sprintf("flags%d",i),""); + settingsChanged=true; + + ImGui::CloseCurrentPopup(); } + ImGui::EndCombo(); } diff --git a/src/gui/songNotes.cpp b/src/gui/songNotes.cpp index cf55b7aef..15ecb2a82 100644 --- a/src/gui/songNotes.cpp +++ b/src/gui/songNotes.cpp @@ -22,18 +22,23 @@ // NOTE: please don't ask me to enable text wrap. // Dear ImGui doesn't have that feature. D: -void FurnaceGUI::drawNotes() { +void FurnaceGUI::drawNotes(bool asChild) { if (nextWindow==GUI_WINDOW_NOTES) { notesOpen=true; ImGui::SetNextWindowFocus(); nextWindow=GUI_WINDOW_NOTHING; } - if (!notesOpen) return; - if (ImGui::Begin("Song Comments",¬esOpen,globalWinFlags,_("Song Comments"))) { + if (!notesOpen && !asChild) return; + bool began=asChild?ImGui::BeginChild("Song Info##Song Information"):ImGui::Begin("Song Comments",¬esOpen,globalWinFlags,_("Song Comments")); + if (began) { if (ImGui::InputTextMultiline("##SongNotes",&e->song.notes,ImGui::GetContentRegionAvail(),ImGuiInputTextFlags_UndoRedo)) { MARK_MODIFIED; } } - if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_NOTES; - ImGui::End(); + if (!asChild && ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_NOTES; + if (asChild) { + ImGui::EndChild(); + } else { + ImGui::End(); + } } diff --git a/src/gui/sysManager.cpp b/src/gui/sysManager.cpp index b4e08fc36..c118c1d77 100644 --- a/src/gui/sysManager.cpp +++ b/src/gui/sysManager.cpp @@ -102,7 +102,7 @@ void FurnaceGUI::drawSysManager() { ImGui::SameLine(); ImGui::Button(_("Change##SysChange")); if (ImGui::BeginPopupContextItem("SysPickerC",ImGuiPopupFlags_MouseButtonLeft)) { - DivSystem picked=systemPicker(); + DivSystem picked=systemPicker(false); if (picked!=DIV_SYSTEM_NULL) { if (e->changeSystem(i,picked,preserveChanPos)) { MARK_MODIFIED; @@ -138,7 +138,7 @@ void FurnaceGUI::drawSysManager() { ImGui::TableNextColumn(); ImGui::Button(ICON_FA_PLUS "##SysAdd"); if (ImGui::BeginPopupContextItem("SysPickerA",ImGuiPopupFlags_MouseButtonLeft)) { - DivSystem picked=systemPicker(); + DivSystem picked=systemPicker(false); if (picked!=DIV_SYSTEM_NULL) { if (!e->addSystem(picked)) { showError(fmt::sprintf(_("cannot add chip! (%s)"),e->getLastError())); diff --git a/src/gui/sysPicker.cpp b/src/gui/sysPicker.cpp index 3eb7f5015..6d10ac754 100644 --- a/src/gui/sysPicker.cpp +++ b/src/gui/sysPicker.cpp @@ -23,7 +23,7 @@ #include "guiConst.h" #include -DivSystem FurnaceGUI::systemPicker() { +DivSystem FurnaceGUI::systemPicker(bool fullWidth) { DivSystem ret=DIV_SYSTEM_NULL; DivSystem hoveredSys=DIV_SYSTEM_NULL; bool reissueSearch=false; @@ -61,7 +61,7 @@ DivSystem FurnaceGUI::systemPicker() { } } } - if (ImGui::BeginTable("SysList",1,ImGuiTableFlags_ScrollY,ImVec2(500.0f*dpiScale,200.0*dpiScale))) { + if (ImGui::BeginTable("SysList",1,ImGuiTableFlags_ScrollY,ImVec2(fullWidth ? ImGui::GetContentRegionAvail().x : 500.0f*dpiScale,200.0f*dpiScale))) { if (sysSearchQuery.empty()) { // display chip list for (int j=0; curSysSection[j]; j++) { diff --git a/src/gui/userPresets.cpp b/src/gui/userPresets.cpp index 14e6f9d7b..d2999c6cc 100644 --- a/src/gui/userPresets.cpp +++ b/src/gui/userPresets.cpp @@ -392,7 +392,7 @@ void FurnaceGUI::drawUserPresets() { tempID=fmt::sprintf("%s##USystem",getSystemName(chip.sys)); ImGui::Button(tempID.c_str(),ImVec2(ImGui::GetContentRegionAvail().x-ImGui::CalcTextSize(_("Invert")).x-ImGui::GetFrameHeightWithSpacing()*2.0-ImGui::GetStyle().ItemSpacing.x*2.0,0)); if (ImGui::BeginPopupContextItem("SysPickerCU",ImGuiPopupFlags_MouseButtonLeft)) { - DivSystem picked=systemPicker(); + DivSystem picked=systemPicker(false); if (picked!=DIV_SYSTEM_NULL) { chip.sys=picked; mustBake=true; @@ -456,7 +456,7 @@ void FurnaceGUI::drawUserPresets() { ImGui::Button(ICON_FA_PLUS "##SysAddU"); if (ImGui::BeginPopupContextItem("SysPickerU",ImGuiPopupFlags_MouseButtonLeft)) { - DivSystem picked=systemPicker(); + DivSystem picked=systemPicker(false); if (picked!=DIV_SYSTEM_NULL) { preset->orig.push_back(FurnaceGUISysDefChip(picked,1.0f,0.0f,"")); mustBake=true;