Multiple Color Pickers and Arrays?

Purchased 200pix addressable light string to replace older incandescent lights for a year-round holiday tree. I am starting off very simple with just a static display to mimic what a traditional light string would be. There is a “Christmas string lights by @jvydunapatten” on the pattern site that does this exactly as needed, the twinkle effect is a nice touch too. However with all things, being satisfied with the basics just didn’t last very long.

Throughout the year I will want to change the colors to match the season/holiday and the way the array is configure does not lend well to doing more pastel type colors as the saturation is static across the board. My initial thought was to change to rgb instead of hsv as I think it’s easier to deal with, but I figure I’ll stay “natively” with hsv just because why not try and learn something new. Well, I hit that wall pretty quick too as my programing skills are far from adequate.

Also ideally I want this to be more kid friendly, so instead of using direct values, I want to leverage the colorpicker function.

First thing first was that I pared down the initial script for easier testing, and that continues to work as expected.

numHues = 2
hues = array(numHues)
hues[0] = 0.995
hues[1] = 0.6

export function render(index) {
  hue = hues[index % numHues]
  twinkle = random(1) < 0.999 // clock speed dependent
  hsv(hue, 1, twinkle)
}

Secondly I incorporated to the colorpicker, but just in the s and v sense to see how that worked, and that worked as expected.

export function hsvPickerColor(s, v) {
  saturation = s
  value = v
}

numHues = 2
hues = array(numHues)
hues[0] = 0.995
hues[1] = 0.6

export function render(index) {
  hue = hues[index % numHues]
  hsv(hue, saturation, value)
}

And this is where I start failing already in this understand on how arrays work when wanting to control hues by variable with it not liking the ‘hues[0] = h’ line

export function hsvPickerColor(h, s, v) {
  hue = h
  saturation = s
  value = v
}

numHues = 2
hues = array(numHues)
hues[0] = h
hues[1] = 0.6

export function render(index) {
  hue = hues[index % numHues]
  hsv(hue, saturation, value)
}

Obviously that’s only going to control the hue anyway, so when trying to incorporate s and v too, now I’m even more lost.

export function hsvPickerColor(h, s, v) {
  hue = h
  saturation = s
  value = v
}

numColors = 1
Colors = array(numColors)
Colors[0] = h,s,v

export function render(index) {
  hsv(Colors)
}

End result is that I want multiple pickers (up to 5 or 6), and it comes down on how to incorporate that. Is it possible to incorporate h,s,v in to a single ‘Colors’ variable?

export function hsvPickerColor1(h1, s1, v1) {
  hue1 = h1
  saturation1 = s1
  value1 = v1
}

export function hsvPickerColor2(h2, s2, v2) {
  hue2 = h2
  saturation2 = s2
  value2 = v2
}

numColors = 2
Colors = array(numColors)
Colors[0] = h1, s1, v1
Colors[1] = h2, s2, v2

export function render(index) {
  hsv(Colors)
}

I suppose more ideally too I’d like to keep the twinkle effect as well, so taking out the value from the picker.

export function hsvPickerColor1(h1, s1) {
  hue1 = h1
  saturation1 = s1
}

export function hsvPickerColor2(h2, s2) {
  hue2 = h2
  saturation2 = s2
}

numColors = 2
Colors = array(numColors)
Colors[0] = h1, s1
Colors[1] = h2, s2

export function render(index) {
  twinkle = random(1) < 0.999 // clock speed dependent
  hsv(Colors, twinkle)
}

Are arrays even the best at this point anyway or should something else be used? At this point I’m kind of lost on how to achieve both ease of use with the picker, and to implement that to alternating static pix.

this is a good example of many ways to do the same thing. I suspect we can golf this a lot.

Welcome to programming! You’re missing a couple of important points about JavaScript (and many similar languages). Once they are clear you will be able to do everything you’re trying to do. In particular I’m looking at your example “… not liking the ‘hues[0] = h’ line”:

  1. Variables which are arguments to functions (e.g. the h,s,v in “hsvPickerColor(h, s, v)”) do not exist outside the function, which is why your “hues[0] = h” line gives you an error message.
  2. Code outside of a function is run only once, when the program starts … so even if that hues[0] = h worked, it would never be run again!

So, here’s a working example where 2 hues are static, and the others are determined by a picker and the 180° complementary color. Saturation comes from the picker, and value comes from a time wave in the beforeRender() function:

numHues = 4
hues = array(numHues)
hues[1] = 0.25
hues[3] = 0.75

var saturation = 1
var value = 1

export function hsvPickerColor(h, s, v) {
  hues[0] = h
  hues[2] = 1 - h
  saturation = s
}

export function beforeRender(delta) {
  value = wave(time(0.0125))
}

export function render(index) {
  hue = hues[index % numHues]
  hsv(hue, saturation, value)
}

If you can get your head around that, you’ll have harder questions soon!

2 Likes

Well, that took awhile… finally had a bit of time to play around with it mostly since I only have the lights on at night, and when I tried just an online vanilla javascript editor it didn’t really work and complained about everything. Is there a good tool to code with while the controller is off? An offline version of it, or an online editor that doesn’t seem to complain?

Thanks to your hint I seem to have a working pattern. Anything that seems glaringly out of sorts or inefficient?

I think the only question I ran in to is how to apply the original hsv set of hsv(hue, 1, isBulb*brightCenter*twinkle) namely the value part, and apply that to what I’m trying to achieve.

In the end does this code base follow PEMDAS? Or does it even matter for these functions?

Is there a difference between value*isBulb*brightCenter*twinkle and value*(isBulb*brightCenter*twinkle)? If so, trying to use () in the hsv set doesn’t work, nor does adding it to value = vals[index/period % numVals] so in the end I just left it as what I have below and it seems to work as expected, but I am skeptical of it.

Thanks.

numHues = 2
hues = array(numHues)

numSats = 2
sats = array (numSats)

numVals = 2
vals = array (numVals)

pixPerBulb = 1
pixBetweenBulbs = 0
period = pixPerBulb + pixBetweenBulbs
bulbs = floor(pixelCount/period)

export function hsvPickerColor1(h, s, v) {
  hues[0] = h
  sats[0] = s
  vals[0] = v
}

export function hsvPickerColor2(h, s, v) {
  hues[1] = h
  sats[1] = s
  vals[1] = v
}

export function render(index) {
  hue = hues[index/period % numHues]
  saturation = sats[index/period % numSats]
  value = vals[index/period % numVals]
  isBulb = (index % period) < pixPerBulb
  brightCenter = 0.1 + 0.9 * triangle((index % period + 0.5) / pixPerBulb)
  brightCenter = pow(brightCenter,3)
  twinkle = random(1) < .9995
  hsv(hue, saturation, value*isBulb*brightCenter*twinkle)
}

Yes, the code base follows PEDMAS, yes it really matters, and no, there is no difference between the two multiplications you give.

There are some things in your code that I should point out:

pixPerBulb = 1
pixBetweenBulbs = 0
period = pixPerBulb + pixBetweenBulbs
bulbs = floor(pixelCount/period)

This section gets run once when the pattern loads, and will result in

period = 1
bulbs = pixelCount

Is that intentional? Plus bulbs is never used.
So this means that

  hue = hues[index/period % numHues]
  saturation = sats[index/period % numSats]
  value = vals[index/period % numVals]
  isBulb = (index % period) < pixPerBulb
  brightCenter = 0.1 + 0.9 * triangle((index % period + 0.5) / pixPerBulb)

Does very little, given that period = 1 and pixPerBulb is 1.
Hue, Saturation and Value will alternate between the values in the array [0] and [1] values (which is fine).
isBulb will always be 1 as period is 1, and index%1 will always return 0 which is less than 1, so you get a result of 1 (which in this case means true).
On the next line, index % period is always 0, plus 0.5 is 0.5, divided by 1 is 0.5. triangle(0.5) will produce a value of 1 (I think, it’s a fixed number anyway).
Now 0.9 * 1 is 0.9 plus 0.1 is 1.
So brightCenter is always 1 (even if the next line cubes it, 1 cubed is 1).
So what your code actually does is alternate between the two color pickers, and add the random twinkle (given that isBulb and brightCenter are both 1).

Tip, one good way to see what your code is doing is to export the variables (including arrays), you can then watch the variables changing in real time (ish). This lets you see if the values are actually changing as expected.

So:

export var hue = array(numHues)
export var pixPerBulb = 1

And so on.
You really need to practice in the editor on the PB itself, as the programming language isn’t really JavaScript, and you get realtime feedback. I have a second PB that I code on, and when I’m happy, I copy the code to the “production” PB.

You should probably start with a simple chase pattern. Once you understand that, Gradually build up to something more complex. Leave arrays and such for later. I think you are jumping in at the middle, having skipped over the basics (like what the % operator does).

Here is your program boiled down to its basics


numPickers = 2
hues = array(numPickers)
sats = array(numPickers)
vals = array(numPickers)

export function hsvPickerColor1(h, s, v) {
  hues[0] = h
  sats[0] = s
  vals[0] = v
}

export function hsvPickerColor2(h, s, v) {
  hues[1] = h
  sats[1] = s
  vals[1] = v
}

export function render(index) {
  sel = index % numPickers
  twinkle = random(1) < .9995
  hsv(hues[sel], sats[sel], vals[sel]*twinkle)
}

Best of luck!

2 Likes

Since @Nick_W has responded so awesomely, I’ll spare you the confusion and just say listen to Nick. Especially the bit about monitoring variables!

One question he didn’t answer was this: If you don’t want to have your LEDs on, but don’t mind having your PixelBlaze on, @zranger1 's PixelTeleporter software and a FTDI USB-to-serial adapter (USD$10 or so) lets you draw your LEDs on the screen based on a real live PixelBlaze feed.

1 Like

[Edit: Sorry, somewhat duplicative of @Nick_W’s excellent help, we must’ve been replying at the same time]

See this thread for the best alternative, but I’ve basically given up on it and prefer to have spare Pixelblazes and code directly on them, even with no LEDs attached (just using the preview bar on the web page).

Yes, but I’ve heard it called “operator precedence” in software engineering. Here’s a good guide for JavaScript which should work for Pixelblaze.

Nope! Shouldn’t be.

No! Looks pretty straightforward to me – great job! Since making that original “Christmas string lights” pattern a while ago, I’ve gotten much fancier with my twinklers. When you want to have a look at that, check out “Twinkling Classic Xmas Strands” on the pattern library (currently on page 2), as well as the following code that emulates candle twinkling.

candles

Candles / Flickerers

// Flickering candle idea from 
// https://cpldcpu.wordpress.com/2013/12/08/hacking-a-candleflicker-led/

var flickerMode = 1
export function sliderFlickerMode(_v) { flickerMode = _v > .5 }


var tBcount = 32
var tBs = array(tBcount) // List of Target Brightness values
for (i = 0; i < tBcount; i++ ) tBs[i] = 1
var tBVals // Count of actual target brightnesses added
add = (b) => { tBs[tBVals] = b; tBVals++ } // Convenience, creating a frequency distribution
add(.9); add(.9); add(.9); add(.9); 
add(.9); add(.9); add(.9); add(.8); 
add(.7); add(.6); add(.5); add(.4); 
add(.3); add(.2); add(.1); add(.9);


candles = 8
candleLen = ceil(pixelCount / candles) // in pixels

var nextTransitionAts = array(candles)
var transitionTimes = array(candles)
var targetVs = array(candles)
var currentVs = array(candles)

var accumDeltas = 0

// Set new target brightness and transition time for a given candle
function setTargets(c) {
  transitionTimes[c] = 1000 * (.2 + random(.1)) // Candles recover from disturbances at 5Hz
  nextTransitionAts[c] = accumDeltas + transitionTimes[c] // We all happily overflow together
  targetVs[c] = tBs[random(tBcount)]
}
for (c = 0; c < candles; c++) { currentVs[c] = 1; setTargets(c) }

var tf, t1
export function beforeRender(delta) {
  accumDeltas += delta

  for (c = 0; c < candles; c++) {
    nextTransitionAts[c] < accumDeltas ? setTargets(c) : 0
    currentVs[c] += (targetVs[c] - currentVs[c]) * delta / transitionTimes[c]
  }
  
  t1 = triangle(time(.1))
  tf = time(.006)
}

var h, s, v
export function render(index) {
  candle = floor(index / candleLen)
  if (candle % 2 == 0) { hsv(0, 0, 0); return } // Candle every three
  
  h = .06; s = .95; v = currentVs[candle]
  
  if (flickerMode == 1) fMode1(index)
  
  candleShape = wave(candles * index / pixelCount-.25)
  hsv(h, sqrt(s), v * v * candleShape * candleShape)
}

// Experimental personal preferences for colors and frequency
function fMode1(index) {
  if (v < .9) { // Flickering
    inv = 1 - v
    v *= v + inv * wave(10 * v * index / pixelCount + tf)
    s = .95 + .12 * inv
    h = .03 - .02 * inv
  } else { // Not flickering
    h = .05 + .02 * t1
    s = .9
  }
}

1 Like

Here’s another way of writing the same pattern:

export function hsvPickerColor1(h, s, v) {
  h1 = h
  s1 = s
  v1 = v
}

export function hsvPickerColor2(h, s, v) {
  h2 = h
  s2 = s
  v2 = v
}

export function render(index) {
  sel = index % 2
  twinkle = random(1) < .9995
  hsv(sel?h1:h2, sel?s1:s2, (sel?v1:v2)*twinkle)
}

No arrays required.
The key here is the Ternary operator.

h1, s1, v1 are not declared using var, so they are global in scope by default. If you declared them using var h1 = h then h1 would be local in scope, and so would not exist outside of the hsvPicker routine (which is not what we want).

sel is either 1 or 0 see % Remainder operator, and 0 is falsey, whereas 1 is truthy, so the returned value will alternate between h1 and h2, s1 and s2, v1 and v2.

Twinkle will be either 0 or 1 depending on if random(1) returns a value greater than 0.9995 (which is fairly unlikely, so it will be 1 most of the time). This causes a random pixel to be off for one refresh cycle (how long this is depends on your frame rate - fps). The < test returns 1 or 0 (depending on if the test is true or false).

The pattern will initially be blank, as h1, s1, v1 etc. will initialize to 0. Once you use the colourpickers though, the colours will be displayed. The setting of the colour picker is saved in flash RAM, so the next time the pattern is loaded, the colorPicker routines are updated to the saved values, and the pattern will resume with the previously used colours.

I actually think

hsv(sel?h1:h2, (sel?s1:s2)*twinkle, sel?v1:v2)

Would look better, as the twinkle would be white then (which seems better for ‘twinkle’).

Hope this helps you figure out how it all works.

2 Likes

Jeff, thanks for the tips, and thanks for creating the christmas light patterns, they were the sole reason I went with PB in the end. I did base my version from your twinkling version, but just slightly altered it, so thanks again! I will check out those candles too.

Nick, amazing info, thank you very much. I should have went in to a little more detail in explaining how I was currently using the code, but you picked it up exactly; I do have the perbulb and betweenbulb “deactivated” at the moment. I left it in there from Jeff’s original code just in case I wanted to utilize it.

You are exactly right in that I did skip over everything and sort of just jumped right in. I tried following the getting started page, reading the variable page, and looking at the chasing pattern, but I guess I still havent had a lot of time to set aside for it to really let it sink in. Are there any other good resources to look in to?

That ‘export var’ logic will help a lot too, so thanks for that tip.

Reading in to ternary does seem like a good to know bit of info, but I do plan on using more than just the two pickers (just using two as an example here); for now it will be 5 patterns, each being a 2, 3, 4, 5, and 6 pattern picker. I think once I get familiar with PB I can get it down to one pattern with 6 pickers and have some logic in there that if one or more of the colors is set to black to adjust the pattern to only show the ones with colors picked.

I was able to utilize your “array(numPickers)” version with a third picker and all of the currently “inactivated” code, and it does look better that way. Thanks again for all of the information!

1 Like

Let us see what you come up with.

You can “chain” tertiaries to deal with more than 2 variables, but it does get a bit unreadable.

There is no switch statement in Pb, but you can get similar effects with if..else if..else statements,

Happy coding!