Good morning,
I have my tracktion engine application that has a timeline. I have a playhead that move smooth when running but when I do “PAUSE” (transport.stop(false, false)) she do a small jump. It seams that there is a problem with the sync.
I have a PositionManager that manage the position of the playhead in this way:
PositionManager.h
#pragma once
#include <JuceHeader.h>
#include "tracktion_engine/tracktion_engine.h"
#include <atomic>
#include <cmath>
class PositionManager : public juce::ChangeBroadcaster,
private juce::AudioIODeviceCallback,
private juce::AsyncUpdater,
private juce::ChangeListener
{
public:
PositionManager(tracktion::engine::Edit& editRef, tracktion::engine::TransportControl& tc, juce::AudioDeviceManager& dm);
~PositionManager() override;
double getCurrentTime() const;
void stopTransport()
{
transport.stop(false, false);
}
private:
void audioDeviceIOCallbackWithContext(const float* const* inputChannelData,
int numInputChannels,
float* const* outputChannelData,
int numOutputChannels,
int numSamples,
const juce::AudioIODeviceCallbackContext& context) override;
void audioDeviceAboutToStart(juce::AudioIODevice*) override;
void audioDeviceStopped() override;
void handleAsyncUpdate() override;
void changeListenerCallback(juce::ChangeBroadcaster* source) override;
double getCurrentAudibleTimeSeconds() const;
tracktion::engine::Edit& edit;
tracktion::engine::TransportControl& transport;
juce::AudioDeviceManager& deviceManager;
std::atomic<double> currentTime{ 0.0 };
std::atomic<bool> forceBroadcastPending{ false };
bool wasTransportPlaying = false;
double lastBroadcastedTime = 0.0;
double lastKnownSampleRate = 0.0;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PositionManager)
};
PositionManager.cpp
#include "PositionManager.h"
PositionManager::PositionManager(tracktion::engine::Edit& editRef,
tracktion::engine::TransportControl& tc,
juce::AudioDeviceManager& dm)
: edit(editRef), transport(tc), deviceManager(dm)
{
currentTime.store(getCurrentAudibleTimeSeconds(), std::memory_order_relaxed);
wasTransportPlaying = transport.isPlaying();
transport.addChangeListener(this);
deviceManager.addAudioCallback(this);
}
PositionManager::~PositionManager()
{
cancelPendingUpdate();
transport.removeChangeListener(this);
deviceManager.removeAudioCallback(this);
}
double PositionManager::getCurrentTime() const
{
return currentTime.load(std::memory_order_relaxed);
}
void PositionManager::audioDeviceIOCallbackWithContext(const float* const* inputChannelData,
int numInputChannels,
float* const* outputChannelData,
int numOutputChannels,
int numSamples,
const juce::AudioIODeviceCallbackContext&)
{
juce::ignoreUnused(inputChannelData, numInputChannels, outputChannelData, numOutputChannels, numSamples);
const bool isPlaying = transport.isPlaying();
const bool transportStateChanged = isPlaying != wasTransportPlaying;
wasTransportPlaying = isPlaying;
const double newTime = getCurrentAudibleTimeSeconds();
currentTime.store(newTime, std::memory_order_relaxed);
const double minDelta = (numSamples > 0 && lastKnownSampleRate > 0.0)
? static_cast<double>(numSamples) / lastKnownSampleRate
: 0.0;
const double timeDelta = std::abs(newTime - lastBroadcastedTime);
if (transportStateChanged && !isPlaying)
{
forceBroadcastPending.store(true, std::memory_order_release);
triggerAsyncUpdate();
return;
}
if (isPlaying && timeDelta < minDelta)
return;
if (!isPlaying && timeDelta < 1e-6)
return;
triggerAsyncUpdate();
}
void PositionManager::audioDeviceAboutToStart(juce::AudioIODevice*)
{
currentTime.store(getCurrentAudibleTimeSeconds(), std::memory_order_relaxed);
wasTransportPlaying = transport.isPlaying();
lastKnownSampleRate = deviceManager.getCurrentAudioDevice() != nullptr
? deviceManager.getCurrentAudioDevice()->getCurrentSampleRate()
: 0.0;
}
void PositionManager::audioDeviceStopped()
{
lastKnownSampleRate = 0.0;
}
void PositionManager::handleAsyncUpdate()
{
const bool forceBroadcast = forceBroadcastPending.exchange(false, std::memory_order_acq_rel);
const double newTime = currentTime.load(std::memory_order_relaxed);
if (forceBroadcast || std::abs(newTime - lastBroadcastedTime) > 1e-6)
{
lastBroadcastedTime = newTime;
sendChangeMessage();
}
}
void PositionManager::changeListenerCallback(juce::ChangeBroadcaster* source)
{
if (source != &transport)
return;
currentTime.store(transport.getPosition().inSeconds(), std::memory_order_relaxed);
forceBroadcastPending.store(true, std::memory_order_release);
triggerAsyncUpdate();
}
double PositionManager::getCurrentAudibleTimeSeconds() const
{
return transport.getPosition().inSeconds();
}
then in my SequencerComponent I have this in a
void SequencerComponent::changeListenerCallback(juce::ChangeBroadcaster* source):
const double timeSec = positionManager.getCurrentTime();
updatePlayheadFromTime(timeSec, !isFollowingPlayhead);
Maybe I’m doing all wrong but someone can help to achieve a real time playhead?
Thanks
Luca
