Add QLab OSC client library with TCP/SLIP transport
This commit is contained in:
152
lib/qlab/osc.go
Normal file
152
lib/qlab/osc.go
Normal file
@@ -0,0 +1,152 @@
|
||||
package qlab
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math"
|
||||
)
|
||||
|
||||
func oscPad(n int) int {
|
||||
return (4 - n%4) % 4
|
||||
}
|
||||
|
||||
func buildOSC(addr string, args ...any) []byte {
|
||||
var buf []byte
|
||||
|
||||
buf = append(buf, []byte(addr)...)
|
||||
buf = append(buf, 0)
|
||||
for range oscPad(len(addr) + 1) {
|
||||
buf = append(buf, 0)
|
||||
}
|
||||
|
||||
typetag := ","
|
||||
for _, arg := range args {
|
||||
switch arg.(type) {
|
||||
case int32:
|
||||
typetag += "i"
|
||||
case float32:
|
||||
typetag += "f"
|
||||
case string:
|
||||
typetag += "s"
|
||||
case []byte:
|
||||
typetag += "b"
|
||||
case int64:
|
||||
typetag += "h"
|
||||
case float64:
|
||||
typetag += "d"
|
||||
}
|
||||
}
|
||||
buf = append(buf, []byte(typetag)...)
|
||||
buf = append(buf, 0)
|
||||
for range oscPad(len(typetag) + 1) {
|
||||
buf = append(buf, 0)
|
||||
}
|
||||
|
||||
for _, arg := range args {
|
||||
switch v := arg.(type) {
|
||||
case int32:
|
||||
buf = binary.BigEndian.AppendUint32(buf, uint32(v))
|
||||
case float32:
|
||||
buf = binary.BigEndian.AppendUint32(buf, math.Float32bits(v))
|
||||
case string:
|
||||
buf = append(buf, []byte(v)...)
|
||||
buf = append(buf, 0)
|
||||
for range oscPad(len(v) + 1) {
|
||||
buf = append(buf, 0)
|
||||
}
|
||||
case []byte:
|
||||
buf = binary.BigEndian.AppendUint32(buf, uint32(len(v)))
|
||||
buf = append(buf, v...)
|
||||
for range oscPad(len(v)) {
|
||||
buf = append(buf, 0)
|
||||
}
|
||||
case int64:
|
||||
buf = binary.BigEndian.AppendUint64(buf, uint64(v))
|
||||
case float64:
|
||||
buf = binary.BigEndian.AppendUint64(buf, math.Float64bits(v))
|
||||
}
|
||||
}
|
||||
|
||||
return buf
|
||||
}
|
||||
|
||||
func parseOSC(data []byte) (addr string, args []any, err error) {
|
||||
if len(data) < 4 {
|
||||
return "", nil, fmt.Errorf("osc: message too short")
|
||||
}
|
||||
|
||||
end := 0
|
||||
for end < len(data) && data[end] != 0 {
|
||||
end++
|
||||
}
|
||||
addr = string(data[:end])
|
||||
pos := end + 1 + oscPad(end+1)
|
||||
|
||||
if pos >= len(data) || data[pos] != ',' {
|
||||
return addr, nil, nil
|
||||
}
|
||||
|
||||
ttEnd := pos
|
||||
for ttEnd < len(data) && data[ttEnd] != 0 {
|
||||
ttEnd++
|
||||
}
|
||||
typetag := string(data[pos+1 : ttEnd])
|
||||
pos = ttEnd + 1 + oscPad(ttEnd-pos)
|
||||
|
||||
for _, t := range typetag {
|
||||
switch t {
|
||||
case 'i':
|
||||
if pos+4 > len(data) {
|
||||
return addr, args, fmt.Errorf("osc: truncated int32")
|
||||
}
|
||||
args = append(args, int32(binary.BigEndian.Uint32(data[pos:])))
|
||||
pos += 4
|
||||
case 'f':
|
||||
if pos+4 > len(data) {
|
||||
return addr, args, fmt.Errorf("osc: truncated float32")
|
||||
}
|
||||
args = append(args, math.Float32frombits(binary.BigEndian.Uint32(data[pos:])))
|
||||
pos += 4
|
||||
case 's':
|
||||
end := pos
|
||||
for end < len(data) && data[end] != 0 {
|
||||
end++
|
||||
}
|
||||
args = append(args, string(data[pos:end]))
|
||||
pos = end + 1 + oscPad(end-pos+1)
|
||||
case 'b':
|
||||
if pos+4 > len(data) {
|
||||
return addr, args, fmt.Errorf("osc: truncated blob size")
|
||||
}
|
||||
size := int(binary.BigEndian.Uint32(data[pos:]))
|
||||
pos += 4
|
||||
if pos+size > len(data) {
|
||||
return addr, args, fmt.Errorf("osc: truncated blob")
|
||||
}
|
||||
b := make([]byte, size)
|
||||
copy(b, data[pos:pos+size])
|
||||
args = append(args, b)
|
||||
pos += size + oscPad(size)
|
||||
case 'h':
|
||||
if pos+8 > len(data) {
|
||||
return addr, args, fmt.Errorf("osc: truncated int64")
|
||||
}
|
||||
args = append(args, int64(binary.BigEndian.Uint64(data[pos:])))
|
||||
pos += 8
|
||||
case 'd':
|
||||
if pos+8 > len(data) {
|
||||
return addr, args, fmt.Errorf("osc: truncated float64")
|
||||
}
|
||||
args = append(args, math.Float64frombits(binary.BigEndian.Uint64(data[pos:])))
|
||||
pos += 8
|
||||
case 'T':
|
||||
args = append(args, true)
|
||||
case 'F':
|
||||
args = append(args, false)
|
||||
case 'N':
|
||||
args = append(args, nil)
|
||||
}
|
||||
}
|
||||
|
||||
return addr, args, nil
|
||||
}
|
||||
Reference in New Issue
Block a user