Code for Task #1 - Random Random Roger Roger!

Add a variable that you always reset to zero when a new pixel starts, and then if your wave is peaked, instead of suddenly on, you can ease it up (and then down)… Add the wave gradually to the variable controlling “brightness”. Don’t just add it all at once, ease adding it in by some margin.

Make sense?

You have a wave… But you really want wave like (or breathing) behavior on a point that starts from zero, climbs to a Max, but maybe it goes up a bit, goes down, goes up up up, goes down… goes up… And goes out.

This is actually a key animation to learn. Think Candle flame, for example.

A smooth transition.

Sorry @Scruffynerf I’m not getting it… Maybe if I showed my beforeRender function?

> Blockquote

export function beforeRender(delta) {
time_bucket += delta
brightness = wave(time(0.015 * max_twinkle))
if ( time_bucket >= max_delay *10000) {
time_bucket = 0
color = random(1) // reset to random color
rand_pixel = floor(random(pixelCount)) // Give me a ramdom pixel between 0 and (pixelCount - 1)
breath_val = 0
}

}

> Blockquote

You have brightness as a “constant” wave, but if in middle of that loop, you decide it’s a new pixel, you should reset that wave, or otherwise ensure the new pixel brightness is zero.

I’m suggesting tracking brightness as a new variable, and making your brightness a value that “influences” the new brightness.

wait there is a way to reset the wave?! I suppose I could look at the wave value and wait until it goes back to zero-ish… and then add the time I waited to the twinkle time

No, but there is a way to zero a variable.

X = wave(whatever) is a wave between 0…1

Y = Y + ( (X - 0.5) * some scale)
Now Y will vary with X… up and down…
You will have to clamp the values of Y so it doesn’t go too high or low
And anytime you set Y to zero, light is off and can only go up… And depending on your scaling, it’ll “warble” or twinkle. Not quite random, but randomize your scale too a bit … then it will wiggle around, see?

1 Like

In early, because it looks like I’m going to be stuck working on a plumbing repair the rest of the day. Thanks @scruffynerf for running this show – I’m already looking forward to next week’s installment!

Today, you will see elegant, compact and efficient code for this job. Then, there’s this. @jeff put the idea in my head - the only way to get rid of it was to write the code. It uses… yes, the center column from a cellular automaton running Wolfram’s Rule 30 to generate a stream of random bits, with which I control all the blinking lights. Without, further ado, here it is:

/*
  Rule 30 Flasher
  https://en.wikipedia.org/wiki/Rule_30 
  
  Flashes a random pixel for a random length of time, with a random amount of twinkling.
  
  Uses a 1D cellular automaton running Wolfram's rule 30 to generate a stream
  of random bits, which we use to build various random numbers.
  
  This pattern looks random, but for a given set of initial conditions, it's actually
  completely deterministic -- it'll be the same every time. 
*/

// arrays to hold the state of the current and previous generations of our
// automaton.
var ca_size = 32;                   // number of cells per generation
var ca_center = floor(ca_size / 2); // index center cell -- precalculates because we use it often

var g0 = array(ca_size);  
var g1 = array(ca_size);

// pointers to data arrays, so we can swap arrays by just swapping pointers
// instead of moving data.
var currState = g0;
var nextState = g1;  

var elapsedTime = 0;   // accumulator for timing pixel displays
var lifetime = 500;    // how long (ms) the current pixel will be displayed
var pix,hue,bri;       // pixel index, color and current brightness

// Get random fraction of specified length from rule 30 generator
function getRandomFraction(n) {
  return result = getRandomBits(n) >> n
}

// return the specified number of random bits into a 32-bit buffer
// (will discard oldest bits if n > 32)
function getRandomBits(n) {
  var result = 0;
  
  for (i = 0; i < n; i++) {
    calcNextGen();    
    result = result << 1;
    result |= currState[ca_center];
  }
  return result;
}

// set up the CA mechanics
function initialize() {

// set up pointers to our data array.
  currState = g0;
  nextState = g1;  

// seed the cell at the center of the array.  Bits in this
// column are reliably random, so it's where we'll get our
// random bits while running.
  currState[ca_center] = 1;
}

// Use the current state and Wolfram's Rule 30 to calculate the CA's next
// generation. Rule 30 generates very chaotic patterns, and the
// center column of the pattern is known to be a reasonably good RNG.
function calcNextGen() {
  var left,curr,right;

// set up 8-bit representation of current cell state. Note that "neighborhood"
// wraps around the ends of the array.
  for (var i = 0; i < ca_size; i++) {
    left = currState[(i > 0) ? i - 1 : ca_size - 1] << 2;
    curr = currState[i] << 1;
    right = currState[(i + 1) % ca_size];
    
// apply transformation rule
    nextState[i] = (30 & (1 << (left | curr | right))) > 0;
  }

// swap state arrays so the data for the generation we just calculated is in
// currState, which we will use to make random things happen.
  var a = currState; currState = nextState; nextState = a;  
}

// Main entry point - set up the CA before we start drawing pixels
initialize();

export function beforeRender(delta) {
  elapsedTime += delta;

// display timer -- accumulate deltas, generate new pixel when
// total time has reached or exceeded this pixel's random
// lifetime
  if (elapsedTime >= lifetime) {

// 8 bits will generate 256 distinct hues.     
    hue = getRandomFraction(8);    
    
// pixels will live for from 100 to 1200 ms   
    lifetime = 100+(1100*getRandomFraction(8));
    
// pick a random pixel. 11 bits will generate 2k unique indices. If your
// strip has more than 2k LEDs, not all pixels will be hit, but the indices
// will still be distributed over the whole strip.
    pix = floor(pixelCount * getRandomFraction(11)); 
    
// reset timer for the new pixel    
    elapsedTime = 0;  
  }
  
// calculate fadeout and twinkle, then adjust gamma.
// twinkle is controlled by grabbing a random bit from the CA on every frame
  bri = 1-((elapsedTime / lifetime)+(0.1 * getRandomBits(1)));
  bri = bri * bri;
}

// draw current pixel.Fadeout is a function of the random lifetime calculated
// in beforeRender, random twinkling is generated by varying saturation according to a bitstream
// from the CA 
export function render(index) {
  hsv(hue,1,(pix == index) * bri)
}
2 Likes

So fun, and a lot of your bitwise work is inspirational.

How the heck did you come up with this? Is this a general form for any of the 256 rules?? I mean, I could follow how to work out what Wikipedia says: “[left_cell XOR (central_cell OR right_cell)]” by writing out a K-map, but this is really interesting.

By setting a fixed size of 32 columns with wrapping, does that make it periodic?

So much good code, put to the least of uses, deciding what pixel to light up. I know that generator could be used again and again.

Here’s my “readable” version! I actually like zranger1’s variable names (elapsedTime, lifetime) better.

export var hue, pixel, blinkDuration, offDuration, accumMs, twinkling, v

function next() {
  hue = random(1)                    // New random color
  pixel = floor(random(pixelCount))  // The pixel index to light, 0..(pixelCount-1)
  blinkDuration = 200 + random(2000) // In ms. 0.2 - 2.2 seconds on
  offDuration = random(1000)         // In ms. 0 - 1 seconds off
  accumMs = 0                        // Reset the animation timer
}

export function beforeRender(delta) {
  // accumMs accumulates how many milliseconds we are into the current 
  // (blinkDuration + offDuration) long cycle
  accumMs += delta
  
  v = triangle(accumMs / blinkDuration)  // Fade in and out, 0->1->0
  
  if (random(100/delta) < 1) twinkling = !twinkling // Chaotic flipflop 0/1
  if (v > .6) v -= twinkling * .5  // If in the top 40% of intensities, twinkle
  v *= v                           // Typical gamma-correction
  
  if (accumMs > blinkDuration) v = 0                // Dark for offDuration
  if (accumMs > blinkDuration + offDuration) next() // Zero's accumMs
}

export function render(index) {
  // Putting "v < someting" for saturation is a fun trick I first saw in
  // Wizzard's code. It says, if brightness is really high, ignore the hue
  // and display white instead!
  if (pixel == index) hsv(hue, v < .95, v)
  else hsv(0, 0, 0) // all other pixels off
}
2 Likes

Here is my submission :slight_smile:

/*
 * Assignment #1 from Devoh
 * Color: random
 * What to light: one random pixel at a time.
 * Speed: not too fast, but not regular either. 
 * It should twinkle randomly for a few, go out for a bit and then light another at random. 
 * We shouldn’t be able to predict it.
 */
 
// Slider for max delay between pixels disappearing and next appearing
export var max_delay = 0.5
export function sliderMaxDelayBetweenStars(v) {
  //  (Max-Min)*v + Min   Gives a value from min to max
    max_delay = v
}

export var max_twinkle = 2
export function sliderMaxStarTwinkleSpeed(t) {
    max_twinkle = t
}

export var time_bucket = 0
export var rand_pixel = 0
export var color = 0
var rand_delay = random(100)

// Brightness is simply a wave that you set the frequency to  it cycles between 0..1
// that is then fed into random and 0.5 added to it and held in range of 0..1 with a clamp..  
// This is all done to give it a twinkle
brightness = clamp(random(wave(time(0.015 * max_twinkle)))+0.5,0,1) 

// the beforeRender function is called once before each animation frame
// and is passed a delta in fractional milliseconds since the last frame
// this has very high resolution, down to 6.25 nanosecons!
export function beforeRender(delta) {
  time_bucket += delta
  brightness = clamp(random(wave(time(0.015 * max_twinkle)))+0.5,0,1)
  if ( time_bucket >= max_delay *100 * rand_delay) {
    time_bucket = 0
    color = random(1)                                             // reset to random color
    rand_pixel = floor(random(pixelCount))                        // Give me a ramdom pixel between 0 and (pixelCount - 1)
    rand_delay= random(100)
  }

}

// the render function is called for every pixel. If the current index is the special pixel 
// then light it with the calculated brightness otherwise make it black.
export function render(index) {
  if (index == rand_pixel) {
    hsv(color,1,brightness)
  } else {
    hsv(0,0,0)
  }
}
3 Likes

:fireworks: Cool, @devoh! Task #1 well done! Your use of wave makes for a nice smooth twinkle. The speed sliders are a nice touch too.

1 Like

Excellent! Really good write up and comments, @devoh

1 Like

@jeff, yes, this method works for all the rules. It’s based on me staring at the CA thing until it finally dawned on me how smart and simple Wolfram’s coding scheme is. It works like this:

This kind of 1D CA determines the next state of a cell by looking at the cell and it’s neighbors to the left and right. They’re binary - can only be on or off. Taken together, they make a 3 bit number, which will have a value from 0 to 7 depending on the cell configuration.

In Wolfram’s scheme, the rule number (0…255) is simply a binary encoding of what the next cell state should be for each of the 8 possible configurations. 8 bits == 256 rules.

So to generate the next state, all you have to do is use the current state to get your 3 bit 0-7 configuration code n, then look up bit n in the rule. If it’s 1, then the cell lives. If 0, it dies.

And yeah, the wrapping is dubious. I kept it in because it looks cooler in the display version, and doesn’t seem to interfere visibly with the chaos. But I didn’t test it, and I should, particularly since I just built tools to do so.

Clearly, there will be randograms, with and without wrap in the near future! I can test various widths too, but I did find a paper where somebody had actually patented a variant of this with only 27(!?) columns. I’m really curious about what other interesting looking streams of bits I can get out of this , maybe using some of the less chaotic rules, or switching periodically between rules. It’s a fun toy to have around!

2 Likes

@jeff, as always, elegant, concise and clear. The aesthetic detail is great - I particularly like the way you control the twinkling and bump the saturation for bright pixels.

2 Likes

Here we go, but not really -
If this is the place for it, I’m wondering what is wrong here… I’m getting an “unexpected token else error”, at least that’s wrong apparently, and I’ve looked at it too long with these old newbie eyes - Thanks!

ThePixel = floor(random(pixelCount))
h = 0
s = 1
v = 1

export function beforeRender(delta) { index = floor(random(pixelCount)) }
// Delta is elapsed cycle time in milliseconds

export function render(index){
if(index==ThePixel)
hsv(h,s,v)
} else {
hsv(0,0,0)
}

Index is your “counter” as you loop thru the pixels, so don’t reuse it like you did in BeforeRender. That is where you should use ThePixel.

Was calling a random “position” on the string, a value returned by floor(random(pixelCount) …

Looks like I issued it redundantly…

Oh your error about unexpected else:

If (something is true) {
Do this
} Else {
Do this other thing
}

But you can also write an if this way:
If (something is true) do this

But you can’t do an else then. You need the {} to use else.

1 Like

Perfect. Got it.
Is there some reason for scattering the { }s about in that particular way?

Only for readability. The only thing required is ensuring you have matching pairs.