Input needed: functions wishlist

Wow, this reply makes 60 comments on this thread and some of the above has been implemented.

Might be worth reviewing, and pulling out what’s done, and what could be done without adding it to the firmware first. (Mappixels being a good example, we’ve got code that can “fake it”, so maybe a working library example and then if it’s really useful, it can be done in firmware [and faster]? )

I’ll clean this up more, later… better posted now than not.
Code examples will be linked to originals, or other posts with better versions.
Will resort and organize more.

Summary of items above

key - 
x - now in 3.18 (if not before)
0 - not yet
code - can be done in code
? - not enough details to flesh out?
u - unknown
coming - mentioned as in development now
  • x - hypot(x,y) - sqrt(xx + yy)
  • x - hypot3(x, y, z) - sqrt(xx + yy + z*z)
  • x - trunc(n) - removes fractional components for positive or negative numbers. e.g.
  • x - frac(n) - just the fractional component, like n % 1. e.g. frac(5.5) == .5 frac(-5.5)
  • x - mod(a, b) a modulo b, AKA floored division 2.
    0 - friendly palettes
    ? - Probability stuff
    u - Sampling from arbitrary distributions via the inverse CDF process 1
    u - Bernoulli state machines normalized to delta (independent of frame rate) for better flickering than random()
    ? - real constants that don’t incur the speed penalty of accessing a variable.
    x - Arctan2
    coming - a way to access XY(Z) map coordinates outside of render2d(3d)
    coming? - a way to access non-normalized XYZ coordinates outside of render2d(3d)
    ? - The sound scaling/normalizing functions in some kind of reduced or simpler format

? - sound normalizing (perhaps on the sensor board itself).
? - has2DMap() - true/false
? - has3DMap() - true/false
? - pixelMapDimensions() returns 1, 2, or 3. Even if no map is installed, we’d assume the default pixel map of index/pixelCount.
coming - render should be passed x or I should add render1D(index, x). At some point I will add support for 1D pixel maps too, with a default pixel map where x will be index/pixelCount and would be very easy to upgrade existing patterns.
x - scaling in one or more dimensions
coming - Map walking functions.
This lets you walk through the pixel map outside of a render call and could be used to make a pattern 2D/3D aware yet still only export render or pre-process pixels.
mapPixels(fn) - walk through the pixels with pixel map coordinates. fn is invoked with 4 arguments: (index, x, y, z) though you can specify fewer. If no pixel map is installed, x will be the same as index/pixelCount, and y and z will be 0. For 2D pixel maps z would be 0.
map2DPixels(fn) - walk through the 2D pixel map, if installed (if not, nothing happens). fn is invoked with 3 arguments: (index, x, y).
map3DPixels(fn) - walk through the 3D pixel map, if installed (if not, nothing happens). fn is invoked with 4 arguments: (index, x, y, z).
x - Pixel Map Coordinate Transformation

? - clock functions need something for sub-seconds… if I could call a function like ClockSeconds for smaller values, like for milliseconds, that would be fine.
? - inter-device communication?
? - A real round function, preferably with the ability to limit precision.
? - A playback from memory. Something that will let me pre-calculate (long) patterns and play them back at rates limited only by the hardware.
? - Ability to add an external serial RAM/EPROM or SD for (long) pattern storage.
? - Quadrature Encoder read - the ability to use an encoder as an input device without having to bit-bang in the script. :wink:
x - various array functions
? - more info in websocket (like first pixel color)
? - universal/persistant speed slider
? - universal/persistant variables
? - include (for libraries, etc)
? - literal arrays

code - “Perceptual HSV”, phsv(h, s, v).

  // Perceptual HSV
  function phsv(h, s, v) {
    h = wave((h % 1 + (h < 0)) / 2 + .75)
    hsv(h, sqrt(s), v * v)
  }

code - Seedable deterministic PRNG.
(we have a few of these, I'll link later)

code - Parameterized noise generation

code - vectors (can be done with 2 element array)

? - A unit normal table (Z table). Math.js calls this norm(x).

code - A dimming function where every element in an array is reduced by some amount every time it is called

function fade(amount){
for(m=0;m<strlength;m++){
pixelsb[m]=pixelsb[m]-min(pixelsb[m],amount)
} }

? - allocate an image buffer that could be drawn into.
code - rendering functions that will draw 1D objects of a specific size, color, hue onto an array, like the following

function dots(increment){
increment=increment/100+maxFrequencyMagnitude*100
for(n=0;n<5;n++){
if(Radius[n]<1){
MaxSize[n]=clamp(random(5)+energyAverage*500,2,20)
Positions[n]=floor(random(strlength-MaxSize[n]*2-2)+MaxSize[n])
Radius[n]=1
Color[n]=loval+(maxFrequency)/100+random(0.2)
Expanding[n]=1

}
  if(Expanding[n]==1){
  Radius[n]+=increment
  if(Radius[n]>=MaxSize[n]){Expanding[n]=0;Radius[n]=MaxSize[n]}
}
else{Radius[n]-=increment}

for(i=0;i<Radius[n];i++){
  pixelsh[Positions[n]+i]=Color[n]+i/30
  pixelsb[Positions[n]+i]=(1-i/MaxSize[n])
  pixelsh[Positions[n]-i]=Color[n]+i/30
  pixelsb[Positions[n]-i]=(1-i/MaxSize[n])
}
fade(0.05)
}}

code - inoise1d(x), inoise2d(x,y), inoise3d(x,y,z)

code - beatsin/beatsin16

//       beatsin( BPM, low, high) returns an value that
//                    rises and falls in a sine wave, 'BPM' times per minute,
//                    between the values of 'low' and 'high'.
function beatsin(bpm, low, high) {
  return wave(time(0.91552734375/bpm)) * (high - low) + low
}

code - A “pixel morph” function that lets me specify a beginning value, ending value, and transition rate. Generates a smooth(ed) transition between the two values.

code - complex numbers

AS THIS SUMMARY IS UNSTABLE… PLEASE DON’T ADD MORE TO THIS THREAD, maybe it’s time for a new thread?

@Scruffynerf , awesome compilation!

I had started a new thread here at the end of April, but folks kept replying here. :man_shrugging:

Keep in mind some of those APIs are alternative design choices, and only one will be implemented. Thats why I went with a poll.

Time for a fresh start? I can archive both and make a new one.

FYI, markdown tables work:

Foo Bar Baz
data1 other stuff more things
:white_check_mark: emoji? :no_entry_sign:

I think having the giant thread summary is nice. Its good for discussion, though I’m not sure tracking current status long term would work well via forum. It’s good to get folks’ feedback and talk about choices/options, and the giant summary is sure to remind folks and bring up some more discussion.

1 Like

Yeah, you could lock both and I’ll post a full summary on the new one?

I have being using After Effects a lot lately and the wiggle function could be very useful here as well. It is a form of constrained random that takes 2 parameters. wiggle(,)

There maybe a way to do this now but an offset for the time/wave functions.

The others are having access to some of the variables in the web portal. I want to be able to read and write to the master brightness slider, and the time OFF/ time ON variables.

I love all the idea to have tweens built in this would be very useful.

I just found a good video on wiggle here:

the short answer is it’s got a frequency and amplitude. time() is the frequency in PB, and amplitude is how much you want to multiply the 0…1 by…
The real meat though is that it’s more of a Perlin/Simplex noise-style changing change, it’s a SMOOTH change, not a random change…

So we’ve got some noise functions in usercode, but it’s not fleshed out into a library (yet). On my todo list.

Adding a noise() into the function list would make sense though.

Yes the smoothing is the important part of this function. Thanks for the explanation.

As long as we’re still adding to this list…

In the UI category:

It would be neat, if a slider could be set to ‘wander’, so that instead of the fixed 0…1 only as set, it could be set to ‘wiggle’, and adjust itself…

so if you had something that went from slow to fast, the user could click a checkbox (or something), and it would allow the value to smoothly adjust itself over time. (Consider this an application of noise() )… so it would speed up and slow down on it’s own. Speed of change is of course an issue, I’m sure.

I’ll see if I can mimic that behavior (minus the UI) in some good way… Maybe a checkbox is really all that is needed? (ie, “randomize?” and then it would ignore the slider in favor of the above behavior.

1 Like

I wouldn’t crowd the UI with a permanent checkbox for something that is used infrequently; when I want to do something like you described I use something like this:

//  The width of the lines.
export var width = 0.1;
export function sliderWidth(v) { width = 0.1 + (0.9 * pow(v,2)); }

//  When on, varies the lineWidth automatically.
export var animateWidth = 1;
export function sliderAnimateWidth(v) { animateWidth = 1 - floor(1 - v); }

and in the beforeRender:

if (animateWidth > 0) width = sliderWidth(triangle(time(.25)));

1 Like

Oh I agree, I didn’t mean a checkbox per slider, I meant an option to add a checkbox… Or really just a UI to add a checkbox in general.

2 Likes

Boolean checkbox UI FTW!

I had some some research to add bezier functions (either in code or API), and with a bit more code could generate a wiggle function.

http://www.inf.ed.ac.uk/teaching/courses/cg/d3/bezierJoin.html

The implementation can be pretty simple as a linear interpolation series, or higher orders can be combined with the help of some math.

A couple of really simple functions that wouldn’t take up much time, space or CPU cycles:

  • hardware_id: returns a number identifying the CPU type (0=esp8266, 1=esp32pico, 2=esp32standard, …
  • software_id: returns the version number as a float, eg. 3.18
  • device_id: used to uniquely identify each PB, returns a nonsensical float made from byte-packing the lowest 24 bits of the MAC address

Those would allow pattern code to remain the same across multiple PBs even when individual instances need to be adjusted (i.e. because the language needs a shim due to differing software versions, or timing needs to be adjusted between v2/v3 hardware, or the pattern is split across multiple PBs, or whatever).

Transparent shims may not be so easy due to how the built-ins work and IDs get defined and resolved.

For timing, I would encourage using time() and delta from beforeRender rather than frame by frame based animations whenever possible. Features will be added that will change FPS dynamically such as cross fading between patterns (might slow down FPS), multi-segmenting, or even parallelization of rendering on additional cores.

As a work-around for frame by frame animations, patterns could detect their FPS by looking at delta values and adjust accordingly. When a fluid adjustment isn’t feasible, you could have 2 set points optimized for V2 and V3. One API I was thinking about that might help, or make some animations easier would be a skipRender() API that could be called in beforeRender() that would simply skip over the render calls and wouldn’t update the LEDs. This could be used to limit animation framerate, or in some cases optimize animation timing around some event or timer instead of re-rendering the same pixels which usually means buffering state right now.

I could see device ID being useful for having a single source with small variations, e.g. cloning a pattern out to ease distribution but allowing some customized code on each. Practically using this in a pattern by pasting in some random number like 12345.6789 feels awkward, maybe if it used the name like isNamed("Node 1") or something?

I wasn’t thinking of doing anything quite so hacky as counting frames; I was just reflecting on a couple of points made in other threads:

  • that 1D patterns which look good on a small strand often need to be slowed down when displayed in large scales like the outside of a house
  • that many of the computationally complex 2D patterns need tuning to look good for a specific combination of device (v2/v3) and display (8x8,8x32,16x16,32x32, …)
  • that there are language differences (such as the name of the analog input variable) between v2 and v3 hardware, regardless of software versions
  • that the language features (including bugfixes like atan2) do not advance in parallel across all hardware platforms simultaneously
  • that there are times when you just need to know what you’re dealing with, like when dealing with the timing of external inputs
  • that the increasing number of Pixelblazes, patterns, and permutations thereof in my life has already become a maintenance headache in only nine months. I built a little tool to fetch the patterns off my 'blazes so I can back them up and put them under version control, but even so it’s a tedious process to diff them and keep track of the little tweaks to code and variables for patterns that geniunely need to be different across devices.

I’ve seen recommendations to put anything that needs parameterization into a variable and then send the correct values to each device over websockets, but that requires yet another device and introduces yet another configuration element to keep track of…whereas if just a little bit of information about the system was made available to a pattern, it would be possible to handle all those sources of variation in a single code base.

Shims don’t need to be transparent; they just need to be optional. Making the use of new language features conditional (i.e. if (software_id() >= 3) { array.mutate((x) = > ... } else { for (i=0;i<10;i++) {array[i]=...} }) could save a lot of cut-and-paste time as the platforms evolve.

Regarding device_id, I had in mind that the device would be referenced using the MAC as a hex literal: if (device_id() == 0xB10862) start_index = 201; but I agree, a textual version like deviceNameIs("name") would be even better.

skipRender() is an interesting idea; I was tempted once to set a “dirty” flag within beforeRender() when there was something new that needed to be output, but in the end I didn’t bother because I didn’t have had anything constructive to do with the time saved while the beforeRender() was accumulating delta during the “not dirty” cycles. Maybe it could save power in some applications like battery-powered costumes?

1 Like

I appreciate the feedback and info on how you use these! Please don’t take any of this as dismissing your request/ideas. Sometimes I offer alternatives/workarounds or might have a preference in direction.

Long term goal: Global speed slider

This is mainly why I convert the map to world units[1][2][3] that scale to any display (or arbitrary non-grid map). Sometimes pixel counts need to be known and assume a regular grid (e.g. making 2D pixel buffers), and a canvas or raster API could help. For now, a few lines of code to configure the size at the top.

For hardware / pin setups, I would expect the pin used to be highly variable and pin numbers to be configurable in code near the top (like most Arduino sketches).

I hope to abstract much of that. Imagine a UI config that lets you tie a slider to an analog input or trigger to a button/touch pin, the pattern is abstracted away from any hardware.

From a language perspective, perhaps I can alias or implement analogRead() on V2 so the API matches and would only need a pin config change.

For other language differences, unfortunately there isn’t an easy way to do polyfills at runtime. Using a function that doesn’t exist will create a compile error, even if it’s wrapped in an if(). Defining an identifier will mask a built-in, and would cause errors if undefined (we called the polyfill arctan2 instead of atan2 partially for this reason). So its quite possible to implement functionality in code, but not conditionally even if you had a way to detect the version.

Quick historical aside: the atan2 function was fixed before V3 released, so that wasn’t so much a platform thing as a version thing.

Unfortunately V2 language & API lagged behind V3 for a while, and during that time some patterns were made that wouldn’t run on V2 without extra work. I would have preferred that it didn’t work out that way, but resources are limited. I kicked off a poll to get a feeling for how y’all felt, proceeded accordingly until I realized how much harm it was causing to the pattern community (and had time to address it).

I prefer that if folks are updated to the latest version that the code would “just work” without needing any extra smarts in the code. Simplicity and universality of patterns is a big goal.

3 Likes

Ok, I totally understand the problem of shims better.

@wizard , I see another use of includes here.
Shims are essentially a missing library, something that the PB in question (let’s imagine v2 was lacking something v3 had, even if that’s mostly untrue right now). If a way to detect v2 existed (if versionId < 3) then
include v2shimlibrary,
which wouldn’t conflict because the missing function names wouldn’t exist on v2 otherwise.

So I’ve been thinking about how includes should work.

They need be a pattern. They need to be an epe.

They need to lack a render/etc, so that you can include them, and the net effect is as if they were typed in the calling pattern as text.
Editing on the PB needs to be possible. Saving needs to be possible.

Naming needs to be universal, so I can write a library, post the epe, post a pattern that uses it, and someone can install both and it just works.

While using a ID value for an epe/pattern is mostly invisible, name is visible, so putting that into code as
include (“example Library”)
Is better than
include(013345665445553)

A function/variable that doesn’t exist causes an error when the code is evaluated during editing. Includes need be treated as present but invisible, as if they were just code hidden behind a collapsed arrow (like a for loop, or if)

If libraries are just patterns, do we need to worry about files/uploading/etc?

Does this gain us the “global speed slider”?
Sure it does … We can write a library that makes a single slider with speed variable that every pattern I install can include… Storage of the variable is the issue? If it stores only with the pattern, we still need globals… But can it store with the include instead?

What’s the blockers on include?

Update:. Wrote the above before bed, just woke up and realized the pattern get compiled into an binary so the library code gets included into that, right? So it’s then part of the binary but not part of the actual text, which could cause an issue if someone loads the epe without the library being available and edits… But would it work if it wasn’t edited? Sigh… non trivial.

No offense taken ¯\(ツ)/¯; it’s your platform and your vision.

WLED’s Global speed slider is the answer to the question “How can I adjust the execution speed of compiled code on a platform that doesn’t allow patterns to generate UI sliders”. It doesn’t make every pattern look good on a particular configuration of pixels; it needs to be adjusted for each and every pattern and then the combination of pattern ID, speed slider position and palette selection saved to “Favorites” for later recall.

The answer to the question “why doesn’t this pattern look good on my setup”, I suspect, has to do with the interplay of pixel density, diffusion and linear speed. Antialiasing effects like KITT look fabulous at any speed on a 144 pixels/meter strip, but when upsizing them to a 10 pixels/meter strip on the side of a house there comes a point where the dots are too far apart (compared to the dot size) at a given speed for the eye to perceive them as motion blur and at that point every pattern looks like a movie marquee. The same applies to 2D patterns; noise/plasma/wave/tixy patterns that look good on a 16x16 matrix need to be adjusted when the world is only 8 pixels wide.

World units are a fantastic abstraction that simplify pattern coding, and canvas and raster abstractions have their uses, but they don’t help with compensating for density. With a “pixelDensity” in Settings (apparent pixel width divided by the space between pixels) made accessible as a 0…1 variable, patterns could calculate things like anti-aliasing distance thresholds automatically rather than with a UI slider.

I see what you mean about software_id(); I had in mind something that would work like the C preprocessor #ifdef/else but of course that wouldn’t work if the interpreter needs to resolve both clauses of the #if before it can generate bytecode for the VM. I jump back and forth between languages, platforms, and compiled/interpreted environments so much that I have trouble remembering what I can do where!

2 Likes

Just noticed that GLSL has most of the same limitations as PB for arrays:

We can also use Arrays. Of course they have to be typed and there are twists:

  • they have a fixed size
  • you can’t push(), pop(), splice() etc. and there is no length property
  • you can’t initialize them immediately with values
  • you have to set the values individually

this won’t work:

int values[3] = [0,0,0];

but this will:

int values[3];
values[0] = 0;
values[1] = 0;
values[2] = 0;

Actually the more I read that appendix 4 (which is GLSL from a JS perspective), the more I think adding vectors/etc is the right way to extend PB’s language, as we’re seeing how useful the shaders are for making good stuff in PB.

it’s lines like this

note : in a shader, color values (R , G , B & A ) are normalised, they range from 0 to 1 and not from 0 to 0xFF,

that tell me PB is a natural fit for GLSL. It won’t be 100%, but then PB isn’t a 100% fit for JS either… but if more GLSL-flavored stuff (like vectors) are added, we might hit the point where porting bits is trivial (which as @zranger’s been doing so, it isn’t quite trivial yet)

1 Like

Don’t know if this thread is still live, but I really like arduino’s map() function: map() - Arduino Reference - although it’s near-trivial, it really good for producing self-documenting code.

It’s easy enough to implement:

function map(x, in_min, in_max, out_min, out_max) {
  return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
}

If it were me I’d also implement some sort of logmap() too, same idea but instead of scaling linearly it would scale logrithmically.

2 Likes