mirror of
https://github.com/tildearrow/furnace.git
synced 2024-10-31 18:12:40 +00:00
GUI: collapse/expand pattern/song
This commit is contained in:
parent
f3a71c6eba
commit
24487936de
6 changed files with 315 additions and 59 deletions
|
@ -23,11 +23,7 @@
|
||||||
static DivPattern emptyPat;
|
static DivPattern emptyPat;
|
||||||
|
|
||||||
DivPattern::DivPattern() {
|
DivPattern::DivPattern() {
|
||||||
memset(data,-1,DIV_MAX_ROWS*DIV_MAX_COLS*sizeof(short));
|
clear();
|
||||||
for (int i=0; i<DIV_MAX_ROWS; i++) {
|
|
||||||
data[i][0]=0;
|
|
||||||
data[i][1]=0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DivPattern* DivChannelData::getPattern(int index, bool create) {
|
DivPattern* DivChannelData::getPattern(int index, bool create) {
|
||||||
|
@ -93,6 +89,14 @@ void DivPattern::copyOn(DivPattern* dest) {
|
||||||
memcpy(dest->data,data,sizeof(data));
|
memcpy(dest->data,data,sizeof(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DivPattern::clear() {
|
||||||
|
memset(data,-1,DIV_MAX_ROWS*DIV_MAX_COLS*sizeof(short));
|
||||||
|
for (int i=0; i<DIV_MAX_ROWS; i++) {
|
||||||
|
data[i][0]=0;
|
||||||
|
data[i][1]=0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
DivChannelData::DivChannelData():
|
DivChannelData::DivChannelData():
|
||||||
effectCols(1) {
|
effectCols(1) {
|
||||||
memset(data,0,DIV_MAX_PATTERNS*sizeof(void*));
|
memset(data,0,DIV_MAX_PATTERNS*sizeof(void*));
|
||||||
|
|
|
@ -24,6 +24,11 @@ struct DivPattern {
|
||||||
String name;
|
String name;
|
||||||
short data[DIV_MAX_ROWS][DIV_MAX_COLS];
|
short data[DIV_MAX_ROWS][DIV_MAX_COLS];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* clear the pattern.
|
||||||
|
*/
|
||||||
|
void clear();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* copy this pattern to another.
|
* copy this pattern to another.
|
||||||
* @param dest the destination pattern.
|
* @param dest the destination pattern.
|
||||||
|
|
|
@ -561,18 +561,32 @@ void FurnaceGUI::doAction(int what) {
|
||||||
doFlip();
|
doFlip();
|
||||||
break;
|
break;
|
||||||
case GUI_ACTION_PAT_COLLAPSE_ROWS:
|
case GUI_ACTION_PAT_COLLAPSE_ROWS:
|
||||||
doCollapse(2);
|
doCollapse(2,selStart,selEnd);
|
||||||
break;
|
break;
|
||||||
case GUI_ACTION_PAT_EXPAND_ROWS:
|
case GUI_ACTION_PAT_EXPAND_ROWS:
|
||||||
doExpand(2);
|
doExpand(2,selStart,selEnd);
|
||||||
break;
|
break;
|
||||||
case GUI_ACTION_PAT_COLLAPSE_PAT: // TODO
|
case GUI_ACTION_PAT_COLLAPSE_PAT: {
|
||||||
|
SelectionPoint selEndPat;
|
||||||
|
selEndPat.xCoarse=e->getTotalChannelCount()-1;
|
||||||
|
selEndPat.xFine=2+e->curPat[selEndPat.xCoarse].effectCols*2;
|
||||||
|
selEndPat.y=e->curSubSong->patLen-1;
|
||||||
|
doCollapse(2,SelectionPoint(0,0,0),selEndPat);
|
||||||
break;
|
break;
|
||||||
case GUI_ACTION_PAT_EXPAND_PAT: // TODO
|
}
|
||||||
|
case GUI_ACTION_PAT_EXPAND_PAT: {
|
||||||
|
SelectionPoint selEndPat;
|
||||||
|
selEndPat.xCoarse=e->getTotalChannelCount()-1;
|
||||||
|
selEndPat.xFine=2+e->curPat[selEndPat.xCoarse].effectCols*2;
|
||||||
|
selEndPat.y=e->curSubSong->patLen-1;
|
||||||
|
doExpand(2,SelectionPoint(0,0,0),selEndPat);
|
||||||
break;
|
break;
|
||||||
case GUI_ACTION_PAT_COLLAPSE_SONG: // TODO
|
}
|
||||||
|
case GUI_ACTION_PAT_COLLAPSE_SONG:
|
||||||
|
doCollapseSong(2);
|
||||||
break;
|
break;
|
||||||
case GUI_ACTION_PAT_EXPAND_SONG: // TODO
|
case GUI_ACTION_PAT_EXPAND_SONG:
|
||||||
|
doExpandSong(2);
|
||||||
break;
|
break;
|
||||||
case GUI_ACTION_PAT_LATCH: // TODO
|
case GUI_ACTION_PAT_LATCH: // TODO
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -67,6 +67,9 @@ void FurnaceGUI::prepareUndo(ActionType action) {
|
||||||
e->curPat[i].getPattern(e->curOrders->ord[i][curOrder],false)->copyOn(oldPat[i]);
|
e->curPat[i].getPattern(e->curOrders->ord[i][curOrder],false)->copyOn(oldPat[i]);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case GUI_UNDO_PATTERN_COLLAPSE_SONG:
|
||||||
|
case GUI_UNDO_PATTERN_EXPAND_SONG: // TODO
|
||||||
|
break;
|
||||||
case GUI_UNDO_REPLACE: // this is handled by doReplace()
|
case GUI_UNDO_REPLACE: // this is handled by doReplace()
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -130,6 +133,9 @@ void FurnaceGUI::makeUndo(ActionType action) {
|
||||||
doPush=true;
|
doPush=true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case GUI_UNDO_PATTERN_COLLAPSE_SONG:
|
||||||
|
case GUI_UNDO_PATTERN_EXPAND_SONG: // TODO
|
||||||
|
break;
|
||||||
case GUI_UNDO_REPLACE: // this is handled by doReplace()
|
case GUI_UNDO_REPLACE: // this is handled by doReplace()
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -839,50 +845,56 @@ void FurnaceGUI::doFlip() {
|
||||||
makeUndo(GUI_UNDO_PATTERN_FLIP);
|
makeUndo(GUI_UNDO_PATTERN_FLIP);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FurnaceGUI::doCollapse(int divider) {
|
void FurnaceGUI::doCollapse(int divider, const SelectionPoint& sStart, const SelectionPoint& sEnd) {
|
||||||
|
if (divider<2) return;
|
||||||
|
if (e->curSubSong->patLen<divider) {
|
||||||
|
showError("can't collapse any further!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
finishSelection();
|
finishSelection();
|
||||||
prepareUndo(GUI_UNDO_PATTERN_COLLAPSE);
|
prepareUndo(GUI_UNDO_PATTERN_COLLAPSE);
|
||||||
|
|
||||||
DivPattern patBuffer;
|
DivPattern patBuffer;
|
||||||
int iCoarse=selStart.xCoarse;
|
int iCoarse=sStart.xCoarse;
|
||||||
int iFine=selStart.xFine;
|
int iFine=sStart.xFine;
|
||||||
for (; iCoarse<=selEnd.xCoarse; iCoarse++) {
|
for (; iCoarse<=sEnd.xCoarse; iCoarse++) {
|
||||||
if (!e->curSubSong->chanShow[iCoarse]) continue;
|
if (!e->curSubSong->chanShow[iCoarse]) continue;
|
||||||
DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true);
|
DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true);
|
||||||
for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
|
for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<sEnd.xCoarse || iFine<=sEnd.xFine); iFine++) {
|
||||||
maskOut(opMaskCollapseExpand,iFine);
|
maskOut(opMaskCollapseExpand,iFine);
|
||||||
for (int j=selStart.y; j<=selEnd.y; j++) {
|
for (int j=sStart.y; j<=sEnd.y; j++) {
|
||||||
if (iFine==0) {
|
if (iFine==0) {
|
||||||
patBuffer.data[j][0]=pat->data[j][0];
|
patBuffer.data[j][0]=pat->data[j][0];
|
||||||
}
|
}
|
||||||
patBuffer.data[j][iFine+1]=pat->data[j][iFine+1];
|
patBuffer.data[j][iFine+1]=pat->data[j][iFine+1];
|
||||||
}
|
}
|
||||||
for (int j=0; j<=selEnd.y-selStart.y; j++) {
|
for (int j=0; j<=sEnd.y-sStart.y; j++) {
|
||||||
if (j*divider>=selEnd.y-selStart.y) {
|
if (j*divider>=sEnd.y-sStart.y) {
|
||||||
if (iFine==0) {
|
if (iFine==0) {
|
||||||
pat->data[j+selStart.y][0]=0;
|
pat->data[j+sStart.y][0]=0;
|
||||||
pat->data[j+selStart.y][1]=0;
|
pat->data[j+sStart.y][1]=0;
|
||||||
} else {
|
} else {
|
||||||
pat->data[j+selStart.y][iFine+1]=-1;
|
pat->data[j+sStart.y][iFine+1]=-1;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (iFine==0) {
|
if (iFine==0) {
|
||||||
pat->data[j+selStart.y][0]=patBuffer.data[j*divider+selStart.y][0];
|
pat->data[j+sStart.y][0]=patBuffer.data[j*divider+sStart.y][0];
|
||||||
}
|
}
|
||||||
pat->data[j+selStart.y][iFine+1]=patBuffer.data[j*divider+selStart.y][iFine+1];
|
pat->data[j+sStart.y][iFine+1]=patBuffer.data[j*divider+sStart.y][iFine+1];
|
||||||
|
|
||||||
if (iFine==0) {
|
if (iFine==0) {
|
||||||
for (int k=1; k<divider; k++) {
|
for (int k=1; k<divider; k++) {
|
||||||
if ((j*divider+k)>=selEnd.y-selStart.y) break;
|
if ((j*divider+k)>=sEnd.y-sStart.y) break;
|
||||||
if (!(pat->data[j+selStart.y][0]==0 && pat->data[j+selStart.y][1]==0)) break;
|
if (!(pat->data[j+sStart.y][0]==0 && pat->data[j+sStart.y][1]==0)) break;
|
||||||
pat->data[j+selStart.y][0]=patBuffer.data[j*divider+selStart.y+k][0];
|
pat->data[j+sStart.y][0]=patBuffer.data[j*divider+sStart.y+k][0];
|
||||||
pat->data[j+selStart.y][1]=patBuffer.data[j*divider+selStart.y+k][1];
|
pat->data[j+sStart.y][1]=patBuffer.data[j*divider+sStart.y+k][1];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (int k=1; k<divider; k++) {
|
for (int k=1; k<divider; k++) {
|
||||||
if ((j*divider+k)>=selEnd.y-selStart.y) break;
|
if ((j*divider+k)>=sEnd.y-sStart.y) break;
|
||||||
if (pat->data[j+selStart.y][iFine+1]!=-1) break;
|
if (pat->data[j+sStart.y][iFine+1]!=-1) break;
|
||||||
pat->data[j+selStart.y][iFine+1]=patBuffer.data[j*divider+selStart.y+k][iFine+1];
|
pat->data[j+sStart.y][iFine+1]=patBuffer.data[j*divider+sStart.y+k][iFine+1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -894,41 +906,41 @@ void FurnaceGUI::doCollapse(int divider) {
|
||||||
makeUndo(GUI_UNDO_PATTERN_COLLAPSE);
|
makeUndo(GUI_UNDO_PATTERN_COLLAPSE);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FurnaceGUI::doExpand(int multiplier) {
|
void FurnaceGUI::doExpand(int multiplier, const SelectionPoint& sStart, const SelectionPoint& sEnd) {
|
||||||
if (multiplier<1) return;
|
if (multiplier<2) return;
|
||||||
|
|
||||||
finishSelection();
|
finishSelection();
|
||||||
prepareUndo(GUI_UNDO_PATTERN_EXPAND);
|
prepareUndo(GUI_UNDO_PATTERN_EXPAND);
|
||||||
|
|
||||||
DivPattern patBuffer;
|
DivPattern patBuffer;
|
||||||
int iCoarse=selStart.xCoarse;
|
int iCoarse=sStart.xCoarse;
|
||||||
int iFine=selStart.xFine;
|
int iFine=sStart.xFine;
|
||||||
for (; iCoarse<=selEnd.xCoarse; iCoarse++) {
|
for (; iCoarse<=sEnd.xCoarse; iCoarse++) {
|
||||||
if (!e->curSubSong->chanShow[iCoarse]) continue;
|
if (!e->curSubSong->chanShow[iCoarse]) continue;
|
||||||
DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true);
|
DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true);
|
||||||
for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
|
for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<sEnd.xCoarse || iFine<=sEnd.xFine); iFine++) {
|
||||||
maskOut(opMaskCollapseExpand,iFine);
|
maskOut(opMaskCollapseExpand,iFine);
|
||||||
for (int j=selStart.y; j<=selEnd.y; j++) {
|
for (int j=sStart.y; j<=sEnd.y; j++) {
|
||||||
if (iFine==0) {
|
if (iFine==0) {
|
||||||
patBuffer.data[j][0]=pat->data[j][0];
|
patBuffer.data[j][0]=pat->data[j][0];
|
||||||
}
|
}
|
||||||
patBuffer.data[j][iFine+1]=pat->data[j][iFine+1];
|
patBuffer.data[j][iFine+1]=pat->data[j][iFine+1];
|
||||||
}
|
}
|
||||||
for (int j=0; j<=(selEnd.y-selStart.y)*multiplier; j++) {
|
for (int j=0; j<=(sEnd.y-sStart.y)*multiplier; j++) {
|
||||||
if ((j+selStart.y)>=e->curSubSong->patLen) break;
|
if ((j+sStart.y)>=e->curSubSong->patLen) break;
|
||||||
if ((j%multiplier)!=0) {
|
if ((j%multiplier)!=0) {
|
||||||
if (iFine==0) {
|
if (iFine==0) {
|
||||||
pat->data[j+selStart.y][0]=0;
|
pat->data[j+sStart.y][0]=0;
|
||||||
pat->data[j+selStart.y][1]=0;
|
pat->data[j+sStart.y][1]=0;
|
||||||
} else {
|
} else {
|
||||||
pat->data[j+selStart.y][iFine+1]=-1;
|
pat->data[j+sStart.y][iFine+1]=-1;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (iFine==0) {
|
if (iFine==0) {
|
||||||
pat->data[j+selStart.y][0]=patBuffer.data[j/multiplier+selStart.y][0];
|
pat->data[j+sStart.y][0]=patBuffer.data[j/multiplier+sStart.y][0];
|
||||||
}
|
}
|
||||||
pat->data[j+selStart.y][iFine+1]=patBuffer.data[j/multiplier+selStart.y][iFine+1];
|
pat->data[j+sStart.y][iFine+1]=patBuffer.data[j/multiplier+sStart.y][iFine+1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
iFine=0;
|
iFine=0;
|
||||||
|
@ -937,6 +949,162 @@ void FurnaceGUI::doExpand(int multiplier) {
|
||||||
makeUndo(GUI_UNDO_PATTERN_EXPAND);
|
makeUndo(GUI_UNDO_PATTERN_EXPAND);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FurnaceGUI::doCollapseSong(int divider) {
|
||||||
|
if (divider<2) return;
|
||||||
|
finishSelection();
|
||||||
|
|
||||||
|
UndoStep us;
|
||||||
|
us.type=GUI_UNDO_PATTERN_COLLAPSE_SONG;
|
||||||
|
|
||||||
|
DivPattern patCopy;
|
||||||
|
|
||||||
|
size_t subSong=e->getCurrentSubSong();
|
||||||
|
for (int i=0; i<e->getTotalChannelCount(); i++) {
|
||||||
|
for (int j=0; j<DIV_MAX_PATTERNS; j++) {
|
||||||
|
if (e->curPat[i].data[j]==NULL) continue;
|
||||||
|
|
||||||
|
DivPattern* pat=e->curPat[i].getPattern(j,true);
|
||||||
|
pat->copyOn(&patCopy);
|
||||||
|
pat->clear();
|
||||||
|
for (int k=0; k<DIV_MAX_ROWS; k++) {
|
||||||
|
for (int l=0; l<DIV_MAX_COLS; l++) {
|
||||||
|
if (l==0) {
|
||||||
|
if (!(pat->data[k/divider][0]==0 && pat->data[k/divider][1]==0)) continue;
|
||||||
|
} else {
|
||||||
|
if (pat->data[k/divider][l+1]!=-1) continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (l==0) {
|
||||||
|
pat->data[k/divider][l]=patCopy.data[k][l];
|
||||||
|
}
|
||||||
|
pat->data[k/divider][l+1]=patCopy.data[k][l+1];
|
||||||
|
|
||||||
|
if (l>3 && !(l&1)) { // scale effects as needed
|
||||||
|
switch (pat->data[k/divider][l]) {
|
||||||
|
case 0x0d:
|
||||||
|
pat->data[k/divider][l+1]/=divider;
|
||||||
|
break;
|
||||||
|
case 0x0f:
|
||||||
|
pat->data[k/divider][l+1]=CLAMP(pat->data[k/divider][l+1]*divider,1,255);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// put undo
|
||||||
|
for (int k=0; k<DIV_MAX_ROWS; k++) {
|
||||||
|
for (int l=0; l<DIV_MAX_COLS; l++) {
|
||||||
|
if (pat->data[k][l]!=patCopy.data[k][l]) {
|
||||||
|
us.pat.push_back(UndoPatternData(subSong,i,j,k,l,patCopy.data[k][l],pat->data[k][l]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// magic
|
||||||
|
unsigned char* subSongInfoCopy=new unsigned char[1024];
|
||||||
|
memcpy(subSongInfoCopy,e->curSubSong,1024);
|
||||||
|
e->curSubSong->patLen/=divider;
|
||||||
|
for (int i=0; i<e->curSubSong->speeds.len; i++) {
|
||||||
|
e->curSubSong->speeds.val[i]=CLAMP(e->curSubSong->speeds.val[i]*divider,1,255);
|
||||||
|
}
|
||||||
|
unsigned char* newSubSongInfo=(unsigned char*)e->curSubSong;
|
||||||
|
for (int i=0; i<1024; i++) {
|
||||||
|
if (subSongInfoCopy[i]!=newSubSongInfo[i]) {
|
||||||
|
us.other.push_back(UndoOtherData(GUI_UNDO_TARGET_SUBSONG,subSong,i,subSongInfoCopy[i],newSubSongInfo[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!us.pat.empty()) {
|
||||||
|
undoHist.push_back(us);
|
||||||
|
redoHist.clear();
|
||||||
|
if (undoHist.size()>settings.maxUndoSteps) undoHist.pop_front();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e->isPlaying()) e->play();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FurnaceGUI::doExpandSong(int multiplier) {
|
||||||
|
if (multiplier<2) return;
|
||||||
|
if (e->curSubSong->patLen>(256/multiplier)) {
|
||||||
|
showError("can't expand any further!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
finishSelection();
|
||||||
|
|
||||||
|
UndoStep us;
|
||||||
|
us.type=GUI_UNDO_PATTERN_EXPAND_SONG;
|
||||||
|
|
||||||
|
DivPattern patCopy;
|
||||||
|
|
||||||
|
size_t subSong=e->getCurrentSubSong();
|
||||||
|
for (int i=0; i<e->getTotalChannelCount(); i++) {
|
||||||
|
for (int j=0; j<DIV_MAX_PATTERNS; j++) {
|
||||||
|
if (e->curPat[i].data[j]==NULL) continue;
|
||||||
|
|
||||||
|
DivPattern* pat=e->curPat[i].getPattern(j,true);
|
||||||
|
pat->copyOn(&patCopy);
|
||||||
|
pat->clear();
|
||||||
|
for (int k=0; k<(256/multiplier); k++) {
|
||||||
|
for (int l=0; l<DIV_MAX_COLS; l++) {
|
||||||
|
if (l==0) {
|
||||||
|
if (!(pat->data[k*multiplier][0]==0 && pat->data[k*multiplier][1]==0)) continue;
|
||||||
|
} else {
|
||||||
|
if (pat->data[k*multiplier][l+1]!=-1) continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (l==0) {
|
||||||
|
pat->data[k*multiplier][l]=patCopy.data[k][l];
|
||||||
|
}
|
||||||
|
pat->data[k*multiplier][l+1]=patCopy.data[k][l+1];
|
||||||
|
|
||||||
|
if (l>3 && !(l&1)) { // scale effects as needed
|
||||||
|
switch (pat->data[k*multiplier][l]) {
|
||||||
|
case 0x0d:
|
||||||
|
pat->data[k*multiplier][l+1]/=multiplier;
|
||||||
|
break;
|
||||||
|
case 0x0f:
|
||||||
|
pat->data[k*multiplier][l+1]=CLAMP(pat->data[k*multiplier][l+1]/multiplier,1,255);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// put undo
|
||||||
|
for (int k=0; k<DIV_MAX_ROWS; k++) {
|
||||||
|
for (int l=0; l<DIV_MAX_COLS; l++) {
|
||||||
|
if (pat->data[k][l]!=patCopy.data[k][l]) {
|
||||||
|
us.pat.push_back(UndoPatternData(subSong,i,j,k,l,patCopy.data[k][l],pat->data[k][l]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// magic
|
||||||
|
unsigned char* subSongInfoCopy=new unsigned char[1024];
|
||||||
|
memcpy(subSongInfoCopy,e->curSubSong,1024);
|
||||||
|
e->curSubSong->patLen*=multiplier;
|
||||||
|
for (int i=0; i<e->curSubSong->speeds.len; i++) {
|
||||||
|
e->curSubSong->speeds.val[i]=CLAMP(e->curSubSong->speeds.val[i]/multiplier,1,255);
|
||||||
|
}
|
||||||
|
unsigned char* newSubSongInfo=(unsigned char*)e->curSubSong;
|
||||||
|
for (int i=0; i<1024; i++) {
|
||||||
|
if (subSongInfoCopy[i]!=newSubSongInfo[i]) {
|
||||||
|
us.other.push_back(UndoOtherData(GUI_UNDO_TARGET_SUBSONG,subSong,i,subSongInfoCopy[i],newSubSongInfo[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!us.pat.empty()) {
|
||||||
|
undoHist.push_back(us);
|
||||||
|
redoHist.clear();
|
||||||
|
if (undoHist.size()>settings.maxUndoSteps) undoHist.pop_front();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e->isPlaying()) e->play();
|
||||||
|
}
|
||||||
|
|
||||||
void FurnaceGUI::doDrag() {
|
void FurnaceGUI::doDrag() {
|
||||||
int len=dragEnd.xCoarse-dragStart.xCoarse+1;
|
int len=dragEnd.xCoarse-dragStart.xCoarse+1;
|
||||||
|
|
||||||
|
@ -985,6 +1153,8 @@ void FurnaceGUI::doUndo() {
|
||||||
case GUI_UNDO_PATTERN_FLIP:
|
case GUI_UNDO_PATTERN_FLIP:
|
||||||
case GUI_UNDO_PATTERN_COLLAPSE:
|
case GUI_UNDO_PATTERN_COLLAPSE:
|
||||||
case GUI_UNDO_PATTERN_EXPAND:
|
case GUI_UNDO_PATTERN_EXPAND:
|
||||||
|
case GUI_UNDO_PATTERN_COLLAPSE_SONG:
|
||||||
|
case GUI_UNDO_PATTERN_EXPAND_SONG:
|
||||||
case GUI_UNDO_PATTERN_DRAG:
|
case GUI_UNDO_PATTERN_DRAG:
|
||||||
case GUI_UNDO_REPLACE:
|
case GUI_UNDO_REPLACE:
|
||||||
for (UndoPatternData& i: us.pat) {
|
for (UndoPatternData& i: us.pat) {
|
||||||
|
@ -1005,6 +1175,22 @@ void FurnaceGUI::doUndo() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool shallReplay=false;
|
||||||
|
for (UndoOtherData& i: us.other) {
|
||||||
|
switch (i.target) {
|
||||||
|
case GUI_UNDO_TARGET_SONG:
|
||||||
|
((unsigned char*)(&e->song))[i.off]=i.oldVal;
|
||||||
|
shallReplay=true;
|
||||||
|
break;
|
||||||
|
case GUI_UNDO_TARGET_SUBSONG:
|
||||||
|
if (i.subtarget<0 || i.subtarget>=(int)e->song.subsong.size()) break;
|
||||||
|
((unsigned char*)(e->song.subsong[i.subtarget]))[i.off]=i.oldVal;
|
||||||
|
shallReplay=true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (shallReplay && e->isPlaying()) play();
|
||||||
|
|
||||||
if (curOrder>=e->curSubSong->ordersLen) {
|
if (curOrder>=e->curSubSong->ordersLen) {
|
||||||
curOrder=e->curSubSong->ordersLen-1;
|
curOrder=e->curSubSong->ordersLen-1;
|
||||||
oldOrder=curOrder;
|
oldOrder=curOrder;
|
||||||
|
@ -1045,6 +1231,8 @@ void FurnaceGUI::doRedo() {
|
||||||
case GUI_UNDO_PATTERN_COLLAPSE:
|
case GUI_UNDO_PATTERN_COLLAPSE:
|
||||||
case GUI_UNDO_PATTERN_EXPAND:
|
case GUI_UNDO_PATTERN_EXPAND:
|
||||||
case GUI_UNDO_PATTERN_DRAG:
|
case GUI_UNDO_PATTERN_DRAG:
|
||||||
|
case GUI_UNDO_PATTERN_COLLAPSE_SONG:
|
||||||
|
case GUI_UNDO_PATTERN_EXPAND_SONG:
|
||||||
case GUI_UNDO_REPLACE:
|
case GUI_UNDO_REPLACE:
|
||||||
for (UndoPatternData& i: us.pat) {
|
for (UndoPatternData& i: us.pat) {
|
||||||
e->changeSongP(i.subSong);
|
e->changeSongP(i.subSong);
|
||||||
|
@ -1065,6 +1253,22 @@ void FurnaceGUI::doRedo() {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool shallReplay=false;
|
||||||
|
for (UndoOtherData& i: us.other) {
|
||||||
|
switch (i.target) {
|
||||||
|
case GUI_UNDO_TARGET_SONG:
|
||||||
|
((unsigned char*)(&e->song))[i.off]=i.newVal;
|
||||||
|
shallReplay=true;
|
||||||
|
break;
|
||||||
|
case GUI_UNDO_TARGET_SUBSONG:
|
||||||
|
if (i.subtarget<0 || i.subtarget>=(int)e->song.subsong.size()) break;
|
||||||
|
((unsigned char*)(e->song.subsong[i.subtarget]))[i.off]=i.newVal;
|
||||||
|
shallReplay=true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (shallReplay && e->isPlaying()) play();
|
||||||
|
|
||||||
if (curOrder>=e->curSubSong->ordersLen) {
|
if (curOrder>=e->curSubSong->ordersLen) {
|
||||||
curOrder=e->curSubSong->ordersLen-1;
|
curOrder=e->curSubSong->ordersLen-1;
|
||||||
oldOrder=curOrder;
|
oldOrder=curOrder;
|
||||||
|
|
|
@ -2842,9 +2842,23 @@ void FurnaceGUI::editOptions(bool topMenu) {
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
|
|
||||||
if (ImGui::MenuItem("flip selection",BIND_FOR(GUI_ACTION_PAT_FLIP_SELECTION))) doFlip();
|
if (ImGui::MenuItem("flip selection",BIND_FOR(GUI_ACTION_PAT_FLIP_SELECTION))) doFlip();
|
||||||
if (ImGui::MenuItem("collapse",BIND_FOR(GUI_ACTION_PAT_COLLAPSE_ROWS))) doCollapse(2);
|
if (ImGui::MenuItem("collapse",BIND_FOR(GUI_ACTION_PAT_COLLAPSE_ROWS))) doCollapse(2,selStart,selEnd);
|
||||||
if (ImGui::MenuItem("expand",BIND_FOR(GUI_ACTION_PAT_EXPAND_ROWS))) doExpand(2);
|
if (ImGui::MenuItem("expand",BIND_FOR(GUI_ACTION_PAT_EXPAND_ROWS))) doExpand(2,selStart,selEnd);
|
||||||
|
|
||||||
|
if (topMenu) {
|
||||||
|
ImGui::Separator();
|
||||||
|
if (ImGui::MenuItem("collapse pattern",BIND_FOR(GUI_ACTION_PAT_COLLAPSE_PAT))) doAction(GUI_ACTION_PAT_COLLAPSE_PAT);
|
||||||
|
if (ImGui::MenuItem("expand pattern",BIND_FOR(GUI_ACTION_PAT_EXPAND_PAT))) doAction(GUI_ACTION_PAT_EXPAND_PAT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (topMenu) {
|
||||||
|
ImGui::Separator();
|
||||||
|
if (ImGui::MenuItem("collapse song",BIND_FOR(GUI_ACTION_PAT_COLLAPSE_SONG))) doAction(GUI_ACTION_PAT_COLLAPSE_SONG);
|
||||||
|
if (ImGui::MenuItem("expand song",BIND_FOR(GUI_ACTION_PAT_EXPAND_SONG))) doAction(GUI_ACTION_PAT_EXPAND_SONG);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!basicMode) {
|
||||||
if (topMenu) {
|
if (topMenu) {
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
if (ImGui::MenuItem("find/replace",BIND_FOR(GUI_ACTION_WINDOW_FIND),findOpen)) {
|
if (ImGui::MenuItem("find/replace",BIND_FOR(GUI_ACTION_WINDOW_FIND),findOpen)) {
|
||||||
|
@ -2856,16 +2870,6 @@ void FurnaceGUI::editOptions(bool topMenu) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*if (topMenu) {
|
|
||||||
ImGui::Separator();
|
|
||||||
ImGui::MenuItem("collapse pattern",BIND_FOR(GUI_ACTION_PAT_COLLAPSE_PAT));
|
|
||||||
ImGui::MenuItem("expand pattern",BIND_FOR(GUI_ACTION_PAT_EXPAND_PAT));
|
|
||||||
|
|
||||||
ImGui::Separator();
|
|
||||||
ImGui::MenuItem("collapse song",BIND_FOR(GUI_ACTION_PAT_COLLAPSE_SONG));
|
|
||||||
ImGui::MenuItem("expand song",BIND_FOR(GUI_ACTION_PAT_EXPAND_SONG));
|
|
||||||
}*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void FurnaceGUI::toggleMobileUI(bool enable, bool force) {
|
void FurnaceGUI::toggleMobileUI(bool enable, bool force) {
|
||||||
|
|
|
@ -721,6 +721,8 @@ enum NoteCtrl {
|
||||||
struct SelectionPoint {
|
struct SelectionPoint {
|
||||||
int xCoarse, xFine;
|
int xCoarse, xFine;
|
||||||
int y;
|
int y;
|
||||||
|
SelectionPoint(int xc, int xf, int yp):
|
||||||
|
xCoarse(xc), xFine(xf), y(yp) {}
|
||||||
SelectionPoint():
|
SelectionPoint():
|
||||||
xCoarse(0), xFine(0), y(0) {}
|
xCoarse(0), xFine(0), y(0) {}
|
||||||
};
|
};
|
||||||
|
@ -742,10 +744,17 @@ enum ActionType {
|
||||||
GUI_UNDO_PATTERN_FLIP,
|
GUI_UNDO_PATTERN_FLIP,
|
||||||
GUI_UNDO_PATTERN_COLLAPSE,
|
GUI_UNDO_PATTERN_COLLAPSE,
|
||||||
GUI_UNDO_PATTERN_EXPAND,
|
GUI_UNDO_PATTERN_EXPAND,
|
||||||
|
GUI_UNDO_PATTERN_COLLAPSE_SONG,
|
||||||
|
GUI_UNDO_PATTERN_EXPAND_SONG,
|
||||||
GUI_UNDO_PATTERN_DRAG,
|
GUI_UNDO_PATTERN_DRAG,
|
||||||
GUI_UNDO_REPLACE
|
GUI_UNDO_REPLACE
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum UndoOtherTarget {
|
||||||
|
GUI_UNDO_TARGET_SONG,
|
||||||
|
GUI_UNDO_TARGET_SUBSONG
|
||||||
|
};
|
||||||
|
|
||||||
struct UndoPatternData {
|
struct UndoPatternData {
|
||||||
int subSong, chan, pat, row, col;
|
int subSong, chan, pat, row, col;
|
||||||
short oldVal, newVal;
|
short oldVal, newVal;
|
||||||
|
@ -770,6 +779,19 @@ struct UndoOrderData {
|
||||||
newVal(v2) {}
|
newVal(v2) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct UndoOtherData {
|
||||||
|
UndoOtherTarget target;
|
||||||
|
int subtarget;
|
||||||
|
size_t off;
|
||||||
|
unsigned char oldVal, newVal;
|
||||||
|
UndoOtherData(UndoOtherTarget t, int st, size_t o, unsigned char v1, unsigned char v2):
|
||||||
|
target(t),
|
||||||
|
subtarget(st),
|
||||||
|
off(o),
|
||||||
|
oldVal(v1),
|
||||||
|
newVal(v2) {}
|
||||||
|
};
|
||||||
|
|
||||||
struct UndoStep {
|
struct UndoStep {
|
||||||
ActionType type;
|
ActionType type;
|
||||||
SelectionPoint cursor, selStart, selEnd;
|
SelectionPoint cursor, selStart, selEnd;
|
||||||
|
@ -779,6 +801,7 @@ struct UndoStep {
|
||||||
int oldPatLen, newPatLen;
|
int oldPatLen, newPatLen;
|
||||||
std::vector<UndoOrderData> ord;
|
std::vector<UndoOrderData> ord;
|
||||||
std::vector<UndoPatternData> pat;
|
std::vector<UndoPatternData> pat;
|
||||||
|
std::vector<UndoOtherData> other;
|
||||||
};
|
};
|
||||||
|
|
||||||
// -1 = any
|
// -1 = any
|
||||||
|
@ -2064,8 +2087,10 @@ class FurnaceGUI {
|
||||||
void doScale(float top);
|
void doScale(float top);
|
||||||
void doRandomize(int bottom, int top, bool mode);
|
void doRandomize(int bottom, int top, bool mode);
|
||||||
void doFlip();
|
void doFlip();
|
||||||
void doCollapse(int divider);
|
void doCollapse(int divider, const SelectionPoint& sStart, const SelectionPoint& sEnd);
|
||||||
void doExpand(int multiplier);
|
void doExpand(int multiplier, const SelectionPoint& sStart, const SelectionPoint& sEnd);
|
||||||
|
void doCollapseSong(int divider);
|
||||||
|
void doExpandSong(int multiplier);
|
||||||
void doUndo();
|
void doUndo();
|
||||||
void doRedo();
|
void doRedo();
|
||||||
void doFind();
|
void doFind();
|
||||||
|
|
Loading…
Reference in a new issue