Developer information and guides - tildearrow/furnace GitHub Wiki
initial notes:
- this page is a work in progress!
- the guides assume you are familiar with C++, have an environment set up and can compile furnace from source
- the guides are (currently) written by one person, with minimal proof-checking
expand...
Files to modify:CMakeLists.txt
src/gui/doAction.cpp
src/gui/gui.cpp
src/gui/gui.h
src/gui/guiConst.cpp
- create a C++ source file in
src/gui/
for your window (e.g.src/gui/myWindow.cpp
) - add that file to the CMakeLists (starting from line 889, in alphabetical order with the other windows)
...
src/gui/mixer.cpp
src/gui/midiMap.cpp
+ src/gui/myWindow.cpp
src/gui/newSong.cpp
...
-
in
src/gui/gui.h
:- add an entry for your window in the
FurnaceGUIWindows
enum (at the end, beforeGUI_WINDOW_SPOILER
)
... GUI_WINDOW_CS_PLAYER, GUI_WINDOW_USER_PRESETS, + GUI_WINDOW_MY_WINDOW, GUI_WINDOW_SPOILER }; ...
- add an entry for your window in the
FurnaceGUIActions
enum (lastGUI_ACTION_WINDOW_*
, beforeGUI_ACTION_COLLAPSE_WINDOW
)
... GUI_ACTION_WINDOW_CS_PLAYER, GUI_ACTION_WINDOW_USER_PRESETS, + GUI_ACTION_WINDOW_MY_WINDOW, GUI_ACTION_COLLAPSE_WINDOW, ...
- add a boolean for the window open state in the
FuranceGUI
class (last*Open
)
... bool subSongsOpen, findOpen, spoilerOpen, patManagerOpen, sysManagerOpen, clockOpen, speedOpen; - bool groovesOpen, xyOscOpen, memoryOpen, csPlayerOpen, cvOpen, userPresetsOpen; + bool groovesOpen, xyOscOpen, memoryOpen, csPlayerOpen, cvOpen, userPresetsOpen, myWindowOpen; ...
- add the function declaration for drawing your window in the
FurnaceGUI
class (lastvoid draw*()
, beforevoid drawSystemChannelInfo(const DivSysDef* whichDef);
)
... void drawXYOsc(); void drawUserPresets(); + void drawMyWindow(); void drawSystemChannelInfo(const DivSysDef* whichDef); ...
- add an entry for your window in the
-
in
src/gui/doAction.cpp
:- add the case for your window's action in the switch in the
doAction(int what)
function (lastcase GUI_ACTION_WINDOW_*
, beforecase GUI_ACTION_COLLAPSE_WINDOW
)
... case GUI_ACTION_WINDOW_USER_PRESETS: nextWindow=GUI_WINDOW_USER_PRESETS; break; + case GUI_ACTION_WINDOW_MY_WINDOW: + nextWindow=GUI_WINDOW_MY_WINDOW; + break; case GUI_ACTION_COLLAPSE_WINDOW: ...
- add the case for the action of closing your window in the same function but in the nested switch for the
GUI_ACTION_CLOSE_WINDOW
case (lastcase GUI_WINDOW_*
, beforedefault:
)
... case GUI_ACTION_CLOSE_WINDOW: switch (curWindow) { ... case GUI_WINDOW_USER_PRESETS: userPresetsOpen=false; break; + case GUI_WINDOW_MY_WINDOW: + myWindowOpen=false; + break; default: ...
- add the case for your window's action in the switch in the
-
in
src/gui/guiConst.cpp
:- add the action definition for your window in the
guiAction[GUI_ACTION_MAX]
array (lastD("WINDOW_*
, beforeD("COLLAPSE_WINDOW...
)
... D("WINDOW_USER_PRESETS", _N("User Presets"), 0), + D("WINDOW_MY_WINDOW", _N("My Window"), 0), D("COLLAPSE_WINDOW", _N("Collapse/expand current window"), 0), ...
note:
- the last parameter of the
D()
macro defines the default keyboard bind for the window. its an integer in theMODIFIER_KEY|REGUALR_KEY
format, whereMODIFIER_KEY
is aFURKMOD_*
macro andREGULAR_KEY
is an enumerator fromSDL_KeyCode
. for example - Ctrl+Z isFURKMOD_CMD|SDL_z
.
- add the action definition for your window in the
-
in
src/gui/gui.cpp
:- declare your window's metric in the
loop()
function (lastDECLARE_METRIC(*)
, beforeDECLARE_METRIC(popup)
)
... DECLARE_METRIC(userPresets) + DECLARE_METRIC(myWindow) DECLARE_METRIC(popup) ...
- add the
IMPORT_CLOSE
for your window in theif (pendingLayoutImportStep==0) {
statement (lastIMPORT_CLOSE(*)
)
... IMPORT_CLOSE(csPlayerOpen); IMPORT_CLOSE(userPresetsOpen); + IMPORT_CLOSE(myWindowOpen); } else if (pendingLayoutImportStep==1) { ...
- add the menu item for your window
notes:- depending on the type of your window, this may be optional, even unnecessary
- the location of the menu item also may depend on your window type. for this guide the entry is added at the end, in the "window" menu, not belonging to any category)
... if (ImGui::MenuItem(_("piano/input pad"),BIND_FOR(GUI_ACTION_WINDOW_PIANO),pianoOpen)) pianoOpen=!pianoOpen; + if (ImGui::MenuItem(_("my window"),BIND_FOR(GUI_ACTION_MY_WINDOW),myWindowOpen)) myWindowOpen=!myWindowOpen; if (spoilerOpen) if (ImGui::MenuItem(_("spoiler"),NULL,spoilerOpen)) spoilerOpen=!spoilerOpen; ...
- add the metric measurement macros for your window (last
MEASURE(*)
)
note for mobile ui:- if your window belongs to a scene, the
MEASURE(...)
statement should be inside the case for that scene. for this guide, it does not
- if your window belongs to a scene, the
... if (mobileUI) { ... MEASURE(userPresets,drawUserPresets()); MEASURE(patManager,drawPatManager()); + MEASURE(myWindow,drawMyWindow()); ...
... } else { ... MEASURE(effectList,drawEffectList()); MEASURE(userPresets,drawUserPresets()); + MEASURE(myWindow,drawMyWindow()); ...
- get the state of your window from the config (last
*Open=e->getConfBool("*Open",false);
... spoilerOpen=e->getConfBool("spoilerOpen",false); userPresetsOpen=e->getConfBool("userPresetsOpen",false); + myWindowOpen=e->getConfBool("myWindowOpen",false); ...
- set the state of your window in the config (last
conf.set("*Open",*Open);
)
... conf.set("spoilerOpen",spoilerOpen); conf.set("userPresetsOpen",userPresetsOpen); + conf.set("myWindowOpen",myWindowOpen); ...
- initialize the bool for your window state in the class constructor (last
*Open(...)
)
... cvOpen(false), userPresetsOpen(false), + myWindowOpen(false), ...
- declare your window's metric in the
-
in
src/gui/myWindow.cpp
:- add this template code for a generic Furnace window
/** * Furnace Tracker - multi-system chiptune tracker * Copyright (C) 2021-2025 tildearrow and contributors * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "gui.h" void FurnaceGUI::drawMyWindow() { if (nextWindow==GUI_WINDOW_MY_WINDOW) { myWindowOpen=true; ImGui::SetNextWindowFocus(); nextWindow=GUI_WINDOW_NOTHING; } if (!myWindowOpen) return; if (ImGui::Begin("My Window",&myWindowOpen,globalWinFlags,_("My Window"))) { // your window code here } if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_MY_WINDOW; ImGui::End(); }
- modify it to add your stuff!
expand...
Files to modify:
src/engine/dispatch.h
src/engine/playback.cpp
src/engine/sysDef.h
src/gui/guiConst.cpp
- the engine platform file(s) for the chip(s) the effect applies to
- in
src/engine/dispatch.h
, add the dispatch command for the effect in theDivDispatchCmds
enum (lastDIV_CMD_*
, beforeDIV_CMD_MAX
)
...
DIV_CMD_FM_FMS2,
DIV_CMD_FM_AMS2,
+
+ DIV_CMD_MY_EFFECT,
DIV_CMD_MAX
...
- in
src/engine/playback.cpp
, add the name of the new dispatch command in thecmdName
array
...
"FM_FMS2",
"FM_AMS2",
+
+ "MY_EFFECT"
};
...
-
pick an appropriate effect number (TODO: elaborate?)
-
in
src/engine/sysDef.h
, add the effect handler map in the system definition(s) of the chip(s).an effect handler map is defined as
std::unordered_map<unsigned char,const EffectHandler> EffectHandlerMap
e.g.
{0x10, {DIV_CMD_MY_EFFECT, "10xx: does my effect"}}
note:
- if the chip has many effects which are shared with many other chps, the vector of the handler maps may be defined externally (e.g.
fmOPNPostEffectHandlerMap
)
a typical effects definition in a system definition may look like
- if the chip has many effects which are shared with many other chps, the vector of the handler maps may be defined externally (e.g.
...
{
+ {0x10, {DIV_CMD_MY_EFFECT, "10xx: does my effect"}},
}
...
-
in the dispatch platform code of the chip(s), in the
dispatch(DivCommand c)
function, add the case for the effect's command.this really depends on the chip, and the effect.
a typical case may look like:
...
case DIV_CMD_MACRO_ON:
chan[c.chan].std.mask(c.value,false);
break;
+ case DIV_CMD_MY_EFFECT:
+ chan[c.chan].myParameter=c.value;
+ // extra command handling code here...
+ break;
default:
break;
...
-
if the effect has an undesired color, in
src/gui/guiConst.cpp
, change it in thefxColors
arraythis step may not be necessary, as some effect number ranges have valid colors