From b6277e874ae5b2017578e924ef38a4bf0b02c570 Mon Sep 17 00:00:00 2001 From: Kai Blaschke Date: Fri, 22 May 2026 13:04:18 +0200 Subject: [PATCH] Add API to set or retrieve values from user sprite expression variables This enables developers to externally control user sprites from the application code, e.g. allowing for interactive effects like rotation or movement to display logos, watermarks and other stuff. --- src/api/include/projectM-4/user_sprites.h | 22 +++++++++++ src/libprojectM/ProjectM.cpp | 10 +++++ src/libprojectM/ProjectM.hpp | 16 ++++++++ src/libprojectM/ProjectMCWrapper.cpp | 13 +++++++ .../UserSprites/MilkdropSprite.cpp | 20 ++++++++++ .../UserSprites/MilkdropSprite.hpp | 22 ++++++----- src/libprojectM/UserSprites/Sprite.hpp | 14 +++++++ src/libprojectM/UserSprites/SpriteManager.cpp | 37 ++++++++++++++++++- src/libprojectM/UserSprites/SpriteManager.hpp | 16 ++++++++ 9 files changed, 160 insertions(+), 10 deletions(-) diff --git a/src/api/include/projectM-4/user_sprites.h b/src/api/include/projectM-4/user_sprites.h index 1f76646f65..87bba1c11d 100644 --- a/src/api/include/projectM-4/user_sprites.h +++ b/src/api/include/projectM-4/user_sprites.h @@ -123,6 +123,28 @@ PROJECTM_EXPORT void projectm_sprite_set_max_sprites(projectm_handle instance, */ PROJECTM_EXPORT uint32_t projectm_sprite_get_max_sprites(projectm_handle instance); +/** + * @brief Retrieves the current value of an expression variable of a given sprite. + * + * @param instance The projectM instance handle. + * @param sprite_id The sprite ID returned by projectm_sprite_create() to retrieve the value for. + * @param var_name The variable name to retrieve the value for. + * @return The current value of the requested variable. Returns 0.0 if the variable is not defined. + * @since 4.2.0 + */ +PROJECTM_EXPORT double projectm_sprite_get_var(projectm_handle instance, uint32_t sprite_id, const char* var_name); + +/** + * @brief Sets the value of the given variable for a single sprite instance. + * + * @param instance The projectM instance handle. + * @param sprite_id The sprite ID returned by projectm_sprite_create() to set the value for. + * @param var_name The variable to set a new value for. + * @param value The new value. + * @since 4.2.0 + */ +PROJECTM_EXPORT void projectm_sprite_set_var(projectm_handle instance, uint32_t sprite_id, const char* var_name, double value); + #ifdef __cplusplus } // extern "C" #endif diff --git a/src/libprojectM/ProjectM.cpp b/src/libprojectM/ProjectM.cpp index a042f244c6..5693bf2487 100644 --- a/src/libprojectM/ProjectM.cpp +++ b/src/libprojectM/ProjectM.cpp @@ -366,6 +366,16 @@ auto ProjectM::UserSpriteIdentifiers() const -> std::vector return m_spriteManager->ActiveSpriteIdentifiers(); } +auto ProjectM::UserSpriteGetVariableValue(uint32_t spriteId, const std::string& variableName) const -> double +{ + return m_spriteManager->GetSpriteVariableValue(spriteId, variableName); +} + +void ProjectM::UserSpriteSetVariableValue(uint32_t spriteId, const std::string& variableName, double value) +{ + m_spriteManager->SetSpriteVariableValue(spriteId, variableName, value); +} + void ProjectM::BurnInTexture(uint32_t openGlTextureId, int left, int top, int width, int height) { if (m_activePreset) diff --git a/src/libprojectM/ProjectM.hpp b/src/libprojectM/ProjectM.hpp index 9879e77f0c..eb7e1adbc4 100644 --- a/src/libprojectM/ProjectM.hpp +++ b/src/libprojectM/ProjectM.hpp @@ -271,6 +271,22 @@ class PROJECTM_CXX_EXPORT ProjectM */ auto UserSpriteIdentifiers() const -> std::vector; + /** + * @brief Returns the current value of a variable in a user sprite's expression code. + * @param spriteId The sprite ID to retrieve the value for. + * @param variableName The variable to retrieve the value for. + * @return The value of the requested variable and sprite. + */ + auto UserSpriteGetVariableValue(uint32_t spriteId, const std::string& variableName) const -> double; + + /** + * @brief Set the value of a variable in a user sprite's expression code. + * @param spriteId The sprite ID to set the value for. + * @param variableName The variable to set the value for. + * @param value The new value. + */ + void UserSpriteSetVariableValue(uint32_t spriteId, const std::string& variableName, double value); + /** * @brief Draws the given texture on the active preset's main texture to get a "burn-in" effect. * @param openGlTextureId The OpenGL texture to draw onto the active preset(s). diff --git a/src/libprojectM/ProjectMCWrapper.cpp b/src/libprojectM/ProjectMCWrapper.cpp index cd2bca2f2e..3c4c30839b 100644 --- a/src/libprojectM/ProjectMCWrapper.cpp +++ b/src/libprojectM/ProjectMCWrapper.cpp @@ -538,6 +538,19 @@ uint32_t projectm_sprite_get_max_sprites(projectm_handle instance) return projectMInstance->UserSpriteLimit(); } +double projectm_sprite_get_var(projectm_handle instance, uint32_t sprite_id, const char* var_name) +{ + auto* projectMInstance = handle_to_instance(instance); + + return projectMInstance->UserSpriteGetVariableValue(sprite_id, var_name); +} + +void projectm_sprite_set_var(projectm_handle instance, uint32_t sprite_id, const char* var_name, double value) +{ + auto* projectMInstance = handle_to_instance(instance); + + projectMInstance->UserSpriteSetVariableValue(sprite_id, var_name, value); +} void projectm_set_log_callback(projectm_log_callback callback, bool current_thread_only, void* user_data) { if (current_thread_only) diff --git a/src/libprojectM/UserSprites/MilkdropSprite.cpp b/src/libprojectM/UserSprites/MilkdropSprite.cpp index 7a8af5cb9e..2350ad2179 100644 --- a/src/libprojectM/UserSprites/MilkdropSprite.cpp +++ b/src/libprojectM/UserSprites/MilkdropSprite.cpp @@ -290,6 +290,26 @@ auto MilkdropSprite::Done() const -> bool return m_spriteDone; } +auto MilkdropSprite::GetVariableValue(const std::string& variableName) const -> double +{ + PRJM_EVAL_F const* var = projectm_eval_context_register_variable(m_codeContext.spriteCodeContext, variableName.c_str()); + if (var != nullptr) + { + return *var; + } + + return 0.0; +} + +void MilkdropSprite::SetVariableValue(const std::string& variableName, double value) +{ + PRJM_EVAL_F * var = projectm_eval_context_register_variable(m_codeContext.spriteCodeContext, variableName.c_str()); + if (var != nullptr) + { + *var = value; + } +} + MilkdropSprite::CodeContext::CodeContext() : spriteCodeContext(projectm_eval_context_create(nullptr, nullptr)) { diff --git a/src/libprojectM/UserSprites/MilkdropSprite.hpp b/src/libprojectM/UserSprites/MilkdropSprite.hpp index d19139012b..806b0cbae5 100644 --- a/src/libprojectM/UserSprites/MilkdropSprite.hpp +++ b/src/libprojectM/UserSprites/MilkdropSprite.hpp @@ -27,6 +27,10 @@ class MilkdropSprite : public Sprite auto Done() const -> bool override; + auto GetVariableValue(const std::string& variableName) const -> double override; + + void SetVariableValue(const std::string& variableName, double value) override; + private: /** * @brief Context for the init and per-frame code. @@ -43,7 +47,7 @@ class MilkdropSprite : public Sprite /** * @brief Compiles and runs the init code of the sprite once, if any. - * Also sets up a the default values of the output variables. + * Also sets up the default values of the output variables. * @param initCode The initialization code. */ void RunInitCode(const std::string& initCode, const Renderer::RenderContext& renderContext); @@ -74,15 +78,15 @@ class MilkdropSprite : public Sprite // Output variables PRJM_EVAL_F* done{}; //!< If this becomes non-zero, the sprite is deleted. Default: 0.0 PRJM_EVAL_F* burn{}; //!< If non-zero, the sprite will be "burned" into currently rendered presets when done is also true, effectively "dissolving" the sprite in the preset. Default: 1.0 - PRJM_EVAL_F* x{}; //!< Sprite x position (position of the image center). Range from -1000 to 1000. Default: 0.5 - PRJM_EVAL_F* y{}; //!< Sprite y position (position of the image center). Range from -1000 to 1000. Default: 0.5 - PRJM_EVAL_F* sx{}; //!< Sprite x scaling factor. Range from -1000 to 1000. Default: 1.0 - PRJM_EVAL_F* sy{}; //!< Sprite y scaling factor. Range from -1000 to 1000. Default: 1.0 + PRJM_EVAL_F* x{}; //!< Sprite X position (position of the image center). Range from -1000 to 1000. Default: 0.5 + PRJM_EVAL_F* y{}; //!< Sprite Y position (position of the image center). Range from -1000 to 1000. Default: 0.5 + PRJM_EVAL_F* sx{}; //!< Sprite X scaling factor. Range from -1000 to 1000. Default: 1.0 + PRJM_EVAL_F* sy{}; //!< Sprite Y scaling factor. Range from -1000 to 1000. Default: 1.0 PRJM_EVAL_F* rot{}; //!< Sprite rotation in radians (2*PI equals one full rotation). Default: 0.0 - PRJM_EVAL_F* flipx{}; //!< If flag is non-zero, the sprite is flipped on the x axis. Default: 0.0 - PRJM_EVAL_F* flipy{}; //!< If flag is non-zero, the sprite is flipped on the y axis. Default: 0.0 - PRJM_EVAL_F* repeatx{}; //!< Repeat count of the image on the sprite quad on the x axis. Fractional values allowed. Range from 0.01 to 100.0. Default: 1.0 - PRJM_EVAL_F* repeaty{}; //!< Repeat count of the image on the sprite quad on the y axis. Fractional values allowed. Range from 0.01 to 100.0. Default: 1.0 + PRJM_EVAL_F* flipx{}; //!< If flag is non-zero, the sprite is flipped on the X axis. Default: 0.0 + PRJM_EVAL_F* flipy{}; //!< If flag is non-zero, the sprite is flipped on the Y axis. Default: 0.0 + PRJM_EVAL_F* repeatx{}; //!< Repeat count of the image on the sprite quad on the X axis. Fractional values allowed. Range from 0.01 to 100.0. Default: 1.0 + PRJM_EVAL_F* repeaty{}; //!< Repeat count of the image on the sprite quad on the Y axis. Fractional values allowed. Range from 0.01 to 100.0. Default: 1.0 PRJM_EVAL_F* blendmode{}; //!< Image blending mode. 0 = Alpha blending (default), 1 = Decal mode (no transparency), 2 = Additive blending, 3 = Source color blending, 4 = Color key blending. Default: 0 PRJM_EVAL_F* r{}; //!< Modulation color used in some blending modes. Default: 1.0 PRJM_EVAL_F* g{}; //!< Modulation color used in some blending modes. Default: 1.0 diff --git a/src/libprojectM/UserSprites/Sprite.hpp b/src/libprojectM/UserSprites/Sprite.hpp index 629426af52..bd6524489f 100644 --- a/src/libprojectM/UserSprites/Sprite.hpp +++ b/src/libprojectM/UserSprites/Sprite.hpp @@ -47,6 +47,20 @@ class Sprite * @return true if the sprite should be deleted, false if not. */ virtual auto Done() const -> bool = 0; + + /** + * @brief Returns the current value of a variable in the sprite's expression code. + * @param variableName The variable to retrieve the value for. + * @return The value of the requested variable and sprite. + */ + virtual auto GetVariableValue(const std::string& variableName) const -> double = 0; + + /** + * @brief Set the value of a variable in the sprite's expression code. + * @param variableName The variable to set the value for. + * @param value The new value. + */ + virtual void SetVariableValue(const std::string& variableName, double value) = 0; }; } // namespace UserSprites diff --git a/src/libprojectM/UserSprites/SpriteManager.cpp b/src/libprojectM/UserSprites/SpriteManager.cpp index 7be05f814d..9891ad8792 100644 --- a/src/libprojectM/UserSprites/SpriteManager.cpp +++ b/src/libprojectM/UserSprites/SpriteManager.cpp @@ -65,7 +65,8 @@ void SpriteManager::Draw(const Audio::FrameAudioData& audioData, { std::vector toDestroy; - for (auto& idAndSprite : m_sprites) { + for (auto& idAndSprite : m_sprites) + { idAndSprite.second->Draw(audioData, renderContext, outputFramebufferObject, presets); if (idAndSprite.second->Done()) @@ -132,6 +133,40 @@ auto SpriteManager::SpriteSlots() const -> uint32_t return m_spriteSlots; } +auto SpriteManager::GetSpriteVariableValue(SpriteIdentifier spriteIdentifier, const std::string& variableName) const -> double +{ + if (m_spriteIdentifiers.find(spriteIdentifier) == m_spriteIdentifiers.end()) + { + return 0.0; + } + + for (const auto& idAndSprite : m_sprites) + { + if (idAndSprite.first == spriteIdentifier) + { + return idAndSprite.second->GetVariableValue(variableName); + } + } + + return 0.0; +} + +void SpriteManager::SetSpriteVariableValue(SpriteIdentifier spriteIdentifier, const std::string& variableName, double value) +{ + if (m_spriteIdentifiers.find(spriteIdentifier) == m_spriteIdentifiers.end()) + { + return; + } + + for (const auto& idAndSprite : m_sprites) + { + if (idAndSprite.first == spriteIdentifier) + { + idAndSprite.second->SetVariableValue(variableName, value); + } + } +} + auto SpriteManager::GetLowestFreeIdentifier() -> SpriteIdentifier { SpriteIdentifier lowestId = 0; diff --git a/src/libprojectM/UserSprites/SpriteManager.hpp b/src/libprojectM/UserSprites/SpriteManager.hpp index 6ad391cd4c..050243dc90 100644 --- a/src/libprojectM/UserSprites/SpriteManager.hpp +++ b/src/libprojectM/UserSprites/SpriteManager.hpp @@ -82,6 +82,22 @@ class SpriteManager */ auto SpriteSlots() const -> uint32_t; + /** + * @brief Returns the current value of a variable in a user sprite's expression code. + * @param spriteIdentifier The sprite ID to retrieve the value for. + * @param variableName The variable to retrieve the value for. + * @return The value of the requested variable and sprite. + */ + auto GetSpriteVariableValue(SpriteIdentifier spriteIdentifier, const std::string& variableName) const -> double; + + /** + * @brief Set the value of a variable in a user sprite's expression code. + * @param spriteIdentifier The sprite ID to set the value for. + * @param variableName The variable to set the value for. + * @param value The new value. + */ + void SetSpriteVariableValue(SpriteIdentifier spriteIdentifier, const std::string& variableName, double value); + private: using SpriteIdPair = std::pair;