projectM-visualizer / projectM-visualizer/projectm

[FEATURE] output filter to prevent seizure; Temporal Flash Limiting

Open
#947 7 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

enhancement
Dominant language
C++
Stars
4.5k
Forks
485
Avg merge
3d 7h
Merged PRs (30d)
5

Description

Please confirm the following points:
  • This feature request is NOT for the Android apps in the Play Store
  • I have searched the project page to check if a similar request was already made
Application or Project

I don't know / Multiple / Other (please elaborate below)

Is Your Enhancement Related to a Problem?

Flashing lights may cause epileptic seizures. To try to prevent seizures, add a visual output filter ((also) with zero guarantee o promoting the feature).

Your Suggested Enhancement

What hertz or other patterns would need to be filtered to prevent causing epileptic seizures with a music visualization?

How would you add this safety feature to projectm?

According to Gemini 3 (an AI LLM model) FWIW:


Title: Feature Request: Photosensitivity Safety Filter (Epilepsy Protection)

Is your feature request related to a problem? Please describe.

Music visualizations, by nature, often contain rapid strobing, high-contrast flashing, and geometric patterns that can trigger Photosensitive Epilepsy (PSE).

Currently, projectM renders presets exactly as written by the community. Since many presets rely on rapid tan(time) oscillations or frame-inverting strobes, the software creates a high risk for users with seizure disorders. There is currently no "gatekeeper" in the rendering pipeline to ensure output remains within safe physiological limits (e.g., WCAG or Harding FPA standards).

Describe the solution you'd like

I propose implementing a Global Post-Processing Safety Shader.

Instead of editing thousands of .milk files, we should add a final render pass that intercepts the image before it hits the screen. This pass acts as a "Limiter" or "Compressor," but for luminance and flash frequency rather than audio volume.

Safety Criteria to Enforce:

  1. Frequency Cap: Strictly limit full-screen luminance changes to < 3 Hz (3 flashes per second).
  2. Chromatic Damping: Detect and dampen high-saturation Red (660–720 nm) transitions, which are statistically more dangerous.
  3. Slew Rate Limiting: Force rapid black-to-white transitions to fade over several frames rather than toggling instantly.
Technical Implementation

We can achieve this by adding a safety_pass.frag shader in the post-processing pipeline. This requires retaining the Previous Frame texture to compare against the Current Frame.

1. The Shader Logic (GLSL Prototype)

This shader calculates the difference in luminance between the current frame and the last frame. If the difference exceeds a safe delta (calculated based on FPS to ensure <3Hz modulation), it clamps the pixel's brightness.

// safety_pass.frag
#version 330 core
uniform sampler2D u_CurrentFrame;  // Output from projectM
uniform sampler2D u_HistoryFrame;  // Output shown to user last frame
uniform float u_SafeDelta;         // Max allowed brightness change per frame

in vec2 v_TexCoord;
out vec4 FragColor;

float getLuma(vec3 color) {
    return dot(color, vec3(0.299, 0.587, 0.114));
}

void main() {
    vec4 current = texture(u_CurrentFrame, v_TexCoord);
    vec4 previous = texture(u_HistoryFrame, v_TexCoord);

    // 1. Red Dampener (Desaturate dangerous red flashes)
    if (current.r > 0.8 && current.g < 0.2 && current.b < 0.2) {
        current.rgb = mix(current.rgb, vec3(current.r * 0.5), 0.5); 
    }

    // 2. Temporal Luminance Limiter
    float lumaCur = getLuma(current.rgb);
    float lumaPrev = getLuma(previous.rgb);
    float diff = lumaCur - lumaPrev;

    // If change is too fast (Strobe detected)
    if (abs(diff) > u_SafeDelta) {
        // Clamp to maximum safe step
        float targetLuma = lumaPrev + sign(diff) * u_SafeDelta;
        
        // Apply scaling to RGB to maintain color, just darker/lighter
        float scale = targetLuma / (lumaCur + 0.0001); 
        current.rgb *= scale;
    }

    FragColor = current;
}
  1. C++ Integration Strategy

In ProjectM::renderFrame (or equivalent render loop):

    1. Render Preset: Render the visualization to an internal FBO as normal.
    1. Calculate Delta: Determine u_SafeDelta dynamically based on current FPS.
      Formula: 1.0 / (CurrentFPS / 3.0)
    1. Apply Safety Pass: Bind the Safety Shader. Pass the current FBO and the History FBO as textures.
    1. Swap: Render result to screen and copy result to History FBO for the next frame.

Alternative Consideration: Preset Pruning

If a shader implementation is too resource-intensive for specific embedded targets, a secondary solution would be a Python script included in the tools directory that parses .milk files to detect and delete presets containing high-frequency triggers (e.g., searching for high-frequency sin/tan waves on monitor or ob_r variables).


Adding this feature might allow projectM to be used with less risk by users with photosensitive epilepsy, significantly increasing the accessibility of the project (and hopefully preventing injury and increasing enjoyment)

Alternative Solutions

Preset pruning

  • #742
Additional Context

I do not and cannot warrant any of this for fitness for purpose; I offer this (initial ai 'slop') in hopes of reducing probability of photoepileptic seizure.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reviewing ProjectM::renderFrame (or the equivalent render loop) and the post-processing pipeline described in the issue; the proposed safety_pass.frag and History FBO are the main integration points. Before implementation, establish validated seizure-safety criteria and a testable definition of done, since the request is exploratory and provides no tests.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp, python
Domain
accessibility, computer-graphics
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.