One PB driving two different size matrix

Is it possible to drive independently two matrix with different size?
I already have my Message Board project complete.
There are two 8x32 Matrix driven by PB + IO Expander.
Each matrix is driven by individual expander channel but PB sees
it as a single 8x64 Matrix.
Now I want to add Icon Display to this project.
Icon Display will be driving 16x16 Matrix.
Obviously I can use a dedicated PB for this addition.
But It will be nice if I can simply use just another expander channel
and run completely different patterns on each matrix.
Is this possible?

No, not separate patterns, one pattern can drive two matrixes, but they aren’t independent like that.

No and yes.

You can’t run two different patterns from the playlist, but you can run one pattern with multiple sets of beforeRender() and render() functions, renamed to avoid collisions. @zranger1 did a deluxe multi-segment pattern which is in the pattern library, but below is a simple example to illustrate the basic principle:

////////////////////////////////////////////////////////////////////////////////
//
//  Simple Segments: different patterns running on different segments.
//
////////////////////////////////////////////////////////////////////////////////


//  Simple sample patterns.  But you're not limited to these; size permitting, any 
//  freestanding pattern can be used within the segments. To do so requires copying 
//  the pattern code into this section, renaming the pattern's "beforeRender()" and 
//  "render()" functions to something unique (like "beforeRender_{patternName}" and 
//  "render_{patternName}"), and assigning it a timeslot.  If the pattern contains 
//  additional functions or variables outside the "beforeRender()" and "render()" 
//  functions, they may also need to be renamed to avoid collisions and confusions.
//  

//  Sample pattern #1: This pattern runs on the first segment of the strand.
var t1;
function beforeRender_Segment1(delta) { t1 = time(0.05); }
function render_Segment1(index) { rgb(t1 + index/pixelCount, 0, 0); }

//  Sample pattern #2: This pattern runs on the second segment of the strand.
var t2;
function beforeRender_Segment2(delta) { t2 = time(0.03); }
function render_Segment2(index) { rgb(0, t2 + index/pixelCount, 0); }

//  Sample pattern #3: This pattern runs on the third segment of the strand.
var t3;
function beforeRender_Segment3(delta) { t3 = wave(time(0.05)); }
function render_Segment3(index) { rgb(0, 0, t3 + index/pixelCount); }


////////////////////////////////////////////////////////////////////////////////
//
//  The segments.
//
//  The "beforeRenderer" and "renderer" columns refer to the renamed "beforeRender()" and "render()" 
//  functions of the particular pattern to be run in this segment.  
//
export var segments = [
  //  [0]               [1]               [2]                       [3]
  //  beginIndex,       endIndex,         beforeRenderer,           renderer
  //  -----------       ---------         ---------------           --------
  [   0,                pixelCount/3,     beforeRender_Segment1,    render_Segment1 ],
  [   pixelCount/3,     pixelCount*2/3,   beforeRender_Segment2,    render_Segment2 ],
  [   pixelCount*2/3,   pixelCount,       beforeRender_Segment3,    render_Segment3 ],
];


////////////////////////////////////////////////////////////////////////////////
//
//  There's no need to edit anything below this line.
//
export var savePixelCount;
export function beforeRender(delta) {
  //  Call the beforeRender() function for each segment's pattern.
  for (var segment = 0; segment < segments.length; segment++) {
    savePixelCount = pixelCount; pixelCount = segments[segment][1] - segments[segment][0];
    segments[segment][2](delta);
    pixelCount = savePixelCount;
  }
}

export var minIndex = array(segments.length), maxIndex = array(segments.length);
minIndex.mutate((v, i, a) => pixelCount+1); maxIndex.mutate((v, i, a) => -1); 
export function render(index) {
  //  Find which segment this pixel belongs to, and call the appropriate render() function.
  for (var segment = 0; segment < segments.length; segment++) {
    var beginIndex = segments[segment][0]; var endIndex = segments[segment][1];
    if ((index >= beginIndex) && (index < endIndex)) {
      var relativeIndex = (index - segments[segment][0]);
      minIndex[segment] = min(minIndex[segment], relativeIndex); maxIndex[segment] = max(maxIndex[segment], relativeIndex);
      savePixelCount = pixelCount; pixelCount = segments[segment][1] - segments[segment][0];
      segments[segment][3](relativeIndex);
      pixelCount = savePixelCount;
      return;
    }
  }
}
2 Likes

I am aware about @zranger1 multi-segment pattern implementation but did not try it yet.
I am sure, this will work perfectly for the strings but I need to run 2 very different matrix.
Each one needs an individual dedicated mapping. I know, mapping could be part of
the pattern but again, I did not try this.
I guess, if mapping could be part of the pattern plus using multi-segment pattern
I may achieve what I am planning to do.

Using a dedicated PB is an option but not really desired. I don’t want to add extra
WiFi nodes without absolute necessity. I live in a big apartment complex with
gazillions of WiFi Routers around. Plus I am running relatively complex Home
Automation with sizable number of Zigbee Devices. Zigbee and WiFi are shearing
the same 2.4GHz band. But so far so good.

Yeah, I thought about mentioning that… And I did an entire multimap pattern that would work great for 2+ matrixes, BUT… It’s one pattern, not what the OP requested, and it’s not as simple as “Can I run different patterns on two matrixes at the same time?” which is definitely a No.

OK,
let me rephrase my question.
This certainly could be one single pattern but running different things in each area.
What I need - is to run my current Message Board pattern on 8x64 matrix and
add 16x16 Icon Matrix to the equation. I will need to display different Messages
and/or Icons based on variable(s) but this will be one single pattern say,
running two sub-patterns on different matrix.
Plan is to add this 16x16 Icon Matrix to the existing project (PB + Expander) and
add extra logic to the existing pattern for controlling this added matrix.
My good guess - this could be done. Right?

Could be, yes, but if you go look at how the “multimatrix” (I called it multimap) code would have to work to do this in your case, it’s not going to be very friendly to work with.

I calculated what to display based some math… In your case, you’d basically set one big map, let’s say that one matrix is half of the total size to the left, and the other to the right, (you could also use a pure index value, to be clear in this case, which might be friendlier to work with actually)

You’d decide which “pattern” code to run based on the index value. So basically calling “code A” for one matrix and “code B” for the other, both of which are just functions within the real pattern. Understood?

This is very good idea, Thank you!
Basically in a “render” I will need to call “renderA” if Index is < 511 and else “renderB”.
Now, in order to use my current Message Board pattern “as is” I will need to remove global
mapping and add it back to the “renderA” code. “renderB” code should have its own
embedded mapping code.
Am I right?

What the trick to run a mapping code from the pattern?
Can the existing mapping code be used “as is” or it must be modified?
I am sorry to ask these questions because I am not a professional SW engineer.
Whatever is obvious for the SW developer is not always quite clear for myself.

Yes, in the renderA, renderB, but no on the mapping. You only have one map… so you have two choices, map all of the values in the same range, so if the first map is 0-15 on X, both would be, and trust the indexing to solve the overlap. OR you could map them side by side as mentioned above, but then each will be awkward since they’ll only be half the 0…1 space of the map space. So I’d do the first option myself.

Just map both on top of each other, and let the index decide what to do.

Map first matrix, then map second matrix IN the same map code, so long as both use the same sizing of values.

Very BIG Thank you for the ideas!
Now I will need to try this with real PB and code.

1 Like

I would test your map first by running one pattern on both, they should do the same thing, that will confirm your scaling/ranges are correct. Then do the A/B render changes and get different displays on each

So to be clear with your 8x64 and 16x16, you probably need to use a 16x64 range as your map range, so that they overlap correctly, and both then map cleanly to 0…1 on all axises (axes?)

So the 8 goes 0 to 15 (skip every other number), and the 16 maps to 64, skip every 4), so that they line up correctly.

This is exactly what I am trying to achieve.
I am trying to use/modify built-in “Multiple Panel Matrix” mapper.
And it works!
But both my matrix are zigzagged by Y coordinate.
I tried to add the zigzagging code for the Y but mapper says at the
bottom in red “something not quite right”.
I cannot figure out what is not right.
Could you please point me what I am doing wrong?
Here is a modified mapping code:

function (pixelCount)
{
  //set zigzag to true if every other LED row or colon travels in reverse
  //if they are all straight across, set it to false
  zigzagX = false
  zigzagY = false
  
  //rotate a point (x, y), along a center (cx, cy), by an angle in degrees
  function rotate(cx, cy, x, y, angle)
  {
    var radians = (Math.PI / 180) * angle,
        cos = Math.cos(radians),
        sin = Math.sin(radians),
        nx = (cos * (x - cx)) + (sin * (y - cy)) + cx,
        ny = (cos * (y - cy)) - (sin * (x - cx)) + cy;
    return [nx, ny];
  }

  //create a set of coordinates for a matrix panel
  //sized (w, h), rotated by an angle, and offset by (sx, sy)
  function panel(w, h, sx, sy, angle)
  {
    var x, x2, y, y2, p, map = []
    
    for (y = 0; y < h; y++)
    {
      if (zigzagY && x % 2 == 1)
      {
        y2 = h - 1 - y
      }
      else
      {
        y2 = y
      }
      
      for (x = 0; x < w; x++)
      {
        //for zigzag, flip direction every other row
        if (zigzagX && y % 2 == 1)
        {
          x2 = w - 1 - x
        }
        else
        {
          x2 = x
        }
        
        p = rotate((w-1)/2, (h-1)/2, x2, y2, angle);
        p[0] += sx
        p[1] += sy
        map.push(p)
      }
    }
    return map;
  }

  //assemble one or more panels
  var map = [];

  map = map.concat(panel(8, 8, 0, 0, 0))
  map = map.concat(panel(8, 8, 8, 0, 0))
  map = map.concat(panel(8, 8, 0, 8, 0))
  map = map.concat(panel(8, 8, 8, 8, 0))

  return map
}

Added code on line 27 is this:

      if (zigzagY && x % 2 == 1)
      {
        y2 = h - 1 - y
      }
      else
      {
        y2 = y
      }

As soon if I change zigzagY = false to zigzagY = true
mapper fails. Problem must be on this line
y2 = h - 1 - y

But what it is?

zigzagX = false/true works as expected.

*** EDIT ***

Very strange, but error goes away if I simply add/delete any character anywhere.
But I still have a problem - this modified code is not zigzagging at all.
Is my added code in a wrong place?

Sometimes simpler is better.

The mapping function is just a way of converting the pixel index into X and Y coordinates. It’s a very nice convenience to abstract it from the pattern code because it doesn’t usually change for a given device, but you can do the mapping yourself easily enough.

For example, this is a reworked version of the library pattern “2D Red-Green Sweep” where I’ve extracted the map for an 8x8 matrix and put it into the pattern code:

//
//  Simple pixelMap replacement:  while viewing the mapping function on the Mapper tab:
//                                (1) open up a browser console window (F12) and select the 'Console' tab),
//                                (2) evaluate the expression 'console.log(pixelMap.toString())', and
//                                (3) copy the resulting list of numbers into the 'pixelMap' array below.
//
var pixelMap = [0,0,36,0,73,0,109,0,146,0,182,0,219,0,255,0,0,36,36,36,73,36,109,36,146,36,182,36,219,36,255,36,0,73,36,73,73,73,109,73,146,73,182,73,219,73,255,73,0,109,36,109,73,109,109,109,146,109,182,109,219,109,255,109,0,146,36,146,73,146,109,146,146,146,182,146,219,146,255,146,0,182,36,182,73,182,109,182,146,182,182,182,219,182,255,182,0,219,36,219,73,219,109,219,146,219,182,219,219,219,255,219,0,255,36,255,73,255,109,255,146,255,182,255,219,255,255,255];
//  These functions extract the X and Y coordinates from the map for use within the render() function.
//  They won't work if a pattern uses the new coordinate transformation functions, but for simple patterns they're fine.
function mapX(index) { return pixelMap[2*index]/255; } function mapY(index) { return pixelMap[1+2*index]/255; }

//  Finally, the pattern's "render2D(index, x, y)" function must be renamed to "render(index)" and
//  the helper functions above must be called to initialize the 'x' and 'y' variables the render code expects.

////////////////////////////////////////////////////////////
//
//  EXAMPLE: the library function "2D Red-Green Sweep", converted for a local map:

/*
This pattern displays a sweep of color across a 2D pixel mapped display.
Red waves should travel left to right, and green waves from top to bottom.
*/

var axis, t1
export function beforeRender(delta) {
  t1 = time(.1)
  axis = t1 > .5
  t1 *= 2
}

/*
export function render2D(index, x, y) {
*/
export function render(index) { x = mapX(index); y = mapY(index); 
/*
*/

  h = axis/3
  v = wave((axis ? y : x)/2 - t1 + .5)
  v = pow(v, 20)
  hsv(h, 1, v)
}

If you have two matrices with different layouts, you could connect them one-at-a-time, define their mapping functions, and extract the coordinates as described above into two arrays. If you then combine this approach with the “Simple segments” one I described above, then you could have a different map table for each segment and the render() code would use the appropriate one to translate the index into X and Y coordinates when calling the renamed render function for each segment.

This example has different patterns running on each of two 8*8 matrices connected in series:

//
//  Simple pixelMap replacement:  while viewing the mapping function on the Mapper tab:
//                                (1) open up a browser console window (F12) and select the 'Console' tab),
//                                (2) evaluate the expression 'console.log(pixelMap.toString())', and
//                                (3) copy the resulting list of numbers into the 'pixelMap' array below.
//
var pixelMap1 = [0,0,36,0,73,0,109,0,146,0,182,0,219,0,255,0,0,36,36,36,73,36,109,36,146,36,182,36,219,36,255,36,0,73,36,73,73,73,109,73,146,73,182,73,219,73,255,73,0,109,36,109,73,109,109,109,146,109,182,109,219,109,255,109,0,146,36,146,73,146,109,146,146,146,182,146,219,146,255,146,0,182,36,182,73,182,109,182,146,182,182,182,219,182,255,182,0,219,36,219,73,219,109,219,146,219,182,219,219,219,255,219,0,255,36,255,73,255,109,255,146,255,182,255,219,255,255,255];
var pixelMap2 = [255,0,219,0,182,0,146,0,109,0,73,0,36,0,0,0,255,36,219,36,182,36,146,36,109,36,73,36,36,36,0,36,255,73,219,73,182,73,146,73,109,73,73,73,36,73,0,73,255,109,219,109,182,109,146,109,109,109,73,109,36,109,0,109,255,146,219,146,182,146,146,146,109,146,73,146,36,146,0,146,255,182,219,182,182,182,146,182,109,182,73,182,36,182,0,182,255,219,219,219,182,219,146,219,109,219,73,219,36,219,0,219,255,255,219,255,182,255,146,255,109,255,73,255,36,255,0,255];


////////////////////////////////////////////////////////////////////////////////
//
//  Simple Segments: different 2D patterns running on different 2D matrices.
//
////////////////////////////////////////////////////////////////////////////////

//  Sample pattern #1 "2D Red-Green Sweep" runs on the first matrix.
/*
This pattern displays a sweep of color across a 2D pixel mapped display.
Red waves should travel left to right, and green waves from top to bottom.
*/

var axis, t1
export function beforeRender_Matrix1(delta) {
  t1 = time(.1)
  axis = t1 > .5
  t1 *= 2
}

export function render2D_Matrix1(index, x, y) {
  h = axis/3
  v = wave((axis ? y : x)/2 - t1 + .5)
  v = pow(v, 20)
  hsv(h, 1, v)
}

//  Sample pattern #2 "Jeff's Sinusoidal Waves" runs on the second matrix.
basePeriod = 1.2 / 65.535
export function beforeRender_Matrix2(delta) {
  t1 = time(basePeriod)
  t2 = time(basePeriod * 1.05)
  t3 = time(basePeriod * 1.1)
}

export function render2D_Matrix2(index, x, y) {
  x /= 2
  y = 1.4 * y - .2
  r = near(y, wave(x - t1))
  g = near(y, wave(x - t2))
  b = near(y, wave(x - t3))
  rgb(r, g, b)
}

// Returns 1 when a & b are proximate, 0 when they are more than `halfwidth`
// apart, and a gamma-corrected brightness for distances within `halfwidth`
function near(a, b, halfwidth) {
  var halfwidthDefault = 0.4
  if (halfwidth == 0) halfwidth = halfwidthDefault
  var v = clamp(1 - abs(a - b) / halfwidth, 0, 1)
  return v * v * v
}


////////////////////////////////////////////////////////////////////////////////
//
//  The matrices.
//
//  The "beforeRenderer" and "renderer2D" columns refer to the renamed "beforeRender()" 
//  and "render2D()" functions of the particular pattern to be run in this matrix.
//
export var segments = [
  //  [0]               [1]               [2]                       [3]                 [4] 
  //  beginIndex,       endIndex,         beforeRenderer,           renderer2D,         pixelMap
  //  -----------       ---------         ---------------           -----------         --------
  [   0,                pixelCount/2,     beforeRender_Matrix1,    render2D_Matrix1,  pixelMap1 ],
  [   pixelCount/2,     pixelCount,       beforeRender_Matrix2,    render2D_Matrix2,  pixelMap2 ]
];


////////////////////////////////////////////////////////////////////////////////
//
//  There's no need to edit anything below this line.
//
export var savePixelCount;
export function beforeRender(delta) {
  //  Call the beforeRender() function for each segment's pattern.
  for (var segment = 0; segment < segments.length; segment++) {
    savePixelCount = pixelCount; pixelCount = segments[segment][1] - segments[segment][0];
    segments[segment][2](delta);
    pixelCount = savePixelCount;
  }
}

export var minIndex = array(segments.length), maxIndex = array(segments.length);
minIndex.mutate((v, i, a) => pixelCount+1); maxIndex.mutate((v, i, a) => -1); 
export function render(index) {
  //  Find which segment this pixel belongs to, and call the appropriate render() function.
  for (var segment = 0; segment < segments.length; segment++) {
    var beginIndex = segments[segment][0]; var endIndex = segments[segment][1];
    if ((index >= beginIndex) && (index < endIndex)) {
      var relativeIndex = (index - segments[segment][0]);
      minIndex[segment] = min(minIndex[segment], relativeIndex); maxIndex[segment] = max(maxIndex[segment], relativeIndex);
      savePixelCount = pixelCount; pixelCount = segments[segment][1] - segments[segment][0];
      relativeX = segments[segment][4][2*index]/255; relativeY = segments[segment][4][1+2*index]/255;
      segments[segment][3](relativeIndex, relativeX, relativeY);
      pixelCount = savePixelCount;
      return;
    }
  }
}

It won’t work on every pattern in the library – specifically, patterns using the new coordinate transformation functions won’t work – but for most patterns it’s good enough.

@Scruffynerf @pixie

First off all - Thank you very much for the very helpful tips and ideas.

Based on what I learned, I created something which actually works
and looks simple for my none SW engineer eyes.
Next code is running two separate instances of a bit modified
“Red_Green_XY_2D_Sweep” code:

// Define Matrix-1
var  heightM1 =   8
var nPixelsM1 = 512

// Define Matrix-1
var  heightM2 =  16
var nPixelsM2 = 256

// X, Y Cordinates for 2D Patterns
var x, y

// Current Active Pattern
var renderCurrent

/*
// Number of Renders
var nRenders = 2

// Array of Renders
var renderFunc = array(nRenders)
*/

// ------------------------------------------------

// Render Function-2
var axis1

function renderF1(Idx, nPixels)
{
  h = axis1/3
  v = wave((axis1 ? y : x)/2 - t1 + .5)
  v = pow(v, 20)
  hsv(h, 1, v)
}

// ------------------------------------------------

// Render Function-2
var axis2

function renderF2(Idx, nPixels)
{
  h = axis2/3
  v = wave((axis2 ? y : x)/2 - t2 + .5)
  v = pow(v, 20)
  hsv(h, 1, v)
}

// ------------------------------------------------

// Select Render Function and Parameters
// for current Global Pixel Index
function selectRender(Idx)
{
  if (Idx < nPixelsM1)
  {
    mHeight     = heightM1
    xAdjust     = 0
    IdxAdjusted = Idx
    nPixels     = nPixelsM1
    renderIdx   = 0
  }
  else 
  {
    mHeight     = heightM2
    xAdjust     = (nPixelsM1 / heightM1)
    IdxAdjusted = Idx - nPixelsM1
    nPixels     = nPixelsM2
    renderIdx   = 1
  }

  x = floor(IdxAdjusted / mHeight) 
  y = (IdxAdjusted % mHeight)

  // Y adjustment for vertical zigzag wiring
  y = (x % 2) == 1 ? (mHeight - 1 - y) : y

  if (0 == renderIdx)
  {
    x = (x / (nPixelsM1 / heightM1))
    y = (y / heightM1)
    
    renderCurrent = renderF1(IdxAdjusted, nPixels)
  }
  else if (1 == renderIdx)
  {
    x = (x / (nPixelsM2 / heightM2))
    y = (y / heightM2)

    renderCurrent = renderF2(IdxAdjusted, nPixels)   
  }
}

// ------------------------------------------------
//
// Common PB Functions
//
// ------------------------------------------------

var t1, t2
export function beforeRender(delta)
{
  // For renderF1
  t1 = time(.2)
  axis1 = t1 > .5
  t1 *= 2

  // For renderF2
  t2 = time(.1)
  axis2 = t2 > .5
  t2 *= 2

}

// ------------------------------------------------

export function render(index)
{
  selectRender(index)
}

// ------------------------------------------------

There is no any mapping code present.
All mapping is done in the “selectRender(Idx)” function which is called from
normal 1D render. And based on index function “selectRender(Idx)” does
all adjustments for global variables and calls correspondent render function.

So, my creation is functional.
But before I will go too far could you please spend a min and tell me if my
approach is good or I did something very stupid and better forget about it?

A software engineer might say that code with related variables and code spread throughout is harder to understand and maintain than code where related items are located close together and protected from modification by unrelated components, but I am not a software engineer.

If it meets your requirements and doesn’t endanger anyone else, it’s good enough.

I 100+% agree.
I am not SW engineer but I am doing a lot of FPGA-based designs.
Code maintainability and readability my high priorities.
Unfortunately PB does not support things like structures, classes, etc.
This way it is harder to create very well structured code.

But my question was not about how hard and maintain a code.
My question was/is - is my approach good for implementing
say, “segmented patterns” or this is something what should be forgotten.

This is very scared message.
I am still working part time for medical equipment company.
Yes, unfortunately some badly designed HW/SW could be dangerous for patients.

Yes. Matrices (or strings) connected in series will have sequential addresses, so you can call a different preRender() or render() function when the index crosses from one matrix to the next. If the pattern depends on the actual index (or x, or y) then you also need to subtract the starting point to make them zero-based again.

Thank you for the response.
This is exactly what my code is doing.