OSC fixes
This commit is contained in:
@@ -33,6 +33,11 @@ bool IsFiniteNumber(double value)
|
||||
return std::isfinite(value) != 0;
|
||||
}
|
||||
|
||||
double Clamp01(double value)
|
||||
{
|
||||
return std::max(0.0, std::min(1.0, value));
|
||||
}
|
||||
|
||||
std::string ToLowerCopy(std::string text)
|
||||
{
|
||||
std::transform(text.begin(), text.end(), text.begin(),
|
||||
@@ -56,6 +61,20 @@ bool MatchesControlKey(const std::string& candidate, const std::string& key)
|
||||
return candidate == key || SimplifyControlKey(candidate) == SimplifyControlKey(key);
|
||||
}
|
||||
|
||||
bool JsonValueContainsOnlyNumbers(const JsonValue& value)
|
||||
{
|
||||
if (!value.isArray())
|
||||
return false;
|
||||
|
||||
for (const JsonValue& item : value.asArray())
|
||||
{
|
||||
if (!item.isNumber() || !IsFiniteNumber(item.asNumber()))
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
double GenerateStartupRandom()
|
||||
{
|
||||
std::random_device randomDevice;
|
||||
@@ -970,7 +989,7 @@ bool RuntimeHost::SetLayerBypass(const std::string& layerId, bool bypassed, std:
|
||||
|
||||
layer->bypass = bypassed;
|
||||
mReloadRequested = true;
|
||||
MarkRenderStateDirtyLocked();
|
||||
MarkParameterStateDirtyLocked();
|
||||
return SavePersistentState(error);
|
||||
}
|
||||
|
||||
@@ -1032,7 +1051,7 @@ bool RuntimeHost::UpdateLayerParameter(const std::string& layerId, const std::st
|
||||
const double previousCount = value.numberValues.empty() ? 0.0 : value.numberValues[0];
|
||||
const double triggerTime = std::chrono::duration_cast<std::chrono::duration<double>>(std::chrono::steady_clock::now() - mStartTime).count();
|
||||
value.numberValues = { previousCount + 1.0, triggerTime };
|
||||
MarkRenderStateDirtyLocked();
|
||||
MarkParameterStateDirtyLocked();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1041,11 +1060,16 @@ bool RuntimeHost::UpdateLayerParameter(const std::string& layerId, const std::st
|
||||
return false;
|
||||
|
||||
layer->parameterValues[parameterId] = normalized;
|
||||
MarkRenderStateDirtyLocked();
|
||||
MarkParameterStateDirtyLocked();
|
||||
return SavePersistentState(error);
|
||||
}
|
||||
|
||||
bool RuntimeHost::UpdateLayerParameterByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue, std::string& error)
|
||||
{
|
||||
return UpdateLayerParameterByControlKey(layerKey, parameterKey, newValue, true, error);
|
||||
}
|
||||
|
||||
bool RuntimeHost::UpdateLayerParameterByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue, bool persistState, std::string& error)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
|
||||
@@ -1089,7 +1113,7 @@ bool RuntimeHost::UpdateLayerParameterByControlKey(const std::string& layerKey,
|
||||
const double previousCount = value.numberValues.empty() ? 0.0 : value.numberValues[0];
|
||||
const double triggerTime = std::chrono::duration_cast<std::chrono::duration<double>>(std::chrono::steady_clock::now() - mStartTime).count();
|
||||
value.numberValues = { previousCount + 1.0, triggerTime };
|
||||
MarkRenderStateDirtyLocked();
|
||||
MarkParameterStateDirtyLocked();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1098,8 +1122,141 @@ bool RuntimeHost::UpdateLayerParameterByControlKey(const std::string& layerKey,
|
||||
return false;
|
||||
|
||||
matchedLayer->parameterValues[parameterIt->id] = normalized;
|
||||
MarkRenderStateDirtyLocked();
|
||||
return SavePersistentState(error);
|
||||
MarkParameterStateDirtyLocked();
|
||||
return !persistState || SavePersistentState(error);
|
||||
}
|
||||
|
||||
bool RuntimeHost::ApplyOscTargetByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& targetValue, double smoothingAmount, bool& keepApplying, std::string& resolvedLayerId, std::string& resolvedParameterId, ShaderParameterValue& appliedValue, std::string& error)
|
||||
{
|
||||
keepApplying = false;
|
||||
resolvedLayerId.clear();
|
||||
resolvedParameterId.clear();
|
||||
appliedValue = ShaderParameterValue();
|
||||
|
||||
std::lock_guard<std::mutex> lock(mMutex);
|
||||
|
||||
LayerPersistentState* matchedLayer = nullptr;
|
||||
const ShaderPackage* matchedPackage = nullptr;
|
||||
for (LayerPersistentState& layer : mPersistentState.layers)
|
||||
{
|
||||
auto shaderIt = mPackagesById.find(layer.shaderId);
|
||||
if (shaderIt == mPackagesById.end())
|
||||
continue;
|
||||
|
||||
if (MatchesControlKey(layer.id, layerKey) || MatchesControlKey(shaderIt->second.id, layerKey) ||
|
||||
MatchesControlKey(shaderIt->second.displayName, layerKey))
|
||||
{
|
||||
matchedLayer = &layer;
|
||||
matchedPackage = &shaderIt->second;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!matchedLayer || !matchedPackage)
|
||||
{
|
||||
error = "Unknown OSC layer key: " + layerKey;
|
||||
return false;
|
||||
}
|
||||
|
||||
resolvedLayerId = matchedLayer->id;
|
||||
|
||||
const auto parameterIt = std::find_if(matchedPackage->parameters.begin(), matchedPackage->parameters.end(),
|
||||
[¶meterKey](const ShaderParameterDefinition& definition)
|
||||
{
|
||||
return MatchesControlKey(definition.id, parameterKey) || MatchesControlKey(definition.label, parameterKey);
|
||||
});
|
||||
if (parameterIt == matchedPackage->parameters.end())
|
||||
{
|
||||
error = "Unknown OSC parameter key: " + parameterKey;
|
||||
return false;
|
||||
}
|
||||
|
||||
resolvedParameterId = parameterIt->id;
|
||||
|
||||
if (parameterIt->type == ShaderParameterType::Trigger)
|
||||
{
|
||||
ShaderParameterValue& value = matchedLayer->parameterValues[parameterIt->id];
|
||||
const double previousCount = value.numberValues.empty() ? 0.0 : value.numberValues[0];
|
||||
const double triggerTime = std::chrono::duration_cast<std::chrono::duration<double>>(std::chrono::steady_clock::now() - mStartTime).count();
|
||||
value.numberValues = { previousCount + 1.0, triggerTime };
|
||||
MarkParameterStateDirtyLocked();
|
||||
appliedValue = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
ShaderParameterValue normalizedTarget;
|
||||
if (!NormalizeAndValidateValue(*parameterIt, targetValue, normalizedTarget, error))
|
||||
return false;
|
||||
|
||||
const bool smoothableType =
|
||||
parameterIt->type == ShaderParameterType::Float ||
|
||||
parameterIt->type == ShaderParameterType::Vec2 ||
|
||||
parameterIt->type == ShaderParameterType::Color;
|
||||
const bool smoothableInput = targetValue.isNumber() || JsonValueContainsOnlyNumbers(targetValue);
|
||||
const double alpha = Clamp01(smoothingAmount);
|
||||
|
||||
if (!smoothableType || !smoothableInput || alpha <= 0.0)
|
||||
{
|
||||
matchedLayer->parameterValues[parameterIt->id] = normalizedTarget;
|
||||
MarkParameterStateDirtyLocked();
|
||||
appliedValue = normalizedTarget;
|
||||
return true;
|
||||
}
|
||||
|
||||
ShaderParameterValue currentValue = DefaultValueForDefinition(*parameterIt);
|
||||
auto currentIt = matchedLayer->parameterValues.find(parameterIt->id);
|
||||
if (currentIt != matchedLayer->parameterValues.end())
|
||||
currentValue = currentIt->second;
|
||||
|
||||
ShaderParameterValue nextValue = normalizedTarget;
|
||||
nextValue.numberValues = normalizedTarget.numberValues;
|
||||
|
||||
if (currentValue.numberValues.size() != normalizedTarget.numberValues.size())
|
||||
currentValue.numberValues = normalizedTarget.numberValues;
|
||||
|
||||
bool changed = false;
|
||||
bool converged = true;
|
||||
for (std::size_t index = 0; index < normalizedTarget.numberValues.size(); ++index)
|
||||
{
|
||||
const double currentNumber = currentValue.numberValues[index];
|
||||
const double targetNumber = normalizedTarget.numberValues[index];
|
||||
const double delta = targetNumber - currentNumber;
|
||||
double nextNumber = currentNumber + delta * alpha;
|
||||
|
||||
if (std::fabs(delta) <= 0.0005)
|
||||
{
|
||||
nextNumber = targetNumber;
|
||||
}
|
||||
else
|
||||
{
|
||||
converged = false;
|
||||
}
|
||||
|
||||
if (std::fabs(nextNumber - currentNumber) > 0.0000001)
|
||||
changed = true;
|
||||
|
||||
nextValue.numberValues[index] = nextNumber;
|
||||
}
|
||||
|
||||
if (!converged)
|
||||
{
|
||||
keepApplying = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
nextValue.numberValues = normalizedTarget.numberValues;
|
||||
}
|
||||
|
||||
if (!changed && !keepApplying)
|
||||
{
|
||||
appliedValue = matchedLayer->parameterValues[parameterIt->id];
|
||||
return true;
|
||||
}
|
||||
|
||||
matchedLayer->parameterValues[parameterIt->id] = nextValue;
|
||||
MarkParameterStateDirtyLocked();
|
||||
appliedValue = nextValue;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RuntimeHost::ResetLayerParameters(const std::string& layerId, std::string& error)
|
||||
@@ -1122,7 +1279,7 @@ bool RuntimeHost::ResetLayerParameters(const std::string& layerId, std::string&
|
||||
|
||||
layer->parameterValues.clear();
|
||||
EnsureLayerDefaultsLocked(*layer, shaderIt->second);
|
||||
MarkRenderStateDirtyLocked();
|
||||
MarkParameterStateDirtyLocked();
|
||||
return SavePersistentState(error);
|
||||
}
|
||||
|
||||
@@ -1226,6 +1383,12 @@ void RuntimeHost::SetSignalStatusLocked(bool hasSignal, unsigned width, unsigned
|
||||
void RuntimeHost::MarkRenderStateDirtyLocked()
|
||||
{
|
||||
mRenderStateVersion.fetch_add(1, std::memory_order_relaxed);
|
||||
mParameterStateVersion.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void RuntimeHost::MarkParameterStateDirtyLocked()
|
||||
{
|
||||
mParameterStateVersion.fetch_add(1, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void RuntimeHost::SetDeckLinkOutputStatus(const std::string& modelName, bool supportsInternalKeying, bool supportsExternalKeying,
|
||||
@@ -1388,6 +1551,34 @@ bool RuntimeHost::TryGetLayerRenderStates(unsigned outputWidth, unsigned outputH
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RuntimeHost::TryRefreshCachedLayerStates(std::vector<RuntimeRenderState>& states) const
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(mMutex, std::try_to_lock);
|
||||
if (!lock.owns_lock())
|
||||
return false;
|
||||
|
||||
for (RuntimeRenderState& state : states)
|
||||
{
|
||||
const auto layerIt = std::find_if(mPersistentState.layers.begin(), mPersistentState.layers.end(),
|
||||
[&state](const LayerPersistentState& layer) { return layer.id == state.layerId; });
|
||||
if (layerIt == mPersistentState.layers.end())
|
||||
continue;
|
||||
|
||||
state.bypass = layerIt->bypass ? 1.0 : 0.0;
|
||||
state.parameterValues.clear();
|
||||
for (const ShaderParameterDefinition& definition : state.parameterDefinitions)
|
||||
{
|
||||
ShaderParameterValue value = DefaultValueForDefinition(definition);
|
||||
auto valueIt = layerIt->parameterValues.find(definition.id);
|
||||
if (valueIt != layerIt->parameterValues.end())
|
||||
value = valueIt->second;
|
||||
state.parameterValues[definition.id] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void RuntimeHost::RefreshDynamicRenderStateFields(std::vector<RuntimeRenderState>& states) const
|
||||
{
|
||||
const RuntimeClockSnapshot clock = GetRuntimeClockSnapshot();
|
||||
@@ -1415,6 +1606,7 @@ void RuntimeHost::BuildLayerRenderStatesLocked(unsigned outputWidth, unsigned ou
|
||||
RuntimeRenderState state;
|
||||
state.layerId = layer.id;
|
||||
state.shaderId = layer.shaderId;
|
||||
state.shaderName = shaderIt->second.displayName;
|
||||
state.mixAmount = 1.0;
|
||||
state.bypass = layer.bypass ? 1.0 : 0.0;
|
||||
state.inputWidth = mSignalWidth;
|
||||
@@ -1476,6 +1668,8 @@ bool RuntimeHost::LoadConfig(std::string& error)
|
||||
mConfig.oscPort = static_cast<unsigned short>(oscPortValue->asNumber(mConfig.oscPort));
|
||||
if (const JsonValue* oscBindAddressValue = configJson.find("oscBindAddress"))
|
||||
mConfig.oscBindAddress = oscBindAddressValue->asString();
|
||||
if (const JsonValue* oscSmoothingValue = configJson.find("oscSmoothing"))
|
||||
mConfig.oscSmoothing = Clamp01(oscSmoothingValue->asNumber(mConfig.oscSmoothing));
|
||||
if (const JsonValue* autoReloadValue = configJson.find("autoReload"))
|
||||
mConfig.autoReload = autoReloadValue->asBoolean(mConfig.autoReload);
|
||||
if (const JsonValue* maxTemporalHistoryFramesValue = configJson.find("maxTemporalHistoryFrames"))
|
||||
@@ -1873,6 +2067,7 @@ JsonValue RuntimeHost::BuildStateValue() const
|
||||
app.set("serverPort", JsonValue(static_cast<double>(mServerPort)));
|
||||
app.set("oscPort", JsonValue(static_cast<double>(mConfig.oscPort)));
|
||||
app.set("oscBindAddress", JsonValue(mConfig.oscBindAddress));
|
||||
app.set("oscSmoothing", JsonValue(mConfig.oscSmoothing));
|
||||
app.set("autoReload", JsonValue(mAutoReloadEnabled));
|
||||
app.set("maxTemporalHistoryFrames", JsonValue(static_cast<double>(mConfig.maxTemporalHistoryFrames)));
|
||||
app.set("previewFps", JsonValue(static_cast<double>(mConfig.previewFps)));
|
||||
|
||||
@@ -31,6 +31,8 @@ public:
|
||||
bool SetLayerShader(const std::string& layerId, const std::string& shaderId, std::string& error);
|
||||
bool UpdateLayerParameter(const std::string& layerId, const std::string& parameterId, const JsonValue& newValue, std::string& error);
|
||||
bool UpdateLayerParameterByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue, std::string& error);
|
||||
bool UpdateLayerParameterByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& newValue, bool persistState, std::string& error);
|
||||
bool ApplyOscTargetByControlKey(const std::string& layerKey, const std::string& parameterKey, const JsonValue& targetValue, double smoothingAmount, bool& keepApplying, std::string& resolvedLayerId, std::string& resolvedParameterId, ShaderParameterValue& appliedValue, std::string& error);
|
||||
bool ResetLayerParameters(const std::string& layerId, std::string& error);
|
||||
bool SaveStackPreset(const std::string& presetName, std::string& error) const;
|
||||
bool LoadStackPreset(const std::string& presetName, std::string& error);
|
||||
@@ -54,9 +56,11 @@ public:
|
||||
bool BuildLayerPassFragmentShaderSources(const std::string& layerId, std::vector<ShaderPassBuildSource>& passSources, std::string& error);
|
||||
std::vector<RuntimeRenderState> GetLayerRenderStates(unsigned outputWidth, unsigned outputHeight) const;
|
||||
bool TryGetLayerRenderStates(unsigned outputWidth, unsigned outputHeight, std::vector<RuntimeRenderState>& states) const;
|
||||
bool TryRefreshCachedLayerStates(std::vector<RuntimeRenderState>& states) const;
|
||||
void RefreshDynamicRenderStateFields(std::vector<RuntimeRenderState>& states) const;
|
||||
std::string BuildStateJson() const;
|
||||
uint64_t GetRenderStateVersion() const { return mRenderStateVersion.load(std::memory_order_relaxed); }
|
||||
uint64_t GetParameterStateVersion() const { return mParameterStateVersion.load(std::memory_order_relaxed); }
|
||||
|
||||
const std::filesystem::path& GetRepoRoot() const { return mRepoRoot; }
|
||||
const std::filesystem::path& GetUiRoot() const { return mUiRoot; }
|
||||
@@ -65,6 +69,7 @@ public:
|
||||
unsigned short GetServerPort() const { return mServerPort; }
|
||||
unsigned short GetOscPort() const { return mConfig.oscPort; }
|
||||
const std::string& GetOscBindAddress() const { return mConfig.oscBindAddress; }
|
||||
double GetOscSmoothing() const { return mConfig.oscSmoothing; }
|
||||
unsigned GetMaxTemporalHistoryFrames() const { return mConfig.maxTemporalHistoryFrames; }
|
||||
unsigned GetPreviewFps() const { return mConfig.previewFps; }
|
||||
bool ExternalKeyingEnabled() const { return mConfig.enableExternalKeying; }
|
||||
@@ -82,6 +87,7 @@ private:
|
||||
unsigned short serverPort = 8080;
|
||||
unsigned short oscPort = 9000;
|
||||
std::string oscBindAddress = "127.0.0.1";
|
||||
double oscSmoothing = 0.18;
|
||||
bool autoReload = true;
|
||||
unsigned maxTemporalHistoryFrames = 4;
|
||||
unsigned previewFps = 30;
|
||||
@@ -141,6 +147,7 @@ private:
|
||||
std::string GenerateLayerId();
|
||||
void SetSignalStatusLocked(bool hasSignal, unsigned width, unsigned height, const std::string& modeName);
|
||||
void MarkRenderStateDirtyLocked();
|
||||
void MarkParameterStateDirtyLocked();
|
||||
void SetPerformanceStatsLocked(double frameBudgetMilliseconds, double renderMilliseconds);
|
||||
void SetFramePacingStatsLocked(double completionIntervalMilliseconds, double smoothedCompletionIntervalMilliseconds,
|
||||
double maxCompletionIntervalMilliseconds, uint64_t lateFrameCount, uint64_t droppedFrameCount, uint64_t flushedFrameCount);
|
||||
@@ -187,5 +194,6 @@ private:
|
||||
std::chrono::steady_clock::time_point mLastScanTime;
|
||||
std::atomic<uint64_t> mFrameCounter{ 0 };
|
||||
std::atomic<uint64_t> mRenderStateVersion{ 0 };
|
||||
std::atomic<uint64_t> mParameterStateVersion{ 0 };
|
||||
uint64_t mNextLayerId;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user