diff --git a/src/engine/pattern.cpp b/src/engine/pattern.cpp index f7b833e1..ea0cca7e 100644 --- a/src/engine/pattern.cpp +++ b/src/engine/pattern.cpp @@ -23,11 +23,7 @@ static DivPattern emptyPat; DivPattern::DivPattern() { - memset(data,-1,DIV_MAX_ROWS*DIV_MAX_COLS*sizeof(short)); - for (int i=0; idata,data,sizeof(data)); } +void DivPattern::clear() { + memset(data,-1,DIV_MAX_ROWS*DIV_MAX_COLS*sizeof(short)); + for (int i=0; igetTotalChannelCount()-1; + selEndPat.xFine=2+e->curPat[selEndPat.xCoarse].effectCols*2; + selEndPat.y=e->curSubSong->patLen-1; + doCollapse(2,SelectionPoint(0,0,0),selEndPat); 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; - case GUI_ACTION_PAT_COLLAPSE_SONG: // TODO + } + case GUI_ACTION_PAT_COLLAPSE_SONG: + doCollapseSong(2); break; - case GUI_ACTION_PAT_EXPAND_SONG: // TODO + case GUI_ACTION_PAT_EXPAND_SONG: + doExpandSong(2); break; case GUI_ACTION_PAT_LATCH: // TODO break; diff --git a/src/gui/editing.cpp b/src/gui/editing.cpp index 77d76da2..1a2b1b11 100644 --- a/src/gui/editing.cpp +++ b/src/gui/editing.cpp @@ -67,6 +67,9 @@ void FurnaceGUI::prepareUndo(ActionType action) { e->curPat[i].getPattern(e->curOrders->ord[i][curOrder],false)->copyOn(oldPat[i]); } break; + case GUI_UNDO_PATTERN_COLLAPSE_SONG: + case GUI_UNDO_PATTERN_EXPAND_SONG: // TODO + break; case GUI_UNDO_REPLACE: // this is handled by doReplace() break; } @@ -130,6 +133,9 @@ void FurnaceGUI::makeUndo(ActionType action) { doPush=true; } break; + case GUI_UNDO_PATTERN_COLLAPSE_SONG: + case GUI_UNDO_PATTERN_EXPAND_SONG: // TODO + break; case GUI_UNDO_REPLACE: // this is handled by doReplace() break; } @@ -839,50 +845,56 @@ void FurnaceGUI::doFlip() { 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->patLencurSubSong->chanShow[iCoarse]) continue; DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); - for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarsecurPat[iCoarse].effectCols*2 && (iCoarsedata[j][0]; } patBuffer.data[j][iFine+1]=pat->data[j][iFine+1]; } - for (int j=0; j<=selEnd.y-selStart.y; j++) { - if (j*divider>=selEnd.y-selStart.y) { + for (int j=0; j<=sEnd.y-sStart.y; j++) { + if (j*divider>=sEnd.y-sStart.y) { if (iFine==0) { - pat->data[j+selStart.y][0]=0; - pat->data[j+selStart.y][1]=0; + pat->data[j+sStart.y][0]=0; + pat->data[j+sStart.y][1]=0; } else { - pat->data[j+selStart.y][iFine+1]=-1; + pat->data[j+sStart.y][iFine+1]=-1; } } else { 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) { for (int k=1; k=selEnd.y-selStart.y) break; - if (!(pat->data[j+selStart.y][0]==0 && pat->data[j+selStart.y][1]==0)) break; - pat->data[j+selStart.y][0]=patBuffer.data[j*divider+selStart.y+k][0]; - pat->data[j+selStart.y][1]=patBuffer.data[j*divider+selStart.y+k][1]; + if ((j*divider+k)>=sEnd.y-sStart.y) break; + if (!(pat->data[j+sStart.y][0]==0 && pat->data[j+sStart.y][1]==0)) break; + pat->data[j+sStart.y][0]=patBuffer.data[j*divider+sStart.y+k][0]; + pat->data[j+sStart.y][1]=patBuffer.data[j*divider+sStart.y+k][1]; } } else { for (int k=1; k=selEnd.y-selStart.y) break; - if (pat->data[j+selStart.y][iFine+1]!=-1) break; - pat->data[j+selStart.y][iFine+1]=patBuffer.data[j*divider+selStart.y+k][iFine+1]; + if ((j*divider+k)>=sEnd.y-sStart.y) break; + if (pat->data[j+sStart.y][iFine+1]!=-1) break; + 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); } -void FurnaceGUI::doExpand(int multiplier) { - if (multiplier<1) return; +void FurnaceGUI::doExpand(int multiplier, const SelectionPoint& sStart, const SelectionPoint& sEnd) { + if (multiplier<2) return; finishSelection(); prepareUndo(GUI_UNDO_PATTERN_EXPAND); DivPattern patBuffer; - int iCoarse=selStart.xCoarse; - int iFine=selStart.xFine; - for (; iCoarse<=selEnd.xCoarse; iCoarse++) { + int iCoarse=sStart.xCoarse; + int iFine=sStart.xFine; + for (; iCoarse<=sEnd.xCoarse; iCoarse++) { if (!e->curSubSong->chanShow[iCoarse]) continue; DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); - for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarsecurPat[iCoarse].effectCols*2 && (iCoarsedata[j][0]; } patBuffer.data[j][iFine+1]=pat->data[j][iFine+1]; } - for (int j=0; j<=(selEnd.y-selStart.y)*multiplier; j++) { - if ((j+selStart.y)>=e->curSubSong->patLen) break; + for (int j=0; j<=(sEnd.y-sStart.y)*multiplier; j++) { + if ((j+sStart.y)>=e->curSubSong->patLen) break; if ((j%multiplier)!=0) { if (iFine==0) { - pat->data[j+selStart.y][0]=0; - pat->data[j+selStart.y][1]=0; + pat->data[j+sStart.y][0]=0; + pat->data[j+sStart.y][1]=0; } else { - pat->data[j+selStart.y][iFine+1]=-1; + pat->data[j+sStart.y][iFine+1]=-1; } continue; } 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; @@ -937,6 +949,162 @@ void FurnaceGUI::doExpand(int multiplier) { 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; igetTotalChannelCount(); i++) { + for (int j=0; jcurPat[i].data[j]==NULL) continue; + + DivPattern* pat=e->curPat[i].getPattern(j,true); + pat->copyOn(&patCopy); + pat->clear(); + for (int k=0; kdata[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; kdata[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; icurSubSong->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; igetTotalChannelCount(); i++) { + for (int j=0; jcurPat[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; ldata[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; kdata[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; icurSubSong->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() { int len=dragEnd.xCoarse-dragStart.xCoarse+1; @@ -985,6 +1153,8 @@ void FurnaceGUI::doUndo() { case GUI_UNDO_PATTERN_FLIP: case GUI_UNDO_PATTERN_COLLAPSE: 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_REPLACE: for (UndoPatternData& i: us.pat) { @@ -1005,6 +1175,22 @@ void FurnaceGUI::doUndo() { 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) { curOrder=e->curSubSong->ordersLen-1; oldOrder=curOrder; @@ -1045,6 +1231,8 @@ void FurnaceGUI::doRedo() { case GUI_UNDO_PATTERN_COLLAPSE: case GUI_UNDO_PATTERN_EXPAND: case GUI_UNDO_PATTERN_DRAG: + case GUI_UNDO_PATTERN_COLLAPSE_SONG: + case GUI_UNDO_PATTERN_EXPAND_SONG: case GUI_UNDO_REPLACE: for (UndoPatternData& i: us.pat) { e->changeSongP(i.subSong); @@ -1065,6 +1253,22 @@ void FurnaceGUI::doRedo() { 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) { curOrder=e->curSubSong->ordersLen-1; oldOrder=curOrder; diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 76214fae..39a7b1cd 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2842,9 +2842,23 @@ void FurnaceGUI::editOptions(bool topMenu) { ImGui::Separator(); 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("expand",BIND_FOR(GUI_ACTION_PAT_EXPAND_ROWS))) doExpand(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,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) { ImGui::Separator(); 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) { diff --git a/src/gui/gui.h b/src/gui/gui.h index fe44ea26..df6700a8 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -721,6 +721,8 @@ enum NoteCtrl { struct SelectionPoint { int xCoarse, xFine; int y; + SelectionPoint(int xc, int xf, int yp): + xCoarse(xc), xFine(xf), y(yp) {} SelectionPoint(): xCoarse(0), xFine(0), y(0) {} }; @@ -742,10 +744,17 @@ enum ActionType { GUI_UNDO_PATTERN_FLIP, GUI_UNDO_PATTERN_COLLAPSE, GUI_UNDO_PATTERN_EXPAND, + GUI_UNDO_PATTERN_COLLAPSE_SONG, + GUI_UNDO_PATTERN_EXPAND_SONG, GUI_UNDO_PATTERN_DRAG, GUI_UNDO_REPLACE }; +enum UndoOtherTarget { + GUI_UNDO_TARGET_SONG, + GUI_UNDO_TARGET_SUBSONG +}; + struct UndoPatternData { int subSong, chan, pat, row, col; short oldVal, newVal; @@ -770,6 +779,19 @@ struct UndoOrderData { 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 { ActionType type; SelectionPoint cursor, selStart, selEnd; @@ -779,6 +801,7 @@ struct UndoStep { int oldPatLen, newPatLen; std::vector ord; std::vector pat; + std::vector other; }; // -1 = any @@ -2064,8 +2087,10 @@ class FurnaceGUI { void doScale(float top); void doRandomize(int bottom, int top, bool mode); void doFlip(); - void doCollapse(int divider); - void doExpand(int multiplier); + void doCollapse(int divider, const SelectionPoint& sStart, const SelectionPoint& sEnd); + void doExpand(int multiplier, const SelectionPoint& sStart, const SelectionPoint& sEnd); + void doCollapseSong(int divider); + void doExpandSong(int multiplier); void doUndo(); void doRedo(); void doFind();