Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 15 additions & 6 deletions JammaLib/src/engine/LoopRemote.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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<double>(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();
}

Expand Down
10 changes: 10 additions & 0 deletions JammaLib/src/engine/LoopRemote.h
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
#pragma once

#include <atomic>
#include <chrono>
#include "Loop.h"

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
{
Expand Down Expand Up @@ -38,5 +44,9 @@ namespace engine
std::atomic<unsigned int> _measureLengthSamps;
std::atomic<unsigned int> _measurePositionSamps;
std::atomic<unsigned int> _visualLengthSamps;

// Throttle: visual updates are limited to _MaxVisualUpdateHz.
static constexpr double _MaxVisualUpdateHz = 15.0;
std::chrono::steady_clock::time_point _lastVisualUpdate;
};
}
19 changes: 16 additions & 3 deletions JammaLib/src/engine/StationRemote.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ StationRemote::StationRemote(StationParams params,
_assignedOutputChannel(0u),
_isConnectedRemote(false),
_intervalLengthSamps(constants::DefaultSampleRate),
_intervalVisualLengthSamps(constants::DefaultSampleRate),
_intervalPositionSamps(0u),
_remoteTake(nullptr),
_leftLoop(nullptr),
Expand Down Expand Up @@ -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);
}
}

Expand Down
1 change: 1 addition & 0 deletions JammaLib/src/engine/StationRemote.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ namespace engine
bool _isConnectedRemote;

std::atomic<unsigned int> _intervalLengthSamps;
std::atomic<unsigned int> _intervalVisualLengthSamps;
std::atomic<unsigned int> _intervalPositionSamps;

std::shared_ptr<LoopTake> _remoteTake;
Expand Down