mirror of
				https://github.com/hyperion-project/hyperion.ng.git
				synced 2025-03-01 10:33:28 +00:00 
			
		
		
		
	Feature: Temporal Color Smoothing with variable decay-rate for long period average windows (#1043)
* Feature: Weighted Moving Average Smoothing with Decay * fix assign * try fix MSVC error related to always inline on static * use proper imports for windows * crossplatform inline declaration
This commit is contained in:
		@@ -282,6 +282,7 @@
 | 
			
		||||
    "edt_conf_enum_hsv": "HSV",
 | 
			
		||||
    "edt_conf_enum_left_right": "Left to right",
 | 
			
		||||
    "edt_conf_enum_linear": "Linear",
 | 
			
		||||
    "edt_conf_enum_decay": "Decay",
 | 
			
		||||
    "edt_conf_enum_logdebug": "Debug",
 | 
			
		||||
    "edt_conf_enum_logsilent": "Silent",
 | 
			
		||||
    "edt_conf_enum_logverbose": "Verbose",
 | 
			
		||||
@@ -375,6 +376,14 @@
 | 
			
		||||
    "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_interpolationRate_expl": "Speed of the calculation of smooth intermediate frames.",
 | 
			
		||||
    "edt_conf_smooth_interpolationRate_title": "Interpolation Rate",
 | 
			
		||||
    "edt_conf_smooth_outputRate_title": "Output Rate",
 | 
			
		||||
    "edt_conf_smooth_outputRate_expl": "The output speed to your led controller.",
 | 
			
		||||
    "edt_conf_smooth_decay_title": "Decay-Power",
 | 
			
		||||
    "edt_conf_smooth_decay_expl": "The speed of decay. 1 is linear, greater values are have stronger effect.",
 | 
			
		||||
    "edt_conf_smooth_dithering_title": "Dithering",
 | 
			
		||||
    "edt_conf_smooth_dithering_expl": "Improve color accuracy at high output speeds by alternating between adjacent colors.",
 | 
			
		||||
    "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)",
 | 
			
		||||
 
 | 
			
		||||
@@ -184,7 +184,7 @@ function initLanguageSelection()
 | 
			
		||||
	for (var i = 0; i < availLang.length; i++)
 | 
			
		||||
	{
 | 
			
		||||
		$("#language-select").append('<option value="'+i+'" selected="">'+availLangText[i]+'</option>');
 | 
			
		||||
	}	
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var langLocale = storedLang;
 | 
			
		||||
 | 
			
		||||
@@ -533,7 +533,7 @@ function createJsonEditor(container,schema,setconfig,usePanel,arrayre)
 | 
			
		||||
	{
 | 
			
		||||
		for(var key in editor.root.editors)
 | 
			
		||||
		{
 | 
			
		||||
			editor.getEditor("root."+key).setValue( window.serverConfig[key] );
 | 
			
		||||
			editor.getEditor("root."+key).setValue(Object.assign({}, editor.getEditor("root."+key).value, window.serverConfig[key] ));
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -48,12 +48,16 @@
 | 
			
		||||
 | 
			
		||||
	"smoothing" :
 | 
			
		||||
	{
 | 
			
		||||
		"enable"           : true,
 | 
			
		||||
		"type"             : "linear",
 | 
			
		||||
		"time_ms"          : 200,
 | 
			
		||||
		"updateFrequency"  : 25.0000,
 | 
			
		||||
		"updateDelay"      : 0,
 | 
			
		||||
		"continuousOutput" : true
 | 
			
		||||
		"enable"            : true,
 | 
			
		||||
		"type"              : "linear",
 | 
			
		||||
		"time_ms"           : 200,
 | 
			
		||||
		"updateFrequency"   : 25.0000,
 | 
			
		||||
		"interpolationRate" : 25.0000,
 | 
			
		||||
		"outputRate"        : 25.0000,
 | 
			
		||||
		"decay"             : 1,
 | 
			
		||||
		"dithering"         : false,
 | 
			
		||||
		"updateDelay"       : 0,
 | 
			
		||||
		"continuousOutput"  : true
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	"grabberV4L2" :
 | 
			
		||||
 
 | 
			
		||||
@@ -6,15 +6,48 @@
 | 
			
		||||
#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)
 | 
			
		||||
#define ALWAYS_INLINE __forceinline
 | 
			
		||||
#else
 | 
			
		||||
#define ALWAYS_INLINE inline
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
/// Clamps the rounded values to the byte-interval of [0, 255].
 | 
			
		||||
ALWAYS_INLINE long clampRounded(const floatT x) {
 | 
			
		||||
	return std::min(255l, std::max(0l, std::lroundf(x)));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// The number of bits that are used for shifting the fixed point values
 | 
			
		||||
const int FPShift = (sizeof(uint64_t)*8 - (12 + 9));
 | 
			
		||||
 | 
			
		||||
/// The number of bits that are reduce the shifting when converting from fixed to floating point. 8 bits = 256 values
 | 
			
		||||
const int SmallShiftBis = sizeof(uint8_t)*8;
 | 
			
		||||
 | 
			
		||||
/// The number of bits that are used for shifting the fixed point values plus SmallShiftBis
 | 
			
		||||
const int FPShiftSmall = (sizeof(uint64_t)*8 - (12 + 9 + SmallShiftBis));
 | 
			
		||||
 | 
			
		||||
const char* SETTINGS_KEY_SMOOTHING_TYPE = "type";
 | 
			
		||||
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";
 | 
			
		||||
 | 
			
		||||
using namespace hyperion;
 | 
			
		||||
 | 
			
		||||
const int64_t  DEFAUL_SETTLINGTIME    = 200;	// settlingtime in ms
 | 
			
		||||
const double   DEFAUL_UPDATEFREQUENCY = 25;	// updatefrequncy in hz
 | 
			
		||||
const int64_t  DEFAUL_UPDATEINTERVALL = static_cast<int64_t>(1000 / DEFAUL_UPDATEFREQUENCY); // updateintervall in ms
 | 
			
		||||
const unsigned DEFAUL_OUTPUTDEPLAY    = 0;	// outputdelay in ms
 | 
			
		||||
const int64_t DEFAUL_SETTLINGTIME = 200;													// settlingtime in ms
 | 
			
		||||
const double DEFAUL_UPDATEFREQUENCY = 25;													// updatefrequncy in hz
 | 
			
		||||
const int64_t DEFAUL_UPDATEINTERVALL = static_cast<int64_t>(1000 / DEFAUL_UPDATEFREQUENCY); // updateintervall in ms
 | 
			
		||||
const unsigned DEFAUL_OUTPUTDEPLAY = 0;														// outputdelay in ms
 | 
			
		||||
 | 
			
		||||
LinearColorSmoothing::LinearColorSmoothing(const QJsonDocument& config, Hyperion* hyperion)
 | 
			
		||||
LinearColorSmoothing::LinearColorSmoothing(const QJsonDocument &config, Hyperion *hyperion)
 | 
			
		||||
	: QObject(hyperion)
 | 
			
		||||
	, _log(Logger::getInstance("SMOOTHING"))
 | 
			
		||||
	, _hyperion(hyperion)
 | 
			
		||||
@@ -22,11 +55,13 @@ LinearColorSmoothing::LinearColorSmoothing(const QJsonDocument& config, Hyperion
 | 
			
		||||
	, _settlingTime(DEFAUL_SETTLINGTIME)
 | 
			
		||||
	, _timer(new QTimer(this))
 | 
			
		||||
	, _outputDelay(DEFAUL_OUTPUTDEPLAY)
 | 
			
		||||
	, _smoothingType(SmoothingType::Linear)
 | 
			
		||||
	, _writeToLedsEnable(false)
 | 
			
		||||
	, _continuousOutput(false)
 | 
			
		||||
	, _pause(false)
 | 
			
		||||
	, _currentConfigId(0)
 | 
			
		||||
	, _enabled(false)
 | 
			
		||||
	, tempValues(std::vector<uint64_t>(0, 0l))
 | 
			
		||||
{
 | 
			
		||||
	// init cfg 0 (default)
 | 
			
		||||
	addConfig(DEFAUL_SETTLINGTIME, DEFAUL_UPDATEFREQUENCY, DEFAUL_OUTPUTDEPLAY);
 | 
			
		||||
@@ -34,38 +69,54 @@ LinearColorSmoothing::LinearColorSmoothing(const QJsonDocument& config, Hyperion
 | 
			
		||||
	selectConfig(0, true);
 | 
			
		||||
 | 
			
		||||
	// add pause on cfg 1
 | 
			
		||||
	SMOOTHING_CFG cfg = {true, 0, 0, 0};
 | 
			
		||||
	SMOOTHING_CFG cfg = {SmoothingType::Linear, 0, 0, 0, 0, 0, false};
 | 
			
		||||
	_cfgList.append(cfg);
 | 
			
		||||
 | 
			
		||||
	// listen for comp changes
 | 
			
		||||
	connect(_hyperion, &Hyperion::compStateChangeRequest, this, &LinearColorSmoothing::componentStateChange);
 | 
			
		||||
	// timer
 | 
			
		||||
	connect(_timer, &QTimer::timeout, this, &LinearColorSmoothing::updateLeds);
 | 
			
		||||
 | 
			
		||||
	Info(_log, "LinearColorSmoothing sizeof floatT == %d", (sizeof(floatT)));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LinearColorSmoothing::handleSettingsUpdate(settings::type type, const QJsonDocument& config)
 | 
			
		||||
void LinearColorSmoothing::handleSettingsUpdate(settings::type type, const QJsonDocument &config)
 | 
			
		||||
{
 | 
			
		||||
	if(type == settings::SMOOTHING)
 | 
			
		||||
	if (type == settings::SMOOTHING)
 | 
			
		||||
	{
 | 
			
		||||
		//	std::cout << "LinearColorSmoothing::handleSettingsUpdate" << std::endl;
 | 
			
		||||
		//	std::cout << config.toJson().toStdString() << std::endl;
 | 
			
		||||
 | 
			
		||||
		QJsonObject obj = config.object();
 | 
			
		||||
		if(enabled() != obj["enable"].toBool(true))
 | 
			
		||||
		if (enabled() != obj["enable"].toBool(true))
 | 
			
		||||
			setEnable(obj["enable"].toBool(true));
 | 
			
		||||
 | 
			
		||||
		_continuousOutput = obj["continuousOutput"].toBool(true);
 | 
			
		||||
 | 
			
		||||
		SMOOTHING_CFG cfg = {false,
 | 
			
		||||
							 static_cast<int64_t>(obj["time_ms"].toInt(DEFAUL_SETTLINGTIME)),
 | 
			
		||||
							 static_cast<int64_t>(1000.0/obj["updateFrequency"].toDouble(DEFAUL_UPDATEFREQUENCY)),
 | 
			
		||||
							 static_cast<unsigned>(obj["updateDelay"].toInt(DEFAUL_OUTPUTDEPLAY))
 | 
			
		||||
							};
 | 
			
		||||
		SMOOTHING_CFG cfg = {SmoothingType::Linear,true, 0, 0, 0, 0, 0, false, 1};
 | 
			
		||||
 | 
			
		||||
		const QString typeString = obj[SETTINGS_KEY_SMOOTHING_TYPE].toString();
 | 
			
		||||
 | 
			
		||||
		if(typeString == "linear") {
 | 
			
		||||
			cfg.smoothingType = SmoothingType::Linear;
 | 
			
		||||
		} else if(typeString == "decay") {
 | 
			
		||||
			cfg.smoothingType = SmoothingType::Decay;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		cfg.pause = false;
 | 
			
		||||
		cfg.settlingTime = static_cast<int64_t>(obj["time_ms"].toInt(DEFAUL_SETTLINGTIME));
 | 
			
		||||
		cfg.updateInterval = static_cast<int64_t>(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);
 | 
			
		||||
 | 
			
		||||
		//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;
 | 
			
		||||
 | 
			
		||||
		// if current id is 0, we need to apply the settings (forced)
 | 
			
		||||
		if( _currentConfigId == 0)
 | 
			
		||||
		if (_currentConfigId == 0)
 | 
			
		||||
		{
 | 
			
		||||
			//Debug( _log, "_currentConfigId == 0");
 | 
			
		||||
			selectConfig(0, true);
 | 
			
		||||
@@ -79,15 +130,18 @@ void LinearColorSmoothing::handleSettingsUpdate(settings::type type, const QJson
 | 
			
		||||
 | 
			
		||||
int LinearColorSmoothing::write(const std::vector<ColorRgb> &ledValues)
 | 
			
		||||
{
 | 
			
		||||
	_targetTime = QDateTime::currentMSecsSinceEpoch() + _settlingTime;
 | 
			
		||||
	_targetTime = micros() + (MS_PER_MICRO * _settlingTime);
 | 
			
		||||
	_targetValues = ledValues;
 | 
			
		||||
 | 
			
		||||
	rememberFrame(ledValues);
 | 
			
		||||
 | 
			
		||||
	// received a new target color
 | 
			
		||||
	if (_previousValues.empty())
 | 
			
		||||
	{
 | 
			
		||||
		// not initialized yet
 | 
			
		||||
		_previousTime = QDateTime::currentMSecsSinceEpoch();
 | 
			
		||||
		_previousWriteTime = micros();
 | 
			
		||||
		_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));
 | 
			
		||||
@@ -96,7 +150,7 @@ int LinearColorSmoothing::write(const std::vector<ColorRgb> &ledValues)
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int LinearColorSmoothing::updateLedValues(const std::vector<ColorRgb>& ledValues)
 | 
			
		||||
int LinearColorSmoothing::updateLedValues(const std::vector<ColorRgb> &ledValues)
 | 
			
		||||
{
 | 
			
		||||
	int retval = 0;
 | 
			
		||||
	if (!_enabled)
 | 
			
		||||
@@ -110,75 +164,366 @@ int LinearColorSmoothing::updateLedValues(const std::vector<ColorRgb>& ledValues
 | 
			
		||||
	return retval;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LinearColorSmoothing::updateLeds()
 | 
			
		||||
void LinearColorSmoothing::intitializeComponentVectors(const size_t ledCount)
 | 
			
		||||
{
 | 
			
		||||
	int64_t now = QDateTime::currentMSecsSinceEpoch();
 | 
			
		||||
	int64_t deltaTime = _targetTime - now;
 | 
			
		||||
 | 
			
		||||
	//Debug(_log, "elapsed Time [%d], _targetTime [%d] - now [%d], deltaTime [%d]", now -_previousTime, _targetTime, now, deltaTime);
 | 
			
		||||
	if (deltaTime < 0)
 | 
			
		||||
	// (Re-)Initialize the color-vectors that store the Mean-Value
 | 
			
		||||
	if (_ledCount != ledCount)
 | 
			
		||||
	{
 | 
			
		||||
		_previousValues = _targetValues;
 | 
			
		||||
		_previousTime = now;
 | 
			
		||||
		_ledCount = ledCount;
 | 
			
		||||
 | 
			
		||||
		queueColors(_previousValues);
 | 
			
		||||
		_writeToLedsEnable = _continuousOutput;
 | 
			
		||||
		const size_t len = 3 * ledCount;
 | 
			
		||||
 | 
			
		||||
		meanValues = std::vector<floatT>(len, 0.0f);
 | 
			
		||||
		residualErrors = std::vector<floatT>(len, 0.0f);
 | 
			
		||||
		tempValues = std::vector<uint64_t>(len, 0l);
 | 
			
		||||
	}
 | 
			
		||||
	else
 | 
			
		||||
 | 
			
		||||
	// Zero the temp vector
 | 
			
		||||
	std::fill(tempValues.begin(), tempValues.end(), 0l);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LinearColorSmoothing::writeDirect()
 | 
			
		||||
{
 | 
			
		||||
	const int64_t now = micros();
 | 
			
		||||
	_previousValues = _targetValues;
 | 
			
		||||
	_previousWriteTime = now;
 | 
			
		||||
 | 
			
		||||
	queueColors(_previousValues);
 | 
			
		||||
	_writeToLedsEnable = _continuousOutput;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
void LinearColorSmoothing::writeFrame()
 | 
			
		||||
{
 | 
			
		||||
	const int64_t now = micros();
 | 
			
		||||
	_previousWriteTime = now;
 | 
			
		||||
	queueColors(_previousValues);
 | 
			
		||||
	_writeToLedsEnable = _continuousOutput;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
ALWAYS_INLINE int64_t LinearColorSmoothing::micros() const
 | 
			
		||||
{
 | 
			
		||||
	const auto now = std::chrono::high_resolution_clock::now();
 | 
			
		||||
	return (std::chrono::duration_cast<std::chrono::microseconds>(now.time_since_epoch())).count();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LinearColorSmoothing::assembleAndDitherFrame()
 | 
			
		||||
{
 | 
			
		||||
	if (meanValues.empty())
 | 
			
		||||
	{
 | 
			
		||||
		_writeToLedsEnable = true;
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
		//std::cout << "LinearColorSmoothing::updateLeds> _previousValues: "; LedDevice::printLedValues ( _previousValues );
 | 
			
		||||
	// The number of leds present in each frame
 | 
			
		||||
	const size_t N = _targetValues.size();
 | 
			
		||||
 | 
			
		||||
		float k = 1.0f - 1.0f * deltaTime / (_targetTime - _previousTime);
 | 
			
		||||
	for (size_t i = 0; i < N; ++i)
 | 
			
		||||
	{
 | 
			
		||||
		// Add residuals for error diffusion (temporal dithering)
 | 
			
		||||
		const floatT fr = meanValues[3 * i + 0] + residualErrors[3 * i + 0];
 | 
			
		||||
		const floatT fg = meanValues[3 * i + 1] + residualErrors[3 * i + 1];
 | 
			
		||||
		const floatT fb = meanValues[3 * i + 2] + residualErrors[3 * i + 2];
 | 
			
		||||
 | 
			
		||||
		int reddif = 0, greendif = 0, bluedif = 0;
 | 
			
		||||
		// Convert to to 8-bit value
 | 
			
		||||
		const long ir = clampRounded(fr);
 | 
			
		||||
		const long ig = clampRounded(fg);
 | 
			
		||||
		const long ib = clampRounded(fb);
 | 
			
		||||
 | 
			
		||||
		for (size_t i = 0; i < _previousValues.size(); ++i)
 | 
			
		||||
		{
 | 
			
		||||
			ColorRgb & prev   = _previousValues[i];
 | 
			
		||||
			ColorRgb & target = _targetValues[i];
 | 
			
		||||
		// Update the colors
 | 
			
		||||
		ColorRgb &prev = _previousValues[i];
 | 
			
		||||
		prev.red = (uint8_t)ir;
 | 
			
		||||
		prev.green = (uint8_t)ig;
 | 
			
		||||
		prev.blue = (uint8_t)ib;
 | 
			
		||||
 | 
			
		||||
			reddif   = target.red   - prev.red;
 | 
			
		||||
			greendif = target.green - prev.green;
 | 
			
		||||
			bluedif  = target.blue  - prev.blue;
 | 
			
		||||
 | 
			
		||||
			prev.red   += (reddif   < 0 ? -1:1) * std::ceil(k * std::abs(reddif));
 | 
			
		||||
			prev.green += (greendif < 0 ? -1:1) * std::ceil(k * std::abs(greendif));
 | 
			
		||||
			prev.blue  += (bluedif  < 0 ? -1:1) * std::ceil(k * std::abs(bluedif));
 | 
			
		||||
		}
 | 
			
		||||
		_previousTime = now;
 | 
			
		||||
 | 
			
		||||
		//std::cout << "LinearColorSmoothing::updateLeds> _targetValues: "; LedDevice::printLedValues ( _targetValues );
 | 
			
		||||
 | 
			
		||||
		queueColors(_previousValues);
 | 
			
		||||
		// Determine the component errors
 | 
			
		||||
		residualErrors[3 * i + 0] = fr - ir;
 | 
			
		||||
		residualErrors[3 * i + 1] = fg - ig;
 | 
			
		||||
		residualErrors[3 * i + 2] = fb - ib;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LinearColorSmoothing::queueColors(const std::vector<ColorRgb> & ledColors)
 | 
			
		||||
void LinearColorSmoothing::assembleFrame()
 | 
			
		||||
{
 | 
			
		||||
	if (meanValues.empty())
 | 
			
		||||
	{
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// The number of leds present in each frame
 | 
			
		||||
	const size_t N = _targetValues.size();
 | 
			
		||||
 | 
			
		||||
	for (size_t i = 0; i < N; ++i)
 | 
			
		||||
	{
 | 
			
		||||
		// Convert to to 8-bit value
 | 
			
		||||
		const long ir = clampRounded(meanValues[3 * i + 0]);
 | 
			
		||||
		const long ig = clampRounded(meanValues[3 * i + 1]);
 | 
			
		||||
		const long ib = clampRounded(meanValues[3 * i + 2]);
 | 
			
		||||
 | 
			
		||||
		// Update the colors
 | 
			
		||||
		ColorRgb &prev = _previousValues[i];
 | 
			
		||||
		prev.red = (uint8_t)ir;
 | 
			
		||||
		prev.green = (uint8_t)ig;
 | 
			
		||||
		prev.blue = (uint8_t)ib;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ALWAYS_INLINE void LinearColorSmoothing::aggregateComponents(const std::vector<ColorRgb>& colors, std::vector<uint64_t>& weighted, const floatT weight) {
 | 
			
		||||
	// Determine the integer-scale by converting the weight to fixed point
 | 
			
		||||
	const uint64_t scale = (1l<<FPShift) * static_cast<double>(weight);
 | 
			
		||||
 | 
			
		||||
	const size_t N = colors.size();
 | 
			
		||||
 | 
			
		||||
	for (size_t i = 0; i < N; ++i)
 | 
			
		||||
	{
 | 
			
		||||
		const ColorRgb &color = colors[i];
 | 
			
		||||
 | 
			
		||||
		// Scale the colors
 | 
			
		||||
		const uint64_t red = scale * color.red;
 | 
			
		||||
		const uint64_t green = scale * color.green;
 | 
			
		||||
		const uint64_t blue = scale * color.blue;
 | 
			
		||||
 | 
			
		||||
		// Accumulate in the vector
 | 
			
		||||
		weighted[3 * i + 0] += red;
 | 
			
		||||
		weighted[3 * i + 1] += green;
 | 
			
		||||
		weighted[3 * i + 2] += blue;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LinearColorSmoothing::interpolateFrame()
 | 
			
		||||
{
 | 
			
		||||
	const int64_t now = micros();
 | 
			
		||||
 | 
			
		||||
	// The number of leds present in each frame
 | 
			
		||||
	const size_t N = _targetValues.size();
 | 
			
		||||
 | 
			
		||||
	intitializeComponentVectors(N);
 | 
			
		||||
 | 
			
		||||
	/// Time where the frame has been shown
 | 
			
		||||
	int64_t frameStart;
 | 
			
		||||
 | 
			
		||||
	/// Time where the frame display would have ended
 | 
			
		||||
	int64_t frameEnd = now;
 | 
			
		||||
 | 
			
		||||
	/// Time where the current window has started
 | 
			
		||||
	const int64_t windowStart = now - (MS_PER_MICRO * _settlingTime);
 | 
			
		||||
 | 
			
		||||
	/// The total weight of the frames that were included in our window; sum of the individual weights
 | 
			
		||||
	floatT fs = 0.0f;
 | 
			
		||||
 | 
			
		||||
	// To calculate the mean component we iterate over all relevant frames;
 | 
			
		||||
	// from the most recent to the oldest frame that still clips our moving-average window given by time (now)
 | 
			
		||||
	for (auto it = _frameQueue.rbegin(); it != _frameQueue.rend() && frameEnd > windowStart; ++it)
 | 
			
		||||
	{
 | 
			
		||||
		// Starting time of a frame in the window is clipped to the window start
 | 
			
		||||
		frameStart = std::max(windowStart, it->time);
 | 
			
		||||
 | 
			
		||||
		// Weight the current frame relative to the overall window based on start and end times
 | 
			
		||||
		const floatT weight = _weightFrame(frameStart, frameEnd, windowStart);
 | 
			
		||||
		fs += weight;
 | 
			
		||||
 | 
			
		||||
		// Aggregate the RGB components of this frame's LED colors using the individual weighting
 | 
			
		||||
		aggregateComponents(it->colors, tempValues, weight);
 | 
			
		||||
 | 
			
		||||
		// The previous (earlier) frame display has ended when the current frame stared to show,
 | 
			
		||||
		// so we can use this as the frame-end time for next iteration
 | 
			
		||||
		frameEnd = frameStart;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/// The inverse scaling factor for the color components, clamped to (0, 1.0]; 1.0 for fs < 1, 1 : fs otherwise
 | 
			
		||||
	const floatT inv_fs = ((fs < 1.0f) ? 1.0f : 1.0f / fs) / (1 << SmallShiftBis);
 | 
			
		||||
 | 
			
		||||
	// Normalize the mean component values for the window (fs)
 | 
			
		||||
	for (size_t i = 0; i < 3 * N; ++i)
 | 
			
		||||
	{
 | 
			
		||||
		meanValues[i] = (tempValues[i] >> FPShiftSmall) * inv_fs;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_previousInterpolationTime = now;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LinearColorSmoothing::performDecay(const int64_t now) {
 | 
			
		||||
	/// The target time when next frame interpolation should be performed
 | 
			
		||||
	const int64_t interpolationTarget = _previousInterpolationTime + _interpolationIntervalMicros;
 | 
			
		||||
 | 
			
		||||
	/// The target time when next write operation should be performed
 | 
			
		||||
	const int64_t writeTarget = _previousWriteTime + _outputIntervalMicros;
 | 
			
		||||
 | 
			
		||||
	/// Whether a frame interpolation is pending
 | 
			
		||||
	const bool interpolatePending = now > interpolationTarget;
 | 
			
		||||
 | 
			
		||||
	/// Whether a write is pending
 | 
			
		||||
	const bool writePending = now > writeTarget;
 | 
			
		||||
 | 
			
		||||
	// Check whether a new interpolation frame is due
 | 
			
		||||
	if (interpolatePending)
 | 
			
		||||
	{
 | 
			
		||||
		interpolateFrame();
 | 
			
		||||
		++_interpolationCounter;
 | 
			
		||||
 | 
			
		||||
		// Assemble the frame now when no dithering is applied
 | 
			
		||||
		if(!_dithering) {
 | 
			
		||||
			assembleFrame();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Check whether to frame output is due
 | 
			
		||||
	if (writePending)
 | 
			
		||||
	{
 | 
			
		||||
		// Dither the frame to diffuse rounding errors
 | 
			
		||||
		if(_dithering) {
 | 
			
		||||
			assembleAndDitherFrame();
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		writeFrame();
 | 
			
		||||
		++_renderedCounter;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Check for sleep when no operation is pending.
 | 
			
		||||
	// As our QTimer is not capable of sub 1ms timing but instead performs spinning -
 | 
			
		||||
	// we have to do µsec-sleep to free CPU time; otherwise the thread would consume 100% CPU time.
 | 
			
		||||
	if(_updateInterval <= 0 && !(interpolatePending || writePending)) {
 | 
			
		||||
		const int64_t nextActionExpected = std::min(interpolationTarget, writeTarget);
 | 
			
		||||
		const int64_t microsTillNextAction = nextActionExpected - now;
 | 
			
		||||
		const int64_t SLEEP_MAX_MICROS = 1000l; // We want to use usleep for up to 1ms
 | 
			
		||||
		const int64_t SLEEP_RES_MICROS = 100l; // Expected resolution is >= 100µs on stock linux
 | 
			
		||||
 | 
			
		||||
		if(microsTillNextAction > SLEEP_RES_MICROS) {
 | 
			
		||||
			const int64_t wait = std::min(microsTillNextAction - SLEEP_RES_MICROS, SLEEP_MAX_MICROS);
 | 
			
		||||
			//usleep(wait);
 | 
			
		||||
			std::this_thread::sleep_for(std::chrono::microseconds(wait));
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Write stats every 30 sec
 | 
			
		||||
	if ((now > (_renderedStatTime + 30 * 1000000)) && (_renderedCounter > _renderedStatCounter))
 | 
			
		||||
	{
 | 
			
		||||
		Info(_log, "decay - rendered frames [%d] (%f/s), interpolated frames [%d] (%f/s) in [%f ms]"
 | 
			
		||||
		, _renderedCounter - _renderedStatCounter
 | 
			
		||||
		, (1.0f * (_renderedCounter - _renderedStatCounter) / ((now - _renderedStatTime) / 1000000.0f))
 | 
			
		||||
		, _interpolationCounter - _interpolationStatCounter
 | 
			
		||||
		, (1.0f * (_interpolationCounter - _interpolationStatCounter) / ((now - _renderedStatTime) / 1000000.0f))
 | 
			
		||||
		, (now - _renderedStatTime) / 1000.0f
 | 
			
		||||
		);
 | 
			
		||||
		_renderedStatTime = now;
 | 
			
		||||
		_renderedStatCounter = _renderedCounter;
 | 
			
		||||
		_interpolationStatCounter = _interpolationCounter;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LinearColorSmoothing::performLinear(const int64_t now) {
 | 
			
		||||
	const int64_t deltaTime = _targetTime - now;
 | 
			
		||||
	const float k = 1.0f - 1.0f * deltaTime / (_targetTime - _previousWriteTime);
 | 
			
		||||
	const size_t N = _previousValues.size();
 | 
			
		||||
 | 
			
		||||
	for (size_t i = 0; i < N; ++i)
 | 
			
		||||
	{
 | 
			
		||||
		const ColorRgb &target = _targetValues[i];
 | 
			
		||||
		ColorRgb &prev         = _previousValues[i];
 | 
			
		||||
 | 
			
		||||
		const int reddif   = target.red   - prev.red;
 | 
			
		||||
		const int greendif = target.green - prev.green;
 | 
			
		||||
		const int bluedif  = target.blue  - prev.blue;
 | 
			
		||||
 | 
			
		||||
		prev.red   += (reddif   < 0 ? -1:1) * std::ceil(k * std::abs(reddif));
 | 
			
		||||
		prev.green += (greendif < 0 ? -1:1) * std::ceil(k * std::abs(greendif));
 | 
			
		||||
		prev.blue  += (bluedif  < 0 ? -1:1) * std::ceil(k * std::abs(bluedif));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	writeFrame();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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();
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	switch (_smoothingType)
 | 
			
		||||
	{
 | 
			
		||||
	case Decay:
 | 
			
		||||
		performDecay(now);
 | 
			
		||||
		break;
 | 
			
		||||
 | 
			
		||||
	case Linear:
 | 
			
		||||
		// Linear interpolation is default
 | 
			
		||||
	default:
 | 
			
		||||
		performLinear(now);
 | 
			
		||||
		break;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LinearColorSmoothing::rememberFrame(const std::vector<ColorRgb> &ledColors)
 | 
			
		||||
{
 | 
			
		||||
	//Info(_log, "rememberFrame -  before _frameQueue.size() [%d]", _frameQueue.size());
 | 
			
		||||
 | 
			
		||||
	const int64_t now = micros();
 | 
			
		||||
 | 
			
		||||
	// Maintain the queue by removing outdated frames
 | 
			
		||||
	const int64_t windowStart = now - (MS_PER_MICRO * _settlingTime);
 | 
			
		||||
 | 
			
		||||
	int p = -1; // Start with -1 instead of 0, so we keep the last frame at least partially clipping the window
 | 
			
		||||
 | 
			
		||||
	// As the frames are ordered chronologically we scan from the front (oldest) till we find the first fresh frame
 | 
			
		||||
	for (auto it = _frameQueue.begin(); it != _frameQueue.end() && it->time < windowStart; ++it)
 | 
			
		||||
	{
 | 
			
		||||
		++p;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (p > 0)
 | 
			
		||||
	{
 | 
			
		||||
		//Info(_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);
 | 
			
		||||
 | 
			
		||||
	//Info(_log, "rememberFrame -  after _frameQueue.size() [%d]", _frameQueue.size());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
void LinearColorSmoothing::clearRememberedFrames()
 | 
			
		||||
{
 | 
			
		||||
	_frameQueue.clear();
 | 
			
		||||
 | 
			
		||||
	_ledCount = 0;
 | 
			
		||||
	meanValues.clear();
 | 
			
		||||
	residualErrors.clear();
 | 
			
		||||
	tempValues.clear();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LinearColorSmoothing::queueColors(const std::vector<ColorRgb> &ledColors)
 | 
			
		||||
{
 | 
			
		||||
	//Debug(_log, "queueColors -  _outputDelay[%d] _outputQueue.size() [%d], _writeToLedsEnable[%d]", _outputDelay, _outputQueue.size(), _writeToLedsEnable);
 | 
			
		||||
	if (_outputDelay == 0)
 | 
			
		||||
	{
 | 
			
		||||
		// No output delay => immediate write
 | 
			
		||||
		if ( _writeToLedsEnable && !_pause)
 | 
			
		||||
		if (_writeToLedsEnable && !_pause)
 | 
			
		||||
		{
 | 
			
		||||
//			if ( ledColors.size() == 0 )
 | 
			
		||||
//				qFatal ("No LedValues! - in LinearColorSmoothing::queueColors() - _outputDelay == 0");
 | 
			
		||||
//			else
 | 
			
		||||
			//			if ( ledColors.size() == 0 )
 | 
			
		||||
			//				qFatal ("No LedValues! - in LinearColorSmoothing::queueColors() - _outputDelay == 0");
 | 
			
		||||
			//			else
 | 
			
		||||
			emit _hyperion->ledDeviceData(ledColors);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	else
 | 
			
		||||
	{
 | 
			
		||||
		// Push new colors in the delay-buffer
 | 
			
		||||
		if ( _writeToLedsEnable )
 | 
			
		||||
		if (_writeToLedsEnable)
 | 
			
		||||
			_outputQueue.push_back(ledColors);
 | 
			
		||||
 | 
			
		||||
		// If the delay-buffer is filled pop the front and write to device
 | 
			
		||||
		if (_outputQueue.size() > 0 )
 | 
			
		||||
		if (_outputQueue.size() > 0)
 | 
			
		||||
		{
 | 
			
		||||
			if ( _outputQueue.size() > _outputDelay || !_writeToLedsEnable )
 | 
			
		||||
			if (_outputQueue.size() > _outputDelay || !_writeToLedsEnable)
 | 
			
		||||
			{
 | 
			
		||||
				if (!_pause)
 | 
			
		||||
				{
 | 
			
		||||
@@ -196,17 +541,19 @@ void LinearColorSmoothing::clearQueuedColors()
 | 
			
		||||
	_previousValues.clear();
 | 
			
		||||
 | 
			
		||||
	_targetValues.clear();
 | 
			
		||||
 | 
			
		||||
	clearRememberedFrames();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void LinearColorSmoothing::componentStateChange(hyperion::Components component, bool state)
 | 
			
		||||
{
 | 
			
		||||
	_writeToLedsEnable = state;
 | 
			
		||||
	if(component == hyperion::COMP_LEDDEVICE)
 | 
			
		||||
	if (component == hyperion::COMP_LEDDEVICE)
 | 
			
		||||
	{
 | 
			
		||||
		clearQueuedColors();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if(component == hyperion::COMP_SMOOTHING)
 | 
			
		||||
	if (component == hyperion::COMP_SMOOTHING)
 | 
			
		||||
	{
 | 
			
		||||
		setEnable(state);
 | 
			
		||||
	}
 | 
			
		||||
@@ -230,7 +577,17 @@ void LinearColorSmoothing::setPause(bool pause)
 | 
			
		||||
 | 
			
		||||
unsigned LinearColorSmoothing::addConfig(int settlingTime_ms, double ledUpdateFrequency_hz, unsigned updateDelay)
 | 
			
		||||
{
 | 
			
		||||
	SMOOTHING_CFG cfg = {false, settlingTime_ms, int64_t(1000.0/ledUpdateFrequency_hz), updateDelay};
 | 
			
		||||
	SMOOTHING_CFG cfg = {
 | 
			
		||||
		SmoothingType::Linear,
 | 
			
		||||
		false,
 | 
			
		||||
		settlingTime_ms,
 | 
			
		||||
		int64_t(1000.0 / ledUpdateFrequency_hz),
 | 
			
		||||
		ledUpdateFrequency_hz,
 | 
			
		||||
		ledUpdateFrequency_hz,
 | 
			
		||||
		updateDelay,
 | 
			
		||||
		false,
 | 
			
		||||
		1
 | 
			
		||||
	};
 | 
			
		||||
	_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 );
 | 
			
		||||
@@ -240,17 +597,26 @@ unsigned LinearColorSmoothing::addConfig(int settlingTime_ms, double ledUpdateFr
 | 
			
		||||
unsigned LinearColorSmoothing::updateConfig(unsigned cfgID, int settlingTime_ms, double ledUpdateFrequency_hz, unsigned updateDelay)
 | 
			
		||||
{
 | 
			
		||||
	unsigned updatedCfgID = cfgID;
 | 
			
		||||
	if ( cfgID < static_cast<unsigned>(_cfgList.count()) )
 | 
			
		||||
	if (cfgID < static_cast<unsigned>(_cfgList.count()))
 | 
			
		||||
	{
 | 
			
		||||
		SMOOTHING_CFG cfg = {false, settlingTime_ms, int64_t(1000.0/ledUpdateFrequency_hz), updateDelay};
 | 
			
		||||
		SMOOTHING_CFG cfg = {
 | 
			
		||||
			SmoothingType::Linear,
 | 
			
		||||
			false,
 | 
			
		||||
			settlingTime_ms,
 | 
			
		||||
			int64_t(1000.0 / ledUpdateFrequency_hz),
 | 
			
		||||
			ledUpdateFrequency_hz,
 | 
			
		||||
			ledUpdateFrequency_hz,
 | 
			
		||||
			updateDelay,
 | 
			
		||||
			false,
 | 
			
		||||
			1};
 | 
			
		||||
		_cfgList[updatedCfgID] = cfg;
 | 
			
		||||
	}
 | 
			
		||||
	else
 | 
			
		||||
	{
 | 
			
		||||
		updatedCfgID = addConfig ( settlingTime_ms, ledUpdateFrequency_hz, updateDelay);
 | 
			
		||||
		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 );
 | 
			
		||||
	//	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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -264,18 +630,54 @@ bool LinearColorSmoothing::selectConfig(unsigned cfg, bool force)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	//Debug( _log, "selectConfig FORCED - _currentConfigId [%u], force [%d]", cfg, force);
 | 
			
		||||
	if ( cfg < (unsigned)_cfgList.count())
 | 
			
		||||
	if (cfg < (unsigned)_cfgList.count())
 | 
			
		||||
	{
 | 
			
		||||
		_settlingTime     = _cfgList[cfg].settlingTime;
 | 
			
		||||
		_outputDelay      = _cfgList[cfg].outputDelay;
 | 
			
		||||
		_pause            = _cfgList[cfg].pause;
 | 
			
		||||
		_smoothingType = _cfgList[cfg].smoothingType;
 | 
			
		||||
		_settlingTime = _cfgList[cfg].settlingTime;
 | 
			
		||||
		_outputDelay = _cfgList[cfg].outputDelay;
 | 
			
		||||
		_pause = _cfgList[cfg].pause;
 | 
			
		||||
		_outputRate = _cfgList[cfg].outputRate;
 | 
			
		||||
		_outputIntervalMicros = int64_t(1000000.0 / _outputRate); // 1s = 1e6 µs
 | 
			
		||||
		_interpolationRate = _cfgList[cfg].interpolationRate;
 | 
			
		||||
		_interpolationIntervalMicros = int64_t(1000000.0 / _interpolationRate);
 | 
			
		||||
		_dithering = _cfgList[cfg].dithering;
 | 
			
		||||
		_decay = _cfgList[cfg].decay;
 | 
			
		||||
		_invWindow = 1.0f / (MS_PER_MICRO * _settlingTime);
 | 
			
		||||
 | 
			
		||||
		// Set _weightFrame based on the given decay
 | 
			
		||||
		const float decay = _decay;
 | 
			
		||||
		const floatT inv_window = _invWindow;
 | 
			
		||||
 | 
			
		||||
		// For decay != 1 use power-based approach for calculating the moving average values
 | 
			
		||||
		if(std::abs(decay - 1.0f) > std::numeric_limits<float>::epsilon()) {
 | 
			
		||||
			// Exponential Decay
 | 
			
		||||
			_weightFrame = [inv_window,decay](const int64_t fs, const int64_t fe, const int64_t ws) {
 | 
			
		||||
				const floatT s = (fs - ws) * inv_window;
 | 
			
		||||
				const floatT t = (fe - ws) * inv_window;
 | 
			
		||||
 | 
			
		||||
				return (decay + 1) * (std::pow(t, decay) - std::pow(s, decay));
 | 
			
		||||
			};
 | 
			
		||||
		} else {
 | 
			
		||||
			// For decay == 1 use linear interpolation of the moving average values
 | 
			
		||||
			// Linear Decay
 | 
			
		||||
			_weightFrame = [inv_window](const int64_t fs, const int64_t fe, const int64_t ws) {
 | 
			
		||||
				// Linear weighting = (end - start) * scale
 | 
			
		||||
				return static_cast<floatT>((fe - fs) * inv_window);
 | 
			
		||||
			};
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		_renderedStatTime = micros();
 | 
			
		||||
		_renderedCounter = 0;
 | 
			
		||||
		_renderedStatCounter = 0;
 | 
			
		||||
		_interpolationCounter = 0;
 | 
			
		||||
		_interpolationStatCounter = 0;
 | 
			
		||||
 | 
			
		||||
		if (_cfgList[cfg].updateInterval != _updateInterval)
 | 
			
		||||
		{
 | 
			
		||||
 | 
			
		||||
			QMetaObject::invokeMethod(_timer, "stop", Qt::QueuedConnection);
 | 
			
		||||
			_updateInterval = _cfgList[cfg].updateInterval;
 | 
			
		||||
			if ( this->enabled() && this->_writeToLedsEnable )
 | 
			
		||||
			if (this->enabled() && this->_writeToLedsEnable)
 | 
			
		||||
			{
 | 
			
		||||
				//Debug( _log, "_cfgList[cfg].updateInterval != _updateInterval - Restart timer - _updateInterval [%d]", _updateInterval);
 | 
			
		||||
				QMetaObject::invokeMethod(_timer, "start", Qt::QueuedConnection, Q_ARG(int, _updateInterval));
 | 
			
		||||
@@ -290,6 +692,9 @@ bool LinearColorSmoothing::selectConfig(unsigned cfg, bool force)
 | 
			
		||||
		//	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;
 | 
			
		||||
		Info( _log, "%s - Time: %d ms, outputRate %f Hz, interpolationRate: %f Hz, timer: %d ms, Dithering: %d, Decay: %f -> HalfTime: %f ms", _smoothingType == SmoothingType::Decay ? "decay" : "linear", _settlingTime, _outputRate, _interpolationRate, _updateInterval, _dithering ? 1 : 0, _decay, thalf);
 | 
			
		||||
 | 
			
		||||
		return true;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@
 | 
			
		||||
 | 
			
		||||
// STL includes
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include <deque>
 | 
			
		||||
 | 
			
		||||
// Qt includes
 | 
			
		||||
#include <QVector>
 | 
			
		||||
@@ -13,14 +14,62 @@
 | 
			
		||||
// 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 Smooting class
 | 
			
		||||
///
 | 
			
		||||
/// This class processes the requested led values and forwards them to the device after applying
 | 
			
		||||
/// a linear smoothing effect. This class can be handled as a generic LedDevice.
 | 
			
		||||
/// 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
 | 
			
		||||
@@ -30,14 +79,14 @@ public:
 | 
			
		||||
	/// @param config    The configuration document smoothing
 | 
			
		||||
	/// @param hyperion  The hyperion parent instance
 | 
			
		||||
	///
 | 
			
		||||
	LinearColorSmoothing(const QJsonDocument& config, Hyperion* hyperion);
 | 
			
		||||
	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);
 | 
			
		||||
	virtual int updateLedValues(const std::vector<ColorRgb> &ledValues);
 | 
			
		||||
 | 
			
		||||
	void setEnable(bool enable);
 | 
			
		||||
	void setPause(bool pause);
 | 
			
		||||
@@ -52,7 +101,7 @@ public:
 | 
			
		||||
	///
 | 
			
		||||
	/// @return The index of the cfg which can be passed to selectConfig()
 | 
			
		||||
	///
 | 
			
		||||
	unsigned addConfig(int settlingTime_ms, double ledUpdateFrequency_hz=25.0, unsigned updateDelay=0);
 | 
			
		||||
	unsigned addConfig(int settlingTime_ms, double ledUpdateFrequency_hz = 25.0, unsigned updateDelay = 0);
 | 
			
		||||
 | 
			
		||||
	///
 | 
			
		||||
	/// @brief Update a smoothing cfg which can be used with selectConfig()
 | 
			
		||||
@@ -65,7 +114,7 @@ public:
 | 
			
		||||
	///
 | 
			
		||||
	/// @return The index of the cfg which can be passed to selectConfig()
 | 
			
		||||
	///
 | 
			
		||||
	unsigned updateConfig(unsigned cfgID, int settlingTime_ms, double ledUpdateFrequency_hz=25.0, unsigned updateDelay=0);
 | 
			
		||||
	unsigned updateConfig(unsigned cfgID, int settlingTime_ms, double ledUpdateFrequency_hz = 25.0, unsigned updateDelay = 0);
 | 
			
		||||
 | 
			
		||||
	///
 | 
			
		||||
	/// @brief select a smoothing cfg given by cfg index from addConfig()
 | 
			
		||||
@@ -82,7 +131,7 @@ public slots:
 | 
			
		||||
	/// @param type   settingyType from enum
 | 
			
		||||
	/// @param config configuration object
 | 
			
		||||
	///
 | 
			
		||||
	void handleSettingsUpdate(settings::type type, const QJsonDocument& config);
 | 
			
		||||
	void handleSettingsUpdate(settings::type type, const QJsonDocument &config);
 | 
			
		||||
 | 
			
		||||
private slots:
 | 
			
		||||
	/// Timer callback which writes updated led values to the led device
 | 
			
		||||
@@ -96,13 +145,12 @@ private slots:
 | 
			
		||||
	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 queueColors(const std::vector<ColorRgb> &ledColors);
 | 
			
		||||
	void clearQueuedColors();
 | 
			
		||||
 | 
			
		||||
	/// write updated values as input for the smoothing filter
 | 
			
		||||
@@ -113,10 +161,10 @@ private:
 | 
			
		||||
	virtual int write(const std::vector<ColorRgb> &ledValues);
 | 
			
		||||
 | 
			
		||||
	/// Logger instance
 | 
			
		||||
	Logger* _log;
 | 
			
		||||
	Logger *_log;
 | 
			
		||||
 | 
			
		||||
	/// Hyperion instance
 | 
			
		||||
	Hyperion* _hyperion;
 | 
			
		||||
	Hyperion *_hyperion;
 | 
			
		||||
 | 
			
		||||
	/// The interval at which to update the leds (msec)
 | 
			
		||||
	int64_t _updateInterval;
 | 
			
		||||
@@ -125,7 +173,7 @@ private:
 | 
			
		||||
	int64_t _settlingTime;
 | 
			
		||||
 | 
			
		||||
	/// The Qt timer object
 | 
			
		||||
	QTimer * _timer;
 | 
			
		||||
	QTimer *_timer;
 | 
			
		||||
 | 
			
		||||
	/// The timestamp at which the target data should be fully applied
 | 
			
		||||
	int64_t _targetTime;
 | 
			
		||||
@@ -134,15 +182,45 @@ private:
 | 
			
		||||
	std::vector<ColorRgb> _targetValues;
 | 
			
		||||
 | 
			
		||||
	/// The timestamp of the previously written led data
 | 
			
		||||
	int64_t _previousTime;
 | 
			
		||||
	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::list<std::vector<ColorRgb> > _outputQueue;
 | 
			
		||||
	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;
 | 
			
		||||
 | 
			
		||||
	/// Prevent sending data to device when no intput data is sent
 | 
			
		||||
	bool _writeToLedsEnable;
 | 
			
		||||
@@ -153,17 +231,146 @@ private:
 | 
			
		||||
	/// 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 temproral 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
 | 
			
		||||
	{
 | 
			
		||||
		bool     pause;
 | 
			
		||||
		int64_t  settlingTime;
 | 
			
		||||
		int64_t  updateInterval;
 | 
			
		||||
		unsigned outputDelay;
 | 
			
		||||
	};
 | 
			
		||||
		/// 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 millisecons of the timer used for scheduling LED update operations. A value of 0 indicates sub-millisecond timing.
 | 
			
		||||
		int64_t 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 temproral 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 config list
 | 
			
		||||
	QVector<SMOOTHING_CFG> _cfgList;
 | 
			
		||||
 | 
			
		||||
	unsigned _currentConfigId;
 | 
			
		||||
	bool   _enabled;
 | 
			
		||||
	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();
 | 
			
		||||
 | 
			
		||||
	/// Performes 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;
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -14,11 +14,10 @@
 | 
			
		||||
		{
 | 
			
		||||
			"type" : "string",
 | 
			
		||||
			"title" : "edt_conf_smooth_type_title",
 | 
			
		||||
			"enum" : ["linear"],
 | 
			
		||||
			"enum" : ["linear", "decay"],
 | 
			
		||||
			"default" : "linear",
 | 
			
		||||
			"options" : {
 | 
			
		||||
				"enum_titles" : ["edt_conf_enum_linear"],
 | 
			
		||||
				"hidden":true
 | 
			
		||||
				"enum_titles" : ["edt_conf_enum_linear", "edt_conf_enum_decay"]
 | 
			
		||||
			},
 | 
			
		||||
			"propertyOrder" : 2
 | 
			
		||||
		},
 | 
			
		||||
@@ -27,7 +26,7 @@
 | 
			
		||||
			"type" : "integer",
 | 
			
		||||
			"title" : "edt_conf_smooth_time_ms_title",
 | 
			
		||||
			"minimum" : 25,
 | 
			
		||||
			"maximum": 600,
 | 
			
		||||
			"maximum": 5000,
 | 
			
		||||
			"default" : 200,
 | 
			
		||||
			"append" : "edt_append_ms",
 | 
			
		||||
			"propertyOrder" : 3
 | 
			
		||||
@@ -37,11 +36,47 @@
 | 
			
		||||
			"type" : "number",
 | 
			
		||||
			"title" : "edt_conf_smooth_updateFrequency_title",
 | 
			
		||||
			"minimum" : 1.0,
 | 
			
		||||
			"maximum" : 100.0,
 | 
			
		||||
			"maximum" : 2000.0,
 | 
			
		||||
			"default" : 25.0,
 | 
			
		||||
			"append" : "edt_append_hz",
 | 
			
		||||
			"propertyOrder" : 4
 | 
			
		||||
		},
 | 
			
		||||
		"interpolationRate" :
 | 
			
		||||
		{
 | 
			
		||||
			"type" : "number",
 | 
			
		||||
			"title" : "edt_conf_smooth_interpolationRate_title",
 | 
			
		||||
			"minimum" : 1.0,
 | 
			
		||||
			"maximum": 1000.0,
 | 
			
		||||
			"default" : 0,
 | 
			
		||||
			"append" : "edt_append_hz",
 | 
			
		||||
			"propertyOrder" : 5
 | 
			
		||||
		},
 | 
			
		||||
		"outputRate" :
 | 
			
		||||
		{
 | 
			
		||||
			"type" : "number",
 | 
			
		||||
			"title" : "edt_conf_smooth_outputRate_title",
 | 
			
		||||
			"minimum" : 1.0,
 | 
			
		||||
			"maximum": 1000.0,
 | 
			
		||||
			"default" : 0,
 | 
			
		||||
			"append" : "edt_append_hz",
 | 
			
		||||
			"propertyOrder" : 6
 | 
			
		||||
		},
 | 
			
		||||
		"decay" :
 | 
			
		||||
		{
 | 
			
		||||
			"type" : "number",
 | 
			
		||||
			"title" : "edt_conf_smooth_decay_title",
 | 
			
		||||
			"default" : 1.0,
 | 
			
		||||
			"minimum" : 1.0,
 | 
			
		||||
			"maximum": 20.0,
 | 
			
		||||
			"propertyOrder" : 7
 | 
			
		||||
		},
 | 
			
		||||
		"dithering" :
 | 
			
		||||
		{
 | 
			
		||||
			"type" : "boolean",
 | 
			
		||||
			"title" : "edt_conf_smooth_dithering_title",
 | 
			
		||||
			"default" : true,
 | 
			
		||||
			"propertyOrder" : 8
 | 
			
		||||
		},
 | 
			
		||||
		"updateDelay" :
 | 
			
		||||
		{
 | 
			
		||||
			"type" : "integer",
 | 
			
		||||
@@ -50,14 +85,14 @@
 | 
			
		||||
			"maximum": 2048,
 | 
			
		||||
			"default" : 0,
 | 
			
		||||
			"append" : "edt_append_ms",
 | 
			
		||||
			"propertyOrder" : 5
 | 
			
		||||
			"propertyOrder" : 9
 | 
			
		||||
		},
 | 
			
		||||
		"continuousOutput" :
 | 
			
		||||
		{
 | 
			
		||||
			"type" : "boolean",
 | 
			
		||||
			"title" : "edt_conf_smooth_continuousOutput_title",
 | 
			
		||||
			"default" : true,
 | 
			
		||||
			"propertyOrder" : 6
 | 
			
		||||
			"propertyOrder" : 10
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
	"additionalProperties" : false
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user