My first successful pattern

Short version: I finally actually banged around enough to make a pattern that does what I want from scratch. Really, the goal was just to have a sine wave scroll across the matrix. Ended up with a sine wave with a wavelength that’s a sine wave. Here it is:

Long version: I’ve owned a pixelblaze since March, 2018. I have one as bias lighting for my TV, lighting around a recessed boardgame table, and up and over the archway between my living room and dining room, but I pretty much always use pre-made patterns. At most, I edit a few variables in existing ones to change the scale or color, but it’s just through trial and error. My programming experience was basically zero and my actual math classes stopped at polynomial functions so I pretty quickly gave up on writing anything interesting myself.

In 2020 I tried learning C, and it made me confident enough to try some arduino stuff. I got tired of “example” projects and really needed something to DO to practice programming, so I figured I should bang my head against pixelblaze some more.

So over the last couple days I set out to write my own pattern without trying to look at other patterns or use example code, and truly just bash my head against it until it made sense. I love working with LEDs because it can help my visualize what is happening in my code, and I often end up debugging by just outputting some variable as the value for the LEDs, like a visual var watcher.

After getting it “done” the first time, I either closed my laptop too quickly or unplugged the pixelblaze too quickly, and my pattern was corrupted, and couldn’t load. Was going to post to see if anyone could help me recover it, but I figured if I actually learned anything by making it, I should be able to do it again. So I remade it, a little better than the first time.

I was expecting the mapper to give me something like a 2D array with the index stored in the X, Y value, and to be working on individual pixels rather than something more scalable. but after seeing what playing with the scale of the “close enough to the curve to light up” variable in functions in some example code that used the mapper values, I saw how good the brain was at filling in the “missing” parts of the pattern, and almost seeing it as a pattern that is behind the PCB, and the LEDs are holes that it shines through. So I accepted my 0…1 values for X and Y and tried to do it the “right” way.

I look forward to trying to actually implement the rest of my idea and realizing that I have to remake everything in an entirely different way to allow for something I want in the future.

4 Likes

Wow good for you. I think when I set out to understand some new code or concept I rely quite a bit on going over and over existing examples!

Your wave while at a low frequency to me looks like a helix spinning, wondering if you add another and split the phase if it will look like a double helix as in DNA. Which would be a cool pattern for me to lift off you :innocent::grinning_face_with_smiling_eyes:

4 Likes

Yeah, I definitely see it as a “helix” when it’s going slow, especially when the line has more “thickness” since it sort of looks like it is more like a ribbon being rotated.

Definitely was only a couple lines away from having two sine waves that are out of phase. I’d like to have it alternate which one is on “top” where they cross.

Right now it only draws whichever of the two lines the LED is closest to for each lit pixel, but I was thinking there could be a variable that determines which one is on top that flips every half cycle, and if the distance from the LED to both sine waves is very small, just use the value from the “top” line.

The biggest problem with that that I see is that the whole thing is rendered each frame, rather than the rows being rendered once and then moved down, so once the variable that determines which function is on top flips, any intersection still on the board will flip. Maybe a moving line that splits the waves at the peak/trough, one side being red on top, the other side being blue on top, and having it move in sync with the sine waves.

EDIT: Messed around with masking one with the other:

2 Likes

Love it, did you upload the pattern or can you post the code here?

Very nice.

I really need to revisit Waves Upon Waves

Ha, this is exactly why I didn’t want to look at anything anyone else had done. As soon as I know how well someone else has solved a problem I’m working on, I start to feel like I’m wasting effort.

Last night after finishing up on the pattern I looked around and saw your pattern that you linked (I think it was a video in another post), and immediately thought “sure glad I didn’t see this before I started working on mine”. Yours look so great, and I truly have to force myself to not look at the code because I know I’m going to want to optimize mine without any helpful insight until I hit a wall.

1 Like

I lost some of the last changes I made last night after I took the video (closed without saving), so I was putting off posting it until I had time to re-do them. It’s some really ugly stuff.

export function beforeRender(delta) {
  t1 = time(0.03)
  t2 = time(0.2)
  lbd = 0.6 //wavelength in "peaks on screen at a time"
  decay = false
  smoosh = 1 // decreases amplitude of sine waves, but so far I haven't added a y-axis offset to re-center
  scale = 5 // higher is thinner
  power = 3 // higher is thinner, odd
  y2offset = 0.5
  
}

export function render2D(index, x, y) {
  top = square(x+t1, 0.5) //way too fragile, doesn't work with other wavelengths?
  //lbd = 1*wave(t2)+0.5 // wavelength changes over time
  yd = wave(x/2) // wave that is subtracted from y1 if decay = true
  y1 = wave((x*lbd)+t1)/smoosh //sine wave 1
  y2 = wave((x*lbd)+y2offset+t1)/smoosh //sine wave 2
  if (decay){
    y1 = y1 * pow(yd,2)
  }
  v=0
  s = 1
  v1 = (1-scale*abs(y-y1)) //brightness factor based on distance from sine wave 1
  v2 = (1-scale*abs(y-y2)) //brightness based on distance from sine wave 2
  if ((v1 + v2) > 0.05){ 
    //if the combined brightnesses are greater than 0.05, then it's a "crossing point", 
    //and we will set the "bottom" sine wave brightness to 0, so that max() will always pick the top color.
    if(top == 1){
      v2 = 0
    }
    else{
      v1 = 0
    }
  }
  v = max(v1, v2) //chooses the brighter of the two to display
  h=1
  if (v1 > v2){ //sets the color of sine wave 1
    h=x/scale+t2
  }
  else{
    h=x/scale+y2offset+t2 //sets the color of sine wave 2, using the y2offset to offset the color from sine wave 1
  }
  hsv(h, s, pow(v, power))
}

3 Likes

I totally understand and I encourage you to do it yourself and see how far you can go. There are lots of ways to do the same thing, many just as good as others. I look at my code and see so many changes I’d make now, so it’s far from the One Twue Way.

And just to add - in my view looking at someone else’s code is never a wasted effort. I find myself looking at it, then understanding it, and then working out how to make it better - either more efficient, or more portable to difference numbers of pixels, or more customisable, perhaps with sliders. But I applaud the effort in coding from scratch - it is certainly worth doing sometimes, and you can always sit back and admire the result of your efforts.

1 Like

I think you’re doing great! Your comments are useful and you have a variable called smoosh so you are definitely on the right track.

All of those values in beforeRender are great candidates for controlling with sliders. You could see the effects live as you wiggle your mouse instead of editing the numbers.

3 Likes

One additional side benefit of that, you define them initially as globals outside of beforeRender, the slider functions that change the values only get invoked when slid, and so your beforeRender() is then both smaller and faster (not that’s a major slowdown to keep redefining them every frame, but it is something removable.)

Sliders are almost always worth using, and give the end user the ability to tweak until they find a sweet spot (or just to explore what the pattern can do)

That’s a great accomplishment and a cool pattern which could evolve into all kinds of things if you wanted. The wave function is so useful. Love it.