Merge branch 'master' of https://github.com/tildearrow/furnace into n163

This commit is contained in:
cam900 2022-03-24 03:22:33 +09:00
commit e8d567d3df
14 changed files with 449 additions and 46 deletions

View file

@ -29,6 +29,7 @@ furthermore, an `or reserved` indicates this field is always present, but is res
the format versions are: the format versions are:
- 70: Furnace dev70
- 69: Furnace dev69 - 69: Furnace dev69
- 68: Furnace dev68 - 68: Furnace dev68
- 67: Furnace dev67 - 67: Furnace dev67
@ -235,6 +236,9 @@ size | description
STR | song comment STR | song comment
4f | master volume, 1.0f=100% (>=59) 4f | master volume, 1.0f=100% (>=59)
| this is 2.0f for modules before 59 | this is 2.0f for modules before 59
--- | **extended compatibility flags** (>=70)
1 | broken speed selection
31 | reserved
``` ```
# instrument # instrument

View file

@ -38,8 +38,8 @@
warnings+=(String("\n")+x); \ warnings+=(String("\n")+x); \
} }
#define DIV_VERSION "dev69" #define DIV_VERSION "dev70"
#define DIV_ENGINE_VERSION 69 #define DIV_ENGINE_VERSION 70
// for imports // for imports
#define DIV_VERSION_MOD 0xff01 #define DIV_VERSION_MOD 0xff01

View file

@ -144,6 +144,7 @@ bool DivEngine::loadDMF(unsigned char* file, size_t len) {
ds.oneTickCut=false; ds.oneTickCut=false;
ds.newInsTriggersInPorta=true; ds.newInsTriggersInPorta=true;
ds.arp0Reset=true; ds.arp0Reset=true;
ds.brokenSpeedSel=true;
// 1.1 compat flags // 1.1 compat flags
if (ds.version>24) { if (ds.version>24) {
@ -1067,6 +1068,14 @@ bool DivEngine::loadFur(unsigned char* file, size_t len) {
ds.masterVol=2.0f; ds.masterVol=2.0f;
} }
if (ds.version>=70) {
// extended compat flags
ds.brokenSpeedSel=reader.readC();
for (int i=0; i<31; i++) {
reader.readC();
}
}
// read instruments // read instruments
for (int i=0; i<ds.insLen; i++) { for (int i=0; i<ds.insLen; i++) {
DivInstrument* ins=new DivInstrument; DivInstrument* ins=new DivInstrument;
@ -1905,6 +1914,12 @@ SafeWriter* DivEngine::saveFur(bool notPrimary) {
w->writeF(song.masterVol); w->writeF(song.masterVol);
// extended compat flags
w->writeC(song.brokenSpeedSel);
for (int i=0; i<31; i++) {
w->writeC(0);
}
/// INSTRUMENT /// INSTRUMENT
for (int i=0; i<song.insLen; i++) { for (int i=0; i<song.insLen; i++) {
DivInstrument* ins=song.ins[i]; DivInstrument* ins=song.ins[i];

View file

@ -1236,14 +1236,24 @@ void DivEngine::nextRow() {
if (haltOn==DIV_HALT_PATTERN) halted=true; if (haltOn==DIV_HALT_PATTERN) halted=true;
} }
if (speedAB) { if (song.brokenSpeedSel) {
ticks=speed2*(song.timeBase+1); if ((song.patLen&1) && curOrder&1) {
nextSpeed=speed1; ticks=((curRow&1)?speed2:speed1)*(song.timeBase+1);
nextSpeed=(curRow&1)?speed1:speed2;
} else {
ticks=((curRow&1)?speed1:speed2)*(song.timeBase+1);
nextSpeed=(curRow&1)?speed2:speed1;
}
} else { } else {
ticks=speed1*(song.timeBase+1); if (speedAB) {
nextSpeed=speed2; ticks=speed2*(song.timeBase+1);
nextSpeed=speed1;
} else {
ticks=speed1*(song.timeBase+1);
nextSpeed=speed2;
}
speedAB=!speedAB;
} }
speedAB=!speedAB;
// post row details // post row details
for (int i=0; i<chans; i++) { for (int i=0; i<chans; i++) {

View file

@ -32,6 +32,10 @@ extern "C" {
#include "../../extern/adpcm/ymz_codec.h" #include "../../extern/adpcm/ymz_codec.h"
} }
DivSampleHistory::~DivSampleHistory() {
if (data!=NULL) delete[] data;
}
bool DivSample::save(const char* path) { bool DivSample::save(const char* path) {
SNDFILE* f; SNDFILE* f;
SF_INFO si; SF_INFO si;
@ -258,7 +262,6 @@ bool DivSample::trim(unsigned int begin, unsigned int end) {
return false; return false;
} }
// TODO: for clipboard
bool DivSample::insert(unsigned int pos, unsigned int length) { bool DivSample::insert(unsigned int pos, unsigned int length) {
unsigned int count=samples+length; unsigned int count=samples+length;
if (depth==8) { if (depth==8) {
@ -283,7 +286,12 @@ bool DivSample::insert(unsigned int pos, unsigned int length) {
short* oldData16=data16; short* oldData16=data16;
data16=NULL; data16=NULL;
initInternal(16,count); initInternal(16,count);
memcpy(data16,oldData16,sizeof(short)*count); if (pos>0) {
memcpy(data16,oldData16,sizeof(short)*pos);
}
if (count-pos-length>0) {
memcpy(&(data16[pos+length]),&(oldData16[pos]),sizeof(short)*(count-pos-length));
}
delete[] oldData16; delete[] oldData16;
} else { } else {
initInternal(16,count); initInternal(16,count);
@ -782,7 +790,92 @@ unsigned int DivSample::getCurBufLen() {
return 0; return 0;
} }
DivSampleHistory* DivSample::prepareUndo(bool data, bool doNotPush) {
DivSampleHistory* h;
if (data) {
unsigned char* duplicate;
if (getCurBuf()==NULL) {
duplicate=NULL;
} else {
duplicate=new unsigned char[getCurBufLen()];
memcpy(duplicate,getCurBuf(),getCurBufLen());
}
h=new DivSampleHistory(duplicate,getCurBufLen(),samples,depth,rate,centerRate,loopStart);
} else {
h=new DivSampleHistory(depth,rate,centerRate,loopStart);
}
if (!doNotPush) {
while (!redoHist.empty()) {
DivSampleHistory* h=redoHist.back();
delete h;
redoHist.pop_back();
}
if (undoHist.size()>100) undoHist.pop_front();
undoHist.push_back(h);
}
return h;
}
#define applyHistory \
depth=h->depth; \
if (h->hasSample) { \
initInternal(h->depth,h->samples); \
samples=h->samples; \
\
if (h->length!=getCurBufLen()) logW("undo buffer length not equal to current buffer length! %d != %d\n",h->length,getCurBufLen()); \
\
void* buf=getCurBuf(); \
\
if (buf!=NULL && h->data!=NULL) { \
memcpy(buf,h->data,h->length); \
} \
} \
rate=h->rate; \
centerRate=h->centerRate; \
loopStart=h->loopStart;
int DivSample::undo() {
if (undoHist.empty()) return 0;
DivSampleHistory* h=undoHist.back();
DivSampleHistory* redo=prepareUndo(h->hasSample,true);
int ret=h->hasSample?2:1;
applyHistory;
redoHist.push_back(redo);
delete h;
undoHist.pop_back();
return ret;
}
int DivSample::redo() {
if (redoHist.empty()) return 0;
DivSampleHistory* h=redoHist.back();
DivSampleHistory* undo=prepareUndo(h->hasSample,true);
int ret=h->hasSample?2:1;
applyHistory;
undoHist.push_back(undo);
delete h;
redoHist.pop_back();
return ret;
}
DivSample::~DivSample() { DivSample::~DivSample() {
while (!undoHist.empty()) {
DivSampleHistory* h=undoHist.back();
delete h;
undoHist.pop_back();
}
while (!redoHist.empty()) {
DivSampleHistory* h=redoHist.back();
delete h;
redoHist.pop_back();
}
if (data8) delete[] data8; if (data8) delete[] data8;
if (data16) delete[] data16; if (data16) delete[] data16;
if (data1) delete[] data1; if (data1) delete[] data1;

View file

@ -18,6 +18,7 @@
*/ */
#include "../ta-utils.h" #include "../ta-utils.h"
#include <deque>
enum DivResampleFilters { enum DivResampleFilters {
DIV_RESAMPLE_NONE=0, DIV_RESAMPLE_NONE=0,
@ -28,6 +29,33 @@ enum DivResampleFilters {
DIV_RESAMPLE_BEST DIV_RESAMPLE_BEST
}; };
struct DivSampleHistory {
unsigned char* data;
unsigned int length, samples;
unsigned char depth;
int rate, centerRate, loopStart;
bool hasSample;
DivSampleHistory(void* d, unsigned int l, unsigned int s, unsigned char de, int r, int cr, int ls):
data((unsigned char*)d),
length(l),
samples(s),
depth(de),
rate(r),
centerRate(cr),
loopStart(ls),
hasSample(true) {}
DivSampleHistory(unsigned char de, int r, int cr, int ls):
data(NULL),
length(0),
samples(0),
depth(de),
rate(r),
centerRate(cr),
loopStart(ls),
hasSample(false) {}
~DivSampleHistory();
};
struct DivSample { struct DivSample {
String name; String name;
int rate, centerRate, loopStart, loopOffP; int rate, centerRate, loopStart, loopOffP;
@ -62,6 +90,9 @@ struct DivSample {
unsigned int samples; unsigned int samples;
std::deque<DivSampleHistory*> undoHist;
std::deque<DivSampleHistory*> redoHist;
/** /**
* @warning DO NOT USE - internal functions * @warning DO NOT USE - internal functions
*/ */
@ -154,6 +185,28 @@ struct DivSample {
* @return the sample data length. * @return the sample data length.
*/ */
unsigned int getCurBufLen(); unsigned int getCurBufLen();
/**
* prepare an undo step for this sample.
* @param data whether to include sample data.
* @param doNotPush if this is true, don't push the DivSampleHistory to the undo history.
* @return the undo step.
*/
DivSampleHistory* prepareUndo(bool data, bool doNotPush=false);
/**
* undo. you may need to call DivEngine::renderSamples afterwards.
* @warning do not attempt to undo outside of a synchronized block!
* @return 0 on failure; 1 on success and 2 on success (data changed).
*/
int undo();
/**
* redo. you may need to call DivEngine::renderSamples afterwards.
* @warning do not attempt to redo outside of a synchronized block!
* @return 0 on failure; 1 on success and 2 on success (data changed).
*/
int redo();
DivSample(): DivSample():
name(""), name(""),
rate(32000), rate(32000),

View file

@ -301,6 +301,7 @@ struct DivSong {
bool oneTickCut; bool oneTickCut;
bool newInsTriggersInPorta; bool newInsTriggersInPorta;
bool arp0Reset; bool arp0Reset;
bool brokenSpeedSel;
DivOrders orders; DivOrders orders;
std::vector<DivInstrument*> ins; std::vector<DivInstrument*> ins;
@ -373,7 +374,8 @@ struct DivSong {
brokenDACMode(false), brokenDACMode(false),
oneTickCut(false), oneTickCut(false),
newInsTriggersInPorta(true), newInsTriggersInPorta(true),
arp0Reset(true) { arp0Reset(true),
brokenSpeedSel(false) {
for (int i=0; i<32; i++) { for (int i=0; i<32; i++) {
system[i]=DIV_SYSTEM_NULL; system[i]=DIV_SYSTEM_NULL;
systemVol[i]=64; systemVol[i]=64;

View file

@ -924,15 +924,15 @@ const char* chanNames[40][32]={
{"Pulse 1", "Pulse 2", "PCM"}, // MMC5 {"Pulse 1", "Pulse 2", "PCM"}, // MMC5
{"FM 1", "FM 2", "FM 3", "PSG 1", "PSG 2", "PSG 3"}, // OPN {"FM 1", "FM 2", "FM 3", "PSG 1", "PSG 2", "PSG 3"}, // OPN
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Square 1", "Square 2", "Square 3", "Kick", "Snare", "Top", "HiHat", "Tom", "Rim", "ADPCM"}, // PC-98 {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Square 1", "Square 2", "Square 3", "Kick", "Snare", "Top", "HiHat", "Tom", "Rim", "ADPCM"}, // PC-98
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8", "FM 9", "FM 10", "FM 11", "FM 12", "FM 13", "FM 14", "FM 15", "FM 16", "FM 17", "FM 18"}, // OPL3 {"4OP 1", "FM 2", "4OP 3", "FM 4", "4OP 5", "FM 6", "4OP 7", "FM 8", "4OP 9", "FM 10", "4OP 11", "FM 12", "FM 13", "FM 14", "FM 15", "FM 16", "FM 17", "FM 18"}, // OPL3
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "Channel 17", "Channel 18", "Channel 19", "Channel 20", "Channel 21", "Channel 22", "Channel 23", "Channel 24", "Channel 25", "Channel 26", "Channel 27", "Channel 28"}, // MultiPCM {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Channel 6", "Channel 7", "Channel 8", "Channel 9", "Channel 10", "Channel 11", "Channel 12", "Channel 13", "Channel 14", "Channel 15", "Channel 16", "Channel 17", "Channel 18", "Channel 19", "Channel 20", "Channel 21", "Channel 22", "Channel 23", "Channel 24", "Channel 25", "Channel 26", "Channel 27", "Channel 28"}, // MultiPCM
{"Square"}, // PC Speaker/Pokémon Mini {"Square"}, // PC Speaker/Pokémon Mini
{"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Noise"}, // Virtual Boy/SCC {"Channel 1", "Channel 2", "Channel 3", "Channel 4", "Channel 5", "Noise"}, // Virtual Boy/SCC
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6", "ADPCM-B"}, // YM2610B {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6", "ADPCM-B"}, // YM2610B
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Kick", "Snare", "Tom", "Top", "HiHat"}, // OPLL/OPL/OPL2 drums {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "Kick", "Snare", "Tom", "Top", "HiHat"}, // OPLL/OPL/OPL2 drums
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "FM 7", "FM 8", "FM 9", "FM 10", "FM 11", "FM 12", "FM 13", "FM 14", "FM 15", "Kick", "Snare", "Tom", "Top", "HiHat"}, // OPL3 drums {"4OP 1", "FM 2", "4OP 3", "FM 4", "4OP 5", "FM 6", "4OP 7", "FM 8", "4OP 9", "FM 10", "4OP 11", "FM 12", "FM 13", "FM 14", "FM 15", "Kick", "Snare", "Tom", "Top", "HiHat"}, // OPL3 drums
{"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "4OP 1", "4OP 2", "4OP 3", "4OP 4", "4OP 5", "4OP 6"}, // OPL3 4-op {"FM 1", "FM 2", "FM 3", "FM 4", "FM 5", "FM 6", "4OP 1", "4OP 2", "4OP 3", "4OP 4", "4OP 5", "4OP 6"}, // OPL3 4-op (UNUSED)
{"FM 1", "FM 2", "FM 3", "4OP 1", "4OP 2", "4OP 3", "4OP 4", "4OP 5", "4OP 6", "Kick", "Snare", "Tom", "Top", "HiHat"}, // OPL3 4-op + drums {"FM 1", "FM 2", "FM 3", "4OP 1", "4OP 2", "4OP 3", "4OP 4", "4OP 5", "4OP 6", "Kick", "Snare", "Tom", "Top", "HiHat"}, // OPL3 4-op + drums (UNUSED)
{"PCM 1", "PCM 2", "PCM 3", "PCM 4", "PCM 5", "PCM 6", "PCM 7", "PCM 8", "PCM 9", "PCM 10", "PCM 11", "PCM 12", "PCM 13", "PCM 14", "PCM 15", "PCM 16", "ADPCM 1", "ADPCM 2", "ADPCM 3"}, // QSound {"PCM 1", "PCM 2", "PCM 3", "PCM 4", "PCM 5", "PCM 6", "PCM 7", "PCM 8", "PCM 9", "PCM 10", "PCM 11", "PCM 12", "PCM 13", "PCM 14", "PCM 15", "PCM 16", "ADPCM 1", "ADPCM 2", "ADPCM 3"}, // QSound
{"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6", "ADPCM-B"}, // YM2610B (extended channel 3) {"FM 1", "FM 2", "FM 3 OP1", "FM 3 OP2", "FM 3 OP3", "FM 3 OP4", "FM 4", "FM 5", "FM 6", "PSG 1", "PSG 2", "PSG 3", "ADPCM-A 1", "ADPCM-A 2", "ADPCM-A 3", "ADPCM-A 4", "ADPCM-A 5", "ADPCM-A 6", "ADPCM-B"}, // YM2610B (extended channel 3)
{"Wave", "Wave/PCM", "Wave", "Wave/Noise"}, // Swan {"Wave", "Wave/PCM", "Wave", "Wave/Noise"}, // Swan
@ -974,8 +974,8 @@ const char* chanShortNames[38][32]={
{"F1", "F2", "F3", "F4", "F5", "F6", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"}, // YM2610B {"F1", "F2", "F3", "F4", "F5", "F6", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"}, // YM2610B
{"F1", "F2", "F3", "F4", "F5", "F6", "BD", "SD", "TM", "TP", "HH"}, // OPLL/OPL/OPL2 drums {"F1", "F2", "F3", "F4", "F5", "F6", "BD", "SD", "TM", "TP", "HH"}, // OPLL/OPL/OPL2 drums
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "BD", "SD", "TM", "TP", "HH"}, // OPL3 drums {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "BD", "SD", "TM", "TP", "HH"}, // OPL3 drums
{"F1", "F2", "F3", "F4", "F5", "F6", "Q1", "Q2", "Q3", "Q4", "Q5", "Q6"}, // OPL3 4-op {"F1", "F2", "F3", "F4", "F5", "F6", "Q1", "Q2", "Q3", "Q4", "Q5", "Q6"}, // OPL3 4-op (UNUSED)
{"F1", "F2", "F3", "Q1", "Q2", "Q3", "Q4", "Q5", "Q6", "BD", "SD", "TM", "TP", "HH"}, // OPL3 4-op + drums {"F1", "F2", "F3", "Q1", "Q2", "Q3", "Q4", "Q5", "Q6", "BD", "SD", "TM", "TP", "HH"}, // OPL3 4-op + drums (UNUSED)
{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "A1", "A2", "A3"}, // QSound {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "A1", "A2", "A3"}, // QSound
{"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"}, // YM2610B (extended channel 3) {"F1", "F2", "O1", "O2", "O3", "O4", "F4", "F5", "F6", "S1", "S2", "S3", "P1", "P2", "P3", "P4", "P5", "P6", "B"}, // YM2610B (extended channel 3)
}; };
@ -1008,15 +1008,15 @@ const int chanTypes[41][32]={
{1, 1, 4}, // MMC5 {1, 1, 4}, // MMC5
{0, 0, 0, 1, 1, 1}, // OPN {0, 0, 0, 1, 1, 1}, // OPN
{0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 4}, // PC-98 {0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 2, 2, 2, 4}, // PC-98
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // OPL3 {5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 0, 0, 0, 0, 0, 0}, // OPL3
{4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, // MultiPCM/QSound {4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, // MultiPCM/QSound
{1}, // PC Speaker/Pokémon Mini {1}, // PC Speaker/Pokémon Mini
{3, 3, 3, 3, 3, 2}, // Virtual Boy/SCC {3, 3, 3, 3, 3, 2}, // Virtual Boy/SCC
{0, 0, 0, 0, 0, 0, 1, 1, 1, 4, 4, 4, 4, 4, 4, 4}, // YM2610B {0, 0, 0, 0, 0, 0, 1, 1, 1, 4, 4, 4, 4, 4, 4, 4}, // YM2610B
{0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2}, // OPLL/OPL/OPL2 drums {0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2}, // OPLL/OPL/OPL2 drums
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2}, // OPL3 drums {5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 5, 0, 0, 0, 0, 2, 2, 2, 2, 2}, // OPL3 drums
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // OPL3 4-op {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // OPL3 4-op (UNUSED)
{0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2}, // OPL3 4-op + drums {0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 2}, // OPL3 4-op + drums (UNUSED)
{3, 3, 3, 3}, // Lynx {3, 3, 3, 3}, // Lynx
{0, 0, 5, 5, 5, 5, 0, 0, 0, 1, 1, 1, 4, 4, 4, 4, 4, 4, 4}, // YM2610B (extended channel 3) {0, 0, 5, 5, 5, 5, 0, 0, 0, 1, 1, 1, 4, 4, 4, 4, 4, 4, 4}, // YM2610B (extended channel 3)
{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4}, // VERA {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 4}, // VERA

View file

@ -81,6 +81,10 @@ void FurnaceGUI::drawCompatFlags() {
if (ImGui::IsItemHovered()) { if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("when enabled, a one-tick note cut will be inserted between non-legato/non-portamento notes.\nthis simulates the behavior of some Amiga/SNES music engines."); ImGui::SetTooltip("when enabled, a one-tick note cut will be inserted between non-legato/non-portamento notes.\nthis simulates the behavior of some Amiga/SNES music engines.");
} }
ImGui::Checkbox("Broken speed alternation",&e->song.brokenSpeedSel);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("determines next speed based on whether the row is odd/even instead of alternating between speeds.");
}
ImGui::Text("Loop modality:"); ImGui::Text("Loop modality:");
if (ImGui::RadioButton("Reset channels",e->song.loopModality==0)) { if (ImGui::RadioButton("Reset channels",e->song.loopModality==0)) {

View file

@ -55,10 +55,18 @@ void FurnaceGUI::doAction(int what) {
openFileDialog(GUI_FILE_SAVE); openFileDialog(GUI_FILE_SAVE);
break; break;
case GUI_ACTION_UNDO: case GUI_ACTION_UNDO:
doUndo(); if (curWindow==GUI_WINDOW_SAMPLE_EDIT) {
doUndoSample();
} else {
doUndo();
}
break; break;
case GUI_ACTION_REDO: case GUI_ACTION_REDO:
doRedo(); if (curWindow==GUI_WINDOW_SAMPLE_EDIT) {
doRedoSample();
} else {
doRedo();
}
break; break;
case GUI_ACTION_PLAY_TOGGLE: case GUI_ACTION_PLAY_TOGGLE:
if (e->isPlaying() && !e->isStepping()) { if (e->isPlaying() && !e->isStepping()) {
@ -594,12 +602,14 @@ void FurnaceGUI::doAction(int what) {
if (end-start<1) break; if (end-start<1) break;
sample->prepareUndo(true);
if (sampleClipboard!=NULL) { if (sampleClipboard!=NULL) {
delete[] sampleClipboard; delete[] sampleClipboard;
} }
sampleClipboard=new short[end-start]; sampleClipboard=new short[end-start];
sampleClipboardLen=end-start; sampleClipboardLen=end-start;
memcpy(sampleClipboard,&(sample->data16[start]),end-start); memcpy(sampleClipboard,&(sample->data16[start]),sizeof(short)*(end-start));
e->lockEngine([this,sample,start,end]() { e->lockEngine([this,sample,start,end]() {
sample->strip(start,end); sample->strip(start,end);
@ -625,18 +635,98 @@ void FurnaceGUI::doAction(int what) {
} }
sampleClipboard=new short[end-start]; sampleClipboard=new short[end-start];
sampleClipboardLen=end-start; sampleClipboardLen=end-start;
memcpy(sampleClipboard,&(sample->data16[start]),end-start); memcpy(sampleClipboard,&(sample->data16[start]),sizeof(short)*(end-start));
break; break;
} }
case GUI_ACTION_SAMPLE_PASTE: // TODO!!! case GUI_ACTION_SAMPLE_PASTE: {
if (curSample<0 || curSample>=(int)e->song.sample.size()) break; if (curSample<0 || curSample>=(int)e->song.sample.size()) break;
if (sampleClipboard==NULL || sampleClipboardLen<1) break;
DivSample* sample=e->song.sample[curSample];
sample->prepareUndo(true);
int pos=(sampleSelStart==-1 || sampleSelStart==sampleSelEnd)?sample->samples:sampleSelStart;
e->lockEngine([this,sample,pos]() {
if (!sample->insert(pos,sampleClipboardLen)) {
showError("couldn't paste! make sure your sample is 8 or 16-bit.");
} else {
if (sample->depth==8) {
for (size_t i=0; i<sampleClipboardLen; i++) {
sample->data8[pos+i]=sampleClipboard[i]>>8;
}
} else {
memcpy(&(sample->data16[pos]),sampleClipboard,sizeof(short)*sampleClipboardLen);
}
}
e->renderSamples();
});
sampleSelStart=pos;
sampleSelEnd=pos+sampleClipboardLen;
updateSampleTex=true;
MARK_MODIFIED;
break; break;
case GUI_ACTION_SAMPLE_PASTE_REPLACE: }
case GUI_ACTION_SAMPLE_PASTE_REPLACE: {
if (curSample<0 || curSample>=(int)e->song.sample.size()) break; if (curSample<0 || curSample>=(int)e->song.sample.size()) break;
if (sampleClipboard==NULL || sampleClipboardLen<1) break;
DivSample* sample=e->song.sample[curSample];
sample->prepareUndo(true);
int pos=(sampleSelStart==-1 || sampleSelStart==sampleSelEnd)?0:sampleSelStart;
e->lockEngine([this,sample,pos]() {
if (sample->depth==8) {
for (size_t i=0; i<sampleClipboardLen; i++) {
if (pos+i>=sample->samples) break;
sample->data8[pos+i]=sampleClipboard[i]>>8;
}
} else {
for (size_t i=0; i<sampleClipboardLen; i++) {
if (pos+i>=sample->samples) break;
sample->data16[pos+i]=sampleClipboard[i];
}
}
e->renderSamples();
});
sampleSelStart=pos;
sampleSelEnd=pos+sampleClipboardLen;
if (sampleSelEnd>(int)sample->samples) sampleSelEnd=sample->samples;
updateSampleTex=true;
MARK_MODIFIED;
break; break;
case GUI_ACTION_SAMPLE_PASTE_MIX: }
case GUI_ACTION_SAMPLE_PASTE_MIX: {
if (curSample<0 || curSample>=(int)e->song.sample.size()) break; if (curSample<0 || curSample>=(int)e->song.sample.size()) break;
if (sampleClipboard==NULL || sampleClipboardLen<1) break;
DivSample* sample=e->song.sample[curSample];
sample->prepareUndo(true);
int pos=(sampleSelStart==-1 || sampleSelStart==sampleSelEnd)?0:sampleSelStart;
e->lockEngine([this,sample,pos]() {
if (sample->depth==8) {
for (size_t i=0; i<sampleClipboardLen; i++) {
if (pos+i>=sample->samples) break;
int val=sample->data8[pos+i]+(sampleClipboard[i]>>8);
if (val>127) val=127;
if (val<-128) val=-128;
sample->data8[pos+i]=val;
}
} else {
for (size_t i=0; i<sampleClipboardLen; i++) {
if (pos+i>=sample->samples) break;
int val=sample->data16[pos+i]+sampleClipboard[i];
if (val>32767) val=32767;
if (val<-32768) val=-32768;
sample->data16[pos+i]=val;
}
}
e->renderSamples();
});
sampleSelStart=pos;
sampleSelEnd=pos+sampleClipboardLen;
if (sampleSelEnd>(int)sample->samples) sampleSelEnd=sample->samples;
updateSampleTex=true;
MARK_MODIFIED;
break; break;
}
case GUI_ACTION_SAMPLE_SELECT_ALL: { case GUI_ACTION_SAMPLE_SELECT_ALL: {
if (curSample<0 || curSample>=(int)e->song.sample.size()) break; if (curSample<0 || curSample>=(int)e->song.sample.size()) break;
DivSample* sample=e->song.sample[curSample]; DivSample* sample=e->song.sample[curSample];
@ -660,6 +750,7 @@ void FurnaceGUI::doAction(int what) {
case GUI_ACTION_SAMPLE_NORMALIZE: { case GUI_ACTION_SAMPLE_NORMALIZE: {
if (curSample<0 || curSample>=(int)e->song.sample.size()) break; if (curSample<0 || curSample>=(int)e->song.sample.size()) break;
DivSample* sample=e->song.sample[curSample]; DivSample* sample=e->song.sample[curSample];
sample->prepareUndo(true);
e->lockEngine([this,sample]() { e->lockEngine([this,sample]() {
SAMPLE_OP_BEGIN; SAMPLE_OP_BEGIN;
float maxVal=0.0f; float maxVal=0.0f;
@ -706,6 +797,7 @@ void FurnaceGUI::doAction(int what) {
case GUI_ACTION_SAMPLE_FADE_IN: { case GUI_ACTION_SAMPLE_FADE_IN: {
if (curSample<0 || curSample>=(int)e->song.sample.size()) break; if (curSample<0 || curSample>=(int)e->song.sample.size()) break;
DivSample* sample=e->song.sample[curSample]; DivSample* sample=e->song.sample[curSample];
sample->prepareUndo(true);
e->lockEngine([this,sample]() { e->lockEngine([this,sample]() {
SAMPLE_OP_BEGIN; SAMPLE_OP_BEGIN;
@ -735,6 +827,7 @@ void FurnaceGUI::doAction(int what) {
case GUI_ACTION_SAMPLE_FADE_OUT: { case GUI_ACTION_SAMPLE_FADE_OUT: {
if (curSample<0 || curSample>=(int)e->song.sample.size()) break; if (curSample<0 || curSample>=(int)e->song.sample.size()) break;
DivSample* sample=e->song.sample[curSample]; DivSample* sample=e->song.sample[curSample];
sample->prepareUndo(true);
e->lockEngine([this,sample]() { e->lockEngine([this,sample]() {
SAMPLE_OP_BEGIN; SAMPLE_OP_BEGIN;
@ -761,9 +854,14 @@ void FurnaceGUI::doAction(int what) {
MARK_MODIFIED; MARK_MODIFIED;
break; break;
} }
case GUI_ACTION_SAMPLE_INSERT:
if (curSample<0 || curSample>=(int)e->song.sample.size()) break;
openSampleSilenceOpt=true;
break;
case GUI_ACTION_SAMPLE_SILENCE: { case GUI_ACTION_SAMPLE_SILENCE: {
if (curSample<0 || curSample>=(int)e->song.sample.size()) break; if (curSample<0 || curSample>=(int)e->song.sample.size()) break;
DivSample* sample=e->song.sample[curSample]; DivSample* sample=e->song.sample[curSample];
sample->prepareUndo(true);
e->lockEngine([this,sample]() { e->lockEngine([this,sample]() {
SAMPLE_OP_BEGIN; SAMPLE_OP_BEGIN;
@ -787,6 +885,7 @@ void FurnaceGUI::doAction(int what) {
case GUI_ACTION_SAMPLE_DELETE: { case GUI_ACTION_SAMPLE_DELETE: {
if (curSample<0 || curSample>=(int)e->song.sample.size()) break; if (curSample<0 || curSample>=(int)e->song.sample.size()) break;
DivSample* sample=e->song.sample[curSample]; DivSample* sample=e->song.sample[curSample];
sample->prepareUndo(true);
e->lockEngine([this,sample]() { e->lockEngine([this,sample]() {
SAMPLE_OP_BEGIN; SAMPLE_OP_BEGIN;
@ -803,6 +902,7 @@ void FurnaceGUI::doAction(int what) {
case GUI_ACTION_SAMPLE_TRIM: { case GUI_ACTION_SAMPLE_TRIM: {
if (curSample<0 || curSample>=(int)e->song.sample.size()) break; if (curSample<0 || curSample>=(int)e->song.sample.size()) break;
DivSample* sample=e->song.sample[curSample]; DivSample* sample=e->song.sample[curSample];
sample->prepareUndo(true);
e->lockEngine([this,sample]() { e->lockEngine([this,sample]() {
SAMPLE_OP_BEGIN; SAMPLE_OP_BEGIN;
@ -819,6 +919,7 @@ void FurnaceGUI::doAction(int what) {
case GUI_ACTION_SAMPLE_REVERSE: { case GUI_ACTION_SAMPLE_REVERSE: {
if (curSample<0 || curSample>=(int)e->song.sample.size()) break; if (curSample<0 || curSample>=(int)e->song.sample.size()) break;
DivSample* sample=e->song.sample[curSample]; DivSample* sample=e->song.sample[curSample];
sample->prepareUndo(true);
e->lockEngine([this,sample]() { e->lockEngine([this,sample]() {
SAMPLE_OP_BEGIN; SAMPLE_OP_BEGIN;
@ -850,6 +951,7 @@ void FurnaceGUI::doAction(int what) {
case GUI_ACTION_SAMPLE_INVERT: { case GUI_ACTION_SAMPLE_INVERT: {
if (curSample<0 || curSample>=(int)e->song.sample.size()) break; if (curSample<0 || curSample>=(int)e->song.sample.size()) break;
DivSample* sample=e->song.sample[curSample]; DivSample* sample=e->song.sample[curSample];
sample->prepareUndo(true);
e->lockEngine([this,sample]() { e->lockEngine([this,sample]() {
SAMPLE_OP_BEGIN; SAMPLE_OP_BEGIN;
@ -875,6 +977,7 @@ void FurnaceGUI::doAction(int what) {
case GUI_ACTION_SAMPLE_SIGN: { case GUI_ACTION_SAMPLE_SIGN: {
if (curSample<0 || curSample>=(int)e->song.sample.size()) break; if (curSample<0 || curSample>=(int)e->song.sample.size()) break;
DivSample* sample=e->song.sample[curSample]; DivSample* sample=e->song.sample[curSample];
sample->prepareUndo(true);
e->lockEngine([this,sample]() { e->lockEngine([this,sample]() {
SAMPLE_OP_BEGIN; SAMPLE_OP_BEGIN;

View file

@ -1539,8 +1539,6 @@ void FurnaceGUI::processDrags(int dragX, int dragY) {
fileName+=fallback; \ fileName+=fallback; \
} }
#define BIND_FOR(x) getKeyName(actionKeys[x],true).c_str()
void FurnaceGUI::editOptions(bool topMenu) { void FurnaceGUI::editOptions(bool topMenu) {
char id[4096]; char id[4096];
if (ImGui::MenuItem("cut",BIND_FOR(GUI_ACTION_PAT_CUT))) doCopy(true); if (ImGui::MenuItem("cut",BIND_FOR(GUI_ACTION_PAT_CUT))) doCopy(true);
@ -2165,6 +2163,9 @@ bool FurnaceGUI::loop() {
if (firstFrame) { if (firstFrame) {
firstFrame=false; firstFrame=false;
if (patternOpen) nextWindow=GUI_WINDOW_PATTERN; if (patternOpen) nextWindow=GUI_WINDOW_PATTERN;
#ifdef __APPLE__
SDL_RaiseWindow(sdlWin);
#endif
} }
if (fileDialog->render(ImVec2(600.0f*dpiScale,400.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale))) { if (fileDialog->render(ImVec2(600.0f*dpiScale,400.0f*dpiScale),ImVec2(scrW*dpiScale,scrH*dpiScale))) {
@ -2624,10 +2625,6 @@ bool FurnaceGUI::init() {
firstFrame=true; firstFrame=true;
#ifdef __APPLE__
SDL_RaiseWindow(sdlWin);
#endif
return true; return true;
} }
@ -2839,6 +2836,7 @@ FurnaceGUI::FurnaceGUI():
prevSampleZoom(1.0), prevSampleZoom(1.0),
samplePos(0), samplePos(0),
resizeSize(1024), resizeSize(1024),
silenceSize(1024),
resampleTarget(32000), resampleTarget(32000),
resampleStrat(5), resampleStrat(5),
amplifyVol(100.0), amplifyVol(100.0),
@ -2864,6 +2862,7 @@ FurnaceGUI::FurnaceGUI():
openSampleResizeOpt(false), openSampleResizeOpt(false),
openSampleResampleOpt(false), openSampleResampleOpt(false),
openSampleAmplifyOpt(false), openSampleAmplifyOpt(false),
openSampleSilenceOpt(false),
openSampleFilterOpt(false) { openSampleFilterOpt(false) {
// value keys // value keys
valueKeys[SDLK_0]=0; valueKeys[SDLK_0]=0;

View file

@ -43,6 +43,8 @@
#define TOGGLE_COLOR(x) ((x)?uiColors[GUI_COLOR_TOGGLE_ON]:uiColors[GUI_COLOR_TOGGLE_OFF]) #define TOGGLE_COLOR(x) ((x)?uiColors[GUI_COLOR_TOGGLE_ON]:uiColors[GUI_COLOR_TOGGLE_OFF])
#define BIND_FOR(x) getKeyName(actionKeys[x],true).c_str()
enum FurnaceGUIColors { enum FurnaceGUIColors {
GUI_COLOR_BACKGROUND=0, GUI_COLOR_BACKGROUND=0,
GUI_COLOR_FRAME_BACKGROUND, GUI_COLOR_FRAME_BACKGROUND,
@ -380,6 +382,7 @@ enum FurnaceGUIActions {
GUI_ACTION_SAMPLE_FADE_IN, GUI_ACTION_SAMPLE_FADE_IN,
GUI_ACTION_SAMPLE_FADE_OUT, GUI_ACTION_SAMPLE_FADE_OUT,
GUI_ACTION_SAMPLE_SILENCE, GUI_ACTION_SAMPLE_SILENCE,
GUI_ACTION_SAMPLE_INSERT,
GUI_ACTION_SAMPLE_DELETE, GUI_ACTION_SAMPLE_DELETE,
GUI_ACTION_SAMPLE_TRIM, GUI_ACTION_SAMPLE_TRIM,
GUI_ACTION_SAMPLE_REVERSE, GUI_ACTION_SAMPLE_REVERSE,
@ -796,7 +799,7 @@ class FurnaceGUI {
double sampleZoom; double sampleZoom;
double prevSampleZoom; double prevSampleZoom;
int samplePos; int samplePos;
int resizeSize; int resizeSize, silenceSize;
double resampleTarget; double resampleTarget;
int resampleStrat; int resampleStrat;
float amplifyVol; float amplifyVol;
@ -810,7 +813,7 @@ class FurnaceGUI {
unsigned char sampleFilterPower; unsigned char sampleFilterPower;
short* sampleClipboard; short* sampleClipboard;
size_t sampleClipboardLen; size_t sampleClipboardLen;
bool openSampleResizeOpt, openSampleResampleOpt, openSampleAmplifyOpt, openSampleFilterOpt; bool openSampleResizeOpt, openSampleResampleOpt, openSampleAmplifyOpt, openSampleSilenceOpt, openSampleFilterOpt;
// visualizer // visualizer
float keyHit[DIV_MAX_CHANS]; float keyHit[DIV_MAX_CHANS];
@ -894,6 +897,9 @@ class FurnaceGUI {
void doRedo(); void doRedo();
void editOptions(bool topMenu); void editOptions(bool topMenu);
void doUndoSample();
void doRedoSample();
void play(int row=0); void play(int row=0);
void stop(); void stop();

View file

@ -26,6 +26,7 @@
#include <fmt/printf.h> #include <fmt/printf.h>
#include "guiConst.h" #include "guiConst.h"
#include "sampleUtil.h" #include "sampleUtil.h"
#include "util.h"
void FurnaceGUI::drawSampleEdit() { void FurnaceGUI::drawSampleEdit() {
if (nextWindow==GUI_WINDOW_SAMPLE_EDIT) { if (nextWindow==GUI_WINDOW_SAMPLE_EDIT) {
@ -62,14 +63,12 @@ void FurnaceGUI::drawSampleEdit() {
for (int i=0; i<17; i++) { for (int i=0; i<17; i++) {
if (sampleDepths[i]==NULL) continue; if (sampleDepths[i]==NULL) continue;
if (ImGui::Selectable(sampleDepths[i])) { if (ImGui::Selectable(sampleDepths[i])) {
sample->prepareUndo(true);
sample->depth=i; sample->depth=i;
e->renderSamplesP(); e->renderSamplesP();
updateSampleTex=true; updateSampleTex=true;
MARK_MODIFIED; MARK_MODIFIED;
} }
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("no undo for sample type change operations!");
}
} }
ImGui::EndCombo(); ImGui::EndCombo();
} }
@ -162,6 +161,7 @@ void FurnaceGUI::drawSampleEdit() {
if (resizeSize>16777215) resizeSize=16777215; if (resizeSize>16777215) resizeSize=16777215;
} }
if (ImGui::Button("Resize")) { if (ImGui::Button("Resize")) {
sample->prepareUndo(true);
e->lockEngine([this,sample]() { e->lockEngine([this,sample]() {
if (!sample->resize(resizeSize)) { if (!sample->resize(resizeSize)) {
showError("couldn't resize! make sure your sample is 8 or 16-bit."); showError("couldn't resize! make sure your sample is 8 or 16-bit.");
@ -216,6 +216,7 @@ void FurnaceGUI::drawSampleEdit() {
} }
ImGui::Combo("Filter",&resampleStrat,resampleStrats,6); ImGui::Combo("Filter",&resampleStrat,resampleStrats,6);
if (ImGui::Button("Resample")) { if (ImGui::Button("Resample")) {
sample->prepareUndo(true);
e->lockEngine([this,sample]() { e->lockEngine([this,sample]() {
if (!sample->resample(resampleTarget,resampleStrat)) { if (!sample->resample(resampleTarget,resampleStrat)) {
showError("couldn't resample! make sure your sample is 8 or 16-bit."); showError("couldn't resample! make sure your sample is 8 or 16-bit.");
@ -252,6 +253,7 @@ void FurnaceGUI::drawSampleEdit() {
ImGui::SameLine(); ImGui::SameLine();
ImGui::Text("(%.1fdB)",20.0*log10(amplifyVol/100.0f)); ImGui::Text("(%.1fdB)",20.0*log10(amplifyVol/100.0f));
if (ImGui::Button("Apply")) { if (ImGui::Button("Apply")) {
sample->prepareUndo(true);
e->lockEngine([this,sample]() { e->lockEngine([this,sample]() {
SAMPLE_OP_BEGIN; SAMPLE_OP_BEGIN;
float vol=amplifyVol/100.0f; float vol=amplifyVol/100.0f;
@ -303,6 +305,37 @@ void FurnaceGUI::drawSampleEdit() {
ImGui::SetTooltip("Fade out"); ImGui::SetTooltip("Fade out");
} }
ImGui::SameLine(); ImGui::SameLine();
ImGui::Button(ICON_FA_ADJUST "##SInsertSilence");
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Insert silence");
}
if (openSampleSilenceOpt) {
openSampleSilenceOpt=false;
ImGui::OpenPopup("SSilenceOpt");
}
if (ImGui::BeginPopupContextItem("SSilenceOpt",ImGuiPopupFlags_MouseButtonLeft)) {
if (ImGui::InputInt("Samples",&silenceSize,1,64)) {
if (silenceSize<0) silenceSize=0;
if (silenceSize>16777215) silenceSize=16777215;
}
if (ImGui::Button("Resize")) {
int pos=(sampleSelStart==-1 || sampleSelStart==sampleSelEnd)?sample->samples:sampleSelStart;
sample->prepareUndo(true);
e->lockEngine([this,sample,pos]() {
if (!sample->insert(pos,silenceSize)) {
showError("couldn't insert! make sure your sample is 8 or 16-bit.");
}
e->renderSamples();
});
updateSampleTex=true;
sampleSelStart=pos;
sampleSelEnd=pos+silenceSize;
MARK_MODIFIED;
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
}
ImGui::SameLine();
if (ImGui::Button(ICON_FA_ERASER "##SSilence")) { if (ImGui::Button(ICON_FA_ERASER "##SSilence")) {
doAction(GUI_ACTION_SAMPLE_SILENCE); doAction(GUI_ACTION_SAMPLE_SILENCE);
} }
@ -406,6 +439,7 @@ void FurnaceGUI::drawSampleEdit() {
} }
if (ImGui::Button("Apply")) { if (ImGui::Button("Apply")) {
sample->prepareUndo(true);
e->lockEngine([this,sample]() { e->lockEngine([this,sample]() {
SAMPLE_OP_BEGIN; SAMPLE_OP_BEGIN;
float res=1.0-pow(sampleFilterRes,0.5f); float res=1.0-pow(sampleFilterRes,0.5f);
@ -550,9 +584,22 @@ void FurnaceGUI::drawSampleEdit() {
logE("error while locking sample texture! %s\n",SDL_GetError()); logE("error while locking sample texture! %s\n",SDL_GetError());
} else { } else {
ImU32 bgColor=ImGui::GetColorU32(ImGuiCol_FrameBg); ImU32 bgColor=ImGui::GetColorU32(ImGuiCol_FrameBg);
ImU32 bgColorLoop=ImAlphaBlendColors(bgColor,ImGui::GetColorU32(ImGuiCol_FrameBgHovered,0.5));
ImU32 lineColor=ImGui::GetColorU32(ImGuiCol_PlotLines); ImU32 lineColor=ImGui::GetColorU32(ImGuiCol_PlotLines);
for (int i=0; i<availX*availY; i++) { ImU32 centerLineColor=ImAlphaBlendColors(bgColor,ImGui::GetColorU32(ImGuiCol_PlotLines,0.25));
data[i]=bgColor; for (int i=0; i<availY; i++) {
for (int j=0; j<availX; j++) {
if (sample->loopStart>=0 && sample->loopStart<(int)sample->samples && j-samplePos>sample->loopStart) {
data[i*availX+j]=bgColorLoop;
} else {
data[i*availX+j]=bgColor;
}
}
}
if (availY>0) {
for (int i=availX*(availY>>1); i<availX*(1+(availY>>1)); i++) {
data[i]=centerLineColor;
}
} }
unsigned int xCoarse=samplePos; unsigned int xCoarse=samplePos;
unsigned int xFine=0; unsigned int xFine=0;
@ -621,15 +668,43 @@ void FurnaceGUI::drawSampleEdit() {
sampleDragActive=true; sampleDragActive=true;
sampleSelStart=-1; sampleSelStart=-1;
sampleSelEnd=-1; sampleSelEnd=-1;
if (sampleDragMode) sample->prepareUndo(true);
processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y); processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y);
} }
} }
} }
if (!sampleDragMode && ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
ImGui::OpenPopup("SRightClick");
}
if (ImGui::BeginPopup("SRightClick",ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_AlwaysAutoResize)) {
if (ImGui::MenuItem("cut",BIND_FOR(GUI_ACTION_SAMPLE_CUT))) {
doAction(GUI_ACTION_SAMPLE_CUT);
}
if (ImGui::MenuItem("copy",BIND_FOR(GUI_ACTION_SAMPLE_COPY))) {
doAction(GUI_ACTION_SAMPLE_COPY);
}
if (ImGui::MenuItem("paste",BIND_FOR(GUI_ACTION_SAMPLE_PASTE))) {
doAction(GUI_ACTION_SAMPLE_PASTE);
}
if (ImGui::MenuItem("paste (replace)",BIND_FOR(GUI_ACTION_SAMPLE_PASTE_REPLACE))) {
doAction(GUI_ACTION_SAMPLE_PASTE_REPLACE);
}
if (ImGui::MenuItem("paste (mix)",BIND_FOR(GUI_ACTION_SAMPLE_PASTE_MIX))) {
doAction(GUI_ACTION_SAMPLE_PASTE_MIX);
}
if (ImGui::MenuItem("select all",BIND_FOR(GUI_ACTION_SAMPLE_SELECT_ALL))) {
doAction(GUI_ACTION_SAMPLE_SELECT_ALL);
}
ImGui::EndPopup();
}
String statusBar=sampleDragMode?"Draw":"Select"; String statusBar=sampleDragMode?"Draw":"Select";
bool drawSelection=false; bool drawSelection=false;
if (!sampleDragMode) { if (!sampleDragMode) {
if (sampleSelStart>=0 && sampleSelEnd>=0 && sampleSelStart!=sampleSelEnd) { if (sampleSelStart>=0 && sampleSelEnd>=0) {
int start=sampleSelStart; int start=sampleSelStart;
int end=sampleSelEnd; int end=sampleSelEnd;
if (start>end) { if (start>end) {
@ -669,13 +744,25 @@ void FurnaceGUI::drawSampleEdit() {
} }
ImDrawList* dl=ImGui::GetWindowDrawList(); ImDrawList* dl=ImGui::GetWindowDrawList();
ImVec2 p1=rectMin; ImVec2 p1=rectMin;
p1.x+=start/sampleZoom-samplePos; p1.x+=(start-samplePos)/sampleZoom;
ImVec2 p2=ImVec2(rectMin.x+end/sampleZoom-samplePos,rectMax.y); ImVec2 p2=ImVec2(rectMin.x+(end-samplePos)/sampleZoom,rectMax.y);
ImVec4 boundColor=uiColors[GUI_COLOR_ACCENT_PRIMARY];
ImVec4 selColor=uiColors[GUI_COLOR_ACCENT_SECONDARY]; ImVec4 selColor=uiColors[GUI_COLOR_ACCENT_SECONDARY];
boundColor.w*=0.5;
selColor.w*=0.25; selColor.w*=0.25;
if (p1.x<rectMin.x) p1.x=rectMin.x;
if (p1.x>rectMax.x) p1.x=rectMax.x;
if (p2.x<rectMin.x) p2.x=rectMin.x;
if (p2.x>rectMax.x) p2.x=rectMax.x;
dl->AddRectFilled(p1,p2,ImGui::GetColorU32(selColor)); dl->AddRectFilled(p1,p2,ImGui::GetColorU32(selColor));
dl->AddLine(ImVec2(p1.x,p1.y),ImVec2(p1.x,p2.y),ImGui::GetColorU32(boundColor));
if (start!=end) {
dl->AddLine(ImVec2(p2.x,p1.y),ImVec2(p2.x,p2.y),ImGui::GetColorU32(boundColor));
}
} }
ImS64 scrollV=samplePos; ImS64 scrollV=samplePos;
@ -744,3 +831,27 @@ void FurnaceGUI::drawSampleEdit() {
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_SAMPLE_EDIT; if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_SAMPLE_EDIT;
ImGui::End(); ImGui::End();
} }
void FurnaceGUI::doUndoSample() {
if (!sampleEditOpen) return;
if (curSample<0 || curSample>=(int)e->song.sample.size()) return;
DivSample* sample=e->song.sample[curSample];
e->lockEngine([this,sample]() {
if (sample->undo()==2) {
e->renderSamples();
updateSampleTex=true;
}
});
}
void FurnaceGUI::doRedoSample() {
if (!sampleEditOpen) return;
if (curSample<0 || curSample>=(int)e->song.sample.size()) return;
DivSample* sample=e->song.sample[curSample];
e->lockEngine([this,sample]() {
if (sample->redo()==2) {
e->renderSamples();
updateSampleTex=true;
}
});
}

View file

@ -883,6 +883,7 @@ void FurnaceGUI::drawSettings() {
UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_NORMALIZE,"Normalize"); UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_NORMALIZE,"Normalize");
UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_FADE_IN,"Fade in"); UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_FADE_IN,"Fade in");
UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_FADE_OUT,"Fade out"); UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_FADE_OUT,"Fade out");
UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_INSERT,"Insert silence");
UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_SILENCE,"Apply silence"); UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_SILENCE,"Apply silence");
UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_DELETE,"Delete"); UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_DELETE,"Delete");
UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_TRIM,"Trim"); UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_TRIM,"Trim");
@ -1158,8 +1159,8 @@ void FurnaceGUI::syncSettings() {
LOAD_KEYBIND(GUI_ACTION_SAMPLE_CUT,FURKMOD_CMD|SDLK_x); LOAD_KEYBIND(GUI_ACTION_SAMPLE_CUT,FURKMOD_CMD|SDLK_x);
LOAD_KEYBIND(GUI_ACTION_SAMPLE_COPY,FURKMOD_CMD|SDLK_c); LOAD_KEYBIND(GUI_ACTION_SAMPLE_COPY,FURKMOD_CMD|SDLK_c);
LOAD_KEYBIND(GUI_ACTION_SAMPLE_PASTE,FURKMOD_CMD|SDLK_v); LOAD_KEYBIND(GUI_ACTION_SAMPLE_PASTE,FURKMOD_CMD|SDLK_v);
LOAD_KEYBIND(GUI_ACTION_SAMPLE_PASTE_REPLACE,FURKMOD_CMD|FURKMOD_SHIFT|SDLK_x); LOAD_KEYBIND(GUI_ACTION_SAMPLE_PASTE_REPLACE,FURKMOD_CMD|FURKMOD_SHIFT|SDLK_v);
LOAD_KEYBIND(GUI_ACTION_SAMPLE_PASTE_MIX,FURKMOD_CMD|FURKMOD_ALT|SDLK_x); LOAD_KEYBIND(GUI_ACTION_SAMPLE_PASTE_MIX,FURKMOD_CMD|FURKMOD_ALT|SDLK_v);
LOAD_KEYBIND(GUI_ACTION_SAMPLE_SELECT_ALL,FURKMOD_CMD|SDLK_a); LOAD_KEYBIND(GUI_ACTION_SAMPLE_SELECT_ALL,FURKMOD_CMD|SDLK_a);
LOAD_KEYBIND(GUI_ACTION_SAMPLE_RESIZE,FURKMOD_CMD|SDLK_r); LOAD_KEYBIND(GUI_ACTION_SAMPLE_RESIZE,FURKMOD_CMD|SDLK_r);
LOAD_KEYBIND(GUI_ACTION_SAMPLE_RESAMPLE,FURKMOD_CMD|SDLK_e); LOAD_KEYBIND(GUI_ACTION_SAMPLE_RESAMPLE,FURKMOD_CMD|SDLK_e);
@ -1167,6 +1168,7 @@ void FurnaceGUI::syncSettings() {
LOAD_KEYBIND(GUI_ACTION_SAMPLE_NORMALIZE,FURKMOD_CMD|SDLK_n); LOAD_KEYBIND(GUI_ACTION_SAMPLE_NORMALIZE,FURKMOD_CMD|SDLK_n);
LOAD_KEYBIND(GUI_ACTION_SAMPLE_FADE_IN,FURKMOD_CMD|SDLK_i); LOAD_KEYBIND(GUI_ACTION_SAMPLE_FADE_IN,FURKMOD_CMD|SDLK_i);
LOAD_KEYBIND(GUI_ACTION_SAMPLE_FADE_OUT,FURKMOD_CMD|SDLK_o); LOAD_KEYBIND(GUI_ACTION_SAMPLE_FADE_OUT,FURKMOD_CMD|SDLK_o);
LOAD_KEYBIND(GUI_ACTION_SAMPLE_INSERT,SDLK_INSERT);
LOAD_KEYBIND(GUI_ACTION_SAMPLE_SILENCE,FURKMOD_SHIFT|SDLK_DELETE); LOAD_KEYBIND(GUI_ACTION_SAMPLE_SILENCE,FURKMOD_SHIFT|SDLK_DELETE);
LOAD_KEYBIND(GUI_ACTION_SAMPLE_DELETE,SDLK_DELETE); LOAD_KEYBIND(GUI_ACTION_SAMPLE_DELETE,SDLK_DELETE);
LOAD_KEYBIND(GUI_ACTION_SAMPLE_TRIM,FURKMOD_CMD|SDLK_DELETE); LOAD_KEYBIND(GUI_ACTION_SAMPLE_TRIM,FURKMOD_CMD|SDLK_DELETE);
@ -1479,6 +1481,7 @@ void FurnaceGUI::commitSettings() {
SAVE_KEYBIND(GUI_ACTION_SAMPLE_NORMALIZE); SAVE_KEYBIND(GUI_ACTION_SAMPLE_NORMALIZE);
SAVE_KEYBIND(GUI_ACTION_SAMPLE_FADE_IN); SAVE_KEYBIND(GUI_ACTION_SAMPLE_FADE_IN);
SAVE_KEYBIND(GUI_ACTION_SAMPLE_FADE_OUT); SAVE_KEYBIND(GUI_ACTION_SAMPLE_FADE_OUT);
SAVE_KEYBIND(GUI_ACTION_SAMPLE_INSERT);
SAVE_KEYBIND(GUI_ACTION_SAMPLE_SILENCE); SAVE_KEYBIND(GUI_ACTION_SAMPLE_SILENCE);
SAVE_KEYBIND(GUI_ACTION_SAMPLE_DELETE); SAVE_KEYBIND(GUI_ACTION_SAMPLE_DELETE);
SAVE_KEYBIND(GUI_ACTION_SAMPLE_TRIM); SAVE_KEYBIND(GUI_ACTION_SAMPLE_TRIM);