Merge branch 'master' into sysmgrtooltip_syschaninfo

This commit is contained in:
tildearrow 2024-10-10 02:38:18 -05:00
commit c6dfd4f10b
309 changed files with 93453 additions and 72703 deletions

View file

@ -35,7 +35,9 @@ const char* aboutLine[]={
_N("-- program --"),
"tildearrow",
_N("A M 4 N (intro tune)"),
"Adam Lederer",
"akumanatt",
"asiekierka",
"cam900",
"djtuBIG-MaliceX",
"Eknous",
@ -89,6 +91,7 @@ const char* aboutLine[]={
"aloelucidity",
"AmigaX",
"AquaDoesStuff",
"AstralBlue",
"AURORA*FIELDS",
"Background2982",
"battybeats",
@ -299,8 +302,12 @@ const char* aboutLine[]={
_N("PowerNoise emulator by scratchminer"),
_N("ep128emu by Istvan Varga"),
_N("NDS sound emulator by cam900"),
_N("openMSX YMF278 emulator (modified version) by the openMSX developers"),
_N("SID2 emulator by LTVA (modification of reSID emulator)"),
_N("SID3 emulator by LTVA"),
"",
_N("greetings to:"),
"floxy!",
"NEOART Costa Rica",
"Xenium Demoparty",
"@party",
@ -341,14 +348,8 @@ void FurnaceGUI::drawAbout() {
float r=0;
float g=0;
float b=0;
float peakMix=settings.partyTime?0:0.3;
if (settings.partyTime) {
for (int j=0; j<e->getAudioDescGot().outChans; j++) {
peakMix+=peak[j];
}
peakMix/=e->getAudioDescGot().outChans;
}
ImGui::ColorConvertHSVtoRGB(aboutHue,1.0,0.25+MIN(0.75f,peakMix*0.75f),r,g,b);
float peakMix=0.3;
ImGui::ColorConvertHSVtoRGB(aboutHue,1.0,0.475,r,g,b);
dl->AddRectFilled(ImVec2(0,0),ImVec2(canvasW,canvasH),0xff000000);
bool skip=false;
bool skip2=false;

View file

@ -25,20 +25,124 @@
#include <algorithm>
#include <ctype.h>
#include "../ta-log.h"
#include "util.h"
static inline bool matchFuzzy(const char* haystack, const char* needle) {
size_t h_i=0; // haystack idx
size_t n_i=0; // needle idx
while (needle[n_i]!='\0') {
for (; std::tolower(haystack[h_i])!=std::tolower(needle[n_i]); h_i++) {
if (haystack[h_i]=='\0')
return false;
}
n_i+=1;
struct MatchScore {
size_t charsBeforeNeedle=0;
size_t charsWithinNeedle=0;
static bool IsFirstPreferable(const MatchScore& a, const MatchScore& b) {
int aBetter;
aBetter=b.charsWithinNeedle-a.charsWithinNeedle;
if (aBetter!=0) return aBetter>0;
aBetter=b.charsBeforeNeedle-a.charsBeforeNeedle;
if (aBetter!=0) return aBetter>0;
return false;
}
return true;
};
struct MatchResult {
MatchScore score;
std::vector<int> highlightChars;
};
static bool charMatch(const char* a, const char* b) {
// stub for future utf8 support, possibly with matching for related chars?
return std::tolower(*a)==std::tolower(*b);
}
// #define MATCH_GREEDY
// #define RUN_MATCH_TEST
static bool matchFuzzy(const char* haystack, int haystackLen, const char* needle, int needleLen, MatchResult* result) {
if (needleLen==0) {
result->score.charsBeforeNeedle=0;
result->score.charsWithinNeedle=0;
result->highlightChars.clear();
return true;
}
std::vector<MatchResult> matchPool(needleLen+1);
std::vector<MatchResult*> unusedMatches(needleLen+1);
std::vector<MatchResult*> matchesByLen(needleLen+1);
for (int i=0; i<needleLen+1; i++) {
unusedMatches[i]=&matchPool[i];
matchesByLen[i]=NULL;
}
for (int hIdx=0; hIdx<haystackLen; hIdx++) {
// try to continue our in-flight valid matches
for (int matchLen=needleLen-1; matchLen>=0; matchLen--) {
MatchResult*& m=matchesByLen[matchLen];
// ignore null matches except for 0
if (matchLen>0 && !m) continue;
#ifdef MATCH_GREEDY
// in greedy mode, don't start any new matches once we've already started matching.
// this will still return the correct bool result, but its score could be much poorer
// than the optimal match. consider the case:
//
// find "gl" in "google"
//
// greedy will see the match "g...l.", which has charsWithinNeedle of 3, while the
// fully algorithm will find the tighter match "...gl.", which has
// charsWithinNeedle of 0
if (matchLen==0 && unusedMatches.size() < matchPool.size()) {
continue;
}
#endif
// check match!
if (charMatch(haystack+hIdx, needle+matchLen)) {
// pull a fresh match from the pool if necessary
if (matchLen==0) {
m=unusedMatches.back();
unusedMatches.pop_back();
m->score.charsBeforeNeedle=hIdx;
m->score.charsWithinNeedle=0;
m->highlightChars.clear();
}
m->highlightChars.push_back(hIdx);
// advance, replacing the previous match of an equal len, which can only have been
// worse because it existed before us, so we can prune it out
if (matchesByLen[matchLen+1]) {
unusedMatches.push_back(matchesByLen[matchLen+1]);
}
matchesByLen[matchLen+1]=m;
m=NULL;
} else {
// tally up charsWithinNeedle
if (matchLen>0) {
matchesByLen[matchLen]->score.charsWithinNeedle++;
}
}
}
}
if (matchesByLen[needleLen]) {
if (result) *result=*matchesByLen[needleLen];
return true;
}
return false;
}
#ifdef RUN_MATCH_TEST
static void matchFuzzyTest() {
String hay="a__i_a_i__o";
String needle="aio";
MatchResult match;
matchFuzzy(hay.c_str(), hay.length(), needle.c_str(), needle.length(), &match);
logI( "match.score.charsWithinNeedle: %d", match.score.charsWithinNeedle );
}
#endif
void FurnaceGUI::drawPalette() {
bool accepted=false;
@ -67,45 +171,52 @@ void FurnaceGUI::drawPalette() {
break;
}
#ifdef RUN_MATCH_TEST
matchFuzzyTest();
#endif
if (ImGui::InputTextWithHint("##CommandPaletteSearch",hint,&paletteQuery) || paletteFirstFrame) {
paletteSearchResults.clear();
std::vector<MatchScore> matchScores;
auto Evaluate=[&](int i, const char* name, int nameLen) {
MatchResult result;
if (matchFuzzy(name, nameLen, paletteQuery.c_str(), paletteQuery.length(), &result)) {
paletteSearchResults.emplace_back();
paletteSearchResults.back().id=i;
paletteSearchResults.back().highlightChars=std::move(result.highlightChars);
matchScores.push_back(result.score);
}
};
switch (curPaletteType) {
case CMDPAL_TYPE_MAIN:
for (int i=0; i<GUI_ACTION_MAX; i++) {
if (guiActions[i].defaultBind==-1) continue;
if (matchFuzzy(guiActions[i].friendlyName,paletteQuery.c_str())) {
paletteSearchResults.push_back(i);
}
if (guiActions[i].isNotABind()) continue;
Evaluate(i,guiActions[i].friendlyName,strlen(guiActions[i].friendlyName));
}
break;
case CMDPAL_TYPE_RECENT:
for (int i=0; i<(int)recentFile.size(); i++) {
if (matchFuzzy(recentFile[i].c_str(),paletteQuery.c_str())) {
paletteSearchResults.push_back(i);
}
Evaluate(i,recentFile[i].c_str(),recentFile[i].length());
}
break;
case CMDPAL_TYPE_INSTRUMENTS:
case CMDPAL_TYPE_INSTRUMENT_CHANGE:
if (matchFuzzy(_("- None -"),paletteQuery.c_str())) {
paletteSearchResults.push_back(0);
}
case CMDPAL_TYPE_INSTRUMENT_CHANGE: {
const char* noneStr=_("- None -");
Evaluate(0,noneStr,strlen(noneStr));
for (int i=0; i<e->song.insLen; i++) {
String s=fmt::sprintf("%02X: %s", i, e->song.ins[i]->name.c_str());
if (matchFuzzy(s.c_str(),paletteQuery.c_str())) {
paletteSearchResults.push_back(i+1); // because over here ins=0 is 'None'
}
Evaluate(i+1,s.c_str(),s.length()); // because over here ins=0 is 'None'
}
break;
}
case CMDPAL_TYPE_SAMPLES:
for (int i=0; i<e->song.sampleLen; i++) {
if (matchFuzzy(e->song.sample[i]->name.c_str(),paletteQuery.c_str())) {
paletteSearchResults.push_back(i);
}
Evaluate(i,e->song.sample[i]->name.c_str(),e->song.sample[i]->name.length());
}
break;
@ -113,9 +224,7 @@ void FurnaceGUI::drawPalette() {
for (int i=0; availableSystems[i]; i++) {
int ds=availableSystems[i];
const char* sysname=getSystemName((DivSystem)ds);
if (matchFuzzy(sysname,paletteQuery.c_str())) {
paletteSearchResults.push_back(ds);
}
Evaluate(ds,sysname,strlen(sysname));
}
break;
@ -124,67 +233,110 @@ void FurnaceGUI::drawPalette() {
ImGui::CloseCurrentPopup();
break;
};
// sort indices by match quality
std::vector<int> sortingIndices(paletteSearchResults.size());
for (size_t i=0; i<sortingIndices.size(); ++i) sortingIndices[i]=(int)i;
std::sort(sortingIndices.begin(), sortingIndices.end(), [&](size_t a, size_t b) {
return MatchScore::IsFirstPreferable(matchScores[a], matchScores[b]);
});
// update paletteSearchResults from sorted indices (taking care not to stomp while we iterate
std::vector<PaletteSearchResult> paletteSearchResultsCopy=paletteSearchResults;
for (size_t i=0; i<sortingIndices.size(); ++i) paletteSearchResults[i]=paletteSearchResultsCopy[sortingIndices[i]];
}
ImVec2 avail=ImGui::GetContentRegionAvail();
avail.y-=ImGui::GetFrameHeightWithSpacing();
if (ImGui::BeginChild("CommandPaletteList",avail,false,0)) {
bool navigated=false;
if (ImGui::IsKeyPressed(ImGuiKey_UpArrow) && curPaletteChoice>0) {
curPaletteChoice-=1;
navigated=true;
}
if (ImGui::IsKeyPressed(ImGuiKey_DownArrow)) {
curPaletteChoice+=1;
navigated=true;
}
if (paletteSearchResults.size()>0 && curPaletteChoice<0) {
curPaletteChoice=0;
navigated=true;
}
if (curPaletteChoice>=(int)paletteSearchResults.size()) {
curPaletteChoice=paletteSearchResults.size()-1;
navigated=true;
}
for (int i=0; i<(int)paletteSearchResults.size(); i++) {
bool current=(i==curPaletteChoice);
int id=paletteSearchResults[i];
String s="???";
switch (curPaletteType) {
case CMDPAL_TYPE_MAIN:
s=guiActions[id].friendlyName;
break;
case CMDPAL_TYPE_RECENT:
s=recentFile[id].c_str();
break;
case CMDPAL_TYPE_INSTRUMENTS:
case CMDPAL_TYPE_INSTRUMENT_CHANGE:
if (id==0) {
s=_("- None -");
} else {
s=fmt::sprintf("%02X: %s", id-1, e->song.ins[id-1]->name.c_str());
}
break;
case CMDPAL_TYPE_SAMPLES:
s=e->song.sample[id]->name.c_str();
break;
case CMDPAL_TYPE_ADD_CHIP:
s=getSystemName((DivSystem)id);
break;
default:
logE(_("invalid command palette type"));
break;
};
if (ImGui::Selectable(s.c_str(),current)) {
curPaletteChoice=i;
accepted=true;
bool navigated=false;
if (ImGui::IsKeyPressed(ImGuiKey_UpArrow) && curPaletteChoice>0) {
curPaletteChoice-=1;
navigated=true;
}
if ((navigated || paletteFirstFrame) && current) ImGui::SetScrollHereY();
if (ImGui::IsKeyPressed(ImGuiKey_DownArrow)) {
curPaletteChoice+=1;
navigated=true;
}
if (paletteSearchResults.size()>0 && curPaletteChoice<0) {
curPaletteChoice=0;
navigated=true;
}
if (curPaletteChoice>=(int)paletteSearchResults.size()) {
curPaletteChoice=paletteSearchResults.size()-1;
navigated=true;
}
int columnCount=curPaletteType==CMDPAL_TYPE_MAIN ? 2 : 1;
if (ImGui::BeginTable("##commandPaletteTable",columnCount,ImGuiTableFlags_SizingStretchProp)) {
// ImGui::TableSetupColumn("##action",ImGuiTableColumnFlags_WidthStretch);
// ImGui::TableSetupColumn("##shortcut");
for (int i=0; i<(int)paletteSearchResults.size(); i++) {
bool current=(i==curPaletteChoice);
int id=paletteSearchResults[i].id;
String s="???";
switch (curPaletteType) {
case CMDPAL_TYPE_MAIN:
s=guiActions[id].friendlyName;
break;
case CMDPAL_TYPE_RECENT:
s=recentFile[id].c_str();
break;
case CMDPAL_TYPE_INSTRUMENTS:
case CMDPAL_TYPE_INSTRUMENT_CHANGE:
if (id==0) {
s=_("- None -");
} else {
s=fmt::sprintf("%02X: %s", id-1, e->song.ins[id-1]->name.c_str());
}
break;
case CMDPAL_TYPE_SAMPLES:
s=e->song.sample[id]->name.c_str();
break;
case CMDPAL_TYPE_ADD_CHIP:
s=getSystemName((DivSystem)id);
break;
default:
logE(_("invalid command palette type"));
break;
};
ImGui::TableNextRow();
ImGui::TableNextColumn();
ImGui::PushID(s.c_str());
bool selectable=ImGui::Selectable("##paletteSearchItem",current,ImGuiSelectableFlags_SpanAllColumns|ImGuiSelectableFlags_AllowOverlap);
const char* str=s.c_str();
size_t chCursor=0;
const std::vector<int>& highlights=paletteSearchResults[i].highlightChars;
for (size_t ch=0; ch<highlights.size(); ch++) {
ImGui::SameLine(0.0f,0.0f);
ImGui::Text("%.*s", (int)(highlights[ch]-chCursor), str+chCursor);
ImGui::SameLine(0.0f,0.0f);
ImGui::TextColored(uiColors[GUI_COLOR_ACCENT_PRIMARY], "%.1s", str+highlights[ch]);
chCursor=highlights[ch]+1;
}
ImGui::SameLine(0.0f,0.0f);
ImGui::Text("%.*s", (int)(s.length()-chCursor), str+chCursor);
if (curPaletteType==CMDPAL_TYPE_MAIN) {
ImGui::TableNextColumn();
ImGui::TextColored(uiColors[GUI_COLOR_TEXT_DISABLED], "%s", getMultiKeysName(actionKeys[paletteSearchResults[i].id].data(),actionKeys[paletteSearchResults[i].id].size(),true).c_str());
}
if (selectable) {
curPaletteChoice=i;
accepted=true;
}
ImGui::PopID();
if ((navigated || paletteFirstFrame) && current) ImGui::SetScrollHereY();
}
ImGui::EndTable();
}
}
ImGui::EndChild();
@ -206,7 +358,7 @@ void FurnaceGUI::drawPalette() {
if (accepted) {
if (paletteSearchResults.size()>0) {
int i=paletteSearchResults[curPaletteChoice];
int i=paletteSearchResults[curPaletteChoice].id;
switch (curPaletteType) {
case CMDPAL_TYPE_MAIN:
doAction(i);

View file

@ -161,6 +161,8 @@ void FurnaceGUI::drawCSPlayer() {
ImGui::TableNextColumn();
ImGui::Text(_("vols"));
ImGui::TableNextColumn();
ImGui::Text(_("volst"));
ImGui::TableNextColumn();
ImGui::Text(_("vib"));
ImGui::TableNextColumn();
ImGui::Text(_("porta"));
@ -189,6 +191,8 @@ void FurnaceGUI::drawCSPlayer() {
ImGui::TableNextColumn();
ImGui::Text("%+d",state->volSpeed);
ImGui::TableNextColumn();
ImGui::Text("%+d",state->volSpeedTarget);
ImGui::TableNextColumn();
ImGui::Text("%d/%d (%d)",state->vibratoDepth,state->vibratoRate,state->vibratoPos);
ImGui::TableNextColumn();
ImGui::Text("-> %d (%d)",state->portaTarget,state->portaSpeed);

View file

@ -69,6 +69,9 @@ void FurnaceGUI::startSelection(int xCoarse, int xFine, int y, bool fullRow) {
selStart.y=y;
selEnd.y=y;
} else {
if (xCoarse!=cursor.xCoarse || y!=cursor.y) {
makeCursorUndo();
}
cursor.xCoarse=xCoarse;
cursor.xFine=xFine;
cursor.y=y;
@ -208,6 +211,9 @@ void FurnaceGUI::finishSelection() {
}
void FurnaceGUI::moveCursor(int x, int y, bool select) {
if (y>=editStepCoarse || y<=-editStepCoarse || x<=-5 || x>=5 ) {
makeCursorUndo();
}
if (!select) {
finishSelection();
}
@ -326,6 +332,7 @@ void FurnaceGUI::moveCursor(int x, int y, bool select) {
}
void FurnaceGUI::moveCursorPrevChannel(bool overflow) {
makeCursorUndo();
finishSelection();
curNibble=false;
@ -354,6 +361,7 @@ void FurnaceGUI::moveCursorPrevChannel(bool overflow) {
}
void FurnaceGUI::moveCursorNextChannel(bool overflow) {
makeCursorUndo();
finishSelection();
curNibble=false;
@ -382,6 +390,7 @@ void FurnaceGUI::moveCursorNextChannel(bool overflow) {
}
void FurnaceGUI::moveCursorTop(bool select) {
makeCursorUndo();
if (!select) {
finishSelection();
}
@ -403,6 +412,7 @@ void FurnaceGUI::moveCursorTop(bool select) {
}
void FurnaceGUI::moveCursorBottom(bool select) {
makeCursorUndo();
if (!select) {
finishSelection();
}

View file

@ -997,6 +997,37 @@ void FurnaceGUI::drawSampleList(bool asChild) {
}
}
// HACK: template. any way to remove it?
template<typename func_waveItemData> void FurnaceGUI::waveListHorizontalGroup(float* wavePreview, int dir, int count, const func_waveItemData& waveItemData) {
if (count==0) return;
float idealWidthMin=225.0f*dpiScale;
float idealWidthMax=350.0f*dpiScale;
float availX=ImGui::GetContentRegionAvail().x;
int columnCount=CLAMP((int)(availX/idealWidthMin),1,count);
int rowCount=(int)ceilf(count/(float)columnCount);
columnCount=(int)ceilf(count/(float)rowCount);
float columnWidth=MIN(CLAMP(availX/columnCount,idealWidthMin,idealWidthMax),availX);
if (ImGui::BeginTable("##waveListGroupTable",columnCount,ImGuiTableFlags_SizingFixedSame)) {
for (int col=0; col<columnCount; col++) {
ImGui::TableSetupColumn("##column",ImGuiTableColumnFlags_WidthFixed,columnWidth);
}
for (int row=0; row<rowCount; row++) {
ImGui::TableNextRow();
for (int col=0; col<columnCount; col++) {
ImGui::TableNextColumn();
int idx=row+col*rowCount;
if (idx>=count) continue;
int waveIdx, asset;
waveItemData(row+col*rowCount,&waveIdx,&asset);
waveListItem(waveIdx,wavePreview,dir,asset);
}
}
ImGui::EndTable();
}
}
void FurnaceGUI::actualWaveList() {
float wavePreview[257];
@ -1021,10 +1052,17 @@ void FurnaceGUI::actualWaveList() {
ImGui::EndPopup();
}
if (treeNode) {
int assetIndex=0;
for (int j: i.entries) {
waveListItem(j,wavePreview,dirIndex,assetIndex);
assetIndex++;
if (settings.horizontalDataView) {
waveListHorizontalGroup(wavePreview,dirIndex,i.entries.size(),[&](int i_, int* waveIdx, int* asset) {
*waveIdx=i.entries[i_];
*asset=i_;
});
} else {
int assetIndex=0;
for (int j: i.entries) {
waveListItem(j,wavePreview,dirIndex,assetIndex);
assetIndex++;
}
}
ImGui::TreePop();
}
@ -1037,10 +1075,19 @@ void FurnaceGUI::actualWaveList() {
});
}
} else {
for (int i=0; i<(int)e->song.wave.size(); i++) {
if (settings.horizontalDataView) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
waveListItem(i,wavePreview,-1,-1);
waveListHorizontalGroup(wavePreview,-1,(int)e->song.wave.size(),[&](int i, int* waveIdx, int* asset) {
*waveIdx=i;
*asset=-1;
});
} else {
for (int i=0; i<(int)e->song.wave.size(); i++) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
waveListItem(i,wavePreview,-1,-1);
}
}
}
}

View file

@ -51,6 +51,7 @@
#include "../engine/platform/pcmdac.h"
#include "../engine/platform/k007232.h"
#include "../engine/platform/ga20.h"
#include "../engine/platform/supervision.h"
#include "../engine/platform/sm8521.h"
#include "../engine/platform/pv1000.h"
#include "../engine/platform/k053260.h"

View file

@ -145,6 +145,7 @@ void FurnaceGUI::drawDebug() {
ImGui::Text("- portaNote = %d",ch->portaNote);
ImGui::Text("- volume = %.4x",ch->volume);
ImGui::Text("- volSpeed = %d",ch->volSpeed);
ImGui::Text("- volSpeedTarget = %d",ch->volSpeedTarget);
ImGui::Text("- cut = %d",ch->cut);
ImGui::Text("- rowDelay = %d",ch->rowDelay);
ImGui::Text("- volMax = %.4x",ch->volMax);
@ -730,6 +731,25 @@ void FurnaceGUI::drawDebug() {
ImGui::Text("result: %.0f%%",realVol*100.0f);
ImGui::TreePop();
}
if (ImGui::TreeNode("Cursor Undo Debug")) {
auto DrawSpot=[&](const CursorJumpPoint& spot) {
ImGui::Text("[%d:%d] <%d:%d, %d>", spot.subSong, spot.order, spot.point.xCoarse, spot.point.xFine, spot.point.y);
};
if (ImGui::BeginChild("##CursorUndoDebugChild", ImVec2(0, 300), true)) {
if (ImGui::BeginTable("##CursorUndoDebug", 2, ImGuiTableFlags_Borders|ImGuiTableFlags_SizingStretchSame)) {
for (size_t row=0; row<MAX(cursorUndoHist.size(),cursorRedoHist.size()); ++row) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
if (row<cursorUndoHist.size()) DrawSpot(cursorUndoHist[cursorUndoHist.size()-row-1]);
ImGui::TableNextColumn();
if (row<cursorRedoHist.size()) DrawSpot(cursorRedoHist[cursorRedoHist.size()-row-1]);
}
ImGui::EndTable();
}
}
ImGui::EndChild();
ImGui::TreePop();
}
if (ImGui::TreeNode("User Interface")) {
if (ImGui::Button("Inspect")) {
inspectorOpen=!inspectorOpen;

View file

@ -73,6 +73,8 @@ void FurnaceGUI::doAction(int what) {
case GUI_ACTION_UNDO:
if (curWindow==GUI_WINDOW_SAMPLE_EDIT) {
doUndoSample();
} else if (curWindow==GUI_WINDOW_INS_EDIT) {
doUndoInstrument();
} else {
doUndo();
}
@ -80,6 +82,8 @@ void FurnaceGUI::doAction(int what) {
case GUI_ACTION_REDO:
if (curWindow==GUI_WINDOW_SAMPLE_EDIT) {
doRedoSample();
} else if (curWindow==GUI_WINDOW_INS_EDIT) {
doRedoInstrument();
} else {
doRedo();
}
@ -123,16 +127,16 @@ void FurnaceGUI::doAction(int what) {
pendingStepUpdate=1;
break;
case GUI_ACTION_OCTAVE_UP:
if (++curOctave>7) {
curOctave=7;
if (++curOctave>GUI_EDIT_OCTAVE_MAX) {
curOctave=GUI_EDIT_OCTAVE_MAX;
} else {
e->autoNoteOffAll();
failedNoteOn=false;
}
break;
case GUI_ACTION_OCTAVE_DOWN:
if (--curOctave<-5) {
curOctave=-5;
if (--curOctave<GUI_EDIT_OCTAVE_MIN) {
curOctave=GUI_EDIT_OCTAVE_MIN;
} else {
e->autoNoteOffAll();
failedNoteOn=false;
@ -676,17 +680,15 @@ void FurnaceGUI::doAction(int what) {
latchTarget=0;
latchNibble=false;
break;
case GUI_ACTION_PAT_ABSORB_INSTRUMENT: {
DivPattern* pat=e->curPat[cursor.xCoarse].getPattern(e->curOrders->ord[cursor.xCoarse][curOrder],false);
if (!pat) break;
for (int i=cursor.y; i>=0; i--) {
if (pat->data[i][2] >= 0) {
curIns=pat->data[i][2];
break;
}
}
case GUI_ACTION_PAT_ABSORB_INSTRUMENT:
doAbsorbInstrument();
break;
case GUI_ACTION_PAT_CURSOR_UNDO:
doCursorUndo();
break;
case GUI_ACTION_PAT_CURSOR_REDO:
doCursorRedo();
break;
}
case GUI_ACTION_INS_LIST_ADD:
if (settings.insTypeMenu) {
@ -1683,11 +1685,17 @@ void FurnaceGUI::doAction(int what) {
case GUI_ACTION_ORDERS_UP:
if (curOrder>0) {
setOrder(curOrder-1);
if (orderEditMode!=0) {
curNibble=false;
}
}
break;
case GUI_ACTION_ORDERS_DOWN:
if (curOrder<e->curSubSong->ordersLen-1) {
setOrder(curOrder+1);
if (orderEditMode!=0) {
curNibble=false;
}
}
break;
case GUI_ACTION_ORDERS_LEFT: {
@ -1700,6 +1708,9 @@ void FurnaceGUI::doAction(int what) {
break;
}
} while (!e->curSubSong->chanShow[orderCursor]);
if (orderEditMode!=0) {
curNibble=false;
}
break;
}
case GUI_ACTION_ORDERS_RIGHT: {
@ -1712,6 +1723,9 @@ void FurnaceGUI::doAction(int what) {
break;
}
} while (!e->curSubSong->chanShow[orderCursor]);
if (orderEditMode!=0) {
curNibble=false;
}
break;
}
case GUI_ACTION_ORDERS_INCREASE: {
@ -1719,6 +1733,9 @@ void FurnaceGUI::doAction(int what) {
if (e->curOrders->ord[orderCursor][curOrder]<0xff) {
e->curOrders->ord[orderCursor][curOrder]++;
}
if (orderEditMode!=0) {
curNibble=false;
}
break;
}
case GUI_ACTION_ORDERS_DECREASE: {
@ -1726,6 +1743,9 @@ void FurnaceGUI::doAction(int what) {
if (e->curOrders->ord[orderCursor][curOrder]>0) {
e->curOrders->ord[orderCursor][curOrder]--;
}
if (orderEditMode!=0) {
curNibble=false;
}
break;
}
case GUI_ACTION_ORDERS_EDIT_MODE:

View file

@ -508,7 +508,7 @@ void FurnaceGUI::drawMobileControls() {
mobileMenuOpen=false;
doAction(GUI_ACTION_SAVE_AS);
}
ImGui::SameLine();
if (ImGui::Button(_("Export"))) {
doAction(GUI_ACTION_EXPORT);
}
@ -533,6 +533,10 @@ void FurnaceGUI::drawMobileControls() {
drawSpeed(true);
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem(_("Comments"))) {
drawNotes(true);
ImGui::EndTabItem();
}
ImGui::EndTabBar();
}
break;
@ -616,6 +620,10 @@ void FurnaceGUI::drawMobileControls() {
mobileMenuPos=0.0f;
aboutOpen=true;
}
ImGui::SameLine();
if (ImGui::Button(_("WelcPopup"))) {
tutorial.protoWelcome=false;
}
if (ImGui::Button(_("Switch to Desktop Mode"))) {
toggleMobileUI(!mobileUI);
}
@ -647,8 +655,8 @@ void FurnaceGUI::drawEditControls() {
ImGui::TableNextColumn();
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
if (ImGui::InputInt("##Octave",&curOctave,1,1)) {
if (curOctave>7) curOctave=7;
if (curOctave<-5) curOctave=-5;
if (curOctave>GUI_EDIT_OCTAVE_MAX) curOctave=GUI_EDIT_OCTAVE_MAX;
if (curOctave<GUI_EDIT_OCTAVE_MIN) curOctave=GUI_EDIT_OCTAVE_MIN;
e->autoNoteOffAll();
failedNoteOn=false;
@ -808,8 +816,8 @@ void FurnaceGUI::drawEditControls() {
ImGui::SameLine();
ImGui::SetNextItemWidth(96.0f*dpiScale);
if (ImGui::InputInt("##Octave",&curOctave,1,1)) {
if (curOctave>7) curOctave=7;
if (curOctave<-5) curOctave=-5;
if (curOctave>GUI_EDIT_OCTAVE_MAX) curOctave=GUI_EDIT_OCTAVE_MAX;
if (curOctave<GUI_EDIT_OCTAVE_MIN) curOctave=GUI_EDIT_OCTAVE_MIN;
e->autoNoteOffAll();
failedNoteOn=false;
@ -926,8 +934,8 @@ void FurnaceGUI::drawEditControls() {
float avail=ImGui::GetContentRegionAvail().x;
ImGui::SetNextItemWidth(avail);
if (ImGui::InputInt("##Octave",&curOctave,0,0)) {
if (curOctave>7) curOctave=7;
if (curOctave<-5) curOctave=-5;
if (curOctave>GUI_EDIT_OCTAVE_MAX) curOctave=GUI_EDIT_OCTAVE_MAX;
if (curOctave<GUI_EDIT_OCTAVE_MIN) curOctave=GUI_EDIT_OCTAVE_MIN;
e->autoNoteOffAll();
failedNoteOn=false;
@ -1093,8 +1101,8 @@ void FurnaceGUI::drawEditControls() {
float avail=ImGui::GetContentRegionAvail().x;
ImGui::SetNextItemWidth(avail);
if (ImGui::InputInt("##Octave",&curOctave,1,1)) {
if (curOctave>7) curOctave=7;
if (curOctave<-5) curOctave=-5;
if (curOctave>GUI_EDIT_OCTAVE_MAX) curOctave=GUI_EDIT_OCTAVE_MAX;
if (curOctave<GUI_EDIT_OCTAVE_MIN) curOctave=GUI_EDIT_OCTAVE_MIN;
e->autoNoteOffAll();
failedNoteOn=false;

View file

@ -678,6 +678,7 @@ void FurnaceGUI::doPasteFurnace(PasteMode mode, int arg, bool readClipboard, Str
if (readClipboard) {
if (settings.cursorPastePos) {
makeCursorUndo();
cursor.y=j;
if (cursor.y>=e->curSubSong->patLen) cursor.y=e->curSubSong->patLen-1;
selStart=cursor;
@ -1220,6 +1221,7 @@ void FurnaceGUI::doPasteMPT(PasteMode mode, int arg, bool readClipboard, String
if (readClipboard) {
if (settings.cursorPastePos) {
makeCursorUndo();
cursor.y=j;
if (cursor.y>=e->curSubSong->patLen) cursor.y=e->curSubSong->patLen-1;
selStart=cursor;
@ -1823,6 +1825,55 @@ void FurnaceGUI::doExpandSong(int multiplier) {
if (e->isPlaying()) e->play();
}
void FurnaceGUI::doAbsorbInstrument() {
bool foundIns=false;
bool foundOctave=false;
auto foundAll = [&]() { return foundIns && foundOctave; };
// search this order and all prior until we find all the data we need
int orderIdx=curOrder;
for (; orderIdx>=0 && !foundAll(); orderIdx--) {
DivPattern* pat=e->curPat[cursor.xCoarse].getPattern(e->curOrders->ord[cursor.xCoarse][orderIdx],false);
if (!pat) continue;
// start on current row when searching current order, but start from end when searching
// prior orders.
int searchStartRow=orderIdx==curOrder ? cursor.y : e->curSubSong->patLen-1;
for (int i=searchStartRow; i>=0 && !foundAll(); i--) {
// absorb most recent instrument
if (!foundIns && pat->data[i][2] >= 0) {
foundIns=true;
curIns=pat->data[i][2];
}
// absorb most recent octave (i.e. set curOctave such that the "main row" (QWERTY) of
// notes will result in an octave number equal to the previous note). make sure to
// skip "special note values" like OFF/REL/=== and "none", since there won't be valid
// octave values
unsigned char note=pat->data[i][0];
if (!foundOctave && note!=0 && note!=100 && note!=101 && note!=102) {
foundOctave=true;
// decode octave data (was signed cast to unsigned char)
int octave=pat->data[i][1];
if (octave>128) octave-=256;
// @NOTE the special handling when note==12, which is really an octave above what's
// stored in the octave data. without this handling, if you press Q, then
// "ABSORB_INSTRUMENT", then Q again, you'd get a different octave!
if (pat->data[i][0]==12) octave++;
curOctave=CLAMP(octave-1, GUI_EDIT_OCTAVE_MIN, GUI_EDIT_OCTAVE_MAX);
}
}
}
// if no instrument has been set at this point, the only way to match it is to use "none"
if (!foundIns) curIns=-1;
logD("doAbsorbInstrument -- searched %d orders", curOrder-orderIdx);
}
void FurnaceGUI::doDrag() {
int len=dragEnd.xCoarse-dragStart.xCoarse+1;
@ -2012,3 +2063,52 @@ void FurnaceGUI::doRedo() {
redoHist.pop_back();
}
CursorJumpPoint FurnaceGUI::getCurrentCursorJumpPoint() {
return CursorJumpPoint(cursor, curOrder, e->getCurrentSubSong());
}
void FurnaceGUI::applyCursorJumpPoint(const CursorJumpPoint& spot) {
cursor=spot.point;
curOrder=MIN(e->curSubSong->ordersLen-1, spot.order);
e->setOrder(curOrder);
e->changeSongP(spot.subSong);
if (!settings.cursorMoveNoScroll) {
updateScroll(cursor.y);
}
}
void FurnaceGUI::makeCursorUndo() {
CursorJumpPoint spot = getCurrentCursorJumpPoint();
if (!cursorUndoHist.empty() && spot == cursorUndoHist.back()) return;
if (cursorUndoHist.size()>=settings.maxUndoSteps) cursorUndoHist.pop_front();
cursorUndoHist.push_back(spot);
// redo history no longer relevant, we've changed timeline
cursorRedoHist.clear();
}
void FurnaceGUI::doCursorUndo() {
if (cursorUndoHist.empty()) return;
// allow returning to current spot
if (cursorRedoHist.size()>=settings.maxUndoSteps) cursorRedoHist.pop_front();
cursorRedoHist.push_back(getCurrentCursorJumpPoint());
// apply spot
applyCursorJumpPoint(cursorUndoHist.back());
cursorUndoHist.pop_back();
}
void FurnaceGUI::doCursorRedo() {
if (cursorRedoHist.empty()) return;
// allow returning to current spot
if (cursorUndoHist.size()>=settings.maxUndoSteps) cursorUndoHist.pop_front();
cursorUndoHist.push_back(getCurrentCursorJumpPoint());
// apply spot
applyCursorJumpPoint(cursorRedoHist.back());
cursorRedoHist.pop_back();
}

View file

@ -319,6 +319,29 @@ void FurnaceGUI::drawExportROM(bool onWindow) {
}
break;
}
case DIV_ROM_ZSM: {
int zsmExportTickRate=romConfig.getInt("zsmrate",60);
bool zsmExportLoop=romConfig.getBool("loop",true);
bool zsmExportOptimize=romConfig.getBool("optimize",true);
if (ImGui::InputInt(_("Tick Rate (Hz)"),&zsmExportTickRate,1,2)) {
if (zsmExportTickRate<1) zsmExportTickRate=1;
if (zsmExportTickRate>44100) zsmExportTickRate=44100;
altered=true;
}
if (ImGui::Checkbox(_("loop"),&zsmExportLoop)) {
altered=true;
}
if (ImGui::Checkbox(_("optimize size"),&zsmExportOptimize)) {
altered=true;
}
if (altered) {
romConfig.set("zsmrate",zsmExportTickRate);
romConfig.set("loop",zsmExportLoop);
romConfig.set("optimize",zsmExportOptimize);
}
break;
}
case DIV_ROM_ABSTRACT:
ImGui::TextWrapped("%s",_("select a target from the menu at the top of this dialog."));
break;
@ -340,28 +363,6 @@ void FurnaceGUI::drawExportROM(bool onWindow) {
}
}
void FurnaceGUI::drawExportZSM(bool onWindow) {
exitDisabledTimer=1;
ImGui::Text(_("Commander X16 Zsound Music File"));
if (ImGui::InputInt(_("Tick Rate (Hz)"),&zsmExportTickRate,1,2)) {
if (zsmExportTickRate<1) zsmExportTickRate=1;
if (zsmExportTickRate>44100) zsmExportTickRate=44100;
}
ImGui::Checkbox(_("loop"),&zsmExportLoop);
ImGui::SameLine();
ImGui::Checkbox(_("optimize size"),&zsmExportOptimize);
if (onWindow) {
ImGui::Separator();
if (ImGui::Button(_("Cancel"),ImVec2(200.0f*dpiScale,0))) ImGui::CloseCurrentPopup();
ImGui::SameLine();
}
if (ImGui::Button(_("Export"),ImVec2(200.0f*dpiScale,0))) {
openFileDialog(GUI_FILE_EXPORT_ZSM);
ImGui::CloseCurrentPopup();
}
}
void FurnaceGUI::drawExportText(bool onWindow) {
exitDisabledTimer=1;
@ -444,16 +445,6 @@ void FurnaceGUI::drawExport() {
ImGui::EndTabItem();
}
}
int numZSMCompat=0;
for (int i=0; i<e->song.systemLen; i++) {
if ((e->song.system[i]==DIV_SYSTEM_VERA) || (e->song.system[i]==DIV_SYSTEM_YM2151)) numZSMCompat++;
}
if (numZSMCompat>0) {
if (ImGui::BeginTabItem(_("ZSM"))) {
drawExportZSM(true);
ImGui::EndTabItem();
}
}
if (ImGui::BeginTabItem(_("Text"))) {
drawExportText(true);
ImGui::EndTabItem();
@ -478,9 +469,6 @@ void FurnaceGUI::drawExport() {
case GUI_EXPORT_ROM:
drawExportROM(true);
break;
case GUI_EXPORT_ZSM:
drawExportZSM(true);
break;
case GUI_EXPORT_TEXT:
drawExportText(true);
break;

View file

@ -18,13 +18,15 @@ struct NFDState {
String header;
std::vector<String> filter;
String path;
String defFileName;
FileDialogSelectCallback clickCallback;
NFDState(unsigned char save, String h, std::vector<String> filt, String pa, FileDialogSelectCallback cc, bool multi):
NFDState(unsigned char save, String h, std::vector<String> filt, String pa, FileDialogSelectCallback cc, bool multi, String defFN):
isSave(save),
allowMultiple(multi),
header(h),
filter(filt),
path(pa),
defFileName(defFN),
clickCallback(cc) {
}
};
@ -40,12 +42,12 @@ void _nfdThread(const NFDState state, std::atomic<bool>* ok, std::vector<String>
if (state.isSave==2) {
ret=NFD_PickFolder(state.path.c_str(),&out);
} else if (state.isSave==1) {
ret=NFD_SaveDialog(state.filter,state.path.c_str(),&out,state.clickCallback);
ret=NFD_SaveDialog(state.filter,state.path.c_str(),&out,state.clickCallback,state.defFileName.empty()?NULL:state.defFileName.c_str());
} else {
if (state.allowMultiple) {
ret=NFD_OpenDialogMultiple(state.filter,state.path.c_str(),&paths,state.clickCallback);
ret=NFD_OpenDialogMultiple(state.filter,state.path.c_str(),&paths,state.clickCallback,state.defFileName.empty()?NULL:state.defFileName.c_str());
} else {
ret=NFD_OpenDialog(state.filter,state.path.c_str(),&out,state.clickCallback);
ret=NFD_OpenDialog(state.filter,state.path.c_str(),&out,state.clickCallback,state.defFileName.empty()?NULL:state.defFileName.c_str());
}
}
@ -131,9 +133,9 @@ bool FurnaceGUIFileDialog::openLoad(String header, std::vector<String> filter, S
#ifdef USE_NFD
dialogOK=false;
#ifdef NFD_NON_THREADED
_nfdThread(NFDState(0,header,filter,path,clickCallback,allowMultiple),&dialogOK,&nfdResult,&hasError);
_nfdThread(NFDState(0,header,filter,path,clickCallback,allowMultiple,hint),&dialogOK,&nfdResult,&hasError);
#else
dialogO=new std::thread(_nfdThread,NFDState(0,header,filter,path,clickCallback,allowMultiple),&dialogOK,&nfdResult,&hasError);
dialogO=new std::thread(_nfdThread,NFDState(0,header,filter,path,clickCallback,allowMultiple,hint),&dialogOK,&nfdResult,&hasError);
#endif
#elif defined(ANDROID)
hasError=false;
@ -223,9 +225,9 @@ bool FurnaceGUIFileDialog::openSave(String header, std::vector<String> filter, S
#ifdef USE_NFD
dialogOK=false;
#ifdef NFD_NON_THREADED
_nfdThread(NFDState(1,header,filter,path,NULL,false),&dialogOK,&nfdResult,&hasError);
_nfdThread(NFDState(1,header,filter,path,NULL,false,hint),&dialogOK,&nfdResult,&hasError);
#else
dialogS=new std::thread(_nfdThread,NFDState(1,header,filter,path,NULL,false),&dialogOK,&nfdResult,&hasError);
dialogS=new std::thread(_nfdThread,NFDState(1,header,filter,path,NULL,false,hint),&dialogOK,&nfdResult,&hasError);
#endif
#elif defined(ANDROID)
hasError=false;
@ -303,9 +305,9 @@ bool FurnaceGUIFileDialog::openSelectDir(String header, String path, double dpiS
#ifdef USE_NFD
dialogOK=false;
#ifdef NFD_NON_THREADED
_nfdThread(NFDState(2,header,std::vector<String>(),path,NULL,false),&dialogOK,&nfdResult,&hasError);
_nfdThread(NFDState(2,header,std::vector<String>(),path,NULL,false,""),&dialogOK,&nfdResult,&hasError);
#else
dialogF=new std::thread(_nfdThread,NFDState(2,header,std::vector<String>(),path,NULL,false),&dialogOK,&nfdResult,&hasError);
dialogF=new std::thread(_nfdThread,NFDState(2,header,std::vector<String>(),path,NULL,false,""),&dialogOK,&nfdResult,&hasError);
#endif
#elif defined(ANDROID)
hasError=true;
@ -455,7 +457,7 @@ bool FurnaceGUIFileDialog::render(const ImVec2& min, const ImVec2& max) {
return false;
#endif
} else {
return ImGuiFileDialog::Instance()->Display("FileDialog",ImGuiWindowFlags_NoCollapse|ImGuiWindowFlags_NoMove,min,max);
return ImGuiFileDialog::Instance()->Display("FileDialog",ImGuiWindowFlags_NoCollapse|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoScrollWithMouse,min,max);
}
}

View file

@ -560,6 +560,7 @@ void FurnaceGUI::drawFindReplace() {
if (ImGui::TableNextColumn()) {
snprintf(tempID,1024,ICON_FA_CHEVRON_RIGHT "##_FR%d",index);
if (ImGui::Selectable(tempID)) {
makeCursorUndo();
e->changeSongP(i.subsong);
if (e->isPlaying()) {
followPattern=false;

File diff suppressed because it is too large Load diff

View file

@ -388,6 +388,7 @@ void FurnaceGUI::decodeMMLStr(String& source, int* macro, unsigned char& macroLe
setBit30=false;
macroLen++;
buf=0;
MARK_MODIFIED;
}
}
@ -424,6 +425,21 @@ void FurnaceGUI::decodeMMLStr(String& source, int* macro, unsigned char& macroLe
} \
}
bool FurnaceGUI::isCtrlWheelModifierHeld() const {
switch (settings.ctrlWheelModifier) {
case 0:
return ImGui::IsKeyDown(ImGuiMod_Ctrl) || ImGui::IsKeyDown(ImGuiMod_Super);
case 1:
return ImGui::IsKeyDown(ImGuiMod_Ctrl);
case 2:
return ImGui::IsKeyDown(ImGuiMod_Super);
case 3:
return ImGui::IsKeyDown(ImGuiMod_Alt);
default:
return false;
}
}
bool FurnaceGUI::CWSliderScalar(const char* label, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags) {
flags^=ImGuiSliderFlags_AlwaysClamp;
if (ImGui::SliderScalar(label,data_type,p_data,p_min,p_max,format,flags)) {
@ -993,11 +1009,6 @@ Pos=339,177\n\
Size=601,400\n\
Collapsed=0\n\
\n\
[Window][Rendering...]\n\
Pos=585,342\n\
Size=114,71\n\
Collapsed=0\n\
\n\
[Window][Export VGM##FileDialog]\n\
Pos=340,177\n\
Size=600,400\n\
@ -1216,6 +1227,7 @@ void FurnaceGUI::play(int row) {
memset(chanOscBright,0,DIV_MAX_CHANS*sizeof(float));
e->walkSong(loopOrder,loopRow,loopEnd);
memset(lastIns,-1,sizeof(int)*DIV_MAX_CHANS);
if (followPattern) makeCursorUndo();
if (!followPattern) e->setOrder(curOrder);
if (row>0) {
if (!e->playToRow(row)) {
@ -1486,13 +1498,24 @@ void FurnaceGUI::keyDown(SDL_Event& ev) {
case SDLK_LGUI: case SDLK_RGUI:
case SDLK_LSHIFT: case SDLK_RSHIFT:
bindSetPending=false;
actionKeys[bindSetTarget]=(mapped&(~FURK_MASK))|0xffffff;
actionKeys[bindSetTarget][bindSetTargetIdx]=(mapped&(~FURK_MASK))|0xffffff;
break;
default:
actionKeys[bindSetTarget]=mapped;
actionKeys[bindSetTarget][bindSetTargetIdx]=mapped;
// de-dupe with an n^2 algorithm that will never ever be a problem (...but for real though)
for (size_t i=0; i<actionKeys[bindSetTarget].size(); i++) {
for (size_t j=i+1; j<actionKeys[bindSetTarget].size(); j++) {
if (actionKeys[bindSetTarget][i]==actionKeys[bindSetTarget][j]) {
actionKeys[bindSetTarget].erase(actionKeys[bindSetTarget].begin()+j);
}
}
}
bindSetActive=false;
bindSetPending=false;
bindSetTarget=0;
bindSetTargetIdx=0;
bindSetPrevValue=0;
parseKeybinds();
break;
@ -2022,16 +2045,6 @@ void FurnaceGUI::openFileDialog(FurnaceGUIFileDialogs type) {
(settings.autoFillSave)?shortName:""
);
break;
case GUI_FILE_EXPORT_ZSM:
if (!dirExists(workingDirZSMExport)) workingDirZSMExport=getHomeDir();
hasOpened=fileDialog->openSave(
_("Export ZSM"),
{_("ZSM file"), "*.zsm"},
workingDirZSMExport,
dpiScale,
(settings.autoFillSave)?shortName:""
);
break;
case GUI_FILE_EXPORT_TEXT:
if (!dirExists(workingDirROMExport)) workingDirROMExport=getHomeDir();
hasOpened=fileDialog->openSave(
@ -2458,6 +2471,20 @@ int FurnaceGUI::load(String path) {
// warn the user
showWarning(_("you have loaded a backup!\nif you need to, please save it somewhere.\n\nDO NOT RELY ON THE BACKUP SYSTEM FOR AUTO-SAVE!\nFurnace will not save backups of backups."),GUI_WARN_GENERIC);
}
// if this is a PC module import, warn the user on the first import.
if (!tutorial.importedMOD && e->song.version==DIV_VERSION_MOD) {
showWarning(_("you have imported a ProTracker/SoundTracker/PC module!\nkeep the following in mind:\n\n- Furnace is not a replacement for your MOD player\n- import is not perfect. your song may sound different:\n - E6x pattern loop is not supported\n\nhave fun!"),GUI_WARN_IMPORT);
}
if (!tutorial.importedS3M && e->song.version==DIV_VERSION_S3M) {
showWarning(_("you have imported a Scream Tracker 3 module!\nkeep the following in mind:\n\n- Furnace is not a replacement for your S3M player\n- import is not perfect. your song may sound different:\n - OPL instruments may be detuned\n\nhave fun!"),GUI_WARN_IMPORT);
}
if (!tutorial.importedXM && e->song.version==DIV_VERSION_XM) {
showWarning(_("you have imported a FastTracker II module!\nkeep the following in mind:\n\n- Furnace is not a replacement for your XM player\n- import is not perfect. your song may sound different:\n - envelopes have been converted to macros\n - global volume changes are not supported\n\nhave fun!"),GUI_WARN_IMPORT);
}
if (!tutorial.importedIT && e->song.version==DIV_VERSION_IT) {
showWarning(_("you have imported an Impulse Tracker module!\nkeep the following in mind:\n\n- Furnace is not a replacement for your IT player\n- import is not perfect. your song may sound different:\n - envelopes have been converted to macros\n - global volume changes are not supported\n - channel volume changes are not supported\n - New Note Actions (NNA) are not supported\n\nhave fun!"),GUI_WARN_IMPORT);
}
return 0;
}
@ -2592,7 +2619,40 @@ int FurnaceGUI::loadStream(String path) {
void FurnaceGUI::exportAudio(String path, DivAudioExportModes mode) {
songOrdersLengths.clear();
int loopOrder=0;
int loopRow=0;
int loopEnd=0;
e->walkSong(loopOrder,loopRow,loopEnd);
e->findSongLength(loopOrder,loopRow,audioExportOptions.fadeOut,songFadeoutSectionLength,songHasSongEndCommand,songOrdersLengths,songLength); // for progress estimation
songLoopedSectionLength=songLength;
for (int i=0; i<loopOrder; i++) {
songLoopedSectionLength-=songOrdersLengths[i];
}
songLoopedSectionLength-=loopRow;
e->saveAudio(path.c_str(),audioExportOptions);
totalFiles=0;
e->getTotalAudioFiles(totalFiles);
int totalLoops=0;
lengthOfOneFile=songLength;
if (!songHasSongEndCommand) {
e->getTotalLoops(totalLoops);
lengthOfOneFile+=songLoopedSectionLength*totalLoops;
lengthOfOneFile+=songFadeoutSectionLength; // account for fadeout
}
totalLength=lengthOfOneFile*totalFiles;
curProgress=0.0f;
displayExporting=true;
}
@ -2795,24 +2855,6 @@ void FurnaceGUI::processDrags(int dragX, int dragY) {
fileName+=x; \
}
#define checkExtensionDual(x,y,fallback) \
String lowerCase=fileName; \
for (char& i: lowerCase) { \
if (i>='A' && i<='Z') i+='a'-'A'; \
} \
if (lowerCase.size()<4 || (lowerCase.rfind(x)!=lowerCase.size()-4 && lowerCase.rfind(y)!=lowerCase.size()-4)) { \
fileName+=fallback; \
}
#define checkExtensionTriple(x,y,z,fallback) \
String lowerCase=fileName; \
for (char& i: lowerCase) { \
if (i>='A' && i<='Z') i+='a'-'A'; \
} \
if (lowerCase.size()<4 || (lowerCase.rfind(x)!=lowerCase.size()-4 && lowerCase.rfind(y)!=lowerCase.size()-4 && lowerCase.rfind(z)!=lowerCase.size()-4)) { \
fileName+=fallback; \
}
#define drawOpMask(m) \
ImGui::PushFont(patFont); \
ImGui::PushID("om_" #m); \
@ -3493,8 +3535,12 @@ void FurnaceGUI::pointDown(int x, int y, int button) {
if (bindSetActive) {
bindSetActive=false;
bindSetPending=false;
actionKeys[bindSetTarget]=bindSetPrevValue;
actionKeys[bindSetTarget][bindSetTargetIdx]=bindSetPrevValue;
if (bindSetTargetIdx==(int)actionKeys[bindSetTarget].size()-1 && bindSetPrevValue<=0) {
actionKeys[bindSetTarget].pop_back();
}
bindSetTarget=0;
bindSetTargetIdx=0;
bindSetPrevValue=0;
}
if (introPos<11.0 && !shortIntro) {
@ -3714,6 +3760,7 @@ bool FurnaceGUI::loop() {
ImGui::GetIO().AddKeyEvent(ImGuiKey_Backspace,false);
injectBackUp=false;
}
while (SDL_PollEvent(&ev)) {
WAKE_UP;
ImGui_ImplSDL2_ProcessEvent(&ev);
@ -3730,13 +3777,16 @@ bool FurnaceGUI::loop() {
}
case SDL_MOUSEBUTTONUP:
pointUp(ev.button.x,ev.button.y,ev.button.button);
insEditMayBeDirty=true;
break;
case SDL_MOUSEBUTTONDOWN:
pointDown(ev.button.x,ev.button.y,ev.button.button);
insEditMayBeDirty=true;
break;
case SDL_MOUSEWHEEL:
wheelX+=ev.wheel.x;
wheelY+=ev.wheel.y;
insEditMayBeDirty=true;
break;
case SDL_WINDOWEVENT:
switch (ev.window.event) {
@ -3813,12 +3863,14 @@ bool FurnaceGUI::loop() {
if (!ImGui::GetIO().WantCaptureKeyboard) {
keyDown(ev);
}
insEditMayBeDirty=true;
#ifdef IS_MOBILE
injectBackUp=true;
#endif
break;
case SDL_KEYUP:
// for now
insEditMayBeDirty=true;
break;
case SDL_DROPFILE:
if (ev.drop.file!=NULL) {
@ -4405,16 +4457,6 @@ bool FurnaceGUI::loop() {
ImGui::EndMenu();
}
}
int numZSMCompat=0;
for (int i=0; i<e->song.systemLen; i++) {
if ((e->song.system[i]==DIV_SYSTEM_VERA) || (e->song.system[i]==DIV_SYSTEM_YM2151)) numZSMCompat++;
}
if (numZSMCompat>0) {
if (ImGui::BeginMenu(_("export ZSM..."))) {
drawExportZSM();
ImGui::EndMenu();
}
}
if (ImGui::BeginMenu(_("export text..."))) {
drawExportText();
ImGui::EndMenu();
@ -4442,16 +4484,6 @@ bool FurnaceGUI::loop() {
displayExport=true;
}
}
int numZSMCompat=0;
for (int i=0; i<e->song.systemLen; i++) {
if ((e->song.system[i]==DIV_SYSTEM_VERA) || (e->song.system[i]==DIV_SYSTEM_YM2151)) numZSMCompat++;
}
if (numZSMCompat>0) {
if (ImGui::MenuItem(_("export ZSM..."))) {
curExportType=GUI_EXPORT_ZSM;
displayExport=true;
}
}
if (ImGui::MenuItem(_("export text..."))) {
curExportType=GUI_EXPORT_TEXT;
displayExport=true;
@ -4477,7 +4509,7 @@ bool FurnaceGUI::loop() {
} else {
if (ImGui::BeginMenu(_("add chip..."))) {
exitDisabledTimer=1;
DivSystem picked=systemPicker();
DivSystem picked=systemPicker(false);
if (picked!=DIV_SYSTEM_NULL) {
if (!e->addSystem(picked)) {
showError(fmt::sprintf(_("cannot add chip! (%s)"),e->getLastError()));
@ -4508,7 +4540,7 @@ bool FurnaceGUI::loop() {
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())) {
DivSystem picked=systemPicker();
DivSystem picked=systemPicker(false);
if (picked!=DIV_SYSTEM_NULL) {
if (e->changeSystem(i,picked,preserveChanPos)) {
MARK_MODIFIED;
@ -4673,6 +4705,7 @@ bool FurnaceGUI::loop() {
if (ImGui::MenuItem(_("debug menu"),BIND_FOR(GUI_ACTION_WINDOW_DEBUG))) debugOpen=!debugOpen;
if (ImGui::MenuItem(_("inspector"))) inspectorOpen=!inspectorOpen;
if (ImGui::MenuItem(_("panic"),BIND_FOR(GUI_ACTION_PANIC))) e->syncReset();
if (ImGui::MenuItem(_("welcome screen"))) tutorial.protoWelcome=false;
if (ImGui::MenuItem(_("about..."),BIND_FOR(GUI_ACTION_WINDOW_ABOUT))) {
aboutOpen=true;
aboutScroll=0;
@ -4778,7 +4811,7 @@ bool FurnaceGUI::loop() {
info=fmt::sprintf(_("Set volume: %d (%.2X, INVALID!)"),p->data[cursor.y][3],p->data[cursor.y][3]);
} else {
float realVol=e->getGain(cursor.xCoarse,p->data[cursor.y][3]);
info=fmt::sprintf(_("Set volume: %d (%.2X, %d%%)"),p->data[cursor.y][3],p->data[cursor.y][3],(int)(realVol*100.0f/(float)maxVol));
info=fmt::sprintf(_("Set volume: %d (%.2X, %d%%)"),p->data[cursor.y][3],p->data[cursor.y][3],(int)(realVol*100.0f));
}
hasInfo=true;
}
@ -5034,9 +5067,6 @@ bool FurnaceGUI::loop() {
case GUI_FILE_EXPORT_VGM:
workingDirVGMExport=fileDialog->getPath()+DIR_SEPARATOR_STR;
break;
case GUI_FILE_EXPORT_ZSM:
workingDirZSMExport=fileDialog->getPath()+DIR_SEPARATOR_STR;
break;
case GUI_FILE_EXPORT_ROM:
case GUI_FILE_EXPORT_TEXT:
case GUI_FILE_EXPORT_CMDSTREAM:
@ -5099,7 +5129,13 @@ bool FurnaceGUI::loop() {
} else {
fileName=fileDialog->getFileName()[0];
}
#ifdef FLATPAK_WORKAROUNDS
// https://github.com/tildearrow/furnace/issues/2096
// Flatpak Portals mangling our path hinders us from adding extension
if (fileName!="" && !settings.sysFileDialog) {
#else
if (fileName!="") {
#endif
if (curFileDialog==GUI_FILE_SAVE) {
checkExtension(".fur");
}
@ -5136,9 +5172,6 @@ bool FurnaceGUI::loop() {
if (curFileDialog==GUI_FILE_EXPORT_ROM) {
checkExtension(romFilterExt.c_str());
}
if (curFileDialog==GUI_FILE_EXPORT_ZSM) {
checkExtension(".zsm");
}
if (curFileDialog==GUI_FILE_EXPORT_TEXT) {
checkExtension(".txt");
}
@ -5614,27 +5647,6 @@ bool FurnaceGUI::loop() {
}
break;
}
case GUI_FILE_EXPORT_ZSM: {
SafeWriter* w=e->saveZSM(zsmExportTickRate,zsmExportLoop,zsmExportOptimize);
if (w!=NULL) {
FILE* f=ps_fopen(copyOfName.c_str(),"wb");
if (f!=NULL) {
fwrite(w->getFinalBuf(),1,w->size(),f);
fclose(f);
pushRecentSys(copyOfName.c_str());
} else {
showError(_("could not open file!"));
}
w->finish();
delete w;
if (!e->getWarnings().empty()) {
showWarning(e->getWarnings(),GUI_WARN_GENERIC);
}
} else {
showError(fmt::sprintf(_("Could not write ZSM! (%s)"),e->getLastError()));
}
break;
}
case GUI_FILE_EXPORT_ROM:
romExportPath=copyOfName;
pendingExport=e->buildROM(romTarget);
@ -5877,8 +5889,52 @@ bool FurnaceGUI::loop() {
MEASURE_BEGIN(popup);
centerNextWindow(_("Rendering..."),canvasW,canvasH);
if (ImGui::BeginPopupModal(_("Rendering..."),NULL,ImGuiWindowFlags_AlwaysAutoResize)) {
ImGui::Text(_("Please wait..."));
// ImGui::SetNextWindowSize(ImVec2(0.0f,0.0f));
if (ImGui::BeginPopupModal(_("Rendering..."),NULL,ImGuiWindowFlags_NoResize|ImGuiWindowFlags_NoMove|ImGuiWindowFlags_NoSavedSettings)) {
// WHAT the HELL?!
WAKE_UP;
if (audioExportOptions.mode!=DIV_EXPORT_MODE_MANY_CHAN) {
ImGui::Text(_("Please wait..."));
}
float* progressLambda=&curProgress;
int curPosInRows=0;
int* curPosInRowsLambda=&curPosInRows;
int loopsLeft=0;
int* loopsLeftLambda=&loopsLeft;
int totalLoops=0;
int* totalLoopsLambda=&totalLoops;
int curFile=0;
int* curFileLambda=&curFile;
if (e->isExporting()) {
e->lockEngine(
[this, progressLambda, curPosInRowsLambda, curFileLambda, loopsLeftLambda, totalLoopsLambda] () {
int curRow=0; int curOrder=0;
e->getCurSongPos(curRow, curOrder);
*curFileLambda=0;
e->getCurFileIndex(*curFileLambda);
*curPosInRowsLambda=curRow;
for (int i=0; i<curOrder; i++) *curPosInRowsLambda+=songOrdersLengths[i];
if (!songHasSongEndCommand) {
e->getLoopsLeft(*loopsLeftLambda);
e->getTotalLoops(*totalLoopsLambda);
if ((*totalLoopsLambda)!=(*loopsLeftLambda)) { // we are going 2nd, 3rd, etc. time through the song
*curPosInRowsLambda-=(songLength-songLoopedSectionLength); // a hack so progress bar does not jump?
}
if (e->getIsFadingOut()) { // we are in fadeout??? why it works like that bruh
// LIVE WITH IT damn it
*curPosInRowsLambda-=(songLength-songLoopedSectionLength); // a hack so progress bar does not jump?
}
}
*progressLambda=(float)((*curPosInRowsLambda)+((*totalLoopsLambda)-(*loopsLeftLambda))*songLength+lengthOfOneFile*(*curFileLambda))/(float)totalLength;
}
);
}
ImGui::Text(_("Row %d of %d"),curPosInRows+((totalLoops)-(loopsLeft))*songLength,lengthOfOneFile);
if (audioExportOptions.mode==DIV_EXPORT_MODE_MANY_CHAN) ImGui::Text(_("Channel %d of %d"),curFile+1,totalFiles);
ImGui::ProgressBar(curProgress,ImVec2(320.0f*dpiScale,0),fmt::sprintf("%.2f%%",curProgress*100.0f).c_str());
if (ImGui::Button(_("Abort"))) {
if (e->haltAudioFile()) {
ImGui::CloseCurrentPopup();
@ -6426,6 +6482,26 @@ bool FurnaceGUI::loop() {
ImGui::CloseCurrentPopup();
}
break;
case GUI_WARN_IMPORT:
if (ImGui::Button(_("Got it"))) {
switch (e->song.version) {
case DIV_VERSION_MOD:
tutorial.importedMOD=true;
break;
case DIV_VERSION_S3M:
tutorial.importedS3M=true;
break;
case DIV_VERSION_XM:
tutorial.importedXM=true;
break;
case DIV_VERSION_IT:
tutorial.importedIT=true;
break;
}
commitTutorial();
ImGui::CloseCurrentPopup();
}
break;
case GUI_WARN_GENERIC:
if (ImGui::Button(_("OK"))) {
ImGui::CloseCurrentPopup();
@ -7202,6 +7278,11 @@ bool FurnaceGUI::loop() {
willCommit=false;
}
// To check for instrument editor modification, we need an up-to-date `insEditMayBeDirty`
// (based on incoming user input events), and we want any possible instrument modifications
// to already have been made.
checkRecordInstrumentUndoStep();
if (shallDetectScale) {
if (--shallDetectScale<1) {
if (settings.dpiScale<0.5f) {
@ -7879,7 +7960,6 @@ void FurnaceGUI::syncState() {
workingDirSample=e->getConfString("lastDirSample",workingDir);
workingDirAudioExport=e->getConfString("lastDirAudioExport",workingDir);
workingDirVGMExport=e->getConfString("lastDirVGMExport",workingDir);
workingDirZSMExport=e->getConfString("lastDirZSMExport",workingDir);
workingDirROMExport=e->getConfString("lastDirROMExport",workingDir);
workingDirFont=e->getConfString("lastDirFont",workingDir);
workingDirColors=e->getConfString("lastDirColors",workingDir);
@ -8038,7 +8118,6 @@ void FurnaceGUI::commitState(DivConfig& conf) {
conf.set("lastDirSample",workingDirSample);
conf.set("lastDirAudioExport",workingDirAudioExport);
conf.set("lastDirVGMExport",workingDirVGMExport);
conf.set("lastDirZSMExport",workingDirZSMExport);
conf.set("lastDirROMExport",workingDirROMExport);
conf.set("lastDirFont",workingDirFont);
conf.set("lastDirColors",workingDirColors);
@ -8256,8 +8335,6 @@ FurnaceGUI::FurnaceGUI():
displayError(false),
displayExporting(false),
vgmExportLoop(true),
zsmExportLoop(true),
zsmExportOptimize(true),
vgmExportPatternHints(false),
vgmExportDirectStream(false),
displayInsTypeList(false),
@ -8300,7 +8377,6 @@ FurnaceGUI::FurnaceGUI():
vgmExportVersion(0x171),
vgmExportTrailingTicks(-1),
drawHalt(10),
zsmExportTickRate(60),
macroPointSize(16),
waveEditStyle(0),
displayInsTypeListMakeInsSample(-1),
@ -8367,11 +8443,21 @@ FurnaceGUI::FurnaceGUI():
bigFont(NULL),
headFont(NULL),
fontRange(NULL),
songLength(0),
songLoopedSectionLength(0),
songFadeoutSectionLength(0),
songHasSongEndCommand(false),
lengthOfOneFile(0),
totalLength(0),
curProgress(0.0f),
totalFiles(0),
localeRequiresJapanese(false),
localeRequiresChinese(false),
localeRequiresChineseTrad(false),
localeRequiresKorean(false),
prevInsData(NULL),
cachedCurInsPtr(NULL),
insEditMayBeDirty(false),
pendingLayoutImport(NULL),
pendingLayoutImportLen(0),
pendingLayoutImportStep(0),
@ -8584,6 +8670,7 @@ FurnaceGUI::FurnaceGUI():
waveDragMax(0),
waveDragActive(false),
bindSetTarget(0),
bindSetTargetIdx(0),
bindSetPrevValue(0),
bindSetActive(false),
bindSetPending(false),
@ -8811,8 +8898,6 @@ FurnaceGUI::FurnaceGUI():
opMaskTransposeValue.effect=false;
opMaskTransposeValue.effectVal=true;
memset(actionKeys,0,GUI_ACTION_MAX*sizeof(int));
memset(patChanX,0,sizeof(float)*(DIV_MAX_CHANS+1));
memset(patChanSlideY,0,sizeof(float)*(DIV_MAX_CHANS+1));
memset(lastIns,-1,sizeof(int)*DIV_MAX_CHANS);
@ -8887,6 +8972,8 @@ FurnaceGUI::FurnaceGUI():
memset(romExportAvail,0,sizeof(bool)*DIV_ROM_MAX);
songOrdersLengths.clear();
strncpy(noteOffLabel,"OFF",32);
strncpy(noteRelLabel,"===",32);
strncpy(macroRelLabel,"REL",32);

View file

@ -39,7 +39,7 @@
#define FURNACE_APP_ID "org.tildearrow.furnace"
#define rightClickable if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) ImGui::SetKeyboardFocusHere(-1);
#define ctrlWheeling ((ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl)) && wheelY!=0)
#define ctrlWheeling (isCtrlWheelModifierHeld() && wheelY!=0)
#define handleUnimportant if (settings.insFocusesPattern && patternOpen) {nextWindow=GUI_WINDOW_PATTERN;}
#define unimportant(x) if (x) {handleUnimportant}
@ -66,10 +66,13 @@
logI("beep!"); \
}
#define BIND_FOR(x) getKeyName(actionKeys[x],true).c_str()
#define BIND_FOR(x) getMultiKeysName(actionKeys[x].data(),actionKeys[x].size(),true).c_str()
#define FM_PREVIEW_SIZE 512
#define CHECK_HIDDEN_SYSTEM(x) \
(x==DIV_SYSTEM_YMU759 || x==DIV_SYSTEM_UPD1771C || x==DIV_SYSTEM_DUMMY || x==DIV_SYSTEM_SEGAPCM_COMPAT || x==DIV_SYSTEM_PONG)
enum FurnaceGUIRenderBackend {
GUI_BACKEND_SDL=0,
GUI_BACKEND_GL3,
@ -137,6 +140,9 @@ enum FurnaceGUIRenderBackend {
#define ngettext momo_ngettext
#endif
#define GUI_EDIT_OCTAVE_MIN -5
#define GUI_EDIT_OCTAVE_MAX 7
// TODO:
// - add colors for FM envelope and waveform
// - maybe add "alternate" color for FM modulators/carriers (a bit difficult)
@ -351,6 +357,9 @@ enum FurnaceGUIColors {
GUI_COLOR_INSTR_GBA_MINMOD,
GUI_COLOR_INSTR_BIFURCATOR,
GUI_COLOR_INSTR_SID2,
GUI_COLOR_INSTR_SUPERVISION,
GUI_COLOR_INSTR_UPD1771C,
GUI_COLOR_INSTR_SID3,
GUI_COLOR_INSTR_UNKNOWN,
GUI_COLOR_CHANNEL_BG,
@ -598,7 +607,6 @@ enum FurnaceGUIFileDialogs {
GUI_FILE_EXPORT_AUDIO_PER_SYS,
GUI_FILE_EXPORT_AUDIO_PER_CHANNEL,
GUI_FILE_EXPORT_VGM,
GUI_FILE_EXPORT_ZSM,
GUI_FILE_EXPORT_CMDSTREAM,
GUI_FILE_EXPORT_TEXT,
GUI_FILE_EXPORT_ROM,
@ -642,6 +650,7 @@ enum FurnaceGUIWarnings {
GUI_WARN_CLEAR_HISTORY,
GUI_WARN_CV,
GUI_WARN_RESET_CONFIG,
GUI_WARN_IMPORT,
GUI_WARN_GENERIC
};
@ -651,7 +660,6 @@ enum FurnaceGUIExportTypes {
GUI_EXPORT_AUDIO=0,
GUI_EXPORT_VGM,
GUI_EXPORT_ROM,
GUI_EXPORT_ZSM,
GUI_EXPORT_CMD_STREAM,
GUI_EXPORT_TEXT,
GUI_EXPORT_DMF
@ -817,6 +825,8 @@ enum FurnaceGUIActions {
GUI_ACTION_PAT_SCROLL_MODE,
GUI_ACTION_PAT_CLEAR_LATCH,
GUI_ACTION_PAT_ABSORB_INSTRUMENT,
GUI_ACTION_PAT_CURSOR_UNDO,
GUI_ACTION_PAT_CURSOR_REDO,
GUI_ACTION_PAT_MAX,
GUI_ACTION_INS_LIST_MIN,
@ -1102,6 +1112,22 @@ struct UndoStep {
newPatLen(0) {}
};
struct CursorJumpPoint {
SelectionPoint point;
int order;
int subSong;
CursorJumpPoint(const SelectionPoint& p, int o, int ss):
point(p), order(o), subSong(ss) {}
CursorJumpPoint():
point(), order(0), subSong(0) {}
bool operator== (const CursorJumpPoint& spot) {
return point.xCoarse==spot.point.xCoarse && point.xFine==spot.point.xFine && point.y==spot.point.y && order==spot.order && subSong==spot.subSong;
}
bool operator!= (const CursorJumpPoint& spot) {
return !(*this == spot);
}
};
// -1 = any
struct MIDIBind {
int type, channel, data1, data2;
@ -1594,7 +1620,7 @@ class FurnaceGUI {
String workingDir, fileName, clipboard, warnString, errorString, lastError, curFileName, nextFile, sysSearchQuery, newSongQuery, paletteQuery, sampleBankSearchQuery;
String workingDirSong, workingDirIns, workingDirWave, workingDirSample, workingDirAudioExport;
String workingDirVGMExport, workingDirZSMExport, workingDirROMExport;
String workingDirVGMExport, workingDirROMExport;
String workingDirFont, workingDirColors, workingDirKeybinds;
String workingDirLayout, workingDirROM, workingDirTest;
String workingDirConfig;
@ -1603,17 +1629,18 @@ class FurnaceGUI {
String mmlStringSNES[DIV_MAX_CHIPS];
String folderString;
struct PaletteSearchResult { int id; std::vector<int> highlightChars; };
std::vector<DivSystem> sysSearchResults;
std::vector<std::pair<DivSample*,bool>> sampleBankSearchResults;
std::vector<FurnaceGUISysDef> newSongSearchResults;
std::vector<int> paletteSearchResults;
std::vector<PaletteSearchResult> paletteSearchResults;
FixedQueue<String,32> recentFile;
std::vector<DivInstrumentType> makeInsTypeList;
std::vector<FurnaceGUIWaveSizeEntry> waveSizeList;
std::vector<String> availRenderDrivers;
std::vector<String> availAudioDrivers;
bool quit, warnQuit, willCommit, edit, editClone, isPatUnique, modified, displayError, displayExporting, vgmExportLoop, zsmExportLoop, zsmExportOptimize, vgmExportPatternHints;
bool quit, warnQuit, willCommit, edit, editClone, isPatUnique, modified, displayError, displayExporting, vgmExportLoop, vgmExportPatternHints;
bool vgmExportDirectStream, displayInsTypeList, displayWaveSizeList;
bool portrait, injectBackUp, mobileMenuOpen, warnColorPushed;
bool wantCaptureKeyboard, oldWantCaptureKeyboard, displayMacroMenu;
@ -1634,7 +1661,6 @@ class FurnaceGUI {
int vgmExportTrailingTicks;
int cvHiScore;
int drawHalt;
int zsmExportTickRate;
int macroPointSize;
int waveEditStyle;
int displayInsTypeListMakeInsSample;
@ -1723,6 +1749,16 @@ class FurnaceGUI {
char emptyLabel[32];
char emptyLabel2[32];
std::vector<int> songOrdersLengths; // lengths of all orders (for drawing song export progress)
int songLength; // length of all the song in rows
int songLoopedSectionLength; // length of looped part of the song
int songFadeoutSectionLength; // length of fading part of the song
bool songHasSongEndCommand; // song has "Song end" command (FFxx)
int lengthOfOneFile; // length of one rendering pass. song length times num of loops + fadeout
int totalLength; // total length of render (lengthOfOneFile times num of files for per-channel export)
float curProgress;
int totalFiles;
struct Settings {
bool settingsChanged;
int mainFontSize, patFontSize, headFontSize, iconSize;
@ -1742,6 +1778,7 @@ class FurnaceGUI {
int opnbCore;
int opl2Core;
int opl3Core;
int opl4Core;
int esfmCore;
int opllCore;
int ayCore;
@ -1768,6 +1805,7 @@ class FurnaceGUI {
int opnbCoreRender;
int opl2CoreRender;
int opl3CoreRender;
int opl4CoreRender;
int esfmCoreRender;
int opllCoreRender;
int ayCoreRender;
@ -1794,6 +1832,7 @@ class FurnaceGUI {
int patRowsBase;
int orderRowsBase;
int soloAction;
int ctrlWheelModifier;
int pullDeleteBehavior;
int wrapHorizontal;
int wrapVertical;
@ -1802,7 +1841,6 @@ class FurnaceGUI {
int allowEditDocking;
int chipNames;
int overflowHighlight;
int partyTime;
int flatNotes;
int germanNotation;
int stepOnDelete;
@ -2004,6 +2042,7 @@ class FurnaceGUI {
opnbCore(1),
opl2Core(0),
opl3Core(0),
opl4Core(0),
esfmCore(0),
opllCore(0),
ayCore(0),
@ -2030,6 +2069,7 @@ class FurnaceGUI {
opnbCoreRender(1),
opl2CoreRender(0),
opl3CoreRender(0),
opl4CoreRender(0),
esfmCoreRender(0),
opllCoreRender(0),
ayCoreRender(0),
@ -2055,6 +2095,7 @@ class FurnaceGUI {
patRowsBase(0),
orderRowsBase(1),
soloAction(0),
ctrlWheelModifier(0),
pullDeleteBehavior(1),
wrapHorizontal(0),
wrapVertical(0),
@ -2063,7 +2104,6 @@ class FurnaceGUI {
allowEditDocking(1),
chipNames(0),
overflowHighlight(0),
partyTime(0),
germanNotation(0),
stepOnDelete(0),
scrollStep(0),
@ -2202,7 +2242,7 @@ class FurnaceGUI {
vsync(1),
frameRateLimit(60),
displayRenderTime(0),
inputRepeat(0),
inputRepeat(1),
glRedSize(8),
glGreenSize(8),
glBlueSize(8),
@ -2244,13 +2284,20 @@ class FurnaceGUI {
struct Tutorial {
bool introPlayed;
bool protoWelcome;
bool importedMOD, importedS3M, importedXM, importedIT;
double popupTimer;
Tutorial():
#ifdef SUPPORT_XP
introPlayed(true),
#else
introPlayed(false),
#endif
protoWelcome(false) {
protoWelcome(false),
importedMOD(false),
importedS3M(false),
importedXM(false),
importedIT(false),
popupTimer(10.0f) {
}
} tutorial;
@ -2263,6 +2310,9 @@ class FurnaceGUI {
std::vector<ImWchar> localeExtraRanges;
DivInstrument* prevInsData;
DivInstrument cachedCurIns;
DivInstrument* cachedCurInsPtr;
bool insEditMayBeDirty;
unsigned char* pendingLayoutImport;
size_t pendingLayoutImportLen;
@ -2328,7 +2378,7 @@ class FurnaceGUI {
// bit 28: meta (win)
// bit 27: alt
// bit 24-26: reserved
int actionKeys[GUI_ACTION_MAX];
std::vector<int> actionKeys[GUI_ACTION_MAX];
std::map<int,int> actionMapGlobal;
std::map<int,int> actionMapPat;
@ -2448,7 +2498,7 @@ class FurnaceGUI {
int waveDragMin, waveDragMax;
bool waveDragActive;
int bindSetTarget, bindSetPrevValue;
int bindSetTarget, bindSetTargetIdx, bindSetPrevValue;
bool bindSetActive, bindSetPending;
float nextScroll, nextAddScroll, orderScroll, orderScrollSlideOrigin;
@ -2491,6 +2541,8 @@ class FurnaceGUI {
std::map<unsigned short,DivPattern*> oldPatMap;
FixedQueue<UndoStep,256> undoHist;
FixedQueue<UndoStep,256> redoHist;
FixedQueue<CursorJumpPoint,256> cursorUndoHist;
FixedQueue<CursorJumpPoint,256> cursorRedoHist;
// sample editor specific
double sampleZoom;
@ -2704,16 +2756,17 @@ class FurnaceGUI {
void drawExportAudio(bool onWindow=false);
void drawExportVGM(bool onWindow=false);
void drawExportROM(bool onWindow=false);
void drawExportZSM(bool onWindow=false);
void drawExportText(bool onWindow=false);
void drawExportCommand(bool onWindow=false);
void drawExportDMF(bool onWindow=false);
void drawSSGEnv(unsigned char type, const ImVec2& size);
void drawWaveform(unsigned char type, bool opz, const ImVec2& size);
void drawWaveformSID3(unsigned char type, const ImVec2& size);
void drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, const ImVec2& size);
void drawESFMAlgorithm(DivInstrumentESFM& esfm, const ImVec2& size);
void drawFMEnv(unsigned char tl, unsigned char ar, unsigned char dr, unsigned char d2r, unsigned char rr, unsigned char sl, unsigned char sus, unsigned char egt, unsigned char algOrGlobalSus, float maxTl, float maxArDr, float maxRr, const ImVec2& size, unsigned short instType);
void drawSID3Env(unsigned char tl, unsigned char ar, unsigned char dr, unsigned char d2r, unsigned char rr, unsigned char sl, unsigned char sus, unsigned char egt, unsigned char algOrGlobalSus, float maxTl, float maxArDr, float maxRr, const ImVec2& size, unsigned short instType);
void drawGBEnv(unsigned char vol, unsigned char len, unsigned char sLen, bool dir, const ImVec2& size);
bool drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& flags, bool modifyOnChange, bool fromMenu=false);
void kvsConfig(DivInstrument* ins, bool supportsKVS=true);
@ -2730,6 +2783,7 @@ class FurnaceGUI {
static bool LocalizedComboGetter(void* data, int idx, const char** out_text);
// these ones offer ctrl-wheel fine value changes.
bool isCtrlWheelModifierHeld() const;
bool CWSliderScalar(const char* label, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format=NULL, ImGuiSliderFlags flags=0);
bool CWVSliderScalar(const char* label, const ImVec2& size, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format=NULL, ImGuiSliderFlags flags=0);
bool CWSliderInt(const char* label, int* v, int v_min, int v_max, const char* format="%d", ImGuiSliderFlags flags=0);
@ -2776,6 +2830,7 @@ class FurnaceGUI {
void insTabFMModernHeader(DivInstrument* ins);
void insTabFM(DivInstrument* ins);
void insTabWavetable(DivInstrument* ins);
void insTabSample(DivInstrument* ins);
void drawOrderButtons();
@ -2783,6 +2838,8 @@ class FurnaceGUI {
void actualWaveList();
void actualSampleList();
// HACK: template. any way to remove it?
template<typename func_waveItemData> void waveListHorizontalGroup(float* wavePreview, int dir, int count, const func_waveItemData& waveItemData);
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);
@ -2796,8 +2853,6 @@ class FurnaceGUI {
void pushToggleColors(bool status);
void popToggleColors();
void highlightWindow(const char* winName);
FurnaceGUIImage* getImage(FurnaceGUIImages image);
FurnaceGUITexture* 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);
@ -2812,6 +2867,7 @@ class FurnaceGUI {
void drawPattern();
void drawInsList(bool asChild=false);
void drawInsEdit();
void drawInsSID3(DivInstrument* ins);
void drawWaveList(bool asChild=false);
void drawWaveEdit();
void drawSampleList(bool asChild=false);
@ -2824,7 +2880,7 @@ class FurnaceGUI {
void drawMemory();
void drawCompatFlags();
void drawPiano();
void drawNotes();
void drawNotes(bool asChild=false);
void drawChannels();
void drawPatManager();
void drawSysManager();
@ -2849,8 +2905,10 @@ class FurnaceGUI {
void drawSystemChannelInfo(const DivSysDef* whichDef);
void drawSystemChannelInfoText(const DivSysDef* whichDef);
void assignActionMap(std::map<int,int>& actionMap, int first, int last);
void drawKeybindSettingsTableRow(FurnaceGUIActions actionIdx);
void parseKeybinds();
void promptKey(int which);
void promptKey(int which, int bindIdx);
void doAction(int what);
bool importColors(String path);
@ -2916,22 +2974,33 @@ class FurnaceGUI {
void doExpand(int multiplier, const SelectionPoint& sStart, const SelectionPoint& sEnd);
void doCollapseSong(int divider);
void doExpandSong(int multiplier);
void doAbsorbInstrument();
void doUndo();
void doRedo();
void doFind();
void doReplace();
void doDrag();
void editOptions(bool topMenu);
DivSystem systemPicker();
DivSystem systemPicker(bool fullWidth);
void noteInput(int num, int key, int vol=-1);
void valueInput(int num, bool direct=false, int target=-1);
void orderInput(int num);
void doGenerateWave();
CursorJumpPoint getCurrentCursorJumpPoint();
void applyCursorJumpPoint(const CursorJumpPoint& spot);
void makeCursorUndo();
void doCursorUndo();
void doCursorRedo();
void doUndoSample();
void doRedoSample();
void checkRecordInstrumentUndoStep();
void doUndoInstrument();
void doRedoInstrument();
void play(int row=0);
void setOrder(unsigned char order, bool forced=false);
void stop();

View file

@ -184,6 +184,9 @@ const char* insTypes[DIV_INS_MAX+1][3]={
{"GBA MinMod",ICON_FA_VOLUME_UP,ICON_FUR_INS_GBA_MINMOD},
{"Bifurcator",ICON_FA_LINE_CHART,ICON_FUR_INS_BIFURCATOR},
{"SID2",ICON_FA_KEYBOARD_O,ICON_FUR_INS_SID2},
{"Watara Supervision",ICON_FA_GAMEPAD,ICON_FUR_INS_SUPERVISION},
{"NEC μPD1771C",ICON_FA_BAR_CHART,ICON_FUR_INS_UPD1771C},
{"SID3",ICON_FA_KEYBOARD_O,ICON_FUR_INS_SID3},
{NULL,ICON_FA_QUESTION,ICON_FA_QUESTION}
};
@ -208,7 +211,7 @@ const char* sampleDepths[DIV_SAMPLE_DEPTH_MAX]={
"8-bit µ-law PCM",
"C219 PCM",
"IMA ADPCM",
NULL,
"12-bit PCM",
NULL,
"16-bit PCM"
};
@ -365,40 +368,40 @@ const FurnaceGUIColors fxColors[256]={
GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,
// 60-6F
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,
GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,
GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,
GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,
GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,
GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,
GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,
GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,
GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,
GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,
GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,
GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,
GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,
GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,
GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,
GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,
// 70-7F
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,
GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,
GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,
GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,
GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,
GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,
GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,
GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,
GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,
GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,
GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,
GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,
GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,
GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,
GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,
GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,
// 80-8F
GUI_COLOR_PATTERN_EFFECT_PANNING,
@ -437,27 +440,27 @@ const FurnaceGUIColors fxColors[256]={
GUI_COLOR_PATTERN_EFFECT_MISC,
// A0-AF
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,
GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,
GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,
GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,
GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,
GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,
GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,
GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,
GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,
GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,
GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,
GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,
GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,
GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,
GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,
GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,
// B0-BF
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,
GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,
GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
@ -494,8 +497,8 @@ const FurnaceGUIColors fxColors[256]={
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_VOLUME,
GUI_COLOR_PATTERN_EFFECT_VOLUME,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
GUI_COLOR_PATTERN_EFFECT_INVALID,
@ -557,7 +560,7 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={
D("EXPORT", _N("Export"), 0),
D("UNDO", _N("Undo"), FURKMOD_CMD|SDLK_z),
#ifdef __APPLE__
D("REDO", _N("Redo"), FURKMOD_CMD|FURKMOD_SHIFT|SDLK_z),
D("REDO", _N("Redo"), FURKMOD_CMD|FURKMOD_SHIFT|SDLK_z, FURKMOD_CMD|SDLK_y),
#else
D("REDO", _N("Redo"), FURKMOD_CMD|SDLK_y),
#endif
@ -708,7 +711,9 @@ const FurnaceGUIActionDef guiActions[GUI_ACTION_MAX]={
D("PAT_LATCH", _N("Set note input latch"), 0),
D("PAT_SCROLL_MODE", _N("Change mobile scroll mode"), 0),
D("PAT_CLEAR_LATCH", _N("Clear note input latch"), 0),
D("PAT_ABSORB_INSTRUMENT", _N("Set current instrument to channel's current instrument column"), 0),
D("PAT_ABSORB_INSTRUMENT", _N("Absorb instrument/octave from status at cursor"), 0),
D("PAT_CURSOR_UNDO", _N("Return cursor to previous jump point"), 0),
D("PAT_CURSOR_REDO", _N("Reverse recent cursor undo"), 0),
D("PAT_MAX", "", NOT_AN_ACTION),
D("INS_LIST_MIN", _N("---Instrument list"), NOT_AN_ACTION),
@ -1037,6 +1042,9 @@ const FurnaceGUIColorDef guiColors[GUI_COLOR_MAX]={
D(GUI_COLOR_INSTR_GBA_MINMOD,"",ImVec4(0.5f,0.45f,0.7f,1.0f)),
D(GUI_COLOR_INSTR_BIFURCATOR,"",ImVec4(0.8925f,0.8925f,0.8925f,1.0f)),
D(GUI_COLOR_INSTR_SID2,"",ImVec4(0.6f,0.75f,1.0f,1.0f)),
D(GUI_COLOR_INSTR_SUPERVISION,"",ImVec4(0.52f,1.0f,0.6f,1.0f)),
D(GUI_COLOR_INSTR_UPD1771C,"",ImVec4(0.94f,0.52f,0.6f,1.0f)),
D(GUI_COLOR_INSTR_SID3,"",ImVec4(0.6f,0.75f,0.6f,1.0f)),
D(GUI_COLOR_INSTR_UNKNOWN,"",ImVec4(0.3f,0.3f,0.3f,1.0f)),
D(GUI_COLOR_CHANNEL_BG,"",ImVec4(0.4f,0.6f,0.8f,1.0f)),
@ -1285,6 +1293,11 @@ const int availableSystems[]={
DIV_SYSTEM_5E01,
DIV_SYSTEM_BIFURCATOR,
DIV_SYSTEM_SID2,
DIV_SYSTEM_OPL4,
DIV_SYSTEM_OPL4_DRUMS,
DIV_SYSTEM_SUPERVISION,
DIV_SYSTEM_UPD1771C,
DIV_SYSTEM_SID3,
0 // don't remove this last one!
};
@ -1320,6 +1333,8 @@ const int chipsFM[]={
DIV_SYSTEM_OPL3_DRUMS,
DIV_SYSTEM_OPZ,
DIV_SYSTEM_ESFM,
DIV_SYSTEM_OPL4,
DIV_SYSTEM_OPL4_DRUMS,
0 // don't remove this last one!
};
@ -1380,6 +1395,9 @@ const int chipsSpecial[]={
DIV_SYSTEM_5E01,
DIV_SYSTEM_BIFURCATOR,
DIV_SYSTEM_SID2,
DIV_SYSTEM_SUPERVISION,
DIV_SYSTEM_UPD1771C,
DIV_SYSTEM_SID3,
0 // don't remove this last one!
};
@ -1405,6 +1423,8 @@ const int chipsSample[]={
DIV_SYSTEM_NDS,
DIV_SYSTEM_GBA_DMA,
DIV_SYSTEM_GBA_MINMOD,
DIV_SYSTEM_OPL4,
DIV_SYSTEM_OPL4_DRUMS,
0 // don't remove this last one!
};

View file

@ -43,9 +43,17 @@ enum FurnaceGUIChanTypes {
struct FurnaceGUIActionDef {
const char* name;
const char* friendlyName;
int defaultBind;
FurnaceGUIActionDef(const char* n, const char* fn, int db):
name(n), friendlyName(fn), defaultBind(db) {}
std::vector<int> defaultBind;
bool isNotABind() const { return defaultBind.size()==1 && defaultBind[0]==-1; }
FurnaceGUIActionDef(const char* n, const char* fn, int db) :
name(n), friendlyName(fn) {
if (db!=0) defaultBind.push_back(db);
}
FurnaceGUIActionDef(const char* n, const char* fn, int db, int db2):
name(n), friendlyName(fn) {
if (db!=0) defaultBind.push_back(db);
if (db2!=0) defaultBind.push_back(db);
}
};
struct FurnaceGUIColorDef {

File diff suppressed because it is too large Load diff

View file

@ -33,6 +33,7 @@ const int _THIRTY_ONE=31;
const int _SIXTY_FOUR=64;
const int _ONE_HUNDRED=100;
const int _ONE_HUNDRED_TWENTY_SEVEN=127;
const int _ONE_HUNDRED_SEVENTY_NINE=179;
const int _TWO_HUNDRED_FIFTY_FIVE=255;
const int _FIVE_HUNDRED_ELEVEN=511;
const int _TWO_THOUSAND_FORTY_SEVEN=2047;

View file

@ -35,6 +35,7 @@ extern const int _THIRTY_ONE;
extern const int _SIXTY_FOUR;
extern const int _ONE_HUNDRED;
extern const int _ONE_HUNDRED_TWENTY_SEVEN;
extern const int _ONE_HUNDRED_SEVENTY_NINE;
extern const int _TWO_HUNDRED_FIFTY_FIVE;
extern const int _FIVE_HUNDRED_ELEVEN;
extern const int _TWO_THOUSAND_FORTY_SEVEN;

View file

@ -37,7 +37,7 @@ void FurnaceGUI::drawSysDefs(std::vector<FurnaceGUISysDef>& category, bool& acce
ImGui::TableNextColumn();
if (!i.subDefs.empty()) {
if (i.orig.empty()) {
sysDefID=fmt::sprintf("%s%s/%dS",i.name,sysDefIDLeader,index);
sysDefID=fmt::sprintf("%s%s/%dS",_(i.name.c_str()),sysDefIDLeader,index);
} else {
sysDefID=fmt::sprintf("%s/%dS",sysDefIDLeader,index);
}
@ -45,7 +45,7 @@ void FurnaceGUI::drawSysDefs(std::vector<FurnaceGUISysDef>& category, bool& acce
ImGui::SameLine();
}
if (!i.orig.empty()) {
sysDefID=fmt::sprintf("%s%s/%d",i.name,sysDefIDLeader,index);
sysDefID=fmt::sprintf("%s%s/%d",_(i.name.c_str()),sysDefIDLeader,index);
if (ImGui::Selectable(sysDefID.c_str(),false,ImGuiSelectableFlags_DontClosePopups)) {
nextDesc=i.definition;
nextDescName=i.name;

View file

@ -1282,116 +1282,120 @@ void FurnaceGUI::drawPattern() {
memset(floors,0,4*4*sizeof(unsigned int));
for (int i=0; i<chans; i++) {
bool isPaired=false;
int numPairs=0;
unsigned int pairMin=i;
unsigned int pairMax=i;
unsigned char curFloor=0;
if (!e->curSubSong->chanShow[i]) {
continue;
}
std::vector<DivChannelPair> pairs;
e->getChanPaired(i,pairs);
DivChannelPair pairs=e->getChanPaired(i);
for (int j=0; j<8; j++) {
if (pairs.pairs[j]==-1) continue;
int pairCh=e->dispatchFirstChan[i]+pairs.pairs[j];
if (!e->curSubSong->chanShow[pairCh]) {
for (DivChannelPair pair: pairs) {
bool isPaired=false;
int numPairs=0;
unsigned int pairMin=i;
unsigned int pairMax=i;
unsigned char curFloor=0;
if (!e->curSubSong->chanShow[i]) {
continue;
}
isPaired=true;
if ((unsigned int)pairCh<pairMin) pairMin=pairCh;
if ((unsigned int)pairCh>pairMax) pairMax=pairCh;
}
if (!isPaired) continue;
float posY=chanHeadBottom;
// find a free floor
while (curFloor<4) {
bool free=true;
for (unsigned int j=pairMin; j<=pairMax; j++) {
const unsigned int j0=j>>5;
const unsigned int j1=1U<<(j&31);
if (floors[curFloor][j0]&j1) {
free=false;
break;
for (int j=0; j<8; j++) {
if (pair.pairs[j]==-1) continue;
int pairCh=e->dispatchFirstChan[i]+pair.pairs[j];
if (!e->curSubSong->chanShow[pairCh]) {
continue;
}
}
if (free) break;
curFloor++;
}
if (curFloor<4) {
// occupy floor
floors[curFloor][pairMin>>5]|=1U<<(pairMin&31);
floors[curFloor][pairMax>>5]|=1U<<(pairMax&31);
}
pos=(patChanX[i+1]+patChanX[i])*0.5;
posCenter=pos;
posMin=pos;
posMax=pos;
numPairs++;
if (pairs.label==NULL) {
textSize=ImGui::CalcTextSize("???");
} else {
textSize=ImGui::CalcTextSize(pairs.label);
}
posY+=(textSize.y+ImGui::GetStyle().ItemSpacing.y)*curFloor;
tdl->AddLine(
ImVec2(pos,chanHeadBottom),
ImVec2(pos,posY+textSize.y),
ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_PAIR]),
2.0f*dpiScale
);
for (int j=0; j<8; j++) {
if (pairs.pairs[j]==-1) continue;
int pairCh=e->dispatchFirstChan[i]+pairs.pairs[j];
if (!e->curSubSong->chanShow[pairCh]) {
continue;
isPaired=true;
if ((unsigned int)pairCh<pairMin) pairMin=pairCh;
if ((unsigned int)pairCh>pairMax) pairMax=pairCh;
}
pos=(patChanX[pairCh+1]+patChanX[pairCh])*0.5;
posCenter+=pos;
if (!isPaired) continue;
float posY=chanHeadBottom;
// find a free floor
while (curFloor<4) {
bool free=true;
for (unsigned int j=pairMin; j<=pairMax; j++) {
const unsigned int j0=j>>5;
const unsigned int j1=1U<<(j&31);
if (floors[curFloor][j0]&j1) {
free=false;
break;
}
}
if (free) break;
curFloor++;
}
if (curFloor<4) {
// occupy floor
floors[curFloor][pairMin>>5]|=1U<<(pairMin&31);
floors[curFloor][pairMax>>5]|=1U<<(pairMax&31);
}
pos=(patChanX[i+1]+patChanX[i])*0.5;
posCenter=pos;
posMin=pos;
posMax=pos;
numPairs++;
if (pos<posMin) posMin=pos;
if (pos>posMax) posMax=pos;
if (pair.label==NULL) {
textSize=ImGui::CalcTextSize("???");
} else {
textSize=ImGui::CalcTextSize(pair.label);
}
posY+=(textSize.y+ImGui::GetStyle().ItemSpacing.y)*curFloor;
tdl->AddLine(
ImVec2(pos,chanHeadBottom),
ImVec2(pos,posY+textSize.y),
ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_PAIR]),
2.0f*dpiScale
);
}
posCenter/=numPairs;
for (int j=0; j<8; j++) {
if (pair.pairs[j]==-1) continue;
int pairCh=e->dispatchFirstChan[i]+pair.pairs[j];
if (!e->curSubSong->chanShow[pairCh]) {
continue;
}
if (pairs.label==NULL) {
tdl->AddLine(
ImVec2(posMin,posY+textSize.y),
ImVec2(posMax,posY+textSize.y),
ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_PAIR]),
2.0f*dpiScale
);
} else {
tdl->AddLine(
ImVec2(posMin,posY+textSize.y),
ImVec2(posCenter-textSize.x*0.5-6.0f*dpiScale,posY+textSize.y),
ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_PAIR]),
2.0f*dpiScale
);
tdl->AddLine(
ImVec2(posCenter+textSize.x*0.5+6.0f*dpiScale,posY+textSize.y),
ImVec2(posMax,posY+textSize.y),
ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_PAIR]),
2.0f*dpiScale
);
pos=(patChanX[pairCh+1]+patChanX[pairCh])*0.5;
posCenter+=pos;
numPairs++;
if (pos<posMin) posMin=pos;
if (pos>posMax) posMax=pos;
tdl->AddLine(
ImVec2(pos,chanHeadBottom),
ImVec2(pos,posY+textSize.y),
ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_PAIR]),
2.0f*dpiScale
);
}
delayedLabels.push_back(DelayedLabel(posCenter,posY,textSize,pairs.label));
posCenter/=numPairs;
if (pair.label==NULL) {
tdl->AddLine(
ImVec2(posMin,posY+textSize.y),
ImVec2(posMax,posY+textSize.y),
ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_PAIR]),
2.0f*dpiScale
);
} else {
tdl->AddLine(
ImVec2(posMin,posY+textSize.y),
ImVec2(posCenter-textSize.x*0.5-6.0f*dpiScale,posY+textSize.y),
ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_PAIR]),
2.0f*dpiScale
);
tdl->AddLine(
ImVec2(posCenter+textSize.x*0.5+6.0f*dpiScale,posY+textSize.y),
ImVec2(posMax,posY+textSize.y),
ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_PAIR]),
2.0f*dpiScale
);
delayedLabels.push_back(DelayedLabel(posCenter,posY,textSize,pair.label));
}
}
}
@ -1500,6 +1504,7 @@ void FurnaceGUI::drawPattern() {
i.cmd==DIV_CMD_HINT_PORTA ||
i.cmd==DIV_CMD_HINT_LEGATO ||
i.cmd==DIV_CMD_HINT_VOL_SLIDE ||
i.cmd==DIV_CMD_HINT_VOL_SLIDE_TARGET ||
i.cmd==DIV_CMD_HINT_ARPEGGIO ||
i.cmd==DIV_CMD_HINT_PITCH ||
i.cmd==DIV_CMD_HINT_VIBRATO ||

View file

@ -185,7 +185,7 @@ void PlotNoLerp(const char* label, const float* values, int values_count, int va
PlotNoLerpEx(ImGuiPlotType_Lines, label, &Plot_ArrayGetter, (void*)&data, values_count, values_offset, overlay_text, scale_min, scale_max, graph_size);
}
int PlotBitfieldEx(const char* label, int (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char** overlay_text, int bits, ImVec2 frame_size, const bool* values_highlight, ImVec4 highlightColor, ImVec4 color)
int PlotBitfieldEx(const char* label, int (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char** overlay_text, int bits, ImVec2 frame_size, const bool* values_highlight, ImVec4 highlightColor, ImVec4 color, std::string (*hoverFunc)(int,float,void*), void* hoverFuncUser)
{
ImGuiContext& g = *GImGui;
ImGuiWindow* window = ImGui::GetCurrentWindow();
@ -225,8 +225,18 @@ int PlotBitfieldEx(const char* label, int (*values_getter)(void* data, int idx),
const int v_idx = (int)(t * item_count);
IM_ASSERT(v_idx >= 0 && v_idx < values_count);
//const float v0 = values_getter(data, (v_idx + values_offset) % values_count);
const float v0 = values_getter(data, (v_idx + values_offset) % values_count);
//ImGui::SetTooltip("%d: %8.4g", v_idx, v0);
if (hoverFunc) {
std::string hoverText=hoverFunc(v_idx,v0,hoverFuncUser);
if (!hoverText.empty()) {
ImGui::SetTooltip("%s",hoverText.c_str());
}
} else {
ImGui::SetTooltip("%d: %d (%X)", v_idx, (int)v0, (int)v0);
}
idx_hovered = v_idx;
}
@ -290,10 +300,10 @@ int PlotBitfieldEx(const char* label, int (*values_getter)(void* data, int idx),
return idx_hovered;
}
void PlotBitfield(const char* label, const int* values, int values_count, int values_offset, const char** overlay_text, int bits, ImVec2 graph_size, int stride, const bool* values_highlight, ImVec4 highlightColor, ImVec4 color)
void PlotBitfield(const char* label, const int* values, int values_count, int values_offset, const char** overlay_text, int bits, ImVec2 graph_size, int stride, const bool* values_highlight, ImVec4 highlightColor, ImVec4 color, std::string (*hoverFunc)(int,float,void*), void* hoverFuncUser)
{
FurnacePlotIntArrayGetterData data(values, stride);
PlotBitfieldEx(label, &Plot_IntArrayGetter, (void*)&data, values_count, values_offset, overlay_text, bits, graph_size, values_highlight, highlightColor, color);
PlotBitfieldEx(label, &Plot_IntArrayGetter, (void*)&data, values_count, values_offset, overlay_text, bits, graph_size, values_highlight, highlightColor, color, hoverFunc, hoverFuncUser);
}
int PlotCustomEx(ImGuiPlotType plot_type, const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_display_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 frame_size, ImVec4 color, int highlight, std::string (*hoverFunc)(int,float,void*), void* hoverFuncUser, bool blockMode, std::string (*guideFunc)(float), const bool* values_highlight, ImVec4 highlightColor)

View file

@ -21,5 +21,5 @@
#include "../pch.h"
void PlotNoLerp(const char* label, const float* values, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0, 0), int stride = sizeof(float));
void PlotBitfield(const char* label, const int* values, int values_count, int values_offset = 0, const char** overlay_text = NULL, int bits = 8, ImVec2 graph_size = ImVec2(0, 0), int stride = sizeof(float), const bool* values_highlight = NULL, ImVec4 highlightColor = ImVec4(1.0f,1.0f,1.0f,1.0f), ImVec4 color = ImVec4(1.0f,1.0f,1.0f,1.0f));
void PlotBitfield(const char* label, const int* values, int values_count, int values_offset = 0, const char** overlay_text = NULL, int bits = 8, ImVec2 graph_size = ImVec2(0, 0), int stride = sizeof(float), const bool* values_highlight = NULL, ImVec4 highlightColor = ImVec4(1.0f,1.0f,1.0f,1.0f), ImVec4 color = ImVec4(1.0f,1.0f,1.0f,1.0f), std::string (*hoverFunc)(int,float,void*) = NULL, void* hoverFuncUser = NULL);
void PlotCustom(const char* label, const float* values, int values_count, int values_offset = 0, const char* overlay_text = NULL, float scale_min = FLT_MAX, float scale_max = FLT_MAX, ImVec2 graph_size = ImVec2(0, 0), int stride = sizeof(float), ImVec4 fgColor = ImVec4(1.0f,1.0f,1.0f,1.0f), int highlight = 0, std::string (*hoverFunc)(int,float,void*) = NULL, void* hoverFuncUser=NULL, bool blockMode=false, std::string (*guideFunc)(float) = NULL, const bool* values_highlight = NULL, ImVec4 highlightColor = ImVec4(1.0f,1.0f,1.0f,1.0f));

File diff suppressed because it is too large Load diff

View file

@ -253,16 +253,27 @@ void FurnaceGUI::drawSampleEdit() {
SAMPLE_WARN(warnLength,"QSound: maximum sample length is 65535");
}
break;
case DIV_SYSTEM_NES:
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->loopStart&511) {
int tryWith=(sample->loopStart)&(~511);
if (tryWith>(int)sample->samples) tryWith-=512;
String alignHint=fmt::sprintf(_("NES: loop start must be a multiple of 512 (try with %d)"),tryWith);
SAMPLE_WARN(warnLoopStart,alignHint);
}
if ((sample->loopEnd-8)&127) {
int tryWith=(sample->loopEnd-8)&(~127);
if (tryWith>(int)sample->samples) tryWith-=128;
tryWith+=8; // +1 bc of how sample length is treated: https://www.nesdev.org/wiki/APU_DMC
String alignHint=fmt::sprintf(_("NES: loop end must be a multiple of 128 (try with %d)"),tryWith);
SAMPLE_WARN(warnLoopEnd,alignHint);
}
}
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"));
@ -402,6 +413,25 @@ void FurnaceGUI::drawSampleEdit() {
SAMPLE_WARN(warnLength,_("GBA DMA: sample length will be padded to multiple of 16"));
}
break;
case DIV_SYSTEM_OPL4:
case DIV_SYSTEM_OPL4_DRUMS:
if (sample->samples>65535) {
SAMPLE_WARN(warnLength,_("OPL4: maximum sample length is 65535"));
}
break;
case DIV_SYSTEM_SUPERVISION:
if (sample->loop) {
if (sample->loopStart!=0 || sample->loopEnd!=(int)(sample->samples)) {
SAMPLE_WARN(warnLoopPos,_("Supervision: loop point ignored on sample channel"));
}
}
if (sample->samples&31) {
SAMPLE_WARN(warnLength,_("Supervision: sample length will be padded to multiple of 32"));
}
if (sample->samples>8192) {
SAMPLE_WARN(warnLength,_("Supervision: maximum sample length is 8192"));
}
break;
default:
break;
}

File diff suppressed because it is too large Load diff

View file

@ -22,18 +22,23 @@
// NOTE: please don't ask me to enable text wrap.
// Dear ImGui doesn't have that feature. D:
void FurnaceGUI::drawNotes() {
void FurnaceGUI::drawNotes(bool asChild) {
if (nextWindow==GUI_WINDOW_NOTES) {
notesOpen=true;
ImGui::SetNextWindowFocus();
nextWindow=GUI_WINDOW_NOTHING;
}
if (!notesOpen) return;
if (ImGui::Begin("Song Comments",&notesOpen,globalWinFlags,_("Song Comments"))) {
if (!notesOpen && !asChild) return;
bool began=asChild?ImGui::BeginChild("Song Info##Song Information"):ImGui::Begin("Song Comments",&notesOpen,globalWinFlags,_("Song Comments"));
if (began) {
if (ImGui::InputTextMultiline("##SongNotes",&e->song.notes,ImGui::GetContentRegionAvail(),ImGuiInputTextFlags_UndoRedo)) {
MARK_MODIFIED;
}
}
if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_NOTES;
ImGui::End();
if (!asChild && ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_NOTES;
if (asChild) {
ImGui::EndChild();
} else {
ImGui::End();
}
}

View file

@ -36,6 +36,7 @@ void FurnaceGUI::drawSubSongs(bool asChild) {
ImGui::TableNextRow();
ImGui::TableNextColumn();
if (ImGui::Selectable(id,i==e->getCurrentSubSong())) {
makeCursorUndo();
e->changeSongP(i);
updateScroll(0);
oldRow=0;
@ -72,6 +73,7 @@ void FurnaceGUI::drawSubSongs(bool asChild) {
if (!e->addSubSong()) {
showError(_("too many subsongs!"));
} else {
makeCursorUndo();
e->changeSongP(e->song.subsong.size()-1);
updateScroll(0);
oldRow=0;
@ -92,6 +94,7 @@ void FurnaceGUI::drawSubSongs(bool asChild) {
if (!e->duplicateSubSong(e->getCurrentSubSong())) {
showError(_("too many subsongs!"));
} else {
makeCursorUndo();
e->changeSongP(e->song.subsong.size()-1);
updateScroll(0);
oldRow=0;

View file

@ -1757,6 +1757,7 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl
case DIV_SYSTEM_Y8950:
case DIV_SYSTEM_Y8950_DRUMS: {
int clockSel=flags.getInt("clockSel",0);
bool compatYPitch=flags.getBool("compatYPitch",false);
ImGui::Text(_("Clock rate:"));
ImGui::Indent();
@ -1786,9 +1787,16 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl
}
ImGui::Unindent();
if ((type==DIV_SYSTEM_Y8950 || type==DIV_SYSTEM_Y8950_DRUMS) && compatYPitch) {
if (ImGui::Checkbox(_("ADPCM channel one octave up (compatibility)"),&compatYPitch)) {
altered=true;
}
}
if (altered) {
e->lockSave([&]() {
flags.set("clockSel",clockSel);
flags.set("compatYPitch",compatYPitch);
});
}
break;
@ -1969,6 +1977,9 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl
echoFilter[6]=flags.getInt("echoFilter6",0);
echoFilter[7]=flags.getInt("echoFilter7",0);
bool interpolationOff=flags.getBool("interpolationOff",false);
bool antiClick=flags.getBool("antiClick",true);
ImGui::Text(_("Volume scale:"));
if (CWSliderInt(_("Left##VolScaleL"),&vsL,0,127)) {
if (vsL<0) vsL=0;
@ -2084,6 +2095,14 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl
ImGui::Text(_("sum: %d"),filterSum);
ImGui::PopStyleColor();
if (ImGui::Checkbox(_("Disable Gaussian interpolation"),&interpolationOff)) {
altered=true;
}
if (ImGui::Checkbox(_("Anti-click"),&antiClick)) {
altered=true;
}
if (altered) {
e->lockSave([&]() {
flags.set("volScaleL",127-vsL);
@ -2102,6 +2121,8 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl
flags.set("echoFilter6",echoFilter[6]);
flags.set("echoFilter7",echoFilter[7]);
flags.set("echoMask",echoMask);
flags.set("interpolationOff",interpolationOff);
flags.set("antiClick",antiClick);
});
}
@ -2317,6 +2338,29 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl
}
break;
}
case DIV_SYSTEM_SUPERVISION: {
bool swapDuty=flags.getInt("swapDuty",true);
if (ImGui::Checkbox(_("Swap noise duty cycles"),&swapDuty)) {
altered=true;
}
bool sqStereo=flags.getInt("sqStereo",false);
if (ImGui::Checkbox(_("Stereo pulse waves"),&sqStereo)) {
altered=true;
}
if (altered) {
e->lockSave([&]() {
flags.set("swapDuty",(int)swapDuty);
});
e->lockSave([&]() {
flags.set("sqStereo",(int)sqStereo);
});
}
break;
}
case DIV_SYSTEM_SM8521:/* {
bool noAntiClick=flags.getBool("noAntiClick",false);
@ -2507,6 +2551,67 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl
}
break;
}
case DIV_SYSTEM_OPL4:
case DIV_SYSTEM_OPL4_DRUMS: {
int clockSel=flags.getInt("clockSel",0);
int ramSize=flags.getInt("ramSize",0);
ImGui::Text(_("Clock rate:"));
ImGui::Indent();
if (ImGui::RadioButton(_("33.8688MHz"),clockSel==0)) {
clockSel=0;
altered=true;
}
if (ImGui::RadioButton(_("28.64MHz (NTSC)"),clockSel==1)) {
clockSel=1;
altered=true;
}
if (ImGui::RadioButton(_("28.38MHz (PAL)"),clockSel==2)) {
clockSel=2;
altered=true;
}
ImGui::Unindent();
ImGui::Text(_("RAM size:"));
ImGui::Indent();
if (ImGui::RadioButton(_("4MB"),ramSize==0)) {
ramSize=0;
altered=true;
}
if (ImGui::RadioButton(_("2MB"),ramSize==1)) {
ramSize=1;
altered=true;
}
if (ImGui::RadioButton(_("1MB"),ramSize==2)) {
ramSize=2;
altered=true;
}
if (ImGui::RadioButton(_("640KB"),ramSize==3)) {
ramSize=3;
altered=true;
}
if (ImGui::RadioButton(_("512KB"),ramSize==4)) {
ramSize=4;
altered=true;
}
if (ImGui::RadioButton(_("256KB"),ramSize==5)) {
ramSize=5;
altered=true;
}
if (ImGui::RadioButton(_("128KB"),ramSize==6)) {
ramSize=6;
altered=true;
}
ImGui::Unindent();
if (altered) {
e->lockSave([&]() {
flags.set("clockSel",clockSel);
flags.set("ramSize",ramSize);
});
}
break;
}
case DIV_SYSTEM_SWAN:
case DIV_SYSTEM_BUBSYS_WSG:
case DIV_SYSTEM_PET:
@ -2515,12 +2620,35 @@ bool FurnaceGUI::drawSysConf(int chan, int sysPos, DivSystem type, DivConfig& fl
case DIV_SYSTEM_C219:
case DIV_SYSTEM_BIFURCATOR:
case DIV_SYSTEM_POWERNOISE:
case DIV_SYSTEM_UPD1771C:
break;
case DIV_SYSTEM_YMU759:
case DIV_SYSTEM_ESFM:
supportsCustomRate=false;
ImGui::Text(_("nothing to configure"));
break;
case DIV_SYSTEM_SID3: {
bool quarterClock=flags.getBool("quarterClock",false);
if (ImGui::Checkbox(_("Quarter clock speed"),&quarterClock)) {
altered=true;
}
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(_("Decreases clock speed and CPU audio load by 4 times.\nCan be used if your CPU is too slow for the chip."
"\nDoes not affect clock speed during export!\n\n"
"Warning! Filters may become unstable at high cutoff and resonance\nif this option or lower clock speed are used!\n"
"Also filters' timbre may be different near these values.\n\n"
"Default clock speed is 1MHz (1000000Hz)."));
}
if (altered) {
e->lockSave([&]() {
flags.set("quarterClock",(int)quarterClock);
});
}
break;
}
default: {
bool sysPal=flags.getInt("clockSel",0);

View file

@ -117,7 +117,7 @@ void FurnaceGUI::drawSysManager() {
ImGui::SameLine();
ImGui::Button(_("Change##SysChange"));
if (ImGui::BeginPopupContextItem("SysPickerC",ImGuiPopupFlags_MouseButtonLeft)) {
DivSystem picked=systemPicker();
DivSystem picked=systemPicker(false);
if (picked!=DIV_SYSTEM_NULL) {
if (e->changeSystem(i,picked,preserveChanPos)) {
MARK_MODIFIED;
@ -153,7 +153,7 @@ void FurnaceGUI::drawSysManager() {
ImGui::TableNextColumn();
ImGui::Button(ICON_FA_PLUS "##SysAdd");
if (ImGui::BeginPopupContextItem("SysPickerA",ImGuiPopupFlags_MouseButtonLeft)) {
DivSystem picked=systemPicker();
DivSystem picked=systemPicker(false);
if (picked!=DIV_SYSTEM_NULL) {
if (!e->addSystem(picked)) {
showError(fmt::sprintf(_("cannot add chip! (%s)"),e->getLastError()));

View file

@ -282,6 +282,12 @@ const char* FurnaceGUI::getSystemPartNumber(DivSystem sys, DivConfig& flags) {
break;
case DIV_SYSTEM_ESFM:
return "ES1xxx";
case DIV_SYSTEM_SUPERVISION:
return "Watara Supervision";
break;
case DIV_SYSTEM_UPD1771C:
return "μPD1771C";
break;
default:
return FurnaceGUI::getSystemName(sys);
break;

View file

@ -23,7 +23,7 @@
#include "guiConst.h"
#include <imgui.h>
DivSystem FurnaceGUI::systemPicker() {
DivSystem FurnaceGUI::systemPicker(bool fullWidth) {
DivSystem ret=DIV_SYSTEM_NULL;
DivSystem hoveredSys=DIV_SYSTEM_NULL;
bool reissueSearch=false;
@ -61,10 +61,11 @@ DivSystem FurnaceGUI::systemPicker() {
}
}
}
if (ImGui::BeginTable("SysList",1,ImGuiTableFlags_ScrollY,ImVec2(500.0f*dpiScale,200.0*dpiScale))) {
if (ImGui::BeginTable("SysList",1,ImGuiTableFlags_ScrollY,ImVec2(fullWidth ? ImGui::GetContentRegionAvail().x : 500.0f*dpiScale,200.0f*dpiScale))) {
if (sysSearchQuery.empty()) {
// display chip list
for (int j=0; curSysSection[j]; j++) {
if (!settings.hiddenSystems && CHECK_HIDDEN_SYSTEM(curSysSection[j])) continue;
ImGui::TableNextRow();
ImGui::TableNextColumn();
if (ImGui::Selectable(e->getSystemName((DivSystem)curSysSection[j]),false,0,ImVec2(500.0f*dpiScale,0.0f))) ret=(DivSystem)curSysSection[j];
@ -75,6 +76,7 @@ DivSystem FurnaceGUI::systemPicker() {
} else {
// display search results
for (DivSystem i: sysSearchResults) {
if (!settings.hiddenSystems && CHECK_HIDDEN_SYSTEM(i)) continue;
ImGui::TableNextRow();
ImGui::TableNextColumn();
if (ImGui::Selectable(e->getSystemName(i),false,0,ImVec2(500.0f*dpiScale,0.0f))) ret=i;

View file

@ -31,6 +31,26 @@
#include <dirent.h>
#endif
#ifndef IS_MOBILE
#define CLICK_TO_OPEN(t) ImGui::TextColored(uiColors[GUI_COLOR_ACCENT_PRIMARY],t); \
if (ImGui::IsItemHovered()) { \
ImGui::SetTooltip("click to open"); \
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand); \
if (ImGui::IsMouseReleased(ImGuiMouseButton_Left)) { \
SDL_OpenURL(t); \
} \
} \
ImGui::SameLine(); \
ImGui::Text(ICON_FA_CLIPBOARD); \
if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayShort)) ImGui::SetTooltip("click to copy"); \
if (ImGui::IsItemClicked()) { \
ImGui::SetClipboardText(t); \
tutorial.popupTimer=0; \
}
#else
#define CLICK_TO_OPEN(t) ImGui::TextColored(uiColors[GUI_COLOR_ACCENT_PRIMARY],t); if (ImGui::IsItemClicked()) SDL_OpenURL(t);
#endif
enum FurnaceCVObjectTypes {
CV_NULL=0,
CV_PLAYER,
@ -505,11 +525,19 @@ void FurnaceGUI::syncTutorial() {
tutorial.introPlayed=e->getConfBool("tutIntroPlayed",false);
#endif
tutorial.protoWelcome=e->getConfBool("tutProtoWelcome2",false);
tutorial.importedMOD=e->getConfBool("tutImportedMOD",false);
tutorial.importedS3M=e->getConfBool("tutImportedS3M",false);
tutorial.importedXM=e->getConfBool("tutImportedXM",false);
tutorial.importedIT=e->getConfBool("tutImportedIT",false);
}
void FurnaceGUI::commitTutorial() {
e->setConf("tutIntroPlayed",tutorial.introPlayed);
e->setConf("tutProtoWelcome2",tutorial.protoWelcome);
e->setConf("tutImportedMOD",tutorial.importedMOD);
e->setConf("tutImportedS3M",tutorial.importedS3M);
e->setConf("tutImportedXM",tutorial.importedXM);
e->setConf("tutImportedIT",tutorial.importedIT);
}
void FurnaceGUI::initRandomDemoSong() {
@ -676,15 +704,23 @@ void FurnaceGUI::drawTutorial() {
"- click on the Orders matrix to change the patterns of a channel (left click increases; right click decreases)"
));
ImGui::Separator();
ImGui::TextWrapped(_(
"if you are new to trackers, you may check the quick start guide:"
));
CLICK_TO_OPEN("https://github.com/tildearrow/furnace/blob/master/doc/1-intro/quickstart.md")
ImGui::TextWrapped(_(
"if you need help, you may:\n"
"- read the manual (a file called manual.pdf)\n"
"- ask for help in Discussions (https://github.com/tildearrow/furnace/discussions)"
"- ask for help in Discussions"
));
CLICK_TO_OPEN("https://github.com/tildearrow/furnace/discussions")
ImGui::Separator();
ImGui::TextWrapped(_("if you find any issues, be sure to report them! the issue tracker is here: https://github.com/tildearrow/furnace/issues"));
ImGui::TextWrapped(_("if you find any issues, be sure to report them! the issue tracker is here:"));
CLICK_TO_OPEN("https://github.com/tildearrow/furnace/issues")
if (ImGui::Button(_("OK"))) {
tutorial.protoWelcome=true;
@ -696,6 +732,18 @@ void FurnaceGUI::drawTutorial() {
(canvasW-ImGui::GetWindowSize().x)*0.5,
(canvasH-ImGui::GetWindowSize().y)*0.5
));
if (tutorial.popupTimer<2.0f) {
ImDrawList* dl=ImGui::GetForegroundDrawList();
const ImVec2 winPos=ImGui::GetWindowPos();
const ImVec2 txtSize=ImGui::CalcTextSize("copied!");
const ImVec2 winSize=ImGui::GetWindowSize();
dl->AddText(ImVec2(
winPos.x+(winSize.x-txtSize.x)/2,
winPos.y+(winSize.y-txtSize.y*2)
),ImGui::ColorConvertFloat4ToU32(uiColors[GUI_COLOR_TOGGLE_ON]),"copied!");
tutorial.popupTimer+=ImGui::GetIO().DeltaTime;
}
ImGui::EndPopup();
}
@ -878,51 +926,6 @@ void FurnaceGUI::drawTutorial() {
}
}
// helper functions
void FurnaceGUI::highlightWindow(const char* winName) {
ImDrawList* dl=ImGui::GetWindowDrawList();
ImU32 col=ImGui::GetColorU32(uiColors[GUI_COLOR_MODAL_BACKDROP]);
ImGuiWindow* win=ImGui::FindWindowByName(winName);
if (win!=NULL) {
ImVec2 start=win->Pos;
ImVec2 end=ImVec2(
start.x+win->Size.x,
start.y+win->Size.y
);
dl->AddRectFilled(
ImVec2(0,0),
ImVec2(start.x,canvasH),
col
);
dl->AddRectFilled(
ImVec2(start.x,0),
ImVec2(canvasW,start.y),
col
);
dl->AddRectFilled(
ImVec2(end.x,start.y),
ImVec2(canvasW,canvasH),
col
);
dl->AddRectFilled(
ImVec2(start.x,end.y),
ImVec2(end.x,canvasH),
col
);
dl->AddRect(start,end,ImGui::GetColorU32(uiColors[GUI_COLOR_TEXT]),0,0,3.0f*dpiScale);
} else {
dl->AddRectFilled(
ImVec2(0,0),
ImVec2(canvasW,canvasH),
col
);
}
}
// CV
// 320x224

View file

@ -392,7 +392,7 @@ void FurnaceGUI::drawUserPresets() {
tempID=fmt::sprintf("%s##USystem",getSystemName(chip.sys));
ImGui::Button(tempID.c_str(),ImVec2(ImGui::GetContentRegionAvail().x-ImGui::CalcTextSize(_("Invert")).x-ImGui::GetFrameHeightWithSpacing()*2.0-ImGui::GetStyle().ItemSpacing.x*2.0,0));
if (ImGui::BeginPopupContextItem("SysPickerCU",ImGuiPopupFlags_MouseButtonLeft)) {
DivSystem picked=systemPicker();
DivSystem picked=systemPicker(false);
if (picked!=DIV_SYSTEM_NULL) {
chip.sys=picked;
mustBake=true;
@ -456,7 +456,7 @@ void FurnaceGUI::drawUserPresets() {
ImGui::Button(ICON_FA_PLUS "##SysAddU");
if (ImGui::BeginPopupContextItem("SysPickerU",ImGuiPopupFlags_MouseButtonLeft)) {
DivSystem picked=systemPicker();
DivSystem picked=systemPicker(false);
if (picked!=DIV_SYSTEM_NULL) {
preset->orig.push_back(FurnaceGUISysDefChip(picked,1.0f,0.0f,""));
mustBake=true;
@ -475,7 +475,8 @@ void FurnaceGUI::drawUserPresets() {
ImGui::SetTooltip(_(
"insert additional settings in `option=value` format.\n"
"available options:\n"
"- tickRate"
"- tickRate \n"
"- chanMask \n"
));
}

View file

@ -112,3 +112,127 @@ String getKeyName(int key, bool emptyNone) {
}
return ret;
}
double sinus(double x) {
return sin(x);
}
double rectSin(double x) {
return sin(x) > 0 ? sin(x) : 0;
}
double absSin(double x) {
return fabs(sin(x));
}
double square(double x) {
return fmod(x, (2 * M_PI)) >= M_PI ? -1 : 1;
}
double rectSquare(double x) {
return square(x) > 0 ? square(x) : 0;
}
double quartSin(double x) {
return absSin(x) * rectSquare(2 * x);
}
double squiSin(double x) {
return sin(x) >= 0 ? sin(2 * x) : 0;
}
double squiAbsSin(double x) {
return fabs(squiSin(x));
}
double saw(double x) {
return atan(tan(x / 2)) / (M_PI / 2);
}
double rectSaw(double x) {
return saw(x) > 0 ? saw(x) : 0;
}
double absSaw(double x) {
return saw(x) < 0 ? saw(x) + 1 : saw(x);
}
double cubSaw(double x) {
return pow(saw(x), 3);
}
double rectCubSaw(double x) {
return pow(rectSaw(x), 3);
}
double absCubSaw(double x) {
return pow(absSaw(x), 3);
}
double cubSine(double x) {
return pow(sin(x), 3);
}
double rectCubSin(double x) {
return pow(rectSin(x), 3);
}
double absCubSin(double x) {
return pow(absSin(x), 3);
}
double quartCubSin(double x) {
return pow(quartSin(x), 3);
}
double squishCubSin(double x) {
return pow(squiSin(x), 3);
}
double squishAbsCubSin(double x) {
return pow(squiAbsSin(x), 3);
}
double triangle(double x) {
return asin(sin(x)) / (M_PI / 2);
}
double rectTri(double x) {
return triangle(x) > 0 ? triangle(x) : 0;
}
double absTri(double x) {
return fabs(triangle(x));
}
double quartTri(double x) {
return absTri(x) * rectSquare(2 * x);
}
double squiTri(double x) {
return sin(x) >= 0 ? triangle(2 * x) : 0;
}
double absSquiTri(double x) {
return fabs(squiTri(x));
}
double cubTriangle(double x) {
return pow(triangle(x), 3);
}
double cubRectTri(double x) {
return pow(rectTri(x), 3);
}
double cubAbsTri(double x) {
return pow(absTri(x), 3);
}
double cubQuartTri(double x) {
return pow(quartTri(x), 3);
}
double cubSquiTri(double x) {
return pow(squiTri(x), 3);
}
double absCubSquiTri(double x) {
return fabs(cubSquiTri(x));
}
String getMultiKeysName(const int* keys, int keyCount, bool emptyNone) {
String ret;
for (int i=0; i<keyCount; i++) {
if (keys[i]==0) continue;
if (!ret.empty()) ret+=", ";
ret+=getKeyName(keys[i]);
}
if (ret.empty()) {
if (emptyNone) {
return "";
} else {
return _("<nothing>");
}
} else {
return ret;
}
}

View file

@ -30,4 +30,39 @@
#endif
String getHomeDir();
String getKeyName(int key, bool emptyNone=false);
String getKeyName(int key, bool emptyNone=false);
double sinus(double x);
double rectSin(double x);
double absSin(double x);
double square(double x);
double rectSquare(double x);
double quartSin(double x);
double squiSin(double x);
double squiAbsSin(double x);
double saw(double x);
double rectSaw(double x);
double absSaw(double x);
double cubSaw(double x);
double rectCubSaw(double x);
double absCubSaw(double x);
double cubSine(double x);
double rectCubSin(double x);
double absCubSin(double x);
double quartCubSin(double x);
double squishCubSin(double x);
double squishAbsCubSin(double x);
double triangle(double x);
double rectTri(double x);
double absTri(double x);
double quartTri(double x);
double squiTri(double x);
double absSquiTri(double x);
double cubTriangle(double x);
double cubRectTri(double x);
double cubAbsTri(double x);
double cubQuartTri(double x);
double cubSquiTri(double x);
double absCubSquiTri(double x);
String getMultiKeysName(const int* keys, int keyCount, bool emptyNone=false);

View file

@ -19,6 +19,7 @@
#define _USE_MATH_DEFINES
#include "gui.h"
#include "util.h"
#include "plot_nolerp.h"
#include "IconsFontAwesome4.h"
#include "misc/cpp/imgui_stdlib.h"
@ -40,111 +41,6 @@ const char* waveInterpolations[4]={
_N("Cubic")
};
double sinus(double x) {
return sin(x);
}
double rectSin(double x) {
return sin(x) > 0 ? sin(x) : 0;
}
double absSin(double x) {
return fabs(sin(x));
}
double square(double x) {
return fmod(x, (2 * M_PI)) >= M_PI ? -1 : 1;
}
double rectSquare(double x) {
return square(x) > 0 ? square(x) : 0;
}
double quartSin(double x) {
return absSin(x) * rectSquare(2 * x);
}
double squiSin(double x) {
return sin(x) >= 0 ? sin(2 * x) : 0;
}
double squiAbsSin(double x) {
return fabs(squiSin(x));
}
double saw(double x) {
return atan(tan(x / 2)) / (M_PI / 2);
}
double rectSaw(double x) {
return saw(x) > 0 ? saw(x) : 0;
}
double absSaw(double x) {
return saw(x) < 0 ? saw(x) + 1 : saw(x);
}
double cubSaw(double x) {
return pow(saw(x), 3);
}
double rectCubSaw(double x) {
return pow(rectSaw(x), 3);
}
double absCubSaw(double x) {
return pow(absSaw(x), 3);
}
double cubSine(double x) {
return pow(sin(x), 3);
}
double rectCubSin(double x) {
return pow(rectSin(x), 3);
}
double absCubSin(double x) {
return pow(absSin(x), 3);
}
double quartCubSin(double x) {
return pow(quartSin(x), 3);
}
double squishCubSin(double x) {
return pow(squiSin(x), 3);
}
double squishAbsCubSin(double x) {
return pow(squiAbsSin(x), 3);
}
double triangle(double x) {
return asin(sin(x)) / (M_PI / 2);
}
double rectTri(double x) {
return triangle(x) > 0 ? triangle(x) : 0;
}
double absTri(double x) {
return fabs(triangle(x));
}
double quartTri(double x) {
return absTri(x) * rectSquare(2 * x);
}
double squiTri(double x) {
return sin(x) >= 0 ? triangle(2 * x) : 0;
}
double absSquiTri(double x) {
return fabs(squiTri(x));
}
double cubTriangle(double x) {
return pow(triangle(x), 3);
}
double cubRectTri(double x) {
return pow(rectTri(x), 3);
}
double cubAbsTri(double x) {
return pow(absTri(x), 3);
}
double cubQuartTri(double x) {
return pow(quartTri(x), 3);
}
double cubSquiTri(double x) {
return pow(squiTri(x), 3);
}
double absCubSquiTri(double x) {
return fabs(cubSquiTri(x));
}
typedef double (*WaveFunc) (double a);
WaveFunc waveFuncs[]={
@ -495,7 +391,7 @@ void FurnaceGUI::drawWaveEdit() {
ImGui::TableNextColumn();
ImGui::Text(_("Width"));
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(_("use a width of:\n- any on Amiga/N163\n- 32 on Game Boy, PC Engine, SCC, Konami Bubble System, Namco WSG, Virtual Boy and WonderSwan\n- 64 on FDS\n- 128 on X1-010\nany other widths will be scaled during playback."));
ImGui::SetTooltip(_("use a width of:\n- any on Amiga/N163\n- 32 on Game Boy, PC Engine, SCC, Konami Bubble System, Namco WSG, Virtual Boy and WonderSwan\n- 64 on FDS\n- 128 on X1-010\n- 256 on SID3\nany other widths will be scaled during playback."));
}
ImGui::SameLine();
ImGui::SetNextItemWidth(96.0f*dpiScale);
@ -509,7 +405,7 @@ void FurnaceGUI::drawWaveEdit() {
ImGui::SameLine();
ImGui::Text(_("Height"));
if (ImGui::IsItemHovered()) {
ImGui::SetTooltip(_("use a height of:\n- 16 for Game Boy, WonderSwan, Namco WSG, Konami Bubble System, X1-010 Envelope shape and N163\n- 32 for PC Engine\n- 64 for FDS and Virtual Boy\n- 256 for X1-010 and SCC\nany other heights will be scaled during playback."));
ImGui::SetTooltip(_("use a height of:\n- 16 for Game Boy, WonderSwan, Namco WSG, Konami Bubble System, X1-010 Envelope shape and N163\n- 32 for PC Engine\n- 64 for FDS and Virtual Boy\n- 256 for X1-010, SCC and SID3\nany other heights will be scaled during playback."));
}
ImGui::SameLine();
ImGui::SetNextItemWidth(96.0f*dpiScale);
@ -947,6 +843,7 @@ void FurnaceGUI::drawWaveEdit() {
wave->len=waveGenScaleX;
MARK_MODIFIED;
});
e->notifyWaveChange(curWave);
}
ImGui::TableNextRow();
@ -965,6 +862,7 @@ void FurnaceGUI::drawWaveEdit() {
wave->max=waveGenScaleY-1;
MARK_MODIFIED;
});
e->notifyWaveChange(curWave);
}
ImGui::TableNextRow();
@ -987,6 +885,7 @@ void FurnaceGUI::drawWaveEdit() {
}
MARK_MODIFIED;
});
e->notifyWaveChange(curWave);
}
ImGui::TableNextRow();
@ -1004,6 +903,7 @@ void FurnaceGUI::drawWaveEdit() {
}
MARK_MODIFIED;
});
e->notifyWaveChange(curWave);
}
ImGui::TableNextRow();
@ -1030,6 +930,7 @@ void FurnaceGUI::drawWaveEdit() {
}
MARK_MODIFIED;
});
e->notifyWaveChange(curWave);
}
ImGui::TableNextRow();
@ -1049,6 +950,7 @@ void FurnaceGUI::drawWaveEdit() {
}
MARK_MODIFIED;
});
e->notifyWaveChange(curWave);
}
ImGui::EndTable();
@ -1092,6 +994,7 @@ void FurnaceGUI::drawWaveEdit() {
}
MARK_MODIFIED;
});
e->notifyWaveChange(curWave);
}
if (ImGui::Button(_("Invert"),buttonSizeHalf)) {
e->lockEngine([this,wave]() {
@ -1100,6 +1003,7 @@ void FurnaceGUI::drawWaveEdit() {
}
MARK_MODIFIED;
});
e->notifyWaveChange(curWave);
}
ImGui::SameLine();
if (ImGui::Button(_("Reverse"),buttonSizeHalf)) {
@ -1112,6 +1016,7 @@ void FurnaceGUI::drawWaveEdit() {
}
MARK_MODIFIED;
});
e->notifyWaveChange(curWave);
}
if (ImGui::Button(_("Half"),buttonSizeHalf)) {
@ -1121,6 +1026,7 @@ void FurnaceGUI::drawWaveEdit() {
for (int i=0; i<wave->len; i++) {
wave->data[i]=origData[i>>1];
}
e->notifyWaveChange(curWave);
MARK_MODIFIED;
}
ImGui::SameLine();
@ -1131,6 +1037,7 @@ void FurnaceGUI::drawWaveEdit() {
for (int i=0; i<wave->len; i++) {
wave->data[i]=origData[(i*2)%wave->len];
}
e->notifyWaveChange(curWave);
MARK_MODIFIED;
}
@ -1145,6 +1052,7 @@ void FurnaceGUI::drawWaveEdit() {
}
MARK_MODIFIED;
});
e->notifyWaveChange(curWave);
}
if (ImGui::Button(_("Randomize"),buttonSize)) {
if (wave->max>0) e->lockEngine([this,wave]() {
@ -1153,6 +1061,7 @@ void FurnaceGUI::drawWaveEdit() {
}
MARK_MODIFIED;
});
e->notifyWaveChange(curWave);
}
ImGui::EndTabItem();
}