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

This commit is contained in:
cam900 2023-06-29 21:36:29 +09:00
commit b1e2e33f2d
431 changed files with 25341 additions and 13648 deletions

View file

@ -78,11 +78,14 @@ const char* aboutLine[]={
"Dippy",
"djtuBIG-MaliceX",
"dumbut",
"ElectricKeet",
"EpicTyphlosion",
"FΛDE",
"Forte",
"Fragmare",
"freq-mod",
"gtr3qq",
"Hortus",
"iyatemu",
"JayBOB18",
"Jimmy-DS",
@ -100,6 +103,7 @@ const char* aboutLine[]={
"MelonadeM",
"Miker",
"nicco1690",
"niffuM",
"<nk>",
"NyaongI",
"potatoTeto",
@ -120,6 +124,7 @@ const char* aboutLine[]={
"Ultraprogramer",
"UserSniper",
"Weeppiko",
"Xan",
"Yuzu4K",
"Zaxolotl",
"ZoomTen (Zumi)",

View file

@ -22,6 +22,7 @@
#include "../ta-log.h"
#include "imgui.h"
#include "imgui_internal.h"
#include "misc/cpp/imgui_stdlib.h"
#define FURNACE_FFT_SIZE 4096
#define FURNACE_FFT_RATE 80.0
@ -93,7 +94,7 @@ void FurnaceGUI::calcChanOsc() {
unsigned short needlePos=buf->needle;
needlePos-=displaySize;
for (unsigned short i=0; i<512; i++) {
float y=(float)buf->data[(unsigned short)(needlePos+(i*displaySize/512))]/65536.0f;
float y=(float)buf->data[(unsigned short)(needlePos+(i*displaySize/512))]/32768.0f;
if (minLevel>y) minLevel=y;
if (maxLevel<y) maxLevel=y;
}
@ -116,7 +117,7 @@ void FurnaceGUI::drawChanOsc() {
}
if (!chanOscOpen) return;
ImGui::SetNextWindowSizeConstraints(ImVec2(64.0f*dpiScale,32.0f*dpiScale),ImVec2(canvasW,canvasH));
if (ImGui::Begin("Oscilloscope (per-channel)",&chanOscOpen,globalWinFlags|((chanOscOptions)?0:ImGuiWindowFlags_NoTitleBar))) {
if (ImGui::Begin("Oscilloscope (per-channel)",&chanOscOpen,globalWinFlags)) {
bool centerSettingReset=false;
ImDrawList* dl=ImGui::GetWindowDrawList();
if (chanOscOptions) {
@ -152,10 +153,10 @@ void FurnaceGUI::drawChanOsc() {
if (chanOscUseGrad) {
if (chanOscGradTex==NULL) {
chanOscGradTex=SDL_CreateTexture(sdlRend,SDL_PIXELFORMAT_ABGR8888,SDL_TEXTUREACCESS_STREAMING,chanOscGrad.width,chanOscGrad.height);
chanOscGradTex=rend->createTexture(true,chanOscGrad.width,chanOscGrad.height);
if (chanOscGradTex==NULL) {
logE("error while creating gradient texture! %s",SDL_GetError());
logE("error while creating gradient texture!");
} else {
updateChanOscGradTex=true;
}
@ -170,16 +171,16 @@ void FurnaceGUI::drawChanOsc() {
if (chanOscGradTex!=NULL) {
if (updateChanOscGradTex) {
chanOscGrad.render();
if (SDL_UpdateTexture(chanOscGradTex,NULL,chanOscGrad.grad.get(),chanOscGrad.width*4)==0) {
if (rend->updateTexture(chanOscGradTex,chanOscGrad.grad.get(),chanOscGrad.width*4)) {
updateChanOscGradTex=false;
} else {
logE("error while updating gradient texture! %s",SDL_GetError());
logE("error while updating gradient texture!");
}
}
ImVec2 gradLeft=ImGui::GetCursorPos();
ImVec2 gradSize=ImVec2(400.0f*dpiScale,400.0f*dpiScale);
ImGui::Image(chanOscGradTex,gradSize);
ImGui::Image(rend->getTextureID(chanOscGradTex),gradSize);
ImVec2 gradLeftAbs=ImGui::GetItemRectMin();
if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
if (chanOscGrad.points.size()<32) {
@ -284,151 +285,266 @@ void FurnaceGUI::drawChanOsc() {
ImGui::ColorPicker4("Color",(float*)&chanOscColor);
}
ImGui::Text("Text format:");
ImGui::SameLine();
ImGui::InputText("##TextFormat",&chanOscTextFormat);
if (ImGui::IsItemHovered()) {
if (ImGui::BeginTooltip()) {
ImGui::TextUnformatted(
"format guide:\n"
"- %c: channel name\n"
"- %C: channel short name\n"
"- %d: channel number (starting from 0)\n"
"- %D: channel number (starting from 1)\n"
"- %i: instrument name\n"
"- %I: instrument number (decimal)\n"
"- %x: instrument number (hex)\n"
"- %s: chip name\n"
"- %S: chip ID\n"
"- %v: volume (decimal)\n"
"- %V: volume (percentage)\n"
"- %b: volume (hex)\n"
"- %%: percent sign"
);
ImGui::EndTooltip();
}
}
ImGui::ColorEdit4("Text color",(float*)&chanOscTextColor);
if (ImGui::Button("OK")) {
chanOscOptions=false;
}
}
} else {
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding,ImVec2(0.0f,0.0f));
float availY=ImGui::GetContentRegionAvail().y;
if (ImGui::BeginTable("ChanOsc",chanOscCols,ImGuiTableFlags_Borders)) {
std::vector<DivDispatchOscBuffer*> oscBufs;
std::vector<ChanOscStatus*> oscFFTs;
std::vector<int> oscChans;
int chans=e->getTotalChannelCount();
ImGuiWindow* window=ImGui::GetCurrentWindow();
ImVec2 waveform[512];
ImGui::PushStyleVar(ImGuiStyleVar_CellPadding,ImVec2(0.0f,0.0f));
float availY=ImGui::GetContentRegionAvail().y;
if (ImGui::BeginTable("ChanOsc",chanOscCols,ImGuiTableFlags_Borders)) {
std::vector<DivDispatchOscBuffer*> oscBufs;
std::vector<ChanOscStatus*> oscFFTs;
std::vector<int> oscChans;
int chans=e->getTotalChannelCount();
ImGuiWindow* window=ImGui::GetCurrentWindow();
ImVec2 waveform[512];
ImGuiStyle& style=ImGui::GetStyle();
ImGuiStyle& style=ImGui::GetStyle();
for (int i=0; i<chans; i++) {
DivDispatchOscBuffer* buf=e->getOscBuffer(i);
if (buf!=NULL && e->curSubSong->chanShow[i]) {
oscBufs.push_back(buf);
oscFFTs.push_back(&chanOscChan[i]);
oscChans.push_back(i);
for (int i=0; i<chans; i++) {
DivDispatchOscBuffer* buf=e->getOscBuffer(i);
if (buf!=NULL && e->curSubSong->chanShow[i]) {
oscBufs.push_back(buf);
oscFFTs.push_back(&chanOscChan[i]);
oscChans.push_back(i);
}
}
}
int rows=(oscBufs.size()+(chanOscCols-1))/chanOscCols;
int rows=(oscBufs.size()+(chanOscCols-1))/chanOscCols;
for (size_t i=0; i<oscBufs.size(); i++) {
if (i%chanOscCols==0) ImGui::TableNextRow();
ImGui::TableNextColumn();
for (size_t i=0; i<oscBufs.size(); i++) {
if (i%chanOscCols==0) ImGui::TableNextRow();
ImGui::TableNextColumn();
DivDispatchOscBuffer* buf=oscBufs[i];
ChanOscStatus* fft=oscFFTs[i];
int ch=oscChans[i];
if (buf==NULL) {
ImGui::Text("Error!");
} else {
ImVec2 size=ImGui::GetContentRegionAvail();
size.y=availY/rows;
DivDispatchOscBuffer* buf=oscBufs[i];
ChanOscStatus* fft=oscFFTs[i];
int ch=oscChans[i];
if (buf==NULL) {
ImGui::Text("Error!");
} else {
ImVec2 size=ImGui::GetContentRegionAvail();
size.y=availY/rows;
if (centerSettingReset) {
buf->readNeedle=buf->needle;
}
if (centerSettingReset) {
buf->readNeedle=buf->needle;
}
// check FFT status existence
if (fft->plan==NULL) {
logD("creating FFT plan for channel %d",ch);
fft->inBuf=(double*)fftw_malloc(FURNACE_FFT_SIZE*sizeof(double));
fft->outBuf=(fftw_complex*)fftw_malloc(FURNACE_FFT_SIZE*sizeof(fftw_complex));
fft->plan=fftw_plan_dft_r2c_1d(FURNACE_FFT_SIZE,fft->inBuf,fft->outBuf,FFTW_ESTIMATE);
}
// check FFT status existence
if (fft->plan==NULL) {
logD("creating FFT plan for channel %d",ch);
fft->inBuf=(double*)fftw_malloc(FURNACE_FFT_SIZE*sizeof(double));
fft->outBuf=(fftw_complex*)fftw_malloc(FURNACE_FFT_SIZE*sizeof(fftw_complex));
fft->plan=fftw_plan_dft_r2c_1d(FURNACE_FFT_SIZE,fft->inBuf,fft->outBuf,FFTW_ESTIMATE);
}
int displaySize=(float)(buf->rate)*(chanOscWindowSize/1000.0f);
int displaySize=(float)(buf->rate)*(chanOscWindowSize/1000.0f);
ImVec2 minArea=window->DC.CursorPos;
ImVec2 maxArea=ImVec2(
minArea.x+size.x,
minArea.y+size.y
);
ImRect rect=ImRect(minArea,maxArea);
ImRect inRect=rect;
inRect.Min.x+=dpiScale;
inRect.Min.y+=dpiScale;
inRect.Max.x-=dpiScale;
inRect.Max.y-=dpiScale;
ImGui::ItemSize(size,style.FramePadding.y);
if (ImGui::ItemAdd(rect,ImGui::GetID("chOscDisplay"))) {
if (!e->isRunning()) {
for (unsigned short i=0; i<512; i++) {
float x=(float)i/512.0f;
waveform[i]=ImLerp(inRect.Min,inRect.Max,ImVec2(x,0.5f));
}
} else {
float minLevel=1.0f;
float maxLevel=-1.0f;
float dcOff=0.0f;
unsigned short needlePos=buf->needle;
for (int i=0; i<FURNACE_FFT_SIZE; i++) {
fft->inBuf[i]=(double)buf->data[(unsigned short)(needlePos-displaySize*2+((i*displaySize*2)/FURNACE_FFT_SIZE))]/32768.0;
}
fftw_execute(fft->plan);
// find origin frequency
int point=1;
double candAmp=0.0;
for (unsigned short i=1; i<512; i++) {
fftw_complex& f=fft->outBuf[i];
// AMPLITUDE
double amp=sqrt(pow(f[0],2.0)+pow(f[1],2.0))/pow((double)i,0.8);
if (amp>candAmp) {
point=i;
candAmp=amp;
ImVec2 minArea=window->DC.CursorPos;
ImVec2 maxArea=ImVec2(
minArea.x+size.x,
minArea.y+size.y
);
ImRect rect=ImRect(minArea,maxArea);
ImRect inRect=rect;
inRect.Min.x+=dpiScale;
inRect.Min.y+=2.0*dpiScale;
inRect.Max.x-=dpiScale;
inRect.Max.y-=2.0*dpiScale;
int precision=inRect.Max.x-inRect.Min.x;
if (precision<1) precision=1;
if (precision>512) precision=512;
ImGui::ItemSize(size,style.FramePadding.y);
if (ImGui::ItemAdd(rect,ImGui::GetID("chOscDisplay"))) {
if (!e->isRunning()) {
for (unsigned short i=0; i<precision; i++) {
float x=(float)i/(float)precision;
waveform[i]=ImLerp(inRect.Min,inRect.Max,ImVec2(x,0.5f));
}
} else {
float minLevel=1.0f;
float maxLevel=-1.0f;
float dcOff=0.0f;
unsigned short needlePos=buf->needle;
for (int i=0; i<FURNACE_FFT_SIZE; i++) {
fft->inBuf[i]=(double)buf->data[(unsigned short)(needlePos-displaySize*2+((i*displaySize*2)/FURNACE_FFT_SIZE))]/32768.0;
}
fftw_execute(fft->plan);
// find origin frequency
int point=1;
double candAmp=0.0;
for (unsigned short i=1; i<512; i++) {
fftw_complex& f=fft->outBuf[i];
// AMPLITUDE
double amp=sqrt(pow(f[0],2.0)+pow(f[1],2.0))/pow((double)i,0.8);
if (amp>candAmp) {
point=i;
candAmp=amp;
}
}
// PHASE
fftw_complex& candPoint=fft->outBuf[point];
double phase=((double)(displaySize*2)/(double)point)*(0.5+(atan2(candPoint[1],candPoint[0])/(M_PI*2)));
if (chanOscWaveCorr) {
needlePos-=phase;
}
chanOscPitch[ch]=(float)point/32.0f;
/*
String cPhase=fmt::sprintf("%d cphase: %f vol: %f",point,phase,chanOscVol[ch]);
dl->AddText(inRect.Min,0xffffffff,cPhase.c_str());
*/
needlePos-=displaySize;
for (unsigned short i=0; i<precision; i++) {
float y=(float)buf->data[(unsigned short)(needlePos+(i*displaySize/precision))]/32768.0f;
if (minLevel>y) minLevel=y;
if (maxLevel<y) maxLevel=y;
}
dcOff=(minLevel+maxLevel)*0.5f;
for (unsigned short i=0; i<precision; i++) {
float x=(float)i/(float)precision;
float y=(float)buf->data[(unsigned short)(needlePos+(i*displaySize/precision))]/32768.0f;
y-=dcOff;
if (y<-0.5f) y=-0.5f;
if (y>0.5f) y=0.5f;
y*=chanOscAmplify;
waveform[i]=ImLerp(inRect.Min,inRect.Max,ImVec2(x,0.5f-y));
}
}
ImU32 color=ImGui::GetColorU32(chanOscColor);
if (chanOscUseGrad) {
float xVal=computeGradPos(chanOscColorX,ch);
float yVal=computeGradPos(chanOscColorY,ch);
// PHASE
fftw_complex& candPoint=fft->outBuf[point];
double phase=((double)(displaySize*2)/(double)point)*(0.5+(atan2(candPoint[1],candPoint[0])/(M_PI*2)));
xVal=CLAMP(xVal,0.0f,1.0f);
yVal=CLAMP(yVal,0.0f,1.0f);
if (chanOscWaveCorr) {
needlePos-=phase;
color=chanOscGrad.get(xVal,1.0f-yVal);
}
chanOscPitch[ch]=(float)point/32.0f;
/*
String cPhase=fmt::sprintf("%d cphase: %f vol: %f",point,phase,chanOscVol[ch]);
dl->AddText(inRect.Min,0xffffffff,cPhase.c_str());
*/
ImGui::PushClipRect(inRect.Min,inRect.Max,false);
needlePos-=displaySize;
for (unsigned short i=0; i<512; i++) {
float y=(float)buf->data[(unsigned short)(needlePos+(i*displaySize/512))]/65536.0f;
if (minLevel>y) minLevel=y;
if (maxLevel<y) maxLevel=y;
}
dcOff=(minLevel+maxLevel)*0.5f;
for (unsigned short i=0; i<512; i++) {
float x=(float)i/512.0f;
float y=(float)buf->data[(unsigned short)(needlePos+(i*displaySize/512))]/65536.0f;
if (y<-0.5f) y=-0.5f;
if (y>0.5f) y=0.5f;
waveform[i]=ImLerp(inRect.Min,inRect.Max,ImVec2(x,0.5f-(y-dcOff)));
dl->AddPolyline(waveform,precision,color,ImDrawFlags_None,dpiScale);
if (!chanOscTextFormat.empty()) {
String text;
bool inFormat=false;
for (char i: chanOscTextFormat) {
if (inFormat) {
switch (i) {
case 'c':
text+=e->getChannelName(ch);
break;
case 'C':
text+=e->getChannelShortName(ch);
break;
case 'd':
text+=fmt::sprintf("%d",ch);
break;
case 'D':
text+=fmt::sprintf("%d",ch+1);
break;
case 'i': {
DivChannelState* chanState=e->getChanState(ch);
if (chanState==NULL) break;
DivInstrument* ins=e->getIns(chanState->lastIns);
text+=ins->name;
break;
}
case 'I': {
DivChannelState* chanState=e->getChanState(ch);
if (chanState==NULL) break;
text+=fmt::sprintf("%d",chanState->lastIns);
break;
}
case 'x': {
DivChannelState* chanState=e->getChanState(ch);
if (chanState==NULL) break;
if (chanState->lastIns<0) {
text+="??";
} else {
text+=fmt::sprintf("%.2X",chanState->lastIns);
}
break;
}
case 's': {
text+=e->getSystemName(e->sysOfChan[ch]);
break;
}
case 'S': {
text+=fmt::sprintf("%d",e->dispatchOfChan[ch]);
break;
}
case 'v':
break;
case 'V':
break;
case 'b':
break;
case '%':
text+='%';
break;
default:
text+='%';
text+=i;
break;
}
inFormat=false;
} else {
if (i=='%') {
inFormat=true;
} else {
text+=i;
}
}
}
dl->AddText(ImLerp(inRect.Min,inRect.Max,ImVec2(0.0f,0.0f)),ImGui::GetColorU32(chanOscTextColor),text.c_str());
}
ImGui::PopClipRect();
}
ImU32 color=ImGui::GetColorU32(chanOscColor);
if (chanOscUseGrad) {
float xVal=computeGradPos(chanOscColorX,ch);
float yVal=computeGradPos(chanOscColorY,ch);
xVal=CLAMP(xVal,0.0f,1.0f);
yVal=CLAMP(yVal,0.0f,1.0f);
color=chanOscGrad.get(xVal,1.0f-yVal);
}
dl->AddPolyline(waveform,512,color,ImDrawFlags_None,dpiScale);
}
}
}
ImGui::EndTable();
ImGui::EndTable();
if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
chanOscOptions=!chanOscOptions;
if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
chanOscOptions=!chanOscOptions;
}
}
ImGui::PopStyleVar();
}
ImGui::PopStyleVar();
}
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_CHAN_OSC;
ImGui::End();

View file

@ -180,6 +180,10 @@ void FurnaceGUI::drawCompatFlags() {
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("behavior changed in 0.6pre4");
}
ImGui::Checkbox("Broken macros in some FM chips after note off",&e->song.brokenFMOff);
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("behavior changed in 0.6pre5");
}
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem(".mod import")) {

File diff suppressed because it is too large Load diff

View file

@ -380,7 +380,7 @@ void FurnaceGUI::drawDebug() {
}
ImGui::TreePop();
}
if (ImGui::TreeNode("Window Debug")) {
if (ImGui::TreeNodeEx("Window Debug",ImGuiTreeNodeFlags_DefaultOpen)) {
ImGui::Text("Screen: %dx%d+%d+%d",scrW,scrH,scrX,scrY);
ImGui::Text("Screen (Conf): %dx%d+%d+%d",scrConfW,scrConfH,scrConfX,scrConfY);
ImGui::Text("Canvas: %dx%d",canvasW,canvasH);
@ -549,6 +549,7 @@ void FurnaceGUI::drawDebug() {
ImGui::Text("audio: %dµs",lastProcTime);
ImGui::Text("render: %.0fµs",(double)renderTimeDelta/perfFreq);
ImGui::Text("draw: %.0fµs",(double)drawTimeDelta/perfFreq);
ImGui::Text("layout: %.0fµs",(double)layoutTimeDelta/perfFreq);
ImGui::Text("event: %.0fµs",(double)eventTimeDelta/perfFreq);
ImGui::Separator();

View file

@ -561,17 +561,17 @@ void FurnaceGUI::doAction(int what) {
doFlip();
break;
case GUI_ACTION_PAT_COLLAPSE_ROWS:
doCollapse(2,selStart,selEnd);
doCollapse(collapseAmount,selStart,selEnd);
break;
case GUI_ACTION_PAT_EXPAND_ROWS:
doExpand(2,selStart,selEnd);
doExpand(collapseAmount,selStart,selEnd);
break;
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);
doCollapse(collapseAmount,SelectionPoint(0,0,0),selEndPat);
break;
}
case GUI_ACTION_PAT_EXPAND_PAT: {
@ -579,14 +579,14 @@ void FurnaceGUI::doAction(int what) {
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);
doExpand(collapseAmount,SelectionPoint(0,0,0),selEndPat);
break;
}
case GUI_ACTION_PAT_COLLAPSE_SONG:
doCollapseSong(2);
doCollapseSong(collapseAmount);
break;
case GUI_ACTION_PAT_EXPAND_SONG:
doExpandSong(2);
doExpandSong(collapseAmount);
break;
case GUI_ACTION_PAT_LATCH: // TODO
break;
@ -684,6 +684,9 @@ void FurnaceGUI::doAction(int what) {
wavePreviewInit=true;
updateFMPreview=true;
break;
case GUI_ACTION_INS_LIST_DIR_VIEW:
insListDir=!insListDir;
break;
case GUI_ACTION_WAVE_LIST_ADD:
curWave=e->addWave();
@ -757,6 +760,9 @@ void FurnaceGUI::doAction(int what) {
if (++curWave>=(int)e->song.wave.size()) curWave=((int)e->song.wave.size())-1;
wantScrollList=true;
break;
case GUI_ACTION_WAVE_LIST_DIR_VIEW:
waveListDir=!waveListDir;
break;
case GUI_ACTION_SAMPLE_LIST_ADD:
curSample=e->addSample();
@ -785,6 +791,8 @@ void FurnaceGUI::doAction(int what) {
sample->loopEnd=prevSample->loopEnd;
sample->loop=prevSample->loop;
sample->loopMode=prevSample->loopMode;
sample->brrEmphasis=prevSample->brrEmphasis;
sample->dither=prevSample->dither;
sample->depth=prevSample->depth;
if (sample->init(prevSample->samples)) {
if (prevSample->getCurBuf()!=NULL) {
@ -815,6 +823,9 @@ void FurnaceGUI::doAction(int what) {
case GUI_ACTION_SAMPLE_LIST_SAVE:
if (curSample>=0 && curSample<(int)e->song.sample.size()) openFileDialog(GUI_FILE_SAMPLE_SAVE);
break;
case GUI_ACTION_SAMPLE_LIST_SAVE_RAW:
if (curSample>=0 && curSample<(int)e->song.sample.size()) openFileDialog(GUI_FILE_SAMPLE_SAVE_RAW);
break;
case GUI_ACTION_SAMPLE_LIST_MOVE_UP:
if (e->moveSampleUp(curSample)) {
curSample--;
@ -835,8 +846,8 @@ void FurnaceGUI::doAction(int what) {
MARK_MODIFIED;
if (curSample>=(int)e->song.sample.size()) {
curSample--;
updateSampleTex=true;
}
updateSampleTex=true;
break;
case GUI_ACTION_SAMPLE_LIST_EDIT:
sampleEditOpen=true;
@ -857,6 +868,9 @@ void FurnaceGUI::doAction(int what) {
case GUI_ACTION_SAMPLE_LIST_STOP_PREVIEW:
e->stopSamplePreview();
break;
case GUI_ACTION_SAMPLE_LIST_DIR_VIEW:
sampleListDir=!sampleListDir;
break;
case GUI_ACTION_SAMPLE_SELECT:
if (curSample<0 || curSample>=(int)e->song.sample.size()) break;

View file

@ -463,181 +463,185 @@ void FurnaceGUI::drawMobileControls() {
ImGui::Separator();
if (settings.unifiedDataView) {
drawInsList(true);
} else {
switch (mobScene) {
case GUI_SCENE_PATTERN:
case GUI_SCENE_ORDERS:
case GUI_SCENE_INSTRUMENT:
switch (mobScene) {
case GUI_SCENE_PATTERN:
case GUI_SCENE_ORDERS:
case GUI_SCENE_INSTRUMENT:
drawInsList(true);
break;
case GUI_SCENE_WAVETABLE:
if (settings.unifiedDataView) {
drawInsList(true);
break;
case GUI_SCENE_WAVETABLE:
} else {
drawWaveList(true);
break;
case GUI_SCENE_SAMPLE:
}
break;
case GUI_SCENE_SAMPLE:
if (settings.unifiedDataView) {
drawInsList(true);
} else {
drawSampleList(true);
break;
case GUI_SCENE_SONG: {
if (ImGui::Button("New")) {
mobileMenuOpen=false;
//doAction(GUI_ACTION_NEW);
if (modified) {
showWarning("Unsaved changes! Save changes before creating a new song?",GUI_WARN_NEW);
} else {
displayNew=true;
}
}
ImGui::SameLine();
if (ImGui::Button("Open")) {
mobileMenuOpen=false;
doAction(GUI_ACTION_OPEN);
}
ImGui::SameLine();
if (ImGui::Button("Save")) {
mobileMenuOpen=false;
doAction(GUI_ACTION_SAVE);
}
ImGui::SameLine();
if (ImGui::Button("Save as...")) {
mobileMenuOpen=false;
doAction(GUI_ACTION_SAVE_AS);
}
ImGui::Button("1.1+ .dmf");
ImGui::SameLine();
ImGui::Button("Legacy .dmf");
ImGui::SameLine();
if (ImGui::Button("Export Audio")) {
openFileDialog(GUI_FILE_EXPORT_AUDIO_ONE);
}
ImGui::SameLine();
if (ImGui::Button("Export VGM")) {
openFileDialog(GUI_FILE_EXPORT_VGM);
}
if (ImGui::Button("CmdStream")) {
openFileDialog(GUI_FILE_EXPORT_CMDSTREAM_BINARY);
}
ImGui::SameLine();
if (ImGui::Button("CmdStream Text")) {
openFileDialog(GUI_FILE_EXPORT_CMDSTREAM);
}
if (ImGui::Button("Restore Backup")) {
mobileMenuOpen=false;
doAction(GUI_ACTION_OPEN_BACKUP);
}
ImGui::Separator();
if (ImGui::BeginTabBar("MobileSong")) {
if (ImGui::BeginTabItem("Song Info")) {
drawSongInfo(true);
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Subsongs")) {
drawSubSongs(true);
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Speed")) {
drawSpeed(true);
ImGui::EndTabItem();
}
ImGui::EndTabBar();
}
break;
}
case GUI_SCENE_CHANNELS:
ImGui::Text("Channels here...");
break;
case GUI_SCENE_CHIPS:
ImGui::Text("Chips here...");
break;
case GUI_SCENE_MIXER:
ImGui::Text("What the hell...");
break;
case GUI_SCENE_OTHER: {
if (ImGui::Button("Osc")) {
oscOpen=!oscOpen;
break;
case GUI_SCENE_SONG: {
if (ImGui::Button("New")) {
mobileMenuOpen=false;
//doAction(GUI_ACTION_NEW);
if (modified) {
showWarning("Unsaved changes! Save changes before creating a new song?",GUI_WARN_NEW);
} else {
displayNew=true;
}
ImGui::SameLine();
if (ImGui::Button("ChanOsc")) {
chanOscOpen=!chanOscOpen;
}
ImGui::SameLine();
if (ImGui::Button("RegView")) {
regViewOpen=!regViewOpen;
}
ImGui::SameLine();
if (ImGui::Button("Stats")) {
statsOpen=!statsOpen;
}
if (ImGui::Button("Compat Flags")) {
compatFlagsOpen=!compatFlagsOpen;
}
ImGui::Separator();
ImGui::Button("Panic");
ImGui::SameLine();
if (ImGui::Button("Settings")) {
mobileMenuOpen=false;
settingsOpen=true;
}
ImGui::SameLine();
if (ImGui::Button("Log")) {
logOpen=!logOpen;
}
ImGui::SameLine();
if (ImGui::Button("Debug")) {
debugOpen=!debugOpen;
}
ImGui::SameLine();
if (ImGui::Button("About")) {
mobileMenuOpen=false;
mobileMenuPos=0.0f;
aboutOpen=true;
}
if (ImGui::Button("Switch to Desktop Mode")) {
toggleMobileUI(!mobileUI);
}
int numAmiga=0;
for (int i=0; i<e->song.systemLen; i++) {
if (e->song.system[i]==DIV_SYSTEM_AMIGA) numAmiga++;
}
if (numAmiga) {
ImGui::Text(
"this is NOT ROM export! only use for making sure the\n"
"Furnace Amiga emulator is working properly by\n"
"comparing it with real Amiga output."
);
ImGui::Text("Directory");
ImGui::SameLine();
ImGui::InputText("##AVDPath",&workingDirROMExport);
if (ImGui::Button("Bake Data")) {
std::vector<DivROMExportOutput> out=e->buildROM(DIV_ROM_AMIGA_VALIDATION);
if (workingDirROMExport.size()>0) {
if (workingDirROMExport[workingDirROMExport.size()-1]!=DIR_SEPARATOR) workingDirROMExport+=DIR_SEPARATOR_STR;
}
for (DivROMExportOutput& i: out) {
String path=workingDirROMExport+i.name;
FILE* outFile=ps_fopen(path.c_str(),"wb");
if (outFile!=NULL) {
fwrite(i.data->getFinalBuf(),1,i.data->size(),outFile);
fclose(outFile);
}
i.data->finish();
delete i.data;
}
showError(fmt::sprintf("Done! Baked %d files.",(int)out.size()));
}
}
break;
}
ImGui::SameLine();
if (ImGui::Button("Open")) {
mobileMenuOpen=false;
doAction(GUI_ACTION_OPEN);
}
ImGui::SameLine();
if (ImGui::Button("Save")) {
mobileMenuOpen=false;
doAction(GUI_ACTION_SAVE);
}
ImGui::SameLine();
if (ImGui::Button("Save as...")) {
mobileMenuOpen=false;
doAction(GUI_ACTION_SAVE_AS);
}
ImGui::Button("1.1+ .dmf");
ImGui::SameLine();
ImGui::Button("Legacy .dmf");
ImGui::SameLine();
if (ImGui::Button("Export Audio")) {
openFileDialog(GUI_FILE_EXPORT_AUDIO_ONE);
}
ImGui::SameLine();
if (ImGui::Button("Export VGM")) {
openFileDialog(GUI_FILE_EXPORT_VGM);
}
if (ImGui::Button("CmdStream")) {
openFileDialog(GUI_FILE_EXPORT_CMDSTREAM_BINARY);
}
ImGui::SameLine();
if (ImGui::Button("CmdStream Text")) {
openFileDialog(GUI_FILE_EXPORT_CMDSTREAM);
}
if (ImGui::Button("Restore Backup")) {
mobileMenuOpen=false;
doAction(GUI_ACTION_OPEN_BACKUP);
}
ImGui::Separator();
if (ImGui::BeginTabBar("MobileSong")) {
if (ImGui::BeginTabItem("Song Info")) {
drawSongInfo(true);
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Subsongs")) {
drawSubSongs(true);
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Speed")) {
drawSpeed(true);
ImGui::EndTabItem();
}
ImGui::EndTabBar();
}
break;
}
case GUI_SCENE_CHANNELS:
ImGui::Text("Channels here...");
break;
case GUI_SCENE_CHIPS:
ImGui::Text("Chips here...");
break;
case GUI_SCENE_MIXER:
ImGui::Text("What the hell...");
break;
case GUI_SCENE_OTHER: {
if (ImGui::Button("Osc")) {
oscOpen=!oscOpen;
}
ImGui::SameLine();
if (ImGui::Button("ChanOsc")) {
chanOscOpen=!chanOscOpen;
}
ImGui::SameLine();
if (ImGui::Button("RegView")) {
regViewOpen=!regViewOpen;
}
ImGui::SameLine();
if (ImGui::Button("Stats")) {
statsOpen=!statsOpen;
}
if (ImGui::Button("Compat Flags")) {
compatFlagsOpen=!compatFlagsOpen;
}
ImGui::Separator();
ImGui::Button("Panic");
ImGui::SameLine();
if (ImGui::Button("Settings")) {
mobileMenuOpen=false;
settingsOpen=true;
}
ImGui::SameLine();
if (ImGui::Button("Log")) {
logOpen=!logOpen;
}
ImGui::SameLine();
if (ImGui::Button("Debug")) {
debugOpen=!debugOpen;
}
ImGui::SameLine();
if (ImGui::Button("About")) {
mobileMenuOpen=false;
mobileMenuPos=0.0f;
aboutOpen=true;
}
if (ImGui::Button("Switch to Desktop Mode")) {
toggleMobileUI(!mobileUI);
}
int numAmiga=0;
for (int i=0; i<e->song.systemLen; i++) {
if (e->song.system[i]==DIV_SYSTEM_AMIGA) numAmiga++;
}
if (numAmiga) {
ImGui::Text(
"this is NOT ROM export! only use for making sure the\n"
"Furnace Amiga emulator is working properly by\n"
"comparing it with real Amiga output."
);
ImGui::Text("Directory");
ImGui::SameLine();
ImGui::InputText("##AVDPath",&workingDirROMExport);
if (ImGui::Button("Bake Data")) {
std::vector<DivROMExportOutput> out=e->buildROM(DIV_ROM_AMIGA_VALIDATION);
if (workingDirROMExport.size()>0) {
if (workingDirROMExport[workingDirROMExport.size()-1]!=DIR_SEPARATOR) workingDirROMExport+=DIR_SEPARATOR_STR;
}
for (DivROMExportOutput& i: out) {
String path=workingDirROMExport+i.name;
FILE* outFile=ps_fopen(path.c_str(),"wb");
if (outFile!=NULL) {
fwrite(i.data->getFinalBuf(),1,i.data->size(),outFile);
fclose(outFile);
}
i.data->finish();
delete i.data;
}
showError(fmt::sprintf("Done! Baked %d files.",(int)out.size()));
}
}
break;
}
}
}

View file

@ -245,14 +245,22 @@ void FurnaceGUI::doPullDelete() {
updateScroll(cursor.y);
}
int iCoarse=selStart.xCoarse;
int iFine=selStart.xFine;
for (; iCoarse<=selEnd.xCoarse; iCoarse++) {
SelectionPoint sStart=selStart;
SelectionPoint sEnd=selEnd;
if (selStart.xCoarse==selEnd.xCoarse && selStart.xFine==selEnd.xFine && selStart.y==selEnd.y && settings.pullDeleteRow) {
sStart.xFine=0;
sEnd.xFine=2+e->curPat[sEnd.xCoarse].effectCols*2;
}
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 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<sEnd.xCoarse || iFine<=sEnd.xFine); iFine++) {
maskOut(opMaskPullDelete,iFine);
for (int j=selStart.y; j<e->curSubSong->patLen; j++) {
for (int j=sStart.y; j<e->curSubSong->patLen; j++) {
if (j<e->curSubSong->patLen-1) {
if (iFine==0) {
pat->data[j][iFine]=pat->data[j+1][iFine];
@ -277,15 +285,23 @@ void FurnaceGUI::doInsert() {
prepareUndo(GUI_UNDO_PATTERN_PUSH);
curNibble=false;
int iCoarse=selStart.xCoarse;
int iFine=selStart.xFine;
for (; iCoarse<=selEnd.xCoarse; iCoarse++) {
SelectionPoint sStart=selStart;
SelectionPoint sEnd=selEnd;
if (selStart.xCoarse==selEnd.xCoarse && selStart.xFine==selEnd.xFine && selStart.y==selEnd.y && settings.insertBehavior) {
sStart.xFine=0;
sEnd.xFine=2+e->curPat[sEnd.xCoarse].effectCols*2;
}
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 && (iCoarse<selEnd.xCoarse || iFine<=selEnd.xFine); iFine++) {
for (; iFine<3+e->curPat[iCoarse].effectCols*2 && (iCoarse<sEnd.xCoarse || iFine<=sEnd.xFine); iFine++) {
maskOut(opMaskInsert,iFine);
for (int j=e->curSubSong->patLen-1; j>=selStart.y; j--) {
if (j==selStart.y) {
for (int j=e->curSubSong->patLen-1; j>=sStart.y; j--) {
if (j==sStart.y) {
if (iFine==0) {
pat->data[j][iFine]=0;
}

View file

@ -1,5 +1,6 @@
#include "fileDialog.h"
#include "ImGuiFileDialog.h"
#include "util.h"
#include "../ta-log.h"
#ifdef USE_NFD
@ -152,6 +153,7 @@ bool FurnaceGUIFileDialog::openLoad(String header, std::vector<String> filter, c
ImGuiFileDialog::Instance()->singleClickSel=mobileUI;
ImGuiFileDialog::Instance()->DpiScale=dpiScale;
ImGuiFileDialog::Instance()->mobileMode=mobileUI;
ImGuiFileDialog::Instance()->homePath=getHomeDir();
ImGuiFileDialog::Instance()->OpenModal("FileDialog",header,noSysFilter,path,allowMultiple?999:1,nullptr,0,clickCallback);
}
opened=true;
@ -235,6 +237,7 @@ bool FurnaceGUIFileDialog::openSave(String header, std::vector<String> filter, c
ImGuiFileDialog::Instance()->singleClickSel=false;
ImGuiFileDialog::Instance()->DpiScale=dpiScale;
ImGuiFileDialog::Instance()->mobileMode=mobileUI;
ImGuiFileDialog::Instance()->homePath=getHomeDir();
ImGuiFileDialog::Instance()->OpenModal("FileDialog",header,noSysFilter,path,1,nullptr,ImGuiFileDialogFlags_ConfirmOverwrite);
}
opened=true;

View file

@ -29,8 +29,6 @@
#include "../fileutils.h"
#include "imgui.h"
#include "imgui_internal.h"
#include "imgui_impl_sdl.h"
#include "imgui_impl_sdlrenderer.h"
#include "ImGuiFileDialog.h"
#include "IconsFontAwesome4.h"
#include "misc/cpp/imgui_stdlib.h"
@ -1726,6 +1724,16 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
dpiScale
);
break;
case GUI_FILE_SAMPLE_SAVE_RAW:
if (!dirExists(workingDirSample)) workingDirSample=getHomeDir();
hasOpened=fileDialog->openSave(
"Load Raw Sample",
{"all files", "*"},
".*",
workingDirSample,
dpiScale
);
break;
case GUI_FILE_EXPORT_AUDIO_ONE:
if (!dirExists(workingDirAudioExport)) workingDirAudioExport=getHomeDir();
hasOpened=fileDialog->openSave(
@ -1958,8 +1966,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
//ImGui::GetIO().ConfigFlags|=ImGuiConfigFlags_NavEnableKeyboard;
}
#define FURNACE_ZLIB_COMPRESS
int FurnaceGUI::save(String path, int dmfVersion) {
SafeWriter* w;
logD("saving file...");
@ -1967,7 +1973,7 @@ int FurnaceGUI::save(String path, int dmfVersion) {
if (dmfVersion<24) dmfVersion=24;
w=e->saveDMF(dmfVersion);
} else {
w=e->saveFur();
w=e->saveFur(false,settings.newPatternFormat);
}
if (w==NULL) {
lastError=e->getLastError();
@ -1982,35 +1988,56 @@ int FurnaceGUI::save(String path, int dmfVersion) {
w->finish();
return 1;
}
#ifdef FURNACE_ZLIB_COMPRESS
unsigned char zbuf[131072];
int ret;
z_stream zl;
memset(&zl,0,sizeof(z_stream));
ret=deflateInit(&zl,Z_DEFAULT_COMPRESSION);
if (ret!=Z_OK) {
logE("zlib error!");
lastError="compression error";
fclose(outFile);
w->finish();
return 2;
}
zl.avail_in=w->size();
zl.next_in=w->getFinalBuf();
while (zl.avail_in>0) {
if (settings.compress) {
unsigned char zbuf[131072];
int ret;
z_stream zl;
memset(&zl,0,sizeof(z_stream));
ret=deflateInit(&zl,Z_DEFAULT_COMPRESSION);
if (ret!=Z_OK) {
logE("zlib error!");
lastError="compression error";
fclose(outFile);
w->finish();
return 2;
}
zl.avail_in=w->size();
zl.next_in=w->getFinalBuf();
while (zl.avail_in>0) {
zl.avail_out=131072;
zl.next_out=zbuf;
if ((ret=deflate(&zl,Z_NO_FLUSH))==Z_STREAM_ERROR) {
logE("zlib stream error!");
lastError="zlib stream error";
deflateEnd(&zl);
fclose(outFile);
w->finish();
return 2;
}
size_t amount=131072-zl.avail_out;
if (amount>0) {
if (fwrite(zbuf,1,amount,outFile)!=amount) {
logE("did not write entirely: %s!",strerror(errno));
lastError=strerror(errno);
deflateEnd(&zl);
fclose(outFile);
w->finish();
return 1;
}
}
}
zl.avail_out=131072;
zl.next_out=zbuf;
if ((ret=deflate(&zl,Z_NO_FLUSH))==Z_STREAM_ERROR) {
logE("zlib stream error!");
lastError="zlib stream error";
if ((ret=deflate(&zl,Z_FINISH))==Z_STREAM_ERROR) {
logE("zlib finish stream error!");
lastError="zlib finish stream error";
deflateEnd(&zl);
fclose(outFile);
w->finish();
return 2;
}
size_t amount=131072-zl.avail_out;
if (amount>0) {
if (fwrite(zbuf,1,amount,outFile)!=amount) {
if (131072-zl.avail_out>0) {
if (fwrite(zbuf,1,131072-zl.avail_out,outFile)!=(131072-zl.avail_out)) {
logE("did not write entirely: %s!",strerror(errno));
lastError=strerror(errno);
deflateEnd(&zl);
@ -2019,37 +2046,16 @@ int FurnaceGUI::save(String path, int dmfVersion) {
return 1;
}
}
}
zl.avail_out=131072;
zl.next_out=zbuf;
if ((ret=deflate(&zl,Z_FINISH))==Z_STREAM_ERROR) {
logE("zlib finish stream error!");
lastError="zlib finish stream error";
deflateEnd(&zl);
fclose(outFile);
w->finish();
return 2;
}
if (131072-zl.avail_out>0) {
if (fwrite(zbuf,1,131072-zl.avail_out,outFile)!=(131072-zl.avail_out)) {
} else {
if (fwrite(w->getFinalBuf(),1,w->size(),outFile)!=w->size()) {
logE("did not write entirely: %s!",strerror(errno));
lastError=strerror(errno);
deflateEnd(&zl);
fclose(outFile);
w->finish();
return 1;
}
}
deflateEnd(&zl);
#else
if (fwrite(w->getFinalBuf(),1,w->size(),outFile)!=w->size()) {
logE("did not write entirely: %s!",strerror(errno));
lastError=strerror(errno);
fclose(outFile);
w->finish();
return 1;
}
#endif
fclose(outFile);
w->finish();
backupLock.lock();
@ -2262,6 +2268,11 @@ void FurnaceGUI::exportAudio(String path, DivAudioExportModes mode) {
displayExporting=true;
}
void FurnaceGUI::editStr(String* which) {
editString=which;
displayEditString=true;
}
void FurnaceGUI::showWarning(String what, FurnaceGUIWarnings type) {
warnString=what;
warnAction=type;
@ -2842,8 +2853,14 @@ 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,selStart,selEnd);
if (ImGui::MenuItem("expand",BIND_FOR(GUI_ACTION_PAT_EXPAND_ROWS))) doExpand(2,selStart,selEnd);
ImGui::SetNextItemWidth(120.0f*dpiScale);
if (ImGui::InputInt("collapse/expand amount##CollapseAmount",&collapseAmount,1,1)) {
if (collapseAmount<2) collapseAmount=2;
if (collapseAmount>256) collapseAmount=256;
}
if (ImGui::MenuItem("collapse",BIND_FOR(GUI_ACTION_PAT_COLLAPSE_ROWS))) doCollapse(collapseAmount,selStart,selEnd);
if (ImGui::MenuItem("expand",BIND_FOR(GUI_ACTION_PAT_EXPAND_ROWS))) doExpand(collapseAmount,selStart,selEnd);
if (topMenu) {
ImGui::Separator();
@ -2933,6 +2950,12 @@ int _processEvent(void* instance, SDL_Event* event) {
return ((FurnaceGUI*)instance)->processEvent(event);
}
#if SDL_VERSION_ATLEAST(2,0,17)
#define VALID_MODS KMOD_NUM|KMOD_CAPS|KMOD_SCROLL
#else
#define VALID_MODS KMOD_NUM|KMOD_CAPS
#endif
int FurnaceGUI::processEvent(SDL_Event* ev) {
if (introPos<11.0 && !shortIntro) return 1;
#ifdef IS_MOBILE
@ -2944,7 +2967,7 @@ int FurnaceGUI::processEvent(SDL_Event* ev) {
}
#endif
if (ev->type==SDL_KEYDOWN) {
if (!ev->key.repeat && latchTarget==0 && !wantCaptureKeyboard && !sampleMapWaitingInput && (ev->key.keysym.mod&(~(KMOD_NUM|KMOD_CAPS|KMOD_SCROLL)))==0) {
if (!ev->key.repeat && latchTarget==0 && !wantCaptureKeyboard && !sampleMapWaitingInput && (ev->key.keysym.mod&(~(VALID_MODS)))==0) {
if (settings.notePreviewBehavior==0) return 1;
switch (curWindow) {
case GUI_WINDOW_SAMPLE_EDIT:
@ -3397,6 +3420,7 @@ bool FurnaceGUI::loop() {
logV("portrait: %d (%dx%d)",portrait,scrW,scrH);
logD("window resized to %dx%d",scrW,scrH);
updateWindow=true;
rend->resized(ev);
break;
case SDL_WINDOWEVENT_MOVED:
scrX=ev.window.data1;
@ -3431,6 +3455,7 @@ bool FurnaceGUI::loop() {
break;
}
break;
#if SDL_VERSION_ATLEAST(2,0,17)
case SDL_DISPLAYEVENT: {
switch (ev.display.event) {
case SDL_DISPLAYEVENT_CONNECTED:
@ -3448,6 +3473,7 @@ bool FurnaceGUI::loop() {
}
break;
}
#endif
case SDL_KEYDOWN:
if (!ImGui::GetIO().WantCaptureKeyboard) {
keyDown(ev);
@ -3464,7 +3490,7 @@ bool FurnaceGUI::loop() {
int sampleCountBefore=e->song.sampleLen;
std::vector<DivInstrument*> instruments=e->instrumentFromFile(ev.drop.file);
DivWavetable* droppedWave=NULL;
DivSample* droppedSample=NULL;;
DivSample* droppedSample=NULL;
if (!instruments.empty()) {
if (e->song.sampleLen!=sampleCountBefore) {
e->renderSamplesP();
@ -3510,7 +3536,8 @@ bool FurnaceGUI::loop() {
// update config x/y/w/h values based on scrMax state
if (updateWindow) {
logV("updateWindow is true");
if (!scrMax) {
if (!scrMax && !fullScreen) {
logV("updating scrConf");
scrConfX=scrX;
scrConfY=scrY;
scrConfW=scrW;
@ -3518,8 +3545,8 @@ bool FurnaceGUI::loop() {
}
}
// update canvas size as well
if (SDL_GetRendererOutputSize(sdlRend,&canvasW,&canvasH)!=0) {
logW("loop: error while getting output size! %s",SDL_GetError());
if (!rend->getOutputSize(canvasW,canvasH)) {
logW("loop: error while getting output size!");
} else {
//logV("updateWindow: canvas size %dx%d",canvasW,canvasH);
// and therefore window size
@ -3740,9 +3767,13 @@ bool FurnaceGUI::loop() {
});
}
bool fontsFailed=false;
layoutTimeBegin=SDL_GetPerformanceCounter();
ImGui_ImplSDLRenderer_NewFrame();
if (!rend->newFrame()) {
fontsFailed=true;
}
ImGui_ImplSDL2_NewFrame(sdlWin);
ImGui::NewFrame();
@ -3768,6 +3799,7 @@ bool FurnaceGUI::loop() {
}
}
if (ImGui::BeginMenu("open recent")) {
exitDisabledTimer=1;
for (int i=0; i<(int)recentFile.size(); i++) {
String item=recentFile[i];
if (ImGui::MenuItem(item.c_str())) {
@ -3814,6 +3846,7 @@ bool FurnaceGUI::loop() {
}
ImGui::Separator();
if (ImGui::BeginMenu("export audio...")) {
exitDisabledTimer=1;
if (ImGui::MenuItem("one file")) {
openFileDialog(GUI_FILE_EXPORT_AUDIO_ONE);
}
@ -3832,6 +3865,7 @@ bool FurnaceGUI::loop() {
ImGui::EndMenu();
}
if (ImGui::BeginMenu("export VGM...")) {
exitDisabledTimer=1;
ImGui::Text("settings:");
if (ImGui::BeginCombo("format version",fmt::sprintf("%d.%.2x",vgmExportVersion>>8,vgmExportVersion&0xff).c_str())) {
for (int i=0; i<7; i++) {
@ -3920,6 +3954,7 @@ bool FurnaceGUI::loop() {
}
if (numZSMCompat > 0) {
if (ImGui::BeginMenu("export ZSM...")) {
exitDisabledTimer=1;
ImGui::Text("Commander X16 Zsound Music File");
if (ImGui::InputInt("Tick Rate (Hz)",&zsmExportTickRate,1,2)) {
if (zsmExportTickRate<1) zsmExportTickRate=1;
@ -3940,6 +3975,7 @@ bool FurnaceGUI::loop() {
}
if (numAmiga && settings.iCannotWait) {
if (ImGui::BeginMenu("export Amiga validation data...")) {
exitDisabledTimer=1;
ImGui::Text(
"this is NOT ROM export! only use for making sure the\n"
"Furnace Amiga emulator is working properly by\n"
@ -3970,6 +4006,7 @@ bool FurnaceGUI::loop() {
}
}
if (ImGui::BeginMenu("export command stream...")) {
exitDisabledTimer=1;
ImGui::Text(
"this option exports a text or binary file which\n"
"contains a dump of the internal command stream\n"
@ -3987,6 +4024,7 @@ bool FurnaceGUI::loop() {
}
ImGui::Separator();
if (ImGui::BeginMenu("add chip...")) {
exitDisabledTimer=1;
DivSystem picked=systemPicker();
if (picked!=DIV_SYSTEM_NULL) {
if (!e->addSystem(picked)) {
@ -4003,6 +4041,7 @@ bool FurnaceGUI::loop() {
ImGui::EndMenu();
}
if (ImGui::BeginMenu("configure chip...")) {
exitDisabledTimer=1;
for (int i=0; i<e->song.systemLen; i++) {
if (ImGui::TreeNode(fmt::sprintf("%d. %s##_SYSP%d",i+1,getSystemName(e->song.system[i]),i).c_str())) {
drawSysConf(i,e->song.system[i],e->song.systemFlags[i],true);
@ -4012,6 +4051,7 @@ bool FurnaceGUI::loop() {
ImGui::EndMenu();
}
if (ImGui::BeginMenu("change chip...")) {
exitDisabledTimer=1;
ImGui::Checkbox("Preserve channel positions",&preserveChanPos);
for (int i=0; i<e->song.systemLen; i++) {
if (ImGui::BeginMenu(fmt::sprintf("%d. %s##_SYSC%d",i+1,getSystemName(e->song.system[i]),i).c_str())) {
@ -4031,6 +4071,7 @@ bool FurnaceGUI::loop() {
ImGui::EndMenu();
}
if (ImGui::BeginMenu("remove chip...")) {
exitDisabledTimer=1;
ImGui::Checkbox("Preserve channel positions",&preserveChanPos);
for (int i=0; i<e->song.systemLen; i++) {
if (ImGui::MenuItem(fmt::sprintf("%d. %s##_SYSR%d",i+1,getSystemName(e->song.system[i]),i).c_str())) {
@ -4047,6 +4088,7 @@ bool FurnaceGUI::loop() {
}
ImGui::EndMenu();
}
ImGui::BeginDisabled(exitDisabledTimer);
ImGui::Separator();
if (ImGui::MenuItem("restore backup",BIND_FOR(GUI_ACTION_OPEN_BACKUP))) {
doAction(GUI_ACTION_OPEN_BACKUP);
@ -4059,7 +4101,10 @@ bool FurnaceGUI::loop() {
quit=true;
}
}
ImGui::EndDisabled();
ImGui::EndMenu();
} else {
exitDisabledTimer=0;
}
if (ImGui::BeginMenu("edit")) {
ImGui::Text("...");
@ -4310,6 +4355,7 @@ bool FurnaceGUI::loop() {
MEASURE(log,drawLog());
MEASURE(compatFlags,drawCompatFlags());
MEASURE(stats,drawStats());
MEASURE(chanOsc,drawChanOsc());
} else {
globalWinFlags=0;
ImGui::DockSpaceOverViewport(NULL,lockLayout?(ImGuiDockNodeFlags_NoWindowMenuButton|ImGuiDockNodeFlags_NoMove|ImGuiDockNodeFlags_NoResize|ImGuiDockNodeFlags_NoCloseButton|ImGuiDockNodeFlags_NoDocking|ImGuiDockNodeFlags_NoDockingSplitMe|ImGuiDockNodeFlags_NoDockingSplitOther):0);
@ -4381,7 +4427,7 @@ bool FurnaceGUI::loop() {
portrait=(scrW<scrH);
logV("portrait: %d (%dx%d)",portrait,scrW,scrH);
SDL_GetRendererOutputSize(sdlRend,&canvasW,&canvasH);
rend->getOutputSize(canvasW,canvasH);
#endif
if (patternOpen) nextWindow=GUI_WINDOW_PATTERN;
#ifdef __APPLE__
@ -4447,6 +4493,7 @@ bool FurnaceGUI::loop() {
case GUI_FILE_SAMPLE_OPEN_REPLACE:
case GUI_FILE_SAMPLE_OPEN_REPLACE_RAW:
case GUI_FILE_SAMPLE_SAVE:
case GUI_FILE_SAMPLE_SAVE_RAW:
workingDirSample=fileDialog->getPath()+DIR_SEPARATOR_STR;
break;
case GUI_FILE_EXPORT_AUDIO_ONE:
@ -4714,7 +4761,16 @@ bool FurnaceGUI::loop() {
break;
case GUI_FILE_SAMPLE_SAVE:
if (curSample>=0 && curSample<(int)e->song.sample.size()) {
e->song.sample[curSample]->save(copyOfName.c_str());
if (!e->song.sample[curSample]->save(copyOfName.c_str())) {
showError("could not save sample! open Log Viewer for more information.");
}
}
break;
case GUI_FILE_SAMPLE_SAVE_RAW:
if (curSample>=0 && curSample<(int)e->song.sample.size()) {
if (!e->song.sample[curSample]->saveRaw(copyOfName.c_str())) {
showError("could not save sample! open Log Viewer for more information.");
}
}
break;
case GUI_FILE_EXPORT_AUDIO_ONE:
@ -5020,7 +5076,28 @@ bool FurnaceGUI::loop() {
newSongQuery="";
newSongFirstFrame=true;
displayNew=false;
ImGui::OpenPopup("New Song");
if (settings.newSongBehavior==1) {
e->createNewFromDefaults();
undoHist.clear();
redoHist.clear();
curFileName="";
modified=false;
curNibble=false;
orderNibble=false;
orderCursor=-1;
samplePos=0;
updateSampleTex=true;
selStart=SelectionPoint();
selEnd=SelectionPoint();
cursor=SelectionPoint();
updateWindowTitle();
} else {
ImGui::OpenPopup("New Song");
}
}
if (displayEditString) {
ImGui::OpenPopup("EditString");
}
if (nextWindow==GUI_WINDOW_ABOUT) {
@ -5044,7 +5121,7 @@ bool FurnaceGUI::loop() {
ImGui::EndPopup();
}
//drawTutorial();
drawTutorial();
ImVec2 newSongMinSize=mobileUI?ImVec2(canvasW-(portrait?0:(60.0*dpiScale)),canvasH-60.0*dpiScale):ImVec2(400.0f*dpiScale,200.0f*dpiScale);
ImVec2 newSongMaxSize=ImVec2(canvasW-((mobileUI && !portrait)?(60.0*dpiScale):0),canvasH-(mobileUI?(60.0*dpiScale):0));
@ -5562,6 +5639,27 @@ bool FurnaceGUI::loop() {
ImGui::EndPopup();
}
if (ImGui::BeginPopup("EditString",ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings)) {
if (editString==NULL) {
ImGui::Text("Error! No string provided!");
} else {
if (displayEditString) {
ImGui::SetItemDefaultFocus();
ImGui::SetKeyboardFocusHere();
}
ImGui::InputText("##StringVal",editString);
}
displayEditString=false;
ImGui::SameLine();
if (ImGui::Button("OK") || ImGui::IsKeyPressed(ImGuiKey_Enter,false)) {
editString=NULL;
ImGui::CloseCurrentPopup();
}
ImGui::EndPopup();
} else {
editString=NULL;
}
MEASURE_END(popup);
if (!tutorial.introPlayed || settings.alwaysPlayIntro!=0) {
@ -5602,7 +5700,7 @@ bool FurnaceGUI::loop() {
}
}
logD("saving backup...");
SafeWriter* w=e->saveFur(true);
SafeWriter* w=e->saveFur(true,true);
logV("writing file...");
if (w!=NULL) {
@ -5724,33 +5822,35 @@ bool FurnaceGUI::loop() {
}
}
SDL_SetRenderDrawColor(sdlRend,uiColors[GUI_COLOR_BACKGROUND].x*255,
uiColors[GUI_COLOR_BACKGROUND].y*255,
uiColors[GUI_COLOR_BACKGROUND].z*255,
uiColors[GUI_COLOR_BACKGROUND].w*255);
SDL_RenderClear(sdlRend);
if (!settings.renderClearPos) {
rend->clear(uiColors[GUI_COLOR_BACKGROUND]);
}
renderTimeBegin=SDL_GetPerformanceCounter();
ImGui::Render();
renderTimeEnd=SDL_GetPerformanceCounter();
ImGui_ImplSDLRenderer_RenderDrawData(ImGui::GetDrawData());
drawTimeBegin=SDL_GetPerformanceCounter();
rend->renderGUI();
if (mustClear) {
SDL_RenderClear(sdlRend);
rend->clear(ImVec4(0,0,0,0));
mustClear--;
} else {
if (initialScreenWipe>0.0f && !settings.disableFadeIn) {
WAKE_UP;
initialScreenWipe-=ImGui::GetIO().DeltaTime*5.0f;
if (initialScreenWipe>0.0f) {
SDL_SetRenderDrawBlendMode(sdlRend,SDL_BLENDMODE_BLEND);
SDL_SetRenderDrawColor(sdlRend,0,0,0,255*pow(initialScreenWipe,2.0f));
SDL_RenderFillRect(sdlRend,NULL);
rend->wipe(pow(initialScreenWipe,2.0f));
}
}
}
SDL_RenderPresent(sdlRend);
drawTimeEnd=SDL_GetPerformanceCounter();
rend->present();
if (settings.renderClearPos) {
rend->clear(uiColors[GUI_COLOR_BACKGROUND]);
}
layoutTimeDelta=layoutTimeEnd-layoutTimeBegin;
renderTimeDelta=renderTimeEnd-renderTimeBegin;
drawTimeDelta=drawTimeEnd-drawTimeBegin;
eventTimeDelta=eventTimeEnd-eventTimeBegin;
soloTimeout-=ImGui::GetIO().DeltaTime;
@ -5760,6 +5860,10 @@ bool FurnaceGUI::loop() {
WAKE_UP;
}
if (!ImGui::IsMouseDown(ImGuiMouseButton_Left)) {
exitDisabledTimer=0;
}
wheelX=0;
wheelY=0;
wantScrollList=false;
@ -5772,6 +5876,20 @@ bool FurnaceGUI::loop() {
willCommit=false;
}
if (fontsFailed) {
showError("it appears I couldn't load these fonts. any setting you can check?");
logE("couldn't load fonts");
ImGui::GetIO().Fonts->Clear();
mainFont=ImGui::GetIO().Fonts->AddFontDefault();
patFont=mainFont;
if (rend) rend->destroyFontsTexture();
if (!ImGui::GetIO().Fonts->Build()) {
logE("error again while building font atlas!");
} else {
rend->createFontsTexture();
}
}
if (!editOptsVisible) {
latchTarget=0;
latchNibble=false;
@ -5846,6 +5964,10 @@ bool FurnaceGUI::init() {
basicMode=true;
}
insListDir=e->getConfBool("insListDir",false);
waveListDir=e->getConfBool("waveListDir",false);
sampleListDir=e->getConfBool("sampleListDir",false);
tempoView=e->getConfBool("tempoView",true);
waveHex=e->getConfBool("waveHex",false);
waveSigned=e->getConfBool("waveSigned",false);
@ -5880,6 +6002,7 @@ bool FurnaceGUI::init() {
pianoOptions=e->getConfBool("pianoOptions",pianoOptions);
pianoSharePosition=e->getConfBool("pianoSharePosition",pianoSharePosition);
pianoOptionsSet=e->getConfBool("pianoOptionsSet",pianoOptionsSet);
pianoReadonly=e->getConfBool("pianoReadonly",false);
pianoOffset=e->getConfInt("pianoOffset",pianoOffset);
pianoOffsetEdit=e->getConfInt("pianoOffsetEdit",pianoOffsetEdit);
pianoView=e->getConfInt("pianoView",pianoView);
@ -5888,13 +6011,22 @@ bool FurnaceGUI::init() {
chanOscCols=e->getConfInt("chanOscCols",3);
chanOscColorX=e->getConfInt("chanOscColorX",GUI_OSCREF_CENTER);
chanOscColorY=e->getConfInt("chanOscColorY",GUI_OSCREF_CENTER);
chanOscTextX=e->getConfFloat("chanOscTextX",0.0f);
chanOscTextY=e->getConfFloat("chanOscTextY",0.0f);
chanOscAmplify=e->getConfFloat("chanOscAmplify",0.95f);
chanOscWindowSize=e->getConfFloat("chanOscWindowSize",20.0f);
chanOscWaveCorr=e->getConfBool("chanOscWaveCorr",true);
chanOscOptions=e->getConfBool("chanOscOptions",false);
chanOscNormalize=e->getConfBool("chanOscNormalize",false);
chanOscTextFormat=e->getConfString("chanOscTextFormat","%c");
chanOscColor.x=e->getConfFloat("chanOscColorR",1.0f);
chanOscColor.y=e->getConfFloat("chanOscColorG",1.0f);
chanOscColor.z=e->getConfFloat("chanOscColorB",1.0f);
chanOscColor.w=e->getConfFloat("chanOscColorA",1.0f);
chanOscTextColor.x=e->getConfFloat("chanOscTextColorR",1.0f);
chanOscTextColor.y=e->getConfFloat("chanOscTextColorG",1.0f);
chanOscTextColor.z=e->getConfFloat("chanOscTextColorB",1.0f);
chanOscTextColor.w=e->getConfFloat("chanOscTextColorA",0.75f);
chanOscUseGrad=e->getConfBool("chanOscUseGrad",false);
chanOscGrad.fromString(e->getConfString("chanOscGrad",""));
chanOscGrad.render();
@ -5920,7 +6052,9 @@ bool FurnaceGUI::init() {
e->setAutoNotePoly(noteInputPoly);
SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER,"1");
#if SDL_VERSION_ATLEAST(2,0,17)
SDL_SetHint(SDL_HINT_MOUSE_TOUCH_EVENTS,"0");
#endif
SDL_SetHint(SDL_HINT_TOUCH_MOUSE_EVENTS,"0");
// don't disable compositing on KWin
#if SDL_VERSION_ATLEAST(2,0,22)
@ -5929,7 +6063,18 @@ bool FurnaceGUI::init() {
#endif
// initialize SDL
SDL_Init(SDL_INIT_VIDEO|SDL_INIT_HAPTIC);
logD("initializing video...");
if (SDL_Init(SDL_INIT_VIDEO)!=0) {
logE("could not initialize video! %s",SDL_GetError());
return false;
}
#ifdef IS_MOBILE
logD("initializing haptic...");
if (SDL_Init(SDL_INIT_HAPTIC)!=0) {
logW("could not initialize haptic! %s",SDL_GetError());
}
#endif
const char* videoBackend=SDL_GetCurrentVideoDriver();
if (videoBackend!=NULL) {
@ -6030,7 +6175,28 @@ bool FurnaceGUI::init() {
logV("window size: %dx%d",scrW,scrH);
sdlWin=SDL_CreateWindow("Furnace",scrX,scrY,scrW,scrH,SDL_WINDOW_RESIZABLE|SDL_WINDOW_ALLOW_HIGHDPI|(scrMax?SDL_WINDOW_MAXIMIZED:0)|(fullScreen?SDL_WINDOW_FULLSCREEN_DESKTOP:0));
if (!initRender()) {
if (settings.renderBackend!="SDL" && !settings.renderBackend.empty()) {
settings.renderBackend="";
e->setConf("renderBackend","");
e->saveConf();
lastError=fmt::sprintf("\r\nthe render backend has been set to a safe value. please restart Furnace.");
} else {
lastError=fmt::sprintf("could not init renderer! %s",SDL_GetError());
if (!settings.renderDriver.empty()) {
settings.renderDriver="";
e->setConf("renderDriver","");
e->saveConf();
lastError=fmt::sprintf("\r\nthe render driver has been set to a safe value. please restart Furnace.");
}
}
return false;
}
rend->preInit();
logD("creating window...");
sdlWin=SDL_CreateWindow("Furnace",scrX,scrY,scrW,scrH,SDL_WINDOW_RESIZABLE|SDL_WINDOW_ALLOW_HIGHDPI|(scrMax?SDL_WINDOW_MAXIMIZED:0)|(fullScreen?SDL_WINDOW_FULLSCREEN_DESKTOP:0)|rend->getWindowFlags());
if (sdlWin==NULL) {
lastError=fmt::sprintf("could not open window! %s",SDL_GetError());
return false;
@ -6094,22 +6260,28 @@ bool FurnaceGUI::init() {
SDL_SetHint(SDL_HINT_RENDER_DRIVER,settings.renderDriver.c_str());
}
sdlRend=SDL_CreateRenderer(sdlWin,-1,SDL_RENDERER_ACCELERATED|SDL_RENDERER_PRESENTVSYNC|SDL_RENDERER_TARGETTEXTURE);
if (sdlRend==NULL) {
lastError=fmt::sprintf("could not init renderer! %s",SDL_GetError());
if (!settings.renderDriver.empty()) {
settings.renderDriver="";
e->setConf("renderDriver","");
e->saveConf();
lastError=fmt::sprintf("\r\nthe render driver has been set to a safe value. please restart Furnace.");
logD("starting render backend...");
if (!rend->init(sdlWin)) {
if (settings.renderBackend!="SDL" && !settings.renderBackend.empty()) {
settings.renderBackend="";
//e->setConf("renderBackend","");
//e->saveConf();
//lastError=fmt::sprintf("\r\nthe render backend has been set to a safe value. please restart Furnace.");
} else {
lastError=fmt::sprintf("could not init renderer! %s",SDL_GetError());
if (!settings.renderDriver.empty()) {
settings.renderDriver="";
e->setConf("renderDriver","");
e->saveConf();
lastError=fmt::sprintf("\r\nthe render driver has been set to a safe value. please restart Furnace.");
}
}
return false;
}
// try acquiring the canvas size
if (SDL_GetRendererOutputSize(sdlRend,&canvasW,&canvasH)!=0) {
logW("could not get renderer output size! %s",SDL_GetError());
if (!rend->getOutputSize(canvasW,canvasH)) {
logW("could not get renderer output size!");
} else {
logV("canvas size: %dx%d",canvasW,canvasH);
}
@ -6117,30 +6289,45 @@ bool FurnaceGUI::init() {
// special consideration for Wayland
if (settings.dpiScale<0.5f) {
if (strcmp(videoBackend,"wayland")==0) {
dpiScale=(double)canvasW/(double)scrW;
int realW=scrW;
int realH=scrH;
SDL_GetWindowSize(sdlWin,&realW,&realH);
if (realW<1) {
logW("screen width is zero!\n");
dpiScale=1.0;
} else {
dpiScale=(double)canvasW/(double)realW;
logV("we're on Wayland... scaling factor: %f",dpiScale);
}
}
}
IMGUI_CHECKVERSION();
ImGui::CreateContext();
updateWindowTitle();
ImGui_ImplSDL2_InitForSDLRenderer(sdlWin,sdlRend);
ImGui_ImplSDLRenderer_Init(sdlRend);
rend->clear(ImVec4(0.0,0.0,0.0,1.0));
rend->present();
logD("preparing user interface...");
rend->initGUI(sdlWin);
applyUISettings();
logD("building font...");
if (!ImGui::GetIO().Fonts->Build()) {
logE("error while building font atlas!");
showError("error while loading fonts! please check your settings.");
ImGui::GetIO().Fonts->Clear();
mainFont=ImGui::GetIO().Fonts->AddFontDefault();
patFont=mainFont;
ImGui_ImplSDLRenderer_DestroyFontsTexture();
if (rend) rend->destroyFontsTexture();
if (!ImGui::GetIO().Fonts->Build()) {
logE("error again while building font atlas!");
}
}
logD("preparing layout...");
strncpy(finalLayoutPath,(e->getConfigPath()+String(LAYOUT_INI)).c_str(),4095);
backupPath=e->getConfigPath();
if (backupPath.size()>0) {
@ -6152,8 +6339,6 @@ bool FurnaceGUI::init() {
ImGui::GetIO().ConfigFlags|=ImGuiConfigFlags_DockingEnable;
toggleMobileUI(mobileUI,true);
updateWindowTitle();
for (int i=0; i<DIV_MAX_CHANS; i++) {
oldPat[i]=new DivPattern;
}
@ -6200,6 +6385,7 @@ bool FurnaceGUI::init() {
return curIns;
});
#ifdef IS_MOBILE
vibrator=SDL_HapticOpen(0);
if (vibrator==NULL) {
logD("could not open vibration device: %s",SDL_GetError());
@ -6210,7 +6396,9 @@ bool FurnaceGUI::init() {
logD("vibration not available: %s",SDL_GetError());
}
}
#endif
logI("done!");
return true;
}
@ -6273,6 +6461,11 @@ void FurnaceGUI::commitState() {
e->setConf("spoilerOpen",spoilerOpen);
e->setConf("basicMode",basicMode);
// commit dir state
e->setConf("insListDir",insListDir);
e->setConf("waveListDir",waveListDir);
e->setConf("sampleListDir",sampleListDir);
// commit last window size
e->setConf("lastWindowWidth",scrConfW);
e->setConf("lastWindowHeight",scrConfH);
@ -6310,6 +6503,7 @@ void FurnaceGUI::commitState() {
e->setConf("pianoOptions",pianoOptions);
e->setConf("pianoSharePosition",pianoSharePosition);
e->setConf("pianoOptionsSet",pianoOptionsSet);
e->setConf("pianoReadonly",pianoReadonly);
e->setConf("pianoOffset",pianoOffset);
e->setConf("pianoOffsetEdit",pianoOffsetEdit);
e->setConf("pianoView",pianoView);
@ -6319,13 +6513,22 @@ void FurnaceGUI::commitState() {
e->setConf("chanOscCols",chanOscCols);
e->setConf("chanOscColorX",chanOscColorX);
e->setConf("chanOscColorY",chanOscColorY);
e->setConf("chanOscTextX",chanOscTextX);
e->setConf("chanOscTextY",chanOscTextY);
e->setConf("chanOscAmplify",chanOscAmplify);
e->setConf("chanOscWindowSize",chanOscWindowSize);
e->setConf("chanOscWaveCorr",chanOscWaveCorr);
e->setConf("chanOscOptions",chanOscOptions);
e->setConf("chanOscNormalize",chanOscNormalize);
e->setConf("chanOscTextFormat",chanOscTextFormat);
e->setConf("chanOscColorR",chanOscColor.x);
e->setConf("chanOscColorG",chanOscColor.y);
e->setConf("chanOscColorB",chanOscColor.z);
e->setConf("chanOscColorA",chanOscColor.w);
e->setConf("chanOscTextColorR",chanOscTextColor.x);
e->setConf("chanOscTextColorG",chanOscTextColor.y);
e->setConf("chanOscTextColorB",chanOscTextColor.z);
e->setConf("chanOscTextColorA",chanOscTextColor.w);
e->setConf("chanOscUseGrad",chanOscUseGrad);
e->setConf("chanOscGrad",chanOscGrad.toString());
@ -6342,10 +6545,10 @@ void FurnaceGUI::commitState() {
bool FurnaceGUI::finish() {
commitState();
ImGui_ImplSDLRenderer_Shutdown();
rend->quitGUI();
ImGui_ImplSDL2_Shutdown();
ImGui::DestroyContext();
SDL_DestroyRenderer(sdlRend);
quitRender();
SDL_DestroyWindow(sdlWin);
if (vibrator) {
@ -6365,8 +6568,9 @@ bool FurnaceGUI::finish() {
FurnaceGUI::FurnaceGUI():
e(NULL),
renderBackend(GUI_BACKEND_SDL),
rend(NULL),
sdlWin(NULL),
sdlRend(NULL),
vibrator(NULL),
vibratorAvailable(false),
sampleTex(NULL),
@ -6388,6 +6592,7 @@ FurnaceGUI::FurnaceGUI():
portrait(false),
injectBackUp(false),
mobileMenuOpen(false),
warnColorPushed(false),
wantCaptureKeyboard(false),
oldWantCaptureKeyboard(false),
displayMacroMenu(false),
@ -6401,6 +6606,7 @@ FurnaceGUI::FurnaceGUI():
displayPendingRawSample(false),
snesFilterHex(false),
modTableHex(false),
displayEditString(false),
mobileEdit(false),
vgmExportVersion(0x171),
vgmExportTrailingTicks(-1),
@ -6420,6 +6626,7 @@ FurnaceGUI::FurnaceGUI():
fmPreviewOn(false),
fmPreviewPaused(false),
fmPreviewOPN(NULL),
editString(NULL),
pendingRawSampleDepth(8),
pendingRawSampleChannels(1),
pendingRawSampleUnsigned(false),
@ -6474,7 +6681,6 @@ FurnaceGUI::FurnaceGUI():
loopEnd(-1),
isClipping(0),
extraChannelButtons(0),
patNameTarget(-1),
newSongCategory(0),
latchTarget(0),
wheelX(0),
@ -6486,6 +6692,7 @@ FurnaceGUI::FurnaceGUI():
oldBeat(-1),
oldBar(-1),
curGroove(-1),
exitDisabledTimer(0),
soloTimeout(0.0f),
exportFadeOut(5.0),
editControlsOpen(true),
@ -6524,6 +6731,10 @@ FurnaceGUI::FurnaceGUI():
groovesOpen(false),
introMonOpen(false),
basicMode(true),
shortIntro(false),
insListDir(false),
waveListDir(false),
sampleListDir(false),
clockShowReal(true),
clockShowRow(true),
clockShowBeat(true),
@ -6541,7 +6752,6 @@ FurnaceGUI::FurnaceGUI():
collapseWindow(false),
demandScrollX(false),
fancyPattern(false),
wantPatName(false),
firstFrame(true),
tempoView(true),
waveHex(false),
@ -6558,6 +6768,7 @@ FurnaceGUI::FurnaceGUI():
dragMobileMenu(false),
dragMobileEditButton(false),
wantGrooveListFocus(false),
lastAssetType(0),
curWindow(GUI_WINDOW_NOTHING),
nextWindow(GUI_WINDOW_NOTHING),
curWindowLast(GUI_WINDOW_NOTHING),
@ -6573,6 +6784,7 @@ FurnaceGUI::FurnaceGUI():
wavePreviewLen(32),
wavePreviewHeight(255),
wavePreviewInit(true),
wavePreviewPaused(false),
pgSys(0),
pgAddr(0),
pgVal(0),
@ -6662,6 +6874,9 @@ FurnaceGUI::FurnaceGUI():
renderTimeBegin(0),
renderTimeEnd(0),
renderTimeDelta(0),
drawTimeBegin(0),
drawTimeEnd(0),
drawTimeDelta(0),
eventTimeBegin(0),
eventTimeEnd(0),
eventTimeDelta(0),
@ -6670,11 +6885,14 @@ FurnaceGUI::FurnaceGUI():
sysToMove(-1),
sysToDelete(-1),
opToMove(-1),
assetToMove(-1),
dirToMove(-1),
transposeAmount(0),
randomizeMin(0),
randomizeMax(255),
fadeMin(0),
fadeMax(255),
collapseAmount(2),
scaleMax(100.0f),
fadeMode(false),
randomMode(false),
@ -6698,6 +6916,7 @@ FurnaceGUI::FurnaceGUI():
sampleDragMode(false),
sampleDrag16(false),
sampleZoomAuto(true),
sampleSelTarget(0),
sampleDragTarget(NULL),
sampleDragStart(0,0),
sampleDragAreaSize(0,0),
@ -6732,11 +6951,17 @@ FurnaceGUI::FurnaceGUI():
chanOscColorX(GUI_OSCREF_CENTER),
chanOscColorY(GUI_OSCREF_CENTER),
chanOscWindowSize(20.0f),
chanOscTextX(0.0f),
chanOscTextY(0.0f),
chanOscAmplify(0.95f),
chanOscWaveCorr(true),
chanOscOptions(false),
updateChanOscGradTex(true),
chanOscUseGrad(false),
chanOscNormalize(false),
chanOscTextFormat("%c"),
chanOscColor(1.0f,1.0f,1.0f,1.0f),
chanOscTextColor(1.0f,1.0f,1.0f,0.75f),
chanOscGrad(64,64),
chanOscGradTex(NULL),
followLog(true),
@ -6746,6 +6971,7 @@ FurnaceGUI::FurnaceGUI():
pianoOptions(true),
pianoSharePosition(false),
pianoOptionsSet(false),
pianoReadonly(false),
pianoOffset(6),
pianoOffsetEdit(9),
pianoView(PIANO_LAYOUT_AUTOMATIC),
@ -6755,6 +6981,7 @@ FurnaceGUI::FurnaceGUI():
pianoOctavesEdit(4),
pianoOptions(false),
pianoSharePosition(true),
pianoReadonly(false),
pianoOffset(6),
pianoOffsetEdit(6),
pianoView(PIANO_LAYOUT_STANDARD),
@ -6779,6 +7006,7 @@ FurnaceGUI::FurnaceGUI():
mustClear(2),
initialScreenWipe(1.0f),
introSkipDo(false),
introStopped(false),
curTutorial(-1),
curTutorialStep(0) {
// value keys

View file

@ -23,8 +23,7 @@
#include "../engine/engine.h"
#include "../engine/waveSynth.h"
#include "imgui.h"
#include "imgui_impl_sdl.h"
#include "imgui_impl_sdlrenderer.h"
#include "imgui_impl_sdl2.h"
#include <SDL.h>
#include <fftw3.h>
#include <deque>
@ -70,23 +69,66 @@
#define FM_PREVIEW_SIZE 512
enum FurnaceGUIRenderBackend {
GUI_BACKEND_SDL=0,
GUI_BACKEND_GL,
GUI_BACKEND_DX11
};
#ifdef HAVE_RENDER_SDL
#define GUI_BACKEND_DEFAULT GUI_BACKEND_SDL
#define GUI_BACKEND_DEFAULT_NAME "SDL"
#else
#ifdef HAVE_RENDER_DX11
#define GUI_BACKEND_DEFAULT GUI_BACKEND_DX11
#define GUI_BACKEND_DEFAULT_NAME "DirectX 11"
#else
#define GUI_BACKEND_DEFAULT GUI_BACKEND_GL
#define GUI_BACKEND_DEFAULT_NAME "OpenGL"
#endif
#endif
// TODO:
// - add colors for FM envelope and waveform
// - maybe add "alternate" color for FM modulators/carriers (a bit difficult)
enum FurnaceGUIColors {
GUI_COLOR_BACKGROUND=0,
GUI_COLOR_FRAME_BACKGROUND,
GUI_COLOR_FRAME_BACKGROUND_CHILD,
GUI_COLOR_FRAME_BACKGROUND_POPUP,
GUI_COLOR_MODAL_BACKDROP,
GUI_COLOR_HEADER,
GUI_COLOR_TEXT,
GUI_COLOR_ACCENT_PRIMARY,
GUI_COLOR_ACCENT_SECONDARY,
GUI_COLOR_TITLE_INACTIVE,
GUI_COLOR_TITLE_COLLAPSED,
GUI_COLOR_MENU_BAR,
GUI_COLOR_BORDER,
GUI_COLOR_BORDER_SHADOW,
GUI_COLOR_SCROLL_BACKGROUND,
GUI_COLOR_SCROLL,
GUI_COLOR_SCROLL_HOVER,
GUI_COLOR_SCROLL_ACTIVE,
GUI_COLOR_SEPARATOR,
GUI_COLOR_SEPARATOR_HOVER,
GUI_COLOR_SEPARATOR_ACTIVE,
GUI_COLOR_DOCKING_PREVIEW,
GUI_COLOR_DOCKING_EMPTY,
GUI_COLOR_TABLE_HEADER,
GUI_COLOR_TABLE_BORDER_HARD,
GUI_COLOR_TABLE_BORDER_SOFT,
GUI_COLOR_DRAG_DROP_TARGET,
GUI_COLOR_NAV_HIGHLIGHT,
GUI_COLOR_NAV_WIN_HIGHLIGHT,
GUI_COLOR_NAV_WIN_BACKDROP,
GUI_COLOR_TOGGLE_OFF,
GUI_COLOR_TOGGLE_ON,
GUI_COLOR_EDITING,
GUI_COLOR_SONG_LOOP,
GUI_COLOR_DESTRUCTIVE,
GUI_COLOR_WARNING,
GUI_COLOR_ERROR,
GUI_COLOR_FILE_DIR,
GUI_COLOR_FILE_SONG_NATIVE,
@ -365,6 +407,7 @@ enum FurnaceGUIFileDialogs {
GUI_FILE_SAMPLE_OPEN_REPLACE,
GUI_FILE_SAMPLE_OPEN_REPLACE_RAW,
GUI_FILE_SAMPLE_SAVE,
GUI_FILE_SAMPLE_SAVE_RAW,
GUI_FILE_EXPORT_AUDIO_ONE,
GUI_FILE_EXPORT_AUDIO_PER_SYS,
GUI_FILE_EXPORT_AUDIO_PER_CHANNEL,
@ -565,6 +608,7 @@ enum FurnaceGUIActions {
GUI_ACTION_INS_LIST_EDIT,
GUI_ACTION_INS_LIST_UP,
GUI_ACTION_INS_LIST_DOWN,
GUI_ACTION_INS_LIST_DIR_VIEW,
GUI_ACTION_INS_LIST_MAX,
GUI_ACTION_WAVE_LIST_MIN,
@ -581,6 +625,7 @@ enum FurnaceGUIActions {
GUI_ACTION_WAVE_LIST_EDIT,
GUI_ACTION_WAVE_LIST_UP,
GUI_ACTION_WAVE_LIST_DOWN,
GUI_ACTION_WAVE_LIST_DIR_VIEW,
GUI_ACTION_WAVE_LIST_MAX,
GUI_ACTION_SAMPLE_LIST_MIN,
@ -591,6 +636,7 @@ enum FurnaceGUIActions {
GUI_ACTION_SAMPLE_LIST_OPEN_RAW,
GUI_ACTION_SAMPLE_LIST_OPEN_REPLACE_RAW,
GUI_ACTION_SAMPLE_LIST_SAVE,
GUI_ACTION_SAMPLE_LIST_SAVE_RAW,
GUI_ACTION_SAMPLE_LIST_MOVE_UP,
GUI_ACTION_SAMPLE_LIST_MOVE_DOWN,
GUI_ACTION_SAMPLE_LIST_DELETE,
@ -599,6 +645,7 @@ enum FurnaceGUIActions {
GUI_ACTION_SAMPLE_LIST_DOWN,
GUI_ACTION_SAMPLE_LIST_PREVIEW,
GUI_ACTION_SAMPLE_LIST_STOP_PREVIEW,
GUI_ACTION_SAMPLE_LIST_DIR_VIEW,
GUI_ACTION_SAMPLE_LIST_MAX,
GUI_ACTION_SAMPLE_MIN,
@ -1165,7 +1212,7 @@ struct FurnaceGUIQueryResult {
struct FurnaceGUIImage {
unsigned char* data;
SDL_Texture* tex;
void* tex;
int width, height, ch;
FurnaceGUIImage():
@ -1187,15 +1234,52 @@ struct FurnaceGUIPerfMetric {
elapsed(0) {}
};
enum FurnaceGUIBlendMode {
GUI_BLEND_MODE_NONE=0,
GUI_BLEND_MODE_BLEND,
GUI_BLEND_MODE_ADD,
GUI_BLEND_MODE_MULTIPLY
};
class FurnaceGUIRender {
public:
virtual ImTextureID getTextureID(void* which);
virtual bool lockTexture(void* which, void** data, int* pitch);
virtual bool unlockTexture(void* which);
virtual bool updateTexture(void* which, void* data, int pitch);
virtual void* createTexture(bool dynamic, int width, int height);
virtual bool destroyTexture(void* which);
virtual void setTextureBlendMode(void* which, FurnaceGUIBlendMode mode);
virtual void setBlendMode(FurnaceGUIBlendMode mode);
virtual void resized(const SDL_Event& ev);
virtual void clear(ImVec4 color);
virtual bool newFrame();
virtual void createFontsTexture();
virtual void destroyFontsTexture();
virtual void renderGUI();
virtual void wipe(float alpha);
virtual void present();
virtual bool getOutputSize(int& w, int& h);
virtual int getWindowFlags();
virtual void preInit();
virtual bool init(SDL_Window* win);
virtual void initGUI(SDL_Window* win);
virtual void quitGUI();
virtual bool quit();
virtual ~FurnaceGUIRender();
};
class FurnaceGUI {
DivEngine* e;
FurnaceGUIRenderBackend renderBackend;
FurnaceGUIRender* rend;
SDL_Window* sdlWin;
SDL_Renderer* sdlRend;
SDL_Haptic* vibrator;
bool vibratorAvailable;
SDL_Texture* sampleTex;
void* sampleTex;
int sampleTexW, sampleTexH;
bool updateSampleTex;
@ -1205,6 +1289,7 @@ class FurnaceGUI {
String workingDirLayout, workingDirROM, workingDirTest;
String mmlString[32];
String mmlStringW, mmlStringSNES, grooveString, grooveListString, mmlStringModTable;
String folderString;
std::vector<DivSystem> sysSearchResults;
std::vector<FurnaceGUISysDef> newSongSearchResults;
@ -1214,10 +1299,10 @@ class FurnaceGUI {
bool quit, warnQuit, willCommit, edit, modified, displayError, displayExporting, vgmExportLoop, zsmExportLoop, vgmExportPatternHints;
bool vgmExportDirectStream, displayInsTypeList;
bool portrait, injectBackUp, mobileMenuOpen;
bool portrait, injectBackUp, mobileMenuOpen, warnColorPushed;
bool wantCaptureKeyboard, oldWantCaptureKeyboard, displayMacroMenu;
bool displayNew, fullScreen, preserveChanPos, wantScrollList, noteInputPoly;
bool displayPendingIns, pendingInsSingle, displayPendingRawSample, snesFilterHex, modTableHex;
bool displayPendingIns, pendingInsSingle, displayPendingRawSample, snesFilterHex, modTableHex, displayEditString;
bool mobileEdit;
bool willExport[DIV_MAX_CHIPS];
int vgmExportVersion;
@ -1235,6 +1320,7 @@ class FurnaceGUI {
short fmPreview[FM_PREVIEW_SIZE];
bool updateFMPreview, fmPreviewOn, fmPreviewPaused;
void* fmPreviewOPN;
String* editString;
String pendingRawSample;
int pendingRawSampleDepth, pendingRawSampleChannels;
@ -1400,8 +1486,10 @@ class FurnaceGUI {
int channelFont;
int channelTextCenter;
int midiOutClock;
int midiOutTime;
int midiOutProgramChange;
int midiOutMode;
int midiOutTimeRate;
int maxRecentFile;
int centerPattern;
int ordersCursor;
@ -1415,6 +1503,12 @@ class FurnaceGUI {
int alwaysPlayIntro;
int iCannotWait;
int orderButtonPos;
int compress;
int newPatternFormat;
int renderClearPos;
int insertBehavior;
int pullDeleteRow;
int newSongBehavior;
unsigned int maxUndoSteps;
String mainFontPath;
String patFontPath;
@ -1422,6 +1516,7 @@ class FurnaceGUI {
String midiInDevice;
String midiOutDevice;
String c163Name;
String renderBackend;
String renderDriver;
String initialSysName;
String noteOffLabel;
@ -1543,8 +1638,10 @@ class FurnaceGUI {
channelFont(1),
channelTextCenter(1),
midiOutClock(0),
midiOutTime(0),
midiOutProgramChange(0),
midiOutMode(1),
midiOutTimeRate(0),
maxRecentFile(10),
centerPattern(0),
ordersCursor(1),
@ -1558,6 +1655,12 @@ class FurnaceGUI {
alwaysPlayIntro(0),
iCannotWait(0),
orderButtonPos(2),
compress(1),
newPatternFormat(1),
renderClearPos(0),
insertBehavior(1),
pullDeleteRow(1),
newSongBehavior(0),
maxUndoSteps(100),
mainFontPath(""),
patFontPath(""),
@ -1565,6 +1668,7 @@ class FurnaceGUI {
midiInDevice(""),
midiOutDevice(""),
c163Name(""),
renderBackend(""),
renderDriver(""),
initialSysName("Sega Genesis/Mega Drive"),
noteOffLabel("OFF"),
@ -1577,12 +1681,12 @@ class FurnaceGUI {
struct Tutorial {
int userComesFrom;
bool introPlayed;
bool welcome;
bool protoWelcome;
bool taken[GUI_TUTORIAL_MAX];
Tutorial():
userComesFrom(0),
introPlayed(false),
welcome(false) {
protoWelcome(false) {
memset(taken,0,GUI_TUTORIAL_MAX*sizeof(bool));
}
} tutorial;
@ -1592,9 +1696,9 @@ class FurnaceGUI {
DivInstrument* prevInsData;
int curIns, curWave, curSample, curOctave, curOrder, prevIns, oldRow, oldOrder, oldOrder1, editStep, exportLoops, soloChan,orderEditMode, orderCursor;
int loopOrder, loopRow, loopEnd, isClipping, extraChannelButtons, patNameTarget, newSongCategory, latchTarget;
int loopOrder, loopRow, loopEnd, isClipping, extraChannelButtons, newSongCategory, latchTarget;
int wheelX, wheelY, dragSourceX, dragSourceXFine, dragSourceY, dragDestinationX, dragDestinationXFine, dragDestinationY, oldBeat, oldBar;
int curGroove;
int curGroove, exitDisabledTimer;
float soloTimeout;
double exportFadeOut;
@ -1608,14 +1712,16 @@ class FurnaceGUI {
bool groovesOpen, introMonOpen;
bool basicMode, shortIntro;
bool insListDir, waveListDir, sampleListDir;
bool clockShowReal, clockShowRow, clockShowBeat, clockShowMetro, clockShowTime;
float clockMetroTick[16];
SelectionPoint selStart, selEnd, cursor, cursorDrag, dragStart, dragEnd;
bool selecting, selectingFull, dragging, curNibble, orderNibble, followOrders, followPattern, changeAllOrders, mobileUI;
bool collapseWindow, demandScrollX, fancyPattern, wantPatName, firstFrame, tempoView, waveHex, waveSigned, waveGenVisible, lockLayout, editOptsVisible, latchNibble, nonLatchNibble;
bool collapseWindow, demandScrollX, fancyPattern, firstFrame, tempoView, waveHex, waveSigned, waveGenVisible, lockLayout, editOptsVisible, latchNibble, nonLatchNibble;
bool keepLoopAlive, keepGrooveAlive, orderScrollLocked, orderScrollTolerance, dragMobileMenu, dragMobileEditButton, wantGrooveListFocus;
unsigned char lastAssetType;
FurnaceGUIWindows curWindow, nextWindow, curWindowLast;
std::atomic<FurnaceGUIWindows> curWindowThreadSafe;
float peak[DIV_MAX_OUTPUTS];
@ -1633,7 +1739,7 @@ class FurnaceGUI {
DivWaveSynth wavePreview;
int wavePreviewLen, wavePreviewHeight;
bool wavePreviewInit;
bool wavePreviewInit, wavePreviewPaused;
// bit 31: ctrl
// bit 30: reserved for SDL scancode mask
@ -1770,6 +1876,7 @@ class FurnaceGUI {
int layoutTimeBegin, layoutTimeEnd, layoutTimeDelta;
int renderTimeBegin, renderTimeEnd, renderTimeDelta;
int drawTimeBegin, drawTimeEnd, drawTimeDelta;
int eventTimeBegin, eventTimeEnd, eventTimeDelta;
FurnaceGUIPerfMetric perfMetrics[64];
@ -1781,6 +1888,7 @@ class FurnaceGUI {
std::map<FurnaceGUIImages,FurnaceGUIImage*> images;
int chanToMove, sysToMove, sysToDelete, opToMove;
int assetToMove, dirToMove;
ImVec2 patWindowPos, patWindowSize;
@ -1789,7 +1897,7 @@ class FurnaceGUI {
ImVec2 noteCellSize, insCellSize, volCellSize, effectCellSize, effectValCellSize;
SelectionPoint sel1, sel2;
int dummyRows, demandX;
int transposeAmount, randomizeMin, randomizeMax, fadeMin, fadeMax;
int transposeAmount, randomizeMin, randomizeMax, fadeMin, fadeMax, collapseAmount;
float scaleMax;
bool fadeMode, randomMode, haveHitBounds, pendingStepUpdate;
@ -1811,6 +1919,9 @@ class FurnaceGUI {
int sampleSelStart, sampleSelEnd;
bool sampleInfo, sampleCompatRate;
bool sampleDragActive, sampleDragMode, sampleDrag16, sampleZoomAuto;
// 0: start
// 1: end
unsigned char sampleSelTarget;
void* sampleDragTarget;
ImVec2 sampleDragStart;
ImVec2 sampleDragAreaSize;
@ -1841,11 +1952,12 @@ class FurnaceGUI {
// per-channel oscilloscope
int chanOscCols, chanOscColorX, chanOscColorY;
float chanOscWindowSize;
bool chanOscWaveCorr, chanOscOptions, updateChanOscGradTex, chanOscUseGrad;
ImVec4 chanOscColor;
float chanOscWindowSize, chanOscTextX, chanOscTextY, chanOscAmplify;
bool chanOscWaveCorr, chanOscOptions, updateChanOscGradTex, chanOscUseGrad, chanOscNormalize;
String chanOscTextFormat;
ImVec4 chanOscColor, chanOscTextColor;
Gradient2D chanOscGrad;
SDL_Texture* chanOscGradTex;
void* chanOscGradTex;
float chanOscLP0[DIV_MAX_CHANS];
float chanOscLP1[DIV_MAX_CHANS];
float chanOscVol[DIV_MAX_CHANS];
@ -1897,6 +2009,7 @@ class FurnaceGUI {
bool pianoOptions, pianoSharePosition, pianoOptionsSet;
float pianoKeyHit[180];
bool pianoKeyPressed[180];
bool pianoReadonly;
int pianoOffset, pianoOffsetEdit;
int pianoView, pianoInputPadMode;
@ -1931,7 +2044,7 @@ class FurnaceGUI {
double monitorPos;
int mustClear;
float initialScreenWipe;
bool introSkipDo;
bool introSkipDo, introStopped;
ImVec2 introMin, introMax;
// tutorial
@ -1973,6 +2086,11 @@ class FurnaceGUI {
void pushAccentColors(const ImVec4& one, const ImVec4& two, const ImVec4& border, const ImVec4& borderShadow);
void popAccentColors();
void pushDestColor();
void popDestColor();
void pushWarningColor(bool warnCond, bool errorCond=false);
void popWarningColor();
float calcBPM(const DivGroovePattern& speeds, float hz, int vN, int vD);
void patternRow(int i, bool isPlaying, float lineHeight, int chans, int ord, const DivPattern** patCache, bool inhibitSel);
@ -1986,6 +2104,10 @@ class FurnaceGUI {
void actualWaveList();
void actualSampleList();
void insListItem(int index, int dir, int asset);
void waveListItem(int index, float* wavePreview, int dir, int asset);
void sampleListItem(int index, int dir, int asset);
void toggleMobileUI(bool enable, bool force=false);
void pushToggleColors(bool status);
@ -1994,7 +2116,7 @@ class FurnaceGUI {
void highlightWindow(const char* winName);
FurnaceGUIImage* getImage(FurnaceGUIImages image);
SDL_Texture* getTexture(FurnaceGUIImages image, SDL_BlendMode blendMode=SDL_BLENDMODE_BLEND);
void* getTexture(FurnaceGUIImages image, FurnaceGUIBlendMode blendMode=GUI_BLEND_MODE_BLEND);
void drawImage(ImDrawList* dl, FurnaceGUIImages image, const ImVec2& pos, const ImVec2& scale, double rotate, const ImVec2& uvMin, const ImVec2& uvMax, const ImVec4& imgColor);
void drawMobileControls();
@ -2143,9 +2265,13 @@ class FurnaceGUI {
String encodeKeyMap(std::map<int,int>& map);
void decodeKeyMap(std::map<int,int>& map, String source);
bool initRender();
bool quitRender();
const char* getSystemName(DivSystem which);
public:
void editStr(String* which);
void showWarning(String what, FurnaceGUIWarnings type);
void showError(String what);
String getLastError();

View file

@ -506,7 +506,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={
D("WINDOW_ABOUT", "About", 0),
D("WINDOW_SETTINGS", "Settings", 0),
D("WINDOW_MIXER", "Mixer", 0),
D("WINDOW_DEBUG", "Debug Menu", 0),
D("WINDOW_DEBUG", "Debug Menu", FURKMOD_CMD|FURKMOD_SHIFT|SDLK_d),
D("WINDOW_OSCILLOSCOPE", "Oscilloscope (master)", 0),
D("WINDOW_VOL_METER", "Volume Meter", 0),
D("WINDOW_STATS", "Statistics", 0),
@ -610,6 +610,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={
D("INS_LIST_EDIT", "Edit", FURKMOD_SHIFT|SDLK_RETURN),
D("INS_LIST_UP", "Cursor up", SDLK_UP),
D("INS_LIST_DOWN", "Cursor down", SDLK_DOWN),
D("INS_LIST_DIR_VIEW", "Toggle folders/standard view", FURKMOD_CMD|SDLK_v),
D("INS_LIST_MAX", "", NOT_AN_ACTION),
D("WAVE_LIST_MIN", "---Wavetable list", NOT_AN_ACTION),
@ -626,6 +627,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={
D("WAVE_LIST_EDIT", "Edit", FURKMOD_SHIFT|SDLK_RETURN),
D("WAVE_LIST_UP", "Cursor up", SDLK_UP),
D("WAVE_LIST_DOWN", "Cursor down", SDLK_DOWN),
D("WAVE_LIST_DIR_VIEW", "Toggle folders/standard view", FURKMOD_CMD|SDLK_v),
D("WAVE_LIST_MAX", "", NOT_AN_ACTION),
D("SAMPLE_LIST_MIN", "---Sample list", NOT_AN_ACTION),
@ -636,6 +638,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={
D("SAMPLE_LIST_OPEN_RAW", "Import raw data", 0),
D("SAMPLE_LIST_OPEN_REPLACE_RAW", "Import raw data (replace current)", 0),
D("SAMPLE_LIST_SAVE", "Save", 0),
D("SAMPLE_LIST_SAVE_RAW", "Save (raw)", 0),
D("SAMPLE_LIST_MOVE_UP", "Move up", FURKMOD_SHIFT|SDLK_UP),
D("SAMPLE_LIST_MOVE_DOWN", "Move down", FURKMOD_SHIFT|SDLK_DOWN),
D("SAMPLE_LIST_DELETE", "Delete", 0),
@ -644,6 +647,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={
D("SAMPLE_LIST_DOWN", "Cursor down", SDLK_DOWN),
D("SAMPLE_LIST_PREVIEW", "Preview", 0),
D("SAMPLE_LIST_STOP_PREVIEW", "Stop preview", 0),
D("SAMPLE_LIST_DIR_VIEW", "Toggle folders/standard view", FURKMOD_CMD|SDLK_v),
D("SAMPLE_LIST_MAX", "", NOT_AN_ACTION),
D("SAMPLE_MIN", "---Sample editor", NOT_AN_ACTION),
@ -706,17 +710,41 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={
const FurnaceGUIColorDef guiColors[GUI_COLOR_MAX]={
D(GUI_COLOR_BACKGROUND,"Background",ImVec4(0.1f,0.1f,0.1f,1.0f)),
D(GUI_COLOR_FRAME_BACKGROUND,"",ImVec4(0.0f,0.0f,0.0f,0.85f)),
D(GUI_COLOR_FRAME_BACKGROUND_CHILD,"",ImVec4(0.0f,0.0f,0.0f,0.0f)),
D(GUI_COLOR_FRAME_BACKGROUND_POPUP,"",ImVec4(0.08f,0.08f,0.08f,0.94f)),
D(GUI_COLOR_MODAL_BACKDROP,"",ImVec4(0.0f,0.0f,0.0f,0.55f)),
D(GUI_COLOR_HEADER,"",ImVec4(0.2f,0.2f,0.2f,1.0f)),
D(GUI_COLOR_TEXT,"",ImVec4(1.0f,1.0f,1.0f,1.0f)),
D(GUI_COLOR_ACCENT_PRIMARY,"",ImVec4(0.06f,0.53f,0.98f,1.0f)),
D(GUI_COLOR_ACCENT_SECONDARY,"",ImVec4(0.26f,0.59f,0.98f,1.0f)),
D(GUI_COLOR_TITLE_INACTIVE,"",ImVec4(0.04f,0.04f,0.04f,1.0f)),
D(GUI_COLOR_TITLE_COLLAPSED,"",ImVec4(0.0f,0.0f,0.0f,0.51f)),
D(GUI_COLOR_MENU_BAR,"",ImVec4(0.14f,0.14f,0.14f,1.0f)),
D(GUI_COLOR_BORDER,"",ImVec4(0.43f,0.43f,0.5f,0.5f)),
D(GUI_COLOR_BORDER_SHADOW,"",ImVec4(0.0f,0.0f,0.0f,0.0f)),
D(GUI_COLOR_SCROLL_BACKGROUND,"",ImVec4(0.02f,0.02f,0.02f,0.33f)),
D(GUI_COLOR_SCROLL,"",ImVec4(0.31f,0.31f,0.31f,1.0f)),
D(GUI_COLOR_SCROLL_HOVER,"",ImVec4(0.41f,0.41f,0.41f,1.0f)),
D(GUI_COLOR_SCROLL_ACTIVE,"",ImVec4(0.51f,0.51f,0.51f,1.0f)),
D(GUI_COLOR_SEPARATOR,"",ImVec4(0.43f,0.43f,0.5f,0.5f)),
D(GUI_COLOR_SEPARATOR_HOVER,"",ImVec4(0.1f,0.4f,0.75f,0.78f)),
D(GUI_COLOR_SEPARATOR_ACTIVE,"",ImVec4(0.1f,0.4f,0.75f,1.0f)),
D(GUI_COLOR_DOCKING_PREVIEW,"",ImVec4(0.26f,0.59f,0.98f,0.7f)),
D(GUI_COLOR_DOCKING_EMPTY,"",ImVec4(0.2f,0.2f,0.2f,1.0f)),
D(GUI_COLOR_TABLE_HEADER,"",ImVec4(0.19f,0.19f,0.2f,1.0f)),
D(GUI_COLOR_TABLE_BORDER_HARD,"",ImVec4(0.31f,0.31f,0.35f,1.0f)),
D(GUI_COLOR_TABLE_BORDER_SOFT,"",ImVec4(0.23f,0.23f,0.25f,1.0f)),
D(GUI_COLOR_DRAG_DROP_TARGET,"",ImVec4(1.0f,1.0f,0.0f,0.9f)),
D(GUI_COLOR_NAV_HIGHLIGHT,"",ImVec4(0.26f,0.59f,0.98f,1.0f)),
D(GUI_COLOR_NAV_WIN_HIGHLIGHT,"",ImVec4(1.0f,1.0f,1.0f,0.7f)),
D(GUI_COLOR_NAV_WIN_BACKDROP,"",ImVec4(0.8f,0.8f,0.8f,0.2f)),
D(GUI_COLOR_TOGGLE_OFF,"",ImVec4(0.2f,0.2f,0.2f,1.0f)),
D(GUI_COLOR_TOGGLE_ON,"",ImVec4(0.2f,0.6f,0.2f,1.0f)),
D(GUI_COLOR_EDITING,"",ImVec4(0.2f,0.1f,0.1f,1.0f)),
D(GUI_COLOR_SONG_LOOP,"",ImVec4(0.3f,0.5f,0.8f,0.4f)),
D(GUI_COLOR_DESTRUCTIVE,"",ImVec4(1.0f,0.2f,0.2f,1.0f)),
D(GUI_COLOR_WARNING,"",ImVec4(0.98f,0.98f,0.06f,1.0f)),
D(GUI_COLOR_ERROR,"",ImVec4(0.98f,0.06f,0.11f,1.0f)),
D(GUI_COLOR_FILE_DIR,"",ImVec4(0.0f,1.0f,1.0f,1.0f)),
D(GUI_COLOR_FILE_SONG_NATIVE,"",ImVec4(0.5f,1.0f,0.5f,1.0f)),

View file

@ -15,9 +15,6 @@
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* this license only applies to the code. for the license of each font used,
* see `papers/`.
*/
#include "gui.h"
@ -49,7 +46,7 @@ const unsigned int imageLen[GUI_IMAGE_MAX]={
image_pat_size
};
SDL_Texture* FurnaceGUI::getTexture(FurnaceGUIImages image, SDL_BlendMode blendMode) {
void* FurnaceGUI::getTexture(FurnaceGUIImages image, FurnaceGUIBlendMode blendMode) {
FurnaceGUIImage* img=getImage(image);
if (img==NULL) return NULL;
@ -57,14 +54,14 @@ SDL_Texture* FurnaceGUI::getTexture(FurnaceGUIImages image, SDL_BlendMode blendM
if (img->width<=0 || img->height<=0) return NULL;
if (img->tex==NULL) {
img->tex=SDL_CreateTexture(sdlRend,SDL_PIXELFORMAT_ABGR8888,SDL_TEXTUREACCESS_STATIC,img->width,img->height);
img->tex=rend->createTexture(false,img->width,img->height);
if (img->tex==NULL) {
logE("error while creating image %d texture! %s",(int)image,SDL_GetError());
return NULL;
}
SDL_SetTextureBlendMode(img->tex,blendMode);
rend->setTextureBlendMode(img->tex,blendMode);
if (SDL_UpdateTexture(img->tex,NULL,img->data,img->width*4)!=0) {
if (!rend->updateTexture(img->tex,img->data,img->width*4)) {
logE("error while updating texture of image %d! %s",(int)image,SDL_GetError());
}
}

View file

@ -2262,9 +2262,11 @@ void FurnaceGUI::drawInsEdit() {
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
ImGui::PushID(2+curIns);
if (ImGui::InputText("##Name",&ins->name)) {
MARK_MODIFIED;
}
ImGui::PopID();
ImGui::TableNextRow();
ImGui::TableNextColumn();
@ -5153,7 +5155,9 @@ void FurnaceGUI::drawInsEdit() {
wavePreview2[i]=wave2->data[i];
}
}
if (ins->ws.enabled) wavePreview.tick(true);
if (ins->ws.enabled && (!wavePreviewPaused || wavePreviewInit)) {
wavePreview.tick(true);
}
for (int i=0; i<wavePreviewLen; i++) {
if (wave2->data[i]>wavePreviewHeight) {
wavePreview3[i]=wavePreviewHeight;
@ -5199,9 +5203,43 @@ void FurnaceGUI::drawInsEdit() {
}
}
ImGui::TableNextColumn();
if (ImGui::Button(wavePreviewPaused?(ICON_FA_PLAY "##WSPause"):(ICON_FA_PAUSE "##WSPause"))) {
wavePreviewPaused=!wavePreviewPaused;
}
if (ImGui::IsItemHovered()) {
if (wavePreviewPaused) {
ImGui::SetTooltip("Resume preview");
} else {
ImGui::SetTooltip("Pause preview");
}
}
ImGui::SameLine();
if (ImGui::Button(ICON_FA_REPEAT "##WSRestart")) {
wavePreviewInit=true;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Restart preview");
}
ImGui::SameLine();
if (ImGui::Button(ICON_FA_UPLOAD "##WSCopy")) {
curWave=e->addWave();
if (curWave==-1) {
showError("too many wavetables!");
} else {
wantScrollList=true;
MARK_MODIFIED;
RESET_WAVE_MACRO_ZOOM;
nextWindow=GUI_WINDOW_WAVE_EDIT;
DivWavetable* copyWave=e->song.wave[curWave];
copyWave->len=wavePreviewLen;
copyWave->max=wavePreviewHeight;
memcpy(copyWave->data,wavePreview.output,256*sizeof(int));
}
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Copy to new wavetable");
}
ImGui::SameLine();
ImGui::Text("(%d×%d)",wavePreviewLen,wavePreviewHeight+1);
ImGui::EndTable();

View file

@ -19,12 +19,13 @@
#define _USE_MATH_DEFINES
#include "gui.h"
#include "../ta-log.h"
#include "imgui_internal.h"
#include <fmt/printf.h>
void FurnaceGUI::drawImage(ImDrawList* dl, FurnaceGUIImages image, const ImVec2& pos, const ImVec2& scale, double rotate, const ImVec2& uvMin, const ImVec2& uvMax, const ImVec4& imgColor) {
FurnaceGUIImage* imgI=getImage(image);
SDL_Texture* img=getTexture(image);
void* img=getTexture(image);
float squareSize=MAX(introMax.x-introMin.x,introMax.y-introMin.y);
float uDiff=uvMax.x-uvMin.x;
@ -69,10 +70,12 @@ void FurnaceGUI::drawImage(ImDrawList* dl, FurnaceGUIImages image, const ImVec2&
ImU32 colorConverted=ImGui::GetColorU32(imgColor);
dl->AddImageQuad(img,quad0,quad1,quad2,quad3,uv0,uv1,uv2,uv3,colorConverted);
dl->AddImageQuad(rend->getTextureID(img),quad0,quad1,quad2,quad3,uv0,uv1,uv2,uv3,colorConverted);
}
void FurnaceGUI::endIntroTune() {
if (introStopped) return;
logV("ending intro");
stop();
if (curFileName.empty()) {
e->createNewFromDefaults();
@ -95,6 +98,8 @@ void FurnaceGUI::endIntroTune() {
selEnd=SelectionPoint();
cursor=SelectionPoint();
updateWindowTitle();
updateScroll(0);
introStopped=true;
}
void FurnaceGUI::drawIntro(double introTime, bool monitor) {
@ -162,7 +167,7 @@ void FurnaceGUI::drawIntro(double introTime, bool monitor) {
getTexture(GUI_IMAGE_TALOGO);
getTexture(GUI_IMAGE_TACHIP);
getTexture(GUI_IMAGE_LOGO);
getTexture(GUI_IMAGE_INTROBG,SDL_BLENDMODE_ADD);
getTexture(GUI_IMAGE_INTROBG,GUI_BLEND_MODE_ADD);
if (monitor) {
ImVec2 textPos=ImLerp(top,bottom,ImVec2(0.5,0.5));
@ -290,7 +295,7 @@ void FurnaceGUI::drawIntro(double introTime, bool monitor) {
if (introSkipDo) {
introSkip+=ImGui::GetIO().DeltaTime;
if (introSkip>=0.5) {
if (e->isPlaying()) endIntroTune();
if (!shortIntro) endIntroTune();
introPos=0.1;
if (introSkip>=0.75) introPos=12.0;
}
@ -317,7 +322,7 @@ void FurnaceGUI::drawIntro(double introTime, bool monitor) {
e->setRepeatPattern(false);
play();
}
if (e->isPlaying() && introPos>=10.0 && !shortIntro) endIntroTune();
if (introPos>=10.0 && !shortIntro) endIntroTune();
introPos+=ImGui::GetIO().DeltaTime;
if (introPos>=(shortIntro?1.0:11.0)) {
introPos=12.0;
@ -325,5 +330,7 @@ void FurnaceGUI::drawIntro(double introTime, bool monitor) {
commitTutorial();
}
}
} else if (!shortIntro) {
endIntroTune();
}
}

View file

@ -49,11 +49,11 @@ void _popPartBlend(const ImDrawList* drawList, const ImDrawCmd* cmd) {
}
void FurnaceGUI::pushPartBlend() {
SDL_SetRenderDrawBlendMode(sdlRend,SDL_BLENDMODE_ADD);
rend->setBlendMode(GUI_BLEND_MODE_ADD);
}
void FurnaceGUI::popPartBlend() {
SDL_SetRenderDrawBlendMode(sdlRend,SDL_BLENDMODE_BLEND);
rend->setBlendMode(GUI_BLEND_MODE_BLEND);
}
// draw a pattern row
@ -287,7 +287,7 @@ inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int
if (pat->data[i][index]>0xff) {
snprintf(id,63,"??##PE%d_%d_%d",k,i,j);
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_INVALID]);
} else if (pat->data[i][index]>0x10 || settings.oneDigitEffects==0) {
} else if (pat->data[i][index]>=0x10 || settings.oneDigitEffects==0) {
const unsigned char data=pat->data[i][index];
snprintf(id,63,"%.2X##PE%d_%d_%d",data,k,i,j);
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[fxColors[data]]);
@ -834,28 +834,9 @@ void FurnaceGUI::drawPattern() {
if (extraChannelButtons==2) {
DivPattern* pat=e->curPat[i].getPattern(e->curOrders->ord[i][ord],true);
ImGui::PushFont(mainFont);
if (patNameTarget==i) {
snprintf(chanID,2048,"##PatNameI%d_%d",i,ord);
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x-(8.0f*dpiScale));
ImGui::SetCursorPosX(ImGui::GetCursorPosX()+4.0f*dpiScale);
ImGui::InputText(chanID,&pat->name);
if (wantPatName) {
wantPatName=false;
ImGui::SetItemDefaultFocus();
ImGui::SetKeyboardFocusHere(-1);
} else {
if (!ImGui::IsItemActive()) {
patNameTarget=-1;
}
}
} else {
snprintf(chanID,2048," %s##PatName%d",pat->name.c_str(),i);
if (ImGui::Selectable(chanID,true,ImGuiSelectableFlags_NoPadWithHalfSpacing,ImVec2(0.0f,lineHeight+1.0f*dpiScale))) {
patNameTarget=i;
wantPatName=true;
snprintf(chanID,2048,"##PatNameI%d_%d",i,ord);
ImGui::SetActiveID(ImGui::GetID(chanID),ImGui::GetCurrentWindow());
}
snprintf(chanID,2048," %s##PatName%d",pat->name.c_str(),i);
if (ImGui::Selectable(chanID,true,ImGuiSelectableFlags_NoPadWithHalfSpacing,ImVec2(0.0f,lineHeight+1.0f*dpiScale))) {
editStr(&pat->name);
}
ImGui::PopFont();
} else if (extraChannelButtons==1) {

View file

@ -123,6 +123,7 @@ void FurnaceGUI::drawPiano() {
pianoInputPadMode=PIANO_INPUT_PAD_SPLIT_VISIBLE;
}
ImGui::Checkbox("Share play/edit offset/range",&pianoSharePosition);
ImGui::Checkbox("Read-only (can't input notes)",&pianoReadonly);
ImGui::EndPopup();
}
@ -223,7 +224,7 @@ void FurnaceGUI::drawPiano() {
//ImGui::ItemSize(size,ImGui::GetStyle().FramePadding.y);
if (ImGui::ItemAdd(rect,ImGui::GetID("pianoDisplay"))) {
bool canInput=false;
if (ImGui::ItemHoverable(rect,ImGui::GetID("pianoDisplay"))) {
if (!pianoReadonly && ImGui::ItemHoverable(rect,ImGui::GetID("pianoDisplay"))) {
canInput=true;
ImGui::InhibitInertialScroll();
}

View file

@ -19,11 +19,11 @@
// portions based on imgui_widgets.cpp
#include "plot_nolerp.h"
#include "imgui.h"
#ifndef IMGUI_DEFINE_MATH_OPERATORS
#define IMGUI_DEFINE_MATH_OPERATORS
#endif
#include "plot_nolerp.h"
#include "imgui.h"
#include "imgui_internal.h"
struct FurnacePlotArrayGetterData

View file

@ -822,10 +822,15 @@ void FurnaceGUI::initSystemPresets() {
}
);
ENTRY(
"ZX Spectrum (48K)", {
"ZX Spectrum (48K, SFX-like engine)", {
CH(DIV_SYSTEM_SFX_BEEPER, 1.0f, 0, "")
}
);
ENTRY(
"ZX Spectrum (48K, QuadTone engine)", {
CH(DIV_SYSTEM_SFX_BEEPER_QUADTONE, 1.0f, 0, "")
}
);
ENTRY(
"ZX Spectrum (128K)", {
CH(DIV_SYSTEM_AY8910, 1.0f, 0, "clockSel=1") //beeper was also included
@ -2597,10 +2602,15 @@ void FurnaceGUI::initSystemPresets() {
}
);
ENTRY(
"ZX Spectrum (beeper only)", {
"ZX Spectrum (beeper only, SFX-like engine)", {
CH(DIV_SYSTEM_SFX_BEEPER, 1.0f, 0, "")
}
);
ENTRY(
"ZX Spectrum (beeper only, QuadTone engine)", {
CH(DIV_SYSTEM_SFX_BEEPER_QUADTONE, 1.0f, 0, "")
}
);
ENTRY(
"Sharp SM8521", {
CH(DIV_SYSTEM_SM8521, 1.0f, 0, "")

79
src/gui/render.cpp Normal file
View file

@ -0,0 +1,79 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "gui.h"
#include "../ta-log.h"
#ifdef HAVE_RENDER_SDL
#include "render/renderSDL.h"
#endif
#ifdef HAVE_RENDER_GL
#include "render/renderGL.h"
#endif
#ifdef HAVE_RENDER_DX11
#include "render/renderDX11.h"
#endif
bool FurnaceGUI::initRender() {
if (rend!=NULL) return false;
if (settings.renderBackend=="OpenGL") {
renderBackend=GUI_BACKEND_GL;
} else if (settings.renderBackend=="DirectX 11") {
renderBackend=GUI_BACKEND_DX11;
} else if (settings.renderBackend=="SDL") {
renderBackend=GUI_BACKEND_SDL;
} else {
renderBackend=GUI_BACKEND_DEFAULT;
}
switch (renderBackend) {
#ifdef HAVE_RENDER_GL
case GUI_BACKEND_GL:
logI("render backend: OpenGL");
rend=new FurnaceGUIRenderGL;
break;
#endif
#ifdef HAVE_RENDER_DX11
case GUI_BACKEND_DX11:
logI("render backend: DirectX 11");
rend=new FurnaceGUIRenderDX11;
break;
#endif
#ifdef HAVE_RENDER_SDL
case GUI_BACKEND_SDL:
logI("render backend: SDL_Renderer");
rend=new FurnaceGUIRenderSDL;
break;
#endif
default:
logE("invalid render backend!");
return false;
break;
}
return true;
}
bool FurnaceGUI::quitRender() {
if (rend==NULL) return false;
bool ret=rend->quit();
delete rend;
rend=NULL;
return ret;
}

103
src/gui/render/abstract.cpp Normal file
View file

@ -0,0 +1,103 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "../gui.h"
ImTextureID FurnaceGUIRender::getTextureID(void* which) {
return NULL;
}
bool FurnaceGUIRender::lockTexture(void* which, void** data, int* pitch) {
return false;
}
bool FurnaceGUIRender::unlockTexture(void* which) {
return false;
}
bool FurnaceGUIRender::updateTexture(void* which, void* data, int pitch) {
return false;
}
void* FurnaceGUIRender::createTexture(bool dynamic, int width, int height) {
return NULL;
}
bool FurnaceGUIRender::destroyTexture(void* which) {
return false;
}
void FurnaceGUIRender::setTextureBlendMode(void* which, FurnaceGUIBlendMode mode) {
}
void FurnaceGUIRender::setBlendMode(FurnaceGUIBlendMode mode) {
}
void FurnaceGUIRender::resized(const SDL_Event& ev) {
}
void FurnaceGUIRender::clear(ImVec4 color) {
}
bool FurnaceGUIRender::newFrame() {
return true;
}
void FurnaceGUIRender::createFontsTexture() {
}
void FurnaceGUIRender::destroyFontsTexture() {
}
void FurnaceGUIRender::renderGUI() {
}
void FurnaceGUIRender::wipe(float alpha) {
}
void FurnaceGUIRender::present() {
}
bool FurnaceGUIRender::getOutputSize(int& w, int& h) {
return false;
}
int FurnaceGUIRender::getWindowFlags() {
return 0;
}
void FurnaceGUIRender::preInit() {
}
bool FurnaceGUIRender::init(SDL_Window* win) {
return false;
}
void FurnaceGUIRender::initGUI(SDL_Window* win) {
}
bool FurnaceGUIRender::quit() {
return false;
}
void FurnaceGUIRender::quitGUI() {
}
FurnaceGUIRender::~FurnaceGUIRender() {
}

View file

@ -0,0 +1,571 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#define INCLUDE_D3D11
#include "renderDX11.h"
#include <SDL_syswm.h>
#include "backends/imgui_impl_dx11.h"
#include "../../ta-log.h"
typedef HRESULT (__stdcall *D3DCompile_t)(LPCVOID,SIZE_T,LPCSTR,D3D_SHADER_MACRO*,ID3DInclude*,LPCSTR,LPCSTR,UINT,UINT,ID3DBlob**,ID3DBlob*);
const char* shD3D11_wipe_srcV=
"cbuffer WipeUniform: register(b0) {\n"
" float alpha;\n"
" float padding1;\n"
" float padding2;\n"
" float padding3;\n"
" float4 padding4;\n"
"};\n"
"\n"
"struct vsInput {\n"
" float4 pos: POSITION;\n"
"};\n"
"\n"
"struct fsInput {\n"
" float4 pos: SV_POSITION;\n"
" float4 color: COLOR0;\n"
"};\n"
"\n"
"fsInput main(vsInput input) {\n"
" fsInput output;\n"
" output.pos=input.pos;\n"
" output.color=float4(0.0f,0.0f,0.0f,alpha);\n"
" return output;\n"
"}";
const char* shD3D11_wipe_srcF=
"struct fsInput {\n"
" float4 pos: SV_POSITION;\n"
" float4 color: COLOR0;\n"
"};\n"
"\n"
"float4 main(fsInput input): SV_Target {\n"
" return input.color;\n"
"}";
const D3D11_INPUT_ELEMENT_DESC shD3D11_wipe_inputLayout={
"POSITION", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0
};
const D3D_FEATURE_LEVEL possibleFeatureLevels[2]={
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_0
};
struct FurnaceDXTexture {
ID3D11Texture2D* tex;
ID3D11ShaderResourceView* view;
int width, height;
unsigned char* lockedData;
bool dynamic;
FurnaceDXTexture():
tex(NULL),
view(NULL),
width(0),
height(0),
lockedData(NULL),
dynamic(false) {}
};
bool FurnaceGUIRenderDX11::destroyRenderTarget() {
if (renderTarget!=NULL) {
renderTarget->Release();
renderTarget=NULL;
return true;
}
return false;
}
bool FurnaceGUIRenderDX11::createRenderTarget() {
ID3D11Texture2D* screen=NULL;
HRESULT result;
destroyRenderTarget();
if (swapchain==NULL || device==NULL) {
logW("createRenderTarget: swapchain or device are NULL!");
return false;
}
DXGI_SWAP_CHAIN_DESC chainDesc;
memset(&chainDesc,0,sizeof(chainDesc));
if (swapchain->GetDesc(&chainDesc)!=S_OK) {
logW("createRenderTarget: could not get swapchain desc!");
} else {
outW=chainDesc.BufferDesc.Width;
outH=chainDesc.BufferDesc.Height;
logI("DX11: buffer desc sizes: %d, %d",chainDesc.BufferDesc.Width,chainDesc.BufferDesc.Height);
}
result=swapchain->GetBuffer(0,IID_PPV_ARGS(&screen));
if (result!=S_OK) {
logW("createRenderTarget: could not get buffer! %.8x",result);
return false;
}
if (screen==NULL) {
logW("createRenderTarget: screen is null!");
return false;
}
result=device->CreateRenderTargetView(screen,NULL,&renderTarget);
if (result!=S_OK) {
logW("createRenderTarget: could not create render target view! %.8x",result);
screen->Release();
return false;
}
if (renderTarget==NULL) {
logW("createRenderTarget: what the hell the render target is null?");
screen->Release();
return false;
}
screen->Release();
return true;
}
ImTextureID FurnaceGUIRenderDX11::getTextureID(void* which) {
FurnaceDXTexture* t=(FurnaceDXTexture*)which;
return (ImTextureID)t->view;
}
bool FurnaceGUIRenderDX11::lockTexture(void* which, void** data, int* pitch) {
FurnaceDXTexture* t=(FurnaceDXTexture*)which;
if (t->lockedData!=NULL) return false;
D3D11_MAPPED_SUBRESOURCE mappedRes;
memset(&mappedRes,0,sizeof(mappedRes));
HRESULT result=context->Map(t->tex,D3D11CalcSubresource(0,0,1),D3D11_MAP_WRITE_DISCARD,0,&mappedRes);
if (result!=S_OK) {
logW("could not map texture! %.8x",result);
return false;
}
t->lockedData=(unsigned char*)mappedRes.pData;
*data=mappedRes.pData;
*pitch=mappedRes.RowPitch;
logV("texture locked... pitch: %d",mappedRes.RowPitch);
return true;
}
bool FurnaceGUIRenderDX11::unlockTexture(void* which) {
FurnaceDXTexture* t=(FurnaceDXTexture*)which;
if (t->lockedData==NULL) return false;
context->Unmap(t->tex,D3D11CalcSubresource(0,0,1));
t->lockedData=NULL;
return true;
}
bool FurnaceGUIRenderDX11::updateTexture(void* which, void* data, int pitch) {
FurnaceDXTexture* t=(FurnaceDXTexture*)which;
if (t->dynamic) {
unsigned char* d=NULL;
int p=0;
if (!lockTexture(t,(void**)&d,&p)) return false;
if (p==pitch) {
memcpy(d,data,p*t->height);
} else {
unsigned char* ucData=(unsigned char*)data;
int srcPos=0;
int destPos=0;
for (int i=0; i<t->height; i++) {
memcpy(&d[destPos],&ucData[srcPos],pitch);
srcPos+=pitch;
destPos+=p;
}
}
unlockTexture(t);
} else {
context->UpdateSubresource(t->tex,D3D11CalcSubresource(0,0,1),NULL,data,pitch,pitch*t->height);
}
return true;
}
void* FurnaceGUIRenderDX11::createTexture(bool dynamic, int width, int height) {
D3D11_TEXTURE2D_DESC texDesc;
D3D11_SHADER_RESOURCE_VIEW_DESC viewDesc;
ID3D11Texture2D* tex=NULL;
ID3D11ShaderResourceView* view=NULL;
HRESULT result;
memset(&texDesc,0,sizeof(texDesc));
memset(&viewDesc,0,sizeof(viewDesc));
texDesc.Width=width;
texDesc.Height=height;
texDesc.MipLevels=1;
texDesc.ArraySize=1;
texDesc.Format=DXGI_FORMAT_R8G8B8A8_UNORM; // ???
texDesc.SampleDesc.Count=1;
texDesc.SampleDesc.Quality=0;
texDesc.Usage=dynamic?D3D11_USAGE_DYNAMIC:D3D11_USAGE_DEFAULT;
texDesc.BindFlags=D3D11_BIND_SHADER_RESOURCE;
texDesc.CPUAccessFlags=dynamic?D3D11_CPU_ACCESS_WRITE:0;
texDesc.MiscFlags=0;
result=device->CreateTexture2D(&texDesc,NULL,&tex);
if (result!=S_OK) {
logW("could not create texture! %.8x",result);
return NULL;
}
viewDesc.Format=texDesc.Format=texDesc.Format;
viewDesc.ViewDimension=D3D11_SRV_DIMENSION_TEXTURE2D;
viewDesc.Texture2D.MostDetailedMip=0;
viewDesc.Texture2D.MipLevels=texDesc.MipLevels;
result=device->CreateShaderResourceView(tex,&viewDesc,&view);
if (result!=S_OK) {
logW("could not create texture view! %.8x",result);
tex->Release();
return NULL;
}
FurnaceDXTexture* ret=new FurnaceDXTexture;
ret->width=width;
ret->height=height;
ret->tex=tex;
ret->view=view;
ret->dynamic=dynamic;
textures.push_back(ret);
return ret;
}
bool FurnaceGUIRenderDX11::destroyTexture(void* which) {
FurnaceDXTexture* t=(FurnaceDXTexture*)which;
t->view->Release();
t->tex->Release();
delete t;
for (size_t i=0; i<textures.size(); i++) {
if (textures[i]==t) {
textures.erase(textures.begin()+i);
break;
}
}
return true;
}
void FurnaceGUIRenderDX11::setTextureBlendMode(void* which, FurnaceGUIBlendMode mode) {
}
void FurnaceGUIRenderDX11::setBlendMode(FurnaceGUIBlendMode mode) {
}
void FurnaceGUIRenderDX11::resized(const SDL_Event& ev) {
destroyRenderTarget();
logI("DX11: resizing buffers");
HRESULT result=swapchain->ResizeBuffers(0,(unsigned int)ev.window.data1,(unsigned int)ev.window.data2,DXGI_FORMAT_UNKNOWN,DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH);
if (result!=S_OK) {
logW("error while resizing swapchain buffers! %.8x",result);
}
createRenderTarget();
}
void FurnaceGUIRenderDX11::clear(ImVec4 color) {
float floatColor[4]={
color.x*color.w,
color.y*color.w,
color.z*color.w,
color.w,
};
context->OMSetRenderTargets(1,&renderTarget,NULL);
context->ClearRenderTargetView(renderTarget,floatColor);
}
bool FurnaceGUIRenderDX11::newFrame() {
ImGui_ImplDX11_NewFrame();
return true;
}
void FurnaceGUIRenderDX11::createFontsTexture() {
ImGui_ImplDX11_CreateDeviceObjects();
}
void FurnaceGUIRenderDX11::destroyFontsTexture() {
ImGui_ImplDX11_InvalidateDeviceObjects();
}
void FurnaceGUIRenderDX11::renderGUI() {
ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());
}
const float blendFactor[4]={
1.0f, 1.0f, 1.0f, 1.0f
};
void FurnaceGUIRenderDX11::wipe(float alpha) {
D3D11_VIEWPORT viewPort;
unsigned int strides=4*sizeof(float);
unsigned int offsets=0;
memset(&viewPort,0,sizeof(viewPort));
viewPort.TopLeftX=0.0f;
viewPort.TopLeftY=0.0f;
viewPort.Width=outW;
viewPort.Height=outH;
viewPort.MinDepth=0.0f;
viewPort.MaxDepth=1.0f;
D3D11_MAPPED_SUBRESOURCE mappedUniform;
if (context->Map(sh_wipe_uniform,0,D3D11_MAP_WRITE_DISCARD,0,&mappedUniform)!=S_OK) {
logW("could not map constant");
}
WipeUniform* sh_wipe_uniformState=(WipeUniform*)mappedUniform.pData;
sh_wipe_uniformState->alpha=alpha;
context->Unmap(sh_wipe_uniform,0);
context->RSSetViewports(1,&viewPort);
context->RSSetState(rsState);
context->OMSetBlendState(omBlendState,blendFactor,0xffffffff);
context->IASetInputLayout(sh_wipe_inputLayout);
context->IASetVertexBuffers(0,1,&quadVertex,&strides,&offsets);
context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
context->VSSetShader(sh_wipe_vertex,NULL,0);
context->VSSetConstantBuffers(0,1,&sh_wipe_uniform);
context->PSSetShader(sh_wipe_fragment,NULL,0);
context->Draw(4,0);
}
void FurnaceGUIRenderDX11::present() {
swapchain->Present(1,0);
}
bool FurnaceGUIRenderDX11::getOutputSize(int& w, int& h) {
w=outW;
h=outH;
return true;
}
int FurnaceGUIRenderDX11::getWindowFlags() {
return 0;
}
void FurnaceGUIRenderDX11::preInit() {
}
const float wipeVertices[4][4]={
-1.0, -1.0, 0.0, 1.0,
1.0, -1.0, 0.0, 1.0,
-1.0, 1.0, 0.0, 1.0,
1.0, 1.0, 0.0, 1.0
};
bool FurnaceGUIRenderDX11::init(SDL_Window* win) {
SDL_SysWMinfo sysWindow;
D3D_FEATURE_LEVEL featureLevel;
SDL_VERSION(&sysWindow.version);
if (SDL_GetWindowWMInfo(win,&sysWindow)==SDL_FALSE) {
logE("could not get window WM info! %s",SDL_GetError());
return false;
}
HWND window=(HWND)sysWindow.info.win.window;
DXGI_SWAP_CHAIN_DESC chainDesc;
memset(&chainDesc,0,sizeof(chainDesc));
chainDesc.BufferDesc.Width=0;
chainDesc.BufferDesc.Height=0;
chainDesc.BufferDesc.Format=DXGI_FORMAT_R8G8B8A8_UNORM;
chainDesc.BufferDesc.RefreshRate.Numerator=60;
chainDesc.BufferDesc.RefreshRate.Denominator=1;
chainDesc.SampleDesc.Count=1;
chainDesc.SampleDesc.Quality=0;
chainDesc.BufferUsage=DXGI_USAGE_RENDER_TARGET_OUTPUT;
chainDesc.BufferCount=2;
chainDesc.OutputWindow=window;
chainDesc.Windowed=TRUE; // TODO: what if we're in full screen mode?
chainDesc.SwapEffect=DXGI_SWAP_EFFECT_DISCARD;
chainDesc.Flags=DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
HRESULT result=D3D11CreateDeviceAndSwapChain(NULL,D3D_DRIVER_TYPE_HARDWARE,NULL,0,possibleFeatureLevels,2,D3D11_SDK_VERSION,&chainDesc,&swapchain,&device,&featureLevel,&context);
if (result!=S_OK) {
logE("could not create device and/or swap chain! %.8x",result);
return false;
}
// https://github.com/ocornut/imgui/pull/638
D3DCompile_t D3DCompile=NULL;
char dllBuffer[20];
for (int i=47; (i>30 && !D3DCompile); i--) {
snprintf(dllBuffer,20,"d3dcompiler_%d.dll",i);
HMODULE hDll=LoadLibraryA(dllBuffer);
if (hDll) {
D3DCompile=(D3DCompile_t)GetProcAddress(hDll,"D3DCompile");
}
}
if (!D3DCompile) {
logE("could not find D3DCompile!");
return false;
}
// create wipe shader
ID3DBlob* wipeBlobV=NULL;
ID3DBlob* wipeBlobF=NULL;
D3D11_BUFFER_DESC wipeConstantDesc;
result=D3DCompile(shD3D11_wipe_srcV,strlen(shD3D11_wipe_srcV),NULL,NULL,NULL,"main","vs_4_0",0,0,&wipeBlobV,NULL);
if (result!=S_OK) {
logE("could not compile vertex shader! %.8x",result);
return false;
}
result=D3DCompile(shD3D11_wipe_srcF,strlen(shD3D11_wipe_srcF),NULL,NULL,NULL,"main","ps_4_0",0,0,&wipeBlobF,NULL);
if (result!=S_OK) {
logE("could not compile pixel shader! %.8x",result);
return false;
}
result=device->CreateVertexShader(wipeBlobV->GetBufferPointer(),wipeBlobV->GetBufferSize(),NULL,&sh_wipe_vertex);
if (result!=S_OK) {
logE("could not create vertex shader! %.8x",result);
return false;
}
result=device->CreatePixelShader(wipeBlobF->GetBufferPointer(),wipeBlobF->GetBufferSize(),NULL,&sh_wipe_fragment);
if (result!=S_OK) {
logE("could not create pixel shader! %.8x",result);
return false;
}
result=device->CreateInputLayout(&shD3D11_wipe_inputLayout,1,wipeBlobV->GetBufferPointer(),wipeBlobV->GetBufferSize(),&sh_wipe_inputLayout);
if (result!=S_OK) {
logE("could not create input layout! %.8x",result);
return false;
}
memset(&wipeConstantDesc,0,sizeof(wipeConstantDesc));
wipeConstantDesc.ByteWidth=sizeof(WipeUniform);
wipeConstantDesc.Usage=D3D11_USAGE_DYNAMIC;
wipeConstantDesc.BindFlags=D3D11_BIND_CONSTANT_BUFFER;
wipeConstantDesc.CPUAccessFlags=D3D11_CPU_ACCESS_WRITE;
wipeConstantDesc.MiscFlags=0;
wipeConstantDesc.StructureByteStride=0;
result=device->CreateBuffer(&wipeConstantDesc,NULL,&sh_wipe_uniform);
if (result!=S_OK) {
logE("could not create constant buffer! %.8x",result);
return false;
}
// create wipe vertices
D3D11_BUFFER_DESC vertexDesc;
D3D11_SUBRESOURCE_DATA vertexRes;
memset(&vertexDesc,0,sizeof(vertexDesc));
memset(&vertexRes,0,sizeof(vertexRes));
vertexDesc.ByteWidth=4*4*sizeof(float);
vertexDesc.Usage=D3D11_USAGE_DEFAULT;
vertexDesc.BindFlags=D3D11_BIND_VERTEX_BUFFER;
vertexDesc.CPUAccessFlags=0;
vertexDesc.MiscFlags=0;
vertexDesc.StructureByteStride=0;
vertexRes.pSysMem=wipeVertices;
vertexRes.SysMemPitch=0;
vertexRes.SysMemSlicePitch=0;
result=device->CreateBuffer(&vertexDesc,&vertexRes,&quadVertex);
if (result!=S_OK) {
logE("could not create vertex buffer! %.8x",result);
return false;
}
// initialize the rest
D3D11_RASTERIZER_DESC rasterDesc;
D3D11_BLEND_DESC blendDesc;
memset(&rasterDesc,0,sizeof(rasterDesc));
memset(&blendDesc,0,sizeof(blendDesc));
rasterDesc.FillMode=D3D11_FILL_SOLID;
rasterDesc.CullMode=D3D11_CULL_NONE;
rasterDesc.FrontCounterClockwise=false;
rasterDesc.DepthBias=0;
rasterDesc.DepthBiasClamp=0.0f;
rasterDesc.SlopeScaledDepthBias=0.0f;
rasterDesc.DepthClipEnable=false;
rasterDesc.ScissorEnable=false;
rasterDesc.MultisampleEnable=false;
rasterDesc.AntialiasedLineEnable=false;
result=device->CreateRasterizerState(&rasterDesc,&rsState);
if (result!=S_OK) {
logE("could not create rasterizer state! %.8x",result);
return false;
}
blendDesc.AlphaToCoverageEnable=false;
blendDesc.IndependentBlendEnable=false;
blendDesc.RenderTarget[0].BlendEnable=true;
blendDesc.RenderTarget[0].SrcBlend=D3D11_BLEND_SRC_ALPHA;
blendDesc.RenderTarget[0].DestBlend=D3D11_BLEND_INV_SRC_ALPHA;
blendDesc.RenderTarget[0].BlendOp=D3D11_BLEND_OP_ADD;
blendDesc.RenderTarget[0].SrcBlendAlpha=D3D11_BLEND_ONE;
blendDesc.RenderTarget[0].DestBlendAlpha=D3D11_BLEND_INV_SRC_ALPHA;
blendDesc.RenderTarget[0].BlendOpAlpha=D3D11_BLEND_OP_ADD;
blendDesc.RenderTarget[0].RenderTargetWriteMask=D3D11_COLOR_WRITE_ENABLE_ALL;
result=device->CreateBlendState(&blendDesc,&omBlendState);
if (result!=S_OK) {
logE("could not create blend state! %.8x",result);
return false;
}
createRenderTarget();
return true;
}
void FurnaceGUIRenderDX11::initGUI(SDL_Window* win) {
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGui_ImplSDL2_InitForD3D(win);
ImGui_ImplDX11_Init(device,context);
}
bool FurnaceGUIRenderDX11::quit() {
destroyRenderTarget();
for (FurnaceDXTexture* i: textures) {
i->view->Release();
i->tex->Release();
delete i;
}
textures.clear();
if (swapchain!=NULL) {
swapchain->Release();
swapchain=NULL;
}
if (context!=NULL) {
context->Release();
context=NULL;
}
if (device!=NULL) {
device->Release();
device=NULL;
}
return true;
}
void FurnaceGUIRenderDX11::quitGUI() {
ImGui_ImplDX11_Shutdown();
}

103
src/gui/render/renderDX11.h Normal file
View file

@ -0,0 +1,103 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "../gui.h"
#ifdef INCLUDE_D3D11
#include <d3d11.h>
#else
typedef void ID3D11DeviceContext;
typedef void ID3D11RenderTargetView;
typedef void ID3D11Buffer;
typedef void ID3D11RasterizerState;
typedef void ID3D11BlendState;
typedef void ID3D11VertexShader;
typedef void ID3D11PixelShader;
typedef void ID3D11InputLayout;
typedef void IDXGISwapChain;
#endif
struct FurnaceDXTexture;
class FurnaceGUIRenderDX11: public FurnaceGUIRender {
ID3D11Device* device;
ID3D11DeviceContext* context;
ID3D11RenderTargetView* renderTarget;
IDXGISwapChain* swapchain;
ID3D11RasterizerState* rsState;
ID3D11BlendState* omBlendState;
ID3D11Buffer* quadVertex;
int outW, outH;
// SHADERS //
// -> wipe
ID3D11VertexShader* sh_wipe_vertex;
ID3D11PixelShader* sh_wipe_fragment;
ID3D11InputLayout* sh_wipe_inputLayout;
ID3D11Buffer* sh_wipe_uniform;
struct WipeUniform {
float alpha;
float padding[7];
};
bool destroyRenderTarget();
bool createRenderTarget();
std::vector<FurnaceDXTexture*> textures;
public:
ImTextureID getTextureID(void* which);
bool lockTexture(void* which, void** data, int* pitch);
bool unlockTexture(void* which);
bool updateTexture(void* which, void* data, int pitch);
void* createTexture(bool dynamic, int width, int height);
bool destroyTexture(void* which);
void setTextureBlendMode(void* which, FurnaceGUIBlendMode mode);
void setBlendMode(FurnaceGUIBlendMode mode);
void resized(const SDL_Event& ev);
void clear(ImVec4 color);
bool newFrame();
void createFontsTexture();
void destroyFontsTexture();
void renderGUI();
void wipe(float alpha);
void present();
bool getOutputSize(int& w, int& h);
int getWindowFlags();
void preInit();
bool init(SDL_Window* win);
void initGUI(SDL_Window* win);
void quitGUI();
bool quit();
FurnaceGUIRenderDX11():
device(NULL),
context(NULL),
renderTarget(NULL),
swapchain(NULL),
rsState(NULL),
omBlendState(NULL),
quadVertex(NULL),
outW(0),
outH(0),
sh_wipe_vertex(NULL),
sh_wipe_fragment(NULL),
sh_wipe_inputLayout(NULL),
sh_wipe_uniform(NULL) {
}
};

387
src/gui/render/renderGL.cpp Normal file
View file

@ -0,0 +1,387 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "renderGL.h"
#include "../../ta-log.h"
#ifdef USE_GLES
#include "SDL_opengles2.h"
#define PIXEL_FORMAT GL_UNSIGNED_BYTE
#else
#include "SDL_opengl.h"
#define PIXEL_FORMAT GL_UNSIGNED_INT_8_8_8_8_REV
#endif
#include "backends/imgui_impl_opengl3.h"
#define C(x) x; if (glGetError()!=GL_NO_ERROR) logW("OpenGL error in %s:%d: " #x,__FILE__,__LINE__);
PFNGLGENBUFFERSPROC furGenBuffers=NULL;
PFNGLBINDBUFFERPROC furBindBuffer=NULL;
PFNGLBUFFERDATAPROC furBufferData=NULL;
PFNGLVERTEXATTRIBPOINTERPROC furVertexAttribPointer=NULL;
PFNGLENABLEVERTEXATTRIBARRAYPROC furEnableVertexAttribArray=NULL;
PFNGLACTIVETEXTUREPROC furActiveTexture=NULL;
PFNGLCREATESHADERPROC furCreateShader=NULL;
PFNGLSHADERSOURCEPROC furShaderSource=NULL;
PFNGLCOMPILESHADERPROC furCompileShader=NULL;
PFNGLGETSHADERIVPROC furGetShaderiv=NULL;
PFNGLATTACHSHADERPROC furAttachShader=NULL;
PFNGLBINDATTRIBLOCATIONPROC furBindAttribLocation=NULL;
PFNGLCREATEPROGRAMPROC furCreateProgram=NULL;
PFNGLLINKPROGRAMPROC furLinkProgram=NULL;
PFNGLGETPROGRAMIVPROC furGetProgramiv=NULL;
PFNGLUSEPROGRAMPROC furUseProgram=NULL;
PFNGLGETUNIFORMLOCATIONPROC furGetUniformLocation=NULL;
PFNGLUNIFORM1FPROC furUniform1f=NULL;
PFNGLGETSHADERINFOLOGPROC furGetShaderInfoLog=NULL;
struct FurnaceGLTexture {
GLuint id;
int width, height;
unsigned char* lockedData;
FurnaceGLTexture():
id(0),
width(0),
height(0),
lockedData(NULL) {}
};
#ifdef USE_GLES
const char* sh_wipe_srcV=
"attribute vec4 fur_position;\n"
"void main() {\n"
" gl_Position=fur_position;\n"
"}\n";
const char* sh_wipe_srcF=
"uniform float uAlpha;\n"
"void main() {\n"
" gl_FragColor=vec4(0.0,0.0,0.0,uAlpha);\n"
"}\n";
#else
const char* sh_wipe_srcV=
"#version 130\n"
"in vec4 fur_position;\n"
"void main() {\n"
" gl_Position=fur_position;\n"
"}\n";
const char* sh_wipe_srcF=
"#version 130\n"
"uniform float uAlpha;\n"
"out vec4 fur_FragColor;\n"
"void main() {\n"
" fur_FragColor=vec4(0.0,0.0,0.0,uAlpha);\n"
"}\n";
#endif
bool FurnaceGUIRenderGL::createShader(const char* vertexS, const char* fragmentS, int& vertex, int& fragment, int& program) {
int status;
char infoLog[4096];
int infoLogLen;
if (!furCreateShader || !furShaderSource || !furCompileShader || !furGetShaderiv ||
!furGetShaderInfoLog || !furCreateProgram || !furAttachShader || !furLinkProgram ||
!furBindAttribLocation || !furGetProgramiv) {
logW("I can't compile shaders");
return false;
}
vertex=furCreateShader(GL_VERTEX_SHADER);
furShaderSource(vertex,1,&vertexS,NULL);
furCompileShader(vertex);
furGetShaderiv(vertex,GL_COMPILE_STATUS,&status);
if (!status) {
logW("failed to compile vertex shader");
furGetShaderInfoLog(vertex,4095,&infoLogLen,infoLog);
infoLog[infoLogLen]=0;
logW("%s",infoLog);
return false;
}
fragment=furCreateShader(GL_FRAGMENT_SHADER);
furShaderSource(fragment,1,&fragmentS,NULL);
furCompileShader(fragment);
furGetShaderiv(fragment,GL_COMPILE_STATUS,&status);
if (!status) {
logW("failed to compile fragment shader");
return false;
}
program=furCreateProgram();
furAttachShader(program,vertex);
furAttachShader(program,fragment);
furBindAttribLocation(program,0,"fur_position");
furLinkProgram(program);
furGetProgramiv(program,GL_LINK_STATUS,&status);
if (!status) {
logW("failed to link shader!");
return false;
}
return true;
}
ImTextureID FurnaceGUIRenderGL::getTextureID(void* which) {
intptr_t ret=((FurnaceGLTexture*)which)->id;
return (ImTextureID)ret;
}
bool FurnaceGUIRenderGL::lockTexture(void* which, void** data, int* pitch) {
FurnaceGLTexture* t=(FurnaceGLTexture*)which;
if (t->lockedData!=NULL) return false;
t->lockedData=new unsigned char[t->width*t->height*4];
*data=t->lockedData;
*pitch=t->width*4;
return true;
}
bool FurnaceGUIRenderGL::unlockTexture(void* which) {
FurnaceGLTexture* t=(FurnaceGLTexture*)which;
if (t->lockedData==NULL) return false;
C(glBindTexture(GL_TEXTURE_2D,t->id));
C(glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,t->width,t->height,0,GL_RGBA,PIXEL_FORMAT,t->lockedData));
C(glFlush());
delete[] t->lockedData;
t->lockedData=NULL;
return true;
}
bool FurnaceGUIRenderGL::updateTexture(void* which, void* data, int pitch) {
FurnaceGLTexture* t=(FurnaceGLTexture*)which;
if (t->width*4!=pitch) return false;
C(glBindTexture(GL_TEXTURE_2D,t->id));
C(glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,t->width,t->height,0,GL_RGBA,PIXEL_FORMAT,data));
return true;
}
void* FurnaceGUIRenderGL::createTexture(bool dynamic, int width, int height) {
FurnaceGLTexture* t=new FurnaceGLTexture;
C(glGenTextures(1,&t->id));
C(glBindTexture(GL_TEXTURE_2D,t->id));
C(glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR));
C(glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR));
C(glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,width,height,0,GL_RGBA,PIXEL_FORMAT,NULL));
C(furActiveTexture(GL_TEXTURE0));
t->width=width;
t->height=height;
return t;
}
bool FurnaceGUIRenderGL::destroyTexture(void* which) {
FurnaceGLTexture* t=(FurnaceGLTexture*)which;
C(glDeleteTextures(1,&t->id));
delete t;
return true;
}
void FurnaceGUIRenderGL::setTextureBlendMode(void* which, FurnaceGUIBlendMode mode) {
}
void FurnaceGUIRenderGL::setBlendMode(FurnaceGUIBlendMode mode) {
switch (mode) {
case GUI_BLEND_MODE_NONE:
C(glBlendFunc(GL_ONE,GL_ZERO));
C(glDisable(GL_BLEND));
break;
case GUI_BLEND_MODE_BLEND:
C(glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA));
C(glEnable(GL_BLEND));
break;
case GUI_BLEND_MODE_ADD:
C(glBlendFunc(GL_SRC_ALPHA,GL_ONE));
C(glEnable(GL_BLEND));
break;
case GUI_BLEND_MODE_MULTIPLY:
C(glBlendFunc(GL_ZERO,GL_SRC_COLOR));
C(glEnable(GL_BLEND));
break;
}
}
void FurnaceGUIRenderGL::clear(ImVec4 color) {
SDL_GL_MakeCurrent(sdlWin,context);
C(glClearColor(color.x,color.y,color.z,color.w));
C(glClear(GL_COLOR_BUFFER_BIT));
}
bool FurnaceGUIRenderGL::newFrame() {
ImGui_ImplOpenGL3_NewFrame();
return true;
}
void FurnaceGUIRenderGL::createFontsTexture() {
ImGui_ImplOpenGL3_CreateFontsTexture();
}
void FurnaceGUIRenderGL::destroyFontsTexture() {
ImGui_ImplOpenGL3_DestroyFontsTexture();
}
void FurnaceGUIRenderGL::renderGUI() {
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
}
void FurnaceGUIRenderGL::wipe(float alpha) {
C(glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA));
C(glEnable(GL_BLEND));
quadVertex[0][0]=-1.0f;
quadVertex[0][1]=-1.0f;
quadVertex[0][2]=0.0f;
quadVertex[1][0]=1.0f;
quadVertex[1][1]=-1.0f;
quadVertex[1][2]=0.0f;
quadVertex[2][0]=-1.0f;
quadVertex[2][1]=1.0f;
quadVertex[2][2]=0.0f;
quadVertex[3][0]=1.0f;
quadVertex[3][1]=1.0f;
quadVertex[3][2]=0.0f;
C(furBindBuffer(GL_ARRAY_BUFFER,quadBuf));
C(furBufferData(GL_ARRAY_BUFFER,sizeof(quadVertex),quadVertex,GL_STATIC_DRAW));
C(furVertexAttribPointer(0,3,GL_FLOAT,GL_FALSE,0,NULL));
C(furEnableVertexAttribArray(0));
C(furActiveTexture(GL_TEXTURE0));
C(glBindTexture(GL_TEXTURE_2D,0));
if (furUseProgram && furUniform1f) {
C(furUseProgram(sh_wipe_program));
C(furUniform1f(sh_wipe_uAlpha,alpha));
}
C(glDrawArrays(GL_TRIANGLE_STRIP,0,4));
}
void FurnaceGUIRenderGL::present() {
SDL_GL_SwapWindow(sdlWin);
C(glFlush());
}
bool FurnaceGUIRenderGL::getOutputSize(int& w, int& h) {
SDL_GL_GetDrawableSize(sdlWin,&w,&h);
return true;
}
int FurnaceGUIRenderGL::getWindowFlags() {
return SDL_WINDOW_OPENGL;
}
void FurnaceGUIRenderGL::preInit() {
#if defined(USE_GLES)
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK,SDL_GL_CONTEXT_PROFILE_ES);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION,2);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION,0);
#elif defined(__APPLE__)
// not recommended...
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK,SDL_GL_CONTEXT_PROFILE_CORE);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION,3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION,2);
#else
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS,0);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION,3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION,0);
#endif
SDL_GL_SetAttribute(SDL_GL_RED_SIZE,8);
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE,8);
SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE,8);
SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE,0);
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER,1);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE,24);
}
#define LOAD_PROC_MANDATORY(_v,_t,_s) \
_v=(_t)SDL_GL_GetProcAddress(_s); \
if (!_v) { \
logE(_s " not found"); \
return false; \
}
#define LOAD_PROC_OPTIONAL(_v,_t,_s) \
_v=(_t)SDL_GL_GetProcAddress(_s); \
if (!_v) { \
logW(_s " not found"); \
}
bool FurnaceGUIRenderGL::init(SDL_Window* win) {
sdlWin=win;
context=SDL_GL_CreateContext(win);
if (context==NULL) {
return false;
}
SDL_GL_MakeCurrent(win,context);
SDL_GL_SetSwapInterval(1);
LOAD_PROC_MANDATORY(furGenBuffers,PFNGLGENBUFFERSPROC,"glGenBuffers");
LOAD_PROC_MANDATORY(furBindBuffer,PFNGLBINDBUFFERPROC,"glBindBuffer");
LOAD_PROC_MANDATORY(furBufferData,PFNGLBUFFERDATAPROC,"glBufferData");
LOAD_PROC_MANDATORY(furVertexAttribPointer,PFNGLVERTEXATTRIBPOINTERPROC,"glVertexAttribPointer");
LOAD_PROC_MANDATORY(furEnableVertexAttribArray,PFNGLENABLEVERTEXATTRIBARRAYPROC,"glEnableVertexAttribArray");
LOAD_PROC_MANDATORY(furActiveTexture,PFNGLACTIVETEXTUREPROC,"glActiveTexture");
LOAD_PROC_OPTIONAL(furCreateShader,PFNGLCREATESHADERPROC,"glCreateShader");
LOAD_PROC_OPTIONAL(furShaderSource,PFNGLSHADERSOURCEPROC,"glShaderSource");
LOAD_PROC_OPTIONAL(furCompileShader,PFNGLCOMPILESHADERPROC,"glCompileShader");
LOAD_PROC_OPTIONAL(furGetShaderiv,PFNGLGETSHADERIVPROC,"glGetShaderiv");
LOAD_PROC_OPTIONAL(furAttachShader,PFNGLATTACHSHADERPROC,"glAttachShader");
LOAD_PROC_OPTIONAL(furBindAttribLocation,PFNGLBINDATTRIBLOCATIONPROC,"glBindAttribLocation");
LOAD_PROC_OPTIONAL(furCreateProgram,PFNGLCREATEPROGRAMPROC,"glCreateProgram");
LOAD_PROC_OPTIONAL(furLinkProgram,PFNGLLINKPROGRAMPROC,"glLinkProgram");
LOAD_PROC_OPTIONAL(furGetProgramiv,PFNGLGETPROGRAMIVPROC,"glGetProgramiv");
LOAD_PROC_OPTIONAL(furUseProgram,PFNGLUSEPROGRAMPROC,"glUseProgram");
LOAD_PROC_OPTIONAL(furGetUniformLocation,PFNGLGETUNIFORMLOCATIONPROC,"glGetUniformLocation");
LOAD_PROC_OPTIONAL(furUniform1f,PFNGLUNIFORM1FPROC,"glUniform1f");
LOAD_PROC_OPTIONAL(furGetShaderInfoLog,PFNGLGETSHADERINFOLOGPROC,"glGetShaderInfoLog");
if (createShader(sh_wipe_srcV,sh_wipe_srcF,sh_wipe_vertex,sh_wipe_fragment,sh_wipe_program)) {
sh_wipe_uAlpha=furGetUniformLocation(sh_wipe_program,"uAlpha");
}
C(furGenBuffers(1,&quadBuf));
return true;
}
void FurnaceGUIRenderGL::initGUI(SDL_Window* win) {
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGui_ImplSDL2_InitForOpenGL(win,context);
ImGui_ImplOpenGL3_Init();
}
bool FurnaceGUIRenderGL::quit() {
if (context==NULL) return false;
SDL_GL_DeleteContext(context);
context=NULL;
return true;
}
void FurnaceGUIRenderGL::quitGUI() {
ImGui_ImplOpenGL3_Shutdown();
}

65
src/gui/render/renderGL.h Normal file
View file

@ -0,0 +1,65 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "../gui.h"
class FurnaceGUIRenderGL: public FurnaceGUIRender {
SDL_GLContext context;
SDL_Window* sdlWin;
float quadVertex[4][3];
unsigned int quadBuf;
// SHADERS //
// -> wipe
int sh_wipe_vertex;
int sh_wipe_fragment;
int sh_wipe_program;
int sh_wipe_uAlpha;
bool createShader(const char* vertexS, const char* fragmentS, int& vertex, int& fragment, int& program);
public:
ImTextureID getTextureID(void* which);
bool lockTexture(void* which, void** data, int* pitch);
bool unlockTexture(void* which);
bool updateTexture(void* which, void* data, int pitch);
void* createTexture(bool dynamic, int width, int height);
bool destroyTexture(void* which);
void setTextureBlendMode(void* which, FurnaceGUIBlendMode mode);
void setBlendMode(FurnaceGUIBlendMode mode);
void clear(ImVec4 color);
bool newFrame();
void createFontsTexture();
void destroyFontsTexture();
void renderGUI();
void wipe(float alpha);
void present();
bool getOutputSize(int& w, int& h);
int getWindowFlags();
void preInit();
bool init(SDL_Window* win);
void initGUI(SDL_Window* win);
void quitGUI();
bool quit();
FurnaceGUIRenderGL():
context(NULL),
sdlWin(NULL) {
memset(quadVertex,0,4*3*sizeof(float));
}
};

View file

@ -0,0 +1,147 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "renderSDL.h"
#include "backends/imgui_impl_sdlrenderer2.h"
ImTextureID FurnaceGUIRenderSDL::getTextureID(void* which) {
return which;
}
bool FurnaceGUIRenderSDL::lockTexture(void* which, void** data, int* pitch) {
return SDL_LockTexture((SDL_Texture*)which,NULL,data,pitch)==0;
}
bool FurnaceGUIRenderSDL::unlockTexture(void* which) {
SDL_UnlockTexture((SDL_Texture*)which);
return true;
}
bool FurnaceGUIRenderSDL::updateTexture(void* which, void* data, int pitch) {
return SDL_UpdateTexture((SDL_Texture*)which,NULL,data,pitch)==0;
}
void* FurnaceGUIRenderSDL::createTexture(bool dynamic, int width, int height) {
return SDL_CreateTexture(sdlRend,SDL_PIXELFORMAT_ABGR8888,dynamic?SDL_TEXTUREACCESS_STREAMING:SDL_TEXTUREACCESS_STATIC,width,height);
}
bool FurnaceGUIRenderSDL::destroyTexture(void* which) {
SDL_DestroyTexture((SDL_Texture*)which);
return true;
}
void FurnaceGUIRenderSDL::setTextureBlendMode(void* which, FurnaceGUIBlendMode mode) {
switch (mode) {
case GUI_BLEND_MODE_NONE:
SDL_SetTextureBlendMode((SDL_Texture*)which,SDL_BLENDMODE_NONE);
break;
case GUI_BLEND_MODE_BLEND:
SDL_SetTextureBlendMode((SDL_Texture*)which,SDL_BLENDMODE_BLEND);
break;
case GUI_BLEND_MODE_ADD:
SDL_SetTextureBlendMode((SDL_Texture*)which,SDL_BLENDMODE_ADD);
break;
case GUI_BLEND_MODE_MULTIPLY:
SDL_SetTextureBlendMode((SDL_Texture*)which,SDL_BLENDMODE_MOD);
break;
}
}
void FurnaceGUIRenderSDL::setBlendMode(FurnaceGUIBlendMode mode) {
switch (mode) {
case GUI_BLEND_MODE_NONE:
SDL_SetRenderDrawBlendMode(sdlRend,SDL_BLENDMODE_NONE);
break;
case GUI_BLEND_MODE_BLEND:
SDL_SetRenderDrawBlendMode(sdlRend,SDL_BLENDMODE_BLEND);
break;
case GUI_BLEND_MODE_ADD:
SDL_SetRenderDrawBlendMode(sdlRend,SDL_BLENDMODE_ADD);
break;
case GUI_BLEND_MODE_MULTIPLY:
SDL_SetRenderDrawBlendMode(sdlRend,SDL_BLENDMODE_MOD);
break;
}
}
void FurnaceGUIRenderSDL::clear(ImVec4 color) {
SDL_SetRenderDrawColor(sdlRend,color.x*255,color.y*255,color.z*255,color.w*255);
SDL_RenderClear(sdlRend);
}
bool FurnaceGUIRenderSDL::newFrame() {
return ImGui_ImplSDLRenderer2_NewFrame();
}
void FurnaceGUIRenderSDL::createFontsTexture() {
ImGui_ImplSDLRenderer2_CreateFontsTexture();
}
void FurnaceGUIRenderSDL::destroyFontsTexture() {
ImGui_ImplSDLRenderer2_DestroyFontsTexture();
}
void FurnaceGUIRenderSDL::renderGUI() {
ImGui_ImplSDLRenderer2_RenderDrawData(ImGui::GetDrawData());
}
void FurnaceGUIRenderSDL::wipe(float alpha) {
SDL_SetRenderDrawBlendMode(sdlRend,SDL_BLENDMODE_BLEND);
SDL_SetRenderDrawColor(sdlRend,0,0,0,255*alpha);
SDL_RenderFillRect(sdlRend,NULL);
}
void FurnaceGUIRenderSDL::present() {
SDL_RenderPresent(sdlRend);
}
bool FurnaceGUIRenderSDL::getOutputSize(int& w, int& h) {
return SDL_GetRendererOutputSize(sdlRend,&w,&h)==0;
}
int FurnaceGUIRenderSDL::getWindowFlags() {
return 0;
}
void FurnaceGUIRenderSDL::preInit() {
}
bool FurnaceGUIRenderSDL::init(SDL_Window* win) {
sdlRend=SDL_CreateRenderer(win,-1,SDL_RENDERER_ACCELERATED|SDL_RENDERER_PRESENTVSYNC|SDL_RENDERER_TARGETTEXTURE);
return (sdlRend!=NULL);
}
void FurnaceGUIRenderSDL::initGUI(SDL_Window* win) {
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGui_ImplSDL2_InitForSDLRenderer(win,sdlRend);
ImGui_ImplSDLRenderer2_Init(sdlRend);
}
void FurnaceGUIRenderSDL::quitGUI() {
ImGui_ImplSDLRenderer2_Shutdown();
}
bool FurnaceGUIRenderSDL::quit() {
if (sdlRend==NULL) return false;
SDL_DestroyRenderer(sdlRend);
sdlRend=NULL;
return true;
}

View file

@ -0,0 +1,49 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "../gui.h"
class FurnaceGUIRenderSDL: public FurnaceGUIRender {
SDL_Renderer* sdlRend;
public:
ImTextureID getTextureID(void* which);
bool lockTexture(void* which, void** data, int* pitch);
bool unlockTexture(void* which);
bool updateTexture(void* which, void* data, int pitch);
void* createTexture(bool dynamic, int width, int height);
bool destroyTexture(void* which);
void setTextureBlendMode(void* which, FurnaceGUIBlendMode mode);
void setBlendMode(FurnaceGUIBlendMode mode);
void clear(ImVec4 color);
bool newFrame();
void createFontsTexture();
void destroyFontsTexture();
void renderGUI();
void wipe(float alpha);
void present();
bool getOutputSize(int& w, int& h);
int getWindowFlags();
void preInit();
bool init(SDL_Window* win);
void initGUI(SDL_Window* win);
void quitGUI();
bool quit();
FurnaceGUIRenderSDL():
sdlRend(NULL) {}
};

View file

@ -43,6 +43,12 @@ const double timeMultipliers[13]={
#define CENTER_TEXT(text) \
ImGui::SetCursorPosX(ImGui::GetCursorPosX()+0.5*(ImGui::GetContentRegionAvail().x-ImGui::CalcTextSize(text).x));
#define SAMPLE_WARN(_x,_text) \
if (_x.find(_text)==String::npos) { \
if (!_x.empty()) _x+='\n'; \
_x+=_text; \
}
void FurnaceGUI::drawSampleEdit() {
if (nextWindow==GUI_WINDOW_SAMPLE_EDIT) {
sampleEditOpen=true;
@ -133,6 +139,12 @@ void FurnaceGUI::drawSampleEdit() {
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Open");
}
if (ImGui::BeginPopupContextItem("SampleEOpenOpt")) {
if (ImGui::MenuItem("import raw...")) {
doAction((curSample>=0 && curSample<(int)e->song.sample.size())?GUI_ACTION_SAMPLE_LIST_OPEN_REPLACE_RAW:GUI_ACTION_SAMPLE_LIST_OPEN_RAW);
}
ImGui::EndPopup();
}
ImGui::SameLine();
if (ImGui::Button(ICON_FA_FLOPPY_O "##SESave")) {
doAction(GUI_ACTION_SAMPLE_LIST_SAVE);
@ -140,18 +152,29 @@ void FurnaceGUI::drawSampleEdit() {
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Save");
}
if (ImGui::BeginPopupContextItem("SampleESaveOpt")) {
if (ImGui::MenuItem("save raw...")) {
doAction(GUI_ACTION_SAMPLE_LIST_SAVE_RAW);
}
ImGui::EndPopup();
}
ImGui::SameLine();
ImGui::Text("Name");
ImGui::SameLine();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
ImGui::PushID(2+curSample);
if (ImGui::InputText("##SampleName",&sample->name)) {
MARK_MODIFIED;
}
ImGui::PopID();
ImGui::Separator();
String warnLoop, warnLoopMode, warnLoopPos;
String warnLength;
bool isChipVisible[DIV_MAX_CHIPS];
bool isTypeVisible[DIV_MAX_SAMPLE_TYPE];
bool isMemVisible[DIV_MAX_SAMPLE_TYPE][DIV_MAX_CHIPS];
@ -160,7 +183,104 @@ void FurnaceGUI::drawSampleEdit() {
memset(isTypeVisible,0,DIV_MAX_SAMPLE_TYPE*sizeof(bool));
memset(isMemVisible,0,DIV_MAX_CHIPS*DIV_MAX_SAMPLE_TYPE*sizeof(bool));
memset(isMemWarning,0,DIV_MAX_CHIPS*DIV_MAX_SAMPLE_TYPE*sizeof(bool));
for (int i=0; i<e->song.systemLen; i++) {
// warnings
switch (e->song.system[i]) {
case DIV_SYSTEM_SNES:
if (sample->loop) {
if (sample->loopStart&15 || sample->loopEnd&15) {
SAMPLE_WARN(warnLoopPos,"SNES: loop must be a multiple of 16");
}
}
break;
case DIV_SYSTEM_QSOUND:
if (sample->loop) {
if (sample->loopEnd-sample->loopStart>32767) {
SAMPLE_WARN(warnLoopPos,"QSound: loop cannot be longer than 32767 samples");
}
}
if (sample->samples>65535) {
SAMPLE_WARN(warnLength,"QSound: maximum sample length is 65535");
}
break;
case DIV_SYSTEM_NES:
if (sample->loop) {
if (sample->loopStart!=0 || sample->loopEnd!=(int)(sample->samples)) {
SAMPLE_WARN(warnLoopPos,"NES: loop point ignored on DPCM (may only loop entire sample)");
}
}
if (sample->samples>32648) {
SAMPLE_WARN(warnLength,"NES: maximum DPCM sample length is 32648");
}
break;
case DIV_SYSTEM_X1_010:
if (sample->loop) {
SAMPLE_WARN(warnLoop,"X1-010: samples can't loop");
}
if (sample->samples>131072) {
SAMPLE_WARN(warnLength,"X1-010: maximum sample length is 131072");
}
break;
case DIV_SYSTEM_GA20:
if (sample->loop) {
SAMPLE_WARN(warnLoop,"GA20: samples can't loop");
}
break;
case DIV_SYSTEM_YM2608:
case DIV_SYSTEM_YM2608_EXT:
case DIV_SYSTEM_YM2608_CSM:
if (sample->loop) {
if (sample->loopStart!=0 || sample->loopEnd!=(int)(sample->samples)) {
SAMPLE_WARN(warnLoopPos,"YM2608: loop point ignored on ADPCM-B (may only loop entire sample)");
}
}
break;
case DIV_SYSTEM_YM2610_FULL:
case DIV_SYSTEM_YM2610_FULL_EXT:
case DIV_SYSTEM_YM2610_CSM:
case DIV_SYSTEM_YM2610B:
case DIV_SYSTEM_YM2610B_EXT:
if (sample->loop) {
SAMPLE_WARN(warnLoop,"YM2610: ADPCM-A samples can't loop");
if (sample->loopStart!=0 || sample->loopEnd!=(int)(sample->samples)) {
SAMPLE_WARN(warnLoopPos,"YM2610: loop point ignored on ADPCM-B (may only loop entire sample)");
}
}
if (sample->samples>2097152) {
SAMPLE_WARN(warnLength,"YM2610: maximum ADPCM-A sample length is 2097152");
}
break;
case DIV_SYSTEM_AMIGA:
if (sample->loop) {
if (sample->loopStart&1 || sample->loopEnd&1) {
SAMPLE_WARN(warnLoopPos,"Amiga: loop must be a multiple of 2");
}
}
if (sample->samples>131070) {
SAMPLE_WARN(warnLength,"Amiga: maximum sample length is 131070");
}
break;
case DIV_SYSTEM_SEGAPCM:
case DIV_SYSTEM_SEGAPCM_COMPAT:
if (sample->samples>65280) {
SAMPLE_WARN(warnLength,"SegaPCM: maximum sample length is 65280");
}
break;
default:
break;
}
if (e->song.system[i]!=DIV_SYSTEM_PCM_DAC) {
if (e->song.system[i]==DIV_SYSTEM_ES5506) {
if (sample->loopMode==DIV_SAMPLE_LOOP_BACKWARD) {
SAMPLE_WARN(warnLoopMode,"ES5506: backward loop mode isn't supported");
}
} else if (sample->loopMode!=DIV_SAMPLE_LOOP_FORWARD) {
SAMPLE_WARN(warnLoopMode,"backward/ping-pong only supported in Generic PCM DAC\nping-pong also on ES5506");
}
}
// chips grid
DivDispatch* dispatch=e->getDispatch(i);
if (dispatch==NULL) continue;
@ -172,10 +292,13 @@ void FurnaceGUI::drawSampleEdit() {
if (!dispatch->isSampleLoaded(j,curSample)) isMemWarning[j][i]=true;
}
}
int selColumns=1;
for (int i=0; i<DIV_MAX_CHIPS; i++) {
if (isChipVisible[i]) selColumns++;
}
int targetRate=sampleCompatRate?sample->rate:sample->centerRate;
if (ImGui::BeginTable("SampleProps",(selColumns>1)?4:3,ImGuiTableFlags_SizingStretchSame|ImGuiTableFlags_BordersV|ImGuiTableFlags_BordersOuterH)) {
ImGui::TableNextRow(ImGuiTableRowFlags_Headers);
@ -201,24 +324,34 @@ void FurnaceGUI::drawSampleEdit() {
}
popToggleColors();
ImGui::TableNextColumn();
bool doLoop=(sample->isLoopable());
bool doLoop=(sample->loop);
pushWarningColor(!warnLoop.empty());
if (ImGui::Checkbox("Loop",&doLoop)) { MARK_MODIFIED
if (doLoop) {
sample->loop=true;
sample->loopStart=0;
sample->loopEnd=sample->samples;
if (sample->loopStart<0) {
sample->loopStart=0;
}
if (sample->loopEnd<0) {
sample->loopEnd=sample->samples;
}
} else {
sample->loop=false;
/*
sample->loopStart=-1;
sample->loopEnd=sample->samples;
sample->loopEnd=sample->samples;*/
}
updateSampleTex=true;
if (e->getSampleFormatMask()&(1U<<DIV_SAMPLE_DEPTH_BRR)) {
e->renderSamplesP();
}
}
if (ImGui::IsItemHovered() && sample->depth==DIV_SAMPLE_DEPTH_BRR) {
ImGui::SetTooltip("changing the loop in a BRR sample may result in glitches!");
popWarningColor();
if (ImGui::IsItemHovered() && (!warnLoop.empty() || sample->depth==DIV_SAMPLE_DEPTH_BRR)) {
if (sample->depth==DIV_SAMPLE_DEPTH_BRR) {
SAMPLE_WARN(warnLoop,"changing the loop in a BRR sample may result in glitches!");
}
ImGui::SetTooltip("%s",warnLoop.c_str());
}
if (selColumns>1) {
@ -238,8 +371,7 @@ void FurnaceGUI::drawSampleEdit() {
if (ImGui::Selectable(sampleDepths[i])) {
sample->prepareUndo(true);
e->lockEngine([this,sample,i]() {
sample->render();
sample->depth=(DivSampleDepth)i;
sample->convert((DivSampleDepth)i);
e->renderSamples();
});
updateSampleTex=true;
@ -273,8 +405,20 @@ void FurnaceGUI::drawSampleEdit() {
}
}
}
if (sample->depth!=DIV_SAMPLE_DEPTH_8BIT && e->getSampleFormatMask()&(1L<<DIV_SAMPLE_DEPTH_8BIT)) {
bool di=sample->dither;
if (ImGui::Checkbox("8-bit dither",&di)) {
sample->prepareUndo(true);
sample->dither=di;
e->renderSamplesP();
updateSampleTex=true;
MARK_MODIFIED;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("dither the sample when used on a chip that only supports 8-bit samples.");
}
}
int targetRate=sampleCompatRate?sample->rate:sample->centerRate;
int sampleNote=round(64.0+(128.0*12.0*log((double)targetRate/8363.0)/log(2.0)));
int sampleNoteCoarse=60+(sampleNote>>7);
int sampleNoteFine=(sampleNote&127)-64;
@ -385,6 +529,7 @@ void FurnaceGUI::drawSampleEdit() {
keepLoopAlive=false;
ImGui::Text("Mode");
ImGui::SameLine();
pushWarningColor(!warnLoopMode.empty());
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
if (ImGui::BeginCombo("##SampleLoopMode",loopType.c_str())) {
for (int i=0; i<DIV_SAMPLE_LOOP_MAX; i++) {
@ -399,7 +544,12 @@ void FurnaceGUI::drawSampleEdit() {
}
ImGui::EndCombo();
}
if (ImGui::IsItemHovered() && !warnLoopMode.empty()) {
ImGui::SetTooltip("%s",warnLoopMode.c_str());
}
popWarningColor();
pushWarningColor(!warnLoopPos.empty());
ImGui::Text("Start");
ImGui::SameLine();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
@ -418,8 +568,11 @@ void FurnaceGUI::drawSampleEdit() {
if (ImGui::IsItemActive()) {
keepLoopAlive=true;
}
if (ImGui::IsItemHovered() && sample->depth==DIV_SAMPLE_DEPTH_BRR) {
ImGui::SetTooltip("changing the loop in a BRR sample may result in glitches!");
if (ImGui::IsItemHovered() && (!warnLoopPos.empty() || sample->depth==DIV_SAMPLE_DEPTH_BRR)) {
if (sample->depth==DIV_SAMPLE_DEPTH_BRR) {
SAMPLE_WARN(warnLoopPos,"changing the loop in a BRR sample may result in glitches!");
}
ImGui::SetTooltip("%s",warnLoopPos.c_str());
}
ImGui::Text("End");
@ -440,9 +593,13 @@ void FurnaceGUI::drawSampleEdit() {
if (ImGui::IsItemActive()) {
keepLoopAlive=true;
}
if (ImGui::IsItemHovered() && sample->depth==DIV_SAMPLE_DEPTH_BRR) {
ImGui::SetTooltip("changing the loop in a BRR sample may result in glitches!");
if (ImGui::IsItemHovered() && (!warnLoopPos.empty() || sample->depth==DIV_SAMPLE_DEPTH_BRR)) {
if (sample->depth==DIV_SAMPLE_DEPTH_BRR) {
SAMPLE_WARN(warnLoopPos,"changing the loop in a BRR sample may result in glitches!");
}
ImGui::SetTooltip("%s",warnLoopPos.c_str());
}
popWarningColor();
ImGui::EndDisabled();
if (selColumns>1) {
@ -608,7 +765,7 @@ void FurnaceGUI::drawSampleEdit() {
ImGui::SameLine();
ImGui::Button(ICON_FA_EXPAND "##SResample");
if (ImGui::IsItemClicked()) {
resampleTarget=sample->rate;
resampleTarget=targetRate;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Resample");
@ -629,23 +786,23 @@ void FurnaceGUI::drawSampleEdit() {
}
ImGui::SameLine();
if (ImGui::Button("==")) {
resampleTarget=sample->rate;
resampleTarget=targetRate;
}
ImGui::SameLine();
if (ImGui::Button("2.0x")) {
resampleTarget*=2.0;
}
double factor=resampleTarget/(double)sample->rate;
double factor=resampleTarget/(double)targetRate;
if (ImGui::InputDouble("Factor",&factor,0.125,0.5,"%g")) {
resampleTarget=(double)sample->rate*factor;
resampleTarget=(double)targetRate*factor;
if (resampleTarget<0) resampleTarget=0;
if (resampleTarget>96000) resampleTarget=96000;
}
ImGui::Combo("Filter",&resampleStrat,resampleStrats,6);
if (ImGui::Button("Resample")) {
sample->prepareUndo(true);
e->lockEngine([this,sample]() {
if (!sample->resample(resampleTarget,resampleStrat)) {
e->lockEngine([this,sample,targetRate]() {
if (!sample->resample(targetRate,resampleTarget,resampleStrat)) {
showError("couldn't resample! make sure your sample is 8 or 16-bit.");
}
e->renderSamples();
@ -658,7 +815,7 @@ void FurnaceGUI::drawSampleEdit() {
}
ImGui::EndPopup();
} else {
resampleTarget=sample->rate;
resampleTarget=targetRate;
}
ImGui::SameLine();
ImGui::Dummy(ImVec2(4.0*dpiScale,dpiScale));
@ -1093,12 +1250,12 @@ void FurnaceGUI::drawSampleEdit() {
if (sampleTex==NULL || sampleTexW!=avail.x || sampleTexH!=avail.y) {
if (sampleTex!=NULL) {
SDL_DestroyTexture(sampleTex);
rend->destroyTexture(sampleTex);
sampleTex=NULL;
}
if (avail.x>=1 && avail.y>=1) {
logD("recreating sample texture.");
sampleTex=SDL_CreateTexture(sdlRend,SDL_PIXELFORMAT_ABGR8888,SDL_TEXTUREACCESS_STREAMING,avail.x,avail.y);
sampleTex=rend->createTexture(true,avail.x,avail.y);
sampleTexW=avail.x;
sampleTexH=avail.y;
if (sampleTex==NULL) {
@ -1114,7 +1271,7 @@ void FurnaceGUI::drawSampleEdit() {
unsigned int* dataT=NULL;
int pitch=0;
logD("updating sample texture.");
if (SDL_LockTexture(sampleTex,NULL,(void**)&dataT,&pitch)!=0) {
if (!rend->lockTexture(sampleTex,(void**)&dataT,&pitch)) {
logE("error while locking sample texture! %s",SDL_GetError());
} else {
unsigned int* data=new unsigned int[sampleTexW*sampleTexH];
@ -1199,19 +1356,54 @@ void FurnaceGUI::drawSampleEdit() {
}
}
memcpy(dataT,data,sampleTexW*sampleTexH*sizeof(unsigned int));
SDL_UnlockTexture(sampleTex);
if ((pitch>>2)==sampleTexW) {
memcpy(dataT,data,sampleTexW*sampleTexH*sizeof(unsigned int));
} else {
int srcY=0;
int destY=0;
for (int i=0; i<sampleTexH; i++) {
memcpy(&dataT[destY],&data[srcY],sampleTexW*sizeof(unsigned int));
srcY+=sampleTexW;
destY+=pitch>>2;
}
}
rend->unlockTexture(sampleTex);
delete[] data;
}
updateSampleTex=false;
}
ImGui::ImageButton(sampleTex,avail,ImVec2(0,0),ImVec2(1,1),0);
ImGui::ImageButton(rend->getTextureID(sampleTex),avail,ImVec2(0,0),ImVec2(1,1),0);
ImVec2 rectMin=ImGui::GetItemRectMin();
ImVec2 rectMax=ImGui::GetItemRectMax();
ImVec2 rectSize=ImGui::GetItemRectSize();
unsigned char selectTarget=255;
if (ImGui::IsItemHovered()) {
int start=sampleSelStart;
int end=sampleSelEnd;
if (start>end) {
start^=end;
end^=start;
start^=end;
}
ImVec2 p1=rectMin;
p1.x+=(start-samplePos)/sampleZoom;
ImVec2 p2=ImVec2(rectMin.x+(end-samplePos)/sampleZoom,rectMax.y);
ImVec2 mousePos=ImGui::GetMousePos();
if (p1.x>=rectMin.x && p1.x<=rectMax.x && fabs(mousePos.x-p1.x)<2.0*dpiScale) {
ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW);
selectTarget=0;
} else if (p2.x>=rectMin.x && p2.x<=rectMax.x && fabs(mousePos.x-p2.x)<2.0*dpiScale) {
ImGui::SetMouseCursor(ImGuiMouseCursor_ResizeEW);
selectTarget=1;
}
}
if (ImGui::IsItemClicked()) {
nextWindow=GUI_WINDOW_SAMPLE_EDIT;
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
@ -1238,9 +1430,23 @@ void FurnaceGUI::drawSampleEdit() {
}
sampleDragLen=sample->samples;
sampleDragActive=true;
sampleSelStart=-1;
sampleSelEnd=-1;
if (sampleDragMode) sample->prepareUndo(true);
if (!sampleDragMode) {
switch (selectTarget) {
case 0:
sampleSelStart^=sampleSelEnd;
sampleSelEnd^=sampleSelStart;
sampleSelStart^=sampleSelEnd;
break;
case 1:
break;
default:
sampleSelStart=-1;
sampleSelEnd=-1;
break;
}
} else {
sample->prepareUndo(true);
}
processDrags(ImGui::GetMousePos().x,ImGui::GetMousePos().y);
}
}
@ -1529,7 +1735,16 @@ void FurnaceGUI::drawSampleEdit() {
ImGui::TableNextColumn();
ImGui::TextUnformatted(statusBar2.c_str());
ImGui::TableNextColumn();
ImGui::TextUnformatted(statusBar3.c_str());
if (!warnLength.empty()) {
ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_WARNING]);
ImGui::TextUnformatted(statusBar3.c_str());
ImGui::PopStyleColor();
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("%s",warnLength.c_str());
}
} else {
ImGui::TextUnformatted(statusBar3.c_str());
}
ImGui::EndTable();
}

View file

@ -508,6 +508,14 @@ void FurnaceGUI::drawSettings() {
settings.alwaysPlayIntro=3;
}
ImGui::Text("When creating new song:");
if (ImGui::RadioButton("Display system preset selector##NSB0",settings.newSongBehavior==0)) {
settings.newSongBehavior=0;
}
if (ImGui::RadioButton("Start with initial system##NSB1",settings.newSongBehavior==1)) {
settings.newSongBehavior=1;
}
ImGui::Separator();
if (CWSliderFloat("Double-click time (seconds)",&settings.doubleClickTime,0.02,1.0,"%.2f")) {
@ -543,6 +551,16 @@ void FurnaceGUI::drawSettings() {
settings.stepOnDelete=stepOnDeleteB;
}
bool insertBehaviorB=settings.insertBehavior;
if (ImGui::Checkbox("Insert pushes entire channel row",&insertBehaviorB)) {
settings.insertBehavior=insertBehaviorB;
}
bool pullDeleteRowB=settings.pullDeleteRow;
if (ImGui::Checkbox("Pull delete affects entire channel row",&pullDeleteRowB)) {
settings.pullDeleteRow=pullDeleteRowB;
}
bool absorbInsInputB=settings.absorbInsInput;
if (ImGui::Checkbox("Change current instrument when changing instrument column (absorb)",&absorbInsInputB)) {
settings.absorbInsInput=absorbInsInputB;
@ -626,6 +644,14 @@ void FurnaceGUI::drawSettings() {
ImGui::SetTooltip("saves power by lowering the frame rate to 2fps when idle.\nmay cause issues under Mesa drivers!");
}
bool renderClearPosB=settings.renderClearPos;
if (ImGui::Checkbox("Late render clear",&renderClearPosB)) {
settings.renderClearPos=renderClearPosB;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("calls rend->clear() after rend->present(). might reduce UI latency by one frame in some drivers.");
}
#ifndef IS_MOBILE
bool noThreadedInputB=settings.noThreadedInput;
if (ImGui::Checkbox("Disable threaded input (restart after changing!)",&noThreadedInputB)) {
@ -654,6 +680,22 @@ void FurnaceGUI::drawSettings() {
settings.saveUnusedPatterns=saveUnusedPatternsB;
}
bool compressB=settings.compress;
if (ImGui::Checkbox("Compress when saving",&compressB)) {
settings.compress=compressB;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("use zlib to compress saved songs.");
}
bool newPatternFormatB=settings.newPatternFormat;
if (ImGui::Checkbox("Use new pattern format when saving",&newPatternFormatB)) {
settings.newPatternFormat=newPatternFormatB;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("use a packed format which saves space when saving songs.\ndisable if you need compatibility with older Furnace and/or tools\nwhich do not support this format.");
}
bool cursorFollowsOrderB=settings.cursorFollowsOrder;
if (ImGui::Checkbox("Cursor follows current order when moving it",&cursorFollowsOrderB)) {
settings.cursorFollowsOrder=cursorFollowsOrderB;
@ -1146,14 +1188,38 @@ void FurnaceGUI::drawSettings() {
settings.midiOutMode=2;
}*/
bool midiOutProgramChangeB=settings.midiOutProgramChange;
if (ImGui::Checkbox("Send Program Change",&midiOutProgramChangeB)) {
settings.midiOutProgramChange=midiOutProgramChangeB;
}
bool midiOutClockB=settings.midiOutClock;
if (ImGui::Checkbox("Send MIDI clock",&midiOutClockB)) {
settings.midiOutClock=midiOutClockB;
}
bool midiOutProgramChangeB=settings.midiOutProgramChange;
if (ImGui::Checkbox("Send Program Change",&midiOutProgramChangeB)) {
settings.midiOutProgramChange=midiOutProgramChangeB;
bool midiOutTimeB=settings.midiOutTime;
if (ImGui::Checkbox("Send MIDI timecode",&midiOutTimeB)) {
settings.midiOutTime=midiOutTimeB;
}
if (settings.midiOutTime) {
ImGui::Text("Timecode frame rate:");
if (ImGui::RadioButton("Closest to Tick Rate",settings.midiOutTimeRate==0)) {
settings.midiOutTimeRate=0;
}
if (ImGui::RadioButton("Film (24fps)",settings.midiOutTimeRate==1)) {
settings.midiOutTimeRate=1;
}
if (ImGui::RadioButton("PAL (25fps)",settings.midiOutTimeRate==2)) {
settings.midiOutTimeRate=2;
}
if (ImGui::RadioButton("NTSC drop (29.97fps)",settings.midiOutTimeRate==3)) {
settings.midiOutTimeRate=3;
}
if (ImGui::RadioButton("NTSC non-drop (30fps)",settings.midiOutTimeRate==4)) {
settings.midiOutTimeRate=4;
}
}
ImGui::TreePop();
@ -1238,17 +1304,38 @@ void FurnaceGUI::drawSettings() {
ImVec2 settingsViewSize=ImGui::GetContentRegionAvail();
settingsViewSize.y-=ImGui::GetFrameHeight()+ImGui::GetStyle().WindowPadding.y;
if (ImGui::BeginChild("SettingsView",settingsViewSize)) {
if (ImGui::BeginCombo("Render driver",settings.renderDriver.empty()?"Automatic":settings.renderDriver.c_str())) {
if (ImGui::Selectable("Automatic",settings.renderDriver.empty())) {
settings.renderDriver="";
String curRenderBackend=settings.renderBackend.empty()?GUI_BACKEND_DEFAULT_NAME:settings.renderBackend;
if (ImGui::BeginCombo("Render backend",curRenderBackend.c_str())) {
#ifdef HAVE_RENDER_SDL
if (ImGui::Selectable("SDL Renderer",curRenderBackend=="SDL")) {
settings.renderBackend="SDL";
}
for (String& i: availRenderDrivers) {
if (ImGui::Selectable(i.c_str(),i==settings.renderDriver)) {
settings.renderDriver=i;
}
#endif
#ifdef HAVE_RENDER_DX11
if (ImGui::Selectable("DirectX 11",curRenderBackend=="DirectX 11")) {
settings.renderBackend="DirectX 11";
}
#endif
#ifdef HAVE_RENDER_GL
if (ImGui::Selectable("OpenGL",curRenderBackend=="OpenGL")) {
settings.renderBackend="OpenGL";
}
#endif
ImGui::EndCombo();
}
if (curRenderBackend=="SDL") {
if (ImGui::BeginCombo("Render driver",settings.renderDriver.empty()?"Automatic":settings.renderDriver.c_str())) {
if (ImGui::Selectable("Automatic",settings.renderDriver.empty())) {
settings.renderDriver="";
}
for (String& i: availRenderDrivers) {
if (ImGui::Selectable(i.c_str(),i==settings.renderDriver)) {
settings.renderDriver=i;
}
}
ImGui::EndCombo();
}
}
bool dpiScaleAuto=(settings.dpiScale<0.5f);
if (ImGui::Checkbox("Automatic UI scaling factor",&dpiScaleAuto)) {
@ -1811,18 +1898,41 @@ void FurnaceGUI::drawSettings() {
}
UI_COLOR_CONFIG(GUI_COLOR_BACKGROUND,"Background");
UI_COLOR_CONFIG(GUI_COLOR_FRAME_BACKGROUND,"Window background");
UI_COLOR_CONFIG(GUI_COLOR_FRAME_BACKGROUND_CHILD,"Sub-window background");
UI_COLOR_CONFIG(GUI_COLOR_FRAME_BACKGROUND_POPUP,"Pop-up background");
UI_COLOR_CONFIG(GUI_COLOR_MODAL_BACKDROP,"Modal backdrop");
UI_COLOR_CONFIG(GUI_COLOR_HEADER,"Header");
UI_COLOR_CONFIG(GUI_COLOR_TEXT,"Text");
UI_COLOR_CONFIG(GUI_COLOR_ACCENT_PRIMARY,"Primary");
UI_COLOR_CONFIG(GUI_COLOR_ACCENT_SECONDARY,"Secondary");
UI_COLOR_CONFIG(GUI_COLOR_TITLE_INACTIVE,"Title bar (inactive)");
UI_COLOR_CONFIG(GUI_COLOR_TITLE_COLLAPSED,"Title bar (collapsed)");
UI_COLOR_CONFIG(GUI_COLOR_MENU_BAR,"Menu bar");
UI_COLOR_CONFIG(GUI_COLOR_BORDER,"Border");
UI_COLOR_CONFIG(GUI_COLOR_BORDER_SHADOW,"Border shadow");
UI_COLOR_CONFIG(GUI_COLOR_SCROLL,"Scroll bar");
UI_COLOR_CONFIG(GUI_COLOR_SCROLL_HOVER,"Scroll bar (hovered)");
UI_COLOR_CONFIG(GUI_COLOR_SCROLL_ACTIVE,"Scroll bar (clicked)");
UI_COLOR_CONFIG(GUI_COLOR_SCROLL_BACKGROUND,"Scroll bar background");
UI_COLOR_CONFIG(GUI_COLOR_SEPARATOR,"Separator");
UI_COLOR_CONFIG(GUI_COLOR_SEPARATOR_HOVER,"Separator (hover)");
UI_COLOR_CONFIG(GUI_COLOR_SEPARATOR_ACTIVE,"Separator (active)");
UI_COLOR_CONFIG(GUI_COLOR_DOCKING_PREVIEW,"Docking preview");
UI_COLOR_CONFIG(GUI_COLOR_DOCKING_EMPTY,"Docking empty");
UI_COLOR_CONFIG(GUI_COLOR_TABLE_HEADER,"Table header");
UI_COLOR_CONFIG(GUI_COLOR_TABLE_BORDER_HARD,"Table border (hard)");
UI_COLOR_CONFIG(GUI_COLOR_TABLE_BORDER_SOFT,"Table border (soft)");
UI_COLOR_CONFIG(GUI_COLOR_DRAG_DROP_TARGET,"Drag and drop target");
UI_COLOR_CONFIG(GUI_COLOR_NAV_WIN_HIGHLIGHT,"Window switcher (highlight)");
UI_COLOR_CONFIG(GUI_COLOR_NAV_WIN_BACKDROP,"Window switcher backdrop");
UI_COLOR_CONFIG(GUI_COLOR_TOGGLE_ON,"Toggle on");
UI_COLOR_CONFIG(GUI_COLOR_TOGGLE_OFF,"Toggle off");
UI_COLOR_CONFIG(GUI_COLOR_EDITING,"Editing");
UI_COLOR_CONFIG(GUI_COLOR_SONG_LOOP,"Song loop");
UI_COLOR_CONFIG(GUI_COLOR_PLAYBACK_STAT,"Playback status");
UI_COLOR_CONFIG(GUI_COLOR_DESTRUCTIVE,"Destructive hint");
UI_COLOR_CONFIG(GUI_COLOR_WARNING,"Warning hint");
UI_COLOR_CONFIG(GUI_COLOR_ERROR,"Error hint");
ImGui::TreePop();
}
if (ImGui::TreeNode("File Picker (built-in)")) {
@ -2327,6 +2437,7 @@ void FurnaceGUI::drawSettings() {
UI_KEYBIND_CONFIG(GUI_ACTION_INS_LIST_EDIT);
UI_KEYBIND_CONFIG(GUI_ACTION_INS_LIST_UP);
UI_KEYBIND_CONFIG(GUI_ACTION_INS_LIST_DOWN);
UI_KEYBIND_CONFIG(GUI_ACTION_INS_LIST_DIR_VIEW);
KEYBIND_CONFIG_END;
ImGui::TreePop();
@ -2344,6 +2455,7 @@ void FurnaceGUI::drawSettings() {
UI_KEYBIND_CONFIG(GUI_ACTION_WAVE_LIST_EDIT);
UI_KEYBIND_CONFIG(GUI_ACTION_WAVE_LIST_UP);
UI_KEYBIND_CONFIG(GUI_ACTION_WAVE_LIST_DOWN);
UI_KEYBIND_CONFIG(GUI_ACTION_WAVE_LIST_DIR_VIEW);
KEYBIND_CONFIG_END;
ImGui::TreePop();
@ -2363,6 +2475,7 @@ void FurnaceGUI::drawSettings() {
UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_LIST_DOWN);
UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_LIST_PREVIEW);
UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_LIST_STOP_PREVIEW);
UI_KEYBIND_CONFIG(GUI_ACTION_SAMPLE_LIST_DIR_VIEW);
KEYBIND_CONFIG_END;
ImGui::TreePop();
@ -2629,8 +2742,10 @@ void FurnaceGUI::syncSettings() {
settings.channelTextCenter=e->getConfInt("channelTextCenter",1);
settings.maxRecentFile=e->getConfInt("maxRecentFile",10);
settings.midiOutClock=e->getConfInt("midiOutClock",0);
settings.midiOutTime=e->getConfInt("midiOutTime",0);
settings.midiOutProgramChange=e->getConfInt("midiOutProgramChange",0);
settings.midiOutMode=e->getConfInt("midiOutMode",1);
settings.midiOutTimeRate=e->getConfInt("midiOutTimeRate",0);
settings.centerPattern=e->getConfInt("centerPattern",0);
settings.ordersCursor=e->getConfInt("ordersCursor",1);
settings.persistFadeOut=e->getConfInt("persistFadeOut",1);
@ -2644,6 +2759,13 @@ void FurnaceGUI::syncSettings() {
settings.cursorFollowsOrder=e->getConfInt("cursorFollowsOrder",1);
settings.iCannotWait=e->getConfInt("iCannotWait",0);
settings.orderButtonPos=e->getConfInt("orderButtonPos",2);
settings.compress=e->getConfInt("compress",1);
settings.newPatternFormat=e->getConfInt("newPatternFormat",1);
settings.renderBackend=e->getConfString("renderBackend","SDL");
settings.renderClearPos=e->getConfInt("renderClearPos",0);
settings.insertBehavior=e->getConfInt("insertBehavior",1);
settings.pullDeleteRow=e->getConfInt("pullDeleteRow",1);
settings.newSongBehavior=e->getConfInt("newSongBehavior",0);
clampSetting(settings.mainFontSize,2,96);
clampSetting(settings.patFontSize,2,96);
@ -2749,8 +2871,10 @@ void FurnaceGUI::syncSettings() {
clampSetting(settings.channelTextCenter,0,1);
clampSetting(settings.maxRecentFile,0,30);
clampSetting(settings.midiOutClock,0,1);
clampSetting(settings.midiOutTime,0,1);
clampSetting(settings.midiOutProgramChange,0,1);
clampSetting(settings.midiOutMode,0,2);
clampSetting(settings.midiOutTimeRate,0,4);
clampSetting(settings.centerPattern,0,1);
clampSetting(settings.ordersCursor,0,1);
clampSetting(settings.persistFadeOut,0,1);
@ -2762,6 +2886,12 @@ void FurnaceGUI::syncSettings() {
clampSetting(settings.cursorFollowsOrder,0,1);
clampSetting(settings.iCannotWait,0,1);
clampSetting(settings.orderButtonPos,0,2);
clampSetting(settings.compress,0,1);
clampSetting(settings.newPatternFormat,0,1);
clampSetting(settings.renderClearPos,0,1);
clampSetting(settings.insertBehavior,0,1);
clampSetting(settings.pullDeleteRow,0,1);
clampSetting(settings.newSongBehavior,0,1);
if (settings.exportLoops<0.0) settings.exportLoops=0.0;
if (settings.exportFadeOut<0.0) settings.exportFadeOut=0.0;
@ -2960,8 +3090,10 @@ void FurnaceGUI::commitSettings() {
e->setConf("channelTextCenter",settings.channelTextCenter);
e->setConf("maxRecentFile",settings.maxRecentFile);
e->setConf("midiOutClock",settings.midiOutClock);
e->setConf("midiOutTime",settings.midiOutTime);
e->setConf("midiOutProgramChange",settings.midiOutProgramChange);
e->setConf("midiOutMode",settings.midiOutMode);
e->setConf("midiOutTimeRate",settings.midiOutTimeRate);
e->setConf("centerPattern",settings.centerPattern);
e->setConf("ordersCursor",settings.ordersCursor);
e->setConf("persistFadeOut",settings.persistFadeOut);
@ -2975,6 +3107,13 @@ void FurnaceGUI::commitSettings() {
e->setConf("cursorFollowsOrder",settings.cursorFollowsOrder);
e->setConf("iCannotWait",settings.iCannotWait);
e->setConf("orderButtonPos",settings.orderButtonPos);
e->setConf("compress",settings.compress);
e->setConf("newPatternFormat",settings.newPatternFormat);
e->setConf("renderBackend",settings.renderBackend);
e->setConf("renderClearPos",settings.renderClearPos);
e->setConf("insertBehavior",settings.insertBehavior);
e->setConf("pullDeleteRow",settings.pullDeleteRow);
e->setConf("newSongBehavior",settings.newSongBehavior);
// colors
for (int i=0; i<GUI_COLOR_MAX; i++) {
@ -3014,17 +3153,21 @@ void FurnaceGUI::commitSettings() {
applyUISettings();
ImGui_ImplSDLRenderer_DestroyFontsTexture();
if (rend) rend->destroyFontsTexture();
if (!ImGui::GetIO().Fonts->Build()) {
logE("error while building font atlas!");
showError("error while loading fonts! please check your settings.");
ImGui::GetIO().Fonts->Clear();
mainFont=ImGui::GetIO().Fonts->AddFontDefault();
patFont=mainFont;
ImGui_ImplSDLRenderer_DestroyFontsTexture();
if (rend) rend->destroyFontsTexture();
if (!ImGui::GetIO().Fonts->Build()) {
logE("error again while building font atlas!");
} else {
rend->createFontsTexture();
}
} else {
rend->createFontsTexture();
}
}
@ -3363,6 +3506,35 @@ void FurnaceGUI::popAccentColors() {
ImGui::PopStyleColor(24);
}
void FurnaceGUI::pushDestColor() {
pushAccentColors(uiColors[GUI_COLOR_DESTRUCTIVE],uiColors[GUI_COLOR_DESTRUCTIVE],uiColors[GUI_COLOR_DESTRUCTIVE],ImVec4(0.0f,0.0f,0.0f,0.0f));
}
void FurnaceGUI::popDestColor() {
popAccentColors();
}
void FurnaceGUI::pushWarningColor(bool warnCond, bool errorCond) {
if (warnColorPushed) {
logE("warnColorPushed");
abort();
}
if (errorCond) {
pushAccentColors(uiColors[GUI_COLOR_ERROR],uiColors[GUI_COLOR_ERROR],uiColors[GUI_COLOR_ERROR],ImVec4(0.0f,0.0f,0.0f,0.0f));
warnColorPushed=true;
} else if (warnCond) {
pushAccentColors(uiColors[GUI_COLOR_WARNING],uiColors[GUI_COLOR_WARNING],uiColors[GUI_COLOR_WARNING],ImVec4(0.0f,0.0f,0.0f,0.0f));
warnColorPushed=true;
}
}
void FurnaceGUI::popWarningColor() {
if (warnColorPushed) {
popAccentColors();
warnColorPushed=false;
}
}
#define IGFD_FileStyleByExtension IGFD_FileStyleByExtention
#ifdef _WIN32
@ -3493,7 +3665,28 @@ void FurnaceGUI::applyUISettings(bool updateFonts) {
}
sty.Colors[ImGuiCol_WindowBg]=uiColors[GUI_COLOR_FRAME_BACKGROUND];
sty.Colors[ImGuiCol_ChildBg]=uiColors[GUI_COLOR_FRAME_BACKGROUND_CHILD];
sty.Colors[ImGuiCol_PopupBg]=uiColors[GUI_COLOR_FRAME_BACKGROUND_POPUP];
sty.Colors[ImGuiCol_TitleBg]=uiColors[GUI_COLOR_TITLE_INACTIVE];
sty.Colors[ImGuiCol_TitleBgCollapsed]=uiColors[GUI_COLOR_TITLE_COLLAPSED];
sty.Colors[ImGuiCol_MenuBarBg]=uiColors[GUI_COLOR_MENU_BAR];
sty.Colors[ImGuiCol_ModalWindowDimBg]=uiColors[GUI_COLOR_MODAL_BACKDROP];
sty.Colors[ImGuiCol_ScrollbarBg]=uiColors[GUI_COLOR_SCROLL_BACKGROUND];
sty.Colors[ImGuiCol_ScrollbarGrab]=uiColors[GUI_COLOR_SCROLL];
sty.Colors[ImGuiCol_ScrollbarGrabHovered]=uiColors[GUI_COLOR_SCROLL_HOVER];
sty.Colors[ImGuiCol_ScrollbarGrabActive]=uiColors[GUI_COLOR_SCROLL_ACTIVE];
sty.Colors[ImGuiCol_Separator]=uiColors[GUI_COLOR_SEPARATOR];
sty.Colors[ImGuiCol_SeparatorHovered]=uiColors[GUI_COLOR_SEPARATOR_HOVER];
sty.Colors[ImGuiCol_SeparatorActive]=uiColors[GUI_COLOR_SEPARATOR_ACTIVE];
sty.Colors[ImGuiCol_DockingPreview]=uiColors[GUI_COLOR_DOCKING_PREVIEW];
sty.Colors[ImGuiCol_DockingEmptyBg]=uiColors[GUI_COLOR_DOCKING_EMPTY];
sty.Colors[ImGuiCol_TableHeaderBg]=uiColors[GUI_COLOR_TABLE_HEADER];
sty.Colors[ImGuiCol_TableBorderStrong]=uiColors[GUI_COLOR_TABLE_BORDER_HARD];
sty.Colors[ImGuiCol_TableBorderLight]=uiColors[GUI_COLOR_TABLE_BORDER_SOFT];
sty.Colors[ImGuiCol_DragDropTarget]=uiColors[GUI_COLOR_DRAG_DROP_TARGET];
sty.Colors[ImGuiCol_NavHighlight]=uiColors[GUI_COLOR_NAV_HIGHLIGHT];
sty.Colors[ImGuiCol_NavWindowingHighlight]=uiColors[GUI_COLOR_NAV_WIN_HIGHLIGHT];
sty.Colors[ImGuiCol_NavWindowingDimBg]=uiColors[GUI_COLOR_NAV_WIN_BACKDROP];
sty.Colors[ImGuiCol_Text]=uiColors[GUI_COLOR_TEXT];
sty.Colors[ImGuiCol_Button]=primary;
@ -3709,7 +3902,8 @@ void FurnaceGUI::applyUISettings(bool updateFonts) {
}
mainFont->FallbackChar='?';
mainFont->DotChar='.';
mainFont->EllipsisChar='.';
mainFont->EllipsisCharCount=3;
}
// TODO: allow changing these colors.
@ -3731,6 +3925,10 @@ void FurnaceGUI::applyUISettings(bool updateFonts) {
ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".ttc",uiColors[GUI_COLOR_FILE_FONT],ICON_FA_FONT);
ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".mod",uiColors[GUI_COLOR_FILE_SONG_IMPORT],ICON_FA_FILE);
ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".fc13",uiColors[GUI_COLOR_FILE_SONG_IMPORT],ICON_FA_FILE);
ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".fc14",uiColors[GUI_COLOR_FILE_SONG_IMPORT],ICON_FA_FILE);
ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".fc",uiColors[GUI_COLOR_FILE_SONG_IMPORT],ICON_FA_FILE);
ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".smod",uiColors[GUI_COLOR_FILE_SONG_IMPORT],ICON_FA_FILE);
ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".ftm",uiColors[GUI_COLOR_FILE_SONG_IMPORT],ICON_FA_FILE);
ImGuiFileDialog::Instance()->SetFileStyle(IGFD_FileStyleByExtension,".tfi",uiColors[GUI_COLOR_FILE_INSTR],ICON_FA_FILE);

View file

@ -56,7 +56,7 @@ void FurnaceGUI::drawSpeed(bool asChild) {
if (tempoView) setHz/=2.5;
if (setHz<1) setHz=1;
if (setHz>999) setHz=999;
e->setSongRate(setHz,setHz<52);
e->setSongRate(setHz);
}
if (tempoView) {
ImGui::SameLine();

View file

@ -92,6 +92,7 @@ void FurnaceGUI::drawSubSongs(bool asChild) {
ImGui::SetTooltip("Add");
}
ImGui::SameLine();
pushDestColor();
if (ImGui::Button(ICON_FA_MINUS "##SubSongDel")) {
if (e->song.subsong.size()<=1) {
showError("this is the only subsong!");
@ -99,6 +100,7 @@ void FurnaceGUI::drawSubSongs(bool asChild) {
showWarning("are you sure you want to remove this subsong?",GUI_WARN_SUBSONG_DEL);
}
}
popDestColor();
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip("Remove");
}

View file

@ -310,6 +310,7 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo
case DIV_SYSTEM_GB: {
int chipType=flags.getInt("chipType",0);
bool noAntiClick=flags.getBool("noAntiClick",false);
bool invertWave=flags.getBool("invertWave",true);
bool enoughAlready=flags.getBool("enoughAlready",false);
if (ImGui::Checkbox("Disable anti-click",&noAntiClick)) {
@ -332,6 +333,26 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo
chipType=3;
altered=true;
}
ImGui::Text("Wave channel orientation:");
if (chipType==3) {
if (ImGui::RadioButton("Normal",!invertWave)) {
invertWave=false;
altered=true;
}
if (ImGui::RadioButton("Inverted",invertWave)) {
invertWave=true;
altered=true;
}
} else {
if (ImGui::RadioButton("Exact data (inverted)",!invertWave)) {
invertWave=false;
altered=true;
}
if (ImGui::RadioButton("Exact output (normal)",invertWave)) {
invertWave=true;
altered=true;
}
}
if (ImGui::Checkbox("Pretty please one more compat flag when I use arpeggio and my sound length",&enoughAlready)) {
altered=true;
}
@ -340,6 +361,7 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo
e->lockSave([&]() {
flags.set("chipType",chipType);
flags.set("noAntiClick",noAntiClick);
flags.set("invertWave",invertWave);
flags.set("enoughAlready",enoughAlready);
});
}
@ -434,6 +456,9 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo
case DIV_SYSTEM_FDS:
case DIV_SYSTEM_MMC5: {
int clockSel=flags.getInt("clockSel",0);
bool dpcmMode=flags.getBool("dpcmMode",true);
ImGui::Text("Clock rate:");
if (ImGui::RadioButton("NTSC (1.79MHz)",clockSel==0)) {
clockSel=0;
@ -448,9 +473,21 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo
altered=true;
}
ImGui::Text("DPCM channel mode:");
if (ImGui::RadioButton("DPCM (muffled samples; low CPU usage)",dpcmMode)) {
dpcmMode=true;
altered=true;
}
if (ImGui::RadioButton("PCM (crisp samples; high CPU usage)",!dpcmMode)) {
dpcmMode=false;
altered=true;
}
if (altered) {
e->lockSave([&]() {
flags.set("clockSel",clockSel);
flags.set("dpcmMode",dpcmMode);
});
}
break;
@ -826,7 +863,7 @@ bool FurnaceGUI::drawSysConf(int chan, DivSystem type, DivConfig& flags, bool mo
if (echoBufSize1<0) echoBufSize1=0;
if (echoBufSize1>2725) echoBufSize1=2725;
echoDelay=2725-echoBufSize1;
altered=true;;
altered=true;
} rightClickable
ImGui::Text("Echo feedback:");
if (CWSliderInt("##EchoFeedback",&echoFeedback,0,255)) {

View file

@ -223,26 +223,28 @@ void FurnaceGUI::initTutorial() {
void FurnaceGUI::syncTutorial() {
// tutorial.userComesFrom=e->getConfInt("tutUserComesFrom",0);
tutorial.introPlayed=e->getConfBool("tutIntroPlayed",false);
// tutorial.welcome=e->getConfBool("tutWelcome",false);
tutorial.protoWelcome=e->getConfBool("tutProtoWelcome2",false);
}
void FurnaceGUI::commitTutorial() {
// e->setConf("tutUserComesFrom",tutorial.userComesFrom);
e->setConf("tutIntroPlayed",tutorial.introPlayed);
// e->setConf("tutWelcome",tutorial.welcome);
e->setConf("tutProtoWelcome2",tutorial.protoWelcome);
}
void FurnaceGUI::activateTutorial(FurnaceGUITutorials which) {
if (tutorial.welcome && !tutorial.taken[which] && !ImGui::IsPopupOpen((const char*)NULL,ImGuiPopupFlags_AnyPopupId|ImGuiPopupFlags_AnyPopupLevel) && curTutorial==-1 && introPos>=10.0) {
/*
if (tutorial.protoWelcome && !tutorial.taken[which] && !ImGui::IsPopupOpen((const char*)NULL,ImGuiPopupFlags_AnyPopupId|ImGuiPopupFlags_AnyPopupLevel) && curTutorial==-1 && introPos>=10.0) {
logV("activating tutorial %d.",which);
curTutorial=which;
curTutorialStep=0;
}
*/
}
void FurnaceGUI::drawTutorial() {
// welcome
if (!tutorial.welcome) {
if (!tutorial.protoWelcome) {
ImGui::OpenPopup("Welcome");
}
if (ImGui::BeginPopupModal("Welcome",NULL,ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoTitleBar)) {
@ -253,7 +255,35 @@ void FurnaceGUI::drawTutorial() {
ImGui::Text("welcome to Furnace, the biggest open-source chiptune tracker!");
ImGui::TextWrapped("get ready for the tutorial, which will teach you how to use it.");
ImGui::TextWrapped(
"did I say that 0.6pre5 will have a tutorial? well, it doesn't...\n"
"the reason is because 0.6pre5 fixes a critical bug which may cause config loss in some machines.\n"
"furthermore, it dramatically improves the backup system. couldn't put this version on hold anymore."
);
ImGui::Separator();
ImGui::TextWrapped("here are some tips to get you started:");
ImGui::TextWrapped(
"- add an instrument by clicking on + in Instruments\n"
"- click on the pattern view to focus it\n"
"- channel columns have the following, in this order: note, instrument, volume and effects\n"
"- hit space bar while on the pattern to toggle Edit Mode\n"
"- click on the pattern or use arrow keys to move the cursor\n"
"- values (instrument, volume, effects and effect values) are in hexadecimal\n"
"- hit enter to play/stop the song\n"
"- extend the song by adding more orders in the Orders window\n"
"- click on the Orders matrix to change the patterns of a channel (left click increases; right click decreases)"
);
ImGui::TextWrapped(
"if you need help, you may:\n"
"- read the (incomplete) manual: https://github.com/tildearrow/furnace/blob/master/doc/README.md\n"
"- ask for help in Discussions (https://github.com/tildearrow/furnace/discussions) or the Furnace Discord (https://discord.gg/EfrwT2wq7z)"
);
ImGui::Separator();
ImGui::TextWrapped(
"there are two interface modes: Basic, and Advanced.\n"
@ -265,13 +295,13 @@ void FurnaceGUI::drawTutorial() {
if (ImGui::Button("Start in Basic Mode")) {
basicMode=true;
tutorial.welcome=true;
tutorial.protoWelcome=true;
commitTutorial();
ImGui::CloseCurrentPopup();
}
if (ImGui::Button("Start in Advanced Mode")) {
basicMode=false;
tutorial.welcome=true;
tutorial.protoWelcome=true;
commitTutorial();
ImGui::CloseCurrentPopup();
}