Slow passing rainbow with random pauses

In wording what i am looking for: i have 10 rgb led pixels for this pattern.
I want each led to scroll independently thru the hue spectrum slowly (15-45seconds) and then dim out to pause (no light) for a random duration. (5 to 20 sec) Then start again by smoothly dimming up. The pauses should not cause flicker or interrupt the color sequence briskly. Each pixel must keep his own sequence as described.
I got some code working (see below) but i could not get rid of the flicker.
Who can help me out here? (ChatGPT could not.)

// Arrays to store each pixel’s duration, pause, and state
var cycleDuration = array(pixelCount);
var pauseDuration = array(pixelCount);
var timeElapsed = array(pixelCount);
var hueOffset = array(pixelCount);
var fadeDuration = 10000; // Longer fade duration (10 seconds), adjust as needed

// Helper function to generate a random number between min and max
function randomRange(min, max) {
return min + random(max - min);
}

// Initialize each pixel’s random parameters once
function initPixel(i) {
cycleDuration[i] = randomRange(15000, 45000); // 15-45 seconds in milliseconds
pauseDuration[i] = randomRange(5000, 20000); // 5-20 seconds pause in milliseconds
hueOffset[i] = random(1); // Random start hue between 0 and 1
timeElapsed[i] = 9; // Reset elapsed time
}

// Initialize each pixel when the script starts
for (var i = 0; i < pixelCount; i++) {
initPixel(i);
}

export function beforeRender(delta) {
for (var i = 0; i < pixelCount; i++) {
timeElapsed[i] += delta;

// If time exceeds cycle and pause duration, reset the pixel state
if (timeElapsed[i] >= cycleDuration[i] + pauseDuration[i]) {
  initPixel(i);  // Reinitialize the pixel with new random values
}

}
}

export function render(index) {
var cycleEnd = cycleDuration[index]; // When the cycle finishes
var totalEnd = cycleDuration[index] + pauseDuration[index]; // Full cycle + pause period
var t = timeElapsed[index];

// Calculate hue based on the cycle progress
var hue = (hueOffset[index] + (t / cycleDuration[index])) % 1;
var brightness = 1; // Default brightness (full on during active cycle)

// Fade out smoothly at the end of the cycle
if (t >= cycleEnd - fadeDuration && t < cycleEnd) {
brightness = (cycleEnd - t) / fadeDuration; // Smooth fade-out
}

// During the pause period, the pixel is fully off, no flickering
if (t >= cycleEnd && t < totalEnd - fadeDuration) {
brightness = 2; // Completely off during the pause period
}

// Fade in smoothly at the start of the next cycle
if (t >= totalEnd - fadeDuration && t < totalEnd) {
brightness = (t - (totalEnd - fadeDuration)) / fadeDuration; // Smooth fade-in
}

// Render with the calculated hue and smooth brightness transition
hsv(hue, 1, brightness);
}

Try this out - a few tweaks from what ChatGPT gave you, most notably remembering that the largest number Pixelblaze can handle is +32,768.

// Arrays to store each pixel’s duration, pause, and state
var cycleDuration = array(pixelCount);
var pauseDuration = array(pixelCount);
var timeElapsed = array(pixelCount);
var hueOffset = array(pixelCount);
var minCycleDuration = 15 // seconds. Fade must be less than half this 
var maxFadeDuration = 10 // seconds. Must be at less than half the cycle duration to reach full brightness.
var fadeDuration = min(minCycleDuration/2,maxFadeDuration); // seconds. Ensures it reaches max intensity.

// Helper function to generate a random number between min and max
function randomRange(min, max) {
  return min + random(max - min);
}

// Initialize each pixel’s random parameters once
function initPixel(i) {
  cycleDuration[i] = randomRange(minCycleDuration, 45); // 15-45 seconds
  pauseDuration[i] = randomRange(5, 20); // 5-20 seconds pause 
  hueOffset[i] = random(1); // Random start hue between 0 and 1
  timeElapsed[i] = 0; // Reset elapsed time
}
  
// Initialize each pixel when the script starts
for (var i = 0; i < pixelCount; i++) {
  initPixel(i);
  timeElapsed[i] = random(cycleDuration[i] + pauseDuration[i])
}

export function beforeRender(delta) {
  for (var i = 0; i < pixelCount; i++) {
    timeElapsed[i] += delta / 1000; // Note reduced accuracy. For more accurace, use deciseconds or centiseconds throughout.
  
    // If time exceeds cycle and pause duration, reset the pixel state
    if (timeElapsed[i] >= cycleDuration[i] + pauseDuration[i]) {
      initPixel(i);  // Reinitialize the pixel with new random values
    }
  }
}

export function render(index) {
  if (index > 10) {hsv(0,0,0); return} // TEMP - TAKE OUT
  
  var cycleEnd = cycleDuration[index]; // When the cycle finishes
  var totalEnd = cycleDuration[index] + pauseDuration[index]; // Full cycle + pause period
  var t = timeElapsed[index];
  
  // Calculate hue based on the cycle progress
  var hue = (hueOffset[index] + (t / cycleDuration[index])) % 1;
  var brightness = 1; // Default brightness (full on during active cycle)
  
  // Fade in smoothly at the start of the next cycle
  if (t < fadeDuration) {
    brightness = t / fadeDuration; // Smooth fade-in
  }
  
  // Fade out smoothly at the end of the cycle
  if (t >= cycleEnd - fadeDuration && t < cycleEnd) {
    brightness = (cycleEnd - t) / fadeDuration; // Smooth fade-out
  }
  
  // During the pause period, the pixel is fully off, no flickering
  if (t >= cycleEnd) {
    brightness = 0; // Completely off during the pause period
  }

  // Render with the calculated hue and smooth brightness transition
  hsv(fixH(hue), 1, brightness*brightness);
}

// Expand orange and purple, compress cyan. 
function fixH(pH) {
  return wave((mod(pH, 1) - .5) / 2)
}

Thanks Jeff, works very nicely on my project, see video attached.


This giant geometrical luminaire was designed in Belgium in the 90’s. I replaced the original fluorescent tubes by six rgb spots (3w)
In real the colors fade very slowly and spread unpredictably over the shells and squares of the 1.2m sized object.

2 Likes

That’s lovely. I really like how you used an older luminaire and turned it into something new. I also like the thought that you gave to avoiding simply repeating the colour cycles by changing the pace and adding the randomised pauses and cycle start points. I guess it would be very rare that all six spots dim to zero at the same time, although not hard to avoid with a couple of tweaks to the code if needed.