Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add external timing API #817

Merged
Merged
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
32 changes: 32 additions & 0 deletions src/api/include/projectM-4/parameters.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,38 @@ PROJECTM_EXPORT void projectm_set_texture_search_paths(projectm_handle instance,
const char** texture_search_paths,
size_t count);

/**
* @brief Sets a user-specified frame time in fractional seconds.
*
* Setting this to any value equal to or larger than zero will make projectM use this time value for
* animations instead of the system clock. Any value less than zero will use the system time instead,
* which is the default behavior.
*
* This method can be used to render visualizations at non-realtime frame rates, e.g. encoding a video
* as fast as projectM can render frames.
*
* While switching back and forth between system and user time values is possible, it will cause
* visual artifacts in the rendering as the time value will make large jumps between frames. Thus,
* it is recommended to stay with one type of timing value.
*
* If using this feature, it is further recommended to set the time to 0.0 on the first frame.
*
* @param instance The projectM instance handle.
* @param seconds_since_first_frame Any value >= 0 to use user-specified timestamps, values < 0 will use the system clock.
*/
PROJECTM_EXPORT void projectm_set_frame_time(projectm_handle instance, double seconds_since_first_frame);

/**
* @brief Returns the fractional seconds time value used rendering the last frame.
* @note This will not return the value set with projectm_set_frame_time, but the actual time
* used to render the last frame. If a user-specified frame time was set, this value is
* returned. Otherwise, the frame time measured via the system clock will be returned.
* @param instance The projectM instance handle.
* @return Time elapsed since projectM was started, or the value of the user-specified time value used
* to render the last frame.
*/
PROJECTM_EXPORT double projectm_get_last_frame_time(projectm_handle instance);

/**
* @brief Sets the beat sensitivity.
*
Expand Down
16 changes: 13 additions & 3 deletions src/libprojectM/ProjectM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ void ProjectM::RenderFrame(uint32_t targetFramebufferObject /*= 0*/)

if (m_transition != nullptr && m_transitioningPreset != nullptr)
{
if (m_transition->IsDone())
if (m_transition->IsDone(m_timeKeeper->GetFrameTime()))
{
m_activePreset = std::move(m_transitioningPreset);
m_transitioningPreset.reset();
Expand All @@ -170,7 +170,7 @@ void ProjectM::RenderFrame(uint32_t targetFramebufferObject /*= 0*/)

if (m_transition != nullptr && m_transitioningPreset != nullptr)
{
m_transition->Draw(*m_activePreset, *m_transitioningPreset, renderContext, audioData);
m_transition->Draw(*m_activePreset, *m_transitioningPreset, renderContext, audioData, m_timeKeeper->GetFrameTime());
}
else
{
Expand Down Expand Up @@ -254,7 +254,7 @@ void ProjectM::StartPresetTransition(std::unique_ptr<Preset>&& preset, bool hard
{
m_transitioningPreset = std::move(preset);
m_timeKeeper->StartSmoothing();
m_transition = std::make_unique<Renderer::PresetTransition>(m_transitionShaderManager->RandomTransition(), m_softCutDuration);
m_transition = std::make_unique<Renderer::PresetTransition>(m_transitionShaderManager->RandomTransition(), m_softCutDuration, m_timeKeeper->GetFrameTime());
}
}

Expand All @@ -281,6 +281,16 @@ auto ProjectM::PresetLocked() const -> bool
return m_presetLocked;
}

void ProjectM::SetFrameTime(double secondsSinceStart)
{
m_timeKeeper->SetFrameTime(secondsSinceStart);
}

double ProjectM::GetFrameTime()
{
return m_timeKeeper->GetFrameTime();
}

void ProjectM::SetBeatSensitivity(float sensitivity)
{
m_beatSensitivity = std::min(std::max(0.0f, sensitivity), 2.0f);
Expand Down
16 changes: 16 additions & 0 deletions src/libprojectM/ProjectM.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,22 @@ class PROJECTM_EXPORT ProjectM

void RenderFrame(uint32_t targetFramebufferObject = 0);

/**
* @brief Sets a user-specified time for rendering the next frame
* Negative values will make projectM use the system clock instead.
* @param secondsSinceStart Fractional seconds since rendering the first frame.
*/
void SetFrameTime(double secondsSinceStart);

/**
* @brief Gets the time of the last frame rendered.
* @note This will not return the value set with SetFrameTime, but the actual time used to render the last frame.
* If a user-specified frame time was set, this value is returned. Otherwise, the frame time measured via the
* system clock will be returned.
* @return Seconds elapsed rendering the last frame since starting projectM.
*/
double GetFrameTime();

void SetBeatSensitivity(float sensitivity);

auto GetBeatSensitivity() const -> float;
Expand Down
13 changes: 13 additions & 0 deletions src/libprojectM/ProjectMCWrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <cstring>
#include <sstream>
#include <projectM-4/render_opengl.h>
#include <projectM-4/parameters.h>


namespace libprojectM {
Expand Down Expand Up @@ -179,6 +180,18 @@ void projectm_opengl_render_frame_fbo(projectm_handle instance, uint32_t framebu
projectMInstance->RenderFrame(framebuffer_object_id);
}

void projectm_set_frame_time(projectm_handle instance, double seconds_since_first_frame)
{
auto projectMInstance = handle_to_instance(instance);
projectMInstance->SetFrameTime(seconds_since_first_frame);
}

double projectm_get_last_frame_time(projectm_handle instance)
{
auto projectMInstance = handle_to_instance(instance);
return projectMInstance->GetFrameTime();
}

void projectm_set_beat_sensitivity(projectm_handle instance, float sensitivity)
{
auto projectMInstance = handle_to_instance(instance);
Expand Down
18 changes: 9 additions & 9 deletions src/libprojectM/Renderer/PresetTransition.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ namespace Renderer {

constexpr double PI = 3.14159265358979323846;

PresetTransition::PresetTransition(const std::shared_ptr<Shader>& transitionShader, double durationSeconds)
PresetTransition::PresetTransition(const std::shared_ptr<Shader>& transitionShader, double durationSeconds, double transitionStartTime)
: m_transitionShader(transitionShader)
, m_durationSeconds(durationSeconds)
, m_transitionStartTime(transitionStartTime)
{
std::mt19937 rand32(m_randomDevice());
m_staticRandomValues = {rand32(), rand32(), rand32(), rand32()};
Expand All @@ -33,19 +34,18 @@ void PresetTransition::InitVertexAttrib()
glBufferData(GL_ARRAY_BUFFER, sizeof(points), points.data(), GL_STATIC_DRAW);
}

auto PresetTransition::IsDone() const -> bool
auto PresetTransition::IsDone(double currentFrameTime) const -> bool
{
const auto secondsSinceStart = std::chrono::duration<double>(std::chrono::system_clock::now() - m_transitionStartTime).count();
const auto secondsSinceStart = currentFrameTime - m_transitionStartTime;
return m_durationSeconds <= 0.0 || secondsSinceStart >= m_durationSeconds;
}

void PresetTransition::Draw(const Preset& oldPreset,
const Preset& newPreset,
const RenderContext& context,
const libprojectM::Audio::FrameAudioData& audioData)
const libprojectM::Audio::FrameAudioData& audioData,
double currentFrameTime)
{
using namespace std::chrono_literals;

if (m_transitionShader == nullptr)
{
return;
Expand All @@ -54,7 +54,7 @@ void PresetTransition::Draw(const Preset& oldPreset,
std::mt19937 rand32(m_randomDevice());

// Calculate progress values
const auto secondsSinceStart = std::chrono::duration<double>(std::chrono::system_clock::now() - m_transitionStartTime).count();
const auto secondsSinceStart = currentFrameTime - m_transitionStartTime;

// If duration is zero,
double linearProgress{1.0};
Expand All @@ -81,7 +81,7 @@ void PresetTransition::Draw(const Preset& oldPreset,
m_durationSeconds});

m_transitionShader->SetUniformFloat2("timeParams", {secondsSinceStart,
std::chrono::duration<float>(std::chrono::system_clock::now() - m_lastFrameTime).count()});
currentFrameTime - m_lastFrameTime});

m_transitionShader->SetUniformInt4("iRandStatic", m_staticRandomValues);

Expand Down Expand Up @@ -130,7 +130,7 @@ void PresetTransition::Draw(const Preset& oldPreset,
Shader::Unbind();

// Update last frame time.
m_lastFrameTime = std::chrono::system_clock::now();
m_lastFrameTime = currentFrameTime;
}

} // namespace Renderer
Expand Down
24 changes: 17 additions & 7 deletions src/libprojectM/Renderer/PresetTransition.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@

#include <glm/glm.hpp>

#include <chrono>
#include <random>

namespace libprojectM {
Expand All @@ -22,27 +21,38 @@ class PresetTransition : public RenderItem
public:
PresetTransition() = delete;

explicit PresetTransition(const std::shared_ptr<Shader>& transitionShader, double durationSeconds);
/**
* Constructor.
* @param transitionShader The transition shader program.
* @param durationSeconds Transition duration in seconds.
* @param transitionStartTime The time in seconds since start of projectM.
*/
explicit PresetTransition(const std::shared_ptr<Shader>& transitionShader,
double durationSeconds,
double transitionStartTime);

void InitVertexAttrib() override;

/**
* @brief Returns true if the transition is done.
* @param currentFrameTime The time in seconds since start of the current frame.
* @return false if the transition is still in progress, true if it's done.
*/
auto IsDone() const -> bool;
auto IsDone(double currentFrameTime) const -> bool;

/**
* @brief Updates the transition variables and renders the shader quad to the current FBO.
* @param oldPreset A reference to the old (fading out) preset.
* @param newPreset A reference to the new (fading in) preset.
* @param context The rendering context used to render the presets.
* @param audioData Current audio data and beat detection values.
* @param currentFrameTime The time in seconds since start of the current frame.
*/
void Draw(const Preset& oldPreset,
const Preset& newPreset,
const RenderContext& context,
const libprojectM::Audio::FrameAudioData& audioData);
const libprojectM::Audio::FrameAudioData& audioData,
double currentFrameTime);

private:
std::vector<std::string> m_noiseTextureNames{"noise_lq",
Expand All @@ -59,9 +69,9 @@ class PresetTransition : public RenderItem
std::shared_ptr<Shader> m_transitionShader; //!< The compiled shader used for this transition.
std::shared_ptr<Sampler> m_presetSampler{std::make_shared<Sampler>(GL_CLAMP_TO_EDGE, GL_LINEAR)}; //!< Sampler for preset textures. Uses bilinear interpolation and no repeat.

double m_durationSeconds{3.0}; //!< Transition duration in seconds.
std::chrono::time_point<std::chrono::system_clock> m_transitionStartTime{std::chrono::system_clock::now()}; //!< Start time of this transition. Duration is measured from this point.
std::chrono::time_point<std::chrono::system_clock> m_lastFrameTime{std::chrono::system_clock::now()}; //!< Time when the previous frame was rendered.
double m_durationSeconds{3.0}; //!< Transition duration in seconds.
double m_transitionStartTime{}; //!< Start time of this transition. Duration is measured from this point.
double m_lastFrameTime{}; //!< Time when the previous frame was rendered.

glm::ivec4 m_staticRandomValues{}; //!< Four random integers, remaining static during the whole transition.

Expand Down
19 changes: 17 additions & 2 deletions src/libprojectM/TimeKeeper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,26 @@ TimeKeeper::TimeKeeper(double presetDuration, double smoothDuration, double hard
UpdateTimers();
}

void TimeKeeper::SetFrameTime(double secondsSinceStart)
{
m_userSpecifiedTime = secondsSinceStart;
}

double TimeKeeper::GetFrameTime() const
{
return m_currentTime;
}

void TimeKeeper::UpdateTimers()
{
auto currentTime = std::chrono::high_resolution_clock::now();
double currentFrameTime{m_userSpecifiedTime};

if (m_userSpecifiedTime < 0.0)
{
auto currentTime = std::chrono::high_resolution_clock::now();
currentFrameTime = std::chrono::duration<double>(currentTime - m_startTime).count();
}

double currentFrameTime = std::chrono::duration<double>(currentTime - m_startTime).count();
m_secondsSinceLastFrame = currentFrameTime - m_currentTime;
m_currentTime = currentFrameTime;
m_presetFrameA++;
Expand Down
21 changes: 21 additions & 0 deletions src/libprojectM/TimeKeeper.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,25 @@ class TimeKeeper
public:
TimeKeeper(double presetDuration, double smoothDuration, double hardcutDuration, double easterEgg);

/**
* @brief Sets a custom time value to use instead of the system time.
* If less than zero, the system time will be used instead.
* @param secondsSinceStart Fractional seconds since rendering the first frame.
*/
void SetFrameTime(double secondsSinceStart);

/**
* @brief Gets the time of the last frame rendered.
* @note This will not return the value set with SetFrameTime, but the actual time used to render the last frame.
* If a user-specified frame time was set, this value is returned. Otherwise, the frame time measured via the
* system clock will be returned.
* @return Seconds elapsed rendering the last frame since starting projectM.
*/
double GetFrameTime() const;

/**
* @brief Updates internal timers with either the system clock or a user-specified time value.
*/
void UpdateTimers();

void StartPreset();
Expand Down Expand Up @@ -93,6 +112,8 @@ class TimeKeeper
std::random_device m_randomDevice{};
std::mt19937 m_randomGenerator{m_randomDevice()};

double m_userSpecifiedTime{-1.0}; //!< User-specifed run time. If set to a value >= 0.0, this time is used instead of the system clock.

double m_secondsSinceLastFrame{};

double m_easterEgg{};
Expand Down
Loading