diff --git a/src/gui/editing.cpp b/src/gui/editing.cpp index ab9da128b..589ccccd8 100644 --- a/src/gui/editing.cpp +++ b/src/gui/editing.cpp @@ -330,6 +330,12 @@ void FurnaceGUI::doSelectAll() { if (!m.effect) continue; \ } +#define touch(_y,_order) \ + if (opTouched[(e->curOrders->ord[iCoarse][_order]<<8)|(_y)]) continue; \ + opTouched[(e->curOrders->ord[iCoarse][_order]<<8)|(_y)]=true; + +#define resetTouches memset(opTouched,0,DIV_MAX_PATTERNS*DIV_MAX_ROWS); + void FurnaceGUI::doDelete() { finishSelection(); prepareUndo(GUI_UNDO_PATTERN_DELETE); @@ -337,24 +343,28 @@ void FurnaceGUI::doDelete() { int iCoarse=selStart.xCoarse; int iFine=selStart.xFine; - int jOrder=selStart.order; for (; iCoarse<=selEnd.xCoarse; iCoarse++) { if (!e->curSubSong->chanShow[iCoarse]) continue; for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarsecurPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][jOrder],true); - for (int j=selStart.y; (j<=selEnd.y || jOrdercurSubSong->patLen && (j<=selEnd.y || jOrderdata[j][iFine]=0; - if (selStart.y==selEnd.y) pat->data[j][2]=-1; + if (selStart.y==selEnd.y && selStart.order==selEnd.order) pat->data[j][2]=-1; } pat->data[j][iFine+1]=(iFine<1)?0:-1; - if (selStart.y==selEnd.y && iFine>2 && iFine&1 && settings.effectDeletionAltersValue) { + if (selStart.y==selEnd.y && selStart.order==selEnd.order && iFine>2 && iFine&1 && settings.effectDeletionAltersValue) { pat->data[j][iFine+2]=-1; } } + j=0; } } iFine=0; @@ -367,7 +377,7 @@ void FurnaceGUI::doPullDelete() { finishSelection(); if (selStart.order!=selEnd.order) { - showError("You can only pull delete within the same order."); + showError(_("you can only pull delete within the same order.")); return; } @@ -441,7 +451,7 @@ void FurnaceGUI::doInsert() { finishSelection(); if (selStart.order!=selEnd.order) { - showError("You can only insert/push within the same order."); + showError(_("you can only insert/push within the same order.")); return; } @@ -492,49 +502,56 @@ void FurnaceGUI::doTranspose(int amount, OperationMask& mask) { int iFine=selStart.xFine; for (; iCoarse<=selEnd.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 && (iCoarsedata[j][0]; - int origOctave=(signed char)pat->data[j][1]; - if (origNote!=0 && origNote!=100 && origNote!=101 && origNote!=102) { - origNote+=amount; - while (origNote>12) { - origNote-=12; - origOctave++; + resetTouches; + for (; jOrder<=selEnd.order; jOrder++) { + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][jOrder],true); + for (; jcurSubSong->patLen && (j<=selEnd.y || jOrderdata[j][0]; + int origOctave=(signed char)pat->data[j][1]; + if (origNote!=0 && origNote!=100 && origNote!=101 && origNote!=102) { + origNote+=amount; + while (origNote>12) { + origNote-=12; + origOctave++; + } + while (origNote<1) { + origNote+=12; + origOctave--; + } + if (origOctave==9 && origNote>11) { + origNote=11; + origOctave=9; + } + if (origOctave>9) { + origNote=11; + origOctave=9; + } + if (origOctave<-5) { + origNote=1; + origOctave=-5; + } + pat->data[j][0]=origNote; + pat->data[j][1]=(unsigned char)origOctave; } - while (origNote<1) { - origNote+=12; - origOctave--; + } else { + int top=255; + if (iFine==1) { + if (e->song.ins.empty()) continue; + top=e->song.ins.size()-1; + } else if (iFine==2) { // volume + top=e->getMaxVolumeChan(iCoarse); } - if (origOctave==9 && origNote>11) { - origNote=11; - origOctave=9; - } - if (origOctave>9) { - origNote=11; - origOctave=9; - } - if (origOctave<-5) { - origNote=1; - origOctave=-5; - } - pat->data[j][0]=origNote; - pat->data[j][1]=(unsigned char)origOctave; + if (pat->data[j][iFine+1]==-1) continue; + pat->data[j][iFine+1]=MIN(top,MAX(0,pat->data[j][iFine+1]+amount)); } - } else { - int top=255; - if (iFine==1) { - if (e->song.ins.empty()) continue; - top=e->song.ins.size()-1; - } else if (iFine==2) { // volume - top=e->getMaxVolumeChan(iCoarse); - } - if (pat->data[j][iFine+1]==-1) continue; - pat->data[j][iFine+1]=MIN(top,MAX(0,pat->data[j][iFine+1]+amount)); } + j=0; } } iFine=0; @@ -552,38 +569,43 @@ String FurnaceGUI::doCopy(bool cut, bool writeClipboard, const SelectionPoint& s } } String clipb=fmt::sprintf("org.tildearrow.furnace - Pattern Data (%d)\n%d",DIV_ENGINE_VERSION,sStart.xFine); + int jOrder=sStart.order; + int j=sStart.y; - for (int j=sStart.y; j<=sEnd.y; j++) { - int iCoarse=sStart.xCoarse; - int iFine=sStart.xFine; - if (iFine>3 && !(iFine&1)) { - iFine--; - } - clipb+='\n'; - 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 && (iCoarsedata[j][0],pat->data[j][1]); - if (cut) { - pat->data[j][0]=0; - pat->data[j][1]=0; - } - } else { - if (pat->data[j][iFine+1]==-1) { - clipb+=".."; + for (; jOrder<=sEnd.order; jOrder++) { + for (; jcurSubSong->patLen && (j<=sEnd.y || jOrder3 && !(iFine&1)) { + iFine--; + } + clipb+='\n'; + for (; iCoarse<=sEnd.xCoarse; iCoarse++) { + if (!e->curSubSong->chanShow[iCoarse]) continue; + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][jOrder],true); + for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarsedata[j][0],pat->data[j][1]); + if (cut) { + pat->data[j][0]=0; + pat->data[j][1]=0; + } } else { - clipb+=fmt::sprintf("%.2X",pat->data[j][iFine+1]); - } - if (cut) { - pat->data[j][iFine+1]=-1; + if (pat->data[j][iFine+1]==-1) { + clipb+=".."; + } else { + clipb+=fmt::sprintf("%.2X",pat->data[j][iFine+1]); + } + if (cut) { + pat->data[j][iFine+1]=-1; + } } } + clipb+='|'; + iFine=0; } - clipb+='|'; - iFine=0; } + j=0; } if (writeClipboard) { @@ -737,6 +759,7 @@ void FurnaceGUI::doPasteFurnace(PasteMode mode, int arg, bool readClipboard, Str if (settings.cursorPastePos) { makeCursorUndo(); cursor.y=j; + cursor.order=curOrder; if (cursor.y>=e->curSubSong->patLen) cursor.y=e->curSubSong->patLen-1; selStart=cursor; selEnd=cursor; @@ -1205,6 +1228,7 @@ void FurnaceGUI::doPasteMPT(PasteMode mode, int arg, bool readClipboard, String if (settings.cursorPastePos) { makeCursorUndo(); cursor.y=j; + cursor.order=curOrder; if (cursor.y>=e->curSubSong->patLen) cursor.y=e->curSubSong->patLen-1; selStart=cursor; selEnd=cursor; @@ -1295,12 +1319,19 @@ void FurnaceGUI::doChangeIns(int ins) { int iCoarse=selStart.xCoarse; for (; iCoarse<=selEnd.xCoarse; iCoarse++) { + int jOrder=selStart.order; + int j=selStart.y; if (!e->curSubSong->chanShow[iCoarse]) continue; - DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); - for (int j=selStart.y; j<=selEnd.y; j++) { - if (pat->data[j][2]!=-1 || !((pat->data[j][0]==0 || pat->data[j][0]==100 || pat->data[j][0]==101 || pat->data[j][0]==102) && pat->data[j][1]==0)) { - pat->data[j][2]=ins; + resetTouches; + for (; jOrder<=selEnd.order; jOrder++) { + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][jOrder],true); + for (; jcurSubSong->patLen && (j<=selEnd.y || jOrderdata[j][2]!=-1 || !((pat->data[j][0]==0 || pat->data[j][0]==100 || pat->data[j][0]==101 || pat->data[j][0]==102) && pat->data[j][1]==0)) { + pat->data[j][2]=ins; + } } + j=0; } } @@ -1311,52 +1342,86 @@ void FurnaceGUI::doInterpolate() { finishSelection(); prepareUndo(GUI_UNDO_PATTERN_INTERPOLATE); + // first: fixed point, 8-bit order.row + // second: value std::vector> points; int iCoarse=selStart.xCoarse; int iFine=selStart.xFine; for (; iCoarse<=selEnd.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 && (iCoarsedata[j][iFine+1]!=-1) { - points.emplace(points.end(),j,pat->data[j][iFine+1]); + int jOrder=selStart.order; + int j=selStart.y; + for (; jOrder<=selEnd.order; jOrder++) { + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][jOrder],true); + for (; jcurSubSong->patLen && (j<=selEnd.y || jOrderdata[j][iFine+1]!=-1) { + points.emplace(points.end(),j|(jOrder<<8),pat->data[j][iFine+1]); + } } + j=0; } if (points.size()>1) for (size_t j=0; j& curPoint=points[j]; std::pair& nextPoint=points[j+1]; - double distance=nextPoint.first-curPoint.first; - for (int k=0; k<(nextPoint.first-curPoint.first); k++) { - pat->data[k+curPoint.first][iFine+1]=curPoint.second+((nextPoint.second-curPoint.second)*(double)k/distance); + int distance=( + ((nextPoint.first&0xff)+((nextPoint.first>>8)*e->curSubSong->patLen))- + ((curPoint.first&0xff)+((curPoint.first>>8)*e->curSubSong->patLen)) + ); + for (int k=0, k_p=curPoint.first; kcurPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][(k_p>>8)&0xff],true); + pat->data[k_p&0xff][iFine+1]=curPoint.second+((nextPoint.second-curPoint.second)*(double)k/(double)distance); + k_p++; + if ((k_p&0xff)>=e->curSubSong->patLen) { + k_p&=~0xff; + k_p+=0x100; + } } } } else { - for (int j=selStart.y; j<=selEnd.y; j++) { - if (pat->data[j][0]!=0 || pat->data[j][1]!=0) { - if (pat->data[j][0]!=100 && pat->data[j][0]!=101 && pat->data[j][0]!=102) { - points.emplace(points.end(),j,pat->data[j][0]+(signed char)pat->data[j][1]*12); + int jOrder=selStart.order; + int j=selStart.y; + for (; jOrder<=selEnd.order; jOrder++) { + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][jOrder],true); + for (; jcurSubSong->patLen && (j<=selEnd.y || jOrderdata[j][0]!=0 || pat->data[j][1]!=0) { + if (pat->data[j][0]!=100 && pat->data[j][0]!=101 && pat->data[j][0]!=102) { + points.emplace(points.end(),j|(jOrder<<8),pat->data[j][0]+(signed char)pat->data[j][1]*12); + } } } + j=0; } if (points.size()>1) for (size_t j=0; j& curPoint=points[j]; std::pair& nextPoint=points[j+1]; - double distance=nextPoint.first-curPoint.first; - for (int k=0; k<(nextPoint.first-curPoint.first); k++) { - int val=curPoint.second+((nextPoint.second-curPoint.second)*(double)k/distance); - pat->data[k+curPoint.first][0]=val%12; - pat->data[k+curPoint.first][1]=val/12; - if (pat->data[k+curPoint.first][0]==0) { - pat->data[k+curPoint.first][0]=12; - pat->data[k+curPoint.first][1]--; + int distance=( + ((nextPoint.first&0xff)+((nextPoint.first>>8)*e->curSubSong->patLen))- + ((curPoint.first&0xff)+((curPoint.first>>8)*e->curSubSong->patLen)) + ); + for (int k=0, k_p=curPoint.first; kcurPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][(k_p>>8)&0xff],true); + int val=curPoint.second+((nextPoint.second-curPoint.second)*(double)k/(double)distance); + pat->data[k_p&0xff][0]=val%12; + pat->data[k_p&0xff][1]=val/12; + if (pat->data[k_p&0xff][0]==0) { + pat->data[k_p&0xff][0]=12; + pat->data[k_p&0xff][1]--; + } + pat->data[k_p&0xff][1]&=255; + k_p++; + if ((k_p&0xff)>=e->curSubSong->patLen) { + k_p&=~0xff; + k_p+=0x100; } - pat->data[k+curPoint.first][1]&=255; } } } @@ -1373,11 +1438,18 @@ void FurnaceGUI::doFade(int p0, int p1, bool mode) { int iCoarse=selStart.xCoarse; int iFine=selStart.xFine; + int distance=( + (selEnd.y+(selEnd.order*e->curSubSong->patLen))- + (selStart.y+(selStart.order*e->curSubSong->patLen)) + ); for (; iCoarse<=selEnd.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 && (iCoarsegetMaxVolumeChan(iCoarse); } - if (selEnd.y-selStart.y<1) continue; - for (int j=selStart.y; j<=selEnd.y; j++) { - double fraction=double(j-selStart.y)/double(selEnd.y-selStart.y); - int value=p0+double(p1-p0)*fraction; - if (mode) { // nibble - value&=15; - pat->data[j][iFine+1]=MIN(absoluteTop,value|(value<<4)); - } else { // byte - pat->data[j][iFine+1]=MIN(absoluteTop,value); + if (distance<1) continue; + for (; jOrder<=selEnd.order; jOrder++) { + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][jOrder],true); + for (; jcurSubSong->patLen && (j<=selEnd.y || jOrderdata[j][iFine+1]=MIN(absoluteTop,value|(value<<4)); + } else { // byte + pat->data[j][iFine+1]=MIN(absoluteTop,value); + } + j_p++; } + j=0; } } } @@ -1413,9 +1490,11 @@ void FurnaceGUI::doInvertValues() { int iFine=selStart.xFine; for (; iCoarse<=selEnd.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 && (iCoarsegetMaxVolumeChan(iCoarse); } - for (int j=selStart.y; j<=selEnd.y; j++) { - if (pat->data[j][iFine+1]==-1) continue; - pat->data[j][iFine+1]=top-pat->data[j][iFine+1]; + for (; jOrder<=selEnd.order; jOrder++) { + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][jOrder],true); + for (; jcurSubSong->patLen && (j<=selEnd.y || jOrderdata[j][iFine+1]==-1) continue; + pat->data[j][iFine+1]=top-pat->data[j][iFine+1]; + } + j=0; } } } @@ -1444,9 +1528,11 @@ void FurnaceGUI::doScale(float top) { int iFine=selStart.xFine; for (; iCoarse<=selEnd.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 && (iCoarsegetMaxVolumeChan(iCoarse); } - for (int j=selStart.y; j<=selEnd.y; j++) { - if (pat->data[j][iFine+1]==-1) continue; - pat->data[j][iFine+1]=MIN(absoluteTop,(double)pat->data[j][iFine+1]*(top/100.0f)); + for (; jOrder<=selEnd.order; jOrder++) { + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][jOrder],true); + for (; jcurSubSong->patLen && (j<=selEnd.y || jOrderdata[j][iFine+1]==-1) continue; + pat->data[j][iFine+1]=MIN(absoluteTop,(double)pat->data[j][iFine+1]*(top/100.0f)); + } + j=0; } } } @@ -1475,9 +1566,11 @@ void FurnaceGUI::doRandomize(int bottom, int top, bool mode, bool eff, int effVa int iFine=selStart.xFine; for (; iCoarse<=selEnd.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 && (iCoarsegetMaxVolumeChan(iCoarse); } - for (int j=selStart.y; j<=selEnd.y; j++) { - int value=0; - int value2=0; - if (top-bottom<=0) { - value=MIN(absoluteTop,bottom); - value2=MIN(absoluteTop,bottom); - } else { - value=MIN(absoluteTop,bottom+(rand()%(top-bottom+1))); - value2=MIN(absoluteTop,bottom+(rand()%(top-bottom+1))); - } - if (mode) { - value&=15; - value2&=15; - pat->data[j][iFine+1]=value|(value2<<4); - } else { - pat->data[j][iFine+1]=value; - } - if (eff && iFine>2 && (iFine&1)) { - pat->data[j][iFine+1]=effVal; + for (; jOrder<=selEnd.order; jOrder++) { + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][jOrder],true); + for (; jcurSubSong->patLen && (j<=selEnd.y || jOrderdata[j][iFine+1]=value|(value2<<4); + } else { + pat->data[j][iFine+1]=value; + } + if (eff && iFine>2 && (iFine&1)) { + pat->data[j][iFine+1]=effVal; + } } + j=0; } } } @@ -1515,29 +1613,54 @@ void FurnaceGUI::doRandomize(int bottom, int top, bool mode, bool eff, int effVa makeUndo(GUI_UNDO_PATTERN_RANDOMIZE); } +struct PatBufferEntry { + short data[DIV_MAX_COLS]; +}; + void FurnaceGUI::doFlip() { finishSelection(); prepareUndo(GUI_UNDO_PATTERN_FLIP); - DivPattern patBuffer; + std::vector patBuffer; int iCoarse=selStart.xCoarse; int iFine=selStart.xFine; for (; iCoarse<=selEnd.xCoarse; iCoarse++) { if (!e->curSubSong->chanShow[iCoarse]) continue; - DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][curOrder],true); + patBuffer.clear(); + + int jOrder=selStart.order; + int j=selStart.y; + + // collect pattern + for (; jOrder<=selEnd.order; jOrder++) { + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][jOrder],true); + for (; jcurSubSong->patLen && (j<=selEnd.y || jOrderdata[j],DIV_MAX_COLS*sizeof(short)); + patBuffer.push_back(put); + } + j=0; + } + for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarsedata[j][0]; + resetTouches; + jOrder=selStart.order; + j=selStart.y; + int j_i=patBuffer.size(); + + // insert flipped version + for (; jOrder<=selEnd.order; jOrder++) { + DivPattern* pat=e->curPat[iCoarse].getPattern(e->curOrders->ord[iCoarse][jOrder],true); + for (; jcurSubSong->patLen && (j<=selEnd.y || jOrderdata[j][0]=patBuffer[j_i].data[0]; + } + pat->data[j][iFine+1]=patBuffer[j_i].data[iFine+1]; } - patBuffer.data[j][iFine+1]=pat->data[j][iFine+1]; - } - for (int j=selStart.y; j<=selEnd.y; j++) { - if (iFine==0) { - pat->data[j][0]=patBuffer.data[selEnd.y-j+selStart.y][0]; - } - pat->data[j][iFine+1]=patBuffer.data[selEnd.y-j+selStart.y][iFine+1]; + j=0; } } iFine=0; @@ -1552,6 +1675,10 @@ void FurnaceGUI::doCollapse(int divider, const SelectionPoint& sStart, const Sel showError(_("can't collapse any further!")); return; } + if (sStart.order!=sEnd.order) { + showError(_("can't collapse across orders.")); + return; + } finishSelection(); prepareUndo(GUI_UNDO_PATTERN_COLLAPSE); @@ -1609,6 +1736,10 @@ void FurnaceGUI::doCollapse(int divider, const SelectionPoint& sStart, const Sel void FurnaceGUI::doExpand(int multiplier, const SelectionPoint& sStart, const SelectionPoint& sEnd) { if (multiplier<2) return; + if (sStart.order!=sEnd.order) { + showError(_("can't expand across orders.")); + return; + } finishSelection(); prepareUndo(GUI_UNDO_PATTERN_EXPAND);