WLED pattern porting?

Yeah, we’re in a bit of bootstrap problem:

We don’t have pallettes, so the need for adding pallette support to the API/firmware isn’t needed, which would make it much easier.

Honestly, a global variable API (someway to stash multi pattern variables) is really the missing bit. Pallette storage is just one possible use.

I took a shot at porting Distortion Waves last night. First I did a more-or-less straight transliteration of the FastLED code (though I got rid of the gamma LUT and replaced the cosine LUT with a PB wave function) and that was OK, though there were lots of artefacts in the middle which I think were due to numeric overflows.

Then I started hacking away to make it more PB-like. I’ve now got it very simple and PB-friendly, but in the process of scaling from FastLED’s 0…255 intensities and Soulmate’s 1-20 coordinates to PB’s 0…1 world I lost track of what the scaling coefficients should be at each stage, so it’s now 95% correct but 100% wrong (i.e. the code looks good but the results don’t resemble the original).

I’m tired of looking at it, and feeling distinctly inadequate after seeing @zranger1’s Great Metaballs of Fire, so I’ll post it here as a starting point if someone with fresh eyes wants to carry it forward…

//  Cobbled together from the original at: https://editor.soulmatelights.com/gallery/1089-distorsion-waves

//  simple replacement for LUT
function cos_wave(proportion) { return 1-wave(proportion+0.25); }
function beatsin(bpm) { return wave(time(0.91552734375/bpm)); }

// adjustments
timeBase = 0.1;
speed = 5;
w = 2;

export function beforeRender(delta) {
  a1=time(timeBase); a2=time(2*timeBase); a3=time(3*timeBase);
  cx1 = beatsin(10-speed); cy1 = beatsin(12-speed); 
  cx2 = beatsin(13-speed); cy2 = beatsin(15-speed);
  cx3 = beatsin(17-speed); cy3 = beatsin(14-speed);
}

export function render2D(index, x, y) {
/*
  byte rdistort = cos_wave[   (cos_wave[((x << 3) + a1) & 255]    + cos_wave[   ((y << 3) - a2) & 255]    + a3     ) & 255   ] >> 1;
  byte gdistort = cos_wave[   (cos_wave[((x << 3) - a2) & 255]    + cos_wave[   ((y << 3) + a3) & 255]    + a1 + 32) & 255   ] >> 1;
  byte bdistort = cos_wave[   (cos_wave[((x << 3) + a3) & 255]    + cos_wave[   ((y << 3) - a1) & 255]    + a2 + 64) & 255   ] >> 1;
*/
  coeff1 = 0.06; 
  r1 = coeff1*cos_wave(x+a1); 
  g1 = coeff1*cos_wave(x-a2); 
  b1 = coeff1*cos_wave(x+a3);
  
  coeff2 = 1; 
  r2 = coeff2*cos_wave(y-a2); 
  g2 = coeff2*cos_wave(y+a1); 
  b2 = coeff2*cos_wave(y-a2);
  
  coeff3 = 1; 
  rdistort = coeff3*cos_wave(r1+r2+a3); 
  gdistort = coeff3*cos_wave(g1+g2+a1+1/8);  
  bdistort = coeff3*cos_wave(b1+b2+a2+1/4); 

/*
  byte valueR = rdistort + w * (a1 - (((xoffs - cx1) * (xoffs - cx1) + (yoffs - cy1) * (yoffs - cy1)) >> 7));
  byte valueG = gdistort + w * (a2 - (((xoffs - cx2) * (xoffs - cx2) + (yoffs - cy2) * (yoffs - cy2)) >> 7));
  byte valueB = bdistort + w * (a3 - (((xoffs - cx3) * (xoffs - cx3) + (yoffs - cy3) * (yoffs - cy3)) >> 7));
*/
  dx1 = x-cx1; dy1 = y-cy1; dx2 = x-cx2; dy2 = y-cy2; dx3 = x-cx3; dy3 = y-cy3;
  r = cos_wave(rdistort + w*(a1-(dx1*dx1 + dy1*dy1)));
  g = cos_wave(gdistort + w*(a2-(dx2*dx2 + dy2*dy2)));
  b = cos_wave(bdistort + w*(a3-(dx3*dx3 + dy3*dy3)));

  rgb(r*r, g*g, b*b); 
}
2 Likes

@Pixie – you had this! The only thing missing is that WLED’s coordinates come in as integer pixel numbers and Pixelblazes are normalized and scaled. To get the numbers back to something like the proper scaling for this pattern, try adding the line:

scale(0.5,0.25)

somewhere in the initialization section. (YMMV on the specific values – this looked good to me, and seems to correct for the aspect ratio of the rectangular display he’s running it on in the video.)

1 Like

I don’t know how I (re)missed the Palette Utility in the pattern collection.

Discussion here (which I even participated in)

I think next step is to try porting a WLED pattern that assumes palette usage and see how awkward it is… I still think maps aren’t an answer, but I’m not sure we have a good answer yet. If we had includes (for example), we could have a palette include with desired palettes.

Nice work!

I will play with this after dinner, and I’m already envisioning replacing some of the hard coded values with sliders to make it more adjustable. But glad @zranger1 noticed the easy fix.

You aren’t alone. I was porting some generic JS stuff and couldn’t figure out why my matrix displayed half of my pattern fine, and the other half all screwy… I was doing the canvas as an array, and referencing it in render, and dammit, what was I doing wrong? Oh right, my matrix is zigzag and so using a bare index? Half of it will be reversed. Or do it the right way and use x, y, which for some silly reason I wasn’t doing)

Sometimes you just need to put it aside and come back fresh, or get new eyeballs.

I have at least one awesome pattern on hold, waiting for me to come back to it with fresh energy and figure out what I wasn’t doing right.

I think it’s been raised elsewhere in the feature enhancements thread, but we really need a render1D(index, X) that respects the pixelmap; otherwise the wiring of many 2D and 3D objects really messes up 1D patterns.

I’ve started pasting this into all the 1D patterns so they’ll look the same across all my 2D objects:

export function render1D(index, x) {
  render(floor(x*pixelCount)); // call the original renderer with a corrected index
}

var matrixWidth = sqrt(pixelCount), matrixHeight = pixelCount / matrixWidth;
export function render2D(index, x, y) {
  row = floor(x*matrixHeight);
  column = floor(y*matrixWidth);
  offset = row * matrixHeight + column;
  render1D(index, offset/pixelCount);
}
1 Like

I found a few differences between the original code formulas and yours, minor but significant in effect, like using a2 instead of a3 (I think that was it)

Here’s a slider heavy version, intended to let someone play with all of the variables until they find an effect they like…

Some changes, including…

  • Using wave(time()) rather than time() to avoid what I found were distracting ‘flash changes’ when something went from 1 to 0… also without millis(), we really don’t have a long ‘t’ to replace it. @wizard, consider this a plea for some way to emulate that, but we also don’t really have anything longer than a 16.16 number, which doesn’t really help us here. millis() is used in the original (and many other patterns) as the driving force, since it’ll grow to 4,294,967,295 (32 unsigned) before it rolls over.

  • Log scale sliders, so picking values below and above 1 was easier.

  • Decoupled the time bases… that’s another change from the original code… in Original, time then time/2 then time/3, but in your code above, you grew the time base by x2 and x3

Distortion Waves TNG, with sliders upon sliders
// original code by ldirko_Yaroslaw Turbin 17-06-2021
// https://twitter.com/ldir_ko        https://vk.com/ldirko
// https://www.youtube.com/c/ldirldir https://www.reddit.com/user/ldirko/  

// original code: https://editor.soulmatelights.com/gallery/1089-distorsion-waves
// and https://wokwi.com/arduino/projects/301639284294681097

// modified/recoded by @pixie at https://forum.electromage.com/t/wled-pattern-porting/1295/22
// then re-modified quite a bit by Scruffynerf

//  simple replacement for original LUTs
function cos_wave(proportion) { return 1-wave(proportion+0.25); }
function beatsin(bpm) { return wave(time(0.91552734375/bpm)); }

// adjustments - all sliders now
export var timeBase1 = 0.2;
export var timeBase2 = 0.1;
export var timeBase3 = 0.66;
export var speed = 4;
export var w = .05;
export var scalefactor = .5;
export var coeff1,coeff2,coeff3

export function sliderSpeed(v){
  speed = v*9.9
}

var minv = log(.1);
var maxv = log(3);

export function sliderScale(v){
  scalefactor = exp(minv + (maxv-minv)*v);
}

var minv2 = log(.01);
export function sliderWiggle(v){
  w = exp(minv2 + (maxv-minv2)*v);
}

var maxv2 = log(30);

export function sliderTime1(v){
  timeBase1 = exp(minv2 + (maxv2-minv2)*v) +.0001
}
export function sliderTime2(v){
  timeBase2 = exp(minv2 + (maxv2-minv2)*v) +.0001
}
export function sliderTime3(v){
  timeBase3 = exp(minv2 + (maxv2-minv2)*v) +.0001
}

coeff1 = 1; 
coeff2 = 1; 
coeff3 = 1; 

export function sliderC1(v){
  coeff1 = exp(minv + (maxv-minv)*v);
}
export function sliderC2(v){
  coeff2 = exp(minv + (maxv-minv)*v);
}
export function sliderC3(v){
  coeff3 = exp(minv + (maxv-minv)*v);
}

export function beforeRender(delta) {
  resetTransform()
  //translate(-.5, -.5)
  scale(scalefactor,scalefactor)
  
  a1=wave(time(timeBase1*2))*2-1;
  a2=wave(time(timeBase2*2))*2-1; 
  a3=wave(time(timeBase3*2))*2-1;

  cx1 = beatsin(10-speed)
  cx2 = beatsin(13-speed)
  cx3 = beatsin(17-speed)
  cy1 = beatsin(12-speed)
  cy2 = beatsin(15-speed)
  cy3 = beatsin(14-speed)
}

// debugging uncomment - 
// export var _r,_g,_b,cx1,cx2,cx3,cy1,cy2,cy3,dx1,dx2,dx3,dy1,dy2,dy3

export function render2D(index, x, y) {
  r1 = coeff1*cos_wave(x+a1); 
  g1 = coeff1*cos_wave(x-a2); 
  b1 = coeff1*cos_wave(x+a3);
  
  r2 = coeff2*cos_wave(y-a2); 
  g2 = coeff2*cos_wave(y+a3); 
  b2 = coeff2*cos_wave(y-a1);
  
  rdistort = coeff3*cos_wave(r1+r2+a3); 
  gdistort = coeff3*cos_wave(g1+g2+a1+1/8);  
  bdistort = coeff3*cos_wave(b1+b2+a2+1/4); 

  dx1 = (x-cx1)*(x-cx1);
  dx2 = (x-cx2)*(x-cx2);
  dx3 = (x-cx3)*(x-cx3);
  dy1 = (y-cy1)*(y-cy1);
  dy2 = (y-cy2)*(y-cy2);
  dy3 = (y-cy3)*(y-cy3);

  _r = rdistort + w*(a1-(dx1 + dy1))
  _g = gdistort + w*(a2-(dx2 + dy2))
  _b = bdistort + w*(a3-(dx3 + dy3))
  rgb(_r*_r, _g*_g, _b*_b);
}
1 Like

For a substitute tick count, here’s what I use when porting shader things:

var timebase = 0;

export function beforeRender(delta) {
  timebase = (timebase + delta/1000) % 3600
}

This gives a fractional second counter that rolls over once an hour. Long enough to avoid most visual artifacts, short enough to give you a little room to scale it and not run into integer scaling issues.

2 Likes

I just came across Aurora, and the video of the many patterns it did/does:

Kudos to @JasonCoon and @embedded-creations on all that work.

I’ve been following links and realizing that bitrot is starting to eat older led libraries/demos/etc (led effects by ratkins is long gone, for example, unless I can find a backup repo) so porting is also a form of preservation and carrying algos forward to the future.

Also a port of it to neomatrix:

I think a lot of these still don’t exist for PB yet, so they go on the (growing) porting pile.

3 Likes

Here’s another port in case it’s useful:

Also some more links with the latest details on actually compiling Aurora for SmartMatrix Library (it’s not straightforward as the latest code is on a branch):

3 Likes

If anyone asks, “Why Pixelblaze?” this is one really good answer…

I was wandering through my pattern library, and stopped to look at the code for @pixie and @scruffynerf’s Distortion Waves port. Just casually wondered, “What would this look like with radial coordinates?”

One line of code:
tmp = atan2(y,x); y = hypot(x,y); x = tmp;
added at the front of render - and a few seconds of parameter tweaking later, I had my answer. It’s… really cool! (I’ve attached source below. Absolutely worth running to have a look! )

Yes, you could do this sort of playing and exploration on a compiled platform, but there are a lot more obstacles in the way. Pixelblaze is really excellent at providing the shortest path between crazy idea and actual blinking LEDs!

WLED Distortion Waves - Radial Version
// original code by ldirko_Yaroslaw Turbin 17-06-2021
// https://twitter.com/ldir_ko        https://vk.com/ldirko
// https://www.youtube.com/c/ldirldir https://www.reddit.com/user/ldirko/  

// original code: https://editor.soulmatelights.com/gallery/1089-distorsion-waves
// and https://wokwi.com/arduino/projects/301639284294681097

// modified/recoded by @pixie at https://forum.electromage.com/t/wled-pattern-porting/1295/22
// then re-modified quite a bit by Scruffynerf

//  simple replacement for original LUTs
function cos_wave(proportion) { return 1-wave(proportion+0.25); }
function beatsin(bpm) { return wave(time(0.91552734375/bpm)); }

// adjustments - all sliders now
export var timeBase1 = 0.06228;
export var timeBase2 = 0.04;
export var timeBase3 = 0.057;
export var speed = 9.2;
export var w = .015;
export var scalefactor = 1;
export var coeff1,coeff2,coeff3

var minv = log(.1);
var maxv = log(3);
var maxv2 = log(30);
coeff1 = .21; 
coeff2 = .6626; 
coeff3 = .8245;
var minv2 = log(.01);

/*  uncomment to re-enable UI
export function sliderSpeed(v){
  speed = v*9.9
}

var minv2 = log(.01);
export function sliderWiggle(v){
  w = exp(minv2 + (maxv-minv2)*v);
}

export function sliderTime1(v){
  timeBase1 = exp(minv2 + (maxv2-minv2)*v) +.0001
}
export function sliderTime2(v){
  timeBase2 = exp(minv2 + (maxv2-minv2)*v) +.0001
}
export function sliderTime3(v){
  timeBase3 = exp(minv2 + (maxv2-minv2)*v) +.0001
}

export function sliderC1(v){
  coeff1 = exp(minv + (maxv-minv)*v);
}
export function sliderC2(v){
  coeff2 = exp(minv + (maxv-minv)*v);
}
export function sliderC3(v){
  coeff3 = exp(minv + (maxv-minv)*v);
}
*/

//  resetTransform()
  translate(-.5, -.5)
  scale(scalefactor,scalefactor)

export function beforeRender(delta) {

  a1=wave(time(timeBase1*2))*2-1;
  a2=wave(time(timeBase2*2))*2-1; 
  a3=wave(time(timeBase3*2))*2-1;

  cx1 = beatsin(10-speed)
  cx2 = beatsin(13-speed)
  cx3 = beatsin(17-speed)
  cy1 = beatsin(12-speed)
  cy2 = beatsin(15-speed)
  cy3 = beatsin(14-speed)
}

// debugging uncomment - 
// export var _r,_g,_b,cx1,cx2,cx3,cy1,cy2,cy3,dx1,dx2,dx3,dy1,dy2,dy3

export function render2D(index, x, y) {
  tmp = atan2(y,x); y = hypot(x,y); x = tmp;
  r1 = coeff1*cos_wave(x+a1); 
  g1 = coeff1*cos_wave(x-a2); 
  b1 = coeff1*cos_wave(x+a3);
  
  r2 = coeff2*cos_wave(y-a2); 
  g2 = coeff2*cos_wave(y+a3); 
  b2 = coeff2*cos_wave(y-a1);
  
  rdistort = coeff3*cos_wave(r1+r2+a3); 
  gdistort = coeff3*cos_wave(g1+g2+a1+1/8);  
  bdistort = coeff3*cos_wave(b1+b2+a2+1/4); 

  dx1 = (x-cx1)*(x-cx1);
  dx2 = (x-cx2)*(x-cx2);
  dx3 = (x-cx3)*(x-cx3);
  dy1 = (y-cy1)*(y-cy1);
  dy2 = (y-cy2)*(y-cy2);
  dy3 = (y-cy3)*(y-cy3);

  _r = rdistort + w*(a1-(dx1 + dy1))
  _g = gdistort + w*(a2-(dx2 + dy2))
  _b = bdistort + w*(a3-(dx3 + dy3))
  rgb(_r*_r, _g*_g, _b*_b);
}
1 Like

actually @zranger1, it’s funny you mention this, cause… aircoookie of WLED is doing a survey, specifically asking about what you’d like to see in a “Live effect editor”

As you may or may not know, I am currently working on my Bachelor’s thesis, and you can help!
The topic is related to WLED and I am conducting some research to assess how much you would like a specific new feature.

It would delight me if you can spare 5 minutes to answer the survey:
https://forms.gle/bHxcg4N5BkUf2rhY6
This survey will run for 14 days.

Thank you very much and have a nice week!

1 Like

Ha! I filled it out – it’ll be interesting to see what he comes up with. Wonder how many votes, “Give us the option of switching between polar and cartesian coordinates”, will get.

1 Like

Now that we’ll have array literals, a few things become easier. Palettes for one thing.
@jeff 's palette utility used some nifty functions to ‘set3’ or ‘set8’ to populate arrays.
Now that it’ll be much easier to just put the array in directly, the code can be cleaner.
I need to revisit it, and see how easy it would be use it for ‘quick’ porting purposes from fastled/wled.

1 Like

Ok, I’ve got all of the main wled palettes ported now. Code shortly. I want to do a lot of cleanup on it. I’ve taken @pixie 's code, and added 8bit packing, so each palette is now essentially a one dimensional array of single 16.16 numbers, stored as small as is possible, meaning you can pack quite a bit in there.
The library will include all of the wled palettes but limiting your own code to a handful you that like will be pretty easy.

It’s not perfect yet… still some bugs to find.
for example:

wledpalette[0] = [
  p( 0, 194, 1, 1),
  p( 94, 1, 29, 18),
  p(132, 57, 131, 28),
  p(255, 113, 1, 1)
];

when I watch that, I get:
wledpalette[0] = [1.006866, 24093.07, -31612.89, -254.9944]
but that’s not quite the same pattern, missing some blue. I suspect it’s 16.15 issue, (but I’m storing red not blue in the last bits… ) but… we’ll see.

1 Like

TIL: WLED has a fixed FPS (frames per second) of 42.

So given how simple some of the patterns are, some sort of FPS limiting will likely be needed, as I’m pretty sure they’d run faster than 42 FPS on a PB.

I did just outline a way to do “delays” in PB, but I suspect matching a FPS like this is a bit more complex. It would involve looking at the Delta and ensure it stayed in the right range (23.8095238 milliseconds per render - so between 23 and 24 millis of Delta) and then do something to either speed up (skip a frame?) or slow down (stay static for a frame?). Ideas?

@Scruffynerf ,
I’ve been considering adding a skipRender() API that could be called in beforeRender. Then you could just skip frames and return early from beforeRender until it was time to render a frame (23.8ms has passed).

However, wouldn’t it be much nicer if the patterns were modified to scale with frame rate?

If your target was 23.8ms per frame, you can get a scaling factor to apply for any changes like this:

var scale42Factor = 1
export function beforeRender(delta) {
  scale42Factor = delta / 23.8095238
  
}

Then you can multiply scale42Factor to any value that changes per frame. It would be pattern by pattern though. Most of them are ugly / boring though. Might be better to just chip away at them one at a time looking at the most interesting ones, and factor out any framework / helpers that you discover along the way.

2 Likes

FastLED/WLED have the EVERY_N_MILLISECONDS() idiom to perform actions at a consistent rate no matter what the actual framerate is, which is achievable (but less elegant) by accumulating delta inside the beforeRender() function like so:

//  FASTLED emulation functions
var maxTimers = 10;
 var elapsedTime = array(maxTimers);
function EVERY_N_MILLISECONDS(delta, accumulator, target, func) {
  elapsedTime[accumulator] += delta;
  if (elapsedTime[accumulator] > target) { 
    elapsedTime[accumulator] -= delta; 
    func(); 
  }
}

//  Simple sample pattern:
export var hue = 0; 
export var brightness = 0;
function changeHue() { hue = (hue + 1/6) % 1; }
function changeBrightness() { brightness = (brightness + 1/10) % 1; }

export function beforeRender(delta) {
  EVERY_N_MILLISECONDS(delta, 0, 500, changeHue);
  EVERY_N_MILLISECONDS(delta, 1, 50, changeBrightness);
}

export function render(index) { 
  hsv(hue, 1, brightness);
}

But @wizard, having a skipRender() to avoid unnecessary calculations and outputs could be very helpful for reducing power in mobile/portable installations…

Actually, from the same Reddit post I learned about the 42 fps (by @Johnny5canuck, who I’m tagging here):

That being said, there’s additional learning required in order to work within the WLED and NeoPixelBus framework. For instance, you cannot use EVERY_N_MILLIS or the ‘static’ keyword and be fully WLED compatible. Those do not work with segments. It also runs at a fixed 42 fps.

So while yes, FastLED has that, WLED patterns aren’t supposed to, but perhaps some do? (And thus don’t work with segments)

I do agree with @wizard, that handling the FPS limit on a case by case basis makes sense. I mostly raised it as an interesting data point. I suspect the “port all the things” end result will be a handful of PB patterns, rather than a monolithic “WLED effects” pattern, and breaking them up into different types (like all of the simple 1D Marquee/alternator ones, especially the fixed colors like Halloween or Christmas, as one pattern with selectable colors/combos.) Hopefully, we’ll have a new playlist to support running the same pattern with different variable presets for some day… Otherwise that “bundling” would be (mildly) annoying if you wanted to run two or more of the effects, as you’d have to clone the pattern each time.

Not mention implementing segments, other than our current “multisegment” pattern method.

Thoughts:

The JavaScript idiomatic way of doing EVERY_N_MILLISECONDS() is to use an interval (which doesn’t exist in PB yet):

setInterval(function() {
  // do stuff here
}, 500) //every 500 ms

One problem here is that using a unit of milliseconds limits the range to about 32 seconds due to the fixed point value range in PB. It would be useful even with that limit, and a variant that worked in seconds would come in handy. I don’t think that would be a problem for these cases.

It also calls the timeout function asynchronously (in the main event loop after other code has returned), whereas the EVERY_N_MILLISECONDS() macro executes it in-line. I suspect for most things the timing wouldn’t matter.

Here’s an implementation of setTimeout (in seconds), which is the one-shot version. It’s not too dissimilar to your implementation, @pixie.