Input needed: functions wishlist

What functions are you missing? What would be convenient, or possibly faster, with a built in function?

We’ve recently discussed a few that I’ll start with:

  • hypot(x,y) - sqrt(x*x + y*y)
  • hypot3(x, y, z) - sqrt(x*x + y*y + z*z)
  • trunc(n) - removes fractional components for positive or negative numbers. e.g. trunc(5.5) == 5, trunc(-5.5) == -5. (Note: I’d use whole() but JS and C use trunc())
  • frac(n) - just the fractional component, like n % 1. e.g. frac(5.5) == .5 frac(-5.5) == -.5 So that trunc(n) + frac(n) == n for positive and negative numbers.

I have a huge list in addition to this but I’m curious what you have in mind!

Ben and I spoke about a couple of these over email, but for consideration by others:

  • mod(a, b) a modulo b, AKA floored division. Not present in ES6; but a correct mathematical definition is used in the ES2020 (ES11) definition 37 times! There’s just no operator for it. This form, where sign matches divisor, is the default behavior of % in Python. Its main usefulness in Pixelblaze is that it wouldn’t mirror abs(x % 1) about x = 0, and thus will produce periodic, consistent sawtooth output for inputs where the dividend can be positive or negative. Note in my recent video the hack where we add 1: (1 + index/pixelCount - t1) % 1, or how I’ve seen a % 1 + (a < 0) in my and @zranger1’s code.

  • We discussed over email “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)
      }
    
  • Seedable deterministic PRNG. The XORShift you made performs well for this, but maybe it’d be nice as a built-in. Until PB supports breakpoints and steps, it can be handy to work on debugging with repeatable randomness.

  • Whatever the Hencke brain invents for friendly palettes

  • Probability stuff I have at times built myself to give patterns more organic life; I completely understand if it’s unpopular, but these things slay my frame rate.

    • Parameterized noise generation

    • A unit normal table (Z table). Math.js calls this norm(x). Otherwise I abuse wave(). Help me fight the abuse of wave() in our community.

    • Sampling from arbitrary distributions via the inverse CDF process

    • Bernoulli state machines normalized to delta (independent of frame rate) for better flickering than random()

    • Tools for Poisson processes, which is probably a generalization of my Bernoulli state machine. Nevermind. As per usual, one starts brainstorming and eventually dives off the “nobody needs this built in, just implement it yourself, Jeff” end :wink:

2 Likes

I like surprises! Looking forward to working with whatever you come up with, really.

Jeff covered my biggest remaining wishlist item – seedable PRNG. mod(a,b) would be nice too. And if you give me noise generation, vector and probability functions, I will definitely (ab)use them.

In addition to being a confirmed and unrepentant wave() abuser, I also abuse arrays to build more complex data structures, and I use variables as fake constants to help me keep track of the indices. The other thing I’d like, though not strictly a function, is the ability to have real constants that don’t incur the speed penalty of accessing a variable.

3 Likes

Arctan2
a way to access XY(Z) map coordinates outside of render2d(3d)
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
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)
} }

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)

}}

1 Like

Oooh Nice - Just wanted to lend my +1 to accessing map data in beforeRender, and sound normalizing (perhaps on the sensor board itself).

3 Likes
wave(scruffynerf);

Sorry, couldn’t resist. I’ll mull this over and reply for real in the next few days. Brainstorming.

Pixel map dimensions

Remapping 2D and 3D pixels is very nice, though we have a lot of 1D patterns out there without a good system for remapping. The index/pixelCount idiom is also somewhat unintuitive to new pixel enchanters. So I’m thinking either 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.

It would be good to have information about an installed pixel map, so I would like to add something to help out:

  • has2DMap() - true/false
  • has3DMap() - true/false

or perhaps

  • pixelMapDimensions() returns 1, 2, or 3. Even if no map is installed, we’d assume the default pixel map of index/pixelCount.

Oh look, a poll! Vote for an API to get info about an installed pixel map

Choose an API to get info about an installed pixel map
  • has2DMap() / has3DMap()
  • pixelMapDimensions()
  • :woman_shrugging:t2:why not both?

0 voters

3 Likes

Ooh I’ve got one I just hit: there is a built in assumption in most code that the 2D map is equally dimensioned (x and y dimensions are equivalently sized aka a square matrix)

I have an 8x32 apa102 panel I hadn’t played with yet and just tested the 18x32 miniws2812 panel I got (yes, that’s a weird size)… And of course, things are stretched out to fit even with the map. Adjusting the ratio is going to take some work and I’ll try to come up with a clean way. (Since my goal is to write 2D patterns, having a “squashed” display will mean it’ll look weird unless it’s corrected for.)

Sone of the suggestions above include a way to access the non normalized… That could help. Still brainstorming how to fix this best.

In Ben’s vein of additions, I can see that having either the dimensional sizes (I have 18 in X, and 32 in Y, and PB learns that either as part of map or value in settings), Or a ratio.

BTW, this problem won’t be an issue for polar mapping… That’s one awesome advantage of it. “Wide” or irregular sized matrix “just work”

@Scruffynerf,
I do not plan to access the unnormalized map, but do plan for APIs to access the pixel map with the same kind of data that is available to render.

For aspect ratio, I have some ideas :slight_smile:

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.

And/or perhaps:

  • 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).

Oh look a poll!

Vote for an API to walk a pixel map outside of render
  • mapPixels(fn) one size fits all
  • map2DPixels(fn) / map3DPixels(fn) an API for every dimension
  • :woman_shrugging:t2:why not both?

0 voters

Pixel Map Coordinate Transformation

The idea here is that the pixel map can be transformed before coordinates are given to render2D/3D and mapPixels and perhaps render/render1D x parameter as well. This will help reduce common coordinate transformations that have to be done in pattern code. A set of transformations can be applied globally on the mapper tab. Perhaps per pattern as well as a setting like controls. This allows you to center the coordinate space around 0, apply a non-square aspect ratio, or scale + translate the map to a segment of the overall world coordinate space for multiple PB rendering cooperation without code.

In addition to global transforms, the pattern has access to an API to apply pattern specific transformations as well. It would make things like GlowFlow much simpler and faster, and allow for cool effects with less code. Rotate, zoom, pan around with ease and speed in 2D or 3D. Scale and offset even 1D would be cool.

  • resetTransform() - resets coordinate transformations back to default or global transformation state. I might also need an applyTransforms() due to the non-commutative nature of matrix composition, but I’ll cross that bridge when I get to it :slight_smile:

For API, it’s tempting to have functions for 2D and for 3D, though for a hybrid compatible pattern working with the individual axes might reduce overall code footprint.

  • translateX(dx) - move on the X axis. Works on 1D and up
  • translateY(dy) - move on the Y axis. Works on 2D and up
  • translateZ(dz) - move on the Z axis. 3D only
  • scaleX(x) - scale the X axis. Works on 1D and up
  • scaleY(y) - scale the Y axis. Works on 2D and up
  • scaleZ(z) - scale the Z axis. 3D only

This kind of gets unintuitive with rotation. There isn’t much point to rotate 1D right? Rotation in 2D transforms both the X and Y (on the Z axis). Would it be weird to see rotateZ() in 2D patterns so perhaps rotate() would be more natural for 2D.

  • rotate(a) aka rotateZ(a) - rotate along the Z axis. Works on 2D and up
  • rotateX(a) - rotate along the X axis. Works in 3D only
  • rotateY(a) - rotate along the Y axis. Works in 3D only
2 Likes

+1 to several of the above regarding access to the map from beforeRender – it would certainly be useful to know the map’s “real” dimensions in order to determine aspect ratio.

Also, I can think of times I’d like to be able to do calculations on pixels in an array outside of render and know where that was going to fall in coordinate space on the display, regardless of wiring.

Re @scruffynerf’s fade(): – it would actually be nice to have an iterator that let us hand any expression to the vm to perform over an array. (And some way to get array size would be good too).

1 Like

I’d rather tackle the aspect ratio with methods mentioned above through scaling. so e.g. a 32x16 array is 2:1, so you could ether get X coordinates with a magnitude of 2 (0-2 or -1 to +1) or Y coordinates with a magnitude of 0.5 (0-0.5 or -.25 to +.25 or something like that).

There are times where you do want pixel level coordinates, the tixy stuff comes to mind, or any 1:1 pixel art, but I think these can still be done with appropriate map scaling (and perhaps snapping to integer values).

Another way to do it would be to allocate an image buffer that could be drawn into. This could be a simple array where you know the pixel arrangement, such as rows and columns, or a cavas-like object with an API, then be able to subsample or supersample from that image buffer inside render, which would still let you have arbitrary coordinates for your physical pixels. Your 32x32 Nyan Cat could be then rendered to any pixel setup (which would already have aspect ratio corrections).

1 Like

Can you explain the mapPixels() better? I am not sure I grasp how you mean it to be used?

I agree, scaling is the right way, and ideally you would scale based on the largest dimension equaling 1, to preserve the 0…1 scale universally. (You could scale to the smaller and have values above 1, but that requires all of your patterns to be aware of that… I’d rather count on 0…1 being boundaries. Far easier.)

1 Like

There are definitely advantages to having a pixel buffer where you could read/write the state. Otherwise, for those applications, you just ending up building an array to do it anyway.

If you add it, and it’ll be faster than
setup/create array,
populate array with initial values if needed,
And then in prerender you manipulate array,
then on render, you push the array to pixels,

then it’s worthwhile to have a built in buffer instead.

Maybe a (faster?) render() replacement that just flushes the buffer to pixels?

On direct frame buffer access: sometimes, I want this. Then I start thinking about color order, the whole RGBW thing, the extra APA brightness bits… Once your pattern has to worry about hardware details, portability becomes a lot more work. (I’d take access to the final pre-hardware 0-1 scaled hsv buffer if you gave it to me though.)

Transforms: I’ve come to like Processing’s pushMatrix()/popMatrix() model – call pushMatrix() to save the current set of transforms, then do whatever additional things you’re doing in local coordinate space with translate() and rotate(). When you’re done, call popMatrix() to restore the previous context.

Simplicity: Accessibility to new users is, I think, my favorite Pixelblaze feature. I give these things to more-or-less non-technical friends. And they love them!

Whatever you do in the advanced 2D/3D space, it should impose no new burden on users with more basic needs. Hopefully, they’ll see these new tools, be curious, and learn them in their own time, but explaining nested coordinate systems, proper order of scaling, rotation & translation… it’s a lot to take in up front.

1 Like

inoise1d(x), inoise2d(x,y), inoise3d(x,y,z) implemented as simplex noise the successor to Perlin noise.

2 Likes

The pixel map is a list of coordinates where the index of the element matches the pixel index. This function lets you iterate over the coordinates. It would be very similar to doing forEach() on the object your pixel code/json map returned (after normalization).

e.g.


//during init, 
mapPixels((i,x,y,z) => {
  // called once for every pixel in the map, just like render2D/3D would be 
})

export function beforeRender(delta) {
  //during beforeRender
  mapPixels((i,x,y,z) => {
    // called once for ever pixel in the map, just like render2D/3D would be 
    // maybe implement a radial fade for those cool tree toppers
  })
}

export function sliderNoiseDensity(scale) {
  //in a control, e.g. to regenerate a noise map
  mapPixels((i,x,y,z) => {
    noiseMap[i] = generateNoise(x * scale, y * scale)
  })
}
2 Likes

mapPixels() would pretty much cover my universal iterator request. Great!

a function like FastLEDs beatsin/beatsin16 (beats_per_minute, low_val, high_val, timebase, phase_offset)