diff --git a/CMakeLists.txt b/CMakeLists.txt index 54d8134..e5066d1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,14 +22,14 @@ FetchContent_Declare( GIT_REPOSITORY https://github.com/thestk/rtmidi.git GIT_TAG 6.0.0 ) -# FetchContent_Declare( -# libconfig -# GIT_REPOSITORY https://github.com/hyperrealm/libconfig.git -# GIT_TAG v1.8.2 -# ) +FetchContent_Declare( + libconfig + GIT_REPOSITORY https://github.com/hyperrealm/libconfig.git + GIT_TAG v1.8.2 +) FetchContent_MakeAvailable(rtaudio) FetchContent_MakeAvailable(rtmidi) -# FetchContent_MakeAvailable(libconfig) +FetchContent_MakeAvailable(libconfig) # needs to be preinstalled find_package(Qt6 REQUIRED COMPONENTS @@ -53,12 +53,18 @@ target_link_libraries(sonobulus_core PRIVATE Qt6::Quick rtaudio rtmidi + libconfig++ ) target_include_directories(sonobulus_core PRIVATE ${CMAKE_SOURCE_DIR}/src/ ${rtaudio_SOURCE_DIR} ${rtmidi_SOURCE_DIR} + ${libconfig_SOURCE_DIR}/lib +) + +target_compile_definitions(sonobulus_core PRIVATE + BINARY_DIR="${CMAKE_BINARY_DIR}" ) qt_add_executable(sonobulus @@ -74,6 +80,7 @@ qt_add_qml_module(sonobulus target_include_directories(sonobulus PRIVATE ${rtaudio_SOURCE_DIR} ${rtmidi_SOURCE_DIR} + ${libconfig_SOURCE_DIR}/lib ) target_link_libraries(sonobulus PRIVATE @@ -95,6 +102,7 @@ if (WIN32) TARGET sonobulus POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different $ + $ $ ) endif() diff --git a/config/.gitkeep b/config/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/config/sonobulus.cfg b/config/sonobulus.cfg new file mode 100644 index 0000000..7498971 --- /dev/null +++ b/config/sonobulus.cfg @@ -0,0 +1,20 @@ + +Loggers = ( + { + Id = "Engine"; + + FlagsEnabled = ( + "Debug", + "Info", + "Warning", + "Error" + ); + + ShowTime = true; + ShowSourceTrace = false; + CoutEnabled = true; + + FileEnabled = true; + FilePath = "Engine.log"; + } +); \ No newline at end of file diff --git a/src/ConfigService.cpp b/src/ConfigService.cpp index e69de29..da4d2d5 100644 --- a/src/ConfigService.cpp +++ b/src/ConfigService.cpp @@ -0,0 +1,80 @@ + +#include "ConfigService.hpp" + +#include +#include + +ConfigService::ConfigService(const std::string& filePath) { + + if(!loadFromFile(filePath)) { + std::cout << "Error loading file " << filePath << std::endl; + } + +} + +bool ConfigService::loadFromFile(const std::string& filePath) { + try { + config_.clear(); + config_.readFile(filePath.c_str()); + lastError_.clear(); + return true; + } catch (const libconfig::FileIOException&) { + lastError_ = "Unable to read config file: " + filePath; + } catch (const libconfig::ParseException& error) { + lastError_ = std::string("Parse error in ") + error.getFile() + ":" + + std::to_string(error.getLine()) + " - " + error.getError(); + } catch (const std::exception& error) { + lastError_ = error.what(); + } + + return false; +} + +bool ConfigService::getLoggerConfig(const std::string& id, LoggerConfig* loggerConfig) const { + try { + const libconfig::Setting& loggers = config_.lookup("Loggers"); + for (int index = 0; index < loggers.getLength(); ++index) { + const libconfig::Setting& loggerSetting = loggers[index]; + + std::string loggerId; + if (!loggerSetting.lookupValue("Id", loggerId) || loggerId != id) { + continue; + } + + return parseLoggerConfig(loggerSetting, loggerConfig); + } + } catch (const libconfig::SettingException& ex) { + std::cout << "libconfig setting exception: " << ex.what() << std::endl; + return false; + } + + return false; +} + +const std::string& ConfigService::lastError() const { + return lastError_; +} + +bool ConfigService::parseLoggerConfig(const libconfig::Setting& loggerSetting, LoggerConfig* loggerConfig) const { + + if (!loggerSetting.lookupValue("Id", loggerConfig->id)) { + return false; + } + + try { + const libconfig::Setting& flags = loggerSetting.lookup("FlagsEnabled"); + for (int index = 0; index < flags.getLength(); ++index) { + loggerConfig->flagsEnabled.push_back(static_cast(flags[index])); + } + } catch (const libconfig::SettingException&) { + loggerConfig->flagsEnabled.clear(); + } + + loggerSetting.lookupValue("ShowTime", loggerConfig->showTime); + loggerSetting.lookupValue("ShowSourceTrace", loggerConfig->showSourceTrace); + loggerSetting.lookupValue("CoutEnabled", loggerConfig->coutEnabled); + loggerSetting.lookupValue("FileEnabled", loggerConfig->fileEnabled); + loggerSetting.lookupValue("FilePath", loggerConfig->filePath); + + return true; +} \ No newline at end of file diff --git a/src/ConfigService.hpp b/src/ConfigService.hpp index 9f248c1..c135da0 100644 --- a/src/ConfigService.hpp +++ b/src/ConfigService.hpp @@ -1,6 +1,47 @@ #pragma once +#include + +#include +#include +#include + +struct LoggerConfig { + std::string id; + std::vector flagsEnabled; + bool showTime = false; + bool showSourceTrace = false; + bool coutEnabled = false; + bool fileEnabled = false; + std::string filePath; +}; + +struct AudioConfig { + uint32_t sampleRate; + uint32_t channels; + uint32_t stereoMode; + uint32_t bufferSize; + float pitchStandard; + int32_t midiHome; + int32_t notesPerOctave; +}; + class ConfigService { +public: + + ConfigService(const std::string& filePath); + ~ConfigService() = default; + + bool loadFromFile(const std::string& filePath); + + bool getLoggerConfig(const std::string& id, LoggerConfig* loggerConfig) const; + const std::string& lastError() const; + +private: + bool parseLoggerConfig(const libconfig::Setting& loggerSetting, LoggerConfig* loggerConfig) const; + + libconfig::Config config_; + std::string lastError_; }; diff --git a/src/LoggerService.cpp b/src/LoggerService.cpp index e69de29..c88d9e2 100644 --- a/src/LoggerService.cpp +++ b/src/LoggerService.cpp @@ -0,0 +1,108 @@ + +#include // Tracking the time when the log function is called +#include +#include +#include "LoggerService.hpp" +#include +#include +#include +#include + +namespace fs = std::filesystem; + +LoggerService::LoggerService(ConfigService* config, const std::string& loggerId) { + + if(!(config->getLoggerConfig(loggerId, &configuration_))) { + std::cout << "Failed to get logger configuration fom config service" << std::endl; + return; + } + + standardOutputEnabled_ = configuration_.coutEnabled; + fileOutputEnabled_ = configuration_.fileEnabled; + additionaldetailsEnabled_ = configuration_.showSourceTrace; + timeEnabled_ = configuration_.showTime; + id_ = configuration_.id; + + for(std::string& flag : configuration_.flagsEnabled) { + bool found = false; + for(const char* validFlag : LogFlagStrings) { + if(flag == std::string(validFlag)) { + found = true; + for(int i = 0; i < LogFlag::Count; i++) { + if(LogFlagStrings[i] == flag) { + activeFlags_.emplace_back(static_cast(i)); + break; + } + } + } + } + if(!found) { + std::cout << "Log flag '" << flag << "' in configuration file is not a valid log flag" << std::endl; + } + } + + const auto now = std::chrono::system_clock::now(); + const std::time_t t_c = std::chrono::system_clock::to_time_t(now); + + std::string finaltime = std::ctime(&t_c); + + finaltime.pop_back(); // removing the newline + + std::replace(finaltime.begin(),finaltime.end(), ':' , '-'); // filenames cant have dashes + std::replace(finaltime.begin(),finaltime.end(), ' ' , '_'); + + fs::create_directories(std::string(BINARY_DIR) + "/" + finaltime); + std::string logPath = std::string(BINARY_DIR) + "/" + finaltime + "/" + configuration_.filePath; + + if(fileOutputEnabled_) outfile_.open(logPath); + + log("Logger", LogFlag::Info, "Logger initialized."); +} + +LoggerService::~LoggerService() { + if(outfile_) outfile_.close(); +} + +void LoggerService::log(std::string component, LogFlag flag, std::string message, std::source_location Source) { + + // check if flag is in the list of active flags + bool culled = true; + for(LogFlag& testFlag : activeFlags_) { + if(flag == testFlag) { + culled = false; + break; + } + } + if(culled) return; + + std::string finalmessage = ""; + + if(timeEnabled_) { + finalmessage = finalmessage + "[" + "Not Implemented" + "] "; + } + + std::string componentTrace = "[" + id_ + ": " + component + "] "; + finalmessage += componentTrace; // component is the section of the program (For example Mesh or Engine) that is calling the logger + + std::string level = ""; + + level = LogFlagStrings[flag]; + + // level.append(7 - level.length(), ' ') pads out the level string with whitespace so every line is aligned the same + // it looked weird though + finalmessage = finalmessage + "[" + level + "] "; + finalmessage = finalmessage + message + " "; + + if (additionaldetailsEnabled_) { + finalmessage = finalmessage + "[Function: " + Source.function_name() + "]" + " " + "[Line: " + std::to_string(Source.line()) + "]" + " " + "[File: " + Source.file_name() + "]"; + } + + if(standardOutputEnabled_) { + std::cout << finalmessage << std::endl; + } + + if(fileOutputEnabled_) { + outfile_ << finalmessage << std::endl; + } + return; +} diff --git a/src/LoggerService.hpp b/src/LoggerService.hpp index e69de29..fea8281 100644 --- a/src/LoggerService.hpp +++ b/src/LoggerService.hpp @@ -0,0 +1,46 @@ + +#pragma once +#include +#include +#include +#include + +#include "ConfigService.hpp" + +enum LogFlag { + Debug, + Info, + Warning, + Error, + Count +}; + +static constexpr const char* LogFlagStrings[] = { + "Debug", + "Info", + "Warning", + "Error" +}; + +class LoggerService { + +public: + + LoggerService(ConfigService* config, const std::string& loggerId); + ~LoggerService(); + + void log(std::string component, LogFlag flag, std::string message, std::source_location Source = std::source_location::current()); // Using the + +private: + + bool standardOutputEnabled_; + bool fileOutputEnabled_; + bool additionaldetailsEnabled_; + bool timeEnabled_; + std::ofstream outfile_; + std::string id_; + + std::vector activeFlags_; + + LoggerConfig configuration_; +}; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index c65c613..fc9f0bc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,6 +6,8 @@ #include #include "TimerComponent.hpp" +#include "LoggerService.hpp" +#include "ConfigService.hpp" #include "synth/AudioEngine.hpp" int main(int argc, char* argv[]) { @@ -28,8 +30,12 @@ int main(int argc, char* argv[]) { return -1; } + // create app objects + ConfigService config = ConfigService("config/sonobulus.cfg"); + LoggerService logger = LoggerService(&config, "Engine"); + // audio synthesizer doohickey - AudioEngine audioEngine = AudioEngine(); + AudioEngine audioEngine = AudioEngine(&logger); audioEngine.start(); // execute app diff --git a/src/synth/AudioEngine.cpp b/src/synth/AudioEngine.cpp index 093a109..19854aa 100644 --- a/src/synth/AudioEngine.cpp +++ b/src/synth/AudioEngine.cpp @@ -5,12 +5,14 @@ #include #include -AudioEngine::AudioEngine() { +AudioEngine::AudioEngine(LoggerService* logger) : logger_(logger) { if(audioDevice_.getDeviceCount() < 1) { std::cout << "No audio devices found" << std::endl; } + if(logger_ == nullptr) std::cout << "err: logger nullptr" << std::endl; + } AudioEngine::~AudioEngine() { @@ -41,7 +43,8 @@ bool AudioEngine::start() { } // sanity check - std::cout << "sample rate: " << sampleRate_ << " buffer frames: " << bufferFrames_ << std::endl; + std::string msg = "sample rate: " + std::to_string(sampleRate_) + ", buffer frames: " + std::to_string(bufferFrames_); + logger_->log("AudioEngine", LogFlag::Info, msg); return true; } @@ -58,7 +61,7 @@ bool AudioEngine::stop() { int32_t AudioEngine::audioCallback(void* outputBuffer, void* inputBuffer, uint32_t nFrames, double, RtAudioStreamStatus status, void* userData) { // error if the callback is late - if(status) std::cout << "stream underflow" << std::endl; + if(status) static_cast(userData)->logger_->log("AudioEngine", LogFlag::Warning, "Audio buffer underflow"); return static_cast(userData)->process(static_cast(outputBuffer), static_cast(nFrames)); diff --git a/src/synth/AudioEngine.hpp b/src/synth/AudioEngine.hpp index 83b3bce..9abe9ed 100644 --- a/src/synth/AudioEngine.hpp +++ b/src/synth/AudioEngine.hpp @@ -5,6 +5,9 @@ #include +#include "LoggerService.hpp" + + #if defined(_WIN32) #define AUDIO_API RtAudio::WINDOWS_WASAPI #else @@ -15,7 +18,7 @@ class AudioEngine { public: - AudioEngine(); + AudioEngine(LoggerService* logger); ~AudioEngine(); bool start(); @@ -36,4 +39,6 @@ private: float phase_ = 0.0f; + LoggerService* logger_; + };