I have all the pieces for this hanging around as part of yet another polar conversion project. Here’s the minimalist version:
(If you haven’t already, you’ll have to set up a 2D map for your display for this to work – go to the Pixelblaze’s Mapper tab, select the “matrix” example, set the width variable appropriately for your display and press the “Save” button near the bottom.)
// Radial rainbow for 2D displays
export var speed = 0.5
export var direction = 1
// UI sliders
// higher is faster
export function sliderSpeed(v) {
speed = 1-v;
}
// left = inward, right = outward
export function sliderDirection(v) {
direction = (v < 0.5) ? 1 : -1;
}
// pythagorean distance from center of display. Pixelblaze
// provides normalized x,y coords, so center is always going
// to be (0.5,0.5) regardless of real world display dimensions
function getRadius(x, y) {
x -= 0.5; y -= 0.5;
return sqrt(x*x + y*y);
}
// generate a timer - a sawtooth wave that we can
// use to animate color -- the direction flag makes
// it positive or negative, depending on the UI
// slider setting
export function beforeRender(delta) {
t1 = direction * time(0.08 * speed);
}
// use radius and our timer to color every pixel
export function render2D(index, x, y) {
hsv(t1+getRadius(x, y), 1, 1);
}
Damn, beat me to it. But very similar to what I was going to do.
Probably the only thing I’d add is a wave size tuner/slider so you could control the length of the rainbow (ie smaller/larger bands of color, going thru the hue spectum faster or slower).
Left an exercise for the new folks wanting to learn to code PB: add that feature. You’ll add a new slider, with a new variable controlled by the slider, and then modify how the hue changes by scaling the radius to your new variable. (Hue is time plus radial length now, so changing radial length adjusts how quickly you run thru the hue of colors.)
@zranger1’s solution always returns values of T adjusted for speed, but then adding radius (which is always between 0 and [quick calc, X and Y are never greater than 0.5, radius is sqrt of X^2 + Y^2, aka sqrt of 0.5 or] .7ish) , is what makes the rainbow effect. So adjusting the speed of change doesn’t currently affect the color band size.
I was trying to figure out how to add this wave slider, but didn’t realize how deep that rabbit hole went. lol
So I was also looking at some of the sound patterns and was looking at adding a sound feature. would I add the code to the bottom of the script that’s already written?
As for how deep the rabbit hole is, adding a new slider (and variable it can control) should be 3-4 lines of code. “Scaling the radius” = multiply the radius by the new variable (which should have a value that varies from 0 (which will mean no rainbow, just the changing color) to say 10, meaning at most it’ll multiply the radius by 10 (which means it’ll be 7ish at most). Hue is a value that loops at 1 (so at 1.01, it’s like .01 again), so it’ll make 7 bands of rainbows. Below 1, it’ll stretch out the rainbow, making wider bands of colors.
So let’s figure out to add sound to this:
The expansion board returns a few sound related variables, including an array of 32 values that condenses the sound it hears. (Think of the classic sound bars, you’d have 32 of them)
It also returns the loudest frequency it heard.
So the easy way: adjust radial length to be influenced by the loudest sound. Now your rainbow circle will pulse depending on the music.
More involved: we didn’t calculate the angle only the radius, we just did a full circle. What if we did? The formula for the angle is atan2 ( y / x ) [corrected from atan]
[Note there was a bug in PB for atan (aka arctan) fixed in most recent firmware, so make sure you’ve updated]
So dividing the circle’s angles into 32 pieces, we could adjust the radial length by the value of the matching bucket… And now it’ll make 32 rainbow arcs that change according to the sound it hears.
I’ll post the code for this in a few days, give the folks who want to learn PB coding some time to try it themselves.
Bonus points if your rainbow band size scaling solution compensates for something @scruffynerf pointed out: The largest possible distance from center in a rectangular matrix is going to be along a diagonal, and because matrix coordinates are all normalized to the range 0…1, it’s going to have the value sqrt(0.5)==0.7071.
In my code, I use distance as the base for hue, which means that you don’t get the full 0…1 range. Some pinks and reds are potentially missing. I decided to do this for simplicity’s sake. It’s easier to read, and is not visually obvious. But IT’S NOT PERFECT, and it can be. If you’re adding band scaling anyway, you might as well fix it!
(My solution requires changing just two lines, if I’m allowed to put the whole band size slider on a single line. )
Actually my quick and dirty solution would be to double the radius, which especially works because despite the square matrix, we’re drawing circles and so the circle is only 0.5 radius, meaning at most we’d do half of the hue spectrum. So just multiply the found radius by 2, which will perfectly fit the spectrum into the circle, and at the corners, be 2*.707 or roughly 1.4, so it’ll be more rainbow but not a double rainbow.
So my fix would be just adding two characters.
[Exact fix removed as I realized it gave away entirely how to do the scaling too]
As promised, circular spectrograph. Rough, so not posting to patterns (yet)… but people can play with it.
// Radial rainbow for 2D displays
// initial circle/rainbow code by @zranger1
// modified for sound by @scruffynerf
export var speed = 0.5
export var direction = 1
export var size = 100
export var spin = 1
export var spincycle = 0
export var spincyclearray = 0
export var frequencyData
export var angle
// UI sliders
// higher is faster
export function sliderSpeed(v) {
speed = 1-v;
}
// left = inward, right = outward
export function sliderDirection(v) {
direction = (v < 0.5) ? 1 : -1;
}
// sizing
export function sliderSize(v) {
size = 200*v
}
// spin
export function sliderSpin(v) {
spin = v
}
// pythagorean distance from center of display. Pixelblaze
// provides normalized x,y coords, so center is always going
// to be (0.5,0.5) regardless of real world display dimensions
function getRadius(x, y) {
x -= 0.5; y -= 0.5;
return sqrt(x*x + y*y)*2;
}
// pythagorean angle from center of display. Pixelblaze
// provides normalized x,y coords, so center is always going
// to be (0.5,0.5) regardless of real world display dimensions
function getAngle(x, y) {
x -= 0.5; y -= 0.5;
angle = floor(((atan2(y,x)+PI)/4)*32)%32
angle = (angle+spincyclearray)%32;
return angle;
}
// generate a timer - a sawtooth wave that we can
// use to animate color -- the direction flag makes
// it positive or negative, depending on the UI slider setting
// spin slider controls the spin, currently only one direction
export function beforeRender(delta) {
t1 = direction * time(0.08 * speed);
spincycle = (spincycle + spin)%32
spincyclearray = floor(spincycle);
}
// use radius+angle and timer to color every pixel, if it's loud enough
export function render2D(index, x, y) {
radius = getRadius(x, y);
if (radius <= frequencyData[getAngle(x,y)]*size) {
hsv(t1+radius, 1, 1);
} else {
hsv(0,0,0)
}
}
there is at least one bug i’m still tracking down. Bonus points for noticing the bug. hint: use a tone generator.
I’ve started a version of this with more solid colors around, rather than rainbow gradients, and with that one, adjusting how many buckets of sound, and which (a sliding range), and it also looks really nice. Coming soon.
The sound activation spins the patterns counter clockwise around the grid, where as Zranger1’s would radiate the color changes from the center —> out or from out ----> in.
Oh it still radiates in and out… That didn’t change.
The only reason it only spins in one direction is that handling the negative spin required extra code I didn’t write (yet). There are 32 buckets and a counter which tracks “spin”. If you subtract instead of add spin, handling the negative cases (when you end up below 0) requires the extra code. Going over is easy to handle (that’s what the %32 (mod 32) does: 33%32 is 1, etc.
The sound doesnt do the spin, only the slider does. The sound is solely size of the spokes.
You can adjust the relative brightness to your liking. The “1” is how bright the spectrograph will be, the 0.3 is how bright the background rainbow will be.
It’s there… Check capitalization and spelling, then check to be sure that all the pairs of braces in the functions match up. If you can’t get yours to work, compare it to:
// Radial rainbow for 2D displays
// initial circle/rainbow code by @zranger1
// modified for sound by @scruffynerf
export var speed = 0.5
export var direction = 1
export var size = 100
export var spin = 1
export var spincycle = 0
export var spincyclearray = 0
export var frequencyData
export var angle
// UI sliders
// higher is faster
export function sliderSpeed(v) {
speed = 1-v;
}
// left = inward, right = outward
export function sliderDirection(v) {
direction = (v < 0.5) ? 1 : -1;
}
// sizing
export function sliderSize(v) {
size = 200*v
}
// spin
export function sliderSpin(v) {
spin = v
}
// pythagorean distance from center of display. Pixelblaze
// provides normalized x,y coords, so center is always going
// to be (0.5,0.5) regardless of real world display dimensions
function getRadius(x, y) {
x -= 0.5; y -= 0.5;
return sqrt(x*x + y*y)*2;
}
// pythagorean angle from center of display. Pixelblaze
// provides normalized x,y coords, so center is always going
// to be (0.5,0.5) regardless of real world display dimensions
function getAngle(x, y) {
x -= 0.5; y -= 0.5;
angle = floor(((atan2(y,x)+PI)/4)*32)%32
angle = (angle+spincyclearray)%32;
return angle;
}
// generate a timer - a sawtooth wave that we can
// use to animate color -- the direction flag makes
// it positive or negative, depending on the UI slider setting
// spin slider controls the spin, currently only one direction
export function beforeRender(delta) {
t1 = direction * time(0.08 * speed);
spincycle = (spincycle + spin)%32
spincyclearray = floor(spincycle);
}
// use radius+angle and timer to color every pixel, if it's loud enough
export function render2D(index, x, y) {
radius = getRadius(x, y);
bri = (radius <= frequencyData[getAngle(x,y)]*size) ? 1 : 0.3
hsv(t1+radius, 1, bri);
}