diff --git a/JammaLib/src/engine/LoopRemote.cpp b/JammaLib/src/engine/LoopRemote.cpp index d1c351e4..82be34f7 100644 --- a/JammaLib/src/engine/LoopRemote.cpp +++ b/JammaLib/src/engine/LoopRemote.cpp @@ -9,15 +9,14 @@ LoopRemote::LoopRemote(LoopParams params, _modelDirty(false), _measureLengthSamps(0u), _measurePositionSamps(0u), - _visualLengthSamps(0u) + _visualLengthSamps(0u), + _lastVisualUpdate(std::chrono::steady_clock::now()) { SetMeasureLength(constants::DefaultSampleRate); SetVisualLength(constants::DefaultSampleRate); SetMeasurePosition(0u); - // Render the remote loop once so something is visible, then keep further - // remote visual updates disabled while the slowdown issue is investigated. + // Render the remote loop once so something is visible. _ForceUpdateLoopModel(); - SetVisualUpdatesEnabled(false); } void LoopRemote::SetVisualLength(unsigned int visualLengthSamps) @@ -32,16 +31,26 @@ void LoopRemote::SetVisualLength(unsigned int visualLengthSamps) void LoopRemote::Update() { - if (!_modelDirty.exchange(false)) + if (!_modelDirty.load()) return; - if (!_visualUpdatesEnabled) + // Throttle visual model updates to _MaxVisualUpdateHz to avoid excessive + // decimation/upload cost when multiple remote users are active. + // The vertex shader handles displacement from the waveform texture so + // no geometry rebuild is needed, but decimation is still O(loopLength). + std::chrono::steady_clock::time_point now = std::chrono::steady_clock::now(); + double elapsed = std::chrono::duration(now - _lastVisualUpdate).count(); + if (elapsed < (1.0 / _MaxVisualUpdateHz)) { + // Leave _modelDirty set so the update is retried next tick. _bufferBank.UpdateCapacity(); _monitorBufferBank.UpdateCapacity(); return; } + // Consume the dirty flag now that we will perform the update. + _modelDirty.store(false); + _lastVisualUpdate = now; Loop::Update(); } diff --git a/JammaLib/src/engine/LoopRemote.h b/JammaLib/src/engine/LoopRemote.h index 73ef07e4..ad724a5b 100644 --- a/JammaLib/src/engine/LoopRemote.h +++ b/JammaLib/src/engine/LoopRemote.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include "Loop.h" namespace engine @@ -8,6 +9,11 @@ namespace engine // A loop that receives audio streamed in from a remote ninjam user. // Audio is written into the loop's BufferBank so it can be played back // and visualised through the standard Loop playback path. + // + // Visual model updates are throttled to a maximum rate to avoid excessive + // CPU cost when many remote users are active. The waveform is rendered via + // vertex-shader displacement from a 1D texture, so the per-update cost is + // limited to decimation + texture upload (no geometry rebuild). class LoopRemote : public Loop { @@ -38,5 +44,9 @@ namespace engine std::atomic _measureLengthSamps; std::atomic _measurePositionSamps; std::atomic _visualLengthSamps; + + // Throttle: visual updates are limited to _MaxVisualUpdateHz. + static constexpr double _MaxVisualUpdateHz = 15.0; + std::chrono::steady_clock::time_point _lastVisualUpdate; }; } diff --git a/JammaLib/src/engine/StationRemote.cpp b/JammaLib/src/engine/StationRemote.cpp index 7b0d464a..bac0dda2 100644 --- a/JammaLib/src/engine/StationRemote.cpp +++ b/JammaLib/src/engine/StationRemote.cpp @@ -13,6 +13,7 @@ StationRemote::StationRemote(StationParams params, _assignedOutputChannel(0u), _isConnectedRemote(false), _intervalLengthSamps(constants::DefaultSampleRate), + _intervalVisualLengthSamps(constants::DefaultSampleRate), _intervalPositionSamps(0u), _remoteTake(nullptr), _leftLoop(nullptr), @@ -120,21 +121,33 @@ void StationRemote::SetRemoteInterval(unsigned int lengthSamps, unsigned int pos { const auto safeLength = std::max(1u, lengthSamps); const auto safeVisualLength = std::max(safeLength, visualLengthSamps); + const auto safePosition = positionSamps % safeLength; + + // Early-out if nothing has changed (avoids redundant buffer + // resize / dirty-flag work on each job tick). + if (safeLength == _intervalLengthSamps.load() && + safeVisualLength == _intervalVisualLengthSamps.load() && + safePosition == _intervalPositionSamps.load()) + { + return; + } + _intervalLengthSamps.store(safeLength); - _intervalPositionSamps.store(positionSamps % safeLength); + _intervalVisualLengthSamps.store(safeVisualLength); + _intervalPositionSamps.store(safePosition); if (_leftLoop) { _leftLoop->SetMeasureLength(safeLength); _leftLoop->SetVisualLength(safeVisualLength); - _leftLoop->SetMeasurePosition(_intervalPositionSamps.load()); + _leftLoop->SetMeasurePosition(safePosition); } if (_rightLoop) { _rightLoop->SetMeasureLength(safeLength); _rightLoop->SetVisualLength(safeVisualLength); - _rightLoop->SetMeasurePosition(_intervalPositionSamps.load()); + _rightLoop->SetMeasurePosition(safePosition); } } diff --git a/JammaLib/src/engine/StationRemote.h b/JammaLib/src/engine/StationRemote.h index ce4d3429..daa4af6e 100644 --- a/JammaLib/src/engine/StationRemote.h +++ b/JammaLib/src/engine/StationRemote.h @@ -58,6 +58,7 @@ namespace engine bool _isConnectedRemote; std::atomic _intervalLengthSamps; + std::atomic _intervalVisualLengthSamps; std::atomic _intervalPositionSamps; std::shared_ptr _remoteTake;