From f32db90c12abc184422e8d7eff5b7443a07166be Mon Sep 17 00:00:00 2001 From: LordGrey <48840279+Lord-Grey@users.noreply.github.com> Date: Wed, 16 Mar 2022 09:28:00 +0100 Subject: [PATCH] Fix Effects and refactor Smoothing (#1442) --- assets/webconfig/i18n/en.json | 5 +- .../js/content_effectsconfigurator.js | 2 + include/effectengine/EffectEngine.h | 6 +- .../hyperion/LinearColorSmoothing.h | 89 +++--- libsrc/effectengine/EffectEngine.cpp | 34 +- libsrc/hyperion/Hyperion.cpp | 8 +- libsrc/hyperion/LinearColorSmoothing.cpp | 296 +++++++++++------- libsrc/hyperion/PriorityMuxer.cpp | 1 + libsrc/hyperion/schema/schema-smoothing.json | 2 +- 9 files changed, 277 insertions(+), 166 deletions(-) rename {libsrc => include}/hyperion/LinearColorSmoothing.h (88%) diff --git a/assets/webconfig/i18n/en.json b/assets/webconfig/i18n/en.json index 08aee947..cedd589e 100644 --- a/assets/webconfig/i18n/en.json +++ b/assets/webconfig/i18n/en.json @@ -211,6 +211,7 @@ "dashboard_newsbox_readmore": "Read more", "dashboard_newsbox_visitblog": "Visit Hyperion-Blog", "edt_append_degree": "°", + "edt_append_frames": "frames", "edt_append_hz": "Hz", "edt_append_leds": "LEDs", "edt_append_ms": "ms", @@ -424,8 +425,8 @@ "edt_conf_smooth_time_ms_title": "Time", "edt_conf_smooth_type_expl": "Type of smoothing.", "edt_conf_smooth_type_title": "Type", - "edt_conf_smooth_updateDelay_expl": "Delay the output in case your ambient light is faster than your TV.", - "edt_conf_smooth_updateDelay_title": "Update delay", + "edt_conf_smooth_updateDelay_expl": "Delay the output by n updates in case your ambient light is faster than your TV.", + "edt_conf_smooth_updateDelay_title": "Output delay", "edt_conf_smooth_updateFrequency_expl": "The output speed to your LED controller.", "edt_conf_smooth_updateFrequency_title": "Update frequency", "edt_conf_v4l2_blueSignalThreshold_expl": "Darkens low blue values (recognized as black)", diff --git a/assets/webconfig/js/content_effectsconfigurator.js b/assets/webconfig/js/content_effectsconfigurator.js index 28774151..33dbfdbd 100644 --- a/assets/webconfig/js/content_effectsconfigurator.js +++ b/assets/webconfig/js/content_effectsconfigurator.js @@ -154,6 +154,7 @@ $(document).ready(function () { // Start test $('#btn_start_test').off().on('click', function () { + $('#btn_start_test').prop('disabled', true); triggerTestEffect(); }); @@ -161,6 +162,7 @@ $(document).ready(function () { $('#btn_stop_test').off().on('click', function () { requestPriorityClear(); testrun = false; + $('#btn_start_test').prop('disabled', false); }); // Continuous test diff --git a/include/effectengine/EffectEngine.h b/include/effectengine/EffectEngine.h index 8a3c52fa..5756345b 100644 --- a/include/effectengine/EffectEngine.h +++ b/include/effectengine/EffectEngine.h @@ -18,6 +18,8 @@ #include #include +#include + // pre-declaration class Effect; class EffectFileHandler; @@ -79,7 +81,7 @@ public slots: , int timeout = PriorityMuxer::ENDLESS , const QString &pythonScript = "" , const QString &origin = "System" - , unsigned smoothCfg=0 + , unsigned smoothCfg=SmoothingConfigID::SYSTEM , const QString &imageData = "" ); @@ -105,7 +107,7 @@ private: , int priority , int timeout = PriorityMuxer::ENDLESS , const QString &origin="System" - , unsigned smoothCfg=0 + , unsigned smoothCfg=SmoothingConfigID::SYSTEM , const QString &imageData = "" ); diff --git a/libsrc/hyperion/LinearColorSmoothing.h b/include/hyperion/LinearColorSmoothing.h similarity index 88% rename from libsrc/hyperion/LinearColorSmoothing.h rename to include/hyperion/LinearColorSmoothing.h index 3eed8451..c80188b0 100644 --- a/libsrc/hyperion/LinearColorSmoothing.h +++ b/include/hyperion/LinearColorSmoothing.h @@ -1,4 +1,5 @@ -#pragma once +#ifndef LINEARCOLORSMOOTHING_H +#define LINEARCOLORSMOOTHING_H // STL includes #include @@ -10,6 +11,7 @@ // hyperion includes #include #include +#include // settings #include @@ -21,13 +23,12 @@ class QTimer; class Logger; class Hyperion; -/// The type of smoothing to perform -enum SmoothingType { - /// "Linear" smoothing algorithm - Linear, - - /// Decay based smoothing algorithm - Decay, +enum SmoothingConfigID +{ + SYSTEM = 0, + PAUSE = 1, + EFFECT_DYNAMIC = 2, + EFFECT_SPECIFIC = 3 }; /// Linear Smoothing class @@ -80,6 +81,7 @@ public: /// @param hyperion The hyperion parent instance /// LinearColorSmoothing(const QJsonDocument &config, Hyperion *hyperion); + ~LinearColorSmoothing() override; /// LED values as input for the smoothing filter /// @@ -114,16 +116,16 @@ public: /// /// @return The index of the configuration, which can be passed to selectConfig() /// - unsigned updateConfig(unsigned cfgID, int settlingTime_ms, double ledUpdateFrequency_hz = 25.0, unsigned updateDelay = 0); + unsigned updateConfig(int cfgID, int settlingTime_ms, double ledUpdateFrequency_hz = 25.0, unsigned updateDelay = 0); /// /// @brief select a smoothing configuration given by cfg index from addConfig() - /// @param cfg The index to use + /// @param cfgID The index to use /// @param force Overwrite in any case the current values (used for cfg 0 settings update) /// /// @return On success return else false (and falls back to cfg 0) /// - bool selectConfig(unsigned cfg, bool force = false); + bool selectConfig(int cfgID, bool force = false); public slots: /// @@ -160,12 +162,17 @@ private: /// virtual int write(const std::vector &ledValues); + QString getConfig(int cfgID); + /// Logger instance Logger *_log; /// Hyperion instance Hyperion *_hyperion; + /// priority muxer instance + PriorityMuxer* _prioMuxer; + /// The interval at which to update the leds (msec) int _updateInterval; @@ -210,14 +217,12 @@ private: REMEMBERED_FRAME ( const REMEMBERED_FRAME & ) = default; REMEMBERED_FRAME & operator= ( const REMEMBERED_FRAME & ) = default; - REMEMBERED_FRAME(const int64_t time, const std::vector colors) + REMEMBERED_FRAME(int64_t time, const std::vector colors) : time(time) , colors(colors) {} }; - /// The type of smoothing to perform - SmoothingType _smoothingType; /// The queue of temporarily remembered frames std::deque _frameQueue; @@ -246,41 +251,53 @@ private: /// Value of 1.0 / settlingTime; inverse of the window size used for weighting of frames. floatT _invWindow; - struct SMOOTHING_CFG - { - /// The type of smoothing to perform - SmoothingType smoothingType; + enum class SmoothingType { Linear = 0, Decay = 1 }; + class SmoothingCfg + { + public: /// Whether to pause output - bool pause; + bool _pause; /// The time of the smoothing window. - int64_t settlingTime; + int64_t _settlingTime; /// The interval time in milliseconds of the timer used for scheduling LED update operations. A value of 0 indicates sub-millisecond timing. - int updateInterval; + int _updateInterval; - // The rate at which color frames should be written to LED device. - double outputRate; + /// The type of smoothing to perform + SmoothingType _type; + + /// The rate at which color frames should be written to LED device. + double _outputRate; /// The rate at which interpolation of LED frames should be performed. - double interpolationRate; + double _interpolationRate; /// The number of frames the output is delayed - unsigned outputDelay; + unsigned _outputDelay; /// Whether to apply temporal dithering to diffuse rounding errors when downsampling to 8-bit RGB colors. Improves color accuracy. - bool dithering; + bool _dithering; /// The decay power > 0. A value of exactly 1 is linear decay, higher numbers indicate a faster decay rate. - double decay; - }; - /// smooth configuration list - QVector _cfgList; + double _decay; - unsigned _currentConfigId; + SmoothingCfg(); + SmoothingCfg(bool pause, int64_t settlingTime, int updateInterval, SmoothingType type = SmoothingType::Linear, double outputRate = 0, double interpolationRate = 0, unsigned outputDelay = 0, bool dithering = false, double decay = 1); + + static QString EnumToString(SmoothingType type); + }; + + /// smoothing configurations + QVector _cfgList; + + int _currentConfigId; bool _enabled; + /// The type of smoothing to perform + SmoothingType _smoothingType; + /// Pushes the colors into the frame queue and cleans outdated frames from memory. /// /// @param ledColors The next colors to queue @@ -292,7 +309,7 @@ private: /// (Re-)Initializes the color-component vectors with given number of values. /// /// @param ledCount The number of colors. - void intitializeComponentVectors(const size_t ledCount); + void intitializeComponentVectors(size_t ledCount); /// The number of led component-values that must be held per color; i.e. size of the color vectors reds / greens / blues size_t _ledCount = 0; @@ -330,10 +347,10 @@ private: /// /// When downsampling the average color values to the 8-bit RGB resolution of the LED device, rounding errors are minimized /// by temporal dithering algorithm (error diffusion of residual errors). - void performDecay(const int64_t now); + void performDecay(int64_t now); /// Performs a linear smoothing effect - void performLinear(const int64_t now); + void performLinear(int64_t now); /// Aggregates the RGB components of the LED colors using the given weight and updates weighted accordingly /// @@ -343,7 +360,7 @@ private: static inline void aggregateComponents(const std::vector& colors, std::vector& weighted, const floatT weight); /// Gets the current time in microseconds from high precision system clock. - inline int64_t micros() const; + static inline int64_t micros() ; /// The time, when the rendering statistics were logged previously int64_t _renderedStatTime; @@ -368,3 +385,5 @@ private: /// @returns The frame weight. std::function _weightFrame; }; + +#endif // LINEARCOLORSMOOTHING_H diff --git a/libsrc/effectengine/EffectEngine.cpp b/libsrc/effectengine/EffectEngine.cpp index e4c8c1c8..a2e69918 100644 --- a/libsrc/effectengine/EffectEngine.cpp +++ b/libsrc/effectengine/EffectEngine.cpp @@ -112,14 +112,17 @@ void EffectEngine::handleUpdatedEffectList() { _availableEffects.clear(); - unsigned id = 2; + //Add smoothing config entry to support dynamic effects done in configurator + _hyperion->updateSmoothingConfig(SmoothingConfigID::EFFECT_DYNAMIC); + + unsigned specificId = SmoothingConfigID::EFFECT_SPECIFIC; for (auto def : _effectFileHandler->getEffects()) { - // add smoothing configs to Hyperion + // add smoothing configurations to Hyperion if (def.args["smoothing-custom-settings"].toBool()) { def.smoothCfg = _hyperion->updateSmoothingConfig( - id, + ++specificId, def.args["smoothing-time_ms"].toInt(), def.args["smoothing-updateFrequency"].toDouble(), 0 ); @@ -127,7 +130,7 @@ void EffectEngine::handleUpdatedEffectList() } else { - def.smoothCfg = _hyperion->updateSmoothingConfig(id); + def.smoothCfg = SmoothingConfigID::SYSTEM; //Debug( _log, "Default Settings: Update effect %s, script %s, file %s, smoothCfg [%u]", QSTRING_CSTR(def.name), QSTRING_CSTR(def.script), QSTRING_CSTR(def.file), def.smoothCfg); } _availableEffects.push_back(def); @@ -137,11 +140,30 @@ void EffectEngine::handleUpdatedEffectList() int EffectEngine::runEffect(const QString &effectName, int priority, int timeout, const QString &origin) { - return runEffect(effectName, QJsonObject(), priority, timeout, "", origin); + unsigned smoothCfg = SmoothingConfigID::SYSTEM; + for (const auto &def : _availableEffects) + { + if (def.name == effectName) + { + smoothCfg = def.smoothCfg; + break; + } + } + return runEffect(effectName, QJsonObject(), priority, timeout, "", origin, smoothCfg); } int EffectEngine::runEffect(const QString &effectName, const QJsonObject &args, int priority, int timeout, const QString &pythonScript, const QString &origin, unsigned smoothCfg, const QString &imageData) { + //In case smoothing information is provided dynamically use temp smoothing config item (2) + if (smoothCfg == SmoothingConfigID::SYSTEM && args["smoothing-custom-settings"].toBool()) + { + smoothCfg = _hyperion->updateSmoothingConfig( + SmoothingConfigID::EFFECT_DYNAMIC, + args["smoothing-time_ms"].toInt(), + args["smoothing-updateFrequency"].toDouble(), + 0 ); + } + if (pythonScript.isEmpty()) { const EffectDefinition *effectDefinition = nullptr; @@ -181,7 +203,7 @@ int EffectEngine::runEffectScript(const QString &script, const QString &name, co _activeEffects.push_back(effect); // start the effect - Debug(_log, "Start the effect: name [%s], smoothCfg [%u]", QSTRING_CSTR(name), smoothCfg); + Debug(_log, "Start the effect: name [%s]", QSTRING_CSTR(name)); _hyperion->registerInput(priority, hyperion::COMP_EFFECT, origin, name ,smoothCfg); effect->start(); diff --git a/libsrc/hyperion/Hyperion.cpp b/libsrc/hyperion/Hyperion.cpp index 5d80276e..e9022344 100644 --- a/libsrc/hyperion/Hyperion.cpp +++ b/libsrc/hyperion/Hyperion.cpp @@ -26,7 +26,7 @@ #include #include -#include "LinearColorSmoothing.h" +#include #if defined(ENABLE_EFFECTENGINE) // effect engine includes @@ -303,10 +303,6 @@ void Hyperion::handleSettingsUpdate(settings::type type, const QJsonDocument& co // TODO: Check, if framegrabber frequency is lower than latchtime..., if yes, stop } - else if(type == settings::SMOOTHING) - { - _deviceSmooth->handleSettingsUpdate( type, config); - } // update once to push single color sets / adjustments/ ledlayout resizes and update ledBuffer color update(); @@ -707,8 +703,6 @@ void Hyperion::update() } else { - _deviceSmooth->selectConfig(priorityInfo.smooth_cfg); - // feed smoothing in pause mode to maintain a smooth transition back to smooth mode if (_deviceSmooth->enabled() || _deviceSmooth->pause()) { diff --git a/libsrc/hyperion/LinearColorSmoothing.cpp b/libsrc/hyperion/LinearColorSmoothing.cpp index 5cfa20f8..70cd0eef 100644 --- a/libsrc/hyperion/LinearColorSmoothing.cpp +++ b/libsrc/hyperion/LinearColorSmoothing.cpp @@ -2,16 +2,13 @@ #include #include -#include "LinearColorSmoothing.h" +#include #include #include #include #include -/// The number of microseconds per millisecond = 1000. -const int64_t MS_PER_MICRO = 1000; - #if defined(COMPILER_GCC) #define ALWAYS_INLINE inline __attribute__((__always_inline__)) #elif defined(COMPILER_MSVC) @@ -25,6 +22,14 @@ ALWAYS_INLINE long clampRounded(const floatT x) { return std::min(255L, std::max(0L, std::lroundf(x))); } +// Constants +namespace { + +const bool verbose = false; + +/// The number of microseconds per millisecond = 1000. +const int64_t MS_PER_MICRO = 1000; + /// The number of bits that are used for shifting the fixed point values const int FPShift = (sizeof(uint64_t)*8 - (12 + 9)); @@ -35,97 +40,113 @@ const int SmallShiftBis = sizeof(uint8_t)*8; const int FPShiftSmall = (sizeof(uint64_t)*8 - (12 + 9 + SmallShiftBis)); const char* SETTINGS_KEY_SMOOTHING_TYPE = "type"; + +const char* SETTINGS_KEY_SETTLING_TIME = "time_ms"; +const char* SETTINGS_KEY_UPDATE_FREQUENCY = "updateFrequency"; +const char* SETTINGS_KEY_OUTPUT_DELAY = "updateDelay"; + +const char* SETTINGS_KEY_DECAY = "decay"; const char* SETTINGS_KEY_INTERPOLATION_RATE = "interpolationRate"; const char* SETTINGS_KEY_OUTPUT_RATE = "outputRate"; const char* SETTINGS_KEY_DITHERING = "dithering"; -const char* SETTINGS_KEY_DECAY = "decay"; + +const int64_t DEFAULT_SETTLINGTIME = 200; // in ms +const int DEFAULT_UPDATEFREQUENCY = 25; // in Hz + +constexpr std::chrono::milliseconds DEFAULT_UPDATEINTERVALL{MS_PER_MICRO/ DEFAULT_UPDATEFREQUENCY}; +const unsigned DEFAULT_OUTPUTDEPLAY = 0; // in frames +} using namespace hyperion; -const int64_t DEFAUL_SETTLINGTIME = 200; // settlingtime in ms -const int DEFAUL_UPDATEFREQUENCY = 25; // updatefrequncy in hz - -constexpr std::chrono::milliseconds DEFAUL_UPDATEINTERVALL{1000/ DEFAUL_UPDATEFREQUENCY}; -const unsigned DEFAUL_OUTPUTDEPLAY = 0; // outputdelay in ms - LinearColorSmoothing::LinearColorSmoothing(const QJsonDocument &config, Hyperion *hyperion) : QObject(hyperion) , _log(nullptr) , _hyperion(hyperion) - , _updateInterval(DEFAUL_UPDATEINTERVALL.count()) - , _settlingTime(DEFAUL_SETTLINGTIME) - , _timer(new QTimer(this)) - , _outputDelay(DEFAUL_OUTPUTDEPLAY) - , _smoothingType(SmoothingType::Linear) + , _prioMuxer(_hyperion->getMuxerInstance()) + , _updateInterval(DEFAULT_UPDATEINTERVALL.count()) + , _settlingTime(DEFAULT_SETTLINGTIME) + , _timer(nullptr) + , _outputDelay(DEFAULT_OUTPUTDEPLAY) , _pause(false) - , _currentConfigId(0) + , _currentConfigId(SmoothingConfigID::SYSTEM) , _enabled(false) + , _smoothingType(SmoothingType::Linear) , tempValues(std::vector(0, 0L)) { QString subComponent = hyperion->property("instance").toString(); _log= Logger::getInstance("SMOOTHING", subComponent); - // init cfg 0 (default) - addConfig(DEFAUL_SETTLINGTIME, DEFAUL_UPDATEFREQUENCY, DEFAUL_OUTPUTDEPLAY); + // timer + _timer = new QTimer(this); + _timer->setTimerType(Qt::PreciseTimer); + + // init cfg (default) + updateConfig(SmoothingConfigID::SYSTEM, DEFAULT_SETTLINGTIME, DEFAULT_UPDATEFREQUENCY, DEFAULT_OUTPUTDEPLAY); handleSettingsUpdate(settings::SMOOTHING, config); - selectConfig(0, true); // add pause on cfg 1 - SMOOTHING_CFG cfg = {SmoothingType::Linear, false, 0, 0, 0, 0, 0, false, 1}; + SmoothingCfg cfg {true, 0, 0}; _cfgList.append(cfg); // listen for comp changes connect(_hyperion, &Hyperion::compStateChangeRequest, this, &LinearColorSmoothing::componentStateChange); - // timer connect(_timer, &QTimer::timeout, this, &LinearColorSmoothing::updateLeds); - //Debug(_log, "LinearColorSmoothing sizeof floatT == %d", (sizeof(floatT))); + connect(_prioMuxer, &PriorityMuxer::prioritiesChanged, this, [=] (int priority){ + const PriorityMuxer::InputInfo priorityInfo = _prioMuxer->getInputInfo(priority); + int smooth_cfg = priorityInfo.smooth_cfg; + if (smooth_cfg != _currentConfigId || smooth_cfg == SmoothingConfigID::EFFECT_DYNAMIC) + { + this->selectConfig(smooth_cfg, false); + } + }); +} + +LinearColorSmoothing::~LinearColorSmoothing() +{ + delete _timer; } void LinearColorSmoothing::handleSettingsUpdate(settings::type type, const QJsonDocument &config) { - if (type == settings::SMOOTHING) + if (type == settings::type::SMOOTHING) { - // std::cout << "LinearColorSmoothing::handleSettingsUpdate" << std::endl; - // std::cout << config.toJson().toStdString() << std::endl; - QJsonObject obj = config.object(); if (enabled() != obj["enable"].toBool(true)) { setEnable(obj["enable"].toBool(true)); } - SMOOTHING_CFG cfg = {SmoothingType::Linear,true, 0, 0, 0, 0, 0, false, 1}; + SmoothingCfg cfg(false, + static_cast(obj[SETTINGS_KEY_SETTLING_TIME].toInt(DEFAULT_SETTLINGTIME)), + static_cast(MS_PER_MICRO / obj[SETTINGS_KEY_UPDATE_FREQUENCY].toDouble(DEFAULT_UPDATEFREQUENCY)) + ); const QString typeString = obj[SETTINGS_KEY_SMOOTHING_TYPE].toString(); - if(typeString == "linear") { - cfg.smoothingType = SmoothingType::Linear; - } else if(typeString == "decay") { - cfg.smoothingType = SmoothingType::Decay; + if(typeString == SETTINGS_KEY_DECAY) { + cfg._type = SmoothingType::Decay; + } + else { + cfg._type = SmoothingType::Linear; } - cfg.pause = false; - cfg.settlingTime = static_cast(obj["time_ms"].toInt(DEFAUL_SETTLINGTIME)); - cfg.updateInterval = static_cast(1000.0 / obj["updateFrequency"].toDouble(DEFAUL_UPDATEFREQUENCY)); - cfg.outputRate = obj[SETTINGS_KEY_OUTPUT_RATE].toDouble(DEFAUL_UPDATEFREQUENCY); - cfg.interpolationRate = obj[SETTINGS_KEY_INTERPOLATION_RATE].toDouble(DEFAUL_UPDATEFREQUENCY); - cfg.outputDelay = static_cast(obj["updateDelay"].toInt(DEFAUL_OUTPUTDEPLAY)); - cfg.dithering = obj[SETTINGS_KEY_DITHERING].toBool(false); - cfg.decay = obj[SETTINGS_KEY_DECAY].toDouble(1.0); + cfg._pause = false; + cfg._outputDelay = static_cast(obj[SETTINGS_KEY_OUTPUT_DELAY].toInt(DEFAULT_OUTPUTDEPLAY)); - //Debug( _log, "smoothing cfg_id %d: pause: %d bool, settlingTime: %d ms, interval: %d ms (%u Hz), updateDelay: %u frames", _currentConfigId, cfg.pause, cfg.settlingTime, cfg.updateInterval, unsigned(1000.0/cfg.updateInterval), cfg.outputDelay ); - _cfgList[0] = cfg; + cfg._outputRate = obj[SETTINGS_KEY_OUTPUT_RATE].toDouble(DEFAULT_UPDATEFREQUENCY); + cfg._interpolationRate = obj[SETTINGS_KEY_INTERPOLATION_RATE].toDouble(DEFAULT_UPDATEFREQUENCY); + cfg._dithering = obj[SETTINGS_KEY_DITHERING].toBool(false); + cfg._decay = obj[SETTINGS_KEY_DECAY].toDouble(1.0); + + _cfgList[SmoothingConfigID::SYSTEM] = cfg; + DebugIf(_enabled,_log,"%s", QSTRING_CSTR(getConfig(SmoothingConfigID::SYSTEM))); // if current id is 0, we need to apply the settings (forced) - if (_currentConfigId == 0) + if (_currentConfigId == SmoothingConfigID::SYSTEM) { - //Debug( _log, "_currentConfigId == 0"); - selectConfig(0, true); - } - else - { - //Debug( _log, "_currentConfigId != 0"); + selectConfig(SmoothingConfigID::SYSTEM, true); } } } @@ -145,8 +166,7 @@ int LinearColorSmoothing::write(const std::vector &ledValues) _previousValues = ledValues; _previousInterpolationTime = micros(); - //Debug( _log, "Start Smoothing timer: settlingTime: %d ms, interval: %d ms (%u Hz), updateDelay: %u frames", _settlingTime, _updateInterval, unsigned(1000.0/_updateInterval), _outputDelay ); - QMetaObject::invokeMethod(_timer, "start", Qt::QueuedConnection, Q_ARG(int, _updateInterval)); + _timer->start(_updateInterval); } return 0; @@ -202,7 +222,7 @@ void LinearColorSmoothing::writeFrame() } -ALWAYS_INLINE int64_t LinearColorSmoothing::micros() const +ALWAYS_INLINE int64_t LinearColorSmoothing::micros() { const auto now = std::chrono::high_resolution_clock::now(); return (std::chrono::duration_cast(now.time_since_epoch())).count(); @@ -215,7 +235,7 @@ void LinearColorSmoothing::assembleAndDitherFrame() return; } - // The number of leds present in each frame + // The number of LEDs present in each frame const size_t N = _targetValues.size(); for (size_t i = 0; i < N; ++i) @@ -250,7 +270,7 @@ void LinearColorSmoothing::assembleFrame() return; } - // The number of leds present in each frame + // The number of LEDs present in each frame const size_t N = _targetValues.size(); for (size_t i = 0; i < N; ++i) @@ -438,7 +458,6 @@ void LinearColorSmoothing::updateLeds() const int64_t now = micros(); const int64_t deltaTime = _targetTime - now; - //Debug(_log, "elapsed Time [%d], _targetTime [%d] - now [%d], deltaTime [%d]", now -_previousWriteTime, _targetTime, now, deltaTime); if (deltaTime < 0) { writeDirect(); @@ -447,12 +466,11 @@ void LinearColorSmoothing::updateLeds() switch (_smoothingType) { - case Decay: + case SmoothingType::Decay: performDecay(now); break; - case Linear: - // Linear interpolation is default + case SmoothingType::Linear: default: performLinear(now); break; @@ -461,8 +479,6 @@ void LinearColorSmoothing::updateLeds() void LinearColorSmoothing::rememberFrame(const std::vector &ledColors) { - //Debug(_log, "rememberFrame - before _frameQueue.size() [%d]", _frameQueue.size()); - const int64_t now = micros(); // Maintain the queue by removing outdated frames @@ -478,15 +494,12 @@ void LinearColorSmoothing::rememberFrame(const std::vector &ledColors) if (p > 0) { - //Debug(_log, "rememberFrame - erasing %d frames", p); _frameQueue.erase(_frameQueue.begin(), _frameQueue.begin() + p); } // Append the latest frame at back of the queue const REMEMBERED_FRAME frame = REMEMBERED_FRAME(now, ledColors); _frameQueue.push_back(frame); - - //Debug(_log, "rememberFrame - after _frameQueue.size() [%d]", _frameQueue.size()); } @@ -532,7 +545,8 @@ void LinearColorSmoothing::queueColors(const std::vector &ledColors) void LinearColorSmoothing::clearQueuedColors() { - QMetaObject::invokeMethod(_timer, "stop", Qt::QueuedConnection); + _timer->stop(); + //QMetaObject::invokeMethod(_timer, "stop", Qt::QueuedConnection); _previousValues.clear(); _targetValues.clear(); @@ -566,71 +580,65 @@ void LinearColorSmoothing::setPause(bool pause) unsigned LinearColorSmoothing::addConfig(int settlingTime_ms, double ledUpdateFrequency_hz, unsigned updateDelay) { - SMOOTHING_CFG cfg = { - SmoothingType::Linear, + SmoothingCfg cfg { false, settlingTime_ms, - static_cast(1000.0 / ledUpdateFrequency_hz), + static_cast(MS_PER_MICRO / ledUpdateFrequency_hz), + SmoothingType::Linear, ledUpdateFrequency_hz, ledUpdateFrequency_hz, - updateDelay, - false, - 1 + updateDelay }; _cfgList.append(cfg); - //Debug( _log, "smoothing cfg %d: pause: %d bool, settlingTime: %d ms, interval: %d ms (%u Hz), updateDelay: %u frames", _cfgList.count()-1, cfg.pause, cfg.settlingTime, cfg.updateInterval, unsigned(1000.0/cfg.updateInterval), cfg.outputDelay ); + DebugIf(verbose && _enabled, _log,"%s", QSTRING_CSTR(getConfig(_cfgList.count()-1))); + return _cfgList.count() - 1; } -unsigned LinearColorSmoothing::updateConfig(unsigned cfgID, int settlingTime_ms, double ledUpdateFrequency_hz, unsigned updateDelay) +unsigned LinearColorSmoothing::updateConfig(int cfgID, int settlingTime_ms, double ledUpdateFrequency_hz, unsigned updateDelay) { - unsigned updatedCfgID = cfgID; - if (cfgID < static_cast(_cfgList.count())) + int updatedCfgID = cfgID; + if (cfgID < _cfgList.count()) { - SMOOTHING_CFG cfg = { - SmoothingType::Linear, + SmoothingCfg cfg { false, settlingTime_ms, - static_cast(1000.0 / ledUpdateFrequency_hz), + static_cast(MS_PER_MICRO / ledUpdateFrequency_hz), + SmoothingType::Linear, ledUpdateFrequency_hz, ledUpdateFrequency_hz, - updateDelay, - false, - 1}; + updateDelay + }; _cfgList[updatedCfgID] = cfg; + DebugIf(verbose && _enabled, _log,"%s", QSTRING_CSTR(getConfig(updatedCfgID))); } else { updatedCfgID = addConfig(settlingTime_ms, ledUpdateFrequency_hz, updateDelay); } - // Debug( _log, "smoothing updatedCfgID %u: settlingTime: %d ms, " - // "interval: %d ms (%u Hz), updateDelay: %u frames", cfgID, _settlingTime, int64_t(1000.0/ledUpdateFrequency_hz), unsigned(ledUpdateFrequency_hz), updateDelay ); return updatedCfgID; } -bool LinearColorSmoothing::selectConfig(unsigned cfg, bool force) +bool LinearColorSmoothing::selectConfig(int cfgID, bool force) { - if (_currentConfigId == cfg && !force) + if (_currentConfigId == cfgID && !force) { - //Debug( _log, "selectConfig SAME as before, not FORCED - _currentConfigId [%u], force [%d]", cfg, force); - //Debug( _log, "current smoothing cfg: %d, settlingTime: %d ms, interval: %d ms (%u Hz), updateDelay: %u frames", _currentConfigId, _settlingTime, _updateInterval, unsigned(1000.0/_updateInterval), _outputDelay ); return true; } - //Debug( _log, "selectConfig FORCED - _currentConfigId [%u], force [%d]", cfg, force); - if (cfg < static_cast(_cfgList.count()) ) + if (cfgID < _cfgList.count() ) { - _smoothingType = _cfgList[cfg].smoothingType; - _settlingTime = _cfgList[cfg].settlingTime; - _outputDelay = _cfgList[cfg].outputDelay; - _pause = _cfgList[cfg].pause; - _outputRate = _cfgList[cfg].outputRate; + _smoothingType = _cfgList[cfgID]._type; + _settlingTime = _cfgList[cfgID]._settlingTime; + _outputDelay = _cfgList[cfgID]._outputDelay; + _pause = _cfgList[cfgID]._pause; + _outputRate = _cfgList[cfgID]._outputRate; _outputIntervalMicros = int64_t(1000000.0 / _outputRate); // 1s = 1e6 µs - _interpolationRate = _cfgList[cfg].interpolationRate; + _interpolationRate = _cfgList[cfgID]._interpolationRate; _interpolationIntervalMicros = int64_t(1000000.0 / _interpolationRate); - _dithering = _cfgList[cfg].dithering; - _decay = _cfgList[cfg].decay; + _dithering = _cfgList[cfgID]._dithering; + _decay = _cfgList[cfgID]._decay; _invWindow = 1.0F / (MS_PER_MICRO * _settlingTime); // Set _weightFrame based on the given decay @@ -661,33 +669,95 @@ bool LinearColorSmoothing::selectConfig(unsigned cfg, bool force) _interpolationCounter = 0; _interpolationStatCounter = 0; - if (_cfgList[cfg].updateInterval != _updateInterval) + if (_cfgList[cfgID]._updateInterval != _updateInterval) { - QMetaObject::invokeMethod(_timer, "stop", Qt::QueuedConnection); - _updateInterval = _cfgList[cfg].updateInterval; + _timer->stop(); + _updateInterval = _cfgList[cfgID]._updateInterval; if (this->enabled()) { - //Debug( _log, "_cfgList[cfg].updateInterval != _updateInterval - Restart timer - _updateInterval [%d]", _updateInterval); - QMetaObject::invokeMethod(_timer, "start", Qt::QueuedConnection, Q_ARG(int, _updateInterval)); - } - else - { - //Debug( _log, "Smoothing disabled, do NOT restart timer"); + _timer->start(_updateInterval); } } - _currentConfigId = cfg; - // Debug( _log, "current smoothing cfg: %d, settlingTime: %d ms, interval: %d ms (%u Hz), updateDelay: %u frames", _currentConfigId, _settlingTime, _updateInterval, unsigned(1000.0/_updateInterval), _outputDelay ); - // DebugIf( enabled() && !_pause, _log, "set smoothing cfg: %u settlingTime: %d ms, interval: %d ms, updateDelay: %u frames", _currentConfigId, _settlingTime, _updateInterval, _outputDelay ); - // DebugIf( _pause, _log, "set smoothing cfg: %d, pause", _currentConfigId ); - - const float thalf = (1.0-std::pow(1.0/2, 1.0/_decay))*_settlingTime; - Debug( _log, "cfg [%d]: Type: %s - Time: %d ms, outputRate %f Hz, interpolationRate: %f Hz, timer: %d ms, Dithering: %d, Decay: %f -> HalfTime: %f ms", cfg, _smoothingType == SmoothingType::Decay ? "decay" : "linear", _settlingTime, _outputRate, _interpolationRate, _updateInterval, _dithering ? 1 : 0, _decay, thalf); + _currentConfigId = cfgID; + DebugIf(_enabled, _log,"%s", QSTRING_CSTR(getConfig(_currentConfigId))); return true; } // reset to default - _currentConfigId = 0; + _currentConfigId = SmoothingConfigID::SYSTEM; return false; } + +QString LinearColorSmoothing::getConfig(int cfgID) +{ + QString configText; + + if (cfgID < _cfgList.count()) + { + SmoothingCfg cfg = _cfgList[cfgID]; + + configText = QString ("[%1] - type: %2, pause: %3, settlingTime: %4ms, interval: %5ms (%6Hz), delay: %7 frames") + .arg(cfgID) + .arg(SmoothingCfg::EnumToString(cfg._type),(cfg._pause) ? "true" : "false") + .arg(cfg._settlingTime) + .arg(cfg._updateInterval) + .arg(int(MS_PER_MICRO/cfg._updateInterval)) + .arg(cfg._outputDelay); + + switch (cfg._type) { + case SmoothingType::Linear: + break; + + case SmoothingType::Decay: + { + const double thalf = (1.0-std::pow(1.0/2, 1.0/_decay))*_settlingTime; + configText += QString (", outputRate %1Hz, interpolationRate: %2Hz, dithering: %3, decay: %4 -> halftime: %5ms") + .arg(cfg._outputRate,0,'f',2) + .arg(cfg._interpolationRate,0,'f',2) + .arg((cfg._dithering) ? "true" : "false") + .arg(cfg._decay,0,'f',2) + .arg(thalf,0,'f',2); + break; + } + } + } + return configText; +} + +LinearColorSmoothing::SmoothingCfg::SmoothingCfg() : + _pause(false), + _settlingTime(DEFAULT_SETTLINGTIME), + _updateInterval(DEFAULT_UPDATEFREQUENCY), + _type(SmoothingType::Linear) +{ +} + +LinearColorSmoothing::SmoothingCfg::SmoothingCfg(bool pause, int64_t settlingTime, int updateInterval, SmoothingType type, double outputRate, double interpolationRate, unsigned outputDelay, bool dithering, double decay) : + _pause(pause), + _settlingTime(settlingTime), + _updateInterval(updateInterval), + _type(type), + _outputRate(outputRate), + _interpolationRate(interpolationRate), + _outputDelay(outputDelay), + _dithering(dithering), + _decay(decay) +{ +} + + +QString LinearColorSmoothing::SmoothingCfg::EnumToString(SmoothingType type) +{ + if (type == SmoothingType::Linear) { + return QString("Linear"); + } + + if (type == SmoothingType::Decay) + { + return QString("Decay"); + } + + return QString("Unknown"); +} diff --git a/libsrc/hyperion/PriorityMuxer.cpp b/libsrc/hyperion/PriorityMuxer.cpp index e42e764e..bfe16dfd 100644 --- a/libsrc/hyperion/PriorityMuxer.cpp +++ b/libsrc/hyperion/PriorityMuxer.cpp @@ -44,6 +44,7 @@ PriorityMuxer::PriorityMuxer(int ledCount, QObject * parent) _lowestPriorityInfo.componentId = hyperion::COMP_COLOR; _lowestPriorityInfo.origin = "System"; _lowestPriorityInfo.owner = ""; + _lowestPriorityInfo.smooth_cfg = 0; _activeInputs[PriorityMuxer::LOWEST_PRIORITY] = _lowestPriorityInfo; diff --git a/libsrc/hyperion/schema/schema-smoothing.json b/libsrc/hyperion/schema/schema-smoothing.json index 7d2cb7c0..3f51c214 100644 --- a/libsrc/hyperion/schema/schema-smoothing.json +++ b/libsrc/hyperion/schema/schema-smoothing.json @@ -94,7 +94,7 @@ "minimum": 0, "maximum": 2048, "default": 0, - "append": "edt_append_ms", + "append": "edt_append_frames", "propertyOrder": 9 } },