diff --git a/CMakeLists.txt b/CMakeLists.txt index bcf8a731..ffd3e63b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,7 +34,7 @@ endif () if (WIN32) set(BONZOMATIC_WINDOWS_FLAVOR "GLFW" CACHE STRING "Windows renderer flavor selected at CMake configure time (DX11, DX9 or GLFW)") - set_property(CACHE BONZOMATIC_WINDOWS_FLAVOR PROPERTY STRINGS DX11 DX9 GLFW) + set_property(CACHE BONZOMATIC_WINDOWS_FLAVOR PROPERTY STRINGS DX11 DX9 GLFW) endif () if (NOT (UNIX AND (NOT APPLE))) #if not linux @@ -151,7 +151,7 @@ set(BZC_PROJECT_LIBS ${BZC_PROJECT_LIBS} bzc_jsonxx) ############################################################################## # NDI if (WIN32 AND BONZOMATIC_NDI) - if(DEFINED ENV{NDI_SDK_DIR}) + if(DEFINED ENV{NDI_SDK_DIR}) set(NDI_SDK_DIR "$ENV{NDI_SDK_DIR}") else() message(FATAL_ERROR "Could not find NDI SDK. The NDI_SDK_DIR environment variable must be set to the SDK path.") @@ -330,6 +330,8 @@ if (APPLE) set(BZC_PLATFORM_SRCS ${CMAKE_SOURCE_DIR}/src/platform_glfw/Renderer.cpp ${CMAKE_SOURCE_DIR}/src/platform_common/FFT.cpp + ${CMAKE_SOURCE_DIR}/src/platform_common/Utils.cpp + ${CMAKE_SOURCE_DIR}/src/platform_common/Config.cpp ${CMAKE_SOURCE_DIR}/src/platform_x11/MIDI.cpp ${CMAKE_SOURCE_DIR}/src/platform_osx/Misc.mm ${CMAKE_SOURCE_DIR}/src/platform_x11/Timer.cpp @@ -346,11 +348,13 @@ elseif (UNIX) set(BZC_PLATFORM_SRCS ${CMAKE_SOURCE_DIR}/src/platform_glfw/Renderer.cpp ${CMAKE_SOURCE_DIR}/src/platform_common/FFT.cpp + ${CMAKE_SOURCE_DIR}/src/platform_common/Utils.cpp ${CMAKE_SOURCE_DIR}/src/platform_x11/MIDI.cpp ${CMAKE_SOURCE_DIR}/src/platform_x11/Misc.cpp ${CMAKE_SOURCE_DIR}/src/platform_x11/SetupDialog.cpp ${CMAKE_SOURCE_DIR}/src/platform_x11/Timer.cpp ${CMAKE_SOURCE_DIR}/src/platform_x11/Clipboard.cpp + ${CMAKE_SOURCE_DIR}/src/platform_linux/Config.cpp ) source_group("Bonzomatic\\Platform" FILES ${BZC_PLATFORM_SRCS}) elseif (WIN32) @@ -358,6 +362,8 @@ elseif (WIN32) set(BZC_PLATFORM_SRCS ${CMAKE_SOURCE_DIR}/src/platform_w32_dx11/Renderer.cpp ${CMAKE_SOURCE_DIR}/src/platform_common/FFT.cpp + ${CMAKE_SOURCE_DIR}/src/platform_common/Utils.cpp + ${CMAKE_SOURCE_DIR}/src/platform_common/Config.cpp ${CMAKE_SOURCE_DIR}/src/platform_w32_common/MIDI.cpp ${CMAKE_SOURCE_DIR}/src/platform_w32_common/Misc.cpp ${CMAKE_SOURCE_DIR}/src/platform_w32_common/SetupDialog.cpp @@ -369,6 +375,8 @@ elseif (WIN32) set(BZC_PLATFORM_SRCS ${CMAKE_SOURCE_DIR}/src/platform_w32_dx9/Renderer.cpp ${CMAKE_SOURCE_DIR}/src/platform_common/FFT.cpp + ${CMAKE_SOURCE_DIR}/src/platform_common/Utils.cpp + ${CMAKE_SOURCE_DIR}/src/platform_common/Config.cpp ${CMAKE_SOURCE_DIR}/src/platform_w32_common/MIDI.cpp ${CMAKE_SOURCE_DIR}/src/platform_w32_common/Misc.cpp ${CMAKE_SOURCE_DIR}/src/platform_w32_common/SetupDialog.cpp @@ -380,6 +388,8 @@ elseif (WIN32) set(BZC_PLATFORM_SRCS ${CMAKE_SOURCE_DIR}/src/platform_glfw/Renderer.cpp ${CMAKE_SOURCE_DIR}/src/platform_common/FFT.cpp + ${CMAKE_SOURCE_DIR}/src/platform_common/Utils.cpp + ${CMAKE_SOURCE_DIR}/src/platform_common/Config.cpp ${CMAKE_SOURCE_DIR}/src/platform_w32_common/MIDI.cpp ${CMAKE_SOURCE_DIR}/src/platform_w32_common/Misc.cpp ${CMAKE_SOURCE_DIR}/src/platform_w32_common/SetupDialog.cpp diff --git a/README.md b/README.md index f670baf2..b7d1c059 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,8 @@ On recent macOS, to allow sound input to be captured (for FFT textures to be gen ## Configuration You can configure Bonzomatic by creating a `config.json` and placing it next to the binary executable you're planning to run in the working directory for the binary; Bonzomatic will helpfully print this directory out for you when you run it, and you can also pass a file (with absolute or relative path, whichever you want) to load any other file as `config.json`. This allows you to have multiple configurations for multiple situations. +On Linux this file will be searched, in this order, in the executable directory, then in `~/.config/bonzomatic`, and finally in `/etc/bonzomatic`. Data (textures and last shader) will then be searched in a directory relative to where the `config.json` file was found. The only exception is if `config.json` is found in `/etc/bonzomatic` then data will be searched in `/usr/share/bonzomatic`. + The file can have the following contents: (all fields are optional) ``` javascript { diff --git a/config.json b/config.json new file mode 100644 index 00000000..02a3ec1b --- /dev/null +++ b/config.json @@ -0,0 +1,41 @@ +{ + "window":{ // default window size / state, if there's a setup dialog, it will override it + "width":1920, + "height":1080, + "fullscreen":true, + }, +// "font":{ // all paths in the file are also relative to the binary, but again, can be absolute paths if that's more convenient +// "file":"Input-Regular_(InputMono-Medium).ttf", +// "size":16, +// }, + "rendering":{ + "fftSmoothFactor": 0.9, // 0.0 means there's no smoothing at all, 1.0 means the FFT is completely smoothed flat + "fftAmplification": 1.0, // 1.0 means no change, larger values will result in brighter/stronger bands, smaller values in darker/weaker ones + }, + "textures":{ // the keys below will become the shader variable names + "texChecker":"textures/checker.png", + "texNoise":"textures/noise.png", + "texTex1":"textures/tex1.jpg", + }, + "gui":{ + "outputHeight": 200, + "opacity": 192, // 255 means the editor occludes the effect completely, 0 means the editor is fully transparent + "texturePreviewWidth": 128, + "spacesForTabs": false, + "tabSize": 8, + "visibleWhitespace": false, + "autoIndent": "smart", // can be "none", "preserve" or "smart" + }, + "midi":{ // the keys below will become the shader variable names, the values are the CC numbers + "fMidiKnob": 16, // e.g. this would be CC#16, i.e. by default the leftmost knob on a nanoKONTROL 2 + }, + // this section is if you want to enable NDI streaming; otherwise just ignore it + "ndi":{ + "enabled": true, + "connectionString": "", // metadata sent to the receiver; completely optional + "identifier": "hello!", // additional string to the device name; helps source discovery/identification in the receiver if there are multiple sources on the network + "frameRate": 60.0, // frames per second + "progressive": true, // progressive or interleaved? + }, + "postExitCmd":"copy_to_dropbox.bat" // this command gets ran when you quit Bonzomatic, and the shader filename gets passed to it as first parameter. Use this to take regular backups. +} \ No newline at end of file diff --git a/src/Config.h b/src/Config.h new file mode 100644 index 00000000..15dcab7a --- /dev/null +++ b/src/Config.h @@ -0,0 +1,61 @@ +#pragma once + +#include "jsonxx.h" + +#include "Utils.h" + +#include + +namespace Config +{ + + class ApplicationSettings + { + public: + enum Location + { + LOC_SYSTEM, + LOC_USER, + LOC_DIRECTORY, + LOC_NONE + }; + + ApplicationSettings( const std::string& cfg_filename ) + : location_( LOC_NONE ), config_filename_( cfg_filename ) {} + + void load() + { + find_location(); + if( location_ != LOC_NONE ) + { + read_config(); + } + } + + const jsonxx::Object get_options() const { return options_; } + const std::string get_config_directory_() const { return config_directory_; } + const std::string get_data_directory() const { return data_directory_; } + + private: + void find_location(); + void read_config() + { + std::string config_path( config_directory_ + "/" + config_filename_ ); + std::vector< char > data; + Utils::read_file( config_path, data ); + options_.parse( &data[ 0 ] ); + } + + Location location_; + std::string config_filename_; + jsonxx::Object options_; + + std::string config_directory_; + std::string data_directory_; + }; + + + + + +} \ No newline at end of file diff --git a/src/Utils.h b/src/Utils.h new file mode 100644 index 00000000..3fb3a120 --- /dev/null +++ b/src/Utils.h @@ -0,0 +1,11 @@ +#pragma once + +#include +#include + +namespace Utils +{ + + bool read_file( const std::string& filename, std::vector< char >& out ); + +} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 335adde7..4d542ac3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,6 +19,11 @@ #include "jsonxx.h" #include "Capture.h" +// #FLO +#include "Config.h" + +#include + void ReplaceTokens( std::string &sDefShader, const char * sTokenBegin, const char * sTokenName, const char * sTokenEnd, std::vector &tokens ) { if (sDefShader.find(sTokenBegin) != std::string::npos @@ -48,38 +53,24 @@ void ReplaceTokens( std::string &sDefShader, const char * sTokenBegin, const cha } } + + + + int main(int argc, const char *argv[]) { Misc::PlatformStartup(); - const char * configFile = "config.json"; + std::string cfg_file_name( "config.json" ); + if ( argc > 1 ) { - configFile = argv[ 1 ]; - printf( "Loading config file '%s'...\n", configFile ); - } - else - { - char configPath[ 256 ] = { 0 }; - if ( getcwd( configPath, 255 ) ) - { - printf( "Looking for config.json in '%s'...\n", configPath ); - } + cfg_file_name = argv[ 1 ]; } + std::cout << "Loading config file '" << cfg_file_name << "'...\n"; - jsonxx::Object options; - FILE * fConf = fopen( configFile, "rb" ); - if (fConf) - { - printf("Config file found, parsing...\n"); - - char szConfig[65535]; - memset( szConfig, 0, 65535 ); - fread( szConfig, 1, 65535, fConf ); - fclose(fConf); - - options.parse( szConfig ); - } + Config::ApplicationSettings app_settings( cfg_file_name ); + app_settings.load(); RENDERER_SETTINGS settings; settings.bVsync = false; @@ -91,14 +82,15 @@ int main(int argc, const char *argv[]) settings.nWidth = 1920; settings.nHeight = 1080; settings.windowMode = RENDERER_WINDOWMODE_FULLSCREEN; - if (options.has("window")) + jsonxx::Object opts = app_settings.get_options(); + if (opts.has("window")) { - if (options.get("window").has("width")) - settings.nWidth = options.get("window").get("width"); - if (options.get("window").has("height")) - settings.nHeight = options.get("window").get("height"); - if (options.get("window").has("fullscreen")) - settings.windowMode = options.get("window").get("fullscreen") ? RENDERER_WINDOWMODE_FULLSCREEN : RENDERER_WINDOWMODE_WINDOWED; + if (opts.get("window").has("width")) + settings.nWidth = opts.get("window").get("width"); + if (opts.get("window").has("height")) + settings.nHeight = opts.get("window").get("height"); + if (opts.get("window").has("fullscreen")) + settings.windowMode = opts.get("window").get("fullscreen") ? RENDERER_WINDOWMODE_FULLSCREEN : RENDERER_WINDOWMODE_WINDOWED; } if (!Renderer::OpenSetupDialog( &settings )) return -1; @@ -150,40 +142,43 @@ int main(int argc, const char *argv[]) std::string sPostExitCmd; - if (!options.empty()) + if (!opts.empty()) { - if (options.has("rendering")) + if (opts.has("rendering")) { - if (options.get("rendering").has("fftSmoothFactor")) - fFFTSmoothingFactor = options.get("rendering").get("fftSmoothFactor"); - if (options.get("rendering").has("fftAmplification")) - FFT::fAmplification = options.get("rendering").get("fftAmplification"); + if (opts.get("rendering").has("fftSmoothFactor")) + fFFTSmoothingFactor = opts.get("rendering").get("fftSmoothFactor"); + if (opts.get("rendering").has("fftAmplification")) + FFT::fAmplification = opts.get("rendering").get("fftAmplification"); } - if (options.has("textures")) + if (opts.has("textures")) { printf("Loading textures...\n"); - std::map tex = options.get("textures").kv_map(); + std::map tex = opts.get("textures").kv_map(); for (std::map::iterator it = tex.begin(); it != tex.end(); it++) { - const char * fn = it->second->string_value_->c_str(); - printf("* %s...\n",fn); - Renderer::Texture * tex = Renderer::CreateRGBA8TextureFromFile( fn ); + //const char * fn = it->second->string_value_->c_str(); + std::string file_name = app_settings.get_data_directory() + std::string( "/" ) + std::string( it->second->string_value_->c_str() ); + //printf("* %s...\n",fn); + std::cout << file_name << "...\n"; + //Renderer::Texture * tex = Renderer::CreateRGBA8TextureFromFile( fn ); + Renderer::Texture * tex = Renderer::CreateRGBA8TextureFromFile( file_name.c_str() ); if (!tex) { - printf("Renderer::CreateRGBA8TextureFromFile(%s) failed\n",fn); + printf("Renderer::CreateRGBA8TextureFromFile(%s) failed\n",file_name.c_str()); return -1; } textures[it->first] = tex; } } - if (options.has("font")) + if (opts.has("font")) { - if (options.get("font").has("size")) - editorOptions.nFontSize = options.get("font").get("size"); - if (options.get("font").has("file")) + if (opts.get("font").has("size")) + editorOptions.nFontSize = opts.get("font").get("size"); + if (opts.get("font").has("file")) { - std::string fontpath = options.get("font").get("file"); + std::string fontpath = opts.get("font").get("file"); if (!Misc::FileExists(fontpath.c_str())) { printf("Font path (%s) is invalid!\n", fontpath.c_str()); @@ -192,23 +187,23 @@ int main(int argc, const char *argv[]) editorOptions.sFontPath = fontpath; } } - if (options.has("gui")) + if (opts.has("gui")) { - if (options.get("gui").has("outputHeight")) - nDebugOutputHeight = options.get("gui").get("outputHeight"); - if (options.get("gui").has("texturePreviewWidth")) - nTexPreviewWidth = options.get("gui").get("texturePreviewWidth"); - if (options.get("gui").has("opacity")) - editorOptions.nOpacity = options.get("gui").get("opacity"); - if (options.get("gui").has("spacesForTabs")) - editorOptions.bUseSpacesForTabs = options.get("gui").get("spacesForTabs"); - if (options.get("gui").has("tabSize")) - editorOptions.nTabSize = options.get("gui").get("tabSize"); - if (options.get("gui").has("visibleWhitespace")) - editorOptions.bVisibleWhitespace = options.get("gui").get("visibleWhitespace"); - if (options.get("gui").has("autoIndent")) + if (opts.get("gui").has("outputHeight")) + nDebugOutputHeight = opts.get("gui").get("outputHeight"); + if (opts.get("gui").has("texturePreviewWidth")) + nTexPreviewWidth = opts.get("gui").get("texturePreviewWidth"); + if (opts.get("gui").has("opacity")) + editorOptions.nOpacity = opts.get("gui").get("opacity"); + if (opts.get("gui").has("spacesForTabs")) + editorOptions.bUseSpacesForTabs = opts.get("gui").get("spacesForTabs"); + if (opts.get("gui").has("tabSize")) + editorOptions.nTabSize = opts.get("gui").get("tabSize"); + if (opts.get("gui").has("visibleWhitespace")) + editorOptions.bVisibleWhitespace = opts.get("gui").get("visibleWhitespace"); + if (opts.get("gui").has("autoIndent")) { - std::string autoIndent = options.get("gui").get("autoIndent"); + std::string autoIndent = opts.get("gui").get("autoIndent"); if (autoIndent == "smart") { editorOptions.eAutoIndent = aitSmart; } else if (autoIndent == "preserve") { @@ -218,19 +213,19 @@ int main(int argc, const char *argv[]) } } } - if (options.has("midi")) + if (opts.has("midi")) { - std::map tex = options.get("midi").kv_map(); + std::map tex = opts.get("midi").kv_map(); for (std::map::iterator it = tex.begin(); it != tex.end(); it++) { midiRoutes[it->second->number_value_] = it->first; } } - if (options.has("postExitCmd")) + if (opts.has("postExitCmd")) { - sPostExitCmd = options.get("postExitCmd"); + sPostExitCmd = opts.get("postExitCmd"); } - Capture::LoadSettings( options ); + Capture::LoadSettings( opts ); } if (!editorOptions.sFontPath.size()) { @@ -250,7 +245,8 @@ int main(int argc, const char *argv[]) bool shaderInitSuccessful = false; char szShader[65535]; char szError[4096]; - FILE * f = fopen(Renderer::defaultShaderFilename,"rb"); + std::string defaultShaderFilePath = app_settings.get_data_directory() + std::string("/") + std::string(Renderer::defaultShaderFilename); + FILE * f = fopen( defaultShaderFilePath.c_str(),"rb" ); if (f) { printf("Loading last shader...\n"); @@ -389,7 +385,7 @@ int main(int argc, const char *argv[]) mDebugOutput.SetText( szError ); } } - else if (Renderer::keyEventBuffer[i].scanCode == 292 || (Renderer::keyEventBuffer[i].ctrl && Renderer::keyEventBuffer[i].scanCode == 'f')) // F11 or Ctrl/Cmd-f + else if (Renderer::keyEventBuffer[i].scanCode == 292 || (Renderer::keyEventBuffer[i].ctrl && Renderer::keyEventBuffer[i].scanCode == 'f')) // F11 or Ctrl/Cmd-f { bShowGui = !bShowGui; } diff --git a/src/platform_common/Config.cpp b/src/platform_common/Config.cpp new file mode 100644 index 00000000..8d62a6a2 --- /dev/null +++ b/src/platform_common/Config.cpp @@ -0,0 +1,38 @@ +#include "Config.h" + +#include +#include + +#ifdef _WIN32 +#include +#define getcwd _getcwd +#else +#include +#endif + + +namespace Config +{ + + void ApplicationSettings::find_location() + { + char cwd[ 256 ]; + getcwd( cwd, 255 ); + std::string curDir( cwd ); + + std::ifstream cfgFile( config_filename_.c_str() ); + if( cfgFile.is_open() ) + { + config_directory_ = curDir; + data_directory_ = curDir; + cfgFile.close(); + location_ = LOC_DIRECTORY; + std::cout << "Config file found in current directory (" << config_directory_ << ")...\n"; + return; + } + + config_directory_ = curDir; + location_ = LOC_NONE; + std::cout << "No config file found, using default settings...\n"; + } +} \ No newline at end of file diff --git a/src/platform_common/Utils.cpp b/src/platform_common/Utils.cpp new file mode 100644 index 00000000..101ce613 --- /dev/null +++ b/src/platform_common/Utils.cpp @@ -0,0 +1,20 @@ +#include +#include +#include + +namespace Utils +{ + + bool read_file( const std::string& filename, std::vector< char >& out ) + { + std::ifstream fp( filename.c_str() ); + if( !fp.is_open() ) return false; + fp.seekg( 0, std::ios::end ); + size_t file_size_in_byte = fp.tellg(); + out.resize( file_size_in_byte ); + fp.seekg( 0, std::ios::beg ); + fp.read( &out[ 0 ], file_size_in_byte ); + return true; + } + +} \ No newline at end of file diff --git a/src/platform_linux/Config.cpp b/src/platform_linux/Config.cpp new file mode 100644 index 00000000..e6e74260 --- /dev/null +++ b/src/platform_linux/Config.cpp @@ -0,0 +1,68 @@ +#include "Config.h" + +#include +#include +#include + +#include +#include + + +namespace Config +{ + + void ApplicationSettings::find_location() + { + const std::string sysPath( "/etc/bonzomatic" ); + const std::string userPath( getenv( "HOME" ) ); + const std::string localConfigPath = "/.config/bonzomatic"; + std::string localPath = userPath + localConfigPath; + + char cwd[ 256 ]; + getcwd( cwd, 255 ); + std::string curDir( cwd ); + + // 1. search in current directory + std::ifstream cfgFile( config_filename_.c_str() ); + if( cfgFile.is_open() ) + { + config_directory_ = curDir; + data_directory_ = curDir; + cfgFile.close(); + location_ = LOC_DIRECTORY; + std::cout << "Config file found in current directory (" << config_directory_ << ")...\n"; + return; + } + + // 2. search in user config path + std::string userCfgPath( localPath + std::string( "/" ) + config_filename_ ); + std::ifstream userCfgFile( userCfgPath.c_str() ); + if( userCfgFile.is_open() ) + { + config_directory_ = userPath; + data_directory_ = userPath; + userCfgFile.close(); + location_ = LOC_USER; + std::cout << "Config file found in user directory (" << config_directory_ << ")...\n"; + return; + } + + // 3. search in system config path + std::string sysCfgPath( sysPath + std::string( "/" ) + config_filename_ ); + std::ifstream sysCfgFile( sysCfgPath.c_str()); + if( sysCfgFile.is_open() ) + { + config_directory_ = sysPath; + data_directory_ = "/usr/share/bonzomatic"; + sysCfgFile.close(); + location_ = LOC_SYSTEM; + std::cout << "Config file found in system config path (" << config_directory_ << ")...\n"; + return; + } + + config_directory_ = curDir; + location_ = LOC_NONE; + std::cout << "No config file found, using default settings...\n"; + } + +} \ No newline at end of file