mirror of
https://github.com/tildearrow/furnace.git
synced 2024-12-01 16:57:27 +00:00
Merge branch 'master' of https://github.com/tildearrow/furnace into SID3
This commit is contained in:
commit
12bd2d3829
28 changed files with 541 additions and 76 deletions
|
@ -215,6 +215,10 @@ bool DivCSPlayer::tick() {
|
||||||
case DIV_CMD_HINT_VOL_SLIDE:
|
case DIV_CMD_HINT_VOL_SLIDE:
|
||||||
arg0=(short)stream.readS();
|
arg0=(short)stream.readS();
|
||||||
break;
|
break;
|
||||||
|
case DIV_CMD_HINT_VOL_SLIDE_TARGET:
|
||||||
|
arg0=(short)stream.readS();
|
||||||
|
arg1=(short)stream.readS();
|
||||||
|
break;
|
||||||
case DIV_CMD_HINT_LEGATO:
|
case DIV_CMD_HINT_LEGATO:
|
||||||
arg0=(unsigned char)stream.readC();
|
arg0=(unsigned char)stream.readC();
|
||||||
if (arg0==0xff) {
|
if (arg0==0xff) {
|
||||||
|
@ -321,6 +325,11 @@ bool DivCSPlayer::tick() {
|
||||||
break;
|
break;
|
||||||
case DIV_CMD_HINT_VOL_SLIDE:
|
case DIV_CMD_HINT_VOL_SLIDE:
|
||||||
chan[i].volSpeed=arg0;
|
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;
|
break;
|
||||||
case DIV_CMD_HINT_PITCH:
|
case DIV_CMD_HINT_PITCH:
|
||||||
chan[i].pitch=arg0;
|
chan[i].pitch=arg0;
|
||||||
|
@ -356,13 +365,29 @@ bool DivCSPlayer::tick() {
|
||||||
|
|
||||||
if (sendVolume || chan[i].volSpeed!=0) {
|
if (sendVolume || chan[i].volSpeed!=0) {
|
||||||
chan[i].volume+=chan[i].volSpeed;
|
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) {
|
if (chan[i].volume<0) {
|
||||||
chan[i].volume=0;
|
chan[i].volume=0;
|
||||||
}
|
}
|
||||||
if (chan[i].volume>chan[i].volMax) {
|
if (chan[i].volume>chan[i].volMax) {
|
||||||
chan[i].volume=chan[i].volMax;
|
chan[i].volume=chan[i].volMax;
|
||||||
}
|
}
|
||||||
|
|
||||||
e->dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8));
|
e->dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ struct DivCSChannelState {
|
||||||
int lastWaitLen;
|
int lastWaitLen;
|
||||||
|
|
||||||
int note, pitch;
|
int note, pitch;
|
||||||
int volume, volMax, volSpeed;
|
int volume, volMax, volSpeed, volSpeedTarget;
|
||||||
int vibratoDepth, vibratoRate, vibratoPos;
|
int vibratoDepth, vibratoRate, vibratoPos;
|
||||||
int portaTarget, portaSpeed;
|
int portaTarget, portaSpeed;
|
||||||
unsigned char arp, arpStage, arpTicks;
|
unsigned char arp, arpStage, arpTicks;
|
||||||
|
@ -56,6 +56,7 @@ struct DivCSChannelState {
|
||||||
volume(0x7f00),
|
volume(0x7f00),
|
||||||
volMax(0),
|
volMax(0),
|
||||||
volSpeed(0),
|
volSpeed(0),
|
||||||
|
volSpeedTarget(-1),
|
||||||
vibratoDepth(0),
|
vibratoDepth(0),
|
||||||
vibratoRate(0),
|
vibratoRate(0),
|
||||||
vibratoPos(0),
|
vibratoPos(0),
|
||||||
|
|
|
@ -59,6 +59,7 @@ void writePackedCommandValues(SafeWriter* w, const DivCommand& c) {
|
||||||
case DIV_CMD_HINT_VOLUME:
|
case DIV_CMD_HINT_VOLUME:
|
||||||
case DIV_CMD_HINT_PORTA:
|
case DIV_CMD_HINT_PORTA:
|
||||||
case DIV_CMD_HINT_VOL_SLIDE:
|
case DIV_CMD_HINT_VOL_SLIDE:
|
||||||
|
case DIV_CMD_HINT_VOL_SLIDE_TARGET:
|
||||||
case DIV_CMD_HINT_LEGATO:
|
case DIV_CMD_HINT_LEGATO:
|
||||||
w->writeC((unsigned char)c.cmd+0xb4);
|
w->writeC((unsigned char)c.cmd+0xb4);
|
||||||
break;
|
break;
|
||||||
|
@ -100,6 +101,10 @@ void writePackedCommandValues(SafeWriter* w, const DivCommand& c) {
|
||||||
case DIV_CMD_HINT_VOL_SLIDE:
|
case DIV_CMD_HINT_VOL_SLIDE:
|
||||||
w->writeS(c.value);
|
w->writeS(c.value);
|
||||||
break;
|
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_MODE:
|
||||||
case DIV_CMD_SAMPLE_FREQ:
|
case DIV_CMD_SAMPLE_FREQ:
|
||||||
case DIV_CMD_SAMPLE_BANK:
|
case DIV_CMD_SAMPLE_BANK:
|
||||||
|
|
|
@ -67,6 +67,7 @@ enum DivDispatchCmds {
|
||||||
DIV_CMD_HINT_ARPEGGIO, // (note1, note2)
|
DIV_CMD_HINT_ARPEGGIO, // (note1, note2)
|
||||||
DIV_CMD_HINT_VOLUME, // (vol)
|
DIV_CMD_HINT_VOLUME, // (vol)
|
||||||
DIV_CMD_HINT_VOL_SLIDE, // (amount, oneTick)
|
DIV_CMD_HINT_VOL_SLIDE, // (amount, oneTick)
|
||||||
|
DIV_CMD_HINT_VOL_SLIDE_TARGET, // (amount, target)
|
||||||
DIV_CMD_HINT_PORTA, // (target, speed)
|
DIV_CMD_HINT_PORTA, // (target, speed)
|
||||||
DIV_CMD_HINT_LEGATO, // (note)
|
DIV_CMD_HINT_LEGATO, // (note)
|
||||||
|
|
||||||
|
|
|
@ -98,6 +98,10 @@ const char* DivEngine::getEffectDesc(unsigned char effect, int chan, bool notNul
|
||||||
break;
|
break;
|
||||||
case 0xc0: case 0xc1: case 0xc2: case 0xc3:
|
case 0xc0: case 0xc1: case 0xc2: case 0xc3:
|
||||||
return _("Cxxx: Set tick rate (hz)");
|
return _("Cxxx: Set tick rate (hz)");
|
||||||
|
case 0xd3:
|
||||||
|
return _("D3xx: Volume portamento");
|
||||||
|
case 0xd4:
|
||||||
|
return _("D4xx: Volume portamento (fast)");
|
||||||
case 0xdc:
|
case 0xdc:
|
||||||
return _("DCxx: Delayed mute");
|
return _("DCxx: Delayed mute");
|
||||||
case 0xe0:
|
case 0xe0:
|
||||||
|
|
|
@ -133,7 +133,7 @@ struct DivAudioExportOptions {
|
||||||
struct DivChannelState {
|
struct DivChannelState {
|
||||||
std::vector<DivDelayedCommand> delayed;
|
std::vector<DivDelayedCommand> delayed;
|
||||||
int note, oldNote, lastIns, pitch, portaSpeed, portaNote;
|
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 delayOrder, delayRow, retrigSpeed, retrigTick;
|
||||||
int vibratoDepth, vibratoRate, vibratoPos, vibratoPosGiant, vibratoShape, vibratoFine;
|
int vibratoDepth, vibratoRate, vibratoPos, vibratoPosGiant, vibratoShape, vibratoFine;
|
||||||
int tremoloDepth, tremoloRate, tremoloPos;
|
int tremoloDepth, tremoloRate, tremoloPos;
|
||||||
|
@ -157,6 +157,7 @@ struct DivChannelState {
|
||||||
portaNote(-1),
|
portaNote(-1),
|
||||||
volume(0x7f00),
|
volume(0x7f00),
|
||||||
volSpeed(0),
|
volSpeed(0),
|
||||||
|
volSpeedTarget(-1),
|
||||||
cut(-1),
|
cut(-1),
|
||||||
volCut(-1),
|
volCut(-1),
|
||||||
legatoDelay(-1),
|
legatoDelay(-1),
|
||||||
|
|
|
@ -409,6 +409,141 @@ void DivInstrument::writeFeatureFM(SafeWriter* w, bool fui) {
|
||||||
FEATURE_END;
|
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; ii<inputSize; ++ii) {
|
||||||
|
if (preBytes[ii] != postBytes[ii]) {
|
||||||
|
lastDiff=ii;
|
||||||
|
firstDiff=diffValid ? firstDiff : ii;
|
||||||
|
diffValid=true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (diffValid) {
|
||||||
|
offset=firstDiff;
|
||||||
|
size=lastDiff - firstDiff + 1;
|
||||||
|
data=new unsigned char[size];
|
||||||
|
|
||||||
|
// the diff is to make pre into post (MemPatch is general, not specific to
|
||||||
|
// undo), so copy from postBytes
|
||||||
|
memcpy(data, postBytes+offset, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
return diffValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MemPatch::applyAndReverse(void* target, size_t targetSize) {
|
||||||
|
if (size==0) return;
|
||||||
|
if (offset+size>targetSize) {
|
||||||
|
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; ii<size; ++ii) {
|
||||||
|
unsigned char tmp=targetBytes[offset+ii];
|
||||||
|
targetBytes[offset+ii] = data[ii];
|
||||||
|
data[ii] = tmp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DivInstrumentUndoStep::applyAndReverse(DivInstrument* target) {
|
||||||
|
if (nameValid) {
|
||||||
|
name.swap(target->name);
|
||||||
|
}
|
||||||
|
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) {
|
void DivInstrument::writeMacro(SafeWriter* w, const DivInstrumentMacro& m) {
|
||||||
if (!m.len) return;
|
if (!m.len) return;
|
||||||
|
|
||||||
|
@ -3538,3 +3673,28 @@ bool DivInstrument::saveDMP(const char* path) {
|
||||||
w->finish();
|
w->finish();
|
||||||
return true;
|
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;
|
||||||
|
}
|
|
@ -23,8 +23,10 @@
|
||||||
#include "dataErrors.h"
|
#include "dataErrors.h"
|
||||||
#include "../ta-utils.h"
|
#include "../ta-utils.h"
|
||||||
#include "../pch.h"
|
#include "../pch.h"
|
||||||
|
#include "../fixedQueue.h"
|
||||||
|
|
||||||
struct DivSong;
|
struct DivSong;
|
||||||
|
struct DivInstrument;
|
||||||
|
|
||||||
// NOTICE!
|
// NOTICE!
|
||||||
// before adding new instrument types to this struct, please ask me first.
|
// before adding new instrument types to this struct, please ask me first.
|
||||||
|
@ -951,7 +953,7 @@ struct DivInstrumentSID3
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DivInstrument {
|
struct DivInstrumentPOD {
|
||||||
String name;
|
String name;
|
||||||
DivInstrumentType type;
|
DivInstrumentType type;
|
||||||
DivInstrumentFM fm;
|
DivInstrumentFM fm;
|
||||||
|
@ -972,6 +974,77 @@ struct DivInstrument {
|
||||||
DivInstrumentSID2 sid2;
|
DivInstrumentSID2 sid2;
|
||||||
DivInstrumentSID3 sid3;
|
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<DivInstrumentUndoStep*, 128> undoHist;
|
||||||
|
FixedQueue<DivInstrumentUndoStep*, 128> redoHist;
|
||||||
|
bool recordUndoStepIfChanged(size_t processTime, const DivInstrument* old);
|
||||||
|
int undo();
|
||||||
|
int redo();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* these are internal functions.
|
* these are internal functions.
|
||||||
*/
|
*/
|
||||||
|
@ -1058,9 +1131,5 @@ struct DivInstrument {
|
||||||
* @return whether it was successful.
|
* @return whether it was successful.
|
||||||
*/
|
*/
|
||||||
bool saveDMP(const char* path);
|
bool saveDMP(const char* path);
|
||||||
DivInstrument():
|
|
||||||
name(""),
|
|
||||||
type(DIV_INS_FM) {
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -423,11 +423,11 @@ void DivPlatformAY8910::tick(bool sysTick) {
|
||||||
chan[i].tfx.counter = 0;
|
chan[i].tfx.counter = 0;
|
||||||
chan[i].tfx.out = 0;
|
chan[i].tfx.out = 0;
|
||||||
if (chan[i].nextPSGMode.val&8) {
|
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 (chan[i].dac.sample<0 || chan[i].dac.sample>=parent->song.sampleLen) {
|
||||||
if (dumpWrites) {
|
if (dumpWrites) {
|
||||||
rWrite(0x08+i,0);
|
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) {
|
if (chan[i].dac.setPos) {
|
||||||
chan[i].dac.setPos=false;
|
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));
|
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<0) chan[i].freq=0;
|
||||||
if (chan[i].freq>4095) chan[i].freq=4095;
|
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) {
|
if (chan[c.chan].dac.sample<0 || chan[c.chan].dac.sample>=parent->song.sampleLen) {
|
||||||
chan[c.chan].dac.sample=-1;
|
chan[c.chan].dac.sample=-1;
|
||||||
if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0);
|
//if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0);
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
if (dumpWrites) {
|
if (dumpWrites) {
|
||||||
rWrite(0x08+c.chan,0);
|
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) {
|
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;
|
chan[c.chan].dac.sample=12*sampleBank+chan[c.chan].note%12;
|
||||||
if (chan[c.chan].dac.sample>=parent->song.sampleLen) {
|
if (chan[c.chan].dac.sample>=parent->song.sampleLen) {
|
||||||
chan[c.chan].dac.sample=-1;
|
chan[c.chan].dac.sample=-1;
|
||||||
if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0);
|
//if (dumpWrites) addWrite(0xffff0002+(c.chan<<8),0);
|
||||||
break;
|
break;
|
||||||
} else {
|
} 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) {
|
if (chan[c.chan].dac.setPos) {
|
||||||
chan[c.chan].dac.setPos=false;
|
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;
|
chan[c.chan].dac.rate=parent->getSample(chan[c.chan].dac.sample)->rate*2048;
|
||||||
if (dumpWrites) {
|
if (dumpWrites) {
|
||||||
rWrite(0x08+c.chan,0);
|
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;
|
chan[c.chan].dac.furnaceDAC=false;
|
||||||
}
|
}
|
||||||
|
@ -686,7 +686,7 @@ int DivPlatformAY8910::dispatch(DivCommand c) {
|
||||||
}
|
}
|
||||||
case DIV_CMD_NOTE_OFF:
|
case DIV_CMD_NOTE_OFF:
|
||||||
chan[c.chan].dac.sample=-1;
|
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].nextPSGMode.val&=~8;
|
||||||
chan[c.chan].keyOff=true;
|
chan[c.chan].keyOff=true;
|
||||||
chan[c.chan].active=false;
|
chan[c.chan].active=false;
|
||||||
|
@ -867,7 +867,7 @@ int DivPlatformAY8910::dispatch(DivCommand c) {
|
||||||
case DIV_CMD_SAMPLE_POS:
|
case DIV_CMD_SAMPLE_POS:
|
||||||
chan[c.chan].dac.pos=c.value;
|
chan[c.chan].dac.pos=c.value;
|
||||||
chan[c.chan].dac.setPos=true;
|
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;
|
break;
|
||||||
case DIV_CMD_MACRO_OFF:
|
case DIV_CMD_MACRO_OFF:
|
||||||
chan[c.chan].std.mask(c.value,true);
|
chan[c.chan].std.mask(c.value,true);
|
||||||
|
|
|
@ -67,6 +67,7 @@ const char* cmdName[]={
|
||||||
"HINT_ARPEGGIO",
|
"HINT_ARPEGGIO",
|
||||||
"HINT_VOLUME",
|
"HINT_VOLUME",
|
||||||
"HINT_VOL_SLIDE",
|
"HINT_VOL_SLIDE",
|
||||||
|
"HINT_VOL_SLIDE_TARGET",
|
||||||
"HINT_PORTA",
|
"HINT_PORTA",
|
||||||
"HINT_LEGATO",
|
"HINT_LEGATO",
|
||||||
|
|
||||||
|
@ -662,7 +663,23 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// volume
|
// volume
|
||||||
if (pat->data[whatRow][3]!=-1) {
|
int volPortaTarget=-1;
|
||||||
|
bool noApplyVolume=false;
|
||||||
|
for (int j=0; j<curPat[i].effectCols; j++) {
|
||||||
|
short effect=pat->data[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 (!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) {
|
if (pat->data[whatRow][0]==0 && pat->data[whatRow][1]==0) {
|
||||||
chan[i].midiAftertouch=true;
|
chan[i].midiAftertouch=true;
|
||||||
|
@ -851,6 +868,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
||||||
} else {
|
} else {
|
||||||
chan[i].volSpeed=0;
|
chan[i].volSpeed=0;
|
||||||
}
|
}
|
||||||
|
chan[i].volSpeedTarget=-1;
|
||||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed));
|
dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed));
|
||||||
break;
|
break;
|
||||||
case 0x06: // vol slide + porta
|
case 0x06: // vol slide + porta
|
||||||
|
@ -892,6 +910,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
||||||
} else {
|
} else {
|
||||||
chan[i].volSpeed=0;
|
chan[i].volSpeed=0;
|
||||||
}
|
}
|
||||||
|
chan[i].volSpeedTarget=-1;
|
||||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed));
|
dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed));
|
||||||
break;
|
break;
|
||||||
case 0x07: // tremolo
|
case 0x07: // tremolo
|
||||||
|
@ -902,6 +921,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
||||||
chan[i].tremoloRate=effectVal>>4;
|
chan[i].tremoloRate=effectVal>>4;
|
||||||
if (chan[i].tremoloDepth!=0) {
|
if (chan[i].tremoloDepth!=0) {
|
||||||
chan[i].volSpeed=0;
|
chan[i].volSpeed=0;
|
||||||
|
chan[i].volSpeedTarget=-1;
|
||||||
} else {
|
} else {
|
||||||
dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8));
|
dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8));
|
||||||
dispatchCmd(DivCommand(DIV_CMD_HINT_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 {
|
} else {
|
||||||
chan[i].volSpeed=0;
|
chan[i].volSpeed=0;
|
||||||
}
|
}
|
||||||
|
chan[i].volSpeedTarget=-1;
|
||||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed));
|
dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed));
|
||||||
break;
|
break;
|
||||||
case 0x00: // arpeggio
|
case 0x00: // arpeggio
|
||||||
|
@ -963,6 +984,22 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
||||||
chan[i].cutType=0;
|
chan[i].cutType=0;
|
||||||
}
|
}
|
||||||
break;
|
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
|
case 0xe0: // arp speed
|
||||||
if (effectVal>0) {
|
if (effectVal>0) {
|
||||||
curSubSong->arpLen=effectVal;
|
curSubSong->arpLen=effectVal;
|
||||||
|
@ -1100,6 +1137,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
||||||
chan[i].tremoloDepth=0;
|
chan[i].tremoloDepth=0;
|
||||||
chan[i].tremoloRate=0;
|
chan[i].tremoloRate=0;
|
||||||
chan[i].volSpeed=effectVal;
|
chan[i].volSpeed=effectVal;
|
||||||
|
chan[i].volSpeedTarget=-1;
|
||||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed));
|
dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed));
|
||||||
break;
|
break;
|
||||||
case 0xf4: // fine volume ramp down
|
case 0xf4: // fine volume ramp down
|
||||||
|
@ -1107,6 +1145,7 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
||||||
chan[i].tremoloDepth=0;
|
chan[i].tremoloDepth=0;
|
||||||
chan[i].tremoloRate=0;
|
chan[i].tremoloRate=0;
|
||||||
chan[i].volSpeed=-effectVal;
|
chan[i].volSpeed=-effectVal;
|
||||||
|
chan[i].volSpeedTarget=-1;
|
||||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed));
|
dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed));
|
||||||
break;
|
break;
|
||||||
case 0xf5: // disable macro
|
case 0xf5: // disable macro
|
||||||
|
@ -1120,12 +1159,14 @@ void DivEngine::processRow(int i, bool afterDelay) {
|
||||||
break;
|
break;
|
||||||
case 0xf8: // single volume ramp up
|
case 0xf8: // single volume ramp up
|
||||||
chan[i].volSpeed=0; // add compat flag?
|
chan[i].volSpeed=0; // add compat flag?
|
||||||
|
chan[i].volSpeedTarget=-1;
|
||||||
chan[i].volume=MIN(chan[i].volume+effectVal*256,chan[i].volMax);
|
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_VOLUME,i,chan[i].volume>>8));
|
||||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8));
|
dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8));
|
||||||
break;
|
break;
|
||||||
case 0xf9: // single volume ramp down
|
case 0xf9: // single volume ramp down
|
||||||
chan[i].volSpeed=0; // add compat flag?
|
chan[i].volSpeed=0; // add compat flag?
|
||||||
|
chan[i].volSpeedTarget=-1;
|
||||||
chan[i].volume=MAX(chan[i].volume-effectVal*256,0);
|
chan[i].volume=MAX(chan[i].volume-effectVal*256,0);
|
||||||
dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8));
|
dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8));
|
||||||
dispatchCmd(DivCommand(DIV_CMD_HINT_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 {
|
} else {
|
||||||
chan[i].volSpeed=0;
|
chan[i].volSpeed=0;
|
||||||
}
|
}
|
||||||
|
chan[i].volSpeedTarget=-1;
|
||||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed));
|
dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,chan[i].volSpeed));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0xfc: // delayed note release
|
case 0xfc: // delayed note release
|
||||||
if (song.delayBehavior==2 || effectVal<nextSpeed) {
|
if (song.delayBehavior==2 || effectVal<nextSpeed) {
|
||||||
chan[i].cut=effectVal+1;
|
chan[i].cut=effectVal+1;
|
||||||
|
@ -1613,9 +1654,30 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) {
|
||||||
if (chan[i].volSpeed!=0) {
|
if (chan[i].volSpeed!=0) {
|
||||||
chan[i].volume=(chan[i].volume&0xff)|(dispatchCmd(DivCommand(DIV_CMD_GET_VOLUME,i))<<8);
|
chan[i].volume=(chan[i].volume&0xff)|(dispatchCmd(DivCommand(DIV_CMD_GET_VOLUME,i))<<8);
|
||||||
chan[i].volume+=chan[i].volSpeed;
|
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;
|
||||||
|
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) {
|
if (chan[i].volume>chan[i].volMax) {
|
||||||
chan[i].volume=chan[i].volMax;
|
chan[i].volume=chan[i].volMax;
|
||||||
chan[i].volSpeed=0;
|
chan[i].volSpeed=0;
|
||||||
|
chan[i].volSpeedTarget=-1;
|
||||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8));
|
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_VOLUME,i,chan[i].volume>>8));
|
||||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,0));
|
dispatchCmd(DivCommand(DIV_CMD_HINT_VOL_SLIDE,i,0));
|
||||||
|
@ -1627,6 +1689,7 @@ bool DivEngine::nextTick(bool noAccum, bool inhibitLowLat) {
|
||||||
} else {
|
} else {
|
||||||
chan[i].volume=0;
|
chan[i].volume=0;
|
||||||
}
|
}
|
||||||
|
chan[i].volSpeedTarget=-1;
|
||||||
dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8));
|
dispatchCmd(DivCommand(DIV_CMD_VOLUME,i,chan[i].volume>>8));
|
||||||
dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8));
|
dispatchCmd(DivCommand(DIV_CMD_HINT_VOLUME,i,chan[i].volume>>8));
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -2169,8 +2169,8 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
|
||||||
w->writeI(0);
|
w->writeI(0);
|
||||||
w->write(writeADPCM_Y8950[i]->getSampleMem(0),writeADPCM_Y8950[i]->getSampleMemUsage(0));
|
w->write(writeADPCM_Y8950[i]->getSampleMem(0),writeADPCM_Y8950[i]->getSampleMemUsage(0));
|
||||||
}
|
}
|
||||||
if (writeQSound[i]!=NULL && writeQSound[i]->getSampleMemUsage()>0) {
|
if (writeQSound[i]!=NULL && writeQSound[i]->getSampleMemUsage(1)>0) {
|
||||||
unsigned int blockSize=(writeQSound[i]->getSampleMemUsage()+0xffff)&(~0xffff);
|
unsigned int blockSize=(writeQSound[i]->getSampleMemUsage(1)+0xffff)&(~0xffff);
|
||||||
if (blockSize > 0x1000000) {
|
if (blockSize > 0x1000000) {
|
||||||
blockSize = 0x1000000;
|
blockSize = 0x1000000;
|
||||||
}
|
}
|
||||||
|
@ -2178,7 +2178,7 @@ SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool p
|
||||||
w->writeC(0x66);
|
w->writeC(0x66);
|
||||||
w->writeC(0x8F);
|
w->writeC(0x8F);
|
||||||
w->writeI((blockSize+8)|(i*0x80000000));
|
w->writeI((blockSize+8)|(i*0x80000000));
|
||||||
w->writeI(writeQSound[i]->getSampleMemCapacity());
|
w->writeI(writeQSound[i]->getSampleMemCapacity(1));
|
||||||
w->writeI(0);
|
w->writeI(0);
|
||||||
w->write(writeQSound[i]->getSampleMem(),blockSize);
|
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->writeWString(ws,false); // japanese author name
|
||||||
w->writeS(0); // date
|
w->writeS(0); // date
|
||||||
w->writeWString(L"Furnace (chiptune tracker)",false); // ripper
|
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;
|
int gd3Len=w->tell()-gd3Off-12;
|
||||||
|
|
||||||
|
|
|
@ -42,6 +42,7 @@ template<typename T, size_t items> struct FixedQueue {
|
||||||
void clear();
|
void clear();
|
||||||
bool empty();
|
bool empty();
|
||||||
size_t size();
|
size_t size();
|
||||||
|
size_t capacity();
|
||||||
FixedQueue():
|
FixedQueue():
|
||||||
readPos(0),
|
readPos(0),
|
||||||
writePos(0) {}
|
writePos(0) {}
|
||||||
|
@ -177,4 +178,8 @@ template <typename T, size_t items> size_t FixedQueue<T,items>::size() {
|
||||||
return writePos-readPos;
|
return writePos-readPos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename T, size_t items> size_t FixedQueue<T,items>::capacity() {
|
||||||
|
return items-1;
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -35,6 +35,7 @@ const char* aboutLine[]={
|
||||||
_N("-- program --"),
|
_N("-- program --"),
|
||||||
"tildearrow",
|
"tildearrow",
|
||||||
_N("A M 4 N (intro tune)"),
|
_N("A M 4 N (intro tune)"),
|
||||||
|
"Adam Lederer",
|
||||||
"akumanatt",
|
"akumanatt",
|
||||||
"cam900",
|
"cam900",
|
||||||
"djtuBIG-MaliceX",
|
"djtuBIG-MaliceX",
|
||||||
|
|
|
@ -161,6 +161,8 @@ void FurnaceGUI::drawCSPlayer() {
|
||||||
ImGui::TableNextColumn();
|
ImGui::TableNextColumn();
|
||||||
ImGui::Text(_("vols"));
|
ImGui::Text(_("vols"));
|
||||||
ImGui::TableNextColumn();
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text(_("volst"));
|
||||||
|
ImGui::TableNextColumn();
|
||||||
ImGui::Text(_("vib"));
|
ImGui::Text(_("vib"));
|
||||||
ImGui::TableNextColumn();
|
ImGui::TableNextColumn();
|
||||||
ImGui::Text(_("porta"));
|
ImGui::Text(_("porta"));
|
||||||
|
@ -189,6 +191,8 @@ void FurnaceGUI::drawCSPlayer() {
|
||||||
ImGui::TableNextColumn();
|
ImGui::TableNextColumn();
|
||||||
ImGui::Text("%+d",state->volSpeed);
|
ImGui::Text("%+d",state->volSpeed);
|
||||||
ImGui::TableNextColumn();
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text("%+d",state->volSpeedTarget);
|
||||||
|
ImGui::TableNextColumn();
|
||||||
ImGui::Text("%d/%d (%d)",state->vibratoDepth,state->vibratoRate,state->vibratoPos);
|
ImGui::Text("%d/%d (%d)",state->vibratoDepth,state->vibratoRate,state->vibratoPos);
|
||||||
ImGui::TableNextColumn();
|
ImGui::TableNextColumn();
|
||||||
ImGui::Text("-> %d (%d)",state->portaTarget,state->portaSpeed);
|
ImGui::Text("-> %d (%d)",state->portaTarget,state->portaSpeed);
|
||||||
|
|
|
@ -145,6 +145,7 @@ void FurnaceGUI::drawDebug() {
|
||||||
ImGui::Text("- portaNote = %d",ch->portaNote);
|
ImGui::Text("- portaNote = %d",ch->portaNote);
|
||||||
ImGui::Text("- volume = %.4x",ch->volume);
|
ImGui::Text("- volume = %.4x",ch->volume);
|
||||||
ImGui::Text("- volSpeed = %d",ch->volSpeed);
|
ImGui::Text("- volSpeed = %d",ch->volSpeed);
|
||||||
|
ImGui::Text("- volSpeedTarget = %d",ch->volSpeedTarget);
|
||||||
ImGui::Text("- cut = %d",ch->cut);
|
ImGui::Text("- cut = %d",ch->cut);
|
||||||
ImGui::Text("- rowDelay = %d",ch->rowDelay);
|
ImGui::Text("- rowDelay = %d",ch->rowDelay);
|
||||||
ImGui::Text("- volMax = %.4x",ch->volMax);
|
ImGui::Text("- volMax = %.4x",ch->volMax);
|
||||||
|
|
|
@ -73,6 +73,8 @@ void FurnaceGUI::doAction(int what) {
|
||||||
case GUI_ACTION_UNDO:
|
case GUI_ACTION_UNDO:
|
||||||
if (curWindow==GUI_WINDOW_SAMPLE_EDIT) {
|
if (curWindow==GUI_WINDOW_SAMPLE_EDIT) {
|
||||||
doUndoSample();
|
doUndoSample();
|
||||||
|
} else if (curWindow==GUI_WINDOW_INS_EDIT) {
|
||||||
|
doUndoInstrument();
|
||||||
} else {
|
} else {
|
||||||
doUndo();
|
doUndo();
|
||||||
}
|
}
|
||||||
|
@ -80,6 +82,8 @@ void FurnaceGUI::doAction(int what) {
|
||||||
case GUI_ACTION_REDO:
|
case GUI_ACTION_REDO:
|
||||||
if (curWindow==GUI_WINDOW_SAMPLE_EDIT) {
|
if (curWindow==GUI_WINDOW_SAMPLE_EDIT) {
|
||||||
doRedoSample();
|
doRedoSample();
|
||||||
|
} else if (curWindow==GUI_WINDOW_INS_EDIT) {
|
||||||
|
doRedoInstrument();
|
||||||
} else {
|
} else {
|
||||||
doRedo();
|
doRedo();
|
||||||
}
|
}
|
||||||
|
@ -677,30 +681,7 @@ void FurnaceGUI::doAction(int what) {
|
||||||
latchNibble=false;
|
latchNibble=false;
|
||||||
break;
|
break;
|
||||||
case GUI_ACTION_PAT_ABSORB_INSTRUMENT: {
|
case GUI_ACTION_PAT_ABSORB_INSTRUMENT: {
|
||||||
DivPattern* pat=e->curPat[cursor.xCoarse].getPattern(e->curOrders->ord[cursor.xCoarse][curOrder],false);
|
doAbsorbInstrument();
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -508,7 +508,7 @@ void FurnaceGUI::drawMobileControls() {
|
||||||
mobileMenuOpen=false;
|
mobileMenuOpen=false;
|
||||||
doAction(GUI_ACTION_SAVE_AS);
|
doAction(GUI_ACTION_SAVE_AS);
|
||||||
}
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
if (ImGui::Button(_("Export"))) {
|
if (ImGui::Button(_("Export"))) {
|
||||||
doAction(GUI_ACTION_EXPORT);
|
doAction(GUI_ACTION_EXPORT);
|
||||||
}
|
}
|
||||||
|
@ -533,6 +533,10 @@ void FurnaceGUI::drawMobileControls() {
|
||||||
drawSpeed(true);
|
drawSpeed(true);
|
||||||
ImGui::EndTabItem();
|
ImGui::EndTabItem();
|
||||||
}
|
}
|
||||||
|
if (ImGui::BeginTabItem(_("Comments"))) {
|
||||||
|
drawNotes(true);
|
||||||
|
ImGui::EndTabItem();
|
||||||
|
}
|
||||||
ImGui::EndTabBar();
|
ImGui::EndTabBar();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -1823,6 +1823,52 @@ void FurnaceGUI::doExpandSong(int multiplier) {
|
||||||
if (e->isPlaying()) e->play();
|
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() {
|
void FurnaceGUI::doDrag() {
|
||||||
int len=dragEnd.xCoarse-dragStart.xCoarse+1;
|
int len=dragEnd.xCoarse-dragStart.xCoarse+1;
|
||||||
|
|
||||||
|
|
|
@ -3739,6 +3739,7 @@ bool FurnaceGUI::loop() {
|
||||||
ImGui::GetIO().AddKeyEvent(ImGuiKey_Backspace,false);
|
ImGui::GetIO().AddKeyEvent(ImGuiKey_Backspace,false);
|
||||||
injectBackUp=false;
|
injectBackUp=false;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (SDL_PollEvent(&ev)) {
|
while (SDL_PollEvent(&ev)) {
|
||||||
WAKE_UP;
|
WAKE_UP;
|
||||||
ImGui_ImplSDL2_ProcessEvent(&ev);
|
ImGui_ImplSDL2_ProcessEvent(&ev);
|
||||||
|
@ -3755,13 +3756,16 @@ bool FurnaceGUI::loop() {
|
||||||
}
|
}
|
||||||
case SDL_MOUSEBUTTONUP:
|
case SDL_MOUSEBUTTONUP:
|
||||||
pointUp(ev.button.x,ev.button.y,ev.button.button);
|
pointUp(ev.button.x,ev.button.y,ev.button.button);
|
||||||
|
insEditMayBeDirty=true;
|
||||||
break;
|
break;
|
||||||
case SDL_MOUSEBUTTONDOWN:
|
case SDL_MOUSEBUTTONDOWN:
|
||||||
pointDown(ev.button.x,ev.button.y,ev.button.button);
|
pointDown(ev.button.x,ev.button.y,ev.button.button);
|
||||||
|
insEditMayBeDirty=true;
|
||||||
break;
|
break;
|
||||||
case SDL_MOUSEWHEEL:
|
case SDL_MOUSEWHEEL:
|
||||||
wheelX+=ev.wheel.x;
|
wheelX+=ev.wheel.x;
|
||||||
wheelY+=ev.wheel.y;
|
wheelY+=ev.wheel.y;
|
||||||
|
insEditMayBeDirty=true;
|
||||||
break;
|
break;
|
||||||
case SDL_WINDOWEVENT:
|
case SDL_WINDOWEVENT:
|
||||||
switch (ev.window.event) {
|
switch (ev.window.event) {
|
||||||
|
@ -3838,12 +3842,14 @@ bool FurnaceGUI::loop() {
|
||||||
if (!ImGui::GetIO().WantCaptureKeyboard) {
|
if (!ImGui::GetIO().WantCaptureKeyboard) {
|
||||||
keyDown(ev);
|
keyDown(ev);
|
||||||
}
|
}
|
||||||
|
insEditMayBeDirty=true;
|
||||||
#ifdef IS_MOBILE
|
#ifdef IS_MOBILE
|
||||||
injectBackUp=true;
|
injectBackUp=true;
|
||||||
#endif
|
#endif
|
||||||
break;
|
break;
|
||||||
case SDL_KEYUP:
|
case SDL_KEYUP:
|
||||||
// for now
|
// for now
|
||||||
|
insEditMayBeDirty=true;
|
||||||
break;
|
break;
|
||||||
case SDL_DROPFILE:
|
case SDL_DROPFILE:
|
||||||
if (ev.drop.file!=NULL) {
|
if (ev.drop.file!=NULL) {
|
||||||
|
@ -4482,7 +4488,7 @@ bool FurnaceGUI::loop() {
|
||||||
} else {
|
} else {
|
||||||
if (ImGui::BeginMenu(_("add chip..."))) {
|
if (ImGui::BeginMenu(_("add chip..."))) {
|
||||||
exitDisabledTimer=1;
|
exitDisabledTimer=1;
|
||||||
DivSystem picked=systemPicker();
|
DivSystem picked=systemPicker(false);
|
||||||
if (picked!=DIV_SYSTEM_NULL) {
|
if (picked!=DIV_SYSTEM_NULL) {
|
||||||
if (!e->addSystem(picked)) {
|
if (!e->addSystem(picked)) {
|
||||||
showError(fmt::sprintf(_("cannot add chip! (%s)"),e->getLastError()));
|
showError(fmt::sprintf(_("cannot add chip! (%s)"),e->getLastError()));
|
||||||
|
@ -4513,7 +4519,7 @@ bool FurnaceGUI::loop() {
|
||||||
ImGui::Checkbox(_("Preserve channel positions"),&preserveChanPos);
|
ImGui::Checkbox(_("Preserve channel positions"),&preserveChanPos);
|
||||||
for (int i=0; i<e->song.systemLen; i++) {
|
for (int i=0; i<e->song.systemLen; i++) {
|
||||||
if (ImGui::BeginMenu(fmt::sprintf("%d. %s##_SYSC%d",i+1,getSystemName(e->song.system[i]),i).c_str())) {
|
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 (picked!=DIV_SYSTEM_NULL) {
|
||||||
if (e->changeSystem(i,picked,preserveChanPos)) {
|
if (e->changeSystem(i,picked,preserveChanPos)) {
|
||||||
MARK_MODIFIED;
|
MARK_MODIFIED;
|
||||||
|
@ -5856,6 +5862,7 @@ bool FurnaceGUI::loop() {
|
||||||
|
|
||||||
centerNextWindow(_("Rendering..."),canvasW,canvasH);
|
centerNextWindow(_("Rendering..."),canvasW,canvasH);
|
||||||
if (ImGui::BeginPopupModal(_("Rendering..."),NULL,ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove)) {
|
if (ImGui::BeginPopupModal(_("Rendering..."),NULL,ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove)) {
|
||||||
|
WAKE_UP;
|
||||||
if(audioExportOptions.mode != DIV_EXPORT_MODE_MANY_CHAN)
|
if(audioExportOptions.mode != DIV_EXPORT_MODE_MANY_CHAN)
|
||||||
{
|
{
|
||||||
ImGui::Text(_("Please wait..."));
|
ImGui::Text(_("Please wait..."));
|
||||||
|
@ -5916,7 +5923,7 @@ bool FurnaceGUI::loop() {
|
||||||
ImGui::Text(_("Channel %d of %d"), curFile + 1, totalFiles);
|
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 (ImGui::Button(_("Abort"))) {
|
||||||
if (e->haltAudioFile()) {
|
if (e->haltAudioFile()) {
|
||||||
|
@ -7241,6 +7248,11 @@ bool FurnaceGUI::loop() {
|
||||||
willCommit=false;
|
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) {
|
||||||
if (--shallDetectScale<1) {
|
if (--shallDetectScale<1) {
|
||||||
if (settings.dpiScale<0.5f) {
|
if (settings.dpiScale<0.5f) {
|
||||||
|
@ -8414,6 +8426,8 @@ FurnaceGUI::FurnaceGUI():
|
||||||
localeRequiresChineseTrad(false),
|
localeRequiresChineseTrad(false),
|
||||||
localeRequiresKorean(false),
|
localeRequiresKorean(false),
|
||||||
prevInsData(NULL),
|
prevInsData(NULL),
|
||||||
|
cachedCurInsPtr(NULL),
|
||||||
|
insEditMayBeDirty(false),
|
||||||
pendingLayoutImport(NULL),
|
pendingLayoutImport(NULL),
|
||||||
pendingLayoutImportLen(0),
|
pendingLayoutImportLen(0),
|
||||||
pendingLayoutImportStep(0),
|
pendingLayoutImportStep(0),
|
||||||
|
|
|
@ -2270,6 +2270,9 @@ class FurnaceGUI {
|
||||||
std::vector<ImWchar> localeExtraRanges;
|
std::vector<ImWchar> localeExtraRanges;
|
||||||
|
|
||||||
DivInstrument* prevInsData;
|
DivInstrument* prevInsData;
|
||||||
|
DivInstrument cachedCurIns;
|
||||||
|
DivInstrument* cachedCurInsPtr;
|
||||||
|
bool insEditMayBeDirty;
|
||||||
|
|
||||||
unsigned char* pendingLayoutImport;
|
unsigned char* pendingLayoutImport;
|
||||||
size_t pendingLayoutImportLen;
|
size_t pendingLayoutImportLen;
|
||||||
|
@ -2834,7 +2837,7 @@ class FurnaceGUI {
|
||||||
void drawMemory();
|
void drawMemory();
|
||||||
void drawCompatFlags();
|
void drawCompatFlags();
|
||||||
void drawPiano();
|
void drawPiano();
|
||||||
void drawNotes();
|
void drawNotes(bool asChild=false);
|
||||||
void drawChannels();
|
void drawChannels();
|
||||||
void drawPatManager();
|
void drawPatManager();
|
||||||
void drawSysManager();
|
void drawSysManager();
|
||||||
|
@ -2924,13 +2927,14 @@ class FurnaceGUI {
|
||||||
void doExpand(int multiplier, const SelectionPoint& sStart, const SelectionPoint& sEnd);
|
void doExpand(int multiplier, const SelectionPoint& sStart, const SelectionPoint& sEnd);
|
||||||
void doCollapseSong(int divider);
|
void doCollapseSong(int divider);
|
||||||
void doExpandSong(int multiplier);
|
void doExpandSong(int multiplier);
|
||||||
|
void doAbsorbInstrument();
|
||||||
void doUndo();
|
void doUndo();
|
||||||
void doRedo();
|
void doRedo();
|
||||||
void doFind();
|
void doFind();
|
||||||
void doReplace();
|
void doReplace();
|
||||||
void doDrag();
|
void doDrag();
|
||||||
void editOptions(bool topMenu);
|
void editOptions(bool topMenu);
|
||||||
DivSystem systemPicker();
|
DivSystem systemPicker(bool fullWidth);
|
||||||
void noteInput(int num, int key, int vol=-1);
|
void noteInput(int num, int key, int vol=-1);
|
||||||
void valueInput(int num, bool direct=false, int target=-1);
|
void valueInput(int num, bool direct=false, int target=-1);
|
||||||
void orderInput(int num);
|
void orderInput(int num);
|
||||||
|
@ -2940,6 +2944,10 @@ class FurnaceGUI {
|
||||||
void doUndoSample();
|
void doUndoSample();
|
||||||
void doRedoSample();
|
void doRedoSample();
|
||||||
|
|
||||||
|
void checkRecordInstrumentUndoStep();
|
||||||
|
void doUndoInstrument();
|
||||||
|
void doRedoInstrument();
|
||||||
|
|
||||||
void play(int row=0);
|
void play(int row=0);
|
||||||
void setOrder(unsigned char order, bool forced=false);
|
void setOrder(unsigned char order, bool forced=false);
|
||||||
void stop();
|
void stop();
|
||||||
|
|
|
@ -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_INVALID,
|
||||||
GUI_COLOR_PATTERN_EFFECT_INVALID,
|
GUI_COLOR_PATTERN_EFFECT_VOLUME,
|
||||||
GUI_COLOR_PATTERN_EFFECT_INVALID,
|
GUI_COLOR_PATTERN_EFFECT_VOLUME,
|
||||||
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_INVALID,
|
||||||
|
|
|
@ -6438,6 +6438,7 @@ void FurnaceGUI::drawInsEdit() {
|
||||||
ImGui::SetNextWindowSizeConstraints(ImVec2(440.0f*dpiScale,400.0f*dpiScale),ImVec2(canvasW,canvasH));
|
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"))) {
|
if (ImGui::Begin("Instrument Editor",&insEditOpen,globalWinFlags|(settings.allowEditDocking?0:ImGuiWindowFlags_NoDocking),_("Instrument Editor"))) {
|
||||||
|
DivInstrument* ins=NULL;
|
||||||
if (curIns==-2) {
|
if (curIns==-2) {
|
||||||
ImGui::SetCursorPosY(ImGui::GetCursorPosY()+(ImGui::GetContentRegionAvail().y-ImGui::GetFrameHeightWithSpacing()+ImGui::GetStyle().ItemSpacing.y)*0.5f);
|
ImGui::SetCursorPosY(ImGui::GetCursorPosY()+(ImGui::GetContentRegionAvail().y-ImGui::GetFrameHeightWithSpacing()+ImGui::GetStyle().ItemSpacing.y)*0.5f);
|
||||||
CENTER_TEXT(_("waiting..."));
|
CENTER_TEXT(_("waiting..."));
|
||||||
|
@ -6465,6 +6466,7 @@ void FurnaceGUI::drawInsEdit() {
|
||||||
curIns=i;
|
curIns=i;
|
||||||
wavePreviewInit=true;
|
wavePreviewInit=true;
|
||||||
updateFMPreview=true;
|
updateFMPreview=true;
|
||||||
|
ins = e->song.ins[curIns];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ImGui::EndCombo();
|
ImGui::EndCombo();
|
||||||
|
@ -6487,7 +6489,7 @@ void FurnaceGUI::drawInsEdit() {
|
||||||
ImGui::EndTable();
|
ImGui::EndTable();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
DivInstrument* ins=e->song.ins[curIns];
|
ins=e->song.ins[curIns];
|
||||||
if (updateFMPreview) {
|
if (updateFMPreview) {
|
||||||
renderFMPreview(ins);
|
renderFMPreview(ins);
|
||||||
updateFMPreview=false;
|
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(_("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(_("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(_("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));
|
macroList.push_back(FurnaceGUIMacroDesc(_("Phase Reset"),&ins->std.phaseResetMacro,0,1,32,uiColors[GUI_COLOR_MACRO_OTHER],false,NULL,NULL,true));
|
||||||
break;
|
break;
|
||||||
|
@ -8703,6 +8707,63 @@ void FurnaceGUI::drawInsEdit() {
|
||||||
ImGui::EndPopup();
|
ImGui::EndPopup();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_INS_EDIT;
|
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_INS_EDIT;
|
||||||
ImGui::End();
|
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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -1507,6 +1507,7 @@ void FurnaceGUI::drawPattern() {
|
||||||
i.cmd==DIV_CMD_HINT_PORTA ||
|
i.cmd==DIV_CMD_HINT_PORTA ||
|
||||||
i.cmd==DIV_CMD_HINT_LEGATO ||
|
i.cmd==DIV_CMD_HINT_LEGATO ||
|
||||||
i.cmd==DIV_CMD_HINT_VOL_SLIDE ||
|
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_ARPEGGIO ||
|
||||||
i.cmd==DIV_CMD_HINT_PITCH ||
|
i.cmd==DIV_CMD_HINT_PITCH ||
|
||||||
i.cmd==DIV_CMD_HINT_VIBRATO ||
|
i.cmd==DIV_CMD_HINT_VIBRATO ||
|
||||||
|
|
|
@ -1084,15 +1084,19 @@ void FurnaceGUI::drawSettings() {
|
||||||
ImGui::PushID(i);
|
ImGui::PushID(i);
|
||||||
|
|
||||||
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x-ImGui::CalcTextSize(_("Invert")).x-ImGui::GetFrameHeightWithSpacing()*2.0-ImGui::GetStyle().ItemSpacing.x*2.0);
|
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))) {
|
if (ImGui::BeginCombo("##System",getSystemName(sysID),ImGuiComboFlags_HeightLargest)) {
|
||||||
for (int j=0; availableSystems[j]; j++) {
|
|
||||||
if (ImGui::Selectable(getSystemName((DivSystem)availableSystems[j]),sysID==availableSystems[j])) {
|
sysID=systemPicker(true);
|
||||||
sysID=(DivSystem)availableSystems[j];
|
|
||||||
settings.initialSys.set(fmt::sprintf("id%d",i),(int)e->systemToFileFur(sysID));
|
if (sysID!=DIV_SYSTEM_NULL)
|
||||||
settings.initialSys.set(fmt::sprintf("flags%d",i),"");
|
{
|
||||||
settingsChanged=true;
|
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();
|
ImGui::EndCombo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,18 +22,23 @@
|
||||||
|
|
||||||
// NOTE: please don't ask me to enable text wrap.
|
// NOTE: please don't ask me to enable text wrap.
|
||||||
// Dear ImGui doesn't have that feature. D:
|
// Dear ImGui doesn't have that feature. D:
|
||||||
void FurnaceGUI::drawNotes() {
|
void FurnaceGUI::drawNotes(bool asChild) {
|
||||||
if (nextWindow==GUI_WINDOW_NOTES) {
|
if (nextWindow==GUI_WINDOW_NOTES) {
|
||||||
notesOpen=true;
|
notesOpen=true;
|
||||||
ImGui::SetNextWindowFocus();
|
ImGui::SetNextWindowFocus();
|
||||||
nextWindow=GUI_WINDOW_NOTHING;
|
nextWindow=GUI_WINDOW_NOTHING;
|
||||||
}
|
}
|
||||||
if (!notesOpen) return;
|
if (!notesOpen && !asChild) return;
|
||||||
if (ImGui::Begin("Song Comments",¬esOpen,globalWinFlags,_("Song Comments"))) {
|
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)) {
|
if (ImGui::InputTextMultiline("##SongNotes",&e->song.notes,ImGui::GetContentRegionAvail(),ImGuiInputTextFlags_UndoRedo)) {
|
||||||
MARK_MODIFIED;
|
MARK_MODIFIED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_NOTES;
|
if (!asChild && ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_NOTES;
|
||||||
ImGui::End();
|
if (asChild) {
|
||||||
|
ImGui::EndChild();
|
||||||
|
} else {
|
||||||
|
ImGui::End();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -102,7 +102,7 @@ void FurnaceGUI::drawSysManager() {
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::Button(_("Change##SysChange"));
|
ImGui::Button(_("Change##SysChange"));
|
||||||
if (ImGui::BeginPopupContextItem("SysPickerC",ImGuiPopupFlags_MouseButtonLeft)) {
|
if (ImGui::BeginPopupContextItem("SysPickerC",ImGuiPopupFlags_MouseButtonLeft)) {
|
||||||
DivSystem picked=systemPicker();
|
DivSystem picked=systemPicker(false);
|
||||||
if (picked!=DIV_SYSTEM_NULL) {
|
if (picked!=DIV_SYSTEM_NULL) {
|
||||||
if (e->changeSystem(i,picked,preserveChanPos)) {
|
if (e->changeSystem(i,picked,preserveChanPos)) {
|
||||||
MARK_MODIFIED;
|
MARK_MODIFIED;
|
||||||
|
@ -138,7 +138,7 @@ void FurnaceGUI::drawSysManager() {
|
||||||
ImGui::TableNextColumn();
|
ImGui::TableNextColumn();
|
||||||
ImGui::Button(ICON_FA_PLUS "##SysAdd");
|
ImGui::Button(ICON_FA_PLUS "##SysAdd");
|
||||||
if (ImGui::BeginPopupContextItem("SysPickerA",ImGuiPopupFlags_MouseButtonLeft)) {
|
if (ImGui::BeginPopupContextItem("SysPickerA",ImGuiPopupFlags_MouseButtonLeft)) {
|
||||||
DivSystem picked=systemPicker();
|
DivSystem picked=systemPicker(false);
|
||||||
if (picked!=DIV_SYSTEM_NULL) {
|
if (picked!=DIV_SYSTEM_NULL) {
|
||||||
if (!e->addSystem(picked)) {
|
if (!e->addSystem(picked)) {
|
||||||
showError(fmt::sprintf(_("cannot add chip! (%s)"),e->getLastError()));
|
showError(fmt::sprintf(_("cannot add chip! (%s)"),e->getLastError()));
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
#include "guiConst.h"
|
#include "guiConst.h"
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
|
|
||||||
DivSystem FurnaceGUI::systemPicker() {
|
DivSystem FurnaceGUI::systemPicker(bool fullWidth) {
|
||||||
DivSystem ret=DIV_SYSTEM_NULL;
|
DivSystem ret=DIV_SYSTEM_NULL;
|
||||||
DivSystem hoveredSys=DIV_SYSTEM_NULL;
|
DivSystem hoveredSys=DIV_SYSTEM_NULL;
|
||||||
bool reissueSearch=false;
|
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()) {
|
if (sysSearchQuery.empty()) {
|
||||||
// display chip list
|
// display chip list
|
||||||
for (int j=0; curSysSection[j]; j++) {
|
for (int j=0; curSysSection[j]; j++) {
|
||||||
|
|
|
@ -392,7 +392,7 @@ void FurnaceGUI::drawUserPresets() {
|
||||||
tempID=fmt::sprintf("%s##USystem",getSystemName(chip.sys));
|
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));
|
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)) {
|
if (ImGui::BeginPopupContextItem("SysPickerCU",ImGuiPopupFlags_MouseButtonLeft)) {
|
||||||
DivSystem picked=systemPicker();
|
DivSystem picked=systemPicker(false);
|
||||||
if (picked!=DIV_SYSTEM_NULL) {
|
if (picked!=DIV_SYSTEM_NULL) {
|
||||||
chip.sys=picked;
|
chip.sys=picked;
|
||||||
mustBake=true;
|
mustBake=true;
|
||||||
|
@ -456,7 +456,7 @@ void FurnaceGUI::drawUserPresets() {
|
||||||
|
|
||||||
ImGui::Button(ICON_FA_PLUS "##SysAddU");
|
ImGui::Button(ICON_FA_PLUS "##SysAddU");
|
||||||
if (ImGui::BeginPopupContextItem("SysPickerU",ImGuiPopupFlags_MouseButtonLeft)) {
|
if (ImGui::BeginPopupContextItem("SysPickerU",ImGuiPopupFlags_MouseButtonLeft)) {
|
||||||
DivSystem picked=systemPicker();
|
DivSystem picked=systemPicker(false);
|
||||||
if (picked!=DIV_SYSTEM_NULL) {
|
if (picked!=DIV_SYSTEM_NULL) {
|
||||||
preset->orig.push_back(FurnaceGUISysDefChip(picked,1.0f,0.0f,""));
|
preset->orig.push_back(FurnaceGUISysDefChip(picked,1.0f,0.0f,""));
|
||||||
mustBake=true;
|
mustBake=true;
|
||||||
|
|
Loading…
Reference in a new issue