Fix Effects and refactor Smoothing (#1442)

This commit is contained in:
LordGrey
2022-03-16 09:28:00 +01:00
committed by GitHub
parent 62829d9bf8
commit f32db90c12
9 changed files with 277 additions and 166 deletions

View File

@@ -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();

View File

@@ -26,7 +26,7 @@
#include <leddevice/LedDeviceWrapper.h>
#include <hyperion/MultiColorAdjustment.h>
#include "LinearColorSmoothing.h"
#include <hyperion/LinearColorSmoothing.h>
#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())
{

View File

@@ -2,16 +2,13 @@
#include <QDateTime>
#include <QTimer>
#include "LinearColorSmoothing.h"
#include <hyperion/LinearColorSmoothing.h>
#include <hyperion/Hyperion.h>
#include <cmath>
#include <chrono>
#include <thread>
/// 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<uint64_t>(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<int64_t>(obj[SETTINGS_KEY_SETTLING_TIME].toInt(DEFAULT_SETTLINGTIME)),
static_cast<int64_t>(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<int64_t>(obj["time_ms"].toInt(DEFAUL_SETTLINGTIME));
cfg.updateInterval = static_cast<int>(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<unsigned>(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<unsigned>(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<ColorRgb> &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<std::chrono::microseconds>(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<ColorRgb> &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<ColorRgb> &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<ColorRgb> &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<int>(1000.0 / ledUpdateFrequency_hz),
static_cast<int>(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<unsigned>(_cfgList.count()))
int updatedCfgID = cfgID;
if (cfgID < _cfgList.count())
{
SMOOTHING_CFG cfg = {
SmoothingType::Linear,
SmoothingCfg cfg {
false,
settlingTime_ms,
static_cast<int>(1000.0 / ledUpdateFrequency_hz),
static_cast<int>(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<uint>(_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");
}

View File

@@ -1,370 +0,0 @@
#pragma once
// STL includes
#include <vector>
#include <deque>
// Qt includes
#include <QVector>
// hyperion includes
#include <leddevice/LedDevice.h>
#include <utils/Components.h>
// settings
#include <utils/settings.h>
// The type of float
#define floatT float // Select double, float or __fp16
class QTimer;
class Logger;
class Hyperion;
/// The type of smoothing to perform
enum SmoothingType {
/// "Linear" smoothing algorithm
Linear,
/// Decay based smoothing algorithm
Decay,
};
/// Linear Smoothing class
///
/// This class processes the requested led values and forwards them to the device after applying
/// a smoothing effect to LED colors. This class can be handled as a generic LedDevice.
///
/// Currently, two types of smoothing are supported:
///
/// - Linear: A linear smoothing effect that interpolates the previous to the target colors.
/// - Decay: A temporal smoothing effect that uses a decay based algorithm that interpolates
/// colors based on the age of previous frames and a given decay-power.
///
/// The smoothing is performed on a history of relevant LED-color frames that are
/// incorporated in the smoothing window (given by the configured settling time).
///
/// For each moment, all ingress frames that were received during the smoothing window
/// are reduced to the concrete color values using a weighted moving average. This is
/// done by applying a decay-controlled weighting-function to individual the colors of
/// each frame.
///
/// Decay
/// =====
/// The decay-power influences the weight of individual frames based on their 'age'.
///
/// * A decay value of 1 indicates linear decay. The colors are given by the moving average
/// with a weight that is strictly proportionate to the fraction of time each frame was
/// visible during the smoothing window. As a result, equidistant frames will have an
/// equal share when calculating an intermediate frame.
///
/// * A decay value greater than 1 indicates non-linear decay. With higher powers, the
/// decay is stronger. I.e. newer frames in the smoothing window will have more influence
/// on colors of intermediate frames than older ones.
///
/// Dithering
/// =========
/// A temporal dithering algorithm is used to minimize rounding errors, when downsampling
/// the average color values to the 8-bit RGB resolution of the LED-device. Effectively,
/// this performs diffusion of the residual errors across multiple egress frames.
///
///
class LinearColorSmoothing : public QObject
{
Q_OBJECT
public:
/// Constructor
/// @param config The configuration document smoothing
/// @param hyperion The hyperion parent instance
///
LinearColorSmoothing(const QJsonDocument &config, Hyperion *hyperion);
/// LED values as input for the smoothing filter
///
/// @param ledValues The color-value per led
/// @return Zero on success else negative
///
virtual int updateLedValues(const std::vector<ColorRgb> &ledValues);
void setEnable(bool enable);
void setPause(bool pause);
bool pause() const { return _pause; }
bool enabled() const { return _enabled && !_pause; }
///
/// @brief Add a new smoothing configuration which can be used with selectConfig()
/// @param settlingTime_ms The buffer time
/// @param ledUpdateFrequency_hz The frequency of update
/// @param updateDelay The delay
///
/// @return The index of the configuration, which can be passed to selectConfig()
///
unsigned addConfig(int settlingTime_ms, double ledUpdateFrequency_hz = 25.0, unsigned updateDelay = 0);
///
/// @brief Update a smoothing cfg which can be used with selectConfig()
/// In case the ID does not exist, a smoothing cfg is added
///
/// @param cfgID Smoothing configuration item to be updated
/// @param settlingTime_ms The buffer time
/// @param ledUpdateFrequency_hz The frequency of update
/// @param updateDelay The delay
///
/// @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);
///
/// @brief select a smoothing configuration given by cfg index from addConfig()
/// @param cfg 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);
public slots:
///
/// @brief Handle settings update from Hyperion Settingsmanager emit or this constructor
/// @param type settingType from enum
/// @param config configuration object
///
void handleSettingsUpdate(settings::type type, const QJsonDocument &config);
private slots:
/// Timer callback which writes updated led values to the led device
void updateLeds();
///
/// @brief Handle component state changes
/// @param component The component
/// @param state The requested state
///
void componentStateChange(hyperion::Components component, bool state);
private:
/**
* Pushes the colors into the output queue and popping the head to the led-device
*
* @param ledColors The colors to queue
*/
void queueColors(const std::vector<ColorRgb> &ledColors);
void clearQueuedColors();
/// write updated values as input for the smoothing filter
///
/// @param ledValues The color-value per led
/// @return Zero on success else negative
///
virtual int write(const std::vector<ColorRgb> &ledValues);
/// Logger instance
Logger *_log;
/// Hyperion instance
Hyperion *_hyperion;
/// The interval at which to update the leds (msec)
int _updateInterval;
/// The time after which the updated led values have been fully applied (msec)
int64_t _settlingTime;
/// The Qt timer object
QTimer *_timer;
/// The timestamp at which the target data should be fully applied
int64_t _targetTime;
/// The target led data
std::vector<ColorRgb> _targetValues;
/// The timestamp of the previously written led data
int64_t _previousWriteTime;
/// The timestamp of the previously data interpolation
int64_t _previousInterpolationTime;
/// The previously written led data
std::vector<ColorRgb> _previousValues;
/// The number of updates to keep in the output queue (delayed) before being output
unsigned _outputDelay;
/// The output queue
std::deque<std::vector<ColorRgb>> _outputQueue;
/// A frame of led colors used for temporal smoothing
class REMEMBERED_FRAME
{
public:
/// The time this frame was received
int64_t time;
/// The led colors
std::vector<ColorRgb> colors;
REMEMBERED_FRAME ( REMEMBERED_FRAME && ) = default;
REMEMBERED_FRAME ( const REMEMBERED_FRAME & ) = default;
REMEMBERED_FRAME & operator= ( const REMEMBERED_FRAME & ) = default;
REMEMBERED_FRAME(const int64_t time, const std::vector<ColorRgb> colors)
: time(time)
, colors(colors)
{}
};
/// The type of smoothing to perform
SmoothingType _smoothingType;
/// The queue of temporarily remembered frames
std::deque<REMEMBERED_FRAME> _frameQueue;
/// Flag for pausing
bool _pause;
/// The rate at which color frames should be written to LED device.
double _outputRate;
/// The interval time in microseconds for writing of LED Frames.
int64_t _outputIntervalMicros;
/// The rate at which interpolation of LED frames should be performed.
double _interpolationRate;
/// The interval time in microseconds for interpolation of LED Frames.
int64_t _interpolationIntervalMicros;
/// Whether to apply temporal dithering to diffuse rounding errors when downsampling to 8-bit RGB colors.
bool _dithering;
/// The decay power > 0. A value of exactly 1 is linear decay, higher numbers indicate a faster decay rate.
double _decay;
/// 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;
/// Whether to pause output
bool pause;
/// The time of the smoothing window.
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;
// 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;
/// The number of frames the output is delayed
unsigned outputDelay;
/// Whether to apply temporal dithering to diffuse rounding errors when downsampling to 8-bit RGB colors. Improves color accuracy.
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<SMOOTHING_CFG> _cfgList;
unsigned _currentConfigId;
bool _enabled;
/// Pushes the colors into the frame queue and cleans outdated frames from memory.
///
/// @param ledColors The next colors to queue
void rememberFrame(const std::vector<ColorRgb> &ledColors);
/// Frees the LED frames that were queued for calculating the moving average.
void clearRememberedFrames();
/// (Re-)Initializes the color-component vectors with given number of values.
///
/// @param ledCount The number of colors.
void intitializeComponentVectors(const 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;
/// The average component colors red, green, blue of the leds
std::vector<floatT> meanValues;
/// The residual component errors of the leds
std::vector<floatT> residualErrors;
/// The accumulated led color values in 64-bit fixed point domain
std::vector<uint64_t> tempValues;
/// Writes the target frame RGB data to the LED device without any interpolation.
void writeDirect();
/// Writes the assembled RGB data to the LED device.
void writeFrame();
/// Assembles a frame of LED colors in order to write RGB data to the LED device.
/// Temporal dithering is applied to diffuse the downsampling error for RGB color components.
void assembleAndDitherFrame();
/// Assembles a frame of LED colors in order to write RGB data to the LED device.
/// No dithering is applied, RGB color components are just rounded to nearest integer.
void assembleFrame();
/// Prepares a frame of LED colors by interpolating using the current smoothing window
void interpolateFrame();
/// Performs a decay-based smoothing effect. The frames are interpolated based on their age and a given decay-power.
///
/// The ingress frames that were received during the current smoothing window are reduced using a weighted moving average
/// by applying the weighting-function to the color components of each frame.
///
/// 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);
/// Performs a linear smoothing effect
void performLinear(const int64_t now);
/// Aggregates the RGB components of the LED colors using the given weight and updates weighted accordingly
///
/// @param colors The LED colors to aggregate.
/// @param weighted The target vector, that accumulates the terms.
/// @param weight The weight to use.
static inline void aggregateComponents(const std::vector<ColorRgb>& colors, std::vector<uint64_t>& weighted, const floatT weight);
/// Gets the current time in microseconds from high precision system clock.
inline int64_t micros() const;
/// The time, when the rendering statistics were logged previously
int64_t _renderedStatTime;
/// The total number of frames that were rendered to the LED device
int64_t _renderedCounter;
/// The count of frames that have been rendered to the LED device when statistics were shown previously
int64_t _renderedStatCounter;
/// The total number of frames that were interpolated using the smoothing algorithm
int64_t _interpolationCounter;
/// The count of frames that have been interpolated when statistics were shown previously
int64_t _interpolationStatCounter;
/// Frame weighting function for finding the frame's integral value
///
/// @param frameStart The start of frame time.
/// @param frameEnd The end of frame time.
/// @param windowStart The window start time.
/// @returns The frame weight.
std::function<floatT(int64_t, int64_t, int64_t)> _weightFrame;
};

View File

@@ -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;

View File

@@ -94,7 +94,7 @@
"minimum": 0,
"maximum": 2048,
"default": 0,
"append": "edt_append_ms",
"append": "edt_append_frames",
"propertyOrder": 9
}
},