start working on ADSR macro mode
This commit is contained in:
parent
2cebd75236
commit
a979bc244d
|
@ -437,6 +437,12 @@ notes:
|
||||||
- the entire instrument is stored, regardless of instrument type.
|
- the entire instrument is stored, regardless of instrument type.
|
||||||
- the macro range varies depending on the instrument type.
|
- the macro range varies depending on the instrument type.
|
||||||
- "macro open" indicates whether the macro is collapsed or not in the instrument editor.
|
- "macro open" indicates whether the macro is collapsed or not in the instrument editor.
|
||||||
|
- as of format version 120, bit 1-2 indicates macro mode:
|
||||||
|
- 0: sequence (normal)
|
||||||
|
- 1: ADSR
|
||||||
|
- 2: LFO
|
||||||
|
- 3: ADSR+LFO
|
||||||
|
- see sub-section for information on how to interpret parameters.
|
||||||
- FM operator order is:
|
- FM operator order is:
|
||||||
- 1/3/2/4 (internal order) for OPN, OPM, OPZ and OPL 4-op
|
- 1/3/2/4 (internal order) for OPN, OPM, OPZ and OPL 4-op
|
||||||
- 1/2/?/? (? = unused) for OPL 2-op and OPLL
|
- 1/2/?/? (? = unused) for OPL 2-op and OPLL
|
||||||
|
@ -1024,6 +1030,33 @@ size | description
|
||||||
1 | KSR macro delay
|
1 | KSR macro delay
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## interpreting macro mode values
|
||||||
|
|
||||||
|
- sequence (normal): I think this is obvious...
|
||||||
|
- ADSR:
|
||||||
|
- `val[0]`: bottom
|
||||||
|
- `val[1]`: top
|
||||||
|
- `val[2]`: attack
|
||||||
|
- `val[3]`: hold time
|
||||||
|
- `val[4]`: decay
|
||||||
|
- `val[5]`: sustain level
|
||||||
|
- `val[6]`: sustain hold time
|
||||||
|
- `val[7]`: decay 2
|
||||||
|
- `val[8]`: release
|
||||||
|
- LFO:
|
||||||
|
- `val[9]`: bottom
|
||||||
|
- `val[10]`: top
|
||||||
|
- `val[11]`: speed
|
||||||
|
- `val[12]`: waveform
|
||||||
|
- 0: triangle
|
||||||
|
- 1: sine
|
||||||
|
- 2: saw
|
||||||
|
- 3: pulse
|
||||||
|
- `val[13]`: phase
|
||||||
|
- `val[14]`: loop
|
||||||
|
- `val[15]`: global (not sure how will I implement this)
|
||||||
|
- for ADSR+LFO just interpret both ADSR and LFO params.
|
||||||
|
|
||||||
# wavetable
|
# wavetable
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
|
@ -47,8 +47,8 @@
|
||||||
#define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock();
|
#define BUSY_BEGIN_SOFT softLocked=true; isBusy.lock();
|
||||||
#define BUSY_END isBusy.unlock(); softLocked=false;
|
#define BUSY_END isBusy.unlock(); softLocked=false;
|
||||||
|
|
||||||
#define DIV_VERSION "dev119"
|
#define DIV_VERSION "dev120"
|
||||||
#define DIV_ENGINE_VERSION 119
|
#define DIV_ENGINE_VERSION 120
|
||||||
// for imports
|
// for imports
|
||||||
#define DIV_VERSION_MOD 0xff01
|
#define DIV_VERSION_MOD 0xff01
|
||||||
#define DIV_VERSION_FC 0xff02
|
#define DIV_VERSION_FC 0xff02
|
||||||
|
|
|
@ -177,7 +177,7 @@ struct DivInstrumentMacro {
|
||||||
String name;
|
String name;
|
||||||
int val[256];
|
int val[256];
|
||||||
unsigned int mode;
|
unsigned int mode;
|
||||||
bool open;
|
unsigned char open;
|
||||||
unsigned char len, delay, speed, loop, rel;
|
unsigned char len, delay, speed, loop, rel;
|
||||||
|
|
||||||
// the following variables are used by the GUI and not saved in the file
|
// the following variables are used by the GUI and not saved in the file
|
||||||
|
|
|
@ -21,9 +21,20 @@
|
||||||
#include "instrument.h"
|
#include "instrument.h"
|
||||||
#include "engine.h"
|
#include "engine.h"
|
||||||
|
|
||||||
|
#define ADSR_LOW source.val[0]
|
||||||
|
#define ADSR_HIGH source.val[1]
|
||||||
|
#define ADSR_AR source.val[2]
|
||||||
|
#define ADSR_HT source.val[3]
|
||||||
|
#define ADSR_DR source.val[4]
|
||||||
|
#define ADSR_SL source.val[5]
|
||||||
|
#define ADSR_ST source.val[6]
|
||||||
|
#define ADSR_SR source.val[7]
|
||||||
|
#define ADSR_RR source.val[8]
|
||||||
|
|
||||||
void DivMacroStruct::prepare(DivInstrumentMacro& source, DivEngine* e) {
|
void DivMacroStruct::prepare(DivInstrumentMacro& source, DivEngine* e) {
|
||||||
has=had=actualHad=will=true;
|
has=had=actualHad=will=true;
|
||||||
mode=source.mode;
|
mode=source.mode;
|
||||||
|
type=(source.open>>1)&3;
|
||||||
linger=(source.name=="vol" && e->song.volMacroLinger);
|
linger=(source.name=="vol" && e->song.volMacroLinger);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,24 +64,70 @@ void DivMacroStruct::doMacro(DivInstrumentMacro& source, bool released, bool tic
|
||||||
}
|
}
|
||||||
actualHad=has;
|
actualHad=has;
|
||||||
had=actualHad;
|
had=actualHad;
|
||||||
|
|
||||||
if (has) {
|
if (has) {
|
||||||
lastPos=pos;
|
if (type==0) { // sequence
|
||||||
val=source.val[pos++];
|
lastPos=pos;
|
||||||
if (pos>source.rel && !released) {
|
val=source.val[pos++];
|
||||||
if (source.loop<source.len && source.loop<source.rel) {
|
if (pos>source.rel && !released) {
|
||||||
pos=source.loop;
|
if (source.loop<source.len && source.loop<source.rel) {
|
||||||
} else {
|
pos=source.loop;
|
||||||
pos--;
|
} else {
|
||||||
|
pos--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (pos>=source.len) {
|
||||||
|
if (source.loop<source.len && (source.loop>=source.rel || source.rel>=source.len)) {
|
||||||
|
pos=source.loop;
|
||||||
|
} else if (linger) {
|
||||||
|
pos--;
|
||||||
|
} else {
|
||||||
|
has=false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (pos>=source.len) {
|
if (type==1 || type==3) { // ADSR
|
||||||
if (source.loop<source.len && (source.loop>=source.rel || source.rel>=source.len)) {
|
if (released && lastPos<3) lastPos=3;
|
||||||
pos=source.loop;
|
switch (lastPos) {
|
||||||
} else if (linger) {
|
case 0: // attack
|
||||||
pos--;
|
pos+=ADSR_AR;
|
||||||
} else {
|
if (pos>255) {
|
||||||
has=false;
|
pos=255;
|
||||||
|
lastPos=1;
|
||||||
|
delay=ADSR_HT;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 1: // decay
|
||||||
|
pos-=ADSR_DR;
|
||||||
|
if (pos<=ADSR_SL) {
|
||||||
|
pos=ADSR_SL;
|
||||||
|
lastPos=2;
|
||||||
|
delay=ADSR_ST;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 2: // sustain
|
||||||
|
pos-=ADSR_SR;
|
||||||
|
if (pos<0) {
|
||||||
|
pos=0;
|
||||||
|
lastPos=4;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 3: // release
|
||||||
|
pos-=ADSR_RR;
|
||||||
|
if (pos<0) {
|
||||||
|
pos=0;
|
||||||
|
lastPos=4;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 4: // end
|
||||||
|
pos=0;
|
||||||
|
if (!linger) has=false;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
val=ADSR_LOW+((pos+(ADSR_HIGH-ADSR_LOW)*pos)>>8);
|
||||||
|
}
|
||||||
|
if (type==2 || type==3) { // LFO
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,10 +28,10 @@ struct DivMacroStruct {
|
||||||
int pos, lastPos, delay;
|
int pos, lastPos, delay;
|
||||||
int val;
|
int val;
|
||||||
bool has, had, actualHad, finished, will, linger, began;
|
bool has, had, actualHad, finished, will, linger, began;
|
||||||
unsigned int mode;
|
unsigned int mode, type;
|
||||||
void doMacro(DivInstrumentMacro& source, bool released, bool tick);
|
void doMacro(DivInstrumentMacro& source, bool released, bool tick);
|
||||||
void init() {
|
void init() {
|
||||||
pos=lastPos=mode=delay=0;
|
pos=lastPos=mode=type=delay=0;
|
||||||
has=had=actualHad=will=false;
|
has=had=actualHad=will=false;
|
||||||
linger=false;
|
linger=false;
|
||||||
began=true;
|
began=true;
|
||||||
|
@ -51,7 +51,8 @@ struct DivMacroStruct {
|
||||||
will(false),
|
will(false),
|
||||||
linger(false),
|
linger(false),
|
||||||
began(true),
|
began(true),
|
||||||
mode(0) {}
|
mode(0),
|
||||||
|
type(0) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
class DivMacroInt {
|
class DivMacroInt {
|
||||||
|
|
|
@ -203,6 +203,13 @@ enum FMParams {
|
||||||
#define FM_NAME(x) fmParamNames[settings.fmNames][x]
|
#define FM_NAME(x) fmParamNames[settings.fmNames][x]
|
||||||
#define FM_SHORT_NAME(x) fmParamShortNames[settings.fmNames][x]
|
#define FM_SHORT_NAME(x) fmParamShortNames[settings.fmNames][x]
|
||||||
|
|
||||||
|
const char* macroTypeLabels[4]={
|
||||||
|
ICON_FA_BAR_CHART "##IMacroType",
|
||||||
|
ICON_FA_AREA_CHART "##IMacroType",
|
||||||
|
ICON_FA_LINE_CHART "##IMacroType",
|
||||||
|
ICON_FA_SIGN_OUT "##IMacroType"
|
||||||
|
};
|
||||||
|
|
||||||
const char* fmOperatorBits[5]={
|
const char* fmOperatorBits[5]={
|
||||||
"op1", "op2", "op3", "op4", NULL
|
"op1", "op2", "op3", "op4", NULL
|
||||||
};
|
};
|
||||||
|
@ -1326,22 +1333,44 @@ void FurnaceGUI::drawMacros(std::vector<FurnaceGUIMacroDesc>& macros) {
|
||||||
ImGui::TableNextColumn();
|
ImGui::TableNextColumn();
|
||||||
ImGui::Text("%s",i.displayName);
|
ImGui::Text("%s",i.displayName);
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
if (ImGui::SmallButton(i.macro->open?(ICON_FA_CHEVRON_UP "##IMacroOpen"):(ICON_FA_CHEVRON_DOWN "##IMacroOpen"))) {
|
if (ImGui::SmallButton((i.macro->open&1)?(ICON_FA_CHEVRON_UP "##IMacroOpen"):(ICON_FA_CHEVRON_DOWN "##IMacroOpen"))) {
|
||||||
i.macro->open=!i.macro->open;
|
i.macro->open^=1;
|
||||||
}
|
}
|
||||||
if (i.macro->open) {
|
if (i.macro->open&1) {
|
||||||
ImGui::SetNextItemWidth(lenAvail);
|
if ((i.macro->open&6)==0) {
|
||||||
int macroLen=i.macro->len;
|
ImGui::SetNextItemWidth(lenAvail);
|
||||||
if (ImGui::InputScalar("##IMacroLen",ImGuiDataType_U8,¯oLen,&_ONE,&_THREE)) { MARK_MODIFIED
|
int macroLen=i.macro->len;
|
||||||
if (macroLen<0) macroLen=0;
|
if (ImGui::InputScalar("##IMacroLen",ImGuiDataType_U8,¯oLen,&_ONE,&_THREE)) { MARK_MODIFIED
|
||||||
if (macroLen>255) macroLen=255;
|
if (macroLen<0) macroLen=0;
|
||||||
i.macro->len=macroLen;
|
if (macroLen>255) macroLen=255;
|
||||||
|
i.macro->len=macroLen;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (ImGui::Button(ICON_FA_BAR_CHART "##IMacroType")) {
|
if (ImGui::Button(macroTypeLabels[(i.macro->open>>1)&3])) {
|
||||||
|
i.macro->open+=2;
|
||||||
|
if (i.macro->open>=8) {
|
||||||
|
i.macro->open-=8;
|
||||||
|
}
|
||||||
|
PARAMETER;
|
||||||
}
|
}
|
||||||
if (ImGui::IsItemHovered()) {
|
if (ImGui::IsItemHovered()) {
|
||||||
ImGui::SetTooltip("Coming soon!");
|
switch (i.macro->open&6) {
|
||||||
|
case 0:
|
||||||
|
ImGui::SetTooltip("Macro type: Sequence");
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
ImGui::SetTooltip("Macro type: ADSR");
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
ImGui::SetTooltip("Macro type: LFO");
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
ImGui::SetTooltip("Macro type: ADSR+LFO");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
ImGui::SetTooltip("Macro type: What's going on here?");
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
ImGui::Button(ICON_FA_ELLIPSIS_H "##IMacroSet");
|
ImGui::Button(ICON_FA_ELLIPSIS_H "##IMacroSet");
|
||||||
|
@ -1372,204 +1401,313 @@ void FurnaceGUI::drawMacros(std::vector<FurnaceGUIMacroDesc>& macros) {
|
||||||
|
|
||||||
// macro area
|
// macro area
|
||||||
ImGui::TableNextColumn();
|
ImGui::TableNextColumn();
|
||||||
for (int j=0; j<256; j++) {
|
if ((i.macro->open&6)==0) {
|
||||||
bit30Indicator[j]=0;
|
for (int j=0; j<256; j++) {
|
||||||
if (j+macroDragScroll>=i.macro->len) {
|
bit30Indicator[j]=0;
|
||||||
asFloat[j]=0;
|
if (j+macroDragScroll>=i.macro->len) {
|
||||||
asInt[j]=0;
|
asFloat[j]=0;
|
||||||
} else {
|
asInt[j]=0;
|
||||||
asFloat[j]=deBit30(i.macro->val[j+macroDragScroll]);
|
} else {
|
||||||
asInt[j]=deBit30(i.macro->val[j+macroDragScroll])+i.bitOffset;
|
asFloat[j]=deBit30(i.macro->val[j+macroDragScroll]);
|
||||||
if (i.bit30) bit30Indicator[j]=enBit30(i.macro->val[j+macroDragScroll]);
|
asInt[j]=deBit30(i.macro->val[j+macroDragScroll])+i.bitOffset;
|
||||||
|
if (i.bit30) bit30Indicator[j]=enBit30(i.macro->val[j+macroDragScroll]);
|
||||||
|
}
|
||||||
|
if (j+macroDragScroll>=i.macro->len || (j+macroDragScroll>i.macro->rel && i.macro->loop<i.macro->rel)) {
|
||||||
|
loopIndicator[j]=0;
|
||||||
|
} else {
|
||||||
|
loopIndicator[j]=((i.macro->loop!=255 && (j+macroDragScroll)>=i.macro->loop))|((i.macro->rel!=255 && (j+macroDragScroll)==i.macro->rel)<<1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (j+macroDragScroll>=i.macro->len || (j+macroDragScroll>i.macro->rel && i.macro->loop<i.macro->rel)) {
|
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,ImVec2(0.0f,0.0f));
|
||||||
loopIndicator[j]=0;
|
|
||||||
} else {
|
|
||||||
loopIndicator[j]=((i.macro->loop!=255 && (j+macroDragScroll)>=i.macro->loop))|((i.macro->rel!=255 && (j+macroDragScroll)==i.macro->rel)<<1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,ImVec2(0.0f,0.0f));
|
|
||||||
|
|
||||||
if (i.macro->vZoom<1) {
|
if (i.macro->vZoom<1) {
|
||||||
if (i.macro->name=="arp") {
|
if (i.macro->name=="arp") {
|
||||||
i.macro->vZoom=24;
|
i.macro->vZoom=24;
|
||||||
i.macro->vScroll=120-12;
|
i.macro->vScroll=120-12;
|
||||||
} else if (i.macro->name=="pitch") {
|
} else if (i.macro->name=="pitch") {
|
||||||
i.macro->vZoom=128;
|
i.macro->vZoom=128;
|
||||||
i.macro->vScroll=2048-64;
|
i.macro->vScroll=2048-64;
|
||||||
} else {
|
} else {
|
||||||
|
i.macro->vZoom=i.max-i.min;
|
||||||
|
i.macro->vScroll=0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (i.macro->vZoom>(i.max-i.min)) {
|
||||||
i.macro->vZoom=i.max-i.min;
|
i.macro->vZoom=i.max-i.min;
|
||||||
i.macro->vScroll=0;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if (i.macro->vZoom>(i.max-i.min)) {
|
|
||||||
i.macro->vZoom=i.max-i.min;
|
|
||||||
}
|
|
||||||
|
|
||||||
memset(doHighlight,0,256*sizeof(bool));
|
memset(doHighlight,0,256*sizeof(bool));
|
||||||
if (e->isRunning()) for (int j=0; j<e->getTotalChannelCount(); j++) {
|
if (e->isRunning()) for (int j=0; j<e->getTotalChannelCount(); j++) {
|
||||||
DivChannelState* chanState=e->getChanState(j);
|
DivChannelState* chanState=e->getChanState(j);
|
||||||
if (chanState==NULL) continue;
|
if (chanState==NULL) continue;
|
||||||
|
|
||||||
if (chanState->keyOff) continue;
|
if (chanState->keyOff) continue;
|
||||||
if (chanState->lastIns!=curIns) continue;
|
if (chanState->lastIns!=curIns) continue;
|
||||||
|
|
||||||
DivMacroInt* macroInt=e->getMacroInt(j);
|
DivMacroInt* macroInt=e->getMacroInt(j);
|
||||||
if (macroInt==NULL) continue;
|
if (macroInt==NULL) continue;
|
||||||
|
|
||||||
DivMacroStruct* macroStruct=macroInt->structByName(i.macro->name);
|
DivMacroStruct* macroStruct=macroInt->structByName(i.macro->name);
|
||||||
if (macroStruct==NULL) continue;
|
if (macroStruct==NULL) continue;
|
||||||
|
|
||||||
if (macroStruct->lastPos>i.macro->len) continue;
|
if (macroStruct->lastPos>i.macro->len) continue;
|
||||||
if (macroStruct->lastPos<macroDragScroll) continue;
|
if (macroStruct->lastPos<macroDragScroll) continue;
|
||||||
if (macroStruct->lastPos>255) continue;
|
if (macroStruct->lastPos>255) continue;
|
||||||
if (!macroStruct->actualHad) continue;
|
if (!macroStruct->actualHad) continue;
|
||||||
|
|
||||||
doHighlight[macroStruct->lastPos-macroDragScroll]=true;
|
doHighlight[macroStruct->lastPos-macroDragScroll]=true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i.isBitfield) {
|
|
||||||
PlotBitfield("##IMacro",asInt,totalFit,0,i.bitfieldBits,i.max,ImVec2(availableWidth,(i.macro->open)?(i.height*dpiScale):(32.0f*dpiScale)),sizeof(float),doHighlight);
|
|
||||||
} else {
|
|
||||||
PlotCustom("##IMacro",asFloat,totalFit,macroDragScroll,NULL,i.min+i.macro->vScroll,i.min+i.macro->vScroll+i.macro->vZoom,ImVec2(availableWidth,(i.macro->open)?(i.height*dpiScale):(32.0f*dpiScale)),sizeof(float),i.color,i.macro->len-macroDragScroll,i.hoverFunc,i.hoverFuncUser,i.blockMode,i.macro->open?genericGuide:NULL,doHighlight);
|
|
||||||
}
|
|
||||||
if (i.macro->open && (ImGui::IsItemClicked(ImGuiMouseButton_Left) || ImGui::IsItemClicked(ImGuiMouseButton_Right))) {
|
|
||||||
macroDragStart=ImGui::GetItemRectMin();
|
|
||||||
macroDragAreaSize=ImVec2(availableWidth,i.height*dpiScale);
|
|
||||||
if (i.isBitfield) {
|
if (i.isBitfield) {
|
||||||
macroDragMin=i.min;
|
PlotBitfield("##IMacro",asInt,totalFit,0,i.bitfieldBits,i.max,ImVec2(availableWidth,(i.macro->open&1)?(i.height*dpiScale):(32.0f*dpiScale)),sizeof(float),doHighlight);
|
||||||
macroDragMax=i.max;
|
|
||||||
} else {
|
} else {
|
||||||
macroDragMin=i.min+i.macro->vScroll;
|
PlotCustom("##IMacro",asFloat,totalFit,macroDragScroll,NULL,i.min+i.macro->vScroll,i.min+i.macro->vScroll+i.macro->vZoom,ImVec2(availableWidth,(i.macro->open&1)?(i.height*dpiScale):(32.0f*dpiScale)),sizeof(float),i.color,i.macro->len-macroDragScroll,i.hoverFunc,i.hoverFuncUser,i.blockMode,(i.macro->open&1)?genericGuide:NULL,doHighlight);
|
||||||
macroDragMax=i.min+i.macro->vScroll+i.macro->vZoom;
|
|
||||||
}
|
}
|
||||||
macroDragBitOff=i.bitOffset;
|
if ((i.macro->open&1) && (ImGui::IsItemClicked(ImGuiMouseButton_Left) || ImGui::IsItemClicked(ImGuiMouseButton_Right))) {
|
||||||
macroDragBitMode=i.isBitfield;
|
macroDragStart=ImGui::GetItemRectMin();
|
||||||
macroDragInitialValueSet=false;
|
macroDragAreaSize=ImVec2(availableWidth,i.height*dpiScale);
|
||||||
macroDragInitialValue=false;
|
if (i.isBitfield) {
|
||||||
macroDragLen=totalFit;
|
macroDragMin=i.min;
|
||||||
macroDragActive=true;
|
macroDragMax=i.max;
|
||||||
macroDragBit30=i.bit30;
|
|
||||||
macroDragSettingBit30=false;
|
|
||||||
macroDragTarget=i.macro->val;
|
|
||||||
macroDragChar=false;
|
|
||||||
macroDragLineMode=(i.isBitfield)?false:ImGui::IsItemClicked(ImGuiMouseButton_Right);
|
|
||||||
macroDragLineInitial=ImVec2(0,0);
|
|
||||||
lastMacroDesc=i;
|
|
||||||
processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y);
|
|
||||||
}
|
|
||||||
if (i.macro->open) {
|
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
if (ctrlWheeling) {
|
|
||||||
if (ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) {
|
|
||||||
i.macro->vZoom+=wheelY*(1+(i.macro->vZoom>>4));
|
|
||||||
if (i.macro->vZoom<1) i.macro->vZoom=1;
|
|
||||||
if (i.macro->vZoom>(i.max-i.min)) i.macro->vZoom=i.max-i.min;
|
|
||||||
if ((i.macro->vScroll+i.macro->vZoom)>(i.max-i.min)) {
|
|
||||||
i.macro->vScroll=(i.max-i.min)-i.macro->vZoom;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
macroPointSize+=wheelY;
|
|
||||||
if (macroPointSize<1) macroPointSize=1;
|
|
||||||
if (macroPointSize>256) macroPointSize=256;
|
|
||||||
}
|
|
||||||
} else if ((ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) && wheelY!=0) {
|
|
||||||
i.macro->vScroll+=wheelY*(1+(i.macro->vZoom>>4));
|
|
||||||
if (i.macro->vScroll<0) i.macro->vScroll=0;
|
|
||||||
if (i.macro->vScroll>((i.max-i.min)-i.macro->vZoom)) i.macro->vScroll=(i.max-i.min)-i.macro->vZoom;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// slider
|
|
||||||
if (!i.isBitfield) {
|
|
||||||
if (settings.oldMacroVSlider) {
|
|
||||||
ImGui::SameLine(0.0f);
|
|
||||||
if (ImGui::VSliderInt("IMacroVScroll",ImVec2(20.0f*dpiScale,i.height*dpiScale),&i.macro->vScroll,0,(i.max-i.min)-i.macro->vZoom,"")) {
|
|
||||||
if (i.macro->vScroll<0) i.macro->vScroll=0;
|
|
||||||
if (i.macro->vScroll>((i.max-i.min)-i.macro->vZoom)) i.macro->vScroll=(i.max-i.min)-i.macro->vZoom;
|
|
||||||
}
|
|
||||||
if (ImGui::IsItemHovered() && ctrlWheeling) {
|
|
||||||
i.macro->vScroll+=wheelY*(1+(i.macro->vZoom>>4));
|
|
||||||
if (i.macro->vScroll<0) i.macro->vScroll=0;
|
|
||||||
if (i.macro->vScroll>((i.max-i.min)-i.macro->vZoom)) i.macro->vScroll=(i.max-i.min)-i.macro->vZoom;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
ImS64 scrollV=(i.max-i.min-i.macro->vZoom)-i.macro->vScroll;
|
macroDragMin=i.min+i.macro->vScroll;
|
||||||
ImS64 availV=i.macro->vZoom;
|
macroDragMax=i.min+i.macro->vScroll+i.macro->vZoom;
|
||||||
ImS64 contentsV=(i.max-i.min);
|
|
||||||
|
|
||||||
ImGui::SameLine(0.0f);
|
|
||||||
ImGui::SetCursorPosX(ImGui::GetCursorPosX()-ImGui::GetStyle().ItemSpacing.x);
|
|
||||||
ImRect scrollbarPos=ImRect(ImGui::GetCursorScreenPos(),ImGui::GetCursorScreenPos());
|
|
||||||
scrollbarPos.Max.x+=ImGui::GetStyle().ScrollbarSize;
|
|
||||||
scrollbarPos.Max.y+=i.height*dpiScale;
|
|
||||||
ImGui::Dummy(ImVec2(ImGui::GetStyle().ScrollbarSize,i.height*dpiScale));
|
|
||||||
if (ImGui::IsItemHovered() && ctrlWheeling) {
|
|
||||||
i.macro->vScroll+=wheelY*(1+(i.macro->vZoom>>4));
|
|
||||||
if (i.macro->vScroll<0) i.macro->vScroll=0;
|
|
||||||
if (i.macro->vScroll>((i.max-i.min)-i.macro->vZoom)) i.macro->vScroll=(i.max-i.min)-i.macro->vZoom;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGuiID scrollbarID=ImGui::GetID("IMacroVScroll");
|
|
||||||
ImGui::KeepAliveID(scrollbarID);
|
|
||||||
if (ImGui::ScrollbarEx(scrollbarPos,scrollbarID,ImGuiAxis_Y,&scrollV,availV,contentsV,0)) {
|
|
||||||
i.macro->vScroll=(i.max-i.min-i.macro->vZoom)-scrollV;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
macroDragBitOff=i.bitOffset;
|
||||||
|
macroDragBitMode=i.isBitfield;
|
||||||
// bit 30 area
|
macroDragInitialValueSet=false;
|
||||||
if (i.bit30) {
|
macroDragInitialValue=false;
|
||||||
PlotCustom("##IMacroBit30",bit30Indicator,totalFit,macroDragScroll,NULL,0,1,ImVec2(availableWidth,12.0f*dpiScale),sizeof(float),i.color,i.macro->len-macroDragScroll,¯oHoverBit30);
|
macroDragLen=totalFit;
|
||||||
if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) {
|
macroDragActive=true;
|
||||||
macroDragStart=ImGui::GetItemRectMin();
|
macroDragBit30=i.bit30;
|
||||||
macroDragAreaSize=ImVec2(availableWidth,12.0f*dpiScale);
|
macroDragSettingBit30=false;
|
||||||
macroDragInitialValueSet=false;
|
macroDragTarget=i.macro->val;
|
||||||
macroDragInitialValue=false;
|
macroDragChar=false;
|
||||||
macroDragLen=totalFit;
|
macroDragLineMode=(i.isBitfield)?false:ImGui::IsItemClicked(ImGuiMouseButton_Right);
|
||||||
macroDragActive=true;
|
macroDragLineInitial=ImVec2(0,0);
|
||||||
macroDragBit30=i.bit30;
|
lastMacroDesc=i;
|
||||||
macroDragSettingBit30=true;
|
|
||||||
macroDragTarget=i.macro->val;
|
|
||||||
macroDragChar=false;
|
|
||||||
macroDragLineMode=false;
|
|
||||||
macroDragLineInitial=ImVec2(0,0);
|
|
||||||
lastMacroDesc=i;
|
|
||||||
processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// loop area
|
|
||||||
PlotCustom("##IMacroLoop",loopIndicator,totalFit,macroDragScroll,NULL,0,2,ImVec2(availableWidth,12.0f*dpiScale),sizeof(float),i.color,i.macro->len-macroDragScroll,¯oHoverLoop);
|
|
||||||
if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) {
|
|
||||||
macroLoopDragStart=ImGui::GetItemRectMin();
|
|
||||||
macroLoopDragAreaSize=ImVec2(availableWidth,12.0f*dpiScale);
|
|
||||||
macroLoopDragLen=totalFit;
|
|
||||||
if (ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) {
|
|
||||||
macroLoopDragTarget=&i.macro->rel;
|
|
||||||
} else {
|
|
||||||
macroLoopDragTarget=&i.macro->loop;
|
|
||||||
}
|
|
||||||
macroLoopDragActive=true;
|
|
||||||
processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y);
|
processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y);
|
||||||
}
|
}
|
||||||
if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
|
if ((i.macro->open&1)) {
|
||||||
if (ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) {
|
if (ImGui::IsItemHovered()) {
|
||||||
i.macro->rel=255;
|
if (ctrlWheeling) {
|
||||||
} else {
|
if (ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) {
|
||||||
i.macro->loop=255;
|
i.macro->vZoom+=wheelY*(1+(i.macro->vZoom>>4));
|
||||||
|
if (i.macro->vZoom<1) i.macro->vZoom=1;
|
||||||
|
if (i.macro->vZoom>(i.max-i.min)) i.macro->vZoom=i.max-i.min;
|
||||||
|
if ((i.macro->vScroll+i.macro->vZoom)>(i.max-i.min)) {
|
||||||
|
i.macro->vScroll=(i.max-i.min)-i.macro->vZoom;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
macroPointSize+=wheelY;
|
||||||
|
if (macroPointSize<1) macroPointSize=1;
|
||||||
|
if (macroPointSize>256) macroPointSize=256;
|
||||||
|
}
|
||||||
|
} else if ((ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) && wheelY!=0) {
|
||||||
|
i.macro->vScroll+=wheelY*(1+(i.macro->vZoom>>4));
|
||||||
|
if (i.macro->vScroll<0) i.macro->vScroll=0;
|
||||||
|
if (i.macro->vScroll>((i.max-i.min)-i.macro->vZoom)) i.macro->vScroll=(i.max-i.min)-i.macro->vZoom;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// slider
|
||||||
|
if (!i.isBitfield) {
|
||||||
|
if (settings.oldMacroVSlider) {
|
||||||
|
ImGui::SameLine(0.0f);
|
||||||
|
if (ImGui::VSliderInt("IMacroVScroll",ImVec2(20.0f*dpiScale,i.height*dpiScale),&i.macro->vScroll,0,(i.max-i.min)-i.macro->vZoom,"")) {
|
||||||
|
if (i.macro->vScroll<0) i.macro->vScroll=0;
|
||||||
|
if (i.macro->vScroll>((i.max-i.min)-i.macro->vZoom)) i.macro->vScroll=(i.max-i.min)-i.macro->vZoom;
|
||||||
|
}
|
||||||
|
if (ImGui::IsItemHovered() && ctrlWheeling) {
|
||||||
|
i.macro->vScroll+=wheelY*(1+(i.macro->vZoom>>4));
|
||||||
|
if (i.macro->vScroll<0) i.macro->vScroll=0;
|
||||||
|
if (i.macro->vScroll>((i.max-i.min)-i.macro->vZoom)) i.macro->vScroll=(i.max-i.min)-i.macro->vZoom;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ImS64 scrollV=(i.max-i.min-i.macro->vZoom)-i.macro->vScroll;
|
||||||
|
ImS64 availV=i.macro->vZoom;
|
||||||
|
ImS64 contentsV=(i.max-i.min);
|
||||||
|
|
||||||
|
ImGui::SameLine(0.0f);
|
||||||
|
ImGui::SetCursorPosX(ImGui::GetCursorPosX()-ImGui::GetStyle().ItemSpacing.x);
|
||||||
|
ImRect scrollbarPos=ImRect(ImGui::GetCursorScreenPos(),ImGui::GetCursorScreenPos());
|
||||||
|
scrollbarPos.Max.x+=ImGui::GetStyle().ScrollbarSize;
|
||||||
|
scrollbarPos.Max.y+=i.height*dpiScale;
|
||||||
|
ImGui::Dummy(ImVec2(ImGui::GetStyle().ScrollbarSize,i.height*dpiScale));
|
||||||
|
if (ImGui::IsItemHovered() && ctrlWheeling) {
|
||||||
|
i.macro->vScroll+=wheelY*(1+(i.macro->vZoom>>4));
|
||||||
|
if (i.macro->vScroll<0) i.macro->vScroll=0;
|
||||||
|
if (i.macro->vScroll>((i.max-i.min)-i.macro->vZoom)) i.macro->vScroll=(i.max-i.min)-i.macro->vZoom;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGuiID scrollbarID=ImGui::GetID("IMacroVScroll");
|
||||||
|
ImGui::KeepAliveID(scrollbarID);
|
||||||
|
if (ImGui::ScrollbarEx(scrollbarPos,scrollbarID,ImGuiAxis_Y,&scrollV,availV,contentsV,0)) {
|
||||||
|
i.macro->vScroll=(i.max-i.min-i.macro->vZoom)-scrollV;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// bit 30 area
|
||||||
|
if (i.bit30) {
|
||||||
|
PlotCustom("##IMacroBit30",bit30Indicator,totalFit,macroDragScroll,NULL,0,1,ImVec2(availableWidth,12.0f*dpiScale),sizeof(float),i.color,i.macro->len-macroDragScroll,¯oHoverBit30);
|
||||||
|
if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) {
|
||||||
|
macroDragStart=ImGui::GetItemRectMin();
|
||||||
|
macroDragAreaSize=ImVec2(availableWidth,12.0f*dpiScale);
|
||||||
|
macroDragInitialValueSet=false;
|
||||||
|
macroDragInitialValue=false;
|
||||||
|
macroDragLen=totalFit;
|
||||||
|
macroDragActive=true;
|
||||||
|
macroDragBit30=i.bit30;
|
||||||
|
macroDragSettingBit30=true;
|
||||||
|
macroDragTarget=i.macro->val;
|
||||||
|
macroDragChar=false;
|
||||||
|
macroDragLineMode=false;
|
||||||
|
macroDragLineInitial=ImVec2(0,0);
|
||||||
|
lastMacroDesc=i;
|
||||||
|
processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// loop area
|
||||||
|
PlotCustom("##IMacroLoop",loopIndicator,totalFit,macroDragScroll,NULL,0,2,ImVec2(availableWidth,12.0f*dpiScale),sizeof(float),i.color,i.macro->len-macroDragScroll,¯oHoverLoop);
|
||||||
|
if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) {
|
||||||
|
macroLoopDragStart=ImGui::GetItemRectMin();
|
||||||
|
macroLoopDragAreaSize=ImVec2(availableWidth,12.0f*dpiScale);
|
||||||
|
macroLoopDragLen=totalFit;
|
||||||
|
if (ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) {
|
||||||
|
macroLoopDragTarget=&i.macro->rel;
|
||||||
|
} else {
|
||||||
|
macroLoopDragTarget=&i.macro->loop;
|
||||||
|
}
|
||||||
|
macroLoopDragActive=true;
|
||||||
|
processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y);
|
||||||
|
}
|
||||||
|
if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
|
||||||
|
if (ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift)) {
|
||||||
|
i.macro->rel=255;
|
||||||
|
} else {
|
||||||
|
i.macro->loop=255;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ImGui::SetNextItemWidth(availableWidth);
|
||||||
|
String& mmlStr=mmlString[index];
|
||||||
|
if (ImGui::InputText("##IMacroMML",&mmlStr)) {
|
||||||
|
decodeMMLStr(mmlStr,i.macro->val,i.macro->len,i.macro->loop,i.min,(i.isBitfield)?((1<<(i.isBitfield?i.max:0))-1):i.max,i.macro->rel,i.bit30);
|
||||||
|
}
|
||||||
|
if (!ImGui::IsItemActive()) {
|
||||||
|
encodeMMLStr(mmlStr,i.macro->val,i.macro->len,i.macro->loop,i.macro->rel,false,i.bit30);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ImGui::SetNextItemWidth(availableWidth);
|
ImGui::PopStyleVar();
|
||||||
String& mmlStr=mmlString[index];
|
} else {
|
||||||
if (ImGui::InputText("##IMacroMML",&mmlStr)) {
|
if (i.macro->open&2) {
|
||||||
decodeMMLStr(mmlStr,i.macro->val,i.macro->len,i.macro->loop,i.min,(i.isBitfield)?((1<<(i.isBitfield?i.max:0))-1):i.max,i.macro->rel,i.bit30);
|
if (ImGui::BeginTable("MacroADSR",4)) {
|
||||||
|
ImGui::TableSetupColumn("c0",ImGuiTableColumnFlags_WidthFixed);
|
||||||
|
ImGui::TableSetupColumn("c1",ImGuiTableColumnFlags_WidthStretch,0.3);
|
||||||
|
ImGui::TableSetupColumn("c2",ImGuiTableColumnFlags_WidthFixed);
|
||||||
|
ImGui::TableSetupColumn("c3",ImGuiTableColumnFlags_WidthStretch,0.3);
|
||||||
|
//ImGui::TableSetupColumn("c4",ImGuiTableColumnFlags_WidthStretch,0.4);
|
||||||
|
|
||||||
|
ImGui::TableNextRow();
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text("Bottom");
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
|
||||||
|
if (ImGui::InputInt("##MABottom",&i.macro->val[0],1,16)) { PARAMETER
|
||||||
|
if (i.macro->val[0]<i.min) i.macro->val[0]=i.min;
|
||||||
|
if (i.macro->val[0]>i.max) i.macro->val[0]=i.max;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text("Top");
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
|
||||||
|
if (ImGui::InputInt("##MATop",&i.macro->val[1],1,16)) { PARAMETER
|
||||||
|
if (i.macro->val[1]<i.min) i.macro->val[1]=i.min;
|
||||||
|
if (i.macro->val[1]>i.max) i.macro->val[1]=i.max;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*ImGui::TableNextColumn();
|
||||||
|
ImGui::Text("the envelope goes here");*/
|
||||||
|
|
||||||
|
ImGui::TableNextRow();
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text("Attack");
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
|
||||||
|
if (CWSliderInt("##MAAR",&i.macro->val[2],0,255)) { PARAMETER
|
||||||
|
if (i.macro->val[2]<0) i.macro->val[2]=0;
|
||||||
|
if (i.macro->val[2]>255) i.macro->val[2]=255;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text("Sustain");
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
|
||||||
|
if (CWSliderInt("##MASL",&i.macro->val[5],0,255)) { PARAMETER
|
||||||
|
if (i.macro->val[5]<0) i.macro->val[5]=0;
|
||||||
|
if (i.macro->val[5]>255) i.macro->val[5]=255;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::TableNextRow();
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text("Hold");
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
|
||||||
|
if (CWSliderInt("##MAHT",&i.macro->val[3],0,255)) { PARAMETER
|
||||||
|
if (i.macro->val[3]<0) i.macro->val[3]=0;
|
||||||
|
if (i.macro->val[3]>255) i.macro->val[3]=255;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text("SusTime");
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
|
||||||
|
if (CWSliderInt("##MAST",&i.macro->val[6],0,255)) { PARAMETER
|
||||||
|
if (i.macro->val[6]<0) i.macro->val[6]=0;
|
||||||
|
if (i.macro->val[6]>255) i.macro->val[6]=255;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::TableNextRow();
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text("Decay");
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
|
||||||
|
if (CWSliderInt("##MADR",&i.macro->val[4],0,255)) { PARAMETER
|
||||||
|
if (i.macro->val[4]<0) i.macro->val[4]=0;
|
||||||
|
if (i.macro->val[4]>255) i.macro->val[4]=255;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text("SusDecay");
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
|
||||||
|
if (CWSliderInt("##MASR",&i.macro->val[7],0,255)) { PARAMETER
|
||||||
|
if (i.macro->val[7]<0) i.macro->val[7]=0;
|
||||||
|
if (i.macro->val[7]>255) i.macro->val[7]=255;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::TableNextRow();
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::Text("Release");
|
||||||
|
ImGui::TableNextColumn();
|
||||||
|
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
|
||||||
|
if (CWSliderInt("##MARR",&i.macro->val[8],0,255)) { PARAMETER
|
||||||
|
if (i.macro->val[8]<0) i.macro->val[8]=0;
|
||||||
|
if (i.macro->val[8]>255) i.macro->val[8]=255;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::EndTable();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!ImGui::IsItemActive()) {
|
if (i.macro->open&4) {
|
||||||
encodeMMLStr(mmlStr,i.macro->val,i.macro->len,i.macro->loop,i.macro->rel,false,i.bit30);
|
ImGui::Text("LFO...");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ImGui::PopStyleVar();
|
|
||||||
ImGui::PopID();
|
ImGui::PopID();
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue