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.txtsrc/gui/doAction.cppsrc/gui/gui.cppsrc/gui/gui.hsrc/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
FurnaceGUIWindowsenum (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
FurnaceGUIActionsenum (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
FuranceGUIclass (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
FurnaceGUIclass (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_WINDOWcase (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_KEYformat, whereMODIFIER_KEYis aFURKMOD_*macro andREGULAR_KEYis 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_CLOSEfor 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.hsrc/engine/playback.cppsrc/engine/sysDef.hsrc/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 theDivDispatchCmdsenum (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 thecmdNamearray
...
"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> EffectHandlerMape.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 thefxColorsarraythis step may not be necessary, as some effect number ranges have valid colors
expand...
note: this guide will not teach you how to write an emulator
Files to modify:
CMakeLists.txtsrc/engine/dispatch.cppsrc/engine/dispatchContainer.cppsrc/engine/song.hsrc/engine/sysDef.cppsrc/gui/guiConst.cppsrc/gui/presets.cpp- (optional)
src/gui/sysConf.cpp - (optional)
src/gui/sysMiscInfo.cpp
- Add the source code for your sound chip's emulation in
src/engine/platform/sound/for your sound chip. If it's meant to be a folder, add the original folder with the source code. - The easiest way of making a DivDispatch is to copy
src/engine/platform/pce.cppand rename the duplicate to something likenamehere.cpp. Rename the duplicatedDivPlatformPCEto something different that starts withDivPlatform. - Update
src/ending/song.hto add the chip name as starting withDIV_SYSTEM_. - Update
src/engine/dispatch.cppto support the new chip and create the instance of the new DivDispatch when used. - Update
src/engine/sysDef.cppto define the new chip, its channels, effects, and description. - Update
src/gui/guiConst.cppto add the new chip to the list, and updatesrc/gui/presets.cppas well.