diff --git a/src/api/include/projectM-4/parameters.h b/src/api/include/projectM-4/parameters.h index 2d9c70c89..63385f51c 100644 --- a/src/api/include/projectM-4/parameters.h +++ b/src/api/include/projectM-4/parameters.h @@ -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. * diff --git a/src/libprojectM/ProjectM.cpp b/src/libprojectM/ProjectM.cpp index 3e97724de..76c9d62c0 100644 --- a/src/libprojectM/ProjectM.cpp +++ b/src/libprojectM/ProjectM.cpp @@ -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); diff --git a/src/libprojectM/ProjectM.hpp b/src/libprojectM/ProjectM.hpp index 844597128..d57b9bf47 100644 --- a/src/libprojectM/ProjectM.hpp +++ b/src/libprojectM/ProjectM.hpp @@ -103,6 +103,22 @@ public: 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; diff --git a/src/libprojectM/ProjectMCWrapper.cpp b/src/libprojectM/ProjectMCWrapper.cpp index f81aefd23..15b7745d3 100644 --- a/src/libprojectM/ProjectMCWrapper.cpp +++ b/src/libprojectM/ProjectMCWrapper.cpp @@ -7,6 +7,7 @@ #include #include #include +#include namespace libprojectM { @@ -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); diff --git a/src/libprojectM/TimeKeeper.cpp b/src/libprojectM/TimeKeeper.cpp index 612b0ab33..6c1817b84 100644 --- a/src/libprojectM/TimeKeeper.cpp +++ b/src/libprojectM/TimeKeeper.cpp @@ -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(currentTime - m_startTime).count(); + } - double currentFrameTime = std::chrono::duration(currentTime - m_startTime).count(); m_secondsSinceLastFrame = currentFrameTime - m_currentTime; m_currentTime = currentFrameTime; m_presetFrameA++; diff --git a/src/libprojectM/TimeKeeper.hpp b/src/libprojectM/TimeKeeper.hpp index 23d17df6c..949bcf5c2 100644 --- a/src/libprojectM/TimeKeeper.hpp +++ b/src/libprojectM/TimeKeeper.hpp @@ -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(); @@ -93,6 +112,8 @@ private: 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{};