Okay I have my matrix soldered up. I fished this strip out of the garbage lol and tried again with new tips and removed the bad segment
. Much better this time around.
8 rows
32 columns
I have looked over your thread you linked @sorcer and tried my hand at generating just some simple text STOP, with the ruby script, but all I have is just some pretty yellow LEDs that occasionally blink now, thoughts?
// PixelBlaze pattern by Tom Rathborne <tom.rathborne@gmail.com>
// Made for fun. Share and enjoy!
// This pattern creates a rectangular framebuffer
// and does its own integer coordinate mapping
// and draws a text scroller with fonts made at https://brutalita.com/
// using Xiaolin Wu's antialiased line algorithm.
// See the bottom of this pattern for a Ruby program which generates these two lines:
// Ruby-generated coordinate data for S, T, O, P
var chars=[[[[2,0],[0.5,0],[0,0.5],[0,1.5],[0.5,2],[1.5,2],[2,2.5],[2,3.5],[1.5,4],[0,4]]],[[[0,0],[2,0]],[[1,0],[1,4]]],[[[0,0.5],[0.5,0],[1.5,0],[2,0.5],[2,3.5],[1.5,4],[0.5,4],[0,3.5],[0,0.5]]],[[[0,2.5],[2,2.5],[2,1],[1,0],[0,0],[0,4]]]];
var texts=[[0, 1, 2, 3]]; // STOP
// FIXME: So far this pattern only uses texts[0]
// value by which we mulitply each pixel on every frame for motion blur
// more than 50% blur is excessive so /2
var blur = 0
export function sliderMotionBlur(v) { blur = v/2 }
// Duplicate PixelBlaze 'Mapper' functionality without normalizing
var width = 32
var height = 8
// whoops = 1/(pixelCount == (width * height)) // crash on user error :P
// Some constants to avoid repeated arithmetic (esp.division) on constants
var widthm1 = width - 1
var widthm1_inv = 1/widthm1
var heightm1_inv = 1/(height-1)
var heightm1_invR = 0.33333333/(height-1) // How much of a rainbow we want
// Italicization
var italic = 0
export function sliderItalicization(v) { italic = 0.5-v }
// RGB framebuffer
// ... Storage
var FB = array(pixelCount)
FB.mutate(()=>{return array(3)})
// ... direct references to pixel by index for a quick render
var IndexPixel = array(pixelCount)
IndexPixel.mutate((foo,index)=>{
if (x >= 0 && x < width && y >= 0 && y < height) {
y = (x % 2 == 1) ? (height - 1 - y) : y
return FB[x + y * width]
} else {
return [0, 0, 0] // Return black if out of bounds
}
})
// HSV to RGB using global variables
var r, g, b // filled by HSVtoRGB
function HSVtoRGB(h, s, v) {
var i, f, p, q, t;
i = floor(h * 6);
f = h * 6 - i;
p = v * (1 - s);
q = v * (1 - f * s);
t = v * (1 - (1 - f) * s);
im6 = i % 6
if (im6 == 0) {
r = v; g = t; b = p;
} else { if (im6 == 1) {
r = q; g = v; b = p;
} else { if (im6 == 2) {
r = p; g = v; b = t;
} else { if (im6 == 3) {
r = p; g = q; b = v;
} else { if (im6 == 4) {
r = t; g = p; b = v;
} else {if (im6 == 5) {
r = v; g = p; b = q;
}}}}}}
}
// ... Render a pixel
// - Only used by WuLine, so we do 'alpha' here to keep WuLine readable
// - Uses max() so overlapping line pixels don't make bright spots
// - XadjustSV makes the left/right areas fade to black via S and V.
var XadjustSV = array(width)
XadjustSV.mutate((foo,x) => { return clamp(1.1 - pow(2*((x*widthm1_inv)-0.5),2),0,1) })
function PixelMax(x,y,a,h,s,v) {
if (x >= 0 && y >= 0 && x < width && y < height) {
adjSV = XadjustSV[x]
HSVtoRGB(
(h + y*heightm1_invR) % 1, // rainbow gradient in Y
s*adjSV,
v*adjSV*adjSV*a*a*a // ^2 and ^3 here are some excuse for gamma
)
var Pixel = FB[x + y * width]
Pixel[0] = max(Pixel[0], r)
Pixel[1] = max(Pixel[1], g)
Pixel[2] = max(Pixel[2], b)
}
}
// Xiaolin Wu's antialiased line algorithm
// Adapted from https://gist.github.com/polyamide/3f33cb4dc69e22fbf8b66cee39b78d60
// (replaced utility functions with PixelBlaze built-ins)
function WuLine(DrawPixel, x0, y0, x1, y1, h, s, v) {
if (x0 == x1 && y0 == y1) return
steep = abs(y1 - y0) > abs(x1 - x0);
if (steep) {
tmp = y0; y0 = x0; x0 = tmp;
tmp = y1; y1 = x1; x1 = tmp;
}
if (x0 > x1) {
tmp = x0; x0 = x1; x1 = tmp;
tmp = y0; y0 = y1; y1 = tmp;
}
dx = x1 - x0;
dy = y1 - y0;
gradient = dy / dx;
xEnd = round(x0);
yEnd = y0 + gradient * (xEnd - x0);
xGap = 1 - frac(x0 + 0.5);
xPx1 = xEnd;
yPx1 = trunc(yEnd);
if (steep) {
DrawPixel(yPx1, xPx1, 1 - frac(yEnd) * xGap, h, s, v )
DrawPixel(yPx1 + 1, xPx1, frac(yEnd) * xGap, h, s, v )
} else {
DrawPixel(xPx1, yPx1, 1 - frac(yEnd) * xGap, h, s, v )
DrawPixel(xPx1, yPx1 + 1, frac(yEnd) * xGap, h, s, v )
}
intery = yEnd + gradient;
xEnd = round(x1);
yEnd = y1 + gradient * (xEnd - x1);
xGap = frac(x1 + 0.5);
xPx2 = xEnd;
yPx2 = trunc(yEnd);
if (steep) {
DrawPixel(yPx2, xPx2, 1 - frac(yEnd) * xGap, h, s, v )
DrawPixel(yPx2 + 1, xPx2, frac(yEnd) * xGap, h, s, v )
} else {
DrawPixel(xPx2, yPx2, 1 - frac(yEnd) * xGap, h, s, v )
DrawPixel(xPx2, yPx2 + 1, frac(yEnd) * xGap, h, s, v )
}
if (steep) {
for (x = xPx1 + 1; x <= xPx2 - 1; x++) {
DrawPixel(trunc(intery), x, 1 - frac(intery), h, s, v )
DrawPixel(trunc(intery) + 1, x, frac(intery), h, s, v )
intery = intery + gradient;
}
} else {
for (x = xPx1 + 1; x <= xPx2 - 1; x++) {
DrawPixel(x, trunc(intery), 1 - frac(intery), h, s, v )
DrawPixel(x, trunc(intery) + 1, frac(intery), h, s, v )
intery = intery + gradient
}
}
}
function WarpPoint(x,y,out) {
xt = x * widthm1_inv
out[0] = widthm1 * 0.5 * (cos(PI + PI * xt) + 1) + y * italic
out[1] = y * clamp((1 - pow(2*(xt-0.5),2)),0,1)
}
// Draw a Character
// - starting at x,y
// - draw WuLine with r,g,b
var pstart = array(2)
var pend = array(2)
function DrawCharacter(chr,x,y,sx,sy,h,s,v) {
var segments = chr.length
for (N=0; N<segments; N++) {
var segment = chr[N]
points = segment.length
if (points == 1) {
WarpPoint(x + segment[0][0] * sx, y + segment[0][1] * sy, pstart)
PixelMax(floor(pstart[0]),floor(pstart[1]),1,h,s,v)
} else {
WarpPoint(x + segment[0][0] * sx, y + segment[0][1] * sy, pstart)
for (L=1; L<points; L++) {
WarpPoint(x + segment[L][0] * sx, y + segment[L][1] * sy, pend)
WuLine(
PixelMax,
pstart[0], pstart[1],
pend[0], pend[1],
h, s, v
)
pstart[0] = pend[0]
pstart[1] = pend[1]
}
}
}
}
// Font data is on a 3x6 grid,
// so we divide our target size by 2 and 5 to get a scaling factor:
cws = 2.3 / 2
chs = 8.3 / 5
// FIXME: just walk over the coordinate array and do this once
// Character spacing
csp = 3.6
// Clear (or fade if blur enabled) framebuffer
// and render the string
export function beforeRender(delta) {
FB.forEach((Pixel) => { Pixel.mutate((v) => { return blur * v } )})
text = texts[0] // FIXME: handle multiple strings?
t1 = time(0.09)
// FIXME: speed depends on string length
// FIXME: we start waaaaay offscreen to the left but could just start -1 character
xoff = -text.length * csp * t1
while (xoff < width) {
text.forEach((nchr) => {
if (xoff > -csp && nchr != -1 && xoff < width) {
DrawCharacter(chars[nchr],xoff,0,cws,chs,t1,1,1)
}
xoff = xoff + csp
})
}
}
// Draw from the framebuffer
export function render(index) {
Pixel = IndexPixel[index]
rgb(Pixel[0], Pixel[1], Pixel[2])
}
// #!/usr/bin/env ruby
//
// # Usage: $0 < font.json "string" ...
//
// require 'multi_json'
//
// # Output variables
// chars = []
// texts = []
//
// # Character -> chars[] index tracker
// cnums = { ' ' => -1 }
// cnum = 0
//
// font = MultiJson.load(STDIN.read)
//
// ARGV.each do |text|
// tcnums = []
// text.chars.each do |char|
// if cnums.key?(char)
// tcnums.append(cnums[char])
// elsif font.key?(char)
// chars[cnum] = font[char]
// cnums[char] = cnum
// tcnums.append(cnum)
// cnum += 1
// else
// warn('Character not in font: ' + char)
// end
// end
// texts.append(tcnums)
// end
//
// puts 'chars = ' + MultiJson.dump(chars)
// puts 'texts = ' + MultiJson.dump(texts)