Big refactor

Signed-off-by: Martyn Ranyard <m@rtyn.berlin>
This commit is contained in:
Martyn 2020-05-16 20:31:25 +02:00
parent 2202ba2fc0
commit bed25ca03c
10 changed files with 461 additions and 132 deletions

27
internal/patterns/common.go Executable file
View File

@ -0,0 +1,27 @@
package patterns
func Even(number int) bool {
return number %2 == 0
}
func FillPanel(w int, h int, r byte, g byte, b byte) [][]RGBcolor {
grid := make([][]RGBcolor, w)
for i := 0; i < w; i++ {
grid[i] = make([]RGBcolor, h)
}
for x := 0; x < w; x++ {
for y := 0; y < h; y++ {
var rgb [3]byte
rgb[0] = r
rgb[1] = g
rgb[2] = b
grid[x][y] = rgb
}
}
return grid
}
func FillPanelRGB(w int, h int, colour RGBcolor) [][]RGBcolor {
return FillPanel(w,h,colour[0],colour[1],colour[2])
}

49
internal/patterns/experiment.go Executable file
View File

@ -0,0 +1,49 @@
package patterns
import "math"
var sineoffset int = 0
var lastSineSpeed uint16 = 0
func Sinewave(w int, h int, bgColor RGBcolor, fgColor RGBcolor, speed uint16) [][]RGBcolor {
if speed != lastSineSpeed {
lastSineSpeed = speed
sineoffset = 0
}
sineoffset += int(speed/40)
background := FillPanelRGB(w,h,bgColor)
for x := 0; x < w; x++ {
y := int(math.Round((math.Sin(float64(x+sineoffset))+1) / 2 * float64(h-1)))
background[x][y] = fgColor
}
return background
}
var lastSinePos int = 0
func SineChase(w int, h int, bgColor RGBcolor, fgColor RGBcolor, speed uint16) [][]RGBcolor {
if speed != lastSineSpeed {
lastSineSpeed = speed
lastSinePos = 0
}
background := FillPanelRGB(w,h,bgColor)
y := int(math.Round((math.Sin(float64(lastSinePos))+1) / 2 * float64(h-1)))
background[lastSinePos][y] = fgColor
lastSinePos += 1
if lastSinePos >= w {
lastSinePos = 0
}
return background
}
func ZigZag(w int, h int) [][]RGBcolor {
var bgColor = [3]byte{0,0,0}
var fgColor = [3]byte{255,0,0}
//var a = [5]int{2, 4, 6, 8, 10}
background := FillPanelRGB(w,h,bgColor)
for x := 0; x < w; x++ {
y := x % (h-1)
background[x][y] = fgColor
}
return background
}

16
internal/patterns/gradient.go Executable file
View File

@ -0,0 +1,16 @@
package patterns
// gradient returns from fromX to toX fading from (fromR,fromG,fromB) to (toR,toG,toB)
// at least that's the idea.
func Gradient(fromR byte, fromG byte, fromB byte, toR byte, toG byte, toB byte, fromX int, toX int) []byte {
ret := make([]byte, toX*3)
var stepR float32 = (float32(toR) - float32(fromR)) / (float32(toX) - float32(fromX))
var stepG float32 = (float32(toG) - float32(fromG)) / (float32(toX) - float32(fromX))
var stepB float32 = (float32(toB) - float32(fromB)) / (float32(toX) - float32(fromX))
for i := fromX; i < toX*3; i += 3 {
ret[i] = fromR + byte(float32(i/3)*stepR)
ret[i+1] = fromG + byte(float32(i/3)*stepG)
ret[i+2] = fromB + byte(float32(i/3)*stepB)
}
return ret
}

View File

@ -0,0 +1,27 @@
package patterns
import "math/rand"
import "time"
func RedPanel(w int, h int) [][]RGBcolor {
return FillPanel(w, h, 255, 0, 0)
}
var lastColour RGBcolor
var lastIteration uint16 = 0
func RandomColourPanel(w int, h int, speed uint16) [][]RGBcolor {
if lastIteration > 0 {
lastIteration -= 40
}
if lastIteration <= 0 {
r := rand.New(rand.NewSource(time.Now().UnixNano()))
var newColour RGBcolor
newColour[0] = byte(r.Intn(255))
newColour[1] = byte(r.Intn(255))
newColour[2] = byte(r.Intn(255))
lastColour = newColour
lastIteration = speed
}
return FillPanel(w, h, lastColour[0], lastColour[1], lastColour[2])
}

68
internal/patterns/plasma.go Executable file
View File

@ -0,0 +1,68 @@
package patterns
import "math"
type RGBcolor = [3]byte
func plasmaRGBFromVal(val byte) (byte, byte, byte) {
var r byte
var g byte
var b byte
if val < 85 {
r = val * 3
g = 255 - r
b = 0
} else if val < 170 {
b = (val - 85) * 3
r = 255 - b
g = 0
} else {
g = (val - 170) * 3
b = 255 - g
r = 0
}
return r, g, b
}
func LinearPlasma(l int) []byte {
line := make([]byte, l*3)
for i := 0; i < l*3; i += 3 {
val := byte(i % 256)
r, g, b := plasmaRGBFromVal(val)
line[i] = r
line[i+1] = g
line[i+2] = b
}
return line
}
var offset float64 = 0.5
func PlasmaPanel(w int, h int, speed uint16) [][]RGBcolor {
grid := make([][]RGBcolor, w)
for i := 0; i < w; i++ {
grid[i] = make([]RGBcolor, h)
}
scale := math.Pi * 2.0 / float64(w)
step := float64(5 * speed / 40)
offset -= step
if offset < 0 {
offset += 1.0
}
for y := 0; y < h; y++ {
for x := 0; x < w; x++ {
u := math.Cos((float64(x) + offset) * scale)
v := math.Cos((float64(y) + offset) * scale)
w := math.Cos((float64(w) + offset) * scale)
e := (u + v + w + 3.0) / 6.0
r, g, b := plasmaRGBFromVal(byte((offset + e) * 255))
var rgb [3]byte
rgb[0] = r
rgb[1] = g
rgb[2] = b
grid[x][y] = rgb
}
}
return grid
}

View File

@ -0,0 +1,17 @@
package patterns
func AlternateRGBLinear(R0 byte, G0 byte, B0 byte, R1 byte, G1 byte, B1 byte, fromX int, toX int) []byte {
ret := make([]byte, toX*3)
for i := fromX; i < toX*3; i += 3 {
if Even(i) {
ret[i] = R0
ret[i+1] = G0
ret[i+2] = B0
} else {
ret[i] = R1
ret[i+1] = G1
ret[i+2] = B1
}
}
return ret
}

7
internal/queue/queue.go Executable file
View File

@ -0,0 +1,7 @@
package queue
type QueueItem struct {
Effect string
Duration uint16
Speed uint16 //only used by some patterns
}

88
internal/remapping/remapping.go Executable file
View File

@ -0,0 +1,88 @@
package remapping
type RGBcolor = [3]byte
func Even(number int) bool {
return number %2 == 0
}
func Slice512(s []byte) [512]byte {
var ret [512]byte
for i := range s {
if i < 512 {
ret[i] = s[i]
}
}
return ret
}
func SliceUnlenthed(s [512]byte) []byte {
ret := make([]byte, 512)
for i := range s {
ret[i] = s[i]
}
return ret
}
func SliceRearrange(rowwidth int, rows int, alternaterows bool, inslice []byte) [][]byte {
var flippedslice []byte
if alternaterows {
flippedslice = make([]byte, len(inslice)+3)
for r := 0; r < rows; r++ {
rowzero := (r * rowwidth * 3)
if Even(r) {
for c := 0; c < (rowwidth)*3; c += 3 {
flippedslice[rowzero+c] = inslice[rowzero+c]
flippedslice[rowzero+c+1] = inslice[rowzero+c+1]
flippedslice[rowzero+c+2] = inslice[rowzero+c+2]
}
} else {
x := rowwidth * 3
for c := 0; c < (rowwidth)*3; c += 3 {
x = x - 3
flippedslice[rowzero+x] = inslice[rowzero+c]
flippedslice[rowzero+x+1] = inslice[rowzero+c+1]
flippedslice[rowzero+x+2] = inslice[rowzero+c+2]
}
}
}
} else {
flippedslice = inslice
}
currentUniverse := 0
currentUniversePosition := 0
universes := make([][]byte, (len(flippedslice)/510)+1)
currentUniverseSlice := make([]byte, 511)
currentRowReverse := false
for i := range flippedslice {
if currentUniversePosition >= 510 {
universes[currentUniverse] = currentUniverseSlice
if !currentRowReverse {
currentRowReverse = true
} else {
currentRowReverse = false
}
currentUniverse += 1
currentUniversePosition = 0
currentUniverseSlice = make([]byte, 511)
}
currentUniverseSlice[currentUniversePosition] = flippedslice[i]
currentUniversePosition += 1
}
universes[currentUniverse] = currentUniverseSlice
return universes
}
func XYGridToLinear(w int, h int, grid [][]RGBcolor) []byte {
ret := make([]byte, w*h*3)
i := 0
for y := 0; y < h; y++ {
for x := 0; x < w; x++ {
ret[i] = grid[x][y][0]
ret[i+1] = grid[x][y][1]
ret[i+2] = grid[x][y][2]
i += 3
}
}
return ret
}

70
internal/webserver/webserver.go Executable file
View File

@ -0,0 +1,70 @@
package webserver
import (
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
queue "git.martyn.berlin/martyn/LEDController/internal/queue"
"fmt"
"net/http"
"os"
"time"
"strconv"
)
func HealthHandler(response http.ResponseWriter, request *http.Request) {
response.Header().Add("Content-type", "text/plain")
fmt.Fprint(response, "I'm okay jack!")
}
func NotFoundHandler(response http.ResponseWriter, request *http.Request) {
response.WriteHeader(404)
fmt.Fprint(response, "I'm lost!")
}
func RootHandler(response http.ResponseWriter, request *http.Request) {
response.Header().Add("Content-type", "text/plain")
fmt.Fprint(response, "Not implemented")
}
var globalQueue chan queue.QueueItem;
func PatternHandler(response http.ResponseWriter, request *http.Request) {
vars := mux.Vars(request)
response.Header().Add("Content-type", "text/plain")
var e queue.QueueItem
e.Effect = vars["pattern"]
i, _ := strconv.Atoi(vars["duration"])
e.Duration = uint16(i)
_, found := vars["speed"]
if found {
i, _ := strconv.Atoi(vars["speed"])
e.Speed = uint16(i)
fmt.Printf("Speed passed %d\n",e.Speed)
} else {
e.Speed = 40
}
globalQueue <- e
fmt.Fprint(response, "OKAY")
}
func HandleHTTP(queueChannel chan queue.QueueItem) {
globalQueue = queueChannel
r := mux.NewRouter()
loggedRouter := handlers.LoggingHandler(os.Stdout, r)
r.NotFoundHandler = http.HandlerFunc(NotFoundHandler)
r.HandleFunc("/", RootHandler)
r.HandleFunc("/healthz", HealthHandler)
r.HandleFunc("/pattern/{pattern}/{duration}", PatternHandler)
r.HandleFunc("/pattern/{pattern}/{duration}/{speed}", PatternHandler)
http.Handle("/", r)
srv := &http.Server{
Handler: loggedRouter,
Addr: "0.0.0.0:5353",
WriteTimeout: 15 * time.Second,
ReadTimeout: 15 * time.Second,
}
fmt.Println("Listening on 0.0.0.0:5353")
srv.ListenAndServe()
}

214
main.go
View File

@ -1,154 +1,114 @@
package main
import (
"fmt"
"log"
"time"
"fmt"
webserver "git.martyn.berlin/martyn/LEDController/internal/webserver"
queue "git.martyn.berlin/martyn/LEDController/internal/queue"
remapping "git.martyn.berlin/martyn/LEDController/internal/remapping"
patterns "git.martyn.berlin/martyn/LEDController/internal/patterns"
"github.com/Hundemeier/go-sacn/sacn"
)
func even(number int) bool {
return number%2 == 0
}
type RGBcolor = [3]byte
func slice_rearrange(rowwidth int, rows int, alternaterows bool, inslice []byte) [][]byte {
var flippedslice []byte
if alternaterows {
flippedslice = make([]byte, len(inslice)+3)
for r := 0; r < rows; r++ {
rowzero := (r*rowwidth*3)
if even(r) {
for c := 0; c < (rowwidth)*3; c+=3 {
flippedslice[rowzero+c] = inslice[rowzero+c]
flippedslice[rowzero+c+1] = inslice[rowzero+c+1]
flippedslice[rowzero+c+2] = inslice[rowzero+c+2]
}
} else {
x := rowwidth*3
for c := 0; c < (rowwidth)*3; c+=3 {
x = x-3
fmt.Println(x)
flippedslice[rowzero+x] = inslice[rowzero+c]
flippedslice[rowzero+x+1] = inslice[rowzero+c+1]
flippedslice[rowzero+x+2] = inslice[rowzero+c+2]/*
flippedslice[rowzero+c] = inslice[rowzero+(((rowwidth-1)*3)-c)]
flippedslice[rowzero+c+1] = inslice[rowzero+(((rowwidth-1)*3)-c)-1]
flippedslice[rowzero+c+2] = inslice[rowzero+(((rowwidth-1)*3)-c)-2]*/
}
}
}
} else {
flippedslice = inslice
}
currentUniverse := 0
currentUniversePosition := 0
universes := make([][]byte, (len(flippedslice)/510)+1)
currentUniverseSlice := make([]byte, 511)
currentRowReverse := false;
for i := range(flippedslice) {
if currentUniversePosition >= 510 {
fmt.Println("Reached end of universe!")
universes[currentUniverse] = currentUniverseSlice
if (!currentRowReverse) {
currentRowReverse = true
} else {
currentRowReverse = false
}
currentUniverse += 1
currentUniversePosition = 0
currentUniverseSlice = make([]byte, 511)
}
currentUniverseSlice[currentUniversePosition] = flippedslice[i]
currentUniversePosition += 1
}
universes[currentUniverse] = currentUniverseSlice
return universes
}
var currentFrame [2][512]byte
var channels [2]chan<-[512]byte
var sema = make(chan struct{}, 1) // a binary semaphore guarding currentFrame
func alternate(R0 byte, G0 byte, B0 byte, R1 byte, G1 byte, B1 byte, fromX int, toX int) []byte {
ret := make([]byte, toX*3);
for i := fromX; i < toX*3; i+=3 {
if even(i) {
ret[i] = R0
ret[i+1] = G0
ret[i+2] = B0
} else {
ret[i] = R1
ret[i+1] = G1
ret[i+2] = B1
}
}
return ret
}
var currentEffect queue.QueueItem;
var globalEffectChannel = make(chan queue.QueueItem, 1024)
// gradient returns from fromX to toX fading from (fromR,fromG,fromB) to (toR,toG,toB)
// at least that's the idea.
func gradient(fromR byte, fromG byte, fromB byte, toR byte, toG byte, toB byte, fromX int, toX int) []byte {
ret := make([]byte, toX*3);
var stepR float32 = (float32(toR) - float32(fromR)) / (float32(toX) - float32(fromX))
var stepG float32 = (float32(toG) - float32(fromG)) / (float32(toX) - float32(fromX))
var stepB float32 = (float32(toB) - float32(fromB)) / (float32(toX) - float32(fromX))
for i := fromX; i < toX*3; i+=3 {
ret[i] = fromR+byte(float32(i/3)*stepR)
ret[i+1] = fromG+byte(float32(i/3)*stepG)
ret[i+2] = fromB+byte(float32(i/3)*stepB)
}
return ret
}
func slice512(s []byte) [512]byte {
var ret [512]byte
for i := range(s) {
if i < 512 {
ret[i] = s[i]
func foreverLoop() {
for /*ever*/ {
time.Sleep(40 * time.Millisecond) //25fps
for u := 0; u < 2; u++ {
sema <- struct{}{} // acquire token
channels[u] <- currentFrame[u]
<- sema
}
}
return ret
}
func sliceUnlenthed(s [512]byte) []byte {
ret := make([]byte, 512)
for i := range(s) {
ret[i] = s[i]
}
return ret
}
func main() {
go func() {
fmt.Printf("Starting webserver on port %s\n", "5353")
webserver.HandleHTTP(globalEffectChannel)
}()
//instead of "" you could provide an ip-address that the socket should bind to
trans, err := sacn.NewTransmitter("", [16]byte{1, 2, 3}, "test")
trans, err := sacn.NewTransmitter("", [16]byte{1, 2}, "test")
if err != nil {
log.Fatal(err)
}
//activates the first universe
ch1, err := trans.Activate(1)
if err != nil {
log.Fatal(err)
}
ch2, err := trans.Activate(2)
if err != nil {
log.Fatal(err)
}
//activates the universes
for i := 0; i < 2; i++ {
channels[i], err = trans.Activate(uint16(i+1))
//deactivate the channel on exit
defer close(ch1)
defer close(ch2)
defer close(channels[i])
trans.SetMulticast(uint16(i+1), false) //this specific setup will not multicast on windows,
trans.SetDestinations(uint16(i+1), []string{"192.168.1.139"})
}
//set a unicast destination, and/or use multicast
trans.SetMulticast(1, false)//this specific setup will not multicast on windows,
//because no bind address was provided
// Plasma for start frame
rearranged := remapping.SliceRearrange(68, 4, true, remapping.XYGridToLinear(68, 4, patterns.PlasmaPanel(68, 4, 40)))
currentFrame[0] = remapping.Slice512(rearranged[0])
currentFrame[1] = remapping.Slice512(rearranged[1])
var e queue.QueueItem
e.Effect = "red"
e.Duration = 40 * 50
globalEffectChannel <- e
e.Effect = "plasma"
e.Duration = 0
e.Speed = 40
globalEffectChannel <- e
//set some example ip-addresses
trans.SetDestinations(1, []string{"192.168.1.139"})
trans.SetMulticast(2, false)//this specific setup will not multicast on windows,
trans.SetDestinations(2, []string{"192.168.1.139"})
//send some random data for 10 seconds
for i := 0; i < 20; i++ {
channels := slice_rearrange(68,4,true,gradient(255,255,255,0,0,0,0,272))
ch1 <- slice512(channels[0])
ch2 <- slice512(channels[1])
time.Sleep(500 * time.Millisecond)
go func() {
for /*ever*/ {
if currentEffect.Duration > 0 {
currentEffect.Duration -= 40
} else {
if len(globalEffectChannel) > 0 {
currentEffect = <- globalEffectChannel
}
}
var rearranged [][]byte
switch currentEffect.Effect {
case "line":
rearranged = remapping.SliceRearrange(68, 4, true, remapping.XYGridToLinear(68, 4, patterns.ZigZag(68, 4)))
case "plasma":
rearranged = remapping.SliceRearrange(68, 4, true, remapping.XYGridToLinear(68, 4, patterns.PlasmaPanel(68, 4, currentEffect.Speed)))
case "red":
rearranged = remapping.SliceRearrange(68, 4, false, remapping.XYGridToLinear(68, 4, patterns.RedPanel(68,4)))
case "random":
rearranged = remapping.SliceRearrange(68, 4, false, remapping.XYGridToLinear(68, 4, patterns.RandomColourPanel(68,4,currentEffect.Speed)))
case "linearplasma":
rearranged = remapping.SliceRearrange(68, 4, false, patterns.LinearPlasma(68*4))
case "gradientred":
rearranged = remapping.SliceRearrange(68, 4, false, patterns.Gradient(255, 0, 0, 0, 0, 255, 0, 68*4))
case "sine":
var black patterns.RGBcolor
var red patterns.RGBcolor
red[0] = 255
rearranged = remapping.SliceRearrange(68, 4, true, remapping.XYGridToLinear(68, 4, patterns.Sinewave(68, 4, black, red, currentEffect.Speed)))
case "sinechase":
var black patterns.RGBcolor
var red patterns.RGBcolor
red[0] = 255
rearranged = remapping.SliceRearrange(68, 4, true, remapping.XYGridToLinear(68, 4, patterns.SineChase(68, 4, black, red, currentEffect.Speed)))
default:
rearranged = remapping.SliceRearrange(68, 4, false, remapping.XYGridToLinear(68, 4, patterns.FillPanel(68,4, 128,0,128)))
}
//rearranged := remapping.SliceRearrange(68,4,true,linearPlasma(68*4))
sema <- struct{}{} // acquire token
currentFrame[0] = remapping.Slice512(rearranged[0])
currentFrame[1] = remapping.Slice512(rearranged[1])
<- sema
time.Sleep(40 * time.Millisecond)
}
}()
foreverLoop();
}