Thunder Documentation

Effect Language

Thunder effects are implemented as WebGPU fragment shaders. We take inspiration from Shadertoy and each shader is implemented using raytracing. While shadertoy uses GLSL, Thunder instead uses WGSL as it’s shader language. If you use chrome, you can get a great online tour of WGSL.

effectMain

Every effect must have an effectMain function.

fn effectMain(in: vec2<f32>) -> vec4<f32> {
  let alpha = sin(in.x + u.beat*5.0)*0.5 + 0.5;
  return vec4<f32>(1.0, 0.0, 0.0, alpha);
}

This function is called for every light in a fixture. The in parameter gives the pixel x and y coordinates. An LED strip only uses the x coordinate with y always equal to 0.

The return value is the pixel rgba colour: (r,g,b,alpha) where each component is a number in [0,1].

Signal Uniform

You can access metadata about the light, as well as current audio signal data through the signal uniform.

struct Uniform {
    width: f32,         // Width of the fixture.
    height: f32,        // Height of the fixture.
    frame: f32,         // Frame number.
    time: f32,          // Time in milliseconds.
    beat: f32,          // Beat clock.
    downbeat: f32,      // Downbeat clock.
    spec_high: f32,     // High frequency spectrum.
    spec_mid_high: f32, // Mid-high frequency spectrum.
    spec_mid_low: f32,  // Mid-low frequency spectrum.
    spec_low: f32,      // Low frequency spectrum.
};

This is bound to the parameter u. For example, the width can be accessed in your code as u.width.

Beat Clock

There are two beat clocks available: beat and downbeat. The beat clock counts each beat, and between beats it gives a floating point value. For example, 2.5 is the moment in time half way between beats 2 and 3. These clocks count upwards until the software is restarted. The downbeat clock works exactly the same way, but counts each downbeat instead.

Frequncy Information

The uniform members spec_low, spec_mid_low, spec_mid_high, and spec_high are the sum of the mel spectrogram over the last several milliseconds. The values are typically from 0 to 100, but at times can go beyond 100.

Parameter Bindings

Effects can have a variable number of user defined parameters. These can be accessed via a parameter binding p. Each parameter becomes a member of the parameter struct bound to p with an lowercase underscored version of its name. For example, if you have a colour palette called Main Colours it will be accessible as p.main_colours. Special characters are stripped out.

Palette

A colour palette parameter can have one or more colours. Each colour is represented as a vec4<f32> with each component mapped to [0,1].

Integer

An integer parameter is represented as an i32.

Float

A float parameter is represented as an f32.

Random Number Functions

Thunder provides several pseudo-random numbers that are seeded from various signals. Usually you’d use the x and y as input, but could use other values too.

Time Based

These functions product new random values on every frame. They are useful for production general noise.

fn time_hash2(seed: vec2<f32>) -> vec3<u32>

Returns three random u32 values based on x and y and the time.

fn time_rand2(seed: vec2<f32>) -> vec3<f32>

Returns three f32 values in the range [0,1] based on x and y and the time.

Beat Based

These functions provide random numbers that only change every time there is a new beat. These are useful to build effects that have random varations based on the beat.

fn beat_hash2(seed: vec2<f32>) -> vec3<u32>

Returns three random u32 values based on x and y and the beat clock.

fn beat_rand2(seed: vec2<f32>) -> vec3<f32>

Returns three f32 values in the range [0,1] based on x and y and the beat clock.